Skip to content

Commit fd1f07a

Browse files
committed
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 341d3e7 commit fd1f07a

13 files changed

Lines changed: 1099 additions & 776 deletions

demo/js/app.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export const app = (window.app = createApp({
5353
const updateUI = async () => {
5454
const {
5555
parser,
56-
layers,
5756
extrusionColor,
5857
topLayerColor,
5958
lastSegmentColor,
@@ -65,16 +64,17 @@ export const app = (window.app = createApp({
6564
renderExtrusion,
6665
lineWidth,
6766
renderTubes,
68-
extrusionWidth
67+
extrusionWidth,
68+
job
6969
} = preview;
7070
const { thumbnails } = parser.metadata;
7171

7272
thumbnail.value = thumbnails['220x124']?.src;
73-
layerCount.value = layers.length;
73+
layerCount.value = job.layers()?.length;
7474
const colors = extrusionColor instanceof Array ? extrusionColor : [extrusionColor];
7575
const currentSettings = {
76-
maxLayer: layers.length,
77-
endLayer: layers.length,
76+
maxLayer: job.layers()?.length,
77+
endLayer: job.layers()?.length,
7878
singleLayerMode,
7979
renderTravel,
8080
travelColor: '#' + travelColor.getHexString(),
@@ -93,7 +93,7 @@ export const app = (window.app = createApp({
9393
};
9494

9595
Object.assign(settings.value, currentSettings);
96-
preview.endLayer = layers.length;
96+
preview.endLayer = job.layers()?.length;
9797
};
9898

9999
const loadGCodeFromServer = async (filename) => {
@@ -112,15 +112,21 @@ export const app = (window.app = createApp({
112112
const prevDevMode = preview.devMode;
113113
preview.clear();
114114
preview.devMode = prevDevMode;
115-
preview.parser.parseGCode(gcode);
115+
const { commands } = preview.parser.parseGCode(gcode);
116+
preview.interpreter.execute(commands, preview.job);
116117

117118
render();
118119
};
119120

120121
const render = async () => {
121122
debounce(async () => {
122123
if (loadProgressive) {
123-
await preview.renderAnimated(Math.ceil(preview.layers.length / 60));
124+
if (preview.job.layers() === null) {
125+
console.warn('Job is not planar');
126+
preview.render();
127+
return;
128+
}
129+
await preview.renderAnimated(Math.ceil(preview.job.layers().length / 60));
124130
} else {
125131
preview.render();
126132
}

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)