nc-gcode-interpreter 0.2.0__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.
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/.github/workflows/build-and-release.yml +7 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/CHANGELOG.md +44 -12
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/Cargo.lock +1 -1
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/Cargo.toml +1 -1
- nc_gcode_interpreter-0.2.1/Development.md +61 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/PKG-INFO +1 -1
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/README.md +2 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/nc_gcode_interpreter/__init__.py +40 -2
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/nc_gcode_interpreter/_internal.pyi +10 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_batches.py +53 -0
- nc_gcode_interpreter-0.2.1/python/tests/test_structured_errors.py +50 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/errors.rs +156 -32
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/flatten.rs +318 -69
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/interpret_rules.rs +208 -103
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/interpreter.rs +206 -59
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/lib.rs +82 -40
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/line_driver.rs +98 -71
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/main.rs +16 -23
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/modal_groups.rs +41 -10
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/output.rs +48 -10
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/state.rs +15 -19
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/structure_scan.rs +3 -8
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/tests/cli.rs +4 -1
- nc_gcode_interpreter-0.2.0/Development.md +0 -46
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/.gitignore +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/CONTRIBUTING.md +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/LICENSE +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/TODO.md +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/docs/sinumerik-execution-model.md +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/actual_position.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/actual_position.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/arc.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/arc.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/arrays.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/arrays.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/axis_index_assignment.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/axis_index_assignment.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/basic_math.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/basic_math.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/case.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/case.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/case_insensitive_variables.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/case_insensitive_variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/custom_vars.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/custom_vars.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/def_string.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/def_string.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/defaults.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/defaults.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/edge_cases.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/edge_cases.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/flattening/flatten_demo.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/flattening/flatten_demo_flattened.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/flattening/flatten_demo_raw.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/for_loop.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/for_loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/function_calls.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/function_calls.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/goto.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/goto.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/if_statement.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/if_statement.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/increment.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/increment.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/logic_operators.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/logic_operators.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/loop.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/multiple_m_codes.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/multiple_m_codes.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/precedence.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/precedence.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/r_param.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/r_param.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/repeat.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/repeat.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/simple.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/simple.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/spline.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/spline.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/tool.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/tool.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trans.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trans.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trans2.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trans2.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trans_ic.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trans_ic.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trans_reset.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trans_reset.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trig.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/trig.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/variables.csv +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/examples/variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/ggroups/generate_g_commands.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/pyproject.toml +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/example/minimal.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/example/streaming.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/nc_gcode_interpreter/cli.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/nc_gcode_interpreter/ggroups.json +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/nc_gcode_interpreter/py.typed +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/nc_gcode_interpreter/viz.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_arithmetic_functions.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_diagnostics.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_expected_output.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_field_defects.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_flags.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_flatten.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_g_groups.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_g_vocabulary.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_jumps.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_real_comparisons.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_stage1.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_streaming.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/tests/test_unsupported.py +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/rustfmt.toml +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/grammar.pest +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/src/types.rs +0 -0
- {nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/uv.lock +0 -0
{nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/.github/workflows/build-and-release.yml
RENAMED
|
@@ -43,7 +43,14 @@ jobs:
|
|
|
43
43
|
runs-on: ubuntu-latest
|
|
44
44
|
steps:
|
|
45
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
|
|
46
51
|
- uses: Swatinem/rust-cache@v2
|
|
52
|
+
- name: Check Rust formatting
|
|
53
|
+
run: cargo fmt --check
|
|
47
54
|
- name: Run Rust unit and integration tests
|
|
48
55
|
run: cargo test
|
|
49
56
|
|
|
@@ -4,20 +4,42 @@ 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.
|
|
7
|
+
## [v0.2.1] - 2026-07-07
|
|
8
8
|
|
|
9
|
-
###
|
|
9
|
+
### Added
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
21
43
|
|
|
22
44
|
### Added
|
|
23
45
|
|
|
@@ -95,6 +117,16 @@ released to PyPI.
|
|
|
95
117
|
|
|
96
118
|
### Changed
|
|
97
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).
|
|
98
130
|
- **Breaking:** `I`/`J`/`K`/`CR` are now treated as arc interpolation-parameter
|
|
99
131
|
block addresses (output columns), so they can no longer be used as user
|
|
100
132
|
variable names: `I=5` followed by `X=I+1` was a variable read before and is
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
### Release profile
|
|
34
|
+
|
|
35
|
+
`[profile.release]` currently sets no `strip`/`lto` overrides. `strip = true` was
|
|
36
|
+
tried and dropped (`e1ff63a`): cargo runs the *host* `strip` binary even on a
|
|
37
|
+
cross-compiled artifact, so stripping the cross-built macOS x86_64 dylib with
|
|
38
|
+
the arm64 host `strip` produced a wheel PyPI rejected as "not a zipfile" -
|
|
39
|
+
stripping was reverted to unblock the release. It was a secondary size win
|
|
40
|
+
(mostly orthogonal to the polars removal that motivated it) and can be
|
|
41
|
+
revisited via maturin's cross-aware `--strip` flag instead of a Cargo
|
|
42
|
+
profile setting.
|
|
43
|
+
|
|
44
|
+
`lto` remains an untried, plausible speed knob (doesn't invoke an external
|
|
45
|
+
tool, so it isn't blocked by the cross-compile issue above) - just not yet
|
|
46
|
+
evaluated for its extra build time across the wheel matrix.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Super simple test
|
|
50
|
+
|
|
51
|
+
There are a bunch of csv files in the examples directory. To test the tool on all of them (use git to check changes)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
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 {}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## python test
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
maturin develop --release --uv && pytest
|
|
61
|
+
```
|
|
@@ -161,6 +161,8 @@ 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
|
+
**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
|
+
|
|
164
166
|
Additionally, conversion from a Polars DataFrame back to an MPF (NC) program is also supported:
|
|
165
167
|
|
|
166
168
|
```bash
|
{nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/nc_gcode_interpreter/__init__.py
RENAMED
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
import polars as pl
|
|
4
4
|
from ._internal import nc_to_rows as _nc_to_rows
|
|
5
5
|
from ._internal import nc_to_batches as _nc_to_batches
|
|
6
|
+
from ._internal import NcError
|
|
6
7
|
from ._internal import __doc__ # noqa: F401
|
|
7
8
|
import json
|
|
8
9
|
from pathlib import Path
|
|
@@ -21,6 +22,7 @@ __all__ = [
|
|
|
21
22
|
"nc_to_batches",
|
|
22
23
|
"sanitize_dataframe",
|
|
23
24
|
"dataframe_to_nc",
|
|
25
|
+
"NcError",
|
|
24
26
|
]
|
|
25
27
|
|
|
26
28
|
# Batch size nc_to_dataframe uses internally when draining the batch stream it
|
|
@@ -60,6 +62,7 @@ def nc_to_dataframe(
|
|
|
60
62
|
axis_index_map: dict[str, int] | None = None,
|
|
61
63
|
allow_undefined_variables: bool = False,
|
|
62
64
|
flatten_tolerance: float | None = None,
|
|
65
|
+
include_line_numbers: bool = False,
|
|
63
66
|
) -> tuple[pl.DataFrame, dict]:
|
|
64
67
|
"""
|
|
65
68
|
Parses Sinumerik-flavored NC G-code and converts it into a Polars DataFrame along with the final state.
|
|
@@ -93,6 +96,12 @@ def nc_to_dataframe(
|
|
|
93
96
|
deviation (in path units, i.e. mm) of the true curve. The interpolation
|
|
94
97
|
parameters (I/J/K/CR, PW/SD/PL) are consumed and do not appear in the
|
|
95
98
|
output. Default None (curves pass through untouched).
|
|
99
|
+
include_line_numbers: bool, optional
|
|
100
|
+
If True, prepend a `line_no` (Int64) column giving the 1-based source
|
|
101
|
+
line each output row came from - repeated under a loop, non-monotonic
|
|
102
|
+
across a jump, matching the streaming `nc_to_rows`. Never
|
|
103
|
+
forward-filled; `dataframe_to_nc` ignores it. Default False (column
|
|
104
|
+
absent, output schema unchanged).
|
|
96
105
|
|
|
97
106
|
Returns:
|
|
98
107
|
--------
|
|
@@ -142,6 +151,7 @@ def nc_to_dataframe(
|
|
|
142
151
|
allow_undefined_variables,
|
|
143
152
|
input_is_path,
|
|
144
153
|
flatten_tolerance,
|
|
154
|
+
include_line_numbers,
|
|
145
155
|
)
|
|
146
156
|
# pl.DataFrame wraps each Arrow record batch via __arrow_c_array__ (polars
|
|
147
157
|
# >= 1.3), no pyarrow needed. Exhaust the iterator before reading .state.
|
|
@@ -245,6 +255,13 @@ def sanitize_dataframe(
|
|
|
245
255
|
pl.DataFrame
|
|
246
256
|
The sanitized DataFrame ready for analysis or conversion back to G-code.
|
|
247
257
|
"""
|
|
258
|
+
# `line_no` (present only when interpreted with include_line_numbers=True)
|
|
259
|
+
# is source-provenance metadata, not an emittable G-code word; drop it up
|
|
260
|
+
# front so it is never cast/forward-filled or written back out by
|
|
261
|
+
# dataframe_to_nc.
|
|
262
|
+
if "line_no" in df.columns:
|
|
263
|
+
df = df.drop("line_no")
|
|
264
|
+
|
|
248
265
|
modal = [g["short_name"] for g in GGroups.g_groups if g["effectiveness"] == "modal"]
|
|
249
266
|
non_modal = [g["short_name"] for g in GGroups.g_groups if g["effectiveness"] != "modal"]
|
|
250
267
|
known_axes = [
|
|
@@ -415,6 +432,13 @@ def nc_to_rows(
|
|
|
415
432
|
an anonymous iterator drops it; a stored iterator keeps the worker
|
|
416
433
|
alive (parked on the bounded channel) until it is deleted or
|
|
417
434
|
garbage-collected.
|
|
435
|
+
|
|
436
|
+
Dropping the iterator is the **supported cancel contract**: the
|
|
437
|
+
background thread's next attempted send hits the now-closed channel and
|
|
438
|
+
returns internally via ``ParsingError::StreamClosed``, so it unwinds
|
|
439
|
+
promptly instead of finishing the rest of the program. There is no
|
|
440
|
+
separate ``.cancel()``/``.close()`` - drop is the whole mechanism.
|
|
441
|
+
|
|
418
442
|
Errors raise ``ValueError`` from ``next()`` when reached. After the
|
|
419
443
|
iterator is exhausted, its ``state`` attribute holds the final
|
|
420
444
|
interpreter state (axes, symbol_table, translation).
|
|
@@ -453,8 +477,9 @@ class _BatchIterator:
|
|
|
453
477
|
|
|
454
478
|
Wraps the Rust batch iterator, which yields each batch as an Arrow record
|
|
455
479
|
batch built column-wise in Rust and handed over the Arrow PyCapsule
|
|
456
|
-
interface (``__arrow_c_array__``,
|
|
457
|
-
|
|
480
|
+
interface (``__arrow_c_array__``, exported directly with the minimal
|
|
481
|
+
arrow-rs crates - no pyo3-arrow/pyo3-polars) - a zero-copy transfer with
|
|
482
|
+
no Python list of primitives materialized. ``pl.DataFrame`` wraps that
|
|
458
483
|
capsule directly (polars >= 1.3), so no ``pyarrow`` is involved. After
|
|
459
484
|
exhaustion its ``state`` attribute holds the final interpreter state (axes,
|
|
460
485
|
symbol_table, translation), like the iterator returned by :func:`nc_to_rows`.
|
|
@@ -485,6 +510,7 @@ def nc_to_batches(
|
|
|
485
510
|
axis_index_map: dict[str, int] | None = None,
|
|
486
511
|
allow_undefined_variables: bool = False,
|
|
487
512
|
flatten_tolerance: float | None = None,
|
|
513
|
+
include_line_numbers: bool = False,
|
|
488
514
|
) -> _BatchIterator:
|
|
489
515
|
"""Interpret an NC program into a stream of columnar polars DataFrames.
|
|
490
516
|
|
|
@@ -504,6 +530,17 @@ def nc_to_batches(
|
|
|
504
530
|
The returned iterator exposes a ``state`` attribute holding the final
|
|
505
531
|
interpreter state once it is exhausted, like :func:`nc_to_rows`.
|
|
506
532
|
|
|
533
|
+
Like :func:`nc_to_rows`, this runs on a background thread behind a
|
|
534
|
+
bounded channel, and dropping the iterator is the supported way to
|
|
535
|
+
cancel: the next attempted send hits the closed channel and returns
|
|
536
|
+
internally via ``ParsingError::StreamClosed``, unwinding the thread
|
|
537
|
+
promptly instead of finishing the rest of the program.
|
|
538
|
+
|
|
539
|
+
With ``include_line_numbers=True`` every batch gains a leading ``line_no``
|
|
540
|
+
(Int64) column: the 1-based source line each row came from (repeated under
|
|
541
|
+
a loop, non-monotonic across a jump), never forward-filled. Default False
|
|
542
|
+
leaves the schema unchanged.
|
|
543
|
+
|
|
507
544
|
Example:
|
|
508
545
|
--------
|
|
509
546
|
>>> batches = nc_to_batches("G1 X10\\nX20 Y5", batch_size=1)
|
|
@@ -526,5 +563,6 @@ def nc_to_batches(
|
|
|
526
563
|
allow_undefined_variables,
|
|
527
564
|
input_is_path,
|
|
528
565
|
flatten_tolerance,
|
|
566
|
+
include_line_numbers,
|
|
529
567
|
)
|
|
530
568
|
return _BatchIterator(inner)
|
{nc_gcode_interpreter-0.2.0 → nc_gcode_interpreter-0.2.1}/python/nc_gcode_interpreter/_internal.pyi
RENAMED
|
@@ -5,6 +5,15 @@ from typing import Any, Iterator, Optional, List, Dict, Tuple
|
|
|
5
5
|
# wrapper imports are declared here. Signatures mirror the `#[pyo3(signature)]`
|
|
6
6
|
# defaults in src/lib.rs.
|
|
7
7
|
|
|
8
|
+
class NcError(ValueError):
|
|
9
|
+
"""NC parse/interpret error carrying structured location data. Subclasses
|
|
10
|
+
ValueError so `except ValueError` keeps working."""
|
|
11
|
+
|
|
12
|
+
line: Optional[int]
|
|
13
|
+
column: Optional[int]
|
|
14
|
+
context: Optional[str]
|
|
15
|
+
line_text: Optional[str]
|
|
16
|
+
|
|
8
17
|
def nc_to_rows(
|
|
9
18
|
input: str,
|
|
10
19
|
initial_state: Optional[str] = None,
|
|
@@ -33,6 +42,7 @@ def nc_to_batches(
|
|
|
33
42
|
allow_undefined_variables: bool = False,
|
|
34
43
|
input_is_path: bool = False,
|
|
35
44
|
flatten_tolerance: Optional[float] = None,
|
|
45
|
+
include_line_numbers: bool = False,
|
|
36
46
|
) -> Any:
|
|
37
47
|
"""Interpret an NC program into an iterator of columnar polars DataFrames."""
|
|
38
48
|
...
|
|
@@ -155,3 +155,56 @@ def test_path_input_batches(tmp_path):
|
|
|
155
155
|
df, _ = nc_to_dataframe(program)
|
|
156
156
|
frames = list(nc_to_batches(mpf, batch_size=2))
|
|
157
157
|
assert_frame_equal(pl.concat(frames, how="diagonal").select(df.columns), df)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_include_line_numbers_is_opt_in():
|
|
161
|
+
"""`line_no` is off by default and, when enabled, is a leading Int64 column
|
|
162
|
+
that repeats under a loop and is non-monotonic across a jump - matching the
|
|
163
|
+
streaming `nc_to_rows` row-for-row and reconstructible across batches."""
|
|
164
|
+
prog = "R1=0\nWHILE R1<2\nX=R1 F100\nR1=R1+1\nENDWHILE\nX9\n"
|
|
165
|
+
|
|
166
|
+
# Default off: schema unchanged, no line_no column anywhere.
|
|
167
|
+
df_off, _ = nc_to_dataframe(prog)
|
|
168
|
+
assert "line_no" not in df_off.columns
|
|
169
|
+
off_batches = list(nc_to_batches(prog, batch_size=1))
|
|
170
|
+
assert all("line_no" not in f.columns for f in off_batches)
|
|
171
|
+
|
|
172
|
+
# Opt-in: leading Int64 line_no with the loop's repeated source lines.
|
|
173
|
+
df_on, _ = nc_to_dataframe(prog, include_line_numbers=True)
|
|
174
|
+
assert df_on.columns[0] == "line_no"
|
|
175
|
+
assert df_on["line_no"].dtype == pl.Int64
|
|
176
|
+
assert df_on["line_no"].to_list() == [3, 3, 6]
|
|
177
|
+
# Dropping line_no reproduces the default-off frame exactly.
|
|
178
|
+
assert_frame_equal(df_on.drop("line_no"), df_off)
|
|
179
|
+
|
|
180
|
+
# Batches carry it too and concatenate back to the whole-file column.
|
|
181
|
+
on_batches = list(nc_to_batches(prog, batch_size=1, include_line_numbers=True))
|
|
182
|
+
assert pl.concat(on_batches, how="diagonal")["line_no"].to_list() == [3, 3, 6]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_include_line_numbers_matches_streaming_under_jumps():
|
|
186
|
+
"""A GOTO reorders execution; the batch `line_no` column must match what
|
|
187
|
+
the streaming `nc_to_rows` yields, line for line."""
|
|
188
|
+
from nc_gcode_interpreter import nc_to_rows
|
|
189
|
+
|
|
190
|
+
prog = "N10 X1\nGOTOF 40\nN30 X999\nN40 X4\n"
|
|
191
|
+
df, _ = nc_to_dataframe(prog, include_line_numbers=True)
|
|
192
|
+
streamed = [line_no for line_no, _row in nc_to_rows(prog)]
|
|
193
|
+
assert df["line_no"].to_list() == streamed == [1, 4]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_include_line_numbers_survives_flattening():
|
|
197
|
+
"""Flatten-generated samples keep the originating block's source line, so a
|
|
198
|
+
flattened arc/spline block yields many rows all carrying its line_no - and
|
|
199
|
+
still matches the streaming path row-for-row."""
|
|
200
|
+
from nc_gcode_interpreter import nc_to_rows
|
|
201
|
+
|
|
202
|
+
program = "G1 X0 Y0 F1000\nG2 X100 Y0 I50 J0\nG1 X100 Y50\n"
|
|
203
|
+
df, _ = nc_to_dataframe(program, flatten_tolerance=0.1, include_line_numbers=True)
|
|
204
|
+
assert df.columns[0] == "line_no"
|
|
205
|
+
assert df["line_no"].null_count() == 0
|
|
206
|
+
per_line = df["line_no"].to_list()
|
|
207
|
+
# The arc on line 2 expands to many G1 samples, every one tagged line 2.
|
|
208
|
+
assert per_line.count(2) > 5
|
|
209
|
+
stream_lines = [line for line, *_ in nc_to_rows(program, flatten_tolerance=0.1)]
|
|
210
|
+
assert per_line == stream_lines
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""NcError exposes an error's source location as data (line / column / context
|
|
2
|
+
/ line_text) instead of only as a formatted string, while remaining a
|
|
3
|
+
ValueError subclass so existing `except ValueError` handlers keep working."""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from nc_gcode_interpreter import NcError, nc_to_dataframe
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_nc_error_is_a_value_error_subclass():
|
|
10
|
+
assert issubclass(NcError, ValueError)
|
|
11
|
+
with pytest.raises(ValueError):
|
|
12
|
+
nc_to_dataframe("X=R99\n") # undefined variable
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_syntax_error_carries_line_and_column():
|
|
16
|
+
# The malformed token is on line 2; the parser reports its column.
|
|
17
|
+
with pytest.raises(NcError) as exc:
|
|
18
|
+
nc_to_dataframe("G1 X10\nX20 Y((\nX30\n")
|
|
19
|
+
e = exc.value
|
|
20
|
+
assert e.line == 2
|
|
21
|
+
assert isinstance(e.column, int) and e.column > 0
|
|
22
|
+
assert e.line_text == "X20 Y(("
|
|
23
|
+
assert e.context == "line parsing"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_semantic_error_carries_line_but_no_column():
|
|
27
|
+
# An undefined-variable error is anchored to a line, not a column.
|
|
28
|
+
with pytest.raises(NcError) as exc:
|
|
29
|
+
nc_to_dataframe("X=R99\n")
|
|
30
|
+
e = exc.value
|
|
31
|
+
assert e.line == 1
|
|
32
|
+
assert e.column is None
|
|
33
|
+
assert e.line_text == "X=R99"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_location_attributes_always_present():
|
|
37
|
+
# Every NcError has the four attributes, even when a value is None, so
|
|
38
|
+
# callers can read them unconditionally.
|
|
39
|
+
with pytest.raises(NcError) as exc:
|
|
40
|
+
nc_to_dataframe("G999 X1\n") # unknown G code
|
|
41
|
+
e = exc.value
|
|
42
|
+
assert e.line == 1
|
|
43
|
+
for attr in ("line", "column", "context", "line_text"):
|
|
44
|
+
assert hasattr(e, attr)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_str_is_still_the_full_message():
|
|
48
|
+
with pytest.raises(NcError) as exc:
|
|
49
|
+
nc_to_dataframe("X=R99\n")
|
|
50
|
+
assert "Undefined variable" in str(exc.value)
|