diff --git a/demo/js/app.js b/demo/js/app.js index 2c4872db..9daf46b9 100644 --- a/demo/js/app.js +++ b/demo/js/app.js @@ -70,11 +70,11 @@ export const app = (window.app = createApp({ const { thumbnails } = parser.metadata; thumbnail.value = thumbnails['220x124']?.src; - layerCount.value = job.layers()?.length; + layerCount.value = job.layers?.length; const colors = extrusionColor instanceof Array ? extrusionColor : [extrusionColor]; const currentSettings = { - maxLayer: job.layers()?.length, - endLayer: job.layers()?.length, + maxLayer: job.layers?.length, + endLayer: job.layers?.length, singleLayerMode, renderTravel, travelColor: '#' + travelColor.getHexString(), @@ -93,7 +93,7 @@ export const app = (window.app = createApp({ }; Object.assign(settings.value, currentSettings); - preview.endLayer = job.layers()?.length; + preview.endLayer = job.layers?.length; }; const loadGCodeFromServer = async (filename) => { @@ -121,12 +121,12 @@ export const app = (window.app = createApp({ const render = async () => { debounce(async () => { if (loadProgressive) { - if (preview.job.layers() === null) { + if (preview.job.layers === null) { console.warn('Job is not planar'); preview.render(); return; } - await preview.renderAnimated(Math.ceil(preview.job.layers().length / 60)); + await preview.renderAnimated(Math.ceil(preview.job.layers?.length / 60)); } else { preview.render(); } diff --git a/src/__tests__/interpreter.ts b/src/__tests__/interpreter.ts index d53b6a2d..98fb1910 100644 --- a/src/__tests__/interpreter.ts +++ b/src/__tests__/interpreter.ts @@ -74,14 +74,15 @@ test('.G0 starts a path if the job has none', () => { const job = interpreter.execute([command]); - expect(job.paths.length).toEqual(1); - expect(job.paths[0].vertices.length).toEqual(6); - expect(job.paths[0].vertices[0]).toEqual(0); - expect(job.paths[0].vertices[1]).toEqual(0); - expect(job.paths[0].vertices[2]).toEqual(0); - expect(job.paths[0].vertices[3]).toEqual(1); - expect(job.paths[0].vertices[4]).toEqual(2); - expect(job.paths[0].vertices[5]).toEqual(0); + expect(job.paths.length).toEqual(0); + expect(job.inprogressPath).not.toBeNull(); + expect(job.inprogressPath?.vertices.length).toEqual(6); + expect(job.inprogressPath?.vertices[0]).toEqual(0); + expect(job.inprogressPath?.vertices[1]).toEqual(0); + expect(job.inprogressPath?.vertices[2]).toEqual(0); + expect(job.inprogressPath?.vertices[3]).toEqual(1); + expect(job.inprogressPath?.vertices[4]).toEqual(2); + expect(job.inprogressPath?.vertices[5]).toEqual(0); }); test('.G0 starts a path if the job has none, starting at the job current state', () => { @@ -94,12 +95,12 @@ test('.G0 starts a path if the job has none, starting at the job current state', interpreter.execute([command], job); - expect(job.paths.length).toEqual(1); - expect(job.paths[0].vertices.length).toEqual(6); - expect(job.paths[0].vertices[0]).toEqual(3); - expect(job.paths[0].vertices[1]).toEqual(4); - expect(job.paths[0].vertices[2]).toEqual(0); - expect(job.paths[0].tool).toEqual(5); + expect(job.paths.length).toEqual(0); + expect(job.inprogressPath?.vertices.length).toEqual(6); + expect(job.inprogressPath?.vertices[0]).toEqual(3); + expect(job.inprogressPath?.vertices[1]).toEqual(4); + expect(job.inprogressPath?.vertices[2]).toEqual(0); + expect(job.inprogressPath?.tool).toEqual(5); }); test('.G0 continues the path if the job has one', () => { @@ -113,11 +114,11 @@ test('.G0 continues the path if the job has one', () => { interpreter.G0(command2, job); - expect(job.paths.length).toEqual(1); - expect(job.paths[0].vertices.length).toEqual(9); - expect(job.paths[0].vertices[6]).toEqual(3); - expect(job.paths[0].vertices[7]).toEqual(4); - expect(job.paths[0].vertices[8]).toEqual(5); + expect(job.paths.length).toEqual(0); + expect(job.inprogressPath?.vertices.length).toEqual(9); + expect(job.inprogressPath?.vertices[6]).toEqual(3); + expect(job.inprogressPath?.vertices[7]).toEqual(4); + expect(job.inprogressPath?.vertices[8]).toEqual(5); }); test(".G0 assigns the travel type if there's no extrusion", () => { @@ -127,8 +128,8 @@ test(".G0 assigns the travel type if there's no extrusion", () => { interpreter.G0(command, job); - expect(job.paths.length).toEqual(1); - expect(job.paths[0].travelType).toEqual(PathType.Travel); + expect(job.paths.length).toEqual(0); + expect(job.inprogressPath?.travelType).toEqual(PathType.Travel); }); test(".G0 assigns the extrusion type if there's extrusion", () => { @@ -138,8 +139,8 @@ test(".G0 assigns the extrusion type if there's extrusion", () => { interpreter.G0(command, job); - expect(job.paths.length).toEqual(1); - expect(job.paths[0].travelType).toEqual('Extrusion'); + expect(job.paths.length).toEqual(0); + expect(job.inprogressPath?.travelType).toEqual('Extrusion'); }); test('.G0 assigns the travel type if the extrusion is a retraction', () => { @@ -149,8 +150,19 @@ test('.G0 assigns the travel type if the extrusion is a retraction', () => { interpreter.G0(command, job); - expect(job.paths.length).toEqual(1); - expect(job.paths[0].travelType).toEqual('Travel'); + expect(job.paths.length).toEqual(0); + expect(job.inprogressPath?.travelType).toEqual('Travel'); +}); + +test('.G0 assigns the travel type if the extrusion is a retraction', () => { + const command = new GCodeCommand('G0 E-2', 'g0', { e: -2 }); + const interpreter = new Interpreter(); + const job = new Job(); + + interpreter.G0(command, job); + + expect(job.paths.length).toEqual(0); + expect(job.inprogressPath?.travelType).toEqual('Travel'); }); test('.G0 starts a new path if the travel type changes from Travel to Extrusion', () => { @@ -162,9 +174,8 @@ test('.G0 starts a new path if the travel type changes from Travel to Extrusion' interpreter.G0(command2, job); - expect(job.paths.length).toEqual(2); - expect(job.paths[0].travelType).toEqual('Travel'); - expect(job.paths[1].travelType).toEqual('Extrusion'); + expect(job.paths.length).toEqual(1); + expect(job.inprogressPath?.travelType).toEqual('Extrusion'); }); test('.G0 starts a new path if the travel type changes from Extrusion to Travel', () => { @@ -176,9 +187,8 @@ test('.G0 starts a new path if the travel type changes from Extrusion to Travel' interpreter.G0(command2, job); - expect(job.paths.length).toEqual(2); - expect(job.paths[0].travelType).toEqual('Extrusion'); - expect(job.paths[1].travelType).toEqual('Travel'); + expect(job.paths.length).toEqual(1); + expect(job.inprogressPath?.travelType).toEqual('Travel'); }); test('.G1 is an alias to .G0', () => { diff --git a/src/__tests__/job.ts b/src/__tests__/job.ts index 8d1d0538..7968fa60 100644 --- a/src/__tests__/job.ts +++ b/src/__tests__/job.ts @@ -12,8 +12,14 @@ describe('.isPlanar', () => { test('returns true if all extrusions are on the same plane', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Extrusion, [1, 2, 0, 5, 6, 0]); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Extrusion, [ + [1, 2, 0], + [5, 6, 0] + ]); expect(job.isPlanar()).toEqual(true); }); @@ -21,8 +27,14 @@ describe('.isPlanar', () => { test('returns false if any extrusions are on a different plane', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Extrusion, [1, 2, 0, 5, 6, 1]); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Extrusion, [ + [1, 2, 0], + [5, 6, 1] + ]); expect(job.isPlanar()).toEqual(false); }); @@ -30,9 +42,19 @@ describe('.isPlanar', () => { test('ignores travel paths', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 1, 1, 2, 0]); - append_path(job, PathType.Extrusion, [1, 2, 0, 5, 6, 0]); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 1], + [1, 2, 0] + ]); + append_path(job, PathType.Extrusion, [ + [1, 2, 0], + [5, 6, 0] + ]); expect(job.isPlanar()).toEqual(true); }); @@ -42,19 +64,31 @@ describe('.layers', () => { test('returns null if the job is not planar', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Extrusion, [5, 6, 0, 5, 6, 1]); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Extrusion, [ + [5, 6, 0], + [5, 6, 1] + ]); - expect(job.layers()).toEqual(null); + expect(job.layers).toEqual(null); }); test('paths without z changes are on the same layer', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 0]); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 0] + ]); - const layers = job.layers(); + const layers = job.layers; expect(layers).not.toBeNull(); expect(layers).toBeInstanceOf(Array); @@ -65,10 +99,16 @@ describe('.layers', () => { test('travel paths moving z create a new layer', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 1]); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 1] + ]); - const layers = job.layers(); + const layers = job.layers; expect(layers).not.toBeNull(); expect(layers).toBeInstanceOf(Array); @@ -80,12 +120,24 @@ describe('.layers', () => { test('multiple travels in a row are on the same layer', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 2]); - append_path(job, PathType.Travel, [5, 6, 2, 5, 6, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 2]); - - const layers = job.layers(); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 2] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 2], + [5, 6, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 2] + ]); + + const layers = job.layers; expect(layers).not.toBeNull(); expect(layers).toBeInstanceOf(Array); @@ -97,13 +149,28 @@ describe('.layers', () => { test('extrusions after travels are on the same layer', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 2]); - append_path(job, PathType.Travel, [5, 6, 2, 5, 6, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 2]); - append_path(job, PathType.Extrusion, [5, 6, 2, 5, 6, 2]); - - const layers = job.layers(); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 2] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 2], + [5, 6, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 2] + ]); + append_path(job, PathType.Extrusion, [ + [5, 6, 2], + [5, 6, 2] + ]); + + const layers = job.layers; expect(layers).not.toBeNull(); expect(layers).toBeInstanceOf(Array); @@ -117,11 +184,20 @@ describe('.extrusions', () => { test('returns all extrusion paths', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 0]); - append_path(job, PathType.Extrusion, [1, 2, 0, 5, 6, 0]); - - const extrusions = job.extrusions(); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 0] + ]); + append_path(job, PathType.Extrusion, [ + [1, 2, 0], + [5, 6, 0] + ]); + + const extrusions = job.extrusions; expect(extrusions).not.toBeNull(); expect(extrusions).toBeInstanceOf(Array); @@ -136,12 +212,24 @@ describe('.travels', () => { test('returns all travel paths', () => { const job = new Job(); - append_path(job, PathType.Extrusion, [0, 0, 0, 1, 2, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 0]); - append_path(job, PathType.Extrusion, [1, 2, 0, 5, 6, 0]); - append_path(job, PathType.Travel, [5, 6, 0, 5, 6, 0]); - - const travels = job.travels(); + append_path(job, PathType.Extrusion, [ + [0, 0, 0], + [1, 2, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 0] + ]); + append_path(job, PathType.Extrusion, [ + [1, 2, 0], + [5, 6, 0] + ]); + append_path(job, PathType.Travel, [ + [5, 6, 0], + [5, 6, 0] + ]); + + const travels = job.travels; expect(travels).not.toBeNull(); expect(travels).toBeInstanceOf(Array); @@ -152,8 +240,8 @@ describe('.travels', () => { }); }); -function append_path(job, travelType, vertices) { +function append_path(job, travelType, points) { const path = new Path(travelType, 0.6, 0.2, job.state.tool); - path.vertices = vertices; - job.paths.push(path); + points.forEach((point: [number, number, number]) => path.addPoint(...point)); + job.addPath(path); } diff --git a/src/__tests__/path.ts b/src/__tests__/path.ts index af040e0b..c845d475 100644 --- a/src/__tests__/path.ts +++ b/src/__tests__/path.ts @@ -67,7 +67,8 @@ test('.path returns an array of Vector3', () => { test('.geometry returns an ExtrusionGeometry from the path', () => { const path = new Path(PathType.Travel, undefined, undefined, undefined); - path.vertices = [0, 0, 0, 1, 2, 3]; + path.addPoint(0, 0, 0); + path.addPoint(1, 2, 3); const result = path.geometry() as ExtrusionGeometry; @@ -80,7 +81,8 @@ test('.geometry returns an ExtrusionGeometry from the path', () => { test('.geometry returns an ExtrusionGeometry with the path extrusion width', () => { const path = new Path(PathType.Travel, 9, undefined, undefined); - path.vertices = [0, 0, 0, 1, 2, 3]; + path.addPoint(0, 0, 0); + path.addPoint(1, 2, 3); const result = path.geometry() as ExtrusionGeometry; @@ -90,7 +92,8 @@ test('.geometry returns an ExtrusionGeometry with the path extrusion width', () test('.geometry returns an ExtrusionGeometry with the path line height', () => { const path = new Path(PathType.Travel, undefined, 5, undefined); - path.vertices = [0, 0, 0, 1, 2, 3]; + path.addPoint(0, 0, 0); + path.addPoint(1, 2, 3); const result = path.geometry() as ExtrusionGeometry; @@ -109,7 +112,8 @@ test('.geometry returns an empty BufferGeometry if there are less than 3 vertice test('.line returns a BufferGeometry from the path', () => { const path = new Path(PathType.Travel, undefined, undefined, undefined); - path.vertices = [0, 0, 0, 1, 2, 3]; + path.addPoint(0, 0, 0); + path.addPoint(1, 2, 3); const result = path.line(); diff --git a/src/interpreter.ts b/src/interpreter.ts index b91ebe7b..40523d04 100644 --- a/src/interpreter.ts +++ b/src/interpreter.ts @@ -17,18 +17,18 @@ export class Interpreter { const { x, y, z, e } = command.params; const { state } = job; - let lastPath = job.paths[job.paths.length - 1]; + let currentPath = job.inprogressPath; const pathType = e > 0 ? PathType.Extrusion : PathType.Travel; - if (lastPath === undefined || lastPath.travelType !== pathType) { - lastPath = this.breakPath(job, pathType); + if (currentPath === undefined || currentPath.travelType !== pathType) { + currentPath = this.breakPath(job, pathType); } state.x = x ?? state.x; state.y = y ?? state.y; state.z = z ?? state.z; - lastPath.addPoint(state.x, state.y, state.z); + currentPath.addPoint(state.x, state.y, state.z); } G1 = this.G0; @@ -39,11 +39,11 @@ export class Interpreter { const { state } = job; const cw = command.code === Code.G2; - let lastPath = job.paths[job.paths.length - 1]; + let currentPath = job.inprogressPath; const pathType = e ? PathType.Extrusion : PathType.Travel; - if (lastPath === undefined || lastPath.travelType !== pathType) { - lastPath = this.breakPath(job, pathType); + if (currentPath === undefined || currentPath.travelType !== pathType) { + currentPath = this.breakPath(job, pathType); } if (r) { @@ -118,14 +118,14 @@ export class Interpreter { px = centerX + arcRadius * Math.cos(currentAngle); py = centerY + arcRadius * Math.sin(currentAngle); pz += zStep; - lastPath.addPoint(px, py, pz); + currentPath.addPoint(px, py, pz); } state.x = x || state.x; state.y = y || state.y; state.z = z || state.z; - lastPath.addPoint(state.x, state.y, state.z); + currentPath.addPoint(state.x, state.y, state.z); } G3 = this.G2; @@ -170,9 +170,10 @@ export class Interpreter { } private breakPath(job: Job, newType: PathType): Path { - const lastPath = new Path(newType, 0.6, 0.2, job.state.tool); - job.paths.push(lastPath); - lastPath.addPoint(job.state.x, job.state.y, job.state.z); - return lastPath; + job.finishPath(); + const currentPath = new Path(newType, 0.6, 0.2, job.state.tool); + currentPath.addPoint(job.state.x, job.state.y, job.state.z); + job.inprogressPath = currentPath; + return currentPath; } } diff --git a/src/job.ts b/src/job.ts index 47f332ed..de372a45 100644 --- a/src/job.ts +++ b/src/job.ts @@ -18,54 +18,141 @@ export class State { export class Job { paths: Path[]; state: State; + private travelPaths: Path[] = []; + private extrusionPaths: Path[] = []; + private layersPaths: Path[][] | null; + private indexers: Indexer[]; + inprogressPath: Path | undefined; constructor(state?: State) { this.paths = []; this.state = state || State.initial; + this.layersPaths = [[]]; + this.indexers = [ + new TravelTypeIndexer({ travel: this.travelPaths, extrusion: this.extrusionPaths }), + new LayersIndexer(this.layersPaths) + ]; } - isPlanar(): boolean { - return ( - this.paths.find( - (path) => - path.travelType === PathType.Extrusion && path.vertices.some((_, i, arr) => i % 3 === 2 && arr[i] !== arr[2]) - ) === undefined - ); + get extrusions(): Path[] { + return this.extrusionPaths; + } + + get travels(): Path[] { + return this.travelPaths; + } + + get layers(): Path[][] | null { + return this.layersPaths; } - layers(): Path[][] | null { - if (!this.isPlanar()) { - return null; + finishPath(): void { + if (this.inprogressPath === undefined) { + return; } + if (this.inprogressPath.vertices.length > 0) { + this.addPath(this.inprogressPath); + } + } + + addPath(path: Path): void { + this.paths.push(path); + this.indexPath(path); + } - const layers: Path[][] = []; - let currentLayer: Path[] = []; - - this.paths.forEach((path) => { - if (path.travelType === PathType.Extrusion) { - currentLayer.push(path); - } else { - if ( - path.vertices.some((_, i, arr) => i % 3 === 2 && arr[i] !== arr[2]) && - currentLayer.find((p) => p.travelType === PathType.Extrusion) - ) { - layers.push(currentLayer); - currentLayer = []; + isPlanar(): boolean { + return this.paths.find((path) => path.travelType === PathType.Extrusion && path.hasVerticalMoves()) === undefined; + } + + private indexPath(path: Path): void { + this.indexers.forEach((indexer) => { + try { + indexer.sortIn(path); + } catch (e) { + if (e instanceof NonApplicableIndexer) { + if (e instanceof NonPlanarPathError) { + this.layersPaths = null; + } + const i = this.indexers.indexOf(indexer); + this.indexers.splice(i, 1); + } else { + throw e; } - currentLayer.push(path); } }); + } +} + +class NonApplicableIndexer extends Error {} +class Indexer { + protected indexes: Record | Path[][]; + constructor(indexes: Record | Path[][]) { + this.indexes = indexes; + } + sortIn(path: Path): void { + path; + throw new Error('Method not implemented.'); + } +} - layers.push(currentLayer); +class TravelTypeIndexer extends Indexer { + protected declare indexes: Record; + constructor(indexes: Record) { + super(indexes); + } - return layers; + sortIn(path: Path): void { + if (path.travelType === PathType.Extrusion) { + this.indexes.extrusion.push(path); + } else { + this.indexes.travel.push(path); + } } +} - extrusions(): Path[] { - return this.paths.filter((path) => path.travelType === PathType.Extrusion); +class NonPlanarPathError extends NonApplicableIndexer { + constructor() { + super("Non-planar paths can't be indexed by layer"); + } +} +class LayersIndexer extends Indexer { + protected declare indexes: Path[][]; + constructor(indexes: Path[][]) { + super(indexes); + } + + sortIn(path: Path): void { + if (path.travelType === PathType.Extrusion && path.vertices.some((_, i, arr) => i % 3 === 2 && arr[i] !== arr[2])) { + throw new NonPlanarPathError(); + } + + if (path.travelType === PathType.Extrusion) { + this.lastLayer().push(path); + } else { + if ( + path.vertices.some((_, i, arr) => i % 3 === 2 && arr[i] !== arr[2]) && + this.lastLayer().find((p) => p.travelType === PathType.Extrusion) + ) { + this.createLayer(); + } + this.lastLayer().push(path); + } + } + + private lastLayer(): Path[] { + if (this.indexes === undefined) { + this.indexes = [[]]; + } + + if (this.indexes[this.indexes.length - 1] === undefined) { + this.createLayer(); + return this.lastLayer(); + } + return this.indexes[this.indexes.length - 1]; } - travels(): Path[] { - return this.paths.filter((path) => path.travelType === PathType.Travel); + private createLayer(): void { + const newLayer: Path[] = []; + this.indexes.push(newLayer); } } diff --git a/src/path.ts b/src/path.ts index 8a89bdf3..4f6fa2ee 100644 --- a/src/path.ts +++ b/src/path.ts @@ -9,31 +9,35 @@ export enum PathType { export class Path { public travelType: PathType; - public vertices: number[]; - extrusionWidth: number; - lineHeight: number; - tool: number; + public extrusionWidth: number; + public lineHeight: number; + public tool: number; + private _vertices: number[]; constructor(travelType: PathType, extrusionWidth = 0.6, lineHeight = 0.2, tool = 0) { this.travelType = travelType; - this.vertices = []; + this._vertices = []; this.extrusionWidth = extrusionWidth; this.lineHeight = lineHeight; this.tool = tool; } + get vertices(): number[] { + return this._vertices; + } + addPoint(x: number, y: number, z: number): void { - this.vertices.push(x, y, z); + this._vertices.push(x, y, z); } checkLineContinuity(x: number, y: number, z: number): boolean { - if (this.vertices.length < 3) { + if (this._vertices.length < 3) { return false; } - const lastX = this.vertices[this.vertices.length - 3]; - const lastY = this.vertices[this.vertices.length - 2]; - const lastZ = this.vertices[this.vertices.length - 1]; + const lastX = this._vertices[this._vertices.length - 3]; + const lastY = this._vertices[this._vertices.length - 2]; + const lastZ = this._vertices[this._vertices.length - 1]; return x === lastX && y === lastY && z === lastZ; } @@ -41,14 +45,14 @@ export class Path { path(): Vector3[] { const path: Vector3[] = []; - for (let i = 0; i < this.vertices.length; i += 3) { - path.push(new Vector3(this.vertices[i], this.vertices[i + 1], this.vertices[i + 2])); + for (let i = 0; i < this._vertices.length; i += 3) { + path.push(new Vector3(this._vertices[i], this._vertices[i + 1], this._vertices[i + 2])); } return path; } geometry(): BufferGeometry { - if (this.vertices.length < 3) { + if (this._vertices.length < 3) { return new BufferGeometry(); } @@ -58,4 +62,8 @@ export class Path { line(): BufferGeometry { return new BufferGeometry().setFromPoints(this.path()); } + + hasVerticalMoves(): boolean { + return this.vertices.some((_, i, arr) => i % 3 === 2 && arr[i] !== arr[2]); + } } diff --git a/src/webgl-preview.ts b/src/webgl-preview.ts index 4e0fb8bb..6ee98ca0 100644 --- a/src/webgl-preview.ts +++ b/src/webgl-preview.ts @@ -322,7 +322,7 @@ export class WebGLPreview { this.renderLayerIndex = 0; - if (this.job.layers() === null) { + if (this.job.layers === null) { console.warn('Job is not planar'); this.render(); return; @@ -334,7 +334,7 @@ export class WebGLPreview { private renderFrameLoop(layerCount: number): Promise { return new Promise((resolve) => { const loop = () => { - if (this.renderLayerIndex >= this.job.layers().length - 1) { + if (this.renderLayerIndex >= this.job.layers?.length - 1) { resolve(); } else { this.renderFrame(layerCount); @@ -348,11 +348,8 @@ export class WebGLPreview { private renderFrame(layerCount: number): void { this.group = this.createGroup('layer' + this.renderLayerIndex); - const endIndex = Math.min(this.renderLayerIndex + layerCount, this.job.layers().length - 1); - const pathsToRender = this.job - .layers() - .slice(this.renderLayerIndex, endIndex) - .flatMap((l) => l); + const endIndex = Math.min(this.renderLayerIndex + layerCount, this.job.layers?.length - 1); + const pathsToRender = this.job.layers?.slice(this.renderLayerIndex, endIndex)?.flatMap((l) => l); this.renderGeometries(pathsToRender.filter((path) => path.travelType === 'Extrusion')); this.renderLines( @@ -433,7 +430,7 @@ export class WebGLPreview { }); } - private renderLines(travels = this.job.travels(), extrusions = this.job.extrusions()): void { + private renderLines(travels = this.job.travels, extrusions = this.job.extrusions): void { if (this.renderTravel) { const material = new LineBasicMaterial({ color: this._travelColor, linewidth: this.lineWidth }); this.disposables.push(material); @@ -467,7 +464,7 @@ export class WebGLPreview { } } - private renderGeometries(paths = this.job.extrusions()): void { + private renderGeometries(paths = this.job.extrusions): void { if (Object.keys(this._geometries).length === 0 && this.renderTubes) { let color: number; paths.forEach((path) => {