@gridspace/raster-path 1.0.3 → 1.0.5

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,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
+ });
@@ -0,0 +1,152 @@
1
+ // radial-thread-limit-test.cjs
2
+ // Test that radial rasterization respects thread limits and produces correct output
3
+
4
+ const { app, BrowserWindow } = require('electron');
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+
8
+ let mainWindow;
9
+
10
+ function createWindow() {
11
+ mainWindow = new BrowserWindow({
12
+ width: 1200,
13
+ height: 800,
14
+ show: false,
15
+ webPreferences: {
16
+ nodeIntegration: false,
17
+ contextIsolation: true,
18
+ enableBlinkFeatures: 'WebGPU',
19
+ }
20
+ });
21
+
22
+ const htmlPath = path.join(__dirname, '../../build/index.html');
23
+ mainWindow.loadFile(htmlPath);
24
+
25
+ mainWindow.webContents.on('did-finish-load', async () => {
26
+ console.log('✓ Page loaded');
27
+
28
+ const testScript = `
29
+ (async function() {
30
+ console.log('=== Radial Thread Limit Test ===\\n');
31
+
32
+ if (!navigator.gpu) {
33
+ return { error: 'WebGPU not available' };
34
+ }
35
+
36
+ const { RasterPath } = await import('./raster-path.js');
37
+
38
+ // Read test STL
39
+ const stlPath = '${path.join(__dirname, '../../benchmark/fixtures/lathe-cylinder.stl')}';
40
+ const response = await fetch('file://' + stlPath);
41
+ if (!response.ok) {
42
+ return { error: 'Failed to load test STL: ' + stlPath };
43
+ }
44
+ const arrayBuffer = await response.arrayBuffer();
45
+
46
+ // Test with different thread limits
47
+ const threadLimits = [
48
+ { limit: 256, desc: 'Default limit (256 threads)' },
49
+ { limit: 128, desc: 'Reduced limit (128 threads)' },
50
+ { limit: 64, desc: 'Very low limit (64 threads)' },
51
+ ];
52
+
53
+ const results = [];
54
+
55
+ for (const config of threadLimits) {
56
+ console.log(\`Testing: \${config.desc}\`);
57
+
58
+ const raster = new RasterPath({
59
+ mode: 'radial',
60
+ resolution: 0.5,
61
+ rotationStep: 1.0,
62
+ toolWidth: 5.0,
63
+ maxConcurrentThreads: config.limit
64
+ });
65
+
66
+ await raster.init();
67
+
68
+ const startTime = performance.now();
69
+ const result = await raster.processSTL(arrayBuffer);
70
+ const elapsed = performance.now() - startTime;
71
+
72
+ // Calculate checksum
73
+ let checksum = 0;
74
+ for (const strip of result.strips) {
75
+ for (let i = 0; i < strip.pathData.length; i++) {
76
+ checksum = (checksum * 31 + strip.pathData[i]) | 0;
77
+ }
78
+ }
79
+
80
+ console.log(\` Time: \${elapsed.toFixed(1)}ms\`);
81
+ console.log(\` Strips: \${result.strips.length}\`);
82
+ console.log(\` Checksum: \${checksum}\`);
83
+ console.log('');
84
+
85
+ results.push({
86
+ limit: config.limit,
87
+ desc: config.desc,
88
+ time: elapsed,
89
+ strips: result.strips.length,
90
+ checksum,
91
+ });
92
+ }
93
+
94
+ // Verify all checksums match
95
+ const referenceChecksum = results[0].checksum;
96
+ const allMatch = results.every(r => r.checksum === referenceChecksum);
97
+
98
+ console.log('=== Results ===');
99
+ console.log(\`Reference checksum: \${referenceChecksum}\`);
100
+ console.log(\`All checksums match: \${allMatch ? '✓ YES' : '❌ NO'}\`);
101
+
102
+ if (!allMatch) {
103
+ console.log('\\nChecksum mismatches:');
104
+ for (const r of results) {
105
+ if (r.checksum !== referenceChecksum) {
106
+ console.log(\` \${r.desc}: \${r.checksum} (expected \${referenceChecksum})\`);
107
+ }
108
+ }
109
+ }
110
+
111
+ console.log('\\nTiming comparison:');
112
+ const baselineTime = results[0].time;
113
+ for (const r of results) {
114
+ const ratio = (r.time / baselineTime).toFixed(2);
115
+ console.log(\` \${r.desc}: \${r.time.toFixed(1)}ms (\${ratio}x)\`);
116
+ }
117
+
118
+ return { success: allMatch, results };
119
+ })();
120
+ `;
121
+
122
+ try {
123
+ const result = await mainWindow.webContents.executeJavaScript(testScript);
124
+
125
+ if (result.error) {
126
+ console.error('❌ Test failed:', result.error);
127
+ app.exit(1);
128
+ } else if (!result.success) {
129
+ console.error('❌ Thread limit test failed - checksums do not match');
130
+ app.exit(1);
131
+ } else {
132
+ console.log('\\n✅ Thread limit test passed - all configurations produce identical output');
133
+ app.exit(0);
134
+ }
135
+ } catch (error) {
136
+ console.error('❌ Test error:', error);
137
+ app.exit(1);
138
+ }
139
+ });
140
+
141
+ mainWindow.webContents.on('console-message', (event, level, message) => {
142
+ console.log(message);
143
+ });
144
+ }
145
+
146
+ app.whenReady().then(createWindow);
147
+
148
+ app.on('window-all-closed', () => {
149
+ if (process.platform !== 'darwin') {
150
+ app.quit();
151
+ }
152
+ });