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