From db213ccbaeb486cd9a9a04aba6f90a7ae7a58f74 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Sep 2025 22:09:20 -0700 Subject: [PATCH 001/110] 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 a157a3f47c0..6173ebe5e4e 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -354,6 +354,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() @@ -364,6 +369,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 0cd96352bc408fee332c41aef3d44021d8760226 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 8 Jul 2026 23:03:57 -0700 Subject: [PATCH 002/110] hal_glib -add function to process one ZMQ message In some cases, we need to directly process one message. eg when using 'wait to don't block' dialog option --- lib/python/common/hal_glib.py | 50 +++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/python/common/hal_glib.py b/lib/python/common/hal_glib.py index 6173ebe5e4e..b5f2b385cdd 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -405,30 +405,40 @@ def init_read_socket(self): GObject.IO_IN|GObject.IO_ERR|GObject.IO_HUP, self.onReadMsg, self.readSocket) - # convert message to a function name and data - # then call that function + # called directly to process any current message + def readNextMsg(self): + event = self.readSocket.poll(timeout=0) + if event & zmq.POLLIN: + self.convertMsg() + + # called when GObject notices a change def onReadMsg(self, queue, condition, sock): 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) - function = y.get('FUNCTION') - data = y.get('ARGS') - LOG.debug('REQUESTED:{}'.format(y)) - if data == '': - try: - self[function]() - except Exception as e: - LOG.debug('not a valid request\n {}'.format(e)) - else: - try: - self[function](data) - except Exception as e: - LOG.debug('not a valid request\n {}'.format(e)) - #self. action(y.get('MESSAGE'),y.get('ARGS')) + self.convertMsg() return True + # convert message to a function name and data + # then call that function + def convertMsg(self): + # get raw message + topic, data = self.readSocket.recv_multipart() + # convert from json object to python object + y = json.loads(data) + function = y.get('FUNCTION') + data = y.get('ARGS') + LOG.debug('REQUESTED:{}'.format(y)) + if data == '': + try: + self[function]() + except Exception as e: + LOG.debug('not a valid request\n {}'.format(e)) + else: + try: + self[function](data) + except Exception as e: + LOG.debug('not a valid request\n {}'.format(e)) + #self. action(y.get('MESSAGE'),y.get('ARGS')) + def merge(self): self.old['command-state'] = self.stat.state self.old['state'] = self.stat.task_state From 3dedf68c893c18ebb14d318bb5fcf1713e78200d Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 14 Jul 2026 21:34:09 -0700 Subject: [PATCH 003/110] hal_glib - add soft key messages --- lib/python/common/hal_glib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/python/common/hal_glib.py b/lib/python/common/hal_glib.py index b5f2b385cdd..c5fef8affd7 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -248,6 +248,7 @@ class _GStat(GObject.GObject): '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,)), 'macro-call-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), + 'softkey-pressed': (GObject.SignalFlags.RUN_FIRST , GObject.TYPE_NONE, (GObject.TYPE_INT,)), } STATES = { linuxcnc.STATE_ESTOP: 'state-estop' @@ -422,6 +423,7 @@ def onReadMsg(self, queue, condition, sock): def convertMsg(self): # get raw message topic, data = self.readSocket.recv_multipart() + LOG.debug(f'RAW REQUESTED:{topic},{data}') # convert from json object to python object y = json.loads(data) function = y.get('FUNCTION') @@ -1509,6 +1511,9 @@ def request_ok(self, data): def request_cancel(self, data): self.emit('cancel-request', data) + def request_softkey(self, index): + self.emit('softkey-pressed', index) + ############################################# def shutdown(self): From b12daca2ddcdbcb3eef9083d711ee33996a05b95 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 17:45:44 -0800 Subject: [PATCH 004/110] 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 096b3f4a5ed..635bf664ecb 100755 --- a/debian/configure +++ b/debian/configure @@ -121,7 +121,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-qtpy,\n 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" +QTVCP_DEPENDS="python3-qtpy,\n 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" YAPPS_RUNTIME="python3-yapps" DEBHELPER="debhelper (>= 12)" COMPAT="12" From 44d5d24cb11292d4549a5fd8e7779fdf7fdba845 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 7 Feb 2026 16:39:36 -0800 Subject: [PATCH 005/110] add python3-zmq to python3 depends --- debian/control.main-pkg.in | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control.main-pkg.in b/debian/control.main-pkg.in index e964cbb2c6d..2bd12e3dc68 100644 --- a/debian/control.main-pkg.in +++ b/debian/control.main-pkg.in @@ -22,6 +22,7 @@ Depends: python3-opengl, python3-configobj, python3-xlib, + python3-zmq, libgtksourceview-4-dev, tcl@TCLTK_VERSION@, tk@TCLTK_VERSION@, From 300b1daf54cedf598aa995cfa61b7c6e1fc4cbf9 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 16 Aug 2025 11:21:10 -0700 Subject: [PATCH 006/110] halui initial testing of embedded python halui add bridgeui folder for HALUI halui -inject socket axis selection bridge -remove HAL pin code, get MDI commands working halui -code clenup, GUI based MDI command feature halui -add cycle start cycle pause pins calls these functions in a GUI halui -add gui ok/canel pins halui -fix memory leaks halui switch u32 to s32 s32 is more common the u32 (easier to connect to) also axis select uses -1 as 'unselected' 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. --- .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 2 +- lib/python/bridgeui/__init__.py | 3 + lib/python/bridgeui/bridge.py | 268 ++++++++++ src/emc/usr_intf/Submakefile | 3 +- src/emc/usr_intf/halui.cc | 478 ++++++++++++++++-- 5 files changed, 722 insertions(+), 32 deletions(-) create mode 100644 lib/python/bridgeui/__init__.py create mode 100644 lib/python/bridgeui/bridge.py diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index 7e5d56e05aa..f437be0811a 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/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..56577ebb664 --- /dev/null +++ b/lib/python/bridgeui/bridge.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 + +import os +import sys +import time + +import json +import signal + +import hal +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,'MPG0':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() + 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(self): + self.jogRate = 0 + self.jogRateAngular = 0 + + self.jogIncrement = 0 + self.jogIncrementAngular = 0 + + self.activeJoint = 0 + + 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): + 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 variables from messages from hal_glib + def action(self, msg, data): + LOG.debug('{} -> {} -> {}'.format(msg, data, data[0])) + if msg == 'jograte-changed': + self.jogRate = float(data[0]) + elif msg == 'jograte-angular-changed': + self.jogRateAngular = float(data[0]) + elif msg == 'jogincrements-changed': + self.jogIncrement = float(data[0][0]) + elif msg == 'jogincrement-angular-changed': + self.jogIncremtAngular = float(data[0][0]) + 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 + self.currentSelectedAxis = data[0] + flag = 0 + else: + state = False + self.axesSelected[i] = int(state) + + if flag: + self.currentSelectedAxis = 'None' + print ('axis state', self.axesSelected,self.currentSelectedAxis) + + # 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'))]) + + 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 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' + 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 + def setJogRate(self, value): + self.writeMsg('set_jograte', value) + + def getJogRateAngular(self): + return self.jogRateAngular + def setJogRateAngular(self, value): + self.writeMsg('set_jograte_angular', value) + + 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): + if index == 100: + letter = 'MPG0' + else: + 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_()) diff --git a/src/emc/usr_intf/Submakefile b/src/emc/usr_intf/Submakefile index cb99070ff44..d2fbdc09dca 100644 --- a/src/emc/usr_intf/Submakefile +++ b/src/emc/usr_intf/Submakefile @@ -44,5 +44,6 @@ TARGETS += ../bin/linuxcnclcd ../bin/halui: $(call TOOBJS, $(HALUISRCS)) ../lib/liblinuxcnc.a ../lib/liblinuxcncini.so.1 ../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 6796e4cf48d..99de81a1436 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -43,6 +43,10 @@ using namespace linuxcnc; +#define PY_SSIZE_T_CLEAN +#include +#include + /* Using halui: see the man page */ static int axis_mask = 0; @@ -89,6 +93,8 @@ typedef hal_uint_t hal_ui32_t; FIELD(bool,program_is_running) /* pin for notifying user that program is running */ \ FIELD(bool,halui_mdi_is_running) /* pin for notifying user that halui MDI commands is running */ \ FIELD(bool,program_is_paused) /* pin for notifying user that program is paused */ \ + FIELD(bool,cycle_start) /* pin for requesting the gui to start */ \ + FIELD(bool,cycle_pause) /* pin for requesting the gui to pause */ \ FIELD(bool,program_run) /* pin for running program */ \ FIELD(bool,program_pause) /* pin for pausing program */ \ FIELD(bool,program_resume) /* pin for resuming program */ \ @@ -101,7 +107,7 @@ typedef hal_uint_t hal_ui32_t; FIELD(bool,program_bd_off) /* pin for setting block delete off */ \ FIELD(bool,program_bd_is_on) /* status pin that block delete is on */ \ \ - FIELD(uint,tool_number) /* pin for current selected tool */ \ + FIELD(sint,tool_number) /* pin for current selected tool */ \ FIELD(real,tool_length_offset_x) /* current applied x tool-length-offset */ \ FIELD(real,tool_length_offset_y) /* current applied y tool-length-offset */ \ FIELD(real,tool_length_offset_z) /* current applied z tool-length-offset */ \ @@ -137,7 +143,8 @@ typedef hal_uint_t hal_ui32_t; ARRAY(bool,joint_override_limits,EMCMOT_MAX_JOINTS+1) /* status pin that the joint is on the hardware max limit */ \ ARRAY(bool,joint_has_fault,EMCMOT_MAX_JOINTS+1) /* status pin that the joint has a fault */ \ FIELD(uint,joint_selected) /* status pin for the joint selected */ \ - FIELD(uint,axis_selected) /* status pin for the axis selected */ \ + FIELD(sint,axis_selected) /* status pin for the axis selected */ \ + FIELD(bool,mpg_select0)\ \ ARRAY(bool,joint_nr_select,EMCMOT_MAX_JOINTS) /* nr. of pins to select a joint */ \ ARRAY(bool,axis_nr_select,EMCMOT_MAX_AXIS) /* nr. of pins to select a axis */ \ @@ -206,6 +213,10 @@ typedef hal_uint_t hal_ui32_t; FIELD(bool,home_all) /* pin for homing all joints in sequence */ \ FIELD(bool,abort) /* pin for aborting */ \ ARRAY(bool,mdi_commands,MDI_MAX) \ + ARRAY(bool,gui_mdi_commands,MDI_MAX) \ +\ + FIELD(bool,gui_ok) /* pin for acknowledging dialog ok */ \ + FIELD(bool,gui_cancel) /* pin for acknowledging dialog cancel */ \ \ FIELD(real,units_per_mm) \ @@ -240,11 +251,21 @@ HAL_FIELDS typedef halui_str_base halui_str; typedef halui_str_base local_halui_str; +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 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 */ @@ -537,6 +558,123 @@ int halui_export_pin_OUT_bit(hal_bool_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 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 + 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; +} /******************************************************************** * @@ -656,8 +794,8 @@ int halui_hal_init(void) CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->fo_value), 0.0, "halui.feed-override.value")); CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->ro_value), 0.0, "halui.rapid-override.value")); CHK(hal_pin_new_ui32(comp_id, HAL_OUT, &(halui_data->joint_selected), 0, "halui.joint.selected")); - CHK(hal_pin_new_ui32(comp_id, HAL_OUT, &(halui_data->axis_selected), 0, "halui.axis.selected")); - CHK(hal_pin_new_ui32(comp_id, HAL_OUT, &(halui_data->tool_number), 0, "halui.tool.number")); + CHK(hal_pin_new_si32(comp_id, HAL_OUT, &(halui_data->axis_selected), 0, "halui.axis.selected")); + CHK(hal_pin_new_si32(comp_id, HAL_OUT, &(halui_data->tool_number), 0, "halui.tool.number")); CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->tool_length_offset_x), 0.0, "halui.tool.length_offset.x")); CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->tool_length_offset_y), 0.0, "halui.tool.length_offset.y")); CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->tool_length_offset_z), 0.0, "halui.tool.length_offset.z")); @@ -684,6 +822,8 @@ int halui_hal_init(void) CHK(halui_export_pin_IN_bit(&(halui_data->mist_off), "halui.mist.off")); CHK(halui_export_pin_IN_bit(&(halui_data->flood_on), "halui.flood.on")); CHK(halui_export_pin_IN_bit(&(halui_data->flood_off), "halui.flood.off")); + CHK(halui_export_pin_IN_bit(&(halui_data->cycle_start), "halui.cycle.start")); + CHK(halui_export_pin_IN_bit(&(halui_data->cycle_pause), "halui.cycle.pause")); CHK(halui_export_pin_IN_bit(&(halui_data->program_run), "halui.program.run")); CHK(halui_export_pin_IN_bit(&(halui_data->program_pause), "halui.program.pause")); CHK(halui_export_pin_IN_bit(&(halui_data->program_resume), "halui.program.resume")); @@ -749,6 +889,7 @@ int halui_hal_init(void) CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->ajog_increment_minus[axis_num]), 0, "halui.axis.%c.increment-minus", c)); } + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->mpg_select0), 0, "halui.mpg-select.0")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->joint_home[num_joints]), 0, "halui.joint.selected.home")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->joint_unhome[num_joints]), 0, "halui.joint.selected.unhome")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->jjog_plus[num_joints]), 0, "halui.joint.selected.plus")); @@ -772,6 +913,15 @@ int halui_hal_init(void) CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->mdi_commands[n]), 0, "halui.mdi-command-%02d", n)); } + for (int n=0; ngui_mdi_commands[n]), 0, "halui.gui.mdi-command-%s", py_call_get_mdi_name(n))); + } + + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_ok), 0, "halui.gui.ok")); + + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_cancel), 0, "halui.gui.cancel")); + hal_ready(comp_id); return 0; } @@ -1112,6 +1262,7 @@ static void sendJogStop(int ja, int jjogmode) static void sendJogCont(int ja, double speed, int jjogmode) { + EMC_JOG_CONT emc_jog_cont_msg; if (emcStatus->task.state != EMC_TASK_STATE::ON) { return; } @@ -1340,6 +1491,12 @@ static int iniLoad(const char *filename) mdi_commands[num_mdi_commands++] = strdup(mc->c_str()); } + int temp = py_call_get_mdi_count(); + for (int n=0; naxis_nr_select[axis_num], old_halui_data.axis_nr_select[axis_num] = 0); + hal_set_bool(halui_data->mpg_select0, old_halui_data.mpg_select0 = 0); hal_set_bool(halui_data->ajog_minus[axis_num], old_halui_data.ajog_minus[axis_num] = 0); hal_set_bool(halui_data->ajog_plus[axis_num], old_halui_data.ajog_plus[axis_num] = 0); hal_set_real(halui_data->ajog_analog[axis_num], old_halui_data.ajog_analog[axis_num] = 0); @@ -1396,7 +1554,7 @@ static void hal_init_pins() hal_set_real(halui_data->ajog_speed, 0); hal_set_ui32(halui_data->joint_selected, 0); // select joint 0 by default - hal_set_ui32(halui_data->axis_selected, 0); // select axis 0 by default + hal_set_si32(halui_data->axis_selected, -1); // select no axis by default hal_set_real(halui_data->fo_scale, old_halui_data.fo_scale = 0.1); //sane default hal_set_real(halui_data->ro_scale, old_halui_data.ro_scale = 0.1); //sane default @@ -1460,6 +1618,118 @@ static bool jogging_selected_axis(local_halui_str &hal) { return (hal.ajog_plus[EMCMOT_MAX_AXIS] || hal.ajog_minus[EMCMOT_MAX_AXIS]); } +static double py_call_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 py_call_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(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); +} + +static int py_call_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 py_call_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(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); +} + +static void py_call_request_MDI( int index) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "runIndexedMacro"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "i", index); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: runIndexedMacro function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function runIndexedMacro"); + } + Py_DECREF(pFuncWrite); +} // this function looks if any of the hal pins has changed // and sends appropriate messages if so @@ -1471,10 +1741,54 @@ static void check_hal_changes() rtapi_bool bit; int js; rtapi_real floatt; + double jogspeed; int jjog_speed_changed; int ajog_speed_changed; + int is_any_axis_selected, deselected; + + // get python to process socket messages + pFuncRead = PyObject_GetAttrString(pInstance, "readMsg"); + + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + 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); + } + Py_DECREF(pFuncRead); + Py_DECREF(pValue); + + // check socket messages for current axis selection + int value = py_call_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) { + hal_set_bool(halui_data->axis_nr_select[axis_num], 1); + }else{ + hal_set_bool(halui_data->axis_nr_select[axis_num], 0); + } + } + if (value == 100) { + hal_set_bool(halui_data->mpg_select0, 1); + }else{ + hal_set_bool(halui_data->mpg_select0, 0); + } + lastaxis = value; + } + copy_hal_data(*halui_data, new_halui_data_mutable); const local_halui_str &new_halui_data = new_halui_data_mutable; @@ -1518,6 +1832,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); @@ -1692,11 +2016,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; + py_call_axis_jogspeed(internaljogspeed); } else { ajog_speed_changed = 0; } + // check socket messages for jogspeed + jogspeed = py_call_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); @@ -1776,47 +2112,58 @@ 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; } @@ -1824,30 +2171,56 @@ 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) { - hal_set_ui32(halui_data->axis_selected, axis_num); - aselect_changed = axis_num; // flag that we changed the selected axis - } - old_halui_data.axis_nr_select[axis_num] = bit; - } + is_any_axis_selected = 1; + hal_set_si32(halui_data->axis_selected, axis_num); + py_call_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; + } + } + + // 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); + hal_set_si32(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); + hal_set_si32(halui_data->axis_selected, -1); } 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) { - hal_set_bool(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) { + hal_set_bool(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 { - hal_set_bool(halui_data->axis_is_selected[axis_num], 1); + hal_set_bool(halui_data->axis_is_selected[axis_num], 1); if (hal_get_bool(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 (hal_get_bool(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) @@ -1896,7 +2269,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; @@ -1906,7 +2279,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; @@ -1916,7 +2289,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; } @@ -1924,14 +2297,33 @@ 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; } + // 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){ + 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 @@ -1992,7 +2384,7 @@ static void modify_hal_pins() hal_set_bool(halui_data->mist_is_on, emcStatus->io.coolant.mist); hal_set_bool(halui_data->flood_is_on, emcStatus->io.coolant.flood); - hal_set_ui32(halui_data->tool_number, emcStatus->io.tool.toolInSpindle); + hal_set_si32(halui_data->tool_number, emcStatus->io.tool.toolInSpindle); hal_set_real(halui_data->tool_length_offset_x, emcStatus->task.toolOffset.tran.x); hal_set_real(halui_data->tool_length_offset_y, emcStatus->task.toolOffset.tran.y); hal_set_real(halui_data->tool_length_offset_z, emcStatus->task.toolOffset.tran.z); @@ -2122,6 +2514,27 @@ int main(int argc, char *argv[]) exit(1); } + /* 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); + }else{ + PyErr_Print(); + fprintf(stderr, "bridge: Failed to load \"%s\"\n", "pyui"); + exit(1); + } + Py_DECREF(pClass); + // get configuration information if (0 != iniLoad(emc_inifile)) { rcs_print_error("iniLoad error\n"); @@ -2137,6 +2550,9 @@ int main(int argc, char *argv[]) //initialize safe values hal_init_pins(); + + + // init NML if (0 != tryNml()) { rcs_print_error("can't connect to emc\n"); @@ -2172,6 +2588,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 e3c9c4830b0d283b8273b591be0853b94e5345db Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 8 Feb 2026 16:42:51 -0800 Subject: [PATCH 007/110] makefile - add halui's bridgeui folder --- src/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Makefile b/src/Makefile index 03d5d76cd2f..0f116bf766e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -779,6 +779,7 @@ install-kernel-indep: install-python install-python: install-dirs $(DIR) $(DESTDIR)$(SITEPY) $(DESTDIR)$(SITEPY)/rs274 $(DIR) $(DESTDIR)$(SITEPY)/mtc + $(DIR) $(DESTDIR)$(SITEPY)/bridgeui $(DIR) $(DESTDIR)$(SITEPY)/common ifeq ($(BUILD_GUI),yes) $(DIR) $(DESTDIR)$(SITEPY)/touchy @@ -804,6 +805,7 @@ ifeq ($(BUILD_GUI),yes) $(DIR) $(DESTDIR)$(SITEPY)/plasmac endif $(FILE) ../lib/python/*.py ../lib/python/*.so $(DESTDIR)$(SITEPY) + $(FILE) ../lib/python/bridgeui/*.py $(DESTDIR)$(SITEPY)/bridgeui $(FILE) ../lib/python/common/*.py $(DESTDIR)$(SITEPY)/common $(FILE) ../lib/python/rs274/*.py $(DESTDIR)$(SITEPY)/rs274 $(FILE) ../lib/python/mtc/*.py $(DESTDIR)$(SITEPY)/mtc From c3d7daf4e7df1a056c0cbe23644cc0c5b3eb0a37 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 10 Apr 2026 13:38:30 -0700 Subject: [PATCH 008/110] halui -add an angular jograte pin and send angular jograte messages halui doesn't use the angular jograte in it's jogging yet but will tell the gui the jograte has changed --- src/emc/usr_intf/halui.cc | 78 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 99de81a1436..09ac813da1d 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -165,6 +165,7 @@ typedef hal_uint_t hal_ui32_t; ARRAY(bool,jjog_increment_minus,EMCMOT_MAX_JOINTS+1) /* Incremental jogging, negative direction */ \ \ FIELD(real,ajog_speed) /* pin for setting the jog speed (halui internal) */ \ + FIELD(real,ajog_speed_angular) /* pin for setting the angular jog speed (halui internal) */ \ ARRAY(bool,ajog_minus,EMCMOT_MAX_AXIS+1) /* pin to jog in positive direction */ \ ARRAY(bool,ajog_plus,EMCMOT_MAX_AXIS+1) /* pin to jog in negative direction */ \ ARRAY(real,ajog_analog,EMCMOT_MAX_AXIS+1) /* pin for analog jogging (-1..0..1) */ \ @@ -259,6 +260,8 @@ static local_halui_str old_halui_data; static double lastjogspeed = 0; static double internaljogspeed = 0; +static double lastangularjogspeed = 0; +static double internalangularjogspeed = 0; static int lastaxis = -1; static char *mdi_commands[MDI_MAX]; @@ -907,6 +910,7 @@ int halui_hal_init(void) CHK(halui_export_pin_IN_float(&(halui_data->jjog_deadband), "halui.joint.jog-deadband")); CHK(halui_export_pin_IN_float(&(halui_data->ajog_speed), "halui.axis.jog-speed")); + CHK(halui_export_pin_IN_float(&(halui_data->ajog_speed_angular), "halui.axis.jog-speed-angular")); CHK(halui_export_pin_IN_float(&(halui_data->ajog_deadband), "halui.axis.jog-deadband")); for (int n = 0; n < num_mdi_commands; n++) { @@ -1552,6 +1556,7 @@ static void hal_init_pins() hal_set_bool(halui_data->ajog_increment_minus[EMCMOT_MAX_AXIS], old_halui_data.ajog_increment_minus[EMCMOT_MAX_AXIS] = 0); hal_set_real(halui_data->ajog_deadband, 0.2); hal_set_real(halui_data->ajog_speed, 0); + hal_set_real(halui_data->ajog_speed_angular, 0); hal_set_ui32(halui_data->joint_selected, 0); // select joint 0 by default hal_set_si32(halui_data->axis_selected, -1); // select no axis by default @@ -1664,9 +1669,55 @@ static void py_call_axis_jogspeed(double speed) Py_DECREF(pFuncWrite); } +static double py_call_axis_get_angular_jogspeed() { + double jspd = 0; + // check socket messages for angular jogspeed + pFuncRead = PyObject_GetAttrString(pInstance, "getJogRateAngular"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: getJogRateAngular 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 py_call_axis_angular_jogspeed(double speed) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "setJogRateAngular"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "d", speed); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: setJogRateAngular function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); +} + static int py_call_get_axis_selected() { int value = 0; - // check socket messages for jogspeed + // check socket messages for selected axis pFuncRead = PyObject_GetAttrString(pInstance, "getSelectedAxis"); if (pFuncRead && PyCallable_Check(pFuncRead)) { pValue = PyObject_CallNoArgs(pFuncRead); @@ -1742,8 +1793,10 @@ static void check_hal_changes() int js; rtapi_real floatt; double jogspeed; + double angularjogspeed; int jjog_speed_changed; int ajog_speed_changed; + int ajog_speed_angular_changed; int is_any_axis_selected, deselected; // get python to process socket messages @@ -2009,6 +2062,7 @@ static void check_hal_changes() } else { jjog_speed_changed = 0; } + // axis stuff (selection, homing..) aselect_changed = -1; // flag to see if the selected joint changed @@ -2032,7 +2086,27 @@ static void check_hal_changes() fprintf(stderr, "JogRate value = %f\n", jogspeed ); } - + // if the ANGULAR jog-speed changes while in a continuous jog, we want to + // re-start the jog with the new speed + if (fabs(old_halui_data.ajog_speed_angular - new_halui_data.ajog_speed_angular) > 0.00001) { + old_halui_data.ajog_speed_angular = new_halui_data.ajog_speed_angular; + internalangularjogspeed = new_halui_data.ajog_speed_angular; + ajog_speed_angular_changed = 1; + py_call_axis_angular_jogspeed(internalangularjogspeed); + } else { + ajog_speed_angular_changed = 0; + } + + // check socket messages for ANGULAR jogspeed + angularjogspeed = py_call_axis_get_angular_jogspeed(); + if (fabs(angularjogspeed - lastangularjogspeed) > 0.00001) { + ajog_speed_angular_changed = 1; + lastangularjogspeed = angularjogspeed; + internalangularjogspeed = angularjogspeed; + fprintf(stderr, "angular JogRate value = %f\n", angularjogspeed ); + } + + 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); From 0b7cfec9f1057438ef1f0fd27362dde74f8bffe3 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 12 Apr 2026 10:15:33 -0700 Subject: [PATCH 009/110] halui -use angular jograte pin/messages for angular axis now checks for axis type and uses the apropriate jograte --- lib/python/bridgeui/bridge.py | 9 ++++ src/emc/usr_intf/halui.cc | 98 ++++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 18 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index 56577ebb664..1d0edfaeddd 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -227,6 +227,15 @@ def isAxisSelected(self, index): letter = 'XYZABCUVW'[index] return int(self.axesSelected[letter]) + def getAxisIndexType(self, axis): + try: + num = self.INFO.GET_JOINT_NUM_FROM_AXIS_INDEX[axis] + flag = self.INFO.JOINT_TYPE_INT[num] + return flag + except: + return 1 + + def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 09ac813da1d..4dd76e3ee61 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -262,6 +262,7 @@ static double lastjogspeed = 0; static double internaljogspeed = 0; static double lastangularjogspeed = 0; static double internalangularjogspeed = 0; +static double tempjogspeed = 0; static int lastaxis = -1; static char *mdi_commands[MDI_MAX]; @@ -648,7 +649,7 @@ static int py_call_get_mdi_count() { return value; } -// turns a undex number into a macro name +// turns a index number into a macro name static char* py_call_get_mdi_name( int num) { pFuncWrite = PyObject_GetAttrString(pInstance, "getMdiName"); @@ -1782,6 +1783,35 @@ static void py_call_request_MDI( int index) Py_DECREF(pFuncWrite); } +static int py_call_get_axis_type( int index) { + int value = 0; + // check socket messages for selected axis + pFuncRead = PyObject_GetAttrString(pInstance, "getAxisIndexType"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallFunction(pFuncRead, "i", index); + 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; +} + // this function looks if any of the hal pins has changed // and sends appropriate messages if so static void check_hal_changes() @@ -1796,7 +1826,6 @@ static void check_hal_changes() double angularjogspeed; int jjog_speed_changed; int ajog_speed_changed; - int ajog_speed_angular_changed; int is_any_axis_selected, deselected; // get python to process socket messages @@ -2091,16 +2120,16 @@ static void check_hal_changes() if (fabs(old_halui_data.ajog_speed_angular - new_halui_data.ajog_speed_angular) > 0.00001) { old_halui_data.ajog_speed_angular = new_halui_data.ajog_speed_angular; internalangularjogspeed = new_halui_data.ajog_speed_angular; - ajog_speed_angular_changed = 1; + ajog_speed_changed = 1; py_call_axis_angular_jogspeed(internalangularjogspeed); } else { - ajog_speed_angular_changed = 0; + ajog_speed_changed = 0; } // check socket messages for ANGULAR jogspeed angularjogspeed = py_call_axis_get_angular_jogspeed(); if (fabs(angularjogspeed - lastangularjogspeed) > 0.00001) { - ajog_speed_angular_changed = 1; + ajog_speed_changed = 1; lastangularjogspeed = angularjogspeed; internalangularjogspeed = angularjogspeed; fprintf(stderr, "angular JogRate value = %f\n", angularjogspeed ); @@ -2194,11 +2223,18 @@ static void check_hal_changes() if ( !(axis_mask & (1 << axis_num)) ) { continue; } + // check for axis type: linear/rotary + if (py_call_get_axis_type(axis_num) == 1){ + tempjogspeed = internaljogspeed; + }else{ + tempjogspeed = internalangularjogspeed; + } + // 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,-internaljogspeed,JOGTELEOP); + sendJogCont(axis_num,-tempjogspeed,JOGTELEOP); else sendJogStop(axis_num,JOGTELEOP); old_halui_data.ajog_minus[axis_num] = bit; @@ -2208,7 +2244,7 @@ static void check_hal_changes() 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,internaljogspeed,JOGTELEOP); + sendJogCont(axis_num,tempjogspeed,JOGTELEOP); else sendJogStop(axis_num,JOGTELEOP); old_halui_data.ajog_plus[axis_num] = bit; @@ -2219,7 +2255,7 @@ static void check_hal_changes() 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,(internaljogspeed) * (new_halui_data.ajog_analog[axis_num]),JOGTELEOP); + sendJogCont(axis_num,(tempjogspeed) * (new_halui_data.ajog_analog[axis_num]),JOGTELEOP); else sendJogStop(axis_num,JOGTELEOP); old_halui_data.ajog_analog[axis_num] = floatt; @@ -2229,7 +2265,7 @@ static void check_hal_changes() bit = new_halui_data.ajog_increment_plus[axis_num]; if (bit != old_halui_data.ajog_increment_plus[axis_num]) { if (bit) - sendJogIncr(axis_num, internaljogspeed, new_halui_data.ajog_increment[axis_num],JOGTELEOP); + sendJogIncr(axis_num, tempjogspeed, new_halui_data.ajog_increment[axis_num],JOGTELEOP); old_halui_data.ajog_increment_plus[axis_num] = bit; } @@ -2237,7 +2273,7 @@ static void check_hal_changes() bit = new_halui_data.ajog_increment_minus[axis_num]; if (bit != old_halui_data.ajog_increment_minus[axis_num]) { if (bit) - sendJogIncr(axis_num, internaljogspeed, -(new_halui_data.ajog_increment[axis_num]),JOGTELEOP); + sendJogIncr(axis_num, tempjogspeed, -(new_halui_data.ajog_increment[axis_num]),JOGTELEOP); old_halui_data.ajog_increment_minus[axis_num] = bit; } @@ -2286,12 +2322,20 @@ static void check_hal_changes() sendJogStop(axis_num,JOGTELEOP); } } else { + + // check for axis type: linear/rotary + if (py_call_get_axis_type(axis_num) == 1){ + tempjogspeed = internaljogspeed; + }else{ + tempjogspeed = internalangularjogspeed; + } + hal_set_bool(halui_data->axis_is_selected[axis_num], 1); if (hal_get_bool(halui_data->ajog_plus[num_axes])) { fprintf(stderr, "halui: jog plus: %d\n",num_axes); - sendJogCont(axis_num, internaljogspeed,JOGTELEOP); + sendJogCont(axis_num, tempjogspeed,JOGTELEOP); } else if (hal_get_bool(halui_data->ajog_minus[num_axes])) { - sendJogCont(axis_num, -internaljogspeed,JOGTELEOP); + sendJogCont(axis_num, -tempjogspeed,JOGTELEOP); } } } @@ -2342,8 +2386,17 @@ static void check_hal_changes() bit = new_halui_data.ajog_minus[EMCMOT_MAX_AXIS]; 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, -internaljogspeed,JOGTELEOP); + if (bit != 0){ + + // check for axis type: linear/rotary + if (py_call_get_axis_type(js) == 1){ + tempjogspeed = internaljogspeed; + }else{ + tempjogspeed = internalangularjogspeed; + } + + sendJogCont(js, -tempjogspeed,JOGTELEOP); + } else sendJogStop(js,JOGTELEOP); old_halui_data.ajog_minus[EMCMOT_MAX_AXIS] = bit; @@ -2352,8 +2405,17 @@ static void check_hal_changes() bit = new_halui_data.ajog_plus[EMCMOT_MAX_AXIS]; 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,internaljogspeed,JOGTELEOP); + if (bit != 0){ + + // check for axis type: linear/rotary + if (py_call_get_axis_type(js) == 1){ + tempjogspeed = internaljogspeed; + }else{ + tempjogspeed = internalangularjogspeed; + } + + sendJogCont(js,tempjogspeed,JOGTELEOP); + } else sendJogStop(js,JOGTELEOP); old_halui_data.ajog_plus[EMCMOT_MAX_AXIS] = bit; @@ -2363,7 +2425,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, internaljogspeed, new_halui_data.ajog_increment[EMCMOT_MAX_AXIS],JOGTELEOP); + sendJogIncr(js, tempjogspeed, new_halui_data.ajog_increment[EMCMOT_MAX_AXIS],JOGTELEOP); old_halui_data.ajog_increment_plus[EMCMOT_MAX_AXIS] = bit; } @@ -2371,7 +2433,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, internaljogspeed, -(new_halui_data.ajog_increment[EMCMOT_MAX_AXIS]),JOGTELEOP); + sendJogIncr(js, tempjogspeed, -(new_halui_data.ajog_increment[EMCMOT_MAX_AXIS]),JOGTELEOP); old_halui_data.ajog_increment_minus[EMCMOT_MAX_AXIS] = bit; } From 0c0f5839a72609d17b406c09e3696e0d86d94a7a Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 2 Jul 2026 19:42:10 -0700 Subject: [PATCH 010/110] bridge -look for macro commands too MDI and macro commands are similar enough to combine --- lib/python/bridgeui/bridge.py | 45 +++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index 1d0edfaeddd..c9d8d4aac4b 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -63,14 +63,6 @@ def __init__(self, readAddress = "tcp://127.0.0.1:5690", 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(self): self.jogRate = 0 self.jogRateAngular = 0 @@ -143,7 +135,7 @@ def action(self, msg, data): # send msg to hal_glib def writeMsg(self, msg, data): - print('Write Msg called') + #print('Write Msg called') if ZMQ: topic = self.writeTopic message = json.dumps({'FUNCTION':msg,'ARGS':data}) @@ -170,27 +162,43 @@ def ok(self): def cancel(self): self.writeMsg('request_cancel', True) + # if the number is bigger then MDI command list + # then look for MACRO commands def getMdiName(self, num): if num >len(self.INFO.MDI_COMMAND_DICT)-1: + offset = len(self.INFO.MDI_COMMAND_DICT) + return self.getMacroName(num-offset) + else: + temp = list(self.INFO.MDI_COMMAND_DICT.keys())[num] + LOG.debug('MDI:{} {}'.format(num,temp)) + return temp + + def getMacroName(self, num): + if num >len(self.INFO.MACRO_COMMAND_DICT)-1: return 'None' - temp = list(self.INFO.MDI_COMMAND_DICT.keys())[num] - LOG.debug('{} {}'.format(num,temp)) + temp = list(self.INFO.MACRO_COMMAND_DICT.keys())[num] + LOG.debug('MACRO:{} {}'.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): + # check for any MDI commands first: name = self.getMdiName(num) LOG.debug('Macro name:{} ,index: {}'.format(name, num)) if name != 'None': self.writeMsg('request_macro_call', name) + # else look for any MACRO commands: + else: + offset = len(self.INFO.MDI_COMMAND_DICT) + name = self.getMacroName(num-offset) + LOG.debug('Macro name:{} ,index: {}'.format(name, num)) + if name != 'None': + self.writeMsg('request_macro_call', name) + + # cound of MDI and MACRO commands def getMdiCount(self): - print(len(self.INFO.MDI_COMMAND_DICT)) - return len(self.INFO.MDI_COMMAND_DICT) + #print('->',len(self.INFO.MDI_COMMAND_DICT),len(self.INFO.MACRO_COMMAND_DICT)) + return len(self.INFO.MDI_COMMAND_DICT) + len(self.INFO.MACRO_COMMAND_DICT) def getJogRate(self): return self.jogRate @@ -202,6 +210,7 @@ def getJogRateAngular(self): def setJogRateAngular(self, value): self.writeMsg('set_jograte_angular', value) + # XYZABCUVW, None, or MPG0 def getSelectedAxis(self): name = self.currentSelectedAxis if name == 'None': From 08e3b602adbbb6aa95ab714cba99ecedd1ede59c Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 2 Jul 2026 19:51:13 -0700 Subject: [PATCH 011/110] hal_bridge -add a warning if the write address is already used --- src/hal/user_comps/hal_bridge.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hal/user_comps/hal_bridge.py b/src/hal/user_comps/hal_bridge.py index 1833ab7e7af..39977946508 100644 --- a/src/hal/user_comps/hal_bridge.py +++ b/src/hal/user_comps/hal_bridge.py @@ -118,7 +118,10 @@ def init_hal(self): def init_write(self): context = zmq.Context() self.writeSocket = context.socket(zmq.PUB) - self.writeSocket.bind(self.writeAddress) + try: + self.writeSocket.bind(self.writeAddress) + except zmq.error.ZMQError: + LOG.critical(f'Write address already in use? {self.writeAddress}') def init_read(self): # ZeroMQ Context From 063d6b634bc9d30bf474f72dacab89aeabee9790 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 14:11:26 -0700 Subject: [PATCH 012/110] halui -add reload display and shutdown pins --- lib/python/bridgeui/bridge.py | 8 +++++- src/emc/usr_intf/halui.cc | 54 ++++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index c9d8d4aac4b..777f88e625f 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -134,7 +134,7 @@ def action(self, msg, data): print ('axis state', self.axesSelected,self.currentSelectedAxis) # send msg to hal_glib - def writeMsg(self, msg, data): + def writeMsg(self, msg, data=''): #print('Write Msg called') if ZMQ: topic = self.writeTopic @@ -162,6 +162,12 @@ def ok(self): def cancel(self): self.writeMsg('request_cancel', True) + def reloadDisplay(self): + self.writeMsg('request_reload_display', True) + + def shutdownController(self): + self.writeMsg('request_shutdown') + # if the number is bigger then MDI command list # then look for MACRO commands def getMdiName(self, num): diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 4dd76e3ee61..de316aba602 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -218,6 +218,9 @@ typedef hal_uint_t hal_ui32_t; \ FIELD(bool,gui_ok) /* pin for acknowledging dialog ok */ \ FIELD(bool,gui_cancel) /* pin for acknowledging dialog cancel */ \ +\ + FIELD(bool,gui_reload) /* pin for acknowledging dialog ok */ \ + FIELD(bool,gui_shutdown) /* pin for acknowledging dialog cancel */ \ \ FIELD(real,units_per_mm) \ @@ -566,7 +569,7 @@ static void py_call_cycleStart() { // check socket messages for jogspeed pFuncWrite = PyObject_GetAttrString(pInstance, "cycleStart"); - if (pFuncRead && PyCallable_Check(pFuncWrite)) { + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { pValue = PyObject_CallNoArgs(pFuncWrite); if (pValue == NULL){ fprintf(stderr, "Halui Bridge: cycleStart function failed: returned NULL\n"); @@ -581,7 +584,7 @@ static void py_call_cyclePause() { // check socket messages for jogspeed pFuncWrite = PyObject_GetAttrString(pInstance, "cyclePause"); - if (pFuncRead && PyCallable_Check(pFuncWrite)) { + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { pValue = PyObject_CallNoArgs(pFuncWrite); if (pValue == NULL){ fprintf(stderr, "Halui Bridge: cyclePause function failed: returned NULL\n"); @@ -596,7 +599,7 @@ static void py_call_ok() { // check socket messages for gui ok message pFuncWrite = PyObject_GetAttrString(pInstance, "ok"); - if (pFuncRead && PyCallable_Check(pFuncWrite)) { + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { pValue = PyObject_CallNoArgs(pFuncWrite); if (pValue == NULL){ fprintf(stderr, "Halui Bridge: ok function failed: returned NULL\n"); @@ -610,7 +613,7 @@ static void py_call_cancel() { // check socket messages for gui cancel message pFuncWrite = PyObject_GetAttrString(pInstance, "cancel"); - if (pFuncRead && PyCallable_Check(pFuncWrite)) { + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { pValue = PyObject_CallNoArgs(pFuncWrite); if (pValue == NULL){ fprintf(stderr, "Halui Bridge: cancel function failed: returned NULL\n"); @@ -620,6 +623,34 @@ static void py_call_cancel() { Py_DECREF(pFuncWrite); return ; } +static void py_call_reload_display() { + + // check socket messages for gui reload display message + pFuncWrite = PyObject_GetAttrString(pInstance, "reloadDisplay"); + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: reload display function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} +static void py_call_shutdown_controller() { + + // check socket messages for gui shutdown message + pFuncWrite = PyObject_GetAttrString(pInstance, "shutdownController"); + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: shutdownController 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 @@ -927,6 +958,10 @@ int halui_hal_init(void) CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_cancel), 0, "halui.gui.cancel")); + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_reload), 0, "halui.gui.reload-display")); + + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_shutdown), 0, "halui.gui.shutdown")); + hal_ready(comp_id); return 0; } @@ -2460,6 +2495,17 @@ static void check_hal_changes() fprintf(stderr,"GUI CANCEL command called\n"); py_call_cancel(); } + + if (check_bit_changed(new_halui_data.gui_reload, old_halui_data.gui_reload) != 0) { + fprintf(stderr,"GUI RELOAD DISPLAY command called\n"); + py_call_reload_display(); + } + + if (check_bit_changed(new_halui_data.gui_shutdown, old_halui_data.gui_shutdown) != 0) { + fprintf(stderr,"GUI SHUTDOWN command called\n"); + py_call_shutdown_controller(); + } + } // this function looks at the received NML status message From 36fe719ebf5fe078ca068490f1a81fd4f6c19213 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 14 Jul 2026 21:36:44 -0700 Subject: [PATCH 013/110] halui -add softkey pins and message --- lib/python/bridgeui/bridge.py | 8 ++++++-- src/emc/usr_intf/halui.cc | 36 ++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index 777f88e625f..e04ccb7837d 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -168,8 +168,12 @@ def reloadDisplay(self): def shutdownController(self): self.writeMsg('request_shutdown') - # if the number is bigger then MDI command list - # then look for MACRO commands + def softkey(self, index): + print(f'Softkey index {index}') + self.writeMsg('request_softkey', index) + + # if the number is bigger then MDI command list + # then look for MACRO commands def getMdiName(self, num): if num >len(self.INFO.MDI_COMMAND_DICT)-1: offset = len(self.INFO.MDI_COMMAND_DICT) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index de316aba602..69bab329614 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -54,6 +54,7 @@ static int axis_mask = 0; #define JOGTELEOP 0 #define MDI_MAX 64 +#define SOFTKEY_MAX 20 // FIXME: This will have to go again when we do proper 64-bit // The typedefs are necessary to map to hal_[gs]et_[su]i32() in the expansion @@ -218,6 +219,7 @@ typedef hal_uint_t hal_ui32_t; \ FIELD(bool,gui_ok) /* pin for acknowledging dialog ok */ \ FIELD(bool,gui_cancel) /* pin for acknowledging dialog cancel */ \ + ARRAY(bool,gui_soft_keys,SOFTKEY_MAX) \ \ FIELD(bool,gui_reload) /* pin for acknowledging dialog ok */ \ FIELD(bool,gui_shutdown) /* pin for acknowledging dialog cancel */ \ @@ -275,6 +277,8 @@ static char *gui_mdi_commands[MDI_MAX]; static int num_gui_mdi_commands = 0; static int have_home_all = 0; +static int num_gui_soft_keys = SOFTKEY_MAX; + static int comp_id, done; /* component ID, main while loop */ static int num_axes = 0; //number of axes, taken from the INI [TRAJ] section @@ -954,6 +958,10 @@ int halui_hal_init(void) CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_mdi_commands[n]), 0, "halui.gui.mdi-command-%s", py_call_get_mdi_name(n))); } + for (int n=0; ngui_soft_keys[n]), 0, "halui.gui.softkey-%d", n)); + } + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_ok), 0, "halui.gui.ok")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_cancel), 0, "halui.gui.cancel")); @@ -1818,6 +1826,24 @@ static void py_call_request_MDI( int index) Py_DECREF(pFuncWrite); } +static void py_call_request_softkey( int index) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "softkey"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "i", index); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: softkey function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function softkey"); + } + Py_DECREF(pFuncWrite); +} static int py_call_get_axis_type( int index) { int value = 0; // check socket messages for selected axis @@ -2478,7 +2504,7 @@ static void check_hal_changes() sendMdiCommand(n); } - // request GUI ti run MDI commands + // request GUI to 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){ fprintf(stderr,"GUI MDI command called index: %i\n", n); @@ -2486,6 +2512,14 @@ static void check_hal_changes() } } + // request GUI soft keys + for(int n = 0; n < num_gui_soft_keys; n++) { + if (check_bit_changed(new_halui_data.gui_soft_keys[n], old_halui_data.gui_soft_keys[n]) != 0){ + fprintf(stderr,"GUI SOFTKEY called index: %i\n", n); + py_call_request_softkey(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(); From 175681cbf47e42c07d34c199f6528289bf2badb8 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 18 Jul 2026 21:51:46 -0700 Subject: [PATCH 014/110] halui -change pin names for cycles and softkeys numbers 00 to 19 for softkeys. add gui. to cycle.start and cycle.pause --- src/emc/usr_intf/halui.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 69bab329614..d8886bb26fb 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -861,8 +861,8 @@ int halui_hal_init(void) CHK(halui_export_pin_IN_bit(&(halui_data->mist_off), "halui.mist.off")); CHK(halui_export_pin_IN_bit(&(halui_data->flood_on), "halui.flood.on")); CHK(halui_export_pin_IN_bit(&(halui_data->flood_off), "halui.flood.off")); - CHK(halui_export_pin_IN_bit(&(halui_data->cycle_start), "halui.cycle.start")); - CHK(halui_export_pin_IN_bit(&(halui_data->cycle_pause), "halui.cycle.pause")); + CHK(halui_export_pin_IN_bit(&(halui_data->cycle_start), "halui.gui.cycle.start")); + CHK(halui_export_pin_IN_bit(&(halui_data->cycle_pause), "halui.gui.cycle.pause")); CHK(halui_export_pin_IN_bit(&(halui_data->program_run), "halui.program.run")); CHK(halui_export_pin_IN_bit(&(halui_data->program_pause), "halui.program.pause")); CHK(halui_export_pin_IN_bit(&(halui_data->program_resume), "halui.program.resume")); @@ -959,7 +959,7 @@ int halui_hal_init(void) } for (int n=0; ngui_soft_keys[n]), 0, "halui.gui.softkey-%d", n)); + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_soft_keys[n]), 0, "halui.gui.softkey-%02d", n)); } CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_ok), 0, "halui.gui.ok")); From c9694ec346617c965ccf9c723d6f3e083dbf6be9 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 20 Jul 2026 19:10:15 -0700 Subject: [PATCH 015/110] halui -gui.mpg-select.0 pin name change --- src/emc/usr_intf/halui.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index d8886bb26fb..edb7a8bed29 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -928,7 +928,7 @@ int halui_hal_init(void) CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->ajog_increment_minus[axis_num]), 0, "halui.axis.%c.increment-minus", c)); } - CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->mpg_select0), 0, "halui.mpg-select.0")); + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->mpg_select0), 0, "halui.gui.mpg-select.0")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->joint_home[num_joints]), 0, "halui.joint.selected.home")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->joint_unhome[num_joints]), 0, "halui.joint.selected.unhome")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->jjog_plus[num_joints]), 0, "halui.joint.selected.plus")); From ec03528cee3613bfd7541836c839d466ca5e1d96 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 10 Aug 2026 20:25:12 -0700 Subject: [PATCH 016/110] halui -change gui pin names as discussed on github --- src/emc/usr_intf/halui.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index edb7a8bed29..aac28bea317 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -861,8 +861,8 @@ int halui_hal_init(void) CHK(halui_export_pin_IN_bit(&(halui_data->mist_off), "halui.mist.off")); CHK(halui_export_pin_IN_bit(&(halui_data->flood_on), "halui.flood.on")); CHK(halui_export_pin_IN_bit(&(halui_data->flood_off), "halui.flood.off")); - CHK(halui_export_pin_IN_bit(&(halui_data->cycle_start), "halui.gui.cycle.start")); - CHK(halui_export_pin_IN_bit(&(halui_data->cycle_pause), "halui.gui.cycle.pause")); + CHK(halui_export_pin_IN_bit(&(halui_data->cycle_start), "halui.gui.cycle-start")); + CHK(halui_export_pin_IN_bit(&(halui_data->cycle_pause), "halui.gui.cycle-pause")); CHK(halui_export_pin_IN_bit(&(halui_data->program_run), "halui.program.run")); CHK(halui_export_pin_IN_bit(&(halui_data->program_pause), "halui.program.pause")); CHK(halui_export_pin_IN_bit(&(halui_data->program_resume), "halui.program.resume")); @@ -955,18 +955,18 @@ int halui_hal_init(void) for (int n=0; ngui_mdi_commands[n]), 0, "halui.gui.mdi-command-%s", py_call_get_mdi_name(n))); + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_mdi_commands[n]), 0, "halui.gui.mdi-command.%s", py_call_get_mdi_name(n))); } for (int n=0; ngui_soft_keys[n]), 0, "halui.gui.softkey-%02d", n)); + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_soft_keys[n]), 0, "halui.gui.softkey.%02d", n)); } CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_ok), 0, "halui.gui.ok")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_cancel), 0, "halui.gui.cancel")); - CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_reload), 0, "halui.gui.reload-display")); + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_reload), 0, "halui.gui.reload-preview")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_shutdown), 0, "halui.gui.shutdown")); From 5eb3eee1e34b13e306010aec9c469168a210fd42 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 16 Aug 2025 11:12:19 -0700 Subject: [PATCH 017/110] 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 8de4dddfbbd011500096bf35bb74800b950d6f5c Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:05:52 -0700 Subject: [PATCH 018/110] gladevcp -gtk_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 247d9df4e93..97a6fc442c5 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 024c00277280f5577b3e20d5e6e25fc4e15ba512 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 18:10:59 -0700 Subject: [PATCH 019/110] 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 97a6fc442c5..ae56852891a 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 524282238d2..5c84ae2df89 100644 --- a/lib/python/qtvcp/qt_action.py +++ b/lib/python/qtvcp/qt_action.py @@ -232,7 +232,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) @@ -249,6 +249,9 @@ 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)) @@ -288,6 +291,13 @@ def RUN_MACRO( self, data): self.CALL_MDI(command, mode_return=True) return + # 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 1d76124c82d8618180b30e64272ba0708b4c72d7 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 10 Apr 2026 13:40:16 -0700 Subject: [PATCH 020/110] gladevcp -speedcontrol -add angular jograte type automatically sends and responds to jograte messages --- lib/python/gladevcp/speedcontrol.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index 5dda24b65a3..a30cc26dd3b 100755 --- a/lib/python/gladevcp/speedcontrol.py +++ b/lib/python/gladevcp/speedcontrol.py @@ -100,8 +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), + 'type' : ( GObject.TYPE_INT, 'Type of adjustment', 'Set to -1 for general, 0 for jograte, 1 for angular jograte', + -1, 1, -1, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT), } __gproperties = __gproperties__ @@ -131,6 +131,7 @@ def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, un self._template = template self._speed = inc_speed self.type_linear_jog = False + self.type_angular_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) @@ -184,8 +185,11 @@ def _hal_init(self): self.hal_pin_decrease.connect("value-changed", self._on_minus_changed) if self.type_linear_jog: - print('->>',self.type_linear_jog) + print('linear jograte ->>',self.type_linear_jog) self._status.connect('jograte-changed', lambda w, data: self.set_value(data)) + elif self.type_angular_jog: + print('Angular jograte ->>',self.type_angular_jog) + self._status.connect('jograte-angular-changed', lambda w, data: self.set_value(data)) # this draws our widget on the screen def expose(self, widget, event): @@ -472,6 +476,10 @@ def do_set_property(self, property, value): print(name,value) if value == 0: self.type_linear_jog = True + self.type_angular_jog = False + elif value == 1: + self.type_linear_jog = False + self.type_angular_jog = True self._draw_widget() else: raise AttributeError('unknown property %s' % property.name) From 880a401852c77d42007c43b730142f5d4df38149 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 12 Apr 2026 10:07:58 -0700 Subject: [PATCH 021/110] gladevcp -speedcontrol: add angular jograte command forgot to add the command to output the rate whenn \the widget is changed --- lib/python/gladevcp/speedcontrol.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index a30cc26dd3b..36cde39a08e 100755 --- a/lib/python/gladevcp/speedcontrol.py +++ b/lib/python/gladevcp/speedcontrol.py @@ -285,8 +285,11 @@ 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) + elif self.type_angular_jog: + self._action.SET_JOG_RATE_ANGULAR(value) if value != self._value: self._value = value From 506ac4e874e5ef071be160a26bf514ebf246b95d Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 10 Aug 2026 20:05:45 -0700 Subject: [PATCH 022/110] gladevcp -speedcontrol: quiet print debugs --- lib/python/gladevcp/speedcontrol.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index 36cde39a08e..61475d8bcd7 100755 --- a/lib/python/gladevcp/speedcontrol.py +++ b/lib/python/gladevcp/speedcontrol.py @@ -185,10 +185,8 @@ def _hal_init(self): self.hal_pin_decrease.connect("value-changed", self._on_minus_changed) if self.type_linear_jog: - print('linear jograte ->>',self.type_linear_jog) self._status.connect('jograte-changed', lambda w, data: self.set_value(data)) elif self.type_angular_jog: - print('Angular jograte ->>',self.type_angular_jog) self._status.connect('jograte-angular-changed', lambda w, data: self.set_value(data)) # this draws our widget on the screen @@ -476,7 +474,6 @@ def do_set_property(self, property, 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.type_angular_jog = False From 0b105a5d192d0016d3d7a0eb8eba097e740f2787 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 14:12:15 -0700 Subject: [PATCH 023/110] gladevcp -hal_gremlin: clear the plot if reloading the screen --- lib/python/gladevcp/hal_gremlin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/python/gladevcp/hal_gremlin.py b/lib/python/gladevcp/hal_gremlin.py index c0cfd5675ea..d7badb66747 100644 --- a/lib/python/gladevcp/hal_gremlin.py +++ b/lib/python/gladevcp/hal_gremlin.py @@ -139,6 +139,7 @@ def __init__(self, *a, **kw): def reloadfile(self,w): try: self.fileloaded(None,self._reload_filename) + self.clear_live_plotter() except: pass self.gstat.emit('graphics-gcode-properties',self.gcode_properties) From f496aa1b595187c5b97eb88b693d5b4b9ff23f82 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 10:20:22 -0700 Subject: [PATCH 024/110] gladevcp gtk_action -get shutdown to work reliably gnome-sessions-quit wil not pop up consistantly withoout the gtk dialog --- lib/python/gladevcp/gtk_action.py | 84 +++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index ae56852891a..0b49e213c93 100644 --- a/lib/python/gladevcp/gtk_action.py +++ b/lib/python/gladevcp/gtk_action.py @@ -616,21 +616,25 @@ def ADJUST_GRAPHICS_PAN(self, x, y): def ADJUST_GRAPHICS_ROTATE(self, x, y): STATUS.emit('graphics-view-changed', 'rotate-view', {'X': x, 'Y': y}) + #TODO without the gtk dialog, gnome-sessions + # does not start reliably def SHUT_SYSTEM_DOWN_PROMPT(self): - import subprocess - try: - try: - subprocess.call('gnome-session-quit --power-off', shell=True) - except: - try: - subprocess.call('xfce4-session-logout', shell=True) - except: - try: - subprocess.call('systemctl poweroff', shell=True) - except: - raise - except Exception as e: - LOG.warning("Couldn't shut system down: {}".format(e)) + import shutil + import time + dialog = YesNoDialog(title='System Shutdown') + dialog.set_keep_above(True) + dialog.format_secondary_text('Unsaved data will be lost') + response = dialog.ask_dialog() + dialog.destroy() + if response == gtk.ResponseType.YES: + + if shutil.which('gnome-session-quit'): + subprocess.run(["gnome-session-quit", "--power-off"]) + elif shutil.which('xfce4-session-logout'): + subprocess.call('xfce4-session-logout', shell=True) + else: + # force a shutdown - no prompt + subprocess.call('systemctl poweroff', shell=True) def SHUT_SYSTEM_DOWN_NOW(self): import subprocess @@ -927,6 +931,58 @@ def error(self, exitcode, stderr): dialog.run() dialog.destroy() +########################################### +# Dialog Class +######################################################################## + +class YesNoDialog(gtk.MessageDialog): + def __init__(self, parent=None, message="Are you sure?", title = "Operator Message"): + super(YesNoDialog, self).__init__( + parent=parent, + flags=gtk.DialogFlags.DESTROY_WITH_PARENT, + type=gtk.MessageType.QUESTION, + buttons=gtk.ButtonsType.NONE, + message_format=message) + + yes_button = gtk.Button.new_with_mnemonic("_Yes") + no_button = gtk.Button.new_with_mnemonic("_No") + yes_button.set_size_request(-1, 56) + no_button.set_size_request(-1, 56) + yes_button.connect("clicked",lambda w:self.response(gtk.ResponseType.YES)) + no_button.connect("clicked",lambda w:self.response(gtk.ResponseType.NO)) + box = gtk.HButtonBox() + box.add(no_button) + box.add(yes_button) + box.set_spacing(10) + box.set_layout(gtk.ButtonBoxStyle.CENTER) + self.action_area.add(box) + self.set_border_width(5) + self.connect("response", self.on_yn_response) + self.set_markup(message) + if title: + self.set_title(str(title)) + + def ask_dialog(self): + self.show_all() + #self.emit("play_sound", "alert") + + # wait but don't block event loop + self.RESPONSE = None + while self.RESPONSE is None: + while gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + STATUS.readNextMsg() + gtk.main_iteration() + + rtn = self.RESPONSE + self.hide() + return bool(rtn in(gtk.ResponseType.YES, gtk.ResponseType.ACCEPT)) + + # update internal variable so dialog will respond + def on_yn_response(self,dialog, rtn): + self.RESPONSE = rtn # For testing purposes From 98318f82e27a8c16d09a649eed576d03a1fc0d2d Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 19:22:13 -0700 Subject: [PATCH 025/110] gladevcp -fix gtk_actions: SHUT_SYSTEM_DOWN_PROMPT dialog returns True/False not Gtk enums --- lib/python/gladevcp/gtk_action.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index 0b49e213c93..f8f6948dbe1 100644 --- a/lib/python/gladevcp/gtk_action.py +++ b/lib/python/gladevcp/gtk_action.py @@ -220,7 +220,6 @@ def CALL_INI_MDI(self, key, mode_return = False): # 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) @@ -627,7 +626,6 @@ def SHUT_SYSTEM_DOWN_PROMPT(self): response = dialog.ask_dialog() dialog.destroy() if response == gtk.ResponseType.YES: - if shutil.which('gnome-session-quit'): subprocess.run(["gnome-session-quit", "--power-off"]) elif shutil.which('xfce4-session-logout'): @@ -636,6 +634,7 @@ def SHUT_SYSTEM_DOWN_PROMPT(self): # force a shutdown - no prompt subprocess.call('systemctl poweroff', shell=True) + def SHUT_SYSTEM_DOWN_NOW(self): import subprocess subprocess.call('shutdown now') From 26ad0ecdaec26e40c8df7b1600ddaa8d1b02bf3d Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 11 Aug 2026 19:53:23 -0700 Subject: [PATCH 026/110] gladevcp -speedcontrol: normalise jograte to machine units hal_glib uses machine units for jograte. we need to convert in speedcontrol is in a different unit. --- lib/python/gladevcp/speedcontrol.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index 61475d8bcd7..c9c6d95149d 100755 --- a/lib/python/gladevcp/speedcontrol.py +++ b/lib/python/gladevcp/speedcontrol.py @@ -35,7 +35,7 @@ else: from .hal_widgets import _HalSpeedControlBase -from gladevcp.core import Status, Action +from gladevcp.core import Status, Action, Info class SpeedControl(Gtk.Box, _HalSpeedControlBase): ''' @@ -118,6 +118,7 @@ def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, un self._action = Action() self._status = Status() + self._info = Info() # basic settings self._size = size @@ -185,7 +186,7 @@ def _hal_init(self): self.hal_pin_decrease.connect("value-changed", self._on_minus_changed) if self.type_linear_jog: - self._status.connect('jograte-changed', lambda w, data: self.set_value(data)) + self._status.connect('jograte-changed', lambda w, data: self.status_set_value(data)) elif self.type_angular_jog: self._status.connect('jograte-angular-changed', lambda w, data: self.set_value(data)) @@ -264,6 +265,14 @@ def _draw_widget(self): self.cr.show_text(label) self.cr.stroke() + # hal_glib (_status) sends jograte in machine units + def status_set_value(self, value): + if self._status.is_metric_mode(): + v = self._info.convert_machine_to_metric(value) + else: + v = self._info.convert_machine_to_imperial(value) + self.set_value(v) + # This allows to set the value from external, i.e. propertys def set_value(self, value): self.adjustment.set_value(value) @@ -285,7 +294,12 @@ def _on_value_changed(self, widget): value = widget.get_value() if self.type_linear_jog: - self._action.SET_JOG_RATE(value) + # hal_glib (_status) expects jograte in machine units + if self._status.is_metric_mode(): + v = self._info.convert_metric_to_machine(value) + else: + v = self._info.convert_imperial_to_machine(value) + self._action.SET_JOG_RATE(v) elif self.type_angular_jog: self._action.SET_JOG_RATE_ANGULAR(value) From d044f41de2400ba9052c239ef6442c39e103ee28 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:07:38 -0700 Subject: [PATCH 027/110] 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 c281869bfcb..69f8639be4c 100644 --- a/lib/python/qtvcp/widgets/dialog_widget.py +++ b/lib/python/qtvcp/widgets/dialog_widget.py @@ -416,6 +416,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) @@ -458,6 +459,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): @@ -508,7 +511,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() @@ -516,10 +519,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() @@ -535,6 +557,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, @@ -576,16 +608,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 c14e1138e01c8ad0891566dd6ffe128d754e9807 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 13 Dec 2025 20:42:40 -0800 Subject: [PATCH 028/110] qtvcp -baseclass: register dialogs for later checks like if you want to send responses to the current showing dialog --- lib/python/qtvcp/widgets/widget_baseclass.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/python/qtvcp/widgets/widget_baseclass.py b/lib/python/qtvcp/widgets/widget_baseclass.py index 23bfa396f45..d2012fe8f82 100644 --- a/lib/python/qtvcp/widgets/widget_baseclass.py +++ b/lib/python/qtvcp/widgets/widget_baseclass.py @@ -17,6 +17,7 @@ # the other subclasses are for simple HAL widget functionality import hal + from qtpy.QtCore import Property from qtpy.QtWidgets import QDialog From 704cf38a77c6ac7d9d47de1c699089ef212837ca Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 27 May 2026 19:04:21 -0700 Subject: [PATCH 029/110] qtvcp -axis tool button: allow for MPG selection --- lib/python/qtvcp/widgets/axis_tool_button.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/python/qtvcp/widgets/axis_tool_button.py b/lib/python/qtvcp/widgets/axis_tool_button.py index d2c58f7a55b..9080ae29efd 100644 --- a/lib/python/qtvcp/widgets/axis_tool_button.py +++ b/lib/python/qtvcp/widgets/axis_tool_button.py @@ -134,6 +134,8 @@ def homed_on_test(): self.hal_pin_joint = self.HAL_GCOMP_.newpin(str(pname + '-joint'), hal.HAL_BIT, hal.HAL_OUT) self.hal_pin_axis = self.HAL_GCOMP_.newpin(str(pname + '-axis'), hal.HAL_BIT, hal.HAL_OUT) STATUS.connect('general',self.return_value) + if 'MPG' in self._axis.upper(): + self.settingMenu.clear() def _enableGroup(self, state,bstate): for i in(self.zeroButton, self.setButton,self.divideButton, @@ -226,6 +228,7 @@ def _a_from_j(self, axis): return axis, r[jnum] def selectJoint(self): + print(self.objectName(),f'select: j{self._joint} a{self._axis} ck{self.isChecked()}') if self._block_signal or self._joint == -1 or self._axis == '': return if self.isChecked() == True: if STATUS.is_joint_mode(): @@ -251,7 +254,8 @@ def selectJoint(self): self.hal_pin_axis.set(False) def ChangeState(self, joint = None, axis = None): - #print(self.objectName(),'change',joint,axis,self._axis) + #print(self.objectName(),f'change: j{joint} a{axis} type{self._axis} ck{self.isChecked()}') + # joint mode if STATUS.is_joint_mode(): if int(joint) != self._joint: @@ -269,12 +273,18 @@ def ChangeState(self, joint = None, axis = None): # axis mode else: if str(axis) != self._axis and self.isChecked(): + #print(self.objectName(),'Set false') + if not self.group() in (0,None): + self.group().setExclusive(False) self._block_signal = True self.setChecked(False) self._block_signal = False + if not self.group() in (0,None): + self.group().setExclusive(True) if self._halpin_option and self._axis != '': self.hal_pin_joint.set(False) elif str(axis) == self._axis and not self.isChecked(): + #print(self.objectName(),'Set True') self._block_signal = True self.setChecked(True) self._block_signal = False @@ -311,6 +321,8 @@ def set_axis(self, data): self.goToG53Button.setText(text) text = 'Go To G5x Origin in {}'.format(self._axis) self.goToG5xButton.setText(text) + elif data.upper() in('MPG0','MPG1'): + self._axis = str(data.upper()) else: self._axis = str('') From ce777dea79d90bc8e076d9cfe97a1922df4502e2 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 8 Jul 2026 23:06:07 -0700 Subject: [PATCH 030/110] qtvcp -dialog_widget: check ZMQ messages directly When waiting for a dialog answer (with wait but don't block option) --- lib/python/qtvcp/widgets/dialog_widget.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/python/qtvcp/widgets/dialog_widget.py b/lib/python/qtvcp/widgets/dialog_widget.py index 69f8639be4c..67e1ff6aee0 100644 --- a/lib/python/qtvcp/widgets/dialog_widget.py +++ b/lib/python/qtvcp/widgets/dialog_widget.py @@ -1871,6 +1871,10 @@ def showdialog(self, preload=None, overlay=True, cycle=False, wait=False): if wait: self._flag = True while self._flag: + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + STATUS.readNextMsg() QApplication.processEvents() return (self.display.text(), self._result) else: From f01900acc1b2d429139977e9254fbfd494a88dca Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 29 Jul 2026 16:21:41 -0700 Subject: [PATCH 031/110] qtvcp -fix close dialog response when using zmq messages we still used exec, so zmq messages were blocked --- lib/python/qtvcp/widgets/dialog_widget.py | 57 +++++++++++++++-------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/lib/python/qtvcp/widgets/dialog_widget.py b/lib/python/qtvcp/widgets/dialog_widget.py index 67e1ff6aee0..fc3b4b11ff0 100644 --- a/lib/python/qtvcp/widgets/dialog_widget.py +++ b/lib/python/qtvcp/widgets/dialog_widget.py @@ -104,6 +104,7 @@ def __init__(self, parent=None): self.timer = QTimer() self.seconds_left = 0 self.timer.timeout.connect(self.update_timer) + self._waitflag = False def _hal_init(self): self.read_preference_geometry(self._geoName) @@ -251,10 +252,19 @@ def showdialog(self, messagetext, more_info=None, details=None, display_type='OK self.timer.start(1000) if use_exec: - retval = self.exec() + self._waitflag = True + while self._waitflag: + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + STATUS.readNextMsg() + QApplication.processEvents() + retval = self.result() STATUS.emit('focus-overlay-changed', False, None, None) - LOG.debug('Value of pressed button: {}'.format(retval)) - return self.qualifiedReturn(retval) + LOG.debug('^Value of pressed button: {}'.format(retval)) + rtn = self.qualifiedReturn(retval) + print('return:',rtn) + return rtn # hack to force details box to present open on first display def forceDetailsOpen(self): @@ -276,25 +286,21 @@ def forceDetailsOpen(self): def qualifiedReturn(self, retval): if retval in(QMessageBox.No, QMessageBox.Cancel): + #print('no/cancel') return False - elif retval in(QMessageBox.Ok, QMessageBox.Yes): + if retval in(QMessageBox.Ok, QMessageBox.Yes): + #print('ok/yes') return True - else: + if self.buttonRole(self.clickedButton()) != -1: + #print('button role') + # destruction role button return self.buttonRole(self.clickedButton()) + return retval # move dialog when shown def showEvent(self, event): self.set_geometry() super(LcncDialog, self).showEvent(event) - return - if self._nblock: - self.set_geometry() - else: - geom = self.frameGeometry() - geom.moveCenter(QApplication.primaryScreen().availableGeometry().center()) - self.setGeometry(geom) - super(LcncDialog, self).showEvent(event) - def update_timer(self): self.seconds_left -= 1 @@ -307,7 +313,8 @@ def update_timer(self): def btn_callback(self, i): LOG.debug('Button pressed is: {}'.format(i.text())) - + # stop waiting + self._waitflag = False # update the dialog position self.record_geometry() @@ -315,19 +322,21 @@ def btn_callback(self, i): return self.hide() - btn = self.standardButton(self.clickedButton()) result = self.qualifiedReturn(btn) LOG.debug('Value of {} pressed button: {}'.format(self, result)) self.process_result(result) def process_result(self, result): + self._waitflag = False self.timer.stop() # these directly call a function with btn info if not self._return_callback is None: + #print('callback return') self._return_callback(self, result) # these return via status messages elif self._message is not None: + #print('message return') self._message['RETURN'] = result STATUS.emit('general', self._message) STATUS.emit('focus-overlay-changed', False, None, None) @@ -360,12 +369,24 @@ def _external_update(self, w, message): LOG.debug('Response is: {}'.format(response)) # update the dialog position self.record_geometry() - self.hide() + self._message = message if response == 0: self.process_result(False) + self.reject() elif response == 1: self.process_result(True) + self.accept() + + def accept(self): + self.record_geometry() + super().accept() + self._waitflag = False + + def reject(self): + self.record_geometry() + super().reject() + self._waitflag = False # ********************** # Designer properties @@ -521,10 +542,8 @@ def external_acknowledge(self, state): # 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' From 46f29b957fef7d587c13cbe63f8772995c664cc3 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 15 Aug 2026 05:36:39 -0700 Subject: [PATCH 032/110] qtvcp -axis tool button: quiet debugging print --- lib/python/qtvcp/widgets/axis_tool_button.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/qtvcp/widgets/axis_tool_button.py b/lib/python/qtvcp/widgets/axis_tool_button.py index 9080ae29efd..2a6d06e4676 100644 --- a/lib/python/qtvcp/widgets/axis_tool_button.py +++ b/lib/python/qtvcp/widgets/axis_tool_button.py @@ -228,7 +228,7 @@ def _a_from_j(self, axis): return axis, r[jnum] def selectJoint(self): - print(self.objectName(),f'select: j{self._joint} a{self._axis} ck{self.isChecked()}') + #print(self.objectName(),f'select: j{self._joint} a{self._axis} ck{self.isChecked()}') if self._block_signal or self._joint == -1 or self._axis == '': return if self.isChecked() == True: if STATUS.is_joint_mode(): From 3baf2760d71f58fc1c60f8f15071936fdfc47f45 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:04:00 -0700 Subject: [PATCH 033/110] 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 | 45 ++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/python/common/iniinfo.py b/lib/python/common/iniinfo.py index 57b78707bd7..c81118f6e37 100644 --- a/lib/python/common/iniinfo.py +++ b/lib/python/common/iniinfo.py @@ -18,7 +18,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') @@ -570,7 +570,7 @@ def update(self): if self.INI.hassection('MDI_COMMAND_LIST'): try: for key,value in self.INI.getvariables('MDI_COMMAND_LIST'): - + #print(f'key:{key},value:{value}') # legacy way: list of repeat 'MDI_COMMAND=XXXX' # in this case order matters in the INI if key == 'MDI_COMMAND': @@ -597,24 +597,51 @@ def update(self): # 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) try: name = (key.replace('MDI_COMMAND_','')) mdidatadict = {} - for num,k in enumerate(value.split(',')): - if num == 0: - mdidatadict['cmd'] = k - if len(value.split(',')) <2: - mdidatadict['label'] = None - else: - mdidatadict['label'] = k + #print(f'name:{name}') + # find the last colon in string or 0 + lastCmd = value.rfind(';') + #print('l ;:',lastCmd) + if lastCmd == -1: lastCmd = 0 + + # find the last colon in string or use the string length + lastComma = value.rfind(',', lastCmd) + #print('l comma:',lastComma,lastCmd) + if lastComma == -1: lastComma = len(value) + + label = value[lastComma+1:] + cmd = value[:lastComma] + #print(value,' 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: LOG.error('INI MDI command parse error:{}'.format(e)) + print(self.MDI_COMMAND_DICT) ################ # MACRO commands # From 136094f1556c6efb6e63533adeda7e4dcd4286ae Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 22 Aug 2025 15:33:19 -0700 Subject: [PATCH 034/110] 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 e6460795b0e..9c782d7c002 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -154,7 +154,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)) 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)) @@ -1320,6 +1320,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 3af02a3a9d2c502c84c63d5caa55895ffd641eb9 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 12 Dec 2025 20:15:21 -0800 Subject: [PATCH 035/110] 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 9c782d7c002..139250803ab 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -1472,9 +1472,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 e227fdfdf4731732358cc7b92bc546abdea6ff09 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 13 Dec 2025 20:46:51 -0800 Subject: [PATCH 036/110] qtdragon -find the currently visible dialog to send messages to halui 'ok' or 'cancel' messages --- share/qtvcp/screens/qtdragon/qtdragon_handler.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index 139250803ab..1398b2be61a 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -789,7 +789,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) @@ -2208,6 +2208,11 @@ def dialog_ext_control(self, pin, value, answer): STATUS.emit('dialog-update',{'NAME':name,'response':answer}) return + # fallback + for w in QtWidgets.QApplication.topLevelWidgets(): + if isinstance(w, QtWidgets.QDialog) and not w.isHidden(): + LOG.verbose(f'Found unregistered dialog {w.objectName()}') + def log_version(self): if INFO.RIP_FLAG: t = _translate("HandlerClass","(RIP)") From 04d1e3931d68e24801116231d538b414b80d3e6a Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 14 Mar 2026 19:03:05 -0700 Subject: [PATCH 037/110] qtdragon -fix MPG selection from panel sometimes selecting a combination of MPG,None,X would end up with None selected when MPG should have. --- lib/python/qtvcp/widgets/axis_tool_button.py | 1 - share/qtvcp/screens/qtdragon/qtdragon.ui | 149 ++++++++++-------- .../screens/qtdragon/qtdragon_handler.py | 14 +- 3 files changed, 90 insertions(+), 74 deletions(-) diff --git a/lib/python/qtvcp/widgets/axis_tool_button.py b/lib/python/qtvcp/widgets/axis_tool_button.py index 2a6d06e4676..41a749a32d7 100644 --- a/lib/python/qtvcp/widgets/axis_tool_button.py +++ b/lib/python/qtvcp/widgets/axis_tool_button.py @@ -255,7 +255,6 @@ def selectJoint(self): def ChangeState(self, joint = None, axis = None): #print(self.objectName(),f'change: j{joint} a{axis} type{self._axis} ck{self.isChecked()}') - # joint mode if STATUS.is_joint_mode(): if int(joint) != self._joint: diff --git a/share/qtvcp/screens/qtdragon/qtdragon.ui b/share/qtvcp/screens/qtdragon/qtdragon.ui index d853a2198f4..4edf09dff06 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon.ui +++ b/share/qtvcp/screens/qtdragon/qtdragon.ui @@ -3504,7 +3504,7 @@ PLATE QFrame::Raised - 0 + 9 @@ -5800,9 +5800,9 @@ LOG 0 - 0 - 192 - 547 + -351 + 306 + 734 @@ -12677,69 +12677,6 @@ LIFT - - - - - 1 - 0 - - - - - 58 - 36 - - - - - 16777215 - 76 - - - - MPG -SCROLL - - - true - - - - - - true - - - false - - - - 255 - 0 - 255 - - - - 4 - - - 0.300000000000000 - - - 0 - - - 0 - - - false - - - selectButtonGroup - - - @@ -14573,6 +14510,81 @@ ALL + + + + + 1 + 0 + + + + + 58 + 36 + + + + + 16777215 + 76 + + + + MPG +SCROLL + + + true + + + false + + + QToolButton::DelayedPopup + + + true + + + + 255 + 0 + 255 + + + + 4 + + + 0 + + + 0 + + + -2 + + + MPG0 + + + CALCULATOR + + + false + + + false + + + false + + + false + + + @@ -18094,7 +18106,7 @@ ALL 2 - 02:37:25 + 08:01:11 PM Qt::AlignCenter @@ -19231,4 +19243,3 @@ ALL - diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index 1398b2be61a..480340dc048 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -158,6 +158,7 @@ def __init__(self, halcomp, widgets, paths): 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)) + STATUS.connect('axis-selection-changed', lambda w,data: self.mpg_selection_changed(data)) self.swoopPath = os.path.join(paths.IMAGEDIR,'lcnc_swoop.png') self.swoopURL = QtCore.QUrl.fromLocalFile(self.swoopPath) @@ -1460,6 +1461,12 @@ def btn_systemtool_toggled(self, state): if state: STATUS.emit('dro-reference-change-request', 1) + def mpg_selection_changed(self, data): + if data =='MPG0': + self.recolorMPGFocusBorder() + elif data == 'None': + self.removeMPGFocusBorder() + def MPG_select_changed(self, button): #print(button) # Auto exclusive doesn't allow unchecking all buttons @@ -1472,15 +1479,14 @@ 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: + else: self.removeMPGFocusBorder() + else: + self.removeMPGFocusBorder() #self.set_statusbar('MPG output Selected: {}'.format(cmd.toolTip()),DEFAULT,noLog=True) self._lastSelectButton = button From 58f8ae4ebdcb5403dacfdc06b3c5e547cbe3e6a0 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 2 Jul 2026 19:49:35 -0700 Subject: [PATCH 038/110] qtdragon -external run of macros, use cycle start for MDI running --- .../screens/qtdragon/qtdragon_handler.py | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index 480340dc048..653d909a818 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -923,20 +923,24 @@ def lastRunLine(self, line): # called from hal_glib to run macros from external event def request_macro_call(self, data): + #print(f'macro call data: {data}') if not self.w.chk_auto_mode_ext_macro.isChecked() and not STATUS.is_mdi_mode(): self.add_status(_translate("HandlerClass",'Machine must be in MDI mode to run macros'), WARNING) return - - if 'ini-macro-cmd' in data: + cmd = INFO.get_ini_mdi_command(data) + #print(f'MDI command:{cmd} data:{data}') + if INFO.get_ini_mdi_command(data) is None: data = data.replace('ini-macro-cmd-','') try: temp = INFO.MACRO_COMMAND_DICT.get(data).get('cmd') + #print(temp) self.run_macro(data=temp) return - except: - self.add_status(_translate(f"HandlerClass",'External requested INI macro data not recognized:{data}'), CRITICAL) + except Exception as e: + print(e) + self.add_status(_translate("HandlerClass",f'External requested INI macro data not recognized:{data}'), CRITICAL) - elif 'ini-mdi-cmd' in data: + elif INFO.get_ini_macro_command(data) is None: for b in range(0,10): button = self.w['macrobutton{}'.format(b)] # prefer named INI MDI commands @@ -959,9 +963,9 @@ def request_macro_call(self, data): self.add_status(_translate("HandlerClass",'Error running macro: {} {}\n{}'.format(key, text, e))) break else: - self.add_status(_translate(f"HandlerClass",'External requested INI mdi {data} does not match button name/number'), CRITICAL) + self.add_status(_translate("HandlerClass",f'External requested INI mdi {data} does not match button name/number'), CRITICAL) else: - self.add_status(_translate(f"HandlerClass",'External requested INI macro data not recognized:{data}'), CRITICAL) + self.add_status(_translate("HandlerClass",f'External requested INI macro data not recognized:{data}'), CRITICAL) ####################### # CALLBACKS FROM FORM # @@ -996,10 +1000,20 @@ def cmb_gcode_history_clicked(self): # program frame def btn_start_clicked(self, obj): + if not STATUS.is_all_homed(): self.add_status(_translate("HandlerClass","Machine must be is homed"), CRITICAL) return - if not os.path.exists(self.last_loaded_program): + if STATUS.is_auto_paused(): + self.command.auto(linuxcnc.AUTO_RESUME) + return + if STATUS.is_mdi_mode(): + self.w.mdiline.submit() + return + if STATUS.is_man_mode(): + self.add_status(_translate("HandlerClass","Can't start cycles or submit MDI commands in manual Mode"),WARNING) + return + if not os.path.exists(self.last_loaded_program): self.add_status(_translate("HandlerClass","No program to execute"), WARNING) return if not STATUS.is_auto_mode() and not self.auto_mode_switch: From e5eed1d37396b85c89071531eb1713b9ba2e9e11 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 20 Jul 2026 21:00:13 -0700 Subject: [PATCH 039/110] qtdragon -add basic support for softkeys to select main tabs --- share/qtvcp/screens/qtdragon/qtdragon_handler.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index 653d909a818..319a1f9be27 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -159,6 +159,7 @@ def __init__(self, halcomp, widgets, paths): 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)) STATUS.connect('axis-selection-changed', lambda w,data: self.mpg_selection_changed(data)) + STATUS.connect('softkey-pressed', lambda w,data: self.softkey_pressed(data)) self.swoopPath = os.path.join(paths.IMAGEDIR,'lcnc_swoop.png') self.swoopURL = QtCore.QUrl.fromLocalFile(self.swoopPath) @@ -967,6 +968,17 @@ def request_macro_call(self, data): else: self.add_status(_translate("HandlerClass",f'External requested INI macro data not recognized:{data}'), CRITICAL) + # external request for a softkey press from HALUI/halbridge + def softkey_pressed(self, index): + tmp=['main','file','offsets','tool','status', + 'probe','gcodes','setup','settings', + 'utils','user','camera'] + + btn = self.w[f'btn_{tmp[index]}'] + #print(f'index{index}, btn, {btn}') + if btn.isVisible(): + btn.click() + ####################### # CALLBACKS FROM FORM # ####################### From 2f6b743372983ff358fb17680797027310857651 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 23 Aug 2025 13:57:34 -0700 Subject: [PATCH 040/110] qtdragon -add halui test sim sim -add scale buttons and MPG dial sim -add MPG button on VCP panel sim -fix goto user macro: raise up to Z0 in g53 rather then user units sim -add a macro command to run from the panel sim -update sim control panel sim update for softkeys qtdragon -update sample panel pin names --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 54 + configs/sim/qtdragon/qtdragon_xyz/panel.ui | 1327 +++++++++++++++++ .../qtdragon_xyz/qtdragon_halui_test.ini | 274 ++++ 3 files changed, 1655 insertions(+) create mode 100644 configs/sim/qtdragon/qtdragon_xyz/panel.hal create mode 100644 configs/sim/qtdragon/qtdragon_xyz/panel.ui create mode 100644 configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal new file mode 100644 index 00000000000..89b5bef186f --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -0,0 +1,54 @@ +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 sgui halui.gui.mpg-select.0 panel.select-gui0 + + +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 + +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-MACRO6 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.gui.cycle.start panel.cycle-start +net start halui.gui.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 + +net softkey0 halui.gui.softkey-00 panel.softkey-0 +net softkey1 halui.gui.softkey-01 panel.softkey-1 +net softkey2 halui.gui.softkey-02 panel.softkey-2 +net softkey3 halui.gui.softkey-03 panel.softkey-3 +net softkey4 halui.gui.softkey-04 panel.softkey-4 +net softkey5 halui.gui.softkey-05 panel.softkey-5 +net softkey6 halui.gui.softkey-06 panel.softkey-6 +net softkey7 halui.gui.softkey-07 panel.softkey-7 +net softkey8 halui.gui.softkey-08 panel.softkey-8 +net softkey9 halui.gui.softkey-09 panel.softkey-9 +net softkey10 halui.gui.softkey-10 panel.softkey-10 +net softkey11 halui.gui.softkey-11 panel.softkey-11 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-display panel.reload diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui new file mode 100644 index 00000000000..ecef3135492 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -0,0 +1,1327 @@ + + + MainWindow + + + + 0 + 0 + 583 + 694 + + + + MainWindow + + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + linear rate / angular rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + Qt::Horizontal + + + jog-rate-angular + + + false + + + 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 + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + false + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + false + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + false + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + Abort + + + cycle-abort + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + Axis Selection + + + + 0 + + + 3 + + + 0 + + + 0 + + + 4 + + + + + None + + + true + + + true + + + axis-none + + + + + + + A + + + true + + + true + + + axis-a + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + B + + + true + + + true + + + axis-b + + + + + + + C + + + true + + + true + + + axis-c + + + + + + + GUI + + + true + + + true + + + select-gui0 + + + + + + + + + + + + ESTOP + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + ON + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + HOME + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + MainTab + + + softkey-0 + + + PushButton::BIT + + + + + + + FileTab + + + softkey-1 + + + PushButton::BIT + + + + + + + Offsets + + + softkey-2 + + + PushButton::BIT + + + + + + + Tools + + + softkey-3 + + + PushButton::BIT + + + + + + + Status + + + softkey-4 + + + PushButton::BIT + + + + + + + Probe + + + softkey-5 + + + PushButton::BIT + + + + + + + Gcodes + + + softkey-6 + + + PushButton::BIT + + + + + + + Setup + + + softkey-7 + + + PushButton::BIT + + + + + + + Settings + + + softkey-8 + + + PushButton::BIT + + + + + + + Utils + + + softkey-9 + + + PushButton::BIT + + + + + + + User + + + softkey-10 + + + PushButton::BIT + + + + + + + Cam + + + softkey-11 + + + PushButton::BIT + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Reload + + + reload + + + PushButton::BIT + + + + + + + exit + + + exit + + + PushButton::BIT + + + + + + + + + + + IndicatedPushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Dial + QDial +
qtvcp.widgets.simple_widgets
+
+ + ActionButton + IndicatedPushButton +
qtvcp.widgets.action_button
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+
+ + + + + +
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..5efd17e4785 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini @@ -0,0 +1,274 @@ +# 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 page up to 10 possible +MDI_COMMAND_MACRO0 = G53 G0 Z0;G0 X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + +[MACROS] +MACRO_COMMAND_MACRO6 = go_to_position x-pos y-pos z-pos, Go To \nPosition +MACRO_COMMAND_MACRO2 = lost, LOST +MACRO_COMMAND_MACRO3 = increment x-incr y-incr, INCR +MACRO_COMMAND_MACRO4 = macro_4 +MACRO_COMMAND_MACRO5 = macro_5 + +[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:./macros + +# 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 + + From 33c5ab86f0f873705297a1c7a4cb9124b03a2d89 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 2 Sep 2026 21:08:31 -0700 Subject: [PATCH 041/110] qtdragon/qtdragon_xyz/panel.hal --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index 89b5bef186f..1d1f8628816 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -22,33 +22,33 @@ 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 -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-MACRO6 panel.mdi-2 +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.MACRO6 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.gui.cycle.start panel.cycle-start -net start halui.gui.cycle.pause panel.cycle-pause +net pause halui.gui.cycle-start panel.cycle-start +net start halui.gui.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 -net softkey0 halui.gui.softkey-00 panel.softkey-0 -net softkey1 halui.gui.softkey-01 panel.softkey-1 -net softkey2 halui.gui.softkey-02 panel.softkey-2 -net softkey3 halui.gui.softkey-03 panel.softkey-3 -net softkey4 halui.gui.softkey-04 panel.softkey-4 -net softkey5 halui.gui.softkey-05 panel.softkey-5 -net softkey6 halui.gui.softkey-06 panel.softkey-6 -net softkey7 halui.gui.softkey-07 panel.softkey-7 -net softkey8 halui.gui.softkey-08 panel.softkey-8 -net softkey9 halui.gui.softkey-09 panel.softkey-9 -net softkey10 halui.gui.softkey-10 panel.softkey-10 -net softkey11 halui.gui.softkey-11 panel.softkey-11 +net softkey0 halui.gui.softkey.00 panel.softkey-0 +net softkey1 halui.gui.softkey.01 panel.softkey-1 +net softkey2 halui.gui.softkey.02 panel.softkey-2 +net softkey3 halui.gui.softkey.03 panel.softkey-3 +net softkey4 halui.gui.softkey.04 panel.softkey-4 +net softkey5 halui.gui.softkey.05 panel.softkey-5 +net softkey6 halui.gui.softkey.06 panel.softkey-6 +net softkey7 halui.gui.softkey.07 panel.softkey-7 +net softkey8 halui.gui.softkey.08 panel.softkey-8 +net softkey9 halui.gui.softkey.09 panel.softkey-9 +net softkey10 halui.gui.softkey.10 panel.softkey-10 +net softkey11 halui.gui.softkey.11 panel.softkey-11 net exit halui.gui.shutdown panel.exit -net reload halui.gui.reload-display panel.reload +net reload halui.gui.reload-preview panel.reload From 3299d062791316733a6064ba079130af67c0e18e Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 27 Jul 2025 14:05:58 -0700 Subject: [PATCH 042/110] 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 | 42 ++++++++++++++++++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/configs/sim/gmoccapy/gmoccapy_right_panel.ini b/configs/sim/gmoccapy/gmoccapy_right_panel.ini index 62213d687d5..7b4c5981da8 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 e9ec0965a84..71dc4db0d92 100644 --- a/src/emc/usr_intf/gmoccapy/getiniinfo.py +++ b/src/emc/usr_intf/gmoccapy/getiniinfo.py @@ -380,7 +380,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 d1a9ce80936..1b7a3ec9262 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -431,6 +431,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) @@ -1314,6 +1315,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") @@ -1355,7 +1383,9 @@ 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)) - btn.set_property("tooltip-text", _("Press to run macro {0}").format(name)) + # 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 btn.show() @@ -6518,6 +6548,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 52dae87e227c8dc38bf8bf46089e41162d0bab23 Mon Sep 17 00:00:00 2001 From: CMorley Date: Wed, 30 Jul 2025 20:50:47 -0700 Subject: [PATCH 043/110] 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 1b7a3ec9262..8c963f76283 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -198,7 +198,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 @@ -1317,7 +1318,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") @@ -6597,6 +6608,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 590557a98dc7d46116b54636c352baf708bfdfd8 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:16:35 -0700 Subject: [PATCH 044/110] 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 8c963f76283..5d6f8aaca41 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -433,6 +433,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) @@ -1316,6 +1318,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 5b51745a01372161f29fd5f566084c39dbf853e7 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 21:27:49 -0700 Subject: [PATCH 045/110] 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 5d6f8aaca41..07649711337 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1332,10 +1332,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 bc7c90d57398eda94aa6d826105753223eba8151 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 17 Oct 2025 21:50:03 -0700 Subject: [PATCH 046/110] 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 71dc4db0d92..e9ec0965a84 100644 --- a/src/emc/usr_intf/gmoccapy/getiniinfo.py +++ b/src/emc/usr_intf/gmoccapy/getiniinfo.py @@ -380,7 +380,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 b83270c927b7dca91957495e14837800e627855b Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 14 Dec 2025 01:10:17 -0800 Subject: [PATCH 047/110] 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 08a16ee7249..ab06d809965 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 07649711337..7882b12c75b 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -272,8 +272,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 @@ -435,6 +436,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) @@ -4283,8 +4286,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) @@ -4334,6 +4338,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 524f2bdf0beab6b5c008b8109a5db15ba0ffedec Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Dec 2025 19:48:56 -0800 Subject: [PATCH 048/110] 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 ab06d809965..a28408d9abe 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 7882b12c75b..210f8fc2827 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -272,10 +272,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 @@ -354,6 +350,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() @@ -436,8 +437,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) @@ -1343,7 +1344,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 @@ -1362,7 +1364,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): @@ -1540,8 +1543,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() @@ -1996,8 +2000,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 @@ -2456,8 +2461,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) @@ -4340,21 +4346,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 @@ -4475,8 +4508,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 @@ -4791,8 +4824,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: @@ -4817,8 +4850,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: @@ -5004,8 +5037,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() @@ -5026,7 +5059,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 @@ -5089,8 +5123,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 @@ -5118,7 +5152,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) @@ -5662,28 +5698,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 @@ -5711,13 +5739,15 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): if not tool: message = _("No tool 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 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 @@ -5725,7 +5755,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: @@ -5733,8 +5764,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, @@ -5744,7 +5775,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 @@ -5772,13 +5804,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 @@ -5799,11 +5833,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 @@ -5821,7 +5857,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 3a0dec4152a42579c89f5f9c93e32f0f3b423239 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 00:00:35 -0800 Subject: [PATCH 049/110] 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 049c965ed4f..efe301beca8 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.glade +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.glade @@ -1,5 +1,5 @@ - + @@ -1410,6 +1410,7 @@ uncomment selection
False rgb(255,129,22) 10500 + 0 mm/min 1500 @@ -1456,6 +1457,7 @@ uncomment selection rgb(255,129,22) 3600 %.d + 0 °/min 360 From 70b40dc917a16fef9479c433761cbde9ba8e53e3 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 00:25:59 -0800 Subject: [PATCH 050/110] 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 210f8fc2827..478189a7be1 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -439,6 +439,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) @@ -6353,6 +6354,9 @@ def _on_message_deleted(self, widget, messages, checkbox_checked): 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 7046fbf25fe0c916634a14f0e9c8fb71da9938f7 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 7 Feb 2026 14:45:11 -0800 Subject: [PATCH 051/110] gmoccapy -can submit the current MDI command from HALUI Pressing HALUI's cycle start when in MDI mode will call the current MDI command in gmoccapy's MDI history widget. --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 478189a7be1..5ff095f4961 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1323,12 +1323,21 @@ def _make_joints_button(self): self.joints_button_dic[name] = btn + # halui/external cycle start request def request_start(self,data): - print('start') - self.widgets.btn_run.emit('clicked') + print('start request') + if self.stat.task_mode == linuxcnc.MODE_MDI: + print('Submit MDI') + self.widgets.hal_mdihistory.submit() + elif self.stat.task_mode == linuxcnc.MODE_MANUAL: + self._show_error((13, _("Can't start cycles or submit MDI commands in manual Mode"))) + else: + print('Cycle Start') + self.widgets.btn_run.emit('clicked') + # halui/external pause request def request_pause(self,data): - print('pause') + print('pause request') self.widgets.tbtn_pause.emit('clicked') # call INI macro (from hal_glib message) From 2bf1b64fdc9a7780797d33a49d00ddc46b286dff Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 3 Apr 2026 18:34:43 -0700 Subject: [PATCH 052/110] gmoccapy -update external pause/start behavior fix pausing in MDI don't toggle pause (only pause not unpause) cycle start will unpause if paused --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 5ff095f4961..fc30acea921 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1326,7 +1326,9 @@ def _make_joints_button(self): # halui/external cycle start request def request_start(self,data): print('start request') - if self.stat.task_mode == linuxcnc.MODE_MDI: + if self.GSTAT.is_auto_paused(): + self.command.auto(linuxcnc.AUTO_RESUME) + elif self.stat.task_mode == linuxcnc.MODE_MDI: print('Submit MDI') self.widgets.hal_mdihistory.submit() elif self.stat.task_mode == linuxcnc.MODE_MANUAL: @@ -1338,7 +1340,15 @@ def request_start(self,data): # halui/external pause request def request_pause(self,data): print('pause request') - self.widgets.tbtn_pause.emit('clicked') + + # don't toggle + if self.GSTAT.is_auto_paused(): + return + + if self.stat.task_mode == linuxcnc.MODE_AUTO: + self.widgets.tbtn_pause.emit('clicked') + else: + self.command.auto(linuxcnc.AUTO_PAUSE) # call INI macro (from hal_glib message) def request_macro_call(self, data): From 9de878ac7f8e13585bda69cdb957197f49ac55ca Mon Sep 17 00:00:00 2001 From: CMorley Date: Thu, 9 Apr 2026 17:00:26 -0700 Subject: [PATCH 053/110] gmoccapy -entry dialog respond to halui, add non blocking wait code wait but don't block code added. entry dialogs now can use halui yes and no responses --- src/emc/usr_intf/gmoccapy/dialogs.py | 172 +++++++++++++++-------- src/emc/usr_intf/gmoccapy/gmoccapy.py | 192 +++++++++++--------------- 2 files changed, 195 insertions(+), 169 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index a28408d9abe..dc9418f37ad 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -36,20 +36,27 @@ 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,)), - '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) + self.ent_dialog = self.entry_dialog(caller) + self.yn_dialog = self.yesno_dialog(caller) + # sent from Gstat messages + # first one found visibly gets the answer + # maybe we should check focus? def dialog_ext_control(self, answer): if self.sys_dialog.get_visible(): self.sys_dialog.response(answer) elif self.warn_dialog.get_visible(): self.warn_dialog.response(answer) + elif self.ent_dialog.get_visible(): + self.ent_dialog.response(answer) + elif self.yn_dialog.get_visible(): + self.yn_dialog.response(answer) # This dialog is for unlocking the system tab # The unlock code number is defined at the top of the page @@ -77,66 +84,94 @@ def system_dialog(self, caller): return dialog def show_system_dialog(self): - self.sys_dialog._calc.set_value("") - self.sys_dialog.show_all() + dialog = self.sys_dialog + dialog._calc.set_value("") + dialog.show_all() self.emit("play_sound", "alert") - def on_system_response(self, dialog, result): + # wait but don't block event loop + dialog.RESPONSE = None + while dialog.RESPONSE is None: + while Gtk.events_pending(): + Gtk.main_iteration() + + dialog.hide() + code = dialog._calc.get_value() - print('Code:',code) rtn = -1 - if result == Gtk.ResponseType.ACCEPT: + if dialog.RESPONSE == 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, + return rtn + + def on_system_response(self, dialog, rtn): + dialog.RESPONSE = rtn + + def entry_dialog(self, caller): + dialog = Gtk.Dialog('', caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) - label = Gtk.Label(label) - label.modify_font(Pango.FontDescription("sans 20")) - label.set_margin_top(15) - calc = gladevcp.Calculator() + dialog.label = Gtk.Label('') + dialog.label.modify_font(Pango.FontDescription("sans 20")) + dialog.label.set_margin_top(15) + dialog.calc = gladevcp.Calculator() content_area = dialog.get_content_area() - content_area.pack_start(child=label, expand=False, fill=False, padding=0) - content_area.add(calc) - if data != None: - calc.set_value(data) - else: - calc.set_value("") - calc.set_property("font", "sans 20") - calc.set_editable(True) - calc.entry.connect("activate", lambda w : dialog.emit("response", Gtk.ResponseType.ACCEPT)) + content_area.pack_start(child=dialog.label, expand=False, fill=False, padding=0) + content_area.add(dialog.calc) + dialog.calc.set_property("font", "sans 20") + dialog.calc.set_editable(True) + dialog.calc.entry.connect("activate", lambda w : self.on_entry_response(dialog, Gtk.ResponseType.ACCEPT)) dialog.parse_geometry("460x400") dialog.set_decorated(True) + dialog.connect("response", self.on_entry_response) + return dialog + + def show_entry_dialog(self, data = None, header = _("Enter value") , + label = _("Enter the value to set"), integer = False): + + dialog = self.ent_dialog + if data != None: + dialog.calc.set_value(data) + else: + dialog.calc.set_value("") if integer: # The user is only allowed to enter integer values, we hide some button - calc.integer_entry_only(True) - calc.num_pad_only(True) + dialog.calc.integer_entry_only(True) + dialog.calc.num_pad_only(True) + dialog.label.set_text(label) + dialog.set_title(header) dialog.show_all() - response = dialog.run() - value = calc.get_value() - dialog.destroy() - if response == Gtk.ResponseType.ACCEPT: + + # wait but don't block event loop + dialog.RESPONSE = None + while dialog.RESPONSE is None: + while Gtk.events_pending(): + Gtk.main_iteration() + + dialog.hide() + + value = dialog.calc.get_value() + if dialog.RESPONSE == Gtk.ResponseType.ACCEPT: if value != None: - if integer: - return int(value) + if dialog.calc.integer_only: + qv = int(value) else: - return float(value) + qv = float(value) else: - return "ERROR" - return "CANCEL" + qv = "ERROR" + else: + qv = "CANCEL" + return qv + + def on_entry_response(self, dialog, rtn): + dialog.RESPONSE = rtn # display warning dialog 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, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE, message) @@ -167,32 +202,34 @@ def periodic(): dialog.connect("response", self.on_warning_response) return dialog - def show_warning_dialog(self, title, message, context=None, sound=True,\ + def show_warning_dialog(self, title, message, 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() + dialog = self.warn_dialog + dialog.set_title(title) + dialog.format_secondary_text(message) + dialog.set_markup(message) + 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) + # wait but don't block event loop + dialog.RESPONSE = None + while dialog.RESPONSE is None: + while Gtk.events_pending(): + Gtk.main_iteration() + dialog.hide() - def yesno_dialog(self, caller, message, title = _("Operator Message")): + return dialog.RESPONSE + + def on_warning_response(self, dialog, rtn): + dialog.RESPONSE = rtn + + def yesno_dialog(self, caller): dialog = Gtk.MessageDialog(caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE) - if title: - dialog.set_title(str(title)) - dialog.set_markup(message) yes_button = Gtk.Button.new_with_mnemonic(_("_Yes")) no_button = Gtk.Button.new_with_mnemonic(_("_No")) yes_button.set_size_request(-1, 56) @@ -206,11 +243,30 @@ def yesno_dialog(self, caller, message, title = _("Operator Message")): box.set_layout(Gtk.ButtonBoxStyle.CENTER) dialog.action_area.add(box) dialog.set_border_width(5) + dialog.connect("response", self.on_yn_response) + return dialog + + def show_yesno_dialog(self, caller, message, title = _("Operator Message")): + dialog = self.yn_dialog + dialog.set_markup(message) + if title: + dialog.set_title(str(title)) dialog.show_all() self.emit("play_sound", "alert") - response = dialog.run() - dialog.destroy() - return response == Gtk.ResponseType.YES + + # wait but don't block event loop + dialog.RESPONSE = None + while dialog.RESPONSE is None: + while Gtk.events_pending(): + Gtk.main_iteration() + + rtn = dialog.RESPONSE + dialog.hide() + return bool(rtn in(Gtk.ResponseType.YES, Gtk.ResponseType.ACCEPT)) + + # update internal variable so dialog will respond + def on_yn_response(self,dialog, rtn): + dialog.RESPONSE = rtn def show_user_message(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 fc30acea921..8b5555497d1 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -352,8 +352,6 @@ def __init__(self, argv): 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() @@ -1354,7 +1352,7 @@ def request_pause(self,data): 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) + print(f'MDI command:{cmd} data:{data}') if not cmd is None: LOG.debug("INI MDI COMMAND #: {} = {}".format(data, cmd)) self.ACTION.CALL_INI_MDI(data,mode_return = True) @@ -1365,7 +1363,7 @@ def request_macro_call(self, data): if not self.GSTAT.is_mdi_mode(): message = _("You must be in MDI mode to run macros") self.dialogs.show_warning_dialog( _("Important Warning!"), - message, context=None) + message) return # look thru the INI macros @@ -1385,7 +1383,7 @@ def request_macro_call(self, data): # didn't match a name - give a hint message = _("Macro {} not found ".format(data)) self.dialogs.show_warning_dialog( _("Important Warning!"), - message, context=None) + message) # check if macros are in the INI file and add them to MDI Button List def _make_macro_button(self): @@ -1564,8 +1562,8 @@ def _make_lathe(self): message += _("an X and an Z axis\n") message += _("Wrong lathe configuration, we will leave here") self.dialogs.show_warning_dialog( _("Very critical situation"), - message, context='systemexit') - return + message) + sys.exit() else: if not len(self.axis_list) == 2 and not len(self.axis_list) < 6: self._arrange_jog_button_by_axis() @@ -2021,8 +2019,8 @@ def _init_tooleditor(self): message = _("Did not find a toolfile file in [EMCIO] TOOL_TABLE") LOG.error(message) self.dialogs.show_warning_dialog( _("Very critical situation"), - message, context='systemexit') - return + message) + sys.exit() 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 @@ -2178,15 +2176,15 @@ def on_tool_col_edit_started(self, widget, filtered_path, new_text, col): toolpage = self.widgets.tooledit1 toolview = toolpage.view1 model, treeiter = toolview.get_selection().get_selected() - value = self.dialogs.entry_dialog(self, - data=model[treeiter][col], + value = self.dialogs.show_entry_dialog(data=model[treeiter][col], header=_("Enter value"), label=_("Tool") + f" {model[treeiter][1]}, {captations[col]}:", integer=col in [1,2,15]) + 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.warning_dialog(_("Conversion error !"), + ("Please enter only numerical values\nValues have not been applied")) elif value == "CANCEL": pass else: @@ -2482,8 +2480,8 @@ def _init_offsetpage(self): message = _("Did not find a parameter file in [RS274NGC] PARAMETER_FILE") LOG.error(message) self.dialogs.show_warning_dialog( _("Very critical situation"), - message, context='systemexit') - return + message) + sys.exit() path = os.path.join(CONFIGPATH, parameterfile) self.widgets.offsetpage1.set_filename(path) @@ -2581,12 +2579,13 @@ def on_offset_col_edit_started(self, widget, filtered_path, new_text, col): path = offsetpage.modelfilter.get_path(treeiter) (store_path,) = offsetpage.modelfilter.convert_path_to_child_path(path) row = store_path - if self.widgets.offsetpage1.btn_edit_offsets.get_active(): - offset = self.dialogs.entry_dialog(self, - data=offsetpage.store[row][col], + if self.widgets.offsetpage1.btn_edit_offsets.get_active() or \ + self.touch_button_dic["edit_offsets"].get_active(): + value = self.dialogs.show_entry_dialog(data=offsetpage.store[row][col], header=_("Enter value for offset"), label=f"{offsetpage.store[row][0]} {AXISLIST[col]}-" + _("offset:"), integer=False) + if offset == "ERROR": LOG.debug("conversion error") self.dialogs.warning_dialog(self, _("Conversion error !"), @@ -2738,7 +2737,7 @@ def _show_user_message(self, pin, message): self.halcomp["messages." + message[2] + "-waiting"] = 1 self.halcomp["messages." + message[2] + "-response"] = 0 title = "Pin " + message[2] + " message" - response = self.dialogs.yesno_dialog(self, message[0], title) + response = self.dialogs.show_yesno_dialog(self, message[0], title) self.halcomp["messages." + message[2] + "-waiting"] = 0 self.halcomp["messages." + message[2] + "-response"] = response else: @@ -3246,13 +3245,14 @@ def on_hal_status_mode_mdi(self, widget): self.last_key_event = None, 0 def on_mdi_calculation_start(self, *args): - position = self.widgets.hal_mdihistory.entry.get_position() - print("position: ", position) - value = self.dialogs.entry_dialog(self, + value = self.dialogs.show_entry_dialog( data=self.widgets.hal_mdihistory.entry.get_text(), header=_("Enter value"), label=_("Calculate value to insert"), integer=False) + + position = self.widgets.hal_mdihistory.entry.get_position() + if value == "ERROR": LOG.debug("conversion error") self.dialogs.warning_dialog(self, _("Conversion error !"), @@ -3458,8 +3458,9 @@ def _on_btn_macro_pressed( self, widget = None, data = None ): command = str( "O<" + o_codes[0] + "> call" ) for code in o_codes[1:]: - parameter = self.dialogs.entry_dialog(self, data=None, header=_("Enter value:"), + parameter = self.dialogs.show_entry_dialog(data=None, header=_("Enter value:"), label=f"{code}:", integer=False) + if parameter == "ERROR": LOG.debug("conversion error") self.dialogs.warning_dialog(self, _("Conversion error !"), @@ -4312,9 +4313,12 @@ 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(): - self.dialogs.show_system_dialog() - # we will wait for response - return + code = self.dialogs.show_system_dialog() + # cancelled? + if code == -1: + self.widgets.tbtn_setup.set_active(False) + widget.set_image(self.widgets.img_settings) + return # Lets see if the user has the right to enter settings if code: self.widgets.ntb_main.set_current_page(1) @@ -4326,7 +4330,7 @@ def on_tbtn_setup_toggled(self, widget, data=None): 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) self.widgets.tbtn_setup.set_active(False) widget.set_image(self.widgets.img_settings) else: @@ -4364,52 +4368,6 @@ 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('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) - 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.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) - 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 def on_tbtn_user_tabs_toggled(self, widget, data=None): if widget.get_active(): @@ -4529,7 +4487,7 @@ def on_btn_classicladder_clicked(self, widget, data=None): p = os.popen("classicladder &", "w") else: self.dialogs.show_warning_dialog(_("INFO:"), - _("Classicladder real-time component not detected"), context='classicfail') + _("Classicladder real-time component not detected")) # ========================================================= # spindle stuff @@ -4823,7 +4781,7 @@ def on_btn_delete_clicked(self, widget, data=None): message = _("Do you really want to delete the MDI history?\n") message += _("This will not delete the MDI History file, but will\n" "delete the listbox entries for this session.") - result = self.dialogs.yesno_dialog(self, message, _("Attention!!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!!")) if result: self.widgets.hal_mdihistory.model.clear() @@ -4837,15 +4795,14 @@ def on_btn_show_calc_clicked(self, widget): text = mdi_entry.get_text() data = text[bounds[0]:bounds[1]] has_selection = True - value = self.dialogs.entry_dialog(self, - data=data, + value = self.dialogs.show_entry_dialog(data=data, header=_("Enter value"), label=_("Calculate value to insert"), integer=False) if value == "ERROR": LOG.debug("conversion error") - self.dialogs.show_warning_dialog(_("INFO:"), - _("Please enter only numerical values\nValues have not been applied"), context=None) + self.dialogs.show_warning_dialog(_("Conversion error !"), + ("Please enter only numerical values\nValues have not been applied")) elif value == "CANCEL": return else: @@ -4863,15 +4820,14 @@ def on_btn_show_calc_clicked(self, widget): bounds = buffer.get_selection_bounds() data = buffer.get_text(bounds[0],bounds[1],False) has_selection = True - value = self.dialogs.entry_dialog(self, - data=data, + value = self.dialogs.show_entry_dialog(data=data, header=_("Enter value"), label=_("Calculate value to insert"), integer=False) if value == "ERROR": LOG.debug("conversion error") - self.dialogs.show_warning_dialog(_("INFO:"), - _("Please enter only numerical values\nValues have not been applied"), context=None) + self.dialogs.show_warning_dialog(_("Conversion error !"), + ("Please enter only numerical values\nValues have not been applied")) elif value == "CANCEL": return else: @@ -4930,7 +4886,7 @@ def on_btn_back_clicked(self, widget, data=None): if self.widgets.ntb_button.get_current_page() == _BB_EDIT: # edit mode, go back to auto_buttons if self.file_changed: message = _("Exit and discard changes?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return self.widgets.ntb_button.set_current_page(_BB_AUTO) @@ -4943,7 +4899,7 @@ def on_btn_back_clicked(self, widget, data=None): else: # else we go to main button on manual if self.widgets.tooledit1.edited: message = _("Discard unsaved changes and exit?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return # check if offset values for current tool have been changed @@ -4965,7 +4921,7 @@ def on_btn_back_clicked(self, widget, data=None): "Do you want to activate tool compensation (G43)\n" \ "using the currently active tool offset?") if message: - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if result: # user says YES self.command.mode(linuxcnc.MODE_MDI) self.command.wait_complete() @@ -5043,22 +4999,22 @@ def _on_btn_set_value_clicked(self, widget, data=None): if self.lathe_mode and axis =="x": if self.diameter_mode: preset = self.prefs.getpref("diameter offset_axis_{0}".format(axis), 0, float) - offset = self.dialogs.entry_dialog(self, data=preset, header=_("Enter value for diameter"), + offset = self.dialogs.show_entry_dialog(data=preset, header=_("Enter value for diameter"), label=_("Set diameter to:"), integer=False) else: preset = self.prefs.getpref("radius offset_axis_{0}".format(axis), 0, float) - offset = self.dialogs.entry_dialog(self, data=preset, header=_("Enter value for radius"), + offset = self.dialogs.show_entry_dialog(data=preset, header=_("Enter value for radius"), label=_("Set radius to:"), integer=False) else: preset = self.prefs.getpref("offset_axis_{0}".format(axis), 0, float) - offset = self.dialogs.entry_dialog(self, data=preset, header=_("Enter value for axis {0}").format(axis.upper()), + offset = self.dialogs.show_entry_dialog(data=preset, header=_("Enter value for axis {0}").format(axis.upper()), label=_("Set axis {0} to:").format(axis.upper()), integer=False) if offset == "CANCEL": return elif offset == "ERROR": LOG.debug("Conversion error in btn_set_value") self.dialogs.show_warning_dialog(_("Conversion error in btn_set_value!"), - _("Please enter only numerical values\nValues have not been applied"), context=None) + _("Please enter only numerical values\nValues have not been applied")) else: self.command.mode(linuxcnc.MODE_MDI) self.command.wait_complete() @@ -5080,7 +5036,7 @@ def _on_btn_set_selected_clicked(self, widget, data=None): 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.show_warning_dialog(_("Important Warning!"), - message, context=None) + message) return if system == self.system_list[self.stat.g5x_index]: return @@ -5130,7 +5086,7 @@ def on_chk_reload_tool_toggled(self, widget, data=None): def on_btn_block_height_clicked(self, widget, data=None): probeheight = self.widgets.spbtn_probe_height.get_value() preset = self.prefs.getpref("blockheight", 0.0, float) - blockheight = self.dialogs.entry_dialog(self, data=preset, header=_("Enter the block height"), + blockheight = self.dialogs.show_entry_dialog(data=preset, header=_("Enter the block height"), label=_("Block height measured from base table"), integer=False) if blockheight == "CANCEL" or blockheight == "ERROR": @@ -5144,7 +5100,7 @@ def on_btn_block_height_clicked(self, widget, data=None): self.prefs.putpref("blockheight", 0.0, float) self.prefs.putpref("probeheight", 0.0, float) self.dialogs.show_warning_dialog(_("Conversion error in btn_block_height!"), - _("Please enter only numerical values\nValues have not been applied"), context=None) + _("Please enter only numerical values\nValues have not been applied")) # set coordinate system to new origin origin = self.get_ini_info.get_axis_2_min_limit() + blockheight @@ -5173,7 +5129,7 @@ def _set_icon_theme(self, name): # 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.show_warning_dialog( _("Just to warn you"), - message, context=None) + message) else: self.icon_theme.set_custom_theme(name) @@ -5718,10 +5674,24 @@ def on_tool_change(self, widget): except: message = _("Tool\n\n# {0:d}\n\n not in the tool table!").format(toolnumber) - self.dialogs.show_warning_dialog( _("Manual Tool change"), + result = self.dialogs.show_warning_dialog( _("Manual Tool change"), message, context='mantoolchange', 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.show_warning_dialog( _("Just to warn you"), + message) + else: self.halcomp['toolchange-changed'] = False @@ -5731,7 +5701,7 @@ def on_btn_delete_tool_clicked(self, widget, data=None): 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) + message) return self.widgets.tooledit1.delete_selected_row(widget) self.widgets.tooledit1.edited = True @@ -5743,7 +5713,7 @@ def on_btn_add_tool_clicked(self, widget, data=None): def on_btn_reload_tooltable_clicked(self, widget, data=None): if self.widgets.tooledit1.edited: message = _("Discard unsaved changes and reload the table?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return self.widgets.tooledit1.reload(None) @@ -5760,14 +5730,14 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): message = _("No tool selected in the tool table. ") message += _("Please select only one tool in the table!") self.dialogs.show_warning_dialog( _("Warning Tool Touch off not possible!"), - message, context=None) + message) return if 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.show_warning_dialog( _("Warning Tool Touch off not possible!"), - message, context=None) + message) self.widgets.tooledit1.reload(self) self.widgets.tooledit1.set_selected_tool(self.stat.tool_in_spindle) return @@ -5776,7 +5746,7 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): 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.show_warning_dialog( _("Warning Tool Touch off not possible!"), - message, context=None) + message) return if widget == self.widgets.btn_tool_touchoff_x: @@ -5785,10 +5755,10 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): axis = "z" else: 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) + _("You managed to come to a place that is not possible in on_btn_tool_touchoff")) return - value = self.dialogs.entry_dialog(self, data=None, + value = self.dialogs.show_entry_dialog(data=None, header=_("Enter value for axis {0} to set:").format(axis.upper()), label=_("Set parameter of tool {0:d} and axis {1} to:").format(self.stat.tool_in_spindle, axis.upper()), integer=False) @@ -5796,7 +5766,7 @@ 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.show_warning_dialog( _("Conversion error !"), - message, context=None) + message) return elif value == "CANCEL": return @@ -5816,23 +5786,23 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): def on_btn_select_tool_by_no_clicked(self, widget, data=None): if self.widgets.tooledit1.edited: message = _("Discard unsaved changes and change tool?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return - value = self.dialogs.entry_dialog(self, data=None, header=_("Enter the tool number as integer "), + value = self.dialogs.show_entry_dialog(data=None, header=_("Enter the tool number as integer "), label=_("Select the tool to change"), integer=True) if value == "ERROR": message = _("Conversion error because of wrong entry for tool number.\n") message += _("Enter only integer numbers!") self.dialogs.show_warning_dialog( _("Conversion error !"), - message, context=None) + message) 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.show_warning_dialog( _("Important Warning!"), - message, context=None) + message) return else: self.tool_change = True @@ -5847,19 +5817,19 @@ def on_btn_select_tool_by_no_clicked(self, widget, data=None): def on_btn_selected_tool_clicked(self, widget, data=None): if self.widgets.tooledit1.edited: message = _("Discard unsaved changes and change tool?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return 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.show_warning_dialog( _("Important Warning!"), - message, context=None) + message) return if tool == self.stat.tool_in_spindle: message = _("Selected tool is already in spindle, no change needed.") self.dialogs.show_warning_dialog( _("Important Warning!"), - message, context=None) + message) return if tool or tool == 0: self.tool_change = True @@ -5878,7 +5848,7 @@ def on_btn_selected_tool_clicked(self, widget, data=None): else: message = _("Could not understand the entered tool number. Will not change anything!") self.dialogs.show_warning_dialog( _("Important Warning!"), - message, context=None) + message) # ========================================================= From 9dbf1225b87ac84636e7020473e8e8e7d0c28f41 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 30 Jul 2026 09:44:20 -0700 Subject: [PATCH 054/110] gmoccapy -dialogs: search through focused dialogs, clean up code --- src/emc/usr_intf/gmoccapy/dialogs.py | 84 ++++++++++++++++++---------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index dc9418f37ad..f09744d41bb 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -40,10 +40,11 @@ class Dialogs(GObject.GObject): def __init__(self, caller): GObject.GObject.__init__(self) - self.sys_dialog = self.system_dialog(caller) - self.warn_dialog = self.warning_dialog(caller) - self.ent_dialog = self.entry_dialog(caller) - self.yn_dialog = self.yesno_dialog(caller) + self._caller = caller + self.sys_dialog = self.system_dialog() + self.warn_dialog = self.warning_dialog() + self.ent_dialog = self.entry_dialog() + self.yn_dialog = self.yesno_dialog() # sent from Gstat messages # first one found visibly gets the answer @@ -57,19 +58,30 @@ def dialog_ext_control(self, answer): self.ent_dialog.response(answer) elif self.yn_dialog.get_visible(): self.yn_dialog.response(answer) + else: + # Get the widget that currently has user focus + focused_widget = self._caller.widgets.window1.get_focus() + + # To inspect all open windows (including dialogs) in your application: + for window in Gtk.Window.list_toplevels(): + if window.get_visible() and isinstance(window, Gtk.Dialog): + print("Found active dialog title:", window.get_title()) + window.response(answer) + return + + print("Can't send external response: No dialog found") # This dialog is for unlocking the system tab # The unlock code number is defined at the top of the page - def system_dialog(self, caller): + def system_dialog(self): dialog = Gtk.Dialog(_("Enter System Unlock Code"), - caller.widgets.window1, + self._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("") @@ -93,6 +105,10 @@ def show_system_dialog(self): dialog.RESPONSE = None while dialog.RESPONSE is None: while Gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + self._caller.GSTAT.readNextMsg() Gtk.main_iteration() dialog.hide() @@ -100,7 +116,7 @@ def show_system_dialog(self): code = dialog._calc.get_value() rtn = -1 if dialog.RESPONSE == Gtk.ResponseType.ACCEPT: - if code == int(dialog._caller.unlock_code): + if code == int(self._caller.unlock_code): rtn = 1 else: rtn = 0 @@ -110,9 +126,9 @@ def show_system_dialog(self): def on_system_response(self, dialog, rtn): dialog.RESPONSE = rtn - def entry_dialog(self, caller): + def entry_dialog(self): dialog = Gtk.Dialog('', - caller.widgets.window1, + self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) dialog.label = Gtk.Label('') dialog.label.modify_font(Pango.FontDescription("sans 20")) @@ -148,6 +164,10 @@ def show_entry_dialog(self, data = None, header = _("Enter value") , dialog.RESPONSE = None while dialog.RESPONSE is None: while Gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + self._caller.GSTAT.readNextMsg() Gtk.main_iteration() dialog.hide() @@ -169,10 +189,10 @@ def on_entry_response(self, dialog, rtn): dialog.RESPONSE = rtn # display warning dialog - def warning_dialog(self, caller, message = '', secondary = None, title = _("Operator Message"),\ + def warning_dialog(self, message = '', secondary = None, title = _("Operator Message"),\ sound = True, confirm_pin = 'warning-confirm', active_pin = None): - dialog = Gtk.MessageDialog(caller.widgets.window1, + dialog = Gtk.MessageDialog(self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE, message) # if there is a secondary message then the first message text is bold @@ -190,11 +210,11 @@ def warning_dialog(self, caller, message = '', secondary = None, title = _("Oper dialog.set_title(title) dialog.context = [] def periodic(): - if caller.halcomp[confirm_pin]: + if self._caller.halcomp[confirm_pin]: dialog.response(Gtk.ResponseType.OK) return False if active_pin is not None: - if not caller.halcomp[active_pin]: + if not self._caller.halcomp[active_pin]: dialog.response(Gtk.ResponseType.CANCEL) return False return True @@ -216,6 +236,10 @@ def show_warning_dialog(self, title, message, sound=True, dialog.RESPONSE = None while dialog.RESPONSE is None: while Gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + self._caller.GSTAT.readNextMsg() Gtk.main_iteration() dialog.hide() @@ -225,8 +249,8 @@ def show_warning_dialog(self, title, message, sound=True, def on_warning_response(self, dialog, rtn): dialog.RESPONSE = rtn - def yesno_dialog(self, caller): - dialog = Gtk.MessageDialog(caller.widgets.window1, + def yesno_dialog(self): + dialog = Gtk.MessageDialog(self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE) @@ -246,7 +270,7 @@ def yesno_dialog(self, caller): dialog.connect("response", self.on_yn_response) return dialog - def show_yesno_dialog(self, caller, message, title = _("Operator Message")): + def show_yesno_dialog(self, message, title = _("Operator Message")): dialog = self.yn_dialog dialog.set_markup(message) if title: @@ -258,6 +282,10 @@ def show_yesno_dialog(self, caller, message, title = _("Operator Message")): dialog.RESPONSE = None while dialog.RESPONSE is None: while Gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + self._caller.GSTAT.readNextMsg() Gtk.main_iteration() rtn = dialog.RESPONSE @@ -268,8 +296,8 @@ def show_yesno_dialog(self, caller, message, title = _("Operator Message")): def on_yn_response(self,dialog, rtn): dialog.RESPONSE = rtn - def show_user_message(self, caller, message, title = _("Operator Message")): - dialog = Gtk.MessageDialog(caller.widgets.window1, + def show_user_message(self, message, title = _("Operator Message")): + dialog = Gtk.MessageDialog(self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE) @@ -290,7 +318,7 @@ def show_user_message(self, caller, message, title = _("Operator Message")): return response == Gtk.ResponseType.OK # dialog for run from line - def restart_dialog(self, caller): + def restart_dialog(self): # highlight the gcode down one line lower # used for run-at-line restart @@ -314,16 +342,16 @@ def on_enter_button(widget, obj, calc): obj.widgets.gcode_view.set_line_number(line) restart_dialog = Gtk.Dialog(_("Restart Entry"), - caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) + self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) label = Gtk.Label(_("Restart Entry")) label.modify_font(Pango.FontDescription("sans 20")) restart_dialog.vbox.pack_start(label, False, False, 0) calc = gladevcp.Calculator() restart_dialog.vbox.add(calc) - calc.set_value("%d" % caller.widgets.gcode_view.get_line_number()) + calc.set_value("%d" % self._caller.widgets.gcode_view.get_line_number()) calc.set_property("font", "sans 20") calc.set_editable(True) - calc.entry.connect("activate", on_enter_button, caller, calc) + calc.entry.connect("activate", on_enter_button, self._caller, calc) calc.integer_entry_only(True) calc.num_pad_only(True) # add additional buttons @@ -336,9 +364,9 @@ def on_enter_button(widget, obj, calc): calc.table.attach(upbutton,3,1,1,1) calc.table.attach(downbutton,3,2,1,1) calc.table.attach(enterbutton,3,3,1,1) - upbutton.connect("clicked", restart_up, caller, calc) - downbutton.connect("clicked", restart_down, caller, calc) - enterbutton.connect("clicked", on_enter_button, caller, calc) + upbutton.connect("clicked", restart_up, self._caller, calc) + downbutton.connect("clicked", restart_down, self._caller, calc) + enterbutton.connect("clicked", on_enter_button, self._caller, calc) restart_dialog.parse_geometry("410x400+0+0") restart_dialog.show_all() @@ -350,5 +378,5 @@ def on_enter_button(widget, obj, calc): line = int(calc.get_value()) if line == None: line = 0 - caller.widgets.gcode_view.set_line_number(line) - caller.start_line = line + self._caller.widgets.gcode_view.set_line_number(line) + self._caller.start_line = line From 8c534eb5074586fe8c19731e59e0dd81389c6983 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 10 Apr 2026 13:41:47 -0700 Subject: [PATCH 055/110] gmoccapy -update the angular speedcontrol to new angular type --- src/emc/usr_intf/gmoccapy/gmoccapy.glade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.glade b/src/emc/usr_intf/gmoccapy/gmoccapy.glade index efe301beca8..622a1cc2c4c 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.glade +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.glade @@ -1457,7 +1457,7 @@ uncomment selection rgb(255,129,22) 3600 %.d - 0 + 1 °/min 360 From 46b7a172b5dcd7b5a8f1959032101721908e62f0 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 14:13:15 -0700 Subject: [PATCH 056/110] gmoccapy -honour shutdown request --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 8b5555497d1..bbc34a188a5 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -438,6 +438,7 @@ def __init__(self, argv): 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()) + self.GSTAT.connect('shutdown-request', lambda w: self.on_btn_exit_clicked(w)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) From 3b57d7564c46aaa3a623677d1e96084ad43bd961 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 10:21:38 -0700 Subject: [PATCH 057/110] gmoccapy -respond to softkeys and make exit do shutdown --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index bbc34a188a5..c9420fd31af 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -438,7 +438,8 @@ def __init__(self, argv): 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()) - self.GSTAT.connect('shutdown-request', lambda w: self.on_btn_exit_clicked(w)) + self.GSTAT.connect('shutdown-request', lambda w: self.system_shutdown(w)) + self.GSTAT.connect('softkey-pressed', lambda w,data: self.softkey_pressed(data)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -2939,6 +2940,9 @@ def on_rbt_auto_pressed(self, widget, data=None): def on_btn_exit_clicked(self, widget, data=None): self.widgets.window1.destroy() + def system_shutdown(self, widget): + self.ACTION.SHUT_SYSTEM_DOWN_PROMPT() + # button handlers End # ========================================================= @@ -6405,6 +6409,20 @@ def _blockdelete(self, pin): self.command.set_optional_stop(pin.get()) # ========================================================= + # external request for a softkey press from HALUI/halbridge + def softkey_pressed(self, index): + if index > 9: + location = "bottom" + number = index -10 + elif index < 7: + location = "right" + number = index + else: + LOG.debug(f"Could not translate softkey {index} to button number") + LOG.debug(f"softkey index: {index}, location: {location}, number: {number}") + button = self._get_child_button(location, number) + self.process_button(button) + # The actions of the buttons def _button_pin_changed(self, pin): # we check if the button is pressed or released, @@ -6428,6 +6446,9 @@ def _button_pin_changed(self, pin): return button = self._get_child_button(location, number) + self.process_button(button) + + def process_button(self, button): if not button: LOG.debug("no button here") return From bac9c71f70aba84227ad57b71e987d57428d1cc5 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 13:23:52 -0700 Subject: [PATCH 058/110] gmoccapy -add MPG input to zoom and scroll scroll the gcode if in auto and not running otherwise zoom the gcode display. Only if the MPG is enabled from HALUI --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index c9420fd31af..b0d1ba262e1 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -228,6 +228,8 @@ def __init__(self, argv): self.so_counts = 0 # need to calculate difference in counts to change the spindle override slider self.jv_counts = 0 # need to calculate difference in counts to change the jog_vel slider self.ro_counts = 0 # need to calculate difference in counts to change the rapid override slider + self._last_count = 0 # need to calculate MPG count difference + self.mpg_enabled = False self.spindle_override = 1 # holds the feed override value and is needed to be able to react to halui pin self.feed_override = 1 # holds the spindle override value and is needed to be able to react to halui pin @@ -440,6 +442,7 @@ def __init__(self, argv): self.GSTAT.connect('cancel-request', lambda w, state: self._del_notification()) self.GSTAT.connect('shutdown-request', lambda w: self.system_shutdown(w)) self.GSTAT.connect('softkey-pressed', lambda w,data: self.softkey_pressed(data)) + self.GSTAT.connect('axis-selection-changed', lambda w,data: self.mpg_selection_changed(data)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -6408,6 +6411,31 @@ def _blockdelete(self, pin): LOG.debug("Received a signal from pin {0} with state = {1}".format(pin.name, pin.get())) self.command.set_optional_stop(pin.get()) + # from gmoccapy's MPG input pin + def _external_mpg(self, pin): + count = pin.get() + diff = count - self._last_count + if self.mpg_enabled: + if diff <0: + if self.GSTAT.is_auto_mode() and not self.GSTAT.is_auto_running(): + self.widgets.gcode_view.line_up() + else: + self.widgets.gremlin.zoom_out() + else: + if self.GSTAT.is_auto_mode() and not self.GSTAT.is_auto_running(): + self.widgets.gcode_view.line_down() + self.widgets.gcode_view.line_down() + else: + self.widgets.gremlin.zoom_in() + self._last_count = count + + # enabled externally by halui/hal_bridge + def mpg_selection_changed(self, data): + if data =='MPG0': + self.mpg_enabled = True + else: + self.mpg_enabled = False + # ========================================================= # external request for a softkey press from HALUI/halbridge def softkey_pressed(self, index): @@ -6642,6 +6670,8 @@ def _make_hal_pins(self): pin = self.halcomp.newpin("blockdelete", hal.HAL_BIT, hal.HAL_IN) hal_glib.GPin(pin).connect("value_changed", self._blockdelete) + pin = self.halcomp.newpin('mpg-in',hal.HAL_S32, hal.HAL_IN) + hal_glib.GPin(pin).connect("value_changed", self._external_mpg) ############################## # required class boiler code # From 5d49c20abd79fa1808736dba21e70851a4d97e95 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:19:59 -0700 Subject: [PATCH 059/110] gmoccapy -add a halui test config with a sim test panel gmoccapy -add a 4 axis halui test config to confirm angular jograte works --- configs/sim/gmoccapy/gmoccapy_halui_test.ini | 225 +++ .../gmoccapy/gmoccapy_halui_test_4_axis.ini | 218 +++ configs/sim/gmoccapy/panel-4axis.hal | 65 + configs/sim/gmoccapy/panel.hal | 59 + configs/sim/gmoccapy/panel.ui | 1407 +++++++++++++++++ 5 files changed, 1974 insertions(+) create mode 100644 configs/sim/gmoccapy/gmoccapy_halui_test.ini create mode 100644 configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini create mode 100644 configs/sim/gmoccapy/panel-4axis.hal 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/gmoccapy_halui_test_4_axis.ini b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini new file mode 100644 index 00000000000..f722d2e6654 --- /dev/null +++ b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini @@ -0,0 +1,218 @@ +# General section ------------------------------------------------------------- +[EMC] +VERSION = 1.1 +MACHINE = gmoccapy 4 axis +DEBUG = 0 + +# for details see nc_files/subroutines/maco_instructions.txt +[DISPLAY] +DISPLAY = gmoccapy + +# Cycle time, in milliseconds, that display will sleep between polls +CYCLE_TIME = 100 + +# Highest value that will be allowed for feed override, 1.0 = 100% +MAX_FEED_OVERRIDE = 1.5 +MAX_SPINDLE_OVERRIDE = 1.2 +MIN_SPINDLE_OVERRIDE = .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, 90.000 ° + +[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_sim4.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-4axis.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 C +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 50 +MAX_LINEAR_VELOCITY = 234 +POSITION_FILE = position.txt + +# First axis = X +[EMCIO] +# tool table file +TOOL_TABLE = tool.tbl +TOOL_CHANGE_POSITION = 100 100 -10 +TOOL_CHANGE_QUILL_UP = 1 + +[KINS] +KINEMATICS = trivkins coordinates=xyzc +JOINTS = 4 + +[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 = Y +[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 +HOME_IS_SHARED = 1 + +# Third axis = Z +[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 + +# Fourth axis = A +# Fifth axis = B + +# Sixt axis = C +[AXIS_C] +MAX_VELOCITY = 90.0 +MAX_ACCELERATION = 1200.0 + +[JOINT_3] +TYPE = ANGULAR +HOME = 0.0 +MAX_VELOCITY = 90.0 +MAX_ACCELERATION = 1200.0 +BACKLASH = 0.000 +INPUT_SCALE = 40 +OUTPUT_SCALE = 1.000 +FERROR = 5.0 +MIN_FERROR = 1.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 0.0 +HOME_LATCH_VEL = 0.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 + +# section for main IO controller parameters ----------------------------------- +[MACROS] +MACRO = i_am_lost +MACRO = halo_world +MACRO = jog_around +MACRO = increment xinc yinc +MACRO = go_to_position X-pos Y-pos Z-pos + +# Sections for display options ------------------------------------------------ diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal new file mode 100644 index 00000000000..8120d678518 --- /dev/null +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -0,0 +1,65 @@ +net rate halui.axis.jog-speed panel.jog-rate +net angular-rate halui.axis.jog-speed-angular panel.jog-rate-angular + +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 sc halui.axis.c.select panel.axis-c + +net sx axis.x.jog-enable +net sy axis.y.jog-enable +net sz axis.z.jog-enable +net sc axis.c.jog-enable +net sgui halui.gui.mpg-select.0 panel.select-gui0 + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +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-scale axis.c.jog-scale + +net mpg-count panel.mpg-wheel-s +net mpg-count gmoccapy.mpg-in +net mpg-count axis.x.jog-counts +net mpg-count axis.y.jog-counts +net mpg-count axis.z.jog-counts +net mpg-count axis.c.jog-counts + +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.gui.cycle.start panel.cycle-start +net start halui.gui.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 + + +net softkey0 halui.gui.softkey-00 panel.softkey-v0 +net softkey1 halui.gui.softkey-01 panel.softkey-v1 +net softkey2 halui.gui.softkey-02 panel.softkey-v2 +net softkey3 halui.gui.softkey-03 panel.softkey-v3 +net softkey4 halui.gui.softkey-04 panel.softkey-v4 +net softkey5 halui.gui.softkey-05 panel.softkey-v5 +net softkey6 halui.gui.softkey-06 panel.softkey-v6 +net softkeyh0 halui.gui.softkey-10 panel.softkey-h0 +net softkeyh1 halui.gui.softkey-11 panel.softkey-h1 +net softkeyh2 halui.gui.softkey-12 panel.softkey-h2 +net softkeyh3 halui.gui.softkey-13 panel.softkey-h3 +net softkeyh4 halui.gui.softkey-14 panel.softkey-h4 +net softkeyh5 halui.gui.softkey-15 panel.softkey-h5 +net softkeyh6 halui.gui.softkey-16 panel.softkey-h6 +net softkeyh7 halui.gui.softkey-17 panel.softkey-h7 +net softkeyh8 halui.gui.softkey-18 panel.softkey-h8 +net softkeyh9 halui.gui.softkey-19 panel.softkey-h9 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-display panel.reload diff --git a/configs/sim/gmoccapy/panel.hal b/configs/sim/gmoccapy/panel.hal new file mode 100644 index 00000000000..0c80e60010c --- /dev/null +++ b/configs/sim/gmoccapy/panel.hal @@ -0,0 +1,59 @@ +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 sx axis.x.jog-enable +net sy axis.y.jog-enable +net sz axis.z.jog-enable +net sgui halui.gui.mpg-select.0 panel.select-gui0 + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +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 gmoccapy.mpg-in +net mpg-count axis.x.jog-counts +net mpg-count axis.y.jog-counts +net mpg-count axis.z.jog-counts + +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.gui.cycle.start panel.cycle-start +net start halui.gui.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 + +net softkey0 halui.gui.softkey-00 panel.softkey-v0 +net softkey1 halui.gui.softkey-01 panel.softkey-v1 +net softkey2 halui.gui.softkey-02 panel.softkey-v2 +net softkey3 halui.gui.softkey-03 panel.softkey-v3 +net softkey4 halui.gui.softkey-04 panel.softkey-v4 +net softkey5 halui.gui.softkey-05 panel.softkey-v5 +net softkey6 halui.gui.softkey-06 panel.softkey-v6 +net softkeyh0 halui.gui.softkey-10 panel.softkey-h0 +net softkeyh1 halui.gui.softkey-11 panel.softkey-h1 +net softkeyh2 halui.gui.softkey-12 panel.softkey-h2 +net softkeyh3 halui.gui.softkey-13 panel.softkey-h3 +net softkeyh4 halui.gui.softkey-14 panel.softkey-h4 +net softkeyh5 halui.gui.softkey-15 panel.softkey-h5 +net softkeyh6 halui.gui.softkey-16 panel.softkey-h6 +net softkeyh7 halui.gui.softkey-17 panel.softkey-h7 +net softkeyh8 halui.gui.softkey-18 panel.softkey-h8 +net softkeyh9 halui.gui.softkey-19 panel.softkey-h9 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-display panel.reload diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui new file mode 100644 index 00000000000..cf614f0bb7f --- /dev/null +++ b/configs/sim/gmoccapy/panel.ui @@ -0,0 +1,1407 @@ + + + MainWindow + + + + 0 + 0 + 874 + 756 + + + + MainWindow + + + + + + + + + + + + ESTOP + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + ON + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + HOME + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + softKey +V0 + + + softkey-v0 + + + PushButton::BIT + + + + + + + SoftKey +V1 + + + softkey-v1 + + + PushButton::BIT + + + + + + + SoftKey +V2 + + + softkey-v2 + + + PushButton::BIT + + + + + + + SoftKey +V3 + + + softkey-v3 + + + PushButton::BIT + + + + + + + SoftKey +V4 + + + softkey-v4 + + + PushButton::BIT + + + + + + + SoftKey +V5 + + + softkey-v5 + + + PushButton::BIT + + + + + + + SoftKey +V6 + + + softkey-v6 + + + PushButton::BIT + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Reload + + + reload + + + PushButton::BIT + + + + + + + SYSTEM +Exit + + + exit + + + PushButton::BIT + + + + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + linear rate / angular rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + Qt::Horizontal + + + jog-rate-angular + + + false + + + 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 + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + false + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + false + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + false + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + Abort + + + cycle-abort + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + Axis Selection + + + + 0 + + + 3 + + + 0 + + + 0 + + + 4 + + + + + None + + + true + + + true + + + axis-none + + + + + + + A + + + true + + + true + + + axis-a + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + B + + + true + + + true + + + axis-b + + + + + + + C + + + true + + + true + + + axis-c + + + + + + + GUI + + + true + + + true + + + select-gui0 + + + + + + + + + + + + H0 + + + softkey-h0 + + + PushButton::BIT + + + + + + + H1 + + + softkey-h1 + + + PushButton::BIT + + + + + + + H2 + + + softkey-h2 + + + PushButton::BIT + + + + + + + H3 + + + softkey-h3 + + + PushButton::BIT + + + + + + + H4 + + + softkey-h4 + + + PushButton::BIT + + + + + + + H5 + + + softkey-h5 + + + PushButton::BIT + + + + + + + H6 + + + softkey-h6 + + + PushButton::BIT + + + + + + + H7 + + + softkey-h7 + + + PushButton::BIT + + + + + + + H8 + + + softkey-h8 + + + PushButton::BIT + + + + + + + H9 + + + softkey-h9 + + + PushButton::BIT + + + + + + + + + + + IndicatedPushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Dial + QDial +
qtvcp.widgets.simple_widgets
+
+ + ActionButton + IndicatedPushButton +
qtvcp.widgets.action_button
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+
+ + + + + +
From cd010c2b0f23380a7bb572240e20c16bdf849715 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Sep 2025 22:27:26 -0700 Subject: [PATCH 060/110] axis sim -add a sim test for halui messages axis -add softkey, exit and reload actions axis -allow MPG scrolling of gcode axis -add mdi/macro calls from halui --- configs/sim/axis/axis_halui_test.ini | 237 +++ configs/sim/axis/gstatmessages.py | 163 ++ configs/sim/axis/macros/go_to_position.ngc | 24 + configs/sim/axis/macros/hello_world.ngc | 24 + configs/sim/axis/macros/increment.ngc | 22 + configs/sim/axis/macros/lost.ngc | 24 + .../sim/axis/macros/macro_Instructions.txt | 39 + configs/sim/axis/macros/move_around.ngc | 28 + configs/sim/axis/panel.hal | 53 + configs/sim/axis/panel.ui | 1328 +++++++++++++++++ configs/sim/gmoccapy/gmoccapy_halui_test.ini | 2 +- 11 files changed, 1943 insertions(+), 1 deletion(-) create mode 100644 configs/sim/axis/axis_halui_test.ini create mode 100644 configs/sim/axis/gstatmessages.py create mode 100644 configs/sim/axis/macros/go_to_position.ngc create mode 100644 configs/sim/axis/macros/hello_world.ngc create mode 100644 configs/sim/axis/macros/increment.ngc create mode 100644 configs/sim/axis/macros/lost.ngc create mode 100644 configs/sim/axis/macros/macro_Instructions.txt create mode 100644 configs/sim/axis/macros/move_around.ngc 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..2e53fb8275a --- /dev/null +++ b/configs/sim/axis/axis_halui_test.ini @@ -0,0 +1,237 @@ +# 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 + +[MDI_COMMAND_LIST] +MDI_COMMAND_MACRO0 = G53 G0 Z0;G0 X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + +[MACROS] +MACRO_COMMAND_MACRO6 = go_to_position x-pos y-pos z-pos, Go To \nPosition +MACRO_COMMAND_MACRO2 = lost, LOST +MACRO_COMMAND_MACRO3 = increment x-incr y-incr, INCR +MACRO_COMMAND_MACRO4 = macro_4 +MACRO_COMMAND_MACRO5 = macro_5 + +[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 +SUBROUTINE_PATH = ./macros + +# 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..702c1b6e0ba --- /dev/null +++ b/configs/sim/axis/gstatmessages.py @@ -0,0 +1,163 @@ + +from hal_glib import GStat +from common.iniinfo import _IStat as IStatParent + +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 + +INFO = Info() + +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)) +GSTAT.connect('softkey-pressed', lambda w,data: softkey_pressed(data)) +GSTAT.connect('shutdown-request', lambda w : General_Halt()) +GSTAT.connect('reload-display', lambda w : commands.clear_live_plot()) + +global last_mpg +last_mpg = 0 +mpg_enabled = 0 + + +def user_hal_pins(): + comp.newpin('mpg-enable', hal.HAL_BIT, hal.HAL_IN) + comp.newpin('mpg-in', hal.HAL_S32, hal.HAL_IN) + comp.ready() + +def user_live_update(): + GSTAT.run_iteration() + global mpg_enabled + try: + if comp['mpg-enable'] or mpg_enabled: + global last_mpg + if comp['mpg-in'] == last_mpg: return + if comp['mpg-in'] > last_mpg:scroll_up(None) + if comp['mpg-in'] < last_mpg:scroll_down(None) + last_mpg = comp['mpg-in'] + except Exception as e: + print(e) + +def select_axis(data): + global mpg_enabled + if data is None: return + if data =='MPG0': + mpg_enabled = True + return + mpg_enabled = False + if data.upper() =='NONE': + return + try: + widget = getattr(widgets, "axis_%s" % data.lower()) + widget.focus() + widget.invoke() + except: + pass + +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) + cmd = INFO.get_ini_mdi_command(name) + #print(f'MDI command:{cmd} name:{name}') + if not INFO.get_ini_mdi_command(name) is None: + run_mdi(data=cmd) + else: + #print(INFO.get_ini_macro_command(name)) + try: + temp = INFO.MACRO_COMMAND_DICT.get(name).get('cmd') + #print(temp) + run_macro(data=temp) + except Exception as e: + print(e) + +def run_mdi(data): + #print(f'run mdi command:{data}') + mdi_list = data.split(';') + for code in (mdi_list): + commands.send_mdi_command(code) + +def run_macro(data): + #print(f'run macro:{data}') + o_codes = data.split() + command = str( "O<" + o_codes[0] + "> call" ) + # check for oword and confirm path exists + #if not self.check_macro_path(command): + # return + for code in o_codes[1:]: + if vars.metric.get(): unit_str = " " + _("mm") + else: unit_str = " " + _("in") + param = prompt_float("Macro", f"Enter a value for: {code}:", + "", unit_str) + if param <= 0: return + if vars.metric.get(): param /= 25.4 + command = command + " [" + str(param) + "] " + commands.send_mdi_command(command) + +def softkey_pressed(index): + + if index == 0: + root_window.tk.call('.pane.top.tabs','raise','manual') + elif index == 1: + root_window.tk.call('.pane.top.tabs','raise','mdi') + elif index == 2: + root_window.tk.call('.pane.top.right','raise','preview') + elif index == 3: + root_window.tk.call('.pane.top.right','raise','numbers') + elif index == 4: + root_window.tk.call('.pane.top.right','raise','user_0') + else: + print(f'Softkey index:{index}') diff --git a/configs/sim/axis/macros/go_to_position.ngc b/configs/sim/axis/macros/go_to_position.ngc new file mode 100644 index 00000000000..19b79e2a1a3 --- /dev/null +++ b/configs/sim/axis/macros/go_to_position.ngc @@ -0,0 +1,24 @@ +; Testfile go to position +; will jog the machine to a given position + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +;#1 = +;#2 = +;#3 = + +(DEBUG, Will now move machine to %fX = #1 , Y = #2 , Z = #3) +G0 X #1 Y #2 Z #3 + +O endsub + +M2 diff --git a/configs/sim/axis/macros/hello_world.ngc b/configs/sim/axis/macros/hello_world.ngc new file mode 100644 index 00000000000..c24424e0695 --- /dev/null +++ b/configs/sim/axis/macros/hello_world.ngc @@ -0,0 +1,24 @@ +; Testfile "hello world" +; will just give messages + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +G0 X10 + +(MSG, Hello World) + +G0X-10 + +O endsub + +M2 + diff --git a/configs/sim/axis/macros/increment.ngc b/configs/sim/axis/macros/increment.ngc new file mode 100644 index 00000000000..af0da2376f3 --- /dev/null +++ b/configs/sim/axis/macros/increment.ngc @@ -0,0 +1,22 @@ +; Testfile "increment" +; will move the machine in relative coordinates + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +G91 G0 X#1 Y#2 +G90 + +(DEBUG, %fX was [#1] and Y was [#2]) + +O endsub + +M2 diff --git a/configs/sim/axis/macros/lost.ngc b/configs/sim/axis/macros/lost.ngc new file mode 100644 index 00000000000..51897355866 --- /dev/null +++ b/configs/sim/axis/macros/lost.ngc @@ -0,0 +1,24 @@ +; Testfile Lost +; will jog to machine zero and set all axis to zero + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +(MSG, Will now move to machine zero) +G53 G0 X0 Y0 Z0 +(MSG, will now set all axis to zero) +G10 L20 P0 X0 Y0 Z0 +(MSG, all done) + + +O endsub + +M2 diff --git a/configs/sim/axis/macros/macro_Instructions.txt b/configs/sim/axis/macros/macro_Instructions.txt new file mode 100644 index 00000000000..6bb07a5d7a6 --- /dev/null +++ b/configs/sim/axis/macros/macro_Instructions.txt @@ -0,0 +1,39 @@ +This is a small instruction to include macros in Qtdragon. + +In your INI File you need to introduce a section called [MACROS] +and for every macro you'll need to include a one-liner like so: + +MACRO = jog_around ,MOVE\nTHERE +or +MACRO = increment xinc yinc ,INCR + +where xinc and yinc are placeholders +Anything after the comma is the button text. + +During execution of the macro, you will be asked to enter the values. + +You are allowed to introduce 9 macros! +If you enter more macros, only the first 9 will appear with button in gmoccapy. + +In the [RS274NGC] section you may want to give a path to your macros like so: + +[RS274NGC] +SUBROUTINE_PATH = nc_files/subroutines + +or you place your macros in the nc_files folder. + +Each macro must have it's own file in one of the mentioned folders and they are normal subroutines, so the must begin with: + +O sub + +and end with + +O endsub +M2 + +The name of the file must be jog_around.ngc. +And an macro must contain at least one movement of one axis. + +macro name in INI file have to be the same as the file name and the sub must have also the same name (case sensitive!)! + + diff --git a/configs/sim/axis/macros/move_around.ngc b/configs/sim/axis/macros/move_around.ngc new file mode 100644 index 00000000000..0c8e3b69322 --- /dev/null +++ b/configs/sim/axis/macros/move_around.ngc @@ -0,0 +1,28 @@ +; Testfile "move around" +; will just move a little bit around + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +G91 G0 X 25 +Y-25 +Z-25 +Y25 +X-25 +Z25 +F250 +G2 I 25 + +(MSG, It is done!) + +O endsub + +M2 diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal new file mode 100644 index 00000000000..04ffc3a6167 --- /dev/null +++ b/configs/sim/axis/panel.hal @@ -0,0 +1,53 @@ +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 sgui halui.gui.mpg-select.0 panel.select-gui0 + +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 axisui.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 + +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-MACRO3 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.gui.cycle.start panel.cycle-start +net start halui.gui.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 + +net softkey0 halui.gui.softkey-00 panel.softkey-0 +net softkey1 halui.gui.softkey-01 panel.softkey-1 +net softkey2 halui.gui.softkey-02 panel.softkey-2 +net softkey3 halui.gui.softkey-03 panel.softkey-3 +net softkey4 halui.gui.softkey-04 panel.softkey-4 +net softkey5 halui.gui.softkey-05 panel.softkey-5 +net softkey6 halui.gui.softkey-06 panel.softkey-6 +net softkey7 halui.gui.softkey-07 panel.softkey-7 +net softkey8 halui.gui.softkey-08 panel.softkey-8 +net softkey9 halui.gui.softkey-09 panel.softkey-9 +net softkey10 halui.gui.softkey-10 panel.softkey-10 +net softkey11 halui.gui.softkey-11 panel.softkey-11 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-display panel.reload diff --git a/configs/sim/axis/panel.ui b/configs/sim/axis/panel.ui new file mode 100644 index 00000000000..164338d0144 --- /dev/null +++ b/configs/sim/axis/panel.ui @@ -0,0 +1,1328 @@ + + + MainWindow + + + + 0 + 0 + 588 + 706 + + + + MainWindow + + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + linear rate / angular rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + Qt::Horizontal + + + jog-rate-angular + + + false + + + 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 + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + false + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + false + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + false + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + Abort + + + cycle-abort + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + Axis Selection + + + + 0 + + + 3 + + + 0 + + + 0 + + + 4 + + + + + None + + + true + + + true + + + axis-none + + + + + + + A + + + true + + + true + + + axis-a + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + B + + + true + + + true + + + axis-b + + + + + + + C + + + true + + + true + + + axis-c + + + + + + + GUI + + + true + + + true + + + select-gui0 + + + + + + + + + + + + ESTOP + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + ON + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + HOME + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Manual + + + softkey-0 + + + PushButton::BIT + + + + + + + MDI + + + softkey-1 + + + PushButton::BIT + + + + + + + Preview + + + softkey-2 + + + PushButton::BIT + + + + + + + DRO + + + softkey-3 + + + PushButton::BIT + + + + + + + User +Tab + + + softkey-4 + + + PushButton::BIT + + + + + + + + + + softkey-5 + + + PushButton::BIT + + + + + + + + + + softkey-6 + + + PushButton::BIT + + + + + + + + + + softkey-7 + + + PushButton::BIT + + + + + + + + + + softkey-8 + + + PushButton::BIT + + + + + + + + + + softkey-9 + + + PushButton::BIT + + + + + + + + + + softkey-10 + + + PushButton::BIT + + + + + + + + + + softkey-11 + + + PushButton::BIT + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Reload + + + reload + + + PushButton::BIT + + + + + + + exit + + + exit + + + PushButton::BIT + + + + + + + + + + + IndicatedPushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Dial + QDial +
qtvcp.widgets.simple_widgets
+
+ + ActionButton + IndicatedPushButton +
qtvcp.widgets.action_button
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+
+ + + + + +
diff --git a/configs/sim/gmoccapy/gmoccapy_halui_test.ini b/configs/sim/gmoccapy/gmoccapy_halui_test.ini index 27579c0b389..8f628a1c1f0 100644 --- a/configs/sim/gmoccapy/gmoccapy_halui_test.ini +++ b/configs/sim/gmoccapy/gmoccapy_halui_test.ini @@ -101,7 +101,7 @@ HALUI = halui [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_MACRO0 = G53 G0 Z0;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) From 3e6a57c4b22894a17e87e47bedbaed80aca0b904 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 2 Sep 2026 21:08:12 -0700 Subject: [PATCH 061/110] axis/panel.hal --- configs/sim/axis/panel.hal | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal index 04ffc3a6167..28cbb492905 100644 --- a/configs/sim/axis/panel.hal +++ b/configs/sim/axis/panel.hal @@ -21,33 +21,33 @@ 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 -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-MACRO3 panel.mdi-2 +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.MACRO3 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.gui.cycle.start panel.cycle-start -net start halui.gui.cycle.pause panel.cycle-pause +net pause halui.gui.cycle-start panel.cycle-start +net start halui.gui.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 -net softkey0 halui.gui.softkey-00 panel.softkey-0 -net softkey1 halui.gui.softkey-01 panel.softkey-1 -net softkey2 halui.gui.softkey-02 panel.softkey-2 -net softkey3 halui.gui.softkey-03 panel.softkey-3 -net softkey4 halui.gui.softkey-04 panel.softkey-4 -net softkey5 halui.gui.softkey-05 panel.softkey-5 -net softkey6 halui.gui.softkey-06 panel.softkey-6 -net softkey7 halui.gui.softkey-07 panel.softkey-7 -net softkey8 halui.gui.softkey-08 panel.softkey-8 -net softkey9 halui.gui.softkey-09 panel.softkey-9 -net softkey10 halui.gui.softkey-10 panel.softkey-10 -net softkey11 halui.gui.softkey-11 panel.softkey-11 +net softkey0 halui.gui.softkey.00 panel.softkey-0 +net softkey1 halui.gui.softkey.01 panel.softkey-1 +net softkey2 halui.gui.softkey.02 panel.softkey-2 +net softkey3 halui.gui.softkey.03 panel.softkey-3 +net softkey4 halui.gui.softkey.04 panel.softkey-4 +net softkey5 halui.gui.softkey.05 panel.softkey-5 +net softkey6 halui.gui.softkey.06 panel.softkey-6 +net softkey7 halui.gui.softkey.07 panel.softkey-7 +net softkey8 halui.gui.softkey.08 panel.softkey-8 +net softkey9 halui.gui.softkey.09 panel.softkey-9 +net softkey10 halui.gui.softkey.10 panel.softkey-10 +net softkey11 halui.gui.softkey.11 panel.softkey-11 net exit halui.gui.shutdown panel.exit -net reload halui.gui.reload-display panel.reload +net reload halui.gui.reload-preview panel.reload From 6954a63f17908197b16863777477f7c334839ac4 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sun, 19 Jul 2026 21:27:51 -0700 Subject: [PATCH 062/110] gmoccapy -add docs for halui messages --- docs/src/gui/gmoccapy.adoc | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/src/gui/gmoccapy.adoc b/docs/src/gui/gmoccapy.adoc index 67cd9331bd3..c49e30bfb96 100644 --- a/docs/src/gui/gmoccapy.adoc +++ b/docs/src/gui/gmoccapy.adoc @@ -935,6 +935,64 @@ net tooloffset-z gmoccapy.tooloffset-z <= motion.tooloffset.z Please note, that GMOCCAPY takes care of its own to update the offsets, sending an G43 after any tool change, *but not in auto mode!* + So writing a program makes you responsible to include an G43 after each tool change! +== HALUI +HALUI will create halui.gui pins that interface with Gmoccapy. + +- There is a cycle start and pause pin - these call the code in Gmoccapy rather then the motion controller. ie if in MDI mode the MDI command will be run with a cycle start.+ +- If there are macros defined in the INI there will be (up to 64) pins available to initiate them. + +- clear/reload the display +- shutdown the linux system +- ok/cancel of dialogs and notify (error) messages +- softkeys 0 - 11 control the main tab keys: + +halui.gui.softkey-00 - halui.gui.softkey-06 are the vertical keys. + +halui.gui.softkey-10 - halui.gui.softkey-09 are the horizontal keys. + +also: + +- enabling halui.gui.mpg-select.0, will allow zooming of the gcode plot with the gmoccapy.mpg-in pin. +- HALUI selected Axis will be reported to Gmoccapy. + +- HALUI selected jog rate will be reported to Gmoccapy. + +In either case the last changed rate/axis (either HALUI or Gmoccapy) will be used for screen button jogging or HALUI pin jogging. + +.Typical HAL pins avaialble: +---- +Component Pins: +Owner Type Dir Value Name + 25 bit IN FALSE halui.gui.cancel + 25 bit IN FALSE halui.gui.cycle.pause + 25 bit IN FALSE halui.gui.cycle.start + 25 bit IN FALSE halui.gui.mdi-command-MACRO0 + 25 bit IN FALSE halui.gui.mdi-command-MACRO1 + 25 bit IN FALSE halui.gui.mdi-command-MACRO2 + 25 bit IN FALSE halui.gui.mdi-command-MACRO3 + 25 bit IN FALSE halui.gui.mdi-command-MACRO4 + 25 bit IN FALSE halui.gui.mdi-command-MACRO5 + 25 bit IN FALSE halui.gui.mpg-select.0 + 25 bit IN FALSE halui.gui.ok + 25 bit IN FALSE halui.gui.reload-display + 25 bit IN FALSE halui.gui.shutdown + 25 bit IN FALSE halui.gui.softkey-00 + 25 bit IN FALSE halui.gui.softkey-01 + 25 bit IN FALSE halui.gui.softkey-02 + 25 bit IN FALSE halui.gui.softkey-03 + 25 bit IN FALSE halui.gui.softkey-04 + 25 bit IN FALSE halui.gui.softkey-05 + 25 bit IN FALSE halui.gui.softkey-06 + 25 bit IN FALSE halui.gui.softkey-07 + 25 bit IN FALSE halui.gui.softkey-08 + 25 bit IN FALSE halui.gui.softkey-09 + 25 bit IN FALSE halui.gui.softkey-10 + 25 bit IN FALSE halui.gui.softkey-11 + 25 bit IN FALSE halui.gui.softkey-12 + 25 bit IN FALSE halui.gui.softkey-13 + 25 bit IN FALSE halui.gui.softkey-14 + 25 bit IN FALSE halui.gui.softkey-15 + 25 bit IN FALSE halui.gui.softkey-16 + 25 bit IN FALSE halui.gui.softkey-17 + 25 bit IN FALSE halui.gui.softkey-18 + 25 bit IN FALSE halui.gui.softkey-19 + +---- [[gmoccapy:auto-tool-measurement]] == Auto Tool Measurement From cb1fc79f55214157cbd0ac9087832f05c3a7bea7 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sun, 19 Jul 2026 21:28:23 -0700 Subject: [PATCH 063/110] qtdragon -add docs for halui messages --- docs/src/gui/qtdragon.adoc | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/src/gui/qtdragon.adoc b/docs/src/gui/qtdragon.adoc index 692eac20a5d..1281916c206 100644 --- a/docs/src/gui/qtdragon.adoc +++ b/docs/src/gui/qtdragon.adoc @@ -462,6 +462,66 @@ Any HAL command can be used. POSTGUI_HALCMD = loadusr qtvcp test_probe POSTGUI_HALCMD = loadusr qtvcp test_led POSTGUI_HALCMD = loadusr halmeter +---- +=== HALUI +HALUI will create halui.gui pins that interface with QtDragon. + +- There is a cycle start and pause pin - these call the code in QtDragon rather then the motion controller. + + This allows custom behaviour, such as spindle lift to work with external buttons. +- If there are macros defined in the INI (see: <>), there will be (up to 64) pins available to initiate them. + + These macros can require the mode to be preset to MDI or to automatically switch from MANUAL to MDI mode by +setting preferences on the settings page. +- clear/reload the display +- shutdown the screen +- ok/cancel of dialogs and notify (error) messages +- softkeys 0 - 11 control the main tab keys: + +'MAIN','FILE','OFFSETS','TOOL','STATUS','PROBE','GCODES','SETUP','SETTINGS','UTILITIES','USER','CAMERA' + +also: + +- enabling halui.gui.mpg-select.0, will allow zooming/scrolling of the gcode plot or text display with an MPG. +- HALUI selected axis will be reported to QtDragon. + +- HALUI selected jog rates/increments will be reported to QtDragon. + +In either case the last changed rate/axis (either HALUI or QtDragon) will be used for screen button jogging or HALUI pin jogging. + +.Typical HAL pins avaialble: +---- +Component Pins: +Owner Type Dir Value Name + 25 bit IN FALSE halui.gui.cancel + 25 bit IN FALSE halui.gui.cycle.pause + 25 bit IN FALSE halui.gui.cycle.start + 25 bit IN FALSE halui.gui.mdi-command-MACRO0 + 25 bit IN FALSE halui.gui.mdi-command-MACRO1 + 25 bit IN FALSE halui.gui.mdi-command-MACRO2 + 25 bit IN FALSE halui.gui.mdi-command-MACRO3 + 25 bit IN FALSE halui.gui.mdi-command-MACRO4 + 25 bit IN FALSE halui.gui.mdi-command-MACRO5 + 25 bit IN FALSE halui.gui.mpg-select.0 + 25 bit IN FALSE halui.gui.ok + 25 bit IN FALSE halui.gui.reload-display + 25 bit IN FALSE halui.gui.shutdown + 25 bit IN FALSE halui.gui.softkey-00 + 25 bit IN FALSE halui.gui.softkey-01 + 25 bit IN FALSE halui.gui.softkey-02 + 25 bit IN FALSE halui.gui.softkey-03 + 25 bit IN FALSE halui.gui.softkey-04 + 25 bit IN FALSE halui.gui.softkey-05 + 25 bit IN FALSE halui.gui.softkey-06 + 25 bit IN FALSE halui.gui.softkey-07 + 25 bit IN FALSE halui.gui.softkey-08 + 25 bit IN FALSE halui.gui.softkey-09 + 25 bit IN FALSE halui.gui.softkey-10 + 25 bit IN FALSE halui.gui.softkey-11 + 25 bit IN FALSE halui.gui.softkey-12 + 25 bit IN FALSE halui.gui.softkey-13 + 25 bit IN FALSE halui.gui.softkey-14 + 25 bit IN FALSE halui.gui.softkey-15 + 25 bit IN FALSE halui.gui.softkey-16 + 25 bit IN FALSE halui.gui.softkey-17 + 25 bit IN FALSE halui.gui.softkey-18 + 25 bit IN FALSE halui.gui.softkey-19 + ---- [[sec:bridge]] === HAL Bridge From 6dbe2d3b979bec4d771b96cd466b5ef3b778d33f Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sun, 19 Jul 2026 21:28:54 -0700 Subject: [PATCH 064/110] halui - add docs for halui zmq messages and pins --- docs/src/gui/halui.adoc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/src/gui/halui.adoc b/docs/src/gui/halui.adoc index 37d92b8ff73..dc2895a1b83 100644 --- a/docs/src/gui/halui.adoc +++ b/docs/src/gui/halui.adoc @@ -117,6 +117,23 @@ Or see http://linuxcnc.org/docs/devel/html/man/man1/halui.1.html * 'halui.feed-override.scale' (float, in) - pin for setting the scale for increase and decrease of 'feed-override'. * 'halui.feed-override.value' (float, out) - current FO value +=== Gui +These are pins used to 'request' a function to run in a screen. + +What these exactly do is dependant on the (GUI) screen used. + +* 'halui.gui.cycle.start' (bit, in) - pin for requesting the screen to run a cycle +* 'halui.gui.cycle.pause' (bit, in) - pin for requesting the screen to pause a cycle +* 'halui.gui.cancel' (bit, in) - pin for cancel/closing dialogs +* 'halui.gui.ok' (bit, in) - pin for ok/apply dialogs +* 'halui.gui.mdi-command-MACRO__' (bit, in) - pin for calling mdi/macro commands +__ is a one or two digit number starting from 0 to 63 + +There can be up to 64 commands defined in the INI under the [MDI_COMMAND_LIST] or [MACROS] headings. + +* 'halui.gui.reload-display' (bit, in) - pin for reloading the screen plot +* 'halui.gui.shutdown' (bit, in) - pin for shutting down linuxcnc or system +* 'halui.gui.softkey__' (bit, in) - pins for screen defined functions +__ is a two digit number starting from 00 to 19 + +* 'halui.gui.mpg-select.0' (bit, in) - pin for selecting MPG based control of the screen, such as scrolling + === Mist * 'halui.mist.is-on' (bit, out) - indicates mist is on From 13baebbc486ceb31c3a7131933c735ac2a913683 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 10 Aug 2026 20:04:40 -0700 Subject: [PATCH 065/110] bridge -quiet print statements --- lib/python/bridgeui/bridge.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index e04ccb7837d..beef042a758 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -16,7 +16,7 @@ log_level=logger.WARNING, logToFile=False) # Force the log level for this module -LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL +#LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL try: import zmq @@ -112,7 +112,7 @@ 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) + #print ('pre axis state', self.axesSelected,self.currentSelectedAxis) flag = 1 if data[0] == 'MPG0': self.currentSelectedAxis = data[0] @@ -131,7 +131,7 @@ def action(self, msg, data): if flag: self.currentSelectedAxis = 'None' - print ('axis state', self.axesSelected,self.currentSelectedAxis) + #print ('axis state', self.axesSelected,self.currentSelectedAxis) # send msg to hal_glib def writeMsg(self, msg, data=''): @@ -169,7 +169,7 @@ def shutdownController(self): self.writeMsg('request_shutdown') def softkey(self, index): - print(f'Softkey index {index}') + #print(f'Softkey index {index}') self.writeMsg('request_softkey', index) # if the number is bigger then MDI command list From f2be79e45dfca31a62bf3492333bac785d7c3d2c Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 10 Aug 2026 20:06:43 -0700 Subject: [PATCH 066/110] iniinfo -fix search for nth INI MDI command ie search by integer --- lib/python/common/iniinfo.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/python/common/iniinfo.py b/lib/python/common/iniinfo.py index c81118f6e37..653a4c1eaf9 100644 --- a/lib/python/common/iniinfo.py +++ b/lib/python/common/iniinfo.py @@ -18,7 +18,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') @@ -641,7 +641,7 @@ def update(self): LOG.error('INI MDI command parse error:{}'.format(e)) except Exception as e: LOG.error('INI MDI command parse error:{}'.format(e)) - print(self.MDI_COMMAND_DICT) + #print(self.MDI_COMMAND_DICT) ################ # MACRO commands # @@ -940,16 +940,25 @@ def get_ini_mdi_command(self, key): using an integer is the legacy way to refer to the nth line. using a string will refer to the specific command regardless what line it is on.""" + # try the string: try: - # should fail if not string return self.MDI_COMMAND_DICT[key]['cmd'] - except: - # fallback to legacy variable - try: - # should fail if not int - return self.MDI_COMMAND_LIST[key] - except: - return None + except Exception as e: + LOG.verbose(e) + # try an integer by finding the nth string: + try: + keystr=list(self.MDI_COMMAND_DICT.keys())[int(key)] + return self.MDI_COMMAND_DICT[keystr]['cmd'] + except Exception as e: + LOG.verbose(e) + + # fallback to legacy variable + try: + # should fail if not int + return self.MDI_COMMAND_LIST[int(key)] + except Exception as e: + LOG.verbose(e) + return None def get_ini_mdi_label(self, key): """ returns A MDI label string from the INI heading [MDI_COMMAND_LIST] or None From bbdf8807ae1eaebaa43d03ed0cf5dcb0e65bcb4b Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 10 Aug 2026 20:08:11 -0700 Subject: [PATCH 067/110] gmoccapy -remove test code from right_panel.ini --- configs/sim/gmoccapy/gmoccapy_right_panel.ini | 6 ------ 1 file changed, 6 deletions(-) diff --git a/configs/sim/gmoccapy/gmoccapy_right_panel.ini b/configs/sim/gmoccapy/gmoccapy_right_panel.ini index 7b4c5981da8..8a3b51ed480 100644 --- a/configs/sim/gmoccapy/gmoccapy_right_panel.ini +++ b/configs/sim/gmoccapy/gmoccapy_right_panel.ini @@ -31,12 +31,6 @@ 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 From 3da79488f8984103e3ce0dd28d4e413f868cf86f Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 2 Sep 2026 21:08:54 -0700 Subject: [PATCH 068/110] gmoccapy/panel-4axis.hal --- configs/sim/gmoccapy/panel-4axis.hal | 46 ++++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal index 8120d678518..42685ace762 100644 --- a/configs/sim/gmoccapy/panel-4axis.hal +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -27,39 +27,39 @@ net mpg-count axis.y.jog-counts net mpg-count axis.z.jog-counts net mpg-count axis.c.jog-counts -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 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.gui.cycle.start panel.cycle-start -net start halui.gui.cycle.pause panel.cycle-pause +net pause halui.gui.cycle-start panel.cycle-start +net start halui.gui.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 -net softkey0 halui.gui.softkey-00 panel.softkey-v0 -net softkey1 halui.gui.softkey-01 panel.softkey-v1 -net softkey2 halui.gui.softkey-02 panel.softkey-v2 -net softkey3 halui.gui.softkey-03 panel.softkey-v3 -net softkey4 halui.gui.softkey-04 panel.softkey-v4 -net softkey5 halui.gui.softkey-05 panel.softkey-v5 -net softkey6 halui.gui.softkey-06 panel.softkey-v6 -net softkeyh0 halui.gui.softkey-10 panel.softkey-h0 -net softkeyh1 halui.gui.softkey-11 panel.softkey-h1 -net softkeyh2 halui.gui.softkey-12 panel.softkey-h2 -net softkeyh3 halui.gui.softkey-13 panel.softkey-h3 -net softkeyh4 halui.gui.softkey-14 panel.softkey-h4 -net softkeyh5 halui.gui.softkey-15 panel.softkey-h5 -net softkeyh6 halui.gui.softkey-16 panel.softkey-h6 -net softkeyh7 halui.gui.softkey-17 panel.softkey-h7 -net softkeyh8 halui.gui.softkey-18 panel.softkey-h8 -net softkeyh9 halui.gui.softkey-19 panel.softkey-h9 +net softkey0 halui.gui.softkey.00 panel.softkey-v0 +net softkey1 halui.gui.softkey.01 panel.softkey-v1 +net softkey2 halui.gui.softkey.02 panel.softkey-v2 +net softkey3 halui.gui.softkey.03 panel.softkey-v3 +net softkey4 halui.gui.softkey.04 panel.softkey-v4 +net softkey5 halui.gui.softkey.05 panel.softkey-v5 +net softkey6 halui.gui.softkey.06 panel.softkey-v6 +net softkeyh0 halui.gui.softkey.10 panel.softkey-h0 +net softkeyh1 halui.gui.softkey.11 panel.softkey-h1 +net softkeyh2 halui.gui.softkey.12 panel.softkey-h2 +net softkeyh3 halui.gui.softkey.13 panel.softkey-h3 +net softkeyh4 halui.gui.softkey.14 panel.softkey-h4 +net softkeyh5 halui.gui.softkey.15 panel.softkey-h5 +net softkeyh6 halui.gui.softkey.16 panel.softkey-h6 +net softkeyh7 halui.gui.softkey.17 panel.softkey-h7 +net softkeyh8 halui.gui.softkey.18 panel.softkey-h8 +net softkeyh9 halui.gui.softkey.19 panel.softkey-h9 net exit halui.gui.shutdown panel.exit -net reload halui.gui.reload-display panel.reload +net reload halui.gui.reload-preview panel.reload From 0abc6369f4ef9eec7270dfe460f864ea4295bcb2 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 10 Aug 2026 20:29:06 -0700 Subject: [PATCH 069/110] gmoccapy -remove one of the halui sims --- configs/sim/gmoccapy/gmoccapy_halui_test.ini | 225 ------------------- configs/sim/gmoccapy/panel.hal | 59 ----- 2 files changed, 284 deletions(-) delete mode 100644 configs/sim/gmoccapy/gmoccapy_halui_test.ini delete mode 100644 configs/sim/gmoccapy/panel.hal diff --git a/configs/sim/gmoccapy/gmoccapy_halui_test.ini b/configs/sim/gmoccapy/gmoccapy_halui_test.ini deleted file mode 100644 index 8f628a1c1f0..00000000000 --- a/configs/sim/gmoccapy/gmoccapy_halui_test.ini +++ /dev/null @@ -1,225 +0,0 @@ -# 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 = G53 G0 Z0;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 deleted file mode 100644 index 0c80e60010c..00000000000 --- a/configs/sim/gmoccapy/panel.hal +++ /dev/null @@ -1,59 +0,0 @@ -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 sx axis.x.jog-enable -net sy axis.y.jog-enable -net sz axis.z.jog-enable -net sgui halui.gui.mpg-select.0 panel.select-gui0 - -net jog-p halui.axis.selected.plus panel.jog-pos -net jog-m halui.axis.selected.minus panel.jog-neg - -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 gmoccapy.mpg-in -net mpg-count axis.x.jog-counts -net mpg-count axis.y.jog-counts -net mpg-count axis.z.jog-counts - -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.gui.cycle.start panel.cycle-start -net start halui.gui.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 - -net softkey0 halui.gui.softkey-00 panel.softkey-v0 -net softkey1 halui.gui.softkey-01 panel.softkey-v1 -net softkey2 halui.gui.softkey-02 panel.softkey-v2 -net softkey3 halui.gui.softkey-03 panel.softkey-v3 -net softkey4 halui.gui.softkey-04 panel.softkey-v4 -net softkey5 halui.gui.softkey-05 panel.softkey-v5 -net softkey6 halui.gui.softkey-06 panel.softkey-v6 -net softkeyh0 halui.gui.softkey-10 panel.softkey-h0 -net softkeyh1 halui.gui.softkey-11 panel.softkey-h1 -net softkeyh2 halui.gui.softkey-12 panel.softkey-h2 -net softkeyh3 halui.gui.softkey-13 panel.softkey-h3 -net softkeyh4 halui.gui.softkey-14 panel.softkey-h4 -net softkeyh5 halui.gui.softkey-15 panel.softkey-h5 -net softkeyh6 halui.gui.softkey-16 panel.softkey-h6 -net softkeyh7 halui.gui.softkey-17 panel.softkey-h7 -net softkeyh8 halui.gui.softkey-18 panel.softkey-h8 -net softkeyh9 halui.gui.softkey-19 panel.softkey-h9 - -net exit halui.gui.shutdown panel.exit -net reload halui.gui.reload-display panel.reload From 2b094b5db5d797c3495046e9cc412f52ff0386ad Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 11 Aug 2026 05:33:22 -0700 Subject: [PATCH 070/110] gmoccapy -fix error from y/n dialog wrong function signature --- src/emc/usr_intf/gmoccapy/dialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index f09744d41bb..17c9105b435 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -270,7 +270,7 @@ def yesno_dialog(self): dialog.connect("response", self.on_yn_response) return dialog - def show_yesno_dialog(self, message, title = _("Operator Message")): + def show_yesno_dialog(self, _caller,message, title = _("Operator Message")): dialog = self.yn_dialog dialog.set_markup(message) if title: From 93aa8af2d78b66487bc97578ad008003629122d0 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 11 Aug 2026 05:35:30 -0700 Subject: [PATCH 071/110] gmoccapy -fix a 'dialog not found' error message --- src/emc/usr_intf/gmoccapy/dialogs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index 17c9105b435..f24176c7636 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -52,12 +52,16 @@ def __init__(self, caller): def dialog_ext_control(self, answer): if self.sys_dialog.get_visible(): self.sys_dialog.response(answer) + return elif self.warn_dialog.get_visible(): self.warn_dialog.response(answer) + return elif self.ent_dialog.get_visible(): self.ent_dialog.response(answer) + return elif self.yn_dialog.get_visible(): self.yn_dialog.response(answer) + return else: # Get the widget that currently has user focus focused_widget = self._caller.widgets.window1.get_focus() From 2efff8673e3ba2b18495082337b4762ff76c2e77 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 11 Aug 2026 20:39:39 -0700 Subject: [PATCH 072/110] gmoccapy -dialogs: hide the close icon --- src/emc/usr_intf/gmoccapy/dialogs.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index f24176c7636..67004138c96 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -102,6 +102,7 @@ def system_dialog(self): def show_system_dialog(self): dialog = self.sys_dialog dialog._calc.set_value("") + dialog.set_deletable(False) dialog.show_all() self.emit("play_sound", "alert") @@ -162,6 +163,7 @@ def show_entry_dialog(self, data = None, header = _("Enter value") , dialog.calc.num_pad_only(True) dialog.label.set_text(label) dialog.set_title(header) + dialog.set_deletable(False) dialog.show_all() # wait but don't block event loop @@ -232,6 +234,7 @@ def show_warning_dialog(self, title, message, sound=True, dialog.set_title(title) dialog.format_secondary_text(message) dialog.set_markup(message) + dialog.set_deletable(False) dialog.show_all() if sound: self.emit("play_sound", "alert") @@ -279,6 +282,7 @@ def show_yesno_dialog(self, _caller,message, title = _("Operator Message")): dialog.set_markup(message) if title: dialog.set_title(str(title)) + dialog.set_deletable(False) dialog.show_all() self.emit("play_sound", "alert") @@ -315,6 +319,7 @@ def show_user_message(self, message, title = _("Operator Message")): box.add(ok_button) dialog.action_area.add(box) dialog.set_border_width(5) + dialog.set_deletable(False) dialog.show_all() self.emit("play_sound", "alert") response = dialog.run() From 28898e5c4c318b58dca4023c422e8861c98357bb Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 11 Aug 2026 21:39:49 -0700 Subject: [PATCH 073/110] gmoccapy -fix warning dialog bold text It seems gtk has a bug, if you set the markup text twice the text is not bold. Now we force it bold --- src/emc/usr_intf/gmoccapy/dialogs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index 67004138c96..3f229527bf6 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -232,8 +232,7 @@ def show_warning_dialog(self, title, message, sound=True, confirm_pin = 'warning-confirm', active_pin = None): dialog = self.warn_dialog dialog.set_title(title) - dialog.format_secondary_text(message) - dialog.set_markup(message) + dialog.set_markup(''+message+'') dialog.set_deletable(False) dialog.show_all() if sound: From a1612e7d8fde540b219da69be36a13b8c9009357 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 18:50:51 -0700 Subject: [PATCH 074/110] gmoccapy -clean up warning dialog code --- src/emc/usr_intf/gmoccapy/dialogs.py | 27 +++++++++++++-------------- src/emc/usr_intf/gmoccapy/gmoccapy.py | 3 +-- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index 3f229527bf6..04e8308cf29 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -195,15 +195,11 @@ def on_entry_response(self, dialog, rtn): dialog.RESPONSE = rtn # display warning dialog - def warning_dialog(self, message = '', secondary = None, title = _("Operator Message"),\ - sound = True, confirm_pin = 'warning-confirm', active_pin = None): - + def warning_dialog(self): dialog = Gtk.MessageDialog(self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, - Gtk.MessageType.INFO, Gtk.ButtonsType.NONE, message) - # if there is a secondary message then the first message text is bold - if secondary: - dialog.format_secondary_text(secondary) + Gtk.MessageType.INFO, + Gtk.ButtonsType.NONE) ok_button = Gtk.Button.new_with_mnemonic("_Ok") ok_button.set_size_request(-1, 56) ok_button.connect("clicked",lambda w:dialog.response(Gtk.ResponseType.OK)) @@ -211,16 +207,16 @@ def warning_dialog(self, message = '', secondary = None, title = _("Operator Mes box.add(ok_button) dialog.action_area.add(box) dialog.set_border_width(5) - if sound: - self.emit("play_sound", "alert") - dialog.set_title(title) - dialog.context = [] + # HAL pin names + dialog.confirm_pin = 'warning-confirm' + dialog.active_pin = None + def periodic(): - if self._caller.halcomp[confirm_pin]: + if self._caller.halcomp[dialog.confirm_pin]: dialog.response(Gtk.ResponseType.OK) return False - if active_pin is not None: - if not self._caller.halcomp[active_pin]: + if dialog.active_pin is not None: + if not self._caller.halcomp[dialog.active_pin]: dialog.response(Gtk.ResponseType.CANCEL) return False return True @@ -234,6 +230,9 @@ def show_warning_dialog(self, title, message, sound=True, dialog.set_title(title) dialog.set_markup(''+message+'') dialog.set_deletable(False) + dialog.confirm_pin = confirm_pin + dialog.active_pin = active_pin + dialog.show_all() if sound: self.emit("play_sound", "alert") diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index b0d1ba262e1..3421f122de7 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -5683,8 +5683,7 @@ def on_tool_change(self, widget): message = _("Tool\n\n# {0:d}\n\n not in the tool table!").format(toolnumber) result = self.dialogs.show_warning_dialog( _("Manual Tool change"), - message, context='mantoolchange', - confirm_pin = 'toolchange-confirm', + message, confirm_pin = 'toolchange-confirm', active_pin = 'toolchange-change') if result: From a6709881c7d6e259aede7d69ab7240f19d80bc0b Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 19:14:51 -0700 Subject: [PATCH 075/110] qtdragon -check if softkey number is invalid --- share/qtvcp/screens/qtdragon/qtdragon_handler.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index 319a1f9be27..dbcacef1d23 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -973,9 +973,11 @@ def softkey_pressed(self, index): tmp=['main','file','offsets','tool','status', 'probe','gcodes','setup','settings', 'utils','user','camera'] - - btn = self.w[f'btn_{tmp[index]}'] - #print(f'index{index}, btn, {btn}') + try: + btn = self.w[f'btn_{tmp[index]}'] + #print(f'index{index}, btn, {btn}') + except: + pass if btn.isVisible(): btn.click() From f2b8768dbc78eb9bffd88035f03330e84a5f0169 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 19:00:37 -0700 Subject: [PATCH 076/110] qtdragon -adjust request macro function signature --- share/qtvcp/screens/qtdragon/qtdragon_handler.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index dbcacef1d23..a5f926582c5 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -155,7 +155,7 @@ def __init__(self, halcomp, widgets, paths): 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.ext_pause_toggled(state)) - STATUS.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) + STATUS.connect('macro-call-request', lambda w, name, key: self.request_macro_call(name, key)) 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)) STATUS.connect('axis-selection-changed', lambda w,data: self.mpg_selection_changed(data)) @@ -923,14 +923,16 @@ def lastRunLine(self, line): self.add_status(_translate("HandlerClass",'last running line before stoppage: {}'.format(line))) # called from hal_glib to run macros from external event - def request_macro_call(self, data): + def request_macro_call(self, data, key): #print(f'macro call data: {data}') if not self.w.chk_auto_mode_ext_macro.isChecked() and not STATUS.is_mdi_mode(): self.add_status(_translate("HandlerClass",'Machine must be in MDI mode to run macros'), WARNING) return + + cmd = INFO.get_ini_mdi_command(data) #print(f'MDI command:{cmd} data:{data}') - if INFO.get_ini_mdi_command(data) is None: + if cmd is None: data = data.replace('ini-macro-cmd-','') try: temp = INFO.MACRO_COMMAND_DICT.get(data).get('cmd') From 9e823007898554aa9653f7efa31d151af8eb533f Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 19:15:38 -0700 Subject: [PATCH 077/110] halui -change gui ok/cancel pin names yo gui.response.ok gui.response.cancel --- src/emc/usr_intf/halui.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index aac28bea317..2e1e80ea18e 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -962,9 +962,9 @@ int halui_hal_init(void) CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_soft_keys[n]), 0, "halui.gui.softkey.%02d", n)); } - CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_ok), 0, "halui.gui.ok")); + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_ok), 0, "halui.gui.response.ok")); - CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_cancel), 0, "halui.gui.cancel")); + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_cancel), 0, "halui.gui.response.cancel")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_reload), 0, "halui.gui.reload-preview")); From 10460ae5356757d6d770dce22ede37ac59721c6c Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 15 Aug 2026 05:35:26 -0700 Subject: [PATCH 078/110] halui -quiet debugging prints --- src/emc/usr_intf/halui.cc | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 2e1e80ea18e..866a9fac5a2 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -696,7 +696,7 @@ static char* py_call_get_mdi_name( int num) { PyObject *pBytes = PyUnicode_AsUTF8String(pValue); char *result_string = PyBytes_AsString(pBytes); Py_XDECREF(pBytes); - printf("Python function returned: %s %i\n", result_string,num); + //printf("Python function returned: %s %i\n", result_string,num); Py_DECREF(pValue); Py_DECREF(pFuncWrite); return result_string; @@ -954,7 +954,7 @@ int halui_hal_init(void) } for (int n=0; ngui_mdi_commands[n]), 0, "halui.gui.mdi-command.%s", py_call_get_mdi_name(n))); } @@ -1541,7 +1541,6 @@ static int iniLoad(const char *filename) int temp = py_call_get_mdi_count(); for (int n=0; n= 0) { - fprintf(stderr, "halui Bridge: axis selected %d\n",aselect_changed); + //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) { @@ -2393,7 +2388,6 @@ static void check_hal_changes() hal_set_bool(halui_data->axis_is_selected[axis_num], 1); if (hal_get_bool(halui_data->ajog_plus[num_axes])) { - fprintf(stderr, "halui: jog plus: %d\n",num_axes); sendJogCont(axis_num, tempjogspeed,JOGTELEOP); } else if (hal_get_bool(halui_data->ajog_minus[num_axes])) { sendJogCont(axis_num, -tempjogspeed,JOGTELEOP); @@ -2507,7 +2501,6 @@ static void check_hal_changes() // request GUI to 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){ - fprintf(stderr,"GUI MDI command called index: %i\n", n); py_call_request_MDI(n); } } @@ -2515,28 +2508,23 @@ static void check_hal_changes() // request GUI soft keys for(int n = 0; n < num_gui_soft_keys; n++) { if (check_bit_changed(new_halui_data.gui_soft_keys[n], old_halui_data.gui_soft_keys[n]) != 0){ - fprintf(stderr,"GUI SOFTKEY called index: %i\n", n); py_call_request_softkey(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(); } if (check_bit_changed(new_halui_data.gui_reload, old_halui_data.gui_reload) != 0) { - fprintf(stderr,"GUI RELOAD DISPLAY command called\n"); py_call_reload_display(); } if (check_bit_changed(new_halui_data.gui_shutdown, old_halui_data.gui_shutdown) != 0) { - fprintf(stderr,"GUI SHUTDOWN command called\n"); py_call_shutdown_controller(); } From c1b529244857559dc6429d456a8f7a811e74ef46 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:36:03 -0700 Subject: [PATCH 079/110] halui -change MPG name to gui.mpg-aux.0 --- src/emc/usr_intf/halui.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 866a9fac5a2..d54bff10c92 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -928,7 +928,7 @@ int halui_hal_init(void) CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->ajog_increment_minus[axis_num]), 0, "halui.axis.%c.increment-minus", c)); } - CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->mpg_select0), 0, "halui.gui.mpg-select.0")); + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->mpg_select0), 0, "halui.gui.mpg-aux.0")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->joint_home[num_joints]), 0, "halui.joint.selected.home")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->joint_unhome[num_joints]), 0, "halui.joint.selected.unhome")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->jjog_plus[num_joints]), 0, "halui.joint.selected.plus")); From 5be5f6dd4231d0789284bdd56e44f1e5cd5b4cb8 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 21 Aug 2026 09:29:10 -0700 Subject: [PATCH 080/110] hal_glib -check if zmq read socket is available quietly ignore if not --- lib/python/common/hal_glib.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/python/common/hal_glib.py b/lib/python/common/hal_glib.py index c5fef8affd7..fa83cb28665 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -314,8 +314,8 @@ def __init__(self, stat = None): self.stat = stat or linuxcnc.stat() self.cmd = linuxcnc.command() - self.readAddress = "tcp://127.0.0.1:5691" + self.read_available = False self.writeAddress = "tcp://127.0.0.1:5690" self.write_available = False # if zmq is imported, create sockets @@ -398,8 +398,10 @@ def init_read_socket(self): self.readSocket.setsockopt_string(zmq.SUBSCRIBE, 'STATUSREQUEST') try: self.readSocket.connect(self.readAddress) + self.read_available = True LOG.debug('hal_glib read socket available: {}'.format(self.readAddress)) except Exception as e: + self.read_available = False LOG.debug('hal_glib read socket error: {}'.format(e)) return GObject.io_add_watch(self.readSocket.getsockopt(zmq.FD), @@ -408,9 +410,10 @@ def init_read_socket(self): # called directly to process any current message def readNextMsg(self): - event = self.readSocket.poll(timeout=0) - if event & zmq.POLLIN: - self.convertMsg() + if self.read_available: + event = self.readSocket.poll(timeout=0) + if event & zmq.POLLIN: + self.convertMsg() # called when GObject notices a change def onReadMsg(self, queue, condition, sock): From 6f80eb1edb7f72a43fa538a37777a2f1bb0a3618 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 18:39:12 -0700 Subject: [PATCH 081/110] bridge - send out a key string with macro/mdi name Makes it easier to decide the type of cammand to call --- lib/python/bridgeui/bridge.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index beef042a758..f7caf799abe 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -134,11 +134,11 @@ def action(self, msg, data): #print ('axis state', self.axesSelected,self.currentSelectedAxis) # send msg to hal_glib - def writeMsg(self, msg, data=''): + def writeMsg(self, msg, data1=()): #print('Write Msg called') if ZMQ: topic = self.writeTopic - message = json.dumps({'FUNCTION':msg,'ARGS':data}) + message = json.dumps({'FUNCTION':msg,'ARGS':data1}) LOG.debug('Sending ZMQ Message:{} {}'.format(topic, message)) self.writeSocket.send_multipart( [bytes(topic.encode('utf-8')), @@ -175,6 +175,7 @@ def softkey(self, index): # if the number is bigger then MDI command list # then look for MACRO commands def getMdiName(self, num): + # macro if num beyound length of MDIs if num >len(self.INFO.MDI_COMMAND_DICT)-1: offset = len(self.INFO.MDI_COMMAND_DICT) return self.getMacroName(num-offset) @@ -191,11 +192,12 @@ def getMacroName(self, num): return temp def runIndexedMacro(self, num): - # check for any MDI commands first: - name = self.getMdiName(num) - LOG.debug('Macro name:{} ,index: {}'.format(name, num)) - if name != 'None': - self.writeMsg('request_macro_call', name) + if num <= len(self.INFO.MDI_COMMAND_DICT)-1: + # check for any MDI commands first: + name = self.getMdiName(num) + LOG.debug('Macro name:{} ,index: {}'.format(name, num)) + if name != 'None': + self.writeMsg('request_macro_call', (name, 'MDI')) # else look for any MACRO commands: else: @@ -203,11 +205,11 @@ def runIndexedMacro(self, num): name = self.getMacroName(num-offset) LOG.debug('Macro name:{} ,index: {}'.format(name, num)) if name != 'None': - self.writeMsg('request_macro_call', name) + self.writeMsg('request_macro_call',(name, 'MACRO')) - # cound of MDI and MACRO commands + # count of MDI and MACRO commands def getMdiCount(self): - #print('->',len(self.INFO.MDI_COMMAND_DICT),len(self.INFO.MACRO_COMMAND_DICT)) + #print('->',len(self.INFO.MDI_COMMAND_DICT) + len(self.INFO.MACRO_COMMAND_DICT)) return len(self.INFO.MDI_COMMAND_DICT) + len(self.INFO.MACRO_COMMAND_DICT) def getJogRate(self): From bc5f6f51d64fda84a4532b4442c02e2c9f9dd216 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 18:46:25 -0700 Subject: [PATCH 082/110] hal_glib -change request macro function signature to add a key string --- lib/python/common/hal_glib.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/python/common/hal_glib.py b/lib/python/common/hal_glib.py index fa83cb28665..cdc24ad582c 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -247,7 +247,7 @@ class _GStat(GObject.GObject): 'cancel-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)), '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,)), - 'macro-call-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), + 'macro-call-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,GObject.TYPE_PYOBJECT)), 'softkey-pressed': (GObject.SignalFlags.RUN_FIRST , GObject.TYPE_NONE, (GObject.TYPE_INT,)), } @@ -432,17 +432,17 @@ def convertMsg(self): function = y.get('FUNCTION') data = y.get('ARGS') LOG.debug('REQUESTED:{}'.format(y)) - if data == '': - try: - self[function]() - except Exception as e: - LOG.debug('not a valid request\n {}'.format(e)) - else: - try: - self[function](data) - except Exception as e: - LOG.debug('not a valid request\n {}'.format(e)) - #self. action(y.get('MESSAGE'),y.get('ARGS')) + + # wrap in a list if not already + if not isinstance(data, list): + data = [data] + + # call function with arbitrary arguments + try: + self[function](*data) + return + except TypeError as e: + LOG.debug(e) def merge(self): self.old['command-state'] = self.stat.state @@ -1499,8 +1499,8 @@ def request_cycle_start(self, data): def request_cycle_pause(self, data): self.emit('cycle-pause-request', data) - def request_macro_call(self, data): - self.emit('macro-call-request', data) + def request_macro_call(self, data, key=''): + self.emit('macro-call-request', data, key) def request_reload_display(self, data): self.emit('reload-display') From 0584432c5b71d6547906034f19aea1680a8aec15 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 13 Aug 2026 18:43:01 -0700 Subject: [PATCH 083/110] bridge -Don't break everything if there is no ZMQ library selection by HAL pins should keep working. --- lib/python/bridgeui/bridge.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index f7caf799abe..be5a547c2fd 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -90,6 +90,9 @@ def init_read(self): # callback from ZMQ read socket def readMsg(self): + # no ZMQ library. return so HALUI works otherwise + if not ZMQ: return + if self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: while self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: # get raw message From 187b5acaf01e9874212f43886720758ed8503841 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 19:18:25 -0700 Subject: [PATCH 084/110] axis sim -change pin names do to halui name change --- configs/sim/axis/panel.hal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal index 28cbb492905..980ad7b1d21 100644 --- a/configs/sim/axis/panel.hal +++ b/configs/sim/axis/panel.hal @@ -33,8 +33,8 @@ net pause halui.gui.cycle-start panel.cycle-start net start halui.gui.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 +net cancel halui.gui.response.cancel panel.cancel +net ok halui.gui.response.ok panel.ok net softkey0 halui.gui.softkey.00 panel.softkey-0 net softkey1 halui.gui.softkey.01 panel.softkey-1 From 81e74b9b5c9a7a9142cab034f7a764b93e4ffc11 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 19:19:23 -0700 Subject: [PATCH 085/110] qtdragon sim -change pin names do to halui name change --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index 1d1f8628816..eb1b4046f5d 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -34,8 +34,8 @@ net pause halui.gui.cycle-start panel.cycle-start net start halui.gui.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 +net cancel halui.gui.response.cancel panel.cancel +net ok halui.gui.response.ok panel.ok net softkey0 halui.gui.softkey.00 panel.softkey-0 net softkey1 halui.gui.softkey.01 panel.softkey-1 From de66056c73909de18c7098f1443d58ead8e9f4bd Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 19:19:53 -0700 Subject: [PATCH 086/110] gmoccapy sim -change pin names do to halui name change --- configs/sim/gmoccapy/panel-4axis.hal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal index 42685ace762..0dd30d02c33 100644 --- a/configs/sim/gmoccapy/panel-4axis.hal +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -39,8 +39,8 @@ net pause halui.gui.cycle-start panel.cycle-start net start halui.gui.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 +net cancel halui.gui.response.cancel panel.cancel +net ok halui.gui.response.ok panel.ok net softkey0 halui.gui.softkey.00 panel.softkey-v0 From 2aac1c18070b790c82e001b9f7cf73662fdbd610 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 21:26:35 -0700 Subject: [PATCH 087/110] axis -incorporate zmq messages pulls in gstat and iniinfo libraries --- src/emc/usr_intf/axis/scripts/axis.py | 173 ++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/src/emc/usr_intf/axis/scripts/axis.py b/src/emc/usr_intf/axis/scripts/axis.py index f9f04cc72a5..bbb1fbb450a 100755 --- a/src/emc/usr_intf/axis/scripts/axis.py +++ b/src/emc/usr_intf/axis/scripts/axis.py @@ -68,6 +68,9 @@ def tkerror(self, arg): import bwidget from math import hypot, atan2, sin, cos, pi, sqrt import linuxcnc +from hal_glib import GStat +from common.iniinfo import _IStat as IStatParent + from glnav import * if "AXIS_NO_HAL" in os.environ: @@ -111,6 +114,15 @@ def putpref(self, option, value, type=bool): self.set("DEFAULT", option, str(value)) self.write(open(self.fn, "w")) +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 + if sys.argv[1] != "-ini": raise SystemExit("-ini must be first argument") @@ -118,6 +130,24 @@ def putpref(self, option, value, type=bool): ap = AxisPreferences() +INFO = Info() +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)) +GSTAT.connect('softkey-pressed', lambda w,data: softkey_pressed(data)) +GSTAT.connect('shutdown-request', lambda w : General_Halt()) +GSTAT.connect('reload-display', lambda w : commands.clear_live_plot()) + +global last_mpg +last_mpg = 0 +mpg_enabled = 0 + # Handle repeated key press events pressed_keys_list = [] def key_pressed(ev): @@ -974,6 +1004,27 @@ def update(self): else: state="normal" root_window.call(jname,"configure","-state",state) + GSTAT.run_iteration() + global mpg_enabled + try: + if comp['mpg-enable'] or mpg_enabled: + global last_mpg + if comp['mpg-in'] == last_mpg: return + if comp['mpg-in'] > last_mpg: + if s.task_mode == linuxcnc.MODE_MDI: + commands._mdi_up_cmd() + else: + scroll_up(None) + if comp['mpg-in'] < last_mpg: + if s.task_mode == linuxcnc.MODE_MDI: + commands._mdi_down_cmd() + else: + scroll_down(None) + + last_mpg = comp['mpg-in'] + except Exception as e: + print(e) + user_live_update() def clear(self): @@ -2443,7 +2494,11 @@ def task_stop(*event): comp["abort"] = False def mdi_up_cmd(*args): + print(args) if args and args[0].char: return # e.g., for KP_Up with numlock on + _mdi_up_cmd() + + def _mdi_up_cmd(): global mdi_history_index if widgets.mdi_command.cget("state") == "disabled": return @@ -2461,6 +2516,9 @@ def mdi_up_cmd(*args): def mdi_down_cmd(*args): if args and args[0].char: return # e.g., for KP_Up with numlock on + _mdi_down_cmd() + + def _mdi_down_cmd(): global mdi_history_index if widgets.mdi_command.cget("state") == "disabled": return @@ -3958,6 +4016,8 @@ def select_run_from(e): comp.newpin("resume-inhibit",hal.HAL_BIT,hal.HAL_IN) comp.newpin("error", hal.HAL_BIT, hal.HAL_OUT) comp.newpin("abort", hal.HAL_BIT, hal.HAL_OUT) + comp.newpin('mpg-enable', hal.HAL_BIT, hal.HAL_IN) + comp.newpin('mpg-in', hal.HAL_S32, hal.HAL_IN) vars.has_ladder.set(hal.component_exists('classicladder_rt')) @@ -4058,6 +4118,119 @@ def remove_tempdir(t): z = (o.canon.min_extents[2] + o.canon.max_extents[2])/2 o.set_centerpoint(x, y, z) +def select_axis(data): + global mpg_enabled + if data is None: return + if data =='MPG0': + mpg_enabled = True + return + mpg_enabled = False + if data.upper() =='NONE': + return + try: + widget = getattr(widgets, "axis_%s" % data.lower()) + widget.focus() + widget.invoke() + except: + pass + +def cycle_start_request(state): + if s.task_mode == linuxcnc.MODE_MDI: + command = vars.mdi_command.get() + commands.send_mdi_command(command) + else: + commands.task_run(None) + +def pause_request(state): + commands.task_pauseresume(None) + +def dialog_ext_control(widget,t,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) + cmd = INFO.get_ini_mdi_command(name) + #print(f'MDI command:{cmd} name:{name}') + if not INFO.get_ini_mdi_command(name) is None: + run_mdi(data=cmd) + else: + #print(INFO.get_ini_macro_command(name)) + try: + temp = INFO.MACRO_COMMAND_DICT.get(name).get('cmd') + #print(temp) + run_macro(data=temp) + except Exception as e: + print(e) + +def run_mdi(data): + #print(f'run mdi command:{data}') + mdi_list = data.split(';') + for code in (mdi_list): + commands.send_mdi_command(code) + +def run_macro(data): + #print(f'run macro:{data}') + o_codes = data.split() + command = str( "O<" + o_codes[0] + "> call" ) + # check for oword and confirm path exists + #if not self.check_macro_path(command): + # return + for code in o_codes[1:]: + if vars.metric.get(): unit_str = " " + _("mm") + else: unit_str = " " + _("in") + param = prompt_float("Macro", f"Enter a value for: {code}:", + "", unit_str) + if param <= 0: return + if vars.metric.get(): param /= 25.4 + command = command + " [" + str(param) + "] " + commands.send_mdi_command(command) + +def softkey_pressed(index): + + if index == 0: + root_window.tk.call('.pane.top.tabs','raise','manual') + elif index == 1: + root_window.tk.call('.pane.top.tabs','raise','mdi') + elif index == 2: + root_window.tk.call('.pane.top.right','raise','preview') + elif index == 3: + root_window.tk.call('.pane.top.right','raise','numbers') + elif index == 4: + root_window.tk.call('.pane.top.right','raise','user_0') + else: + print(f'Softkey index:{index}') + def destroy_splash(): try: root_window.send("popimage", "destroy", ".") From 8d4f22216947027bd016f828d358fe99f13b7719 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 21:28:23 -0700 Subject: [PATCH 088/110] axis sim -remove user command file got zmq messages incorporated in axis now --- configs/sim/axis/axis_halui_test.ini | 2 - configs/sim/axis/gstatmessages.py | 163 --------------------------- 2 files changed, 165 deletions(-) delete mode 100644 configs/sim/axis/gstatmessages.py diff --git a/configs/sim/axis/axis_halui_test.ini b/configs/sim/axis/axis_halui_test.ini index 2e53fb8275a..9f0d56b9346 100644 --- a/configs/sim/axis/axis_halui_test.ini +++ b/configs/sim/axis/axis_halui_test.ini @@ -53,8 +53,6 @@ TOOL_EDITOR = tooledit INCREMENTS = 1 in, 0.1 in, 10 mil, 1 mil, 1mm, .1mm, 1/8000 in -USER_COMMAND_FILE=gstatmessages.py - [MDI_COMMAND_LIST] MDI_COMMAND_MACRO0 = G53 G0 Z0;G0 X0 Y0;Z0, Goto\nUser\nZero MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero diff --git a/configs/sim/axis/gstatmessages.py b/configs/sim/axis/gstatmessages.py deleted file mode 100644 index 702c1b6e0ba..00000000000 --- a/configs/sim/axis/gstatmessages.py +++ /dev/null @@ -1,163 +0,0 @@ - -from hal_glib import GStat -from common.iniinfo import _IStat as IStatParent - -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 - -INFO = Info() - -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)) -GSTAT.connect('softkey-pressed', lambda w,data: softkey_pressed(data)) -GSTAT.connect('shutdown-request', lambda w : General_Halt()) -GSTAT.connect('reload-display', lambda w : commands.clear_live_plot()) - -global last_mpg -last_mpg = 0 -mpg_enabled = 0 - - -def user_hal_pins(): - comp.newpin('mpg-enable', hal.HAL_BIT, hal.HAL_IN) - comp.newpin('mpg-in', hal.HAL_S32, hal.HAL_IN) - comp.ready() - -def user_live_update(): - GSTAT.run_iteration() - global mpg_enabled - try: - if comp['mpg-enable'] or mpg_enabled: - global last_mpg - if comp['mpg-in'] == last_mpg: return - if comp['mpg-in'] > last_mpg:scroll_up(None) - if comp['mpg-in'] < last_mpg:scroll_down(None) - last_mpg = comp['mpg-in'] - except Exception as e: - print(e) - -def select_axis(data): - global mpg_enabled - if data is None: return - if data =='MPG0': - mpg_enabled = True - return - mpg_enabled = False - if data.upper() =='NONE': - return - try: - widget = getattr(widgets, "axis_%s" % data.lower()) - widget.focus() - widget.invoke() - except: - pass - -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) - cmd = INFO.get_ini_mdi_command(name) - #print(f'MDI command:{cmd} name:{name}') - if not INFO.get_ini_mdi_command(name) is None: - run_mdi(data=cmd) - else: - #print(INFO.get_ini_macro_command(name)) - try: - temp = INFO.MACRO_COMMAND_DICT.get(name).get('cmd') - #print(temp) - run_macro(data=temp) - except Exception as e: - print(e) - -def run_mdi(data): - #print(f'run mdi command:{data}') - mdi_list = data.split(';') - for code in (mdi_list): - commands.send_mdi_command(code) - -def run_macro(data): - #print(f'run macro:{data}') - o_codes = data.split() - command = str( "O<" + o_codes[0] + "> call" ) - # check for oword and confirm path exists - #if not self.check_macro_path(command): - # return - for code in o_codes[1:]: - if vars.metric.get(): unit_str = " " + _("mm") - else: unit_str = " " + _("in") - param = prompt_float("Macro", f"Enter a value for: {code}:", - "", unit_str) - if param <= 0: return - if vars.metric.get(): param /= 25.4 - command = command + " [" + str(param) + "] " - commands.send_mdi_command(command) - -def softkey_pressed(index): - - if index == 0: - root_window.tk.call('.pane.top.tabs','raise','manual') - elif index == 1: - root_window.tk.call('.pane.top.tabs','raise','mdi') - elif index == 2: - root_window.tk.call('.pane.top.right','raise','preview') - elif index == 3: - root_window.tk.call('.pane.top.right','raise','numbers') - elif index == 4: - root_window.tk.call('.pane.top.right','raise','user_0') - else: - print(f'Softkey index:{index}') From 96fc28c3945b056771e1a14f382062a5d91e7c12 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 13 Aug 2026 05:53:49 -0700 Subject: [PATCH 089/110] axis -fix macro entry dialog error --- src/emc/usr_intf/axis/scripts/axis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/emc/usr_intf/axis/scripts/axis.py b/src/emc/usr_intf/axis/scripts/axis.py index bbb1fbb450a..00f1806f155 100755 --- a/src/emc/usr_intf/axis/scripts/axis.py +++ b/src/emc/usr_intf/axis/scripts/axis.py @@ -1797,6 +1797,7 @@ def result(self): return None def run(self): + self.t.wait_visibility() self.t.grab_set() self._after = self.t.after_idle(self.do_focus) self.t.wait_window() From f131f86808d884ec9926416a680b85a117de6bf9 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:51:34 -0700 Subject: [PATCH 090/110] axis -sim: name change for gui.mpg-aux.0 --- configs/sim/axis/panel.hal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal index 980ad7b1d21..b3e91428b14 100644 --- a/configs/sim/axis/panel.hal +++ b/configs/sim/axis/panel.hal @@ -6,7 +6,7 @@ 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 sgui halui.gui.mpg-select.0 panel.select-gui0 +net sgui halui.gui.mpg-aux.0 panel.select-gui0 net mpg-scale axis.x.jog-scale panel.mpg-scale net mpg-scale axis.y.jog-scale From 951d5e92cd20155b47452abd366e5e906a277e0e Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 19:00:01 -0700 Subject: [PATCH 091/110] axis -adjust request macro function signature --- src/emc/usr_intf/axis/scripts/axis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/emc/usr_intf/axis/scripts/axis.py b/src/emc/usr_intf/axis/scripts/axis.py index 00f1806f155..cf0ee716f4b 100755 --- a/src/emc/usr_intf/axis/scripts/axis.py +++ b/src/emc/usr_intf/axis/scripts/axis.py @@ -139,7 +139,7 @@ def __new__(cls, *args, **kwargs): 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)) +GSTAT.connect('macro-call-request', lambda w, key, name: request_macro_call(key, name)) GSTAT.connect('softkey-pressed', lambda w,data: softkey_pressed(data)) GSTAT.connect('shutdown-request', lambda w : General_Halt()) GSTAT.connect('reload-display', lambda w : commands.clear_live_plot()) @@ -4179,7 +4179,7 @@ def dialog_ext_control(widget,t,state): if state == 0: notifications.clear_one() -def request_macro_call(name): +def request_macro_call(name, key): #print('request macro:',name) cmd = INFO.get_ini_mdi_command(name) #print(f'MDI command:{cmd} name:{name}') From 209073bdf1f10f37b33764460b8f2aca9ebf9e72 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 24 Aug 2026 16:16:26 -0700 Subject: [PATCH 092/110] axis -switch halui.gui.shutdown to shutdown system --- src/emc/usr_intf/axis/scripts/axis.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/emc/usr_intf/axis/scripts/axis.py b/src/emc/usr_intf/axis/scripts/axis.py index cf0ee716f4b..4d01d41a64e 100755 --- a/src/emc/usr_intf/axis/scripts/axis.py +++ b/src/emc/usr_intf/axis/scripts/axis.py @@ -67,6 +67,7 @@ def tkerror(self, arg): import locale import bwidget from math import hypot, atan2, sin, cos, pi, sqrt +import subprocess import linuxcnc from hal_glib import GStat from common.iniinfo import _IStat as IStatParent @@ -141,7 +142,7 @@ def __new__(cls, *args, **kwargs): GSTAT.connect('cancel-request', lambda w, state: dialog_ext_control(w,1,0)) GSTAT.connect('macro-call-request', lambda w, key, name: request_macro_call(key, name)) GSTAT.connect('softkey-pressed', lambda w,data: softkey_pressed(data)) -GSTAT.connect('shutdown-request', lambda w : General_Halt()) +GSTAT.connect('shutdown-request', lambda w : request_shutdown()) GSTAT.connect('reload-display', lambda w : commands.clear_live_plot()) global last_mpg @@ -1180,7 +1181,6 @@ def next_line(self, st): progress_re = re.compile("^FILTER_PROGRESS=(\\d*)$") def filter_program(program_filter, infilename, outfilename): - import subprocess outfile = open(outfilename, "w") infilename_q = infilename.replace("'", "'\\''") env = dict(os.environ) @@ -4059,12 +4059,11 @@ def load_gladevcp_panel(): del gladecmd[gladecmd.index('-c')] else: gladename = 'gladevcp' - from subprocess import Popen xid = gladevcp_frame.winfo_id() cmd = "halcmd loadusr -Wn {0} gladevcp -c {0}".format(gladename).split() cmd += ['-d', '-x', str(xid)] + gladecmd print(cmd) - child = Popen(cmd) + child = subprocess.Popen(cmd) _dynamic_childs['{}'.format(gladename)] = (child, cmd, True) notifications = Notification(root_window) @@ -4194,6 +4193,17 @@ def request_macro_call(name, key): except Exception as e: print(e) +def request_shutdown(): + if shutil.which('gnome-session-quit'): + time.sleep(.05) + subprocess.run(["gnome-session-quit", "--power-off"], check=True) + elif shutil.which('xfce4-session-logout'): + subprocess.call('xfce4-session-logout', shell=True) + else: + # force a shutdown - no prompt + subprocess.call('systemctl poweroff', shell=True) + + def run_mdi(data): #print(f'run mdi command:{data}') mdi_list = data.split(';') @@ -4244,7 +4254,6 @@ def _dynamic_tab(name, text): return tab def _dynamic_tabs(inifile): - from subprocess import Popen tab_names = inifile.findall("DISPLAY", "EMBED_TAB_NAME") tab_cmd = inifile.findall("DISPLAY", "EMBED_TAB_COMMAND") if len(tab_names) != len(tab_cmd): @@ -4278,7 +4287,7 @@ def _dynamic_tabs(inifile): f.pack(fill="both", expand=1) xid = f.winfo_id() cmd = c.replace('{XID}', str(xid)).split() - child = Popen(cmd) + child = subprocess.Popen(cmd) wait = cmd[:2] == ['halcmd', 'loadusr'] _dynamic_childs[str(w)] = (child, cmd, wait) From 1c0488c5b6004e2bda44ac63ee72251b7412439a Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 25 Aug 2026 20:53:14 -0700 Subject: [PATCH 093/110] axis -change the close dialog to modeless So an external zmq message can control the buttons --- src/emc/usr_intf/axis/scripts/axis.py | 90 +++++++++++++++++++++------ 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/src/emc/usr_intf/axis/scripts/axis.py b/src/emc/usr_intf/axis/scripts/axis.py index 4d01d41a64e..78685bceb1a 100755 --- a/src/emc/usr_intf/axis/scripts/axis.py +++ b/src/emc/usr_intf/axis/scripts/axis.py @@ -203,9 +203,51 @@ def key_released(ev): raise def General_Halt(): + # Create a non-modal (modeless) dialog window + dialog = Tkinter.Toplevel(root_window) + # 2. Keep it always on top + dialog.attributes("-topmost", True) + + dialog.title(_("Confirm Close")) + parent = root_window + # Force Tkinter to calculate window sizes before rendering + #dialog.update_idletasks() + + # Calculate centering coordinates relative to root window + root_w = parent.winfo_width() + root_h = parent.winfo_height() + root_x = parent.winfo_x() + root_y = parent.winfo_y() + + win_w = dialog.winfo_width() + win_h = dialog.winfo_height() + + # Center formula: root position + (half of root size) - (half of child size) + x = root_x + (root_w // 2) - (win_w // 2) + y = root_y + (root_h // 2) - (win_h // 2) + + # Apply the calculated geometry + dialog.geometry(f"+{x}+{y}") + # Add message label text = _("Do you really want to close LinuxCNC?") - if not root_window.tk.call("nf_dialog", ".error", _("Confirm Close"), text, "warning", 1, _("Yes"), _("No")): + label = Tkinter.Label(dialog, text=text) + label.pack(padx=20, pady=20) + + # Yes button destroys the main window + def on_yes(): + dialog.destroy() root_window.destroy() + + # No button just closes the dialog box + def on_no(): + dialog.destroy() + + # Add buttons + btn_yes = Tkinter.Button(dialog, text=_("Yes"), width=10, command=on_yes) + btn_yes.pack(side=Tkinter.LEFT, padx=20, pady=10) + + btn_no = Tkinter.Button(dialog, text=_("No"), width=10, command=on_no) + btn_no.pack(side=Tkinter.RIGHT, padx=20, pady=10) root_window.protocol("WM_DELETE_WINDOW", General_Halt) @@ -4145,6 +4187,20 @@ def pause_request(state): commands.task_pauseresume(None) def dialog_ext_control(widget,t,state): + + def process(widget,state): + txt = widget.cget("text") + #print(txt) + if txt.lower() in ('ok','yes') and state: + #print('Ok') + widget.invoke() + return True + elif txt.lower() in('no','cancel') and not state: + #print('Cancel') + widget.invoke() + return True + return False + flag = False for child in root_window.winfo_children(): #print(child) @@ -4158,18 +4214,14 @@ def dialog_ext_control(widget,t,state): 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() + if process(child3,state): flag = True break + if isinstance(child2, Tkinter.Button): + if process(child2,state): + flag = True + break + if flag: break if flag: break else: @@ -4194,14 +4246,14 @@ def request_macro_call(name, key): print(e) def request_shutdown(): - if shutil.which('gnome-session-quit'): - time.sleep(.05) - subprocess.run(["gnome-session-quit", "--power-off"], check=True) - elif shutil.which('xfce4-session-logout'): - subprocess.call('xfce4-session-logout', shell=True) - else: - # force a shutdown - no prompt - subprocess.call('systemctl poweroff', shell=True) + if shutil.which('gnome-session-quit'): + time.sleep(.05) + subprocess.run(["gnome-session-quit", "--power-off"], check=True) + elif shutil.which('xfce4-session-logout'): + subprocess.call('xfce4-session-logout', shell=True) + else: + # force a shutdown - no prompt + subprocess.call('systemctl poweroff', shell=True) def run_mdi(data): From be70a16ff5b0b22f7f9b19b091f9c14cfbab3a01 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 24 Aug 2026 16:17:39 -0700 Subject: [PATCH 094/110] axis -docs: add info about HALUI gui pins - add info about mdi/macro commands --- docs/src/gui/axis.adoc | 134 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/docs/src/gui/axis.adoc b/docs/src/gui/axis.adoc index 81c50d8e6d5..2b072eb88e4 100644 --- a/docs/src/gui/axis.adoc +++ b/docs/src/gui/axis.adoc @@ -1076,6 +1076,140 @@ Type Dir Name bit IN axisui.resume-inhibit ---- +== AXIS with HALUI + +HALUI will create halui.gui pins that interface with AXIS. +This could be used to connect a control panel that interacts better with AXIS. + +- There is a cycle start and cycle pause pin - these call the code in AXIS rather then the motion controller. + +- If there are macros/mdi commands defined in the INI there will be (up to 64) pins available to initiate them. + +- clear/reload the display +- shutdown the system +- cancel of notify (error) messages +- softkeys 0 - 4 control tab keys: + +- enabling halui.gui.mpg-aux.0, will allow scrolling of the gcode text with the axis.mpg-in pin. +- HALUI selected axis will be reported to AXIS. + +- HALUI selected jog rate will be reported to AXIS. + +In either case the last changed rate/axis (either HALUI or AXIS) will be used for screen button jogging or HALUI pin jogging. + +=== Typical HAL pins available: +---- +Component Pins: +Owner Type Dir Value Name + 25 bit IN FALSE halui.gui.cycle-pause + 25 bit IN FALSE halui.gui.cycle-start + 25 bit IN FALSE halui.gui.mdi-command.MACRO0 + 25 bit IN FALSE halui.gui.mdi-command.MACRO1 + 25 bit IN FALSE halui.gui.mdi-command.MACRO2 + 25 bit IN FALSE halui.gui.mdi-command.MACRO3 + 25 bit IN FALSE halui.gui.mdi-command.0 + 25 bit IN FALSE halui.gui.mdi-command.1 + 25 bit IN FALSE halui.gui.mpg-aux.0 + 25 bit IN FALSE halui.gui.reload-preview + 25 bit IN FALSE halui.gui.response.cancel + 25 bit IN FALSE halui.gui.response.ok + 25 bit IN FALSE halui.gui.shutdown + 25 bit IN FALSE halui.gui.softkey.00 + 25 bit IN FALSE halui.gui.softkey.01 + 25 bit IN FALSE halui.gui.softkey.02 + 25 bit IN FALSE halui.gui.softkey.03 + 25 bit IN FALSE halui.gui.softkey.04 + 25 bit IN FALSE halui.gui.softkey.05 + 25 bit IN FALSE halui.gui.softkey.06 + 25 bit IN FALSE halui.gui.softkey.07 + 25 bit IN FALSE halui.gui.softkey.08 + 25 bit IN FALSE halui.gui.softkey.09 + 25 bit IN FALSE halui.gui.softkey.10 + 25 bit IN FALSE halui.gui.softkey.11 + 25 bit IN FALSE halui.gui.softkey.12 + 25 bit IN FALSE halui.gui.softkey.13 + 25 bit IN FALSE halui.gui.softkey.14 + 25 bit IN FALSE halui.gui.softkey.15 + 25 bit IN FALSE halui.gui.softkey.16 + 25 bit IN FALSE halui.gui.softkey.17 + 25 bit IN FALSE halui.gui.softkey.18 + 25 bit IN FALSE halui.gui.softkey.19 + +---- + +=== HALUI GUI Cycle Start +The halui.gui.cycle-start pin will request AXIS to start a cycle. + +AXIS will decide what will happen by what mode it's in. + +In Auto mode, a loaded gcode program will run. + +In MDI mode, the command in AXIS's MDI line will run. + +=== HALUI GUI Macros +One can initiate GMoccapy styled Macros from HALUI pins. + +These would be the macros found in the INI under the heading [MACROS] + +These call Oword subroutines of the same name. + +These Oword subroutines can require variables supplied at runtime by the user. + +Here is an example entry: + +[source,ini] +---- +[MACROS] +MACRO_COMMAND_MACRO0 = i_am_lost +MACRO_COMMAND_MACRO1 = halo_world +MACRO_COMMAND_MACRO2 = increment xinc yinc +---- + +And here are what the pin names would be: + +For 'i_am_lost': halui.gui.mdi-command.MACRO0 + +For 'halo_world': halui.gui.mdi-command.MACRO1 + +For 'increment': halui.gui.mdi-command.MACRO2 + + +The above example 'increment', will prompt the user for two values 'xinc' and 'yinc' when run. + + +[NOTE] +All defined Oword files must be located on the system as defined in the +[DISPLAY]PROGRAM_PREFIX or [RS274NGC]SUBROUTINE_PATH INI entries. + +=== HALUI INI MDI Commands +One can initiate MDI Command Macros from HALUI pins. + +MDI commands are defined under the heading [MDI_COMMAND_LIST] as + +MDI_COMMAND_MACRO0 = gcode + +These could also call OWord routines if desired. + +Commands separated by the ';' are run one after another. + +Usually best for short convenience positioning as at runtime user entry is not available. + +[source,ini] +---- +[MDI_COMMAND_LIST] +MDI_COMMAND_MACRO3 = G53 G0 Z0;G0 X0 Y0;Z0 +MDI_COMMAND_MACRO4 = G53 G0 Z0;G53 G0 X0 Y0 +---- + +And here are what the pin names would be: + +For 'MACRO3': halui.gui.mdi-command.MACRO3 + +For 'MACRO4': halui.gui.mdi-command.MACRO4 + + +In this example: + +'MACRO3' is used to move to the current user system origin (zero point). + +The sequence will run like this: move Z axis up first in G53 (machine coordinate system) mode. + +Then move to X0 and Y0 in the current user system, and finally move the Z axis to 0 in the current user system. + +'MACRO4' is used to move to the machine system origin. + +The sequence will run like this: move Z axis up first in G53 (machine coordinate system) mode. + +Then move to X0 and Y0 in the G53 mode. + +=== Soft Keys +Softkeys 0 - 4 control AXIS tab keys: + +halui.gui.softkey.00 = Manual tab. + +halui.gui.softkey.01 = MDI tab. + +halui.gui.softkey.02 = preview tab. + +halui.gui.softkey.03 = DRO tab. + +halui.gui.softkey.04 = User tab, if available + + +=== MPG-Aux +Enabling halui.gui.mpg-aux.0, will allow scrolling of the gcode text with the axis.mpg-in pin, typically connected to an MPG encoder count pin. + +=== Shutdown +If halui.gui.shutdown is set true, the whole linux system will shutdown. + +If the system supports a builtin shutdown option dialog, it will show, otherwise the system will immediately shutdown. + == AXIS Customization Hints AXIS is a fairly large and difficult-to-penetrate code base, this is helpful From 6fe837107942acd4a37ce9b4fec980f07900e363 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:35:17 -0700 Subject: [PATCH 095/110] gmoccapy -fix some missed warning dialog function signatures --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 3421f122de7..44e0fc0ee7c 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2593,7 +2593,7 @@ def on_offset_col_edit_started(self, widget, filtered_path, new_text, col): if offset == "ERROR": LOG.debug("conversion error") - self.dialogs.warning_dialog(self, _("Conversion error !"), + self.dialogs.show_warning_dialog(_("Conversion error !"), ("Please enter only numerical values\nValues have not been applied")) elif offset == "CANCEL": pass @@ -2882,7 +2882,7 @@ def _show_error(self, error): def on_gremlin_gcode_error(self, widget, errortext): self.gcodeerror = errortext - self.dialogs.warning_dialog(self, _("Important Warning"), errortext) + self.dialogs.show_warning_dialog(_("Important Warning"), errortext) # ========================================================= @@ -3263,7 +3263,7 @@ def on_mdi_calculation_start(self, *args): if value == "ERROR": LOG.debug("conversion error") - self.dialogs.warning_dialog(self, _("Conversion error !"), + self.dialogs.show_warning_dialog(_("Conversion error !"), ("Please enter only numerical values\nValues have not been applied")) elif value == "CANCEL": return @@ -3471,7 +3471,7 @@ def _on_btn_macro_pressed( self, widget = None, data = None ): if parameter == "ERROR": LOG.debug("conversion error") - self.dialogs.warning_dialog(self, _("Conversion error !"), + self.dialogs.show_warning_dialog(_("Conversion error !"), ("Please enter only numerical values\nValues have not been applied")) return elif parameter == "CANCEL": From b7fc91e0b5262989bff0d284e344f79efd99b016 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 20:34:07 -0700 Subject: [PATCH 096/110] gmoccapy -add back dialog destroy buttons TRhey are the same as a negative response --- src/emc/usr_intf/gmoccapy/dialogs.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index 04e8308cf29..90dcb6ef08e 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -81,6 +81,7 @@ def system_dialog(self): dialog = Gtk.Dialog(_("Enter System Unlock Code"), self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) + dialog.connect("delete-event", self.on_delete_event) dialog.set_modal(True) label = Gtk.Label(_("Enter System Unlock Code")) label.modify_font(Pango.FontDescription("sans 20")) @@ -102,7 +103,6 @@ def system_dialog(self): def show_system_dialog(self): dialog = self.sys_dialog dialog._calc.set_value("") - dialog.set_deletable(False) dialog.show_all() self.emit("play_sound", "alert") @@ -135,6 +135,7 @@ def entry_dialog(self): dialog = Gtk.Dialog('', self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) + dialog.connect("delete-event", self.on_delete_event) dialog.label = Gtk.Label('') dialog.label.modify_font(Pango.FontDescription("sans 20")) dialog.label.set_margin_top(15) @@ -163,7 +164,6 @@ def show_entry_dialog(self, data = None, header = _("Enter value") , dialog.calc.num_pad_only(True) dialog.label.set_text(label) dialog.set_title(header) - dialog.set_deletable(False) dialog.show_all() # wait but don't block event loop @@ -200,6 +200,7 @@ def warning_dialog(self): Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE) + dialog.connect("delete-event", self.on_delete_event) ok_button = Gtk.Button.new_with_mnemonic("_Ok") ok_button.set_size_request(-1, 56) ok_button.connect("clicked",lambda w:dialog.response(Gtk.ResponseType.OK)) @@ -229,7 +230,6 @@ def show_warning_dialog(self, title, message, sound=True, dialog = self.warn_dialog dialog.set_title(title) dialog.set_markup(''+message+'') - dialog.set_deletable(False) dialog.confirm_pin = confirm_pin dialog.active_pin = active_pin @@ -254,11 +254,17 @@ def show_warning_dialog(self, title, message, sound=True, def on_warning_response(self, dialog, rtn): dialog.RESPONSE = rtn + def on_delete_event(self, dialog, event): + dialog.RESPONSE = Gtk.ResponseType.CANCEL + #widget.hide() + return True + def yesno_dialog(self): dialog = Gtk.MessageDialog(self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE) + dialog.connect("delete-event", self.on_delete_event) yes_button = Gtk.Button.new_with_mnemonic(_("_Yes")) no_button = Gtk.Button.new_with_mnemonic(_("_No")) yes_button.set_size_request(-1, 56) @@ -280,7 +286,6 @@ def show_yesno_dialog(self, _caller,message, title = _("Operator Message")): dialog.set_markup(message) if title: dialog.set_title(str(title)) - dialog.set_deletable(False) dialog.show_all() self.emit("play_sound", "alert") @@ -307,6 +312,7 @@ def show_user_message(self, message, title = _("Operator Message")): Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE) + dialog.connect("delete-event", self.on_delete_event) if title: dialog.set_title(str(title)) dialog.set_markup(message) @@ -317,7 +323,6 @@ def show_user_message(self, message, title = _("Operator Message")): box.add(ok_button) dialog.action_area.add(box) dialog.set_border_width(5) - dialog.set_deletable(False) dialog.show_all() self.emit("play_sound", "alert") response = dialog.run() From 5b65ea7027ad5a4392100173a95793b62a555df2 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 18 Aug 2026 18:26:13 -0700 Subject: [PATCH 097/110] gmoccapy -fix dialog error when editing tools or offsets --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 44e0fc0ee7c..2f6b1cd22af 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2188,7 +2188,7 @@ def on_tool_col_edit_started(self, widget, filtered_path, new_text, col): if value == "ERROR": LOG.debug("conversion error") - self.dialogs.warning_dialog(_("Conversion error !"), + self.dialogs.show_warning_dialog(_("Conversion error !"), ("Please enter only numerical values\nValues have not been applied")) elif value == "CANCEL": pass @@ -2586,7 +2586,7 @@ def on_offset_col_edit_started(self, widget, filtered_path, new_text, col): row = store_path if self.widgets.offsetpage1.btn_edit_offsets.get_active() or \ self.touch_button_dic["edit_offsets"].get_active(): - value = self.dialogs.show_entry_dialog(data=offsetpage.store[row][col], + offset = self.dialogs.show_entry_dialog(data=offsetpage.store[row][col], header=_("Enter value for offset"), label=f"{offsetpage.store[row][0]} {AXISLIST[col]}-" + _("offset:"), integer=False) From 5a1b33a35862f4684fa8bdcfdddbe5d34dc39512 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 18 Aug 2026 18:28:32 -0700 Subject: [PATCH 098/110] gmoccapy -create a dialog in gmoccapy for system shutdown and make gladevcp action's optional --- lib/python/gladevcp/gtk_action.py | 19 ++++++++++--------- src/emc/usr_intf/gmoccapy/gmoccapy.py | 5 ++++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index f8f6948dbe1..524d51ff361 100644 --- a/lib/python/gladevcp/gtk_action.py +++ b/lib/python/gladevcp/gtk_action.py @@ -617,15 +617,17 @@ def ADJUST_GRAPHICS_ROTATE(self, x, y): #TODO without the gtk dialog, gnome-sessions # does not start reliably - def SHUT_SYSTEM_DOWN_PROMPT(self): + def SHUT_SYSTEM_DOWN_PROMPT(self, prompt=True): import shutil - import time - dialog = YesNoDialog(title='System Shutdown') - dialog.set_keep_above(True) - dialog.format_secondary_text('Unsaved data will be lost') - response = dialog.ask_dialog() - dialog.destroy() - if response == gtk.ResponseType.YES: + if prompt: + dialog = YesNoDialog(title='System Shutdown') + dialog.set_keep_above(True) + dialog.format_secondary_text('Unsaved data will be lost') + response = dialog.ask_dialog() + dialog.destroy() + else: + response = True + if response == True: if shutil.which('gnome-session-quit'): subprocess.run(["gnome-session-quit", "--power-off"]) elif shutil.which('xfce4-session-logout'): @@ -634,7 +636,6 @@ def SHUT_SYSTEM_DOWN_PROMPT(self): # force a shutdown - no prompt subprocess.call('systemctl poweroff', shell=True) - def SHUT_SYSTEM_DOWN_NOW(self): import subprocess subprocess.call('shutdown now') diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 2f6b1cd22af..1a7448acc4d 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2944,7 +2944,10 @@ def on_btn_exit_clicked(self, widget, data=None): self.widgets.window1.destroy() def system_shutdown(self, widget): - self.ACTION.SHUT_SYSTEM_DOWN_PROMPT() + response = self.dialogs.show_yesno_dialog(self, + _('Unsaved data could be lost'), _('System Shutdown')) + if response: + self.ACTION.SHUT_SYSTEM_DOWN_PROMPT(False) # button handlers End # ========================================================= From 9be43cff56620e717c9a958f112b47fad91b90f2 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 18 Aug 2026 18:50:22 -0700 Subject: [PATCH 099/110] gmoccapy -remove halbridge INI entry HALUI code supercedes it --- configs/sim/gmoccapy/gmoccapy_right_panel.ini | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/configs/sim/gmoccapy/gmoccapy_right_panel.ini b/configs/sim/gmoccapy/gmoccapy_right_panel.ini index 8a3b51ed480..f54cd670f40 100644 --- a/configs/sim/gmoccapy/gmoccapy_right_panel.ini +++ b/configs/sim/gmoccapy/gmoccapy_right_panel.ini @@ -74,11 +74,10 @@ HALFILE = simulated_home.hal # Single file that is executed after the GUI has started. POSTGUI_HALFILE = gmoccapy_postgui.hal +[HALUI] HALUI = halui -HALBRIDGE = hal_bridge -d + # Trajectory planner section -------------------------------------------------- -[HALUI] -#No Content [TRAJ] COORDINATES = X Y Z From 21d208b63333754ab3693946f02271fe17814e47 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 18:55:00 -0700 Subject: [PATCH 100/110] gmoccapy -fix request macro function to call macro properly previously it always called ini mdi commands --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 71 ++++++++++++++------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 1a7448acc4d..ad10be8c4a6 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -434,7 +434,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)) + self.GSTAT.connect('macro-call-request', lambda w, key, name: self.request_macro_call(key, 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(Gtk.ResponseType.ACCEPT)) @@ -1354,41 +1354,44 @@ def request_pause(self,data): self.command.auto(linuxcnc.AUTO_PAUSE) # 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(f'MDI command:{cmd} data:{data}') - if not cmd is None: - LOG.debug("INI MDI COMMAND #: {} = {}".format(data, cmd)) - self.ACTION.CALL_INI_MDI(data,mode_return = True) - return - - # run Macros - # some error checking - if not self.GSTAT.is_mdi_mode(): - message = _("You must be in MDI mode to run macros") - self.dialogs.show_warning_dialog( _("Important Warning!"), - message) - return + def request_macro_call(self, data, key): + #print(f'key:{key} data"{data}"') + if key == 'MDI': + # if MDI command change to MDI and run + cmd = self.INFO.get_ini_mdi_command(data) + if not cmd is None: + LOG.debug("INI MDI COMMAND #: {} = {}".format(data, cmd)) + self.ACTION.CALL_INI_MDI(data,mode_return = True) + 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.show_warning_dialog( _("Important Warning!"), - message) + # run Macros + # some error checking + if not self.GSTAT.is_mdi_mode(): + message = _("You must be in MDI mode to run macros") + self.dialogs.show_warning_dialog( _("Important Warning!"), + message) + return + + # look thru the INI macros + macros = self.get_ini_info.get_macros() + macro_name = macros[int(data)] + 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 macro_name == 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.show_warning_dialog( _("Important Warning!"), + message) # check if macros are in the INI file and add them to MDI Button List def _make_macro_button(self): From c1ae477cb6a760525dc1b2205ecdfd52a9e8f712 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 28 Aug 2026 20:29:50 -0700 Subject: [PATCH 101/110] gmoccapy -fix request to run macro with variables names didn't match. In th eend we don;t need to search through the list of macros anyways. --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index ad10be8c4a6..a39cfba2a39 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1373,23 +1373,15 @@ def request_macro_call(self, data, key): message) return - # look thru the INI macros + # get the INI macros macros = self.get_ini_info.get_macros() - macro_name = macros[int(data)] - 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 macro_name == name: - # get the button instance and click it - button = self["button_macro_{0}".format(pos)] - button.emit("clicked") - break - else: + try: + # get the button instance and click it + button = self["button_macro_{0}".format(int(data))] + button.emit("clicked") + except: # didn't match a name - give a hint - message = _("Macro {} not found ".format(data)) + message = _("[MACROS] macro {} not found ".format(data)) self.dialogs.show_warning_dialog( _("Important Warning!"), message) From e1237b26481d22efa1fa7067352a3c83684bc622 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 28 Aug 2026 21:24:48 -0700 Subject: [PATCH 102/110] gmoccapy dialogs: allow setting of message type for yes/no dialog This allows a change of icon. --- src/emc/usr_intf/gmoccapy/dialogs.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index 90dcb6ef08e..ec9210490da 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -281,8 +281,13 @@ def yesno_dialog(self): dialog.connect("response", self.on_yn_response) return dialog - def show_yesno_dialog(self, _caller,message, title = _("Operator Message")): + def show_yesno_dialog(self, _caller, message, + title = _("Operator Message"),icon='QUESTION'): dialog = self.yn_dialog + if icon == 'ERROR': i = Gtk.MessageType.ERROR + elif icon == 'WARNING': i = Gtk.MessageType.WARNING + else: i = Gtk.MessageType.QUESTION + dialog.set_property("message-type", i) dialog.set_markup(message) if title: dialog.set_title(str(title)) From dcbf30dd78ff0cace180d2f8cd6c39be2f5ed9c2 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 28 Aug 2026 21:25:44 -0700 Subject: [PATCH 103/110] gmoccapy -change system shutdown dialog message add a warning icon and 'Are you Sure' text to get attention --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index a39cfba2a39..f9c433666f2 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2939,8 +2939,9 @@ def on_btn_exit_clicked(self, widget, data=None): self.widgets.window1.destroy() def system_shutdown(self, widget): + msg = _('Unsaved Data Will Be Lost.\n Are You Sure ?') response = self.dialogs.show_yesno_dialog(self, - _('Unsaved data could be lost'), _('System Shutdown')) + msg , _('Warning: System Shutdown'),'WARNING') if response: self.ACTION.SHUT_SYSTEM_DOWN_PROMPT(False) From b94251bb5cd42387d2c8cf06227b9774521b95fc Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:50:07 -0700 Subject: [PATCH 104/110] gmoccapy -sim: name change for gui.mpg-aux.0 --- configs/sim/gmoccapy/panel-4axis.hal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal index 0dd30d02c33..633cf3e504d 100644 --- a/configs/sim/gmoccapy/panel-4axis.hal +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -10,7 +10,7 @@ net sx axis.x.jog-enable net sy axis.y.jog-enable net sz axis.z.jog-enable net sc axis.c.jog-enable -net sgui halui.gui.mpg-select.0 panel.select-gui0 +net sgui halui.gui.mpg-aux.0 panel.select-gui0 net jog-p halui.axis.selected.plus panel.jog-pos net jog-m halui.axis.selected.minus panel.jog-neg From 0924b0ce17662886708d0a7841e58cf0c3469177 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 18:58:01 -0700 Subject: [PATCH 105/110] gmoccapy -sims: change panel/ini to run mdi and macros for testing --- configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini | 4 ---- configs/sim/gmoccapy/panel-4axis.hal | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini index f722d2e6654..70f85349600 100644 --- a/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini +++ b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini @@ -78,10 +78,6 @@ HALUI = halui # 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 C diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal index 633cf3e504d..d00a5365e05 100644 --- a/configs/sim/gmoccapy/panel-4axis.hal +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -29,7 +29,7 @@ net mpg-count axis.c.jog-counts 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 m2 halui.gui.mdi-command.1 panel.mdi-2 net man panel.manual-mode halui.mode.manual net mdi panel.mdi-mode halui.mode.mdi From 9892a6ba8d4269991a49d94dd4269ed749d232ed Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 28 Aug 2026 20:31:25 -0700 Subject: [PATCH 106/110] gmoccapy sim: switch INI mdi to use a G53 to exclude running with G91 a plain G0 command will be different in G90/91 modes. If we use G53, it will error if in G91 mode. --- configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini index 70f85349600..d1a135566f0 100644 --- a/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini +++ b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini @@ -76,7 +76,7 @@ HALUI = 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_MACRO0 = G53 G0 Z0;G0 X0 Y0;Z0, Goto\nUser\nZero MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero [TRAJ] From b0d3b51448f2fb3b8be3d32e2d69ee40760edf55 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:50:48 -0700 Subject: [PATCH 107/110] qtdragon -sim: name change for gui.mpg-aux --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index eb1b4046f5d..0463a1df20a 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -6,7 +6,7 @@ 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 sgui halui.gui.mpg-select.0 panel.select-gui0 +net sgui halui.gui.mpg-aux.0 panel.select-gui0 net mpg-scale axis.x.jog-scale panel.mpg-scale From 1a837999c2f82e3adfa0fb0129af6d08c96d5766 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:52:56 -0700 Subject: [PATCH 108/110] qtdragon -docs: update - update info about halui gui pins --- docs/src/gui/qtdragon.adoc | 166 ++++++++++++++++++++++++++++--------- 1 file changed, 128 insertions(+), 38 deletions(-) diff --git a/docs/src/gui/qtdragon.adoc b/docs/src/gui/qtdragon.adoc index 1281916c206..884bbacd670 100644 --- a/docs/src/gui/qtdragon.adoc +++ b/docs/src/gui/qtdragon.adoc @@ -465,6 +465,7 @@ POSTGUI_HALCMD = loadusr halmeter ---- === HALUI HALUI will create halui.gui pins that interface with QtDragon. +This could be used to connect a control panel that interacts better with QtDragon. - There is a cycle start and pause pin - these call the code in QtDragon rather then the motion controller. + This allows custom behaviour, such as spindle lift to work with external buttons. @@ -475,54 +476,143 @@ setting preferences on the settings page. - shutdown the screen - ok/cancel of dialogs and notify (error) messages - softkeys 0 - 11 control the main tab keys: + -'MAIN','FILE','OFFSETS','TOOL','STATUS','PROBE','GCODES','SETUP','SETTINGS','UTILITIES','USER','CAMERA' - -also: - -- enabling halui.gui.mpg-select.0, will allow zooming/scrolling of the gcode plot or text display with an MPG. +- enabling halui.gui.mpg-aux.0, will allow panning/scrolling of the gcode plot or text display with an MPG. - HALUI selected axis will be reported to QtDragon. + - HALUI selected jog rates/increments will be reported to QtDragon. + In either case the last changed rate/axis (either HALUI or QtDragon) will be used for screen button jogging or HALUI pin jogging. -.Typical HAL pins avaialble: +=== Typical HAL pins avaialble: ---- Component Pins: Owner Type Dir Value Name - 25 bit IN FALSE halui.gui.cancel - 25 bit IN FALSE halui.gui.cycle.pause - 25 bit IN FALSE halui.gui.cycle.start - 25 bit IN FALSE halui.gui.mdi-command-MACRO0 - 25 bit IN FALSE halui.gui.mdi-command-MACRO1 - 25 bit IN FALSE halui.gui.mdi-command-MACRO2 - 25 bit IN FALSE halui.gui.mdi-command-MACRO3 - 25 bit IN FALSE halui.gui.mdi-command-MACRO4 - 25 bit IN FALSE halui.gui.mdi-command-MACRO5 - 25 bit IN FALSE halui.gui.mpg-select.0 - 25 bit IN FALSE halui.gui.ok - 25 bit IN FALSE halui.gui.reload-display + 25 bit IN FALSE halui.gui.cycle-pause + 25 bit IN FALSE halui.gui.cycle-start + 25 bit IN FALSE halui.gui.mdi-command.MACRO0 + 25 bit IN FALSE halui.gui.mdi-command.MACRO1 + 25 bit IN FALSE halui.gui.mdi-command.MACRO2 + 25 bit IN FALSE halui.gui.mdi-command.MACRO3 + 25 bit IN FALSE halui.gui.mdi-command.0 + 25 bit IN FALSE halui.gui.mdi-command.1 + 25 bit IN FALSE halui.gui.mpg-aux.0 + 25 bit IN FALSE halui.gui.reload-preview + 25 bit IN FALSE halui.gui.response.cancel + 25 bit IN FALSE halui.gui.response.ok 25 bit IN FALSE halui.gui.shutdown - 25 bit IN FALSE halui.gui.softkey-00 - 25 bit IN FALSE halui.gui.softkey-01 - 25 bit IN FALSE halui.gui.softkey-02 - 25 bit IN FALSE halui.gui.softkey-03 - 25 bit IN FALSE halui.gui.softkey-04 - 25 bit IN FALSE halui.gui.softkey-05 - 25 bit IN FALSE halui.gui.softkey-06 - 25 bit IN FALSE halui.gui.softkey-07 - 25 bit IN FALSE halui.gui.softkey-08 - 25 bit IN FALSE halui.gui.softkey-09 - 25 bit IN FALSE halui.gui.softkey-10 - 25 bit IN FALSE halui.gui.softkey-11 - 25 bit IN FALSE halui.gui.softkey-12 - 25 bit IN FALSE halui.gui.softkey-13 - 25 bit IN FALSE halui.gui.softkey-14 - 25 bit IN FALSE halui.gui.softkey-15 - 25 bit IN FALSE halui.gui.softkey-16 - 25 bit IN FALSE halui.gui.softkey-17 - 25 bit IN FALSE halui.gui.softkey-18 - 25 bit IN FALSE halui.gui.softkey-19 + 25 bit IN FALSE halui.gui.softkey.00 + 25 bit IN FALSE halui.gui.softkey.01 + 25 bit IN FALSE halui.gui.softkey.02 + 25 bit IN FALSE halui.gui.softkey.03 + 25 bit IN FALSE halui.gui.softkey.04 + 25 bit IN FALSE halui.gui.softkey.05 + 25 bit IN FALSE halui.gui.softkey.06 + 25 bit IN FALSE halui.gui.softkey.07 + 25 bit IN FALSE halui.gui.softkey.08 + 25 bit IN FALSE halui.gui.softkey.09 + 25 bit IN FALSE halui.gui.softkey.10 + 25 bit IN FALSE halui.gui.softkey.11 + 25 bit IN FALSE halui.gui.softkey.12 + 25 bit IN FALSE halui.gui.softkey.13 + 25 bit IN FALSE halui.gui.softkey.14 + 25 bit IN FALSE halui.gui.softkey.15 + 25 bit IN FALSE halui.gui.softkey.16 + 25 bit IN FALSE halui.gui.softkey.17 + 25 bit IN FALSE halui.gui.softkey.18 + 25 bit IN FALSE halui.gui.softkey.19 + +---- +=== HALUI GUI Cycle Start +The halui.gui.cycle-start pin will request QtDragon to start a cycle. + +QtDragon will decide what will happen by what mode it's in. + +In Auto mode and idle, a loaded gcode program will run. + +If In Auto mode, idle, and a line besides 0 is selected, start program at line. + +If in Auto mode and paused, a loaded gcode program will resume. + +In MDI mode, the command in QtDragon's MDI line will run. + + +=== HALUI GUI Macros +One can initiate GMOCCAPY styled Macros from HALUI pins. + +These would be the macros found in the INI under the heading [MACROS] + +These call Oword subroutines of the same name. + +These Oword subroutines can require variables supplied at runtime by the user. + +Here is an example entry: +[source,ini] ---- +[MACROS] +MACRO = i_am_lost +MACRO = halo_world +MACRO = increment xinc yinc +---- + +And here are what the pin names would be: + +For 'i_am_lost': halui.gui.mdi-command.0 + +For 'halo_world': halui.gui.mdi-command.1 + +For 'increment': halui.gui.mdi-command.2 + + +The above example 'increment', will prompt the user for two values 'xinc' and 'yinc' when run. + + +[NOTE] +All defined Oword files must be located on the system as defined in the +[DISPLAY]PROGRAM_PREFIX or [RS274NGC]SUBROUTINE_PATH INI entries. + +=== HALUI INI MDI Commands +One can initiate MDI Command Macros from HALUI pins. + +MDI commands are defined under the heading [MDI_COMMAND_LIST] as + +MDI_COMMAND_MACRO0 = gcode + +These could also call OWord routines if desired. + +Commands separated by the ';' are run one after another. + +Usually best for short convenience positioning as at runtime user entry is not available. + +[source,ini] +---- +[MDI_COMMAND_LIST] +MDI_COMMAND_MACRO0 = G53 G0 Z0;G0 X0 Y0;Z0 +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0 +---- + +And here are what the pin names would be: + +For 'MACRO0': halui.gui.mdi-command.MACRO0 + +For 'MACRO1': halui.gui.mdi-command.MACRO1 + + +In this example: + +'MACRO0' is used to move to the current user system origin (zero point). + +The sequence will run like this: move Z axis up first in G53 (machine coordinate system) mode. + +Then move to X0 and Y0 in the current user system, and finally move the Z axis to 0 in the current user system. + +'MACRO1' is used to move to the machine system origin. + +The sequence will run like this: move Z axis up first in G53 (machine coordinate system) mode. + +Then move to X0 and Y0 in the G53 mode. + +=== Soft Keys +Softkeys 0 - 11 control QtDragon's main tab keys: + + +halui.gui.softkey.00 = 'MAIN' + +halui.gui.softkey.01 = 'FILE' + +halui.gui.softkey.02 = 'OFFSETS' + +halui.gui.softkey.03 = 'TOOL' + +halui.gui.softkey.04 = 'STATUS' + +halui.gui.softkey.05 = 'PROBE' + +halui.gui.softkey.06 = 'GCODES' + +halui.gui.softkey.07 = 'SETUP' + +halui.gui.softkey.08 = 'SETTINGS' + +halui.gui.softkey.09 = 'UTILITIES' + +halui.gui.softkey.10 = 'USER' + +halui.gui.softkey.11 = 'CAMERA' + + +=== MPG-Aux +Enabling halui.gui.mpg-aux.0, will allow adjustment/scrolling of the selected widgets with the qtdragon.mpg-in pin, typically connected to an MPG encoder count pin. + +Defaults to panning of the gcode plot. + +=== Shutdown +If halui.gui.shutdown is set true, the normal confirmation dialog will display. + +If 'Shutdown' is selected, the whole linux system will shutdown. + +If the system supports a builtin shutdown option dialog, it will show, otherwise the system will immediately shutdown. + +If 'Yes' is selected just linuxcnc will shutdown. + +'No' will close the dialog and return you back to QtDragon. + +A timer can be set to automatically shut down linuxcnc after a set timeout. + [[sec:bridge]] === HAL Bridge Hal Bridge is similar to HALUI - it has HAL pins that communicate with QtDragon. + From 789c9c0711649db3c73aa52f434bce845a4b796d Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:53:16 -0700 Subject: [PATCH 109/110] gmoccapy -docs: update - add info about halui mdi/macro commands --- docs/src/gui/gmoccapy.adoc | 165 ++++++++++++++++++++++++++----------- 1 file changed, 119 insertions(+), 46 deletions(-) diff --git a/docs/src/gui/gmoccapy.adoc b/docs/src/gui/gmoccapy.adoc index c49e30bfb96..d2c6901dc4e 100644 --- a/docs/src/gui/gmoccapy.adoc +++ b/docs/src/gui/gmoccapy.adoc @@ -936,63 +936,136 @@ Please note, that GMOCCAPY takes care of its own to update the offsets, sending So writing a program makes you responsible to include an G43 after each tool change! == HALUI -HALUI will create halui.gui pins that interface with Gmoccapy. +HALUI will create halui.gui pins that interface with GMOCCAPY. + +This could be used to connect a control panel that interacts better with GMOCCAPY. -- There is a cycle start and pause pin - these call the code in Gmoccapy rather then the motion controller. ie if in MDI mode the MDI command will be run with a cycle start.+ -- If there are macros defined in the INI there will be (up to 64) pins available to initiate them. + +- There is a cycle start and pause pin - these call the code in GMOCCAPY rather then the motion controller. + +- If there are macros/mdi commands defined in the INI there will be (up to 64) pins available to initiate them. + - clear/reload the display - shutdown the linux system - ok/cancel of dialogs and notify (error) messages -- softkeys 0 - 11 control the main tab keys: + -halui.gui.softkey-00 - halui.gui.softkey-06 are the vertical keys. + -halui.gui.softkey-10 - halui.gui.softkey-09 are the horizontal keys. +- HALUI selected axis will be reported to GMOCCAPY. + +- HALUI selected jog rate will be reported to GMOCCAPY. + +In either case the last changed rate/axis (either HALUI or GMOCCAPY) will be used for screen button jogging or HALUI pin jogging. +- Zoom the gcode display with an MPG -also: - -- enabling halui.gui.mpg-select.0, will allow zooming of the gcode plot with the gmoccapy.mpg-in pin. -- HALUI selected Axis will be reported to Gmoccapy. + -- HALUI selected jog rate will be reported to Gmoccapy. + -In either case the last changed rate/axis (either HALUI or Gmoccapy) will be used for screen button jogging or HALUI pin jogging. - -.Typical HAL pins avaialble: +=== Typical HAL pins available: ---- Component Pins: Owner Type Dir Value Name - 25 bit IN FALSE halui.gui.cancel - 25 bit IN FALSE halui.gui.cycle.pause - 25 bit IN FALSE halui.gui.cycle.start - 25 bit IN FALSE halui.gui.mdi-command-MACRO0 - 25 bit IN FALSE halui.gui.mdi-command-MACRO1 - 25 bit IN FALSE halui.gui.mdi-command-MACRO2 - 25 bit IN FALSE halui.gui.mdi-command-MACRO3 - 25 bit IN FALSE halui.gui.mdi-command-MACRO4 - 25 bit IN FALSE halui.gui.mdi-command-MACRO5 - 25 bit IN FALSE halui.gui.mpg-select.0 - 25 bit IN FALSE halui.gui.ok - 25 bit IN FALSE halui.gui.reload-display + 25 bit IN FALSE halui.gui.cycle-pause + 25 bit IN FALSE halui.gui.cycle-start + 25 bit IN FALSE halui.gui.mdi-command.MACRO0 + 25 bit IN FALSE halui.gui.mdi-command.MACRO1 + 25 bit IN FALSE halui.gui.mdi-command.MACRO2 + 25 bit IN FALSE halui.gui.mdi-command.MACRO3 + 25 bit IN FALSE halui.gui.mdi-command.0 + 25 bit IN FALSE halui.gui.mdi-command.1 + 25 bit IN FALSE halui.gui.mpg-aux.0 + 25 bit IN FALSE halui.gui.reload-preview + 25 bit IN FALSE halui.gui.response.cancel + 25 bit IN FALSE halui.gui.response.ok 25 bit IN FALSE halui.gui.shutdown - 25 bit IN FALSE halui.gui.softkey-00 - 25 bit IN FALSE halui.gui.softkey-01 - 25 bit IN FALSE halui.gui.softkey-02 - 25 bit IN FALSE halui.gui.softkey-03 - 25 bit IN FALSE halui.gui.softkey-04 - 25 bit IN FALSE halui.gui.softkey-05 - 25 bit IN FALSE halui.gui.softkey-06 - 25 bit IN FALSE halui.gui.softkey-07 - 25 bit IN FALSE halui.gui.softkey-08 - 25 bit IN FALSE halui.gui.softkey-09 - 25 bit IN FALSE halui.gui.softkey-10 - 25 bit IN FALSE halui.gui.softkey-11 - 25 bit IN FALSE halui.gui.softkey-12 - 25 bit IN FALSE halui.gui.softkey-13 - 25 bit IN FALSE halui.gui.softkey-14 - 25 bit IN FALSE halui.gui.softkey-15 - 25 bit IN FALSE halui.gui.softkey-16 - 25 bit IN FALSE halui.gui.softkey-17 - 25 bit IN FALSE halui.gui.softkey-18 - 25 bit IN FALSE halui.gui.softkey-19 + 25 bit IN FALSE halui.gui.softkey.00 + 25 bit IN FALSE halui.gui.softkey.01 + 25 bit IN FALSE halui.gui.softkey.02 + 25 bit IN FALSE halui.gui.softkey.03 + 25 bit IN FALSE halui.gui.softkey.04 + 25 bit IN FALSE halui.gui.softkey.05 + 25 bit IN FALSE halui.gui.softkey.06 + 25 bit IN FALSE halui.gui.softkey.07 + 25 bit IN FALSE halui.gui.softkey.08 + 25 bit IN FALSE halui.gui.softkey.09 + 25 bit IN FALSE halui.gui.softkey.10 + 25 bit IN FALSE halui.gui.softkey.11 + 25 bit IN FALSE halui.gui.softkey.12 + 25 bit IN FALSE halui.gui.softkey.13 + 25 bit IN FALSE halui.gui.softkey.14 + 25 bit IN FALSE halui.gui.softkey.15 + 25 bit IN FALSE halui.gui.softkey.16 + 25 bit IN FALSE halui.gui.softkey.17 + 25 bit IN FALSE halui.gui.softkey.18 + 25 bit IN FALSE halui.gui.softkey.19 + +---- + +=== HALUI GUI Cycle Start +The halui.gui.cycle-start pin will request GMOCCAPY to start a cycle. + +GMOCCAPY will decide what will happen by what mode it's in. + +In Auto mode, a loaded gcode program will run. + +In MDI mode, the command in GMOCCAPY's MDI line will run. + +=== HALUI GUI Macros +One can initiate GMOCCAPY styled Macros from HALUI pins. + +These would be the macros found in the INI under the heading [MACROS] + +These call Oword subroutines of the same name. + +These Oword subroutines can require variables supplied at runtime by the user. + +Here is an example entry: +[source,ini] +---- +[MACROS] +MACRO = i_am_lost +MACRO = halo_world +MACRO = increment xinc yinc ---- + +And here are what the pin names would be: + +For 'i_am_lost': halui.gui.mdi-command.0 + +For 'halo_world': halui.gui.mdi-command.1 + +For 'increment': halui.gui.mdi-command.2 + + +The above example 'increment', will prompt the user for two values 'xinc' and 'yinc' when run. + + +[NOTE] +All defined Oword files must be located on the system as defined in the +[DISPLAY]PROGRAM_PREFIX or [RS274NGC]SUBROUTINE_PATH INI entries. + +=== HALUI INI MDI Commands +One can initiate MDI Command Macros from HALUI pins. + +MDI commands are defined under the heading [MDI_COMMAND_LIST] as + +MDI_COMMAND_MACRO0 = gcode + +These could also call OWord routines if desired. + +Commands separated by the ';' are run one after another. + +Usually best for short convenience positioning as at runtime user entry is not available. + +[source,ini] +---- +[MDI_COMMAND_LIST] +MDI_COMMAND_MACRO0 = G53 G0 Z0;G0 X0 Y0;Z0 +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0 +---- + +And here are what the pin names would be: + +For 'MACRO0': halui.gui.mdi-command.MACRO0 + +For 'MACRO1': halui.gui.mdi-command.MACRO1 + + +In this example: + +'MACRO0' is used to move to the current user system origin (zero point). + +The sequence will run like this: move Z axis up first in G53 (machine coordinate system) mode. + +Then move to X0 and Y0 in the current user system, and finally move the Z axis to 0 in the current user system. + +'MACRO1' is used to move to the machine system origin. + +The sequence will run like this: move Z axis up first in G53 (machine coordinate system) mode. + +Then move to X0 and Y0 in the G53 mode. + +=== Soft Keys +Softkeys 0 - 11 control GMOCCAPY's main tab keys: + +halui.gui.softkey.00 - halui.gui.softkey.06 are the vertical keys. + +halui.gui.softkey.10 - halui.gui.softkey.09 are the horizontal keys. + +=== MPG-Aux +Enabling halui.gui.mpg-aux.0, will allow zooming of the gcode plot with the gmoccapy.mpg-in pin, typically connected to an MPG encoder count pin. + +=== Shutdown +If halui.gui.shutdown is set true, a confirmation dialog will display. + +If confirmed, the whole linux system will shutdown. + +If the system supports a builtin shutdown option dialog, it will show, otherwise the system will immediately shutdown. + + [[gmoccapy:auto-tool-measurement]] == Auto Tool Measurement From 53552c19d67c6dad4373b96ba307c09b838f882b Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:53:35 -0700 Subject: [PATCH 110/110] halui -docs: update --- docs/src/gui/halui.adoc | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/src/gui/halui.adoc b/docs/src/gui/halui.adoc index dc2895a1b83..bc5798e314a 100644 --- a/docs/src/gui/halui.adoc +++ b/docs/src/gui/halui.adoc @@ -121,18 +121,19 @@ Or see http://linuxcnc.org/docs/devel/html/man/man1/halui.1.html These are pins used to 'request' a function to run in a screen. + What these exactly do is dependant on the (GUI) screen used. -* 'halui.gui.cycle.start' (bit, in) - pin for requesting the screen to run a cycle -* 'halui.gui.cycle.pause' (bit, in) - pin for requesting the screen to pause a cycle -* 'halui.gui.cancel' (bit, in) - pin for cancel/closing dialogs -* 'halui.gui.ok' (bit, in) - pin for ok/apply dialogs -* 'halui.gui.mdi-command-MACRO__' (bit, in) - pin for calling mdi/macro commands +* 'halui.gui.cycle-start' (bit, in) - pin for requesting the screen to run a cycle +* 'halui.gui.cycle-pause' (bit, in) - pin for requesting the screen to pause a cycle +* 'halui.gui.response.cancel' (bit, in) - pin for cancel/closing dialogs +* 'halui.gui.response.ok' (bit, in) - pin for ok/apply dialogs +* 'halui.gui.mdi-command.MACRO__' (bit, in) - pin for calling mdi commands +* 'halui.gui.mdi-command.__' (bit, in) - pin for calling macro commands __ is a one or two digit number starting from 0 to 63 + There can be up to 64 commands defined in the INI under the [MDI_COMMAND_LIST] or [MACROS] headings. + -* 'halui.gui.reload-display' (bit, in) - pin for reloading the screen plot +* 'halui.gui.reload-preview' (bit, in) - pin for reloading the screen plot * 'halui.gui.shutdown' (bit, in) - pin for shutting down linuxcnc or system -* 'halui.gui.softkey__' (bit, in) - pins for screen defined functions +* 'halui.gui.softkey.__' (bit, in) - pins for screen defined functions __ is a two digit number starting from 00 to 19 + -* 'halui.gui.mpg-select.0' (bit, in) - pin for selecting MPG based control of the screen, such as scrolling +* 'halui.gui.mpg-aux.0' (bit, in) - pin for selecting MPG based control of the screen, such as scrolling === Mist