From 971fb46232c64831dcf4f86e5afa58e0cd29603f Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Sep 2025 22:09:20 -0700 Subject: [PATCH 01/76] 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 54c9817b2ba84e5dcd5d389d1914f634fe04ffb6 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 8 Jul 2026 23:03:57 -0700 Subject: [PATCH 02/76] 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 c802e9c1ca69c4a8440f3af0549c1f03e3b54f93 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 14 Jul 2026 21:34:09 -0700 Subject: [PATCH 03/76] 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 8a259901be3c99c938c9acc42aac5b5272739fc4 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 17:45:44 -0800 Subject: [PATCH 04/76] 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 00e0446d9bd85b6da86f0dda33abeed9d533da3c Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 7 Feb 2026 16:39:36 -0800 Subject: [PATCH 05/76] 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 cb6be33b33ab01903399c0c60542ed094eb08c43 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 16 Aug 2025 11:21:10 -0700 Subject: [PATCH 06/76] 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. bridge -look for macro commands too MDI and macro commands are similar enough to combine --- .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 2 +- lib/python/bridgeui/__init__.py | 3 + lib/python/bridgeui/bridge.py | 277 ++++++++++ src/Makefile | 2 + src/emc/usr_intf/Submakefile | 3 +- src/emc/usr_intf/halui.cc | 478 ++++++++++++++++-- 6 files changed, 733 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 c246e1d6845..449821c6dda 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..e462a7eef90 --- /dev/null +++ b/lib/python/bridgeui/bridge.py @@ -0,0 +1,277 @@ +#!/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 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) + + # 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.MACRO_COMMAND_DICT.keys())[num] + LOG.debug('MACRO:{} {}'.format(num,temp)) + 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) + + # 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),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 + 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) + + # XYZABCUVW, None, or MPG0 + 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/Makefile b/src/Makefile index 2c56a18ed3a..f97242cea7a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -778,6 +778,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 @@ -803,6 +804,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 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..87234041a47 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.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")); @@ -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 e7772126e22126eb51a89eebcfc80394bbbda564 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 10 Apr 2026 13:38:30 -0700 Subject: [PATCH 07/76] 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 87234041a47..8a08e682926 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 cdbdb2b1577a0f515951bd81f785d1751c68f62e Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 12 Apr 2026 10:15:33 -0700 Subject: [PATCH 08/76] 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 e462a7eef90..c9d8d4aac4b 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -236,6 +236,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 8a08e682926..17df0e5f93c 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 6de1692d95d1d45bc5e20f46fa2dada0b1150e95 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 2 Jul 2026 19:51:13 -0700 Subject: [PATCH 09/76] 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 267ee41a4d44f49a51301779a57838a614fed1f7 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 14:11:26 -0700 Subject: [PATCH 10/76] 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 17df0e5f93c..f2a93406cb6 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 3027f806cc17135a60399f57d041a339bc17fe33 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 14 Jul 2026 21:36:44 -0700 Subject: [PATCH 11/76] 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 f2a93406cb6..7d9617d3802 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 5ca7b812c71717e63050ca58e969306b3b9ff69a Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 18 Jul 2026 21:51:46 -0700 Subject: [PATCH 12/76] 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 7d9617d3802..edb7a8bed29 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 5eedf9e234afa363406b3fc25fbbebd2505e2562 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 10 Aug 2026 20:25:12 -0700 Subject: [PATCH 13/76] 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 3e174c709279a73e6c03de8dc332960efc7c58df Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 10 Aug 2026 20:04:40 -0700 Subject: [PATCH 14/76] 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 c8c76a6924b1c7c9c5127b73aa238ca30f1d46f5 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 19:15:38 -0700 Subject: [PATCH 15/76] 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 e7d794021fdf23cb2d6e30c9295be7239588b571 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 15 Aug 2026 05:35:26 -0700 Subject: [PATCH 16/76] 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 a1bfafbe863864c05fadaf09a4e94fd71209e0f0 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:36:03 -0700 Subject: [PATCH 17/76] 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 06fbcf267c6da51420379b04036b2695924ef9b9 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 21 Aug 2026 09:29:10 -0700 Subject: [PATCH 18/76] 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 7c59adb8b80a94e916001569eed0bd5ff761855a Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 18:39:12 -0700 Subject: [PATCH 19/76] 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 fed260d97180404d02d7b4ae2f1390480f410870 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 18:46:25 -0700 Subject: [PATCH 20/76] 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 3811656968685b04f6e349e7c643a8afaba6cdb9 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 13 Aug 2026 18:43:01 -0700 Subject: [PATCH 21/76] 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 4136366c612b7a1430f922a47d5ab8e22e35b2ea Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sun, 19 Jul 2026 21:28:54 -0700 Subject: [PATCH 22/76] halui - add docs for halui zmq messages and pins --- docs/src/gui/halui.adoc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/src/gui/halui.adoc b/docs/src/gui/halui.adoc index 37d92b8ff73..bc5798e314a 100644 --- a/docs/src/gui/halui.adoc +++ b/docs/src/gui/halui.adoc @@ -117,6 +117,24 @@ 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.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-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 +__ is a two digit number starting from 00 to 19 + +* 'halui.gui.mpg-aux.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 31daa6e42ce6bebd086e446dbf9394479f505468 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 16 Aug 2025 11:12:19 -0700 Subject: [PATCH 23/76] gladevcp -speedcontrol: add angular jograte type and command and Gstat -Gstat sends out socket messages for jog rate -automatically sends and responds to jograte messages -quiet print debugs -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 | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index 332f025ab0d..c9c6d95149d 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, Info + 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 for angular jograte', + -1, 1, -1, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT), } __gproperties = __gproperties__ @@ -112,6 +116,10 @@ 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() + self._info = Info() + # basic settings self._size = size self._value = value @@ -123,6 +131,8 @@ 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.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) @@ -175,6 +185,11 @@ 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: + 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)) + # this draws our widget on the screen def expose(self, widget, event): # create the cairo window @@ -250,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) @@ -269,6 +292,17 @@ def get_value(self): # we are not sync, so def _on_value_changed(self, widget): value = widget.get_value() + + if self.type_linear_jog: + # 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) + if value != self._value: self._value = value self.set_value(self._value) @@ -453,6 +487,13 @@ def do_set_property(self, property, value): self._template = value if name == "do_hide_button": self.hide_button(value) + if name == "type": + 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 065a3229581967b33e7521b9034ac68679b79619 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:05:52 -0700 Subject: [PATCH 24/76] 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 1ae322446188eb40edbd50fcddf934dd6be84846 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 18:10:59 -0700 Subject: [PATCH 25/76] 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 f244726e698780c15279776352911f33d67ee0bb Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 14:12:15 -0700 Subject: [PATCH 26/76] 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 03196116611..e54e78b0c52 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 d29124781f319e8d6981e5242cf1e1a92ff3aad8 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 10:20:22 -0700 Subject: [PATCH 27/76] gladevcp -gtk_action :get shutdown to work reliably gnome-sessions-quit wil not pop up consistantly without a short sleep. Dialog is also optional --- lib/python/gladevcp/gtk_action.py | 91 +++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 16 deletions(-) diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index ae56852891a..e69c6187dff 100644 --- a/lib/python/gladevcp/gtk_action.py +++ b/lib/python/gladevcp/gtk_action.py @@ -1,5 +1,6 @@ import os import subprocess +from time import sleep import gi gi.require_version("Gtk","3.0") @@ -220,7 +221,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) @@ -616,21 +616,28 @@ 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}) - 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)) + #TODO without the gtk dialog, gnome-sessions + # does not start reliably + def SHUT_SYSTEM_DOWN_PROMPT(self, prompt=True): + 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 + import shutil + # sleep make gnome-session-quit reliable + sleep(.1) + if response == True: + 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 +934,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 86424e5afb2cf71ca4e17862a413c319458b8f1a Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:07:38 -0700 Subject: [PATCH 28/76] 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 dbc345d3b4ecee7858465ad8fb0441972a4bb40e Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 13 Dec 2025 20:42:40 -0800 Subject: [PATCH 29/76] 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 be9becee2da41a6fea465306e95898dede48d642 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 27 May 2026 19:04:21 -0700 Subject: [PATCH 30/76] 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 0d16fb98d9851c7805a0a82e368aeaac1abb90ad Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 8 Jul 2026 23:06:07 -0700 Subject: [PATCH 31/76] 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 22f2094da26a1f82cdd1ca5c4a3c7a982167578c Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 29 Jul 2026 16:21:41 -0700 Subject: [PATCH 32/76] 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 61f624215513479ce546939590b7733289502875 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 15 Aug 2026 05:36:39 -0700 Subject: [PATCH 33/76] 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 11f7603390fe68be165a55d067a84d9e30d6186f Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 9 Sep 2026 14:07:17 -0700 Subject: [PATCH 34/76] qtvcp -allow setting of window title from designer mostly for simpole VCPs --- src/emc/usr_intf/qtvcp/qtvcp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/emc/usr_intf/qtvcp/qtvcp.py b/src/emc/usr_intf/qtvcp/qtvcp.py index 40c7dff2212..6b1223cddb6 100644 --- a/src/emc/usr_intf/qtvcp/qtvcp.py +++ b/src/emc/usr_intf/qtvcp/qtvcp.py @@ -338,7 +338,9 @@ def _handle_quit_signal(signum, frame): title ='QTvcp-Screen-%s'% opts.component else: title = 'QTvcp-Panel-%s'% opts.component - window.setWindowTitle(title) + print('****',window.windowTitle()) + if 'MainWindow' in window.windowTitle(): + window.setWindowTitle(title) if opts.usermod and "pre_hal_init__" in dir(window.handler_instance): LOG.debug('''Calling the handler file's pre_hal_init__ function''') From b88ceab9acba554554a188c89f6868dcfe577e94 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:04:00 -0700 Subject: [PATCH 35/76] common/iniinfo -parse ini commands in a better way mdi commands with a comma in it (ie MSG, text) would not be interpeted properly iniinfo -fix search for nth INI MDI command ie search by integer --- lib/python/common/iniinfo.py | 68 +++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/lib/python/common/iniinfo.py b/lib/python/common/iniinfo.py index 57b78707bd7..653a4c1eaf9 100644 --- a/lib/python/common/iniinfo.py +++ b/lib/python/common/iniinfo.py @@ -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 # @@ -913,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 c97d6ec383178e2957f61280c9fa04661655fb84 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 22 Aug 2025 15:33:19 -0700 Subject: [PATCH 36/76] 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 60b0811a8a169e00afacec8a7a35352dcf44f689 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 12 Dec 2025 20:15:21 -0800 Subject: [PATCH 37/76] 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 0a8a941703e26dbf4d309f6092470867c9cebb33 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 13 Dec 2025 20:46:51 -0800 Subject: [PATCH 38/76] 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 c483e3a26cf439c45357363f6b204f037c6b7900 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 14 Mar 2026 19:03:05 -0700 Subject: [PATCH 39/76] 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 824bc61a51f1dabd2307810c72c0a49acfa1df12 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 2 Jul 2026 19:49:35 -0700 Subject: [PATCH 40/76] 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 3ecf40d3c241e7708f67da1b02950f2af09d5ab6 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 20 Jul 2026 21:00:13 -0700 Subject: [PATCH 41/76] 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 dc981852c7565602187e12fc116ae8526dfcb2b0 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 19:14:51 -0700 Subject: [PATCH 42/76] 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 da570613fb4404da0303bc01678d60d3ae7ca2d4 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 19:00:37 -0700 Subject: [PATCH 43/76] 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 9a2ccfaf5e9dbd7a472827a08049fb8396525ab9 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 23 Aug 2025 13:57:34 -0700 Subject: [PATCH 44/76] 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/simulated_control_panel.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/simulated_control_panel.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..1d1f8628816 --- /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-preview 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..7eb38aba218 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -0,0 +1,1327 @@ + + + MainWindow + + + + 0 + 0 + 588 + 706 + + + + Simulated Control Panel + + + + + + + + + + + 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/simulated_control_panel.ini b/configs/sim/qtdragon/qtdragon_xyz/simulated_control_panel.ini new file mode 100644 index 00000000000..5efd17e4785 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/simulated_control_panel.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 7d82c3fb0971b446e5e5d2b488b8c0ddd13384bb Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sun, 19 Jul 2026 21:28:23 -0700 Subject: [PATCH 45/76] qtdragon -add docs for halui messages/gui pins --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 6 +- docs/src/gui/qtdragon.adoc | 150 ++++++++++++++++++++ 2 files changed, 153 insertions(+), 3 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index 1d1f8628816..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 @@ -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 diff --git a/docs/src/gui/qtdragon.adoc b/docs/src/gui/qtdragon.adoc index 692eac20a5d..884bbacd670 100644 --- a/docs/src/gui/qtdragon.adoc +++ b/docs/src/gui/qtdragon.adoc @@ -463,6 +463,156 @@ 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. +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. +- 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: + +- 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: +---- +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 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 a53259276dff818c71699b55ae68b3adb9df45ca Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 9 Sep 2026 12:17:56 -0700 Subject: [PATCH 46/76] gmoccapy -allow ZMQ messages to call macros in gmoccapy This is a proof of concept to allow 3rd party programs 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. Python ZMQ module package must be available for this to work --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 42 ++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index f468e9ca8a9..60489182cce 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -444,6 +444,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) @@ -1327,6 +1328,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") @@ -1368,7 +1396,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() @@ -6531,6 +6561,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 626f32d2e8b908c79498738e7ee5944d89f22708 Mon Sep 17 00:00:00 2001 From: CMorley Date: Wed, 30 Jul 2025 20:50:47 -0700 Subject: [PATCH 47/76] 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 60489182cce..cfaf7c33b23 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -211,7 +211,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 @@ -1330,7 +1331,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") @@ -6610,6 +6621,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 56ffa81a5eef6cce19f6587bdafa903bb5474cb6 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:16:35 -0700 Subject: [PATCH 48/76] 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 cfaf7c33b23..132204959a9 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -446,6 +446,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) @@ -1329,6 +1331,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 4d46635c2928131c222138e2c7aaf12bfa9877c6 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 21:27:49 -0700 Subject: [PATCH 49/76] 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 132204959a9..beabf82ecef 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1345,10 +1345,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 deff062ccb520aa615683f6a34bae6a20a78dab4 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 14 Dec 2025 01:10:17 -0800 Subject: [PATCH 50/76] 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 beabf82ecef..ff17f4c7594 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -285,8 +285,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 @@ -448,6 +449,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) @@ -4296,8 +4299,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) @@ -4347,6 +4351,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 1978354525713ad5fb53ee50b23d12408c07ca89 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Dec 2025 19:48:56 -0800 Subject: [PATCH 51/76] 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 ff17f4c7594..1f00fd27212 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -285,10 +285,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 @@ -367,6 +363,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() @@ -449,8 +450,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) @@ -1356,7 +1357,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 @@ -1375,7 +1377,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): @@ -1553,8 +1556,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() @@ -2009,8 +2013,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 @@ -2469,8 +2474,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) @@ -4353,21 +4359,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 @@ -4488,8 +4521,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 @@ -4804,8 +4837,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: @@ -4830,8 +4863,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: @@ -5017,8 +5050,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() @@ -5039,7 +5072,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 @@ -5102,8 +5136,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 @@ -5131,7 +5165,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) @@ -5675,28 +5711,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 @@ -5724,13 +5752,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 @@ -5738,7 +5768,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: @@ -5746,8 +5777,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, @@ -5757,7 +5788,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 @@ -5785,13 +5817,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 @@ -5812,11 +5846,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 @@ -5834,7 +5870,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 0a8538a0264508c2459e05d63874b0924819e6fd Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 00:00:35 -0800 Subject: [PATCH 52/76] 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 e045f89bf87af88965da0bf4d2bd5c37f72de207 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 00:25:59 -0800 Subject: [PATCH 53/76] 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 1f00fd27212..017b7422546 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -452,6 +452,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) @@ -6366,6 +6367,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 bcfad7e78166fe6e0e6f4b1b01d6dbd6cf8962c2 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 7 Feb 2026 14:45:11 -0800 Subject: [PATCH 54/76] 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 017b7422546..b0348bae067 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1336,12 +1336,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 5f3da1ee39a20530c77f4a17e13d0cc9862b24f5 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 3 Apr 2026 18:34:43 -0700 Subject: [PATCH 55/76] 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 b0348bae067..6b7c5a503d4 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1339,7 +1339,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: @@ -1351,7 +1353,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 b57e2929e496e54e594ae1247ae0dbbaea4728d2 Mon Sep 17 00:00:00 2001 From: CMorley Date: Thu, 9 Apr 2026 17:00:26 -0700 Subject: [PATCH 56/76] 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 6b7c5a503d4..60e9348707d 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -365,8 +365,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() @@ -1367,7 +1365,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) @@ -1378,7 +1376,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 @@ -1398,7 +1396,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): @@ -1577,8 +1575,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() @@ -2034,8 +2032,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 @@ -2191,15 +2189,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: @@ -2495,8 +2493,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) @@ -2594,12 +2592,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 !"), @@ -2751,7 +2750,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: @@ -3259,13 +3258,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 !"), @@ -3471,8 +3471,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 !"), @@ -4325,9 +4326,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) @@ -4339,7 +4343,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: @@ -4377,52 +4381,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(): @@ -4542,7 +4500,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 @@ -4836,7 +4794,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() @@ -4850,15 +4808,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: @@ -4876,15 +4833,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: @@ -4943,7 +4899,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) @@ -4956,7 +4912,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 @@ -4978,7 +4934,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() @@ -5056,22 +5012,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() @@ -5093,7 +5049,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 @@ -5143,7 +5099,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": @@ -5157,7 +5113,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 @@ -5186,7 +5142,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) @@ -5731,10 +5687,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 @@ -5744,7 +5714,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 @@ -5756,7 +5726,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) @@ -5773,14 +5743,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 @@ -5789,7 +5759,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: @@ -5798,10 +5768,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) @@ -5809,7 +5779,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 @@ -5829,23 +5799,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 @@ -5860,19 +5830,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 @@ -5891,7 +5861,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 c361fe4f8ddd26f122b014546eb70f1788af5bd8 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 30 Jul 2026 09:44:20 -0700 Subject: [PATCH 57/76] 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 fd6561260b42bc2b1dacd1c9b1c6e66aba0da0ea Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 10 Apr 2026 13:41:47 -0700 Subject: [PATCH 58/76] 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 85901b6ee8341c978cc2805eb9e0d7cfd1174014 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 14:13:15 -0700 Subject: [PATCH 59/76] 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 60e9348707d..a7338bf4328 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -451,6 +451,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 43866a361f99f44dd70e20b120e88027dce85fb3 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 10:21:38 -0700 Subject: [PATCH 60/76] 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 a7338bf4328..b3ed4711e3c 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -451,7 +451,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) @@ -2952,6 +2953,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 # ========================================================= @@ -6418,6 +6422,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, @@ -6441,6 +6459,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 52b9df3f199d1321a5ee1cc099143c031fc8bafb Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 13:23:52 -0700 Subject: [PATCH 61/76] 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 b3ed4711e3c..fb8a47b9c86 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -241,6 +241,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 @@ -453,6 +455,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) @@ -6421,6 +6424,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): @@ -6655,6 +6683,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 594a69f7900a21577a9ad9a38249faf0d59349db Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 11 Aug 2026 05:33:22 -0700 Subject: [PATCH 62/76] 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 bef7725408eadacf6a2399c36b86b9589dbdc7a3 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 11 Aug 2026 05:35:30 -0700 Subject: [PATCH 63/76] 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 d411e6ccc222ae64df73b9503cd15129201a0db5 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 11 Aug 2026 20:39:39 -0700 Subject: [PATCH 64/76] gmoccapy -dialogs: cleanup warning dialog, control exit -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 -clean up warning dialog code -dialog destroy buttons are now the same as a negative response --- src/emc/usr_intf/gmoccapy/dialogs.py | 40 ++++++++++++++++----------- src/emc/usr_intf/gmoccapy/gmoccapy.py | 3 +- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index f24176c7636..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")) @@ -134,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) @@ -193,15 +195,12 @@ 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) + 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)) @@ -209,16 +208,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 @@ -230,8 +229,10 @@ 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.confirm_pin = confirm_pin + dialog.active_pin = active_pin + dialog.show_all() if sound: self.emit("play_sound", "alert") @@ -253,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) @@ -305,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) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index fb8a47b9c86..5dc3f170c5c 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -5696,8 +5696,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 bcb5520c80ed352fb60ea083775d2dc5d1954e89 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 17 Aug 2026 18:35:17 -0700 Subject: [PATCH 65/76] 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 5dc3f170c5c..13644146819 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2606,7 +2606,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 @@ -2895,7 +2895,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) # ========================================================= @@ -3276,7 +3276,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 @@ -3484,7 +3484,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 b15baf1e5ffd0c4dc9bdbe04c7e3a60e5bed18dc Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 18 Aug 2026 18:26:13 -0700 Subject: [PATCH 66/76] 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 13644146819..a1e2b374d0c 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2201,7 +2201,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 @@ -2599,7 +2599,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 8a4f3ce27eb16f6233429d65aeade64ab405b245 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 9 Sep 2026 14:57:37 -0700 Subject: [PATCH 67/76] gmoccapy -create a dialog in gmoccapy for system shutdown --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index a1e2b374d0c..1b5417ace8a 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2957,7 +2957,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 364ae0614629325ebde3999dfd5191b8b9104a1c Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sat, 22 Aug 2026 18:55:00 -0700 Subject: [PATCH 68/76] 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 1b5417ace8a..ad0ee1977bc 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -447,7 +447,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)) @@ -1367,41 +1367,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 6ea3ecd998e0c3d3615339db601f3ec3dc67ce27 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 28 Aug 2026 20:29:50 -0700 Subject: [PATCH 69/76] 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 ad0ee1977bc..b436ad1c270 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1386,23 +1386,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 0e7cc169de952a0ea4f2909801d8e152c5134536 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 28 Aug 2026 21:24:48 -0700 Subject: [PATCH 70/76] 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 72a007634ae0eb6ae92ef61fb7b518179c1c9e2e Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 28 Aug 2026 21:25:44 -0700 Subject: [PATCH 71/76] 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 b436ad1c270..c09425ffca5 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2952,8 +2952,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 6b9c6fb9b3e93be2c755ec73d9b2d4d57ca4c460 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:19:59 -0700 Subject: [PATCH 72/76] gmoccapy -add a halui test config with a sim test panel add a 4 axis halui test config to confirm angular jograte works gmoccapy -remove one of the halui sims gmoccapy sim -change pin names do to halui name change gmoccapy -sim: name change for gui.mpg-aux.0 gmoccapy -sims: change panel/ini to run mdi and macros for testing 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. --- .../sim/gmoccapy/control_panel_simulated.ini | 214 +++ configs/sim/gmoccapy/panel-4axis.hal | 65 + configs/sim/gmoccapy/panel.ui | 1407 +++++++++++++++++ 3 files changed, 1686 insertions(+) create mode 100644 configs/sim/gmoccapy/control_panel_simulated.ini create mode 100644 configs/sim/gmoccapy/panel-4axis.hal create mode 100644 configs/sim/gmoccapy/panel.ui diff --git a/configs/sim/gmoccapy/control_panel_simulated.ini b/configs/sim/gmoccapy/control_panel_simulated.ini new file mode 100644 index 00000000000..d1a135566f0 --- /dev/null +++ b/configs/sim/gmoccapy/control_panel_simulated.ini @@ -0,0 +1,214 @@ +# 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 = G53 G0 Z0;G0 X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + +[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..d00a5365e05 --- /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-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 + +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.1 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.response.cancel panel.cancel +net ok halui.gui.response.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-preview panel.reload diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui new file mode 100644 index 00000000000..c221ce2ec74 --- /dev/null +++ b/configs/sim/gmoccapy/panel.ui @@ -0,0 +1,1407 @@ + + + MainWindow + + + + 0 + 0 + 874 + 756 + + + + Simulated Control Panel + + + + + + + + + + + + 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 232ba42aa072bb92159cfc7f01370710385b06fd Mon Sep 17 00:00:00 2001 From: Cmorley Date: Sun, 19 Jul 2026 21:27:51 -0700 Subject: [PATCH 73/76] gmoccapy -add docs for halui messages --- docs/src/gui/gmoccapy.adoc | 131 +++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/docs/src/gui/gmoccapy.adoc b/docs/src/gui/gmoccapy.adoc index d7d1d4b849c..90a32b228f1 100644 --- a/docs/src/gui/gmoccapy.adoc +++ b/docs/src/gui/gmoccapy.adoc @@ -936,6 +936,137 @@ 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. + +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. + +- 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 +- 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 + +=== 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 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 7d0eab23fb14b741ffabfd7056ee40a8abcae217 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 12 Aug 2026 21:26:35 -0700 Subject: [PATCH 74/76] axis -incorporate zmq messages pulls in gstat and iniinfo libraries -fix macro entry dialog error -adjust request macro function signature -switch halui.gui.shutdown to shutdown system -change the close dialog to modeless So an external zmq message can control the buttons -call softkeys in a way that can be easily overridden --- src/emc/usr_intf/axis/scripts/axis.py | 251 +++++++++++++++++++++++++- 1 file changed, 245 insertions(+), 6 deletions(-) diff --git a/src/emc/usr_intf/axis/scripts/axis.py b/src/emc/usr_intf/axis/scripts/axis.py index f9f04cc72a5..84fdda6a1d6 100755 --- a/src/emc/usr_intf/axis/scripts/axis.py +++ b/src/emc/usr_intf/axis/scripts/axis.py @@ -67,7 +67,11 @@ 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 + from glnav import * if "AXIS_NO_HAL" in os.environ: @@ -111,6 +115,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 +131,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, key, name: request_macro_call(key, name)) +GSTAT.connect('softkey-pressed', lambda w,data: softkey_pressed(data)) +GSTAT.connect('shutdown-request', lambda w : request_shutdown()) +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): @@ -172,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) @@ -974,6 +1047,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): @@ -1129,7 +1223,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) @@ -1746,6 +1839,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() @@ -2443,7 +2537,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 +2559,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 +4059,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')) @@ -3998,12 +4101,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) @@ -4058,6 +4160,144 @@ 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): + + 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) + 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): + 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: + #print('No window') + # remove one error message + if state == 0: + notifications.clear_one() + +def request_macro_call(name, key): + #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 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(';') + 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): + try: + # call a softkey function directed by index + globals()[f'softkey_action_{index}']() + except: + pass + +# standard softkey functions. Can be overridden in user file +def softkey_action_0(): + root_window.tk.call('.pane.top.tabs','raise','manual') +def softkey_action_1(): + root_window.tk.call('.pane.top.tabs','raise','mdi') +def softkey_action_2(): + root_window.tk.call('.pane.top.right','raise','preview') +def softkey_action_3(): + root_window.tk.call('.pane.top.right','raise','numbers') +def softkey_action_4(): + root_window.tk.call('.pane.top.right','raise','user_0') + def destroy_splash(): try: root_window.send("popimage", "destroy", ".") @@ -4070,7 +4310,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): @@ -4104,7 +4343,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 f91914cfc154b3f14ad857223fe7e9c3216030c0 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 7 Sep 2026 20:17:50 -0700 Subject: [PATCH 75/76] axis -sim: for halui zmq messages incorporated in axis now axis -add macros for sim axis -add test sim axis add panel.hal --- configs/sim/axis/control_panel_simulated.ini | 235 +++ 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 +++++++++++++++++ 9 files changed, 1777 insertions(+) create mode 100644 configs/sim/axis/control_panel_simulated.ini 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/control_panel_simulated.ini b/configs/sim/axis/control_panel_simulated.ini new file mode 100644 index 00000000000..9f0d56b9346 --- /dev/null +++ b/configs/sim/axis/control_panel_simulated.ini @@ -0,0 +1,235 @@ +# 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 + +[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/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..b3e91428b14 --- /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-aux.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.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 +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-preview panel.reload diff --git a/configs/sim/axis/panel.ui b/configs/sim/axis/panel.ui new file mode 100644 index 00000000000..a91e8943557 --- /dev/null +++ b/configs/sim/axis/panel.ui @@ -0,0 +1,1328 @@ + + + MainWindow + + + + 0 + 0 + 588 + 731 + + + + Simulated Control Panel + + + + + + + + + + + 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
+
+
+ + + + + +
From 080643745e0d3d00f3acf73805a2c77f386888b0 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Mon, 24 Aug 2026 16:17:39 -0700 Subject: [PATCH 76/76] axis -docs: add info about HALUI gui pins - add info about mdi/macro commands --- docs/src/gui/axis.adoc | 147 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/docs/src/gui/axis.adoc b/docs/src/gui/axis.adoc index 28f121355a1..9862aa73304 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 @@ -1481,4 +1615,17 @@ except Exception as e: print(e) ---- +=== Change/Add Softkey Functions +There are 20 (0-19) softkeys that can be controlled from HALUI pins. + +By default only 0-4 are defined. You can change or add functions by +writing the functions in the user file. + +Here we add a function to close AXIS, either with a dialog or not +by adding softkey function 5. You must uncomment one of the options. + +[source,python] +---- +def softkey_action_5(): + #General_Halt() + #root_window.destroy() +---- // vim: set syntax=asciidoc: