@gridspace/raster-path 1.0.7 → 1.0.9

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/README.md CHANGED
@@ -4,10 +4,11 @@ Fast browser-based terrain + tool path generator using WebGPU compute shaders.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Dual Mode Rasterization**: Planar (traditional XY grid) and Radial (cylindrical unwrap) modes
7
+ - **Multiple Operational Modes**: Planar (XY grid), Radial (cylindrical), and Tracing (path-following)
8
8
  - **CNC Toolpath Generation**: Generate toolpaths by simulating tool movement over terrain
9
9
  - **GPU Accelerated**: 20-100× faster than CPU-based solutions
10
- - **Unified API**: Clean three-method interface that works uniformly across both modes
10
+ - **Optimized Radial Variants**: V2 (default), V3 (memory-optimized), and V4 (slice-based lathe)
11
+ - **Unified API**: Clean three-method interface that works uniformly across all modes
11
12
  - **ESM Module**: Importable package for browser applications
12
13
 
13
14
  ## Quick Start
@@ -53,7 +54,7 @@ raster.terminate();
53
54
  ### Radial Mode (for cylindrical parts)
54
55
 
55
56
  ```javascript
56
- // Initialize for radial mode
57
+ // Initialize for radial mode (V2 default)
57
58
  const raster = new RasterPath({
58
59
  mode: 'radial',
59
60
  resolution: 0.1, // Radial resolution (mm)
@@ -79,6 +80,62 @@ const toolpathData = await raster.generateToolpaths({
79
80
  console.log(`Generated ${toolpathData.numStrips} strips, ${toolpathData.totalPoints} points`);
80
81
  ```
81
82
 
83
+ **Radial Variants:**
84
+ ```javascript
85
+ // Use V3 (memory-optimized) for large models
86
+ const rasterV3 = new RasterPath({
87
+ mode: 'radial',
88
+ resolution: 0.1,
89
+ rotationStep: 1.0,
90
+ radialV3: true
91
+ });
92
+
93
+ // Use V4 (slice-based lathe, experimental) with pre-sliced data
94
+ const rasterV4 = new RasterPath({
95
+ mode: 'radial',
96
+ resolution: 0.5,
97
+ rotationStep: 1.0,
98
+ radialV4: true
99
+ });
100
+ ```
101
+
102
+ ### Tracing Mode (for path-following)
103
+
104
+ ```javascript
105
+ // Initialize for tracing mode
106
+ const raster = new RasterPath({
107
+ mode: 'tracing',
108
+ resolution: 0.1 // Terrain rasterization resolution
109
+ });
110
+ await raster.init();
111
+
112
+ // Load tool and terrain
113
+ await raster.loadTool({ triangles: toolTriangles });
114
+ await raster.loadTerrain({
115
+ triangles: terrainTriangles,
116
+ zFloor: -100
117
+ });
118
+
119
+ // Define input paths as arrays of XY coordinate pairs
120
+ const paths = [
121
+ new Float32Array([x1, y1, x2, y2, x3, y3, ...]), // Path 1
122
+ new Float32Array([x1, y1, x2, y2, ...]) // Path 2
123
+ ];
124
+
125
+ // Generate toolpaths by tracing along paths
126
+ const toolpathData = await raster.generateToolpaths({
127
+ paths: paths,
128
+ step: 0.5, // Sample every 0.5mm along each path
129
+ zFloor: -100
130
+ });
131
+
132
+ // Output is array of XYZ coordinate arrays (one per path)
133
+ console.log(`Generated ${toolpathData.pathResults.length} traced paths`);
134
+ toolpathData.pathResults.forEach((path, i) => {
135
+ console.log(` Path ${i}: ${path.length / 3} points`);
136
+ });
137
+ ```
138
+
82
139
  ### Demo UI
83
140
 
84
141
  ```bash
@@ -101,6 +158,9 @@ Open http://localhost:3000 and drag STL files onto the interface.
101
158
 
102
159
  ### Radial Mode (Cylindrical Rasterization)
103
160
 
161
+ Three variants are available with different performance characteristics:
162
+
163
+ #### V2 (Default) - Ray-Based Rasterization
104
164
  1. **Tool Rasterization**: Rasterize tool in planar mode (same as above)
105
165
  2. **Terrain Preparation**: Center terrain in YZ plane and store triangles
106
166
  3. **Toolpath Generation**:
@@ -110,6 +170,56 @@ Open http://localhost:3000 and drag STL files onto the interface.
110
170
  - Calculate tool-terrain collisions along each radial strip
111
171
  - Output array of strips (one per angle), each containing Z-heights along X-axis
112
172
 
173
+ #### V3 - Bucket-Angle Pipeline (Memory Optimized)
174
+ Enable with `radialV3: true` option.
175
+
176
+ **Algorithm:**
177
+ 1. **Tool Rasterization**: Same as V2
178
+ 2. **Terrain Preparation**: Bucket triangles by X-coordinate
179
+ 3. **Toolpath Generation** (for each rotation angle):
180
+ - Rotate all triangles in bucket by angle (GPU parallel)
181
+ - Filter by Y-bounds (skip triangles outside tool radius)
182
+ - Rasterize all buckets in single dispatch → dense terrain strip
183
+ - Generate toolpath from strip immediately
184
+
185
+ **Advantages over V2:**
186
+ - Lower memory usage (only one angle's data in GPU at a time)
187
+ - Y-axis filtering reduces unnecessary triangle processing
188
+ - Better cache locality by processing each bucket completely
189
+
190
+ #### V4 - Slice-Based Lathe (Experimental)
191
+ Enable with `radialV4: true` option.
192
+
193
+ **Algorithm:**
194
+ 1. **Tool Rasterization**: Same as V2
195
+ 2. **Terrain Slicing** (CPU): Slice model along X-axis at dense intervals
196
+ - Each slice is a YZ plane intersection → array of line segments
197
+ 3. **Toolpath Generation** (for each rotation angle):
198
+ - Rotate all slice lines around X-axis (CPU)
199
+ - GPU shader traces tool through rotated slices
200
+ - For each X position, ray-cast through corresponding slice to find max Z collision
201
+
202
+ **Advantages:**
203
+ - No rasterization overhead, works directly with geometry
204
+ - CPU/GPU balanced workload
205
+ - Based on proven Kiri:Moto lathePath algorithm
206
+
207
+ **Note:** V4 expects pre-sliced data and is designed for integration with external slicing engines.
208
+
209
+ ### Tracing Mode (Path-Following Toolpath)
210
+
211
+ 1. **Tool Rasterization**: Rasterize tool in planar mode
212
+ 2. **Terrain Rasterization**: Rasterize terrain on XY grid (same as planar mode)
213
+ 3. **Path Sampling**: Sample each input polyline at specified step resolution (e.g., every 0.5mm)
214
+ 4. **Toolpath Generation**:
215
+ - For each sampled point on each path:
216
+ - Convert world coordinates to terrain grid coordinates
217
+ - Test tool collision at that grid position using planar algorithm
218
+ - Calculate maximum collision Z-height
219
+ - Output array of XYZ coordinate arrays (one per input path)
220
+
221
+ **Use Case:** Generate toolpaths that follow pre-defined paths (e.g., outlines, contours) rather than scanning the entire grid.
222
+
113
223
  ## Performance
114
224
 
115
225
  Example (84×84×28mm model, 6,120 triangles):
@@ -148,9 +258,11 @@ build/ # Built files (generated by npm run build)
148
258
  Constructor: `new RasterPath(options)`
149
259
 
150
260
  **Options**:
151
- - `mode` (string): `'planar'` or `'radial'`
261
+ - `mode` (string): `'planar'`, `'radial'`, or `'tracing'`
152
262
  - `resolution` (number): Grid resolution in mm (e.g., 0.1)
153
263
  - `rotationStep` (number, radial only): Degrees between rays (e.g., 1.0)
264
+ - `radialV3` (boolean, radial only): Enable V3 memory-optimized pipeline (default: false)
265
+ - `radialV4` (boolean, radial only): Enable V4 slice-based lathe pipeline (default: false)
154
266
 
155
267
  #### `async init()`
156
268
  Initialize WebGPU worker. Must be called before other methods.
@@ -221,13 +333,22 @@ await raster.loadTerrain({
221
333
 
222
334
  ---
223
335
 
224
- #### `async generateToolpaths({ xStep, yStep, zFloor, onProgress })`
336
+ #### `async generateToolpaths(options)`
225
337
  Generate toolpaths from loaded tool and terrain. Must call `loadTool()` and `loadTerrain()` first.
226
338
 
227
- **Parameters**:
339
+ **Parameters (mode-dependent)**:
340
+
341
+ **Planar and Radial modes:**
228
342
  - `xStep` (number): Sample every Nth point in X direction
229
343
  - `yStep` (number): Sample every Nth point in Y direction
230
344
  - `zFloor` (number): Z floor value for out-of-bounds areas
345
+ - `radiusOffset` (number, radial only): Radial offset in mm
346
+ - `onProgress` (function, optional): Progress callback `(progress: number) => void`
347
+
348
+ **Tracing mode:**
349
+ - `paths` (Array<Float32Array>): Array of input polylines (each as XY coordinate pairs)
350
+ - `step` (number): Sample resolution along paths in world units (e.g., 0.5mm)
351
+ - `zFloor` (number): Z floor value for out-of-bounds areas
231
352
  - `onProgress` (function, optional): Progress callback `(progress: number) => void`
232
353
 
233
354
  **Returns**:
@@ -243,15 +364,30 @@ Generate toolpaths from loaded tool and terrain. Must call `loadTool()` and `loa
243
364
  - `numStrips` (number): Total number of strips
244
365
  - `totalPoints` (number): Sum of all points across strips
245
366
 
246
- **Example**:
367
+ - Tracing mode: `Promise<object>` with:
368
+ - `pathResults` (Array<Float32Array>): Array of XYZ coordinate arrays (one per input path)
369
+ - `totalPoints` (number): Sum of all points across paths
370
+
371
+ **Examples**:
247
372
  ```javascript
248
- // Works for both planar and radial modes!
373
+ // Planar and radial modes
249
374
  const toolpathData = await raster.generateToolpaths({
250
375
  xStep: 5,
251
376
  yStep: 5,
252
377
  zFloor: -100,
253
378
  radiusOffset: 20 // radial mode only
254
379
  });
380
+
381
+ // Tracing mode
382
+ const paths = [
383
+ new Float32Array([x1, y1, x2, y2, ...]),
384
+ new Float32Array([x1, y1, x2, y2, ...])
385
+ ];
386
+ const toolpathData = await raster.generateToolpaths({
387
+ paths: paths,
388
+ step: 0.5, // Sample every 0.5mm
389
+ zFloor: -100
390
+ });
255
391
  ```
256
392
 
257
393
  ---
package/build/app.js CHANGED
@@ -71,6 +71,12 @@ function saveParameters() {
71
71
  if (showWrappedCheckbox) {
72
72
  localStorage.setItem('raster-showWrapped', showWrappedCheckbox.checked);
73
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
+ }
74
80
  }
75
81
 
76
82
  function loadParameters() {
@@ -153,6 +159,15 @@ function loadParameters() {
153
159
  showWrappedCheckbox.checked = savedShowWrapped === 'true';
154
160
  }
155
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
+ }
156
171
  }
157
172
 
158
173
  // ============================================================================
@@ -521,10 +536,14 @@ async function initRasterPath() {
521
536
  rasterPath.terminate();
522
537
  }
523
538
 
539
+ const radialV3Checkbox = document.getElementById('radial-v3');
540
+ const useRadialV3 = mode === 'radial' && radialV3Checkbox && radialV3Checkbox.checked;
541
+
524
542
  rasterPath = new RasterPath({
525
543
  mode: mode,
526
544
  resolution: resolution,
527
545
  rotationStep: mode === 'radial' ? angleStep : undefined,
546
+ radialV3: useRadialV3,
528
547
  batchDivisor: 5,
529
548
  debug: true
530
549
  });
@@ -1442,6 +1461,7 @@ function updateModeUI() {
1442
1461
  const traceStepContainer = document.getElementById('trace-step-container').classList;
1443
1462
  const xStepContainer = document.getElementById('x-step-container').classList;
1444
1463
  const yStepContainer = document.getElementById('y-step-container').classList;
1464
+ const radialV3Container = document.getElementById('radial-v3-container').classList;
1445
1465
 
1446
1466
  if (mode === 'radial') {
1447
1467
  wrappedContainer.remove('hide');
@@ -1449,6 +1469,7 @@ function updateModeUI() {
1449
1469
  traceStepContainer.add('hide');
1450
1470
  xStepContainer.remove('hide');
1451
1471
  yStepContainer.remove('hide');
1472
+ radialV3Container.remove('hide');
1452
1473
  } else if (mode === 'tracing') {
1453
1474
  wrappedContainer.add('hide');
1454
1475
  angleStepContainer.add('hide');
@@ -1460,6 +1481,7 @@ function updateModeUI() {
1460
1481
  wrappedContainer.add('hide');
1461
1482
  angleStepContainer.add('hide');
1462
1483
  traceStepContainer.add('hide');
1484
+ radialV3Container.add('hide');
1463
1485
  xStepContainer.remove('hide');
1464
1486
  yStepContainer.remove('hide');
1465
1487
  }
@@ -1575,6 +1597,7 @@ document.addEventListener('DOMContentLoaded', async () => {
1575
1597
  modelRasterData = null; // Need to re-rasterize with new angle step
1576
1598
  toolRasterData = null;
1577
1599
  toolpathData = null;
1600
+ initRasterPath(); // Reinit with new angle step
1578
1601
  }
1579
1602
  saveParameters();
1580
1603
  updateInfo(`Angle Step changed to ${angleStep}°`);
@@ -1591,6 +1614,19 @@ document.addEventListener('DOMContentLoaded', async () => {
1591
1614
  updateButtonStates();
1592
1615
  });
1593
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
+
1594
1630
  // Tool size change
1595
1631
  document.getElementById('tool-size').addEventListener('change', async (e) => {
1596
1632
  toolSize = parseFloat(e.target.value);
package/build/index.html CHANGED
@@ -92,6 +92,9 @@
92
92
  <label id="trace-step-container" class="hide">
93
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
94
  </label>
95
+ <label id="radial-v3-container" class="hide">
96
+ <input type="checkbox" id="radial-v3"> Use V3 (experimental)
97
+ </label>
95
98
  </div>
96
99
 
97
100
  <div class="section">
@@ -111,6 +111,8 @@ export class RasterPath {
111
111
  gpuMemorySafetyMargin: config.gpuMemorySafetyMargin ?? 0.8,
112
112
  autoTiling: config.autoTiling ?? true,
113
113
  batchDivisor: config.batchDivisor ?? 1, // For testing batching overhead
114
+ radialV3: config.radialV3 ?? false, // Use radial V3 pipeline (rotate-filter-toolpath)
115
+ radialV4: config.radialV4 ?? false, // Use radial V4 pipeline (slice-based lathe)
114
116
  debug: config.debug,
115
117
  quiet: config.quiet
116
118
  };
@@ -531,9 +533,15 @@ export class RasterPath {
531
533
  resolve(data);
532
534
  };
533
535
 
534
- // Send entire pipeline to worker
536
+ // Send entire pipeline to worker (use V3 or V4 if configured)
537
+ const messageType = this.config.radialV4
538
+ ? 'radial-generate-toolpaths-v4'
539
+ : this.config.radialV3
540
+ ? 'radial-generate-toolpaths-v3'
541
+ : 'radial-generate-toolpaths';
542
+
535
543
  this.#sendMessage(
536
- 'radial-generate-toolpaths',
544
+ messageType,
537
545
  {
538
546
  triangles: triangles,
539
547
  bucketData,