Skip to content

Commit

Permalink
Introduce an interpreter to extract command interpretation logic (#211)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sophiedeziel authored Oct 13, 2024
1 parent 6b726b7 commit f5ca83b
Show file tree
Hide file tree
Showing 13 changed files with 1,100 additions and 795 deletions.
25 changes: 16 additions & 9 deletions demo/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ export const app = (window.app = createApp({
const updateUI = async () => {
const {
parser,
layers,
extrusionColor,
topLayerColor,
lastSegmentColor,
Expand All @@ -66,16 +65,17 @@ export const app = (window.app = createApp({
renderExtrusion,
lineWidth,
renderTubes,
extrusionWidth
extrusionWidth,
job
} = preview;
const { thumbnails } = parser.metadata;

thumbnail.value = thumbnails['220x124']?.src;
layerCount.value = layers.length;
layerCount.value = job.layers()?.length;
const colors = extrusionColor instanceof Array ? extrusionColor : [extrusionColor];
const currentSettings = {
maxLayer: layers.length,
endLayer: layers.length,
maxLayer: job.layers()?.length,
endLayer: job.layers()?.length,
singleLayerMode,
renderTravel,
travelColor: '#' + travelColor.getHexString(),
Expand All @@ -94,7 +94,7 @@ export const app = (window.app = createApp({
};

Object.assign(settings.value, currentSettings);
preview.endLayer = layers.length;
preview.endLayer = job.layers()?.length;
};

const loadGCodeFromServer = async (filename) => {
Expand All @@ -113,9 +113,16 @@ export const app = (window.app = createApp({
const prevDevMode = preview.devMode;
preview.clear();
preview.devMode = prevDevMode;

if (loadProgressive) {
preview.parser.parseGCode(gcode);
// await preview.renderAnimated(Math.ceil(preview.layers.length / 60));
const { commands } = preview.parser.parseGCode(gcode);
preview.interpreter.execute(commands, preview.job);
if (preview.job.layers() === null) {
console.warn('Job is not planar');
preview.render();
return;
}
await preview.renderAnimated(Math.ceil(preview.job.layers().length / 60));
} else {
preview.processGCode(gcode);
}
Expand Down Expand Up @@ -205,7 +212,7 @@ export const app = (window.app = createApp({
preview.lastSegmentColor = settings.value.highlightLastSegment ? settings.value.lastSegmentColor : undefined;

debounce(() => {
preview.renderAnimated(Math.ceil(preview.layers.length / 60));
preview.renderAnimated(Math.ceil(preview.job.layers().length / 60));
});
});
});
Expand Down
217 changes: 50 additions & 167 deletions src/__tests__/gcode-parser.ts
Original file line number Diff line number Diff line change
@@ -1,243 +1,126 @@
import { test, expect } from 'vitest';
import { GCodeCommand, MoveCommand, Parser, SelectToolCommand } from '../gcode-parser';
import { GCodeCommand, Parser } from '../gcode-parser';

test('a single extrusion cmd should result in 1 layer with 1 command', () => {
const parser = new Parser(0);
test('a single extrusion cmd should result in 1 command', () => {
const parser = new Parser();
const gcode = `G1 X0 Y0 Z1 E1`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(1);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.layers[0].commands.length).toEqual(1);
expect(parsed.commands).not.toBeNull();
expect(parsed.commands.length).toEqual(1);
});

test('a gcode cmd w/o extrusion should not result in a layer', () => {
const parser = new Parser(0);
const gcode = `G1 X0 Y0 Z1`;
test('a single extrusion cmd should parse attributes', () => {
const parser = new Parser();
const gcode = `G1 X5 Y6 Z3 E1.9`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(0);
});

test('a gcode cmd with 0 extrusion should not result in a layer', () => {
const parser = new Parser(0);
const gcode = `G1 X0 Y0 Z1 E0`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(0);
const cmd = parsed.commands[0];
expect(cmd.params.x).toEqual(5);
expect(cmd.params.y).toEqual(6);
expect(cmd.params.z).toEqual(3);
expect(cmd.params.e).toEqual(1.9);
});

test('2 horizontal extrusion moves should result in 1 layer with 2 commands', () => {
const parser = new Parser(0);
const gcode = `G1 X0 Y0 Z1 E1
G1 X10 Y10 Z1 E2`;
test('multiple cmd results in an array of commands', () => {
const parser = new Parser();
const gcode = `G1 X5 Y6 Z3 E1.9
G1 X6 Y6 E1.9
G1 X5 Y7 E1.9`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(1);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.layers[0].commands.length).toEqual(2);
});

test('2 vertical extrusion moves should result in 2 layers with 1 command', () => {
const parser = new Parser(0);
const gcode = `G1 X0 Y0 Z1 E1
G1 X0 Y0 Z2 E2`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(2);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.layers[0].commands.length).toEqual(1);
});

test('2 vertical extrusion moves in consecutive gcode chunks should result in 2 layers with 1 command', () => {
const parser = new Parser(0);
const gcode1 = 'G1 X0 Y0 Z1 E1';
const gcode2 = 'G1 X0 Y0 Z2 E2';
const parsed = parser.parseGCode(gcode1);
parser.parseGCode(gcode2);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(2);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.layers[0].commands.length).toEqual(1);
});

test('2 vertical extrusion moves in consecutive gcode chunks as string arrays should result in 2 layers with 1 command', () => {
const parser = new Parser(0);
const gcode1 = ['G1 X0 Y0 Z1 E1'];
const gcode2 = ['G1 X0 Y0 Z2 E2'];
const parsed = parser.parseGCode(gcode1);
parser.parseGCode(gcode2);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(2);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.layers[0].commands.length).toEqual(1);
});

test('2 extrusion moves with a z difference below the threshold should result in only 1 layer', () => {
const threshold = 1;
const parser = new Parser(threshold);
const gcode = `G1 X0 Y0 Z1 E1
G1 X10 Y10 Z1.5 E2`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(1);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.layers[0].commands.length).toEqual(2);
});

test('2 extrusion moves with a z difference above the threshold should result in 2 layers', () => {
const threshold = 1;
const parser = new Parser(threshold);
const gcode = `G1 X0 Y0 Z1 E1
G1 X10 Y10 Z3 E2`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(2);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.layers[0].commands.length).toEqual(1);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.layers[0].commands.length).toEqual(1);
});

test('2 extrusion moves with a z diff exactly at the threshold should result in 1 layer', () => {
const threshold = 1;
const parser = new Parser(threshold);
const gcode = `G1 X0 Y0 Z1 E1
G1 X10 Y10 Z2 E2`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(1);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.layers[0].commands.length).toEqual(2);
});

test('Layers should have calculated heights', () => {
const threshold = 0.05;
const parser = new Parser(threshold);
const gcode = `G0 X0 Y0 Z0.1 E1
G1 X10 Y10 Z0.2 E2
G1 X20 Y20 Z0.3 E3
G1 X30 Y30 Z0.5 E4
G1 X40 Y40 Z0.8 E5
`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(5);
expect(parsed.layers[0].height).toEqual(expect.closeTo(0.1, 3));
expect(parsed.layers[1].height).toEqual(expect.closeTo(0.1, 3));
expect(parsed.layers[2].height).toEqual(expect.closeTo(0.1, 3));
expect(parsed.layers[3].height).toEqual(expect.closeTo(0.2, 3));
expect(parsed.commands).not.toBeNull();
expect(parsed.commands.length).toEqual(3);
});

test('T0 command should result in a tool change to tool with index 0', () => {
const parser = new Parser(0);
test('T0 command should result in a tool change', () => {
const parser = new Parser();
const gcode = `G1 X0 Y0 Z1 E1
T0`;
const parsed = parser.parseGCode(gcode);
expect(parsed).not.toBeNull();
expect(parsed.layers).not.toBeNull();
expect(parsed.layers.length).toEqual(1);
expect(parsed.layers[0].commands).not.toBeNull();
expect(parsed.commands).not.toBeNull();
expect(parsed.commands.length).toEqual(2);

const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
const cmd = parsed.commands[1];
expect(cmd.gcode).toEqual('t0');
expect(cmd.toolIndex).toEqual(0);
});

test('T1 command should result in a tool change to tool with index 0', () => {
const parser = new Parser(0);
test('T1 command should result in a tool change', () => {
const parser = new Parser();
const gcode = `G1 X0 Y0 Z1 E1
T1`;
const parsed = parser.parseGCode(gcode);
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
const cmd = parsed.commands[1];
expect(cmd.gcode).toEqual('t1');
expect(cmd.toolIndex).toEqual(1);
});

test('T2 command should result in a tool change to tool with index 0', () => {
const parser = new Parser(0);
test('T2 command should result in a tool change', () => {
const parser = new Parser();
const gcode = `G1 X0 Y0 Z1 E1
T2`;
const parsed = parser.parseGCode(gcode);
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
const cmd = parsed.commands[1];
expect(cmd.gcode).toEqual('t2');
expect(cmd.toolIndex).toEqual(2);
});

test('T3 command should result in a tool change to tool with index 0', () => {
const parser = new Parser(0);
test('T3 command should result in a tool change', () => {
const parser = new Parser();
const gcode = `G1 X0 Y0 Z1 E1
T3`;
const parsed = parser.parseGCode(gcode);
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
const cmd = parsed.commands[1];
expect(cmd.gcode).toEqual('t3');
expect(cmd.toolIndex).toEqual(3);
});

// repeat fot T4 .. T7
test('T4 command should result in a tool change to tool with index 0', () => {
const parser = new Parser(0);
test('T4 command should result in a tool change', () => {
const parser = new Parser();
const gcode = `G1 X0 Y0 Z1 E1
T4`;
const parsed = parser.parseGCode(gcode);
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
const cmd = parsed.commands[1];
expect(cmd.gcode).toEqual('t4');
expect(cmd.toolIndex).toEqual(4);
});

test('T5 command should result in a tool change to tool with index 0', () => {
const parser = new Parser(0);
test('T5 command should result in a tool change', () => {
const parser = new Parser();
const gcode = `G1 X0 Y0 Z1 E1
T5`;
const parsed = parser.parseGCode(gcode);
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
const cmd = parsed.commands[1];
expect(cmd.gcode).toEqual('t5');
expect(cmd.toolIndex).toEqual(5);
});

test('T6 command should result in a tool change to tool with index 0', () => {
const parser = new Parser(0);
test('T6 command should result in a tool change', () => {
const parser = new Parser();
const gcode = `G1 X0 Y0 Z1 E1
T6`;
const parsed = parser.parseGCode(gcode);
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
const cmd = parsed.commands[1];
expect(cmd.gcode).toEqual('t6');
expect(cmd.toolIndex).toEqual(6);
});

test('T7 command should result in a tool change to tool with index 0', () => {
const parser = new Parser(0);
test('T7 command should result in a tool change', () => {
const parser = new Parser();
const gcode = `G1 X0 Y0 Z1 E1
T7`;
const parsed = parser.parseGCode(gcode);
const cmd = parsed.layers[0].commands[1] as SelectToolCommand;
const cmd = parsed.commands[1];
expect(cmd.gcode).toEqual('t7');
expect(cmd.toolIndex).toEqual(7);
});

test('gcode commands with spaces between letters and numbers should be parsed correctly', () => {
const parser = new Parser(0);
const parser = new Parser();
const gcode = `G 1 E 42 X 42`;
const parsed = parser.parseGCode(gcode);
const cmd = parsed.layers[0].commands[0] as MoveCommand;
const cmd = parsed.commands[0];
expect(cmd.gcode).toEqual('g1');
expect(cmd.params.x).toEqual(42);
expect(cmd.params.e).toEqual(42);
});

// test that a line withouth a gcode command results in a command with empty string gcode
test('gcode commands without gcode should result in a command with empty string gcode', () => {
const parser = new Parser(0);
const parser = new Parser();
const gcode = ` ; comment`;
const cmd = parser.parseCommand(gcode) as GCodeCommand;
expect(cmd.gcode).toEqual('');
Expand Down
Loading

0 comments on commit f5ca83b

Please sign in to comment.