From 887d34499c34e196068755dffef4049deaddb205 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 16 Aug 2025 11:21:10 -0700 Subject: [PATCH 01/42] add bridgeui folder for HALUI --- lib/python/bridgeui/__init__.py | 3 + lib/python/bridgeui/bridge.py | 302 ++++++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 lib/python/bridgeui/__init__.py create mode 100644 lib/python/bridgeui/bridge.py diff --git a/lib/python/bridgeui/__init__.py b/lib/python/bridgeui/__init__.py new file mode 100644 index 00000000000..b28b04f6431 --- /dev/null +++ b/lib/python/bridgeui/__init__.py @@ -0,0 +1,3 @@ + + + diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py new file mode 100644 index 00000000000..54ea67d225e --- /dev/null +++ b/lib/python/bridgeui/bridge.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 + +import os +import sys +import time + +import json +import signal + +import hal +from qtvcp.qt_halobjects import Qhal +from common.iniinfo import _IStat as IStatParent +from common import logger + +# LOG is for running code logging +LOG = logger.initBaseLogger('HAL bridge', log_file=None, + log_level=logger.WARNING, logToFile=False) + +# Force the log level for this module +LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL + +try: + import zmq + ZMQ = True +except: + LOG.critical('ZMQ python library problem - Is python3-zmq installed?') + ZMQ = False + +class Info(IStatParent): + _instance = None + _instanceNum = 0 + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = IStatParent.__new__(cls, *args, **kwargs) + return cls._instance + +# Instantiate the library with global reference + + +class Bridge(object): + def __init__(self, readAddress = "tcp://127.0.0.1:5690", + writeAddress = "tcp://127.0.0.1:5691"): + super(Bridge, self).__init__() + self.INFO = Info() + + self.currentSelectedAxis = 'None' + self.axesSelected = {'X':0,'Y':0,'Z':0,'A':0,'B':0,'C':0, + 'U':0,'V':0,'W':0} + self.readAddress = readAddress + self.writeAddress = writeAddress + LOG.debug('read port: {}'.format(readAddress)) + LOG.debug('write port: {}'.format(writeAddress)) + + self.readTopic = "" + self.writeTopic = "STATUSREQUEST" + + # catch control c and terminate signals + signal.signal(signal.SIGTERM, self.shutdown) + signal.signal(signal.SIGINT, self.shutdown) + + self.init_hal() + if ZMQ: + self.init_read() + self.init_write() + + def update(self, *arg): + print(self, arg) + raw=arg[0]; row=arg[1];column=arg[2];state=arg[3] + LOG.debug('raw {}, row {}, col {}, state {}'.format(raw,row,column,state)) + print ('raw',raw,'row:',row,'column:',column,'state:',state) + self.writeMsg('set_selected_axis','Y') + self.activeJoint.set(10) + + def init_hal(self): + self.comp = h = hal.component("bridge") + QHAL = Qhal(comp=self.comp, hal=hal) + + self.jogRate = QHAL.newpin("jog-rate", hal.HAL_FLOAT, hal.HAL_OUT) + self.jogRateIn = QHAL.newpin("jog-rate-in", hal.HAL_FLOAT, hal.HAL_IN) + self.jogRateIn.pinValueChanged.connect(self.pinChanged) + + self.jogRateAngular = QHAL.newpin("jog-rate-angular", hal.HAL_FLOAT, hal.HAL_OUT) + self.jogRateAngularIn = QHAL.newpin("jog-rate-angular-in", hal.HAL_FLOAT, hal.HAL_OUT) + self.jogRateAngularIn.pinValueChanged.connect(self.pinChanged) + + self.jogIncrement = QHAL.newpin("jog-increment", hal.HAL_FLOAT, hal.HAL_OUT) + self.jogIncrementAngular = QHAL.newpin("jog-increment-angular", hal.HAL_FLOAT, hal.HAL_OUT) + self.activeJoint = QHAL.newpin('joint-selected', hal.HAL_S32, hal.HAL_OUT) + + for i in (self.INFO.AVAILABLE_AXES): + let = i.lower() + # input + self['axis{}Select'.format(let)] = QHAL.newpin('axis-%s-select'%let, hal.HAL_BIT, hal.HAL_IN) + self['axis{}Select'.format(let)].pinValueChanged.connect(self.pinChanged) + # output + self['Axis{}IsSelected'.format(let)] = QHAL.newpin('axis-%s-is-selected'%let, hal.HAL_BIT, hal.HAL_OUT) + + self.cycle_start = QHAL.newpin('cycle-start-in',QHAL.HAL_BIT, QHAL.HAL_IN) + self.cycle_start.pinValueChanged.connect(self.pinChanged) + self.cycle_pause = QHAL.newpin('cycle-pause-in',QHAL.HAL_BIT, QHAL.HAL_IN) + self.cycle_pause.pinValueChanged.connect(self.pinChanged) + + for i in self.INFO.MDI_COMMAND_DICT: + LOG.debug('{} {}'.format(i,self.INFO.MDI_COMMAND_DICT.get(i))) + self[i] = QHAL.newpin('ini-mdi-cmd-{}'.format(i),QHAL.HAL_BIT, QHAL.HAL_IN) + self[i].pinValueChanged.connect(self.runMacroChanged) + + for i in self.INFO.INI_MACROS: + name = i.split()[0] + LOG.debug('{} {}'.format(name,i)) + self[name] = QHAL.newpin('ini-macro-cmd-{}'.format(name),QHAL.HAL_BIT, QHAL.HAL_IN) + self[name].pinValueChanged.connect(self.runMacroChanged) + + QHAL.setUpdateRate(100) + h.ready() + + def init_write(self): + context = zmq.Context() + self.writeSocket = context.socket(zmq.PUB) + self.writeSocket.bind(self.writeAddress) + + def init_read(self): + # ZeroMQ Context + self.readContext = zmq.Context() + + # Define the socket using the "Context" + self.readSocket = self.readContext.socket(zmq.SUB) + + # Define subscription and messages with topic to accept. + self.readSocket.setsockopt_string(zmq.SUBSCRIBE, self.readTopic) + self.readSocket.connect(self.readAddress) + + # callback from ZMQ read socket + def readMsg(self, msg): + if self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: + while self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: + # get raw message + topic, data = self.readSocket.recv_multipart() + # convert from json object to python object + y = json.loads(data) + self. action(y.get('MESSAGE'),y.get('ARGS')) + + # set our output HAL pins from messages from hal_glib + def action(self, msg, data): + LOG.debug('{} {}'.format(msg, data)) + if msg == 'jograte-changed': + self.jogRate.set(float(data[0])) + elif msg == 'jograte-angular-changed': + self.jogRateAngular.set(float(data[0])) + elif msg == 'jogincrements-changed': + self.jogIncrement.set(float(data[0][0])) + elif msg == 'jogincrement-angular-changed': + self.jogIncremtAngular.set(float(data[0][0])) + elif msg == 'joint-selection-changed': + self.activeJoint.set(int(data[0])) + elif msg == 'axis-selection-changed': + flag = 1 + for i in(self.INFO.AVAILABLE_AXES): + if data[0] == i: + state = True + self.currentSelectedAxis = data[0] + flag = 0 + else: + state = False + self['Axis{}IsSelected'.format(i.lower())].set(state) + self.axesSelected[i] = int(state) + if flag: + self.currentSelectedAxis = 'None' + #print ('axis state', self.axesSelected) + + # send msg to hal_glib + def writeMsg(self, msg, data): + print('Write Msg called') + if ZMQ: + topic = self.writeTopic + message = json.dumps({'FUNCTION':msg,'ARGS':data}) + LOG.debug('Sending ZMQ Message:{} {}'.format(topic, message)) + self.writeSocket.send_multipart( + [bytes(topic.encode('utf-8')), + bytes((message).encode('utf-8'))]) + + # callback from HAL input pins + def pinChanged(self, pinObject, value): + LOG.debug('Pin name:{} changed value to {}'.format(pinObject.text(), value)) + #print(type(value)) + # Axis selction change request + if 'select' in pinObject.text(): + if bool(value) == False: + pass + #print('Not true state') + return + for i in (self.INFO.AVAILABLE_AXES): + if '-{}-'.format(i.lower()) in pinObject.text(): + self.writeMsg('set_selected_axis', i) + break + else: + if 'None' in pinObject.text(): + self.writeMsg('set_selected_axis', '') + + # cycle start + elif self.cycle_start == pinObject: + if value: + self.writeMsg('request_cycle_start', value) + + # cycle pause + elif self.cycle_pause == pinObject: + #if value: + self.writeMsg('request_cycle_pause', value) + + # linear jog rate + elif self.jogRateIn == pinObject: + self.writeMsg('set_jograte', value) + + # angular jog rate + elif self.jogRateAngularIn == pinObject: + self.writeMsg('set_jograte_angular', value) + + # catch all default + else: + self.writeMsg(pinObject.text(),value) + + # callback; request to run a specific macro + def runMacroChanged(self, pinObject, value): + LOG.debug('Macro Pin name:{} changed value to {}'.format(pinObject.text(), value)) + #LOG.debug(type(value)) + name = pinObject.text().strip('macro-cmd-') + if value: + self.writeMsg('request_macro_call', name) + + def shutdown(self,signum=None,stack_frame=None): + LOG.debug('shutdown') + global app + app.quit() + + def getJogRate(self): + return self.jogRate.get() + def setJogRate(self, value): + self.writeMsg('set_jograte', value) + + def getJogRateAngular(self): + return self.jogRateAngular.get() + def setJogRateAngular(self, value): + self.writeMsg('set_jograte_angular', value) + + def getSelectedAxis(self): + name = self.currentSelectedAxis + if name == 'None': + index = -1 + else: + index = 'XYZABCUVW'.index(name) + return index + def setSelectedAxis(self, value): + if value < 0: + letter = 'None' + else: + letter ='XYZABCUVW'[value] + self.writeMsg('set_selected_axis', letter) + + def isAxisSelected(self, index): + letter = 'XYZABCUVW'[index] + return int(self.axesSelected[letter]) + + def __getitem__(self, item): + return getattr(self, item) + def __setitem__(self, item, value): + return setattr(self, item, value) + +if __name__ == "__main__": + import sys + import getopt + from PyQt5.QtWidgets import QApplication + + letters = 'dh' # the : means an argument needs to be passed after the letter + keywords = ['readport=', 'writeport=' ] # the = means that a value is expected after + # the keyword + + opts, extraparam = getopt.getopt(sys.argv[1:],letters,keywords) + # starts at the second element of argv since the first one is the script name + # extraparms are extra arguments passed after all option/keywords are assigned + # opts is a list containing the pair "option"/"value" + + readport = "tcp://127.0.0.1:5690" + writeport = "tcp://127.0.0.1:5691" + + for o,p in opts: + if o in ['-d']: + LOG.setLevel(logger.DEBUG) + elif o in ['--readport']: + readport = p + elif o in ['--writeport']: + writeport = p + elif o in ['-h','--help']: + print('HAL bridge: GUI to HAL interface using ZMQ') + print('option "-d" = debug print mode') + print('option "--readport=" read socket address') + print('option "--writeport=" write socket address') + print('example: hal_bridge -d --readport=tcp://127.0.0.1:5692') + + app = QApplication(sys.argv) + test = Bridge(readport, writeport) + sys.exit(app.exec_()) From 8e37015ed37c047ee2627772e4437ef702acebef Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 2 Aug 2025 19:38:15 -0700 Subject: [PATCH 02/42] halui initial testing of embedded python halui --- .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 2 +- src/emc/usr_intf/Submakefile | 3 +- src/emc/usr_intf/halui.cc | 244 ++++++++++++++++-- 3 files changed, 226 insertions(+), 23 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index 327d051bd5b..a00b52ab427 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini @@ -151,7 +151,7 @@ SPINDLES = 1 [HAL] HALUI = halui -HALBRIDGE = hal_bridge +#HALBRIDGE = hal_bridge # loads the HAL machine simulation HALFILE = core_sim.hal diff --git a/src/emc/usr_intf/Submakefile b/src/emc/usr_intf/Submakefile index 0b1df44be40..2efe36b90a9 100644 --- a/src/emc/usr_intf/Submakefile +++ b/src/emc/usr_intf/Submakefile @@ -37,5 +37,6 @@ TARGETS += ../bin/linuxcnclcd ../bin/halui: $(call TOOBJS, $(HALUISRCS)) ../lib/liblinuxcnc.a ../lib/liblinuxcncini.so.0 ../lib/libnml.so.0 ../lib/liblinuxcnchal.so.0 ../lib/libtooldata.so.0 $(ECHO) Linking $(notdir $@) - $(Q)$(CXX) $(CXXFLAGS) -o $@ $(ULFLAGS) $^ $(LDFLAGS) + $(Q)$(CXX) $(CXXFLAGS) -o $@ $(ULFLAGS) $^ $(LDFLAGS) $(PYTHON_LIBS) $(PYTHON_EXTRA_LIBS) TARGETS += ../bin/halui + diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index e9e23735c94..d5e931a4576 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -39,6 +39,10 @@ #include #include "tooldata.hh" +#define PY_SSIZE_T_CLEAN +#include +#include + /* Using halui: see the man page */ static int axis_mask = 0; @@ -230,9 +234,16 @@ typedef halui_str_base halui_str; typedef halui_str_base local_halui_str; #pragma GCC diagnostic pop +PyObject *pModule, *pFuncRead, *pFuncWrite, *pInstance, *pClass; +PyObject *pValue; + static halui_str *halui_data; static local_halui_str old_halui_data; +static double lastjogspeed = 0; +static double internaljogspeed = 0; +static int lastaxis = -1; + static char *mdi_commands[MDI_MAX]; static int num_mdi_commands=0; static int have_home_all = 0; @@ -1675,7 +1686,101 @@ static bool jogging_selected_axis(local_halui_str &hal) { return (hal.ajog_plus[EMCMOT_MAX_AXIS] || hal.ajog_minus[EMCMOT_MAX_AXIS]); } +static double write_msg_axis_get_jogspeed() { + double jspd = 0; + // check socket messages for jogspeed + pFuncRead = PyObject_GetAttrString(pInstance, "getJogRate"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: getJogRate function failed: returned NULL\n"); + jspd = 0; + }else{ + if (PyFloat_Check(pValue)) { + jspd = PyFloat_AsDouble(pValue); + if (PyErr_Occurred()) { + jspd = 0; + // Handle conversion error + PyErr_Print(); + // Clear the error state if needed + PyErr_Clear(); + } + } + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncRead); + return jspd; +} + +static void write_msg_axis_jogspeed(double speed) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "setJogRate"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "d", speed); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: writeMsg function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + }else{ + Py_DECREF(pValue); + } + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); +} + +static int write_msg_get_axis_selected() { + int value = 0; + // check socket messages for jogspeed + pFuncRead = PyObject_GetAttrString(pInstance, "getSelectedAxis"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + if (pValue == NULL){ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "Halui Bridge: getSelectAxis function failed: returned NULL\n"); + value = -1; + }else{ + if (PyLong_Check(pValue)) { + value = (int) PyLong_AsLong(pValue); + //fprintf(stderr, "axis value %d\n",value); + if (PyErr_Occurred()) { + value = -1; + // Handle conversion error + PyErr_Print(); + // Clear the error state if needed + PyErr_Clear(); + } + } + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncRead); + return value; +} + +static void write_msg_axis_changed( int axis) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "setSelectedAxis"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "i", axis); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: writeMsg function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + }else{ + Py_DECREF(pValue); + } + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); +} // this function looks if any of the hal pins has changed // and sends appropriate messages if so static void check_hal_changes() @@ -1686,13 +1791,30 @@ static void check_hal_changes() hal_bit_t bit; int js; hal_float_t floatt; + double jogspeed; int jjog_speed_changed; int ajog_speed_changed; + int is_any_axis_selected, deselected; local_halui_str new_halui_data_mutable; copy_hal_data(*halui_data, new_halui_data_mutable); const local_halui_str &new_halui_data = new_halui_data_mutable; + // read socket messages + pFuncRead = PyObject_GetAttrString(pInstance, "readMsg"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallFunction(pFuncRead, "O", pClass); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: readMsg function failed: returned NULL\n"); + } + if (PyErr_Occurred()) PyErr_Print(); + }else{ + if (PyErr_Occurred()){ + PyErr_Print(); + } + fprintf(stderr, "Bridge: Failed python function"); + exit(1); + } //check if machine_on pin has changed (the rest work exactly the same) if (check_bit_changed(new_halui_data.machine_on, old_halui_data.machine_on) != 0) @@ -1908,11 +2030,23 @@ static void check_hal_changes() // re-start the jog with the new speed if (fabs(old_halui_data.ajog_speed - new_halui_data.ajog_speed) > 0.00001) { old_halui_data.ajog_speed = new_halui_data.ajog_speed; + internaljogspeed = new_halui_data.ajog_speed; ajog_speed_changed = 1; + write_msg_axis_jogspeed(internaljogspeed); } else { ajog_speed_changed = 0; } + // check socket messages for jogspeed + jogspeed = write_msg_axis_get_jogspeed(); + if (fabs(jogspeed - lastjogspeed) > 0.00001) { + ajog_speed_changed = 1; + lastjogspeed = jogspeed; + internaljogspeed = jogspeed; + fprintf(stderr, "JogRate value = %f\n", jogspeed ); + } + + for (joint=0; joint < num_joints; joint++) { if (check_bit_changed(new_halui_data.joint_home[joint], old_halui_data.joint_home[joint]) != 0) sendHome(joint); @@ -1992,47 +2126,57 @@ static void check_hal_changes() } } + + // check thru axes + is_any_axis_selected = 0; + deselected = 0; for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { if ( !(axis_mask & (1 << axis_num)) ) { continue; } + + // axis jog - bit = new_halui_data.ajog_minus[axis_num]; if ((bit != old_halui_data.ajog_minus[axis_num]) || (bit && ajog_speed_changed)) { if (bit != 0) - sendJogCont(axis_num,-new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(axis_num,-internaljogspeed,JOGTELEOP); else sendJogStop(axis_num,JOGTELEOP); old_halui_data.ajog_minus[axis_num] = bit; } + // axis jog + bit = new_halui_data.ajog_plus[axis_num]; if ((bit != old_halui_data.ajog_plus[axis_num]) || (bit && ajog_speed_changed)) { if (bit != 0) - sendJogCont(axis_num,new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(axis_num,internaljogspeed,JOGTELEOP); else sendJogStop(axis_num,JOGTELEOP); old_halui_data.ajog_plus[axis_num] = bit; } + // axis jog analog floatt = new_halui_data.ajog_analog[axis_num]; bit = (fabs(floatt) > new_halui_data.ajog_deadband); if ((floatt != old_halui_data.ajog_analog[axis_num]) || (bit && ajog_speed_changed)) { if (bit) - sendJogCont(axis_num,(new_halui_data.ajog_speed) * (new_halui_data.ajog_analog[axis_num]),JOGTELEOP); + sendJogCont(axis_num,(internaljogspeed) * (new_halui_data.ajog_analog[axis_num]),JOGTELEOP); else sendJogStop(axis_num,JOGTELEOP); old_halui_data.ajog_analog[axis_num] = floatt; } + // axis jog + increment bit = new_halui_data.ajog_increment_plus[axis_num]; if (bit != old_halui_data.ajog_increment_plus[axis_num]) { if (bit) - sendJogIncr(axis_num, new_halui_data.ajog_speed, new_halui_data.ajog_increment[axis_num],JOGTELEOP); + sendJogIncr(axis_num, internaljogspeed, new_halui_data.ajog_increment[axis_num],JOGTELEOP); old_halui_data.ajog_increment_plus[axis_num] = bit; } + // jog a- increment bit = new_halui_data.ajog_increment_minus[axis_num]; if (bit != old_halui_data.ajog_increment_minus[axis_num]) { if (bit) - sendJogIncr(axis_num, new_halui_data.ajog_speed, -(new_halui_data.ajog_increment[axis_num]),JOGTELEOP); + sendJogIncr(axis_num, internaljogspeed, -(new_halui_data.ajog_increment[axis_num]),JOGTELEOP); old_halui_data.ajog_increment_minus[axis_num] = bit; } @@ -2040,30 +2184,46 @@ static void check_hal_changes() bit = new_halui_data.axis_nr_select[axis_num]; if (bit != old_halui_data.axis_nr_select[axis_num]) { if (bit != 0) { + is_any_axis_selected = 1; *halui_data->axis_selected = axis_num; + write_msg_axis_changed(axis_num); aselect_changed = axis_num; // flag that we changed the selected axis - } + }else{ + deselected = 1; + } old_halui_data.axis_nr_select[axis_num] = bit; - } + } + } + // last axis has been deselected - no axis is selected now + if (is_any_axis_selected == 0 and deselected == 1) { + write_msg_axis_changed(-1); + } + + // check socket messages for axis selection change + int value = write_msg_get_axis_selected(); + if (value != lastaxis){ + aselect_changed = lastaxis = value; } if (aselect_changed >= 0) { - for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { - if ( !(axis_mask & (1 << axis_num)) ) { continue; } - if (axis_num != aselect_changed) { - *(halui_data->axis_is_selected[axis_num]) = 0; + fprintf(stderr, "halui Bridge: axis selected %d\n",aselect_changed); + for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { + if ( !(axis_mask & (1 << axis_num)) ) { continue; } + if (axis_num != aselect_changed) { + *(halui_data->axis_is_selected[axis_num]) = 0; if (jogging_selected_axis(old_halui_data) && !jogging_axis(old_halui_data, axis_num)) { - sendJogStop(axis_num,JOGTELEOP); + sendJogStop(axis_num,JOGTELEOP); } } else { - *(halui_data->axis_is_selected[axis_num]) = 1; + *(halui_data->axis_is_selected[axis_num]) = 1; if (*halui_data->ajog_plus[num_axes]) { - sendJogCont(axis_num, new_halui_data.ajog_speed,JOGTELEOP); + fprintf(stderr, "halui: jog plus: %d\n",num_axes); + sendJogCont(axis_num, internaljogspeed,JOGTELEOP); } else if (*halui_data->ajog_minus[num_axes]) { - sendJogCont(axis_num, -new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(axis_num, -internaljogspeed,JOGTELEOP); } - } - } + } + } } if (check_bit_changed(new_halui_data.joint_home[num_joints], old_halui_data.joint_home[num_joints]) != 0) @@ -2112,7 +2272,7 @@ static void check_hal_changes() js = new_halui_data.axis_selected; if ((bit != old_halui_data.ajog_minus[EMCMOT_MAX_AXIS]) || (bit && ajog_speed_changed)) { if (bit != 0) - sendJogCont(js, -new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(js, -internaljogspeed,JOGTELEOP); else sendJogStop(js,JOGTELEOP); old_halui_data.ajog_minus[EMCMOT_MAX_AXIS] = bit; @@ -2122,7 +2282,7 @@ static void check_hal_changes() js = new_halui_data.axis_selected; if ((bit != old_halui_data.ajog_plus[EMCMOT_MAX_AXIS]) || (bit && ajog_speed_changed)) { if (bit != 0) - sendJogCont(js,new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(js,internaljogspeed,JOGTELEOP); else sendJogStop(js,JOGTELEOP); old_halui_data.ajog_plus[EMCMOT_MAX_AXIS] = bit; @@ -2132,7 +2292,7 @@ static void check_hal_changes() js = new_halui_data.axis_selected; if (bit != old_halui_data.ajog_increment_plus[EMCMOT_MAX_AXIS]) { if (bit) - sendJogIncr(js, new_halui_data.ajog_speed, new_halui_data.ajog_increment[EMCMOT_MAX_AXIS],JOGTELEOP); + sendJogIncr(js, internaljogspeed, new_halui_data.ajog_increment[EMCMOT_MAX_AXIS],JOGTELEOP); old_halui_data.ajog_increment_plus[EMCMOT_MAX_AXIS] = bit; } @@ -2140,7 +2300,7 @@ static void check_hal_changes() js = new_halui_data.axis_selected; if (bit != old_halui_data.ajog_increment_minus[EMCMOT_MAX_AXIS]) { if (bit) - sendJogIncr(js, new_halui_data.ajog_speed, -(new_halui_data.ajog_increment[EMCMOT_MAX_AXIS]),JOGTELEOP); + sendJogIncr(js, internaljogspeed, -(new_halui_data.ajog_increment[EMCMOT_MAX_AXIS]),JOGTELEOP); old_halui_data.ajog_increment_minus[EMCMOT_MAX_AXIS] = bit; } @@ -2385,6 +2545,46 @@ int main(int argc, char *argv[]) //initialize safe values hal_init_pins(); + + /* import the python module and get references for needed function */ + + PyConfig config; + PyConfig_InitPythonConfig(&config); + char name[] = "halui"; + wchar_t *wname = Py_DecodeLocale(name, NULL); + PyConfig_SetString(&config, &config.program_name, wname); + Py_Initialize(); + + PyRun_SimpleString("print('PYTHON EMBEDDED!!')\n" + ); + pModule = PyImport_ImportModule("bridgeui.bridge"); + if (pModule != NULL) { + pClass = PyObject_GetAttrString(pModule, "Bridge"); + pInstance = PyObject_CallObject(pClass, NULL); + pFuncWrite = PyObject_GetAttrString(pInstance, "update"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "Olllh", pClass, 0, 0, 0, 0); + if (pValue == NULL){ + fprintf(stderr, "Panelui: update function failed: returned NULL\n"); + } + if (PyErr_Occurred()) PyErr_Print(); + }else{ + if (PyErr_Occurred()){ + PyErr_Print(); + } + fprintf(stderr, "Bridge: Failed python function"); + exit(1); + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + }else{ + PyErr_Print(); + fprintf(stderr, "bridge: Failed to load \"%s\"\n", "pyui"); + exit(1); + } + + // init NML if (0 != tryNml()) { rcs_print_error("can't connect to emc\n"); @@ -2420,6 +2620,8 @@ int main(int argc, char *argv[]) } } check_hal_changes(); //if anything changed send NML messages + + modify_hal_pins(); //if status changed modify HAL too esleep(0.02); //sleep for a while updateStatus(); From 80babde84ba4d5e7fb8feea0bd62865a5658582d Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 16 Aug 2025 10:13:59 -0700 Subject: [PATCH 03/42] halui -inject socket axis selection --- src/emc/usr_intf/halui.cc | 53 ++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index d5e931a4576..0f4c625bb6d 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -1281,6 +1281,9 @@ static void sendJogStop(int ja, int jjogmode) static void sendJogCont(int ja, double speed, int jjogmode) { + // no selected axis/joint + if (ja < 0) { return; } + EMC_JOG_CONT emc_jog_cont_msg; if (emcStatus->task.state != EMC_TASK_STATE::ON) { return; } @@ -1631,7 +1634,7 @@ static void hal_init_pins() *(halui_data->ajog_speed) = 0; *(halui_data->joint_selected) = 0; // select joint 0 by default - *(halui_data->axis_selected) = 0; // select axis 0 by default + *(halui_data->axis_selected) = -1; // select no axis by default *(halui_data->fo_scale) = old_halui_data.fo_scale = 0.1; //sane default *(halui_data->ro_scale) = old_halui_data.ro_scale = 0.1; //sane default @@ -1796,11 +1799,7 @@ static void check_hal_changes() int ajog_speed_changed; int is_any_axis_selected, deselected; - local_halui_str new_halui_data_mutable; - copy_hal_data(*halui_data, new_halui_data_mutable); - const local_halui_str &new_halui_data = new_halui_data_mutable; - - // read socket messages + // get python to process socket messages pFuncRead = PyObject_GetAttrString(pInstance, "readMsg"); if (pFuncRead && PyCallable_Check(pFuncRead)) { pValue = PyObject_CallFunction(pFuncRead, "O", pClass); @@ -1816,6 +1815,29 @@ static void check_hal_changes() exit(1); } + // check socket messages for current axis selection + int value = write_msg_get_axis_selected(); + + local_halui_str new_halui_data_mutable; + + if (value != lastaxis) { + // inject socket axis selection over hal data + for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { + if ( !(axis_mask & (1 << axis_num)) ) { continue; } + + if (axis_num == value) { + *(halui_data->axis_nr_select[axis_num]) = 1; + }else{ + *(halui_data->axis_nr_select[axis_num]) = 0; + } + } + lastaxis = value; + } + + copy_hal_data(*halui_data, new_halui_data_mutable); + const local_halui_str &new_halui_data = new_halui_data_mutable; + + //check if machine_on pin has changed (the rest work exactly the same) if (check_bit_changed(new_halui_data.machine_on, old_halui_data.machine_on) != 0) sendMachineOn(); //send MachineOn NML command @@ -2184,25 +2206,20 @@ static void check_hal_changes() bit = new_halui_data.axis_nr_select[axis_num]; if (bit != old_halui_data.axis_nr_select[axis_num]) { if (bit != 0) { - is_any_axis_selected = 1; - *halui_data->axis_selected = axis_num; - write_msg_axis_changed(axis_num); - aselect_changed = axis_num; // flag that we changed the selected axis + is_any_axis_selected = 1; + *halui_data->axis_selected = axis_num; + write_msg_axis_changed(axis_num); + aselect_changed = axis_num; // flag that we changed the selected axis }else{ deselected = 1; - } - old_halui_data.axis_nr_select[axis_num] = bit; } + old_halui_data.axis_nr_select[axis_num] = bit; + } } // last axis has been deselected - no axis is selected now if (is_any_axis_selected == 0 and deselected == 1) { write_msg_axis_changed(-1); - } - - // check socket messages for axis selection change - int value = write_msg_get_axis_selected(); - if (value != lastaxis){ - aselect_changed = lastaxis = value; + *halui_data->axis_selected = -1; } if (aselect_changed >= 0) { From 6fe129671a6b7a02fbb27c741f6b2246fcb48f3d Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 16 Aug 2025 11:12:19 -0700 Subject: [PATCH 04/42] gladevcp -speedcontrol: put Gstat status in the loop Gstat sends out socket messages for jog rate --- lib/python/gladevcp/speedcontrol.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index 332f025ab0d..5dda24b65a3 100755 --- a/lib/python/gladevcp/speedcontrol.py +++ b/lib/python/gladevcp/speedcontrol.py @@ -35,6 +35,8 @@ else: from .hal_widgets import _HalSpeedControlBase +from gladevcp.core import Status, Action + class SpeedControl(Gtk.Box, _HalSpeedControlBase): ''' The SpeedControl Widget serves as a slider with button to increment od decrease @@ -98,6 +100,8 @@ class SpeedControl(Gtk.Box, _HalSpeedControlBase): "%.1f", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT), 'do_hide_button' : ( GObject.TYPE_BOOLEAN, 'Hide the button', 'Display the button + and - to alter the values', False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT), + 'type' : ( GObject.TYPE_INT, 'Type of adjustment', 'Set to -1 for general, 0 for jograte', + -1, 0, -1, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT), } __gproperties = __gproperties__ @@ -112,6 +116,9 @@ class SpeedControl(Gtk.Box, _HalSpeedControlBase): def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, unit = "", color = "#FF8116", template = "%.1f"): super(SpeedControl, self).__init__() + self._action = Action() + self._status = Status() + # basic settings self._size = size self._value = value @@ -123,6 +130,7 @@ def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, un self._increment = (self._max - self._min) / 100.0 self._template = template self._speed = inc_speed + self.type_linear_jog = False self.adjustment = Gtk.Adjustment(value = self._value, lower = self._min, upper = self._max, step_increment = self._increment, page_increment = 0) self.adjustment.connect("value_changed", self._on_value_changed) @@ -175,6 +183,10 @@ def _hal_init(self): self.hal_pin_decrease = self.hal.newpin(self.hal_name+".decrease", hal.HAL_BIT, hal.HAL_IN) self.hal_pin_decrease.connect("value-changed", self._on_minus_changed) + if self.type_linear_jog: + print('->>',self.type_linear_jog) + self._status.connect('jograte-changed', lambda w, data: self.set_value(data)) + # this draws our widget on the screen def expose(self, widget, event): # create the cairo window @@ -269,6 +281,9 @@ def get_value(self): # we are not sync, so def _on_value_changed(self, widget): value = widget.get_value() + if self.type_linear_jog: + self._action.SET_JOG_RATE(value) + if value != self._value: self._value = value self.set_value(self._value) @@ -453,6 +468,10 @@ def do_set_property(self, property, value): self._template = value if name == "do_hide_button": self.hide_button(value) + if name == "type": + print(name,value) + if value == 0: + self.type_linear_jog = True self._draw_widget() else: raise AttributeError('unknown property %s' % property.name) From 7217bbbe83ea4d0e3e3cc5b557f6f2ff903649e2 Mon Sep 17 00:00:00 2001 From: CMorley Date: Tue, 19 Aug 2025 20:18:49 -0700 Subject: [PATCH 05/42] bridge -remove HAL pin code, get MDI commands working --- lib/python/bridgeui/bridge.py | 98 ++++++++++++++--------------------- 1 file changed, 38 insertions(+), 60 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index 54ea67d225e..32cc74b8440 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -8,7 +8,6 @@ import signal import hal -from qtvcp.qt_halobjects import Qhal from common.iniinfo import _IStat as IStatParent from common import logger @@ -59,7 +58,7 @@ def __init__(self, readAddress = "tcp://127.0.0.1:5690", signal.signal(signal.SIGTERM, self.shutdown) signal.signal(signal.SIGINT, self.shutdown) - self.init_hal() + self.init() if ZMQ: self.init_read() self.init_write() @@ -72,48 +71,14 @@ def update(self, *arg): self.writeMsg('set_selected_axis','Y') self.activeJoint.set(10) - def init_hal(self): - self.comp = h = hal.component("bridge") - QHAL = Qhal(comp=self.comp, hal=hal) + def init(self): + self.jogRate = 0 + self.jogRateAngular = 0 - self.jogRate = QHAL.newpin("jog-rate", hal.HAL_FLOAT, hal.HAL_OUT) - self.jogRateIn = QHAL.newpin("jog-rate-in", hal.HAL_FLOAT, hal.HAL_IN) - self.jogRateIn.pinValueChanged.connect(self.pinChanged) + self.jogIncrement = 0 + self.jogIncrementAngular = 0 - self.jogRateAngular = QHAL.newpin("jog-rate-angular", hal.HAL_FLOAT, hal.HAL_OUT) - self.jogRateAngularIn = QHAL.newpin("jog-rate-angular-in", hal.HAL_FLOAT, hal.HAL_OUT) - self.jogRateAngularIn.pinValueChanged.connect(self.pinChanged) - - self.jogIncrement = QHAL.newpin("jog-increment", hal.HAL_FLOAT, hal.HAL_OUT) - self.jogIncrementAngular = QHAL.newpin("jog-increment-angular", hal.HAL_FLOAT, hal.HAL_OUT) - self.activeJoint = QHAL.newpin('joint-selected', hal.HAL_S32, hal.HAL_OUT) - - for i in (self.INFO.AVAILABLE_AXES): - let = i.lower() - # input - self['axis{}Select'.format(let)] = QHAL.newpin('axis-%s-select'%let, hal.HAL_BIT, hal.HAL_IN) - self['axis{}Select'.format(let)].pinValueChanged.connect(self.pinChanged) - # output - self['Axis{}IsSelected'.format(let)] = QHAL.newpin('axis-%s-is-selected'%let, hal.HAL_BIT, hal.HAL_OUT) - - self.cycle_start = QHAL.newpin('cycle-start-in',QHAL.HAL_BIT, QHAL.HAL_IN) - self.cycle_start.pinValueChanged.connect(self.pinChanged) - self.cycle_pause = QHAL.newpin('cycle-pause-in',QHAL.HAL_BIT, QHAL.HAL_IN) - self.cycle_pause.pinValueChanged.connect(self.pinChanged) - - for i in self.INFO.MDI_COMMAND_DICT: - LOG.debug('{} {}'.format(i,self.INFO.MDI_COMMAND_DICT.get(i))) - self[i] = QHAL.newpin('ini-mdi-cmd-{}'.format(i),QHAL.HAL_BIT, QHAL.HAL_IN) - self[i].pinValueChanged.connect(self.runMacroChanged) - - for i in self.INFO.INI_MACROS: - name = i.split()[0] - LOG.debug('{} {}'.format(name,i)) - self[name] = QHAL.newpin('ini-macro-cmd-{}'.format(name),QHAL.HAL_BIT, QHAL.HAL_IN) - self[name].pinValueChanged.connect(self.runMacroChanged) - - QHAL.setUpdateRate(100) - h.ready() + self.activeJoint = 0 def init_write(self): context = zmq.Context() @@ -141,19 +106,19 @@ def readMsg(self, msg): y = json.loads(data) self. action(y.get('MESSAGE'),y.get('ARGS')) - # set our output HAL pins from messages from hal_glib + # set our variables from messages from hal_glib def action(self, msg, data): LOG.debug('{} {}'.format(msg, data)) if msg == 'jograte-changed': - self.jogRate.set(float(data[0])) + self.jogRate = float(data[0]) elif msg == 'jograte-angular-changed': - self.jogRateAngular.set(float(data[0])) + self.jogRateAngular = float(data[0]) elif msg == 'jogincrements-changed': - self.jogIncrement.set(float(data[0][0])) + self.jogIncrement = float(data[0][0]) elif msg == 'jogincrement-angular-changed': - self.jogIncremtAngular.set(float(data[0][0])) + self.jogIncremtAngular = float(data[0][0]) elif msg == 'joint-selection-changed': - self.activeJoint.set(int(data[0])) + self.activeJoint = int(data[0]) elif msg == 'axis-selection-changed': flag = 1 for i in(self.INFO.AVAILABLE_AXES): @@ -163,7 +128,6 @@ def action(self, msg, data): flag = 0 else: state = False - self['Axis{}IsSelected'.format(i.lower())].set(state) self.axesSelected[i] = int(state) if flag: self.currentSelectedAxis = 'None' @@ -183,8 +147,7 @@ def writeMsg(self, msg, data): # callback from HAL input pins def pinChanged(self, pinObject, value): LOG.debug('Pin name:{} changed value to {}'.format(pinObject.text(), value)) - #print(type(value)) - # Axis selction change request + # Axis selection change request if 'select' in pinObject.text(): if bool(value) == False: pass @@ -220,26 +183,41 @@ def pinChanged(self, pinObject, value): else: self.writeMsg(pinObject.text(),value) - # callback; request to run a specific macro - def runMacroChanged(self, pinObject, value): - LOG.debug('Macro Pin name:{} changed value to {}'.format(pinObject.text(), value)) - #LOG.debug(type(value)) - name = pinObject.text().strip('macro-cmd-') - if value: - self.writeMsg('request_macro_call', name) def shutdown(self,signum=None,stack_frame=None): LOG.debug('shutdown') global app app.quit() + def getMdiName(self, num): + if num >len(self.INFO.MDI_COMMAND_DICT)-1: + return 'None' + temp = list(self.INFO.MDI_COMMAND_DICT.keys())[num] + LOG.debug('{} {}'.format(num,temp)) + return temp + + def getMacroNames(self): + for i in self.INFO.INI_MACROS: + name = i.split()[0] + LOG.debug('{} {}'.format(name,i)) + + def runIndexedMacro(self, num): + name = self.getMdiName(num) + LOG.debug('Macro name:{} ,index: {}'.format(name, num)) + if name != 'None': + self.writeMsg('request_macro_call', name) + + def getMdiCount(self): + print(len(self.INFO.MDI_COMMAND_DICT)) + return len(self.INFO.MDI_COMMAND_DICT) + def getJogRate(self): - return self.jogRate.get() + return self.jogRate def setJogRate(self, value): self.writeMsg('set_jograte', value) def getJogRateAngular(self): - return self.jogRateAngular.get() + return self.jogRateAngular def setJogRateAngular(self, value): self.writeMsg('set_jograte_angular', value) From 1d0dbc5aa2062ac44c0bb746d53b3651692faf46 Mon Sep 17 00:00:00 2001 From: CMorley Date: Tue, 19 Aug 2025 20:19:38 -0700 Subject: [PATCH 06/42] halui -code clenup, GUI based MDI command feature --- src/emc/usr_intf/halui.cc | 177 ++++++++++++++++++++++++++++---------- 1 file changed, 133 insertions(+), 44 deletions(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 0f4c625bb6d..65dcc57f631 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -203,6 +203,7 @@ static int axis_mask = 0; FIELD(hal_bit_t,home_all) /* pin for homing all joints in sequence */ \ FIELD(hal_bit_t,abort) /* pin for aborting */ \ ARRAY(hal_bit_t,mdi_commands,MDI_MAX) \ + ARRAY(hal_bit_t,gui_mdi_commands,MDI_MAX) \ \ FIELD(hal_float_t,units_per_mm) \ @@ -246,6 +247,9 @@ static int lastaxis = -1; static char *mdi_commands[MDI_MAX]; static int num_mdi_commands=0; + +static char *gui_mdi_commands[MDI_MAX]; +static int num_gui_mdi_commands = 0; static int have_home_all = 0; static int comp_id, done; /* component ID, main while loop */ @@ -551,6 +555,65 @@ int halui_export_pin_OUT_bit(hal_bit_t **pin, const char *name) return 0; } +static int py_call_get_mdi_count() { + int value = 0; + // check socket messages for jogspeed + pFuncRead = PyObject_GetAttrString(pInstance, "getMdiCount"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + if (pValue == NULL){ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "Halui Bridge: getMdiCountfunction failed: returned NULL\n"); + value = -1; + }else{ + if (PyLong_Check(pValue)) { + value = (int) PyLong_AsLong(pValue); + //fprintf(stderr, "axis value %d\n",value); + if (PyErr_Occurred()) { + value = -1; + // Handle conversion error + PyErr_Print(); + // Clear the error state if needed + PyErr_Clear(); + } + } + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncRead); + return value; +} + +// turns a undex number into a macro name +static char* py_call_get_mdi_name( int num) { + pFuncWrite = PyObject_GetAttrString(pInstance, "getMdiName"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "i", num); + + if (pValue != NULL) { + if (PyUnicode_Check(pValue)) { + PyObject *pBytes = PyUnicode_AsUTF8String(pValue); + char *result_string = PyBytes_AsString(pBytes); + Py_XDECREF(pBytes); + printf("Python function returned: %s %i\n", result_string,num); + Py_DECREF(pValue); + Py_DECREF(pFuncWrite); + return result_string; + } else { + fprintf(stderr, "Return value is not a string. %i\n", num); + } + } else { + PyErr_Print(); // Print Python error if call failed + } + Py_DECREF(pValue); + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); + return NULL; +} /******************************************************************** * @@ -941,6 +1004,12 @@ int halui_hal_init(void) if (retval < 0) return retval; } + for (int n=0; ngui_mdi_commands[n]), comp_id, "halui.mdi-command-%s", py_call_get_mdi_name(n)); + if (retval < 0) return retval; + } + hal_ready(comp_id); return 0; } @@ -1575,6 +1644,13 @@ static int iniLoad(const char *filename) mdi_commands[num_mdi_commands++] = strdup(*mc); } + int temp = py_call_get_mdi_count(); + for (int n=0; n 0.00001) { ajog_speed_changed = 1; lastjogspeed = jogspeed; @@ -2208,7 +2305,7 @@ static void check_hal_changes() if (bit != 0) { is_any_axis_selected = 1; *halui_data->axis_selected = axis_num; - write_msg_axis_changed(axis_num); + py_call_axis_changed(axis_num); aselect_changed = axis_num; // flag that we changed the selected axis }else{ deselected = 1; @@ -2218,7 +2315,7 @@ static void check_hal_changes() } // last axis has been deselected - no axis is selected now if (is_any_axis_selected == 0 and deselected == 1) { - write_msg_axis_changed(-1); + py_call_axis_changed(-1); *halui_data->axis_selected = -1; } @@ -2321,10 +2418,19 @@ static void check_hal_changes() old_halui_data.ajog_increment_minus[EMCMOT_MAX_AXIS] = bit; } + // run HALUI commands for(int n = 0; n < num_mdi_commands; n++) { if (check_bit_changed(new_halui_data.mdi_commands[n], old_halui_data.mdi_commands[n]) != 0) sendMdiCommand(n); } + + // request GUI ti run MDI commands + for(int n = 0; n < num_gui_mdi_commands; n++) { + if (check_bit_changed(new_halui_data.gui_mdi_commands[n], old_halui_data.gui_mdi_commands[n]) != 0){ + printf("GUI MDI command called index: %i\n", n); + py_call_request_MDI(n); + } + } } // this function looks at the received NML status message @@ -2547,22 +2653,6 @@ int main(int argc, char *argv[]) exit(1); } - // get configuration information - if (0 != iniLoad(emc_inifile)) { - rcs_print_error("iniLoad error\n"); - exit(2); - } - - //init HAL and export pins - if (0 != halui_hal_init()) { - rcs_print_error("hal_init error\n"); - exit(1); - } - - //initialize safe values - hal_init_pins(); - - /* import the python module and get references for needed function */ PyConfig config; @@ -2572,35 +2662,34 @@ int main(int argc, char *argv[]) PyConfig_SetString(&config, &config.program_name, wname); Py_Initialize(); - PyRun_SimpleString("print('PYTHON EMBEDDED!!')\n" - ); + PyRun_SimpleString("print('PYTHON EMBEDDED!!')\n"); pModule = PyImport_ImportModule("bridgeui.bridge"); if (pModule != NULL) { pClass = PyObject_GetAttrString(pModule, "Bridge"); pInstance = PyObject_CallObject(pClass, NULL); - pFuncWrite = PyObject_GetAttrString(pInstance, "update"); - - if (pFuncWrite && PyCallable_Check(pFuncWrite)) { - pValue = PyObject_CallFunction(pFuncWrite, "Olllh", pClass, 0, 0, 0, 0); - if (pValue == NULL){ - fprintf(stderr, "Panelui: update function failed: returned NULL\n"); - } - if (PyErr_Occurred()) PyErr_Print(); - }else{ - if (PyErr_Occurred()){ - PyErr_Print(); - } - fprintf(stderr, "Bridge: Failed python function"); - exit(1); - Py_DECREF(pValue); - } - Py_DECREF(pFuncWrite); }else{ PyErr_Print(); fprintf(stderr, "bridge: Failed to load \"%s\"\n", "pyui"); exit(1); } + // get configuration information + if (0 != iniLoad(emc_inifile)) { + rcs_print_error("iniLoad error\n"); + exit(2); + } + + //init HAL and export pins + if (0 != halui_hal_init()) { + rcs_print_error("hal_init error\n"); + exit(1); + } + + //initialize safe values + hal_init_pins(); + + + // init NML if (0 != tryNml()) { From 927e717119f0d71c6ae42e53645004d60c920bc6 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 22 Aug 2025 15:32:30 -0700 Subject: [PATCH 07/42] halui -add cycle start cycle pause pins calls these functions in a GUI --- lib/python/bridgeui/bridge.py | 47 ++++++----------------------------- src/emc/usr_intf/halui.cc | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 40 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index 32cc74b8440..862ae0244d9 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -144,51 +144,18 @@ def writeMsg(self, msg, data): [bytes(topic.encode('utf-8')), bytes((message).encode('utf-8'))]) - # callback from HAL input pins - def pinChanged(self, pinObject, value): - LOG.debug('Pin name:{} changed value to {}'.format(pinObject.text(), value)) - # Axis selection change request - if 'select' in pinObject.text(): - if bool(value) == False: - pass - #print('Not true state') - return - for i in (self.INFO.AVAILABLE_AXES): - if '-{}-'.format(i.lower()) in pinObject.text(): - self.writeMsg('set_selected_axis', i) - break - else: - if 'None' in pinObject.text(): - self.writeMsg('set_selected_axis', '') - - # cycle start - elif self.cycle_start == pinObject: - if value: - self.writeMsg('request_cycle_start', value) - - # cycle pause - elif self.cycle_pause == pinObject: - #if value: - self.writeMsg('request_cycle_pause', value) - - # linear jog rate - elif self.jogRateIn == pinObject: - self.writeMsg('set_jograte', value) - - # angular jog rate - elif self.jogRateAngularIn == pinObject: - self.writeMsg('set_jograte_angular', value) - - # catch all default - else: - self.writeMsg(pinObject.text(),value) - - def shutdown(self,signum=None,stack_frame=None): LOG.debug('shutdown') global app app.quit() + def cycleStart(self): + # cycle start + self.writeMsg('request_cycle_start', True) + + def cyclePause(self): + self.writeMsg('request_cycle_pause', True) + def getMdiName(self, num): if num >len(self.INFO.MDI_COMMAND_DICT)-1: return 'None' diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 65dcc57f631..8be76637bac 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -86,6 +86,8 @@ static int axis_mask = 0; FIELD(hal_bit_t,program_is_running) /* pin for notifying user that program is running */ \ FIELD(hal_bit_t,halui_mdi_is_running) /* pin for notifying user that halui MDI commands is running */ \ FIELD(hal_bit_t,program_is_paused) /* pin for notifying user that program is paused */ \ + FIELD(hal_bit_t,cycle_start) /* pin for running program */ \ + FIELD(hal_bit_t,cycle_pause) /* pin for running program */ \ FIELD(hal_bit_t,program_run) /* pin for running program */ \ FIELD(hal_bit_t,program_pause) /* pin for pausing program */ \ FIELD(hal_bit_t,program_resume) /* pin for resuming program */ \ @@ -555,6 +557,36 @@ int halui_export_pin_OUT_bit(hal_bit_t **pin, const char *name) return 0; } +static void py_call_cycleStart() { + + // check socket messages for jogspeed + pFuncWrite = PyObject_GetAttrString(pInstance, "cycleStart"); + if (pFuncRead && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: cycleStart function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} + +static void py_call_cyclePause() { + + // check socket messages for jogspeed + pFuncWrite = PyObject_GetAttrString(pInstance, "cyclePause"); + if (pFuncRead && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: cyclePause function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} + static int py_call_get_mdi_count() { int value = 0; // check socket messages for jogspeed @@ -846,6 +878,10 @@ int halui_hal_init(void) if (retval < 0) return retval; retval = halui_export_pin_IN_bit(&(halui_data->flood_off), "halui.flood.off"); if (retval < 0) return retval; + retval = halui_export_pin_IN_bit(&(halui_data->cycle_start), "halui.cycle.start"); + if (retval < 0) return retval; + retval = halui_export_pin_IN_bit(&(halui_data->cycle_pause), "halui.cycle.pause"); + if (retval < 0) return retval; retval = halui_export_pin_IN_bit(&(halui_data->program_run), "halui.program.run"); if (retval < 0) return retval; retval = halui_export_pin_IN_bit(&(halui_data->program_pause), "halui.program.pause"); @@ -1975,6 +2011,16 @@ static void check_hal_changes() if (check_bit_changed(new_halui_data.flood_off, old_halui_data.flood_off) != 0) sendFloodOff(); + if (check_bit_changed(new_halui_data.cycle_start, old_halui_data.cycle_start) != 0){ + fprintf(stderr, "cycle-start value = %i\n", new_halui_data.cycle_start ); + py_call_cycleStart(); + } + + if (check_bit_changed(new_halui_data.cycle_pause, old_halui_data.cycle_pause) != 0){ + fprintf(stderr, "cycle-pause value = %i\n", new_halui_data.cycle_pause ); + py_call_cyclePause(); + } + if (check_bit_changed(new_halui_data.program_run, old_halui_data.program_run) != 0) sendProgramRun(0); From f7f19e9e256cb1e0290068c1bfb65776e0feecce Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 22 Aug 2025 15:33:19 -0700 Subject: [PATCH 08/42] qtdragon -adjust external pause message to toggle --- share/qtvcp/screens/qtdragon/qtdragon_handler.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index 4fcbce2eeba..f7617c285fb 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -153,7 +153,7 @@ def __init__(self, halcomp, widgets, paths): STATUS.connect('status-message', lambda w, d, o: self.add_external_status(d,o)) STATUS.connect('runstop-line-changed', lambda w, l :self.lastRunLine(l)) STATUS.connect('cycle-start-request', lambda w, state :self.btn_start_clicked(state)) - STATUS.connect('cycle-pause-request', lambda w, state: self.btn_pause_clicked(state)) + STATUS.connect('cycle-pause-request', lambda w, state: self.ext_pause_toggled(state)) STATUS.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) self.swoopPath = os.path.join(paths.IMAGEDIR,'lcnc_swoop.png') @@ -1274,6 +1274,12 @@ def btn_spindle_z_down_clicked(self): if self.h['eoffset-clear'] != True: self.h['eoffset-spindle-count'] = int(fval) + def ext_pause_toggled(self, state): + if STATUS.is_auto_paused(): + self.btn_pause_clicked(False) + return + self.btn_pause_clicked(True) + def btn_pause_clicked(self, data): # pause request From d1da33e7e50fb8529ddb94bd7385f92811233dbd Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 27 Jul 2025 14:05:58 -0700 Subject: [PATCH 09/42] gmoccapy/hal_bridge -allow hal_bridge to call macros in gmoccapy This is a proof of concept to allow 3rd party (hal_bridge) to call macros. The macros are run from Gmoccapy so no timing problems should occur and and pre checks or post changes can be covered. There needs to be agreement on where to put macro definitions in the INI. Gmoccapy puts them under [MACROS], iniinfo under [DISPLAY] python ZMQ module package must be available for this to work --- configs/sim/gmoccapy/gmoccapy_right_panel.ini | 8 +++- src/emc/usr_intf/gmoccapy/getiniinfo.py | 2 +- src/emc/usr_intf/gmoccapy/gmoccapy.py | 40 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/configs/sim/gmoccapy/gmoccapy_right_panel.ini b/configs/sim/gmoccapy/gmoccapy_right_panel.ini index 2b7c12274c0..2816745579d 100644 --- a/configs/sim/gmoccapy/gmoccapy_right_panel.ini +++ b/configs/sim/gmoccapy/gmoccapy_right_panel.ini @@ -31,6 +31,12 @@ INTRO_TIME = 5 # list of selectable jog increments INCREMENTS = 1mm, 0.1mm, 0.01mm, 0.001mm, 1.2345in +MACRO = i_am_lost +MACRO = halo_world +MACRO = jog_around +MACRO = increment xinc yinc +MACRO = go_to_position X-pos Y-pos Z-pos + [FILTER] PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image PROGRAM_EXTENSION = .py Python Script @@ -75,7 +81,7 @@ HALFILE = simulated_home.hal POSTGUI_HALFILE = gmoccapy_postgui.hal HALUI = halui - +HALBRIDGE = hal_bridge -d # Trajectory planner section -------------------------------------------------- [HALUI] #No Content diff --git a/src/emc/usr_intf/gmoccapy/getiniinfo.py b/src/emc/usr_intf/gmoccapy/getiniinfo.py index 63077dc6a50..38ae7970a85 100644 --- a/src/emc/usr_intf/gmoccapy/getiniinfo.py +++ b/src/emc/usr_intf/gmoccapy/getiniinfo.py @@ -393,7 +393,7 @@ def get_tool_sensor_data(self): def get_macros(self): # lets look in the INI file, if there are any entries - macros = self.inifile.findall("MACROS", "MACRO") + macros = self.inifile.findall("DISPLAY", "MACRO") # If there are no entries we will return False if not macros: return False diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 02f5a1d0df4..31c82d632eb 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -417,6 +417,7 @@ def __init__(self, argv): self.GSTAT = Status() self.GSTAT.connect("graphics-gcode-properties", self.on_gcode_properties) self.GSTAT.connect("file-loaded", self.on_hal_status_file_loaded) + self.GSTAT.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -1350,6 +1351,33 @@ def _make_joints_button(self): self.joints_button_dic[name] = btn + # call INI macro (from hal_glib message) + def request_macro_call(self, data): + + # some error checking + if not self.GSTAT.is_mdi_mode(): + message = _("You must be in MDI mode to run macros") + self.dialogs.warning_dialog(self, _("Important Warning!"), message) + return + + # look thru the INI macros + macros = self.get_ini_info.get_macros() + num_macros = len(macros) + if num_macros > 14: + num_macros = 14 + for pos in range(0, num_macros): + # extract just the macro name + name = macros[pos].split()[0] + if data == name: + # get the button instance and click it + button = self["button_macro_{0}".format(pos)] + button.emit("clicked") + break + else: + # didn't match a name - give a hint + message = _("Macro {} not found ".format(data)) + self.dialogs.warning_dialog(self, _("Important Warning!"), message) + # check if macros are in the INI file and add them to MDI Button List def _make_macro_button(self): LOG.debug("Entering make macro button") @@ -1391,6 +1419,8 @@ def _make_macro_button(self): btn.set_halign(Gtk.Align.CENTER) btn.set_valign(Gtk.Align.CENTER) btn.set_property("name","macro_{0}".format(pos)) + # keep a reference of the button + self["button_macro_{0}".format(pos)] = btn btn.set_property("tooltip-text", _("Press to run macro {0}".format(name))) btn.connect("clicked", self._on_btn_macro_pressed, name) btn.position = pos @@ -6391,6 +6421,16 @@ def _make_hal_pins(self): hal_glib.GPin(pin).connect("value_changed", self._blockdelete) + ############################## + # required class boiler code # + # for subscriptable objects # + ############################## + def __getitem__(self, item): + return getattr(self, item) + + def __setitem__(self, item, value): + return setattr(self, item, value) + # Hal Pin Handling End # ========================================================= From 1432d46b056777efef870309303a79771f0ba8b2 Mon Sep 17 00:00:00 2001 From: CMorley Date: Wed, 30 Jul 2025 20:50:47 -0700 Subject: [PATCH 10/42] gmoccapy -ability to run INI MDI commands using HAL bridge --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 31c82d632eb..8a2ad06fec3 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -184,7 +184,8 @@ def __init__(self, argv): self.error_channel.poll() # set INI path for INI info class before widgets are loaded - INFO = Info(ini=argv[2]) + self.INFO = Info(ini=argv[2]) + self.ACTION = Action() self.builder = Gtk.Builder() # translation of the glade file will be done with @@ -1353,7 +1354,17 @@ def _make_joints_button(self): # call INI macro (from hal_glib message) def request_macro_call(self, data): + # if MDI command change to MDI and run + cmd = self.INFO.get_ini_mdi_command(data) + print('MDI command:',data,cmd) + if not cmd is None: + self.ACTION.RECORD_CURRENT_MODE() + LOG.debug("INI MDI COMMAND #: {} = {}".format(data, cmd)) + self.ACTION.CALL_INI_MDI(data) + self.ACTION.RESTORE_RECORDED_MODE() + return + # run Macros # some error checking if not self.GSTAT.is_mdi_mode(): message = _("You must be in MDI mode to run macros") @@ -6470,6 +6481,7 @@ def __setitem__(self, item, value): # Some of these libraries log when imported so logging level must already be set. import gladevcp.makepins from gladevcp.core import Info, Status + from gladevcp.core import Info, Status, Action from gladevcp.combi_dro import Combi_DRO # we will need it to make the DRO from gmoccapy import widgets # a class to handle the widgets From 4e83fb27a5f4b3c08b94d146c7ab814a5d9051e1 Mon Sep 17 00:00:00 2001 From: CMorley Date: Wed, 30 Jul 2025 20:51:51 -0700 Subject: [PATCH 11/42] gmoccapy -sample config: add INI MDI commands to test with --- configs/sim/gmoccapy/gmoccapy_right_panel.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/configs/sim/gmoccapy/gmoccapy_right_panel.ini b/configs/sim/gmoccapy/gmoccapy_right_panel.ini index 2816745579d..9704bec48f9 100644 --- a/configs/sim/gmoccapy/gmoccapy_right_panel.ini +++ b/configs/sim/gmoccapy/gmoccapy_right_panel.ini @@ -37,6 +37,11 @@ MACRO = jog_around MACRO = increment xinc yinc MACRO = go_to_position X-pos Y-pos Z-pos +[MDI_COMMAND_LIST] +# for macro buttons on main oage up to 10 possible +MDI_COMMAND = G0 Z1;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + [FILTER] PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image PROGRAM_EXTENSION = .py Python Script From 0958c894eb8648ce72c3065978d4b5193e5cc82a Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 23 Aug 2025 06:37:17 -0700 Subject: [PATCH 12/42] halui -add gui ok/canel pins --- lib/python/bridgeui/bridge.py | 6 ++++ src/emc/usr_intf/halui.cc | 52 +++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index 862ae0244d9..27397528660 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -156,6 +156,12 @@ def cycleStart(self): def cyclePause(self): self.writeMsg('request_cycle_pause', True) + def ok(self): + self.writeMsg('request_ok', True) + + def cancel(self): + self.writeMsg('request_cancel', True) + def getMdiName(self, num): if num >len(self.INFO.MDI_COMMAND_DICT)-1: return 'None' diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 8be76637bac..fc86de990ed 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -204,8 +204,12 @@ static int axis_mask = 0; \ FIELD(hal_bit_t,home_all) /* pin for homing all joints in sequence */ \ FIELD(hal_bit_t,abort) /* pin for aborting */ \ +\ ARRAY(hal_bit_t,mdi_commands,MDI_MAX) \ ARRAY(hal_bit_t,gui_mdi_commands,MDI_MAX) \ +\ + FIELD(hal_bit_t,gui_ok) /* pin for acknowledging dialog ok */ \ + FIELD(hal_bit_t,gui_cancel) /* pin for acknowledging dialog cancel */ \ \ FIELD(hal_float_t,units_per_mm) \ @@ -587,6 +591,34 @@ static void py_call_cyclePause() { return ; } +static void py_call_ok() { + + // check socket messages for gui ok message + pFuncWrite = PyObject_GetAttrString(pInstance, "ok"); + if (pFuncRead && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: ok function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} +static void py_call_cancel() { + + // check socket messages for gui cancel message + pFuncWrite = PyObject_GetAttrString(pInstance, "cancel"); + if (pFuncRead && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: cancel function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} static int py_call_get_mdi_count() { int value = 0; // check socket messages for jogspeed @@ -1042,10 +1074,16 @@ int halui_hal_init(void) for (int n=0; ngui_mdi_commands[n]), comp_id, "halui.mdi-command-%s", py_call_get_mdi_name(n)); + retval = hal_pin_bit_newf(HAL_IN, &(halui_data->gui_mdi_commands[n]), comp_id, "halui.gui.mdi-command-%s", py_call_get_mdi_name(n)); if (retval < 0) return retval; } + retval = halui_export_pin_IN_bit(&(halui_data->gui_ok), "halui.gui.ok"); + if (retval < 0) return retval; + + retval = halui_export_pin_IN_bit(&(halui_data->gui_cancel), "halui.gui.cancel"); + if (retval < 0) return retval; + hal_ready(comp_id); return 0; } @@ -2473,10 +2511,20 @@ static void check_hal_changes() // request GUI ti run MDI commands for(int n = 0; n < num_gui_mdi_commands; n++) { if (check_bit_changed(new_halui_data.gui_mdi_commands[n], old_halui_data.gui_mdi_commands[n]) != 0){ - printf("GUI MDI command called index: %i\n", n); + fprintf(stderr,"GUI MDI command called index: %i\n", n); py_call_request_MDI(n); } } + + if (check_bit_changed(new_halui_data.gui_ok, old_halui_data.gui_ok) != 0) { + fprintf(stderr,"GUI OK command called\n"); + py_call_ok(); + } + + if (check_bit_changed(new_halui_data.gui_cancel, old_halui_data.gui_cancel) != 0) { + fprintf(stderr,"GUI CANCEL command called\n"); + py_call_cancel(); + } } // this function looks at the received NML status message From f8c6bfe30384ba8af9c62951c4773afc179ecc83 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 23 Aug 2025 10:04:15 -0700 Subject: [PATCH 13/42] hal_glib -add ok and cancel messages --- lib/python/common/hal_glib.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/python/common/hal_glib.py b/lib/python/common/hal_glib.py index a90e0790c09..df8f95a1528 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -244,6 +244,8 @@ class _GStat(GObject.GObject): 'following-error': (GObject.SignalFlags.RUN_FIRST , GObject.TYPE_NONE,(GObject.TYPE_PYOBJECT,)), 'cycle-start-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)), 'cycle-pause-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)), + 'ok-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)), + 'cancel-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)), 'macro-call-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), } @@ -1460,6 +1462,12 @@ def request_cycle_pause(self, data): def request_macro_call(self, data): self.emit('macro-call-request', data) + def request_ok(self, data): + self.emit('ok-request', data) + + def request_cancel(self, data): + self.emit('cancel-request', data) + ############################################# def shutdown(self): From 7f63bf746158788f2c1b094f4a4bf57da320fc9b Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 23 Aug 2025 10:05:39 -0700 Subject: [PATCH 14/42] qtdragon -react to ok and cancel messages --- share/qtvcp/screens/qtdragon/qtdragon_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index f7617c285fb..60ec42b286d 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -155,6 +155,8 @@ def __init__(self, halcomp, widgets, paths): STATUS.connect('cycle-start-request', lambda w, state :self.btn_start_clicked(state)) STATUS.connect('cycle-pause-request', lambda w, state: self.ext_pause_toggled(state)) STATUS.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) + STATUS.connect('ok-request', lambda w, state: self.dialog_ext_control(w,1,1)) + STATUS.connect('cancel-request', lambda w, state: self.dialog_ext_control(w,1,0)) self.swoopPath = os.path.join(paths.IMAGEDIR,'lcnc_swoop.png') self.swoopURL = QtCore.QUrl.fromLocalFile(self.swoopPath) From f6aec0a7fad367fc6ecad36ad2edf49926b77250 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 23 Aug 2025 13:57:34 -0700 Subject: [PATCH 15/42] qtdragon metric -add a control panel --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 17 ++ configs/sim/qtdragon/qtdragon_xyz/panel.ui | 237 ++++++++++++++++++ .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 3 +- 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 configs/sim/qtdragon/qtdragon_xyz/panel.hal create mode 100644 configs/sim/qtdragon/qtdragon_xyz/panel.ui diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal new file mode 100644 index 00000000000..e9407a91feb --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -0,0 +1,17 @@ +net rate halui.axis.jog-speed panel.Jog-rate-f + +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z + +net jog-p halui.axis.selected.plus +net jog-m halui.axis.selected.minus +net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 + +net pause halui.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui new file mode 100644 index 00000000000..d9200fc614e --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -0,0 +1,237 @@ + + + MainWindow + + + + 0 + 0 + 313 + 467 + + + + MainWindow + + + + + + + + + Axis Selection + + + + + + X + + + axis-x + + + + + + + Y + + + axis-y + + + + + + + Z + + + axis-z + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + 300 + + + Qt::Horizontal + + + Jog-rate + + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + + + 0 + 0 + 313 + 22 + + + + + + + + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Slider + QSlider +
qtvcp.widgets.simple_widgets
+
+
+ + +
diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index a00b52ab427..ca436b20837 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini @@ -166,7 +166,8 @@ POSTGUI_HALFILE = qtdragon_postgui.hal # you can add multiple entries # uncomment this one to print all HAL pins that start with qt #POSTGUI_HALCMD = show pin qt - +POSTGUI_HALCMD = show pin halui.gui +POSTGUI_HALCMD = loadusr qtvcp -H panel.hal panel [HALUI] # no content From af864dc3c4d56c423a3e4530a8deb4d51a9ed695 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:04:00 -0700 Subject: [PATCH 16/42] iniinfo -parse ini commands in a better way mdi commands with a comma in it (ie MSG, text) would not be interpeted properly --- lib/python/common/iniinfo.py | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/python/common/iniinfo.py b/lib/python/common/iniinfo.py index 8cdabb3a291..177548849a9 100644 --- a/lib/python/common/iniinfo.py +++ b/lib/python/common/iniinfo.py @@ -19,7 +19,7 @@ def __init__(self, ini=None): global LOG LOG = logger.getLogger(__name__) # Force the log level for this module only - #LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL + LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL inipath = os.environ.get('INI_FILE_NAME', '/dev/null') self.LINUXCNC_IS_RUNNING = bool(inipath != '/dev/null') @@ -571,7 +571,6 @@ def update(self): if self.parser.has_section('MDI_COMMAND_LIST'): try: for key in self.parser['MDI_COMMAND_LIST']: - # legacy way: list of repeat 'MDI_COMMAND=XXXX' # in this case order matters in the INI if key == 'MDI_COMMAND': @@ -594,10 +593,24 @@ def update(self): self.MDI_COMMAND_LABEL_LIST.append(k) mdidatadict['label'] = k self.MDI_COMMAND_DICT[str(count)] = mdidatadict - break # new way: 'MDI_COMMAND_SSS = XXXX' (SSS being any string) # order of commands doesn't matter in the INI + + # here are some samples, the last three are difficult + # the third is invalid + # MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + # cmd: G53 G0 Z0;G53 G0 X0 Y0 label: Goto\nMachn\nZero + + # MDI_COMMAND_MACRO2 = (MSG, macro 2); + # cmd: (MSG, macro 2); label: + + # MDI_COMMAND_MACRO3 = (MSG, macro 2) + # cmd: (MSG label: macro 2) + + # MDI_COMMAND_MACRO4 = (MSG, macro 2),test + # cmd: (MSG, macro 2) label: test + else: self.MDI_COMMAND_LIST.append(None) self.MDI_COMMAND_LABEL_LIST.append(None) @@ -605,14 +618,25 @@ def update(self): temp = self.INI.find("MDI_COMMAND_LIST",key) name = (key.replace('MDI_COMMAND_','')) mdidatadict = {} - for num,k in enumerate(temp.split(',')): - if num == 0: - mdidatadict['cmd'] = k - if len(temp.split(',')) <2: - mdidatadict['label'] = None - else: - mdidatadict['label'] = k + + # find the last colon in string or 0 + lastCmd = temp.rfind(';') + #print('l ;:',lastCmd) + if lastCmd == -1: lastCmd = 0 + + # find the last colon in string or use the string length + lastComma = temp.rfind(',', lastCmd) + #print('l comma:',lastComma,lastCmd) + if lastComma == -1: lastComma = len(temp) + + label = temp[lastComma+1:] + cmd = temp[:lastComma] + #print(temp,' cmd:',cmd,' label:',label) + + mdidatadict['cmd'] = cmd + mdidatadict['label'] = label self.MDI_COMMAND_DICT[name] = mdidatadict + except Exception as e: LOG.error('INI MDI command parse error:{}'.format(e)) except Exception as e: From cc0d6ced06c603c5ef02d65ca6308b27c018d70e Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:05:52 -0700 Subject: [PATCH 17/42] gladevcp -gth_action: add ability to run new style INI MDI commands named INI MDI commands were not recognised. --- lib/python/gladevcp/gtk_action.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index c7eb96d3671..548a3255acc 100644 --- a/lib/python/gladevcp/gtk_action.py +++ b/lib/python/gladevcp/gtk_action.py @@ -191,14 +191,23 @@ def CALL_MDI_WAIT(self, code, time=5, mode_return=False): self.ensure_mode(premode) return 0 - def CALL_INI_MDI(self, number): + def CALL_INI_MDI(self, key): try: - mdi = INFO.MDI_COMMAND_LIST[number] + # prefer named INI MDI commands + mdi = INFO.get_ini_mdi_command(key) + LOG.debug('COMMAND= {}'.format(mdi)) + if mdi is None: raise Exception except: - msg = 'MDI_COMMAND= # {} Not found under [MDI_COMMAND_LIST] in INI file'.format(number) - LOG.error(msg) - self.SET_ERROR_MESSAGE(msg) - return + # fallback to legacy nth line + try: + mdi = INFO.MDI_COMMAND_LIST[key] + except: + msg = 'MDI_COMMAND_{} Not found under [MDI_COMMAND_LIST] in INI file'.format(key) + LOG.error(msg) + self.SET_ERROR_MESSAGE(msg) + return + + mdi_list = mdi.split(';') self.ensure_mode(linuxcnc.MODE_MDI) for code in (mdi_list): From 776fad63413ef74e6f3fa4f0031e97ff668324af Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:07:38 -0700 Subject: [PATCH 18/42] qtvcp -dialog widget: add status message control of tool change dialog you can use STATUS messages to 'press' ok or cancel --- lib/python/qtvcp/widgets/dialog_widget.py | 46 +++++++++++++++++------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/lib/python/qtvcp/widgets/dialog_widget.py b/lib/python/qtvcp/widgets/dialog_widget.py index 55c199baf8f..958ba61f4bb 100644 --- a/lib/python/qtvcp/widgets/dialog_widget.py +++ b/lib/python/qtvcp/widgets/dialog_widget.py @@ -398,6 +398,7 @@ def __init__(self, parent=None): class ToolDialog(LcncDialog, GeometryMixin): def __init__(self, parent=None): super(ToolDialog, self).__init__(parent) + self._request_name = 'TOOLCHANGE' self.setText('Manual Tool Change Request') self.setInformativeText('Please Insert Tool 0') self.setStandardButtons(QMessageBox.Ok) @@ -440,6 +441,8 @@ def _hal_init(self): self.sound_type = self.PREFS_.getpref('toolDialog_sound_type', 'READY', str, 'DIALOG_OPTIONS') else: self.play_sound = False + # can acknowledge from status messages too + STATUS.connect('dialog-update', self._status_update) # process callback from 'change' HAL pin def tool_change(self, change): @@ -490,7 +493,7 @@ def tool_change(self, change): # process callback for 'change-button' HAL pin # hide the message dialog or desktop notify message def external_acknowledge(self, state): - #print('external acklnowledge: {}'.format(state)) + #print('external acknowledge: {}'.format(state)) if state: if self._useDesktopNotify: self.deskNotice.close() @@ -498,10 +501,29 @@ def external_acknowledge(self, state): self.hide() self._processChange(True) + # callback from status 'update-dialog' + def _status_update(self, w, message): + print(message) + if message.get('NAME') == self._request_name: + if not self.isVisible(): return + print(self._request_name) + response = message.get('response') + if not response is None: + # 'ok' + if response == 1: + if self._useDesktopNotify: + self.deskNotice.close() + elif self.isVisible(): + self.hide() + self._processChange(True) + # 'cancel' + elif response == 0: + self.hide() + self._processChange(False) # This also is called from DesktopDialog def _processChange(self,answer): - #print('proces change: {}'.format(answer)) + print('process change: {}'.format(answer)) if answer == -1: self.changed.set(True) ACTION.ABORT() @@ -517,6 +539,16 @@ def _processChange(self,answer): self.record_geometry() STATUS.emit('focus-overlay-changed', False, None, None) + # decode button presses + def msgbtn(self, i): + LOG.debug('Button pressed is: {}'.format(i.text())) + if self.clickedButton() == self._actionbutton: + self._processChange(-1) + elif self.standardButton(self.clickedButton()) == QMessageBox.Ok: + self._processChange(True) + else: + self._processChange(False) + ###### overridden functions ################ def showdialog(self, message, more_info=None, details=None, @@ -558,16 +590,6 @@ def showEvent(self, event): self.set_geometry() super(LcncDialog, self).showEvent(event) - # decode button presses - def msgbtn(self, i): - LOG.debug('Button pressed is: {}'.format(i.text())) - if self.clickedButton() == self._actionbutton: - self._processChange(-1) - elif self.standardButton(self.clickedButton()) == QMessageBox.Ok: - self._processChange(True) - else: - self._processChange(False) - ############################################ # ********************** From 04302d095a167750fe5ff7a9017a50c6b1447b7c Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:13:51 -0700 Subject: [PATCH 19/42] qtdragon -add status control of toolchange dialog so you can use halui to accept the toolchange --- share/qtvcp/screens/qtdragon/qtdragon_handler.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index 60ec42b286d..d3f27af3ef5 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -901,6 +901,7 @@ def lastRunLine(self, line): # called from hal_glib to run macros from external event def request_macro_call(self, data): + print('macro request:',data) if not STATUS.is_mdi_mode(): self.add_status(_translate("HandlerClass",'Machine must be in MDI mode to run macros'), CRITICAL) return @@ -925,6 +926,8 @@ def request_macro_call(self, data): self.add_status(_translate("HandlerClass",'Running macro: {} {}'.format(key, text))) button.click() break + else: + self.add_status(_translate("HandlerClass","can't find button for macro: {}".format(data))) ####################### # CALLBACKS FROM FORM # @@ -2083,9 +2086,14 @@ def external_mpg(self, count): def dialog_ext_control(self, pin, value, answer): if value: + # handler defined dialog? if not self._dialog_message is None: name = self._dialog_message.get('NAME') STATUS.emit('dialog-update',{'NAME':name,'response':answer}) + else: + # tool change dialog? + if self.w.toolDialog_.isVisible(): + STATUS.emit('dialog-update',{'NAME':'TOOLCHANGE','response':answer}) def log_version(self): if INFO.RIP_FLAG: From b35b5803d43b1de55ffce5e9ef8e440f6bb382d5 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:16:35 -0700 Subject: [PATCH 20/42] gmoccapy -add gstat message control of start and pause --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 8a2ad06fec3..95ae4484944 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -419,6 +419,8 @@ def __init__(self, argv): self.GSTAT.connect("graphics-gcode-properties", self.on_gcode_properties) self.GSTAT.connect("file-loaded", self.on_hal_status_file_loaded) self.GSTAT.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) + self.GSTAT.connect('cycle-start-request', lambda w, state :self.request_start(state)) + self.GSTAT.connect('cycle-pause-request', lambda w, state: self.request_pause(state)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -1352,6 +1354,14 @@ def _make_joints_button(self): self.joints_button_dic[name] = btn + def request_start(self,data): + print('start') + self.widgets.btn_run.emit('clicked') + + def request_pause(self,data): + print('pause') + self.widgets.tbtn_pause.emit('clicked') + # call INI macro (from hal_glib message) def request_macro_call(self, data): # if MDI command change to MDI and run From 161e252d0ee4bccb0cce62f01a2ee5446171c4bc Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:19:59 -0700 Subject: [PATCH 21/42] gmoccapy -add a halui test config with a sim test panel --- configs/sim/gmoccapy/gmoccapy_halui_test.ini | 225 ++++++++++++ configs/sim/gmoccapy/panel.hal | 24 ++ configs/sim/gmoccapy/panel.ui | 365 +++++++++++++++++++ 3 files changed, 614 insertions(+) create mode 100644 configs/sim/gmoccapy/gmoccapy_halui_test.ini create mode 100644 configs/sim/gmoccapy/panel.hal create mode 100644 configs/sim/gmoccapy/panel.ui diff --git a/configs/sim/gmoccapy/gmoccapy_halui_test.ini b/configs/sim/gmoccapy/gmoccapy_halui_test.ini new file mode 100644 index 00000000000..27579c0b389 --- /dev/null +++ b/configs/sim/gmoccapy/gmoccapy_halui_test.ini @@ -0,0 +1,225 @@ +# EMC controller parameters for a simulated machine. +# General note: Comments can either be preceded with a # or ; - either is +# acceptable, although # is in keeping with most linux config files. + +# General section ------------------------------------------------------------- +[EMC] +VERSION = 1.1 +MACHINE = gmoccapy +DEBUG = 0 + +# Sections for display options ------------------------------------------------ +[DISPLAY] +DISPLAY = gmoccapy -i +# Log level: +# DEBUG -d +# INFO -i +# VERBOSE -v +# ERROR -q + +# Cycle time, in milliseconds, that display will sleep between polls +CYCLE_TIME = 100 + +# Values that will be allowed for override, 1.0 = 100% +MAX_FEED_OVERRIDE = 1.5 +MAX_SPINDLE_OVERRIDE = 1.2 +MIN_SPINDLE_OVERRIDE = 0.5 + +# Initial value for spindle speed +DEFAULT_SPINDLE_SPEED = 450 + +# The following are not used, added here to suppress warnings (from qt_istat/logger). +DEFAULT_LINEAR_VELOCITY = 35 +MIN_LINEAR_VELOCITY = 0 +MAX_LINEAR_VELOCITY = 234 +DEFAULT_SPINDLE_0_SPEED = 500 +MIN_SPINDLE_0_SPEED = 0 +MAX_SPINDLE_0_SPEED = 3000 +MAX_SPINDLE_0_OVERRIDE = 1.2 +MIN_SPINDLE_0_OVERRIDE = 0.5 + +# Prefix to be used +PROGRAM_PREFIX = ../../nc_files/ + +# Introductory graphic +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 + +# list of selectable jog increments +INCREMENTS = 1.000 mm, 0.100 mm, 0.010 mm, 0.001 mm, 1.2345 inch + +# for details see nc_files/subroutines/maco_instructions.txt +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image +PROGRAM_EXTENSION = .py Python Script +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +# Task controller section ----------------------------------------------------- +[RS274NGC] +RS274NGC_STARTUP_CODE = G17 G21 G40 G43H0 G54 G64P0.005 G80 G90 G94 G97 M5 M9 +PARAMETER_FILE = sim.var +SUBROUTINE_PATH = ./macros +REMAP=M6 modalgroup=6 prolog=change_prolog ngc=change_g43 epilog=change_epilog +REMAP=M61 modalgroup=6 prolog=settool_prolog ngc=settool_g43 epilog=settool_epilog + +# the Python plugins serves interpreter and task +[PYTHON] +PATH_PREPEND = ./python +TOPLEVEL = ./python/toplevel.py +LOG_LEVEL = 0 + +# Motion control section ------------------------------------------------------ +[EMCMOT] +EMCMOT = motmod +COMM_TIMEOUT = 1.0 +BASE_PERIOD = 100000 +SERVO_PERIOD = 1000000 + +# Hardware Abstraction Layer section -------------------------------------------------- +[TASK] +TASK = milltask +CYCLE_TIME = 0.001 + +# Part program interpreter section -------------------------------------------- +[HAL] +HALFILE = core_sim.hal +HALFILE = spindle_sim.hal +HALFILE = simulated_home.hal + +# Single file that is executed after the GUI has started. +POSTGUI_HALFILE = gmoccapy_postgui.hal +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel + +HALUI = halui + +# Trajectory planner section -------------------------------------------------- +[HALUI] +#No Content + +[MDI_COMMAND_LIST] +# for macro buttons on main oage up to 10 possible +MDI_COMMAND_MACRO0 = G0 Z1;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero +MDI_COMMAND_MACRO2 = (MSG, macro 2); +MDI_COMMAND_MACRO3 = (MSG, macro 2) +MDI_COMMAND_MACRO4 = (MSG, macro 2),test +[TRAJ] +COORDINATES = X Y Z +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 35 +MAX_LINEAR_VELOCITY = 234 +POSITION_FILE = position.txt +#NO_FORCE_HOMING = 1 + +[EMCIO] +# tool table file +TOOL_TABLE = tool.tbl +TOOL_CHANGE_POSITION = 100 100 -10 +TOOL_CHANGE_QUILL_UP = 1 + +[KINS] +KINEMATICS = trivkins coordinates=xyz +JOINTS = 3 + +[AXIS_X] +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_0] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 0.0 +HOME = 10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 +HOME_IS_SHARED = 1 + +# Second axis +[AXIS_Y] +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_1] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 0.0 +HOME = 10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 + +# Third axis +[AXIS_Z] +MIN_LIMIT = -400.0 +MAX_LIMIT = 0.001 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_2] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 0.001 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 1.0 +HOME = -10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 0 +HOME_IS_SHARED = 1 + +# section for main IO controller parameters ----------------------------------- +[MACROS] +MACRO = go_to_position x-pos y-pos z-pos +MACRO = i_am_lost +MACRO = increment x-incr y-incr +MACRO = macro_4 +MACRO = macro_5 +MACRO = macro_6 +MACRO = macro_7 +MACRO = macro_8 +MACRO = macro_9 +MACRO = macro_10 +MACRO = macro_11 +MACRO = macro_12 +MACRO = macro_13 +MACRO = macro_14 +MACRO = macro_15 + + diff --git a/configs/sim/gmoccapy/panel.hal b/configs/sim/gmoccapy/panel.hal new file mode 100644 index 00000000000..d48117a20ff --- /dev/null +++ b/configs/sim/gmoccapy/panel.hal @@ -0,0 +1,24 @@ +net rate halui.axis.jog-speed panel.Jog-rate-f + +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +net m2 halui.gui.mdi-command-MACRO2 panel.mdi-2 + +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + +net pause halui.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui new file mode 100644 index 00000000000..c671b279767 --- /dev/null +++ b/configs/sim/gmoccapy/panel.ui @@ -0,0 +1,365 @@ + + + MainWindow + + + + 0 + 0 + 313 + 467 + + + + MainWindow + + + + + + + + + Axis Selection + + + + + + None + + + true + + + true + + + axis-none + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + 300 + + + Qt::Horizontal + + + Jog-rate + + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + true + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + true + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + true + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + Abort + + + cycle-abort + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + + + 0 + 0 + 313 + 22 + + + + + + + + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Slider + QSlider +
qtvcp.widgets.simple_widgets
+
+
+ + +
From 1e67e3579e8e1079a98962286f57f3f380b700b4 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:24:15 -0700 Subject: [PATCH 22/42] qtdragon -update test config test panel --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 24 ++-- configs/sim/qtdragon/qtdragon_xyz/panel.ui | 130 +++++++++++++++++- .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 4 +- 3 files changed, 144 insertions(+), 14 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index e9407a91feb..a5c68d8da0c 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -1,17 +1,19 @@ -net rate halui.axis.jog-speed panel.Jog-rate-f +net rate halui.axis.jog-speed panel.Jog-rate-f -net sx halui.axis.x.select panel.axis-x -net sy halui.axis.y.select panel.axis-y -net sz halui.axis.z.select panel.axis-z +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z -net jog-p halui.axis.selected.plus -net jog-m halui.axis.selected.minus -net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 -net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg -net pause halui.cycle.start panel.cycle-start -net start halui.cycle.pause panel.cycle-pause +#net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +#net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +#net m2 halui.gui.mdi-command-0 panel.mdi-2 -net cancel halui.gui.cancel panel.cancel +net pause halui.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause + +net cancel halui.gui.cancel panel.cancel net ok halui.gui.ok panel.ok diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index d9200fc614e..c671b279767 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -23,11 +23,33 @@ Axis Selection + + + + None + + + true + + + true + + + axis-none + + + X + + true + + + true + axis-x @@ -38,6 +60,12 @@ Y + + true + + + true + axis-y @@ -48,6 +76,12 @@ Z + + true + + + true + axis-z @@ -146,6 +180,90 @@ + + + + Mode Comands + + + + + + Manual + + + true + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + true + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + true + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + @@ -163,7 +281,7 @@ - + pause @@ -172,6 +290,16 @@ + + + + Abort + + + cycle-abort + + + diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index ca436b20837..e7a674f81b2 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini @@ -10,7 +10,7 @@ DEBUG = 0x00000000 # sets qtdragon as screen. for debug output to terminal add -d or -v # sets window title # sets icon in task manager -DISPLAY = qtvcp qtdragon +DISPLAY = qtvcp -d qtdragon TITLE = QtDragon XYZ Metric ICON = silver_dragon.png @@ -167,7 +167,7 @@ POSTGUI_HALFILE = qtdragon_postgui.hal # uncomment this one to print all HAL pins that start with qt #POSTGUI_HALCMD = show pin qt POSTGUI_HALCMD = show pin halui.gui -POSTGUI_HALCMD = loadusr qtvcp -H panel.hal panel +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel [HALUI] # no content From ad6346268ca8f278e5f39c95ba727aaca208289b Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 18:10:59 -0700 Subject: [PATCH 23/42] qtvcp/gladevcp -action: add ability; return to mode after INI mdi --- lib/python/gladevcp/gtk_action.py | 12 +++++++++++- lib/python/qtvcp/qt_action.py | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index 548a3255acc..ab22f063f7b 100644 --- a/lib/python/gladevcp/gtk_action.py +++ b/lib/python/gladevcp/gtk_action.py @@ -191,7 +191,7 @@ def CALL_MDI_WAIT(self, code, time=5, mode_return=False): self.ensure_mode(premode) return 0 - def CALL_INI_MDI(self, key): + def CALL_INI_MDI(self, key, mode_return = False): try: # prefer named INI MDI commands mdi = INFO.get_ini_mdi_command(key) @@ -209,11 +209,21 @@ def CALL_INI_MDI(self, key): mdi_list = mdi.split(';') + if mode_return: + self.RECORD_CURRENT_MODE() + self._a = STATUS.connect('command-stopped', lambda w: self.return_mode_after_finish()) self.ensure_mode(linuxcnc.MODE_MDI) for code in (mdi_list): LOG.debug('CALL_INI_MDI command:{}'.format(code)) self.cmd.mdi('%s' % code) + # when command stops - we try to continue the generator. + # if generator is done - return to recorded mode. + def return_mode_after_finish(self): + print('ini command end') + self.RESTORE_RECORDED_MODE() + STATUS.handler_disconnect(self._a) + def CALL_OWORD(self, code, time=5): LOG.debug('OWORD_COMMAND= {}'.format(code)) self.ensure_mode(linuxcnc.MODE_MDI) diff --git a/lib/python/qtvcp/qt_action.py b/lib/python/qtvcp/qt_action.py index 2c5f18cedb0..3d57ef050c8 100644 --- a/lib/python/qtvcp/qt_action.py +++ b/lib/python/qtvcp/qt_action.py @@ -227,7 +227,7 @@ def CALL_MDI_WAIT(self, code, time=5, mode_return=False): self.ensure_mode(premode) return 0 - def CALL_INI_MDI(self, key): + def CALL_INI_MDI(self, key, mode_return = False): try: # prefer named INI MDI commands mdi = INFO.get_ini_mdi_command(key) @@ -244,11 +244,21 @@ def CALL_INI_MDI(self, key): return mdi_list = mdi.split(';') + if mode_return: + self.RECORD_CURRENT_MODE() + self._a = STATUS.connect('command-stopped', lambda w: self.return_mode_after_finish()) self.ensure_mode(linuxcnc.MODE_MDI) for code in (mdi_list): LOG.debug('CALL_INI_MDI command:{}'.format(code)) self.cmd.mdi('%s' % code) + # when command stops - we try to continue the generator. + # if generator is done - return to recorded mode. + def return_mode_after_finish(self): + print('ini command end') + self.RESTORE_RECORDED_MODE() + STATUS.handler_disconnect(self._a) + def CALL_OWORD(self, code, time=5): LOG.debug('OWORD_COMMAND= {}'.format(code)) self.ensure_mode(linuxcnc.MODE_MDI) From 51b33669d422a9d51a4d1c344f432f2459ea10b9 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 21:27:49 -0700 Subject: [PATCH 24/42] gmoccapy -used new mode return INI MDI function --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 95ae4484944..b2e391647fc 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1368,10 +1368,8 @@ def request_macro_call(self, data): cmd = self.INFO.get_ini_mdi_command(data) print('MDI command:',data,cmd) if not cmd is None: - self.ACTION.RECORD_CURRENT_MODE() LOG.debug("INI MDI COMMAND #: {} = {}".format(data, cmd)) - self.ACTION.CALL_INI_MDI(data) - self.ACTION.RESTORE_RECORDED_MODE() + self.ACTION.CALL_INI_MDI(data,mode_return = True) return # run Macros From ca045bf84458bee3551ced3d9ae66f7977d24854 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 5 Sep 2025 20:39:53 -0700 Subject: [PATCH 25/42] halui -fix memory leaks --- lib/python/bridgeui/bridge.py | 2 +- src/emc/usr_intf/halui.cc | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index 27397528660..f2377f26100 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -97,7 +97,7 @@ def init_read(self): self.readSocket.connect(self.readAddress) # callback from ZMQ read socket - def readMsg(self, msg): + def readMsg(self): if self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: while self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: # get raw message diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index fc86de990ed..893a97f68f3 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -1875,9 +1875,8 @@ static void py_call_axis_jogspeed(double speed) if (pValue == NULL){ fprintf(stderr, "halui bridge: writeMsg function failed: returned NULL\n"); if (PyErr_Occurred()) PyErr_Print(); - }else{ - Py_DECREF(pValue); } + Py_DECREF(pValue); }else{ if (PyErr_Occurred()) PyErr_Print(); @@ -1924,9 +1923,8 @@ static void py_call_axis_changed( int axis) if (pValue == NULL){ fprintf(stderr, "halui bridge: writeMsg function failed: returned NULL\n"); if (PyErr_Occurred()) PyErr_Print(); - }else{ - Py_DECREF(pValue); } + Py_DECREF(pValue); }else{ if (PyErr_Occurred()) PyErr_Print(); @@ -1944,9 +1942,8 @@ static void py_call_request_MDI( int index) if (pValue == NULL){ fprintf(stderr, "halui bridge: runIndexedMacro function failed: returned NULL\n"); if (PyErr_Occurred()) PyErr_Print(); - }else{ - Py_DECREF(pValue); } + Py_DECREF(pValue); }else{ if (PyErr_Occurred()) PyErr_Print(); @@ -1972,8 +1969,9 @@ static void check_hal_changes() // get python to process socket messages pFuncRead = PyObject_GetAttrString(pInstance, "readMsg"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { - pValue = PyObject_CallFunction(pFuncRead, "O", pClass); + pValue = PyObject_CallNoArgs(pFuncRead); if (pValue == NULL){ fprintf(stderr, "Halui Bridge: readMsg function failed: returned NULL\n"); } @@ -1985,6 +1983,8 @@ static void check_hal_changes() fprintf(stderr, "Bridge: Failed python function"); exit(1); } + Py_DECREF(pFuncRead); + Py_DECREF(pValue); // check socket messages for current axis selection int value = py_call_get_axis_selected(); @@ -2766,6 +2766,7 @@ int main(int argc, char *argv[]) fprintf(stderr, "bridge: Failed to load \"%s\"\n", "pyui"); exit(1); } + Py_DECREF(pClass); // get configuration information if (0 != iniLoad(emc_inifile)) { From 995ea778ea1042402a02952c2b3d44652f94fe76 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 5 Sep 2025 20:40:47 -0700 Subject: [PATCH 26/42] qtdragon -test files update --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 11 ++++++++--- configs/sim/qtdragon/qtdragon_xyz/panel.ui | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index a5c68d8da0c..04bc14e8e26 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -1,4 +1,4 @@ -net rate halui.axis.jog-speed panel.Jog-rate-f +net rate halui.axis.jog-speed panel.jog-rate net sx halui.axis.x.select panel.axis-x net sy halui.axis.y.select panel.axis-y @@ -7,12 +7,17 @@ net sz halui.axis.z.select panel.axis-z net jog-p halui.axis.selected.plus panel.jog-pos net jog-m halui.axis.selected.minus panel.jog-neg -#net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 -#net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 #net m2 halui.gui.mdi-command-0 panel.mdi-2 +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + net pause halui.cycle.start panel.cycle-start net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort net cancel halui.gui.cancel panel.cancel net ok halui.gui.ok panel.ok diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index c671b279767..74a4afbef7d 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -6,8 +6,8 @@ 0 0 - 313 - 467 + 404 + 560 @@ -126,15 +126,15 @@ - - - 300 - + Qt::Horizontal - Jog-rate + jog-rate + + + true @@ -341,7 +341,7 @@ 0 0 - 313 + 404 22 @@ -355,9 +355,9 @@
qtvcp.widgets.simple_widgets
- Slider + StatusSlider QSlider -
qtvcp.widgets.simple_widgets
+
qtvcp.widgets.status_slider
From 0626afffc58cf2a806019e4aa7386bf431075e55 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Sep 2025 22:09:20 -0700 Subject: [PATCH 27/42] hal_glib -add a function to run the gobject mainloop once so GUIs not basd in gobject can run the message system --- lib/python/common/hal_glib.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/python/common/hal_glib.py b/lib/python/common/hal_glib.py index df8f95a1528..a2dcc926e58 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -352,6 +352,11 @@ def __init__(self, stat = None): def set_timer(self): GLib.timeout_add(CYCLE_TIME, self.update) + # used to run the Gobject mainloop once + # allows a GUI that is not GLib based to update the mainloop + def run_iteration(self): + GLib.MainContext.default().iteration (True) + # open a zmq socket for writing out data def init_write_socket(self): context = zmq.Context() @@ -362,6 +367,7 @@ def init_write_socket(self): self.write_available = True except Exception as e: LOG.debug('hal_glib write socket not available: {}'.format(e)) + LOG.debug('hal_glib write socket not available\n {}'.format(e)) self.write_available = False # convert and actually send out the message From c3d9729f0b622031943e75651c5bf9ef5353345f Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Sep 2025 22:27:26 -0700 Subject: [PATCH 28/42] axis sinm -add a sim test for halui messages --- configs/sim/axis/axis_halui_test.ini | 225 +++++++++++++++++ configs/sim/axis/gstatmessages.py | 66 +++++ configs/sim/axis/panel.hal | 24 ++ configs/sim/axis/panel.ui | 365 +++++++++++++++++++++++++++ 4 files changed, 680 insertions(+) create mode 100644 configs/sim/axis/axis_halui_test.ini create mode 100644 configs/sim/axis/gstatmessages.py create mode 100644 configs/sim/axis/panel.hal create mode 100644 configs/sim/axis/panel.ui diff --git a/configs/sim/axis/axis_halui_test.ini b/configs/sim/axis/axis_halui_test.ini new file mode 100644 index 00000000000..877a91db9d8 --- /dev/null +++ b/configs/sim/axis/axis_halui_test.ini @@ -0,0 +1,225 @@ +# EMC controller parameters for a simulated machine. + +# General note: Comments can either be preceded with a # or ; - either is +# acceptable, although # is in keeping with most linux config files. + +# General section ------------------------------------------------------------- +[EMC] + +# Version of this INI file +VERSION = 1.1 + +# Name of machine, for use with display, etc. +MACHINE = LinuxCNC-HAL-SIM-AXIS + +# Debug level, 0 means no messages. See src/emc/nml_int/emcglb.h for others +#DEBUG = 0x7FFFFFFF +DEBUG = 0 + +# Sections for display options ------------------------------------------------ +[DISPLAY] + +# Name of display program, e.g., axis +DISPLAY = axis + +# Cycle time, in seconds, that display will sleep between polls +CYCLE_TIME = 0.100 + +# Path to help file +HELP_FILE = doc/help.txt + +# Initial display setting for position, RELATIVE or MACHINE +POSITION_OFFSET = RELATIVE + +# Initial display setting for position, COMMANDED or ACTUAL +POSITION_FEEDBACK = ACTUAL + +# Highest value that will be allowed for feed override, 1.0 = 100% +MAX_FEED_OVERRIDE = 1.2 +MAX_SPINDLE_OVERRIDE = 1.0 + +MAX_LINEAR_VELOCITY = 5 +DEFAULT_LINEAR_VELOCITY = .25 +DEFAULT_SPINDLE_SPEED = 200 +# Prefix to be used +PROGRAM_PREFIX = ../../nc_files/ + +# Introductory graphic +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 + +#EDITOR = geany +TOOL_EDITOR = tooledit + +INCREMENTS = 1 in, 0.1 in, 10 mil, 1 mil, 1mm, .1mm, 1/8000 in + +USER_COMMAND_FILE=gstatmessages.py + +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image +PROGRAM_EXTENSION = .py Python Script + +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +# Task controller section ----------------------------------------------------- +[TASK] + +# Name of task controller program, e.g., milltask +TASK = milltask + +# Cycle time, in seconds, that task controller will sleep between polls +CYCLE_TIME = 0.001 + +# Part program interpreter section -------------------------------------------- +[RS274NGC] + +# File containing interpreter variables +PARAMETER_FILE = sim.var + +# Motion control section ------------------------------------------------------ +[EMCMOT] + +EMCMOT = motmod + +# Timeout for comm to emcmot, in seconds +COMM_TIMEOUT = 1.0 + +# BASE_PERIOD is unused in this configuration but specified in core_sim.hal +BASE_PERIOD = 0 +# Servo task period, in nano-seconds +SERVO_PERIOD = 1000000 + +# section for main IO controller parameters ----------------------------------- +[EMCIO] +# tool table file +TOOL_TABLE = sim.tbl +TOOL_CHANGE_POSITION = 0 0 0 +TOOL_CHANGE_QUILL_UP = 1 + +# Hardware Abstraction Layer section -------------------------------------------------- +[HAL] + +# The run script first uses halcmd to execute any HALFILE +# files, and then to execute any individual HALCMD commands. +# + +# list of hal config files to run through halcmd +# files are executed in the order in which they appear +HALFILE = core_sim.hal +HALFILE = sim_spindle_encoder.hal +HALFILE = axis_manualtoolchange.hal +HALFILE = simulated_home.hal +HALFILE = check_xyz_constraints.hal +HALFILE = cooling.hal + +# list of halcmd commands to execute +# commands are executed in the order in which they appear +#HALCMD = save neta + +# Single file that is executed after the GUI has started. Only supported by +# AXIS at this time (only AXIS creates a HAL component of its own) +#POSTGUI_HALFILE = test_postgui.hal +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel + +HALUI = halui + +# Trajectory planner section -------------------------------------------------- +[TRAJ] +COORDINATES = X Y Z +LINEAR_UNITS = inch +ANGULAR_UNITS = degree +MAX_LINEAR_VELOCITY = 4 +DEFAULT_LINEAR_ACCELERATION = 100 +MAX_LINEAR_ACCELERATION = 100 +POSITION_FILE = position.txt + +[KINS] +KINEMATICS = trivkins +JOINTS = 3 + +# Axes sections --------------- +[AXIS_X] +MAX_VELOCITY = 4 +MAX_ACCELERATION = 100.0 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 + +[AXIS_Y] +MAX_VELOCITY = 4 +MAX_ACCELERATION = 100.0 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 + +[AXIS_Z] +MAX_VELOCITY = 4 +MAX_ACCELERATION = 100.0 +MIN_LIMIT = -8.0 +MAX_LIMIT = 0.12 + +# Joints sections ------------- +[JOINT_0] +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 5 +MAX_ACCELERATION = 50.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +FERROR = 0.050 +MIN_FERROR = 0.010 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 20.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 +HOME_IS_SHARED = 1 + +[JOINT_1] +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 5 +MAX_ACCELERATION = 50.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +FERROR = 0.050 +MIN_FERROR = 0.010 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 20.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 + +[JOINT_2] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 5 +MAX_ACCELERATION = 50.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -8.0 + +# Normally the Z max should be 0.000! +# The only reason it's greater than 0 here is so that the splash screen +# gcode will run. +MAX_LIMIT = 0.12 + +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 1.0 +HOME_SEARCH_VEL = 20.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 0 +HOME_IS_SHARED = 1 diff --git a/configs/sim/axis/gstatmessages.py b/configs/sim/axis/gstatmessages.py new file mode 100644 index 00000000000..5c9e1e95d16 --- /dev/null +++ b/configs/sim/axis/gstatmessages.py @@ -0,0 +1,66 @@ + +from hal_glib import GStat +GSTAT = GStat() +GSTAT.forced_update() +GSTAT.connect('jograte-changed', lambda w, data: vars.jog_speed.set(data)) +GSTAT.connect('axis-selection-changed', lambda w,data: select_axis(data)) +GSTAT.connect('cycle-start-request', lambda w, state : cycle_start_request(state)) +GSTAT.connect('cycle-pause-request', lambda w, state: pause_request(state)) +GSTAT.connect('ok-request', lambda w, state: dialog_ext_control(w,1,1)) +GSTAT.connect('cancel-request', lambda w, state: dialog_ext_control(w,1,0)) +GSTAT.connect('macro-call-request', lambda w, name: request_macro_call(name)) +def user_live_update(): + GSTAT.run_iteration() + +def select_axis(data): + if data is None: return + widget = getattr(widgets, "axis_%s" % data.lower()) + widget.focus() + widget.invoke() + +def cycle_start_request(state): + print('cycle start',state) + commands.task_run(None) + +def pause_request(state): + print('cycle pause',state) + commands.task_pauseresume(None) + +def dialog_ext_control(widget,t,state): + print('dialog control',widget,state) + + flag = False + for child in root_window.winfo_children(): + #print(child) + if isinstance(child, Tkinter.Toplevel): + #print(f"Found a Toplevel window: {child}") + if '.!toplevel' in str(child): + #print('sending command:',child) + for child2 in child.winfo_children(): + #print(child2) + if isinstance(child2, Tkinter.Frame): + for child3 in child2.winfo_children(): + #print(child3) + if isinstance(child3, Tkinter.Button): + #print(dir(child3)) + txt = child3.cget("text") + if txt.lower() == 'ok' and state: + #print('Ok') + child3.invoke() + flag = True + break + elif txt.lower() == 'cancel' and not state: + #print('Cancel') + child3.invoke() + flag = True + break + if flag: break + if flag: break + else: + #print('No window') + # remove one error message + if state == 0: + notifications.clear_one() + +def request_macro_call(name): + print('request macro:',name) diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal new file mode 100644 index 00000000000..5db03ed71da --- /dev/null +++ b/configs/sim/axis/panel.hal @@ -0,0 +1,24 @@ +net rate halui.axis.jog-speed panel.jog-rate + +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +#net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +#net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +#net m2 halui.gui.mdi-command-0 panel.mdi-2 + +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + +net pause halui.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + diff --git a/configs/sim/axis/panel.ui b/configs/sim/axis/panel.ui new file mode 100644 index 00000000000..74a4afbef7d --- /dev/null +++ b/configs/sim/axis/panel.ui @@ -0,0 +1,365 @@ + + + MainWindow + + + + 0 + 0 + 404 + 560 + + + + MainWindow + + + + + + + + + Axis Selection + + + + + + None + + + true + + + true + + + axis-none + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + true + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + true + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + true + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + Abort + + + cycle-abort + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + + + 0 + 0 + 404 + 22 + + + + + + + + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+
+ + +
From 3c9e488ea12c612d47b2e0144f73ab054dfb5a5b Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Sep 2025 22:34:12 -0700 Subject: [PATCH 29/42] qtdragon -add halui test sim --- .../qtdragon_xyz/qtdragon_halui_test.ini | 267 ++++++++++++++++++ .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 5 +- 2 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini new file mode 100644 index 00000000000..e7a674f81b2 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini @@ -0,0 +1,267 @@ +# This file was created with the 7i96 Wizard on Jun 10 2019 11:12:47 +# Changes to most things are ok and will be read by the wizard + +[EMC] +VERSION = 1.1 +MACHINE = qtdragon_metric +DEBUG = 0x00000000 + +[DISPLAY] +# sets qtdragon as screen. for debug output to terminal add -d or -v +# sets window title +# sets icon in task manager +DISPLAY = qtvcp -d qtdragon +TITLE = QtDragon XYZ Metric +ICON = silver_dragon.png + +# qtdragon saves most preference to this file +PREFERENCE_FILE_PATH = WORKINGFOLDER/qtdragon.pref + +# min/max percentage overrides allowed in qtdragon 1 = 100% +MAX_FEED_OVERRIDE = 1.2 +MIN_SPINDLE_0_OVERRIDE = 0.5 +MAX_SPINDLE_0_OVERRIDE = 1.2 + +# manual spindle speed will start at this RPM +DEFAULT_SPINDLE_0_SPEED = 12000 + +# spindle up/down increment in RPM +SPINDLE_INCREMENT = 200 + +# min max apindle speed manually allowed +MIN_SPINDLE_0_SPEED = 1000 +MAX_SPINDLE_0_SPEED = 20000 + +# max spindle power in Watts +MAX_SPINDLE_POWER = 2000 + +# min/max/default jog velocities in qtdragon in units/sec +MIN_LINEAR_VELOCITY = 0 +MAX_LINEAR_VELOCITY = 60.00 +DEFAULT_LINEAR_VELOCITY = 50.0 + +# incremental jog step length options +INCREMENTS = 10 mm, 1.0 mm, 0.10 mm, 0.01 mm, 1.0 inch, 0.1 inch, 0.01 inch + +# Display grid increments +GRIDS = 0, .1 mm, 1 mm, 2 mm, 5 mm, 10 mm, .25 in, .5 in + +CYCLE_TIME = 100 +INTRO_GRAPHIC = silver_dragon.png +INTRO_TIME = 2 + +# default program search path +PROGRAM_PREFIX = ~/linuxcnc/nc_files + +# NGCGUI subroutine path. +# Thr path must also be in [RS274NGC] SUBROUTINE_PATH +NGCGUI_SUBFILE_PATH = ../../../nc_files/ngcgui_lib/ +# pre selected programs tabs +# specify filenames only, files must be in the NGCGUI_SUBFILE_PATH +NGCGUI_SUBFILE = slot.ngc +NGCGUI_SUBFILE = qpocket.ngc + +# qtdragon saves MDI cxommands to this file +MDI_HISTORY_FILE = mdi_history.dat +# qtdragon saves rnning logs to this file +LOG_FILE = qtdragon.log + +# optional user dialogs (3), controlled by HAL pins +MESSAGE_BOLDTEXT = Critical and Persistent +MESSAGE_TEXT = This is a persistent dialog test +MESSAGE_DETAILS = There seems to be something wrong\n You must fix it to clear message +MESSAGE_TYPE = nonedialog +MESSAGE_PINNAME = nonedialogtest +MESSAGE_ICON = CRITICAL + +MESSAGE_BOLDTEXT = Do You Want To Make A Choice? +MESSAGE_TEXT = This is a yes no dialog test +MESSAGE_DETAILS = Y/N DETAILS +MESSAGE_TYPE = yesnodialog +MESSAGE_PINNAME = yndialogtest +MESSAGE_ICON = QUESTION + +MESSAGE_BOLDTEXT = This is an information message +MESSAGE_TEXT = This is low priority +MESSAGE_DETAILS = press ok to clear +MESSAGE_TYPE = okdialog status +MESSAGE_PINNAME = bothtest +MESSAGE_ICON = INFO + +# optional tab showing an external qtvcp panel +EMBED_TAB_NAME=Vismach demo +EMBED_TAB_COMMAND=qtvcp vismach_mill_xyz +EMBED_TAB_LOCATION=tabWidget_utilities + +[MDI_COMMAND_LIST] +# for macro buttons on main oage up to 10 possible +MDI_COMMAND_MACRO0 = G0 Z25;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + +[FILTER] +# Controls what programs are shown inqtdragon file manager +PROGRAM_EXTENSION = .ngc,.nc,.tap G-Code File (*.ngc,*.nc,*.tap) +PROGRAM_EXTENSION = .png,.gif,.jpg Greyscale Depth Image +PROGRAM_EXTENSION = .py Python Script + +# specifies what special 'filter' programs runs based on program ending +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +[KINS] +KINEMATICS = trivkins coordinates=XYZ +JOINTS = 3 + +[EMCIO] +TOOL_TABLE = tool.tbl + +[RS274NGC] +# motion controller saves parameters to this file +PARAMETER_FILE = qtdragon.var + +# start up G/M codes when first loaded +RS274NGC_STARTUP_CODE = G17 G21 G40 G43H0 G54 G64P0.0127 G80 G90 G94 G97 M5 M9 + +# subroutine/remap path list +SUBROUTINE_PATH = ../../../../nc_files/probe/basic_probe/macros:~/linuxcnc/nc_files/examples/ngcgui_lib:~/linuxcnc/nc_files/examples/ngcgui_lib/utilitysubs + +# on abort, this ngc file is called. required for basic/versa probe +ON_ABORT_COMMAND=O call + +[EMCMOT] +EMCMOT = motmod +SERVO_PERIOD = 1000000 +COMM_TIMEOUT = 1.0 +COMM_WAIT = 0.010 +BASE_PERIOD = 100000 + +[TASK] +TASK = milltask +CYCLE_TIME = 0.010 + +[TRAJ] +COORDINATES = XYZ +LINEAR_UNITS = metric +ANGULAR_UNITS = degree +MAX_LINEAR_VELOCITY = 60.00 +DEFAULT_LINEAR_VELOCITY = 50.00 +SPINDLES = 1 + +[HAL] +HALUI = halui +#HALBRIDGE = hal_bridge + +# loads the HAL machine simulation +HALFILE = core_sim.hal +HALFILE = simulated_home.hal + +# this file is loaded after qtdragon has made it's HAl pins +# you can add multiple entries +POSTGUI_HALFILE = qtdragon_postgui.hal + +# this command is run after qtdragon has made it's HAl pins +# any HAL conmmand can be used +# you can add multiple entries +# uncomment this one to print all HAL pins that start with qt +#POSTGUI_HALCMD = show pin qt +POSTGUI_HALCMD = show pin halui.gui +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel +[HALUI] +# no content + +[PROBE] +# pick basic probe or versa probe or remove for none +#USE_PROBE = versaprobe +USE_PROBE = basicprobe + +[AXIS_X] +MIN_LIMIT = -0.001 +MAX_LIMIT = 520.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 + +[AXIS_Y] +MIN_LIMIT = -0.001 +MAX_LIMIT = 630.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 + +[AXIS_Z] +# used by external offsets for auto spindle lift +OFFSET_AV_RATIO = 0.2 +MIN_LIMIT = -115.0 +MAX_LIMIT = 10.0 +MAX_VELOCITY = 40.0 +MAX_ACCELERATION = 500.0 + +[JOINT_0] +AXIS = X +MIN_LIMIT = -0.001 +MAX_LIMIT = 520.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 +TYPE = LINEAR +SCALE = 160.0 +STEPGEN_MAX_VEL = 72.0 +STEPGEN_MAX_ACC = 600.0 +FERROR = 1.0 +MIN_FERROR = 0.5 +MAX_OUTPUT = 0 +MAX_ERROR = 0.0127 +HOME = 20.0 +HOME_OFFSET = 0.00000 +HOME_SEARCH_VEL = 20.000000 +HOME_LATCH_VEL = 10.000 +HOME_SEQUENCE = 1 +HOME_USE_INDEX = False +HOME_IGNORE_LIMITS = False +HOME_IS_SHARED = 1 + +[JOINT_1] +AXIS = Y +MIN_LIMIT = -0.001 +MAX_LIMIT = 630.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 +TYPE = LINEAR +SCALE = 160.0 +STEPGEN_MAX_VEL = 72.0 +STEPGEN_MAX_ACC = 600.0 +FERROR = 1.0 +MIN_FERROR = 0.5 +MAX_OUTPUT = 0 +MAX_ERROR = 0.0127 +HOME = 20.0 +HOME_OFFSET = 0.000000 +HOME_SEARCH_VEL = 20.00 +HOME_LATCH_VEL = 10.00 +HOME_SEQUENCE = 2 +HOME_USE_INDEX = False +HOME_IGNORE_LIMITS = False + +[JOINT_2] +AXIS = Z +MIN_LIMIT = -115.0 +MAX_LIMIT = 10.0 +MAX_VELOCITY = 40.0 +MAX_ACCELERATION = 500.0 +TYPE = LINEAR +SCALE = 160.0 +STEPGEN_MAX_VEL = 48.0 +STEPGEN_MAX_ACC = 600.0 +FERROR = 1.0 +MIN_FERROR = 0.5 +MAX_OUTPUT = 0 +MAX_ERROR = 0.0127 +HOME = -10.0 +HOME_OFFSET = 0.000000 +HOME_SEARCH_VEL = 20.000000 +HOME_LATCH_VEL = 10.00 +HOME_SEQUENCE = 0 +HOME_USE_INDEX = False +HOME_IGNORE_LIMITS = False +HOME_IS_SHARED = 1 + + diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index e7a674f81b2..a00b52ab427 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini @@ -10,7 +10,7 @@ DEBUG = 0x00000000 # sets qtdragon as screen. for debug output to terminal add -d or -v # sets window title # sets icon in task manager -DISPLAY = qtvcp -d qtdragon +DISPLAY = qtvcp qtdragon TITLE = QtDragon XYZ Metric ICON = silver_dragon.png @@ -166,8 +166,7 @@ POSTGUI_HALFILE = qtdragon_postgui.hal # you can add multiple entries # uncomment this one to print all HAL pins that start with qt #POSTGUI_HALCMD = show pin qt -POSTGUI_HALCMD = show pin halui.gui -POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel + [HALUI] # no content From 5fdba0ce56ee84cb39453589cf511472d8cc58a3 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 17 Oct 2025 21:47:47 -0700 Subject: [PATCH 30/42] test panel - make mode buttons not checkable The indicator shows state, so we don't need to chow checked state. in a real panel the buttons would be momentary buttons anyways --- configs/sim/axis/panel.ui | 6 +++--- configs/sim/gmoccapy/panel.ui | 12 ++++++------ configs/sim/qtdragon/qtdragon_xyz/panel.ui | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/configs/sim/axis/panel.ui b/configs/sim/axis/panel.ui index 74a4afbef7d..7264d58f6d0 100644 --- a/configs/sim/axis/panel.ui +++ b/configs/sim/axis/panel.ui @@ -192,7 +192,7 @@ Manual - true + false true @@ -217,7 +217,7 @@ MDI - true + false true @@ -242,7 +242,7 @@ Auto - true + false true diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui index c671b279767..6ed71542f13 100644 --- a/configs/sim/gmoccapy/panel.ui +++ b/configs/sim/gmoccapy/panel.ui @@ -6,8 +6,8 @@ 0 0 - 313 - 467 + 404 + 537 @@ -195,7 +195,7 @@ true - true + false manual-mode @@ -220,7 +220,7 @@ true - true + false mdi-mode @@ -242,7 +242,7 @@ Auto - true + false true @@ -341,7 +341,7 @@ 0 0 - 313 + 404 22 diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index 74a4afbef7d..7264d58f6d0 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -192,7 +192,7 @@ Manual - true + false true @@ -217,7 +217,7 @@ MDI - true + false true @@ -242,7 +242,7 @@ Auto - true + false true From e953f30c03f137f608738a5450554b414328031a Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 17 Oct 2025 21:50:03 -0700 Subject: [PATCH 31/42] gmoccapy -getinfo.py: fix macro search title this must have been fix in master after I branched --- src/emc/usr_intf/gmoccapy/getiniinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/getiniinfo.py b/src/emc/usr_intf/gmoccapy/getiniinfo.py index 38ae7970a85..63077dc6a50 100644 --- a/src/emc/usr_intf/gmoccapy/getiniinfo.py +++ b/src/emc/usr_intf/gmoccapy/getiniinfo.py @@ -393,7 +393,7 @@ def get_tool_sensor_data(self): def get_macros(self): # lets look in the INI file, if there are any entries - macros = self.inifile.findall("DISPLAY", "MACRO") + macros = self.inifile.findall("MACROS", "MACRO") # If there are no entries we will return False if not macros: return False From 4d554f856bcef6d572fd7c7c80effc6b4571730e Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 15 Nov 2025 14:50:23 -0800 Subject: [PATCH 32/42] halui switch u32 to s32 s32 is more common the u32 (easier to connect to) also axis select uses -1 as 'unselected' --- src/emc/usr_intf/halui.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 893a97f68f3..5b7facc0220 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -100,7 +100,7 @@ static int axis_mask = 0; FIELD(hal_bit_t,program_bd_off) /* pin for setting block delete off */ \ FIELD(hal_bit_t,program_bd_is_on) /* status pin that block delete is on */ \ \ - FIELD(hal_u32_t,tool_number) /* pin for current selected tool */ \ + FIELD(hal_s32_t,tool_number) /* pin for current selected tool */ \ FIELD(hal_float_t,tool_length_offset_x) /* current applied x tool-length-offset */ \ FIELD(hal_float_t,tool_length_offset_y) /* current applied y tool-length-offset */ \ FIELD(hal_float_t,tool_length_offset_z) /* current applied z tool-length-offset */ \ @@ -135,8 +135,8 @@ static int axis_mask = 0; ARRAY(hal_bit_t,joint_on_hard_max_limit,EMCMOT_MAX_JOINTS+1) /* status pin that the joint is on the hardware max limit */ \ ARRAY(hal_bit_t,joint_override_limits,EMCMOT_MAX_JOINTS+1) /* status pin that the joint is on the hardware max limit */ \ ARRAY(hal_bit_t,joint_has_fault,EMCMOT_MAX_JOINTS+1) /* status pin that the joint has a fault */ \ - FIELD(hal_u32_t,joint_selected) /* status pin for the joint selected */ \ - FIELD(hal_u32_t,axis_selected) /* status pin for the axis selected */ \ + FIELD(hal_s32_t,joint_selected) /* status pin for the joint selected */ \ + FIELD(hal_s32_t,axis_selected) /* status pin for the axis selected */ \ \ ARRAY(hal_bit_t,joint_nr_select,EMCMOT_MAX_JOINTS) /* nr. of pins to select a joint */ \ ARRAY(hal_bit_t,axis_nr_select,EMCMOT_MAX_AXIS) /* nr. of pins to select a axis */ \ @@ -855,11 +855,11 @@ int halui_hal_init(void) if (retval < 0) return retval; retval = hal_pin_float_newf(HAL_OUT, &(halui_data->ro_value), comp_id, "halui.rapid-override.value"); if (retval < 0) return retval; - retval = hal_pin_u32_newf(HAL_OUT, &(halui_data->joint_selected), comp_id, "halui.joint.selected"); + retval = hal_pin_s32_newf(HAL_OUT, &(halui_data->joint_selected), comp_id, "halui.joint.selected"); if (retval < 0) return retval; - retval = hal_pin_u32_newf(HAL_OUT, &(halui_data->axis_selected), comp_id, "halui.axis.selected"); + retval = hal_pin_s32_newf(HAL_OUT, &(halui_data->axis_selected), comp_id, "halui.axis.selected"); if (retval < 0) return retval; - retval = hal_pin_u32_newf(HAL_OUT, &(halui_data->tool_number), comp_id, "halui.tool.number"); + retval = hal_pin_s32_newf(HAL_OUT, &(halui_data->tool_number), comp_id, "halui.tool.number"); if (retval < 0) return retval; retval = hal_pin_float_newf(HAL_OUT, &(halui_data->tool_length_offset_x), comp_id, "halui.tool.length_offset.x"); if (retval < 0) return retval; From 4ce34f1388c32d4459937aedd96b9a0462027c5c Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 12 Dec 2025 20:13:10 -0800 Subject: [PATCH 33/42] halui -add mpg_select0 pin Some GUIs use the MPG for other things then jogging. Qtdragon uses MPG for scrolling and needs a selected pin. --- lib/python/bridgeui/bridge.py | 23 +++++++++++++++++++---- src/emc/usr_intf/halui.cc | 28 +++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index f2377f26100..56577ebb664 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -45,7 +45,7 @@ def __init__(self, readAddress = "tcp://127.0.0.1:5690", self.currentSelectedAxis = 'None' self.axesSelected = {'X':0,'Y':0,'Z':0,'A':0,'B':0,'C':0, - 'U':0,'V':0,'W':0} + 'U':0,'V':0,'W':0,'MPG0':0} self.readAddress = readAddress self.writeAddress = writeAddress LOG.debug('read port: {}'.format(readAddress)) @@ -108,7 +108,7 @@ def readMsg(self): # set our variables from messages from hal_glib def action(self, msg, data): - LOG.debug('{} {}'.format(msg, data)) + LOG.debug('{} -> {} -> {}'.format(msg, data, data[0])) if msg == 'jograte-changed': self.jogRate = float(data[0]) elif msg == 'jograte-angular-changed': @@ -120,7 +120,14 @@ def action(self, msg, data): elif msg == 'joint-selection-changed': self.activeJoint = int(data[0]) elif msg == 'axis-selection-changed': + print ('pre axis state', self.axesSelected,self.currentSelectedAxis) flag = 1 + if data[0] == 'MPG0': + self.currentSelectedAxis = data[0] + flag = 0 + self.axesSelected['MPG0'] = True + else: + self.axesSelected['MPG0'] = False for i in(self.INFO.AVAILABLE_AXES): if data[0] == i: state = True @@ -129,9 +136,10 @@ def action(self, msg, data): else: state = False self.axesSelected[i] = int(state) + if flag: self.currentSelectedAxis = 'None' - #print ('axis state', self.axesSelected) + print ('axis state', self.axesSelected,self.currentSelectedAxis) # send msg to hal_glib def writeMsg(self, msg, data): @@ -198,18 +206,25 @@ def getSelectedAxis(self): name = self.currentSelectedAxis if name == 'None': index = -1 + elif name =='MPG0': + index = 100 else: index = 'XYZABCUVW'.index(name) return index def setSelectedAxis(self, value): if value < 0: letter = 'None' + elif value == 100: + letter = 'MPG0' else: letter ='XYZABCUVW'[value] self.writeMsg('set_selected_axis', letter) def isAxisSelected(self, index): - letter = 'XYZABCUVW'[index] + if index == 100: + letter = 'MPG0' + else: + letter = 'XYZABCUVW'[index] return int(self.axesSelected[letter]) def __getitem__(self, item): diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 5b7facc0220..1c87144d2cc 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -140,6 +140,7 @@ static int axis_mask = 0; \ ARRAY(hal_bit_t,joint_nr_select,EMCMOT_MAX_JOINTS) /* nr. of pins to select a joint */ \ ARRAY(hal_bit_t,axis_nr_select,EMCMOT_MAX_AXIS) /* nr. of pins to select a axis */ \ + FIELD(hal_bit_t,mpg_select0)\ \ ARRAY(hal_bit_t,joint_is_selected,EMCMOT_MAX_JOINTS) /* nr. of status pins for joint selected */ \ ARRAY(hal_bit_t,axis_is_selected,EMCMOT_MAX_AXIS) /* nr. of status pins for axis selected */ \ @@ -1032,6 +1033,9 @@ int halui_hal_init(void) if (retval < 0) return retval; } + retval = halui_export_pin_IN_bit(&(halui_data->mpg_select0), "halui.mpg-select.0"); + if (retval < 0) return retval; + retval = hal_pin_bit_newf(HAL_IN, &(halui_data->joint_home[num_joints]), comp_id, "halui.joint.selected.home"); if (retval < 0) return retval; retval = hal_pin_bit_newf(HAL_IN, &(halui_data->joint_unhome[num_joints]), comp_id, "halui.joint.selected.unhome"); @@ -1759,6 +1763,7 @@ static void hal_init_pins() for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { if ( !(axis_mask & (1 << axis_num)) ) { continue; } *(halui_data->axis_nr_select[axis_num]) = old_halui_data.axis_nr_select[axis_num] = 0; + *(halui_data->mpg_select0) = old_halui_data.mpg_select0 = 0; *(halui_data->ajog_minus[axis_num]) = old_halui_data.ajog_minus[axis_num] = 0; *(halui_data->ajog_plus[axis_num]) = old_halui_data.ajog_plus[axis_num] = 0; *(halui_data->ajog_analog[axis_num]) = old_halui_data.ajog_analog[axis_num] = 0; @@ -2002,6 +2007,11 @@ static void check_hal_changes() *(halui_data->axis_nr_select[axis_num]) = 0; } } + if (value == 100) { + *(halui_data->mpg_select0) = 1; + }else{ + *(halui_data->mpg_select0) = 0; + } lastaxis = value; } @@ -2334,6 +2344,7 @@ static void check_hal_changes() is_any_axis_selected = 0; deselected = 0; for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { + if ( !(axis_mask & (1 << axis_num)) ) { continue; } // axis jog - @@ -2397,7 +2408,22 @@ static void check_hal_changes() old_halui_data.axis_nr_select[axis_num] = bit; } } - // last axis has been deselected - no axis is selected now + + // is MPG0 selected? + bit = new_halui_data.mpg_select0; + if (bit != old_halui_data.mpg_select0) { + if (bit != 0) { + is_any_axis_selected = 1; + py_call_axis_changed(100); + *halui_data->axis_selected = 100; + }else{ + deselected = 1; + } + old_halui_data.mpg_select0 = bit; + } + + + // last axis has been deselected - no axis is selected now if (is_any_axis_selected == 0 and deselected == 1) { py_call_axis_changed(-1); *halui_data->axis_selected = -1; From 0f6ac8e1dc8d4dd6a9a8ef6a362cb9a2d676dca0 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 12 Dec 2025 20:15:21 -0800 Subject: [PATCH 34/42] qtdragon -add mpg_select button logic control --- share/qtvcp/screens/qtdragon/qtdragon_handler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index d3f27af3ef5..fdd6c6330c5 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -1430,9 +1430,12 @@ def MPG_select_changed(self, button): if button == self.w.btn_mpg_scroll: self.removeMPGFocusBorder() + if not self.w.btn_mpg_scroll.isChecked(): + ACTION.SET_SELECTED_AXIS('None') return if button == self.w.btn_mpg_scroll: if self.w.btn_mpg_scroll.isChecked(): + ACTION.SET_SELECTED_AXIS('MPG0') self.recolorMPGFocusBorder() else: self.removeMPGFocusBorder() From 5203648a8bde914706513ffb92439fb162295051 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 12 Dec 2025 22:06:22 -0800 Subject: [PATCH 35/42] sim panel -add scale buttons and MPG dial --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 13 ++ configs/sim/qtdragon/qtdragon_xyz/panel.ui | 243 ++++++++++++++++---- 2 files changed, 206 insertions(+), 50 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index 04bc14e8e26..7c7b85458c9 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -1,8 +1,21 @@ net rate halui.axis.jog-speed panel.jog-rate net sx halui.axis.x.select panel.axis-x +net sx axis.x.jog-enable net sy halui.axis.y.select panel.axis-y +net sy axis.y.jog-enable net sz halui.axis.z.select panel.axis-z +net sz axis.z.jog-enable + +net mpg-scale axis.x.jog-scale panel.mpg-scale +net mpg-scale axis.y.jog-scale +net mpg-scale axis.z.jog-scale + +net mpg-count panel.mpg-wheel-s +net mpg-count qtdragon.mpg-in +net mpg-count axis.x.jog-counts +net mpg-count axis.y.jog-counts +net mpg-count axis.z.jog-counts net jog-p halui.axis.selected.plus panel.jog-pos net jog-m halui.axis.selected.minus panel.jog-neg diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index 7264d58f6d0..c5447316fb4 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -6,8 +6,8 @@ 0 0 - 404 - 560 + 418 + 587 @@ -91,55 +91,190 @@ - - - Axis Jog - - - - - - + - - - jog-pos - - - - - - - - - - - jog-neg - - - - - + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + - - - jog rate - - - - - - Qt::Horizontal - - - jog-rate - - - true - - - - - + + + + + MPG + + + + + + .001 + + + true + + + true + + + true + + + mpg-scale-small + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.001000000000000 + + + buttonGroup_mpgscale + + + + + + + .01 + + + true + + + true + + + mpg-scale-med + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.010000000000000 + + + buttonGroup_mpgscale + + + + + + + .1 + + + true + + + true + + + mpg-scale-large + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.100000000000000 + + + buttonGroup_mpgscale + + + + + + + + + + + 0 + 74 + + + + false + + + true + + + false + + + mpg-wheel + + + + @@ -341,7 +476,7 @@ 0 0 - 404 + 418 22 @@ -354,6 +489,11 @@ QPushButton
qtvcp.widgets.simple_widgets
+ + Dial + QDial +
qtvcp.widgets.simple_widgets
+
StatusSlider QSlider @@ -362,4 +502,7 @@ + + + From 416fcaa5359e5d091996b75fb61a018df4fdb25f Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 13 Dec 2025 20:42:40 -0800 Subject: [PATCH 36/42] qtvcp -baseclass: register dialogs for later checks like if you want to send responses to the current showing dialog --- lib/python/qtvcp/qt_makegui.py | 7 +++++++ lib/python/qtvcp/widgets/widget_baseclass.py | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/python/qtvcp/qt_makegui.py b/lib/python/qtvcp/qt_makegui.py index 2bad32c4ad6..52b956aa890 100644 --- a/lib/python/qtvcp/qt_makegui.py +++ b/lib/python/qtvcp/qt_makegui.py @@ -95,6 +95,7 @@ def __init__(self, halcomp=None, path=None): self.originalCloseEvent_ = self.closeEvent self._halWidgetList = [] self._VCPWindowList = [] + self._DialogList = [] self.settings = QtCore.QSettings('QtVcp', path.BASENAME) log.info('Qsettings file path: yellow<{}>'.format(self.settings.fileName())) # make an instance with embedded variables so they @@ -107,6 +108,12 @@ def registerHalWidget(self, widget): def getRegisteredHalWidgetList(self): return self._halWidgetList + def registerDialog(self, widget): + self._DialogList.append(widget) + + def getRegisteredDialogList(self): + return self._DialogList + # These catch events if using a plain VCP panel and there is no handler file def keyPressEvent(self, e): self.keyPressTrap(e) diff --git a/lib/python/qtvcp/widgets/widget_baseclass.py b/lib/python/qtvcp/widgets/widget_baseclass.py index 53a134a7a57..917e7a6ca65 100644 --- a/lib/python/qtvcp/widgets/widget_baseclass.py +++ b/lib/python/qtvcp/widgets/widget_baseclass.py @@ -18,6 +18,8 @@ import hal from PyQt5.QtCore import pyqtProperty +from PyQt5.QtWidgets import QDialog + from qtvcp import logger # Instantiate the libraries with global reference @@ -52,6 +54,7 @@ def __init__(self,comp=None,path=None,window=None): # INSTANCE_NAME is the embedded panel name def hal_init(self, HAL_NAME=None,INSTANCE_NAME = None): self.__class__.QTVCP_INSTANCE_.registerHalWidget(self) + if INSTANCE_NAME is not None: self.__class__.THIS_INSTANCE_ = self.__class__.QTVCP_INSTANCE_[INSTANCE_NAME] else: @@ -67,6 +70,13 @@ def hal_init(self, HAL_NAME=None,INSTANCE_NAME = None): self.PREFS_ = self.QTVCP_INSTANCE_.PREFS_ except: self.PREFS_ = None + + # register avaliable dialogs (for external controls) + if isinstance(self, QDialog): + idname = self.objectName() + LOG.verbose('green {}'.format(idname)) + self.__class__.QTVCP_INSTANCE_.registerDialog(self) + LOG.verbose("HAL_init: ObjectName:'{}'\n SELF:{}\n HAL NAME:{}\n PREFS:{}\n INSTANMCE:{}".format(self.objectName(),self,self.HAL_NAME_,self.PREFS_ ,self.__class__.THIS_INSTANCE_)) self._hal_init() From 7dfd7d02107021b988c3998eaf430faa2e9c3a5f Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 13 Dec 2025 20:46:51 -0800 Subject: [PATCH 37/42] qtdragon -find the currently visible dialog to send messages to halui 'ok' or 'cancel' messages --- .../qtvcp/screens/qtdragon/qtdragon_handler.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index fdd6c6330c5..c9ed234d1ca 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -769,7 +769,7 @@ def dialog_return(self, w, message): self.touchoff('touchplate') elif sensor_code and name == 'MESSAGE' and rtn is True: self.touchoff('sensor') - elif wait_code and name == 'MESSAGE': + elif wait_code and name == 'MESSAGE' and rtn is True: self.lowerSpindle() elif unhome_code and name == 'MESSAGE' and rtn is True: ACTION.SET_MACHINE_UNHOMED(-1) @@ -2089,14 +2089,14 @@ def external_mpg(self, count): def dialog_ext_control(self, pin, value, answer): if value: - # handler defined dialog? - if not self._dialog_message is None: - name = self._dialog_message.get('NAME') - STATUS.emit('dialog-update',{'NAME':name,'response':answer}) - else: - # tool change dialog? - if self.w.toolDialog_.isVisible(): - STATUS.emit('dialog-update',{'NAME':'TOOLCHANGE','response':answer}) + # search the registered dialogs for a match + dlist = self.w.getRegisteredDialogList() + for i in (dlist): + if i.isVisible(): + print('Found dialog',i.objectName()) + name = i.getIdName() + STATUS.emit('dialog-update',{'NAME':name,'response':answer}) + return def log_version(self): if INFO.RIP_FLAG: From d8199f7ed99de55a25d1d25527dbea6a8755a0a1 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 14 Dec 2025 01:10:17 -0800 Subject: [PATCH 38/42] gmoccapy -change system dialog to accept halui messages ok and cancel are used by the system unlock dialog as an example. To do this properly for all dialogs would require some more thought. --- src/emc/usr_intf/gmoccapy/dialogs.py | 46 +++++++++++++++++++++------ src/emc/usr_intf/gmoccapy/gmoccapy.py | 29 +++++++++++++++-- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index e3d630062dd..2cdfe124b05 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -36,10 +36,19 @@ class Dialogs(GObject.GObject): __gsignals__ = { 'play_sound': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), + 'system-dialog-result': (GObject.SignalFlags.RUN_FIRST , GObject.TYPE_NONE, (GObject.TYPE_INT,)) } - def __init__(self): + def __init__(self, caller): GObject.GObject.__init__(self) + self.sys_dialog = self.system_dialog(caller) + + def dialog_ext_control(self, answer): + if self.sys_dialog.get_visible(): + if answer: + self.sys_dialog.response(Gtk.ResponseType.ACCEPT) + else: + self.sys_dialog.response(Gtk.ResponseType.CANCEL) # This dialog is for unlocking the system tab # The unlock code number is defined at the top of the page @@ -47,9 +56,12 @@ def system_dialog(self, caller): dialog = Gtk.Dialog(_("Enter System Unlock Code"), caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) + dialog.set_modal(True) label = Gtk.Label(_("Enter System Unlock Code")) label.modify_font(Pango.FontDescription("sans 20")) calc = gladevcp.Calculator() + dialog._calc = calc + dialog._caller = caller dialog.vbox.pack_start(label, False, False, 0) dialog.vbox.add(calc) calc.set_value("") @@ -57,18 +69,32 @@ def system_dialog(self, caller): calc.set_editable(True) calc.integer_entry_only(True) calc.num_pad_only(True) - calc.entry.connect("activate", lambda w : dialog.emit("response", Gtk.ResponseType.ACCEPT)) + calc.entry.connect("activate", lambda w : self.on_system_response(dialog,Gtk.ResponseType.ACCEPT)) dialog.parse_geometry("360x400") dialog.set_decorated(True) - dialog.show_all() + dialog.connect("response", self.on_system_response) + return dialog + + def show_system_dialog(self): + self.sys_dialog._calc.set_value("") + self.sys_dialog.show_all() self.emit("play_sound", "alert") - response = dialog.run() - code = calc.get_value() - dialog.destroy() - if response == Gtk.ResponseType.ACCEPT: - if code == int(caller.unlock_code): - return True - return False + + def on_system_response(self, dialog, result): + code = dialog._calc.get_value() + print('Code:',code) + rtn = -1 + if result == Gtk.ResponseType.ACCEPT: + if code == int(dialog._caller.unlock_code): + print('Yes') + rtn = 1 + else: + print('No') + rtn = 0 + else: + print('Cancelled') + self.emit('system-dialog-result',rtn) + dialog.hide() def entry_dialog(self, caller, data = None, header = _("Enter value") , label = _("Enter the value to set"), integer = False): dialog = Gtk.Dialog(header, diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index b2e391647fc..1b562e96821 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -258,8 +258,9 @@ def __init__(self, argv): self.icon_theme.append_search_path(ICON_THEME_DIR) self.icon_theme.append_search_path(USER_ICON_THEME_DIR) - self.dialogs = dialogs.Dialogs() + self.dialogs = dialogs.Dialogs(caller = self) self.dialogs.connect("play_sound", self._on_play_sound) + self.dialogs.connect('system-dialog-result', self.system_dialog_return) # check the arguments given from the command line (Ini file) self.user_mode = False @@ -421,6 +422,8 @@ def __init__(self, argv): self.GSTAT.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) self.GSTAT.connect('cycle-start-request', lambda w, state :self.request_start(state)) self.GSTAT.connect('cycle-pause-request', lambda w, state: self.request_pause(state)) + self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(1)) + self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(0)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -4199,8 +4202,9 @@ def on_tbtn_setup_toggled(self, widget, data=None): code = True # else we ask for the code using the system.dialog if self.widgets.rbt_use_unlock.get_active(): - if self.dialogs.system_dialog(self): - code = True + self.dialogs.show_system_dialog() + # we will wait for response + return # Lets see if the user has the right to enter settings if code: self.widgets.ntb_main.set_current_page(1) @@ -4253,6 +4257,25 @@ def on_tbtn_setup_toggled(self, widget, data=None): widget.set_image(self.widgets.img_settings) + # return code from system dialog + def system_dialog_return(self,widget,result): + print(widget,result) + # Lets see if the user has the right to enter settings + if result == 1: + self.widgets.ntb_main.set_current_page(1) + self.widgets.ntb_setup.set_current_page(0) + self.widgets.ntb_button.set_current_page(_BB_SETUP) + #widget.set_image(self.widgets.img_settings_on) + elif result == 0: + if self.widgets.rbt_hal_unlock.get_active(): + message = _("Hal Pin is low, Access denied") + else: + message = _("wrong code entered, Access denied") + self.dialogs.warning_dialog(self, _("Just to warn you"), message) + self.widgets.tbtn_setup.set_active(False) + #widget.set_image(self.widgets.img_settings) + + # Show or hide the user tabs def on_tbtn_user_tabs_toggled(self, widget, data=None): if widget.get_active(): From 66794ad2994bb6246ae2084f8761336c36b990d3 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Dec 2025 19:48:56 -0800 Subject: [PATCH 39/42] gmoccapy -change warning dialogs to accept HALUI messages --- src/emc/usr_intf/gmoccapy/dialogs.py | 38 +++++-- src/emc/usr_intf/gmoccapy/gmoccapy.py | 152 ++++++++++++++++---------- 2 files changed, 122 insertions(+), 68 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index 2cdfe124b05..43eb478cda5 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -36,19 +36,20 @@ class Dialogs(GObject.GObject): __gsignals__ = { 'play_sound': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), - 'system-dialog-result': (GObject.SignalFlags.RUN_FIRST , GObject.TYPE_NONE, (GObject.TYPE_INT,)) + 'system-dialog-result': (GObject.SignalFlags.RUN_FIRST , GObject.TYPE_NONE, (GObject.TYPE_INT,)), + 'warning-dialog-result': (GObject.SignalFlags.RUN_FIRST , GObject.TYPE_NONE, (GObject.TYPE_INT, GObject.TYPE_STRING)) } def __init__(self, caller): GObject.GObject.__init__(self) self.sys_dialog = self.system_dialog(caller) + self.warn_dialog = self.warning_dialog(caller) def dialog_ext_control(self, answer): if self.sys_dialog.get_visible(): - if answer: - self.sys_dialog.response(Gtk.ResponseType.ACCEPT) - else: - self.sys_dialog.response(Gtk.ResponseType.CANCEL) + self.sys_dialog.response(answer) + elif self.warn_dialog.get_visible(): + self.warn_dialog.response(answer) # This dialog is for unlocking the system tab # The unlock code number is defined at the top of the page @@ -134,7 +135,7 @@ def entry_dialog(self, caller, data = None, header = _("Enter value") , label = return "CANCEL" # display warning dialog - def warning_dialog(self, caller, message, secondary = None, title = _("Operator Message"),\ + def warning_dialog(self, caller, message = '', secondary = None, title = _("Operator Message"),\ sound = True, confirm_pin = 'warning-confirm', active_pin = None): dialog = Gtk.MessageDialog(caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, @@ -149,11 +150,10 @@ def warning_dialog(self, caller, message, secondary = None, title = _("Operator box.add(ok_button) dialog.action_area.add(box) dialog.set_border_width(5) - dialog.show_all() if sound: self.emit("play_sound", "alert") dialog.set_title(title) - + dialog.context = [] def periodic(): if caller.halcomp[confirm_pin]: dialog.response(Gtk.ResponseType.OK) @@ -164,10 +164,26 @@ def periodic(): return False return True GLib.timeout_add(100, periodic) + dialog.connect("response", self.on_warning_response) + return dialog - response = dialog.run() - dialog.destroy() - return response == Gtk.ResponseType.OK + def show_warning_dialog(self, title, message, context=None, sound=True,\ + confirm_pin = 'warning-confirm', active_pin = None): + print(message,context) + self.warn_dialog.context.append(context) + self.warn_dialog.set_title(title) + self.warn_dialog.format_secondary_text(message) + self.warn_dialog.set_markup(message) + self.warn_dialog.show_all() + if sound: + self.emit("play_sound", "alert") + print(self.warn_dialog.context) + + def on_warning_response(self, dialog, rtn): + context = dialog.context.pop() + print(context) + self.emit('warning-dialog-result', rtn, context) + dialog.hide() def yesno_dialog(self, caller, message, title = _("Operator Message")): dialog = Gtk.MessageDialog(caller.widgets.window1, diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 1b562e96821..67a2e929262 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -258,10 +258,6 @@ def __init__(self, argv): self.icon_theme.append_search_path(ICON_THEME_DIR) self.icon_theme.append_search_path(USER_ICON_THEME_DIR) - self.dialogs = dialogs.Dialogs(caller = self) - self.dialogs.connect("play_sound", self._on_play_sound) - self.dialogs.connect('system-dialog-result', self.system_dialog_return) - # check the arguments given from the command line (Ini file) self.user_mode = False self.logofile = None @@ -340,6 +336,11 @@ def __init__(self, argv): self.builder.connect_signals(self) + self.dialogs = dialogs.Dialogs(caller = self) + self.dialogs.connect("play_sound", self._on_play_sound) + self.dialogs.connect('system-dialog-result', self.system_dialog_return) + self.dialogs.connect('warning-dialog-result', self.warning_dialog_return) + # this are settings to be done before window show self._init_preferences() @@ -422,8 +423,8 @@ def __init__(self, argv): self.GSTAT.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) self.GSTAT.connect('cycle-start-request', lambda w, state :self.request_start(state)) self.GSTAT.connect('cycle-pause-request', lambda w, state: self.request_pause(state)) - self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(1)) - self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(0)) + self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.ACCEPT)) + self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.CANCEL)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -1379,7 +1380,8 @@ def request_macro_call(self, data): # some error checking if not self.GSTAT.is_mdi_mode(): message = _("You must be in MDI mode to run macros") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message, context=None) return # look thru the INI macros @@ -1398,7 +1400,8 @@ def request_macro_call(self, data): else: # didn't match a name - give a hint message = _("Macro {} not found ".format(data)) - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message, context=None) # check if macros are in the INI file and add them to MDI Button List def _make_macro_button(self): @@ -1576,8 +1579,9 @@ def _make_lathe(self): message += _("this is not a lathe, as a lathe must have at least\n") message += _("an X and an Z axis\n") message += _("Wrong lathe configuration, we will leave here") - self.dialogs.warning_dialog(self, _("Very critical situation"), message, sound = False) - sys.exit() + self.dialogs.show_warning_dialog( _("Very critical situation"), + message, context='systemexit') + return else: if not len(self.axis_list) == 2 and not len(self.axis_list) < 6: self._arrange_jog_button_by_axis() @@ -2022,8 +2026,9 @@ def _init_tooleditor(self): if not tooltable: message = _("Did not find a toolfile file in [EMCIO] TOOL_TABLE") LOG.error(message) - self.dialogs.warning_dialog(self, _("Very critical situation"), message, sound = False) - sys.exit() + self.dialogs.show_warning_dialog( _("Very critical situation"), + message, context='systemexit') + return toolfile = os.path.join(CONFIGPATH, tooltable) self.widgets.tooledit1.set_filename(toolfile) # first we hide all the axis columns the unhide the ones we want @@ -2458,8 +2463,9 @@ def _init_offsetpage(self): if not parameterfile: message = _("Did not find a parameter file in [RS274NGC] PARAMETER_FILE") LOG.error(message) - self.dialogs.warning_dialog(self, _("Very critical situation"), message, sound = False) - sys.exit() + self.dialogs.show_warning_dialog( _("Very critical situation"), + message, context='systemexit') + return path = os.path.join(CONFIGPATH, parameterfile) self.widgets.offsetpage1.set_filename(path) @@ -4259,21 +4265,48 @@ def on_tbtn_setup_toggled(self, widget, data=None): # return code from system dialog def system_dialog_return(self,widget,result): - print(widget,result) + print('System ->',widget,result) # Lets see if the user has the right to enter settings if result == 1: self.widgets.ntb_main.set_current_page(1) self.widgets.ntb_setup.set_current_page(0) self.widgets.ntb_button.set_current_page(_BB_SETUP) - #widget.set_image(self.widgets.img_settings_on) + self.widgets.tbtn_setup.set_image(self.widgets.img_settings_on) elif result == 0: if self.widgets.rbt_hal_unlock.get_active(): message = _("Hal Pin is low, Access denied") else: message = _("wrong code entered, Access denied") - self.dialogs.warning_dialog(self, _("Just to warn you"), message) + self.dialogs.show_warning_dialog( _("Just to warn you"), message, context='sytemunlockfail') + # we will wait for response + + + + # return code from system dialog + def warning_dialog_return(self,widget,result,context): + print('Warning ->',widget,result,context) + if context is None: + return + + if context == 'systemunlockfail': self.widgets.tbtn_setup.set_active(False) - #widget.set_image(self.widgets.img_settings) + self.widgets.tbtn_setup.set_image(self.widgets.img_settings) + elif context == 'systemexit': + sys.exit() + + elif context == 'mantoolchange': + if result: + self.halcomp["toolchange-changed"] = True + else: + LOG.debug("toolchange abort {0} {1}".format(self.stat.tool_in_spindle, self.halcomp['toolchange-number'])) + self.command.abort() + self.halcomp['toolchange-number'] = self.stat.tool_in_spindle + self.halcomp['toolchange-change'] = False + self.halcomp['toolchange-changed'] = True + message = _("Tool Change has been aborted!\n") + message += _("The old tool will remain set!") + self.dialogs.show_warning_dialog( _("Just to warn you"), + message, context=None) # Show or hide the user tabs @@ -4393,8 +4426,8 @@ def on_btn_classicladder_clicked(self, widget, data=None): if hal.component_exists("classicladder_rt"): p = os.popen("classicladder &", "w") else: - self.dialogs.warning_dialog(self, _("INFO:"), - _("Classicladder real-time component not detected")) + self.dialogs.show_warning_dialog(_("INFO:"), + _("Classicladder real-time component not detected"), context='classicfail') # ========================================================= # spindle stuff @@ -4709,8 +4742,8 @@ def on_btn_show_calc_clicked(self, widget): integer=False) if value == "ERROR": LOG.debug("conversion error") - self.dialogs.warning_dialog(self, _("Conversion error !"), - ("Please enter only numerical values\nValues have not been applied")) + self.dialogs.show_warning_dialog(_("INFO:"), + _("Please enter only numerical values\nValues have not been applied"), context=None) elif value == "CANCEL": return else: @@ -4735,8 +4768,8 @@ def on_btn_show_calc_clicked(self, widget): integer=False) if value == "ERROR": LOG.debug("conversion error") - self.dialogs.warning_dialog(self, _("Conversion error !"), - ("Please enter only numerical values\nValues have not been applied")) + self.dialogs.show_warning_dialog(_("INFO:"), + _("Please enter only numerical values\nValues have not been applied"), context=None) elif value == "CANCEL": return else: @@ -4913,8 +4946,8 @@ def _on_btn_set_value_clicked(self, widget, data=None): return elif offset == "ERROR": LOG.debug("Conversion error in btn_set_value") - self.dialogs.warning_dialog(self, _("Conversion error in btn_set_value!"), - _("Please enter only numerical values. Values have not been applied")) + self.dialogs.show_warning_dialog(_("Conversion error in btn_set_value!"), + _("Please enter only numerical values\nValues have not been applied"), context=None) else: self.command.mode(linuxcnc.MODE_MDI) self.command.wait_complete() @@ -4929,7 +4962,8 @@ def _on_btn_set_selected_clicked(self, widget, data=None): system, name = self.widgets.offsetpage1.get_selected() if system not in ["G54", "G55", "G56", "G57", "G58", "G59", "G59.1", "G59.2", "G59.3"]: message = _("You did not select a system to be changed to, so nothing will be changed") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog(_("Important Warning!"), + message, context=None) return if system == self.system_list[self.stat.g5x_index]: return @@ -4992,8 +5026,8 @@ def on_btn_block_height_clicked(self, widget, data=None): else: self.prefs.putpref("blockheight", 0.0, float) self.prefs.putpref("probeheight", 0.0, float) - self.dialogs.warning_dialog(self, _("Conversion error in btn_block_height!"), - _("Please enter only numerical values\nValues have not been applied")) + self.dialogs.show_warning_dialog(_("Conversion error in btn_block_height!"), + _("Please enter only numerical values\nValues have not been applied"), context=None) # set coordinate system to new origin origin = self.get_ini_info.get_axis_2_min_limit() + blockheight @@ -5021,7 +5055,9 @@ def _set_icon_theme(self, name): if name is None or name == "none": # Switching to none required a restart (skip entire icon theme stuff) message = "Change to no icon theme requires a restart to take effect." - self.dialogs.warning_dialog(self, _("Just to warn you"), message) + self.dialogs.show_warning_dialog( _("Just to warn you"), + message, context=None) + else: self.icon_theme.set_custom_theme(name) self.notification.set_property('icon_theme_name', name) @@ -5546,28 +5582,20 @@ def on_tool_change(self, widget): except: message = _("Tool\n\n# {0:d}\n\n not in the tool table!").format(toolnumber) - result = self.dialogs.warning_dialog(self, message, title=_("Manual Tool change"),\ - confirm_pin = 'toolchange-confirm', active_pin = 'toolchange-change') - if result: - self.halcomp["toolchange-changed"] = True - else: - LOG.debug("toolchange abort {0} {1}".format(self.stat.tool_in_spindle, self.halcomp['toolchange-number'])) - self.command.abort() - self.halcomp['toolchange-number'] = self.stat.tool_in_spindle - self.halcomp['toolchange-change'] = False - self.halcomp['toolchange-changed'] = True - message = _("Tool Change has been aborted!\n") - message += _("The old tool will remain set!") - self.dialogs.warning_dialog(self, message) + self.dialogs.show_warning_dialog( _("Manual Tool change"), + message, context='mantoolchange', + confirm_pin = 'toolchange-confirm', + active_pin = 'toolchange-change') else: self.halcomp['toolchange-changed'] = False def on_btn_delete_tool_clicked(self, widget, data=None): selected_tool = self.widgets.tooledit1.get_selected_row() if self.stat.tool_in_spindle == selected_tool: - message = _("You are trying to delete the tool mounted in the spindle.\n" - "This is not allowed, please change tool prior to delete it.") - self.dialogs.warning_dialog(self, _("Warning Tool can not be deleted!"), message) + message = _("You are trying to delete the tool mounted in the spindle\n") + message += _("This is not allowed, please change tool prior to delete it") + self.dialogs.show_warning_dialog( _("Warning Tool can not be deleted!"), + message, context=None) return self.widgets.tooledit1.delete_selected_row(widget) self.widgets.tooledit1.edited = True @@ -5594,13 +5622,15 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): if not self.widgets.tooledit1.get_selected_tool(): message = _("No or multiple tools selected in the tool table. ") message += _("Please select only one tool in the table!") - self.dialogs.warning_dialog(self, _("Warning Tool Touch off not possible!"), message) + self.dialogs.show_warning_dialog( _("Warning Tool Touch off not possible!"), + message, context=None) return if self.widgets.tooledit1.get_selected_tool() != self.stat.tool_in_spindle: message = _("You can not touch off a tool, which is not mounted in the spindle! ") message += _("Your selection has been reset to the tool in spindle.") - self.dialogs.warning_dialog(self, _("Warning Tool Touch off not possible!"), message) + self.dialogs.show_warning_dialog( _("Warning Tool Touch off not possible!"), + message, context=None) self.widgets.tooledit1.reload(self) self.widgets.tooledit1.set_selected_tool(self.stat.tool_in_spindle) return @@ -5608,7 +5638,8 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): if "G41" in self.active_gcodes or "G42" in self.active_gcodes: message = _("Tool touch off is not possible with cutter radius compensation switched on!\n") message += _("Please emit an G40 before tool touch off.") - self.dialogs.warning_dialog(self, _("Warning Tool Touch off not possible!"), message) + self.dialogs.show_warning_dialog( _("Warning Tool Touch off not possible!"), + message, context=None) return if widget == self.widgets.btn_tool_touchoff_x: @@ -5616,8 +5647,8 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): elif widget == self.widgets.btn_tool_touchoff_z: axis = "z" else: - self.dialogs.warning_dialog(self, _("Real big error!"), - _("You managed to come to a place that is not possible in on_btn_tool_touchoff")) + self.dialogs.show_warning_dialog(_("Real big error!"), + _("You managed to come to a place that is not possible in on_btn_tool_touchoff"), context=None) return value = self.dialogs.entry_dialog(self, data=None, @@ -5627,7 +5658,8 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): if value == "ERROR": message = _("Conversion error because of wrong entry for touch off axis {0}").format(axis.upper()) - self.dialogs.warning_dialog(self, _("Conversion error !"), message) + self.dialogs.show_warning_dialog( _("Conversion error !"), + message, context=None) return elif value == "CANCEL": return @@ -5655,13 +5687,15 @@ def on_btn_select_tool_by_no_clicked(self, widget, data=None): if value == "ERROR": message = _("Conversion error because of wrong entry for tool number.\n") message += _("Enter only integer numbers!") - self.dialogs.warning_dialog(self, _("Conversion error !"), message) + self.dialogs.show_warning_dialog( _("Conversion error !"), + message, context=None) return elif value == "CANCEL": return elif int(value) == self.stat.tool_in_spindle: message = _("Selected tool is already in spindle, no change needed.") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message, context=None) return else: self.tool_change = True @@ -5683,11 +5717,13 @@ def on_btn_selected_tool_clicked(self, widget, data=None): tool = self.widgets.tooledit1.get_selected_row() if tool == None: message = _("you selected no or more than one tool, the tool selection must be unique") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message, context=None) return if tool == self.stat.tool_in_spindle: message = _("Selected tool is already in spindle, no change needed.") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message, context=None) return if tool or tool == 0: self.tool_change = True @@ -5705,7 +5741,9 @@ def on_btn_selected_tool_clicked(self, widget, data=None): self.command.mdi(command) else: message = _("Could not understand the entered tool number. Will not change anything!") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message, context=None) + # ========================================================= # gremlin relevant calls From fa4784529be746e85cd297e07c1c9f16f7b21786 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 00:00:35 -0800 Subject: [PATCH 40/42] gmoccapy -set jog speed control widget to use messages Sends out a jog rate gobject message --- src/emc/usr_intf/gmoccapy/gmoccapy.glade | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.glade b/src/emc/usr_intf/gmoccapy/gmoccapy.glade index 063242d0a24..0742802488b 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.glade +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.glade @@ -1,5 +1,5 @@ - + @@ -1406,6 +1406,7 @@ uncomment selection False rgb(255,129,22) 10500 + 0 mm/min 1500 @@ -1452,6 +1453,7 @@ uncomment selection rgb(255,129,22) 3600 %.d + 0 °/min 360 From 98dd3805ffd46c58d27233ad65a4c8c81fbbd57b Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 00:25:59 -0800 Subject: [PATCH 41/42] gmoccapy -use halui message to cancel notifications --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 67a2e929262..06193249c14 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -425,6 +425,7 @@ def __init__(self, argv): self.GSTAT.connect('cycle-pause-request', lambda w, state: self.request_pause(state)) self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.ACCEPT)) self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.CANCEL)) + self.GSTAT.connect('cancel-request', lambda w, state: self._del_notification()) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -6226,6 +6227,9 @@ def _on_message_deleted(self, widget, messages): def _del_message_changed(self, pin): if pin.get(): + self._del_notification() + + def _del_notification(self): if self.halcomp["error"] == True: number = [] messages = self.notification.messages From 57399eee1f05ad726dec390627b594cbdc3d10ea Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 17:45:44 -0800 Subject: [PATCH 42/42] add python3-zmq package for halui --- debian/configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/configure b/debian/configure index c960e40050c..205902fd889 100755 --- a/debian/configure +++ b/debian/configure @@ -107,7 +107,7 @@ PYTHON_GST=python3-gst-1.0,gstreamer1.0-plugins-base TCLTK_VERSION=8.6 PYTHON_IMAGING=python3-pil PYTHON_IMAGING_TK=python3-pil.imagetk -QTVCP_DEPENDS="python3-pyqt5,\n python3-pyqt5.qsci,\n python3-pyqt5.qtsvg,\n python3-pyqt5.qtopengl,\n python3-opencv,\n python3-dbus,\n python3-espeak,\n python3-dbus.mainloop.pyqt5,\n python3-pyqt5.qtwebengine,\n espeak-ng,\n pyqt5-dev-tools,\n gstreamer1.0-tools,\n espeak,\n sound-theme-freedesktop,\n python3-poppler-qt5" +QTVCP_DEPENDS="python3-pyqt5,\n python3-pyqt5.qsci,\n python3-pyqt5.qtsvg,\n python3-pyqt5.qtopengl,\n python3-opencv,\n python3-dbus,\n python3-espeak,\n python3-dbus.mainloop.pyqt5,\n python3-pyqt5.qtwebengine,\n espeak-ng,\n pyqt5-dev-tools,\n gstreamer1.0-tools,\n espeak,\n sound-theme-freedesktop,\n python3-zmq,\n python3-poppler-qt5" YAPPS_RUNTIME="python3-yapps" DEBHELPER="debhelper (>= 12)" COMPAT="12"