fix: parser skips non-drawing moves; clean up TS build

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-08 22:26:48 +02:00
parent c5c1b5e5c2
commit 960d453f57
3 changed files with 26 additions and 6 deletions

View file

@ -47,4 +47,23 @@ describe('parseGcode', () => {
expect(segments).toHaveLength(1); expect(segments).toHaveLength(1);
expect(warnings).toHaveLength(0); expect(warnings).toHaveLength(0);
}); });
it('does not emit segments for pure-Z plunge moves but keeps drawing XY after', () => {
const { segments } = parseGcode(`G21 G90\nG1 X5 Y0\nG1 Z-3\nG1 X10 Y0`);
expect(segments).toHaveLength(2);
expect(segments[1]!.points).toEqual([[5, 0], [10, 0]]);
});
it('does not emit a segment or warning for a bare motion word', () => {
const { segments, warnings } = parseGcode(`G21 G90\nG2\nG1 X5 Y0`);
expect(segments).toHaveLength(1);
expect(warnings).toHaveLength(0);
});
it('still flattens a full-circle arc where start == end', () => {
const { segments } = parseGcode(`G21 G90\nG0 X10 Y0\nG2 X10 Y0 I-10 J0`);
const arc = segments[1]!;
expect(arc.kind).toBe('cut');
expect(arc.points.length).toBeGreaterThan(4);
});
}); });

View file

@ -39,13 +39,11 @@ export function parseGcode(text: string): ParseResult {
} }
const axis: Record<string, number> = {}; const axis: Record<string, number> = {};
let motionThisLine: number | null = null;
for (const [letter, value] of words) { for (const [letter, value] of words) {
switch (letter) { switch (letter) {
case 'G': case 'G':
if (value === 0 || value === 1 || value === 2 || value === 3) { if (value === 0 || value === 1 || value === 2 || value === 3) {
st.motion = value; st.motion = value;
motionThisLine = value;
} else if (value === 20) st.scale = 25.4; } else if (value === 20) st.scale = 25.4;
else if (value === 21) st.scale = 1; else if (value === 21) st.scale = 1;
else if (value === 90) st.absolute = true; else if (value === 90) st.absolute = true;
@ -61,8 +59,12 @@ export function parseGcode(text: string): ParseResult {
} }
} }
const hasCoord = 'X' in axis || 'Y' in axis || 'Z' in axis; if ('Z' in axis) st.z = axis['Z']!; // track depth even on pure-Z moves
if (motionThisLine === null && !hasCoord) continue; // no movement on this line const hasXY = 'X' in axis || 'Y' in axis;
const hasArc = 'I' in axis || 'J' in axis || 'R' in axis;
// Only XY motion (or an arc, including full circles) yields a drawable segment.
// This skips bare motion words (e.g. `G1`), pure-Z plunges, and modal/comment lines.
if (!hasXY && !hasArc) continue;
const start: Vec2 = [st.x, st.y]; const start: Vec2 = [st.x, st.y];
const target = resolveTarget(st, axis); const target = resolveTarget(st, axis);
@ -88,7 +90,6 @@ export function parseGcode(text: string): ParseResult {
} }
st.x = target[0]; st.x = target[0];
st.y = target[1]; st.y = target[1];
if ('Z' in axis) st.z = axis['Z']!;
} }
return { segments, warnings }; return { segments, warnings };

View file

@ -17,7 +17,7 @@ describe('flattenArc', () => {
it('CW arc sweeps the other way (midpoint has negative y)', () => { it('CW arc sweeps the other way (midpoint has negative y)', () => {
// centre (0,0), radius 10, from (10,0) CW to (0,10) → goes the long way through y<0 // centre (0,0), radius 10, from (10,0) CW to (0,10) → goes the long way through y<0
const pts = flattenArc([10, 0], [0, 10], [0, 0], true, 0.1); const pts = flattenArc([10, 0], [0, 10], [0, 0], true, 0.1);
const mid = pts[Math.floor(pts.length / 2)]; const mid = pts[Math.floor(pts.length / 2)]!;
expect(mid[1]).toBeLessThan(0); expect(mid[1]).toBeLessThan(0);
}); });