nc-gcode-interpreter 0.2.7__tar.gz → 0.2.9__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/.github/workflows/benchmark.yml +41 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/.github/workflows/build-and-release.yml +4 -2
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/CHANGELOG.md +188 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/Cargo.lock +1 -1
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/Cargo.toml +7 -1
- nc_gcode_interpreter-0.2.9/Development.md +146 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/PKG-INFO +1 -1
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/README.md +19 -2
- nc_gcode_interpreter-0.2.9/benches/fixtures.rs +50 -0
- nc_gcode_interpreter-0.2.9/benches/workloads.rs +194 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/flattening/flatten_demo_flattened.csv +0 -1
- nc_gcode_interpreter-0.2.9/examples/rot.csv +17 -0
- nc_gcode_interpreter-0.2.9/examples/rot.mpf +18 -0
- nc_gcode_interpreter-0.2.9/examples/trans2.csv +5 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/__init__.py +3 -2
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/_internal.pyi +1 -1
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_batch_variables.py +1 -1
- nc_gcode_interpreter-0.2.9/python/tests/test_compare_benchmarks.py +156 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_diagnostics.py +64 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_flatten.py +36 -0
- nc_gcode_interpreter-0.2.9/python/tests/test_frames.py +419 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_stage1.py +32 -15
- nc_gcode_interpreter-0.2.9/python/tests/test_string_operations.py +169 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_structured_errors.py +19 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_unsupported.py +3 -2
- nc_gcode_interpreter-0.2.9/scripts/benchmark.py +339 -0
- nc_gcode_interpreter-0.2.9/scripts/compare_benchmarks.py +134 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/errors.rs +6 -3
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/flatten.rs +235 -73
- nc_gcode_interpreter-0.2.9/src/frame.rs +187 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/grammar.pest +46 -32
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/interpret_rules.rs +194 -39
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/interpreter.rs +333 -23
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/lib.rs +36 -25
- nc_gcode_interpreter-0.2.9/src/line_driver/experiments.rs +243 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/line_driver.rs +739 -160
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/main.rs +2 -1
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/output.rs +109 -22
- nc_gcode_interpreter-0.2.9/src/state.rs +704 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/structure_scan.rs +204 -24
- nc_gcode_interpreter-0.2.9/src/types.rs +57 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/vm.rs +12 -4
- nc_gcode_interpreter-0.2.9/tests/performance_contract.rs +202 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/uv.lock +53 -32
- nc_gcode_interpreter-0.2.7/Development.md +0 -88
- nc_gcode_interpreter-0.2.7/examples/trans2.csv +0 -5
- nc_gcode_interpreter-0.2.7/python/tests/test_string_operations.py +0 -79
- nc_gcode_interpreter-0.2.7/src/state.rs +0 -351
- nc_gcode_interpreter-0.2.7/src/types.rs +0 -14
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/.gitignore +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/CONTRIBUTING.md +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/LICENSE +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/TODO.md +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/build.rs +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/build_backend/nc_build_backend.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/docs/resumable-interpretation-design.md +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/docs/sinumerik-execution-model.md +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/actual_position.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/actual_position.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/arc.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/arc.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/arrays.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/arrays.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/axis_index_assignment.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/axis_index_assignment.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/basic_math.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/basic_math.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/case.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/case.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/case_insensitive_variables.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/case_insensitive_variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/custom_vars.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/custom_vars.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/def_string.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/def_string.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/defaults.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/defaults.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/edge_cases.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/edge_cases.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/flattening/flatten_demo.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/flattening/flatten_demo_raw.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/for_loop.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/for_loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/function_calls.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/function_calls.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/goto.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/goto.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/if_statement.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/if_statement.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/increment.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/increment.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/logic_operators.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/logic_operators.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/loop.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/multiple_m_codes.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/multiple_m_codes.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/precedence.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/precedence.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/r_param.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/r_param.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/repeat.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/repeat.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/simple.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/simple.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/spline.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/spline.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/tool.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/tool.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/trans.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/trans.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/trans2.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/trans_ic.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/trans_ic.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/trans_reset.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/trans_reset.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/trig.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/trig.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/variables.csv +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/examples/variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/ggroups/generate_g_commands.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/pyproject.toml +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/example/minimal.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/example/streaming.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/cli.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/ggroups.json +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/py.typed +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/viz.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/nc_source.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_arithmetic_functions.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_batches.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_expected_output.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_field_defects.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_flags.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_g_groups.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_g_vocabulary.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_jumps.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_real_comparisons.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/python/tests/test_streaming.py +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/rustfmt.toml +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/src/modal_groups.rs +0 -0
- {nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/tests/cli.rs +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Benchmark
|
|
2
|
+
on:
|
|
3
|
+
workflow_dispatch:
|
|
4
|
+
inputs:
|
|
5
|
+
lines:
|
|
6
|
+
description: Space-separated synthetic line counts
|
|
7
|
+
default: '100000 400000'
|
|
8
|
+
required: true
|
|
9
|
+
samples:
|
|
10
|
+
description: Warm samples per case
|
|
11
|
+
default: '5'
|
|
12
|
+
required: true
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
jobs:
|
|
16
|
+
benchmark:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 30
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v5
|
|
21
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
22
|
+
- uses: Swatinem/rust-cache@v2
|
|
23
|
+
- uses: actions/setup-python@v6
|
|
24
|
+
with:
|
|
25
|
+
python-version: '3.13'
|
|
26
|
+
- name: Run correctness gates
|
|
27
|
+
run: cargo test --features bench-experiments
|
|
28
|
+
- name: Differential benchmark smoke test
|
|
29
|
+
run: python scripts/benchmark.py --lines 1000 --shapes flat --edits coordinate parameter insert delete --threads 1 --blocks 64 --samples 1 --verify-digest --output benchmark-smoke.json
|
|
30
|
+
- name: Record baseline and experiments
|
|
31
|
+
env:
|
|
32
|
+
BENCH_LINES: ${{ inputs.lines }}
|
|
33
|
+
BENCH_SAMPLES: ${{ inputs.samples }}
|
|
34
|
+
run: python scripts/benchmark.py --lines $BENCH_LINES --samples "$BENCH_SAMPLES" --output benchmark-results.json
|
|
35
|
+
- uses: actions/upload-artifact@v4
|
|
36
|
+
if: always()
|
|
37
|
+
with:
|
|
38
|
+
name: benchmark-results
|
|
39
|
+
path: |
|
|
40
|
+
benchmark-results.json
|
|
41
|
+
benchmark-smoke.json
|
{nc_gcode_interpreter-0.2.7 → nc_gcode_interpreter-0.2.9}/.github/workflows/build-and-release.yml
RENAMED
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
- name: Check Rust formatting
|
|
55
55
|
run: cargo fmt --check
|
|
56
56
|
- name: Run Rust unit and integration tests
|
|
57
|
-
run: cargo test
|
|
57
|
+
run: cargo test --features bench-experiments
|
|
58
58
|
|
|
59
59
|
linux-x64:
|
|
60
60
|
needs: [lint]
|
|
@@ -156,7 +156,9 @@ jobs:
|
|
|
156
156
|
uses: PyO3/maturin-action@v1
|
|
157
157
|
with:
|
|
158
158
|
target: ${{ matrix.platform.target }}
|
|
159
|
-
|
|
159
|
+
# Named interpreters: --find-interpreter also picks up the image's PyPy,
|
|
160
|
+
# which pyo3-ffi 0.29.0 fails to compile for.
|
|
161
|
+
args: --release --out dist -i python3.12 -i python3.13 -i python3.14 -i python3.14t
|
|
160
162
|
sccache: 'true'
|
|
161
163
|
manylinux: auto
|
|
162
164
|
- name: Upload wheels
|
|
@@ -4,6 +4,194 @@ 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.9] - 2026-11-01
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `nc_to_batches` and `nc_to_rows` borrow the Python str's UTF-8 buffer for
|
|
12
|
+
the run instead of copying it twice (once into a Rust `String`, once more
|
|
13
|
+
for error messages). On a 5M-line, 322 MB program the peak footprint of
|
|
14
|
+
`nc_to_batches` drops from 1.28 to 0.72 GiB. The Rust entry points
|
|
15
|
+
(`nc_to_table`, `nc_to_row_stream`, `nc_to_batch_stream*`) take
|
|
16
|
+
`impl Into<SourceText>`: a `&str` still works and is copied once, an owned
|
|
17
|
+
`String` or `Arc<str>` is shared without a copy (#89).
|
|
18
|
+
|
|
19
|
+
- Every jump-free program now runs on the streaming line driver, not only
|
|
20
|
+
those of 64 MiB or more, and the streaming driver decodes ahead on a second
|
|
21
|
+
thread instead of decoding inline. On a 1M-line, 63 MB program
|
|
22
|
+
`nc_to_batches` peaks at 0.21 GiB instead of 0.60 GiB and takes 1.02 s
|
|
23
|
+
instead of 1.24 s; at 5M lines it takes 5.1 s instead of 6.3 s. Programs
|
|
24
|
+
with jumps keep the indexed driver; `NC_STREAM_MIN_BYTES` still sets a size
|
|
25
|
+
threshold (#89).
|
|
26
|
+
|
|
27
|
+
- Less work per output row on the interpreter thread: the batch builder moves
|
|
28
|
+
string cells out of the rows it recycles instead of cloning them, and the
|
|
29
|
+
per-row column set and the flattener's position maps hash with FxHash. At
|
|
30
|
+
5M lines `nc_to_batches` takes 4.4 s instead of 5.2 s (#89).
|
|
31
|
+
|
|
32
|
+
## [v0.2.8] - 2026-10-15
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- `MATCH` and `MINDEX` string search functions (manual 4.1.4.6), next to
|
|
37
|
+
`INDEX`/`RINDEX`: `MATCH` returns the 0-based position of a substring,
|
|
38
|
+
`MINDEX` that of the first character found in a character list; both return
|
|
39
|
+
`-1` when nothing matches (#64).
|
|
40
|
+
|
|
41
|
+
- The `<<` operator is accepted in function-argument position, including the
|
|
42
|
+
prefix form with no left operand, which is the implicit conversion to STRING
|
|
43
|
+
(manual 4.1.4.1). Real programs write `MATCH(<machine data>, <<NAME)` in an
|
|
44
|
+
IF condition; that line used to be a parse error (#64).
|
|
45
|
+
|
|
46
|
+
- Programmable frame rotations `ROT` and `AROT`, with `X.. Y.. Z..` angles or
|
|
47
|
+
the planar form `RPL=`, on top of the existing `TRANS`/`ATRANS` offsets
|
|
48
|
+
(#68). The geometry axes X, Y, Z are output as machine coordinates under a
|
|
49
|
+
rotation matrix plus translation (machine = R * local + t); every other axis
|
|
50
|
+
keeps its plain offset, so programmed A/B/C orientations are never rotated.
|
|
51
|
+
The semantics follow the Sinumerik frame chapters (NC programming manual
|
|
52
|
+
3.12.2.1 and 3.12.2.4, Basic Functions manual 4.5.2.3 and 4.5.2.4): `ROT`
|
|
53
|
+
and `TRANS` are both absolute and each deletes the whole programmable frame,
|
|
54
|
+
so a `TRANS` after `AROT` drops the rotation and a `ROT` after `TRANS` drops
|
|
55
|
+
the offset; `AROT` chains onto the current frame; several angles in one
|
|
56
|
+
block are RPY angles executed Z, then Y', then X'' (MD10600
|
|
57
|
+
FRAME_ANGLE_INPUT_MODE = 1, the default; the Euler ZX'Z'' mode is out of
|
|
58
|
+
scope); `RPL` turns about the normal of the active G17/G18/G19 plane;
|
|
59
|
+
`ATRANS` after a rotation offsets in the rotated system. `IC()` increments
|
|
60
|
+
stay in local coordinates. G2/G3 arcs (I/J/K and CR forms, TURN) are
|
|
61
|
+
supported while the rotation keeps the arc plane equal to the machine plane
|
|
62
|
+
(RPL, or a rotation about the plane normal): the centre offsets are rotated
|
|
63
|
+
with the frame and the flattener works unchanged. An arc under a rotation
|
|
64
|
+
that tilts the plane, `RPL=` mixed with axis angles, angles on non-geometry
|
|
65
|
+
axes, and the still unmodeled `ROTS`/`AROTS`/`CROTS`, `SCALE`/`ASCALE` and
|
|
66
|
+
`MIRROR`/`AMIRROR` all raise with line context. The final state dict gains a
|
|
67
|
+
`rotation` entry (row-major 3x3 list); `translation` keeps its meaning. The
|
|
68
|
+
fast path and the streaming/batch APIs mirror the whole-file interpreter.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- Jump-free programs of 64 MiB or more on the fast path no longer keep every
|
|
73
|
+
decoded line in memory. A parallel preflight scan validates the whole
|
|
74
|
+
program first (a parse error anywhere, also after `M30`, is still reported
|
|
75
|
+
before any row is emitted), then execution decodes and parses one line at a
|
|
76
|
+
time, so parse storage is bounded by the longest line instead of the program
|
|
77
|
+
length. Peak RSS of `nc_to_batches` on a 1.28M-line (112 MB) field program
|
|
78
|
+
drops from about 1.3 GiB to 0.4 GiB, and on a 22M-line (1.1 GB) one from
|
|
79
|
+
5.4 GiB to 2.4 GiB; the second decode costs up to about 20 percent of
|
|
80
|
+
interpretation time. Below 64 MiB the indexed path stays: its retained state
|
|
81
|
+
is at most about 0.7 GiB there, and the second decode would cost the most
|
|
82
|
+
relative time. `NC_STREAM_MIN_BYTES` sets the threshold in bytes (0 streams
|
|
83
|
+
every jump-free program). Programs with a jump keep the indexed path at any
|
|
84
|
+
size (#73).
|
|
85
|
+
|
|
86
|
+
- The fast path builds its jump-target index (one `N:` key per block number,
|
|
87
|
+
one `LABEL:` key per label) only when a grammar-bound line mentions GOTO.
|
|
88
|
+
Trivial lines can never carry a jump, so a program without one skips the
|
|
89
|
+
allocation entirely; a GOTO inside a comment merely builds an unused index.
|
|
90
|
+
About 40 % less time on a 400k-line straight-line program (#73).
|
|
91
|
+
|
|
92
|
+
- A frame instruction no longer moves the interpreted position. `TRANS`,
|
|
93
|
+
`ATRANS`, `ROT` and `AROT` hold the machine position of every positioned
|
|
94
|
+
axis and re-derive its local (workpiece) coordinate under the new frame, as
|
|
95
|
+
a control does when a frame becomes active (Basic Functions manual 4.5.11:
|
|
96
|
+
`$AA_IM` is the machine setpoint, `$AA_IW` the workpiece one). So a block
|
|
97
|
+
after the frame change that programs only some axes moves the others only
|
|
98
|
+
through the rotation, `$AA_IM` agrees with the output rows, `$AA_IW` reports
|
|
99
|
+
the re-derived value, and an `IC()` increment after a `TRANS` moves from the
|
|
100
|
+
current position (NC programming manual 3.1.1.5) instead of jumping by the
|
|
101
|
+
offset; `examples/trans2.csv` changes accordingly. A geometry axis that a
|
|
102
|
+
rotation couples to a positioned axis before any block positioned it is
|
|
103
|
+
taken at machine 0 (with a warning) from then on, so it can be read with
|
|
104
|
+
`$AA_IM` and appears in the rows.
|
|
105
|
+
|
|
106
|
+
- `scripts/compare_benchmarks.py` refuses to compare reports recorded on
|
|
107
|
+
different inputs or, when both were recorded with `--verify-digest`, with
|
|
108
|
+
differing row counts or output/state digests in any matched case (#76).
|
|
109
|
+
|
|
110
|
+
- A control-structure keyword must stand alone in its block (#84). The
|
|
111
|
+
grammar accepted statements on the same line and ran them as part of the
|
|
112
|
+
body: `WHILE R1<3 X1` (likewise after `LOOP`, `REPEAT`, `FOR ... TO ...` and
|
|
113
|
+
`ELSE`) ran `X1` as the first body block of every pass, and a mid-line
|
|
114
|
+
`X1 ELSE X2` ended the true branch. The NC programming manual (4.1.7) shows
|
|
115
|
+
every structure keyword in a block of its own and does not say a control
|
|
116
|
+
accepts more, so these forms are now parse errors that name the text to
|
|
117
|
+
move. A comment after the keyword and a block number before it are still
|
|
118
|
+
allowed. A closer after other content (`X1 ENDIF`) is reported at the closer
|
|
119
|
+
instead of as "IF is never closed". A jump label on a structure line
|
|
120
|
+
(`LB: WHILE R1<3`, `LB: ENDIF`) is rejected too, as a real control does with
|
|
121
|
+
alarm 12630; a conditional jump (`LB: IF R1<3 GOTOB LB`) is no structure and
|
|
122
|
+
keeps its label.
|
|
123
|
+
|
|
124
|
+
### Fixed
|
|
125
|
+
|
|
126
|
+
- Flattened `BSPLINE` samples now carry the line number of the control-point
|
|
127
|
+
block they belong to, instead of all carrying the last control point's line.
|
|
128
|
+
`ASPLINE`/`CSPLINE` and G2/G3 already attributed their samples per segment,
|
|
129
|
+
so a simulator that highlights the executing line from `line_no` stuck on
|
|
130
|
+
one line for a B-spline and walked through the control-point lines for
|
|
131
|
+
every other curve. The curve is now split at the Greville abscissae of the
|
|
132
|
+
clamped knot vector: a sample belongs to the first control point whose
|
|
133
|
+
Greville abscissa is at or beyond the sample's parameter, the last control
|
|
134
|
+
point taking the remainder. Between its Greville abscissae a control point
|
|
135
|
+
is the one with the most influence on the curve, and for degree 1 the rule
|
|
136
|
+
reduces to the polyline segment ending at each programmed point. The
|
|
137
|
+
sampled geometry is unchanged (same coordinates, order and count), only the
|
|
138
|
+
`line_no` distribution changes: the auxiliary cells of a control-point
|
|
139
|
+
block (feed, M codes, comments) now ride on its first sample, and a block
|
|
140
|
+
that owns samples no longer emits an aux-only placeholder row. Only the
|
|
141
|
+
curve end stays a programmed position; every other sample keeps
|
|
142
|
+
`flattened = 1`. The checked-in `examples/flattening/flatten_demo_flattened.csv`
|
|
143
|
+
is regenerated accordingly (#62).
|
|
144
|
+
|
|
145
|
+
- Two statements glued onto one line with no separator (`... = SET(...)DEF INT
|
|
146
|
+
...`, a copy-paste defect seen in a real program) are still rejected, but
|
|
147
|
+
the error now names the trailing text and points at the column where the
|
|
148
|
+
second statement starts, instead of "expected the end of the program". The
|
|
149
|
+
INDEX-based single-character write from the same report has worked since
|
|
150
|
+
v0.2.6 and is now pinned by a test (#58).
|
|
151
|
+
|
|
152
|
+
- Reading an array element or indexed system variable that was never assigned
|
|
153
|
+
reported "Invalid array index ... used as an array index"; the message now
|
|
154
|
+
says the element is undefined and how to supply it. It still names the
|
|
155
|
+
variable and the line, so an unknown string-typed machine data in a string
|
|
156
|
+
argument is a loud error rather than a silent empty string (#64).
|
|
157
|
+
|
|
158
|
+
- A quoted string passed to a numeric function (`SIN("abc")`) was silently
|
|
159
|
+
dropped from the argument list and surfaced as an arity error; it is now a
|
|
160
|
+
type error naming the string.
|
|
161
|
+
|
|
162
|
+
- Nested function calls parse in linear time: each function argument, and
|
|
163
|
+
each assignment right-hand side, is parsed once instead of once per grammar
|
|
164
|
+
alternative, so a deeply nested `SIN(SIN(...))` is instant (#78).
|
|
165
|
+
|
|
166
|
+
- The line fast path no longer declines whole programs because a few hundred
|
|
167
|
+
grammar-bound lines (procedure calls such as `SUPPORT_PIN_TRIGGER(2.5)`,
|
|
168
|
+
`MATERIAL_UPDATE`, `R1=R1+1`, `STOPRE`) are spread through a large file
|
|
169
|
+
(#69). Grammar-bound lines used to be parsed one at a time, each padded with
|
|
170
|
+
one newline per preceding line so that pest reported whole-file positions;
|
|
171
|
+
the padding cost O(line index) bytes per line, and a budget guard sent any
|
|
172
|
+
program over it to the whole-file parse. The grammar-bound lines are now
|
|
173
|
+
parsed in one pest run over a sparse copy of the program (only those lines
|
|
174
|
+
keep their text, every other line is empty), which gives the same whole-file
|
|
175
|
+
positions at O(input) bytes. The budget guard, the padded buffers and the
|
|
176
|
+
grammar-line ratio guard are gone: a program that is entirely grammar-bound
|
|
177
|
+
now costs the same as the whole-file parse (measured equal at 100k and 400k
|
|
178
|
+
lines), so every structure-free program takes the line path and the
|
|
179
|
+
whole-file parse is only used for programs with IF/WHILE/FOR/LOOP/REPEAT
|
|
180
|
+
blocks. On a 112 MB, 1.28M-line CAM
|
|
181
|
+
program with about 12,000 such lines the Python batch API went from 24.3 s
|
|
182
|
+
and 3.0 GiB peak RSS (whole-file parse) to 2.2 s and 1.3 GiB.
|
|
183
|
+
|
|
184
|
+
- The line fast path no longer gives three line shapes a different meaning
|
|
185
|
+
than the grammar does (#83). `N10X5` (a block number glued to the next word)
|
|
186
|
+
was read as block 10 plus `X5`, where the grammar reads one call named
|
|
187
|
+
`N10X5` and warns about the glued word; `TRANS=5` (a frame keyword before
|
|
188
|
+
`=`) was stored as a variable, where the grammar rejects the line; and
|
|
189
|
+
`X=5 AND Y1` (likewise `OR`, `XOR`, `B_AND`, `B_OR`, `B_XOR` after a
|
|
190
|
+
constant) became `X=5`, a call `AND` and `Y1`, where the grammar evaluates
|
|
191
|
+
`5 AND Y1`. The decoder now hands these lines to the grammar, so the result
|
|
192
|
+
no longer depends on which path a program takes. A seeded random-line test
|
|
193
|
+
compares every line the decoder claims against the grammar.
|
|
194
|
+
|
|
7
195
|
## [v0.2.7] - 2026-09-08
|
|
8
196
|
|
|
9
197
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "nc-gcode-interpreter"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.9"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
|
|
@@ -47,4 +47,10 @@ arrow-data = { version = "59", optional = true, default-features = false }
|
|
|
47
47
|
|
|
48
48
|
[features]
|
|
49
49
|
default = []
|
|
50
|
+
bench-experiments = []
|
|
50
51
|
python = ["pyo3", "arrow-array", "arrow-schema", "arrow-data"]
|
|
52
|
+
|
|
53
|
+
[[bench]]
|
|
54
|
+
name = "workloads"
|
|
55
|
+
harness = false
|
|
56
|
+
required-features = ["bench-experiments"]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
Use Cargo to build and run the CLi tool:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
cargo run -- --help
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
To compile the python module:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
maturin develop
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup python environment
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv venv -p 3.12
|
|
21
|
+
uv synv --all-extras
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Release
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cargo build --release
|
|
30
|
+
maturin develop --release --uv
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Versioning
|
|
34
|
+
|
|
35
|
+
`Cargo.toml` holds the placeholder version `0.0.0-dev`; the release workflow
|
|
36
|
+
rewrites it from the git tag before building the wheels, so the released
|
|
37
|
+
version always comes from the tag and never from a committed number.
|
|
38
|
+
|
|
39
|
+
Local builds get a version too. `build_backend/nc_build_backend.py` is an
|
|
40
|
+
in-tree PEP 517 backend that wraps maturin: when it sees the placeholder and
|
|
41
|
+
finds a git checkout with tags, it stamps
|
|
42
|
+
`git describe --tags --long --dirty` onto the `Cargo.toml` version as
|
|
43
|
+
`0.2.6-dev.3+g<sha>` (maturin normalises that to the PEP 440
|
|
44
|
+
`0.2.6.dev3+<sha>`), builds, then restores `Cargo.toml` and `Cargo.lock`. So an
|
|
45
|
+
editable `uv sync` reports the commit it was built from rather than
|
|
46
|
+
`0.0.0.dev0` - which is what lets a downstream consumer notice it is running a
|
|
47
|
+
local edit of the interpreter rather than a release.
|
|
48
|
+
|
|
49
|
+
Any non-placeholder version in `Cargo.toml` is left alone, so the CI release
|
|
50
|
+
path is unaffected. Without git (an unpacked sdist, say) the placeholder
|
|
51
|
+
stands.
|
|
52
|
+
|
|
53
|
+
A plain `cargo build` doesn't go through that shim, so `build.rs` derives the
|
|
54
|
+
same version independently and exposes it as `NC_GCODE_INTERPRETER_VERSION`,
|
|
55
|
+
which is what the CLI's `--version` reports. It applies the same rules: an
|
|
56
|
+
already-stamped `Cargo.toml` version wins, and without git the placeholder
|
|
57
|
+
stands. The CLI therefore shows cargo's semver spelling
|
|
58
|
+
(`0.2.6-dev.1+ge5f2306`), not maturin's PEP 440 normalisation of it.
|
|
59
|
+
|
|
60
|
+
### Release profile
|
|
61
|
+
|
|
62
|
+
`[profile.release]` currently sets no `strip`/`lto` overrides. `strip = true` was
|
|
63
|
+
tried and dropped (`e1ff63a`): cargo runs the *host* `strip` binary even on a
|
|
64
|
+
cross-compiled artifact, so stripping the cross-built macOS x86_64 dylib with
|
|
65
|
+
the arm64 host `strip` produced a wheel PyPI rejected as "not a zipfile" -
|
|
66
|
+
stripping was reverted to unblock the release. It was a secondary size win
|
|
67
|
+
(mostly orthogonal to the polars removal that motivated it) and can be
|
|
68
|
+
revisited via maturin's cross-aware `--strip` flag instead of a Cargo
|
|
69
|
+
profile setting.
|
|
70
|
+
|
|
71
|
+
`lto` remains an untried, plausible speed knob (doesn't invoke an external
|
|
72
|
+
tool, so it isn't blocked by the cross-compile issue above) - just not yet
|
|
73
|
+
evaluated for its extra build time across the wheel matrix.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Super simple test
|
|
77
|
+
|
|
78
|
+
There are a bunch of csv files in the examples directory. To test the tool on all of them (use git to check changes)
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
rm **/*.csv && cargo build --release && find examples -name "*.mpf" -type f -print0 | xargs -0 -I {} sh -c './target/release/nc-gcode-interpreter --axis-index-map E:4 --initial_state=examples/defaults.mpf "$1" || echo "Failed to process $1" >&2' sh {}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## python test
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
maturin develop --release --uv && pytest
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Benchmark harness and parsing experiments
|
|
91
|
+
|
|
92
|
+
The `bench-experiments` cargo feature compiles research-only strategies
|
|
93
|
+
(`src/line_driver/experiments.rs`), a benchmark executable (`benches/`) and
|
|
94
|
+
differential tests (`tests/performance_contract.rs`). Normal library and Python
|
|
95
|
+
builds do not include any of it. The strategies are a laboratory for #71, #72,
|
|
96
|
+
#73 and #74, not a session or incremental API.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cargo test --features bench-experiments
|
|
100
|
+
python3 scripts/benchmark.py --lines 10000 100000 400000 --samples 5 --output tmp/baseline.json
|
|
101
|
+
python3 scripts/compare_benchmarks.py tmp/baseline.json tmp/candidate.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`scripts/benchmark.py` (Python 3.11 or newer, macOS or Linux) runs each case in
|
|
105
|
+
its own release-build process and records cold and warm samples, time to the
|
|
106
|
+
first batch, per-process peak RSS, build settings, the source and binary
|
|
107
|
+
SHA-256 and the git revision. Sample zero is cold. `prepare_ms + run_ms` is the
|
|
108
|
+
interpreter turnaround. The timings exclude Python, Arrow and viewer work, so
|
|
109
|
+
they say nothing about edit-to-visible latency (#72). Do not run other builds
|
|
110
|
+
concurrently when comparing. Results are not committed; keep them under `tmp/`.
|
|
111
|
+
|
|
112
|
+
| Mode | What it measures | Boundary |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| `fresh` | The public batch API, 1 or 4 Rayon threads; `--reference` adds the whole-file grammar and the VM | Full interpretation every time |
|
|
115
|
+
| `grammar-cache` | Keeping a pest tree for unchanged reruns | Every edit reparses; O(file) AST |
|
|
116
|
+
| `decoded-cache` | Immutable decoded blocks, patched by line range | Trivial label-free lines only; output is re-executed |
|
|
117
|
+
| `bounded` | Validate eligibility, then decode and execute one line at a time | Two decode passes; trivial label-free lines only |
|
|
118
|
+
| `fused` | Decode and execute immediately with a reusable word arena | Same language; a later unsupported line fails after output started |
|
|
119
|
+
|
|
120
|
+
Edit cases (`--edits coordinate parameter insert delete translation`) prepare
|
|
121
|
+
two revised sources before timing; `decoded-cache` undoes the previous patch
|
|
122
|
+
and applies the next one inside `prepare_ms`. `--input <file>` benchmarks a
|
|
123
|
+
local program with `--edits none`; `--verify-digest` additionally checks that
|
|
124
|
+
every strategy produces the same rows and final state (hashing adds time, so
|
|
125
|
+
do not compare those timings with plain runs). `--cancel` drops the consumer
|
|
126
|
+
after the first batch. The manually dispatched Benchmark workflow runs the
|
|
127
|
+
correctness gate and uploads the JSON; it asserts no wall-clock numbers.
|
|
128
|
+
|
|
129
|
+
Findings so far (Apple M1 Pro, release builds, median of five warm samples,
|
|
130
|
+
single thread, 400k-line synthetic straight-line program):
|
|
131
|
+
|
|
132
|
+
| Strategy | Time | Peak RSS |
|
|
133
|
+
| --- | ---: | ---: |
|
|
134
|
+
| Public batch API | 672 ms | 560 MiB |
|
|
135
|
+
| Cached decoded blocks (64 lines) | 260 ms | 272 MiB |
|
|
136
|
+
| Fused decode and execute | 357 ms | 87 MiB |
|
|
137
|
+
|
|
138
|
+
Parsing reuse removes about a quarter of the warm-edit time; execution and
|
|
139
|
+
output dominate the rest, so output chunks or checkpoints (#47, #74) are
|
|
140
|
+
needed to go further. Keeping the whole pest tree was slower than the fast
|
|
141
|
+
path on ordinary CAM input and cost 3.7 s to rebuild after a TRANS edit. Four
|
|
142
|
+
Rayon threads did not consistently beat one. Thin LTO changed the flat cases by
|
|
143
|
+
1 to 9 %. Fused decoding handled synthetic 7M and 22M-line programs in 6.3 s
|
|
144
|
+
and 21 s at 1.0 and 3.1 GiB; the input copy and the State line index still
|
|
145
|
+
scale with the source. The sparse grammar buffer that came out of this work is
|
|
146
|
+
now the production fast path (#69).
|
|
@@ -11,7 +11,7 @@ The **NC-GCode-Interpreter** offers a streamlined and efficient solution for int
|
|
|
11
11
|
### Supported G-code Features
|
|
12
12
|
|
|
13
13
|
- **G Group Commands**: Recognizes G-code groups and modal G-code commands.
|
|
14
|
-
- **
|
|
14
|
+
- **Programmable frames**: `TRANS`/`ATRANS` work offsets and `ROT`/`AROT` rotations (`X.. Y.. Z..` RPY angles or `RPL=` in the active plane) for the geometry axes X, Y, Z; output coordinates are machine coordinates with the frame applied, and the final state reports the `translation` and `rotation`.
|
|
15
15
|
- **Looping Constructs**: Handles loops using `WHILE`, `FOR`, `REPEAT ... UNTIL` and `LOOP ... ENDLOOP` statements.
|
|
16
16
|
- **Variable Handling**: Supports definition and manipulation of local variables.
|
|
17
17
|
- **Conditional Logic**: Implements conditional execution with `IF`, `ELSE`, and `ENDIF`.
|
|
@@ -23,7 +23,7 @@ The **NC-GCode-Interpreter** offers a streamlined and efficient solution for int
|
|
|
23
23
|
|
|
24
24
|
### Additional Functionality
|
|
25
25
|
|
|
26
|
-
- **Streaming interpretation**: `nc_to_rows(program)` yields `(line_no, row)` tuples lazily while the interpreter runs on a background thread —
|
|
26
|
+
- **Streaming interpretation**: `nc_to_rows(program)` yields `(line_no, row)` tuples lazily while the interpreter runs on a background thread — bounded output buffering, early exit by dropping the iterator (breaking out of a `for` loop over an anonymous iterator does this; a stored iterator keeps the run alive until it is deleted or garbage-collected), and source-line numbers for mapping trace rows back to the program. With `include_variables=True` it yields `(line_no, row, variables)` instead, exposing every variable assignment (`R1=R1+1`, `DEF`, FOR counters) as it happens — including blocks that only assign variables, which are invisible in the batch DataFrame.
|
|
27
27
|
|
|
28
28
|
- **Curve flattening**: pass `flatten_tolerance` (Python) or `--flatten-tolerance` (CLI) to convert curved motions — `G2`/`G3` arcs (`I`/`J`/`K` centre offsets or `CR=` radius form, `G17`/`G18`/`G19` planes, helical third axis with `TURN=` multi-turn support, full circles) and `ASPLINE`/`BSPLINE`/`CSPLINE` splines (including `PW` weights and `SD` degree) — into runs of plain `G1` rows in the same table format. `CIP`/`CT`/`POLY`/thread/involute blocks pass through unchanged with a warning; spline start/end conditions (`BAUTO`/`BNAT`/... ) are approximated by natural (CSPLINE) / Akima (ASPLINE) boundaries — see the `flatten` module docs for the full list of checked approximations. The single tolerance is the maximum deviation between the emitted polyline and the true curve, in path units: arcs use the exact sagitta bound, splines adaptive subdivision. The interpolation addresses (`I`/`J`/`K`/`CR`/`PW`/`SD`/`PL`) are consumed and do not appear in the output; sampled rows keep the source block's line number, and its other cells (`F`, `M`, comments, ...) ride on the first sample so modal behavior is preserved. Generated samples carry a `flattened = 1` marker column (never forward-filled); programmed positions stay null there, so filtering on null recovers the original toolpath points.
|
|
29
29
|
- **Toolpath visualization** (optional): with the `viz` extra (`pip install 'nc-gcode-interpreter[viz]'`), `nc_gcode_interpreter.viz.view_toolpath(df)` renders an interpreted toolpath in [threejs-viewer](https://pypi.org/project/threejs-viewer/) as an extruded bead tube with a draw-range animation replaying the program at feed-rate-proportional speed. Combined with `flatten_tolerance`, programmed points render orange and flattener-generated samples blue. The extra also installs the `nc-view` command: `nc-view part.mpf` interprets, flattens (default tolerance 0.1, `--no-flatten` to skip) and opens the animated toolpath in the browser; `--speed` sets the time-lapse factor (default 60 = one machine-minute per second).
|
|
@@ -161,6 +161,23 @@ for line_no, row, variables in nc_to_rows(program, include_variables=True):
|
|
|
161
161
|
|
|
162
162
|
Rows are typed and forward-filled like the batch DataFrame (disable with `forward_fill=False`); errors raise from `next()` at the offending row; after exhaustion the iterator's `state` attribute holds the final interpreter state. See `python/example/streaming.py` for a runnable version.
|
|
163
163
|
|
|
164
|
+
**Memory.** Programs without multi-line control blocks or jumps are validated
|
|
165
|
+
in a preflight scan and then executed one line at a time, with the next lines
|
|
166
|
+
decoded on a second thread, so parse storage is bounded by a chunk of lines
|
|
167
|
+
instead of the program length. The input text (held once: the Python str is
|
|
168
|
+
borrowed, not copied, and error messages read from the same buffer), its
|
|
169
|
+
line-offset index, variable state and any output the caller retains still grow
|
|
170
|
+
with the program, so use `nc_to_batches` with a bounded consumer for large
|
|
171
|
+
files. Programs with jumps keep the indexed fast path; `NC_STREAM_MIN_BYTES`
|
|
172
|
+
sets a size below which jump-free programs take it too (default 0). Control
|
|
173
|
+
blocks and `NC_STAGE1=0` still use the whole-file parser. On a 1M-line (63 MB)
|
|
174
|
+
generated program the peak footprint of `nc_to_batches` is about 0.2 GiB, and
|
|
175
|
+
streaming is 5 to 20 percent faster than the indexed driver from 1 MB up.
|
|
176
|
+
Measure a local program with
|
|
177
|
+
`python scripts/benchmark.py --input <file> --modes fresh --edits none
|
|
178
|
+
--threads 4 --samples 1 --batch-size 4096 --allow-undefined --axis-index-e 4
|
|
179
|
+
--axis-index-elx 5 --output tmp/memory.json`.
|
|
180
|
+
|
|
164
181
|
**Cancellation.** Dropping the iterator is the supported way to cancel a `nc_to_rows`/`nc_to_batches` run — leaving a `for` loop early or `del`-ing it drops it immediately; relying on garbage collection works too but only whenever the runtime happens to collect it, so it is not prompt on all interpreters: the background interpreter thread's next attempt to send a row hits a closed channel and returns internally via `ParsingError::StreamClosed`, so the thread unwinds promptly instead of running the rest of the program to completion. No explicit `.close()`/`.cancel()` call is needed or provided — drop is the whole contract, and downstreams (e.g. ribweaver's `/sim` live-edit reinterpret) rely on it for latest-wins cancellation.
|
|
165
182
|
|
|
166
183
|
Additionally, conversion from a Polars DataFrame back to an MPF (NC) program is also supported:
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
use std::fmt::Write;
|
|
2
|
+
|
|
3
|
+
pub fn program(shape: &str, lines: usize) -> String {
|
|
4
|
+
assert!(lines >= 8);
|
|
5
|
+
let mut text = String::with_capacity(lines * 90);
|
|
6
|
+
text.push_str(if shape == "frames" {
|
|
7
|
+
"TRANS X10\n"
|
|
8
|
+
} else {
|
|
9
|
+
"MULTIPLIER=1\n"
|
|
10
|
+
});
|
|
11
|
+
text.push_str("G1 X0 Y0 Z0 E0 F1200\n");
|
|
12
|
+
for i in 2..lines - 1 {
|
|
13
|
+
match shape {
|
|
14
|
+
"calls" if i % 1000 == 0 => text.push_str("SUPPORT_PIN_TRIGGER(2.5)\n"),
|
|
15
|
+
"grammar" => text.push_str("X=SIN(30) Y=COS(60)\n"),
|
|
16
|
+
"structured" if i == 2 => text.push_str("IF 1==1\n"),
|
|
17
|
+
"structured" if i == lines - 2 => text.push_str("ENDIF\n"),
|
|
18
|
+
_ => writeln!(
|
|
19
|
+
text,
|
|
20
|
+
"N{i} G1 X{}.125 Y{}.25 Z1 E=IC({}) ELX=100 F1200",
|
|
21
|
+
i % 1000,
|
|
22
|
+
i % 300,
|
|
23
|
+
if shape == "frames" { "0.025" } else { "MULTIPLIER*0.025" }
|
|
24
|
+
)
|
|
25
|
+
.unwrap(),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
text.push_str("M30\n");
|
|
29
|
+
text
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// All edits are against the original revision, allowing alternating edit/undo.
|
|
33
|
+
pub fn edit(source: &str, kind: &str, alternate: bool) -> (String, std::ops::Range<usize>, String) {
|
|
34
|
+
let mut lines: Vec<_> = source.lines().collect();
|
|
35
|
+
let mid = lines.len() / 2;
|
|
36
|
+
let (range, replacement) = match kind {
|
|
37
|
+
"none" => (0..0, String::new()),
|
|
38
|
+
"coordinate" => (
|
|
39
|
+
mid..mid + 1,
|
|
40
|
+
format!("G1 X{} Y2 E=IC(0.025)", if alternate { 7 } else { 8 }),
|
|
41
|
+
),
|
|
42
|
+
"parameter" => (0..1, format!("MULTIPLIER={}", if alternate { 2 } else { 3 })),
|
|
43
|
+
"translation" => (0..1, format!("TRANS X{}", if alternate { 20 } else { 30 })),
|
|
44
|
+
"insert" => (mid..mid, "; inserted comment\nG1 X3".into()),
|
|
45
|
+
"delete" => (mid..mid + 1, String::new()),
|
|
46
|
+
_ => panic!("unknown edit {kind}"),
|
|
47
|
+
};
|
|
48
|
+
lines.splice(range.clone(), replacement.lines());
|
|
49
|
+
(format!("{}\n", lines.join("\n")), range, replacement)
|
|
50
|
+
}
|