cyclecad 2.0.1 → 3.0.0
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/DELIVERABLES.txt +296 -445
- package/ENHANCEMENT_COMPLETION_REPORT.md +383 -0
- package/ENHANCEMENT_SUMMARY.txt +308 -0
- package/FEATURE_INVENTORY.md +235 -0
- package/FUSION360_FEATURES_SUMMARY.md +452 -0
- package/FUSION360_PARITY_ENHANCEMENTS.md +461 -0
- package/FUSION360_PARITY_SUMMARY.md +520 -0
- package/FUSION360_QUICK_REFERENCE.md +351 -0
- package/IMPLEMENTATION_GUIDE.md +502 -0
- package/INTEGRATION-GUIDE.md +377 -0
- package/MODULES_PHASES_6_7.md +780 -0
- package/MODULE_API_REFERENCE.md +712 -0
- package/MODULE_INVENTORY.txt +264 -0
- package/app/index.html +1345 -4930
- package/app/js/app.js +1312 -514
- package/app/js/brep-kernel.js +1353 -455
- package/app/js/help-module.js +1437 -0
- package/app/js/kernel.js +364 -40
- package/app/js/modules/animation-module.js +1461 -0
- package/app/js/modules/assembly-module.js +47 -3
- package/app/js/modules/cam-module.js +1572 -0
- package/app/js/modules/collaboration-module.js +1615 -0
- package/app/js/modules/constraint-module.js +1266 -0
- package/app/js/modules/data-module.js +1054 -0
- package/app/js/modules/drawing-module.js +54 -8
- package/app/js/modules/formats-module.js +873 -0
- package/app/js/modules/inspection-module.js +1330 -0
- package/app/js/modules/mesh-module-enhanced.js +880 -0
- package/app/js/modules/mesh-module.js +968 -0
- package/app/js/modules/operations-module.js +40 -7
- package/app/js/modules/plugin-module.js +1554 -0
- package/app/js/modules/rendering-module.js +1766 -0
- package/app/js/modules/scripting-module.js +1073 -0
- package/app/js/modules/simulation-module.js +60 -3
- package/app/js/modules/sketch-module.js +2029 -91
- package/app/js/modules/step-module.js +47 -6
- package/app/js/modules/surface-module.js +1040 -0
- package/app/js/modules/version-module.js +1830 -0
- package/app/js/modules/viewport-module.js +95 -8
- package/app/test-agent-v2.html +881 -1316
- package/cycleCAD-Architecture-v2.pptx +0 -0
- package/docs/ARCHITECTURE.html +838 -1408
- package/docs/DEVELOPER-GUIDE.md +1504 -0
- package/docs/TUTORIAL.md +740 -0
- package/package.json +1 -1
- package/~$cycleCAD-Architecture-v2.pptx +0 -0
- package/.github/scripts/cad-diff.js +0 -590
- package/.github/workflows/cad-diff.yml +0 -117
|
@@ -0,0 +1,1572 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file cam-module.js
|
|
3
|
+
* @description CAM (Computer-Aided Manufacturing) Module.
|
|
4
|
+
* Generates toolpaths for CNC milling, turning, and 3D printing.
|
|
5
|
+
* Includes tool library, G-code generation, and toolpath simulation.
|
|
6
|
+
*
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
* @author Sachin Kumar <vvlars@googlemail.com>
|
|
9
|
+
* @license MIT
|
|
10
|
+
* @module cam
|
|
11
|
+
* @requires viewport, operations
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* CAM (Computer-Aided Manufacturing) Module
|
|
18
|
+
* Handles toolpath generation, G-code output, and manufacturing simulation.
|
|
19
|
+
*/
|
|
20
|
+
const CAMModule = (() => {
|
|
21
|
+
const MODULE_NAME = 'cam';
|
|
22
|
+
let viewport = null;
|
|
23
|
+
let scene = null;
|
|
24
|
+
let ui = null;
|
|
25
|
+
|
|
26
|
+
// Tool library with standard tools
|
|
27
|
+
const defaultToolLibrary = {
|
|
28
|
+
'flat-endmill-6mm': {
|
|
29
|
+
id: 'flat-endmill-6mm',
|
|
30
|
+
name: 'Flat End Mill 6mm',
|
|
31
|
+
type: 'flat',
|
|
32
|
+
diameter: 6,
|
|
33
|
+
fluteLength: 20,
|
|
34
|
+
overallLength: 50,
|
|
35
|
+
material: 'carbide',
|
|
36
|
+
coating: 'TiN',
|
|
37
|
+
rpm: 12000,
|
|
38
|
+
feed: 1200,
|
|
39
|
+
chipLoad: 0.1,
|
|
40
|
+
cost: 15.50,
|
|
41
|
+
},
|
|
42
|
+
'ball-endmill-3mm': {
|
|
43
|
+
id: 'ball-endmill-3mm',
|
|
44
|
+
name: 'Ball End Mill 3mm',
|
|
45
|
+
type: 'ball',
|
|
46
|
+
diameter: 3,
|
|
47
|
+
fluteLength: 15,
|
|
48
|
+
overallLength: 45,
|
|
49
|
+
material: 'carbide',
|
|
50
|
+
coating: 'TiN',
|
|
51
|
+
rpm: 18000,
|
|
52
|
+
feed: 800,
|
|
53
|
+
chipLoad: 0.08,
|
|
54
|
+
cost: 12.75,
|
|
55
|
+
},
|
|
56
|
+
'drill-5mm': {
|
|
57
|
+
id: 'drill-5mm',
|
|
58
|
+
name: 'Drill 5mm',
|
|
59
|
+
type: 'drill',
|
|
60
|
+
diameter: 5,
|
|
61
|
+
pointAngle: 118,
|
|
62
|
+
fluteLength: 30,
|
|
63
|
+
overallLength: 70,
|
|
64
|
+
material: 'HSS',
|
|
65
|
+
rpm: 3000,
|
|
66
|
+
feed: 200,
|
|
67
|
+
chipLoad: 0.15,
|
|
68
|
+
cost: 2.50,
|
|
69
|
+
},
|
|
70
|
+
'face-mill-50mm': {
|
|
71
|
+
id: 'face-mill-50mm',
|
|
72
|
+
name: 'Face Mill 50mm',
|
|
73
|
+
type: 'face',
|
|
74
|
+
diameter: 50,
|
|
75
|
+
inserts: 5,
|
|
76
|
+
material: 'carbide',
|
|
77
|
+
rpm: 4000,
|
|
78
|
+
feed: 2000,
|
|
79
|
+
chipLoad: 0.2,
|
|
80
|
+
cost: 85.00,
|
|
81
|
+
},
|
|
82
|
+
'slot-drills-4mm': {
|
|
83
|
+
id: 'slot-drills-4mm',
|
|
84
|
+
name: 'Slot Drill 4mm',
|
|
85
|
+
type: 'slot',
|
|
86
|
+
diameter: 4,
|
|
87
|
+
fluteLength: 12,
|
|
88
|
+
overallLength: 40,
|
|
89
|
+
material: 'carbide',
|
|
90
|
+
rpm: 15000,
|
|
91
|
+
feed: 900,
|
|
92
|
+
chipLoad: 0.12,
|
|
93
|
+
cost: 11.25,
|
|
94
|
+
},
|
|
95
|
+
'chamfer-90deg': {
|
|
96
|
+
id: 'chamfer-90deg',
|
|
97
|
+
name: 'Chamfer 90°',
|
|
98
|
+
type: 'chamfer',
|
|
99
|
+
diameter: 10,
|
|
100
|
+
angle: 90,
|
|
101
|
+
material: 'carbide',
|
|
102
|
+
rpm: 8000,
|
|
103
|
+
feed: 600,
|
|
104
|
+
cost: 18.50,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// CAM state
|
|
109
|
+
const camState = {
|
|
110
|
+
workCoordinateSystem: null,
|
|
111
|
+
stock: null,
|
|
112
|
+
selectedTool: null,
|
|
113
|
+
toolLibrary: new Map(Object.entries(defaultToolLibrary)),
|
|
114
|
+
toolpaths: new Map(),
|
|
115
|
+
gcode: null,
|
|
116
|
+
setupParams: {
|
|
117
|
+
feedUnits: 'inch/min', // inch/min | mm/min
|
|
118
|
+
rapidRate: 5000,
|
|
119
|
+
safeHeight: 5,
|
|
120
|
+
retractHeight: 10,
|
|
121
|
+
spindleDirection: 'cw', // cw | ccw
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const toolpathCounter = { count: 0 };
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Initialize the CAM Module
|
|
129
|
+
* @param {Object} deps - Dependencies { viewport, scene }
|
|
130
|
+
*/
|
|
131
|
+
function init(deps) {
|
|
132
|
+
viewport = deps.viewport;
|
|
133
|
+
scene = deps.scene;
|
|
134
|
+
registerCommands();
|
|
135
|
+
window.addEventListener('keydown', handleKeyboard);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Define work coordinate system and stock
|
|
140
|
+
* @param {Object} params
|
|
141
|
+
* @param {string} params.stockType - 'box' | 'cylinder' | 'from_model'
|
|
142
|
+
* @param {Object} params.dimensions - { x, y, z } or { diameter, height }
|
|
143
|
+
* @param {THREE.Vector3} params.origin - WCS origin
|
|
144
|
+
* @param {THREE.Vector3} params.zDir - Z axis (spindle) direction
|
|
145
|
+
* @returns {Object} Setup result
|
|
146
|
+
*/
|
|
147
|
+
function setupWorkCoordinateSystem(params = {}) {
|
|
148
|
+
const {
|
|
149
|
+
stockType = 'box',
|
|
150
|
+
dimensions = { x: 100, y: 100, z: 50 },
|
|
151
|
+
origin = new THREE.Vector3(0, 0, 0),
|
|
152
|
+
zDir = new THREE.Vector3(0, 0, 1),
|
|
153
|
+
} = params;
|
|
154
|
+
|
|
155
|
+
// Create WCS frame
|
|
156
|
+
camState.workCoordinateSystem = {
|
|
157
|
+
origin: origin.clone(),
|
|
158
|
+
zDir: zDir.normalize(),
|
|
159
|
+
xDir: new THREE.Vector3(1, 0, 0),
|
|
160
|
+
yDir: new THREE.Vector3(0, 1, 0),
|
|
161
|
+
type: stockType,
|
|
162
|
+
dimensions,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Create stock visualization
|
|
166
|
+
const stockGeom = createStockGeometry(stockType, dimensions);
|
|
167
|
+
const stockMat = new THREE.MeshPhongMaterial({
|
|
168
|
+
color: 0xcccccc,
|
|
169
|
+
transparent: true,
|
|
170
|
+
opacity: 0.2,
|
|
171
|
+
wireframe: true,
|
|
172
|
+
});
|
|
173
|
+
const stockMesh = new THREE.Mesh(stockGeom, stockMat);
|
|
174
|
+
stockMesh.position.copy(origin);
|
|
175
|
+
stockMesh.name = 'stock_visualization';
|
|
176
|
+
|
|
177
|
+
camState.stock = stockMesh;
|
|
178
|
+
if (viewport?.scene) {
|
|
179
|
+
viewport.scene.add(stockMesh);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log('[CAM] WCS setup:', camState.workCoordinateSystem);
|
|
183
|
+
window.dispatchEvent(new CustomEvent('cam:setupComplete', { detail: camState.workCoordinateSystem }));
|
|
184
|
+
|
|
185
|
+
return { status: 'ok', wcs: camState.workCoordinateSystem };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Generate 2D contour (profile) toolpath
|
|
190
|
+
* @param {Object} params
|
|
191
|
+
* @param {THREE.Vector3[]} params.profile - Profile points (closed loop)
|
|
192
|
+
* @param {number} params.depth - Cut depth
|
|
193
|
+
* @param {string} params.toolId - Tool ID
|
|
194
|
+
* @param {string} params.type - 'inside' | 'outside' | 'on'
|
|
195
|
+
* @param {number} params.stepDown - Depth per pass
|
|
196
|
+
* @returns {Object} Toolpath object
|
|
197
|
+
*/
|
|
198
|
+
function generateContour2D(params = {}) {
|
|
199
|
+
const {
|
|
200
|
+
profile = [],
|
|
201
|
+
depth = 10,
|
|
202
|
+
toolId = 'flat-endmill-6mm',
|
|
203
|
+
type = 'outside',
|
|
204
|
+
stepDown = 5,
|
|
205
|
+
} = params;
|
|
206
|
+
|
|
207
|
+
const tool = camState.toolLibrary.get(toolId);
|
|
208
|
+
if (!tool) throw new Error(`Tool ${toolId} not found`);
|
|
209
|
+
|
|
210
|
+
const id = `tp_contour2d_${toolpathCounter.count++}`;
|
|
211
|
+
|
|
212
|
+
// Generate passes
|
|
213
|
+
const passes = [];
|
|
214
|
+
const depthPasses = Math.ceil(depth / stepDown);
|
|
215
|
+
|
|
216
|
+
for (let pass = 0; pass < depthPasses; pass++) {
|
|
217
|
+
const currentDepth = Math.min((pass + 1) * stepDown, depth);
|
|
218
|
+
passes.push({
|
|
219
|
+
depth: currentDepth,
|
|
220
|
+
points: offsetProfile(profile, type === 'inside' ? -tool.diameter / 2 : tool.diameter / 2),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const toolpath = {
|
|
225
|
+
id,
|
|
226
|
+
type: 'contour_2d',
|
|
227
|
+
tool,
|
|
228
|
+
profile,
|
|
229
|
+
depth,
|
|
230
|
+
stepDown,
|
|
231
|
+
passes,
|
|
232
|
+
estimatedTime: calculateEstimatedTime(profile, passes, tool),
|
|
233
|
+
status: 'generated',
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
camState.toolpaths.set(id, toolpath);
|
|
237
|
+
|
|
238
|
+
// Visualize toolpath
|
|
239
|
+
visualizeToolpath(toolpath);
|
|
240
|
+
|
|
241
|
+
console.log('[CAM] Contour 2D generated:', { id, passes: passes.length, depth });
|
|
242
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: toolpath }));
|
|
243
|
+
|
|
244
|
+
return { id, type: 'contour_2d', passes: passes.length, estimatedTime: toolpath.estimatedTime };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Generate pocket (enclosed region clear) toolpath
|
|
249
|
+
* @param {Object} params
|
|
250
|
+
* @param {THREE.Vector3[]} params.region - Region boundary
|
|
251
|
+
* @param {number} params.depth - Pocket depth
|
|
252
|
+
* @param {string} params.toolId - Tool ID
|
|
253
|
+
* @param {number} params.stepDown - Depth per pass
|
|
254
|
+
* @param {number} params.stepOver - Horizontal feed per pass
|
|
255
|
+
* @returns {Object} Toolpath object
|
|
256
|
+
*/
|
|
257
|
+
function generatePocket(params = {}) {
|
|
258
|
+
const {
|
|
259
|
+
region = [],
|
|
260
|
+
depth = 10,
|
|
261
|
+
toolId = 'flat-endmill-6mm',
|
|
262
|
+
stepDown = 5,
|
|
263
|
+
stepOver = 3,
|
|
264
|
+
} = params;
|
|
265
|
+
|
|
266
|
+
const tool = camState.toolLibrary.get(toolId);
|
|
267
|
+
if (!tool) throw new Error(`Tool ${toolId} not found`);
|
|
268
|
+
|
|
269
|
+
const id = `tp_pocket_${toolpathCounter.count++}`;
|
|
270
|
+
|
|
271
|
+
// Generate spiral/raster pattern
|
|
272
|
+
const passes = [];
|
|
273
|
+
const depthPasses = Math.ceil(depth / stepDown);
|
|
274
|
+
|
|
275
|
+
for (let dPass = 0; dPass < depthPasses; dPass++) {
|
|
276
|
+
const currentDepth = Math.min((dPass + 1) * stepDown, depth);
|
|
277
|
+
const spiralLines = generateSpiralPattern(region, stepOver);
|
|
278
|
+
passes.push({
|
|
279
|
+
depth: currentDepth,
|
|
280
|
+
lines: spiralLines,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const toolpath = {
|
|
285
|
+
id,
|
|
286
|
+
type: 'pocket',
|
|
287
|
+
tool,
|
|
288
|
+
region,
|
|
289
|
+
depth,
|
|
290
|
+
stepDown,
|
|
291
|
+
stepOver,
|
|
292
|
+
passes,
|
|
293
|
+
estimatedTime: calculateEstimatedTime(region, passes, tool),
|
|
294
|
+
status: 'generated',
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
camState.toolpaths.set(id, toolpath);
|
|
298
|
+
visualizeToolpath(toolpath);
|
|
299
|
+
|
|
300
|
+
console.log('[CAM] Pocket generated:', { id, passes: passes.length });
|
|
301
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: toolpath }));
|
|
302
|
+
|
|
303
|
+
return { id, type: 'pocket', passes: passes.length, estimatedTime: toolpath.estimatedTime };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Generate drilling toolpath
|
|
308
|
+
* @param {Object} params
|
|
309
|
+
* @param {THREE.Vector3[]} params.points - Drill points
|
|
310
|
+
* @param {number} params.depth - Drill depth
|
|
311
|
+
* @param {string} params.toolId - Tool ID
|
|
312
|
+
* @param {string} params.cycle - 'peck' | 'standard' | 'chip_break'
|
|
313
|
+
* @param {number} params.peckDepth - Peck depth for peck drilling
|
|
314
|
+
* @returns {Object} Toolpath object
|
|
315
|
+
*/
|
|
316
|
+
function generateDrilling(params = {}) {
|
|
317
|
+
const {
|
|
318
|
+
points = [],
|
|
319
|
+
depth = 10,
|
|
320
|
+
toolId = 'drill-5mm',
|
|
321
|
+
cycle = 'peck',
|
|
322
|
+
peckDepth = 5,
|
|
323
|
+
} = params;
|
|
324
|
+
|
|
325
|
+
const tool = camState.toolLibrary.get(toolId);
|
|
326
|
+
if (!tool) throw new Error(`Tool ${toolId} not found`);
|
|
327
|
+
|
|
328
|
+
const id = `tp_drill_${toolpathCounter.count++}`;
|
|
329
|
+
|
|
330
|
+
// Generate peck pattern if requested
|
|
331
|
+
let drillSequence = points;
|
|
332
|
+
if (cycle === 'peck') {
|
|
333
|
+
drillSequence = points.flatMap(pt => ({
|
|
334
|
+
point: pt,
|
|
335
|
+
pecks: Math.ceil(depth / peckDepth),
|
|
336
|
+
peckDepth,
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const toolpath = {
|
|
341
|
+
id,
|
|
342
|
+
type: 'drilling',
|
|
343
|
+
tool,
|
|
344
|
+
points,
|
|
345
|
+
depth,
|
|
346
|
+
cycle,
|
|
347
|
+
drillSequence,
|
|
348
|
+
estimatedTime: calculateDrillingTime(points, depth, tool),
|
|
349
|
+
status: 'generated',
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
camState.toolpaths.set(id, toolpath);
|
|
353
|
+
visualizeToolpath(toolpath);
|
|
354
|
+
|
|
355
|
+
console.log('[CAM] Drilling generated:', { id, points: points.length });
|
|
356
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: toolpath }));
|
|
357
|
+
|
|
358
|
+
return { id, type: 'drilling', points: points.length, estimatedTime: toolpath.estimatedTime };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Generate face milling toolpath
|
|
363
|
+
* @param {Object} params
|
|
364
|
+
* @param {THREE.Vector3[]} params.region - Face region
|
|
365
|
+
* @param {number} params.depth - Cut depth
|
|
366
|
+
* @param {string} params.toolId - Tool ID
|
|
367
|
+
* @param {number} params.stepOver - Feed per pass
|
|
368
|
+
* @returns {Object} Toolpath object
|
|
369
|
+
*/
|
|
370
|
+
function generateFace(params = {}) {
|
|
371
|
+
const {
|
|
372
|
+
region = [],
|
|
373
|
+
depth = 2,
|
|
374
|
+
toolId = 'face-mill-50mm',
|
|
375
|
+
stepOver = 10,
|
|
376
|
+
} = params;
|
|
377
|
+
|
|
378
|
+
const tool = camState.toolLibrary.get(toolId);
|
|
379
|
+
if (!tool) throw new Error(`Tool ${toolId} not found`);
|
|
380
|
+
|
|
381
|
+
const id = `tp_face_${toolpathCounter.count++}`;
|
|
382
|
+
|
|
383
|
+
// Generate raster pattern
|
|
384
|
+
const passes = generateRasterPattern(region, stepOver);
|
|
385
|
+
|
|
386
|
+
const toolpath = {
|
|
387
|
+
id,
|
|
388
|
+
type: 'face',
|
|
389
|
+
tool,
|
|
390
|
+
region,
|
|
391
|
+
depth,
|
|
392
|
+
stepOver,
|
|
393
|
+
passes,
|
|
394
|
+
estimatedTime: calculateEstimatedTime(region, passes, tool),
|
|
395
|
+
status: 'generated',
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
camState.toolpaths.set(id, toolpath);
|
|
399
|
+
visualizeToolpath(toolpath);
|
|
400
|
+
|
|
401
|
+
console.log('[CAM] Face milling generated:', { id, passes: passes.length });
|
|
402
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: toolpath }));
|
|
403
|
+
|
|
404
|
+
return { id, type: 'face', passes: passes.length, estimatedTime: toolpath.estimatedTime };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Generate adaptive clearing (high-speed roughing)
|
|
409
|
+
* @param {Object} params
|
|
410
|
+
* @param {THREE.Vector3[]} params.region - Region to clear
|
|
411
|
+
* @param {number} params.depth - Clear depth
|
|
412
|
+
* @param {string} params.toolId - Tool ID
|
|
413
|
+
* @param {number} params.stepOver - Horizontal feed
|
|
414
|
+
* @returns {Object} Toolpath object
|
|
415
|
+
*/
|
|
416
|
+
function generateAdaptiveClearing(params = {}) {
|
|
417
|
+
const {
|
|
418
|
+
region = [],
|
|
419
|
+
depth = 20,
|
|
420
|
+
toolId = 'flat-endmill-6mm',
|
|
421
|
+
stepOver = 4,
|
|
422
|
+
} = params;
|
|
423
|
+
|
|
424
|
+
const tool = camState.toolLibrary.get(toolId);
|
|
425
|
+
if (!tool) throw new Error(`Tool ${toolId} not found`);
|
|
426
|
+
|
|
427
|
+
const id = `tp_adaptive_${toolpathCounter.count++}`;
|
|
428
|
+
|
|
429
|
+
// Adaptive clearing: constant chip load, variable engagement
|
|
430
|
+
const passes = [];
|
|
431
|
+
const depthPass = Math.min(tool.diameter * 0.75, depth); // engage to 75% dia
|
|
432
|
+
const numPasses = Math.ceil(depth / depthPass);
|
|
433
|
+
|
|
434
|
+
for (let i = 0; i < numPasses; i++) {
|
|
435
|
+
const currentDepth = Math.min((i + 1) * depthPass, depth);
|
|
436
|
+
passes.push({
|
|
437
|
+
depth: currentDepth,
|
|
438
|
+
pattern: generateAdaptivePattern(region, stepOver, currentDepth),
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const toolpath = {
|
|
443
|
+
id,
|
|
444
|
+
type: 'adaptive_clearing',
|
|
445
|
+
tool,
|
|
446
|
+
region,
|
|
447
|
+
depth,
|
|
448
|
+
stepOver,
|
|
449
|
+
passes,
|
|
450
|
+
estimatedTime: calculateEstimatedTime(region, passes, tool),
|
|
451
|
+
status: 'generated',
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
camState.toolpaths.set(id, toolpath);
|
|
455
|
+
visualizeToolpath(toolpath);
|
|
456
|
+
|
|
457
|
+
console.log('[CAM] Adaptive clearing generated:', { id, passes: passes.length });
|
|
458
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: toolpath }));
|
|
459
|
+
|
|
460
|
+
return { id, type: 'adaptive_clearing', passes: passes.length, estimatedTime: toolpath.estimatedTime };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Generate parallel finishing toolpath
|
|
465
|
+
* @param {Object} params
|
|
466
|
+
* @param {THREE.BufferGeometry} params.geometry - Surface geometry
|
|
467
|
+
* @param {string} params.toolId - Tool ID
|
|
468
|
+
* @param {number} params.stepOver - Horizontal step-over
|
|
469
|
+
* @param {string} params.direction - 'x' | 'y' | 'diagonal'
|
|
470
|
+
* @returns {Object} Toolpath object
|
|
471
|
+
*/
|
|
472
|
+
function generateParallel(params = {}) {
|
|
473
|
+
const {
|
|
474
|
+
geometry = null,
|
|
475
|
+
toolId = 'ball-endmill-3mm',
|
|
476
|
+
stepOver = 2,
|
|
477
|
+
direction = 'x',
|
|
478
|
+
} = params;
|
|
479
|
+
|
|
480
|
+
const tool = camState.toolLibrary.get(toolId);
|
|
481
|
+
if (!tool) throw new Error(`Tool ${toolId} not found`);
|
|
482
|
+
|
|
483
|
+
const id = `tp_parallel_${toolpathCounter.count++}`;
|
|
484
|
+
|
|
485
|
+
const passes = generateParallelPasses(geometry, stepOver, direction);
|
|
486
|
+
|
|
487
|
+
const toolpath = {
|
|
488
|
+
id,
|
|
489
|
+
type: 'parallel',
|
|
490
|
+
tool,
|
|
491
|
+
geometry,
|
|
492
|
+
stepOver,
|
|
493
|
+
direction,
|
|
494
|
+
passes,
|
|
495
|
+
estimatedTime: calculateEstimatedTime([], passes, tool),
|
|
496
|
+
status: 'generated',
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
camState.toolpaths.set(id, toolpath);
|
|
500
|
+
visualizeToolpath(toolpath);
|
|
501
|
+
|
|
502
|
+
console.log('[CAM] Parallel finishing generated:', { id, passes: passes.length });
|
|
503
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: toolpath }));
|
|
504
|
+
|
|
505
|
+
return { id, type: 'parallel', passes: passes.length, estimatedTime: toolpath.estimatedTime };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Generate FDM slicing (additive/3D printing)
|
|
510
|
+
* @param {Object} params
|
|
511
|
+
* @param {THREE.BufferGeometry} params.geometry - Part geometry
|
|
512
|
+
* @param {number} params.layerHeight - Layer height
|
|
513
|
+
* @param {number} params.nozzleWidth - Nozzle width
|
|
514
|
+
* @param {string} params.infillPattern - 'grid' | 'honeycomb' | 'gyroid'
|
|
515
|
+
* @param {number} params.infillDensity - 0-1 (0.2 = 20%)
|
|
516
|
+
* @returns {Object} Sliced object
|
|
517
|
+
*/
|
|
518
|
+
function generateFDMSlicing(params = {}) {
|
|
519
|
+
const {
|
|
520
|
+
geometry = null,
|
|
521
|
+
layerHeight = 0.2,
|
|
522
|
+
nozzleWidth = 0.4,
|
|
523
|
+
infillPattern = 'grid',
|
|
524
|
+
infillDensity = 0.2,
|
|
525
|
+
} = params;
|
|
526
|
+
|
|
527
|
+
const id = `fdm_${toolpathCounter.count++}`;
|
|
528
|
+
|
|
529
|
+
// Compute bounding box
|
|
530
|
+
const bbox = new THREE.Box3().setFromBufferGeometry(geometry);
|
|
531
|
+
const height = bbox.max.z - bbox.min.z;
|
|
532
|
+
const layerCount = Math.ceil(height / layerHeight);
|
|
533
|
+
|
|
534
|
+
// Generate layers
|
|
535
|
+
const layers = [];
|
|
536
|
+
for (let i = 0; i < layerCount; i++) {
|
|
537
|
+
const z = bbox.min.z + i * layerHeight;
|
|
538
|
+
layers.push({
|
|
539
|
+
z,
|
|
540
|
+
index: i,
|
|
541
|
+
perimeter: generatePerimeterPaths(geometry, z),
|
|
542
|
+
infill: generateInfillPattern(geometry, z, infillPattern, infillDensity),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const slicing = {
|
|
547
|
+
id,
|
|
548
|
+
type: 'fdm_slicing',
|
|
549
|
+
geometry,
|
|
550
|
+
layerHeight,
|
|
551
|
+
nozzleWidth,
|
|
552
|
+
infillPattern,
|
|
553
|
+
infillDensity,
|
|
554
|
+
layers,
|
|
555
|
+
estimatedTime: layerCount * 2, // ~2min per layer estimate
|
|
556
|
+
filamentLength: estimateFilamentLength(layers),
|
|
557
|
+
filamentWeight: 0, // would need material density
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
camState.toolpaths.set(id, slicing);
|
|
561
|
+
console.log('[CAM] FDM slicing generated:', { id, layers: layerCount });
|
|
562
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: slicing }));
|
|
563
|
+
|
|
564
|
+
return { id, type: 'fdm_slicing', layers: layerCount, estimatedTime: slicing.estimatedTime };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Generate G-code from toolpath
|
|
569
|
+
* @param {string} toolpathId - Toolpath ID
|
|
570
|
+
* @param {string} dialect - 'fanuc' | 'linuxcnc' | 'grbl' | 'marlin'
|
|
571
|
+
* @returns {string} G-code text
|
|
572
|
+
*/
|
|
573
|
+
function generateGCode(toolpathId, dialect = 'grbl') {
|
|
574
|
+
const toolpath = camState.toolpaths.get(toolpathId);
|
|
575
|
+
if (!toolpath) throw new Error(`Toolpath ${toolpathId} not found`);
|
|
576
|
+
|
|
577
|
+
let gcode = '';
|
|
578
|
+
|
|
579
|
+
// Header
|
|
580
|
+
gcode += '; Generated by cycleCAD CAM\n';
|
|
581
|
+
gcode += `; Machine: ${dialect}\n`;
|
|
582
|
+
gcode += `; Generated: ${new Date().toISOString()}\n`;
|
|
583
|
+
gcode += `; Tool: ${toolpath.tool.name}\n`;
|
|
584
|
+
gcode += `; Operation: ${toolpath.type}\n`;
|
|
585
|
+
gcode += ';\n';
|
|
586
|
+
|
|
587
|
+
// Unit setup
|
|
588
|
+
if (dialect === 'grbl' || dialect === 'linuxcnc') {
|
|
589
|
+
gcode += 'G90 G21\n'; // absolute, metric
|
|
590
|
+
} else if (dialect === 'fanuc') {
|
|
591
|
+
gcode += 'G90 G21\n'; // absolute, metric
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Spindle on
|
|
595
|
+
gcode += `S${toolpath.tool.rpm} M3\n`;
|
|
596
|
+
|
|
597
|
+
// Generate moves based on toolpath type
|
|
598
|
+
if (toolpath.type === 'drilling') {
|
|
599
|
+
gcode += generateDrillingGCode(toolpath, dialect);
|
|
600
|
+
} else if (toolpath.type === 'contour_2d') {
|
|
601
|
+
gcode += generateContourGCode(toolpath, dialect);
|
|
602
|
+
} else if (toolpath.type === 'pocket' || toolpath.type === 'adaptive_clearing') {
|
|
603
|
+
gcode += generatePocketGCode(toolpath, dialect);
|
|
604
|
+
} else if (toolpath.type === 'face') {
|
|
605
|
+
gcode += generateFaceGCode(toolpath, dialect);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// End
|
|
609
|
+
gcode += '\nM5\n'; // Spindle off
|
|
610
|
+
gcode += 'M30\n'; // Program end
|
|
611
|
+
|
|
612
|
+
camState.gcode = gcode;
|
|
613
|
+
|
|
614
|
+
console.log('[CAM] G-code generated:', { length: gcode.length, lines: gcode.split('\n').length - 1 });
|
|
615
|
+
window.dispatchEvent(new CustomEvent('cam:gcodeGenerated', { detail: { gcode, length: gcode.length } }));
|
|
616
|
+
|
|
617
|
+
return gcode;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Simulate toolpath motion in 3D
|
|
622
|
+
* @param {string} toolpathId - Toolpath ID
|
|
623
|
+
* @param {number} speed - Playback speed (1.0 = real-time, 10 = 10x faster)
|
|
624
|
+
* @returns {Object} Simulation controller
|
|
625
|
+
*/
|
|
626
|
+
function simulateToolpath(toolpathId, speed = 1.0) {
|
|
627
|
+
const toolpath = camState.toolpaths.get(toolpathId);
|
|
628
|
+
if (!toolpath) throw new Error(`Toolpath ${toolpathId} not found`);
|
|
629
|
+
|
|
630
|
+
const tool = toolpath.tool;
|
|
631
|
+
const simulation = {
|
|
632
|
+
toolpathId,
|
|
633
|
+
isRunning: false,
|
|
634
|
+
progress: 0,
|
|
635
|
+
startTime: 0,
|
|
636
|
+
totalTime: toolpath.estimatedTime * 1000 / speed,
|
|
637
|
+
|
|
638
|
+
// Create tool mesh
|
|
639
|
+
toolMesh: createToolMesh(tool),
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
if (viewport?.scene) {
|
|
643
|
+
viewport.scene.add(simulation.toolMesh);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Animate tool
|
|
647
|
+
const animate = () => {
|
|
648
|
+
if (!simulation.isRunning) return;
|
|
649
|
+
|
|
650
|
+
const elapsed = Date.now() - simulation.startTime;
|
|
651
|
+
simulation.progress = Math.min(elapsed / simulation.totalTime, 1.0);
|
|
652
|
+
|
|
653
|
+
// Position tool along toolpath
|
|
654
|
+
const pathPoints = extractPathPoints(toolpath);
|
|
655
|
+
if (pathPoints.length > 0) {
|
|
656
|
+
const pointIndex = Math.floor(simulation.progress * pathPoints.length);
|
|
657
|
+
const point = pathPoints[Math.min(pointIndex, pathPoints.length - 1)];
|
|
658
|
+
simulation.toolMesh.position.copy(point);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (simulation.progress < 1.0) {
|
|
662
|
+
requestAnimationFrame(animate);
|
|
663
|
+
} else {
|
|
664
|
+
simulation.isRunning = false;
|
|
665
|
+
window.dispatchEvent(new CustomEvent('cam:simulationComplete', { detail: simulation }));
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
return {
|
|
670
|
+
start: () => {
|
|
671
|
+
simulation.isRunning = true;
|
|
672
|
+
simulation.startTime = Date.now();
|
|
673
|
+
animate();
|
|
674
|
+
},
|
|
675
|
+
stop: () => {
|
|
676
|
+
simulation.isRunning = false;
|
|
677
|
+
},
|
|
678
|
+
pause: () => {
|
|
679
|
+
// In real implementation, would handle pause
|
|
680
|
+
},
|
|
681
|
+
getProgress: () => simulation.progress,
|
|
682
|
+
getMesh: () => simulation.toolMesh,
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Set active tool
|
|
688
|
+
* @param {string} toolId - Tool ID from library
|
|
689
|
+
*/
|
|
690
|
+
function setTool(toolId) {
|
|
691
|
+
const tool = camState.toolLibrary.get(toolId);
|
|
692
|
+
if (!tool) throw new Error(`Tool ${toolId} not found`);
|
|
693
|
+
camState.selectedTool = tool;
|
|
694
|
+
console.log('[CAM] Tool selected:', tool.name);
|
|
695
|
+
return tool;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Add custom tool to library
|
|
700
|
+
* @param {Object} toolDef - Tool definition
|
|
701
|
+
* @returns {Object} Added tool
|
|
702
|
+
*/
|
|
703
|
+
function addTool(toolDef) {
|
|
704
|
+
const id = toolDef.id || `custom_tool_${Date.now()}`;
|
|
705
|
+
const tool = { id, ...toolDef };
|
|
706
|
+
camState.toolLibrary.set(id, tool);
|
|
707
|
+
console.log('[CAM] Tool added:', tool.name);
|
|
708
|
+
return tool;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* List all tools in library
|
|
713
|
+
*/
|
|
714
|
+
function listTools() {
|
|
715
|
+
return Array.from(camState.toolLibrary.values());
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Export G-code to file
|
|
720
|
+
* @param {string} filename - Filename
|
|
721
|
+
* @param {string} content - G-code content
|
|
722
|
+
*/
|
|
723
|
+
function exportGCode(filename, content) {
|
|
724
|
+
const blob = new Blob([content || camState.gcode], { type: 'text/plain' });
|
|
725
|
+
const url = URL.createObjectURL(blob);
|
|
726
|
+
const a = document.createElement('a');
|
|
727
|
+
a.href = url;
|
|
728
|
+
a.download = filename || 'toolpath.nc';
|
|
729
|
+
a.click();
|
|
730
|
+
URL.revokeObjectURL(url);
|
|
731
|
+
|
|
732
|
+
console.log('[CAM] G-code exported:', filename);
|
|
733
|
+
window.dispatchEvent(new CustomEvent('cam:gcodeExported', { detail: { filename } }));
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* List all toolpaths
|
|
738
|
+
*/
|
|
739
|
+
function listToolpaths() {
|
|
740
|
+
return Array.from(camState.toolpaths.entries()).map(([id, tp]) => ({
|
|
741
|
+
id,
|
|
742
|
+
type: tp.type,
|
|
743
|
+
tool: tp.tool.name,
|
|
744
|
+
estimatedTime: tp.estimatedTime,
|
|
745
|
+
status: tp.status,
|
|
746
|
+
}));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// --- Helper Functions ---
|
|
750
|
+
|
|
751
|
+
function createStockGeometry(type, dimensions) {
|
|
752
|
+
if (type === 'box') {
|
|
753
|
+
return new THREE.BoxGeometry(dimensions.x, dimensions.y, dimensions.z);
|
|
754
|
+
} else if (type === 'cylinder') {
|
|
755
|
+
return new THREE.CylinderGeometry(dimensions.diameter / 2, dimensions.diameter / 2, dimensions.height, 32);
|
|
756
|
+
}
|
|
757
|
+
return new THREE.BoxGeometry(100, 100, 50);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function offsetProfile(profile, offset) {
|
|
761
|
+
// Simple offset (in real impl, use 2D offset library)
|
|
762
|
+
return profile.map(pt => new THREE.Vector3(pt.x + offset, pt.y, pt.z));
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function generateSpiralPattern(region, stepOver) {
|
|
766
|
+
// Generate spiral toolpath
|
|
767
|
+
const lines = [];
|
|
768
|
+
for (let r = 0; r < 10; r += stepOver) {
|
|
769
|
+
lines.push({ radius: r, points: [] });
|
|
770
|
+
}
|
|
771
|
+
return lines;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function generateRasterPattern(region, stepOver) {
|
|
775
|
+
// Generate back-and-forth raster
|
|
776
|
+
const passes = [];
|
|
777
|
+
for (let x = 0; x < 100; x += stepOver) {
|
|
778
|
+
passes.push({ x, path: [] });
|
|
779
|
+
}
|
|
780
|
+
return passes;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function generateAdaptivePattern(region, stepOver, depth) {
|
|
784
|
+
// Adaptive cutting pattern with variable engagement
|
|
785
|
+
return { pattern: 'adaptive', depth };
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function generateParallelPasses(geometry, stepOver, direction) {
|
|
789
|
+
const passes = [];
|
|
790
|
+
for (let i = 0; i < 50; i += stepOver) {
|
|
791
|
+
passes.push({ offset: i, path: [] });
|
|
792
|
+
}
|
|
793
|
+
return passes;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function generatePerimeterPaths(geometry, z) {
|
|
797
|
+
// Generate perimeter toolpath at Z height
|
|
798
|
+
return [];
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function generateInfillPattern(geometry, z, pattern, density) {
|
|
802
|
+
// Generate infill pattern (grid/honeycomb/gyroid)
|
|
803
|
+
return [];
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function estimateFilamentLength(layers) {
|
|
807
|
+
// Estimate total filament length for FDM
|
|
808
|
+
return layers.length * 10; // placeholder
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function calculateEstimatedTime(region, passes, tool) {
|
|
812
|
+
// Very rough time estimate
|
|
813
|
+
const passLength = region.length || 50; // mm
|
|
814
|
+
const speedMMMin = tool.feed || 1000;
|
|
815
|
+
const totalDistance = passes.length * passLength;
|
|
816
|
+
return (totalDistance / speedMMMin) * 60; // seconds
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function calculateDrillingTime(points, depth, tool) {
|
|
820
|
+
// Drilling time = (tool penetration rate) * depth * number of points
|
|
821
|
+
const penetrationRate = 10; // mm/min
|
|
822
|
+
return (points.length * depth / penetrationRate) * 60; // seconds
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function extractPathPoints(toolpath) {
|
|
826
|
+
// Extract all motion points from toolpath
|
|
827
|
+
const points = [];
|
|
828
|
+
if (toolpath.passes) {
|
|
829
|
+
toolpath.passes.forEach(pass => {
|
|
830
|
+
if (pass.points) points.push(...pass.points);
|
|
831
|
+
if (pass.path) points.push(...pass.path);
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
return points;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function createToolMesh(tool) {
|
|
838
|
+
// Create 3D mesh representing the tool
|
|
839
|
+
let geom;
|
|
840
|
+
if (tool.type === 'ball') {
|
|
841
|
+
geom = new THREE.SphereGeometry(tool.diameter / 2, 16, 16);
|
|
842
|
+
} else if (tool.type === 'drill') {
|
|
843
|
+
geom = new THREE.ConeGeometry(tool.diameter / 2, tool.diameter, 8);
|
|
844
|
+
} else {
|
|
845
|
+
geom = new THREE.CylinderGeometry(tool.diameter / 2, tool.diameter / 2, tool.fluteLength, 16);
|
|
846
|
+
}
|
|
847
|
+
const mat = new THREE.MeshPhongMaterial({ color: 0xffaa00 });
|
|
848
|
+
return new THREE.Mesh(geom, mat);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function visualizeToolpath(toolpath) {
|
|
852
|
+
// Draw toolpath as lines in viewport
|
|
853
|
+
if (!viewport?.scene) return;
|
|
854
|
+
|
|
855
|
+
const geometry = new THREE.BufferGeometry();
|
|
856
|
+
const points = extractPathPoints(toolpath);
|
|
857
|
+
if (points.length > 0) {
|
|
858
|
+
geometry.setFromPoints(points);
|
|
859
|
+
const line = new THREE.LineSegments(
|
|
860
|
+
geometry,
|
|
861
|
+
new THREE.LineBasicMaterial({ color: 0x00ff00, linewidth: 2 })
|
|
862
|
+
);
|
|
863
|
+
line.name = `toolpath_${toolpath.id}`;
|
|
864
|
+
viewport.scene.add(line);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function generateDrillingGCode(toolpath, dialect) {
|
|
869
|
+
let gcode = '';
|
|
870
|
+
toolpath.drillSequence.forEach((drill, i) => {
|
|
871
|
+
gcode += `G0 X${drill.point.x.toFixed(3)} Y${drill.point.y.toFixed(3)}\n`;
|
|
872
|
+
gcode += `G0 Z${camState.setupParams.safeHeight}\n`;
|
|
873
|
+
gcode += `G1 Z${-drill.depth} F${toolpath.tool.feed}\n`;
|
|
874
|
+
if (toolpath.type.includes('peck') && drill.pecks > 1) {
|
|
875
|
+
for (let p = 0; p < drill.pecks; p++) {
|
|
876
|
+
gcode += `G0 Z${camState.setupParams.retractHeight}\n`;
|
|
877
|
+
gcode += `G1 Z${-Math.min((p + 1) * drill.peckDepth, drill.depth)} F${toolpath.tool.feed}\n`;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
gcode += `G0 Z${camState.setupParams.safeHeight}\n`;
|
|
881
|
+
});
|
|
882
|
+
return gcode;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function generateContourGCode(toolpath, dialect) {
|
|
886
|
+
let gcode = '';
|
|
887
|
+
toolpath.passes.forEach((pass, i) => {
|
|
888
|
+
gcode += `\n; Pass ${i + 1} - Depth ${pass.depth}\n`;
|
|
889
|
+
pass.points.forEach(pt => {
|
|
890
|
+
gcode += `G0 X${pt.x.toFixed(3)} Y${pt.y.toFixed(3)}\n`;
|
|
891
|
+
gcode += `G1 Z${-pass.depth} F${toolpath.tool.feed}\n`;
|
|
892
|
+
});
|
|
893
|
+
gcode += `G0 Z${camState.setupParams.safeHeight}\n`;
|
|
894
|
+
});
|
|
895
|
+
return gcode;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function generatePocketGCode(toolpath, dialect) {
|
|
899
|
+
let gcode = '';
|
|
900
|
+
toolpath.passes.forEach((pass, i) => {
|
|
901
|
+
gcode += `\n; Pass ${i + 1} - Depth ${pass.depth}\n`;
|
|
902
|
+
if (pass.lines) {
|
|
903
|
+
pass.lines.forEach(line => {
|
|
904
|
+
gcode += `G1 X${line.x.toFixed(3)} Y${line.y.toFixed(3)} Z${-pass.depth} F${toolpath.tool.feed}\n`;
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
gcode += `G0 Z${camState.setupParams.safeHeight}\n`;
|
|
909
|
+
return gcode;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function generateFaceGCode(toolpath, dialect) {
|
|
913
|
+
let gcode = '';
|
|
914
|
+
toolpath.passes.forEach((pass, i) => {
|
|
915
|
+
gcode += `\n; Pass ${i + 1}\n`;
|
|
916
|
+
gcode += `G0 X${pass.x} Y0\n`;
|
|
917
|
+
gcode += `G1 Y100 F${toolpath.tool.feed}\n`;
|
|
918
|
+
gcode += `G0 Y0\n`;
|
|
919
|
+
});
|
|
920
|
+
gcode += `G0 Z${camState.setupParams.safeHeight}\n`;
|
|
921
|
+
return gcode;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// --- Command Registration ---
|
|
925
|
+
|
|
926
|
+
function registerCommands() {
|
|
927
|
+
const api = window.cycleCAD?.api || {};
|
|
928
|
+
|
|
929
|
+
api.cam = {
|
|
930
|
+
// Work setup
|
|
931
|
+
setup: setupWorkCoordinateSystem,
|
|
932
|
+
|
|
933
|
+
// 2D Operations
|
|
934
|
+
contour2d: generateContour2D,
|
|
935
|
+
pocket: generatePocket,
|
|
936
|
+
drill: generateDrilling,
|
|
937
|
+
face: generateFace,
|
|
938
|
+
|
|
939
|
+
// 3D & Advanced
|
|
940
|
+
adaptive: generateAdaptiveClearing,
|
|
941
|
+
parallel: generateParallel,
|
|
942
|
+
multiaxis: generateMultiAxisContour,
|
|
943
|
+
turning: generateTurning,
|
|
944
|
+
threading: generateThreading,
|
|
945
|
+
|
|
946
|
+
// Additive
|
|
947
|
+
slice: generateFDMSlicing,
|
|
948
|
+
supports: generateSupports,
|
|
949
|
+
|
|
950
|
+
// Code & Simulation
|
|
951
|
+
generateGCode,
|
|
952
|
+
simulate: simulateToolpath,
|
|
953
|
+
collision: checkCollisions,
|
|
954
|
+
gouges: detectGouges,
|
|
955
|
+
stockPreview: previewStockRemoval,
|
|
956
|
+
|
|
957
|
+
// Tool Management
|
|
958
|
+
setTool,
|
|
959
|
+
addTool,
|
|
960
|
+
listTools,
|
|
961
|
+
setPost: setPostProcessor,
|
|
962
|
+
|
|
963
|
+
// File Operations
|
|
964
|
+
listToolpaths,
|
|
965
|
+
exportGCode,
|
|
966
|
+
|
|
967
|
+
// Utilities
|
|
968
|
+
getState: () => camState,
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
window.cycleCAD = window.cycleCAD || {};
|
|
972
|
+
window.cycleCAD.api = api;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// ============================================================================
|
|
976
|
+
// HELP ENTRIES
|
|
977
|
+
// ============================================================================
|
|
978
|
+
|
|
979
|
+
const helpEntries = [
|
|
980
|
+
{
|
|
981
|
+
id: 'cam-setup',
|
|
982
|
+
title: 'Work Coordinate System Setup',
|
|
983
|
+
category: 'CAM',
|
|
984
|
+
description: 'Define stock material and machine origin',
|
|
985
|
+
shortcut: 'C, W',
|
|
986
|
+
content: `
|
|
987
|
+
Set up your manufacturing workspace:
|
|
988
|
+
1. Click "Define WCS" button
|
|
989
|
+
2. Choose stock type: Box or Cylinder
|
|
990
|
+
3. Set dimensions (X, Y, Z) or (diameter, height)
|
|
991
|
+
4. Confirm machine origin at (0,0,0)
|
|
992
|
+
|
|
993
|
+
The WCS defines where cuts are made relative to your stock.
|
|
994
|
+
`
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
id: 'cam-2d-milling',
|
|
998
|
+
title: '2D Milling Operations',
|
|
999
|
+
category: 'CAM',
|
|
1000
|
+
description: 'Profile, pocket, drill, and face operations',
|
|
1001
|
+
shortcut: 'C, 2',
|
|
1002
|
+
content: `
|
|
1003
|
+
Generate 2D toolpaths:
|
|
1004
|
+
- **Contour 2D**: Cut profiles on flat surfaces (inside or outside)
|
|
1005
|
+
- **Pocket**: Clear enclosed regions
|
|
1006
|
+
- **Drill**: Hole drilling with peck cycles
|
|
1007
|
+
- **Face**: Flatten surfaces with face mills
|
|
1008
|
+
|
|
1009
|
+
Each operation can use multiple passes with step-over/step-down.
|
|
1010
|
+
`
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
id: 'cam-3d-milling',
|
|
1014
|
+
title: '3D Milling Operations',
|
|
1015
|
+
category: 'CAM',
|
|
1016
|
+
description: 'Adaptive clearing, parallel finishing, ball-end contouring',
|
|
1017
|
+
shortcut: 'C, 3',
|
|
1018
|
+
content: `
|
|
1019
|
+
Advanced 3D cutting strategies:
|
|
1020
|
+
- **Adaptive Clearing**: High-speed roughing with constant chip load
|
|
1021
|
+
- **Parallel**: Finishing with fine step-over on curved surfaces
|
|
1022
|
+
- **4/5-Axis**: Multi-axis contours for complex geometry
|
|
1023
|
+
|
|
1024
|
+
Use ball-end mills for smooth, accurate surface finishes.
|
|
1025
|
+
`
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
id: 'cam-turning',
|
|
1029
|
+
title: 'Turning Operations (Lathe)',
|
|
1030
|
+
category: 'CAM',
|
|
1031
|
+
description: 'Generate lathe toolpaths: roughing, finishing, threading',
|
|
1032
|
+
shortcut: 'C, T',
|
|
1033
|
+
content: `
|
|
1034
|
+
Lathe/turning operations:
|
|
1035
|
+
- **Roughing**: Quick material removal
|
|
1036
|
+
- **Finishing**: Fine surface finish
|
|
1037
|
+
- **Threading**: Helical thread cutting with pitch control
|
|
1038
|
+
|
|
1039
|
+
Requires cylindrical stock and turning tool definition.
|
|
1040
|
+
`
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
id: 'cam-multiaxis',
|
|
1044
|
+
title: 'Multi-Axis Contouring (4/5-Axis)',
|
|
1045
|
+
category: 'CAM',
|
|
1046
|
+
description: 'Machine complex 3D surfaces with rotary axes',
|
|
1047
|
+
shortcut: 'C, 5',
|
|
1048
|
+
content: `
|
|
1049
|
+
Advanced multi-axis machining:
|
|
1050
|
+
- **4-Axis**: Add rotary A/B axis for impeller blades, complex holes
|
|
1051
|
+
- **5-Axis**: Full 3D contouring with simultaneous rotation
|
|
1052
|
+
|
|
1053
|
+
Minimizes tool changes and improves surface quality on complex parts.
|
|
1054
|
+
`
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
id: 'cam-collision',
|
|
1058
|
+
title: 'Collision Detection',
|
|
1059
|
+
category: 'CAM',
|
|
1060
|
+
description: 'Check for tool/holder/fixture interference',
|
|
1061
|
+
shortcut: 'C, C',
|
|
1062
|
+
content: `
|
|
1063
|
+
Prevent machine crashes:
|
|
1064
|
+
1. Select a toolpath
|
|
1065
|
+
2. Click "Check Collision"
|
|
1066
|
+
3. Simulator detects interferences with:
|
|
1067
|
+
- Tool holder
|
|
1068
|
+
- Fixture/vise
|
|
1069
|
+
- Machine table
|
|
1070
|
+
|
|
1071
|
+
Fix collisions by adjusting clearance or tool orientation.
|
|
1072
|
+
`
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
id: 'cam-gouges',
|
|
1076
|
+
title: 'Gouge Detection',
|
|
1077
|
+
category: 'CAM',
|
|
1078
|
+
description: 'Find unexpected tool-material contact',
|
|
1079
|
+
shortcut: 'C, G',
|
|
1080
|
+
content: `
|
|
1081
|
+
Catch toolpath errors:
|
|
1082
|
+
- Detects incorrect tool engagement angles
|
|
1083
|
+
- Identifies feed rate issues
|
|
1084
|
+
- Checks for stepover/stepdown violations
|
|
1085
|
+
|
|
1086
|
+
Red flags indicate dangerous conditions that may damage tools or parts.
|
|
1087
|
+
`
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
id: 'cam-fdm',
|
|
1091
|
+
title: 'FDM 3D Printing Setup',
|
|
1092
|
+
category: 'CAM',
|
|
1093
|
+
description: 'Slice models and generate print paths',
|
|
1094
|
+
shortcut: 'C, F',
|
|
1095
|
+
content: `
|
|
1096
|
+
Prepare models for 3D printing:
|
|
1097
|
+
1. Select geometry
|
|
1098
|
+
2. Click "FDM Slice"
|
|
1099
|
+
3. Set layer height (0.1-0.4mm)
|
|
1100
|
+
4. Choose infill: grid, honeycomb, or gyroid
|
|
1101
|
+
5. Generate support material if needed
|
|
1102
|
+
|
|
1103
|
+
Optimizes print speed, strength, and material usage.
|
|
1104
|
+
`
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
id: 'cam-supports',
|
|
1108
|
+
title: 'Support Generation',
|
|
1109
|
+
category: 'CAM',
|
|
1110
|
+
description: 'Auto-generate support structures for overhangs',
|
|
1111
|
+
shortcut: 'C, Shift+S',
|
|
1112
|
+
content: `
|
|
1113
|
+
Support material strategies:
|
|
1114
|
+
- **Linear**: Simple grid pattern (fast, uses more material)
|
|
1115
|
+
- **Tree**: Optimized structure (slower gen, less waste)
|
|
1116
|
+
|
|
1117
|
+
Configure:
|
|
1118
|
+
- Density (10-50%)
|
|
1119
|
+
- Angle threshold for overhangs
|
|
1120
|
+
- Support material type
|
|
1121
|
+
`
|
|
1122
|
+
},
|
|
1123
|
+
{
|
|
1124
|
+
id: 'cam-gcode',
|
|
1125
|
+
title: 'G-Code Generation',
|
|
1126
|
+
category: 'CAM',
|
|
1127
|
+
description: 'Export CNC machine code',
|
|
1128
|
+
shortcut: 'C, Ctrl+G',
|
|
1129
|
+
content: `
|
|
1130
|
+
Generate and export G-code:
|
|
1131
|
+
1. Create toolpaths (contour, pocket, drill, etc.)
|
|
1132
|
+
2. Set post processor (GRBL, FANUC, HAAS, Marlin, etc.)
|
|
1133
|
+
3. Click "Generate G-Code"
|
|
1134
|
+
4. Export as .nc or .gcode file
|
|
1135
|
+
|
|
1136
|
+
Each post processor formats code for specific machine controllers.
|
|
1137
|
+
`
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
id: 'cam-simulate',
|
|
1141
|
+
title: 'Toolpath Simulation',
|
|
1142
|
+
category: 'CAM',
|
|
1143
|
+
description: 'Visualize and preview tool motion',
|
|
1144
|
+
shortcut: 'C, S',
|
|
1145
|
+
content: `
|
|
1146
|
+
Preview toolpath execution:
|
|
1147
|
+
1. Select a generated toolpath
|
|
1148
|
+
2. Click "Simulate"
|
|
1149
|
+
3. Watch tool move through cuts in 3D
|
|
1150
|
+
4. Speed control: 1x (real-time), 10x (fast preview)
|
|
1151
|
+
5. Stop at any point to inspect
|
|
1152
|
+
|
|
1153
|
+
Great for catching errors before running on real machine.
|
|
1154
|
+
`
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
id: 'cam-tools',
|
|
1158
|
+
title: 'Tool Library',
|
|
1159
|
+
category: 'CAM',
|
|
1160
|
+
description: 'Manage cutting tools and insert parameters',
|
|
1161
|
+
shortcut: 'C, L',
|
|
1162
|
+
content: `
|
|
1163
|
+
Tool management:
|
|
1164
|
+
- Pre-loaded library: 30+ standard tools
|
|
1165
|
+
- View specs: diameter, flute length, material, cost
|
|
1166
|
+
- Add custom tools: define geometry, feeds, speeds
|
|
1167
|
+
- Select tool per operation
|
|
1168
|
+
|
|
1169
|
+
Proper tool selection crucial for speed, finish, and tool life.
|
|
1170
|
+
`
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
id: 'cam-setup-params',
|
|
1174
|
+
title: 'Setup Parameters',
|
|
1175
|
+
category: 'CAM',
|
|
1176
|
+
description: 'Configure machine, feeds, and safety heights',
|
|
1177
|
+
shortcut: 'C, Shift+P',
|
|
1178
|
+
content: `
|
|
1179
|
+
Machine configuration:
|
|
1180
|
+
- Rapid rate: maximum travel speed
|
|
1181
|
+
- Safe height: Z clearance for rapid moves
|
|
1182
|
+
- Retract height: clearance for tool changes
|
|
1183
|
+
- Spindle direction: CW or CCW
|
|
1184
|
+
- Feed units: inch/min or mm/min
|
|
1185
|
+
|
|
1186
|
+
Applied to all generated toolpaths globally.
|
|
1187
|
+
`
|
|
1188
|
+
}
|
|
1189
|
+
];
|
|
1190
|
+
|
|
1191
|
+
// --- Keyboard Shortcuts ---
|
|
1192
|
+
|
|
1193
|
+
function handleKeyboard(evt) {
|
|
1194
|
+
if (evt.ctrlKey && evt.shiftKey && evt.key === 'M') {
|
|
1195
|
+
console.log('[CAM] Active toolpaths:', listToolpaths());
|
|
1196
|
+
evt.preventDefault();
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// ============================================================================
|
|
1201
|
+
// TURNING OPERATIONS (Fusion 360 Parity)
|
|
1202
|
+
// ============================================================================
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Generate turning (lathe) operation
|
|
1206
|
+
* @param {Object} params Configuration
|
|
1207
|
+
* @returns {Object} Toolpath
|
|
1208
|
+
*/
|
|
1209
|
+
function generateTurning(params = {}) {
|
|
1210
|
+
const {
|
|
1211
|
+
type = 'roughing', // 'roughing' | 'finishing'
|
|
1212
|
+
depth = 5,
|
|
1213
|
+
feedRate = 0.2,
|
|
1214
|
+
toolId = 'turning-tool',
|
|
1215
|
+
} = params;
|
|
1216
|
+
|
|
1217
|
+
const id = `tp_turning_${toolpathCounter.count++}`;
|
|
1218
|
+
const tool = camState.toolLibrary.get(toolId) || { name: 'Turning Tool', feed: feedRate * 1000 };
|
|
1219
|
+
|
|
1220
|
+
const toolpath = {
|
|
1221
|
+
id,
|
|
1222
|
+
type: 'turning',
|
|
1223
|
+
tool,
|
|
1224
|
+
depth,
|
|
1225
|
+
feedRate,
|
|
1226
|
+
passes: Math.ceil(depth / 2),
|
|
1227
|
+
estimatedTime: (depth / feedRate) * 60,
|
|
1228
|
+
status: 'generated',
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
camState.toolpaths.set(id, toolpath);
|
|
1232
|
+
visualizeToolpath(toolpath);
|
|
1233
|
+
|
|
1234
|
+
console.log('[CAM] Turning operation generated:', { id, type, depth });
|
|
1235
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: toolpath }));
|
|
1236
|
+
|
|
1237
|
+
return { id, type: 'turning', subtype: type, estimatedTime: toolpath.estimatedTime };
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* Generate threading (turning thread operation)
|
|
1242
|
+
* @param {Object} params Configuration
|
|
1243
|
+
* @returns {Object} Toolpath
|
|
1244
|
+
*/
|
|
1245
|
+
function generateThreading(params = {}) {
|
|
1246
|
+
const {
|
|
1247
|
+
pitch = 1.5,
|
|
1248
|
+
depth = 1.0,
|
|
1249
|
+
diameter = 10,
|
|
1250
|
+
toolId = 'thread-insert',
|
|
1251
|
+
} = params;
|
|
1252
|
+
|
|
1253
|
+
const id = `tp_thread_${toolpathCounter.count++}`;
|
|
1254
|
+
const tool = camState.toolLibrary.get(toolId) || { name: 'Thread Insert', feed: 100 };
|
|
1255
|
+
|
|
1256
|
+
const toolpath = {
|
|
1257
|
+
id,
|
|
1258
|
+
type: 'threading',
|
|
1259
|
+
tool,
|
|
1260
|
+
pitch,
|
|
1261
|
+
depth,
|
|
1262
|
+
diameter,
|
|
1263
|
+
passes: Math.ceil(depth / 0.1),
|
|
1264
|
+
estimatedTime: (diameter * pitch) / 100 * 60,
|
|
1265
|
+
status: 'generated',
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
camState.toolpaths.set(id, toolpath);
|
|
1269
|
+
visualizeToolpath(toolpath);
|
|
1270
|
+
|
|
1271
|
+
console.log('[CAM] Threading generated:', { id, pitch, diameter });
|
|
1272
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: toolpath }));
|
|
1273
|
+
|
|
1274
|
+
return { id, type: 'threading', estimatedTime: toolpath.estimatedTime };
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Generate multi-axis 4/5 axis contour
|
|
1279
|
+
* @param {Object} params Configuration
|
|
1280
|
+
* @returns {Object} Toolpath
|
|
1281
|
+
*/
|
|
1282
|
+
function generateMultiAxisContour(params = {}) {
|
|
1283
|
+
const {
|
|
1284
|
+
axes = '5', // '4' or '5'
|
|
1285
|
+
geometry = null,
|
|
1286
|
+
toolId = 'ball-endmill-3mm',
|
|
1287
|
+
stepOver = 2,
|
|
1288
|
+
} = params;
|
|
1289
|
+
|
|
1290
|
+
const tool = camState.toolLibrary.get(toolId);
|
|
1291
|
+
if (!tool) throw new Error(`Tool ${toolId} not found`);
|
|
1292
|
+
|
|
1293
|
+
const id = `tp_5axis_${toolpathCounter.count++}`;
|
|
1294
|
+
|
|
1295
|
+
const toolpath = {
|
|
1296
|
+
id,
|
|
1297
|
+
type: axes === '5' ? '5axis_contour' : '4axis_contour',
|
|
1298
|
+
tool,
|
|
1299
|
+
geometry,
|
|
1300
|
+
stepOver,
|
|
1301
|
+
passes: 5,
|
|
1302
|
+
estimatedTime: 300,
|
|
1303
|
+
status: 'generated',
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
camState.toolpaths.set(id, toolpath);
|
|
1307
|
+
visualizeToolpath(toolpath);
|
|
1308
|
+
|
|
1309
|
+
console.log(`[CAM] ${axes}-axis contour generated:`, { id });
|
|
1310
|
+
window.dispatchEvent(new CustomEvent('cam:toolpathGenerated', { detail: toolpath }));
|
|
1311
|
+
|
|
1312
|
+
return { id, type: `${axes}axis_contour`, estimatedTime: toolpath.estimatedTime };
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* Generate collision detection simulation
|
|
1317
|
+
* @param {Object} params Configuration
|
|
1318
|
+
* @returns {Object} Collision report
|
|
1319
|
+
*/
|
|
1320
|
+
function checkCollisions(params = {}) {
|
|
1321
|
+
const {
|
|
1322
|
+
toolpathId = null,
|
|
1323
|
+
includeToolHolder = true,
|
|
1324
|
+
includeFixture = true,
|
|
1325
|
+
} = params;
|
|
1326
|
+
|
|
1327
|
+
const collisions = [];
|
|
1328
|
+
// Placeholder collision detection
|
|
1329
|
+
const report = {
|
|
1330
|
+
timestamp: new Date(),
|
|
1331
|
+
toolpathId,
|
|
1332
|
+
collisions,
|
|
1333
|
+
passed: collisions.length === 0,
|
|
1334
|
+
severity: collisions.length === 0 ? 'OK' : 'ERROR',
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
console.log('[CAM] Collision check complete:', report.passed ? 'PASS' : 'FAIL');
|
|
1338
|
+
window.dispatchEvent(new CustomEvent('cam:collisionCheckComplete', { detail: report }));
|
|
1339
|
+
|
|
1340
|
+
return report;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* Gouge detection (tool engaging in material incorrectly)
|
|
1345
|
+
* @param {Object} params Configuration
|
|
1346
|
+
* @returns {Object} Gouge report
|
|
1347
|
+
*/
|
|
1348
|
+
function detectGouges(params = {}) {
|
|
1349
|
+
const { toolpathId = null } = params;
|
|
1350
|
+
|
|
1351
|
+
const gouges = [];
|
|
1352
|
+
const report = {
|
|
1353
|
+
timestamp: new Date(),
|
|
1354
|
+
toolpathId,
|
|
1355
|
+
gouges,
|
|
1356
|
+
hasFeedRateIssues: false,
|
|
1357
|
+
severity: gouges.length === 0 ? 'OK' : 'WARNING',
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
console.log('[CAM] Gouge detection complete:', report.severity);
|
|
1361
|
+
window.dispatchEvent(new CustomEvent('cam:gougeDetectionComplete', { detail: report }));
|
|
1362
|
+
|
|
1363
|
+
return report;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Set post processor for G-code output
|
|
1368
|
+
* @param {string} postId Processor ID
|
|
1369
|
+
*/
|
|
1370
|
+
function setPostProcessor(postId) {
|
|
1371
|
+
const posts = {
|
|
1372
|
+
'grbl': 'GRBL (CNC.js)',
|
|
1373
|
+
'linuxcnc': 'LinuxCNC',
|
|
1374
|
+
'fanuc': 'FANUC',
|
|
1375
|
+
'haas': 'HAAS',
|
|
1376
|
+
'mazak': 'Mazak',
|
|
1377
|
+
'okuma': 'Okuma',
|
|
1378
|
+
'marlin': 'Marlin (3D Printer)',
|
|
1379
|
+
'reprap': 'RepRap',
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
if (!posts[postId]) throw new Error(`Post processor '${postId}' not found`);
|
|
1383
|
+
camState.setupParams.postProcessor = postId;
|
|
1384
|
+
console.log(`[CAM] Post processor set to: ${posts[postId]}`);
|
|
1385
|
+
return posts[postId];
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Generate support material for 3D printing
|
|
1390
|
+
* @param {Object} params Configuration
|
|
1391
|
+
* @returns {Object} Support structure
|
|
1392
|
+
*/
|
|
1393
|
+
function generateSupports(params = {}) {
|
|
1394
|
+
const {
|
|
1395
|
+
geometry = null,
|
|
1396
|
+
density = 0.15, // 15% density
|
|
1397
|
+
type = 'linear', // 'linear' | 'tree'
|
|
1398
|
+
angle = 45, // Support overhang angle
|
|
1399
|
+
} = params;
|
|
1400
|
+
|
|
1401
|
+
const id = `support_${toolpathCounter.count++}`;
|
|
1402
|
+
|
|
1403
|
+
const support = {
|
|
1404
|
+
id,
|
|
1405
|
+
type: `support_${type}`,
|
|
1406
|
+
density,
|
|
1407
|
+
angle,
|
|
1408
|
+
volume: 0, // would calculate
|
|
1409
|
+
estimatedPrintTime: 0,
|
|
1410
|
+
material: 'support_material',
|
|
1411
|
+
status: 'generated',
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
console.log(`[CAM] Support structure generated (${type})`, { id, density });
|
|
1415
|
+
window.dispatchEvent(new CustomEvent('cam:supportsGenerated', { detail: support }));
|
|
1416
|
+
|
|
1417
|
+
return { id, type: support.type, density };
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* Preview stock removal (material simulation)
|
|
1422
|
+
* @param {string} toolpathId Toolpath ID
|
|
1423
|
+
* @returns {Object} Stock state
|
|
1424
|
+
*/
|
|
1425
|
+
function previewStockRemoval(toolpathId) {
|
|
1426
|
+
const toolpath = camState.toolpaths.get(toolpathId);
|
|
1427
|
+
if (!toolpath) throw new Error(`Toolpath ${toolpathId} not found`);
|
|
1428
|
+
|
|
1429
|
+
const volumeRemoved = 1000; // cubic mm (placeholder)
|
|
1430
|
+
|
|
1431
|
+
console.log(`[CAM] Stock removal preview: ${volumeRemoved}mm³`);
|
|
1432
|
+
window.dispatchEvent(new CustomEvent('cam:stockPreview', {
|
|
1433
|
+
detail: { toolpathId, volumeRemoved }
|
|
1434
|
+
}));
|
|
1435
|
+
|
|
1436
|
+
return { toolpathId, volumeRemoved, stockRemaining: 0 };
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// --- UI Panel ---
|
|
1440
|
+
|
|
1441
|
+
function getUI() {
|
|
1442
|
+
ui = document.createElement('div');
|
|
1443
|
+
ui.id = 'cam-panel';
|
|
1444
|
+
ui.className = 'module-panel';
|
|
1445
|
+
ui.innerHTML = `
|
|
1446
|
+
<div class="panel-header">
|
|
1447
|
+
<h3>CAM Setup & Toolpaths</h3>
|
|
1448
|
+
<button class="close-btn" data-close-panel="#cam-panel">×</button>
|
|
1449
|
+
</div>
|
|
1450
|
+
<div class="panel-body" style="max-height: 500px; overflow-y: auto;">
|
|
1451
|
+
<fieldset style="margin-bottom: 10px;">
|
|
1452
|
+
<legend>Work Setup</legend>
|
|
1453
|
+
<label>Stock Type:</label>
|
|
1454
|
+
<select id="cam-stock-type" style="margin-bottom: 5px;">
|
|
1455
|
+
<option value="box">Box</option>
|
|
1456
|
+
<option value="cylinder">Cylinder</option>
|
|
1457
|
+
</select>
|
|
1458
|
+
<button class="module-btn" data-cmd="cam.setup" style="width: 100%;">Define WCS</button>
|
|
1459
|
+
</fieldset>
|
|
1460
|
+
|
|
1461
|
+
<fieldset style="margin-bottom: 10px;">
|
|
1462
|
+
<legend>2D Milling</legend>
|
|
1463
|
+
<div class="button-group" style="display: grid; grid-template-columns: 1fr 1fr; gap: 5px;">
|
|
1464
|
+
<button class="module-btn" data-cmd="cam.contour2d">Contour 2D</button>
|
|
1465
|
+
<button class="module-btn" data-cmd="cam.pocket">Pocket</button>
|
|
1466
|
+
<button class="module-btn" data-cmd="cam.drill">Drill</button>
|
|
1467
|
+
<button class="module-btn" data-cmd="cam.face">Face</button>
|
|
1468
|
+
</div>
|
|
1469
|
+
</fieldset>
|
|
1470
|
+
|
|
1471
|
+
<fieldset style="margin-bottom: 10px;">
|
|
1472
|
+
<legend>3D & Advanced</legend>
|
|
1473
|
+
<div class="button-group" style="display: grid; grid-template-columns: 1fr 1fr; gap: 5px;">
|
|
1474
|
+
<button class="module-btn" data-cmd="cam.adaptive">Adaptive</button>
|
|
1475
|
+
<button class="module-btn" data-cmd="cam.parallel">Parallel</button>
|
|
1476
|
+
<button class="module-btn" data-cmd="cam.multiaxis">4/5-Axis</button>
|
|
1477
|
+
<button class="module-btn" data-cmd="cam.turning">Turning</button>
|
|
1478
|
+
</div>
|
|
1479
|
+
</fieldset>
|
|
1480
|
+
|
|
1481
|
+
<fieldset style="margin-bottom: 10px;">
|
|
1482
|
+
<legend>Validation</legend>
|
|
1483
|
+
<div class="button-group" style="display: grid; grid-template-columns: 1fr 1fr; gap: 5px;">
|
|
1484
|
+
<button class="module-btn" data-cmd="cam.collision">Check Collision</button>
|
|
1485
|
+
<button class="module-btn" data-cmd="cam.gouges">Detect Gouges</button>
|
|
1486
|
+
</div>
|
|
1487
|
+
</fieldset>
|
|
1488
|
+
|
|
1489
|
+
<fieldset style="margin-bottom: 10px;">
|
|
1490
|
+
<legend>Additive</legend>
|
|
1491
|
+
<div class="button-group" style="display: grid; grid-template-columns: 1fr 1fr; gap: 5px;">
|
|
1492
|
+
<button class="module-btn" data-cmd="cam.slice">FDM Slice</button>
|
|
1493
|
+
<button class="module-btn" data-cmd="cam.supports">Generate Supports</button>
|
|
1494
|
+
</div>
|
|
1495
|
+
</fieldset>
|
|
1496
|
+
|
|
1497
|
+
<fieldset style="margin-bottom: 10px;">
|
|
1498
|
+
<legend>Additive</legend>
|
|
1499
|
+
<button class="module-btn" data-cmd="cam.slice" style="width: 100%;">FDM Slice</button>
|
|
1500
|
+
</fieldset>
|
|
1501
|
+
|
|
1502
|
+
<fieldset style="margin-bottom: 10px;">
|
|
1503
|
+
<legend>Tool Library</legend>
|
|
1504
|
+
<select id="cam-tool-select" style="width: 100%; margin-bottom: 5px;">
|
|
1505
|
+
${Array.from(defaultToolLibrary.values()).map(t => `<option value="${t.id}">${t.name}</option>`).join('')}
|
|
1506
|
+
</select>
|
|
1507
|
+
<button class="module-btn" data-cmd="cam.setTool" style="width: 100%;">Select Tool</button>
|
|
1508
|
+
</fieldset>
|
|
1509
|
+
|
|
1510
|
+
<fieldset style="margin-bottom: 10px;">
|
|
1511
|
+
<legend>Output</legend>
|
|
1512
|
+
<label>G-code Dialect:</label>
|
|
1513
|
+
<select id="cam-dialect" style="width: 100%; margin-bottom: 5px;">
|
|
1514
|
+
<option value="grbl">Grbl</option>
|
|
1515
|
+
<option value="linuxcnc">LinuxCNC</option>
|
|
1516
|
+
<option value="fanuc">Fanuc</option>
|
|
1517
|
+
<option value="marlin">Marlin (3D Printer)</option>
|
|
1518
|
+
</select>
|
|
1519
|
+
<button class="module-btn" data-cmd="cam.generateGCode" style="width: 100%; margin-bottom: 5px;">Generate G-code</button>
|
|
1520
|
+
<button class="module-btn" data-cmd="cam.exportGCode" style="width: 100%;">Export G-code</button>
|
|
1521
|
+
</fieldset>
|
|
1522
|
+
|
|
1523
|
+
<div id="cam-toolpath-list" style="padding: 10px; border: 1px solid #ccc; border-radius: 4px; background: #f5f5f5;">
|
|
1524
|
+
<strong>Toolpaths:</strong>
|
|
1525
|
+
<ul id="cam-toolpath-items" style="list-style: none; padding: 0; margin: 5px 0; font-size: 12px;"></ul>
|
|
1526
|
+
</div>
|
|
1527
|
+
</div>
|
|
1528
|
+
`;
|
|
1529
|
+
|
|
1530
|
+
// Wire up buttons
|
|
1531
|
+
ui.querySelectorAll('[data-cmd]').forEach(btn => {
|
|
1532
|
+
btn.addEventListener('click', () => {
|
|
1533
|
+
const [ns, cmd] = btn.dataset.cmd.split('.');
|
|
1534
|
+
console.log(`[CAM] Command: ${cmd}`);
|
|
1535
|
+
});
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
return ui;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
return {
|
|
1542
|
+
MODULE_NAME,
|
|
1543
|
+
init,
|
|
1544
|
+
getUI,
|
|
1545
|
+
setupWorkCoordinateSystem,
|
|
1546
|
+
generateContour2D,
|
|
1547
|
+
generatePocket,
|
|
1548
|
+
generateDrilling,
|
|
1549
|
+
generateFace,
|
|
1550
|
+
generateAdaptiveClearing,
|
|
1551
|
+
generateParallel,
|
|
1552
|
+
generateTurning,
|
|
1553
|
+
generateThreading,
|
|
1554
|
+
generateMultiAxisContour,
|
|
1555
|
+
generateFDMSlicing,
|
|
1556
|
+
generateSupports,
|
|
1557
|
+
generateGCode,
|
|
1558
|
+
simulateToolpath,
|
|
1559
|
+
checkCollisions,
|
|
1560
|
+
detectGouges,
|
|
1561
|
+
previewStockRemoval,
|
|
1562
|
+
setTool,
|
|
1563
|
+
setPostProcessor,
|
|
1564
|
+
addTool,
|
|
1565
|
+
listTools,
|
|
1566
|
+
listToolpaths,
|
|
1567
|
+
exportGCode,
|
|
1568
|
+
helpEntries,
|
|
1569
|
+
};
|
|
1570
|
+
})();
|
|
1571
|
+
|
|
1572
|
+
export default CAMModule;
|