@gridspace/raster-path 1.0.2 → 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.
@@ -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
+ });