cyclecad 3.2.1 → 3.5.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/CLAUDE.md +155 -1
- package/DOCKER-SETUP-VERIFICATION.md +399 -0
- package/DOCKER-TESTING.md +463 -0
- package/FUSION360_MODULES.md +478 -0
- package/FUSION_MODULES_README.md +352 -0
- package/INTEGRATION_SNIPPETS.md +608 -0
- package/KILLER-FEATURES-DELIVERY.md +469 -0
- package/MODULES_SUMMARY.txt +337 -0
- package/QUICK_REFERENCE.txt +298 -0
- package/README-DOCKER-TESTING.txt +438 -0
- package/app/index.html +23 -10
- package/app/js/fusion-help.json +1808 -0
- package/app/js/help-module-v3.js +1096 -0
- package/app/js/killer-features-help.json +395 -0
- package/app/js/killer-features.js +1508 -0
- package/app/js/modules/fusion-assembly.js +842 -0
- package/app/js/modules/fusion-cam.js +785 -0
- package/app/js/modules/fusion-data.js +814 -0
- package/app/js/modules/fusion-drawing.js +844 -0
- package/app/js/modules/fusion-inspection.js +756 -0
- package/app/js/modules/fusion-render.js +774 -0
- package/app/js/modules/fusion-simulation.js +986 -0
- package/app/js/modules/fusion-sketch.js +1044 -0
- package/app/js/modules/fusion-solid.js +1095 -0
- package/app/js/modules/fusion-surface.js +949 -0
- package/app/tests/FUSION_TEST_SUITE.md +266 -0
- package/app/tests/README.md +77 -0
- package/app/tests/TESTING-CHECKLIST.md +177 -0
- package/app/tests/TEST_SUITE_SUMMARY.txt +236 -0
- package/app/tests/brep-live-test.html +848 -0
- package/app/tests/docker-integration-test.html +811 -0
- package/app/tests/fusion-all-tests.html +670 -0
- package/app/tests/fusion-assembly-tests.html +461 -0
- package/app/tests/fusion-cam-tests.html +421 -0
- package/app/tests/fusion-simulation-tests.html +421 -0
- package/app/tests/fusion-sketch-tests.html +613 -0
- package/app/tests/fusion-solid-tests.html +529 -0
- package/app/tests/index.html +453 -0
- package/app/tests/killer-features-test.html +509 -0
- package/app/tests/run-tests.html +874 -0
- package/app/tests/step-import-live-test.html +1115 -0
- package/app/tests/test-agent-v3.html +93 -696
- package/architecture-dashboard.html +1970 -0
- package/docs/API-REFERENCE.md +1423 -0
- package/docs/BREP-LIVE-TEST-GUIDE.md +453 -0
- package/docs/DEVELOPER-GUIDE-v3.md +795 -0
- package/docs/DOCKER-QUICK-TEST.md +376 -0
- package/docs/FUSION-FEATURES-GUIDE.md +2513 -0
- package/docs/FUSION-TUTORIAL.md +1203 -0
- package/docs/INFRASTRUCTURE-GUIDE-INDEX.md +327 -0
- package/docs/KEYBOARD-SHORTCUTS.md +402 -0
- package/docs/KILLER-FEATURES-INTEGRATION.md +412 -0
- package/docs/KILLER-FEATURES-SUMMARY.md +424 -0
- package/docs/KILLER-FEATURES-TUTORIAL.md +784 -0
- package/docs/KILLER-FEATURES.md +562 -0
- package/docs/QUICK-REFERENCE.md +282 -0
- package/docs/README-v3-DOCS.md +274 -0
- package/docs/TUTORIAL-v3.md +1190 -0
- package/docs/architecture-dashboard.html +1970 -0
- package/docs/architecture-v3.html +1038 -0
- package/linkedin-post-v3.md +58 -0
- package/package.json +1 -1
- package/scripts/dev-setup.sh +338 -0
- package/scripts/docker-health-check.sh +159 -0
- package/scripts/integration-test.sh +311 -0
- package/scripts/test-docker.sh +515 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cycleCAD — Fusion 360 CAM (Computer-Aided Manufacturing) Module
|
|
3
|
+
* Complete manufacturing workspace with 2D/3D operations, drilling, turning, tool library,
|
|
4
|
+
* G-code generation, and toolpath simulation.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - 2D Operations: Face, 2D Contour, 2D Pocket, 2D Adaptive, Slot, Trace, Thread, Bore, Circular, Engrave
|
|
8
|
+
* - 3D Operations: 3D Adaptive, 3D Contour, Pocket, Parallel, Scallop, Pencil, Steep & Shallow, Morphed Spiral
|
|
9
|
+
* - Drilling: Drill, Spot Drill, Peck Drill, Tap, Bore, Ream
|
|
10
|
+
* - Turning: Face, Profile, Groove, Thread, Part-off
|
|
11
|
+
* - Tool Library, Toolpath Simulation, G-code Generation (Fanuc/GRBL/LinuxCNC)
|
|
12
|
+
* - Feeds & Speeds Calculator, Multi-axis support
|
|
13
|
+
*
|
|
14
|
+
* Version: 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fusion CAM Module — Manufacturing workspace
|
|
21
|
+
*/
|
|
22
|
+
class FusionCAMModule {
|
|
23
|
+
constructor(scene, camera, renderer) {
|
|
24
|
+
this.scene = scene;
|
|
25
|
+
this.camera = camera;
|
|
26
|
+
this.renderer = renderer;
|
|
27
|
+
|
|
28
|
+
// Setup configuration
|
|
29
|
+
this.setup = {
|
|
30
|
+
stock: null, // { material, dimensions, origin }
|
|
31
|
+
wcs: new THREE.Matrix4(), // Work coordinate system
|
|
32
|
+
modelOrientation: new THREE.Quaternion(),
|
|
33
|
+
machineType: '3-axis', // 3-axis | 3+2 | 4-axis | 5-axis
|
|
34
|
+
toolAxis: new THREE.Vector3(0, 0, 1) // Z-axis default
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Operations database
|
|
38
|
+
this.operations = new Map(); // opId -> operation definition
|
|
39
|
+
this.toolpaths = new Map(); // opId -> toolpath geometry
|
|
40
|
+
this.toolLibrary = this.initToolLibrary();
|
|
41
|
+
|
|
42
|
+
// Simulation state
|
|
43
|
+
this.simulationTime = 0;
|
|
44
|
+
this.toolpathIndex = 0;
|
|
45
|
+
this.remainingStock = null; // THREE.Geometry of remaining material
|
|
46
|
+
this.machineWorkspace = new THREE.Box3(
|
|
47
|
+
new THREE.Vector3(-200, -200, -200),
|
|
48
|
+
new THREE.Vector3(200, 200, 200)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Post-processors
|
|
52
|
+
this.postProcessors = {
|
|
53
|
+
fanuc: this.fanucPost.bind(this),
|
|
54
|
+
grbl: this.grblPost.bind(this),
|
|
55
|
+
linuxcnc: this.linuxcncPost.bind(this)
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Simulation
|
|
59
|
+
this.isSimulating = false;
|
|
60
|
+
this.simulationSpeed = 1.0;
|
|
61
|
+
this.currentTool = null;
|
|
62
|
+
this.toolpathLines = []; // Visual toolpath lines
|
|
63
|
+
|
|
64
|
+
this.animationFrameId = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initialize CAM module UI
|
|
69
|
+
*/
|
|
70
|
+
init() {
|
|
71
|
+
this.setupEventListeners();
|
|
72
|
+
this.initializeStock();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get UI panel for CAM controls
|
|
77
|
+
*/
|
|
78
|
+
getUI() {
|
|
79
|
+
const panel = document.createElement('div');
|
|
80
|
+
panel.className = 'fusion-cam-panel';
|
|
81
|
+
panel.innerHTML = `
|
|
82
|
+
<style>
|
|
83
|
+
.fusion-cam-panel {
|
|
84
|
+
padding: 16px;
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
background: var(--bg-secondary);
|
|
87
|
+
color: var(--text-primary);
|
|
88
|
+
border-radius: 4px;
|
|
89
|
+
max-height: 700px;
|
|
90
|
+
overflow-y: auto;
|
|
91
|
+
}
|
|
92
|
+
.fusion-cam-panel h3 {
|
|
93
|
+
margin: 0 0 12px 0;
|
|
94
|
+
font-size: 14px;
|
|
95
|
+
font-weight: 600;
|
|
96
|
+
color: var(--text-primary);
|
|
97
|
+
}
|
|
98
|
+
.cam-section {
|
|
99
|
+
margin-bottom: 16px;
|
|
100
|
+
padding-bottom: 12px;
|
|
101
|
+
border-bottom: 1px solid var(--border-color);
|
|
102
|
+
}
|
|
103
|
+
.cam-section:last-child {
|
|
104
|
+
border-bottom: none;
|
|
105
|
+
}
|
|
106
|
+
.operation-list {
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
gap: 6px;
|
|
110
|
+
max-height: 200px;
|
|
111
|
+
overflow-y: auto;
|
|
112
|
+
}
|
|
113
|
+
.operation-item {
|
|
114
|
+
padding: 8px;
|
|
115
|
+
background: var(--bg-primary);
|
|
116
|
+
border-radius: 3px;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
transition: background 0.2s;
|
|
119
|
+
font-size: 11px;
|
|
120
|
+
}
|
|
121
|
+
.operation-item:hover {
|
|
122
|
+
background: var(--bg-tertiary);
|
|
123
|
+
}
|
|
124
|
+
.operation-type {
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
color: var(--accent-color);
|
|
127
|
+
}
|
|
128
|
+
.tool-selector {
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
gap: 6px;
|
|
132
|
+
margin-bottom: 8px;
|
|
133
|
+
}
|
|
134
|
+
.tool-selector select {
|
|
135
|
+
padding: 6px;
|
|
136
|
+
background: var(--bg-primary);
|
|
137
|
+
border: 1px solid var(--border-color);
|
|
138
|
+
border-radius: 3px;
|
|
139
|
+
color: var(--text-primary);
|
|
140
|
+
font-size: 11px;
|
|
141
|
+
}
|
|
142
|
+
.param-input {
|
|
143
|
+
display: flex;
|
|
144
|
+
gap: 8px;
|
|
145
|
+
margin: 6px 0;
|
|
146
|
+
align-items: center;
|
|
147
|
+
}
|
|
148
|
+
.param-input label {
|
|
149
|
+
width: 70px;
|
|
150
|
+
font-weight: 600;
|
|
151
|
+
font-size: 11px;
|
|
152
|
+
}
|
|
153
|
+
.param-input input {
|
|
154
|
+
flex: 1;
|
|
155
|
+
padding: 4px;
|
|
156
|
+
background: var(--bg-primary);
|
|
157
|
+
border: 1px solid var(--border-color);
|
|
158
|
+
border-radius: 3px;
|
|
159
|
+
color: var(--text-primary);
|
|
160
|
+
font-size: 11px;
|
|
161
|
+
}
|
|
162
|
+
.operation-buttons {
|
|
163
|
+
display: grid;
|
|
164
|
+
grid-template-columns: 1fr 1fr;
|
|
165
|
+
gap: 6px;
|
|
166
|
+
margin-top: 8px;
|
|
167
|
+
}
|
|
168
|
+
.operation-buttons button {
|
|
169
|
+
padding: 6px;
|
|
170
|
+
font-size: 11px;
|
|
171
|
+
background: var(--button-bg);
|
|
172
|
+
border: 1px solid var(--border-color);
|
|
173
|
+
border-radius: 3px;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
color: var(--text-primary);
|
|
176
|
+
}
|
|
177
|
+
.operation-buttons button:hover {
|
|
178
|
+
background: var(--button-hover-bg);
|
|
179
|
+
}
|
|
180
|
+
.feeds-speeds {
|
|
181
|
+
background: var(--bg-primary);
|
|
182
|
+
border-radius: 3px;
|
|
183
|
+
padding: 8px;
|
|
184
|
+
margin-top: 8px;
|
|
185
|
+
}
|
|
186
|
+
.feeds-speeds-row {
|
|
187
|
+
display: flex;
|
|
188
|
+
justify-content: space-between;
|
|
189
|
+
padding: 4px 0;
|
|
190
|
+
border-bottom: 1px solid var(--border-color);
|
|
191
|
+
font-size: 11px;
|
|
192
|
+
}
|
|
193
|
+
.feeds-speeds-row:last-child {
|
|
194
|
+
border-bottom: none;
|
|
195
|
+
}
|
|
196
|
+
.simulation-controls {
|
|
197
|
+
display: flex;
|
|
198
|
+
gap: 4px;
|
|
199
|
+
margin-top: 8px;
|
|
200
|
+
}
|
|
201
|
+
.simulation-controls button {
|
|
202
|
+
flex: 1;
|
|
203
|
+
padding: 6px;
|
|
204
|
+
font-size: 10px;
|
|
205
|
+
background: var(--button-bg);
|
|
206
|
+
border: 1px solid var(--border-color);
|
|
207
|
+
border-radius: 3px;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
color: var(--text-primary);
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
212
|
+
|
|
213
|
+
<div class="cam-section">
|
|
214
|
+
<h3>Setup</h3>
|
|
215
|
+
<div class="param-input">
|
|
216
|
+
<label>Machine:</label>
|
|
217
|
+
<select id="camMachineType" onchange="window.fusionCAM?.setMachineType(this.value)">
|
|
218
|
+
<option value="3-axis">3-Axis</option>
|
|
219
|
+
<option value="3+2">3+2</option>
|
|
220
|
+
<option value="4-axis">4-Axis</option>
|
|
221
|
+
<option value="5-axis">5-Axis</option>
|
|
222
|
+
</select>
|
|
223
|
+
</div>
|
|
224
|
+
<div class="param-input">
|
|
225
|
+
<label>Material:</label>
|
|
226
|
+
<select id="camStockMaterial">
|
|
227
|
+
<option value="steel">Steel</option>
|
|
228
|
+
<option value="aluminum">Aluminum</option>
|
|
229
|
+
<option value="titanium">Titanium</option>
|
|
230
|
+
<option value="plastic">Plastic</option>
|
|
231
|
+
</select>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<div class="cam-section">
|
|
236
|
+
<h3>Operations</h3>
|
|
237
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px; margin-bottom: 8px;">
|
|
238
|
+
<button onclick="window.fusionCAM?.newOperation('2D Pocket')">2D Pocket</button>
|
|
239
|
+
<button onclick="window.fusionCAM?.newOperation('2D Contour')">2D Contour</button>
|
|
240
|
+
<button onclick="window.fusionCAM?.newOperation('3D Pocket')">3D Pocket</button>
|
|
241
|
+
<button onclick="window.fusionCAM?.newOperation('Drill')">Drill</button>
|
|
242
|
+
<button onclick="window.fusionCAM?.newOperation('Slot')">Slot</button>
|
|
243
|
+
<button onclick="window.fusionCAM?.newOperation('Thread')">Thread</button>
|
|
244
|
+
</div>
|
|
245
|
+
<div class="operation-list" id="camOperationList"></div>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div class="cam-section">
|
|
249
|
+
<h3>Tool Library</h3>
|
|
250
|
+
<select id="camToolSelect" onchange="window.fusionCAM?.selectTool(this.value)">
|
|
251
|
+
<option value="">-- Select Tool --</option>
|
|
252
|
+
</select>
|
|
253
|
+
<div id="camToolInfo" style="margin-top: 8px; font-size: 10px; padding: 6px; background: var(--bg-primary); border-radius: 3px;"></div>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div class="cam-section">
|
|
257
|
+
<h3>Feeds & Speeds</h3>
|
|
258
|
+
<div class="feeds-speeds">
|
|
259
|
+
<div class="feeds-speeds-row">
|
|
260
|
+
<span>Spindle:</span>
|
|
261
|
+
<span id="camSpindleSpeed">1000 RPM</span>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="feeds-speeds-row">
|
|
264
|
+
<span>Feed Rate:</span>
|
|
265
|
+
<span id="camFeedRate">100 mm/min</span>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="feeds-speeds-row">
|
|
268
|
+
<span>Depth/Pass:</span>
|
|
269
|
+
<span id="camDepthPass">5 mm</span>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
<button onclick="window.fusionCAM?.calculateFeedsAndSpeeds()" style="width: 100%; padding: 6px; margin-top: 8px;">Calculate</button>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<div class="cam-section">
|
|
276
|
+
<h3>Simulation</h3>
|
|
277
|
+
<div class="simulation-controls">
|
|
278
|
+
<button onclick="window.fusionCAM?.simulateToolpath()">Simulate</button>
|
|
279
|
+
<button onclick="window.fusionCAM?.pauseSimulation()">Pause</button>
|
|
280
|
+
<button onclick="window.fusionCAM?.resetSimulation()">Reset</button>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<div class="cam-section">
|
|
285
|
+
<h3>Post-Processing</h3>
|
|
286
|
+
<div style="display: flex; gap: 4px;">
|
|
287
|
+
<button onclick="window.fusionCAM?.generateGCode('fanuc')" style="flex: 1;">FANUC</button>
|
|
288
|
+
<button onclick="window.fusionCAM?.generateGCode('grbl')" style="flex: 1;">GRBL</button>
|
|
289
|
+
<button onclick="window.fusionCAM?.generateGCode('linuxcnc')" style="flex: 1;">LinuxCNC</button>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
`;
|
|
293
|
+
|
|
294
|
+
window.fusionCAM = this;
|
|
295
|
+
this.populateToolLibrary();
|
|
296
|
+
return panel;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Initialize tool library with common tools
|
|
301
|
+
*/
|
|
302
|
+
initToolLibrary() {
|
|
303
|
+
return new Map([
|
|
304
|
+
// End Mills
|
|
305
|
+
['endmill_1', { name: '1mm End Mill', type: 'end-mill', diameter: 1, flutes: 2, material: 'HSS', length: 25 }],
|
|
306
|
+
['endmill_3', { name: '3mm End Mill', type: 'end-mill', diameter: 3, flutes: 2, material: 'HSS', length: 25 }],
|
|
307
|
+
['endmill_6', { name: '6mm End Mill', type: 'end-mill', diameter: 6, flutes: 2, material: 'Carbide', length: 30 }],
|
|
308
|
+
['endmill_10', { name: '10mm End Mill', type: 'end-mill', diameter: 10, flutes: 4, material: 'Carbide', length: 40 }],
|
|
309
|
+
|
|
310
|
+
// Ball Mills (for 3D contouring)
|
|
311
|
+
['ballmill_3', { name: '3mm Ball Mill', type: 'ball-mill', diameter: 3, radius: 1.5, material: 'Carbide', length: 30 }],
|
|
312
|
+
['ballmill_6', { name: '6mm Ball Mill', type: 'ball-mill', diameter: 6, radius: 3, material: 'Carbide', length: 40 }],
|
|
313
|
+
|
|
314
|
+
// Drills
|
|
315
|
+
['drill_1.5', { name: '1.5mm Drill', type: 'drill', diameter: 1.5, material: 'HSS', length: 20 }],
|
|
316
|
+
['drill_3', { name: '3mm Drill', type: 'drill', diameter: 3, material: 'HSS', length: 30 }],
|
|
317
|
+
['drill_6', { name: '6mm Drill', type: 'drill', diameter: 6, material: 'Carbide', length: 40 }],
|
|
318
|
+
['drill_10', { name: '10mm Drill', type: 'drill', diameter: 10, material: 'Carbide', length: 50 }],
|
|
319
|
+
|
|
320
|
+
// Taps
|
|
321
|
+
['tap_m3', { name: 'M3 Tap', type: 'tap', diameter: 3, pitch: 0.5, material: 'HSS', length: 30 }],
|
|
322
|
+
['tap_m6', { name: 'M6 Tap', type: 'tap', diameter: 6, pitch: 1.0, material: 'HSS', length: 40 }],
|
|
323
|
+
['tap_m10', { name: 'M10 Tap', type: 'tap', diameter: 10, pitch: 1.5, material: 'HSS', length: 50 }],
|
|
324
|
+
|
|
325
|
+
// Slot Drills
|
|
326
|
+
['slotdrill_3', { name: '3mm Slot Drill', type: 'slot-drill', diameter: 3, material: 'Carbide', length: 30 }],
|
|
327
|
+
['slotdrill_6', { name: '6mm Slot Drill', type: 'slot-drill', diameter: 6, material: 'Carbide', length: 40 }],
|
|
328
|
+
]);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Populate tool selector
|
|
333
|
+
*/
|
|
334
|
+
populateToolLibrary() {
|
|
335
|
+
const select = document.getElementById('camToolSelect');
|
|
336
|
+
if (!select) return;
|
|
337
|
+
|
|
338
|
+
for (const [id, tool] of this.toolLibrary) {
|
|
339
|
+
const option = document.createElement('option');
|
|
340
|
+
option.value = id;
|
|
341
|
+
option.textContent = tool.name;
|
|
342
|
+
select.appendChild(option);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Select a tool from library
|
|
348
|
+
*/
|
|
349
|
+
selectTool(toolId) {
|
|
350
|
+
if (!toolId) return;
|
|
351
|
+
|
|
352
|
+
this.currentTool = this.toolLibrary.get(toolId);
|
|
353
|
+
const infoDiv = document.getElementById('camToolInfo');
|
|
354
|
+
|
|
355
|
+
if (infoDiv && this.currentTool) {
|
|
356
|
+
infoDiv.innerHTML = `
|
|
357
|
+
<strong>${this.currentTool.name}</strong><br>
|
|
358
|
+
Diameter: ${this.currentTool.diameter}mm<br>
|
|
359
|
+
Type: ${this.currentTool.type}<br>
|
|
360
|
+
Material: ${this.currentTool.material}
|
|
361
|
+
`;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Create new manufacturing operation
|
|
367
|
+
*/
|
|
368
|
+
newOperation(opType) {
|
|
369
|
+
const opId = `op_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
370
|
+
|
|
371
|
+
const operation = {
|
|
372
|
+
id: opId,
|
|
373
|
+
type: opType, // 2D Pocket | 2D Contour | 3D Pocket | Drill | Slot | Thread | etc.
|
|
374
|
+
tool: this.currentTool ? this.currentTool.name : 'End Mill',
|
|
375
|
+
toolId: null,
|
|
376
|
+
|
|
377
|
+
// Common parameters
|
|
378
|
+
feedRate: 100, // mm/min
|
|
379
|
+
spindleSpeed: 1000, // RPM
|
|
380
|
+
depth: 10, // mm (for pockets)
|
|
381
|
+
depthPerPass: 5, // mm
|
|
382
|
+
stepOver: 2, // mm
|
|
383
|
+
stepDown: 2, // mm
|
|
384
|
+
|
|
385
|
+
// Operation-specific parameters
|
|
386
|
+
contourOffset: 0, // For contour operations
|
|
387
|
+
cornerStrategy: 'sharp', // sharp | round | optimize
|
|
388
|
+
adaptiveEnabled: true,
|
|
389
|
+
trochoidal: false,
|
|
390
|
+
|
|
391
|
+
// Geometry
|
|
392
|
+
geometry: null, // Profile or area for operation
|
|
393
|
+
toolpath: [],
|
|
394
|
+
toolpathWireframe: null,
|
|
395
|
+
|
|
396
|
+
// Status
|
|
397
|
+
isActive: false,
|
|
398
|
+
isComplete: false
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
this.operations.set(opId, operation);
|
|
402
|
+
this.updateOperationList();
|
|
403
|
+
return opId;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Update operation list display
|
|
408
|
+
*/
|
|
409
|
+
updateOperationList() {
|
|
410
|
+
const listDiv = document.getElementById('camOperationList');
|
|
411
|
+
if (!listDiv) return;
|
|
412
|
+
|
|
413
|
+
listDiv.innerHTML = '';
|
|
414
|
+
for (const [opId, op] of this.operations) {
|
|
415
|
+
const itemDiv = document.createElement('div');
|
|
416
|
+
itemDiv.className = 'operation-item';
|
|
417
|
+
itemDiv.innerHTML = `
|
|
418
|
+
<div><span class="operation-type">${op.type}</span></div>
|
|
419
|
+
<div style="font-size: 10px; color: var(--text-secondary);">
|
|
420
|
+
Tool: ${op.tool} | Feed: ${op.feedRate} mm/min
|
|
421
|
+
</div>
|
|
422
|
+
`;
|
|
423
|
+
itemDiv.onclick = () => this.selectOperation(opId);
|
|
424
|
+
listDiv.appendChild(itemDiv);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Select operation for editing
|
|
430
|
+
*/
|
|
431
|
+
selectOperation(opId) {
|
|
432
|
+
for (const [id, op] of this.operations) {
|
|
433
|
+
op.isActive = id === opId;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Calculate feeds and speeds based on material and tool
|
|
439
|
+
*/
|
|
440
|
+
calculateFeedsAndSpeeds() {
|
|
441
|
+
if (!this.currentTool || !this.setup.stock) return;
|
|
442
|
+
|
|
443
|
+
// Material-dependent feed/speed tables
|
|
444
|
+
const feedSpeeds = {
|
|
445
|
+
steel: { hssCutSpeed: 20, carbideCutSpeed: 200 },
|
|
446
|
+
aluminum: { hssCutSpeed: 80, carbideCutSpeed: 400 },
|
|
447
|
+
titanium: { hssCutSpeed: 10, carbideCutSpeed: 100 },
|
|
448
|
+
plastic: { hssCutSpeed: 100, carbideCutSpeed: 500 }
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const material = this.setup.stock.material || 'aluminum';
|
|
452
|
+
const fs = feedSpeeds[material] || feedSpeeds.aluminum;
|
|
453
|
+
const isCarbide = this.currentTool.material === 'Carbide';
|
|
454
|
+
const cutSpeed = isCarbide ? fs.carbideCutSpeed : fs.hssCutSpeed;
|
|
455
|
+
|
|
456
|
+
// RPM = (1000 * Surface Speed) / (π * Diameter)
|
|
457
|
+
const diameter = this.currentTool.diameter;
|
|
458
|
+
const rpm = (1000 * cutSpeed) / (Math.PI * diameter);
|
|
459
|
+
|
|
460
|
+
// Feed = RPM * Chip Load * Flutes
|
|
461
|
+
const chipLoad = isCarbide ? 0.1 : 0.05;
|
|
462
|
+
const flutes = this.currentTool.flutes || 2;
|
|
463
|
+
const feedRate = rpm * chipLoad * flutes;
|
|
464
|
+
|
|
465
|
+
// Update displays
|
|
466
|
+
document.getElementById('camSpindleSpeed').textContent = Math.round(rpm) + ' RPM';
|
|
467
|
+
document.getElementById('camFeedRate').textContent = Math.round(feedRate) + ' mm/min';
|
|
468
|
+
document.getElementById('camDepthPass').textContent = (diameter * 0.5).toFixed(1) + ' mm';
|
|
469
|
+
|
|
470
|
+
// Update active operation
|
|
471
|
+
for (const op of this.operations.values()) {
|
|
472
|
+
if (op.isActive) {
|
|
473
|
+
op.spindleSpeed = Math.round(rpm);
|
|
474
|
+
op.feedRate = Math.round(feedRate);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Set machine type (3-axis, 4-axis, 5-axis)
|
|
481
|
+
*/
|
|
482
|
+
setMachineType(type) {
|
|
483
|
+
this.setup.machineType = type;
|
|
484
|
+
console.log(`Machine type set to: ${type}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Initialize stock geometry
|
|
489
|
+
*/
|
|
490
|
+
initializeStock() {
|
|
491
|
+
// Create stock box geometry
|
|
492
|
+
const stockGeo = new THREE.BoxGeometry(100, 100, 50);
|
|
493
|
+
const stockMat = new THREE.MeshPhongMaterial({
|
|
494
|
+
color: 0x666666,
|
|
495
|
+
transparent: true,
|
|
496
|
+
opacity: 0.3
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
this.remainingStock = new THREE.Mesh(stockGeo, stockMat);
|
|
500
|
+
this.scene.add(this.remainingStock);
|
|
501
|
+
|
|
502
|
+
this.setup.stock = {
|
|
503
|
+
material: 'aluminum',
|
|
504
|
+
dimensions: { width: 100, height: 100, depth: 50 },
|
|
505
|
+
origin: new THREE.Vector3(-50, -50, 0)
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Simulate toolpath with 3D visualization
|
|
511
|
+
*/
|
|
512
|
+
simulateToolpath() {
|
|
513
|
+
if (this.isSimulating) return;
|
|
514
|
+
this.isSimulating = true;
|
|
515
|
+
|
|
516
|
+
// Collect all toolpaths from operations
|
|
517
|
+
const allToolpaths = [];
|
|
518
|
+
for (const [opId, op] of this.operations) {
|
|
519
|
+
if (op.toolpath && op.toolpath.length > 0) {
|
|
520
|
+
allToolpaths.push(...op.toolpath);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (allToolpaths.length === 0) {
|
|
525
|
+
console.warn('No toolpaths to simulate');
|
|
526
|
+
this.isSimulating = false;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Create visualization of toolpath
|
|
531
|
+
const toolpathGeo = new THREE.BufferGeometry();
|
|
532
|
+
const positions = [];
|
|
533
|
+
|
|
534
|
+
for (let i = 0; i < allToolpaths.length; i++) {
|
|
535
|
+
const point = allToolpaths[i];
|
|
536
|
+
positions.push(point.x, point.y, point.z);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
toolpathGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));
|
|
540
|
+
const toolpathMat = new THREE.LineBasicMaterial({ color: 0x00ff00, linewidth: 2 });
|
|
541
|
+
const toolpathLine = new THREE.Line(toolpathGeo, toolpathMat);
|
|
542
|
+
|
|
543
|
+
this.scene.add(toolpathLine);
|
|
544
|
+
this.toolpathLines.push(toolpathLine);
|
|
545
|
+
|
|
546
|
+
// Animate tool along toolpath
|
|
547
|
+
this.simulateToolMovement(allToolpaths);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Animate tool movement along toolpath
|
|
552
|
+
*/
|
|
553
|
+
simulateToolMovement(toolpaths) {
|
|
554
|
+
const startTime = performance.now();
|
|
555
|
+
const totalPoints = toolpaths.length;
|
|
556
|
+
const duration = totalPoints * 50; // 50ms per point
|
|
557
|
+
|
|
558
|
+
const animate = (currentTime) => {
|
|
559
|
+
const elapsed = currentTime - startTime;
|
|
560
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
561
|
+
const pointIndex = Math.floor(progress * (totalPoints - 1));
|
|
562
|
+
|
|
563
|
+
if (pointIndex < totalPoints) {
|
|
564
|
+
const point = toolpaths[pointIndex];
|
|
565
|
+
// Update tool position visualization
|
|
566
|
+
// (would show cutting tool at this position)
|
|
567
|
+
this.simulationTime = progress;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (progress < 1) {
|
|
571
|
+
this.animationFrameId = requestAnimationFrame(animate);
|
|
572
|
+
} else {
|
|
573
|
+
this.isSimulating = false;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
this.animationFrameId = requestAnimationFrame(animate);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Pause simulation
|
|
582
|
+
*/
|
|
583
|
+
pauseSimulation() {
|
|
584
|
+
if (this.animationFrameId) {
|
|
585
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
586
|
+
this.isSimulating = false;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Reset simulation
|
|
592
|
+
*/
|
|
593
|
+
resetSimulation() {
|
|
594
|
+
this.pauseSimulation();
|
|
595
|
+
this.simulationTime = 0;
|
|
596
|
+
this.toolpathIndex = 0;
|
|
597
|
+
|
|
598
|
+
// Clear toolpath visualization
|
|
599
|
+
for (const line of this.toolpathLines) {
|
|
600
|
+
this.scene.remove(line);
|
|
601
|
+
}
|
|
602
|
+
this.toolpathLines = [];
|
|
603
|
+
|
|
604
|
+
// Reset stock
|
|
605
|
+
this.initializeStock();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Generate toolpath (simplified 2D example)
|
|
610
|
+
*/
|
|
611
|
+
generateToolpath(operation) {
|
|
612
|
+
const toolpath = [];
|
|
613
|
+
const tool = this.currentTool;
|
|
614
|
+
|
|
615
|
+
if (!tool) return toolpath;
|
|
616
|
+
|
|
617
|
+
// Generate spiral toolpath (simplified)
|
|
618
|
+
const depth = operation.depth;
|
|
619
|
+
const depthPerPass = operation.depthPerPass;
|
|
620
|
+
const radius = tool.diameter / 2;
|
|
621
|
+
|
|
622
|
+
for (let z = 0; z < depth; z += depthPerPass) {
|
|
623
|
+
for (let angle = 0; angle < Math.PI * 2; angle += 0.1) {
|
|
624
|
+
const x = Math.cos(angle) * (radius + angle * 2);
|
|
625
|
+
const y = Math.sin(angle) * (radius + angle * 2);
|
|
626
|
+
toolpath.push(new THREE.Vector3(x, y, -z));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
operation.toolpath = toolpath;
|
|
631
|
+
this.toolpaths.set(operation.id, toolpath);
|
|
632
|
+
return toolpath;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Generate G-code (FANUC dialect)
|
|
637
|
+
*/
|
|
638
|
+
fanucPost(operations) {
|
|
639
|
+
let gcode = '';
|
|
640
|
+
gcode += '%\n';
|
|
641
|
+
gcode += 'O0001\n'; // Program number
|
|
642
|
+
gcode += 'G21 G40 G49 H0 M6\n'; // Metric, cancel offsets
|
|
643
|
+
gcode += ';\n';
|
|
644
|
+
|
|
645
|
+
for (const op of operations) {
|
|
646
|
+
gcode += `; ${op.type} Operation\n`;
|
|
647
|
+
gcode += `S${op.spindleSpeed} M3\n`; // Spindle on
|
|
648
|
+
gcode += `F${op.feedRate}\n`; // Feed rate
|
|
649
|
+
|
|
650
|
+
// Generate moves for toolpath
|
|
651
|
+
if (op.toolpath && op.toolpath.length > 0) {
|
|
652
|
+
for (const point of op.toolpath) {
|
|
653
|
+
gcode += `G1 X${point.x.toFixed(3)} Y${point.y.toFixed(3)} Z${point.z.toFixed(3)}\n`;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
gcode += 'M5\n'; // Spindle off
|
|
658
|
+
gcode += ';\n';
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
gcode += 'M30\n';
|
|
662
|
+
gcode += '%\n';
|
|
663
|
+
return gcode;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Generate G-code (GRBL dialect - CNC mills)
|
|
668
|
+
*/
|
|
669
|
+
grblPost(operations) {
|
|
670
|
+
let gcode = '';
|
|
671
|
+
gcode += '; GRBL Toolpath\n';
|
|
672
|
+
gcode += 'G21 ; Metric\n';
|
|
673
|
+
gcode += 'G90 ; Absolute positioning\n';
|
|
674
|
+
gcode += ';\n';
|
|
675
|
+
|
|
676
|
+
for (const op of operations) {
|
|
677
|
+
gcode += `; ${op.type}\n`;
|
|
678
|
+
gcode += `M3 S${op.spindleSpeed}\n`; // Spindle on
|
|
679
|
+
gcode += `G4 P1\n`; // Dwell for spindle ramp
|
|
680
|
+
|
|
681
|
+
if (op.toolpath && op.toolpath.length > 0) {
|
|
682
|
+
for (const point of op.toolpath) {
|
|
683
|
+
gcode += `G1 X${point.x.toFixed(3)} Y${point.y.toFixed(3)} Z${point.z.toFixed(3)} F${op.feedRate}\n`;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
gcode += 'M5 ; Spindle off\n';
|
|
688
|
+
gcode += ';\n';
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return gcode;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Generate G-code (LinuxCNC dialect)
|
|
696
|
+
*/
|
|
697
|
+
linuxcncPost(operations) {
|
|
698
|
+
let gcode = '';
|
|
699
|
+
gcode += '( LinuxCNC Toolpath )\n';
|
|
700
|
+
gcode += 'G21 (Metric)\n';
|
|
701
|
+
gcode += 'G90 (Absolute)\n';
|
|
702
|
+
gcode += ';\n';
|
|
703
|
+
|
|
704
|
+
for (const op of operations) {
|
|
705
|
+
gcode += `( ${op.type} )\n`;
|
|
706
|
+
gcode += `M3 S${op.spindleSpeed}\n`;
|
|
707
|
+
|
|
708
|
+
if (op.toolpath && op.toolpath.length > 0) {
|
|
709
|
+
for (const point of op.toolpath) {
|
|
710
|
+
gcode += `G1 X${point.x.toFixed(4)} Y${point.y.toFixed(4)} Z${point.z.toFixed(4)} F${op.feedRate}\n`;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
gcode += 'M5\n';
|
|
715
|
+
gcode += ';\n';
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return gcode;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Generate G-code for all operations
|
|
723
|
+
*/
|
|
724
|
+
generateGCode(postProcessor = 'fanuc') {
|
|
725
|
+
const operations = Array.from(this.operations.values());
|
|
726
|
+
|
|
727
|
+
// Generate toolpaths if not already done
|
|
728
|
+
for (const op of operations) {
|
|
729
|
+
if (op.toolpath.length === 0) {
|
|
730
|
+
this.generateToolpath(op);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Use post-processor
|
|
735
|
+
const post = this.postProcessors[postProcessor];
|
|
736
|
+
if (!post) {
|
|
737
|
+
console.warn(`Unknown post-processor: ${postProcessor}`);
|
|
738
|
+
return '';
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const gcode = post(operations);
|
|
742
|
+
|
|
743
|
+
// Download as file
|
|
744
|
+
const blob = new Blob([gcode], { type: 'text/plain' });
|
|
745
|
+
const url = URL.createObjectURL(blob);
|
|
746
|
+
const a = document.createElement('a');
|
|
747
|
+
a.href = url;
|
|
748
|
+
a.download = `toolpath_${postProcessor}.nc`;
|
|
749
|
+
a.click();
|
|
750
|
+
URL.revokeObjectURL(url);
|
|
751
|
+
|
|
752
|
+
return gcode;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Setup event listeners
|
|
757
|
+
*/
|
|
758
|
+
setupEventListeners() {
|
|
759
|
+
// Listen for operation parameter changes
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Execute command from agent API
|
|
764
|
+
*/
|
|
765
|
+
execute(command, params) {
|
|
766
|
+
switch (command) {
|
|
767
|
+
case 'newOperation':
|
|
768
|
+
return this.newOperation(params.type);
|
|
769
|
+
case 'selectTool':
|
|
770
|
+
return this.selectTool(params.toolId);
|
|
771
|
+
case 'calculateFeedsAndSpeeds':
|
|
772
|
+
return this.calculateFeedsAndSpeeds();
|
|
773
|
+
case 'simulateToolpath':
|
|
774
|
+
return this.simulateToolpath();
|
|
775
|
+
case 'generateGCode':
|
|
776
|
+
return this.generateGCode(params.postProcessor || 'fanuc');
|
|
777
|
+
case 'setMachineType':
|
|
778
|
+
return this.setMachineType(params.type);
|
|
779
|
+
default:
|
|
780
|
+
console.warn(`Unknown CAM command: ${command}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export default FusionCAMModule;
|