cyclecad 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CNAME +1 -0
- package/app/docs/api-reference.html +1436 -0
- package/app/docs/examples.html +803 -0
- package/app/docs/getting-started.html +1620 -0
- package/app/duo-project-browser.html +1321 -0
- package/app/duo-rebuild-guide.html +861 -0
- package/app/index.html +1635 -0
- package/app/js/ai-chat.js +992 -0
- package/app/js/app.js +724 -0
- package/app/js/export.js +658 -0
- package/app/js/inventor-parser.js +1138 -0
- package/app/js/operations.js +689 -0
- package/app/js/params.js +523 -0
- package/app/js/reverse-engineer.js +1275 -0
- package/app/js/shortcuts.js +350 -0
- package/app/js/sketch.js +899 -0
- package/app/js/tree.js +479 -0
- package/app/js/viewport.js +643 -0
- package/app/samples/Leistenbuerstenblech.ipt +0 -0
- package/app/samples/Rahmen_Seite.iam +0 -0
- package/app/samples/TraegerHoehe1.ipt +0 -0
- package/index.html +1226 -0
- package/package.json +33 -0
package/app/index.html
ADDED
|
@@ -0,0 +1,1635 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>cycleCAD - Parametric 3D CAD Modeler</title>
|
|
7
|
+
|
|
8
|
+
<!-- Import Map for Three.js -->
|
|
9
|
+
<script type="importmap">
|
|
10
|
+
{
|
|
11
|
+
"imports": {
|
|
12
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
|
|
13
|
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<style>
|
|
19
|
+
/* ===== CSS Variables & Root ===== */
|
|
20
|
+
:root {
|
|
21
|
+
--bg-primary: #1e1e1e;
|
|
22
|
+
--bg-secondary: #252526;
|
|
23
|
+
--bg-tertiary: #2d2d30;
|
|
24
|
+
--border-color: #3e3e42;
|
|
25
|
+
--text-primary: #e0e0e0;
|
|
26
|
+
--text-secondary: #a0a0a0;
|
|
27
|
+
--text-muted: #696969;
|
|
28
|
+
--accent-blue: #58a6ff;
|
|
29
|
+
--accent-blue-dark: #1f6feb;
|
|
30
|
+
--accent-green: #3fb950;
|
|
31
|
+
--accent-red: #f85149;
|
|
32
|
+
--accent-yellow: #d29922;
|
|
33
|
+
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
34
|
+
--shadow-md: 0 8px 16px rgba(0, 0, 0, 0.4);
|
|
35
|
+
--shadow-lg: 0 16px 32px rgba(0, 0, 0, 0.5);
|
|
36
|
+
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
37
|
+
--transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
38
|
+
--toolbar-height: 48px;
|
|
39
|
+
--statusbar-height: 36px;
|
|
40
|
+
--panel-width: 260px;
|
|
41
|
+
--properties-width: 300px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* ===== Reset & Globals ===== */
|
|
45
|
+
* {
|
|
46
|
+
margin: 0;
|
|
47
|
+
padding: 0;
|
|
48
|
+
box-sizing: border-box;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
html, body {
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 100%;
|
|
54
|
+
background: var(--bg-primary);
|
|
55
|
+
color: var(--text-primary);
|
|
56
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", sans-serif;
|
|
57
|
+
font-size: 13px;
|
|
58
|
+
line-height: 1.5;
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
-webkit-font-smoothing: antialiased;
|
|
61
|
+
-moz-osx-font-smoothing: grayscale;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
button, input, select, textarea {
|
|
65
|
+
font-family: inherit;
|
|
66
|
+
font-size: inherit;
|
|
67
|
+
color: inherit;
|
|
68
|
+
background: none;
|
|
69
|
+
border: none;
|
|
70
|
+
padding: 0;
|
|
71
|
+
margin: 0;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
button:disabled {
|
|
76
|
+
opacity: 0.5;
|
|
77
|
+
cursor: not-allowed;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ===== Main Layout ===== */
|
|
81
|
+
#app {
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
width: 100%;
|
|
85
|
+
height: 100%;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#toolbar {
|
|
89
|
+
height: var(--toolbar-height);
|
|
90
|
+
background: var(--bg-secondary);
|
|
91
|
+
border-bottom: 1px solid var(--border-color);
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
padding: 0 8px;
|
|
95
|
+
gap: 8px;
|
|
96
|
+
user-select: none;
|
|
97
|
+
z-index: 100;
|
|
98
|
+
box-shadow: var(--shadow-sm);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#content {
|
|
102
|
+
flex: 1;
|
|
103
|
+
display: flex;
|
|
104
|
+
gap: 0;
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#left-panel {
|
|
109
|
+
width: var(--panel-width);
|
|
110
|
+
background: var(--bg-secondary);
|
|
111
|
+
border-right: 1px solid var(--border-color);
|
|
112
|
+
display: flex;
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
z-index: 50;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#viewport-container {
|
|
119
|
+
flex: 1;
|
|
120
|
+
position: relative;
|
|
121
|
+
background: var(--bg-primary);
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#viewport {
|
|
126
|
+
width: 100%;
|
|
127
|
+
height: 100%;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#right-panel {
|
|
131
|
+
width: var(--properties-width);
|
|
132
|
+
background: var(--bg-secondary);
|
|
133
|
+
border-left: 1px solid var(--border-color);
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
z-index: 50;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#statusbar {
|
|
141
|
+
height: var(--statusbar-height);
|
|
142
|
+
background: var(--bg-secondary);
|
|
143
|
+
border-top: 1px solid var(--border-color);
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
padding: 0 12px;
|
|
147
|
+
gap: 24px;
|
|
148
|
+
font-size: 12px;
|
|
149
|
+
user-select: none;
|
|
150
|
+
z-index: 100;
|
|
151
|
+
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.3);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* ===== Toolbar Styling ===== */
|
|
155
|
+
.toolbar-group {
|
|
156
|
+
display: flex;
|
|
157
|
+
align-items: center;
|
|
158
|
+
gap: 0;
|
|
159
|
+
border-right: 1px solid var(--border-color);
|
|
160
|
+
padding-right: 8px;
|
|
161
|
+
margin-right: 8px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.toolbar-group:last-child {
|
|
165
|
+
border-right: none;
|
|
166
|
+
margin-right: 0;
|
|
167
|
+
padding-right: 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.toolbar-button {
|
|
171
|
+
padding: 6px 8px;
|
|
172
|
+
height: 32px;
|
|
173
|
+
display: flex;
|
|
174
|
+
align-items: center;
|
|
175
|
+
justify-content: center;
|
|
176
|
+
gap: 4px;
|
|
177
|
+
border-radius: 3px;
|
|
178
|
+
background: transparent;
|
|
179
|
+
color: var(--text-primary);
|
|
180
|
+
transition: all var(--transition-fast);
|
|
181
|
+
white-space: nowrap;
|
|
182
|
+
font-size: 12px;
|
|
183
|
+
font-weight: 500;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.toolbar-button:hover:not(:disabled) {
|
|
187
|
+
background: var(--bg-tertiary);
|
|
188
|
+
color: var(--accent-blue);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.toolbar-button.active {
|
|
192
|
+
background: var(--accent-blue-dark);
|
|
193
|
+
color: var(--accent-blue);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.toolbar-button:active:not(:disabled) {
|
|
197
|
+
transform: scale(0.98);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.toolbar-icon {
|
|
201
|
+
width: 16px;
|
|
202
|
+
height: 16px;
|
|
203
|
+
display: inline-flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
justify-content: center;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.toolbar-label {
|
|
209
|
+
font-size: 11px;
|
|
210
|
+
letter-spacing: 0.3px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* ===== Left Panel (Feature Tree) ===== */
|
|
214
|
+
#left-panel-header {
|
|
215
|
+
padding: 8px 12px;
|
|
216
|
+
border-bottom: 1px solid var(--border-color);
|
|
217
|
+
font-weight: 600;
|
|
218
|
+
font-size: 12px;
|
|
219
|
+
color: var(--text-secondary);
|
|
220
|
+
text-transform: uppercase;
|
|
221
|
+
letter-spacing: 0.5px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#feature-tree {
|
|
225
|
+
flex: 1;
|
|
226
|
+
overflow-y: auto;
|
|
227
|
+
overflow-x: hidden;
|
|
228
|
+
padding: 4px 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#feature-tree::-webkit-scrollbar {
|
|
232
|
+
width: 10px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#feature-tree::-webkit-scrollbar-track {
|
|
236
|
+
background: transparent;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#feature-tree::-webkit-scrollbar-thumb {
|
|
240
|
+
background: var(--border-color);
|
|
241
|
+
border-radius: 5px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#feature-tree::-webkit-scrollbar-thumb:hover {
|
|
245
|
+
background: var(--text-muted);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.tree-item {
|
|
249
|
+
padding: 4px 8px;
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
transition: background var(--transition-fast);
|
|
252
|
+
user-select: none;
|
|
253
|
+
display: flex;
|
|
254
|
+
align-items: center;
|
|
255
|
+
gap: 6px;
|
|
256
|
+
font-size: 12px;
|
|
257
|
+
margin: 1px 4px;
|
|
258
|
+
border-radius: 3px;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.tree-item:hover {
|
|
262
|
+
background: var(--bg-tertiary);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.tree-item.active {
|
|
266
|
+
background: var(--accent-blue-dark);
|
|
267
|
+
color: var(--accent-blue);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.tree-icon {
|
|
271
|
+
width: 14px;
|
|
272
|
+
height: 14px;
|
|
273
|
+
flex-shrink: 0;
|
|
274
|
+
opacity: 0.7;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* ===== Right Panel (Properties + Chat) ===== */
|
|
278
|
+
#properties-header {
|
|
279
|
+
padding: 8px 12px;
|
|
280
|
+
border-bottom: 1px solid var(--border-color);
|
|
281
|
+
font-weight: 600;
|
|
282
|
+
font-size: 12px;
|
|
283
|
+
color: var(--text-secondary);
|
|
284
|
+
text-transform: uppercase;
|
|
285
|
+
letter-spacing: 0.5px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#properties-tabs {
|
|
289
|
+
display: flex;
|
|
290
|
+
gap: 0;
|
|
291
|
+
border-bottom: 1px solid var(--border-color);
|
|
292
|
+
padding: 4px 4px 0 4px;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.properties-tab {
|
|
296
|
+
flex: 1;
|
|
297
|
+
padding: 6px 8px;
|
|
298
|
+
background: transparent;
|
|
299
|
+
color: var(--text-secondary);
|
|
300
|
+
border-bottom: 2px solid transparent;
|
|
301
|
+
transition: all var(--transition-fast);
|
|
302
|
+
font-size: 11px;
|
|
303
|
+
font-weight: 500;
|
|
304
|
+
text-align: center;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.properties-tab:hover {
|
|
308
|
+
color: var(--text-primary);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.properties-tab.active {
|
|
312
|
+
color: var(--accent-blue);
|
|
313
|
+
border-bottom-color: var(--accent-blue);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#properties-content {
|
|
317
|
+
flex: 1;
|
|
318
|
+
overflow-y: auto;
|
|
319
|
+
padding: 8px;
|
|
320
|
+
min-height: 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
#properties-content::-webkit-scrollbar {
|
|
324
|
+
width: 8px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#properties-content::-webkit-scrollbar-track {
|
|
328
|
+
background: transparent;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
#properties-content::-webkit-scrollbar-thumb {
|
|
332
|
+
background: var(--border-color);
|
|
333
|
+
border-radius: 4px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
#properties-content::-webkit-scrollbar-thumb:hover {
|
|
337
|
+
background: var(--text-muted);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.property-group {
|
|
341
|
+
margin-bottom: 12px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.property-group-title {
|
|
345
|
+
font-size: 11px;
|
|
346
|
+
font-weight: 600;
|
|
347
|
+
color: var(--text-secondary);
|
|
348
|
+
text-transform: uppercase;
|
|
349
|
+
letter-spacing: 0.5px;
|
|
350
|
+
margin-bottom: 6px;
|
|
351
|
+
padding-bottom: 4px;
|
|
352
|
+
border-bottom: 1px solid var(--border-color);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.property-row {
|
|
356
|
+
display: flex;
|
|
357
|
+
gap: 4px;
|
|
358
|
+
margin-bottom: 6px;
|
|
359
|
+
align-items: center;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.property-label {
|
|
363
|
+
min-width: 80px;
|
|
364
|
+
font-size: 11px;
|
|
365
|
+
color: var(--text-secondary);
|
|
366
|
+
font-weight: 500;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.property-input {
|
|
370
|
+
flex: 1;
|
|
371
|
+
padding: 4px 6px;
|
|
372
|
+
background: var(--bg-tertiary);
|
|
373
|
+
color: var(--text-primary);
|
|
374
|
+
border: 1px solid var(--border-color);
|
|
375
|
+
border-radius: 3px;
|
|
376
|
+
font-size: 11px;
|
|
377
|
+
transition: border-color var(--transition-fast);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.property-input:focus {
|
|
381
|
+
outline: none;
|
|
382
|
+
border-color: var(--accent-blue);
|
|
383
|
+
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.1);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* ===== Status Bar ===== */
|
|
387
|
+
.statusbar-item {
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
gap: 6px;
|
|
391
|
+
color: var(--text-secondary);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.statusbar-label {
|
|
395
|
+
font-weight: 500;
|
|
396
|
+
color: var(--text-muted);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.statusbar-value {
|
|
400
|
+
color: var(--text-primary);
|
|
401
|
+
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
402
|
+
font-size: 11px;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.status-indicator {
|
|
406
|
+
width: 8px;
|
|
407
|
+
height: 8px;
|
|
408
|
+
border-radius: 50%;
|
|
409
|
+
background: var(--accent-green);
|
|
410
|
+
animation: pulse 2s infinite;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.status-indicator.idle {
|
|
414
|
+
background: var(--text-muted);
|
|
415
|
+
animation: none;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.status-indicator.error {
|
|
419
|
+
background: var(--accent-red);
|
|
420
|
+
animation: none;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
@keyframes pulse {
|
|
424
|
+
0%, 100% { opacity: 1; }
|
|
425
|
+
50% { opacity: 0.5; }
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* ===== Welcome Splash Screen ===== */
|
|
429
|
+
#welcome-splash {
|
|
430
|
+
position: fixed;
|
|
431
|
+
top: 0;
|
|
432
|
+
left: 0;
|
|
433
|
+
right: 0;
|
|
434
|
+
bottom: 0;
|
|
435
|
+
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
|
|
436
|
+
display: flex;
|
|
437
|
+
flex-direction: column;
|
|
438
|
+
align-items: center;
|
|
439
|
+
justify-content: center;
|
|
440
|
+
z-index: 1000;
|
|
441
|
+
gap: 32px;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
#welcome-splash.hidden {
|
|
445
|
+
display: none;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.splash-logo {
|
|
449
|
+
font-size: 48px;
|
|
450
|
+
font-weight: 700;
|
|
451
|
+
letter-spacing: -1px;
|
|
452
|
+
text-align: center;
|
|
453
|
+
margin-bottom: 16px;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.splash-logo-cycle {
|
|
457
|
+
color: var(--text-primary);
|
|
458
|
+
font-weight: 400;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.splash-logo-cad {
|
|
462
|
+
color: var(--accent-blue);
|
|
463
|
+
font-weight: 700;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.splash-subtitle {
|
|
467
|
+
font-size: 14px;
|
|
468
|
+
color: var(--text-secondary);
|
|
469
|
+
text-align: center;
|
|
470
|
+
max-width: 400px;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.splash-options {
|
|
474
|
+
display: grid;
|
|
475
|
+
grid-template-columns: repeat(3, 1fr);
|
|
476
|
+
gap: 12px;
|
|
477
|
+
max-width: 420px;
|
|
478
|
+
justify-content: center;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.splash-button {
|
|
482
|
+
padding: 12px 24px;
|
|
483
|
+
border-radius: 6px;
|
|
484
|
+
font-weight: 600;
|
|
485
|
+
font-size: 14px;
|
|
486
|
+
transition: all var(--transition-base);
|
|
487
|
+
display: flex;
|
|
488
|
+
align-items: center;
|
|
489
|
+
gap: 8px;
|
|
490
|
+
min-width: 180px;
|
|
491
|
+
justify-content: center;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.splash-button-primary {
|
|
495
|
+
background: var(--accent-blue);
|
|
496
|
+
color: #000;
|
|
497
|
+
box-shadow: 0 4px 12px rgba(88, 166, 255, 0.2);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.splash-button-primary:hover {
|
|
501
|
+
background: #5db3ff;
|
|
502
|
+
box-shadow: 0 6px 16px rgba(88, 166, 255, 0.3);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.splash-button-secondary {
|
|
506
|
+
background: var(--bg-tertiary);
|
|
507
|
+
color: var(--text-primary);
|
|
508
|
+
border: 1px solid var(--border-color);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.splash-button-secondary:hover {
|
|
512
|
+
background: var(--bg-tertiary);
|
|
513
|
+
border-color: var(--accent-blue);
|
|
514
|
+
color: var(--accent-blue);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/* ===== Viewport Overlays ===== */
|
|
518
|
+
#viewport-overlay {
|
|
519
|
+
position: absolute;
|
|
520
|
+
top: 0;
|
|
521
|
+
left: 0;
|
|
522
|
+
right: 0;
|
|
523
|
+
pointer-events: none;
|
|
524
|
+
padding: 12px;
|
|
525
|
+
z-index: 10;
|
|
526
|
+
display: flex;
|
|
527
|
+
flex-direction: column;
|
|
528
|
+
gap: 8px;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.mode-indicator {
|
|
532
|
+
background: rgba(0, 0, 0, 0.6);
|
|
533
|
+
border: 1px solid var(--accent-blue);
|
|
534
|
+
color: var(--accent-blue);
|
|
535
|
+
padding: 4px 8px;
|
|
536
|
+
border-radius: 3px;
|
|
537
|
+
font-size: 11px;
|
|
538
|
+
font-weight: 600;
|
|
539
|
+
letter-spacing: 0.5px;
|
|
540
|
+
text-transform: uppercase;
|
|
541
|
+
width: fit-content;
|
|
542
|
+
backdrop-filter: blur(4px);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.coordinate-display {
|
|
546
|
+
background: rgba(0, 0, 0, 0.6);
|
|
547
|
+
color: var(--text-secondary);
|
|
548
|
+
padding: 6px 8px;
|
|
549
|
+
border-radius: 3px;
|
|
550
|
+
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
551
|
+
font-size: 10px;
|
|
552
|
+
width: fit-content;
|
|
553
|
+
backdrop-filter: blur(4px);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.coordinate-value {
|
|
557
|
+
color: var(--accent-blue);
|
|
558
|
+
margin: 0 4px;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.snap-indicator {
|
|
562
|
+
background: rgba(0, 0, 0, 0.6);
|
|
563
|
+
color: var(--accent-green);
|
|
564
|
+
padding: 4px 8px;
|
|
565
|
+
border-radius: 3px;
|
|
566
|
+
font-size: 10px;
|
|
567
|
+
font-weight: 500;
|
|
568
|
+
width: fit-content;
|
|
569
|
+
display: none;
|
|
570
|
+
backdrop-filter: blur(4px);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.snap-indicator.active {
|
|
574
|
+
display: block;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/* ===== Sketch Canvas Overlay ===== */
|
|
578
|
+
#sketch-canvas-overlay {
|
|
579
|
+
position: absolute;
|
|
580
|
+
top: 0;
|
|
581
|
+
left: 0;
|
|
582
|
+
display: none;
|
|
583
|
+
z-index: 20;
|
|
584
|
+
cursor: crosshair;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
#sketch-canvas-overlay.active {
|
|
588
|
+
display: block;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/* ===== Loading Spinner ===== */
|
|
592
|
+
#kernel-spinner {
|
|
593
|
+
position: fixed;
|
|
594
|
+
top: 50%;
|
|
595
|
+
left: 50%;
|
|
596
|
+
transform: translate(-50%, -50%);
|
|
597
|
+
z-index: 2000;
|
|
598
|
+
display: none;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
#kernel-spinner.active {
|
|
602
|
+
display: flex;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.spinner-container {
|
|
606
|
+
display: flex;
|
|
607
|
+
flex-direction: column;
|
|
608
|
+
align-items: center;
|
|
609
|
+
gap: 16px;
|
|
610
|
+
background: var(--bg-secondary);
|
|
611
|
+
padding: 32px;
|
|
612
|
+
border-radius: 8px;
|
|
613
|
+
box-shadow: var(--shadow-lg);
|
|
614
|
+
border: 1px solid var(--border-color);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.spinner {
|
|
618
|
+
width: 40px;
|
|
619
|
+
height: 40px;
|
|
620
|
+
border: 3px solid var(--border-color);
|
|
621
|
+
border-top-color: var(--accent-blue);
|
|
622
|
+
border-radius: 50%;
|
|
623
|
+
animation: spin 1s linear infinite;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
@keyframes spin {
|
|
627
|
+
to { transform: rotate(360deg); }
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.spinner-text {
|
|
631
|
+
color: var(--text-secondary);
|
|
632
|
+
font-size: 12px;
|
|
633
|
+
font-weight: 500;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/* ===== Scrollbars Global ===== */
|
|
637
|
+
::-webkit-scrollbar {
|
|
638
|
+
width: 10px;
|
|
639
|
+
height: 10px;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
::-webkit-scrollbar-track {
|
|
643
|
+
background: transparent;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
::-webkit-scrollbar-thumb {
|
|
647
|
+
background: var(--border-color);
|
|
648
|
+
border-radius: 5px;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
::-webkit-scrollbar-thumb:hover {
|
|
652
|
+
background: var(--text-muted);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/* ===== Context Menu ===== */
|
|
656
|
+
#context-menu {
|
|
657
|
+
position: fixed;
|
|
658
|
+
background: var(--bg-secondary);
|
|
659
|
+
border: 1px solid var(--border-color);
|
|
660
|
+
border-radius: 4px;
|
|
661
|
+
box-shadow: var(--shadow-lg);
|
|
662
|
+
z-index: 300;
|
|
663
|
+
display: none;
|
|
664
|
+
min-width: 180px;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
#context-menu.visible {
|
|
668
|
+
display: block;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.context-item {
|
|
672
|
+
padding: 6px 12px;
|
|
673
|
+
cursor: pointer;
|
|
674
|
+
color: var(--text-primary);
|
|
675
|
+
font-size: 12px;
|
|
676
|
+
transition: background var(--transition-fast);
|
|
677
|
+
display: flex;
|
|
678
|
+
align-items: center;
|
|
679
|
+
gap: 8px;
|
|
680
|
+
white-space: nowrap;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.context-item:hover {
|
|
684
|
+
background: var(--bg-tertiary);
|
|
685
|
+
color: var(--accent-blue);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.context-item.separator {
|
|
689
|
+
height: 1px;
|
|
690
|
+
background: var(--border-color);
|
|
691
|
+
margin: 2px 0;
|
|
692
|
+
cursor: default;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.context-item.separator:hover {
|
|
696
|
+
background: var(--border-color);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/* ===== Modals & Dialogs ===== */
|
|
700
|
+
.modal-overlay {
|
|
701
|
+
position: fixed;
|
|
702
|
+
top: 0;
|
|
703
|
+
left: 0;
|
|
704
|
+
right: 0;
|
|
705
|
+
bottom: 0;
|
|
706
|
+
background: rgba(0, 0, 0, 0.6);
|
|
707
|
+
display: none;
|
|
708
|
+
align-items: center;
|
|
709
|
+
justify-content: center;
|
|
710
|
+
z-index: 500;
|
|
711
|
+
backdrop-filter: blur(2px);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.modal-overlay.visible {
|
|
715
|
+
display: flex;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.modal {
|
|
719
|
+
background: var(--bg-secondary);
|
|
720
|
+
border: 1px solid var(--border-color);
|
|
721
|
+
border-radius: 8px;
|
|
722
|
+
box-shadow: var(--shadow-lg);
|
|
723
|
+
max-width: 500px;
|
|
724
|
+
width: 90%;
|
|
725
|
+
max-height: 80vh;
|
|
726
|
+
overflow-y: auto;
|
|
727
|
+
padding: 0;
|
|
728
|
+
display: flex;
|
|
729
|
+
flex-direction: column;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.modal-header {
|
|
733
|
+
padding: 16px;
|
|
734
|
+
border-bottom: 1px solid var(--border-color);
|
|
735
|
+
font-weight: 600;
|
|
736
|
+
font-size: 14px;
|
|
737
|
+
display: flex;
|
|
738
|
+
justify-content: space-between;
|
|
739
|
+
align-items: center;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.modal-close-btn {
|
|
743
|
+
background: transparent;
|
|
744
|
+
color: var(--text-secondary);
|
|
745
|
+
font-size: 18px;
|
|
746
|
+
cursor: pointer;
|
|
747
|
+
transition: color var(--transition-fast);
|
|
748
|
+
width: 24px;
|
|
749
|
+
height: 24px;
|
|
750
|
+
display: flex;
|
|
751
|
+
align-items: center;
|
|
752
|
+
justify-content: center;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
.modal-close-btn:hover {
|
|
756
|
+
color: var(--text-primary);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.modal-body {
|
|
760
|
+
padding: 16px;
|
|
761
|
+
flex: 1;
|
|
762
|
+
overflow-y: auto;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.modal-footer {
|
|
766
|
+
padding: 12px 16px;
|
|
767
|
+
border-top: 1px solid var(--border-color);
|
|
768
|
+
display: flex;
|
|
769
|
+
gap: 8px;
|
|
770
|
+
justify-content: flex-end;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/* ===== Buttons ===== */
|
|
774
|
+
.btn {
|
|
775
|
+
padding: 6px 12px;
|
|
776
|
+
border-radius: 3px;
|
|
777
|
+
font-weight: 500;
|
|
778
|
+
font-size: 12px;
|
|
779
|
+
transition: all var(--transition-fast);
|
|
780
|
+
cursor: pointer;
|
|
781
|
+
border: 1px solid transparent;
|
|
782
|
+
display: inline-flex;
|
|
783
|
+
align-items: center;
|
|
784
|
+
gap: 4px;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.btn-primary {
|
|
788
|
+
background: var(--accent-blue);
|
|
789
|
+
color: #000;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.btn-primary:hover:not(:disabled) {
|
|
793
|
+
background: #5db3ff;
|
|
794
|
+
box-shadow: 0 2px 8px rgba(88, 166, 255, 0.2);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.btn-secondary {
|
|
798
|
+
background: var(--bg-tertiary);
|
|
799
|
+
color: var(--text-primary);
|
|
800
|
+
border-color: var(--border-color);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.btn-secondary:hover:not(:disabled) {
|
|
804
|
+
border-color: var(--accent-blue);
|
|
805
|
+
color: var(--accent-blue);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.btn-danger {
|
|
809
|
+
background: var(--accent-red);
|
|
810
|
+
color: #fff;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
.btn-danger:hover:not(:disabled) {
|
|
814
|
+
background: #ff7b72;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/* ===== Input Fields ===== */
|
|
818
|
+
input[type="text"],
|
|
819
|
+
input[type="number"],
|
|
820
|
+
textarea,
|
|
821
|
+
select {
|
|
822
|
+
padding: 6px 8px;
|
|
823
|
+
background: var(--bg-tertiary);
|
|
824
|
+
color: var(--text-primary);
|
|
825
|
+
border: 1px solid var(--border-color);
|
|
826
|
+
border-radius: 3px;
|
|
827
|
+
font-size: 12px;
|
|
828
|
+
transition: border-color var(--transition-fast);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
input[type="text"]:focus,
|
|
832
|
+
input[type="number"]:focus,
|
|
833
|
+
textarea:focus,
|
|
834
|
+
select:focus {
|
|
835
|
+
outline: none;
|
|
836
|
+
border-color: var(--accent-blue);
|
|
837
|
+
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.1);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
textarea {
|
|
841
|
+
resize: none;
|
|
842
|
+
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/* ===== Chat Panel ===== */
|
|
846
|
+
#chat-panel {
|
|
847
|
+
display: flex;
|
|
848
|
+
flex-direction: column;
|
|
849
|
+
flex: 1;
|
|
850
|
+
min-height: 0;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
#chat-messages {
|
|
854
|
+
flex: 1;
|
|
855
|
+
overflow-y: auto;
|
|
856
|
+
padding: 8px;
|
|
857
|
+
display: flex;
|
|
858
|
+
flex-direction: column;
|
|
859
|
+
gap: 8px;
|
|
860
|
+
min-height: 0;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
#chat-messages::-webkit-scrollbar {
|
|
864
|
+
width: 8px;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
#chat-messages::-webkit-scrollbar-thumb {
|
|
868
|
+
background: var(--border-color);
|
|
869
|
+
border-radius: 4px;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.chat-message {
|
|
873
|
+
padding: 8px;
|
|
874
|
+
border-radius: 4px;
|
|
875
|
+
font-size: 11px;
|
|
876
|
+
line-height: 1.4;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.chat-message.user {
|
|
880
|
+
background: var(--accent-blue-dark);
|
|
881
|
+
color: var(--text-primary);
|
|
882
|
+
margin-left: 12px;
|
|
883
|
+
border: 1px solid var(--accent-blue);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.chat-message.assistant {
|
|
887
|
+
background: var(--bg-tertiary);
|
|
888
|
+
color: var(--text-primary);
|
|
889
|
+
margin-right: 12px;
|
|
890
|
+
border: 1px solid var(--border-color);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
#chat-input-area {
|
|
894
|
+
padding: 8px;
|
|
895
|
+
border-top: 1px solid var(--border-color);
|
|
896
|
+
display: flex;
|
|
897
|
+
gap: 4px;
|
|
898
|
+
flex-shrink: 0;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
#chat-input {
|
|
902
|
+
flex: 1;
|
|
903
|
+
padding: 6px 8px;
|
|
904
|
+
background: var(--bg-tertiary);
|
|
905
|
+
color: var(--text-primary);
|
|
906
|
+
border: 1px solid var(--border-color);
|
|
907
|
+
border-radius: 3px;
|
|
908
|
+
font-size: 11px;
|
|
909
|
+
resize: none;
|
|
910
|
+
max-height: 80px;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
#chat-input:focus {
|
|
914
|
+
outline: none;
|
|
915
|
+
border-color: var(--accent-blue);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
#chat-send-btn {
|
|
919
|
+
padding: 4px 8px;
|
|
920
|
+
background: var(--accent-blue);
|
|
921
|
+
color: #000;
|
|
922
|
+
border-radius: 3px;
|
|
923
|
+
font-weight: 600;
|
|
924
|
+
font-size: 11px;
|
|
925
|
+
transition: all var(--transition-fast);
|
|
926
|
+
flex-shrink: 0;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
#chat-send-btn:hover:not(:disabled) {
|
|
930
|
+
background: #5db3ff;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/* ===== Responsive ===== */
|
|
934
|
+
@media (max-width: 1280px) {
|
|
935
|
+
:root {
|
|
936
|
+
--panel-width: 220px;
|
|
937
|
+
--properties-width: 260px;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
@media (max-width: 1024px) {
|
|
942
|
+
:root {
|
|
943
|
+
--panel-width: 200px;
|
|
944
|
+
--properties-width: 240px;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
</style>
|
|
949
|
+
</head>
|
|
950
|
+
<body>
|
|
951
|
+
<div id="app">
|
|
952
|
+
<!-- Welcome Splash Screen -->
|
|
953
|
+
<div id="welcome-splash">
|
|
954
|
+
<div>
|
|
955
|
+
<div class="splash-logo">
|
|
956
|
+
<span class="splash-logo-cycle">cycle</span><span class="splash-logo-cad">CAD</span>
|
|
957
|
+
</div>
|
|
958
|
+
<p class="splash-subtitle">Parametric 3D CAD Modeler for the Mechanical Designer</p>
|
|
959
|
+
</div>
|
|
960
|
+
<div class="splash-options">
|
|
961
|
+
<button class="splash-button splash-button-primary" id="btn-empty-project" style="grid-column: 1 / -1;">
|
|
962
|
+
<span>📦</span> Empty Project
|
|
963
|
+
</button>
|
|
964
|
+
<button class="splash-button splash-button-secondary" id="btn-new-sketch">
|
|
965
|
+
<span>📐</span> New Sketch
|
|
966
|
+
</button>
|
|
967
|
+
<button class="splash-button splash-button-secondary" id="btn-import-inventor">
|
|
968
|
+
<span>🏭</span> Import Inventor
|
|
969
|
+
</button>
|
|
970
|
+
<button class="splash-button splash-button-secondary" id="btn-ai-generate">
|
|
971
|
+
<span>✨</span> AI Generate
|
|
972
|
+
</button>
|
|
973
|
+
<button class="splash-button splash-button-secondary" id="btn-open-browser" style="grid-column: 1 / -1; background: rgba(255,140,0,0.1); border-color: rgba(255,140,0,0.3);">
|
|
974
|
+
<span>📂</span> DUO Project Browser (473 Parts)
|
|
975
|
+
</button>
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
|
|
979
|
+
<!-- Top Toolbar -->
|
|
980
|
+
<div id="toolbar">
|
|
981
|
+
<!-- Sketch Tools -->
|
|
982
|
+
<div class="toolbar-group">
|
|
983
|
+
<button class="toolbar-button" id="tool-sketch" title="Start Sketch (S)">
|
|
984
|
+
<span class="toolbar-icon">📐</span>
|
|
985
|
+
<span class="toolbar-label">Sketch</span>
|
|
986
|
+
</button>
|
|
987
|
+
<button class="toolbar-button" id="tool-line" title="Draw Line (L)">
|
|
988
|
+
<span class="toolbar-icon">/</span>
|
|
989
|
+
<span class="toolbar-label">Line</span>
|
|
990
|
+
</button>
|
|
991
|
+
<button class="toolbar-button" id="tool-rect" title="Draw Rectangle (R)">
|
|
992
|
+
<span class="toolbar-icon">▭</span>
|
|
993
|
+
<span class="toolbar-label">Rect</span>
|
|
994
|
+
</button>
|
|
995
|
+
<button class="toolbar-button" id="tool-circle" title="Draw Circle (C)">
|
|
996
|
+
<span class="toolbar-icon">⭕</span>
|
|
997
|
+
<span class="toolbar-label">Circle</span>
|
|
998
|
+
</button>
|
|
999
|
+
<button class="toolbar-button" id="tool-arc" title="Draw Arc (A)">
|
|
1000
|
+
<span class="toolbar-icon">⌢</span>
|
|
1001
|
+
<span class="toolbar-label">Arc</span>
|
|
1002
|
+
</button>
|
|
1003
|
+
<button class="toolbar-button" id="tool-dimension" title="Add Dimension (D)">
|
|
1004
|
+
<span class="toolbar-icon">↔</span>
|
|
1005
|
+
<span class="toolbar-label">Dim</span>
|
|
1006
|
+
</button>
|
|
1007
|
+
</div>
|
|
1008
|
+
|
|
1009
|
+
<!-- Modeling Tools -->
|
|
1010
|
+
<div class="toolbar-group">
|
|
1011
|
+
<button class="toolbar-button" id="tool-extrude" title="Extrude (E)">
|
|
1012
|
+
<span class="toolbar-icon">⬆</span>
|
|
1013
|
+
<span class="toolbar-label">Extrude</span>
|
|
1014
|
+
</button>
|
|
1015
|
+
<button class="toolbar-button" id="tool-revolve" title="Revolve">
|
|
1016
|
+
<span class="toolbar-icon">🔄</span>
|
|
1017
|
+
<span class="toolbar-label">Revolve</span>
|
|
1018
|
+
</button>
|
|
1019
|
+
<button class="toolbar-button" id="tool-fillet" title="Fillet">
|
|
1020
|
+
<span class="toolbar-icon">⌒</span>
|
|
1021
|
+
<span class="toolbar-label">Fillet</span>
|
|
1022
|
+
</button>
|
|
1023
|
+
<button class="toolbar-button" id="tool-chamfer" title="Chamfer">
|
|
1024
|
+
<span class="toolbar-icon">⌉</span>
|
|
1025
|
+
<span class="toolbar-label">Chamfer</span>
|
|
1026
|
+
</button>
|
|
1027
|
+
<button class="toolbar-button" id="tool-cut" title="Boolean Cut">
|
|
1028
|
+
<span class="toolbar-icon">−</span>
|
|
1029
|
+
<span class="toolbar-label">Cut</span>
|
|
1030
|
+
</button>
|
|
1031
|
+
<button class="toolbar-button" id="tool-union" title="Boolean Union">
|
|
1032
|
+
<span class="toolbar-icon">+</span>
|
|
1033
|
+
<span class="toolbar-label">Union</span>
|
|
1034
|
+
</button>
|
|
1035
|
+
</div>
|
|
1036
|
+
|
|
1037
|
+
<!-- Export Tools -->
|
|
1038
|
+
<div class="toolbar-group">
|
|
1039
|
+
<button class="toolbar-button" id="export-stl" title="Export STL">
|
|
1040
|
+
<span class="toolbar-icon">💾</span>
|
|
1041
|
+
<span class="toolbar-label">STL</span>
|
|
1042
|
+
</button>
|
|
1043
|
+
<button class="toolbar-button" id="export-step" title="Export STEP">
|
|
1044
|
+
<span class="toolbar-icon">💾</span>
|
|
1045
|
+
<span class="toolbar-label">STEP</span>
|
|
1046
|
+
</button>
|
|
1047
|
+
</div>
|
|
1048
|
+
|
|
1049
|
+
<!-- Reverse Engineer -->
|
|
1050
|
+
<div class="toolbar-group">
|
|
1051
|
+
<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);">
|
|
1052
|
+
<span class="toolbar-icon">🔍</span>
|
|
1053
|
+
<span class="toolbar-label">Reverse Engineer</span>
|
|
1054
|
+
</button>
|
|
1055
|
+
<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);">
|
|
1056
|
+
<span class="toolbar-icon">🏭</span>
|
|
1057
|
+
<span class="toolbar-label">Inventor</span>
|
|
1058
|
+
</button>
|
|
1059
|
+
</div>
|
|
1060
|
+
|
|
1061
|
+
<!-- Edit Operations -->
|
|
1062
|
+
<div class="toolbar-group">
|
|
1063
|
+
<button class="toolbar-button" id="btn-undo" title="Undo (Ctrl+Z)">
|
|
1064
|
+
<span class="toolbar-icon">↶</span>
|
|
1065
|
+
</button>
|
|
1066
|
+
<button class="toolbar-button" id="btn-redo" title="Redo (Ctrl+Y)">
|
|
1067
|
+
<span class="toolbar-icon">↷</span>
|
|
1068
|
+
</button>
|
|
1069
|
+
</div>
|
|
1070
|
+
|
|
1071
|
+
<!-- View Buttons -->
|
|
1072
|
+
<div class="toolbar-group">
|
|
1073
|
+
<button class="toolbar-button" id="view-front" title="Front View (V+F)">
|
|
1074
|
+
<span class="toolbar-label">Front</span>
|
|
1075
|
+
</button>
|
|
1076
|
+
<button class="toolbar-button" id="view-top" title="Top View (V+T)">
|
|
1077
|
+
<span class="toolbar-label">Top</span>
|
|
1078
|
+
</button>
|
|
1079
|
+
<button class="toolbar-button" id="view-right" title="Right View (V+R)">
|
|
1080
|
+
<span class="toolbar-label">Right</span>
|
|
1081
|
+
</button>
|
|
1082
|
+
<button class="toolbar-button" id="view-iso" title="Isometric View (V+I)">
|
|
1083
|
+
<span class="toolbar-label">Iso</span>
|
|
1084
|
+
</button>
|
|
1085
|
+
<button class="toolbar-button" id="view-fit" title="Fit All (V)">
|
|
1086
|
+
<span class="toolbar-label">Fit</span>
|
|
1087
|
+
</button>
|
|
1088
|
+
</div>
|
|
1089
|
+
|
|
1090
|
+
</div>
|
|
1091
|
+
|
|
1092
|
+
<!-- Main Content Area -->
|
|
1093
|
+
<div id="content">
|
|
1094
|
+
<!-- Left Panel: Feature Tree -->
|
|
1095
|
+
<div id="left-panel">
|
|
1096
|
+
<div id="left-panel-header">Model Tree</div>
|
|
1097
|
+
<div id="feature-tree">
|
|
1098
|
+
<!-- Populated by JavaScript -->
|
|
1099
|
+
</div>
|
|
1100
|
+
</div>
|
|
1101
|
+
|
|
1102
|
+
<!-- Center: Viewport -->
|
|
1103
|
+
<div id="viewport-container">
|
|
1104
|
+
|
|
1105
|
+
<!-- Viewport Overlays -->
|
|
1106
|
+
<div id="viewport-overlay">
|
|
1107
|
+
<div class="mode-indicator" id="mode-indicator">Ready</div>
|
|
1108
|
+
<div class="coordinate-display">
|
|
1109
|
+
<span>X:</span> <span class="coordinate-value" id="coord-x">0.00</span>
|
|
1110
|
+
<span>Y:</span> <span class="coordinate-value" id="coord-y">0.00</span>
|
|
1111
|
+
<span>Z:</span> <span class="coordinate-value" id="coord-z">0.00</span>
|
|
1112
|
+
</div>
|
|
1113
|
+
<div class="snap-indicator" id="snap-indicator">Snap: Grid</div>
|
|
1114
|
+
</div>
|
|
1115
|
+
|
|
1116
|
+
<!-- Sketch Canvas Overlay -->
|
|
1117
|
+
<canvas id="sketch-canvas-overlay"></canvas>
|
|
1118
|
+
|
|
1119
|
+
<!-- Loading Spinner -->
|
|
1120
|
+
<div id="kernel-spinner">
|
|
1121
|
+
<div class="spinner-container">
|
|
1122
|
+
<div class="spinner"></div>
|
|
1123
|
+
<div class="spinner-text">Loading Kernel...</div>
|
|
1124
|
+
</div>
|
|
1125
|
+
</div>
|
|
1126
|
+
</div>
|
|
1127
|
+
|
|
1128
|
+
<!-- Right Panel: Properties + Chat -->
|
|
1129
|
+
<div id="right-panel">
|
|
1130
|
+
<div id="properties-header">Properties</div>
|
|
1131
|
+
|
|
1132
|
+
<!-- Tab Navigation -->
|
|
1133
|
+
<div id="properties-tabs">
|
|
1134
|
+
<button class="properties-tab active" data-tab="properties">Properties</button>
|
|
1135
|
+
<button class="properties-tab" data-tab="chat">Chat</button>
|
|
1136
|
+
</div>
|
|
1137
|
+
|
|
1138
|
+
<!-- Properties Content -->
|
|
1139
|
+
<div id="properties-content">
|
|
1140
|
+
<div id="tab-properties">
|
|
1141
|
+
<!-- Properties populated by JavaScript -->
|
|
1142
|
+
</div>
|
|
1143
|
+
<div id="tab-chat" style="display: none;">
|
|
1144
|
+
<!-- Chat tab populated by JavaScript -->
|
|
1145
|
+
</div>
|
|
1146
|
+
</div>
|
|
1147
|
+
</div>
|
|
1148
|
+
</div>
|
|
1149
|
+
|
|
1150
|
+
<!-- Bottom Status Bar -->
|
|
1151
|
+
<div id="statusbar">
|
|
1152
|
+
<div class="statusbar-item">
|
|
1153
|
+
<span class="status-indicator" id="kernel-status"></span>
|
|
1154
|
+
<span class="statusbar-label">Kernel:</span>
|
|
1155
|
+
<span class="statusbar-value" id="kernel-status-text">Idle</span>
|
|
1156
|
+
</div>
|
|
1157
|
+
<div class="statusbar-item">
|
|
1158
|
+
<span class="statusbar-label">Mode:</span>
|
|
1159
|
+
<span class="statusbar-value" id="mode-value">Normal</span>
|
|
1160
|
+
</div>
|
|
1161
|
+
<div class="statusbar-item">
|
|
1162
|
+
<span class="statusbar-label">Units:</span>
|
|
1163
|
+
<span class="statusbar-value" id="units-value">mm</span>
|
|
1164
|
+
</div>
|
|
1165
|
+
<div class="statusbar-item">
|
|
1166
|
+
<span class="statusbar-label">Grid:</span>
|
|
1167
|
+
<span class="statusbar-value" id="grid-value">1.0</span>
|
|
1168
|
+
</div>
|
|
1169
|
+
<div class="statusbar-item">
|
|
1170
|
+
<span class="statusbar-label">FPS:</span>
|
|
1171
|
+
<span class="statusbar-value" id="fps-value">0</span>
|
|
1172
|
+
</div>
|
|
1173
|
+
<div style="margin-left:auto;">
|
|
1174
|
+
<button id="btn-hard-refresh" title="Hard Refresh — Nuke Cache & Reload" style="background:rgba(248,81,73,0.12);border:1px solid rgba(248,81,73,0.3);color:#f85149;font-size:11px;padding:2px 10px;border-radius:4px;cursor:pointer;display:flex;align-items:center;gap:4px;">
|
|
1175
|
+
<span style="font-size:12px;">🔄</span> Hard Refresh
|
|
1176
|
+
</button>
|
|
1177
|
+
</div>
|
|
1178
|
+
</div>
|
|
1179
|
+
</div>
|
|
1180
|
+
|
|
1181
|
+
<!-- Context Menu -->
|
|
1182
|
+
<div id="context-menu"></div>
|
|
1183
|
+
|
|
1184
|
+
<!-- Module Loader -->
|
|
1185
|
+
<script type="module">
|
|
1186
|
+
import { initViewport, setView, addToScene, removeFromScene, getScene, getCamera, getControls, toggleGrid as vpToggleGrid, fitToObject } from './js/viewport.js';
|
|
1187
|
+
import { startSketch, endSketch, setTool, getEntities, clearSketch } from './js/sketch.js';
|
|
1188
|
+
import { extrudeProfile, createPrimitive, rebuildFeature, createMaterial } from './js/operations.js';
|
|
1189
|
+
import { initChat, parseCADPrompt, addMessage } from './js/ai-chat.js';
|
|
1190
|
+
import { initTree, addFeature, selectFeature, onSelect } from './js/tree.js';
|
|
1191
|
+
import { initParams, showParams, onParamChange } from './js/params.js';
|
|
1192
|
+
import { exportSTL, exportOBJ, exportJSON } from './js/export.js';
|
|
1193
|
+
import { initShortcuts } from './js/shortcuts.js';
|
|
1194
|
+
import { createReverseEngineerPanel, importFile, analyzeGeometry, reconstructFeatureTree, createWalkthrough } from './js/reverse-engineer.js';
|
|
1195
|
+
import { createInventorPanel, parseInventorFile } from './js/inventor-parser.js';
|
|
1196
|
+
|
|
1197
|
+
// ========== Application State ==========
|
|
1198
|
+
const APP = {
|
|
1199
|
+
mode: 'idle',
|
|
1200
|
+
currentSketch: null,
|
|
1201
|
+
selectedFeature: null,
|
|
1202
|
+
features: [],
|
|
1203
|
+
history: [],
|
|
1204
|
+
historyIndex: -1,
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
// ========== Initialization ==========
|
|
1208
|
+
async function init() {
|
|
1209
|
+
try {
|
|
1210
|
+
console.log('Initializing cycleCAD...');
|
|
1211
|
+
|
|
1212
|
+
// 1. Initialize 3D viewport
|
|
1213
|
+
initViewport('viewport-container');
|
|
1214
|
+
document.getElementById('kernel-status').classList.add('ready');
|
|
1215
|
+
document.getElementById('kernel-status-text').textContent = 'Ready';
|
|
1216
|
+
|
|
1217
|
+
// 2. Initialize feature tree
|
|
1218
|
+
const treeContainer = document.getElementById('feature-tree');
|
|
1219
|
+
if (treeContainer) initTree(treeContainer);
|
|
1220
|
+
|
|
1221
|
+
// 3. Initialize properties panel
|
|
1222
|
+
const propsContainer = document.getElementById('tab-properties');
|
|
1223
|
+
if (propsContainer) initParams(propsContainer);
|
|
1224
|
+
|
|
1225
|
+
// 4. Initialize AI chat
|
|
1226
|
+
const chatTab = document.getElementById('tab-chat');
|
|
1227
|
+
if (chatTab) {
|
|
1228
|
+
// Create chat UI structure
|
|
1229
|
+
chatTab.innerHTML = `
|
|
1230
|
+
<div id="chat-messages" style="flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:6px;min-height:0;"></div>
|
|
1231
|
+
<div style="display:flex;gap:4px;padding:8px;border-top:1px solid var(--border-color);">
|
|
1232
|
+
<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;">
|
|
1233
|
+
<button id="chat-send" style="padding:6px 12px;background:var(--accent-blue);color:#fff;border-radius:4px;font-size:12px;">Send</button>
|
|
1234
|
+
</div>
|
|
1235
|
+
`;
|
|
1236
|
+
chatTab.style.display = 'none';
|
|
1237
|
+
chatTab.style.flexDirection = 'column';
|
|
1238
|
+
chatTab.style.height = '100%';
|
|
1239
|
+
|
|
1240
|
+
initChat(
|
|
1241
|
+
document.getElementById('chat-messages'),
|
|
1242
|
+
document.getElementById('chat-input'),
|
|
1243
|
+
document.getElementById('chat-send'),
|
|
1244
|
+
(cmd) => executeParsedPrompt(cmd)
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// 5. Wire up toolbar buttons
|
|
1249
|
+
setupToolbar();
|
|
1250
|
+
|
|
1251
|
+
// 6. Wire up tab switching
|
|
1252
|
+
setupTabs();
|
|
1253
|
+
|
|
1254
|
+
// 7. Wire up tree selection → params
|
|
1255
|
+
onSelect((featureId) => {
|
|
1256
|
+
APP.selectedFeature = APP.features.find(f => f.id === featureId);
|
|
1257
|
+
if (APP.selectedFeature) showParams(APP.selectedFeature);
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
onParamChange((paramName, value) => {
|
|
1261
|
+
if (APP.selectedFeature) {
|
|
1262
|
+
APP.selectedFeature.params[paramName] = value;
|
|
1263
|
+
rebuildFeature(APP.selectedFeature);
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
// 8. Initialize keyboard shortcuts
|
|
1268
|
+
initShortcuts({
|
|
1269
|
+
newSketch: () => startNewSketch(),
|
|
1270
|
+
line: () => setTool('line'),
|
|
1271
|
+
rect: () => setTool('rect'),
|
|
1272
|
+
circle: () => setTool('circle'),
|
|
1273
|
+
arc: () => setTool('arc'),
|
|
1274
|
+
extrude: () => doExtrude(),
|
|
1275
|
+
undo: () => { /* TODO */ },
|
|
1276
|
+
redo: () => { /* TODO */ },
|
|
1277
|
+
delete: () => deleteSelected(),
|
|
1278
|
+
escape: () => cancelOperation(),
|
|
1279
|
+
enter: () => confirmOperation(),
|
|
1280
|
+
viewFront: () => setView('front'),
|
|
1281
|
+
viewBack: () => setView('back'),
|
|
1282
|
+
viewRight: () => setView('right'),
|
|
1283
|
+
viewLeft: () => setView('left'),
|
|
1284
|
+
viewTop: () => setView('top'),
|
|
1285
|
+
viewBottom: () => setView('bottom'),
|
|
1286
|
+
viewIso: () => setView('iso'),
|
|
1287
|
+
toggleGrid: () => vpToggleGrid(),
|
|
1288
|
+
fitAll: () => { /* TODO */ },
|
|
1289
|
+
save: () => saveProject(),
|
|
1290
|
+
exportSTL: () => doExportSTL(),
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
// 9. Setup welcome splash
|
|
1294
|
+
setupWelcome();
|
|
1295
|
+
|
|
1296
|
+
// 10. Hard Refresh button — nukes all caches, service workers, and reloads
|
|
1297
|
+
const hardRefreshBtn = document.getElementById('btn-hard-refresh');
|
|
1298
|
+
if (hardRefreshBtn) hardRefreshBtn.addEventListener('click', async () => {
|
|
1299
|
+
hardRefreshBtn.textContent = 'Clearing...';
|
|
1300
|
+
hardRefreshBtn.style.opacity = '0.6';
|
|
1301
|
+
// Kill service workers
|
|
1302
|
+
if ('serviceWorker' in navigator) {
|
|
1303
|
+
const regs = await navigator.serviceWorker.getRegistrations();
|
|
1304
|
+
for (const r of regs) await r.unregister();
|
|
1305
|
+
}
|
|
1306
|
+
// Kill Cache API caches
|
|
1307
|
+
if ('caches' in window) {
|
|
1308
|
+
const names = await caches.keys();
|
|
1309
|
+
for (const n of names) await caches.delete(n);
|
|
1310
|
+
}
|
|
1311
|
+
// Reload with timestamp cache bust
|
|
1312
|
+
const url = new URL(window.location.href);
|
|
1313
|
+
url.searchParams.set('v', Date.now());
|
|
1314
|
+
window.location.replace(url.toString());
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
console.log('cycleCAD initialized successfully');
|
|
1318
|
+
updateStatus('Ready');
|
|
1319
|
+
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
console.error('Failed to initialize cycleCAD:', error);
|
|
1322
|
+
document.getElementById('kernel-status').classList.add('error');
|
|
1323
|
+
document.getElementById('kernel-status-text').textContent = 'Error';
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// ========== Toolbar Wiring ==========
|
|
1328
|
+
function setupToolbar() {
|
|
1329
|
+
const bind = (id, fn) => {
|
|
1330
|
+
const el = document.getElementById(id);
|
|
1331
|
+
if (el) el.addEventListener('click', fn);
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
// Sketch tools
|
|
1335
|
+
bind('tool-sketch', () => startNewSketch());
|
|
1336
|
+
bind('tool-line', () => { if (APP.mode === 'sketch') setTool('line'); else { startNewSketch(); setTool('line'); } });
|
|
1337
|
+
bind('tool-rect', () => { if (APP.mode === 'sketch') setTool('rect'); else { startNewSketch(); setTool('rect'); } });
|
|
1338
|
+
bind('tool-circle', () => { if (APP.mode === 'sketch') setTool('circle'); else { startNewSketch(); setTool('circle'); } });
|
|
1339
|
+
bind('tool-arc', () => { if (APP.mode === 'sketch') setTool('arc'); else { startNewSketch(); setTool('arc'); } });
|
|
1340
|
+
bind('tool-dimension', () => setTool('dimension'));
|
|
1341
|
+
|
|
1342
|
+
// 3D operations
|
|
1343
|
+
bind('tool-extrude', () => doExtrude());
|
|
1344
|
+
bind('tool-revolve', () => updateStatus('Revolve: coming soon'));
|
|
1345
|
+
bind('tool-fillet', () => updateStatus('Fillet: coming soon'));
|
|
1346
|
+
bind('tool-chamfer', () => updateStatus('Chamfer: coming soon'));
|
|
1347
|
+
bind('tool-cut', () => updateStatus('Boolean Cut: coming soon'));
|
|
1348
|
+
bind('tool-union', () => updateStatus('Boolean Union: coming soon'));
|
|
1349
|
+
|
|
1350
|
+
// Export
|
|
1351
|
+
bind('export-stl', () => doExportSTL());
|
|
1352
|
+
bind('export-step', () => updateStatus('STEP export: coming soon'));
|
|
1353
|
+
|
|
1354
|
+
// Edit
|
|
1355
|
+
bind('btn-undo', () => updateStatus('Undo: coming soon'));
|
|
1356
|
+
bind('btn-redo', () => updateStatus('Redo: coming soon'));
|
|
1357
|
+
|
|
1358
|
+
// Views
|
|
1359
|
+
bind('view-front', () => setView('front'));
|
|
1360
|
+
bind('view-top', () => setView('top'));
|
|
1361
|
+
bind('view-right', () => setView('right'));
|
|
1362
|
+
bind('view-iso', () => setView('iso'));
|
|
1363
|
+
bind('view-fit', () => updateStatus('Fit All'));
|
|
1364
|
+
|
|
1365
|
+
// Reverse Engineer
|
|
1366
|
+
bind('btn-reverse-engineer', () => {
|
|
1367
|
+
const splash = document.getElementById('welcome-splash');
|
|
1368
|
+
if (splash) splash.classList.add('hidden');
|
|
1369
|
+
createReverseEngineerPanel(getScene());
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
// Inventor Import (.ipt / .iam)
|
|
1373
|
+
bind('btn-inventor-import', () => {
|
|
1374
|
+
const splash = document.getElementById('welcome-splash');
|
|
1375
|
+
if (splash) splash.classList.add('hidden');
|
|
1376
|
+
createInventorPanel((parsedData) => {
|
|
1377
|
+
console.log('Inventor file parsed:', parsedData);
|
|
1378
|
+
// Add parsed features to tree
|
|
1379
|
+
if (parsedData.features && parsedData.features.length > 0) {
|
|
1380
|
+
parsedData.features.forEach((f, i) => {
|
|
1381
|
+
addFeature({
|
|
1382
|
+
id: `inv-${i}`,
|
|
1383
|
+
type: f.type || 'Unknown',
|
|
1384
|
+
name: f.name || `Feature ${i + 1}`,
|
|
1385
|
+
params: f.parameters || {},
|
|
1386
|
+
icon: f.icon || '📦'
|
|
1387
|
+
});
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
updateStatus(`Inventor file loaded: ${parsedData.metadata?.fileName || 'unknown'} — ${parsedData.features?.length || 0} features found`);
|
|
1391
|
+
});
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// ========== Tab Switching ==========
|
|
1396
|
+
function setupTabs() {
|
|
1397
|
+
document.querySelectorAll('.properties-tab').forEach(tab => {
|
|
1398
|
+
tab.addEventListener('click', () => {
|
|
1399
|
+
document.querySelectorAll('.properties-tab').forEach(t => t.classList.remove('active'));
|
|
1400
|
+
tab.classList.add('active');
|
|
1401
|
+
const target = tab.getAttribute('data-tab');
|
|
1402
|
+
document.getElementById('tab-properties').style.display = target === 'properties' ? 'block' : 'none';
|
|
1403
|
+
document.getElementById('tab-chat').style.display = target === 'chat' ? 'flex' : 'none';
|
|
1404
|
+
});
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// ========== Welcome Splash ==========
|
|
1409
|
+
function setupWelcome() {
|
|
1410
|
+
const splash = document.getElementById('welcome-splash');
|
|
1411
|
+
if (!splash) return;
|
|
1412
|
+
|
|
1413
|
+
const hide = () => splash.classList.add('hidden');
|
|
1414
|
+
|
|
1415
|
+
// Empty Project — just dismiss splash and show empty workspace
|
|
1416
|
+
const emptyBtn = document.getElementById('btn-empty-project');
|
|
1417
|
+
if (emptyBtn) emptyBtn.addEventListener('click', () => {
|
|
1418
|
+
hide();
|
|
1419
|
+
updateStatus('Empty project — use toolbar to sketch, import, or generate');
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
// New Sketch — start sketching immediately
|
|
1423
|
+
const newSketchBtn = document.getElementById('btn-new-sketch');
|
|
1424
|
+
if (newSketchBtn) newSketchBtn.addEventListener('click', () => { hide(); startNewSketch(); });
|
|
1425
|
+
|
|
1426
|
+
// Import Inventor — open the Inventor parser panel
|
|
1427
|
+
const invBtn = document.getElementById('btn-import-inventor');
|
|
1428
|
+
if (invBtn) invBtn.addEventListener('click', () => {
|
|
1429
|
+
hide();
|
|
1430
|
+
createInventorPanel((parsedData) => {
|
|
1431
|
+
console.log('Inventor file parsed:', parsedData);
|
|
1432
|
+
if (parsedData.allFeatures && parsedData.allFeatures.length > 0) {
|
|
1433
|
+
parsedData.allFeatures.forEach((f, i) => {
|
|
1434
|
+
addFeature({
|
|
1435
|
+
id: `inv-${i}`,
|
|
1436
|
+
type: f.type || 'Unknown',
|
|
1437
|
+
name: `${f.icon || '📦'} ${f.type} ${i + 1}`,
|
|
1438
|
+
params: {},
|
|
1439
|
+
icon: f.icon || '📦'
|
|
1440
|
+
});
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
updateStatus(`Inventor file loaded: ${parsedData.filename} — ${parsedData.allFeatures?.length || 0} features`);
|
|
1444
|
+
});
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
// AI Generate — switch to chat tab
|
|
1448
|
+
const aiBtn = document.getElementById('btn-ai-generate');
|
|
1449
|
+
if (aiBtn) aiBtn.addEventListener('click', () => {
|
|
1450
|
+
hide();
|
|
1451
|
+
document.querySelectorAll('.properties-tab').forEach(t => t.classList.remove('active'));
|
|
1452
|
+
const chatTabBtn = document.querySelector('[data-tab="chat"]');
|
|
1453
|
+
if (chatTabBtn) chatTabBtn.classList.add('active');
|
|
1454
|
+
document.getElementById('tab-properties').style.display = 'none';
|
|
1455
|
+
document.getElementById('tab-chat').style.display = 'flex';
|
|
1456
|
+
const chatInput = document.getElementById('chat-input');
|
|
1457
|
+
if (chatInput) chatInput.focus();
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
// DUO Project Browser — open in new tab
|
|
1461
|
+
const browserBtn = document.getElementById('btn-open-browser');
|
|
1462
|
+
if (browserBtn) browserBtn.addEventListener('click', () => {
|
|
1463
|
+
window.open('duo-project-browser.html', '_blank');
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// ========== Core Operations ==========
|
|
1468
|
+
function startNewSketch() {
|
|
1469
|
+
if (APP.mode === 'sketch') {
|
|
1470
|
+
updateStatus('Finish current sketch first (Enter=extrude, Esc=cancel)');
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
const splash = document.getElementById('welcome-splash');
|
|
1474
|
+
if (splash) splash.classList.add('hidden');
|
|
1475
|
+
|
|
1476
|
+
APP.mode = 'sketch';
|
|
1477
|
+
APP.currentSketch = startSketch('XY', getCamera(), getControls());
|
|
1478
|
+
updateStatus('Sketch mode — L=Line, R=Rect, C=Circle, A=Arc. Enter=Extrude, Esc=Cancel');
|
|
1479
|
+
document.getElementById('mode-indicator').textContent = 'Sketch: XY';
|
|
1480
|
+
document.getElementById('mode-value').textContent = 'Sketch';
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function doExtrude() {
|
|
1484
|
+
if (APP.mode !== 'sketch') {
|
|
1485
|
+
updateStatus('Start a sketch first');
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
const entities = getEntities();
|
|
1489
|
+
if (!entities || entities.length === 0) {
|
|
1490
|
+
updateStatus('No sketch geometry to extrude — draw something first');
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const height = prompt('Enter extrusion height (mm):', '20');
|
|
1495
|
+
if (height === null) return;
|
|
1496
|
+
const h = parseFloat(height);
|
|
1497
|
+
if (isNaN(h) || h === 0) { updateStatus('Invalid height'); return; }
|
|
1498
|
+
|
|
1499
|
+
try {
|
|
1500
|
+
const mesh = extrudeProfile(entities, h);
|
|
1501
|
+
endSketch();
|
|
1502
|
+
APP.currentSketch = null;
|
|
1503
|
+
APP.mode = 'idle';
|
|
1504
|
+
addToScene(mesh);
|
|
1505
|
+
|
|
1506
|
+
const feature = {
|
|
1507
|
+
id: 'feature_' + Date.now(),
|
|
1508
|
+
name: `Extrusion (${h}mm)`,
|
|
1509
|
+
type: 'extrude',
|
|
1510
|
+
mesh,
|
|
1511
|
+
params: { height: h },
|
|
1512
|
+
};
|
|
1513
|
+
APP.features.push(feature);
|
|
1514
|
+
addFeature(feature);
|
|
1515
|
+
|
|
1516
|
+
updateStatus(`Created extrusion: ${h}mm`);
|
|
1517
|
+
document.getElementById('mode-indicator').textContent = 'Ready';
|
|
1518
|
+
document.getElementById('mode-value').textContent = 'Normal';
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
console.error('Extrude failed:', err);
|
|
1521
|
+
updateStatus('Extrude failed: ' + err.message);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
function executeParsedPrompt(prompt) {
|
|
1526
|
+
try {
|
|
1527
|
+
const splash = document.getElementById('welcome-splash');
|
|
1528
|
+
if (splash) splash.classList.add('hidden');
|
|
1529
|
+
|
|
1530
|
+
const result = createPrimitive(prompt.type || prompt.action, prompt.params || prompt);
|
|
1531
|
+
addToScene(result.mesh || result);
|
|
1532
|
+
|
|
1533
|
+
const feature = {
|
|
1534
|
+
id: 'feature_' + Date.now(),
|
|
1535
|
+
name: prompt.name || prompt.type || prompt.action || 'Part',
|
|
1536
|
+
type: prompt.type || prompt.action,
|
|
1537
|
+
mesh: result.mesh || result,
|
|
1538
|
+
params: prompt.params || prompt,
|
|
1539
|
+
};
|
|
1540
|
+
APP.features.push(feature);
|
|
1541
|
+
addFeature(feature);
|
|
1542
|
+
updateStatus(`Created: ${feature.name}`);
|
|
1543
|
+
} catch (err) {
|
|
1544
|
+
console.error('AI create failed:', err);
|
|
1545
|
+
updateStatus('Failed: ' + err.message);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
function cancelOperation() {
|
|
1550
|
+
if (APP.mode === 'sketch') {
|
|
1551
|
+
endSketch();
|
|
1552
|
+
APP.currentSketch = null;
|
|
1553
|
+
APP.mode = 'idle';
|
|
1554
|
+
updateStatus('Sketch cancelled');
|
|
1555
|
+
document.getElementById('mode-indicator').textContent = 'Ready';
|
|
1556
|
+
document.getElementById('mode-value').textContent = 'Normal';
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function confirmOperation() {
|
|
1561
|
+
if (APP.mode === 'sketch') {
|
|
1562
|
+
const entities = getEntities();
|
|
1563
|
+
if (entities && entities.length > 0) doExtrude();
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
function deleteSelected() {
|
|
1568
|
+
if (!APP.selectedFeature) return;
|
|
1569
|
+
removeFromScene(APP.selectedFeature.mesh);
|
|
1570
|
+
APP.features = APP.features.filter(f => f.id !== APP.selectedFeature.id);
|
|
1571
|
+
APP.selectedFeature = null;
|
|
1572
|
+
updateStatus('Feature deleted');
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function doExportSTL() {
|
|
1576
|
+
if (APP.features.length === 0) { updateStatus('Nothing to export'); return; }
|
|
1577
|
+
try {
|
|
1578
|
+
exportSTL(APP.features);
|
|
1579
|
+
updateStatus('Exported STL');
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
updateStatus('Export failed: ' + err.message);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
function saveProject() {
|
|
1586
|
+
const data = {
|
|
1587
|
+
version: '1.0',
|
|
1588
|
+
timestamp: new Date().toISOString(),
|
|
1589
|
+
features: APP.features.map(f => ({ id: f.id, name: f.name, type: f.type, params: f.params })),
|
|
1590
|
+
};
|
|
1591
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
1592
|
+
const url = URL.createObjectURL(blob);
|
|
1593
|
+
const a = document.createElement('a');
|
|
1594
|
+
a.href = url;
|
|
1595
|
+
a.download = `cyclecad_project_${Date.now()}.json`;
|
|
1596
|
+
a.click();
|
|
1597
|
+
URL.revokeObjectURL(url);
|
|
1598
|
+
updateStatus('Project saved');
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
function updateStatus(msg) {
|
|
1602
|
+
const el = document.getElementById('status-bar');
|
|
1603
|
+
if (el) el.textContent = msg;
|
|
1604
|
+
// Also update mode indicator if it exists
|
|
1605
|
+
const modeEl = document.getElementById('mode-indicator');
|
|
1606
|
+
if (modeEl && APP.mode === 'idle') modeEl.textContent = 'Ready';
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// ========== FPS Counter ==========
|
|
1610
|
+
let frameCount = 0;
|
|
1611
|
+
let lastFPSTime = performance.now();
|
|
1612
|
+
function updateFPS() {
|
|
1613
|
+
frameCount++;
|
|
1614
|
+
const now = performance.now();
|
|
1615
|
+
if (now - lastFPSTime >= 1000) {
|
|
1616
|
+
const fpsEl = document.getElementById('fps-value');
|
|
1617
|
+
if (fpsEl) fpsEl.textContent = frameCount;
|
|
1618
|
+
frameCount = 0;
|
|
1619
|
+
lastFPSTime = now;
|
|
1620
|
+
}
|
|
1621
|
+
requestAnimationFrame(updateFPS);
|
|
1622
|
+
}
|
|
1623
|
+
requestAnimationFrame(updateFPS);
|
|
1624
|
+
|
|
1625
|
+
// ========== Start ==========
|
|
1626
|
+
if (document.readyState === 'loading') {
|
|
1627
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
1628
|
+
} else {
|
|
1629
|
+
init();
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
window.cycleCAD = { version: '1.0.0', APP, init };
|
|
1633
|
+
</script>
|
|
1634
|
+
</body>
|
|
1635
|
+
</html>
|