@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
@@ -0,0 +1,183 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import {
3
+ buildMeasurementAngleArcPoints,
4
+ cubicMetersToVolumeUnit,
5
+ formatAreaLabel,
6
+ formatLinearMeasurement,
7
+ formatVolumeLabel,
8
+ getAreaUnitLabel,
9
+ getLinearUnitLabel,
10
+ getVolumeUnitLabel,
11
+ linearControlValueToMeters,
12
+ linearUnitToMeters,
13
+ MEASUREMENT_ACTIVE_COLOR,
14
+ MEASUREMENT_DANGLING_COLOR,
15
+ MEASUREMENT_FLOORPLAN_COLOR,
16
+ MEASUREMENT_PERSISTENT_COLOR,
17
+ measurementFloorplanPresentationColor,
18
+ measurementPresentationColor,
19
+ metersToLinearUnit,
20
+ squareMetersToAreaUnit,
21
+ } from './measurements'
22
+
23
+ describe('measurement presentation', () => {
24
+ test('uses black at rest, indigo while active, and red for dangling references', () => {
25
+ expect(measurementPresentationColor(false, false)).toBe(MEASUREMENT_PERSISTENT_COLOR)
26
+ expect(measurementPresentationColor(false, true)).toBe(MEASUREMENT_ACTIVE_COLOR)
27
+ expect(measurementPresentationColor(true, false)).toBe(MEASUREMENT_DANGLING_COLOR)
28
+ expect(measurementPresentationColor(true, true)).toBe(MEASUREMENT_DANGLING_COLOR)
29
+ })
30
+
31
+ test('uses an indigo analysis color for resting 2D measurements', () => {
32
+ expect(measurementFloorplanPresentationColor(false, false)).toBe(MEASUREMENT_FLOORPLAN_COLOR)
33
+ expect(measurementFloorplanPresentationColor(false, true)).toBe(MEASUREMENT_ACTIVE_COLOR)
34
+ expect(measurementFloorplanPresentationColor(true, false)).toBe(MEASUREMENT_DANGLING_COLOR)
35
+ })
36
+ })
37
+
38
+ describe('angle arc presentation', () => {
39
+ test('samples the smaller angle from the first ray to the second', () => {
40
+ const arc = buildMeasurementAngleArcPoints([1, 0, 0], [0, 0, 0], [0, 0, 1], {
41
+ radius: 0.25,
42
+ sampleCount: 8,
43
+ })
44
+
45
+ expect(arc).toHaveLength(9)
46
+ expect(arc[0]?.[0]).toBeCloseTo(0.25)
47
+ expect(arc[0]?.[2]).toBeCloseTo(0)
48
+ expect(arc.at(-1)?.[0]).toBeCloseTo(0)
49
+ expect(arc.at(-1)?.[2]).toBeCloseTo(0.25)
50
+ expect(arc[4]?.[0]).toBeGreaterThan(0)
51
+ expect(arc[4]?.[2]).toBeGreaterThan(0)
52
+ })
53
+
54
+ test('keeps a constant radius on an arbitrary 3D angle plane', () => {
55
+ const arc = buildMeasurementAngleArcPoints([1, 0, 0], [0, 0, 0], [0, 1, 0], {
56
+ radius: 0.3,
57
+ })
58
+
59
+ expect(arc.length).toBeGreaterThan(4)
60
+ for (const point of arc) expect(Math.hypot(...point)).toBeCloseTo(0.3)
61
+ expect(arc.at(-1)?.[1]).toBeCloseTo(0.3)
62
+ })
63
+
64
+ test('omits an arc when either ray is degenerate', () => {
65
+ expect(buildMeasurementAngleArcPoints([0, 0, 0], [0, 0, 0], [1, 0, 0])).toEqual([])
66
+ })
67
+ })
68
+
69
+ describe('linear measurements', () => {
70
+ test('formats metric measurements in meters', () => {
71
+ expect(formatLinearMeasurement(3, 'metric')).toBe('3m')
72
+ expect(formatLinearMeasurement(3.456, 'metric')).toBe('3.46m')
73
+ })
74
+
75
+ test('formats imperial measurements as feet and inches', () => {
76
+ expect(formatLinearMeasurement(3.048, 'imperial')).toBe(`10'0"`)
77
+ expect(formatLinearMeasurement(3.2004, 'imperial')).toBe(`10'6"`)
78
+ })
79
+
80
+ test('carries rounded 12 inches into the next foot', () => {
81
+ expect(formatLinearMeasurement(3.047, 'imperial')).toBe(`10'0"`)
82
+ })
83
+
84
+ test('returns a placeholder for non-finite measurements', () => {
85
+ expect(formatLinearMeasurement(NaN, 'imperial')).toBe('--')
86
+ expect(formatLinearMeasurement(Infinity, 'imperial')).toBe('--')
87
+ expect(formatLinearMeasurement(NaN, 'metric')).toBe('--')
88
+ })
89
+
90
+ test('formats zero measurements', () => {
91
+ expect(formatLinearMeasurement(0, 'imperial')).toBe(`0'0"`)
92
+ expect(formatLinearMeasurement(0, 'metric')).toBe('0m')
93
+ })
94
+
95
+ test('formats sub-foot imperial measurements', () => {
96
+ expect(formatLinearMeasurement(0.1524, 'imperial')).toBe(`0'6"`)
97
+ })
98
+
99
+ test('formats negative measurements with a sign', () => {
100
+ expect(formatLinearMeasurement(-0.1524, 'imperial')).toBe(`-0'6"`)
101
+ expect(formatLinearMeasurement(-0.1524, 'metric')).toBe('-0.15m')
102
+ })
103
+
104
+ test('converts between meters and the active linear unit', () => {
105
+ expect(metersToLinearUnit(0, 'imperial')).toBe(0)
106
+ expect(linearUnitToMeters(0, 'imperial')).toBe(0)
107
+
108
+ expect(metersToLinearUnit(1, 'metric')).toBe(1)
109
+ expect(linearUnitToMeters(1, 'metric')).toBe(1)
110
+
111
+ expect(metersToLinearUnit(0.3048, 'imperial')).toBeCloseTo(1)
112
+ expect(linearUnitToMeters(1, 'imperial')).toBeCloseTo(0.3048)
113
+ })
114
+
115
+ test('converts numeric control input back to meters for wall panel edits', () => {
116
+ expect(linearControlValueToMeters(10, 'imperial')).toBeCloseTo(3.048)
117
+ expect(linearControlValueToMeters(0.5, 'imperial')).toBeCloseTo(0.1524)
118
+ expect(linearControlValueToMeters(-1, 'imperial')).toBeCloseTo(-0.3048)
119
+ expect(linearControlValueToMeters(3.5, 'metric')).toBe(3.5)
120
+ })
121
+
122
+ test('clamps numeric control input after converting to meters', () => {
123
+ expect(linearControlValueToMeters(0.1, 'imperial', { minMeters: 0.1 })).toBe(0.1)
124
+ expect(linearControlValueToMeters(0.3, 'imperial', { minMeters: 0.1 })).toBe(0.1)
125
+ expect(linearControlValueToMeters(19.7, 'imperial', { maxMeters: 6 })).toBe(6)
126
+ expect(linearControlValueToMeters(0.2, 'metric', { minMeters: 0.1 })).toBe(0.2)
127
+ expect(linearControlValueToMeters(0.2, 'metric', { maxMeters: 0.15 })).toBe(0.15)
128
+ })
129
+
130
+ test('returns the display label for numeric controls', () => {
131
+ expect(getLinearUnitLabel('metric')).toBe('m')
132
+ expect(getLinearUnitLabel('imperial')).toBe('ft')
133
+ })
134
+ })
135
+
136
+ describe('area measurements', () => {
137
+ test('converts square meters to the active area unit', () => {
138
+ expect(squareMetersToAreaUnit(0, 'imperial')).toBe(0)
139
+ expect(squareMetersToAreaUnit(12.5, 'metric')).toBe(12.5)
140
+ expect(squareMetersToAreaUnit(1, 'imperial')).toBeCloseTo(10.7639)
141
+ })
142
+
143
+ test('returns the display label for area readouts', () => {
144
+ expect(getAreaUnitLabel('metric')).toBe('m²')
145
+ expect(getAreaUnitLabel('imperial')).toBe('ft²')
146
+ })
147
+
148
+ test('formats an area label with value and unit', () => {
149
+ expect(formatAreaLabel(12.34, 'metric')).toBe('12.3m²')
150
+ expect(formatAreaLabel(1, 'imperial')).toBe('10.8ft²')
151
+ expect(formatAreaLabel(12.34, 'metric', 2)).toBe('12.34m²')
152
+ })
153
+
154
+ test('returns a placeholder for non-finite areas', () => {
155
+ expect(formatAreaLabel(NaN, 'metric')).toBe('--')
156
+ expect(formatAreaLabel(Infinity, 'imperial')).toBe('--')
157
+ })
158
+ })
159
+
160
+ describe('volume measurements', () => {
161
+ test('converts cubic meters to the active volume unit', () => {
162
+ expect(cubicMetersToVolumeUnit(0, 'imperial')).toBe(0)
163
+ expect(cubicMetersToVolumeUnit(12.5, 'metric')).toBe(12.5)
164
+ expect(cubicMetersToVolumeUnit(1, 'imperial')).toBeCloseTo(35.3147)
165
+ })
166
+
167
+ test('returns the display label for volume readouts', () => {
168
+ expect(getVolumeUnitLabel('metric')).toBe('m³')
169
+ expect(getVolumeUnitLabel('imperial')).toBe('ft³')
170
+ })
171
+
172
+ test('formats a volume label with value and unit', () => {
173
+ expect(formatVolumeLabel(12.34, 'metric')).toBe('12.3m³')
174
+ expect(formatVolumeLabel(1, 'imperial')).toBe('35.3ft³')
175
+ expect(formatVolumeLabel(12.34, 'metric', 2)).toBe('12.34m³')
176
+ })
177
+
178
+ test('returns a placeholder for non-finite volumes', () => {
179
+ expect(formatVolumeLabel(NaN, 'metric')).toBe('--')
180
+ expect(formatVolumeLabel(Infinity, 'imperial')).toBe('--')
181
+ expect(formatVolumeLabel(Number.NEGATIVE_INFINITY, 'metric')).toBe('--')
182
+ })
183
+ })
@@ -0,0 +1,199 @@
1
+ import type { MeasurementPoint } from '@pascal-app/core'
2
+
3
+ export type LinearUnit = 'metric' | 'imperial'
4
+
5
+ export const MEASUREMENT_ACTIVE_COLOR = '#6366f1'
6
+ export const MEASUREMENT_DANGLING_COLOR = '#dc2626'
7
+ export const MEASUREMENT_FLOORPLAN_COLOR = '#4f46e5'
8
+ export const MEASUREMENT_PERSISTENT_COLOR = '#111827'
9
+
10
+ export function measurementPresentationColor(dangling: boolean, active: boolean): string {
11
+ if (dangling) return MEASUREMENT_DANGLING_COLOR
12
+ return active ? MEASUREMENT_ACTIVE_COLOR : MEASUREMENT_PERSISTENT_COLOR
13
+ }
14
+
15
+ export function measurementFloorplanPresentationColor(dangling: boolean, active: boolean): string {
16
+ if (dangling) return MEASUREMENT_DANGLING_COLOR
17
+ return active ? MEASUREMENT_ACTIVE_COLOR : MEASUREMENT_FLOORPLAN_COLOR
18
+ }
19
+
20
+ type MeasurementAngleArcOptions = {
21
+ radius?: number
22
+ sampleCount?: number
23
+ }
24
+
25
+ const ANGLE_ARC_EPSILON = 1e-9
26
+
27
+ const subtractPoint = (point: MeasurementPoint, origin: MeasurementPoint): MeasurementPoint => [
28
+ point[0] - origin[0],
29
+ point[1] - origin[1],
30
+ point[2] - origin[2],
31
+ ]
32
+
33
+ const pointLength = (point: MeasurementPoint): number => Math.hypot(...point)
34
+
35
+ const scalePoint = (point: MeasurementPoint, scale: number): MeasurementPoint => [
36
+ point[0] * scale,
37
+ point[1] * scale,
38
+ point[2] * scale,
39
+ ]
40
+
41
+ const crossPoint = (first: MeasurementPoint, second: MeasurementPoint): MeasurementPoint => [
42
+ first[1] * second[2] - first[2] * second[1],
43
+ first[2] * second[0] - first[0] * second[2],
44
+ first[0] * second[1] - first[1] * second[0],
45
+ ]
46
+
47
+ const dotPoint = (first: MeasurementPoint, second: MeasurementPoint): number =>
48
+ first[0] * second[0] + first[1] * second[1] + first[2] * second[2]
49
+
50
+ export function buildMeasurementAngleArcPoints(
51
+ start: MeasurementPoint,
52
+ vertex: MeasurementPoint,
53
+ end: MeasurementPoint,
54
+ options: MeasurementAngleArcOptions = {},
55
+ ): MeasurementPoint[] {
56
+ if (![...start, ...vertex, ...end].every(Number.isFinite)) return []
57
+
58
+ const startVector = subtractPoint(start, vertex)
59
+ const endVector = subtractPoint(end, vertex)
60
+ const startLength = pointLength(startVector)
61
+ const endLength = pointLength(endVector)
62
+ if (startLength <= ANGLE_ARC_EPSILON || endLength <= ANGLE_ARC_EPSILON) return []
63
+
64
+ const startDirection = scalePoint(startVector, 1 / startLength)
65
+ const endDirection = scalePoint(endVector, 1 / endLength)
66
+ const cosine = Math.max(-1, Math.min(1, dotPoint(startDirection, endDirection)))
67
+ const angle = Math.acos(cosine)
68
+ if (angle <= 1e-4) return []
69
+
70
+ let normal = crossPoint(startDirection, endDirection)
71
+ let normalLength = pointLength(normal)
72
+ if (normalLength <= ANGLE_ARC_EPSILON) {
73
+ const reference: MeasurementPoint = Math.abs(startDirection[1]) < 0.9 ? [0, 1, 0] : [1, 0, 0]
74
+ normal = crossPoint(startDirection, reference)
75
+ normalLength = pointLength(normal)
76
+ }
77
+ if (normalLength <= ANGLE_ARC_EPSILON) return []
78
+ normal = scalePoint(normal, 1 / normalLength)
79
+ const tangent = crossPoint(normal, startDirection)
80
+
81
+ const shortestSide = Math.min(startLength, endLength)
82
+ const preferredRadius = options.radius ?? Math.min(Math.max(shortestSide * 0.28, 0.08), 0.75)
83
+ const radius = Math.min(Math.max(preferredRadius, 0), shortestSide * 0.45)
84
+ if (radius <= ANGLE_ARC_EPSILON) return []
85
+
86
+ const sampleCount = Math.min(
87
+ 64,
88
+ Math.max(4, Math.round(options.sampleCount ?? Math.max(8, (angle / Math.PI) * 32))),
89
+ )
90
+ return Array.from({ length: sampleCount + 1 }, (_, index) => {
91
+ const sampleAngle = angle * (index / sampleCount)
92
+ const direction: MeasurementPoint = [
93
+ startDirection[0] * Math.cos(sampleAngle) + tangent[0] * Math.sin(sampleAngle),
94
+ startDirection[1] * Math.cos(sampleAngle) + tangent[1] * Math.sin(sampleAngle),
95
+ startDirection[2] * Math.cos(sampleAngle) + tangent[2] * Math.sin(sampleAngle),
96
+ ]
97
+ return [
98
+ vertex[0] + direction[0] * radius,
99
+ vertex[1] + direction[1] * radius,
100
+ vertex[2] + direction[2] * radius,
101
+ ]
102
+ })
103
+ }
104
+
105
+ const METERS_PER_FOOT = 0.3048
106
+ const FEET_PER_METER = 1 / METERS_PER_FOOT
107
+
108
+ type LinearControlValueOptions = {
109
+ minMeters?: number
110
+ maxMeters?: number
111
+ }
112
+
113
+ export function metersToLinearUnit(meters: number, unit: LinearUnit): number {
114
+ return unit === 'imperial' ? meters * FEET_PER_METER : meters
115
+ }
116
+
117
+ export function linearUnitToMeters(value: number, unit: LinearUnit): number {
118
+ return unit === 'imperial' ? value * METERS_PER_FOOT : value
119
+ }
120
+
121
+ export function linearControlValueToMeters(
122
+ value: number,
123
+ unit: LinearUnit,
124
+ options: LinearControlValueOptions = {},
125
+ ): number {
126
+ const meters = linearUnitToMeters(value, unit)
127
+ const minMeters = options.minMeters ?? Number.NEGATIVE_INFINITY
128
+ const maxMeters = options.maxMeters ?? Number.POSITIVE_INFINITY
129
+
130
+ return Math.min(Math.max(meters, minMeters), maxMeters)
131
+ }
132
+
133
+ export function getLinearUnitLabel(unit: LinearUnit): string {
134
+ return unit === 'imperial' ? 'ft' : 'm'
135
+ }
136
+
137
+ const SQUARE_FEET_PER_SQUARE_METER = FEET_PER_METER * FEET_PER_METER
138
+ const CUBIC_FEET_PER_CUBIC_METER = SQUARE_FEET_PER_SQUARE_METER * FEET_PER_METER
139
+
140
+ export function squareMetersToAreaUnit(squareMeters: number, unit: LinearUnit): number {
141
+ return unit === 'imperial' ? squareMeters * SQUARE_FEET_PER_SQUARE_METER : squareMeters
142
+ }
143
+
144
+ export function getAreaUnitLabel(unit: LinearUnit): string {
145
+ return unit === 'imperial' ? 'ft²' : 'm²'
146
+ }
147
+
148
+ export function formatAreaLabel(
149
+ squareMeters: number,
150
+ unit: LinearUnit,
151
+ fractionDigits = 1,
152
+ ): string {
153
+ if (!Number.isFinite(squareMeters)) return '--'
154
+
155
+ return `${squareMetersToAreaUnit(squareMeters, unit).toFixed(fractionDigits)}${getAreaUnitLabel(unit)}`
156
+ }
157
+
158
+ export function cubicMetersToVolumeUnit(cubicMeters: number, unit: LinearUnit): number {
159
+ return unit === 'imperial' ? cubicMeters * CUBIC_FEET_PER_CUBIC_METER : cubicMeters
160
+ }
161
+
162
+ export function getVolumeUnitLabel(unit: LinearUnit): string {
163
+ return unit === 'imperial' ? 'ft³' : 'm³'
164
+ }
165
+
166
+ export function formatVolumeLabel(
167
+ cubicMeters: number,
168
+ unit: LinearUnit,
169
+ fractionDigits = 1,
170
+ ): string {
171
+ if (!Number.isFinite(cubicMeters)) return '--'
172
+
173
+ return `${cubicMetersToVolumeUnit(cubicMeters, unit).toFixed(fractionDigits)}${getVolumeUnitLabel(unit)}`
174
+ }
175
+
176
+ export function formatLinearMeasurement(meters: number, unit: LinearUnit): string {
177
+ if (!Number.isFinite(meters)) return '--'
178
+
179
+ const absoluteMeters = Math.abs(meters)
180
+
181
+ if (unit === 'imperial') {
182
+ const feet = metersToLinearUnit(absoluteMeters, unit)
183
+ let wholeFeet = Math.floor(feet)
184
+ let inches = Math.round((feet - wholeFeet) * 12)
185
+ if (inches === 12) {
186
+ wholeFeet += 1
187
+ inches = 0
188
+ }
189
+
190
+ const sign = meters < 0 && (wholeFeet !== 0 || inches !== 0) ? '-' : ''
191
+
192
+ return `${sign}${wholeFeet}'${inches}"`
193
+ }
194
+
195
+ const roundedMeters = Number.parseFloat(absoluteMeters.toFixed(2))
196
+ const sign = meters < 0 && roundedMeters !== 0 ? '-' : ''
197
+
198
+ return `${sign}${roundedMeters}m`
199
+ }