cyclecad 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/DELIVERABLES.txt +296 -445
  2. package/ENHANCEMENT_COMPLETION_REPORT.md +383 -0
  3. package/ENHANCEMENT_SUMMARY.txt +308 -0
  4. package/FEATURE_INVENTORY.md +235 -0
  5. package/FUSION360_FEATURES_SUMMARY.md +452 -0
  6. package/FUSION360_PARITY_ENHANCEMENTS.md +461 -0
  7. package/FUSION360_PARITY_SUMMARY.md +520 -0
  8. package/FUSION360_QUICK_REFERENCE.md +351 -0
  9. package/IMPLEMENTATION_GUIDE.md +502 -0
  10. package/INTEGRATION-GUIDE.md +377 -0
  11. package/MODULES_PHASES_6_7.md +780 -0
  12. package/MODULE_API_REFERENCE.md +712 -0
  13. package/MODULE_INVENTORY.txt +264 -0
  14. package/app/index.html +1345 -4930
  15. package/app/js/app.js +1312 -514
  16. package/app/js/brep-kernel.js +1353 -455
  17. package/app/js/help-module.js +1437 -0
  18. package/app/js/kernel.js +364 -40
  19. package/app/js/modules/animation-module.js +1461 -0
  20. package/app/js/modules/assembly-module.js +47 -3
  21. package/app/js/modules/cam-module.js +1572 -0
  22. package/app/js/modules/collaboration-module.js +1615 -0
  23. package/app/js/modules/constraint-module.js +1266 -0
  24. package/app/js/modules/data-module.js +1054 -0
  25. package/app/js/modules/drawing-module.js +54 -8
  26. package/app/js/modules/formats-module.js +873 -0
  27. package/app/js/modules/inspection-module.js +1330 -0
  28. package/app/js/modules/mesh-module-enhanced.js +880 -0
  29. package/app/js/modules/mesh-module.js +968 -0
  30. package/app/js/modules/operations-module.js +40 -7
  31. package/app/js/modules/plugin-module.js +1554 -0
  32. package/app/js/modules/rendering-module.js +1766 -0
  33. package/app/js/modules/scripting-module.js +1073 -0
  34. package/app/js/modules/simulation-module.js +60 -3
  35. package/app/js/modules/sketch-module.js +2029 -91
  36. package/app/js/modules/step-module.js +47 -6
  37. package/app/js/modules/surface-module.js +1040 -0
  38. package/app/js/modules/version-module.js +1830 -0
  39. package/app/js/modules/viewport-module.js +95 -8
  40. package/app/test-agent-v2.html +881 -1316
  41. package/cycleCAD-Architecture-v2.pptx +0 -0
  42. package/docs/ARCHITECTURE.html +838 -1408
  43. package/docs/DEVELOPER-GUIDE.md +1504 -0
  44. package/docs/TUTORIAL.md +740 -0
  45. package/package.json +1 -1
  46. package/~$cycleCAD-Architecture-v2.pptx +0 -0
  47. package/.github/scripts/cad-diff.js +0 -590
  48. package/.github/workflows/cad-diff.yml +0 -117
package/app/js/kernel.js CHANGED
@@ -1,49 +1,191 @@
1
1
  /**
2
- * cycleCAD Microkernel
2
+ * @file kernel.js
3
+ * @description cycleCAD Microkernel — A lightweight, modular architecture for pluggable CAD components.
4
+ * Manages module lifecycle, dependencies, lazy loading, hot-swapping, and inter-module communication
5
+ * via events and commands.
3
6
  *
4
- * A lightweight, modular architecture for pluggable CAD components.
5
- * Manages module lifecycle, dependencies, lazy loading, hot-swapping,
6
- * and inter-module communication via events and commands.
7
+ * @version 1.0.0
8
+ * @author cycleCAD Team
9
+ * @license MIT
10
+ * @see {@link https://github.com/vvlars-cmd/cyclecad}
7
11
  *
8
12
  * @module kernel
9
- * @version 1.0.0
13
+ * @requires nothing (foundational — no dependencies)
14
+ *
15
+ * Architecture:
16
+ * ┌──────────────────────────────────────────────┐
17
+ * │ KERNEL (tiny & modular) │
18
+ * │ Registry · Bus · Loader · Memory · API │
19
+ * └──────────────────────────────────────────────┘
20
+ *
21
+ * Design Patterns:
22
+ * - Module Registry: Central catalog of all loadable modules
23
+ * - State Management: Shared kernel.state with watchers
24
+ * - Event Bus: Pub/sub for inter-module communication
25
+ * - Lazy Loading: Modules load on-demand when commands execute
26
+ * - Memory Management: Automatic LRU eviction when budget exceeded
27
+ * - Dependency Resolution: Recursive load of dependencies before activation
28
+ *
29
+ * Usage Example:
30
+ * ```javascript
31
+ * import kernel from './kernel.js';
32
+ *
33
+ * // Register a module
34
+ * kernel.register({
35
+ * id: 'my-module',
36
+ * name: 'My Module',
37
+ * version: '1.0.0',
38
+ * category: 'engine',
39
+ * dependencies: ['viewport'],
40
+ * memoryEstimate: 10,
41
+ * async load(kernel) { console.log('Loading...'); },
42
+ * async activate(kernel) { console.log('Activated'); },
43
+ * async deactivate(kernel) { console.log('Deactivated'); },
44
+ * async unload(kernel) { console.log('Unloaded'); },
45
+ * provides: {
46
+ * commands: {
47
+ * 'my-module.greet': (kernel) => (name) => `Hello, ${name}!`
48
+ * }
49
+ * }
50
+ * });
51
+ *
52
+ * // Execute a command (auto-loads module if needed)
53
+ * const result = await kernel.exec('my-module.greet', {name: 'Alice'});
54
+ *
55
+ * // Listen for events
56
+ * kernel.on('module:loaded', (data) => {
57
+ * console.log('Module loaded:', data.id);
58
+ * });
59
+ *
60
+ * // Watch shared state
61
+ * kernel.state.watch('selectedPart', (newVal, oldVal) => {
62
+ * console.log(`Selection changed from ${oldVal} to ${newVal}`);
63
+ * });
64
+ * ```
10
65
  */
11
66
 
67
+ // ═══════════════════════════════════════════════════════
68
+ // MODULE STATES
69
+ // ═══════════════════════════════════════════════════════
70
+
12
71
  /**
13
- * Module lifecycle states
72
+ * Module lifecycle states enum
73
+ *
74
+ * State transitions:
75
+ * REGISTERED → LOADING → INACTIVE/ACTIVE ↔ INACTIVE → UNLOADING → UNLOADED
76
+ * ↓ (error) ↓ (error)
77
+ * ERROR (requires manual recovery) ERROR
78
+ *
14
79
  * @enum {string}
80
+ * @const
15
81
  */
16
82
  const ModuleState = {
83
+ /** Initial state after registration, not yet loaded */
17
84
  REGISTERED: 'registered',
85
+ /** Currently loading (resolving dependencies, running load hook) */
18
86
  LOADING: 'loading',
87
+ /** Loaded and activated, commands available */
19
88
  ACTIVE: 'active',
89
+ /** Loaded but not activated, takes memory but no commands */
20
90
  INACTIVE: 'inactive',
91
+ /** Currently unloading (running unload hook, freeing memory) */
21
92
  UNLOADING: 'unloading',
93
+ /** Fully unloaded, memory freed, requires full reload to use again */
22
94
  UNLOADED: 'unloaded',
95
+ /** Load or activation failed, error message stored in metadata */
23
96
  ERROR: 'error',
24
97
  };
25
98
 
99
+ // ═══════════════════════════════════════════════════════
100
+ // KERNEL CLASS
101
+ // ═══════════════════════════════════════════════════════
102
+
26
103
  /**
27
- * Kernel class — manages all modules and inter-module communication
104
+ * Kernel class — Central manager for all modules and inter-module communication
105
+ *
106
+ * The Kernel is the tiny heart of cycleCAD, handling:
107
+ * - Module registration, loading, activation, deactivation, unloading
108
+ * - Lazy loading on command execution
109
+ * - Dependency resolution (recursive load of dependencies)
110
+ * - Module state tracking (REGISTERED → LOADING → ACTIVE ↔ INACTIVE → UNLOADING → UNLOADED)
111
+ * - Shared state management with watchers
112
+ * - Event bus for pub/sub communication
113
+ * - Command registry and dispatch
114
+ * - Memory management with LRU eviction
115
+ * - Hot-swap capability (swap old module for new one)
116
+ *
117
+ * @class Kernel
118
+ * @param {Object} [config={}] - Configuration options
119
+ * @param {number} [config.memoryBudget=512] - Total memory budget in MB
120
+ * @param {boolean} [config.autoGC=true] - Enable automatic garbage collection
28
121
  */
29
122
  class Kernel {
30
123
  constructor(config = {}) {
124
+ /**
125
+ * Kernel configuration
126
+ * @type {Object}
127
+ * @property {number} memoryBudget - Total memory budget in MB (default 512)
128
+ * @property {boolean} autoGC - Auto-GC enabled (default true)
129
+ */
31
130
  this.config = {
32
131
  memoryBudget: 512, // MB
33
132
  autoGC: true,
34
133
  ...config,
35
134
  };
36
135
 
37
- // Registries
38
- this.modules = new Map(); // id → {definition, state, instance, metadata}
39
- this.commands = new Map(); // 'module.cmd' → handler
40
- this.shortcuts = new Map(); // 'shortcut' → 'module.cmd'
41
-
42
- // Event bus
43
- this.eventListeners = new Map(); // event → Set of handlers
44
- this.onceListeners = new Map(); // event → Map(handler → true)
45
-
46
- // Shared state (accessible to all modules)
136
+ // ═══════════════════════════════════════════════════════
137
+ // REGISTRIES
138
+ // ═══════════════════════════════════════════════════════
139
+
140
+ /**
141
+ * Module registry: id → {definition, state, instance, metadata}
142
+ * @type {Map<string, Object>}
143
+ */
144
+ this.modules = new Map();
145
+
146
+ /**
147
+ * Command registry: 'module.cmd' → handler function
148
+ * @type {Map<string, Function>}
149
+ */
150
+ this.commands = new Map();
151
+
152
+ /**
153
+ * Keyboard shortcut bindings: 'Ctrl+B' → 'module.cmd'
154
+ * @type {Map<string, string>}
155
+ */
156
+ this.shortcuts = new Map();
157
+
158
+ // ═══════════════════════════════════════════════════════
159
+ // EVENT BUS
160
+ // ═══════════════════════════════════════════════════════
161
+
162
+ /**
163
+ * Event listeners: event → Set of handler functions
164
+ * Supports wildcards (e.g., 'module:*' matches 'module:loaded')
165
+ * @type {Map<string, Set<Function>>}
166
+ */
167
+ this.eventListeners = new Map();
168
+
169
+ /**
170
+ * One-time listeners: event → Map(handler → true)
171
+ * Auto-unsubscribed after first fire
172
+ * @type {Map<string, Map<Function, boolean>>}
173
+ */
174
+ this.onceListeners = new Map();
175
+
176
+ // ═══════════════════════════════════════════════════════
177
+ // SHARED STATE
178
+ // ═══════════════════════════════════════════════════════
179
+
180
+ /**
181
+ * Shared kernel state — accessible and watchable by all modules
182
+ * Usage: kernel.state.set('selectedPart', partId);
183
+ * kernel.state.watch('selectedPart', (newVal, oldVal) => {...});
184
+ *
185
+ * @type {Object}
186
+ * @property {Map} _values - Stored state values (key → value)
187
+ * @property {Map} _watchers - Watchers (key → Set of handler functions)
188
+ */
47
189
  this.state = {
48
190
  _values: new Map(),
49
191
  _watchers: new Map(), // key → Set of handlers
@@ -81,7 +223,22 @@ class Kernel {
81
223
  all: () => Object.fromEntries(this.state._values),
82
224
  };
83
225
 
84
- // Memory manager
226
+ // ═══════════════════════════════════════════════════════
227
+ // MEMORY MANAGER
228
+ // ═══════════════════════════════════════════════════════
229
+
230
+ /**
231
+ * Memory management system with LRU eviction policy
232
+ * Tracks memory usage across modules and auto-evicts LRU inactive modules
233
+ * if budget exceeded (when pressure > 0.8)
234
+ *
235
+ * @type {Object}
236
+ * @property {Map} _estimates - Module ID → estimated memory in MB
237
+ * @property {Function} usage() - Get total memory used
238
+ * @property {Function} pressure() - Get pressure ratio (0-1, where 1.0 = at budget)
239
+ * @property {number} budget - Total memory budget in MB
240
+ * @property {Function} gc() - Run garbage collection (auto evicts if needed)
241
+ */
85
242
  this.memory = {
86
243
  _estimates: new Map(), // moduleId → MB
87
244
 
@@ -117,10 +274,62 @@ class Kernel {
117
274
  console.log('[Kernel] Initialized with memory budget:', this.config.memoryBudget, 'MB');
118
275
  }
119
276
 
277
+ // ═══════════════════════════════════════════════════════
278
+ // MODULE LIFECYCLE METHODS
279
+ // ═══════════════════════════════════════════════════════
280
+
120
281
  /**
121
- * Register a module definition
282
+ * Register a module definition with the kernel
283
+ *
284
+ * The module is not loaded until explicitly requested or when one of its
285
+ * commands is executed. This allows lazy loading of heavy modules.
286
+ *
122
287
  * @param {Object} definition - Module definition object
123
- * @returns {boolean} Success
288
+ * @param {string} definition.id - Unique module identifier (e.g., 'viewport', 'sketch')
289
+ * @param {string} definition.name - Human-readable name (e.g., 'Sketch Engine')
290
+ * @param {string} definition.version - Semantic version string (e.g., '1.0.0')
291
+ * @param {string} definition.category - Module category: 'engine'|'tool'|'data'|'service'|'core'
292
+ * @param {string[]} [definition.dependencies] - Array of module IDs this depends on
293
+ * @param {number} definition.memoryEstimate - Estimated memory usage in MB
294
+ * @param {string[]} [definition.replaces] - Module IDs this replaces (for hot-swap)
295
+ * @param {Function} [definition.load] - Async load hook: async load(kernel) {...}
296
+ * @param {Function} [definition.activate] - Async activate hook: async activate(kernel) {...}
297
+ * @param {Function} [definition.deactivate] - Async deactivate hook: async deactivate(kernel) {...}
298
+ * @param {Function} [definition.unload] - Async unload hook: async unload(kernel) {...}
299
+ * @param {Object} definition.provides - Provided API
300
+ * @param {Object} [definition.provides.commands] - Commands: {cmd: handler(kernel)}
301
+ * @param {Object} [definition.provides.ui] - UI components and shortcuts
302
+ * @returns {boolean} Registration success
303
+ * @throws {Error} If module ID already registered
304
+ *
305
+ * @example
306
+ * kernel.register({
307
+ * id: 'my-module',
308
+ * name: 'My Module',
309
+ * version: '1.0.0',
310
+ * category: 'tool',
311
+ * dependencies: ['viewport'],
312
+ * memoryEstimate: 15,
313
+ * async load(kernel) {
314
+ * console.log('Module loading...');
315
+ * },
316
+ * async activate(kernel) {
317
+ * console.log('Module activated');
318
+ * },
319
+ * async deactivate(kernel) {
320
+ * console.log('Module deactivated');
321
+ * },
322
+ * async unload(kernel) {
323
+ * console.log('Module unloaded, memory freed');
324
+ * },
325
+ * provides: {
326
+ * commands: {
327
+ * 'my-module.doSomething': (kernel) => (param) => {
328
+ * return `Did something with ${param}`;
329
+ * }
330
+ * }
331
+ * }
332
+ * });
124
333
  */
125
334
  register(definition) {
126
335
  if (!definition.id || !definition.name) {
@@ -198,10 +407,25 @@ class Kernel {
198
407
  }
199
408
 
200
409
  /**
201
- * Load a module (async)
202
- * Resolves dependencies first, then calls module.load()
203
- * @param {string} moduleId
204
- * @returns {Promise<boolean>}
410
+ * Load a module asynchronously
411
+ *
412
+ * Handles complete lifecycle: resolves dependencies recursively,
413
+ * marks state as LOADING, calls module.load() hook, transitions to INACTIVE.
414
+ * Idempotent — safe to call multiple times.
415
+ *
416
+ * State transition: REGISTERED → LOADING → INACTIVE
417
+ *
418
+ * @async
419
+ * @param {string} moduleId - Module identifier to load
420
+ * @returns {Promise<boolean>} True if load successful, false on error
421
+ * @emits module:loaded - When module enters INACTIVE state
422
+ * @emits module:error - If load fails
423
+ *
424
+ * @example
425
+ * const loaded = await kernel.load('viewport');
426
+ * if (loaded) {
427
+ * console.log('Viewport module is ready to activate');
428
+ * }
205
429
  */
206
430
  async load(moduleId) {
207
431
  const module = this.modules.get(moduleId);
@@ -253,9 +477,27 @@ class Kernel {
253
477
  }
254
478
 
255
479
  /**
256
- * Activate a module (load first if needed, then call activate hook)
257
- * @param {string} moduleId
258
- * @returns {Promise<boolean>}
480
+ * Activate a module (load first if needed)
481
+ *
482
+ * Ensures module is loaded, then calls activate hook and registers commands.
483
+ * Only ACTIVE modules have their commands available in the command registry.
484
+ * Idempotent — safe to call multiple times.
485
+ *
486
+ * State transition: REGISTERED → LOADING → INACTIVE → ACTIVE
487
+ * (or jump to ACTIVE if already INACTIVE)
488
+ *
489
+ * @async
490
+ * @param {string} moduleId - Module identifier to activate
491
+ * @returns {Promise<boolean>} True if activation successful, false on error
492
+ * @emits module:activated - When module reaches ACTIVE state
493
+ * @emits module:error - If activation fails
494
+ *
495
+ * @example
496
+ * const activated = await kernel.activate('viewport');
497
+ * if (activated) {
498
+ * // viewport.fitAll command is now available
499
+ * await kernel.exec('viewport.fitAll');
500
+ * }
259
501
  */
260
502
  async activate(moduleId) {
261
503
  const module = this.modules.get(moduleId);
@@ -480,11 +722,31 @@ class Kernel {
480
722
  }
481
723
 
482
724
  /**
483
- * Execute a named command
484
- * Auto-loads the command's module if not already loaded
485
- * @param {string} commandName - e.g., "brep.makeBox"
486
- * @param {Object} params - Command parameters
487
- * @returns {Promise<any>} Command result
725
+ * Execute a named command by name
726
+ *
727
+ * If command exists and module is active, executes immediately.
728
+ * Otherwise, auto-loads and auto-activates the command's module first.
729
+ * This enables transparent lazy loading — caller doesn't care if module is loaded.
730
+ *
731
+ * Command names follow pattern: 'module.command' (e.g., 'viewport.fitAll')
732
+ *
733
+ * @async
734
+ * @param {string} commandName - Fully-qualified command name (e.g., 'viewport.fitAll', 'brep.makeBox')
735
+ * @param {Object} [params={}] - Command parameters (passed directly to handler)
736
+ * @returns {Promise<any>} Command result (return value of handler function)
737
+ * @throws {Error} If command not found after module load/activate
738
+ * @throws {Error} If module fails to load or activate
739
+ *
740
+ * @example
741
+ * // Direct execution (module auto-loads if needed)
742
+ * const result = await kernel.exec('viewport.fitAll');
743
+ *
744
+ * // With parameters
745
+ * const box = await kernel.exec('brep.makeBox', {
746
+ * width: 10,
747
+ * height: 20,
748
+ * depth: 30
749
+ * });
488
750
  */
489
751
  async exec(commandName, params = {}) {
490
752
  // Check if command exists
@@ -549,10 +811,30 @@ class Kernel {
549
811
  return await this.exec(commandName, { fromShortcut: true });
550
812
  }
551
813
 
814
+ // ═══════════════════════════════════════════════════════
815
+ // EVENT BUS METHODS
816
+ // ═══════════════════════════════════════════════════════
817
+
552
818
  /**
553
- * Subscribe to an event
554
- * @param {string} event - Event name or pattern (e.g., "module:*")
555
- * @param {Function} handler
819
+ * Subscribe to an event (persistent listener)
820
+ *
821
+ * Listener fires every time the event is emitted.
822
+ * Supports wildcard patterns: 'module:*' matches 'module:loaded', 'module:error', etc.
823
+ *
824
+ * @param {string} event - Event name or wildcard pattern (e.g., 'module:loaded', 'module:*')
825
+ * @param {Function} handler - Handler function: handler(data) {...}
826
+ * @returns {void}
827
+ *
828
+ * @example
829
+ * // Listen for specific event
830
+ * kernel.on('module:loaded', (data) => {
831
+ * console.log('Module loaded:', data.id);
832
+ * });
833
+ *
834
+ * // Listen for wildcard (all module events)
835
+ * kernel.on('module:*', (data) => {
836
+ * console.log('Module event:', data);
837
+ * });
556
838
  */
557
839
  on(event, handler) {
558
840
  if (!this.eventListeners.has(event)) {
@@ -587,9 +869,24 @@ class Kernel {
587
869
  }
588
870
 
589
871
  /**
590
- * Emit an event
591
- * @param {string} event
592
- * @param {Object} data
872
+ * Emit an event to all subscribers
873
+ *
874
+ * Triggers both direct listeners (exact event match) and wildcard listeners
875
+ * (pattern match on event prefix). All handlers called synchronously in order.
876
+ *
877
+ * Wildcard matching: 'module:loaded' triggers listeners for 'module:*'
878
+ *
879
+ * @param {string} event - Event name to emit (e.g., 'module:loaded')
880
+ * @param {Object} [data={}] - Event data (passed to all handler functions)
881
+ * @throws {Error} Suppressed — errors in handlers are caught and logged
882
+ * @returns {void}
883
+ *
884
+ * @example
885
+ * // Emit event with data
886
+ * kernel.emit('module:loaded', {
887
+ * id: 'viewport',
888
+ * version: '1.0.0'
889
+ * });
593
890
  */
594
891
  emit(event, data = {}) {
595
892
  // Direct listeners
@@ -647,9 +944,36 @@ class Kernel {
647
944
  }
648
945
  }
649
946
 
947
+ // ═══════════════════════════════════════════════════════
948
+ // INSPECTION METHODS
949
+ // ═══════════════════════════════════════════════════════
950
+
650
951
  /**
651
- * Get detailed kernel status
652
- * @returns {Object}
952
+ * Get detailed kernel status and statistics
953
+ *
954
+ * Returns comprehensive status of all modules, commands, state, and memory.
955
+ * Useful for debugging and monitoring kernel health.
956
+ *
957
+ * @returns {Object} Status report
958
+ * @returns {Object} .modules - Module counts by state
959
+ * @returns {number} .modules.registered - Number of registered modules
960
+ * @returns {number} .modules.active - Number of active modules
961
+ * @returns {number} .modules.inactive - Number of inactive (loaded but not active) modules
962
+ * @returns {number} .modules.error - Number of modules in error state
963
+ * @returns {number} .commands - Total registered commands
964
+ * @returns {number} .shortcuts - Total keyboard shortcuts
965
+ * @returns {Object} .memory - Memory usage information
966
+ * @returns {number} .memory.usage - Current usage in MB
967
+ * @returns {number} .memory.budget - Total budget in MB
968
+ * @returns {string} .memory.pressure - Pressure as percentage (0-100%)
969
+ * @returns {Object} .state - Shared state information
970
+ * @returns {number} .state.keys - Number of shared state keys
971
+ * @returns {number} .state.watchers - Number of active state watchers
972
+ *
973
+ * @example
974
+ * const status = kernel.status();
975
+ * console.log(`${status.modules.active} modules active, ${status.memory.pressure} pressure`);
976
+ * // Output: "3 modules active, 25.5% pressure"
653
977
  */
654
978
  status() {
655
979
  return {