tangential-knife-cnc 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tangential_knife_cnc-1.0.0/LICENSE +165 -0
- tangential_knife_cnc-1.0.0/PKG-INFO +380 -0
- tangential_knife_cnc-1.0.0/README.md +360 -0
- tangential_knife_cnc-1.0.0/pyproject.toml +135 -0
- tangential_knife_cnc-1.0.0/setup.cfg +4 -0
- tangential_knife_cnc-1.0.0/src/tangential_knife_cnc.egg-info/PKG-INFO +380 -0
- tangential_knife_cnc-1.0.0/src/tangential_knife_cnc.egg-info/SOURCES.txt +38 -0
- tangential_knife_cnc-1.0.0/src/tangential_knife_cnc.egg-info/dependency_links.txt +1 -0
- tangential_knife_cnc-1.0.0/src/tangential_knife_cnc.egg-info/entry_points.txt +2 -0
- tangential_knife_cnc-1.0.0/src/tangential_knife_cnc.egg-info/requires.txt +2 -0
- tangential_knife_cnc-1.0.0/src/tangential_knife_cnc.egg-info/top_level.txt +1 -0
- tangential_knife_cnc-1.0.0/src/tcnc/__init__.py +54 -0
- tangential_knife_cnc-1.0.0/src/tcnc/__main__.py +5 -0
- tangential_knife_cnc-1.0.0/src/tcnc/cli.py +455 -0
- tangential_knife_cnc-1.0.0/src/tcnc/corners.py +270 -0
- tangential_knife_cnc-1.0.0/src/tcnc/errors.py +25 -0
- tangential_knife_cnc-1.0.0/src/tcnc/gcode.py +540 -0
- tangential_knife_cnc-1.0.0/src/tcnc/jobfile.py +228 -0
- tangential_knife_cnc-1.0.0/src/tcnc/offset.py +166 -0
- tangential_knife_cnc-1.0.0/src/tcnc/options.py +656 -0
- tangential_knife_cnc-1.0.0/src/tcnc/ordering.py +64 -0
- tangential_knife_cnc-1.0.0/src/tcnc/output.py +109 -0
- tangential_knife_cnc-1.0.0/src/tcnc/plan.py +84 -0
- tangential_knife_cnc-1.0.0/src/tcnc/preview.py +190 -0
- tangential_knife_cnc-1.0.0/src/tcnc/py.typed +0 -0
- tangential_knife_cnc-1.0.0/src/tcnc/svg.py +454 -0
- tangential_knife_cnc-1.0.0/src/tcnc/toolpath.py +612 -0
- tangential_knife_cnc-1.0.0/tests/test_cli.py +165 -0
- tangential_knife_cnc-1.0.0/tests/test_corners.py +189 -0
- tangential_knife_cnc-1.0.0/tests/test_gcode.py +256 -0
- tangential_knife_cnc-1.0.0/tests/test_golden.py +73 -0
- tangential_knife_cnc-1.0.0/tests/test_job.py +410 -0
- tangential_knife_cnc-1.0.0/tests/test_offset.py +139 -0
- tangential_knife_cnc-1.0.0/tests/test_options.py +135 -0
- tangential_knife_cnc-1.0.0/tests/test_ordering.py +52 -0
- tangential_knife_cnc-1.0.0/tests/test_output.py +136 -0
- tangential_knife_cnc-1.0.0/tests/test_plan.py +79 -0
- tangential_knife_cnc-1.0.0/tests/test_preview.py +76 -0
- tangential_knife_cnc-1.0.0/tests/test_svg.py +317 -0
- tangential_knife_cnc-1.0.0/tests/test_toolpath.py +405 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
GNU LESSER GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
6
|
+
of this license document, but changing it is not allowed.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
This version of the GNU Lesser General Public License incorporates
|
|
10
|
+
the terms and conditions of version 3 of the GNU General Public
|
|
11
|
+
License, supplemented by the additional permissions listed below.
|
|
12
|
+
|
|
13
|
+
0. Additional Definitions.
|
|
14
|
+
|
|
15
|
+
As used herein, "this License" refers to version 3 of the GNU Lesser
|
|
16
|
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
|
17
|
+
General Public License.
|
|
18
|
+
|
|
19
|
+
"The Library" refers to a covered work governed by this License,
|
|
20
|
+
other than an Application or a Combined Work as defined below.
|
|
21
|
+
|
|
22
|
+
An "Application" is any work that makes use of an interface provided
|
|
23
|
+
by the Library, but which is not otherwise based on the Library.
|
|
24
|
+
Defining a subclass of a class defined by the Library is deemed a mode
|
|
25
|
+
of using an interface provided by the Library.
|
|
26
|
+
|
|
27
|
+
A "Combined Work" is a work produced by combining or linking an
|
|
28
|
+
Application with the Library. The particular version of the Library
|
|
29
|
+
with which the Combined Work was made is also called the "Linked
|
|
30
|
+
Version".
|
|
31
|
+
|
|
32
|
+
The "Minimal Corresponding Source" for a Combined Work means the
|
|
33
|
+
Corresponding Source for the Combined Work, excluding any source code
|
|
34
|
+
for portions of the Combined Work that, considered in isolation, are
|
|
35
|
+
based on the Application, and not on the Linked Version.
|
|
36
|
+
|
|
37
|
+
The "Corresponding Application Code" for a Combined Work means the
|
|
38
|
+
object code and/or source code for the Application, including any data
|
|
39
|
+
and utility programs needed for reproducing the Combined Work from the
|
|
40
|
+
Application, but excluding the System Libraries of the Combined Work.
|
|
41
|
+
|
|
42
|
+
1. Exception to Section 3 of the GNU GPL.
|
|
43
|
+
|
|
44
|
+
You may convey a covered work under sections 3 and 4 of this License
|
|
45
|
+
without being bound by section 3 of the GNU GPL.
|
|
46
|
+
|
|
47
|
+
2. Conveying Modified Versions.
|
|
48
|
+
|
|
49
|
+
If you modify a copy of the Library, and, in your modifications, a
|
|
50
|
+
facility refers to a function or data to be supplied by an Application
|
|
51
|
+
that uses the facility (other than as an argument passed when the
|
|
52
|
+
facility is invoked), then you may convey a copy of the modified
|
|
53
|
+
version:
|
|
54
|
+
|
|
55
|
+
a) under this License, provided that you make a good faith effort to
|
|
56
|
+
ensure that, in the event an Application does not supply the
|
|
57
|
+
function or data, the facility still operates, and performs
|
|
58
|
+
whatever part of its purpose remains meaningful, or
|
|
59
|
+
|
|
60
|
+
b) under the GNU GPL, with none of the additional permissions of
|
|
61
|
+
this License applicable to that copy.
|
|
62
|
+
|
|
63
|
+
3. Object Code Incorporating Material from Library Header Files.
|
|
64
|
+
|
|
65
|
+
The object code form of an Application may incorporate material from
|
|
66
|
+
a header file that is part of the Library. You may convey such object
|
|
67
|
+
code under terms of your choice, provided that, if the incorporated
|
|
68
|
+
material is not limited to numerical parameters, data structure
|
|
69
|
+
layouts and accessors, or small macros, inline functions and templates
|
|
70
|
+
(ten or fewer lines in length), you do both of the following:
|
|
71
|
+
|
|
72
|
+
a) Give prominent notice with each copy of the object code that the
|
|
73
|
+
Library is used in it and that the Library and its use are
|
|
74
|
+
covered by this License.
|
|
75
|
+
|
|
76
|
+
b) Accompany the object code with a copy of the GNU GPL and this license
|
|
77
|
+
document.
|
|
78
|
+
|
|
79
|
+
4. Combined Works.
|
|
80
|
+
|
|
81
|
+
You may convey a Combined Work under terms of your choice that,
|
|
82
|
+
taken together, effectively do not restrict modification of the
|
|
83
|
+
portions of the Library contained in the Combined Work and reverse
|
|
84
|
+
engineering for debugging such modifications, if you also do each of
|
|
85
|
+
the following:
|
|
86
|
+
|
|
87
|
+
a) Give prominent notice with each copy of the Combined Work that
|
|
88
|
+
the Library is used in it and that the Library and its use are
|
|
89
|
+
covered by this License.
|
|
90
|
+
|
|
91
|
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
|
92
|
+
document.
|
|
93
|
+
|
|
94
|
+
c) For a Combined Work that displays copyright notices during
|
|
95
|
+
execution, include the copyright notice for the Library among
|
|
96
|
+
these notices, as well as a reference directing the user to the
|
|
97
|
+
copies of the GNU GPL and this license document.
|
|
98
|
+
|
|
99
|
+
d) Do one of the following:
|
|
100
|
+
|
|
101
|
+
0) Convey the Minimal Corresponding Source under the terms of this
|
|
102
|
+
License, and the Corresponding Application Code in a form
|
|
103
|
+
suitable for, and under terms that permit, the user to
|
|
104
|
+
recombine or relink the Application with a modified version of
|
|
105
|
+
the Linked Version to produce a modified Combined Work, in the
|
|
106
|
+
manner specified by section 6 of the GNU GPL for conveying
|
|
107
|
+
Corresponding Source.
|
|
108
|
+
|
|
109
|
+
1) Use a suitable shared library mechanism for linking with the
|
|
110
|
+
Library. A suitable mechanism is one that (a) uses at run time
|
|
111
|
+
a copy of the Library already present on the user's computer
|
|
112
|
+
system, and (b) will operate properly with a modified version
|
|
113
|
+
of the Library that is interface-compatible with the Linked
|
|
114
|
+
Version.
|
|
115
|
+
|
|
116
|
+
e) Provide Installation Information, but only if you would otherwise
|
|
117
|
+
be required to provide such information under section 6 of the
|
|
118
|
+
GNU GPL, and only to the extent that such information is
|
|
119
|
+
necessary to install and execute a modified version of the
|
|
120
|
+
Combined Work produced by recombining or relinking the
|
|
121
|
+
Application with a modified version of the Linked Version. (If
|
|
122
|
+
you use option 4d0, the Installation Information must accompany
|
|
123
|
+
the Minimal Corresponding Source and Corresponding Application
|
|
124
|
+
Code. If you use option 4d1, you must provide the Installation
|
|
125
|
+
Information in the manner specified by section 6 of the GNU GPL
|
|
126
|
+
for conveying Corresponding Source.)
|
|
127
|
+
|
|
128
|
+
5. Combined Libraries.
|
|
129
|
+
|
|
130
|
+
You may place library facilities that are a work based on the
|
|
131
|
+
Library side by side in a single library together with other library
|
|
132
|
+
facilities that are not Applications and are not covered by this
|
|
133
|
+
License, and convey such a combined library under terms of your
|
|
134
|
+
choice, if you do both of the following:
|
|
135
|
+
|
|
136
|
+
a) Accompany the combined library with a copy of the same work based
|
|
137
|
+
on the Library, uncombined with any other library facilities,
|
|
138
|
+
conveyed under the terms of this License.
|
|
139
|
+
|
|
140
|
+
b) Give prominent notice with the combined library that part of it
|
|
141
|
+
is a work based on the Library, and explaining where to find the
|
|
142
|
+
accompanying uncombined form of the same work.
|
|
143
|
+
|
|
144
|
+
6. Revised Versions of the GNU Lesser General Public License.
|
|
145
|
+
|
|
146
|
+
The Free Software Foundation may publish revised and/or new versions
|
|
147
|
+
of the GNU Lesser General Public License from time to time. Such new
|
|
148
|
+
versions will be similar in spirit to the present version, but may
|
|
149
|
+
differ in detail to address new problems or concerns.
|
|
150
|
+
|
|
151
|
+
Each version is given a distinguishing version number. If the
|
|
152
|
+
Library as you received it specifies that a certain numbered version
|
|
153
|
+
of the GNU Lesser General Public License "or any later version"
|
|
154
|
+
applies to it, you have the option of following the terms and
|
|
155
|
+
conditions either of that published version or of any later version
|
|
156
|
+
published by the Free Software Foundation. If the Library as you
|
|
157
|
+
received it does not specify a version number of the GNU Lesser
|
|
158
|
+
General Public License, you may choose any version of the GNU Lesser
|
|
159
|
+
General Public License ever published by the Free Software Foundation.
|
|
160
|
+
|
|
161
|
+
If the Library as you received it specifies that a proxy can decide
|
|
162
|
+
whether future versions of the GNU Lesser General Public License shall
|
|
163
|
+
apply, that proxy's public statement of acceptance of any version is
|
|
164
|
+
permanent authorization for you to choose that version for the
|
|
165
|
+
Library.
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tangential-knife-cnc
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: SVG to LinuxCNC G-code for an oscillating tangential knife
|
|
5
|
+
Author-email: Ryan Jarvis <ryan@technoloft.com>
|
|
6
|
+
Maintainer-email: Ryan Jarvis <ryan@technoloft.com>
|
|
7
|
+
License-Expression: LGPL-3.0-or-later
|
|
8
|
+
Project-URL: Repository, https://github.com/Cabalist/tangential-knife-cnc
|
|
9
|
+
Keywords: svg,gcode,cnc,tangential knife,linuxcnc
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.14
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: tangential-knife-cnc-geometry>=1.0.0
|
|
18
|
+
Requires-Dist: svgelements>=1.9
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# tangential-knife-cnc
|
|
22
|
+
|
|
23
|
+
SVG in, LinuxCNC G-code out, for a 3.5-axis machine with an **oscillating
|
|
24
|
+
tangential knife** (the package and command are `tcnc`): X/Y position, Z depth, an A axis that keeps the blade
|
|
25
|
+
tangent to the cut, and the oscillating head switched like a spindle.
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
tcnc drawing.svg -o drawing.ngc --preview drawing-preview.svg \
|
|
29
|
+
--z-depth -1.5 --corner-angle 15 --overcut 1
|
|
30
|
+
tcnc --job box.toml # several tools: crease, cut, draw
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Everything is metric: option lengths are millimetres and the G-code is
|
|
34
|
+
written under `G21`. A job file (below) runs several tools, a knife, a
|
|
35
|
+
creasing wheel and a pen, in one program with `T n M6` tool changes.
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
- Python 3.14 or newer.
|
|
40
|
+
- A LinuxCNC-compatible controller with X, Y, Z and a rotary A axis about Z.
|
|
41
|
+
The head is switched with `M3`/`M5` (with an optional `S` word).
|
|
42
|
+
- Dependencies: [`tangential-knife-cnc-geometry`](https://github.com/Cabalist/tangential-knife-cnc-geometry)
|
|
43
|
+
(2D geometry kernel, import name `geom2d`, LGPL) and
|
|
44
|
+
[`svgelements`](https://pypi.org/project/svgelements/) (SVG parsing, MIT).
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
From PyPI, once released: `uv tool install tangential-knife-cnc` (or
|
|
49
|
+
`pipx install tangential-knife-cnc`). From a checkout, with
|
|
50
|
+
[uv](https://docs.astral.sh/uv/):
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
uv sync # library + CLI into .venv
|
|
54
|
+
uv run tcnc --help
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
or as a tool: `uv tool install .` (or `pipx install .`).
|
|
58
|
+
|
|
59
|
+
## What it does
|
|
60
|
+
|
|
61
|
+
1. **Parse.** svgelements reads the file and composes the `viewBox` and
|
|
62
|
+
every transform into a matrix per shape. Every visible path, rect,
|
|
63
|
+
circle, ellipse, line, polyline, polygon and `<use>` clone is a
|
|
64
|
+
candidate; `--id` (an element id or a clone's id) and `--layer` narrow
|
|
65
|
+
the selection. Elements with `display:none` or `visibility:hidden` are
|
|
66
|
+
skipped; opacity, clipping and masks are not considered. Malformed path
|
|
67
|
+
data is an error, not a silently missing cut. The root `<svg>` must
|
|
68
|
+
declare `width` and `height`; a physical unit there (mm, cm, in, pt,
|
|
69
|
+
pc) is converted to px exactly before parsing, so the page has the size
|
|
70
|
+
it declares and px content keeps its exact scale.
|
|
71
|
+
2. **Convert.** Every point goes through its shape's matrix exactly, then
|
|
72
|
+
is scaled to millimetres and Y-flipped so the machine origin is the
|
|
73
|
+
bottom-left corner of the page. Lines stay lines; circular arcs under a
|
|
74
|
+
rotation, uniform scale or mirror stay arcs built from their exact
|
|
75
|
+
endpoints (split to at most 90°, a nearly complete arc included);
|
|
76
|
+
elliptical arcs and arcs under a shear or non-uniform scale become
|
|
77
|
+
cubic Béziers, refined until a sampled error estimate is within
|
|
78
|
+
`--biarc-tolerance` (an error, not a guess, if 4096 cubics are not
|
|
79
|
+
enough); all Béziers then become biarcs (G2/G3) within the same
|
|
80
|
+
tolerance. A zero-radius arc is the straight line SVG defines it as and
|
|
81
|
+
an arc with identical endpoints is nothing, as SVG says; a thin ellipse
|
|
82
|
+
is still an ellipse. The loader converts faithfully; the resolution
|
|
83
|
+
policy lives in the toolpath stage: `--tolerance` is the job's
|
|
84
|
+
resolution, circular arcs are checked (and if need be repaired) against
|
|
85
|
+
their endpoints at it, a Bézier or an arc that never leaves it around
|
|
86
|
+
its chord (and turns less than a degree) is the chord, and a path
|
|
87
|
+
whose ends meet within it is closed. Runs of
|
|
88
|
+
pieces shorter than it (dense polylines, tracer noise) are replaced by
|
|
89
|
+
chords that stay within it of every vertex they replace, a run that
|
|
90
|
+
fits inside it is left out, and where those chords sample a curve that
|
|
91
|
+
is smooth at this resolution the blade heading follows the curve, so a
|
|
92
|
+
densely sampled circle is still one smooth loop and a real corner is
|
|
93
|
+
still a corner. It must be at least geom2d's numerical floor (`1e-8`);
|
|
94
|
+
the toolpath remembers it and every later stage judges coincidence at
|
|
95
|
+
the same distance.
|
|
96
|
+
3. **Order.** `--path-sort-method nearest` walks greedily from the origin,
|
|
97
|
+
reversing open paths and rotating closed ones to start at the nearest
|
|
98
|
+
vertex; `none` keeps file order.
|
|
99
|
+
4. **Blade offset** (optional). With `--blade-offset` the path is shifted
|
|
100
|
+
forward, along the blade heading, by the distance the blade edge trails
|
|
101
|
+
the axis (a chord whose heading turns along it is shifted in pieces so
|
|
102
|
+
the edge stays within `--tolerance` of the artwork); corners get a
|
|
103
|
+
small arc about the original vertex so the edge follows the artwork.
|
|
104
|
+
Each connector remembers the whole turn of the source joint it spans
|
|
105
|
+
(also after being split into 90° pieces), so a sharp corner is still a
|
|
106
|
+
lift after compensation.
|
|
107
|
+
5. **Corners.** Wherever the blade heading would change by more than
|
|
108
|
+
`--corner-angle` while cutting, the path is split and the knife lifts,
|
|
109
|
+
turns and plunges again. Every run is extended at both ends along the
|
|
110
|
+
blade heading by `--overcut` so the angled blade finishes the corner. A
|
|
111
|
+
closed shape with no sharp corner is one loop that overruns its start.
|
|
112
|
+
A closed shape keeps the start it was given when that start is already a
|
|
113
|
+
corner (nearest-neighbour ordering picks a corner when there is one).
|
|
114
|
+
6. **Passes.** `--z-step` cuts each run in several passes down to
|
|
115
|
+
`--z-depth`, lifting to `--z-safe` in between.
|
|
116
|
+
7. **Oscillation.** `--oscillation-mode operation` (or `program`, the
|
|
117
|
+
same thing) switches the head on once at the start of the operation
|
|
118
|
+
and off at its end; `cut` switches it around every plunge; `off` never
|
|
119
|
+
emits `M3`/`M5`.
|
|
120
|
+
8. **Operations and tools** (job files). Each operation cuts one
|
|
121
|
+
selection with one tool; a tool change (`T n M6` then `G43`, which
|
|
122
|
+
applies the tool table's offsets) is written whenever the tool differs
|
|
123
|
+
from the one in use. A knife and a creaser are tangential and lift at
|
|
124
|
+
corners; a pen parks the A axis once at its mounting angle, never
|
|
125
|
+
lifts at corners and writes no A words. See "Job files".
|
|
126
|
+
|
|
127
|
+
The A axis follows the cut tangent (plus `--a-offset` for the blade
|
|
128
|
+
mounting angle). At every corner and every rapid it takes the shortest
|
|
129
|
+
rotation, so its value accumulates around closed shapes. Configure the
|
|
130
|
+
A axis in LinuxCNC as an **unwrapped** rotary axis (no `WRAPPED_ROTARY`);
|
|
131
|
+
a wrapped axis rejects absolute values at or beyond ±360 and would need a
|
|
132
|
+
different encoding.
|
|
133
|
+
|
|
134
|
+
## Options
|
|
135
|
+
|
|
136
|
+
All lengths are in millimetres, times in seconds, angles in degrees.
|
|
137
|
+
|
|
138
|
+
| Group | Option | Default | Meaning |
|
|
139
|
+
|---|---|---|---|
|
|
140
|
+
| I/O | `INPUT` | | SVG file to cut |
|
|
141
|
+
| | `-o`, `--output PATH` | `INPUT.ngc` | G-code file |
|
|
142
|
+
| | `--preview PATH` | | also write an SVG preview of the cut plan |
|
|
143
|
+
| | `--id ID` | | cut only these element ids (repeatable) |
|
|
144
|
+
| | `--layer NAME` | | cut only elements inside this Inkscape layer label or group id (repeatable) |
|
|
145
|
+
| | `--flip-y` / `--no-flip-y` | on | machine origin at the bottom left |
|
|
146
|
+
| | `--gcode-comments` / `--no-gcode-comments` | on | comments in the output |
|
|
147
|
+
| | `--gcode-line-numbers` | off | `N` line numbers |
|
|
148
|
+
| | `--write-settings` | off | list every option in the header |
|
|
149
|
+
| | `--debug` | off | tracebacks on errors |
|
|
150
|
+
| Geometry | `--tolerance` | `0.01` | job resolution (mm), at least `1e-8` |
|
|
151
|
+
| | `--biarc-tolerance` | `0.01` | curve-to-biarc fit tolerance, at least `1e-8` |
|
|
152
|
+
| | `--biarc-max-depth` | `8` | curve subdivision limit (halvings per inflection-free span) |
|
|
153
|
+
| | `--output-precision` | `3` | decimals in G-code words |
|
|
154
|
+
| Machine | `--xy-feed` | `250` | XY feed, mm/min |
|
|
155
|
+
| | `--z-feed` | `250` | plunge feed, mm/min |
|
|
156
|
+
| | `--a-feed` | `60` | A feed, deg/min (used for in-place rotations) |
|
|
157
|
+
| | `--z-safe` | `10` | Z for rapids; must be above the material surface (Z0) |
|
|
158
|
+
| | `--z-depth` | `-1` | final depth, at or below the surface |
|
|
159
|
+
| | `--z-step` | `0` | depth per pass (0 = one pass) |
|
|
160
|
+
| | `--tool-wait` | `0` | dwell after plunge and lift |
|
|
161
|
+
| | `--blend-mode` | `default` | `default` (leave the controller's), `blend` (G64) or `exact` (G61) |
|
|
162
|
+
| | `--blend-tolerance` | `0` | G64 P value |
|
|
163
|
+
| Knife | `--corner-angle` | `15` | lift threshold, degrees |
|
|
164
|
+
| | `--overcut` | `0` | extension at both ends of every run |
|
|
165
|
+
| | `--blade-offset` | `0` | blade trail behind the axis (0 = off) |
|
|
166
|
+
| | `--blade-width` | `0` | blade width, for the preview's heading ticks |
|
|
167
|
+
| | `--a-offset` | `0` | blade mounting angle added to every A |
|
|
168
|
+
| | `--oscillation-mode` | `program` | `program` (alias of `operation`), `cut` or `off` |
|
|
169
|
+
| | `--spindle-speed` | `0` | `S` word for the head (0 = none) |
|
|
170
|
+
| | `--spindle-wait-on` | `0` | dwell after switching the head on |
|
|
171
|
+
| Paths | `--path-sort-method` | `none` | `none` or `nearest` |
|
|
172
|
+
|
|
173
|
+
Exit codes: `0` success, `1` bad option or usage (including an output path
|
|
174
|
+
that collides with the input or the preview, and a job file that cannot be
|
|
175
|
+
read or validated), `2` SVG problem (missing or malformed file, nothing
|
|
176
|
+
cuttable, an operation that selects nothing), `3` geometry, planning or
|
|
177
|
+
output-file failure. Outputs are published as one unit: both files are generated in
|
|
178
|
+
memory, written to unique temporary files, and only then moved into place;
|
|
179
|
+
if any step fails, files (or symlinks) already replaced are restored, so a
|
|
180
|
+
failed run leaves the previous G-code and preview exactly as they were,
|
|
181
|
+
and should a restoration itself fail the error names the backup that
|
|
182
|
+
still holds the previous content.
|
|
183
|
+
|
|
184
|
+
## Job files
|
|
185
|
+
|
|
186
|
+
`tcnc --job box.toml` runs a TOML job file. It names the tools of the
|
|
187
|
+
machine's tool table, the operations in cutting order, and optionally the
|
|
188
|
+
files; a positional SVG and `-o`/`--preview` on the command line override
|
|
189
|
+
the files, and the knife options above cannot be combined with `--job`.
|
|
190
|
+
|
|
191
|
+
```toml
|
|
192
|
+
[job] # job-wide settings; every key is optional
|
|
193
|
+
input = "box.svg"
|
|
194
|
+
z_safe = 8
|
|
195
|
+
|
|
196
|
+
[tools.knife] # one table per tool
|
|
197
|
+
kind = "knife"
|
|
198
|
+
number = 1 # T number; leave out for the tool already mounted
|
|
199
|
+
spindle_speed = 1000
|
|
200
|
+
|
|
201
|
+
[tools.creaser]
|
|
202
|
+
kind = "creaser"
|
|
203
|
+
number = 2
|
|
204
|
+
a_offset = 90 # degrees
|
|
205
|
+
corner_angle = 8 # this tool's own lift threshold
|
|
206
|
+
|
|
207
|
+
[tools.pen]
|
|
208
|
+
kind = "pen"
|
|
209
|
+
number = 3
|
|
210
|
+
|
|
211
|
+
[[operations]] # in cutting order
|
|
212
|
+
name = "crease"
|
|
213
|
+
tool = "creaser"
|
|
214
|
+
layers = ["Crease"]
|
|
215
|
+
z_depth = -0.4
|
|
216
|
+
|
|
217
|
+
[[operations]]
|
|
218
|
+
name = "cut"
|
|
219
|
+
tool = "knife"
|
|
220
|
+
layers = ["Cut"]
|
|
221
|
+
z_depth = -1.5
|
|
222
|
+
overcut = 1.0
|
|
223
|
+
|
|
224
|
+
[[operations]]
|
|
225
|
+
name = "marks"
|
|
226
|
+
tool = "pen"
|
|
227
|
+
layers = ["Marks"]
|
|
228
|
+
z_depth = -0.5
|
|
229
|
+
z_safe = 3 # per-operation safe height
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Settings resolve operation, then tool, then job, then the built-in
|
|
233
|
+
defaults. `[job]` takes `flip_y`, `tolerance`, `biarc_tolerance`,
|
|
234
|
+
`biarc_max_depth`, `output_precision`, `z_safe`, `blend_mode`,
|
|
235
|
+
`blend_tolerance`, `gcode_comments`, `gcode_line_numbers`,
|
|
236
|
+
`write_settings`, the feeds and `tool_wait`, plus `input`, `output` and
|
|
237
|
+
`preview`. A tool takes `kind`, `number`, `a_offset`, `corner_angle`,
|
|
238
|
+
`blade_offset`, `blade_width`, `oscillation`, `spindle_speed`,
|
|
239
|
+
`spindle_wait_on` and its own feed and wait defaults. An operation takes
|
|
240
|
+
`name`, `tool`, `ids`, `layers`, `z_depth`, `z_step`, `z_safe`, `overcut`,
|
|
241
|
+
`corner_angle`, `sort_method`, `oscillation_mode` and feed and wait
|
|
242
|
+
overrides. Unknown keys are errors; angles are degrees.
|
|
243
|
+
|
|
244
|
+
The tool kinds: a **knife** oscillates (`M3`/`M5`) by default, follows
|
|
245
|
+
the heading with the A axis and lifts at corners above its threshold
|
|
246
|
+
(15° by default). A **creaser** is tangential too, never oscillates, and
|
|
247
|
+
lifts at corners above its own threshold (10° by default; a wheel cannot
|
|
248
|
+
pivot in the material). A **pen** parks the A axis once at its mounting
|
|
249
|
+
angle, never lifts at corners, has no overcut, no blade offset and a
|
|
250
|
+
single pass. A tool without a number is the one already mounted; it can
|
|
251
|
+
only be used by the leading operations, since the program cannot change
|
|
252
|
+
back to it. Nothing LinuxCNC does itself is repeated: the program does
|
|
253
|
+
not move to a change position, wait for the change or set offsets by
|
|
254
|
+
hand; `G43` after `M6` applies the loaded tool's tool-table offsets, and
|
|
255
|
+
each tool is assumed to have been touched off so that Z0 is the material
|
|
256
|
+
surface. Before every change the program retracts to safe height, for
|
|
257
|
+
the first change in the coordinates active at the start (the header
|
|
258
|
+
cancels tool length compensation), so make sure that height clears the
|
|
259
|
+
material for whatever is mounted, or let `TOOL_CHANGE_QUILL_UP` handle
|
|
260
|
+
the retract. After `G43` every axis is positioned again explicitly,
|
|
261
|
+
since `M6` may have moved the machine and `G43` changes the compensated
|
|
262
|
+
coordinates. The job file itself can never be an output.
|
|
263
|
+
|
|
264
|
+
## Machine contract
|
|
265
|
+
|
|
266
|
+
- Header modes: `G17` (XY plane), `G21` (millimetres), `G90` (absolute),
|
|
267
|
+
`G94` (feed per minute), `G91.1` (arc centres relative to the start),
|
|
268
|
+
`G97` (spindle speed in RPM), `G40`, `G49`, then `G64`/`G61` only when
|
|
269
|
+
`--blend-mode` asks for it. The active work coordinate system is left as
|
|
270
|
+
the controller has it.
|
|
271
|
+
- The material surface is Z0. `--z-depth` is below it, `--z-safe` above
|
|
272
|
+
it and above every pass. Heights, the depth step and the feeds are
|
|
273
|
+
validated on the values the machine will read, i.e. after rounding to
|
|
274
|
+
`--output-precision`. Passes are planned on the grid of depths the
|
|
275
|
+
output can represent, spaced by the largest representable step not
|
|
276
|
+
above `--z-step`, so no written increment exceeds the step and no depth
|
|
277
|
+
is written twice; a step below the output resolution is rejected, and a
|
|
278
|
+
job may have at most 1000 passes.
|
|
279
|
+
- Every word is written at `--output-precision` decimals and the writer
|
|
280
|
+
tracks the rounded values, so modal suppression, arc validation and the
|
|
281
|
+
choice of feed see what the controller sees. The default feed is chosen
|
|
282
|
+
from the axes that still move after rounding (XY, else Z, else A). An
|
|
283
|
+
arc is validated the way LinuxCNC reads it: the radii at both rounded
|
|
284
|
+
ends must agree within 0.005 mm, and the directed sweep the rounded
|
|
285
|
+
words describe must be the nominal sweep (equal start and end angles
|
|
286
|
+
mean a full turn to the controller). An arc that cannot be expressed at
|
|
287
|
+
that precision (its ends collapse onto each other or onto the centre, or
|
|
288
|
+
the written sweep would differ) becomes a straight move when its chord
|
|
289
|
+
is within the output resolution of the arc, and an error otherwise. Segments that meet
|
|
290
|
+
within `--tolerance` rather than exactly are joined by the next move;
|
|
291
|
+
before an arc the writer first feeds to the arc's own start point
|
|
292
|
+
(nothing is written when the rounded words do not change).
|
|
293
|
+
- The A axis is unwrapped, as above. Comments are sanitized so no text from
|
|
294
|
+
the SVG can become a command.
|
|
295
|
+
|
|
296
|
+
## Preview
|
|
297
|
+
|
|
298
|
+
`--preview out.svg` writes a standalone SVG the size of the page, drawn the
|
|
299
|
+
way the part looks on the machine (Y up): cuts in red, lead-in and overcut
|
|
300
|
+
in light red, rapids as dashed green lines, blade-heading ticks along each
|
|
301
|
+
cut (spaced by `--blade-width`, but never more than about 2000 per plan),
|
|
302
|
+
and an orange dot wherever the knife lifts.
|
|
303
|
+
|
|
304
|
+
## Library
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
from tcnc import KnifeOptions, load_document, plan_job, write_program
|
|
308
|
+
|
|
309
|
+
opts = KnifeOptions(z_depth=-1.5, overcut=1.0, blade_offset=0.2, sort_method="nearest")
|
|
310
|
+
doc = load_document("drawing.svg", opts) # every loader setting from the options
|
|
311
|
+
plan = plan_job(doc, opts) # selection, ordering, compensation, corners, per operation
|
|
312
|
+
gcode = write_program(plan)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
A `Job` (tools plus operations) takes the place of `KnifeOptions`
|
|
316
|
+
everywhere; `KnifeOptions.to_job()` is the one-knife job, and
|
|
317
|
+
`load_job_file` reads a TOML job file. `Job.settings` resolves every
|
|
318
|
+
operation into an `OperationSettings` record; `plan_job` returns a
|
|
319
|
+
`JobPlan` of `OperationPlan`s; `plan_toolpaths(toolpaths, job)` plans
|
|
320
|
+
pre-built toolpaths under a single-operation job and `plan_cuts` builds
|
|
321
|
+
the cuts of one operation. `load_svg` is the loader with explicit keyword
|
|
322
|
+
settings; `SvgDocument.select` picks paths by id or layer. Every model
|
|
323
|
+
type is a frozen, slotted dataclass and the settings records are
|
|
324
|
+
keyword-only. `Toolpath` and `Cut` validate that their segments connect
|
|
325
|
+
within their `tolerance` (`None` means geom2d's `EPSILON`), that a closed
|
|
326
|
+
path or loop meets itself, and that arcs sweep at most 90°; `Hints`
|
|
327
|
+
rejects non-finite angles and a rotation that does not lead from its
|
|
328
|
+
start heading to its end heading. `Toolpath.from_geometry` takes the job
|
|
329
|
+
tolerance and the toolpath carries it through ordering, compensation and
|
|
330
|
+
corner planning into every `Cut`. Errors are `ValueError` subclasses:
|
|
331
|
+
`OptionError`, `SvgError`, `PlanError`, `OutputError` (also from
|
|
332
|
+
`write_preview`), plus `geom2d.GeometryError`.
|
|
333
|
+
|
|
334
|
+
## Dependencies
|
|
335
|
+
|
|
336
|
+
- [`tangential-knife-cnc-geometry`](https://github.com/Cabalist/tangential-knife-cnc-geometry)
|
|
337
|
+
1.0 (import name `geom2d`): the 2D geometry kernel. tcnc relies on `P`, `Line`, `Arc`, `CubicBezier`, the
|
|
338
|
+
`Segment` protocol and `Path` helpers (`path_length`, `path_bounding_box`,
|
|
339
|
+
`path_is_closed`), `Arc.from_sweep` and its construction invariant,
|
|
340
|
+
`split_max_sweep`, `biarc_approximation`, `calc_rotation`,
|
|
341
|
+
`normalize_angle`, `angle_eq`, `segments_are_g1`, and the
|
|
342
|
+
`GeometryError` hierarchy. Its dataclasses are frozen and hashable with
|
|
343
|
+
positional field order `Line(p1, p2)` and `Arc(p1, p2, radius, angle,
|
|
344
|
+
center)`, which the G-code writer matches on. `angle` is the signed
|
|
345
|
+
sweep in radians, CCW positive. geom2d's `EPSILON` (`1e-8`) is only its
|
|
346
|
+
numerical floor; the job's own resolution is passed explicitly through
|
|
347
|
+
the `tolerance` keywords of `Arc.from_sweep`, `P.almost_equal`,
|
|
348
|
+
`path_is_closed` and `segments_are_g1`, and both tolerances given to
|
|
349
|
+
`KnifeOptions` must be at least `EPSILON` (`biarc_approximation` rejects
|
|
350
|
+
anything finer).
|
|
351
|
+
- [`svgelements`](https://pypi.org/project/svgelements/): parsing only.
|
|
352
|
+
tcnc reads unreified shapes and applies each shape's matrix itself.
|
|
353
|
+
|
|
354
|
+
## Development
|
|
355
|
+
|
|
356
|
+
```sh
|
|
357
|
+
uv sync --all-groups
|
|
358
|
+
uv run prek run --all-files # ruff check + format, ty, pyrefly
|
|
359
|
+
uv run pytest # unit, fixture and golden tests
|
|
360
|
+
TCNC_UPDATE_GOLDEN=1 uv run pytest tests/test_golden.py # regenerate goldens on purpose
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
`stubs/svgelements/` holds the type stubs the checkers use for svgelements
|
|
364
|
+
(its source is ISO-8859-1 encoded and unreadable to them); keep the stubs
|
|
365
|
+
in step with what `src/tcnc/svg.py` uses. The hooks run `uv run --locked`,
|
|
366
|
+
so they fail rather than resolve or change dependencies. The geometry
|
|
367
|
+
kernel comes from PyPI (`tangential-knife-cnc-geometry`); to work against
|
|
368
|
+
a local checkout of it, add a `[tool.uv.sources]` path entry locally and
|
|
369
|
+
do not commit it. CI runs the checks, builds the wheel and installs it
|
|
370
|
+
into a clean environment; publishing runs the same steps before building.
|
|
371
|
+
Dependabot proposes weekly, grouped updates for the actions and for the
|
|
372
|
+
uv lock (runtime dependencies and tooling separately).
|
|
373
|
+
|
|
374
|
+
## Licence and provenance
|
|
375
|
+
|
|
376
|
+
LGPL-3.0-or-later. Rewritten in 2026 from
|
|
377
|
+
[utlco/utl-tcnc](https://github.com/utlco/utl-tcnc) by Claude Zervas, which
|
|
378
|
+
was an Inkscape extension for a brush and knife machine; this version drops
|
|
379
|
+
Inkscape support and the brush features, and targets the oscillating knife
|
|
380
|
+
only. See `CHANGELOG.md`.
|