@gridspace/raster-path 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/app.js +151 -12
- package/build/index.html +6 -2
- package/build/raster-path.js +118 -9
- package/build/raster-worker.js +421 -7
- package/package.json +4 -2
- package/src/core/path-radial.js +0 -1
- package/src/core/path-tracing.js +492 -0
- package/src/core/raster-config.js +13 -4
- package/src/core/raster-path.js +118 -9
- package/src/core/raster-worker.js +41 -0
- package/src/core/workload-calibrate.js +57 -3
- package/src/shaders/tracing-toolpath.wgsl +95 -0
- package/src/test/tracing-test.cjs +307 -0
- package/src/web/app.js +151 -12
- package/src/web/index.html +6 -2
package/build/app.js
CHANGED
|
@@ -12,6 +12,7 @@ let zFloor = -100;
|
|
|
12
12
|
let xStep = 5;
|
|
13
13
|
let yStep = 5;
|
|
14
14
|
let angleStep = 1.0; // degrees
|
|
15
|
+
let traceStep = 0.5; // mm - sampling resolution for tracing mode
|
|
15
16
|
let toolSize = 2.5; // mm - target tool diameter
|
|
16
17
|
|
|
17
18
|
let modelSTL = null; // ArrayBuffer (current, possibly rotated)
|
|
@@ -61,6 +62,7 @@ function saveParameters() {
|
|
|
61
62
|
localStorage.setItem('raster-xStep', xStep);
|
|
62
63
|
localStorage.setItem('raster-yStep', yStep);
|
|
63
64
|
localStorage.setItem('raster-angleStep', angleStep);
|
|
65
|
+
localStorage.setItem('raster-traceStep', traceStep);
|
|
64
66
|
localStorage.setItem('raster-toolSize', toolSize);
|
|
65
67
|
console.log(`[App] Saved tool size: ${toolSize}mm`);
|
|
66
68
|
|
|
@@ -127,6 +129,12 @@ function loadParameters() {
|
|
|
127
129
|
document.getElementById('angle-step').value = angleStep;
|
|
128
130
|
}
|
|
129
131
|
|
|
132
|
+
const savedTraceStep = localStorage.getItem('raster-traceStep');
|
|
133
|
+
if (savedTraceStep !== null) {
|
|
134
|
+
traceStep = parseFloat(savedTraceStep);
|
|
135
|
+
document.getElementById('trace-step').value = traceStep;
|
|
136
|
+
}
|
|
137
|
+
|
|
130
138
|
const savedToolSize = localStorage.getItem('raster-toolSize');
|
|
131
139
|
if (savedToolSize !== null) {
|
|
132
140
|
toolSize = parseFloat(savedToolSize);
|
|
@@ -545,8 +553,8 @@ async function rasterizeAll() {
|
|
|
545
553
|
updateInfo(`Tool loaded in ${(t1 - t0).toFixed(0)}ms`);
|
|
546
554
|
}
|
|
547
555
|
|
|
548
|
-
if (mode === 'planar') {
|
|
549
|
-
// Planar mode: rasterize terrain immediately
|
|
556
|
+
if (mode === 'planar' || mode === 'tracing') {
|
|
557
|
+
// Planar/Tracing mode: rasterize terrain immediately
|
|
550
558
|
if (modelTriangles) {
|
|
551
559
|
updateInfo('Rasterizing terrain...');
|
|
552
560
|
const t0 = performance.now();
|
|
@@ -557,7 +565,7 @@ async function rasterizeAll() {
|
|
|
557
565
|
const t1 = performance.now();
|
|
558
566
|
updateInfo(`Terrain rasterized in ${(t1 - t0).toFixed(0)}ms`);
|
|
559
567
|
}
|
|
560
|
-
} else {
|
|
568
|
+
} else if (mode === 'radial') {
|
|
561
569
|
// Radial mode: MUST load tool FIRST
|
|
562
570
|
if (!toolTriangles) {
|
|
563
571
|
updateInfo('Error: Radial mode requires tool to be loaded first');
|
|
@@ -608,30 +616,81 @@ async function generateToolpath() {
|
|
|
608
616
|
updateInfo('Model must be rasterized first');
|
|
609
617
|
return;
|
|
610
618
|
}
|
|
611
|
-
} else {
|
|
619
|
+
} else if (mode === 'radial') {
|
|
612
620
|
// Radial mode: terrain must be loaded (stored internally)
|
|
613
621
|
if (!modelTriangles) {
|
|
614
622
|
updateInfo('Model STL must be loaded');
|
|
615
623
|
return;
|
|
616
624
|
}
|
|
625
|
+
} else if (mode === 'tracing') {
|
|
626
|
+
// Tracing mode: terrain must be rasterized
|
|
627
|
+
if (!modelRasterData) {
|
|
628
|
+
updateInfo('Model must be rasterized first');
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
617
631
|
}
|
|
618
632
|
|
|
619
633
|
try {
|
|
620
634
|
const t0 = performance.now();
|
|
621
635
|
updateInfo('Generating toolpath...');
|
|
622
636
|
|
|
623
|
-
//
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
637
|
+
// Generate trace paths for tracing mode
|
|
638
|
+
let tracePaths = null;
|
|
639
|
+
if (mode === 'tracing') {
|
|
640
|
+
// Get model bounds from raster data
|
|
641
|
+
const bounds = modelRasterData.bounds;
|
|
642
|
+
const minX = bounds.min.x;
|
|
643
|
+
const maxX = bounds.max.x;
|
|
644
|
+
const minY = bounds.min.y;
|
|
645
|
+
const maxY = bounds.max.y;
|
|
646
|
+
|
|
647
|
+
// Create two cross paths: horizontal through center, vertical through center
|
|
648
|
+
const centerY = (minY + maxY) / 2;
|
|
649
|
+
const centerX = (minX + maxX) / 2;
|
|
650
|
+
|
|
651
|
+
tracePaths = [
|
|
652
|
+
new Float32Array([minX, centerY, maxX, centerY]), // Horizontal line
|
|
653
|
+
new Float32Array([centerX, minY, centerX, maxY]) // Vertical line
|
|
654
|
+
];
|
|
655
|
+
|
|
656
|
+
debug.log(`Generated trace paths: H(${minX.toFixed(2)}, ${centerY.toFixed(2)}) to (${maxX.toFixed(2)}, ${centerY.toFixed(2)})`);
|
|
657
|
+
debug.log(` V(${centerX.toFixed(2)}, ${minY.toFixed(2)}) to (${centerX.toFixed(2)}, ${maxY.toFixed(2)})`);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Unified API - works for all modes!
|
|
661
|
+
const generateParams = {
|
|
627
662
|
zFloor: zFloor
|
|
628
|
-
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
if (mode === 'tracing') {
|
|
666
|
+
generateParams.paths = tracePaths;
|
|
667
|
+
generateParams.step = traceStep;
|
|
668
|
+
} else {
|
|
669
|
+
generateParams.xStep = xStep;
|
|
670
|
+
generateParams.yStep = yStep;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
toolpathData = await rasterPath.generateToolpaths(generateParams);
|
|
629
674
|
|
|
630
675
|
const t1 = performance.now();
|
|
631
676
|
|
|
632
677
|
if (mode === 'planar') {
|
|
633
678
|
const numPoints = toolpathData.pathData.length;
|
|
634
679
|
updateInfo(`Toolpath generated: ${numPoints.toLocaleString()} points in ${(t1 - t0).toFixed(0)}ms`);
|
|
680
|
+
} else if (mode === 'tracing') {
|
|
681
|
+
const totalPoints = toolpathData.paths.reduce((sum, path) => sum + path.length / 3, 0);
|
|
682
|
+
debug.log(`[Tracing] Generated ${toolpathData.paths.length} paths with ${totalPoints} total points`);
|
|
683
|
+
|
|
684
|
+
// Log sample Z values from each path
|
|
685
|
+
toolpathData.paths.forEach((path, idx) => {
|
|
686
|
+
const zValues = [];
|
|
687
|
+
for (let i = 2; i < Math.min(path.length, 15); i += 3) {
|
|
688
|
+
zValues.push(path[i].toFixed(2));
|
|
689
|
+
}
|
|
690
|
+
debug.log(`[Tracing] Path ${idx} Z samples:`, zValues.join(', '));
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
updateInfo(`Toolpath generated: ${toolpathData.paths.length} paths, ${totalPoints.toLocaleString()} points in ${(t1 - t0).toFixed(0)}ms`);
|
|
635
694
|
} else {
|
|
636
695
|
// debug.log('[Radial] Toolpaths generated:', toolpathData);
|
|
637
696
|
debug.log(`[Radial] Received ${toolpathData.strips.length} strips from worker, numStrips=${toolpathData.numStrips}`);
|
|
@@ -901,8 +960,8 @@ function displayModelRaster(wrapped) {
|
|
|
901
960
|
const positions = [];
|
|
902
961
|
const colors = [];
|
|
903
962
|
|
|
904
|
-
if (mode === 'planar') {
|
|
905
|
-
// Planar: terrain is dense (Z-only array)
|
|
963
|
+
if (mode === 'planar' || mode === 'tracing') {
|
|
964
|
+
// Planar/Tracing: terrain is dense (Z-only array)
|
|
906
965
|
const { positions: rasterPos, bounds, gridWidth, gridHeight } = modelRasterData;
|
|
907
966
|
const stepSize = resolution;
|
|
908
967
|
|
|
@@ -1057,6 +1116,59 @@ function displayToolpaths(wrapped) {
|
|
|
1057
1116
|
return;
|
|
1058
1117
|
}
|
|
1059
1118
|
|
|
1119
|
+
if (mode === 'tracing') {
|
|
1120
|
+
// Tracing toolpaths - array of XYZ paths
|
|
1121
|
+
const { paths } = toolpathData;
|
|
1122
|
+
|
|
1123
|
+
// Calculate total points
|
|
1124
|
+
let totalPoints = 0;
|
|
1125
|
+
for (const path of paths) {
|
|
1126
|
+
totalPoints += path.length / 3;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
debug.log('[Toolpath Display] Tracing mode:', paths.length, 'paths,', totalPoints, 'total points');
|
|
1130
|
+
|
|
1131
|
+
// Preallocate typed arrays
|
|
1132
|
+
const positions = new Float32Array(totalPoints * 3);
|
|
1133
|
+
const colors = new Float32Array(totalPoints * 3);
|
|
1134
|
+
|
|
1135
|
+
let arrayIdx = 0;
|
|
1136
|
+
for (let pathIdx = 0; pathIdx < paths.length; pathIdx++) {
|
|
1137
|
+
const path = paths[pathIdx];
|
|
1138
|
+
const numPoints = path.length / 3;
|
|
1139
|
+
|
|
1140
|
+
// Use different colors for each path
|
|
1141
|
+
const color = pathIdx === 0 ? [1, 0.4, 0] : [0, 0.8, 1]; // Orange for horizontal, cyan for vertical
|
|
1142
|
+
|
|
1143
|
+
for (let i = 0; i < numPoints; i++) {
|
|
1144
|
+
positions[arrayIdx] = path[i * 3]; // X
|
|
1145
|
+
positions[arrayIdx + 1] = path[i * 3 + 1]; // Y
|
|
1146
|
+
positions[arrayIdx + 2] = path[i * 3 + 2]; // Z
|
|
1147
|
+
|
|
1148
|
+
colors[arrayIdx] = color[0];
|
|
1149
|
+
colors[arrayIdx + 1] = color[1];
|
|
1150
|
+
colors[arrayIdx + 2] = color[2];
|
|
1151
|
+
|
|
1152
|
+
arrayIdx += 3;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Create geometry
|
|
1157
|
+
const geometry = new THREE.BufferGeometry();
|
|
1158
|
+
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
1159
|
+
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
1160
|
+
|
|
1161
|
+
const material = new THREE.PointsMaterial({
|
|
1162
|
+
size: resolution * 1.5,
|
|
1163
|
+
vertexColors: true
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
toolpathPoints = new THREE.Points(geometry, material);
|
|
1167
|
+
rotatedGroup.add(toolpathPoints);
|
|
1168
|
+
|
|
1169
|
+
return; // Exit early for tracing mode
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1060
1172
|
if (mode === 'planar') {
|
|
1061
1173
|
// Planar toolpaths
|
|
1062
1174
|
const { pathData, numScanlines, pointsPerLine } = toolpathData;
|
|
@@ -1324,15 +1436,32 @@ function updateButtonStates() {
|
|
|
1324
1436
|
// ============================================================================
|
|
1325
1437
|
|
|
1326
1438
|
function updateModeUI() {
|
|
1327
|
-
// Show/hide
|
|
1439
|
+
// Show/hide mode-specific controls
|
|
1328
1440
|
const wrappedContainer = document.getElementById('wrapped-container').classList;
|
|
1329
1441
|
const angleStepContainer = document.getElementById('angle-step-container').classList;
|
|
1442
|
+
const traceStepContainer = document.getElementById('trace-step-container').classList;
|
|
1443
|
+
const xStepContainer = document.getElementById('x-step-container').classList;
|
|
1444
|
+
const yStepContainer = document.getElementById('y-step-container').classList;
|
|
1445
|
+
|
|
1330
1446
|
if (mode === 'radial') {
|
|
1331
1447
|
wrappedContainer.remove('hide');
|
|
1332
1448
|
angleStepContainer.remove('hide');
|
|
1449
|
+
traceStepContainer.add('hide');
|
|
1450
|
+
xStepContainer.remove('hide');
|
|
1451
|
+
yStepContainer.remove('hide');
|
|
1452
|
+
} else if (mode === 'tracing') {
|
|
1453
|
+
wrappedContainer.add('hide');
|
|
1454
|
+
angleStepContainer.add('hide');
|
|
1455
|
+
traceStepContainer.remove('hide');
|
|
1456
|
+
xStepContainer.add('hide');
|
|
1457
|
+
yStepContainer.add('hide');
|
|
1333
1458
|
} else {
|
|
1459
|
+
// planar
|
|
1334
1460
|
wrappedContainer.add('hide');
|
|
1335
1461
|
angleStepContainer.add('hide');
|
|
1462
|
+
traceStepContainer.add('hide');
|
|
1463
|
+
xStepContainer.remove('hide');
|
|
1464
|
+
yStepContainer.remove('hide');
|
|
1336
1465
|
}
|
|
1337
1466
|
}
|
|
1338
1467
|
|
|
@@ -1452,6 +1581,16 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
1452
1581
|
updateButtonStates();
|
|
1453
1582
|
});
|
|
1454
1583
|
|
|
1584
|
+
document.getElementById('trace-step').addEventListener('change', (e) => {
|
|
1585
|
+
traceStep = parseFloat(e.target.value);
|
|
1586
|
+
if (mode === 'tracing') {
|
|
1587
|
+
toolpathData = null; // Need to regenerate toolpath
|
|
1588
|
+
}
|
|
1589
|
+
saveParameters();
|
|
1590
|
+
updateInfo(`Trace Step changed to ${traceStep}mm`);
|
|
1591
|
+
updateButtonStates();
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1455
1594
|
// Tool size change
|
|
1456
1595
|
document.getElementById('tool-size').addEventListener('change', async (e) => {
|
|
1457
1596
|
toolSize = parseFloat(e.target.value);
|
package/build/index.html
CHANGED
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
<div class="mode-toggle">
|
|
54
54
|
<label><input type="radio" name="mode" value="planar" checked> Planar</label>
|
|
55
55
|
<label><input type="radio" name="mode" value="radial"> Radial</label>
|
|
56
|
+
<label><input type="radio" name="mode" value="tracing"> Tracing</label>
|
|
56
57
|
</div>
|
|
57
58
|
</div>
|
|
58
59
|
|
|
@@ -79,15 +80,18 @@
|
|
|
79
80
|
<label>
|
|
80
81
|
Z Floor: <input type="number" id="z-floor" value="-100" step="10" style="width: 70px;">
|
|
81
82
|
</label>
|
|
82
|
-
<label>
|
|
83
|
+
<label id="x-step-container">
|
|
83
84
|
X Step: <input type="number" id="x-step" value="5" min="1" max="50" style="width: 60px;">
|
|
84
85
|
</label>
|
|
85
|
-
<label>
|
|
86
|
+
<label id="y-step-container">
|
|
86
87
|
Y Step: <input type="number" id="y-step" value="5" min="1" max="50" style="width: 60px;">
|
|
87
88
|
</label>
|
|
88
89
|
<label id="angle-step-container" class="hide">
|
|
89
90
|
Angle Step (deg): <input type="number" id="angle-step" value="1" min="0.1" max="10" step="0.1" style="width: 60px;">
|
|
90
91
|
</label>
|
|
92
|
+
<label id="trace-step-container" class="hide">
|
|
93
|
+
Trace Step (mm): <input type="number" id="trace-step" value="0.5" min="0.1" max="5" step="0.1" style="width: 60px;">
|
|
94
|
+
</label>
|
|
91
95
|
</div>
|
|
92
96
|
|
|
93
97
|
<div class="section">
|
package/build/raster-path.js
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
/**
|
|
46
46
|
* Configuration options for RasterPath
|
|
47
47
|
* @typedef {Object} RasterPathConfig
|
|
48
|
-
* @property {'planar'|'radial'} mode - Rasterization mode (default: 'planar')
|
|
48
|
+
* @property {'planar'|'radial'|'tracing'} mode - Rasterization mode (default: 'planar')
|
|
49
49
|
* @property {boolean} autoTiling - Automatically tile large datasets (default: true)
|
|
50
50
|
* @property {number} gpuMemorySafetyMargin - Safety margin as percentage (default: 0.8 = 80%)
|
|
51
51
|
* @property {number} maxGPUMemoryMB - Maximum GPU memory per tile (default: 256MB)
|
|
@@ -80,8 +80,8 @@ export class RasterPath {
|
|
|
80
80
|
|
|
81
81
|
// Validate mode
|
|
82
82
|
const mode = config.mode || 'planar';
|
|
83
|
-
if (mode !== 'planar' && mode !== 'radial') {
|
|
84
|
-
throw new Error(`Invalid mode: ${mode}. Must be 'planar' or '
|
|
83
|
+
if (mode !== 'planar' && mode !== 'radial' && mode !== 'tracing') {
|
|
84
|
+
throw new Error(`Invalid mode: ${mode}. Must be 'planar', 'radial', or 'tracing'`);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// Validate rotationStep for radial mode
|
|
@@ -221,8 +221,8 @@ export class RasterPath {
|
|
|
221
221
|
throw new Error('RasterPath not initialized. Call init() first.');
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
if (this.mode === 'planar') {
|
|
225
|
-
// Planar: rasterize and return
|
|
224
|
+
if (this.mode === 'planar' || this.mode === 'tracing') {
|
|
225
|
+
// Planar/Tracing: rasterize and return (tracing reuses planar terrain rasterization)
|
|
226
226
|
const terrainData = await this.#rasterizePlanar({ triangles, zFloor, boundsOverride, isForTool: false, onProgress });
|
|
227
227
|
this.terrainData = terrainData;
|
|
228
228
|
return terrainData;
|
|
@@ -269,7 +269,7 @@ export class RasterPath {
|
|
|
269
269
|
* @param {function} params.onProgress - Optional progress callback (progress: number, info?: string) => void
|
|
270
270
|
* @returns {Promise<object>} Planar: {pathData, width, height} | Radial: {strips[], numStrips, totalPoints}
|
|
271
271
|
*/
|
|
272
|
-
async generateToolpaths({ xStep, yStep, zFloor, onProgress }) {
|
|
272
|
+
async generateToolpaths({ xStep, yStep, zFloor, onProgress, paths, step }) {
|
|
273
273
|
if (!this.isInitialized) {
|
|
274
274
|
throw new Error('RasterPath not initialized. Call init() first.');
|
|
275
275
|
}
|
|
@@ -278,7 +278,7 @@ export class RasterPath {
|
|
|
278
278
|
throw new Error('Tool not loaded. Call loadTool() first.');
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
debug.log('gen.paths', { xStep, yStep, zFloor });
|
|
281
|
+
debug.log('gen.paths', { xStep, yStep, zFloor, paths: paths?.length, step });
|
|
282
282
|
|
|
283
283
|
if (this.mode === 'planar') {
|
|
284
284
|
if (!this.terrainData) {
|
|
@@ -292,7 +292,7 @@ export class RasterPath {
|
|
|
292
292
|
zFloor,
|
|
293
293
|
onProgress
|
|
294
294
|
});
|
|
295
|
-
} else {
|
|
295
|
+
} else if (this.mode === 'radial') {
|
|
296
296
|
// Radial mode: use stored triangles
|
|
297
297
|
if (!this.terrainTriangles) {
|
|
298
298
|
throw new Error('Terrain not loaded. Call loadTerrain() first.');
|
|
@@ -306,14 +306,83 @@ export class RasterPath {
|
|
|
306
306
|
zFloor: zFloor ?? this.terrainZFloor,
|
|
307
307
|
onProgress
|
|
308
308
|
});
|
|
309
|
+
} else if (this.mode === 'tracing') {
|
|
310
|
+
// Tracing mode: follow input paths
|
|
311
|
+
if (!this.terrainData) {
|
|
312
|
+
throw new Error('Terrain not loaded. Call loadTerrain() first.');
|
|
313
|
+
}
|
|
314
|
+
if (!paths || paths.length === 0) {
|
|
315
|
+
throw new Error('Tracing mode requires paths parameter (array of Float32Array XY coordinates)');
|
|
316
|
+
}
|
|
317
|
+
if (!step || step <= 0) {
|
|
318
|
+
throw new Error('Tracing mode requires step parameter (sampling resolution in world units)');
|
|
319
|
+
}
|
|
320
|
+
return this.#generateToolpathsTracing({
|
|
321
|
+
paths,
|
|
322
|
+
step,
|
|
323
|
+
zFloor,
|
|
324
|
+
onProgress
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Create reusable GPU buffers for tracing mode (optimization for iterative tracing)
|
|
331
|
+
* Call this after loadTerrain() and loadTool() to cache buffers across multiple trace calls
|
|
332
|
+
* @returns {Promise<void>}
|
|
333
|
+
*/
|
|
334
|
+
async createTracingBuffers() {
|
|
335
|
+
if (this.mode !== 'tracing') {
|
|
336
|
+
throw new Error('createTracingBuffers() only available in tracing mode');
|
|
309
337
|
}
|
|
338
|
+
if (!this.terrainData || !this.toolData) {
|
|
339
|
+
throw new Error('Must call loadTerrain() and loadTool() before createTracingBuffers()');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return new Promise((resolve, reject) => {
|
|
343
|
+
const handler = () => resolve();
|
|
344
|
+
this.#sendMessage(
|
|
345
|
+
'create-tracing-buffers',
|
|
346
|
+
{
|
|
347
|
+
terrainPositions: this.terrainData.positions,
|
|
348
|
+
toolPositions: this.toolData.positions
|
|
349
|
+
},
|
|
350
|
+
'tracing-buffers-created',
|
|
351
|
+
handler
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Destroy reusable tracing buffers
|
|
358
|
+
* @returns {Promise<void>}
|
|
359
|
+
*/
|
|
360
|
+
async destroyTracingBuffers() {
|
|
361
|
+
if (this.mode !== 'tracing') {
|
|
362
|
+
return; // No-op for non-tracing modes
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return new Promise((resolve, reject) => {
|
|
366
|
+
const handler = () => resolve();
|
|
367
|
+
this.#sendMessage(
|
|
368
|
+
'destroy-tracing-buffers',
|
|
369
|
+
{},
|
|
370
|
+
'tracing-buffers-destroyed',
|
|
371
|
+
handler
|
|
372
|
+
);
|
|
373
|
+
});
|
|
310
374
|
}
|
|
311
375
|
|
|
312
376
|
/**
|
|
313
377
|
* Terminate worker and cleanup resources
|
|
314
378
|
*/
|
|
315
|
-
terminate() {
|
|
379
|
+
async terminate() {
|
|
316
380
|
if (this.worker) {
|
|
381
|
+
// Cleanup tracing buffers if in tracing mode
|
|
382
|
+
if (this.mode === 'tracing') {
|
|
383
|
+
await this.destroyTracingBuffers();
|
|
384
|
+
}
|
|
385
|
+
|
|
317
386
|
this.worker.terminate();
|
|
318
387
|
this.worker = null;
|
|
319
388
|
this.isInitialized = false;
|
|
@@ -388,6 +457,46 @@ export class RasterPath {
|
|
|
388
457
|
});
|
|
389
458
|
}
|
|
390
459
|
|
|
460
|
+
async #generateToolpathsTracing({ paths, step, zFloor, onProgress }) {
|
|
461
|
+
return new Promise((resolve, reject) => {
|
|
462
|
+
// Set up progress handler if callback provided
|
|
463
|
+
if (onProgress) {
|
|
464
|
+
const progressHandler = (data) => {
|
|
465
|
+
onProgress(data.percent, { current: data.current, total: data.total, pathIndex: data.pathIndex });
|
|
466
|
+
};
|
|
467
|
+
this.messageHandlers.set('tracing-progress', progressHandler);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const handler = (data) => {
|
|
471
|
+
// Clean up progress handler
|
|
472
|
+
if (onProgress) {
|
|
473
|
+
this.messageHandlers.delete('tracing-progress');
|
|
474
|
+
}
|
|
475
|
+
resolve(data);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
this.#sendMessage(
|
|
479
|
+
'tracing-generate-toolpaths',
|
|
480
|
+
{
|
|
481
|
+
paths,
|
|
482
|
+
terrainPositions: this.terrainData.positions,
|
|
483
|
+
terrainData: {
|
|
484
|
+
width: this.terrainData.gridWidth,
|
|
485
|
+
height: this.terrainData.gridHeight,
|
|
486
|
+
bounds: this.terrainData.bounds
|
|
487
|
+
},
|
|
488
|
+
toolPositions: this.toolData.positions,
|
|
489
|
+
step,
|
|
490
|
+
gridStep: this.resolution,
|
|
491
|
+
terrainBounds: this.terrainData.bounds,
|
|
492
|
+
zFloor: zFloor ?? 0
|
|
493
|
+
},
|
|
494
|
+
'tracing-toolpaths-complete',
|
|
495
|
+
handler
|
|
496
|
+
);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
391
500
|
async #generateToolpathsRadial({ triangles, bounds, toolData, xStep, yStep, zFloor, onProgress }) {
|
|
392
501
|
const maxRadius = this.#calculateMaxRadius(triangles);
|
|
393
502
|
|