@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
@@ -10,7 +10,9 @@ import {
10
10
  type AnyNode,
11
11
  type AnyNodeId,
12
12
  type BuildingNode,
13
+ floorPlateRoom,
13
14
  getRoofSegmentDeckReach,
15
+ isOutdoorRoomType,
14
16
  type LevelNode,
15
17
  migrateSiteMetadata,
16
18
  type RoofSegmentNode,
@@ -201,7 +203,7 @@ function coveredShare(ring: readonly Pt[], covers: readonly Pt[][]): number {
201
203
 
202
204
  /**
203
205
  * The level's outdoor slabs in site metres: porches, decks, patios and
204
- * landings (a generator `metadata.porch`, or a deck / porch-slab floor, or a
206
+ * landings (a generator `metadata.porch`, an outdoor room’s plate, or a
205
207
  * slab lying outside the house's walls) and the flatwork. The house's own
206
208
  * floors (the storey slab, a raised platform, the garage pad) and a porch
207
209
  * cover's beam are not outdoor parts. `roofs` decides which are covered.
@@ -247,7 +249,8 @@ export function outdoorParts(
247
249
  // a porch cover's beam box is not a floor
248
250
  if (/beam/i.test(floor) || /beam/i.test(slab.name ?? '')) continue
249
251
  const porch = meta.porch as { policy?: string; entrance?: string } | undefined
250
- const outdoorFloor = floor === 'deck' || floor === 'porch-slab'
252
+ const roomType = floorPlateRoom(child, (id) => scene.nodes[id])?.roomType
253
+ const outdoorFloor = isOutdoorRoomType(roomType)
251
254
  if (!porch && !outdoorFloor) {
252
255
  // a hand-drawn slab: the house's floor when it lies under the walls
253
256
  if (HOUSE_FLOORS.has(floor)) continue
@@ -257,7 +260,7 @@ export function outdoorParts(
257
260
  )
258
261
  if (walls.length === 0 || walls.some((w) => pointInPolygon(w, c[0], c[1]))) continue
259
262
  }
260
- const wood = floor === 'deck'
263
+ const wood = roomType === 'deck'
261
264
  const policy = porch?.policy ?? ''
262
265
  const kind: OutdoorKind =
263
266
  policy === 'deck' || (wood && policy !== 'full' && policy !== 'entry')
@@ -0,0 +1,114 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import {
3
+ type AnyNode,
4
+ type ArtifactStore,
5
+ artifactUrl,
6
+ BuildingNode,
7
+ GeometryArtifactManifest,
8
+ generateId,
9
+ ItemNode,
10
+ LevelNode,
11
+ scriptSource,
12
+ } from '@pascal-app/core'
13
+ import {
14
+ captureRegistryVersions,
15
+ effectIds,
16
+ openDetachedScene,
17
+ SceneEnvironmentDisposedError,
18
+ type SceneSnapshot,
19
+ } from '@pascal-app/core/runtime'
20
+ import { compileAndStoreGeometryScript } from './author'
21
+
22
+ // A compile of an object for a detached scene reads the scene and mints the object's id before
23
+ // its first await: the scene records those reads and allocates that id in its synchronous scope,
24
+ // and a compile that resolves after the scene is disposed reaches nothing of it.
25
+
26
+ const sha = 'a'.repeat(64)
27
+ const compiled = {
28
+ sha256: sha,
29
+ script: 'b'.repeat(64),
30
+ language: 'three' as const,
31
+ mount: 'floor' as const,
32
+ params: {},
33
+ manifest: GeometryArtifactManifest.parse({
34
+ bounds: { min: [-0.5, 0, -0.5], max: [0.5, 1, 0.5] },
35
+ triangles: 12,
36
+ }),
37
+ }
38
+
39
+ function scene() {
40
+ const screen = ItemNode.parse({
41
+ id: 'item_screen',
42
+ parentId: 'level_0',
43
+ name: 'Screen',
44
+ source: scriptSource(compiled),
45
+ asset: {
46
+ id: 'script_screen',
47
+ category: 'screen',
48
+ name: 'Screen',
49
+ thumbnail: '',
50
+ source: 'mine',
51
+ src: artifactUrl(sha),
52
+ dimensions: [1, 1, 1],
53
+ },
54
+ })
55
+ const level = LevelNode.parse({ id: 'level_0', parentId: 'building_0', children: [screen.id] })
56
+ const building = BuildingNode.parse({ id: 'building_0', children: [level.id] })
57
+ const snapshot = {
58
+ nodes: Object.fromEntries([building, level, screen].map((node) => [node.id, node])),
59
+ rootNodeIds: [building.id],
60
+ collections: {},
61
+ materials: {},
62
+ installedPlugins: [],
63
+ } as unknown as SceneSnapshot
64
+ return openDetachedScene(snapshot, {
65
+ base: { kind: 'authority', seq: 1 },
66
+ context: {
67
+ activeLevelId: 'level_0',
68
+ selection: [],
69
+ units: 'metric',
70
+ catalogVersion: 'test',
71
+ registryVersions: captureRegistryVersions(),
72
+ },
73
+ ids: effectIds('effect_compile'),
74
+ })
75
+ }
76
+
77
+ const artifacts = {
78
+ url: () => null,
79
+ put: async (hash: string) => hash,
80
+ text: async () => 'export default function build() {}',
81
+ } as unknown as ArtifactStore
82
+
83
+ describe('compileAndStoreGeometryScript on a detached scene', () => {
84
+ test('reads the node it rebuilds before the first await', async () => {
85
+ const detached = scene()
86
+ const compiling = detached.run(() =>
87
+ compileAndStoreGeometryScript(
88
+ { nodeId: 'item_screen', params: {} },
89
+ { nodes: detached.reader().nodes as Record<string, AnyNode>, artifacts },
90
+ ),
91
+ )
92
+ expect(detached.dependencies()).toContainEqual(
93
+ expect.objectContaining({ kind: 'node', id: 'item_screen' }),
94
+ )
95
+ detached.dispose()
96
+ const error = await compiling.catch((thrown: unknown) => thrown)
97
+ expect(error).not.toBeInstanceOf(SceneEnvironmentDisposedError)
98
+ })
99
+
100
+ test("mints a new object's id in the scene's scope, before the first await", async () => {
101
+ const detached = scene()
102
+ const next = detached.run(() => {
103
+ void compileAndStoreGeometryScript(
104
+ { code: 'export default function build() {}', kind: 'item' },
105
+ { nodes: detached.reader().nodes as Record<string, AnyNode>, artifacts },
106
+ ).catch(() => {})
107
+ return generateId('item')
108
+ })
109
+ const ids = effectIds('effect_compile')
110
+ ids('item')
111
+ expect(next as string).toBe(ids('item'))
112
+ detached.dispose()
113
+ })
114
+ })
@@ -1,5 +1,7 @@
1
1
  import {
2
+ type AnyNode,
2
3
  type AnyNodeId,
4
+ type ArtifactStore,
3
5
  type CompiledGeometryScript,
4
6
  GEOMETRY_SCRIPT_MIME_TYPES,
5
7
  GeometryArtifactMetadata,
@@ -41,28 +43,46 @@ export function configureBlenderScriptBuilder(next: BlenderScriptBuilder | null)
41
43
  blenderBuilder = next
42
44
  }
43
45
 
46
+ /**
47
+ * What a compile reads from the scene and the artifact service it stores to. By default the
48
+ * editor's own; a detached scene passes its tracked nodes, so the reads are recorded there.
49
+ */
50
+ export type GeometryScriptScene = {
51
+ nodes?: Readonly<Record<string, AnyNode>>
52
+ artifacts?: ArtifactStore
53
+ }
54
+
44
55
  /**
45
56
  * The editor's compile step for `add_object`: runs the module (a three.js one
46
57
  * in the worker, a bpy one through the host's Blender builder) and stores the
47
58
  * GLB and the module text, so the core operation can reference both by hash.
48
59
  * Without `code`, the node's stored script is rebuilt; either way an edit keeps
49
60
  * the node's param values it does not override.
61
+ *
62
+ * Everything it reads from the scene, and the id of a new object, are taken
63
+ * synchronously, before the first await: a detached scene records reads and
64
+ * mints ids only inside its synchronous scope, and is disposed by the time a
65
+ * late compile resolves.
50
66
  */
51
- export async function compileAndStoreGeometryScript(input: {
52
- code?: string
53
- language?: GeometryScriptLanguage
54
- nodeId?: string
55
- params?: Record<string, GeometryScriptParamValue>
56
- kind?: 'item' | 'door' | 'window' | 'column'
57
- inputs?: Record<string, unknown>
58
- name?: string
59
- description?: string
60
- category?: string
61
- tags?: string[]
62
- provenance?: GeometryArtifactMetadata
63
- }): Promise<CompiledGeometryScript> {
64
- const code = input.code ?? (await storedScript(input.nodeId))
65
- const node = input.nodeId ? useScene.getState().nodes[input.nodeId as AnyNodeId] : undefined
67
+ export async function compileAndStoreGeometryScript(
68
+ input: {
69
+ code?: string
70
+ language?: GeometryScriptLanguage
71
+ nodeId?: string
72
+ params?: Record<string, GeometryScriptParamValue>
73
+ kind?: 'item' | 'door' | 'window' | 'column'
74
+ inputs?: Record<string, unknown>
75
+ name?: string
76
+ description?: string
77
+ category?: string
78
+ tags?: string[]
79
+ provenance?: GeometryArtifactMetadata
80
+ },
81
+ scene: GeometryScriptScene = {},
82
+ ): Promise<CompiledGeometryScript> {
83
+ const nodes = scene.nodes ?? useScene.getState().nodes
84
+ const artifacts = scene.artifacts ?? getArtifactStore()
85
+ const node = input.nodeId ? nodes[input.nodeId as AnyNodeId] : undefined
66
86
  const language = editedScriptLanguage(node, input)
67
87
  const params = editedScriptParams(node, input.params)
68
88
  const kind =
@@ -71,12 +91,16 @@ export async function compileAndStoreGeometryScript(input: {
71
91
  ? node.type
72
92
  : 'item')
73
93
  const nodeId = input.nodeId ?? generateId(kind)
94
+ const authored = isScriptedNode(node) ? scriptedObjectMeta(node) : {}
95
+ const script = input.code === undefined ? authoredScript(nodes, input.nodeId) : null
74
96
  const metadata = GeometryArtifactMetadata.parse({
75
- ...(isScriptedNode(node) ? scriptedObjectMeta(node) : {}),
97
+ ...authored,
76
98
  ...input.provenance,
77
99
  ...GeometryReuseFields.parse(input),
78
100
  kind,
79
101
  })
102
+
103
+ const code = input.code ?? (await readScript(artifacts, script!, input.nodeId!))
80
104
  if (language === 'bpy') {
81
105
  if (!blenderBuilder) throw new Error('This editor cannot run Blender (bpy) scripts')
82
106
  return { ...(await blenderBuilder({ code, params, nodeId, metadata })), nodeId }
@@ -87,10 +111,9 @@ export async function compileAndStoreGeometryScript(input: {
87
111
  inputs: input.inputs,
88
112
  })
89
113
  const context = { nodeId, metadata: { ...metadata, mount: compiled.mount } }
90
- const store = getArtifactStore()
91
114
  const [sha256] = await Promise.all([
92
- store.put(compiled.sha256, glb, 'model/gltf-binary', context),
93
- store.put(
115
+ artifacts.put(compiled.sha256, glb, 'model/gltf-binary', context),
116
+ artifacts.put(
94
117
  compiled.script,
95
118
  new TextEncoder().encode(code),
96
119
  GEOMETRY_SCRIPT_MIME_TYPES.three,
@@ -100,15 +123,24 @@ export async function compileAndStoreGeometryScript(input: {
100
123
  return { ...compiled, sha256, nodeId }
101
124
  }
102
125
 
103
- /** The module text of an authored object, read back from the artifact store. */
104
- export async function storedScript(nodeId: string | undefined): Promise<string> {
126
+ /** The hash of an authored object's script, read from the scene. */
127
+ function authoredScript(nodes: Readonly<Record<string, AnyNode>>, nodeId: string | undefined) {
105
128
  if (!nodeId) throw new Error('Pass the code to build a new object')
106
- const node = authoredObject(useScene.getState().nodes, nodeId)
107
- const code = await getArtifactStore().text(node.source.script)
129
+ return authoredObject(nodes as Record<string, AnyNode>, nodeId).source.script
130
+ }
131
+
132
+ async function readScript(artifacts: ArtifactStore, script: string, nodeId: string) {
133
+ const code = await artifacts.text(script)
108
134
  if (code === null) throw new Error(`The script of ${nodeId} could not be read`)
109
135
  return code
110
136
  }
111
137
 
138
+ /** The module text of an authored object, read back from the artifact store. */
139
+ export async function storedScript(nodeId: string | undefined): Promise<string> {
140
+ const script = authoredScript(useScene.getState().nodes, nodeId)
141
+ return readScript(getArtifactStore(), script, nodeId!)
142
+ }
143
+
112
144
  const rebuildGeneration = new Map<string, number>()
113
145
 
114
146
  /**
@@ -1,4 +1,5 @@
1
1
  import type { GeometryScriptParamValue } from '@pascal-app/core'
2
+ import { sdfChecksOn } from '@pascal-app/core/usage-field'
2
3
  import type { GeometryScriptCompileOutput } from '@pascal-app/geometry-script'
3
4
  import type { GeometryScriptWorkerRequest, GeometryScriptWorkerResponse } from './protocol'
4
5
 
@@ -148,6 +149,8 @@ export async function compileGeometryScriptInWorker(input: {
148
149
  code: input.code,
149
150
  params: input.params,
150
151
  inputs: input.inputs,
152
+ // the sandbox is its own context: the flag travels with the request, only when it is on
153
+ ...(sdfChecksOn() ? { usageShape: true } : {}),
151
154
  }
152
155
  return new Promise((resolve, reject) => {
153
156
  const timeout = setTimeout(() => {
@@ -17,9 +17,9 @@ import {
17
17
  import { compileGeometryScriptInWorker } from './client'
18
18
 
19
19
  /**
20
- * Runs a finish script (a room's floor, a wall's face) in the sandbox for what it finishes and
21
- * stores the GLB and the module text, answering the compile with the stored hashes
22
- * (`setRoomFloorScript` and `setWallFaceScript` take it with its `inputs`).
20
+ * Runs a finish script (a room's floor, a slab's surface, a wall's face) in the sandbox for what it
21
+ * finishes and stores the GLB and the module text, answering the compile with the stored hashes
22
+ * (`setRoomFloorScript`, `setSlabSurfaceScript` and `setWallFaceScript` take it with its `inputs`).
23
23
  */
24
24
  export async function compileAndStoreFinishScript(
25
25
  input: FinishScriptTarget & {
@@ -65,10 +65,10 @@ const SETTLE_MS = 150
65
65
 
66
66
  /**
67
67
  * Keeps every finish script built for what it finishes: after a scene change settles, each script
68
- * whose room's floor the structure re-derived, or whose wall face changed, runs again in the
69
- * sandbox. The new build is written outside history, as the reconcile's derived writes are, and
70
- * only while its floor or face is still the one it was built for; undo brings back the room or
71
- * wall and the script built for it together.
68
+ * whose room's floor the structure re-derived, whose slab was reshaped, or whose wall face changed,
69
+ * runs again in the sandbox. The new build is written outside history, as the reconcile's derived
70
+ * writes are, and only while its floor or face is still the one it was built for; undo brings back
71
+ * the room, slab or wall and the script built for it together.
72
72
  */
73
73
  export function initFinishScriptRefresh(): () => void {
74
74
  let timer: ReturnType<typeof setTimeout> | null = null
@@ -33,7 +33,9 @@ export type GestureSpec = {
33
33
  kind: string
34
34
  /**
35
35
  * Drops the gesture's own in-flight state (draft, preview, listeners,
36
- * pending frames). Never writes the scene. Runs once, on cancel only.
36
+ * pending frames). Runs once, on cancel only. Undo and redo cancel before
37
+ * they jump, so this must record no history: a gesture that previews
38
+ * through scene writes under paused history reverts them here, then resumes.
37
39
  */
38
40
  onCancel: (reason: GestureCancelReason) => void
39
41
  /** A scope the owner begins for the gesture and releases only while it is still this one. */