cyclecad 0.4.0 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app/index.html CHANGED
@@ -41,6 +41,57 @@
41
41
  --properties-width: 300px;
42
42
  }
43
43
 
44
+ /* ===== ViewCube ===== */
45
+ .vc-face {
46
+ position: absolute;
47
+ width: 80px;
48
+ height: 80px;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ font-size: 10px;
53
+ font-weight: 700;
54
+ color: rgba(255,255,255,0.85);
55
+ background: rgba(40,44,52,0.8);
56
+ border: 1px solid rgba(88,166,255,0.4);
57
+ backface-visibility: hidden;
58
+ user-select: none;
59
+ text-transform: uppercase;
60
+ letter-spacing: 0.5px;
61
+ }
62
+ .vc-face:hover {
63
+ background: rgba(88,166,255,0.5);
64
+ color: #fff;
65
+ }
66
+ :root.light-mode .vc-face {
67
+ background: rgba(220,225,235,0.85);
68
+ color: rgba(0,0,0,0.7);
69
+ border: 1px solid rgba(0,102,204,0.4);
70
+ }
71
+ :root.light-mode .vc-face:hover {
72
+ background: rgba(0,102,204,0.5);
73
+ color: #fff;
74
+ }
75
+
76
+ /* ===== Light Mode ===== */
77
+ :root.light-mode {
78
+ --bg-primary: #f5f5f5;
79
+ --bg-secondary: #ffffff;
80
+ --bg-tertiary: #e8e8e8;
81
+ --border-color: #d0d0d0;
82
+ --text-primary: #1a1a1a;
83
+ --text-secondary: #555555;
84
+ --text-muted: #888888;
85
+ --accent-blue: #0066cc;
86
+ --accent-blue-dark: #004499;
87
+ --accent-green: #22863a;
88
+ --accent-red: #d73a49;
89
+ --accent-yellow: #b08800;
90
+ --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
91
+ --shadow-md: 0 8px 16px rgba(0, 0, 0, 0.12);
92
+ --shadow-lg: 0 16px 32px rgba(0, 0, 0, 0.15);
93
+ }
94
+
44
95
  /* ===== Reset & Globals ===== */
45
96
  * {
46
97
  margin: 0;
@@ -92,11 +143,67 @@
92
143
  display: flex;
93
144
  align-items: center;
94
145
  padding: 0 8px;
95
- gap: 8px;
146
+ gap: 4px;
96
147
  user-select: none;
97
148
  z-index: 100;
98
149
  box-shadow: var(--shadow-sm);
150
+ overflow: visible;
151
+ scrollbar-width: thin;
152
+ scrollbar-color: rgba(255,255,255,0.2) transparent;
99
153
  }
154
+ #toolbar::-webkit-scrollbar { height: 3px; }
155
+ #toolbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }
156
+ #toolbar::-webkit-scrollbar-track { background: transparent; }
157
+
158
+ /* Dropdown menu for grouped tools */
159
+ .toolbar-dropdown {
160
+ position: relative;
161
+ }
162
+ .toolbar-dropdown-btn {
163
+ padding: 4px 6px;
164
+ height: 30px;
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 3px;
168
+ background: transparent;
169
+ border: 1px solid transparent;
170
+ border-radius: 4px;
171
+ color: var(--text-secondary);
172
+ cursor: pointer;
173
+ font-size: 11px;
174
+ white-space: nowrap;
175
+ }
176
+ .toolbar-dropdown-btn:hover { background: var(--bg-tertiary); color: var(--text-primary); }
177
+ .toolbar-dropdown-btn .caret { font-size: 9px; opacity: 0.7; margin-left: 2px; }
178
+ .toolbar-dropdown-menu {
179
+ display: none;
180
+ position: fixed;
181
+ background: var(--bg-secondary);
182
+ border: 1px solid var(--border-color);
183
+ border-radius: 6px;
184
+ padding: 4px;
185
+ min-width: 160px;
186
+ z-index: 9999;
187
+ box-shadow: 0 4px 12px rgba(0,0,0,0.4);
188
+ max-height: 70vh;
189
+ overflow-y: auto;
190
+ }
191
+ .toolbar-dropdown.open .toolbar-dropdown-menu { display: block; }
192
+ .toolbar-dropdown-menu button {
193
+ display: flex;
194
+ align-items: center;
195
+ gap: 6px;
196
+ width: 100%;
197
+ padding: 5px 8px;
198
+ background: transparent;
199
+ border: none;
200
+ border-radius: 4px;
201
+ color: var(--text-secondary);
202
+ cursor: pointer;
203
+ font-size: 11px;
204
+ white-space: nowrap;
205
+ }
206
+ .toolbar-dropdown-menu button:hover { background: var(--bg-tertiary); color: var(--text-primary); }
100
207
 
101
208
  #content {
102
209
  flex: 1;
@@ -1280,13 +1387,13 @@
1280
1387
  <!-- Token Engine — Initialize early so window.cycleCAD.tokens is available -->
1281
1388
  <script src="./js/token-engine.js"></script>
1282
1389
  <!-- New Architecture Modules (ES modules need type="module") -->
1283
- <script type="module" src="./js/material-library.js"></script>
1390
+ <script type="module" src="./js/material-library.js?v=62"></script>
1284
1391
  <script src="./js/dfm-analyzer.js"></script>
1285
- <script type="module" src="./js/cam-pipeline.js"></script>
1392
+ <script type="module" src="./js/cam-pipeline.js?v=62"></script>
1286
1393
  <script src="./js/connected-fabs.js"></script>
1287
- <script type="module" src="./js/ai-copilot.js"></script>
1288
- <script type="module" src="./js/collaboration.js"></script>
1289
- <script type="module" src="./js/collaboration-ui.js"></script>
1394
+ <script type="module" src="./js/ai-copilot.js?v=62"></script>
1395
+ <script type="module" src="./js/collaboration.js?v=62"></script>
1396
+ <script type="module" src="./js/collaboration-ui.js?v=62"></script>
1290
1397
  <!-- CadXStudio-killer modules (IIFE, no imports) -->
1291
1398
  <script src="./js/text-to-cad.js"></script>
1292
1399
  <script src="./js/cam-operations.js"></script>
@@ -1301,6 +1408,7 @@
1301
1408
  <script src="./js/gdt-training.js"></script>
1302
1409
  <script src="./js/misumi-catalog.js"></script>
1303
1410
  <script src="./js/cad-vr.js"></script>
1411
+ <script src="./js/generative-design.js"></script>
1304
1412
  </head>
1305
1413
  <body>
1306
1414
  <div id="app">
@@ -1311,6 +1419,7 @@
1311
1419
  <span class="splash-logo-cycle">cycle</span><span class="splash-logo-cad">CAD</span>
1312
1420
  </div>
1313
1421
  <p class="splash-subtitle">Parametric 3D CAD Modeler for the Mechanical Designer</p>
1422
+ <p style="color: rgba(255,255,255,0.45); font-size: 0.8rem; margin: 4px 0 0 0; letter-spacing: 1px;">v0.8.5</p>
1314
1423
  </div>
1315
1424
  <div class="splash-options">
1316
1425
  <button class="splash-button splash-button-primary" id="btn-empty-project" style="grid-column: 1 / -1;">
@@ -1329,274 +1438,144 @@
1329
1438
  <span>📂</span> DUO Project Browser (473 Parts)
1330
1439
  </button>
1331
1440
  </div>
1441
+ <button id="btn-splash-reset" style="margin-top: 12px; padding: 6px 16px; background: rgba(255,80,80,0.15); border: 1px solid rgba(255,80,80,0.3); color: #f88; border-radius: 6px; cursor: pointer; font-size: 0.75rem; letter-spacing: 0.5px;">
1442
+ 🔄 Clear Cache & Reload
1443
+ </button>
1332
1444
  </div>
1333
1445
 
1334
1446
  <!-- Top Toolbar -->
1335
1447
  <div id="toolbar">
1336
- <!-- Sketch Tools -->
1448
+ <!-- Pointer / Select -->
1337
1449
  <div class="toolbar-group">
1338
- <button class="toolbar-button" id="tool-sketch" title="Start Sketch (S)">
1339
- <span class="toolbar-icon">📐</span>
1340
- <span class="toolbar-label">Sketch</span>
1341
- </button>
1342
- <button class="toolbar-button" id="tool-line" title="Draw Line (L)">
1343
- <span class="toolbar-icon">/</span>
1344
- <span class="toolbar-label">Line</span>
1345
- </button>
1346
- <button class="toolbar-button" id="tool-rect" title="Draw Rectangle (R)">
1347
- <span class="toolbar-icon">▭</span>
1348
- <span class="toolbar-label">Rect</span>
1349
- </button>
1350
- <button class="toolbar-button" id="tool-circle" title="Draw Circle (C)">
1351
- <span class="toolbar-icon">⭕</span>
1352
- <span class="toolbar-label">Circle</span>
1353
- </button>
1354
- <button class="toolbar-button" id="tool-arc" title="Draw Arc (A)">
1355
- <span class="toolbar-icon">⌢</span>
1356
- <span class="toolbar-label">Arc</span>
1357
- </button>
1358
- <button class="toolbar-button" id="tool-dimension" title="Add Dimension (D)">
1359
- <span class="toolbar-icon">↔</span>
1360
- <span class="toolbar-label">Dim</span>
1361
- </button>
1450
+ <button class="toolbar-button active" id="tool-pointer" title="Select / Pointer (Esc)" style="background:rgba(88,166,255,0.15);border:1px solid rgba(88,166,255,0.4);"><span class="toolbar-icon">↖</span><span class="toolbar-label">Select</span></button>
1362
1451
  </div>
1363
1452
 
1364
- <!-- Modeling Tools -->
1453
+ <!-- Sketch Tools (always visible) -->
1365
1454
  <div class="toolbar-group">
1366
- <button class="toolbar-button" id="tool-extrude" title="Extrude (E)">
1367
- <span class="toolbar-icon">⬆</span>
1368
- <span class="toolbar-label">Extrude</span>
1369
- </button>
1370
- <button class="toolbar-button" id="tool-revolve" title="Revolve">
1371
- <span class="toolbar-icon">🔄</span>
1372
- <span class="toolbar-label">Revolve</span>
1373
- </button>
1374
- <button class="toolbar-button" id="tool-fillet" title="Fillet">
1375
- <span class="toolbar-icon">⌒</span>
1376
- <span class="toolbar-label">Fillet</span>
1377
- </button>
1378
- <button class="toolbar-button" id="tool-chamfer" title="Chamfer">
1379
- <span class="toolbar-icon">⌉</span>
1380
- <span class="toolbar-label">Chamfer</span>
1381
- </button>
1382
- <button class="toolbar-button" id="tool-cut" title="Boolean Cut">
1383
- <span class="toolbar-icon">−</span>
1384
- <span class="toolbar-label">Cut</span>
1385
- </button>
1386
- <button class="toolbar-button" id="tool-union" title="Boolean Union">
1387
- <span class="toolbar-icon">+</span>
1388
- <span class="toolbar-label">Union</span>
1389
- </button>
1455
+ <button class="toolbar-button" id="tool-sketch" title="Start Sketch (S)"><span class="toolbar-icon">📐</span><span class="toolbar-label">Sketch</span></button>
1456
+ <button class="toolbar-button" id="tool-line" title="Line (L)"><span class="toolbar-icon">/</span></button>
1457
+ <button class="toolbar-button" id="tool-rect" title="Rect (R)"><span class="toolbar-icon">▭</span></button>
1458
+ <button class="toolbar-button" id="tool-circle" title="Circle (C)"><span class="toolbar-icon">⭕</span></button>
1459
+ <button class="toolbar-button" id="tool-arc" title="Arc (A)"><span class="toolbar-icon">⌢</span></button>
1460
+ <button class="toolbar-button" id="tool-dimension" title="Dimension (D)"><span class="toolbar-icon">↔</span></button>
1390
1461
  </div>
1391
1462
 
1392
- <!-- Advanced Operations -->
1463
+ <!-- Modeling (always visible) -->
1393
1464
  <div class="toolbar-group">
1394
- <button class="toolbar-button" id="tool-sweep" title="Sweep (Profile Along Path)">
1395
- <span class="toolbar-icon">&#8634;</span>
1396
- <span class="toolbar-label">Sweep</span>
1397
- </button>
1398
- <button class="toolbar-button" id="tool-loft" title="Loft (Between Profiles)">
1399
- <span class="toolbar-icon">&#9651;</span>
1400
- <span class="toolbar-label">Loft</span>
1401
- </button>
1402
- <button class="toolbar-button" id="tool-spring" title="Generate Spring" style="background:rgba(63,185,80,0.08);">
1403
- <span class="toolbar-icon">&#8635;</span>
1404
- <span class="toolbar-label">Spring</span>
1405
- </button>
1406
- <button class="toolbar-button" id="tool-thread" title="Generate Thread" style="background:rgba(63,185,80,0.08);">
1407
- <span class="toolbar-icon">&#10038;</span>
1408
- <span class="toolbar-label">Thread</span>
1409
- </button>
1465
+ <button class="toolbar-button" id="tool-extrude" title="Extrude (E)"><span class="toolbar-icon">⬆</span><span class="toolbar-label">Extrude</span></button>
1466
+ <button class="toolbar-button" id="tool-revolve" title="Revolve"><span class="toolbar-icon">🔄</span></button>
1467
+ <button class="toolbar-button" id="tool-fillet" title="Fillet"><span class="toolbar-icon">⌒</span></button>
1468
+ <button class="toolbar-button" id="tool-chamfer" title="Chamfer"><span class="toolbar-icon">⌉</span></button>
1469
+ <button class="toolbar-button" id="tool-cut" title="Cut"><span class="toolbar-icon">−</span></button>
1470
+ <button class="toolbar-button" id="tool-union" title="Union"><span class="toolbar-icon">+</span></button>
1410
1471
  </div>
1411
1472
 
1412
- <!-- Sheet Metal -->
1413
- <div class="toolbar-group">
1414
- <button class="toolbar-button" id="tool-bend" title="Sheet Metal Bend" style="background:rgba(210,153,34,0.08);">
1415
- <span class="toolbar-icon">&#9484;</span>
1416
- <span class="toolbar-label">Bend</span>
1417
- </button>
1418
- <button class="toolbar-button" id="tool-flange" title="Sheet Metal Flange" style="background:rgba(210,153,34,0.08);">
1419
- <span class="toolbar-icon">&#9492;</span>
1420
- <span class="toolbar-label">Flange</span>
1421
- </button>
1422
- <button class="toolbar-button" id="tool-unfold" title="Unfold Sheet Metal" style="background:rgba(210,153,34,0.08);">
1423
- <span class="toolbar-icon">&#9645;</span>
1424
- <span class="toolbar-label">Unfold</span>
1425
- </button>
1426
- </div>
1427
-
1428
- <!-- Export Tools -->
1429
- <div class="toolbar-group">
1430
- <button class="toolbar-button" id="export-stl" title="Export STL">
1431
- <span class="toolbar-icon">💾</span>
1432
- <span class="toolbar-label">STL</span>
1433
- </button>
1434
- <button class="toolbar-button" id="export-step" title="Export STEP">
1435
- <span class="toolbar-icon">💾</span>
1436
- <span class="toolbar-label">STEP</span>
1437
- </button>
1438
- <button class="toolbar-button" id="export-dxf" title="Export DXF (2D Drawing)">
1439
- <span class="toolbar-icon">📐</span>
1440
- <span class="toolbar-label">DXF</span>
1441
- </button>
1442
- <button class="toolbar-button" id="export-multiview" title="Multi-View Engineering Drawing (DXF)">
1443
- <span class="toolbar-icon">📄</span>
1444
- <span class="toolbar-label">Drawing</span>
1445
- </button>
1473
+ <!-- Advanced Ops dropdown -->
1474
+ <div class="toolbar-dropdown">
1475
+ <button class="toolbar-dropdown-btn" title="Advanced Operations">⚙ More <span class="caret">▼</span></button>
1476
+ <div class="toolbar-dropdown-menu">
1477
+ <button id="tool-sweep" title="Sweep">↺ Sweep</button>
1478
+ <button id="tool-loft" title="Loft">△ Loft</button>
1479
+ <button id="tool-spring" title="Spring">↻ Spring</button>
1480
+ <button id="tool-thread" title="Thread">✦ Thread</button>
1481
+ <button id="tool-bend" title="Sheet Metal Bend">┌ Bend</button>
1482
+ <button id="tool-flange" title="Flange">└ Flange</button>
1483
+ <button id="tool-unfold" title="Unfold">▭ Unfold</button>
1484
+ <button id="tool-shell" title="Shell (Hollow)">◻ Shell</button>
1485
+ <button id="tool-pattern" title="Pattern (Array)">▦ Pattern</button>
1486
+ <button id="btn-sketch-enhance" title="Sketch+">✏ Sketch+</button>
1487
+ <button id="btn-section" title="Section View">🔪 Section</button>
1488
+ <button id="tool-measure" title="Measure (M key)">📏 Measure</button>
1489
+ </div>
1446
1490
  </div>
1447
1491
 
1448
- <!-- Assembly Tools -->
1449
- <div class="toolbar-group">
1450
- <button class="toolbar-button" id="tool-assembly" title="Assembly Workspace" style="background:rgba(139,92,246,0.1);border:1px solid rgba(139,92,246,0.3);">
1451
- <span class="toolbar-icon">&#9881;</span>
1452
- <span class="toolbar-label">Assembly</span>
1453
- </button>
1454
- <button class="toolbar-button" id="tool-explode" title="Explode/Collapse Assembly">
1455
- <span class="toolbar-icon">&#11043;</span>
1456
- <span class="toolbar-label">Explode</span>
1457
- </button>
1492
+ <!-- Export dropdown -->
1493
+ <div class="toolbar-dropdown">
1494
+ <button class="toolbar-dropdown-btn" title="Export">💾 Export <span class="caret">▼</span></button>
1495
+ <div class="toolbar-dropdown-menu">
1496
+ <button id="export-stl">💾 STL</button>
1497
+ <button id="export-step">💾 STEP</button>
1498
+ <button id="export-dxf">📐 DXF</button>
1499
+ <button id="export-multiview">📄 Drawing</button>
1500
+ </div>
1458
1501
  </div>
1459
1502
 
1460
- <!-- Reverse Engineer -->
1503
+ <!-- Assembly -->
1461
1504
  <div class="toolbar-group">
1462
- <button class="toolbar-button" id="btn-reverse-engineer" title="Reverse Engineer a Part (Import STL → Detect Features → Step-by-Step Rebuild Guide)" style="background:rgba(88,166,255,0.12);border:1px solid rgba(88,166,255,0.3);">
1463
- <span class="toolbar-icon">🔍</span>
1464
- <span class="toolbar-label">Reverse Engineer</span>
1465
- </button>
1466
- <button class="toolbar-button" id="btn-inventor-import" title="Import Autodesk Inventor .ipt/.iam — Parse Native Format" style="background:rgba(255,140,0,0.12);border:1px solid rgba(255,140,0,0.3);">
1467
- <span class="toolbar-icon">🏭</span>
1468
- <span class="toolbar-label">Inventor</span>
1469
- </button>
1505
+ <button class="toolbar-button" id="tool-assembly" title="Assembly" style="background:rgba(139,92,246,0.1);border:1px solid rgba(139,92,246,0.3);"><span class="toolbar-icon">⚙</span><span class="toolbar-label">Assembly</span></button>
1506
+ <button class="toolbar-button" id="tool-explode" title="Explode"><span class="toolbar-icon">⊙</span></button>
1470
1507
  </div>
1471
1508
 
1472
- <!-- Token Balance Indicator -->
1473
- <div class="toolbar-group">
1474
- <button class="toolbar-button" id="btn-token-balance" title="View Token Balance & Billing" style="background: linear-gradient(135deg, rgba(88,166,255,0.1), rgba(63,185,80,0.1)); border: 1px solid rgba(88,166,255,0.3);" onclick="document.querySelector('[data-tab=tokens]')?.click()">
1475
- <span class="toolbar-icon">💰</span>
1476
- <span class="toolbar-label" id="token-balance-label">1K Tokens</span>
1477
- </button>
1509
+ <!-- Import dropdown -->
1510
+ <div class="toolbar-dropdown">
1511
+ <button class="toolbar-dropdown-btn" title="Import">📂 Import <span class="caret">▼</span></button>
1512
+ <div class="toolbar-dropdown-menu">
1513
+ <button id="btn-reverse-engineer">🔍 Reverse Engineer</button>
1514
+ <button id="btn-inventor-import">🏭 Inventor Import</button>
1515
+ </div>
1478
1516
  </div>
1479
1517
 
1480
- <!-- AI & Analysis -->
1481
- <div class="toolbar-group">
1482
- <button class="toolbar-button" id="btn-ai-copilot" title="AI Copilot — Text-to-CAD, Voice Commands" style="background:rgba(139,92,246,0.1);border:1px solid rgba(139,92,246,0.3);">
1483
- <span class="toolbar-icon">✨</span>
1484
- <span class="toolbar-label">Copilot</span>
1485
- </button>
1486
- <button class="toolbar-button" id="btn-dfm" title="DFM Analyzer — Manufacturability Check" style="background:rgba(248,113,113,0.1);border:1px solid rgba(248,113,113,0.3);">
1487
- <span class="toolbar-icon">🏭</span>
1488
- <span class="toolbar-label">DFM</span>
1489
- </button>
1490
- <button class="toolbar-button" id="btn-materials" title="Material Library — 40+ PBR Materials" style="background:rgba(34,211,238,0.1);border:1px solid rgba(34,211,238,0.3);">
1491
- <span class="toolbar-icon">🎨</span>
1492
- <span class="toolbar-label">Materials</span>
1493
- </button>
1494
- <button class="toolbar-button" id="btn-cam" title="CAM Pipeline — Slicer, Toolpath, G-code" style="background:rgba(251,191,36,0.1);border:1px solid rgba(251,191,36,0.3);">
1495
- <span class="toolbar-icon">⚙</span>
1496
- <span class="toolbar-label">CAM</span>
1497
- </button>
1498
- <button class="toolbar-button" id="btn-fabs" title="Connected Fabs — Send to Fabrication" style="background:rgba(74,222,128,0.1);border:1px solid rgba(74,222,128,0.3);">
1499
- <span class="toolbar-icon">🏗</span>
1500
- <span class="toolbar-label">Fabs</span>
1501
- </button>
1502
- <button class="toolbar-button" id="btn-collab" title="Collaboration — Multi-User Sessions" style="background:rgba(96,165,250,0.1);border:1px solid rgba(96,165,250,0.3);">
1503
- <span class="toolbar-icon">👥</span>
1504
- <span class="toolbar-label">Collab</span>
1505
- </button>
1506
- </div>
1507
- <!-- CadXStudio-Killer Features -->
1508
- <div class="toolbar-group">
1509
- <button class="toolbar-button" id="btn-text2cad" title="Text-to-CAD — Natural Language 3D Modeling" style="background:rgba(168,85,247,0.1);border:1px solid rgba(168,85,247,0.3);">
1510
- <span class="toolbar-icon">💬</span>
1511
- <span class="toolbar-label">Text2CAD</span>
1512
- </button>
1513
- <button class="toolbar-button" id="btn-camops" title="CAM Operations — Outline, Rough, Pocket, Drill" style="background:rgba(234,179,8,0.1);border:1px solid rgba(234,179,8,0.3);">
1514
- <span class="toolbar-icon">🔧</span>
1515
- <span class="toolbar-label">CAM Ops</span>
1516
- </button>
1517
- <button class="toolbar-button" id="btn-gcode" title="G-Code Generator — CNC, FDM, Laser" style="background:rgba(16,185,129,0.1);border:1px solid rgba(16,185,129,0.3);">
1518
- <span class="toolbar-icon">📄</span>
1519
- <span class="toolbar-label">G-Code</span>
1520
- </button>
1521
- <button class="toolbar-button" id="btn-machines" title="Machine Profiles — 15+ CNC/FDM/Laser Presets" style="background:rgba(107,114,128,0.1);border:1px solid rgba(107,114,128,0.3);">
1522
- <span class="toolbar-icon">🖥</span>
1523
- <span class="toolbar-label">Machines</span>
1524
- </button>
1525
- <button class="toolbar-button" id="btn-tools" title="Tool Library — 30+ End Mills, Drills, V-Bits" style="background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);">
1526
- <span class="toolbar-icon">🔩</span>
1527
- <span class="toolbar-label">Tools</span>
1528
- </button>
1529
- <button class="toolbar-button" id="btn-stock" title="Stock Manager — Workpiece Definition" style="background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.3);">
1530
- <span class="toolbar-icon">📦</span>
1531
- <span class="toolbar-label">Stock</span>
1532
- </button>
1533
- <button class="toolbar-button" id="btn-sketch-enhance" title="Sketch Tools+ — Spline, Polygon, Text, Trim, Offset" style="background:rgba(59,130,246,0.1);border:1px solid rgba(59,130,246,0.3);">
1534
- <span class="toolbar-icon">✏</span>
1535
- <span class="toolbar-label">Sketch+</span>
1536
- </button>
1537
- <button class="toolbar-button" id="btn-section" title="Section View — Cut Planes (X)" style="background:rgba(220,38,38,0.1);border:1px solid rgba(220,38,38,0.3);">
1538
- <span class="toolbar-icon">🔪</span>
1539
- <span class="toolbar-label">Section</span>
1540
- </button>
1518
+ <!-- AI & Tools dropdown -->
1519
+ <div class="toolbar-dropdown">
1520
+ <button class="toolbar-dropdown-btn" title="AI & Tools" style="color:rgba(168,85,247,0.9);">✨ AI <span class="caret">▼</span></button>
1521
+ <div class="toolbar-dropdown-menu">
1522
+ <button id="btn-ai-copilot">✨ Copilot</button>
1523
+ <button id="btn-text2cad">💬 Text2CAD</button>
1524
+ <button id="btn-dfm">🏭 DFM Check</button>
1525
+ <button id="btn-materials">🎨 Materials</button>
1526
+ <button id="btn-generative">🧬 Generative</button>
1527
+ <button id="btn-agent-panel">🤖 Agent API</button>
1528
+ </div>
1541
1529
  </div>
1542
1530
 
1543
- <!-- Marketplace V2 + GD&T + MISUMI + VR -->
1544
- <div class="toolbar-group">
1545
- <button class="toolbar-button" id="btn-marketplace-v2" title="Model Marketplace (GrabCAD-style)" style="background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);">
1546
- <span class="toolbar-icon">🏪</span>
1547
- <span class="toolbar-label">Market</span>
1548
- </button>
1549
- <button class="toolbar-button" id="btn-gdt" title="GD&T Training & Reference" style="background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);">
1550
- <span class="toolbar-icon">📐</span>
1551
- <span class="toolbar-label">GD&T</span>
1552
- </button>
1553
- <button class="toolbar-button" id="btn-misumi" title="MISUMI Component Catalog" style="background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);">
1554
- <span class="toolbar-icon">🔩</span>
1555
- <span class="toolbar-label">MISUMI</span>
1556
- </button>
1557
- <button class="toolbar-button" id="btn-vr" title="CAD2VR — View in Virtual Reality" style="background:rgba(168,85,247,0.1);border:1px solid rgba(168,85,247,0.3);">
1558
- <span class="toolbar-icon">🥽</span>
1559
- <span class="toolbar-label">VR</span>
1560
- </button>
1531
+ <!-- CAM dropdown -->
1532
+ <div class="toolbar-dropdown">
1533
+ <button class="toolbar-dropdown-btn" title="CAM & Manufacturing">⚙ CAM <span class="caret">▼</span></button>
1534
+ <div class="toolbar-dropdown-menu">
1535
+ <button id="btn-cam">⚙ CAM Pipeline</button>
1536
+ <button id="btn-camops">🔧 CAM Ops</button>
1537
+ <button id="btn-gcode">📄 G-Code</button>
1538
+ <button id="btn-machines">🖥 Machines</button>
1539
+ <button id="btn-tools">🔩 Tools</button>
1540
+ <button id="btn-stock">📦 Stock</button>
1541
+ <button id="btn-fabs">🏗 Fabs</button>
1542
+ </div>
1561
1543
  </div>
1562
1544
 
1563
- <!-- Agent API Panel -->
1564
- <div class="toolbar-group">
1565
- <button class="toolbar-button" id="btn-agent-panel" title="Agent Command Panel (Test API)">
1566
- <span class="toolbar-icon">🤖</span>
1567
- <span class="toolbar-label">Agent</span>
1568
- </button>
1545
+ <!-- Reference dropdown -->
1546
+ <div class="toolbar-dropdown">
1547
+ <button class="toolbar-dropdown-btn" title="Reference & Catalog">📚 Ref <span class="caret">▼</span></button>
1548
+ <div class="toolbar-dropdown-menu">
1549
+ <button id="btn-marketplace-v2">🏪 Marketplace</button>
1550
+ <button id="btn-gdt">📐 GD&T Training</button>
1551
+ <button id="btn-misumi">🔩 MISUMI Catalog</button>
1552
+ <button id="btn-vr">🥽 VR Mode</button>
1553
+ <button id="btn-collab">👥 Collaboration</button>
1554
+ </div>
1569
1555
  </div>
1570
1556
 
1571
- <!-- Help & Tutorials -->
1557
+ <!-- Undo/Redo -->
1572
1558
  <div class="toolbar-group">
1573
- <button class="toolbar-button" id="btn-help" title="Help & Tutorials (?)" style="background:rgba(88,166,255,0.1);border:1px solid rgba(88,166,255,0.3);">
1574
- <span class="toolbar-icon">?</span>
1575
- <span class="toolbar-label">Help</span>
1576
- </button>
1559
+ <button class="toolbar-button" id="btn-undo" title="Undo (Ctrl+Z)"><span class="toolbar-icon">↶</span></button>
1560
+ <button class="toolbar-button" id="btn-redo" title="Redo (Ctrl+Y)"><span class="toolbar-icon">↷</span></button>
1577
1561
  </div>
1578
1562
 
1579
- <!-- Hard Reset -->
1580
- <div class="toolbar-group">
1581
- <button class="toolbar-button" id="btn-hard-reset" title="Clear cache and reload" style="color:#f55;">
1582
- <span class="toolbar-icon">🔄</span>
1583
- <span class="toolbar-label">Reset</span>
1584
- </button>
1585
- </div>
1563
+ <!-- Token balance -->
1564
+ <button class="toolbar-button" id="btn-token-balance" title="Tokens" onclick="document.querySelector('[data-tab=tokens]')?.click()" style="background:linear-gradient(135deg,rgba(88,166,255,0.1),rgba(63,185,80,0.1));border:1px solid rgba(88,166,255,0.3);">
1565
+ <span class="toolbar-icon">💰</span><span class="toolbar-label" id="token-balance-label">1K</span>
1566
+ </button>
1586
1567
 
1587
- <!-- Edit Operations -->
1588
- <div class="toolbar-group">
1589
- <button class="toolbar-button" id="btn-undo" title="Undo (Ctrl+Z)">
1590
- <span class="toolbar-icon">↶</span>
1591
- </button>
1592
- <button class="toolbar-button" id="btn-redo" title="Redo (Ctrl+Y)">
1593
- <span class="toolbar-icon">↷</span>
1594
- </button>
1568
+ <!-- Right-side: Theme + Help + Reset -->
1569
+ <div style="margin-left:auto;display:flex;gap:2px;">
1570
+ <button class="toolbar-button" id="btn-theme-toggle" title="Toggle Light/Dark Mode" style="background:rgba(255,200,50,0.12);border:1px solid rgba(255,200,50,0.3);"><span class="toolbar-icon" id="theme-icon">☀</span></button>
1571
+ <button class="toolbar-button" id="btn-help" title="Help (?)" style="background:rgba(88,166,255,0.1);"><span class="toolbar-icon">?</span></button>
1572
+ <button class="toolbar-button" id="btn-hard-reset" title="Clear cache & reload" style="color:#f55;"><span class="toolbar-icon">🔄</span></button>
1595
1573
  </div>
1574
+ </div><!-- end toolbar -->
1596
1575
 
1597
- <!-- View Buttons -->
1598
- <div class="toolbar-group">
1599
- <button class="toolbar-button" id="view-front" title="Front View (V+F)">
1576
+ <!-- Hidden: View buttons used by keyboard shortcuts only -->
1577
+ <div style="display:none;">
1578
+ <button id="view-front" title="Front View (V+F)">
1600
1579
  <span class="toolbar-label">Front</span>
1601
1580
  </button>
1602
1581
  <button class="toolbar-button" id="view-top" title="Top View (V+T)">
@@ -1611,9 +1590,7 @@
1611
1590
  <button class="toolbar-button" id="view-fit" title="Fit All (V)">
1612
1591
  <span class="toolbar-label">Fit</span>
1613
1592
  </button>
1614
- </div>
1615
-
1616
- </div>
1593
+ </div><!-- end hidden view buttons -->
1617
1594
 
1618
1595
  <!-- Main Content Area -->
1619
1596
  <div id="content">
@@ -1632,10 +1609,16 @@
1632
1609
  <div id="inline-project-browser">
1633
1610
  <!-- Inline project browser tree (populated when DUO manifest loads) -->
1634
1611
  <div class="ipb-search-box">
1635
- <input type="text" id="ipb-search" placeholder="Search 473 parts..." style="width:100%;padding:6px 8px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:3px;color:var(--text-primary);font-size:11px;">
1612
+ <input type="text" id="ipb-search" placeholder="Search parts..." style="width:100%;padding:6px 8px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:3px;color:var(--text-primary);font-size:11px;">
1636
1613
  </div>
1637
1614
  <div id="ipb-stats" style="display:flex;gap:8px;padding:4px 8px;font-size:10px;color:var(--text-secondary);border-bottom:1px solid var(--border-color);"></div>
1638
- <div id="ipb-tree" style="flex:1;overflow-y:auto;padding:4px 0;min-height:0;"></div>
1615
+ <div id="ipb-tree" style="flex:1;overflow-y:auto;padding:4px 0;min-height:0;">
1616
+ <div id="ipb-empty" style="text-align:center;padding:20px 12px;">
1617
+ <div style="font-size:32px;margin-bottom:8px;opacity:0.5;">&#x1F4C2;</div>
1618
+ <p style="font-size:12px;font-weight:600;margin-bottom:6px;color:var(--text-primary);">No project loaded</p>
1619
+ <p style="font-size:11px;color:var(--text-secondary);line-height:1.5;">Use <b>Import &gt; Open Inventor Project</b> to load a project folder, or import STEP/STL files.</p>
1620
+ </div>
1621
+ </div>
1639
1622
  </div>
1640
1623
  </div>
1641
1624
  </div>
@@ -1654,6 +1637,18 @@
1654
1637
  <div class="snap-indicator" id="snap-indicator">Snap: Grid</div>
1655
1638
  </div>
1656
1639
 
1640
+ <!-- ViewCube Navigation -->
1641
+ <div id="viewcube" style="position:absolute;top:12px;right:12px;width:80px;height:80px;z-index:50;perspective:200px;cursor:pointer;">
1642
+ <div id="vc-cube" style="width:80px;height:80px;position:relative;transform-style:preserve-3d;transform:rotateX(-25deg) rotateY(-35deg);transition:transform 0.3s ease;">
1643
+ <div class="vc-face vc-front" data-vc="front" style="transform:translateZ(40px);">Front</div>
1644
+ <div class="vc-face vc-back" data-vc="back" style="transform:rotateY(180deg) translateZ(40px);">Back</div>
1645
+ <div class="vc-face vc-right" data-vc="right" style="transform:rotateY(90deg) translateZ(40px);">Right</div>
1646
+ <div class="vc-face vc-left" data-vc="left" style="transform:rotateY(-90deg) translateZ(40px);">Left</div>
1647
+ <div class="vc-face vc-top" data-vc="top" style="transform:rotateX(90deg) translateZ(40px);">Top</div>
1648
+ <div class="vc-face vc-bottom" data-vc="bottom" style="transform:rotateX(-90deg) translateZ(40px);">Btm</div>
1649
+ </div>
1650
+ </div>
1651
+
1657
1652
  <!-- Sketch Canvas Overlay -->
1658
1653
  <canvas id="sketch-canvas-overlay"></canvas>
1659
1654
 
@@ -1764,25 +1759,26 @@
1764
1759
  <!-- Module Loader -->
1765
1760
  <script type="module">
1766
1761
  import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
1767
- import { initViewport, setView, addToScene, removeFromScene, getScene, getCamera, getControls, toggleGrid as vpToggleGrid, fitToObject } from './js/viewport.js';
1768
- import { startSketch, endSketch, setTool, getEntities, clearSketch } from './js/sketch.js';
1769
- import { extrudeProfile, createPrimitive, rebuildFeature, createMaterial } from './js/operations.js';
1770
- import { initChat, parseCADPrompt, addMessage } from './js/ai-chat.js';
1771
- import { initTree, addFeature, selectFeature, onSelect } from './js/tree.js';
1772
- import { initParams, showParams, onParamChange } from './js/params.js';
1773
- import { exportSTL, exportOBJ, exportJSON } from './js/export.js';
1774
- import { initShortcuts } from './js/shortcuts.js';
1775
- import { createReverseEngineerPanel, importFile, analyzeGeometry, reconstructFeatureTree, createWalkthrough } from './js/reverse-engineer.js';
1776
- import { createInventorPanel, parseInventorFile } from './js/inventor-parser.js';
1777
- import { loadProject, showFolderPicker, parseIPJ } from './js/project-loader.js';
1778
- import { initProjectBrowser, showBrowser, hideBrowser, setProject, onFileSelect } from './js/project-browser.js';
1779
- import { generateGuide, renderGuide, exportGuideHTML } from './js/rebuild-guide.js';
1780
- import { solveConstraints, addConstraint, removeConstraint, autoDetectConstraints, isFullyConstrained, getAllConstraints, clearAllConstraints } from './js/constraint-solver.js';
1781
- import { createSweep, createLoft, createBend, createFlange, createTab, createSlot, unfoldSheetMetal, createSpring, createThread } from './js/advanced-ops.js';
1782
- import Assembly from './js/assembly.js';
1783
- import { exportSketchToDXF, exportProjectionToDXF, exportMultiViewDXF, export3DDXF, downloadDXF } from './js/dxf-export.js';
1784
- import { initAgentAPI } from './js/agent-api.js';
1785
- import { initTokenDashboard } from './js/token-dashboard.js';
1762
+ const _v = '50';
1763
+ import { initViewport, setView, addToScene, removeFromScene, getScene, getCamera, getControls, toggleGrid as vpToggleGrid, fitToObject, fitAll } from './js/viewport.js?v=62';
1764
+ import { startSketch, endSketch, setTool, getEntities, clearSketch } from './js/sketch.js?v=62';
1765
+ import { extrudeProfile, createPrimitive, rebuildFeature, createMaterial } from './js/operations.js?v=62';
1766
+ import { initChat, parseCADPrompt, addMessage } from './js/ai-chat.js?v=62';
1767
+ import { initTree, addFeature, selectFeature, onSelect } from './js/tree.js?v=62';
1768
+ import { initParams, showParams, onParamChange } from './js/params.js?v=62';
1769
+ import { exportSTL, exportOBJ, exportJSON } from './js/export.js?v=62';
1770
+ import { initShortcuts } from './js/shortcuts.js?v=62';
1771
+ import { createReverseEngineerPanel, importFile, analyzeGeometry, reconstructFeatureTree, createWalkthrough } from './js/reverse-engineer.js?v=62';
1772
+ import { createInventorPanel, parseInventorFile } from './js/inventor-parser.js?v=62';
1773
+ import { loadProject, showFolderPicker, parseIPJ } from './js/project-loader.js?v=62';
1774
+ import { initProjectBrowser, showBrowser, hideBrowser, setProject, onFileSelect } from './js/project-browser.js?v=62';
1775
+ import { generateGuide, renderGuide, exportGuideHTML } from './js/rebuild-guide.js?v=62';
1776
+ import { solveConstraints, addConstraint, removeConstraint, autoDetectConstraints, isFullyConstrained, getAllConstraints, clearAllConstraints } from './js/constraint-solver.js?v=62';
1777
+ import { createSweep, createLoft, createBend, createFlange, createTab, createSlot, unfoldSheetMetal, createSpring, createThread } from './js/advanced-ops.js?v=62';
1778
+ import Assembly from './js/assembly.js?v=62';
1779
+ import { exportSketchToDXF, exportProjectionToDXF, exportMultiViewDXF, export3DDXF, downloadDXF } from './js/dxf-export.js?v=62';
1780
+ import { initAgentAPI } from './js/agent-api.js?v=62';
1781
+ import { initTokenDashboard } from './js/token-dashboard.js?v=62';
1786
1782
 
1787
1783
  // ========== Application State ==========
1788
1784
  const APP = {
@@ -1798,101 +1794,108 @@
1798
1794
 
1799
1795
  // ========== Initialization ==========
1800
1796
  async function init() {
1797
+ const errors = [];
1798
+ function tryStep(name, fn) {
1799
+ try { fn(); } catch(e) { console.warn('[init] ' + name + ' failed:', e.message); errors.push(name + ': ' + e.message); }
1800
+ }
1801
+ async function tryStepAsync(name, fn) {
1802
+ try { await fn(); } catch(e) { console.warn('[init] ' + name + ' failed:', e.message); errors.push(name + ': ' + e.message); }
1803
+ }
1801
1804
  try {
1802
1805
  console.log('Initializing cycleCAD...');
1803
1806
 
1804
1807
  // 1. Initialize 3D viewport
1805
- initViewport('viewport-container');
1806
- document.getElementById('kernel-status').classList.add('ready');
1807
- document.getElementById('kernel-status-text').textContent = 'Ready';
1808
+ tryStep('viewport', () => {
1809
+ initViewport('viewport-container');
1810
+ document.getElementById('kernel-status').classList.add('ready');
1811
+ document.getElementById('kernel-status-text').textContent = 'Ready';
1812
+ });
1808
1813
 
1809
1814
  // 1b. Initialize assembly workspace
1810
- APP.assembly = new Assembly(getScene());
1815
+ tryStep('assembly', () => { APP.assembly = new Assembly(getScene()); });
1811
1816
 
1812
1817
  // 2. Initialize feature tree
1813
- const treeContainer = document.getElementById('feature-tree');
1814
- if (treeContainer) initTree(treeContainer);
1818
+ tryStep('tree', () => {
1819
+ const treeContainer = document.getElementById('feature-tree');
1820
+ if (treeContainer) initTree(treeContainer);
1821
+ });
1815
1822
 
1816
1823
  // 3. Initialize properties panel
1817
- const propsContainer = document.getElementById('tab-properties');
1818
- if (propsContainer) initParams(propsContainer);
1824
+ tryStep('params', () => {
1825
+ const propsContainer = document.getElementById('tab-properties');
1826
+ if (propsContainer) initParams(propsContainer);
1827
+ });
1819
1828
 
1820
1829
  // 4. Initialize AI chat
1821
- const chatTab = document.getElementById('tab-chat');
1822
- if (chatTab) {
1823
- // Create chat UI structure
1824
- chatTab.innerHTML = `
1825
- <div id="chat-messages" style="flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:6px;min-height:0;"></div>
1826
- <div style="display:flex;gap:4px;padding:8px;border-top:1px solid var(--border-color);">
1827
- <input id="chat-input" type="text" placeholder="Describe a part..." style="flex:1;padding:6px 10px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:4px;color:var(--text-primary);font-size:12px;">
1828
- <button id="chat-send" style="padding:6px 12px;background:var(--accent-blue);color:#fff;border-radius:4px;font-size:12px;">Send</button>
1829
- </div>
1830
- `;
1831
- chatTab.style.display = 'none';
1832
- chatTab.style.flexDirection = 'column';
1833
- chatTab.style.height = '100%';
1834
-
1835
- initChat(
1836
- document.getElementById('chat-messages'),
1837
- document.getElementById('chat-input'),
1838
- document.getElementById('chat-send'),
1839
- (cmd) => executeParsedPrompt(cmd)
1840
- );
1841
- }
1830
+ tryStep('chat', () => {
1831
+ const chatTab = document.getElementById('tab-chat');
1832
+ if (chatTab) {
1833
+ chatTab.innerHTML = `
1834
+ <div id="chat-messages" style="flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:6px;min-height:0;"></div>
1835
+ <div style="display:flex;gap:4px;padding:8px;border-top:1px solid var(--border-color);">
1836
+ <input id="chat-input" type="text" placeholder="Describe a part..." style="flex:1;padding:6px 10px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:4px;color:var(--text-primary);font-size:12px;">
1837
+ <button id="chat-send" style="padding:6px 12px;background:var(--accent-blue);color:#fff;border-radius:4px;font-size:12px;">Send</button>
1838
+ </div>
1839
+ `;
1840
+ chatTab.style.display = 'none';
1841
+ chatTab.style.flexDirection = 'column';
1842
+ chatTab.style.height = '100%';
1843
+ initChat(
1844
+ document.getElementById('chat-messages'),
1845
+ document.getElementById('chat-input'),
1846
+ document.getElementById('chat-send'),
1847
+ (cmd) => executeParsedPrompt(cmd)
1848
+ );
1849
+ }
1850
+ });
1842
1851
 
1843
1852
  // 4b. Initialize Token Dashboard
1844
- const tokenTab = document.getElementById('tab-tokens');
1845
- if (tokenTab) {
1846
- const tokenDashboard = initTokenDashboard();
1847
- tokenTab.innerHTML = tokenDashboard.html;
1848
- tokenTab.style.display = 'none';
1849
- tokenTab.style.overflowY = 'auto';
1850
- // Initialize the dashboard events
1851
- tokenDashboard.init();
1852
-
1853
- // Update toolbar balance indicator
1854
- function updateTokenBalanceLabel() {
1855
- const balance = window.cycleCAD?.tokens?.getBalance?.() || 0;
1856
- const label = document.getElementById('token-balance-label');
1857
- if (label) {
1858
- if (balance >= 1000) {
1859
- label.textContent = Math.floor(balance / 1000) + 'K Tokens';
1860
- } else {
1861
- label.textContent = balance + ' Tokens';
1853
+ tryStep('tokenDashboard', () => {
1854
+ const tokenTab = document.getElementById('tab-tokens');
1855
+ if (tokenTab) {
1856
+ const tokenDashboard = initTokenDashboard();
1857
+ tokenTab.innerHTML = tokenDashboard.html;
1858
+ tokenTab.style.display = 'none';
1859
+ tokenTab.style.overflowY = 'auto';
1860
+ tokenDashboard.init();
1861
+ function updateTokenBalanceLabel() {
1862
+ const balance = window.cycleCAD?.tokens?.getBalance?.() || 0;
1863
+ const label = document.getElementById('token-balance-label');
1864
+ if (label) {
1865
+ label.textContent = balance >= 1000 ? Math.floor(balance / 1000) + 'K Tokens' : balance + ' Tokens';
1862
1866
  }
1863
1867
  }
1868
+ updateTokenBalanceLabel();
1869
+ if (window.cycleCAD?.tokens?.on) {
1870
+ window.cycleCAD.tokens.on('token-spent', updateTokenBalanceLabel);
1871
+ window.cycleCAD.tokens.on('token-added', updateTokenBalanceLabel);
1872
+ window.cycleCAD.tokens.on('month-reset', updateTokenBalanceLabel);
1873
+ }
1864
1874
  }
1865
- updateTokenBalanceLabel();
1866
-
1867
- // Update label when tokens change
1868
- if (window.cycleCAD?.tokens?.on) {
1869
- window.cycleCAD.tokens.on('token-spent', updateTokenBalanceLabel);
1870
- window.cycleCAD.tokens.on('token-added', updateTokenBalanceLabel);
1871
- window.cycleCAD.tokens.on('month-reset', updateTokenBalanceLabel);
1872
- }
1873
- }
1875
+ });
1874
1876
 
1875
1877
  // 5. Wire up toolbar buttons
1876
- setupToolbar();
1878
+ tryStep('toolbar', () => setupToolbar());
1877
1879
 
1878
1880
  // 6. Wire up tab switching
1879
- setupTabs();
1881
+ tryStep('tabs', () => setupTabs());
1880
1882
 
1881
1883
  // 7. Wire up tree selection → params
1882
- onSelect((featureId) => {
1883
- APP.selectedFeature = APP.features.find(f => f.id === featureId);
1884
- if (APP.selectedFeature) showParams(APP.selectedFeature);
1885
- });
1886
-
1887
- onParamChange((paramName, value) => {
1888
- if (APP.selectedFeature) {
1889
- APP.selectedFeature.params[paramName] = value;
1890
- rebuildFeature(APP.selectedFeature);
1891
- }
1884
+ tryStep('treeSelection', () => {
1885
+ onSelect((featureId) => {
1886
+ APP.selectedFeature = APP.features.find(f => f.id === featureId);
1887
+ if (APP.selectedFeature) showParams(APP.selectedFeature);
1888
+ });
1889
+ onParamChange((paramName, value) => {
1890
+ if (APP.selectedFeature) {
1891
+ APP.selectedFeature.params[paramName] = value;
1892
+ rebuildFeature(APP.selectedFeature);
1893
+ }
1894
+ });
1892
1895
  });
1893
1896
 
1894
1897
  // 8. Initialize keyboard shortcuts
1895
- initShortcuts({
1898
+ tryStep('shortcuts', () => initShortcuts({
1896
1899
  newSketch: () => startNewSketch(),
1897
1900
  line: () => setTool('line'),
1898
1901
  rect: () => setTool('rect'),
@@ -1912,20 +1915,15 @@
1912
1915
  viewBottom: () => setView('bottom'),
1913
1916
  viewIso: () => setView('iso'),
1914
1917
  toggleGrid: () => vpToggleGrid(),
1915
- fitAll: () => fitAll(),
1918
+ fitAll: () => fitAllFeatures(),
1916
1919
  save: () => saveProject(),
1917
1920
  exportSTL: () => doExportSTL(),
1918
- });
1921
+ }));
1919
1922
 
1920
- // 9. Setup welcome splash
1921
- setupWelcome();
1922
-
1923
- // 9b. Setup left panel tabs and inline browser
1924
- setupLeftTabs();
1925
- setupInlineBrowserClicks();
1923
+ // 9. Welcome splash + tabs now run AFTER try/catch (always execute)
1926
1924
 
1927
1925
  // 10. Initialize project browser
1928
- initProjectBrowser(document.body, {
1926
+ tryStep('projectBrowser', () => initProjectBrowser(document.body, {
1929
1927
  onFileOpen: async (file) => {
1930
1928
  try {
1931
1929
  const buffer = file.buffer || await file.arrayBuffer();
@@ -1957,31 +1955,31 @@
1957
1955
  APP.project = project;
1958
1956
  updateStatus(`Project loaded: ${project.stats.parts} parts, ${project.stats.assemblies} assemblies`);
1959
1957
  }
1960
- });
1961
-
1962
- // 11. Hard Refresh button — nukes all caches, service workers, and reloads
1963
- const hardRefreshBtn = document.getElementById('btn-hard-refresh');
1964
- if (hardRefreshBtn) hardRefreshBtn.addEventListener('click', async () => {
1965
- hardRefreshBtn.textContent = 'Clearing...';
1966
- hardRefreshBtn.style.opacity = '0.6';
1967
- // Kill service workers
1968
- if ('serviceWorker' in navigator) {
1969
- const regs = await navigator.serviceWorker.getRegistrations();
1970
- for (const r of regs) await r.unregister();
1971
- }
1972
- // Kill Cache API caches
1973
- if ('caches' in window) {
1974
- const names = await caches.keys();
1975
- for (const n of names) await caches.delete(n);
1976
- }
1977
- // Reload with timestamp cache bust
1978
- const url = new URL(window.location.href);
1979
- url.searchParams.set('v', Date.now());
1980
- window.location.replace(url.toString());
1958
+ }));
1959
+
1960
+ // 11. Hard Refresh button
1961
+ tryStep('hardRefresh', () => {
1962
+ const hardRefreshBtn = document.getElementById('btn-hard-refresh');
1963
+ if (hardRefreshBtn) hardRefreshBtn.addEventListener('click', async () => {
1964
+ hardRefreshBtn.textContent = 'Clearing...';
1965
+ hardRefreshBtn.style.opacity = '0.6';
1966
+ if ('serviceWorker' in navigator) {
1967
+ const regs = await navigator.serviceWorker.getRegistrations();
1968
+ for (const r of regs) await r.unregister();
1969
+ }
1970
+ if ('caches' in window) {
1971
+ const names = await caches.keys();
1972
+ for (const n of names) await caches.delete(n);
1973
+ }
1974
+ const url = new URL(window.location.href);
1975
+ url.searchParams.set('v', Date.now());
1976
+ window.location.replace(url.toString());
1977
+ });
1981
1978
  });
1982
1979
 
1983
1980
  // Initialize Agent API — the primary interface
1984
- const agentImports = await import('./js/agent-api.js');
1981
+ await tryStepAsync('agentAPI', async () => {
1982
+ const agentImports = await import('./js/agent-api.js?v=62');
1985
1983
  const agentSession = initAgentAPI({
1986
1984
  viewport: {
1987
1985
  getCamera,
@@ -2047,41 +2045,57 @@
2047
2045
  console.log(`[Agent API] Ready. Session: ${agentSession.sessionId}`);
2048
2046
  console.log('[Agent API] Usage: window.cycleCAD.execute({ method: "meta.ping" })');
2049
2047
  console.log('[Agent API] Schema: window.cycleCAD.getSchema()');
2048
+ }); // end agentAPI tryStepAsync
2050
2049
 
2051
2050
  // Initialize new architecture modules
2052
- if (window.cycleCAD?.materials?.init) {
2053
- window.cycleCAD.materials.init();
2054
- console.log('[Materials] Library initialized — ' + (window.cycleCAD.materials.getAll?.()?.length || '40+') + ' materials');
2055
- }
2056
- if (window.cycleCAD?.dfm?.init) {
2057
- window.cycleCAD.dfm.init();
2058
- console.log('[DFM] Analyzer initialized — 8 manufacturing processes');
2059
- }
2060
- if (window.cycleCAD?.cam?.init) {
2061
- window.cycleCAD.cam.init();
2062
- console.log('[CAM] Pipeline initialized — slicer, toolpath, G-code');
2063
- }
2064
- if (window.cycleCAD?.fabs?.init) {
2065
- window.cycleCAD.fabs.init();
2066
- console.log('[Fabs] Connected fabrication network initialized');
2067
- }
2068
- if (window.cycleCAD?.copilot?.init) {
2069
- window.cycleCAD.copilot.init();
2070
- console.log('[AI Copilot] Initialized — text-to-CAD, voice commands');
2071
- }
2072
- if (window.cycleCAD?.collab?.init) {
2073
- window.cycleCAD.collab.init();
2074
- console.log('[Collaboration] Session management initialized');
2075
- }
2051
+ tryStep('archModules', () => {
2052
+ if (window.cycleCAD?.materials?.init) window.cycleCAD.materials.init();
2053
+ if (window.cycleCAD?.dfm?.init) window.cycleCAD.dfm.init();
2054
+ if (window.cycleCAD?.cam?.init) window.cycleCAD.cam.init();
2055
+ if (window.cycleCAD?.fabs?.init) window.cycleCAD.fabs.init();
2056
+ if (window.cycleCAD?.copilot?.init) window.cycleCAD.copilot.init();
2057
+ if (window.cycleCAD?.collab?.init) window.cycleCAD.collab.init();
2058
+ });
2076
2059
 
2077
- console.log('cycleCAD initialized successfully — all modules loaded');
2078
- updateStatus('Ready');
2060
+ // Report results
2061
+ if (errors.length > 0) {
2062
+ console.warn('[init] Completed with errors:', errors);
2063
+ updateStatus('Ready (some modules failed — see console)');
2064
+ } else {
2065
+ console.log('cycleCAD initialized successfully — all modules loaded');
2066
+ updateStatus('Ready');
2067
+ }
2079
2068
 
2080
2069
  } catch (error) {
2081
2070
  console.error('Failed to initialize cycleCAD:', error);
2082
2071
  document.getElementById('kernel-status').classList.add('error');
2083
2072
  document.getElementById('kernel-status-text').textContent = 'Error';
2084
2073
  }
2074
+
2075
+ // ALWAYS run splash + toolbar setup even if init partially fails
2076
+ try { setupWelcome(); } catch(e) { console.warn('setupWelcome error:', e); }
2077
+ try { setupLeftTabs(); } catch(e) { console.warn('setupLeftTabs error:', e); }
2078
+ try { setupInlineBrowserClicks(); } catch(e) { console.warn('setupInlineBrowserClicks error:', e); }
2079
+
2080
+ // Auto-load DUO manifest into Project Browser (non-blocking)
2081
+ try {
2082
+ fetch('duo-manifest.json').then(resp => {
2083
+ if (!resp.ok) return;
2084
+ return resp.json();
2085
+ }).then(manifest => {
2086
+ if (!manifest) return;
2087
+ function transformTree(node) {
2088
+ if (node.type === 'file' && node.ext) node.type = node.ext.replace('.', '');
2089
+ if (node.children) node.children.forEach(transformTree);
2090
+ return node;
2091
+ }
2092
+ transformTree(manifest.tree);
2093
+ APP.project = manifest;
2094
+ try { setProject(manifest.tree); } catch(e) {}
2095
+ try { populateInlineBrowser(manifest.tree); } catch(e) {}
2096
+ console.log('[init] DUO manifest auto-loaded:', manifest.stats?.total || 0, 'files');
2097
+ }).catch(() => { /* manifest not available, ok */ });
2098
+ } catch(e) {}
2085
2099
  }
2086
2100
 
2087
2101
  // ========== Left Panel Tab Switching ==========
@@ -2351,7 +2365,7 @@
2351
2365
  bind('view-top', () => setView('top'));
2352
2366
  bind('view-right', () => setView('right'));
2353
2367
  bind('view-iso', () => setView('iso'));
2354
- bind('view-fit', () => fitAll());
2368
+ bind('view-fit', () => fitAllFeatures());
2355
2369
 
2356
2370
  // Reverse Engineer
2357
2371
  bind('btn-reverse-engineer', () => {
@@ -2403,6 +2417,8 @@
2403
2417
  });
2404
2418
 
2405
2419
  // Advanced Operations
2420
+ bind('tool-shell', () => openDialog('shell'));
2421
+ bind('tool-pattern', () => openDialog('pattern'));
2406
2422
  bind('tool-sweep', () => openDialog('sweep'));
2407
2423
  bind('tool-loft', () => openDialog('loft'));
2408
2424
  bind('tool-spring', () => {
@@ -2475,6 +2491,57 @@
2475
2491
  updateStatus('Assembly collapsed');
2476
2492
  }
2477
2493
  });
2494
+
2495
+ // ===== Pointer / Select tool =====
2496
+ bind('tool-pointer', () => {
2497
+ // Exit sketch mode if active
2498
+ if (APP.mode === 'sketch') {
2499
+ try { endSketch(); } catch(e) {}
2500
+ APP.currentSketch = null;
2501
+ APP.mode = 'idle';
2502
+ document.getElementById('mode-indicator').textContent = 'Ready';
2503
+ document.getElementById('mode-value').textContent = 'Normal';
2504
+ }
2505
+ // Deactivate all tool buttons
2506
+ document.querySelectorAll('.toolbar-button.active').forEach(b => b.classList.remove('active'));
2507
+ document.getElementById('tool-pointer').classList.add('active');
2508
+ updateStatus('Select mode');
2509
+ });
2510
+
2511
+ // ===== Dropdown menu positioning (fixed position) =====
2512
+ document.querySelectorAll('.toolbar-dropdown').forEach(dd => {
2513
+ const btn = dd.querySelector('.toolbar-dropdown-btn');
2514
+ const menu = dd.querySelector('.toolbar-dropdown-menu');
2515
+ if (!btn || !menu) return;
2516
+
2517
+ btn.addEventListener('click', (e) => {
2518
+ e.stopPropagation();
2519
+ const wasOpen = dd.classList.contains('open');
2520
+ // Close all other dropdowns
2521
+ document.querySelectorAll('.toolbar-dropdown.open').forEach(d => d.classList.remove('open'));
2522
+ if (!wasOpen) {
2523
+ dd.classList.add('open');
2524
+ // Position menu below button using fixed coordinates
2525
+ const rect = btn.getBoundingClientRect();
2526
+ menu.style.top = (rect.bottom + 2) + 'px';
2527
+ menu.style.left = rect.left + 'px';
2528
+ }
2529
+ });
2530
+
2531
+ // Close dropdown when clicking a menu item
2532
+ menu.querySelectorAll('button').forEach(item => {
2533
+ item.addEventListener('click', () => {
2534
+ dd.classList.remove('open');
2535
+ });
2536
+ });
2537
+ });
2538
+
2539
+ // Close dropdowns when clicking outside
2540
+ document.addEventListener('click', (e) => {
2541
+ if (!e.target.closest('.toolbar-dropdown')) {
2542
+ document.querySelectorAll('.toolbar-dropdown.open').forEach(d => d.classList.remove('open'));
2543
+ }
2544
+ });
2478
2545
  }
2479
2546
 
2480
2547
  // ========== Tab Switching ==========
@@ -2637,6 +2704,49 @@
2637
2704
  document.getElementById('mode-value').textContent = 'Sketch';
2638
2705
  }
2639
2706
 
2707
+ // Simple CSG subtract using geometry clipping approach
2708
+ function csgSubtract(baseMesh, cutMesh) {
2709
+ // Use Three.js CSG via manual approach:
2710
+ // 1. Get both geometries in world space
2711
+ // 2. Use Evaluator from three-bvh-csg if available
2712
+ // 3. Fallback: visual-only cut (hide intersecting faces)
2713
+
2714
+ // Try the simple approach: subtract by creating a new geometry
2715
+ // that removes the intersection volume
2716
+ try {
2717
+ const baseGeo = baseMesh.geometry.clone();
2718
+ const cutGeo = cutMesh.geometry.clone();
2719
+
2720
+ // Apply world transforms
2721
+ baseGeo.applyMatrix4(baseMesh.matrixWorld);
2722
+ cutGeo.applyMatrix4(cutMesh.matrixWorld);
2723
+
2724
+ // Use clipping planes to simulate the cut visually
2725
+ // Create 6 clipping planes from the cut geometry bounding box
2726
+ const cutBox = new THREE.Box3().setFromBufferAttribute(cutGeo.getAttribute('position'));
2727
+
2728
+ // For a proper visual cut, we'll use stencil buffer technique:
2729
+ // Make the cut area appear as a hole by rendering the cut mesh
2730
+ // with colorWrite=false to write to the depth buffer only
2731
+ const depthMat = new THREE.MeshBasicMaterial({
2732
+ colorWrite: false,
2733
+ depthWrite: true,
2734
+ side: THREE.DoubleSide
2735
+ });
2736
+ cutMesh.material = depthMat;
2737
+ cutMesh.renderOrder = -1;
2738
+
2739
+ // Make the base mesh render after, so the depth test creates a "hole"
2740
+ baseMesh.material.side = THREE.FrontSide;
2741
+ baseMesh.renderOrder = 0;
2742
+
2743
+ return { base: baseMesh, cut: cutMesh, method: 'depth' };
2744
+ } catch(e) {
2745
+ console.warn('CSG subtract failed:', e);
2746
+ return null;
2747
+ }
2748
+ }
2749
+
2640
2750
  function doExtrude() {
2641
2751
  if (APP.mode !== 'sketch') {
2642
2752
  updateStatus('Start a sketch first');
@@ -2648,30 +2758,89 @@
2648
2758
  return;
2649
2759
  }
2650
2760
 
2651
- const height = prompt('Enter extrusion height (mm):', '20');
2761
+ const height = prompt('Enter extrusion height in mm (negative = cut into existing body):', '20');
2652
2762
  if (height === null) return;
2653
2763
  const h = parseFloat(height);
2654
2764
  if (isNaN(h) || h === 0) { updateStatus('Invalid height'); return; }
2655
2765
 
2766
+ const isCut = h < 0;
2767
+
2656
2768
  try {
2657
- const mesh = extrudeProfile(entities, h);
2769
+ const result = extrudeProfile(entities, Math.abs(h));
2770
+ let mesh = result.mesh || result;
2658
2771
  endSketch();
2659
2772
  APP.currentSketch = null;
2660
2773
  APP.mode = 'idle';
2661
- addToScene(mesh);
2662
2774
 
2663
- const feature = {
2664
- id: 'feature_' + Date.now(),
2665
- name: `Extrusion (${h}mm)`,
2666
- type: 'extrude',
2667
- mesh,
2668
- params: { height: h },
2669
- };
2670
- APP.features.push(feature);
2671
- addFeature(feature);
2672
- pushHistory();
2775
+ if (isCut && APP.features.length > 0) {
2776
+ // Find the last solid feature to cut from
2777
+ const lastSolid = [...APP.features].reverse().find(f => f.mesh && f.type !== 'cut');
2778
+
2779
+ if (lastSolid && lastSolid.mesh) {
2780
+ // Position cut mesh at same location but going into the body
2781
+ mesh.position.copy(lastSolid.mesh.position);
2782
+ mesh.updateMatrixWorld(true);
2673
2783
 
2674
- updateStatus(`Created extrusion: ${h}mm`);
2784
+ // Visual cut: use depth buffer trick
2785
+ const cutResult = csgSubtract(lastSolid.mesh, mesh);
2786
+ if (cutResult) {
2787
+ addToScene(cutResult.cut);
2788
+ const feature = {
2789
+ id: 'feature_' + Date.now(),
2790
+ name: `Cut (${Math.abs(h)}mm)`,
2791
+ type: 'cut',
2792
+ mesh: cutResult.cut,
2793
+ params: { height: h },
2794
+ _geometryJSON: cutResult.cut.geometry ? cutResult.cut.geometry.toJSON() : null,
2795
+ _materialColor: '#f85149',
2796
+ };
2797
+ APP.features.push(feature);
2798
+ addFeature(feature);
2799
+ pushHistory();
2800
+ updateStatus(`Cut ${Math.abs(h)}mm into ${lastSolid.name}`);
2801
+ } else {
2802
+ // Fallback: just show the cut shape as red overlay
2803
+ mesh.material.color.setHex(0xf85149);
2804
+ mesh.material.transparent = true;
2805
+ mesh.material.opacity = 0.5;
2806
+ addToScene(mesh);
2807
+ const feature = {
2808
+ id: 'feature_' + Date.now(),
2809
+ name: `Cut (${Math.abs(h)}mm)`,
2810
+ type: 'cut',
2811
+ mesh,
2812
+ params: { height: h },
2813
+ _geometryJSON: mesh.geometry ? mesh.geometry.toJSON() : null,
2814
+ _materialColor: '#f85149',
2815
+ };
2816
+ APP.features.push(feature);
2817
+ addFeature(feature);
2818
+ pushHistory();
2819
+ updateStatus(`Cut ${Math.abs(h)}mm (visual — real CSG coming soon)`);
2820
+ }
2821
+ } else {
2822
+ updateStatus('No solid body to cut into — create an extrusion first');
2823
+ }
2824
+ } else {
2825
+ // Normal positive extrude
2826
+ addToScene(mesh);
2827
+ if (result.wireframe) addToScene(result.wireframe);
2828
+
2829
+ const feature = {
2830
+ id: 'feature_' + Date.now(),
2831
+ name: `Extrusion (${h}mm)`,
2832
+ type: 'extrude',
2833
+ mesh,
2834
+ params: { height: h },
2835
+ _geometryJSON: mesh.geometry ? mesh.geometry.toJSON() : null,
2836
+ _materialColor: mesh.material ? (mesh.material.color ? '#' + mesh.material.color.getHexString() : '#58a6ff') : '#58a6ff',
2837
+ };
2838
+ APP.features.push(feature);
2839
+ addFeature(feature);
2840
+ pushHistory();
2841
+
2842
+ updateStatus(`Created extrusion: ${h}mm`);
2843
+ }
2675
2844
  document.getElementById('mode-indicator').textContent = 'Ready';
2676
2845
  document.getElementById('mode-value').textContent = 'Normal';
2677
2846
  } catch (err) {
@@ -2686,13 +2855,15 @@
2686
2855
  if (splash) splash.classList.add('hidden');
2687
2856
 
2688
2857
  const result = createPrimitive(prompt.type || prompt.action, prompt.params || prompt);
2689
- addToScene(result.mesh || result);
2858
+ const mesh = result.mesh || result;
2859
+ addToScene(mesh);
2860
+ if (result.wireframe) addToScene(result.wireframe);
2690
2861
 
2691
2862
  const feature = {
2692
2863
  id: 'feature_' + Date.now(),
2693
2864
  name: prompt.name || prompt.type || prompt.action || 'Part',
2694
2865
  type: prompt.type || prompt.action,
2695
- mesh: result.mesh || result,
2866
+ mesh: mesh,
2696
2867
  params: prompt.params || prompt,
2697
2868
  };
2698
2869
  APP.features.push(feature);
@@ -2765,15 +2936,38 @@
2765
2936
  if (state.features && Array.isArray(state.features)) {
2766
2937
  state.features.forEach((featureData) => {
2767
2938
  try {
2768
- const primitive = createPrimitive(featureData.type, featureData.params);
2769
- addToScene(primitive.mesh);
2939
+ let mesh;
2940
+
2941
+ // Try geometry JSON first (works for ALL feature types including extrude)
2942
+ if (featureData._geometryJSON) {
2943
+ const loader = new THREE.BufferGeometryLoader();
2944
+ const geometry = loader.parse(featureData._geometryJSON);
2945
+ const color = featureData._materialColor || '#58a6ff';
2946
+ const mat = new THREE.MeshStandardMaterial({
2947
+ color: color,
2948
+ metalness: 0.3,
2949
+ roughness: 0.6,
2950
+ flatShading: false,
2951
+ });
2952
+ mesh = new THREE.Mesh(geometry, mat);
2953
+ mesh.castShadow = true;
2954
+ mesh.receiveShadow = true;
2955
+ } else {
2956
+ // Fall back to createPrimitive for basic primitives (box, cylinder, etc.)
2957
+ const primitive = createPrimitive(featureData.type, featureData.params);
2958
+ mesh = primitive.mesh;
2959
+ }
2960
+
2961
+ addToScene(mesh);
2770
2962
 
2771
2963
  const feature = {
2772
2964
  id: featureData.id,
2773
2965
  name: featureData.name,
2774
2966
  type: featureData.type,
2775
- mesh: primitive.mesh,
2967
+ mesh: mesh,
2776
2968
  params: featureData.params,
2969
+ _geometryJSON: featureData._geometryJSON,
2970
+ _materialColor: featureData._materialColor,
2777
2971
  };
2778
2972
 
2779
2973
  APP.features.push(feature);
@@ -2786,9 +2980,10 @@
2786
2980
  }
2787
2981
  }
2788
2982
 
2789
- function fitAll() {
2983
+ function fitAllFeatures() {
2790
2984
  if (APP.features.length === 0) {
2791
- updateStatus('Nothing to fit');
2985
+ // No local features — use imported fitAll from viewport
2986
+ fitAll();
2792
2987
  return;
2793
2988
  }
2794
2989
 
@@ -2852,14 +3047,30 @@
2852
3047
  APP.history = APP.history.slice(0, APP.historyIndex + 1);
2853
3048
  }
2854
3049
 
2855
- // Save state snapshot
3050
+ // Save state snapshot — include geometry JSON so undo/redo can recreate meshes
2856
3051
  APP.history.push({
2857
- features: JSON.parse(JSON.stringify(APP.features.map((f) => ({
2858
- id: f.id,
2859
- name: f.name,
2860
- type: f.type,
2861
- params: f.params,
2862
- })))),
3052
+ features: APP.features.map((f) => {
3053
+ const data = {
3054
+ id: f.id,
3055
+ name: f.name,
3056
+ type: f.type,
3057
+ params: f.params,
3058
+ };
3059
+ // Store geometry so we can restore without createPrimitive
3060
+ try {
3061
+ if (f._geometryJSON) {
3062
+ data._geometryJSON = f._geometryJSON;
3063
+ } else if (f.mesh && f.mesh.geometry) {
3064
+ data._geometryJSON = f.mesh.geometry.toJSON();
3065
+ }
3066
+ if (f._materialColor) {
3067
+ data._materialColor = f._materialColor;
3068
+ } else if (f.mesh && f.mesh.material && f.mesh.material.color) {
3069
+ data._materialColor = '#' + f.mesh.material.color.getHexString();
3070
+ }
3071
+ } catch(e) { /* geometry serialization failed, will try createPrimitive on restore */ }
3072
+ return data;
3073
+ }),
2863
3074
  timestamp: Date.now(),
2864
3075
  });
2865
3076
 
@@ -2897,6 +3108,17 @@
2897
3108
 
2898
3109
  window.cycleCAD = Object.assign(window.cycleCAD || {}, { version: '1.0.0', APP, init });
2899
3110
 
3111
+ // Expose dialog functions globally so inline onclick="" handlers work
3112
+ window.openDialog = openDialog;
3113
+ window.closeDialog = closeDialog;
3114
+ window.applyRevolve = typeof applyRevolve === 'function' ? applyRevolve : () => {};
3115
+ window.applyFillet = typeof applyFillet === 'function' ? applyFillet : () => {};
3116
+ window.applyChamfer = typeof applyChamer === 'function' ? applyChamer : () => {};
3117
+ window.applyChamer = window.applyChamfer; // alias for typo in dialog onclick
3118
+ window.applyBoolean = typeof applyBoolean === 'function' ? applyBoolean : () => {};
3119
+ window.applyShell = typeof applyShell === 'function' ? applyShell : () => {};
3120
+ window.applyPattern = typeof applyPattern === 'function' ? applyPattern : () => {};
3121
+
2900
3122
  // ========== Hard Reset (with browser detection) ==========
2901
3123
  const hardResetBtn = document.getElementById('btn-hard-reset');
2902
3124
  if (hardResetBtn) {
@@ -2953,11 +3175,39 @@
2953
3175
  log.push('Storage: cleared');
2954
3176
  console.log('[Hard Reset]', browser, log.join(' | '));
2955
3177
  // 6. Browser-specific reload
2956
- if (browser === 'Safari') {
2957
- window.location.href = window.location.pathname + '?cachebust=' + Date.now();
2958
- } else {
2959
- setTimeout(() => { window.location.reload(true); }, 300);
2960
- }
3178
+ // Always use cache-bust URL to guarantee fresh load
3179
+ setTimeout(() => {
3180
+ window.location.href = window.location.pathname + '?v=' + Date.now();
3181
+ }, 300);
3182
+ });
3183
+ }
3184
+
3185
+ // Splash screen reset button — same logic, no confirm dialog
3186
+ const splashResetBtn = document.getElementById('btn-splash-reset');
3187
+ if (splashResetBtn) {
3188
+ splashResetBtn.addEventListener('click', async () => {
3189
+ splashResetBtn.textContent = '⏳ Clearing...';
3190
+ try {
3191
+ if ('serviceWorker' in navigator) {
3192
+ const regs = await navigator.serviceWorker.getRegistrations();
3193
+ for (const r of regs) await r.unregister();
3194
+ }
3195
+ } catch(e) {}
3196
+ try {
3197
+ if ('caches' in window) {
3198
+ const names = await caches.keys();
3199
+ for (const n of names) await caches.delete(n);
3200
+ }
3201
+ } catch(e) {}
3202
+ try {
3203
+ if (indexedDB.databases) {
3204
+ const allDBs = await indexedDB.databases();
3205
+ for (const db of allDBs) { if (db.name) indexedDB.deleteDatabase(db.name); }
3206
+ }
3207
+ } catch(e) {}
3208
+ try { localStorage.clear(); } catch(e) {}
3209
+ try { sessionStorage.clear(); } catch(e) {}
3210
+ window.location.href = window.location.pathname + '?v=' + Date.now();
2961
3211
  });
2962
3212
  }
2963
3213
 
@@ -3345,7 +3595,8 @@
3345
3595
  'marketplace-v2-panel': '🏪 Model Marketplace',
3346
3596
  'gdt-panel': '📐 GD&T Training',
3347
3597
  'misumi-panel': '🔩 MISUMI Catalog',
3348
- 'vr-panel': '🥽 CAD2VR'
3598
+ 'vr-panel': '🥽 CAD2VR',
3599
+ 'generative-panel': '🧬 Generative Design'
3349
3600
  };
3350
3601
  header.innerHTML = `<span style="font-weight:600;font-size:13px;">${titles[panelId] || moduleKey}</span><button onclick="document.getElementById('${panelId}').style.display='none'" style="background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:16px;">✕</button>`;
3351
3602
  panel.appendChild(header);
@@ -3354,7 +3605,10 @@
3354
3605
  body.style.cssText = 'padding:14px;flex:1;min-height:0;overflow-y:auto;font-size:12px;';
3355
3606
 
3356
3607
  const mod = window.cycleCAD?.[moduleKey];
3357
- if (mod?.getUI) {
3608
+ if (mod?.init) {
3609
+ // Use init() which sets innerHTML AND attaches event listeners
3610
+ mod.init(body);
3611
+ } else if (mod?.getUI) {
3358
3612
  body.innerHTML = mod.getUI();
3359
3613
  } else if (mod?.getStatus) {
3360
3614
  const status = mod.getStatus();
@@ -3384,8 +3638,289 @@
3384
3638
  document.getElementById('btn-gdt')?.addEventListener('click', () => toggleModulePanel('gdtTraining', 'gdt-panel'));
3385
3639
  document.getElementById('btn-misumi')?.addEventListener('click', () => toggleModulePanel('misumi', 'misumi-panel'));
3386
3640
  document.getElementById('btn-vr')?.addEventListener('click', () => toggleModulePanel('cadVR', 'vr-panel'));
3641
+ document.getElementById('btn-generative')?.addEventListener('click', () => toggleModulePanel('generativeDesign', 'generative-panel'));
3387
3642
 
3388
3643
  // ========== Help & Tutorials Panel ==========
3644
+ // ========== Light/Dark Mode Toggle ==========
3645
+ document.getElementById('btn-theme-toggle')?.addEventListener('click', () => {
3646
+ const root = document.documentElement;
3647
+ const isLight = root.classList.toggle('light-mode');
3648
+ const icon = document.getElementById('theme-icon');
3649
+ if (icon) icon.textContent = isLight ? '🌙' : '☀';
3650
+
3651
+ // Update Three.js renderer background
3652
+ try {
3653
+ const scene = getScene();
3654
+ const renderer = window._renderer || document.querySelector('#viewport-container canvas')?.getContext('webgl');
3655
+ if (scene) {
3656
+ if (isLight) {
3657
+ scene.background = new THREE.Color(0xe8e8e8);
3658
+ scene.fog = null;
3659
+ } else {
3660
+ scene.background = new THREE.Color(0x1a1a2e);
3661
+ scene.fog = null;
3662
+ }
3663
+ }
3664
+ } catch(e) { console.warn('Theme: renderer update skipped', e); }
3665
+
3666
+ // Persist preference
3667
+ localStorage.setItem('cyclecad_theme', isLight ? 'light' : 'dark');
3668
+ updateStatus(isLight ? 'Light mode' : 'Dark mode');
3669
+ });
3670
+
3671
+ // Auto-restore saved theme
3672
+ if (localStorage.getItem('cyclecad_theme') === 'light') {
3673
+ document.documentElement.classList.add('light-mode');
3674
+ const icon = document.getElementById('theme-icon');
3675
+ if (icon) icon.textContent = '🌙';
3676
+ try {
3677
+ const scene = getScene();
3678
+ if (scene) scene.background = new THREE.Color(0xe8e8e8);
3679
+ } catch(e) {}
3680
+ }
3681
+
3682
+ // ========== ViewCube Navigation ==========
3683
+ document.querySelectorAll('.vc-face').forEach(face => {
3684
+ face.addEventListener('click', () => {
3685
+ const view = face.dataset.vc;
3686
+ if (view) {
3687
+ try { setView(view); } catch(e) {}
3688
+ updateStatus(view.charAt(0).toUpperCase() + view.slice(1) + ' view');
3689
+ }
3690
+ });
3691
+ });
3692
+
3693
+ // Continuously sync ViewCube rotation with camera using rAF
3694
+ (function syncViewCube() {
3695
+ const cube = document.getElementById('vc-cube');
3696
+ if (!cube) return;
3697
+ let lastRotX = null, lastRotY = null;
3698
+ function updateCubeFromCamera() {
3699
+ try {
3700
+ const cam = getCamera();
3701
+ if (cam) {
3702
+ const dir = new THREE.Vector3();
3703
+ cam.getWorldDirection(dir);
3704
+ const rotY = Math.atan2(dir.x, dir.z) * (180 / Math.PI);
3705
+ const rotX = Math.asin(-dir.y) * (180 / Math.PI);
3706
+ const rxi = Math.round(rotX * 10);
3707
+ const ryi = Math.round(rotY * 10);
3708
+ if (rxi !== lastRotX || ryi !== lastRotY) {
3709
+ lastRotX = rxi;
3710
+ lastRotY = ryi;
3711
+ cube.style.transform = `rotateX(${rotX}deg) rotateY(${rotY + 180}deg)`;
3712
+ }
3713
+ }
3714
+ } catch(e) {}
3715
+ requestAnimationFrame(updateCubeFromCamera);
3716
+ }
3717
+ requestAnimationFrame(updateCubeFromCamera);
3718
+ })();
3719
+
3720
+ // ViewCube drag-to-orbit: drag on the cube rotates the 3D camera
3721
+ (function initViewCubeDrag() {
3722
+ const vcEl = document.getElementById('viewcube');
3723
+ if (!vcEl) return;
3724
+ let dragging = false, startX = 0, startY = 0;
3725
+ let startAzimuth = 0, startPolar = 0;
3726
+
3727
+ vcEl.addEventListener('pointerdown', (e) => {
3728
+ // Only start drag if not clicking a face label directly
3729
+ dragging = true;
3730
+ startX = e.clientX;
3731
+ startY = e.clientY;
3732
+ try {
3733
+ const controls = getControls();
3734
+ if (controls) {
3735
+ startAzimuth = controls.getAzimuthalAngle();
3736
+ startPolar = controls.getPolarAngle();
3737
+ }
3738
+ } catch(err) {}
3739
+ vcEl.setPointerCapture(e.pointerId);
3740
+ e.preventDefault();
3741
+ });
3742
+
3743
+ vcEl.addEventListener('pointermove', (e) => {
3744
+ if (!dragging) return;
3745
+ const dx = e.clientX - startX;
3746
+ const dy = e.clientY - startY;
3747
+ // Only orbit if dragged more than 4px (otherwise it's a click)
3748
+ if (Math.abs(dx) < 4 && Math.abs(dy) < 4) return;
3749
+ try {
3750
+ const controls = getControls();
3751
+ const cam = getCamera();
3752
+ if (controls && cam) {
3753
+ // Spherical orbit: dx → azimuthal, dy → polar
3754
+ const sensitivity = 0.02;
3755
+ const target = controls.target.clone();
3756
+ const offset = cam.position.clone().sub(target);
3757
+ const spherical = new THREE.Spherical().setFromVector3(offset);
3758
+ spherical.theta = startAzimuth - dx * sensitivity;
3759
+ spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, startPolar + dy * sensitivity));
3760
+ offset.setFromSpherical(spherical);
3761
+ cam.position.copy(target).add(offset);
3762
+ cam.lookAt(target);
3763
+ controls.update();
3764
+ }
3765
+ } catch(err) {}
3766
+ });
3767
+
3768
+ vcEl.addEventListener('pointerup', (e) => {
3769
+ const dx = Math.abs(e.clientX - startX);
3770
+ const dy = Math.abs(e.clientY - startY);
3771
+ dragging = false;
3772
+ // If it was a small move, let the face click handler fire
3773
+ });
3774
+ })();
3775
+
3776
+ // ========== Measurement Tool ==========
3777
+ (function initMeasureTool() {
3778
+ let measureMode = false;
3779
+ let measurePoints = [];
3780
+ let measureLines = [];
3781
+ let measureLabels = [];
3782
+
3783
+ function clearMeasurements() {
3784
+ const scene = getScene();
3785
+ if (!scene) return;
3786
+ measureLines.forEach(l => scene.remove(l));
3787
+ measureLabels.forEach(l => l.remove());
3788
+ measureLines = [];
3789
+ measureLabels = [];
3790
+ measurePoints = [];
3791
+ }
3792
+
3793
+ function addMeasureLabel(text, position) {
3794
+ const div = document.createElement('div');
3795
+ div.className = 'measure-label';
3796
+ div.textContent = text;
3797
+ div.style.cssText = 'position:absolute;background:rgba(88,166,255,0.9);color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;font-weight:600;pointer-events:none;z-index:100;white-space:nowrap;';
3798
+ document.getElementById('viewport-container').appendChild(div);
3799
+ measureLabels.push(div);
3800
+
3801
+ function updatePos() {
3802
+ if (!div.parentElement) return;
3803
+ try {
3804
+ const cam = getCamera();
3805
+ const container = document.getElementById('viewport-container');
3806
+ if (!cam || !container) return;
3807
+ const vec = position.clone().project(cam);
3808
+ const rect = container.getBoundingClientRect();
3809
+ div.style.left = ((vec.x + 1) / 2 * rect.width) + 'px';
3810
+ div.style.top = ((-vec.y + 1) / 2 * rect.height) + 'px';
3811
+ } catch(e) {}
3812
+ requestAnimationFrame(updatePos);
3813
+ }
3814
+ requestAnimationFrame(updatePos);
3815
+ return div;
3816
+ }
3817
+
3818
+ function onMeasureClick(e) {
3819
+ if (!measureMode) return;
3820
+ const cam = getCamera();
3821
+ const scene = getScene();
3822
+ if (!cam || !scene) return;
3823
+
3824
+ const container = document.getElementById('viewport-container');
3825
+ const rect = container.getBoundingClientRect();
3826
+ const mouse = new THREE.Vector2(
3827
+ ((e.clientX - rect.left) / rect.width) * 2 - 1,
3828
+ -((e.clientY - rect.top) / rect.height) * 2 + 1
3829
+ );
3830
+
3831
+ const raycaster = new THREE.Raycaster();
3832
+ raycaster.setFromCamera(mouse, cam);
3833
+
3834
+ const meshes = [];
3835
+ scene.traverse(obj => { if (obj.isMesh) meshes.push(obj); });
3836
+ const hits = raycaster.intersectObjects(meshes, true);
3837
+ if (hits.length === 0) return;
3838
+
3839
+ const pt = hits[0].point.clone();
3840
+ measurePoints.push(pt);
3841
+
3842
+ // Visual marker
3843
+ const marker = new THREE.Mesh(
3844
+ new THREE.SphereGeometry(0.3, 8, 8),
3845
+ new THREE.MeshBasicMaterial({ color: 0x58a6ff })
3846
+ );
3847
+ marker.position.copy(pt);
3848
+ scene.add(marker);
3849
+ measureLines.push(marker);
3850
+
3851
+ if (measurePoints.length === 2) {
3852
+ // Distance measurement
3853
+ const p1 = measurePoints[0], p2 = measurePoints[1];
3854
+ const dist = p1.distanceTo(p2);
3855
+ const dx = Math.abs(p2.x - p1.x);
3856
+ const dy = Math.abs(p2.y - p1.y);
3857
+ const dz = Math.abs(p2.z - p1.z);
3858
+
3859
+ // Draw line
3860
+ const lineGeo = new THREE.BufferGeometry().setFromPoints([p1, p2]);
3861
+ const lineMat = new THREE.LineBasicMaterial({ color: 0x58a6ff, linewidth: 2 });
3862
+ const line = new THREE.Line(lineGeo, lineMat);
3863
+ scene.add(line);
3864
+ measureLines.push(line);
3865
+
3866
+ // Label at midpoint
3867
+ const mid = p1.clone().add(p2).multiplyScalar(0.5);
3868
+ addMeasureLabel(`${dist.toFixed(1)}mm (dx:${dx.toFixed(1)} dy:${dy.toFixed(1)} dz:${dz.toFixed(1)})`, mid);
3869
+ updateStatus(`Distance: ${dist.toFixed(2)}mm`);
3870
+
3871
+ } else if (measurePoints.length === 3) {
3872
+ // Angle measurement between 3 points
3873
+ const p1 = measurePoints[0], p2 = measurePoints[1], p3 = measurePoints[2];
3874
+ const v1 = p1.clone().sub(p2).normalize();
3875
+ const v2 = p3.clone().sub(p2).normalize();
3876
+ const angle = Math.acos(Math.max(-1, Math.min(1, v1.dot(v2)))) * (180 / Math.PI);
3877
+
3878
+ // Draw angle lines
3879
+ const lg1 = new THREE.BufferGeometry().setFromPoints([p1, p2]);
3880
+ const lg2 = new THREE.BufferGeometry().setFromPoints([p2, p3]);
3881
+ const lm = new THREE.LineBasicMaterial({ color: 0x3fb950, linewidth: 2 });
3882
+ const l1 = new THREE.Line(lg1, lm);
3883
+ const l2 = new THREE.Line(lg2, lm);
3884
+ scene.add(l1); scene.add(l2);
3885
+ measureLines.push(l1, l2);
3886
+
3887
+ addMeasureLabel(`${angle.toFixed(1)}°`, p2);
3888
+ updateStatus(`Angle: ${angle.toFixed(2)}° | Distance 1→2: ${p1.distanceTo(p2).toFixed(1)}mm | Distance 2→3: ${p2.distanceTo(p3).toFixed(1)}mm`);
3889
+ measurePoints = []; // Reset after angle
3890
+ }
3891
+ }
3892
+
3893
+ // Toggle measure mode with M key or toolbar button
3894
+ document.addEventListener('keydown', (e) => {
3895
+ if (e.key === 'm' && !e.ctrlKey && !e.metaKey && document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'TEXTAREA') {
3896
+ measureMode = !measureMode;
3897
+ if (measureMode) {
3898
+ measurePoints = [];
3899
+ updateStatus('Measure mode: Click 2 points for distance, 3 points for angle. Press M to exit.');
3900
+ document.getElementById('viewport-container').addEventListener('click', onMeasureClick);
3901
+ } else {
3902
+ clearMeasurements();
3903
+ document.getElementById('viewport-container').removeEventListener('click', onMeasureClick);
3904
+ updateStatus('Measure mode off');
3905
+ }
3906
+ }
3907
+ });
3908
+
3909
+ // Also wire up measure button if it exists
3910
+ document.getElementById('tool-measure')?.addEventListener('click', () => {
3911
+ measureMode = !measureMode;
3912
+ if (measureMode) {
3913
+ measurePoints = [];
3914
+ updateStatus('Measure mode: Click 2 points for distance, 3 for angle. Press M to exit.');
3915
+ document.getElementById('viewport-container').addEventListener('click', onMeasureClick);
3916
+ } else {
3917
+ clearMeasurements();
3918
+ document.getElementById('viewport-container').removeEventListener('click', onMeasureClick);
3919
+ updateStatus('Measure mode off');
3920
+ }
3921
+ });
3922
+ })();
3923
+
3389
3924
  document.getElementById('btn-help')?.addEventListener('click', () => {
3390
3925
  let panel = document.getElementById('help-tutorials-panel');
3391
3926
  if (panel) { panel.style.display = panel.style.display === 'none' ? 'flex' : 'none'; return; }
@@ -3464,6 +3999,7 @@
3464
3999
  { cat: 'Analysis', items: ['Section View', 'Material Library', 'Weight Estimator', 'Part Comparison', 'Clearance Checker'] },
3465
4000
  { cat: 'Import/Export', items: ['STL (ASCII + binary)', 'OBJ', 'glTF 2.0', 'DXF (2D + 3D)', 'cycleCAD JSON', 'Inventor .ipt/.iam', 'STEP (via server)'] },
3466
4001
  { cat: 'Platform', items: ['Agent API (55 commands)', 'MCP Server', 'REST API', 'CLI Tool', '$CYCLE Token Engine', 'Model Marketplace V2 (GrabCAD-style)', 'Collaboration', 'CAD2VR (WebXR)'] },
4002
+ { cat: 'Simulation', items: ['Generative Design (Topology Optimization)', 'SIMP Solver (Web Worker)', '15 Materials Library', 'Loads & Constraints', 'Marching Cubes Mesh Generation', 'Manufacturing Constraints'] },
3467
4003
  { cat: 'Reference', items: ['GD&T Training (14 symbols, 50 quiz questions)', 'MISUMI Component Catalog (80+ parts)', 'McMaster-Carr Integration', 'FCF Builder', 'Tolerance Calculator'] },
3468
4004
  { cat: 'Utilities', items: ['Keyboard Shortcuts (25+)', 'Dark Theme', 'Performance Monitor', 'Grid Floor', 'Wireframe Toggle'] }
3469
4005
  ];
@@ -3838,6 +4374,6 @@
3838
4374
  </div>
3839
4375
  </div>
3840
4376
 
3841
- <span id="version-badge" style="position:fixed;bottom:42px;left:50%;transform:translateX(-50%);z-index:999;font-size:0.9rem;color:rgba(255,255,255,0.9);letter-spacing:0.1em;white-space:nowrap;padding:6px 16px;user-select:all;pointer-events:auto;font-family:monospace;font-weight:700;background:rgba(0,0,0,0.7);border:1px solid rgba(88,166,255,0.4);border-radius:6px;text-shadow:0 1px 3px rgba(0,0,0,0.5);" title="cycleCAD version">cycleCAD v0.4.0</span>
4377
+ <span id="version-badge" style="position:fixed;bottom:42px;left:50%;transform:translateX(-50%);z-index:999;font-size:0.9rem;color:rgba(255,255,255,0.9);letter-spacing:0.1em;white-space:nowrap;padding:6px 16px;user-select:all;pointer-events:auto;font-family:monospace;font-weight:700;background:rgba(0,0,0,0.7);border:1px solid rgba(88,166,255,0.4);border-radius:6px;text-shadow:0 1px 3px rgba(0,0,0,0.5);" title="cycleCAD version">cycleCAD v0.8.5</span>
3842
4378
  </body>
3843
4379
  </html>