Skip to content

Commit b924a14

Browse files
committed
Use constants instead of strings in case name changes
1 parent 60d7bc1 commit b924a14

File tree

1 file changed

+209
-0
lines changed

1 file changed

+209
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright (C) 2021 Sienci Labs Inc.
3+
*
4+
* This file is part of gSender.
5+
*
6+
* gSender is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, under version 3 of the License.
9+
*
10+
* gSender is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with gSender. If not, see <https://www.gnu.org/licenses/>.
17+
*
18+
* Contact for information regarding this program and its license
19+
* can be sent through gSender@sienci.com or mailed to the main office
20+
* of Sienci Labs Inc. in Waterloo, Ontario, Canada.
21+
*
22+
*/
23+
24+
import concaveman from 'concaveman';
25+
import GCodeVirtualizer from 'app/lib/GCodeVirtualizer';
26+
import {OUTLINE_MODE_RAPIDLESS_SQUARE} from "app/constants";
27+
28+
self.onmessage = ({ data }) => {
29+
const { isLaser = false, parsedData = [], mode, bbox, zTravel, content = '' } = data;
30+
31+
const getOutlineGcode = (concavity = Infinity) => {
32+
// 1. Extract 2D [x, y] points (parsedData is flat: x0,y0,z0,x1,y1,z1,...)
33+
const points2D = [];
34+
for (let i = 0; i < parsedData.length; i += 3) {
35+
points2D.push([
36+
parseFloat(parsedData[i].toFixed(3)),
37+
parseFloat(parsedData[i + 1].toFixed(3)),
38+
]);
39+
}
40+
41+
// 2. Deduplicate on 0.5mm grid for efficiency on large files
42+
const seen = new Set();
43+
const deduped = [];
44+
for (const [x, y] of points2D) {
45+
const key = `${Math.round(x * 2)},${Math.round(y * 2)}`;
46+
if (!seen.has(key)) {
47+
seen.add(key);
48+
deduped.push([x, y]);
49+
}
50+
}
51+
52+
// 3. Compute concave hull; remove duplicate closing point
53+
let hull = concaveman(deduped, concavity).slice(0, -1);
54+
55+
// 4. Ensure clockwise winding (negative signed area in standard XY)
56+
// Shoelace cross-product variant: sum of (x2-x1)*(y2+y1)
57+
const area = hull.reduce((sum, pt, i) => {
58+
const next = hull[(i + 1) % hull.length];
59+
return sum + (next[0] - pt[0]) * (next[1] + pt[1]);
60+
}, 0);
61+
if (area > 0) {
62+
hull.reverse();
63+
}
64+
65+
// 5. Rotate hull to start at vertex nearest to (0, 0)
66+
let startIdx = 0;
67+
let minDist = Infinity;
68+
hull.forEach(([x, y], i) => {
69+
const d = x * x + y * y;
70+
if (d < minDist) {
71+
minDist = d;
72+
startIdx = i;
73+
}
74+
});
75+
const orderedHull = [...hull.slice(startIdx), ...hull.slice(0, startIdx)];
76+
77+
return convertPointsToGCode(orderedHull, isLaser);
78+
};
79+
80+
const getSimpleOutline = () => {
81+
if (parsedData && parsedData.length <= 0) {
82+
return [
83+
'%X0=posx,Y0=posy,Z0=posz',
84+
'%MM=modal.distance',
85+
`G21 G91 G0 Z${zTravel}`,
86+
'G90',
87+
`G0 X[${bbox.min.x}] Y[${bbox.min.y}]`,
88+
`G0 X[${bbox.min.x}] Y[${bbox.max.y}]`,
89+
`G0 X[${bbox.max.x}] Y[${bbox.max.y}]`,
90+
`G0 X[${bbox.max.x}] Y[${bbox.min.y}]`,
91+
`G0 X[${bbox.min.x}] Y[${bbox.min.y}]`,
92+
'G0 X[X0] Y[Y0]',
93+
`G21 G91 G0 Z-${zTravel}`,
94+
'[MM]',
95+
];
96+
} else {
97+
return [
98+
'%X0=posx,Y0=posy,Z0=posz',
99+
'%MM=modal.distance',
100+
`G21 G91 G0 Z${zTravel}`,
101+
'G90',
102+
'G0 X[xmin] Y[ymin]',
103+
'G0 X[xmin] Y[ymax]',
104+
'G0 X[xmax] Y[ymax]',
105+
'G0 X[xmax] Y[ymin]',
106+
'G0 X[xmin] Y[ymin]',
107+
'G0 X[X0] Y[Y0]',
108+
`G21 G91 G0 Z-${zTravel}`,
109+
'[MM]',
110+
];
111+
}
112+
};
113+
114+
const getRapidlessSquareOutline = (fileContent: string) => {
115+
let xmin = Infinity, xmax = -Infinity, ymin = Infinity, ymax = -Infinity;
116+
117+
const updateBounds = (v1: any, v2: any) => {
118+
for (const v of [v1, v2]) {
119+
if (v.x < xmin) xmin = v.x;
120+
if (v.x > xmax) xmax = v.x;
121+
if (v.y < ymin) ymin = v.y;
122+
if (v.y > ymax) ymax = v.y;
123+
}
124+
};
125+
126+
const vm = new GCodeVirtualizer({
127+
addLine: (modal: any, v1: any, v2: any) => {
128+
if (modal.motion !== 'G0') updateBounds(v1, v2);
129+
},
130+
addArcCurve: (_modal: any, v1: any, v2: any) => {
131+
updateBounds(v1, v2);
132+
},
133+
addCurve: (modal: any, v1: any, v2: any) => {
134+
if (modal.motion !== 'G0') updateBounds(v1, v2);
135+
},
136+
});
137+
138+
// Parse line-by-line (same pattern as Visualize.worker.ts)
139+
const len = fileContent.length;
140+
let lineStart = 0;
141+
for (let i = 0; i < len; i++) {
142+
const ch = fileContent.charCodeAt(i);
143+
if (ch !== 10 && ch !== 13) continue;
144+
vm.virtualize(fileContent.slice(lineStart, i));
145+
if (ch === 13 && i + 1 < len && fileContent.charCodeAt(i + 1) === 10) i++;
146+
lineStart = i + 1;
147+
}
148+
vm.virtualize(fileContent.slice(lineStart));
149+
150+
if (!isFinite(xmin)) {
151+
// No cutting moves found — fall back to regular square
152+
return getSimpleOutline();
153+
}
154+
155+
return [
156+
'%X0=posx,Y0=posy,Z0=posz',
157+
'%MM=modal.distance',
158+
`G21 G91 G0 Z${zTravel}`,
159+
'G90',
160+
`G0 X${xmin.toFixed(3)} Y${ymin.toFixed(3)}`,
161+
`G0 X${xmin.toFixed(3)} Y${ymax.toFixed(3)}`,
162+
`G0 X${xmax.toFixed(3)} Y${ymax.toFixed(3)}`,
163+
`G0 X${xmax.toFixed(3)} Y${ymin.toFixed(3)}`,
164+
`G0 X${xmin.toFixed(3)} Y${ymin.toFixed(3)}`,
165+
'G0 X[X0] Y[Y0]',
166+
`G21 G91 G0 Z-${zTravel}`,
167+
'[MM]',
168+
];
169+
};
170+
171+
function convertPointsToGCode(points: number[][], isLaser = false) {
172+
const gCode = [];
173+
const movementModal = isLaser ? 'G1' : 'G0'; // G1 is necessary for laser outline since G0 won't enable it
174+
gCode.push('%X0=posx,Y0=posy,Z0=posz');
175+
gCode.push('%MM=modal.distance');
176+
gCode.push(`G21 G91 G0 Z${zTravel}`);
177+
// Laser outline requires some additional preamble for feedrate and enabling the laser
178+
if (isLaser) {
179+
gCode.push('G1F3000 M3 S1');
180+
}
181+
points.forEach((point) => {
182+
const [x, y] = point;
183+
gCode.push(`G21 G90 ${movementModal} X${x} Y${y}`);
184+
});
185+
// Close the loop by returning to the first point
186+
if (points.length > 0) {
187+
const [x, y] = points[0];
188+
gCode.push(`G21 G90 ${movementModal} X${x} Y${y}`);
189+
}
190+
if (isLaser) {
191+
gCode.push('M5 S0');
192+
}
193+
gCode.push('G0 X[X0] Y[Y0]');
194+
gCode.push(`G21 G91 G0 Z-${zTravel}`);
195+
196+
gCode.push('[MM]');
197+
return gCode;
198+
}
199+
200+
let outlineGcode;
201+
if (mode === 'Square') {
202+
outlineGcode = getSimpleOutline();
203+
} else if (mode === OUTLINE_MODE_RAPIDLESS_SQUARE) {
204+
outlineGcode = getRapidlessSquareOutline(content);
205+
} else {
206+
outlineGcode = getOutlineGcode();
207+
}
208+
postMessage({ outlineGcode });
209+
};

0 commit comments

Comments
 (0)