nc-gcode-interpreter 0.2.9__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/.github/workflows/build-and-release.yml +6 -6
  2. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/CHANGELOG.md +282 -0
  3. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/Cargo.lock +37 -1
  4. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/Cargo.toml +7 -1
  5. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/Development.md +1 -1
  6. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/PKG-INFO +2 -2
  7. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/README.md +22 -5
  8. nc_gcode_interpreter-0.3.0/benches/extensions.rs +160 -0
  9. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/docs/resumable-interpretation-design.md +1 -1
  10. nc_gcode_interpreter-0.3.0/examples/axis_index_assignment.csv +5 -0
  11. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/axis_index_assignment.mpf +1 -1
  12. nc_gcode_interpreter-0.3.0/examples/defaults.def +6 -0
  13. nc_gcode_interpreter-0.3.0/examples/defaults_start.json +1 -0
  14. nc_gcode_interpreter-0.3.0/examples/function_calls.csv +3 -0
  15. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/function_calls.mpf +1 -1
  16. nc_gcode_interpreter-0.3.0/examples/multiple_m_codes.csv +9 -0
  17. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/multiple_m_codes.mpf +1 -1
  18. nc_gcode_interpreter-0.3.0/examples/r_param.csv +3 -0
  19. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/r_param.mpf +1 -1
  20. nc_gcode_interpreter-0.3.0/examples/simple.csv +6 -0
  21. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/simple.mpf +1 -1
  22. nc_gcode_interpreter-0.3.0/examples/trans_ic.csv +14 -0
  23. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/trans_ic.mpf +1 -1
  24. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/ggroups/generate_g_commands.py +14 -0
  25. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/pyproject.toml +1 -1
  26. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/nc_gcode_interpreter/__init__.py +184 -33
  27. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/nc_gcode_interpreter/_internal.pyi +19 -3
  28. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/nc_gcode_interpreter/cli.py +23 -4
  29. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_batch_variables.py +7 -4
  30. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_batches.py +25 -8
  31. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_diagnostics.py +3 -3
  32. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_expected_output.py +14 -14
  33. nc_gcode_interpreter-0.3.0/python/tests/test_feed_check.py +180 -0
  34. nc_gcode_interpreter-0.3.0/python/tests/test_feed_group.py +132 -0
  35. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_flags.py +2 -2
  36. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_flatten.py +8 -3
  37. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_frames.py +41 -41
  38. nc_gcode_interpreter-0.3.0/python/tests/test_g_group_enum.py +138 -0
  39. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_g_vocabulary.py +3 -3
  40. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_real_comparisons.py +1 -1
  41. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_stage1.py +29 -11
  42. nc_gcode_interpreter-0.3.0/python/tests/test_start_inputs.py +292 -0
  43. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_streaming.py +11 -8
  44. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_string_operations.py +3 -3
  45. nc_gcode_interpreter-0.3.0/python/tests/test_subprograms.py +628 -0
  46. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_unsupported.py +2 -2
  47. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/errors.rs +45 -7
  48. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/flatten.rs +70 -36
  49. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/grammar.pest +59 -8
  50. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/interpret_rules.rs +1246 -34
  51. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/interpreter.rs +246 -37
  52. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/lib.rs +558 -46
  53. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/line_driver.rs +117 -23
  54. nc_gcode_interpreter-0.3.0/src/main.rs +318 -0
  55. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/modal_groups.rs +214 -0
  56. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/output.rs +1047 -60
  57. nc_gcode_interpreter-0.3.0/src/start.rs +1010 -0
  58. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/state.rs +117 -2
  59. nc_gcode_interpreter-0.3.0/src/subprogram.rs +655 -0
  60. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/vm.rs +2 -1
  61. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/tests/cli.rs +48 -0
  62. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/uv.lock +59 -47
  63. nc_gcode_interpreter-0.2.9/examples/axis_index_assignment.csv +0 -5
  64. nc_gcode_interpreter-0.2.9/examples/function_calls.csv +0 -3
  65. nc_gcode_interpreter-0.2.9/examples/multiple_m_codes.csv +0 -9
  66. nc_gcode_interpreter-0.2.9/examples/r_param.csv +0 -3
  67. nc_gcode_interpreter-0.2.9/examples/simple.csv +0 -6
  68. nc_gcode_interpreter-0.2.9/examples/trans_ic.csv +0 -14
  69. nc_gcode_interpreter-0.2.9/src/main.rs +0 -168
  70. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/.github/workflows/benchmark.yml +0 -0
  71. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/.gitignore +0 -0
  72. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/CONTRIBUTING.md +0 -0
  73. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/LICENSE +0 -0
  74. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/TODO.md +0 -0
  75. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/benches/fixtures.rs +0 -0
  76. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/benches/workloads.rs +0 -0
  77. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/build.rs +0 -0
  78. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/build_backend/nc_build_backend.py +0 -0
  79. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/docs/sinumerik-execution-model.md +0 -0
  80. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/actual_position.csv +0 -0
  81. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/actual_position.mpf +0 -0
  82. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/arc.csv +0 -0
  83. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/arc.mpf +0 -0
  84. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/arrays.csv +0 -0
  85. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/arrays.mpf +0 -0
  86. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/basic_math.csv +0 -0
  87. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/basic_math.mpf +0 -0
  88. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/case.csv +0 -0
  89. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/case.mpf +0 -0
  90. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/case_insensitive_variables.csv +0 -0
  91. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/case_insensitive_variables.mpf +0 -0
  92. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/custom_vars.csv +0 -0
  93. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/custom_vars.mpf +0 -0
  94. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/def_string.csv +0 -0
  95. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/def_string.mpf +0 -0
  96. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/defaults.csv +0 -0
  97. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/defaults.mpf +0 -0
  98. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/edge_cases.csv +0 -0
  99. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/edge_cases.mpf +0 -0
  100. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/flattening/flatten_demo.mpf +0 -0
  101. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/flattening/flatten_demo_flattened.csv +0 -0
  102. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/flattening/flatten_demo_raw.csv +0 -0
  103. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/for_loop.csv +0 -0
  104. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/for_loop.mpf +0 -0
  105. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/goto.csv +0 -0
  106. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/goto.mpf +0 -0
  107. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/if_statement.csv +0 -0
  108. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/if_statement.mpf +0 -0
  109. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/increment.csv +0 -0
  110. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/increment.mpf +0 -0
  111. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/logic_operators.csv +0 -0
  112. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/logic_operators.mpf +0 -0
  113. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/loop.csv +0 -0
  114. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/loop.mpf +0 -0
  115. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/precedence.csv +0 -0
  116. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/precedence.mpf +0 -0
  117. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/repeat.csv +0 -0
  118. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/repeat.mpf +0 -0
  119. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/rot.csv +0 -0
  120. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/rot.mpf +0 -0
  121. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/spline.csv +0 -0
  122. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/spline.mpf +0 -0
  123. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/tool.csv +0 -0
  124. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/tool.mpf +0 -0
  125. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/trans.csv +0 -0
  126. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/trans.mpf +0 -0
  127. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/trans2.csv +0 -0
  128. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/trans2.mpf +0 -0
  129. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/trans_reset.csv +0 -0
  130. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/trans_reset.mpf +0 -0
  131. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/trig.csv +0 -0
  132. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/trig.mpf +0 -0
  133. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/variables.csv +0 -0
  134. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/examples/variables.mpf +0 -0
  135. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/example/minimal.py +0 -0
  136. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/example/streaming.py +0 -0
  137. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/nc_gcode_interpreter/ggroups.json +0 -0
  138. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/nc_gcode_interpreter/py.typed +0 -0
  139. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/nc_gcode_interpreter/viz.py +0 -0
  140. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/nc_source.py +0 -0
  141. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_arithmetic_functions.py +0 -0
  142. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_compare_benchmarks.py +0 -0
  143. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_field_defects.py +0 -0
  144. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_g_groups.py +0 -0
  145. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_jumps.py +0 -0
  146. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/python/tests/test_structured_errors.py +0 -0
  147. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/rustfmt.toml +0 -0
  148. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/scripts/benchmark.py +0 -0
  149. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/scripts/compare_benchmarks.py +0 -0
  150. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/frame.rs +0 -0
  151. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/line_driver/experiments.rs +0 -0
  152. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/structure_scan.rs +0 -0
  153. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/src/types.rs +0 -0
  154. {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.3.0}/tests/performance_contract.rs +0 -0
@@ -80,7 +80,7 @@ jobs:
80
80
  uses: PyO3/maturin-action@v1
81
81
  with:
82
82
  target: ${{ matrix.platform.target }}
83
- args: --release --out dist --find-interpreter
83
+ args: --release --out dist -i python3.12 -i python3.13 -i python3.14 -i python3.14t -i python3.15 -i python3.15t
84
84
  sccache: 'true'
85
85
  manylinux: auto
86
86
  - name: Upload wheels
@@ -156,8 +156,8 @@ jobs:
156
156
  uses: PyO3/maturin-action@v1
157
157
  with:
158
158
  target: ${{ matrix.platform.target }}
159
- # Named interpreters: --find-interpreter also picks up the image's PyPy,
160
- # which pyo3-ffi 0.29.0 fails to compile for.
159
+ # Every wheel job names its interpreters (the set 0.2.8 shipped per platform):
160
+ # --find-interpreter picks up whatever the image has, e.g. PyPy, which fails.
161
161
  args: --release --out dist -i python3.12 -i python3.13 -i python3.14 -i python3.14t
162
162
  sccache: 'true'
163
163
  manylinux: auto
@@ -216,7 +216,7 @@ jobs:
216
216
  uses: PyO3/maturin-action@v1
217
217
  with:
218
218
  target: ${{ matrix.platform.target }}
219
- args: --release --out dist --find-interpreter
219
+ args: --release --out dist -i python3.12 -i python3.13 -i python3.14
220
220
  sccache: 'true'
221
221
  - name: Upload wheels
222
222
  uses: actions/upload-artifact@v4
@@ -256,7 +256,7 @@ jobs:
256
256
  uses: PyO3/maturin-action@v1
257
257
  with:
258
258
  target: ${{ matrix.platform.target }}
259
- args: --release --out dist --find-interpreter
259
+ args: --release --out dist -i python3.12 -i python3.13 -i python3.14 -i python3.15
260
260
  sccache: 'true'
261
261
  - name: Upload wheels
262
262
  uses: actions/upload-artifact@v4
@@ -305,7 +305,7 @@ jobs:
305
305
  contents: write
306
306
  attestations: write
307
307
  steps:
308
- # Do NOT use `merge-multiple: true`. With `--find-interpreter`, every
308
+ # Do NOT use `merge-multiple: true`. With a shared interpreter list, every
309
309
  # build matrix leg (py3.12 AND py3.13) produces cp312/cp313/cp314 wheels
310
310
  # with identical filenames but non-identical bytes (sccache makes builds
311
311
  # nondeterministic). Merging them into one directory makes
@@ -4,6 +4,288 @@ Notable changes to **nc-gcode-interpreter**. The format loosely follows
4
4
  [Keep a Changelog](https://keepachangelog.com/); versions are git tags,
5
5
  released to PyPI.
6
6
 
7
+ ## [v0.3.0] - 2026-11-11
8
+
9
+ This release changes the API and the output. To upgrade:
10
+
11
+ - Replace `initial_state` with `definitions` (definition files),
12
+ `variables` (values) and `start` (machine state); see the first entry
13
+ under Changed.
14
+ - Program a feed before the first path motion, or pass it in `start` (key
15
+ `"feed"`): a missing feed is now the `feed_not_programmed` error, as on
16
+ the control (alarm 10860).
17
+ - The G-group columns are `pl.Enum`, and polars 1.32 or newer is required.
18
+ `cast(pl.String)` restores the old dtype.
19
+ - New loud errors for constructs that used to be misread: two calls in one
20
+ block, `G4` with axis words, `FGROUP`/`FGREF` on non-axes, reading
21
+ `FGREF`, and others listed below.
22
+
23
+ ### Added
24
+
25
+ - Subprogram execution (#48). `nc_to_dataframe` and `nc_to_batches` take
26
+ `subprograms`, a mapping of subprogram name to source text. A call whose
27
+ name is in the mapping runs that text; any other call stays a
28
+ `non_returning_function_call` cell as before. Supported: a `PROC` header
29
+ with by-value parameters (numbers and strings, with defaults), `VAR`
30
+ parameters and `VAR` array parameters, local `DEF` variables (invisible to
31
+ callees, gone on return), return on `M17` or `RET`, and nested calls up to
32
+ 16 levels. Rows produced inside a call keep the main-program line of the
33
+ call in `line_no` and carry two new columns: `subprogram`, the call path
34
+ (`OUTER/INNER`), and `subprogram_line`, the line inside that subprogram's
35
+ text. The interpreter does not read files; the caller supplies the texts.
36
+ - `m_function_subprograms`, a mapping of M function number to subprogram
37
+ name: the subprogram runs at the end of every block that programs that M
38
+ function, as a control does for a tool-change cycle on `M6`. It does not
39
+ apply inside the replacement subprogram itself.
40
+ - External variables. `external_variables` takes a provider object with
41
+ `names`, `read(name, indices)` and `write(name, indices, value)`. Every
42
+ read or write of a listed name (`$A_IN`, `$A_DBB`, `$AA_IM`, or any other
43
+ variable name) goes to the provider and is not stored by the interpreter,
44
+ so a program can branch and wait on machine state the interpreter does not
45
+ model. Indices arrive as ints, or as the axis name where an axis is the
46
+ index. With `nc_to_batches` the rows produced so far are yielded before the
47
+ provider is asked, and the provider is called on the consuming thread, so
48
+ a simulator can play those rows first. An exception raised by the provider
49
+ ends the run and propagates unchanged.
50
+ - Rust API: `interpreter::nc_to_table_with` and
51
+ `interpreter::nc_to_batch_stream_with` take a `subprogram::Extensions`
52
+ value with the same extensions; the provider is the
53
+ `subprogram::ExternalVariables` trait.
54
+ - `required_subprograms`: a call of a listed name that is not among
55
+ `subprograms` is an error that names the call path, where it would
56
+ otherwise stay an opaque cell.
57
+ - `stop_on_setal`: a `SETAL` call ends the run with an error that carries the
58
+ call as written and the call path.
59
+ - String comparison with `==` and `<>` in conditions and expressions
60
+ (`IF (KIND == "MILL")`). Other operators on strings, and a string compared
61
+ with a number, are errors. A served external variable compared with a
62
+ string is read from the provider as text (`read` returns a `str`).
63
+ - A frame value assigned to a served external variable
64
+ (`$P_UIFR[7] = CTRANS(X, 10) : CROT(Z, 90)`) reaches the provider's `write`
65
+ as `[("CTRANS", ("X", 10.0)), ("CROT", ("Z", 90.0))]`. Assigned to any
66
+ other variable it is an error.
67
+ - Machine axis names (`AX1`) and frame components (`TR`, `FI`, `RT`, `SC`,
68
+ `MI`) are passed to the provider as index names.
69
+ - `transformation` column: the last `TRAORI`, `TRANSMIT`, `TRACYL`, `TRAANG`,
70
+ `TRACON` or `TRAFOOF` call as written, forward-filled like a modal G group.
71
+ The call also stays in `non_returning_function_call`.
72
+ - Definition files parse: the `DEF` attributes for range, preprocessing stop,
73
+ access rights, unit and limits (`DEF CHAN APW 4 REAL LLI 0 ULI 10 NAME`),
74
+ the `CHAR` type, and attribute-only `REDEF` lines are accepted and not
75
+ interpreted.
76
+ - Array assignment as a statement: `NAME[n] = SET(v, ...)` and
77
+ `NAME[n] = REP(v[, count])` write consecutive elements starting at the
78
+ indexed one, last index fastest. Before, only the `DEF` initializer form
79
+ was interpreted and the statement form was an internal error.
80
+ - A subprogram whose `PROC` header has the `SAVE` attribute gives the caller
81
+ back its modal G functions on return: each G group the subprogram changed
82
+ gets the caller's value on the row after the body's rows. A group the
83
+ program never set before the call cannot be restored and gives a warning.
84
+ - Loud errors for constructs the subprogram mechanism does not model: a
85
+ `FOR` counter, a `PROC` parameter or a local `DEF` with the name of an
86
+ external variable, a string assigned to an external variable, a `PROC` parameter with the name of an axis or
87
+ address, `SET`/`REP` statements that write past the declared array, and
88
+ two `subprograms` entries whose names differ only in case.
89
+ - Program repetitions: `L6 P2` and `L6 P=2` call `L6` twice (manual
90
+ 4.2.3.3). A supplied subprogram runs that many times, with its arguments
91
+ passed once; without a supplied body the call keeps its name and the
92
+ count goes to a new `call_repetitions` column. A count outside 1 to 9999
93
+ is an error. Before, `L6 P2` was recorded as a call named `P2` and
94
+ `L6 P=2` set a user variable `P` (part of #105).
95
+ - `PCALL NAME(...)` calls `NAME` (manual 4.2.3.8). A path before the name
96
+ (`PCALL/_N_WKS_DIR/_N_X_WPD/NAME`) is not interpreted and gives a
97
+ warning; the call goes to `NAME`. Before, `PCALL` was dropped silently.
98
+
99
+ - `FGROUP(<axes>)` and `FGREF[<axis>]=<radius>` are modal state (manual
100
+ 3.7.1). A forward-filled, dictionary-encoded `FGROUP` column holds the axes
101
+ the path feed refers to, comma-separated as programmed; `FGROUP()` writes
102
+ `""` for the machine-data default, which the interpreter does not know.
103
+ Each programmed axis gets a forward-filled `FGREF[<axis>]` float column;
104
+ null means the control default. Both columns appear only when the program
105
+ uses them, after the axis columns. Before, `FGROUP` was stored as a
106
+ subprogram call in `non_returning_function_call` and `FGREF[<axis>]` needed
107
+ an `axis_index_map` entry and became the variable `FGREF[<index>]`; it now
108
+ resolves by axis name and is no longer a variable. An argument that is not
109
+ a configured channel axis (including N, F, S, D and T) is an
110
+ `unexpected_axis` error. Reading `FGREF[<axis>]` is an
111
+ `unsupported_statement` error, and so is a user array named `FGREF` (#107).
112
+
113
+ ### Changed
114
+
115
+ - **Breaking:** `initial_state` is removed from `nc_to_dataframe`,
116
+ `nc_to_batches`, `nc_to_rows`, the Rust interpreter functions and the CLIs
117
+ (`-i/--initial_state`, `--initial-state`). It ran a string of NC text before
118
+ the program and discarded its rows, which mixed three jobs. Three inputs
119
+ replace it, applied in this order before the main program:
120
+ - `definitions`: a list of definition files (GUD/MGUD/UGUD `.DEF`), each
121
+ read on its own. A file may hold DEF and REDEF statements, assignments to
122
+ variables and comments; the `%_N_..._DEF` header and `$PATH=` line are
123
+ skipped. M17, M30 or M2 ends that file and reading goes on with the next,
124
+ so several files no longer need their end codes stripped. `CHANDATA(1)`
125
+ is accepted and ignored; another channel number is an error. `TRAFOOF`
126
+ on a block of its own sets the start transformation to off, in the first
127
+ row and the returned state, unless `start["transformation"]` gives
128
+ another, so a restore program (CHANDATA(1), TRAFOOF, SET array
129
+ assignments, M30) reads unchanged. The program stops M0 and M1 are
130
+ ignored. A subprogram call stays an error naming the call. Assigning
131
+ to a name the files have not declared is an error, unless it is an R
132
+ parameter or `allow_undefined_variables` is set. Motion, G codes or
133
+ anything else is an error naming the file (`NcError.definition_file`,
134
+ its index in the list) and the line within it. Migration: pass the DEF
135
+ files as a list instead of concatenating them into `initial_state`.
136
+ - `variables`: a dict of values (`float`, `int`, `bool`, `str`) written
137
+ straight into the variable tables and checked against the declared type
138
+ (2.5 into an INT, or a string into a REAL or an R parameter, is an
139
+ error). Names must be
140
+ defined unless they are R parameters, system variables or
141
+ `allow_undefined_variables` is set. Migration: replace generated
142
+ `NAME=VALUE` lines with `variables={"NAME": value}`; strings now work.
143
+ - `start`: the machine state at program start, in the shape of the
144
+ returned state dict (`axes`, `translation`, `rotation`, `symbol_table`,
145
+ `string_table`, plus the new `g_groups`, `feed`, `tool`,
146
+ `transformation`, `fgroup` and `fgref`), so a returned state can be
147
+ passed back. `fgroup` is the FGROUP column value and `fgref` maps a
148
+ channel axis to its FGREF reference radius. Modal G groups
149
+ are checked against the G group table, and they, `feed`, `tool` (the `T`
150
+ column), `transformation`, `fgroup` and `fgref` fill the first output row, so those columns
151
+ no longer start as null. Axis positions and the frame only seed the internal state.
152
+ A name in both `variables` and `start` is an error. Migration: replace
153
+ axis words and frame instructions in `initial_state` with
154
+ `start={"axes": {...}, "translation": {...}}`, and modal G codes and F
155
+ with `start={"g_groups": {...}, "feed": ...}`.
156
+ The returned state gains `g_groups` (modal G group column to code, as of
157
+ the last row), `feed`, `tool`, `transformation`, `fgroup` and `fgref`. The CLIs take `--definitions FILE` (repeatable),
158
+ `--variables FILE.json` and `--start FILE.json`. The Rust functions take
159
+ `Option<&start::StartInputs>` where they took `Option<&str>`.
160
+
161
+ - **Breaking:** a block that traverses a path (G1, G2, G3, CIP, splines and
162
+ the other path motions) while no feed is programmed is an error
163
+ (`NcError.kind == "feed_not_programmed"`), as alarm 10860 is on the
164
+ control. G0, the G4 dwell, G74/G75 and the thread commands need no F;
165
+ G63 tapping does. A change of feed unit deletes F and FZ (Axes and
166
+ spindles manual 7.2.1, 7.2.2): inverse time (G93), traversing time
167
+ (G931), per minute (G94, G961, G971, G942) and per revolution (G95, G96,
168
+ G97, G952, G973); G962 and G972 keep the unit that was active. A change
169
+ within one unit (G95 to G96) keeps F. When the previous feed type is
170
+ unknown (never programmed, not in `start["g_groups"]`) nothing is
171
+ deleted. A feed seeded from `start` is start state, not an F of the first
172
+ block, so a feed type change there deletes it too. `FZ=` (feed per tooth)
173
+ counts as a feed under a revolutional feed type and `FB=` (non-modal feed,
174
+ 7.4.10) for its own block; both are now output columns (`FZ`
175
+ forward-filled, `FB` not), where they used to become variables. G4 with
176
+ axis words in the same block is an error (G4 stands alone, manual
177
+ 3.14.7). G93 recommends but does not
178
+ require F in every block, so that is not checked. The control's default
179
+ feed SD42110 `$SC_DEFAULT_FEED` (0 unless set) is evaluated at program
180
+ start: pass it as `start["feed"]`. The check is off when F is not a
181
+ configured axis. FGROUP only selects the axes F refers to, so a block
182
+ moving only synchronized or rotary axes needs F the same way, and FGROUP
183
+ and FGREF neither move nor delete F. Tree walker, VM and line fast path
184
+ raise the same error.
185
+ Migration: program F before the first path motion or pass
186
+ `start={"feed": ...}`. The examples `simple`, `trans_ic`, `r_param`,
187
+ `axis_index_assignment`, `multiple_m_codes` and `function_calls` gained
188
+ `F1000` on their first path motion, and their expected CSVs gained the
189
+ `F` column (no other column changed).
190
+
191
+ - **Breaking:** every G-group column (`gg01_motion`, `gg02_wait`, ...) in the
192
+ output of `nc_to_batches`, `nc_to_dataframe` and `sanitize_dataframe` is
193
+ now a `pl.Enum` instead of `String`. Its categories are that group's
194
+ commands in manual order, the same in every batch and every run, so batches
195
+ and runs concatenate without a cast and a value outside the group cannot
196
+ occur. Rust hands the codes over as an Arrow dictionary that polars imports
197
+ as `Enum` directly, with no cast. The new `G_GROUP_DTYPES` maps each G-group column name to its
198
+ `pl.Enum`, for matching schemas and empty frames. `T`, `comment`,
199
+ `non_returning_function_call` and other free-text columns stay `String`,
200
+ and `nc_to_rows` still yields `str`. On a 1M-row program the G-group cells
201
+ take 1 byte each instead of a 16-byte string view, and filtering or
202
+ grouping on them is about 1.4 to 2 times faster, at unchanged
203
+ interpretation time. Consequences
204
+ for code that reads these columns:
205
+ - `.str` methods raise `InvalidOperationError`, and joining against a
206
+ `String` column raises `SchemaError`.
207
+ - `fill_null("")` silently leaves the nulls in place, because `""` is not a
208
+ category.
209
+ - `is_in` with a value outside the group raises; `==` with such a literal
210
+ returns no matches.
211
+ - Sorting follows manual order (`G0`, `G1`, `G2`, ...), not alphabetical
212
+ order.
213
+ - A strict `pl.concat` with frames from an older version, or
214
+ `assert_frame_equal` against `String` frames, fails on the dtype.
215
+ - Migration: `df.with_columns(pl.col(c).cast(pl.String) for c in
216
+ G_GROUP_DTYPES if c in df.columns)` restores the old dtype.
217
+
218
+ - **Breaking:** the minimum polars version is now 1.32 (was 1.9). The Enum
219
+ handoff relies on the Arrow field metadata polars reads from 1.32 on.
220
+
221
+ - A program that calls `TRAORI`, `TRANSMIT`, `TRACYL`, `TRAANG`, `TRACON` or
222
+ `TRAFOOF` gets the new `transformation` column, also when it uses none of
223
+ the new arguments.
224
+ - Two subprogram calls in one block (`FOO BAR`) are an error. Before, the
225
+ second silently replaced the first. Keyword G commands such as `SOFT`
226
+ next to a call are unaffected. A repeat count before the name (`P3 FOO`,
227
+ `P=3 FOO`) is an error as well (part of #105).
228
+ - `PROC`, `EXTERN` and `RET` are reserved words and no longer read as
229
+ subprogram calls. A `PROC` header and an `EXTERN` declaration produce no
230
+ output; `RET` ends the program at the top level like `M17`. Before,
231
+ `PROC FOO SAVE` was recorded as a call named `SAVE` and `RET` as a call
232
+ while interpretation continued (part of #105). A main program whose `PROC`
233
+ header declares parameters is an error.
234
+ - A program without control structures keeps the line fast path when an
235
+ extension is used. Only its lines with a call, an M function mapped to a
236
+ subprogram, or a word that names a served variable go through the
237
+ grammar, which dispatches them. Before, any extension sent the whole
238
+ program through the grammar path, about seven times slower per line.
239
+ - `DEF REAL A[n]` declares the elements `A[0]` to `A[n-1]`. Before, it also
240
+ created `A[n]`, so an out-of-range access went unnoticed.
241
+ - `REDEF` and `PCALL` are reserved words.
242
+ - A supplied subprogram is checked and parsed on its first call, and later
243
+ calls in the same run reuse the parse tree. Before, every call parsed the
244
+ text again, which was about two thirds of the time of a call of a 20-line
245
+ body. Adds the `self_cell` dependency, which holds the tree next to the
246
+ text it borrows from.
247
+ - `nc_to_dataframe` with `external_variables` no longer sends the rows
248
+ produced so far as a separate batch before each provider call, since its
249
+ caller reads no row before the run ends. A program that reads the provider
250
+ on every motion row built one batch per row. `nc_to_batches` and the Rust
251
+ API keep handing the rows over first; Rust callers can opt out with
252
+ `Extensions::defer_rows`.
253
+
254
+ ## [v0.2.10] - 2026-11-10
255
+
256
+ ### Changed
257
+
258
+ - `nc_to_batches` and `nc_to_dataframe` do less work per output row. The
259
+ G-group columns are held as per-batch dictionary codes and written straight
260
+ to the Arrow string array, instead of one `String` per row per column that
261
+ the consumer thread then freed; the batch builder finds a cell's column
262
+ without hashing its name; the streaming driver reuses its decode buffers.
263
+ Python output is unchanged (same columns, `String` dtype, values and
264
+ `variable_events`). With `flatten_tolerance`, line numbers and variables on,
265
+ a 7.2M-row, 442 MiB linear program takes 3.3 s instead of 8.3 s and a
266
+ 22M-row, 1.1 GB program 10.5 s instead of 24 s, at the same peak memory
267
+ (#89).
268
+
269
+ - `flatten_tolerance` costs almost nothing on rows that are not curves: the
270
+ flattener tracked positions with an allocation and a lookup per configured
271
+ axis for every row, about 2 s on the 7.2M-row program above, now about
272
+ 0.15 s. B-spline flattening reuses one working buffer per curve; a
273
+ 5.4M-row, 380 MB spline program takes 8 s instead of 16 s (#89).
274
+
275
+ - Rust API: `Column` has a new `Dict(DictColumn)` variant, used for the
276
+ G-group columns (`values` plus one `u32` code per row,
277
+ `DictColumn::NULL` for a null cell). Code that matches `Column`
278
+ exhaustively needs one more arm; `DictColumn::iter` yields the cells as
279
+ `Option<&str>` (#89).
280
+
281
+ ### Fixed
282
+
283
+ - A numeric tool selection (`T2`, `T=3`, `T=R1`) now reaches the output table.
284
+ It is written to the same string `T` column as `T="name"`, as the number's
285
+ text (`"2"`), on the row of the block that programs it. Before, only named
286
+ tools appeared and a numeric one was dropped without a warning. The value is
287
+ still stored as the variable `T` in the final state, as before (#101).
288
+
7
289
  ## [v0.2.9] - 2026-11-01
8
290
 
9
291
  ### Changed
@@ -506,7 +506,7 @@ checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
506
506
 
507
507
  [[package]]
508
508
  name = "nc-gcode-interpreter"
509
- version = "0.2.9"
509
+ version = "0.3.0"
510
510
  dependencies = [
511
511
  "arrow-array",
512
512
  "arrow-data",
@@ -518,6 +518,8 @@ dependencies = [
518
518
  "pyo3",
519
519
  "rayon",
520
520
  "rustc-hash",
521
+ "self_cell",
522
+ "serde_json",
521
523
  "thiserror",
522
524
  ]
523
525
 
@@ -745,6 +747,21 @@ version = "1.0.23"
745
747
  source = "registry+https://github.com/rust-lang/crates.io-index"
746
748
  checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
747
749
 
750
+ [[package]]
751
+ name = "self_cell"
752
+ version = "1.3.0"
753
+ source = "registry+https://github.com/rust-lang/crates.io-index"
754
+ checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813"
755
+
756
+ [[package]]
757
+ name = "serde"
758
+ version = "1.0.228"
759
+ source = "registry+https://github.com/rust-lang/crates.io-index"
760
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
761
+ dependencies = [
762
+ "serde_core",
763
+ ]
764
+
748
765
  [[package]]
749
766
  name = "serde_core"
750
767
  version = "1.0.228"
@@ -765,6 +782,19 @@ dependencies = [
765
782
  "syn",
766
783
  ]
767
784
 
785
+ [[package]]
786
+ name = "serde_json"
787
+ version = "1.0.154"
788
+ source = "registry+https://github.com/rust-lang/crates.io-index"
789
+ checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
790
+ dependencies = [
791
+ "itoa",
792
+ "memchr",
793
+ "serde",
794
+ "serde_core",
795
+ "zmij",
796
+ ]
797
+
768
798
  [[package]]
769
799
  name = "sha2"
770
800
  version = "0.10.9"
@@ -1023,3 +1053,9 @@ dependencies = [
1023
1053
  "quote",
1024
1054
  "syn",
1025
1055
  ]
1056
+
1057
+ [[package]]
1058
+ name = "zmij"
1059
+ version = "1.0.23"
1060
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1061
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "nc-gcode-interpreter"
3
- version = "0.2.9"
3
+ version = "0.3.0"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -20,6 +20,7 @@ thiserror = "2.0.18"
20
20
  clap = { version = "4.6", features = ["derive"] }
21
21
  rayon = "1.10"
22
22
  rustc-hash = "2.1"
23
+ serde_json = "1"
23
24
 
24
25
  # NOTE: keep every dependency version inline (`crate = { version = "..." }`),
25
26
  # NOT as a `[dependencies.crate]` block with a standalone `version = "..."`
@@ -44,6 +45,7 @@ pyo3 = { version = "0.29", optional = true }
44
45
  arrow-array = { version = "59", optional = true, default-features = false, features = ["ffi"] }
45
46
  arrow-schema = { version = "59", optional = true, default-features = false }
46
47
  arrow-data = { version = "59", optional = true, default-features = false }
48
+ self_cell = "1"
47
49
 
48
50
  [features]
49
51
  default = []
@@ -54,3 +56,7 @@ python = ["pyo3", "arrow-array", "arrow-schema", "arrow-data"]
54
56
  name = "workloads"
55
57
  harness = false
56
58
  required-features = ["bench-experiments"]
59
+
60
+ [[bench]]
61
+ name = "extensions"
62
+ harness = false
@@ -78,7 +78,7 @@ evaluated for its extra build time across the wheel matrix.
78
78
  There are a bunch of csv files in the examples directory. To test the tool on all of them (use git to check changes)
79
79
 
80
80
  ```bash
81
- rm **/*.csv && cargo build --release && find examples -name "*.mpf" -type f -print0 | xargs -0 -I {} sh -c './target/release/nc-gcode-interpreter --axis-index-map E:4 --initial_state=examples/defaults.mpf "$1" || echo "Failed to process $1" >&2' sh {}
81
+ rm **/*.csv && cargo build --release && find examples -name "*.mpf" -type f -print0 | xargs -0 -I {} sh -c './target/release/nc-gcode-interpreter --axis-index-map E:4 --definitions examples/defaults.def --start examples/defaults_start.json "$1" || echo "Failed to process $1" >&2' sh {}
82
82
  ```
83
83
 
84
84
  ## python test
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nc-gcode-interpreter
3
- Version: 0.2.9
3
+ Version: 0.3.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -9,7 +9,7 @@ Classifier: License :: OSI Approved :: MIT License
9
9
  Classifier: Operating System :: OS Independent
10
10
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
11
  Requires-Dist: maturin>=1.7.4
12
- Requires-Dist: polars>=1.9.0
12
+ Requires-Dist: polars>=1.32.0
13
13
  Requires-Dist: jupyter>=1.1.1 ; extra == 'dev'
14
14
  Requires-Dist: pytest>=8.3.3 ; extra == 'test'
15
15
  Requires-Dist: mypy ; extra == 'test'
@@ -20,6 +20,7 @@ The **NC-GCode-Interpreter** offers a streamlined and efficient solution for int
20
20
  - **Array Operations**: Manages arrays and allows operations on them.
21
21
  - **Incremental Changes**: Facilitates incremental changes in axes positions like `X=IC(2)`.
22
22
  - **Spline Programming**: `ASPLINE`, `BSPLINE` and `CSPLINE` blocks with their start/end conditions (`BAUTO`/`BNAT`/`BTAN`, `EAUTO`/`ENAT`/`ETAN`) and the spline block addresses `PW` (point weight), `SD` (spline degree) and `PL` (parameter interval length). Block addresses appear as output columns; unlike axes they receive no `TRANS` offset and are not forward-filled, since e.g. a point weight only applies to the point it is programmed with.
23
+ - **Feed group**: `FGROUP(<axes>)` and `FGREF[<axis>]=<radius>` are modal state, not calls or variables. The `FGROUP` column holds the axes the path feed `F` refers to, comma-separated as programmed (`"RA1,RA2,RA3"`); `FGROUP()` gives `""`, the machine-data default, as does a null before the first `FGROUP`. Each `FGREF[<axis>]` column holds the reference radius of that axis; null means the control default (360 mm / 2π for a rotary axis). Both are forward-filled, appear only when the program uses them, and need no `axis_index_map` entry. The interpreter computes no timing from them.
23
24
 
24
25
  ### Additional Functionality
25
26
 
@@ -28,7 +29,8 @@ The **NC-GCode-Interpreter** offers a streamlined and efficient solution for int
28
29
  - **Curve flattening**: pass `flatten_tolerance` (Python) or `--flatten-tolerance` (CLI) to convert curved motions — `G2`/`G3` arcs (`I`/`J`/`K` centre offsets or `CR=` radius form, `G17`/`G18`/`G19` planes, helical third axis with `TURN=` multi-turn support, full circles) and `ASPLINE`/`BSPLINE`/`CSPLINE` splines (including `PW` weights and `SD` degree) — into runs of plain `G1` rows in the same table format. `CIP`/`CT`/`POLY`/thread/involute blocks pass through unchanged with a warning; spline start/end conditions (`BAUTO`/`BNAT`/... ) are approximated by natural (CSPLINE) / Akima (ASPLINE) boundaries — see the `flatten` module docs for the full list of checked approximations. The single tolerance is the maximum deviation between the emitted polyline and the true curve, in path units: arcs use the exact sagitta bound, splines adaptive subdivision. The interpolation addresses (`I`/`J`/`K`/`CR`/`PW`/`SD`/`PL`) are consumed and do not appear in the output; sampled rows keep the source block's line number, and its other cells (`F`, `M`, comments, ...) ride on the first sample so modal behavior is preserved. Generated samples carry a `flattened = 1` marker column (never forward-filled); programmed positions stay null there, so filtering on null recovers the original toolpath points.
29
30
  - **Toolpath visualization** (optional): with the `viz` extra (`pip install 'nc-gcode-interpreter[viz]'`), `nc_gcode_interpreter.viz.view_toolpath(df)` renders an interpreted toolpath in [threejs-viewer](https://pypi.org/project/threejs-viewer/) as an extruded bead tube with a draw-range animation replaying the program at feed-rate-proportional speed. Combined with `flatten_tolerance`, programmed points render orange and flattener-generated samples blue. The extra also installs the `nc-view` command: `nc-view part.mpf` interprets, flattens (default tolerance 0.1, `--no-flatten` to skip) and opens the animated toolpath in the browser; `--speed` sets the time-lapse factor (default 60 = one machine-minute per second).
30
31
  - **Custom Axes**: Allows users to define additional axes beyond the standard `X`, `Y`, `Z`.
31
- - **Initial State Configuration**: Enables the use of an initial state MPF file to set default values for multiple runs.
32
+ - **Definitions, variables and start state**: `definitions` takes the definition files a control loads (GUD/MGUD/UGUD `.DEF`: DEF and REDEF statements, assignments, comments; M17/M30/M2 ends a file), `variables` takes variable values checked against their declared type, and `start` takes the machine state at program start in the shape of the returned `state` dict (axes, frame, variables, modal G groups, feed, tool, transformation, FGROUP and FGREF radii). The G groups, feed, tool, transformation, FGROUP and FGREF fill the first output row; seeded G groups have the same `pl.Enum` dtype as the rest of the column. They are applied in that order, before the main program. In a definition file `CHANDATA(1)` is ignored, another channel is an error, `TRAFOOF` sets the start transformation to off (a later `start["transformation"]` wins), M0 and M1 are ignored, a subprogram call is an error, and assigning to an undeclared name is an error unless it is an R parameter or `allow_undefined_variables` is set.
33
+ - **Feed check**: a path motion (G1, G2, G3, CIP, splines) with no feed programmed is an error, as alarm 10860 is on the control; a change of feed unit (inverse time, per minute, per revolution) deletes F; `FZ=` counts as a feed under a revolutional feed type and `FB=` for its own block. FGROUP does not change this: a block moving only axes outside the FGROUP, or only rotary axes, needs F too. Pass the control's default feed (SD42110 `$SC_DEFAULT_FEED`) as `start["feed"]`. The check is off when F is not a configured axis. On the CLI: `--definitions FILE` (repeatable), `--variables FILE.json` and `--start FILE.json`.
32
34
  - **CLI Options**: Numerous command-line options to customize the processing, such as axis overriding, loop limits, and more.
33
35
 
34
36
  ## Example Usage
@@ -66,7 +68,9 @@ Arguments:
66
68
  Options:
67
69
  -a, --axes <AXIS> Override default axis identifiers (comma-separated, e.g., "X,Y,Z")
68
70
  -e, --extra-axes <EXTRA_AXIS> Add extra axis identifiers (comma-separated, e.g., "RA1,RA2")
69
- -i, --initial_state <INITIAL_STATE> Optional initial state file to e.g. define global variables or set axis positions
71
+ --definitions <FILE> Definition file (GUD/MGUD .DEF) to read before the program; repeat for several, read in order
72
+ --variables <FILE> JSON object of variable values applied after the definitions, e.g. {"COUNT": 3}
73
+ --start <FILE> JSON object with the machine state at program start (the shape of the returned state)
70
74
  -l, --iteration_limit <LIMIT> Maximum number of iterations for loops [default: 10000]
71
75
  -f, --disable-forward-fill Disable forward-filling of null values in axes columns
72
76
  -h, --help Print help
@@ -113,7 +117,7 @@ shape: (14, 7)
113
117
  ┌───────┬───────┬──────┬────────┬───────────┬─────────────┬─────────────────────────────────┐
114
118
  │ X ┆ Y ┆ Z ┆ F ┆ M ┆ gg01_motion ┆ comment │
115
119
  │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
116
- │ f64 ┆ f64 ┆ f64 ┆ f64 ┆ list[str] ┆ str ┆ str │
120
+ │ f64 ┆ f64 ┆ f64 ┆ f64 ┆ list[str] ┆ enum ┆ str │
117
121
  ╞═══════╪═══════╪══════╪════════╪═══════════╪═════════════╪═════════════════════════════════╡
118
122
  │ null ┆ null ┆ null ┆ null ┆ null ┆ null ┆ ;size of the square │
119
123
  │ null ┆ null ┆ null ┆ null ┆ null ┆ null ┆ ;size of the square │
@@ -131,6 +135,17 @@ shape: (14, 7)
131
135
 
132
136
  The Python bindings also return the state of the program after execution, which can be used for inspection.
133
137
 
138
+ #### Column dtypes
139
+
140
+ Axis and other value columns are `Float64`, `N` is `Int64` and `M` is `List(String)`. Each G-group column (`gg01_motion`, `gg06_plane_select`, ...) is a `pl.Enum` whose categories are that group's commands in manual order; `G_GROUP_DTYPES` maps every G-group column name to its dtype, for building matching schemas or empty frames. Every batch and every run has the same Enum per column, so frames concatenate without a cast. The Python package requires polars 1.32 or newer. `T`, `comment`, `non_returning_function_call` and other free-text columns stay `String`. `nc_to_rows` yields plain `str` values.
141
+
142
+ Things that differ from a `String` column:
143
+
144
+ - `pl.col("gg01_motion") == "G1"` and `is_in(["G0", "G1"])` work; `is_in` with a value outside the group raises.
145
+ - `.str` methods and joins against a `String` column raise; cast first with `.cast(pl.String)`.
146
+ - `fill_null("")` leaves the nulls in place, because `""` is not a category. Cast to `String` first.
147
+ - Sorting follows manual order (`G0`, `G1`, `G2`, ...), not alphabetical order.
148
+
134
149
  #### Streaming
135
150
 
136
151
  For long programs (or when you want results before the whole file is interpreted), `nc_to_rows` yields rows lazily while the interpreter runs on a background thread. Each row carries the 1-based source line it came from — loops and jumps repeat line numbers, which is exactly what a visualizer needs to map trace rows back to the program:
@@ -178,7 +193,7 @@ Measure a local program with
178
193
  --threads 4 --samples 1 --batch-size 4096 --allow-undefined --axis-index-e 4
179
194
  --axis-index-elx 5 --output tmp/memory.json`.
180
195
 
181
- **Cancellation.** Dropping the iterator is the supported way to cancel a `nc_to_rows`/`nc_to_batches` run — leaving a `for` loop early or `del`-ing it drops it immediately; relying on garbage collection works too but only whenever the runtime happens to collect it, so it is not prompt on all interpreters: the background interpreter thread's next attempt to send a row hits a closed channel and returns internally via `ParsingError::StreamClosed`, so the thread unwinds promptly instead of running the rest of the program to completion. No explicit `.close()`/`.cancel()` call is needed or provided — drop is the whole contract, and downstreams (e.g. ribweaver's `/sim` live-edit reinterpret) rely on it for latest-wins cancellation.
196
+ **Cancellation.** Dropping the iterator is the supported way to cancel a `nc_to_rows`/`nc_to_batches` run — leaving a `for` loop early or `del`-ing it drops it immediately; relying on garbage collection works too but only whenever the runtime happens to collect it, so it is not prompt on all interpreters: the background interpreter thread's next attempt to send a row hits a closed channel and returns internally via `ParsingError::StreamClosed`, so the thread unwinds promptly instead of running the rest of the program to completion. No explicit `.close()`/`.cancel()` call is needed or provided — drop is the whole contract, and downstreams (for example a live-edit re-interpret in an editor) rely on it for latest-wins cancellation.
182
197
 
183
198
  Additionally, conversion from a Polars DataFrame back to an MPF (NC) program is also supported:
184
199
 
@@ -202,7 +217,9 @@ Arguments:
202
217
  Options:
203
218
  -a, --axes <AXIS> Override default axis identifiers (comma-separated, e.g., "X,Y,Z")
204
219
  -e, --extra-axes <EXTRA_AXIS> Add extra axis identifiers (comma-separated, e.g., "RA1,RA2")
205
- -i, --initial_state <INITIAL_STATE> Optional initial_state file to initialize state
220
+ --definitions <FILE> Definition file (GUD/MGUD .DEF) to read before the program; repeat for several, read in order
221
+ --variables <FILE> JSON object of variable values applied after the definitions, e.g. {"COUNT": 3}
222
+ --start <FILE> JSON object with the machine state at program start (the shape of the returned state)
206
223
  -l, --iteration_limit <LIMIT> Maximum number of iterations for loops [default: 10000]
207
224
  -f, --disable-forward-fill Disable forward-filling of null values in axes columns
208
225
  -h, --help Print help