@pascal-app/editor 0.8.0 → 0.9.1
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 +8 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
- package/src/components/editor/custom-camera-controls.tsx +754 -25
- package/src/components/editor/editor-layout-mobile.tsx +3 -3
- package/src/components/editor/editor-layout-v2.tsx +58 -51
- package/src/components/editor/export-manager.tsx +32 -3
- package/src/components/editor/first-person/build-collider-world.test.ts +149 -0
- package/src/components/editor/first-person/build-collider-world.ts +201 -83
- package/src/components/editor/first-person/bvh-ecctrl.tsx +8 -8
- package/src/components/editor/first-person-controls.tsx +825 -20
- package/src/components/editor/floating-action-menu.tsx +287 -196
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.tsx +5829 -12005
- package/src/components/editor/grid.tsx +51 -17
- package/src/components/editor/group-move-handle.tsx +316 -0
- package/src/components/editor/group-rotate-handle.tsx +382 -0
- package/src/components/editor/group-transform-shared.test.ts +205 -0
- package/src/components/editor/group-transform-shared.ts +256 -0
- package/src/components/editor/handles/handle-arrow.tsx +523 -0
- package/src/components/editor/handles/use-handle-drag.ts +213 -0
- package/src/components/editor/index.tsx +178 -149
- package/src/components/editor/measurement-pill.tsx +75 -0
- package/src/components/editor/node-arrow-handles.tsx +1453 -0
- package/src/components/editor/selection-manager.tsx +433 -156
- package/src/components/editor/site-edge-labels.tsx +44 -20
- package/src/components/editor/slab-hole-highlights.tsx +466 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +136 -62
- package/src/components/editor/thumbnail-generator.tsx +9 -3
- package/src/components/editor/use-floorplan-background-placement.ts +119 -32
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +14 -2
- package/src/components/editor/wall-measurement-label.tsx +11 -6
- package/src/components/editor/wall-move-side-handles.tsx +855 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +150 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +6 -1
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +227 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +616 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +75 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +26 -3
- package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
- package/src/components/systems/roof/roof-edit-system.tsx +86 -12
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- package/src/components/tools/elevator/elevator-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +349 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
- package/src/components/tools/fence/fence-drafting.ts +64 -22
- package/src/components/tools/item/move-tool.tsx +41 -105
- package/src/components/tools/item/placement-math.test.ts +10 -0
- package/src/components/tools/item/placement-math.ts +31 -5
- package/src/components/tools/item/placement-strategies.ts +190 -15
- package/src/components/tools/item/placement-types.ts +10 -1
- package/src/components/tools/item/use-draft-node.ts +20 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +694 -191
- package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
- package/src/components/tools/roof/roof-tool.tsx +108 -12
- package/src/components/tools/select/box-select-state.ts +77 -0
- package/src/components/tools/select/box-select-tool.tsx +324 -538
- package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
- package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
- package/src/components/tools/select/select-candidates.test.ts +109 -0
- package/src/components/tools/select/select-candidates.ts +85 -0
- package/src/components/tools/shared/affordance-dispatch.ts +30 -0
- package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
- package/src/components/tools/shared/floor-stack-preview.ts +25 -0
- package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
- package/src/components/tools/shared/placement-box-geometry.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +123 -0
- package/src/components/tools/shared/polygon-editor.tsx +617 -167
- package/src/components/tools/shared/segment-angle.ts +56 -0
- package/src/components/tools/site/site-boundary-editor.tsx +464 -12
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -1
- package/src/components/tools/stair/stair-tool.tsx +269 -35
- package/src/components/tools/tool-manager.tsx +172 -56
- package/src/components/tools/wall/wall-drafting.ts +98 -98
- package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
- package/src/components/tools/zone/zone-tool.tsx +16 -6
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/control-modes.tsx +5 -82
- package/src/components/ui/action-menu/index.tsx +1 -113
- package/src/components/ui/action-menu/structure-tools.tsx +13 -88
- package/src/components/ui/action-menu/view-toggles.tsx +243 -6
- package/src/components/ui/command-palette/editor-commands.tsx +18 -13
- package/src/components/ui/command-palette/index.tsx +2 -1
- package/src/components/ui/controls/material-paint-panel.tsx +83 -0
- package/src/components/ui/controls/material-picker.tsx +11 -32
- package/src/components/ui/controls/metric-control.tsx +33 -11
- package/src/components/ui/floating-level-selector.tsx +47 -10
- package/src/components/ui/helpers/helper-manager.tsx +16 -17
- package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1 -0
- package/src/components/ui/item-catalog/item-catalog.tsx +3 -0
- package/src/components/ui/level-duplicate-dialog.tsx +2 -1
- package/src/components/ui/panels/node-display.ts +1 -0
- package/src/components/ui/panels/panel-manager.tsx +35 -47
- package/src/components/ui/panels/panel-wrapper.tsx +217 -12
- package/src/components/ui/panels/parametric-inspector.tsx +469 -0
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/scene-loader.tsx +1 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +4 -6
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -36
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
- package/src/components/ui/sidebar/tab-bar.tsx +61 -1
- package/src/components/viewer-overlay.tsx +169 -52
- package/src/components/viewer-zone-system.tsx +1 -1
- package/src/hooks/use-auto-save.ts +10 -3
- package/src/hooks/use-ceiling-events.ts +175 -0
- package/src/hooks/use-drag-action.ts +124 -0
- package/src/hooks/use-keyboard.ts +115 -62
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +237 -5
- package/src/lib/constants.ts +6 -2
- package/src/lib/editor-api.ts +62 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.ts +111 -0
- package/src/lib/floorplan/index.ts +7 -0
- package/src/lib/floorplan/selection-tool.ts +40 -0
- package/src/lib/floorplan/stairs.ts +12 -1
- package/src/lib/fresh-planar-placement.test.ts +102 -0
- package/src/lib/fresh-planar-placement.ts +63 -0
- package/src/lib/history.ts +2 -1
- package/src/lib/level-duplication.ts +54 -42
- package/src/lib/level-name.ts +11 -0
- package/src/lib/material-paint.ts +211 -19
- package/src/lib/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +43 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/roof-duplication.ts +1 -1
- package/src/lib/scene-clipboard.ts +267 -0
- package/src/lib/scene.ts +13 -4
- package/src/lib/sfx-bus.ts +23 -1
- package/src/lib/sfx-player.ts +70 -22
- package/src/lib/site-boundary.ts +1 -0
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/world-grid-snap.ts +199 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-editor.tsx +320 -37
- package/src/store/use-placement-preview.ts +31 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +32 -0
- package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
- package/src/components/tools/building/move-building-tool.tsx +0 -157
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
- package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
- package/src/components/tools/column/column-tool.tsx +0 -97
- package/src/components/tools/column/move-column-tool.tsx +0 -105
- package/src/components/tools/door/door-math.ts +0 -110
- package/src/components/tools/door/door-tool.tsx +0 -324
- package/src/components/tools/door/move-door-tool.tsx +0 -412
- package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
- package/src/components/tools/fence/move-fence-tool.tsx +0 -302
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -364
- package/src/components/tools/slab/move-slab-tool.tsx +0 -182
- package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
- package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
- package/src/components/tools/slab/slab-tool.tsx +0 -322
- package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
- package/src/components/tools/spawn/spawn-tool.tsx +0 -130
- package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
- package/src/components/tools/wall/move-wall-tool.tsx +0 -358
- package/src/components/tools/wall/wall-tool.tsx +0 -332
- package/src/components/tools/window/move-window-tool.tsx +0 -447
- package/src/components/tools/window/window-math.ts +0 -117
- package/src/components/tools/window/window-tool.tsx +0 -332
- package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
- package/src/components/ui/helpers/slab-helper.tsx +0 -20
- package/src/components/ui/helpers/wall-helper.tsx +0 -20
- package/src/components/ui/panels/ceiling-panel.tsx +0 -249
- package/src/components/ui/panels/column-panel.tsx +0 -759
- package/src/components/ui/panels/door-panel.tsx +0 -1299
- package/src/components/ui/panels/fence-panel.tsx +0 -222
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -280
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
- package/src/components/ui/panels/slab-panel.tsx +0 -250
- package/src/components/ui/panels/spawn-panel.tsx +0 -161
- package/src/components/ui/panels/stair-panel.tsx +0 -577
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -273
- package/src/components/ui/panels/window-panel.tsx +0 -968
- package/src/contexts/presets-context.tsx +0 -121
- package/src/hooks/use-contextual-tools.ts +0 -61
package/src/index.tsx
CHANGED
|
@@ -1,21 +1,162 @@
|
|
|
1
|
+
// Re-exports of the scene / viewer hooks so consumers composing their
|
|
2
|
+
// own shells on top of `@pascal-app/editor` (community-app, embedders)
|
|
3
|
+
// don't have to learn three separate package imports. The canonical
|
|
4
|
+
// definitions still live in `@pascal-app/core` / `@pascal-app/viewer`.
|
|
5
|
+
export { useScene } from '@pascal-app/core'
|
|
6
|
+
export { useViewer } from '@pascal-app/viewer'
|
|
1
7
|
export type { EditorProps } from './components/editor'
|
|
2
8
|
export { default as Editor } from './components/editor'
|
|
9
|
+
// Headless component aliases: the implementation files keep their
|
|
10
|
+
// internal names (`ParametricInspector`, `FloatingActionMenu`) because
|
|
11
|
+
// they're referenced throughout the editor's own internals; the public
|
|
12
|
+
// surface uses the shorter, shell-friendly names from the unified
|
|
13
|
+
// preset-system spec.
|
|
14
|
+
export { FloatingActionMenu as FloatingMenu } from './components/editor/floating-action-menu'
|
|
15
|
+
export { formatMeasurement, MeasurementPill } from './components/editor/measurement-pill'
|
|
3
16
|
export {
|
|
4
17
|
type SnapshotCameraData,
|
|
5
18
|
ThumbnailGenerator,
|
|
6
19
|
} from './components/editor/thumbnail-generator'
|
|
7
|
-
|
|
8
|
-
|
|
20
|
+
// SVG path builders for arc / annular-sector / arrow-head shapes —
|
|
21
|
+
// inlined into `kind: 'path'` / `kind: 'polygon'` primitives by curved
|
|
22
|
+
// stair rendering in `nodes/src/stair/floorplan.ts`.
|
|
23
|
+
export {
|
|
24
|
+
buildSvgAnnularSectorPath,
|
|
25
|
+
buildSvgArcPath,
|
|
26
|
+
buildSvgArrowHeadPoints,
|
|
27
|
+
getArcPlanPoint,
|
|
28
|
+
} from './components/editor-2d/svg-paths'
|
|
29
|
+
// Phase 5 Stage D transitional exports — pure drafting / angle helpers
|
|
30
|
+
// consumed by kind-owned drag actions in @pascal-app/nodes. Stage F
|
|
31
|
+
// cleanup moves these into @pascal-app/nodes (fence/drafting.ts +
|
|
32
|
+
// shared/segment-angle.ts) once every Stage D port is in.
|
|
33
|
+
export {
|
|
34
|
+
createFenceOnCurrentLevel,
|
|
35
|
+
type FencePlanPoint,
|
|
36
|
+
snapFenceDraftPoint,
|
|
37
|
+
} from './components/tools/fence/fence-drafting'
|
|
38
|
+
// Placement-math helpers — shared by kind-owned placement tools in
|
|
39
|
+
// `@pascal-app/nodes` (wall curve sagitta snap, door / window placement,
|
|
40
|
+
// item drop) so kinds don't reach into editor internals.
|
|
41
|
+
export {
|
|
42
|
+
calculateCursorRotation,
|
|
43
|
+
calculateItemRotation,
|
|
44
|
+
getSideFromNormal,
|
|
45
|
+
isValidWallSideFace,
|
|
46
|
+
snapToGrid,
|
|
47
|
+
snapToHalf,
|
|
48
|
+
snapUpToGridStep,
|
|
49
|
+
stripTransient,
|
|
50
|
+
} from './components/tools/item/placement-math'
|
|
51
|
+
export type { PlacementState } from './components/tools/item/placement-types'
|
|
52
|
+
// Item placement / move primitives. Re-exported here so the registry-driven
|
|
53
|
+
// item move-tool in `@pascal-app/nodes` can compose them — same hooks the
|
|
54
|
+
// legacy `MoveItemContent` + `ItemTool` use. Once item placement is fully
|
|
55
|
+
// owned by `nodes`, these can be inlined there and dropped from editor.
|
|
56
|
+
export { type DraftNodeHandle, useDraftNode } from './components/tools/item/use-draft-node'
|
|
57
|
+
export {
|
|
58
|
+
type PlacementCoordinatorConfig,
|
|
59
|
+
usePlacementCoordinator,
|
|
60
|
+
} from './components/tools/item/use-placement-coordinator'
|
|
61
|
+
export { CursorSphere } from './components/tools/shared/cursor-sphere'
|
|
62
|
+
export { DragBoundingBox } from './components/tools/shared/drag-bounding-box'
|
|
63
|
+
export { getFloorStackPreviewPosition } from './components/tools/shared/floor-stack-preview'
|
|
64
|
+
export { useFreshPlacementVisibility } from './components/tools/shared/fresh-placement-visibility'
|
|
65
|
+
// Phase 5 Stage D — PolygonEditor for slab/ceiling boundary + hole editors.
|
|
66
|
+
export {
|
|
67
|
+
PolygonEditor,
|
|
68
|
+
type PolygonEditorProps,
|
|
69
|
+
} from './components/tools/shared/polygon-editor'
|
|
70
|
+
export {
|
|
71
|
+
formatAngleRadians,
|
|
72
|
+
getAngleArcToSegmentReference,
|
|
73
|
+
getAngleToSegmentReference,
|
|
74
|
+
getSegmentAngleReferenceAtPoint,
|
|
75
|
+
type SegmentAngleReference,
|
|
76
|
+
} from './components/tools/shared/segment-angle'
|
|
77
|
+
// Stair placement defaults — used by the kind-owned stair / stair-segment
|
|
78
|
+
// panels. Re-exported from `components/tools/stair/stair-defaults.ts`.
|
|
79
|
+
export {
|
|
80
|
+
DEFAULT_CURVED_STAIR_INNER_RADIUS,
|
|
81
|
+
DEFAULT_CURVED_STAIR_SWEEP_ANGLE,
|
|
82
|
+
DEFAULT_SPIRAL_SHOW_CENTER_COLUMN,
|
|
83
|
+
DEFAULT_SPIRAL_SHOW_STEP_SUPPORTS,
|
|
84
|
+
DEFAULT_SPIRAL_STAIR_SWEEP_ANGLE,
|
|
85
|
+
DEFAULT_SPIRAL_TOP_LANDING_DEPTH,
|
|
86
|
+
DEFAULT_SPIRAL_TOP_LANDING_MODE,
|
|
87
|
+
DEFAULT_STAIR_ATTACHMENT_SIDE,
|
|
88
|
+
DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
89
|
+
DEFAULT_STAIR_HEIGHT,
|
|
90
|
+
DEFAULT_STAIR_LENGTH,
|
|
91
|
+
DEFAULT_STAIR_RAILING_HEIGHT,
|
|
92
|
+
DEFAULT_STAIR_RAILING_MODE,
|
|
93
|
+
DEFAULT_STAIR_STEP_COUNT,
|
|
94
|
+
DEFAULT_STAIR_THICKNESS,
|
|
95
|
+
DEFAULT_STAIR_TYPE,
|
|
96
|
+
DEFAULT_STAIR_WIDTH,
|
|
97
|
+
} from './components/tools/stair/stair-defaults'
|
|
98
|
+
export {
|
|
99
|
+
createWallOnCurrentLevel,
|
|
100
|
+
getSegmentGridStep,
|
|
101
|
+
isSegmentLongEnough,
|
|
102
|
+
snapPointToGrid,
|
|
103
|
+
snapScalarToGrid,
|
|
104
|
+
snapWallDraftPoint,
|
|
105
|
+
snapWallDraftPointDetailed,
|
|
106
|
+
WALL_FINE_GRID_STEP,
|
|
107
|
+
WALL_GRID_STEP,
|
|
108
|
+
type WallDraftSnapKind,
|
|
109
|
+
type WallDraftSnapResult,
|
|
110
|
+
type WallPlanPoint,
|
|
111
|
+
} from './components/tools/wall/wall-drafting'
|
|
112
|
+
// `ToolbarLeft` / `ToolbarRight` are the headless-spec aliases for the
|
|
113
|
+
// existing `ViewerToolbarLeft` / `ViewerToolbarRight` exports — the
|
|
114
|
+
// underlying components are the same; the alias just matches the names
|
|
115
|
+
// used in `pascalorg/private-editor:plans/community-preset-system.md`
|
|
116
|
+
// so consumer code stays close to the spec vocabulary.
|
|
117
|
+
export {
|
|
118
|
+
CameraActions as ToolbarRight,
|
|
119
|
+
CameraActions as ViewerToolbarRight,
|
|
120
|
+
} from './components/ui/action-menu/camera-actions'
|
|
121
|
+
export {
|
|
122
|
+
ViewToggles as ToolbarLeft,
|
|
123
|
+
ViewToggles as ViewerToolbarLeft,
|
|
124
|
+
} from './components/ui/action-menu/view-toggles'
|
|
9
125
|
export { useCommandPalette } from './components/ui/command-palette'
|
|
126
|
+
export { ActionButton, ActionGroup } from './components/ui/controls/action-button'
|
|
127
|
+
export { MaterialPaintPanel } from './components/ui/controls/material-paint-panel'
|
|
128
|
+
export { MaterialPicker } from './components/ui/controls/material-picker'
|
|
129
|
+
export { MetricControl } from './components/ui/controls/metric-control'
|
|
130
|
+
export { PanelSection } from './components/ui/controls/panel-section'
|
|
131
|
+
export { SegmentedControl } from './components/ui/controls/segmented-control'
|
|
10
132
|
export { SliderControl } from './components/ui/controls/slider-control'
|
|
133
|
+
export { ToggleControl } from './components/ui/controls/toggle-control'
|
|
11
134
|
export { FloatingLevelSelector } from './components/ui/floating-level-selector'
|
|
12
135
|
export { CATALOG_ITEMS } from './components/ui/item-catalog/catalog-items'
|
|
136
|
+
// Item collections UI — used by the kind-owned ItemPanel in nodes/.
|
|
137
|
+
export { CollectionsPopover } from './components/ui/panels/collections/collections-popover'
|
|
138
|
+
// Phase 5 Stage E — kinds with bespoke editors (slab holes list,
|
|
139
|
+
// ceiling height presets, etc.) use `parametrics.customPanel` to mount
|
|
140
|
+
// a kind-owned panel and need PanelWrapper for the chrome.
|
|
141
|
+
export { PanelWrapper } from './components/ui/panels/panel-wrapper'
|
|
142
|
+
export { ParametricInspector as Inspector } from './components/ui/panels/parametric-inspector'
|
|
13
143
|
export { PALETTE_COLORS } from './components/ui/primitives/color-dot'
|
|
144
|
+
export {
|
|
145
|
+
DropdownMenu,
|
|
146
|
+
DropdownMenuContent,
|
|
147
|
+
DropdownMenuItem,
|
|
148
|
+
DropdownMenuSeparator,
|
|
149
|
+
DropdownMenuSub,
|
|
150
|
+
DropdownMenuSubContent,
|
|
151
|
+
DropdownMenuSubTrigger,
|
|
152
|
+
DropdownMenuTrigger,
|
|
153
|
+
} from './components/ui/primitives/dropdown-menu'
|
|
14
154
|
export { useSidebarStore } from './components/ui/primitives/sidebar'
|
|
15
155
|
export { Slider } from './components/ui/primitives/slider'
|
|
16
156
|
export { SceneLoader } from './components/ui/scene-loader'
|
|
17
157
|
export type { ExtraPanel } from './components/ui/sidebar/icon-rail'
|
|
18
158
|
export { ItemsPanel } from './components/ui/sidebar/panels/items-panel'
|
|
159
|
+
export type { FunctionTreeNode } from './components/ui/sidebar/panels/items-panel/function-tree-panel'
|
|
19
160
|
export {
|
|
20
161
|
type ProjectVisibility,
|
|
21
162
|
SettingsPanel,
|
|
@@ -23,19 +164,110 @@ export {
|
|
|
23
164
|
} from './components/ui/sidebar/panels/settings-panel'
|
|
24
165
|
export type { SitePanelProps } from './components/ui/sidebar/panels/site-panel'
|
|
25
166
|
export type { SidebarTab } from './components/ui/sidebar/tab-bar'
|
|
26
|
-
export type { PresetsAdapter, PresetsTab } from './contexts/presets-context'
|
|
27
|
-
export { PresetsProvider } from './contexts/presets-context'
|
|
28
167
|
export type { SaveStatus } from './hooks/use-auto-save'
|
|
168
|
+
// useDragAction is the React-side glue for the registry's DragAction
|
|
169
|
+
// primitive. Public so registry-driven kinds (Phase 5+ Stage D ports)
|
|
170
|
+
// can express their affordances declaratively in their own folder.
|
|
171
|
+
export { type UseDragActionArgs, useDragAction } from './hooks/use-drag-action'
|
|
172
|
+
// Phase 5 Stage D — extras for kind-owned placement tools (FenceTool etc.).
|
|
173
|
+
export { markToolCancelConsumed } from './hooks/use-keyboard'
|
|
174
|
+
export { type Selection, useSelection } from './hooks/use-selection'
|
|
175
|
+
export { EDITOR_LAYER } from './lib/constants'
|
|
176
|
+
// Helper libs used by the kind-owned roof / stair / elevator panels.
|
|
177
|
+
export {
|
|
178
|
+
resolveCurrentBuildingId,
|
|
179
|
+
resolveElevatorNodeSupportY,
|
|
180
|
+
resolveElevatorSupportLevelId,
|
|
181
|
+
resolveElevatorSupportY,
|
|
182
|
+
} from './lib/elevator-support'
|
|
183
|
+
// Floor-plan stair helpers — the cumulative-transform walk
|
|
184
|
+
// (`computeFloorplanStairSegmentTransforms`) and the rich segment-entry
|
|
185
|
+
// builder (`buildFloorplanStairEntry`) used by the kind-owned stair
|
|
186
|
+
// floor-plan emitter in `@pascal-app/nodes/src/stair/floorplan.ts`.
|
|
187
|
+
// Each flight's transform depends on every prior sibling's length /
|
|
188
|
+
// height / `attachmentSide`, so individual stair-segments can't compute
|
|
189
|
+
// their own polygon in isolation — the stair (parent) owns the
|
|
190
|
+
// computation and emits the whole stack as one registry entry.
|
|
191
|
+
export {
|
|
192
|
+
alignFloorplanDraftPoint,
|
|
193
|
+
applyFloorplanAlignment,
|
|
194
|
+
buildFloorplanStairEntry,
|
|
195
|
+
FLOORPLAN_ALIGNMENT_THRESHOLD_M,
|
|
196
|
+
FLOORPLAN_DRAFT_ALIGN_ID,
|
|
197
|
+
type FloorplanAlignmentResult,
|
|
198
|
+
type FloorplanStairArrowEntry,
|
|
199
|
+
type FloorplanStairEntry,
|
|
200
|
+
type FloorplanStairSegmentEntry,
|
|
201
|
+
getFloorplanWallThickness,
|
|
202
|
+
} from './lib/floorplan'
|
|
203
|
+
export { commitFreshPlacementSubtree } from './lib/fresh-planar-placement'
|
|
204
|
+
export {
|
|
205
|
+
buildResetSurfaceMaterialUpdates,
|
|
206
|
+
buildRoofSurfaceMaterialPatch,
|
|
207
|
+
buildSingleSurfaceMaterialPatch,
|
|
208
|
+
buildStairSurfaceMaterialPatch,
|
|
209
|
+
buildWallSurfaceMaterialPatch,
|
|
210
|
+
getActivePaintMaterialLabel,
|
|
211
|
+
hasActivePaintMaterial,
|
|
212
|
+
} from './lib/material-paint'
|
|
213
|
+
export {
|
|
214
|
+
addFreshPlacementMetadata,
|
|
215
|
+
getPlacementMetadataRecord,
|
|
216
|
+
isFreshPlacementMetadata,
|
|
217
|
+
stripPlacementMetadataFlags,
|
|
218
|
+
} from './lib/placement-metadata'
|
|
219
|
+
export {
|
|
220
|
+
type PlanarCursorPlacementMode,
|
|
221
|
+
type PlanarPoint,
|
|
222
|
+
resolvePlanarCursorPosition,
|
|
223
|
+
} from './lib/planar-cursor-placement'
|
|
224
|
+
export { clearRoofDuplicateMetadata, duplicateRoofSubtree } from './lib/roof-duplication'
|
|
29
225
|
export type { SceneGraph } from './lib/scene'
|
|
30
226
|
export { applySceneGraphToEditor } from './lib/scene'
|
|
31
227
|
export { triggerSFX } from './lib/sfx-bus'
|
|
228
|
+
export { duplicateStairSubtree } from './lib/stair-duplication'
|
|
229
|
+
export {
|
|
230
|
+
getBuildingLevelsForLevel,
|
|
231
|
+
getStairLevelOptions,
|
|
232
|
+
resolveStairDestinationLevel,
|
|
233
|
+
resolveStairFromLevelId,
|
|
234
|
+
resolveStairPlacementLevelId,
|
|
235
|
+
resolveStairToLevelId,
|
|
236
|
+
} from './lib/stair-levels'
|
|
237
|
+
// `cn` (twMerge + clsx) — used by kind-owned panels in `@pascal-app/
|
|
238
|
+
// nodes` so they don't need their own copy / their own tailwind-merge
|
|
239
|
+
// dependency.
|
|
240
|
+
export { cn } from './lib/utils'
|
|
241
|
+
export {
|
|
242
|
+
getActiveBuildingPose,
|
|
243
|
+
resolveAlignmentForActiveBuilding,
|
|
244
|
+
snapBuildingLocalToWorldGrid,
|
|
245
|
+
snapWorldXZForActiveBuilding,
|
|
246
|
+
} from './lib/world-grid-snap'
|
|
247
|
+
export { default as useAlignmentGuides } from './store/use-alignment-guides'
|
|
32
248
|
export { default as useAudio } from './store/use-audio'
|
|
33
249
|
export { type CommandAction, useCommandRegistry } from './store/use-command-registry'
|
|
34
|
-
export type {
|
|
250
|
+
export type {
|
|
251
|
+
FloorplanSelectionTool,
|
|
252
|
+
MovingFenceEndpoint,
|
|
253
|
+
MovingWallEndpoint,
|
|
254
|
+
SplitOrientation,
|
|
255
|
+
Tool,
|
|
256
|
+
ToolDefaults,
|
|
257
|
+
ViewMode,
|
|
258
|
+
WorkspaceMode,
|
|
259
|
+
} from './store/use-editor'
|
|
35
260
|
export { default as useEditor } from './store/use-editor'
|
|
36
261
|
export {
|
|
37
262
|
type PaletteView,
|
|
38
263
|
type PaletteViewProps,
|
|
39
264
|
usePaletteViewRegistry,
|
|
40
265
|
} from './store/use-palette-view-registry'
|
|
266
|
+
export { default as usePlacementPreview } from './store/use-placement-preview'
|
|
41
267
|
export { useUploadStore } from './store/use-upload'
|
|
268
|
+
export { useWallMoveGhosts, type WallMoveGhostBridge } from './store/use-wall-move-ghosts'
|
|
269
|
+
export {
|
|
270
|
+
default as useWallSnapIndicator,
|
|
271
|
+
type WallSnapKind,
|
|
272
|
+
type WallSnapPoint,
|
|
273
|
+
} from './store/use-wall-snap-indicator'
|
package/src/lib/constants.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { OVERLAY_LAYER } from '@pascal-app/viewer'
|
|
2
|
+
|
|
1
3
|
/** Three.js layer used for editor-only objects (helpers, grid, polygon editors).
|
|
2
|
-
* The thumbnail camera renders only layer 0, so these are excluded from thumbnails.
|
|
3
|
-
|
|
4
|
+
* The thumbnail camera renders only layer 0, so these are excluded from thumbnails.
|
|
5
|
+
* Aliased to viewer's `OVERLAY_LAYER` so the post-processing overlay pass and the
|
|
6
|
+
* editor's overlay meshes stay on the same layer. */
|
|
7
|
+
export const EDITOR_LAYER = OVERLAY_LAYER
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { AnyNode, EditorApi, FenceNode, WallNode } from '@pascal-app/core'
|
|
2
|
+
import useEditor from '../store/use-editor'
|
|
3
|
+
|
|
4
|
+
type EditorState = ReturnType<typeof useEditor.getState>
|
|
5
|
+
type EndpointEngager = (node: AnyNode, endpoint: 'start' | 'end', editor: EditorState) => void
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Per-kind endpoint-move engagement. Kinds whose 2D endpoint drag
|
|
9
|
+
* needs its own store field (wall ↔ `movingWallEndpoint`, fence ↔
|
|
10
|
+
* `movingFenceEndpoint`) register their bridge here. The dispatcher
|
|
11
|
+
* is a table lookup rather than an `if (type === 'wall')` chain so
|
|
12
|
+
* adding a new endpoint-draggable kind is a one-line entry instead
|
|
13
|
+
* of a new branch. Each entry casts the generic `AnyNode` to its
|
|
14
|
+
* concrete kind — the lookup key already guarantees the type.
|
|
15
|
+
*/
|
|
16
|
+
const endpointEngagers: Record<string, EndpointEngager> = {
|
|
17
|
+
wall: (node, endpoint, editor) =>
|
|
18
|
+
editor.setMovingWallEndpoint({ wall: node as WallNode, endpoint }),
|
|
19
|
+
fence: (node, endpoint, editor) =>
|
|
20
|
+
editor.setMovingFenceEndpoint({ fence: node as FenceNode, endpoint }),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Concrete {@link EditorApi} backed by `useEditor`. Descriptors call into
|
|
25
|
+
* editor state through this interface; the editor owns the actual setter
|
|
26
|
+
* names so core stays decoupled.
|
|
27
|
+
*
|
|
28
|
+
* `engageMove` clears any in-progress endpoint drag or curve gesture so
|
|
29
|
+
* the move tool takes over cleanly — mirrors the legacy bookkeeping that
|
|
30
|
+
* lived inside `WallMoveArrowHandle.activateWallMove` / `FenceMoveArrowHandle`.
|
|
31
|
+
*/
|
|
32
|
+
export function createEditorApi(): EditorApi {
|
|
33
|
+
return {
|
|
34
|
+
engageMove(node: AnyNode) {
|
|
35
|
+
const editor = useEditor.getState()
|
|
36
|
+
editor.setPlacementDragMode(false)
|
|
37
|
+
// `setMovingNode` is typed against a narrower union than `AnyNode`
|
|
38
|
+
// (every concrete kind enumerated). Descriptors pass any node; the
|
|
39
|
+
// cast lets registry-driven move kinds through without forcing a
|
|
40
|
+
// schema-level type widening.
|
|
41
|
+
editor.setMovingNode(node as Parameters<typeof editor.setMovingNode>[0])
|
|
42
|
+
editor.setMovingWallEndpoint(null)
|
|
43
|
+
editor.setMovingFenceEndpoint(null)
|
|
44
|
+
editor.setCurvingWall(null)
|
|
45
|
+
editor.setCurvingFence(null)
|
|
46
|
+
},
|
|
47
|
+
engageMoveDrag(node: AnyNode) {
|
|
48
|
+
const editor = useEditor.getState()
|
|
49
|
+
// Flag drag mode BEFORE mounting the move tool so the coordinator reads
|
|
50
|
+
// it at setup and wires its commit-on-release listener.
|
|
51
|
+
editor.setPlacementDragMode(true)
|
|
52
|
+
editor.setMovingNode(node as Parameters<typeof editor.setMovingNode>[0])
|
|
53
|
+
editor.setMovingWallEndpoint(null)
|
|
54
|
+
editor.setMovingFenceEndpoint(null)
|
|
55
|
+
editor.setCurvingWall(null)
|
|
56
|
+
editor.setCurvingFence(null)
|
|
57
|
+
},
|
|
58
|
+
engageEndpointMove(node: AnyNode, endpoint: 'start' | 'end') {
|
|
59
|
+
endpointEngagers[node.type]?.(node, endpoint, useEditor.getState())
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyNode,
|
|
3
|
+
type AnyNodeId,
|
|
4
|
+
type BuildingNode,
|
|
5
|
+
type ElevatorNode,
|
|
6
|
+
type LevelNode,
|
|
7
|
+
spatialGridManager,
|
|
8
|
+
useScene,
|
|
9
|
+
} from '@pascal-app/core'
|
|
10
|
+
|
|
11
|
+
function getBuildingLevels(
|
|
12
|
+
buildingId: string | null | undefined,
|
|
13
|
+
nodes: Record<string, AnyNode>,
|
|
14
|
+
): LevelNode[] {
|
|
15
|
+
if (!buildingId) return []
|
|
16
|
+
const building = nodes[buildingId as AnyNodeId]
|
|
17
|
+
if (building?.type !== 'building') return []
|
|
18
|
+
|
|
19
|
+
return building.children
|
|
20
|
+
.map((childId) => nodes[childId as AnyNodeId])
|
|
21
|
+
.filter((entry): entry is LevelNode => entry?.type === 'level')
|
|
22
|
+
.sort((left, right) => left.level - right.level)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveCurrentBuildingId({
|
|
26
|
+
buildingId,
|
|
27
|
+
levelId,
|
|
28
|
+
nodes,
|
|
29
|
+
}: {
|
|
30
|
+
buildingId: BuildingNode['id'] | null
|
|
31
|
+
levelId: LevelNode['id'] | null
|
|
32
|
+
nodes: Record<string, AnyNode>
|
|
33
|
+
}): BuildingNode['id'] | null {
|
|
34
|
+
if (buildingId) return buildingId
|
|
35
|
+
if (!levelId) return null
|
|
36
|
+
|
|
37
|
+
const level = nodes[levelId as AnyNodeId]
|
|
38
|
+
if (
|
|
39
|
+
level?.type === 'level' &&
|
|
40
|
+
level.parentId &&
|
|
41
|
+
nodes[level.parentId as AnyNodeId]?.type === 'building'
|
|
42
|
+
) {
|
|
43
|
+
return level.parentId as BuildingNode['id']
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function resolveElevatorSupportLevelId({
|
|
50
|
+
buildingId,
|
|
51
|
+
preferredLevelId,
|
|
52
|
+
}: {
|
|
53
|
+
buildingId: string | null | undefined
|
|
54
|
+
preferredLevelId?: string | null
|
|
55
|
+
}): LevelNode['id'] | null {
|
|
56
|
+
const nodes = useScene.getState().nodes
|
|
57
|
+
const preferred = preferredLevelId ? nodes[preferredLevelId as AnyNodeId] : undefined
|
|
58
|
+
const levels = getBuildingLevels(buildingId, nodes)
|
|
59
|
+
const preferredInBuilding = preferredLevelId
|
|
60
|
+
? levels.find((level) => level.id === preferredLevelId)
|
|
61
|
+
: undefined
|
|
62
|
+
|
|
63
|
+
if (preferredInBuilding) return preferredInBuilding.id
|
|
64
|
+
if (levels.length === 0) return preferred?.type === 'level' ? preferred.id : null
|
|
65
|
+
|
|
66
|
+
return levels[0]?.id ?? null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function resolveElevatorSupportY({
|
|
70
|
+
buildingId,
|
|
71
|
+
preferredLevelId,
|
|
72
|
+
x,
|
|
73
|
+
z,
|
|
74
|
+
}: {
|
|
75
|
+
buildingId: string | null | undefined
|
|
76
|
+
preferredLevelId?: string | null
|
|
77
|
+
x: number
|
|
78
|
+
z: number
|
|
79
|
+
}): number {
|
|
80
|
+
const levelId = resolveElevatorSupportLevelId({ buildingId, preferredLevelId })
|
|
81
|
+
if (!levelId) return 0
|
|
82
|
+
|
|
83
|
+
return Math.max(0, spatialGridManager.getSlabElevationAt(levelId, x, z))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolveElevatorNodeSupportY(
|
|
87
|
+
node: ElevatorNode,
|
|
88
|
+
position: [number, number, number] = node.position,
|
|
89
|
+
): number {
|
|
90
|
+
return resolveElevatorSupportY({
|
|
91
|
+
buildingId: node.parentId,
|
|
92
|
+
preferredLevelId: node.fromLevelId ?? node.defaultLevelId,
|
|
93
|
+
x: position[0],
|
|
94
|
+
z: position[2],
|
|
95
|
+
})
|
|
96
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AlignmentAnchor,
|
|
3
|
+
type AlignmentGuide,
|
|
4
|
+
collectAlignmentAnchors,
|
|
5
|
+
resolveAlignment,
|
|
6
|
+
useScene,
|
|
7
|
+
} from '@pascal-app/core'
|
|
8
|
+
import { useAlignmentGuides } from '@pascal-app/editor'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Fixed Figma-style alignment threshold (meters) for floor-plan placement /
|
|
12
|
+
* move — parity with the 3D tools' `ALIGNMENT_THRESHOLD_M`. Pure-2D drafting
|
|
13
|
+
* can pass a zoom-scaled `threshold` instead so the magnetic pull stays
|
|
14
|
+
* constant in screen pixels across zoom levels.
|
|
15
|
+
*/
|
|
16
|
+
export const FLOORPLAN_ALIGNMENT_THRESHOLD_M = 0.08
|
|
17
|
+
|
|
18
|
+
export type FloorplanAlignmentResult = {
|
|
19
|
+
/** Plan point with the alignment snap delta applied (grid snap should
|
|
20
|
+
* already be baked into the input point). */
|
|
21
|
+
point: [number, number]
|
|
22
|
+
snapped: boolean
|
|
23
|
+
guides: AlignmentGuide[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Layer Figma-style alignment on top of an already grid-snapped plan point,
|
|
28
|
+
* shared by the 2D move sessions (`*FloorplanMoveTarget`) and the structural
|
|
29
|
+
* drafting branches in `floorplan-panel`.
|
|
30
|
+
*
|
|
31
|
+
* Publishes guides to the `useAlignmentGuides` store as a side effect — set
|
|
32
|
+
* on a match, cleared otherwise — so the mounted `FloorplanAlignmentGuideLayer`
|
|
33
|
+
* renders them. Returns the adjusted point. When `bypass` is true (Alt held)
|
|
34
|
+
* the point is returned unchanged and guides are cleared, matching the
|
|
35
|
+
* "No snap" affordance the placement tools advertise.
|
|
36
|
+
*
|
|
37
|
+
* `candidates` should be gathered ONCE per drag (`collectAlignmentAnchors`);
|
|
38
|
+
* the scene is stable during a single drag, so re-collecting per pointer-move
|
|
39
|
+
* is wasted work.
|
|
40
|
+
*/
|
|
41
|
+
export function applyFloorplanAlignment(
|
|
42
|
+
point: readonly [number, number],
|
|
43
|
+
movingAnchors: AlignmentAnchor[],
|
|
44
|
+
candidates: AlignmentAnchor[],
|
|
45
|
+
opts?: { bypass?: boolean; threshold?: number },
|
|
46
|
+
): FloorplanAlignmentResult {
|
|
47
|
+
if (opts?.bypass) {
|
|
48
|
+
useAlignmentGuides.getState().clear()
|
|
49
|
+
return { point: [point[0], point[1]], snapped: false, guides: [] }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result = resolveAlignment({
|
|
53
|
+
moving: movingAnchors,
|
|
54
|
+
candidates,
|
|
55
|
+
threshold: opts?.threshold ?? FLOORPLAN_ALIGNMENT_THRESHOLD_M,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
useAlignmentGuides.getState().set(result.guides)
|
|
59
|
+
|
|
60
|
+
if (!result.snap) {
|
|
61
|
+
return { point: [point[0], point[1]], snapped: false, guides: result.guides }
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
point: [point[0] + result.snap.dx, point[1] + result.snap.dz],
|
|
65
|
+
snapped: true,
|
|
66
|
+
guides: result.guides,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Synthetic node id for the in-progress structural-draft vertex. Never
|
|
71
|
+
* collides with a real scene node, so `collectAlignmentAnchors` excludes
|
|
72
|
+
* nothing real. */
|
|
73
|
+
export const FLOORPLAN_DRAFT_ALIGN_ID = '__floorplan_draft__'
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Align a single grid-snapped structural-draft vertex (wall / fence / polygon
|
|
77
|
+
* / roof endpoint) against every other node's anchors (incl. wall faces from
|
|
78
|
+
* the wall-face anchor work). Treats the vertex as one corner anchor, gathers
|
|
79
|
+
* candidates from the live scene, publishes guides (cleared on `bypass`), and
|
|
80
|
+
* returns the possibly-snapped point.
|
|
81
|
+
*
|
|
82
|
+
* Used by BOTH the move-preview branch and the click-commit handler so the
|
|
83
|
+
* committed vertex lands exactly where the preview showed it. Caller owns the
|
|
84
|
+
* per-kind precedence (existing-wall endpoint/join snap wins; angle-snap
|
|
85
|
+
* suppresses alignment) and only calls this when alignment should apply.
|
|
86
|
+
*
|
|
87
|
+
* `excludeIds` drops those nodes' anchors from the candidate pool — used when
|
|
88
|
+
* dragging a wall / fence endpoint so the moving endpoint doesn't try to
|
|
89
|
+
* align to its own (and its linked siblings') geometry that moves with it.
|
|
90
|
+
*/
|
|
91
|
+
export function alignFloorplanDraftPoint(
|
|
92
|
+
point: readonly [number, number],
|
|
93
|
+
opts?: { bypass?: boolean; threshold?: number; excludeIds?: readonly string[] },
|
|
94
|
+
): [number, number] {
|
|
95
|
+
if (opts?.bypass) {
|
|
96
|
+
useAlignmentGuides.getState().clear()
|
|
97
|
+
return [point[0], point[1]]
|
|
98
|
+
}
|
|
99
|
+
let candidates = collectAlignmentAnchors(useScene.getState().nodes, FLOORPLAN_DRAFT_ALIGN_ID)
|
|
100
|
+
if (opts?.excludeIds?.length) {
|
|
101
|
+
const excluded = new Set(opts.excludeIds)
|
|
102
|
+
candidates = candidates.filter((anchor) => !excluded.has(anchor.nodeId))
|
|
103
|
+
}
|
|
104
|
+
const { point: snapped } = applyFloorplanAlignment(
|
|
105
|
+
point,
|
|
106
|
+
[{ nodeId: FLOORPLAN_DRAFT_ALIGN_ID, kind: 'corner', x: point[0], z: point[1] }],
|
|
107
|
+
candidates,
|
|
108
|
+
{ threshold: opts?.threshold },
|
|
109
|
+
)
|
|
110
|
+
return snapped
|
|
111
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CeilingNode,
|
|
3
|
+
ColumnNode,
|
|
3
4
|
DoorNode,
|
|
5
|
+
ElevatorNode,
|
|
4
6
|
ItemNode,
|
|
5
7
|
Point2D,
|
|
6
8
|
RoofNode,
|
|
@@ -53,6 +55,16 @@ type CeilingEntry = {
|
|
|
53
55
|
holes: Point2D[][]
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
type ColumnEntry = {
|
|
59
|
+
column: ColumnNode
|
|
60
|
+
polygon: Point2D[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type ElevatorEntry = {
|
|
64
|
+
elevator: ElevatorNode
|
|
65
|
+
polygon: Point2D[]
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
type RoofEntry = {
|
|
57
69
|
roof: RoofNode
|
|
58
70
|
segments: Array<{
|
|
@@ -71,6 +83,8 @@ type FloorplanSelectionToolContext = {
|
|
|
71
83
|
walls: WallEntry[]
|
|
72
84
|
slabs: SlabEntry[]
|
|
73
85
|
ceilings: CeilingEntry[]
|
|
86
|
+
columns: ColumnEntry[]
|
|
87
|
+
elevators: ElevatorEntry[]
|
|
74
88
|
roofs: RoofEntry[]
|
|
75
89
|
openingHitTolerance: number
|
|
76
90
|
wallHitTolerance: number
|
|
@@ -123,6 +137,20 @@ export function getFloorplanHitNodeId(context: FloorplanSelectionToolContext) {
|
|
|
123
137
|
return stairHit.stair.id
|
|
124
138
|
}
|
|
125
139
|
|
|
140
|
+
const elevatorHit = context.elevators.find(({ polygon }) =>
|
|
141
|
+
isPointInsidePolygon(context.point, polygon),
|
|
142
|
+
)
|
|
143
|
+
if (elevatorHit) {
|
|
144
|
+
return elevatorHit.elevator.id
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const columnHit = context.columns.find(({ polygon }) =>
|
|
148
|
+
isPointInsidePolygon(context.point, polygon),
|
|
149
|
+
)
|
|
150
|
+
if (columnHit) {
|
|
151
|
+
return columnHit.column.id
|
|
152
|
+
}
|
|
153
|
+
|
|
126
154
|
const wallHit = context.walls.find(
|
|
127
155
|
({ wall, polygon }) =>
|
|
128
156
|
isPointInsidePolygon(context.point, polygon) ||
|
|
@@ -166,6 +194,8 @@ type FloorplanSelectionBoundsContext = {
|
|
|
166
194
|
openings: OpeningPolygonEntry[]
|
|
167
195
|
slabs: SlabEntry[]
|
|
168
196
|
ceilings: CeilingEntry[]
|
|
197
|
+
columns: ColumnEntry[]
|
|
198
|
+
elevators: ElevatorEntry[]
|
|
169
199
|
stairs: StairEntry[]
|
|
170
200
|
roofs: RoofEntry[]
|
|
171
201
|
}
|
|
@@ -179,6 +209,8 @@ export function getFloorplanSelectionIdsInBounds({
|
|
|
179
209
|
openings,
|
|
180
210
|
slabs,
|
|
181
211
|
ceilings,
|
|
212
|
+
columns,
|
|
213
|
+
elevators,
|
|
182
214
|
stairs,
|
|
183
215
|
roofs,
|
|
184
216
|
}: FloorplanSelectionBoundsContext) {
|
|
@@ -204,6 +236,12 @@ export function getFloorplanSelectionIdsInBounds({
|
|
|
204
236
|
const ceilingIds = ceilings
|
|
205
237
|
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
|
206
238
|
.map(({ ceiling }) => ceiling.id)
|
|
239
|
+
const columnIds = columns
|
|
240
|
+
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
|
241
|
+
.map(({ column }) => column.id)
|
|
242
|
+
const elevatorIds = elevators
|
|
243
|
+
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
|
244
|
+
.map(({ elevator }) => elevator.id)
|
|
207
245
|
const stairIds = stairs
|
|
208
246
|
.filter((stair) =>
|
|
209
247
|
getStairHitPolygons(stair).some((polygon) =>
|
|
@@ -224,6 +262,8 @@ export function getFloorplanSelectionIdsInBounds({
|
|
|
224
262
|
...openingIds,
|
|
225
263
|
...slabIds,
|
|
226
264
|
...ceilingIds,
|
|
265
|
+
...columnIds,
|
|
266
|
+
...elevatorIds,
|
|
227
267
|
...stairIds,
|
|
228
268
|
...roofIds,
|
|
229
269
|
]),
|