@pascal-app/editor 1.0.3 → 1.0.5

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 (624) hide show
  1. package/README.md +106 -0
  2. package/package.json +42 -38
  3. package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
  4. package/src/components/editor/action-menu-button.tsx +83 -0
  5. package/src/components/editor/bake-thumbnail.tsx +307 -5
  6. package/src/components/editor/capture-level-picker.tsx +72 -0
  7. package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
  8. package/src/components/editor/construction-reveal-audio.tsx +40 -0
  9. package/src/components/editor/custom-camera-controls.tsx +275 -57
  10. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  11. package/src/components/editor/duplicate-node.ts +244 -0
  12. package/src/components/editor/editor-layout-mobile.tsx +37 -26
  13. package/src/components/editor/editor-layout-v2.tsx +132 -59
  14. package/src/components/editor/export-manager.tsx +47 -26
  15. package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
  16. package/src/components/editor/first-person/build-collider-world.ts +28 -7
  17. package/src/components/editor/first-person/ground-holes.test.ts +128 -0
  18. package/src/components/editor/first-person/ground-holes.ts +130 -0
  19. package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
  20. package/src/components/editor/first-person/mechanism-targets.ts +88 -0
  21. package/src/components/editor/first-person-controls.tsx +220 -9
  22. package/src/components/editor/floating-action-menu.tsx +63 -207
  23. package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
  24. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  25. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  26. package/src/components/editor/floor-region-controls.tsx +846 -0
  27. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  28. package/src/components/editor/floor-surface.test.ts +165 -0
  29. package/src/components/editor/floor-surface.ts +182 -0
  30. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  31. package/src/components/editor/floorplan-background-selection.ts +1 -3
  32. package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
  33. package/src/components/editor/floorplan-panel.tsx +414 -363
  34. package/src/components/editor/follow-pascal.test.tsx +912 -0
  35. package/src/components/editor/follow-pascal.tsx +354 -0
  36. package/src/components/editor/footprint-height-handle.tsx +204 -0
  37. package/src/components/editor/group-actions.ts +56 -2
  38. package/src/components/editor/group-move-3d.ts +2 -0
  39. package/src/components/editor/group-rotate-handle.tsx +2 -0
  40. package/src/components/editor/group-transform-shared.test.ts +460 -0
  41. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  42. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  43. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  45. package/src/components/editor/index.tsx +409 -197
  46. package/src/components/editor/join-walls-pill.tsx +44 -0
  47. package/src/components/editor/node-action-menu.tsx +68 -70
  48. package/src/components/editor/node-arrow-handles.tsx +25 -6
  49. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  50. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  52. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  53. package/src/components/editor/registry-action-contributions.tsx +80 -1
  54. package/src/components/editor/riser-diagram-panel.tsx +14 -7
  55. package/src/components/editor/room-controls.tsx +847 -0
  56. package/src/components/editor/room-floor-highlight.tsx +128 -0
  57. package/src/components/editor/room-handles-2d.tsx +354 -0
  58. package/src/components/editor/room-handles.tsx +675 -0
  59. package/src/components/editor/room-highlight.tsx +233 -0
  60. package/src/components/editor/room-labels.test.ts +850 -0
  61. package/src/components/editor/room-labels.tsx +1890 -0
  62. package/src/components/editor/room-transform-ghost.tsx +256 -0
  63. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  64. package/src/components/editor/selection-manager.tsx +474 -419
  65. package/src/components/editor/slab-hole-highlights.tsx +5 -2
  66. package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
  67. package/src/components/editor/snapshot-capture.test.ts +119 -60
  68. package/src/components/editor/snapshot-capture.ts +36 -5
  69. package/src/components/editor/thumbnail-generator.test.ts +654 -0
  70. package/src/components/editor/thumbnail-generator.tsx +1103 -223
  71. package/src/components/editor/use-floorplan-background-placement.ts +34 -12
  72. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  73. package/src/components/editor/views/host-view.tsx +61 -0
  74. package/src/components/editor/views/plugins-view.tsx +663 -0
  75. package/src/components/editor/views/scene-region.tsx +59 -0
  76. package/src/components/editor/views/use-editor-views.ts +57 -0
  77. package/src/components/editor/views/view-bar.test.tsx +54 -0
  78. package/src/components/editor/views/view-bar.tsx +343 -0
  79. package/src/components/editor/wall-justification.test.ts +140 -0
  80. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  81. package/src/components/editor/wall-push-arrow.tsx +92 -0
  82. package/src/components/editor/wall-region-handles.tsx +509 -0
  83. package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
  84. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  85. package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
  86. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  87. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  88. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
  89. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  90. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
  91. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  92. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  93. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  94. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  95. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  96. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  97. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
  98. package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
  99. package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
  100. package/src/components/editor-2d/svg-paths.ts +12 -15
  101. package/src/components/point-ask/bubble.tsx +538 -0
  102. package/src/components/point-ask/hover-chip.tsx +91 -0
  103. package/src/components/point-ask/mode-pill.tsx +92 -0
  104. package/src/components/point-ask/optional-motion.ts +22 -0
  105. package/src/components/point-ask/outline-corners.tsx +93 -0
  106. package/src/components/point-ask/pins.tsx +711 -0
  107. package/src/components/point-ask/point-ask-layer.tsx +83 -0
  108. package/src/components/point-ask/point-ask-styles.ts +105 -0
  109. package/src/components/point-ask/region-marquee-styles.ts +15 -0
  110. package/src/components/point-ask/region-marquee.tsx +64 -0
  111. package/src/components/procedural-items/inspector.tsx +40 -15
  112. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
  113. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  114. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  115. package/src/components/systems/roof/roof-edit-system.tsx +1 -0
  116. package/src/components/systems/zone/zone-system.test.ts +47 -0
  117. package/src/components/systems/zone/zone-system.tsx +29 -68
  118. package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
  119. package/src/components/tools/distribution/connection-compatibility.ts +43 -0
  120. package/src/components/tools/distribution/distribution-public.ts +9 -0
  121. package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
  122. package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
  123. package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
  124. package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
  125. package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
  126. package/src/components/tools/distribution/draw-alignment.ts +35 -0
  127. package/src/components/tools/distribution/ports.test.ts +56 -0
  128. package/src/components/tools/distribution/ports.ts +378 -0
  129. package/src/components/tools/distribution/run-cursor.test.ts +135 -0
  130. package/src/components/tools/distribution/run-cursor.ts +36 -0
  131. package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
  132. package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
  133. package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
  134. package/src/components/tools/distribution/run-port-snap.ts +25 -0
  135. package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
  136. package/src/components/tools/distribution/run-screen-direction.ts +30 -0
  137. package/src/components/tools/elevator/elevator-tool.tsx +3 -6
  138. package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
  139. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
  140. package/src/components/tools/item/placement-history.test.tsx +512 -0
  141. package/src/components/tools/item/placement-strategies.test.ts +46 -1
  142. package/src/components/tools/item/placement-strategies.ts +11 -10
  143. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
  144. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  145. package/src/components/tools/item/use-draft-node.ts +166 -95
  146. package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
  147. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  148. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  149. package/src/components/tools/registry/item-surface-move.test.ts +62 -63
  150. package/src/components/tools/registry/item-surface-move.ts +2 -0
  151. package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
  152. package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
  153. package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
  154. package/src/components/tools/select/box-select-tool.tsx +2 -12
  155. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  156. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  157. package/src/components/tools/select/select-candidates.test.ts +37 -4
  158. package/src/components/tools/select/select-candidates.ts +15 -11
  159. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  160. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  161. package/src/components/tools/tool-manager.tsx +59 -24
  162. package/src/components/tools/wall/wall-drafting.test.ts +175 -47
  163. package/src/components/tools/zone/zone-tool.tsx +4 -12
  164. package/src/components/ui/action-menu/action-button.tsx +9 -1
  165. package/src/components/ui/action-menu/camera-actions.tsx +9 -4
  166. package/src/components/ui/action-menu/control-modes.tsx +15 -42
  167. package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
  168. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
  169. package/src/components/ui/action-menu/index.tsx +56 -33
  170. package/src/components/ui/action-menu/measurement-control.tsx +14 -19
  171. package/src/components/ui/action-menu/placement.ts +20 -0
  172. package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
  173. package/src/components/ui/action-menu/view-toggles.tsx +47 -42
  174. package/src/components/ui/build-panel/build-panel.tsx +242 -0
  175. package/src/components/ui/command-palette/editor-commands.tsx +7 -42
  176. package/src/components/ui/command-palette/index.tsx +50 -39
  177. package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
  178. package/src/components/ui/controls/action-button.tsx +5 -2
  179. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  180. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  181. package/src/components/ui/controls/material-picker.tsx +4 -1
  182. package/src/components/ui/controls/metric-control.tsx +1 -1
  183. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  184. package/src/components/ui/controls/segmented-control.tsx +21 -8
  185. package/src/components/ui/controls/select-control.tsx +65 -0
  186. package/src/components/ui/controls/slider-control.tsx +1 -1
  187. package/src/components/ui/controls/toggle-control.tsx +3 -3
  188. package/src/components/ui/controls/tool-options-panel.tsx +1 -1
  189. package/src/components/ui/floating-level-selector.tsx +159 -59
  190. package/src/components/ui/helpers/building-helper.tsx +4 -1
  191. package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
  192. package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
  193. package/src/components/ui/helpers/helper-manager.tsx +331 -14
  194. package/src/components/ui/helpers/item-helper.tsx +17 -1
  195. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  196. package/src/components/ui/icon-ref.tsx +45 -1
  197. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  198. package/src/components/ui/node-icon-image.tsx +16 -0
  199. package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
  200. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  201. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  202. package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
  203. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
  204. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  205. package/src/components/ui/panels/multi-field-value.ts +6 -3
  206. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  207. package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
  208. package/src/components/ui/panels/node-display.ts +8 -3
  209. package/src/components/ui/panels/panel-manager.tsx +103 -68
  210. package/src/components/ui/panels/panel-wrapper.tsx +130 -62
  211. package/src/components/ui/panels/parametric-field-control.tsx +3 -3
  212. package/src/components/ui/panels/parametric-inspector.tsx +7 -7
  213. package/src/components/ui/panels/reference-panel.tsx +2 -3
  214. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  215. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  216. package/src/components/ui/panels/room-name-options.ts +47 -0
  217. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  218. package/src/components/ui/panels/room-panel.tsx +790 -0
  219. package/src/components/ui/panels/shape-choice.tsx +58 -0
  220. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  221. package/src/components/ui/primitives/badge.tsx +8 -0
  222. package/src/components/ui/primitives/color-dot.tsx +36 -24
  223. package/src/components/ui/primitives/sidebar.tsx +3 -1
  224. package/src/components/ui/right-stack.test.ts +29 -0
  225. package/src/components/ui/right-stack.tsx +164 -0
  226. package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
  227. package/src/components/ui/sidebar/icon-rail.tsx +8 -3
  228. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  229. package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
  230. package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
  231. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
  232. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
  233. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
  234. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
  235. package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
  236. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  237. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
  238. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
  239. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  240. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  241. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  242. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  243. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  244. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  245. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  246. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  247. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  248. package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
  249. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  250. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  251. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  252. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  253. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  254. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  255. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  256. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  257. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  258. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  259. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
  260. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  261. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  262. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  263. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  264. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
  265. package/src/components/ui/sidebar/tab-bar.tsx +14 -4
  266. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  267. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  268. package/src/components/viewer/floorplan-preview.tsx +5 -6
  269. package/src/components/viewer/viewer-controls-bar.tsx +18 -6
  270. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  271. package/src/components/viewer/viewer-rooms.tsx +191 -0
  272. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  273. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  274. package/src/components/viewer/viewer-stage.tsx +2 -2
  275. package/src/components/viewer-zone-system.tsx +3 -8
  276. package/src/hooks/use-auto-save.integration.test.tsx +215 -43
  277. package/src/hooks/use-auto-save.ts +60 -9
  278. package/src/hooks/use-ceiling-events.test.ts +123 -118
  279. package/src/hooks/use-grid-events.test.ts +105 -105
  280. package/src/hooks/use-keyboard.test.ts +243 -19
  281. package/src/hooks/use-keyboard.ts +118 -83
  282. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  283. package/src/hooks/use-open-wall-ends.ts +238 -0
  284. package/src/hooks/use-placement-notice.test.ts +71 -0
  285. package/src/hooks/use-placement-notice.ts +73 -0
  286. package/src/hooks/use-selected-room.ts +143 -0
  287. package/src/index.tsx +312 -58
  288. package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
  289. package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
  290. package/src/lib/add-storey.test.ts +177 -0
  291. package/src/lib/add-storey.ts +59 -0
  292. package/src/lib/below-grade.test.tsx +171 -0
  293. package/src/lib/below-grade.ts +133 -0
  294. package/src/lib/camera-facing.test.ts +24 -0
  295. package/src/lib/camera-facing.ts +24 -0
  296. package/src/lib/ceiling-edit-session.test.ts +321 -0
  297. package/src/lib/ceiling-edit-session.ts +206 -0
  298. package/src/lib/ceiling-region-session.test.ts +179 -0
  299. package/src/lib/continuation.test.ts +19 -1
  300. package/src/lib/continuation.ts +25 -4
  301. package/src/lib/direct-manipulation.ts +2 -0
  302. package/src/lib/door-interaction.ts +14 -0
  303. package/src/lib/download-project-file.ts +17 -0
  304. package/src/lib/editor-level-display.test.ts +96 -0
  305. package/src/lib/editor-level-display.ts +55 -0
  306. package/src/lib/editor-views.ts +178 -0
  307. package/src/lib/export-geometry-wait.test.ts +90 -0
  308. package/src/lib/export-texture-utils.ts +2 -4
  309. package/src/lib/floor-edit-session.ts +153 -0
  310. package/src/lib/floor-footprints.test.ts +563 -0
  311. package/src/lib/floor-footprints.ts +395 -0
  312. package/src/lib/floor-foundation-support.test.ts +284 -0
  313. package/src/lib/floor-opening-draft.ts +218 -0
  314. package/src/lib/floor-openings.test.ts +216 -0
  315. package/src/lib/floor-region-geometry.ts +191 -0
  316. package/src/lib/floor-region-session.test.ts +485 -0
  317. package/src/lib/floor-region-session.ts +589 -0
  318. package/src/lib/floor-region-snap.ts +101 -0
  319. package/src/lib/floor-step-hover.test.ts +32 -0
  320. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  321. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  322. package/src/lib/floorplan/dimension-drive.ts +479 -0
  323. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  324. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  325. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  326. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  327. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  328. package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
  329. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
  330. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
  331. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  332. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  333. package/src/lib/floorplan/geometry.ts +13 -6
  334. package/src/lib/floorplan/index.ts +0 -9
  335. package/src/lib/floorplan/items.ts +5 -2
  336. package/src/lib/floorplan/marks.test.ts +213 -0
  337. package/src/lib/floorplan/marks.ts +238 -0
  338. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  339. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  340. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  341. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  342. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  343. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  344. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  345. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  346. package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
  347. package/src/lib/floorplan/schedules.test.ts +189 -0
  348. package/src/lib/floorplan/schedules.ts +339 -0
  349. package/src/lib/floorplan/sheet-export.test.ts +124 -0
  350. package/src/lib/floorplan/sheet-export.ts +318 -0
  351. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
  352. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  353. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  354. package/src/lib/floorplan/site-plan/coverage.ts +230 -0
  355. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  356. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  357. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  358. package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
  359. package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
  360. package/src/lib/floorplan/site-plan/geometry.ts +142 -0
  361. package/src/lib/floorplan/site-plan/index.ts +55 -0
  362. package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
  363. package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
  364. package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
  365. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  366. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  367. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  368. package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
  369. package/src/lib/floorplan/types.ts +1 -27
  370. package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
  371. package/src/lib/follow-pascal-exterior.test.ts +171 -0
  372. package/src/lib/follow-pascal-exterior.ts +39 -0
  373. package/src/lib/follow-pascal-presentation.test.ts +203 -0
  374. package/src/lib/follow-pascal-presentation.ts +129 -0
  375. package/src/lib/follow-pascal-sight.ts +84 -0
  376. package/src/lib/follow-pascal-targets.test.ts +406 -0
  377. package/src/lib/follow-pascal-targets.ts +198 -0
  378. package/src/lib/follow-pascal.test.ts +350 -0
  379. package/src/lib/follow-pascal.ts +284 -0
  380. package/src/lib/fresh-planar-placement.test.ts +148 -0
  381. package/src/lib/fresh-planar-placement.ts +114 -37
  382. package/src/lib/geometry-script/author.ts +157 -0
  383. package/src/lib/geometry-script/client.ts +159 -0
  384. package/src/lib/geometry-script/finish-scripts.ts +127 -0
  385. package/src/lib/geometry-script/protocol.ts +13 -0
  386. package/src/lib/gesture-lifecycle.test.ts +595 -0
  387. package/src/lib/gesture-lifecycle.ts +290 -0
  388. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  389. package/src/lib/glb-export.test.ts +636 -126
  390. package/src/lib/glb-export.ts +241 -120
  391. package/src/lib/history.test.ts +18 -7
  392. package/src/lib/history.ts +7 -0
  393. package/src/lib/hud-title.ts +109 -0
  394. package/src/lib/hydration-gated-storage.ts +49 -0
  395. package/src/lib/ifc-export.test.ts +174 -0
  396. package/src/lib/ifc-export.ts +201 -0
  397. package/src/lib/ifc-parts.ts +15 -0
  398. package/src/lib/inspector-expanded.test.ts +44 -0
  399. package/src/lib/inspector-expanded.ts +36 -0
  400. package/src/lib/interaction/scope.ts +22 -0
  401. package/src/lib/keyboard-pan.test.ts +129 -7
  402. package/src/lib/keyboard-pan.ts +161 -12
  403. package/src/lib/level-selection.test.ts +57 -0
  404. package/src/lib/level-selection.ts +16 -1
  405. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  406. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  407. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  408. package/src/lib/lot/clean-ring.test.ts +187 -0
  409. package/src/lib/lot/clean-ring.ts +217 -0
  410. package/src/lib/lot/dossier.test.ts +202 -0
  411. package/src/lib/lot/dossier.ts +498 -0
  412. package/src/lib/lot/drop-in.test.ts +326 -0
  413. package/src/lib/lot/drop-in.ts +392 -0
  414. package/src/lib/lot/index.ts +43 -0
  415. package/src/lib/lot/lot-patch.test.ts +340 -0
  416. package/src/lib/lot/lot-patch.ts +394 -0
  417. package/src/lib/lot/parcel-provider.ts +43 -0
  418. package/src/lib/lot/terrain.test.ts +161 -0
  419. package/src/lib/lot/terrain.ts +266 -0
  420. package/src/lib/material-catalog-model.ts +3 -1
  421. package/src/lib/material-paint-panel-model.ts +9 -5
  422. package/src/lib/material-paint.ts +4 -0
  423. package/src/lib/measurement-kind.test.ts +6 -8
  424. package/src/lib/measurements.test.ts +23 -0
  425. package/src/lib/mezzanine-actions.test.ts +342 -0
  426. package/src/lib/mezzanine-draft.test.ts +501 -0
  427. package/src/lib/mezzanine-draft.ts +278 -0
  428. package/src/lib/mezzanine-messages.ts +26 -0
  429. package/src/lib/mezzanine-selection.test.ts +207 -0
  430. package/src/lib/mezzanine-stairs.ts +46 -0
  431. package/src/lib/model-export.ts +3 -1
  432. package/src/lib/node-action-movement.ts +17 -6
  433. package/src/lib/node-display-label.ts +37 -0
  434. package/src/lib/orbit-floor-clearance.test.ts +201 -0
  435. package/src/lib/orbit-floor-clearance.ts +154 -0
  436. package/src/lib/paint-commit-route.test.ts +37 -0
  437. package/src/lib/paint-commit-route.ts +17 -0
  438. package/src/lib/paint-eyedropper.test.tsx +165 -0
  439. package/src/lib/paint-eyedropper.ts +99 -0
  440. package/src/lib/paint-pass-through.test.ts +122 -0
  441. package/src/lib/paint-pass-through.ts +78 -0
  442. package/src/lib/paint-region-hover.ts +25 -0
  443. package/src/lib/paint-region-mode.ts +154 -0
  444. package/src/lib/paint-regions.test.ts +166 -0
  445. package/src/lib/paint-regions.ts +192 -0
  446. package/src/lib/paint-scope.test.ts +317 -35
  447. package/src/lib/paint-scope.ts +267 -25
  448. package/src/lib/panel-rows.ts +21 -0
  449. package/src/lib/parametric-node-update.ts +16 -7
  450. package/src/lib/plate-paint-affected.test.ts +300 -0
  451. package/src/lib/plate-paint-affected.ts +177 -0
  452. package/src/lib/plugin-chat.ts +65 -0
  453. package/src/lib/plugin-panels.test.ts +25 -0
  454. package/src/lib/plugin-panels.ts +94 -0
  455. package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
  456. package/src/lib/point-ask/__fixtures__/house.ts +81 -0
  457. package/src/lib/point-ask/ask-created.test.ts +195 -0
  458. package/src/lib/point-ask/ask-flow.test.ts +380 -0
  459. package/src/lib/point-ask/build-context.test.ts +173 -0
  460. package/src/lib/point-ask/build-context.ts +94 -0
  461. package/src/lib/point-ask/capture-crop.ts +129 -0
  462. package/src/lib/point-ask/choreography.ts +69 -0
  463. package/src/lib/point-ask/compare.test.ts +145 -0
  464. package/src/lib/point-ask/compare.ts +73 -0
  465. package/src/lib/point-ask/describe-target.test.ts +165 -0
  466. package/src/lib/point-ask/describe-target.ts +234 -0
  467. package/src/lib/point-ask/dictation.ts +79 -0
  468. package/src/lib/point-ask/edge-marker.test.ts +61 -0
  469. package/src/lib/point-ask/edge-marker.ts +27 -0
  470. package/src/lib/point-ask/frame-loop.ts +25 -0
  471. package/src/lib/point-ask/history-marks.test.ts +94 -0
  472. package/src/lib/point-ask/history-marks.ts +39 -0
  473. package/src/lib/point-ask/host-api.ts +32 -0
  474. package/src/lib/point-ask/outline-box.test.ts +30 -0
  475. package/src/lib/point-ask/outline-box.ts +11 -0
  476. package/src/lib/point-ask/overlay-metrics.ts +6 -0
  477. package/src/lib/point-ask/pick.test.ts +220 -0
  478. package/src/lib/point-ask/pick.ts +121 -0
  479. package/src/lib/point-ask/pin-machine.test.ts +494 -0
  480. package/src/lib/point-ask/pin-machine.ts +217 -0
  481. package/src/lib/point-ask/placement.test.ts +203 -0
  482. package/src/lib/point-ask/placement.ts +141 -0
  483. package/src/lib/point-ask/pointer-tracker.ts +34 -0
  484. package/src/lib/point-ask/projector.ts +83 -0
  485. package/src/lib/point-ask/region.test.ts +157 -0
  486. package/src/lib/point-ask/region.ts +136 -0
  487. package/src/lib/point-ask/retry.test.ts +217 -0
  488. package/src/lib/point-ask/retry.ts +37 -0
  489. package/src/lib/point-ask/scene-commit.test.ts +312 -0
  490. package/src/lib/point-ask/scene-commit.ts +32 -0
  491. package/src/lib/point-ask/session.ts +1391 -0
  492. package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
  493. package/src/lib/point-ask/spring-keyframes.ts +38 -0
  494. package/src/lib/point-ask/spring.test.ts +63 -0
  495. package/src/lib/point-ask/spring.ts +107 -0
  496. package/src/lib/point-ask/suggestions.test.ts +80 -0
  497. package/src/lib/point-ask/suggestions.ts +125 -0
  498. package/src/lib/point-ask/target-scope.test.ts +622 -0
  499. package/src/lib/point-ask/target-scope.ts +338 -0
  500. package/src/lib/point-ask/types.ts +48 -0
  501. package/src/lib/portable-export.test.ts +34 -1
  502. package/src/lib/portable-export.ts +119 -53
  503. package/src/lib/print-feature-thickness.ts +10 -1
  504. package/src/lib/print-roof-solids.test.ts +39 -1
  505. package/src/lib/print-roof-solids.ts +43 -10
  506. package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
  507. package/src/lib/print-shell-compiler.ts +1 -0
  508. package/src/lib/registered-draft-snap.ts +12 -0
  509. package/src/lib/roof-duplication.ts +10 -2
  510. package/src/lib/room-assembly-overlay.test.ts +275 -0
  511. package/src/lib/room-assembly-overlay.ts +265 -0
  512. package/src/lib/room-built-on.test.ts +379 -0
  513. package/src/lib/room-built-on.ts +138 -0
  514. package/src/lib/room-construction-commands.ts +712 -0
  515. package/src/lib/room-construction.test.ts +746 -0
  516. package/src/lib/room-construction.ts +385 -0
  517. package/src/lib/room-controls-phase6.test.ts +630 -0
  518. package/src/lib/room-divide-session.ts +523 -0
  519. package/src/lib/room-handle-drag.ts +1068 -0
  520. package/src/lib/room-name-catalog.test.ts +90 -0
  521. package/src/lib/room-name-catalog.ts +168 -0
  522. package/src/lib/room-push-dimensions.test.ts +311 -0
  523. package/src/lib/room-push-dimensions.ts +251 -0
  524. package/src/lib/room-selection-commands.ts +62 -0
  525. package/src/lib/room-selection.test.ts +1017 -0
  526. package/src/lib/room-selection.ts +417 -0
  527. package/src/lib/room-structure-commands.ts +94 -0
  528. package/src/lib/room-structure.test.ts +921 -0
  529. package/src/lib/room-transform-session.ts +570 -0
  530. package/src/lib/room-zone-routing.test.ts +294 -0
  531. package/src/lib/room-zone-routing.ts +197 -0
  532. package/src/lib/scene-clipboard.test.ts +610 -1
  533. package/src/lib/scene-clipboard.ts +269 -58
  534. package/src/lib/scene-material-refs.test.ts +79 -0
  535. package/src/lib/scene-material-refs.ts +79 -0
  536. package/src/lib/scene.test.ts +24 -36
  537. package/src/lib/scene.ts +33 -57
  538. package/src/lib/selection-routing.test.ts +129 -45
  539. package/src/lib/selection-routing.ts +54 -75
  540. package/src/lib/session-writes.test.ts +120 -0
  541. package/src/lib/session-writes.ts +205 -0
  542. package/src/lib/sfx-bus.ts +13 -0
  543. package/src/lib/sfx-player.test.ts +70 -9
  544. package/src/lib/sfx-player.ts +6 -0
  545. package/src/lib/sidebar-panel.ts +62 -0
  546. package/src/lib/site-boundary.test.ts +4 -6
  547. package/src/lib/site-boundary.ts +1 -1
  548. package/src/lib/snapping-mode.test.ts +4 -0
  549. package/src/lib/snapping-mode.ts +8 -1
  550. package/src/lib/stair-levels.test.ts +25 -0
  551. package/src/lib/stair-levels.ts +22 -3
  552. package/src/lib/terrace-draft.test.ts +145 -0
  553. package/src/lib/terrace-draft.ts +214 -0
  554. package/src/lib/tool-cursor-icon.test.ts +16 -0
  555. package/src/lib/tool-cursor-icon.ts +11 -0
  556. package/src/lib/typing-target.ts +9 -0
  557. package/src/lib/units.ts +77 -39
  558. package/src/lib/vector-edges.test.ts +58 -0
  559. package/src/lib/vector-edges.ts +400 -0
  560. package/src/lib/viewer-selection.test.ts +577 -0
  561. package/src/lib/viewer-selection.ts +580 -0
  562. package/src/lib/wall-draw-variant.test.ts +71 -0
  563. package/src/lib/wall-draw-variant.ts +91 -0
  564. package/src/lib/wall-paint-region-session.ts +246 -0
  565. package/src/lib/wall-paint-region.test.ts +373 -0
  566. package/src/lib/wall-placement-pointer.test.ts +42 -0
  567. package/src/lib/wall-placement-pointer.ts +57 -0
  568. package/src/lib/wall-polygon-draft.test.ts +262 -0
  569. package/src/lib/wall-polygon-draft.ts +286 -0
  570. package/src/lib/wall-push.test.ts +453 -0
  571. package/src/lib/wall-region-face.ts +352 -0
  572. package/src/lib/wall-region-handles.test.ts +304 -0
  573. package/src/lib/wall-region-handles.ts +368 -0
  574. package/src/lib/wall-region-list.test.ts +119 -0
  575. package/src/lib/wall-region-list.ts +136 -0
  576. package/src/lib/wall-region-snap.ts +110 -0
  577. package/src/lib/window-changes.ts +35 -0
  578. package/src/lib/window-interaction.ts +14 -0
  579. package/src/lib/zone-content.ts +4 -1
  580. package/src/lib/zone-name.test.ts +17 -0
  581. package/src/lib/zone-name.ts +17 -0
  582. package/src/store/opens-in-select-mode.test.ts +223 -0
  583. package/src/store/paint-mode.test.ts +188 -0
  584. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  585. package/src/store/tool-mode.test.ts +137 -6
  586. package/src/store/use-audio.test.ts +146 -0
  587. package/src/store/use-audio.tsx +127 -3
  588. package/src/store/use-construction-reveal.test.ts +173 -0
  589. package/src/store/use-construction-reveal.ts +145 -0
  590. package/src/store/use-corner-snap-hint.test.ts +24 -0
  591. package/src/store/use-corner-snap-hint.ts +47 -0
  592. package/src/store/use-delete-confirmation.ts +28 -1
  593. package/src/store/use-drawing-view.test.ts +24 -3
  594. package/src/store/use-drawing-view.ts +29 -1
  595. package/src/store/use-editor.tsx +432 -321
  596. package/src/store/use-floorplan-draft-preview.ts +13 -0
  597. package/src/store/use-floorplan-marquee.ts +12 -2
  598. package/src/store/use-follow-pascal.test.ts +62 -0
  599. package/src/store/use-follow-pascal.ts +53 -0
  600. package/src/store/use-point-ask.ts +247 -0
  601. package/src/store/use-quick-measurement-hud.ts +3 -3
  602. package/src/store/use-stair-build-preview.ts +33 -5
  603. package/src/store/use-wall-move-ghosts.ts +4 -2
  604. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  605. package/src/store/use-wall-region-handles-selection.ts +139 -0
  606. package/src/store/view-layout.test.ts +201 -0
  607. package/src/store/view-layout.ts +199 -0
  608. package/src/test-utils/immediate-animation-frames.ts +16 -0
  609. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  610. package/src/test-utils/render-current-markup.tsx +16 -0
  611. package/src/test-utils/selection-harness.ts +82 -0
  612. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  613. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  614. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  615. package/src/components/tools/fence/fence-drafting.ts +0 -266
  616. package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
  617. package/src/components/tools/stair/stair-click-guard.ts +0 -72
  618. package/src/components/tools/stair/stair-defaults.ts +0 -18
  619. package/src/components/tools/stair/stair-tool.tsx +0 -634
  620. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  621. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  622. package/src/lib/floorplan/stairs.ts +0 -489
  623. package/src/lib/level-duplication.test.ts +0 -144
  624. package/src/lib/level-duplication.ts +0 -195
@@ -1,14 +1,7 @@
1
1
  import '../../../three-types'
2
2
 
3
3
  import { Icon } from '@iconify/react'
4
- import {
5
- type AnyNodeId,
6
- emitter,
7
- type GridEvent,
8
- sceneRegistry,
9
- useScene,
10
- type ZoneNode,
11
- } from '@pascal-app/core'
4
+ import { type AnyNodeId, emitter, type GridEvent, sceneRegistry, useScene } from '@pascal-app/core'
12
5
  import { useViewer } from '@pascal-app/viewer'
13
6
  import { useThree } from '@react-three/fiber'
14
7
  import { useCallback, useEffect, useRef } from 'react'
@@ -26,9 +19,10 @@ import {
26
19
  Vector2,
27
20
  Vector3,
28
21
  } from 'three'
22
+ import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
29
23
  import { EDITOR_LAYER } from '../../../lib/constants'
30
24
  import { sfxEmitter } from '../../../lib/sfx-bus'
31
- import useEditor from '../../../store/use-editor'
25
+ import useInteractionScope from '../../../store/use-interaction-scope'
32
26
  import { CursorSphere } from '../shared/cursor-sphere'
33
27
  import { isBoxSelectPointerSuppressed, markBoxSelectHandled } from './box-select-state'
34
28
  import { collectSelectableCandidateIds } from './select-candidates'
@@ -297,6 +291,7 @@ export const PlaneBoxSelectTool: React.FC = () => {
297
291
  const currentPoint = useRef(new Vector3())
298
292
  const pointerDown = useRef(false)
299
293
  const isDragging = useRef(false)
294
+ const ownedGesture = useRef<object | null>(null)
300
295
  const startClientX = useRef(0)
301
296
  const startClientY = useRef(0)
302
297
  const gridY = useRef(0)
@@ -320,11 +315,36 @@ export const PlaneBoxSelectTool: React.FC = () => {
320
315
  const resetDrag = useCallback(() => {
321
316
  pointerDown.current = false
322
317
  isDragging.current = false
323
- rectFillRef.current.visible = false
318
+ const gesture = ownedGesture.current
319
+ if (gesture)
320
+ useInteractionScope.getState().endIf(() => useInteractionScope.getState().gesture === gesture)
321
+ ownedGesture.current = null
322
+ previousGridPosition.current = null
323
+ if (rectFillRef.current) rectFillRef.current.visible = false
324
324
  outlineRef.current.visible = false
325
325
  syncPreviewSelectedIds([])
326
326
  }, [syncPreviewSelectedIds])
327
327
 
328
+ useEffect(() => {
329
+ const onCancel = () => {
330
+ if (!pointerDown.current) return
331
+ if (
332
+ (!ownedGesture.current && useInteractionScope.getState().scope.kind === 'idle') ||
333
+ (ownedGesture.current && useInteractionScope.getState().gesture === ownedGesture.current)
334
+ ) {
335
+ markToolCancelConsumed()
336
+ }
337
+ resetDrag()
338
+ }
339
+ emitter.on('tool:cancel', onCancel)
340
+ window.addEventListener('pointercancel', resetDrag)
341
+ return () => {
342
+ emitter.off('tool:cancel', onCancel)
343
+ window.removeEventListener('pointercancel', resetDrag)
344
+ resetDrag()
345
+ }
346
+ }, [resetDrag])
347
+
328
348
  const raycastToGround = useCallback(
329
349
  (event: PointerEvent): Vector3 | null => {
330
350
  const rect = gl.domElement.getBoundingClientRect()
@@ -342,12 +362,11 @@ export const PlaneBoxSelectTool: React.FC = () => {
342
362
  useEffect(() => {
343
363
  const outline = outlineRef.current
344
364
  return () => {
345
- previewSelectedIdsRef.current = []
346
- setPreviewSelectedIds([])
365
+ resetDrag()
347
366
  outline.geometry.dispose()
348
367
  ;(outline.material as LineBasicMaterial).dispose()
349
368
  }
350
- }, [setPreviewSelectedIds])
369
+ }, [resetDrag])
351
370
 
352
371
  useEffect(() => {
353
372
  const unsubscribe = useViewer.subscribe((state) => {
@@ -440,15 +459,7 @@ export const PlaneBoxSelectTool: React.FC = () => {
440
459
 
441
460
  const ids = collectNodeIdsInPlaneBounds(bounds)
442
461
  const shouldAppend = event.metaKey || event.ctrlKey || event.shiftKey
443
- const { phase, structureLayer } = useEditor.getState()
444
-
445
- if (phase === 'structure' && structureLayer === 'zones') {
446
- if (ids.length > 0) {
447
- useViewer.getState().setSelection({ zoneId: ids[0] as ZoneNode['id'] })
448
- } else if (!shouldAppend) {
449
- useViewer.getState().setSelection({ zoneId: null })
450
- }
451
- } else if (shouldAppend) {
462
+ if (shouldAppend) {
452
463
  const currentIds = useViewer.getState().selection.selectedIds
453
464
  useViewer.getState().setSelection({
454
465
  selectedIds: Array.from(new Set([...currentIds, ...ids])),
@@ -507,6 +518,8 @@ export const PlaneBoxSelectTool: React.FC = () => {
507
518
  const dy = nativeEvent.clientY - startClientY.current
508
519
  if (!isDragging.current && Math.hypot(dx, dy) >= DRAG_THRESHOLD_PX) {
509
520
  isDragging.current = true
521
+ useInteractionScope.getState().begin({ kind: 'box-select' })
522
+ ownedGesture.current = useInteractionScope.getState().gesture
510
523
  }
511
524
 
512
525
  if (isDragging.current && rectFillRef.current && outlineRef.current) {
@@ -47,10 +47,7 @@ describe('selectable candidates', () => {
47
47
  },
48
48
  previewSelectedIds: [],
49
49
  })
50
- useEditor.setState({
51
- phase: 'structure',
52
- structureLayer: 'elements',
53
- })
50
+ useEditor.setState({ phase: 'building' })
54
51
  })
55
52
 
56
53
  test('includes building-scoped elevators for the active level building', () => {
@@ -106,4 +103,40 @@ describe('selectable candidates', () => {
106
103
 
107
104
  expect(collectSelectableCandidateIds()).toContain('elevator_test')
108
105
  })
106
+
107
+ test('Building box selection includes structural and furnishing kinds on the active level', () => {
108
+ for (const category of ['structure', 'furnish'] as const) {
109
+ const kind = `box-select-test-${category}`
110
+ if (!nodeRegistry.has(kind)) {
111
+ registerNode({
112
+ kind,
113
+ schemaVersion: 1,
114
+ schema: z.object({ type: z.literal(kind) }) as never,
115
+ category,
116
+ defaults: () => ({}),
117
+ capabilities: { selectable: {} },
118
+ } as AnyNodeDefinition)
119
+ }
120
+ }
121
+ useScene.setState({
122
+ nodes: {
123
+ level_test: {
124
+ id: 'level_test',
125
+ type: 'level',
126
+ parentId: 'building_test',
127
+ children: ['wall_pick', 'item_pick', 'item_hidden'],
128
+ },
129
+ wall_pick: { id: 'wall_pick', type: 'box-select-test-structure', parentId: 'level_test' },
130
+ item_pick: { id: 'item_pick', type: 'box-select-test-furnish', parentId: 'level_test' },
131
+ item_hidden: {
132
+ id: 'item_hidden',
133
+ type: 'box-select-test-furnish',
134
+ parentId: 'level_test',
135
+ visible: false,
136
+ },
137
+ item_other: { id: 'item_other', type: 'box-select-test-furnish', parentId: 'level_other' },
138
+ } as unknown as Record<string, AnyNode>,
139
+ })
140
+ expect(collectSelectableCandidateIds().sort()).toEqual(['item_pick', 'wall_pick'])
141
+ })
109
142
  })
@@ -19,17 +19,29 @@ function isVisibleSelectableNode(node: AnyNode): boolean {
19
19
  export function collectSelectableCandidateIds(): string[] {
20
20
  const { levelId } = useViewer.getState().selection
21
21
  const { nodes } = useScene.getState()
22
- const { phase, structureLayer } = useEditor.getState()
22
+ const { phase } = useEditor.getState()
23
23
  const result: string[] = []
24
24
  const seen = new Set<string>()
25
25
  const addNode = (node: AnyNode | undefined) => {
26
- if (!node || seen.has(node.id) || (node as { visible?: boolean }).visible === false) return
26
+ if (
27
+ !node ||
28
+ node.type === 'zone' ||
29
+ seen.has(node.id) ||
30
+ (node as { visible?: boolean }).visible === false
31
+ )
32
+ return
27
33
  seen.add(node.id)
28
34
  result.push(node.id)
29
35
  }
30
36
  const visitLevelDescendant = (id: AnyNodeId) => {
31
37
  const node = nodes[id]
32
- if (!node || seen.has(node.id) || (node as { visible?: boolean }).visible === false) return
38
+ if (
39
+ !node ||
40
+ node.type === 'zone' ||
41
+ seen.has(node.id) ||
42
+ (node as { visible?: boolean }).visible === false
43
+ )
44
+ return
33
45
 
34
46
  if (isRegistrySelectable(node.type)) {
35
47
  addNode(node)
@@ -52,14 +64,6 @@ export function collectSelectableCandidateIds(): string[] {
52
64
  const levelNode = nodes[levelId as AnyNodeId] as LevelNode | undefined
53
65
  if (levelNode?.type !== 'level') return []
54
66
 
55
- if (phase === 'structure' && structureLayer === 'zones') {
56
- for (const childId of levelNode.children) {
57
- const node = nodes[childId as AnyNodeId]
58
- if (node?.type === 'zone') addNode(node)
59
- }
60
- return result
61
- }
62
-
63
67
  for (const childId of levelNode.children) {
64
68
  visitLevelDescendant(childId as AnyNodeId)
65
69
  }
@@ -1,10 +1,11 @@
1
1
  import { Html } from '@react-three/drei'
2
2
  import type { ThreeElements } from '@react-three/fiber'
3
3
  import { forwardRef } from 'react'
4
- import type { Group } from 'three'
4
+ import { CircleGeometry, CylinderGeometry, type Group, SphereGeometry } from 'three'
5
5
  import { furnishTools } from '../../../components/ui/action-menu/furnish-tools'
6
6
  import { tools } from '../../../components/ui/action-menu/structure-tools'
7
7
  import { EDITOR_LAYER } from '../../../lib/constants'
8
+ import { wallCursorIcon } from '../../../lib/tool-cursor-icon'
8
9
  import useEditor from '../../../store/use-editor'
9
10
  import useWallSnapIndicator from '../../../store/use-wall-snap-indicator'
10
11
 
@@ -25,6 +26,19 @@ interface CursorSphereProps extends Omit<ThreeElements['group'], 'ref'> {
25
26
  tooltipContent?: React.ReactNode
26
27
  }
27
28
 
29
+ // One geometry each for the life of the module: the vertical line used to
30
+ // be a `<cylinderGeometry args={[…, height, 8]}>` rebuilt on EVERY height
31
+ // change (the cursor's drop to the floor moves with the pointer), and the
32
+ // ring / dot / tip were built and disposed on every toggle — WebGPU then
33
+ // drew a frame whose vertex buffers had just been destroyed ("Vertex
34
+ // buffer slot 0 required by [RenderPipeline "renderPipeline_MeshBasicMaterial_…"]
35
+ // was not set … DrawIndexed(96 …)" — the 8-segment cylinder's 96 indices).
36
+ // The line is a unit cylinder scaled to `height` instead.
37
+ const DOT_GEOMETRY = new CircleGeometry(0.06, 32)
38
+ const RING_GEOMETRY = new CircleGeometry(0.2, 32)
39
+ const LINE_GEOMETRY = new CylinderGeometry(0.01, 0.01, 1, 8)
40
+ const TIP_GEOMETRY = new SphereGeometry(0.08, 20, 14)
41
+
28
42
  export const CursorSphere = forwardRef<Group, CursorSphereProps>(function CursorSphere(
29
43
  {
30
44
  color = '#818cf8',
@@ -45,11 +59,14 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
45
59
  // (green square at a corner); hide the cursor's ground dot/ring so it
46
60
  // doesn't sit on top of that glyph.
47
61
  const isSnapping = useWallSnapIndicator((s) => s.point !== null)
62
+ const wallMode = useEditor((s) => s.continuationByContext.wall)
48
63
 
49
- // Find the icon for the current tool
50
- let activeToolConfig = null
64
+ // Find the icon for the current tool — the wall shows its Rooms variant.
65
+ let activeToolConfig: { label: string; iconSrc: string } | null | undefined = null
51
66
  if (mode === 'build' && tool) {
52
- if (tool === 'item' && catalogCategory) {
67
+ if (tool === 'wall') {
68
+ activeToolConfig = wallCursorIcon(wallMode)
69
+ } else if (tool === 'item' && catalogCategory) {
53
70
  activeToolConfig = furnishTools.find((t) => t.catalogCategory === catalogCategory)
54
71
  } else {
55
72
  activeToolConfig = tools.find((t) => t.id === tool)
@@ -67,8 +84,7 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
67
84
  <group rotation={[-Math.PI / 2, 0, 0]}>
68
85
  {/* Center dot — at the ground unless the placement point is elevated */}
69
86
  {!dotAtTip && (
70
- <mesh layers={EDITOR_LAYER} renderOrder={2}>
71
- <circleGeometry args={[0.06, 32]} />
87
+ <mesh geometry={DOT_GEOMETRY} layers={EDITOR_LAYER} renderOrder={2}>
72
88
  <meshBasicMaterial
73
89
  color={color}
74
90
  depthTest={false}
@@ -80,8 +96,7 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
80
96
  )}
81
97
 
82
98
  {/* Outer ring / glow */}
83
- <mesh layers={EDITOR_LAYER} renderOrder={2}>
84
- <circleGeometry args={[0.2, 32]} />
99
+ <mesh geometry={RING_GEOMETRY} layers={EDITOR_LAYER} renderOrder={2}>
85
100
  <meshBasicMaterial
86
101
  color={color}
87
102
  depthTest={false}
@@ -95,8 +110,13 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
95
110
 
96
111
  {/* Vertical line */}
97
112
  {height > 0 && (
98
- <mesh layers={EDITOR_LAYER} position={[0, height / 2, 0]} renderOrder={2}>
99
- <cylinderGeometry args={[0.01, 0.01, height, 8]} />
113
+ <mesh
114
+ geometry={LINE_GEOMETRY}
115
+ layers={EDITOR_LAYER}
116
+ position={[0, height / 2, 0]}
117
+ renderOrder={2}
118
+ scale={[1, height, 1]}
119
+ >
100
120
  <meshBasicMaterial
101
121
  color={color}
102
122
  depthTest={false}
@@ -110,8 +130,12 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
110
130
  {/* Bright marker dot at the tip of the line — the actual placement
111
131
  point, riding at the cursor while the line drops to the floor. */}
112
132
  {dotAtTip && height > 0 && (
113
- <mesh layers={EDITOR_LAYER} position={[0, height, 0]} renderOrder={2}>
114
- <sphereGeometry args={[0.08, 20, 14]} />
133
+ <mesh
134
+ geometry={TIP_GEOMETRY}
135
+ layers={EDITOR_LAYER}
136
+ position={[0, height, 0]}
137
+ renderOrder={2}
138
+ >
115
139
  <meshBasicMaterial color={color} depthTest={false} depthWrite={false} />
116
140
  </mesh>
117
141
  )}
@@ -458,8 +458,7 @@ export const SiteBoundaryEditor: React.FC = () => {
458
458
 
459
459
  const exitSiteEditing = useCallback(() => {
460
460
  const editor = useEditor.getState()
461
- editor.setPhase('structure')
462
- editor.setStructureLayer('elements')
461
+ editor.setPhase('building')
463
462
  editor.setMode('select')
464
463
  }, [])
465
464
 
@@ -5,17 +5,24 @@ import {
5
5
  type CeilingNode,
6
6
  createSceneApi,
7
7
  type FenceNode,
8
+ isStoreyContent,
9
+ type LevelNode,
8
10
  nodeRegistry,
9
11
  type SlabNode,
12
+ storeyOf,
10
13
  useScene,
11
14
  type WallNode,
15
+ type ZoneNode,
12
16
  } from '@pascal-app/core'
13
17
  import { useViewer } from '@pascal-app/viewer'
14
- import { type ComponentType, lazy, Suspense, useMemo } from 'react'
18
+ import { type ComponentType, lazy, Suspense, useEffect, useMemo } from 'react'
15
19
  import { useRegisteredToolEnabled } from '../../hooks/use-registered-tool-enabled'
20
+ import { useCeilingEditCeilingId } from '../../lib/ceiling-edit-session'
16
21
  import type { ReshapeKind } from '../../lib/interaction/scope'
22
+ import { selectPlacedNode } from '../../lib/level-selection'
23
+ import { paintRegionTargets, usePaintRegionMode } from '../../lib/paint-region-mode'
17
24
  import { siteBoundaryHandlesEnabled } from '../../lib/site-boundary'
18
- import useEditor, { type Phase, type Tool } from '../../store/use-editor'
25
+ import useEditor, { type Tool } from '../../store/use-editor'
19
26
  import useInteractionScope, {
20
27
  useControlPointReshape,
21
28
  useEditingHole,
@@ -29,10 +36,14 @@ import useInteractionScope, {
29
36
  } from '../../store/use-interaction-scope'
30
37
  import { Alignment3DGuideLayer } from '../editor/alignment-3d-guide-layer'
31
38
  import { Elevation3DGuideLayer } from '../editor/elevation-3d-guide-layer'
39
+ import { FloorOpeningsOverlay3D } from '../editor/floor-openings-overlay'
40
+ import { FloorRegionControls3D } from '../editor/floor-region-controls'
41
+ import { OpenWallEnds3DLayer } from '../editor/open-wall-ends-3d-layer'
32
42
  import { OpeningGuides3DLayer } from '../editor/opening-guides-3d-layer'
33
43
  import { WallSnapBeaconLayer } from '../editor/wall-snap-beacon-layer'
34
44
  import { ElevatorTool } from './elevator/elevator-tool'
35
45
  import { MoveTool } from './item/move-tool'
46
+ import { WallPaintRegionTool } from './paint-region/wall-paint-region-tool'
36
47
  import { RegistryToolProvider } from './registry-tool-context'
37
48
  import {
38
49
  getRegistryAffordanceTool,
@@ -41,7 +52,6 @@ import {
41
52
  import { FacingPoseIndicator } from './shared/facing-pose-indicator'
42
53
  import { SiteBoundaryEditor } from './site/site-boundary-editor'
43
54
  import { TerrainSculptTool } from './site/terrain-sculpt-tool'
44
- import { StairTool } from './stair/stair-tool'
45
55
  import { ZoneBoundaryEditor } from './zone/zone-boundary-editor'
46
56
  import { ZoneTool } from './zone/zone-tool'
47
57
 
@@ -100,21 +110,16 @@ function getRegistryTool(tool: Tool | null): ComponentType | null {
100
110
  // Legacy tool fallbacks — kinds whose placement tools haven't migrated
101
111
  // to `def.tool` yet. Wall / fence / slab / ceiling / door / window /
102
112
  // item / shelf / spawn now go through the registry path above.
103
- const tools: Record<Phase, Partial<Record<Tool, React.FC>>> = {
104
- site: {
105
- 'property-line': SiteBoundaryEditor,
106
- },
107
- structure: {
108
- stair: StairTool,
109
- zone: ZoneTool,
110
- },
111
- furnish: {},
113
+ const tools: Partial<Record<Tool, React.FC>> = {
114
+ 'property-line': SiteBoundaryEditor,
115
+ zone: ZoneTool,
112
116
  }
113
117
 
114
118
  export const ToolManager: React.FC = () => {
115
119
  const phase = useEditor((state) => state.phase)
116
120
  const mode = useEditor((state) => state.mode)
117
121
  const tool = useEditor((state) => state.tool)
122
+ const paintsWallRegions = usePaintRegionMode((state) => paintRegionTargets(state.mode).wall)
118
123
  const registeredToolEnabled = useRegisteredToolEnabled(tool)
119
124
  const movingNode = useMovingNode()
120
125
  const registryToolOwnsPlacement = useInteractionScope(
@@ -157,23 +162,41 @@ export const ToolManager: React.FC = () => {
157
162
  }
158
163
  }, [reshapingNode, tangentReshape])
159
164
  const editingHole = useEditingHole()
160
- const selectedZoneId = useViewer((state) => state.selection.zoneId)
165
+ const editCeilingId = useCeilingEditCeilingId()
166
+ // A zone picked as a zone, from the Scene list (zoneId) or its area or pill (sole selection).
167
+ const zoneSelectionId = useViewer((state) => state.selection.zoneId)
168
+ const soleSelectedId = useViewer((state) =>
169
+ state.selection.selectedIds.length === 1 ? state.selection.selectedIds[0] : undefined,
170
+ )
171
+ const soleSelectedZone = useScene((state) =>
172
+ soleSelectedId && state.nodes[soleSelectedId as AnyNodeId]?.type === 'zone'
173
+ ? (soleSelectedId as ZoneNode['id'])
174
+ : null,
175
+ )
176
+ const selectedZoneId = zoneSelectionId ?? soleSelectedZone
161
177
  const selectedIds = useViewer((state) => state.selection.selectedIds)
162
178
  const buildingId = useViewer((state) => state.selection.buildingId)
163
179
  const activeLevelId = useViewer((state) => state.selection.levelId)
164
180
  const unit = useViewer((state) => state.unit)
165
181
  const setSelection = useViewer((state) => state.setSelection)
166
182
  const nodes = useScene((state) => state.nodes)
183
+ // Placing a roof makes its level active (selectPlacedNode); a wall, room, slab, stair or item tool
184
+ // armed there, then or after a level change, draws on the storey the roof level covers.
185
+ useEffect(() => {
186
+ if (!(tool && activeLevelId && isStoreyContent(tool, undefined))) return
187
+ const storeyId = storeyOf(useScene.getState().nodes, activeLevelId)
188
+ if (storeyId !== activeLevelId) setSelection({ levelId: storeyId as LevelNode['id'] })
189
+ }, [tool, activeLevelId, setSelection])
167
190
  const registrySceneApi = useMemo(() => createSceneApi(useScene), [])
168
191
  const registryToolContext = useMemo(
169
192
  () => ({
170
193
  activeLevelId: activeLevelId ?? null,
171
194
  isCameraDragging: () => useViewer.getState().cameraDragging,
172
195
  sceneApi: registrySceneApi,
173
- selectNode: (nodeId: AnyNodeId) => setSelection({ selectedIds: [nodeId] }),
196
+ selectNode: selectPlacedNode,
174
197
  unit,
175
198
  }),
176
- [activeLevelId, registrySceneApi, setSelection, unit],
199
+ [activeLevelId, registrySceneApi, unit],
177
200
  )
178
201
 
179
202
  // Building transform for the local group — all building-relative tools live inside this group
@@ -213,12 +236,14 @@ export const ToolManager: React.FC = () => {
213
236
  // edge handles — mounts only for a sole selection.
214
237
  const isSoleSelection = selectedIds.length === 1
215
238
 
216
- // Show slab boundary editor when in structure/select mode with a slab selected (but not editing a hole)
239
+ // Show slab boundary editor in select mode with a slab selected (but not editing a hole)
217
240
  const showSlabBoundaryEditor =
218
- phase === 'structure' &&
219
241
  mode === 'select' &&
220
242
  isSoleSelection &&
221
243
  selectedSlabId !== undefined &&
244
+ // Derived plates (a footprint's floor, a room's raised plate) follow their
245
+ // rooms; only user-drawn slabs get the outline editor.
246
+ !selectedSlab?.plateRole &&
222
247
  !isFloorplanDrivenReshape &&
223
248
  !editingSlabHoleIsManual
224
249
 
@@ -230,26 +255,28 @@ export const ToolManager: React.FC = () => {
230
255
  !isFloorplanDrivenReshape &&
231
256
  editingSlabHoleIsManual
232
257
 
233
- // Show ceiling boundary editor when in structure/select mode with a ceiling selected (but not editing a hole)
258
+ // Show ceiling boundary editor only inside the ceiling's Edit ceiling session (not while editing a hole)
234
259
  const showCeilingBoundaryEditor =
235
- phase === 'structure' &&
236
260
  mode === 'select' &&
237
261
  isSoleSelection &&
238
262
  selectedCeilingId !== undefined &&
263
+ selectedCeilingId === editCeilingId &&
239
264
  !isFloorplanDrivenReshape &&
240
265
  (!editingHole || editingHole.nodeId !== selectedCeilingId)
241
266
 
242
- // Show ceiling hole editor when editing a hole on the selected ceiling
267
+ // Show ceiling hole editor when editing a manual hole inside the ceiling's Edit ceiling session
243
268
  const showCeilingHoleEditor =
244
269
  selectedCeilingId !== undefined &&
270
+ selectedCeilingId === editCeilingId &&
245
271
  editingHole !== null &&
246
272
  editingHole.nodeId === selectedCeilingId &&
273
+ ((nodes[selectedCeilingId as AnyNodeId] as CeilingNode).holeMetadata?.[editingHole.holeIndex]
274
+ ?.source ?? 'manual') === 'manual' &&
247
275
  !isFloorplanDrivenReshape
248
276
 
249
- // Show zone boundary editor when in structure/select mode with a zone selected
277
+ // Show zone boundary editor in select mode with a zone selected
250
278
  // Hide when editing a slab or ceiling to avoid overlapping handles
251
279
  const showZoneBoundaryEditor =
252
- phase === 'structure' &&
253
280
  mode === 'select' &&
254
281
  selectedZoneId !== null &&
255
282
  !isFloorplanDrivenReshape &&
@@ -274,7 +301,7 @@ export const ToolManager: React.FC = () => {
274
301
  const RegistryToolComponent = showBuildTool ? getRegistryTool(tool) : null
275
302
  const useRegistryTool = RegistryToolComponent != null
276
303
 
277
- const BuildToolComponent = showBuildTool && !useRegistryTool ? tools[phase]?.[tool] : null
304
+ const BuildToolComponent = showBuildTool && !useRegistryTool ? tools[tool] : null
278
305
  const handlePlacedNodeSelected = (nodeId: AnyNodeId) => {
279
306
  setSelection({ selectedIds: [nodeId] })
280
307
  }
@@ -298,10 +325,16 @@ export const ToolManager: React.FC = () => {
298
325
  <RegistryToolProvider value={registryToolContext}>
299
326
  {/* World-space tools: site boundary and building movement operate in world coordinates */}
300
327
  {showSiteBoundaryEditor && <SiteBoundaryEditor />}
301
- {/* Terrain sculpting is a mode rather than a `tools[phase][tool]` entry —
328
+ {/* Terrain sculpting is a mode rather than a `tools[tool]` entry —
302
329
  it places no node — so it gets its own gate here. World-space, because
303
330
  the ground is not building-local. */}
304
331
  {sculpting && <TerrainSculptTool />}
332
+ {/* "Paint part of the floor": level-space, follows the level matrix itself. */}
333
+ <FloorRegionControls3D />
334
+ {/* Floor and ceiling openings: level-space, follows the level matrix itself. */}
335
+ <FloorOpeningsOverlay3D />
336
+ {/* "Paint part of the wall": follows the wall mesh's own matrix. */}
337
+ {mode === 'material-paint' && paintsWallRegions && <WallPaintRegionTool />}
305
338
  {showMover && movingNode?.type === 'building' && (
306
339
  <MoveTool onNodeMoved={handlePlacedNodeSelected} onSpawnMoved={handlePlacedNodeSelected} />
307
340
  )}
@@ -454,6 +487,8 @@ export const ToolManager: React.FC = () => {
454
487
  <Elevation3DGuideLayer />
455
488
  {/* "Magnetic" beacon at the active wall-draft snap point. */}
456
489
  <WallSnapBeaconLayer />
490
+ {/* Wall ends that aren't joined, with a one-click join — twin of the floor plan's. */}
491
+ <OpenWallEnds3DLayer />
457
492
  </group>
458
493
  </RegistryToolProvider>
459
494
  )