nc-gcode-interpreter 0.2.9__tar.gz → 0.2.10__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.
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/.github/workflows/build-and-release.yml +6 -6
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/CHANGELOG.md +35 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/Cargo.lock +1 -1
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/Cargo.toml +1 -1
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/PKG-INFO +1 -1
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/README.md +1 -1
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/docs/resumable-interpretation-design.md +1 -1
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_batches.py +17 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_stage1.py +17 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/flatten.rs +57 -35
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/interpret_rules.rs +3 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/interpreter.rs +1 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/lib.rs +13 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/line_driver.rs +35 -7
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/output.rs +270 -43
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/state.rs +15 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/uv.lock +6 -27
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/.github/workflows/benchmark.yml +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/.gitignore +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/CONTRIBUTING.md +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/Development.md +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/LICENSE +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/TODO.md +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/benches/fixtures.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/benches/workloads.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/build.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/build_backend/nc_build_backend.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/docs/sinumerik-execution-model.md +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/actual_position.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/actual_position.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/arc.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/arc.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/arrays.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/arrays.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/axis_index_assignment.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/axis_index_assignment.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/basic_math.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/basic_math.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/case.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/case.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/case_insensitive_variables.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/case_insensitive_variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/custom_vars.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/custom_vars.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/def_string.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/def_string.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/defaults.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/defaults.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/edge_cases.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/edge_cases.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/flattening/flatten_demo.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/flattening/flatten_demo_flattened.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/flattening/flatten_demo_raw.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/for_loop.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/for_loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/function_calls.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/function_calls.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/goto.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/goto.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/if_statement.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/if_statement.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/increment.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/increment.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/logic_operators.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/logic_operators.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/loop.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/multiple_m_codes.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/multiple_m_codes.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/precedence.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/precedence.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/r_param.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/r_param.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/repeat.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/repeat.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/rot.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/rot.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/simple.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/simple.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/spline.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/spline.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/tool.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/tool.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trans.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trans.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trans2.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trans2.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trans_ic.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trans_ic.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trans_reset.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trans_reset.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trig.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/trig.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/variables.csv +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/examples/variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/ggroups/generate_g_commands.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/pyproject.toml +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/example/minimal.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/example/streaming.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/__init__.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/_internal.pyi +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/cli.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/ggroups.json +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/py.typed +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/viz.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/nc_source.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_arithmetic_functions.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_batch_variables.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_compare_benchmarks.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_diagnostics.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_expected_output.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_field_defects.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_flags.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_flatten.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_frames.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_g_groups.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_g_vocabulary.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_jumps.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_real_comparisons.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_streaming.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_string_operations.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_structured_errors.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/python/tests/test_unsupported.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/rustfmt.toml +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/scripts/benchmark.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/scripts/compare_benchmarks.py +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/errors.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/frame.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/grammar.pest +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/line_driver/experiments.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/main.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/modal_groups.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/structure_scan.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/types.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/src/vm.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/tests/cli.rs +0 -0
- {nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/tests/performance_contract.rs +0 -0
{nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/.github/workflows/build-and-release.yml
RENAMED
|
@@ -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
|
|
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
|
-
#
|
|
160
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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,41 @@ 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.2.10] - 2026-11-10
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `nc_to_batches` and `nc_to_dataframe` do less work per output row. The
|
|
12
|
+
G-group columns are held as per-batch dictionary codes and written straight
|
|
13
|
+
to the Arrow string array, instead of one `String` per row per column that
|
|
14
|
+
the consumer thread then freed; the batch builder finds a cell's column
|
|
15
|
+
without hashing its name; the streaming driver reuses its decode buffers.
|
|
16
|
+
Python output is unchanged (same columns, `String` dtype, values and
|
|
17
|
+
`variable_events`). With `flatten_tolerance`, line numbers and variables on,
|
|
18
|
+
a 7.2M-row, 442 MiB linear program takes 3.3 s instead of 8.3 s and a
|
|
19
|
+
22M-row, 1.1 GB program 10.5 s instead of 24 s, at the same peak memory
|
|
20
|
+
(#89).
|
|
21
|
+
|
|
22
|
+
- `flatten_tolerance` costs almost nothing on rows that are not curves: the
|
|
23
|
+
flattener tracked positions with an allocation and a lookup per configured
|
|
24
|
+
axis for every row, about 2 s on the 7.2M-row program above, now about
|
|
25
|
+
0.15 s. B-spline flattening reuses one working buffer per curve; a
|
|
26
|
+
5.4M-row, 380 MB spline program takes 8 s instead of 16 s (#89).
|
|
27
|
+
|
|
28
|
+
- Rust API: `Column` has a new `Dict(DictColumn)` variant, used for the
|
|
29
|
+
G-group columns (`values` plus one `u32` code per row,
|
|
30
|
+
`DictColumn::NULL` for a null cell). Code that matches `Column`
|
|
31
|
+
exhaustively needs one more arm; `DictColumn::iter` yields the cells as
|
|
32
|
+
`Option<&str>` (#89).
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- A numeric tool selection (`T2`, `T=3`, `T=R1`) now reaches the output table.
|
|
37
|
+
It is written to the same string `T` column as `T="name"`, as the number's
|
|
38
|
+
text (`"2"`), on the row of the block that programs it. Before, only named
|
|
39
|
+
tools appeared and a numeric one was dropped without a warning. The value is
|
|
40
|
+
still stored as the variable `T` in the final state, as before (#101).
|
|
41
|
+
|
|
7
42
|
## [v0.2.9] - 2026-11-01
|
|
8
43
|
|
|
9
44
|
### Changed
|
|
@@ -178,7 +178,7 @@ Measure a local program with
|
|
|
178
178
|
--threads 4 --samples 1 --batch-size 4096 --allow-undefined --axis-index-e 4
|
|
179
179
|
--axis-index-elx 5 --output tmp/memory.json`.
|
|
180
180
|
|
|
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 (
|
|
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 (for example a live-edit re-interpret in an editor) rely on it for latest-wins cancellation.
|
|
182
182
|
|
|
183
183
|
Additionally, conversion from a Polars DataFrame back to an MPF (NC) program is also supported:
|
|
184
184
|
|
{nc_gcode_interpreter-0.2.9 → nc_gcode_interpreter-0.2.10}/docs/resumable-interpretation-design.md
RENAMED
|
@@ -15,7 +15,7 @@ Expose a resumable interpretation handle:
|
|
|
15
15
|
- `resume(token, patched_text)` — restart interpretation from a checkpoint,
|
|
16
16
|
against an edited program.
|
|
17
17
|
|
|
18
|
-
Consumer:
|
|
18
|
+
Consumer: a live-edit *incremental re-interpret* in a G-code editor, for multi-million-row
|
|
19
19
|
loop expansions. When the user edits source line *L*, it resumes from the last
|
|
20
20
|
checkpoint whose block cursor precedes *L* instead of re-running the whole program.
|
|
21
21
|
Downstream is evidence-gated: debounced-full re-interpret is fine below ~10⁵ rows,
|
|
@@ -208,3 +208,20 @@ def test_include_line_numbers_survives_flattening():
|
|
|
208
208
|
assert per_line.count(2) > 5
|
|
209
209
|
stream_lines = [line for line, *_ in nc_to_rows(program, flatten_tolerance=0.1)]
|
|
210
210
|
assert per_line == stream_lines
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_g_group_columns_stay_plain_strings():
|
|
214
|
+
# The interpreter holds G-group columns as dictionary codes internally;
|
|
215
|
+
# Python must keep seeing String columns with the same values.
|
|
216
|
+
program = "G1 G64 X1\nX2\nG4 F0.5\nG0 X3\nG17 X4\nM30\n"
|
|
217
|
+
frames = list(nc_to_batches(program, batch_size=2))
|
|
218
|
+
assert [f.height for f in frames] == [2, 2, 2]
|
|
219
|
+
for frame in frames:
|
|
220
|
+
for name in frame.columns:
|
|
221
|
+
if name.startswith("gg"):
|
|
222
|
+
assert frame[name].dtype == pl.String, name
|
|
223
|
+
df = pl.concat(frames, how="diagonal")
|
|
224
|
+
assert df["gg01_motion"].to_list() == ["G1", "G1", "G1", "G0", "G0", "G0"]
|
|
225
|
+
assert df["gg10_exact_stop_mode"].to_list() == ["G64"] * 6
|
|
226
|
+
assert df["gg02_wait"].to_list() == [None, None, "G4", None, None, None]
|
|
227
|
+
assert df["gg06_plane_select"].to_list() == [None, None, None, None, "G17", "G17"]
|
|
@@ -159,3 +159,20 @@ def test_fast_path_matches_on_real_corpus(mpf):
|
|
|
159
159
|
extra_axes=["ELX"],
|
|
160
160
|
allow_undefined_variables=True,
|
|
161
161
|
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.mark.parametrize(
|
|
165
|
+
"program, expected",
|
|
166
|
+
[
|
|
167
|
+
("G1 X0\nT2\nX1", [None, "2", None]),
|
|
168
|
+
("G1 X0\nT=3\nX1", [None, "3", None]),
|
|
169
|
+
("G1 X0\nt4\nX1", [None, "4", None]),
|
|
170
|
+
("R1=7\nG1 X0\nT=R1\nX1", [None, "7", None]),
|
|
171
|
+
('G1 X0\nT="NOZ_A"\nT5 X1', [None, "NOZ_A", "5"]),
|
|
172
|
+
],
|
|
173
|
+
ids=["T2", "T=3", "lowercase", "expression", "mixed with a named tool"],
|
|
174
|
+
)
|
|
175
|
+
def test_numeric_tool_selection_reaches_the_t_column(program, expected):
|
|
176
|
+
"""A numeric tool selection lands in the same string T column as a named one (#101)."""
|
|
177
|
+
df = run_both(program)
|
|
178
|
+
assert df["T"].to_list() == expected
|
|
@@ -67,9 +67,8 @@
|
|
|
67
67
|
//! than being misread).
|
|
68
68
|
|
|
69
69
|
use crate::errors::ParsingError;
|
|
70
|
-
use crate::output::{intern_column, CellMap, Row, FLATTENED_COLUMN};
|
|
70
|
+
use crate::output::{intern_column, key_eq, CellMap, Row, FLATTENED_COLUMN};
|
|
71
71
|
use crate::state::emit_warning;
|
|
72
|
-
use crate::state::FxMap;
|
|
73
72
|
use crate::types::Value;
|
|
74
73
|
use std::collections::HashMap;
|
|
75
74
|
|
|
@@ -136,8 +135,9 @@ pub struct Flattener {
|
|
|
136
135
|
/// Geometric axis columns (configured axis identifiers minus the
|
|
137
136
|
/// non-geometric value columns), interned.
|
|
138
137
|
geometric_axes: Vec<&'static str>,
|
|
139
|
-
/// Last known machine coordinate per geometric axis
|
|
140
|
-
|
|
138
|
+
/// Last known machine coordinate per geometric axis, indexed like
|
|
139
|
+
/// `geometric_axes` (a row updates a few slots, with no hashing).
|
|
140
|
+
positions: Vec<Option<f64>>,
|
|
141
141
|
motion: Motion,
|
|
142
142
|
plane: Plane,
|
|
143
143
|
/// Buffered blocks of the active spline, in program order. Non-empty
|
|
@@ -145,7 +145,7 @@ pub struct Flattener {
|
|
|
145
145
|
spline_buffer: Vec<SplineItem>,
|
|
146
146
|
/// Snapshot of `positions` taken when the spline was selected: the
|
|
147
147
|
/// spline's start point.
|
|
148
|
-
spline_start:
|
|
148
|
+
spline_start: Vec<Option<f64>>,
|
|
149
149
|
/// Current B-spline degree (`SD=`, modal within the spline).
|
|
150
150
|
spline_degree: usize,
|
|
151
151
|
/// Curve motion words already warned about (CIP, CT, POLY, ...): one
|
|
@@ -160,8 +160,8 @@ impl Flattener {
|
|
|
160
160
|
/// point even though the interpreter does.
|
|
161
161
|
pub fn seed_position(&mut self, axis: &str, machine_value: f64) {
|
|
162
162
|
let key = intern_column(&axis.to_uppercase());
|
|
163
|
-
if self.
|
|
164
|
-
self.positions
|
|
163
|
+
if let Some(slot) = self.axis_slot(key) {
|
|
164
|
+
self.positions[slot] = Some(machine_value);
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -171,7 +171,7 @@ impl Flattener {
|
|
|
171
171
|
message: format!("flatten tolerance must be a positive number, got {}", tolerance),
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
|
-
let geometric_axes = axis_identifiers
|
|
174
|
+
let geometric_axes: Vec<&'static str> = axis_identifiers
|
|
175
175
|
.iter()
|
|
176
176
|
.map(|a| a.to_uppercase())
|
|
177
177
|
.filter(|a| !NON_GEOMETRIC_AXES.contains(&a.as_str()))
|
|
@@ -179,12 +179,12 @@ impl Flattener {
|
|
|
179
179
|
.collect();
|
|
180
180
|
Ok(Flattener {
|
|
181
181
|
tolerance,
|
|
182
|
+
positions: vec![None; geometric_axes.len()],
|
|
182
183
|
geometric_axes,
|
|
183
|
-
positions: FxMap::default(),
|
|
184
184
|
motion: Motion::Linear,
|
|
185
185
|
plane: G17,
|
|
186
186
|
spline_buffer: Vec::new(),
|
|
187
|
-
spline_start:
|
|
187
|
+
spline_start: Vec::new(),
|
|
188
188
|
spline_degree: 3,
|
|
189
189
|
warned_motions: Vec::new(),
|
|
190
190
|
})
|
|
@@ -267,10 +267,23 @@ impl Flattener {
|
|
|
267
267
|
!self.spline_buffer.is_empty()
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
/// Index of `name` in `geometric_axes`.
|
|
271
|
+
fn axis_slot(&self, name: &str) -> Option<usize> {
|
|
272
|
+
self.geometric_axes.iter().position(|&axis| key_eq(axis, name))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/// Last known machine coordinate of a geometric axis.
|
|
276
|
+
fn position(&self, axis: &str) -> Option<f64> {
|
|
277
|
+
self.axis_slot(axis).and_then(|slot| self.positions[slot])
|
|
278
|
+
}
|
|
279
|
+
|
|
270
280
|
fn track_positions(&mut self, row: &Row) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
// Walk the row's few cells, not every configured axis.
|
|
282
|
+
for (&key, value) in row.cells.iter() {
|
|
283
|
+
if let Value::Float(v) = value {
|
|
284
|
+
if let Some(slot) = self.axis_slot(key) {
|
|
285
|
+
self.positions[slot] = Some(*v);
|
|
286
|
+
}
|
|
274
287
|
}
|
|
275
288
|
}
|
|
276
289
|
}
|
|
@@ -310,7 +323,7 @@ impl Flattener {
|
|
|
310
323
|
return;
|
|
311
324
|
}
|
|
312
325
|
|
|
313
|
-
let (Some(su), Some(sv)) = (self.
|
|
326
|
+
let (Some(su), Some(sv)) = (self.position(u_axis), self.position(v_axis)) else {
|
|
314
327
|
self.pass_through_with_warning(row, "arc start position is unknown", out);
|
|
315
328
|
return;
|
|
316
329
|
};
|
|
@@ -412,8 +425,8 @@ impl Flattener {
|
|
|
412
425
|
continue;
|
|
413
426
|
}
|
|
414
427
|
if let Some(end) = cell_float(&row, axis) {
|
|
415
|
-
let start = match self.
|
|
416
|
-
Some(
|
|
428
|
+
let start = match self.position(axis) {
|
|
429
|
+
Some(start) => start,
|
|
417
430
|
None => {
|
|
418
431
|
// First-ever position of this axis: there is nothing
|
|
419
432
|
// to ramp from, so the channel holds its end value
|
|
@@ -502,17 +515,19 @@ impl Flattener {
|
|
|
502
515
|
let mut current: Vec<f64> = channels
|
|
503
516
|
.iter()
|
|
504
517
|
.map(|&c| {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
518
|
+
self.axis_slot(c)
|
|
519
|
+
.and_then(|slot| start.get(slot).copied().flatten())
|
|
520
|
+
.unwrap_or_else(|| {
|
|
521
|
+
// Channel never positioned before the spline: start it at
|
|
522
|
+
// its first programmed value (a zero-length lead-in).
|
|
523
|
+
items
|
|
524
|
+
.iter()
|
|
525
|
+
.find_map(|item| match item {
|
|
526
|
+
SplineItem::Point(row) => cell_float(row, c),
|
|
527
|
+
_ => None,
|
|
528
|
+
})
|
|
529
|
+
.unwrap_or(0.0)
|
|
530
|
+
})
|
|
516
531
|
})
|
|
517
532
|
.collect();
|
|
518
533
|
points.push(current.clone());
|
|
@@ -957,6 +972,9 @@ struct BSpline {
|
|
|
957
972
|
knots: Vec<f64>,
|
|
958
973
|
ctrl: Vec<Vec<f64>>,
|
|
959
974
|
channels: usize,
|
|
975
|
+
/// de Boor working rows, `(p + 1) * (channels + 1)` values, reused by
|
|
976
|
+
/// every `eval` (the flattener evaluates several times per control point).
|
|
977
|
+
scratch: std::cell::RefCell<Vec<f64>>,
|
|
960
978
|
}
|
|
961
979
|
|
|
962
980
|
impl BSpline {
|
|
@@ -985,6 +1003,7 @@ impl BSpline {
|
|
|
985
1003
|
knots,
|
|
986
1004
|
ctrl,
|
|
987
1005
|
channels: points[0].len(),
|
|
1006
|
+
scratch: std::cell::RefCell::new(Vec::new()),
|
|
988
1007
|
}
|
|
989
1008
|
}
|
|
990
1009
|
|
|
@@ -999,7 +1018,13 @@ impl BSpline {
|
|
|
999
1018
|
} else {
|
|
1000
1019
|
p + t as usize
|
|
1001
1020
|
};
|
|
1002
|
-
|
|
1021
|
+
// Row j of `d` is control point j + k - p.
|
|
1022
|
+
let width = channels + 1;
|
|
1023
|
+
let mut d = self.scratch.borrow_mut();
|
|
1024
|
+
d.clear();
|
|
1025
|
+
for j in 0..=p {
|
|
1026
|
+
d.extend_from_slice(&self.ctrl[j + k - p]);
|
|
1027
|
+
}
|
|
1003
1028
|
for r in 1..=p {
|
|
1004
1029
|
for j in (r..=p).rev() {
|
|
1005
1030
|
let i = j + k - p;
|
|
@@ -1009,17 +1034,14 @@ impl BSpline {
|
|
|
1009
1034
|
} else {
|
|
1010
1035
|
(t - knots[i]) / denom
|
|
1011
1036
|
};
|
|
1012
|
-
// Indexing two rows of `d` at once: a plain indexed loop is
|
|
1013
|
-
// clearer than a split_at_mut dance.
|
|
1014
|
-
#[allow(clippy::needless_range_loop)]
|
|
1015
1037
|
for c in 0..=channels {
|
|
1016
|
-
let previous = d[j - 1
|
|
1017
|
-
d[j
|
|
1038
|
+
let previous = d[(j - 1) * width + c];
|
|
1039
|
+
d[j * width + c] = (1.0 - alpha) * previous + alpha * d[j * width + c];
|
|
1018
1040
|
}
|
|
1019
1041
|
}
|
|
1020
1042
|
}
|
|
1021
|
-
let w = d[p
|
|
1022
|
-
d[p
|
|
1043
|
+
let w = d[p * width + channels];
|
|
1044
|
+
d[p * width..p * width + channels].iter().map(|v| v / w).collect()
|
|
1023
1045
|
}
|
|
1024
1046
|
|
|
1025
1047
|
/// Greville abscissa of control point `i`, the mean of the `p` knots
|
|
@@ -2514,6 +2514,9 @@ fn interpret_statement(element: Pair<Rule>, output: &mut Output, state: &mut Sta
|
|
|
2514
2514
|
}
|
|
2515
2515
|
None => {
|
|
2516
2516
|
state.warn_unsupported_address(&key, line_no);
|
|
2517
|
+
if key.eq_ignore_ascii_case("T") {
|
|
2518
|
+
last.insert("T", Value::Str(crate::output::tool_number_text(local_value)));
|
|
2519
|
+
}
|
|
2517
2520
|
output.record_variable_change(&key, local_value);
|
|
2518
2521
|
}
|
|
2519
2522
|
}
|
|
@@ -1478,6 +1478,7 @@ mod tests {
|
|
|
1478
1478
|
let strs = |name: &str| -> Vec<Option<String>> {
|
|
1479
1479
|
match &table.columns.iter().find(|(n, _)| n == name).unwrap().1 {
|
|
1480
1480
|
Column::Str(v) => v.clone(),
|
|
1481
|
+
Column::Dict(v) => v.iter().map(|cell| cell.map(str::to_string)).collect(),
|
|
1481
1482
|
other => panic!("column {name} is not a str column: {other:?}"),
|
|
1482
1483
|
}
|
|
1483
1484
|
};
|
|
@@ -162,6 +162,19 @@ mod python_bindings {
|
|
|
162
162
|
// build of the offset + value buffers.
|
|
163
163
|
Arc::new(v.into_iter().collect::<StringArray>()),
|
|
164
164
|
),
|
|
165
|
+
Column::Dict(column) => {
|
|
166
|
+
// Same Utf8 array as a `Column::Str`, written from the codes.
|
|
167
|
+
let lengths: Vec<usize> = column.values.iter().map(String::len).collect();
|
|
168
|
+
let bytes: usize = column.codes.iter().filter_map(|&code| lengths.get(code as usize)).sum();
|
|
169
|
+
let mut builder = StringBuilder::with_capacity(column.codes.len(), bytes);
|
|
170
|
+
for &code in &column.codes {
|
|
171
|
+
match column.values.get(code as usize) {
|
|
172
|
+
Some(value) => builder.append_value(value),
|
|
173
|
+
None => builder.append_null(),
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
(DataType::Utf8, Arc::new(builder.finish()))
|
|
177
|
+
}
|
|
165
178
|
Column::StrList(v) => {
|
|
166
179
|
let values_cap: usize = v.iter().filter_map(|c| c.as_ref()).map(|l| l.len()).sum();
|
|
167
180
|
let mut builder = ListBuilder::with_capacity(StringBuilder::new(), v.len())
|
|
@@ -123,6 +123,14 @@ enum LineExec<'a, 'i> {
|
|
|
123
123
|
Blank,
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/// A numeric tool selection (`T2`, `T=3`) goes to the row's `T` column, like `T="name"`.
|
|
127
|
+
fn record_tool_number(key: &str, value: f64, output: &mut Output) {
|
|
128
|
+
if key == "T" {
|
|
129
|
+
let last = output.last_mut().expect("row was just pushed");
|
|
130
|
+
last.insert("T", Value::Str(crate::output::tool_number_text(value)));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
126
134
|
/// One type per name (mirrors `interpret_assignment`): assigning a number to
|
|
127
135
|
/// an existing STRING variable is a hard error, also on the fast path.
|
|
128
136
|
fn reject_string_variable(key: &str, state: &State, line_no: usize) -> Result<(), ParsingError> {
|
|
@@ -406,7 +414,10 @@ fn interpret_streaming(input: &str, output: &mut Output, state: &mut State) -> R
|
|
|
406
414
|
// for the decode the indexed driver runs in parallel.
|
|
407
415
|
std::thread::scope(|scope| {
|
|
408
416
|
let (sender, receiver) = std::sync::mpsc::sync_channel::<DecodedChunk>(2);
|
|
409
|
-
|
|
417
|
+
// Executed chunks go back to the decode thread, which refills their
|
|
418
|
+
// buffers instead of allocating and freeing three per chunk.
|
|
419
|
+
let (spent_sender, spent) = std::sync::mpsc::channel::<DecodedChunk>();
|
|
420
|
+
scope.spawn(move || decode_chunks(input, sender, spent));
|
|
410
421
|
// Returning early drops `receiver`, which stops the decode thread.
|
|
411
422
|
for chunk in receiver {
|
|
412
423
|
for (offset, (decoded, line)) in chunk.results.iter().zip(&chunk.lines).enumerate() {
|
|
@@ -415,6 +426,7 @@ fn interpret_streaming(input: &str, output: &mut Output, state: &mut State) -> R
|
|
|
415
426
|
return Ok(flow);
|
|
416
427
|
}
|
|
417
428
|
}
|
|
429
|
+
let _ = spent_sender.send(chunk);
|
|
418
430
|
}
|
|
419
431
|
Ok(BlockFlow::Continue)
|
|
420
432
|
})
|
|
@@ -432,16 +444,30 @@ struct DecodedChunk<'a> {
|
|
|
432
444
|
arena: Vec<Word<'a>>,
|
|
433
445
|
}
|
|
434
446
|
|
|
435
|
-
fn decode_chunks<'a>(
|
|
447
|
+
fn decode_chunks<'a>(
|
|
448
|
+
input: &'a str,
|
|
449
|
+
sender: std::sync::mpsc::SyncSender<DecodedChunk<'a>>,
|
|
450
|
+
spent: std::sync::mpsc::Receiver<DecodedChunk<'a>>,
|
|
451
|
+
) {
|
|
436
452
|
let mut lines = input.lines().peekable();
|
|
437
453
|
let mut first_line = 0;
|
|
438
454
|
while lines.peek().is_some() {
|
|
439
|
-
let mut chunk =
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
455
|
+
let mut chunk = match spent.try_recv() {
|
|
456
|
+
Ok(mut chunk) => {
|
|
457
|
+
chunk.lines.clear();
|
|
458
|
+
chunk.results.clear();
|
|
459
|
+
chunk.arena.clear();
|
|
460
|
+
chunk
|
|
461
|
+
}
|
|
462
|
+
Err(_) => DecodedChunk {
|
|
463
|
+
first_line: 0,
|
|
464
|
+
lines: Vec::with_capacity(STREAM_CHUNK_LINES),
|
|
465
|
+
results: Vec::with_capacity(STREAM_CHUNK_LINES),
|
|
466
|
+
arena: Vec::with_capacity(STREAM_CHUNK_LINES * 4),
|
|
467
|
+
},
|
|
444
468
|
};
|
|
469
|
+
chunk.first_line = first_line;
|
|
470
|
+
chunk.lines.extend(lines.by_ref().take(STREAM_CHUNK_LINES));
|
|
445
471
|
for (offset, line) in chunk.lines.iter().enumerate() {
|
|
446
472
|
chunk
|
|
447
473
|
.results
|
|
@@ -614,6 +640,7 @@ fn execute_decoded(
|
|
|
614
640
|
let key = key.to_uppercase();
|
|
615
641
|
reject_string_variable(&key, state, line.line_no)?;
|
|
616
642
|
output.record_variable_change(&key, *value);
|
|
643
|
+
record_tool_number(&key, *value, output);
|
|
617
644
|
state.symbol_table.insert(key, *value);
|
|
618
645
|
}
|
|
619
646
|
}
|
|
@@ -701,6 +728,7 @@ fn execute_decoded(
|
|
|
701
728
|
reject_string_variable(&key, state, line.line_no)?;
|
|
702
729
|
let local_value = increment_local(state, &key, value);
|
|
703
730
|
output.record_variable_change(&key, local_value);
|
|
731
|
+
record_tool_number(&key, local_value, output);
|
|
704
732
|
state.symbol_table.insert(key, local_value);
|
|
705
733
|
}
|
|
706
734
|
}
|