Skip to content

Commit f559ea9

Browse files
authored
Merge pull request #306 from xyz-tools/fix/layer-logic
Update layer logic
2 parents a3dcb15 + 84785dd commit f559ea9

5 files changed

Lines changed: 172 additions & 60 deletions

File tree

src/__tests__/job.ts

Lines changed: 130 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ test('it has an initial state', () => {
1111
});
1212

1313
describe('.isPlanar', () => {
14-
test('returns true if all extrusions are on the same plane', () => {
14+
test('if all extrusions are on the same plane (Z=0)', () => {
1515
const job = new Job();
1616

1717
append_path(job, PathType.Extrusion, [
@@ -23,22 +23,33 @@ describe('.isPlanar', () => {
2323
[5, 6, 0]
2424
]);
2525

26-
expect(job.isPlanar()).toEqual(true);
26+
expect(job.isPlanar).toEqual(true);
2727
});
2828

29-
test('returns false if any extrusions are on a different plane', () => {
29+
test('if all extrusions are on the same plane (Z=1)', () => {
3030
const job = new Job();
3131

3232
append_path(job, PathType.Extrusion, [
33-
[0, 0, 0],
34-
[1, 2, 0]
33+
[0, 0, 1],
34+
[1, 2, 1]
3535
]);
36+
append_path(job, PathType.Extrusion, [
37+
[1, 2, 1],
38+
[5, 6, 1]
39+
]);
40+
41+
expect(job.isPlanar).toEqual(true);
42+
});
43+
44+
test('if any extrusion path has a Z value that exceeds the default tolerance', () => {
45+
const job = new Job();
46+
3647
append_path(job, PathType.Extrusion, [
3748
[1, 2, 0],
3849
[5, 6, 1]
3950
]);
4051

41-
expect(job.isPlanar()).toEqual(false);
52+
expect(job.isPlanar).toEqual(false);
4253
});
4354

4455
test('ignores travel paths', () => {
@@ -58,12 +69,29 @@ describe('.isPlanar', () => {
5869
[5, 6, 0]
5970
]);
6071

61-
expect(job.isPlanar()).toEqual(true);
72+
expect(job.isPlanar).toEqual(true);
6273
});
6374
});
6475

6576
describe('.layers', () => {
66-
test('returns null if the job is not planar', () => {
77+
test('returns empty list if no paths are present', () => {
78+
const job = new Job();
79+
80+
expect(job.layers).toEqual([]);
81+
});
82+
83+
test('returns empty list if no extrusion is present', () => {
84+
const job = new Job();
85+
86+
append_path(job, PathType.Travel, [
87+
[0, 0, 0],
88+
[1, 2, 0]
89+
]);
90+
91+
expect(job.layers).toEqual([]);
92+
});
93+
94+
test('returns empty list if the job is not planar', () => {
6795
const job = new Job();
6896

6997
append_path(job, PathType.Extrusion, [
@@ -76,16 +104,17 @@ describe('.layers', () => {
76104
]);
77105

78106
expect(job.layers).toEqual([]);
107+
expect(job.isPlanar).toEqual(false);
79108
});
80109

81-
test('paths without z changes are on the same layer', () => {
110+
test('extrusions with same Z value are on the same layer', () => {
82111
const job = new Job();
83112

84113
append_path(job, PathType.Extrusion, [
85114
[0, 0, 0],
86115
[1, 2, 0]
87116
]);
88-
append_path(job, PathType.Travel, [
117+
append_path(job, PathType.Extrusion, [
89118
[5, 6, 0],
90119
[5, 6, 0]
91120
]);
@@ -119,7 +148,7 @@ describe('.layers', () => {
119148
expect(layers[1].paths.length).toEqual(1);
120149
});
121150

122-
test('travel paths moving z under the default tolerance are on the same layer', () => {
151+
test('travel with a z component is still on the same layer', () => {
123152
const job = new Job();
124153

125154
append_path(job, PathType.Extrusion, [
@@ -128,7 +157,7 @@ describe('.layers', () => {
128157
]);
129158
append_path(job, PathType.Travel, [
130159
[5, 6, 0],
131-
[5, 6, LayersIndexer.DEFAULT_TOLERANCE - 0.01]
160+
[5, 6, 42]
132161
]);
133162

134163
const layers = job.layers;
@@ -146,7 +175,7 @@ describe('.layers', () => {
146175
[0, 0, 0],
147176
[1, 2, 0]
148177
]);
149-
append_path(job, PathType.Travel, [
178+
append_path(job, PathType.Extrusion, [
150179
[5, 6, 0],
151180
[5, 6, 0.09]
152181
]);
@@ -190,6 +219,38 @@ describe('.layers', () => {
190219
test('extrusions after travels are on the same layer', () => {
191220
const job = new Job();
192221

222+
append_path(job, PathType.Extrusion, [
223+
[0, 0, 0],
224+
[1, 2, 0]
225+
]);
226+
append_path(job, PathType.Travel, [
227+
[5, 6, 0],
228+
[5, 6, 2]
229+
]);
230+
append_path(job, PathType.Travel, [
231+
[5, 6, 2],
232+
[5, 6, 0]
233+
]);
234+
append_path(job, PathType.Travel, [
235+
[5, 6, 0],
236+
[5, 6, 2]
237+
]);
238+
append_path(job, PathType.Extrusion, [
239+
[5, 6, 0],
240+
[5, 6, 0]
241+
]);
242+
243+
const layers = job.layers;
244+
245+
expect(layers).not.toBeNull();
246+
expect(layers).toBeInstanceOf(Array);
247+
expect(layers.length).toEqual(1);
248+
expect(layers[0].paths.length).toEqual(5);
249+
});
250+
251+
test('extrusions with a new Z value after travels are on a new layer', () => {
252+
const job = new Job();
253+
193254
append_path(job, PathType.Extrusion, [
194255
[0, 0, 0],
195256
[1, 2, 0]
@@ -220,7 +281,7 @@ describe('.layers', () => {
220281
expect(layers[1].paths.length).toEqual(1);
221282
});
222283

223-
test('initial travels are on the same layer as the first extrusion', () => {
284+
test('travel paths before the first extrusion are not indexed', () => {
224285
const job = new Job();
225286

226287
append_path(job, PathType.Travel, [
@@ -244,7 +305,61 @@ describe('.layers', () => {
244305

245306
expect(layers).not.toBeNull();
246307
expect(layers.length).toEqual(1);
247-
expect(layers[0].paths.length).toEqual(4);
308+
expect(layers[0].paths.length).toEqual(1);
309+
});
310+
311+
test('layer z must equal path z', () => {
312+
const job = new Job();
313+
314+
append_path(job, PathType.Extrusion, [
315+
[5, 6, 2],
316+
[5, 6, 2]
317+
]);
318+
319+
const layers = job.layers;
320+
321+
expect(layers).not.toBeNull();
322+
expect(layers.length).toEqual(1);
323+
expect(layers[0].z).toEqual(2);
324+
});
325+
326+
test('layer z must equal extrusion path z', () => {
327+
const job = new Job();
328+
329+
append_path(job, PathType.Extrusion, [
330+
[5, 6, 2],
331+
[5, 6, 2]
332+
]);
333+
334+
append_path(job, PathType.Travel, [
335+
[5, 6, 4],
336+
[5, 6, 4]
337+
]);
338+
339+
const layers = job.layers;
340+
341+
expect(layers).not.toBeNull();
342+
expect(layers.length).toEqual(1);
343+
expect(layers[0].z).toEqual(2);
344+
});
345+
346+
test('layer z must equal path z, for second layer', () => {
347+
const job = new Job();
348+
349+
append_path(job, PathType.Extrusion, [
350+
[5, 6, 2],
351+
[5, 6, 2]
352+
]);
353+
append_path(job, PathType.Extrusion, [
354+
[5, 6, 4],
355+
[5, 6, 4]
356+
]);
357+
358+
const layers = job.layers;
359+
360+
expect(layers).not.toBeNull();
361+
expect(layers.length).toEqual(2);
362+
expect(layers[1].z).toEqual(4);
248363
});
249364
});
250365

src/indexers.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ export class TravelTypeIndexer extends Indexer {
6262
/**
6363
* Error thrown when attempting to index a non-planar path
6464
*/
65-
export class NonPlanarPathError extends NonApplicableIndexer {
65+
export class NonPlanarExtrusionError extends NonApplicableIndexer {
6666
constructor() {
67-
super("Non-planar paths can't be indexed by layer");
67+
super('Non-planar extrusions cannot be indexed by layer');
6868
}
6969
}
7070

@@ -101,41 +101,40 @@ export class LayersIndexer extends Indexer {
101101
path.travelType === PathType.Extrusion &&
102102
path.vertices.some((_, i, arr) => i > 3 && i % 3 === 2 && Math.abs(arr[i] - arr[i - 3]) > this.tolerance)
103103
) {
104-
throw new NonPlanarPathError();
104+
throw new NonPlanarExtrusionError();
105105
}
106106

107-
if (this.indexes[this.indexes.length - 1] === undefined) {
108-
// Create the first layer at the current Z height (which is always 0 bc the gcode origin is at 0,0,0)
109-
this.createLayer(0);
110-
}
107+
// new layers are only created when extruding
108+
if (path.travelType === PathType.Extrusion) {
109+
const newZ = path.vertices[2];
110+
const lastZ = this.lastLayer?.z;
111111

112-
if (
113-
path.travelType === PathType.Extrusion &&
114-
this.lastLayer().paths.some((p) => p.travelType === PathType.Extrusion)
115-
) {
116-
if (path.vertices[2] - (this.lastLayer().z || 0) > this.tolerance) {
117-
this.createLayer(path.vertices[2]);
112+
// either this is the first extrusion path
113+
// or this is an extrusion path that is higher than the last layer
114+
if (!this.lastLayer || newZ - lastZ > this.tolerance) {
115+
this.createLayerAt(newZ);
118116
}
119117
}
120-
this.lastLayer().paths.push(path);
118+
119+
this.lastLayer?.paths.push(path);
121120
}
122121

123122
/**
124123
* Gets the last layer in the indexes
125124
* @returns The most recent layer
126125
*/
127-
private lastLayer(): Layer {
126+
private get lastLayer(): Layer {
128127
return this.indexes[this.indexes.length - 1];
129128
}
130129

131130
/**
132131
* Creates a new layer at the specified Z height
133132
* @param z - Z height for the new layer
134133
*/
135-
private createLayer(z: number): void {
136-
const layerNumber = this.indexes.length;
137-
const height = z - (this.lastLayer()?.z || 0);
138-
this.indexes.push(new Layer(this.indexes.length, [], layerNumber, height, z));
134+
private createLayerAt(z: number): void {
135+
const lastZ = this.lastLayer?.z || 0;
136+
const height = z - lastZ;
137+
this.indexes.push(new Layer(z, height));
139138
}
140139
}
141140

src/job.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { Path } from './path';
22
import { State } from './state';
33
import { Layer } from './layer';
4-
import { TravelTypeIndexer, LayersIndexer, ToolIndexer, Indexer, NonApplicableIndexer } from './indexers';
4+
import {
5+
TravelTypeIndexer,
6+
LayersIndexer,
7+
ToolIndexer,
8+
Indexer,
9+
NonApplicableIndexer,
10+
NonPlanarExtrusionError
11+
} from './indexers';
512
import { BoundingBox } from './bounding-box';
613

714
/**
@@ -135,10 +142,10 @@ export class Job {
135142
}
136143

137144
/**
138-
* Checks if the job contains planar layers
145+
* Checks if the job contains planar extrusion layers
139146
* @returns True if the job contains at least one layer, false otherwise
140147
*/
141-
isPlanar(): boolean {
148+
get isPlanar(): boolean {
142149
return this.layers.length > 0;
143150
}
144151

@@ -159,9 +166,12 @@ export class Job {
159166
throw e; // If the error is not a NonApplicableIndexer, it will be thrown.
160167
}
161168

162-
console.warn('Non-planar path detected; clearing layer index');
163-
this._layers = [];
169+
if (e instanceof NonPlanarExtrusionError) {
170+
console.warn('Non-planar path detected; clearing layer index');
171+
this._layers = [];
172+
}
164173

174+
// Remove the indexer that cannot handle this path
165175
const i = this.indexers.indexOf(indexer);
166176
this.indexers.splice(i, 1);
167177
}

src/layer.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,16 @@ import { Path } from './path';
66
* Contains information about the layer number, paths, height, and Z position
77
*/
88
export class Layer {
9-
/** Layer number (0-based index) */
10-
public layer: number;
119
/** Array of paths in this layer */
12-
public paths: Path[];
13-
/** Line number in the G-code file where this layer starts */
14-
public lineNumber: number;
15-
/** Height of this layer */
16-
public height: number = 0;
17-
/** Z position of this layer */
18-
public z: number = 0;
10+
paths: Path[] = [];
1911

2012
/**
2113
* Creates a new Layer instance
22-
* @param layer - Layer number
23-
* @param paths - Array of paths in this layer
24-
* @param lineNumber - Line number in G-code file
2514
* @param height - Layer height (default: 0)
2615
* @param z - Z position (default: 0)
2716
*/
28-
constructor(layer: number, paths: Path[], lineNumber: number, height: number = 0, z: number = 0) {
29-
this.layer = layer;
30-
this.paths = paths;
31-
this.lineNumber = lineNumber;
32-
this.height = height;
33-
this.z = z;
34-
}
17+
constructor(
18+
public z: number,
19+
public height: number = 0
20+
) {}
3521
}

0 commit comments

Comments
 (0)