@gridspace/raster-path 1.0.6 → 1.0.8

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/src/web/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
 
@@ -69,6 +71,12 @@ function saveParameters() {
69
71
  if (showWrappedCheckbox) {
70
72
  localStorage.setItem('raster-showWrapped', showWrappedCheckbox.checked);
71
73
  }
74
+
75
+ // Save radial V3 checkbox
76
+ const radialV3Checkbox = document.getElementById('radial-v3');
77
+ if (radialV3Checkbox) {
78
+ localStorage.setItem('raster-radialV3', radialV3Checkbox.checked);
79
+ }
72
80
  }
73
81
 
74
82
  function loadParameters() {
@@ -127,6 +135,12 @@ function loadParameters() {
127
135
  document.getElementById('angle-step').value = angleStep;
128
136
  }
129
137
 
138
+ const savedTraceStep = localStorage.getItem('raster-traceStep');
139
+ if (savedTraceStep !== null) {
140
+ traceStep = parseFloat(savedTraceStep);
141
+ document.getElementById('trace-step').value = traceStep;
142
+ }
143
+
130
144
  const savedToolSize = localStorage.getItem('raster-toolSize');
131
145
  if (savedToolSize !== null) {
132
146
  toolSize = parseFloat(savedToolSize);
@@ -145,6 +159,15 @@ function loadParameters() {
145
159
  showWrappedCheckbox.checked = savedShowWrapped === 'true';
146
160
  }
147
161
  }
162
+
163
+ // Restore radial V3 checkbox
164
+ const savedRadialV3 = localStorage.getItem('raster-radialV3');
165
+ if (savedRadialV3 !== null) {
166
+ const radialV3Checkbox = document.getElementById('radial-v3');
167
+ if (radialV3Checkbox) {
168
+ radialV3Checkbox.checked = savedRadialV3 === 'true';
169
+ }
170
+ }
148
171
  }
149
172
 
150
173
  // ============================================================================
@@ -513,10 +536,14 @@ async function initRasterPath() {
513
536
  rasterPath.terminate();
514
537
  }
515
538
 
539
+ const radialV3Checkbox = document.getElementById('radial-v3');
540
+ const useRadialV3 = mode === 'radial' && radialV3Checkbox && radialV3Checkbox.checked;
541
+
516
542
  rasterPath = new RasterPath({
517
543
  mode: mode,
518
544
  resolution: resolution,
519
545
  rotationStep: mode === 'radial' ? angleStep : undefined,
546
+ radialV3: useRadialV3,
520
547
  batchDivisor: 5,
521
548
  debug: true
522
549
  });
@@ -545,8 +572,8 @@ async function rasterizeAll() {
545
572
  updateInfo(`Tool loaded in ${(t1 - t0).toFixed(0)}ms`);
546
573
  }
547
574
 
548
- if (mode === 'planar') {
549
- // Planar mode: rasterize terrain immediately
575
+ if (mode === 'planar' || mode === 'tracing') {
576
+ // Planar/Tracing mode: rasterize terrain immediately
550
577
  if (modelTriangles) {
551
578
  updateInfo('Rasterizing terrain...');
552
579
  const t0 = performance.now();
@@ -557,7 +584,7 @@ async function rasterizeAll() {
557
584
  const t1 = performance.now();
558
585
  updateInfo(`Terrain rasterized in ${(t1 - t0).toFixed(0)}ms`);
559
586
  }
560
- } else {
587
+ } else if (mode === 'radial') {
561
588
  // Radial mode: MUST load tool FIRST
562
589
  if (!toolTriangles) {
563
590
  updateInfo('Error: Radial mode requires tool to be loaded first');
@@ -608,30 +635,81 @@ async function generateToolpath() {
608
635
  updateInfo('Model must be rasterized first');
609
636
  return;
610
637
  }
611
- } else {
638
+ } else if (mode === 'radial') {
612
639
  // Radial mode: terrain must be loaded (stored internally)
613
640
  if (!modelTriangles) {
614
641
  updateInfo('Model STL must be loaded');
615
642
  return;
616
643
  }
644
+ } else if (mode === 'tracing') {
645
+ // Tracing mode: terrain must be rasterized
646
+ if (!modelRasterData) {
647
+ updateInfo('Model must be rasterized first');
648
+ return;
649
+ }
617
650
  }
618
651
 
619
652
  try {
620
653
  const t0 = performance.now();
621
654
  updateInfo('Generating toolpath...');
622
655
 
623
- // Unified API - works for both modes!
624
- toolpathData = await rasterPath.generateToolpaths({
625
- xStep: xStep,
626
- yStep: yStep,
656
+ // Generate trace paths for tracing mode
657
+ let tracePaths = null;
658
+ if (mode === 'tracing') {
659
+ // Get model bounds from raster data
660
+ const bounds = modelRasterData.bounds;
661
+ const minX = bounds.min.x;
662
+ const maxX = bounds.max.x;
663
+ const minY = bounds.min.y;
664
+ const maxY = bounds.max.y;
665
+
666
+ // Create two cross paths: horizontal through center, vertical through center
667
+ const centerY = (minY + maxY) / 2;
668
+ const centerX = (minX + maxX) / 2;
669
+
670
+ tracePaths = [
671
+ new Float32Array([minX, centerY, maxX, centerY]), // Horizontal line
672
+ new Float32Array([centerX, minY, centerX, maxY]) // Vertical line
673
+ ];
674
+
675
+ debug.log(`Generated trace paths: H(${minX.toFixed(2)}, ${centerY.toFixed(2)}) to (${maxX.toFixed(2)}, ${centerY.toFixed(2)})`);
676
+ debug.log(` V(${centerX.toFixed(2)}, ${minY.toFixed(2)}) to (${centerX.toFixed(2)}, ${maxY.toFixed(2)})`);
677
+ }
678
+
679
+ // Unified API - works for all modes!
680
+ const generateParams = {
627
681
  zFloor: zFloor
628
- });
682
+ };
683
+
684
+ if (mode === 'tracing') {
685
+ generateParams.paths = tracePaths;
686
+ generateParams.step = traceStep;
687
+ } else {
688
+ generateParams.xStep = xStep;
689
+ generateParams.yStep = yStep;
690
+ }
691
+
692
+ toolpathData = await rasterPath.generateToolpaths(generateParams);
629
693
 
630
694
  const t1 = performance.now();
631
695
 
632
696
  if (mode === 'planar') {
633
697
  const numPoints = toolpathData.pathData.length;
634
698
  updateInfo(`Toolpath generated: ${numPoints.toLocaleString()} points in ${(t1 - t0).toFixed(0)}ms`);
699
+ } else if (mode === 'tracing') {
700
+ const totalPoints = toolpathData.paths.reduce((sum, path) => sum + path.length / 3, 0);
701
+ debug.log(`[Tracing] Generated ${toolpathData.paths.length} paths with ${totalPoints} total points`);
702
+
703
+ // Log sample Z values from each path
704
+ toolpathData.paths.forEach((path, idx) => {
705
+ const zValues = [];
706
+ for (let i = 2; i < Math.min(path.length, 15); i += 3) {
707
+ zValues.push(path[i].toFixed(2));
708
+ }
709
+ debug.log(`[Tracing] Path ${idx} Z samples:`, zValues.join(', '));
710
+ });
711
+
712
+ updateInfo(`Toolpath generated: ${toolpathData.paths.length} paths, ${totalPoints.toLocaleString()} points in ${(t1 - t0).toFixed(0)}ms`);
635
713
  } else {
636
714
  // debug.log('[Radial] Toolpaths generated:', toolpathData);
637
715
  debug.log(`[Radial] Received ${toolpathData.strips.length} strips from worker, numStrips=${toolpathData.numStrips}`);
@@ -901,8 +979,8 @@ function displayModelRaster(wrapped) {
901
979
  const positions = [];
902
980
  const colors = [];
903
981
 
904
- if (mode === 'planar') {
905
- // Planar: terrain is dense (Z-only array)
982
+ if (mode === 'planar' || mode === 'tracing') {
983
+ // Planar/Tracing: terrain is dense (Z-only array)
906
984
  const { positions: rasterPos, bounds, gridWidth, gridHeight } = modelRasterData;
907
985
  const stepSize = resolution;
908
986
 
@@ -1057,6 +1135,59 @@ function displayToolpaths(wrapped) {
1057
1135
  return;
1058
1136
  }
1059
1137
 
1138
+ if (mode === 'tracing') {
1139
+ // Tracing toolpaths - array of XYZ paths
1140
+ const { paths } = toolpathData;
1141
+
1142
+ // Calculate total points
1143
+ let totalPoints = 0;
1144
+ for (const path of paths) {
1145
+ totalPoints += path.length / 3;
1146
+ }
1147
+
1148
+ debug.log('[Toolpath Display] Tracing mode:', paths.length, 'paths,', totalPoints, 'total points');
1149
+
1150
+ // Preallocate typed arrays
1151
+ const positions = new Float32Array(totalPoints * 3);
1152
+ const colors = new Float32Array(totalPoints * 3);
1153
+
1154
+ let arrayIdx = 0;
1155
+ for (let pathIdx = 0; pathIdx < paths.length; pathIdx++) {
1156
+ const path = paths[pathIdx];
1157
+ const numPoints = path.length / 3;
1158
+
1159
+ // Use different colors for each path
1160
+ const color = pathIdx === 0 ? [1, 0.4, 0] : [0, 0.8, 1]; // Orange for horizontal, cyan for vertical
1161
+
1162
+ for (let i = 0; i < numPoints; i++) {
1163
+ positions[arrayIdx] = path[i * 3]; // X
1164
+ positions[arrayIdx + 1] = path[i * 3 + 1]; // Y
1165
+ positions[arrayIdx + 2] = path[i * 3 + 2]; // Z
1166
+
1167
+ colors[arrayIdx] = color[0];
1168
+ colors[arrayIdx + 1] = color[1];
1169
+ colors[arrayIdx + 2] = color[2];
1170
+
1171
+ arrayIdx += 3;
1172
+ }
1173
+ }
1174
+
1175
+ // Create geometry
1176
+ const geometry = new THREE.BufferGeometry();
1177
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
1178
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
1179
+
1180
+ const material = new THREE.PointsMaterial({
1181
+ size: resolution * 1.5,
1182
+ vertexColors: true
1183
+ });
1184
+
1185
+ toolpathPoints = new THREE.Points(geometry, material);
1186
+ rotatedGroup.add(toolpathPoints);
1187
+
1188
+ return; // Exit early for tracing mode
1189
+ }
1190
+
1060
1191
  if (mode === 'planar') {
1061
1192
  // Planar toolpaths
1062
1193
  const { pathData, numScanlines, pointsPerLine } = toolpathData;
@@ -1324,15 +1455,35 @@ function updateButtonStates() {
1324
1455
  // ============================================================================
1325
1456
 
1326
1457
  function updateModeUI() {
1327
- // Show/hide wrapped toggle and angle step for radial mode
1458
+ // Show/hide mode-specific controls
1328
1459
  const wrappedContainer = document.getElementById('wrapped-container').classList;
1329
1460
  const angleStepContainer = document.getElementById('angle-step-container').classList;
1461
+ const traceStepContainer = document.getElementById('trace-step-container').classList;
1462
+ const xStepContainer = document.getElementById('x-step-container').classList;
1463
+ const yStepContainer = document.getElementById('y-step-container').classList;
1464
+ const radialV3Container = document.getElementById('radial-v3-container').classList;
1465
+
1330
1466
  if (mode === 'radial') {
1331
1467
  wrappedContainer.remove('hide');
1332
1468
  angleStepContainer.remove('hide');
1469
+ traceStepContainer.add('hide');
1470
+ xStepContainer.remove('hide');
1471
+ yStepContainer.remove('hide');
1472
+ radialV3Container.remove('hide');
1473
+ } else if (mode === 'tracing') {
1474
+ wrappedContainer.add('hide');
1475
+ angleStepContainer.add('hide');
1476
+ traceStepContainer.remove('hide');
1477
+ xStepContainer.add('hide');
1478
+ yStepContainer.add('hide');
1333
1479
  } else {
1480
+ // planar
1334
1481
  wrappedContainer.add('hide');
1335
1482
  angleStepContainer.add('hide');
1483
+ traceStepContainer.add('hide');
1484
+ radialV3Container.add('hide');
1485
+ xStepContainer.remove('hide');
1486
+ yStepContainer.remove('hide');
1336
1487
  }
1337
1488
  }
1338
1489
 
@@ -1446,12 +1597,36 @@ document.addEventListener('DOMContentLoaded', async () => {
1446
1597
  modelRasterData = null; // Need to re-rasterize with new angle step
1447
1598
  toolRasterData = null;
1448
1599
  toolpathData = null;
1600
+ initRasterPath(); // Reinit with new angle step
1449
1601
  }
1450
1602
  saveParameters();
1451
1603
  updateInfo(`Angle Step changed to ${angleStep}°`);
1452
1604
  updateButtonStates();
1453
1605
  });
1454
1606
 
1607
+ document.getElementById('trace-step').addEventListener('change', (e) => {
1608
+ traceStep = parseFloat(e.target.value);
1609
+ if (mode === 'tracing') {
1610
+ toolpathData = null; // Need to regenerate toolpath
1611
+ }
1612
+ saveParameters();
1613
+ updateInfo(`Trace Step changed to ${traceStep}mm`);
1614
+ updateButtonStates();
1615
+ });
1616
+
1617
+ document.getElementById('radial-v3').addEventListener('change', (e) => {
1618
+ if (mode === 'radial') {
1619
+ modelRasterData = null; // Need to re-rasterize with different algorithm
1620
+ toolRasterData = null;
1621
+ toolpathData = null;
1622
+ initRasterPath(); // Reinit with V3 setting
1623
+ }
1624
+ saveParameters();
1625
+ const v3Status = e.target.checked ? 'V3 (experimental)' : 'V2 (default)';
1626
+ updateInfo(`Radial algorithm: ${v3Status}`);
1627
+ updateButtonStates();
1628
+ });
1629
+
1455
1630
  // Tool size change
1456
1631
  document.getElementById('tool-size').addEventListener('change', async (e) => {
1457
1632
  toolSize = parseFloat(e.target.value);
@@ -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,21 @@
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>
95
+ <label id="radial-v3-container" class="hide">
96
+ <input type="checkbox" id="radial-v3"> Use V3 (experimental)
97
+ </label>
91
98
  </div>
92
99
 
93
100
  <div class="section">