@pascal-app/editor 0.9.2 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/package.json +9 -7
  2. package/src/components/editor/bake-exporter.tsx +3 -1
  3. package/src/components/editor/bake-thumbnail.tsx +95 -0
  4. package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
  5. package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
  6. package/src/components/editor/custom-camera-controls.tsx +307 -212
  7. package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
  8. package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
  9. package/src/components/editor/first-person/build-collider-world.test.ts +66 -2
  10. package/src/components/editor/first-person/build-collider-world.ts +20 -0
  11. package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
  12. package/src/components/editor/first-person/terrain-collider.ts +212 -0
  13. package/src/components/editor/first-person-controls.tsx +295 -132
  14. package/src/components/editor/floating-action-menu.tsx +59 -65
  15. package/src/components/editor/floorplan-background-selection.test.ts +16 -2
  16. package/src/components/editor/floorplan-background-selection.ts +4 -18
  17. package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
  18. package/src/components/editor/floorplan-camera-sync.ts +213 -0
  19. package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
  20. package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
  21. package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
  22. package/src/components/editor/floorplan-panel.tsx +931 -861
  23. package/src/components/editor/group-actions.ts +155 -15
  24. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  25. package/src/components/editor/group-rotate-handle.tsx +1 -1
  26. package/src/components/editor/group-selection-box-3d.tsx +1 -1
  27. package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
  28. package/src/components/editor/handles/handle-drag-history.ts +14 -0
  29. package/src/components/editor/handles/preview-overrides.test.ts +26 -0
  30. package/src/components/editor/handles/preview-overrides.ts +13 -0
  31. package/src/components/editor/handles/resize-snap.test.ts +52 -0
  32. package/src/components/editor/handles/resize-snap.ts +23 -0
  33. package/src/components/editor/handles/use-handle-drag.ts +17 -7
  34. package/src/components/editor/index.tsx +42 -9
  35. package/src/components/editor/measurement-pill.tsx +6 -1
  36. package/src/components/editor/node-arrow-handles.tsx +51 -15
  37. package/src/components/editor/quick-measurement-card.tsx +9 -3
  38. package/src/components/editor/quick-measurement-hud.tsx +7 -1
  39. package/src/components/editor/selection-manager.tsx +3 -1
  40. package/src/components/editor/site-edge-labels.tsx +24 -2
  41. package/src/components/editor/snapshot-capture-overlay.tsx +4 -1
  42. package/src/components/editor/thumbnail-generator.tsx +66 -294
  43. package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
  44. package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
  45. package/src/components/editor/use-mesh-settle-epoch.ts +1 -0
  46. package/src/components/editor/wall-measurement-label.tsx +8 -16
  47. package/src/components/editor/wall-move-side-handles.tsx +193 -18
  48. package/src/components/editor/wall-opening-highlights.tsx +63 -32
  49. package/src/components/editor/wall-snap-beacon-layer.tsx +6 -4
  50. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
  51. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +39 -8
  52. package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
  53. package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
  54. package/src/components/editor-2d/floorplan-group-move.tsx +47 -4
  55. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +21 -5
  56. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
  57. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +93 -59
  58. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +27 -0
  59. package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
  60. package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
  61. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
  62. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
  63. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
  64. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
  65. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
  66. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
  67. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +75 -1
  68. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +92 -0
  69. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +93 -28
  70. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +279 -0
  71. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +621 -281
  72. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
  73. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +12 -7
  74. package/src/components/tools/fence/fence-drafting.ts +27 -0
  75. package/src/components/tools/item/use-draft-node.ts +131 -72
  76. package/src/components/tools/item/use-placement-coordinator.tsx +52 -4
  77. package/src/components/tools/registry/move-registry-node-tool.tsx +75 -4
  78. package/src/components/tools/roof/roof-tool.tsx +16 -0
  79. package/src/components/tools/shared/floor-stack-preview.ts +4 -0
  80. package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
  81. package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
  82. package/src/components/tools/shared/pointer-support-cap.ts +273 -0
  83. package/src/components/tools/site/site-boundary-editor.tsx +13 -6
  84. package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
  85. package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
  86. package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
  87. package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
  88. package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
  89. package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
  90. package/src/components/tools/stair/stair-click-guard.ts +72 -0
  91. package/src/components/tools/stair/stair-tool.tsx +98 -47
  92. package/src/components/tools/tool-manager.tsx +67 -9
  93. package/src/components/tools/wall/wall-drafting.test.ts +118 -1
  94. package/src/components/tools/wall/wall-drafting.ts +128 -9
  95. package/src/components/tools/wall/wall-snap-geometry.test.ts +8 -0
  96. package/src/components/tools/wall/wall-snap-geometry.ts +70 -3
  97. package/src/components/tools/zone/zone-tool.tsx +16 -0
  98. package/src/components/ui/action-menu/control-modes.tsx +4 -1
  99. package/src/components/ui/action-menu/measurement-control.tsx +107 -28
  100. package/src/components/ui/command-palette/editor-commands.tsx +14 -1
  101. package/src/components/ui/controls/material-paint-panel.tsx +7 -1
  102. package/src/components/ui/controls/material-picker.tsx +86 -7
  103. package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
  104. package/src/components/ui/floating-level-selector.tsx +73 -19
  105. package/src/components/ui/helpers/helper-manager.tsx +46 -0
  106. package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
  107. package/src/components/ui/panels/panel-manager.tsx +14 -1
  108. package/src/components/ui/panels/parametric-inspector.tsx +4 -2
  109. package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
  110. package/src/components/ui/panels/selection-breakdown.ts +20 -0
  111. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +14 -4
  112. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +16 -0
  113. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
  114. package/src/components/ui/sidebar/panels/site-panel/index.tsx +2 -0
  115. package/src/components/viewer/viewer-controls-bar.tsx +466 -0
  116. package/src/components/viewer/viewer-scene-header.tsx +235 -0
  117. package/src/components/viewer-overlay.tsx +20 -655
  118. package/src/components/walkthrough-hud.tsx +111 -0
  119. package/src/hooks/use-grid-events.ts +24 -0
  120. package/src/hooks/use-keyboard.ts +53 -30
  121. package/src/index.tsx +137 -4
  122. package/src/lib/active-placement-surface.test.ts +24 -0
  123. package/src/lib/active-placement-surface.ts +8 -1
  124. package/src/lib/camera-pose.test.ts +237 -0
  125. package/src/lib/camera-pose.ts +172 -0
  126. package/src/lib/direct-manipulation.test.ts +83 -0
  127. package/src/lib/direct-manipulation.ts +9 -0
  128. package/src/lib/door-interaction.ts +1 -1
  129. package/src/lib/elevation-guides.test.ts +107 -0
  130. package/src/lib/elevation-guides.ts +265 -0
  131. package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
  132. package/src/lib/floorplan/annotation-visibility.ts +133 -0
  133. package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
  134. package/src/lib/floorplan/drawing-coordination.ts +13 -0
  135. package/src/lib/floorplan/floorplan-export.test.ts +319 -0
  136. package/src/lib/floorplan/floorplan-export.tsx +745 -131
  137. package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
  138. package/src/lib/floorplan/floorplan-extension.ts +181 -0
  139. package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
  140. package/src/lib/floorplan/floorplan-mode.ts +73 -0
  141. package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
  142. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
  143. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
  144. package/src/lib/floorplan/geometry.ts +2 -3
  145. package/src/lib/fresh-planar-placement.test.ts +44 -1
  146. package/src/lib/fresh-planar-placement.ts +21 -4
  147. package/src/lib/glb-export.test.ts +116 -1
  148. package/src/lib/glb-export.ts +204 -7
  149. package/src/lib/ground-surface.ts +95 -0
  150. package/src/lib/history.test.ts +96 -0
  151. package/src/lib/history.ts +22 -0
  152. package/src/lib/interaction/overlay-policy.test.ts +2 -1
  153. package/src/lib/interaction/scope.ts +40 -8
  154. package/src/lib/level-duplication.test.ts +2 -1
  155. package/src/lib/measurements.test.ts +5 -0
  156. package/src/lib/measurements.ts +12 -1
  157. package/src/lib/quick-action-feedback.ts +33 -0
  158. package/src/lib/quick-action-nodes.test.ts +94 -0
  159. package/src/lib/quick-action-nodes.ts +47 -0
  160. package/src/lib/scene-clipboard.test.ts +130 -1
  161. package/src/lib/scene-clipboard.ts +237 -13
  162. package/src/lib/selection-routing.test.ts +16 -1
  163. package/src/lib/selection-routing.ts +5 -0
  164. package/src/lib/sfx-bus.ts +33 -2
  165. package/src/lib/sfx-player.test.ts +148 -6
  166. package/src/lib/sfx-player.ts +156 -8
  167. package/src/lib/site-boundary.test.ts +53 -0
  168. package/src/lib/site-boundary.ts +27 -0
  169. package/src/lib/snapping-mode.test.ts +29 -2
  170. package/src/lib/snapping-mode.ts +15 -3
  171. package/src/lib/stair-duplication.test.ts +103 -0
  172. package/src/lib/stair-duplication.ts +57 -85
  173. package/src/lib/stair-levels.ts +2 -0
  174. package/src/lib/terrain-sculpt.test.ts +429 -0
  175. package/src/lib/terrain-sculpt.ts +301 -0
  176. package/src/lib/terrain-verb-color.test.ts +51 -0
  177. package/src/lib/terrain-verb-color.ts +58 -0
  178. package/src/lib/touch-gesture-priority.test.ts +45 -0
  179. package/src/lib/touch-gesture-priority.ts +30 -0
  180. package/src/lib/window-interaction.ts +1 -1
  181. package/src/store/camera-pose-store.test.ts +29 -0
  182. package/src/store/camera-pose-store.ts +27 -0
  183. package/src/store/live-draft-preview-stores.test.ts +136 -0
  184. package/src/store/navigation-sync-pose-store.test.ts +34 -0
  185. package/src/store/navigation-sync-pose-store.ts +27 -0
  186. package/src/store/terrain-sculpt-mode.test.ts +223 -0
  187. package/src/store/use-delete-confirmation.ts +27 -0
  188. package/src/store/use-drawing-view.test.ts +23 -0
  189. package/src/store/use-drawing-view.ts +84 -0
  190. package/src/store/use-editor.tsx +184 -15
  191. package/src/store/use-elevation-guides.ts +40 -0
  192. package/src/store/use-fence-curve-draft.ts +38 -8
  193. package/src/store/use-first-person-hud.ts +40 -0
  194. package/src/store/use-floorplan-annotation-visibility.ts +60 -0
  195. package/src/store/use-floorplan-draft-preview.ts +54 -4
  196. package/src/store/use-floorplan-mode.ts +110 -0
  197. package/src/store/use-interaction-scope.test.ts +23 -3
  198. package/src/store/use-interaction-scope.ts +8 -0
  199. package/src/store/use-measurement-draft.test.ts +29 -0
  200. package/src/store/use-measurement-draft.ts +33 -6
  201. package/src/store/use-path-draft-preview.ts +90 -0
  202. package/src/store/use-stair-build-preview.ts +12 -2
  203. package/src/lib/floorplan/selection-tool.ts +0 -271
@@ -1,195 +1,178 @@
1
1
  'use client'
2
2
 
3
- import type {
4
- AnyNode,
5
- CeilingNode,
6
- ColumnNode,
7
- DoorNode,
8
- ElevatorNode,
9
- ItemNode,
10
- Point2D,
11
- RoofNode,
12
- RoofSegmentNode,
13
- SlabNode,
14
- StairNode,
15
- StairSegmentNode,
16
- WallNode,
17
- WindowNode,
18
- } from '@pascal-app/core'
19
- import { useCallback } from 'react'
20
- import {
21
- getFloorplanHitNodeId,
22
- getFloorplanSelectionIdsInBounds,
23
- } from '../../lib/floorplan/selection-tool'
3
+ import { type RefObject, useCallback } from 'react'
24
4
  import type { FloorplanSelectionBounds } from '../../lib/floorplan/types'
5
+ import {
6
+ type Point2 as MarqueePoint2,
7
+ polygonsIntersect,
8
+ segmentIntersectsPolygon,
9
+ } from '../tools/select/marquee-geometry'
10
+ import { collectSelectableCandidateIds } from '../tools/select/select-candidates'
25
11
  import type { WallPlanPoint } from '../tools/wall/wall-drafting'
26
12
 
27
- type OpeningNode = WindowNode | DoorNode
13
+ const REGISTRY_ENTRY_SELECTOR = '.floorplan-registry-base .floorplan-registry-entry[data-node-id]'
14
+ const REGISTRY_GEOMETRY_SELECTOR = 'path, polygon, polyline, rect, circle, line, image'
15
+ const POINT_HIT_EPSILON = 1e-4
28
16
 
29
- type WallPolygonEntry = {
30
- wall: WallNode
31
- polygon: Point2D[]
17
+ type FloorplanHitTestingOptions = {
18
+ sceneRef: RefObject<SVGGElement | null>
32
19
  }
33
20
 
34
- type OpeningPolygonEntry = {
35
- opening: OpeningNode
36
- polygon: Point2D[]
21
+ function boundsPolygon(bounds: FloorplanSelectionBounds): MarqueePoint2[] {
22
+ return [
23
+ [bounds.minX, bounds.minY],
24
+ [bounds.maxX, bounds.minY],
25
+ [bounds.maxX, bounds.maxY],
26
+ [bounds.minX, bounds.maxY],
27
+ ]
37
28
  }
38
29
 
39
- type SlabPolygonEntry = {
40
- slab: SlabNode
41
- polygon: Point2D[]
42
- holes: Point2D[][]
43
- }
30
+ function transformElementBoxToScene(
31
+ element: SVGGraphicsElement,
32
+ scene: SVGGElement,
33
+ ): MarqueePoint2[] | null {
34
+ const elementMatrix = element.getScreenCTM()
35
+ const sceneMatrix = scene.getScreenCTM()
36
+ const svg = scene.ownerSVGElement
37
+ if (!(elementMatrix && sceneMatrix && svg)) {
38
+ return null
39
+ }
44
40
 
45
- type CeilingPolygonEntry = {
46
- ceiling: CeilingNode
47
- polygon: Point2D[]
48
- holes: Point2D[][]
49
- }
41
+ let box: DOMRect
42
+ try {
43
+ box = element.getBBox()
44
+ } catch {
45
+ return null
46
+ }
50
47
 
51
- type ColumnPolygonEntry = {
52
- column: ColumnNode
53
- polygon: Point2D[]
48
+ const inverseSceneMatrix = sceneMatrix.inverse()
49
+ const corners: MarqueePoint2[] = [
50
+ [box.x, box.y],
51
+ [box.x + box.width, box.y],
52
+ [box.x + box.width, box.y + box.height],
53
+ [box.x, box.y + box.height],
54
+ ]
55
+ return corners.map(([x, y]) => {
56
+ const point = svg.createSVGPoint()
57
+ point.x = x
58
+ point.y = y
59
+ const screenPoint = point.matrixTransform(elementMatrix)
60
+ const scenePoint = screenPoint.matrixTransform(inverseSceneMatrix)
61
+ return [scenePoint.x, scenePoint.y] as MarqueePoint2
62
+ })
54
63
  }
55
64
 
56
- type ElevatorPolygonEntry = {
57
- elevator: ElevatorNode
58
- polygon: Point2D[]
59
- }
65
+ function elementIntersectsPlanPolygon(
66
+ element: SVGGraphicsElement,
67
+ scene: SVGGElement,
68
+ selectionPolygon: MarqueePoint2[],
69
+ ): boolean {
70
+ const polygon = transformElementBoxToScene(element, scene)
71
+ if (!polygon) {
72
+ return false
73
+ }
60
74
 
61
- type FloorplanRoofEntry = {
62
- roof: RoofNode
63
- segments: Array<{
64
- polygon: Point2D[]
65
- segment: RoofSegmentNode
66
- }>
67
- }
75
+ const [first, second, third] = polygon
76
+ if (!(first && second && third)) {
77
+ return false
78
+ }
68
79
 
69
- type FloorplanItemEntry = {
70
- item: ItemNode
71
- polygon: Point2D[]
72
- }
80
+ const width = Math.hypot(second[0] - first[0], second[1] - first[1])
81
+ const height = Math.hypot(third[0] - second[0], third[1] - second[1])
82
+ if (width <= POINT_HIT_EPSILON || height <= POINT_HIT_EPSILON) {
83
+ const end = width >= height ? second : third
84
+ return segmentIntersectsPolygon(first, end, selectionPolygon)
85
+ }
73
86
 
74
- type FloorplanStairSegmentEntry = {
75
- polygon: Point2D[]
76
- segment: StairSegmentNode | AnyNode
87
+ return polygonsIntersect(polygon, selectionPolygon)
77
88
  }
78
89
 
79
- type FloorplanStairEntry = {
80
- hitPolygons: Point2D[][]
81
- stair: StairNode
82
- segments: FloorplanStairSegmentEntry[]
90
+ export function collectRegistrySelectionIdsInBounds(
91
+ scene: SVGGElement,
92
+ bounds: FloorplanSelectionBounds,
93
+ candidateIds = collectSelectableCandidateIds(),
94
+ ): string[] {
95
+ const candidateIdSet = new Set(candidateIds)
96
+ const selectionPolygon = boundsPolygon(bounds)
97
+ const hitIds = new Set<string>()
98
+
99
+ for (const entry of scene.querySelectorAll<SVGGElement>(REGISTRY_ENTRY_SELECTOR)) {
100
+ const nodeId = entry.dataset.nodeId
101
+ if (!nodeId || !candidateIdSet.has(nodeId)) {
102
+ continue
103
+ }
104
+
105
+ const geometry = entry.querySelectorAll<SVGGraphicsElement>(REGISTRY_GEOMETRY_SELECTOR)
106
+ const elements = geometry.length > 0 ? Array.from(geometry) : [entry]
107
+ if (
108
+ elements.some((element) => elementIntersectsPlanPolygon(element, scene, selectionPolygon))
109
+ ) {
110
+ hitIds.add(nodeId)
111
+ }
112
+ }
113
+
114
+ return candidateIds.filter((id) => hitIds.has(id))
83
115
  }
84
116
 
85
- type UseFloorplanHitTestingArgs = {
86
- ceilingPolygons: CeilingPolygonEntry[]
87
- columnPolygons: ColumnPolygonEntry[]
88
- displaySlabPolygons: SlabPolygonEntry[]
89
- displayWallPolygons: WallPolygonEntry[]
90
- floorplanElevatorEntries: ElevatorPolygonEntry[]
91
- floorplanItemEntries: FloorplanItemEntry[]
92
- floorplanOpeningHitTolerance: number
93
- floorplanRoofEntries: FloorplanRoofEntry[]
94
- floorplanStairEntries: FloorplanStairEntry[]
95
- floorplanWallHitTolerance: number
96
- getOpeningCenterLine: (polygon: Point2D[]) => { start: Point2D; end: Point2D } | null
97
- isFloorplanItemContextActive: boolean
98
- openingsPolygons: OpeningPolygonEntry[]
99
- phase: 'site' | 'structure' | 'furnish'
100
- toPoint2D: (point: WallPlanPoint) => Point2D
117
+ export function getRegistryHitIdAtPlanPoint(
118
+ scene: SVGGElement,
119
+ planPoint: WallPlanPoint,
120
+ candidateIds = collectSelectableCandidateIds(),
121
+ ): string | null {
122
+ const sceneMatrix = scene.getScreenCTM()
123
+ const svg = scene.ownerSVGElement
124
+ if (!(sceneMatrix && svg)) {
125
+ return null
126
+ }
127
+
128
+ const point = svg.createSVGPoint()
129
+ point.x = planPoint[0]
130
+ point.y = planPoint[1]
131
+ const screenPoint = point.matrixTransform(sceneMatrix)
132
+ const candidateIdSet = new Set(candidateIds)
133
+
134
+ if (typeof document !== 'undefined' && typeof document.elementsFromPoint === 'function') {
135
+ for (const element of document.elementsFromPoint(screenPoint.x, screenPoint.y)) {
136
+ const entry = element.closest<SVGGElement>('.floorplan-registry-entry[data-node-id]')
137
+ const nodeId = entry?.dataset.nodeId
138
+ if (entry && nodeId && candidateIdSet.has(nodeId) && scene.contains(entry)) {
139
+ return nodeId
140
+ }
141
+ }
142
+ }
143
+
144
+ const hits = collectRegistrySelectionIdsInBounds(
145
+ scene,
146
+ {
147
+ minX: planPoint[0] - POINT_HIT_EPSILON,
148
+ maxX: planPoint[0] + POINT_HIT_EPSILON,
149
+ minY: planPoint[1] - POINT_HIT_EPSILON,
150
+ maxY: planPoint[1] + POINT_HIT_EPSILON,
151
+ },
152
+ candidateIds,
153
+ )
154
+ return hits.at(-1) ?? null
101
155
  }
102
156
 
103
- export function useFloorplanHitTesting({
104
- ceilingPolygons,
105
- columnPolygons,
106
- displaySlabPolygons,
107
- displayWallPolygons,
108
- floorplanElevatorEntries,
109
- floorplanItemEntries,
110
- floorplanOpeningHitTolerance,
111
- floorplanRoofEntries,
112
- floorplanStairEntries,
113
- floorplanWallHitTolerance,
114
- getOpeningCenterLine,
115
- isFloorplanItemContextActive,
116
- openingsPolygons,
117
- phase,
118
- toPoint2D,
119
- }: UseFloorplanHitTestingArgs) {
157
+ export function useFloorplanHitTesting({ sceneRef }: FloorplanHitTestingOptions) {
120
158
  const getFloorplanHitIdAtPoint = useCallback(
121
159
  (planPoint: WallPlanPoint) => {
122
- const point = toPoint2D(planPoint)
123
- return getFloorplanHitNodeId({
124
- point,
125
- ceilings: ceilingPolygons,
126
- phase,
127
- isItemContextActive: isFloorplanItemContextActive,
128
- items: floorplanItemEntries,
129
- openings: openingsPolygons,
130
- roofs: floorplanRoofEntries,
131
- stairs: floorplanStairEntries,
132
- elevators: floorplanElevatorEntries,
133
- walls: displayWallPolygons,
134
- slabs: displaySlabPolygons,
135
- openingHitTolerance: floorplanOpeningHitTolerance,
136
- wallHitTolerance: floorplanWallHitTolerance,
137
- columns: columnPolygons,
138
- getOpeningCenterLine,
139
- })
160
+ const scene = sceneRef.current
161
+ return scene ? getRegistryHitIdAtPlanPoint(scene, planPoint) : null
140
162
  },
141
- [
142
- ceilingPolygons,
143
- columnPolygons,
144
- displaySlabPolygons,
145
- displayWallPolygons,
146
- floorplanItemEntries,
147
- floorplanElevatorEntries,
148
- floorplanOpeningHitTolerance,
149
- floorplanRoofEntries,
150
- floorplanStairEntries,
151
- floorplanWallHitTolerance,
152
- getOpeningCenterLine,
153
- isFloorplanItemContextActive,
154
- openingsPolygons,
155
- phase,
156
- toPoint2D,
157
- ],
163
+ [sceneRef],
158
164
  )
159
165
 
160
- const getFloorplanSelectionIdsInBoundsForArea = useCallback(
161
- (bounds: FloorplanSelectionBounds) =>
162
- getFloorplanSelectionIdsInBounds({
163
- bounds,
164
- ceilings: ceilingPolygons,
165
- phase,
166
- isItemContextActive: isFloorplanItemContextActive,
167
- items: floorplanItemEntries,
168
- walls: displayWallPolygons,
169
- openings: openingsPolygons,
170
- roofs: floorplanRoofEntries,
171
- slabs: displaySlabPolygons,
172
- columns: columnPolygons,
173
- elevators: floorplanElevatorEntries,
174
- stairs: floorplanStairEntries,
175
- }),
176
- [
177
- ceilingPolygons,
178
- columnPolygons,
179
- displaySlabPolygons,
180
- displayWallPolygons,
181
- floorplanItemEntries,
182
- floorplanElevatorEntries,
183
- floorplanRoofEntries,
184
- floorplanStairEntries,
185
- isFloorplanItemContextActive,
186
- openingsPolygons,
187
- phase,
188
- ],
166
+ const getFloorplanSelectionIdsInBounds = useCallback(
167
+ (bounds: FloorplanSelectionBounds) => {
168
+ const scene = sceneRef.current
169
+ return scene ? collectRegistrySelectionIdsInBounds(scene, bounds) : []
170
+ },
171
+ [sceneRef],
189
172
  )
190
173
 
191
174
  return {
192
175
  getFloorplanHitIdAtPoint,
193
- getFloorplanSelectionIdsInBounds: getFloorplanSelectionIdsInBoundsForArea,
176
+ getFloorplanSelectionIdsInBounds,
194
177
  }
195
178
  }
@@ -13,6 +13,7 @@ import { useEffect, useState } from 'react'
13
13
  export function useMeshSettleEpoch(nodes: unknown): number {
14
14
  const [epoch, setEpoch] = useState(0)
15
15
  useEffect(() => {
16
+ void nodes
16
17
  let raf2 = 0
17
18
  const raf1 = requestAnimationFrame(() => {
18
19
  raf2 = requestAnimationFrame(() => setEpoch((e) => e + 1))
@@ -1,10 +1,11 @@
1
1
  'use client'
2
2
 
3
3
  import {
4
+ type AnyNode,
4
5
  type AnyNodeId,
5
6
  calculateLevelMiters,
6
- DEFAULT_WALL_HEIGHT,
7
7
  getWallCurveLength,
8
+ getWallEffectiveHeightForNodes,
8
9
  getWallMiterBoundaryPoints,
9
10
  getWallPlanFootprint,
10
11
  getWallSurfacePolygon,
@@ -91,10 +92,7 @@ export function WallMeasurementLabel() {
91
92
  return createPortal(<SelectedMeasurementAnnotation node={measurableNode} />, selectedObject)
92
93
  }
93
94
 
94
- function getLevelWalls(
95
- wall: WallNode,
96
- nodes: Record<string, WallNode | { type: string; children?: string[] }>,
97
- ): WallNode[] {
95
+ function getLevelWalls(wall: WallNode, nodes: Record<string, AnyNode>): WallNode[] {
98
96
  if (!wall.parentId) return [wall]
99
97
 
100
98
  const levelNode = nodes[wall.parentId as AnyNodeId]
@@ -308,7 +306,7 @@ function getCurvedWallMeasurementPath(
308
306
 
309
307
  function buildMeasurementGuide(
310
308
  wall: WallNode,
311
- nodes: Record<string, WallNode | { type: string; children?: string[] }>,
309
+ nodes: Record<string, AnyNode>,
312
310
  ): MeasurementGuide | null {
313
311
  const levelWalls = getLevelWalls(wall, nodes)
314
312
  const miterData = calculateLevelMiters(levelWalls)
@@ -317,7 +315,7 @@ function buildMeasurementGuide(
317
315
  const measurementPoints = measurementLine ?? fallbackMiddlePoints
318
316
  if (!measurementPoints) return null
319
317
 
320
- const height = wall.height ?? DEFAULT_WALL_HEIGHT
318
+ const height = getWallEffectiveHeightForNodes(wall, nodes)
321
319
  const startLocal = worldPointToWallLocal(wall, measurementPoints.start)
322
320
  const endLocal = worldPointToWallLocal(wall, measurementPoints.end)
323
321
  const curvedMeasurementPath = isCurvedWall(wall)
@@ -516,14 +514,7 @@ function WallMeasurementAnnotation({ wall }: { wall: WallNode }) {
516
514
  const color = isNight ? '#ffffff' : '#111111'
517
515
  const shadowColor = isNight ? '#111111' : '#ffffff'
518
516
 
519
- const guide = useMemo(
520
- () =>
521
- buildMeasurementGuide(
522
- wall,
523
- nodes as Record<string, WallNode | { type: string; children?: string[] }>,
524
- ),
525
- [nodes, wall],
526
- )
517
+ const guide = useMemo(() => buildMeasurementGuide(wall, nodes), [nodes, wall])
527
518
  const length = useMemo(() => {
528
519
  if (!guide?.guidePath?.length || guide.guidePath.length < 2) {
529
520
  return getWallCurveLength(wall)
@@ -538,7 +529,8 @@ function WallMeasurementAnnotation({ wall }: { wall: WallNode }) {
538
529
  return total
539
530
  }, [guide, wall])
540
531
  const label = formatLinearMeasurement(length, unit)
541
- const heightLabel = `H ${formatLinearMeasurement(wall.height ?? DEFAULT_WALL_HEIGHT, unit)}`
532
+ const height = useMemo(() => getWallEffectiveHeightForNodes(wall, nodes), [nodes, wall])
533
+ const heightLabel = `H ${formatLinearMeasurement(height, unit)}`
542
534
 
543
535
  if (!(guide && Number.isFinite(length) && length >= 0.01)) return null
544
536