@gridspace/raster-path 1.0.2
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/LICENSE +20 -0
- package/README.md +292 -0
- package/build/app.js +1254 -0
- package/build/index.html +92 -0
- package/build/parse-stl.js +114 -0
- package/build/raster-path.js +688 -0
- package/build/serve.json +12 -0
- package/build/style.css +158 -0
- package/build/webgpu-worker.js +3011 -0
- package/package.json +58 -0
- package/scripts/build-shaders.js +65 -0
- package/src/index.js +688 -0
- package/src/shaders/planar-rasterize.wgsl +213 -0
- package/src/shaders/planar-toolpath.wgsl +83 -0
- package/src/shaders/radial-raster-v2.wgsl +195 -0
- package/src/web/app.js +1254 -0
- package/src/web/parse-stl.js +114 -0
- package/src/web/webgpu-worker.js +2520 -0
package/build/index.html
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Raster Path</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="container">
|
|
11
|
+
<!-- Controls Panel -->
|
|
12
|
+
<div class="controls">
|
|
13
|
+
<div class="section">
|
|
14
|
+
<h3>Mode</h3>
|
|
15
|
+
<div class="mode-toggle">
|
|
16
|
+
<label><input type="radio" name="mode" value="planar" checked> Planar</label>
|
|
17
|
+
<label><input type="radio" name="mode" value="radial"> Radial</label>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="section">
|
|
22
|
+
<h3>Load STL</h3>
|
|
23
|
+
<button id="load-model" class="btn">Load Model</button>
|
|
24
|
+
<div id="model-status" class="status">No model loaded</div>
|
|
25
|
+
<button id="load-tool" class="btn">Load Tool</button>
|
|
26
|
+
<div id="tool-status" class="status">No tool loaded</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="section">
|
|
30
|
+
<h3>Resolution</h3>
|
|
31
|
+
<select id="resolution">
|
|
32
|
+
<option value="0.100" selected>0.100mm</option>
|
|
33
|
+
<option value="0.050">0.050mm</option>
|
|
34
|
+
<option value="0.025">0.025mm</option>
|
|
35
|
+
<option value="0.010">0.010mm</option>
|
|
36
|
+
</select>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="section">
|
|
40
|
+
<h3>Parameters</h3>
|
|
41
|
+
<label>
|
|
42
|
+
Z Floor: <input type="number" id="z-floor" value="-100" step="10" style="width: 70px;">
|
|
43
|
+
</label>
|
|
44
|
+
<label>
|
|
45
|
+
X Step: <input type="number" id="x-step" value="5" min="1" max="50" style="width: 60px;">
|
|
46
|
+
</label>
|
|
47
|
+
<label>
|
|
48
|
+
Y Step: <input type="number" id="y-step" value="5" min="1" max="50" style="width: 60px;">
|
|
49
|
+
</label>
|
|
50
|
+
<label id="angle-step-container" class="hide">
|
|
51
|
+
Angle Step (deg): <input type="number" id="angle-step" value="1" min="0.1" max="10" step="0.1" style="width: 60px;">
|
|
52
|
+
</label>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="section">
|
|
56
|
+
<h3>Process</h3>
|
|
57
|
+
<button id="rasterize" class="btn" disabled>Rasterize</button>
|
|
58
|
+
<button id="generate-toolpath" class="btn" disabled>Generate Toolpath</button>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="section">
|
|
62
|
+
<h3>View</h3>
|
|
63
|
+
<label><input type="checkbox" id="show-model" checked> Model</label>
|
|
64
|
+
<label><input type="checkbox" id="show-raster"> Raster</label>
|
|
65
|
+
<label><input type="checkbox" id="show-paths"> Toolpaths</label>
|
|
66
|
+
<label id="wrapped-container" class="hide">
|
|
67
|
+
<input type="checkbox" id="show-wrapped"> Wrapped
|
|
68
|
+
</label>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="section">
|
|
72
|
+
<h3>Info</h3>
|
|
73
|
+
<div id="info" class="info-text">Ready</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<!-- 3D Canvas -->
|
|
78
|
+
<canvas id="canvas"></canvas>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<script type="importmap">
|
|
82
|
+
{
|
|
83
|
+
"imports": {
|
|
84
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
|
|
85
|
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
89
|
+
<script type="module" src="app.js"></script>
|
|
90
|
+
<!-- <script type="module" src="webgpu-worker.js"></script> -->
|
|
91
|
+
</body>
|
|
92
|
+
</html>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// parse-stl.js
|
|
2
|
+
// Pure JavaScript STL parser (binary and ASCII)
|
|
3
|
+
|
|
4
|
+
// Calculate bounds from positions array
|
|
5
|
+
function calculateBounds(positions) {
|
|
6
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
7
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
10
|
+
const x = positions[i];
|
|
11
|
+
const y = positions[i + 1];
|
|
12
|
+
const z = positions[i + 2];
|
|
13
|
+
|
|
14
|
+
minX = Math.min(minX, x);
|
|
15
|
+
maxX = Math.max(maxX, x);
|
|
16
|
+
minY = Math.min(minY, y);
|
|
17
|
+
maxY = Math.max(maxY, y);
|
|
18
|
+
minZ = Math.min(minZ, z);
|
|
19
|
+
maxZ = Math.max(maxZ, z);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
min: { x: minX, y: minY, z: minZ },
|
|
24
|
+
max: { x: maxX, y: maxY, z: maxZ }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Parse binary STL file
|
|
29
|
+
export function parseBinarySTL(buffer) {
|
|
30
|
+
const dataView = new DataView(buffer);
|
|
31
|
+
|
|
32
|
+
// Skip 80-byte header
|
|
33
|
+
const numTriangles = dataView.getUint32(80, true); // little-endian
|
|
34
|
+
|
|
35
|
+
const positions = new Float32Array(numTriangles * 9);
|
|
36
|
+
let offset = 84; // After header and triangle count
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < numTriangles; i++) {
|
|
39
|
+
// Skip normal (12 bytes)
|
|
40
|
+
offset += 12;
|
|
41
|
+
|
|
42
|
+
// Read 3 vertices (9 floats)
|
|
43
|
+
for (let j = 0; j < 9; j++) {
|
|
44
|
+
positions[i * 9 + j] = dataView.getFloat32(offset, true);
|
|
45
|
+
offset += 4;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Skip attribute byte count (2 bytes)
|
|
49
|
+
offset += 2;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bounds = calculateBounds(positions);
|
|
53
|
+
return { positions, triangleCount: numTriangles, bounds };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Parse ASCII STL file
|
|
57
|
+
export function parseASCIISTL(text) {
|
|
58
|
+
const positions = [];
|
|
59
|
+
const lines = text.split('\n');
|
|
60
|
+
let inFacet = false;
|
|
61
|
+
const vertices = [];
|
|
62
|
+
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
|
|
66
|
+
if (trimmed.startsWith('facet')) {
|
|
67
|
+
inFacet = true;
|
|
68
|
+
vertices.length = 0;
|
|
69
|
+
} else if (trimmed.startsWith('vertex')) {
|
|
70
|
+
if (inFacet) {
|
|
71
|
+
const parts = trimmed.split(/\s+/);
|
|
72
|
+
vertices.push(
|
|
73
|
+
parseFloat(parts[1]),
|
|
74
|
+
parseFloat(parts[2]),
|
|
75
|
+
parseFloat(parts[3])
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
} else if (trimmed.startsWith('endfacet')) {
|
|
79
|
+
if (vertices.length === 9) {
|
|
80
|
+
positions.push(...vertices);
|
|
81
|
+
}
|
|
82
|
+
inFacet = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const positionsArray = new Float32Array(positions);
|
|
87
|
+
const bounds = calculateBounds(positionsArray);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
positions: positionsArray,
|
|
91
|
+
triangleCount: positions.length / 9,
|
|
92
|
+
bounds
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Auto-detect and parse STL file
|
|
97
|
+
export function parseSTL(buffer) {
|
|
98
|
+
// Try to detect if binary or ASCII
|
|
99
|
+
const view = new Uint8Array(buffer);
|
|
100
|
+
const header = String.fromCharCode(...view.slice(0, 5));
|
|
101
|
+
|
|
102
|
+
if (header === 'solid') {
|
|
103
|
+
// Might be ASCII, but could also be binary with "solid" in header
|
|
104
|
+
// Try to decode as text
|
|
105
|
+
const text = new TextDecoder().decode(buffer);
|
|
106
|
+
if (text.includes('facet') && text.includes('vertex')) {
|
|
107
|
+
// Looks like ASCII
|
|
108
|
+
return parseASCIISTL(text);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Default to binary
|
|
113
|
+
return parseBinarySTL(buffer);
|
|
114
|
+
}
|