nc-gcode-interpreter 0.1.12__tar.gz → 0.2.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 (133) hide show
  1. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/.github/workflows/build-and-release.yml +45 -17
  2. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/.gitignore +16 -0
  3. nc_gcode_interpreter-0.2.0/CHANGELOG.md +219 -0
  4. nc_gcode_interpreter-0.2.0/Cargo.lock +1018 -0
  5. nc_gcode_interpreter-0.2.0/Cargo.toml +49 -0
  6. nc_gcode_interpreter-0.2.0/PKG-INFO +27 -0
  7. nc_gcode_interpreter-0.1.12/PKG-INFO → nc_gcode_interpreter-0.2.0/README.md +39 -30
  8. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/TODO.md +2 -2
  9. nc_gcode_interpreter-0.2.0/docs/sinumerik-execution-model.md +302 -0
  10. nc_gcode_interpreter-0.2.0/examples/actual_position.csv +20 -0
  11. nc_gcode_interpreter-0.2.0/examples/actual_position.mpf +17 -0
  12. nc_gcode_interpreter-0.2.0/examples/arc.csv +14 -0
  13. nc_gcode_interpreter-0.2.0/examples/arc.mpf +14 -0
  14. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/arrays.csv +12 -12
  15. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/basic_math.csv +2 -0
  16. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/basic_math.mpf +3 -1
  17. nc_gcode_interpreter-0.2.0/examples/case.csv +6 -0
  18. nc_gcode_interpreter-0.2.0/examples/case.mpf +11 -0
  19. nc_gcode_interpreter-0.2.0/examples/case_insensitive_variables.csv +10 -0
  20. nc_gcode_interpreter-0.2.0/examples/case_insensitive_variables.mpf +13 -0
  21. nc_gcode_interpreter-0.2.0/examples/def_string.csv +8 -0
  22. nc_gcode_interpreter-0.2.0/examples/def_string.mpf +14 -0
  23. nc_gcode_interpreter-0.2.0/examples/edge_cases.csv +46 -0
  24. nc_gcode_interpreter-0.2.0/examples/edge_cases.mpf +73 -0
  25. nc_gcode_interpreter-0.2.0/examples/flattening/flatten_demo.mpf +14 -0
  26. nc_gcode_interpreter-0.2.0/examples/flattening/flatten_demo_flattened.csv +55 -0
  27. nc_gcode_interpreter-0.2.0/examples/flattening/flatten_demo_raw.csv +15 -0
  28. nc_gcode_interpreter-0.2.0/examples/goto.csv +18 -0
  29. nc_gcode_interpreter-0.2.0/examples/goto.mpf +15 -0
  30. nc_gcode_interpreter-0.2.0/examples/logic_operators.csv +13 -0
  31. nc_gcode_interpreter-0.2.0/examples/logic_operators.mpf +29 -0
  32. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/multiple_m_codes.csv +0 -1
  33. nc_gcode_interpreter-0.2.0/examples/precedence.csv +13 -0
  34. nc_gcode_interpreter-0.2.0/examples/precedence.mpf +12 -0
  35. nc_gcode_interpreter-0.2.0/examples/spline.csv +19 -0
  36. nc_gcode_interpreter-0.2.0/examples/spline.mpf +22 -0
  37. nc_gcode_interpreter-0.2.0/examples/trans2.csv +5 -0
  38. nc_gcode_interpreter-0.2.0/examples/trans2.mpf +5 -0
  39. nc_gcode_interpreter-0.2.0/examples/trans_ic.csv +14 -0
  40. nc_gcode_interpreter-0.2.0/examples/trans_ic.mpf +21 -0
  41. nc_gcode_interpreter-0.2.0/examples/trans_reset.csv +11 -0
  42. nc_gcode_interpreter-0.2.0/examples/trans_reset.mpf +15 -0
  43. nc_gcode_interpreter-0.2.0/examples/trig.csv +11 -0
  44. nc_gcode_interpreter-0.2.0/examples/trig.mpf +10 -0
  45. nc_gcode_interpreter-0.2.0/ggroups/generate_g_commands.py +51 -0
  46. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/pyproject.toml +17 -2
  47. nc_gcode_interpreter-0.2.0/python/example/streaming.py +31 -0
  48. nc_gcode_interpreter-0.2.0/python/nc_gcode_interpreter/__init__.py +530 -0
  49. nc_gcode_interpreter-0.2.0/python/nc_gcode_interpreter/_internal.pyi +40 -0
  50. nc_gcode_interpreter-0.2.0/python/nc_gcode_interpreter/cli.py +187 -0
  51. nc_gcode_interpreter-0.2.0/python/nc_gcode_interpreter/viz.py +343 -0
  52. nc_gcode_interpreter-0.2.0/python/tests/test_arithmetic_functions.py +35 -0
  53. nc_gcode_interpreter-0.2.0/python/tests/test_batches.py +157 -0
  54. nc_gcode_interpreter-0.2.0/python/tests/test_diagnostics.py +111 -0
  55. nc_gcode_interpreter-0.2.0/python/tests/test_field_defects.py +121 -0
  56. nc_gcode_interpreter-0.2.0/python/tests/test_flatten.py +378 -0
  57. nc_gcode_interpreter-0.2.0/python/tests/test_g_vocabulary.py +60 -0
  58. nc_gcode_interpreter-0.2.0/python/tests/test_jumps.py +202 -0
  59. nc_gcode_interpreter-0.2.0/python/tests/test_real_comparisons.py +57 -0
  60. nc_gcode_interpreter-0.2.0/python/tests/test_stage1.py +144 -0
  61. nc_gcode_interpreter-0.2.0/python/tests/test_streaming.py +227 -0
  62. nc_gcode_interpreter-0.2.0/python/tests/test_unsupported.py +110 -0
  63. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/src/errors.rs +62 -0
  64. nc_gcode_interpreter-0.2.0/src/flatten.rs +1637 -0
  65. nc_gcode_interpreter-0.2.0/src/grammar.pest +238 -0
  66. nc_gcode_interpreter-0.2.0/src/interpret_rules.rs +2041 -0
  67. nc_gcode_interpreter-0.2.0/src/interpreter.rs +897 -0
  68. nc_gcode_interpreter-0.2.0/src/lib.rs +615 -0
  69. nc_gcode_interpreter-0.2.0/src/line_driver.rs +1247 -0
  70. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/src/main.rs +20 -6
  71. nc_gcode_interpreter-0.2.0/src/modal_groups.rs +137 -0
  72. nc_gcode_interpreter-0.2.0/src/output.rs +778 -0
  73. nc_gcode_interpreter-0.2.0/src/state.rs +305 -0
  74. nc_gcode_interpreter-0.2.0/src/structure_scan.rs +242 -0
  75. nc_gcode_interpreter-0.2.0/src/types.rs +14 -0
  76. nc_gcode_interpreter-0.2.0/tests/cli.rs +80 -0
  77. nc_gcode_interpreter-0.2.0/uv.lock +1970 -0
  78. nc_gcode_interpreter-0.1.12/Cargo.lock +0 -3754
  79. nc_gcode_interpreter-0.1.12/Cargo.toml +0 -20
  80. nc_gcode_interpreter-0.1.12/README.md +0 -164
  81. nc_gcode_interpreter-0.1.12/ggroups/generate_modal_ggroups.py +0 -29
  82. nc_gcode_interpreter-0.1.12/ggroups/generate_pest.py +0 -57
  83. nc_gcode_interpreter-0.1.12/ggroups/ggroups.pest +0 -66
  84. nc_gcode_interpreter-0.1.12/python/nc_gcode_interpreter/__init__.py +0 -268
  85. nc_gcode_interpreter-0.1.12/python/nc_gcode_interpreter/_internal.pyi +0 -68
  86. nc_gcode_interpreter-0.1.12/src/grammar.pest +0 -204
  87. nc_gcode_interpreter-0.1.12/src/interpret_rules.rs +0 -1037
  88. nc_gcode_interpreter-0.1.12/src/interpreter.rs +0 -280
  89. nc_gcode_interpreter-0.1.12/src/lib.rs +0 -69
  90. nc_gcode_interpreter-0.1.12/src/modal_groups.rs +0 -65
  91. nc_gcode_interpreter-0.1.12/src/state.rs +0 -149
  92. nc_gcode_interpreter-0.1.12/src/types.rs +0 -25
  93. nc_gcode_interpreter-0.1.12/uv.lock +0 -1568
  94. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/CONTRIBUTING.md +0 -0
  95. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/Development.md +0 -0
  96. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/LICENSE +0 -0
  97. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/arrays.mpf +0 -0
  98. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/axis_index_assignment.csv +0 -0
  99. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/axis_index_assignment.mpf +0 -0
  100. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/custom_vars.csv +0 -0
  101. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/custom_vars.mpf +0 -0
  102. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/defaults.csv +0 -0
  103. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/defaults.mpf +0 -0
  104. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/for_loop.csv +0 -0
  105. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/for_loop.mpf +0 -0
  106. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/function_calls.csv +0 -0
  107. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/function_calls.mpf +0 -0
  108. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/if_statement.csv +0 -0
  109. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/if_statement.mpf +0 -0
  110. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/increment.csv +0 -0
  111. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/increment.mpf +0 -0
  112. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/loop.csv +0 -0
  113. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/loop.mpf +0 -0
  114. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/multiple_m_codes.mpf +0 -0
  115. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/r_param.csv +0 -0
  116. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/r_param.mpf +0 -0
  117. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/repeat.csv +0 -0
  118. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/repeat.mpf +0 -0
  119. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/simple.csv +0 -0
  120. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/simple.mpf +0 -0
  121. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/tool.csv +0 -0
  122. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/tool.mpf +0 -0
  123. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/trans.csv +0 -0
  124. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/trans.mpf +0 -0
  125. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/variables.csv +0 -0
  126. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/examples/variables.mpf +0 -0
  127. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/python/example/minimal.py +0 -0
  128. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/python/nc_gcode_interpreter/ggroups.json +0 -0
  129. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/python/nc_gcode_interpreter/py.typed +0 -0
  130. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/python/tests/test_expected_output.py +0 -0
  131. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/python/tests/test_flags.py +0 -0
  132. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/python/tests/test_g_groups.py +0 -0
  133. {nc_gcode_interpreter-0.1.12 → nc_gcode_interpreter-0.2.0}/rustfmt.toml +0 -0
@@ -30,8 +30,8 @@ jobs:
30
30
  lint:
31
31
  runs-on: ubuntu-latest
32
32
  steps:
33
- - uses: actions/checkout@v4
34
- - uses: actions/setup-python@v5
33
+ - uses: actions/checkout@v5
34
+ - uses: actions/setup-python@v6
35
35
  with:
36
36
  python-version: 3.13
37
37
  - name: Install Python linter
@@ -39,6 +39,14 @@ jobs:
39
39
  - name: Lint Python code with ruff
40
40
  run: ruff check .
41
41
 
42
+ rust-test:
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v5
46
+ - uses: Swatinem/rust-cache@v2
47
+ - name: Run Rust unit and integration tests
48
+ run: cargo test
49
+
42
50
  linux-x64:
43
51
  needs: [lint]
44
52
  runs-on: ${{ matrix.platform.runner }}
@@ -49,8 +57,8 @@ jobs:
49
57
  target: x86_64
50
58
  python-version: ["3.12", "3.13"]
51
59
  steps:
52
- - uses: actions/checkout@v4
53
- - uses: actions/setup-python@v5
60
+ - uses: actions/checkout@v5
61
+ - uses: actions/setup-python@v6
54
62
  with:
55
63
  python-version: ${{ matrix.python-version }}
56
64
  - name: Update Cargo.toml Version
@@ -79,12 +87,12 @@ jobs:
79
87
  matrix:
80
88
  python-version: ["3.12", "3.13"]
81
89
  steps:
82
- - uses: actions/checkout@v4
90
+ - uses: actions/checkout@v5
83
91
  - uses: actions/download-artifact@v4
84
92
  with:
85
93
  name: wheels-ubuntu-latest-x86_64-py${{ matrix.python-version }}-${{ github.sha }}
86
94
  path: dist
87
- - uses: actions/setup-python@v5
95
+ - uses: actions/setup-python@v6
88
96
  with:
89
97
  python-version: ${{ matrix.python-version }}
90
98
  - name: Install py_opw_kinematics
@@ -120,8 +128,8 @@ jobs:
120
128
  target: aarch64
121
129
  python-version: ["3.12", "3.13"]
122
130
  steps:
123
- - uses: actions/checkout@v4
124
- - uses: actions/setup-python@v5
131
+ - uses: actions/checkout@v5
132
+ - uses: actions/setup-python@v6
125
133
  with:
126
134
  python-version: ${{ matrix.python-version }}
127
135
  - name: Update Cargo.toml Version
@@ -159,8 +167,8 @@ jobs:
159
167
  target: x64
160
168
  python-version: ["3.12", "3.13"]
161
169
  steps:
162
- - uses: actions/checkout@v4
163
- - uses: actions/setup-python@v5
170
+ - uses: actions/checkout@v5
171
+ - uses: actions/setup-python@v6
164
172
  with:
165
173
  python-version: ${{ matrix.python-version }}
166
174
  architecture: ${{ matrix.platform.target }}
@@ -218,8 +226,8 @@ jobs:
218
226
  target: aarch64
219
227
  python-version: ["3.12", "3.13"]
220
228
  steps:
221
- - uses: actions/checkout@v4
222
- - uses: actions/setup-python@v5
229
+ - uses: actions/checkout@v5
230
+ - uses: actions/setup-python@v6
223
231
  with:
224
232
  python-version: ${{ matrix.python-version }}
225
233
  - name: Update Cargo.toml Version
@@ -250,8 +258,8 @@ jobs:
250
258
  needs: [lint, test]
251
259
  runs-on: ubuntu-latest
252
260
  steps:
253
- - uses: actions/checkout@v4
254
- - uses: actions/setup-python@v5
261
+ - uses: actions/checkout@v5
262
+ - uses: actions/setup-python@v6
255
263
  with:
256
264
  python-version: 3.12
257
265
  - name: Update Cargo.toml Version
@@ -286,13 +294,33 @@ jobs:
286
294
  contents: write
287
295
  attestations: write
288
296
  steps:
297
+ # Do NOT use `merge-multiple: true`. With `--find-interpreter`, every
298
+ # build matrix leg (py3.12 AND py3.13) produces cp312/cp313/cp314 wheels
299
+ # with identical filenames but non-identical bytes (sccache makes builds
300
+ # nondeterministic). Merging them into one directory makes
301
+ # download-artifact overwrite colliding paths concurrently, truncating
302
+ # the file mid-write and corrupting the ZIP central directory - which
303
+ # PyPI reports as "Trailing data" and `wheel unpack` as "Bad magic
304
+ # number for central directory". Instead download each artifact into its
305
+ # own subdirectory and collect one intact copy of each filename.
289
306
  - uses: actions/download-artifact@v4
290
307
  with:
291
- merge-multiple: true
292
- path: dist
293
- - uses: actions/setup-python@v5
308
+ path: artifacts
309
+ - uses: actions/setup-python@v6
294
310
  with:
295
311
  python-version: 3.12
312
+ - name: Collect unique wheels and sdist
313
+ run: |
314
+ mkdir -p dist
315
+ # Each artifact is in its own subdir, so no path collides during
316
+ # download. Copy the first occurrence of each basename; duplicate
317
+ # filenames are the same wheel for the same platform tag, so either
318
+ # copy is a valid, complete ZIP.
319
+ find artifacts -type f \( -name '*.whl' -o -name '*.tar.gz' \) | while read -r f; do
320
+ b=$(basename "$f")
321
+ [ -e "dist/$b" ] || cp "$f" "dist/$b"
322
+ done
323
+ echo "Collected artifacts:" && ls -1 dist/
296
324
  - name: Generate artifact attestation
297
325
  uses: actions/attest-build-provenance@v1
298
326
  with:
@@ -26,3 +26,19 @@ dist/
26
26
  __pycache__
27
27
  python/**/*.so
28
28
  python/**/*.pyc
29
+
30
+ # macOS
31
+ .DS_Store
32
+
33
+ # local scratch
34
+ tmp/
35
+
36
+ # real-world NC files used for manual/local testing, not for CI
37
+ test-data/
38
+
39
+ # local exploration scripts
40
+ python/example/plot_trace.py
41
+ python/example/smooth_e.py
42
+
43
+ # local field-test data (customer programs; never commit)
44
+ test-data/
@@ -0,0 +1,219 @@
1
+ # Changelog
2
+
3
+ Notable changes to **nc-gcode-interpreter**. The format loosely follows
4
+ [Keep a Changelog](https://keepachangelog.com/); versions are git tags,
5
+ released to PyPI.
6
+
7
+ ## [v0.2.0] - 2026-07-07
8
+
9
+ ### Changed
10
+
11
+ - Rust-side polars is gone. The Table -> Python DataFrame handoff no longer
12
+ builds a polars DataFrame in Rust (via `pyo3-polars`); it builds an Arrow
13
+ record batch with the minimal `arrow-array`/`arrow-schema`/`arrow-data`
14
+ crates and hands it to Python zero-copy through the Arrow PyCapsule
15
+ interface (`__arrow_c_array__`), where `pl.DataFrame(...)` wraps it. The
16
+ Python API is unchanged (still returns `polars.DataFrame`), needs no
17
+ `pyarrow`, and performance is unchanged. This drops ~60 crates from the
18
+ `python`-feature build (127 -> 64), cutting a clean release build ~4x
19
+ (83s -> 21s), and bumps PyO3 0.28 -> 0.29 (resolving the RUSTSEC pyo3
20
+ advisories).
21
+
22
+ ### Added
23
+
24
+ - `dwell` output column: F/S on a `G4` block is the dwell time (seconds /
25
+ spindle revolutions), a per-block parameter - it now lands in its own
26
+ never-forward-filled `dwell` column instead of polluting the modal F/S
27
+ columns (previously `G4 F0.01` set the feed to 0.01 mm/min for every
28
+ following block until the next real F word)
29
+ - Loud warnings for known-but-uninterpreted constructs (never silently
30
+ butcher a statement): assignments to `AR`/`AP`/`RP` (opening-angle and
31
+ polar arc forms) warn once per run that the motion will be wrong; `G91`
32
+ warns once that incremental dimensioning is not applied; the flattener
33
+ warns per word for CIP/CT/POLY/thread/involute pass-through
34
+ - `TURN` output column (block address, never forward-filled): additional
35
+ full helix turns on G2/G3 blocks; previously swallowed as a user variable
36
+ - Curve flattening: `flatten_tolerance` on `nc_to_dataframe` / `nc_to_rows` /
37
+ `nc_to_batches` (CLI: `--flatten-tolerance`) converts G2/G3 arcs (I/J/K and
38
+ CR= forms, all planes, helical, full circles) and ASPLINE/BSPLINE/CSPLINE
39
+ splines (PW weights, SD degree) into runs of G1 rows within a single
40
+ max-deviation tolerance of the true curve; interpolation addresses are
41
+ consumed, source line numbers and auxiliary cells preserved; generated
42
+ samples carry a `flattened = 1` marker column so the original programmed
43
+ points remain distinguishable
44
+ - Optional `viz` extra (threejs-viewer >= 0.0.41):
45
+ `nc_gcode_interpreter.viz.view_toolpath(df)` shows a toolpath in
46
+ threejs-viewer as an animated bead tube (feed-rate float64 time base,
47
+ programmed vs flattened point coloring), and the `nc-view` console command
48
+ interprets + flattens + animates an .mpf in one step, with a nozzle
49
+ marker riding the path tip, camera follow/look-at tracking, and travel
50
+ moves drawn natively as a thin line in lockstep with the bead
51
+
52
+ - Program jumps and branches: `GOTOF`/`GOTOB`/`GOTO`/`GOTOC`/`GOTOS` and
53
+ `CASE ... OF ... DEFAULT`, with per-scope label/block-number resolution,
54
+ jumps out of IF bodies and loops, and alarm-14080 semantics for an
55
+ unresolved target (`GOTOC` warns and continues) (#29)
56
+ - Streaming API `nc_to_rows(program)`: yields `(line_no, row)` lazily while
57
+ the interpreter runs on a background thread — batch-identical typing and
58
+ forward-fill, constant memory, early abort by dropping the iterator,
59
+ errors raised at the offending row, final state on the exhausted
60
+ iterator (#35)
61
+ - `nc_to_rows(..., include_variables=True)` yields `(line_no, row,
62
+ variables)` with per-block variable-assignment deltas, exposing
63
+ variable-only blocks that the batch DataFrame prunes (#35)
64
+ - Spline programming: `PW`/`SD`/`PL` block addresses become output columns
65
+ (not forward-filled, no `TRANS` offset) instead of being silently
66
+ swallowed (#18)
67
+ - G2/G3 arc interpolation parameters `I`/`J`/`K`/`CR` become per-block output
68
+ columns (not forward-filled, no `TRANS` offset) instead of being silently
69
+ dropped — arcs previously came out as straight-line endpoints (#37)
70
+ - `nc_to_batches(program, batch_size=...)`: interpret a program into a
71
+ stream of columnar polars DataFrames built on a worker thread and handed
72
+ over via the Arrow C data interface — bounded memory for programs too
73
+ large to fit in one DataFrame (#37)
74
+ - Parsing: leading-underscore identifiers (`_WITH_M0`), assignment to
75
+ `$AC_*` system variables (`$AC_TIMER[1] = 0`), and the `NOT` logical
76
+ operator (#37)
77
+ - `DEF STRING[n]` string-variable declarations and quoted-string
78
+ assignments; strings stay out of the numeric pipeline (a string in an
79
+ expression, or a type mismatch, is a hard error, never a silent 0.0).
80
+ String *processing* (`SPRINT`/`INDEX`/`<<`) remains out of scope and
81
+ fails loudly (#40)
82
+ - Logic, comparison and bit operators in expressions (`AND`/`OR`/`XOR`,
83
+ `B_AND`/`B_OR`/`B_XOR`, `==`/`<>`/`<`/`>`/`<=`/`>=`) at the manual's
84
+ priorities, so conditions like `IF (A == 1 AND B == 1)` work and
85
+ comparison results are assignable (`R11 = R10 >= 100`) (#41)
86
+ - `$AA_IW[<axis>]` / `$AA_IM[<axis>]` read the interpreted actual work /
87
+ machine position of an axis, so layer loops
88
+ (`REPEAT ... UNTIL $AA_IW[Z] > H`) terminate; reading before the axis is
89
+ positioned errors loudly and the variables are read-only (#42)
90
+ - `nc-view` prints a corrected retry command on the classic new-machine
91
+ failures (missing `--axis-index-map`, undefined machine-parameter
92
+ variables) instead of a bare traceback
93
+ - `docs/sinumerik-execution-model.md`: how a real control executes NC code
94
+ versus this interpreter, and why (#30)
95
+
96
+ ### Changed
97
+
98
+ - **Breaking:** `I`/`J`/`K`/`CR` are now treated as arc interpolation-parameter
99
+ block addresses (output columns), so they can no longer be used as user
100
+ variable names: `I=5` followed by `X=I+1` was a variable read before and is
101
+ now an undefined-variable error. Matches Sinumerik address semantics (#37)
102
+ - `nc_to_dataframe` is now the concatenation of the internal batch stream
103
+ rather than collecting every row up front: same output, but bounded
104
+ intermediate memory and interpretation overlapped with DataFrame assembly
105
+ (a 1.1 GB program went from ~209 s / 6 GB to ~33 s / 3.3 GB) (#37)
106
+ - An executed `M2`/`M17`/`M30` now ends the program immediately, even
107
+ mid-file, instead of being ignored (#29)
108
+ - Unknown G codes now error like Sinumerik alarm 12470 instead of silently
109
+ parsing as a subprogram call (#31)
110
+ - Unsupported or unmodeled statements (parameterized `ROT`/`SCALE`/`MIRROR`
111
+ frames, and previously jumps/`LOOP` before #29 implemented them) raise a
112
+ clear `UnsupportedStatement` error instead of corrupting output; a frame
113
+ instruction following another statement in the same block errors (#20,
114
+ #32)
115
+ - Frame semantics per the manual: absolute frame instructions (`TRANS`,
116
+ bare `ROT`/`SCALE`/`MIRROR`) substitute — deleting previously programmed
117
+ offsets — rather than accumulate (#20, #26)
118
+ - Values are f64 end-to-end to match the control's 64-bit `REAL`:
119
+ coordinates are bit-exact for decimal literals, `DIV` truncates the real
120
+ result (`7 DIV 4.1 = 1`), and `==`/`<>`/`<=`/`>=` use the manual's 1e-12
121
+ relative tolerance (#23)
122
+ - The ~488-command G vocabulary and its 60 G-groups moved out of the PEG
123
+ grammar into a Rust table generated from `ggroups.json`; the grammar
124
+ recognizes lexical shape only (#32)
125
+ - Rust-side polars removed: the core returns a plain typed table and the
126
+ Python wrapper builds the DataFrame, so any Python polars version works;
127
+ CSV and DataFrame output are unchanged (#27)
128
+ - Dependency refresh: pest 2.8.6, pyo3 0.28, clap 4.6, polars 1.42.1 (#28)
129
+
130
+ ### Fixed
131
+
132
+ - The NC language is case-insensitive (manual 3.3.2): lowercase axis
133
+ words (`g1 x0 y0`) are axis moves rather than silently-dropped subprogram
134
+ calls, G/M values are normalized to uppercase, and user-variable
135
+ identifiers fold case (a program that declares `lAYER_HEIGHT` but assigns
136
+ `LAYER_HEIGHT` is one variable, not two) (#43)
137
+ - The IC-before-position warning now states what actually happens ("axis
138
+ incremented with `IC()` before any absolute position was set; assuming it
139
+ starts at 0") instead of the vague "behavior may be indeterminate"
140
+ - Expression evaluation: correct operator precedence (`2+3*4` is now 14,
141
+ not 20), degree-based trig per the manual, corrected `ATAN2` argument
142
+ order, and `DIV` by zero errors instead of panicking (#19)
143
+ - `TRANS`/`ATRANS` translation is applied per row at output time under the
144
+ frame active at that block; fixes double-applied offsets on `IC()` moves
145
+ and axis movement as a parsing side effect (#26)
146
+ - Unclosed or crossed `IF`/`WHILE`/`FOR`/`LOOP`/`REPEAT` structures are
147
+ reported at the (innermost) opener's own line instead of as a parse
148
+ failure at end-of-file (#33)
149
+ - Parse errors are phrased for humans (no grammar-internal rule names),
150
+ mistyped axis words like `Y2O` warn, and unresolved jump targets get
151
+ did-you-mean suggestions — direction-aware when the label exists but only
152
+ in the other search direction (#31)
153
+
154
+ ### Performance
155
+
156
+ - Stage-1 line triage: structure-free CAM programs run through a byte
157
+ decoder for the >99.9% trivial lines, per-line parses for the rest —
158
+ a 319k-line program drops from ~33 s to ~9 s in `nc_to_dataframe`
159
+ (`NC_STAGE1=0` disables) (#34)
160
+ - Grammar restructuring: ~15% faster parsing, plus an ignored 1M-line
161
+ benchmark harness (#30); an earlier redundant-lookahead removal cut parse
162
+ time ~27% (#25)
163
+
164
+ ## [v0.1.12] - 2025-06-26
165
+
166
+ - Added `allow_undefined_variables`: undefined variables initialize to 0.0
167
+ with a warning instead of erroring (#17)
168
+ - Dependency updates
169
+
170
+ ## [v0.1.11] - 2025-06-25
171
+
172
+ - Added arithmetic functions and `REPEAT ... UNTIL` (#15)
173
+ - Added `axis_index_map` to map axis identifiers to array indices (e.g.
174
+ `FL[E]=10`)
175
+ - Improved error messages and grammar edge cases (comments in `IF`
176
+ statements, relational-operator ordering)
177
+
178
+ ## [v0.1.10] - 2025-02-21
179
+
180
+ - Updated supported Python versions and release versioning; no interpreter
181
+ changes
182
+
183
+ ## [v0.1.9] - 2024-10-25
184
+
185
+ - Added `dataframe_to_nc`: write a polars DataFrame back out as NC G-code
186
+ (round-trip), with the `sanitize_dataframe` helper exposed (#13)
187
+
188
+ ## [v0.1.8] - 2024-10-22
189
+
190
+ - Exposed G-code groups (ggroups) in the API (#12); added type hints (#11)
191
+
192
+ ## [v0.1.7] - 2024-10-17
193
+
194
+ - Git-tag-based versioning; CI cleanup (minimum Python 3.11)
195
+
196
+ ## [v0.1.6] - 2024-10-16
197
+
198
+ - Implemented tool selection (`T="..."`/`T5`); M and T matching is
199
+ case-insensitive; quoted strings are unquoted in output (#8)
200
+
201
+ ## [v0.1.5] - 2024-10-09
202
+
203
+ - Reordered G-group parsing, roughly doubling parse performance (#7)
204
+
205
+ ## [v0.1.1] – [v0.1.4] - 2024-10-03
206
+
207
+ - Packaging and CI: PyPI publishing via Trusted Publisher, contributing
208
+ guidelines, macOS builds, WASM and binary bindings disabled to fix the
209
+ Python package (#1–#3)
210
+
211
+ ## [v0.1] - 2024-09-26
212
+
213
+ - Initial release: Rust (pest) interpreter for Sinumerik-flavored NC
214
+ G-code with a CLI (MPF → CSV) and Python bindings (MPF → polars
215
+ DataFrame + final state)
216
+ - G-code groups and modal commands, `TRANS`/`ATRANS`, `WHILE`/`FOR`/
217
+ `IF`/`ELSE`, local variables and arrays, arithmetic, incremental moves
218
+ (`IC()`), custom/extra axes, initial-state file, iteration limit,
219
+ forward-fill toggle