cyclecad 0.2.2 → 0.3.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/API-BUILD-MANIFEST.txt +339 -0
- package/API-SERVER.md +535 -0
- package/Architecture-Deck.pptx +0 -0
- package/CLAUDE.md +172 -11
- package/CLI-BUILD-SUMMARY.md +504 -0
- package/CLI-INDEX.md +356 -0
- package/CLI-README.md +466 -0
- package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
- package/CONNECTED_FABS_GUIDE.md +612 -0
- package/CONNECTED_FABS_README.md +310 -0
- package/DELIVERABLES.md +343 -0
- package/DFM-ANALYZER-INTEGRATION.md +368 -0
- package/DFM-QUICK-START.js +253 -0
- package/Dockerfile +69 -0
- package/IMPLEMENTATION.md +327 -0
- package/LICENSE +31 -0
- package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
- package/MCP-INDEX.md +264 -0
- package/QUICKSTART-API.md +388 -0
- package/QUICKSTART-CLI.md +211 -0
- package/QUICKSTART-MCP.md +196 -0
- package/README-MCP.md +208 -0
- package/TEST-TOKEN-ENGINE.md +319 -0
- package/TOKEN-ENGINE-SUMMARY.md +266 -0
- package/TOKENS-README.md +263 -0
- package/TOOLS-REFERENCE.md +254 -0
- package/app/index.html +373 -3
- package/app/js/TOKEN-INTEGRATION.md +391 -0
- package/app/js/agent-api.js +3 -3
- package/app/js/ai-copilot.js +1435 -0
- package/app/js/cad-vr.js +917 -0
- package/app/js/cam-operations.js +638 -0
- package/app/js/cam-pipeline.js +840 -0
- package/app/js/collaboration-ui.js +995 -0
- package/app/js/collaboration.js +1116 -0
- package/app/js/connected-fabs-example.js +404 -0
- package/app/js/connected-fabs.js +1449 -0
- package/app/js/dfm-analyzer.js +1760 -0
- package/app/js/gcode-generator.js +485 -0
- package/app/js/gdt-training.js +1144 -0
- package/app/js/machine-profiles.js +534 -0
- package/app/js/marketplace-v2.js +766 -0
- package/app/js/marketplace.js +1994 -0
- package/app/js/material-library.js +2115 -0
- package/app/js/misumi-catalog.js +904 -0
- package/app/js/section-view.js +666 -0
- package/app/js/sketch-enhance.js +779 -0
- package/app/js/stock-manager.js +482 -0
- package/app/js/text-to-cad.js +806 -0
- package/app/js/token-dashboard.js +563 -0
- package/app/js/token-engine.js +743 -0
- package/app/js/tool-library.js +593 -0
- package/app/test-agent.html +1801 -0
- package/app/tutorials/advanced.html +1924 -0
- package/app/tutorials/basic.html +1160 -0
- package/app/tutorials/intermediate.html +1456 -0
- package/bin/cyclecad-cli.js +662 -0
- package/bin/cyclecad-mcp +2 -0
- package/bin/server.js +242 -0
- package/cycleCAD-Architecture.pptx +0 -0
- package/cycleCAD-Investor-Deck.pptx +0 -0
- package/demo-mcp.sh +60 -0
- package/docs/API-SERVER-SUMMARY.md +375 -0
- package/docs/API-SERVER.md +667 -0
- package/docs/CAM-EXAMPLES.md +344 -0
- package/docs/CAM-INTEGRATION.md +612 -0
- package/docs/CAM-QUICK-REFERENCE.md +199 -0
- package/docs/CLI-INTEGRATION.md +510 -0
- package/docs/CLI.md +872 -0
- package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
- package/docs/MARKETPLACE-INTEGRATION.md +467 -0
- package/docs/MARKETPLACE-SETUP.html +439 -0
- package/docs/MCP-SERVER.md +403 -0
- package/examples/api-client-example.js +488 -0
- package/examples/api-client-example.py +359 -0
- package/examples/batch-manufacturing.txt +28 -0
- package/examples/batch-simple.txt +26 -0
- package/linkedin-post-combined.md +31 -0
- package/model-marketplace.html +1273 -0
- package/package.json +14 -3
- package/server/api-server.js +1120 -0
- package/server/mcp-server.js +1161 -0
- package/test-api-server.js +432 -0
- package/test-mcp.js +198 -0
- package/~$cycleCAD-Investor-Deck.pptx +0 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stock/Workpiece Manager
|
|
3
|
+
* Manages stock material (rectangular, cylindrical, custom mesh) with 3D visualization
|
|
4
|
+
* Registers on window.cycleCAD.stock
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* window.cycleCAD.stock.setStock({ type: 'rectangular', x: 100, y: 100, z: 50, material: 'aluminum' })
|
|
8
|
+
* const stock = window.cycleCAD.stock.getStock()
|
|
9
|
+
* window.cycleCAD.stock.updateVisualization()
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
(function() {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
// Material database (density in g/cm³)
|
|
16
|
+
const MATERIALS = {
|
|
17
|
+
'aluminum': { name: 'Aluminum', density: 2.70, color: '#C0C0C0' },
|
|
18
|
+
'steel': { name: 'Steel', density: 7.85, color: '#555555' },
|
|
19
|
+
'stainless': { name: 'Stainless Steel', density: 8.0, color: '#808080' },
|
|
20
|
+
'brass': { name: 'Brass', density: 8.53, color: '#D4AF37' },
|
|
21
|
+
'copper': { name: 'Copper', density: 8.96, color: '#B87333' },
|
|
22
|
+
'titanium': { name: 'Titanium', density: 4.51, color: '#E7DECD' },
|
|
23
|
+
'cast-iron': { name: 'Cast Iron', density: 7.2, color: '#3C3C3C' },
|
|
24
|
+
'plastic': { name: 'Plastic (ABS)', density: 1.05, color: '#E8E8E8' },
|
|
25
|
+
'wood': { name: 'Wood', density: 0.75, color: '#8B4513' },
|
|
26
|
+
'foam': { name: 'Foam', density: 0.25, color: '#FFFACD' }
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Default stock
|
|
30
|
+
let currentStock = {
|
|
31
|
+
type: 'rectangular',
|
|
32
|
+
x: 100,
|
|
33
|
+
y: 100,
|
|
34
|
+
z: 50,
|
|
35
|
+
material: 'aluminum',
|
|
36
|
+
origin: { x: 0, y: 0, z: 0 },
|
|
37
|
+
rotated: false,
|
|
38
|
+
indexed: false,
|
|
39
|
+
indexCount: 1,
|
|
40
|
+
indexAxis: 'Z'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Three.js visualization objects
|
|
44
|
+
let stockGroup = null;
|
|
45
|
+
let stockMesh = null;
|
|
46
|
+
let boundingBox = null;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Stock manager API
|
|
50
|
+
*/
|
|
51
|
+
const stockAPI = {
|
|
52
|
+
/**
|
|
53
|
+
* Set stock configuration
|
|
54
|
+
*/
|
|
55
|
+
setStock(config) {
|
|
56
|
+
currentStock = {
|
|
57
|
+
...currentStock,
|
|
58
|
+
...config
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Validate
|
|
62
|
+
if (!MATERIALS[currentStock.material]) {
|
|
63
|
+
console.warn(`Material "${currentStock.material}" not found. Using aluminum.`);
|
|
64
|
+
currentStock.material = 'aluminum';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (currentStock.type === 'rectangular') {
|
|
68
|
+
if (currentStock.x <= 0 || currentStock.y <= 0 || currentStock.z <= 0) {
|
|
69
|
+
console.warn('Stock dimensions must be positive.');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
} else if (currentStock.type === 'cylindrical') {
|
|
73
|
+
if (currentStock.diameter <= 0 || currentStock.height <= 0) {
|
|
74
|
+
console.warn('Stock diameter and height must be positive.');
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get current stock
|
|
84
|
+
*/
|
|
85
|
+
getStock() {
|
|
86
|
+
return { ...currentStock };
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get stock dimensions object
|
|
91
|
+
*/
|
|
92
|
+
getDimensions() {
|
|
93
|
+
if (currentStock.type === 'rectangular') {
|
|
94
|
+
return {
|
|
95
|
+
x: currentStock.x,
|
|
96
|
+
y: currentStock.y,
|
|
97
|
+
z: currentStock.z
|
|
98
|
+
};
|
|
99
|
+
} else if (currentStock.type === 'cylindrical') {
|
|
100
|
+
return {
|
|
101
|
+
diameter: currentStock.diameter,
|
|
102
|
+
radius: currentStock.diameter / 2,
|
|
103
|
+
height: currentStock.height
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Calculate stock volume (mm³)
|
|
111
|
+
*/
|
|
112
|
+
calculateVolume() {
|
|
113
|
+
if (currentStock.type === 'rectangular') {
|
|
114
|
+
return currentStock.x * currentStock.y * currentStock.z;
|
|
115
|
+
} else if (currentStock.type === 'cylindrical') {
|
|
116
|
+
const radius = currentStock.diameter / 2;
|
|
117
|
+
return Math.PI * radius * radius * currentStock.height;
|
|
118
|
+
}
|
|
119
|
+
return 0;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Calculate stock weight (grams)
|
|
124
|
+
*/
|
|
125
|
+
calculateWeight() {
|
|
126
|
+
const volumeMm3 = this.calculateVolume();
|
|
127
|
+
const volumeCm3 = volumeMm3 / 1000; // Convert mm³ to cm³
|
|
128
|
+
const density = MATERIALS[currentStock.material].density;
|
|
129
|
+
return volumeCm3 * density;
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get material info
|
|
134
|
+
*/
|
|
135
|
+
getMaterial(materialId) {
|
|
136
|
+
return MATERIALS[materialId] || null;
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* List all materials
|
|
141
|
+
*/
|
|
142
|
+
listMaterials() {
|
|
143
|
+
return Object.keys(MATERIALS).map(id => ({
|
|
144
|
+
id,
|
|
145
|
+
...MATERIALS[id]
|
|
146
|
+
}));
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Set material
|
|
151
|
+
*/
|
|
152
|
+
setMaterial(materialId) {
|
|
153
|
+
if (!MATERIALS[materialId]) {
|
|
154
|
+
console.warn(`Material "${materialId}" not found.`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
currentStock.material = materialId;
|
|
159
|
+
return true;
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Auto-size stock from part bounding box
|
|
164
|
+
* Requires window._scene to be set (Three.js scene)
|
|
165
|
+
*/
|
|
166
|
+
autoSizeFromPart(offset = { x: 10, y: 10, z: 5 }) {
|
|
167
|
+
if (!window._scene) {
|
|
168
|
+
console.warn('Three.js scene not available.');
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let minX = Infinity, maxX = -Infinity;
|
|
173
|
+
let minY = Infinity, maxY = -Infinity;
|
|
174
|
+
let minZ = Infinity, maxZ = -Infinity;
|
|
175
|
+
|
|
176
|
+
// Iterate through all meshes in scene
|
|
177
|
+
window._scene.traverse(obj => {
|
|
178
|
+
if (obj.isMesh && obj.geometry) {
|
|
179
|
+
obj.geometry.computeBoundingBox();
|
|
180
|
+
const bb = obj.geometry.boundingBox;
|
|
181
|
+
|
|
182
|
+
minX = Math.min(minX, bb.min.x);
|
|
183
|
+
maxX = Math.max(maxX, bb.max.x);
|
|
184
|
+
minY = Math.min(minY, bb.min.y);
|
|
185
|
+
maxY = Math.max(maxY, bb.max.y);
|
|
186
|
+
minZ = Math.min(minZ, bb.min.z);
|
|
187
|
+
maxZ = Math.max(maxZ, bb.max.z);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (minX === Infinity) {
|
|
192
|
+
console.warn('No geometry found in scene.');
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const width = maxX - minX + offset.x;
|
|
197
|
+
const depth = maxY - minY + offset.y;
|
|
198
|
+
const height = maxZ - minZ + offset.z;
|
|
199
|
+
|
|
200
|
+
currentStock.type = 'rectangular';
|
|
201
|
+
currentStock.x = width;
|
|
202
|
+
currentStock.y = depth;
|
|
203
|
+
currentStock.z = height;
|
|
204
|
+
currentStock.origin = {
|
|
205
|
+
x: minX - offset.x / 2,
|
|
206
|
+
y: minY - offset.y / 2,
|
|
207
|
+
z: minZ - offset.z / 2
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return true;
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Enable indexed stock (rotary axis)
|
|
215
|
+
*/
|
|
216
|
+
setIndexed(enabled, count = 1, axis = 'Z') {
|
|
217
|
+
currentStock.indexed = enabled;
|
|
218
|
+
currentStock.indexCount = count;
|
|
219
|
+
currentStock.indexAxis = axis;
|
|
220
|
+
return true;
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Update Three.js visualization
|
|
225
|
+
* Requires window._scene and window._renderer to be set
|
|
226
|
+
*/
|
|
227
|
+
updateVisualization() {
|
|
228
|
+
if (!window._scene || !window._renderer) {
|
|
229
|
+
console.warn('Three.js scene or renderer not available.');
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Remove old stock group if it exists
|
|
234
|
+
if (stockGroup) {
|
|
235
|
+
window._scene.remove(stockGroup);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Create new group
|
|
239
|
+
stockGroup = new window.THREE.Group();
|
|
240
|
+
stockGroup.name = 'StockWireframe';
|
|
241
|
+
|
|
242
|
+
const matColor = MATERIALS[currentStock.material].color;
|
|
243
|
+
|
|
244
|
+
if (currentStock.type === 'rectangular') {
|
|
245
|
+
// Create wireframe box
|
|
246
|
+
const geom = new window.THREE.BoxGeometry(
|
|
247
|
+
currentStock.x,
|
|
248
|
+
currentStock.y,
|
|
249
|
+
currentStock.z
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const matWireframe = new window.THREE.LineBasicMaterial({
|
|
253
|
+
color: matColor,
|
|
254
|
+
linewidth: 2
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const wireframe = new window.THREE.LineSegments(
|
|
258
|
+
new window.THREE.EdgesGeometry(geom),
|
|
259
|
+
matWireframe
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
wireframe.position.set(
|
|
263
|
+
currentStock.origin.x + currentStock.x / 2,
|
|
264
|
+
currentStock.origin.y + currentStock.y / 2,
|
|
265
|
+
currentStock.origin.z + currentStock.z / 2
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
stockGroup.add(wireframe);
|
|
269
|
+
|
|
270
|
+
// Optional: add semi-transparent box mesh
|
|
271
|
+
const matMesh = new window.THREE.MeshBasicMaterial({
|
|
272
|
+
color: matColor,
|
|
273
|
+
transparent: true,
|
|
274
|
+
opacity: 0.1,
|
|
275
|
+
depthWrite: false
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
stockMesh = new window.THREE.Mesh(geom, matMesh);
|
|
279
|
+
stockMesh.position.copy(wireframe.position);
|
|
280
|
+
stockGroup.add(stockMesh);
|
|
281
|
+
|
|
282
|
+
} else if (currentStock.type === 'cylindrical') {
|
|
283
|
+
// Create wireframe cylinder
|
|
284
|
+
const geom = new window.THREE.CylinderGeometry(
|
|
285
|
+
currentStock.diameter / 2,
|
|
286
|
+
currentStock.diameter / 2,
|
|
287
|
+
currentStock.height,
|
|
288
|
+
32
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const matWireframe = new window.THREE.LineBasicMaterial({
|
|
292
|
+
color: matColor,
|
|
293
|
+
linewidth: 2
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const wireframe = new window.THREE.LineSegments(
|
|
297
|
+
new window.THREE.EdgesGeometry(geom),
|
|
298
|
+
matWireframe
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
wireframe.position.set(
|
|
302
|
+
currentStock.origin.x,
|
|
303
|
+
currentStock.origin.y + currentStock.height / 2,
|
|
304
|
+
currentStock.origin.z
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
stockGroup.add(wireframe);
|
|
308
|
+
|
|
309
|
+
// Semi-transparent cylinder
|
|
310
|
+
const matMesh = new window.THREE.MeshBasicMaterial({
|
|
311
|
+
color: matColor,
|
|
312
|
+
transparent: true,
|
|
313
|
+
opacity: 0.1,
|
|
314
|
+
depthWrite: false
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
stockMesh = new window.THREE.Mesh(geom, matMesh);
|
|
318
|
+
stockMesh.position.copy(wireframe.position);
|
|
319
|
+
stockGroup.add(stockMesh);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
window._scene.add(stockGroup);
|
|
323
|
+
return true;
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Hide stock visualization
|
|
328
|
+
*/
|
|
329
|
+
hideVisualization() {
|
|
330
|
+
if (stockGroup) {
|
|
331
|
+
stockGroup.visible = false;
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Show stock visualization
|
|
337
|
+
*/
|
|
338
|
+
showVisualization() {
|
|
339
|
+
if (stockGroup) {
|
|
340
|
+
stockGroup.visible = true;
|
|
341
|
+
} else {
|
|
342
|
+
this.updateVisualization();
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Get stock info panel HTML
|
|
348
|
+
*/
|
|
349
|
+
getInfoHTML() {
|
|
350
|
+
const volume = this.calculateVolume();
|
|
351
|
+
const weight = this.calculateWeight();
|
|
352
|
+
const matInfo = MATERIALS[currentStock.material];
|
|
353
|
+
|
|
354
|
+
let dimStr = '';
|
|
355
|
+
if (currentStock.type === 'rectangular') {
|
|
356
|
+
dimStr = `${currentStock.x.toFixed(1)} × ${currentStock.y.toFixed(1)} × ${currentStock.z.toFixed(1)} mm`;
|
|
357
|
+
} else if (currentStock.type === 'cylindrical') {
|
|
358
|
+
dimStr = `Ø${currentStock.diameter.toFixed(1)} × ${currentStock.height.toFixed(1)} mm`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return `
|
|
362
|
+
<div style="font-size: 12px; line-height: 1.6;">
|
|
363
|
+
<strong>Stock Type:</strong> ${currentStock.type.charAt(0).toUpperCase() + currentStock.type.slice(1)}<br/>
|
|
364
|
+
<strong>Dimensions:</strong> ${dimStr}<br/>
|
|
365
|
+
<strong>Material:</strong> ${matInfo.name}<br/>
|
|
366
|
+
<strong>Volume:</strong> ${volume.toFixed(0)} mm³<br/>
|
|
367
|
+
<strong>Weight:</strong> ${weight.toFixed(1)} g<br/>
|
|
368
|
+
${currentStock.indexed ? `<strong>Indexed:</strong> ${currentStock.indexCount} positions (${currentStock.indexAxis})<br/>` : ''}
|
|
369
|
+
</div>
|
|
370
|
+
`;
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Compare stock vs part (how much material to remove)
|
|
375
|
+
*/
|
|
376
|
+
compareWithPart() {
|
|
377
|
+
if (!window._scene) {
|
|
378
|
+
console.warn('Three.js scene not available.');
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
let partVolume = 0;
|
|
383
|
+
let partBounds = {
|
|
384
|
+
minX: Infinity, maxX: -Infinity,
|
|
385
|
+
minY: Infinity, maxY: -Infinity,
|
|
386
|
+
minZ: Infinity, maxZ: -Infinity
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
window._scene.traverse(obj => {
|
|
390
|
+
if (obj.isMesh && obj.geometry && obj !== stockMesh) {
|
|
391
|
+
obj.geometry.computeBoundingBox();
|
|
392
|
+
const bb = obj.geometry.boundingBox;
|
|
393
|
+
|
|
394
|
+
// Rough volume estimate from bounding box
|
|
395
|
+
const w = bb.max.x - bb.min.x;
|
|
396
|
+
const h = bb.max.y - bb.min.y;
|
|
397
|
+
const d = bb.max.z - bb.min.z;
|
|
398
|
+
partVolume += w * h * d;
|
|
399
|
+
|
|
400
|
+
partBounds.minX = Math.min(partBounds.minX, bb.min.x);
|
|
401
|
+
partBounds.maxX = Math.max(partBounds.maxX, bb.max.x);
|
|
402
|
+
partBounds.minY = Math.min(partBounds.minY, bb.min.y);
|
|
403
|
+
partBounds.maxY = Math.max(partBounds.maxY, bb.max.y);
|
|
404
|
+
partBounds.minZ = Math.min(partBounds.minZ, bb.min.z);
|
|
405
|
+
partBounds.maxZ = Math.max(partBounds.maxZ, bb.max.z);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const stockVolume = this.calculateVolume();
|
|
410
|
+
const removalVolume = stockVolume - partVolume;
|
|
411
|
+
const removalPercent = (removalVolume / stockVolume * 100).toFixed(1);
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
stockVolume: stockVolume,
|
|
415
|
+
partVolume: partVolume,
|
|
416
|
+
removalVolume: removalVolume,
|
|
417
|
+
removalPercent: removalPercent,
|
|
418
|
+
partBounds: partBounds
|
|
419
|
+
};
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Clip toolpaths to stock bounds
|
|
424
|
+
*/
|
|
425
|
+
clipToStock(toolpaths) {
|
|
426
|
+
if (!toolpaths || !Array.isArray(toolpaths)) return [];
|
|
427
|
+
|
|
428
|
+
const dims = this.getDimensions();
|
|
429
|
+
const origin = currentStock.origin;
|
|
430
|
+
|
|
431
|
+
return toolpaths.map(point => {
|
|
432
|
+
let clipped = { ...point };
|
|
433
|
+
|
|
434
|
+
if (currentStock.type === 'rectangular') {
|
|
435
|
+
clipped.x = Math.max(origin.x, Math.min(point.x, origin.x + dims.x));
|
|
436
|
+
clipped.y = Math.max(origin.y, Math.min(point.y, origin.y + dims.y));
|
|
437
|
+
clipped.z = Math.max(origin.z, Math.min(point.z, origin.z + dims.z));
|
|
438
|
+
} else if (currentStock.type === 'cylindrical') {
|
|
439
|
+
const cx = origin.x;
|
|
440
|
+
const cy = origin.y;
|
|
441
|
+
const rad = Math.sqrt((point.x - cx) ** 2 + (point.y - cy) ** 2);
|
|
442
|
+
|
|
443
|
+
if (rad > dims.radius) {
|
|
444
|
+
const angle = Math.atan2(point.y - cy, point.x - cx);
|
|
445
|
+
clipped.x = cx + Math.cos(angle) * dims.radius;
|
|
446
|
+
clipped.y = cy + Math.sin(angle) * dims.radius;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
clipped.z = Math.max(origin.y, Math.min(point.z, origin.y + dims.height));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return clipped;
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Export stock config as JSON
|
|
458
|
+
*/
|
|
459
|
+
exportConfig() {
|
|
460
|
+
return JSON.stringify(currentStock, null, 2);
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Import stock config from JSON
|
|
465
|
+
*/
|
|
466
|
+
importConfig(jsonString) {
|
|
467
|
+
try {
|
|
468
|
+
const config = JSON.parse(jsonString);
|
|
469
|
+
return this.setStock(config);
|
|
470
|
+
} catch (err) {
|
|
471
|
+
console.error('Failed to import stock config:', err);
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// Register on window.cycleCAD
|
|
478
|
+
window.cycleCAD = window.cycleCAD || {};
|
|
479
|
+
window.cycleCAD.stock = stockAPI;
|
|
480
|
+
|
|
481
|
+
console.log('[cycleCAD.stock] Module loaded. Stock manager ready.');
|
|
482
|
+
})();
|