@pascal-app/editor 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +63 -25
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/export-manager.tsx +71 -63
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +36 -3
- package/src/components/editor/first-person/build-collider-world.ts +32 -5
- package/src/components/editor/first-person-controls.tsx +118 -7
- package/src/components/editor/floating-action-menu.tsx +410 -33
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +1804 -849
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +457 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +102 -64
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +192 -1
- package/src/components/editor/group-transform-shared.ts +240 -7
- package/src/components/editor/handles/handle-arrow.tsx +81 -23
- package/src/components/editor/handles/use-handle-drag.ts +37 -4
- package/src/components/editor/index.tsx +248 -27
- package/src/components/editor/measurement-pill.tsx +62 -22
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +366 -173
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +812 -466
- package/src/components/editor/site-edge-labels.tsx +4 -4
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +255 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +136 -43
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +3 -13
- package/src/components/editor/wall-move-side-handles.tsx +36 -21
- package/src/components/editor/wall-snap-beacon-layer.tsx +160 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +1 -1
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +169 -4
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +300 -59
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1704 -342
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +419 -60
- package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
- package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-tool.tsx +34 -8
- package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
- package/src/components/tools/fence/fence-drafting.ts +80 -8
- package/src/components/tools/item/move-tool.tsx +17 -13
- package/src/components/tools/item/placement-math.test.ts +13 -1
- package/src/components/tools/item/placement-math.ts +28 -2
- package/src/components/tools/item/placement-strategies.ts +246 -5
- package/src/components/tools/item/placement-types.ts +13 -1
- package/src/components/tools/item/use-draft-node.ts +31 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +761 -121
- package/src/components/tools/registry/move-registry-node-tool.tsx +561 -99
- package/src/components/tools/roof/roof-tool.tsx +320 -94
- package/src/components/tools/select/box-select-state.ts +18 -7
- package/src/components/tools/select/box-select-tool.tsx +176 -40
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
- package/src/components/tools/select/select-candidates.ts +1 -1
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +88 -36
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +82 -11
- package/src/components/tools/tool-manager.tsx +86 -25
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +180 -103
- package/src/components/tools/wall/wall-snap-geometry.test.ts +44 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +67 -6
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +82 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -93
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +2 -4
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +22 -13
- package/src/components/ui/action-menu/view-toggles.tsx +42 -90
- package/src/components/ui/command-palette/editor-commands.tsx +2 -2
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +86 -17
- package/src/components/ui/controls/material-picker.tsx +83 -152
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +118 -44
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/floating-level-selector.tsx +1 -1
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +227 -14
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +22 -13
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +15 -2
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +71 -40
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +60 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +40 -17
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +42 -28
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer-overlay.tsx +85 -10
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +54 -7
- package/src/hooks/use-ceiling-events.ts +3 -2
- package/src/hooks/use-drag-action.ts +4 -1
- package/src/hooks/use-keyboard.ts +385 -40
- package/src/index.tsx +219 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +3 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/fresh-planar-placement.test.ts +240 -3
- package/src/lib/fresh-planar-placement.ts +68 -1
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +9 -0
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +36 -49
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +183 -0
- package/src/lib/measurements.ts +199 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/planar-cursor-placement.test.ts +57 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +6 -6
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +196 -0
- package/src/lib/scene-clipboard.ts +73 -8
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +50 -13
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +111 -66
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +484 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +11 -3
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +43 -0
- package/src/store/use-wall-snap-indicator.ts +2 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/group-move-handle.tsx +0 -316
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/lib/level-name.ts +0 -11
package/src/index.tsx
CHANGED
|
@@ -11,8 +11,57 @@ export { default as Editor } from './components/editor'
|
|
|
11
11
|
// they're referenced throughout the editor's own internals; the public
|
|
12
12
|
// surface uses the shorter, shell-friendly names from the unified
|
|
13
13
|
// preset-system spec.
|
|
14
|
+
export { BakeExporter } from './components/editor/bake-exporter'
|
|
14
15
|
export { FloatingActionMenu as FloatingMenu } from './components/editor/floating-action-menu'
|
|
15
|
-
|
|
16
|
+
// Embed surface — the editor's real in-canvas affordances, so a host can mount
|
|
17
|
+
// authentic selection handles, interactive build tools, and the mover on top
|
|
18
|
+
// of a bare `<Viewer>` without the full `<Editor>` shell.
|
|
19
|
+
// - `NodeArrowHandles` renders the selected node's registry resize/rotate/move
|
|
20
|
+
// handles.
|
|
21
|
+
// - `MoveTool` runs the kind-owned mover once a translate handle arms
|
|
22
|
+
// `useEditor.movingNode`.
|
|
23
|
+
// - `ToolManager` mounts the active registry build tool (wall / door / window /
|
|
24
|
+
// …) for interactive placement when `useEditor` is in build mode with a
|
|
25
|
+
// tool, plus the snap/alignment guide layers. Mount it only while a tool is
|
|
26
|
+
// active to avoid its select-mode boundary editors.
|
|
27
|
+
// - `Grid` is the interactive drafting plane: it raycasts the pointer and
|
|
28
|
+
// emits the `grid:move` / `grid:click` events the build tools consume (the
|
|
29
|
+
// wall tool is driven entirely by them; door/window use them for free-follow
|
|
30
|
+
// alongside the viewer's `wall:*` mesh events). Without it the tools mount
|
|
31
|
+
// but their cursor never tracks the pointer. Mount it while a tool is active.
|
|
32
|
+
// All read `useViewer` selection + `useEditor` state, and cooperate with host
|
|
33
|
+
// camera controls via the `useViewer.inputDragging` / `useEditor.movingNode`
|
|
34
|
+
// flags. Tools place onto `useViewer.selection.levelId`, so the host must set a
|
|
35
|
+
// building + level selection first.
|
|
36
|
+
export { Grid } from './components/editor/grid'
|
|
37
|
+
export {
|
|
38
|
+
DimensionPill,
|
|
39
|
+
type DimensionPillPart,
|
|
40
|
+
formatMeasurement,
|
|
41
|
+
MeasurementPill,
|
|
42
|
+
} from './components/editor/measurement-pill'
|
|
43
|
+
// In-world arrow handle primitives (chevron geometry, invisible hit area,
|
|
44
|
+
// shared material, palette + scale constants). Re-exported so kind-owned
|
|
45
|
+
// 3D selection affordances in `@pascal-app/nodes` (duct side-move / height /
|
|
46
|
+
// extend arrows) reuse the same UI family as the wall / fence side handles.
|
|
47
|
+
export {
|
|
48
|
+
ARROW_COLOR,
|
|
49
|
+
ARROW_HOVER_COLOR,
|
|
50
|
+
ARROW_SCALE,
|
|
51
|
+
createArrowHandleGeometry,
|
|
52
|
+
createArrowHitAreaGeometry,
|
|
53
|
+
HandleArrow,
|
|
54
|
+
type HandleArrowInputShape,
|
|
55
|
+
type HandleArrowPlacement,
|
|
56
|
+
type HandleArrowProps,
|
|
57
|
+
InvisibleHandleHitArea,
|
|
58
|
+
NO_RAYCAST,
|
|
59
|
+
NodeArrowHandles,
|
|
60
|
+
swallowNextClick,
|
|
61
|
+
useArrowMaterial,
|
|
62
|
+
useInvisibleHitAreaMaterial,
|
|
63
|
+
} from './components/editor/node-arrow-handles'
|
|
64
|
+
export { QuickMeasurementCard } from './components/editor/quick-measurement-card'
|
|
16
65
|
export {
|
|
17
66
|
type SnapshotCameraData,
|
|
18
67
|
ThumbnailGenerator,
|
|
@@ -32,9 +81,11 @@ export {
|
|
|
32
81
|
// shared/segment-angle.ts) once every Stage D port is in.
|
|
33
82
|
export {
|
|
34
83
|
createFenceOnCurrentLevel,
|
|
84
|
+
createSplineFenceOnCurrentLevel,
|
|
35
85
|
type FencePlanPoint,
|
|
36
86
|
snapFenceDraftPoint,
|
|
37
87
|
} from './components/tools/fence/fence-drafting'
|
|
88
|
+
export { MoveTool } from './components/tools/item/move-tool'
|
|
38
89
|
// Placement-math helpers — shared by kind-owned placement tools in
|
|
39
90
|
// `@pascal-app/nodes` (wall curve sagitta snap, door / window placement,
|
|
40
91
|
// item drop) so kinds don't reach into editor internals.
|
|
@@ -62,9 +113,11 @@ export { CursorSphere } from './components/tools/shared/cursor-sphere'
|
|
|
62
113
|
export { DragBoundingBox } from './components/tools/shared/drag-bounding-box'
|
|
63
114
|
export { getFloorStackPreviewPosition } from './components/tools/shared/floor-stack-preview'
|
|
64
115
|
export { useFreshPlacementVisibility } from './components/tools/shared/fresh-placement-visibility'
|
|
116
|
+
export { PlacementBox } from './components/tools/shared/placement-box'
|
|
65
117
|
// Phase 5 Stage D — PolygonEditor for slab/ceiling boundary + hole editors.
|
|
66
118
|
export {
|
|
67
119
|
PolygonEditor,
|
|
120
|
+
type PolygonEditorPlanPointSnapContext,
|
|
68
121
|
type PolygonEditorProps,
|
|
69
122
|
} from './components/tools/shared/polygon-editor'
|
|
70
123
|
export {
|
|
@@ -95,19 +148,24 @@ export {
|
|
|
95
148
|
DEFAULT_STAIR_TYPE,
|
|
96
149
|
DEFAULT_STAIR_WIDTH,
|
|
97
150
|
} from './components/tools/stair/stair-defaults'
|
|
151
|
+
export { ToolManager } from './components/tools/tool-manager'
|
|
98
152
|
export {
|
|
153
|
+
chainEndJoinsExistingWall,
|
|
99
154
|
createWallOnCurrentLevel,
|
|
100
155
|
getSegmentGridStep,
|
|
101
156
|
isSegmentLongEnough,
|
|
157
|
+
resolveEndpointWallSplit,
|
|
102
158
|
snapPointToGrid,
|
|
103
159
|
snapScalarToGrid,
|
|
104
160
|
snapWallDraftPoint,
|
|
105
161
|
snapWallDraftPointDetailed,
|
|
106
|
-
|
|
162
|
+
WALL_CONNECT_SNAP_RADIUS,
|
|
107
163
|
WALL_GRID_STEP,
|
|
164
|
+
WALL_JOIN_SNAP_RADIUS,
|
|
108
165
|
type WallDraftSnapKind,
|
|
109
166
|
type WallDraftSnapResult,
|
|
110
167
|
type WallPlanPoint,
|
|
168
|
+
type WallSnapRadii,
|
|
111
169
|
} from './components/tools/wall/wall-drafting'
|
|
112
170
|
// `ToolbarLeft` / `ToolbarRight` are the headless-spec aliases for the
|
|
113
171
|
// existing `ViewerToolbarLeft` / `ViewerToolbarRight` exports — the
|
|
@@ -164,6 +222,13 @@ export {
|
|
|
164
222
|
} from './components/ui/sidebar/panels/settings-panel'
|
|
165
223
|
export type { SitePanelProps } from './components/ui/sidebar/panels/site-panel'
|
|
166
224
|
export type { SidebarTab } from './components/ui/sidebar/tab-bar'
|
|
225
|
+
export {
|
|
226
|
+
resolveAssetSnapTarget,
|
|
227
|
+
resolveNodeSnapTarget,
|
|
228
|
+
type SnapTarget,
|
|
229
|
+
SnapTargetBadge,
|
|
230
|
+
SnapTargetIcon,
|
|
231
|
+
} from './components/ui/snap-target-badge'
|
|
167
232
|
export type { SaveStatus } from './hooks/use-auto-save'
|
|
168
233
|
// useDragAction is the React-side glue for the registry's DragAction
|
|
169
234
|
// primitive. Public so registry-driven kinds (Phase 5+ Stage D ports)
|
|
@@ -172,8 +237,28 @@ export { type UseDragActionArgs, useDragAction } from './hooks/use-drag-action'
|
|
|
172
237
|
// Phase 5 Stage D — extras for kind-owned placement tools (FenceTool etc.).
|
|
173
238
|
export { markToolCancelConsumed } from './hooks/use-keyboard'
|
|
174
239
|
export { type Selection, useSelection } from './hooks/use-selection'
|
|
240
|
+
export {
|
|
241
|
+
clearPlacementSurface,
|
|
242
|
+
getPlacementSurface,
|
|
243
|
+
type PlacementSurface,
|
|
244
|
+
publishPlacementSurface,
|
|
245
|
+
} from './lib/active-placement-surface'
|
|
246
|
+
export {
|
|
247
|
+
CEILING_ALIGNMENT_THRESHOLD_M,
|
|
248
|
+
type CeilingPlanSnapInput,
|
|
249
|
+
type CeilingPlanSnapResult,
|
|
250
|
+
clearCeilingSnapFeedback,
|
|
251
|
+
resolveCeilingPlanPointSnap,
|
|
252
|
+
} from './lib/ceiling-plan-snap'
|
|
175
253
|
export { EDITOR_LAYER } from './lib/constants'
|
|
176
254
|
// Helper libs used by the kind-owned roof / stair / elevator panels.
|
|
255
|
+
export {
|
|
256
|
+
CONTINUATION_PROFILES,
|
|
257
|
+
type ContinuationContext,
|
|
258
|
+
type ContinuationMode,
|
|
259
|
+
continuationContextOf,
|
|
260
|
+
nextContinuation,
|
|
261
|
+
} from './lib/continuation'
|
|
177
262
|
export {
|
|
178
263
|
resolveCurrentBuildingId,
|
|
179
264
|
resolveElevatorNodeSupportY,
|
|
@@ -201,15 +286,55 @@ export {
|
|
|
201
286
|
getFloorplanWallThickness,
|
|
202
287
|
} from './lib/floorplan'
|
|
203
288
|
export { commitFreshPlacementSubtree } from './lib/fresh-planar-placement'
|
|
289
|
+
export { exportSceneToGlb } from './lib/glb-export'
|
|
290
|
+
export {
|
|
291
|
+
boundaryReshapeScope,
|
|
292
|
+
curveReshapeScope,
|
|
293
|
+
endpointReshapeScope,
|
|
294
|
+
holeEditScope,
|
|
295
|
+
movingNodeOf,
|
|
296
|
+
} from './lib/interaction/scope'
|
|
204
297
|
export {
|
|
205
298
|
buildResetSurfaceMaterialUpdates,
|
|
206
299
|
buildRoofSurfaceMaterialPatch,
|
|
207
300
|
buildSingleSurfaceMaterialPatch,
|
|
208
301
|
buildStairSurfaceMaterialPatch,
|
|
209
|
-
buildWallSurfaceMaterialPatch,
|
|
210
302
|
getActivePaintMaterialLabel,
|
|
211
303
|
hasActivePaintMaterial,
|
|
212
304
|
} from './lib/material-paint'
|
|
305
|
+
export {
|
|
306
|
+
CREATABLE_MEASUREMENT_KINDS,
|
|
307
|
+
type CreatableMeasurementKind,
|
|
308
|
+
DEFAULT_CREATABLE_MEASUREMENT_KIND,
|
|
309
|
+
isCreatableMeasurementKind,
|
|
310
|
+
normalizeCreatableMeasurementKind,
|
|
311
|
+
} from './lib/measurement-kind'
|
|
312
|
+
export {
|
|
313
|
+
measurementPolygonLabelAnchor,
|
|
314
|
+
triangulateMeasurementPolygon,
|
|
315
|
+
} from './lib/measurement-label'
|
|
316
|
+
export {
|
|
317
|
+
buildMeasurementAngleArcPoints,
|
|
318
|
+
cubicMetersToVolumeUnit,
|
|
319
|
+
formatAreaLabel,
|
|
320
|
+
formatLinearMeasurement,
|
|
321
|
+
formatVolumeLabel,
|
|
322
|
+
getAreaUnitLabel,
|
|
323
|
+
getLinearUnitLabel,
|
|
324
|
+
getVolumeUnitLabel,
|
|
325
|
+
type LinearUnit,
|
|
326
|
+
linearControlValueToMeters,
|
|
327
|
+
linearUnitToMeters,
|
|
328
|
+
MEASUREMENT_ACTIVE_COLOR,
|
|
329
|
+
MEASUREMENT_DANGLING_COLOR,
|
|
330
|
+
MEASUREMENT_FLOORPLAN_COLOR,
|
|
331
|
+
MEASUREMENT_PERSISTENT_COLOR,
|
|
332
|
+
measurementFloorplanPresentationColor,
|
|
333
|
+
measurementPresentationColor,
|
|
334
|
+
metersToLinearUnit,
|
|
335
|
+
squareMetersToAreaUnit,
|
|
336
|
+
} from './lib/measurements'
|
|
337
|
+
export { consumePlacementDragRelease } from './lib/placement-drag-release'
|
|
213
338
|
export {
|
|
214
339
|
addFreshPlacementMetadata,
|
|
215
340
|
getPlacementMetadataRecord,
|
|
@@ -221,10 +346,36 @@ export {
|
|
|
221
346
|
type PlanarPoint,
|
|
222
347
|
resolvePlanarCursorPosition,
|
|
223
348
|
} from './lib/planar-cursor-placement'
|
|
349
|
+
export {
|
|
350
|
+
type EditorHostPanel,
|
|
351
|
+
type EditorHostPanelWorkspace,
|
|
352
|
+
editorHostPanelRegistry,
|
|
353
|
+
registerEditorHostPanel,
|
|
354
|
+
} from './lib/plugin-panels'
|
|
355
|
+
export {
|
|
356
|
+
createQuickMeasurementPointerScheduler,
|
|
357
|
+
quickMeasurementContext,
|
|
358
|
+
resolveQuickMeasurementReport,
|
|
359
|
+
} from './lib/quick-measurement'
|
|
224
360
|
export { clearRoofDuplicateMetadata, duplicateRoofSubtree } from './lib/roof-duplication'
|
|
361
|
+
// Roof wall-face hit resolution + overlap guard — shared by the
|
|
362
|
+
// kind-owned door / window tools in `@pascal-app/nodes` and the item
|
|
363
|
+
// placement coordinator's roof-wall strategy.
|
|
364
|
+
export { hasRoofFaceChildOverlap, type RoofWallHit, resolveRoofWallHit } from './lib/roof-wall-hit'
|
|
225
365
|
export type { SceneGraph } from './lib/scene'
|
|
226
366
|
export { applySceneGraphToEditor } from './lib/scene'
|
|
367
|
+
export { movementSfxStepKey } from './lib/sfx/movement-tick'
|
|
227
368
|
export { triggerSFX } from './lib/sfx-bus'
|
|
369
|
+
export {
|
|
370
|
+
clearSlabSnapFeedback,
|
|
371
|
+
resolveSlabEdgeBandSnap,
|
|
372
|
+
resolveSlabPlanPointSnap,
|
|
373
|
+
SLAB_ALIGNMENT_THRESHOLD_M,
|
|
374
|
+
type SlabEdgeBandSnapInput,
|
|
375
|
+
type SlabEdgeBandSnapResult,
|
|
376
|
+
type SlabPlanSnapInput,
|
|
377
|
+
type SlabPlanSnapResult,
|
|
378
|
+
} from './lib/slab-plan-snap'
|
|
228
379
|
export { duplicateStairSubtree } from './lib/stair-duplication'
|
|
229
380
|
export {
|
|
230
381
|
getBuildingLevelsForLevel,
|
|
@@ -234,13 +385,22 @@ export {
|
|
|
234
385
|
resolveStairPlacementLevelId,
|
|
235
386
|
resolveStairToLevelId,
|
|
236
387
|
} from './lib/stair-levels'
|
|
388
|
+
export {
|
|
389
|
+
clearSurfacePlanSnapFeedback,
|
|
390
|
+
resolveSurfacePlanPointSnap,
|
|
391
|
+
SURFACE_ALIGNMENT_THRESHOLD_M,
|
|
392
|
+
type SurfacePlanSnapInput,
|
|
393
|
+
type SurfacePlanSnapResult,
|
|
394
|
+
} from './lib/surface-plan-snap'
|
|
237
395
|
// `cn` (twMerge + clsx) — used by kind-owned panels in `@pascal-app/
|
|
238
396
|
// nodes` so they don't need their own copy / their own tailwind-merge
|
|
239
397
|
// dependency.
|
|
240
398
|
export { cn } from './lib/utils'
|
|
241
399
|
export {
|
|
242
400
|
getActiveBuildingPose,
|
|
401
|
+
projectAlignmentGuidesWorldToActiveBuildingLocal,
|
|
243
402
|
resolveAlignmentForActiveBuilding,
|
|
403
|
+
resolveAlignmentForFloorplanView,
|
|
244
404
|
snapBuildingLocalToWorldGrid,
|
|
245
405
|
snapWorldXZForActiveBuilding,
|
|
246
406
|
} from './lib/world-grid-snap'
|
|
@@ -248,22 +408,75 @@ export { default as useAlignmentGuides } from './store/use-alignment-guides'
|
|
|
248
408
|
export { default as useAudio } from './store/use-audio'
|
|
249
409
|
export { type CommandAction, useCommandRegistry } from './store/use-command-registry'
|
|
250
410
|
export type {
|
|
411
|
+
CaptureMode,
|
|
251
412
|
FloorplanSelectionTool,
|
|
252
|
-
|
|
253
|
-
|
|
413
|
+
SnapshotCropMode,
|
|
414
|
+
SnapshotStandardAspect,
|
|
254
415
|
SplitOrientation,
|
|
255
416
|
Tool,
|
|
256
417
|
ToolDefaults,
|
|
257
418
|
ViewMode,
|
|
258
419
|
WorkspaceMode,
|
|
259
420
|
} from './store/use-editor'
|
|
260
|
-
export {
|
|
421
|
+
export {
|
|
422
|
+
default as useEditor,
|
|
423
|
+
getActiveContinuationContext,
|
|
424
|
+
getContinuation,
|
|
425
|
+
isAlignmentGuideActive,
|
|
426
|
+
isAngleSnapActive,
|
|
427
|
+
isGridSnapActive,
|
|
428
|
+
isMagneticSnapActive,
|
|
429
|
+
} from './store/use-editor'
|
|
430
|
+
export { default as useFacingPose, type FacingPose } from './store/use-facing-pose'
|
|
431
|
+
export { default as useFenceCurveDraft } from './store/use-fence-curve-draft'
|
|
432
|
+
export {
|
|
433
|
+
default as useInteractionScope,
|
|
434
|
+
getEditingHole,
|
|
435
|
+
getIsCurveReshape,
|
|
436
|
+
getMovingNode,
|
|
437
|
+
useActiveHandleDrag,
|
|
438
|
+
useEditingHole,
|
|
439
|
+
useEndpointReshape,
|
|
440
|
+
useIsCurveReshape,
|
|
441
|
+
useMovingNode,
|
|
442
|
+
useReshapingNode,
|
|
443
|
+
} from './store/use-interaction-scope'
|
|
444
|
+
export {
|
|
445
|
+
commitMeasurementDraft,
|
|
446
|
+
finishMeasurementDraft,
|
|
447
|
+
handleMeasurementDraftEscape,
|
|
448
|
+
type MeasurementAxis,
|
|
449
|
+
type MeasurementAxisGuide,
|
|
450
|
+
type MeasurementDraftOwner,
|
|
451
|
+
type MeasurementDraftPayload,
|
|
452
|
+
type MeasurementDraftStage,
|
|
453
|
+
type MeasurementKind,
|
|
454
|
+
type MeasurementPoint,
|
|
455
|
+
type MeasurementSurfacePoint,
|
|
456
|
+
measurementPolygonMidpoints,
|
|
457
|
+
useMeasurementDraft,
|
|
458
|
+
} from './store/use-measurement-draft'
|
|
459
|
+
export {
|
|
460
|
+
default as useOpeningGuides,
|
|
461
|
+
type OpeningGuide3D,
|
|
462
|
+
type OpeningGuideVec3,
|
|
463
|
+
} from './store/use-opening-guides'
|
|
261
464
|
export {
|
|
262
465
|
type PaletteView,
|
|
263
466
|
type PaletteViewProps,
|
|
264
467
|
usePaletteViewRegistry,
|
|
265
468
|
} from './store/use-palette-view-registry'
|
|
266
469
|
export { default as usePlacementPreview } from './store/use-placement-preview'
|
|
470
|
+
export {
|
|
471
|
+
activateQuickMeasurementHudSource,
|
|
472
|
+
clearQuickMeasurementHudSource,
|
|
473
|
+
publishQuickMeasurementHudSource,
|
|
474
|
+
type QuickMeasurementHudEntry,
|
|
475
|
+
type QuickMeasurementHudSource,
|
|
476
|
+
selectQuickMeasurementHudEntry,
|
|
477
|
+
useQuickMeasurementHud,
|
|
478
|
+
} from './store/use-quick-measurement-hud'
|
|
479
|
+
export { default as useSegmentDraftChain } from './store/use-segment-draft-chain'
|
|
267
480
|
export { useUploadStore } from './store/use-upload'
|
|
268
481
|
export { useWallMoveGhosts, type WallMoveGhostBridge } from './store/use-wall-move-ghosts'
|
|
269
482
|
export {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Vector3 } from 'three'
|
|
2
|
+
|
|
3
|
+
// The surface the active placement/move ghost is currently snapped to: a contact
|
|
4
|
+
// point (world space) and the surface's outward unit normal. Published each frame
|
|
5
|
+
// by the placement tools (the item coordinator + the drawn-kind tools) and read
|
|
6
|
+
// by the grid so its snap patch sits at the ghost's height AND orients to the
|
|
7
|
+
// surface — horizontal on a floor / shelf top, vertical in a wall plane.
|
|
8
|
+
//
|
|
9
|
+
// A plain module singleton (not a store): both writer and reader run inside
|
|
10
|
+
// `useFrame`, so reactivity would only add overhead. The vectors are reused, so
|
|
11
|
+
// readers must consume them within the same frame.
|
|
12
|
+
export type PlacementSurface = {
|
|
13
|
+
point: Vector3
|
|
14
|
+
normal: Vector3
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const surface: PlacementSurface = {
|
|
18
|
+
point: new Vector3(),
|
|
19
|
+
normal: new Vector3(0, 1, 0),
|
|
20
|
+
}
|
|
21
|
+
let active = false
|
|
22
|
+
|
|
23
|
+
export function publishPlacementSurface(point: Vector3, normal: Vector3): void {
|
|
24
|
+
surface.point.copy(point)
|
|
25
|
+
surface.normal.copy(normal)
|
|
26
|
+
active = true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function clearPlacementSurface(): void {
|
|
30
|
+
active = false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getPlacementSurface(): PlacementSurface | null {
|
|
34
|
+
return active ? surface : null
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearSurfacePlanSnapFeedback,
|
|
3
|
+
resolveSurfacePlanPointSnap,
|
|
4
|
+
SURFACE_ALIGNMENT_THRESHOLD_M,
|
|
5
|
+
type SurfacePlanSnapInput,
|
|
6
|
+
type SurfacePlanSnapResult,
|
|
7
|
+
} from './surface-plan-snap'
|
|
8
|
+
|
|
9
|
+
const CEILING_SNAP_MOVING_ID = '__ceiling_snap__'
|
|
10
|
+
|
|
11
|
+
export const CEILING_ALIGNMENT_THRESHOLD_M = SURFACE_ALIGNMENT_THRESHOLD_M
|
|
12
|
+
export type CeilingPlanSnapInput = SurfacePlanSnapInput
|
|
13
|
+
export type CeilingPlanSnapResult = SurfacePlanSnapResult
|
|
14
|
+
|
|
15
|
+
export function clearCeilingSnapFeedback() {
|
|
16
|
+
clearSurfacePlanSnapFeedback()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function resolveCeilingPlanPointSnap(input: CeilingPlanSnapInput): CeilingPlanSnapResult {
|
|
20
|
+
return resolveSurfacePlanPointSnap({
|
|
21
|
+
...input,
|
|
22
|
+
movingId: input.movingId ?? CEILING_SNAP_MOVING_ID,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { resolveSelectModeHelpHints } from './contextual-help'
|
|
3
|
+
|
|
4
|
+
describe('resolveSelectModeHelpHints', () => {
|
|
5
|
+
test('stays hidden in idle select mode with no selection', () => {
|
|
6
|
+
expect(
|
|
7
|
+
resolveSelectModeHelpHints({
|
|
8
|
+
selectedCount: 0,
|
|
9
|
+
hasMovableSelection: false,
|
|
10
|
+
hasRotatableSelection: false,
|
|
11
|
+
commandPressed: false,
|
|
12
|
+
shiftPressed: false,
|
|
13
|
+
}),
|
|
14
|
+
).toEqual([])
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('shows multi-select guidance when a modifier is held without selection', () => {
|
|
18
|
+
expect(
|
|
19
|
+
resolveSelectModeHelpHints({
|
|
20
|
+
selectedCount: 0,
|
|
21
|
+
hasMovableSelection: false,
|
|
22
|
+
hasRotatableSelection: false,
|
|
23
|
+
commandPressed: true,
|
|
24
|
+
shiftPressed: false,
|
|
25
|
+
}),
|
|
26
|
+
).toEqual([
|
|
27
|
+
{
|
|
28
|
+
keys: [['Cmd/Ctrl', 'Shift'], 'Left click'],
|
|
29
|
+
label: 'Add or remove objects from the selection',
|
|
30
|
+
active: true,
|
|
31
|
+
},
|
|
32
|
+
])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('shows direct manipulation tips for selected movable and rotatable nodes', () => {
|
|
36
|
+
const hints = resolveSelectModeHelpHints({
|
|
37
|
+
selectedCount: 1,
|
|
38
|
+
hasMovableSelection: true,
|
|
39
|
+
hasRotatableSelection: true,
|
|
40
|
+
commandPressed: false,
|
|
41
|
+
shiftPressed: false,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
expect(hints).toContainEqual({
|
|
45
|
+
keys: ['Left click'],
|
|
46
|
+
label: 'Drag selected movable object',
|
|
47
|
+
})
|
|
48
|
+
expect(hints).toContainEqual({
|
|
49
|
+
keys: ['Cmd/Ctrl', 'Right click'],
|
|
50
|
+
label: 'Drag left or right to rotate selected object',
|
|
51
|
+
})
|
|
52
|
+
// Cmd/Ctrl and Shift click both toggle selection membership (3D selection
|
|
53
|
+
// manager and 2D floorplan alike) — advertised as a single or-group row.
|
|
54
|
+
expect(hints).toContainEqual({
|
|
55
|
+
keys: [['Cmd/Ctrl', 'Shift'], 'Left click'],
|
|
56
|
+
label: 'Add or remove objects from the selection',
|
|
57
|
+
active: false,
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('multi-selection advertises the group move + rotate gestures', () => {
|
|
62
|
+
const hints = resolveSelectModeHelpHints({
|
|
63
|
+
selectedCount: 3,
|
|
64
|
+
hasMovableSelection: true,
|
|
65
|
+
hasRotatableSelection: true,
|
|
66
|
+
commandPressed: false,
|
|
67
|
+
shiftPressed: false,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
expect(hints).toEqual([
|
|
71
|
+
{
|
|
72
|
+
keys: ['Left click'],
|
|
73
|
+
label: 'Click or drag the selection to move it as one',
|
|
74
|
+
},
|
|
75
|
+
{ keys: ['R / T'], label: 'Rotate the selection ±45°' },
|
|
76
|
+
{
|
|
77
|
+
keys: [['Cmd/Ctrl', 'Shift'], 'Left click'],
|
|
78
|
+
label: 'Add or remove objects from the selection',
|
|
79
|
+
active: false,
|
|
80
|
+
},
|
|
81
|
+
{ keys: ['Esc'], label: 'Clear the selection (or click outside)' },
|
|
82
|
+
])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('holding a modifier keeps the same rows and only lights the selection one', () => {
|
|
86
|
+
// Guides/snapping are governed by the snapping mode (Shift toggles it),
|
|
87
|
+
// so no modifier-specific "freely / with guides / bypass" variants exist.
|
|
88
|
+
const hints = resolveSelectModeHelpHints({
|
|
89
|
+
selectedCount: 1,
|
|
90
|
+
hasMovableSelection: true,
|
|
91
|
+
hasRotatableSelection: true,
|
|
92
|
+
commandPressed: true,
|
|
93
|
+
shiftPressed: true,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expect(hints).toEqual([
|
|
97
|
+
{
|
|
98
|
+
keys: ['Left click'],
|
|
99
|
+
label: 'Drag selected movable object',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
keys: ['Cmd/Ctrl', 'Right click'],
|
|
103
|
+
label: 'Drag left or right to rotate selected object',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
keys: [['Cmd/Ctrl', 'Shift'], 'Left click'],
|
|
107
|
+
label: 'Add or remove objects from the selection',
|
|
108
|
+
active: true,
|
|
109
|
+
},
|
|
110
|
+
])
|
|
111
|
+
})
|
|
112
|
+
})
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export type ContextualShortcutHint = {
|
|
2
|
+
// A combo of keys pressed together (rendered joined by "+"). An entry may
|
|
3
|
+
// itself be an array of alternatives (rendered joined by "/"), e.g.
|
|
4
|
+
// [['Cmd/Ctrl', 'Shift'], 'Left click'] → "⌘ / ⇧ + click".
|
|
5
|
+
keys: Array<string | string[]>
|
|
6
|
+
label: string
|
|
7
|
+
// Optional secondary line under the label for a terser qualifier
|
|
8
|
+
// (e.g. "disable 15° snap"). The HUD wraps both lines rather than truncating.
|
|
9
|
+
subtitle?: string
|
|
10
|
+
active?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// `activeHandleDrag.label` value a rotate gizmo sets while dragging, so the
|
|
14
|
+
// contextual HUD can surface the Shift = free-rotation toggle for the duration
|
|
15
|
+
// (mirrors how wall drafting advertises Shift). Distinct from resize handles,
|
|
16
|
+
// which route their own measurement label here.
|
|
17
|
+
export const ROTATE_HANDLE_DRAG_LABEL = 'rotate-handle'
|
|
18
|
+
|
|
19
|
+
// `activeHandleDrag.label` a plain resize / radial-resize arrow sets while
|
|
20
|
+
// dragging (when it carries no dimension `measureLabel`). It exists only so the
|
|
21
|
+
// interaction scope is non-idle during a resize, which keeps the idle
|
|
22
|
+
// select-mode hints off-screen — a resize is its own action, not a selection.
|
|
23
|
+
export const RESIZE_HANDLE_DRAG_LABEL = 'resize-handle'
|
|
24
|
+
|
|
25
|
+
// `activeHandleDrag.label`s for the multi-selection group drags: the body
|
|
26
|
+
// drag sessions (2D floorplan + 3D ground plane) and the rotate gizmo. Kept
|
|
27
|
+
// here (not in the session/gizmo modules) so `snapping-mode.ts` can resolve
|
|
28
|
+
// the group move to the 'item' snap context without importing component code.
|
|
29
|
+
export const GROUP_MOVE_DRAG_LABEL = 'group-move-handle'
|
|
30
|
+
export const GROUP_ROTATE_DRAG_LABEL = 'group-rotate-handle'
|
|
31
|
+
|
|
32
|
+
// Hints shown while a rotate gizmo is mid-drag: Shift bypasses the angle step
|
|
33
|
+
// (free rotation), the same toggle wall drafting exposes. `active` lights the
|
|
34
|
+
// pill while Shift is held.
|
|
35
|
+
export function resolveRotateHandleHelpHints(shiftPressed: boolean): ContextualShortcutHint[] {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
keys: [SHIFT_KEY],
|
|
39
|
+
label: shiftPressed ? 'Rotating freely (no angle step)' : 'Hold to rotate freely',
|
|
40
|
+
active: shiftPressed,
|
|
41
|
+
},
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type SelectModeHelpContext = {
|
|
46
|
+
selectedCount: number
|
|
47
|
+
hasMovableSelection: boolean
|
|
48
|
+
hasRotatableSelection: boolean
|
|
49
|
+
commandPressed: boolean
|
|
50
|
+
shiftPressed: boolean
|
|
51
|
+
// When a single MEP node is selected its in-world handle rig (click a dot to
|
|
52
|
+
// reveal move arrows) is the real editing path, so the panel leads with the
|
|
53
|
+
// handle-specific hints instead of just the generic Cmd-drag tips.
|
|
54
|
+
mepSelection?: 'run' | 'fitting' | null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const COMMAND_KEY = 'Cmd/Ctrl'
|
|
58
|
+
const LEFT_CLICK = 'Left click'
|
|
59
|
+
const RIGHT_CLICK = 'Right click'
|
|
60
|
+
const SHIFT_KEY = 'Shift'
|
|
61
|
+
const CLICK = 'Click'
|
|
62
|
+
const ALT_KEY = 'Alt'
|
|
63
|
+
const ROTATE_KEYS = 'R / T'
|
|
64
|
+
const ESC_KEY = 'Esc'
|
|
65
|
+
|
|
66
|
+
export function resolveSelectModeHelpHints({
|
|
67
|
+
selectedCount,
|
|
68
|
+
hasMovableSelection,
|
|
69
|
+
hasRotatableSelection,
|
|
70
|
+
commandPressed,
|
|
71
|
+
shiftPressed,
|
|
72
|
+
mepSelection = null,
|
|
73
|
+
}: SelectModeHelpContext): ContextualShortcutHint[] {
|
|
74
|
+
const hints: ContextualShortcutHint[] = []
|
|
75
|
+
|
|
76
|
+
if (selectedCount === 0) {
|
|
77
|
+
if (!commandPressed && !shiftPressed) return hints
|
|
78
|
+
|
|
79
|
+
hints.push({
|
|
80
|
+
keys: [[COMMAND_KEY, SHIFT_KEY], LEFT_CLICK],
|
|
81
|
+
label: 'Add or remove objects from the selection',
|
|
82
|
+
active: true,
|
|
83
|
+
})
|
|
84
|
+
return hints
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Multi-selection — advertise the group gestures: drag any member to slide
|
|
88
|
+
// the whole selection (2D floor plan body drag + the 3D move gizmo), R / T
|
|
89
|
+
// to step-rotate the group around its bounding-box center.
|
|
90
|
+
if (selectedCount > 1) {
|
|
91
|
+
hints.push({
|
|
92
|
+
keys: [LEFT_CLICK],
|
|
93
|
+
label: 'Click or drag the selection to move it as one',
|
|
94
|
+
})
|
|
95
|
+
hints.push({ keys: [ROTATE_KEYS], label: 'Rotate the selection ±45°' })
|
|
96
|
+
hints.push({
|
|
97
|
+
keys: [[COMMAND_KEY, SHIFT_KEY], LEFT_CLICK],
|
|
98
|
+
label: 'Add or remove objects from the selection',
|
|
99
|
+
active: commandPressed || shiftPressed,
|
|
100
|
+
})
|
|
101
|
+
hints.push({ keys: [ESC_KEY], label: 'Clear the selection (or click outside)' })
|
|
102
|
+
return hints
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// MEP handle workflow — duct/pipe runs and fittings are edited through the
|
|
106
|
+
// in-world arrow rig that a click on the handle dot reveals, so surface those
|
|
107
|
+
// hints first. A run endpoint's side / up-down arrows swing the run and Alt
|
|
108
|
+
// detaches the joint mid-drag; a fitting's cluster adds rotate arcs, with
|
|
109
|
+
// R / T (and Alt to switch axis) for keyboard rotation.
|
|
110
|
+
if (mepSelection === 'run') {
|
|
111
|
+
hints.push({ keys: [CLICK], label: 'Click a handle dot to show move arrows' })
|
|
112
|
+
hints.push({ keys: [ALT_KEY], label: 'Detach the joint while dragging an arrow' })
|
|
113
|
+
} else if (mepSelection === 'fitting') {
|
|
114
|
+
hints.push({ keys: [CLICK], label: 'Click the handle dot to show move + rotate handles' })
|
|
115
|
+
hints.push({ keys: [ROTATE_KEYS], label: 'Rotate ±45°' })
|
|
116
|
+
hints.push({ keys: [ALT_KEY], label: 'Switch the rotation axis (Y → X → Z)' })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The rows are the same whatever modifier is held — guides/snapping are
|
|
120
|
+
// governed by the snapping mode (Shift toggles it), not by the modifier of
|
|
121
|
+
// this gesture, so there are no modifier-specific variants to advertise.
|
|
122
|
+
// Holding Cmd/Ctrl or Shift just lights the selection row.
|
|
123
|
+
if (hasMovableSelection) {
|
|
124
|
+
hints.push({
|
|
125
|
+
keys: [LEFT_CLICK],
|
|
126
|
+
label: 'Drag selected movable object',
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (hasRotatableSelection) {
|
|
131
|
+
hints.push({
|
|
132
|
+
keys: [COMMAND_KEY, RIGHT_CLICK],
|
|
133
|
+
label: 'Drag left or right to rotate selected object',
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
hints.push({
|
|
138
|
+
keys: [[COMMAND_KEY, SHIFT_KEY], LEFT_CLICK],
|
|
139
|
+
label: 'Add or remove objects from the selection',
|
|
140
|
+
active: commandPressed || shiftPressed,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
return hints
|
|
144
|
+
}
|