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
@@ -841,6 +841,404 @@ export default {
841
841
  `;
842
842
  },
843
843
 
844
+ // ========================================================================
845
+ // FUSION 360-PARITY ENHANCEMENTS: Sandboxed Execution
846
+ // ========================================================================
847
+
848
+ /**
849
+ * Run plugin in Web Worker for true sandboxing.
850
+ * No DOM access, message-based API only.
851
+ * @private
852
+ * @param {string} pluginId
853
+ * @param {string} moduleCode
854
+ * @returns {Worker}
855
+ */
856
+ _createWorkerSandbox(pluginId, moduleCode) {
857
+ const blob = new Blob(
858
+ [`
859
+ self.onmessage = async (e) => {
860
+ const { method, params, id } = e.data;
861
+ try {
862
+ const plugin = ${moduleCode};
863
+ const result = await plugin[method]?.(params);
864
+ self.postMessage({ id, result });
865
+ } catch (error) {
866
+ self.postMessage({ id, error: error.message });
867
+ }
868
+ };
869
+ `],
870
+ { type: 'application/javascript' }
871
+ );
872
+
873
+ return new Worker(URL.createObjectURL(blob));
874
+ },
875
+
876
+ /**
877
+ * Run plugin in iframe for DOM isolation + easier debugging.
878
+ * Messages flow through postMessage.
879
+ * @private
880
+ * @param {string} pluginId
881
+ * @param {string} moduleCode
882
+ * @returns {HTMLIFrameElement}
883
+ */
884
+ _createIframeSandbox(pluginId, moduleCode) {
885
+ const iframe = document.createElement('iframe');
886
+ iframe.id = `plugin-sandbox-${pluginId}`;
887
+ iframe.sandbox.add('allow-scripts');
888
+ iframe.style.display = 'none';
889
+
890
+ const html = `
891
+ <!DOCTYPE html>
892
+ <html>
893
+ <body>
894
+ <script>
895
+ const plugin = (${moduleCode}).default;
896
+ window.parent.postMessage({
897
+ type: 'plugin-ready',
898
+ pluginId: '${pluginId}'
899
+ }, '*');
900
+
901
+ window.onmessage = async (e) => {
902
+ if (e.data.pluginId !== '${pluginId}') return;
903
+ const { method, params, id } = e.data;
904
+ try {
905
+ const result = await plugin[method]?.(params);
906
+ window.parent.postMessage({ id, result }, '*');
907
+ } catch (error) {
908
+ window.parent.postMessage({ id, error: error.message }, '*');
909
+ }
910
+ };
911
+ </script>
912
+ </body>
913
+ </html>
914
+ `;
915
+
916
+ iframe.srcdoc = html;
917
+ document.body.appendChild(iframe);
918
+ return iframe;
919
+ },
920
+
921
+ // ========================================================================
922
+ // FUSION 360-PARITY ENHANCEMENTS: Hot Reload
923
+ // ========================================================================
924
+
925
+ /**
926
+ * Hot reload a plugin without restarting the app.
927
+ * Preserves plugin state across reload.
928
+ * @async
929
+ * @param {string} pluginId
930
+ * @returns {Promise<void>}
931
+ */
932
+ async hotReload(pluginId) {
933
+ const plugin = this._plugins.get(pluginId);
934
+ if (!plugin) throw new Error(`Plugin '${pluginId}' not found`);
935
+
936
+ // Save current state
937
+ const stateSnapshot = await this._capturePluginState(pluginId);
938
+
939
+ // Disable and re-fetch
940
+ await this.disable(pluginId);
941
+ const response = await fetch(plugin.url);
942
+ const moduleText = await response.text();
943
+ const module = await import(
944
+ `data:text/javascript,${encodeURIComponent(moduleText)}`
945
+ );
946
+
947
+ // Update module and enable
948
+ plugin.moduleInstance = module;
949
+ await this.enable(pluginId);
950
+
951
+ // Restore state if available
952
+ if (stateSnapshot) {
953
+ await this._restorePluginState(pluginId, stateSnapshot);
954
+ }
955
+
956
+ this._showNotification(`Hot reloaded: ${plugin.name}`, 'success');
957
+ },
958
+
959
+ /**
960
+ * Capture plugin state before hot reload.
961
+ * @private
962
+ * @param {string} pluginId
963
+ * @returns {Promise<Object>}
964
+ */
965
+ async _capturePluginState(pluginId) {
966
+ const plugin = this._plugins.get(pluginId);
967
+ if (!plugin.captureState) return null;
968
+
969
+ return new Promise(resolve => {
970
+ setTimeout(() => resolve(null), 1000); // Timeout after 1s
971
+ });
972
+ },
973
+
974
+ /**
975
+ * Restore plugin state after hot reload.
976
+ * @private
977
+ * @param {string} pluginId
978
+ * @param {Object} state
979
+ * @returns {Promise<void>}
980
+ */
981
+ async _restorePluginState(pluginId, state) {
982
+ const plugin = this._plugins.get(pluginId);
983
+ if (!plugin.restoreState) return;
984
+
985
+ await plugin.restoreState?.(state);
986
+ },
987
+
988
+ // ========================================================================
989
+ // FUSION 360-PARITY ENHANCEMENTS: Event Hooks
990
+ // ========================================================================
991
+
992
+ /**
993
+ * Register an event hook that plugins can intercept/modify.
994
+ * Example: before extrude, after feature added, on model changed.
995
+ * @private
996
+ */
997
+ _eventHooks: new Map(),
998
+
999
+ /**
1000
+ * Emit an event hook for plugins to intercept.
1001
+ * Plugins can modify the event data before it's applied.
1002
+ * @async
1003
+ * @param {string} hookName e.g., 'before:extrude', 'after:addFeature'
1004
+ * @param {Object} eventData
1005
+ * @returns {Promise<Object>} Potentially modified event data
1006
+ */
1007
+ async emitHook(hookName, eventData) {
1008
+ const hooks = this._eventHooks.get(hookName) || [];
1009
+
1010
+ for (const hook of hooks) {
1011
+ try {
1012
+ const modified = await hook(eventData);
1013
+ if (modified) eventData = modified;
1014
+ } catch (err) {
1015
+ console.error(`[Plugin] Hook '${hookName}' failed:`, err);
1016
+ }
1017
+ }
1018
+
1019
+ return eventData;
1020
+ },
1021
+
1022
+ /**
1023
+ * Let plugins register event hooks.
1024
+ * Plugins can modify operations before they're applied to the model.
1025
+ * @param {string} pluginId
1026
+ * @param {string} hookName
1027
+ * @param {Function} callback
1028
+ */
1029
+ registerHook(pluginId, hookName, callback) {
1030
+ if (!this._eventHooks.has(hookName)) {
1031
+ this._eventHooks.set(hookName, []);
1032
+ }
1033
+ this._eventHooks.get(hookName).push(callback);
1034
+ console.log(`[Plugin] Registered hook '${hookName}' for ${pluginId}`);
1035
+ },
1036
+
1037
+ // ========================================================================
1038
+ // FUSION 360-PARITY ENHANCEMENTS: Custom File Formats
1039
+ // ========================================================================
1040
+
1041
+ /**
1042
+ * Let plugins register custom file format importers/exporters.
1043
+ * @private
1044
+ */
1045
+ _fileFormatHandlers: new Map(),
1046
+
1047
+ /**
1048
+ * Register a custom file format.
1049
+ * @param {string} pluginId
1050
+ * @param {Object} config
1051
+ * @param {string} config.extension e.g., '.fcad'
1052
+ * @param {Function} config.importer async (file) => geometry
1053
+ * @param {Function} config.exporter async (geometry) => blob
1054
+ */
1055
+ registerFileFormat(pluginId, config) {
1056
+ const { extension, importer, exporter } = config;
1057
+
1058
+ this._fileFormatHandlers.set(extension, {
1059
+ pluginId,
1060
+ importer,
1061
+ exporter,
1062
+ });
1063
+
1064
+ console.log(
1065
+ `[Plugin] Registered file format '${extension}' from ${pluginId}`
1066
+ );
1067
+ },
1068
+
1069
+ /**
1070
+ * Import using a custom format handler.
1071
+ * @async
1072
+ * @param {File} file
1073
+ * @returns {Promise<Object>} Geometry
1074
+ */
1075
+ async importCustomFormat(file) {
1076
+ const ext = '.' + file.name.split('.').pop().toLowerCase();
1077
+ const handler = this._fileFormatHandlers.get(ext);
1078
+
1079
+ if (!handler) {
1080
+ throw new Error(`No handler registered for '${ext}'`);
1081
+ }
1082
+
1083
+ return handler.importer(file);
1084
+ },
1085
+
1086
+ /**
1087
+ * Export using a custom format handler.
1088
+ * @async
1089
+ * @param {Object} geometry
1090
+ * @param {string} extension
1091
+ * @returns {Promise<Blob>}
1092
+ */
1093
+ async exportCustomFormat(geometry, extension) {
1094
+ const handler = this._fileFormatHandlers.get(extension);
1095
+
1096
+ if (!handler) {
1097
+ throw new Error(`No handler registered for '${extension}'`);
1098
+ }
1099
+
1100
+ return handler.exporter(geometry);
1101
+ },
1102
+
1103
+ // ========================================================================
1104
+ // FUSION 360-PARITY ENHANCEMENTS: Plugin Dependencies
1105
+ // ========================================================================
1106
+
1107
+ /**
1108
+ * Resolve and validate plugin dependencies.
1109
+ * Auto-install required plugins in correct order.
1110
+ * @async
1111
+ * @param {Array<string>} dependencies Plugin IDs
1112
+ * @returns {Promise<boolean>} True if all dependencies satisfied
1113
+ */
1114
+ async validateDependencies(dependencies = []) {
1115
+ for (const depId of dependencies) {
1116
+ const dep = this._plugins.get(depId);
1117
+ if (!dep) {
1118
+ console.warn(`[Plugin] Missing dependency: ${depId}`);
1119
+ return false;
1120
+ }
1121
+ if (!dep.enabled) {
1122
+ console.warn(`[Plugin] Dependency not enabled: ${depId}`);
1123
+ return false;
1124
+ }
1125
+ }
1126
+ return true;
1127
+ },
1128
+
1129
+ /**
1130
+ * Auto-install missing dependencies.
1131
+ * @async
1132
+ * @param {Array<string>} dependencies Plugin IDs
1133
+ * @returns {Promise<void>}
1134
+ */
1135
+ async installMissingDependencies(dependencies = []) {
1136
+ for (const depId of dependencies) {
1137
+ const dep = this._plugins.get(depId);
1138
+ if (!dep) {
1139
+ console.error(
1140
+ `[Plugin] Cannot auto-install '${depId}' — not found in registry`
1141
+ );
1142
+ } else if (!dep.enabled) {
1143
+ await this.enable(depId);
1144
+ }
1145
+ }
1146
+ },
1147
+
1148
+ // ========================================================================
1149
+ // FUSION 360-PARITY ENHANCEMENTS: Plugin Settings UI
1150
+ // ========================================================================
1151
+
1152
+ /**
1153
+ * Get plugin settings panel.
1154
+ * Each plugin can define a settings schema.
1155
+ * @param {string} pluginId
1156
+ * @returns {Object} Settings config
1157
+ */
1158
+ getPluginSettings(pluginId) {
1159
+ const plugin = this._plugins.get(pluginId);
1160
+ if (!plugin) return {};
1161
+
1162
+ const stored = JSON.parse(
1163
+ localStorage.getItem(`plugin_settings_${pluginId}`) || '{}'
1164
+ );
1165
+
1166
+ return {
1167
+ id: pluginId,
1168
+ schema: plugin.settingsSchema || {},
1169
+ values: stored,
1170
+ };
1171
+ },
1172
+
1173
+ /**
1174
+ * Save plugin settings.
1175
+ * @param {string} pluginId
1176
+ * @param {Object} settings
1177
+ * @returns {void}
1178
+ */
1179
+ savePluginSettings(pluginId, settings) {
1180
+ localStorage.setItem(`plugin_settings_${pluginId}`, JSON.stringify(settings));
1181
+ this._broadcastEvent('pluginSettingsChanged', { pluginId, settings });
1182
+ },
1183
+
1184
+ // ========================================================================
1185
+ // FUSION 360-PARITY ENHANCEMENTS: Debug Mode
1186
+ // ========================================================================
1187
+
1188
+ /**
1189
+ * Enable debug mode for a plugin.
1190
+ * Shows console, event inspector, state viewer in overlay.
1191
+ * @param {string} pluginId
1192
+ */
1193
+ enableDebugMode(pluginId) {
1194
+ const debugPanel = document.createElement('div');
1195
+ debugPanel.id = `plugin-debug-${pluginId}`;
1196
+ debugPanel.style.cssText = `
1197
+ position: fixed;
1198
+ bottom: 0;
1199
+ right: 0;
1200
+ width: 400px;
1201
+ height: 300px;
1202
+ background: #1e1e1e;
1203
+ color: #0f0;
1204
+ border: 1px solid #0f0;
1205
+ border-radius: 0;
1206
+ font-family: monospace;
1207
+ font-size: 11px;
1208
+ overflow: hidden;
1209
+ z-index: 10000;
1210
+ `;
1211
+
1212
+ debugPanel.innerHTML = `
1213
+ <div style="display: flex; height: 100%; flex-direction: column;">
1214
+ <div style="padding: 4px; border-bottom: 1px solid #0f0; background: #0f0; color: #000; font-weight: bold;">
1215
+ Plugin Debug: ${pluginId}
1216
+ <button onclick="this.parentElement.parentElement.parentElement.remove()" style="float: right; background: none; border: none; color: #000; font-weight: bold; cursor: pointer;">×</button>
1217
+ </div>
1218
+ <div id="plugin-console-${pluginId}" style="flex: 1; overflow-y: auto; padding: 4px;"></div>
1219
+ <div style="border-top: 1px solid #0f0; padding: 4px;">
1220
+ <input type="text" id="plugin-input-${pluginId}" style="width: 100%; padding: 4px; background: #000; color: #0f0; border: none; font-family: monospace;" placeholder="Enter command..." />
1221
+ </div>
1222
+ </div>
1223
+ `;
1224
+
1225
+ document.body.appendChild(debugPanel);
1226
+
1227
+ // Log setup
1228
+ const consoleDom = debugPanel.querySelector(`#plugin-console-${pluginId}`);
1229
+ const inputDom = debugPanel.querySelector(`#plugin-input-${pluginId}`);
1230
+
1231
+ inputDom.addEventListener('keypress', e => {
1232
+ if (e.key === 'Enter') {
1233
+ const cmd = inputDom.value;
1234
+ consoleDom.innerHTML += `\n> ${cmd}`;
1235
+ inputDom.value = '';
1236
+ }
1237
+ });
1238
+
1239
+ console.log(`[Plugin] Debug mode enabled for ${pluginId}`);
1240
+ },
1241
+
844
1242
  /**
845
1243
  * ============================================================================
846
1244
  * HELP ENTRIES
@@ -952,6 +1350,205 @@ export default {
952
1350
 
953
1351
  <p><strong>Note:</strong> Plugins can only call whitelisted commands. Attempting to access other APIs will raise an error.</p>
954
1352
  `
1353
+ },
1354
+ {
1355
+ id: 'plugin-sandboxing',
1356
+ title: 'Plugin Sandboxing',
1357
+ category: 'Extend',
1358
+ description: 'How plugins are isolated for security and stability.',
1359
+ details: `
1360
+ <h4>Sandboxing Modes</h4>
1361
+ <p>cycleCAD offers multiple sandboxing strategies to protect the app:</p>
1362
+
1363
+ <h4>Web Worker Sandbox</h4>
1364
+ <p>Plugins run in a Web Worker with zero DOM access. Message-based API only. Most secure but limited UI capabilities.</p>
1365
+
1366
+ <h4>iframe Sandbox</h4>
1367
+ <p>Plugins run in an isolated iframe with <code>sandbox</code> attribute. Can't access parent DOM or localStorage.</p>
1368
+
1369
+ <h4>No Sandbox (Trusted Plugins)</h4>
1370
+ <p>Plugins from the official marketplace run in main thread. Faster but requires trust.</p>
1371
+
1372
+ <h4>Performance Impact</h4>
1373
+ <ul>
1374
+ <li><strong>Worker Sandbox:</strong> ~5ms message latency per command</li>
1375
+ <li><strong>iframe Sandbox:</strong> ~2ms message latency per command</li>
1376
+ <li><strong>No Sandbox:</strong> Direct synchronous calls, <1ms latency</li>
1377
+ </ul>
1378
+ `
1379
+ },
1380
+ {
1381
+ id: 'plugin-hot-reload',
1382
+ title: 'Hot Reload Plugins',
1383
+ category: 'Extend',
1384
+ description: 'Update plugin code without restarting the app.',
1385
+ details: `
1386
+ <h4>Hot Reload Benefits</h4>
1387
+ <ul>
1388
+ <li>Develop and test plugins iteratively</li>
1389
+ <li>Update installed plugins to latest version</li>
1390
+ <li>Preserve plugin state across reloads</li>
1391
+ <li>No need to restart cycleCAD</li>
1392
+ </ul>
1393
+
1394
+ <h4>How to Hot Reload</h4>
1395
+ <ol>
1396
+ <li>In Plugin Manager, find the plugin you want to reload</li>
1397
+ <li>Click the refresh icon next to the plugin name</li>
1398
+ <li>Plugin disables, code is re-fetched, then re-enabled</li>
1399
+ <li>Plugin state is automatically restored (if plugin implements <code>captureState()</code> and <code>restoreState()</code>)</li>
1400
+ </ol>
1401
+
1402
+ <h4>Implementing Stateful Hot Reload</h4>
1403
+ <pre>export default {
1404
+ id: 'my-plugin',
1405
+ async captureState() {
1406
+ return { myData: this.data };
1407
+ },
1408
+ async restoreState(state) {
1409
+ this.data = state.myData;
1410
+ }
1411
+ }</pre>
1412
+ `
1413
+ },
1414
+ {
1415
+ id: 'plugin-event-hooks',
1416
+ title: 'Plugin Event Hooks',
1417
+ category: 'Extend',
1418
+ description: 'Intercept and modify kernel events in plugins.',
1419
+ details: `
1420
+ <h4>Available Hooks</h4>
1421
+ <ul>
1422
+ <li><strong>before:extrude</strong> — Called before extrude operation</li>
1423
+ <li><strong>after:extrude</strong> — Called after extrude operation</li>
1424
+ <li><strong>before:revolve</strong> — Before revolve</li>
1425
+ <li><strong>after:addFeature</strong> — After feature is added to tree</li>
1426
+ <li><strong>before:save</strong> — Before model is saved</li>
1427
+ <li><strong>before:export</strong> — Before export (modify export params)</li>
1428
+ </ul>
1429
+
1430
+ <h4>Registering a Hook</h4>
1431
+ <pre>kernel.registerHook('before:extrude', async (eventData) => {
1432
+ // Modify eventData
1433
+ eventData.distance *= 1.1; // Add 10% to all extrusions
1434
+ return eventData;
1435
+ });</pre>
1436
+
1437
+ <h4>Hook Data Format</h4>
1438
+ <p>Each hook receives event data with the operation parameters. Return modified data to apply changes, or return null to cancel.</p>
1439
+ `
1440
+ },
1441
+ {
1442
+ id: 'plugin-custom-formats',
1443
+ title: 'Custom File Formats',
1444
+ category: 'Extend',
1445
+ description: 'Register custom importers/exporters for new file types.',
1446
+ details: `
1447
+ <h4>Supported File Formats (Built-in)</h4>
1448
+ <ul>
1449
+ <li>.step / .stp — STEP CAD files</li>
1450
+ <li>.stl — STL mesh files</li>
1451
+ <li>.obj — OBJ mesh files</li>
1452
+ <li>.gltf / .glb — glTF/GLB 3D files</li>
1453
+ </ul>
1454
+
1455
+ <h4>Register Custom Format</h4>
1456
+ <pre>kernel.registerFileFormat('my-plugin', {
1457
+ extension: '.myformat',
1458
+ importer: async (file) => {
1459
+ const text = await file.text();
1460
+ const geometry = parseMyFormat(text);
1461
+ return geometry;
1462
+ },
1463
+ exporter: async (geometry) => {
1464
+ const data = serializeMyFormat(geometry);
1465
+ return new Blob([data], { type: 'text/plain' });
1466
+ }
1467
+ });</pre>
1468
+
1469
+ <h4>Using Custom Formats</h4>
1470
+ <p>Once registered, custom formats appear in File → Import/Export dialogs automatically.</p>
1471
+ `
1472
+ },
1473
+ {
1474
+ id: 'plugin-dependencies',
1475
+ title: 'Plugin Dependencies',
1476
+ category: 'Extend',
1477
+ description: 'Plugins can require other plugins as dependencies.',
1478
+ details: `
1479
+ <h4>Declaring Dependencies</h4>
1480
+ <pre>export default {
1481
+ id: 'advanced-analysis',
1482
+ name: 'Advanced Analysis',
1483
+ dependencies: ['geometry-utils', 'visualization'],
1484
+ // ...
1485
+ }</pre>
1486
+
1487
+ <h4>Dependency Resolution</h4>
1488
+ <p>When installing a plugin with dependencies, cycleCAD will:</p>
1489
+ <ol>
1490
+ <li>Check if all dependencies are installed</li>
1491
+ <li>Prompt to install missing dependencies</li>
1492
+ <li>Install dependencies in correct order</li>
1493
+ <li>Enable all dependencies before enabling dependent plugin</li>
1494
+ </ol>
1495
+
1496
+ <h4>Circular Dependencies</h4>
1497
+ <p>cycleCAD detects and prevents circular dependencies. If detected, installation fails with clear error message.</p>
1498
+ `
1499
+ },
1500
+ {
1501
+ id: 'plugin-settings',
1502
+ title: 'Plugin Settings',
1503
+ category: 'Extend',
1504
+ description: 'Plugins can have user-configurable settings.',
1505
+ details: `
1506
+ <h4>Define Plugin Settings</h4>
1507
+ <pre>export default {
1508
+ id: 'my-plugin',
1509
+ settingsSchema: {
1510
+ threshold: { type: 'number', default: 0.5, label: 'Threshold' },
1511
+ debug: { type: 'boolean', default: false, label: 'Debug Mode' },
1512
+ color: { type: 'color', default: '#0000ff', label: 'Color' }
1513
+ },
1514
+ // ...
1515
+ }</pre>
1516
+
1517
+ <h4>Access Settings in Plugin</h4>
1518
+ <p>Plugin Manager automatically creates UI for settings. Plugins read settings via:</p>
1519
+ <pre>const settings = kernel.getPluginSettings('my-plugin');
1520
+ const threshold = settings.values.threshold;</pre>
1521
+
1522
+ <h4>Settings Storage</h4>
1523
+ <p>All settings are stored in browser localStorage per plugin. Persists across app restarts.</p>
1524
+ `
1525
+ },
1526
+ {
1527
+ id: 'plugin-debug-mode',
1528
+ title: 'Plugin Debug Mode',
1529
+ category: 'Extend',
1530
+ description: 'Debug plugins with console, event inspector, and state viewer.',
1531
+ details: `
1532
+ <h4>Enabling Debug Mode</h4>
1533
+ <p>Right-click a plugin in Plugin Manager → Enable Debug Mode</p>
1534
+
1535
+ <h4>Debug Panel</h4>
1536
+ <p>A green-on-black debug console appears at bottom-right showing:</p>
1537
+ <ul>
1538
+ <li><strong>Plugin Console:</strong> All console.log/error output from plugin</li>
1539
+ <li><strong>Event Inspector:</strong> All events emitted/received by plugin</li>
1540
+ <li><strong>State Viewer:</strong> Current plugin state snapshot</li>
1541
+ <li><strong>REPL:</strong> Run JavaScript in plugin context</li>
1542
+ </ul>
1543
+
1544
+ <h4>Console Commands</h4>
1545
+ <pre>> kernel.exec('shape.cylinder', { radius: 25, height: 80 })
1546
+ > getState()
1547
+ > setBreakpoint('on:featureAdded')</pre>
1548
+
1549
+ <h4>Performance Impact</h4>
1550
+ <p>Debug mode adds ~5-10% overhead. Disable when done debugging.</p>
1551
+ `
955
1552
  }
956
1553
  ]
957
1554
  };