@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,253 @@
1
+ // planar-test.cjs
2
+ // Regression test for planar mode using new RasterPath API
3
+ // Tests: loadTool() + loadTerrain() + generateToolpaths()
4
+
5
+ const { app, BrowserWindow } = require('electron');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+
9
+ const OUTPUT_DIR = path.join(__dirname, '../../test-output');
10
+ const BASELINE_FILE = path.join(OUTPUT_DIR, 'planar-baseline.json');
11
+ const CURRENT_FILE = path.join(OUTPUT_DIR, 'planar-current.json');
12
+
13
+ if (!fs.existsSync(OUTPUT_DIR)) {
14
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
15
+ }
16
+
17
+ let mainWindow;
18
+
19
+ function createWindow() {
20
+ mainWindow = new BrowserWindow({
21
+ width: 1200,
22
+ height: 800,
23
+ show: false,
24
+ webPreferences: {
25
+ nodeIntegration: false,
26
+ contextIsolation: true,
27
+ enableBlinkFeatures: 'WebGPU',
28
+ }
29
+ });
30
+
31
+ const htmlPath = path.join(__dirname, '../../build/index.html');
32
+ mainWindow.loadFile(htmlPath);
33
+
34
+ mainWindow.webContents.on('did-finish-load', async () => {
35
+ console.log('✓ Page loaded');
36
+
37
+ const testScript = `
38
+ (async function() {
39
+ console.log('=== Planar Mode Regression Test ===');
40
+
41
+ if (!navigator.gpu) {
42
+ return { error: 'WebGPU not available' };
43
+ }
44
+ console.log('✓ WebGPU available');
45
+
46
+ // Import RasterPath
47
+ const { RasterPath } = await import('./raster-path.js');
48
+
49
+ // Load STL files
50
+ console.log('\\nLoading STL files...');
51
+ const terrainResponse = await fetch('../benchmark/fixtures/terrain.stl');
52
+ const terrainBuffer = await terrainResponse.arrayBuffer();
53
+
54
+ const toolResponse = await fetch('../benchmark/fixtures/tool.stl');
55
+ const toolBuffer = await toolResponse.arrayBuffer();
56
+
57
+ console.log('✓ Loaded terrain.stl:', terrainBuffer.byteLength, 'bytes');
58
+ console.log('✓ Loaded tool.stl:', toolBuffer.byteLength, 'bytes');
59
+
60
+ // Parse STL files
61
+ function parseBinarySTL(buffer) {
62
+ const dataView = new DataView(buffer);
63
+ const numTriangles = dataView.getUint32(80, true);
64
+ const positions = new Float32Array(numTriangles * 9);
65
+ let offset = 84;
66
+
67
+ for (let i = 0; i < numTriangles; i++) {
68
+ offset += 12; // Skip normal
69
+ for (let j = 0; j < 9; j++) {
70
+ positions[i * 9 + j] = dataView.getFloat32(offset, true);
71
+ offset += 4;
72
+ }
73
+ offset += 2; // Skip attribute byte count
74
+ }
75
+ return positions;
76
+ }
77
+
78
+ const terrainTriangles = parseBinarySTL(terrainBuffer);
79
+ const toolTriangles = parseBinarySTL(toolBuffer);
80
+ console.log('✓ Parsed terrain:', terrainTriangles.length / 9, 'triangles');
81
+ console.log('✓ Parsed tool:', toolTriangles.length / 9, 'triangles');
82
+
83
+ // Test parameters
84
+ const resolution = 0.05; // 0.05mm high resolution
85
+ const xStep = 1;
86
+ const yStep = 1;
87
+ const zFloor = -100;
88
+
89
+ console.log('\\nTest parameters:');
90
+ console.log(' Resolution:', resolution, 'mm');
91
+ console.log(' XY step:', xStep + 'x' + yStep, 'points');
92
+ console.log(' Z floor:', zFloor, 'mm');
93
+
94
+ // Create RasterPath instance for planar mode
95
+ console.log('\\nInitializing RasterPath (planar mode)...');
96
+ const raster = new RasterPath({
97
+ mode: 'planar',
98
+ resolution: resolution
99
+ });
100
+ await raster.init();
101
+ console.log('✓ RasterPath initialized');
102
+
103
+ // Load tool (NEW API)
104
+ console.log('\\n1. Loading tool...');
105
+ const t0 = performance.now();
106
+ const toolData = await raster.loadTool({
107
+ triangles: toolTriangles
108
+ });
109
+ const toolTime = performance.now() - t0;
110
+ console.log('✓ Tool:', toolData.pointCount, 'points in', toolTime.toFixed(1), 'ms');
111
+
112
+ // Load terrain (NEW API)
113
+ console.log('\\n2. Loading terrain...');
114
+ const t1 = performance.now();
115
+ const terrainData = await raster.loadTerrain({
116
+ triangles: terrainTriangles,
117
+ zFloor: zFloor
118
+ });
119
+ const terrainTime = performance.now() - t1;
120
+ console.log('✓ Terrain:', terrainData.pointCount, 'points in', terrainTime.toFixed(1), 'ms');
121
+
122
+ // Generate toolpaths (NEW API - no need to pass terrainData/toolData)
123
+ console.log('\\n3. Generating toolpaths...');
124
+ const t2 = performance.now();
125
+ const toolpathData = await raster.generateToolpaths({
126
+ xStep: xStep,
127
+ yStep: yStep,
128
+ zFloor: zFloor
129
+ });
130
+ const toolpathTime = performance.now() - t2;
131
+ console.log('✓ Toolpath:', toolpathData.numScanlines + 'x' + toolpathData.pointsPerLine, '=', toolpathData.pathData.length, 'Z-values');
132
+ console.log(' Generation time:', toolpathTime.toFixed(1), 'ms');
133
+
134
+ // Cleanup
135
+ raster.terminate();
136
+
137
+ // Calculate checksum for regression detection
138
+ let checksum = 0;
139
+ for (let i = 0; i < toolpathData.pathData.length; i++) {
140
+ checksum = (checksum + toolpathData.pathData[i] * (i + 1)) | 0;
141
+ }
142
+
143
+ // Sample first 30 Z-values for debugging
144
+ const sampleSize = Math.min(30, toolpathData.pathData.length);
145
+ const sampleValues = [];
146
+ for (let i = 0; i < sampleSize; i++) {
147
+ sampleValues.push(toolpathData.pathData[i].toFixed(2));
148
+ }
149
+
150
+ return {
151
+ success: true,
152
+ output: {
153
+ parameters: {
154
+ mode: 'planar',
155
+ resolution: resolution,
156
+ xStep,
157
+ yStep,
158
+ zFloor,
159
+ terrainTriangles: terrainTriangles.length / 9,
160
+ toolTriangles: toolTriangles.length / 9
161
+ },
162
+ result: {
163
+ terrainPoints: terrainData.pointCount,
164
+ toolPoints: toolData.pointCount,
165
+ toolpathSize: toolpathData.pathData.length,
166
+ numScanlines: toolpathData.numScanlines,
167
+ pointsPerLine: toolpathData.pointsPerLine,
168
+ checksum: checksum,
169
+ sampleValues: sampleValues
170
+ },
171
+ timing: {
172
+ terrain: terrainTime,
173
+ tool: toolTime,
174
+ toolpath: toolpathTime,
175
+ total: terrainTime + toolTime + toolpathTime
176
+ }
177
+ }
178
+ };
179
+ })();
180
+ `;
181
+
182
+ try {
183
+ const result = await mainWindow.webContents.executeJavaScript(testScript);
184
+
185
+ if (result.error) {
186
+ console.error('❌ Test failed:', result.error);
187
+ app.exit(1);
188
+ return;
189
+ }
190
+
191
+ // Save current output
192
+ const currentData = {
193
+ parameters: result.output.parameters,
194
+ result: result.output.result,
195
+ timing: result.output.timing
196
+ };
197
+
198
+ fs.writeFileSync(CURRENT_FILE, JSON.stringify(currentData, null, 2));
199
+ console.log('\n✓ Saved current output to', CURRENT_FILE);
200
+ console.log(` Toolpath size: ${result.output.result.toolpathSize} Z-values`);
201
+ console.log(` Checksum: ${result.output.result.checksum}`);
202
+ console.log(` Total time: ${result.output.timing.total.toFixed(1)}ms`);
203
+
204
+ // Check if baseline exists
205
+ if (!fs.existsSync(BASELINE_FILE)) {
206
+ console.log('\n📝 No baseline found - saving current as baseline');
207
+ fs.writeFileSync(BASELINE_FILE, JSON.stringify(currentData, null, 2));
208
+ console.log('✅ Baseline created');
209
+ app.exit(0);
210
+ return;
211
+ }
212
+
213
+ // Compare with baseline
214
+ const baseline = JSON.parse(fs.readFileSync(BASELINE_FILE, 'utf8'));
215
+
216
+ console.log('\n=== Comparison ===');
217
+ console.log('Baseline checksum:', baseline.result.checksum);
218
+ console.log('Current checksum:', result.output.result.checksum);
219
+
220
+ let passed = true;
221
+
222
+ if (baseline.result.toolpathSize !== result.output.result.toolpathSize) {
223
+ console.error('❌ Toolpath size mismatch!');
224
+ console.error(` Expected: ${baseline.result.toolpathSize}, Got: ${result.output.result.toolpathSize}`);
225
+ passed = false;
226
+ }
227
+
228
+ if (baseline.result.checksum !== result.output.result.checksum) {
229
+ console.error('❌ Checksum mismatch!');
230
+ console.error(` Expected: ${baseline.result.checksum}, Got: ${result.output.result.checksum}`);
231
+ passed = false;
232
+ } else {
233
+ console.log('✓ Checksum matches');
234
+ }
235
+
236
+ if (passed) {
237
+ console.log('\n✅ All checks passed - output matches baseline');
238
+ app.exit(0);
239
+ } else {
240
+ console.log('\n❌ Regression detected - output differs from baseline');
241
+ console.log('To update baseline: cp', CURRENT_FILE, BASELINE_FILE);
242
+ app.exit(1);
243
+ }
244
+
245
+ } catch (error) {
246
+ console.error('Error running test:', error);
247
+ app.exit(1);
248
+ }
249
+ });
250
+ }
251
+
252
+ app.whenReady().then(createWindow);
253
+ app.on('window-all-closed', () => app.quit());
@@ -0,0 +1,230 @@
1
+ // planar-tiling-test.cjs
2
+ // Test for planar tiling with very high resolution (0.01mm)
3
+ // This should trigger automatic tiling to avoid GPU memory allocation failures
4
+
5
+ const { app, BrowserWindow } = require('electron');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+
9
+ const OUTPUT_DIR = path.join(__dirname, '../../test-output');
10
+ const OUTPUT_FILE = path.join(OUTPUT_DIR, 'planar-tiling-test.json');
11
+
12
+ if (!fs.existsSync(OUTPUT_DIR)) {
13
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
14
+ }
15
+
16
+ let mainWindow;
17
+
18
+ function createWindow() {
19
+ mainWindow = new BrowserWindow({
20
+ width: 1200,
21
+ height: 800,
22
+ show: false,
23
+ webPreferences: {
24
+ nodeIntegration: false,
25
+ contextIsolation: true,
26
+ enableBlinkFeatures: 'WebGPU',
27
+ }
28
+ });
29
+
30
+ const htmlPath = path.join(__dirname, '../../build/index.html');
31
+ mainWindow.loadFile(htmlPath);
32
+
33
+ mainWindow.webContents.on('did-finish-load', async () => {
34
+ console.log('✓ Page loaded');
35
+
36
+ const testScript = `
37
+ (async function() {
38
+ console.log('=== Planar Tiling Test (0.01mm resolution) ===');
39
+
40
+ if (!navigator.gpu) {
41
+ return { error: 'WebGPU not available' };
42
+ }
43
+ console.log('✓ WebGPU available');
44
+
45
+ const { RasterPath } = await import('./raster-path.js');
46
+
47
+ console.log('\\nLoading STL files...');
48
+ const terrainResponse = await fetch('../benchmark/fixtures/terrain.stl');
49
+ const terrainBuffer = await terrainResponse.arrayBuffer();
50
+
51
+ const toolResponse = await fetch('../benchmark/fixtures/tool.stl');
52
+ const toolBuffer = await toolResponse.arrayBuffer();
53
+
54
+ console.log('✓ Loaded terrain.stl:', terrainBuffer.byteLength, 'bytes');
55
+ console.log('✓ Loaded tool.stl:', toolBuffer.byteLength, 'bytes');
56
+
57
+ function parseBinarySTL(buffer) {
58
+ const dataView = new DataView(buffer);
59
+ const numTriangles = dataView.getUint32(80, true);
60
+ const positions = new Float32Array(numTriangles * 9);
61
+ let offset = 84;
62
+
63
+ for (let i = 0; i < numTriangles; i++) {
64
+ offset += 12;
65
+ for (let j = 0; j < 9; j++) {
66
+ positions[i * 9 + j] = dataView.getFloat32(offset, true);
67
+ offset += 4;
68
+ }
69
+ offset += 2;
70
+ }
71
+ return positions;
72
+ }
73
+
74
+ const terrainTriangles = parseBinarySTL(terrainBuffer);
75
+ const toolTriangles = parseBinarySTL(toolBuffer);
76
+ console.log('✓ Parsed terrain:', terrainTriangles.length / 9, 'triangles');
77
+ console.log('✓ Parsed tool:', toolTriangles.length / 9, 'triangles');
78
+
79
+ // HIGH RESOLUTION - should trigger tiling
80
+ const resolution = 0.01;
81
+ const xStep = 1;
82
+ const yStep = 1;
83
+ const zFloor = -100;
84
+
85
+ console.log('\\nTest parameters:');
86
+ console.log(' Resolution:', resolution, 'mm (VERY HIGH - should trigger tiling)');
87
+ console.log(' XY step:', xStep + 'x' + yStep, 'points');
88
+ console.log(' Z floor:', zFloor, 'mm');
89
+
90
+ // Calculate expected grid size
91
+ // Terrain is roughly 1000x1000mm, so at 0.01mm = 100,000 x 100,000 grid
92
+ // = 10 billion points * 4 bytes = 40GB (way over GPU limit!)
93
+ const terrainSize = 1000; // approximate
94
+ const expectedGridSize = Math.ceil(terrainSize / resolution);
95
+ const expectedPoints = expectedGridSize * expectedGridSize;
96
+ const expectedMemoryMB = (expectedPoints * 4) / (1024 * 1024);
97
+ console.log('\\nExpected memory usage:');
98
+ console.log(' Grid size:', expectedGridSize + 'x' + expectedGridSize);
99
+ console.log(' Total points:', (expectedPoints / 1e6).toFixed(1) + 'M');
100
+ console.log(' Memory needed:', expectedMemoryMB.toFixed(0) + 'MB');
101
+ console.log(' GPU limit: ~512MB (should trigger tiling)');
102
+
103
+ console.log('\\nInitializing RasterPath (planar mode)...');
104
+ const raster = new RasterPath({
105
+ mode: 'planar',
106
+ resolution: resolution
107
+ });
108
+ await raster.init();
109
+ console.log('✓ RasterPath initialized');
110
+
111
+ console.log('\\n1. Loading tool (NEW API)...');
112
+ const t0 = performance.now();
113
+ const toolData = await raster.loadTool({
114
+ triangles: toolTriangles
115
+ });
116
+ const toolTime = performance.now() - t0;
117
+ console.log('✓ Tool:', toolData.pointCount, 'points in', toolTime.toFixed(1), 'ms');
118
+
119
+ console.log('\\n2. Loading terrain (NEW API - should use tiling)...');
120
+ const t1 = performance.now();
121
+ let terrainData;
122
+ let terrainTime;
123
+ try {
124
+ terrainData = await raster.loadTerrain({
125
+ triangles: terrainTriangles,
126
+ zFloor: zFloor
127
+ });
128
+ terrainTime = performance.now() - t1;
129
+ console.log('✓ Terrain:', terrainData.pointCount, 'points in', terrainTime.toFixed(1), 'ms');
130
+ } catch (error) {
131
+ console.error('❌ Terrain loading FAILED:', error.message);
132
+ return {
133
+ error: 'Terrain loading failed: ' + error.message,
134
+ expectedTiling: true,
135
+ resolution: resolution
136
+ };
137
+ }
138
+
139
+ console.log('\\n3. Generating toolpaths (NEW API)...');
140
+ const t2 = performance.now();
141
+ const toolpathData = await raster.generateToolpaths({
142
+ xStep: xStep,
143
+ yStep: yStep,
144
+ zFloor: zFloor
145
+ });
146
+ const toolpathTime = performance.now() - t2;
147
+ console.log('✓ Toolpath:', toolpathData.numScanlines + 'x' + toolpathData.pointsPerLine, '=', toolpathData.pathData.length, 'Z-values');
148
+ console.log(' Generation time:', toolpathTime.toFixed(1), 'ms');
149
+
150
+ raster.terminate();
151
+
152
+ // Calculate checksum
153
+ let checksum = 0;
154
+ for (let i = 0; i < Math.min(1000, toolpathData.pathData.length); i++) {
155
+ checksum = (checksum + toolpathData.pathData[i] * (i + 1)) | 0;
156
+ }
157
+
158
+ return {
159
+ success: true,
160
+ tilingWorked: true,
161
+ output: {
162
+ parameters: {
163
+ resolution: resolution,
164
+ expectedMemoryMB: Math.round(expectedMemoryMB)
165
+ },
166
+ result: {
167
+ terrainPoints: terrainData.pointCount,
168
+ toolPoints: toolData.pointCount,
169
+ toolpathSize: toolpathData.pathData.length,
170
+ checksum: checksum
171
+ },
172
+ timing: {
173
+ terrain: terrainTime,
174
+ tool: toolTime,
175
+ toolpath: toolpathTime,
176
+ total: terrainTime + toolTime + toolpathTime
177
+ }
178
+ }
179
+ };
180
+ })();
181
+ `;
182
+
183
+ try {
184
+ const result = await mainWindow.webContents.executeJavaScript(testScript);
185
+
186
+ if (result.error) {
187
+ console.error('\n❌ TEST FAILED - Tiling did not work!');
188
+ console.error('Error:', result.error);
189
+ console.error('Resolution:', result.resolution);
190
+ console.error('Expected tiling:', result.expectedTiling);
191
+
192
+ fs.writeFileSync(OUTPUT_FILE, JSON.stringify({
193
+ failed: true,
194
+ error: result.error,
195
+ resolution: result.resolution
196
+ }, null, 2));
197
+
198
+ app.exit(1);
199
+ return;
200
+ }
201
+
202
+ console.log('\n=== Test Complete ===');
203
+ console.log('✅ Tiling worked correctly!');
204
+ console.log('Terrain points:', result.output.result.terrainPoints);
205
+ console.log('Tool points:', result.output.result.toolPoints);
206
+ console.log('Toolpath size:', result.output.result.toolpathSize);
207
+ console.log('Total time:', result.output.timing.total.toFixed(1), 'ms');
208
+
209
+ fs.writeFileSync(OUTPUT_FILE, JSON.stringify(result.output, null, 2));
210
+ console.log('\n✓ Results written to:', OUTPUT_FILE);
211
+
212
+ app.exit(0);
213
+ } catch (error) {
214
+ console.error('❌ Test execution error:', error);
215
+ app.exit(1);
216
+ }
217
+ });
218
+
219
+ mainWindow.webContents.on('console-message', (event, level, message) => {
220
+ if (message.includes('[Raster') || message.includes('[Worker') || message.includes('Tiling')) {
221
+ console.log('[Browser]', message);
222
+ }
223
+ });
224
+ }
225
+
226
+ app.whenReady().then(createWindow);
227
+
228
+ app.on('window-all-closed', () => {
229
+ app.quit();
230
+ });