From 4103c6d511ee4d04bdf16d9205654744b0cb1680 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:21:47 +1000 Subject: [PATCH] preview: one preamble for every GUI, restating modes only AXIS restated the last block's tool change from stat.mcodes[3], so a reload after an MDI M61 Q1 ran a bare M61 and failed. The gremlin GUIs restated a bare G43, which in the preview takes the loaded tool's table offset, so after G43 H, G43.1 or G43.2 the preview started at the wrong height. Reported by Sigma1912. preview_helpers.preview_initcodes() is now the one preamble: it restates only the slots that hold a mode, and the tool length offset in force as G43.1 or G49. AXIS, gremlin and qt5_graphics call it. --- lib/python/preview_helpers.py | 68 ++++++++++++++++-------- src/emc/usr_intf/axis/scripts/axis.py | 65 ++++------------------ src/emc/usr_intf/gremlin/gremlin.py | 4 +- src/emc/usr_intf/gremlin/qt5_graphics.py | 4 +- tests/interp_initcode/README | 2 +- tests/interp_initcode/expected | 19 +++++-- tests/interp_initcode/test-ui.py | 16 ++++-- tests/workplane-preview/test-ui.py | 2 +- 8 files changed, 88 insertions(+), 92 deletions(-) diff --git a/lib/python/preview_helpers.py b/lib/python/preview_helpers.py index bcbb191ba1c..6e2b33aeb3e 100644 --- a/lib/python/preview_helpers.py +++ b/lib/python/preview_helpers.py @@ -3,31 +3,57 @@ import axis_kinds -def create_unitcode_and_initcode(s, inifile): - wrapped = axis_kinds.wrapped(inifile) +# The slots of stat.gcodes and stat.mcodes that hold a mode. The others hold +# the sequence number (issue #271) or the last block's one-shot codes: G10 +# (issue #269), M2, which blanks the next preview, and M6 or M61, which has +# no Q to restate. The tool length offset (gcodes[9]) and the tilted work +# plane (gcodes[12]) are restated with their values instead. +MODAL_GCODES = (3, 6, 14, 7, 5, 4, 10, 16, 8, 11, 13, 15) +MODAL_MCODES = (2, 4, 5, 6, 7, 8, 9) +def preview_initcodes(s, inifile): + """The machine's state as G-code for a preview interpreter to start + from: the startup code, the units, the tilted work plane, the tool + in the spindle, the position, the active modes and the tool length + offset in force.""" + wrapped = axis_kinds.wrapped(inifile) s.poll() - # create unitcode and initcode reflecting currently active modal gcodes - unitcode = "G%d" % (20 + (s.linear_units == 1)) - initcode = "G53 G0 " - for i in range(9): - if s.axis_mask & (1< -1: - initcode = initcode + 'G' + str(active_gcodes[i]/10) + ' ' + codes = [] + startup = inifile.getstring("EMC", "RS274NGC_STARTUP_CODE", fallback="") or \ + inifile.getstring("RS274NGC", "RS274NGC_STARTUP_CODE", fallback="") + if startup: + codes.append(startup) + codes.append("G%d" % (20 + (s.linear_units == 1))) plane = workplane_code(s) if plane: - unitcode = unitcode + ' ' + plane - return unitcode, initcode + codes.append(plane) + codes.append("G90") + codes.append("T%d M6" % s.tool_in_spindle) + axes = [(i, "XYZABCUVW"[i]) for i in range(9) if s.axis_mask & (1 << i)] + position = "G53 G0" + for i, axis in axes: + pos = s.position[i] % 360.0 if wrapped[i] else s.position[i] + position += " %s%.8f" % (axis, pos) + codes.append(position) + for i in MODAL_GCODES: + g = s.gcodes[i] + if g == -1: + continue + if g == 960: # Issue #1232 + codes.append("G96 S%.0f" % s.settings[2]) + else: + codes.append("G%.1f" % (g * .1)) + # The offset itself, which a bare G43 would take from the preview's tool + # table rather than the machine's + if s.gcodes[9] == 490: + codes.append("G49") + else: + codes.append("G43.1" + "".join(" %s%.8f" % (axis, s.tool_offset[i]) + for i, axis in axes)) + for i in MODAL_MCODES: + if s.mcodes[i] != -1: + codes.append("M%d" % s.mcodes[i]) + return codes def workplane_code(s): """The tilted work plane in effect on the machine as one G68.2 block, diff --git a/src/emc/usr_intf/axis/scripts/axis.py b/src/emc/usr_intf/axis/scripts/axis.py index 9477cca0332..7a1c3687cc4 100755 --- a/src/emc/usr_intf/axis/scripts/axis.py +++ b/src/emc/usr_intf/axis/scripts/axis.py @@ -1309,59 +1309,13 @@ def open_file_guts(f, filtered=False, addrecent=True): if timeout: canon.set_timeout(float(timeout)) - initcode = inifile.getstring("EMC", "RS274NGC_STARTUP_CODE", fallback="") - if initcode == "": - initcode = inifile.getstring("RS274NGC", "RS274NGC_STARTUP_CODE", fallback="") - initcodes = [] - if initcode: - initcodes.append(initcode) - if not interpname: - unitcode = "G%d" % (20 + (s.linear_units == 1)) - initcodes.append(unitcode) - plane = preview_helpers.workplane_code(s) - if plane: - initcodes.append(plane) - initcodes.append("g90") - initcodes.append("t%d m6" % s.tool_in_spindle) - for i in range(9): - if s.axis_mask & (1< gcode.MIN_ERROR: self.report_gcode_error(result, seq, filename) self.calculate_gcode_properties(canon) diff --git a/src/emc/usr_intf/gremlin/qt5_graphics.py b/src/emc/usr_intf/gremlin/qt5_graphics.py index ff129882d8e..c4e14cb2c13 100644 --- a/src/emc/usr_intf/gremlin/qt5_graphics.py +++ b/src/emc/usr_intf/gremlin/qt5_graphics.py @@ -500,8 +500,8 @@ def load(self,filename = None): if parameter: shutil.copy(parameter, temp_parameter) canon.parameter_file = temp_parameter - initcodes = preview_helpers.create_unitcode_and_initcode(s, self.inifile) - result, seq = self.load_preview(filename, canon, *initcodes) + initcodes = preview_helpers.preview_initcodes(s, self.inifile) + result, seq = self.load_preview(filename, canon, initcodes) canon.show_notification() if result > gcode.MIN_ERROR: self.report_gcode_error(result, seq, filename) diff --git a/tests/interp_initcode/README b/tests/interp_initcode/README index 7623658061e..8659d9262e7 100644 --- a/tests/interp_initcode/README +++ b/tests/interp_initcode/README @@ -1,5 +1,5 @@ This test - starts LinuxCNC using test.ini - uses MDI commands to change the startup state of the modal gcodes -- initializes an interpreter instance to the currently active modal gcodes using the gremlin helper function and loads a test gcode file that lacks a preamble +- initializes an interpreter instance to the machine state using preview_helpers.preview_initcodes, which all GUI previews use, and loads a test gcode file that lacks a preamble - checks that the start up state reflects [RS274NGC] RS274NGC_STARTUP_CODE and output of the interpreter after loading the test gcode file. diff --git a/tests/interp_initcode/expected b/tests/interp_initcode/expected index c93f6946634..ff7d1c62e18 100644 --- a/tests/interp_initcode/expected +++ b/tests/interp_initcode/expected @@ -1,5 +1,5 @@ startup gcodes (0, 800, -1, 170, 400, 200, 900, 940, 540, 430, 990, 640, -1, 970, 911, 80, -1) -set_g5x_offset (1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +set_g5x_offset (2, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) set_g92_offset (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) set_xy_rotation (0.0,) # interpreter: feed mode set to units per minute @@ -7,14 +7,23 @@ set feed rate 0.0 set plane 1 # interpreter: cutter radius compensation off tool_offset (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) -set_g5x_offset (2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +set_g5x_offset (1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) set_g92_offset (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) set_xy_rotation (0.0,) -# interpreter: retract mode set to r_plane +# interpreter: motion mode set to none straight_traverse (1.23, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +set plane 1 +# interpreter: feed mode set to units per minute +set feed rate 0.0 +# interpreter: cutter radius compensation off +# interpreter: retract mode set to r_plane +set_g5x_offset (2, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +set_g92_offset (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +set_xy_rotation (0.0,) +tool_offset (0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) set feed rate 100.0 -arc_feed (1.23, 0.0, 21.23, 0.0, -1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) -straight_traverse (10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +arc_feed (1.23, 0.0, 21.23, 0.0, -1, -1.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +straight_traverse (10.0, 0.0, -1.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) set_g5x_offset (1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) set_xy_rotation (0.0,) set feed rate 0.0 diff --git a/tests/interp_initcode/test-ui.py b/tests/interp_initcode/test-ui.py index b463ee0ad6b..d433c4f32e6 100755 --- a/tests/interp_initcode/test-ui.py +++ b/tests/interp_initcode/test-ui.py @@ -85,7 +85,6 @@ def close(self, *args): # setup preview interpreter filename = "test.ngc" -canon = PreviewCanon(s, random, parameter) # setup linuxcnc c.state(linuxcnc.STATE_ESTOP_RESET) @@ -98,6 +97,9 @@ def close(self, *args): s.poll() print("startup gcodes ", s.gcodes) +# after a poll: StatMixin copies stat.tool_table, which is empty until then +canon = PreviewCanon(s, random, parameter) + # change some states c.mode(linuxcnc.MODE_MDI) c.wait_complete() @@ -109,13 +111,19 @@ def close(self, *args): c.wait_complete() c.mdi("G10 L2 P0 Z1") c.wait_complete() +# a tool length offset that is not the tool table's (G43.1), and a tool +# change as the last block, which leaves M61 in stat.mcodes[3] +c.mdi("G43.1 Z0.5") +c.wait_complete() +c.mdi("M61 Q0") +c.wait_complete() # parse gcode test file try: - initcodes = preview_helpers.create_unitcode_and_initcode(s, inifile) - result, seq = gcode.parse(filename, canon, *initcodes) + initcodes = preview_helpers.preview_initcodes(s, inifile) + result, seq = gcode.parse(filename, canon, initcodes) if result > gcode.MIN_ERROR: - print(f"G-code error at line {seq}: error code {result}",file=sys.stderr) + print(f"G-code error at line {seq}: {gcode.strerror(result)}", file=sys.stderr) sys.exit(1) except Exception as e: diff --git a/tests/workplane-preview/test-ui.py b/tests/workplane-preview/test-ui.py index e52f1759059..758b9fab35e 100755 --- a/tests/workplane-preview/test-ui.py +++ b/tests/workplane-preview/test-ui.py @@ -75,7 +75,7 @@ def case(name, prog, *setup): errors() s.poll() canon = Canon(s) - res, seq = gcode.parse(prog, canon, *preview_helpers.create_unitcode_and_initcode(s, inifile)) + res, seq = gcode.parse(prog, canon, preview_helpers.preview_initcodes(s, inifile)) c.mode(linuxcnc.MODE_AUTO) c.wait_complete() c.program_open(prog)