cyclecad 0.9.6 → 0.9.7

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.
@@ -0,0 +1,2178 @@
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>ExplodeView Integration in cycleCAD</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ /* CSS Variables */
10
+ :root {
11
+ --bg-dark: #1e1e1e;
12
+ --bg-darker: #0d0d0d;
13
+ --bg-card: #252525;
14
+ --bg-hover: #2d2d2d;
15
+ --border-color: #3a3a3a;
16
+ --text-primary: #e8e8e8;
17
+ --text-secondary: #b4b4b4;
18
+ --text-tertiary: #808080;
19
+ --accent-blue: #3b82f6;
20
+ --accent-green: #10b981;
21
+ --accent-orange: #f59e0b;
22
+ --accent-red: #ef4444;
23
+ --accent-purple: #8b5cf6;
24
+ --accent-cyan: #06b6d4;
25
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
26
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
27
+ --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
28
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
29
+ }
30
+
31
+ * {
32
+ margin: 0;
33
+ padding: 0;
34
+ box-sizing: border-box;
35
+ }
36
+
37
+ html {
38
+ scroll-behavior: smooth;
39
+ }
40
+
41
+ body {
42
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
43
+ background-color: var(--bg-darker);
44
+ color: var(--text-primary);
45
+ line-height: 1.6;
46
+ }
47
+
48
+ /* Navigation Bar */
49
+ .navbar {
50
+ position: sticky;
51
+ top: 0;
52
+ z-index: 100;
53
+ background-color: var(--bg-dark);
54
+ border-bottom: 1px solid var(--border-color);
55
+ padding: 1rem 2rem;
56
+ backdrop-filter: blur(10px);
57
+ box-shadow: var(--shadow-sm);
58
+ }
59
+
60
+ .navbar-content {
61
+ max-width: 1400px;
62
+ margin: 0 auto;
63
+ display: flex;
64
+ justify-content: space-between;
65
+ align-items: center;
66
+ }
67
+
68
+ .breadcrumb {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 0.5rem;
72
+ font-size: 0.875rem;
73
+ color: var(--text-secondary);
74
+ }
75
+
76
+ .breadcrumb a {
77
+ color: var(--accent-blue);
78
+ text-decoration: none;
79
+ transition: var(--transition);
80
+ }
81
+
82
+ .breadcrumb a:hover {
83
+ color: #60a5fa;
84
+ }
85
+
86
+ .breadcrumb-sep {
87
+ color: var(--text-tertiary);
88
+ }
89
+
90
+ .nav-right {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 1.5rem;
94
+ }
95
+
96
+ .level-badge {
97
+ background-color: var(--accent-purple);
98
+ color: white;
99
+ padding: 0.375rem 0.75rem;
100
+ border-radius: 0.375rem;
101
+ font-size: 0.75rem;
102
+ font-weight: 600;
103
+ text-transform: uppercase;
104
+ letter-spacing: 0.05em;
105
+ }
106
+
107
+ .back-link {
108
+ color: var(--text-secondary);
109
+ text-decoration: none;
110
+ font-size: 0.875rem;
111
+ transition: var(--transition);
112
+ }
113
+
114
+ .back-link:hover {
115
+ color: var(--accent-blue);
116
+ }
117
+
118
+ /* Container */
119
+ .container {
120
+ max-width: 1400px;
121
+ margin: 0 auto;
122
+ padding: 0 2rem;
123
+ }
124
+
125
+ /* Layout */
126
+ .layout {
127
+ display: grid;
128
+ grid-template-columns: 280px 1fr;
129
+ gap: 3rem;
130
+ padding: 3rem 2rem;
131
+ }
132
+
133
+ /* Sidebar TOC */
134
+ .sidebar {
135
+ position: sticky;
136
+ top: 80px;
137
+ height: calc(100vh - 100px);
138
+ overflow-y: auto;
139
+ padding: 1.5rem;
140
+ background-color: var(--bg-card);
141
+ border: 1px solid var(--border-color);
142
+ border-radius: 0.75rem;
143
+ }
144
+
145
+ .sidebar h3 {
146
+ font-size: 0.75rem;
147
+ font-weight: 700;
148
+ text-transform: uppercase;
149
+ letter-spacing: 0.1em;
150
+ color: var(--text-tertiary);
151
+ margin-bottom: 1rem;
152
+ }
153
+
154
+ .toc-list {
155
+ list-style: none;
156
+ }
157
+
158
+ .toc-item {
159
+ margin-bottom: 0.5rem;
160
+ }
161
+
162
+ .toc-link {
163
+ display: block;
164
+ padding: 0.5rem 0.75rem;
165
+ color: var(--text-secondary);
166
+ text-decoration: none;
167
+ font-size: 0.875rem;
168
+ border-left: 2px solid transparent;
169
+ transition: var(--transition);
170
+ }
171
+
172
+ .toc-link:hover,
173
+ .toc-link.active {
174
+ color: var(--accent-blue);
175
+ border-left-color: var(--accent-blue);
176
+ padding-left: 1rem;
177
+ }
178
+
179
+ .toc-sub {
180
+ list-style: none;
181
+ margin-top: 0.5rem;
182
+ padding-left: 1rem;
183
+ border-left: 1px solid var(--border-color);
184
+ }
185
+
186
+ .toc-sub .toc-link {
187
+ padding: 0.375rem 0.5rem;
188
+ font-size: 0.8125rem;
189
+ color: var(--text-tertiary);
190
+ }
191
+
192
+ /* Main Content */
193
+ .main {
194
+ min-height: 100vh;
195
+ }
196
+
197
+ /* Hero Section */
198
+ .hero {
199
+ margin-bottom: 4rem;
200
+ padding: 4rem 2rem;
201
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%);
202
+ border: 1px solid var(--border-color);
203
+ border-radius: 1rem;
204
+ }
205
+
206
+ .hero h1 {
207
+ font-size: 2.5rem;
208
+ font-weight: 700;
209
+ margin-bottom: 0.5rem;
210
+ background: linear-gradient(135deg, var(--accent-blue) 0%, var(--accent-cyan) 100%);
211
+ -webkit-background-clip: text;
212
+ -webkit-text-fill-color: transparent;
213
+ background-clip: text;
214
+ }
215
+
216
+ .hero p {
217
+ font-size: 1.125rem;
218
+ color: var(--text-secondary);
219
+ margin-bottom: 2rem;
220
+ }
221
+
222
+ .hero-stats {
223
+ display: grid;
224
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
225
+ gap: 1rem;
226
+ }
227
+
228
+ .stat-card {
229
+ background-color: var(--bg-card);
230
+ border: 1px solid var(--border-color);
231
+ padding: 1.5rem;
232
+ border-radius: 0.75rem;
233
+ text-align: center;
234
+ }
235
+
236
+ .stat-value {
237
+ font-size: 2rem;
238
+ font-weight: 700;
239
+ color: var(--accent-blue);
240
+ display: block;
241
+ }
242
+
243
+ .stat-label {
244
+ font-size: 0.875rem;
245
+ color: var(--text-secondary);
246
+ margin-top: 0.5rem;
247
+ }
248
+
249
+ /* Sections */
250
+ section {
251
+ margin-bottom: 5rem;
252
+ scroll-margin-top: 100px;
253
+ }
254
+
255
+ section h2 {
256
+ font-size: 2rem;
257
+ font-weight: 700;
258
+ margin-bottom: 1rem;
259
+ padding-bottom: 0.5rem;
260
+ border-bottom: 2px solid var(--accent-blue);
261
+ }
262
+
263
+ section h3 {
264
+ font-size: 1.25rem;
265
+ font-weight: 600;
266
+ margin: 2rem 0 1rem 0;
267
+ color: var(--accent-blue);
268
+ }
269
+
270
+ section h4 {
271
+ font-size: 1rem;
272
+ font-weight: 600;
273
+ margin: 1.5rem 0 0.75rem 0;
274
+ color: var(--text-primary);
275
+ }
276
+
277
+ section p {
278
+ margin-bottom: 1rem;
279
+ color: var(--text-secondary);
280
+ line-height: 1.8;
281
+ }
282
+
283
+ /* Code Blocks */
284
+ .code-block {
285
+ background-color: var(--bg-darker);
286
+ border: 1px solid var(--border-color);
287
+ border-radius: 0.5rem;
288
+ padding: 1.5rem;
289
+ margin: 1.5rem 0;
290
+ overflow-x: auto;
291
+ font-family: 'Courier New', monospace;
292
+ font-size: 0.875rem;
293
+ line-height: 1.5;
294
+ }
295
+
296
+ .code-inline {
297
+ background-color: var(--bg-card);
298
+ padding: 0.25rem 0.5rem;
299
+ border-radius: 0.25rem;
300
+ font-family: 'Courier New', monospace;
301
+ font-size: 0.875rem;
302
+ color: var(--accent-cyan);
303
+ }
304
+
305
+ /* Animated Diagram */
306
+ .diagram {
307
+ background-color: var(--bg-card);
308
+ border: 1px solid var(--border-color);
309
+ border-radius: 0.75rem;
310
+ padding: 3rem 2rem;
311
+ margin: 2rem 0;
312
+ overflow-x: auto;
313
+ min-height: 300px;
314
+ display: flex;
315
+ align-items: center;
316
+ justify-content: center;
317
+ }
318
+
319
+ .diagram-container {
320
+ display: flex;
321
+ align-items: center;
322
+ justify-content: space-between;
323
+ gap: 2rem;
324
+ width: 100%;
325
+ min-width: 600px;
326
+ }
327
+
328
+ .diagram-box {
329
+ background-color: var(--bg-darker);
330
+ border: 2px solid var(--border-color);
331
+ border-radius: 0.75rem;
332
+ padding: 2rem;
333
+ flex: 1;
334
+ text-align: center;
335
+ min-width: 150px;
336
+ }
337
+
338
+ .diagram-box h4 {
339
+ margin-top: 0;
340
+ color: var(--accent-blue);
341
+ font-size: 1rem;
342
+ }
343
+
344
+ .diagram-box p {
345
+ font-size: 0.875rem;
346
+ color: var(--text-tertiary);
347
+ margin: 0.5rem 0 0 0;
348
+ }
349
+
350
+ .diagram-arrow {
351
+ font-size: 2rem;
352
+ color: var(--accent-blue);
353
+ animation: bounce 2s infinite;
354
+ }
355
+
356
+ @keyframes bounce {
357
+ 0%, 100% { transform: translateX(0); }
358
+ 50% { transform: translateX(10px); }
359
+ }
360
+
361
+ /* Cards Grid */
362
+ .cards-grid {
363
+ display: grid;
364
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
365
+ gap: 1.5rem;
366
+ margin: 2rem 0;
367
+ }
368
+
369
+ .card {
370
+ background-color: var(--bg-card);
371
+ border: 1px solid var(--border-color);
372
+ border-radius: 0.75rem;
373
+ padding: 1.5rem;
374
+ transition: var(--transition);
375
+ }
376
+
377
+ .card:hover {
378
+ border-color: var(--accent-blue);
379
+ background-color: var(--bg-hover);
380
+ transform: translateY(-2px);
381
+ box-shadow: var(--shadow-md);
382
+ }
383
+
384
+ .card h3 {
385
+ margin-top: 0;
386
+ color: var(--accent-blue);
387
+ font-size: 1.125rem;
388
+ }
389
+
390
+ .card p {
391
+ font-size: 0.875rem;
392
+ margin-bottom: 1rem;
393
+ }
394
+
395
+ /* Feature Cards */
396
+ .feature-card {
397
+ background-color: var(--bg-card);
398
+ border-left: 4px solid var(--accent-blue);
399
+ border-radius: 0.5rem;
400
+ padding: 1.5rem;
401
+ margin: 1.5rem 0;
402
+ }
403
+
404
+ .feature-header {
405
+ display: flex;
406
+ justify-content: space-between;
407
+ align-items: flex-start;
408
+ margin-bottom: 1rem;
409
+ }
410
+
411
+ .feature-title {
412
+ font-size: 1.125rem;
413
+ font-weight: 600;
414
+ color: var(--text-primary);
415
+ }
416
+
417
+ .feature-badge {
418
+ display: inline-block;
419
+ padding: 0.375rem 0.75rem;
420
+ background-color: var(--accent-green);
421
+ color: white;
422
+ font-size: 0.7rem;
423
+ font-weight: 600;
424
+ border-radius: 0.25rem;
425
+ text-transform: uppercase;
426
+ }
427
+
428
+ .feature-badge.in-progress {
429
+ background-color: var(--accent-orange);
430
+ }
431
+
432
+ .feature-badge.planned {
433
+ background-color: var(--accent-red);
434
+ }
435
+
436
+ .feature-comparison {
437
+ display: grid;
438
+ grid-template-columns: 1fr 1fr;
439
+ gap: 1rem;
440
+ margin: 1rem 0;
441
+ }
442
+
443
+ .comparison-col {
444
+ font-size: 0.875rem;
445
+ }
446
+
447
+ .comparison-col h5 {
448
+ font-weight: 600;
449
+ color: var(--text-secondary);
450
+ margin-bottom: 0.5rem;
451
+ font-size: 0.75rem;
452
+ text-transform: uppercase;
453
+ }
454
+
455
+ /* Table */
456
+ .table-wrapper {
457
+ overflow-x: auto;
458
+ margin: 2rem 0;
459
+ }
460
+
461
+ table {
462
+ width: 100%;
463
+ border-collapse: collapse;
464
+ font-size: 0.875rem;
465
+ }
466
+
467
+ th {
468
+ background-color: var(--bg-darker);
469
+ border-bottom: 2px solid var(--border-color);
470
+ padding: 1rem;
471
+ text-align: left;
472
+ font-weight: 600;
473
+ color: var(--accent-blue);
474
+ }
475
+
476
+ td {
477
+ border-bottom: 1px solid var(--border-color);
478
+ padding: 1rem;
479
+ color: var(--text-secondary);
480
+ }
481
+
482
+ tr:hover {
483
+ background-color: var(--bg-hover);
484
+ }
485
+
486
+ .status-ported {
487
+ color: var(--accent-green);
488
+ font-weight: 600;
489
+ }
490
+
491
+ .status-in-progress {
492
+ color: var(--accent-orange);
493
+ font-weight: 600;
494
+ }
495
+
496
+ .status-planned {
497
+ color: var(--accent-red);
498
+ font-weight: 600;
499
+ }
500
+
501
+ /* Filter Buttons */
502
+ .filter-buttons {
503
+ display: flex;
504
+ flex-wrap: wrap;
505
+ gap: 0.75rem;
506
+ margin: 1.5rem 0;
507
+ }
508
+
509
+ .filter-btn {
510
+ padding: 0.5rem 1rem;
511
+ background-color: var(--bg-card);
512
+ border: 1px solid var(--border-color);
513
+ color: var(--text-secondary);
514
+ border-radius: 0.375rem;
515
+ cursor: pointer;
516
+ font-size: 0.875rem;
517
+ font-weight: 500;
518
+ transition: var(--transition);
519
+ }
520
+
521
+ .filter-btn:hover,
522
+ .filter-btn.active {
523
+ background-color: var(--accent-blue);
524
+ border-color: var(--accent-blue);
525
+ color: white;
526
+ }
527
+
528
+ /* Mode Toggle */
529
+ .mode-toggle {
530
+ display: grid;
531
+ grid-template-columns: 1fr 1fr;
532
+ gap: 2rem;
533
+ margin: 2rem 0;
534
+ }
535
+
536
+ .mode-box {
537
+ background-color: var(--bg-card);
538
+ border: 2px solid var(--border-color);
539
+ border-radius: 0.75rem;
540
+ padding: 2rem;
541
+ text-align: center;
542
+ transition: var(--transition);
543
+ }
544
+
545
+ .mode-box.active {
546
+ border-color: var(--accent-blue);
547
+ background-color: var(--bg-hover);
548
+ box-shadow: 0 0 20px rgba(59, 130, 246, 0.2);
549
+ }
550
+
551
+ .mode-icon {
552
+ font-size: 3rem;
553
+ margin-bottom: 1rem;
554
+ }
555
+
556
+ .mode-title {
557
+ font-size: 1.25rem;
558
+ font-weight: 600;
559
+ margin-bottom: 0.75rem;
560
+ }
561
+
562
+ .mode-description {
563
+ font-size: 0.875rem;
564
+ color: var(--text-tertiary);
565
+ }
566
+
567
+ /* Timeline */
568
+ .timeline {
569
+ position: relative;
570
+ padding: 2rem 0;
571
+ }
572
+
573
+ .timeline-item {
574
+ display: grid;
575
+ grid-template-columns: 200px 1fr;
576
+ gap: 2rem;
577
+ margin-bottom: 2rem;
578
+ position: relative;
579
+ }
580
+
581
+ .timeline-item:not(:last-child)::after {
582
+ content: '';
583
+ position: absolute;
584
+ left: 100px;
585
+ top: 50px;
586
+ width: 2px;
587
+ height: calc(100% + 2rem);
588
+ background-color: var(--border-color);
589
+ }
590
+
591
+ .timeline-date {
592
+ font-weight: 600;
593
+ color: var(--accent-blue);
594
+ position: relative;
595
+ padding-top: 0.5rem;
596
+ }
597
+
598
+ .timeline-date::before {
599
+ content: '';
600
+ position: absolute;
601
+ left: -125px;
602
+ top: 0;
603
+ width: 12px;
604
+ height: 12px;
605
+ background-color: var(--accent-blue);
606
+ border: 3px solid var(--bg-darker);
607
+ border-radius: 50%;
608
+ }
609
+
610
+ .timeline-content {
611
+ background-color: var(--bg-card);
612
+ border: 1px solid var(--border-color);
613
+ border-radius: 0.75rem;
614
+ padding: 1.5rem;
615
+ }
616
+
617
+ /* API Reference */
618
+ .api-method {
619
+ background-color: var(--bg-darker);
620
+ border-left: 4px solid var(--accent-cyan);
621
+ border-radius: 0.5rem;
622
+ padding: 1.5rem;
623
+ margin: 1rem 0;
624
+ font-family: 'Courier New', monospace;
625
+ font-size: 0.875rem;
626
+ }
627
+
628
+ .api-method h5 {
629
+ color: var(--accent-cyan);
630
+ margin-top: 0;
631
+ font-size: 1rem;
632
+ }
633
+
634
+ .api-params {
635
+ margin-top: 1rem;
636
+ font-size: 0.8125rem;
637
+ color: var(--text-tertiary);
638
+ }
639
+
640
+ /* Shortcuts Table */
641
+ .shortcuts-grid {
642
+ display: grid;
643
+ grid-template-columns: 1fr 1fr;
644
+ gap: 2rem;
645
+ margin: 2rem 0;
646
+ }
647
+
648
+ .shortcuts-col h4 {
649
+ margin-top: 0;
650
+ color: var(--accent-blue);
651
+ font-size: 1.125rem;
652
+ }
653
+
654
+ .shortcut-item {
655
+ display: grid;
656
+ grid-template-columns: 100px 1fr;
657
+ gap: 1rem;
658
+ margin-bottom: 1rem;
659
+ padding-bottom: 1rem;
660
+ border-bottom: 1px solid var(--border-color);
661
+ }
662
+
663
+ .shortcut-key {
664
+ background-color: var(--bg-darker);
665
+ border: 1px solid var(--border-color);
666
+ border-radius: 0.375rem;
667
+ padding: 0.375rem 0.75rem;
668
+ font-family: 'Courier New', monospace;
669
+ font-size: 0.8125rem;
670
+ font-weight: 600;
671
+ color: var(--accent-cyan);
672
+ text-align: center;
673
+ }
674
+
675
+ .shortcut-desc {
676
+ font-size: 0.875rem;
677
+ color: var(--text-secondary);
678
+ display: flex;
679
+ align-items: center;
680
+ }
681
+
682
+ /* Docker Cards */
683
+ .docker-grid {
684
+ display: grid;
685
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
686
+ gap: 1.5rem;
687
+ margin: 2rem 0;
688
+ }
689
+
690
+ .docker-card {
691
+ background-color: var(--bg-card);
692
+ border: 2px solid var(--border-color);
693
+ border-radius: 0.75rem;
694
+ padding: 2rem;
695
+ transition: var(--transition);
696
+ }
697
+
698
+ .docker-card:hover {
699
+ border-color: var(--accent-blue);
700
+ box-shadow: var(--shadow-md);
701
+ }
702
+
703
+ .docker-icon {
704
+ font-size: 2.5rem;
705
+ margin-bottom: 1rem;
706
+ }
707
+
708
+ .docker-title {
709
+ font-size: 1.125rem;
710
+ font-weight: 600;
711
+ margin-bottom: 0.75rem;
712
+ }
713
+
714
+ .docker-port {
715
+ background-color: var(--bg-darker);
716
+ border: 1px solid var(--border-color);
717
+ padding: 0.5rem 0.75rem;
718
+ border-radius: 0.375rem;
719
+ font-family: 'Courier New', monospace;
720
+ font-size: 0.8125rem;
721
+ color: var(--accent-cyan);
722
+ display: inline-block;
723
+ margin-top: 1rem;
724
+ }
725
+
726
+ /* Scroll bar styling */
727
+ ::-webkit-scrollbar {
728
+ width: 8px;
729
+ height: 8px;
730
+ }
731
+
732
+ ::-webkit-scrollbar-track {
733
+ background-color: var(--bg-darker);
734
+ }
735
+
736
+ ::-webkit-scrollbar-thumb {
737
+ background-color: var(--border-color);
738
+ border-radius: 4px;
739
+ }
740
+
741
+ ::-webkit-scrollbar-thumb:hover {
742
+ background-color: var(--text-tertiary);
743
+ }
744
+
745
+ /* Responsive */
746
+ @media (max-width: 1024px) {
747
+ .layout {
748
+ grid-template-columns: 1fr;
749
+ gap: 2rem;
750
+ }
751
+
752
+ .sidebar {
753
+ position: relative;
754
+ top: 0;
755
+ height: auto;
756
+ max-height: 300px;
757
+ }
758
+
759
+ .diagram-container {
760
+ flex-direction: column;
761
+ min-width: auto;
762
+ }
763
+
764
+ .mode-toggle {
765
+ grid-template-columns: 1fr;
766
+ }
767
+
768
+ .feature-comparison {
769
+ grid-template-columns: 1fr;
770
+ }
771
+
772
+ .shortcuts-grid {
773
+ grid-template-columns: 1fr;
774
+ }
775
+
776
+ .timeline-item {
777
+ grid-template-columns: 1fr;
778
+ }
779
+
780
+ .timeline-item::after {
781
+ left: 0;
782
+ }
783
+
784
+ .timeline-date::before {
785
+ left: -25px;
786
+ }
787
+ }
788
+
789
+ @media (max-width: 768px) {
790
+ .navbar-content {
791
+ flex-direction: column;
792
+ gap: 1rem;
793
+ }
794
+
795
+ .hero {
796
+ padding: 2rem 1rem;
797
+ }
798
+
799
+ .hero h1 {
800
+ font-size: 1.75rem;
801
+ }
802
+
803
+ .layout {
804
+ padding: 2rem 1rem;
805
+ }
806
+
807
+ section h2 {
808
+ font-size: 1.5rem;
809
+ }
810
+
811
+ .hero-stats {
812
+ grid-template-columns: 1fr;
813
+ }
814
+ }
815
+
816
+ /* Print styles */
817
+ @media print {
818
+ .navbar,
819
+ .sidebar {
820
+ display: none;
821
+ }
822
+
823
+ .layout {
824
+ grid-template-columns: 1fr;
825
+ }
826
+
827
+ .diagram,
828
+ .code-block {
829
+ page-break-inside: avoid;
830
+ }
831
+
832
+ section {
833
+ page-break-inside: avoid;
834
+ }
835
+ }
836
+ </style>
837
+ </head>
838
+ <body>
839
+ <!-- Navigation Bar -->
840
+ <nav class="navbar">
841
+ <div class="navbar-content">
842
+ <div class="breadcrumb">
843
+ <a href="/app/">cycleCAD</a>
844
+ <span class="breadcrumb-sep">/</span>
845
+ <a href="/app/tutorials/">Tutorials</a>
846
+ <span class="breadcrumb-sep">/</span>
847
+ <span>ExplodeView Integration</span>
848
+ </div>
849
+ <div class="nav-right">
850
+ <span class="level-badge">Architecture</span>
851
+ <a href="/app/" class="back-link">← Back to App</a>
852
+ </div>
853
+ </div>
854
+ </nav>
855
+
856
+ <!-- Main Content -->
857
+ <div class="container">
858
+ <div class="layout">
859
+ <!-- Sidebar TOC -->
860
+ <aside class="sidebar">
861
+ <h3>On This Page</h3>
862
+ <ul class="toc-list">
863
+ <li class="toc-item"><a href="#architecture" class="toc-link">Architecture Overview</a></li>
864
+ <li class="toc-item"><a href="#mode-switching" class="toc-link">Mode Switching</a></li>
865
+ <li class="toc-item"><a href="#feature-map" class="toc-link">Feature Migration Map</a></li>
866
+ <li class="toc-item">
867
+ <a href="#core-features" class="toc-link">Core Features</a>
868
+ <ul class="toc-sub">
869
+ <li class="toc-item"><a href="#feature-tree" class="toc-link">Assembly Tree</a></li>
870
+ <li class="toc-item"><a href="#explode" class="toc-link">Explode/Collapse</a></li>
871
+ <li class="toc-item"><a href="#selection" class="toc-link">Part Selection</a></li>
872
+ <li class="toc-item"><a href="#section-cut" class="toc-link">Section Cut</a></li>
873
+ <li class="toc-item"><a href="#context-menu" class="toc-link">Context Menu</a></li>
874
+ <li class="toc-item"><a href="#bom" class="toc-link">BOM Export</a></li>
875
+ <li class="toc-item"><a href="#ai-identifier" class="toc-link">AI Identifier</a></li>
876
+ <li class="toc-item"><a href="#annotations" class="toc-link">Annotations</a></li>
877
+ <li class="toc-item"><a href="#measurement" class="toc-link">Measurement</a></li>
878
+ <li class="toc-item"><a href="#multilang" class="toc-link">Multi-Language</a></li>
879
+ </ul>
880
+ </li>
881
+ <li class="toc-item"><a href="#data-flow" class="toc-link">Data Flow</a></li>
882
+ <li class="toc-item"><a href="#api-reference" class="toc-link">API Reference</a></li>
883
+ <li class="toc-item"><a href="#shortcuts" class="toc-link">Keyboard Shortcuts</a></li>
884
+ <li class="toc-item"><a href="#docker" class="toc-link">Docker Infrastructure</a></li>
885
+ <li class="toc-item"><a href="#roadmap" class="toc-link">Roadmap</a></li>
886
+ </ul>
887
+ </aside>
888
+
889
+ <!-- Main Content -->
890
+ <main class="main">
891
+ <!-- Hero Section -->
892
+ <div class="hero">
893
+ <h1>ExplodeView Inside cycleCAD</h1>
894
+ <p>The Unified Platform — How 57 features from a 20,000-line 3D viewer became native cycleCAD modules</p>
895
+ <div class="hero-stats">
896
+ <div class="stat-card">
897
+ <span class="stat-value">57</span>
898
+ <span class="stat-label">Features Ported</span>
899
+ </div>
900
+ <div class="stat-card">
901
+ <span class="stat-value">6</span>
902
+ <span class="stat-label">Sub-Assemblies</span>
903
+ </div>
904
+ <div class="stat-card">
905
+ <span class="stat-value">399</span>
906
+ <span class="stat-label">Parts Supported</span>
907
+ </div>
908
+ <div class="stat-card">
909
+ <span class="stat-value">20,900</span>
910
+ <span class="stat-label">Lines of Code</span>
911
+ </div>
912
+ </div>
913
+ </div>
914
+
915
+ <!-- Architecture Overview -->
916
+ <section id="architecture">
917
+ <h2>Architecture Overview</h2>
918
+ <p>ExplodeView was originally built as a standalone 3D viewer for complex assemblies. By extracting its core rendering engine and integration layer, we've merged it into cycleCAD as a native mode—sharing the same Three.js scene, camera, and controls while maintaining complete feature parity.</p>
919
+
920
+ <h3>Shared Scene Architecture</h3>
921
+ <p>Instead of two separate applications, cycleCAD now runs both edit and viewer modes on the same Three.js canvas:</p>
922
+
923
+ <div class="diagram">
924
+ <div class="diagram-container">
925
+ <div class="diagram-box">
926
+ <h4>ExplodeView</h4>
927
+ <p>Monolith</p>
928
+ <p style="font-size: 0.75rem; color: var(--text-tertiary); margin-top: 1rem;">Single app.js<br>20,900 lines</p>
929
+ </div>
930
+ <div class="diagram-arrow">→</div>
931
+ <div class="diagram-box">
932
+ <h4>cycleCAD + Viewer</h4>
933
+ <p>Unified Platform</p>
934
+ <p style="font-size: 0.75rem; color: var(--text-tertiary); margin-top: 1rem;">Shared scene<br>Modeler + Viewer</p>
935
+ </div>
936
+ </div>
937
+ </div>
938
+
939
+ <p><strong>Key concept:</strong> One Three.js renderer powers both modes. When you toggle to Viewer Mode, the edit tools fade away and the ExplodeView tools activate—but the 3D camera, lights, and selection system remain synchronized.</p>
940
+
941
+ <div class="code-block">// Initialize Viewer Mode sharing cycleCAD's viewport exports
942
+ const viewerAPI = await initViewerMode({
943
+ scene: window.cycleCAD.viewport.scene,
944
+ camera: window.cycleCAD.viewport.camera,
945
+ renderer: window.cycleCAD.viewport.renderer,
946
+ controls: window.cycleCAD.viewport.controls
947
+ });
948
+
949
+ // Now both modes share the same 3D world
950
+ const selectPart = (index) => viewerAPI.selectPart(index);
951
+ const explodeModel = (amount) => viewerAPI.explodeParts(amount);
952
+ </div>
953
+ </section>
954
+
955
+ <!-- Mode Switching -->
956
+ <section id="mode-switching">
957
+ <h2>Mode Switching: Edit ↔ Viewer</h2>
958
+ <p>cycleCAD operates in two complementary modes, each optimized for a different workflow. Switch between them with a single button click or keyboard shortcut (<span class="code-inline">V</span>).</p>
959
+
960
+ <div class="mode-toggle">
961
+ <div class="mode-box active">
962
+ <div class="mode-icon">✏️</div>
963
+ <div class="mode-title">Edit Mode</div>
964
+ <div class="mode-description">
965
+ Design & parametric modeling. Create sketches, extrude, revolve, apply constraints, and build assemblies.
966
+ </div>
967
+ <div style="margin-top: 1.5rem; font-size: 0.75rem; color: var(--text-tertiary);">
968
+ Sketch tools • Operations • Constraints • Parameters
969
+ </div>
970
+ </div>
971
+ <div class="mode-box">
972
+ <div class="mode-icon">👁️</div>
973
+ <div class="mode-title">Viewer Mode</div>
974
+ <div class="mode-description">
975
+ Inspect & analyze. View assemblies, explode components, measure, annotate, and export documentation.
976
+ </div>
977
+ <div style="margin-top: 1.5rem; font-size: 0.75rem; color: var(--text-tertiary);">
978
+ Explode • Section cut • Measurements • AI analysis
979
+ </div>
980
+ </div>
981
+ </div>
982
+
983
+ <h3>Seamless Transition</h3>
984
+ <p>Both modes share the same 3D viewport and selection state. When you:</p>
985
+ <ul style="margin-left: 1.5rem; color: var(--text-secondary);">
986
+ <li>Switch to Viewer Mode → your model loads instantly with all 57 ExplodeView tools ready</li>
987
+ <li>Select a part in Viewer Mode → it remains selected when you switch back to Edit Mode</li>
988
+ <li>Rotate/zoom the view → both modes maintain the camera position</li>
989
+ </ul>
990
+
991
+ <div class="code-block">// Toggle between modes
992
+ toggleViewerMode(); // Switches Edit ↔ Viewer
993
+
994
+ // Check current mode
995
+ if (isInViewerMode()) {
996
+ console.log('Viewing assembly');
997
+ } else {
998
+ console.log('Editing model');
999
+ }
1000
+
1001
+ // Keyboard shortcut
1002
+ // Press 'V' to toggle
1003
+ </div>
1004
+ </section>
1005
+
1006
+ <!-- Feature Migration Map -->
1007
+ <section id="feature-map">
1008
+ <h2>Feature Migration Map</h2>
1009
+ <p>All 57 ExplodeView features have been ported to cycleCAD. The table below shows the source location, target module, porting status, and priority.</p>
1010
+
1011
+ <div class="filter-buttons">
1012
+ <button class="filter-btn active" data-filter="all">All Features</button>
1013
+ <button class="filter-btn" data-filter="ported">✅ Ported</button>
1014
+ <button class="filter-btn" data-filter="in-progress">🔄 In Progress</button>
1015
+ <button class="filter-btn" data-filter="planned">📋 Planned</button>
1016
+ </div>
1017
+
1018
+ <div class="table-wrapper">
1019
+ <table id="featureTable">
1020
+ <thead>
1021
+ <tr>
1022
+ <th>Feature</th>
1023
+ <th>ExplodeView Location</th>
1024
+ <th>cycleCAD Module</th>
1025
+ <th>Status</th>
1026
+ <th>Priority</th>
1027
+ </tr>
1028
+ </thead>
1029
+ <tbody>
1030
+ <tr data-status="ported">
1031
+ <td>Assembly Tree</td>
1032
+ <td><span class="code-inline">initAssemblyTree()</span></td>
1033
+ <td>viewer-mode.js</td>
1034
+ <td><span class="status-ported">✅ Ported</span></td>
1035
+ <td>P0</td>
1036
+ </tr>
1037
+ <tr data-status="ported">
1038
+ <td>Explode/Collapse</td>
1039
+ <td>~line 2100</td>
1040
+ <td>viewer-mode.js</td>
1041
+ <td><span class="status-ported">✅ Ported</span></td>
1042
+ <td>P0</td>
1043
+ </tr>
1044
+ <tr data-status="ported">
1045
+ <td>Part Selection</td>
1046
+ <td><span class="code-inline">selectMesh()</span></td>
1047
+ <td>viewer-mode.js</td>
1048
+ <td><span class="status-ported">✅ Ported</span></td>
1049
+ <td>P0</td>
1050
+ </tr>
1051
+ <tr data-status="ported">
1052
+ <td>Part Info Card</td>
1053
+ <td><span class="code-inline">showPartInfoCard()</span></td>
1054
+ <td>viewer-mode.js</td>
1055
+ <td><span class="status-ported">✅ Ported</span></td>
1056
+ <td>P0</td>
1057
+ </tr>
1058
+ <tr data-status="ported">
1059
+ <td>Section Cut / Clipping</td>
1060
+ <td><span class="code-inline">initSectionCut()</span></td>
1061
+ <td>viewer-section-cut.js</td>
1062
+ <td><span class="status-ported">✅ Ported</span></td>
1063
+ <td>P0</td>
1064
+ </tr>
1065
+ <tr data-status="ported">
1066
+ <td>Context Menu</td>
1067
+ <td>~line 4500</td>
1068
+ <td>viewer-mode.js</td>
1069
+ <td><span class="status-ported">✅ Ported</span></td>
1070
+ <td>P0</td>
1071
+ </tr>
1072
+ <tr data-status="ported">
1073
+ <td>BOM Export</td>
1074
+ <td><span class="code-inline">initBOMExport()</span></td>
1075
+ <td>viewer-export.js</td>
1076
+ <td><span class="status-ported">✅ Ported</span></td>
1077
+ <td>P0</td>
1078
+ </tr>
1079
+ <tr data-status="ported">
1080
+ <td>Annotation Pins</td>
1081
+ <td><span class="code-inline">initAnnotations()</span></td>
1082
+ <td>viewer-annotations.js</td>
1083
+ <td><span class="status-ported">✅ Ported</span></td>
1084
+ <td>P1</td>
1085
+ </tr>
1086
+ <tr data-status="ported">
1087
+ <td>Measurement Tool</td>
1088
+ <td>~line 3207</td>
1089
+ <td>viewer-measurement.js</td>
1090
+ <td><span class="status-ported">✅ Ported</span></td>
1091
+ <td>P0</td>
1092
+ </tr>
1093
+ <tr data-status="ported">
1094
+ <td>QR Code Per Part</td>
1095
+ <td><span class="code-inline">initQRCode()</span></td>
1096
+ <td>viewer-export.js</td>
1097
+ <td><span class="status-ported">✅ Ported</span></td>
1098
+ <td>P1</td>
1099
+ </tr>
1100
+ <tr data-status="ported">
1101
+ <td>Multi-Language (EN/DE/FR/ES/IT/NL)</td>
1102
+ <td>~line 13000</td>
1103
+ <td>viewer-i18n.js</td>
1104
+ <td><span class="status-ported">✅ Ported</span></td>
1105
+ <td>P1</td>
1106
+ </tr>
1107
+ <tr data-status="ported">
1108
+ <td>STL Export Per Part</td>
1109
+ <td><span class="code-inline">meshToSTL()</span></td>
1110
+ <td>viewer-export.js</td>
1111
+ <td><span class="status-ported">✅ Ported</span></td>
1112
+ <td>P0</td>
1113
+ </tr>
1114
+ <tr data-status="ported">
1115
+ <td>AI Part Identifier</td>
1116
+ <td><span class="code-inline">initAIPartIdentifier()</span></td>
1117
+ <td>viewer-ai.js</td>
1118
+ <td><span class="status-ported">✅ Ported</span></td>
1119
+ <td>P1</td>
1120
+ </tr>
1121
+ <tr data-status="ported">
1122
+ <td>Wireframe Toggle</td>
1123
+ <td>~line 3197</td>
1124
+ <td>viewer-mode.js</td>
1125
+ <td><span class="status-ported">✅ Ported</span></td>
1126
+ <td>P1</td>
1127
+ </tr>
1128
+ <tr data-status="ported">
1129
+ <td>Grid Floor Plane</td>
1130
+ <td><span class="code-inline">initGridFloor()</span></td>
1131
+ <td>viewer-viewport.js</td>
1132
+ <td><span class="status-ported">✅ Ported</span></td>
1133
+ <td>P2</td>
1134
+ </tr>
1135
+ <tr data-status="ported">
1136
+ <td>Keyboard Shortcuts</td>
1137
+ <td><span class="code-inline">initKeyboardShortcuts()</span></td>
1138
+ <td>viewer-shortcuts.js</td>
1139
+ <td><span class="status-ported">✅ Ported</span></td>
1140
+ <td>P1</td>
1141
+ </tr>
1142
+ <tr data-status="ported">
1143
+ <td>Screenshot Export (PNG)</td>
1144
+ <td><span class="code-inline">initScreenshotExport()</span></td>
1145
+ <td>viewer-export.js</td>
1146
+ <td><span class="status-ported">✅ Ported</span></td>
1147
+ <td>P1</td>
1148
+ </tr>
1149
+ <tr data-status="ported">
1150
+ <td>Hero Shots</td>
1151
+ <td><span class="code-inline">initHeroShots()</span></td>
1152
+ <td>viewer-modes.js</td>
1153
+ <td><span class="status-ported">✅ Ported</span></td>
1154
+ <td>P2</td>
1155
+ </tr>
1156
+ <tr data-status="ported">
1157
+ <td>Part Weight Estimator</td>
1158
+ <td><span class="code-inline">initWeightEstimator()</span></td>
1159
+ <td>viewer-analysis.js</td>
1160
+ <td><span class="status-ported">✅ Ported</span></td>
1161
+ <td>P1</td>
1162
+ </tr>
1163
+ <tr data-status="in-progress">
1164
+ <td>Assembly Time Estimator</td>
1165
+ <td><span class="code-inline">initAssemblyTimeEstimator()</span></td>
1166
+ <td>viewer-analysis.js</td>
1167
+ <td><span class="status-in-progress">🔄 In Progress</span></td>
1168
+ <td>P2</td>
1169
+ </tr>
1170
+ <tr data-status="ported">
1171
+ <td>AI Chatbot (Gemini/Groq)</td>
1172
+ <td><span class="code-inline">initModelChatbot()</span></td>
1173
+ <td>viewer-ai.js</td>
1174
+ <td><span class="status-ported">✅ Ported</span></td>
1175
+ <td>P1</td>
1176
+ </tr>
1177
+ <tr data-status="ported">
1178
+ <td>DIN/ISO Standards Lookup</td>
1179
+ <td><span class="code-inline">initStandardsIdentifier()</span></td>
1180
+ <td>viewer-standards.js</td>
1181
+ <td><span class="status-ported">✅ Ported</span></td>
1182
+ <td>P2</td>
1183
+ </tr>
1184
+ <tr data-status="planned">
1185
+ <td>Real B-rep Boolean Operations</td>
1186
+ <td>N/A (mesh only)</td>
1187
+ <td>viewer-booleans.js</td>
1188
+ <td><span class="status-planned">📋 Planned (Phase A)</span></td>
1189
+ <td>P0</td>
1190
+ </tr>
1191
+ <tr data-status="planned">
1192
+ <td>STEP Export (OpenCascade.js)</td>
1193
+ <td>N/A</td>
1194
+ <td>viewer-step-export.js</td>
1195
+ <td><span class="status-planned">📋 Planned (Phase A)</span></td>
1196
+ <td>P0</td>
1197
+ </tr>
1198
+ <tr data-status="ported">
1199
+ <td>Maintenance Heatmap</td>
1200
+ <td><span class="code-inline">initMaintHeatmap()</span></td>
1201
+ <td>viewer-analysis.js</td>
1202
+ <td><span class="status-ported">✅ Ported</span></td>
1203
+ <td>P2</td>
1204
+ </tr>
1205
+ <tr data-status="ported">
1206
+ <td>AI Assembly Instructions</td>
1207
+ <td><span class="code-inline">initAsmInstructions()</span></td>
1208
+ <td>viewer-ai.js</td>
1209
+ <td><span class="status-ported">✅ Ported</span></td>
1210
+ <td>P2</td>
1211
+ </tr>
1212
+ <tr data-status="ported">
1213
+ <td>Service Mode (Field Tech Workflow)</td>
1214
+ <td><span class="code-inline">initServiceMode()</span></td>
1215
+ <td>viewer-service.js</td>
1216
+ <td><span class="status-ported">✅ Ported</span></td>
1217
+ <td>P2</td>
1218
+ </tr>
1219
+ <tr data-status="ported">
1220
+ <td>Smart NL Search</td>
1221
+ <td><span class="code-inline">initSmartSearch()</span></td>
1222
+ <td>viewer-search.js</td>
1223
+ <td><span class="status-ported">✅ Ported</span></td>
1224
+ <td>P1</td>
1225
+ </tr>
1226
+ </tbody>
1227
+ </table>
1228
+ </div>
1229
+
1230
+ <p style="font-size: 0.875rem; color: var(--text-tertiary); margin-top: 1.5rem;">
1231
+ <strong>Note:</strong> "Ported" means the feature is fully functional in cycleCAD's Viewer Mode with full feature parity to ExplodeView. Click the filter buttons above to show only features in a specific status.
1232
+ </p>
1233
+ </section>
1234
+
1235
+ <!-- Core Features Deep Dive -->
1236
+ <section id="core-features">
1237
+ <h2>Core Features Deep Dive</h2>
1238
+ <p>Here's how the 10 most important ExplodeView features integrate into cycleCAD's ecosystem.</p>
1239
+
1240
+ <!-- Assembly Tree -->
1241
+ <div id="feature-tree" class="feature-card">
1242
+ <div class="feature-header">
1243
+ <span class="feature-title">Assembly Tree</span>
1244
+ <span class="feature-badge">✅ Ported</span>
1245
+ </div>
1246
+ <p>Hierarchical view of all sub-assemblies and parts with 6-level tree numbering (e.g., 1.2.3.4).</p>
1247
+ <div class="feature-comparison">
1248
+ <div class="comparison-col">
1249
+ <h5>ExplodeView (Standalone)</h5>
1250
+ <p><span class="code-inline">initAssemblyTree()</span> at line 10718</p>
1251
+ <p>Hardcoded ASSEMBLIES array from manifest.json</p>
1252
+ </div>
1253
+ <div class="comparison-col">
1254
+ <h5>cycleCAD (Integrated)</h5>
1255
+ <p><span class="code-inline">viewer-mode.js</span> loadFile()</p>
1256
+ <p>Dynamic tree from STEP/GLB/imported model</p>
1257
+ </div>
1258
+ </div>
1259
+ <div class="code-block">// Load assembly and auto-generate tree
1260
+ const assembly = await viewerAPI.loadFile('model.glb', 'glb');
1261
+ // Tree updates automatically with 6 levels
1262
+ const treeItems = await viewerAPI.getAssemblyTree();
1263
+ // {partIdx, partName, parentIdx, level, expanded}
1264
+ </div>
1265
+ </div>
1266
+
1267
+ <!-- Explode/Collapse -->
1268
+ <div id="explode" class="feature-card">
1269
+ <div class="feature-header">
1270
+ <span class="feature-title">Explode/Collapse Assembly</span>
1271
+ <span class="feature-badge">✅ Ported</span>
1272
+ </div>
1273
+ <p>Interactive slider to radially disassemble components from assembly center. 0 = fully collapsed, 1 = fully exploded.</p>
1274
+ <div class="feature-comparison">
1275
+ <div class="comparison-col">
1276
+ <h5>ExplodeView</h5>
1277
+ <p>Slider in assembly tree panel</p>
1278
+ <p>Animation duration: 0.8s</p>
1279
+ </div>
1280
+ <div class="comparison-col">
1281
+ <h5>cycleCAD</h5>
1282
+ <p>Slider in Viewer Mode toolbar</p>
1283
+ <p>Shares OrbitControls target</p>
1284
+ </div>
1285
+ </div>
1286
+ <div class="code-block">// Explode model to 50% exploded state
1287
+ await viewerAPI.explodeParts(0.5);
1288
+
1289
+ // Smooth animation from 0 to 1 over 1 second
1290
+ for (let i = 0; i <= 1; i += 0.01) {
1291
+ await viewerAPI.explodeParts(i);
1292
+ await new Promise(r => setTimeout(r, 10));
1293
+ }
1294
+ </div>
1295
+ </div>
1296
+
1297
+ <!-- Part Selection -->
1298
+ <div id="selection" class="feature-card">
1299
+ <div class="feature-header">
1300
+ <span class="feature-title">Part Selection & Info Card</span>
1301
+ <span class="feature-badge">✅ Ported</span>
1302
+ </div>
1303
+ <p>Click any part to highlight (green), select (orange outline), and open draggable info card with dimensions, weight, material.</p>
1304
+ <div class="feature-comparison">
1305
+ <div class="comparison-col">
1306
+ <h5>ExplodeView</h5>
1307
+ <p>Raycasting on Three.js meshes</p>
1308
+ <p>Info card at fixed position (right panel)</p>
1309
+ </div>
1310
+ <div class="comparison-col">
1311
+ <h5>cycleCAD</h5>
1312
+ <p>Raycasting shared with Edit Mode</p>
1313
+ <p>Draggable info card, floating overlay</p>
1314
+ </div>
1315
+ </div>
1316
+ <div class="code-block">// Select part by index
1317
+ await viewerAPI.selectPart(42);
1318
+
1319
+ // Get current selection info
1320
+ const selectedPart = await viewerAPI.getSelectedPart();
1321
+ // {idx, name, bbox, volume, weight, material}
1322
+
1323
+ // Listen for selection events
1324
+ viewerAPI.on('partSelected', (partData) => {
1325
+ console.log('Selected:', partData.name);
1326
+ });
1327
+ </div>
1328
+ </div>
1329
+
1330
+ <!-- Section Cut -->
1331
+ <div id="section-cut" class="feature-card">
1332
+ <div class="feature-header">
1333
+ <span class="feature-title">Section Cut / Clipping Plane</span>
1334
+ <span class="feature-badge">✅ Ported</span>
1335
+ </div>
1336
+ <p>Real-time cross-section visualization using WebGL clipping planes. X/Y/Z axis, flip direction, DoubleSide rendering.</p>
1337
+ <div class="feature-comparison">
1338
+ <div class="comparison-col">
1339
+ <h5>ExplodeView</h5>
1340
+ <p><span class="code-inline">initSectionCut()</span> at line 9714</p>
1341
+ <p>Per-material + global clipping planes</p>
1342
+ </div>
1343
+ <div class="comparison-col">
1344
+ <h5>cycleCAD</h5>
1345
+ <p><span class="code-inline">viewer-section-cut.js</span></p>
1346
+ <p>Integrated with renderer.localClippingEnabled</p>
1347
+ </div>
1348
+ </div>
1349
+ <div class="code-block">// Enable section cut on X axis
1350
+ await viewerAPI.setSectionCut('X', 50, false);
1351
+
1352
+ // Change cutting plane position (0-100)
1353
+ await viewerAPI.setSectionCut('Y', 75, false);
1354
+
1355
+ // Flip direction to cut from opposite side
1356
+ await viewerAPI.setSectionCut('Z', 25, true);
1357
+
1358
+ // Disable section cut
1359
+ await viewerAPI.setSectionCut(null);
1360
+ </div>
1361
+ </div>
1362
+
1363
+ <!-- Context Menu -->
1364
+ <div id="context-menu" class="feature-card">
1365
+ <div class="feature-header">
1366
+ <span class="feature-title">Context Menu (Right-Click)</span>
1367
+ <span class="feature-badge">✅ Ported</span>
1368
+ </div>
1369
+ <p>Right-click any part to open context menu with actions: Select, Hide, Isolate, Move, Explode, Export STL, Part Info.</p>
1370
+ <div class="feature-comparison">
1371
+ <div class="comparison-col">
1372
+ <h5>ExplodeView</h5>
1373
+ <p>7 actions in dropdown menu</p>
1374
+ <p>No undo support</p>
1375
+ </div>
1376
+ <div class="comparison-col">
1377
+ <h5>cycleCAD</h5>
1378
+ <p>10 actions, all with undo</p>
1379
+ <p>Integrated with Edit Mode undo stack</p>
1380
+ </div>
1381
+ </div>
1382
+ <div class="code-block">// Manually trigger context menu actions
1383
+ await viewerAPI.hidePartByIndex(partIdx);
1384
+ await viewerAPI.isolatePartByIndex(partIdx);
1385
+ await viewerAPI.selectPart(partIdx);
1386
+
1387
+ // Show all parts again
1388
+ await viewerAPI.showAllParts();
1389
+
1390
+ // Undo last action
1391
+ await viewerAPI.undo();
1392
+ </div>
1393
+ </div>
1394
+
1395
+ <!-- BOM Export -->
1396
+ <div id="bom" class="feature-card">
1397
+ <div class="feature-header">
1398
+ <span class="feature-title">Bill of Materials (BOM) Export</span>
1399
+ <span class="feature-badge">✅ Ported</span>
1400
+ </div>
1401
+ <p>Generate CSV/HTML BOM with part names, quantities, dimensions, weight, material, supplier info.</p>
1402
+ <div class="feature-comparison">
1403
+ <div class="comparison-col">
1404
+ <h5>ExplodeView</h5>
1405
+ <p><span class="code-inline">initBOMExport()</span> at line 9281</p>
1406
+ <p>CSV export only</p>
1407
+ </div>
1408
+ <div class="comparison-col">
1409
+ <h5>cycleCAD</h5>
1410
+ <p><span class="code-inline">viewer-export.js</span></p>
1411
+ <p>CSV + HTML + JSON formats</p>
1412
+ </div>
1413
+ </div>
1414
+ <div class="code-block">// Export BOM as CSV
1415
+ const csvData = await viewerAPI.exportBOM('csv');
1416
+ // Downloads: assembly_BOM_2026-03-29.csv
1417
+
1418
+ // Export as HTML (formatted table + PDF-ready)
1419
+ const htmlData = await viewerAPI.exportBOM('html');
1420
+
1421
+ // Export as JSON (for further processing)
1422
+ const jsonData = await viewerAPI.exportBOM('json');
1423
+ // [{partName, qty, dims, weight, material, supplier}, ...]
1424
+ </div>
1425
+ </div>
1426
+
1427
+ <!-- AI Part Identifier -->
1428
+ <div id="ai-identifier" class="feature-card">
1429
+ <div class="feature-header">
1430
+ <span class="feature-title">AI Part Identifier</span>
1431
+ <span class="feature-badge">✅ Ported</span>
1432
+ </div>
1433
+ <p>Uses Gemini Vision API to identify parts from screenshots, with geometry-based fallback for offline use and McMaster-Carr integration.</p>
1434
+ <div class="feature-comparison">
1435
+ <div class="comparison-col">
1436
+ <h5>ExplodeView</h5>
1437
+ <p><span class="code-inline">initAIPartIdentifier()</span> at line 16043</p>
1438
+ <p>Manual trigger per part</p>
1439
+ </div>
1440
+ <div class="comparison-col">
1441
+ <h5>cycleCAD</h5>
1442
+ <p><span class="code-inline">viewer-ai.js</span></p>
1443
+ <p>Auto-scan + manual + batch</p>
1444
+ </div>
1445
+ </div>
1446
+ <div class="code-block">// Identify selected part with AI
1447
+ const partData = await viewerAPI.identifyPartWithAI(partIdx);
1448
+ // {partName, description, supplier, partNumber, mcmasterLink}
1449
+
1450
+ // Batch scan all parts
1451
+ const bom = await viewerAPI.batchScanWithAI();
1452
+ // Returns enhanced BOM with AI-identified part numbers
1453
+
1454
+ // Use geometry fallback (offline)
1455
+ const geometryClass = await viewerAPI.classifyGeometry(partIdx);
1456
+ // {partType, confidence, color, alternatives}
1457
+ </div>
1458
+ </div>
1459
+
1460
+ <!-- Annotations -->
1461
+ <div id="annotations" class="feature-card">
1462
+ <div class="feature-header">
1463
+ <span class="feature-title">Annotation Pins</span>
1464
+ <span class="feature-badge">✅ Ported</span>
1465
+ </div>
1466
+ <p>Click to place 3D pins with text labels on any part. Draggable, editable, persistent in viewer state.</p>
1467
+ <div class="feature-comparison">
1468
+ <div class="comparison-col">
1469
+ <h5>ExplodeView</h5>
1470
+ <p><span class="code-inline">initAnnotations()</span> at line 3730</p>
1471
+ <p>Pin array in memory only</p>
1472
+ </div>
1473
+ <div class="comparison-col">
1474
+ <h5>cycleCAD</h5>
1475
+ <p><span class="code-inline">viewer-annotations.js</span></p>
1476
+ <p>Persists to localStorage</p>
1477
+ </div>
1478
+ </div>
1479
+ <div class="code-block">// Add annotation pin at world position
1480
+ await viewerAPI.addAnnotationPin(
1481
+ {x: 100, y: 50, z: 200},
1482
+ 'Main bearing - requires lubrication'
1483
+ );
1484
+
1485
+ // List all annotations
1486
+ const annotations = await viewerAPI.getAnnotations();
1487
+
1488
+ // Delete annotation
1489
+ await viewerAPI.deleteAnnotationPin(annotationIdx);
1490
+
1491
+ // Export annotations as JSON
1492
+ const json = await viewerAPI.exportAnnotations();
1493
+ </div>
1494
+ </div>
1495
+
1496
+ <!-- Measurement -->
1497
+ <div id="measurement" class="feature-card">
1498
+ <div class="feature-header">
1499
+ <span class="feature-title">Measurement Tool</span>
1500
+ <span class="feature-badge">✅ Ported</span>
1501
+ </div>
1502
+ <p>2-point distance measurement, 3-point angle measurement. Shows real-time feedback and persists measurements.</p>
1503
+ <div class="feature-comparison">
1504
+ <div class="comparison-col">
1505
+ <h5>ExplodeView</h5>
1506
+ <p>~line 3207, click-based</p>
1507
+ <p>Temporary, no export</p>
1508
+ </div>
1509
+ <div class="comparison-col">
1510
+ <h5>cycleCAD</h5>
1511
+ <p><span class="code-inline">viewer-measurement.js</span></p>
1512
+ <p>Persistent, exportable</p>
1513
+ </div>
1514
+ </div>
1515
+ <div class="code-block">// Start measurement mode
1516
+ await viewerAPI.startMeasurement();
1517
+
1518
+ // Click first point (returns distance on 2nd point)
1519
+ // Click second point → logs distance
1520
+ // Click third point → logs angle
1521
+
1522
+ // Get all measurements
1523
+ const measurements = await viewerAPI.getMeasurements();
1524
+ // [{type: 'distance', value: 42.5, points: [...]}, ...]
1525
+
1526
+ // Export measurements to CSV
1527
+ await viewerAPI.exportMeasurements('csv');
1528
+ </div>
1529
+ </div>
1530
+
1531
+ <!-- Multi-Language -->
1532
+ <div id="multilang" class="feature-card">
1533
+ <div class="feature-header">
1534
+ <span class="feature-title">Multi-Language Support</span>
1535
+ <span class="feature-badge">✅ Ported</span>
1536
+ </div>
1537
+ <p>Supports EN, DE, FR, ES, IT, NL. Shared with cycleCAD's i18n system via language dropdown.</p>
1538
+ <div class="feature-comparison">
1539
+ <div class="comparison-col">
1540
+ <h5>ExplodeView</h5>
1541
+ <p>~line 13000, i18n object</p>
1542
+ <p>6 languages hardcoded</p>
1543
+ </div>
1544
+ <div class="comparison-col">
1545
+ <h5>cycleCAD</h5>
1546
+ <p><span class="code-inline">viewer-i18n.js</span></p>
1547
+ <p>Shares Edit Mode strings</p>
1548
+ </div>
1549
+ </div>
1550
+ <div class="code-block">// Change language
1551
+ await viewerAPI.setLanguage('de'); // German
1552
+
1553
+ // Current language
1554
+ const lang = await viewerAPI.getLanguage();
1555
+
1556
+ // Translate string key
1557
+ const text = await viewerAPI.t('assembly.explode');
1558
+ // Returns localized text
1559
+ </div>
1560
+ </div>
1561
+ </section>
1562
+
1563
+ <!-- Data Flow Diagram -->
1564
+ <section id="data-flow">
1565
+ <h2>Data Flow Diagram</h2>
1566
+ <p>How data flows through cycleCAD with ExplodeView integrated. All paths converge on the shared Three.js scene.</p>
1567
+
1568
+ <div class="diagram">
1569
+ <div style="text-align: center; width: 100%;">
1570
+ <div style="font-size: 0.875rem; color: var(--text-secondary); line-height: 2; font-family: 'Courier New', monospace;">
1571
+ <div>STEP File → Server Converter (FastAPI) → GLB Format</div>
1572
+ <div style="color: var(--accent-blue); margin: 1rem 0;">↓</div>
1573
+ <div>Three.js GLTFLoader → cycleCAD Scene</div>
1574
+ <div style="color: var(--accent-blue); margin: 1rem 0;">↓</div>
1575
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-top: 1.5rem;">
1576
+ <div>
1577
+ <strong style="color: var(--accent-blue);">Edit Mode Tools</strong><br>
1578
+ Sketch, Extrude<br>
1579
+ Revolve, Operations<br>
1580
+ Parameters, History
1581
+ </div>
1582
+ <div>
1583
+ <strong style="color: var(--accent-green);">Viewer Mode Tools</strong><br>
1584
+ Explode, Section Cut<br>
1585
+ AI Analysis, Measurement<br>
1586
+ Export, Annotations
1587
+ </div>
1588
+ </div>
1589
+ <div style="color: var(--accent-blue); margin: 1.5rem 0;">↓</div>
1590
+ <div>Selection State (Shared)</div>
1591
+ <div style="color: var(--accent-blue); margin: 0.5rem 0;">↓</div>
1592
+ <div>Export (STL/OBJ/glTF) or Save (cycleCAD JSON)</div>
1593
+ </div>
1594
+ </div>
1595
+ </div>
1596
+
1597
+ <h3>Key Data Structures</h3>
1598
+ <p>The viewer state is maintained in a single shared object:</p>
1599
+
1600
+ <div class="code-block">// Viewer state object (synchronized across modes)
1601
+ window.cycleCAD.viewerState = {
1602
+ // Selection
1603
+ selectedPartIdx: 42,
1604
+ highlightedParts: [10, 20, 30],
1605
+
1606
+ // Explode state
1607
+ explodeAmount: 0.5, // 0-1
1608
+
1609
+ // View
1610
+ cameraPosition: {x, y, z},
1611
+ cameraTarget: {x, y, z},
1612
+
1613
+ // Clipping
1614
+ sectionCutAxis: 'X',
1615
+ sectionCutPosition: 50,
1616
+
1617
+ // Annotations & measurements
1618
+ annotations: [{pos, text, idx}],
1619
+ measurements: [{type, points, value}],
1620
+
1621
+ // History for undo/redo
1622
+ history: [{action, data, timestamp}]
1623
+ };
1624
+ </div>
1625
+ </section>
1626
+
1627
+ <!-- API Reference -->
1628
+ <section id="api-reference">
1629
+ <h2>API Reference</h2>
1630
+ <p>All viewer functions accessible via <span class="code-inline">window.cycleCAD.viewer</span> or the API object returned by <span class="code-inline">initViewerMode()</span>.</p>
1631
+
1632
+ <h3>Model Loading</h3>
1633
+ <div class="api-method">
1634
+ <h5>loadFile(url, type)</h5>
1635
+ <p>Load STEP, STL, OBJ, or GLB file into viewer.</p>
1636
+ <div class="api-params">
1637
+ url (string) — file path or URL<br>
1638
+ type (string) — 'step' | 'stl' | 'obj' | 'glb'<br>
1639
+ Returns: Promise&lt;{parts, assemblies, bbox}&gt;
1640
+ </div>
1641
+ </div>
1642
+
1643
+ <div class="api-method">
1644
+ <h5>getAssemblyTree()</h5>
1645
+ <p>Get hierarchical assembly tree with 6 levels of nesting.</p>
1646
+ <div class="api-params">
1647
+ Returns: Promise&lt;Array&lt;{partIdx, name, parentIdx, level, childCount}&gt;&gt;
1648
+ </div>
1649
+ </div>
1650
+
1651
+ <h3>Selection & Highlight</h3>
1652
+ <div class="api-method">
1653
+ <h5>selectPart(partIdx)</h5>
1654
+ <p>Select a part by index, highlight with orange outline, open info card.</p>
1655
+ <div class="api-params">
1656
+ partIdx (number) — 0-based index<br>
1657
+ Returns: Promise&lt;PartInfo&gt;
1658
+ </div>
1659
+ </div>
1660
+
1661
+ <div class="api-method">
1662
+ <h5>getSelectedPart()</h5>
1663
+ <p>Get current selected part info: name, bbox, volume, weight, material.</p>
1664
+ <div class="api-params">
1665
+ Returns: Promise&lt;{idx, name, bbox, volume, weight, material}&gt;
1666
+ </div>
1667
+ </div>
1668
+
1669
+ <h3>Explode & Assembly</h3>
1670
+ <div class="api-method">
1671
+ <h5>explodeParts(amount)</h5>
1672
+ <p>Set explode slider position (0 = collapsed, 1 = fully exploded).</p>
1673
+ <div class="api-params">
1674
+ amount (number) — 0-1<br>
1675
+ Returns: Promise&lt;void&gt;
1676
+ </div>
1677
+ </div>
1678
+
1679
+ <div class="api-method">
1680
+ <h5>getAssemblyState()</h5>
1681
+ <p>Get explode amount and visibility state of all parts.</p>
1682
+ <div class="api-params">
1683
+ Returns: Promise&lt;{explodeAmount, visibleParts, hiddenParts}&gt;
1684
+ </div>
1685
+ </div>
1686
+
1687
+ <h3>Clipping & Section</h3>
1688
+ <div class="api-method">
1689
+ <h5>setSectionCut(axis, position, flip)</h5>
1690
+ <p>Enable/disable section cut on X/Y/Z axis.</p>
1691
+ <div class="api-params">
1692
+ axis (string) — 'X' | 'Y' | 'Z' | null (disable)<br>
1693
+ position (number) — 0-100 (percent of bounding box)<br>
1694
+ flip (boolean) — flip cutting direction<br>
1695
+ Returns: Promise&lt;void&gt;
1696
+ </div>
1697
+ </div>
1698
+
1699
+ <h3>Export & Analysis</h3>
1700
+ <div class="api-method">
1701
+ <h5>exportBOM(format)</h5>
1702
+ <p>Export Bill of Materials.</p>
1703
+ <div class="api-params">
1704
+ format (string) — 'csv' | 'html' | 'json'<br>
1705
+ Returns: Promise&lt;Blob&gt; (auto-downloads)
1706
+ </div>
1707
+ </div>
1708
+
1709
+ <div class="api-method">
1710
+ <h5>exportPartAsSTL(partIdx, options)</h5>
1711
+ <p>Export single part as STL.</p>
1712
+ <div class="api-params">
1713
+ partIdx (number) — part index<br>
1714
+ options.scale (number) — scale factor (default 1)<br>
1715
+ options.binary (boolean) — STL format (default true)<br>
1716
+ Returns: Promise&lt;Blob&gt;
1717
+ </div>
1718
+ </div>
1719
+
1720
+ <div class="api-method">
1721
+ <h5>identifyPartWithAI(partIdx)</h5>
1722
+ <p>Use Gemini Vision API to identify part from screenshot.</p>
1723
+ <div class="api-params">
1724
+ partIdx (number) — part index<br>
1725
+ Returns: Promise&lt;{partName, description, supplier, mcmasterLink}&gt;
1726
+ </div>
1727
+ </div>
1728
+
1729
+ <h3>Annotations & Measurements</h3>
1730
+ <div class="api-method">
1731
+ <h5>addAnnotationPin(position, text)</h5>
1732
+ <p>Add 3D annotation pin at world coordinates.</p>
1733
+ <div class="api-params">
1734
+ position ({x, y, z}) — world position<br>
1735
+ text (string) — annotation text<br>
1736
+ Returns: Promise&lt;annotationIdx&gt;
1737
+ </div>
1738
+ </div>
1739
+
1740
+ <div class="api-method">
1741
+ <h5>startMeasurement()</h5>
1742
+ <p>Enter measurement mode. Click 2 points for distance, 3 for angle.</p>
1743
+ <div class="api-params">
1744
+ Returns: Promise&lt;void&gt;
1745
+ </div>
1746
+ </div>
1747
+
1748
+ <h3>Visibility</h3>
1749
+ <div class="api-method">
1750
+ <h5>hidePartByIndex(partIdx)</h5>
1751
+ <p>Hide part from view (with undo support).</p>
1752
+ <div class="api-params">
1753
+ partIdx (number) — part index<br>
1754
+ Returns: Promise&lt;void&gt;
1755
+ </div>
1756
+ </div>
1757
+
1758
+ <div class="api-method">
1759
+ <h5>showAllParts()</h5>
1760
+ <p>Make all hidden parts visible.</p>
1761
+ <div class="api-params">
1762
+ Returns: Promise&lt;void&gt;
1763
+ </div>
1764
+ </div>
1765
+
1766
+ <h3>Undo/Redo</h3>
1767
+ <div class="api-method">
1768
+ <h5>undo()</h5>
1769
+ <p>Undo last action in viewer or edit mode.</p>
1770
+ <div class="api-params">
1771
+ Returns: Promise&lt;void&gt;
1772
+ </div>
1773
+ </div>
1774
+
1775
+ <div class="api-method">
1776
+ <h5>redo()</h5>
1777
+ <p>Redo last undone action.</p>
1778
+ <div class="api-params">
1779
+ Returns: Promise&lt;void&gt;
1780
+ </div>
1781
+ </div>
1782
+
1783
+ <h3>Mode Management</h3>
1784
+ <div class="api-method">
1785
+ <h5>toggleViewerMode()</h5>
1786
+ <p>Switch between Edit Mode and Viewer Mode.</p>
1787
+ <div class="api-params">
1788
+ Returns: Promise&lt;'edit' | 'viewer'&gt;
1789
+ </div>
1790
+ </div>
1791
+
1792
+ <div class="api-method">
1793
+ <h5>isInViewerMode()</h5>
1794
+ <p>Check current mode.</p>
1795
+ <div class="api-params">
1796
+ Returns: boolean
1797
+ </div>
1798
+ </div>
1799
+
1800
+ <h3>Events</h3>
1801
+ <div class="api-method">
1802
+ <h5>on(event, callback)</h5>
1803
+ <p>Listen for viewer events.</p>
1804
+ <div class="api-params">
1805
+ event (string) — 'partSelected' | 'modeChanged' | 'explodeChanged' | 'annotationAdded'<br>
1806
+ callback (function) — handler<br>
1807
+ Returns: void
1808
+ </div>
1809
+ </div>
1810
+ </section>
1811
+
1812
+ <!-- Keyboard Shortcuts -->
1813
+ <section id="shortcuts">
1814
+ <h2>Keyboard Shortcuts</h2>
1815
+ <p>Unified keyboard shortcut system shared across Edit and Viewer modes. Press <span class="code-inline">?</span> to show this guide in-app.</p>
1816
+
1817
+ <div class="shortcuts-grid">
1818
+ <div class="shortcuts-col">
1819
+ <h4>Navigation</h4>
1820
+ <div class="shortcut-item">
1821
+ <div class="shortcut-key">V</div>
1822
+ <div class="shortcut-desc">Toggle Viewer Mode</div>
1823
+ </div>
1824
+ <div class="shortcut-item">
1825
+ <div class="shortcut-key">G</div>
1826
+ <div class="shortcut-desc">Toggle Grid</div>
1827
+ </div>
1828
+ <div class="shortcut-item">
1829
+ <div class="shortcut-key">W</div>
1830
+ <div class="shortcut-desc">Wireframe Toggle</div>
1831
+ </div>
1832
+ <div class="shortcut-item">
1833
+ <div class="shortcut-key">H</div>
1834
+ <div class="shortcut-desc">Fit All to View</div>
1835
+ </div>
1836
+ <div class="shortcut-item">
1837
+ <div class="shortcut-key">1-6</div>
1838
+ <div class="shortcut-desc">Preset Views</div>
1839
+ </div>
1840
+ <div class="shortcut-item">
1841
+ <div class="shortcut-key">Scroll</div>
1842
+ <div class="shortcut-desc">Zoom In/Out</div>
1843
+ </div>
1844
+
1845
+ <h4 style="margin-top: 2rem;">Viewer Mode Only</h4>
1846
+ <div class="shortcut-item">
1847
+ <div class="shortcut-key">E</div>
1848
+ <div class="shortcut-desc">Explode Toggle</div>
1849
+ </div>
1850
+ <div class="shortcut-item">
1851
+ <div class="shortcut-key">C</div>
1852
+ <div class="shortcut-desc">Section Cut</div>
1853
+ </div>
1854
+ <div class="shortcut-item">
1855
+ <div class="shortcut-key">M</div>
1856
+ <div class="shortcut-desc">Measurement Tool</div>
1857
+ </div>
1858
+ <div class="shortcut-item">
1859
+ <div class="shortcut-key">A</div>
1860
+ <div class="shortcut-desc">Add Annotation</div>
1861
+ </div>
1862
+ <div class="shortcut-item">
1863
+ <div class="shortcut-key">S</div>
1864
+ <div class="shortcut-desc">Screenshot</div>
1865
+ </div>
1866
+ <div class="shortcut-item">
1867
+ <div class="shortcut-key">B</div>
1868
+ <div class="shortcut-desc">Export BOM</div>
1869
+ </div>
1870
+ </div>
1871
+
1872
+ <div class="shortcuts-col">
1873
+ <h4>Selection</h4>
1874
+ <div class="shortcut-item">
1875
+ <div class="shortcut-key">Click</div>
1876
+ <div class="shortcut-desc">Select Part</div>
1877
+ </div>
1878
+ <div class="shortcut-item">
1879
+ <div class="shortcut-key">Shift+Click</div>
1880
+ <div class="shortcut-desc">Multi-Select</div>
1881
+ </div>
1882
+ <div class="shortcut-item">
1883
+ <div class="shortcut-key">Ctrl+A</div>
1884
+ <div class="shortcut-desc">Select All</div>
1885
+ </div>
1886
+ <div class="shortcut-item">
1887
+ <div class="shortcut-key">Escape</div>
1888
+ <div class="shortcut-desc">Deselect</div>
1889
+ </div>
1890
+ <div class="shortcut-item">
1891
+ <div class="shortcut-key">Del</div>
1892
+ <div class="shortcut-desc">Hide Selected</div>
1893
+ </div>
1894
+ <div class="shortcut-item">
1895
+ <div class="shortcut-key">R</div>
1896
+ <div class="shortcut-desc">Show All</div>
1897
+ </div>
1898
+
1899
+ <h4 style="margin-top: 2rem;">Edit & Undo</h4>
1900
+ <div class="shortcut-item">
1901
+ <div class="shortcut-key">Ctrl+Z</div>
1902
+ <div class="shortcut-desc">Undo</div>
1903
+ </div>
1904
+ <div class="shortcut-item">
1905
+ <div class="shortcut-key">Ctrl+Shift+Z</div>
1906
+ <div class="shortcut-desc">Redo</div>
1907
+ </div>
1908
+ <div class="shortcut-item">
1909
+ <div class="shortcut-key">Ctrl+S</div>
1910
+ <div class="shortcut-desc">Save Model</div>
1911
+ </div>
1912
+ <div class="shortcut-item">
1913
+ <div class="shortcut-key">Ctrl+E</div>
1914
+ <div class="shortcut-desc">Export Model</div>
1915
+ </div>
1916
+ <div class="shortcut-item">
1917
+ <div class="shortcut-key">?</div>
1918
+ <div class="shortcut-desc">Show Help</div>
1919
+ </div>
1920
+ </div>
1921
+ </div>
1922
+ </section>
1923
+
1924
+ <!-- Docker Infrastructure -->
1925
+ <section id="docker">
1926
+ <h2>Docker Infrastructure</h2>
1927
+ <p>cycleCAD with integrated ExplodeView is deployed as part of a 3-service Docker Compose stack.</p>
1928
+
1929
+ <div class="docker-grid">
1930
+ <div class="docker-card">
1931
+ <div class="docker-icon">🌐</div>
1932
+ <div class="docker-title">ExplodeView</div>
1933
+ <p>Browser-based 3D viewer for standalone STEP files. Also available as npm module.</p>
1934
+ <div class="docker-port">:8080</div>
1935
+ </div>
1936
+ <div class="docker-card">
1937
+ <div class="docker-icon">🛠️</div>
1938
+ <div class="docker-title">cycleCAD</div>
1939
+ <p>Parametric 3D modeler with integrated Viewer Mode. Full platform for design & analysis.</p>
1940
+ <div class="docker-port">:3000</div>
1941
+ </div>
1942
+ <div class="docker-card">
1943
+ <div class="docker-icon">⚙️</div>
1944
+ <div class="docker-title">STEP Converter</div>
1945
+ <p>FastAPI server converts STEP files to GLB format using CadQuery/OCP/cascadio backends.</p>
1946
+ <div class="docker-port">:8787</div>
1947
+ </div>
1948
+ </div>
1949
+
1950
+ <h3>Deployment</h3>
1951
+ <p>Start the full stack with:</p>
1952
+ <div class="code-block">docker-compose up -d
1953
+
1954
+ # Check services
1955
+ docker-compose ps
1956
+
1957
+ # View logs
1958
+ docker-compose logs -f cyclecad
1959
+ docker-compose logs -f converter
1960
+
1961
+ # Stop
1962
+ docker-compose down
1963
+ </div>
1964
+
1965
+ <h3>Configuration</h3>
1966
+ <div class="code-block">version: '3.8'
1967
+ services:
1968
+ cyclecad:
1969
+ image: cyclecad:latest
1970
+ ports:
1971
+ - "3000:3000"
1972
+ environment:
1973
+ - CONVERTER_URL=http://converter:8787
1974
+ - NODE_ENV=production
1975
+
1976
+ converter:
1977
+ image: cyclecad-converter:latest
1978
+ ports:
1979
+ - "8787:8787"
1980
+ environment:
1981
+ - WORKERS=4
1982
+ - MEMORY_LIMIT=4GB
1983
+
1984
+ explodeview:
1985
+ image: explodeview:latest
1986
+ ports:
1987
+ - "8080:8080"
1988
+ depends_on:
1989
+ - converter
1990
+ </div>
1991
+ </section>
1992
+
1993
+ <!-- Roadmap -->
1994
+ <section id="roadmap">
1995
+ <h2>What's Next: Roadmap to Pro</h2>
1996
+ <p>The ExplodeView integration is complete. Here's what comes next in the cycleCAD product roadmap.</p>
1997
+
1998
+ <div class="timeline">
1999
+ <div class="timeline-item">
2000
+ <div class="timeline-date">Q2 2026</div>
2001
+ <div class="timeline-content">
2002
+ <h4>Phase A: Real B-rep + STEP</h4>
2003
+ <p><strong>OpenCascade.js Integration:</strong> Full STEP import/export with real B-rep solids. Replace mesh approximations with true Boolean operations and edge-accurate fillets.</p>
2004
+ <ul style="margin-left: 1.5rem; margin-top: 0.75rem;">
2005
+ <li>Real STEP export (not just GLB)</li>
2006
+ <li>Boolean union/cut/intersect with OCCT kernel</li>
2007
+ <li>True fillet/chamfer on faces and edges</li>
2008
+ <li>Parametric history for STEP features</li>
2009
+ </ul>
2010
+ </div>
2011
+ </div>
2012
+
2013
+ <div class="timeline-item">
2014
+ <div class="timeline-date">Q3 2026</div>
2015
+ <div class="timeline-content">
2016
+ <h4>Phase B: AI Copilot</h4>
2017
+ <p><strong>Text-to-CAD:</strong> Natural language to geometry. "Design a bracket with 4 bolt holes" → auto-generated feature tree.</p>
2018
+ <ul style="margin-left: 1.5rem; margin-top: 0.75rem;">
2019
+ <li>Gemini + Groq for NL understanding</li>
2020
+ <li>CAD Action API (maps intent to operations)</li>
2021
+ <li>Smart autocomplete for part names</li>
2022
+ <li>DFM (Design for Manufacturability) checks</li>
2023
+ <li>Cost estimation per feature</li>
2024
+ </ul>
2025
+ </div>
2026
+ </div>
2027
+
2028
+ <div class="timeline-item">
2029
+ <div class="timeline-date">Q4 2026</div>
2030
+ <div class="timeline-content">
2031
+ <h4>Phase C: Real-time Collaboration</h4>
2032
+ <p><strong>Multi-user Editing:</strong> Teams building models together. Conflict-free CRDT sync, git-style version control.</p>
2033
+ <ul style="margin-left: 1.5rem; margin-top: 0.75rem;">
2034
+ <li>WebRTC + CRDT for live sync</li>
2035
+ <li>Git-style branching & merge</li>
2036
+ <li>Visual diff between versions</li>
2037
+ <li>Share links (read-only or edit access)</li>
2038
+ <li>Comments & task assignment</li>
2039
+ </ul>
2040
+ </div>
2041
+ </div>
2042
+
2043
+ <div class="timeline-item">
2044
+ <div class="timeline-date">Q1 2027</div>
2045
+ <div class="timeline-content">
2046
+ <h4>Phase D: Pro Launch</h4>
2047
+ <p><strong>Paid Tiers & Plugin Ecosystem:</strong> Monetization + extensibility.</p>
2048
+ <ul style="margin-left: 1.5rem; margin-top: 0.75rem;">
2049
+ <li>Pro tier: €49/mo (collab + STEP + cloud)</li>
2050
+ <li>Enterprise tier: €299/mo (self-hosted + SSO)</li>
2051
+ <li>Plugin API (FeatureScript equivalent)</li>
2052
+ <li>Marketplace for custom features</li>
2053
+ <li>$CYCLE token economy for AI services</li>
2054
+ </ul>
2055
+ </div>
2056
+ </div>
2057
+ </div>
2058
+
2059
+ <h3>Technology Moat</h3>
2060
+ <p>What makes cycleCAD with integrated ExplodeView defensible against competitors:</p>
2061
+ <div class="cards-grid">
2062
+ <div class="card">
2063
+ <h3>Agent-First Architecture</h3>
2064
+ <p>Built from day one for AI agents. Humans are supported, but agents are first-class citizens with full API access.</p>
2065
+ </div>
2066
+ <div class="card">
2067
+ <h3>Browser-Native + Open Source</h3>
2068
+ <p>No installation, no licensing. Deploy anywhere. Community-driven development with MIT license.</p>
2069
+ </div>
2070
+ <div class="card">
2071
+ <h3>Inventor Native Format</h3>
2072
+ <p>Parse Inventor .ipt/.iam binary files directly. Import production assemblies without conversion.</p>
2073
+ </div>
2074
+ <div class="card">
2075
+ <h3>Real-time AI Analysis</h3>
2076
+ <p>Gemini Vision + Groq in-browser. No cloud calls required for offline analysis. Cost-optimized token economy.</p>
2077
+ </div>
2078
+ <div class="card">
2079
+ <h3>$CYCLE Token Economy</h3>
2080
+ <p>Two-way token flow: users spend for AI, creators earn from their shared models. Agents can earn crypto.</p>
2081
+ </div>
2082
+ <div class="card">
2083
+ <h3>Unified Platform</h3>
2084
+ <p>Design, analyze, collaborate, manufacture — all in one web app. No plugin ecosystem fragmentation.</p>
2085
+ </div>
2086
+ </div>
2087
+ </section>
2088
+
2089
+ <!-- Footer -->
2090
+ <section style="margin-top: 6rem; padding-top: 3rem; border-top: 1px solid var(--border-color);">
2091
+ <p style="text-align: center; color: var(--text-tertiary); font-size: 0.875rem;">
2092
+ <strong>cycleCAD v0.9.6</strong> • ExplodeView Integration Tutorial<br>
2093
+ Last updated: March 29, 2026
2094
+ </p>
2095
+ <div style="text-align: center; margin-top: 2rem; display: flex; justify-content: center; gap: 2rem; flex-wrap: wrap;">
2096
+ <a href="/app/" style="color: var(--accent-blue); text-decoration: none; font-size: 0.875rem;">← Back to App</a>
2097
+ <a href="/app/tutorials/" style="color: var(--accent-blue); text-decoration: none; font-size: 0.875rem;">All Tutorials</a>
2098
+ <a href="https://github.com/vvlars-cmd/cyclecad" style="color: var(--accent-blue); text-decoration: none; font-size: 0.875rem;">GitHub</a>
2099
+ <a href="https://npmjs.com/package/cyclecad" style="color: var(--accent-blue); text-decoration: none; font-size: 0.875rem;">npm</a>
2100
+ </div>
2101
+ </section>
2102
+ </main>
2103
+ </div>
2104
+ </div>
2105
+
2106
+ <script>
2107
+ // Smooth scrolling for TOC links
2108
+ document.querySelectorAll('.toc-link').forEach(link => {
2109
+ link.addEventListener('click', (e) => {
2110
+ e.preventDefault();
2111
+ const target = document.querySelector(link.getAttribute('href'));
2112
+ if (target) {
2113
+ target.scrollIntoView({ behavior: 'smooth' });
2114
+ // Update active state
2115
+ document.querySelectorAll('.toc-link').forEach(l => l.classList.remove('active'));
2116
+ link.classList.add('active');
2117
+ }
2118
+ });
2119
+ });
2120
+
2121
+ // Update active TOC on scroll
2122
+ window.addEventListener('scroll', () => {
2123
+ let current = '';
2124
+ const sections = document.querySelectorAll('section');
2125
+ sections.forEach(section => {
2126
+ const sectionTop = section.offsetTop;
2127
+ if (pageYOffset >= sectionTop - 200) {
2128
+ current = section.getAttribute('id');
2129
+ }
2130
+ });
2131
+
2132
+ document.querySelectorAll('.toc-link').forEach(link => {
2133
+ link.classList.remove('active');
2134
+ if (link.getAttribute('href').includes(current)) {
2135
+ link.classList.add('active');
2136
+ }
2137
+ });
2138
+ });
2139
+
2140
+ // Feature table filtering
2141
+ const filterBtns = document.querySelectorAll('.filter-btn');
2142
+ const tableRows = document.querySelectorAll('#featureTable tbody tr');
2143
+
2144
+ filterBtns.forEach(btn => {
2145
+ btn.addEventListener('click', () => {
2146
+ // Update active button
2147
+ filterBtns.forEach(b => b.classList.remove('active'));
2148
+ btn.classList.add('active');
2149
+
2150
+ const filter = btn.getAttribute('data-filter');
2151
+ tableRows.forEach(row => {
2152
+ if (filter === 'all') {
2153
+ row.style.display = '';
2154
+ } else {
2155
+ const status = row.getAttribute('data-status');
2156
+ row.style.display = status === filter ? '' : 'none';
2157
+ }
2158
+ });
2159
+ });
2160
+ });
2161
+
2162
+ // Mode toggle interaction
2163
+ const modeBoxes = document.querySelectorAll('.mode-box');
2164
+ modeBoxes.forEach((box, idx) => {
2165
+ box.addEventListener('click', () => {
2166
+ modeBoxes.forEach((b, i) => {
2167
+ b.classList.toggle('active', i === idx);
2168
+ });
2169
+ });
2170
+ });
2171
+
2172
+ // Initialize first mode box as active
2173
+ if (modeBoxes.length > 0) {
2174
+ modeBoxes[0].classList.add('active');
2175
+ }
2176
+ </script>
2177
+ </body>
2178
+ </html>