@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.
Files changed (223) hide show
  1. package/package.json +8 -9
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
  3. package/src/components/editor/custom-camera-controls.tsx +754 -25
  4. package/src/components/editor/editor-layout-mobile.tsx +3 -3
  5. package/src/components/editor/editor-layout-v2.tsx +58 -51
  6. package/src/components/editor/export-manager.tsx +32 -3
  7. package/src/components/editor/first-person/build-collider-world.test.ts +149 -0
  8. package/src/components/editor/first-person/build-collider-world.ts +201 -83
  9. package/src/components/editor/first-person/bvh-ecctrl.tsx +8 -8
  10. package/src/components/editor/first-person-controls.tsx +825 -20
  11. package/src/components/editor/floating-action-menu.tsx +287 -196
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floorplan-panel.tsx +5829 -12005
  14. package/src/components/editor/grid.tsx +51 -17
  15. package/src/components/editor/group-move-handle.tsx +316 -0
  16. package/src/components/editor/group-rotate-handle.tsx +382 -0
  17. package/src/components/editor/group-transform-shared.test.ts +205 -0
  18. package/src/components/editor/group-transform-shared.ts +256 -0
  19. package/src/components/editor/handles/handle-arrow.tsx +523 -0
  20. package/src/components/editor/handles/use-handle-drag.ts +213 -0
  21. package/src/components/editor/index.tsx +178 -149
  22. package/src/components/editor/measurement-pill.tsx +75 -0
  23. package/src/components/editor/node-arrow-handles.tsx +1453 -0
  24. package/src/components/editor/selection-manager.tsx +433 -156
  25. package/src/components/editor/site-edge-labels.tsx +44 -20
  26. package/src/components/editor/slab-hole-highlights.tsx +466 -0
  27. package/src/components/editor/snapshot-capture-overlay.tsx +136 -62
  28. package/src/components/editor/thumbnail-generator.tsx +9 -3
  29. package/src/components/editor/use-floorplan-background-placement.ts +119 -32
  30. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  31. package/src/components/editor/use-floorplan-scene-data.ts +14 -2
  32. package/src/components/editor/wall-measurement-label.tsx +11 -6
  33. package/src/components/editor/wall-move-side-handles.tsx +855 -0
  34. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  35. package/src/components/editor/wall-snap-beacon-layer.tsx +150 -0
  36. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +6 -1
  37. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  38. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  39. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +227 -0
  40. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +616 -0
  41. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  42. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +75 -0
  43. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  44. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  45. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  46. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
  47. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
  48. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  49. package/src/components/editor-2d/svg-paths.ts +10 -2
  50. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +26 -3
  51. package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
  52. package/src/components/systems/roof/roof-edit-system.tsx +86 -12
  53. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  54. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  55. package/src/components/tools/elevator/elevator-tool.tsx +349 -0
  56. package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
  57. package/src/components/tools/fence/fence-drafting.ts +64 -22
  58. package/src/components/tools/item/move-tool.tsx +41 -105
  59. package/src/components/tools/item/placement-math.test.ts +10 -0
  60. package/src/components/tools/item/placement-math.ts +31 -5
  61. package/src/components/tools/item/placement-strategies.ts +190 -15
  62. package/src/components/tools/item/placement-types.ts +10 -1
  63. package/src/components/tools/item/use-draft-node.ts +20 -1
  64. package/src/components/tools/item/use-placement-coordinator.tsx +694 -191
  65. package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
  66. package/src/components/tools/roof/roof-tool.tsx +108 -12
  67. package/src/components/tools/select/box-select-state.ts +77 -0
  68. package/src/components/tools/select/box-select-tool.tsx +324 -538
  69. package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
  70. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  71. package/src/components/tools/select/select-candidates.test.ts +109 -0
  72. package/src/components/tools/select/select-candidates.ts +85 -0
  73. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  74. package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
  75. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  76. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  77. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  78. package/src/components/tools/shared/placement-box.tsx +123 -0
  79. package/src/components/tools/shared/polygon-editor.tsx +617 -167
  80. package/src/components/tools/shared/segment-angle.ts +56 -0
  81. package/src/components/tools/site/site-boundary-editor.tsx +464 -12
  82. package/src/components/tools/site/site-flag-model.ts +18 -0
  83. package/src/components/tools/stair/stair-defaults.ts +1 -1
  84. package/src/components/tools/stair/stair-tool.tsx +269 -35
  85. package/src/components/tools/tool-manager.tsx +172 -56
  86. package/src/components/tools/wall/wall-drafting.ts +98 -98
  87. package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
  88. package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
  89. package/src/components/tools/zone/zone-tool.tsx +16 -6
  90. package/src/components/ui/action-menu/action-button.tsx +21 -1
  91. package/src/components/ui/action-menu/control-modes.tsx +5 -82
  92. package/src/components/ui/action-menu/index.tsx +1 -113
  93. package/src/components/ui/action-menu/structure-tools.tsx +13 -88
  94. package/src/components/ui/action-menu/view-toggles.tsx +243 -6
  95. package/src/components/ui/command-palette/editor-commands.tsx +18 -13
  96. package/src/components/ui/command-palette/index.tsx +2 -1
  97. package/src/components/ui/controls/material-paint-panel.tsx +83 -0
  98. package/src/components/ui/controls/material-picker.tsx +11 -32
  99. package/src/components/ui/controls/metric-control.tsx +33 -11
  100. package/src/components/ui/floating-level-selector.tsx +47 -10
  101. package/src/components/ui/helpers/helper-manager.tsx +16 -17
  102. package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
  103. package/src/components/ui/item-catalog/catalog-items.tsx +1 -0
  104. package/src/components/ui/item-catalog/item-catalog.tsx +3 -0
  105. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  106. package/src/components/ui/panels/node-display.ts +1 -0
  107. package/src/components/ui/panels/panel-manager.tsx +35 -47
  108. package/src/components/ui/panels/panel-wrapper.tsx +217 -12
  109. package/src/components/ui/panels/parametric-inspector.tsx +469 -0
  110. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  111. package/src/components/ui/scene-loader.tsx +1 -3
  112. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  113. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  114. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
  115. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
  116. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
  117. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  118. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
  119. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
  120. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
  121. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  122. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
  123. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
  124. package/src/components/ui/sidebar/panels/site-panel/index.tsx +4 -6
  125. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  126. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
  127. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
  128. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
  129. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
  130. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
  131. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  132. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -36
  133. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  134. package/src/components/ui/sidebar/tab-bar.tsx +61 -1
  135. package/src/components/viewer-overlay.tsx +169 -52
  136. package/src/components/viewer-zone-system.tsx +1 -1
  137. package/src/hooks/use-auto-save.ts +10 -3
  138. package/src/hooks/use-ceiling-events.ts +175 -0
  139. package/src/hooks/use-drag-action.ts +124 -0
  140. package/src/hooks/use-keyboard.ts +115 -62
  141. package/src/hooks/use-selection.ts +64 -0
  142. package/src/index.tsx +237 -5
  143. package/src/lib/constants.ts +6 -2
  144. package/src/lib/editor-api.ts +62 -0
  145. package/src/lib/elevator-support.ts +96 -0
  146. package/src/lib/floorplan/apply-alignment.ts +111 -0
  147. package/src/lib/floorplan/index.ts +7 -0
  148. package/src/lib/floorplan/selection-tool.ts +40 -0
  149. package/src/lib/floorplan/stairs.ts +12 -1
  150. package/src/lib/fresh-planar-placement.test.ts +102 -0
  151. package/src/lib/fresh-planar-placement.ts +63 -0
  152. package/src/lib/history.ts +2 -1
  153. package/src/lib/level-duplication.ts +54 -42
  154. package/src/lib/level-name.ts +11 -0
  155. package/src/lib/material-paint.ts +211 -19
  156. package/src/lib/placement-metadata.ts +29 -0
  157. package/src/lib/planar-cursor-placement.test.ts +43 -0
  158. package/src/lib/planar-cursor-placement.ts +42 -0
  159. package/src/lib/roof-duplication.ts +1 -1
  160. package/src/lib/scene-clipboard.ts +267 -0
  161. package/src/lib/scene.ts +13 -4
  162. package/src/lib/sfx-bus.ts +23 -1
  163. package/src/lib/sfx-player.ts +70 -22
  164. package/src/lib/site-boundary.ts +1 -0
  165. package/src/lib/stair-levels.test.ts +146 -0
  166. package/src/lib/stair-levels.ts +177 -0
  167. package/src/lib/world-grid-snap.ts +199 -0
  168. package/src/lib/zone-content.ts +130 -0
  169. package/src/store/use-alignment-guides.ts +21 -0
  170. package/src/store/use-editor.tsx +320 -37
  171. package/src/store/use-placement-preview.ts +31 -0
  172. package/src/store/use-wall-move-ghosts.ts +36 -0
  173. package/src/store/use-wall-snap-indicator.ts +32 -0
  174. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  175. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  176. package/src/components/tools/building/move-building-tool.tsx +0 -157
  177. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  178. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  179. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  180. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  181. package/src/components/tools/column/column-tool.tsx +0 -97
  182. package/src/components/tools/column/move-column-tool.tsx +0 -105
  183. package/src/components/tools/door/door-math.ts +0 -110
  184. package/src/components/tools/door/door-tool.tsx +0 -324
  185. package/src/components/tools/door/move-door-tool.tsx +0 -412
  186. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  187. package/src/components/tools/fence/fence-tool.tsx +0 -346
  188. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  189. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  190. package/src/components/tools/item/item-tool.tsx +0 -26
  191. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  192. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  193. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  194. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  195. package/src/components/tools/slab/slab-tool.tsx +0 -322
  196. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  197. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  198. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  199. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  200. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  201. package/src/components/tools/wall/wall-tool.tsx +0 -332
  202. package/src/components/tools/window/move-window-tool.tsx +0 -447
  203. package/src/components/tools/window/window-math.ts +0 -117
  204. package/src/components/tools/window/window-tool.tsx +0 -332
  205. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  206. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  207. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  208. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  209. package/src/components/ui/panels/column-panel.tsx +0 -759
  210. package/src/components/ui/panels/door-panel.tsx +0 -1299
  211. package/src/components/ui/panels/fence-panel.tsx +0 -222
  212. package/src/components/ui/panels/item-panel.tsx +0 -306
  213. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  214. package/src/components/ui/panels/roof-panel.tsx +0 -280
  215. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  216. package/src/components/ui/panels/slab-panel.tsx +0 -250
  217. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  218. package/src/components/ui/panels/stair-panel.tsx +0 -577
  219. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  220. package/src/components/ui/panels/wall-panel.tsx +0 -273
  221. package/src/components/ui/panels/window-panel.tsx +0 -968
  222. package/src/contexts/presets-context.tsx +0 -121
  223. 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
- export { CameraActions as ViewerToolbarRight } from './components/ui/action-menu/camera-actions'
8
- export { ViewToggles as ViewerToolbarLeft } from './components/ui/action-menu/view-toggles'
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 { FloorplanSelectionTool, SplitOrientation, ViewMode } from './store/use-editor'
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'
@@ -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
- export const EDITOR_LAYER = 1
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,3 +1,10 @@
1
+ export {
2
+ alignFloorplanDraftPoint,
3
+ applyFloorplanAlignment,
4
+ FLOORPLAN_ALIGNMENT_THRESHOLD_M,
5
+ FLOORPLAN_DRAFT_ALIGN_ID,
6
+ type FloorplanAlignmentResult,
7
+ } from './apply-alignment'
1
8
  export {
2
9
  clampPlanValue,
3
10
  doesPolygonIntersectSelectionBounds,
@@ -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
  ]),