-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy patharchigraph.yaml
More file actions
8077 lines (8059 loc) · 323 KB
/
Copy patharchigraph.yaml
File metadata and controls
8077 lines (8059 loc) · 323 KB
1
2
3
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
archigraph: '0.3'
meta:
system: DraftDown
description: "A free, open-source 3D CAD application built with Electron + React + Three.js.\n Features push/pull\
\ modeling (dual-mode: extrude 2D faces, move 3D faces), drawing tools\n (line, rectangle, circle, arc, polygon), axis\
\ locking, snap system, face splitting,\n CSG boolean operations via Manifold WASM, and an AI chat assistant.\n Available\
\ as a web app at draftdownapp.com and as desktop binaries for\n macOS (signed/notarized DMG, x64+arm64), Windows (NSIS\
\ installer, x64), and Linux (AppImage, x64).\n SKP file import via native skp2obj tool linked against DraftDown 2026 C\
\ SDK. All computation runs locally."
environment:
- desktop
- web
tags:
- cad
- 3d-modeling
- electron
- 3d-cad
- local-first
owners:
- core-team
nodes:
- id: actor.designer
kind: actor
layer: product
name: 3D Designer
x:
docs:
description: 'Primary user — architects, interior designers, makers, hobbyists who create 3D models using intuitive
push/pull modeling. Expects native-style UX: click to draw faces, push/pull to extrude, orbit to navigate.'
impl:
status: implemented
uuid: g7fVsvXA
- id: actor.plugin_dev
kind: actor
layer: product
name: Plugin Developer
x:
docs:
description: Developer who extends DraftDown with custom tools, importers/exporters, and rendering modes via the plugin
API.
impl:
status: implemented
uuid: oN4ZxAP3
- id: window.main
kind: window
layer: ui
name: Main Window
x:
electron:
processType: renderer
windowOptions: '{"width": 1400, "height": 900, "minWidth": 800, "minHeight": 600, "title": "DraftDown", "webPreferences":
{"nodeIntegration": false, "contextIsolation": true, "preload": "preload.js"}}'
devTools: true
ui:
framework: React
layout: docked
resizable: true
impl:
language: TypeScript
framework: React
complexity: complex
status: implemented
docs:
description: >-
Main application window containing the 3D viewport, toolbars, panels, and menus. Uses a flexbox-based docking
layout with resizable panels. The viewport occupies the center, with toolbars on top/left and panels on right. Supports
dark/light themes. Global keyboard handler in App.tsx: Cmd/Ctrl+G creates component from selection
(auto-includes face edges), Escape exits component editing before clearing selection.
Context menu (ContextMenu.tsx): face/edge menus include Make Component, component menu has
Edit Component and Explode. Actions wired to SceneManager component API.
uuid: 6jdgpbuQ
- id: window.preferences
kind: window
layer: ui
name: Preferences Window
x:
electron:
processType: renderer
windowOptions: '{"width": 600, "height": 500, "modal": true, "resizable": false, "title": "Preferences"}'
ui:
framework: React
layout: modal
impl:
language: TypeScript
complexity: simple
status: implemented
docs:
description: 'Modal preferences dialog for configuring application settings: default units (metric/imperial), grid spacing,
snap settings, rendering quality, keyboard shortcuts, plugin management, and auto-save intervals.'
uuid: Tj4qa0I2
- id: window.materials
kind: window
layer: ui
name: Materials Browser
x:
electron:
processType: renderer
windowOptions: '{"width": 400, "height": 600, "title": "Materials"}'
ui:
framework: React
layout: floating
resizable: true
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: Floating materials browser window. Displays material library with thumbnails, allows drag-and-drop onto
faces, and supports creating/editing PBR materials (albedo, roughness, metalness, normal map). Materials are stored
as JSON with texture file references.
uuid: iLjOOnSg
- id: toolbar.main
kind: toolbar
layer: ui
name: Main Toolbar (Removed)
x:
ui:
framework: React
position: top
docs:
description: 'REMOVED — replaced by native Electron menu bar (menu.main). File/Edit dropdown menus were removed from
the renderer. All tools previously here are now in toolbar.drawing.'
impl:
status: removed
uuid: sVgEpC7b
- id: toolbar.drawing
kind: toolbar
layer: ui
name: Drawing Toolbar
x:
ui:
framework: React
position: left
docs:
description: 'Vertical toolbar docked on the left side. Contains ALL tools organized in collapsible groups:
Select (Select, Eraser), Draw (Line, Rectangle, Circle, Arc, Polygon), Modify (Push/Pull, Move, Rotate, Scale,
Offset, Sweep, Paint), Measure (Tape Measure, Protractor, Dimension, Text, Section Plane, Axes),
Navigate (Orbit, Pan, Zoom). Shows tool icon + label with keyboard shortcut in tooltip.'
impl:
status: implemented
uuid: osvXnUrz
- id: toolbar.views
kind: toolbar
layer: ui
name: Views Toolbar
x:
ui:
framework: React
position: top
docs:
description: 'Secondary toolbar for view controls: standard views (Front, Back, Left, Right, Top, Bottom, Iso), render
modes (Wireframe, Shaded, Textured, X-Ray), zoom controls (Zoom Extents, Zoom Window), and section planes.'
impl:
status: implemented
uuid: LwC50UvP
- id: menu.main
kind: menu
layer: ui
name: Application Menu
x:
ui:
framework: React
docs:
description: 'Native Electron application menu bar with menus: File (New, Open, Save, Save As, Import, Export, Recent
Files), Edit (Undo, Redo, Cut, Copy, Paste, Delete, Select All), Draw (all drawing tools), Tools (all modification
tools), View (render modes, toolbars, panels), Camera (standard views, projection), Window (panels), Plugins, Help.'
impl:
status: implemented
uuid: 68XHTgxn
- id: menu.context
kind: menu
layer: ui
name: Context Menu
x:
ui:
framework: React
docs:
description: 'Right-click context menu that adapts based on selection. For faces: Edit Material, Reverse Face, Intersect
Faces, Make Group/Component, Entity Info. For edges: Divide, Weld, Hide. For groups/components: Edit Group, Explode,
Make Unique, Lock. For empty space: Paste, Select All, Zoom Extents.'
impl:
status: implemented
uuid: xeMjbF7O
- id: panel.properties
kind: panel
layer: ui
name: Entity Info Panel
x:
ui:
framework: React
position: right
collapsible: true
resizable: true
docs:
description: Right-side panel showing properties of the selected entity. Displays entity type, layer, material, dimensions
(editable), area/volume calculations, and component definition info. Updates in real-time as selection changes.
impl:
status: implemented
uuid: Pe6CMGVv
- id: panel.outliner
kind: panel
layer: ui
name: Outliner Panel
x:
ui:
framework: React
position: right
collapsible: true
resizable: true
docs:
description: Hierarchical tree view of all scene objects (groups, components, loose geometry). Supports drag-and-drop
reordering, visibility toggles, lock toggles, and search/filter. Double-click to enter group/component editing context.
impl:
status: implemented
uuid: KyznaKNF
- id: panel.layers
kind: panel
layer: ui
name: Layers Panel
x:
ui:
framework: React
position: right
collapsible: true
docs:
description: Layer management panel (called 'Tags' in modern DraftDown). Create, rename, delete layers. Toggle visibility
and lock per layer. Assign selected entities to layers. Active layer indicator. Color-by-layer display mode.
impl:
status: implemented
uuid: vjwdEO3q
- id: panel.components
kind: panel
layer: ui
name: Components Panel
x:
ui:
framework: React
position: right
collapsible: true
resizable: true
docs:
description: Component browser panel. Lists all component definitions in the model. Shows thumbnail preview, instance
count, and description. Supports creating new components from selection, editing in-place, and importing from local
.skp files.
impl:
status: implemented
uuid: uliv0zgg
- id: panel.measurements
kind: panel
layer: ui
name: Measurements Panel
x:
ui:
framework: React
position: bottom
docs:
description: Bottom status bar showing the Measurements/VCB (Value Control Box). Displays current measurement while
drawing (length, angle, radius). Accepts typed input for precise values. Shows axis indicators (red=X, green=Y, blue=Z)
and inference engine status.
impl:
status: implemented
uuid: 146eQPLI
- id: tool.base
kind: service
layer: interaction
name: Tool Base (shared infrastructure)
x:
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: "Shared infrastructure every tool.* node extends, in implementations/tool.base/.\n BaseTool.ts:\
\ lifecycle (activate/deactivate/phase), VCB + status helpers, undo transactions,\n drawing-plane + axis-lock\
\ helpers, snap-aware point resolution, and classic-CAD-style directional\n inference (applyDirectionInference:\
\ auto axis snap, Shift-pin, parallel-to-edge — guides + labels).\n drawingPlanes.ts: DRAWING_PLANES /\
\ DrawingPlaneAxis arrow-key plane vocabulary. planeGeometry.ts:\n planeBasis (orthonormal in-plane axes)\
\ and rayPlaneIntersect — used by circle/polygon/rectangle/rotate.\n VertexTransformSession.ts: gather/snapshot/apply/restore\
\ of selection vertices — used by move/rotate/scale.\n GripOverlay.ts: clickable camera-scaled overlay grips\
\ with hover highlight — used by rotate (handles)\n and scale (grips). New tools should compose these instead\
\ of re-implementing them."
uuid: sToolBase
- id: tool.manager
kind: service
layer: interaction
name: Tool Manager
x:
impl:
language: TypeScript
complexity: simple
status: implemented
docs:
description: "Tool registry + activation (implementations/tool.manager/ToolManager.ts). Registers all\n 23 tools\
\ at Application.initialize, routes activate/deactivate, emits tool-changed events consumed\n by the React\
\ toolbar and ViewportCanvas."
uuid: sToolMgr1
- id: tool.select
kind: tool
layer: interaction
name: Select Tool
x:
tool:
category: select
shortcut: Space
cursor: pointer
icon: cursor-arrow
impl:
language: TypeScript
complexity: complex
status: implemented
docs:
description: "Point-pick on mouseDown with instant feedback. Shift+click toggles multi-select.\n\
\ Drag-box selection with visual overlay (blue solid=window, green dashed=crossing).\n\
\ Cursor changes to pointer over selectable entities. Pre-selection highlight on hover\n\
\ (orange for faces, orange glow tube for edges). Also hosts BaseTool and ToolManager.\n\
\ Resolves component IDs for component selection.\n\
\ Double-click on component enters editing mode (dimmed surroundings, blue bounding box).\n\
\ Escape exits component editing. Delete on component deletes all member geometry.\n\
\ BaseTool.resolveSelectedEntityIds() expands component IDs to member face/edge IDs.\n\
\ BaseTool provides default getEventNeeds(phase) based on category — tools override\n\
\ to declare snap/raycast/edgeRaycast/liveSyncOnMove/mutatesOnClick needs per phase.\n\
\ ToolEventNeeds drives unified buildToolEvent() in ViewportCanvas, eliminating\n\
\ hardcoded tool ID lists."
notes: "## Typed Interface\n\n```typescript\nimport { ITool, ToolContext, ToolEvent, MouseEvent3D, KeyEvent } from '../types/tool';\n\
import { SelectionManager, SelectionSet, SelectionMode } from '../data/selection';\nimport { SceneManager, Entity,\
\ EntityType } from '../data/scene';\nimport { RaycastResult } from '../rendering/raycast';\nimport { InferenceResult\
\ } from '../geometry/inference';\nimport { Rect2D, Vector2 } from '../math/types';\n\nexport interface SelectToolState\
\ {\n mode: 'idle' | 'clicking' | 'box-selecting' | 'dragging';\n boxStart: Vector2 | null;\n boxEnd: Vector2 |\
\ null;\n boxDirection: 'window' | 'crossing'; // left-to-right vs right-to-left\n lastClickTime: number;\n clickCount:\
\ number; // for double/triple click detection\n hoverEntity: Entity | null;\n}\n\nexport interface SelectToolConfig\
\ {\n doubleClickTimeout: number; // ms, default 300\n tripleClickTimeout: number; // ms, default 500\n dragThreshold:\
\ number; // pixels before click becomes drag, default 5\n boxSelectColor: { window: string; crossing: string };\n\
}\n\nexport class SelectTool implements ITool {\n readonly id = 'tool.select';\n readonly category = 'select';\n\
\ readonly shortcut = 'Space';\n readonly cursor = 'pointer';\n\n private state: SelectToolState;\n private config:\
\ SelectToolConfig;\n private ctx: ToolContext;\n\n // Lifecycle\n activate(ctx: ToolContext): void;\n deactivate():\
\ void;\n\n // Input handlers\n onMouseDown(event: MouseEvent3D): void;\n onMouseMove(event: MouseEvent3D): void;\n\
\ onMouseUp(event: MouseEvent3D): void;\n onKeyDown(event: KeyEvent): void;\n onKeyUp(event: KeyEvent): void;\n\
\n // Internal\n private performRaycast(event: MouseEvent3D): RaycastResult | null;\n private handleSingleClick(hit:\
\ RaycastResult | null, modifiers: { shift: boolean; ctrl: boolean }): void;\n private handleDoubleClick(hit: RaycastResult):\
\ void;\n private handleTripleClick(hit: RaycastResult): void;\n private updateBoxSelection(start: Vector2, end:\
\ Vector2, direction: 'window' | 'crossing'): void;\n private getEntitiesInBox(rect: Rect2D, mode: 'window' | 'crossing'):\
\ Entity[];\n private updatePreselectionHighlight(event: MouseEvent3D): void;\n}\n```\n\n## Selection Logic\n- **Single\
\ click**: `selectionManager.replace(hit.entity)` or `selectionManager.clear()` if no hit\n- **Shift+click**: `selectionManager.toggle(hit.entity)`\n\
- **Ctrl+click**: `selectionManager.add(hit.entity)`\n- **Double-click on group**: `sceneManager.enterEditingContext(hit.entity)`\n\
- **Triple-click**: `selectionManager.selectConnected(hit.entity)`\n- **Box select (window)**: Only entities fully\
\ inside the box\n- **Box select (crossing)**: Entities intersecting or inside the box\n\n## Pre-selection\nOn every\
\ mouseMove, raycast and highlight the entity under cursor with a subtle overlay (different from full selection highlight).\
\ This gives the user visual feedback before clicking."
uuid: ceUtTwvQ
- id: tool.line
kind: tool
layer: interaction
name: Line Tool
x:
tool:
category: draw
shortcut: L
cursor: crosshair
icon: line
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: "Multi-point line drawing with axis locking (Up=Y, Right=X, Left=Z, Down=unlock).\n Uses ray-to-axis\
\ projection for accurate 3D positioning from any camera angle.\n VCB input respects locked axis direction\
\ and cursor sign. Auto-face via createEdgeWithAutoFace.\n splitFaceWithPath on deactivate. findOrCreateVertex\
\ from BaseTool for vertex reuse.\n Commits on deactivate (not abort). Midpoint snap works via getStandardDrawPoint.\n\
\ Face plane lock: when the first click lands on a face (event.hitFaceId set),\n the tool captures\
\ that face's plane as lockedFacePlane. Subsequent points raycast onto\n the locked plane unless the snapped\
\ worldPoint already lies on it (within 0.01).\n This mirrors DraftDown behavior — drawing a line that starts\
\ on a face stays on the\n face even when the cursor strays into empty space."
notes: "## Typed Interface\n\n```typescript\nimport { ITool, ToolContext, MouseEvent3D, KeyEvent } from '../types/tool';\n\
import { Vector3 } from '../math/types';\nimport { InferenceResult, InferenceEngine } from '../geometry/inference';\n\
import { Edge, Face, Vertex } from '../geometry/mesh';\nimport { SceneManager } from '../data/scene';\nimport { HistoryManager,\
\ Transaction } from '../data/history';\n\nexport interface LineToolState {\n phase: 'idle' | 'drawing'; // idle\
\ = waiting for first click, drawing = placing subsequent points\n points: Vector3[]; // accumulated points in current\
\ polyline\n currentInference: InferenceResult | null;\n activeTransaction: Transaction | null;\n lockedAxis: 'x'\
\ | 'y' | 'z' | null; // arrow key axis lock\n}\n\nexport interface LineToolResult {\n edges: Edge[]; // newly created\
\ edges\n faces: Face[]; // auto-detected faces from closed loops\n vertices: Vertex[]; // new or snapped-to vertices\n\
}\n\nexport class LineTool implements ITool {\n readonly id = 'tool.line';\n readonly category = 'draw';\n readonly\
\ shortcut = 'L';\n readonly cursor = 'crosshair';\n\n private state: LineToolState;\n private ctx: ToolContext;\n\
\ private inference: InferenceEngine;\n\n // Lifecycle\n activate(ctx: ToolContext): void;\n deactivate(): void;\n\
\n // Input\n onMouseDown(event: MouseEvent3D): void; // place point\n onMouseMove(event: MouseEvent3D): void;\
\ // update inference preview\n onMouseUp(event: MouseEvent3D): void;\n onKeyDown(event: KeyEvent): void; // Esc=finish,\
\ Arrow=lock axis, Enter=finish\n onVCBInput(value: string): void; // typed distance like '500' or '300,200'\n\n\
\ // Core logic\n private placePoint(position: Vector3, inference: InferenceResult | null): void;\n private createEdge(from:\
\ Vector3, to: Vector3): Edge;\n private detectClosedLoop(edges: Edge[]): Face | null;\n private finishDrawing():\
\ LineToolResult;\n private updatePreview(cursorPos: Vector3, inference: InferenceResult | null): void;\n private\
\ drawRubberBandLine(from: Vector3, to: Vector3, axisColor?: string): void;\n}\n```\n\n## Face Auto-Detection\nAfter\
\ each edge is created, check if the new edge completes a closed loop of coplanar edges. If so, automatically create\
\ a Face from that loop. Algorithm:\n1. From the new edge's endpoint, traverse connected edges\n2. If a path returns\
\ to the start vertex and all edges are coplanar (within tolerance), create face\n3. Use the face normal consistent\
\ with the winding order\n\n## VCB Input\n- Single number `500`: set edge length to 500 in current direction\n- Two\
\ numbers `300,200`: set relative X,Y offset from start point\n- Three numbers `100,200,50`: set relative X,Y,Z offset\n\
- Negative numbers allowed for reverse direction"
uuid: WXBGmpCI
- id: tool.rectangle
kind: tool
layer: interaction
name: Rectangle Tool
x:
tool:
category: draw
shortcut: R
cursor: crosshair
icon: rectangle
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: "Two-click rectangle with arrow key plane switching (Right=YZ, Left=XY,\n Up=XZ, Down=reset).\
\ Uses raycastDrawingPlane for mouse position on active plane.\n VCB direction-aware: cursor quadrant determines\
\ sign of width/height.\n Degenerate check prevents faces with < 0.001 width or height.\n Preview\
\ shows dashed polygon outline that updates on plane change.\n Face-snap drawing plane: when the first click\
\ hits a face, getEffectiveDrawingPlane\n captures that face's plane as drawPlane. getPlaneAxes() builds two\
\ orthogonal axes\n that LIE IN the drawing plane via cross(normal, ref) × cross(normal, axis1) for any\n\
\ non-axis-aligned plane (e.g., slanted/diagonal faces) — earlier code used world\n axes which\
\ pushed two of the four corners off the plane. Axis-aligned planes keep\n the existing world-axis pairs for\
\ VCB compatibility."
uuid: QWmYWzAR
- id: tool.circle
kind: tool
layer: interaction
name: Circle Tool
x:
tool:
category: draw
shortcut: C
cursor: crosshair
icon: circle
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: "Draw circles by clicking center then radius point. Type radius in VCB for exact size.\n Configurable\
\ segment count (default 24, type 's' + number). Arrow keys change drawing plane.\n Creates edges with intersection\
\ detection (createEdgeWithIntersection), groups edges\n with a shared curveId. Explicitly creates face from\
\ vertex loop since autoCreateFaces\n BFS maxDepth=20 is too small for 24-segment circles. Snaps center to\
\ existing geometry."
uuid: wnTrJPzS
- id: tool.arc
kind: tool
layer: interaction
name: Arc Tool
x:
tool:
category: draw
shortcut: A
cursor: crosshair
icon: arc
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: 'Draw arcs with 3 clicks: start point, end point, then bulge distance. Type radius or number of segments
in VCB. Creates arc edges that form tangent connections with adjacent lines. Supports 2-point arc (start/end + bulge)
and pie arc modes.'
uuid: 28E5lsDz
- id: tool.polygon
kind: tool
layer: interaction
name: Polygon Tool
x:
tool:
category: draw
shortcut: Shift+P
cursor: crosshair
icon: polygon
impl:
language: TypeScript
complexity: simple
status: implemented
docs:
description: Draw regular polygons (3-100 sides). Type number of sides before clicking center. Click center, then drag
to set radius. Creates a face with N edges. Default is hexagon (6 sides).
uuid: wBEeXzL9
- id: tool.pushpull
kind: tool
layer: interaction
name: Push/Pull Tool
x:
tool:
category: modify
shortcut: P
cursor: move
icon: push-pull
impl:
language: TypeScript
complexity: complex
status: implemented
docs:
description: "Dual-mode push/pull: 2D standalone faces extrude (create side walls + top cap),\n 3D faces with\
\ existing side walls just move vertices (walls stretch automatically).\n Detects mode via hasSideWalls()\
\ — checks adjacent face normals for non-coplanar neighbors.\n Recomputes face normal from current vertex\
\ positions (Newell's method) so push/pull\n works correctly on rotated geometry. Screen-space Y for distance.\
\ Pre-selected face auto-starts.\n Preview shows top face outline + vertical guide lines. VCB for exact distance."
notes: "## Typed Interface\n\n```typescript\nimport { ITool, ToolContext, MouseEvent3D, KeyEvent } from '../types/tool';\n\
import { Face, Mesh, Edge, Vertex } from '../geometry/mesh';\nimport { Vector3 } from '../math/types';\nimport { InferenceResult\
\ } from '../geometry/inference';\nimport { ExtrudeOperation, ExtrudeParams, ExtrudeResult } from '../operations/extrude';\n\
import { HistoryManager, Transaction } from '../data/history';\n\nexport interface PushPullState {\n phase: 'idle'\
\ | 'face-picked' | 'dragging';\n targetFace: Face | null;\n faceNormal: Vector3 | null;\n dragStartPoint: Vector3\
\ | null;\n currentDistance: number;\n lastDistance: number | null; // for double-click repeat\n createNewFace:\
\ boolean; // Ctrl mode - leave original face\n preview: ExtrudeResult | null;\n activeTransaction: Transaction\
\ | null;\n}\n\nexport interface PushPullConfig {\n minDistance: number; // epsilon to avoid zero-height extrusions\n\
\ snapToExistingFaces: boolean; // snap to coplanar faces during drag\n autoBooleanSubtract: boolean; // push into\
\ solid = boolean cut\n}\n\nexport class PushPullTool implements ITool {\n readonly id = 'tool.pushpull';\n readonly\
\ category = 'modify';\n readonly shortcut = 'P';\n readonly cursor = 'move';\n\n private state: PushPullState;\n\
\ private config: PushPullConfig;\n private extrudeOp: ExtrudeOperation;\n\n // Lifecycle\n activate(ctx: ToolContext):\
\ void;\n deactivate(): void;\n\n // Input\n onMouseDown(event: MouseEvent3D): void;\n onMouseMove(event: MouseEvent3D):\
\ void;\n onMouseUp(event: MouseEvent3D): void;\n onDoubleClick(event: MouseEvent3D): void; // repeat last distance\n\
\ onKeyDown(event: KeyEvent): void; // Ctrl=new face mode, Esc=cancel\n onVCBInput(value: string): void; // exact\
\ distance\n\n // Core logic\n private pickFace(event: MouseEvent3D): Face | null;\n private computeDistance(event:\
\ MouseEvent3D): number;\n private updatePreview(distance: number): void;\n private commitExtrude(distance: number):\
\ ExtrudeResult;\n private detectBooleanCut(face: Face, direction: Vector3, distance: number): boolean;\n private\
\ performBooleanCut(face: Face, distance: number): ExtrudeResult;\n}\n```\n\n## Extrusion Algorithm\n1. Pick a face\
\ → get face normal as extrude direction\n2. For each boundary edge of the face, create a side face (quad)\n3. Clone\
\ the original face and translate by `normal * distance`\n4. Connect side faces to cloned face edges\n5. If `distance\
\ < 0`, flip the direction (push into solid)\n6. If pushing into an existing solid, delegate to `op.boolean_subtract`\n\
7. Merge coplanar adjacent faces when applicable\n\n## Double-Click Repeat\nStore `lastDistance` as static across\
\ tool activations. On double-click, pick face and immediately extrude by `lastDistance`."
uuid: XlCNBL59
- id: tool.move
kind: tool
layer: interaction
name: Move Tool
x:
tool:
category: modify
shortcut: M
cursor: move
icon: move
impl:
language: TypeScript
complexity: complex
status: implemented
docs:
description: "Live vertex movement with preview. Gathers vertices from all selected faces/edges\n (resolves\
\ component IDs). Saves/restores original positions for cancel. VCB distance\n follows cursor direction.\n\
\ Arrow-key axis lock (Up=Y, Right=X, Left=Z, Down=unlock) — matches Line tool\n convention. The\
\ lock applies to the live preview, the VCB distance display, and\n typed-distance VCB input (signs taken\
\ from the cursor's side along the locked axis).\n The lock auto-clears between move operations via reset()."
notes: "## Typed Interface\n\n```typescript\nimport { ITool, ToolContext, MouseEvent3D, KeyEvent } from '../types/tool';\n\
import { Entity } from '../data/scene';\nimport { Vector3 } from '../math/types';\nimport { InferenceResult } from\
\ '../geometry/inference';\nimport { SelectionManager } from '../data/selection';\nimport { HistoryManager, Transaction\
\ } from '../data/history';\n\nexport interface MoveToolState {\n phase: 'idle' | 'base-point-picked' | 'moving';\n\
\ entities: Entity[]; // entities being moved\n basePoint: Vector3 | null;\n currentOffset: Vector3;\n copyMode:\
\ boolean; // Ctrl held = copy instead of move\n lockedAxis: 'x' | 'y' | 'z' | null;\n lastMoveVector: Vector3 |\
\ null; // for array repeat\n activeTransaction: Transaction | null;\n}\n\nexport interface MoveResult {\n movedEntities:\
\ Entity[];\n copiedEntities: Entity[] | null; // if copy mode\n displacement: Vector3;\n}\n\nexport class MoveTool\
\ implements ITool {\n readonly id = 'tool.move';\n readonly category = 'modify';\n readonly shortcut = 'M';\n\
\ readonly cursor = 'move';\n\n // Lifecycle\n activate(ctx: ToolContext): void;\n deactivate(): void;\n\n //\
\ Input\n onMouseDown(event: MouseEvent3D): void; // pick base point or destination\n onMouseMove(event: MouseEvent3D):\
\ void; // preview move\n onMouseUp(event: MouseEvent3D): void;\n onKeyDown(event: KeyEvent): void; // Ctrl=copy,\
\ Arrow=lock axis, Esc=cancel\n onVCBInput(value: string): void; // '500' = distance, '100,200,50' = offset, 'x3'\
\ = array\n\n // Core\n private pickBasePoint(event: MouseEvent3D): Vector3;\n private computeDisplacement(event:\
\ MouseEvent3D): Vector3;\n private applyMove(entities: Entity[], displacement: Vector3): MoveResult;\n private\
\ applyCopy(entities: Entity[], displacement: Vector3): MoveResult;\n private createLinearArray(entities: Entity[],\
\ displacement: Vector3, count: number): Entity[];\n private handleAutoFold(entity: Entity, displacement: Vector3):\
\ void; // fold connected faces\n}\n```\n\n## Auto-Fold\nWhen moving a vertex or edge that is shared between faces,\
\ and the move would break coplanarity, automatically fold the connected faces along the shared edge rather than creating\
\ invalid geometry.\n\n## VCB Array Input\nAfter a copy-move, typing `x3` creates 2 additional copies (3 total) evenly\
\ spaced. Typing `/3` divides the distance into 3 equal copies."
uuid: 56EqChJo
- id: tool.rotate
kind: tool
layer: interaction
name: Rotate Tool
x:
tool:
category: modify
shortcut: Q
cursor: rotate
icon: rotate
impl:
language: TypeScript
complexity: complex
status: implemented
docs:
description: "Three-step rotation: click handle for center, click for start angle, drag to rotate.\n Arrow\
\ keys switch rotation axis: Up=Green/Y, Right=Red/X, Left=Blue/Z, Down=reset.\n Toggle behavior — pressing\
\ same arrow key twice resets to Y. Bounding box handles\n update to show rotation plane for current axis.\
\ Protractor circle + axis line drawn\n in overlay scene. Rodrigues rotation formula for vertex transforms.\n\
\ VCB accepts degrees for exact angle. Handles scale to camera distance."
uuid: WZyKJ6DX
- id: tool.scale
kind: tool
layer: interaction
name: Scale Tool
x:
tool:
category: modify
shortcut: S
cursor: scale
icon: scale
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: "Shows 9 green grip handles (4 corners + 4 edge midpoints + 1 center) with yellow\n\
\ bounding box and dotted extension lines. Edge midpoint grips constrain scaling to single\n\
\ axis (moves just that side). Corner grips scale both axes. Grips maintain constant screen size."
uuid: PIor44v3
- id: tool.offset
kind: tool
layer: interaction
name: Offset Tool
x:
tool:
category: modify
shortcut: F
cursor: crosshair
icon: offset
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: "Click face, drag to inset/outset. Preview shows dashed offset polygon.\n Creates inner face +\
\ connecting ring. Replaces original face."
uuid: 3ZdOJaJh
- id: tool.eraser
kind: tool
layer: interaction
name: Eraser Tool
x:
tool:
category: modify
shortcut: E
cursor: pointer
icon: eraser
impl:
language: TypeScript
complexity: simple
status: implemented
docs:
description: "Click/drag to delete faces and edges. Pre-selection highlight shows what will\n be deleted. Each\
\ click wraps in transaction for undo support."
uuid: 96zZ0Ia9
- id: tool.paint
kind: tool
layer: interaction
name: Paint Bucket Tool
x:
tool:
category: modify
shortcut: B
cursor: pointer
icon: paint-bucket
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: Apply materials/colors/textures to faces. Click to paint, Shift+click to sample, Alt+click to fill all
faces with the same current material. Material selection via ToolSettingsPanel (right sidebar) with swatch grid showing
color and procedural texture thumbnails. Supports PBR materials with albedo color, texture map, opacity, roughness,
metalness. Paint operations are undoable.
uuid: 2gnrR81m
- id: tool.orbit
kind: tool
layer: interaction
name: Orbit Tool
x:
tool:
category: view
shortcut: O
cursor: grab
icon: orbit
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: Orbit the camera around the model. Click+drag to orbit. Middle-mouse-button activates orbit from any tool.
Shift+middle-mouse pans. Scroll wheel zooms. Ctrl+Shift constrains to vertical orbit. Centers orbit around clicked
point on geometry.
uuid: J8odz0eM
- id: tool.pan
kind: tool
layer: interaction
name: Pan Tool
x:
tool:
category: view
shortcut: H
cursor: grab
icon: pan
impl:
language: TypeScript
complexity: simple
status: implemented
docs:
description: Pan the camera view. Click+drag to pan. Shift+middle-mouse activates pan from any tool. Maintains current
zoom level and orbit center relative to panned position.
uuid: 1NLwLQgx
- id: tool.zoom
kind: tool
layer: interaction
name: Zoom Tool
x:
tool:
category: view
shortcut: Z
cursor: pointer
icon: zoom
impl:
language: TypeScript
complexity: simple
status: implemented
docs:
description: Zoom in/out. Click+drag up to zoom in, down to zoom out. Scroll wheel zooms toward cursor position. Shift+Z
for Zoom Extents (fit all geometry in view). Ctrl+Shift+Z for Zoom Window (draw box to zoom to). Type FOV angle in
VCB to change field of view.
uuid: aybhjdyU
- id: tool.tape_measure
kind: tool
layer: interaction
name: Tape Measure Tool
x:
tool:
category: measure
shortcut: T
cursor: crosshair
icon: tape-measure
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: "Click two points to measure distance. Live preview line during placement.\n Creates dashed construction\
\ guide line between measured points (linewidth=2,\n solid black, selectable). VCB input for exact distance\n\
\ in current cursor direction. Uses getStandardDrawPoint and findOrCreateVertex\n from BaseTool.\
\ Supports arrow key plane switching. Fully undoable — wraps\n measurement in beginTransaction/commitTransaction\
\ and records guide lines via\n HistoryManager.recordGuideLine() for undo/redo support; deletions are recorded\n\
\ via recordGuideLineRemoval() (added to DeltaTransaction.guideLineRemovals) so\n select-and-Delete\
\ on a guide line is also undoable. The guide line is registered\n with the renderer's _entityObjects so the\
\ existing GPU pick + Line highlight\n pipeline (blue + glow tube) treats it as a first-class selectable entity."
uuid: 9yqt3Wjr
- id: tool.protractor
kind: tool
layer: interaction
name: Protractor Tool
x:
tool:
category: measure
shortcut: Shift+T
cursor: crosshair
icon: protractor
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: "Three-click angle measurement (center, baseline, angle point) with live preview\n showing both\
\ baseline and angle lines. Places dashed construction guide line at measured angle.\n VCB input for exact\
\ degrees. Guide extends 50 units from center."
uuid: p5t0Ix1W
- id: tool.sweep_tool
kind: tool
layer: interaction
name: Sweep Tool
x:
tool:
category: modify
shortcut: Shift+F
cursor: crosshair
icon: sweep
impl:
language: TypeScript
complexity: complex
status: implemented
docs:
description: "Select face as profile, click edge to sweep along connected edges. Collects\n path by walking\
\ connected edges avoiding profile boundary. Calls SweepOperation which\n generates cross-section rings at\
\ each path vertex and connects them with quad faces.\n Supports pre-selected face from selection."
uuid: udD539qm
- id: tool.dimension
kind: tool
layer: interaction
name: Dimension Tool
x:
tool:
category: annotation
shortcut: D
cursor: crosshair
icon: dimension
impl:
language: TypeScript
complexity: moderate
status: implemented
docs:
description: "Three-step workflow (start, end, offset) places linear dimensions with extension lines,\n\
\ tick marks, and text sprite. Displays values with unit labels (mm, cm, m, \", ') at most\n\
\ 1 decimal place via formatDistance(). Dimensions are selectable and movable (constrained\n\
\ perpendicular to measurement line). Associative — tracks vertex IDs and updates when\n\
\ geometry moves. DimensionStore.syncToGeometry() updates sprite text with current units.\n\
\ Fully undoable — HistoryManager snapshots DimensionDelta (serializable, no Three.js objects)\n\
\ at begin/commit time. Undo removes dimensions and guide lines; redo recreates them\n\
\ via Application.recreateDimension(). Uses DimensionStore singleton for state management."
uuid: gHVP0JyU
- id: tool.text
kind: tool
layer: interaction
name: 3D Text / Label Tool
x:
tool:
category: annotation
shortcut: Shift+D
cursor: crosshair
icon: text
impl:
language: TypeScript
complexity: simple
status: implemented
docs:
description: "Creates real vector geometry (edges + faces) using Three.js Font system.\n Opens floating dialog\
\ with text input, font selection (Helvetica, Gentilis, Optimer + bold variants),\n size, and color. Text\
\ placed as actual edges/faces on the ground plane — selectable, movable, push/pullable."
uuid: LUHz0ZsR
- id: gesture.click
kind: gesture
layer: interaction
name: Click Gesture
x:
gesture:
inputType: mouse
pattern: click
modifiers: ''
docs:
description: Single left-click — primary action for all tools. Used for point placement, entity selection, face picking.
impl:
status: implemented
uuid: AD2u34xM
- id: gesture.double_click
kind: gesture
layer: interaction
name: Double-Click Gesture
x:
gesture:
inputType: mouse
pattern: double-click
modifiers: ''
docs:
description: 'Double left-click. In Select tool: enter group/component editing context. In Push/Pull: repeat last distance.
In Offset: repeat last offset.'
impl:
status: implemented
uuid: gj5q2nlZ
- id: gesture.drag
kind: gesture
layer: interaction
name: Drag Gesture
x:
gesture:
inputType: mouse
pattern: click-drag
modifiers: ''
docs:
description: Left-click and drag. Used by Move, Rotate, Scale, Push/Pull for interactive manipulation. Also used by
Select for box selection.
impl:
status: implemented
uuid: Q9tmi8PP
- id: gesture.middle_drag
kind: gesture
layer: interaction
name: Middle Mouse Drag
x:
gesture:
inputType: mouse
pattern: middle-drag
modifiers: ''
docs:
description: Middle mouse button drag activates Orbit from any tool. Most important navigation gesture — allows orbiting
without switching tools.
impl:
status: implemented
uuid: 8lnC03XS
- id: gesture.shift_middle_drag
kind: gesture
layer: interaction
name: Shift + Middle Mouse Drag
x:
gesture:
inputType: mouse
pattern: middle-drag
modifiers: Shift
docs:
description: Shift + middle mouse drag activates Pan from any tool. Combined with middle-drag orbit and scroll zoom,
provides complete navigation without changing tools.
impl:
status: implemented
uuid: f13DpSyN
- id: gesture.scroll
kind: gesture
layer: interaction
name: Scroll Wheel Gesture
x:
gesture:
inputType: mouse
pattern: wheel
modifiers: ''
docs:
description: Mouse scroll wheel zooms toward/away from cursor position. Zoom centers on the 3D point under the cursor
for intuitive navigation. Smooth scrolling supported.
impl:
status: implemented
uuid: zMGzQ3lu
- id: gesture.keyboard_value
kind: gesture
layer: interaction
name: Keyboard Value Input
x:
gesture:
inputType: keyboard
pattern: type-number
modifiers: ''
docs:
description: 'Typing numbers during any tool operation enters the value directly into the VCB (Value Control Box). For
Line: sets length. For Rectangle: sets width,height. For Rotate: sets angle. For Move: sets distance. Tab switches
between VCB fields. Enter confirms.'
impl:
status: implemented
uuid: EgeltoK9
- id: viewport.main
kind: viewport
layer: rendering
name: Main 3D Viewport
x:
viewport:
renderMode: shaded
projection: perspective
grid: true
axes: true
background: gradient
impl:
language: TypeScript
framework: Three.js
complexity: complex
status: implemented
docs:
description: "Canvas via Three.js setSize. ResizeObserver syncs dimensions.\n\
\ projectionMatrixInverse manually recomputed before every raycast. Raycasts both main\n\
\ and overlay scenes. Edge threshold scales with camera distance. Edges returned before\n\
\ faces for reliable edge selection. Middle mouse orbit/pan with window-level mouseup\n\
\ listener and e.buttons===0 safety check.\n\
\ Unified event system: single buildToolEvent(e, needs) replaces three separate event\n\
\ builders. GPU pick always runs first (O(1)), raycast/edge-raycast/snap are additive\n\
\ based on tool-declared ToolEventNeeds metadata. Tools declare needs via getEventNeeds(phase)\n\
\ — no hardcoded tool ID lists in ViewportCanvas. Throttled mousemove at 16ms (~60fps).\n\
\ GPU pick results resolved through scene manager: layer visibility, locked state, and\n\
\ component protection — protected entities return component ID instead of member entity ID.\n\
\ resolveHit() applies same checks for raycast/edge-raycast fallback paths."
uuid: f8kRBGQr
- id: renderer.webgl
kind: renderer
layer: rendering
name: WebGL Renderer
x:
renderer:
engine: Three.js
antialiasing: MSAA
shadows: true
postProcessing: SSAO, edge detection outlines, selection highlight glow
perf:
critical: true
optimizations: Frustum culling, instanced rendering for components, LOD for distant objects, GPU picking for selection,
batched geometry for draw call reduction, Earcut triangulation cache (reuse tri indices when face topology unchanged),
in-place Float32Array buffer writes for faces and edges (skip geometry disposal/creation during transforms),
dirty vertex tracking with vertex→face/edge adjacency for targeted sync iteration (O(dirty) not O(all)),
deferred component bounding box updates (unit-box with scale transform, skip unaffected components)
benchmarks: Target 60fps for models up to 500K faces, 30fps for models up to 2M faces
impl:
language: TypeScript
framework: Three.js
complexity: very-complex
status: implemented
docs:
description: "Three.js WebGL renderer with ambient+directional+hemisphere lighting, logarithmicDepthBuffer,\n\
\ and localClippingEnabled. Infinite shader-based ground grid. Face highlighting uses an\n\
\ OVERLAY mesh approach (not material swap) — when a face is selected/pre-selected the\n\
\ original textured material stays attached, and a translucent tinted overlay mesh sharing\n\
\ the same geometry is added on top so the texture shows through the highlight tint. The\n\
\ per-entity overlays are tracked in _faceHighlightOverlays and removed on un-highlight.\n\
\ Edge highlighting still swaps the line material and adds a camera-distance-scaled glow\n\
\ tube cylinder. Pre-selection=orange, selection=blue. Edge lines in main scene\n\
\ (depth-tested against faces, polygonOffset on faces prevents z-fighting).\n\
\ setSectionPlane() for real-time clipping. GPU picking: renders entity IDs as unique\n\
\ colors to offscreen framebuffer via ShaderMaterial (bypasses Three.js color management).\n\
\ Per-entity pick meshes for small models, vertex-color-encoded batched pick mesh for large\n\
\ imported models. Pick buffer re-rendered only on camera move. Batched mode: syncBatched()\n\
\ merges all faces into single BufferGeometry with frustumCulled=false, castShadow=false.\n\