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/LICENSE +17 -27
- package/app/index.html +963 -427
- package/app/js/agent-api.js +1 -6
- package/app/js/generative-design.js +625 -0
- package/app/js/operations.js +117 -55
- package/app/js/sketch.js +44 -14
- package/app/js/tree.js +6 -1
- package/app/js/viewport.js +8 -0
- package/package.json +2 -2
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:
|
|
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
|
-
<!--
|
|
1448
|
+
<!-- Pointer / Select -->
|
|
1337
1449
|
<div class="toolbar-group">
|
|
1338
|
-
<button class="toolbar-button" id="tool-
|
|
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
|
-
<!--
|
|
1453
|
+
<!-- Sketch Tools (always visible) -->
|
|
1365
1454
|
<div class="toolbar-group">
|
|
1366
|
-
<button class="toolbar-button" id="tool-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
<button class="toolbar-button" id="tool-
|
|
1371
|
-
|
|
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
|
-
<!--
|
|
1463
|
+
<!-- Modeling (always visible) -->
|
|
1393
1464
|
<div class="toolbar-group">
|
|
1394
|
-
<button class="toolbar-button" id="tool-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
<button class="toolbar-button" id="tool-
|
|
1399
|
-
|
|
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">↻</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">✶</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
|
-
<!--
|
|
1413
|
-
<div class="toolbar-
|
|
1414
|
-
<button class="toolbar-
|
|
1415
|
-
|
|
1416
|
-
<
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
<
|
|
1420
|
-
<
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
<
|
|
1424
|
-
<
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
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
|
-
<!--
|
|
1449
|
-
<div class="toolbar-
|
|
1450
|
-
<button class="toolbar-
|
|
1451
|
-
|
|
1452
|
-
<
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
<
|
|
1456
|
-
|
|
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
|
-
<!--
|
|
1503
|
+
<!-- Assembly -->
|
|
1461
1504
|
<div class="toolbar-group">
|
|
1462
|
-
<button class="toolbar-button" id="
|
|
1463
|
-
|
|
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
|
-
<!--
|
|
1473
|
-
<div class="toolbar-
|
|
1474
|
-
<button class="toolbar-
|
|
1475
|
-
|
|
1476
|
-
<
|
|
1477
|
-
|
|
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 &
|
|
1481
|
-
<div class="toolbar-
|
|
1482
|
-
<button class="toolbar-
|
|
1483
|
-
|
|
1484
|
-
<
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
<
|
|
1488
|
-
<
|
|
1489
|
-
|
|
1490
|
-
|
|
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
|
-
<!--
|
|
1544
|
-
<div class="toolbar-
|
|
1545
|
-
<button class="toolbar-
|
|
1546
|
-
|
|
1547
|
-
<
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
<
|
|
1551
|
-
<
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
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
|
-
<!--
|
|
1564
|
-
<div class="toolbar-
|
|
1565
|
-
<button class="toolbar-
|
|
1566
|
-
|
|
1567
|
-
<
|
|
1568
|
-
|
|
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
|
-
<!--
|
|
1557
|
+
<!-- Undo/Redo -->
|
|
1572
1558
|
<div class="toolbar-group">
|
|
1573
|
-
<button class="toolbar-button" id="btn-
|
|
1574
|
-
|
|
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
|
-
<!--
|
|
1580
|
-
<
|
|
1581
|
-
<
|
|
1582
|
-
|
|
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
|
-
<!--
|
|
1588
|
-
<div
|
|
1589
|
-
<button class="toolbar-button" id="btn-
|
|
1590
|
-
|
|
1591
|
-
|
|
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
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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
|
-
|
|
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
|
|
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;"
|
|
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;">📂</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 > 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
|
-
|
|
1768
|
-
import {
|
|
1769
|
-
import {
|
|
1770
|
-
import {
|
|
1771
|
-
import {
|
|
1772
|
-
import {
|
|
1773
|
-
import {
|
|
1774
|
-
import {
|
|
1775
|
-
import {
|
|
1776
|
-
import {
|
|
1777
|
-
import {
|
|
1778
|
-
import {
|
|
1779
|
-
import {
|
|
1780
|
-
import {
|
|
1781
|
-
import {
|
|
1782
|
-
import
|
|
1783
|
-
import
|
|
1784
|
-
import {
|
|
1785
|
-
import {
|
|
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
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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
|
-
|
|
1814
|
-
|
|
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
|
-
|
|
1818
|
-
|
|
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
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
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
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
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: () =>
|
|
1918
|
+
fitAll: () => fitAllFeatures(),
|
|
1916
1919
|
save: () => saveProject(),
|
|
1917
1920
|
exportSTL: () => doExportSTL(),
|
|
1918
|
-
});
|
|
1921
|
+
}));
|
|
1919
1922
|
|
|
1920
|
-
// 9.
|
|
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
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
hardRefreshBtn.
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2053
|
-
window.cycleCAD.materials.init();
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
window.cycleCAD.
|
|
2058
|
-
|
|
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
|
-
|
|
2078
|
-
|
|
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', () =>
|
|
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 (
|
|
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
|
|
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
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
mesh
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
2769
|
-
|
|
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:
|
|
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
|
|
2983
|
+
function fitAllFeatures() {
|
|
2790
2984
|
if (APP.features.length === 0) {
|
|
2791
|
-
|
|
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:
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
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
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
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?.
|
|
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.
|
|
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>
|