@pascal-app/core 0.8.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/dist/events/bus.d.ts +67 -13
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  6. package/dist/hooks/scene-registry/scene-registry.d.ts +14 -23
  7. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  8. package/dist/hooks/scene-registry/scene-registry.js +59 -26
  9. package/dist/hooks/scene-registry/scene-registry.test.d.ts +2 -0
  10. package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +1 -0
  11. package/dist/hooks/scene-registry/scene-registry.test.js +26 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  13. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  14. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  15. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  16. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  17. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +366 -0
  18. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +79 -11
  19. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  20. package/dist/hooks/spatial-grid/spatial-grid-manager.js +508 -141
  21. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  22. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  23. package/dist/hooks/spatial-grid/spatial-grid-sync.js +109 -18
  24. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
  25. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
  26. package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
  27. package/dist/index.d.ts +36 -8
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +33 -6
  30. package/dist/lib/level-name.d.ts +4 -0
  31. package/dist/lib/level-name.d.ts.map +1 -0
  32. package/dist/lib/level-name.js +10 -0
  33. package/dist/lib/measurement-geometry.d.ts +19 -0
  34. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  35. package/dist/lib/measurement-geometry.js +232 -0
  36. package/dist/lib/measurement-geometry.test.d.ts +2 -0
  37. package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
  38. package/dist/lib/measurement-geometry.test.js +102 -0
  39. package/dist/lib/polygon-geometry.d.ts +0 -1
  40. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  41. package/dist/lib/polygon-geometry.js +0 -18
  42. package/dist/lib/polygon-relations.d.ts +10 -0
  43. package/dist/lib/polygon-relations.d.ts.map +1 -0
  44. package/dist/lib/polygon-relations.js +73 -0
  45. package/dist/lib/selection-proxy.d.ts +4 -0
  46. package/dist/lib/selection-proxy.d.ts.map +1 -0
  47. package/dist/lib/selection-proxy.js +12 -0
  48. package/dist/lib/slab-polygon.d.ts +31 -2
  49. package/dist/lib/slab-polygon.d.ts.map +1 -1
  50. package/dist/lib/slab-polygon.js +525 -31
  51. package/dist/lib/slab-polygon.test.d.ts +2 -0
  52. package/dist/lib/slab-polygon.test.d.ts.map +1 -0
  53. package/dist/lib/slab-polygon.test.js +635 -0
  54. package/dist/lib/slots.d.ts +14 -0
  55. package/dist/lib/slots.d.ts.map +1 -0
  56. package/dist/lib/slots.js +26 -0
  57. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  58. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  59. package/dist/lib/space-detection-pause.test.js +42 -0
  60. package/dist/lib/space-detection.d.ts +54 -2
  61. package/dist/lib/space-detection.d.ts.map +1 -1
  62. package/dist/lib/space-detection.js +599 -124
  63. package/dist/lib/space-detection.test.d.ts +2 -0
  64. package/dist/lib/space-detection.test.d.ts.map +1 -0
  65. package/dist/lib/space-detection.test.js +376 -0
  66. package/dist/lib/wall-distance.d.ts +60 -0
  67. package/dist/lib/wall-distance.d.ts.map +1 -0
  68. package/dist/lib/wall-distance.js +85 -0
  69. package/dist/lib/zone-quantities.d.ts +21 -0
  70. package/dist/lib/zone-quantities.d.ts.map +1 -0
  71. package/dist/lib/zone-quantities.js +183 -0
  72. package/dist/lib/zone-quantities.test.d.ts +2 -0
  73. package/dist/lib/zone-quantities.test.d.ts.map +1 -0
  74. package/dist/lib/zone-quantities.test.js +82 -0
  75. package/dist/material-library.d.ts +20 -1
  76. package/dist/material-library.d.ts.map +1 -1
  77. package/dist/material-library.js +3737 -200
  78. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  79. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  80. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  81. package/dist/registry/handles.d.ts +394 -0
  82. package/dist/registry/handles.d.ts.map +1 -0
  83. package/dist/registry/handles.js +24 -0
  84. package/dist/registry/index.d.ts +7 -0
  85. package/dist/registry/index.d.ts.map +1 -0
  86. package/dist/registry/index.js +4 -0
  87. package/dist/registry/registry.d.ts +136 -0
  88. package/dist/registry/registry.d.ts.map +1 -0
  89. package/dist/registry/registry.js +282 -0
  90. package/dist/registry/registry.test.d.ts +2 -0
  91. package/dist/registry/registry.test.d.ts.map +1 -0
  92. package/dist/registry/registry.test.js +196 -0
  93. package/dist/registry/relations-resolver.d.ts +42 -0
  94. package/dist/registry/relations-resolver.d.ts.map +1 -0
  95. package/dist/registry/relations-resolver.js +77 -0
  96. package/dist/registry/relations-resolver.test.d.ts +2 -0
  97. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  98. package/dist/registry/relations-resolver.test.js +183 -0
  99. package/dist/registry/scene-api.d.ts +44 -0
  100. package/dist/registry/scene-api.d.ts.map +1 -0
  101. package/dist/registry/scene-api.js +111 -0
  102. package/dist/registry/scene-api.test.d.ts +2 -0
  103. package/dist/registry/scene-api.test.d.ts.map +1 -0
  104. package/dist/registry/scene-api.test.js +183 -0
  105. package/dist/registry/subtree.d.ts +67 -0
  106. package/dist/registry/subtree.d.ts.map +1 -0
  107. package/dist/registry/subtree.js +123 -0
  108. package/dist/registry/subtree.test.d.ts +2 -0
  109. package/dist/registry/subtree.test.d.ts.map +1 -0
  110. package/dist/registry/subtree.test.js +163 -0
  111. package/dist/registry/types.d.ts +2099 -0
  112. package/dist/registry/types.d.ts.map +1 -0
  113. package/dist/registry/types.js +1 -0
  114. package/dist/schema/index.d.ts +35 -4
  115. package/dist/schema/index.d.ts.map +1 -1
  116. package/dist/schema/index.js +32 -4
  117. package/dist/schema/material.d.ts +13 -2
  118. package/dist/schema/material.d.ts.map +1 -1
  119. package/dist/schema/material.js +17 -5
  120. package/dist/schema/material.test.d.ts +2 -0
  121. package/dist/schema/material.test.d.ts.map +1 -0
  122. package/dist/schema/material.test.js +43 -0
  123. package/dist/schema/nodes/box-vent.d.ts +74 -0
  124. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  125. package/dist/schema/nodes/box-vent.js +63 -0
  126. package/dist/schema/nodes/building.d.ts +1 -1
  127. package/dist/schema/nodes/building.d.ts.map +1 -1
  128. package/dist/schema/nodes/building.js +3 -2
  129. package/dist/schema/nodes/cabinet.d.ts +534 -0
  130. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  131. package/dist/schema/nodes/cabinet.js +137 -0
  132. package/dist/schema/nodes/ceiling.d.ts +5 -2
  133. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  134. package/dist/schema/nodes/ceiling.js +5 -1
  135. package/dist/schema/nodes/chimney.d.ts +158 -0
  136. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  137. package/dist/schema/nodes/chimney.js +67 -0
  138. package/dist/schema/nodes/column.d.ts +600 -7
  139. package/dist/schema/nodes/column.d.ts.map +1 -1
  140. package/dist/schema/nodes/column.js +589 -6
  141. package/dist/schema/nodes/cupola.d.ts +66 -0
  142. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  143. package/dist/schema/nodes/cupola.js +28 -0
  144. package/dist/schema/nodes/door.d.ts +13 -5
  145. package/dist/schema/nodes/door.d.ts.map +1 -1
  146. package/dist/schema/nodes/door.js +12 -0
  147. package/dist/schema/nodes/dormer.d.ts +228 -0
  148. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  149. package/dist/schema/nodes/dormer.js +119 -0
  150. package/dist/schema/nodes/downspout.d.ts +75 -0
  151. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  152. package/dist/schema/nodes/downspout.js +63 -0
  153. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  154. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  155. package/dist/schema/nodes/duct-fitting.js +92 -0
  156. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  157. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  158. package/dist/schema/nodes/duct-segment.js +73 -0
  159. package/dist/schema/nodes/duct-terminal.d.ts +57 -0
  160. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  161. package/dist/schema/nodes/duct-terminal.js +51 -0
  162. package/dist/schema/nodes/elevator.d.ts +106 -0
  163. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  164. package/dist/schema/nodes/elevator.js +57 -0
  165. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  166. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  167. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  168. package/dist/schema/nodes/fence.d.ts +19 -2
  169. package/dist/schema/nodes/fence.d.ts.map +1 -1
  170. package/dist/schema/nodes/fence.js +27 -2
  171. package/dist/schema/nodes/gutter.d.ts +84 -0
  172. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  173. package/dist/schema/nodes/gutter.js +76 -0
  174. package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
  175. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  176. package/dist/schema/nodes/hvac-equipment.js +55 -0
  177. package/dist/schema/nodes/item.d.ts +12 -0
  178. package/dist/schema/nodes/item.d.ts.map +1 -1
  179. package/dist/schema/nodes/item.js +20 -0
  180. package/dist/schema/nodes/level.d.ts +1 -1
  181. package/dist/schema/nodes/level.d.ts.map +1 -1
  182. package/dist/schema/nodes/level.js +4 -0
  183. package/dist/schema/nodes/lineset.d.ts +42 -0
  184. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  185. package/dist/schema/nodes/lineset.js +38 -0
  186. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  187. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  188. package/dist/schema/nodes/liquid-line.js +24 -0
  189. package/dist/schema/nodes/measurement.d.ts +306 -0
  190. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  191. package/dist/schema/nodes/measurement.js +87 -0
  192. package/dist/schema/nodes/measurement.test.d.ts +2 -0
  193. package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
  194. package/dist/schema/nodes/measurement.test.js +139 -0
  195. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  196. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  197. package/dist/schema/nodes/pipe-fitting.js +45 -0
  198. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  199. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  200. package/dist/schema/nodes/pipe-segment.js +36 -0
  201. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  202. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  203. package/dist/schema/nodes/pipe-trap.js +36 -0
  204. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +2 -0
  205. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
  206. package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
  207. package/dist/schema/nodes/ridge-vent.d.ts +82 -0
  208. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  209. package/dist/schema/nodes/ridge-vent.js +248 -0
  210. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  211. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  212. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  213. package/dist/schema/nodes/roof-segment-shape.test.d.ts +2 -0
  214. package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
  215. package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
  216. package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
  217. package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
  218. package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
  219. package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
  220. package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
  221. package/dist/schema/nodes/roof-segment-trim.test.js +107 -0
  222. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  223. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  224. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  225. package/dist/schema/nodes/roof-segment-walls.test.d.ts +2 -0
  226. package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
  227. package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
  228. package/dist/schema/nodes/roof-segment.d.ts +273 -3
  229. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  230. package/dist/schema/nodes/roof-segment.js +539 -3
  231. package/dist/schema/nodes/roof.d.ts +8 -8
  232. package/dist/schema/nodes/roof.d.ts.map +1 -1
  233. package/dist/schema/nodes/roof.js +4 -16
  234. package/dist/schema/nodes/shelf.d.ts +106 -0
  235. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  236. package/dist/schema/nodes/shelf.js +90 -0
  237. package/dist/schema/nodes/site.d.ts +1 -122
  238. package/dist/schema/nodes/site.d.ts.map +1 -1
  239. package/dist/schema/nodes/site.js +2 -6
  240. package/dist/schema/nodes/skylight.d.ts +279 -0
  241. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  242. package/dist/schema/nodes/skylight.js +152 -0
  243. package/dist/schema/nodes/slab.d.ts +5 -2
  244. package/dist/schema/nodes/slab.d.ts.map +1 -1
  245. package/dist/schema/nodes/slab.js +5 -1
  246. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  247. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  248. package/dist/schema/nodes/solar-panel.js +47 -0
  249. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  250. package/dist/schema/nodes/stair.d.ts +13 -12
  251. package/dist/schema/nodes/stair.d.ts.map +1 -1
  252. package/dist/schema/nodes/stair.js +3 -0
  253. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  254. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  255. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  256. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  257. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  258. package/dist/schema/nodes/turbine-vent.js +41 -0
  259. package/dist/schema/nodes/wall.d.ts +245 -6
  260. package/dist/schema/nodes/wall.d.ts.map +1 -1
  261. package/dist/schema/nodes/wall.js +257 -0
  262. package/dist/schema/nodes/wall.test.d.ts +2 -0
  263. package/dist/schema/nodes/wall.test.d.ts.map +1 -0
  264. package/dist/schema/nodes/wall.test.js +208 -0
  265. package/dist/schema/nodes/window.d.ts +11 -3
  266. package/dist/schema/nodes/window.d.ts.map +1 -1
  267. package/dist/schema/nodes/window.js +12 -0
  268. package/dist/schema/nodes/zone.d.ts +2 -0
  269. package/dist/schema/nodes/zone.d.ts.map +1 -1
  270. package/dist/schema/nodes/zone.js +6 -0
  271. package/dist/schema/scene-material.d.ts +41 -0
  272. package/dist/schema/scene-material.d.ts.map +1 -0
  273. package/dist/schema/scene-material.js +9 -0
  274. package/dist/schema/types.d.ts +2850 -655
  275. package/dist/schema/types.d.ts.map +1 -1
  276. package/dist/schema/types.js +49 -0
  277. package/dist/services/alignment-anchors.d.ts +100 -0
  278. package/dist/services/alignment-anchors.d.ts.map +1 -0
  279. package/dist/services/alignment-anchors.js +312 -0
  280. package/dist/services/alignment-anchors.test.d.ts +2 -0
  281. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  282. package/dist/services/alignment-anchors.test.js +383 -0
  283. package/dist/services/alignment.d.ts +131 -0
  284. package/dist/services/alignment.d.ts.map +1 -0
  285. package/dist/services/alignment.js +171 -0
  286. package/dist/services/alignment.test.d.ts +2 -0
  287. package/dist/services/alignment.test.d.ts.map +1 -0
  288. package/dist/services/alignment.test.js +92 -0
  289. package/dist/services/drag-session.d.ts +47 -0
  290. package/dist/services/drag-session.d.ts.map +1 -0
  291. package/dist/services/drag-session.js +100 -0
  292. package/dist/services/drag-session.test.d.ts +2 -0
  293. package/dist/services/drag-session.test.d.ts.map +1 -0
  294. package/dist/services/drag-session.test.js +219 -0
  295. package/dist/services/hosting.d.ts +88 -0
  296. package/dist/services/hosting.d.ts.map +1 -0
  297. package/dist/services/hosting.js +131 -0
  298. package/dist/services/hosting.test.d.ts +2 -0
  299. package/dist/services/hosting.test.d.ts.map +1 -0
  300. package/dist/services/hosting.test.js +230 -0
  301. package/dist/services/index.d.ts +13 -0
  302. package/dist/services/index.d.ts.map +1 -0
  303. package/dist/services/index.js +12 -0
  304. package/dist/services/level-height.d.ts +28 -0
  305. package/dist/services/level-height.d.ts.map +1 -0
  306. package/dist/services/level-height.js +62 -0
  307. package/dist/services/movement.d.ts +48 -0
  308. package/dist/services/movement.d.ts.map +1 -0
  309. package/dist/services/movement.js +65 -0
  310. package/dist/services/movement.test.d.ts +2 -0
  311. package/dist/services/movement.test.d.ts.map +1 -0
  312. package/dist/services/movement.test.js +133 -0
  313. package/dist/services/opening-guides.d.ts +141 -0
  314. package/dist/services/opening-guides.d.ts.map +1 -0
  315. package/dist/services/opening-guides.js +267 -0
  316. package/dist/services/opening-guides.test.d.ts +2 -0
  317. package/dist/services/opening-guides.test.d.ts.map +1 -0
  318. package/dist/services/opening-guides.test.js +208 -0
  319. package/dist/services/port-connectivity.d.ts +115 -0
  320. package/dist/services/port-connectivity.d.ts.map +1 -0
  321. package/dist/services/port-connectivity.js +300 -0
  322. package/dist/services/port-connectivity.test.d.ts +2 -0
  323. package/dist/services/port-connectivity.test.d.ts.map +1 -0
  324. package/dist/services/port-connectivity.test.js +297 -0
  325. package/dist/services/riser-diagram.d.ts +40 -0
  326. package/dist/services/riser-diagram.d.ts.map +1 -0
  327. package/dist/services/riser-diagram.js +110 -0
  328. package/dist/services/riser-diagram.test.d.ts +2 -0
  329. package/dist/services/riser-diagram.test.d.ts.map +1 -0
  330. package/dist/services/riser-diagram.test.js +66 -0
  331. package/dist/services/single-undo-dance.test.d.ts +2 -0
  332. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  333. package/dist/services/single-undo-dance.test.js +210 -0
  334. package/dist/services/snap.d.ts +80 -0
  335. package/dist/services/snap.d.ts.map +1 -0
  336. package/dist/services/snap.js +140 -0
  337. package/dist/services/snap.test.d.ts +2 -0
  338. package/dist/services/snap.test.d.ts.map +1 -0
  339. package/dist/services/snap.test.js +137 -0
  340. package/dist/services/system-graph.d.ts +30 -0
  341. package/dist/services/system-graph.d.ts.map +1 -0
  342. package/dist/services/system-graph.js +164 -0
  343. package/dist/services/system-graph.test.d.ts +2 -0
  344. package/dist/services/system-graph.test.d.ts.map +1 -0
  345. package/dist/services/system-graph.test.js +138 -0
  346. package/dist/services/validate-dwv.d.ts +32 -0
  347. package/dist/services/validate-dwv.d.ts.map +1 -0
  348. package/dist/services/validate-dwv.js +118 -0
  349. package/dist/services/validate-dwv.test.d.ts +2 -0
  350. package/dist/services/validate-dwv.test.d.ts.map +1 -0
  351. package/dist/services/validate-dwv.test.js +109 -0
  352. package/dist/solar-panel-presets.d.ts +17 -0
  353. package/dist/solar-panel-presets.d.ts.map +1 -0
  354. package/dist/solar-panel-presets.js +43 -0
  355. package/dist/store/actions/node-actions.d.ts +14 -2
  356. package/dist/store/actions/node-actions.d.ts.map +1 -1
  357. package/dist/store/actions/node-actions.js +585 -15
  358. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  359. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  360. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  361. package/dist/store/actions/reparent.test.d.ts +2 -0
  362. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  363. package/dist/store/actions/reparent.test.js +212 -0
  364. package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
  365. package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
  366. package/dist/store/actions/ridge-vent-update.test.js +264 -0
  367. package/dist/store/history-control.d.ts +11 -0
  368. package/dist/store/history-control.d.ts.map +1 -1
  369. package/dist/store/history-control.js +33 -0
  370. package/dist/store/use-interactive.d.ts +39 -0
  371. package/dist/store/use-interactive.d.ts.map +1 -1
  372. package/dist/store/use-interactive.js +75 -0
  373. package/dist/store/use-live-node-overrides.d.ts +22 -0
  374. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  375. package/dist/store/use-live-node-overrides.js +59 -0
  376. package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
  377. package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
  378. package/dist/store/use-scene-dirty-tracking.test.js +68 -0
  379. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  380. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  381. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  382. package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
  383. package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
  384. package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
  385. package/dist/store/use-scene-plugins.test.d.ts +2 -0
  386. package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
  387. package/dist/store/use-scene-plugins.test.js +51 -0
  388. package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
  389. package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
  390. package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
  391. package/dist/store/use-scene-window-migration.test.d.ts +2 -0
  392. package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
  393. package/dist/store/use-scene-window-migration.test.js +85 -0
  394. package/dist/store/use-scene.d.ts +28 -2
  395. package/dist/store/use-scene.d.ts.map +1 -1
  396. package/dist/store/use-scene.js +546 -49
  397. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  398. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  399. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  400. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  401. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  402. package/dist/systems/elevator/elevator-geometry.js +50 -0
  403. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  404. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  405. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  406. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  407. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  408. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  409. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  410. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  411. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  412. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  413. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  414. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  415. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  416. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  417. package/dist/systems/elevator/elevator-runtime.js +225 -0
  418. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  419. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  420. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  421. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  422. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  423. package/dist/systems/elevator/elevator-service.js +111 -0
  424. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  425. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  426. package/dist/systems/fence/fence-centerline.js +37 -0
  427. package/dist/systems/fence/fence-spline.d.ts +39 -0
  428. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  429. package/dist/systems/fence/fence-spline.js +196 -0
  430. package/dist/systems/fence/fence-spline.test.d.ts +2 -0
  431. package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
  432. package/dist/systems/fence/fence-spline.test.js +136 -0
  433. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  434. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  435. package/dist/systems/stair/stair-footprint.js +178 -0
  436. package/dist/systems/stair/stair-opening-preview.d.ts +2447 -0
  437. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  438. package/dist/systems/stair/stair-opening-preview.js +93 -0
  439. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  440. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  441. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  442. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  443. package/dist/systems/stair/stair-opening-sync.js +155 -147
  444. package/dist/systems/stair/stair-opening-sync.test.js +424 -2
  445. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  446. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  447. package/dist/systems/stair/stair-opening-system.js +104 -0
  448. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  449. package/dist/systems/wall/wall-mitering.js +38 -2
  450. package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
  451. package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
  452. package/dist/systems/wall/wall-mitering.test.js +63 -0
  453. package/dist/systems/wall/wall-move.d.ts +48 -0
  454. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  455. package/dist/systems/wall/wall-move.js +193 -0
  456. package/dist/utils/clone-scene-graph.d.ts +7 -4
  457. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  458. package/dist/utils/clone-scene-graph.js +29 -7
  459. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  460. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  461. package/dist/utils/clone-scene-graph.test.js +62 -0
  462. package/dist/utils/heal-scene-graph.d.ts +18 -0
  463. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  464. package/dist/utils/heal-scene-graph.js +90 -0
  465. package/dist/utils/heal-scene-graph.test.d.ts +2 -0
  466. package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
  467. package/dist/utils/heal-scene-graph.test.js +88 -0
  468. package/dist/validation/validate-build-json.d.ts +44 -0
  469. package/dist/validation/validate-build-json.d.ts.map +1 -0
  470. package/dist/validation/validate-build-json.js +256 -0
  471. package/package.json +11 -4
@@ -3,11 +3,91 @@ import { z } from 'zod';
3
3
  import { BaseNode, nodeType, objectId } from '../base';
4
4
  import { MaterialSchema } from '../material';
5
5
  export const RoofType = z.enum(['hip', 'gable', 'shed', 'gambrel', 'dutch', 'mansard', 'flat']);
6
+ export const MIN_ROOF_SEGMENT_TRIM_SPAN = 0.1;
7
+ const DEFAULT_ROOF_SEGMENT_WIDTH = 8;
8
+ const DEFAULT_ROOF_SEGMENT_DEPTH = 6;
9
+ export const RoofSegmentTrim = z
10
+ .object({
11
+ left: z.number().min(0).default(0),
12
+ right: z.number().min(0).default(0),
13
+ front: z.number().min(0).default(0),
14
+ back: z.number().min(0).default(0),
15
+ frontLeft: z.number().min(0).default(0),
16
+ frontRight: z.number().min(0).default(0),
17
+ backLeft: z.number().min(0).default(0),
18
+ backRight: z.number().min(0).default(0),
19
+ frontLeftX: z.number().min(0).default(0),
20
+ frontLeftZ: z.number().min(0).default(0),
21
+ frontRightX: z.number().min(0).default(0),
22
+ frontRightZ: z.number().min(0).default(0),
23
+ backLeftX: z.number().min(0).default(0),
24
+ backLeftZ: z.number().min(0).default(0),
25
+ backRightX: z.number().min(0).default(0),
26
+ backRightZ: z.number().min(0).default(0),
27
+ })
28
+ .default({
29
+ left: 0,
30
+ right: 0,
31
+ front: 0,
32
+ back: 0,
33
+ frontLeft: 0,
34
+ frontRight: 0,
35
+ backLeft: 0,
36
+ backRight: 0,
37
+ frontLeftX: 0,
38
+ frontLeftZ: 0,
39
+ frontRightX: 0,
40
+ frontRightZ: 0,
41
+ backLeftX: 0,
42
+ backLeftZ: 0,
43
+ backRightX: 0,
44
+ backRightZ: 0,
45
+ });
46
+ // Default shape ratios. Tuning these used to require editing the geometry
47
+ // code in two places; they are now schema fields with these defaults.
48
+ export const ROOF_SHAPE_DEFAULTS = {
49
+ /** Gambrel: lower (steep) face occupies this fraction of the horizontal half-depth. */
50
+ gambrelLowerWidthRatio: 0.5,
51
+ /** Gambrel: lower (steep) face rises this fraction of the way to the peak. */
52
+ gambrelLowerHeightRatio: 0.6,
53
+ /** Mansard: steep face occupies this fraction of `min(width, depth)`. */
54
+ mansardSteepWidthRatio: 0.15,
55
+ /** Mansard: steep face rises this fraction of the way to the peak. */
56
+ mansardSteepHeightRatio: 0.7,
57
+ /** Dutch: hip face occupies this fraction of `min(width, depth)`. */
58
+ dutchHipWidthRatio: 0.25,
59
+ /** Dutch: hip face rises this fraction of the way to the peak. */
60
+ dutchHipHeightRatio: 0.5,
61
+ /** Dutch: gable waist span along the ridge axis, as a fraction of the max span. */
62
+ dutchWaistLengthRatio: 0.98,
63
+ /**
64
+ * Dutch: how far the gablet's barge board extends outward past the gablet
65
+ * end-wall, along the ridge axis, in metres. 0 disables the rake. The board
66
+ * lies in the gablet's slope planes (coplanar with the main Dutch slopes)
67
+ * and overhangs the lower hip skirt; the gablet end-wall itself stays put.
68
+ */
69
+ dutchGabletRake: 0.48,
70
+ /** Dutch: thickness of the top gable rake slab. */
71
+ dutchTopRakeThickness: 0.21,
72
+ };
6
73
  export const RoofSegmentNode = BaseNode.extend({
7
74
  id: objectId('rseg'),
8
75
  type: nodeType('roof-segment'),
76
+ // Catch-all material — splatted across all 4 slots in the renderer
77
+ // when no role-specific override is set. Kept for back-compat with
78
+ // older scenes; new paint operations should prefer the role fields.
9
79
  material: MaterialSchema.optional(),
10
80
  materialPreset: z.string().optional(),
81
+ // Role-specific overrides mirror the parent roof's surface roles.
82
+ // When set they win over the segment's catch-all `material` and over
83
+ // the parent roof's role / catch-all materials. Resolution order:
84
+ // segment role → segment catch-all → roof role → roof catch-all.
85
+ topMaterial: MaterialSchema.optional(),
86
+ topMaterialPreset: z.string().optional(),
87
+ edgeMaterial: MaterialSchema.optional(),
88
+ edgeMaterialPreset: z.string().optional(),
89
+ wallMaterial: MaterialSchema.optional(),
90
+ wallMaterialPreset: z.string().optional(),
11
91
  position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
12
92
  // Rotation around Y axis in radians
13
93
  rotation: z.number().default(0),
@@ -16,23 +96,479 @@ export const RoofSegmentNode = BaseNode.extend({
16
96
  // Footprint dimensions
17
97
  width: z.number().default(8),
18
98
  depth: z.number().default(6),
19
- // Vertical dimensions
99
+ // Segment-local distances trimmed from each footprint side. The trim
100
+ // boundary is projected vertically through the roof volume, so the
101
+ // resulting edge follows the actual sloped roof surfaces.
102
+ trim: RoofSegmentTrim,
103
+ // Wall height beneath the roof
20
104
  wallHeight: z.number().default(0.5),
21
- roofHeight: z.number().default(2.5),
105
+ // Roof pitch in degrees — angle of the primary slope face.
106
+ // For gable/hip/shed this is the only slope; for gambrel/mansard/dutch
107
+ // it is the steep (lower) slope. The overall peak height is derived
108
+ // from pitch + footprint + roofType via getActiveRoofHeight().
109
+ pitch: z.number().min(0).max(85).default(40),
22
110
  // Structure thicknesses
23
111
  wallThickness: z.number().default(0.1),
24
112
  deckThickness: z.number().default(0.1),
25
113
  overhang: z.number().default(0.3),
26
114
  shingleThickness: z.number().default(0.05),
115
+ // Shape-specific ratios. Only the pair matching `roofType` is read; the
116
+ // rest are inert. Defined on every segment so the panel can flip
117
+ // roofType without losing the previous shape's tuning.
118
+ gambrelLowerWidthRatio: z
119
+ .number()
120
+ .min(0.1)
121
+ .max(0.9)
122
+ .default(ROOF_SHAPE_DEFAULTS.gambrelLowerWidthRatio),
123
+ gambrelLowerHeightRatio: z
124
+ .number()
125
+ .min(0.1)
126
+ .max(0.9)
127
+ .default(ROOF_SHAPE_DEFAULTS.gambrelLowerHeightRatio),
128
+ mansardSteepWidthRatio: z
129
+ .number()
130
+ .min(0.05)
131
+ .max(0.45)
132
+ .default(ROOF_SHAPE_DEFAULTS.mansardSteepWidthRatio),
133
+ mansardSteepHeightRatio: z
134
+ .number()
135
+ .min(0.1)
136
+ .max(0.9)
137
+ .default(ROOF_SHAPE_DEFAULTS.mansardSteepHeightRatio),
138
+ dutchHipWidthRatio: z
139
+ .number()
140
+ .min(0.05)
141
+ .max(0.45)
142
+ .default(ROOF_SHAPE_DEFAULTS.dutchHipWidthRatio),
143
+ dutchHipHeightRatio: z
144
+ .number()
145
+ .min(0.1)
146
+ .max(0.9)
147
+ .default(ROOF_SHAPE_DEFAULTS.dutchHipHeightRatio),
148
+ dutchWaistLengthRatio: z
149
+ .number()
150
+ .min(0.1)
151
+ .max(1)
152
+ .default(ROOF_SHAPE_DEFAULTS.dutchWaistLengthRatio),
153
+ dutchGabletRake: z.number().min(0).max(3).default(ROOF_SHAPE_DEFAULTS.dutchGabletRake),
154
+ dutchTopRakeThickness: z
155
+ .number()
156
+ .min(0.01)
157
+ .max(0.5)
158
+ .default(ROOF_SHAPE_DEFAULTS.dutchTopRakeThickness),
159
+ // Hosted accessories — chimney, dormer, skylight, box-vent,
160
+ // ridge-vent, solar-panel, gutter. Each accessory's `parentId` points back
161
+ // here; the segment renderer mounts them recursively via
162
+ // `<NodeRenderer>` so they inherit the segment's transform stack.
163
+ // Required for `createNode(child, segmentId)` to append the child
164
+ // to this array — see
165
+ // `wiki/architecture/node-definitions.md` ("Host kinds need a
166
+ // `children` field on the schema").
167
+ children: z.array(z.string()).default([]),
27
168
  }).describe(dedent `
28
169
  Roof segment node - an individual roof module within a roof group.
29
170
  Each segment generates a complete architectural volume (walls + roof).
30
171
  Multiple segments can be combined to form complex roof shapes.
31
172
  - roofType: hip, gable, shed, gambrel, dutch, mansard, flat
32
173
  - width/depth: footprint dimensions
174
+ - trim: segment-local side cut distances
33
175
  - wallHeight: height of walls below the roof
34
- - roofHeight: height of the roof peak above the walls
176
+ - pitch: roof slope in degrees (angle of the primary slope face)
35
177
  - wallThickness/deckThickness: structural thicknesses
36
178
  - overhang: eave overhang distance
37
179
  - shingleThickness: outer shingle layer thickness
180
+ - gambrelLowerWidthRatio / gambrelLowerHeightRatio: kink position on gambrel roofs
181
+ - mansardSteepWidthRatio / mansardSteepHeightRatio: waist position on mansard roofs
182
+ - dutchHipWidthRatio / dutchHipHeightRatio: hip-to-gable split on dutch roofs
183
+ - dutchWaistLengthRatio: gable waist span along the ridge axis
184
+ - dutchGabletRake: gablet barge-board overhang past the gablet end-wall (m, 0 = none)
185
+ - dutchTopRakeThickness: thickness of the top gable rake slab
38
186
  `);
187
+ function finiteNonNegative(value) {
188
+ return typeof value === 'number' && Number.isFinite(value) ? Math.max(0, value) : 0;
189
+ }
190
+ function finitePositive(value, fallback) {
191
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
192
+ }
193
+ function normalizeTrimAxis(start, end, span) {
194
+ const maxTotal = Math.max(0, finiteNonNegative(span) - MIN_ROOF_SEGMENT_TRIM_SPAN);
195
+ let a = Math.min(finiteNonNegative(start), maxTotal);
196
+ let b = Math.min(finiteNonNegative(end), maxTotal);
197
+ const total = a + b;
198
+ if (total > maxTotal && total > 0) {
199
+ const scale = maxTotal / total;
200
+ a *= scale;
201
+ b *= scale;
202
+ }
203
+ return [a, b];
204
+ }
205
+ export function normalizeRoofSegmentTrim(node) {
206
+ const trim = node.trim ?? {};
207
+ const [left, right] = normalizeTrimAxis(trim.left, trim.right, node.width);
208
+ const [back, front] = normalizeTrimAxis(trim.back, trim.front, node.depth);
209
+ const maxWidthDiagonal = Math.max(0, finiteNonNegative(node.width) - left - right);
210
+ const maxDepthDiagonal = Math.max(0, finiteNonNegative(node.depth) - front - back);
211
+ const maxWidthPair = Math.max(0, maxWidthDiagonal - MIN_ROOF_SEGMENT_TRIM_SPAN);
212
+ const maxDepthPair = Math.max(0, maxDepthDiagonal - MIN_ROOF_SEGMENT_TRIM_SPAN);
213
+ let [frontLeftX, frontLeftZ] = normalizeCornerAxisTrim(trim.frontLeft, trim.frontLeftX, trim.frontLeftZ, maxWidthPair, maxDepthPair);
214
+ let [frontRightX, frontRightZ] = normalizeCornerAxisTrim(trim.frontRight, trim.frontRightX, trim.frontRightZ, maxWidthPair, maxDepthPair);
215
+ let [backLeftX, backLeftZ] = normalizeCornerAxisTrim(trim.backLeft, trim.backLeftX, trim.backLeftZ, maxWidthPair, maxDepthPair);
216
+ let [backRightX, backRightZ] = normalizeCornerAxisTrim(trim.backRight, trim.backRightX, trim.backRightZ, maxWidthPair, maxDepthPair);
217
+ for (let i = 0; i < 3; i += 1) {
218
+ ;
219
+ [frontLeftX, frontRightX] = normalizeTrimPair(frontLeftX, frontRightX, maxWidthPair);
220
+ [backLeftX, backRightX] = normalizeTrimPair(backLeftX, backRightX, maxWidthPair);
221
+ [frontLeftZ, backLeftZ] = normalizeTrimPair(frontLeftZ, backLeftZ, maxDepthPair);
222
+ [frontRightZ, backRightZ] = normalizeTrimPair(frontRightZ, backRightZ, maxDepthPair);
223
+ }
224
+ const frontLeft = Math.min(frontLeftX, frontLeftZ);
225
+ const frontRight = Math.min(frontRightX, frontRightZ);
226
+ const backLeft = Math.min(backLeftX, backLeftZ);
227
+ const backRight = Math.min(backRightX, backRightZ);
228
+ return {
229
+ left,
230
+ right,
231
+ front,
232
+ back,
233
+ frontLeft,
234
+ frontRight,
235
+ backLeft,
236
+ backRight,
237
+ frontLeftX,
238
+ frontLeftZ,
239
+ frontRightX,
240
+ frontRightZ,
241
+ backLeftX,
242
+ backLeftZ,
243
+ backRightX,
244
+ backRightZ,
245
+ };
246
+ }
247
+ function normalizeTrimPair(a, b, maxTotal) {
248
+ const total = a + b;
249
+ if (total <= maxTotal || total <= 0)
250
+ return [a, b];
251
+ const scale = maxTotal / total;
252
+ return [a * scale, b * scale];
253
+ }
254
+ function normalizeCornerAxisTrim(scalar, axisX, axisZ, maxX, maxZ) {
255
+ const x = finiteNonNegative(axisX);
256
+ const z = finiteNonNegative(axisZ);
257
+ const fallback = finiteNonNegative(scalar);
258
+ if (x > 0 || z > 0) {
259
+ return [Math.min(x, maxX), Math.min(z, maxZ)];
260
+ }
261
+ return [Math.min(fallback, maxX), Math.min(fallback, maxZ)];
262
+ }
263
+ function getDutchUpperShellBounds(node) {
264
+ const metrics = getDutchRoofMetrics(node);
265
+ const width = finitePositive(node.width, DEFAULT_ROOF_SEGMENT_WIDTH);
266
+ const depth = finitePositive(node.depth, DEFAULT_ROOF_SEGMENT_DEPTH);
267
+ const rake = node.dutchGabletRake ?? ROOF_SHAPE_DEFAULTS.dutchGabletRake;
268
+ const rakeReach = metrics.axis === 'x'
269
+ ? Math.min(Math.max(0, rake), Math.max(0, width / 2 - metrics.waistHalfX) * 0.98)
270
+ : Math.min(Math.max(0, rake), Math.max(0, depth / 2 - metrics.waistHalfZ) * 0.98);
271
+ return {
272
+ ...metrics,
273
+ upperHalfX: metrics.axis === 'x' ? metrics.waistHalfX + rakeReach : metrics.waistHalfX,
274
+ upperHalfZ: metrics.axis === 'x' ? metrics.waistHalfZ : metrics.waistHalfZ + rakeReach,
275
+ };
276
+ }
277
+ function withRatioDefaults(input) {
278
+ return {
279
+ ...input,
280
+ width: finitePositive(input.width, DEFAULT_ROOF_SEGMENT_WIDTH),
281
+ depth: finitePositive(input.depth, DEFAULT_ROOF_SEGMENT_DEPTH),
282
+ gambrelLowerWidthRatio: input.gambrelLowerWidthRatio ?? ROOF_SHAPE_DEFAULTS.gambrelLowerWidthRatio,
283
+ gambrelLowerHeightRatio: input.gambrelLowerHeightRatio ?? ROOF_SHAPE_DEFAULTS.gambrelLowerHeightRatio,
284
+ mansardSteepWidthRatio: input.mansardSteepWidthRatio ?? ROOF_SHAPE_DEFAULTS.mansardSteepWidthRatio,
285
+ mansardSteepHeightRatio: input.mansardSteepHeightRatio ?? ROOF_SHAPE_DEFAULTS.mansardSteepHeightRatio,
286
+ dutchHipWidthRatio: input.dutchHipWidthRatio ?? ROOF_SHAPE_DEFAULTS.dutchHipWidthRatio,
287
+ dutchHipHeightRatio: input.dutchHipHeightRatio ?? ROOF_SHAPE_DEFAULTS.dutchHipHeightRatio,
288
+ dutchWaistLengthRatio: input.dutchWaistLengthRatio ?? ROOF_SHAPE_DEFAULTS.dutchWaistLengthRatio,
289
+ };
290
+ }
291
+ export function getDutchRoofMetrics(input) {
292
+ const width = finitePositive(input.width, DEFAULT_ROOF_SEGMENT_WIDTH);
293
+ const depth = finitePositive(input.depth, DEFAULT_ROOF_SEGMENT_DEPTH);
294
+ const inset = Math.min(width, depth) * (input.dutchHipWidthRatio ?? ROOF_SHAPE_DEFAULTS.dutchHipWidthRatio);
295
+ const waistLengthRatio = input.dutchWaistLengthRatio ?? ROOF_SHAPE_DEFAULTS.dutchWaistLengthRatio;
296
+ if (width >= depth) {
297
+ const waistHalfX = Math.max(0, (width / 2 - inset) * waistLengthRatio);
298
+ const waistHalfZ = Math.max(0, depth / 2 - inset);
299
+ return {
300
+ axis: 'x',
301
+ inset,
302
+ waistHalfX,
303
+ waistHalfZ,
304
+ ridgeStart: [-waistHalfX, 0],
305
+ ridgeEnd: [waistHalfX, 0],
306
+ shoulderInsetAlongDepth: Math.max(0, depth / 2 - waistHalfZ),
307
+ shoulderInsetAlongWidth: Math.max(0, width / 2 - waistHalfX),
308
+ };
309
+ }
310
+ const waistHalfX = Math.max(0, width / 2 - inset);
311
+ const waistHalfZ = Math.max(0, (depth / 2 - inset) * waistLengthRatio);
312
+ return {
313
+ axis: 'z',
314
+ inset,
315
+ waistHalfX,
316
+ waistHalfZ,
317
+ ridgeStart: [0, waistHalfZ],
318
+ ridgeEnd: [0, -waistHalfZ],
319
+ shoulderInsetAlongDepth: Math.max(0, depth / 2 - waistHalfZ),
320
+ shoulderInsetAlongWidth: Math.max(0, width / 2 - waistHalfX),
321
+ };
322
+ }
323
+ function getPrimarySlopeRun(input) {
324
+ const min = Math.min(input.width, input.depth);
325
+ switch (input.roofType) {
326
+ case 'shed':
327
+ return input.depth;
328
+ case 'gable':
329
+ return input.depth / 2;
330
+ case 'gambrel':
331
+ return (input.depth / 2) * input.gambrelLowerWidthRatio;
332
+ case 'mansard':
333
+ return min * input.mansardSteepWidthRatio;
334
+ case 'dutch':
335
+ return min * input.dutchHipWidthRatio;
336
+ default:
337
+ return min / 2;
338
+ }
339
+ }
340
+ // Fraction of the overall peak height that is taken up by the primary slope.
341
+ function getPrimarySlopeRiseFraction(input) {
342
+ switch (input.roofType) {
343
+ case 'gambrel':
344
+ return input.gambrelLowerHeightRatio;
345
+ case 'mansard':
346
+ return input.mansardSteepHeightRatio;
347
+ case 'dutch':
348
+ return input.dutchHipHeightRatio;
349
+ default:
350
+ return 1;
351
+ }
352
+ }
353
+ /**
354
+ * One stop for the slope math every roof-segment consumer needs. Builds
355
+ * `run`, `rise`, the trig triple, and the overall peak height from the
356
+ * segment's pitch + footprint + roofType. Before this helper existed,
357
+ * the table was duplicated in three places (the brush builder, the
358
+ * skylight surface-frame routine, and the segment-hit raycaster) and
359
+ * silently drifted when a new roof type was added.
360
+ */
361
+ export function getSegmentSlopeFrame(node) {
362
+ const ratios = withRatioDefaults(node);
363
+ const run = getPrimarySlopeRun(ratios);
364
+ // `!(pitch > 0)` (not `pitch <= 0`) so a missing/NaN pitch — e.g. a segment
365
+ // from an older migration that only set `roofHeight`, or stale persisted data —
366
+ // resolves to a flat frame instead of computing `Math.tan(NaN)` → NaN geometry,
367
+ // which poisons the merged-roof CSG ("Coplanar clip not handled" + NaN positions).
368
+ if (node.roofType === 'flat' || !(node.pitch > 0)) {
369
+ return { run, rise: 0, tanTheta: 0, cosTheta: 1, sinTheta: 0, activeRh: 0 };
370
+ }
371
+ const pitchRad = (node.pitch * Math.PI) / 180;
372
+ const tanTheta = Math.tan(pitchRad);
373
+ const cosTheta = Math.cos(pitchRad) || 1;
374
+ const sinTheta = Math.sin(pitchRad);
375
+ const rise = run * tanTheta;
376
+ const activeRh = rise / getPrimarySlopeRiseFraction(ratios);
377
+ return { run, rise, tanTheta, cosTheta, sinTheta, activeRh };
378
+ }
379
+ /**
380
+ * The eave-to-peak height of the assembled segment, derived from pitch +
381
+ * footprint + roofType. Replaces the legacy `roofHeight` field on the node.
382
+ */
383
+ export function getActiveRoofHeight(node) {
384
+ return getSegmentSlopeFrame(node).activeRh;
385
+ }
386
+ export function getRoofSegmentVisibleTopBounds(segment) {
387
+ const { activeRh, cosTheta, sinTheta } = getSegmentSlopeFrame(segment);
388
+ const width = finitePositive(segment.width, DEFAULT_ROOF_SEGMENT_WIDTH);
389
+ const depth = finitePositive(segment.depth, DEFAULT_ROOF_SEGMENT_DEPTH);
390
+ const trim = normalizeRoofSegmentTrim({ ...segment, width, depth });
391
+ const horizontalOverhang = finiteNonNegative(segment.overhang) * cosTheta;
392
+ const deckExt = finiteNonNegative(segment.wallThickness) / 2 + horizontalOverhang;
393
+ const shingleOverhang = finiteNonNegative(segment.shingleThickness) * sinTheta;
394
+ let xExt = deckExt;
395
+ let frontExt = deckExt;
396
+ let backExt = deckExt;
397
+ if (segment.roofType === 'hip' ||
398
+ segment.roofType === 'mansard' ||
399
+ segment.roofType === 'dutch') {
400
+ xExt += shingleOverhang;
401
+ frontExt += shingleOverhang;
402
+ backExt += shingleOverhang;
403
+ }
404
+ else if (segment.roofType === 'gable' || segment.roofType === 'gambrel') {
405
+ frontExt += shingleOverhang;
406
+ backExt += shingleOverhang;
407
+ }
408
+ else if (segment.roofType === 'shed' && activeRh > 0) {
409
+ frontExt += shingleOverhang;
410
+ }
411
+ let minX = trim.left > 0 ? -width / 2 + trim.left : -width / 2 - xExt;
412
+ let maxX = trim.right > 0 ? width / 2 - trim.right : width / 2 + xExt;
413
+ let minZ = trim.back > 0 ? -depth / 2 + trim.back : -depth / 2 - backExt;
414
+ let maxZ = trim.front > 0 ? depth / 2 - trim.front : depth / 2 + frontExt;
415
+ if (trim.frontLeftX > 0 && trim.frontLeftZ > 0 && maxZ - trim.frontLeftZ < 0) {
416
+ minX = Math.max(minX, minX + (trim.frontLeftX * (trim.frontLeftZ - maxZ)) / trim.frontLeftZ);
417
+ }
418
+ if (trim.backLeftX > 0 && trim.backLeftZ > 0 && minZ + trim.backLeftZ > 0) {
419
+ minX = Math.max(minX, minX + (trim.backLeftX * (trim.backLeftZ + minZ)) / trim.backLeftZ);
420
+ }
421
+ if (trim.frontRightX > 0 && trim.frontRightZ > 0 && maxZ - trim.frontRightZ < 0) {
422
+ maxX = Math.min(maxX, maxX - (trim.frontRightX * (trim.frontRightZ - maxZ)) / trim.frontRightZ);
423
+ }
424
+ if (trim.backRightX > 0 && trim.backRightZ > 0 && minZ + trim.backRightZ > 0) {
425
+ maxX = Math.min(maxX, maxX - (trim.backRightX * (trim.backRightZ + minZ)) / trim.backRightZ);
426
+ }
427
+ if (trim.frontLeftX > 0 && trim.frontLeftZ > 0 && minX + trim.frontLeftX > 0) {
428
+ maxZ = Math.min(maxZ, maxZ - (trim.frontLeftZ * (trim.frontLeftX + minX)) / trim.frontLeftX);
429
+ }
430
+ if (trim.frontRightX > 0 && trim.frontRightZ > 0 && maxX - trim.frontRightX < 0) {
431
+ maxZ = Math.min(maxZ, maxZ - (trim.frontRightZ * (trim.frontRightX - maxX)) / trim.frontRightX);
432
+ }
433
+ if (trim.backLeftX > 0 && trim.backLeftZ > 0 && minX + trim.backLeftX > 0) {
434
+ minZ = Math.max(minZ, minZ + (trim.backLeftZ * (trim.backLeftX + minX)) / trim.backLeftX);
435
+ }
436
+ if (trim.backRightX > 0 && trim.backRightZ > 0 && maxX - trim.backRightX < 0) {
437
+ minZ = Math.max(minZ, minZ + (trim.backRightZ * (trim.backRightX - maxX)) / trim.backRightX);
438
+ }
439
+ return {
440
+ minX,
441
+ maxX,
442
+ minZ,
443
+ maxZ,
444
+ width: Math.max(0.01, maxX - minX),
445
+ depth: Math.max(0.01, maxZ - minZ),
446
+ };
447
+ }
448
+ /** Segment-local surface height used by roof accessory placement and hit disambiguation. */
449
+ export function getRoofSegmentSurfaceY(node, localX, localZ) {
450
+ const slopeFrame = getSegmentSlopeFrame(node);
451
+ const activeRh = slopeFrame.activeRh;
452
+ const peakY = node.wallHeight + activeRh;
453
+ if (activeRh === 0)
454
+ return node.wallHeight;
455
+ if (node.roofType === 'gable' || node.roofType === 'gambrel' || node.roofType === 'mansard') {
456
+ const t = node.depth > 0 ? Math.abs(localZ) / (node.depth / 2) : 0;
457
+ return peakY - t * activeRh;
458
+ }
459
+ if (node.roofType === 'dutch') {
460
+ const hipHeightRatio = node.dutchHipHeightRatio ?? ROOF_SHAPE_DEFAULTS.dutchHipHeightRatio;
461
+ const metrics = getDutchUpperShellBounds(node);
462
+ const lowerRise = activeRh * hipHeightRatio;
463
+ if (metrics.axis === 'x') {
464
+ const waistHalfZ = Math.max(0.0001, metrics.waistHalfZ);
465
+ if (Math.abs(localX) <= metrics.upperHalfX && Math.abs(localZ) <= waistHalfZ) {
466
+ const upperRise = activeRh * (1 - hipHeightRatio);
467
+ const upperTan = upperRise / waistHalfZ;
468
+ return peakY - Math.abs(localZ) * upperTan;
469
+ }
470
+ const xProgressDenom = Math.max(0.0001, node.width / 2 - metrics.waistHalfX);
471
+ const zProgressDenom = Math.max(0.0001, node.depth / 2 - waistHalfZ);
472
+ const xProgress = Math.max(0, Math.abs(localX) - metrics.waistHalfX) / xProgressDenom;
473
+ const zProgress = Math.max(0, Math.abs(localZ) - waistHalfZ) / zProgressDenom;
474
+ return node.wallHeight + lowerRise * (1 - Math.min(1, Math.max(xProgress, zProgress)));
475
+ }
476
+ const waistHalfX = Math.max(0.0001, metrics.waistHalfX);
477
+ if (Math.abs(localX) <= waistHalfX && Math.abs(localZ) <= metrics.upperHalfZ) {
478
+ const upperRise = activeRh * (1 - hipHeightRatio);
479
+ const upperRun = waistHalfX;
480
+ const upperTan = upperRise / upperRun;
481
+ return peakY - Math.abs(localX) * upperTan;
482
+ }
483
+ const xProgressDenom = Math.max(0.0001, node.width / 2 - waistHalfX);
484
+ const zProgressDenom = Math.max(0.0001, node.depth / 2 - metrics.waistHalfZ);
485
+ const xProgress = Math.max(0, Math.abs(localX) - waistHalfX) / xProgressDenom;
486
+ const zProgress = Math.max(0, Math.abs(localZ) - metrics.waistHalfZ) / zProgressDenom;
487
+ return node.wallHeight + lowerRise * (1 - Math.min(1, Math.max(xProgress, zProgress)));
488
+ }
489
+ if (node.roofType === 'shed') {
490
+ const t = (localZ + node.depth / 2) / (node.depth || 1);
491
+ return peakY - t * activeRh;
492
+ }
493
+ if (node.roofType === 'hip') {
494
+ const fx = node.width > 0 ? Math.abs(localX) / (node.width / 2) : 0;
495
+ const fz = node.depth > 0 ? Math.abs(localZ) / (node.depth / 2) : 0;
496
+ return peakY - Math.max(fx, fz) * activeRh;
497
+ }
498
+ const t = node.depth > 0 ? Math.abs(localZ) / (node.depth / 2) : 0;
499
+ return peakY - t * activeRh;
500
+ }
501
+ /**
502
+ * Inverse of `getActiveRoofHeight` — recover the pitch a legacy
503
+ * `roofHeight` value would correspond to. Used by the scene migration.
504
+ * Ratio overrides are optional and default to the shape defaults.
505
+ */
506
+ export function getPitchFromActiveRoofHeight(input) {
507
+ if (input.roofType === 'flat' || input.roofHeight <= 0)
508
+ return 0;
509
+ const ratios = withRatioDefaults(input);
510
+ const run = getPrimarySlopeRun(ratios);
511
+ if (run <= 0)
512
+ return 0;
513
+ const rise = input.roofHeight * getPrimarySlopeRiseFraction(ratios);
514
+ return (Math.atan2(rise, run) * 180) / Math.PI;
515
+ }
516
+ function getLegacyRoofSegmentSurfaceMaterial(node) {
517
+ return {
518
+ material: node.material,
519
+ materialPreset: typeof node.materialPreset === 'string' ? node.materialPreset : undefined,
520
+ };
521
+ }
522
+ /**
523
+ * Resolve the segment-level material for one of the three surface roles.
524
+ * Falls back through: role-specific field → catch-all `material`. Pass the
525
+ * parent roof to `parentFallback` when you want the roof's role material
526
+ * to fill in for an unset segment slot — typical from the renderer.
527
+ */
528
+ export function getEffectiveSegmentSurfaceMaterial(node, role, parentFallback) {
529
+ if (role === 'top') {
530
+ if (node.topMaterial !== undefined || typeof node.topMaterialPreset === 'string') {
531
+ return {
532
+ material: node.topMaterial,
533
+ materialPreset: typeof node.topMaterialPreset === 'string' ? node.topMaterialPreset : undefined,
534
+ };
535
+ }
536
+ }
537
+ else if (role === 'edge') {
538
+ if (node.edgeMaterial !== undefined || typeof node.edgeMaterialPreset === 'string') {
539
+ return {
540
+ material: node.edgeMaterial,
541
+ materialPreset: typeof node.edgeMaterialPreset === 'string' ? node.edgeMaterialPreset : undefined,
542
+ };
543
+ }
544
+ }
545
+ else if (role === 'wall') {
546
+ if (node.wallMaterial !== undefined || typeof node.wallMaterialPreset === 'string') {
547
+ return {
548
+ material: node.wallMaterial,
549
+ materialPreset: typeof node.wallMaterialPreset === 'string' ? node.wallMaterialPreset : undefined,
550
+ };
551
+ }
552
+ }
553
+ const legacy = getLegacyRoofSegmentSurfaceMaterial(node);
554
+ if (legacy.material !== undefined || legacy.materialPreset !== undefined)
555
+ return legacy;
556
+ return parentFallback ?? { material: undefined, materialPreset: undefined };
557
+ }
558
+ /**
559
+ * Returns true when the segment has any segment-level material override —
560
+ * either the legacy catch-all or any of the three role-specific fields.
561
+ * Used by `RoofRenderer` and `updateMergedRoofGeometry` to decide whether
562
+ * the segment should be drawn as its own mesh or folded into the merged
563
+ * shell.
564
+ */
565
+ export function hasSegmentMaterialOverride(node) {
566
+ return (node.material !== undefined ||
567
+ typeof node.materialPreset === 'string' ||
568
+ node.topMaterial !== undefined ||
569
+ typeof node.topMaterialPreset === 'string' ||
570
+ node.edgeMaterial !== undefined ||
571
+ typeof node.edgeMaterialPreset === 'string' ||
572
+ node.wallMaterial !== undefined ||
573
+ typeof node.wallMaterialPreset === 'string');
574
+ }
@@ -25,7 +25,7 @@ export declare const RoofNode: z.ZodObject<{
25
25
  type: z.ZodDefault<z.ZodLiteral<"roof">>;
26
26
  material: z.ZodOptional<z.ZodObject<{
27
27
  id: z.ZodOptional<z.ZodString>;
28
- preset: z.ZodOptional<z.ZodEnum<{
28
+ preset: z.ZodOptional<z.ZodCatch<z.ZodEnum<{
29
29
  custom: "custom";
30
30
  white: "white";
31
31
  brick: "brick";
@@ -36,7 +36,7 @@ export declare const RoofNode: z.ZodObject<{
36
36
  plaster: "plaster";
37
37
  tile: "tile";
38
38
  marble: "marble";
39
- }>>;
39
+ }>>>;
40
40
  properties: z.ZodOptional<z.ZodObject<{
41
41
  color: z.ZodDefault<z.ZodString>;
42
42
  roughness: z.ZodDefault<z.ZodNumber>;
@@ -58,7 +58,7 @@ export declare const RoofNode: z.ZodObject<{
58
58
  materialPreset: z.ZodOptional<z.ZodString>;
59
59
  topMaterial: z.ZodOptional<z.ZodObject<{
60
60
  id: z.ZodOptional<z.ZodString>;
61
- preset: z.ZodOptional<z.ZodEnum<{
61
+ preset: z.ZodOptional<z.ZodCatch<z.ZodEnum<{
62
62
  custom: "custom";
63
63
  white: "white";
64
64
  brick: "brick";
@@ -69,7 +69,7 @@ export declare const RoofNode: z.ZodObject<{
69
69
  plaster: "plaster";
70
70
  tile: "tile";
71
71
  marble: "marble";
72
- }>>;
72
+ }>>>;
73
73
  properties: z.ZodOptional<z.ZodObject<{
74
74
  color: z.ZodDefault<z.ZodString>;
75
75
  roughness: z.ZodDefault<z.ZodNumber>;
@@ -91,7 +91,7 @@ export declare const RoofNode: z.ZodObject<{
91
91
  topMaterialPreset: z.ZodOptional<z.ZodString>;
92
92
  edgeMaterial: z.ZodOptional<z.ZodObject<{
93
93
  id: z.ZodOptional<z.ZodString>;
94
- preset: z.ZodOptional<z.ZodEnum<{
94
+ preset: z.ZodOptional<z.ZodCatch<z.ZodEnum<{
95
95
  custom: "custom";
96
96
  white: "white";
97
97
  brick: "brick";
@@ -102,7 +102,7 @@ export declare const RoofNode: z.ZodObject<{
102
102
  plaster: "plaster";
103
103
  tile: "tile";
104
104
  marble: "marble";
105
- }>>;
105
+ }>>>;
106
106
  properties: z.ZodOptional<z.ZodObject<{
107
107
  color: z.ZodDefault<z.ZodString>;
108
108
  roughness: z.ZodDefault<z.ZodNumber>;
@@ -124,7 +124,7 @@ export declare const RoofNode: z.ZodObject<{
124
124
  edgeMaterialPreset: z.ZodOptional<z.ZodString>;
125
125
  wallMaterial: z.ZodOptional<z.ZodObject<{
126
126
  id: z.ZodOptional<z.ZodString>;
127
- preset: z.ZodOptional<z.ZodEnum<{
127
+ preset: z.ZodOptional<z.ZodCatch<z.ZodEnum<{
128
128
  custom: "custom";
129
129
  white: "white";
130
130
  brick: "brick";
@@ -135,7 +135,7 @@ export declare const RoofNode: z.ZodObject<{
135
135
  plaster: "plaster";
136
136
  tile: "tile";
137
137
  marble: "marble";
138
- }>>;
138
+ }>>>;
139
139
  properties: z.ZodOptional<z.ZodObject<{
140
140
  color: z.ZodDefault<z.ZodString>;
141
141
  roughness: z.ZodDefault<z.ZodNumber>;
@@ -1 +1 @@
1
- {"version":3,"file":"roof.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/roof.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,cAAc,IAAI,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIvE,MAAM,MAAM,uBAAuB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAC7D,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyBpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA;AAS/C,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,uBAAuB,GAC5B,uBAAuB,CAoDzB"}
1
+ {"version":3,"file":"roof.d.ts","sourceRoot":"","sources":["../../../src/schema/nodes/roof.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,cAAc,IAAI,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIvE,MAAM,MAAM,uBAAuB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAC7D,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyBpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA;AAS/C,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,uBAAuB,GAC5B,uBAAuB,CAoCzB"}
@@ -58,21 +58,9 @@ export function getEffectiveRoofSurfaceMaterial(node, role) {
58
58
  };
59
59
  }
60
60
  }
61
- if (role === 'edge') {
62
- if (node.wallMaterial !== undefined || typeof node.wallMaterialPreset === 'string') {
63
- return {
64
- material: node.wallMaterial,
65
- materialPreset: typeof node.wallMaterialPreset === 'string' ? node.wallMaterialPreset : undefined,
66
- };
67
- }
68
- }
69
- if (role === 'wall') {
70
- if (node.edgeMaterial !== undefined || typeof node.edgeMaterialPreset === 'string') {
71
- return {
72
- material: node.edgeMaterial,
73
- materialPreset: typeof node.edgeMaterialPreset === 'string' ? node.edgeMaterialPreset : undefined,
74
- };
75
- }
76
- }
61
+ // No cross-role fallback: an unset role resolves only to the legacy
62
+ // catch-all (which covers all three roles for back-compat) and otherwise
63
+ // to the caller's theme default. Painting one surface must never bleed
64
+ // onto the others.
77
65
  return getLegacyRoofSurfaceMaterial(node);
78
66
  }