@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 +144 -8
- package/build/app.js +36 -0
- package/build/index.html +3 -0
- package/build/raster-path.js +10 -2
- package/build/raster-worker.js +657 -75
- package/package.json +3 -2
- package/src/core/path-radial-v3.js +405 -0
- package/src/core/path-tracing.js +206 -115
- package/src/core/raster-config.js +24 -0
- package/src/core/raster-path.js +10 -2
- package/src/core/raster-worker.js +10 -0
- package/src/shaders/radial-rasterize-batched.wgsl +164 -0
- package/src/shaders/radial-rotate-triangles.wgsl +70 -0
- package/src/test/radial-v3-benchmark.cjs +184 -0
- package/src/test/radial-v3-bucket-test.cjs +154 -0
- package/src/web/app.js +36 -0
- package/src/web/index.html +3 -0
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
|
-
- **
|
|
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
|
-
- **
|
|
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 `'
|
|
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(
|
|
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
|
-
|
|
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
|
-
//
|
|
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">
|
package/build/raster-path.js
CHANGED
|
@@ -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
|
-
|
|
544
|
+
messageType,
|
|
537
545
|
{
|
|
538
546
|
triangles: triangles,
|
|
539
547
|
bucketData,
|