@pascal-app/core 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1003) hide show
  1. package/dist/agent-operations/__fixtures__/add-object-cases.d.ts +31 -0
  2. package/dist/agent-operations/__fixtures__/add-object-cases.d.ts.map +1 -1
  3. package/dist/agent-operations/__fixtures__/build-room-cases.d.ts.map +1 -1
  4. package/dist/agent-operations/__fixtures__/build-room-cases.js +27 -0
  5. package/dist/agent-operations/__fixtures__/cases.d.ts.map +1 -1
  6. package/dist/agent-operations/__fixtures__/cases.js +19 -11
  7. package/dist/agent-operations/__fixtures__/design-brief-cases.d.ts +25 -2
  8. package/dist/agent-operations/__fixtures__/design-brief-cases.d.ts.map +1 -1
  9. package/dist/agent-operations/__fixtures__/design-brief-cases.js +509 -66
  10. package/dist/agent-operations/__fixtures__/dressing-scenes.d.ts +30 -0
  11. package/dist/agent-operations/__fixtures__/dressing-scenes.d.ts.map +1 -0
  12. package/dist/agent-operations/__fixtures__/dressing-scenes.js +82 -0
  13. package/dist/agent-operations/__fixtures__/native-materials.d.ts +411 -9
  14. package/dist/agent-operations/__fixtures__/native-materials.d.ts.map +1 -1
  15. package/dist/agent-operations/__fixtures__/notation-cases.d.ts.map +1 -1
  16. package/dist/agent-operations/__fixtures__/notation-cases.js +14 -0
  17. package/dist/agent-operations/__fixtures__/roof-bearing-cases.d.ts +13804 -10600
  18. package/dist/agent-operations/__fixtures__/roof-bearing-cases.d.ts.map +1 -1
  19. package/dist/agent-operations/__fixtures__/room-cases.d.ts +1 -0
  20. package/dist/agent-operations/__fixtures__/room-cases.d.ts.map +1 -1
  21. package/dist/agent-operations/__fixtures__/room-cases.js +57 -1
  22. package/dist/agent-operations/__fixtures__/room-check-scenes.d.ts +51 -0
  23. package/dist/agent-operations/__fixtures__/room-check-scenes.d.ts.map +1 -0
  24. package/dist/agent-operations/__fixtures__/room-check-scenes.js +121 -0
  25. package/dist/agent-operations/__fixtures__/scene-check-cases.d.ts.map +1 -1
  26. package/dist/agent-operations/__fixtures__/scene-check-cases.js +152 -18
  27. package/dist/agent-operations/__fixtures__/search-assets-cases.d.ts.map +1 -1
  28. package/dist/agent-operations/__fixtures__/search-assets-cases.js +91 -0
  29. package/dist/agent-operations/__fixtures__/site-cases.d.ts +40 -1
  30. package/dist/agent-operations/__fixtures__/site-cases.d.ts.map +1 -1
  31. package/dist/agent-operations/__fixtures__/structure-cases.d.ts +3 -0
  32. package/dist/agent-operations/__fixtures__/structure-cases.d.ts.map +1 -1
  33. package/dist/agent-operations/__fixtures__/structure-cases.js +199 -18
  34. package/dist/agent-operations/__fixtures__/unit-cases.d.ts +6 -0
  35. package/dist/agent-operations/__fixtures__/unit-cases.d.ts.map +1 -0
  36. package/dist/agent-operations/__fixtures__/unit-cases.js +266 -0
  37. package/dist/agent-operations/__fixtures__/verify-scene-cases.d.ts +37 -1
  38. package/dist/agent-operations/__fixtures__/verify-scene-cases.d.ts.map +1 -1
  39. package/dist/agent-operations/add-column.d.ts.map +1 -1
  40. package/dist/agent-operations/add-column.js +32 -3
  41. package/dist/agent-operations/add-fence.d.ts.map +1 -1
  42. package/dist/agent-operations/add-fence.js +2 -1
  43. package/dist/agent-operations/add-level.d.ts.map +1 -1
  44. package/dist/agent-operations/add-level.js +4 -4
  45. package/dist/agent-operations/add-object.d.ts +32 -6
  46. package/dist/agent-operations/add-object.d.ts.map +1 -1
  47. package/dist/agent-operations/add-object.js +59 -12
  48. package/dist/agent-operations/add-wall.d.ts +3 -8
  49. package/dist/agent-operations/add-wall.d.ts.map +1 -1
  50. package/dist/agent-operations/add-wall.js +31 -8
  51. package/dist/agent-operations/apply-changes.d.ts.map +1 -1
  52. package/dist/agent-operations/apply-changes.js +2 -1
  53. package/dist/agent-operations/asset-catalog.d.ts +63 -0
  54. package/dist/agent-operations/asset-catalog.d.ts.map +1 -0
  55. package/dist/agent-operations/asset-catalog.js +182 -0
  56. package/dist/agent-operations/asset-views.d.ts +4 -2
  57. package/dist/agent-operations/asset-views.d.ts.map +1 -1
  58. package/dist/agent-operations/asset-views.js +1 -3
  59. package/dist/agent-operations/author-scene-checks.d.ts +38 -0
  60. package/dist/agent-operations/author-scene-checks.d.ts.map +1 -0
  61. package/dist/agent-operations/author-scene-checks.js +266 -0
  62. package/dist/agent-operations/author-scene.d.ts +54 -20
  63. package/dist/agent-operations/author-scene.d.ts.map +1 -1
  64. package/dist/agent-operations/author-scene.js +1163 -196
  65. package/dist/agent-operations/authored-scene-merge.d.ts.map +1 -1
  66. package/dist/agent-operations/authored-scene-merge.js +61 -3
  67. package/dist/agent-operations/batch.d.ts +1 -1
  68. package/dist/agent-operations/batch.d.ts.map +1 -1
  69. package/dist/agent-operations/batch.js +38 -39
  70. package/dist/agent-operations/ceiling-object-observations.d.ts.map +1 -1
  71. package/dist/agent-operations/ceiling-object-observations.js +5 -4
  72. package/dist/agent-operations/circulation-geometry.d.ts +14 -0
  73. package/dist/agent-operations/circulation-geometry.d.ts.map +1 -0
  74. package/dist/agent-operations/circulation-geometry.js +151 -0
  75. package/dist/agent-operations/circulation-graph.d.ts +72 -0
  76. package/dist/agent-operations/circulation-graph.d.ts.map +1 -0
  77. package/dist/agent-operations/circulation-graph.js +713 -0
  78. package/dist/agent-operations/circulation-non-spaces.d.ts +10 -0
  79. package/dist/agent-operations/circulation-non-spaces.d.ts.map +1 -0
  80. package/dist/agent-operations/circulation-non-spaces.js +88 -0
  81. package/dist/agent-operations/circulation-spaces.d.ts +5881 -0
  82. package/dist/agent-operations/circulation-spaces.d.ts.map +1 -0
  83. package/dist/agent-operations/circulation-spaces.js +368 -0
  84. package/dist/agent-operations/collections.d.ts.map +1 -1
  85. package/dist/agent-operations/collections.js +9 -7
  86. package/dist/agent-operations/corner-window.d.ts.map +1 -1
  87. package/dist/agent-operations/corner-window.js +2 -4
  88. package/dist/agent-operations/create-roof.d.ts.map +1 -1
  89. package/dist/agent-operations/create-roof.js +5 -7
  90. package/dist/agent-operations/create-room.d.ts +4 -1
  91. package/dist/agent-operations/create-room.d.ts.map +1 -1
  92. package/dist/agent-operations/create-room.js +7 -4
  93. package/dist/agent-operations/create-stair.js +2 -2
  94. package/dist/agent-operations/design-brief.d.ts +13 -7
  95. package/dist/agent-operations/design-brief.d.ts.map +1 -1
  96. package/dist/agent-operations/design-brief.js +172 -92
  97. package/dist/agent-operations/door-clearance.d.ts +68 -8
  98. package/dist/agent-operations/door-clearance.d.ts.map +1 -1
  99. package/dist/agent-operations/door-clearance.js +127 -20
  100. package/dist/agent-operations/dress-targets.d.ts +23 -0
  101. package/dist/agent-operations/dress-targets.d.ts.map +1 -0
  102. package/dist/agent-operations/dress-targets.js +34 -0
  103. package/dist/agent-operations/duplicate-node.d.ts +14 -0
  104. package/dist/agent-operations/duplicate-node.d.ts.map +1 -0
  105. package/dist/agent-operations/duplicate-node.js +173 -0
  106. package/dist/agent-operations/find-by-type.d.ts.map +1 -1
  107. package/dist/agent-operations/find-by-type.js +3 -0
  108. package/dist/agent-operations/furnish-room.d.ts +2 -0
  109. package/dist/agent-operations/furnish-room.d.ts.map +1 -1
  110. package/dist/agent-operations/furnish-room.js +15 -4
  111. package/dist/agent-operations/furniture-kind.d.ts +28 -1
  112. package/dist/agent-operations/furniture-kind.d.ts.map +1 -1
  113. package/dist/agent-operations/furniture-kind.js +105 -6
  114. package/dist/agent-operations/ground-reader.d.ts +10 -0
  115. package/dist/agent-operations/ground-reader.d.ts.map +1 -0
  116. package/dist/agent-operations/ground-reader.js +25 -0
  117. package/dist/agent-operations/index.d.ts +118 -12
  118. package/dist/agent-operations/index.d.ts.map +1 -1
  119. package/dist/agent-operations/index.js +26 -1
  120. package/dist/agent-operations/item-solids.d.ts +39 -0
  121. package/dist/agent-operations/item-solids.d.ts.map +1 -0
  122. package/dist/agent-operations/item-solids.js +128 -0
  123. package/dist/agent-operations/layout-clearance.d.ts +91 -11
  124. package/dist/agent-operations/layout-clearance.d.ts.map +1 -1
  125. package/dist/agent-operations/layout-clearance.js +280 -73
  126. package/dist/agent-operations/level-height-fill.d.ts.map +1 -1
  127. package/dist/agent-operations/level-height-fill.js +7 -1
  128. package/dist/agent-operations/level-reads.d.ts +5 -0
  129. package/dist/agent-operations/level-reads.d.ts.map +1 -1
  130. package/dist/agent-operations/level-reads.js +39 -3
  131. package/dist/agent-operations/list-levels.d.ts.map +1 -1
  132. package/dist/agent-operations/list-levels.js +2 -4
  133. package/dist/agent-operations/move-node.d.ts +12 -0
  134. package/dist/agent-operations/move-node.d.ts.map +1 -0
  135. package/dist/agent-operations/move-node.js +28 -0
  136. package/dist/agent-operations/move-plan.d.ts +28 -0
  137. package/dist/agent-operations/move-plan.d.ts.map +1 -0
  138. package/dist/agent-operations/move-plan.js +259 -0
  139. package/dist/agent-operations/node-patch.d.ts.map +1 -1
  140. package/dist/agent-operations/node-patch.js +15 -0
  141. package/dist/agent-operations/paint.d.ts +3 -1
  142. package/dist/agent-operations/paint.d.ts.map +1 -1
  143. package/dist/agent-operations/paint.js +14 -1
  144. package/dist/agent-operations/photo-elevation.d.ts.map +1 -1
  145. package/dist/agent-operations/photo-elevation.js +2 -1
  146. package/dist/agent-operations/place-in-room.d.ts.map +1 -1
  147. package/dist/agent-operations/place-in-room.js +1 -0
  148. package/dist/agent-operations/place-items.d.ts.map +1 -1
  149. package/dist/agent-operations/place-items.js +3 -4
  150. package/dist/agent-operations/plan-clearance.d.ts +28 -0
  151. package/dist/agent-operations/plan-clearance.d.ts.map +1 -0
  152. package/dist/agent-operations/plan-clearance.js +47 -0
  153. package/dist/agent-operations/plan-geometry.d.ts +2 -0
  154. package/dist/agent-operations/plan-geometry.d.ts.map +1 -1
  155. package/dist/agent-operations/plan-geometry.js +10 -0
  156. package/dist/agent-operations/project-files.d.ts +10 -0
  157. package/dist/agent-operations/project-files.d.ts.map +1 -1
  158. package/dist/agent-operations/reachability.d.ts +41 -0
  159. package/dist/agent-operations/reachability.d.ts.map +1 -0
  160. package/dist/agent-operations/reachability.js +199 -0
  161. package/dist/agent-operations/room-checks.d.ts +60 -0
  162. package/dist/agent-operations/room-checks.d.ts.map +1 -0
  163. package/dist/agent-operations/room-checks.js +226 -0
  164. package/dist/agent-operations/room-faces.d.ts +30 -0
  165. package/dist/agent-operations/room-faces.d.ts.map +1 -0
  166. package/dist/agent-operations/room-faces.js +31 -0
  167. package/dist/agent-operations/room-findings.d.ts +5 -2
  168. package/dist/agent-operations/room-findings.d.ts.map +1 -1
  169. package/dist/agent-operations/room-findings.js +30 -0
  170. package/dist/agent-operations/room-finishes.d.ts +87 -0
  171. package/dist/agent-operations/room-finishes.d.ts.map +1 -0
  172. package/dist/agent-operations/room-finishes.js +193 -0
  173. package/dist/agent-operations/room-floor-script.d.ts +3 -3
  174. package/dist/agent-operations/room-floor-script.js +3 -3
  175. package/dist/agent-operations/room-readings.d.ts +90 -0
  176. package/dist/agent-operations/room-readings.d.ts.map +1 -0
  177. package/dist/agent-operations/room-readings.js +369 -0
  178. package/dist/agent-operations/room-tile.d.ts +15 -0
  179. package/dist/agent-operations/room-tile.d.ts.map +1 -0
  180. package/dist/agent-operations/room-tile.js +30 -0
  181. package/dist/agent-operations/room-wet.d.ts +55 -0
  182. package/dist/agent-operations/room-wet.d.ts.map +1 -0
  183. package/dist/agent-operations/room-wet.js +203 -0
  184. package/dist/agent-operations/scene-check-policy.d.ts +5 -0
  185. package/dist/agent-operations/scene-check-policy.d.ts.map +1 -0
  186. package/dist/agent-operations/scene-check-policy.js +24 -0
  187. package/dist/agent-operations/scene-checkpoint.d.ts +10 -0
  188. package/dist/agent-operations/scene-checkpoint.d.ts.map +1 -1
  189. package/dist/agent-operations/scene-checkpoint.js +31 -0
  190. package/dist/agent-operations/scene-issue-groups.d.ts +17 -0
  191. package/dist/agent-operations/scene-issue-groups.d.ts.map +1 -0
  192. package/dist/agent-operations/scene-issue-groups.js +71 -0
  193. package/dist/agent-operations/scene-materials.d.ts +23 -0
  194. package/dist/agent-operations/scene-materials.d.ts.map +1 -1
  195. package/dist/agent-operations/scene-materials.js +41 -0
  196. package/dist/agent-operations/scene-queries.d.ts +2525 -1723
  197. package/dist/agent-operations/scene-queries.d.ts.map +1 -1
  198. package/dist/agent-operations/scene-queries.js +3 -3
  199. package/dist/agent-operations/scene-review.d.ts.map +1 -1
  200. package/dist/agent-operations/scene-review.js +3 -2
  201. package/dist/agent-operations/scene-view.d.ts.map +1 -1
  202. package/dist/agent-operations/scene-view.js +91 -21
  203. package/dist/agent-operations/search-assets.d.ts +10 -6
  204. package/dist/agent-operations/search-assets.d.ts.map +1 -1
  205. package/dist/agent-operations/search-assets.js +37 -22
  206. package/dist/agent-operations/set-checkpoint.d.ts.map +1 -1
  207. package/dist/agent-operations/set-checkpoint.js +1 -0
  208. package/dist/agent-operations/set-ground-level.d.ts +1 -1
  209. package/dist/agent-operations/set-ground-level.d.ts.map +1 -1
  210. package/dist/agent-operations/set-ground-level.js +3 -14
  211. package/dist/agent-operations/site.js +3 -2
  212. package/dist/agent-operations/slab-surface-script.d.ts +25 -0
  213. package/dist/agent-operations/slab-surface-script.d.ts.map +1 -0
  214. package/dist/agent-operations/slab-surface-script.js +60 -0
  215. package/dist/agent-operations/stair-plan-footprint.d.ts +9 -0
  216. package/dist/agent-operations/stair-plan-footprint.d.ts.map +1 -0
  217. package/dist/agent-operations/stair-plan-footprint.js +50 -0
  218. package/dist/agent-operations/types.d.ts +10 -5
  219. package/dist/agent-operations/types.d.ts.map +1 -1
  220. package/dist/agent-operations/units.d.ts +26 -0
  221. package/dist/agent-operations/units.d.ts.map +1 -0
  222. package/dist/agent-operations/units.js +110 -0
  223. package/dist/agent-operations/unreachable-rooms-check.d.ts +17 -0
  224. package/dist/agent-operations/unreachable-rooms-check.d.ts.map +1 -0
  225. package/dist/agent-operations/unreachable-rooms-check.js +35 -0
  226. package/dist/agent-operations/usage-checks.d.ts +2 -0
  227. package/dist/agent-operations/usage-checks.d.ts.map +1 -0
  228. package/dist/agent-operations/usage-checks.js +12 -0
  229. package/dist/agent-operations/verify-scene.d.ts +8 -11
  230. package/dist/agent-operations/verify-scene.d.ts.map +1 -1
  231. package/dist/agent-operations/verify-scene.js +59 -13
  232. package/dist/agent-operations/wall-face-defaults.d.ts +44 -0
  233. package/dist/agent-operations/wall-face-defaults.d.ts.map +1 -0
  234. package/dist/agent-operations/wall-face-defaults.js +229 -0
  235. package/dist/agent-operations/wall-joins.d.ts +119 -0
  236. package/dist/agent-operations/wall-joins.d.ts.map +1 -0
  237. package/dist/agent-operations/wall-joins.js +308 -0
  238. package/dist/agent-operations/wall-object-observations.d.ts.map +1 -1
  239. package/dist/agent-operations/wall-object-observations.js +5 -7
  240. package/dist/agent-operations/wall-skirting.d.ts.map +1 -1
  241. package/dist/agent-operations/wall-skirting.js +2 -0
  242. package/dist/agent-operations/wall-type.d.ts +49 -0
  243. package/dist/agent-operations/wall-type.d.ts.map +1 -0
  244. package/dist/agent-operations/wall-type.js +31 -0
  245. package/dist/agent-operations/zone-coverage.d.ts +85 -0
  246. package/dist/agent-operations/zone-coverage.d.ts.map +1 -0
  247. package/dist/agent-operations/zone-coverage.js +208 -0
  248. package/dist/agent-tools/add-object.d.ts +31 -0
  249. package/dist/agent-tools/add-object.d.ts.map +1 -1
  250. package/dist/agent-tools/add-object.js +44 -7
  251. package/dist/agent-tools/assets.d.ts +4 -0
  252. package/dist/agent-tools/assets.d.ts.map +1 -1
  253. package/dist/agent-tools/assets.js +11 -1
  254. package/dist/agent-tools/author-scene.d.ts +810 -15
  255. package/dist/agent-tools/author-scene.d.ts.map +1 -1
  256. package/dist/agent-tools/author-scene.js +275 -34
  257. package/dist/agent-tools/batch.d.ts +1 -1
  258. package/dist/agent-tools/batch.d.ts.map +1 -1
  259. package/dist/agent-tools/batch.js +5 -1
  260. package/dist/agent-tools/checkpoints.js +1 -1
  261. package/dist/agent-tools/columns.d.ts +14 -0
  262. package/dist/agent-tools/columns.d.ts.map +1 -1
  263. package/dist/agent-tools/columns.js +14 -2
  264. package/dist/agent-tools/contract.d.ts +10 -0
  265. package/dist/agent-tools/contract.d.ts.map +1 -1
  266. package/dist/agent-tools/contract.js +5 -1
  267. package/dist/agent-tools/corner-window.d.ts +1 -0
  268. package/dist/agent-tools/corner-window.d.ts.map +1 -1
  269. package/dist/agent-tools/corner-window.js +1 -1
  270. package/dist/agent-tools/create-room.d.ts +85 -0
  271. package/dist/agent-tools/create-room.d.ts.map +1 -1
  272. package/dist/agent-tools/create-room.js +8 -1
  273. package/dist/agent-tools/design-brief.d.ts +513 -7
  274. package/dist/agent-tools/design-brief.d.ts.map +1 -1
  275. package/dist/agent-tools/design-brief.js +192 -14
  276. package/dist/agent-tools/designs.js +1 -1
  277. package/dist/agent-tools/find-by-type.js +1 -1
  278. package/dist/agent-tools/index.d.ts +658 -10
  279. package/dist/agent-tools/index.d.ts.map +1 -1
  280. package/dist/agent-tools/index.js +9 -1
  281. package/dist/agent-tools/levels.js +4 -4
  282. package/dist/agent-tools/move-duplicate.d.ts +65 -0
  283. package/dist/agent-tools/move-duplicate.d.ts.map +1 -0
  284. package/dist/agent-tools/move-duplicate.js +68 -0
  285. package/dist/agent-tools/paint.d.ts +6 -0
  286. package/dist/agent-tools/paint.d.ts.map +1 -1
  287. package/dist/agent-tools/paint.js +7 -3
  288. package/dist/agent-tools/photo-elevation.js +1 -1
  289. package/dist/agent-tools/project-files.d.ts +2 -2
  290. package/dist/agent-tools/project-files.d.ts.map +1 -1
  291. package/dist/agent-tools/project-files.js +1 -1
  292. package/dist/agent-tools/refusal.d.ts +9 -1
  293. package/dist/agent-tools/refusal.d.ts.map +1 -1
  294. package/dist/agent-tools/refusal.js +5 -1
  295. package/dist/agent-tools/roofs.js +1 -1
  296. package/dist/agent-tools/room-structure.d.ts +60 -0
  297. package/dist/agent-tools/room-structure.d.ts.map +1 -1
  298. package/dist/agent-tools/room-structure.js +20 -1
  299. package/dist/agent-tools/search-materials.d.ts +1 -0
  300. package/dist/agent-tools/search-materials.d.ts.map +1 -1
  301. package/dist/agent-tools/site.js +1 -1
  302. package/dist/agent-tools/stairs.js +1 -1
  303. package/dist/agent-tools/units.d.ts +84 -0
  304. package/dist/agent-tools/units.d.ts.map +1 -0
  305. package/dist/agent-tools/units.js +54 -0
  306. package/dist/agent-tools/verify-scene-usage.d.ts +44 -0
  307. package/dist/agent-tools/verify-scene-usage.d.ts.map +1 -0
  308. package/dist/agent-tools/verify-scene-usage.js +146 -0
  309. package/dist/agent-tools/verify-scene.d.ts +12 -1
  310. package/dist/agent-tools/verify-scene.d.ts.map +1 -1
  311. package/dist/agent-tools/verify-scene.js +183 -3
  312. package/dist/agent-tools/view-scene.d.ts +3 -0
  313. package/dist/agent-tools/view-scene.d.ts.map +1 -1
  314. package/dist/agent-tools/view-scene.js +4 -1
  315. package/dist/agent-tools/wall-openings.d.ts +121 -2
  316. package/dist/agent-tools/wall-openings.d.ts.map +1 -1
  317. package/dist/agent-tools/wall-openings.js +123 -18
  318. package/dist/agent-tools/walls.d.ts +30 -0
  319. package/dist/agent-tools/walls.d.ts.map +1 -1
  320. package/dist/agent-tools/walls.js +54 -2
  321. package/dist/building/__fixtures__/wall-opening-cases.d.ts +181 -35
  322. package/dist/building/__fixtures__/wall-opening-cases.d.ts.map +1 -1
  323. package/dist/building/__fixtures__/wall-opening-cases.js +13 -0
  324. package/dist/building/code-profile.d.ts +231 -0
  325. package/dist/building/code-profile.d.ts.map +1 -0
  326. package/dist/building/code-profile.js +208 -0
  327. package/dist/building/corner-window.d.ts +2 -2
  328. package/dist/building/corner-window.d.ts.map +1 -1
  329. package/dist/building/corner-window.js +11 -2
  330. package/dist/building/door-types.d.ts +1 -0
  331. package/dist/building/door-types.d.ts.map +1 -1
  332. package/dist/building/door-types.js +2 -0
  333. package/dist/building/floor-item-fit.d.ts.map +1 -1
  334. package/dist/building/floor-item-fit.js +2 -4
  335. package/dist/building/ground-level.d.ts +1 -3
  336. package/dist/building/ground-level.d.ts.map +1 -1
  337. package/dist/building/ground-level.js +3 -5
  338. package/dist/building/index.d.ts +2 -0
  339. package/dist/building/index.d.ts.map +1 -1
  340. package/dist/building/index.js +2 -0
  341. package/dist/building/level-duplication.d.ts +4667 -3065
  342. package/dist/building/level-duplication.d.ts.map +1 -1
  343. package/dist/building/level-insertion.d.ts +1 -1
  344. package/dist/building/level-wall-copy.d.ts +24 -0
  345. package/dist/building/level-wall-copy.d.ts.map +1 -0
  346. package/dist/building/level-wall-copy.js +53 -0
  347. package/dist/building/merge-windows.d.ts.map +1 -1
  348. package/dist/building/merge-windows.js +5 -3
  349. package/dist/building/opening-style-presets.d.ts +2 -0
  350. package/dist/building/opening-style-presets.d.ts.map +1 -1
  351. package/dist/building/opening-style-presets.js +26 -5
  352. package/dist/building/room-use.d.ts +26 -0
  353. package/dist/building/room-use.d.ts.map +1 -0
  354. package/dist/building/room-use.js +102 -0
  355. package/dist/building/wall-openings.d.ts +134 -12
  356. package/dist/building/wall-openings.d.ts.map +1 -1
  357. package/dist/building/wall-openings.js +182 -41
  358. package/dist/building/window-types.d.ts +1 -0
  359. package/dist/building/window-types.d.ts.map +1 -1
  360. package/dist/building/window-types.js +3 -0
  361. package/dist/coherence/__fixtures__/enclosure-scenes.d.ts +84 -0
  362. package/dist/coherence/__fixtures__/enclosure-scenes.d.ts.map +1 -0
  363. package/dist/coherence/__fixtures__/enclosure-scenes.js +44 -0
  364. package/dist/commands/structure/attached-walls.d.ts +16 -0
  365. package/dist/commands/structure/attached-walls.d.ts.map +1 -0
  366. package/dist/commands/structure/attached-walls.js +53 -0
  367. package/dist/commands/structure/index.d.ts +3 -0
  368. package/dist/commands/structure/index.d.ts.map +1 -1
  369. package/dist/commands/structure/index.js +3 -0
  370. package/dist/commands/structure/move-separators.d.ts +62 -0
  371. package/dist/commands/structure/move-separators.d.ts.map +1 -0
  372. package/dist/commands/structure/move-separators.js +346 -0
  373. package/dist/commands/structure/set-floor-foundation.d.ts +5 -0
  374. package/dist/commands/structure/set-floor-foundation.d.ts.map +1 -1
  375. package/dist/commands/structure/set-wall-geometry.d.ts.map +1 -1
  376. package/dist/commands/structure/set-wall-geometry.js +44 -11
  377. package/dist/commands/structure/set-zone-intent.d.ts +26 -0
  378. package/dist/commands/structure/set-zone-intent.d.ts.map +1 -1
  379. package/dist/commands/structure/set-zone-intent.js +107 -2
  380. package/dist/commands/structure/shared.d.ts +889 -88
  381. package/dist/commands/structure/shared.d.ts.map +1 -1
  382. package/dist/commands/structure/wall-overlap.d.ts +45 -0
  383. package/dist/commands/structure/wall-overlap.d.ts.map +1 -0
  384. package/dist/commands/structure/wall-overlap.js +289 -0
  385. package/dist/commands/structure/zone-wall-merge.d.ts +2897 -1938
  386. package/dist/commands/structure/zone-wall-merge.d.ts.map +1 -1
  387. package/dist/contracts/reference-discovery.d.ts +2 -2
  388. package/dist/contracts/reference-discovery.d.ts.map +1 -1
  389. package/dist/contracts/reference-discovery.js +5 -3
  390. package/dist/contracts/reference-inventory.d.ts.map +1 -1
  391. package/dist/contracts/reference-inventory.js +75 -25
  392. package/dist/events/bus.d.ts +15 -2
  393. package/dist/events/bus.d.ts.map +1 -1
  394. package/dist/hooks/spatial-grid/built-in-floor-placed.d.ts +40 -0
  395. package/dist/hooks/spatial-grid/built-in-floor-placed.d.ts.map +1 -0
  396. package/dist/hooks/spatial-grid/built-in-floor-placed.js +220 -0
  397. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
  398. package/dist/hooks/spatial-grid/floor-placed-elevation.js +13 -7
  399. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +62 -2
  400. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  401. package/dist/hooks/spatial-grid/spatial-grid-manager.js +207 -36
  402. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +2915 -2114
  403. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  404. package/dist/hooks/spatial-grid/spatial-grid-sync.js +32 -5
  405. package/dist/hooks/spatial-grid/support-host-patch.js +10 -10
  406. package/dist/i18n/format.d.ts +41 -0
  407. package/dist/i18n/format.d.ts.map +1 -0
  408. package/dist/i18n/format.js +124 -0
  409. package/dist/i18n/index.d.ts +5 -0
  410. package/dist/i18n/index.d.ts.map +1 -0
  411. package/dist/i18n/index.js +3 -0
  412. package/dist/i18n/registry.d.ts +78 -0
  413. package/dist/i18n/registry.d.ts.map +1 -0
  414. package/dist/i18n/registry.js +252 -0
  415. package/dist/i18n/use-translate.d.ts +16 -0
  416. package/dist/i18n/use-translate.d.ts.map +1 -0
  417. package/dist/i18n/use-translate.js +23 -0
  418. package/dist/index.d.ts +43 -20
  419. package/dist/index.d.ts.map +1 -1
  420. package/dist/index.js +42 -19
  421. package/dist/lib/__fixtures__/mezzanine.d.ts +10900 -8225
  422. package/dist/lib/__fixtures__/mezzanine.d.ts.map +1 -1
  423. package/dist/lib/absorb-wall-separators.d.ts +1629 -828
  424. package/dist/lib/absorb-wall-separators.d.ts.map +1 -1
  425. package/dist/lib/beam.d.ts +137 -0
  426. package/dist/lib/beam.d.ts.map +1 -0
  427. package/dist/lib/beam.js +297 -0
  428. package/dist/lib/below-grade-footprints.d.ts +13 -5
  429. package/dist/lib/below-grade-footprints.d.ts.map +1 -1
  430. package/dist/lib/below-grade-footprints.js +53 -10
  431. package/dist/lib/build-mode.d.ts +11 -0
  432. package/dist/lib/build-mode.d.ts.map +1 -0
  433. package/dist/lib/build-mode.js +24 -0
  434. package/dist/lib/build-up.d.ts +89 -0
  435. package/dist/lib/build-up.d.ts.map +1 -0
  436. package/dist/lib/build-up.js +216 -0
  437. package/dist/lib/corner-pair.d.ts +207 -4
  438. package/dist/lib/corner-pair.d.ts.map +1 -1
  439. package/dist/lib/deck-boards.d.ts +80 -0
  440. package/dist/lib/deck-boards.d.ts.map +1 -0
  441. package/dist/lib/deck-boards.js +184 -0
  442. package/dist/lib/deck-framing.d.ts +150 -0
  443. package/dist/lib/deck-framing.d.ts.map +1 -0
  444. package/dist/lib/deck-framing.js +515 -0
  445. package/dist/lib/deepest-drawn.d.ts +3 -6
  446. package/dist/lib/deepest-drawn.d.ts.map +1 -1
  447. package/dist/lib/deepest-drawn.js +4 -8
  448. package/dist/lib/door-interaction.d.ts +14 -0
  449. package/dist/lib/door-interaction.d.ts.map +1 -0
  450. package/dist/lib/door-interaction.js +62 -0
  451. package/dist/lib/door-opening.d.ts +19 -0
  452. package/dist/lib/door-opening.d.ts.map +1 -0
  453. package/dist/lib/door-opening.js +27 -0
  454. package/dist/lib/door-operation.d.ts +2 -0
  455. package/dist/lib/door-operation.d.ts.map +1 -1
  456. package/dist/lib/door-operation.js +4 -0
  457. package/dist/lib/elevator-support.d.ts +15 -0
  458. package/dist/lib/elevator-support.d.ts.map +1 -0
  459. package/dist/lib/elevator-support.js +40 -0
  460. package/dist/lib/exterior-wall-loops.d.ts.map +1 -1
  461. package/dist/lib/exterior-wall-loops.js +2 -1
  462. package/dist/lib/finish-script.d.ts +5 -5
  463. package/dist/lib/finish-script.d.ts.map +1 -1
  464. package/dist/lib/finish-script.js +12 -5
  465. package/dist/lib/floor-footprint-name.d.ts +17 -0
  466. package/dist/lib/floor-footprint-name.d.ts.map +1 -1
  467. package/dist/lib/floor-footprint-name.js +17 -4
  468. package/dist/lib/floor-intent-changes.d.ts.map +1 -1
  469. package/dist/lib/floor-intent-changes.js +66 -39
  470. package/dist/lib/floor-opening-footprints.d.ts +1 -1
  471. package/dist/lib/floor-opening-footprints.d.ts.map +1 -1
  472. package/dist/lib/floor-opening-footprints.js +10 -4
  473. package/dist/lib/floor-plate-paint.d.ts +2 -0
  474. package/dist/lib/floor-plate-paint.d.ts.map +1 -1
  475. package/dist/lib/floor-plate-paint.js +12 -0
  476. package/dist/lib/floor-plates.d.ts +250 -0
  477. package/dist/lib/floor-plates.d.ts.map +1 -1
  478. package/dist/lib/floor-plates.js +43 -4
  479. package/dist/lib/floor-script.d.ts +35 -6
  480. package/dist/lib/floor-script.d.ts.map +1 -1
  481. package/dist/lib/floor-script.js +96 -16
  482. package/dist/lib/foundation-construction.d.ts +4 -13
  483. package/dist/lib/foundation-construction.d.ts.map +1 -1
  484. package/dist/lib/foundation-construction.js +4 -14
  485. package/dist/lib/foundation-standard.d.ts +2 -3
  486. package/dist/lib/foundation-standard.d.ts.map +1 -1
  487. package/dist/lib/foundation-standard.js +2 -3
  488. package/dist/lib/foundation-wall.d.ts +1 -2
  489. package/dist/lib/foundation-wall.d.ts.map +1 -1
  490. package/dist/lib/foundation-wall.js +1 -2
  491. package/dist/lib/framing-post.d.ts +91 -0
  492. package/dist/lib/framing-post.d.ts.map +1 -0
  493. package/dist/lib/framing-post.js +174 -0
  494. package/dist/lib/geometry-script-node.d.ts +11 -0
  495. package/dist/lib/geometry-script-node.d.ts.map +1 -1
  496. package/dist/lib/geometry-script-node.js +35 -0
  497. package/dist/lib/grade-standing.d.ts +16 -2
  498. package/dist/lib/grade-standing.d.ts.map +1 -1
  499. package/dist/lib/grade-standing.js +129 -9
  500. package/dist/lib/ground-seat.d.ts +2 -22
  501. package/dist/lib/ground-seat.d.ts.map +1 -1
  502. package/dist/lib/ground-seat.js +2 -17
  503. package/dist/lib/ground-window.d.ts +2 -1
  504. package/dist/lib/ground-window.d.ts.map +1 -1
  505. package/dist/lib/ground-window.js +5 -2
  506. package/dist/lib/hierarchy.d.ts +19 -0
  507. package/dist/lib/hierarchy.d.ts.map +1 -0
  508. package/dist/lib/hierarchy.js +53 -0
  509. package/dist/lib/level-footprints.d.ts +2 -0
  510. package/dist/lib/level-footprints.d.ts.map +1 -1
  511. package/dist/lib/level-footprints.js +7 -0
  512. package/dist/lib/opening-casing.d.ts +67 -0
  513. package/dist/lib/opening-casing.d.ts.map +1 -0
  514. package/dist/lib/opening-casing.js +131 -0
  515. package/dist/lib/opening-exterior-face.d.ts +6 -0
  516. package/dist/lib/opening-exterior-face.d.ts.map +1 -0
  517. package/dist/lib/opening-exterior-face.js +15 -0
  518. package/dist/lib/opening-floor-datum.d.ts +11 -5
  519. package/dist/lib/opening-floor-datum.d.ts.map +1 -1
  520. package/dist/lib/opening-floor-datum.js +40 -8
  521. package/dist/lib/opening-frame-depth.d.ts +18 -0
  522. package/dist/lib/opening-frame-depth.d.ts.map +1 -0
  523. package/dist/lib/opening-frame-depth.js +22 -0
  524. package/dist/lib/outdoor-room.d.ts +32 -0
  525. package/dist/lib/outdoor-room.d.ts.map +1 -0
  526. package/dist/lib/outdoor-room.js +66 -0
  527. package/dist/lib/own-floor-intent.d.ts.map +1 -1
  528. package/dist/lib/own-floor-intent.js +2 -2
  529. package/dist/lib/plate-surface.d.ts +8 -0
  530. package/dist/lib/plate-surface.d.ts.map +1 -1
  531. package/dist/lib/plate-surface.js +53 -3
  532. package/dist/lib/polygon-boolean.d.ts.map +1 -1
  533. package/dist/lib/polygon-boolean.js +2 -1
  534. package/dist/lib/porch-framing-sync.d.ts +46 -0
  535. package/dist/lib/porch-framing-sync.d.ts.map +1 -0
  536. package/dist/lib/porch-framing-sync.js +206 -0
  537. package/dist/lib/porch-framing.d.ts +44 -0
  538. package/dist/lib/porch-framing.d.ts.map +1 -0
  539. package/dist/lib/porch-framing.js +159 -0
  540. package/dist/lib/railing-rooms.d.ts +13 -0
  541. package/dist/lib/railing-rooms.d.ts.map +1 -0
  542. package/dist/lib/railing-rooms.js +37 -0
  543. package/dist/lib/roof-eave.d.ts +135 -0
  544. package/dist/lib/roof-eave.d.ts.map +1 -0
  545. package/dist/lib/roof-eave.js +186 -0
  546. package/dist/lib/room-floor-elevation.d.ts +12 -0
  547. package/dist/lib/room-floor-elevation.d.ts.map +1 -0
  548. package/dist/lib/room-floor-elevation.js +41 -0
  549. package/dist/lib/room-floor-feasibility.d.ts.map +1 -1
  550. package/dist/lib/room-floor-feasibility.js +1 -1
  551. package/dist/lib/room-floor-plate.d.ts +3 -1
  552. package/dist/lib/room-floor-plate.d.ts.map +1 -1
  553. package/dist/lib/room-floor-plate.js +7 -0
  554. package/dist/lib/space-detection.d.ts +13 -0
  555. package/dist/lib/space-detection.d.ts.map +1 -1
  556. package/dist/lib/space-detection.js +113 -24
  557. package/dist/lib/structure-kernel.d.ts +22 -9
  558. package/dist/lib/structure-kernel.d.ts.map +1 -1
  559. package/dist/lib/structure-kernel.js +146 -28
  560. package/dist/lib/structure-reconcile.d.ts +1 -1
  561. package/dist/lib/structure-reconcile.d.ts.map +1 -1
  562. package/dist/lib/structure-reconcile.js +7 -4
  563. package/dist/lib/surface-intent.d.ts +10 -0
  564. package/dist/lib/surface-intent.d.ts.map +1 -0
  565. package/dist/lib/surface-intent.js +18 -0
  566. package/dist/lib/terrain-source.d.ts.map +1 -1
  567. package/dist/lib/terrain-source.js +2 -1
  568. package/dist/lib/wall-face-script.d.ts.map +1 -1
  569. package/dist/lib/wall-face-script.js +8 -5
  570. package/dist/lib/window-interaction.d.ts +16 -0
  571. package/dist/lib/window-interaction.d.ts.map +1 -0
  572. package/dist/lib/window-interaction.js +82 -0
  573. package/dist/lib/zone-quantities.js +1 -1
  574. package/dist/material-kinds.d.ts +1 -1
  575. package/dist/material-kinds.d.ts.map +1 -1
  576. package/dist/material-kinds.js +1 -0
  577. package/dist/material-library.d.ts +15 -0
  578. package/dist/material-library.d.ts.map +1 -1
  579. package/dist/material-library.js +458 -59
  580. package/dist/node-slots/column.d.ts +5 -0
  581. package/dist/node-slots/column.d.ts.map +1 -1
  582. package/dist/node-slots/column.js +10 -0
  583. package/dist/node-slots/door.d.ts +3 -4
  584. package/dist/node-slots/door.d.ts.map +1 -1
  585. package/dist/node-slots/door.js +4 -3
  586. package/dist/node-slots/wall.d.ts.map +1 -1
  587. package/dist/node-slots/wall.js +21 -3
  588. package/dist/node-slots/window.d.ts +2 -2
  589. package/dist/node-slots/window.d.ts.map +1 -1
  590. package/dist/node-slots/window.js +6 -1
  591. package/dist/procedural-items/design.d.ts +8 -0
  592. package/dist/procedural-items/design.d.ts.map +1 -1
  593. package/dist/procedural-items/design.js +169 -21
  594. package/dist/procedural-items/motion-controller.d.ts +2 -0
  595. package/dist/procedural-items/motion-controller.d.ts.map +1 -1
  596. package/dist/procedural-items/motion-controller.js +2 -1
  597. package/dist/registry/index.d.ts +3 -3
  598. package/dist/registry/index.d.ts.map +1 -1
  599. package/dist/registry/index.js +2 -2
  600. package/dist/registry/mechanism.d.ts +4 -1
  601. package/dist/registry/mechanism.d.ts.map +1 -1
  602. package/dist/registry/mechanism.js +10 -0
  603. package/dist/registry/registry.d.ts +5 -0
  604. package/dist/registry/registry.d.ts.map +1 -1
  605. package/dist/registry/registry.js +10 -0
  606. package/dist/registry/types.d.ts +89 -23
  607. package/dist/registry/types.d.ts.map +1 -1
  608. package/dist/runtime/__fixtures__/live-scene.d.ts +14 -0
  609. package/dist/runtime/__fixtures__/live-scene.d.ts.map +1 -0
  610. package/dist/runtime/__fixtures__/live-scene.js +64 -0
  611. package/dist/runtime/apply.d.ts +42 -0
  612. package/dist/runtime/apply.d.ts.map +1 -0
  613. package/dist/runtime/apply.js +274 -0
  614. package/dist/runtime/dependencies.d.ts +97 -0
  615. package/dist/runtime/dependencies.d.ts.map +1 -0
  616. package/dist/runtime/dependencies.js +42 -0
  617. package/dist/runtime/ids.d.ts +11 -0
  618. package/dist/runtime/ids.d.ts.map +1 -0
  619. package/dist/runtime/ids.js +80 -0
  620. package/dist/runtime/index.d.ts +14 -0
  621. package/dist/runtime/index.d.ts.map +1 -0
  622. package/dist/runtime/index.js +11 -0
  623. package/dist/runtime/node-queries.d.ts +16 -0
  624. package/dist/runtime/node-queries.d.ts.map +1 -0
  625. package/dist/runtime/node-queries.js +30 -0
  626. package/dist/runtime/patch.d.ts +26 -0
  627. package/dist/runtime/patch.d.ts.map +1 -0
  628. package/dist/runtime/patch.js +165 -0
  629. package/dist/runtime/reader.d.ts +104 -0
  630. package/dist/runtime/reader.d.ts.map +1 -0
  631. package/dist/runtime/reader.js +455 -0
  632. package/dist/runtime/scene-runtime.d.ts +112 -0
  633. package/dist/runtime/scene-runtime.d.ts.map +1 -0
  634. package/dist/runtime/scene-runtime.js +302 -0
  635. package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -1
  636. package/dist/schema/__fixtures__/node-fixtures.js +1 -0
  637. package/dist/schema/assembly.d.ts +3 -0
  638. package/dist/schema/assembly.d.ts.map +1 -1
  639. package/dist/schema/assembly.js +13 -0
  640. package/dist/schema/asset-url.d.ts +37 -2
  641. package/dist/schema/asset-url.d.ts.map +1 -1
  642. package/dist/schema/asset-url.js +80 -11
  643. package/dist/schema/base.d.ts +8 -0
  644. package/dist/schema/base.d.ts.map +1 -1
  645. package/dist/schema/base.js +19 -1
  646. package/dist/schema/build-up.d.ts +247 -0
  647. package/dist/schema/build-up.d.ts.map +1 -0
  648. package/dist/schema/build-up.js +83 -0
  649. package/dist/schema/geometry-source.d.ts +80 -0
  650. package/dist/schema/geometry-source.d.ts.map +1 -1
  651. package/dist/schema/geometry-source.js +38 -0
  652. package/dist/schema/index.d.ts +12 -7
  653. package/dist/schema/index.d.ts.map +1 -1
  654. package/dist/schema/index.js +12 -6
  655. package/dist/schema/material.d.ts +5 -0
  656. package/dist/schema/material.d.ts.map +1 -1
  657. package/dist/schema/material.js +6 -0
  658. package/dist/schema/nodes/beam.d.ts +264 -0
  659. package/dist/schema/nodes/beam.d.ts.map +1 -0
  660. package/dist/schema/nodes/beam.js +139 -0
  661. package/dist/schema/nodes/box-vent.d.ts +5 -0
  662. package/dist/schema/nodes/box-vent.d.ts.map +1 -1
  663. package/dist/schema/nodes/cabinet.d.ts +10 -0
  664. package/dist/schema/nodes/cabinet.d.ts.map +1 -1
  665. package/dist/schema/nodes/ceiling.d.ts +48 -0
  666. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  667. package/dist/schema/nodes/ceiling.js +5 -0
  668. package/dist/schema/nodes/chimney.d.ts +10 -0
  669. package/dist/schema/nodes/chimney.d.ts.map +1 -1
  670. package/dist/schema/nodes/column.d.ts +42 -0
  671. package/dist/schema/nodes/column.d.ts.map +1 -1
  672. package/dist/schema/nodes/column.js +11 -1
  673. package/dist/schema/nodes/cupola.d.ts +5 -0
  674. package/dist/schema/nodes/cupola.d.ts.map +1 -1
  675. package/dist/schema/nodes/door.d.ts +54 -3
  676. package/dist/schema/nodes/door.d.ts.map +1 -1
  677. package/dist/schema/nodes/door.js +41 -7
  678. package/dist/schema/nodes/dormer.d.ts +20 -0
  679. package/dist/schema/nodes/dormer.d.ts.map +1 -1
  680. package/dist/schema/nodes/downspout.d.ts +5 -0
  681. package/dist/schema/nodes/downspout.d.ts.map +1 -1
  682. package/dist/schema/nodes/elevator.d.ts +5 -0
  683. package/dist/schema/nodes/elevator.d.ts.map +1 -1
  684. package/dist/schema/nodes/eyebrow-vent.d.ts +5 -0
  685. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
  686. package/dist/schema/nodes/fence.d.ts +5 -0
  687. package/dist/schema/nodes/fence.d.ts.map +1 -1
  688. package/dist/schema/nodes/guide.d.ts +14 -0
  689. package/dist/schema/nodes/guide.d.ts.map +1 -1
  690. package/dist/schema/nodes/guide.js +2 -0
  691. package/dist/schema/nodes/gutter.d.ts +5 -0
  692. package/dist/schema/nodes/gutter.d.ts.map +1 -1
  693. package/dist/schema/nodes/item.d.ts +71 -0
  694. package/dist/schema/nodes/item.d.ts.map +1 -1
  695. package/dist/schema/nodes/item.js +5 -1
  696. package/dist/schema/nodes/level.d.ts +1 -1
  697. package/dist/schema/nodes/level.d.ts.map +1 -1
  698. package/dist/schema/nodes/opening-types.d.ts +13 -0
  699. package/dist/schema/nodes/opening-types.d.ts.map +1 -1
  700. package/dist/schema/nodes/opening-types.js +8 -1
  701. package/dist/schema/nodes/railing-wall.d.ts +112 -0
  702. package/dist/schema/nodes/railing-wall.d.ts.map +1 -0
  703. package/dist/schema/nodes/railing-wall.js +160 -0
  704. package/dist/schema/nodes/ridge-vent.d.ts +5 -0
  705. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  706. package/dist/schema/nodes/roof-segment-resize.d.ts.map +1 -1
  707. package/dist/schema/nodes/roof-segment-resize.js +2 -2
  708. package/dist/schema/nodes/roof-segment.d.ts +226 -1
  709. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  710. package/dist/schema/nodes/roof-segment.js +97 -12
  711. package/dist/schema/nodes/roof-types.d.ts +11 -0
  712. package/dist/schema/nodes/roof-types.d.ts.map +1 -1
  713. package/dist/schema/nodes/roof-types.js +6 -0
  714. package/dist/schema/nodes/roof.d.ts +60 -0
  715. package/dist/schema/nodes/roof.d.ts.map +1 -1
  716. package/dist/schema/nodes/roof.js +4 -0
  717. package/dist/schema/nodes/shelf.d.ts +5 -0
  718. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  719. package/dist/schema/nodes/site.d.ts +80 -0
  720. package/dist/schema/nodes/site.d.ts.map +1 -1
  721. package/dist/schema/nodes/site.js +7 -0
  722. package/dist/schema/nodes/skylight.d.ts +10 -0
  723. package/dist/schema/nodes/skylight.d.ts.map +1 -1
  724. package/dist/schema/nodes/slab.d.ts +137 -0
  725. package/dist/schema/nodes/slab.d.ts.map +1 -1
  726. package/dist/schema/nodes/slab.js +3 -0
  727. package/dist/schema/nodes/solar-panel.d.ts +10 -0
  728. package/dist/schema/nodes/solar-panel.d.ts.map +1 -1
  729. package/dist/schema/nodes/stair-segment.d.ts +5 -0
  730. package/dist/schema/nodes/stair-segment.d.ts.map +1 -1
  731. package/dist/schema/nodes/stair-structure.d.ts +69 -0
  732. package/dist/schema/nodes/stair-structure.d.ts.map +1 -0
  733. package/dist/schema/nodes/stair-structure.js +39 -0
  734. package/dist/schema/nodes/stair.d.ts +46 -0
  735. package/dist/schema/nodes/stair.d.ts.map +1 -1
  736. package/dist/schema/nodes/stair.js +3 -0
  737. package/dist/schema/nodes/turbine-vent.d.ts +5 -0
  738. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
  739. package/dist/schema/nodes/wall.d.ts +136 -2
  740. package/dist/schema/nodes/wall.d.ts.map +1 -1
  741. package/dist/schema/nodes/wall.js +48 -6
  742. package/dist/schema/nodes/window.d.ts +66 -1
  743. package/dist/schema/nodes/window.d.ts.map +1 -1
  744. package/dist/schema/nodes/window.js +50 -5
  745. package/dist/schema/nodes/zone.d.ts +68 -0
  746. package/dist/schema/nodes/zone.d.ts.map +1 -1
  747. package/dist/schema/nodes/zone.js +32 -0
  748. package/dist/schema/scene-material.d.ts +5 -0
  749. package/dist/schema/scene-material.d.ts.map +1 -1
  750. package/dist/schema/types.d.ts +1080 -4
  751. package/dist/schema/types.d.ts.map +1 -1
  752. package/dist/schema/types.js +2 -0
  753. package/dist/schema/wall-type.d.ts +95 -0
  754. package/dist/schema/wall-type.d.ts.map +1 -0
  755. package/dist/schema/wall-type.js +32 -0
  756. package/dist/services/index.d.ts +1 -1
  757. package/dist/services/index.d.ts.map +1 -1
  758. package/dist/services/index.js +1 -1
  759. package/dist/services/movement.d.ts +7 -0
  760. package/dist/services/movement.d.ts.map +1 -1
  761. package/dist/services/movement.js +13 -0
  762. package/dist/services/port-connectivity.d.ts +15 -6
  763. package/dist/services/port-connectivity.d.ts.map +1 -1
  764. package/dist/services/port-connectivity.js +83 -17
  765. package/dist/services/roof-bearing.d.ts.map +1 -1
  766. package/dist/services/roof-bearing.js +8 -4
  767. package/dist/services/roof-level.d.ts.map +1 -1
  768. package/dist/services/roof-level.js +4 -3
  769. package/dist/services/roof-move.d.ts.map +1 -1
  770. package/dist/services/roof-move.js +13 -7
  771. package/dist/services/storey.d.ts +6 -1
  772. package/dist/services/storey.d.ts.map +1 -1
  773. package/dist/services/storey.js +24 -10
  774. package/dist/spatial/usage-field/beds.d.ts +51 -0
  775. package/dist/spatial/usage-field/beds.d.ts.map +1 -0
  776. package/dist/spatial/usage-field/beds.js +346 -0
  777. package/dist/spatial/usage-field/blocked.d.ts +28 -0
  778. package/dist/spatial/usage-field/blocked.d.ts.map +1 -0
  779. package/dist/spatial/usage-field/blocked.js +185 -0
  780. package/dist/spatial/usage-field/boxes.d.ts +17 -0
  781. package/dist/spatial/usage-field/boxes.d.ts.map +1 -0
  782. package/dist/spatial/usage-field/boxes.js +82 -0
  783. package/dist/spatial/usage-field/catalogue-shapes.d.ts +3 -0
  784. package/dist/spatial/usage-field/catalogue-shapes.d.ts.map +1 -0
  785. package/dist/spatial/usage-field/catalogue-shapes.js +257 -0
  786. package/dist/spatial/usage-field/catalogue.d.ts +6 -0
  787. package/dist/spatial/usage-field/catalogue.d.ts.map +1 -0
  788. package/dist/spatial/usage-field/catalogue.js +11 -0
  789. package/dist/spatial/usage-field/clash.d.ts +40 -0
  790. package/dist/spatial/usage-field/clash.d.ts.map +1 -0
  791. package/dist/spatial/usage-field/clash.js +187 -0
  792. package/dist/spatial/usage-field/door-stairs.d.ts +18 -0
  793. package/dist/spatial/usage-field/door-stairs.d.ts.map +1 -0
  794. package/dist/spatial/usage-field/door-stairs.js +129 -0
  795. package/dist/spatial/usage-field/entry-way.d.ts +14 -0
  796. package/dist/spatial/usage-field/entry-way.d.ts.map +1 -0
  797. package/dist/spatial/usage-field/entry-way.js +219 -0
  798. package/dist/spatial/usage-field/fences.d.ts +18 -0
  799. package/dist/spatial/usage-field/fences.d.ts.map +1 -0
  800. package/dist/spatial/usage-field/fences.js +253 -0
  801. package/dist/spatial/usage-field/field.d.ts +44 -0
  802. package/dist/spatial/usage-field/field.d.ts.map +1 -0
  803. package/dist/spatial/usage-field/field.js +145 -0
  804. package/dist/spatial/usage-field/findings.d.ts +76 -0
  805. package/dist/spatial/usage-field/findings.d.ts.map +1 -0
  806. package/dist/spatial/usage-field/findings.js +252 -0
  807. package/dist/spatial/usage-field/fixtures.d.ts +18 -0
  808. package/dist/spatial/usage-field/fixtures.d.ts.map +1 -0
  809. package/dist/spatial/usage-field/fixtures.js +160 -0
  810. package/dist/spatial/usage-field/flag.d.ts +10 -0
  811. package/dist/spatial/usage-field/flag.d.ts.map +1 -0
  812. package/dist/spatial/usage-field/flag.js +12 -0
  813. package/dist/spatial/usage-field/floors.d.ts +13 -0
  814. package/dist/spatial/usage-field/floors.d.ts.map +1 -0
  815. package/dist/spatial/usage-field/floors.js +36 -0
  816. package/dist/spatial/usage-field/index.d.ts +7 -0
  817. package/dist/spatial/usage-field/index.d.ts.map +1 -0
  818. package/dist/spatial/usage-field/index.js +5 -0
  819. package/dist/spatial/usage-field/items.d.ts +38 -0
  820. package/dist/spatial/usage-field/items.d.ts.map +1 -0
  821. package/dist/spatial/usage-field/items.js +261 -0
  822. package/dist/spatial/usage-field/labels.d.ts +8 -0
  823. package/dist/spatial/usage-field/labels.d.ts.map +1 -0
  824. package/dist/spatial/usage-field/labels.js +14 -0
  825. package/dist/spatial/usage-field/level-field.d.ts +47 -0
  826. package/dist/spatial/usage-field/level-field.d.ts.map +1 -0
  827. package/dist/spatial/usage-field/level-field.js +96 -0
  828. package/dist/spatial/usage-field/measure.d.ts +324 -0
  829. package/dist/spatial/usage-field/measure.d.ts.map +1 -0
  830. package/dist/spatial/usage-field/measure.js +458 -0
  831. package/dist/spatial/usage-field/obstacles.d.ts +63 -0
  832. package/dist/spatial/usage-field/obstacles.d.ts.map +1 -0
  833. package/dist/spatial/usage-field/obstacles.js +215 -0
  834. package/dist/spatial/usage-field/openings.d.ts +12 -0
  835. package/dist/spatial/usage-field/openings.d.ts.map +1 -0
  836. package/dist/spatial/usage-field/openings.js +162 -0
  837. package/dist/spatial/usage-field/path.d.ts +48 -0
  838. package/dist/spatial/usage-field/path.d.ts.map +1 -0
  839. package/dist/spatial/usage-field/path.js +354 -0
  840. package/dist/spatial/usage-field/pieces.d.ts +27 -0
  841. package/dist/spatial/usage-field/pieces.d.ts.map +1 -0
  842. package/dist/spatial/usage-field/pieces.js +89 -0
  843. package/dist/spatial/usage-field/shapes.d.ts +18 -0
  844. package/dist/spatial/usage-field/shapes.d.ts.map +1 -0
  845. package/dist/spatial/usage-field/shapes.js +92 -0
  846. package/dist/spatial/usage-field/stairs.d.ts +23 -0
  847. package/dist/spatial/usage-field/stairs.d.ts.map +1 -0
  848. package/dist/spatial/usage-field/stairs.js +163 -0
  849. package/dist/spatial/usage-field/support.d.ts +24 -0
  850. package/dist/spatial/usage-field/support.d.ts.map +1 -0
  851. package/dist/spatial/usage-field/support.js +46 -0
  852. package/dist/spatial/usage-field/swings.d.ts +847 -0
  853. package/dist/spatial/usage-field/swings.d.ts.map +1 -0
  854. package/dist/spatial/usage-field/swings.js +427 -0
  855. package/dist/spatial/usage-field/types.d.ts +65 -0
  856. package/dist/spatial/usage-field/types.d.ts.map +1 -0
  857. package/dist/spatial/usage-field/types.js +1 -0
  858. package/dist/spatial/usage-field/walls.d.ts +1059 -0
  859. package/dist/spatial/usage-field/walls.d.ts.map +1 -0
  860. package/dist/spatial/usage-field/walls.js +180 -0
  861. package/dist/spatial/usage-field/way-in.d.ts +26 -0
  862. package/dist/spatial/usage-field/way-in.d.ts.map +1 -0
  863. package/dist/spatial/usage-field/way-in.js +113 -0
  864. package/dist/store/actions/node-actions.d.ts +1467 -664
  865. package/dist/store/actions/node-actions.d.ts.map +1 -1
  866. package/dist/store/actions/node-actions.js +65 -21
  867. package/dist/store/derived-node-guard.d.ts +10 -2
  868. package/dist/store/derived-node-guard.d.ts.map +1 -1
  869. package/dist/store/derived-node-guard.js +33 -12
  870. package/dist/store/document-settling.d.ts +9 -0
  871. package/dist/store/document-settling.d.ts.map +1 -0
  872. package/dist/store/document-settling.js +22 -0
  873. package/dist/store/history-control.d.ts +19 -10
  874. package/dist/store/history-control.d.ts.map +1 -1
  875. package/dist/store/history-control.js +176 -127
  876. package/dist/store/history-drafts.d.ts.map +1 -1
  877. package/dist/store/history-drafts.js +21 -17
  878. package/dist/store/live-previews.d.ts +9 -0
  879. package/dist/store/live-previews.d.ts.map +1 -0
  880. package/dist/store/live-previews.js +19 -0
  881. package/dist/store/scene-annotations.d.ts +5409 -3807
  882. package/dist/store/scene-annotations.d.ts.map +1 -1
  883. package/dist/store/scene-environment.d.ts +33 -0
  884. package/dist/store/scene-environment.d.ts.map +1 -0
  885. package/dist/store/scene-environment.js +54 -0
  886. package/dist/store/scene-hydration.d.ts.map +1 -1
  887. package/dist/store/scene-hydration.js +41 -30
  888. package/dist/store/use-interactive.d.ts +9 -3
  889. package/dist/store/use-interactive.d.ts.map +1 -1
  890. package/dist/store/use-interactive.js +43 -11
  891. package/dist/store/use-live-node-overrides.d.ts.map +1 -1
  892. package/dist/store/use-live-node-overrides.js +3 -0
  893. package/dist/store/use-scene.d.ts +25 -0
  894. package/dist/store/use-scene.d.ts.map +1 -1
  895. package/dist/store/use-scene.js +408 -336
  896. package/dist/systems/document-reconcile.d.ts +40 -0
  897. package/dist/systems/document-reconcile.d.ts.map +1 -0
  898. package/dist/systems/document-reconcile.js +159 -0
  899. package/dist/systems/elevator/elevator-geometry.d.ts +25 -0
  900. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -1
  901. package/dist/systems/elevator/elevator-geometry.js +40 -8
  902. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -1
  903. package/dist/systems/elevator/elevator-opening-sync.js +4 -35
  904. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
  905. package/dist/systems/elevator/elevator-opening-system.js +9 -24
  906. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -1
  907. package/dist/systems/elevator/elevator-runtime.js +95 -18
  908. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  909. package/dist/systems/elevator/elevator-service.js +24 -4
  910. package/dist/systems/fence/guard-layout.d.ts +69 -0
  911. package/dist/systems/fence/guard-layout.d.ts.map +1 -0
  912. package/dist/systems/fence/guard-layout.js +194 -0
  913. package/dist/systems/owned-floor-openings.d.ts +13 -1
  914. package/dist/systems/owned-floor-openings.d.ts.map +1 -1
  915. package/dist/systems/owned-floor-openings.js +58 -2
  916. package/dist/systems/reconcile-owned-floor-openings.d.ts +3 -1
  917. package/dist/systems/reconcile-owned-floor-openings.d.ts.map +1 -1
  918. package/dist/systems/reconcile-owned-floor-openings.js +4 -4
  919. package/dist/systems/roof/roof-elevation-system.d.ts.map +1 -1
  920. package/dist/systems/roof/roof-elevation-system.js +8 -26
  921. package/dist/systems/roof/roof-elevation.d.ts +16 -1
  922. package/dist/systems/roof/roof-elevation.d.ts.map +1 -1
  923. package/dist/systems/roof/roof-elevation.js +87 -5
  924. package/dist/systems/roof/roof-footprint.d.ts +42 -1
  925. package/dist/systems/roof/roof-footprint.d.ts.map +1 -1
  926. package/dist/systems/roof/roof-footprint.js +160 -4
  927. package/dist/systems/slab/__fixtures__/floor-step.d.ts +2714 -1481
  928. package/dist/systems/slab/__fixtures__/floor-step.d.ts.map +1 -1
  929. package/dist/systems/slab/__fixtures__/raised-room.d.ts +3815 -2565
  930. package/dist/systems/slab/__fixtures__/raised-room.d.ts.map +1 -1
  931. package/dist/systems/stair/stair-clearance.d.ts +7 -0
  932. package/dist/systems/stair/stair-clearance.d.ts.map +1 -1
  933. package/dist/systems/stair/stair-clearance.js +31 -11
  934. package/dist/systems/stair/stair-construction.d.ts +8 -3
  935. package/dist/systems/stair/stair-construction.d.ts.map +1 -1
  936. package/dist/systems/stair/stair-construction.js +55 -4
  937. package/dist/systems/stair/stair-opening-preview.d.ts +1630 -829
  938. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  939. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  940. package/dist/systems/stair/stair-opening-system.js +18 -34
  941. package/dist/systems/stair/stair-presets.d.ts +36 -0
  942. package/dist/systems/stair/stair-presets.d.ts.map +1 -1
  943. package/dist/systems/stair/stair-rise-query.d.ts.map +1 -1
  944. package/dist/systems/stair/stair-rise-query.js +3 -2
  945. package/dist/systems/stair/stair-rise.d.ts.map +1 -1
  946. package/dist/systems/stair/stair-rise.js +6 -0
  947. package/dist/systems/stair/stair-sizing.d.ts +42 -11
  948. package/dist/systems/stair/stair-sizing.d.ts.map +1 -1
  949. package/dist/systems/stair/stair-sizing.js +10 -1
  950. package/dist/systems/stair/stair-structure.d.ts +277 -0
  951. package/dist/systems/stair/stair-structure.d.ts.map +1 -0
  952. package/dist/systems/stair/stair-structure.js +496 -0
  953. package/dist/systems/wall/wall-assembly.d.ts +84 -6
  954. package/dist/systems/wall/wall-assembly.d.ts.map +1 -1
  955. package/dist/systems/wall/wall-assembly.js +204 -5
  956. package/dist/systems/wall/wall-curve.d.ts +10 -0
  957. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  958. package/dist/systems/wall/wall-curve.js +38 -0
  959. package/dist/systems/wall/wall-finish.d.ts +38 -2
  960. package/dist/systems/wall/wall-finish.d.ts.map +1 -1
  961. package/dist/systems/wall/wall-finish.js +70 -2
  962. package/dist/systems/wall/wall-footprint.js +2 -2
  963. package/dist/systems/wall/wall-frame.d.ts.map +1 -1
  964. package/dist/systems/wall/wall-frame.js +8 -0
  965. package/dist/systems/wall/wall-merge.d.ts +9 -1
  966. package/dist/systems/wall/wall-merge.d.ts.map +1 -1
  967. package/dist/systems/wall/wall-merge.js +66 -13
  968. package/dist/systems/wall/wall-mitering.d.ts +1 -0
  969. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  970. package/dist/systems/wall/wall-mitering.js +152 -52
  971. package/dist/systems/wall/wall-move.d.ts +23 -6
  972. package/dist/systems/wall/wall-move.d.ts.map +1 -1
  973. package/dist/systems/wall/wall-move.js +65 -0
  974. package/dist/systems/wall/wall-top.d.ts +11 -4
  975. package/dist/systems/wall/wall-top.d.ts.map +1 -1
  976. package/dist/systems/wall/wall-top.js +28 -7
  977. package/dist/systems/wall/wall-topology.d.ts +32 -1
  978. package/dist/systems/wall/wall-topology.d.ts.map +1 -1
  979. package/dist/systems/wall/wall-topology.js +137 -11
  980. package/dist/systems/wall/wall-trim-profiles.d.ts +12 -0
  981. package/dist/systems/wall/wall-trim-profiles.d.ts.map +1 -0
  982. package/dist/systems/wall/wall-trim-profiles.js +191 -0
  983. package/dist/utils/ensure-scene-openings.d.ts +4663 -3061
  984. package/dist/utils/ensure-scene-openings.d.ts.map +1 -1
  985. package/dist/utils/floor-piece-adoption.d.ts +5 -0
  986. package/dist/utils/floor-piece-adoption.d.ts.map +1 -1
  987. package/dist/utils/floor-plate-migration.d.ts +3372 -2571
  988. package/dist/utils/floor-plate-migration.d.ts.map +1 -1
  989. package/dist/utils/measurement-format.d.ts +27 -0
  990. package/dist/utils/measurement-format.d.ts.map +1 -0
  991. package/dist/utils/measurement-format.js +143 -0
  992. package/dist/utils/owned-floor-opening-migration.d.ts +1629 -828
  993. package/dist/utils/owned-floor-opening-migration.d.ts.map +1 -1
  994. package/dist/utils/reconcile-structure-on-load.d.ts +2308 -1507
  995. package/dist/utils/reconcile-structure-on-load.d.ts.map +1 -1
  996. package/dist/utils/structure-id.d.ts.map +1 -1
  997. package/dist/utils/structure-id.js +4 -0
  998. package/dist/validation/validate-build-json.d.ts.map +1 -1
  999. package/dist/validation/validate-build-json.js +3 -1
  1000. package/package.json +22 -5
  1001. package/dist/lib/generic-foundation.d.ts +0 -40
  1002. package/dist/lib/generic-foundation.d.ts.map +0 -1
  1003. package/dist/lib/generic-foundation.js +0 -200
@@ -1,32 +1,88 @@
1
1
  import { refuse } from '../agent-tools/refusal.js';
2
- import { doorTypeFields } from '../building/door-types.js';
2
+ import { doorTypeFields, NEW_DOOR_FRAME_THICKNESS } from '../building/door-types.js';
3
3
  import { DOOR_STYLES, doorStyleLook, getWindowStyleOverrides, WINDOW_STYLES, } from '../building/opening-style-presets.js';
4
- import { windowTakesStyle, windowTypeFields } from '../building/window-types.js';
4
+ import { doorFacing, wallOpeningCeilingOver } from '../building/wall-openings.js';
5
+ import { NEW_WINDOW_FRAME_THICKNESS, windowTakesStyle, windowTypeFields, } from '../building/window-types.js';
6
+ import { cutFloorOpening } from '../commands/structure/floor-opening.js';
7
+ import { doorPositionForFacing, getDoorOpeningSize } from '../lib/door-opening.js';
5
8
  import { floorRoomFaces } from '../lib/floor-room-faces.js';
6
9
  import { matchScriptSlotsToLibrary, requireMeasured, scriptedSize, scriptSource, } from '../lib/geometry-script-node.js';
10
+ import { exposedIntervals } from '../lib/level-footprints.js';
11
+ import { area, containsPoint, difference, intersection, union, } from '../lib/polygon-boolean.js';
12
+ import { roundFloorElevation } from '../lib/room-floor-feasibility.js';
7
13
  import { detectSpacesForLevel } from '../lib/space-detection.js';
8
14
  import { reconcileSceneStructure } from '../lib/structure-reconcile.js';
9
15
  import { wallFaceScriptInputs } from '../lib/wall-face-script.js';
16
+ import { nodeSlotDeclarations } from '../node-slots/index.js';
10
17
  import { nearestLibraryColorRef } from '../procedural-items/library-colors.js';
11
- import { ColumnNode, DoorNode, LevelNode, SlabNode, WallNode, WindowNode, ZoneNode, } from '../schema/index.js';
18
+ import { ColumnNode, CurtainWallConfig, DoorNode, FenceNode, getActiveRoofHeight, getCurtainWallConfig, getSegmentSlopeFrame, LevelNode, RoofNode, RoofSegmentNode, SlabNode, UnitNode, WallNode, WindowNode, ZoneNode, } from '../schema/index.js';
12
19
  import { DoorType, WindowType } from '../schema/nodes/opening-types.js';
13
- import { resolveCeilingHeight } from '../services/level-height.js';
20
+ import { MIN_SLAB_THICKNESS } from '../schema/nodes/slab.js';
21
+ import { DEFAULT_LEVEL_HEIGHT, resolveCeilingHeight } from '../services/level-height.js';
22
+ import { planRoofLevel } from '../services/roof-level.js';
14
23
  import { CEILING_DRAW_OFFSET } from '../services/storey.js';
24
+ import { resolveRoofElevation } from '../systems/roof/roof-elevation.js';
25
+ import { stairArrivalOpening } from '../systems/stair/stair-footprint.js';
26
+ import { getMaxWallCurveOffset, getWallCenterlineSegments, getWallCurveLength, getWallSurfacePolygon, isCurvedWall, normalizeWallCurveOffset, } from '../systems/wall/wall-curve.js';
27
+ import { getWallPlanFootprint, getWallThickness } from '../systems/wall/wall-footprint.js';
15
28
  import { getWallLocalFaceZ } from '../systems/wall/wall-frame.js';
29
+ import { calculateLevelMiters } from '../systems/wall/wall-mitering.js';
16
30
  import { roomFaceCover } from '../systems/wall/wall-room-sides.js';
17
31
  import { migrateRoomZones } from '../utils/room-zone-migration.js';
18
32
  import { addObject } from './add-object.js';
33
+ import { applySceneChanges } from './apply-changes.js';
19
34
  import { authoredSceneChanges } from './authored-scene-merge.js';
35
+ import { createStair } from './create-stair.js';
20
36
  import { paint } from './paint.js';
21
37
  import { pointInPolygon, projectWorldPointToWallLocalX } from './plan-geometry.js';
22
- import { resolveInlinePaintMaterial } from './scene-materials.js';
38
+ import { exactColorMaterial, exactScriptSlots, MATTE } from './scene-materials.js';
39
+ import { queryNodes } from './scene-queries.js';
23
40
  import { setWallSkirting } from './wall-skirting.js';
41
+ const BOOKKEEPING = ['authorSceneBase', 'authorSceneWritten'];
42
+ /** A value as live collaboration and storage keep it: no undefined, no non-finite number. */
43
+ const plain = (value) => JSON.parse(JSON.stringify(value));
44
+ /** A node without the conversion's bookkeeping: what a person edits and a revision merges. */
45
+ function withoutBookkeeping(node) {
46
+ const metadata = node.metadata;
47
+ if (!(metadata && BOOKKEEPING.some((key) => key in metadata)))
48
+ return node;
49
+ const kept = { ...metadata };
50
+ for (const key of BOOKKEEPING)
51
+ delete kept[key];
52
+ return { ...node, metadata: kept };
53
+ }
54
+ /** A node the person deleted since the last conversion: present in the baseline, never read. */
55
+ const DELETED = { type: 'deleted' };
56
+ /**
57
+ * The last conversion of `building`, read back from its levels and nodes: its seed, its source-key
58
+ * identities, and the baseline of every node it wrote (a deleted one marked so).
59
+ */
60
+ function authoredBaseline(scene, building) {
61
+ const state = building.metadata?.authorSceneState;
62
+ if (!(state?.version === 2 && state.buildingId === building.id && typeof state.seed === 'string'))
63
+ return undefined;
64
+ const ids = {};
65
+ const nodes = {};
66
+ // The building lists what it holds itself (its units); each level, what stands on it.
67
+ for (const holder of [building, ...queryNodes(scene, { type: 'level', parentId: building.id })]) {
68
+ const written = holder.metadata?.authorSceneWritten;
69
+ if (!written)
70
+ continue;
71
+ Object.assign(ids, written.keys);
72
+ for (const id of written.ids) {
73
+ const node = scene[id];
74
+ nodes[id] = node?.metadata?.authorSceneBase ?? DELETED;
75
+ }
76
+ }
77
+ return { seed: state.seed, ids, nodes };
78
+ }
24
79
  const JOIN_REACH = 0.3;
80
+ /** The deepest floor between storeys: a wall stopping this far under the floor above carries it. */
81
+ const FLOOR_DEPTH = 0.5;
25
82
  const THIN_SLAB = 0.02;
26
83
  const MANY_SLABS = 20;
27
84
  const MANY_REGIONS = 24;
28
85
  const HEX = /^#[0-9a-f]{6}$/i;
29
- const GLASS = 'library:preset-glass';
30
86
  /**
31
87
  * Explicit payload keys retain their identities across revisions; derived nodes use the
32
88
  * conversion's retained seed. A changed payload never remints every existing wall or item.
@@ -52,6 +108,17 @@ function payloadSeed(payload) {
52
108
  }
53
109
  const dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
54
110
  const metres = (value) => `${value.toFixed(2)} m`;
111
+ /** The value given most often, the first one on a tie; undefined for none. */
112
+ const mostCommon = (values) => {
113
+ const counts = new Map();
114
+ for (const value of values)
115
+ counts.set(value, (counts.get(value) ?? 0) + 1);
116
+ let best;
117
+ for (const [value, count] of counts)
118
+ if (!best || count > counts.get(best))
119
+ best = value;
120
+ return best;
121
+ };
55
122
  /**
56
123
  * Where a wall end meets another wall's centerline: the projection onto that line, as long as it
57
124
  * lands on the wall or within reach past its ends. The line, not the segment, so a partition
@@ -65,6 +132,37 @@ function nearestOnSegment(p, a, b) {
65
132
  const q = [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1])];
66
133
  return { point: q, distance: dist(p, q) };
67
134
  }
135
+ /**
136
+ * The bend Pascal stores (`curveOffset`) for an arc from start to end through a plan point: the
137
+ * point's distance from the straight line, positive when it lies to the right looking from start
138
+ * to end (away from the wall's left normal).
139
+ */
140
+ function bendThrough(start, end, through) {
141
+ const [dx, dz] = [end[0] - start[0], end[1] - start[1]];
142
+ return ((through[0] - start[0]) * dz - (through[1] - start[1]) * dx) / (Math.hypot(dx, dz) || 1);
143
+ }
144
+ /** The same on a curved wall: the nearest point of its arc, so a partition meets the curve. */
145
+ function nearestOnWall(p, wall) {
146
+ if (!wall.spec.through)
147
+ return nearestOnSegment(p, wall.start, wall.end);
148
+ const arc = {
149
+ start: wall.start,
150
+ end: wall.end,
151
+ curveOffset: bendThrough(wall.start, wall.end, wall.spec.through),
152
+ };
153
+ let best = { point: [p[0], p[1]], distance: Number.POSITIVE_INFINITY };
154
+ for (const [a, b] of getWallCenterlineSegments(arc, 64)) {
155
+ const [dx, dz] = [b.x - a.x, b.y - a.y];
156
+ const t = ((p[0] - a.x) * dx + (p[1] - a.y) * dz) / (dx * dx + dz * dz || 1);
157
+ const q = [
158
+ a.x + Math.max(0, Math.min(1, t)) * dx,
159
+ a.y + Math.max(0, Math.min(1, t)) * dz,
160
+ ];
161
+ if (dist(p, q) < best.distance)
162
+ best = { point: q, distance: dist(p, q) };
163
+ }
164
+ return best;
165
+ }
68
166
  const cross = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
69
167
  /** Whether two segments cross each other's interior (touching ends and shared edges do not). */
70
168
  function segmentsCross(a, b, c, d) {
@@ -83,6 +181,8 @@ function polygonsOverlap(a, b) {
83
181
  }
84
182
  /** A wall's footprint, pulled in by a few millimetres so a slab meeting its face is not on it. */
85
183
  function wallFootprint(wall) {
184
+ if (wall.curveOffset)
185
+ return getWallSurfacePolygon({ ...wall, thickness: wall.thickness - 0.01 }).map(({ x, y }) => [x, y]);
86
186
  const inset = 0.005;
87
187
  const [dx, dz] = [wall.end[0] - wall.start[0], wall.end[1] - wall.start[1]];
88
188
  const len = Math.hypot(dx, dz) || 1;
@@ -106,7 +206,31 @@ function toLocal(frame, world) {
106
206
  const [c, s] = [Math.cos(frame.yaw), Math.sin(frame.yaw)];
107
207
  return [c * dx - s * dz, dy, s * dx + c * dz];
108
208
  }
109
- const MATTE = { roughness: 0.9, metalness: 0 };
209
+ /**
210
+ * A payload light's beam in its host's frame (so it turns with the host), or none for a point
211
+ * light. A target on the light itself points it down.
212
+ */
213
+ function lightSpot(light, at, host) {
214
+ if (!light.direction && !light.target && light.angle === undefined)
215
+ return undefined;
216
+ let [x, y, z] = light.target
217
+ ? [light.target[0] - at[0], light.target[1] - at[1], light.target[2] - at[2]]
218
+ : (light.direction ?? [0, -1, 0]);
219
+ let length = Math.hypot(x, y, z);
220
+ if (length < 1e-6)
221
+ [x, y, z, length] = [0, -1, 0, 1];
222
+ const [c, s] = [Math.cos(host.yaw), Math.sin(host.yaw)];
223
+ const round = (v) => Math.round(v * 1e4) / 1e4;
224
+ return {
225
+ direction: [
226
+ round((c * x - s * z) / length),
227
+ round(y / length),
228
+ round((s * x + c * z) / length),
229
+ ],
230
+ angle: light.angle ?? 60,
231
+ softness: light.softness ?? 0.4,
232
+ };
233
+ }
110
234
  /**
111
235
  * The finish a payload value writes. A library or scene ref passes through; a hex colour is kept
112
236
  * as an exact native material, one scene material per distinct colour and surface across the
@@ -127,25 +251,13 @@ function finishPool(context, say) {
127
251
  return { ref: value };
128
252
  if (!HEX.test(value))
129
253
  return { error: `${value} is no colour: give #rrggbb or library:<id>.` };
130
- const hex = value.toLowerCase();
131
254
  if (!materials) {
132
255
  if (!snapped)
133
256
  say('this host keeps no scene materials: colours written as the nearest library colour');
134
257
  snapped = true;
135
- return { ref: nearestLibraryColorRef(hex).ref };
258
+ return { ref: nearestLibraryColorRef(value).ref };
136
259
  }
137
- const resolved = resolveInlinePaintMaterial(materials, {
138
- properties: {
139
- color: hex,
140
- roughness: surface.roughness,
141
- metalness: surface.metalness,
142
- opacity: 1,
143
- transparent: false,
144
- side: 'front',
145
- },
146
- }, surface.roughness === MATTE.roughness && surface.metalness === MATTE.metalness
147
- ? hex
148
- : `${hex}, roughness ${+surface.roughness.toFixed(2)}, metalness ${+surface.metalness.toFixed(2)}`);
260
+ const resolved = exactColorMaterial(materials, value, surface);
149
261
  if (resolved.created) {
150
262
  materials[resolved.sceneMaterial.id] = resolved.sceneMaterial;
151
263
  created.push(resolved.sceneMaterial);
@@ -166,8 +278,13 @@ export default function build({ THREE }) {
166
278
  }`;
167
279
  /** Candidate modules, compiled by the host before room derivation decides which points are used. */
168
280
  export function authorSceneCeilingPoints(payload) {
169
- return payload.levels.flatMap((level, index) => level.lights.flatMap((light, i) => light.fixture ? [] : [{ id: `light/${index}/${i}`, code: CEILING_POINT_CODE }]));
281
+ return payload.levels.flatMap((level, index) => [
282
+ ...level.lights.flatMap((light, i) => light.fixture ? [] : [{ id: `light/${index}/${i}`, code: CEILING_POINT_CODE }]),
283
+ // A room left with no light gets one at its seed.
284
+ ...level.rooms.map((_, i) => ({ id: roomLightId(index, i), code: CEILING_POINT_CODE })),
285
+ ]);
170
286
  }
287
+ const roomLightId = (level, room) => `light/${level}/room/${room}`;
171
288
  /**
172
289
  * The rooms whose floor finish is a module, by their zone once the write made it: the host reads
173
290
  * each room's floor (`floorScriptInputs`), compiles the module for it and writes it with
@@ -181,12 +298,27 @@ export function authorSceneFloorScripts(payload, report) {
181
298
  return room.floorCode && zoneId ? [{ roomId, zoneId, code: room.floorCode }] : [];
182
299
  }));
183
300
  }
301
+ /**
302
+ * The slabs whose surface finish is a module, by their node once the write made them: the host
303
+ * reads each slab's surface (`slabSurfaceScriptInputs`), compiles the module for it and writes it
304
+ * with `setSlabSurfaceScript`, as it does a room's floor module.
305
+ */
306
+ export function authorSceneSlabScripts(payload, report) {
307
+ return payload.levels.flatMap((level, index) => (level.slabs ?? []).flatMap((slab, i) => {
308
+ const slabId = `slab/${index}/${i}`;
309
+ const nodeId = report.ids[slabId];
310
+ return slab.surfaceCode && nodeId
311
+ ? [{ slabId, nodeId, name: slab.name ?? slabId, code: slab.surfaceCode }]
312
+ : [];
313
+ }));
314
+ }
184
315
  /**
185
316
  * Which faces the payload's face modules build on, and why the others cannot be: a wall's own
186
- * `faceLeft` (face a) and `faceRight` (face b), then each room's `wallCode` on the faces its floor
187
- * meets (the spans of the room the zone was derived from, the faces `setWallSkirting` runs along),
188
- * where a wall's own module wins. A face with nothing to build on (a curved or curtain wall) or
189
- * that no room looks into is a `no_face` defect.
317
+ * `faceLeft` (face a) and `faceRight` (face b), its `faceOutside` on the faces no room of its level
318
+ * looks into, then each room's `wallCode` on the faces its floor meets (the spans of the room the
319
+ * zone was derived from, the faces `setWallSkirting` runs along), where a wall's own module wins.
320
+ * A face with nothing to build on (a curved or curtain wall), a left or right face no room looks
321
+ * into, or an outside module on a wall with rooms on both faces is a `no_face` defect.
190
322
  */
191
323
  function resolveFaceScripts(payload, ids, nodes) {
192
324
  const scripts = [];
@@ -225,6 +357,27 @@ function resolveFaceScripts(payload, ids, nodes) {
225
357
  else
226
358
  add(spec.id, face, nodeId, module.code, module.toHeight);
227
359
  }
360
+ const outside = spec.faceOutside;
361
+ if (!outside)
362
+ continue;
363
+ if (!wallFaceScriptInputs(nodes, nodeId, 'a', outside.toHeight)) {
364
+ defects.push({
365
+ id: `${spec.id}/outside`,
366
+ code: 'no_face',
367
+ message: `Wall ${spec.id} ${flat}: its outside face has nothing a script can build on and keeps its paint.`,
368
+ });
369
+ continue;
370
+ }
371
+ const cover = roomFaceCover(nodes, nodeId);
372
+ const faces = ['a', 'b'].filter((face) => cover[face] === 'none');
373
+ if (!faces.length)
374
+ defects.push({
375
+ id: `${spec.id}/outside`,
376
+ code: 'no_face',
377
+ message: `Rooms look into both faces of wall ${spec.id}: it has no outside face, so its faceOutside was not written.`,
378
+ });
379
+ for (const face of faces)
380
+ add(spec.id, face, nodeId, outside.code, outside.toHeight);
228
381
  }
229
382
  const byRoom = new Map();
230
383
  for (const [i, room] of level.rooms.entries()) {
@@ -232,7 +385,10 @@ function resolveFaceScripts(payload, ids, nodes) {
232
385
  const zone = nodes[ids[roomId] ?? ''];
233
386
  if (!room.wallCode || zone?.type !== 'zone')
234
387
  continue;
235
- const boundaries = Object.values(nodes).filter((n) => n.parentId === zone.parentId && (n.type === 'wall' || n.type === 'separator'));
388
+ const boundaries = queryNodes(nodes, {
389
+ type: ['wall', 'separator'],
390
+ parentId: zone.parentId ?? null,
391
+ });
236
392
  const zoneWalls = [...zone.boundaryWallIds].sort().join();
237
393
  const derived = floorRoomFaces(boundaries).find((r) => [...new Set(r.spans.filter((s) => s.kind === 'wall').map((s) => s.boundaryId))]
238
394
  .sort()
@@ -347,6 +503,8 @@ export function authorScene(scene, input, context) {
347
503
  ['slab', level.slabs],
348
504
  ['column', level.columns],
349
505
  ['light', level.lights],
506
+ ['stair', level.stairs],
507
+ ['void', level.voids],
350
508
  ])
351
509
  parts?.forEach((_, i) => {
352
510
  claimSourceKey(`${kind}/${index}/${i}`);
@@ -360,6 +518,10 @@ export function authorScene(scene, input, context) {
360
518
  for (const item of level.items ?? [])
361
519
  claimSourceKey(item.id);
362
520
  });
521
+ for (const item of payload.roof?.items ?? [])
522
+ claimSourceKey(item.id);
523
+ for (const unit of payload.units)
524
+ claimSourceKey(`unit/${unit.name}`);
363
525
  const buildings = Object.values(scene).filter((n) => n.type === 'building');
364
526
  const selected = input.buildingId ?? (context.activeLevelId ? scene[context.activeLevelId]?.parentId : undefined);
365
527
  const building = selected
@@ -370,14 +532,7 @@ export function authorScene(scene, input, context) {
370
532
  if (!building)
371
533
  refuse('no_building', 'The scene has no building to put the levels in.');
372
534
  const existingLevels = Object.values(scene).filter((n) => n.type === 'level' && n.parentId === building.id);
373
- const saved = building.metadata?.authorSceneState;
374
- const previous = saved?.version === 1 &&
375
- saved.buildingId === building.id &&
376
- saved.nodes &&
377
- saved.ids &&
378
- typeof saved.seed === 'string'
379
- ? saved
380
- : undefined;
535
+ const previous = authoredBaseline(scene, building);
381
536
  const converted = Boolean(previous) || existingLevels.some((l) => l.metadata?.authorScene);
382
537
  if (converted && !input.replace)
383
538
  refuse('conversion_exists', 'This scene already holds a conversion. Pass replace: true to write over it.');
@@ -392,11 +547,16 @@ export function authorScene(scene, input, context) {
392
547
  walls: 0,
393
548
  doors: 0,
394
549
  windows: 0,
550
+ passages: 0,
395
551
  rooms: 0,
396
552
  roomsNamed: 0,
397
553
  slabs: 0,
398
554
  columns: 0,
399
555
  items: 0,
556
+ stairs: 0,
557
+ voids: 0,
558
+ roofSegments: 0,
559
+ units: 0,
400
560
  lights: 0,
401
561
  floorScripts: 0,
402
562
  faceScripts: 0,
@@ -421,23 +581,7 @@ export function authorScene(scene, input, context) {
421
581
  }
422
582
  return got.ref;
423
583
  };
424
- const exactSlots = (compiled, id) => {
425
- const slots = matchScriptSlotsToLibrary(compiled.manifest);
426
- for (const slot of compiled.manifest.slots) {
427
- // Glass keeps the glass add_object writes, drawn see-through and letting light pass. A
428
- // scene material carries no emission, so an emissive slot keeps the module's own material
429
- // (the library match leaves it alone) and a bulb stays lit.
430
- if (!slot.color || slot.emissive || slots[slot.id] === GLASS)
431
- continue;
432
- const ref = finishRef(slot.color, id, {
433
- roughness: slot.roughness ?? MATTE.roughness,
434
- metalness: slot.metalness ?? MATTE.metalness,
435
- });
436
- if (ref)
437
- slots[slot.id] = ref;
438
- }
439
- return slots;
440
- };
584
+ const exactSlots = (compiled, id) => exactScriptSlots(compiled.manifest, matchScriptSlotsToLibrary(compiled.manifest), (hex, surface) => finishRef(hex, id, surface));
441
585
  const paintContext = (levelId) => ({
442
586
  ...context,
443
587
  activeLevelId: levelId,
@@ -452,11 +596,20 @@ export function authorScene(scene, input, context) {
452
596
  }
453
597
  const scaffoldIds = new Set(Object.keys(draft));
454
598
  const levelNodes = [];
599
+ const levelWalls = [];
455
600
  const itemNodeIds = new Map();
456
601
  const itemFrames = new Map();
457
602
  const skirtings = [];
458
603
  let untypedWindows = 0;
604
+ /** By upper level index: the storey under it and the height of the tallest wall carrying it. */
605
+ const floorsAbove = new Map();
459
606
  payload.levels.forEach((level, index) => {
607
+ // A storey is as tall as the climb to the next level's floor; the top one, as its walls.
608
+ const above = payload.levels[index + 1];
609
+ const walled = Math.max(0, ...(level.walls ?? []).map((w) => w.height)) || DEFAULT_LEVEL_HEIGHT;
610
+ if (above && above.elevation <= level.elevation)
611
+ say(`"${above.name}" (elevation ${metres(above.elevation)}) is not above "${level.name}" (${metres(level.elevation)}): "${level.name}" is as tall as its walls, ${metres(walled)}`);
612
+ const storey = above && above.elevation > level.elevation ? above.elevation - level.elevation : 0;
460
613
  const levelNode = LevelNode.parse({
461
614
  object: 'node',
462
615
  type: 'level',
@@ -466,6 +619,7 @@ export function authorScene(scene, input, context) {
466
619
  metadata: { authorScene: { elevation: level.elevation } },
467
620
  name: level.name,
468
621
  level: index,
622
+ height: storey || walled,
469
623
  children: [],
470
624
  });
471
625
  draft[levelNode.id] = levelNode;
@@ -476,11 +630,26 @@ export function authorScene(scene, input, context) {
476
630
  levelNode.children.push(node.id);
477
631
  };
478
632
  // ---------- walls, joined ----------
633
+ // Walls keep their height; the floor above is built between the storeys, from the tallest wall
634
+ // carrying it up to its surface. A wall reaching into that floor stops under it, following the
635
+ // storey as a drawn wall does.
636
+ const reaches = (height) => storey > 0 && height > storey - MIN_SLAB_THICKNESS;
479
637
  const walls = (level.walls ?? []).map((w) => ({
480
638
  spec: w,
481
639
  start: [...w.start],
482
640
  end: [...w.end],
641
+ height: reaches(w.height) ? storey : w.height,
483
642
  }));
643
+ const carrying = walls
644
+ .map((w) => w.spec.height)
645
+ .filter((height) => storey > 0 && !reaches(height) && height >= storey - FLOOR_DEPTH);
646
+ if (carrying.length)
647
+ floorsAbove.set(index + 1, { below: level.name, wallHeight: Math.max(...carrying), storey });
648
+ const stopped = [
649
+ ...new Set(walls.filter((w) => w.height !== w.spec.height).map((w) => w.spec.height)),
650
+ ];
651
+ if (stopped.length)
652
+ say(`walls of "${level.name}" given ${stopped.map(metres).join(', ')} reach the floor of "${above.name}", ${metres(storey)} up: they stop under it, following the storey`);
484
653
  for (const w of walls) {
485
654
  for (const end of ['start', 'end']) {
486
655
  const pt = w[end];
@@ -488,7 +657,7 @@ export function authorScene(scene, input, context) {
488
657
  for (const o of walls) {
489
658
  if (o === w)
490
659
  continue;
491
- const near = nearestOnSegment(pt, o.start, o.end);
660
+ const near = nearestOnWall(pt, o);
492
661
  if (near.distance < JOIN_REACH && (!best || near.distance < best.distance))
493
662
  best = near;
494
663
  }
@@ -508,6 +677,13 @@ export function authorScene(scene, input, context) {
508
677
  defect(w.spec.id, 'wall_too_short', 'The wall is shorter than 5 cm after joining.');
509
678
  continue;
510
679
  }
680
+ const bend = w.spec.through ? bendThrough(w.start, w.end, w.spec.through) : 0;
681
+ const curve = bend ? normalizeWallCurveOffset(w, bend) : 0;
682
+ if (w.spec.through && !curve)
683
+ say(`wall ${w.spec.id}: its through point is on the straight line: written straight`);
684
+ else if (Math.abs(bend) > getMaxWallCurveOffset(w) + 1e-9)
685
+ say(`wall ${w.spec.id}: its through point stands ${metres(Math.abs(bend))} off the straight line, more than half its ${metres(dist(w.start, w.end))} span: written as a half circle, ${metres(Math.abs(curve))} out`);
686
+ const { curtain } = w.spec;
511
687
  const node = WallNode.parse({
512
688
  object: 'node',
513
689
  type: 'wall',
@@ -518,8 +694,20 @@ export function authorScene(scene, input, context) {
518
694
  children: [],
519
695
  start: w.start,
520
696
  end: w.end,
521
- height: w.spec.height,
697
+ ...(w.height === w.spec.height ? { height: w.spec.height } : {}),
522
698
  thickness: w.spec.thickness,
699
+ ...(curve ? { curveOffset: curve } : {}),
700
+ ...(curtain
701
+ ? {
702
+ wallType: 'curtain',
703
+ curtainWall: CurtainWallConfig.parse({
704
+ columns: {
705
+ layout: 'maximum-spacing',
706
+ spacing: Math.min(100, Math.max(0.2, curtain.spacing)),
707
+ },
708
+ }),
709
+ }
710
+ : {}),
523
711
  });
524
712
  place(node);
525
713
  wallNodes.set(w.spec.id, { ...w, node });
@@ -532,13 +720,20 @@ export function authorScene(scene, input, context) {
532
720
  });
533
721
  report.ids[w.spec.id] = node.id;
534
722
  report.walls++;
535
- // ---------- its openings ----------
536
- const length = dist(w.start, w.end);
723
+ // ---------- its openings: along the arc on a curved wall, as Pascal places them ----------
724
+ const length = curve ? getWallCurveLength(node) : dist(w.start, w.end);
537
725
  (w.spec.openings ?? []).forEach((o, i) => {
538
726
  const id = `${w.spec.id}/${i}`;
727
+ const passage = o.kind === 'passage';
728
+ const kind = passage ? 'door' : o.kind;
729
+ if (passage) {
730
+ const leaf = ['type', 'style', 'leaves', 'hinge', 'swing', 'code'].filter((field) => o[field] !== undefined);
731
+ if (leaf.length)
732
+ say(`passage ${id}: it has no leaf, its ${leaf.join(', ')} ignored`);
733
+ }
539
734
  // A module supplies geometry; the declared type still describes how the opening works.
540
735
  let compiled;
541
- if (o.code) {
736
+ if (o.code && !passage) {
542
737
  compiled = input.compiled[id];
543
738
  if (!compiled) {
544
739
  defect(id, 'compile_failed', input.compileErrors?.[id] ?? 'The module did not compile.');
@@ -560,24 +755,53 @@ export function authorScene(scene, input, context) {
560
755
  say(`${o.kind} ${id}: style ignored, its module builds it`);
561
756
  }
562
757
  const [width, height] = compiled ? scriptedSize(compiled.manifest) : [o.width, o.height];
563
- if (width > length + 0.01) {
758
+ const size = o.kind === 'door' && !compiled
759
+ ? getDoorOpeningSize({
760
+ ...o,
761
+ width,
762
+ height,
763
+ doorType: o.type && isDoorType(o.type) ? o.type : 'hinged',
764
+ })
765
+ : { width, height, leafOffsetX: 0 };
766
+ if (size.width > length + 0.01) {
564
767
  defect(id, 'opening_wider_than_wall', `${o.kind} ${width.toFixed(2)} m wide on a ${length.toFixed(2)} m wall.`);
565
768
  return;
566
769
  }
567
- const x = Math.min(Math.max(o.along, width / 2), length - width / 2);
568
- if (Math.abs(x - o.along) > 0.01)
569
- say(`${o.kind} ${id}: moved ${Math.abs(x - o.along).toFixed(2)} m along the wall to fit inside it`);
770
+ if (o.kind === 'door') {
771
+ const wallHeight = wallOpeningCeilingOver(node, draft, o.along - size.width / 2, o.along + size.width / 2) - (node.wallType === 'curtain' ? getCurtainWallConfig(node).perimeterWidth : 0);
772
+ if (o.sill + size.height > wallHeight + 1e-6) {
773
+ defect(id, 'wall_too_low', `Wall ${w.spec.id} is ${metres(wallHeight)} high, too low for a ${metres(size.height)} door at sill ${metres(o.sill)}.`);
774
+ return;
775
+ }
776
+ }
777
+ // A door or window is flat, set on the arc at its centre: its ends leave the curve.
778
+ if (curve) {
779
+ const chord = dist(w.start, w.end);
780
+ const radius = (chord * chord) / (8 * Math.abs(curve)) + Math.abs(curve) / 2;
781
+ const standoff = radius - Math.sqrt(Math.max(0, radius ** 2 - (size.width / 2) ** 2));
782
+ const half = w.spec.thickness / 2;
783
+ if (standoff > half)
784
+ warn(id, 'flat_on_arc', `The ${metres(width)} ${o.kind} is flat on wall ${w.spec.id}, an arc of radius ${metres(radius)}: its ends stand ${metres(standoff)} off the curve, past the wall's faces. Set narrower ones side by side, at most ${metres(2 * Math.sqrt(radius ** 2 - (radius - half) ** 2))} wide.`);
785
+ }
786
+ const centre = Math.min(Math.max(o.along, size.width / 2), length - size.width / 2);
787
+ const x = centre + size.leafOffsetX;
788
+ if (Math.abs(centre - o.along) > 0.01)
789
+ say(`${o.kind} ${id}: moved ${Math.abs(centre - o.along).toFixed(2)} m along the wall to fit inside it`);
570
790
  const common = {
571
791
  object: 'node',
572
- id: mint(o.kind, id),
792
+ id: mint(kind, id),
573
793
  parentId: node.id,
574
794
  visible: true,
575
795
  metadata: { authorScene: { id, color: o.color ?? null } },
576
796
  wallId: node.id,
797
+ casingFaceA: true,
798
+ casingFaceB: true,
577
799
  width,
578
800
  height,
579
801
  rotation: [0, 0, 0],
580
802
  ...(o.name ? { name: o.name } : {}),
803
+ ...(o.shape ? { openingShape: o.shape } : {}),
804
+ ...(o.archHeight === undefined ? {} : { archHeight: Math.min(o.archHeight, height) }),
581
805
  ...(compiled
582
806
  ? {
583
807
  source: scriptSource(compiled, { category: o.kind }),
@@ -586,7 +810,17 @@ export function authorScene(scene, input, context) {
586
810
  : {}),
587
811
  };
588
812
  let opening;
589
- if (o.kind === 'door') {
813
+ if (passage) {
814
+ opening = DoorNode.parse({
815
+ ...common,
816
+ type: 'door',
817
+ frameThickness: NEW_DOOR_FRAME_THICKNESS,
818
+ position: [x, o.sill + height / 2, 0],
819
+ openingKind: 'opening',
820
+ });
821
+ report.passages++;
822
+ }
823
+ else if (o.kind === 'door') {
590
824
  const type = o.type;
591
825
  if (type && !isDoorType(type))
592
826
  defect(id, 'type_mismatch', `${type} is a window type; the door is written hinged.`);
@@ -600,12 +834,19 @@ export function authorScene(scene, input, context) {
600
834
  opening = DoorNode.parse({
601
835
  ...common,
602
836
  type: 'door',
603
- position: [x, height / 2, 0],
837
+ frameThickness: NEW_DOOR_FRAME_THICKNESS,
838
+ position: [x, o.sill + height / 2, 0],
604
839
  ...(type && isDoorType(type) ? doorTypeFields(type) : {}),
605
840
  ...(style && isDoorStyle(style) ? doorStyleLook(style) : {}),
606
841
  ...(o.leaves ? { leafCount: o.leaves } : {}),
842
+ sidelightLeft: o.sidelightLeft,
843
+ sidelightRight: o.sidelightRight,
844
+ transomHeight: o.transomHeight,
845
+ slidingStyle: o.slidingStyle,
846
+ ...(o.slideDirection ? { slideDirection: o.slideDirection } : {}),
607
847
  hingesSide,
608
848
  handleSide,
849
+ handleStyle: o.handleStyle,
609
850
  ...(o.swing ? { swingDirection: o.swing === 'back' ? 'outward' : 'inward' } : {}),
610
851
  });
611
852
  report.doors++;
@@ -626,13 +867,35 @@ export function authorScene(scene, input, context) {
626
867
  opening = WindowNode.parse({
627
868
  ...common,
628
869
  type: 'window',
870
+ hingesSide: o.hingesSide ?? (o.hinge === 'end' ? 'right' : 'left'),
871
+ sashGrid: !windowTakesStyle(windowType ?? 'fixed') && (o.columns || o.rows) ? true : o.sashGrid,
872
+ windowHandle: o.windowHandle,
873
+ sillInterior: o.sillInterior,
874
+ shutters: o.shutters,
875
+ shutterWidth: o.shutterWidth,
876
+ tiltTurnMode: o.tiltTurnMode,
877
+ sillOverhang: o.sillOverhang,
878
+ sillInteriorDepth: o.sillInteriorDepth,
879
+ sillInteriorThickness: o.sillInteriorThickness,
880
+ sillInteriorOverhang: o.sillInteriorOverhang,
881
+ shutterOpen: o.shutterOpen,
882
+ shutterSides: o.shutterSides,
883
+ rollerShutter: o.rollerShutter,
884
+ rollerShutterBoxHeight: o.rollerShutterBoxHeight,
885
+ rollerShutterOpen: o.rollerShutterOpen,
886
+ frameThickness: NEW_WINDOW_FRAME_THICKNESS,
629
887
  position: [x, o.sill + height / 2, 0],
630
888
  ...(windowType ? windowTypeFields(windowType) : {}),
889
+ ...(o.openingDirection === undefined ? {} : { openingDirection: o.openingDirection }),
631
890
  ...(style && isWindowStyle(style) && takesStyle ? getWindowStyleOverrides(style) : {}),
891
+ ...(o.columns
892
+ ? { columnRatios: Array.from({ length: o.columns }, () => 1 / o.columns) }
893
+ : {}),
894
+ ...(o.rows ? { rowRatios: Array.from({ length: o.rows }, () => 1 / o.rows) } : {}),
632
895
  });
633
896
  report.windows++;
634
897
  }
635
- const panel = o.kind === 'door' ? finishRef(o.color, id) : undefined;
898
+ const panel = kind === 'door' ? finishRef(o.color, id) : undefined;
636
899
  const frame = finishRef(o.frameColor ?? o.color, id);
637
900
  if (panel || frame)
638
901
  opening = {
@@ -658,10 +921,14 @@ export function authorScene(scene, input, context) {
658
921
  const polygon = s.polygon;
659
922
  // A slab over a wall's footprint, higher than half the wall: a cap, which would lift the
660
923
  // wall onto it. Pascal caps its walls itself.
661
- const lifted = [...wallNodes.values()].find((w) => s.top > w.spec.height / 2 &&
662
- polygonsOverlap(polygon, wallFootprint({ ...w, thickness: w.spec.thickness })));
924
+ const lifted = [...wallNodes.values()].find((w) => s.top > w.height / 2 &&
925
+ polygonsOverlap(polygon, wallFootprint({
926
+ ...w,
927
+ thickness: w.spec.thickness,
928
+ curveOffset: w.node.curveOffset,
929
+ })));
663
930
  if (lifted) {
664
- defect(id, 'slab_over_wall', `${label} overlaps wall ${lifted.spec.id} with its top at ${metres(s.top)}, above half the wall's ${metres(lifted.spec.height)}: it would lift the wall onto it. Pascal builds a wall's cap itself; fold it into the wall.`);
931
+ defect(id, 'slab_over_wall', `${label} overlaps wall ${lifted.spec.id} with its top at ${metres(s.top)}, above half the wall's ${metres(lifted.height)}: it would lift the wall onto it. Pascal builds a wall's cap itself; fold it into the wall.`);
665
932
  return;
666
933
  }
667
934
  if (s.thickness < THIN_SLAB)
@@ -679,35 +946,527 @@ export function authorScene(scene, input, context) {
679
946
  elevation: s.top,
680
947
  thickness: s.thickness,
681
948
  ...(finish ? { slots: { surface: finish } } : {}),
682
- ...(s.railing?.length
683
- ? { railing: s.railing.map((r) => ({ start: r.start, end: r.end })) }
684
- : {}),
685
949
  });
686
950
  place(node);
687
951
  report.ids[id] = node.id;
688
952
  report.slabs++;
953
+ (s.railing ?? []).forEach((r, k) => {
954
+ const railId = `${id}/railing/${k}`;
955
+ let fence = FenceNode.parse({
956
+ object: 'node',
957
+ id: mint('fence', railId),
958
+ parentId: levelNode.id,
959
+ visible: true,
960
+ metadata: { authorScene: { id: railId } },
961
+ name: 'Railing',
962
+ start: r.start,
963
+ end: r.end,
964
+ height: r.height,
965
+ style: 'rail',
966
+ baseStyle: 'floating',
967
+ baseHeight: 0,
968
+ postCap: 'none',
969
+ supportSlabId: node.id,
970
+ });
971
+ const railFinish = finishRef(r.color, railId);
972
+ if (railFinish)
973
+ fence = {
974
+ ...fence,
975
+ slots: Object.fromEntries((nodeSlotDeclarations(fence) ?? []).map((slot) => [slot.slotId, railFinish])),
976
+ };
977
+ place(fence);
978
+ });
689
979
  });
690
980
  (level.columns ?? []).forEach((c, i) => {
691
- const node = ColumnNode.parse({
981
+ const id = `column/${index}/${i}`;
982
+ let compiled;
983
+ if (c.code) {
984
+ compiled = input.compiled[id];
985
+ if (!compiled) {
986
+ defect(id, 'compile_failed', input.compileErrors?.[id] ?? 'The module did not compile.');
987
+ return;
988
+ }
989
+ if (compiled.mount !== 'floor') {
990
+ defect(id, 'wrong_mount', `A column's module uses mount 'floor', not '${compiled.mount}'.`);
991
+ return;
992
+ }
993
+ try {
994
+ requireMeasured(compiled, { label: c.name ?? id, tool: 'author_scene' });
995
+ }
996
+ catch (error) {
997
+ const e = error;
998
+ defect(id, e.code ?? 'unmeasurable_object', e.message ?? String(error));
999
+ return;
1000
+ }
1001
+ if (c.color)
1002
+ say(`column ${id}: built by its module, which keeps its own colours; color ignored`);
1003
+ }
1004
+ const size = compiled && scriptedSize(compiled.manifest);
1005
+ let node = ColumnNode.parse({
692
1006
  object: 'node',
693
1007
  type: 'column',
694
- id: mint('column', `column/${index}/${i}`),
1008
+ id: mint('column', id),
695
1009
  parentId: levelNode.id,
696
1010
  visible: true,
697
- metadata: { authorScene: { color: c.color ?? null } },
1011
+ metadata: { authorScene: { id } },
698
1012
  ...(c.name ? { name: c.name } : {}),
699
1013
  position: [c.position[0], 0, c.position[1]],
700
- crossSection: 'square',
701
- width: c.width,
702
- depth: c.depth,
703
- height: c.height,
704
- baseHeight: 0,
705
- capitalHeight: 0,
1014
+ rotation: (c.rotation * Math.PI) / 180,
1015
+ ...(compiled && size
1016
+ ? {
1017
+ source: scriptSource(compiled, { category: 'column' }),
1018
+ slots: exactSlots(compiled, id),
1019
+ width: size[0],
1020
+ height: size[1],
1021
+ depth: size[2],
1022
+ }
1023
+ : {
1024
+ crossSection: c.shape,
1025
+ ...(c.shape === 'round' ? { radius: c.width / 2 } : {}),
1026
+ width: c.width,
1027
+ depth: c.shape === 'round' ? c.width : (c.depth ?? c.width),
1028
+ height: c.height,
1029
+ baseHeight: 0,
1030
+ capitalHeight: 0,
1031
+ }),
706
1032
  });
1033
+ const finish = !compiled && finishRef(c.color, id);
1034
+ if (finish)
1035
+ node = {
1036
+ ...node,
1037
+ slots: Object.fromEntries((nodeSlotDeclarations(node) ?? []).map((slot) => [slot.slotId, finish])),
1038
+ };
707
1039
  place(node);
708
- report.ids[`column/${index}/${i}`] = node.id;
1040
+ report.ids[id] = node.id;
709
1041
  report.columns++;
710
1042
  });
1043
+ levelWalls.push({ walls, wallNodes });
1044
+ });
1045
+ report.levels = levelNodes.length;
1046
+ if (untypedWindows)
1047
+ say(`${untypedWindows} window${untypedWindows === 1 ? ' has' : 's have'} no type: written fixed (cannot open); give type`);
1048
+ // ---------- which face of each wall looks into a room: read from the plan, as the editor does ----------
1049
+ for (const levelNode of levelNodes) {
1050
+ const boundaries = Object.values(draft).filter((n) => n.parentId === levelNode.id && (n.type === 'wall' || n.type === 'separator'));
1051
+ for (const { wallId, ...sides } of detectSpacesForLevel(levelNode.id, boundaries).wallUpdates)
1052
+ draft[wallId] = { ...draft[wallId], ...sides };
1053
+ }
1054
+ // ---------- rooms: Pascal's own derivation, then the payload's names and floors ----------
1055
+ const { nodes: withZones, createdZoneIds } = migrateRoomZones(draft);
1056
+ const zones = createdZoneIds.map((id) => ZoneNode.parse(withZones[id]));
1057
+ for (const zone of zones) {
1058
+ draft[zone.id] = zone;
1059
+ const level = draft[zone.parentId];
1060
+ draft[zone.parentId] = { ...level, children: [...level.children, zone.id] };
1061
+ report.rooms++;
1062
+ }
1063
+ const joined = new Map();
1064
+ const roomIds = new Map();
1065
+ payload.levels.forEach((level, index) => {
1066
+ const levelNode = levelNodes[index];
1067
+ for (const [i, room] of level.rooms.entries()) {
1068
+ const id = `room/${index}/${i}`;
1069
+ roomIds.set(room, id);
1070
+ const zone = zones.find((z) => z.parentId === levelNode.id && pointInPolygon(room.seed, z.polygon));
1071
+ // Several seeds in one enclosure name one open-plan room, in the order given.
1072
+ const sharing = zone && joined.get(zone.id);
1073
+ if (sharing) {
1074
+ sharing.push(room);
1075
+ continue;
1076
+ }
1077
+ if (!zone) {
1078
+ defect(id, 'room_not_enclosed', `No closed room around (${room.seed[0]}, ${room.seed[1]}) "${room.name}": its walls do not meet.`);
1079
+ continue;
1080
+ }
1081
+ if (room.floorRegions.length > MANY_REGIONS)
1082
+ warn(id, 'many_regions', `"${room.name}" has ${room.floorRegions.length} floor regions: regions are fixed polygons that do not follow a resize; use one floor finish.`);
1083
+ const finish = finishRef(room.floor, id);
1084
+ const regions = room.floorRegions.map((r, k) => ({
1085
+ id: `region_${k}`,
1086
+ polygon: r.polygon,
1087
+ finish: finishRef(r.finish, id) ?? r.finish,
1088
+ }));
1089
+ draft[zone.id] = ZoneNode.parse({
1090
+ ...zone,
1091
+ name: room.name,
1092
+ spaceRole: 'room',
1093
+ ...(finish || regions.length
1094
+ ? {
1095
+ floor: {
1096
+ ...zone.floor,
1097
+ ...(finish ? { finish } : {}),
1098
+ ...(regions.length ? { regions } : {}),
1099
+ },
1100
+ }
1101
+ : {}),
1102
+ metadata: { ...zone.metadata, authorScene: { seed: room.seed } },
1103
+ });
1104
+ joined.set(zone.id, [room]);
1105
+ report.ids[id] = zone.id;
1106
+ report.roomsNamed++;
1107
+ }
1108
+ });
1109
+ for (const [zoneId, rooms] of joined) {
1110
+ if (rooms.length < 2)
1111
+ continue;
1112
+ const [first, ...rest] = rooms.map((room) => room.name);
1113
+ // "Kitchen" joins as "kitchen"; a name that is not a capitalised word ("WC") stays as given.
1114
+ const names = [
1115
+ first,
1116
+ ...rest.map((name) => name.replace(/^([A-Z])(?=[a-z])/, (c) => c.toLowerCase())),
1117
+ ];
1118
+ const name = `${names.slice(0, -1).join(', ')} and ${names.at(-1)}`;
1119
+ draft[zoneId] = { ...draft[zoneId], name };
1120
+ report.roomsNamed += rest.length;
1121
+ const floors = rooms
1122
+ .slice(1)
1123
+ .filter((room) => room.floor || room.floorCode || room.floorRegions.length);
1124
+ say(`${rooms.map((room) => `"${room.name}"`).join(', ')} are one enclosure, no wall between them: one open-plan room, "${name}"${floors.length ? `, with the floor of "${first}"` : ''}`);
1125
+ }
1126
+ // ---------- units: one per distinct name, under the building, holding the rooms that name it ----------
1127
+ const units = new Map();
1128
+ for (const unit of payload.units) {
1129
+ const color = unit.color && HEX.test(unit.color) ? unit.color : undefined;
1130
+ if (unit.color && !color)
1131
+ defect(`unit/${unit.name}`, 'invalid_color', `"${unit.color}" is not a #rrggbb colour.`);
1132
+ units.set(unit.name, { kind: unit.kind, color, zones: [] });
1133
+ }
1134
+ for (const [zoneId, rooms] of joined) {
1135
+ const names = [...new Set(rooms.flatMap((room) => (room.unit ? [room.unit] : [])))];
1136
+ // One enclosure is one room, so it is in one unit: seeds naming two want a wall between them.
1137
+ if (names.length > 1) {
1138
+ defect(roomIds.get(rooms[0]), 'unit_split_room', `${rooms.map((room) => `"${room.name}"`).join(', ')} are one open-plan room but name different units (${names.join(', ')}): the room is in none. Put a wall between them, or one unit on all of them.`);
1139
+ continue;
1140
+ }
1141
+ if (!names[0])
1142
+ continue;
1143
+ const entry = units.get(names[0]) ?? { kind: 'apartment', zones: [] };
1144
+ entry.zones.push(zoneId);
1145
+ units.set(names[0], entry);
1146
+ }
1147
+ for (const [name, { kind, color, zones: members }] of units) {
1148
+ const unit = UnitNode.parse({
1149
+ id: mint('unit', `unit/${name}`),
1150
+ parentId: building.id,
1151
+ name,
1152
+ kind,
1153
+ ...(color ? { color } : {}),
1154
+ members,
1155
+ });
1156
+ draft[unit.id] = unit;
1157
+ report.ids[`unit/${name}`] = unit.id;
1158
+ report.units++;
1159
+ if (!members.length)
1160
+ warn(`unit/${name}`, 'unit_no_rooms', `No room names unit "${name}".`);
1161
+ }
1162
+ // ---------- floors and ceilings, as the editor derives them on load ----------
1163
+ const levelIds = levelNodes.map((l) => l.id);
1164
+ let written = {
1165
+ ...reconcileSceneStructure({ nodes: draft, levelIds, mintId: (kind) => mint(kind) }).nodes,
1166
+ };
1167
+ for (const opening of Object.values(written)) {
1168
+ const wall = written[opening.parentId ?? ''];
1169
+ if (opening.type !== 'door' || wall?.type !== 'wall' || scaffoldIds.has(opening.id))
1170
+ continue;
1171
+ const facing = doorFacing(wall);
1172
+ written[opening.id] = {
1173
+ ...opening,
1174
+ ...facing,
1175
+ position: doorPositionForFacing(opening, facing.rotation),
1176
+ };
1177
+ }
1178
+ // A room's plate and ceiling are named by its room, whose id follows its walls: a revision keeps
1179
+ // an unchanged room's, and a room it adds brings its own with it (never another room's renamed).
1180
+ const derivedIds = new Map();
1181
+ for (const node of Object.values(written)) {
1182
+ const zones = node.type === 'ceiling' && node.zoneId
1183
+ ? [node.zoneId]
1184
+ : node.type === 'slab' && node.plateRole === 'base'
1185
+ ? [...(node.zoneIds ?? [])].sort()
1186
+ : [];
1187
+ if (zones.length && !scaffoldIds.has(node.id))
1188
+ derivedIds.set(node.id, mint(node.type, `${node.type}/${zones.join('+')}`));
1189
+ }
1190
+ if (derivedIds.size) {
1191
+ let json = JSON.stringify(written);
1192
+ for (const [from, to] of derivedIds)
1193
+ json = json.replaceAll(`"${from}"`, `"${to}"`);
1194
+ written = JSON.parse(json);
1195
+ }
1196
+ // ---------- the floor between storeys: each upper plate keeps its top and reaches down to the walls ----------
1197
+ for (const [index, { below, wallHeight, storey }] of floorsAbove) {
1198
+ const plates = (levelId) => Object.values(written).filter((node) => node.type === 'slab' && node.parentId === levelId && node.plateRole === 'base');
1199
+ // The carrying walls stand on the floor below; their tops, in this level's frame, are its underside.
1200
+ const lower = plates(levelNodes[index - 1].id);
1201
+ const underside = (lower.length ? Math.max(...lower.map((p) => p.elevation)) : 0) + wallHeight;
1202
+ // Seen from outside, the band continues the cladding under it: the colour most of those walls
1203
+ // wear outside paints the plate's edge and sides.
1204
+ const outsides = (payload.levels[index - 1].walls ?? []).flatMap((w) => w.colorOutside ? [w.colorOutside] : []);
1205
+ const cladding = mostCommon(outsides);
1206
+ const bandRef = cladding && finishRef(cladding, `level/${index}/floor`);
1207
+ const depths = new Set();
1208
+ for (const plate of plates(levelNodes[index].id)) {
1209
+ const thickness = roundFloorElevation(plate.elevation - (underside - storey));
1210
+ written[plate.id] = {
1211
+ ...plate,
1212
+ thickness,
1213
+ ...(bandRef ? { slots: { ...plate.slots, edge: bandRef, side: bandRef } } : {}),
1214
+ };
1215
+ depths.add(thickness);
1216
+ }
1217
+ if (depths.size)
1218
+ say(`"${payload.levels[index].name}" stands on a ${[...depths].map(metres).join(', ')} floor over the ${metres(wallHeight)} walls of "${below}": its plate reaches down to their tops`);
1219
+ }
1220
+ /**
1221
+ * The room a plan point of a level is in, its ceiling, and the height in that level's frame that
1222
+ * ceiling hangs things from. Under an opening in the ceiling (a void, a stairwell) it is the
1223
+ * ceiling above, the room's double height.
1224
+ */
1225
+ const ceilingAt = (index, x, z) => {
1226
+ const levelId = levelNodes[index].id;
1227
+ const room = Object.values(written).find((node) => node.type === 'zone' &&
1228
+ node.spaceRole === 'room' &&
1229
+ node.parentId === levelId &&
1230
+ pointInPolygon([x, z], node.polygon) &&
1231
+ !node.holes.some((hole) => pointInPolygon([x, z], hole)));
1232
+ const ceiling = room &&
1233
+ Object.values(written).find((node) => node.type === 'ceiling' && node.zoneId === room.id && node.parentId === levelId);
1234
+ if (!room || !ceiling)
1235
+ return undefined;
1236
+ if (levelNodes[index + 1] && ceiling.holes.some((hole) => pointInPolygon([x, z], hole))) {
1237
+ const above = ceilingAt(index + 1, x, z);
1238
+ const climb = payload.levels[index + 1].elevation - payload.levels[index].elevation;
1239
+ if (above)
1240
+ return { ...above, height: above.height + climb };
1241
+ }
1242
+ return { room, ceiling, height: resolveCeilingHeight(ceiling, written) - CEILING_DRAW_OFFSET };
1243
+ };
1244
+ // ---------- stairs, as create_stair builds them: up to the next level, opening its floor ----------
1245
+ let stairsWritten = false;
1246
+ payload.levels.forEach((level, index) => {
1247
+ const levelNode = levelNodes[index];
1248
+ const upper = levelNodes[index + 1];
1249
+ (level.stairs ?? []).forEach((stair, i) => {
1250
+ const id = `stair/${index}/${i}`;
1251
+ if (!upper) {
1252
+ defect(id, 'no_level_above', `A stair on "${level.name}", the top level, has nothing to rise to: give the level it reaches after this one in levels.`);
1253
+ return;
1254
+ }
1255
+ try {
1256
+ const outcome = createStair(written, {
1257
+ x: stair.start[0],
1258
+ z: stair.start[1],
1259
+ rotation: stair.rotation,
1260
+ layout: stair.layout,
1261
+ width: stair.width,
1262
+ ...(stair.turn ? { turn: stair.turn } : {}),
1263
+ ...(stair.length === undefined ? {} : { length: stair.length }),
1264
+ ...(stair.steps === undefined ? {} : { steps: stair.steps }),
1265
+ ...(stair.lowerSteps === undefined ? {} : { lowerSteps: stair.lowerSteps }),
1266
+ ...(stair.railing ? { railingMode: stair.railing } : {}),
1267
+ ...(stair.railingStyle ? { railingStyle: stair.railingStyle } : {}),
1268
+ ...(stair.name ? { name: stair.name } : {}),
1269
+ levelId: levelNode.id,
1270
+ toLevelId: upper.id,
1271
+ }, { ...context, activeLevelId: levelNode.id });
1272
+ const result = outcome.result;
1273
+ // create_stair mints fresh ids; the conversion's own keep the stair across revisions.
1274
+ let json = JSON.stringify(outcome.changes);
1275
+ (outcome.changes?.create ?? []).forEach(({ node }, k) => {
1276
+ const kind = node.id.slice(0, node.id.indexOf('_'));
1277
+ json = json.replaceAll(node.id, mint(kind, k ? `${id}/${k}` : id));
1278
+ });
1279
+ const changes = JSON.parse(json);
1280
+ const [made, ...rest] = changes.create;
1281
+ changes.create = [
1282
+ {
1283
+ ...made,
1284
+ node: {
1285
+ ...made.node,
1286
+ metadata: { ...made.node.metadata, authorScene: { id } },
1287
+ },
1288
+ },
1289
+ ...rest,
1290
+ ];
1291
+ written = applySceneChanges(written, changes);
1292
+ const finishes = [
1293
+ ['body', stair.color],
1294
+ ['treads', stair.treads ?? stair.color],
1295
+ ['railing', stair.railingColor],
1296
+ ];
1297
+ for (const [role, color] of finishes) {
1298
+ const finish = finishRef(color, id);
1299
+ if (!finish)
1300
+ continue;
1301
+ if (!nodeSlotDeclarations(written[made.node.id])?.some((slot) => slot.slotId === role)) {
1302
+ say(`${id}: it has no ${role}, its ${role === 'railing' ? 'railingColor' : 'color'} ignored`);
1303
+ continue;
1304
+ }
1305
+ try {
1306
+ const { changes } = paint(written, { targets: [made.node.id], material: finish, role }, paintContext(levelNode.id));
1307
+ written = applySceneChanges(written, changes);
1308
+ }
1309
+ catch (error) {
1310
+ defect(id, 'paint_refused', error.message);
1311
+ }
1312
+ }
1313
+ report.ids[id] = made.node.id;
1314
+ report.stairs++;
1315
+ stairsWritten = true;
1316
+ if (result.slabHoleCut)
1317
+ say(`${id}: ${result.message}`);
1318
+ else
1319
+ warn(id, 'no_floor_opening', result.message);
1320
+ }
1321
+ catch (error) {
1322
+ const e = error;
1323
+ defect(id, e.code ?? 'stair_refused', e.message ?? String(error));
1324
+ }
1325
+ });
1326
+ });
1327
+ /** The plan footprints of a level's walls as written, each grown by `grow` on both faces. */
1328
+ const wallFootprints = (levelId, grow = 0) => {
1329
+ const walls = Object.values(written).flatMap((node) => node.type === 'wall' && node.parentId === levelId
1330
+ ? [grow ? { ...node, thickness: getWallThickness(node) + 2 * grow } : node]
1331
+ : []);
1332
+ const miters = calculateLevelMiters(walls);
1333
+ return walls.map((wall) => getWallPlanFootprint(wall, miters).map(({ x, y }) => [x, y]));
1334
+ };
1335
+ // ---------- voids: openings in a level's floor, the room below rising through them ----------
1336
+ const voidCuts = new Map();
1337
+ const voidsCut = [];
1338
+ payload.levels.forEach((level, index) => {
1339
+ const levelNode = levelNodes[index];
1340
+ (level.voids ?? []).forEach((v, i) => {
1341
+ const id = `void/${index}/${i}`;
1342
+ const label = v.name ? `"${v.name}"` : `void ${i}`;
1343
+ if (index === 0) {
1344
+ defect(id, 'no_level_below', `${label} is in the floor of "${level.name}", the first level, with nothing under it to open onto: give it in the floor of the level above the room that rises through it.`);
1345
+ return;
1346
+ }
1347
+ // The cut stops at the faces of the walls standing on this floor, as the floor carries them,
1348
+ // and at the floor's own edge (a void drawn past a curved outside wall).
1349
+ const floor = union(Object.values(written).flatMap((node) => node.type === 'slab' && node.parentId === levelNode.id && node.plateRole === 'base'
1350
+ ? [node.polygon]
1351
+ : []));
1352
+ const cut = intersection(difference(v.polygon, union(wallFootprints(levelNode.id))), floor).filter((part) => area([part]) > 1e-4);
1353
+ const opened = area(cut);
1354
+ if (opened < 1e-4) {
1355
+ defect(id, 'no_floor', `No floor of "${level.name}" lies under ${label} clear of its walls: there is nothing to open.`);
1356
+ return;
1357
+ }
1358
+ if (cut.some((part) => part.holes.length))
1359
+ warn(id, 'wall_in_void', `A wall of "${level.name}" stands inside ${label}, over the opening around it: end the void at the wall, or move the wall.`);
1360
+ let cutsCeiling = false;
1361
+ cut.forEach((part, k) => {
1362
+ const plan = cutFloorOpening(written, {
1363
+ levelId: levelNode.id,
1364
+ polygon: part.outer,
1365
+ drawnOn: 'floor',
1366
+ mintId: () => mint('floor-opening', k ? `${id}/${k}` : id),
1367
+ });
1368
+ for (const change of plan.changes) {
1369
+ if (change.op === 'create') {
1370
+ const opening = change.node;
1371
+ cutsCeiling ||= opening.cutsAdjacent;
1372
+ written[opening.id] = {
1373
+ ...opening,
1374
+ name: v.name ?? 'Void',
1375
+ metadata: { ...opening.metadata, authorScene: { id } },
1376
+ };
1377
+ if (!k)
1378
+ report.ids[id] = opening.id;
1379
+ }
1380
+ else if (change.op === 'update')
1381
+ written[change.id] = { ...written[change.id], ...change.data };
1382
+ }
1383
+ });
1384
+ report.voids++;
1385
+ say(`${id}: the floor of "${level.name}" opened over ${opened.toFixed(1)} m², ${cutsCeiling ? `and the ceiling of "${payload.levels[index - 1].name}" under it` : `with no ceiling of "${payload.levels[index - 1].name}" under it`}`);
1386
+ voidCuts.set(index, union([...(voidCuts.get(index) ?? []), ...cut]));
1387
+ voidsCut.push({
1388
+ id,
1389
+ index,
1390
+ cut,
1391
+ railing: v.railing === null ? null : (v.railing ?? { height: 1.1 }),
1392
+ });
1393
+ });
1394
+ });
1395
+ // The openings the stairs and voids own cut the floor plates and ceilings they pass, as on load.
1396
+ if (stairsWritten || report.voids)
1397
+ written = {
1398
+ ...reconcileSceneStructure({ nodes: written, levelIds, mintId: (kind) => mint(kind) }).nodes,
1399
+ };
1400
+ // ---------- each void's railing: along its open edges, on the floor, as the slab railing looks ----------
1401
+ for (const { id, index, cut, railing } of voidsCut) {
1402
+ const levelNode = levelNodes[index];
1403
+ // Inside the double height the floor's edge round the void continues the walls it rises
1404
+ // between: it wears their inside paint, where the facade's edge keeps the cladding.
1405
+ const insides = (payload.levels[index - 1].walls ?? []).flatMap((w) => w.colorInside ? [w.colorInside] : []);
1406
+ const edge = finishRef(mostCommon(insides), id);
1407
+ if (edge)
1408
+ for (const zone of Object.values(written))
1409
+ if (zone.type === 'zone' &&
1410
+ zone.parentId === levelNode.id &&
1411
+ zone.spaceRole === 'room' &&
1412
+ area(intersection(cut, zone.polygon)) > 1e-4)
1413
+ written[zone.id] = { ...zone, floorEdgeFinish: edge };
1414
+ if (!railing)
1415
+ continue;
1416
+ // A run stops at a wall standing on the edge and where a stair arrives.
1417
+ const arrivals = Object.values(written).flatMap((node) => node.type === 'stair' && node.toLevelId === levelNode.id
1418
+ ? [stairArrivalOpening(node, written)]
1419
+ : []);
1420
+ const cover = union([
1421
+ ...wallFootprints(levelNode.id, 0.005),
1422
+ ...arrivals.filter((ring) => ring.length >= 3),
1423
+ ]);
1424
+ const runs = exposedIntervals(cut, [], cover).filter(({ start, end }) => dist(start, end) >= 0.1);
1425
+ const plate = Object.values(written)
1426
+ .filter((node) => node.type === 'slab' && node.parentId === levelNode.id && node.plateRole === 'base')
1427
+ .map((node) => ({ node, shared: area(intersection(cut, node.polygon)) }))
1428
+ .sort((a, b) => b.shared - a.shared)[0]?.node;
1429
+ const fences = runs.map((run, k) => FenceNode.parse({
1430
+ object: 'node',
1431
+ id: mint('fence', `${id}/railing/${k}`),
1432
+ parentId: levelNode.id,
1433
+ visible: true,
1434
+ metadata: { authorScene: { id } },
1435
+ name: 'Railing',
1436
+ start: run.start,
1437
+ end: run.end,
1438
+ height: railing.height,
1439
+ style: 'rail',
1440
+ baseStyle: 'floating',
1441
+ baseHeight: 0,
1442
+ postCap: 'none',
1443
+ ...(plate ? { supportSlabId: plate.id } : {}),
1444
+ }));
1445
+ if (!fences.length)
1446
+ continue;
1447
+ const level = written[levelNode.id];
1448
+ written[level.id] = {
1449
+ ...level,
1450
+ children: [...level.children, ...fences.map((f) => f.id)],
1451
+ };
1452
+ for (const fence of fences)
1453
+ written[fence.id] = fence;
1454
+ const finish = finishRef(railing.color, id);
1455
+ if (finish)
1456
+ try {
1457
+ const { changes } = paint(written, { targets: fences.map((fence) => fence.id), material: finish }, paintContext(levelNode.id));
1458
+ for (const u of changes?.update ?? [])
1459
+ written[u.id] = { ...written[u.id], ...u.data };
1460
+ }
1461
+ catch (error) {
1462
+ defect(id, 'paint_refused', error.message);
1463
+ }
1464
+ say(`${id}: a ${metres(railing.height)} railing along ${metres(runs.reduce((sum, run) => sum + dist(run.start, run.end), 0))} of its edges`);
1465
+ }
1466
+ // ---------- items, on their floors and ceilings, then each wall's paint ----------
1467
+ payload.levels.forEach((level, index) => {
1468
+ const levelNode = levelNodes[index];
1469
+ const { walls, wallNodes } = levelWalls[index];
711
1470
  // ---------- items, hosts first ----------
712
1471
  const pending = [...(level.items ?? [])];
713
1472
  let progress = true;
@@ -725,14 +1484,20 @@ export function authorScene(scene, input, context) {
725
1484
  defect(item.id, 'compile_failed', input.compileErrors?.[item.id] ?? 'The module did not compile.');
726
1485
  continue;
727
1486
  }
1487
+ if (/^(stairs?|staircases?)$/i.test(item.category))
1488
+ warn(item.id, 'use_stairs', `"${item.name}" is a stair written as an item: give it as its level's stairs, so it rises with the storey and opens the floor above.`);
728
1489
  const world = [item.position[0], item.position[1], item.position[2]];
729
1490
  const yaw = (item.rotation * Math.PI) / 180;
730
1491
  let placed;
1492
+ if (hostWall && isCurvedWall(hostWall.node)) {
1493
+ defect(item.id, 'curved_host', `Wall ${item.host} is curved: nothing hangs on a curved wall, as in the editor. Hang "${item.name}" on a straight wall, or stand it on the floor.`);
1494
+ continue;
1495
+ }
731
1496
  if (hostWall) {
732
1497
  // On the wall as place_items puts a wall-side item there: along it from its start, the
733
1498
  // payload's height for its bottom, on the face nearest the point, facing the room.
734
1499
  const [width, height] = scriptedSize(compiled.manifest);
735
- const wallHeight = hostWall.spec.height;
1500
+ const wallHeight = hostWall.height;
736
1501
  if (height > wallHeight) {
737
1502
  defect(item.id, 'item_too_tall', `"${item.name}" is ${metres(height)} tall; wall ${item.host} is ${metres(wallHeight)}.`);
738
1503
  continue;
@@ -773,8 +1538,27 @@ export function authorScene(scene, input, context) {
773
1538
  },
774
1539
  };
775
1540
  }
1541
+ else if (compiled.mount === 'ceiling' && !item.host) {
1542
+ // From the ceiling of the room its plan point is in, its top on that ceiling.
1543
+ const over = ceilingAt(index, world[0], world[2]);
1544
+ if (!over) {
1545
+ defect(item.id, 'no_ceiling', `"${item.name}" hangs from a ceiling, and no room on "${level.name}" contains (${world[0]}, ${world[2]}): move it into a room, or build it with mount 'floor'.`);
1546
+ continue;
1547
+ }
1548
+ say(`${item.id}: hung from the ceiling of ${over.room.name}, ${metres(over.height)} up`);
1549
+ placed = {
1550
+ compiled,
1551
+ parentId: over.ceiling.id,
1552
+ position: [world[0], 0, world[2]],
1553
+ rotation: item.rotation,
1554
+ frame: { position: [world[0], over.height, world[2]], yaw },
1555
+ };
1556
+ }
776
1557
  else {
777
1558
  const host = hostItemId ? itemFrames.get(item.host) : undefined;
1559
+ const opening = voidCuts.get(index);
1560
+ if (!host && opening && containsPoint(opening, [world[0], world[2]]))
1561
+ warn(item.id, 'over_void', `"${item.name}" stands at (${world[0]}, ${world[2]}), over a void in the floor of "${level.name}": nothing is under it. Move it onto the floor.`);
778
1562
  placed = {
779
1563
  compiled,
780
1564
  parentId: hostItemId ?? levelNode.id,
@@ -784,7 +1568,7 @@ export function authorScene(scene, input, context) {
784
1568
  };
785
1569
  }
786
1570
  try {
787
- const outcome = addObject(draft, {
1571
+ const outcome = addObject(written, {
788
1572
  compiled: { ...placed.compiled, nodeId: mint('item', item.id) },
789
1573
  name: item.name,
790
1574
  category: item.category,
@@ -795,13 +1579,16 @@ export function authorScene(scene, input, context) {
795
1579
  ...(placed.side ? { side: placed.side } : {}),
796
1580
  }, { ...context, activeLevelId: levelNode.id });
797
1581
  for (const { node, parentId } of outcome.changes?.create ?? []) {
798
- draft[node.id] = {
1582
+ written[node.id] = {
799
1583
  ...node,
800
1584
  slots: exactSlots(compiled, item.id),
801
1585
  ...(placed.wallT === undefined ? {} : { wallT: placed.wallT }),
802
1586
  };
803
- const parent = draft[parentId ?? levelNode.id];
804
- parent.children = [...(parent.children ?? []), node.id];
1587
+ const parent = written[parentId ?? levelNode.id];
1588
+ written[parent.id] = {
1589
+ ...parent,
1590
+ children: [...(parent.children ?? []), node.id],
1591
+ };
805
1592
  itemNodeIds.set(item.id, node.id);
806
1593
  itemFrames.set(item.id, placed.frame);
807
1594
  report.ids[item.id] = node.id;
@@ -817,15 +1604,46 @@ export function authorScene(scene, input, context) {
817
1604
  for (const item of pending)
818
1605
  defect(item.id, 'host_missing', `Its host ${item.host} was not placed.`);
819
1606
  // ---------- wall paint, through the editor's paint: both faces, or each its own ----------
1607
+ const inside = {
1608
+ left: [],
1609
+ right: [],
1610
+ both: [],
1611
+ neither: [],
1612
+ };
820
1613
  for (const w of walls) {
821
1614
  const node = wallNodes.get(w.spec.id)?.node;
822
1615
  if (!node)
823
1616
  continue;
824
- const faces = {
825
- a: w.spec.colorLeft ?? w.spec.color,
826
- b: w.spec.colorRight ?? w.spec.color,
827
- };
828
1617
  const coats = [];
1618
+ if (w.spec.curtain) {
1619
+ // Glass and mullions: no face to paint; its frame takes the colour.
1620
+ const ignored = ['color', 'colorInside', 'colorOutside', 'colorLeft', 'colorRight'].filter((field) => w.spec[field]);
1621
+ if (ignored.length)
1622
+ say(`wall ${w.spec.id}: a curtain wall has no faces to paint, its ${ignored.join(', ')} ignored; curtain.frameColor colours its frame`);
1623
+ const frame = finishRef(w.spec.curtain.frameColor, w.spec.id);
1624
+ if (frame)
1625
+ coats.push({ role: 'curtain-frame', material: frame });
1626
+ }
1627
+ const { colorInside, colorOutside } = w.spec.curtain ? {} : w.spec;
1628
+ // A face is inside when a room of this level stands on it, known once the rooms are derived.
1629
+ const cover = colorInside || colorOutside ? roomFaceCover(written, node.id) : undefined;
1630
+ const meant = (face) => cover && (cover[face] === 'none' ? colorOutside : colorInside);
1631
+ if (cover) {
1632
+ const faces = ['a', 'b'].filter((face) => cover[face] !== 'none');
1633
+ inside[faces.length === 2
1634
+ ? 'both'
1635
+ : faces.length === 0
1636
+ ? 'neither'
1637
+ : faces[0] === 'a'
1638
+ ? 'left'
1639
+ : 'right'].push(w.spec.id);
1640
+ }
1641
+ const faces = w.spec.curtain
1642
+ ? {}
1643
+ : {
1644
+ a: w.spec.colorLeft ?? meant('a') ?? w.spec.color,
1645
+ b: w.spec.colorRight ?? meant('b') ?? w.spec.color,
1646
+ };
829
1647
  if (faces.a && faces.a === faces.b) {
830
1648
  const ref = finishRef(faces.a, w.spec.id);
831
1649
  if (ref)
@@ -839,84 +1657,24 @@ export function authorScene(scene, input, context) {
839
1657
  }
840
1658
  for (const coat of coats) {
841
1659
  try {
842
- const { changes } = paint(draft, { targets: [node.id], ...coat }, paintContext(levelNode.id));
1660
+ const { changes } = paint(written, { targets: [node.id], ...coat }, paintContext(levelNode.id));
843
1661
  for (const u of changes?.update ?? [])
844
- draft[u.id] = { ...draft[u.id], ...u.data };
1662
+ written[u.id] = { ...written[u.id], ...u.data };
845
1663
  }
846
1664
  catch (error) {
847
1665
  defect(w.spec.id, 'paint_refused', error.message);
848
1666
  }
849
1667
  }
850
1668
  }
1669
+ const resolved = [
1670
+ inside.left.length && `the left face of ${inside.left.join(', ')}`,
1671
+ inside.right.length && `the right face of ${inside.right.join(', ')}`,
1672
+ inside.both.length && `both faces of ${inside.both.join(', ')}`,
1673
+ inside.neither.length && `neither face of ${inside.neither.join(', ')}`,
1674
+ ].filter(Boolean);
1675
+ if (resolved.length)
1676
+ say(`colorInside / colorOutside on "${level.name}", left and right as seen from each wall's start to its end: inside is ${resolved.join('; ')}`);
851
1677
  });
852
- report.levels = levelNodes.length;
853
- if (untypedWindows)
854
- say(`${untypedWindows} window${untypedWindows === 1 ? ' has' : 's have'} no type: written fixed (cannot open); give type`);
855
- // ---------- which face of each wall looks into a room: read from the plan, as the editor does ----------
856
- for (const levelNode of levelNodes) {
857
- const boundaries = Object.values(draft).filter((n) => n.parentId === levelNode.id && (n.type === 'wall' || n.type === 'separator'));
858
- for (const { wallId, ...sides } of detectSpacesForLevel(levelNode.id, boundaries).wallUpdates)
859
- draft[wallId] = { ...draft[wallId], ...sides };
860
- }
861
- // ---------- rooms: Pascal's own derivation, then the payload's names and floors ----------
862
- const { nodes: withZones, createdZoneIds } = migrateRoomZones(draft);
863
- const zones = createdZoneIds.map((id) => ZoneNode.parse(withZones[id]));
864
- for (const zone of zones) {
865
- draft[zone.id] = zone;
866
- const level = draft[zone.parentId];
867
- draft[zone.parentId] = { ...level, children: [...level.children, zone.id] };
868
- report.rooms++;
869
- }
870
- const claimed = new Map();
871
- payload.levels.forEach((level, index) => {
872
- const levelNode = levelNodes[index];
873
- for (const [i, room] of level.rooms.entries()) {
874
- const id = `room/${index}/${i}`;
875
- const zone = zones.find((z) => z.parentId === levelNode.id && pointInPolygon(room.seed, z.polygon));
876
- // Two seeds in one enclosure: the walls do not separate the rooms the payload names.
877
- if (zone && claimed.has(zone.id)) {
878
- defect(id, 'room_shares_enclosure', `"${room.name}" and "${claimed.get(zone.id)}" are one enclosure: no wall separates them.`);
879
- continue;
880
- }
881
- if (!zone) {
882
- defect(id, 'room_not_enclosed', `No closed room around (${room.seed[0]}, ${room.seed[1]}) "${room.name}": its walls do not meet.`);
883
- continue;
884
- }
885
- if (room.floorRegions.length > MANY_REGIONS)
886
- warn(id, 'many_regions', `"${room.name}" has ${room.floorRegions.length} floor regions: regions are fixed polygons that do not follow a resize; use one floor finish.`);
887
- const finish = finishRef(room.floor, id);
888
- const regions = room.floorRegions.map((r, k) => ({
889
- id: `region_${k}`,
890
- polygon: r.polygon,
891
- finish: finishRef(r.finish, id) ?? r.finish,
892
- }));
893
- draft[zone.id] = ZoneNode.parse({
894
- ...zone,
895
- name: room.name,
896
- spaceRole: 'room',
897
- ...(finish || regions.length
898
- ? {
899
- floor: {
900
- ...zone.floor,
901
- ...(finish ? { finish } : {}),
902
- ...(regions.length ? { regions } : {}),
903
- },
904
- }
905
- : {}),
906
- metadata: { ...zone.metadata, authorScene: { seed: room.seed } },
907
- });
908
- claimed.set(zone.id, room.name);
909
- report.ids[id] = zone.id;
910
- report.roomsNamed++;
911
- }
912
- });
913
- // ---------- floors and ceilings, as the editor derives them on load ----------
914
- const structure = reconcileSceneStructure({
915
- nodes: draft,
916
- levelIds: levelNodes.map((l) => l.id),
917
- mintId: (kind) => mint(kind),
918
- });
919
- const written = { ...structure.nodes };
920
1678
  // ---------- skirting, native on each wall, along the faces its rooms stand on ----------
921
1679
  for (const { id, nodeId, levelId, skirting } of skirtings) {
922
1680
  const finish = finishRef(skirting.color, id);
@@ -944,22 +1702,47 @@ export function authorScene(scene, input, context) {
944
1702
  // Ceilings exist only after structure reconciliation; lights can now use their room's host.
945
1703
  payload.levels.forEach((level, index) => {
946
1704
  const levelNode = levelNodes[index];
947
- for (const [i, light] of level.lights.entries()) {
948
- const id = `light/${index}/${i}`;
1705
+ const entries = level.lights.map((light, i) => ({ id: `light/${index}/${i}`, light }));
1706
+ // A named room with no light of its own (none whose plan point is in it, given or built into
1707
+ // an item) gets a plain ceiling light at its seed.
1708
+ const lit = [
1709
+ ...level.lights.map((light) => [light.position[0], light.position[2]]),
1710
+ ...(level.items ?? []).flatMap((item) => {
1711
+ const frame = itemFrames.get(item.id);
1712
+ return frame && input.compiled[item.id]?.manifest.lights?.length
1713
+ ? [[frame.position[0], frame.position[2]]]
1714
+ : [];
1715
+ }),
1716
+ ];
1717
+ const seen = new Set();
1718
+ level.rooms.forEach((room, i) => {
1719
+ const zone = written[report.ids[`room/${index}/${i}`] ?? ''];
1720
+ if (!zone || seen.has(zone.id))
1721
+ return;
1722
+ seen.add(zone.id);
1723
+ const inside = (p) => pointInPolygon(p, zone.polygon) && !zone.holes.some((hole) => pointInPolygon(p, hole));
1724
+ if (lit.some(inside))
1725
+ return;
1726
+ const over = ceilingAt(index, room.seed[0], room.seed[1]);
1727
+ if (!over)
1728
+ return;
1729
+ entries.push({
1730
+ id: roomLightId(index, i),
1731
+ light: {
1732
+ position: [room.seed[0], over.height - 0.1, room.seed[1]],
1733
+ color: '#ffffff',
1734
+ intensity: 1,
1735
+ },
1736
+ room: zone.name ?? room.name,
1737
+ });
1738
+ });
1739
+ for (const { id, light, room: unlit } of entries) {
949
1740
  const at = [light.position[0], light.position[1], light.position[2]];
950
1741
  let hostId = light.fixture ? itemNodeIds.get(light.fixture) : undefined;
951
1742
  let hostFrame = light.fixture ? itemFrames.get(light.fixture) : undefined;
952
- const room = !light.fixture
953
- ? Object.values(written).find((node) => node.type === 'zone' &&
954
- node.spaceRole === 'room' &&
955
- node.parentId === levelNode.id &&
956
- pointInPolygon([at[0], at[2]], node.polygon) &&
957
- !node.holes.some((hole) => pointInPolygon([at[0], at[2]], hole)))
958
- : undefined;
959
- const ceiling = room
960
- ? Object.values(written).find((node) => node.type === 'ceiling' && node.zoneId === room.id && node.parentId === levelNode.id)
961
- : undefined;
962
- if (ceiling) {
1743
+ const over = light.fixture ? undefined : ceilingAt(index, at[0], at[2]);
1744
+ if (over) {
1745
+ const { room, ceiling } = over;
963
1746
  const compiled = input.compiled[id];
964
1747
  if (!compiled) {
965
1748
  defect(id, 'compile_failed', input.compileErrors?.[id] ?? 'The ceiling point module did not compile.');
@@ -980,12 +1763,14 @@ export function authorScene(scene, input, context) {
980
1763
  written[ceiling.id] = { ...ceiling, children: [...ceiling.children, node.id] };
981
1764
  hostId = node.id;
982
1765
  hostFrame = {
983
- position: [at[0], resolveCeilingHeight(ceiling, written) - CEILING_DRAW_OFFSET, at[2]],
1766
+ position: [at[0], over.height, at[2]],
984
1767
  yaw: 0,
985
1768
  };
986
1769
  report.ids[id] = node.id;
987
1770
  report.items++;
988
- say(`light at [${at.join(', ')}] has no fixture: a ceiling point in ${room.name}`);
1771
+ say(unlit
1772
+ ? `${unlit} had no light: a ceiling light at its centre`
1773
+ : `light at [${at.join(', ')}] has no fixture: a ceiling point in ${room.name}`);
989
1774
  }
990
1775
  catch (error) {
991
1776
  const e = error;
@@ -1023,6 +1808,9 @@ export function authorScene(scene, input, context) {
1023
1808
  color: light.color,
1024
1809
  intensityRange: [0, light.intensity],
1025
1810
  offset: toLocal(hostFrame, at),
1811
+ spot: lightSpot(light, at, hostFrame),
1812
+ shadows: light.shadows,
1813
+ glow: light.glow,
1026
1814
  },
1027
1815
  ];
1028
1816
  written[hostNode.id] = {
@@ -1032,14 +1820,129 @@ export function authorScene(scene, input, context) {
1032
1820
  report.lights++;
1033
1821
  }
1034
1822
  });
1035
- const nodes = Object.fromEntries(Object.entries(written).filter(([id]) => !scaffoldIds.has(id)));
1036
- const candidate = {
1037
- version: 1,
1038
- buildingId: building.id,
1039
- seed,
1040
- ids: report.ids,
1041
- nodes,
1042
- };
1823
+ // ---------- the roof: on the building's roof level above the top storey, as create_roof puts it ----------
1824
+ const roof = payload.roof;
1825
+ if (roof && (roof.segments.length || roof.items?.length)) {
1826
+ const top = levelNodes.at(-1);
1827
+ const segments = roof.segments.map((spec, i) => {
1828
+ const segment = RoofSegmentNode.parse({
1829
+ roofType: spec.roofType,
1830
+ width: spec.width,
1831
+ depth: spec.depth,
1832
+ wallHeight: 0,
1833
+ rotation: (spec.rotation * Math.PI) / 180,
1834
+ position: [spec.center[0], 0, spec.center[1]],
1835
+ ...(spec.pitch !== undefined && { pitch: spec.pitch }),
1836
+ ...(spec.gableOverhang !== undefined && { gableOverhang: spec.gableOverhang }),
1837
+ });
1838
+ // The overhang is asked in plan; the segment stores it along its slope (a barrel's, at its ends).
1839
+ const overhang = spec.roofType === 'barrel'
1840
+ ? spec.overhang
1841
+ : spec.overhang / getSegmentSlopeFrame(segment).cosTheta;
1842
+ return { ...segment, id: mint('rseg', `roof/segment/${i}`), overhang };
1843
+ });
1844
+ const peak = Math.max(0, ...segments.map((segment) => getActiveRoofHeight(segment)));
1845
+ // Nothing stands above the payload's top level in its candidate: the plan is a roof level.
1846
+ const plan = planRoofLevel(written, top.id, peak);
1847
+ const created = plan.kind === 'roof' ? plan.create : null;
1848
+ const levelId = created ? mint('level', 'level/roof') : plan.levelId;
1849
+ if (created)
1850
+ written = applySceneChanges(written, {
1851
+ create: [{ node: { ...created, id: levelId }, parentId: building.id }],
1852
+ });
1853
+ report.ids['level/roof'] = levelId;
1854
+ // A roof measured from the top storey's floor stands this much lower on the roof level.
1855
+ const offset = plan.storeyOffset;
1856
+ let rises = peak;
1857
+ if (segments.length) {
1858
+ const cx = segments.reduce((sum, s) => sum + s.position[0], 0) / segments.length;
1859
+ const cz = segments.reduce((sum, s) => sum + s.position[2], 0) / segments.length;
1860
+ const node = RoofNode.parse({
1861
+ id: mint('roof', 'roof'),
1862
+ name: roof.name ?? 'Roof',
1863
+ parentId: levelId,
1864
+ position: [cx, 0, cz],
1865
+ rotation: 0,
1866
+ support: { kind: 'walls' },
1867
+ children: segments.map((segment) => segment.id),
1868
+ metadata: { authorScene: { id: 'roof' } },
1869
+ });
1870
+ written = applySceneChanges(written, {
1871
+ create: [
1872
+ { node, parentId: levelId },
1873
+ ...segments.map((segment) => ({
1874
+ node: {
1875
+ ...segment,
1876
+ parentId: node.id,
1877
+ position: [segment.position[0] - cx, 0, segment.position[2] - cz],
1878
+ },
1879
+ parentId: node.id,
1880
+ })),
1881
+ ],
1882
+ });
1883
+ const elevation = resolveRoofElevation(written[node.id], written);
1884
+ written[node.id] = { ...node, position: [cx, elevation, cz] };
1885
+ rises = elevation + peak;
1886
+ report.ids.roof = node.id;
1887
+ report.roofSegments = segments.length;
1888
+ for (const [role, color] of [
1889
+ ['top', roof.color],
1890
+ ['wall', roof.wallColor],
1891
+ ]) {
1892
+ const finish = finishRef(color, 'roof');
1893
+ if (finish)
1894
+ try {
1895
+ const { changes } = paint(written, { targets: [node.id], material: finish, role }, paintContext(levelId));
1896
+ for (const u of changes?.update ?? [])
1897
+ written[u.id] = { ...written[u.id], ...u.data };
1898
+ }
1899
+ catch (error) {
1900
+ defect('roof', 'paint_refused', error.message);
1901
+ }
1902
+ }
1903
+ }
1904
+ for (const item of roof.items ?? []) {
1905
+ const compiled = input.compiled[item.id];
1906
+ if (!compiled) {
1907
+ defect(item.id, 'compile_failed', input.compileErrors?.[item.id] ?? 'The module did not compile.');
1908
+ continue;
1909
+ }
1910
+ try {
1911
+ const outcome = addObject(written, {
1912
+ compiled: { ...compiled, nodeId: mint('item', item.id) },
1913
+ name: item.name,
1914
+ category: item.category,
1915
+ reason: `author_scene: ${item.name}`,
1916
+ position: [item.position[0], item.position[1] + offset, item.position[2]],
1917
+ rotation: item.rotation,
1918
+ parentId: levelId,
1919
+ role: 'roof',
1920
+ }, { ...context, activeLevelId: levelId });
1921
+ for (const { node } of outcome.changes?.create ?? []) {
1922
+ written = applySceneChanges(written, {
1923
+ create: [
1924
+ {
1925
+ node: { ...node, slots: exactSlots(compiled, item.id) },
1926
+ parentId: levelId,
1927
+ },
1928
+ ],
1929
+ });
1930
+ report.ids[item.id] = node.id;
1931
+ report.items++;
1932
+ rises = Math.max(rises, item.position[1] + offset + scriptedSize(compiled.manifest)[1]);
1933
+ }
1934
+ }
1935
+ catch (error) {
1936
+ const e = error;
1937
+ defect(item.id, e.code ?? 'item_refused', e.message ?? String(error));
1938
+ }
1939
+ }
1940
+ // A roof level made for it is as tall as the roof rises over it.
1941
+ if (created)
1942
+ written[levelId] = { ...written[levelId], height: Math.max(rises, 0.2) };
1943
+ }
1944
+ const nodes = plain(Object.fromEntries(Object.entries(written).filter(([id]) => !scaffoldIds.has(id))));
1945
+ const candidate = { buildingId: building.id, seed, ids: report.ids, nodes };
1043
1946
  const outcome = {
1044
1947
  result: report,
1045
1948
  candidate,
@@ -1053,24 +1956,88 @@ export function finalizeAuthorScene(scene, outcome) {
1053
1956
  const building = scene[outcome.buildingId];
1054
1957
  if (building?.type !== 'building')
1055
1958
  refuse('no_building', 'The target building no longer exists.');
1056
- const previous = building.metadata?.authorSceneState;
1057
- const base = previous?.nodes ??
1058
- Object.fromEntries(Object.values(scene)
1059
- .filter((node) => node.type === 'level' && node.parentId === building.id)
1060
- .map((node) => [node.id, node]));
1061
- const changes = authoredSceneChanges(scene, base, outcome.candidate.nodes);
1959
+ const { candidate } = outcome;
1960
+ const current = Object.fromEntries(Object.entries(scene).map(([id, node]) => [id, withoutBookkeeping(node)]));
1961
+ const base = authoredBaseline(scene, building)?.nodes ??
1962
+ Object.fromEntries(queryNodes(current, { type: 'level', parentId: building.id }).map((node) => [node.id, node]));
1963
+ const changes = authoredSceneChanges(current, base, candidate.nodes);
1964
+ // Each written node keeps its own baseline; each written level lists what stands on it and the
1965
+ // building what it holds itself (its units), so a revision finds every node of the last one.
1966
+ const holderOf = (id) => {
1967
+ let node = candidate.nodes[id];
1968
+ while (node && node.type !== 'level') {
1969
+ if (node.parentId === building.id)
1970
+ return building.id;
1971
+ node = node.parentId ? candidate.nodes[node.parentId] : undefined;
1972
+ }
1973
+ return node?.id;
1974
+ };
1975
+ const keyOf = new Map(Object.entries(candidate.ids).map(([key, id]) => [id, key]));
1976
+ const writtenOn = new Map();
1977
+ for (const id of Object.keys(candidate.nodes)) {
1978
+ const holder = holderOf(id);
1979
+ if (!holder)
1980
+ continue;
1981
+ const written = writtenOn.get(holder) ?? { ids: [], keys: {} };
1982
+ written.ids.push(id);
1983
+ const key = keyOf.get(id);
1984
+ if (key)
1985
+ written.keys[key] = id;
1986
+ writtenOn.set(holder, written);
1987
+ }
1988
+ const bookkeeping = (id) => ({
1989
+ authorSceneBase: withoutBookkeeping(candidate.nodes[id]),
1990
+ ...(writtenOn.has(id) ? { authorSceneWritten: writtenOn.get(id) } : {}),
1991
+ });
1992
+ const create = (changes.create ?? []).map((entry) => candidate.nodes[entry.node.id]
1993
+ ? {
1994
+ ...entry,
1995
+ node: {
1996
+ ...entry.node,
1997
+ metadata: { ...entry.node.metadata, ...bookkeeping(entry.node.id) },
1998
+ },
1999
+ }
2000
+ : entry);
2001
+ const created = new Set(create.map((entry) => entry.node.id));
2002
+ const removed = new Set(changes.delete ?? []);
2003
+ const updated = new Map((changes.update ?? []).map((entry) => [entry.id, entry]));
2004
+ for (const id of Object.keys(candidate.nodes)) {
2005
+ const now = scene[id];
2006
+ if (created.has(id) || removed.has(id) || !now)
2007
+ continue;
2008
+ const kept = { ...bookkeeping(id) };
2009
+ const stored = Object.fromEntries(BOOKKEEPING.flatMap((key) => now.metadata && key in now.metadata ? [[key, now.metadata[key]]] : []));
2010
+ const entry = updated.get(id);
2011
+ if (!entry && equalJson(stored, kept))
2012
+ continue;
2013
+ const metadata = (entry?.data.metadata ??
2014
+ current[id]?.metadata ??
2015
+ {});
2016
+ updated.set(id, {
2017
+ id,
2018
+ data: { ...entry?.data, metadata: { ...metadata, ...kept } },
2019
+ });
2020
+ }
2021
+ const state = { version: 2, buildingId: building.id, seed: candidate.seed };
2022
+ updated.set(building.id, {
2023
+ id: building.id,
2024
+ data: {
2025
+ ...updated.get(building.id)?.data,
2026
+ metadata: {
2027
+ ...withoutBookkeeping(building).metadata,
2028
+ authorSceneState: state,
2029
+ ...(writtenOn.has(building.id) ? { authorSceneWritten: writtenOn.get(building.id) } : {}),
2030
+ },
2031
+ },
2032
+ });
1062
2033
  return {
1063
2034
  ...outcome,
1064
2035
  changes: {
1065
2036
  ...changes,
2037
+ create,
1066
2038
  ...(outcome.changes?.materials ? { materials: outcome.changes.materials } : {}),
1067
- update: [
1068
- ...(changes.update ?? []),
1069
- {
1070
- id: building.id,
1071
- data: { metadata: { ...building.metadata, authorSceneState: outcome.candidate } },
1072
- },
1073
- ],
2039
+ update: [...updated.values()],
1074
2040
  },
1075
2041
  };
1076
2042
  }
2043
+ const equalJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);