@chestnutlabs/gcode-parser 0.3.0 → 0.5.0
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.
- package/dist/growable.d.ts +16 -1
- package/dist/growable.d.ts.map +1 -1
- package/dist/growable.js +38 -5
- package/dist/parse.d.ts +8 -0
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +395 -195
- package/dist/worker.js +6 -2
- package/package.json +5 -5
package/dist/growable.d.ts
CHANGED
|
@@ -28,6 +28,9 @@ export interface SegmentRecord {
|
|
|
28
28
|
tool: number;
|
|
29
29
|
layer: number;
|
|
30
30
|
srcByte: number;
|
|
31
|
+
/** Current value of each requested opt-in modal channel (DD-012 D3), keyed by channel id.
|
|
32
|
+
* Missing/undefined entries store NaN — an honest "no value here", never a fabricated 0. */
|
|
33
|
+
modal?: Record<string, number>;
|
|
31
34
|
}
|
|
32
35
|
export interface FinalChannels {
|
|
33
36
|
count: number;
|
|
@@ -45,6 +48,9 @@ export interface FinalChannels {
|
|
|
45
48
|
feature: Uint8Array;
|
|
46
49
|
object: Uint32Array;
|
|
47
50
|
srcByte: Uint32Array;
|
|
51
|
+
/** Opt-in modal channels (DD-012 D3), present only when requested at construction. Each is a
|
|
52
|
+
* Float32 column of length `count`; an unset value is NaN, never a fabricated 0. */
|
|
53
|
+
modal?: Record<string, Float32Array>;
|
|
48
54
|
}
|
|
49
55
|
export declare class SegmentWriter {
|
|
50
56
|
private readonly maxBufferBytes;
|
|
@@ -68,7 +74,13 @@ export declare class SegmentWriter {
|
|
|
68
74
|
private accountedBytes;
|
|
69
75
|
/** Extra live bytes the caller wants included in the budget (line buffer, warnings). */
|
|
70
76
|
private externalBytes;
|
|
71
|
-
|
|
77
|
+
/** Opt-in modal channel ids (DD-012 D3), stable order; empty for a default parse. */
|
|
78
|
+
private readonly modalNames;
|
|
79
|
+
/** Growable Float32 columns, parallel to `modalNames`. */
|
|
80
|
+
private modalArrays;
|
|
81
|
+
/** Bytes per segment across the core set plus any requested modal columns. */
|
|
82
|
+
private readonly bytesPerSegment;
|
|
83
|
+
constructor(maxBufferBytes: number, modalChannels?: readonly string[]);
|
|
72
84
|
get count(): number;
|
|
73
85
|
get accounted(): number;
|
|
74
86
|
/** Report additional live working bytes (e.g., the line-drain buffer) into the budget. */
|
|
@@ -82,6 +94,9 @@ export declare class SegmentWriter {
|
|
|
82
94
|
setLayerRange(start: number, end: number, layerIndex: number): void;
|
|
83
95
|
/** Zero the whole layer channel (non-planar clearing semantics). */
|
|
84
96
|
clearLayers(): void;
|
|
97
|
+
/** Build the optional `modal` output record (DD-012 D3), or `{}` when no channels were requested
|
|
98
|
+
* so a default parse yields no `modal` key at all. `view` slices/subarrays each column to count. */
|
|
99
|
+
private modalOutput;
|
|
85
100
|
/**
|
|
86
101
|
* Copy a segment range into fresh right-sized arrays (progressive-preview
|
|
87
102
|
* snapshots, DD-004 §5.4 / issue #60). Budget-checked: the copies coexist with
|
package/dist/growable.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"growable.d.ts","sourceRoot":"","sources":["../src/growable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,cAAc,EAAE,MAAM;aACtB,cAAc,EAAE,MAAM;aACtB,cAAc,EAAE,MAAM;gBAFtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM;CAOzC;
|
|
1
|
+
{"version":3,"file":"growable.d.ts","sourceRoot":"","sources":["../src/growable.ts"],"names":[],"mappings":"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"}
|
package/dist/growable.js
CHANGED
|
@@ -37,7 +37,8 @@ const CHANNELS = [
|
|
|
37
37
|
{ name: 'object', bytesPerElement: 4 },
|
|
38
38
|
{ name: 'srcByte', bytesPerElement: 4 }
|
|
39
39
|
];
|
|
40
|
-
const
|
|
40
|
+
const CORE_BYTES_PER_SEGMENT = CHANNELS.reduce((a, c) => a + c.bytesPerElement, 0); // 40 B core set
|
|
41
|
+
const MODAL_BYTES_PER_ELEMENT = 4; // each opt-in modal channel is one Float32 column
|
|
41
42
|
const INITIAL_CAPACITY = 4096;
|
|
42
43
|
export class SegmentWriter {
|
|
43
44
|
maxBufferBytes;
|
|
@@ -61,8 +62,16 @@ export class SegmentWriter {
|
|
|
61
62
|
accountedBytes = 0;
|
|
62
63
|
/** Extra live bytes the caller wants included in the budget (line buffer, warnings). */
|
|
63
64
|
externalBytes = 0;
|
|
64
|
-
|
|
65
|
+
/** Opt-in modal channel ids (DD-012 D3), stable order; empty for a default parse. */
|
|
66
|
+
modalNames;
|
|
67
|
+
/** Growable Float32 columns, parallel to `modalNames`. */
|
|
68
|
+
modalArrays = [];
|
|
69
|
+
/** Bytes per segment across the core set plus any requested modal columns. */
|
|
70
|
+
bytesPerSegment;
|
|
71
|
+
constructor(maxBufferBytes, modalChannels = []) {
|
|
65
72
|
this.maxBufferBytes = maxBufferBytes;
|
|
73
|
+
this.modalNames = modalChannels;
|
|
74
|
+
this.bytesPerSegment = CORE_BYTES_PER_SEGMENT + MODAL_BYTES_PER_ELEMENT * modalChannels.length;
|
|
66
75
|
this.allocate(INITIAL_CAPACITY);
|
|
67
76
|
}
|
|
68
77
|
get count() {
|
|
@@ -83,7 +92,7 @@ export class SegmentWriter {
|
|
|
83
92
|
}
|
|
84
93
|
allocate(newCapacity) {
|
|
85
94
|
// During a grow/compact, old and new capacity coexist until copies complete.
|
|
86
|
-
const newBytes = newCapacity *
|
|
95
|
+
const newBytes = newCapacity * this.bytesPerSegment;
|
|
87
96
|
this.ensureBudget(newBytes);
|
|
88
97
|
const nx0 = new Float32Array(newCapacity);
|
|
89
98
|
const ny0 = new Float32Array(newCapacity);
|
|
@@ -99,6 +108,13 @@ export class SegmentWriter {
|
|
|
99
108
|
const nfeature = new Uint8Array(newCapacity);
|
|
100
109
|
const nobject = new Uint32Array(newCapacity);
|
|
101
110
|
const nsrc = new Uint32Array(newCapacity);
|
|
111
|
+
// Opt-in modal columns grow in lockstep with the core set.
|
|
112
|
+
const nmodal = this.modalNames.map((_, k) => {
|
|
113
|
+
const a = new Float32Array(newCapacity);
|
|
114
|
+
if (this.capacity > 0)
|
|
115
|
+
a.set(this.modalArrays[k].subarray(0, this._count));
|
|
116
|
+
return a;
|
|
117
|
+
});
|
|
102
118
|
if (this.capacity > 0) {
|
|
103
119
|
nx0.set(this.x0.subarray(0, this._count));
|
|
104
120
|
ny0.set(this.y0.subarray(0, this._count));
|
|
@@ -115,7 +131,8 @@ export class SegmentWriter {
|
|
|
115
131
|
nobject.set(this.object.subarray(0, this._count));
|
|
116
132
|
nsrc.set(this.srcByte.subarray(0, this._count));
|
|
117
133
|
}
|
|
118
|
-
const oldBytes = this.capacity *
|
|
134
|
+
const oldBytes = this.capacity * this.bytesPerSegment;
|
|
135
|
+
this.modalArrays = nmodal;
|
|
119
136
|
this.x0 = nx0;
|
|
120
137
|
this.y0 = ny0;
|
|
121
138
|
this.z0 = nz0;
|
|
@@ -153,6 +170,10 @@ export class SegmentWriter {
|
|
|
153
170
|
this.feature[i] = 0;
|
|
154
171
|
this.object[i] = 0;
|
|
155
172
|
this.srcByte[i] = s.srcByte;
|
|
173
|
+
for (let k = 0; k < this.modalNames.length; k++) {
|
|
174
|
+
const v = s.modal?.[this.modalNames[k]];
|
|
175
|
+
this.modalArrays[k][i] = v === undefined ? NaN : v;
|
|
176
|
+
}
|
|
156
177
|
this._count++;
|
|
157
178
|
}
|
|
158
179
|
/** Overwrite the layer channel for a segment range (used for post-hoc layer resolution). */
|
|
@@ -163,6 +184,16 @@ export class SegmentWriter {
|
|
|
163
184
|
clearLayers() {
|
|
164
185
|
this.layer.fill(0, 0, this._count);
|
|
165
186
|
}
|
|
187
|
+
/** Build the optional `modal` output record (DD-012 D3), or `{}` when no channels were requested
|
|
188
|
+
* so a default parse yields no `modal` key at all. `view` slices/subarrays each column to count. */
|
|
189
|
+
modalOutput(view) {
|
|
190
|
+
if (this.modalNames.length === 0)
|
|
191
|
+
return {};
|
|
192
|
+
const modal = {};
|
|
193
|
+
for (let k = 0; k < this.modalNames.length; k++)
|
|
194
|
+
modal[this.modalNames[k]] = view(this.modalArrays[k]);
|
|
195
|
+
return { modal };
|
|
196
|
+
}
|
|
166
197
|
/**
|
|
167
198
|
* Copy a segment range into fresh right-sized arrays (progressive-preview
|
|
168
199
|
* snapshots, DD-004 §5.4 / issue #60). Budget-checked: the copies coexist with
|
|
@@ -172,9 +203,10 @@ export class SegmentWriter {
|
|
|
172
203
|
*/
|
|
173
204
|
snapshotRange(start, end) {
|
|
174
205
|
const count = Math.max(0, end - start);
|
|
175
|
-
this.ensureBudget(count *
|
|
206
|
+
this.ensureBudget(count * this.bytesPerSegment);
|
|
176
207
|
return {
|
|
177
208
|
count,
|
|
209
|
+
...this.modalOutput((a) => a.slice(start, end)),
|
|
178
210
|
x0: this.x0.slice(start, end),
|
|
179
211
|
y0: this.y0.slice(start, end),
|
|
180
212
|
z0: this.z0.slice(start, end),
|
|
@@ -198,6 +230,7 @@ export class SegmentWriter {
|
|
|
198
230
|
}
|
|
199
231
|
return {
|
|
200
232
|
count: this._count,
|
|
233
|
+
...this.modalOutput((a) => a.subarray(0, this._count)),
|
|
201
234
|
x0: this.x0.subarray(0, this._count),
|
|
202
235
|
y0: this.y0.subarray(0, this._count),
|
|
203
236
|
z0: this.z0.subarray(0, this._count),
|
package/dist/parse.d.ts
CHANGED
|
@@ -63,6 +63,14 @@ export interface ParseOptions {
|
|
|
63
63
|
* that has confidently classified the firmware. The byte-exact engine never sniffs firmware itself.
|
|
64
64
|
*/
|
|
65
65
|
extruderFollowsPositioning?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Opt-in modal channels to capture per segment (DD-012 D3, #189). Each requested id adds one
|
|
68
|
+
* Float32 column to the IR (`segments.modal[id]`); a default parse requests none and pays nothing,
|
|
69
|
+
* so FDM output is unchanged. Supported id: `'toolPower'` — the modal spindle/laser `S` value while
|
|
70
|
+
* a tool is engaged (`M3`/`M4`), NaN when the tool is off. Presentation (Watts vs RPM) is a dialect
|
|
71
|
+
* label, not a separate channel (DD-012 D4). Fan/temp/accel etc. (#180) reuse the same mechanism.
|
|
72
|
+
*/
|
|
73
|
+
modalChannels?: readonly string[];
|
|
66
74
|
/**
|
|
67
75
|
* DD-005 §4.3 read-only hooks: observe comments/commands during the parse.
|
|
68
76
|
* Inert when unset (one branch per line); they cannot alter lexing, dispatch,
|
package/dist/parse.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
|
package/dist/parse.js
CHANGED
|
@@ -38,29 +38,61 @@ export const DEFAULT_LIMITS = {
|
|
|
38
38
|
};
|
|
39
39
|
const LAYER_TOLERANCE = 0.05;
|
|
40
40
|
const UNRESOLVED_LAYER = 0xffffffff;
|
|
41
|
+
/** Opt-in modal channels this engine can capture (DD-012 D3, #189). Requested via
|
|
42
|
+
* `ParseOptions.modalChannels`; unknown ids are ignored with a warning. Extend as later phases
|
|
43
|
+
* (spindle/laser already covered by `toolPower`; #180's fan/temp/accel land here next). */
|
|
44
|
+
const SUPPORTED_MODAL_CHANNELS = new Set(['toolPower']);
|
|
41
45
|
const SPLIT_LETTERS = /([a-zA-Z])/g;
|
|
42
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Lex a line into its G/M/T command words plus the axis/param words. Real CNC G-code routinely puts
|
|
48
|
+
* several commands on one line and prefixes lines with `N` line numbers; both are handled here.
|
|
49
|
+
* `N`-words (line numbers) are dropped, and non-command letters become params. FDM lines have a single
|
|
50
|
+
* command word, so `codes = [thatCommand]` and behavior is byte-identical to the inherited lexer.
|
|
51
|
+
*/
|
|
43
52
|
function lexLine(line) {
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const parts = cmd
|
|
53
|
+
const body = line.trim().split(';')[0];
|
|
54
|
+
const parts = body
|
|
47
55
|
.split(SPLIT_LETTERS)
|
|
48
56
|
.slice(1)
|
|
49
57
|
.map((s) => s.trim());
|
|
50
|
-
const
|
|
51
|
-
// Faithful replication of the inherited parseParams reduce (odd indices are values):
|
|
58
|
+
const codes = [];
|
|
52
59
|
const params = {};
|
|
53
|
-
|
|
54
|
-
for (let
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
let sawCommand = false;
|
|
61
|
+
for (let i = 0; i + 1 < parts.length; i += 2) {
|
|
62
|
+
const key = parts[i].toLowerCase();
|
|
63
|
+
const cc = key.charCodeAt(0);
|
|
64
|
+
if (cc < 97 || cc > 122)
|
|
65
|
+
continue; // not a letter
|
|
66
|
+
const value = parts[i + 1];
|
|
67
|
+
// A real G-code word is a letter followed by a NUMBER. This guard rejects letters embedded in
|
|
68
|
+
// extended-command words (Klipper `EXCLUDE_OBJECT … POLYGON=…`, `CENTER=`, `NAME=`) where a letter
|
|
69
|
+
// is followed by another letter or `=…` — the inherited lexer skipped these by only reading the
|
|
70
|
+
// first word; scanning every letter must not resurrect them as commands/params.
|
|
71
|
+
const num = value.trim() === '' ? NaN : Number(value);
|
|
72
|
+
if (key === 'g' || key === 'm') {
|
|
73
|
+
if (Number.isFinite(num)) {
|
|
74
|
+
codes.push(`${key}${num}`); // e.g. 'g1', 'm3' — leading zeros normalized
|
|
75
|
+
sawCommand = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (key === 't' && !sawCommand) {
|
|
79
|
+
// `T` is a tool-select command only as the LEADING command word (`T1 M6`). After an M-code it is
|
|
80
|
+
// that code's parameter, NOT a tool change — e.g. Marlin `M486 T<count>`, `M104 T<tool> S<temp>`.
|
|
81
|
+
if (Number.isFinite(num)) {
|
|
82
|
+
codes.push(`t${num}`);
|
|
83
|
+
sawCommand = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (key === 'n') {
|
|
87
|
+
// Line number — not a command, not a motion word. Ignored (fixes Fanuc/Mach/TinyG files).
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const p = parseFloat(value);
|
|
91
|
+
if (Number.isFinite(p))
|
|
92
|
+
params[key] = p;
|
|
61
93
|
}
|
|
62
94
|
}
|
|
63
|
-
return {
|
|
95
|
+
return { codes, params };
|
|
64
96
|
}
|
|
65
97
|
/** Internal: build an engine over decoded text (streaming passes '' and feeds lines itself). */
|
|
66
98
|
export function createEngine(input, opts) {
|
|
@@ -92,7 +124,18 @@ export function createEngine(input, opts) {
|
|
|
92
124
|
warnings.set(code, { code, message, severity: 'warn', srcByte, count: 1 });
|
|
93
125
|
}
|
|
94
126
|
};
|
|
95
|
-
|
|
127
|
+
// Opt-in modal channels (DD-012 D3): dedupe, keep supported, warn (once) on any unknown id.
|
|
128
|
+
const requestedModal = [];
|
|
129
|
+
for (const id of opts.modalChannels ?? []) {
|
|
130
|
+
if (!SUPPORTED_MODAL_CHANNELS.has(id)) {
|
|
131
|
+
warn('modal-channel-unsupported', `Ignoring unsupported modal channel '${id}'.`);
|
|
132
|
+
}
|
|
133
|
+
else if (!requestedModal.includes(id)) {
|
|
134
|
+
requestedModal.push(id);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const wantToolPower = requestedModal.includes('toolPower');
|
|
138
|
+
const writer = new SegmentWriter(limits.maxBufferBytes, requestedModal);
|
|
96
139
|
// Machine state (port of State.initial + Interpreter fields).
|
|
97
140
|
let sx = 0;
|
|
98
141
|
let sy = 0;
|
|
@@ -126,6 +169,23 @@ export function createEngine(input, opts) {
|
|
|
126
169
|
let g92z = 0;
|
|
127
170
|
let coordSystemSeen = false; // any G53/G54–G59/G92-XYZ/G10 → coordinateSystem 'known'
|
|
128
171
|
let g53OneShot = false; // next move ignores the work offset (machine coordinates)
|
|
172
|
+
// Tool-state modal (DD-012 D2/D4, #189). Spindle/laser on (M3/M4) engages the tool; M5 disengages.
|
|
173
|
+
// A FEED move (G1/G2/G3) with no extrusion E while the tool is engaged is a Cut (productive); a
|
|
174
|
+
// rapid (G0) stays Travel even with the tool engaged (refined #189). FDM slices never issue M3/M4,
|
|
175
|
+
// so toolEngaged stays false and their classification is byte-identical.
|
|
176
|
+
let toolEngaged = false;
|
|
177
|
+
let toolStateSeen = false; // any M3/M4/M5 observed → cutMoves capability 'known'
|
|
178
|
+
let modalS = NaN; // current modal spindle/laser S value (power / RPM); NaN until first seen (#189)
|
|
179
|
+
let modalMotion = null; // last G0–G3 mode for modal-motion lines (#189)
|
|
180
|
+
// Canned drilling cycle state (DD-012 phase 2, #189). Z/R/Q/initial-plane are captured when the
|
|
181
|
+
// cycle is defined and retained across modal (bare X/Y) repeats until G80 or a G0–G3 cancels it.
|
|
182
|
+
let cannedCycle = null;
|
|
183
|
+
let cannedZ = 0; // final drill depth (absolute)
|
|
184
|
+
let cannedR = 0; // retract / reference plane (absolute Z)
|
|
185
|
+
let cannedQ = 0; // peck increment (G83)
|
|
186
|
+
let cannedInitialZ = 0; // Z when the cycle activated — the G98 retract plane
|
|
187
|
+
let cannedRetractInitial = true; // G98 (retract to initial Z) vs G99 (retract to R); modal, default G98
|
|
188
|
+
let cannedCyclesSeen = false; // any G81/G82/G83 expanded → cannedCycles capability 'known'
|
|
129
189
|
// Per-axis position certainty (DD-010 D4 amendment, #158). A G31 probe endpoint is reached at
|
|
130
190
|
// RUNTIME (workpiece contact), not the commanded value, so it marks the probed axes uncertain. A
|
|
131
191
|
// following G92 then RESYNCS the logical frame at the datum (rather than datum-shifting a stale
|
|
@@ -214,7 +274,10 @@ export function createEngine(input, opts) {
|
|
|
214
274
|
kind,
|
|
215
275
|
tool: path?.tool ?? tool,
|
|
216
276
|
layer: UNRESOLVED_LAYER,
|
|
217
|
-
srcByte: currentSrcByte
|
|
277
|
+
srcByte: currentSrcByte,
|
|
278
|
+
// toolPower is the modal S while a tool is engaged; NaN when off (M5 / never engaged) — an
|
|
279
|
+
// honest "no cutting power here", not a fabricated 0. Only built when requested (DD-012 D3).
|
|
280
|
+
modal: wantToolPower ? { toolPower: toolEngaged ? modalS : NaN } : undefined
|
|
218
281
|
});
|
|
219
282
|
}
|
|
220
283
|
catch (err) {
|
|
@@ -308,7 +371,10 @@ export function createEngine(input, opts) {
|
|
|
308
371
|
prevY = sy;
|
|
309
372
|
prevZ = sz;
|
|
310
373
|
};
|
|
311
|
-
|
|
374
|
+
// Map a resolved path type to its MoveKind bit (DD-012 D2). `cut` only ever arises on a feed move
|
|
375
|
+
// when the tool is engaged and E is absent, so FDM (never `cut`) is byte-identical.
|
|
376
|
+
const kindForPath = (t) => t === 'extrusion' ? MoveKind.Extrude : t === 'cut' ? MoveKind.Cut : MoveKind.Travel;
|
|
377
|
+
const g0 = (p, rapid) => {
|
|
312
378
|
const { x, y, z, e, f } = p;
|
|
313
379
|
// Classify on the true per-move delta, not the raw E word (DD-010 D1). For the M83/relative
|
|
314
380
|
// corpus the delta equals the word, so output is byte-identical to the pre-E10 engine.
|
|
@@ -339,7 +405,10 @@ export function createEngine(input, opts) {
|
|
|
339
405
|
stats.points++;
|
|
340
406
|
if (f !== undefined)
|
|
341
407
|
modalFeed = f;
|
|
342
|
-
|
|
408
|
+
// A rapid (G0) is a non-cutting positioning traverse — Travel even while the tool is engaged
|
|
409
|
+
// (a router keeps its spindle on during rapids; a GRBL-laser gates the beam off during G0). Only
|
|
410
|
+
// a FEED move (G1/G2/G3) with the tool engaged and no extrusion is a Cut (DD-012 D2, refined #189).
|
|
411
|
+
const pathType = eDelta > 0 ? 'extrusion' : toolEngaged && !rapid ? 'cut' : 'travel';
|
|
343
412
|
if (path === null || path.type !== pathType) {
|
|
344
413
|
breakPath(pathType);
|
|
345
414
|
}
|
|
@@ -358,7 +427,7 @@ export function createEngine(input, opts) {
|
|
|
358
427
|
certainZ = true;
|
|
359
428
|
}
|
|
360
429
|
g53OneShot = false; // one-shot machine-coordinate bypass consumed by this move
|
|
361
|
-
return emitSegment(sx, sy, sz, eDelta, pathType
|
|
430
|
+
return emitSegment(sx, sy, sz, eDelta, kindForPath(pathType));
|
|
362
431
|
};
|
|
363
432
|
const g2 = (p, cw) => {
|
|
364
433
|
const { e, f } = p;
|
|
@@ -367,7 +436,7 @@ export function createEngine(input, opts) {
|
|
|
367
436
|
modalFeed = f;
|
|
368
437
|
// E is delta-based (DD-010 D1) so M82 arcs classify correctly and `lastE` stays consistent with g0.
|
|
369
438
|
const eDelta = resolveEDelta(e);
|
|
370
|
-
const pathType = eDelta ? 'extrusion' : 'travel';
|
|
439
|
+
const pathType = eDelta ? 'extrusion' : toolEngaged ? 'cut' : 'travel';
|
|
371
440
|
if (path === null || path.type !== pathType) {
|
|
372
441
|
breakPath(pathType);
|
|
373
442
|
}
|
|
@@ -427,7 +496,7 @@ export function createEngine(input, opts) {
|
|
|
427
496
|
// Through-axis linear ramp — preserves the inherited step (byte-identical for planar XY arcs,
|
|
428
497
|
// where the through axis is unchanged so the ramp is flat).
|
|
429
498
|
const cStep = (sc - tc) / totalSegments;
|
|
430
|
-
const kind = (pathType
|
|
499
|
+
const kind = kindForPath(pathType) | MoveKind.ArcSegment;
|
|
431
500
|
const eachE = e !== undefined ? eDelta / Math.max(1, Math.ceil(totalSegments)) : 0;
|
|
432
501
|
// Map an in-plane point (pa,pb) + through-axis pc back to (x,y,z) for the active plane.
|
|
433
502
|
const emitArc = (pa, pb, pcv) => {
|
|
@@ -449,6 +518,46 @@ export function createEngine(input, opts) {
|
|
|
449
518
|
sz = tz;
|
|
450
519
|
return emitArc(ta, tb, tc);
|
|
451
520
|
};
|
|
521
|
+
// Canned drilling cycle (DD-012 phase 2, #189): expand G81/G82/G83 into explicit sub-moves so the
|
|
522
|
+
// holes are real geometry (today they emit nothing). Rapids are Travel; the plunge(s) are Cut. Z/R/Q
|
|
523
|
+
// and the initial plane are captured when the cycle is defined and retained across modal repeats.
|
|
524
|
+
const runCannedCycle = (p) => {
|
|
525
|
+
// 1. Rapid to the hole XY at the current Z.
|
|
526
|
+
sx = nextAxis(sx, p.x, offX());
|
|
527
|
+
sy = nextAxis(sy, p.y, offY());
|
|
528
|
+
g53OneShot = false;
|
|
529
|
+
if (!emitSegment(sx, sy, sz, 0, MoveKind.Travel))
|
|
530
|
+
return false;
|
|
531
|
+
// 2. Rapid Z down to the R (reference) plane.
|
|
532
|
+
sz = cannedR;
|
|
533
|
+
if (!emitSegment(sx, sy, sz, 0, MoveKind.Travel))
|
|
534
|
+
return false;
|
|
535
|
+
// 3. Feed to depth — one plunge (G81/G82) or a peck loop (G83). Productive → Cut.
|
|
536
|
+
if (cannedCycle === 'g83' && cannedQ > 0 && cannedR > cannedZ) {
|
|
537
|
+
let depth = cannedR;
|
|
538
|
+
while (depth > cannedZ) {
|
|
539
|
+
depth = Math.max(cannedZ, depth - cannedQ);
|
|
540
|
+
sz = depth;
|
|
541
|
+
if (!emitSegment(sx, sy, sz, 0, MoveKind.Cut))
|
|
542
|
+
return false; // feed down one peck
|
|
543
|
+
if (depth > cannedZ) {
|
|
544
|
+
sz = cannedR;
|
|
545
|
+
if (!emitSegment(sx, sy, sz, 0, MoveKind.Travel))
|
|
546
|
+
return false; // rapid retract to R between pecks
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
sz = cannedZ;
|
|
552
|
+
if (!emitSegment(sx, sy, sz, 0, MoveKind.Cut))
|
|
553
|
+
return false; // single plunge
|
|
554
|
+
}
|
|
555
|
+
// 4. Retract (rapid) to the initial plane (G98) or the R plane (G99).
|
|
556
|
+
sz = cannedRetractInitial ? cannedInitialZ : cannedR;
|
|
557
|
+
if (!emitSegment(sx, sy, sz, 0, MoveKind.Travel))
|
|
558
|
+
return false;
|
|
559
|
+
return true;
|
|
560
|
+
};
|
|
452
561
|
const onComment = opts.onComment;
|
|
453
562
|
const onCommand = opts.onCommand;
|
|
454
563
|
const processLine = (rawLine, offset) => {
|
|
@@ -465,188 +574,266 @@ export function createEngine(input, opts) {
|
|
|
465
574
|
onComment(rawLine.slice(ci + 1), offset);
|
|
466
575
|
}
|
|
467
576
|
const cmd = lexLine(rawLine);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (
|
|
472
|
-
|
|
577
|
+
// Skip only genuinely empty lines. A line with no lexable words but real content (a dialect
|
|
578
|
+
// extended command like `EXCLUDE_OBJECT_START NAME=cube`, a macro) must NOT be dropped — the
|
|
579
|
+
// observer below still forwards its raw line. The body split runs only when there are no words.
|
|
580
|
+
if (cmd.codes.length === 0 && Object.keys(cmd.params).length === 0 && rawLine.split(';')[0].trim() === '') {
|
|
581
|
+
return; // blank or comment-only line
|
|
473
582
|
}
|
|
583
|
+
stats.commands++;
|
|
584
|
+
// Modal S/F latch from params on ANY line — including bare `S1000` / `F600` lines that carry no
|
|
585
|
+
// command word (common in GRBL-laser output). S is captured for toolPower only when requested.
|
|
586
|
+
if (wantToolPower && cmd.params.s !== undefined)
|
|
587
|
+
modalS = cmd.params.s;
|
|
588
|
+
if (cmd.params.f !== undefined)
|
|
589
|
+
modalFeed = cmd.params.f;
|
|
474
590
|
let ok = true;
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
case 'g3':
|
|
484
|
-
ok = g2(cmd.params, false);
|
|
485
|
-
break;
|
|
486
|
-
case 'g20':
|
|
487
|
-
units = 'in';
|
|
488
|
-
unitsSeen = true;
|
|
489
|
-
break;
|
|
490
|
-
case 'g21':
|
|
491
|
-
units = 'mm';
|
|
492
|
-
unitsSeen = true;
|
|
493
|
-
break;
|
|
494
|
-
case 'g28':
|
|
495
|
-
sx = 0;
|
|
496
|
-
sy = 0;
|
|
497
|
-
sz = 0;
|
|
498
|
-
break;
|
|
499
|
-
case 't0':
|
|
500
|
-
case 't1':
|
|
501
|
-
case 't2':
|
|
502
|
-
case 't3':
|
|
503
|
-
case 't4':
|
|
504
|
-
case 't5':
|
|
505
|
-
case 't6':
|
|
506
|
-
case 't7':
|
|
507
|
-
tool = Number(cmd.gcode.slice(1));
|
|
508
|
-
break;
|
|
509
|
-
case 'm600':
|
|
510
|
-
// Manual filament swap = color boundary (#147). A marker, not motion — do
|
|
511
|
-
// NOT break/finish the current path; record position (current head), the
|
|
512
|
-
// next segment index (slot boundary), and the active tool for provenance.
|
|
513
|
-
colorChangeEvents.push({ x: sx, y: sy, z: sz, segIndex: writer.count, srcByte: offset, tool });
|
|
514
|
-
break;
|
|
515
|
-
// Motion-model modal commands (DD-010 E10 phase 1).
|
|
516
|
-
case 'g90':
|
|
517
|
-
xyzAbsolute = true;
|
|
518
|
-
positioningSeen = true;
|
|
519
|
-
break;
|
|
520
|
-
case 'g91':
|
|
521
|
-
xyzAbsolute = false;
|
|
522
|
-
positioningSeen = true;
|
|
523
|
-
break;
|
|
524
|
-
// Arc-plane selection (DD-010 D3, #157, E10 phase 2).
|
|
525
|
-
case 'g17':
|
|
526
|
-
arcPlane = 'xy';
|
|
527
|
-
arcPlaneSeen = true;
|
|
528
|
-
break;
|
|
529
|
-
case 'g18':
|
|
530
|
-
arcPlane = 'xz';
|
|
531
|
-
arcPlaneSeen = true;
|
|
532
|
-
break;
|
|
533
|
-
case 'g19':
|
|
534
|
-
arcPlane = 'yz';
|
|
535
|
-
arcPlaneSeen = true;
|
|
536
|
-
break;
|
|
537
|
-
case 'm82':
|
|
538
|
-
eModeExplicit = 'absolute';
|
|
539
|
-
break;
|
|
540
|
-
case 'm83':
|
|
541
|
-
eModeExplicit = 'relative';
|
|
542
|
-
break;
|
|
543
|
-
// Coordinate systems (DD-010 D4, #158, E10 phase 3).
|
|
544
|
-
case 'g53':
|
|
545
|
-
// One-shot: the following move is in machine coordinates (ignores the work offset).
|
|
546
|
-
g53OneShot = true;
|
|
547
|
-
coordSystemSeen = true;
|
|
548
|
-
break;
|
|
549
|
-
case 'g54':
|
|
550
|
-
case 'g55':
|
|
551
|
-
case 'g56':
|
|
552
|
-
case 'g57':
|
|
553
|
-
case 'g58':
|
|
554
|
-
case 'g59':
|
|
555
|
-
activeWcs = Number(cmd.gcode.slice(1)) - 54; // g54→0 … g59→5
|
|
556
|
-
coordSystemSeen = true;
|
|
557
|
-
break;
|
|
558
|
-
case 'g10': {
|
|
559
|
-
// Set a work-coordinate offset. L2 P<n>: set the offset directly. L20 P<n>: set it so the
|
|
560
|
-
// current position reads the given value in WCS n. P1→G54 … P6→G59 (default: active system).
|
|
561
|
-
const l = cmd.params.l;
|
|
562
|
-
const idx = cmd.params.p !== undefined ? Math.round(cmd.params.p) - 1 : activeWcs;
|
|
563
|
-
if (idx >= 0 && idx < 6 && (l === 2 || l === 20)) {
|
|
564
|
-
const w = wcsOffsets[idx];
|
|
565
|
-
const setAxis = (word, cur, axis) => {
|
|
566
|
-
if (word === undefined)
|
|
567
|
-
return;
|
|
568
|
-
w[axis] = l === 2 ? word : cur - word; // L20: offset = current logical − desired reading
|
|
569
|
-
};
|
|
570
|
-
setAxis(cmd.params.x, sx, 'x');
|
|
571
|
-
setAxis(cmd.params.y, sy, 'y');
|
|
572
|
-
setAxis(cmd.params.z, sz, 'z');
|
|
573
|
-
coordSystemSeen = true;
|
|
574
|
-
}
|
|
575
|
-
break;
|
|
591
|
+
if (cmd.codes.length === 0) {
|
|
592
|
+
// No G/M/T command word. This is either a dialect extended command (EXCLUDE_OBJECT_*, macros —
|
|
593
|
+
// matched on the raw line, DD-005) or a bare motion/param line. Forward the raw line to the
|
|
594
|
+
// observer so dialects still see it, then continue the active modal motion group if the line
|
|
595
|
+
// carries motion axes (the CNC/LinuxCNC modal form; FDM always emits the G word, so this never
|
|
596
|
+
// runs for FDM — byte-identical).
|
|
597
|
+
if (onCommand !== undefined) {
|
|
598
|
+
onCommand({ gcode: '', params: cmd.params, rawLine, srcByte: offset, segIndex: writer.count });
|
|
576
599
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
probed = true;
|
|
585
|
-
}
|
|
586
|
-
if (cmd.params.y !== undefined) {
|
|
587
|
-
certainY = false;
|
|
588
|
-
probed = true;
|
|
589
|
-
}
|
|
590
|
-
if (cmd.params.z !== undefined) {
|
|
591
|
-
certainZ = false;
|
|
592
|
-
probed = true;
|
|
593
|
-
}
|
|
594
|
-
if (probed) {
|
|
595
|
-
warn('probe-position-runtime-dependent', 'G31 probe endpoint is determined at runtime; the probed axis is runtime-dependent until re-established (G92 or an absolute move).', offset);
|
|
596
|
-
}
|
|
597
|
-
break;
|
|
600
|
+
if (cmd.params.x !== undefined || cmd.params.y !== undefined || cmd.params.z !== undefined) {
|
|
601
|
+
if (cannedCycle !== null)
|
|
602
|
+
ok = runCannedCycle(cmd.params);
|
|
603
|
+
else if (modalMotion === 'g2' || modalMotion === 'g3')
|
|
604
|
+
ok = g2(cmd.params, modalMotion === 'g2');
|
|
605
|
+
else if (modalMotion === 'g0' || modalMotion === 'g1')
|
|
606
|
+
ok = g0(cmd.params, modalMotion === 'g0');
|
|
598
607
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
// declared to be <v>, the offset is reset, certainty is restored, and the current path is
|
|
605
|
-
// finalized so the next move starts a NEW frame at the datum — no fabricated move is drawn
|
|
606
|
-
// across the unknown probe result.
|
|
607
|
-
if (cmd.params.e !== undefined)
|
|
608
|
-
lastE = cmd.params.e;
|
|
609
|
-
let resync = false;
|
|
610
|
-
if (cmd.params.x !== undefined) {
|
|
611
|
-
if (certainX)
|
|
612
|
-
g92x = sx - cmd.params.x - wcsOffsets[activeWcs].x;
|
|
613
|
-
else {
|
|
614
|
-
sx = cmd.params.x + wcsOffsets[activeWcs].x;
|
|
615
|
-
g92x = 0;
|
|
616
|
-
certainX = true;
|
|
617
|
-
resync = true;
|
|
618
|
-
}
|
|
619
|
-
coordSystemSeen = true;
|
|
608
|
+
}
|
|
609
|
+
else
|
|
610
|
+
for (const gcode of cmd.codes) {
|
|
611
|
+
if (onCommand !== undefined) {
|
|
612
|
+
onCommand({ gcode, params: cmd.params, rawLine, srcByte: offset, segIndex: writer.count });
|
|
620
613
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
614
|
+
switch (gcode) {
|
|
615
|
+
case 'g0':
|
|
616
|
+
case 'g1':
|
|
617
|
+
modalMotion = gcode;
|
|
618
|
+
cannedCycle = null; // a G0–G3 motion command cancels the canned-cycle modal group
|
|
619
|
+
ok = g0(cmd.params, gcode === 'g0');
|
|
620
|
+
break;
|
|
621
|
+
case 'g2':
|
|
622
|
+
modalMotion = 'g2';
|
|
623
|
+
cannedCycle = null;
|
|
624
|
+
ok = g2(cmd.params, true);
|
|
625
|
+
break;
|
|
626
|
+
case 'g3':
|
|
627
|
+
modalMotion = 'g3';
|
|
628
|
+
cannedCycle = null;
|
|
629
|
+
ok = g2(cmd.params, false);
|
|
630
|
+
break;
|
|
631
|
+
// Canned drilling cycles (DD-012 phase 2, #189). A fresh G81/G82/G83 captures Z/R/Q + the
|
|
632
|
+
// initial plane and drills the first hole; bare X/Y lines (cannedRepeat) drill more, retaining
|
|
633
|
+
// that state; G80 cancels; G98/G99 set the retract plane.
|
|
634
|
+
case 'g81':
|
|
635
|
+
case 'g82':
|
|
636
|
+
case 'g83':
|
|
637
|
+
cannedCyclesSeen = true;
|
|
638
|
+
cannedCycle = gcode;
|
|
639
|
+
cannedInitialZ = sz;
|
|
640
|
+
if (cmd.params.z !== undefined)
|
|
641
|
+
cannedZ = nextAxis(sz, cmd.params.z, offZ());
|
|
642
|
+
if (cmd.params.r !== undefined)
|
|
643
|
+
cannedR = nextAxis(sz, cmd.params.r, offZ());
|
|
644
|
+
if (cmd.params.q !== undefined)
|
|
645
|
+
cannedQ = Math.abs(cmd.params.q);
|
|
646
|
+
modalMotion = null;
|
|
647
|
+
ok = runCannedCycle(cmd.params);
|
|
648
|
+
break;
|
|
649
|
+
case 'g80':
|
|
650
|
+
cannedCycle = null;
|
|
651
|
+
break;
|
|
652
|
+
case 'g98':
|
|
653
|
+
cannedRetractInitial = true;
|
|
654
|
+
break;
|
|
655
|
+
case 'g99':
|
|
656
|
+
cannedRetractInitial = false;
|
|
657
|
+
break;
|
|
658
|
+
case 'g20':
|
|
659
|
+
units = 'in';
|
|
660
|
+
unitsSeen = true;
|
|
661
|
+
break;
|
|
662
|
+
case 'g21':
|
|
663
|
+
units = 'mm';
|
|
664
|
+
unitsSeen = true;
|
|
665
|
+
break;
|
|
666
|
+
case 'g28':
|
|
667
|
+
sx = 0;
|
|
668
|
+
sy = 0;
|
|
669
|
+
sz = 0;
|
|
670
|
+
break;
|
|
671
|
+
case 't0':
|
|
672
|
+
case 't1':
|
|
673
|
+
case 't2':
|
|
674
|
+
case 't3':
|
|
675
|
+
case 't4':
|
|
676
|
+
case 't5':
|
|
677
|
+
case 't6':
|
|
678
|
+
case 't7':
|
|
679
|
+
tool = Number(gcode.slice(1));
|
|
680
|
+
break;
|
|
681
|
+
case 'm600':
|
|
682
|
+
// Manual filament swap = color boundary (#147). A marker, not motion — do
|
|
683
|
+
// NOT break/finish the current path; record position (current head), the
|
|
684
|
+
// next segment index (slot boundary), and the active tool for provenance.
|
|
685
|
+
colorChangeEvents.push({ x: sx, y: sy, z: sz, segIndex: writer.count, srcByte: offset, tool });
|
|
686
|
+
break;
|
|
687
|
+
// Motion-model modal commands (DD-010 E10 phase 1).
|
|
688
|
+
case 'g90':
|
|
689
|
+
xyzAbsolute = true;
|
|
690
|
+
positioningSeen = true;
|
|
691
|
+
break;
|
|
692
|
+
case 'g91':
|
|
693
|
+
xyzAbsolute = false;
|
|
694
|
+
positioningSeen = true;
|
|
695
|
+
break;
|
|
696
|
+
// Arc-plane selection (DD-010 D3, #157, E10 phase 2).
|
|
697
|
+
case 'g17':
|
|
698
|
+
arcPlane = 'xy';
|
|
699
|
+
arcPlaneSeen = true;
|
|
700
|
+
break;
|
|
701
|
+
case 'g18':
|
|
702
|
+
arcPlane = 'xz';
|
|
703
|
+
arcPlaneSeen = true;
|
|
704
|
+
break;
|
|
705
|
+
case 'g19':
|
|
706
|
+
arcPlane = 'yz';
|
|
707
|
+
arcPlaneSeen = true;
|
|
708
|
+
break;
|
|
709
|
+
case 'm82':
|
|
710
|
+
eModeExplicit = 'absolute';
|
|
711
|
+
break;
|
|
712
|
+
case 'm83':
|
|
713
|
+
eModeExplicit = 'relative';
|
|
714
|
+
break;
|
|
715
|
+
// Tool-state modal (DD-012 D2/D4, #189). M3 (CW) / M4 (CCW) engage the spindle/laser;
|
|
716
|
+
// M5 disengages. Drives Cut-vs-Travel classification for non-extrusion moves. The `S` word
|
|
717
|
+
// (power/RPM) is a modal channel handled in a later phase; here we track only engagement.
|
|
718
|
+
case 'm3':
|
|
719
|
+
case 'm4':
|
|
720
|
+
toolEngaged = true;
|
|
721
|
+
toolStateSeen = true;
|
|
722
|
+
break;
|
|
723
|
+
case 'm5':
|
|
724
|
+
toolEngaged = false;
|
|
725
|
+
toolStateSeen = true;
|
|
726
|
+
break;
|
|
727
|
+
// Coordinate systems (DD-010 D4, #158, E10 phase 3).
|
|
728
|
+
case 'g53':
|
|
729
|
+
// One-shot: the following move is in machine coordinates (ignores the work offset).
|
|
730
|
+
g53OneShot = true;
|
|
731
|
+
coordSystemSeen = true;
|
|
732
|
+
break;
|
|
733
|
+
case 'g54':
|
|
734
|
+
case 'g55':
|
|
735
|
+
case 'g56':
|
|
736
|
+
case 'g57':
|
|
737
|
+
case 'g58':
|
|
738
|
+
case 'g59':
|
|
739
|
+
activeWcs = Number(gcode.slice(1)) - 54; // g54→0 … g59→5
|
|
740
|
+
coordSystemSeen = true;
|
|
741
|
+
break;
|
|
742
|
+
case 'g10': {
|
|
743
|
+
// Set a work-coordinate offset. L2 P<n>: set the offset directly. L20 P<n>: set it so the
|
|
744
|
+
// current position reads the given value in WCS n. P1→G54 … P6→G59 (default: active system).
|
|
745
|
+
const l = cmd.params.l;
|
|
746
|
+
const idx = cmd.params.p !== undefined ? Math.round(cmd.params.p) - 1 : activeWcs;
|
|
747
|
+
if (idx >= 0 && idx < 6 && (l === 2 || l === 20)) {
|
|
748
|
+
const w = wcsOffsets[idx];
|
|
749
|
+
const setAxis = (word, cur, axis) => {
|
|
750
|
+
if (word === undefined)
|
|
751
|
+
return;
|
|
752
|
+
w[axis] = l === 2 ? word : cur - word; // L20: offset = current logical − desired reading
|
|
753
|
+
};
|
|
754
|
+
setAxis(cmd.params.x, sx, 'x');
|
|
755
|
+
setAxis(cmd.params.y, sy, 'y');
|
|
756
|
+
setAxis(cmd.params.z, sz, 'z');
|
|
757
|
+
coordSystemSeen = true;
|
|
758
|
+
}
|
|
759
|
+
break;
|
|
629
760
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
761
|
+
case 'g31': {
|
|
762
|
+
// Probe move (DD-010 D4 amendment, #158): the endpoint is reached at RUNTIME (workpiece
|
|
763
|
+
// contact), NOT the commanded value. Do not advance the position or draw a fabricated probe
|
|
764
|
+
// move; mark the probed axes runtime-dependent so a following G92 resyncs the logical frame.
|
|
765
|
+
let probed = false;
|
|
766
|
+
if (cmd.params.x !== undefined) {
|
|
767
|
+
certainX = false;
|
|
768
|
+
probed = true;
|
|
769
|
+
}
|
|
770
|
+
if (cmd.params.y !== undefined) {
|
|
771
|
+
certainY = false;
|
|
772
|
+
probed = true;
|
|
773
|
+
}
|
|
774
|
+
if (cmd.params.z !== undefined) {
|
|
775
|
+
certainZ = false;
|
|
776
|
+
probed = true;
|
|
777
|
+
}
|
|
778
|
+
if (probed) {
|
|
779
|
+
warn('probe-position-runtime-dependent', 'G31 probe endpoint is determined at runtime; the probed axis is runtime-dependent until re-established (G92 or an absolute move).', offset);
|
|
780
|
+
}
|
|
781
|
+
break;
|
|
640
782
|
}
|
|
641
|
-
|
|
783
|
+
case 'g92': {
|
|
784
|
+
// Datum WITHOUT motion (DD-010 D4 + probe amendment, #158). `G92 E<v>` rebases the absolute-E
|
|
785
|
+
// origin (also used in phase 1). For X/Y/Z, when the position is KNOWN it is a datum SHIFT (the
|
|
786
|
+
// work offset is set so the current logical position reads <v>, preserving continuity). When the
|
|
787
|
+
// axis is runtime-dependent (post-probe) it is a logical RESYNC: the current logical position is
|
|
788
|
+
// declared to be <v>, the offset is reset, certainty is restored, and the current path is
|
|
789
|
+
// finalized so the next move starts a NEW frame at the datum — no fabricated move is drawn
|
|
790
|
+
// across the unknown probe result.
|
|
791
|
+
if (cmd.params.e !== undefined)
|
|
792
|
+
lastE = cmd.params.e;
|
|
793
|
+
let resync = false;
|
|
794
|
+
if (cmd.params.x !== undefined) {
|
|
795
|
+
if (certainX)
|
|
796
|
+
g92x = sx - cmd.params.x - wcsOffsets[activeWcs].x;
|
|
797
|
+
else {
|
|
798
|
+
sx = cmd.params.x + wcsOffsets[activeWcs].x;
|
|
799
|
+
g92x = 0;
|
|
800
|
+
certainX = true;
|
|
801
|
+
resync = true;
|
|
802
|
+
}
|
|
803
|
+
coordSystemSeen = true;
|
|
804
|
+
}
|
|
805
|
+
if (cmd.params.y !== undefined) {
|
|
806
|
+
if (certainY)
|
|
807
|
+
g92y = sy - cmd.params.y - wcsOffsets[activeWcs].y;
|
|
808
|
+
else {
|
|
809
|
+
sy = cmd.params.y + wcsOffsets[activeWcs].y;
|
|
810
|
+
g92y = 0;
|
|
811
|
+
certainY = true;
|
|
812
|
+
resync = true;
|
|
813
|
+
}
|
|
814
|
+
coordSystemSeen = true;
|
|
815
|
+
}
|
|
816
|
+
if (cmd.params.z !== undefined) {
|
|
817
|
+
if (certainZ)
|
|
818
|
+
g92z = sz - cmd.params.z - wcsOffsets[activeWcs].z;
|
|
819
|
+
else {
|
|
820
|
+
sz = cmd.params.z + wcsOffsets[activeWcs].z;
|
|
821
|
+
g92z = 0;
|
|
822
|
+
certainZ = true;
|
|
823
|
+
resync = true;
|
|
824
|
+
}
|
|
825
|
+
coordSystemSeen = true;
|
|
826
|
+
}
|
|
827
|
+
if (resync)
|
|
828
|
+
finishPath(); // start a new frame at the datum; no move connects across the probe
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
default:
|
|
832
|
+
warn('unsupported-command', `unsupported command '${gcode}' preserved as metadata`, offset);
|
|
642
833
|
}
|
|
643
|
-
if (
|
|
644
|
-
|
|
645
|
-
break;
|
|
834
|
+
if (!ok)
|
|
835
|
+
break;
|
|
646
836
|
}
|
|
647
|
-
default:
|
|
648
|
-
warn('unsupported-command', `unsupported command '${cmd.gcode}' preserved as metadata`, offset);
|
|
649
|
-
}
|
|
650
837
|
if (!ok) {
|
|
651
838
|
truncatedAtByte = offset;
|
|
652
839
|
}
|
|
@@ -692,6 +879,13 @@ export function createEngine(input, opts) {
|
|
|
692
879
|
// per-move G-code signal and stays 'unavailable' (a future geometry-heuristic DD may change it).
|
|
693
880
|
wipeMoves: 'unavailable',
|
|
694
881
|
seamMoves: 'unavailable',
|
|
882
|
+
// Non-extrusion productive moves (DD-012 D2, #189). 'known' once a tool-state modal (M3/M4/M5)
|
|
883
|
+
// was seen — i.e. this is a CNC/laser/plotter file whose Cut moves are meaningful; 'unavailable'
|
|
884
|
+
// for FDM (no tool-state), where every move is Extrude/Travel and Cut is never set.
|
|
885
|
+
cutMoves: toolStateSeen ? 'known' : 'unavailable',
|
|
886
|
+
// Canned drilling cycles expanded to geometry (DD-012 phase 2, #189). 'known' once a G81/G82/G83
|
|
887
|
+
// was seen (holes are real segments), else 'unavailable' (no cycles / FDM).
|
|
888
|
+
cannedCycles: cannedCyclesSeen ? 'known' : 'unavailable',
|
|
695
889
|
// Motion-model modes (DD-010 E10 phase 1). 'known' when the governing command was seen
|
|
696
890
|
// (M82/M83, or a firmware-known G90/G91 for E); 'inferred' when defaulted (absolute).
|
|
697
891
|
extrusionMode: eModeExplicit !== null || (extruderFollowsPositioning && positioningSeen) ? 'known' : 'inferred',
|
|
@@ -702,6 +896,10 @@ export function createEngine(input, opts) {
|
|
|
702
896
|
// 'inferred' (identity WCS / no offset assumed — the FDM-slicer default).
|
|
703
897
|
coordinateSystem: coordSystemSeen ? 'known' : 'inferred'
|
|
704
898
|
};
|
|
899
|
+
// Opt-in modal channels (DD-012 D3): only surfaced when requested. 'known' once its controlling
|
|
900
|
+
// signal was seen (toolPower ← a tool-state modal), else 'unavailable' — never a fabricated value.
|
|
901
|
+
if (wantToolPower)
|
|
902
|
+
capabilities.toolPower = toolStateSeen ? 'known' : 'unavailable';
|
|
705
903
|
if (layersCapability === 'unavailable') {
|
|
706
904
|
warn('layers-unavailable', 'No planar layer index; all segments assigned to layer 0.');
|
|
707
905
|
}
|
|
@@ -804,6 +1002,8 @@ export function createEngine(input, opts) {
|
|
|
804
1002
|
colorChanges: 'unavailable', // color-change boundaries resolve on the final IR
|
|
805
1003
|
wipeMoves: 'unavailable', // annotation move kinds resolve on the final IR (DD-016)
|
|
806
1004
|
seamMoves: 'unavailable',
|
|
1005
|
+
cutMoves: 'unavailable', // non-extrusion classification resolves on the final IR (DD-012)
|
|
1006
|
+
cannedCycles: 'unavailable', // canned-cycle expansion resolves on the final IR (DD-012)
|
|
807
1007
|
extrusionMode: 'inferred', // motion modes resolve fully on the final IR (E10)
|
|
808
1008
|
positioningMode: 'inferred',
|
|
809
1009
|
arcPlanes: 'inferred',
|
package/dist/worker.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* `worker-slim.ts`; custom adapters use a consumer-supplied worker entry
|
|
11
11
|
* calling `createWorkerHandler(post, { dialects: createDialectRunner([...]) })`.
|
|
12
12
|
*/
|
|
13
|
-
import { createDialectRunner, cura, klipper, marlin, orcaBambu, prusaSlicer, repRap } from '@chestnutlabs/gcode-dialects';
|
|
13
|
+
import { createDialectRunner, cura, grblLaser, grblMill, klipper, linuxCnc, marlin, orcaBambu, prusaSlicer, repRap } from '@chestnutlabs/gcode-dialects';
|
|
14
14
|
import { openGcode3mf, sniffGcode3mf } from '@chestnutlabs/gcode-containers';
|
|
15
15
|
import { openBgcodeContainer, sniffBgcode } from '@chestnutlabs/gcode-bgcode';
|
|
16
16
|
import { createWorkerHandler } from './worker-core.js';
|
|
@@ -21,7 +21,11 @@ const BUILTIN_ADAPTERS = [
|
|
|
21
21
|
cura(),
|
|
22
22
|
klipper(),
|
|
23
23
|
marlin(),
|
|
24
|
-
repRap()
|
|
24
|
+
repRap(),
|
|
25
|
+
// Non-extrusion controllers (DD-012 phase 3, #189) — all EXPERIMENTAL tier until hardware-validated.
|
|
26
|
+
grblLaser(),
|
|
27
|
+
grblMill(),
|
|
28
|
+
linuxCnc()
|
|
25
29
|
];
|
|
26
30
|
/** Built-in container adapters (DD-005 §4.4): .gcode.3mf and Prusa binary G-code (.bgcode, #188). */
|
|
27
31
|
const BUILTIN_CONTAINERS = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chestnutlabs/gcode-parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Worker-safe G-code parse core producing ToolpathIR (DD-003).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gcode",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"test": "vitest run"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@chestnutlabs/toolpath-core": "0.
|
|
53
|
-
"@chestnutlabs/gcode-dialects": "0.
|
|
54
|
-
"@chestnutlabs/gcode-containers": "0.
|
|
55
|
-
"@chestnutlabs/gcode-bgcode": "0.
|
|
52
|
+
"@chestnutlabs/toolpath-core": "0.5.0",
|
|
53
|
+
"@chestnutlabs/gcode-dialects": "0.5.0",
|
|
54
|
+
"@chestnutlabs/gcode-containers": "0.5.0",
|
|
55
|
+
"@chestnutlabs/gcode-bgcode": "0.5.0"
|
|
56
56
|
}
|
|
57
57
|
}
|