Repository navigation
Releases: CEAD-group/nc-gcode-interpreter
Release list
v0.2.7
v0.2.6
v0.2.5
v0.2.4
v0.2.3
v0.2.3 — NcError.kind discriminator + source location on validation e…
v0.2.2
v0.2.2 — batch-path variable events + string_table in state dict (#46)
v0.2.1
CHANGELOG: date the v0.2.1 section for release Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RX8t1oNZLCC9CL8SfHEyg3
v0.2.0
Sinumerik-flavored NC G-code interpreter, released to PyPI.
pip install nc-gcode-interpreter==0.2.0
Full changelog: https://github.com/CEAD-group/nc-gcode-interpreter/blob/main/CHANGELOG.md
Added
-
dwelloutput column: F/S on aG4block is the dwell time (seconds /
spindle revolutions), a per-block parameter - it now lands in its own
never-forward-filleddwellcolumn instead of polluting the modal F/S
columns (previouslyG4 F0.01set the feed to 0.01 mm/min for every
following block until the next real F word) -
Loud warnings for known-but-uninterpreted constructs (never silently
butcher a statement): assignments toAR/AP/RP(opening-angle and
polar arc forms) warn once per run that the motion will be wrong;G91
warns once that incremental dimensioning is not applied; the flattener
warns per word for CIP/CT/POLY/thread/involute pass-through -
TURNoutput column (block address, never forward-filled): additional
full helix turns on G2/G3 blocks; previously swallowed as a user variable -
Curve flattening:
flatten_toleranceonnc_to_dataframe/nc_to_rows/
nc_to_batches(CLI:--flatten-tolerance) converts G2/G3 arcs (I/J/K and
CR= forms, all planes, helical, full circles) and ASPLINE/BSPLINE/CSPLINE
splines (PW weights, SD degree) into runs of G1 rows within a single
max-deviation tolerance of the true curve; interpolation addresses are
consumed, source line numbers and auxiliary cells preserved; generated
samples carry aflattened = 1marker column so the original programmed
points remain distinguishable -
Optional
vizextra (threejs-viewer >= 0.0.41):
nc_gcode_interpreter.viz.view_toolpath(df)shows a toolpath in
threejs-viewer as an animated bead tube (feed-rate float64 time base,
programmed vs flattened point coloring), and thenc-viewconsole command
interprets + flattens + animates an .mpf in one step, with a nozzle
marker riding the path tip, camera follow/look-at tracking, and travel
moves drawn natively as a thin line in lockstep with the bead -
Program jumps and branches:
GOTOF/GOTOB/GOTO/GOTOC/GOTOSand
CASE ... OF ... DEFAULT, with per-scope label/block-number resolution,
jumps out of IF bodies and loops, and alarm-14080 semantics for an
unresolved target (GOTOCwarns and continues) (#29) -
Streaming API
nc_to_rows(program): yields(line_no, row)lazily while
the interpreter runs on a background thread — batch-identical typing and
forward-fill, constant memory, early abort by dropping the iterator,
errors raised at the offending row, final state on the exhausted
iterator (#35) -
nc_to_rows(..., include_variables=True)yields(line_no, row, variables)with per-block variable-assignment deltas, exposing
variable-only blocks that the batch DataFrame prunes (#35) -
Spline programming:
PW/SD/PLblock addresses become output columns
(not forward-filled, noTRANSoffset) instead of being silently
swallowed (#18) -
G2/G3 arc interpolation parameters
I/J/K/CRbecome per-block output
columns (not forward-filled, noTRANSoffset) instead of being silently
dropped — arcs previously came out as straight-line endpoints (#37) -
nc_to_batches(program, batch_size=...): interpret a program into a
stream of columnar polars DataFrames built on a worker thread and handed
over via the Arrow C data interface — bounded memory for programs too
large to fit in one DataFrame (#37) -
Parsing: leading-underscore identifiers (
_WITH_M0), assignment to
$AC_*system variables ($AC_TIMER[1] = 0), and theNOTlogical
operator (#37) -
DEF STRING[n]string-variable declarations and quoted-string
assignments; strings stay out of the numeric pipeline (a string in an
expression, or a type mismatch, is a hard error, never a silent 0.0).
String processing (SPRINT/INDEX/<<) remains out of scope and
fails loudly (#40) -
Logic, comparison and bit operators in expressions (
AND/OR/XOR,
B_AND/B_OR/B_XOR,==/<>/</>/<=/>=) at the manual's
priorities, so conditions likeIF (A == 1 AND B == 1)work and
comparison results are assignable (R11 = R10 >= 100) (#41) -
$AA_IW[<axis>]/$AA_IM[<axis>]read the interpreted actual work /
machine position of an axis, so layer loops
(REPEAT ... UNTIL $AA_IW[Z] > H) terminate; reading before the axis is
positioned errors loudly and the variables are read-only (#42) -
nc-viewprints a corrected retry command on the classic new-machine
failures (missing--axis-index-map, undefined machine-parameter
variables) instead of a bare traceback -
docs/sinumerik-execution-model.md: how a real control executes NC code
versus this interpreter, and why (#30)
Changed
- Rust-side polars is gone. The Table -> Python DataFrame handoff no longer
builds a polars DataFrame in Rust (viapyo3-polars); it builds an Arrow
record batch with the minimalarrow-array/arrow-schema/arrow-data
crates and hands it to Python zero-copy through the Arrow PyCapsule
interface (__arrow_c_array__), wherepl.DataFrame(...)wraps it. The
Python API is unchanged (still returnspolars.DataFrame), needs no
pyarrow, and performance is unchanged. This drops ~60 crates from the
python-feature build (127 -> 64), cutting a clean release build ~4x
(83s -> 21s), and bumps PyO3 0.28 -> 0.29 (resolving the RUSTSEC pyo3
advisories). - Breaking:
I/J/K/CRare now treated as arc interpolation-parameter
block addresses (output columns), so they can no longer be used as user
variable names:I=5followed byX=I+1was a variable read before and is
now an undefined-variable error. Matches Sinumerik address semantics (#37) nc_to_dataframeis now the concatenation of the internal batch stream
rather than collecting every row up front: same output, but bounded
intermediate memory and interpretation overlapped with DataFrame assembly
(a 1.1 GB program went from ~209 s / 6 GB to ~33 s / 3.3 GB) (#37)- An executed
M2/M17/M30now ends the program immediately, even
mid-file, instead of being ignored (#29) - Unknown G codes now error like Sinumerik alarm 12470 instead of silently
parsing as a subprogram call (#31) - Unsupported or unmodeled statements (parameterized
ROT/SCALE/MIRROR
frames, and previously jumps/LOOPbefore #29 implemented them) raise a
clearUnsupportedStatementerror instead of corrupting output; a frame
instruction following another statement in the same block errors (#20,
#32) - Frame semantics per the manual: absolute frame instructions (
TRANS,
bareROT/SCALE/MIRROR) substitute — deleting previously programmed
offsets — rather than accumulate (#20, #26) - Values are f64 end-to-end to match the control's 64-bit
REAL:
coordinates are bit-exact for decimal literals,DIVtruncates the real
result (7 DIV 4.1 = 1), and==/<>/<=/>=use the manual's 1e-12
relative tolerance (#23) - The ~488-command G vocabulary and its 60 G-groups moved out of the PEG
grammar into a Rust table generated fromggroups.json; the grammar
recognizes lexical shape only (#32) - Rust-side polars removed: the core returns a plain typed table and the
Python wrapper builds the DataFrame, so any Python polars version works;
CSV and DataFrame output are unchanged (#27) - Dependency refresh: pest 2.8.6, pyo3 0.28, clap 4.6, polars 1.42.1 (#28)
Fixed
- The NC language is case-insensitive (manual 3.3.2): lowercase axis
words (g1 x0 y0) are axis moves rather than silently-dropped subprogram
calls, G/M values are normalized to uppercase, and user-variable
identifiers fold case (a program that declareslAYER_HEIGHTbut assigns
LAYER_HEIGHTis one variable, not two) (#43) - The IC-before-position warning now states what actually happens ("axis
incremented withIC()before any absolute position was set; assuming it
starts at 0") instead of the vague "behavior may be indeterminate" - Expression evaluation: correct operator precedence (
2+3*4is now 14,
not 20), degree-based trig per the manual, correctedATAN2argument
order, andDIVby zero errors instead of panicking (#19) TRANS/ATRANStranslation is applied per row at output time under the
frame active at that block; fixes double-applied offsets onIC()moves
and axis movement as a parsing side effect (#26)- Unclosed or crossed
IF/WHILE/FOR/LOOP/REPEATstructures are
reported at the (innermost) opener's own line instead of as a parse
failure at end-of-file (#33) - Parse errors are phrased for humans (no grammar-internal rule names),
mistyped axis words likeY2Owarn, and unresolved jump targets get
did-you-mean suggestions — direction-aware when the label exists but only
in the other search direction (#31)
Performance
- Stage-1 line triage: structure-free CAM programs run through a byte
decoder for the >99.9% trivial lines, per-line parses for the rest —
a 319k-line program drops from ~33 s to ~9 s innc_to_dataframe
(NC_STAGE1=0disables) (#34) - Grammar restructuring: ~15% faster parsing, plus an ignored 1M-line
benchmark harness (#30); an earlier redundant-lookahead removal cut parse
time ~27% (#25)