From cd8849f9ed2d9b8e16085a19c84d41743fbb8f77 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Sep 2025 22:09:20 -0700 Subject: [PATCH 01/69] 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 864bf95b0ec..caa00c6ba75 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -353,6 +353,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() @@ -363,6 +368,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 05ae1eb82d58101a9037f5f2042508919747f704 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 8 Jul 2026 23:03:57 -0700 Subject: [PATCH 02/69] 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 caa00c6ba75..ec7e19aec02 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -404,30 +404,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 ec23790390f0467f3acc1e0b6e00f7f5f163ea6c Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 14 Jul 2026 21:34:09 -0700 Subject: [PATCH 03/69] 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 ec7e19aec02..7da10816f76 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' @@ -421,6 +422,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') @@ -1497,6 +1499,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 be0d7e056015daad80adf1f81ef71edca965bfe9 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 17:45:44 -0800 Subject: [PATCH 04/69] 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 e49f8d018ba71a6d00eff3e5a4dc05fcd13efedf Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 7 Feb 2026 16:39:36 -0800 Subject: [PATCH 05/69] 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 56653dc0827..2af4d385f91 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 17c361b009e46de2a53c3a9fbb20d53b742c6b86 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 16 Aug 2025 11:21:10 -0700 Subject: [PATCH 06/69] halui initial testing of embedded python halui add bridgeui folder for HALUI halui -inject socket axis selection bridge -remove HAL pin code, get MDI commands working halui -code clenup, GUI based MDI command feature halui -add cycle start cycle pause pins calls these functions in a GUI halui -add gui ok/canel pins halui -fix memory leaks halui switch u32 to s32 s32 is more common the u32 (easier to connect to) also axis select uses -1 as 'unselected' halui -add mpg_select0 pin Some GUIs use the MPG for other things then jogging. Qtdragon uses MPG for scrolling and needs a selected pin. --- .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 2 +- lib/python/bridgeui/__init__.py | 3 + lib/python/bridgeui/bridge.py | 268 ++++++++++ src/emc/usr_intf/Submakefile | 3 +- src/emc/usr_intf/halui.cc | 488 ++++++++++++++++-- 5 files changed, 731 insertions(+), 33 deletions(-) create mode 100644 lib/python/bridgeui/__init__.py create mode 100644 lib/python/bridgeui/bridge.py diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index 7e5d56e05aa..f437be0811a 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini @@ -151,7 +151,7 @@ SPINDLES = 1 [HAL] HALUI = halui -HALBRIDGE = hal_bridge +#HALBRIDGE = hal_bridge # loads the HAL machine simulation HALFILE = core_sim.hal diff --git a/lib/python/bridgeui/__init__.py b/lib/python/bridgeui/__init__.py new file mode 100644 index 00000000000..b28b04f6431 --- /dev/null +++ b/lib/python/bridgeui/__init__.py @@ -0,0 +1,3 @@ + + + diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py new file mode 100644 index 00000000000..56577ebb664 --- /dev/null +++ b/lib/python/bridgeui/bridge.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 + +import os +import sys +import time + +import json +import signal + +import hal +from common.iniinfo import _IStat as IStatParent +from common import logger + +# LOG is for running code logging +LOG = logger.initBaseLogger('HAL bridge', log_file=None, + log_level=logger.WARNING, logToFile=False) + +# Force the log level for this module +LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL + +try: + import zmq + ZMQ = True +except: + LOG.critical('ZMQ python library problem - Is python3-zmq installed?') + ZMQ = False + +class Info(IStatParent): + _instance = None + _instanceNum = 0 + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = IStatParent.__new__(cls, *args, **kwargs) + return cls._instance + +# Instantiate the library with global reference + + +class Bridge(object): + def __init__(self, readAddress = "tcp://127.0.0.1:5690", + writeAddress = "tcp://127.0.0.1:5691"): + super(Bridge, self).__init__() + self.INFO = Info() + + self.currentSelectedAxis = 'None' + self.axesSelected = {'X':0,'Y':0,'Z':0,'A':0,'B':0,'C':0, + 'U':0,'V':0,'W':0,'MPG0':0} + self.readAddress = readAddress + self.writeAddress = writeAddress + LOG.debug('read port: {}'.format(readAddress)) + LOG.debug('write port: {}'.format(writeAddress)) + + self.readTopic = "" + self.writeTopic = "STATUSREQUEST" + + # catch control c and terminate signals + signal.signal(signal.SIGTERM, self.shutdown) + signal.signal(signal.SIGINT, self.shutdown) + + self.init() + if ZMQ: + self.init_read() + self.init_write() + + def update(self, *arg): + print(self, arg) + raw=arg[0]; row=arg[1];column=arg[2];state=arg[3] + LOG.debug('raw {}, row {}, col {}, state {}'.format(raw,row,column,state)) + print ('raw',raw,'row:',row,'column:',column,'state:',state) + self.writeMsg('set_selected_axis','Y') + self.activeJoint.set(10) + + def init(self): + self.jogRate = 0 + self.jogRateAngular = 0 + + self.jogIncrement = 0 + self.jogIncrementAngular = 0 + + self.activeJoint = 0 + + def init_write(self): + context = zmq.Context() + self.writeSocket = context.socket(zmq.PUB) + self.writeSocket.bind(self.writeAddress) + + def init_read(self): + # ZeroMQ Context + self.readContext = zmq.Context() + + # Define the socket using the "Context" + self.readSocket = self.readContext.socket(zmq.SUB) + + # Define subscription and messages with topic to accept. + self.readSocket.setsockopt_string(zmq.SUBSCRIBE, self.readTopic) + self.readSocket.connect(self.readAddress) + + # callback from ZMQ read socket + def readMsg(self): + if self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: + while self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: + # get raw message + topic, data = self.readSocket.recv_multipart() + # convert from json object to python object + y = json.loads(data) + self. action(y.get('MESSAGE'),y.get('ARGS')) + + # set our variables from messages from hal_glib + def action(self, msg, data): + LOG.debug('{} -> {} -> {}'.format(msg, data, data[0])) + if msg == 'jograte-changed': + self.jogRate = float(data[0]) + elif msg == 'jograte-angular-changed': + self.jogRateAngular = float(data[0]) + elif msg == 'jogincrements-changed': + self.jogIncrement = float(data[0][0]) + elif msg == 'jogincrement-angular-changed': + self.jogIncremtAngular = float(data[0][0]) + elif msg == 'joint-selection-changed': + self.activeJoint = int(data[0]) + elif msg == 'axis-selection-changed': + print ('pre axis state', self.axesSelected,self.currentSelectedAxis) + flag = 1 + if data[0] == 'MPG0': + self.currentSelectedAxis = data[0] + flag = 0 + self.axesSelected['MPG0'] = True + else: + self.axesSelected['MPG0'] = False + for i in(self.INFO.AVAILABLE_AXES): + if data[0] == i: + state = True + self.currentSelectedAxis = data[0] + flag = 0 + else: + state = False + self.axesSelected[i] = int(state) + + if flag: + self.currentSelectedAxis = 'None' + print ('axis state', self.axesSelected,self.currentSelectedAxis) + + # send msg to hal_glib + def writeMsg(self, msg, data): + print('Write Msg called') + if ZMQ: + topic = self.writeTopic + message = json.dumps({'FUNCTION':msg,'ARGS':data}) + LOG.debug('Sending ZMQ Message:{} {}'.format(topic, message)) + self.writeSocket.send_multipart( + [bytes(topic.encode('utf-8')), + bytes((message).encode('utf-8'))]) + + def shutdown(self,signum=None,stack_frame=None): + LOG.debug('shutdown') + global app + app.quit() + + def cycleStart(self): + # cycle start + self.writeMsg('request_cycle_start', True) + + def cyclePause(self): + self.writeMsg('request_cycle_pause', True) + + def ok(self): + self.writeMsg('request_ok', True) + + def cancel(self): + self.writeMsg('request_cancel', True) + + def getMdiName(self, num): + if num >len(self.INFO.MDI_COMMAND_DICT)-1: + return 'None' + temp = list(self.INFO.MDI_COMMAND_DICT.keys())[num] + LOG.debug('{} {}'.format(num,temp)) + return temp + + def getMacroNames(self): + for i in self.INFO.INI_MACROS: + name = i.split()[0] + LOG.debug('{} {}'.format(name,i)) + + def runIndexedMacro(self, num): + name = self.getMdiName(num) + LOG.debug('Macro name:{} ,index: {}'.format(name, num)) + if name != 'None': + self.writeMsg('request_macro_call', name) + + def getMdiCount(self): + print(len(self.INFO.MDI_COMMAND_DICT)) + return len(self.INFO.MDI_COMMAND_DICT) + + def getJogRate(self): + return self.jogRate + def setJogRate(self, value): + self.writeMsg('set_jograte', value) + + def getJogRateAngular(self): + return self.jogRateAngular + def setJogRateAngular(self, value): + self.writeMsg('set_jograte_angular', value) + + def getSelectedAxis(self): + name = self.currentSelectedAxis + if name == 'None': + index = -1 + elif name =='MPG0': + index = 100 + else: + index = 'XYZABCUVW'.index(name) + return index + def setSelectedAxis(self, value): + if value < 0: + letter = 'None' + elif value == 100: + letter = 'MPG0' + else: + letter ='XYZABCUVW'[value] + self.writeMsg('set_selected_axis', letter) + + def isAxisSelected(self, index): + if index == 100: + letter = 'MPG0' + else: + letter = 'XYZABCUVW'[index] + return int(self.axesSelected[letter]) + + def __getitem__(self, item): + return getattr(self, item) + def __setitem__(self, item, value): + return setattr(self, item, value) + +if __name__ == "__main__": + import sys + import getopt + from PyQt5.QtWidgets import QApplication + + letters = 'dh' # the : means an argument needs to be passed after the letter + keywords = ['readport=', 'writeport=' ] # the = means that a value is expected after + # the keyword + + opts, extraparam = getopt.getopt(sys.argv[1:],letters,keywords) + # starts at the second element of argv since the first one is the script name + # extraparms are extra arguments passed after all option/keywords are assigned + # opts is a list containing the pair "option"/"value" + + readport = "tcp://127.0.0.1:5690" + writeport = "tcp://127.0.0.1:5691" + + for o,p in opts: + if o in ['-d']: + LOG.setLevel(logger.DEBUG) + elif o in ['--readport']: + readport = p + elif o in ['--writeport']: + writeport = p + elif o in ['-h','--help']: + print('HAL bridge: GUI to HAL interface using ZMQ') + print('option "-d" = debug print mode') + print('option "--readport=" read socket address') + print('option "--writeport=" write socket address') + print('example: hal_bridge -d --readport=tcp://127.0.0.1:5692') + + app = QApplication(sys.argv) + test = Bridge(readport, writeport) + sys.exit(app.exec_()) diff --git a/src/emc/usr_intf/Submakefile b/src/emc/usr_intf/Submakefile index b914e30ffe1..4dbd9ec9afb 100644 --- a/src/emc/usr_intf/Submakefile +++ b/src/emc/usr_intf/Submakefile @@ -42,5 +42,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 4e1761f64d9..cf2e79c1de0 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; @@ -86,6 +90,8 @@ static int axis_mask = 0; FIELD(hal_bit_t,program_is_running) /* pin for notifying user that program is running */ \ FIELD(hal_bit_t,halui_mdi_is_running) /* pin for notifying user that halui MDI commands is running */ \ FIELD(hal_bit_t,program_is_paused) /* pin for notifying user that program is paused */ \ + FIELD(hal_bit_t,cycle_start) /* pin for running program */ \ + FIELD(hal_bit_t,cycle_pause) /* pin for running program */ \ FIELD(hal_bit_t,program_run) /* pin for running program */ \ FIELD(hal_bit_t,program_pause) /* pin for pausing program */ \ FIELD(hal_bit_t,program_resume) /* pin for resuming program */ \ @@ -98,7 +104,7 @@ static int axis_mask = 0; FIELD(hal_bit_t,program_bd_off) /* pin for setting block delete off */ \ FIELD(hal_bit_t,program_bd_is_on) /* status pin that block delete is on */ \ \ - FIELD(hal_u32_t,tool_number) /* pin for current selected tool */ \ + FIELD(hal_s32_t,tool_number) /* pin for current selected tool */ \ FIELD(hal_float_t,tool_length_offset_x) /* current applied x tool-length-offset */ \ FIELD(hal_float_t,tool_length_offset_y) /* current applied y tool-length-offset */ \ FIELD(hal_float_t,tool_length_offset_z) /* current applied z tool-length-offset */ \ @@ -133,11 +139,12 @@ static int axis_mask = 0; ARRAY(hal_bit_t,joint_on_hard_max_limit,EMCMOT_MAX_JOINTS+1) /* status pin that the joint is on the hardware max limit */ \ ARRAY(hal_bit_t,joint_override_limits,EMCMOT_MAX_JOINTS+1) /* status pin that the joint is on the hardware max limit */ \ ARRAY(hal_bit_t,joint_has_fault,EMCMOT_MAX_JOINTS+1) /* status pin that the joint has a fault */ \ - FIELD(hal_u32_t,joint_selected) /* status pin for the joint selected */ \ - FIELD(hal_u32_t,axis_selected) /* status pin for the axis selected */ \ + FIELD(hal_s32_t,joint_selected) /* status pin for the joint selected */ \ + FIELD(hal_s32_t,axis_selected) /* status pin for the axis selected */ \ \ ARRAY(hal_bit_t,joint_nr_select,EMCMOT_MAX_JOINTS) /* nr. of pins to select a joint */ \ ARRAY(hal_bit_t,axis_nr_select,EMCMOT_MAX_AXIS) /* nr. of pins to select a axis */ \ + FIELD(hal_bit_t,mpg_select0)\ \ ARRAY(hal_bit_t,joint_is_selected,EMCMOT_MAX_JOINTS) /* nr. of status pins for joint selected */ \ ARRAY(hal_bit_t,axis_is_selected,EMCMOT_MAX_AXIS) /* nr. of status pins for axis selected */ \ @@ -202,7 +209,12 @@ static int axis_mask = 0; \ FIELD(hal_bit_t,home_all) /* pin for homing all joints in sequence */ \ FIELD(hal_bit_t,abort) /* pin for aborting */ \ +\ ARRAY(hal_bit_t,mdi_commands,MDI_MAX) \ + ARRAY(hal_bit_t,gui_mdi_commands,MDI_MAX) \ +\ + FIELD(hal_bit_t,gui_ok) /* pin for acknowledging dialog ok */ \ + FIELD(hal_bit_t,gui_cancel) /* pin for acknowledging dialog cancel */ \ \ FIELD(hal_float_t,units_per_mm) \ @@ -234,11 +246,21 @@ typedef halui_str_base halui_str; typedef halui_str_base local_halui_str; #pragma GCC diagnostic pop +PyObject *pModule, *pFuncRead, *pFuncWrite, *pInstance, *pClass; +PyObject *pValue; + static halui_str *halui_data; static local_halui_str old_halui_data; +static double lastjogspeed = 0; +static double internaljogspeed = 0; +static int lastaxis = -1; + static char *mdi_commands[MDI_MAX]; static int num_mdi_commands=0; + +static 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 */ @@ -531,6 +553,123 @@ int halui_export_pin_OUT_bit(hal_bit_t **pin, const char *name) return 0; } +static void py_call_cycleStart() { + + // check socket messages for jogspeed + pFuncWrite = PyObject_GetAttrString(pInstance, "cycleStart"); + if (pFuncRead && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: cycleStart function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} + +static void py_call_cyclePause() { + + // check socket messages for jogspeed + pFuncWrite = PyObject_GetAttrString(pInstance, "cyclePause"); + if (pFuncRead && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: cyclePause function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} + +static 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; +} /******************************************************************** * @@ -708,11 +847,11 @@ int halui_hal_init(void) if (retval < 0) return retval; retval = hal_pin_float_newf(HAL_OUT, &(halui_data->ro_value), comp_id, "halui.rapid-override.value"); if (retval < 0) return retval; - retval = hal_pin_u32_newf(HAL_OUT, &(halui_data->joint_selected), comp_id, "halui.joint.selected"); + retval = hal_pin_s32_newf(HAL_OUT, &(halui_data->joint_selected), comp_id, "halui.joint.selected"); if (retval < 0) return retval; - retval = hal_pin_u32_newf(HAL_OUT, &(halui_data->axis_selected), comp_id, "halui.axis.selected"); + retval = hal_pin_s32_newf(HAL_OUT, &(halui_data->axis_selected), comp_id, "halui.axis.selected"); if (retval < 0) return retval; - retval = hal_pin_u32_newf(HAL_OUT, &(halui_data->tool_number), comp_id, "halui.tool.number"); + retval = hal_pin_s32_newf(HAL_OUT, &(halui_data->tool_number), comp_id, "halui.tool.number"); if (retval < 0) return retval; retval = hal_pin_float_newf(HAL_OUT, &(halui_data->tool_length_offset_x), comp_id, "halui.tool.length_offset.x"); if (retval < 0) return retval; @@ -763,6 +902,10 @@ int halui_hal_init(void) if (retval < 0) return retval; retval = halui_export_pin_IN_bit(&(halui_data->flood_off), "halui.flood.off"); if (retval < 0) return retval; + retval = halui_export_pin_IN_bit(&(halui_data->cycle_start), "halui.cycle.start"); + if (retval < 0) return retval; + retval = halui_export_pin_IN_bit(&(halui_data->cycle_pause), "halui.cycle.pause"); + if (retval < 0) return retval; retval = halui_export_pin_IN_bit(&(halui_data->program_run), "halui.program.run"); if (retval < 0) return retval; retval = halui_export_pin_IN_bit(&(halui_data->program_pause), "halui.program.pause"); @@ -881,6 +1024,9 @@ int halui_hal_init(void) if (retval < 0) return retval; } + retval = halui_export_pin_IN_bit(&(halui_data->mpg_select0), "halui.mpg-select.0"); + if (retval < 0) return retval; + retval = hal_pin_bit_newf(HAL_IN, &(halui_data->joint_home[num_joints]), comp_id, "halui.joint.selected.home"); if (retval < 0) return retval; retval = hal_pin_bit_newf(HAL_IN, &(halui_data->joint_unhome[num_joints]), comp_id, "halui.joint.selected.unhome"); @@ -921,6 +1067,18 @@ int halui_hal_init(void) if (retval < 0) return retval; } + for (int n=0; ngui_mdi_commands[n]), comp_id, "halui.gui.mdi-command-%s", py_call_get_mdi_name(n)); + if (retval < 0) return retval; + } + + retval = halui_export_pin_IN_bit(&(halui_data->gui_ok), "halui.gui.ok"); + if (retval < 0) return retval; + + retval = halui_export_pin_IN_bit(&(halui_data->gui_cancel), "halui.gui.cancel"); + if (retval < 0) return retval; + hal_ready(comp_id); return 0; } @@ -1261,6 +1419,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; } @@ -1489,6 +1648,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; + *(halui_data->mpg_select0) = old_halui_data.mpg_select0 = 0; *(halui_data->ajog_minus[axis_num]) = old_halui_data.ajog_minus[axis_num] = 0; *(halui_data->ajog_plus[axis_num]) = old_halui_data.ajog_plus[axis_num] = 0; *(halui_data->ajog_analog[axis_num]) = old_halui_data.ajog_analog[axis_num] = 0; @@ -1545,7 +1711,7 @@ static void hal_init_pins() *(halui_data->ajog_speed) = 0; *(halui_data->joint_selected) = 0; // select joint 0 by default - *(halui_data->axis_selected) = 0; // select axis 0 by default + *(halui_data->axis_selected) = -1; // select no axis by default *(halui_data->fo_scale) = old_halui_data.fo_scale = 0.1; //sane default *(halui_data->ro_scale) = old_halui_data.ro_scale = 0.1; //sane default @@ -1600,6 +1766,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 @@ -1611,10 +1889,54 @@ static void check_hal_changes() hal_bit_t bit; int js; hal_float_t floatt; + double jogspeed; int jjog_speed_changed; int ajog_speed_changed; + int is_any_axis_selected, deselected; + + // 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) { + *(halui_data->axis_nr_select[axis_num]) = 1; + }else{ + *(halui_data->axis_nr_select[axis_num]) = 0; + } + } + if (value == 100) { + *(halui_data->mpg_select0) = 1; + }else{ + *(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; @@ -1659,6 +1981,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); @@ -1833,11 +2165,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); @@ -1917,47 +2261,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; } @@ -1965,30 +2320,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) { - *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; + *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); + *halui_data->axis_selected = 100; + }else{ + deselected = 1; + } + old_halui_data.mpg_select0 = bit; + } + + + // last axis has been deselected - no axis is selected now + if (is_any_axis_selected == 0 and deselected == 1) { + py_call_axis_changed(-1); + *halui_data->axis_selected = -1; } if (aselect_changed >= 0) { - for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { - if ( !(axis_mask & (1 << axis_num)) ) { continue; } - if (axis_num != aselect_changed) { - *(halui_data->axis_is_selected[axis_num]) = 0; + fprintf(stderr, "halui Bridge: axis selected %d\n",aselect_changed); + for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { + if ( !(axis_mask & (1 << axis_num)) ) { continue; } + if (axis_num != aselect_changed) { + *(halui_data->axis_is_selected[axis_num]) = 0; if (jogging_selected_axis(old_halui_data) && !jogging_axis(old_halui_data, axis_num)) { - sendJogStop(axis_num,JOGTELEOP); + sendJogStop(axis_num,JOGTELEOP); } } else { - *(halui_data->axis_is_selected[axis_num]) = 1; + *(halui_data->axis_is_selected[axis_num]) = 1; if (*halui_data->ajog_plus[num_axes]) { - sendJogCont(axis_num, new_halui_data.ajog_speed,JOGTELEOP); + fprintf(stderr, "halui: jog plus: %d\n",num_axes); + sendJogCont(axis_num, internaljogspeed,JOGTELEOP); } else if (*halui_data->ajog_minus[num_axes]) { - sendJogCont(axis_num, -new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(axis_num, -internaljogspeed,JOGTELEOP); } - } - } + } + } } if (check_bit_changed(new_halui_data.joint_home[num_joints], old_halui_data.joint_home[num_joints]) != 0) @@ -2037,7 +2418,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; @@ -2047,7 +2428,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; @@ -2057,7 +2438,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; } @@ -2065,14 +2446,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 @@ -2295,6 +2695,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"); @@ -2310,6 +2731,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"); @@ -2345,6 +2769,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 5c2b24c5e94bc999d76d3f16fd7cc9fe88c23c77 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 8 Feb 2026 16:42:51 -0800 Subject: [PATCH 07/69] makefile - add halui's bridgeui folder --- src/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Makefile b/src/Makefile index 69952ddeac9..b91e5dcad30 100644 --- a/src/Makefile +++ b/src/Makefile @@ -757,6 +757,7 @@ install-kernel-indep: install-dirs install-kernel-indep: install-python install-python: install-dirs $(DIR) $(DESTDIR)$(SITEPY) $(DESTDIR)$(SITEPY)/rs274 + $(DIR) $(DESTDIR)$(SITEPY)/bridgeui $(DIR) $(DESTDIR)$(SITEPY)/common $(DIR) $(DESTDIR)$(SITEPY)/touchy $(DIR) $(DESTDIR)$(SITEPY)/gscreen @@ -780,6 +781,7 @@ install-python: install-dirs $(DIR) $(DESTDIR)$(SITEPY)/pyui $(DIR) $(DESTDIR)$(SITEPY)/plasmac $(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/touchy/*.py $(DESTDIR)$(SITEPY)/touchy From 659ece4f82cf8724bd7c2c99482df5a7b5e0b137 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 10 Apr 2026 13:38:30 -0700 Subject: [PATCH 08/69] 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 | 79 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index cf2e79c1de0..4dae92b230b 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -162,6 +162,7 @@ static int axis_mask = 0; ARRAY(hal_bit_t,jjog_increment_minus,EMCMOT_MAX_JOINTS+1) /* Incremental jogging, negative direction */ \ \ FIELD(hal_float_t,ajog_speed) /* pin for setting the jog speed (halui internal) */ \ + FIELD(hal_float_t,ajog_speed_angular) /* pin for setting the angular jog speed (halui internal) */ \ ARRAY(hal_bit_t,ajog_minus,EMCMOT_MAX_AXIS+1) /* pin to jog in positive direction */ \ ARRAY(hal_bit_t,ajog_plus,EMCMOT_MAX_AXIS+1) /* pin to jog in negative direction */ \ ARRAY(hal_float_t,ajog_analog,EMCMOT_MAX_AXIS+1) /* pin for analog jogging (-1..0..1) */ \ @@ -254,6 +255,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]; @@ -1059,6 +1062,8 @@ int halui_hal_init(void) retval = halui_export_pin_IN_float(&(halui_data->ajog_speed), "halui.axis.jog-speed"); if (retval < 0) return retval; + retval = halui_export_pin_IN_float(&(halui_data->ajog_speed_angular), "halui.axis.jog-speed-angular"); + if (retval < 0) return retval; retval = halui_export_pin_IN_float(&(halui_data->ajog_deadband), "halui.axis.jog-deadband"); if (retval < 0) return retval; @@ -1709,6 +1714,7 @@ static void hal_init_pins() *(halui_data->ajog_increment_minus[EMCMOT_MAX_AXIS]) = old_halui_data.ajog_increment_minus[EMCMOT_MAX_AXIS] = 0; *(halui_data->ajog_deadband) = 0.2; *(halui_data->ajog_speed) = 0; + *(halui_data->ajog_speed_angular) = 0; *(halui_data->joint_selected) = 0; // select joint 0 by default *(halui_data->axis_selected) = -1; // select no axis by default @@ -1812,9 +1818,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); @@ -1890,8 +1942,10 @@ static void check_hal_changes() int js; hal_float_t 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 @@ -2158,6 +2212,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 @@ -2181,7 +2236,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 86d1314b753852b4a7f2d36a0b696e2a348a32be Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 12 Apr 2026 10:15:33 -0700 Subject: [PATCH 09/69] halui -use angular jograte pin/messages for angular axis now checks for axis type and uses the apropriate jograte --- lib/python/bridgeui/bridge.py | 9 ++++ src/emc/usr_intf/halui.cc | 98 ++++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 18 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index 56577ebb664..1d0edfaeddd 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -227,6 +227,15 @@ def isAxisSelected(self, index): letter = 'XYZABCUVW'[index] return int(self.axesSelected[letter]) + def getAxisIndexType(self, axis): + try: + num = self.INFO.GET_JOINT_NUM_FROM_AXIS_INDEX[axis] + flag = self.INFO.JOINT_TYPE_INT[num] + return flag + except: + return 1 + + def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 4dae92b230b..96293f63562 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -257,6 +257,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]; @@ -643,7 +644,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"); @@ -1931,6 +1932,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() @@ -1945,7 +1975,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 @@ -2241,16 +2270,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 ); @@ -2344,11 +2373,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; @@ -2358,7 +2394,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; @@ -2369,7 +2405,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; @@ -2379,7 +2415,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; } @@ -2387,7 +2423,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; } @@ -2436,12 +2472,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; + } + *(halui_data->axis_is_selected[axis_num]) = 1; if (*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 (*halui_data->ajog_minus[num_axes]) { - sendJogCont(axis_num, -internaljogspeed,JOGTELEOP); + sendJogCont(axis_num, -tempjogspeed,JOGTELEOP); } } } @@ -2492,8 +2536,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; @@ -2502,8 +2555,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; @@ -2513,7 +2575,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; } @@ -2521,7 +2583,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 56cd74499a20672ceaeb4caa3715b2b7a3b17faf Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 2 Jul 2026 19:42:10 -0700 Subject: [PATCH 10/69] bridge -look for macro commands too MDI and macro commands are similar enough to combine --- lib/python/bridgeui/bridge.py | 45 +++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py index 1d0edfaeddd..c9d8d4aac4b 100644 --- a/lib/python/bridgeui/bridge.py +++ b/lib/python/bridgeui/bridge.py @@ -63,14 +63,6 @@ def __init__(self, readAddress = "tcp://127.0.0.1:5690", self.init_read() self.init_write() - def update(self, *arg): - print(self, arg) - raw=arg[0]; row=arg[1];column=arg[2];state=arg[3] - LOG.debug('raw {}, row {}, col {}, state {}'.format(raw,row,column,state)) - print ('raw',raw,'row:',row,'column:',column,'state:',state) - self.writeMsg('set_selected_axis','Y') - self.activeJoint.set(10) - def init(self): self.jogRate = 0 self.jogRateAngular = 0 @@ -143,7 +135,7 @@ def action(self, msg, data): # send msg to hal_glib def writeMsg(self, msg, data): - print('Write Msg called') + #print('Write Msg called') if ZMQ: topic = self.writeTopic message = json.dumps({'FUNCTION':msg,'ARGS':data}) @@ -170,27 +162,43 @@ def ok(self): def cancel(self): self.writeMsg('request_cancel', True) + # if the number is bigger then MDI command list + # then look for MACRO commands def getMdiName(self, num): if num >len(self.INFO.MDI_COMMAND_DICT)-1: + offset = len(self.INFO.MDI_COMMAND_DICT) + return self.getMacroName(num-offset) + else: + temp = list(self.INFO.MDI_COMMAND_DICT.keys())[num] + LOG.debug('MDI:{} {}'.format(num,temp)) + return temp + + def getMacroName(self, num): + if num >len(self.INFO.MACRO_COMMAND_DICT)-1: return 'None' - temp = list(self.INFO.MDI_COMMAND_DICT.keys())[num] - LOG.debug('{} {}'.format(num,temp)) + temp = list(self.INFO.MACRO_COMMAND_DICT.keys())[num] + LOG.debug('MACRO:{} {}'.format(num,temp)) return temp - def getMacroNames(self): - for i in self.INFO.INI_MACROS: - name = i.split()[0] - LOG.debug('{} {}'.format(name,i)) - def runIndexedMacro(self, num): + # check for any MDI commands first: name = self.getMdiName(num) LOG.debug('Macro name:{} ,index: {}'.format(name, num)) if name != 'None': self.writeMsg('request_macro_call', name) + # else look for any MACRO commands: + else: + offset = len(self.INFO.MDI_COMMAND_DICT) + name = self.getMacroName(num-offset) + LOG.debug('Macro name:{} ,index: {}'.format(name, num)) + if name != 'None': + self.writeMsg('request_macro_call', name) + + # cound of MDI and MACRO commands def getMdiCount(self): - print(len(self.INFO.MDI_COMMAND_DICT)) - return len(self.INFO.MDI_COMMAND_DICT) + #print('->',len(self.INFO.MDI_COMMAND_DICT),len(self.INFO.MACRO_COMMAND_DICT)) + return len(self.INFO.MDI_COMMAND_DICT) + len(self.INFO.MACRO_COMMAND_DICT) def getJogRate(self): return self.jogRate @@ -202,6 +210,7 @@ def getJogRateAngular(self): def setJogRateAngular(self, value): self.writeMsg('set_jograte_angular', value) + # XYZABCUVW, None, or MPG0 def getSelectedAxis(self): name = self.currentSelectedAxis if name == 'None': From 1e3b9c697133cf2c346bd79f31db3d810d992b24 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 2 Jul 2026 19:51:13 -0700 Subject: [PATCH 11/69] 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 190eb34aa40d5dc1e955d24c20c53ab2ea73ceb5 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 14:11:26 -0700 Subject: [PATCH 12/69] halui -add reload display and shutdown pins --- lib/python/bridgeui/bridge.py | 8 ++++- src/emc/usr_intf/halui.cc | 55 ++++++++++++++++++++++++++++++++--- 2 files changed, 58 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 96293f63562..41fddbba8a7 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -216,6 +216,8 @@ static int axis_mask = 0; \ FIELD(hal_bit_t,gui_ok) /* pin for acknowledging dialog ok */ \ FIELD(hal_bit_t,gui_cancel) /* pin for acknowledging dialog cancel */ \ + FIELD(hal_bit_t,gui_reload) /* pin for acknowledging dialog ok */ \ + FIELD(hal_bit_t,gui_shutdown) /* pin for acknowledging dialog cancel */ \ \ FIELD(hal_float_t,units_per_mm) \ @@ -561,7 +563,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"); @@ -576,7 +578,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"); @@ -591,7 +593,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"); @@ -605,7 +607,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"); @@ -615,6 +617,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 @@ -1085,6 +1115,12 @@ int halui_hal_init(void) retval = halui_export_pin_IN_bit(&(halui_data->gui_cancel), "halui.gui.cancel"); if (retval < 0) return retval; + retval = halui_export_pin_IN_bit(&(halui_data->gui_reload), "halui.gui.reload-display"); + if (retval < 0) return retval; + + retval = halui_export_pin_IN_bit(&(halui_data->gui_shutdown), "halui.gui.shutdown"); + if (retval < 0) return retval; + hal_ready(comp_id); return 0; } @@ -2610,6 +2646,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 7e20c6e36d7c5ca09b378d47026a16edddeb51a1 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Tue, 14 Jul 2026 21:36:44 -0700 Subject: [PATCH 13/69] halui -add softkey pins and message --- lib/python/bridgeui/bridge.py | 8 ++++++-- src/emc/usr_intf/halui.cc | 37 ++++++++++++++++++++++++++++++++++- 2 files changed, 42 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 41fddbba8a7..70cdd2b0746 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 #pragma GCC diagnostic push #if defined(__GNUC__) && (__GNUC__ > 4) @@ -213,6 +214,7 @@ static int axis_mask = 0; \ ARRAY(hal_bit_t,mdi_commands,MDI_MAX) \ ARRAY(hal_bit_t,gui_mdi_commands,MDI_MAX) \ + ARRAY(hal_bit_t,gui_soft_keys,SOFTKEY_MAX) \ \ FIELD(hal_bit_t,gui_ok) /* pin for acknowledging dialog ok */ \ FIELD(hal_bit_t,gui_cancel) /* pin for acknowledging dialog cancel */ \ @@ -269,6 +271,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 @@ -1109,6 +1113,11 @@ int halui_hal_init(void) if (retval < 0) return retval; } + for (int n=0; ngui_soft_keys[n]), comp_id, "halui.gui.softkey-%d", n); + if (retval < 0) return retval; + } + retval = halui_export_pin_IN_bit(&(halui_data->gui_ok), "halui.gui.ok"); if (retval < 0) return retval; @@ -1968,6 +1977,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 @@ -2629,7 +2656,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); @@ -2637,6 +2664,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 e87fc00540e3af4f1e57cb573e41094af4940a0c Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 16 Aug 2025 11:12:19 -0700 Subject: [PATCH 14/69] gladevcp -speedcontrol: put Gstat status in the loop Gstat sends out socket messages for jog rate --- lib/python/gladevcp/speedcontrol.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index 332f025ab0d..5dda24b65a3 100755 --- a/lib/python/gladevcp/speedcontrol.py +++ b/lib/python/gladevcp/speedcontrol.py @@ -35,6 +35,8 @@ else: from .hal_widgets import _HalSpeedControlBase +from gladevcp.core import Status, Action + class SpeedControl(Gtk.Box, _HalSpeedControlBase): ''' The SpeedControl Widget serves as a slider with button to increment od decrease @@ -98,6 +100,8 @@ class SpeedControl(Gtk.Box, _HalSpeedControlBase): "%.1f", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT), 'do_hide_button' : ( GObject.TYPE_BOOLEAN, 'Hide the button', 'Display the button + and - to alter the values', False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT), + 'type' : ( GObject.TYPE_INT, 'Type of adjustment', 'Set to -1 for general, 0 for jograte', + -1, 0, -1, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT), } __gproperties = __gproperties__ @@ -112,6 +116,9 @@ class SpeedControl(Gtk.Box, _HalSpeedControlBase): def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, unit = "", color = "#FF8116", template = "%.1f"): super(SpeedControl, self).__init__() + self._action = Action() + self._status = Status() + # basic settings self._size = size self._value = value @@ -123,6 +130,7 @@ def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, un self._increment = (self._max - self._min) / 100.0 self._template = template self._speed = inc_speed + self.type_linear_jog = False self.adjustment = Gtk.Adjustment(value = self._value, lower = self._min, upper = self._max, step_increment = self._increment, page_increment = 0) self.adjustment.connect("value_changed", self._on_value_changed) @@ -175,6 +183,10 @@ def _hal_init(self): self.hal_pin_decrease = self.hal.newpin(self.hal_name+".decrease", hal.HAL_BIT, hal.HAL_IN) self.hal_pin_decrease.connect("value-changed", self._on_minus_changed) + if self.type_linear_jog: + print('->>',self.type_linear_jog) + self._status.connect('jograte-changed', lambda w, data: self.set_value(data)) + # this draws our widget on the screen def expose(self, widget, event): # create the cairo window @@ -269,6 +281,9 @@ def get_value(self): # we are not sync, so def _on_value_changed(self, widget): value = widget.get_value() + if self.type_linear_jog: + self._action.SET_JOG_RATE(value) + if value != self._value: self._value = value self.set_value(self._value) @@ -453,6 +468,10 @@ def do_set_property(self, property, value): self._template = value if name == "do_hide_button": self.hide_button(value) + if name == "type": + print(name,value) + if value == 0: + self.type_linear_jog = True self._draw_widget() else: raise AttributeError('unknown property %s' % property.name) From 6904f0535f8d4fbc4bb8d7ed46f1efaf63004fa0 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:05:52 -0700 Subject: [PATCH 15/69] gladevcp -gth_action: add ability to run new style INI MDI commands named INI MDI commands were not recognised. --- lib/python/gladevcp/gtk_action.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index 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 14b696956933232eb22111b0a74cb48d5fbaa667 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 18:10:59 -0700 Subject: [PATCH 16/69] 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 ee49f2c33f5..e7e6f14e595 100644 --- a/lib/python/qtvcp/qt_action.py +++ b/lib/python/qtvcp/qt_action.py @@ -231,7 +231,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) @@ -248,6 +248,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)) @@ -287,6 +290,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 7f13703026eb074f95c8a268b3d74cc865ed7ddd Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 10 Apr 2026 13:40:16 -0700 Subject: [PATCH 17/69] gladevcp -speedcontrol -add angular jograte type automatically sends and responds to jograte messages --- lib/python/gladevcp/speedcontrol.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index 5dda24b65a3..a30cc26dd3b 100755 --- a/lib/python/gladevcp/speedcontrol.py +++ b/lib/python/gladevcp/speedcontrol.py @@ -100,8 +100,8 @@ class SpeedControl(Gtk.Box, _HalSpeedControlBase): "%.1f", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT), 'do_hide_button' : ( GObject.TYPE_BOOLEAN, 'Hide the button', 'Display the button + and - to alter the values', False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT), - 'type' : ( GObject.TYPE_INT, 'Type of adjustment', 'Set to -1 for general, 0 for jograte', - -1, 0, -1, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT), + 'type' : ( GObject.TYPE_INT, 'Type of adjustment', 'Set to -1 for general, 0 for jograte, 1 for angular jograte', + -1, 1, -1, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT), } __gproperties = __gproperties__ @@ -131,6 +131,7 @@ def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, un self._template = template self._speed = inc_speed self.type_linear_jog = False + self.type_angular_jog = False self.adjustment = Gtk.Adjustment(value = self._value, lower = self._min, upper = self._max, step_increment = self._increment, page_increment = 0) self.adjustment.connect("value_changed", self._on_value_changed) @@ -184,8 +185,11 @@ def _hal_init(self): self.hal_pin_decrease.connect("value-changed", self._on_minus_changed) if self.type_linear_jog: - print('->>',self.type_linear_jog) + print('linear jograte ->>',self.type_linear_jog) self._status.connect('jograte-changed', lambda w, data: self.set_value(data)) + elif self.type_angular_jog: + print('Angular jograte ->>',self.type_angular_jog) + self._status.connect('jograte-angular-changed', lambda w, data: self.set_value(data)) # this draws our widget on the screen def expose(self, widget, event): @@ -472,6 +476,10 @@ def do_set_property(self, property, value): print(name,value) if value == 0: self.type_linear_jog = True + self.type_angular_jog = False + elif value == 1: + self.type_linear_jog = False + self.type_angular_jog = True self._draw_widget() else: raise AttributeError('unknown property %s' % property.name) From ff5531a06074faef19986af2e794c23c7d0405f1 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 12 Apr 2026 10:07:58 -0700 Subject: [PATCH 18/69] gladevcp -speedcontrol: add angular jograte command forgot to add the command to output the rate whenn \the widget is changed --- lib/python/gladevcp/speedcontrol.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index a30cc26dd3b..36cde39a08e 100755 --- a/lib/python/gladevcp/speedcontrol.py +++ b/lib/python/gladevcp/speedcontrol.py @@ -285,8 +285,11 @@ def get_value(self): # we are not sync, so def _on_value_changed(self, widget): value = widget.get_value() + if self.type_linear_jog: self._action.SET_JOG_RATE(value) + elif self.type_angular_jog: + self._action.SET_JOG_RATE_ANGULAR(value) if value != self._value: self._value = value From 2c3cfe1b284bfcc20f852dfb2c5cdf34359ccbed Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 14:12:15 -0700 Subject: [PATCH 19/69] gladevcp -hal_gremlin: clear the plot if reloading the screen --- lib/python/gladevcp/hal_gremlin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/python/gladevcp/hal_gremlin.py b/lib/python/gladevcp/hal_gremlin.py index c0cfd5675ea..d7badb66747 100644 --- a/lib/python/gladevcp/hal_gremlin.py +++ b/lib/python/gladevcp/hal_gremlin.py @@ -139,6 +139,7 @@ def __init__(self, *a, **kw): def reloadfile(self,w): try: self.fileloaded(None,self._reload_filename) + self.clear_live_plotter() except: pass self.gstat.emit('graphics-gcode-properties',self.gcode_properties) From be29d12981dcf00898c8e8053d1a71c0b84e4214 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 10:20:22 -0700 Subject: [PATCH 20/69] gladevcp gtk_action -get shutdown to work reliably gnome-sessions-quit wil not pop up consistantly withoout the gtk dialog --- lib/python/gladevcp/gtk_action.py | 36 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index ae56852891a..834b62ac665 100644 --- a/lib/python/gladevcp/gtk_action.py +++ b/lib/python/gladevcp/gtk_action.py @@ -616,21 +616,29 @@ def ADJUST_GRAPHICS_PAN(self, x, y): def ADJUST_GRAPHICS_ROTATE(self, x, y): STATUS.emit('graphics-view-changed', 'rotate-view', {'X': x, 'Y': y}) + #TODO without the gtk dialog, gnome-sessions + # does not start reliably def SHUT_SYSTEM_DOWN_PROMPT(self): - import subprocess - try: - try: - subprocess.call('gnome-session-quit --power-off', shell=True) - except: - try: - subprocess.call('xfce4-session-logout', shell=True) - except: - try: - subprocess.call('systemctl poweroff', shell=True) - except: - raise - except Exception as e: - LOG.warning("Couldn't shut system down: {}".format(e)) + import shutil + import time + + dialog = gtk.MessageDialog(parent=None, + message_type=gtk.MessageType.QUESTION, + buttons=gtk.ButtonsType.YES_NO, + text='Shutdown System?') + dialog.set_keep_above(True) + dialog.format_secondary_text('Unsaved data will be lost') + response = dialog.run() + dialog.destroy() + if response == gtk.ResponseType.YES: + + if shutil.which('gnome-session-quit'): + subprocess.run(["gnome-session-quit", "--power-off"]) + elif shutil.which('xfce4-session-logout'): + subprocess.call('xfce4-session-logout', shell=True) + else: + # force a shutdown - no prompt + subprocess.call('systemctl poweroff', shell=True) def SHUT_SYSTEM_DOWN_NOW(self): import subprocess From fb58d41be7fa98fd4bbfe9db5a30c484c16ca462 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:07:38 -0700 Subject: [PATCH 21/69] 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 e0f9735afb9..25a7e3f5452 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 d5cfc8004cf42afa739178721f3ac5d6c5a688ea Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 13 Dec 2025 20:42:40 -0800 Subject: [PATCH 22/69] 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 69cf20bca0db1e09e819d9754c5b7f71276a84ac Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 27 May 2026 19:04:21 -0700 Subject: [PATCH 23/69] 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 2eab89c135adddcf47fee98a3fb09f272de7e586 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 8 Jul 2026 23:06:07 -0700 Subject: [PATCH 24/69] 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 25a7e3f5452..a0fa1028c1b 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 676c6fefaefc8681ff7b370c511f83ec63566f4c Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:04:00 -0700 Subject: [PATCH 25/69] iniinfo -parse ini commands in a better way mdi commands with a comma in it (ie MSG, text) would not be interpeted properly --- lib/python/common/iniinfo.py | 45 ++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/python/common/iniinfo.py b/lib/python/common/iniinfo.py index 57b78707bd7..c81118f6e37 100644 --- a/lib/python/common/iniinfo.py +++ b/lib/python/common/iniinfo.py @@ -18,7 +18,7 @@ def __init__(self, ini=None): global LOG LOG = logger.getLogger(__name__) # Force the log level for this module only - #LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL + LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL inipath = os.environ.get('INI_FILE_NAME', '/dev/null') self.LINUXCNC_IS_RUNNING = bool(inipath != '/dev/null') @@ -570,7 +570,7 @@ def update(self): if self.INI.hassection('MDI_COMMAND_LIST'): try: for key,value in self.INI.getvariables('MDI_COMMAND_LIST'): - + #print(f'key:{key},value:{value}') # legacy way: list of repeat 'MDI_COMMAND=XXXX' # in this case order matters in the INI if key == 'MDI_COMMAND': @@ -597,24 +597,51 @@ def update(self): # new way: 'MDI_COMMAND_SSS = XXXX' (SSS being any string) # order of commands doesn't matter in the INI + + # here are some samples, the last three are difficult + # the third is invalid + # MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + # cmd: G53 G0 Z0;G53 G0 X0 Y0 label: Goto\nMachn\nZero + + # MDI_COMMAND_MACRO2 = (MSG, macro 2); + # cmd: (MSG, macro 2); label: + + # MDI_COMMAND_MACRO3 = (MSG, macro 2) + # cmd: (MSG label: macro 2) + + # MDI_COMMAND_MACRO4 = (MSG, macro 2),test + # cmd: (MSG, macro 2) label: test + else: self.MDI_COMMAND_LIST.append(None) self.MDI_COMMAND_LABEL_LIST.append(None) try: name = (key.replace('MDI_COMMAND_','')) mdidatadict = {} - for num,k in enumerate(value.split(',')): - if num == 0: - mdidatadict['cmd'] = k - if len(value.split(',')) <2: - mdidatadict['label'] = None - else: - mdidatadict['label'] = k + #print(f'name:{name}') + # find the last colon in string or 0 + lastCmd = value.rfind(';') + #print('l ;:',lastCmd) + if lastCmd == -1: lastCmd = 0 + + # find the last colon in string or use the string length + lastComma = value.rfind(',', lastCmd) + #print('l comma:',lastComma,lastCmd) + if lastComma == -1: lastComma = len(value) + + label = value[lastComma+1:] + cmd = value[:lastComma] + #print(value,' cmd:',cmd,' label:',label) + + mdidatadict['cmd'] = cmd + mdidatadict['label'] = label self.MDI_COMMAND_DICT[name] = mdidatadict + except Exception as e: LOG.error('INI MDI command parse error:{}'.format(e)) except Exception as e: LOG.error('INI MDI command parse error:{}'.format(e)) + print(self.MDI_COMMAND_DICT) ################ # MACRO commands # From c4491ea769366a72661b1340c5bccdd6d3a61bd2 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 22 Aug 2025 15:33:19 -0700 Subject: [PATCH 26/69] 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 d778a567e61..eed86470af3 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -153,7 +153,7 @@ def __init__(self, halcomp, widgets, paths): STATUS.connect('status-message', lambda w, d, o: self.add_external_status(d,o)) STATUS.connect('runstop-line-changed', lambda w, l :self.lastRunLine(l)) STATUS.connect('cycle-start-request', lambda w, state :self.btn_start_clicked(state)) - STATUS.connect('cycle-pause-request', lambda w, state: self.btn_pause_clicked(state)) + STATUS.connect('cycle-pause-request', lambda w, state: self.ext_pause_toggled(state)) STATUS.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) 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)) @@ -1318,6 +1318,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 32e62c8fcae87bc92315c0d31675822fdf63ddb6 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 23 Aug 2025 13:57:34 -0700 Subject: [PATCH 27/69] qtdragon metric -add a control panel --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 17 ++ configs/sim/qtdragon/qtdragon_xyz/panel.ui | 237 ++++++++++++++++++ .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 3 +- 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 configs/sim/qtdragon/qtdragon_xyz/panel.hal create mode 100644 configs/sim/qtdragon/qtdragon_xyz/panel.ui diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal new file mode 100644 index 00000000000..e9407a91feb --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -0,0 +1,17 @@ +net rate halui.axis.jog-speed panel.Jog-rate-f + +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z + +net jog-p halui.axis.selected.plus +net jog-m halui.axis.selected.minus +net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 + +net pause halui.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui new file mode 100644 index 00000000000..d9200fc614e --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -0,0 +1,237 @@ + + + MainWindow + + + + 0 + 0 + 313 + 467 + + + + MainWindow + + + + + + + + + Axis Selection + + + + + + X + + + axis-x + + + + + + + Y + + + axis-y + + + + + + + Z + + + axis-z + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + 300 + + + Qt::Horizontal + + + Jog-rate + + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + + + 0 + 0 + 313 + 22 + + + + + + + + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Slider + QSlider +
qtvcp.widgets.simple_widgets
+
+
+ + +
diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index f437be0811a..c62a2dd16a9 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini @@ -166,7 +166,8 @@ POSTGUI_HALFILE = qtdragon_postgui.hal # you can add multiple entries # uncomment this one to print all HAL pins that start with qt #POSTGUI_HALCMD = show pin qt - +POSTGUI_HALCMD = show pin halui.gui +POSTGUI_HALCMD = loadusr qtvcp -H panel.hal panel [HALUI] # no content From 1c85e81f2287ea12bc6d48fa1aee3b49021bd6f3 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:24:15 -0700 Subject: [PATCH 28/69] qtdragon -update test config test panel --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 24 ++-- configs/sim/qtdragon/qtdragon_xyz/panel.ui | 130 +++++++++++++++++- .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 4 +- 3 files changed, 144 insertions(+), 14 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index e9407a91feb..a5c68d8da0c 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -1,17 +1,19 @@ -net rate halui.axis.jog-speed panel.Jog-rate-f +net rate halui.axis.jog-speed panel.Jog-rate-f -net sx halui.axis.x.select panel.axis-x -net sy halui.axis.y.select panel.axis-y -net sz halui.axis.z.select panel.axis-z +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z -net jog-p halui.axis.selected.plus -net jog-m halui.axis.selected.minus -net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 -net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg -net pause halui.cycle.start panel.cycle-start -net start halui.cycle.pause panel.cycle-pause +#net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +#net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +#net m2 halui.gui.mdi-command-0 panel.mdi-2 -net cancel halui.gui.cancel panel.cancel +net pause halui.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause + +net cancel halui.gui.cancel panel.cancel net ok halui.gui.ok panel.ok diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index d9200fc614e..c671b279767 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -23,11 +23,33 @@ Axis Selection + + + + None + + + true + + + true + + + axis-none + + + X + + true + + + true + axis-x @@ -38,6 +60,12 @@ Y + + true + + + true + axis-y @@ -48,6 +76,12 @@ Z + + true + + + true + axis-z @@ -146,6 +180,90 @@ + + + + Mode Comands + + + + + + Manual + + + true + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + true + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + true + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + @@ -163,7 +281,7 @@ - + pause @@ -172,6 +290,16 @@ + + + + Abort + + + cycle-abort + + + diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index c62a2dd16a9..7dce4b83a04 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini @@ -10,7 +10,7 @@ DEBUG = 0x00000000 # sets qtdragon as screen. for debug output to terminal add -d or -v # sets window title # sets icon in task manager -DISPLAY = qtvcp qtdragon +DISPLAY = qtvcp -d qtdragon TITLE = QtDragon XYZ Metric ICON = silver_dragon.png @@ -167,7 +167,7 @@ POSTGUI_HALFILE = qtdragon_postgui.hal # uncomment this one to print all HAL pins that start with qt #POSTGUI_HALCMD = show pin qt POSTGUI_HALCMD = show pin halui.gui -POSTGUI_HALCMD = loadusr qtvcp -H panel.hal panel +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel [HALUI] # no content From b5c817cb98af31e16f2eb33d70ee6b3d0ca49e06 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 5 Sep 2025 20:40:47 -0700 Subject: [PATCH 29/69] qtdragon -test files update --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 11 ++++++++--- configs/sim/qtdragon/qtdragon_xyz/panel.ui | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index a5c68d8da0c..04bc14e8e26 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -1,4 +1,4 @@ -net rate halui.axis.jog-speed panel.Jog-rate-f +net rate halui.axis.jog-speed panel.jog-rate net sx halui.axis.x.select panel.axis-x net sy halui.axis.y.select panel.axis-y @@ -7,12 +7,17 @@ net sz halui.axis.z.select panel.axis-z net jog-p halui.axis.selected.plus panel.jog-pos net jog-m halui.axis.selected.minus panel.jog-neg -#net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 -#net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 #net m2 halui.gui.mdi-command-0 panel.mdi-2 +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + net pause halui.cycle.start panel.cycle-start net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort net cancel halui.gui.cancel panel.cancel net ok halui.gui.ok panel.ok diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index c671b279767..74a4afbef7d 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -6,8 +6,8 @@ 0 0 - 313 - 467 + 404 + 560 @@ -126,15 +126,15 @@ - - - 300 - + Qt::Horizontal - Jog-rate + jog-rate + + + true @@ -341,7 +341,7 @@ 0 0 - 313 + 404 22 @@ -355,9 +355,9 @@
qtvcp.widgets.simple_widgets
- Slider + StatusSlider QSlider -
qtvcp.widgets.simple_widgets
+
qtvcp.widgets.status_slider
From 8e022b6d8801d3d0b7a75c8a4887657e767a6aea Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Sep 2025 22:34:12 -0700 Subject: [PATCH 30/69] qtdragon -add halui test sim --- .../qtdragon_xyz/qtdragon_halui_test.ini | 267 ++++++++++++++++++ .../qtdragon/qtdragon_xyz/qtdragon_metric.ini | 5 +- 2 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini new file mode 100644 index 00000000000..e7a674f81b2 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini @@ -0,0 +1,267 @@ +# This file was created with the 7i96 Wizard on Jun 10 2019 11:12:47 +# Changes to most things are ok and will be read by the wizard + +[EMC] +VERSION = 1.1 +MACHINE = qtdragon_metric +DEBUG = 0x00000000 + +[DISPLAY] +# sets qtdragon as screen. for debug output to terminal add -d or -v +# sets window title +# sets icon in task manager +DISPLAY = qtvcp -d qtdragon +TITLE = QtDragon XYZ Metric +ICON = silver_dragon.png + +# qtdragon saves most preference to this file +PREFERENCE_FILE_PATH = WORKINGFOLDER/qtdragon.pref + +# min/max percentage overrides allowed in qtdragon 1 = 100% +MAX_FEED_OVERRIDE = 1.2 +MIN_SPINDLE_0_OVERRIDE = 0.5 +MAX_SPINDLE_0_OVERRIDE = 1.2 + +# manual spindle speed will start at this RPM +DEFAULT_SPINDLE_0_SPEED = 12000 + +# spindle up/down increment in RPM +SPINDLE_INCREMENT = 200 + +# min max apindle speed manually allowed +MIN_SPINDLE_0_SPEED = 1000 +MAX_SPINDLE_0_SPEED = 20000 + +# max spindle power in Watts +MAX_SPINDLE_POWER = 2000 + +# min/max/default jog velocities in qtdragon in units/sec +MIN_LINEAR_VELOCITY = 0 +MAX_LINEAR_VELOCITY = 60.00 +DEFAULT_LINEAR_VELOCITY = 50.0 + +# incremental jog step length options +INCREMENTS = 10 mm, 1.0 mm, 0.10 mm, 0.01 mm, 1.0 inch, 0.1 inch, 0.01 inch + +# Display grid increments +GRIDS = 0, .1 mm, 1 mm, 2 mm, 5 mm, 10 mm, .25 in, .5 in + +CYCLE_TIME = 100 +INTRO_GRAPHIC = silver_dragon.png +INTRO_TIME = 2 + +# default program search path +PROGRAM_PREFIX = ~/linuxcnc/nc_files + +# NGCGUI subroutine path. +# Thr path must also be in [RS274NGC] SUBROUTINE_PATH +NGCGUI_SUBFILE_PATH = ../../../nc_files/ngcgui_lib/ +# pre selected programs tabs +# specify filenames only, files must be in the NGCGUI_SUBFILE_PATH +NGCGUI_SUBFILE = slot.ngc +NGCGUI_SUBFILE = qpocket.ngc + +# qtdragon saves MDI cxommands to this file +MDI_HISTORY_FILE = mdi_history.dat +# qtdragon saves rnning logs to this file +LOG_FILE = qtdragon.log + +# optional user dialogs (3), controlled by HAL pins +MESSAGE_BOLDTEXT = Critical and Persistent +MESSAGE_TEXT = This is a persistent dialog test +MESSAGE_DETAILS = There seems to be something wrong\n You must fix it to clear message +MESSAGE_TYPE = nonedialog +MESSAGE_PINNAME = nonedialogtest +MESSAGE_ICON = CRITICAL + +MESSAGE_BOLDTEXT = Do You Want To Make A Choice? +MESSAGE_TEXT = This is a yes no dialog test +MESSAGE_DETAILS = Y/N DETAILS +MESSAGE_TYPE = yesnodialog +MESSAGE_PINNAME = yndialogtest +MESSAGE_ICON = QUESTION + +MESSAGE_BOLDTEXT = This is an information message +MESSAGE_TEXT = This is low priority +MESSAGE_DETAILS = press ok to clear +MESSAGE_TYPE = okdialog status +MESSAGE_PINNAME = bothtest +MESSAGE_ICON = INFO + +# optional tab showing an external qtvcp panel +EMBED_TAB_NAME=Vismach demo +EMBED_TAB_COMMAND=qtvcp vismach_mill_xyz +EMBED_TAB_LOCATION=tabWidget_utilities + +[MDI_COMMAND_LIST] +# for macro buttons on main oage up to 10 possible +MDI_COMMAND_MACRO0 = G0 Z25;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + +[FILTER] +# Controls what programs are shown inqtdragon file manager +PROGRAM_EXTENSION = .ngc,.nc,.tap G-Code File (*.ngc,*.nc,*.tap) +PROGRAM_EXTENSION = .png,.gif,.jpg Greyscale Depth Image +PROGRAM_EXTENSION = .py Python Script + +# specifies what special 'filter' programs runs based on program ending +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +[KINS] +KINEMATICS = trivkins coordinates=XYZ +JOINTS = 3 + +[EMCIO] +TOOL_TABLE = tool.tbl + +[RS274NGC] +# motion controller saves parameters to this file +PARAMETER_FILE = qtdragon.var + +# start up G/M codes when first loaded +RS274NGC_STARTUP_CODE = G17 G21 G40 G43H0 G54 G64P0.0127 G80 G90 G94 G97 M5 M9 + +# subroutine/remap path list +SUBROUTINE_PATH = ../../../../nc_files/probe/basic_probe/macros:~/linuxcnc/nc_files/examples/ngcgui_lib:~/linuxcnc/nc_files/examples/ngcgui_lib/utilitysubs + +# on abort, this ngc file is called. required for basic/versa probe +ON_ABORT_COMMAND=O call + +[EMCMOT] +EMCMOT = motmod +SERVO_PERIOD = 1000000 +COMM_TIMEOUT = 1.0 +COMM_WAIT = 0.010 +BASE_PERIOD = 100000 + +[TASK] +TASK = milltask +CYCLE_TIME = 0.010 + +[TRAJ] +COORDINATES = XYZ +LINEAR_UNITS = metric +ANGULAR_UNITS = degree +MAX_LINEAR_VELOCITY = 60.00 +DEFAULT_LINEAR_VELOCITY = 50.00 +SPINDLES = 1 + +[HAL] +HALUI = halui +#HALBRIDGE = hal_bridge + +# loads the HAL machine simulation +HALFILE = core_sim.hal +HALFILE = simulated_home.hal + +# this file is loaded after qtdragon has made it's HAl pins +# you can add multiple entries +POSTGUI_HALFILE = qtdragon_postgui.hal + +# this command is run after qtdragon has made it's HAl pins +# any HAL conmmand can be used +# you can add multiple entries +# uncomment this one to print all HAL pins that start with qt +#POSTGUI_HALCMD = show pin qt +POSTGUI_HALCMD = show pin halui.gui +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel +[HALUI] +# no content + +[PROBE] +# pick basic probe or versa probe or remove for none +#USE_PROBE = versaprobe +USE_PROBE = basicprobe + +[AXIS_X] +MIN_LIMIT = -0.001 +MAX_LIMIT = 520.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 + +[AXIS_Y] +MIN_LIMIT = -0.001 +MAX_LIMIT = 630.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 + +[AXIS_Z] +# used by external offsets for auto spindle lift +OFFSET_AV_RATIO = 0.2 +MIN_LIMIT = -115.0 +MAX_LIMIT = 10.0 +MAX_VELOCITY = 40.0 +MAX_ACCELERATION = 500.0 + +[JOINT_0] +AXIS = X +MIN_LIMIT = -0.001 +MAX_LIMIT = 520.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 +TYPE = LINEAR +SCALE = 160.0 +STEPGEN_MAX_VEL = 72.0 +STEPGEN_MAX_ACC = 600.0 +FERROR = 1.0 +MIN_FERROR = 0.5 +MAX_OUTPUT = 0 +MAX_ERROR = 0.0127 +HOME = 20.0 +HOME_OFFSET = 0.00000 +HOME_SEARCH_VEL = 20.000000 +HOME_LATCH_VEL = 10.000 +HOME_SEQUENCE = 1 +HOME_USE_INDEX = False +HOME_IGNORE_LIMITS = False +HOME_IS_SHARED = 1 + +[JOINT_1] +AXIS = Y +MIN_LIMIT = -0.001 +MAX_LIMIT = 630.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 +TYPE = LINEAR +SCALE = 160.0 +STEPGEN_MAX_VEL = 72.0 +STEPGEN_MAX_ACC = 600.0 +FERROR = 1.0 +MIN_FERROR = 0.5 +MAX_OUTPUT = 0 +MAX_ERROR = 0.0127 +HOME = 20.0 +HOME_OFFSET = 0.000000 +HOME_SEARCH_VEL = 20.00 +HOME_LATCH_VEL = 10.00 +HOME_SEQUENCE = 2 +HOME_USE_INDEX = False +HOME_IGNORE_LIMITS = False + +[JOINT_2] +AXIS = Z +MIN_LIMIT = -115.0 +MAX_LIMIT = 10.0 +MAX_VELOCITY = 40.0 +MAX_ACCELERATION = 500.0 +TYPE = LINEAR +SCALE = 160.0 +STEPGEN_MAX_VEL = 48.0 +STEPGEN_MAX_ACC = 600.0 +FERROR = 1.0 +MIN_FERROR = 0.5 +MAX_OUTPUT = 0 +MAX_ERROR = 0.0127 +HOME = -10.0 +HOME_OFFSET = 0.000000 +HOME_SEARCH_VEL = 20.000000 +HOME_LATCH_VEL = 10.00 +HOME_SEQUENCE = 0 +HOME_USE_INDEX = False +HOME_IGNORE_LIMITS = False +HOME_IS_SHARED = 1 + + diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index 7dce4b83a04..f437be0811a 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini @@ -10,7 +10,7 @@ DEBUG = 0x00000000 # sets qtdragon as screen. for debug output to terminal add -d or -v # sets window title # sets icon in task manager -DISPLAY = qtvcp -d qtdragon +DISPLAY = qtvcp qtdragon TITLE = QtDragon XYZ Metric ICON = silver_dragon.png @@ -166,8 +166,7 @@ POSTGUI_HALFILE = qtdragon_postgui.hal # you can add multiple entries # uncomment this one to print all HAL pins that start with qt #POSTGUI_HALCMD = show pin qt -POSTGUI_HALCMD = show pin halui.gui -POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel + [HALUI] # no content From 4aa13ad3ee4b26a5766e7abe18f5be8ab08c554e Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 15 Jul 2026 19:01:04 -0700 Subject: [PATCH 31/69] test panel - qtdragon: make mode buttons not checkable --- configs/sim/qtdragon/qtdragon_xyz/panel.ui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index 74a4afbef7d..7264d58f6d0 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -192,7 +192,7 @@ Manual - true + false true @@ -217,7 +217,7 @@ MDI - true + false true @@ -242,7 +242,7 @@ Auto - true + false true From 11e4724b3c5ca7507a7e56bc9a4b3aff0e6f96c1 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 12 Dec 2025 20:15:21 -0800 Subject: [PATCH 32/69] 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 eed86470af3..93ccb809245 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -1469,9 +1469,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 41ab29b787e8425a7863e3911ba04720fa13bf76 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 12 Dec 2025 22:06:22 -0800 Subject: [PATCH 33/69] sim panel -add scale buttons and MPG dial --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 13 ++ configs/sim/qtdragon/qtdragon_xyz/panel.ui | 243 ++++++++++++++++---- 2 files changed, 206 insertions(+), 50 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index 04bc14e8e26..7c7b85458c9 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -1,8 +1,21 @@ net rate halui.axis.jog-speed panel.jog-rate net sx halui.axis.x.select panel.axis-x +net sx axis.x.jog-enable net sy halui.axis.y.select panel.axis-y +net sy axis.y.jog-enable net sz halui.axis.z.select panel.axis-z +net sz axis.z.jog-enable + +net mpg-scale axis.x.jog-scale panel.mpg-scale +net mpg-scale axis.y.jog-scale +net mpg-scale axis.z.jog-scale + +net mpg-count panel.mpg-wheel-s +net mpg-count qtdragon.mpg-in +net mpg-count axis.x.jog-counts +net mpg-count axis.y.jog-counts +net mpg-count axis.z.jog-counts net jog-p halui.axis.selected.plus panel.jog-pos net jog-m halui.axis.selected.minus panel.jog-neg diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index 7264d58f6d0..c5447316fb4 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -6,8 +6,8 @@ 0 0 - 404 - 560 + 418 + 587 @@ -91,55 +91,190 @@ - - - Axis Jog - - - - - - + - - - jog-pos - - - - - - - - - - - jog-neg - - - - - + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + - - - jog rate - - - - - - Qt::Horizontal - - - jog-rate - - - true - - - - - + + + + + MPG + + + + + + .001 + + + true + + + true + + + true + + + mpg-scale-small + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.001000000000000 + + + buttonGroup_mpgscale + + + + + + + .01 + + + true + + + true + + + mpg-scale-med + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.010000000000000 + + + buttonGroup_mpgscale + + + + + + + .1 + + + true + + + true + + + mpg-scale-large + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.100000000000000 + + + buttonGroup_mpgscale + + + + + + + + + + + 0 + 74 + + + + false + + + true + + + false + + + mpg-wheel + + + + @@ -341,7 +476,7 @@ 0 0 - 404 + 418 22 @@ -354,6 +489,11 @@ QPushButton
qtvcp.widgets.simple_widgets
+ + Dial + QDial +
qtvcp.widgets.simple_widgets
+
StatusSlider QSlider @@ -362,4 +502,7 @@ + + + From 05b9cb059016d21291662cff54bc36dcdcb91ee2 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 13 Dec 2025 20:46:51 -0800 Subject: [PATCH 34/69] qtdragon -find the currently visible dialog to send messages to halui 'ok' or 'cancel' messages --- share/qtvcp/screens/qtdragon/qtdragon_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index 93ccb809245..af990080aa5 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -787,7 +787,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) From 409a63d50c55b03da5101d7e15fa75547fe938b9 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 14 Mar 2026 18:57:07 -0700 Subject: [PATCH 35/69] gmoccapy -sim: add MPG button on VCP panel --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 2 ++ configs/sim/qtdragon/qtdragon_xyz/panel.ui | 22 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index 7c7b85458c9..c630fcf6d87 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -6,6 +6,8 @@ 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.mpg-select.0 panel.select-gui0 + net mpg-scale axis.x.jog-scale panel.mpg-scale net mpg-scale axis.y.jog-scale diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index c5447316fb4..50da9ae96f1 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -6,7 +6,7 @@ 0 0 - 418 + 495 587 @@ -25,6 +25,22 @@ + + GUI + + + true + + + true + + + select-gui0 + + + + + None @@ -148,7 +164,7 @@ - + MPG @@ -476,7 +492,7 @@ 0 0 - 418 + 495 22 From 12385a9a229a61b66ff52b040e08046f22f86fc2 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 14 Mar 2026 19:03:05 -0700 Subject: [PATCH 36/69] 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 9080ae29efd..ea93e00f62a 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 af990080aa5..253bf8e9ab6 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -157,6 +157,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) @@ -1457,6 +1458,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 @@ -1469,15 +1476,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 a62d5757eacbffbeeca8f6a520bed5b843d0c5dd Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 27 May 2026 19:08:59 -0700 Subject: [PATCH 37/69] qtdragon sim -fix goto user macro raise up to Z0 in g53 rather then user units --- configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini index e7a674f81b2..2e5e1884e84 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini @@ -95,7 +95,7 @@ EMBED_TAB_LOCATION=tabWidget_utilities [MDI_COMMAND_LIST] # for macro buttons on main oage up to 10 possible -MDI_COMMAND_MACRO0 = G0 Z25;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO0 = G53 G0 Z0;G0 X0 Y0;Z0, Goto\nUser\nZero MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero [FILTER] From 1bf68581a3b3a04e744e461e973bede2c54104a9 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 2 Jul 2026 19:49:35 -0700 Subject: [PATCH 38/69] 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 253bf8e9ab6..604c8dcfc2a 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -921,20 +921,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 @@ -957,9 +961,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 # @@ -994,10 +998,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(): From 376ca70b004f560d788907475e2b5eea4653ba7b Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 2 Jul 2026 19:52:43 -0700 Subject: [PATCH 39/69] qtdragon sim -add a macro command to run from the panel --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 2 +- .../sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index c630fcf6d87..43f84036885 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -24,7 +24,7 @@ net jog-m halui.axis.selected.minus panel.jog-neg net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 -#net m2 halui.gui.mdi-command-0 panel.mdi-2 +net 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 diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini index 2e5e1884e84..5efd17e4785 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini @@ -94,10 +94,17 @@ EMBED_TAB_COMMAND=qtvcp vismach_mill_xyz EMBED_TAB_LOCATION=tabWidget_utilities [MDI_COMMAND_LIST] -# for macro buttons on main oage up to 10 possible +# 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) @@ -125,7 +132,7 @@ PARAMETER_FILE = qtdragon.var 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 +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 From bcb479126e347eec4378ac64edbf02cfd3ff5446 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 13:06:20 -0700 Subject: [PATCH 40/69] qtdragon -update sim control panel --- configs/sim/qtdragon/qtdragon_xyz/panel.ui | 811 ++++++++++++++++++--- 1 file changed, 715 insertions(+), 96 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index 50da9ae96f1..86f4a0f4e99 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -6,106 +6,17 @@ 0 0 - 495 - 587 + 571 + 756 MainWindow - - + + - - - - Axis Selection - - - - - - GUI - - - true - - - true - - - select-gui0 - - - - - - - None - - - true - - - true - - - axis-none - - - - - - - X - - - true - - - true - - - axis-x - - - - - - - Y - - - true - - - true - - - axis-y - - - - - - - Z - - - true - - - true - - - axis-z - - - - - - @@ -140,7 +51,7 @@ - jog rate + linear rate /angular rate @@ -156,6 +67,22 @@ + + + + Qt::Horizontal + + + jog-rate-angular + + + false + + + true + + + @@ -485,6 +412,688 @@
+ + + + 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 + + + + + + @@ -492,14 +1101,19 @@ 0 0 - 495 - 22 + 571 + 31 + + IndicatedPushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
PushButton QPushButton @@ -510,6 +1124,11 @@ QDial
qtvcp.widgets.simple_widgets
+ + ActionButton + IndicatedPushButton +
qtvcp.widgets.action_button
+
StatusSlider QSlider From 76736b0bd0ba621f57112979009a69b02a7d0471 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 21:29:04 -0700 Subject: [PATCH 41/69] qtdragon -add basic support for softkeys for Tab selection --- configs/sim/qtdragon/qtdragon_xyz/panel.hal | 15 ++ configs/sim/qtdragon/qtdragon_xyz/panel.ui | 212 ++++++++++++++++-- .../screens/qtdragon/qtdragon_handler.py | 12 + 3 files changed, 225 insertions(+), 14 deletions(-) diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal index 43f84036885..22f87b4cea2 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.hal +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -37,3 +37,18 @@ 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-0 panel.softkey-0 +net softkey1 halui.gui.softkey-1 panel.softkey-1 +net softkey2 halui.gui.softkey-2 panel.softkey-2 +net softkey3 halui.gui.softkey-3 panel.softkey-3 +net softkey4 halui.gui.softkey-4 panel.softkey-4 +net softkey5 halui.gui.softkey-5 panel.softkey-5 +net softkey6 halui.gui.softkey-6 panel.softkey-6 +net softkey7 halui.gui.softkey-7 panel.softkey-7 +net softkey8 halui.gui.softkey-8 panel.softkey-8 +net softkey9 halui.gui.softkey-9 panel.softkey-9 +net softkey10 halui.gui.softkey-10 panel.softkey-10 +net softkey11 halui.gui.softkey-11 panel.softkey-11 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-display panel.reload diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui index 86f4a0f4e99..ecef3135492 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/panel.ui +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -6,8 +6,8 @@ 0 0 - 571 - 756 + 583 + 694 @@ -51,7 +51,7 @@ - linear rate /angular rate + linear rate / angular rate @@ -1092,21 +1092,205 @@ + + + + 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 + + + - - - - 0 - 0 - 571 - 31 - - - - diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index 604c8dcfc2a..9091fd1f69a 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('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) @@ -965,6 +966,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 2318054b88101da3c7dbf69dfd8eca39c4189251 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 27 Jul 2025 14:05:58 -0700 Subject: [PATCH 42/69] gmoccapy/hal_bridge -allow hal_bridge to call macros in gmoccapy This is a proof of concept to allow 3rd party (hal_bridge) to call macros. The macros are run from Gmoccapy so no timing problems should occur and and pre checks or post changes can be covered. There needs to be agreement on where to put macro definitions in the INI. Gmoccapy puts them under [MACROS], iniinfo under [DISPLAY] python ZMQ module package must be available for this to work --- configs/sim/gmoccapy/gmoccapy_right_panel.ini | 8 +++- src/emc/usr_intf/gmoccapy/getiniinfo.py | 2 +- src/emc/usr_intf/gmoccapy/gmoccapy.py | 42 ++++++++++++++++++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/configs/sim/gmoccapy/gmoccapy_right_panel.ini b/configs/sim/gmoccapy/gmoccapy_right_panel.ini index 62213d687d5..7b4c5981da8 100644 --- a/configs/sim/gmoccapy/gmoccapy_right_panel.ini +++ b/configs/sim/gmoccapy/gmoccapy_right_panel.ini @@ -31,6 +31,12 @@ INTRO_TIME = 5 # list of selectable jog increments INCREMENTS = 1mm, 0.1mm, 0.01mm, 0.001mm, 1.2345in +MACRO = i_am_lost +MACRO = halo_world +MACRO = jog_around +MACRO = increment xinc yinc +MACRO = go_to_position X-pos Y-pos Z-pos + [FILTER] PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image PROGRAM_EXTENSION = .py Python Script @@ -75,7 +81,7 @@ HALFILE = simulated_home.hal POSTGUI_HALFILE = gmoccapy_postgui.hal HALUI = halui - +HALBRIDGE = hal_bridge -d # Trajectory planner section -------------------------------------------------- [HALUI] #No Content diff --git a/src/emc/usr_intf/gmoccapy/getiniinfo.py b/src/emc/usr_intf/gmoccapy/getiniinfo.py index 20be167976e..8d45c6ccec8 100644 --- a/src/emc/usr_intf/gmoccapy/getiniinfo.py +++ b/src/emc/usr_intf/gmoccapy/getiniinfo.py @@ -377,7 +377,7 @@ def get_tool_sensor_data(self): def get_macros(self): # lets look in the INI file, if there are any entries - macros = self.inifile.findall("MACROS", "MACRO") + macros = self.inifile.findall("DISPLAY", "MACRO") # If there are no entries we will return False if not macros: return False diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index c7afde2fcbf..ebeb1bd3255 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -431,6 +431,7 @@ def __init__(self, argv): self.GSTAT = Status() self.GSTAT.connect("graphics-gcode-properties", self.on_gcode_properties) self.GSTAT.connect("file-loaded", self.on_hal_status_file_loaded) + self.GSTAT.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -1314,6 +1315,33 @@ def _make_joints_button(self): self.joints_button_dic[name] = btn + # call INI macro (from hal_glib message) + def request_macro_call(self, data): + + # some error checking + if not self.GSTAT.is_mdi_mode(): + message = _("You must be in MDI mode to run macros") + self.dialogs.warning_dialog(self, _("Important Warning!"), message) + return + + # look thru the INI macros + macros = self.get_ini_info.get_macros() + num_macros = len(macros) + if num_macros > 14: + num_macros = 14 + for pos in range(0, num_macros): + # extract just the macro name + name = macros[pos].split()[0] + if data == name: + # get the button instance and click it + button = self["button_macro_{0}".format(pos)] + button.emit("clicked") + break + else: + # didn't match a name - give a hint + message = _("Macro {} not found ".format(data)) + self.dialogs.warning_dialog(self, _("Important Warning!"), message) + # check if macros are in the INI file and add them to MDI Button List def _make_macro_button(self): LOG.debug("Entering make macro button") @@ -1355,7 +1383,9 @@ def _make_macro_button(self): btn.set_halign(Gtk.Align.CENTER) btn.set_valign(Gtk.Align.CENTER) btn.set_property("name","macro_{0}".format(pos)) - btn.set_property("tooltip-text", _("Press to run macro {0}").format(name)) + # keep a reference of the button + self["button_macro_{0}".format(pos)] = btn + btn.set_property("tooltip-text", _("Press to run macro {0}".format(name))) btn.connect("clicked", self._on_btn_macro_pressed, name) btn.position = pos btn.show() @@ -6513,6 +6543,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 55bc82814b93e08365c3de996d56620af173dec5 Mon Sep 17 00:00:00 2001 From: CMorley Date: Wed, 30 Jul 2025 20:50:47 -0700 Subject: [PATCH 43/69] 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 ebeb1bd3255..22e6e6a7dd5 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -198,7 +198,8 @@ def __init__(self, argv): self.error_channel.poll() # set INI path for INI info class before widgets are loaded - INFO = Info(ini=argv[2]) + self.INFO = Info(ini=argv[2]) + self.ACTION = Action() self.builder = Gtk.Builder() # translation of the glade file will be done with @@ -1317,7 +1318,17 @@ def _make_joints_button(self): # call INI macro (from hal_glib message) def request_macro_call(self, data): + # if MDI command change to MDI and run + cmd = self.INFO.get_ini_mdi_command(data) + print('MDI command:',data,cmd) + if not cmd is None: + self.ACTION.RECORD_CURRENT_MODE() + LOG.debug("INI MDI COMMAND #: {} = {}".format(data, cmd)) + self.ACTION.CALL_INI_MDI(data) + self.ACTION.RESTORE_RECORDED_MODE() + return + # run Macros # some error checking if not self.GSTAT.is_mdi_mode(): message = _("You must be in MDI mode to run macros") @@ -6592,6 +6603,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 fb60aaf2bf1ee79a4bbcd5f2110a46fe19523664 Mon Sep 17 00:00:00 2001 From: CMorley Date: Wed, 30 Jul 2025 20:51:51 -0700 Subject: [PATCH 44/69] gmoccapy -sample config: add INI MDI commands to test with --- configs/sim/gmoccapy/gmoccapy_right_panel.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/configs/sim/gmoccapy/gmoccapy_right_panel.ini b/configs/sim/gmoccapy/gmoccapy_right_panel.ini index 7b4c5981da8..01312384c08 100644 --- a/configs/sim/gmoccapy/gmoccapy_right_panel.ini +++ b/configs/sim/gmoccapy/gmoccapy_right_panel.ini @@ -37,6 +37,11 @@ MACRO = jog_around MACRO = increment xinc yinc MACRO = go_to_position X-pos Y-pos Z-pos +[MDI_COMMAND_LIST] +# for macro buttons on main oage up to 10 possible +MDI_COMMAND = G0 Z1;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + [FILTER] PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image PROGRAM_EXTENSION = .py Python Script From dbe034b99f2ac3578f620f06ad48e3df2516b5f6 Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:16:35 -0700 Subject: [PATCH 45/69] 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 22e6e6a7dd5..073490d6f46 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -433,6 +433,8 @@ def __init__(self, argv): self.GSTAT.connect("graphics-gcode-properties", self.on_gcode_properties) self.GSTAT.connect("file-loaded", self.on_hal_status_file_loaded) self.GSTAT.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) + self.GSTAT.connect('cycle-start-request', lambda w, state :self.request_start(state)) + self.GSTAT.connect('cycle-pause-request', lambda w, state: self.request_pause(state)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -1316,6 +1318,14 @@ def _make_joints_button(self): self.joints_button_dic[name] = btn + def request_start(self,data): + print('start') + self.widgets.btn_run.emit('clicked') + + def request_pause(self,data): + print('pause') + self.widgets.tbtn_pause.emit('clicked') + # call INI macro (from hal_glib message) def request_macro_call(self, data): # if MDI command change to MDI and run From fe6852d7f9f8aff287cba4497637f843a69ed0cf Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 10:19:59 -0700 Subject: [PATCH 46/69] gmoccapy -add a halui test config with a sim test panel --- configs/sim/gmoccapy/gmoccapy_halui_test.ini | 225 ++++++++++++ configs/sim/gmoccapy/panel.hal | 24 ++ configs/sim/gmoccapy/panel.ui | 365 +++++++++++++++++++ 3 files changed, 614 insertions(+) create mode 100644 configs/sim/gmoccapy/gmoccapy_halui_test.ini create mode 100644 configs/sim/gmoccapy/panel.hal create mode 100644 configs/sim/gmoccapy/panel.ui diff --git a/configs/sim/gmoccapy/gmoccapy_halui_test.ini b/configs/sim/gmoccapy/gmoccapy_halui_test.ini new file mode 100644 index 00000000000..27579c0b389 --- /dev/null +++ b/configs/sim/gmoccapy/gmoccapy_halui_test.ini @@ -0,0 +1,225 @@ +# EMC controller parameters for a simulated machine. +# General note: Comments can either be preceded with a # or ; - either is +# acceptable, although # is in keeping with most linux config files. + +# General section ------------------------------------------------------------- +[EMC] +VERSION = 1.1 +MACHINE = gmoccapy +DEBUG = 0 + +# Sections for display options ------------------------------------------------ +[DISPLAY] +DISPLAY = gmoccapy -i +# Log level: +# DEBUG -d +# INFO -i +# VERBOSE -v +# ERROR -q + +# Cycle time, in milliseconds, that display will sleep between polls +CYCLE_TIME = 100 + +# Values that will be allowed for override, 1.0 = 100% +MAX_FEED_OVERRIDE = 1.5 +MAX_SPINDLE_OVERRIDE = 1.2 +MIN_SPINDLE_OVERRIDE = 0.5 + +# Initial value for spindle speed +DEFAULT_SPINDLE_SPEED = 450 + +# The following are not used, added here to suppress warnings (from qt_istat/logger). +DEFAULT_LINEAR_VELOCITY = 35 +MIN_LINEAR_VELOCITY = 0 +MAX_LINEAR_VELOCITY = 234 +DEFAULT_SPINDLE_0_SPEED = 500 +MIN_SPINDLE_0_SPEED = 0 +MAX_SPINDLE_0_SPEED = 3000 +MAX_SPINDLE_0_OVERRIDE = 1.2 +MIN_SPINDLE_0_OVERRIDE = 0.5 + +# Prefix to be used +PROGRAM_PREFIX = ../../nc_files/ + +# Introductory graphic +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 + +# list of selectable jog increments +INCREMENTS = 1.000 mm, 0.100 mm, 0.010 mm, 0.001 mm, 1.2345 inch + +# for details see nc_files/subroutines/maco_instructions.txt +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image +PROGRAM_EXTENSION = .py Python Script +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +# Task controller section ----------------------------------------------------- +[RS274NGC] +RS274NGC_STARTUP_CODE = G17 G21 G40 G43H0 G54 G64P0.005 G80 G90 G94 G97 M5 M9 +PARAMETER_FILE = sim.var +SUBROUTINE_PATH = ./macros +REMAP=M6 modalgroup=6 prolog=change_prolog ngc=change_g43 epilog=change_epilog +REMAP=M61 modalgroup=6 prolog=settool_prolog ngc=settool_g43 epilog=settool_epilog + +# the Python plugins serves interpreter and task +[PYTHON] +PATH_PREPEND = ./python +TOPLEVEL = ./python/toplevel.py +LOG_LEVEL = 0 + +# Motion control section ------------------------------------------------------ +[EMCMOT] +EMCMOT = motmod +COMM_TIMEOUT = 1.0 +BASE_PERIOD = 100000 +SERVO_PERIOD = 1000000 + +# Hardware Abstraction Layer section -------------------------------------------------- +[TASK] +TASK = milltask +CYCLE_TIME = 0.001 + +# Part program interpreter section -------------------------------------------- +[HAL] +HALFILE = core_sim.hal +HALFILE = spindle_sim.hal +HALFILE = simulated_home.hal + +# Single file that is executed after the GUI has started. +POSTGUI_HALFILE = gmoccapy_postgui.hal +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel + +HALUI = halui + +# Trajectory planner section -------------------------------------------------- +[HALUI] +#No Content + +[MDI_COMMAND_LIST] +# for macro buttons on main oage up to 10 possible +MDI_COMMAND_MACRO0 = G0 Z1;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero +MDI_COMMAND_MACRO2 = (MSG, macro 2); +MDI_COMMAND_MACRO3 = (MSG, macro 2) +MDI_COMMAND_MACRO4 = (MSG, macro 2),test +[TRAJ] +COORDINATES = X Y Z +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 35 +MAX_LINEAR_VELOCITY = 234 +POSITION_FILE = position.txt +#NO_FORCE_HOMING = 1 + +[EMCIO] +# tool table file +TOOL_TABLE = tool.tbl +TOOL_CHANGE_POSITION = 100 100 -10 +TOOL_CHANGE_QUILL_UP = 1 + +[KINS] +KINEMATICS = trivkins coordinates=xyz +JOINTS = 3 + +[AXIS_X] +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_0] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 0.0 +HOME = 10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 +HOME_IS_SHARED = 1 + +# Second axis +[AXIS_Y] +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_1] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 0.0 +HOME = 10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 + +# Third axis +[AXIS_Z] +MIN_LIMIT = -400.0 +MAX_LIMIT = 0.001 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_2] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 0.001 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 1.0 +HOME = -10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 0 +HOME_IS_SHARED = 1 + +# section for main IO controller parameters ----------------------------------- +[MACROS] +MACRO = go_to_position x-pos y-pos z-pos +MACRO = i_am_lost +MACRO = increment x-incr y-incr +MACRO = macro_4 +MACRO = macro_5 +MACRO = macro_6 +MACRO = macro_7 +MACRO = macro_8 +MACRO = macro_9 +MACRO = macro_10 +MACRO = macro_11 +MACRO = macro_12 +MACRO = macro_13 +MACRO = macro_14 +MACRO = macro_15 + + diff --git a/configs/sim/gmoccapy/panel.hal b/configs/sim/gmoccapy/panel.hal new file mode 100644 index 00000000000..d48117a20ff --- /dev/null +++ b/configs/sim/gmoccapy/panel.hal @@ -0,0 +1,24 @@ +net rate halui.axis.jog-speed panel.Jog-rate-f + +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +net m2 halui.gui.mdi-command-MACRO2 panel.mdi-2 + +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + +net pause halui.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui new file mode 100644 index 00000000000..c671b279767 --- /dev/null +++ b/configs/sim/gmoccapy/panel.ui @@ -0,0 +1,365 @@ + + + MainWindow + + + + 0 + 0 + 313 + 467 + + + + MainWindow + + + + + + + + + Axis Selection + + + + + + None + + + true + + + true + + + axis-none + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + 300 + + + Qt::Horizontal + + + Jog-rate + + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + true + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + true + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + true + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + Abort + + + cycle-abort + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + + + 0 + 0 + 313 + 22 + + + + + + + + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Slider + QSlider +
qtvcp.widgets.simple_widgets
+
+
+ + +
From bd885afc1bb755608f6a3e1b4afc8f4daec21a5b Mon Sep 17 00:00:00 2001 From: CMorley Date: Mon, 1 Sep 2025 21:27:49 -0700 Subject: [PATCH 47/69] 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 073490d6f46..3c2c43bfc04 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1332,10 +1332,8 @@ def request_macro_call(self, data): cmd = self.INFO.get_ini_mdi_command(data) print('MDI command:',data,cmd) if not cmd is None: - self.ACTION.RECORD_CURRENT_MODE() LOG.debug("INI MDI COMMAND #: {} = {}".format(data, cmd)) - self.ACTION.CALL_INI_MDI(data) - self.ACTION.RESTORE_RECORDED_MODE() + self.ACTION.CALL_INI_MDI(data,mode_return = True) return # run Macros From 1188e4ccc43e70fd53087aa1549c2fe92a5a2e16 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 15 Jul 2026 19:01:26 -0700 Subject: [PATCH 48/69] test panel - gmoccapy: make mode buttons not checkable --- configs/sim/gmoccapy/panel.ui | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui index c671b279767..6ed71542f13 100644 --- a/configs/sim/gmoccapy/panel.ui +++ b/configs/sim/gmoccapy/panel.ui @@ -6,8 +6,8 @@ 0 0 - 313 - 467 + 404 + 537 @@ -195,7 +195,7 @@ true - true + false manual-mode @@ -220,7 +220,7 @@ true - true + false mdi-mode @@ -242,7 +242,7 @@ Auto - true + false true @@ -341,7 +341,7 @@ 0 0 - 313 + 404 22 From a90ca0012a9777909af723eaf1a0a9a4a9fd8edb Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 17 Oct 2025 21:50:03 -0700 Subject: [PATCH 49/69] gmoccapy -getinfo.py: fix macro search title this must have been fix in master after I branched --- src/emc/usr_intf/gmoccapy/getiniinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/getiniinfo.py b/src/emc/usr_intf/gmoccapy/getiniinfo.py index 8d45c6ccec8..20be167976e 100644 --- a/src/emc/usr_intf/gmoccapy/getiniinfo.py +++ b/src/emc/usr_intf/gmoccapy/getiniinfo.py @@ -377,7 +377,7 @@ def get_tool_sensor_data(self): def get_macros(self): # lets look in the INI file, if there are any entries - macros = self.inifile.findall("DISPLAY", "MACRO") + macros = self.inifile.findall("MACROS", "MACRO") # If there are no entries we will return False if not macros: return False From 4a48cd25cdf2d18067bd9c5cf68a7f72032acbe1 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 14 Dec 2025 01:10:17 -0800 Subject: [PATCH 50/69] 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 3c2c43bfc04..a2a9aac5196 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -272,8 +272,9 @@ def __init__(self, argv): self.icon_theme.append_search_path(ICON_THEME_DIR) self.icon_theme.append_search_path(USER_ICON_THEME_DIR) - self.dialogs = dialogs.Dialogs() + self.dialogs = dialogs.Dialogs(caller = self) self.dialogs.connect("play_sound", self._on_play_sound) + self.dialogs.connect('system-dialog-result', self.system_dialog_return) # check the arguments given from the command line (Ini file) self.user_mode = False @@ -435,6 +436,8 @@ def __init__(self, argv): self.GSTAT.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) self.GSTAT.connect('cycle-start-request', lambda w, state :self.request_start(state)) self.GSTAT.connect('cycle-pause-request', lambda w, state: self.request_pause(state)) + self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(1)) + self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(0)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -4278,8 +4281,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) @@ -4329,6 +4333,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 058ed3eb71eccd3e2e1db2b806a08108631503c8 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Dec 2025 19:48:56 -0800 Subject: [PATCH 51/69] 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 a2a9aac5196..84771469140 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -272,10 +272,6 @@ def __init__(self, argv): self.icon_theme.append_search_path(ICON_THEME_DIR) self.icon_theme.append_search_path(USER_ICON_THEME_DIR) - self.dialogs = dialogs.Dialogs(caller = self) - self.dialogs.connect("play_sound", self._on_play_sound) - self.dialogs.connect('system-dialog-result', self.system_dialog_return) - # check the arguments given from the command line (Ini file) self.user_mode = False self.logofile = None @@ -354,6 +350,11 @@ def __init__(self, argv): self.builder.connect_signals(self) + self.dialogs = dialogs.Dialogs(caller = self) + self.dialogs.connect("play_sound", self._on_play_sound) + self.dialogs.connect('system-dialog-result', self.system_dialog_return) + self.dialogs.connect('warning-dialog-result', self.warning_dialog_return) + # this are settings to be done before window show self._init_preferences() @@ -436,8 +437,8 @@ def __init__(self, argv): self.GSTAT.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) self.GSTAT.connect('cycle-start-request', lambda w, state :self.request_start(state)) self.GSTAT.connect('cycle-pause-request', lambda w, state: self.request_pause(state)) - self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(1)) - self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(0)) + self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.ACCEPT)) + self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.CANCEL)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -1343,7 +1344,8 @@ def request_macro_call(self, data): # some error checking if not self.GSTAT.is_mdi_mode(): message = _("You must be in MDI mode to run macros") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message, context=None) return # look thru the INI macros @@ -1362,7 +1364,8 @@ def request_macro_call(self, data): else: # didn't match a name - give a hint message = _("Macro {} not found ".format(data)) - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message, context=None) # check if macros are in the INI file and add them to MDI Button List def _make_macro_button(self): @@ -1540,8 +1543,9 @@ def _make_lathe(self): message += _("this is not a lathe, as a lathe must have at least\n") message += _("an X and an Z axis\n") message += _("Wrong lathe configuration, we will leave here") - self.dialogs.warning_dialog(self, _("Very critical situation"), message, sound = False) - sys.exit() + self.dialogs.show_warning_dialog( _("Very critical situation"), + message, context='systemexit') + return else: if not len(self.axis_list) == 2 and not len(self.axis_list) < 6: self._arrange_jog_button_by_axis() @@ -1991,8 +1995,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 @@ -2451,8 +2456,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) @@ -4335,21 +4341,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 @@ -4470,8 +4503,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 @@ -4786,8 +4819,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: @@ -4812,8 +4845,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: @@ -4999,8 +5032,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() @@ -5021,7 +5054,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 @@ -5084,8 +5118,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 @@ -5113,7 +5147,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) @@ -5657,28 +5693,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 @@ -5706,13 +5734,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 @@ -5720,7 +5750,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: @@ -5728,8 +5759,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, @@ -5739,7 +5770,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 @@ -5767,13 +5799,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 @@ -5794,11 +5828,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 @@ -5816,7 +5852,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 b6af008292cef9eb2caff29a3e07c18ea5a34d40 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 00:00:35 -0800 Subject: [PATCH 52/69] 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 cc9f778849196e4bbd8be5b69ed0e43e06a4f869 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 1 Feb 2026 00:25:59 -0800 Subject: [PATCH 53/69] 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 84771469140..2fb03eec134 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -439,6 +439,7 @@ def __init__(self, argv): self.GSTAT.connect('cycle-pause-request', lambda w, state: self.request_pause(state)) self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.ACCEPT)) self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.CANCEL)) + self.GSTAT.connect('cancel-request', lambda w, state: self._del_notification()) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -6348,6 +6349,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 f00e5a8bc2f20c79a2533cb1262ebcbb6e9fe7dc Mon Sep 17 00:00:00 2001 From: CMorley Date: Sat, 7 Feb 2026 14:45:11 -0800 Subject: [PATCH 54/69] 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 2fb03eec134..3c5bd167d5b 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1323,12 +1323,21 @@ def _make_joints_button(self): self.joints_button_dic[name] = btn + # halui/external cycle start request def request_start(self,data): - print('start') - self.widgets.btn_run.emit('clicked') + print('start request') + if self.stat.task_mode == linuxcnc.MODE_MDI: + print('Submit MDI') + self.widgets.hal_mdihistory.submit() + elif self.stat.task_mode == linuxcnc.MODE_MANUAL: + self._show_error((13, _("Can't start cycles or submit MDI commands in manual Mode"))) + else: + print('Cycle Start') + self.widgets.btn_run.emit('clicked') + # halui/external pause request def request_pause(self,data): - print('pause') + print('pause request') self.widgets.tbtn_pause.emit('clicked') # call INI macro (from hal_glib message) From d60759be908e548222622110dd023fac99716949 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 3 Apr 2026 18:34:43 -0700 Subject: [PATCH 55/69] 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 3c5bd167d5b..05a42ba45d4 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1326,7 +1326,9 @@ def _make_joints_button(self): # halui/external cycle start request def request_start(self,data): print('start request') - if self.stat.task_mode == linuxcnc.MODE_MDI: + if self.GSTAT.is_auto_paused(): + self.command.auto(linuxcnc.AUTO_RESUME) + elif self.stat.task_mode == linuxcnc.MODE_MDI: print('Submit MDI') self.widgets.hal_mdihistory.submit() elif self.stat.task_mode == linuxcnc.MODE_MANUAL: @@ -1338,7 +1340,15 @@ def request_start(self,data): # halui/external pause request def request_pause(self,data): print('pause request') - self.widgets.tbtn_pause.emit('clicked') + + # don't toggle + if self.GSTAT.is_auto_paused(): + return + + if self.stat.task_mode == linuxcnc.MODE_AUTO: + self.widgets.tbtn_pause.emit('clicked') + else: + self.command.auto(linuxcnc.AUTO_PAUSE) # call INI macro (from hal_glib message) def request_macro_call(self, data): From 014af873d7f66194565f2bcf567fae51f68b65a6 Mon Sep 17 00:00:00 2001 From: CMorley Date: Thu, 9 Apr 2026 17:00:26 -0700 Subject: [PATCH 56/69] 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 | 190 +++++++++++--------------- 2 files changed, 194 insertions(+), 168 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 05a42ba45d4..ab27cab9123 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -352,8 +352,6 @@ def __init__(self, argv): self.dialogs = dialogs.Dialogs(caller = self) self.dialogs.connect("play_sound", self._on_play_sound) - self.dialogs.connect('system-dialog-result', self.system_dialog_return) - self.dialogs.connect('warning-dialog-result', self.warning_dialog_return) # this are settings to be done before window show self._init_preferences() @@ -1365,7 +1363,7 @@ def request_macro_call(self, data): if not self.GSTAT.is_mdi_mode(): message = _("You must be in MDI mode to run macros") self.dialogs.show_warning_dialog( _("Important Warning!"), - message, context=None) + message) return # look thru the INI macros @@ -1385,7 +1383,7 @@ def request_macro_call(self, data): # didn't match a name - give a hint message = _("Macro {} not found ".format(data)) self.dialogs.show_warning_dialog( _("Important Warning!"), - message, context=None) + message) # check if macros are in the INI file and add them to MDI Button List def _make_macro_button(self): @@ -1564,8 +1562,8 @@ def _make_lathe(self): message += _("an X and an Z axis\n") message += _("Wrong lathe configuration, we will leave here") self.dialogs.show_warning_dialog( _("Very critical situation"), - message, context='systemexit') - return + message) + sys.exit() else: if not len(self.axis_list) == 2 and not len(self.axis_list) < 6: self._arrange_jog_button_by_axis() @@ -2016,8 +2014,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 @@ -2173,15 +2171,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: @@ -2477,8 +2475,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) @@ -2576,12 +2574,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 \ + if 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 !"), @@ -2733,7 +2732,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: @@ -3241,13 +3240,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 !"), @@ -3453,8 +3453,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 !"), @@ -4307,9 +4308,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) @@ -4321,7 +4325,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: @@ -4359,52 +4363,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(): @@ -4524,7 +4482,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 @@ -4818,7 +4776,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() @@ -4832,15 +4790,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: @@ -4858,15 +4815,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: @@ -4925,7 +4881,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) @@ -4938,7 +4894,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 @@ -4960,7 +4916,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() @@ -5038,22 +4994,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() @@ -5075,7 +5031,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 @@ -5125,7 +5081,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": @@ -5139,7 +5095,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 @@ -5168,7 +5124,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) @@ -5713,10 +5669,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 @@ -5726,7 +5696,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 @@ -5738,7 +5708,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) @@ -5755,14 +5725,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 @@ -5771,7 +5741,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: @@ -5780,10 +5750,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) @@ -5791,7 +5761,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 @@ -5811,23 +5781,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 @@ -5842,19 +5812,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 @@ -5873,7 +5843,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 72964389aa7f58f7a9e68ddf86d3897029ecd0b0 Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 10 Apr 2026 13:41:47 -0700 Subject: [PATCH 57/69] 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 11b5c5be27765a135be7fff6b9987cf8a3d47b2d Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 12 Apr 2026 10:43:09 -0700 Subject: [PATCH 58/69] gmoccapy -update vcp panel for angular axes --- configs/sim/gmoccapy/panel.hal | 2 +- configs/sim/gmoccapy/panel.ui | 127 +++++++++++++++++++++++++++++---- 2 files changed, 113 insertions(+), 16 deletions(-) diff --git a/configs/sim/gmoccapy/panel.hal b/configs/sim/gmoccapy/panel.hal index d48117a20ff..e93020bc83f 100644 --- a/configs/sim/gmoccapy/panel.hal +++ b/configs/sim/gmoccapy/panel.hal @@ -1,4 +1,4 @@ -net rate halui.axis.jog-speed panel.Jog-rate-f +net rate halui.axis.jog-speed panel.jog-rate-f net sx halui.axis.x.select panel.axis-x net sy halui.axis.y.select panel.axis-y diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui index 6ed71542f13..73215dec2cd 100644 --- a/configs/sim/gmoccapy/panel.ui +++ b/configs/sim/gmoccapy/panel.ui @@ -6,8 +6,8 @@ 0 0 - 404 - 537 + 389 + 600 @@ -22,11 +22,26 @@ Axis Selection - - - + + + 0 + + + 3 + + + 0 + + + 0 + + + 4 + + + - None + Z true @@ -35,11 +50,11 @@ true - axis-none + axis-z - + X @@ -55,7 +70,23 @@ - + + + + None + + + true + + + true + + + axis-none + + + + Y @@ -71,10 +102,10 @@ - - + + - Z + C true @@ -83,7 +114,39 @@ true - axis-z + axis-c + + + + + + + B + + + true + + + true + + + axis-b + + + + + + + A + + + true + + + true + + + axis-a @@ -96,6 +159,18 @@ Axis Jog + + 0 + + + 3 + + + 0 + + + 0 + @@ -134,7 +209,29 @@ Qt::Horizontal - Jog-rate + jog-rate + + + + + + + + + + jAngular jog rate + + + + + + 1800 + + + Qt::Horizontal + + + jog-rate-angular @@ -341,7 +438,7 @@ 0 0 - 404 + 389 22 From 326c5c57f0c379cfe35f57ae3a943101aacc9097 Mon Sep 17 00:00:00 2001 From: CMorley Date: Sun, 12 Apr 2026 10:44:31 -0700 Subject: [PATCH 59/69] gmoccapy -add a 4 axis halui test config to confirm angular jograte works --- .../gmoccapy/gmoccapy_halui_test_4_axis.ini | 218 ++++++++++++++++++ configs/sim/gmoccapy/panel-4axis.hal | 26 +++ 2 files changed, 244 insertions(+) create mode 100644 configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini create mode 100644 configs/sim/gmoccapy/panel-4axis.hal diff --git a/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini new file mode 100644 index 00000000000..f722d2e6654 --- /dev/null +++ b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini @@ -0,0 +1,218 @@ +# General section ------------------------------------------------------------- +[EMC] +VERSION = 1.1 +MACHINE = gmoccapy 4 axis +DEBUG = 0 + +# for details see nc_files/subroutines/maco_instructions.txt +[DISPLAY] +DISPLAY = gmoccapy + +# Cycle time, in milliseconds, that display will sleep between polls +CYCLE_TIME = 100 + +# Highest value that will be allowed for feed override, 1.0 = 100% +MAX_FEED_OVERRIDE = 1.5 +MAX_SPINDLE_OVERRIDE = 1.2 +MIN_SPINDLE_OVERRIDE = .5 + +# Prefix to be used +PROGRAM_PREFIX = ../../nc_files/ + +# Introductory graphic +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 + +# list of selectable jog increments +INCREMENTS = 1.000 mm, 0.100 mm, 0.010 mm, 0.001 mm, 90.000 ° + +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image +PROGRAM_EXTENSION = .py Python Script +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +# Task controller section ----------------------------------------------------- +[RS274NGC] +RS274NGC_STARTUP_CODE = G17 G21 G40 G43H0 G54 G64P0.005 G80 G90 G94 G97 M5 M9 +PARAMETER_FILE = sim.var +SUBROUTINE_PATH = ./macros +REMAP=M6 modalgroup=6 prolog=change_prolog ngc=change_g43 epilog=change_epilog +REMAP=M61 modalgroup=6 prolog=settool_prolog ngc=settool_g43 epilog=settool_epilog + +# the Python plugins serves interpreter and task +[PYTHON] +PATH_PREPEND = ./python +TOPLEVEL = ./python/toplevel.py +LOG_LEVEL = 0 + +# Motion control section ------------------------------------------------------ +[EMCMOT] +EMCMOT = motmod +COMM_TIMEOUT = 1.0 +BASE_PERIOD = 100000 +SERVO_PERIOD = 1000000 + +# Hardware Abstraction Layer section -------------------------------------------------- +[TASK] +TASK = milltask +CYCLE_TIME = 0.001 + +# Part program interpreter section -------------------------------------------- +[HAL] +HALFILE = core_sim4.hal +HALFILE = spindle_sim.hal +HALFILE = simulated_home.hal + +# Single file that is executed after the GUI has started. +POSTGUI_HALFILE = gmoccapy_postgui.hal +POSTGUI_HALCMD = loadusr qtvcp -a -H panel-4axis.hal panel +HALUI = halui + +# Trajectory planner section -------------------------------------------------- +[HALUI] +#No Content +[MDI_COMMAND_LIST] +# for macro buttons on main oage up to 10 possible +MDI_COMMAND_MACRO0 = G0 Z1;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero +MDI_COMMAND_MACRO2 = (MSG, macro 2); +MDI_COMMAND_MACRO3 = (MSG, macro 2) +MDI_COMMAND_MACRO4 = (MSG, macro 2),test + + +[TRAJ] +COORDINATES = X Y Z C +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 50 +MAX_LINEAR_VELOCITY = 234 +POSITION_FILE = position.txt + +# First axis = X +[EMCIO] +# tool table file +TOOL_TABLE = tool.tbl +TOOL_CHANGE_POSITION = 100 100 -10 +TOOL_CHANGE_QUILL_UP = 1 + +[KINS] +KINEMATICS = trivkins coordinates=xyzc +JOINTS = 4 + +[AXIS_X] +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_0] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 0.0 +HOME = 10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 +HOME_IS_SHARED = 1 + +# Second axis = Y +[AXIS_Y] +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_1] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 0.0 +HOME = 10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 +HOME_IS_SHARED = 1 + +# Third axis = Z +[AXIS_Z] +MIN_LIMIT = -400.0 +MAX_LIMIT = 0.001 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_2] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 0.001 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 1.0 +HOME = -10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 0 +HOME_IS_SHARED = 1 + +# Fourth axis = A +# Fifth axis = B + +# Sixt axis = C +[AXIS_C] +MAX_VELOCITY = 90.0 +MAX_ACCELERATION = 1200.0 + +[JOINT_3] +TYPE = ANGULAR +HOME = 0.0 +MAX_VELOCITY = 90.0 +MAX_ACCELERATION = 1200.0 +BACKLASH = 0.000 +INPUT_SCALE = 40 +OUTPUT_SCALE = 1.000 +FERROR = 5.0 +MIN_FERROR = 1.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 0.0 +HOME_LATCH_VEL = 0.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 + +# section for main IO controller parameters ----------------------------------- +[MACROS] +MACRO = i_am_lost +MACRO = halo_world +MACRO = jog_around +MACRO = increment xinc yinc +MACRO = go_to_position X-pos Y-pos Z-pos + +# Sections for display options ------------------------------------------------ diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal new file mode 100644 index 00000000000..d1dde2dcefb --- /dev/null +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -0,0 +1,26 @@ +net rate halui.axis.jog-speed panel.jog-rate-f +net angular-rate halui.axis.jog-speed-angular panel.jog-rate-angular-f + +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z +net sc halui.axis.c.select panel.axis-c + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +net m2 halui.gui.mdi-command-MACRO2 panel.mdi-2 + +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + +net pause halui.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + From e41af0e4ef487cec50873bde48ea32219b59a1cd Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 24 Jun 2026 20:49:11 -0700 Subject: [PATCH 60/69] gmoccapy -fix merge error --- 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 ab27cab9123..34026f8e210 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1352,7 +1352,7 @@ def request_pause(self,data): def request_macro_call(self, data): # if MDI command change to MDI and run cmd = self.INFO.get_ini_mdi_command(data) - print('MDI command:',data,cmd) + print(f'MDI command:{cmd} data:{data}') if not cmd is None: LOG.debug("INI MDI COMMAND #: {} = {}".format(data, cmd)) self.ACTION.CALL_INI_MDI(data,mode_return = True) @@ -2575,7 +2575,7 @@ def on_offset_col_edit_started(self, widget, filtered_path, new_text, col): (store_path,) = offsetpage.modelfilter.convert_path_to_child_path(path) row = store_path if self.widgets.offsetpage1.btn_edit_offsets.get_active() or \ - if self.touch_button_dic["edit_offsets"].get_active(): + 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:"), From cf02b8455cb17f4daf71ebaeb8aaa22577854237 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 13:05:22 -0700 Subject: [PATCH 61/69] gmoccapy -update sample control panel --- configs/sim/gmoccapy/panel-4axis.hal | 4 +- configs/sim/gmoccapy/panel.hal | 2 +- configs/sim/gmoccapy/panel.ui | 1139 ++++++++++++++++++++------ 3 files changed, 913 insertions(+), 232 deletions(-) diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal index d1dde2dcefb..5dd73b509ff 100644 --- a/configs/sim/gmoccapy/panel-4axis.hal +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -1,5 +1,5 @@ -net rate halui.axis.jog-speed panel.jog-rate-f -net angular-rate halui.axis.jog-speed-angular panel.jog-rate-angular-f +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 diff --git a/configs/sim/gmoccapy/panel.hal b/configs/sim/gmoccapy/panel.hal index e93020bc83f..8429fce4cb4 100644 --- a/configs/sim/gmoccapy/panel.hal +++ b/configs/sim/gmoccapy/panel.hal @@ -1,4 +1,4 @@ -net rate halui.axis.jog-speed panel.jog-rate-f +net rate halui.axis.jog-speed panel.jog-rate net sx halui.axis.x.select panel.axis-x net sy halui.axis.y.select panel.axis-y diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui index 73215dec2cd..86f4a0f4e99 100644 --- a/configs/sim/gmoccapy/panel.ui +++ b/configs/sim/gmoccapy/panel.ui @@ -6,237 +6,218 @@ 0 0 - 389 - 600 + 571 + 756 MainWindow - - + + - - - Axis Selection - - - - 0 - - - 3 - - - 0 - - - 0 - - - 4 - - - - - Z - - - true - - - true - - - axis-z - - - - - - - X - - - true - - - true - - - axis-x - - - - - - - None - - - true - - - true - - - axis-none - - - - - - - Y - - - true - - - true - - - axis-y - - - - - - - C - - - true - - - true - - - axis-c - - - - - - - B - - - true - - - true - - - axis-b - - - - - - - A - - - true - - - true - - - axis-a - - - - - - - - - - Axis Jog - - - - 0 - - - 3 - - - 0 - - - 0 - - - - - + - - - jog-pos - - - - - - - - - - - jog-neg - - - - - + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + linear rate /angular rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + Qt::Horizontal + + + jog-rate-angular + + + false + + + true + + + + + + + - - - jog rate - - - - - - 300 - - - Qt::Horizontal - - - jog-rate - - - - - - - - - - jAngular jog rate - - - - - - 1800 - - - Qt::Horizontal - - - jog-rate-angular - - - - - + + + + + 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 + + + + @@ -289,10 +270,10 @@ Manual - true + false - false + true manual-mode @@ -314,10 +295,10 @@ MDI - true + false - false + true mdi-mode @@ -431,6 +412,688 @@ + + + + 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 + + + + + + @@ -438,25 +1101,43 @@ 0 0 - 389 - 22 + 571 + 31 + + IndicatedPushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
PushButton QPushButton
qtvcp.widgets.simple_widgets
- Slider - QSlider + Dial + QDial
qtvcp.widgets.simple_widgets
+ + ActionButton + IndicatedPushButton +
qtvcp.widgets.action_button
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+ + + From 15eff2bdc5fd7e7d47232dc3f9c75d7821427937 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Thu, 9 Jul 2026 14:13:15 -0700 Subject: [PATCH 62/69] 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 34026f8e210..b67234d36d5 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -438,6 +438,7 @@ def __init__(self, argv): self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.ACCEPT)) self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.CANCEL)) self.GSTAT.connect('cancel-request', lambda w, state: self._del_notification()) + self.GSTAT.connect('shutdown-request', lambda w: self.on_btn_exit_clicked(w)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) From d8f1a9e7b10b2e302c83ae5ec409ae5938d942f4 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 10:21:38 -0700 Subject: [PATCH 63/69] 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 b67234d36d5..6829053fc21 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -438,7 +438,8 @@ def __init__(self, argv): self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.ACCEPT)) self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.CANCEL)) self.GSTAT.connect('cancel-request', lambda w, state: self._del_notification()) - self.GSTAT.connect('shutdown-request', lambda w: self.on_btn_exit_clicked(w)) + self.GSTAT.connect('shutdown-request', lambda w: self.system_shutdown(w)) + self.GSTAT.connect('softkey-pressed', lambda w,data: self.softkey_pressed(data)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -2934,6 +2935,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 # ========================================================= @@ -6400,6 +6404,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, @@ -6423,6 +6441,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 57a7c600b4c8650f25dfa38e36f7952f95ca5392 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 12:17:23 -0700 Subject: [PATCH 64/69] gmoccapy -update simulated control panels MPG jog --- configs/sim/gmoccapy/panel-4axis.hal | 38 + configs/sim/gmoccapy/panel.hal | 34 + configs/sim/gmoccapy/panel.ui | 1752 +++++++++++++++----------- 3 files changed, 1080 insertions(+), 744 deletions(-) diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal index 5dd73b509ff..7b12e1ef3a1 100644 --- a/configs/sim/gmoccapy/panel-4axis.hal +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -6,9 +6,26 @@ 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.mpg-select.0 panel.select-gui0 + net jog-p halui.axis.selected.plus panel.jog-pos net jog-m halui.axis.selected.minus panel.jog-neg +net mpg-scale axis.x.jog-scale panel.mpg-scale +net mpg-scale axis.y.jog-scale +net mpg-scale axis.z.jog-scale +net mpg-scale axis.c.jog-scale + +net mpg-count panel.mpg-wheel-s +net mpg-count axis.x.jog-counts +net mpg-count axis.y.jog-counts +net mpg-count axis.z.jog-counts +net mpg-count axis.c.jog-counts + net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 net m2 halui.gui.mdi-command-MACRO2 panel.mdi-2 @@ -24,3 +41,24 @@ 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-0 panel.softkey-v0 +net softkey1 halui.gui.softkey-1 panel.softkey-v1 +net softkey2 halui.gui.softkey-2 panel.softkey-v2 +net softkey3 halui.gui.softkey-3 panel.softkey-v3 +net softkey4 halui.gui.softkey-4 panel.softkey-v4 +net softkey5 halui.gui.softkey-5 panel.softkey-v5 +net softkey6 halui.gui.softkey-6 panel.softkey-v6 +net softkeyh0 halui.gui.softkey-10 panel.softkey-h0 +net softkeyh1 halui.gui.softkey-11 panel.softkey-h1 +net softkeyh2 halui.gui.softkey-12 panel.softkey-h2 +net softkeyh3 halui.gui.softkey-13 panel.softkey-h3 +net softkeyh4 halui.gui.softkey-14 panel.softkey-h4 +net softkeyh5 halui.gui.softkey-15 panel.softkey-h5 +net softkeyh6 halui.gui.softkey-16 panel.softkey-h6 +net softkeyh7 halui.gui.softkey-17 panel.softkey-h7 +net softkeyh8 halui.gui.softkey-18 panel.softkey-h8 +net softkeyh9 halui.gui.softkey-19 panel.softkey-h9 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-display panel.reload diff --git a/configs/sim/gmoccapy/panel.hal b/configs/sim/gmoccapy/panel.hal index 8429fce4cb4..dd7799248c2 100644 --- a/configs/sim/gmoccapy/panel.hal +++ b/configs/sim/gmoccapy/panel.hal @@ -4,9 +4,23 @@ net sx halui.axis.x.select panel.axis-x net sy halui.axis.y.select panel.axis-y net sz halui.axis.z.select panel.axis-z +net sx axis.x.jog-enable +net sy axis.y.jog-enable +net sz axis.z.jog-enable +net sgui halui.mpg-select.0 panel.select-gui0 + net jog-p halui.axis.selected.plus panel.jog-pos net jog-m halui.axis.selected.minus panel.jog-neg +net mpg-scale axis.x.jog-scale panel.mpg-scale +net mpg-scale axis.y.jog-scale +net mpg-scale axis.z.jog-scale + +net mpg-count panel.mpg-wheel-s +net mpg-count axis.x.jog-counts +net mpg-count axis.y.jog-counts +net mpg-count axis.z.jog-counts + net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 net m2 halui.gui.mdi-command-MACRO2 panel.mdi-2 @@ -22,3 +36,23 @@ 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-0 panel.softkey-v0 +net softkey1 halui.gui.softkey-1 panel.softkey-v1 +net softkey2 halui.gui.softkey-2 panel.softkey-v2 +net softkey3 halui.gui.softkey-3 panel.softkey-v3 +net softkey4 halui.gui.softkey-4 panel.softkey-v4 +net softkey5 halui.gui.softkey-5 panel.softkey-v5 +net softkey6 halui.gui.softkey-6 panel.softkey-v6 +net softkeyh0 halui.gui.softkey-10 panel.softkey-h0 +net softkeyh1 halui.gui.softkey-11 panel.softkey-h1 +net softkeyh2 halui.gui.softkey-12 panel.softkey-h2 +net softkeyh3 halui.gui.softkey-13 panel.softkey-h3 +net softkeyh4 halui.gui.softkey-14 panel.softkey-h4 +net softkeyh5 halui.gui.softkey-15 panel.softkey-h5 +net softkeyh6 halui.gui.softkey-16 panel.softkey-h6 +net softkeyh7 halui.gui.softkey-17 panel.softkey-h7 +net softkeyh8 halui.gui.softkey-18 panel.softkey-h8 +net softkeyh9 halui.gui.softkey-19 panel.softkey-h9 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-display panel.reload diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui index 86f4a0f4e99..cf614f0bb7f 100644 --- a/configs/sim/gmoccapy/panel.ui +++ b/configs/sim/gmoccapy/panel.ui @@ -6,7 +6,7 @@ 0 0 - 571 + 874 756 @@ -15,561 +15,186 @@ - - - - - - - - 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 + + + + + - ESTOP + ON false @@ -693,7 +318,7 @@ 0 - + true @@ -738,9 +363,9 @@ - + - ON + HOME false @@ -864,249 +489,888 @@ 0 - + true - - false + + 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 - - -1 + + reload - - 0.010000000000000 + + PushButton::BIT - - 0.025000000000000 + + + + + + SYSTEM +Exit - - -1.000000000000000 + + exit - - false + + PushButton::BIT - - 0.300000000000000 + + + + + + + + + + + + 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 - - 50.000000000000000 + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands - - P + + + + + 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 - - 0 + + + + + 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 - - %1.3f in + + softkey-h0 - - %1.2f mm + + PushButton::BIT - + - 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 + H1 - - false - - - false - - - false + + softkey-h1 - - false - - - false + + PushButton::BIT - - false + + + + + + H2 - - false + + softkey-h2 - - false + + PushButton::BIT - - false + + + + + + H3 - - false + + softkey-h3 - - false + + PushButton::BIT - - 0 + + + + + + H4 - - true + + softkey-h4 - - false + + PushButton::BIT - - -1 + + + + + + H5 - - 0.010000000000000 + + softkey-h5 - - 0.025000000000000 + + PushButton::BIT - - -1.000000000000000 + + + + + + H6 - - false + + softkey-h6 - - 0.300000000000000 + + PushButton::BIT - - 50.000000000000000 + + + + + + H7 - - P + + softkey-h7 - - + + PushButton::BIT - - 0 + + + + + + H8 - - %1.3f in + + softkey-h8 - - %1.2f mm + + PushButton::BIT - - - Qt::Vertical + + + H9 - - - 20 - 40 - + + softkey-h9 - + + PushButton::BIT + +
- - - - 0 - 0 - 571 - 31 - - - - From 3971d7120a4d4fd6eecbeffecbfd2ab959495b13 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 13:23:52 -0700 Subject: [PATCH 65/69] 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 6829053fc21..58ba3707705 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -228,6 +228,8 @@ def __init__(self, argv): self.so_counts = 0 # need to calculate difference in counts to change the spindle override slider self.jv_counts = 0 # need to calculate difference in counts to change the jog_vel slider self.ro_counts = 0 # need to calculate difference in counts to change the rapid override slider + self._last_count = 0 # need to calculate MPG count difference + self.mpg_enabled = False self.spindle_override = 1 # holds the feed override value and is needed to be able to react to halui pin self.feed_override = 1 # holds the spindle override value and is needed to be able to react to halui pin @@ -440,6 +442,7 @@ def __init__(self, argv): self.GSTAT.connect('cancel-request', lambda w, state: self._del_notification()) self.GSTAT.connect('shutdown-request', lambda w: self.system_shutdown(w)) self.GSTAT.connect('softkey-pressed', lambda w,data: self.softkey_pressed(data)) + self.GSTAT.connect('axis-selection-changed', lambda w,data: self.mpg_selection_changed(data)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -6403,6 +6406,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): @@ -6637,6 +6665,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 62866b9caeded857d15d8bca40b8b208e21115f1 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 10 Jul 2026 13:26:37 -0700 Subject: [PATCH 66/69] gmoccapy -sim: connect GUI MPG signals --- configs/sim/gmoccapy/panel-4axis.hal | 1 + configs/sim/gmoccapy/panel.hal | 1 + 2 files changed, 2 insertions(+) diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal index 7b12e1ef3a1..95e7d4fdf6c 100644 --- a/configs/sim/gmoccapy/panel-4axis.hal +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -21,6 +21,7 @@ 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 diff --git a/configs/sim/gmoccapy/panel.hal b/configs/sim/gmoccapy/panel.hal index dd7799248c2..3be61d9baf4 100644 --- a/configs/sim/gmoccapy/panel.hal +++ b/configs/sim/gmoccapy/panel.hal @@ -17,6 +17,7 @@ net mpg-scale axis.y.jog-scale net mpg-scale axis.z.jog-scale net mpg-count panel.mpg-wheel-s +net mpg-count gmoccapy.mpg-in net mpg-count axis.x.jog-counts net mpg-count axis.y.jog-counts net mpg-count axis.z.jog-counts From 76105fd3ad37f47fbe897b6b8ad9809a6f7e218b Mon Sep 17 00:00:00 2001 From: CMorley Date: Fri, 26 Sep 2025 22:27:26 -0700 Subject: [PATCH 67/69] axis sim -add a sim test for halui messages --- configs/sim/axis/axis_halui_test.ini | 225 +++++++++++++++++ configs/sim/axis/gstatmessages.py | 66 +++++ configs/sim/axis/panel.hal | 24 ++ configs/sim/axis/panel.ui | 365 +++++++++++++++++++++++++++ 4 files changed, 680 insertions(+) create mode 100644 configs/sim/axis/axis_halui_test.ini create mode 100644 configs/sim/axis/gstatmessages.py create mode 100644 configs/sim/axis/panel.hal create mode 100644 configs/sim/axis/panel.ui diff --git a/configs/sim/axis/axis_halui_test.ini b/configs/sim/axis/axis_halui_test.ini new file mode 100644 index 00000000000..877a91db9d8 --- /dev/null +++ b/configs/sim/axis/axis_halui_test.ini @@ -0,0 +1,225 @@ +# EMC controller parameters for a simulated machine. + +# General note: Comments can either be preceded with a # or ; - either is +# acceptable, although # is in keeping with most linux config files. + +# General section ------------------------------------------------------------- +[EMC] + +# Version of this INI file +VERSION = 1.1 + +# Name of machine, for use with display, etc. +MACHINE = LinuxCNC-HAL-SIM-AXIS + +# Debug level, 0 means no messages. See src/emc/nml_int/emcglb.h for others +#DEBUG = 0x7FFFFFFF +DEBUG = 0 + +# Sections for display options ------------------------------------------------ +[DISPLAY] + +# Name of display program, e.g., axis +DISPLAY = axis + +# Cycle time, in seconds, that display will sleep between polls +CYCLE_TIME = 0.100 + +# Path to help file +HELP_FILE = doc/help.txt + +# Initial display setting for position, RELATIVE or MACHINE +POSITION_OFFSET = RELATIVE + +# Initial display setting for position, COMMANDED or ACTUAL +POSITION_FEEDBACK = ACTUAL + +# Highest value that will be allowed for feed override, 1.0 = 100% +MAX_FEED_OVERRIDE = 1.2 +MAX_SPINDLE_OVERRIDE = 1.0 + +MAX_LINEAR_VELOCITY = 5 +DEFAULT_LINEAR_VELOCITY = .25 +DEFAULT_SPINDLE_SPEED = 200 +# Prefix to be used +PROGRAM_PREFIX = ../../nc_files/ + +# Introductory graphic +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 + +#EDITOR = geany +TOOL_EDITOR = tooledit + +INCREMENTS = 1 in, 0.1 in, 10 mil, 1 mil, 1mm, .1mm, 1/8000 in + +USER_COMMAND_FILE=gstatmessages.py + +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image +PROGRAM_EXTENSION = .py Python Script + +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +# Task controller section ----------------------------------------------------- +[TASK] + +# Name of task controller program, e.g., milltask +TASK = milltask + +# Cycle time, in seconds, that task controller will sleep between polls +CYCLE_TIME = 0.001 + +# Part program interpreter section -------------------------------------------- +[RS274NGC] + +# File containing interpreter variables +PARAMETER_FILE = sim.var + +# Motion control section ------------------------------------------------------ +[EMCMOT] + +EMCMOT = motmod + +# Timeout for comm to emcmot, in seconds +COMM_TIMEOUT = 1.0 + +# BASE_PERIOD is unused in this configuration but specified in core_sim.hal +BASE_PERIOD = 0 +# Servo task period, in nano-seconds +SERVO_PERIOD = 1000000 + +# section for main IO controller parameters ----------------------------------- +[EMCIO] +# tool table file +TOOL_TABLE = sim.tbl +TOOL_CHANGE_POSITION = 0 0 0 +TOOL_CHANGE_QUILL_UP = 1 + +# Hardware Abstraction Layer section -------------------------------------------------- +[HAL] + +# The run script first uses halcmd to execute any HALFILE +# files, and then to execute any individual HALCMD commands. +# + +# list of hal config files to run through halcmd +# files are executed in the order in which they appear +HALFILE = core_sim.hal +HALFILE = sim_spindle_encoder.hal +HALFILE = axis_manualtoolchange.hal +HALFILE = simulated_home.hal +HALFILE = check_xyz_constraints.hal +HALFILE = cooling.hal + +# list of halcmd commands to execute +# commands are executed in the order in which they appear +#HALCMD = save neta + +# Single file that is executed after the GUI has started. Only supported by +# AXIS at this time (only AXIS creates a HAL component of its own) +#POSTGUI_HALFILE = test_postgui.hal +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel + +HALUI = halui + +# Trajectory planner section -------------------------------------------------- +[TRAJ] +COORDINATES = X Y Z +LINEAR_UNITS = inch +ANGULAR_UNITS = degree +MAX_LINEAR_VELOCITY = 4 +DEFAULT_LINEAR_ACCELERATION = 100 +MAX_LINEAR_ACCELERATION = 100 +POSITION_FILE = position.txt + +[KINS] +KINEMATICS = trivkins +JOINTS = 3 + +# Axes sections --------------- +[AXIS_X] +MAX_VELOCITY = 4 +MAX_ACCELERATION = 100.0 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 + +[AXIS_Y] +MAX_VELOCITY = 4 +MAX_ACCELERATION = 100.0 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 + +[AXIS_Z] +MAX_VELOCITY = 4 +MAX_ACCELERATION = 100.0 +MIN_LIMIT = -8.0 +MAX_LIMIT = 0.12 + +# Joints sections ------------- +[JOINT_0] +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 5 +MAX_ACCELERATION = 50.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +FERROR = 0.050 +MIN_FERROR = 0.010 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 20.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 +HOME_IS_SHARED = 1 + +[JOINT_1] +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 5 +MAX_ACCELERATION = 50.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +FERROR = 0.050 +MIN_FERROR = 0.010 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 20.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 + +[JOINT_2] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 5 +MAX_ACCELERATION = 50.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -8.0 + +# Normally the Z max should be 0.000! +# The only reason it's greater than 0 here is so that the splash screen +# gcode will run. +MAX_LIMIT = 0.12 + +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 1.0 +HOME_SEARCH_VEL = 20.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 0 +HOME_IS_SHARED = 1 diff --git a/configs/sim/axis/gstatmessages.py b/configs/sim/axis/gstatmessages.py new file mode 100644 index 00000000000..5c9e1e95d16 --- /dev/null +++ b/configs/sim/axis/gstatmessages.py @@ -0,0 +1,66 @@ + +from hal_glib import GStat +GSTAT = GStat() +GSTAT.forced_update() +GSTAT.connect('jograte-changed', lambda w, data: vars.jog_speed.set(data)) +GSTAT.connect('axis-selection-changed', lambda w,data: select_axis(data)) +GSTAT.connect('cycle-start-request', lambda w, state : cycle_start_request(state)) +GSTAT.connect('cycle-pause-request', lambda w, state: pause_request(state)) +GSTAT.connect('ok-request', lambda w, state: dialog_ext_control(w,1,1)) +GSTAT.connect('cancel-request', lambda w, state: dialog_ext_control(w,1,0)) +GSTAT.connect('macro-call-request', lambda w, name: request_macro_call(name)) +def user_live_update(): + GSTAT.run_iteration() + +def select_axis(data): + if data is None: return + widget = getattr(widgets, "axis_%s" % data.lower()) + widget.focus() + widget.invoke() + +def cycle_start_request(state): + print('cycle start',state) + commands.task_run(None) + +def pause_request(state): + print('cycle pause',state) + commands.task_pauseresume(None) + +def dialog_ext_control(widget,t,state): + print('dialog control',widget,state) + + flag = False + for child in root_window.winfo_children(): + #print(child) + if isinstance(child, Tkinter.Toplevel): + #print(f"Found a Toplevel window: {child}") + if '.!toplevel' in str(child): + #print('sending command:',child) + for child2 in child.winfo_children(): + #print(child2) + if isinstance(child2, Tkinter.Frame): + for child3 in child2.winfo_children(): + #print(child3) + if isinstance(child3, Tkinter.Button): + #print(dir(child3)) + txt = child3.cget("text") + if txt.lower() == 'ok' and state: + #print('Ok') + child3.invoke() + flag = True + break + elif txt.lower() == 'cancel' and not state: + #print('Cancel') + child3.invoke() + flag = True + break + if flag: break + if flag: break + else: + #print('No window') + # remove one error message + if state == 0: + notifications.clear_one() + +def request_macro_call(name): + print('request macro:',name) diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal new file mode 100644 index 00000000000..5db03ed71da --- /dev/null +++ b/configs/sim/axis/panel.hal @@ -0,0 +1,24 @@ +net rate halui.axis.jog-speed panel.jog-rate + +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +#net m0 halui.gui.mdi-command-MACRO0 panel.mdi-0 +#net m1 halui.gui.mdi-command-MACRO1 panel.mdi-1 +#net m2 halui.gui.mdi-command-0 panel.mdi-2 + +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + +net pause halui.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + diff --git a/configs/sim/axis/panel.ui b/configs/sim/axis/panel.ui new file mode 100644 index 00000000000..7264d58f6d0 --- /dev/null +++ b/configs/sim/axis/panel.ui @@ -0,0 +1,365 @@ + + + MainWindow + + + + 0 + 0 + 404 + 560 + + + + MainWindow + + + + + + + + + Axis Selection + + + + + + None + + + true + + + true + + + axis-none + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + 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 + + + + + + + + + + + + + + 0 + 0 + 404 + 22 + + + + + + + + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+
+ + +
From b936cf95636c83baf84e53d771736e9572ff1489 Mon Sep 17 00:00:00 2001 From: Cmorley Date: Wed, 15 Jul 2026 21:00:23 -0700 Subject: [PATCH 68/69] axis -add softkey, exit and reload actions --- configs/sim/axis/gstatmessages.py | 20 + configs/sim/axis/panel.hal | 30 +- configs/sim/axis/panel.ui | 1233 +++++++++++++++++++++++++---- 3 files changed, 1147 insertions(+), 136 deletions(-) diff --git a/configs/sim/axis/gstatmessages.py b/configs/sim/axis/gstatmessages.py index 5c9e1e95d16..8f44c53f499 100644 --- a/configs/sim/axis/gstatmessages.py +++ b/configs/sim/axis/gstatmessages.py @@ -9,6 +9,10 @@ GSTAT.connect('ok-request', lambda w, state: dialog_ext_control(w,1,1)) GSTAT.connect('cancel-request', lambda w, state: dialog_ext_control(w,1,0)) GSTAT.connect('macro-call-request', lambda w, name: request_macro_call(name)) +GSTAT.connect('softkey-pressed', lambda w,data: softkey_pressed(data)) +GSTAT.connect('shutdown-request', lambda w : General_Halt()) +GSTAT.connect('reload-display', lambda w : commands.clear_live_plot()) + def user_live_update(): GSTAT.run_iteration() @@ -64,3 +68,19 @@ def dialog_ext_control(widget,t,state): def request_macro_call(name): print('request macro:',name) + +# external request for a softkey press from HALUI/halbridge +def softkey_pressed(index): + + if index == 0: + root_window.tk.call('.pane.top.tabs','raise','manual') + elif index == 1: + root_window.tk.call('.pane.top.tabs','raise','mdi') + elif index == 2: + root_window.tk.call('.pane.top.right','raise','preview') + elif index == 3: + root_window.tk.call('.pane.top.right','raise','numbers') + elif index == 4: + root_window.tk.call('.pane.top.right','raise','user_0') + else: + print(f'Softkey index:{index}') diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal index 5db03ed71da..c77034fda7d 100644 --- a/configs/sim/axis/panel.hal +++ b/configs/sim/axis/panel.hal @@ -1,8 +1,21 @@ net rate halui.axis.jog-speed panel.jog-rate net sx halui.axis.x.select panel.axis-x +net sx axis.x.jog-enable net sy halui.axis.y.select panel.axis-y +net sy axis.y.jog-enable net sz halui.axis.z.select panel.axis-z +net sz axis.z.jog-enable +net sgui halui.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 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 @@ -20,5 +33,20 @@ net start halui.cycle.pause panel.cycle-pause net abort halui.abort panel.cycle-abort net cancel halui.gui.cancel panel.cancel -net ok halui.gui.ok panel.ok +net ok halui.gui.ok panel.ok + +net softkey0 halui.gui.softkey-0 panel.softkey-0 +net softkey1 halui.gui.softkey-1 panel.softkey-1 +net softkey2 halui.gui.softkey-2 panel.softkey-2 +net softkey3 halui.gui.softkey-3 panel.softkey-3 +net softkey4 halui.gui.softkey-4 panel.softkey-4 +net softkey5 halui.gui.softkey-5 panel.softkey-5 +net softkey6 halui.gui.softkey-6 panel.softkey-6 +net softkey7 halui.gui.softkey-7 panel.softkey-7 +net softkey8 halui.gui.softkey-8 panel.softkey-8 +net softkey9 halui.gui.softkey-9 panel.softkey-9 +net softkey10 halui.gui.softkey-10 panel.softkey-10 +net softkey11 halui.gui.softkey-11 panel.softkey-11 +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-display panel.reload diff --git a/configs/sim/axis/panel.ui b/configs/sim/axis/panel.ui index 7264d58f6d0..164338d0144 100644 --- a/configs/sim/axis/panel.ui +++ b/configs/sim/axis/panel.ui @@ -6,140 +6,218 @@ 0 0 - 404 - 560 + 588 + 706 MainWindow - - + + - - - Axis Selection - - - - - - None - - - true - - - true - - - axis-none - - - - - - - X - - - true - - - true - - - axis-x - - - - - - - Y - - - true - - - true - - - axis-y - - - - - - - Z - - - true - - - true - - - axis-z - - - - - + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + linear rate / angular rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + Qt::Horizontal + + + jog-rate-angular + + + false + + + true + + + + + + + - - - Axis Jog - - - - - - + - - - jog-pos - - - - - - - - - - - jog-neg - - - - - - - - - - jog rate - - - - - - Qt::Horizontal - - - jog-rate - - - true - - - - - + + + + + MPG + + + + + + .001 + + + true + + + true + + + true + + + mpg-scale-small + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.001000000000000 + + + buttonGroup_mpgscale + + + + + + + .01 + + + true + + + true + + + mpg-scale-med + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.010000000000000 + + + buttonGroup_mpgscale + + + + + + + .1 + + + true + + + true + + + mpg-scale-large + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.100000000000000 + + + buttonGroup_mpgscale + + + + + + + + + + + 0 + 74 + + + + false + + + true + + + false + + + mpg-wheel + + + + @@ -334,26 +412,908 @@ + + + + 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 + + + + + - - - - 0 - 0 - 404 - 22 - - - - + + 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 @@ -362,4 +1322,7 @@
+ + + From 4ad3a657b27d3e5b47ae0af44757961adca7b56c Mon Sep 17 00:00:00 2001 From: Cmorley Date: Fri, 17 Jul 2026 05:56:28 -0700 Subject: [PATCH 69/69] axis -allow MPG scrolling of gcode --- configs/sim/axis/gstatmessages.py | 35 ++++++++++++++++++++++++++++--- configs/sim/axis/panel.hal | 1 + 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/configs/sim/axis/gstatmessages.py b/configs/sim/axis/gstatmessages.py index 8f44c53f499..9ab1ccd28b8 100644 --- a/configs/sim/axis/gstatmessages.py +++ b/configs/sim/axis/gstatmessages.py @@ -13,14 +13,43 @@ GSTAT.connect('shutdown-request', lambda w : General_Halt()) GSTAT.connect('reload-display', lambda w : commands.clear_live_plot()) +global last_mpg +last_mpg = 0 +mpg_enabled = 0 + +def user_hal_pins(): + comp.newpin('mpg-enable', hal.HAL_BIT, hal.HAL_IN) + comp.newpin('mpg-in', hal.HAL_S32, hal.HAL_IN) + comp.ready() + def user_live_update(): GSTAT.run_iteration() + global mpg_enabled + try: + if comp['mpg-enable'] or mpg_enabled: + global last_mpg + if comp['mpg-in'] == last_mpg: return + if comp['mpg-in'] > last_mpg:scroll_up(None) + if comp['mpg-in'] < last_mpg:scroll_down(None) + last_mpg = comp['mpg-in'] + except Exception as e: + print(e) def select_axis(data): + global mpg_enabled if data is None: return - widget = getattr(widgets, "axis_%s" % data.lower()) - widget.focus() - widget.invoke() + if data =='MPG0': + mpg_enabled = True + return + mpg_enabled = False + if data.upper() =='NONE': + return + try: + widget = getattr(widgets, "axis_%s" % data.lower()) + widget.focus() + widget.invoke() + except: + pass def cycle_start_request(state): print('cycle start',state) diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal index c77034fda7d..7faa2c26f3b 100644 --- a/configs/sim/axis/panel.hal +++ b/configs/sim/axis/panel.hal @@ -13,6 +13,7 @@ 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