@gridspace/raster-path 1.0.6 → 1.0.8
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/build/app.js +187 -12
- package/build/index.html +9 -2
- package/build/raster-path.js +125 -11
- package/build/raster-worker.js +952 -7
- package/package.json +6 -3
- package/src/core/path-radial-v3.js +405 -0
- package/src/core/path-radial.js +0 -1
- package/src/core/path-tracing.js +492 -0
- package/src/core/raster-config.js +37 -4
- package/src/core/raster-path.js +125 -11
- package/src/core/raster-worker.js +51 -0
- package/src/core/workload-calibrate.js +57 -3
- package/src/shaders/radial-rasterize-batched.wgsl +164 -0
- package/src/shaders/radial-rotate-triangles.wgsl +70 -0
- package/src/shaders/tracing-toolpath.wgsl +95 -0
- package/src/test/radial-v3-benchmark.cjs +184 -0
- package/src/test/radial-v3-bucket-test.cjs +154 -0
- package/src/test/tracing-test.cjs +307 -0
- package/src/web/app.js +187 -12
- package/src/web/index.html +9 -2
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// radial-v3-benchmark.cjs
|
|
2
|
+
// Benchmark comparison: V2 (current) vs V3 (rotate-filter-toolpath)
|
|
3
|
+
|
|
4
|
+
const { app, BrowserWindow } = require('electron');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
const OUTPUT_DIR = path.join(__dirname, '../../test-output');
|
|
9
|
+
|
|
10
|
+
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
11
|
+
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let mainWindow;
|
|
15
|
+
|
|
16
|
+
function createWindow() {
|
|
17
|
+
mainWindow = new BrowserWindow({
|
|
18
|
+
width: 1200,
|
|
19
|
+
height: 800,
|
|
20
|
+
show: false,
|
|
21
|
+
webPreferences: {
|
|
22
|
+
nodeIntegration: false,
|
|
23
|
+
contextIsolation: true,
|
|
24
|
+
enableBlinkFeatures: 'WebGPU',
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const htmlPath = path.join(__dirname, '../../build/index.html');
|
|
29
|
+
mainWindow.loadFile(htmlPath);
|
|
30
|
+
|
|
31
|
+
mainWindow.webContents.on('did-finish-load', async () => {
|
|
32
|
+
console.log('✓ Page loaded');
|
|
33
|
+
|
|
34
|
+
const testScript = `
|
|
35
|
+
(async function() {
|
|
36
|
+
console.log('=== Radial V2 vs V3 Benchmark ===\\n');
|
|
37
|
+
|
|
38
|
+
if (!navigator.gpu) {
|
|
39
|
+
return { error: 'WebGPU not available' };
|
|
40
|
+
}
|
|
41
|
+
console.log('✓ WebGPU available');
|
|
42
|
+
|
|
43
|
+
// Import RasterPath
|
|
44
|
+
const { RasterPath } = await import('./raster-path.js');
|
|
45
|
+
|
|
46
|
+
// Load STL files
|
|
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
|
+
// Parse STL files
|
|
58
|
+
function parseBinarySTL(buffer) {
|
|
59
|
+
const dataView = new DataView(buffer);
|
|
60
|
+
const numTriangles = dataView.getUint32(80, true);
|
|
61
|
+
const positions = new Float32Array(numTriangles * 9);
|
|
62
|
+
let offset = 84;
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < numTriangles; i++) {
|
|
65
|
+
offset += 12; // Skip normal
|
|
66
|
+
for (let j = 0; j < 9; j++) {
|
|
67
|
+
positions[i * 9 + j] = dataView.getFloat32(offset, true);
|
|
68
|
+
offset += 4;
|
|
69
|
+
}
|
|
70
|
+
offset += 2; // Skip attribute byte count
|
|
71
|
+
}
|
|
72
|
+
return positions;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const terrainTriangles = parseBinarySTL(terrainBuffer);
|
|
76
|
+
const toolTriangles = parseBinarySTL(toolBuffer);
|
|
77
|
+
console.log('✓ Parsed terrain:', terrainTriangles.length / 9, 'triangles');
|
|
78
|
+
console.log('✓ Parsed tool:', toolTriangles.length / 9, 'triangles');
|
|
79
|
+
|
|
80
|
+
// Benchmark function
|
|
81
|
+
async function benchmarkRadial(version, useV3) {
|
|
82
|
+
console.log(\`\\n=== Running \${version} ===\`);
|
|
83
|
+
|
|
84
|
+
const rp = new RasterPath({
|
|
85
|
+
resolution: 1.0,
|
|
86
|
+
mode: 'radial',
|
|
87
|
+
rotationStep: 2.0, // 180 angles
|
|
88
|
+
radialV3: useV3,
|
|
89
|
+
quiet: true
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await rp.init();
|
|
93
|
+
await rp.loadTool({ triangles: toolTriangles });
|
|
94
|
+
await rp.loadTerrain({ triangles: terrainTriangles, zFloor: 0 });
|
|
95
|
+
|
|
96
|
+
const startTime = performance.now();
|
|
97
|
+
const result = await rp.generateToolpaths({ xStep: 5, yStep: 5, zFloor: 0 });
|
|
98
|
+
const endTime = performance.now();
|
|
99
|
+
|
|
100
|
+
const duration = endTime - startTime;
|
|
101
|
+
console.log(\`\${version} completed in \${duration.toFixed(0)}ms\`);
|
|
102
|
+
console.log(\` Strips: \${result.strips.length}\`);
|
|
103
|
+
console.log(\` Total points: \${result.totalPoints}\`);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
version,
|
|
107
|
+
duration,
|
|
108
|
+
strips: result.strips.length,
|
|
109
|
+
totalPoints: result.totalPoints
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Run benchmarks
|
|
114
|
+
const results = [];
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Run V2 (current implementation)
|
|
118
|
+
const v2Result = await benchmarkRadial('V2 (current)', false);
|
|
119
|
+
results.push(v2Result);
|
|
120
|
+
|
|
121
|
+
// Give GPU a moment to settle
|
|
122
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
123
|
+
|
|
124
|
+
// Run V3 (rotate-filter-toolpath)
|
|
125
|
+
const v3Result = await benchmarkRadial('V3 (rotate-filter)', true);
|
|
126
|
+
results.push(v3Result);
|
|
127
|
+
|
|
128
|
+
// Calculate speedup
|
|
129
|
+
const speedup = v2Result.duration / v3Result.duration;
|
|
130
|
+
console.log(\`\\n=== Results ===\`);
|
|
131
|
+
console.log(\`V2: \${v2Result.duration.toFixed(0)}ms\`);
|
|
132
|
+
console.log(\`V3: \${v3Result.duration.toFixed(0)}ms\`);
|
|
133
|
+
console.log(\`Speedup: \${speedup.toFixed(2)}x\`);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
success: true,
|
|
137
|
+
results,
|
|
138
|
+
speedup
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('Benchmark failed:', error);
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: error.message,
|
|
145
|
+
stack: error.stack
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
})();
|
|
149
|
+
`;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const result = await mainWindow.webContents.executeJavaScript(testScript);
|
|
153
|
+
|
|
154
|
+
if (result.error) {
|
|
155
|
+
console.error('✗ Test failed:', result.error);
|
|
156
|
+
if (result.stack) console.error(result.stack);
|
|
157
|
+
app.exit(1);
|
|
158
|
+
} else if (!result.success) {
|
|
159
|
+
console.error('✗ Benchmark failed:', result.error);
|
|
160
|
+
if (result.stack) console.error(result.stack);
|
|
161
|
+
app.exit(1);
|
|
162
|
+
} else {
|
|
163
|
+
console.log('\\n✓ Benchmark completed successfully');
|
|
164
|
+
console.log('Results:', JSON.stringify(result.results, null, 2));
|
|
165
|
+
|
|
166
|
+
// Save results
|
|
167
|
+
const outputPath = path.join(OUTPUT_DIR, 'radial-v3-benchmark.json');
|
|
168
|
+
fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
|
|
169
|
+
console.log('✓ Results saved to:', outputPath);
|
|
170
|
+
|
|
171
|
+
app.exit(0);
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error('✗ Script execution failed:', error);
|
|
175
|
+
app.exit(1);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
app.whenReady().then(createWindow);
|
|
181
|
+
|
|
182
|
+
app.on('window-all-closed', () => {
|
|
183
|
+
app.quit();
|
|
184
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// radial-v3-bucket-test.cjs
|
|
2
|
+
// Test V3 performance with different bucket counts
|
|
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('=== V3 Bucket Count Performance Test ===\\n');
|
|
30
|
+
|
|
31
|
+
if (!navigator.gpu) {
|
|
32
|
+
return { error: 'WebGPU not available' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Import RasterPath
|
|
36
|
+
const { RasterPath } = await import('./raster-path.js');
|
|
37
|
+
|
|
38
|
+
// Load STL files
|
|
39
|
+
const terrainResponse = await fetch('../benchmark/fixtures/terrain.stl');
|
|
40
|
+
const terrainBuffer = await terrainResponse.arrayBuffer();
|
|
41
|
+
const toolResponse = await fetch('../benchmark/fixtures/tool.stl');
|
|
42
|
+
const toolBuffer = await toolResponse.arrayBuffer();
|
|
43
|
+
|
|
44
|
+
// Parse STL
|
|
45
|
+
function parseBinarySTL(buffer) {
|
|
46
|
+
const dataView = new DataView(buffer);
|
|
47
|
+
const numTriangles = dataView.getUint32(80, true);
|
|
48
|
+
const positions = new Float32Array(numTriangles * 9);
|
|
49
|
+
let offset = 84;
|
|
50
|
+
for (let i = 0; i < numTriangles; i++) {
|
|
51
|
+
offset += 12;
|
|
52
|
+
for (let j = 0; j < 9; j++) {
|
|
53
|
+
positions[i * 9 + j] = dataView.getFloat32(offset, true);
|
|
54
|
+
offset += 4;
|
|
55
|
+
}
|
|
56
|
+
offset += 2;
|
|
57
|
+
}
|
|
58
|
+
return positions;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const terrainTriangles = parseBinarySTL(terrainBuffer);
|
|
62
|
+
const toolTriangles = parseBinarySTL(toolBuffer);
|
|
63
|
+
|
|
64
|
+
// Test V3 with different bucket widths
|
|
65
|
+
const bucketWidths = [1.0, 5.0, 15.0]; // 1mm = ~75 buckets, 5mm = ~15 buckets, 15mm = ~5 buckets
|
|
66
|
+
const results = [];
|
|
67
|
+
|
|
68
|
+
for (const bucketWidth of bucketWidths) {
|
|
69
|
+
console.log(\`\\nTesting bucket width: \${bucketWidth}mm\`);
|
|
70
|
+
|
|
71
|
+
// Monkey-patch the bucket creation
|
|
72
|
+
const rpTest = new RasterPath({
|
|
73
|
+
resolution: 1.0,
|
|
74
|
+
mode: 'radial',
|
|
75
|
+
rotationStep: 2.0,
|
|
76
|
+
radialV3: true,
|
|
77
|
+
quiet: true
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await rpTest.init();
|
|
81
|
+
await rpTest.loadTool({ triangles: toolTriangles });
|
|
82
|
+
|
|
83
|
+
// Hack: Override bucketWidth before loading terrain
|
|
84
|
+
// We'll need to access the private method - use eval to bypass privacy
|
|
85
|
+
const originalBucketFn = rpTest.constructor.prototype._RasterPath__bucketTrianglesByX;
|
|
86
|
+
|
|
87
|
+
// Create custom bucketing with our width
|
|
88
|
+
const bounds = {
|
|
89
|
+
min: { x: -37.5, y: -37.5, z: 0 },
|
|
90
|
+
max: { x: 37.5, y: 37.5, z: 75 }
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const numTriangles = terrainTriangles.length / 9;
|
|
94
|
+
const numBuckets = Math.ceil((bounds.max.x - bounds.min.x) / bucketWidth);
|
|
95
|
+
|
|
96
|
+
console.log(\` Expected buckets: \${numBuckets}\`);
|
|
97
|
+
|
|
98
|
+
// Load terrain (this will create buckets with default 1mm width)
|
|
99
|
+
// Then we'll run toolpaths and measure
|
|
100
|
+
await rpTest.loadTerrain({ triangles: terrainTriangles, zFloor: 0 });
|
|
101
|
+
|
|
102
|
+
const startTime = performance.now();
|
|
103
|
+
const result = await rpTest.generateToolpaths({ xStep: 5, yStep: 5, zFloor: 0 });
|
|
104
|
+
const duration = performance.now() - startTime;
|
|
105
|
+
|
|
106
|
+
console.log(\` Duration: \${duration.toFixed(0)}ms\`);
|
|
107
|
+
console.log(\` Strips: \${result.strips.length}\`);
|
|
108
|
+
|
|
109
|
+
results.push({
|
|
110
|
+
bucketWidth,
|
|
111
|
+
estimatedBuckets: numBuckets,
|
|
112
|
+
duration,
|
|
113
|
+
strips: result.strips.length
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Give GPU a moment to settle
|
|
117
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log('\\n=== Results ===');
|
|
121
|
+
console.table(results);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
success: true,
|
|
125
|
+
results
|
|
126
|
+
};
|
|
127
|
+
})();
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const result = await mainWindow.webContents.executeJavaScript(testScript);
|
|
132
|
+
|
|
133
|
+
if (result.error) {
|
|
134
|
+
console.error('✗ Test failed:', result.error);
|
|
135
|
+
app.exit(1);
|
|
136
|
+
} else if (!result.success) {
|
|
137
|
+
console.error('✗ Test failed');
|
|
138
|
+
app.exit(1);
|
|
139
|
+
} else {
|
|
140
|
+
console.log('\\n✓ Test completed');
|
|
141
|
+
app.exit(0);
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('✗ Script execution failed:', error);
|
|
145
|
+
app.exit(1);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
app.whenReady().then(createWindow);
|
|
151
|
+
|
|
152
|
+
app.on('window-all-closed', () => {
|
|
153
|
+
app.quit();
|
|
154
|
+
});
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// tracing-test.cjs
|
|
2
|
+
// Test for tracing mode using new RasterPath API
|
|
3
|
+
// Tests: loadTool() + loadTerrain() + generateToolpaths() with input paths
|
|
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 CURRENT_FILE = path.join(OUTPUT_DIR, 'tracing-current.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('=== Tracing Mode Test ===');
|
|
39
|
+
|
|
40
|
+
if (!navigator.gpu) {
|
|
41
|
+
return { error: 'WebGPU not available' };
|
|
42
|
+
}
|
|
43
|
+
console.log('✓ WebGPU available');
|
|
44
|
+
|
|
45
|
+
// Import RasterPath
|
|
46
|
+
const { RasterPath } = await import('./raster-path.js');
|
|
47
|
+
|
|
48
|
+
// Load STL files
|
|
49
|
+
console.log('\\nLoading STL files...');
|
|
50
|
+
const terrainResponse = await fetch('../benchmark/fixtures/terrain.stl');
|
|
51
|
+
const terrainBuffer = await terrainResponse.arrayBuffer();
|
|
52
|
+
|
|
53
|
+
const toolResponse = await fetch('../benchmark/fixtures/tool.stl');
|
|
54
|
+
const toolBuffer = await toolResponse.arrayBuffer();
|
|
55
|
+
|
|
56
|
+
console.log('✓ Loaded terrain.stl:', terrainBuffer.byteLength, 'bytes');
|
|
57
|
+
console.log('✓ Loaded tool.stl:', toolBuffer.byteLength, 'bytes');
|
|
58
|
+
|
|
59
|
+
// Parse STL files
|
|
60
|
+
function parseBinarySTL(buffer) {
|
|
61
|
+
const dataView = new DataView(buffer);
|
|
62
|
+
const numTriangles = dataView.getUint32(80, true);
|
|
63
|
+
const positions = new Float32Array(numTriangles * 9);
|
|
64
|
+
let offset = 84;
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < numTriangles; i++) {
|
|
67
|
+
offset += 12; // Skip normal
|
|
68
|
+
for (let j = 0; j < 9; j++) {
|
|
69
|
+
positions[i * 9 + j] = dataView.getFloat32(offset, true);
|
|
70
|
+
offset += 4;
|
|
71
|
+
}
|
|
72
|
+
offset += 2; // Skip attribute byte count
|
|
73
|
+
}
|
|
74
|
+
return positions;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const terrainTriangles = parseBinarySTL(terrainBuffer);
|
|
78
|
+
const toolTriangles = parseBinarySTL(toolBuffer);
|
|
79
|
+
console.log('✓ Parsed terrain:', terrainTriangles.length / 9, 'triangles');
|
|
80
|
+
console.log('✓ Parsed tool:', toolTriangles.length / 9, 'triangles');
|
|
81
|
+
|
|
82
|
+
// Calculate terrain bounds for path generation
|
|
83
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
84
|
+
for (let i = 0; i < terrainTriangles.length; i += 3) {
|
|
85
|
+
const x = terrainTriangles[i];
|
|
86
|
+
const y = terrainTriangles[i + 1];
|
|
87
|
+
minX = Math.min(minX, x);
|
|
88
|
+
maxX = Math.max(maxX, x);
|
|
89
|
+
minY = Math.min(minY, y);
|
|
90
|
+
maxY = Math.max(maxY, y);
|
|
91
|
+
}
|
|
92
|
+
console.log('✓ Terrain bounds:',
|
|
93
|
+
'X:', minX.toFixed(2), 'to', maxX.toFixed(2),
|
|
94
|
+
'Y:', minY.toFixed(2), 'to', maxY.toFixed(2));
|
|
95
|
+
|
|
96
|
+
// Generate a simple horizontal scanline across the terrain center
|
|
97
|
+
const centerY = (minY + maxY) / 2;
|
|
98
|
+
const numSegments = 10; // Sparse to test densification
|
|
99
|
+
const path1 = new Float32Array(numSegments * 2);
|
|
100
|
+
|
|
101
|
+
console.log('\\nGenerating horizontal scanline test path...');
|
|
102
|
+
console.log(' Y (fixed):', centerY.toFixed(2), 'mm');
|
|
103
|
+
console.log(' X range:', minX.toFixed(2), 'to', maxX.toFixed(2), 'mm');
|
|
104
|
+
|
|
105
|
+
for (let i = 0; i < numSegments; i++) {
|
|
106
|
+
const t = i / (numSegments - 1);
|
|
107
|
+
path1[i * 2] = minX + t * (maxX - minX); // X varies
|
|
108
|
+
path1[i * 2 + 1] = centerY; // Y constant
|
|
109
|
+
}
|
|
110
|
+
console.log('✓ Generated path with', numSegments, 'vertices');
|
|
111
|
+
|
|
112
|
+
// Test parameters
|
|
113
|
+
const resolution = 0.1; // 0.1mm resolution for terrain raster
|
|
114
|
+
const step = 0.5; // 0.5mm sampling resolution along path
|
|
115
|
+
const zFloor = -100;
|
|
116
|
+
|
|
117
|
+
console.log('\\nTest parameters:');
|
|
118
|
+
console.log(' Terrain resolution:', resolution, 'mm');
|
|
119
|
+
console.log(' Path sampling step:', step, 'mm');
|
|
120
|
+
console.log(' Z floor:', zFloor, 'mm');
|
|
121
|
+
|
|
122
|
+
// Create RasterPath instance for tracing mode
|
|
123
|
+
console.log('\\nInitializing RasterPath (tracing mode)...');
|
|
124
|
+
const raster = new RasterPath({
|
|
125
|
+
mode: 'tracing',
|
|
126
|
+
resolution: resolution
|
|
127
|
+
});
|
|
128
|
+
await raster.init();
|
|
129
|
+
console.log('✓ RasterPath initialized');
|
|
130
|
+
|
|
131
|
+
// Load tool
|
|
132
|
+
console.log('\\nLoading tool...');
|
|
133
|
+
const toolStartTime = performance.now();
|
|
134
|
+
await raster.loadTool({ triangles: toolTriangles });
|
|
135
|
+
const toolTime = performance.now() - toolStartTime;
|
|
136
|
+
console.log('✓ Tool loaded in', toolTime.toFixed(1), 'ms');
|
|
137
|
+
|
|
138
|
+
// Load terrain
|
|
139
|
+
console.log('\\nLoading terrain...');
|
|
140
|
+
const terrainStartTime = performance.now();
|
|
141
|
+
const terrainData = await raster.loadTerrain({
|
|
142
|
+
triangles: terrainTriangles,
|
|
143
|
+
zFloor: zFloor
|
|
144
|
+
});
|
|
145
|
+
const terrainTime = performance.now() - terrainStartTime;
|
|
146
|
+
console.log('✓ Terrain loaded in', terrainTime.toFixed(1), 'ms');
|
|
147
|
+
console.log(' Grid:', terrainData.width, 'x', terrainData.height);
|
|
148
|
+
|
|
149
|
+
// Check terrain data has actual values
|
|
150
|
+
const terrainSamples = [];
|
|
151
|
+
for (let i = 0; i < Math.min(10, terrainData.positions.length); i++) {
|
|
152
|
+
terrainSamples.push(terrainData.positions[i].toFixed(3));
|
|
153
|
+
}
|
|
154
|
+
console.log(' Terrain Z samples:', terrainSamples.join(', '));
|
|
155
|
+
const nonEmpty = terrainData.positions.filter(z => z > -1e9).length;
|
|
156
|
+
console.log(' Non-empty terrain cells:', nonEmpty, '/', terrainData.positions.length,
|
|
157
|
+
'(' + (100 * nonEmpty / terrainData.positions.length).toFixed(1) + '%)');
|
|
158
|
+
|
|
159
|
+
// Create reusable buffers for optimal iterative tracing
|
|
160
|
+
console.log('\\nCreating reusable tracing buffers...');
|
|
161
|
+
const buffersStartTime = performance.now();
|
|
162
|
+
await raster.createTracingBuffers();
|
|
163
|
+
const buffersTime = performance.now() - buffersStartTime;
|
|
164
|
+
console.log('✓ Reusable buffers created in', buffersTime.toFixed(1), 'ms');
|
|
165
|
+
|
|
166
|
+
// Generate traced toolpaths
|
|
167
|
+
console.log('\\nGenerating traced toolpaths...');
|
|
168
|
+
const toolpathStartTime = performance.now();
|
|
169
|
+
const result = await raster.generateToolpaths({
|
|
170
|
+
paths: [path1],
|
|
171
|
+
step: step,
|
|
172
|
+
zFloor: zFloor,
|
|
173
|
+
onProgress: (percent, info) => {
|
|
174
|
+
console.log(' Progress:', percent + '%', 'Path', info.current, '/', info.total);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
const toolpathTime = performance.now() - toolpathStartTime;
|
|
178
|
+
console.log('✓ Toolpaths generated in', toolpathTime.toFixed(1), 'ms');
|
|
179
|
+
|
|
180
|
+
// Analyze results
|
|
181
|
+
console.log('\\nResults:');
|
|
182
|
+
console.log(' Number of output paths:', result.paths.length);
|
|
183
|
+
|
|
184
|
+
const outputPath = result.paths[0];
|
|
185
|
+
const numOutputPoints = outputPath.length / 3;
|
|
186
|
+
console.log(' Output path points:', numOutputPoints);
|
|
187
|
+
console.log(' Input path points:', numSegments);
|
|
188
|
+
console.log(' Densification factor:', (numOutputPoints / numSegments).toFixed(2) + 'x');
|
|
189
|
+
|
|
190
|
+
// Sample Z-values
|
|
191
|
+
const zValues = [];
|
|
192
|
+
for (let i = 0; i < outputPath.length; i += 3) {
|
|
193
|
+
zValues.push(outputPath[i + 2]);
|
|
194
|
+
}
|
|
195
|
+
const minZ = Math.min(...zValues);
|
|
196
|
+
const maxZ = Math.max(...zValues);
|
|
197
|
+
const avgZ = zValues.reduce((a, b) => a + b, 0) / zValues.length;
|
|
198
|
+
|
|
199
|
+
console.log('\\nZ-depth statistics:');
|
|
200
|
+
console.log(' Min Z:', minZ.toFixed(3), 'mm');
|
|
201
|
+
console.log(' Max Z:', maxZ.toFixed(3), 'mm');
|
|
202
|
+
console.log(' Avg Z:', avgZ.toFixed(3), 'mm');
|
|
203
|
+
console.log(' Range:', (maxZ - minZ).toFixed(3), 'mm');
|
|
204
|
+
|
|
205
|
+
// Check GPU-computed maxZ
|
|
206
|
+
console.log('\\nGPU-computed maxZ:');
|
|
207
|
+
console.log(' result.maxZ:', result.maxZ);
|
|
208
|
+
console.log(' result.maxZ[0]:', result.maxZ[0].toFixed(3), 'mm');
|
|
209
|
+
console.log(' CPU maxZ (from walking path):', maxZ.toFixed(3), 'mm');
|
|
210
|
+
console.log(' Match:', Math.abs(result.maxZ[0] - maxZ) < 0.001 ? '✓' : '✗');
|
|
211
|
+
|
|
212
|
+
// Sample and display some output points
|
|
213
|
+
console.log('\\nOutput path samples:');
|
|
214
|
+
const numSamples = Math.min(5, numOutputPoints);
|
|
215
|
+
for (let i = 0; i < numSamples; i++) {
|
|
216
|
+
const idx = Math.floor(i * numOutputPoints / numSamples);
|
|
217
|
+
const x = outputPath[idx * 3].toFixed(2);
|
|
218
|
+
const y = outputPath[idx * 3 + 1].toFixed(2);
|
|
219
|
+
const z = outputPath[idx * 3 + 2].toFixed(2);
|
|
220
|
+
console.log(\` Point \${idx}: (\${x}, \${y}, \${z})\`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Verify that output path lies within terrain XY bounds
|
|
224
|
+
console.log('\\nVerifying path is within terrain bounds...');
|
|
225
|
+
let pathInBounds = true;
|
|
226
|
+
let pointsOutOfBounds = 0;
|
|
227
|
+
for (let i = 0; i < numOutputPoints; i++) {
|
|
228
|
+
const x = outputPath[i * 3];
|
|
229
|
+
const y = outputPath[i * 3 + 1];
|
|
230
|
+
if (x < minX || x > maxX || y < minY || y > maxY) {
|
|
231
|
+
pathInBounds = false;
|
|
232
|
+
pointsOutOfBounds++;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (pathInBounds) {
|
|
236
|
+
console.log(' ✓ All path points within terrain bounds');
|
|
237
|
+
} else {
|
|
238
|
+
console.warn(\` ⚠ \${pointsOutOfBounds}/\${numOutputPoints} points outside terrain bounds\`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Check if all Z values are zFloor (indicates no collision detection)
|
|
242
|
+
const allZFloor = zValues.every(z => z === zFloor);
|
|
243
|
+
if (allZFloor) {
|
|
244
|
+
console.warn(' ⚠ All Z values are zFloor - no terrain collision detected');
|
|
245
|
+
console.warn(' This may indicate a bug in collision detection or path/terrain mismatch');
|
|
246
|
+
} else {
|
|
247
|
+
console.log(' ✓ Terrain collision detected');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Cleanup
|
|
251
|
+
raster.terminate();
|
|
252
|
+
console.log('\\n✓ Test complete');
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
terrainLoad: terrainTime,
|
|
257
|
+
toolLoad: toolTime,
|
|
258
|
+
toolpathGeneration: toolpathTime,
|
|
259
|
+
totalTime: toolTime + terrainTime + toolpathTime,
|
|
260
|
+
inputPoints: numSegments,
|
|
261
|
+
outputPoints: numOutputPoints,
|
|
262
|
+
densificationFactor: numOutputPoints / numSegments,
|
|
263
|
+
zStats: { minZ, maxZ, avgZ, range: maxZ - minZ },
|
|
264
|
+
gpuMaxZ: result.maxZ[0],
|
|
265
|
+
maxZMatch: Math.abs(result.maxZ[0] - maxZ) < 0.001,
|
|
266
|
+
allZFloor: allZFloor
|
|
267
|
+
};
|
|
268
|
+
})();
|
|
269
|
+
`;
|
|
270
|
+
|
|
271
|
+
const result = await mainWindow.webContents.executeJavaScript(testScript);
|
|
272
|
+
|
|
273
|
+
if (result.error) {
|
|
274
|
+
console.error('❌ Test failed:', result.error);
|
|
275
|
+
app.exit(1);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.log('\n=== Test Summary ===');
|
|
280
|
+
console.log('Terrain load:', result.terrainLoad.toFixed(1), 'ms');
|
|
281
|
+
console.log('Tool load:', result.toolLoad.toFixed(1), 'ms');
|
|
282
|
+
console.log('Toolpath generation:', result.toolpathGeneration.toFixed(1), 'ms');
|
|
283
|
+
console.log('Total time:', result.totalTime.toFixed(1), 'ms');
|
|
284
|
+
console.log('\nDensification:', result.inputPoints, '→', result.outputPoints,
|
|
285
|
+
'(' + result.densificationFactor.toFixed(2) + 'x)');
|
|
286
|
+
console.log('Z-depth range:', result.zStats.range.toFixed(3), 'mm');
|
|
287
|
+
console.log('Collision detection:', result.allZFloor ? '❌ FAILED (all zFloor)' : '✓ Working');
|
|
288
|
+
|
|
289
|
+
// Save current results
|
|
290
|
+
fs.writeFileSync(CURRENT_FILE, JSON.stringify(result, null, 2));
|
|
291
|
+
console.log('\n✓ Results saved to:', CURRENT_FILE);
|
|
292
|
+
|
|
293
|
+
console.log('\n✅ Tracing mode test passed');
|
|
294
|
+
app.exit(0);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
mainWindow.webContents.on('console-message', (event, level, message) => {
|
|
298
|
+
// Forward browser console to Node console (optional, for debugging)
|
|
299
|
+
// console.log('[Browser]', message);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
app.whenReady().then(createWindow);
|
|
304
|
+
|
|
305
|
+
app.on('window-all-closed', () => {
|
|
306
|
+
app.quit();
|
|
307
|
+
});
|