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.
Files changed (94) hide show
  1. package/BILLING-IMPLEMENTATION-SUMMARY.md +425 -0
  2. package/BILLING-INDEX.md +293 -0
  3. package/BILLING-INTEGRATION-GUIDE.md +414 -0
  4. package/COLLABORATION-INDEX.md +440 -0
  5. package/COLLABORATION-SYSTEM-SUMMARY.md +548 -0
  6. package/DELIVERABLES.txt +296 -445
  7. package/DOCKER-BUILD-MANIFEST.txt +483 -0
  8. package/DOCKER-FILES-REFERENCE.md +440 -0
  9. package/DOCKER-INFRASTRUCTURE.md +475 -0
  10. package/DOCKER-README.md +435 -0
  11. package/Dockerfile +33 -55
  12. package/ENHANCEMENT_COMPLETION_REPORT.md +383 -0
  13. package/ENHANCEMENT_SUMMARY.txt +308 -0
  14. package/FEATURE_INVENTORY.md +235 -0
  15. package/FUSION360_FEATURES_SUMMARY.md +452 -0
  16. package/FUSION360_PARITY_ENHANCEMENTS.md +461 -0
  17. package/FUSION360_PARITY_SUMMARY.md +520 -0
  18. package/FUSION360_QUICK_REFERENCE.md +351 -0
  19. package/MODULE_API_REFERENCE.md +712 -0
  20. package/MODULE_INVENTORY.txt +264 -0
  21. package/PWA-FILES-CREATED.txt +350 -0
  22. package/QUICK-START-TESTING.md +126 -0
  23. package/STEP-IMPORT-QUICKSTART.md +347 -0
  24. package/STEP-IMPORT-SYSTEM-SUMMARY.md +502 -0
  25. package/app/css/mobile.css +1074 -0
  26. package/app/icons/generate-icons.js +203 -0
  27. package/app/index.html +1342 -5031
  28. package/app/js/app.js +1312 -514
  29. package/app/js/billing-ui.js +990 -0
  30. package/app/js/brep-kernel.js +933 -981
  31. package/app/js/collab-client.js +750 -0
  32. package/app/js/mobile-nav.js +623 -0
  33. package/app/js/mobile-toolbar.js +476 -0
  34. package/app/js/modules/animation-module.js +497 -3
  35. package/app/js/modules/billing-module.js +724 -0
  36. package/app/js/modules/cam-module.js +507 -2
  37. package/app/js/modules/collaboration-module.js +513 -0
  38. package/app/js/modules/constraint-module.js +1266 -0
  39. package/app/js/modules/data-module.js +544 -1146
  40. package/app/js/modules/formats-module.js +438 -738
  41. package/app/js/modules/inspection-module.js +393 -0
  42. package/app/js/modules/mesh-module-enhanced.js +880 -0
  43. package/app/js/modules/plugin-module.js +597 -0
  44. package/app/js/modules/rendering-module.js +460 -0
  45. package/app/js/modules/scripting-module.js +593 -475
  46. package/app/js/modules/sketch-module.js +998 -2
  47. package/app/js/modules/step-module-enhanced.js +938 -0
  48. package/app/js/modules/surface-module.js +312 -0
  49. package/app/js/modules/version-module.js +420 -0
  50. package/app/js/offline-manager.js +705 -0
  51. package/app/js/responsive-init.js +360 -0
  52. package/app/js/touch-handler.js +429 -0
  53. package/app/manifest.json +211 -0
  54. package/app/offline.html +508 -0
  55. package/app/sw.js +571 -0
  56. package/app/tests/billing-tests.html +779 -0
  57. package/app/tests/brep-tests.html +980 -0
  58. package/app/tests/collab-tests.html +743 -0
  59. package/app/tests/mobile-tests.html +1299 -0
  60. package/app/tests/pwa-tests.html +1134 -0
  61. package/app/tests/step-tests.html +1042 -0
  62. package/app/tests/test-agent-v3.html +719 -0
  63. package/cycleCAD-Architecture-v2.pptx +0 -0
  64. package/docker-compose.yml +225 -0
  65. package/docs/BILLING-HELP.json +260 -0
  66. package/docs/BILLING-README.md +639 -0
  67. package/docs/BILLING-TUTORIAL.md +736 -0
  68. package/docs/BREP-HELP.json +326 -0
  69. package/docs/BREP-TUTORIAL.md +802 -0
  70. package/docs/COLLABORATION-HELP.json +228 -0
  71. package/docs/COLLABORATION-TUTORIAL.md +818 -0
  72. package/docs/DOCKER-HELP.json +224 -0
  73. package/docs/DOCKER-TUTORIAL.md +974 -0
  74. package/docs/MOBILE-HELP.json +243 -0
  75. package/docs/MOBILE-RESPONSIVE-README.md +378 -0
  76. package/docs/MOBILE-TUTORIAL.md +747 -0
  77. package/docs/PWA-HELP.json +228 -0
  78. package/docs/PWA-README.md +662 -0
  79. package/docs/PWA-TUTORIAL.md +757 -0
  80. package/docs/STEP-HELP.json +481 -0
  81. package/docs/STEP-IMPORT-TUTORIAL.md +824 -0
  82. package/docs/TESTING-GUIDE.md +528 -0
  83. package/docs/TESTING-HELP.json +182 -0
  84. package/fusion-vs-cyclecad.html +1771 -0
  85. package/nginx.conf +237 -0
  86. package/package.json +1 -1
  87. package/server/Dockerfile.converter +51 -0
  88. package/server/Dockerfile.signaling +28 -0
  89. package/server/billing-server.js +487 -0
  90. package/server/converter-enhanced.py +528 -0
  91. package/server/requirements-converter.txt +29 -0
  92. package/server/signaling-server.js +801 -0
  93. package/tests/docker-tests.sh +389 -0
  94. 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: Simple `cad.*` wrappers for geometry operations
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 1.0.0
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
- // CAD API HELPER OBJECT
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
- /** Set scale */
142
- scale: (sx, sy, sz) => {
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) obj.scale.set(sx || 1, sy || 1, sz || 1);
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
- /** Boolean union */
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
- /** Revolve profile around axis */
196
- revolve: (angle, axis = 'Z') => {
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
- /** Sweep profile along path */
201
- sweep: (profileId, pathId, options = {}) => {
202
- return executeKernelCommand('ops.sweep', { profileId, pathId, ...options });
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
- /** Loft between profiles */
206
- loft: (profileIds) => {
207
- return executeKernelCommand('ops.loft', { profileIds });
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.material) {
216
- const densities = {
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
- // === INSPECTION ===
441
+ // === SELECTION & VISIBILITY ===
245
442
 
246
- /** Get mass properties */
247
- getMass: () => {
248
- const obj = getSelectedObject();
249
- if (!obj) return null;
250
- return executeKernelCommand('inspect.massProperties', { meshId: obj });
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
- /** Get bounding box */
254
- getBounds: () => {
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
- /** Get volume */
262
- getVolume: () => {
263
- const obj = getSelectedObject();
264
- if (!obj) return 0;
265
- // Rough estimation from bounding box
266
- const bbox = obj.geometry?.boundingBox;
267
- if (!bbox) return 0;
268
- const size = bbox.getSize(new THREE.Vector3());
269
- return size.x * size.y * size.z;
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
- // === SCENE ===
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
- /** Select object by name */
297
- select: (name) => {
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
- /** Hide object */
306
- hide: (name) => {
307
- const obj = scriptingState.viewport.scene.getObjectByName(name);
308
- if (obj) obj.visible = false;
309
- return cadHelper;
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
- /** Show object */
313
- show: (name) => {
314
- const obj = scriptingState.viewport.scene.getObjectByName(name);
315
- if (obj) obj.visible = true;
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
- /** Delete object */
320
- delete: (name) => {
321
- const obj = scriptingState.viewport.scene.getObjectByName(name);
322
- if (obj) scriptingState.viewport.scene.remove(obj);
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
- // === UTILITY ===
327
-
328
- /** Print to console and log */
329
- print: (message) => {
330
- console.log('[CAD Script]', message);
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
- * Execute a script string
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
- // Create function with cad context
391
- const fullContext = { ...scriptingState.executionContext, ...context };
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, i) => {
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
- shortcut: 'Ctrl+Shift+S',
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-shapes',
805
- title: 'Creating Shapes',
1014
+ id: 'scripting-example-scripts',
1015
+ title: 'Example Scripts',
806
1016
  category: 'Scripting',
807
- description: 'Programmatically create 3D geometry',
808
- shortcut: 'Shift+Ctrl+N',
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-operations',
824
- title: 'Geometry Operations',
1021
+ id: 'scripting-debugging',
1022
+ title: 'Script Debugging',
825
1023
  category: 'Scripting',
826
- description: 'Modify shapes with fillet, hole, boolean, etc.',
827
- shortcut: 'Shift+Ctrl+O',
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-library',
847
- title: 'Script Library',
1028
+ id: 'scripting-parameters',
1029
+ title: 'Script Parameters',
848
1030
  category: 'Scripting',
849
- description: 'Save and load scripts for reuse',
850
- shortcut: 'Shift+Ctrl+L',
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
- shortcut: 'Shift+Ctrl+B',
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
  };