@pascal-app/editor 1.0.6 → 1.1.0

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 (382) hide show
  1. package/package.json +18 -15
  2. package/src/components/editor/action-menu-button.tsx +4 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
  4. package/src/components/editor/bake-thumbnail.tsx +3 -2
  5. package/src/components/editor/custom-camera-controls.tsx +110 -42
  6. package/src/components/editor/duplicate-node.ts +2 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +77 -68
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +31 -11
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +2 -2
  15. package/src/components/editor/floorplan-panel.test.ts +220 -0
  16. package/src/components/editor/floorplan-panel.tsx +458 -140
  17. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  18. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  19. package/src/components/editor/footprint-height-handle.tsx +2 -2
  20. package/src/components/editor/grid.tsx +24 -15
  21. package/src/components/editor/group-actions.ts +19 -1
  22. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  23. package/src/components/editor/group-move-3d.ts +27 -2
  24. package/src/components/editor/group-selection-box-3d.tsx +4 -1
  25. package/src/components/editor/group-transform-shared.ts +23 -1
  26. package/src/components/editor/index.tsx +150 -20
  27. package/src/components/editor/inspector-extension-actions.tsx +36 -0
  28. package/src/components/editor/localized-html.tsx +22 -0
  29. package/src/components/editor/measurement-pill.tsx +1 -1
  30. package/src/components/editor/node-action-menu.tsx +46 -12
  31. package/src/components/editor/node-arrow-handles.tsx +2 -3
  32. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  33. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  34. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  35. package/src/components/editor/quick-measurement-card.tsx +1 -1
  36. package/src/components/editor/registry-action-contributions.test.tsx +67 -6
  37. package/src/components/editor/registry-action-contributions.tsx +115 -15
  38. package/src/components/editor/riser-diagram-panel.tsx +59 -9
  39. package/src/components/editor/room-controls.tsx +4 -3
  40. package/src/components/editor/room-handles-2d.tsx +158 -7
  41. package/src/components/editor/room-handles.tsx +244 -54
  42. package/src/components/editor/room-highlight.tsx +2 -0
  43. package/src/components/editor/room-labels.tsx +33 -17
  44. package/src/components/editor/selection-manager.tsx +15 -102
  45. package/src/components/editor/separator-end-grip.tsx +74 -0
  46. package/src/components/editor/site-edge-labels.tsx +7 -3
  47. package/src/components/editor/thumbnail-generator.test.ts +15 -6
  48. package/src/components/editor/thumbnail-generator.tsx +26 -1
  49. package/src/components/editor/use-floorplan-background-placement.ts +55 -52
  50. package/src/components/editor/views/view-bar.tsx +111 -26
  51. package/src/components/editor/wall-measurement-label.tsx +31 -15
  52. package/src/components/editor/wall-move-side-handles.tsx +15 -7
  53. package/src/components/editor/wall-pull-grip.tsx +193 -0
  54. package/src/components/editor/wall-region-handles.tsx +2 -2
  55. package/src/components/editor/wall-snap-beacon-layer.tsx +134 -33
  56. package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
  57. package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
  58. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
  59. package/src/components/editor-2d/floorplan-group-move.tsx +32 -4
  60. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
  61. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +33 -24
  62. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +12 -5
  63. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  64. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +29 -5
  65. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
  66. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
  67. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  68. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
  69. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  70. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  71. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +34 -1
  72. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +211 -39
  73. package/src/components/procedural-items/inspector.tsx +59 -12
  74. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
  75. package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
  76. package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
  77. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  78. package/src/components/systems/roof/roof-edit-system.tsx +30 -19
  79. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  80. package/src/components/tools/distribution/distribution-run-tool.tsx +200 -58
  81. package/src/components/tools/item/move-tool.tsx +1 -6
  82. package/src/components/tools/item/placement-history.test.tsx +1 -1
  83. package/src/components/tools/item/placement-strategies.test.ts +64 -1
  84. package/src/components/tools/item/placement-strategies.ts +5 -9
  85. package/src/components/tools/item/use-placement-coordinator.tsx +31 -35
  86. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
  87. package/src/components/tools/registry/item-surface-move.test.ts +11 -0
  88. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  89. package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
  90. package/src/components/tools/select/select-candidates.test.ts +45 -0
  91. package/src/components/tools/select/select-candidates.ts +13 -2
  92. package/src/components/tools/shared/cursor-sphere.tsx +10 -3
  93. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  94. package/src/components/tools/shared/placement-box.tsx +2 -6
  95. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  96. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  97. package/src/components/tools/shared/segment-angle.ts +0 -4
  98. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  99. package/src/components/tools/tool-manager.tsx +1 -24
  100. package/src/components/tools/wall/wall-drafting.test.ts +299 -23
  101. package/src/components/tools/wall/wall-drafting.ts +132 -24
  102. package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
  103. package/src/components/tools/wall/wall-pull-snap.ts +563 -0
  104. package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
  105. package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
  106. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  107. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  108. package/src/components/tools/zone/zone-tool.tsx +3 -3
  109. package/src/components/ui/action-menu/action-button.tsx +2 -0
  110. package/src/components/ui/action-menu/camera-actions.tsx +8 -7
  111. package/src/components/ui/action-menu/control-modes.tsx +9 -5
  112. package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
  113. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
  114. package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
  115. package/src/components/ui/action-menu/index.tsx +1 -0
  116. package/src/components/ui/action-menu/measurement-control.tsx +35 -29
  117. package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
  118. package/src/components/ui/action-menu/structure-tools.tsx +25 -23
  119. package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
  120. package/src/components/ui/action-menu/view-toggles.tsx +256 -51
  121. package/src/components/ui/build-panel/build-panel.tsx +163 -25
  122. package/src/components/ui/command-palette/editor-commands.tsx +38 -11
  123. package/src/components/ui/controls/finish-swatch.tsx +45 -19
  124. package/src/components/ui/controls/material-picker.tsx +24 -17
  125. package/src/components/ui/controls/metric-control.test.ts +52 -0
  126. package/src/components/ui/controls/metric-control.tsx +62 -24
  127. package/src/components/ui/controls/segmented-control.tsx +5 -2
  128. package/src/components/ui/controls/slider-control.test.tsx +23 -0
  129. package/src/components/ui/controls/slider-control.tsx +47 -19
  130. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  131. package/src/components/ui/floating-level-selector.tsx +100 -48
  132. package/src/components/ui/helpers/building-helper.tsx +5 -3
  133. package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
  134. package/src/components/ui/helpers/helper-manager.test.tsx +106 -3
  135. package/src/components/ui/helpers/helper-manager.tsx +136 -57
  136. package/src/components/ui/helpers/item-helper.tsx +6 -4
  137. package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
  138. package/src/components/ui/level-duplicate-dialog.tsx +32 -23
  139. package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
  140. package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
  141. package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
  142. package/src/components/ui/panels/floor-foundation-panel.tsx +114 -39
  143. package/src/components/ui/panels/floor-opening-panel.tsx +74 -26
  144. package/src/components/ui/panels/inspector-extensions.tsx +66 -0
  145. package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
  146. package/src/components/ui/panels/mobile-selection-bar.tsx +34 -28
  147. package/src/components/ui/panels/multi-field-value.test.ts +27 -0
  148. package/src/components/ui/panels/multi-field-value.ts +20 -1
  149. package/src/components/ui/panels/multi-height-mode.tsx +11 -10
  150. package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
  151. package/src/components/ui/panels/multi-selection-panel.tsx +76 -36
  152. package/src/components/ui/panels/node-display.ts +35 -24
  153. package/src/components/ui/panels/panel-manager.tsx +9 -5
  154. package/src/components/ui/panels/panel-wrapper.tsx +63 -127
  155. package/src/components/ui/panels/parametric-field-control.tsx +13 -10
  156. package/src/components/ui/panels/parametric-inspector.tsx +18 -12
  157. package/src/components/ui/panels/reference-panel.tsx +51 -35
  158. package/src/components/ui/panels/room-construction-rows.tsx +465 -66
  159. package/src/components/ui/panels/room-name-options.test.ts +36 -3
  160. package/src/components/ui/panels/room-name-options.ts +110 -11
  161. package/src/components/ui/panels/room-openings-field.tsx +22 -13
  162. package/src/components/ui/panels/room-panel.tsx +207 -100
  163. package/src/components/ui/panels/shape-choice.tsx +7 -5
  164. package/src/components/ui/panels/wall-region-list.tsx +7 -7
  165. package/src/components/ui/primitives/shortcut-token.tsx +10 -6
  166. package/src/components/ui/right-stack.test.ts +13 -6
  167. package/src/components/ui/right-stack.tsx +21 -10
  168. package/src/components/ui/sidebar/icon-rail.tsx +11 -9
  169. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
  170. package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
  171. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
  172. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
  173. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
  174. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
  175. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -3
  176. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
  177. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
  178. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
  179. package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
  180. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
  181. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
  182. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
  183. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
  184. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
  185. package/src/components/ui/sidebar/panels/site-panel/index.tsx +118 -72
  186. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
  187. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
  188. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
  189. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
  190. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
  191. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
  192. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -4
  193. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
  194. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
  195. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
  196. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
  197. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
  198. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
  199. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
  200. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
  201. package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
  202. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +19 -9
  203. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
  204. package/src/components/ui/sidebar/tab-bar.tsx +4 -1
  205. package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
  206. package/src/components/ui/snap-target-badge.tsx +7 -6
  207. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
  208. package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
  209. package/src/components/viewer/floorplan-compass-button.tsx +12 -3
  210. package/src/components/viewer/floorplan-preview.tsx +27 -19
  211. package/src/components/viewer/render-settings-menu.tsx +294 -0
  212. package/src/components/viewer/sky-control.tsx +710 -0
  213. package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
  214. package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
  215. package/src/components/viewer/viewer-controls-bar.tsx +278 -260
  216. package/src/components/viewer/viewer-hover-label.tsx +12 -4
  217. package/src/components/viewer/viewer-rooms.tsx +62 -5
  218. package/src/components/viewer/viewer-scene-header.tsx +28 -17
  219. package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
  220. package/src/components/viewer-overlay.tsx +8 -0
  221. package/src/components/walkthrough-hud.tsx +75 -2
  222. package/src/hooks/use-keyboard.test.ts +85 -4
  223. package/src/hooks/use-keyboard.ts +65 -7
  224. package/src/hooks/use-selected-room.ts +12 -1
  225. package/src/i18n/floor-footprint-name.ts +30 -0
  226. package/src/i18n/level-name.ts +39 -0
  227. package/src/i18n/locales/en/messages.po +15849 -0
  228. package/src/i18n/locales/en/messages.ts +1 -0
  229. package/src/i18n/locales/en-XA/messages.ts +1 -0
  230. package/src/i18n/locales/es/messages.po +39331 -0
  231. package/src/i18n/locales/es/messages.ts +1 -0
  232. package/src/i18n/locales/fr/messages.po +39331 -0
  233. package/src/i18n/locales/fr/messages.ts +1 -0
  234. package/src/i18n/locales/ja/messages.po +39331 -0
  235. package/src/i18n/locales/ja/messages.ts +1 -0
  236. package/src/i18n/locales/zh-CN/messages.po +39331 -0
  237. package/src/i18n/locales/zh-CN/messages.ts +1 -0
  238. package/src/i18n/locales.test.ts +63 -0
  239. package/src/i18n/locales.ts +32 -0
  240. package/src/i18n/message-slot.tsx +25 -0
  241. package/src/i18n/node-messages.ts +169 -0
  242. package/src/i18n/pseudo-locale.ts +17 -0
  243. package/src/i18n/published-entry.test.ts +17 -0
  244. package/src/i18n/scene-theme-name.ts +16 -0
  245. package/src/index.tsx +224 -45
  246. package/src/lib/add-storey.test.ts +3 -3
  247. package/src/lib/contextual-help.test.ts +20 -1
  248. package/src/lib/contextual-help.ts +101 -19
  249. package/src/lib/continuation.ts +38 -11
  250. package/src/lib/elevation-guides.ts +21 -6
  251. package/src/lib/elevator-support.ts +1 -72
  252. package/src/lib/floor-footprints.test.ts +3 -2
  253. package/src/lib/floor-footprints.ts +1 -9
  254. package/src/lib/floor-foundation-support.test.ts +15 -6
  255. package/src/lib/floor-opening-draft.ts +1 -1
  256. package/src/lib/floor-region-session.ts +1 -1
  257. package/src/lib/floorplan/dimension-drive.test.ts +36 -2
  258. package/src/lib/floorplan/dimension-drive.ts +68 -3
  259. package/src/lib/floorplan/floorplan-export.test.ts +16 -5
  260. package/src/lib/floorplan/floorplan-export.tsx +13 -16
  261. package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
  262. package/src/lib/floorplan/floorplan-extension.ts +37 -18
  263. package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
  264. package/src/lib/floorplan/floorplan-mode.ts +14 -12
  265. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
  266. package/src/lib/floorplan/floorplan-readonly.ts +3 -3
  267. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  268. package/src/lib/floorplan/schedules.ts +10 -6
  269. package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
  270. package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
  271. package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
  272. package/src/lib/geometry-script/author.test.ts +114 -0
  273. package/src/lib/geometry-script/author.ts +55 -23
  274. package/src/lib/geometry-script/client.ts +3 -0
  275. package/src/lib/geometry-script/finish-scripts.ts +7 -7
  276. package/src/lib/gesture-lifecycle.ts +3 -1
  277. package/src/lib/glb-export.test.ts +327 -0
  278. package/src/lib/glb-export.ts +70 -2
  279. package/src/lib/grid-frame.test.ts +56 -3
  280. package/src/lib/grid-frame.ts +28 -1
  281. package/src/lib/history.test.ts +2 -1
  282. package/src/lib/hud-title.ts +48 -24
  283. package/src/lib/ifc-export.test.ts +41 -1
  284. package/src/lib/inspector-card-mode.test.ts +3 -31
  285. package/src/lib/inspector-card-mode.ts +5 -10
  286. package/src/lib/inspector-expanded.test.ts +13 -0
  287. package/src/lib/inspector-expanded.ts +13 -1
  288. package/src/lib/interaction/scope.ts +1 -0
  289. package/src/lib/keyboard-pan.test.ts +7 -0
  290. package/src/lib/keyboard-pan.ts +9 -2
  291. package/src/lib/layer-thickness.test.ts +22 -0
  292. package/src/lib/layer-thickness.ts +79 -0
  293. package/src/lib/level-selection.test.ts +18 -2
  294. package/src/lib/level-selection.ts +31 -8
  295. package/src/lib/linear-display.ts +1 -1
  296. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  297. package/src/lib/lot/drop-in.test.ts +59 -1
  298. package/src/lib/lot/drop-in.ts +42 -50
  299. package/src/lib/material-swatch-style.ts +24 -0
  300. package/src/lib/measurement-label.test.ts +1 -1
  301. package/src/lib/measurement-parser.ts +7 -3
  302. package/src/lib/measurements.test.ts +24 -6
  303. package/src/lib/measurements.ts +1 -203
  304. package/src/lib/mezzanine-selection.test.ts +3 -1
  305. package/src/lib/node-action-movement.test.tsx +115 -0
  306. package/src/lib/node-action-movement.ts +2 -0
  307. package/src/lib/orbit-near-plane.test.ts +12 -0
  308. package/src/lib/orbit-near-plane.ts +13 -0
  309. package/src/lib/paint-regions.test.ts +8 -5
  310. package/src/lib/paint-scope.test.ts +53 -0
  311. package/src/lib/paint-scope.ts +18 -4
  312. package/src/lib/panel-rows.ts +17 -8
  313. package/src/lib/placement-metadata.ts +4 -0
  314. package/src/lib/point-ask/describe-target.ts +6 -8
  315. package/src/lib/point-ask/region.ts +1 -1
  316. package/src/lib/print-shell-compiler.ts +12 -1
  317. package/src/lib/print-wall-solids.test.ts +11 -0
  318. package/src/lib/print-wall-solids.ts +9 -0
  319. package/src/lib/reference-scale.ts +83 -0
  320. package/src/lib/room-assembly-overlay.ts +1 -1
  321. package/src/lib/room-built-on.test.ts +28 -17
  322. package/src/lib/room-built-on.ts +21 -17
  323. package/src/lib/room-construction-commands.ts +3 -2
  324. package/src/lib/room-construction.test.ts +28 -1
  325. package/src/lib/room-construction.ts +14 -0
  326. package/src/lib/room-controls-phase6.test.ts +141 -1
  327. package/src/lib/room-handle-drag.ts +554 -58
  328. package/src/lib/room-name-catalog.test.ts +8 -0
  329. package/src/lib/room-name-catalog.ts +8 -1
  330. package/src/lib/room-name-messages.test.ts +132 -0
  331. package/src/lib/room-name-messages.ts +147 -0
  332. package/src/lib/room-name-storage.test.ts +334 -0
  333. package/src/lib/room-push-dimensions.test.ts +38 -1
  334. package/src/lib/room-push-dimensions.ts +226 -2
  335. package/src/lib/room-selection.test.ts +23 -6
  336. package/src/lib/scene-bounds.test.ts +144 -0
  337. package/src/lib/scene-bounds.ts +79 -15
  338. package/src/lib/scene-clipboard.test.ts +70 -49
  339. package/src/lib/scene-clipboard.ts +11 -5
  340. package/src/lib/selection-cycle.ts +63 -0
  341. package/src/lib/separator-edit.test.ts +431 -0
  342. package/src/lib/separator-edit.ts +179 -0
  343. package/src/lib/separator-lines.ts +38 -0
  344. package/src/lib/sfx-bus.ts +27 -1
  345. package/src/lib/sfx-player.ts +16 -0
  346. package/src/lib/snapping-mode.test.ts +23 -1
  347. package/src/lib/tool-cursor-icon.ts +5 -1
  348. package/src/lib/tool-shortcuts.ts +130 -0
  349. package/src/lib/viewer-selection.test.ts +28 -24
  350. package/src/lib/viewer-selection.ts +21 -6
  351. package/src/lib/wall-draw-variant.test.ts +7 -6
  352. package/src/lib/wall-draw-variant.ts +107 -6
  353. package/src/lib/wall-nudge.test.ts +44 -0
  354. package/src/lib/wall-nudge.ts +131 -0
  355. package/src/lib/wall-parallel-snap.test.ts +83 -0
  356. package/src/lib/wall-parallel-snap.ts +176 -0
  357. package/src/lib/wall-placement-pointer.test.ts +23 -0
  358. package/src/lib/wall-placement-pointer.ts +18 -0
  359. package/src/lib/wall-pull.test.ts +245 -0
  360. package/src/lib/wall-pull.ts +209 -0
  361. package/src/lib/wall-push-line-up.test.ts +132 -0
  362. package/src/lib/wall-region-list.ts +17 -6
  363. package/src/lib/wall-run-snap.test.ts +188 -0
  364. package/src/lib/wall-run-snap.ts +83 -0
  365. package/src/lib/wall-type.test.ts +125 -0
  366. package/src/lib/wall-type.ts +119 -0
  367. package/src/lib/window-interaction.test.ts +44 -0
  368. package/src/lib/world-grid-snap.test.ts +139 -2
  369. package/src/lib/world-grid-snap.ts +102 -105
  370. package/src/store/use-alignment-guides.ts +27 -1
  371. package/src/store/use-editor.tsx +53 -0
  372. package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
  373. package/src/store/use-floorplan-annotation-visibility.ts +47 -22
  374. package/src/store/use-placement-typing.test.ts +165 -0
  375. package/src/store/use-placement-typing.ts +186 -0
  376. package/src/store/use-wall-snap-indicator.ts +15 -2
  377. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  378. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  379. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  380. package/src/lib/door-interaction.ts +0 -102
  381. package/src/lib/measurement-label.ts +0 -68
  382. package/src/lib/window-interaction.ts +0 -100
@@ -9,49 +9,74 @@ import {
9
9
  normalizeFloorplanAnnotationVisibility,
10
10
  } from '../lib/floorplan/annotation-visibility'
11
11
  import {
12
- DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE,
13
- type FloorplanWallDimensionReference,
14
- normalizeFloorplanWallDimensionReference,
12
+ DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES,
13
+ type FloorplanWallDimensionFaces,
14
+ normalizeFloorplanWallDimensionFaces,
15
15
  } from '../lib/floorplan/floorplan-extension'
16
16
 
17
- type FloorplanAnnotationVisibilityState = {
17
+ type PersistedFloorplanAnnotationState = {
18
18
  visibility: FloorplanAnnotationVisibility
19
- wallDimensionReference: FloorplanWallDimensionReference
19
+ showDimensions: boolean
20
+ wallDimensionFaces: FloorplanWallDimensionFaces
21
+ }
22
+
23
+ type FloorplanAnnotationVisibilityState = PersistedFloorplanAnnotationState & {
20
24
  setCategory: (category: FloorplanAnnotationCategory, visible: boolean) => void
21
- setWallDimensionReference: (reference: FloorplanWallDimensionReference) => void
25
+ setShowDimensions: (shown: boolean) => void
26
+ setWallDimensionFaces: (faces: FloorplanWallDimensionFaces) => void
22
27
  reset: () => void
23
28
  }
24
29
 
30
+ const defaultState = (): PersistedFloorplanAnnotationState => ({
31
+ visibility: { ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY },
32
+ showDimensions: true,
33
+ wallDimensionFaces: DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES,
34
+ })
35
+
36
+ /**
37
+ * The persisted part of the store, read back from storage. Anything stale or
38
+ * unknown (an older shape, a retired `wallDimensionReference`) falls back to
39
+ * the default.
40
+ */
41
+ function normalizeFloorplanAnnotationState(persisted: unknown): PersistedFloorplanAnnotationState {
42
+ const record = (persisted && typeof persisted === 'object' ? persisted : {}) as Record<
43
+ string,
44
+ unknown
45
+ >
46
+ return {
47
+ visibility: normalizeFloorplanAnnotationVisibility(record.visibility),
48
+ showDimensions: record.showDimensions !== false,
49
+ wallDimensionFaces: normalizeFloorplanWallDimensionFaces(record.wallDimensionFaces),
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Display preferences for dimensions and annotations, kept per browser:
55
+ * Display → Dimensions (`showDimensions`) and Measure walls
56
+ * (`wallDimensionFaces`) apply to the plan and the 3D view; `visibility` is
57
+ * the Expert plan's annotation categories.
58
+ */
25
59
  const useFloorplanAnnotationVisibility = create<FloorplanAnnotationVisibilityState>()(
26
60
  persist(
27
61
  (set) => ({
28
- visibility: { ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY },
29
- wallDimensionReference: DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE,
62
+ ...defaultState(),
30
63
  setCategory: (category, visible) =>
31
64
  set((state) => ({ visibility: { ...state.visibility, [category]: visible } })),
32
- setWallDimensionReference: (wallDimensionReference) => set({ wallDimensionReference }),
33
- reset: () =>
34
- set({
35
- visibility: { ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY },
36
- wallDimensionReference: DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE,
37
- }),
65
+ setShowDimensions: (showDimensions) => set({ showDimensions }),
66
+ setWallDimensionFaces: (wallDimensionFaces) => set({ wallDimensionFaces }),
67
+ reset: () => set(defaultState()),
38
68
  }),
39
69
  {
40
70
  name: 'pascal-floorplan-annotation-visibility',
41
71
  merge: (persistedState, currentState) => ({
42
72
  ...currentState,
43
- visibility: normalizeFloorplanAnnotationVisibility(
44
- (persistedState as { visibility?: unknown } | undefined)?.visibility,
45
- ),
46
- wallDimensionReference: normalizeFloorplanWallDimensionReference(
47
- (persistedState as { wallDimensionReference?: unknown } | undefined)
48
- ?.wallDimensionReference,
49
- ),
73
+ ...normalizeFloorplanAnnotationState(persistedState),
50
74
  }),
51
75
  partialize: (state) =>
52
76
  ({
53
77
  visibility: state.visibility,
54
- wallDimensionReference: state.wallDimensionReference,
78
+ showDimensions: state.showDimensions,
79
+ wallDimensionFaces: state.wallDimensionFaces,
55
80
  }) as FloorplanAnnotationVisibilityState,
56
81
  },
57
82
  ),
@@ -0,0 +1,165 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test'
2
+ import {
3
+ isPlacementTypingKey,
4
+ PLACEMENT_TYPING_DISTANCE_OFFSET,
5
+ PLACEMENT_TYPING_LENGTH_ANGLE,
6
+ routePlacementTypingKey,
7
+ usePlacementTyping,
8
+ } from './use-placement-typing'
9
+
10
+ const key = (
11
+ k: string,
12
+ mods: Partial<Pick<KeyboardEvent, 'metaKey' | 'ctrlKey' | 'altKey'>> = {},
13
+ ) => ({
14
+ key: k,
15
+ metaKey: false,
16
+ ctrlKey: false,
17
+ altKey: false,
18
+ ...mods,
19
+ })
20
+
21
+ const beginTwoFields = (activeField: 0 | 1) => {
22
+ usePlacementTyping.getState().begin({ labels: PLACEMENT_TYPING_DISTANCE_OFFSET, activeField })
23
+ return true
24
+ }
25
+
26
+ describe('usePlacementTyping', () => {
27
+ beforeEach(() => {
28
+ usePlacementTyping.getState().clear()
29
+ })
30
+
31
+ test('keeps two independent field buffers and toggles the active field', () => {
32
+ const typing = usePlacementTyping.getState()
33
+ typing.begin({ labels: PLACEMENT_TYPING_DISTANCE_OFFSET, fieldDefaults: ['1.2', '0'] })
34
+ typing.append('2')
35
+ typing.toggleField()
36
+ typing.append('-')
37
+ typing.append('4')
38
+
39
+ expect(usePlacementTyping.getState().fields).toEqual(['2', '-4'])
40
+ expect(usePlacementTyping.getState().fieldDefaults).toEqual(['1.2', '0'])
41
+ expect(usePlacementTyping.getState().activeField).toBe(1)
42
+ })
43
+
44
+ test('backspace only changes the active field', () => {
45
+ const typing = usePlacementTyping.getState()
46
+ typing.begin({ labels: PLACEMENT_TYPING_DISTANCE_OFFSET })
47
+ typing.append('25')
48
+ typing.toggleField()
49
+ typing.append('3')
50
+ typing.backspace()
51
+
52
+ expect(usePlacementTyping.getState().fields).toEqual(['25', ''])
53
+ })
54
+
55
+ test('publishes projection and an enter request without clearing input', () => {
56
+ const typing = usePlacementTyping.getState()
57
+ typing.begin({ labels: PLACEMENT_TYPING_DISTANCE_OFFSET })
58
+ typing.append('2')
59
+ typing.setProjectedPosition([2, 0, 1])
60
+ typing.requestCommit()
61
+
62
+ expect(usePlacementTyping.getState().projectedPosition).toEqual([2, 0, 1])
63
+ expect(usePlacementTyping.getState().submitRevision).toBe(1)
64
+ expect(usePlacementTyping.getState().isActive).toBe(true)
65
+ })
66
+
67
+ test('clear ends the session and forgets its labels', () => {
68
+ usePlacementTyping.getState().begin({ labels: PLACEMENT_TYPING_LENGTH_ANGLE })
69
+ usePlacementTyping.getState().append('3')
70
+ usePlacementTyping.getState().clear()
71
+ const state = usePlacementTyping.getState()
72
+ expect(state.isActive).toBe(false)
73
+ expect(state.fields).toEqual(['', ''])
74
+ expect(state.labels).toEqual([])
75
+ })
76
+ })
77
+
78
+ describe('routePlacementTypingKey', () => {
79
+ beforeEach(() => {
80
+ usePlacementTyping.getState().clear()
81
+ })
82
+
83
+ test('a digit or a leading dot opens a session on the first field', () => {
84
+ expect(routePlacementTypingKey(key('5'), { begin: beginTwoFields })).toBe('edit')
85
+ expect(usePlacementTyping.getState().fields).toEqual(['5', ''])
86
+ usePlacementTyping.getState().clear()
87
+ expect(routePlacementTypingKey(key('.'), { begin: beginTwoFields })).toBe('edit')
88
+ expect(usePlacementTyping.getState().fields).toEqual(['.', ''])
89
+ })
90
+
91
+ test('letters, signs and separators leave tool shortcuts alone until a session opens', () => {
92
+ for (const k of ['c', 'm', 't', 'f', '-', ' ', ',']) {
93
+ expect(routePlacementTypingKey(key(k), { begin: beginTwoFields })).toBeNull()
94
+ }
95
+ expect(usePlacementTyping.getState().isActive).toBe(false)
96
+ })
97
+
98
+ test('no session opens when the tool refuses or offers no begin', () => {
99
+ expect(routePlacementTypingKey(key('5'), { begin: () => false })).toBeNull()
100
+ expect(routePlacementTypingKey(key('5'))).toBeNull()
101
+ expect(usePlacementTyping.getState().isActive).toBe(false)
102
+ })
103
+
104
+ test('Tab opens a session on the second field only when the tool asks for it', () => {
105
+ expect(routePlacementTypingKey(key('Tab'), { begin: beginTwoFields })).toBeNull()
106
+ expect(routePlacementTypingKey(key('Tab'), { begin: beginTwoFields, tabBegins: true })).toBe(
107
+ 'edit',
108
+ )
109
+ expect(usePlacementTyping.getState().activeField).toBe(1)
110
+ })
111
+
112
+ test('inside a session: unit letters extend, Tab and comma switch, Enter commits', () => {
113
+ routePlacementTypingKey(key('1'), { begin: beginTwoFields })
114
+ for (const k of ['8', '0', 'c', 'm']) routePlacementTypingKey(key(k))
115
+ expect(routePlacementTypingKey(key('Tab'))).toBe('edit')
116
+ routePlacementTypingKey(key('-'))
117
+ routePlacementTypingKey(key('2'))
118
+ expect(routePlacementTypingKey(key(','))).toBe('edit')
119
+ expect(usePlacementTyping.getState().fields).toEqual(['180cm', '-2'])
120
+ expect(usePlacementTyping.getState().activeField).toBe(0)
121
+ expect(routePlacementTypingKey(key('Enter'))).toBe('commit')
122
+ expect(usePlacementTyping.getState().isActive).toBe(true)
123
+ })
124
+
125
+ test('Backspace edits, Delete empties the active field', () => {
126
+ routePlacementTypingKey(key('4'), { begin: beginTwoFields })
127
+ routePlacementTypingKey(key('2'))
128
+ routePlacementTypingKey(key('Backspace'))
129
+ expect(usePlacementTyping.getState().fields[0]).toBe('4')
130
+ routePlacementTypingKey(key('Delete'))
131
+ expect(usePlacementTyping.getState().fields[0]).toBe('')
132
+ expect(usePlacementTyping.getState().isActive).toBe(true)
133
+ })
134
+
135
+ test('the first Escape only ends the session', () => {
136
+ routePlacementTypingKey(key('3'), { begin: beginTwoFields })
137
+ expect(routePlacementTypingKey(key('Escape'))).toBe('clear')
138
+ expect(usePlacementTyping.getState().isActive).toBe(false)
139
+ expect(routePlacementTypingKey(key('Escape'))).toBeNull()
140
+ })
141
+
142
+ test('chords are never consumed', () => {
143
+ expect(
144
+ routePlacementTypingKey(key('5', { metaKey: true }), { begin: beginTwoFields }),
145
+ ).toBeNull()
146
+ routePlacementTypingKey(key('5'), { begin: beginTwoFields })
147
+ expect(routePlacementTypingKey(key('z', { ctrlKey: true }))).toBeNull()
148
+ expect(routePlacementTypingKey(key('Enter', { altKey: true }))).toBeNull()
149
+ expect(usePlacementTyping.getState().fields[0]).toBe('5')
150
+ })
151
+ })
152
+
153
+ describe('isPlacementTypingKey', () => {
154
+ test('accepts the measurement grammar', () => {
155
+ for (const k of ['0', '9', 'm', 'c', '.', "'", '"', '-', '+', ' ', '/']) {
156
+ expect(isPlacementTypingKey(k)).toBe(true)
157
+ }
158
+ })
159
+
160
+ test('rejects control keys and the field separator', () => {
161
+ for (const k of ['Enter', 'Escape', 'Tab', 'Shift', '', 'F1', ',']) {
162
+ expect(isPlacementTypingKey(k)).toBe(false)
163
+ }
164
+ })
165
+ })
@@ -0,0 +1,186 @@
1
+ import type { MessageDescriptor } from '@pascal-app/core'
2
+ import { create } from 'zustand'
3
+
4
+ /**
5
+ * Typed coordinate entry shared by the placement tools: a drafted wall's
6
+ * length/angle and a cabinet's distance/offset along its wall. One session at
7
+ * a time; the active tool owns what the two fields mean and reads them on
8
+ * every change, the HUD (`PlacementCoordinateInput`) only displays them.
9
+ */
10
+ export type PlacementTypingField = 0 | 1
11
+ export type PlacementTypingValues = [string, string]
12
+ export type PlacementTypingProjection = [number, number, number]
13
+
14
+ export const PLACEMENT_TYPING_DISTANCE_OFFSET: readonly MessageDescriptor[] = [
15
+ /* i18n */ { id: 'editor.placementTyping.distance', message: 'Distance' },
16
+ /* i18n */ { id: 'editor.placementTyping.offset', message: 'Offset' },
17
+ ]
18
+
19
+ export const PLACEMENT_TYPING_LENGTH_ANGLE: readonly MessageDescriptor[] = [
20
+ /* i18n */ { id: 'editor.placementTyping.length', message: 'Length' },
21
+ /* i18n */ { id: 'editor.placementTyping.angle', message: 'Angle' },
22
+ ]
23
+
24
+ export type PlacementTypingSession = {
25
+ /** What each field means, in field order; the HUD shows one box per label. */
26
+ labels: readonly MessageDescriptor[]
27
+ fieldDefaults?: PlacementTypingValues
28
+ activeField?: PlacementTypingField
29
+ }
30
+
31
+ export type PlacementTypingState = {
32
+ isActive: boolean
33
+ labels: readonly MessageDescriptor[]
34
+ fields: PlacementTypingValues
35
+ /** What an empty field stands for, shown as its placeholder. */
36
+ fieldDefaults: PlacementTypingValues
37
+ activeField: PlacementTypingField
38
+ projectedPosition: PlacementTypingProjection | null
39
+ submitRevision: number
40
+ begin: (session: PlacementTypingSession) => void
41
+ append: (value: string) => void
42
+ backspace: () => void
43
+ setField: (field: PlacementTypingField, value: string) => void
44
+ setActiveField: (field: PlacementTypingField) => void
45
+ toggleField: () => void
46
+ setProjectedPosition: (position: PlacementTypingProjection | null) => void
47
+ requestCommit: () => void
48
+ clear: () => void
49
+ }
50
+
51
+ const EMPTY_VALUES: PlacementTypingValues = ['', '']
52
+
53
+ export const usePlacementTyping = create<PlacementTypingState>((set) => ({
54
+ isActive: false,
55
+ labels: [],
56
+ fields: [...EMPTY_VALUES],
57
+ fieldDefaults: [...EMPTY_VALUES],
58
+ activeField: 0,
59
+ projectedPosition: null,
60
+ submitRevision: 0,
61
+ begin: ({ labels, fieldDefaults = EMPTY_VALUES, activeField = 0 }) =>
62
+ set({
63
+ isActive: true,
64
+ labels,
65
+ fields: [...EMPTY_VALUES],
66
+ fieldDefaults: [...fieldDefaults],
67
+ activeField,
68
+ projectedPosition: null,
69
+ }),
70
+ append: (value) =>
71
+ set((state) => {
72
+ const fields: PlacementTypingValues = [...state.fields]
73
+ fields[state.activeField] += value
74
+ return { isActive: true, fields }
75
+ }),
76
+ backspace: () =>
77
+ set((state) => {
78
+ const fields: PlacementTypingValues = [...state.fields]
79
+ fields[state.activeField] = fields[state.activeField].slice(0, -1)
80
+ return { fields }
81
+ }),
82
+ setField: (field, value) =>
83
+ set((state) => {
84
+ const fields: PlacementTypingValues = [...state.fields]
85
+ fields[field] = value
86
+ return { isActive: true, fields }
87
+ }),
88
+ setActiveField: (activeField) => set({ activeField }),
89
+ toggleField: () => set((state) => ({ activeField: state.activeField === 0 ? 1 : 0 })),
90
+ setProjectedPosition: (projectedPosition) => set({ projectedPosition }),
91
+ requestCommit: () =>
92
+ set((state: PlacementTypingState) => ({ submitRevision: state.submitRevision + 1 })),
93
+ clear: () =>
94
+ set({
95
+ isActive: false,
96
+ labels: [],
97
+ fields: [...EMPTY_VALUES],
98
+ fieldDefaults: [...EMPTY_VALUES],
99
+ activeField: 0,
100
+ projectedPosition: null,
101
+ }),
102
+ }))
103
+
104
+ /** Keys that extend a field: digits, unit letters, separators, signs, fractions. */
105
+ export function isPlacementTypingKey(key: string): boolean {
106
+ return key.length === 1 && /^[0-9a-zA-Z.'"+\-/ ]$/.test(key)
107
+ }
108
+
109
+ /**
110
+ * Keys that may open a session. Letters only ever continue a field, so a
111
+ * tool's single-letter shortcuts keep working until a number is typed.
112
+ */
113
+ export function isPlacementTypingStartKey(key: string): boolean {
114
+ return key.length === 1 && /^[0-9.]$/.test(key)
115
+ }
116
+
117
+ export type PlacementTypingKeyOutcome = 'edit' | 'commit' | 'clear'
118
+
119
+ export type PlacementTypingKeyOptions = {
120
+ /**
121
+ * Opens a session for a start key (or Tab, with `tabBegins`) when none is
122
+ * active. Returns false when the tool cannot take typed entry right now.
123
+ */
124
+ begin?: (activeField: PlacementTypingField) => boolean
125
+ /** Tab with no session opens one on the second field. */
126
+ tabBegins?: boolean
127
+ }
128
+
129
+ /**
130
+ * The one keyboard convention for typed entry. Tab or comma switches field,
131
+ * Enter asks the tool to commit, Escape clears the session (the tool's next
132
+ * Escape cancels its draft), Backspace edits and Delete empties the active
133
+ * field. Chords with Cmd/Ctrl/Alt are never consumed. Returns null when the
134
+ * key is not part of typed entry; the caller prevents default and stops
135
+ * propagation for every other outcome.
136
+ */
137
+ export function routePlacementTypingKey(
138
+ event: Pick<KeyboardEvent, 'key' | 'metaKey' | 'ctrlKey' | 'altKey'>,
139
+ options: PlacementTypingKeyOptions = {},
140
+ ): PlacementTypingKeyOutcome | null {
141
+ if (event.metaKey || event.ctrlKey || event.altKey) return null
142
+ const typing = usePlacementTyping.getState()
143
+ const { key } = event
144
+ if (!typing.isActive) {
145
+ if (isPlacementTypingStartKey(key)) {
146
+ if (!options.begin?.(0)) return null
147
+ usePlacementTyping.getState().append(key)
148
+ return 'edit'
149
+ }
150
+ if (key === 'Tab' && options.tabBegins && options.begin?.(1)) return 'edit'
151
+ return null
152
+ }
153
+ if (key === 'Tab' || key === ',') {
154
+ typing.toggleField()
155
+ return 'edit'
156
+ }
157
+ if (key === 'Enter') return 'commit'
158
+ if (key === 'Escape') {
159
+ typing.clear()
160
+ return 'clear'
161
+ }
162
+ if (key === 'Backspace') {
163
+ typing.backspace()
164
+ return 'edit'
165
+ }
166
+ if (key === 'Delete') {
167
+ typing.setField(typing.activeField, '')
168
+ return 'edit'
169
+ }
170
+ if (isPlacementTypingKey(key)) {
171
+ typing.append(key)
172
+ return 'edit'
173
+ }
174
+ return null
175
+ }
176
+
177
+ /** Parse options for a typed length: a bare number is read in the display unit. */
178
+ export function placementTypingLengthOptions(
179
+ unit: 'metric' | 'imperial',
180
+ metricNotation: 'meters' | 'millimeters',
181
+ ) {
182
+ return {
183
+ bareUnit: unit === 'imperial' ? 'in' : metricNotation === 'millimeters' ? 'mm' : 'm',
184
+ system: unit === 'imperial' ? ('imperial' as const) : ('metric' as const),
185
+ }
186
+ }
@@ -7,8 +7,19 @@
7
7
 
8
8
  import { create } from 'zustand'
9
9
 
10
- /** Which kind of wall geometry the draft point snapped to. */
11
- export type WallSnapKind = 'endpoint' | 'midpoint' | 'intersection' | 'wall'
10
+ /**
11
+ * Which kind of geometry the draft point snapped to. A pulled wall also lands
12
+ * square on a wall (`perpendicular`), on a projection guide (`alignment`), or
13
+ * at a grid length (`grid`).
14
+ */
15
+ export type WallSnapKind =
16
+ | 'endpoint'
17
+ | 'midpoint'
18
+ | 'intersection'
19
+ | 'wall'
20
+ | 'perpendicular'
21
+ | 'alignment'
22
+ | 'grid'
12
23
 
13
24
  export type WallSnapPoint = {
14
25
  /** Building-local plan coordinates (XZ meters). */
@@ -17,6 +28,8 @@ export type WallSnapPoint = {
17
28
  kind: WallSnapKind
18
29
  /** Optional wall ids whose geometry produced this snap. */
19
30
  wallIds?: string[]
31
+ /** Words the marker shows beside its glyph ("Endpoint", "On wall"); none when absent. */
32
+ label?: string
20
33
  }
21
34
 
22
35
  type WallSnapIndicatorState = {
@@ -1,8 +0,0 @@
1
- export const DEFAULT_ELEVATOR_WIDTH = 1.84
2
- export const DEFAULT_ELEVATOR_DEPTH = 1.84
3
- export const DEFAULT_ELEVATOR_CAB_HEIGHT = 2.35
4
- export const DEFAULT_ELEVATOR_DOOR_WIDTH = 0.95
5
- export const DEFAULT_ELEVATOR_DOOR_HEIGHT = 2.1
6
- export const DEFAULT_ELEVATOR_SPEED = 2.2
7
- export const DEFAULT_ELEVATOR_DOOR_DURATION_MS = 900
8
- export const DEFAULT_ELEVATOR_DWELL_MS = 1400