Skip to content

master gremlin: Initialize preview interpreter with active modal gcodes#4123

Merged
andypugh merged 1 commit into
LinuxCNC:masterfrom
Sigma1912:gremlin_fix_initializing_preview_interp
Jun 8, 2026
Merged

master gremlin: Initialize preview interpreter with active modal gcodes#4123
andypugh merged 1 commit into
LinuxCNC:masterfrom
Sigma1912:gremlin_fix_initializing_preview_interp

Conversation

@Sigma1912

@Sigma1912 Sigma1912 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

fixes #4121 and improves preview for gcode with incomplete preambles.

  • Creates a helper file in the Gremlin folder that is copied to lib/python on make.
  • Adds a test creating the initcode according to the current modal gcodes and checking interpreter output on loading a gcode file lacking a preamble.
  • gremlin.py and `test-ui.py' import and use the inicodes helper function
  • qt5-graphics.py used by QtDragon can be easily modified to use the same helper function in a separate PR.

No regression on #2753
No regression on #3855

Affects gmoccapy and any other gui using gremlin.

@Sigma1912 Sigma1912 changed the title gremlin: Initialize preview interpreter with active modal gcodes master gremlin: Initialize preview interpreter with active modal gcodes Jun 4, 2026
@Sigma1912 Sigma1912 force-pushed the gremlin_fix_initializing_preview_interp branch from 7dda1b6 to 93bf5ad Compare June 4, 2026 08:13
Comment thread src/emc/usr_intf/gremlin/gremlin.py
@Sigma1912 Sigma1912 force-pushed the gremlin_fix_initializing_preview_interp branch from 93bf5ad to bd031ae Compare June 4, 2026 13:12
@grandixximo

Copy link
Copy Markdown
Contributor

s.gcodes is scaled by 10 (G21=210, G38.2=382). In Py3 active_gcodes[i]/10 is float division: 170 -> 'G17.0' (invalid), 382 -> 'G38.2' (ok). Whole codes get a stray .0 and won't parse.

hal_glib.py does it right:

code = "G%d" % (i//10) if i%10==0 else "G%d.%d" % (i//10, i%10)

Suggest same split, and a test that initcode round-trips through the interpreter.

@c-morley

c-morley commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

As qr_graphics uses very similar cide, can you add to or create a pull request for it too? I'm away working and dont want to get them out of step.

@Sigma1912

Copy link
Copy Markdown
Contributor Author

@grandixximo

Whole codes get a stray .0 and won't parse.

It's an easy enough change but having a trailing .0 parses fine for me.
Examples:

  • G7.0 does the same as G7
  • G0.0 X1 does the same as G0 X1

I'll have a look at adding a test. Do you have any pointers on how I might test the preview?

@c-morley
Sure, I'll have a look as soon as this is merged.

@c-morley

c-morley commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Your the best thanks ;)

@grandixximo

Copy link
Copy Markdown
Contributor

You're right, I checked read_g in interp_read.cc ( value_read*10, floor, fraction <0.001 accepted ), so G17.0 rounds to 170 and parses identically to G17. My bad, it's purely cosmetic. Only reason to split would be cleaner debug output, fully optional.

For testing the preview headless: gcode.parse(filename, canon, *initcodes) is the core call ( see GLCanon.load_preview in lib/python/rs274/glcanon.py ). You can drive it without a GUI using a canon subclassed from Translated in lib/python/rs274/interpret.py that just records the emitted straight_feed / arc_feed segments. Build the same unitcode/initcode strings your patch generates, feed a g-code file with an incomplete preamble ( e.g. a G2/G3 arc relying on a modal plane, or motion relying on modal G90/G91 ), and assert the recorded segments match the expected path. That can drop straight into tests/ as a non-GUI sim test so it guards against regressions on #4121, #2753 and #3855.

@Sigma1912

Sigma1912 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

@grandixximo
Trying to run gcode.parse(filename, canon, *initcodes) using the PrintCanonclass but I'm struggling.

Running my rump python code (see below) I get this error message :

Starting 
File exists
Traceback (most recent call last):
  File "/home/user/git/mylinuxcnc-master/tests/interp_initcode/./test-ui.py", line 50, in <module>
    result, seq = gcode.parse(filename, PrintCanon, unitcode, initcode)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: interp_error > 0 but no Python exception set
Shutting down and cleaning up LinuxCNC...

After some print debugging I found that apparently ìnterp_error is 18 after this line:

pinterp->init();

Any idea what that might mean?

This is the current rump code:

#!/usr/bin/env python3
import gcode
import sys
import os

class PrintCanon:
    def set_g5x_offset(self, *args):
        print("set_g5x_offset", args)

    def set_g92_offset(self, *args):
        print("set_g92_offset", args)

    def next_line(self, state):
        print("next_line", state.sequence_number)
        self.state = state

    def set_plane(self, plane):
        print("set plane", plane)

    def set_feed_rate(self, arg):
        print("set feed rate", arg)

    def comment(self, arg):
        print("#", arg)

    def straight_traverse(self, *args):
        print("straight_traverse %.4g %.4g %.4g  %.4g %.4g %.4g" % args)

    def straight_feed(self, *args):
        print("straight_feed %.4g %.4g %.4g  %.4g %.4g %.4g" % args)

    def dwell(self, arg):
        if arg < .1:
            print("dwell %f ms" % (1000 * arg))
        else:
            print("dwell %f seconds" % arg)

    def arc_feed(self, *args):
        print("arc_feed %.4g %.4g  %.4g %.4g %.4g  %.4g  %.4g %.4g %.4g" % args)


sys.stderr.write("Starting \n")
filename = "test.ngc"
unitcode = "G21"
initcode = "G7"
if os.path.exists(filename):
    print("File exists")
else:
    print("File does not exist")
result, seq = gcode.parse(filename, PrintCanon, unitcode, initcode)

fail = True
if fail:
    sys.exit(1)

sys.stderr.write('Success\n')
sys.exit(0)

@grandixximo

Copy link
Copy Markdown
Contributor

error 18 isn't a single g-code error, it's the interp_error counter in gcodemodule.cc. It gets bumped every time the interpreter asks the canon a question and the canon has no method to answer ( see the interp_error++ lines in gcodemodule.cc: get_block_delete, get_tool, get_axis_mask, get_external_angular_units, get_external_length_units ). Your bare PrintCanon ( copied from lib/python/rs274/interpret.py ) implements only the motion-output callbacks, not these query callbacks, so it racks up errors during setup. Those callbacks normally live in StatMixin, which PrintCanon doesn't inherit.

For a headless test that needs no running LinuxCNC, just stub them with constants:

class PreviewCanon(PrintCanon):
    def get_external_length_units(self):  return 1.0   # mm; use 1/25.4 for inch
    def get_external_angular_units(self): return 1.0
    def get_axis_mask(self):              return 0b111111111  # all 9 axes
    def get_block_delete(self):           return 0
    def change_tool(self, pocket):        pass
    def get_tool(self, pocket):
        # toolno, x,y,z,a,b,c,u,v,w, diam, frontangle, backangle, orientation
        return (pocket, 0.,0.,0.,0.,0.,0.,0.,0.,0., 0., 0., 0., 0)
    def next_line(self, state):
        self.state = state

canon = PreviewCanon()
result, seq = gcode.parse(filename, canon, "G21", "G7")
if result > gcode.MIN_ERROR:
    print(gcode.strerror(result))

If you'd rather mirror what gremlin actually does ( real tool table, real units ), subclass StatMixin with a live linuxcnc.stat() instead of the stubs, but for a sim test in tests/ the constants above keep it self-contained.

@Sigma1912

Copy link
Copy Markdown
Contributor Author

Thanks for the pointers.

I've got something that seems to be working now.

@Sigma1912 Sigma1912 marked this pull request as draft June 6, 2026 11:39
@Sigma1912 Sigma1912 force-pushed the gremlin_fix_initializing_preview_interp branch 5 times, most recently from d173205 to bf2bdac Compare June 6, 2026 14:17
@Sigma1912 Sigma1912 marked this pull request as ready for review June 6, 2026 14:19
@Sigma1912

Copy link
Copy Markdown
Contributor Author

@grandixximo
Added the test. Is that what you had in mind?

@grandixximo

Copy link
Copy Markdown
Contributor

Add a .gitignore for result, stderr, test.var*, temp_log like tests/startup-state/.gitignore.

@Sigma1912 Sigma1912 force-pushed the gremlin_fix_initializing_preview_interp branch from bf2bdac to e31c081 Compare June 7, 2026 06:51
@Sigma1912

Copy link
Copy Markdown
Contributor Author

Add a .gitignore for result, stderr, test.var*, temp_log like tests/startup-state/.gitignore.

Done

@grandixximo

Copy link
Copy Markdown
Contributor

The .gitignore went into the wrong dir: it landed in tests/abort/feed-rate/.gitignore and overwrote that file's sim.var* line. Please revert feed-rate to sim.var* and add a new tests/interp_initcode/.gitignore:

result
stderr
test.var*
temp_log

Two other things still open:

1. Duplicated init-code logic. create_unitcode_and_initcode() in test-ui.py is a verbatim copy of the loop in gremlin.py's load(), so the test guards its own copy, not the real code. Factor it into one helper both import.

2. finally: sys.exit(0) defeats the error path. It overrides the SystemExit from except, so the script always exits 0 and the sys.exit(1) calls are dead. Also print(e) goes to temp_log, not stderr. Drop the sys.exit(0) from finally and send the error to stderr.

@Sigma1912 Sigma1912 marked this pull request as draft June 7, 2026 09:43
@Sigma1912 Sigma1912 force-pushed the gremlin_fix_initializing_preview_interp branch 4 times, most recently from ec28ca3 to 7d1088f Compare June 7, 2026 12:00
@Sigma1912

Sigma1912 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for your valuable input (and patience).

The .gitignore went into the wrong dir

Apologies, that was a bit sloppy on my part.

  1. Duplicated init-code logic. create_unitcode_and_initcode() in test-ui.py is a verbatim copy of the loop in gremlin.py's load(), so the test guards its own copy, not the real code. Factor it into one helper both import.
  • Created a helper file in the Gremlin folder that is copied to lib/python on make.
  • gremlin.py and `test-ui.py' import and use the inicodes helper function
  • qt5-graphics.py used by QtDragon can be easily modified to use the same helper function in a separate PR.
  1. finally: sys.exit(0) defeats the error path. It overrides the SystemExit from except, so the script always exits 0 and the sys.exit(1) calls are dead. Also print(e) goes to temp_log, not stderr. Drop the sys.exit(0) from finally and send the error to stderr.

Done

Tested:

  • Gremlin preview using Gmoccapy
  • New tests/interp_initcode/

@Sigma1912 Sigma1912 marked this pull request as ready for review June 7, 2026 12:25
@grandixximo

Copy link
Copy Markdown
Contributor

All three points look good now, thanks. One tiny thing on the Submakefile: the added rule targets emc/usr_intf/preview_helpers/%, but the file lives in emc/usr_intf/gremlin/, so that rule never matches. The existing ../lib/python/%: emc/usr_intf/gremlin/% rule right above it already copies preview_helpers.py. The new rule can be dropped ( and there's a stray trailing space after preview_helpers.py in the PYTARGETS += line ).

@Sigma1912 Sigma1912 marked this pull request as draft June 7, 2026 12:49
- Introduces a helper function to create the initcodes required to
 initialize the preview interpreter with the currently active modal
 gcodes.
- Improves preview when loading gcode files with incomplete preambles
- Adds a test that uses the helper function and checks the interpreter
 output after loading a test program with no preamble
@Sigma1912 Sigma1912 force-pushed the gremlin_fix_initializing_preview_interp branch from 7d1088f to 1fabe94 Compare June 7, 2026 12:55
@Sigma1912

Copy link
Copy Markdown
Contributor Author

The new rule can be dropped ( and there's a stray trailing space after preview_helpers.py in the PYTARGETS += line ).

Done. Thanks again for the review!

@Sigma1912 Sigma1912 marked this pull request as ready for review June 7, 2026 12:57
@grandixximo

Copy link
Copy Markdown
Contributor

Great, all looks good now. Thanks for the quick turnaround and the thorough testing. LGTM.

@andypugh andypugh merged commit 0b2133a into LinuxCNC:master Jun 8, 2026
15 checks passed
@Sigma1912 Sigma1912 deleted the gremlin_fix_initializing_preview_interp branch June 8, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(gremlin): incorrect gcode checking due to wrong initialization

4 participants