@pascal-app/editor 0.7.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 (244) hide show
  1. package/package.json +12 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
  3. package/src/components/editor/custom-camera-controls.tsx +755 -25
  4. package/src/components/editor/editor-layout-mobile.tsx +3 -3
  5. package/src/components/editor/editor-layout-v2.tsx +62 -54
  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 +203 -87
  9. package/src/components/editor/first-person/bvh-ecctrl.tsx +127 -62
  10. package/src/components/editor/first-person-controls.tsx +836 -31
  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 +5837 -12006
  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 +234 -190
  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 +435 -158
  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 +539 -0
  28. package/src/components/editor/thumbnail-generator.tsx +27 -64
  29. package/src/components/editor/use-floorplan-background-placement.ts +122 -35
  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-draft-layer.tsx +6 -1
  45. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  46. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -61
  47. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
  48. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
  49. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +29 -14
  50. package/src/components/editor-2d/svg-paths.ts +10 -2
  51. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +30 -9
  52. package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
  53. package/src/components/systems/roof/roof-edit-system.tsx +86 -12
  54. package/src/components/systems/stair/stair-edit-system.tsx +2 -2
  55. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
  56. package/src/components/systems/zone/zone-system.tsx +0 -0
  57. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  58. package/src/components/tools/elevator/elevator-tool.tsx +349 -0
  59. package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
  60. package/src/components/tools/fence/fence-drafting.ts +64 -22
  61. package/src/components/tools/item/move-tool.tsx +40 -107
  62. package/src/components/tools/item/placement-math.test.ts +10 -0
  63. package/src/components/tools/item/placement-math.ts +33 -9
  64. package/src/components/tools/item/placement-strategies.ts +201 -25
  65. package/src/components/tools/item/placement-types.ts +10 -1
  66. package/src/components/tools/item/use-draft-node.ts +20 -2
  67. package/src/components/tools/item/use-placement-coordinator.tsx +700 -299
  68. package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
  69. package/src/components/tools/roof/roof-tool.tsx +108 -12
  70. package/src/components/tools/select/box-select-state.ts +77 -0
  71. package/src/components/tools/select/box-select-tool.tsx +324 -543
  72. package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
  73. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  74. package/src/components/tools/select/select-candidates.test.ts +109 -0
  75. package/src/components/tools/select/select-candidates.ts +85 -0
  76. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  77. package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
  78. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  79. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  80. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  81. package/src/components/tools/shared/placement-box.tsx +123 -0
  82. package/src/components/tools/shared/polygon-editor.tsx +617 -167
  83. package/src/components/tools/shared/segment-angle.ts +57 -1
  84. package/src/components/tools/site/site-boundary-editor.tsx +464 -12
  85. package/src/components/tools/site/site-flag-model.ts +18 -0
  86. package/src/components/tools/stair/stair-defaults.ts +1 -1
  87. package/src/components/tools/stair/stair-tool.tsx +269 -35
  88. package/src/components/tools/tool-manager.tsx +172 -56
  89. package/src/components/tools/wall/wall-drafting.ts +98 -98
  90. package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
  91. package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
  92. package/src/components/tools/zone/zone-tool.tsx +36 -11
  93. package/src/components/ui/action-menu/action-button.tsx +21 -1
  94. package/src/components/ui/action-menu/camera-actions.tsx +0 -0
  95. package/src/components/ui/action-menu/control-modes.tsx +5 -76
  96. package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
  97. package/src/components/ui/action-menu/index.tsx +33 -196
  98. package/src/components/ui/action-menu/structure-tools.tsx +13 -88
  99. package/src/components/ui/action-menu/view-toggles.tsx +262 -37
  100. package/src/components/ui/command-palette/editor-commands.tsx +24 -17
  101. package/src/components/ui/command-palette/index.tsx +6 -256
  102. package/src/components/ui/controls/material-paint-panel.tsx +83 -0
  103. package/src/components/ui/controls/material-picker.tsx +17 -35
  104. package/src/components/ui/controls/metric-control.tsx +33 -11
  105. package/src/components/ui/floating-level-selector.tsx +48 -11
  106. package/src/components/ui/helpers/helper-manager.tsx +21 -17
  107. package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
  108. package/src/components/ui/item-catalog/catalog-items.tsx +1743 -315
  109. package/src/components/ui/item-catalog/item-catalog.tsx +91 -46
  110. package/src/components/ui/level-duplicate-dialog.tsx +5 -6
  111. package/src/components/ui/panels/node-display.ts +1 -0
  112. package/src/components/ui/panels/paint-panel.tsx +66 -41
  113. package/src/components/ui/panels/panel-manager.tsx +33 -74
  114. package/src/components/ui/panels/panel-wrapper.tsx +217 -12
  115. package/src/components/ui/panels/parametric-inspector.tsx +469 -0
  116. package/src/components/ui/panels/reference-panel.tsx +28 -13
  117. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  118. package/src/components/ui/primitives/number-input.tsx +1 -1
  119. package/src/components/ui/primitives/sidebar.tsx +0 -0
  120. package/src/components/ui/scene-loader.tsx +1 -3
  121. package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
  122. package/src/components/ui/sidebar/icon-rail.tsx +0 -0
  123. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  124. package/src/components/ui/sidebar/panels/items-panel/index.tsx +404 -0
  125. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
  126. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
  127. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
  128. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  129. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
  130. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
  131. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
  132. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  133. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
  134. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
  135. package/src/components/ui/sidebar/panels/site-panel/index.tsx +8 -12
  136. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  137. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
  138. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
  139. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
  140. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
  141. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
  142. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -34
  144. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -1
  145. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +52 -3
  146. package/src/components/ui/sidebar/tab-bar.tsx +61 -1
  147. package/src/components/ui/slider.tsx +1 -1
  148. package/src/components/viewer-overlay.tsx +169 -52
  149. package/src/components/viewer-zone-system.tsx +1 -1
  150. package/src/hooks/use-auto-save.ts +24 -3
  151. package/src/hooks/use-ceiling-events.ts +175 -0
  152. package/src/hooks/use-drag-action.ts +124 -0
  153. package/src/hooks/use-keyboard.ts +125 -62
  154. package/src/hooks/use-selection.ts +64 -0
  155. package/src/index.tsx +243 -4
  156. package/src/lib/constants.ts +6 -2
  157. package/src/lib/editor-api.ts +62 -0
  158. package/src/lib/elevator-support.ts +96 -0
  159. package/src/lib/floorplan/apply-alignment.ts +111 -0
  160. package/src/lib/floorplan/index.ts +7 -0
  161. package/src/lib/floorplan/selection-tool.ts +40 -0
  162. package/src/lib/floorplan/stairs.ts +12 -1
  163. package/src/lib/fresh-planar-placement.test.ts +102 -0
  164. package/src/lib/fresh-planar-placement.ts +63 -0
  165. package/src/lib/history.ts +2 -1
  166. package/src/lib/level-duplication.test.ts +0 -2
  167. package/src/lib/level-duplication.ts +55 -43
  168. package/src/lib/level-name.ts +11 -0
  169. package/src/lib/material-paint.ts +212 -20
  170. package/src/lib/placement-metadata.ts +29 -0
  171. package/src/lib/planar-cursor-placement.test.ts +43 -0
  172. package/src/lib/planar-cursor-placement.ts +42 -0
  173. package/src/lib/roof-duplication.ts +2 -2
  174. package/src/lib/scene-bounds.ts +1 -1
  175. package/src/lib/scene-clipboard.ts +267 -0
  176. package/src/lib/scene.ts +13 -4
  177. package/src/lib/sfx-bus.ts +25 -1
  178. package/src/lib/sfx-player.ts +75 -27
  179. package/src/lib/site-boundary.ts +1 -0
  180. package/src/lib/stair-duplication.ts +2 -2
  181. package/src/lib/stair-levels.test.ts +146 -0
  182. package/src/lib/stair-levels.ts +177 -0
  183. package/src/lib/world-grid-snap.ts +199 -0
  184. package/src/lib/zone-content.ts +130 -0
  185. package/src/store/use-alignment-guides.ts +21 -0
  186. package/src/store/use-editor.tsx +342 -91
  187. package/src/store/use-placement-preview.ts +31 -0
  188. package/src/store/use-wall-move-ghosts.ts +36 -0
  189. package/src/store/use-wall-snap-indicator.ts +32 -0
  190. package/tsconfig.json +2 -1
  191. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  192. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  193. package/src/components/feedback-dialog.tsx +0 -265
  194. package/src/components/pascal-radio.tsx +0 -280
  195. package/src/components/preview-button.tsx +0 -16
  196. package/src/components/tools/building/move-building-tool.tsx +0 -157
  197. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  198. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  199. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  200. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -257
  201. package/src/components/tools/column/column-tool.tsx +0 -97
  202. package/src/components/tools/column/move-column-tool.tsx +0 -105
  203. package/src/components/tools/door/door-math.ts +0 -110
  204. package/src/components/tools/door/door-tool.tsx +0 -324
  205. package/src/components/tools/door/move-door-tool.tsx +0 -412
  206. package/src/components/tools/fence/curve-fence-tool.tsx +0 -179
  207. package/src/components/tools/fence/fence-tool.tsx +0 -346
  208. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -438
  209. package/src/components/tools/fence/move-fence-tool.tsx +0 -298
  210. package/src/components/tools/item/item-tool.tsx +0 -26
  211. package/src/components/tools/roof/move-roof-tool.tsx +0 -359
  212. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  213. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  214. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  215. package/src/components/tools/slab/slab-tool.tsx +0 -322
  216. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  217. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  218. package/src/components/tools/wall/curve-wall-tool.tsx +0 -176
  219. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -423
  220. package/src/components/tools/wall/move-wall-tool.tsx +0 -356
  221. package/src/components/tools/wall/wall-tool.tsx +0 -332
  222. package/src/components/tools/window/move-window-tool.tsx +0 -447
  223. package/src/components/tools/window/window-math.ts +0 -117
  224. package/src/components/tools/window/window-tool.tsx +0 -332
  225. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  226. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  227. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  228. package/src/components/ui/panels/ceiling-panel.tsx +0 -250
  229. package/src/components/ui/panels/column-panel.tsx +0 -715
  230. package/src/components/ui/panels/door-panel.tsx +0 -1292
  231. package/src/components/ui/panels/fence-panel.tsx +0 -227
  232. package/src/components/ui/panels/item-panel.tsx +0 -306
  233. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  234. package/src/components/ui/panels/roof-panel.tsx +0 -230
  235. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  236. package/src/components/ui/panels/slab-panel.tsx +0 -250
  237. package/src/components/ui/panels/spawn-panel.tsx +0 -155
  238. package/src/components/ui/panels/stair-panel.tsx +0 -525
  239. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  240. package/src/components/ui/panels/wall-panel.tsx +0 -177
  241. package/src/components/ui/panels/window-panel.tsx +0 -960
  242. package/src/components/ui/viewer-toolbar.tsx +0 -436
  243. package/src/contexts/presets-context.tsx +0 -121
  244. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+
3
+ import type { AnyNode, AnyNodeId } from '@pascal-app/core'
4
+ import { useScene } from '@pascal-app/core'
5
+ import { useViewer } from '@pascal-app/viewer'
6
+
7
+ /**
8
+ * Resolved current selection — selected node IDs plus a convenience
9
+ * lookup into the live scene store. Returned shape is intentionally
10
+ * narrow: hosts that need richer per-node state can compose this with
11
+ * `useScene()` themselves.
12
+ */
13
+ export type Selection = {
14
+ /** Multi-select node IDs (drives the rest of the editor's UI). */
15
+ selectedIds: AnyNodeId[]
16
+ /** Currently active building context — surfaces / palettes filter on this. */
17
+ buildingId: AnyNodeId | null
18
+ /** Currently active level context. */
19
+ levelId: AnyNodeId | null
20
+ /** Currently active zone context. */
21
+ zoneId: AnyNodeId | null
22
+ /**
23
+ * Resolved nodes for `selectedIds`. Missing entries (deleted nodes) are
24
+ * filtered out, so the array length may be ≤ `selectedIds.length`.
25
+ */
26
+ selectedNodes: AnyNode[]
27
+ /**
28
+ * The single selected node, or `null` when zero or multiple nodes are
29
+ * selected. Useful for "save as preset" / inspector gating where the
30
+ * UI only makes sense for a unique selection.
31
+ */
32
+ selectedNode: AnyNode | null
33
+ }
34
+
35
+ /**
36
+ * Subscribe to the current selection. Equivalent to reading from
37
+ * `useViewer().selection` plus a live `useScene()` lookup, packaged as
38
+ * a single hook so consumers building their own shells (community,
39
+ * standalone editor app, embedders) don't have to learn the two
40
+ * separate stores.
41
+ *
42
+ * Selection state intentionally lives in `useViewer` (it tracks the
43
+ * camera / visibility hierarchy: building → level → zone → nodes), not
44
+ * `useScene` — see `wiki/architecture/scene-registry.md`.
45
+ */
46
+ export function useSelection(): Selection {
47
+ const selection = useViewer((s) => s.selection)
48
+ const nodes = useScene((s) => s.nodes)
49
+
50
+ const selectedIds = selection.selectedIds as AnyNodeId[]
51
+ const selectedNodes = selectedIds
52
+ .map((id) => nodes[id])
53
+ .filter((n): n is AnyNode => n !== undefined)
54
+ const selectedNode = selectedNodes.length === 1 ? (selectedNodes[0] ?? null) : null
55
+
56
+ return {
57
+ selectedIds,
58
+ buildingId: (selection.buildingId ?? null) as AnyNodeId | null,
59
+ levelId: (selection.levelId ?? null) as AnyNodeId | null,
60
+ zoneId: (selection.zoneId ?? null) as AnyNodeId | null,
61
+ selectedNodes,
62
+ selectedNode,
63
+ }
64
+ }
package/src/index.tsx CHANGED
@@ -1,13 +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'
16
+ export {
17
+ type SnapshotCameraData,
18
+ ThumbnailGenerator,
19
+ } from './components/editor/thumbnail-generator'
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'
3
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'
4
132
  export { SliderControl } from './components/ui/controls/slider-control'
133
+ export { ToggleControl } from './components/ui/controls/toggle-control'
5
134
  export { FloatingLevelSelector } from './components/ui/floating-level-selector'
6
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'
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'
7
154
  export { useSidebarStore } from './components/ui/primitives/sidebar'
8
155
  export { Slider } from './components/ui/primitives/slider'
9
156
  export { SceneLoader } from './components/ui/scene-loader'
10
157
  export type { ExtraPanel } from './components/ui/sidebar/icon-rail'
158
+ export { ItemsPanel } from './components/ui/sidebar/panels/items-panel'
159
+ export type { FunctionTreeNode } from './components/ui/sidebar/panels/items-panel/function-tree-panel'
11
160
  export {
12
161
  type ProjectVisibility,
13
162
  SettingsPanel,
@@ -15,20 +164,110 @@ export {
15
164
  } from './components/ui/sidebar/panels/settings-panel'
16
165
  export type { SitePanelProps } from './components/ui/sidebar/panels/site-panel'
17
166
  export type { SidebarTab } from './components/ui/sidebar/tab-bar'
18
- export { ViewerToolbarLeft, ViewerToolbarRight } from './components/ui/viewer-toolbar'
19
- export type { PresetsAdapter, PresetsTab } from './contexts/presets-context'
20
- export { PresetsProvider } from './contexts/presets-context'
21
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'
22
225
  export type { SceneGraph } from './lib/scene'
23
226
  export { applySceneGraphToEditor } from './lib/scene'
24
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'
25
248
  export { default as useAudio } from './store/use-audio'
26
249
  export { type CommandAction, useCommandRegistry } from './store/use-command-registry'
27
- 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'
28
260
  export { default as useEditor } from './store/use-editor'
29
261
  export {
30
262
  type PaletteView,
31
263
  type PaletteViewProps,
32
264
  usePaletteViewRegistry,
33
265
  } from './store/use-palette-view-registry'
266
+ export { default as usePlacementPreview } from './store/use-placement-preview'
34
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,