@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
@@ -15,6 +15,13 @@ export type SegmentAngleReference = {
15
15
  orientation: 'directed' | 'axis'
16
16
  }
17
17
 
18
+ export type SegmentAngleArc = {
19
+ angle: number
20
+ startAngle: number
21
+ endAngle: number
22
+ midAngle: number
23
+ }
24
+
18
25
  const POINT_MATCH_TOLERANCE = 1e-5
19
26
  const SEGMENT_POINT_TOLERANCE = 0.15
20
27
  const CURVE_TANGENT_SAMPLE_SPACING = 0.08
@@ -92,6 +99,36 @@ export function getAngleBetweenVectors(first: PlanPoint, second: PlanPoint): num
92
99
  return Math.acos(cosine)
93
100
  }
94
101
 
102
+ function normalizeSignedAngle(angle: number) {
103
+ let nextAngle = angle
104
+
105
+ while (nextAngle <= -Math.PI) {
106
+ nextAngle += Math.PI * 2
107
+ }
108
+
109
+ while (nextAngle > Math.PI) {
110
+ nextAngle -= Math.PI * 2
111
+ }
112
+
113
+ return nextAngle
114
+ }
115
+
116
+ function getSignedAngleArc(vector: PlanPoint, referenceVector: PlanPoint): SegmentAngleArc | null {
117
+ const angle = getAngleBetweenVectors(vector, referenceVector)
118
+ if (angle === null) return null
119
+
120
+ const startAngle = Math.atan2(referenceVector[1], referenceVector[0])
121
+ const vectorAngle = Math.atan2(vector[1], vector[0])
122
+ const signedDelta = normalizeSignedAngle(vectorAngle - startAngle)
123
+
124
+ return {
125
+ angle: Math.abs(signedDelta),
126
+ startAngle,
127
+ endAngle: startAngle + signedDelta,
128
+ midAngle: startAngle + signedDelta / 2,
129
+ }
130
+ }
131
+
95
132
  export function getAngleToSegmentReference(
96
133
  vector: PlanPoint,
97
134
  reference: SegmentAngleReference,
@@ -111,6 +148,25 @@ export function getAngleToSegmentReference(
111
148
  return Math.min(angle, reverseAngle)
112
149
  }
113
150
 
151
+ export function getAngleArcToSegmentReference(
152
+ vector: PlanPoint,
153
+ reference: SegmentAngleReference,
154
+ ): SegmentAngleArc | null {
155
+ const directArc = getSignedAngleArc(vector, reference.vector)
156
+
157
+ if (!directArc || reference.orientation === 'directed') {
158
+ return directArc
159
+ }
160
+
161
+ const reverseArc = getSignedAngleArc(vector, [-reference.vector[0], -reference.vector[1]])
162
+
163
+ if (!reverseArc) {
164
+ return directArc
165
+ }
166
+
167
+ return reverseArc.angle < directArc.angle ? reverseArc : directArc
168
+ }
169
+
114
170
  export function getSegmentAngleReferenceAtPoint(
115
171
  point: PlanPoint,
116
172
  segment: SegmentAngleLike,
@@ -1,19 +1,341 @@
1
- import { type SiteNode, useScene } from '@pascal-app/core'
2
- import { useCallback } from 'react'
3
- import { PolygonEditor } from '../shared/polygon-editor'
4
-
5
- /**
6
- * Site boundary editor - allows editing site polygon when in site phase
7
- * Uses the generic PolygonEditor component
8
- */
1
+ import { emitter, type SiteNode, useLiveNodeOverrides, useScene } from '@pascal-app/core'
2
+ import { SCENE_LAYER } from '@pascal-app/viewer'
3
+ import { useGLTF } from '@react-three/drei/core/Gltf'
4
+ import { useFrame } from '@react-three/fiber'
5
+ import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'
6
+ import {
7
+ Color,
8
+ CylinderGeometry,
9
+ DoubleSide,
10
+ type Group,
11
+ type Mesh,
12
+ type Object3D,
13
+ RingGeometry,
14
+ } from 'three'
15
+ import { MeshBasicNodeMaterial } from 'three/webgpu'
16
+ import { EDITOR_LAYER } from '../../../lib/constants'
17
+ import { sfxEmitter } from '../../../lib/sfx-bus'
18
+ import { SITE_BOUNDARY_DRAG_LABEL } from '../../../lib/site-boundary'
19
+ import useEditor, { selectSiteFloorplanContext } from '../../../store/use-editor'
20
+ import useInteractionScope from '../../../store/use-interaction-scope'
21
+ import {
22
+ ARROW_COLOR,
23
+ ARROW_HOVER_COLOR,
24
+ NO_RAYCAST,
25
+ useInvisibleHitAreaMaterial,
26
+ } from '../../editor/node-arrow-handles'
27
+ import {
28
+ PolygonEditor,
29
+ type PolygonHandleHandlers,
30
+ type PolygonMidpointHandleRenderProps,
31
+ type PolygonVertexHandleRenderProps,
32
+ } from '../shared/polygon-editor'
33
+ import { SITE_FLAG_MODEL_URL } from './site-flag-model'
34
+
35
+ const SITE_FLAG_BASE_Y = 0
36
+ const SITE_FLAG_HIT_HEIGHT = 0.5
37
+ const SITE_FLAG_HIT_RADIUS = 0.24
38
+ const SITE_FLAG_HIT_Y = SITE_FLAG_BASE_Y + SITE_FLAG_HIT_HEIGHT / 2
39
+ const SITE_FLAG_MODEL_MIN_Y = -0.034798760316334665
40
+ const SITE_FLAG_SCALE = 0.5
41
+ const SITE_FLAG_ACTIVE_LIFT = 0.08
42
+ const SITE_FLAG_MODEL_Y = SITE_FLAG_BASE_Y - SITE_FLAG_MODEL_MIN_Y * SITE_FLAG_SCALE
43
+ const SITE_FLAG_HALO_INNER_RADIUS = 0.08
44
+ const SITE_FLAG_HALO_OUTER_RADIUS = SITE_FLAG_HIT_RADIUS * 0.95
45
+ const SITE_FLAG_HALO_Y = SITE_FLAG_BASE_Y + 0.002
46
+ const SITE_FLAG_HALO_COLOR = '#6366f1'
47
+
48
+ type TintableMaterial = {
49
+ color?: Color
50
+ depthTest: boolean
51
+ depthWrite: boolean
52
+ opacity: number
53
+ needsUpdate: boolean
54
+ transparent: boolean
55
+ }
56
+
57
+ function SiteFlagModel({
58
+ active,
59
+ hovered,
60
+ opacity = 1,
61
+ scale = SITE_FLAG_SCALE,
62
+ }: {
63
+ active: boolean
64
+ hovered: boolean
65
+ opacity?: number
66
+ scale?: number
67
+ }) {
68
+ const { scene } = useGLTF(SITE_FLAG_MODEL_URL, true)
69
+ const modelRef = useRef<Object3D>(null)
70
+ const flagScene = useMemo(() => {
71
+ const cloned = scene.clone(true)
72
+
73
+ cloned.traverse((object) => {
74
+ object.layers.set(SCENE_LAYER)
75
+
76
+ if ((object as Mesh).isMesh) {
77
+ const mesh = object as Mesh
78
+ mesh.castShadow = false
79
+ mesh.frustumCulled = false
80
+ mesh.raycast = NO_RAYCAST
81
+ mesh.receiveShadow = false
82
+ mesh.material = new MeshBasicNodeMaterial({
83
+ color: new Color(ARROW_COLOR),
84
+ depthTest: true,
85
+ depthWrite: opacity >= 0.999,
86
+ opacity,
87
+ transparent: opacity < 0.999,
88
+ })
89
+ }
90
+ })
91
+
92
+ return cloned
93
+ }, [opacity, scene])
94
+
95
+ useEffect(() => {
96
+ const color = new Color(active || hovered ? ARROW_HOVER_COLOR : ARROW_COLOR)
97
+
98
+ flagScene.traverse((object) => {
99
+ if ((object as Mesh).isMesh) {
100
+ const mesh = object as Mesh
101
+ const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material]
102
+
103
+ for (const material of materials as Array<MeshBasicNodeMaterial & TintableMaterial>) {
104
+ material.color?.copy(color)
105
+ material.depthTest = true
106
+ material.opacity = opacity
107
+ material.transparent = opacity < 0.999
108
+ material.depthWrite = opacity >= 0.999
109
+ material.needsUpdate = true
110
+ }
111
+ }
112
+ })
113
+ }, [active, flagScene, hovered, opacity])
114
+
115
+ useFrame((_, delta) => {
116
+ const model = modelRef.current
117
+ if (!model) return
118
+
119
+ const smoothing = 1 - Math.exp(-delta * 18)
120
+ const targetY = SITE_FLAG_MODEL_Y + (active ? SITE_FLAG_ACTIVE_LIFT : 0)
121
+ model.position.y += (targetY - model.position.y) * smoothing
122
+ })
123
+
124
+ useEffect(
125
+ () => () => {
126
+ flagScene.traverse((object) => {
127
+ if ((object as Mesh).isMesh) {
128
+ const materials = (object as Mesh).material
129
+ if (Array.isArray(materials)) {
130
+ materials.forEach((material) => {
131
+ material.dispose()
132
+ })
133
+ } else {
134
+ materials.dispose()
135
+ }
136
+ }
137
+ })
138
+ },
139
+ [flagScene],
140
+ )
141
+
142
+ return (
143
+ <primitive
144
+ object={flagScene}
145
+ position={[0, SITE_FLAG_MODEL_Y, 0]}
146
+ ref={modelRef}
147
+ scale={scale}
148
+ />
149
+ )
150
+ }
151
+
152
+ function SiteFlagHoverHalo({ visible }: { visible: boolean }) {
153
+ const haloRef = useRef<Mesh>(null)
154
+ const geometry = useMemo(() => {
155
+ const nextGeometry = new RingGeometry(
156
+ SITE_FLAG_HALO_INNER_RADIUS,
157
+ SITE_FLAG_HALO_OUTER_RADIUS,
158
+ 56,
159
+ )
160
+ nextGeometry.rotateX(-Math.PI / 2)
161
+ return nextGeometry
162
+ }, [])
163
+ const material = useMemo(
164
+ () =>
165
+ new MeshBasicNodeMaterial({
166
+ color: new Color(SITE_FLAG_HALO_COLOR),
167
+ depthTest: true,
168
+ depthWrite: false,
169
+ opacity: 0,
170
+ side: DoubleSide,
171
+ transparent: true,
172
+ }),
173
+ [],
174
+ )
175
+
176
+ useFrame((_, delta) => {
177
+ const mesh = haloRef.current
178
+ if (!mesh) return
179
+
180
+ const smoothing = 1 - Math.exp(-delta * 18)
181
+ const targetOpacity = visible ? 0.52 : 0
182
+ const targetScale = visible ? 1 : 0.08
183
+ const nextOpacity = material.opacity + (targetOpacity - material.opacity) * smoothing
184
+ const nextScale = mesh.scale.x + (targetScale - mesh.scale.x) * smoothing
185
+
186
+ material.opacity = nextOpacity
187
+ mesh.scale.setScalar(nextScale)
188
+ mesh.visible = visible || nextOpacity > 0.01
189
+ })
190
+
191
+ useEffect(
192
+ () => () => {
193
+ geometry.dispose()
194
+ material.dispose()
195
+ },
196
+ [geometry, material],
197
+ )
198
+
199
+ return (
200
+ <mesh
201
+ frustumCulled={false}
202
+ geometry={geometry}
203
+ layers={SCENE_LAYER}
204
+ material={material}
205
+ position={[0, SITE_FLAG_HALO_Y, 0]}
206
+ raycast={NO_RAYCAST}
207
+ ref={haloRef}
208
+ renderOrder={1009}
209
+ scale={0.08}
210
+ visible={false}
211
+ />
212
+ )
213
+ }
214
+
215
+ function SiteFlagFallback({
216
+ active,
217
+ hovered,
218
+ opacity = 1,
219
+ }: {
220
+ active: boolean
221
+ hovered: boolean
222
+ opacity?: number
223
+ }) {
224
+ const color = active || hovered ? ARROW_HOVER_COLOR : ARROW_COLOR
225
+
226
+ return (
227
+ <group position={[0, SITE_FLAG_BASE_Y + (active ? SITE_FLAG_ACTIVE_LIFT : 0), 0]}>
228
+ <mesh layers={SCENE_LAYER} position={[0, 0.16, 0]} raycast={NO_RAYCAST}>
229
+ <cylinderGeometry args={[0.11, 0.16, 0.32, 24]} />
230
+ <meshBasicMaterial
231
+ color={color}
232
+ depthTest
233
+ depthWrite={opacity >= 0.999}
234
+ opacity={opacity}
235
+ transparent={opacity < 0.999}
236
+ />
237
+ </mesh>
238
+ <mesh layers={SCENE_LAYER} position={[0, 0.34, 0]} raycast={NO_RAYCAST}>
239
+ <cylinderGeometry args={[0.04, 0.11, 0.14, 24]} />
240
+ <meshBasicMaterial
241
+ color={color}
242
+ depthTest
243
+ depthWrite={opacity >= 0.999}
244
+ opacity={opacity}
245
+ transparent={opacity < 0.999}
246
+ />
247
+ </mesh>
248
+ </group>
249
+ )
250
+ }
251
+
252
+ function SiteBoundaryFlagHandle({
253
+ active = false,
254
+ baseY,
255
+ handleProps,
256
+ hovered,
257
+ modelOpacity = 1,
258
+ modelScale = SITE_FLAG_SCALE,
259
+ modelVisible = true,
260
+ point,
261
+ }: {
262
+ active?: boolean
263
+ baseY: number
264
+ handleProps: PolygonHandleHandlers
265
+ hovered: boolean
266
+ modelOpacity?: number
267
+ modelScale?: number
268
+ modelVisible?: boolean
269
+ point: [number, number]
270
+ }) {
271
+ const hitGeometry = useMemo(
272
+ () =>
273
+ new CylinderGeometry(SITE_FLAG_HIT_RADIUS, SITE_FLAG_HIT_RADIUS, SITE_FLAG_HIT_HEIGHT, 24),
274
+ [],
275
+ )
276
+ const hitMaterial = useInvisibleHitAreaMaterial()
277
+
278
+ useEffect(() => () => hitGeometry.dispose(), [hitGeometry])
279
+
280
+ return (
281
+ <group position={[point[0], baseY, point[1]]}>
282
+ <SiteFlagHoverHalo visible={active || hovered} />
283
+ {(modelVisible || active || hovered) && (
284
+ <Suspense
285
+ fallback={<SiteFlagFallback active={active} hovered={hovered} opacity={modelOpacity} />}
286
+ >
287
+ <SiteFlagModel
288
+ active={active}
289
+ hovered={hovered}
290
+ opacity={modelOpacity}
291
+ scale={modelScale}
292
+ />
293
+ </Suspense>
294
+ )}
295
+ <mesh
296
+ frustumCulled={false}
297
+ geometry={hitGeometry}
298
+ layers={EDITOR_LAYER}
299
+ material={hitMaterial}
300
+ position={[0, SITE_FLAG_HIT_Y, 0]}
301
+ renderOrder={1012}
302
+ {...handleProps}
303
+ />
304
+ </group>
305
+ )
306
+ }
307
+
9
308
  export const SiteBoundaryEditor: React.FC = () => {
10
309
  const nodes = useScene((state) => state.nodes)
11
310
  const rootNodeIds = useScene((state) => state.rootNodeIds)
12
311
  const updateNode = useScene((state) => state.updateNode)
312
+ const phase = useEditor((state) => state.phase)
313
+ const mode = useEditor((state) => state.mode)
314
+ const [hoveredVertex, setHoveredVertex] = useState<number | null>(null)
315
+ const [hoveredMidpoint, setHoveredMidpoint] = useState<number | null>(null)
13
316
 
14
- // Get the site node (first root node)
15
317
  const siteNode = rootNodeIds[0] ? nodes[rootNodeIds[0]] : null
16
318
  const site = siteNode?.type === 'site' ? (siteNode as SiteNode) : null
319
+ const siteId = site?.id
320
+ const isSiteEditing = phase === 'site'
321
+ const showSiteHandles = isSiteEditing || mode === 'select'
322
+ const isSiteBoundaryHighlighted =
323
+ isSiteEditing || hoveredVertex !== null || hoveredMidpoint !== null
324
+ const [isDraggingSiteBoundary, setIsDraggingSiteBoundary] = useState(false)
325
+ const isDraggingSiteBoundaryRef = useRef(false)
326
+ const livePolygon = useLiveNodeOverrides(
327
+ useCallback(
328
+ (state) => {
329
+ if (!siteId) return null
330
+ return (state.overrides.get(siteId)?.polygon as SiteNode['polygon'] | undefined) ?? null
331
+ },
332
+ [siteId],
333
+ ),
334
+ )
335
+ const displayPolygon =
336
+ !(isDraggingSiteBoundary || isDraggingSiteBoundaryRef.current) && livePolygon?.points
337
+ ? livePolygon.points
338
+ : site?.polygon?.points
17
339
 
18
340
  const handlePolygonChange = useCallback(
19
341
  (newPolygon: Array<[number, number]>) => {
@@ -29,14 +351,196 @@ export const SiteBoundaryEditor: React.FC = () => {
29
351
  [site, updateNode],
30
352
  )
31
353
 
32
- if (!site?.polygon?.points || site.polygon.points.length < 3) return null
354
+ const handlePolygonPreview = useCallback(
355
+ (previewPolygon: ReadonlyArray<readonly [number, number]> | null) => {
356
+ if (!siteId) return
357
+
358
+ if (!previewPolygon) {
359
+ useLiveNodeOverrides.getState().clearFields(siteId, ['polygon'])
360
+ return
361
+ }
362
+
363
+ useLiveNodeOverrides.getState().set(siteId, {
364
+ polygon: {
365
+ type: 'polygon',
366
+ points: previewPolygon.map(([x, z]) => [x, z] as [number, number]),
367
+ },
368
+ })
369
+ },
370
+ [siteId],
371
+ )
372
+
373
+ const handleSiteBoundaryDragChange = useCallback(
374
+ (isDragging: boolean) => {
375
+ isDraggingSiteBoundaryRef.current = isDragging
376
+ setIsDraggingSiteBoundary(isDragging)
377
+
378
+ if (!siteId) return
379
+
380
+ if (isDragging) {
381
+ useInteractionScope
382
+ .getState()
383
+ .begin({ kind: 'handle-drag', nodeId: siteId, handle: SITE_BOUNDARY_DRAG_LABEL })
384
+ } else {
385
+ const scope = useInteractionScope.getState().scope
386
+ const activeHandleDrag =
387
+ scope.kind === 'handle-drag' ? { nodeId: scope.nodeId, label: scope.handle } : null
388
+ if (
389
+ activeHandleDrag?.nodeId === siteId &&
390
+ activeHandleDrag.label === SITE_BOUNDARY_DRAG_LABEL
391
+ ) {
392
+ useInteractionScope.getState().endIf((sc) => sc.kind === 'handle-drag')
393
+ }
394
+ }
395
+
396
+ if (!isDragging) {
397
+ useLiveNodeOverrides.getState().clearFields(siteId, ['polygon'])
398
+ }
399
+ },
400
+ [siteId],
401
+ )
402
+
403
+ useEffect(
404
+ () => () => {
405
+ if (!siteId) return
406
+ const scope = useInteractionScope.getState().scope
407
+ const activeHandleDrag =
408
+ scope.kind === 'handle-drag' ? { nodeId: scope.nodeId, label: scope.handle } : null
409
+ if (
410
+ activeHandleDrag?.nodeId === siteId &&
411
+ activeHandleDrag.label === SITE_BOUNDARY_DRAG_LABEL
412
+ ) {
413
+ useInteractionScope.getState().endIf((sc) => sc.kind === 'handle-drag')
414
+ }
415
+ useLiveNodeOverrides.getState().clearFields(siteId, ['polygon'])
416
+ isDraggingSiteBoundaryRef.current = false
417
+ },
418
+ [siteId],
419
+ )
420
+
421
+ useEffect(() => {
422
+ if (isSiteEditing && siteId) {
423
+ selectSiteFloorplanContext()
424
+ }
425
+ }, [isSiteEditing, siteId])
426
+
427
+ // The flag models render on SCENE_LAYER (scene-depth occlusion), so unlike
428
+ // EDITOR_LAYER affordances the thumbnail camera can't filter them — hide
429
+ // them around captures (preset/snapshot/auto-save thumbnails), same as
430
+ // `handle-arrow.tsx`.
431
+ const handlesRootRef = useRef<Group>(null)
432
+ useEffect(() => {
433
+ const hideForCapture = () => {
434
+ if (handlesRootRef.current) handlesRootRef.current.visible = false
435
+ }
436
+ const restoreAfterCapture = () => {
437
+ if (handlesRootRef.current) handlesRootRef.current.visible = true
438
+ }
439
+ emitter.on('thumbnail:before-capture', hideForCapture)
440
+ emitter.on('thumbnail:after-capture', restoreAfterCapture)
441
+ return () => {
442
+ emitter.off('thumbnail:before-capture', hideForCapture)
443
+ emitter.off('thumbnail:after-capture', restoreAfterCapture)
444
+ }
445
+ }, [])
446
+
447
+ const activateSiteEditing = useCallback(() => {
448
+ isDraggingSiteBoundaryRef.current = true
449
+ setIsDraggingSiteBoundary(true)
450
+ if (useEditor.getState().phase !== 'site') {
451
+ useEditor.setState({ catalogCategory: null, mode: 'select', phase: 'site', tool: null })
452
+ }
453
+ selectSiteFloorplanContext()
454
+ }, [])
455
+
456
+ const exitSiteEditing = useCallback(() => {
457
+ const editor = useEditor.getState()
458
+ editor.setPhase('structure')
459
+ editor.setStructureLayer('elements')
460
+ editor.setMode('select')
461
+ }, [])
462
+
463
+ useEffect(() => {
464
+ if (!isSiteEditing) return
465
+
466
+ const onGridClick = () => {
467
+ if (useEditor.getState().phase !== 'site') return
468
+ exitSiteEditing()
469
+ }
470
+
471
+ emitter.on('grid:click', onGridClick)
472
+ return () => {
473
+ emitter.off('grid:click', onGridClick)
474
+ }
475
+ }, [exitSiteEditing, isSiteEditing])
476
+
477
+ const renderSiteFlagVertex = useCallback(
478
+ ({
479
+ handleProps,
480
+ height,
481
+ isDragging,
482
+ isHovered,
483
+ point,
484
+ position,
485
+ }: PolygonVertexHandleRenderProps) => {
486
+ const baseY = position[1] - height / 2
487
+
488
+ return (
489
+ <SiteBoundaryFlagHandle
490
+ active={isDragging}
491
+ baseY={baseY}
492
+ handleProps={handleProps}
493
+ hovered={isHovered}
494
+ point={point}
495
+ />
496
+ )
497
+ },
498
+ [],
499
+ )
500
+ const renderSiteFlagMidpoint = useCallback(
501
+ ({ handleProps, height, isHovered, point, position }: PolygonMidpointHandleRenderProps) => {
502
+ const baseY = position[1] - height / 2
503
+
504
+ return (
505
+ <SiteBoundaryFlagHandle
506
+ baseY={baseY}
507
+ handleProps={handleProps}
508
+ hovered={isHovered}
509
+ modelOpacity={0.48}
510
+ modelScale={SITE_FLAG_SCALE * 0.92}
511
+ modelVisible={isHovered}
512
+ point={point}
513
+ />
514
+ )
515
+ },
516
+ [],
517
+ )
518
+
519
+ if (!displayPolygon || displayPolygon.length < 3) return null
520
+ if (!showSiteHandles) return null
33
521
 
34
522
  return (
35
- <PolygonEditor
36
- color="#10b981"
37
- minVertices={3}
38
- onPolygonChange={handlePolygonChange}
39
- polygon={site.polygon.points}
40
- />
523
+ <group ref={handlesRootRef}>
524
+ <PolygonEditor
525
+ color={isSiteBoundaryHighlighted ? ARROW_COLOR : '#10b981'}
526
+ minVertices={3}
527
+ onBeforeVertexDrag={activateSiteEditing}
528
+ onDragCommit={() => {
529
+ sfxEmitter.emit('sfx:item-place')
530
+ exitSiteEditing()
531
+ }}
532
+ onDragStart={() => sfxEmitter.emit('sfx:item-pick')}
533
+ onDragStateChange={handleSiteBoundaryDragChange}
534
+ onMidpointHoverChange={setHoveredMidpoint}
535
+ onPolygonChange={handlePolygonChange}
536
+ onPolygonPreview={handlePolygonPreview}
537
+ onVertexHoverChange={setHoveredVertex}
538
+ polygon={displayPolygon}
539
+ renderMidpointHandle={renderSiteFlagMidpoint}
540
+ renderVertexHandle={renderSiteFlagVertex}
541
+ showBorderLine={isSiteBoundaryHighlighted}
542
+ showMidpointHandles={showSiteHandles}
543
+ />
544
+ </group>
41
545
  )
42
546
  }
@@ -0,0 +1,18 @@
1
+ const SITE_FLAG_MODEL_BASE64 = [
2
+ 'Z2xURgIAAADgAwAAAAMAAEpTT057ImFzc2V0Ijp7ImdlbmVyYXRvciI6ImdsVEYtVHJhbnNmb3JtIHY0LjMuMCIsInZlcnNpb24i',
3
+ 'OiIyLjAifSwiYWNjZXNzb3JzIjpbeyJ0eXBlIjoiU0NBTEFSIiwiY29tcG9uZW50VHlwZSI6NTEyMywiY291bnQiOjE2OH0seyJ0',
4
+ 'eXBlIjoiVkVDMyIsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjozMCwibWF4IjpbMC4wMDY3MzM3MTM2NzE1NjUwNTYsMC4w',
5
+ 'MDYyNDAyODE3NDIwNjYxNDUsMC4wMDAzNDc5ODc2MDMxNjMzNDY2NV0sIm1pbiI6Wy0wLjAwMDMwMTM2NjExODk0NzA0NCwtMC4w',
6
+ 'MDAzNDc5ODc2MDMxNjMzNDY2NSwtMC4wMDAzNDc5ODc4NjUwOTc4MjA3Nl19XSwiYnVmZmVyVmlld3MiOlt7ImJ1ZmZlciI6MCwi',
7
+ 'Ynl0ZU9mZnNldCI6MCwiYnl0ZUxlbmd0aCI6MTkzfV0sImJ1ZmZlcnMiOlt7ImJ5dGVMZW5ndGgiOjE5M31dLCJtZXNoZXMiOlt7',
8
+ 'InByaW1pdGl2ZXMiOlt7ImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjF9LCJtb2RlIjo0LCJpbmRpY2VzIjowLCJleHRlbnNpb25z',
9
+ 'Ijp7IktIUl9kcmFjb19tZXNoX2NvbXByZXNzaW9uIjp7ImJ1ZmZlclZpZXciOjAsImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjB9',
10
+ 'fX19XX1dLCJub2RlcyI6W3sic2NhbGUiOlsxMDAsMTAwLDEwMF0sIm1lc2giOjB9XSwic2NlbmVzIjpbeyJub2RlcyI6WzBdfV0s',
11
+ 'InNjZW5lIjowLCJleHRlbnNpb25zVXNlZCI6WyJLSFJfZHJhY29fbWVzaF9jb21wcmVzc2lvbiJdLCJleHRlbnNpb25zUmVxdWly',
12
+ 'ZWQiOlsiS0hSX2RyYWNvX21lc2hfY29tcHJlc3Npb24iXX0gICDEAAAAQklOAERSQUNPAgIBAQAAAB44ADcDAA7f6qkv1VVRZfUo',
13
+ '1qKKAgEBEAH/AAABAAkDAAACAQEBAA0DEREXaQaJCEUEiQg1EwsuVMV2v4nwGqm5Tl7xigAAAFMDACwAZQAcaFlXAMoAAJS7Umpl',
14
+ 'CAIAyqteasnAqAAAAEt9IJaxqKVWGb4CAIBXSq0MAKDUyQAA/CcAQABAmVEBAADA1KdWGV4BKPOK/1QGgAWQDAAAAAD/DwAArQCe',
15
+ 'uRtytrkkcra5h4bmOwwAAAA=',
16
+ ].join('')
17
+
18
+ export const SITE_FLAG_MODEL_URL = `data:model/gltf-binary;base64,${SITE_FLAG_MODEL_BASE64}`
@@ -6,6 +6,7 @@ export const DEFAULT_STAIR_STEP_COUNT = 10
6
6
  export const DEFAULT_STAIR_ATTACHMENT_SIDE = 'front' as const
7
7
  export const DEFAULT_STAIR_FILL_TO_FLOOR = true
8
8
  export const DEFAULT_STAIR_THICKNESS = 0.25
9
+ export const DEFAULT_STAIR_OPENING_OFFSET = 0
9
10
  export const DEFAULT_CURVED_STAIR_INNER_RADIUS = 0.9
10
11
  export const DEFAULT_CURVED_STAIR_SWEEP_ANGLE = Math.PI / 2
11
12
  export const DEFAULT_SPIRAL_STAIR_SWEEP_ANGLE = (400 * Math.PI) / 180
@@ -13,5 +14,5 @@ export const DEFAULT_SPIRAL_TOP_LANDING_MODE = 'none' as const
13
14
  export const DEFAULT_SPIRAL_TOP_LANDING_DEPTH = 0.9
14
15
  export const DEFAULT_SPIRAL_SHOW_CENTER_COLUMN = true
15
16
  export const DEFAULT_SPIRAL_SHOW_STEP_SUPPORTS = true
16
- export const DEFAULT_STAIR_RAILING_MODE = 'right' as const
17
+ export const DEFAULT_STAIR_RAILING_MODE = 'both' as const
17
18
  export const DEFAULT_STAIR_RAILING_HEIGHT = 0.92