@chestnutlabs/gcode-parser 0.2.0 → 0.3.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/parse.d.ts +7 -0
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +288 -45
- package/dist/worker.js +4 -2
- package/package.json +5 -4
package/dist/parse.d.ts
CHANGED
|
@@ -56,6 +56,13 @@ export interface ParseOptions {
|
|
|
56
56
|
limits?: Partial<ParseLimits>;
|
|
57
57
|
/** Layer-detection tolerance (inherited default 0.05). */
|
|
58
58
|
minLayerThreshold?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Firmware hint (DD-010 D2): does `G90`/`G91` also set the *extruder* mode? Marlin/Klipper —
|
|
61
|
+
* `true`; RepRapFirmware treats E independently — `false`. Default `false` (RRF/unknown-conservative).
|
|
62
|
+
* Only consulted while no `M82`/`M83` has latched the E-mode; supplied by the dialect layer / caller
|
|
63
|
+
* that has confidently classified the firmware. The byte-exact engine never sniffs firmware itself.
|
|
64
|
+
*/
|
|
65
|
+
extruderFollowsPositioning?: boolean;
|
|
59
66
|
/**
|
|
60
67
|
* DD-005 §4.3 read-only hooks: observe comments/commands during the parse.
|
|
61
68
|
* 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
|
@@ -101,6 +101,62 @@ export function createEngine(input, opts) {
|
|
|
101
101
|
let units = 'mm';
|
|
102
102
|
let unitsSeen = false;
|
|
103
103
|
let modalFeed = NaN;
|
|
104
|
+
// Motion-model modal state (DD-010 E10 phase 1). `xyzAbsolute` (G90/G91) and the extruder mode
|
|
105
|
+
// resolve the effective per-move E delta and the extrude/travel classification. Defaults are the
|
|
106
|
+
// firmware power-on convention: absolute XYZ, absolute E. `eModeExplicit` is set only by M82/M83;
|
|
107
|
+
// `lastE` is the running absolute-E datum (also reset by `G92 E`).
|
|
108
|
+
let xyzAbsolute = true;
|
|
109
|
+
let positioningSeen = false; // any G90/G91 observed → positioningMode 'known'
|
|
110
|
+
let eModeExplicit = null; // M82/M83; null = infer
|
|
111
|
+
let lastE = 0;
|
|
112
|
+
const extruderFollowsPositioning = opts.extruderFollowsPositioning === true;
|
|
113
|
+
// Arc-plane modal state (DD-010 D3, #157, E10 phase 2). G17=XY (default, power-on),
|
|
114
|
+
// G18=XZ, G19=YZ. Arc flattening runs in the active plane; the through axis ramps linearly.
|
|
115
|
+
let arcPlane = 'xy';
|
|
116
|
+
let arcPlaneSeen = false; // any G17/G18/G19 observed → arcPlanes 'known'
|
|
117
|
+
// Coordinate systems (DD-010 D4, #158, E10 phase 3). The IR stays in the logical/work frame
|
|
118
|
+
// (Option A): the emitted position is `commanded + activeWcsOffset + g92off`. FDM slicer files use
|
|
119
|
+
// the identity WCS with no G92, so every offset is 0 → the corpus is byte-identical. G54–G59 select
|
|
120
|
+
// a per-system offset (settable via G10 L2/L20); G92 X/Y/Z shifts the datum without motion; G53 is a
|
|
121
|
+
// one-shot machine-coordinate bypass for the following move.
|
|
122
|
+
let activeWcs = 0; // 0..5 → G54..G59; G54 (identity) is the power-on default
|
|
123
|
+
const wcsOffsets = Array.from({ length: 6 }, () => ({ x: 0, y: 0, z: 0 }));
|
|
124
|
+
let g92x = 0;
|
|
125
|
+
let g92y = 0;
|
|
126
|
+
let g92z = 0;
|
|
127
|
+
let coordSystemSeen = false; // any G53/G54–G59/G92-XYZ/G10 → coordinateSystem 'known'
|
|
128
|
+
let g53OneShot = false; // next move ignores the work offset (machine coordinates)
|
|
129
|
+
// Per-axis position certainty (DD-010 D4 amendment, #158). A G31 probe endpoint is reached at
|
|
130
|
+
// RUNTIME (workpiece contact), not the commanded value, so it marks the probed axes uncertain. A
|
|
131
|
+
// following G92 then RESYNCS the logical frame at the datum (rather than datum-shifting a stale
|
|
132
|
+
// position); an absolute move re-establishes certainty. Independent of the work offsets above.
|
|
133
|
+
let certainX = true;
|
|
134
|
+
let certainY = true;
|
|
135
|
+
let certainZ = true;
|
|
136
|
+
const offX = () => (g53OneShot ? 0 : wcsOffsets[activeWcs].x + g92x);
|
|
137
|
+
const offY = () => (g53OneShot ? 0 : wcsOffsets[activeWcs].y + g92y);
|
|
138
|
+
const offZ = () => (g53OneShot ? 0 : wcsOffsets[activeWcs].z + g92z);
|
|
139
|
+
/**
|
|
140
|
+
* Effective per-move E delta from the modal extruder mode (DD-010 D1/D2 precedence:
|
|
141
|
+
* explicit M82/M83 → firmware-conditioned G90/G91 → absolute default). Updates `lastE`.
|
|
142
|
+
* For relative E the delta is the raw word (byte-identical to the pre-E10 engine for the
|
|
143
|
+
* M83 corpus); for absolute E it is `eParam − lastE`.
|
|
144
|
+
*/
|
|
145
|
+
const resolveEDelta = (eParam) => {
|
|
146
|
+
if (eParam === undefined)
|
|
147
|
+
return 0;
|
|
148
|
+
const absolute = eModeExplicit !== null ? eModeExplicit === 'absolute' : extruderFollowsPositioning ? xyzAbsolute : true; // firmware-neutral default: absolute, disclosed 'inferred'
|
|
149
|
+
const delta = absolute ? eParam - lastE : eParam;
|
|
150
|
+
lastE = absolute ? eParam : lastE + eParam;
|
|
151
|
+
return delta;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Logical (work-frame) target for one axis given the positioning mode (G90/G91) and the active
|
|
155
|
+
* work offset (DD-010 D4). Absolute: `word + off`; relative: `cur + word` (the offset is already
|
|
156
|
+
* baked into `cur`); an absent word holds the current logical position. With `off === 0` (the
|
|
157
|
+
* identity WCS + no G92) this reduces to the pre-#158 `word ?? cur`, so the corpus is byte-identical.
|
|
158
|
+
*/
|
|
159
|
+
const nextAxis = (cur, word, off = 0) => xyzAbsolute ? (word !== undefined ? word + off : cur) : cur + (word ?? 0);
|
|
104
160
|
// Floating origin (DD-001 §4.6): fixed at the first emitted segment's start.
|
|
105
161
|
let originSet = false;
|
|
106
162
|
let ox = 0;
|
|
@@ -254,19 +310,22 @@ export function createEngine(input, opts) {
|
|
|
254
310
|
};
|
|
255
311
|
const g0 = (p) => {
|
|
256
312
|
const { x, y, z, e, f } = p;
|
|
313
|
+
// Classify on the true per-move delta, not the raw E word (DD-010 D1). For the M83/relative
|
|
314
|
+
// corpus the delta equals the word, so output is byte-identical to the pre-E10 engine.
|
|
315
|
+
const eDelta = resolveEDelta(e);
|
|
257
316
|
if (x === undefined && y === undefined && z === undefined) {
|
|
258
|
-
if (
|
|
317
|
+
if (eDelta > 0)
|
|
259
318
|
stats.retractions++;
|
|
260
|
-
else if (
|
|
319
|
+
else if (eDelta < 0)
|
|
261
320
|
stats.deretractions++;
|
|
262
321
|
else if (f !== undefined)
|
|
263
322
|
stats.feedrateChanges++;
|
|
264
|
-
if (e !== undefined &&
|
|
323
|
+
if (e !== undefined && eDelta !== 0) {
|
|
265
324
|
retractionEvents.push({
|
|
266
325
|
x: sx,
|
|
267
326
|
y: sy,
|
|
268
327
|
z: sz,
|
|
269
|
-
kind:
|
|
328
|
+
kind: eDelta < 0 ? 'retract' : 'unretract',
|
|
270
329
|
srcByte: currentSrcByte,
|
|
271
330
|
segIndex: writer.count
|
|
272
331
|
});
|
|
@@ -280,47 +339,75 @@ export function createEngine(input, opts) {
|
|
|
280
339
|
stats.points++;
|
|
281
340
|
if (f !== undefined)
|
|
282
341
|
modalFeed = f;
|
|
283
|
-
const pathType =
|
|
342
|
+
const pathType = eDelta > 0 ? 'extrusion' : 'travel';
|
|
284
343
|
if (path === null || path.type !== pathType) {
|
|
285
344
|
breakPath(pathType);
|
|
286
345
|
}
|
|
287
|
-
if (
|
|
288
|
-
stats.extrusionDistance +=
|
|
289
|
-
sx = x
|
|
290
|
-
sy = y
|
|
291
|
-
sz = z
|
|
292
|
-
|
|
346
|
+
if (eDelta > 0)
|
|
347
|
+
stats.extrusionDistance += eDelta;
|
|
348
|
+
sx = nextAxis(sx, x, offX());
|
|
349
|
+
sy = nextAxis(sy, y, offY());
|
|
350
|
+
sz = nextAxis(sz, z, offZ());
|
|
351
|
+
// An absolute commanded move re-establishes certainty for the axes it names (#158).
|
|
352
|
+
if (xyzAbsolute) {
|
|
353
|
+
if (x !== undefined)
|
|
354
|
+
certainX = true;
|
|
355
|
+
if (y !== undefined)
|
|
356
|
+
certainY = true;
|
|
357
|
+
if (z !== undefined)
|
|
358
|
+
certainZ = true;
|
|
359
|
+
}
|
|
360
|
+
g53OneShot = false; // one-shot machine-coordinate bypass consumed by this move
|
|
361
|
+
return emitSegment(sx, sy, sz, eDelta, pathType === 'extrusion' ? MoveKind.Extrude : MoveKind.Travel);
|
|
293
362
|
};
|
|
294
363
|
const g2 = (p, cw) => {
|
|
295
|
-
const {
|
|
296
|
-
let {
|
|
364
|
+
const { e, f } = p;
|
|
365
|
+
let { r } = p;
|
|
297
366
|
if (f !== undefined)
|
|
298
367
|
modalFeed = f;
|
|
299
|
-
|
|
368
|
+
// E is delta-based (DD-010 D1) so M82 arcs classify correctly and `lastE` stays consistent with g0.
|
|
369
|
+
const eDelta = resolveEDelta(e);
|
|
370
|
+
const pathType = eDelta ? 'extrusion' : 'travel';
|
|
300
371
|
if (path === null || path.type !== pathType) {
|
|
301
372
|
breakPath(pathType);
|
|
302
373
|
}
|
|
303
|
-
if (
|
|
304
|
-
stats.extrusionDistance +=
|
|
374
|
+
if (eDelta > 0)
|
|
375
|
+
stats.extrusionDistance += eDelta;
|
|
376
|
+
// Absolute targets, honoring G90/G91 (DD-010 D2 — the deferred G91-arc geometry lands here, #157)
|
|
377
|
+
// and the active work offset (DD-010 D4, #158). I/J/K are ALWAYS current-relative center offsets.
|
|
378
|
+
const tx = nextAxis(sx, p.x, offX());
|
|
379
|
+
const ty = nextAxis(sy, p.y, offY());
|
|
380
|
+
const tz = nextAxis(sz, p.z, offZ());
|
|
381
|
+
g53OneShot = false; // one-shot machine-coordinate bypass consumed by this move
|
|
382
|
+
// Select the active arc plane (DD-010 D3): (sa,sb)→(ta,tb) is the in-plane arc with center offsets
|
|
383
|
+
// (oa,ob); sc→tc is the through axis (linear ramp). XY reproduces the pre-#157 math exactly, so the
|
|
384
|
+
// XY-arc corpus stays byte-identical; only G18/G19 arcs newly flatten in the correct plane.
|
|
385
|
+
const [sa, sb, sc, ta, tb, tc, oa, ob] = arcPlane === 'xz'
|
|
386
|
+
? [sx, sz, sy, tx, tz, ty, p.i, p.k]
|
|
387
|
+
: arcPlane === 'yz'
|
|
388
|
+
? [sy, sz, sx, ty, tz, tx, p.j, p.k]
|
|
389
|
+
: [sx, sy, sz, tx, ty, tz, p.i, p.j];
|
|
390
|
+
let ia = oa;
|
|
391
|
+
let ib = ob;
|
|
305
392
|
if (r) {
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const minR = Math.sqrt(Math.pow(
|
|
393
|
+
const deltaA = ta - sa;
|
|
394
|
+
const deltaB = tb - sb;
|
|
395
|
+
const minR = Math.sqrt(Math.pow(deltaA / 2, 2) + Math.pow(deltaB / 2, 2));
|
|
309
396
|
r = Math.max(r, minR);
|
|
310
|
-
const dSquared = Math.pow(
|
|
397
|
+
const dSquared = Math.pow(deltaA, 2) + Math.pow(deltaB, 2);
|
|
311
398
|
const hSquared = Math.pow(r, 2) - dSquared / 4;
|
|
312
399
|
let hDivD = Math.sqrt(hSquared / dSquared);
|
|
313
400
|
if ((cw && r < 0.0) || (!cw && r > 0.0))
|
|
314
401
|
hDivD = -hDivD;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
const wholeCircle =
|
|
319
|
-
const
|
|
320
|
-
const
|
|
321
|
-
const arcRadius = Math.sqrt(
|
|
322
|
-
const arcCurrentAngle = Math.atan2(-
|
|
323
|
-
const finalTheta = Math.atan2(
|
|
402
|
+
ia = deltaA / 2 + deltaB * hDivD;
|
|
403
|
+
ib = deltaB / 2 - deltaA * hDivD;
|
|
404
|
+
}
|
|
405
|
+
const wholeCircle = sa == ta && sb == tb;
|
|
406
|
+
const centerA = sa + ia;
|
|
407
|
+
const centerB = sb + ib;
|
|
408
|
+
const arcRadius = Math.sqrt(ia * ia + ib * ib);
|
|
409
|
+
const arcCurrentAngle = Math.atan2(-ib, -ia);
|
|
410
|
+
const finalTheta = Math.atan2(tb - centerB, ta - centerA);
|
|
324
411
|
let totalArc;
|
|
325
412
|
if (wholeCircle) {
|
|
326
413
|
totalArc = 2 * Math.PI;
|
|
@@ -337,26 +424,30 @@ export function createEngine(input, opts) {
|
|
|
337
424
|
totalSegments = 1;
|
|
338
425
|
let arcAngleIncrement = totalArc / totalSegments;
|
|
339
426
|
arcAngleIncrement *= cw ? -1 : 1;
|
|
340
|
-
|
|
341
|
-
|
|
427
|
+
// Through-axis linear ramp — preserves the inherited step (byte-identical for planar XY arcs,
|
|
428
|
+
// where the through axis is unchanged so the ramp is flat).
|
|
429
|
+
const cStep = (sc - tc) / totalSegments;
|
|
342
430
|
const kind = (pathType === 'extrusion' ? MoveKind.Extrude : MoveKind.Travel) | MoveKind.ArcSegment;
|
|
343
|
-
const eachE = e !== undefined ?
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
431
|
+
const eachE = e !== undefined ? eDelta / Math.max(1, Math.ceil(totalSegments)) : 0;
|
|
432
|
+
// Map an in-plane point (pa,pb) + through-axis pc back to (x,y,z) for the active plane.
|
|
433
|
+
const emitArc = (pa, pb, pcv) => {
|
|
434
|
+
const [px, py, pz] = arcPlane === 'xz' ? [pa, pcv, pb] : arcPlane === 'yz' ? [pcv, pa, pb] : [pa, pb, pcv];
|
|
435
|
+
return emitSegment(px, py, pz, eachE, kind);
|
|
436
|
+
};
|
|
437
|
+
let pc = sc;
|
|
347
438
|
let currentAngle = arcCurrentAngle;
|
|
348
439
|
for (let moveIdx = 0; moveIdx < totalSegments - 1; moveIdx++) {
|
|
349
440
|
currentAngle += arcAngleIncrement;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (!
|
|
441
|
+
const pa = centerA + arcRadius * Math.cos(currentAngle);
|
|
442
|
+
const pb = centerB + arcRadius * Math.sin(currentAngle);
|
|
443
|
+
pc += cStep;
|
|
444
|
+
if (!emitArc(pa, pb, pc))
|
|
354
445
|
return false;
|
|
355
446
|
}
|
|
356
|
-
sx =
|
|
357
|
-
sy =
|
|
358
|
-
sz =
|
|
359
|
-
return
|
|
447
|
+
sx = tx;
|
|
448
|
+
sy = ty;
|
|
449
|
+
sz = tz;
|
|
450
|
+
return emitArc(ta, tb, tc);
|
|
360
451
|
};
|
|
361
452
|
const onComment = opts.onComment;
|
|
362
453
|
const onCommand = opts.onCommand;
|
|
@@ -421,6 +512,138 @@ export function createEngine(input, opts) {
|
|
|
421
512
|
// next segment index (slot boundary), and the active tool for provenance.
|
|
422
513
|
colorChangeEvents.push({ x: sx, y: sy, z: sz, segIndex: writer.count, srcByte: offset, tool });
|
|
423
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;
|
|
576
|
+
}
|
|
577
|
+
case 'g31': {
|
|
578
|
+
// Probe move (DD-010 D4 amendment, #158): the endpoint is reached at RUNTIME (workpiece
|
|
579
|
+
// contact), NOT the commanded value. Do not advance the position or draw a fabricated probe
|
|
580
|
+
// move; mark the probed axes runtime-dependent so a following G92 resyncs the logical frame.
|
|
581
|
+
let probed = false;
|
|
582
|
+
if (cmd.params.x !== undefined) {
|
|
583
|
+
certainX = false;
|
|
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;
|
|
598
|
+
}
|
|
599
|
+
case 'g92': {
|
|
600
|
+
// Datum WITHOUT motion (DD-010 D4 + probe amendment, #158). `G92 E<v>` rebases the absolute-E
|
|
601
|
+
// origin (also used in phase 1). For X/Y/Z, when the position is KNOWN it is a datum SHIFT (the
|
|
602
|
+
// work offset is set so the current logical position reads <v>, preserving continuity). When the
|
|
603
|
+
// axis is runtime-dependent (post-probe) it is a logical RESYNC: the current logical position is
|
|
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;
|
|
620
|
+
}
|
|
621
|
+
if (cmd.params.y !== undefined) {
|
|
622
|
+
if (certainY)
|
|
623
|
+
g92y = sy - cmd.params.y - wcsOffsets[activeWcs].y;
|
|
624
|
+
else {
|
|
625
|
+
sy = cmd.params.y + wcsOffsets[activeWcs].y;
|
|
626
|
+
g92y = 0;
|
|
627
|
+
certainY = true;
|
|
628
|
+
resync = true;
|
|
629
|
+
}
|
|
630
|
+
coordSystemSeen = true;
|
|
631
|
+
}
|
|
632
|
+
if (cmd.params.z !== undefined) {
|
|
633
|
+
if (certainZ)
|
|
634
|
+
g92z = sz - cmd.params.z - wcsOffsets[activeWcs].z;
|
|
635
|
+
else {
|
|
636
|
+
sz = cmd.params.z + wcsOffsets[activeWcs].z;
|
|
637
|
+
g92z = 0;
|
|
638
|
+
certainZ = true;
|
|
639
|
+
resync = true;
|
|
640
|
+
}
|
|
641
|
+
coordSystemSeen = true;
|
|
642
|
+
}
|
|
643
|
+
if (resync)
|
|
644
|
+
finishPath(); // start a new frame at the datum; no move connects across the probe
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
424
647
|
default:
|
|
425
648
|
warn('unsupported-command', `unsupported command '${cmd.gcode}' preserved as metadata`, offset);
|
|
426
649
|
}
|
|
@@ -463,7 +686,21 @@ export function createEngine(input, opts) {
|
|
|
463
686
|
featureRoles: 'unavailable',
|
|
464
687
|
objects: 'unavailable',
|
|
465
688
|
retractions: retractionEvents.length > 0 ? 'known' : 'unavailable',
|
|
466
|
-
colorChanges: colorChangeEvents.length > 0 ? 'known' : 'unavailable'
|
|
689
|
+
colorChanges: colorChangeEvents.length > 0 ? 'known' : 'unavailable',
|
|
690
|
+
// Annotation-derived move kinds (DD-016, #182). The parser never sets these — they come from a
|
|
691
|
+
// slicer adapter's WIPE_START/END brackets (upgraded to 'known' via the sink). Seam has no
|
|
692
|
+
// per-move G-code signal and stays 'unavailable' (a future geometry-heuristic DD may change it).
|
|
693
|
+
wipeMoves: 'unavailable',
|
|
694
|
+
seamMoves: 'unavailable',
|
|
695
|
+
// Motion-model modes (DD-010 E10 phase 1). 'known' when the governing command was seen
|
|
696
|
+
// (M82/M83, or a firmware-known G90/G91 for E); 'inferred' when defaulted (absolute).
|
|
697
|
+
extrusionMode: eModeExplicit !== null || (extruderFollowsPositioning && positioningSeen) ? 'known' : 'inferred',
|
|
698
|
+
positioningMode: positioningSeen ? 'known' : 'inferred',
|
|
699
|
+
// Arc plane (DD-010 D3, #157): 'known' once a G17/G18/G19 was seen, else 'inferred' (XY assumed).
|
|
700
|
+
arcPlanes: arcPlaneSeen ? 'known' : 'inferred',
|
|
701
|
+
// Coordinate system (DD-010 D4, #158): 'known' once a G53/G54–G59/G92-XYZ/G10 was seen, else
|
|
702
|
+
// 'inferred' (identity WCS / no offset assumed — the FDM-slicer default).
|
|
703
|
+
coordinateSystem: coordSystemSeen ? 'known' : 'inferred'
|
|
467
704
|
};
|
|
468
705
|
if (layersCapability === 'unavailable') {
|
|
469
706
|
warn('layers-unavailable', 'No planar layer index; all segments assigned to layer 0.');
|
|
@@ -564,7 +801,13 @@ export function createEngine(input, opts) {
|
|
|
564
801
|
featureRoles: 'unavailable',
|
|
565
802
|
objects: 'unavailable',
|
|
566
803
|
retractions: 'unavailable', // markers resolve on the final IR, not on preview slices
|
|
567
|
-
colorChanges: 'unavailable' // color-change boundaries resolve on the final IR
|
|
804
|
+
colorChanges: 'unavailable', // color-change boundaries resolve on the final IR
|
|
805
|
+
wipeMoves: 'unavailable', // annotation move kinds resolve on the final IR (DD-016)
|
|
806
|
+
seamMoves: 'unavailable',
|
|
807
|
+
extrusionMode: 'inferred', // motion modes resolve fully on the final IR (E10)
|
|
808
|
+
positioningMode: 'inferred',
|
|
809
|
+
arcPlanes: 'inferred',
|
|
810
|
+
coordinateSystem: 'inferred'
|
|
568
811
|
}
|
|
569
812
|
},
|
|
570
813
|
segments: channels,
|
package/dist/worker.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { createDialectRunner, cura, klipper, marlin, orcaBambu, prusaSlicer, repRap } from '@chestnutlabs/gcode-dialects';
|
|
14
14
|
import { openGcode3mf, sniffGcode3mf } from '@chestnutlabs/gcode-containers';
|
|
15
|
+
import { openBgcodeContainer, sniffBgcode } from '@chestnutlabs/gcode-bgcode';
|
|
15
16
|
import { createWorkerHandler } from './worker-core.js';
|
|
16
17
|
/** Built-in dialect adapter set (DD-005 phases 3–4; phase 5 adds command semantics). */
|
|
17
18
|
const BUILTIN_ADAPTERS = [
|
|
@@ -22,9 +23,10 @@ const BUILTIN_ADAPTERS = [
|
|
|
22
23
|
marlin(),
|
|
23
24
|
repRap()
|
|
24
25
|
];
|
|
25
|
-
/** Built-in container adapters (DD-005 §4.4): .gcode.3mf. */
|
|
26
|
+
/** Built-in container adapters (DD-005 §4.4): .gcode.3mf and Prusa binary G-code (.bgcode, #188). */
|
|
26
27
|
const BUILTIN_CONTAINERS = [
|
|
27
|
-
{ id: 'gcode-3mf', sniff: sniffGcode3mf, open: (bytes) => openGcode3mf(bytes) }
|
|
28
|
+
{ id: 'gcode-3mf', sniff: sniffGcode3mf, open: (bytes) => openGcode3mf(bytes) },
|
|
29
|
+
{ id: 'bgcode', sniff: (prefix) => sniffBgcode(prefix), open: (bytes) => openBgcodeContainer(bytes) }
|
|
28
30
|
];
|
|
29
31
|
const post = (msg, transfer) => self.postMessage(msg, transfer);
|
|
30
32
|
const handle = createWorkerHandler(post, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chestnutlabs/gcode-parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Worker-safe G-code parse core producing ToolpathIR (DD-003).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gcode",
|
|
@@ -49,8 +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.
|
|
52
|
+
"@chestnutlabs/toolpath-core": "0.3.0",
|
|
53
|
+
"@chestnutlabs/gcode-dialects": "0.3.0",
|
|
54
|
+
"@chestnutlabs/gcode-containers": "0.3.0",
|
|
55
|
+
"@chestnutlabs/gcode-bgcode": "0.3.0"
|
|
55
56
|
}
|
|
56
57
|
}
|