@gridspace/raster-path 1.0.3 → 1.0.4
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 +3 -5
- package/build/app.js +363 -39
- package/build/index.html +39 -1
- package/build/raster-path.js +13 -13
- package/build/style.css +65 -0
- package/build/webgpu-worker.js +475 -686
- package/package.json +6 -2
- package/scripts/build-shaders.js +1 -1
- package/src/index.js +13 -13
- package/src/shaders/{radial-raster-v2.wgsl → radial-raster.wgsl} +8 -2
- package/src/test/batch-divisor-benchmark.cjs +286 -0
- package/src/test/lathe-cylinder-2-debug.cjs +334 -0
- package/src/test/lathe-cylinder-2-test.cjs +157 -0
- package/src/test/lathe-cylinder-test.cjs +198 -0
- package/src/test/work-estimation-profile.cjs +406 -0
- package/src/test/workload-calculator-demo.cjs +113 -0
- package/src/test/workload-calibration.cjs +310 -0
- package/src/web/app.js +363 -39
- package/src/web/index.html +39 -1
- package/src/web/style.css +65 -0
- package/src/web/webgpu-worker.js +470 -687
- package/src/workload-calculator.js +318 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// lathe-cylinder-2-debug.cjs
|
|
2
|
+
// Debug test for lathe-cylinder-2.stl with specific parameters
|
|
3
|
+
// Run multiple times to check for non-deterministic behavior
|
|
4
|
+
|
|
5
|
+
const { app, BrowserWindow } = require('electron');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const TEST_ITERATIONS = 5; // Run test 5 times to check consistency
|
|
9
|
+
|
|
10
|
+
const TEST_CONFIG = {
|
|
11
|
+
model: '../benchmark/fixtures/lathe-cylinder-2.stl',
|
|
12
|
+
tool: '../benchmark/fixtures/tool.stl',
|
|
13
|
+
resolution: 0.05,
|
|
14
|
+
rotationStep: 1.0,
|
|
15
|
+
toolDiameter: 5.0, // Scale tool to 5mm
|
|
16
|
+
xStep: 1,
|
|
17
|
+
yStep: 1
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
console.log('=== Lathe Cylinder 2 Debug Test ===');
|
|
21
|
+
console.log('Testing for non-deterministic behavior / missing toolpaths');
|
|
22
|
+
console.log(`Model: lathe-cylinder-2.stl`);
|
|
23
|
+
console.log(`Resolution: ${TEST_CONFIG.resolution}mm`);
|
|
24
|
+
console.log(`Tool size: ${TEST_CONFIG.toolDiameter}mm`);
|
|
25
|
+
console.log(`Rotation step: ${TEST_CONFIG.rotationStep}°`);
|
|
26
|
+
console.log(`XY steps: ${TEST_CONFIG.xStep}`);
|
|
27
|
+
console.log(`Iterations: ${TEST_ITERATIONS}`);
|
|
28
|
+
console.log('');
|
|
29
|
+
|
|
30
|
+
let win;
|
|
31
|
+
let testResults = [];
|
|
32
|
+
let currentIteration = 0;
|
|
33
|
+
|
|
34
|
+
app.whenReady().then(async () => {
|
|
35
|
+
win = new BrowserWindow({
|
|
36
|
+
width: 1200,
|
|
37
|
+
height: 800,
|
|
38
|
+
show: false,
|
|
39
|
+
webPreferences: {
|
|
40
|
+
nodeIntegration: false,
|
|
41
|
+
contextIsolation: true
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Capture console messages
|
|
46
|
+
win.webContents.on('console-message', (event, level, message) => {
|
|
47
|
+
console.log(message);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await win.loadFile(path.join(__dirname, '../../build/index.html'));
|
|
51
|
+
console.log('✓ Page loaded\n');
|
|
52
|
+
|
|
53
|
+
// Run test multiple times
|
|
54
|
+
await runNextIteration();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
async function runNextIteration() {
|
|
58
|
+
if (currentIteration >= TEST_ITERATIONS) {
|
|
59
|
+
// All iterations complete - analyze results
|
|
60
|
+
analyzeResults();
|
|
61
|
+
app.quit();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
currentIteration++;
|
|
66
|
+
console.log('======================================================================');
|
|
67
|
+
console.log(`ITERATION ${currentIteration} of ${TEST_ITERATIONS}`);
|
|
68
|
+
console.log('======================================================================');
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const result = await runTest();
|
|
72
|
+
testResults.push(result);
|
|
73
|
+
|
|
74
|
+
// Wait a bit between iterations
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
76
|
+
|
|
77
|
+
// Run next iteration
|
|
78
|
+
await runNextIteration();
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`✗ Iteration ${currentIteration} failed:`, error);
|
|
81
|
+
testResults.push({
|
|
82
|
+
iteration: currentIteration,
|
|
83
|
+
success: false,
|
|
84
|
+
error: error.message
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Continue with next iteration even on failure
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
89
|
+
await runNextIteration();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function runTest() {
|
|
94
|
+
const result = await win.webContents.executeJavaScript(`
|
|
95
|
+
(async () => {
|
|
96
|
+
const startTime = performance.now();
|
|
97
|
+
|
|
98
|
+
// Load model
|
|
99
|
+
const modelResponse = await fetch('${TEST_CONFIG.model}');
|
|
100
|
+
const modelBuffer = await modelResponse.arrayBuffer();
|
|
101
|
+
|
|
102
|
+
// Parse model triangles
|
|
103
|
+
function parseSTL(arrayBuffer) {
|
|
104
|
+
const view = new DataView(arrayBuffer);
|
|
105
|
+
const numTriangles = view.getUint32(80, true);
|
|
106
|
+
const triangles = new Float32Array(numTriangles * 9);
|
|
107
|
+
|
|
108
|
+
let offset = 84;
|
|
109
|
+
for (let i = 0; i < numTriangles; i++) {
|
|
110
|
+
offset += 12; // Skip normal
|
|
111
|
+
for (let v = 0; v < 3; v++) {
|
|
112
|
+
triangles[i * 9 + v * 3 + 0] = view.getFloat32(offset + 0, true);
|
|
113
|
+
triangles[i * 9 + v * 3 + 1] = view.getFloat32(offset + 4, true);
|
|
114
|
+
triangles[i * 9 + v * 3 + 2] = view.getFloat32(offset + 8, true);
|
|
115
|
+
offset += 12;
|
|
116
|
+
}
|
|
117
|
+
offset += 2; // Skip attribute
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return triangles;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const modelTriangles = parseSTL(modelBuffer);
|
|
124
|
+
|
|
125
|
+
// Load and scale tool
|
|
126
|
+
const toolResponse = await fetch('${TEST_CONFIG.tool}');
|
|
127
|
+
const toolBuffer = await toolResponse.arrayBuffer();
|
|
128
|
+
const toolTriangles = parseSTL(toolBuffer);
|
|
129
|
+
|
|
130
|
+
// Scale tool to target diameter (${TEST_CONFIG.toolDiameter}mm)
|
|
131
|
+
function calculateToolDiameter(triangles) {
|
|
132
|
+
let minX = Infinity, maxX = -Infinity;
|
|
133
|
+
let minY = Infinity, maxY = -Infinity;
|
|
134
|
+
for (let i = 0; i < triangles.length; i += 3) {
|
|
135
|
+
minX = Math.min(minX, triangles[i]);
|
|
136
|
+
maxX = Math.max(maxX, triangles[i]);
|
|
137
|
+
minY = Math.min(minY, triangles[i + 1]);
|
|
138
|
+
maxY = Math.max(maxY, triangles[i + 1]);
|
|
139
|
+
}
|
|
140
|
+
return Math.max(maxX - minX, maxY - minY);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const originalDiameter = calculateToolDiameter(toolTriangles);
|
|
144
|
+
const scale = ${TEST_CONFIG.toolDiameter} / originalDiameter;
|
|
145
|
+
const scaledToolTriangles = new Float32Array(toolTriangles.length);
|
|
146
|
+
for (let i = 0; i < toolTriangles.length; i++) {
|
|
147
|
+
scaledToolTriangles[i] = toolTriangles[i] * scale;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Import worker
|
|
151
|
+
const { RasterPath } = await import('./raster-path.js');
|
|
152
|
+
const rasterPath = new RasterPath({
|
|
153
|
+
mode: 'radial',
|
|
154
|
+
resolution: ${TEST_CONFIG.resolution},
|
|
155
|
+
rotationStep: ${TEST_CONFIG.rotationStep}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Initialize
|
|
159
|
+
await rasterPath.init();
|
|
160
|
+
|
|
161
|
+
const toolStart = performance.now();
|
|
162
|
+
await rasterPath.loadTool({ triangles: scaledToolTriangles });
|
|
163
|
+
const toolTime = performance.now() - toolStart;
|
|
164
|
+
|
|
165
|
+
const terrainStart = performance.now();
|
|
166
|
+
await rasterPath.loadTerrain({
|
|
167
|
+
triangles: modelTriangles,
|
|
168
|
+
zFloor: 0
|
|
169
|
+
});
|
|
170
|
+
const terrainTime = performance.now() - terrainStart;
|
|
171
|
+
|
|
172
|
+
const toolpathStart = performance.now();
|
|
173
|
+
const toolpathResult = await rasterPath.generateToolpaths({
|
|
174
|
+
xStep: ${TEST_CONFIG.xStep},
|
|
175
|
+
yStep: ${TEST_CONFIG.yStep},
|
|
176
|
+
zFloor: 0,
|
|
177
|
+
radiusOffset: 20
|
|
178
|
+
});
|
|
179
|
+
const toolpathTime = performance.now() - toolpathStart;
|
|
180
|
+
|
|
181
|
+
const totalTime = performance.now() - startTime;
|
|
182
|
+
|
|
183
|
+
// Count strips and points
|
|
184
|
+
let numStrips = 0;
|
|
185
|
+
let totalPoints = 0;
|
|
186
|
+
let minPoints = Infinity;
|
|
187
|
+
let maxPoints = -Infinity;
|
|
188
|
+
let emptyStrips = 0;
|
|
189
|
+
|
|
190
|
+
if (toolpathResult) {
|
|
191
|
+
numStrips = toolpathResult.numStrips || 0;
|
|
192
|
+
totalPoints = toolpathResult.totalPoints || 0;
|
|
193
|
+
|
|
194
|
+
// If we have the raw strips data, analyze it
|
|
195
|
+
if (toolpathResult.strips) {
|
|
196
|
+
for (const strip of toolpathResult.strips) {
|
|
197
|
+
const pointCount = strip.length / 3;
|
|
198
|
+
if (pointCount === 0) {
|
|
199
|
+
emptyStrips++;
|
|
200
|
+
}
|
|
201
|
+
minPoints = Math.min(minPoints, pointCount);
|
|
202
|
+
maxPoints = Math.max(maxPoints, pointCount);
|
|
203
|
+
}
|
|
204
|
+
} else if (toolpathResult.result && toolpathResult.result.strips) {
|
|
205
|
+
// Alternative format
|
|
206
|
+
numStrips = toolpathResult.result.strips.length;
|
|
207
|
+
for (const strip of toolpathResult.result.strips) {
|
|
208
|
+
const pointCount = strip.length / 3;
|
|
209
|
+
totalPoints += pointCount;
|
|
210
|
+
if (pointCount === 0) {
|
|
211
|
+
emptyStrips++;
|
|
212
|
+
}
|
|
213
|
+
minPoints = Math.min(minPoints, pointCount);
|
|
214
|
+
maxPoints = Math.max(maxPoints, pointCount);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
success: true,
|
|
221
|
+
triangleCount: modelTriangles.length / 9,
|
|
222
|
+
timing: {
|
|
223
|
+
terrain: terrainTime,
|
|
224
|
+
tool: toolTime,
|
|
225
|
+
toolpath: toolpathTime,
|
|
226
|
+
total: totalTime
|
|
227
|
+
},
|
|
228
|
+
result: {
|
|
229
|
+
numStrips,
|
|
230
|
+
totalPoints,
|
|
231
|
+
minPoints: minPoints === Infinity ? 0 : minPoints,
|
|
232
|
+
maxPoints: maxPoints === -Infinity ? 0 : maxPoints,
|
|
233
|
+
emptyStrips,
|
|
234
|
+
avgPointsPerStrip: numStrips > 0 ? (totalPoints / numStrips).toFixed(1) : 0
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
})()
|
|
238
|
+
`);
|
|
239
|
+
|
|
240
|
+
console.log(`\nResults (Iteration ${currentIteration}):`);
|
|
241
|
+
console.log(` Triangle count: ${result.triangleCount.toLocaleString()}`);
|
|
242
|
+
console.log(` Terrain time: ${result.timing.terrain.toFixed(1)}ms`);
|
|
243
|
+
console.log(` Tool time: ${result.timing.tool.toFixed(1)}ms`);
|
|
244
|
+
console.log(` Toolpath time: ${result.timing.toolpath.toFixed(1)}ms`);
|
|
245
|
+
console.log(` Total time: ${result.timing.total.toFixed(1)}ms`);
|
|
246
|
+
console.log('');
|
|
247
|
+
console.log('Toolpath Output:');
|
|
248
|
+
console.log(` Strips: ${result.result.numStrips}`);
|
|
249
|
+
console.log(` Total points: ${result.result.totalPoints.toLocaleString()}`);
|
|
250
|
+
console.log(` Empty strips: ${result.result.emptyStrips}`);
|
|
251
|
+
console.log(` Min points/strip: ${result.result.minPoints}`);
|
|
252
|
+
console.log(` Max points/strip: ${result.result.maxPoints}`);
|
|
253
|
+
console.log(` Avg points/strip: ${result.result.avgPointsPerStrip}`);
|
|
254
|
+
console.log('');
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
iteration: currentIteration,
|
|
258
|
+
...result
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function analyzeResults() {
|
|
263
|
+
console.log('');
|
|
264
|
+
console.log('======================================================================');
|
|
265
|
+
console.log('CONSISTENCY ANALYSIS');
|
|
266
|
+
console.log('======================================================================');
|
|
267
|
+
|
|
268
|
+
const successful = testResults.filter(r => r.success);
|
|
269
|
+
|
|
270
|
+
if (successful.length === 0) {
|
|
271
|
+
console.log('✗ All iterations failed!');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (successful.length < TEST_ITERATIONS) {
|
|
276
|
+
console.log(`⚠️ Only ${successful.length} of ${TEST_ITERATIONS} iterations succeeded`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Check consistency of results
|
|
280
|
+
const totalPointsCounts = successful.map(r => r.result.totalPoints);
|
|
281
|
+
const uniqueTotalPoints = [...new Set(totalPointsCounts)];
|
|
282
|
+
|
|
283
|
+
const numStripsCounts = successful.map(r => r.result.numStrips);
|
|
284
|
+
const uniqueNumStrips = [...new Set(numStripsCounts)];
|
|
285
|
+
|
|
286
|
+
const emptyStripsCounts = successful.map(r => r.result.emptyStrips);
|
|
287
|
+
const uniqueEmptyStrips = [...new Set(emptyStripsCounts)];
|
|
288
|
+
|
|
289
|
+
console.log('\nTotal Points Consistency:');
|
|
290
|
+
if (uniqueTotalPoints.length === 1) {
|
|
291
|
+
console.log(` ✓ All iterations produced ${totalPointsCounts[0].toLocaleString()} points`);
|
|
292
|
+
} else {
|
|
293
|
+
console.log(` ✗ INCONSISTENT! Got ${uniqueTotalPoints.length} different values:`);
|
|
294
|
+
uniqueTotalPoints.forEach(val => {
|
|
295
|
+
const count = totalPointsCounts.filter(v => v === val).length;
|
|
296
|
+
console.log(` ${val.toLocaleString()} points: ${count} times`);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log('\nEmpty Strips Consistency:');
|
|
301
|
+
if (uniqueEmptyStrips.length === 1) {
|
|
302
|
+
console.log(` ${uniqueEmptyStrips[0] === 0 ? '✓' : '⚠️'} All iterations had ${emptyStripsCounts[0]} empty strips`);
|
|
303
|
+
} else {
|
|
304
|
+
console.log(` ✗ INCONSISTENT! Got ${uniqueEmptyStrips.length} different values:`);
|
|
305
|
+
uniqueEmptyStrips.forEach(val => {
|
|
306
|
+
const count = emptyStripsCounts.filter(v => v === val).length;
|
|
307
|
+
console.log(` ${val} empty strips: ${count} times`);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Timing statistics
|
|
312
|
+
const toolpathTimes = successful.map(r => r.timing.toolpath);
|
|
313
|
+
const avgTime = toolpathTimes.reduce((a, b) => a + b, 0) / toolpathTimes.length;
|
|
314
|
+
const minTime = Math.min(...toolpathTimes);
|
|
315
|
+
const maxTime = Math.max(...toolpathTimes);
|
|
316
|
+
const stdDev = Math.sqrt(
|
|
317
|
+
toolpathTimes.reduce((sum, t) => sum + Math.pow(t - avgTime, 2), 0) / toolpathTimes.length
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
console.log('\nToolpath Timing Statistics:');
|
|
321
|
+
console.log(` Average: ${avgTime.toFixed(1)}ms`);
|
|
322
|
+
console.log(` Min: ${minTime.toFixed(1)}ms`);
|
|
323
|
+
console.log(` Max: ${maxTime.toFixed(1)}ms`);
|
|
324
|
+
console.log(` StdDev: ${stdDev.toFixed(1)}ms (${(stdDev / avgTime * 100).toFixed(1)}%)`);
|
|
325
|
+
|
|
326
|
+
// Overall verdict
|
|
327
|
+
console.log('');
|
|
328
|
+
if (uniqueTotalPoints.length === 1 && uniqueEmptyStrips[0] === 0) {
|
|
329
|
+
console.log('✅ RESULTS ARE CONSISTENT - No non-deterministic behavior detected');
|
|
330
|
+
} else {
|
|
331
|
+
console.log('🛑 RESULTS ARE INCONSISTENT - Non-deterministic behavior detected!');
|
|
332
|
+
console.log(' This suggests workgroups may be getting killed or timing out.');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// lathe-cylinder-2-test.cjs
|
|
2
|
+
// Test for lathe-cylinder-2.stl (201 buckets) with bucket batching
|
|
3
|
+
|
|
4
|
+
const { app, BrowserWindow } = require('electron');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
let mainWindow;
|
|
8
|
+
|
|
9
|
+
function createWindow() {
|
|
10
|
+
mainWindow = new BrowserWindow({
|
|
11
|
+
width: 1200,
|
|
12
|
+
height: 800,
|
|
13
|
+
show: false,
|
|
14
|
+
webPreferences: {
|
|
15
|
+
nodeIntegration: false,
|
|
16
|
+
contextIsolation: true,
|
|
17
|
+
enableBlinkFeatures: 'WebGPU',
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const htmlPath = path.join(__dirname, '../../build/index.html');
|
|
22
|
+
mainWindow.loadFile(htmlPath);
|
|
23
|
+
|
|
24
|
+
mainWindow.webContents.on('did-finish-load', async () => {
|
|
25
|
+
console.log('✓ Page loaded');
|
|
26
|
+
|
|
27
|
+
const testScript = `
|
|
28
|
+
(async function() {
|
|
29
|
+
console.log('=== Lathe Cylinder 2 Test (201 buckets) ===');
|
|
30
|
+
|
|
31
|
+
if (!navigator.gpu) {
|
|
32
|
+
return { error: 'WebGPU not available' };
|
|
33
|
+
}
|
|
34
|
+
console.log('✓ WebGPU available');
|
|
35
|
+
|
|
36
|
+
// Import RasterPath
|
|
37
|
+
const { RasterPath } = await import('./raster-path.js');
|
|
38
|
+
|
|
39
|
+
// Load STL files
|
|
40
|
+
console.log('\\nLoading lathe-cylinder-2.stl...');
|
|
41
|
+
const terrainResponse = await fetch('../benchmark/fixtures/lathe-cylinder-2.stl');
|
|
42
|
+
const terrainBuffer = await terrainResponse.arrayBuffer();
|
|
43
|
+
|
|
44
|
+
const toolResponse = await fetch('../benchmark/fixtures/tool.stl');
|
|
45
|
+
const toolBuffer = await toolResponse.arrayBuffer();
|
|
46
|
+
|
|
47
|
+
console.log('✓ Loaded lathe-cylinder-2.stl:', terrainBuffer.byteLength, 'bytes');
|
|
48
|
+
console.log('✓ Loaded tool.stl:', toolBuffer.byteLength, 'bytes');
|
|
49
|
+
|
|
50
|
+
// Parse STL files
|
|
51
|
+
function parseBinarySTL(buffer) {
|
|
52
|
+
const dataView = new DataView(buffer);
|
|
53
|
+
const numTriangles = dataView.getUint32(80, true);
|
|
54
|
+
const positions = new Float32Array(numTriangles * 9);
|
|
55
|
+
let offset = 84;
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < numTriangles; i++) {
|
|
58
|
+
offset += 12; // Skip normal
|
|
59
|
+
for (let j = 0; j < 9; j++) {
|
|
60
|
+
positions[i * 9 + j] = dataView.getFloat32(offset, true);
|
|
61
|
+
offset += 4;
|
|
62
|
+
}
|
|
63
|
+
offset += 2; // Skip attribute byte count
|
|
64
|
+
}
|
|
65
|
+
return positions;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const terrainTriangles = parseBinarySTL(terrainBuffer);
|
|
69
|
+
const toolTriangles = parseBinarySTL(toolBuffer);
|
|
70
|
+
console.log('✓ Parsed terrain:', terrainTriangles.length / 9, 'triangles');
|
|
71
|
+
console.log('✓ Parsed tool:', toolTriangles.length / 9, 'triangles');
|
|
72
|
+
|
|
73
|
+
// Test parameters
|
|
74
|
+
const resolution = 0.5;
|
|
75
|
+
const rotationStep = 5.0;
|
|
76
|
+
const xStep = 1;
|
|
77
|
+
const yStep = 1;
|
|
78
|
+
const zFloor = -50;
|
|
79
|
+
|
|
80
|
+
console.log('\\nTest parameters:');
|
|
81
|
+
console.log(' Resolution:', resolution, 'mm');
|
|
82
|
+
console.log(' Rotation step:', rotationStep, '°');
|
|
83
|
+
console.log(' XY step:', xStep + 'x' + yStep, 'points');
|
|
84
|
+
console.log(' Z floor:', zFloor, 'mm');
|
|
85
|
+
|
|
86
|
+
// Create RasterPath instance for radial mode
|
|
87
|
+
console.log('\\nInitializing RasterPath (radial mode with diagnostic=true)...');
|
|
88
|
+
const raster = new RasterPath({
|
|
89
|
+
mode: 'radial',
|
|
90
|
+
resolution: resolution,
|
|
91
|
+
rotationStep: rotationStep,
|
|
92
|
+
diagnostic: true
|
|
93
|
+
});
|
|
94
|
+
await raster.init();
|
|
95
|
+
console.log('✓ RasterPath initialized');
|
|
96
|
+
|
|
97
|
+
// Load tool
|
|
98
|
+
console.log('\\nLoading tool...');
|
|
99
|
+
const t0 = performance.now();
|
|
100
|
+
const toolData = await raster.loadTool({ triangles: toolTriangles });
|
|
101
|
+
const toolTime = performance.now() - t0;
|
|
102
|
+
console.log('✓ Tool:', toolData.pointCount, 'points in', toolTime.toFixed(1), 'ms');
|
|
103
|
+
|
|
104
|
+
// Load terrain
|
|
105
|
+
console.log('\\nLoading terrain...');
|
|
106
|
+
const t1 = performance.now();
|
|
107
|
+
await raster.loadTerrain({ triangles: terrainTriangles });
|
|
108
|
+
const terrainTime = performance.now() - t1;
|
|
109
|
+
console.log('✓ Terrain loaded in', terrainTime.toFixed(1), 'ms');
|
|
110
|
+
|
|
111
|
+
// Generate toolpaths (this should trigger bucket batching)
|
|
112
|
+
console.log('\\n*** Generating toolpaths (watch for bucket batching messages) ***');
|
|
113
|
+
const t2 = performance.now();
|
|
114
|
+
const toolpaths = await raster.generateToolpaths({
|
|
115
|
+
xStep,
|
|
116
|
+
yStep,
|
|
117
|
+
zFloor
|
|
118
|
+
});
|
|
119
|
+
const toolpathTime = performance.now() - t2;
|
|
120
|
+
|
|
121
|
+
console.log('\\n✓ Generated', toolpaths.length, 'toolpaths in', (toolpathTime / 1000).toFixed(2), 's');
|
|
122
|
+
if (toolpaths.length > 0) {
|
|
123
|
+
console.log(' First toolpath:', toolpaths[0].numScanlines, 'scanlines ×', toolpaths[0].pointsPerLine, 'points');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log('\\n✅ TEST PASSED - No GPU timeout!');
|
|
127
|
+
|
|
128
|
+
return { success: true };
|
|
129
|
+
})();
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const result = await mainWindow.webContents.executeJavaScript(testScript);
|
|
134
|
+
|
|
135
|
+
if (result && result.error) {
|
|
136
|
+
console.error('❌ Test failed:', result.error);
|
|
137
|
+
app.exit(1);
|
|
138
|
+
} else {
|
|
139
|
+
console.log('\n✅ Test completed successfully');
|
|
140
|
+
app.exit(0);
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('❌ Test execution failed:', error);
|
|
144
|
+
app.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
mainWindow.webContents.on('console-message', (event, level, message) => {
|
|
149
|
+
console.log(message);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
app.whenReady().then(createWindow);
|
|
154
|
+
|
|
155
|
+
app.on('window-all-closed', () => {
|
|
156
|
+
app.quit();
|
|
157
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
const { app, BrowserWindow } = require('electron');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
let testPassed = false;
|
|
6
|
+
|
|
7
|
+
app.on('ready', async () => {
|
|
8
|
+
const win = new BrowserWindow({
|
|
9
|
+
width: 800,
|
|
10
|
+
height: 600,
|
|
11
|
+
show: false,
|
|
12
|
+
webPreferences: {
|
|
13
|
+
nodeIntegration: false,
|
|
14
|
+
contextIsolation: true
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Load test page
|
|
19
|
+
await win.loadFile(path.join(__dirname, '../../build/index.html'));
|
|
20
|
+
|
|
21
|
+
// Inject test script
|
|
22
|
+
await win.webContents.executeJavaScript(`
|
|
23
|
+
(async () => {
|
|
24
|
+
console.log('=== Lathe Cylinder Radial Test (0.025mm resolution) ===');
|
|
25
|
+
|
|
26
|
+
// Load STL
|
|
27
|
+
const stlPath = '${path.join(__dirname, '../../benchmark/fixtures/lathe-cylinder.stl')}';
|
|
28
|
+
const stlBuffer = await (await fetch('file://' + stlPath)).arrayBuffer();
|
|
29
|
+
|
|
30
|
+
console.log('STL Size:', (stlBuffer.byteLength / 1024 / 1024).toFixed(2), 'MB');
|
|
31
|
+
|
|
32
|
+
// Parse STL
|
|
33
|
+
function parseSTL(arrayBuffer) {
|
|
34
|
+
const view = new DataView(arrayBuffer);
|
|
35
|
+
const triangleCount = view.getUint32(80, true);
|
|
36
|
+
console.log('Triangle count:', triangleCount);
|
|
37
|
+
|
|
38
|
+
const triangles = new Float32Array(triangleCount * 9);
|
|
39
|
+
let offset = 84;
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < triangleCount; i++) {
|
|
42
|
+
offset += 12; // Skip normal
|
|
43
|
+
for (let j = 0; j < 9; j++) {
|
|
44
|
+
triangles[i * 9 + j] = view.getFloat32(offset, true);
|
|
45
|
+
offset += 4;
|
|
46
|
+
}
|
|
47
|
+
offset += 2; // Skip attribute byte count
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return triangles;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const triangles = parseSTL(stlBuffer);
|
|
54
|
+
|
|
55
|
+
// Calculate bounds
|
|
56
|
+
let minX = Infinity, maxX = -Infinity;
|
|
57
|
+
let minY = Infinity, maxY = -Infinity;
|
|
58
|
+
let minZ = Infinity, maxZ = -Infinity;
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < triangles.length; i += 3) {
|
|
61
|
+
const x = triangles[i];
|
|
62
|
+
const y = triangles[i + 1];
|
|
63
|
+
const z = triangles[i + 2];
|
|
64
|
+
minX = Math.min(minX, x); maxX = Math.max(maxX, x);
|
|
65
|
+
minY = Math.min(minY, y); maxY = Math.max(maxY, y);
|
|
66
|
+
minZ = Math.min(minZ, z); maxZ = Math.max(maxZ, z);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log('Bounds:');
|
|
70
|
+
console.log(' X:', minX.toFixed(2), 'to', maxX.toFixed(2), '(size:', (maxX - minX).toFixed(2), 'mm)');
|
|
71
|
+
console.log(' Y:', minY.toFixed(2), 'to', maxY.toFixed(2), '(size:', (maxY - minY).toFixed(2), 'mm)');
|
|
72
|
+
console.log(' Z:', minZ.toFixed(2), 'to', maxZ.toFixed(2), '(size:', (maxZ - minZ).toFixed(2), 'mm)');
|
|
73
|
+
|
|
74
|
+
const resolution = 0.025;
|
|
75
|
+
const rotationStep = 1.0;
|
|
76
|
+
|
|
77
|
+
console.log('\\nTest parameters:');
|
|
78
|
+
console.log(' Resolution:', resolution, 'mm');
|
|
79
|
+
console.log(' Rotation step:', rotationStep, 'degrees');
|
|
80
|
+
|
|
81
|
+
// Calculate expected dimensions
|
|
82
|
+
const xSize = maxX - minX;
|
|
83
|
+
const ySize = maxY - minY;
|
|
84
|
+
const zSize = maxZ - minZ;
|
|
85
|
+
const maxRadius = Math.sqrt(ySize * ySize + zSize * zSize) / 2;
|
|
86
|
+
|
|
87
|
+
const gridXSize = Math.ceil(xSize / resolution);
|
|
88
|
+
const gridYHeight = Math.ceil(ySize / resolution);
|
|
89
|
+
const numAngles = Math.ceil(360 / rotationStep);
|
|
90
|
+
|
|
91
|
+
console.log('\\nExpected grid dimensions:');
|
|
92
|
+
console.log(' X grid size:', gridXSize, 'cells');
|
|
93
|
+
console.log(' Y grid height:', gridYHeight, 'cells');
|
|
94
|
+
console.log(' Number of angles:', numAngles);
|
|
95
|
+
console.log(' Max radius:', maxRadius.toFixed(2), 'mm');
|
|
96
|
+
|
|
97
|
+
// Calculate memory requirements
|
|
98
|
+
const bytesPerCell = 4; // f32
|
|
99
|
+
const totalCells = gridXSize * gridYHeight * numAngles;
|
|
100
|
+
const totalMemoryMB = (totalCells * bytesPerCell / 1024 / 1024).toFixed(2);
|
|
101
|
+
|
|
102
|
+
console.log('\\nMemory requirements:');
|
|
103
|
+
console.log(' Total cells:', totalCells.toLocaleString());
|
|
104
|
+
console.log(' Total memory:', totalMemoryMB, 'MB');
|
|
105
|
+
|
|
106
|
+
// Test with RasterPath
|
|
107
|
+
console.log('\\n=== Starting RasterPath test ===');
|
|
108
|
+
|
|
109
|
+
const { RasterPath } = await import('./raster-path.js');
|
|
110
|
+
const raster = new RasterPath({
|
|
111
|
+
mode: 'radial',
|
|
112
|
+
resolution: resolution,
|
|
113
|
+
rotationStep: rotationStep
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await raster.init();
|
|
117
|
+
console.log('RasterPath initialized');
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Load terrain
|
|
121
|
+
console.log('Loading terrain...');
|
|
122
|
+
const startLoad = Date.now();
|
|
123
|
+
await raster.loadTerrain({ triangles, zFloor: 0 });
|
|
124
|
+
const loadTime = Date.now() - startLoad;
|
|
125
|
+
console.log('Terrain loaded in', loadTime, 'ms');
|
|
126
|
+
|
|
127
|
+
// Generate toolpaths with a dummy tool
|
|
128
|
+
console.log('Generating toolpaths...');
|
|
129
|
+
const startToolpath = Date.now();
|
|
130
|
+
|
|
131
|
+
// Create a simple cylindrical tool (10mm diameter, 10mm height)
|
|
132
|
+
const toolRadius = 5;
|
|
133
|
+
const toolHeight = 10;
|
|
134
|
+
const toolSegments = 16;
|
|
135
|
+
const toolTriangles = new Float32Array(toolSegments * 2 * 9);
|
|
136
|
+
|
|
137
|
+
for (let i = 0; i < toolSegments; i++) {
|
|
138
|
+
const angle1 = (i / toolSegments) * Math.PI * 2;
|
|
139
|
+
const angle2 = ((i + 1) / toolSegments) * Math.PI * 2;
|
|
140
|
+
|
|
141
|
+
const x1 = Math.cos(angle1) * toolRadius;
|
|
142
|
+
const y1 = Math.sin(angle1) * toolRadius;
|
|
143
|
+
const x2 = Math.cos(angle2) * toolRadius;
|
|
144
|
+
const y2 = Math.sin(angle2) * toolRadius;
|
|
145
|
+
|
|
146
|
+
// Bottom triangle
|
|
147
|
+
const idx = i * 18;
|
|
148
|
+
toolTriangles[idx] = 0; toolTriangles[idx + 1] = 0; toolTriangles[idx + 2] = 0;
|
|
149
|
+
toolTriangles[idx + 3] = x1; toolTriangles[idx + 4] = y1; toolTriangles[idx + 5] = 0;
|
|
150
|
+
toolTriangles[idx + 6] = x2; toolTriangles[idx + 7] = y2; toolTriangles[idx + 8] = 0;
|
|
151
|
+
|
|
152
|
+
// Side rectangle (as 2 triangles) - just one for simplicity
|
|
153
|
+
toolTriangles[idx + 9] = x1; toolTriangles[idx + 10] = y1; toolTriangles[idx + 11] = 0;
|
|
154
|
+
toolTriangles[idx + 12] = x1; toolTriangles[idx + 13] = y1; toolTriangles[idx + 14] = toolHeight;
|
|
155
|
+
toolTriangles[idx + 15] = x2; toolTriangles[idx + 16] = y2; toolTriangles[idx + 17] = 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await raster.loadTool({ triangles: toolTriangles });
|
|
159
|
+
|
|
160
|
+
const toolpathData = await raster.generateToolpaths({
|
|
161
|
+
xStep: 10,
|
|
162
|
+
yStep: 10,
|
|
163
|
+
zFloor: 0,
|
|
164
|
+
radiusOffset: 20
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const toolpathTime = Date.now() - startToolpath;
|
|
168
|
+
|
|
169
|
+
console.log('\\n=== Test Results ===');
|
|
170
|
+
console.log('Toolpath generation time:', toolpathTime, 'ms');
|
|
171
|
+
console.log('Number of strips:', toolpathData.numStrips);
|
|
172
|
+
console.log('Total points:', toolpathData.totalPoints);
|
|
173
|
+
console.log('Average points per strip:', (toolpathData.totalPoints / toolpathData.numStrips).toFixed(0));
|
|
174
|
+
console.log('\\nTEST PASSED');
|
|
175
|
+
|
|
176
|
+
return { success: true };
|
|
177
|
+
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('\\n=== TEST FAILED ===');
|
|
180
|
+
console.error('Error:', error.message);
|
|
181
|
+
console.error('Stack:', error.stack);
|
|
182
|
+
return { success: false, error: error.message };
|
|
183
|
+
} finally {
|
|
184
|
+
raster.terminate();
|
|
185
|
+
}
|
|
186
|
+
})();
|
|
187
|
+
`).then((result) => {
|
|
188
|
+
testPassed = result.success === true;
|
|
189
|
+
app.quit();
|
|
190
|
+
}).catch((error) => {
|
|
191
|
+
console.error('Test execution error:', error);
|
|
192
|
+
app.quit();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
app.on('window-all-closed', () => {
|
|
197
|
+
process.exit(testPassed ? 0 : 1);
|
|
198
|
+
});
|