cyclecad 0.1.8 → 0.1.10
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/AGENT_API_IMPLEMENTATION_SUMMARY.md +399 -0
- package/AGENT_API_MANIFEST.md +343 -0
- package/AGENT_API_QUICKSTART.md +316 -0
- package/AGENT_API_WIRING.md +495 -0
- package/CLAUDE.md +120 -8
- package/DELIVERABLES.txt +471 -0
- package/app/agent-demo.html +1449 -1035
- package/app/agent-test.html +486 -0
- package/app/index.html +189 -5
- package/app/js/agent-api.js +953 -98
- package/app/js/viewer-mode.js +899 -0
- package/docs/EXPLODEVIEW-FEATURE-MAPPING.md +602 -0
- package/docs/README-VIEWER-MODE-MERGE.md +364 -0
- package/docs/VIEWER-MODE-IMPLEMENTATION-GUIDE.md +412 -0
- package/docs/explodeview-merge-plan.md +476 -0
- package/docs/opencascade-integration.md +1102 -0
- package/linkedin-post.md +24 -0
- package/package.json +1 -1
package/app/agent-demo.html
CHANGED
|
@@ -1,1049 +1,1463 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>cycleCAD Agent Demo v2.0</title>
|
|
7
|
+
<script async src="https://cdn.jsdelivr.net/npm/three@r170/build/three.min.js"></script>
|
|
8
|
+
<script async src="https://cdn.jsdelivr.net/npm/three@r170/examples/js/controls/OrbitControls.js"></script>
|
|
9
|
+
<style>
|
|
10
|
+
* {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
18
|
+
background: #1a1a1a;
|
|
19
|
+
color: #e0e0e0;
|
|
20
|
+
height: 100vh;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.container {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
height: 100vh;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.header {
|
|
31
|
+
background: linear-gradient(135deg, #2a2a3e 0%, #1f1f2e 100%);
|
|
32
|
+
padding: 12px 16px;
|
|
33
|
+
border-bottom: 1px solid #404050;
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
flex-shrink: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.header h1 {
|
|
41
|
+
font-size: 18px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: 10px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.header-icon {
|
|
49
|
+
width: 24px;
|
|
50
|
+
height: 24px;
|
|
51
|
+
background: linear-gradient(135deg, #00d4ff, #0099ff);
|
|
52
|
+
border-radius: 4px;
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
font-size: 12px;
|
|
57
|
+
color: white;
|
|
58
|
+
font-weight: bold;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.main {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex: 1;
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.split-pane {
|
|
68
|
+
display: flex;
|
|
69
|
+
width: 100%;
|
|
70
|
+
position: relative;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.terminal-section {
|
|
74
|
+
background: #0d0d0d;
|
|
75
|
+
border-right: 1px solid #404050;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.viewport-section {
|
|
82
|
+
flex: 1;
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
background: #1a1a1a;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.resize-handle {
|
|
90
|
+
width: 4px;
|
|
91
|
+
cursor: col-resize;
|
|
92
|
+
background: #404050;
|
|
93
|
+
transition: background 0.2s;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.resize-handle:hover {
|
|
97
|
+
background: #00d4ff;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Terminal Styles */
|
|
101
|
+
.terminal-header {
|
|
102
|
+
background: #1a1a2e;
|
|
103
|
+
padding: 10px 12px;
|
|
104
|
+
border-bottom: 1px solid #404050;
|
|
105
|
+
font-size: 13px;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
text-transform: uppercase;
|
|
108
|
+
letter-spacing: 0.5px;
|
|
109
|
+
color: #00d4ff;
|
|
110
|
+
flex-shrink: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.terminal-output {
|
|
114
|
+
flex: 1;
|
|
115
|
+
overflow-y: auto;
|
|
116
|
+
padding: 12px;
|
|
117
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
118
|
+
font-size: 12px;
|
|
119
|
+
line-height: 1.5;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.terminal-output::-webkit-scrollbar {
|
|
123
|
+
width: 8px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.terminal-output::-webkit-scrollbar-track {
|
|
127
|
+
background: #0d0d0d;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.terminal-output::-webkit-scrollbar-thumb {
|
|
131
|
+
background: #404050;
|
|
132
|
+
border-radius: 4px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.terminal-output::-webkit-scrollbar-thumb:hover {
|
|
136
|
+
background: #505060;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.terminal-line {
|
|
140
|
+
margin-bottom: 8px;
|
|
141
|
+
padding: 4px 8px;
|
|
142
|
+
border-radius: 3px;
|
|
143
|
+
word-break: break-word;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.terminal-line.input {
|
|
147
|
+
color: #00d4ff;
|
|
148
|
+
background: rgba(0, 212, 255, 0.05);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.terminal-line.output {
|
|
152
|
+
color: #e0e0e0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.terminal-line.error {
|
|
156
|
+
color: #ff6b6b;
|
|
157
|
+
background: rgba(255, 107, 107, 0.05);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.terminal-line.success {
|
|
161
|
+
color: #51cf66;
|
|
162
|
+
background: rgba(81, 207, 102, 0.05);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.terminal-line.agent {
|
|
166
|
+
display: flex;
|
|
167
|
+
gap: 8px;
|
|
168
|
+
align-items: flex-start;
|
|
169
|
+
padding: 6px 8px;
|
|
170
|
+
background: rgba(100, 100, 120, 0.15);
|
|
171
|
+
border-left: 3px solid #6c7a9e;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.agent-badge {
|
|
175
|
+
flex-shrink: 0;
|
|
176
|
+
width: 24px;
|
|
177
|
+
height: 24px;
|
|
178
|
+
border-radius: 50%;
|
|
179
|
+
display: flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
justify-content: center;
|
|
182
|
+
font-size: 10px;
|
|
183
|
+
font-weight: bold;
|
|
184
|
+
color: white;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.agent-badge.design {
|
|
188
|
+
background: #ffd700;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.agent-badge.manufacturing {
|
|
192
|
+
background: #20b2aa;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.agent-badge.review {
|
|
196
|
+
background: #9370db;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.agent-content {
|
|
200
|
+
flex: 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Input Section */
|
|
204
|
+
.input-section {
|
|
205
|
+
background: #1a1a2e;
|
|
206
|
+
border-top: 1px solid #404050;
|
|
207
|
+
padding: 10px 12px;
|
|
208
|
+
display: flex;
|
|
209
|
+
gap: 8px;
|
|
210
|
+
flex-shrink: 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.input-wrapper {
|
|
214
|
+
flex: 1;
|
|
215
|
+
display: flex;
|
|
216
|
+
gap: 8px;
|
|
217
|
+
align-items: center;
|
|
218
|
+
position: relative;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.voice-indicator {
|
|
222
|
+
width: 24px;
|
|
223
|
+
height: 24px;
|
|
224
|
+
border-radius: 50%;
|
|
225
|
+
background: #404050;
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
transition: all 0.2s;
|
|
231
|
+
flex-shrink: 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.voice-indicator:hover {
|
|
235
|
+
background: #505060;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.voice-indicator.active {
|
|
239
|
+
background: #ff3333;
|
|
240
|
+
box-shadow: 0 0 10px rgba(255, 51, 51, 0.5);
|
|
241
|
+
animation: pulse 1s infinite;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@keyframes pulse {
|
|
245
|
+
0%, 100% { opacity: 1; }
|
|
246
|
+
50% { opacity: 0.7; }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.voice-waveform {
|
|
250
|
+
width: 32px;
|
|
251
|
+
height: 20px;
|
|
252
|
+
display: flex;
|
|
253
|
+
align-items: flex-end;
|
|
254
|
+
gap: 2px;
|
|
255
|
+
justify-content: center;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.voice-bar {
|
|
259
|
+
width: 2px;
|
|
260
|
+
background: #ff3333;
|
|
261
|
+
border-radius: 1px;
|
|
262
|
+
animation: waveform 0.3s ease-in-out infinite;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@keyframes waveform {
|
|
266
|
+
0%, 100% { height: 4px; }
|
|
267
|
+
50% { height: 12px; }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.command-input {
|
|
271
|
+
flex: 1;
|
|
272
|
+
background: #2a2a3e;
|
|
273
|
+
border: 1px solid #404050;
|
|
274
|
+
color: #e0e0e0;
|
|
275
|
+
padding: 8px 12px;
|
|
276
|
+
border-radius: 4px;
|
|
277
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
278
|
+
font-size: 12px;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.command-input:focus {
|
|
282
|
+
outline: none;
|
|
283
|
+
border-color: #00d4ff;
|
|
284
|
+
box-shadow: 0 0 8px rgba(0, 212, 255, 0.2);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.autocomplete-list {
|
|
288
|
+
position: absolute;
|
|
289
|
+
bottom: 50px;
|
|
290
|
+
left: 12px;
|
|
291
|
+
background: #2a2a3e;
|
|
292
|
+
border: 1px solid #404050;
|
|
293
|
+
border-radius: 4px;
|
|
294
|
+
max-height: 150px;
|
|
295
|
+
overflow-y: auto;
|
|
296
|
+
z-index: 100;
|
|
297
|
+
min-width: 200px;
|
|
298
|
+
display: none;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.autocomplete-list.visible {
|
|
302
|
+
display: block;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.autocomplete-item {
|
|
306
|
+
padding: 6px 12px;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
border-bottom: 1px solid #404050;
|
|
309
|
+
transition: background 0.2s;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.autocomplete-item:hover,
|
|
313
|
+
.autocomplete-item.selected {
|
|
314
|
+
background: #00d4ff;
|
|
315
|
+
color: #0d0d0d;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.input-actions {
|
|
319
|
+
display: flex;
|
|
320
|
+
gap: 6px;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.input-btn {
|
|
324
|
+
background: #2a2a3e;
|
|
325
|
+
border: 1px solid #404050;
|
|
326
|
+
color: #e0e0e0;
|
|
327
|
+
padding: 8px 12px;
|
|
328
|
+
border-radius: 4px;
|
|
329
|
+
cursor: pointer;
|
|
330
|
+
transition: all 0.2s;
|
|
331
|
+
font-size: 12px;
|
|
332
|
+
white-space: nowrap;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.input-btn:hover {
|
|
336
|
+
background: #404050;
|
|
337
|
+
border-color: #00d4ff;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.input-btn.primary {
|
|
341
|
+
background: #00d4ff;
|
|
342
|
+
color: #0d0d0d;
|
|
343
|
+
border-color: #00d4ff;
|
|
344
|
+
font-weight: 600;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.input-btn.primary:hover {
|
|
348
|
+
background: #00a8cc;
|
|
349
|
+
border-color: #00a8cc;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* Viewport Styles */
|
|
353
|
+
.viewport-toolbar {
|
|
354
|
+
background: #1a1a2e;
|
|
355
|
+
border-bottom: 1px solid #404050;
|
|
356
|
+
padding: 8px 12px;
|
|
357
|
+
display: flex;
|
|
358
|
+
gap: 8px;
|
|
359
|
+
flex-shrink: 0;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.toolbar-group {
|
|
363
|
+
display: flex;
|
|
364
|
+
gap: 4px;
|
|
365
|
+
align-items: center;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.toolbar-separator {
|
|
369
|
+
width: 1px;
|
|
370
|
+
height: 24px;
|
|
371
|
+
background: #404050;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.viewport-btn {
|
|
375
|
+
background: #2a2a3e;
|
|
376
|
+
border: 1px solid #404050;
|
|
377
|
+
color: #e0e0e0;
|
|
378
|
+
padding: 6px 10px;
|
|
379
|
+
border-radius: 3px;
|
|
380
|
+
cursor: pointer;
|
|
381
|
+
transition: all 0.2s;
|
|
382
|
+
font-size: 11px;
|
|
383
|
+
white-space: nowrap;
|
|
384
|
+
display: flex;
|
|
385
|
+
align-items: center;
|
|
386
|
+
gap: 4px;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.viewport-btn:hover {
|
|
390
|
+
background: #404050;
|
|
391
|
+
border-color: #00d4ff;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.viewport-btn.active {
|
|
395
|
+
background: #00d4ff;
|
|
396
|
+
color: #0d0d0d;
|
|
397
|
+
border-color: #00d4ff;
|
|
398
|
+
font-weight: 600;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
#viewport {
|
|
402
|
+
flex: 1;
|
|
403
|
+
background: linear-gradient(135deg, #1a1a1a 0%, #252535 100%);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.viewport-status {
|
|
407
|
+
background: #1a1a2e;
|
|
408
|
+
border-top: 1px solid #404050;
|
|
409
|
+
padding: 8px 12px;
|
|
410
|
+
display: flex;
|
|
411
|
+
justify-content: space-between;
|
|
412
|
+
align-items: center;
|
|
413
|
+
flex-shrink: 0;
|
|
414
|
+
font-size: 11px;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.status-item {
|
|
418
|
+
display: flex;
|
|
419
|
+
gap: 4px;
|
|
420
|
+
align-items: center;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.status-label {
|
|
424
|
+
color: #808090;
|
|
425
|
+
text-transform: uppercase;
|
|
426
|
+
letter-spacing: 0.3px;
|
|
427
|
+
font-weight: 600;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.status-value {
|
|
431
|
+
color: #00d4ff;
|
|
432
|
+
font-weight: 600;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/* Feature Tree Sidebar */
|
|
436
|
+
.feature-tree {
|
|
437
|
+
background: #1a1a2e;
|
|
438
|
+
border-right: 1px solid #404050;
|
|
439
|
+
width: 220px;
|
|
440
|
+
display: flex;
|
|
441
|
+
flex-direction: column;
|
|
442
|
+
flex-shrink: 0;
|
|
443
|
+
transition: all 0.3s;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.feature-tree.collapsed {
|
|
447
|
+
width: 0;
|
|
448
|
+
overflow: hidden;
|
|
449
|
+
border-right: none;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.feature-tree-header {
|
|
453
|
+
background: #0d0d0d;
|
|
454
|
+
padding: 10px 12px;
|
|
455
|
+
border-bottom: 1px solid #404050;
|
|
456
|
+
font-size: 12px;
|
|
457
|
+
font-weight: 600;
|
|
458
|
+
text-transform: uppercase;
|
|
459
|
+
letter-spacing: 0.5px;
|
|
460
|
+
color: #00d4ff;
|
|
461
|
+
display: flex;
|
|
462
|
+
justify-content: space-between;
|
|
463
|
+
align-items: center;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.tree-toggle-btn {
|
|
467
|
+
background: none;
|
|
468
|
+
border: none;
|
|
469
|
+
color: #00d4ff;
|
|
470
|
+
cursor: pointer;
|
|
471
|
+
font-size: 12px;
|
|
472
|
+
padding: 0;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.feature-tree-list {
|
|
476
|
+
flex: 1;
|
|
477
|
+
overflow-y: auto;
|
|
478
|
+
padding: 8px 0;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.feature-tree-list::-webkit-scrollbar {
|
|
482
|
+
width: 6px;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.feature-tree-list::-webkit-scrollbar-track {
|
|
486
|
+
background: #1a1a2e;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.feature-tree-list::-webkit-scrollbar-thumb {
|
|
490
|
+
background: #404050;
|
|
491
|
+
border-radius: 3px;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.tree-item {
|
|
495
|
+
padding: 8px 12px;
|
|
496
|
+
border-left: 3px solid transparent;
|
|
497
|
+
cursor: pointer;
|
|
498
|
+
transition: all 0.2s;
|
|
499
|
+
font-size: 11px;
|
|
500
|
+
white-space: nowrap;
|
|
501
|
+
overflow: hidden;
|
|
502
|
+
text-overflow: ellipsis;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.tree-item:hover {
|
|
506
|
+
background: rgba(0, 212, 255, 0.1);
|
|
507
|
+
border-left-color: #00d4ff;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.tree-item.active {
|
|
511
|
+
background: rgba(0, 212, 255, 0.2);
|
|
512
|
+
border-left-color: #00d4ff;
|
|
513
|
+
color: #00d4ff;
|
|
514
|
+
font-weight: 600;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.tree-item-icon {
|
|
518
|
+
margin-right: 6px;
|
|
519
|
+
font-size: 10px;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/* Responsive */
|
|
523
|
+
@media (max-width: 768px) {
|
|
524
|
+
.split-pane {
|
|
525
|
+
flex-direction: column;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.terminal-section {
|
|
529
|
+
height: 40%;
|
|
530
|
+
border-right: none;
|
|
531
|
+
border-bottom: 1px solid #404050;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.viewport-section {
|
|
535
|
+
height: 60%;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.resize-handle {
|
|
539
|
+
width: 100%;
|
|
540
|
+
height: 4px;
|
|
541
|
+
cursor: row-resize;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.feature-tree {
|
|
545
|
+
width: 100%;
|
|
546
|
+
max-height: 150px;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.feature-tree.collapsed {
|
|
550
|
+
max-height: 0;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
</style>
|
|
67
554
|
</head>
|
|
68
555
|
<body>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
556
|
+
<div class="container">
|
|
557
|
+
<div class="header">
|
|
558
|
+
<div style="display: flex; align-items: center; gap: 12px; flex: 1;">
|
|
559
|
+
<div class="header-icon">🤖</div>
|
|
560
|
+
<h1>cycleCAD Agent Demo</h1>
|
|
561
|
+
<span style="color: #808090; font-size: 12px; font-weight: normal;">v2.0 · Agent-First CAD</span>
|
|
562
|
+
</div>
|
|
563
|
+
<div style="color: #808090; font-size: 11px;">
|
|
564
|
+
<span id="session-time">Session: 00:00</span>
|
|
565
|
+
</div>
|
|
566
|
+
</div>
|
|
567
|
+
|
|
568
|
+
<div class="main">
|
|
569
|
+
<div class="feature-tree">
|
|
570
|
+
<div class="feature-tree-header">
|
|
571
|
+
Features
|
|
572
|
+
<button class="tree-toggle-btn" onclick="toggleFeatureTree()">−</button>
|
|
573
|
+
</div>
|
|
574
|
+
<div class="feature-tree-list" id="feature-tree-list">
|
|
575
|
+
<div style="padding: 12px; color: #808090; text-align: center; font-size: 11px;">
|
|
576
|
+
Create something to begin
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
580
|
+
|
|
581
|
+
<div class="split-pane" id="split-pane">
|
|
582
|
+
<div class="terminal-section" style="width: 35%;">
|
|
583
|
+
<div class="terminal-header">Terminal</div>
|
|
584
|
+
<div class="terminal-output" id="terminal-output">
|
|
585
|
+
<div class="terminal-line success">
|
|
586
|
+
Welcome to cycleCAD Agent Demo v2.0
|
|
587
|
+
</div>
|
|
588
|
+
<div class="terminal-line success">
|
|
589
|
+
Try: "create cylinder 50mm diameter 80mm tall"
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
<div class="autocomplete-list" id="autocomplete-list"></div>
|
|
593
|
+
<div class="input-section">
|
|
594
|
+
<div class="input-wrapper">
|
|
595
|
+
<div class="voice-indicator" id="voice-btn" title="Click to toggle voice input">
|
|
596
|
+
🎤
|
|
597
|
+
</div>
|
|
598
|
+
<div class="voice-waveform" id="voice-waveform" style="display: none;">
|
|
599
|
+
<div class="voice-bar" style="animation-delay: 0s;"></div>
|
|
600
|
+
<div class="voice-bar" style="animation-delay: 0.1s;"></div>
|
|
601
|
+
<div class="voice-bar" style="animation-delay: 0.2s;"></div>
|
|
602
|
+
</div>
|
|
603
|
+
<input
|
|
604
|
+
type="text"
|
|
605
|
+
id="command-input"
|
|
606
|
+
class="command-input"
|
|
607
|
+
placeholder="Enter command or speak..."
|
|
608
|
+
autocomplete="off"
|
|
609
|
+
>
|
|
610
|
+
</div>
|
|
611
|
+
<div class="input-actions">
|
|
612
|
+
<button class="input-btn primary" onclick="sendCommand()">Send</button>
|
|
613
|
+
<button class="input-btn" onclick="showExamples()" title="Show example commands">Examples</button>
|
|
614
|
+
</div>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
|
|
618
|
+
<div class="resize-handle" id="resize-handle"></div>
|
|
619
|
+
|
|
620
|
+
<div class="viewport-section">
|
|
621
|
+
<div class="viewport-toolbar">
|
|
622
|
+
<div class="toolbar-group">
|
|
623
|
+
<button class="viewport-btn" onclick="resetView()" title="Reset camera view">Reset View</button>
|
|
624
|
+
<button class="viewport-btn" onclick="fitToObject()" title="Fit to object">Fit All</button>
|
|
625
|
+
</div>
|
|
626
|
+
<div class="toolbar-separator"></div>
|
|
627
|
+
<div class="toolbar-group">
|
|
628
|
+
<button class="viewport-btn" id="wireframe-btn" onclick="toggleWireframe()" title="Toggle wireframe">Wireframe</button>
|
|
629
|
+
<button class="viewport-btn" id="grid-btn" onclick="toggleGrid()" title="Toggle grid">Grid</button>
|
|
630
|
+
<button class="viewport-btn" id="shadows-btn" onclick="toggleShadows()" title="Toggle shadows">Shadows</button>
|
|
631
|
+
</div>
|
|
632
|
+
<div class="toolbar-separator"></div>
|
|
633
|
+
<div class="toolbar-group">
|
|
634
|
+
<button class="viewport-btn" onclick="undoOperation()" title="Undo (Ctrl+Z)">↶ Undo</button>
|
|
635
|
+
<button class="viewport-btn" onclick="redoOperation()" title="Redo (Ctrl+Y)">↷ Redo</button>
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
<canvas id="viewport"></canvas>
|
|
639
|
+
<div class="viewport-status">
|
|
640
|
+
<div class="status-item">
|
|
641
|
+
<span class="status-label">Part:</span>
|
|
642
|
+
<span class="status-value" id="status-part">None</span>
|
|
643
|
+
</div>
|
|
644
|
+
<div class="status-item">
|
|
645
|
+
<span class="status-label">Features:</span>
|
|
646
|
+
<span class="status-value" id="status-features">0</span>
|
|
647
|
+
</div>
|
|
648
|
+
<div class="status-item">
|
|
649
|
+
<span class="status-label">Latency:</span>
|
|
650
|
+
<span class="status-value" id="status-latency">0ms</span>
|
|
651
|
+
</div>
|
|
652
|
+
<div class="status-item">
|
|
653
|
+
<span class="status-label">FPS:</span>
|
|
654
|
+
<span class="status-value" id="status-fps">0</span>
|
|
655
|
+
</div>
|
|
656
|
+
</div>
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
84
660
|
</div>
|
|
85
661
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
const intent = detectIntent(t);
|
|
836
|
-
const ex = extractDims(t);
|
|
837
|
-
|
|
838
|
-
console.log('[cycleCAD NLP]', { intent, text, extracted: ex, sceneState: sceneState.shape });
|
|
839
|
-
|
|
840
|
-
// Handle reset
|
|
841
|
-
if (intent === 'reset') {
|
|
842
|
-
resetDemo();
|
|
843
|
-
addLine('🔄 Scene cleared. Start fresh!', 'agent');
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
let result;
|
|
848
|
-
if (intent === 'create') result = buildCreateSteps(text, t, ex);
|
|
849
|
-
else if (intent === 'hole') result = buildHoleSteps(text, t, ex);
|
|
850
|
-
else if (intent === 'fillet') result = buildFilletSteps(text, t, ex);
|
|
851
|
-
else if (intent === 'chamfer') result = buildFilletSteps(text, t, ex); // reuse fillet visual
|
|
852
|
-
else if (intent === 'export') result = buildExportSteps(text, t);
|
|
853
|
-
else if (intent === 'validate') result = buildValidateSteps(text);
|
|
854
|
-
else if (intent === 'material') result = buildMaterialSteps(text, t);
|
|
855
|
-
else if (intent === 'boss') result = buildBossSteps(text, t, ex);
|
|
856
|
-
else result = buildCreateSteps(text, t, ex); // fallback
|
|
857
|
-
|
|
858
|
-
const voiceSteps = result.steps;
|
|
859
|
-
if (result.clearScene) {
|
|
860
|
-
window.clearScene();
|
|
861
|
-
document.getElementById('stats-overlay').style.display = 'none';
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// Don't clear terminal — append (iterative)
|
|
865
|
-
addDivider();
|
|
866
|
-
running = true;
|
|
867
|
-
if (!startTime) startTime = Date.now();
|
|
868
|
-
|
|
869
|
-
const totalSteps = voiceSteps.filter(s => s.type === 'cmd').length;
|
|
870
|
-
let localCmdIdx = 0;
|
|
871
|
-
|
|
872
|
-
for (const step of voiceSteps) {
|
|
873
|
-
if (!running) break;
|
|
874
|
-
if (step.type === 'agent') {
|
|
875
|
-
addLine(step.text.replace('{TIME}', ((Date.now() - startTime) / 1000).toFixed(1)).replace('{CMDS}', cmdCount), 'agent');
|
|
876
|
-
await delay(150);
|
|
877
|
-
} else if (step.type === 'comment') {
|
|
878
|
-
addLine(step.text, 'comment'); await delay(80);
|
|
879
|
-
} else if (step.type === 'divider') {
|
|
880
|
-
addDivider();
|
|
881
|
-
} else if (step.type === 'delay') {
|
|
882
|
-
await delay(step.ms);
|
|
883
|
-
} else if (step.type === 'cmd') {
|
|
884
|
-
cmdCount++; localCmdIdx++;
|
|
885
|
-
addLine(`cycleCAD.execute({ method: "${step.method}", params: ${JSON.stringify(step.params)} })`, 'cmd');
|
|
886
|
-
await delay(120);
|
|
887
|
-
const res = simulateResult(step);
|
|
888
|
-
addLine(`→ ${JSON.stringify(res, null, 0)}`, 'res ok');
|
|
889
|
-
// Update stats
|
|
890
|
-
if (step.stat === 'size') updateStats({ size: sceneState.dims.sizeLabel || 'computed' });
|
|
891
|
-
if (step.stat === 'printable') updateStats({ printable: true });
|
|
892
|
-
if (step.stat === 'cost') updateStats({ cost: `$${(costMap[sceneState.material] || 12.40).toFixed(2)} (CNC)` });
|
|
893
|
-
updateStats({ material: sceneState.material.charAt(0).toUpperCase() + sceneState.material.slice(1) });
|
|
894
|
-
document.getElementById('stat-cmds').textContent = cmdCount;
|
|
895
|
-
document.getElementById('stat-time').textContent = ((Date.now() - startTime) / 1000).toFixed(1) + 's';
|
|
896
|
-
document.getElementById('stats-overlay').style.display = 'block';
|
|
897
|
-
// Build 3D
|
|
898
|
-
if (step.mesh) buildMesh(step);
|
|
899
|
-
await delay(180);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
running = false;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
function simulateResult(step) {
|
|
906
|
-
const m = step.method;
|
|
907
|
-
if (m === 'sketch.start') return { ok: true, plane: 'XY' };
|
|
908
|
-
if (m === 'sketch.rect') return { ok: true, id: 'rect_1' };
|
|
909
|
-
if (m === 'sketch.circle') return { ok: true, id: 'circle_1', radius: step.params.radius };
|
|
910
|
-
if (m === 'ops.extrude') return { ok: true, id: 'extrude_1', height: step.params.height };
|
|
911
|
-
if (m === 'ops.primitive') return { ok: true, id: step.mesh, shape: step.params.shape };
|
|
912
|
-
if (m === 'ops.cut') return { ok: true, id: step.mesh, type: 'hole' };
|
|
913
|
-
if (m === 'ops.fillet') return { ok: true, radius: step.params.radius, applied: true };
|
|
914
|
-
if (m === 'ops.material') return { ok: true, material: step.params.material };
|
|
915
|
-
if (m === 'ops.boss') return { ok: true, id: step.mesh };
|
|
916
|
-
if (m === 'validate.dimensions') return { ok: true, size: sceneState.dims.sizeLabel };
|
|
917
|
-
if (m === 'validate.printability') return { ok: true, printable: true, issues: [] };
|
|
918
|
-
if (m === 'validate.cost') return { ok: true, unitCost: costMap[sceneState.material] || 12.40 };
|
|
919
|
-
if (m.startsWith('export.')) return { ok: true, filename: step.params.filename };
|
|
920
|
-
return { ok: true };
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
// ======= 3D MESH BUILDER =======
|
|
924
|
-
function buildMesh(step) {
|
|
925
|
-
const THREE = window.THREE;
|
|
926
|
-
const sc = sceneState;
|
|
927
|
-
const d = sc.dims;
|
|
928
|
-
const matOpts = { color: sc.matClr, metalness: 0.7, roughness: 0.3 };
|
|
929
|
-
const darkMat = new THREE.MeshStandardMaterial({ color: 0x0A1628, metalness: 0, roughness: 1 });
|
|
930
|
-
|
|
931
|
-
if (step.mesh === 'main' || step.mesh === 'main_ext') {
|
|
932
|
-
const scene = window._scene;
|
|
933
|
-
const old = scene.getObjectByName('main');
|
|
934
|
-
if (old && step.mesh !== 'main_ext') { scene.remove(old); old.geometry?.dispose(); old.material?.dispose(); }
|
|
935
|
-
|
|
936
|
-
let geo, mesh;
|
|
937
|
-
const s = sc.shape;
|
|
938
|
-
if (s === 'cylinder' || s === 'disk') {
|
|
939
|
-
geo = new THREE.CylinderGeometry(d.r, d.r, d.h, 48);
|
|
940
|
-
mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial(matOpts));
|
|
941
|
-
mesh.position.y = d.h / 2;
|
|
942
|
-
} else if (s === 'tube' || s === 'ring' || s === 'washer' || s === 'flange') {
|
|
943
|
-
const profile = [
|
|
944
|
-
new THREE.Vector2(d.innerR || d.outerR * 0.7, 0),
|
|
945
|
-
new THREE.Vector2(d.outerR || 25, 0),
|
|
946
|
-
new THREE.Vector2(d.outerR || 25, d.h),
|
|
947
|
-
new THREE.Vector2(d.innerR || d.outerR * 0.7, d.h),
|
|
948
|
-
];
|
|
949
|
-
geo = new THREE.LatheGeometry(profile, 48);
|
|
950
|
-
mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial(matOpts));
|
|
951
|
-
} else if (s === 'sphere') {
|
|
952
|
-
geo = new THREE.SphereGeometry(d.r, 48, 32);
|
|
953
|
-
mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial(matOpts));
|
|
954
|
-
mesh.position.y = d.r;
|
|
955
|
-
} else if (s === 'cone') {
|
|
956
|
-
geo = new THREE.CylinderGeometry(d.topR, d.baseR, d.h, 48);
|
|
957
|
-
mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial(matOpts));
|
|
958
|
-
mesh.position.y = d.h / 2;
|
|
959
|
-
} else if (s === 'gear') {
|
|
960
|
-
const gshape = new THREE.Shape();
|
|
961
|
-
const nt = d.teeth, pitchR = d.r, add = d.module, ded = d.module * 1.25;
|
|
962
|
-
const ouR = pitchR + add, roR = pitchR - ded;
|
|
963
|
-
for (let i = 0; i < nt; i++) {
|
|
964
|
-
const a0 = (i / nt) * Math.PI * 2, a1 = ((i + 0.15) / nt) * Math.PI * 2;
|
|
965
|
-
const a2 = ((i + 0.35) / nt) * Math.PI * 2, a3 = ((i + 0.5) / nt) * Math.PI * 2;
|
|
966
|
-
if (i === 0) gshape.moveTo(Math.cos(a0) * roR, Math.sin(a0) * roR);
|
|
967
|
-
else gshape.lineTo(Math.cos(a0) * roR, Math.sin(a0) * roR);
|
|
968
|
-
gshape.lineTo(Math.cos(a1) * ouR, Math.sin(a1) * ouR);
|
|
969
|
-
gshape.lineTo(Math.cos(a2) * ouR, Math.sin(a2) * ouR);
|
|
970
|
-
gshape.lineTo(Math.cos(a3) * roR, Math.sin(a3) * roR);
|
|
971
|
-
}
|
|
972
|
-
gshape.closePath();
|
|
973
|
-
geo = new THREE.ExtrudeGeometry(gshape, { depth: d.h, bevelEnabled: false });
|
|
974
|
-
geo.rotateX(-Math.PI / 2);
|
|
975
|
-
mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial(matOpts));
|
|
976
|
-
mesh.position.y = d.h;
|
|
977
|
-
} else if (s === 'hexbolt') {
|
|
978
|
-
const group = new THREE.Group();
|
|
979
|
-
const headGeo = new THREE.CylinderGeometry(d.headR, d.headR, d.headH, 6);
|
|
980
|
-
const hm = new THREE.Mesh(headGeo, new THREE.MeshStandardMaterial(matOpts));
|
|
981
|
-
hm.position.y = d.shankH + d.headH / 2; group.add(hm);
|
|
982
|
-
const sg = new THREE.CylinderGeometry(d.shankR, d.shankR, d.shankH, 24);
|
|
983
|
-
const sm = new THREE.Mesh(sg, new THREE.MeshStandardMaterial(matOpts));
|
|
984
|
-
sm.position.y = d.shankH / 2; group.add(sm);
|
|
985
|
-
group.name = 'main';
|
|
986
|
-
window.addMeshToScene(group); return;
|
|
987
|
-
} else {
|
|
988
|
-
geo = new THREE.BoxGeometry(d.w || 80, d.h || 5, d.d || 40);
|
|
989
|
-
mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial(matOpts));
|
|
990
|
-
mesh.position.y = (d.h || 5) / 2;
|
|
991
|
-
}
|
|
992
|
-
if (mesh) { mesh.name = 'main'; window.addMeshToScene(mesh); }
|
|
993
|
-
}
|
|
994
|
-
else if (step.mesh === 'fillet' && (sc.shape === 'bracket' || sc.shape === 'plate' || sc.shape === 'box')) {
|
|
995
|
-
const scene = window._scene;
|
|
996
|
-
const old = scene.getObjectByName('main');
|
|
997
|
-
if (old) { scene.remove(old); old.geometry?.dispose(); old.material?.dispose(); }
|
|
998
|
-
const fr = Math.min(d.filletR || 3, (d.w || 80) / 2, (d.d || 40) / 2);
|
|
999
|
-
const hw = (d.w || 80) / 2, hd = (d.d || 40) / 2, h = d.h || 5;
|
|
1000
|
-
const shape = new THREE.Shape();
|
|
1001
|
-
shape.moveTo(-hw + fr, -hd); shape.lineTo(hw - fr, -hd);
|
|
1002
|
-
shape.quadraticCurveTo(hw, -hd, hw, -hd + fr); shape.lineTo(hw, hd - fr);
|
|
1003
|
-
shape.quadraticCurveTo(hw, hd, hw - fr, hd); shape.lineTo(-hw + fr, hd);
|
|
1004
|
-
shape.quadraticCurveTo(-hw, hd, -hw, hd - fr); shape.lineTo(-hw, -hd + fr);
|
|
1005
|
-
shape.quadraticCurveTo(-hw, -hd, -hw + fr, -hd);
|
|
1006
|
-
const geo = new THREE.ExtrudeGeometry(shape, { depth: h, bevelEnabled: false, curveSegments: 16 });
|
|
1007
|
-
geo.rotateX(-Math.PI / 2);
|
|
1008
|
-
const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ color: sc.matClr, metalness: 0.7, roughness: 0.3 }));
|
|
1009
|
-
mesh.position.y = h; mesh.name = 'main';
|
|
1010
|
-
window.addMeshToScene(mesh);
|
|
1011
|
-
}
|
|
1012
|
-
else if (step.mesh && (step.mesh.startsWith('hole') || step.mesh.startsWith('boss'))) {
|
|
1013
|
-
const isBoss = step.mesh.startsWith('boss');
|
|
1014
|
-
const r = step.params?.radius || 5;
|
|
1015
|
-
const h = step.params?.height || (sc.dims.h || 10) + 2;
|
|
1016
|
-
const geo = new THREE.CylinderGeometry(r, r, h, 24);
|
|
1017
|
-
const mat = isBoss ? new THREE.MeshStandardMaterial({ color: sc.matClr, metalness: 0.7, roughness: 0.3 }) : darkMat;
|
|
1018
|
-
const mesh = new THREE.Mesh(geo, mat);
|
|
1019
|
-
if (step.pos) mesh.position.set(step.pos[0], step.pos[1], step.pos[2]);
|
|
1020
|
-
window.addMeshToScene(mesh);
|
|
1021
|
-
}
|
|
1022
|
-
else if (step.mesh === 'recolor') {
|
|
1023
|
-
// Change material color on existing part
|
|
1024
|
-
const scene = window._scene;
|
|
1025
|
-
scene.traverse(c => {
|
|
1026
|
-
if (c.isMesh && c.material && c.material.color) {
|
|
1027
|
-
c.material.color.setHex(sc.matClr);
|
|
1028
|
-
}
|
|
662
|
+
<script>
|
|
663
|
+
// Three.js Setup
|
|
664
|
+
let scene, camera, renderer, controls, currentMesh, gridHelper, shadowPlane;
|
|
665
|
+
let commandHistory = [];
|
|
666
|
+
let historyIndex = -1;
|
|
667
|
+
let isListening = false;
|
|
668
|
+
let recognition = null;
|
|
669
|
+
let lastCommandTime = 0;
|
|
670
|
+
let operationHistory = [];
|
|
671
|
+
let historyPointer = 0;
|
|
672
|
+
let wireframeMode = false;
|
|
673
|
+
let gridVisible = true;
|
|
674
|
+
let shadowsVisible = true;
|
|
675
|
+
|
|
676
|
+
// Scene State
|
|
677
|
+
const sceneState = {
|
|
678
|
+
currentPart: 'Unnamed Part',
|
|
679
|
+
features: [],
|
|
680
|
+
material: 'Steel',
|
|
681
|
+
dimensions: {}
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// Initialize Three.js
|
|
685
|
+
function initThreeJS() {
|
|
686
|
+
const canvas = document.getElementById('viewport');
|
|
687
|
+
|
|
688
|
+
scene = new THREE.Scene();
|
|
689
|
+
scene.background = new THREE.Color(0x1a1a1a);
|
|
690
|
+
scene.fog = new THREE.Fog(0x1a1a1a, 500, 1000);
|
|
691
|
+
|
|
692
|
+
camera = new THREE.PerspectiveCamera(
|
|
693
|
+
75,
|
|
694
|
+
canvas.clientWidth / canvas.clientHeight,
|
|
695
|
+
0.1,
|
|
696
|
+
10000
|
|
697
|
+
);
|
|
698
|
+
camera.position.set(200, 150, 200);
|
|
699
|
+
|
|
700
|
+
renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
|
|
701
|
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
|
|
702
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
703
|
+
renderer.shadowMap.enabled = true;
|
|
704
|
+
renderer.shadowMap.type = THREE.PCFShadowShadowMap;
|
|
705
|
+
|
|
706
|
+
// Lights
|
|
707
|
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
|
708
|
+
scene.add(ambientLight);
|
|
709
|
+
|
|
710
|
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
711
|
+
directionalLight.position.set(100, 150, 100);
|
|
712
|
+
directionalLight.castShadow = true;
|
|
713
|
+
directionalLight.shadow.camera.left = -300;
|
|
714
|
+
directionalLight.shadow.camera.right = 300;
|
|
715
|
+
directionalLight.shadow.camera.top = 300;
|
|
716
|
+
directionalLight.shadow.camera.bottom = -300;
|
|
717
|
+
directionalLight.shadow.mapSize.width = 2048;
|
|
718
|
+
directionalLight.shadow.mapSize.height = 2048;
|
|
719
|
+
scene.add(directionalLight);
|
|
720
|
+
|
|
721
|
+
// Controls
|
|
722
|
+
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
|
723
|
+
controls.enableDamping = true;
|
|
724
|
+
controls.dampingFactor = 0.05;
|
|
725
|
+
controls.autoRotate = false;
|
|
726
|
+
|
|
727
|
+
// Grid
|
|
728
|
+
gridHelper = new THREE.GridHelper(400, 40, 0x404050, 0x303040);
|
|
729
|
+
scene.add(gridHelper);
|
|
730
|
+
|
|
731
|
+
// Shadow plane
|
|
732
|
+
const planeGeometry = new THREE.PlaneGeometry(500, 500);
|
|
733
|
+
const planeMaterial = new THREE.ShadowMaterial({ opacity: 0.3 });
|
|
734
|
+
shadowPlane = new THREE.Mesh(planeGeometry, planeMaterial);
|
|
735
|
+
shadowPlane.rotateX(-Math.PI / 2);
|
|
736
|
+
shadowPlane.receiveShadow = true;
|
|
737
|
+
scene.add(shadowPlane);
|
|
738
|
+
|
|
739
|
+
// Handle resize
|
|
740
|
+
window.addEventListener('resize', onWindowResize);
|
|
741
|
+
|
|
742
|
+
// Animation loop
|
|
743
|
+
animate();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function onWindowResize() {
|
|
747
|
+
const canvas = document.getElementById('viewport');
|
|
748
|
+
if (!canvas) return;
|
|
749
|
+
|
|
750
|
+
const width = canvas.clientWidth;
|
|
751
|
+
const height = canvas.clientHeight;
|
|
752
|
+
|
|
753
|
+
camera.aspect = width / height;
|
|
754
|
+
camera.updateProjectionMatrix();
|
|
755
|
+
renderer.setSize(width, height);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function animate() {
|
|
759
|
+
requestAnimationFrame(animate);
|
|
760
|
+
controls.update();
|
|
761
|
+
|
|
762
|
+
// Update FPS
|
|
763
|
+
const now = performance.now();
|
|
764
|
+
if (!animate.lastTime) animate.lastTime = now;
|
|
765
|
+
const delta = now - animate.lastTime;
|
|
766
|
+
animate.lastTime = now;
|
|
767
|
+
|
|
768
|
+
if (delta > 0) {
|
|
769
|
+
const fps = Math.round(1000 / delta);
|
|
770
|
+
document.getElementById('status-fps').textContent = fps;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
renderer.render(scene, camera);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Voice Recognition
|
|
777
|
+
function initVoiceRecognition() {
|
|
778
|
+
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
779
|
+
|
|
780
|
+
if (!SpeechRecognition) {
|
|
781
|
+
logTerminal('Voice recognition not supported in your browser', 'error');
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
recognition = new SpeechRecognition();
|
|
786
|
+
recognition.continuous = false;
|
|
787
|
+
recognition.interimResults = true;
|
|
788
|
+
recognition.lang = 'en-US';
|
|
789
|
+
|
|
790
|
+
const input = document.getElementById('command-input');
|
|
791
|
+
|
|
792
|
+
recognition.onstart = () => {
|
|
793
|
+
isListening = true;
|
|
794
|
+
document.getElementById('voice-btn').classList.add('active');
|
|
795
|
+
document.getElementById('voice-waveform').style.display = 'flex';
|
|
796
|
+
input.placeholder = 'Listening...';
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
recognition.onresult = (event) => {
|
|
800
|
+
let interimTranscript = '';
|
|
801
|
+
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
802
|
+
const transcript = event.results[i].transcript;
|
|
803
|
+
if (event.results[i].isFinal) {
|
|
804
|
+
input.value = transcript;
|
|
805
|
+
sendCommand();
|
|
806
|
+
} else {
|
|
807
|
+
interimTranscript += transcript;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (interimTranscript) {
|
|
811
|
+
input.value = interimTranscript;
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
recognition.onerror = (event) => {
|
|
816
|
+
logTerminal(`Voice error: ${event.error}`, 'error');
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
recognition.onend = () => {
|
|
820
|
+
isListening = false;
|
|
821
|
+
document.getElementById('voice-btn').classList.remove('active');
|
|
822
|
+
document.getElementById('voice-waveform').style.display = 'none';
|
|
823
|
+
input.placeholder = 'Enter command or speak...';
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Voice Button Toggle
|
|
828
|
+
document.getElementById('voice-btn').addEventListener('click', () => {
|
|
829
|
+
if (!recognition) initVoiceRecognition();
|
|
830
|
+
if (isListening) {
|
|
831
|
+
recognition.stop();
|
|
832
|
+
} else {
|
|
833
|
+
try {
|
|
834
|
+
recognition.start();
|
|
835
|
+
} catch (e) {
|
|
836
|
+
logTerminal('Microphone access denied. Use text input instead.', 'error');
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
// Command Input
|
|
842
|
+
document.getElementById('command-input').addEventListener('keydown', (e) => {
|
|
843
|
+
if (e.key === 'Enter') {
|
|
844
|
+
sendCommand();
|
|
845
|
+
} else if (e.key === 'ArrowUp') {
|
|
846
|
+
e.preventDefault();
|
|
847
|
+
historyIndex = Math.min(historyIndex + 1, commandHistory.length - 1);
|
|
848
|
+
if (historyIndex >= 0) {
|
|
849
|
+
e.target.value = commandHistory[commandHistory.length - 1 - historyIndex];
|
|
850
|
+
}
|
|
851
|
+
} else if (e.key === 'ArrowDown') {
|
|
852
|
+
e.preventDefault();
|
|
853
|
+
historyIndex = Math.max(historyIndex - 1, -1);
|
|
854
|
+
if (historyIndex >= 0) {
|
|
855
|
+
e.target.value = commandHistory[commandHistory.length - 1 - historyIndex];
|
|
856
|
+
} else {
|
|
857
|
+
e.target.value = '';
|
|
858
|
+
}
|
|
859
|
+
} else if (e.key === 'Tab') {
|
|
860
|
+
e.preventDefault();
|
|
861
|
+
showAutocomplete(e.target.value);
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
document.getElementById('command-input').addEventListener('input', (e) => {
|
|
866
|
+
updateAutocomplete(e.target.value);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
// Autocomplete
|
|
870
|
+
function updateAutocomplete(value) {
|
|
871
|
+
const list = document.getElementById('autocomplete-list');
|
|
872
|
+
if (value.length < 2) {
|
|
873
|
+
list.classList.remove('visible');
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const commands = [
|
|
878
|
+
'create cylinder', 'create box', 'create sphere', 'create cone', 'create torus',
|
|
879
|
+
'add hole', 'add fillet', 'add chamfer', 'add pattern',
|
|
880
|
+
'sweep', 'loft', 'measure', 'section', 'undo', 'redo',
|
|
881
|
+
'copy', 'move', 'constraint', 'export stl', 'export step',
|
|
882
|
+
'help', 'history', 'clear', 'reset view', 'wireframe', 'grid'
|
|
883
|
+
];
|
|
884
|
+
|
|
885
|
+
const matches = commands.filter(cmd => cmd.includes(value.toLowerCase()));
|
|
886
|
+
|
|
887
|
+
if (matches.length > 0) {
|
|
888
|
+
list.innerHTML = matches.slice(0, 8)
|
|
889
|
+
.map(m => `<div class="autocomplete-item" onclick="selectAutocomplete('${m}')">${m}</div>`)
|
|
890
|
+
.join('');
|
|
891
|
+
list.classList.add('visible');
|
|
892
|
+
} else {
|
|
893
|
+
list.classList.remove('visible');
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function selectAutocomplete(cmd) {
|
|
898
|
+
document.getElementById('command-input').value = cmd;
|
|
899
|
+
document.getElementById('autocomplete-list').classList.remove('visible');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Command Processing
|
|
903
|
+
function sendCommand() {
|
|
904
|
+
const input = document.getElementById('command-input');
|
|
905
|
+
const command = input.value.trim();
|
|
906
|
+
|
|
907
|
+
if (!command) return;
|
|
908
|
+
|
|
909
|
+
const startTime = performance.now();
|
|
910
|
+
logTerminal(command, 'input');
|
|
911
|
+
|
|
912
|
+
commandHistory.push(command);
|
|
913
|
+
historyIndex = -1;
|
|
914
|
+
input.value = '';
|
|
915
|
+
|
|
916
|
+
processCommand(command);
|
|
917
|
+
|
|
918
|
+
const latency = Math.round(performance.now() - startTime);
|
|
919
|
+
document.getElementById('status-latency').textContent = latency + 'ms';
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function processCommand(command) {
|
|
923
|
+
const cmd = command.toLowerCase().trim();
|
|
924
|
+
|
|
925
|
+
// Create commands
|
|
926
|
+
if (cmd.startsWith('create ')) {
|
|
927
|
+
const shape = cmd.substring(7).trim();
|
|
928
|
+
createShape(shape);
|
|
929
|
+
saveHistory();
|
|
930
|
+
}
|
|
931
|
+
// Feature commands
|
|
932
|
+
else if (cmd.startsWith('add hole')) {
|
|
933
|
+
addHole(parseFloat(cmd.match(/radius\s+([\d.]+)/)?.[1] || 5));
|
|
934
|
+
saveHistory();
|
|
935
|
+
}
|
|
936
|
+
else if (cmd.startsWith('add fillet')) {
|
|
937
|
+
addFillet(parseFloat(cmd.match(/radius\s+([\d.]+)/)?.[1] || 5));
|
|
938
|
+
saveHistory();
|
|
939
|
+
}
|
|
940
|
+
else if (cmd.startsWith('add chamfer')) {
|
|
941
|
+
addChamfer(parseFloat(cmd.match(/size\s+([\d.]+)/)?.[1] || 2));
|
|
942
|
+
saveHistory();
|
|
943
|
+
}
|
|
944
|
+
else if (cmd.startsWith('add pattern')) {
|
|
945
|
+
addPattern();
|
|
946
|
+
saveHistory();
|
|
947
|
+
}
|
|
948
|
+
else if (cmd.startsWith('sweep')) {
|
|
949
|
+
logTerminal('Sweep operation: select profile and path', 'success');
|
|
950
|
+
sceneState.features.push('Sweep');
|
|
951
|
+
updateFeatureTree();
|
|
952
|
+
runDesignReview();
|
|
953
|
+
saveHistory();
|
|
954
|
+
}
|
|
955
|
+
else if (cmd.startsWith('loft')) {
|
|
956
|
+
logTerminal('Loft operation: select two profiles', 'success');
|
|
957
|
+
sceneState.features.push('Loft');
|
|
958
|
+
updateFeatureTree();
|
|
959
|
+
runManufacturingCheck();
|
|
960
|
+
saveHistory();
|
|
961
|
+
}
|
|
962
|
+
else if (cmd.startsWith('measure')) {
|
|
963
|
+
logTerminal('Measurement tool: click on two points to measure distance', 'success');
|
|
964
|
+
}
|
|
965
|
+
else if (cmd.startsWith('section')) {
|
|
966
|
+
logTerminal('Cross-section view: use arrow keys to adjust plane', 'success');
|
|
967
|
+
}
|
|
968
|
+
else if (cmd.startsWith('copy')) {
|
|
969
|
+
if (currentMesh) {
|
|
970
|
+
logTerminal('Part copied to clipboard', 'success');
|
|
971
|
+
} else {
|
|
972
|
+
logTerminal('No active part to copy', 'error');
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
else if (cmd.startsWith('move')) {
|
|
976
|
+
if (currentMesh) {
|
|
977
|
+
logTerminal('Move tool active: drag part in viewport', 'success');
|
|
978
|
+
} else {
|
|
979
|
+
logTerminal('No active part to move', 'error');
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
else if (cmd.startsWith('constraint')) {
|
|
983
|
+
if (currentMesh) {
|
|
984
|
+
logTerminal('Constraint tool: add dimensional/geometric constraints', 'success');
|
|
985
|
+
} else {
|
|
986
|
+
logTerminal('No active part for constraints', 'error');
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
else if (cmd === 'undo') {
|
|
990
|
+
undoOperation();
|
|
991
|
+
}
|
|
992
|
+
else if (cmd === 'redo') {
|
|
993
|
+
redoOperation();
|
|
994
|
+
}
|
|
995
|
+
else if (cmd === 'help') {
|
|
996
|
+
showHelp();
|
|
997
|
+
}
|
|
998
|
+
else if (cmd === 'history') {
|
|
999
|
+
showHistory();
|
|
1000
|
+
}
|
|
1001
|
+
else if (cmd.startsWith('export')) {
|
|
1002
|
+
const format = cmd.includes('step') ? 'STEP' : 'STL';
|
|
1003
|
+
if (currentMesh) {
|
|
1004
|
+
logTerminal(`Exporting as ${format}...`, 'success');
|
|
1005
|
+
logTerminal(`✓ File saved: part.${format.toLowerCase()}`, 'success');
|
|
1006
|
+
} else {
|
|
1007
|
+
logTerminal('No active part to export', 'error');
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
else if (cmd === 'clear') {
|
|
1011
|
+
document.getElementById('terminal-output').innerHTML = '';
|
|
1012
|
+
}
|
|
1013
|
+
else if (cmd.startsWith('reset')) {
|
|
1014
|
+
resetView();
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
logTerminal('Unknown command. Type "help" for assistance.', 'error');
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function createShape(shape) {
|
|
1022
|
+
// Remove previous mesh
|
|
1023
|
+
if (currentMesh) scene.remove(currentMesh);
|
|
1024
|
+
|
|
1025
|
+
let geometry;
|
|
1026
|
+
const params = parseParams(shape);
|
|
1027
|
+
|
|
1028
|
+
if (shape.includes('cylinder')) {
|
|
1029
|
+
const radius = params.diameter / 2 || 25;
|
|
1030
|
+
const height = params.height || 80;
|
|
1031
|
+
geometry = new THREE.CylinderGeometry(radius, radius, height, 32);
|
|
1032
|
+
sceneState.currentPart = 'Cylinder';
|
|
1033
|
+
} else if (shape.includes('box') || shape.includes('cube')) {
|
|
1034
|
+
const w = params.width || 100;
|
|
1035
|
+
const h = params.height || 100;
|
|
1036
|
+
const d = params.depth || 100;
|
|
1037
|
+
geometry = new THREE.BoxGeometry(w, h, d);
|
|
1038
|
+
sceneState.currentPart = 'Box';
|
|
1039
|
+
} else if (shape.includes('sphere')) {
|
|
1040
|
+
const radius = params.radius || 50;
|
|
1041
|
+
geometry = new THREE.SphereGeometry(radius, 32, 32);
|
|
1042
|
+
sceneState.currentPart = 'Sphere';
|
|
1043
|
+
} else if (shape.includes('cone')) {
|
|
1044
|
+
const radius = params.radius || 50;
|
|
1045
|
+
const height = params.height || 100;
|
|
1046
|
+
geometry = new THREE.ConeGeometry(radius, height, 32);
|
|
1047
|
+
sceneState.currentPart = 'Cone';
|
|
1048
|
+
} else if (shape.includes('torus')) {
|
|
1049
|
+
const radius = params.radius || 50;
|
|
1050
|
+
const tube = params.tube || 20;
|
|
1051
|
+
geometry = new THREE.TorusGeometry(radius, tube, 16, 100);
|
|
1052
|
+
sceneState.currentPart = 'Torus';
|
|
1053
|
+
} else {
|
|
1054
|
+
logTerminal('Unknown shape type. Try: cylinder, box, sphere, cone, torus', 'error');
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const material = new THREE.MeshStandardMaterial({
|
|
1059
|
+
color: 0x4a9eff,
|
|
1060
|
+
roughness: 0.7,
|
|
1061
|
+
metalness: 0.3
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
currentMesh = new THREE.Mesh(geometry, material);
|
|
1065
|
+
currentMesh.castShadow = true;
|
|
1066
|
+
currentMesh.receiveShadow = true;
|
|
1067
|
+
scene.add(currentMesh);
|
|
1068
|
+
|
|
1069
|
+
// Reset features
|
|
1070
|
+
sceneState.features = ['Base'];
|
|
1071
|
+
sceneState.dimensions = params;
|
|
1072
|
+
|
|
1073
|
+
logTerminal(`✓ Created ${sceneState.currentPart} ${JSON.stringify(params)}`, 'success');
|
|
1074
|
+
|
|
1075
|
+
updateFeatureTree();
|
|
1076
|
+
updateStatus();
|
|
1077
|
+
fitToObject();
|
|
1078
|
+
runDesignReview();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function parseParams(text) {
|
|
1082
|
+
const params = {};
|
|
1083
|
+
const patterns = {
|
|
1084
|
+
diameter: /diameter\s+([\d.]+)/,
|
|
1085
|
+
height: /(?:height|tall)\s+([\d.]+)/,
|
|
1086
|
+
radius: /radius\s+([\d.]+)/,
|
|
1087
|
+
width: /width\s+([\d.]+)/,
|
|
1088
|
+
depth: /depth\s+([\d.]+)/,
|
|
1089
|
+
mm: /\d+(?:mm|\.?\d+)?/
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
for (let [key, regex] of Object.entries(patterns)) {
|
|
1093
|
+
const match = text.match(regex);
|
|
1094
|
+
if (match && key !== 'mm') {
|
|
1095
|
+
params[key] = parseFloat(match[1] || match[2]);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return params;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function addHole(radius = 5) {
|
|
1103
|
+
if (!currentMesh) {
|
|
1104
|
+
logTerminal('No active part. Create something first.', 'error');
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
sceneState.features.push(`Hole (r=${radius}mm)`);
|
|
1108
|
+
logTerminal(`✓ Added hole radius ${radius}mm`, 'success');
|
|
1109
|
+
updateFeatureTree();
|
|
1110
|
+
updateStatus();
|
|
1111
|
+
runManufacturingCheck();
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function addFillet(radius = 5) {
|
|
1115
|
+
if (!currentMesh) {
|
|
1116
|
+
logTerminal('No active part. Create something first.', 'error');
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
sceneState.features.push(`Fillet (r=${radius}mm)`);
|
|
1120
|
+
logTerminal(`✓ Added fillet radius ${radius}mm`, 'success');
|
|
1121
|
+
|
|
1122
|
+
// Visual highlight edges
|
|
1123
|
+
const edges = new THREE.EdgesGeometry(currentMesh.geometry);
|
|
1124
|
+
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xff6b6b, linewidth: 2 }));
|
|
1125
|
+
currentMesh.add(line);
|
|
1126
|
+
|
|
1127
|
+
setTimeout(() => {
|
|
1128
|
+
if (currentMesh && currentMesh.children.length > 0) {
|
|
1129
|
+
currentMesh.remove(line);
|
|
1130
|
+
}
|
|
1131
|
+
}, 1500);
|
|
1132
|
+
|
|
1133
|
+
updateFeatureTree();
|
|
1134
|
+
updateStatus();
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function addChamfer(size = 2) {
|
|
1138
|
+
if (!currentMesh) {
|
|
1139
|
+
logTerminal('No active part. Create something first.', 'error');
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
sceneState.features.push(`Chamfer (${size}mm)`);
|
|
1143
|
+
logTerminal(`✓ Added chamfer ${size}mm`, 'success');
|
|
1144
|
+
updateFeatureTree();
|
|
1145
|
+
updateStatus();
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function addPattern() {
|
|
1149
|
+
if (!currentMesh) {
|
|
1150
|
+
logTerminal('No active part. Create something first.', 'error');
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
sceneState.features.push('Pattern (3×3)');
|
|
1154
|
+
logTerminal('✓ Added pattern (3×3 rectangular array)', 'success');
|
|
1155
|
+
updateFeatureTree();
|
|
1156
|
+
updateStatus();
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// History Management
|
|
1160
|
+
function saveHistory() {
|
|
1161
|
+
operationHistory.splice(historyPointer);
|
|
1162
|
+
operationHistory.push({
|
|
1163
|
+
features: JSON.parse(JSON.stringify(sceneState.features)),
|
|
1164
|
+
part: sceneState.currentPart,
|
|
1165
|
+
dimensions: JSON.parse(JSON.stringify(sceneState.dimensions))
|
|
1166
|
+
});
|
|
1167
|
+
historyPointer = operationHistory.length - 1;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function undoOperation() {
|
|
1171
|
+
if (historyPointer > 0) {
|
|
1172
|
+
historyPointer--;
|
|
1173
|
+
const state = operationHistory[historyPointer];
|
|
1174
|
+
sceneState.features = state.features;
|
|
1175
|
+
sceneState.currentPart = state.part;
|
|
1176
|
+
sceneState.dimensions = state.dimensions;
|
|
1177
|
+
const lastOp = state.features[state.features.length - 1] || 'Base';
|
|
1178
|
+
logTerminal(`↶ Undo: ${lastOp}`, 'success');
|
|
1179
|
+
updateFeatureTree();
|
|
1180
|
+
updateStatus();
|
|
1181
|
+
} else {
|
|
1182
|
+
logTerminal('Nothing to undo', 'error');
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function redoOperation() {
|
|
1187
|
+
if (historyPointer < operationHistory.length - 1) {
|
|
1188
|
+
historyPointer++;
|
|
1189
|
+
const state = operationHistory[historyPointer];
|
|
1190
|
+
sceneState.features = state.features;
|
|
1191
|
+
sceneState.currentPart = state.part;
|
|
1192
|
+
sceneState.dimensions = state.dimensions;
|
|
1193
|
+
const lastOp = state.features[state.features.length - 1] || 'Base';
|
|
1194
|
+
logTerminal(`↷ Redo: ${lastOp}`, 'success');
|
|
1195
|
+
updateFeatureTree();
|
|
1196
|
+
updateStatus();
|
|
1197
|
+
} else {
|
|
1198
|
+
logTerminal('Nothing to redo', 'error');
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// Design Review Agent
|
|
1203
|
+
function runDesignReview() {
|
|
1204
|
+
if (sceneState.features.length % 3 === 0 && sceneState.features.length > 0) {
|
|
1205
|
+
const critiques = [
|
|
1206
|
+
'Consider adding a chamfer to sharp edges for manufacturability',
|
|
1207
|
+
'Wall thickness looks good for injection molding',
|
|
1208
|
+
'Part appears well-balanced - good mass distribution',
|
|
1209
|
+
'Recommend adding draft angles for easier mold release',
|
|
1210
|
+
'Design follows best practices for 3D printing'
|
|
1211
|
+
];
|
|
1212
|
+
|
|
1213
|
+
const critique = critiques[Math.floor(Math.random() * critiques.length)];
|
|
1214
|
+
const line = document.createElement('div');
|
|
1215
|
+
line.className = 'terminal-line agent';
|
|
1216
|
+
line.innerHTML = `<span class="agent-badge review">R</span><div class="agent-content"><strong>Design Review:</strong> ${critique}</div>`;
|
|
1217
|
+
document.getElementById('terminal-output').appendChild(line);
|
|
1218
|
+
document.getElementById('terminal-output').scrollTop = document.getElementById('terminal-output').scrollHeight;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Manufacturing Agent
|
|
1223
|
+
function runManufacturingCheck() {
|
|
1224
|
+
if (sceneState.features.some(f => f.includes('Hole'))) {
|
|
1225
|
+
const suggestions = [
|
|
1226
|
+
'Hole depth within tolerance (✓ 3mm min)',
|
|
1227
|
+
'Drilling sequence optimized',
|
|
1228
|
+
'Tool change required for different hole sizes'
|
|
1229
|
+
];
|
|
1230
|
+
|
|
1231
|
+
const suggestion = suggestions[Math.floor(Math.random() * suggestions.length)];
|
|
1232
|
+
const line = document.createElement('div');
|
|
1233
|
+
line.className = 'terminal-line agent';
|
|
1234
|
+
line.innerHTML = `<span class="agent-badge manufacturing">M</span><div class="agent-content"><strong>Mfg Check:</strong> ${suggestion}</div>`;
|
|
1235
|
+
document.getElementById('terminal-output').appendChild(line);
|
|
1236
|
+
document.getElementById('terminal-output').scrollTop = document.getElementById('terminal-output').scrollHeight;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// UI Functions
|
|
1241
|
+
function logTerminal(text, type = 'output') {
|
|
1242
|
+
const output = document.getElementById('terminal-output');
|
|
1243
|
+
const line = document.createElement('div');
|
|
1244
|
+
line.className = `terminal-line ${type}`;
|
|
1245
|
+
line.textContent = text;
|
|
1246
|
+
|
|
1247
|
+
output.appendChild(line);
|
|
1248
|
+
output.scrollTop = output.scrollHeight;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function updateFeatureTree() {
|
|
1252
|
+
const tree = document.getElementById('feature-tree-list');
|
|
1253
|
+
if (sceneState.features.length === 0) {
|
|
1254
|
+
tree.innerHTML = '<div style="padding: 12px; color: #808090; text-align: center; font-size: 11px;">No features</div>';
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
const icons = {
|
|
1259
|
+
'Base': '📦',
|
|
1260
|
+
'Hole': '⭕',
|
|
1261
|
+
'Fillet': '⌬',
|
|
1262
|
+
'Chamfer': '⬢',
|
|
1263
|
+
'Pattern': '🔲',
|
|
1264
|
+
'Sweep': '➡️',
|
|
1265
|
+
'Loft': '≈',
|
|
1266
|
+
'default': '▪️'
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
tree.innerHTML = sceneState.features
|
|
1270
|
+
.map((f, i) => {
|
|
1271
|
+
const icon = Object.keys(icons).find(k => f.includes(k)) || 'default';
|
|
1272
|
+
return `<div class="tree-item ${i === sceneState.features.length - 1 ? 'active' : ''}" onclick="selectFeature(${i})"><span class="tree-item-icon">${icons[icon]}</span>${f}</div>`;
|
|
1273
|
+
})
|
|
1274
|
+
.join('');
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function selectFeature(index) {
|
|
1278
|
+
// Highlight feature
|
|
1279
|
+
const items = document.querySelectorAll('.tree-item');
|
|
1280
|
+
items.forEach(item => item.classList.remove('active'));
|
|
1281
|
+
items[index]?.classList.add('active');
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function updateStatus() {
|
|
1285
|
+
document.getElementById('status-part').textContent = sceneState.currentPart || 'None';
|
|
1286
|
+
document.getElementById('status-features').textContent = sceneState.features.length;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function toggleFeatureTree() {
|
|
1290
|
+
document.querySelector('.feature-tree').classList.toggle('collapsed');
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// Viewport Functions
|
|
1294
|
+
function resetView() {
|
|
1295
|
+
camera.position.set(200, 150, 200);
|
|
1296
|
+
controls.target.set(0, 0, 0);
|
|
1297
|
+
controls.update();
|
|
1298
|
+
logTerminal('✓ Camera reset', 'success');
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function fitToObject() {
|
|
1302
|
+
if (!currentMesh) return;
|
|
1303
|
+
|
|
1304
|
+
const box = new THREE.Box3().setFromObject(currentMesh);
|
|
1305
|
+
const size = box.getSize(new THREE.Vector3());
|
|
1306
|
+
const maxDim = Math.max(size.x, size.y, size.z);
|
|
1307
|
+
const fov = camera.fov * (Math.PI / 180);
|
|
1308
|
+
let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));
|
|
1309
|
+
|
|
1310
|
+
cameraZ *= 1.5;
|
|
1311
|
+
|
|
1312
|
+
camera.position.set(cameraZ, cameraZ * 0.7, cameraZ);
|
|
1313
|
+
camera.lookAt(currentMesh.position);
|
|
1314
|
+
controls.target.copy(currentMesh.position);
|
|
1315
|
+
controls.update();
|
|
1316
|
+
logTerminal('✓ Fit to view', 'success');
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
function toggleWireframe() {
|
|
1320
|
+
wireframeMode = !wireframeMode;
|
|
1321
|
+
if (currentMesh) {
|
|
1322
|
+
currentMesh.material.wireframe = wireframeMode;
|
|
1323
|
+
}
|
|
1324
|
+
document.getElementById('wireframe-btn').classList.toggle('active');
|
|
1325
|
+
logTerminal(wireframeMode ? '✓ Wireframe enabled' : '✓ Wireframe disabled', 'success');
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function toggleGrid() {
|
|
1329
|
+
gridVisible = !gridVisible;
|
|
1330
|
+
gridHelper.visible = gridVisible;
|
|
1331
|
+
document.getElementById('grid-btn').classList.toggle('active');
|
|
1332
|
+
logTerminal(gridVisible ? '✓ Grid enabled' : '✓ Grid disabled', 'success');
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function toggleShadows() {
|
|
1336
|
+
shadowsVisible = !shadowsVisible;
|
|
1337
|
+
renderer.shadowMap.enabled = shadowsVisible;
|
|
1338
|
+
if (currentMesh) currentMesh.castShadow = shadowsVisible;
|
|
1339
|
+
shadowPlane.visible = shadowsVisible;
|
|
1340
|
+
document.getElementById('shadows-btn').classList.toggle('active');
|
|
1341
|
+
logTerminal(shadowsVisible ? '✓ Shadows enabled' : '✓ Shadows disabled', 'success');
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function showHelp() {
|
|
1345
|
+
logTerminal('=== cycleCAD Agent Commands ===', 'output');
|
|
1346
|
+
logTerminal('', 'output');
|
|
1347
|
+
logTerminal('CREATE SHAPES:', 'output');
|
|
1348
|
+
logTerminal(' create cylinder diameter=50 height=80', 'output');
|
|
1349
|
+
logTerminal(' create box width=100 height=100 depth=100', 'output');
|
|
1350
|
+
logTerminal(' create sphere radius=50', 'output');
|
|
1351
|
+
logTerminal(' create cone radius=50 height=100', 'output');
|
|
1352
|
+
logTerminal(' create torus radius=50 tube=20', 'output');
|
|
1353
|
+
logTerminal('', 'output');
|
|
1354
|
+
logTerminal('MODIFY FEATURES:', 'output');
|
|
1355
|
+
logTerminal(' add hole radius=10', 'output');
|
|
1356
|
+
logTerminal(' add fillet radius=5', 'output');
|
|
1357
|
+
logTerminal(' add chamfer size=2', 'output');
|
|
1358
|
+
logTerminal(' add pattern', 'output');
|
|
1359
|
+
logTerminal('', 'output');
|
|
1360
|
+
logTerminal('ADVANCED OPERATIONS:', 'output');
|
|
1361
|
+
logTerminal(' sweep | loft | measure | section', 'output');
|
|
1362
|
+
logTerminal(' copy | move | constraint', 'output');
|
|
1363
|
+
logTerminal('', 'output');
|
|
1364
|
+
logTerminal('VIEWPORT CONTROL:', 'output');
|
|
1365
|
+
logTerminal(' reset view | wireframe | grid | shadows', 'output');
|
|
1366
|
+
logTerminal('', 'output');
|
|
1367
|
+
logTerminal('HISTORY & EXPORT:', 'output');
|
|
1368
|
+
logTerminal(' undo | redo | history', 'output');
|
|
1369
|
+
logTerminal(' export stl | export step', 'output');
|
|
1370
|
+
logTerminal('', 'output');
|
|
1371
|
+
logTerminal('KEYBOARD: ↑/↓ = history, Ctrl+Z/Y = undo/redo, ? = help', 'output');
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function showHistory() {
|
|
1375
|
+
logTerminal('=== Command History ===', 'output');
|
|
1376
|
+
if (commandHistory.length === 0) {
|
|
1377
|
+
logTerminal('(empty)', 'output');
|
|
1378
|
+
} else {
|
|
1379
|
+
commandHistory.forEach((c, i) => {
|
|
1380
|
+
logTerminal(` ${i + 1}. ${c}`, 'output');
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function showExamples() {
|
|
1386
|
+
const examples = [
|
|
1387
|
+
'create cylinder diameter=50 height=80',
|
|
1388
|
+
'add hole radius 10',
|
|
1389
|
+
'add fillet radius 5',
|
|
1390
|
+
'add chamfer size 2',
|
|
1391
|
+
'add pattern',
|
|
1392
|
+
'sweep',
|
|
1393
|
+
'loft',
|
|
1394
|
+
'export stl',
|
|
1395
|
+
'help'
|
|
1396
|
+
];
|
|
1397
|
+
|
|
1398
|
+
logTerminal('=== Example Commands ===', 'output');
|
|
1399
|
+
examples.forEach(ex => {
|
|
1400
|
+
logTerminal(` ${ex}`, 'output');
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// Resizable Split Pane
|
|
1405
|
+
const resizeHandle = document.getElementById('resize-handle');
|
|
1406
|
+
const splitPane = document.getElementById('split-pane');
|
|
1407
|
+
let isResizing = false;
|
|
1408
|
+
|
|
1409
|
+
resizeHandle.addEventListener('mousedown', () => {
|
|
1410
|
+
isResizing = true;
|
|
1029
1411
|
});
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
1412
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1413
|
+
document.addEventListener('mousemove', (e) => {
|
|
1414
|
+
if (!isResizing) return;
|
|
1415
|
+
|
|
1416
|
+
const rect = splitPane.getBoundingClientRect();
|
|
1417
|
+
const newWidth = ((e.clientX - rect.left) / rect.width) * 100;
|
|
1037
1418
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1419
|
+
if (newWidth > 20 && newWidth < 80) {
|
|
1420
|
+
splitPane.children[0].style.width = newWidth + '%';
|
|
1421
|
+
splitPane.children[2].style.width = (100 - newWidth) + '%';
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1040
1424
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1425
|
+
document.addEventListener('mouseup', () => {
|
|
1426
|
+
isResizing = false;
|
|
1427
|
+
});
|
|
1043
1428
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1429
|
+
// Session Timer
|
|
1430
|
+
const startTime = Date.now();
|
|
1431
|
+
setInterval(() => {
|
|
1432
|
+
const elapsed = Date.now() - startTime;
|
|
1433
|
+
const minutes = Math.floor(elapsed / 60000);
|
|
1434
|
+
const seconds = Math.floor((elapsed % 60000) / 1000);
|
|
1435
|
+
document.getElementById('session-time').textContent =
|
|
1436
|
+
`Session: ${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
|
1437
|
+
}, 1000);
|
|
1438
|
+
|
|
1439
|
+
// Keyboard Shortcuts
|
|
1440
|
+
document.addEventListener('keydown', (e) => {
|
|
1441
|
+
if (e.ctrlKey && e.key === 'z') {
|
|
1442
|
+
e.preventDefault();
|
|
1443
|
+
undoOperation();
|
|
1444
|
+
} else if (e.ctrlKey && e.key === 'y') {
|
|
1445
|
+
e.preventDefault();
|
|
1446
|
+
redoOperation();
|
|
1447
|
+
} else if (e.key === '?') {
|
|
1448
|
+
e.preventDefault();
|
|
1449
|
+
showHelp();
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
// Initialize
|
|
1454
|
+
window.addEventListener('load', () => {
|
|
1455
|
+
initThreeJS();
|
|
1456
|
+
initVoiceRecognition();
|
|
1457
|
+
document.getElementById('command-input').focus();
|
|
1458
|
+
logTerminal('✓ Agent Demo Ready', 'success');
|
|
1459
|
+
logTerminal('Try: "create cylinder diameter=50 height=80"', 'output');
|
|
1460
|
+
});
|
|
1461
|
+
</script>
|
|
1048
1462
|
</body>
|
|
1049
1463
|
</html>
|