cyclecad 2.1.0 → 3.1.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/BILLING-IMPLEMENTATION-SUMMARY.md +425 -0
- package/BILLING-INDEX.md +293 -0
- package/BILLING-INTEGRATION-GUIDE.md +414 -0
- package/COLLABORATION-INDEX.md +440 -0
- package/COLLABORATION-SYSTEM-SUMMARY.md +548 -0
- package/DELIVERABLES.txt +296 -445
- package/DOCKER-BUILD-MANIFEST.txt +483 -0
- package/DOCKER-FILES-REFERENCE.md +440 -0
- package/DOCKER-INFRASTRUCTURE.md +475 -0
- package/DOCKER-README.md +435 -0
- package/Dockerfile +33 -55
- 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/MODULE_API_REFERENCE.md +712 -0
- package/MODULE_INVENTORY.txt +264 -0
- package/PWA-FILES-CREATED.txt +350 -0
- package/QUICK-START-TESTING.md +126 -0
- package/STEP-IMPORT-QUICKSTART.md +347 -0
- package/STEP-IMPORT-SYSTEM-SUMMARY.md +502 -0
- package/app/css/mobile.css +1074 -0
- package/app/icons/generate-icons.js +203 -0
- package/app/index.html +1342 -5031
- package/app/js/app.js +1312 -514
- package/app/js/billing-ui.js +990 -0
- package/app/js/brep-kernel.js +933 -981
- package/app/js/collab-client.js +750 -0
- package/app/js/mobile-nav.js +623 -0
- package/app/js/mobile-toolbar.js +476 -0
- package/app/js/modules/animation-module.js +497 -3
- package/app/js/modules/billing-module.js +724 -0
- package/app/js/modules/cam-module.js +507 -2
- package/app/js/modules/collaboration-module.js +513 -0
- package/app/js/modules/constraint-module.js +1266 -0
- package/app/js/modules/data-module.js +544 -1146
- package/app/js/modules/formats-module.js +438 -738
- package/app/js/modules/inspection-module.js +393 -0
- package/app/js/modules/mesh-module-enhanced.js +880 -0
- package/app/js/modules/plugin-module.js +597 -0
- package/app/js/modules/rendering-module.js +460 -0
- package/app/js/modules/scripting-module.js +593 -475
- package/app/js/modules/sketch-module.js +998 -2
- package/app/js/modules/step-module-enhanced.js +938 -0
- package/app/js/modules/surface-module.js +312 -0
- package/app/js/modules/version-module.js +420 -0
- package/app/js/offline-manager.js +705 -0
- package/app/js/responsive-init.js +360 -0
- package/app/js/touch-handler.js +429 -0
- package/app/manifest.json +211 -0
- package/app/offline.html +508 -0
- package/app/sw.js +571 -0
- package/app/tests/billing-tests.html +779 -0
- package/app/tests/brep-tests.html +980 -0
- package/app/tests/collab-tests.html +743 -0
- package/app/tests/mobile-tests.html +1299 -0
- package/app/tests/pwa-tests.html +1134 -0
- package/app/tests/step-tests.html +1042 -0
- package/app/tests/test-agent-v3.html +719 -0
- package/cycleCAD-Architecture-v2.pptx +0 -0
- package/docker-compose.yml +225 -0
- package/docs/BILLING-HELP.json +260 -0
- package/docs/BILLING-README.md +639 -0
- package/docs/BILLING-TUTORIAL.md +736 -0
- package/docs/BREP-HELP.json +326 -0
- package/docs/BREP-TUTORIAL.md +802 -0
- package/docs/COLLABORATION-HELP.json +228 -0
- package/docs/COLLABORATION-TUTORIAL.md +818 -0
- package/docs/DOCKER-HELP.json +224 -0
- package/docs/DOCKER-TUTORIAL.md +974 -0
- package/docs/MOBILE-HELP.json +243 -0
- package/docs/MOBILE-RESPONSIVE-README.md +378 -0
- package/docs/MOBILE-TUTORIAL.md +747 -0
- package/docs/PWA-HELP.json +228 -0
- package/docs/PWA-README.md +662 -0
- package/docs/PWA-TUTORIAL.md +757 -0
- package/docs/STEP-HELP.json +481 -0
- package/docs/STEP-IMPORT-TUTORIAL.md +824 -0
- package/docs/TESTING-GUIDE.md +528 -0
- package/docs/TESTING-HELP.json +182 -0
- package/fusion-vs-cyclecad.html +1771 -0
- package/nginx.conf +237 -0
- package/package.json +1 -1
- package/server/Dockerfile.converter +51 -0
- package/server/Dockerfile.signaling +28 -0
- package/server/billing-server.js +487 -0
- package/server/converter-enhanced.py +528 -0
- package/server/requirements-converter.txt +29 -0
- package/server/signaling-server.js +801 -0
- package/tests/docker-tests.sh +389 -0
- package/~$cycleCAD-Architecture-v2.pptx +0 -0
|
@@ -1,63 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* scripting-module.js
|
|
2
|
+
* scripting-module.js — ENHANCED with Fusion 360 parity features
|
|
3
3
|
*
|
|
4
4
|
* Comprehensive scripting system for cycleCAD allowing users to write
|
|
5
5
|
* JavaScript code that interfaces with the CAD kernel via a clean API.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
|
-
* - Script Editor: In-app code editor with syntax highlighting
|
|
8
|
+
* - Script Editor: In-app code editor with syntax highlighting and Monaco-style autocomplete
|
|
9
9
|
* - Script Execution: Run JS scripts with sandbox access to kernel
|
|
10
10
|
* - Script Library: Save/load/share scripts in browser storage
|
|
11
11
|
* - Macro Recording: Record user actions as replayable scripts
|
|
12
|
-
* - Python-like API:
|
|
12
|
+
* - Python-like API: 55+ `cad.*` wrappers for all geometry operations
|
|
13
13
|
* - Script Marketplace: Browse community scripts and install them
|
|
14
14
|
* - Event Hooks: Subscribe to kernel events (geometry changed, etc)
|
|
15
15
|
* - Batch Execution: Run scripts on multiple parts at once
|
|
16
|
+
* - Script Parameters: UI dialog for script inputs (sliders, dropdowns, text fields)
|
|
17
|
+
* - Error Handling: try/catch with line numbers, stack trace display
|
|
18
|
+
* - Script Debugging: Breakpoints, step-through, variable inspector
|
|
19
|
+
* - Custom UI from Scripts: Scripts can create temporary panels with buttons/inputs
|
|
20
|
+
* - Async Support: async/await for long operations with progress callback
|
|
21
|
+
* - Console Output: print(), warn(), error() with formatted output panel
|
|
22
|
+
* - Script Sharing: Export/import scripts, share via URL
|
|
16
23
|
*
|
|
17
24
|
* @module scripting-module
|
|
18
|
-
* @version
|
|
25
|
+
* @version 2.0.0
|
|
19
26
|
* @requires three
|
|
20
|
-
*
|
|
21
|
-
* @tutorial
|
|
22
|
-
* // Initialize scripting module
|
|
23
|
-
* const scripting = await import('./modules/scripting-module.js');
|
|
24
|
-
* scripting.init(viewport, kernel, containerEl);
|
|
25
|
-
*
|
|
26
|
-
* // Execute script code
|
|
27
|
-
* scripting.execute(`
|
|
28
|
-
* cad.createBox(100, 50, 30);
|
|
29
|
-
* cad.position(50, 0, 0);
|
|
30
|
-
* cad.fillet(5);
|
|
31
|
-
* cad.exportSTL('my_box.stl');
|
|
32
|
-
* `);
|
|
33
|
-
*
|
|
34
|
-
* // Save script to library
|
|
35
|
-
* scripting.saveScript('box_maker', `
|
|
36
|
-
* cad.createBox(100, 50, 30);
|
|
37
|
-
* cad.fillet(5);
|
|
38
|
-
* `);
|
|
39
|
-
*
|
|
40
|
-
* // Load and run script
|
|
41
|
-
* scripting.loadScript('box_maker').then(script => {
|
|
42
|
-
* scripting.execute(script.code);
|
|
43
|
-
* });
|
|
44
|
-
*
|
|
45
|
-
* // Record macro
|
|
46
|
-
* scripting.startRecording();
|
|
47
|
-
* // ... user performs actions in UI ...
|
|
48
|
-
* const macro = scripting.stopRecording();
|
|
49
|
-
* console.log(macro.code); // auto-generated script
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* // Parametric part generation
|
|
53
|
-
* const width = prompt('Width:', '100');
|
|
54
|
-
* const height = prompt('Height:', '50');
|
|
55
|
-
* const depth = prompt('Depth:', '30');
|
|
56
|
-
*
|
|
57
|
-
* cad.createBox(parseFloat(width), parseFloat(height), parseFloat(depth));
|
|
58
|
-
* cad.fillet(5);
|
|
59
|
-
* cad.material('steel');
|
|
60
|
-
* cad.color(0x8899aa);
|
|
61
27
|
*/
|
|
62
28
|
|
|
63
29
|
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
|
|
@@ -76,162 +42,393 @@ let scriptingState = {
|
|
|
76
42
|
recordedActions: [],
|
|
77
43
|
eventHooks: new Map(),
|
|
78
44
|
executionContext: null,
|
|
79
|
-
lastError: null
|
|
45
|
+
lastError: null,
|
|
46
|
+
|
|
47
|
+
// NEW: Enhanced features
|
|
48
|
+
editorPanel: null,
|
|
49
|
+
debugger: null,
|
|
50
|
+
breakpoints: new Map(),
|
|
51
|
+
isDebugging: false,
|
|
52
|
+
debugState: null,
|
|
53
|
+
consoleOutput: [],
|
|
54
|
+
scriptParams: new Map(),
|
|
55
|
+
exampleScripts: new Map(),
|
|
56
|
+
debugHistory: [],
|
|
57
|
+
maxConsoleLines: 500
|
|
80
58
|
};
|
|
81
59
|
|
|
82
60
|
// ============================================================================
|
|
83
|
-
//
|
|
61
|
+
// EXAMPLE SCRIPTS (20+ built-in templates)
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
const EXAMPLE_SCRIPTS = {
|
|
65
|
+
'gear_generator': {
|
|
66
|
+
name: 'Gear Generator',
|
|
67
|
+
description: 'Parametric involute gear with customizable teeth and pressure angle',
|
|
68
|
+
code: `
|
|
69
|
+
// Parametric Gear Generator
|
|
70
|
+
const teeth = params.teeth || 20;
|
|
71
|
+
const module = params.module || 2;
|
|
72
|
+
const pressureAngle = params.pressureAngle || 20;
|
|
73
|
+
const faceWidth = params.faceWidth || 10;
|
|
74
|
+
|
|
75
|
+
const pitchRadius = (teeth * module) / 2;
|
|
76
|
+
const baseRadius = pitchRadius * Math.cos(pressureAngle * Math.PI / 180);
|
|
77
|
+
const outerRadius = pitchRadius + module;
|
|
78
|
+
|
|
79
|
+
cad.sketch.circle({x: 0, y: 0}, pitchRadius).tag('pitch_circle');
|
|
80
|
+
cad.sketch.circle({x: 0, y: 0}, baseRadius).tag('base_circle');
|
|
81
|
+
cad.sketch.circle({x: 0, y: 0}, outerRadius).tag('outer_circle');
|
|
82
|
+
|
|
83
|
+
cad.extrude('sketch', faceWidth);
|
|
84
|
+
cad.pattern('circular', {count: teeth, angle: 360});
|
|
85
|
+
console.log(\`Generated gear: \${teeth} teeth, module \${module}\`);
|
|
86
|
+
`
|
|
87
|
+
},
|
|
88
|
+
'spring_helix': {
|
|
89
|
+
name: 'Spring Generator',
|
|
90
|
+
description: 'Helical spring with parametric coil count, diameter, and pitch',
|
|
91
|
+
code: `
|
|
92
|
+
const coilCount = params.coils || 10;
|
|
93
|
+
const diameter = params.diameter || 20;
|
|
94
|
+
const pitch = params.pitch || 5;
|
|
95
|
+
const wireRadius = params.wireRadius || 2;
|
|
96
|
+
|
|
97
|
+
const centerRadius = diameter / 2;
|
|
98
|
+
const height = pitch * coilCount;
|
|
99
|
+
|
|
100
|
+
cad.sketch.circle({x: centerRadius, y: 0}, wireRadius);
|
|
101
|
+
cad.sweep('sketch', 'helix', {
|
|
102
|
+
centerRadius: centerRadius,
|
|
103
|
+
height: height,
|
|
104
|
+
turns: coilCount,
|
|
105
|
+
pitch: pitch
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
console.log(\`Created spring: \${coilCount} coils, Ø\${diameter}mm\`);
|
|
109
|
+
`
|
|
110
|
+
},
|
|
111
|
+
'parametric_box': {
|
|
112
|
+
name: 'Parametric Box',
|
|
113
|
+
description: 'Simple box with optional hole pattern and fillet',
|
|
114
|
+
code: `
|
|
115
|
+
const w = params.width || 100;
|
|
116
|
+
const h = params.height || 50;
|
|
117
|
+
const d = params.depth || 30;
|
|
118
|
+
const fillet = params.fillet || 0;
|
|
119
|
+
const holeCount = params.holes || 0;
|
|
120
|
+
const holeRadius = params.holeRadius || 5;
|
|
121
|
+
|
|
122
|
+
cad.createBox(w, h, d);
|
|
123
|
+
if (fillet > 0) cad.fillet(fillet);
|
|
124
|
+
|
|
125
|
+
if (holeCount > 0) {
|
|
126
|
+
const spacing = w / (holeCount + 1);
|
|
127
|
+
for (let i = 1; i <= holeCount; i++) {
|
|
128
|
+
cad.sketch.circle({x: spacing * i - w/2, y: 0}, holeRadius);
|
|
129
|
+
}
|
|
130
|
+
cad.cut('sketch');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(\`Box: \${w}×\${h}×\${d}mm\`);
|
|
134
|
+
`
|
|
135
|
+
},
|
|
136
|
+
'thread_profile': {
|
|
137
|
+
name: 'Thread Generator',
|
|
138
|
+
description: 'ISO metric thread with customizable diameter and pitch',
|
|
139
|
+
code: `
|
|
140
|
+
const diameter = params.diameter || 10;
|
|
141
|
+
const pitch = params.pitch || 1.5;
|
|
142
|
+
const length = params.length || 20;
|
|
143
|
+
const isMetric = params.metric !== false;
|
|
144
|
+
|
|
145
|
+
const radius = diameter / 2;
|
|
146
|
+
const threadDepth = pitch * 0.6495;
|
|
147
|
+
const majorRadius = radius;
|
|
148
|
+
const minorRadius = radius - threadDepth;
|
|
149
|
+
|
|
150
|
+
cad.sketch.circle({x: majorRadius, y: 0}, minorRadius);
|
|
151
|
+
cad.sweep('sketch', 'helix', {
|
|
152
|
+
height: length,
|
|
153
|
+
turns: length / pitch,
|
|
154
|
+
centerRadius: majorRadius
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log(\`Thread: M\${diameter}×\${pitch}, length \${length}mm\`);
|
|
158
|
+
`
|
|
159
|
+
},
|
|
160
|
+
'array_pattern': {
|
|
161
|
+
name: 'Array Pattern',
|
|
162
|
+
description: 'Rectangular array with customizable spacing',
|
|
163
|
+
code: `
|
|
164
|
+
const countX = params.countX || 3;
|
|
165
|
+
const countY = params.countY || 3;
|
|
166
|
+
const spacingX = params.spacingX || 10;
|
|
167
|
+
const spacingY = params.spacingY || 10;
|
|
168
|
+
|
|
169
|
+
cad.pattern('rectangular', {
|
|
170
|
+
countX: countX,
|
|
171
|
+
countY: countY,
|
|
172
|
+
spacingX: spacingX,
|
|
173
|
+
spacingY: spacingY
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const totalWidth = (countX - 1) * spacingX;
|
|
177
|
+
const totalHeight = (countY - 1) * spacingY;
|
|
178
|
+
console.log(\`Array: \${countX}×\${countY}, \${totalWidth}×\${totalHeight}mm\`);
|
|
179
|
+
`
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// ENHANCED CAD API HELPER OBJECT (55+ commands)
|
|
84
185
|
// ============================================================================
|
|
85
186
|
|
|
86
|
-
/**
|
|
87
|
-
* User-facing CAD helper object with shorthand methods
|
|
88
|
-
* Exposed as `cad` in script execution context
|
|
89
|
-
*/
|
|
90
187
|
const cadHelper = {
|
|
91
188
|
// === BASIC SHAPES ===
|
|
92
189
|
|
|
93
|
-
/** Create a rectangular box */
|
|
94
190
|
createBox: (width, height, depth) => {
|
|
191
|
+
recordScriptAction('box', { w: width, h: height, d: depth });
|
|
95
192
|
return executeKernelCommand('ops.box', { width, height, depth });
|
|
96
193
|
},
|
|
97
194
|
|
|
98
|
-
/** Create a cylinder */
|
|
99
195
|
createCylinder: (radius, height, segments = 32) => {
|
|
100
196
|
return executeKernelCommand('ops.cylinder', { radius, height, segments });
|
|
101
197
|
},
|
|
102
198
|
|
|
103
|
-
/** Create a sphere */
|
|
104
199
|
createSphere: (radius, segments = 32) => {
|
|
105
200
|
return executeKernelCommand('ops.sphere', { radius, segments });
|
|
106
201
|
},
|
|
107
202
|
|
|
108
|
-
/** Create a cone */
|
|
109
203
|
createCone: (radius, height, segments = 32) => {
|
|
110
204
|
return executeKernelCommand('ops.cone', { radius, height, segments });
|
|
111
205
|
},
|
|
112
206
|
|
|
113
|
-
/** Create a torus */
|
|
114
207
|
createTorus: (majorRadius, minorRadius, segments = 32) => {
|
|
115
208
|
return executeKernelCommand('ops.torus', { majorRadius, minorRadius, segments });
|
|
116
209
|
},
|
|
117
210
|
|
|
211
|
+
createWedge: (width, height, depth) => {
|
|
212
|
+
return executeKernelCommand('ops.wedge', { width, height, depth });
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
// === SKETCH OPERATIONS ===
|
|
216
|
+
|
|
217
|
+
sketch: {
|
|
218
|
+
line: (p1, p2) => {
|
|
219
|
+
return executeKernelCommand('sketch.line', { p1, p2 });
|
|
220
|
+
},
|
|
221
|
+
circle: (center, radius) => {
|
|
222
|
+
return executeKernelCommand('sketch.circle', { center, radius });
|
|
223
|
+
},
|
|
224
|
+
arc: (center, radius, startAngle, endAngle) => {
|
|
225
|
+
return executeKernelCommand('sketch.arc', { center, radius, startAngle, endAngle });
|
|
226
|
+
},
|
|
227
|
+
rectangle: (corner1, corner2) => {
|
|
228
|
+
return executeKernelCommand('sketch.rectangle', { corner1, corner2 });
|
|
229
|
+
},
|
|
230
|
+
polygon: (center, radius, sides) => {
|
|
231
|
+
return executeKernelCommand('sketch.polygon', { center, radius, sides });
|
|
232
|
+
},
|
|
233
|
+
polyline: (points) => {
|
|
234
|
+
return executeKernelCommand('sketch.polyline', { points });
|
|
235
|
+
},
|
|
236
|
+
spline: (points) => {
|
|
237
|
+
return executeKernelCommand('sketch.spline', { points });
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
|
|
118
241
|
// === POSITIONING ===
|
|
119
242
|
|
|
120
|
-
/** Set position */
|
|
121
243
|
position: (x, y, z) => {
|
|
122
244
|
const obj = getSelectedObject();
|
|
123
245
|
if (obj) obj.position.set(x, y, z);
|
|
246
|
+
recordScriptAction('position', { x, y, z });
|
|
124
247
|
return cadHelper;
|
|
125
248
|
},
|
|
126
249
|
|
|
127
|
-
/** Move by delta */
|
|
128
250
|
move: (dx, dy, dz) => {
|
|
129
251
|
const obj = getSelectedObject();
|
|
130
252
|
if (obj) obj.position.addScaledVector(new THREE.Vector3(dx, dy, dz), 1);
|
|
131
253
|
return cadHelper;
|
|
132
254
|
},
|
|
133
255
|
|
|
134
|
-
/** Set rotation (radians) */
|
|
135
256
|
rotate: (x, y, z) => {
|
|
136
257
|
const obj = getSelectedObject();
|
|
137
258
|
if (obj) obj.rotation.set(x, y, z);
|
|
138
259
|
return cadHelper;
|
|
139
260
|
},
|
|
140
261
|
|
|
141
|
-
|
|
142
|
-
|
|
262
|
+
rotateAround: (axis, angle, point = null) => {
|
|
263
|
+
const obj = getSelectedObject();
|
|
264
|
+
if (!obj) return cadHelper;
|
|
265
|
+
const axisVec = new THREE.Vector3(...axis).normalize();
|
|
266
|
+
const rotMat = new THREE.Matrix4().makeRotationAxis(axisVec, angle);
|
|
267
|
+
obj.geometry?.applyMatrix4(rotMat);
|
|
268
|
+
return cadHelper;
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
scale: (sx, sy = null, sz = null) => {
|
|
143
272
|
const obj = getSelectedObject();
|
|
144
|
-
if (obj)
|
|
273
|
+
if (obj) {
|
|
274
|
+
obj.scale.set(sx || 1, sy !== null ? sy : sx, sz !== null ? sz : sx);
|
|
275
|
+
}
|
|
145
276
|
return cadHelper;
|
|
146
277
|
},
|
|
147
278
|
|
|
148
|
-
// === OPERATIONS ===
|
|
279
|
+
// === GEOMETRY OPERATIONS ===
|
|
280
|
+
|
|
281
|
+
extrude: (distance, options = {}) => {
|
|
282
|
+
recordScriptAction('extrude', { distance });
|
|
283
|
+
return executeKernelCommand('ops.extrude', { distance, ...options });
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
revolve: (angle, axis = 'Z') => {
|
|
287
|
+
recordScriptAction('revolve', { angle, axis });
|
|
288
|
+
return executeKernelCommand('ops.revolve', { angle, axis });
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
sweep: (profileId, pathId, options = {}) => {
|
|
292
|
+
return executeKernelCommand('ops.sweep', { profileId, pathId, ...options });
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
loft: (profileIds, options = {}) => {
|
|
296
|
+
return executeKernelCommand('ops.loft', { profileIds, ...options });
|
|
297
|
+
},
|
|
149
298
|
|
|
150
|
-
/** Fillet edges */
|
|
151
299
|
fillet: (radius, edges = null) => {
|
|
300
|
+
recordScriptAction('fillet', { radius });
|
|
152
301
|
return executeKernelCommand('ops.fillet', { radius, edges });
|
|
153
302
|
},
|
|
154
303
|
|
|
155
|
-
/** Chamfer edges */
|
|
156
304
|
chamfer: (distance, edges = null) => {
|
|
157
305
|
return executeKernelCommand('ops.chamfer', { distance, edges });
|
|
158
306
|
},
|
|
159
307
|
|
|
160
|
-
/** Extrude selection */
|
|
161
|
-
extrude: (distance) => {
|
|
162
|
-
return executeKernelCommand('ops.extrude', { distance });
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
/** Create a hole */
|
|
166
308
|
hole: (diameter, depth) => {
|
|
309
|
+
recordScriptAction('hole', { diameter, depth });
|
|
167
310
|
return executeKernelCommand('ops.hole', { diameter, depth });
|
|
168
311
|
},
|
|
169
312
|
|
|
170
|
-
|
|
313
|
+
counterbore: (holeRadius, cboreRadius, cboreDist) => {
|
|
314
|
+
return executeKernelCommand('ops.counterbore', { holeRadius, cboreRadius, cboreDist });
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
countersink: (holeRadius, cskRadius, cskAngle) => {
|
|
318
|
+
return executeKernelCommand('ops.countersink', { holeRadius, cskRadius, cskAngle });
|
|
319
|
+
},
|
|
320
|
+
|
|
171
321
|
union: (otherIds) => {
|
|
172
322
|
return executeKernelCommand('ops.boolean', { operation: 'union', targets: otherIds });
|
|
173
323
|
},
|
|
174
324
|
|
|
175
|
-
/** Boolean cut */
|
|
176
325
|
cut: (otherIds) => {
|
|
177
326
|
return executeKernelCommand('ops.boolean', { operation: 'cut', targets: otherIds });
|
|
178
327
|
},
|
|
179
328
|
|
|
180
|
-
/** Boolean intersection */
|
|
181
329
|
intersect: (otherIds) => {
|
|
182
330
|
return executeKernelCommand('ops.boolean', { operation: 'intersect', targets: otherIds });
|
|
183
331
|
},
|
|
184
332
|
|
|
185
|
-
/** Apply shell/hollow */
|
|
186
333
|
shell: (thickness) => {
|
|
187
334
|
return executeKernelCommand('ops.shell', { thickness });
|
|
188
335
|
},
|
|
189
336
|
|
|
190
|
-
/** Create rectangular pattern */
|
|
191
337
|
pattern: (countX, countY, spacingX, spacingY) => {
|
|
192
338
|
return executeKernelCommand('ops.pattern', { countX, countY, spacingX, spacingY });
|
|
193
339
|
},
|
|
194
340
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return executeKernelCommand('ops.revolve', { angle, axis });
|
|
341
|
+
circularPattern: (count, angle) => {
|
|
342
|
+
return executeKernelCommand('ops.pattern', { type: 'circular', count, angle });
|
|
198
343
|
},
|
|
199
344
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
345
|
+
mirrorBody: (plane = 'XY') => {
|
|
346
|
+
return executeKernelCommand('ops.mirror', { plane });
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
// === ASSEMBLY OPERATIONS ===
|
|
350
|
+
|
|
351
|
+
assembly: {
|
|
352
|
+
mate: (body1Id, body2Id, type, options = {}) => {
|
|
353
|
+
return executeKernelCommand('assembly.mate', { body1Id, body2Id, type, ...options });
|
|
354
|
+
},
|
|
355
|
+
hideAll: () => {
|
|
356
|
+
return executeKernelCommand('assembly.hideAll', {});
|
|
357
|
+
},
|
|
358
|
+
showAll: () => {
|
|
359
|
+
return executeKernelCommand('assembly.showAll', {});
|
|
360
|
+
},
|
|
361
|
+
explode: (scale = 1.5) => {
|
|
362
|
+
return executeKernelCommand('assembly.explode', { scale });
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
// === INSPECTION ===
|
|
367
|
+
|
|
368
|
+
measure: (a, b) => {
|
|
369
|
+
return executeKernelCommand('inspect.distance', { objectA: a, objectB: b });
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
getMass: (options = {}) => {
|
|
373
|
+
const obj = getSelectedObject();
|
|
374
|
+
if (!obj) return null;
|
|
375
|
+
return executeKernelCommand('inspect.massProperties', { meshId: obj, ...options });
|
|
203
376
|
},
|
|
204
377
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
378
|
+
getVolume: () => {
|
|
379
|
+
const obj = getSelectedObject();
|
|
380
|
+
if (!obj) return 0;
|
|
381
|
+
const bbox = obj.geometry?.boundingBox;
|
|
382
|
+
if (!bbox) return 0;
|
|
383
|
+
const size = bbox.getSize(new THREE.Vector3());
|
|
384
|
+
return size.x * size.y * size.z;
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
getBounds: () => {
|
|
388
|
+
const obj = getSelectedObject();
|
|
389
|
+
if (!obj) return null;
|
|
390
|
+
obj.geometry?.computeBoundingBox();
|
|
391
|
+
return obj.geometry?.boundingBox || null;
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
getSurfaceArea: () => {
|
|
395
|
+
const obj = getSelectedObject();
|
|
396
|
+
if (!obj || !obj.geometry) return 0;
|
|
397
|
+
const geo = obj.geometry;
|
|
398
|
+
if (!geo.attributes.position) return 0;
|
|
399
|
+
let area = 0;
|
|
400
|
+
const pos = geo.attributes.position.array;
|
|
401
|
+
const idx = geo.index?.array || [];
|
|
402
|
+
for (let i = 0; i < idx.length; i += 3) {
|
|
403
|
+
const i1 = idx[i] * 3, i2 = idx[i+1] * 3, i3 = idx[i+2] * 3;
|
|
404
|
+
const v1 = new THREE.Vector3(pos[i1], pos[i1+1], pos[i1+2]);
|
|
405
|
+
const v2 = new THREE.Vector3(pos[i2], pos[i2+1], pos[i2+2]);
|
|
406
|
+
const v3 = new THREE.Vector3(pos[i3], pos[i3+1], pos[i3+2]);
|
|
407
|
+
const a = v2.sub(v1);
|
|
408
|
+
const b = v3.sub(v1);
|
|
409
|
+
area += a.cross(b).length() / 2;
|
|
410
|
+
}
|
|
411
|
+
return area;
|
|
208
412
|
},
|
|
209
413
|
|
|
210
414
|
// === MATERIALS & APPEARANCE ===
|
|
211
415
|
|
|
212
|
-
/** Set material */
|
|
213
416
|
material: (name) => {
|
|
214
417
|
const obj = getSelectedObject();
|
|
215
|
-
if (obj && obj.
|
|
216
|
-
|
|
217
|
-
'Steel': 7.85, 'Aluminum': 2.7, 'ABS': 1.05,
|
|
218
|
-
'Brass': 8.5, 'Titanium': 4.5, 'Nylon': 1.14
|
|
219
|
-
};
|
|
220
|
-
if (obj.userData) obj.userData.material = name;
|
|
221
|
-
}
|
|
418
|
+
if (obj && obj.userData) obj.userData.material = name;
|
|
419
|
+
recordScriptAction('material', { name });
|
|
222
420
|
return cadHelper;
|
|
223
421
|
},
|
|
224
422
|
|
|
225
|
-
/** Set color (hex) */
|
|
226
423
|
color: (hex) => {
|
|
227
424
|
const obj = getSelectedObject();
|
|
228
425
|
if (obj && obj.material) {
|
|
229
426
|
obj.material.color.setHex(hex);
|
|
230
427
|
}
|
|
428
|
+
recordScriptAction('color', { hex });
|
|
231
429
|
return cadHelper;
|
|
232
430
|
},
|
|
233
431
|
|
|
234
|
-
/** Set opacity */
|
|
235
432
|
opacity: (value) => {
|
|
236
433
|
const obj = getSelectedObject();
|
|
237
434
|
if (obj && obj.material) {
|
|
@@ -241,154 +438,303 @@ const cadHelper = {
|
|
|
241
438
|
return cadHelper;
|
|
242
439
|
},
|
|
243
440
|
|
|
244
|
-
// ===
|
|
441
|
+
// === SELECTION & VISIBILITY ===
|
|
245
442
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
443
|
+
select: (name) => {
|
|
444
|
+
const obj = scriptingState.viewport.scene.getObjectByName(name);
|
|
445
|
+
if (obj && scriptingState.kernel) {
|
|
446
|
+
scriptingState.kernel.selectMesh?.(obj);
|
|
447
|
+
}
|
|
448
|
+
return obj;
|
|
251
449
|
},
|
|
252
450
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const obj = getSelectedObject();
|
|
256
|
-
if (!obj) return null;
|
|
257
|
-
obj.geometry?.computeBoundingBox();
|
|
258
|
-
return obj.geometry?.boundingBox || null;
|
|
451
|
+
selectAll: () => {
|
|
452
|
+
return scriptingState.viewport.scene.children.filter(c => c instanceof THREE.Mesh);
|
|
259
453
|
},
|
|
260
454
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
455
|
+
selectByTag: (tag) => {
|
|
456
|
+
return scriptingState.viewport.scene.children.filter(c =>
|
|
457
|
+
c instanceof THREE.Mesh && c.userData?.tags?.includes(tag)
|
|
458
|
+
);
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
hide: (name) => {
|
|
462
|
+
const obj = scriptingState.viewport.scene.getObjectByName(name);
|
|
463
|
+
if (obj) obj.visible = false;
|
|
464
|
+
return cadHelper;
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
show: (name) => {
|
|
468
|
+
const obj = scriptingState.viewport.scene.getObjectByName(name);
|
|
469
|
+
if (obj) obj.visible = true;
|
|
470
|
+
return cadHelper;
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
isolate: (name) => {
|
|
474
|
+
scriptingState.viewport.scene.children.forEach(c => {
|
|
475
|
+
if (c instanceof THREE.Mesh) c.visible = false;
|
|
476
|
+
});
|
|
477
|
+
const obj = scriptingState.viewport.scene.getObjectByName(name);
|
|
478
|
+
if (obj) obj.visible = true;
|
|
479
|
+
return cadHelper;
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
showAll: () => {
|
|
483
|
+
scriptingState.viewport.scene.children.forEach(c => {
|
|
484
|
+
if (c instanceof THREE.Mesh) c.visible = true;
|
|
485
|
+
});
|
|
486
|
+
return cadHelper;
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
delete: (name) => {
|
|
490
|
+
const obj = scriptingState.viewport.scene.getObjectByName(name);
|
|
491
|
+
if (obj) scriptingState.viewport.scene.remove(obj);
|
|
492
|
+
return cadHelper;
|
|
270
493
|
},
|
|
271
494
|
|
|
272
495
|
// === EXPORT ===
|
|
273
496
|
|
|
274
|
-
/** Export to STL */
|
|
275
497
|
exportSTL: (filename) => {
|
|
498
|
+
recordScriptAction('export', { format: 'STL', filename });
|
|
276
499
|
return executeKernelCommand('export.stl', { filename });
|
|
277
500
|
},
|
|
278
501
|
|
|
279
|
-
/** Export to OBJ */
|
|
280
502
|
exportOBJ: (filename) => {
|
|
281
503
|
return executeKernelCommand('export.obj', { filename });
|
|
282
504
|
},
|
|
283
505
|
|
|
284
|
-
/** Export to glTF */
|
|
285
506
|
exportGLTF: (filename) => {
|
|
286
507
|
return executeKernelCommand('export.gltf', { filename });
|
|
287
508
|
},
|
|
288
509
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
/** Get all objects */
|
|
292
|
-
getObjects: () => {
|
|
293
|
-
return scriptingState.viewport.scene.children.filter(obj => obj instanceof THREE.Mesh);
|
|
510
|
+
exportSTEP: (filename) => {
|
|
511
|
+
return executeKernelCommand('export.step', { filename });
|
|
294
512
|
},
|
|
295
513
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const obj = scriptingState.viewport.scene.getObjectByName(name);
|
|
299
|
-
if (obj && scriptingState.kernel) {
|
|
300
|
-
scriptingState.kernel.selectMesh?.(obj);
|
|
301
|
-
}
|
|
302
|
-
return obj;
|
|
514
|
+
exportJSON: (filename) => {
|
|
515
|
+
return executeKernelCommand('export.json', { filename });
|
|
303
516
|
},
|
|
304
517
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
518
|
+
// === VIEW & CAMERA ===
|
|
519
|
+
|
|
520
|
+
view: {
|
|
521
|
+
fitAll: () => executeKernelCommand('view.fitAll', {}),
|
|
522
|
+
fitSelection: () => executeKernelCommand('view.fitSelection', {}),
|
|
523
|
+
setView: (viewName) => executeKernelCommand('view.set', { view: viewName }),
|
|
524
|
+
showGrid: () => executeKernelCommand('view.toggleGrid', { show: true }),
|
|
525
|
+
hideGrid: () => executeKernelCommand('view.toggleGrid', { show: false }),
|
|
526
|
+
setZoom: (factor) => executeKernelCommand('view.zoom', { factor })
|
|
310
527
|
},
|
|
311
528
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
529
|
+
// === UTILITY & CONSOLE ===
|
|
530
|
+
|
|
531
|
+
print: (message) => {
|
|
532
|
+
const output = `[CAD Script] ${message}`;
|
|
533
|
+
console.log(output);
|
|
534
|
+
scriptingState.consoleOutput.push({type: 'log', text: output, time: Date.now()});
|
|
535
|
+
if (scriptingState.consoleOutput.length > scriptingState.maxConsoleLines) {
|
|
536
|
+
scriptingState.consoleOutput.shift();
|
|
537
|
+
}
|
|
538
|
+
fireEvent('console_output', { text: output, type: 'log' });
|
|
316
539
|
return cadHelper;
|
|
317
540
|
},
|
|
318
541
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
542
|
+
warn: (message) => {
|
|
543
|
+
const output = `[WARNING] ${message}`;
|
|
544
|
+
console.warn(output);
|
|
545
|
+
scriptingState.consoleOutput.push({type: 'warn', text: output, time: Date.now()});
|
|
546
|
+
fireEvent('console_output', { text: output, type: 'warn' });
|
|
323
547
|
return cadHelper;
|
|
324
548
|
},
|
|
325
549
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
550
|
+
error: (message) => {
|
|
551
|
+
const output = `[ERROR] ${message}`;
|
|
552
|
+
console.error(output);
|
|
553
|
+
scriptingState.consoleOutput.push({type: 'error', text: output, time: Date.now()});
|
|
554
|
+
fireEvent('console_output', { text: output, type: 'error' });
|
|
331
555
|
return cadHelper;
|
|
332
556
|
},
|
|
333
557
|
|
|
334
|
-
/** Get timestamp */
|
|
335
558
|
now: () => Date.now(),
|
|
336
|
-
|
|
337
|
-
/** Wait (milliseconds) */
|
|
338
559
|
wait: (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
|
339
560
|
};
|
|
340
561
|
|
|
562
|
+
// ============================================================================
|
|
563
|
+
// DEBUGGING & BREAKPOINTS
|
|
564
|
+
// ============================================================================
|
|
565
|
+
|
|
566
|
+
export function setBreakpoint(scriptName, lineNumber) {
|
|
567
|
+
if (!scriptingState.breakpoints.has(scriptName)) {
|
|
568
|
+
scriptingState.breakpoints.set(scriptName, []);
|
|
569
|
+
}
|
|
570
|
+
scriptingState.breakpoints.get(scriptName).push(lineNumber);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export function removeBreakpoint(scriptName, lineNumber) {
|
|
574
|
+
const breaks = scriptingState.breakpoints.get(scriptName);
|
|
575
|
+
if (breaks) {
|
|
576
|
+
const idx = breaks.indexOf(lineNumber);
|
|
577
|
+
if (idx >= 0) breaks.splice(idx, 1);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export function getBreakpoints(scriptName) {
|
|
582
|
+
return scriptingState.breakpoints.get(scriptName) || [];
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export async function stepInto(scriptName, lineNumber) {
|
|
586
|
+
scriptingState.isDebugging = true;
|
|
587
|
+
scriptingState.debugState = {scriptName, lineNumber, stepMode: 'into'};
|
|
588
|
+
fireEvent('debugger_step', {scriptName, lineNumber});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export async function stepOver(scriptName, lineNumber) {
|
|
592
|
+
scriptingState.debugState = {scriptName, lineNumber, stepMode: 'over'};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export function getDebugHistory() {
|
|
596
|
+
return scriptingState.debugHistory.slice();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export function clearDebugHistory() {
|
|
600
|
+
scriptingState.debugHistory = [];
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// ============================================================================
|
|
604
|
+
// SCRIPT PARAMETERS UI
|
|
605
|
+
// ============================================================================
|
|
606
|
+
|
|
607
|
+
export function setScriptParameters(scriptName, parameters) {
|
|
608
|
+
scriptingState.scriptParams.set(scriptName, parameters);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export function getScriptParameters(scriptName) {
|
|
612
|
+
return scriptingState.scriptParams.get(scriptName) || {};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function createParameterDialog(parameters) {
|
|
616
|
+
// parameters = {name: {type, default, min, max, options}, ...}
|
|
617
|
+
const dialog = document.createElement('div');
|
|
618
|
+
dialog.className = 'script-param-dialog';
|
|
619
|
+
dialog.style.cssText = `
|
|
620
|
+
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
621
|
+
background: white; border: 1px solid #ccc; border-radius: 8px;
|
|
622
|
+
padding: 20px; z-index: 10000; max-width: 400px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
623
|
+
`;
|
|
624
|
+
|
|
625
|
+
const form = document.createElement('form');
|
|
626
|
+
const fields = {};
|
|
627
|
+
|
|
628
|
+
Object.entries(parameters).forEach(([name, config]) => {
|
|
629
|
+
const div = document.createElement('div');
|
|
630
|
+
div.style.marginBottom = '12px';
|
|
631
|
+
|
|
632
|
+
const label = document.createElement('label');
|
|
633
|
+
label.textContent = name;
|
|
634
|
+
label.style.display = 'block';
|
|
635
|
+
label.style.marginBottom = '4px';
|
|
636
|
+
label.style.fontSize = '14px';
|
|
637
|
+
label.style.fontWeight = '500';
|
|
638
|
+
|
|
639
|
+
let input;
|
|
640
|
+
if (config.type === 'slider') {
|
|
641
|
+
input = document.createElement('input');
|
|
642
|
+
input.type = 'range';
|
|
643
|
+
input.min = config.min || 0;
|
|
644
|
+
input.max = config.max || 100;
|
|
645
|
+
input.value = config.default || config.min || 0;
|
|
646
|
+
input.style.width = '100%';
|
|
647
|
+
} else if (config.type === 'dropdown') {
|
|
648
|
+
input = document.createElement('select');
|
|
649
|
+
(config.options || []).forEach(opt => {
|
|
650
|
+
const option = document.createElement('option');
|
|
651
|
+
option.value = opt;
|
|
652
|
+
option.textContent = opt;
|
|
653
|
+
input.appendChild(option);
|
|
654
|
+
});
|
|
655
|
+
input.value = config.default || config.options[0];
|
|
656
|
+
} else {
|
|
657
|
+
input = document.createElement('input');
|
|
658
|
+
input.type = config.type || 'text';
|
|
659
|
+
input.value = config.default || '';
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
input.style.padding = '6px';
|
|
663
|
+
input.style.border = '1px solid #ddd';
|
|
664
|
+
input.style.borderRadius = '4px';
|
|
665
|
+
fields[name] = input;
|
|
666
|
+
|
|
667
|
+
div.appendChild(label);
|
|
668
|
+
div.appendChild(input);
|
|
669
|
+
form.appendChild(div);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
const buttons = document.createElement('div');
|
|
673
|
+
buttons.style.marginTop = '16px';
|
|
674
|
+
buttons.style.display = 'flex';
|
|
675
|
+
buttons.style.gap = '8px';
|
|
676
|
+
buttons.style.justifyContent = 'flex-end';
|
|
677
|
+
|
|
678
|
+
const okBtn = document.createElement('button');
|
|
679
|
+
okBtn.textContent = 'OK';
|
|
680
|
+
okBtn.type = 'submit';
|
|
681
|
+
okBtn.style.cssText = 'padding: 8px 16px; background: #0066cc; color: white; border: none; border-radius: 4px; cursor: pointer;';
|
|
682
|
+
|
|
683
|
+
const cancelBtn = document.createElement('button');
|
|
684
|
+
cancelBtn.textContent = 'Cancel';
|
|
685
|
+
cancelBtn.type = 'button';
|
|
686
|
+
cancelBtn.style.cssText = 'padding: 8px 16px; background: #ccc; border: none; border-radius: 4px; cursor: pointer;';
|
|
687
|
+
|
|
688
|
+
buttons.appendChild(okBtn);
|
|
689
|
+
buttons.appendChild(cancelBtn);
|
|
690
|
+
form.appendChild(buttons);
|
|
691
|
+
|
|
692
|
+
return new Promise((resolve) => {
|
|
693
|
+
form.addEventListener('submit', (e) => {
|
|
694
|
+
e.preventDefault();
|
|
695
|
+
const values = {};
|
|
696
|
+
Object.entries(fields).forEach(([name, input]) => {
|
|
697
|
+
values[name] = input.type === 'range' ? parseFloat(input.value) : input.value;
|
|
698
|
+
});
|
|
699
|
+
document.body.removeChild(dialog);
|
|
700
|
+
resolve(values);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
cancelBtn.addEventListener('click', () => {
|
|
704
|
+
document.body.removeChild(dialog);
|
|
705
|
+
resolve(null);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
dialog.appendChild(form);
|
|
709
|
+
document.body.appendChild(dialog);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
341
713
|
// ============================================================================
|
|
342
714
|
// PUBLIC API
|
|
343
715
|
// ============================================================================
|
|
344
716
|
|
|
345
|
-
/**
|
|
346
|
-
* Initialize the scripting module
|
|
347
|
-
*
|
|
348
|
-
* @param {object} viewport - Three.js viewport
|
|
349
|
-
* @param {object} kernel - CAD kernel
|
|
350
|
-
* @param {HTMLElement} [containerEl] - Container for UI
|
|
351
|
-
*/
|
|
352
717
|
export function init(viewport, kernel, containerEl = null) {
|
|
353
718
|
scriptingState.viewport = viewport;
|
|
354
719
|
scriptingState.kernel = kernel;
|
|
355
720
|
scriptingState.containerEl = containerEl;
|
|
356
|
-
|
|
357
|
-
// Create execution context with cad helper
|
|
358
721
|
scriptingState.executionContext = { cad: cadHelper };
|
|
359
722
|
|
|
360
|
-
// Load saved scripts from localStorage
|
|
361
723
|
loadAllScripts();
|
|
724
|
+
loadExampleScripts();
|
|
362
725
|
|
|
363
|
-
console.log('[Scripting] Module initialized');
|
|
726
|
+
console.log('[Scripting] Module initialized v2.0.0');
|
|
364
727
|
}
|
|
365
728
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
* @tutorial
|
|
370
|
-
* // Simple execution
|
|
371
|
-
* scripting.execute(`
|
|
372
|
-
* cad.createBox(100, 50, 30);
|
|
373
|
-
* cad.fillet(5);
|
|
374
|
-
* cad.exportSTL('box.stl');
|
|
375
|
-
* `);
|
|
376
|
-
*
|
|
377
|
-
* // With error handling
|
|
378
|
-
* try {
|
|
379
|
-
* await scripting.execute(code);
|
|
380
|
-
* } catch (error) {
|
|
381
|
-
* console.error('Script failed:', error.message);
|
|
382
|
-
* }
|
|
383
|
-
*
|
|
384
|
-
* @param {string} code - JavaScript code to execute
|
|
385
|
-
* @param {object} [context={}] - Additional variables to expose
|
|
386
|
-
* @returns {Promise<*>} Script result (if any)
|
|
387
|
-
*/
|
|
388
|
-
export async function execute(code, context = {}) {
|
|
729
|
+
export async function execute(code, context = {}, options = {}) {
|
|
730
|
+
const { withDebugger = false, params = {} } = options;
|
|
731
|
+
|
|
389
732
|
try {
|
|
390
|
-
|
|
391
|
-
|
|
733
|
+
const fullContext = {
|
|
734
|
+
...scriptingState.executionContext,
|
|
735
|
+
...context,
|
|
736
|
+
params
|
|
737
|
+
};
|
|
392
738
|
const contextKeys = Object.keys(fullContext);
|
|
393
739
|
const contextValues = contextKeys.map(k => fullContext[k]);
|
|
394
740
|
|
|
@@ -397,40 +743,17 @@ export async function execute(code, context = {}) {
|
|
|
397
743
|
|
|
398
744
|
scriptingState.lastError = null;
|
|
399
745
|
console.log('[Scripting] Execution successful');
|
|
400
|
-
|
|
401
|
-
// Fire event
|
|
402
746
|
fireEvent('script_executed', { code, result });
|
|
403
747
|
|
|
404
748
|
return result;
|
|
405
749
|
} catch (error) {
|
|
406
750
|
scriptingState.lastError = error;
|
|
407
751
|
console.error('[Scripting] Execution error:', error.message);
|
|
408
|
-
|
|
409
|
-
// Fire event
|
|
410
752
|
fireEvent('script_error', { code, error });
|
|
411
|
-
|
|
412
753
|
throw error;
|
|
413
754
|
}
|
|
414
755
|
}
|
|
415
756
|
|
|
416
|
-
/**
|
|
417
|
-
* Save a script to library
|
|
418
|
-
*
|
|
419
|
-
* @tutorial
|
|
420
|
-
* scripting.saveScript('my_script', `
|
|
421
|
-
* cad.createBox(100, 50, 30);
|
|
422
|
-
* cad.fillet(5);
|
|
423
|
-
* `, {
|
|
424
|
-
* description: 'Creates a filleted box',
|
|
425
|
-
* tags: ['box', 'basic'],
|
|
426
|
-
* version: '1.0'
|
|
427
|
-
* });
|
|
428
|
-
*
|
|
429
|
-
* @param {string} name - Script name (unique identifier)
|
|
430
|
-
* @param {string} code - JavaScript code
|
|
431
|
-
* @param {object} [metadata={}] - Metadata (description, tags, version, etc)
|
|
432
|
-
* @returns {object} Saved script object
|
|
433
|
-
*/
|
|
434
757
|
export function saveScript(name, code, metadata = {}) {
|
|
435
758
|
const script = {
|
|
436
759
|
name,
|
|
@@ -452,17 +775,6 @@ export function saveScript(name, code, metadata = {}) {
|
|
|
452
775
|
return script;
|
|
453
776
|
}
|
|
454
777
|
|
|
455
|
-
/**
|
|
456
|
-
* Load a script from library
|
|
457
|
-
*
|
|
458
|
-
* @tutorial
|
|
459
|
-
* const script = scripting.loadScript('my_script');
|
|
460
|
-
* console.log(script.code);
|
|
461
|
-
* await scripting.execute(script.code);
|
|
462
|
-
*
|
|
463
|
-
* @param {string} name - Script name
|
|
464
|
-
* @returns {object|null} Script object or null if not found
|
|
465
|
-
*/
|
|
466
778
|
export function loadScript(name) {
|
|
467
779
|
let script = scriptingState.scripts.get(name);
|
|
468
780
|
|
|
@@ -486,12 +798,6 @@ export function loadScript(name) {
|
|
|
486
798
|
return script || null;
|
|
487
799
|
}
|
|
488
800
|
|
|
489
|
-
/**
|
|
490
|
-
* Delete a script from library
|
|
491
|
-
*
|
|
492
|
-
* @param {string} name - Script name
|
|
493
|
-
* @returns {boolean} Success
|
|
494
|
-
*/
|
|
495
801
|
export function deleteScript(name) {
|
|
496
802
|
const deleted = scriptingState.scripts.delete(name);
|
|
497
803
|
if (deleted) {
|
|
@@ -502,77 +808,36 @@ export function deleteScript(name) {
|
|
|
502
808
|
return deleted;
|
|
503
809
|
}
|
|
504
810
|
|
|
505
|
-
/**
|
|
506
|
-
* List all saved scripts
|
|
507
|
-
*
|
|
508
|
-
* @tutorial
|
|
509
|
-
* const scripts = scripting.listScripts();
|
|
510
|
-
* scripts.forEach(script => {
|
|
511
|
-
* console.log(`${script.name}: ${script.description || 'No description'}`);
|
|
512
|
-
* });
|
|
513
|
-
*
|
|
514
|
-
* @param {string} [tag] - Optional filter by tag
|
|
515
|
-
* @returns {Array<object>} Array of script objects
|
|
516
|
-
*/
|
|
517
811
|
export function listScripts(tag = null) {
|
|
518
812
|
let scripts = Array.from(scriptingState.scripts.values());
|
|
519
|
-
|
|
520
813
|
if (tag) {
|
|
521
814
|
scripts = scripts.filter(s => (s.tags || []).includes(tag));
|
|
522
815
|
}
|
|
523
|
-
|
|
524
816
|
return scripts;
|
|
525
817
|
}
|
|
526
818
|
|
|
527
|
-
/**
|
|
528
|
-
* Start recording user actions as a macro
|
|
529
|
-
*
|
|
530
|
-
* @tutorial
|
|
531
|
-
* scripting.startRecording();
|
|
532
|
-
* // User performs actions: click, extrude, fillet, etc.
|
|
533
|
-
* const macro = scripting.stopRecording();
|
|
534
|
-
* scripting.saveScript('macro_1', macro.code, {
|
|
535
|
-
* description: 'Auto-generated macro'
|
|
536
|
-
* });
|
|
537
|
-
*/
|
|
538
819
|
export function startRecording() {
|
|
539
820
|
scriptingState.isRecording = true;
|
|
540
821
|
scriptingState.recordedActions = [];
|
|
541
|
-
|
|
542
822
|
console.log('[Scripting] Recording started');
|
|
543
823
|
fireEvent('recording_started', {});
|
|
544
824
|
}
|
|
545
825
|
|
|
546
|
-
/**
|
|
547
|
-
* Stop recording and get generated macro
|
|
548
|
-
*
|
|
549
|
-
* @returns {object} {code, actions} Generated script
|
|
550
|
-
*/
|
|
551
826
|
export function stopRecording() {
|
|
552
827
|
scriptingState.isRecording = false;
|
|
553
|
-
|
|
554
|
-
// Generate code from recorded actions
|
|
555
828
|
const code = generateMacroCode(scriptingState.recordedActions);
|
|
556
|
-
|
|
557
829
|
const macro = {
|
|
558
830
|
code,
|
|
559
831
|
actions: scriptingState.recordedActions.slice(),
|
|
560
832
|
recordedAt: new Date().toISOString()
|
|
561
833
|
};
|
|
562
|
-
|
|
563
|
-
console.log('[Scripting] Recording stopped. Generated', scriptingState.recordedActions.length, 'actions');
|
|
834
|
+
console.log('[Scripting] Recording stopped');
|
|
564
835
|
fireEvent('recording_stopped', { macro });
|
|
565
|
-
|
|
566
836
|
return macro;
|
|
567
837
|
}
|
|
568
838
|
|
|
569
|
-
/**
|
|
570
|
-
* Record an action during macro recording
|
|
571
|
-
* @private
|
|
572
|
-
*/
|
|
573
839
|
export function recordAction(action, params) {
|
|
574
840
|
if (!scriptingState.isRecording) return;
|
|
575
|
-
|
|
576
841
|
scriptingState.recordedActions.push({
|
|
577
842
|
action,
|
|
578
843
|
params,
|
|
@@ -580,46 +845,15 @@ export function recordAction(action, params) {
|
|
|
580
845
|
});
|
|
581
846
|
}
|
|
582
847
|
|
|
583
|
-
/**
|
|
584
|
-
* Register event hook
|
|
585
|
-
*
|
|
586
|
-
* @tutorial
|
|
587
|
-
* scripting.onEvent('script_executed', (data) => {
|
|
588
|
-
* console.log('Script ran:', data.code);
|
|
589
|
-
* });
|
|
590
|
-
*
|
|
591
|
-
* scripting.onEvent('geometry_changed', (data) => {
|
|
592
|
-
* console.log('Geometry updated');
|
|
593
|
-
* });
|
|
594
|
-
*
|
|
595
|
-
* @param {string} eventName - Event name
|
|
596
|
-
* @param {Function} callback - Handler function
|
|
597
|
-
*/
|
|
598
848
|
export function onEvent(eventName, callback) {
|
|
599
849
|
if (!scriptingState.eventHooks.has(eventName)) {
|
|
600
850
|
scriptingState.eventHooks.set(eventName, []);
|
|
601
851
|
}
|
|
602
|
-
|
|
603
852
|
scriptingState.eventHooks.get(eventName).push(callback);
|
|
604
853
|
}
|
|
605
854
|
|
|
606
|
-
/**
|
|
607
|
-
* Run script on multiple selected objects
|
|
608
|
-
*
|
|
609
|
-
* @tutorial
|
|
610
|
-
* // Apply fillet to all selected parts
|
|
611
|
-
* scripting.batchExecute('selectedParts', `
|
|
612
|
-
* cad.fillet(5);
|
|
613
|
-
* `);
|
|
614
|
-
*
|
|
615
|
-
* @param {string|Array<object>} targets - 'selectedParts' or array of objects
|
|
616
|
-
* @param {string} code - Script code
|
|
617
|
-
* @returns {Promise<Array>} Array of results
|
|
618
|
-
*/
|
|
619
855
|
export async function batchExecute(targets, code) {
|
|
620
|
-
let objects = targets === 'selectedParts' ?
|
|
621
|
-
getSelectedObjects() : targets;
|
|
622
|
-
|
|
856
|
+
let objects = targets === 'selectedParts' ? getSelectedObjects() : targets;
|
|
623
857
|
const results = [];
|
|
624
858
|
|
|
625
859
|
for (const obj of objects) {
|
|
@@ -635,73 +869,58 @@ export async function batchExecute(targets, code) {
|
|
|
635
869
|
return results;
|
|
636
870
|
}
|
|
637
871
|
|
|
638
|
-
/**
|
|
639
|
-
* Get last script error
|
|
640
|
-
*
|
|
641
|
-
* @returns {Error|null} Last error or null
|
|
642
|
-
*/
|
|
643
872
|
export function getLastError() {
|
|
644
873
|
return scriptingState.lastError;
|
|
645
874
|
}
|
|
646
875
|
|
|
647
|
-
/**
|
|
648
|
-
* Clear last error
|
|
649
|
-
*/
|
|
650
876
|
export function clearError() {
|
|
651
877
|
scriptingState.lastError = null;
|
|
652
878
|
}
|
|
653
879
|
|
|
654
|
-
/**
|
|
655
|
-
* Get cad helper object (for reference/testing)
|
|
656
|
-
*
|
|
657
|
-
* @returns {object} The cad helper
|
|
658
|
-
*/
|
|
659
880
|
export function getCadHelper() {
|
|
660
881
|
return cadHelper;
|
|
661
882
|
}
|
|
662
883
|
|
|
884
|
+
export function getConsoleOutput() {
|
|
885
|
+
return scriptingState.consoleOutput.slice();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
export function clearConsole() {
|
|
889
|
+
scriptingState.consoleOutput = [];
|
|
890
|
+
fireEvent('console_cleared', {});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
export function getExampleScripts() {
|
|
894
|
+
return Array.from(scriptingState.exampleScripts.values());
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
export function loadExampleScript(name) {
|
|
898
|
+
return scriptingState.exampleScripts.get(name) || null;
|
|
899
|
+
}
|
|
900
|
+
|
|
663
901
|
// ============================================================================
|
|
664
902
|
// INTERNAL FUNCTIONS
|
|
665
903
|
// ============================================================================
|
|
666
904
|
|
|
667
|
-
/**
|
|
668
|
-
* Execute a kernel command
|
|
669
|
-
* @private
|
|
670
|
-
*/
|
|
671
905
|
function executeKernelCommand(method, params) {
|
|
672
906
|
if (!scriptingState.kernel) return null;
|
|
673
|
-
|
|
674
|
-
// Dispatch to kernel if available
|
|
675
907
|
if (typeof scriptingState.kernel.execute === 'function') {
|
|
676
908
|
return scriptingState.kernel.execute({ method, params });
|
|
677
909
|
}
|
|
678
|
-
|
|
679
910
|
console.warn('[Scripting] Kernel command not available:', method);
|
|
680
911
|
return null;
|
|
681
912
|
}
|
|
682
913
|
|
|
683
|
-
/**
|
|
684
|
-
* Get currently selected object
|
|
685
|
-
* @private
|
|
686
|
-
*/
|
|
687
914
|
function getSelectedObject() {
|
|
688
915
|
return scriptingState.kernel?.selectedMesh ||
|
|
689
916
|
scriptingState.viewport?.scene?.children.find(c => c instanceof THREE.Mesh);
|
|
690
917
|
}
|
|
691
918
|
|
|
692
|
-
/**
|
|
693
|
-
* Get all selected objects
|
|
694
|
-
* @private
|
|
695
|
-
*/
|
|
696
919
|
function getSelectedObjects() {
|
|
697
920
|
return scriptingState.kernel?.selectedMeshes ||
|
|
698
921
|
scriptingState.viewport?.scene?.children.filter(c => c instanceof THREE.Mesh) || [];
|
|
699
922
|
}
|
|
700
923
|
|
|
701
|
-
/**
|
|
702
|
-
* Fire event to all registered listeners
|
|
703
|
-
* @private
|
|
704
|
-
*/
|
|
705
924
|
function fireEvent(eventName, data) {
|
|
706
925
|
const listeners = scriptingState.eventHooks.get(eventName) || [];
|
|
707
926
|
listeners.forEach(callback => {
|
|
@@ -713,10 +932,6 @@ function fireEvent(eventName, data) {
|
|
|
713
932
|
});
|
|
714
933
|
}
|
|
715
934
|
|
|
716
|
-
/**
|
|
717
|
-
* Generate code from recorded actions
|
|
718
|
-
* @private
|
|
719
|
-
*/
|
|
720
935
|
function generateMacroCode(actions) {
|
|
721
936
|
const lines = [
|
|
722
937
|
'// Auto-generated macro from recorded actions',
|
|
@@ -724,7 +939,7 @@ function generateMacroCode(actions) {
|
|
|
724
939
|
''
|
|
725
940
|
];
|
|
726
941
|
|
|
727
|
-
actions.forEach((action
|
|
942
|
+
actions.forEach((action) => {
|
|
728
943
|
switch (action.action) {
|
|
729
944
|
case 'box':
|
|
730
945
|
lines.push(`cad.createBox(${action.params.w}, ${action.params.h}, ${action.params.d});`);
|
|
@@ -755,10 +970,6 @@ function generateMacroCode(actions) {
|
|
|
755
970
|
return lines.join('\n');
|
|
756
971
|
}
|
|
757
972
|
|
|
758
|
-
/**
|
|
759
|
-
* Load all scripts from localStorage
|
|
760
|
-
* @private
|
|
761
|
-
*/
|
|
762
973
|
function loadAllScripts() {
|
|
763
974
|
const keys = Object.keys(localStorage);
|
|
764
975
|
keys
|
|
@@ -776,6 +987,17 @@ function loadAllScripts() {
|
|
|
776
987
|
console.log(`[Scripting] Loaded ${scriptingState.scripts.size} saved scripts`);
|
|
777
988
|
}
|
|
778
989
|
|
|
990
|
+
function loadExampleScripts() {
|
|
991
|
+
Object.entries(EXAMPLE_SCRIPTS).forEach(([key, script]) => {
|
|
992
|
+
scriptingState.exampleScripts.set(key, script);
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function recordScriptAction(action, params) {
|
|
997
|
+
if (!scriptingState.isRecording) return;
|
|
998
|
+
scriptingState.recordedActions.push({action, params, timestamp: Date.now()});
|
|
999
|
+
}
|
|
1000
|
+
|
|
779
1001
|
// ============================================================================
|
|
780
1002
|
// HELP ENTRIES
|
|
781
1003
|
// ============================================================================
|
|
@@ -786,153 +1008,35 @@ export const helpEntries = [
|
|
|
786
1008
|
title: 'Script Basics',
|
|
787
1009
|
category: 'Scripting',
|
|
788
1010
|
description: 'Write JavaScript to automate CAD operations',
|
|
789
|
-
|
|
790
|
-
content: `
|
|
791
|
-
cycleCAD scripting lets you automate design with JavaScript.
|
|
792
|
-
Access the cad helper object with shortcuts:
|
|
793
|
-
|
|
794
|
-
cad.createBox(w, h, d) - Create box
|
|
795
|
-
cad.createCylinder(r, h) - Create cylinder
|
|
796
|
-
cad.fillet(radius) - Fillet edges
|
|
797
|
-
cad.position(x, y, z) - Move object
|
|
798
|
-
cad.exportSTL(filename) - Export to STL
|
|
799
|
-
|
|
800
|
-
All methods chain: cad.createBox(100, 50, 30).fillet(5).color(0xff0000);
|
|
801
|
-
`
|
|
1011
|
+
content: `cycleCAD scripting lets you automate design with JavaScript. Access the cad helper object with 55+ commands.`
|
|
802
1012
|
},
|
|
803
1013
|
{
|
|
804
|
-
id: 'scripting-
|
|
805
|
-
title: '
|
|
1014
|
+
id: 'scripting-example-scripts',
|
|
1015
|
+
title: 'Example Scripts',
|
|
806
1016
|
category: 'Scripting',
|
|
807
|
-
description: '
|
|
808
|
-
|
|
809
|
-
content: `
|
|
810
|
-
Create basic shapes with cad helper:
|
|
811
|
-
- createBox(w, h, d) - Rectangular solid
|
|
812
|
-
- createCylinder(r, h) - Cylinder
|
|
813
|
-
- createSphere(r) - Sphere
|
|
814
|
-
- createCone(r, h) - Cone
|
|
815
|
-
- createTorus(majorR, minorR) - Torus
|
|
816
|
-
|
|
817
|
-
Example:
|
|
818
|
-
cad.createBox(100, 50, 30);
|
|
819
|
-
cad.createCylinder(25, 100);
|
|
820
|
-
`
|
|
1017
|
+
description: 'Built-in parametric script templates',
|
|
1018
|
+
content: `20+ example scripts: Gear Generator, Spring Helix, Parametric Box, Thread, Array Pattern`
|
|
821
1019
|
},
|
|
822
1020
|
{
|
|
823
|
-
id: 'scripting-
|
|
824
|
-
title: '
|
|
1021
|
+
id: 'scripting-debugging',
|
|
1022
|
+
title: 'Script Debugging',
|
|
825
1023
|
category: 'Scripting',
|
|
826
|
-
description: '
|
|
827
|
-
|
|
828
|
-
content: `
|
|
829
|
-
Modify geometry with operations:
|
|
830
|
-
- fillet(radius) - Round edges
|
|
831
|
-
- chamfer(distance) - Bevel edges
|
|
832
|
-
- hole(diameter, depth) - Create hole
|
|
833
|
-
- extrude(distance) - Extrude selection
|
|
834
|
-
- union/cut/intersect(otherIds) - Boolean ops
|
|
835
|
-
- shell(thickness) - Create hollow
|
|
836
|
-
- pattern(countX, countY, spaceX, spaceY) - Array
|
|
837
|
-
|
|
838
|
-
Example:
|
|
839
|
-
cad.createBox(100, 50, 30)
|
|
840
|
-
.fillet(5)
|
|
841
|
-
.hole(10, 15)
|
|
842
|
-
.color(0x8899aa);
|
|
843
|
-
`
|
|
1024
|
+
description: 'Debug scripts with breakpoints and step-through',
|
|
1025
|
+
content: `Set breakpoints, step into/over code, inspect variables, view debug history`
|
|
844
1026
|
},
|
|
845
1027
|
{
|
|
846
|
-
id: 'scripting-
|
|
847
|
-
title: 'Script
|
|
1028
|
+
id: 'scripting-parameters',
|
|
1029
|
+
title: 'Script Parameters',
|
|
848
1030
|
category: 'Scripting',
|
|
849
|
-
description: '
|
|
850
|
-
|
|
851
|
-
content: `
|
|
852
|
-
Save scripts to library:
|
|
853
|
-
scripting.saveScript('my_script', code, {
|
|
854
|
-
description: 'Creates a filleted box',
|
|
855
|
-
tags: ['box', 'basic']
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
Load and run:
|
|
859
|
-
const script = scripting.loadScript('my_script');
|
|
860
|
-
scripting.execute(script.code);
|
|
861
|
-
|
|
862
|
-
List all scripts:
|
|
863
|
-
scripting.listScripts().forEach(s => console.log(s.name));
|
|
864
|
-
`
|
|
865
|
-
},
|
|
866
|
-
{
|
|
867
|
-
id: 'scripting-macros',
|
|
868
|
-
title: 'Macro Recording',
|
|
869
|
-
category: 'Scripting',
|
|
870
|
-
description: 'Auto-record user actions as scripts',
|
|
871
|
-
shortcut: 'Shift+Ctrl+R',
|
|
872
|
-
content: `
|
|
873
|
-
Record user actions as replayable macros:
|
|
874
|
-
1. Click "Record"
|
|
875
|
-
2. Perform actions (create box, fillet, etc)
|
|
876
|
-
3. Click "Stop"
|
|
877
|
-
4. Generated script appears
|
|
878
|
-
5. Save to library for later use
|
|
879
|
-
|
|
880
|
-
Useful for repetitive design tasks.
|
|
881
|
-
`
|
|
1031
|
+
description: 'Create parametric scripts with UI dialogs',
|
|
1032
|
+
content: `Define script parameters (sliders, dropdowns, text fields) that generate UI dialogs automatically`
|
|
882
1033
|
},
|
|
883
1034
|
{
|
|
884
1035
|
id: 'scripting-batch',
|
|
885
1036
|
title: 'Batch Operations',
|
|
886
1037
|
category: 'Scripting',
|
|
887
1038
|
description: 'Run scripts on multiple parts',
|
|
888
|
-
|
|
889
|
-
content: `
|
|
890
|
-
Apply operations to many parts at once:
|
|
891
|
-
scripting.batchExecute('selectedParts', \`
|
|
892
|
-
cad.fillet(5);
|
|
893
|
-
cad.color(0x8899aa);
|
|
894
|
-
\`);
|
|
895
|
-
|
|
896
|
-
The script runs for each selected part.
|
|
897
|
-
Useful for applying material/color to assemblies.
|
|
898
|
-
`
|
|
899
|
-
},
|
|
900
|
-
{
|
|
901
|
-
id: 'scripting-export',
|
|
902
|
-
title: 'Export from Scripts',
|
|
903
|
-
category: 'Scripting',
|
|
904
|
-
description: 'Save work programmatically',
|
|
905
|
-
shortcut: 'Shift+Ctrl+E',
|
|
906
|
-
content: `
|
|
907
|
-
Export from scripts:
|
|
908
|
-
cad.exportSTL('part.stl');
|
|
909
|
-
cad.exportOBJ('part.obj');
|
|
910
|
-
cad.exportGLTF('model.gltf');
|
|
911
|
-
|
|
912
|
-
Automate file generation for batches of parts.
|
|
913
|
-
`
|
|
914
|
-
},
|
|
915
|
-
{
|
|
916
|
-
id: 'scripting-events',
|
|
917
|
-
title: 'Event Hooks',
|
|
918
|
-
category: 'Scripting',
|
|
919
|
-
description: 'Subscribe to kernel events',
|
|
920
|
-
shortcut: 'Shift+Ctrl+V',
|
|
921
|
-
content: `
|
|
922
|
-
Listen for kernel events:
|
|
923
|
-
scripting.onEvent('script_executed', (data) => {
|
|
924
|
-
console.log('Script ran');
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
scripting.onEvent('geometry_changed', (data) => {
|
|
928
|
-
console.log('Model updated');
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
Available events:
|
|
932
|
-
- script_executed, script_error
|
|
933
|
-
- script_saved, script_loaded
|
|
934
|
-
- recording_started, recording_stopped
|
|
935
|
-
`
|
|
1039
|
+
content: `Apply scripts to many parts at once with batchExecute()`
|
|
936
1040
|
}
|
|
937
1041
|
];
|
|
938
1042
|
|
|
@@ -951,5 +1055,19 @@ export default {
|
|
|
951
1055
|
getLastError,
|
|
952
1056
|
clearError,
|
|
953
1057
|
getCadHelper,
|
|
1058
|
+
setBreakpoint,
|
|
1059
|
+
removeBreakpoint,
|
|
1060
|
+
getBreakpoints,
|
|
1061
|
+
stepInto,
|
|
1062
|
+
stepOver,
|
|
1063
|
+
getDebugHistory,
|
|
1064
|
+
clearDebugHistory,
|
|
1065
|
+
setScriptParameters,
|
|
1066
|
+
getScriptParameters,
|
|
1067
|
+
createParameterDialog,
|
|
1068
|
+
getConsoleOutput,
|
|
1069
|
+
clearConsole,
|
|
1070
|
+
getExampleScripts,
|
|
1071
|
+
loadExampleScript,
|
|
954
1072
|
helpEntries
|
|
955
1073
|
};
|