Skip to content

Commit f5ca83b

Browse files
authored
Introduce an interpreter to extract command interpretation logic (#211)
* Interpreter prototype * arc support * units * Simplify toolchange * travelType * remove setInches * remove targetId * layers * render lines * extract machine * wip * Make it work with rerenders * Remove some layer references * Rename Machine to Job * Simplify parsing attributes * Am I going too far? * get rid of geometries once used * the geometries disposition is handled by the batchMesh * Fix tests * Bring back some code * Bring back progressive rendering * update dev-gui with job * Test Path * First interpreter tests * Test G0 and G1 * Test everything by G2 * Adding missing codes * Minimize diff on app.js * Leave G21 for a future PR * Job tests (and fixes!) * Keep G28 for a separate PR * Improve tests * Code improvements * Retractions are travel
1 parent 6b726b7 commit f5ca83b

13 files changed

Lines changed: 1100 additions & 795 deletions

demo/js/app.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ export const app = (window.app = createApp({
5454
const updateUI = async () => {
5555
const {
5656
parser,
57-
layers,
5857
extrusionColor,
5958
topLayerColor,
6059
lastSegmentColor,
@@ -66,16 +65,17 @@ export const app = (window.app = createApp({
6665
renderExtrusion,
6766
lineWidth,
6867
renderTubes,
69-
extrusionWidth
68+
extrusionWidth,
69+
job
7070
} = preview;
7171
const { thumbnails } = parser.metadata;
7272

7373
thumbnail.value = thumbnails['220x124']?.src;
74-
layerCount.value = layers.length;
74+
layerCount.value = job.layers()?.length;
7575
const colors = extrusionColor instanceof Array ? extrusionColor : [extrusionColor];
7676
const currentSettings = {
77-
maxLayer: layers.length,
78-
endLayer: layers.length,
77+
maxLayer: job.layers()?.length,
78+
endLayer: job.layers()?.length,
7979
singleLayerMode,
8080
renderTravel,
8181
travelColor: '#' + travelColor.getHexString(),
@@ -94,7 +94,7 @@ export const app = (window.app = createApp({
9494
};
9595

9696
Object.assign(settings.value, currentSettings);
97-
preview.endLayer = layers.length;
97+
preview.endLayer = job.layers()?.length;
9898
};
9999

100100
const loadGCodeFromServer = async (filename) => {
@@ -113,9 +113,16 @@ export const app = (window.app = createApp({
113113
const prevDevMode = preview.devMode;
114114
preview.clear();
115115
preview.devMode = prevDevMode;
116+
116117
if (loadProgressive) {
117-
preview.parser.parseGCode(gcode);
118-
// await preview.renderAnimated(Math.ceil(preview.layers.length / 60));
118+
const { commands } = preview.parser.parseGCode(gcode);
119+
preview.interpreter.execute(commands, preview.job);
120+
if (preview.job.layers() === null) {
121+
console.warn('Job is not planar');
122+
preview.render();
123+
return;
124+
}
125+
await preview.renderAnimated(Math.ceil(preview.job.layers().length / 60));
119126
} else {
120127
preview.processGCode(gcode);
121128
}
@@ -205,7 +212,7 @@ export const app = (window.app = createApp({
205212
preview.lastSegmentColor = settings.value.highlightLastSegment ? settings.value.lastSegmentColor : undefined;
206213

207214
debounce(() => {
208-
preview.renderAnimated(Math.ceil(preview.layers.length / 60));
215+
preview.renderAnimated(Math.ceil(preview.job.layers().length / 60));
209216
});
210217
});
211218
});

src/__tests__/gcode-parser.ts

Lines changed: 50 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,243 +1,126 @@
11
import { test, expect } from 'vitest';
2-
import { GCodeCommand, MoveCommand, Parser, SelectToolCommand } from '../gcode-parser';
2+
import { GCodeCommand, Parser } from '../gcode-parser';
33

4-
test('a single extrusion cmd should result in 1 layer with 1 command', () => {
5-
const parser = new Parser(0);
4+
test('a single extrusion cmd should result in 1 command', () => {
5+
const parser = new Parser();
66
const gcode = `G1 X0 Y0 Z1 E1`;
77
const parsed = parser.parseGCode(gcode);
88
expect(parsed).not.toBeNull();
9-
expect(parsed.layers).not.toBeNull();
10-
expect(parsed.layers.length).toEqual(1);
11-
expect(parsed.layers[0].commands).not.toBeNull();
12-
expect(parsed.layers[0].commands.length).toEqual(1);
9+
expect(parsed.commands).not.toBeNull();
10+
expect(parsed.commands.length).toEqual(1);
1311
});
1412

15-
test('a gcode cmd w/o extrusion should not result in a layer', () => {
16-
const parser = new Parser(0);
17-
const gcode = `G1 X0 Y0 Z1`;
13+
test('a single extrusion cmd should parse attributes', () => {
14+
const parser = new Parser();
15+
const gcode = `G1 X5 Y6 Z3 E1.9`;
1816
const parsed = parser.parseGCode(gcode);
19-
expect(parsed).not.toBeNull();
20-
expect(parsed.layers).not.toBeNull();
21-
expect(parsed.layers.length).toEqual(0);
22-
});
23-
24-
test('a gcode cmd with 0 extrusion should not result in a layer', () => {
25-
const parser = new Parser(0);
26-
const gcode = `G1 X0 Y0 Z1 E0`;
27-
const parsed = parser.parseGCode(gcode);
28-
expect(parsed).not.toBeNull();
29-
expect(parsed.layers).not.toBeNull();
30-
expect(parsed.layers.length).toEqual(0);
17+
const cmd = parsed.commands[0];
18+
expect(cmd.params.x).toEqual(5);
19+
expect(cmd.params.y).toEqual(6);
20+
expect(cmd.params.z).toEqual(3);
21+
expect(cmd.params.e).toEqual(1.9);
3122
});
3223

33-
test('2 horizontal extrusion moves should result in 1 layer with 2 commands', () => {
34-
const parser = new Parser(0);
35-
const gcode = `G1 X0 Y0 Z1 E1
36-
G1 X10 Y10 Z1 E2`;
24+
test('multiple cmd results in an array of commands', () => {
25+
const parser = new Parser();
26+
const gcode = `G1 X5 Y6 Z3 E1.9
27+
G1 X6 Y6 E1.9
28+
G1 X5 Y7 E1.9`;
3729
const parsed = parser.parseGCode(gcode);
38-
expect(parsed).not.toBeNull();
39-
expect(parsed.layers).not.toBeNull();
40-
expect(parsed.layers.length).toEqual(1);
41-
expect(parsed.layers[0].commands).not.toBeNull();
42-
expect(parsed.layers[0].commands.length).toEqual(2);
43-
});
44-
45-
test('2 vertical extrusion moves should result in 2 layers with 1 command', () => {
46-
const parser = new Parser(0);
47-
const gcode = `G1 X0 Y0 Z1 E1
48-
G1 X0 Y0 Z2 E2`;
49-
const parsed = parser.parseGCode(gcode);
50-
expect(parsed).not.toBeNull();
51-
expect(parsed.layers).not.toBeNull();
52-
expect(parsed.layers.length).toEqual(2);
53-
expect(parsed.layers[0].commands).not.toBeNull();
54-
expect(parsed.layers[0].commands.length).toEqual(1);
55-
});
56-
57-
test('2 vertical extrusion moves in consecutive gcode chunks should result in 2 layers with 1 command', () => {
58-
const parser = new Parser(0);
59-
const gcode1 = 'G1 X0 Y0 Z1 E1';
60-
const gcode2 = 'G1 X0 Y0 Z2 E2';
61-
const parsed = parser.parseGCode(gcode1);
62-
parser.parseGCode(gcode2);
63-
expect(parsed).not.toBeNull();
64-
expect(parsed.layers).not.toBeNull();
65-
expect(parsed.layers.length).toEqual(2);
66-
expect(parsed.layers[0].commands).not.toBeNull();
67-
expect(parsed.layers[0].commands.length).toEqual(1);
68-
});
69-
70-
test('2 vertical extrusion moves in consecutive gcode chunks as string arrays should result in 2 layers with 1 command', () => {
71-
const parser = new Parser(0);
72-
const gcode1 = ['G1 X0 Y0 Z1 E1'];
73-
const gcode2 = ['G1 X0 Y0 Z2 E2'];
74-
const parsed = parser.parseGCode(gcode1);
75-
parser.parseGCode(gcode2);
76-
expect(parsed).not.toBeNull();
77-
expect(parsed.layers).not.toBeNull();
78-
expect(parsed.layers.length).toEqual(2);
79-
expect(parsed.layers[0].commands).not.toBeNull();
80-
expect(parsed.layers[0].commands.length).toEqual(1);
81-
});
82-
83-
test('2 extrusion moves with a z difference below the threshold should result in only 1 layer', () => {
84-
const threshold = 1;
85-
const parser = new Parser(threshold);
86-
const gcode = `G1 X0 Y0 Z1 E1
87-
G1 X10 Y10 Z1.5 E2`;
88-
const parsed = parser.parseGCode(gcode);
89-
expect(parsed).not.toBeNull();
90-
expect(parsed.layers).not.toBeNull();
91-
expect(parsed.layers.length).toEqual(1);
92-
expect(parsed.layers[0].commands).not.toBeNull();
93-
expect(parsed.layers[0].commands.length).toEqual(2);
94-
});
95-
96-
test('2 extrusion moves with a z difference above the threshold should result in 2 layers', () => {
97-
const threshold = 1;
98-
const parser = new Parser(threshold);
99-
const gcode = `G1 X0 Y0 Z1 E1
100-
G1 X10 Y10 Z3 E2`;
101-
const parsed = parser.parseGCode(gcode);
102-
expect(parsed).not.toBeNull();
103-
expect(parsed.layers).not.toBeNull();
104-
expect(parsed.layers.length).toEqual(2);
105-
expect(parsed.layers[0].commands).not.toBeNull();
106-
expect(parsed.layers[0].commands.length).toEqual(1);
107-
expect(parsed.layers[0].commands).not.toBeNull();
108-
expect(parsed.layers[0].commands.length).toEqual(1);
109-
});
110-
111-
test('2 extrusion moves with a z diff exactly at the threshold should result in 1 layer', () => {
112-
const threshold = 1;
113-
const parser = new Parser(threshold);
114-
const gcode = `G1 X0 Y0 Z1 E1
115-
G1 X10 Y10 Z2 E2`;
116-
const parsed = parser.parseGCode(gcode);
117-
expect(parsed).not.toBeNull();
118-
expect(parsed.layers).not.toBeNull();
119-
expect(parsed.layers.length).toEqual(1);
120-
expect(parsed.layers[0].commands).not.toBeNull();
121-
expect(parsed.layers[0].commands.length).toEqual(2);
122-
});
123-
124-
test('Layers should have calculated heights', () => {
125-
const threshold = 0.05;
126-
const parser = new Parser(threshold);
127-
const gcode = `G0 X0 Y0 Z0.1 E1
128-
G1 X10 Y10 Z0.2 E2
129-
G1 X20 Y20 Z0.3 E3
130-
G1 X30 Y30 Z0.5 E4
131-
G1 X40 Y40 Z0.8 E5
132-
`;
133-
const parsed = parser.parseGCode(gcode);
134-
expect(parsed).not.toBeNull();
135-
expect(parsed.layers).not.toBeNull();
136-
expect(parsed.layers.length).toEqual(5);
137-
expect(parsed.layers[0].height).toEqual(expect.closeTo(0.1, 3));
138-
expect(parsed.layers[1].height).toEqual(expect.closeTo(0.1, 3));
139-
expect(parsed.layers[2].height).toEqual(expect.closeTo(0.1, 3));
140-
expect(parsed.layers[3].height).toEqual(expect.closeTo(0.2, 3));
30+
expect(parsed.commands).not.toBeNull();
31+
expect(parsed.commands.length).toEqual(3);
14132
});
14233

143-
test('T0 command should result in a tool change to tool with index 0', () => {
144-
const parser = new Parser(0);
34+
test('T0 command should result in a tool change', () => {
35+
const parser = new Parser();
14536
const gcode = `G1 X0 Y0 Z1 E1
14637
T0`;
14738
const parsed = parser.parseGCode(gcode);
14839
expect(parsed).not.toBeNull();
149-
expect(parsed.layers).not.toBeNull();
150-
expect(parsed.layers.length).toEqual(1);
151-
expect(parsed.layers[0].commands).not.toBeNull();
40+
expect(parsed.commands).not.toBeNull();
41+
expect(parsed.commands.length).toEqual(2);
15242

153-
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
43+
const cmd = parsed.commands[1];
15444
expect(cmd.gcode).toEqual('t0');
155-
expect(cmd.toolIndex).toEqual(0);
15645
});
15746

158-
test('T1 command should result in a tool change to tool with index 0', () => {
159-
const parser = new Parser(0);
47+
test('T1 command should result in a tool change', () => {
48+
const parser = new Parser();
16049
const gcode = `G1 X0 Y0 Z1 E1
16150
T1`;
16251
const parsed = parser.parseGCode(gcode);
163-
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
52+
const cmd = parsed.commands[1];
16453
expect(cmd.gcode).toEqual('t1');
165-
expect(cmd.toolIndex).toEqual(1);
16654
});
16755

168-
test('T2 command should result in a tool change to tool with index 0', () => {
169-
const parser = new Parser(0);
56+
test('T2 command should result in a tool change', () => {
57+
const parser = new Parser();
17058
const gcode = `G1 X0 Y0 Z1 E1
17159
T2`;
17260
const parsed = parser.parseGCode(gcode);
173-
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
61+
const cmd = parsed.commands[1];
17462
expect(cmd.gcode).toEqual('t2');
175-
expect(cmd.toolIndex).toEqual(2);
17663
});
17764

178-
test('T3 command should result in a tool change to tool with index 0', () => {
179-
const parser = new Parser(0);
65+
test('T3 command should result in a tool change', () => {
66+
const parser = new Parser();
18067
const gcode = `G1 X0 Y0 Z1 E1
18168
T3`;
18269
const parsed = parser.parseGCode(gcode);
183-
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
70+
const cmd = parsed.commands[1];
18471
expect(cmd.gcode).toEqual('t3');
185-
expect(cmd.toolIndex).toEqual(3);
18672
});
18773

18874
// repeat fot T4 .. T7
189-
test('T4 command should result in a tool change to tool with index 0', () => {
190-
const parser = new Parser(0);
75+
test('T4 command should result in a tool change', () => {
76+
const parser = new Parser();
19177
const gcode = `G1 X0 Y0 Z1 E1
19278
T4`;
19379
const parsed = parser.parseGCode(gcode);
194-
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
80+
const cmd = parsed.commands[1];
19581
expect(cmd.gcode).toEqual('t4');
196-
expect(cmd.toolIndex).toEqual(4);
19782
});
19883

199-
test('T5 command should result in a tool change to tool with index 0', () => {
200-
const parser = new Parser(0);
84+
test('T5 command should result in a tool change', () => {
85+
const parser = new Parser();
20186
const gcode = `G1 X0 Y0 Z1 E1
20287
T5`;
20388
const parsed = parser.parseGCode(gcode);
204-
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
89+
const cmd = parsed.commands[1];
20590
expect(cmd.gcode).toEqual('t5');
206-
expect(cmd.toolIndex).toEqual(5);
20791
});
20892

209-
test('T6 command should result in a tool change to tool with index 0', () => {
210-
const parser = new Parser(0);
93+
test('T6 command should result in a tool change', () => {
94+
const parser = new Parser();
21195
const gcode = `G1 X0 Y0 Z1 E1
21296
T6`;
21397
const parsed = parser.parseGCode(gcode);
214-
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
98+
const cmd = parsed.commands[1];
21599
expect(cmd.gcode).toEqual('t6');
216-
expect(cmd.toolIndex).toEqual(6);
217100
});
218101

219-
test('T7 command should result in a tool change to tool with index 0', () => {
220-
const parser = new Parser(0);
102+
test('T7 command should result in a tool change', () => {
103+
const parser = new Parser();
221104
const gcode = `G1 X0 Y0 Z1 E1
222105
T7`;
223106
const parsed = parser.parseGCode(gcode);
224-
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
107+
const cmd = parsed.commands[1];
225108
expect(cmd.gcode).toEqual('t7');
226-
expect(cmd.toolIndex).toEqual(7);
227109
});
228110

229111
test('gcode commands with spaces between letters and numbers should be parsed correctly', () => {
230-
const parser = new Parser(0);
112+
const parser = new Parser();
231113
const gcode = `G 1 E 42 X 42`;
232114
const parsed = parser.parseGCode(gcode);
233-
const cmd = parsed.layers[0].commands[0] as MoveCommand;
115+
const cmd = parsed.commands[0];
234116
expect(cmd.gcode).toEqual('g1');
235117
expect(cmd.params.x).toEqual(42);
118+
expect(cmd.params.e).toEqual(42);
236119
});
237120

238121
// test that a line withouth a gcode command results in a command with empty string gcode
239122
test('gcode commands without gcode should result in a command with empty string gcode', () => {
240-
const parser = new Parser(0);
123+
const parser = new Parser();
241124
const gcode = ` ; comment`;
242125
const cmd = parser.parseCommand(gcode) as GCodeCommand;
243126
expect(cmd.gcode).toEqual('');

0 commit comments

Comments
 (0)