From ed5442c83927f40501af12a2df5360e5928cb063 Mon Sep 17 00:00:00 2001 From: david mueller Date: Sun, 18 Jan 2026 12:06:27 +0100 Subject: [PATCH 1/6] Gmoccapy: Restore tooltips and reconnect handlers for tooltable buttons --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 06b67f7bd58..1b156853bd3 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1997,24 +1997,31 @@ def _init_tooleditor(self): btn_delete.set_size_request(56, 56) btn_delete.set_label("") btn_delete.set_image(self.widgets.img_tool_delete) + btn_delete.set_tooltip_text(_("Delete selected tools")) btn_delete.set_always_show_image(True) + btn_delete.connect("clicked",self.on_btn_delete_tool_clicked) # Add button btn_add = self.widgets.tooledit1.wTree.get_object("add") btn_add.set_size_request(56, 56) btn_add.set_label("") btn_add.set_image(self.widgets.img_tool_add) + btn_add.set_tooltip_text(_("Add new tool")) btn_add.set_always_show_image(True) # Reload button btn_reload = self.widgets.tooledit1.wTree.get_object("reload") btn_reload.set_size_request(56, 56) btn_reload.set_label("") btn_reload.set_image(self.widgets.img_tool_reload) + btn_reload.set_tooltip_text(_("Reload tool table from file")) btn_reload.set_always_show_image(True) + btn_reload.disconnect_by_func(self.widgets.tooledit1.reload) + btn_reload.connect("clicked",self.on_btn_reload_tooltable_clicked) # Save button btn_save = self.widgets.tooledit1.wTree.get_object("apply") btn_save.set_size_request(56, 56) btn_save.set_label("") btn_save.set_image(self.widgets.img_tool_save) + btn_save.set_tooltip_text(_("Save tool table to file")) btn_save.set_always_show_image(True) # Create a label for current tool in spindle lbl_tool = Gtk.Label() @@ -5446,17 +5453,10 @@ def on_btn_delete_tool_clicked(self, widget, data=None): self.widgets.tooledit1.delete(None) self.widgets.tooledit1.set_selected_tool(act_tool) - def on_btn_add_tool_clicked(self, widget, data=None): - self.widgets.tooledit1.add(None) - def on_btn_reload_tooltable_clicked(self, widget, data=None): self.widgets.tooledit1.reload(None) self.widgets.tooledit1.set_selected_tool(self.stat.tool_in_spindle) - def on_btn_save_tool_changes_clicked(self, widget, data=None): - self.widgets.tooledit1.save(None) - self.widgets.tooledit1.set_selected_tool(self.stat.tool_in_spindle) - def on_btn_tool_touchoff_clicked(self, widget, data=None): if not self.widgets.tooledit1.get_selected_tool(): message = _("No or multiple tools selected in the tool table. ") From 027c75af03ffb4ec2ec3ccc6e8a404b5d9f22cbd Mon Sep 17 00:00:00 2001 From: david mueller Date: Sun, 18 Jan 2026 12:27:33 +0100 Subject: [PATCH 2/6] Gmoccapy: Fix current tool check when deleting multiple tools --- lib/python/gladevcp/tooledit_widget.py | 10 ++++++++-- src/emc/usr_intf/gmoccapy/gmoccapy.py | 11 ++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/python/gladevcp/tooledit_widget.py b/lib/python/gladevcp/tooledit_widget.py index b5df98c98b3..1f800782f61 100644 --- a/lib/python/gladevcp/tooledit_widget.py +++ b/lib/python/gladevcp/tooledit_widget.py @@ -206,10 +206,16 @@ def match_value_cb(model, path, iter, pathlist): pathlist = [] liststore.foreach(match_value_cb, pathlist) # foreach works in a depth first fashion - if len(pathlist) != 1: + if len(pathlist) == 0: return None - else: + elif len(pathlist) == 1: return(liststore.get_value(liststore.get_iter(pathlist[0]),1)) + else: + selected_tools = [] + for path in pathlist: + tool = (liststore.get_value(liststore.get_iter(path[0]),1)) + selected_tools.append(tool) + return selected_tools # return tool number of the highlighted (ie selected) row def get_selected_row(self): diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 1b156853bd3..300dacb1049 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -1999,6 +1999,7 @@ def _init_tooleditor(self): btn_delete.set_image(self.widgets.img_tool_delete) btn_delete.set_tooltip_text(_("Delete selected tools")) btn_delete.set_always_show_image(True) + btn_delete.disconnect_by_func(self.widgets.tooledit1.delete) btn_delete.connect("clicked",self.on_btn_delete_tool_clicked) # Add button btn_add = self.widgets.tooledit1.wTree.get_object("add") @@ -5443,15 +5444,15 @@ def on_tool_change(self, widget): self.halcomp['toolchange-changed'] = False def on_btn_delete_tool_clicked(self, widget, data=None): - act_tool = self.stat.tool_in_spindle - if act_tool == self.widgets.tooledit1.get_selected_tool(): + selected_tools = self.widgets.tooledit1.get_selected_tool() + if not isinstance(selected_tools, list): + selected_tools = [selected_tools] + if self.stat.tool_in_spindle in selected_tools: 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.warning_dialog(self, _("Warning Tool can not be deleted!"), message) return - - self.widgets.tooledit1.delete(None) - self.widgets.tooledit1.set_selected_tool(act_tool) + self.widgets.tooledit1.delete(widget) def on_btn_reload_tooltable_clicked(self, widget, data=None): self.widgets.tooledit1.reload(None) From 5635759a3f80a123fb39d2e82cbddc2e4ac42396 Mon Sep 17 00:00:00 2001 From: david mueller Date: Sun, 18 Jan 2026 17:47:08 +0100 Subject: [PATCH 3/6] Gmoccapy: better handling of unsaved changes in tooltable - show message on leaving tool table with unsaved changes - reload tooltable from file when showing the table --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 54 ++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 300dacb1049..96d41125565 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2008,6 +2008,8 @@ def _init_tooleditor(self): btn_add.set_image(self.widgets.img_tool_add) btn_add.set_tooltip_text(_("Add new tool")) btn_add.set_always_show_image(True) + btn_add.disconnect_by_func(self.widgets.tooledit1.add) + btn_add.connect("clicked",self.on_btn_add_tool_clicked) # Reload button btn_reload = self.widgets.tooledit1.wTree.get_object("reload") btn_reload.set_size_request(56, 56) @@ -2024,6 +2026,8 @@ def _init_tooleditor(self): btn_save.set_image(self.widgets.img_tool_save) btn_save.set_tooltip_text(_("Save tool table to file")) btn_save.set_always_show_image(True) + btn_save.disconnect_by_func(self.widgets.tooledit1.save) + btn_save.connect("clicked",self.on_btn_save_tool_changes_clicked) # Create a label for current tool in spindle lbl_tool = Gtk.Label() self.widgets.tooledit1.lbl_tool = lbl_tool @@ -2038,12 +2042,16 @@ def _init_tooleditor(self): btn_calculator.set_active(self.toolpage_use_calc) btn_calculator.connect("toggled", self.on_toolpage_use_calc_toggled) buttonbox.pack_start(btn_calculator,False,False,50) - column_cell_ids = ["toggle", "tool#1", "pos1", "x1", "y1", "z1", "a1", "b1", "c1", "u1", "v1", "w1", - "d1", "front1", "back1", "orient1", "cell_comments1"] + column_cell_ids = ["toggle", "tool#1", "pos1", + "x1", "y1", "z1", "a1", "b1", "c1", "u1", "v1", "w1", + "d1", "front1", "back1", "orient1", "comments1"] for col, name in enumerate(column_cell_ids): - if col > 0 and col < 16: + if col > 0 and col < 17: temp = self.widgets.tooledit1.wTree.get_object("cell_%s" % name) - temp.connect('editing-started', self.on_tool_col_edit_started, col) + if col < 16: # calulator is only useful for nummeric columns (ie not for 'Comments') + temp.connect('editing-started', self.on_tool_col_edit_started, col) + temp.connect('edited', self.on_tool_col_edited) + self.widgets.tooledit1.edited = False # override 'tooledit_widget' method 'set_selected_tool' self.widgets.tooledit1.set_selected_tool = self.set_selected_tool @@ -2124,6 +2132,7 @@ def on_tool_col_edit_started(self, widget, filtered_path, new_text, col): store[row][col] = value else: store[row][col] = f"{value:11.4f}" + self.widgets.tooledit1.edited = True # this is needed to get offsetview out of editing mode GLib.timeout_add(50, toolview.set_cursor, @@ -2131,6 +2140,9 @@ def on_tool_col_edit_started(self, widget, filtered_path, new_text, col): toolview.get_columns()[0], True) + def on_tool_col_edited (self, *args): + self.widgets.tooledit1.edited = True + def _init_themes(self): # If there are themes then add them to combo box model = self.widgets.theme_choice.get_model() @@ -2606,6 +2618,8 @@ def _show_tooledit_tab(self, state): self.widgets.ntb_preview.set_property("show-tabs", not state) self.widgets.vbx_jog.hide() self.widgets.ntb_preview.set_current_page(2) + self.widgets.tooledit1.reload(None) + self.widgets.tooledit1.edited = False self.widgets.tooledit1.set_selected_tool(self.stat.tool_in_spindle) if self.widgets.chk_use_kb_on_tooledit.get_active(): self.widgets.ntb_info.set_current_page(1) @@ -4710,6 +4724,11 @@ def on_btn_back_clicked(self, widget, data=None): self.widgets.tbtn_fullsize_preview0.set_active(False) self.on_tbtn_fullsize_preview_toggled(self.widgets.tbtn_fullsize_preview0) 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!")) + if not result: # user says no, he want to save + return self.widgets.ntb_button.set_current_page(_BB_MANUAL) self.widgets.ntb_main.set_current_page(0) self.widgets.ntb_preview.set_current_page(0) @@ -5453,11 +5472,26 @@ def on_btn_delete_tool_clicked(self, widget, data=None): self.dialogs.warning_dialog(self, _("Warning Tool can not be deleted!"), message) return self.widgets.tooledit1.delete(widget) + self.widgets.tooledit1.edited = True + + def on_btn_add_tool_clicked(self, widget, data=None): + self.widgets.tooledit1.add(None) + self.widgets.tooledit1.edited = True 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!")) + if not result: # user says no, he want to save + return self.widgets.tooledit1.reload(None) + self.widgets.tooledit1.edited = False self.widgets.tooledit1.set_selected_tool(self.stat.tool_in_spindle) + def on_btn_save_tool_changes_clicked(self, widget, data=None): + self.widgets.tooledit1.save(None) + self.widgets.tooledit1.edited = False + def on_btn_tool_touchoff_clicked(self, widget, data=None): if not self.widgets.tooledit1.get_selected_tool(): message = _("No or multiple tools selected in the tool table. ") @@ -5513,6 +5547,11 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): # select a tool entering a number 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!")) + 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 "), label=_("Select the tool to change"), integer=True) if value == "ERROR": @@ -5535,9 +5574,14 @@ def on_btn_select_tool_by_no_clicked(self, widget, data=None): # Next two lines fix issue #3129 caused by GStat missing changes in interpreter mode command = "G4 P{0}".format(self.get_ini_info.get_cycle_time()/1000) self.command.mdi(command) - + # set tool with M61 Q? or with T? M6 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!")) + 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") From 354d91ea2976b434b39ef5c4cd202230d7678a69 Mon Sep 17 00:00:00 2001 From: david mueller Date: Tue, 20 Jan 2026 08:56:16 +0100 Subject: [PATCH 4/6] Gmoccapy: Fix 'Diameter' value in 'Tool information' frame not updating on saving tooltable --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 96d41125565..e2bf4065ab1 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2052,8 +2052,15 @@ def _init_tooleditor(self): temp.connect('editing-started', self.on_tool_col_edit_started, col) temp.connect('edited', self.on_tool_col_edited) self.widgets.tooledit1.edited = False - # override 'tooledit_widget' method 'set_selected_tool' + # override 'tooledit_widget' method 'set_selected_tool' so we can set the label text self.widgets.tooledit1.set_selected_tool = self.set_selected_tool + # override 'tooledit_widget' method 'toolfile_stale' so we can also update toolinfo + self.widgets.tooledit1.toolfile_stale = self.toolfile_stale + + def toolfile_stale(self): + self._update_toolinfo(self.widgets.tooledit1.toolinfo_num) + self.widgets.tooledit1.reload(None) + self.widgets.tooledit1.set_selected_tool(self.widgets.tooledit1.toolinfo_num) def set_selected_tool(self, toolnumber): lbl_tool_text = "Tool loaded: " + str(toolnumber) From 4788f77e613f03bf470afbf2e346e2fc891ecfef Mon Sep 17 00:00:00 2001 From: david mueller Date: Wed, 21 Jan 2026 08:38:27 +0100 Subject: [PATCH 5/6] Gmoccapy: Add user confirmation on leaving tooltable with active tool offset changed --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index e2bf4065ab1..22c4d0f15a5 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -4736,6 +4736,24 @@ def on_btn_back_clicked(self, widget, data=None): result = self.dialogs.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 + tt = self.stat.tool_table[0] + new_offset = (tt.xoffset, tt.yoffset, tt.zoffset, + tt.aoffset, tt.boffset, tt.coffset, + tt.uoffset, tt.voffset, tt.woffset) + if (new_offset != self.stat.tool_offset) and ("G43" in self.active_gcodes): + message = _("Offset values for the tool in the spindle\n" \ + "have been changed whith tool compensation (G43) active.\n\n" \ + "Do you want the new values to be applied as the currently\n" \ + "active tool offset?") + result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + if result: # user says YES + self.command.mode(linuxcnc.MODE_MDI) + self.command.wait_complete() + self.command.mdi("G43") + self.command.wait_complete() + self.command.mode(linuxcnc.MODE_MANUAL) + self.command.wait_complete() self.widgets.ntb_button.set_current_page(_BB_MANUAL) self.widgets.ntb_main.set_current_page(0) self.widgets.ntb_preview.set_current_page(0) From 746679c206821ca9d09004d155016b99e22cbdc0 Mon Sep 17 00:00:00 2001 From: david mueller Date: Thu, 22 Jan 2026 08:47:08 +0100 Subject: [PATCH 6/6] Gmoccapy: hide/show 'Select' column with 'Delete' button Avoids confusion about using the 'Select' column to select tool to change to --- src/emc/usr_intf/gmoccapy/gmoccapy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index 22c4d0f15a5..65693d28160 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -2042,6 +2042,7 @@ def _init_tooleditor(self): btn_calculator.set_active(self.toolpage_use_calc) btn_calculator.connect("toggled", self.on_toolpage_use_calc_toggled) buttonbox.pack_start(btn_calculator,False,False,50) + self.widgets.tooledit1.wTree.get_object("s1").set_visible(False) column_cell_ids = ["toggle", "tool#1", "pos1", "x1", "y1", "z1", "a1", "b1", "c1", "u1", "v1", "w1", "d1", "front1", "back1", "orient1", "comments1"] @@ -5488,6 +5489,9 @@ def on_tool_change(self, widget): self.halcomp['toolchange-changed'] = False def on_btn_delete_tool_clicked(self, widget, data=None): + if not self.widgets.tooledit1.wTree.get_object("s1").get_visible(): + self.widgets.tooledit1.wTree.get_object("s1").set_visible(True) + return selected_tools = self.widgets.tooledit1.get_selected_tool() if not isinstance(selected_tools, list): selected_tools = [selected_tools] @@ -5496,6 +5500,7 @@ def on_btn_delete_tool_clicked(self, widget, data=None): message += _("This is not allowed, please change tool prior to delete it") self.dialogs.warning_dialog(self, _("Warning Tool can not be deleted!"), message) return + self.widgets.tooledit1.wTree.get_object("s1").set_visible(False) self.widgets.tooledit1.delete(widget) self.widgets.tooledit1.edited = True