@pascal-app/editor 0.8.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/package.json +15 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
  3. package/src/components/editor/bake-exporter.tsx +47 -0
  4. package/src/components/editor/custom-camera-controls.tsx +795 -28
  5. package/src/components/editor/editor-layout-mobile.tsx +8 -3
  6. package/src/components/editor/editor-layout-v2.tsx +76 -51
  7. package/src/components/editor/export-manager.tsx +69 -32
  8. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  9. package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
  10. package/src/components/editor/first-person/build-collider-world.ts +228 -83
  11. package/src/components/editor/first-person-controls.tsx +943 -27
  12. package/src/components/editor/floating-action-menu.tsx +676 -208
  13. package/src/components/editor/floating-building-action-menu.tsx +2 -2
  14. package/src/components/editor/floorplan-background-selection.test.ts +58 -0
  15. package/src/components/editor/floorplan-background-selection.ts +3 -2
  16. package/src/components/editor/floorplan-panel.tsx +6916 -12137
  17. package/src/components/editor/grid.tsx +197 -36
  18. package/src/components/editor/group-actions.ts +457 -0
  19. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  20. package/src/components/editor/group-move-3d.ts +397 -0
  21. package/src/components/editor/group-rotate-handle.tsx +420 -0
  22. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  23. package/src/components/editor/group-transform-shared.test.ts +396 -0
  24. package/src/components/editor/group-transform-shared.ts +489 -0
  25. package/src/components/editor/handles/handle-arrow.tsx +581 -0
  26. package/src/components/editor/handles/use-handle-drag.ts +246 -0
  27. package/src/components/editor/index.tsx +412 -162
  28. package/src/components/editor/measurement-pill.tsx +115 -0
  29. package/src/components/editor/node-action-menu.tsx +14 -1
  30. package/src/components/editor/node-arrow-handles.tsx +1646 -0
  31. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  32. package/src/components/editor/quick-measurement-card.tsx +70 -0
  33. package/src/components/editor/quick-measurement-hud.tsx +28 -0
  34. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  35. package/src/components/editor/selection-manager.tsx +1106 -483
  36. package/src/components/editor/site-edge-labels.tsx +45 -21
  37. package/src/components/editor/slab-hole-highlights.tsx +474 -0
  38. package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
  39. package/src/components/editor/three-context-bridge.ts +22 -0
  40. package/src/components/editor/thumbnail-generator.tsx +140 -41
  41. package/src/components/editor/use-floorplan-background-placement.ts +190 -35
  42. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  43. package/src/components/editor/use-floorplan-scene-data.ts +19 -7
  44. package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
  45. package/src/components/editor/wall-measurement-label.tsx +14 -19
  46. package/src/components/editor/wall-move-side-handles.tsx +870 -0
  47. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  48. package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
  49. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
  50. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  51. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  52. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  53. package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
  54. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  55. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
  56. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  57. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
  58. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
  59. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  60. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
  61. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  62. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  63. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
  64. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
  65. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  66. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
  69. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  70. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  71. package/src/components/editor-2d/svg-paths.ts +10 -2
  72. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
  73. package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
  74. package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
  75. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  76. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  77. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  78. package/src/components/systems/zone/zone-system.tsx +42 -13
  79. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  80. package/src/components/tools/elevator/elevator-tool.tsx +375 -0
  81. package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
  82. package/src/components/tools/fence/fence-drafting.ts +141 -27
  83. package/src/components/tools/item/move-tool.tsx +45 -105
  84. package/src/components/tools/item/placement-math.test.ts +22 -0
  85. package/src/components/tools/item/placement-math.ts +59 -7
  86. package/src/components/tools/item/placement-strategies.ts +432 -16
  87. package/src/components/tools/item/placement-types.ts +22 -1
  88. package/src/components/tools/item/use-draft-node.ts +51 -2
  89. package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
  90. package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
  91. package/src/components/tools/roof/roof-tool.tsx +350 -28
  92. package/src/components/tools/select/box-select-state.ts +88 -0
  93. package/src/components/tools/select/box-select-tool.tsx +442 -520
  94. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  95. package/src/components/tools/select/marquee-geometry.ts +155 -0
  96. package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
  97. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  98. package/src/components/tools/select/select-candidates.test.ts +109 -0
  99. package/src/components/tools/select/select-candidates.ts +85 -0
  100. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  101. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  102. package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
  103. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  104. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  105. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  106. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  107. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  108. package/src/components/tools/shared/placement-box.tsx +305 -0
  109. package/src/components/tools/shared/polygon-editor.tsx +866 -175
  110. package/src/components/tools/shared/segment-angle.ts +56 -0
  111. package/src/components/tools/site/site-boundary-editor.tsx +520 -16
  112. package/src/components/tools/site/site-flag-model.ts +18 -0
  113. package/src/components/tools/stair/stair-defaults.ts +2 -1
  114. package/src/components/tools/stair/stair-tool.tsx +342 -37
  115. package/src/components/tools/tool-manager.tsx +239 -62
  116. package/src/components/tools/wall/wall-drafting.test.ts +330 -0
  117. package/src/components/tools/wall/wall-drafting.ts +248 -171
  118. package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
  119. package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
  120. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  121. package/src/components/tools/zone/zone-tool.tsx +86 -82
  122. package/src/components/ui/action-menu/action-button.tsx +21 -1
  123. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  124. package/src/components/ui/action-menu/control-modes.tsx +40 -170
  125. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  126. package/src/components/ui/action-menu/index.tsx +3 -117
  127. package/src/components/ui/action-menu/measurement-control.tsx +188 -0
  128. package/src/components/ui/action-menu/structure-tools.tsx +27 -93
  129. package/src/components/ui/action-menu/view-toggles.tsx +279 -90
  130. package/src/components/ui/command-palette/editor-commands.tsx +20 -15
  131. package/src/components/ui/command-palette/index.tsx +5 -3
  132. package/src/components/ui/controls/material-paint-panel.tsx +152 -0
  133. package/src/components/ui/controls/material-picker.tsx +87 -177
  134. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  135. package/src/components/ui/controls/metric-control.tsx +142 -46
  136. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  137. package/src/components/ui/controls/slider-control.tsx +88 -38
  138. package/src/components/ui/floating-level-selector.tsx +47 -10
  139. package/src/components/ui/helpers/building-helper.tsx +10 -23
  140. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  141. package/src/components/ui/helpers/helper-manager.tsx +235 -23
  142. package/src/components/ui/helpers/item-helper.tsx +28 -32
  143. package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
  144. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  145. package/src/components/ui/icon-ref.tsx +47 -0
  146. package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
  147. package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
  148. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  149. package/src/components/ui/panels/node-display.ts +17 -16
  150. package/src/components/ui/panels/panel-manager.tsx +55 -58
  151. package/src/components/ui/panels/panel-wrapper.tsx +238 -12
  152. package/src/components/ui/panels/parametric-inspector.tsx +482 -0
  153. package/src/components/ui/panels/reference-panel.tsx +54 -19
  154. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  155. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  156. package/src/components/ui/primitives/sidebar.tsx +1 -0
  157. package/src/components/ui/scene-loader.tsx +1 -3
  158. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  159. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  160. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  161. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  162. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  163. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
  164. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
  165. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
  166. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  167. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
  168. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
  169. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  170. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  171. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
  172. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  173. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
  174. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
  175. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
  176. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  177. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  178. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
  179. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
  180. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
  181. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
  182. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
  183. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  184. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  185. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
  186. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  187. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  188. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  189. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  190. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  191. package/src/components/ui/sidebar/tab-bar.tsx +75 -1
  192. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  193. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  194. package/src/components/ui/snap-target-badge.tsx +88 -0
  195. package/src/components/viewer-overlay.tsx +253 -61
  196. package/src/components/viewer-zone-system.tsx +7 -2
  197. package/src/hooks/use-auto-save.test.ts +14 -0
  198. package/src/hooks/use-auto-save.ts +64 -10
  199. package/src/hooks/use-ceiling-events.ts +176 -0
  200. package/src/hooks/use-drag-action.ts +127 -0
  201. package/src/hooks/use-keyboard.ts +493 -95
  202. package/src/hooks/use-selection.ts +64 -0
  203. package/src/index.tsx +451 -6
  204. package/src/lib/active-placement-surface.ts +35 -0
  205. package/src/lib/ceiling-plan-snap.ts +24 -0
  206. package/src/lib/constants.ts +6 -2
  207. package/src/lib/contextual-help.test.ts +112 -0
  208. package/src/lib/contextual-help.ts +144 -0
  209. package/src/lib/continuation.ts +64 -0
  210. package/src/lib/direct-manipulation.test.ts +192 -0
  211. package/src/lib/direct-manipulation.ts +109 -0
  212. package/src/lib/editor-api.ts +50 -0
  213. package/src/lib/elevator-support.ts +96 -0
  214. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  215. package/src/lib/floorplan/apply-alignment.ts +117 -0
  216. package/src/lib/floorplan/floorplan-export.tsx +364 -0
  217. package/src/lib/floorplan/geometry.ts +53 -0
  218. package/src/lib/floorplan/index.ts +10 -0
  219. package/src/lib/floorplan/items.ts +5 -2
  220. package/src/lib/floorplan/plan-coords.ts +21 -0
  221. package/src/lib/floorplan/selection-tool.ts +40 -0
  222. package/src/lib/floorplan/stairs.ts +12 -1
  223. package/src/lib/fresh-planar-placement.test.ts +339 -0
  224. package/src/lib/fresh-planar-placement.ts +130 -0
  225. package/src/lib/glb-export.test.ts +441 -0
  226. package/src/lib/glb-export.ts +874 -0
  227. package/src/lib/history.ts +11 -1
  228. package/src/lib/interaction/hot-set.test.ts +133 -0
  229. package/src/lib/interaction/hot-set.ts +67 -0
  230. package/src/lib/interaction/overlay-policy.test.ts +51 -0
  231. package/src/lib/interaction/overlay-policy.ts +59 -0
  232. package/src/lib/interaction/scope.ts +211 -0
  233. package/src/lib/level-duplication.ts +54 -42
  234. package/src/lib/level-selection.ts +2 -2
  235. package/src/lib/material-paint.ts +227 -48
  236. package/src/lib/measurement-kind.test.ts +30 -0
  237. package/src/lib/measurement-kind.ts +19 -0
  238. package/src/lib/measurement-label.test.ts +47 -0
  239. package/src/lib/measurement-label.ts +68 -0
  240. package/src/lib/measurement-parser.ts +116 -0
  241. package/src/lib/measurements.test.ts +183 -0
  242. package/src/lib/measurements.ts +199 -0
  243. package/src/lib/paint-scope.test.ts +454 -0
  244. package/src/lib/paint-scope.ts +461 -0
  245. package/src/lib/placement-drag-release.ts +23 -0
  246. package/src/lib/placement-metadata.ts +29 -0
  247. package/src/lib/planar-cursor-placement.test.ts +100 -0
  248. package/src/lib/planar-cursor-placement.ts +42 -0
  249. package/src/lib/plugin-panels.test.ts +19 -0
  250. package/src/lib/plugin-panels.ts +91 -0
  251. package/src/lib/quick-measurement.test.ts +77 -0
  252. package/src/lib/quick-measurement.ts +109 -0
  253. package/src/lib/roof-duplication.ts +7 -7
  254. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  255. package/src/lib/roof-wall-hit.ts +164 -0
  256. package/src/lib/scene-clipboard.test.ts +196 -0
  257. package/src/lib/scene-clipboard.ts +332 -0
  258. package/src/lib/scene.ts +40 -8
  259. package/src/lib/selection-routing.test.ts +298 -0
  260. package/src/lib/selection-routing.ts +175 -0
  261. package/src/lib/sfx/index.ts +1 -0
  262. package/src/lib/sfx/movement-tick.test.ts +65 -0
  263. package/src/lib/sfx/movement-tick.ts +18 -0
  264. package/src/lib/sfx-bus.ts +69 -10
  265. package/src/lib/sfx-player.test.ts +124 -0
  266. package/src/lib/sfx-player.ts +148 -55
  267. package/src/lib/site-boundary.ts +1 -0
  268. package/src/lib/slab-plan-snap.test.ts +93 -0
  269. package/src/lib/slab-plan-snap.ts +108 -0
  270. package/src/lib/snapping-mode.test.ts +138 -0
  271. package/src/lib/snapping-mode.ts +179 -0
  272. package/src/lib/stair-duplication.ts +4 -4
  273. package/src/lib/stair-levels.test.ts +146 -0
  274. package/src/lib/stair-levels.ts +177 -0
  275. package/src/lib/surface-plan-snap.test.ts +71 -0
  276. package/src/lib/surface-plan-snap.ts +256 -0
  277. package/src/lib/use-linear-display.ts +40 -0
  278. package/src/lib/world-grid-snap.ts +231 -0
  279. package/src/lib/zone-content.ts +130 -0
  280. package/src/store/use-alignment-guides.ts +21 -0
  281. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  282. package/src/store/use-editor.tsx +767 -114
  283. package/src/store/use-facing-pose.ts +44 -0
  284. package/src/store/use-fence-curve-draft.ts +21 -0
  285. package/src/store/use-floorplan-draft-preview.ts +101 -0
  286. package/src/store/use-floorplan-marquee.ts +50 -0
  287. package/src/store/use-interaction-scope.test.ts +186 -0
  288. package/src/store/use-interaction-scope.ts +132 -0
  289. package/src/store/use-measurement-draft.test.ts +530 -0
  290. package/src/store/use-measurement-draft.ts +543 -0
  291. package/src/store/use-opening-guides.ts +41 -0
  292. package/src/store/use-placement-preview.ts +39 -0
  293. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  294. package/src/store/use-quick-measurement-hud.ts +77 -0
  295. package/src/store/use-segment-draft-chain.ts +28 -0
  296. package/src/store/use-stair-build-preview.ts +43 -0
  297. package/src/store/use-wall-move-ghosts.ts +36 -0
  298. package/src/store/use-wall-snap-indicator.ts +34 -0
  299. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  300. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  301. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  302. package/src/components/tools/building/move-building-tool.tsx +0 -157
  303. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  304. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  305. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  306. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  307. package/src/components/tools/column/column-tool.tsx +0 -97
  308. package/src/components/tools/column/move-column-tool.tsx +0 -105
  309. package/src/components/tools/door/door-math.ts +0 -110
  310. package/src/components/tools/door/door-tool.tsx +0 -324
  311. package/src/components/tools/door/move-door-tool.tsx +0 -412
  312. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  313. package/src/components/tools/fence/fence-tool.tsx +0 -346
  314. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  315. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  316. package/src/components/tools/item/item-tool.tsx +0 -26
  317. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  318. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  319. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  320. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  321. package/src/components/tools/slab/slab-tool.tsx +0 -322
  322. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  323. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  324. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  325. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  326. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  327. package/src/components/tools/wall/wall-tool.tsx +0 -332
  328. package/src/components/tools/window/move-window-tool.tsx +0 -447
  329. package/src/components/tools/window/window-math.ts +0 -117
  330. package/src/components/tools/window/window-tool.tsx +0 -332
  331. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  332. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  333. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  334. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  335. package/src/components/ui/panels/column-panel.tsx +0 -759
  336. package/src/components/ui/panels/door-panel.tsx +0 -1299
  337. package/src/components/ui/panels/fence-panel.tsx +0 -222
  338. package/src/components/ui/panels/item-panel.tsx +0 -306
  339. package/src/components/ui/panels/paint-panel.tsx +0 -163
  340. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  341. package/src/components/ui/panels/roof-panel.tsx +0 -280
  342. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  343. package/src/components/ui/panels/slab-panel.tsx +0 -250
  344. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  345. package/src/components/ui/panels/stair-panel.tsx +0 -577
  346. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  347. package/src/components/ui/panels/wall-panel.tsx +0 -273
  348. package/src/components/ui/panels/window-panel.tsx +0 -968
  349. package/src/contexts/presets-context.tsx +0 -121
  350. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -1,22 +1,30 @@
1
1
  'use client'
2
2
 
3
3
  import type { SiteNode } from '@pascal-app/core'
4
- import { sceneRegistry, useScene } from '@pascal-app/core'
5
- import { useViewer } from '@pascal-app/viewer'
4
+ import { sceneRegistry, useLiveNodeOverrides, useScene } from '@pascal-app/core'
5
+ import { getSceneTheme, useViewer } from '@pascal-app/viewer'
6
6
  import { Html } from '@react-three/drei'
7
- import { createPortal, useFrame } from '@react-three/fiber'
8
- import { useMemo, useRef, useState } from 'react'
9
- import type { Object3D } from 'three'
7
+ import { createPortal, useFrame, useThree } from '@react-three/fiber'
8
+ import { useCallback, useMemo, useRef, useState } from 'react'
9
+ import { type Camera, type Object3D, Vector3 } from 'three'
10
+ import { formatLinearMeasurement } from '../../lib/measurements'
11
+ import { SITE_BOUNDARY_DRAG_LABEL } from '../../lib/site-boundary'
12
+ import { useActiveHandleDrag } from '../../store/use-interaction-scope'
10
13
 
11
- function formatMeasurement(value: number, unit: 'metric' | 'imperial') {
12
- if (unit === 'imperial') {
13
- const feet = value * 3.280_84
14
- const wholeFeet = Math.floor(feet)
15
- const inches = Math.round((feet - wholeFeet) * 12)
16
- if (inches === 12) return `${wholeFeet + 1}'0"`
17
- return `${wholeFeet}'${inches}"`
18
- }
19
- return `${Number.parseFloat(value.toFixed(2))}m`
14
+ type ViewportSize = {
15
+ width: number
16
+ height: number
17
+ }
18
+
19
+ const htmlPosition = new Vector3()
20
+
21
+ function calculateHtmlPosition(el: Object3D, camera: Camera, size: ViewportSize) {
22
+ htmlPosition.setFromMatrixPosition(el.matrixWorld)
23
+ htmlPosition.project(camera)
24
+
25
+ const widthHalf = size.width / 2
26
+ const heightHalf = size.height / 2
27
+ return [htmlPosition.x * widthHalf + widthHalf, -htmlPosition.y * heightHalf + heightHalf]
20
28
  }
21
29
 
22
30
  export function SiteEdgeLabels() {
@@ -29,12 +37,28 @@ export function SiteEdgeLabels() {
29
37
  const node = state.nodes[firstRoot]
30
38
  return node?.type === 'site' ? (node as SiteNode) : null
31
39
  })
40
+ const activeHandleDrag = useActiveHandleDrag()
32
41
  const unit = useViewer((state) => state.unit)
33
- const theme = useViewer((state) => state.theme)
42
+ const cameraMode = useViewer((state) => state.cameraMode)
43
+ const isNight = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark')
44
+ const camera = useThree((state) => state.camera)
45
+ // Drei Html can hold the previous default camera across a camera-object swap.
46
+ const calculateLabelPosition = useCallback(
47
+ (el: Object3D, _camera: Camera, size: ViewportSize) => calculateHtmlPosition(el, camera, size),
48
+ [camera],
49
+ )
34
50
 
35
51
  const siteNodeId = siteNode?.id
52
+ const livePolygon = useLiveNodeOverrides((state) => {
53
+ if (!siteNodeId) return null
54
+ return (state.overrides.get(siteNodeId)?.polygon as SiteNode['polygon'] | undefined) ?? null
55
+ })
56
+ const polygon = livePolygon?.points ?? siteNode?.polygon?.points ?? []
57
+ const shouldShowLabels =
58
+ Boolean(siteNodeId) &&
59
+ activeHandleDrag?.nodeId === siteNodeId &&
60
+ activeHandleDrag?.label === SITE_BOUNDARY_DRAG_LABEL
36
61
 
37
- const isNight = theme === 'dark'
38
62
  const color = isNight ? '#ffffff' : '#111111'
39
63
  const shadowColor = isNight ? '#111111' : '#ffffff'
40
64
 
@@ -55,7 +79,6 @@ export function SiteEdgeLabels() {
55
79
  })
56
80
 
57
81
  const edges = useMemo(() => {
58
- const polygon = siteNode?.polygon?.points ?? []
59
82
  if (polygon.length < 2) return []
60
83
  return polygon.map(([x1, z1], i) => {
61
84
  const [x2, z2] = polygon[(i + 1) % polygon.length]!
@@ -64,16 +87,17 @@ export function SiteEdgeLabels() {
64
87
  const dist = Math.sqrt((x2 - x1!) ** 2 + (z2 - z1!) ** 2)
65
88
  return { midX, midZ, dist }
66
89
  })
67
- }, [siteNode?.polygon?.points])
90
+ }, [polygon])
68
91
 
69
- if (!siteObj || edges.length === 0) return null
92
+ if (!shouldShowLabels || !siteObj || edges.length === 0) return null
70
93
 
71
94
  return createPortal(
72
95
  <>
73
96
  {edges.map((edge, i) => (
74
97
  <Html
75
98
  center
76
- key={`edge-${i}`}
99
+ calculatePosition={calculateLabelPosition}
100
+ key={`${cameraMode}-${camera.uuid}-edge-${i}`}
77
101
  occlude
78
102
  position={[edge.midX, 0.5, edge.midZ]}
79
103
  style={{ pointerEvents: 'none', userSelect: 'none' }}
@@ -86,7 +110,7 @@ export function SiteEdgeLabels() {
86
110
  textShadow: `-1.5px -1.5px 0 ${shadowColor}, 1.5px -1.5px 0 ${shadowColor}, -1.5px 1.5px 0 ${shadowColor}, 1.5px 1.5px 0 ${shadowColor}, 0 0 4px ${shadowColor}, 0 0 4px ${shadowColor}`,
87
111
  }}
88
112
  >
89
- {formatMeasurement(edge.dist, unit)}
113
+ {formatLinearMeasurement(edge.dist, unit)}
90
114
  </div>
91
115
  </Html>
92
116
  ))}
@@ -0,0 +1,474 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNodeId,
5
+ type BuildingNode,
6
+ type LevelNode,
7
+ resolveBuildingForLevel,
8
+ resolveLevelId,
9
+ type SlabNode,
10
+ type SurfaceHoleMetadata,
11
+ sceneRegistry,
12
+ useLiveNodeOverrides,
13
+ useScene,
14
+ } from '@pascal-app/core'
15
+ import { useViewer } from '@pascal-app/viewer'
16
+ import { createPortal, type ThreeEvent } from '@react-three/fiber'
17
+ import { useCallback, useEffect, useMemo, useState } from 'react'
18
+ import {
19
+ BufferGeometry,
20
+ DoubleSide,
21
+ Float32BufferAttribute,
22
+ type Object3D,
23
+ ShapeUtils,
24
+ Vector2,
25
+ } from 'three'
26
+ import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu'
27
+ import { EDITOR_LAYER } from '../../lib/constants'
28
+ import { holeEditScope } from '../../lib/interaction/scope'
29
+ import useEditor from '../../store/use-editor'
30
+ import useInteractionScope, { useEditingHole } from '../../store/use-interaction-scope'
31
+ import { suppressBoxSelectForPointer } from '../tools/select/box-select-state'
32
+ import { swallowNextClick } from './handles/use-handle-drag'
33
+
34
+ const ACCENT = 0x83_81_ed
35
+ const SURFACE_OFFSET = 0.01
36
+ const MIN_HIT_HEIGHT = 0.16
37
+
38
+ const NO_RAYCAST = () => null
39
+ let restoreNodeClickSuppression: (() => void) | null = null
40
+
41
+ const outlineMaterial = new LineBasicNodeMaterial({
42
+ color: ACCENT,
43
+ depthTest: false,
44
+ depthWrite: false,
45
+ })
46
+
47
+ const fillMaterial = new MeshBasicNodeMaterial({
48
+ color: ACCENT,
49
+ transparent: true,
50
+ opacity: 0.5,
51
+ depthTest: false,
52
+ depthWrite: false,
53
+ side: DoubleSide,
54
+ })
55
+
56
+ // Invisible hit targets must stay out of the scene MRT pass; keep them on
57
+ // EDITOR_LAYER even though they never write color.
58
+ const hitMaterial = new MeshBasicNodeMaterial({
59
+ color: ACCENT,
60
+ colorWrite: false,
61
+ depthTest: false,
62
+ depthWrite: false,
63
+ opacity: 0,
64
+ side: DoubleSide,
65
+ transparent: true,
66
+ })
67
+
68
+ type HolePolygon = Array<[number, number]>
69
+
70
+ function makeFillGeometry(hole: HolePolygon, y: number): BufferGeometry {
71
+ const contour2d = hole.map(([x, z]) => new Vector2(x, z))
72
+ const positions: number[] = []
73
+ const indices: number[] = []
74
+
75
+ for (const point of contour2d) {
76
+ positions.push(point.x, y, point.y)
77
+ }
78
+
79
+ const triangles = ShapeUtils.triangulateShape(contour2d, [])
80
+ for (const tri of triangles) {
81
+ indices.push(tri[0]!, tri[2]!, tri[1]!)
82
+ }
83
+
84
+ const geometry = new BufferGeometry()
85
+ geometry.setAttribute('position', new Float32BufferAttribute(positions, 3))
86
+ geometry.setIndex(indices)
87
+ geometry.computeVertexNormals()
88
+ geometry.computeBoundingSphere()
89
+ return geometry
90
+ }
91
+
92
+ function makeOutlineGeometry(hole: HolePolygon, y: number): BufferGeometry {
93
+ const positions: number[] = []
94
+
95
+ for (let index = 0; index < hole.length; index += 1) {
96
+ const [ax, az] = hole[index]!
97
+ const [bx, bz] = hole[(index + 1) % hole.length]!
98
+ positions.push(ax, y, az, bx, y, bz)
99
+ }
100
+
101
+ const geometry = new BufferGeometry()
102
+ geometry.setAttribute('position', new Float32BufferAttribute(positions, 3))
103
+ geometry.computeBoundingSphere()
104
+ return geometry
105
+ }
106
+
107
+ function makeHitGeometry(hole: HolePolygon, centerY: number, height: number): BufferGeometry {
108
+ const topY = centerY + height / 2
109
+ const bottomY = centerY - height / 2
110
+ const positions: number[] = []
111
+ const indices: number[] = []
112
+
113
+ for (const [x, z] of hole) positions.push(x, topY, z)
114
+ for (const [x, z] of hole) positions.push(x, bottomY, z)
115
+
116
+ const triangles = ShapeUtils.triangulateShape(
117
+ hole.map(([x, z]) => new Vector2(x, z)),
118
+ [],
119
+ )
120
+ const bottomOffset = hole.length
121
+ for (const tri of triangles) {
122
+ indices.push(tri[0]!, tri[2]!, tri[1]!)
123
+ indices.push(bottomOffset + tri[0]!, bottomOffset + tri[1]!, bottomOffset + tri[2]!)
124
+ }
125
+
126
+ for (let index = 0; index < hole.length; index += 1) {
127
+ const nextIndex = (index + 1) % hole.length
128
+ indices.push(index, nextIndex, bottomOffset + nextIndex)
129
+ indices.push(index, bottomOffset + nextIndex, bottomOffset + index)
130
+ }
131
+
132
+ const geometry = new BufferGeometry()
133
+ geometry.setAttribute('position', new Float32BufferAttribute(positions, 3))
134
+ geometry.setIndex(indices)
135
+ geometry.computeVertexNormals()
136
+ geometry.computeBoundingSphere()
137
+ return geometry
138
+ }
139
+
140
+ function getSurfaceY(slab: SlabNode): number {
141
+ const elevation = slab.elevation ?? 0.05
142
+ return (elevation > 0 ? elevation : 0) + SURFACE_OFFSET
143
+ }
144
+
145
+ function getHitCenterY(slab: SlabNode): number {
146
+ const elevation = slab.elevation ?? 0.05
147
+ const surfaceTop = elevation > 0 ? elevation : 0
148
+ // Keep the hit box's TOP at the slab surface (never poke above it) so the
149
+ // slab's centre thickness/height handle, which sits on top, always wins the
150
+ // raycast over the hole's selectable area.
151
+ return surfaceTop - getHitHeight(slab) / 2
152
+ }
153
+
154
+ function getHitHeight(slab: SlabNode): number {
155
+ return Math.max(Math.abs(slab.elevation ?? 0.05), MIN_HIT_HEIGHT)
156
+ }
157
+
158
+ function clearHoveredHoleIfMatches(nodeId: string, holeIndex: number) {
159
+ const { hoveredHole, setHoveredHole } = useEditor.getState()
160
+ if (hoveredHole?.nodeId === nodeId && hoveredHole.holeIndex === holeIndex) {
161
+ setHoveredHole(null)
162
+ }
163
+ }
164
+
165
+ function selectOwnedNode(ownerId: string, expectedType: 'stair' | 'elevator') {
166
+ const nodes = useScene.getState().nodes
167
+ const owner = nodes[ownerId as AnyNodeId]
168
+ if (owner?.type !== expectedType) return
169
+
170
+ const selectedId = owner.id as AnyNodeId
171
+
172
+ if (owner.type === 'elevator') {
173
+ const buildingId =
174
+ owner.parentId && nodes[owner.parentId as AnyNodeId]?.type === 'building'
175
+ ? (owner.parentId as BuildingNode['id'])
176
+ : null
177
+
178
+ useViewer
179
+ .getState()
180
+ .setSelection(
181
+ buildingId ? { buildingId, selectedIds: [selectedId] } : { selectedIds: [selectedId] },
182
+ )
183
+ return
184
+ }
185
+
186
+ const levelId = resolveLevelId(owner, nodes)
187
+ const buildingId =
188
+ levelId && levelId !== 'default' ? resolveBuildingForLevel(levelId as AnyNodeId, nodes) : null
189
+
190
+ useViewer.getState().setSelection({
191
+ ...(buildingId ? { buildingId: buildingId as BuildingNode['id'] } : {}),
192
+ ...(levelId && levelId !== 'default' ? { levelId: levelId as LevelNode['id'] } : {}),
193
+ selectedIds: [selectedId],
194
+ })
195
+ }
196
+
197
+ function resetPointerCursor() {
198
+ if (document.body.style.cursor === 'pointer') {
199
+ document.body.style.cursor = ''
200
+ }
201
+ }
202
+
203
+ function stopPointerPropagation(event: ThreeEvent<PointerEvent>) {
204
+ event.stopPropagation()
205
+ suppressBoxSelectForPointer(event)
206
+ event.nativeEvent.stopPropagation()
207
+ event.nativeEvent.stopImmediatePropagation()
208
+ }
209
+
210
+ function suppressNodeClickUntilPointerUp() {
211
+ restoreNodeClickSuppression?.()
212
+ const previousInputDragging = useViewer.getState().inputDragging
213
+ useViewer.getState().setInputDragging(true)
214
+ swallowNextClick()
215
+
216
+ const restore = () => {
217
+ if (restoreNodeClickSuppression !== restore) return
218
+ useViewer.getState().setInputDragging(previousInputDragging)
219
+ window.removeEventListener('pointerup', restore)
220
+ window.removeEventListener('pointercancel', restore)
221
+ restoreNodeClickSuppression = null
222
+ }
223
+
224
+ restoreNodeClickSuppression = restore
225
+ window.addEventListener('pointerup', restore, { once: true })
226
+ window.addEventListener('pointercancel', restore, { once: true })
227
+ }
228
+
229
+ function releaseNodeClickSuppression() {
230
+ restoreNodeClickSuppression?.()
231
+ }
232
+
233
+ export function SlabHoleHighlights() {
234
+ const selectedIds = useViewer((state) => state.selection.selectedIds)
235
+ const hoveredHole = useEditor((state) => state.hoveredHole)
236
+ const setHoveredHole = useEditor((state) => state.setHoveredHole)
237
+
238
+ useEffect(() => {
239
+ if (hoveredHole && !selectedIds.includes(hoveredHole.nodeId as AnyNodeId)) {
240
+ setHoveredHole(null)
241
+ }
242
+ }, [hoveredHole, selectedIds, setHoveredHole])
243
+
244
+ // Sole selection only — hole outlines are click-to-edit chrome, and a
245
+ // multi-selection shows highlight only (the group transforms as one piece).
246
+ if (selectedIds.length !== 1) return null
247
+
248
+ return (
249
+ <>
250
+ {selectedIds.map((id) => (
251
+ <SelectedSlabHoleHighlights key={id} slabId={id} />
252
+ ))}
253
+ </>
254
+ )
255
+ }
256
+
257
+ function SelectedSlabHoleHighlights({ slabId }: { slabId: string }) {
258
+ const node = useScene((state) => state.nodes[slabId as AnyNodeId])
259
+ const override = useLiveNodeOverrides((state) => state.overrides.get(slabId))
260
+ const hoveredHole = useEditor((state) => state.hoveredHole)
261
+ const editingHole = useEditingHole()
262
+ const setHoveredHole = useEditor((state) => state.setHoveredHole)
263
+
264
+ const slab = node?.type === 'slab' ? (node as SlabNode) : null
265
+ const effectiveSlab = slab ? ({ ...slab, ...(override ?? {}) } as SlabNode) : null
266
+ const holes = effectiveSlab?.holes ?? []
267
+ const holeMetadata = effectiveSlab?.holeMetadata ?? []
268
+
269
+ // Portal the highlights into the slab's OWN object — exactly how
270
+ // NodeArrowHandles portals into a node object. This is what makes R3F deliver
271
+ // pointer events to the hit meshes (portaling into the raw scene renders but
272
+ // never receives hover/click), and the children inherit the slab's world
273
+ // transform so the holes sit in slab-local space with no manual math.
274
+ const [slabObject, setSlabObject] = useState<Object3D | null>(
275
+ () => sceneRegistry.nodes.get(slabId as AnyNodeId) ?? null,
276
+ )
277
+ useEffect(() => {
278
+ let frameId = 0
279
+ const resolve = () => {
280
+ const next = sceneRegistry.nodes.get(slabId as AnyNodeId) ?? null
281
+ setSlabObject((cur) => (cur === next ? cur : next))
282
+ if (!next) {
283
+ frameId = window.requestAnimationFrame(resolve)
284
+ }
285
+ }
286
+ resolve()
287
+ return () => {
288
+ if (frameId) window.cancelAnimationFrame(frameId)
289
+ }
290
+ }, [slabId])
291
+
292
+ useEffect(() => {
293
+ if (hoveredHole?.nodeId !== slabId) return
294
+ if (!effectiveSlab || hoveredHole.holeIndex >= holes.length) {
295
+ setHoveredHole(null)
296
+ }
297
+ }, [effectiveSlab, holes.length, hoveredHole, slabId, setHoveredHole])
298
+
299
+ if (!effectiveSlab || holes.length === 0 || !slabObject) return null
300
+
301
+ const surfaceY = getSurfaceY(effectiveSlab)
302
+ const hitCenterY = getHitCenterY(effectiveSlab)
303
+ const hitHeight = getHitHeight(effectiveSlab)
304
+
305
+ return createPortal(
306
+ <group>
307
+ {holes.map((hole, holeIndex) => {
308
+ const metadata = holeMetadata[holeIndex]
309
+ if (hole.length < 3) return null
310
+
311
+ const isActive =
312
+ (hoveredHole?.nodeId === slabId && hoveredHole.holeIndex === holeIndex) ||
313
+ (editingHole?.nodeId === slabId && editingHole.holeIndex === holeIndex)
314
+
315
+ return (
316
+ <SlabHoleHighlight
317
+ active={isActive}
318
+ hitCenterY={hitCenterY}
319
+ hitHeight={hitHeight}
320
+ hole={hole}
321
+ holeIndex={holeIndex}
322
+ key={`${slabId}:${holeIndex}`}
323
+ metadata={metadata}
324
+ slabId={slabId}
325
+ surfaceY={surfaceY}
326
+ />
327
+ )
328
+ })}
329
+ </group>,
330
+ slabObject,
331
+ )
332
+ }
333
+
334
+ function SlabHoleHighlight({
335
+ active,
336
+ hitCenterY,
337
+ hitHeight,
338
+ hole,
339
+ holeIndex,
340
+ metadata,
341
+ slabId,
342
+ surfaceY,
343
+ }: {
344
+ active: boolean
345
+ hitCenterY: number
346
+ hitHeight: number
347
+ hole: HolePolygon
348
+ holeIndex: number
349
+ metadata: SurfaceHoleMetadata | undefined
350
+ slabId: string
351
+ surfaceY: number
352
+ }) {
353
+ const fillGeometry = useMemo(() => makeFillGeometry(hole, surfaceY), [hole, surfaceY])
354
+ const outlineGeometry = useMemo(() => makeOutlineGeometry(hole, surfaceY), [hole, surfaceY])
355
+ const hitGeometry = useMemo(
356
+ () => makeHitGeometry(hole, hitCenterY, hitHeight),
357
+ [hitCenterY, hitHeight, hole],
358
+ )
359
+
360
+ useEffect(() => () => fillGeometry.dispose(), [fillGeometry])
361
+ useEffect(() => () => outlineGeometry.dispose(), [outlineGeometry])
362
+ useEffect(() => () => hitGeometry.dispose(), [hitGeometry])
363
+ useEffect(() => () => clearHoveredHoleIfMatches(slabId, holeIndex), [holeIndex, slabId])
364
+
365
+ const handlePointerEnter = useCallback(
366
+ (event: ThreeEvent<PointerEvent>) => {
367
+ event.stopPropagation()
368
+ useEditor.getState().setHoveredHole({ nodeId: slabId, holeIndex })
369
+ if (document.body.style.cursor !== 'pointer') {
370
+ document.body.style.cursor = 'pointer'
371
+ }
372
+ },
373
+ [holeIndex, slabId],
374
+ )
375
+
376
+ const handlePointerLeave = useCallback(
377
+ (event: ThreeEvent<PointerEvent>) => {
378
+ event.stopPropagation()
379
+ clearHoveredHoleIfMatches(slabId, holeIndex)
380
+ resetPointerCursor()
381
+ },
382
+ [holeIndex, slabId],
383
+ )
384
+
385
+ const handlePointerDown = useCallback(
386
+ (event: ThreeEvent<PointerEvent>) => {
387
+ if (event.button !== 0) return
388
+ stopPointerPropagation(event)
389
+ suppressNodeClickUntilPointerUp()
390
+ useEditor.getState().setHoveredHole({ nodeId: slabId, holeIndex })
391
+
392
+ // Auto-managed cutouts (stair / elevator) jump to their owner so the
393
+ // user edits the source rather than the synced hole. Everything else —
394
+ // manual holes and holes that predate holeMetadata — opens the editor.
395
+ if (metadata?.source === 'stair' && metadata.stairId) {
396
+ useInteractionScope
397
+ .getState()
398
+ .endIf((sc) => sc.kind === 'reshaping' && sc.reshape === 'hole')
399
+ useEditor.getState().setHoveredHole(null)
400
+ resetPointerCursor()
401
+ selectOwnedNode(metadata.stairId, 'stair')
402
+ return
403
+ }
404
+ if (metadata?.source === 'elevator' && metadata.elevatorId) {
405
+ useInteractionScope
406
+ .getState()
407
+ .endIf((sc) => sc.kind === 'reshaping' && sc.reshape === 'hole')
408
+ useEditor.getState().setHoveredHole(null)
409
+ resetPointerCursor()
410
+ selectOwnedNode(metadata.elevatorId, 'elevator')
411
+ return
412
+ }
413
+
414
+ useInteractionScope.getState().begin(holeEditScope({ nodeId: slabId, holeIndex }))
415
+ useViewer.getState().setSelection({ selectedIds: [slabId as AnyNodeId] })
416
+ },
417
+ [holeIndex, metadata, slabId],
418
+ )
419
+
420
+ const handlePointerUp = useCallback((event: ThreeEvent<PointerEvent>) => {
421
+ releaseNodeClickSuppression()
422
+ stopPointerPropagation(event)
423
+ }, [])
424
+
425
+ return (
426
+ <>
427
+ <mesh
428
+ frustumCulled={false}
429
+ geometry={fillGeometry}
430
+ material={fillMaterial}
431
+ raycast={NO_RAYCAST}
432
+ renderOrder={1004}
433
+ />
434
+ <lineSegments
435
+ frustumCulled={false}
436
+ geometry={outlineGeometry}
437
+ material={outlineMaterial}
438
+ raycast={NO_RAYCAST}
439
+ renderOrder={1005}
440
+ />
441
+ {active && (
442
+ <>
443
+ <mesh
444
+ frustumCulled={false}
445
+ geometry={fillGeometry}
446
+ material={fillMaterial}
447
+ raycast={NO_RAYCAST}
448
+ renderOrder={1006}
449
+ />
450
+ <lineSegments
451
+ frustumCulled={false}
452
+ geometry={outlineGeometry}
453
+ material={outlineMaterial}
454
+ raycast={NO_RAYCAST}
455
+ renderOrder={1007}
456
+ />
457
+ </>
458
+ )}
459
+ <mesh
460
+ frustumCulled={false}
461
+ geometry={hitGeometry}
462
+ layers={EDITOR_LAYER}
463
+ material={hitMaterial}
464
+ onPointerDown={handlePointerDown}
465
+ onPointerEnter={handlePointerEnter}
466
+ onPointerLeave={handlePointerLeave}
467
+ onPointerUp={handlePointerUp}
468
+ renderOrder={1006}
469
+ />
470
+ </>
471
+ )
472
+ }
473
+
474
+ export default SlabHoleHighlights