@pascal-app/nodes 0.1.1 → 1.0.0-beta.1

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 (408) hide show
  1. package/README.md +34 -0
  2. package/dist/building/definition.js +1 -1
  3. package/dist/cabinet/definition.d.ts.map +1 -1
  4. package/dist/cabinet/definition.js +809 -72
  5. package/dist/cabinet/geometry.d.ts.map +1 -1
  6. package/dist/cabinet/geometry.js +4 -0
  7. package/dist/cabinet/move-frame.js +3 -3
  8. package/dist/cabinet/panel.d.ts.map +1 -1
  9. package/dist/cabinet/panel.js +1 -0
  10. package/dist/cabinet/placement-snap.d.ts +8 -0
  11. package/dist/cabinet/placement-snap.d.ts.map +1 -0
  12. package/dist/cabinet/placement-snap.js +19 -0
  13. package/dist/cabinet/presets.js +8 -8
  14. package/dist/cabinet/quick-actions.d.ts.map +1 -1
  15. package/dist/cabinet/quick-actions.js +9 -3
  16. package/dist/cabinet/resize-limits.d.ts +11 -0
  17. package/dist/cabinet/resize-limits.d.ts.map +1 -0
  18. package/dist/cabinet/resize-limits.js +22 -0
  19. package/dist/cabinet/run-layout.d.ts +8 -2
  20. package/dist/cabinet/run-layout.d.ts.map +1 -1
  21. package/dist/cabinet/run-layout.js +59 -7
  22. package/dist/cabinet/run-ops.d.ts +44 -3
  23. package/dist/cabinet/run-ops.d.ts.map +1 -1
  24. package/dist/cabinet/run-ops.js +646 -110
  25. package/dist/cabinet/run-panel.d.ts +2 -1
  26. package/dist/cabinet/run-panel.d.ts.map +1 -1
  27. package/dist/cabinet/run-panel.js +38 -4
  28. package/dist/cabinet/stack-transitions.js +4 -4
  29. package/dist/cabinet/tool.d.ts.map +1 -1
  30. package/dist/cabinet/tool.js +95 -17
  31. package/dist/cabinet/wall-snap.d.ts.map +1 -1
  32. package/dist/cabinet/wall-snap.js +2 -6
  33. package/dist/ceiling/boundary-editor.d.ts.map +1 -1
  34. package/dist/ceiling/boundary-editor.js +2 -2
  35. package/dist/ceiling/definition.d.ts +1 -1
  36. package/dist/ceiling/definition.d.ts.map +1 -1
  37. package/dist/ceiling/definition.js +36 -5
  38. package/dist/ceiling/floorplan-affordances.d.ts +4 -4
  39. package/dist/ceiling/floorplan-move.d.ts +4 -4
  40. package/dist/ceiling/floorplan-move.d.ts.map +1 -1
  41. package/dist/ceiling/floorplan-move.js +5 -4
  42. package/dist/ceiling/hole-editor.d.ts.map +1 -1
  43. package/dist/ceiling/hole-editor.js +2 -2
  44. package/dist/ceiling/move-tool.d.ts.map +1 -1
  45. package/dist/ceiling/move-tool.js +4 -3
  46. package/dist/ceiling/panel.d.ts.map +1 -1
  47. package/dist/ceiling/panel.js +65 -3
  48. package/dist/ceiling/renderer.d.ts.map +1 -1
  49. package/dist/ceiling/renderer.js +6 -2
  50. package/dist/ceiling/tool.d.ts.map +1 -1
  51. package/dist/ceiling/tool.js +12 -1
  52. package/dist/chimney/definition.d.ts.map +1 -1
  53. package/dist/chimney/definition.js +1 -0
  54. package/dist/chimney/tool.d.ts.map +1 -1
  55. package/dist/chimney/tool.js +16 -1
  56. package/dist/column/definition.d.ts.map +1 -1
  57. package/dist/column/definition.js +14 -2
  58. package/dist/column/floorplan-affordances.d.ts.map +1 -1
  59. package/dist/column/floorplan-affordances.js +19 -14
  60. package/dist/column/floorplan-move.d.ts.map +1 -1
  61. package/dist/column/floorplan-move.js +7 -2
  62. package/dist/column/floorplan.d.ts +9 -1
  63. package/dist/column/floorplan.d.ts.map +1 -1
  64. package/dist/column/floorplan.js +59 -4
  65. package/dist/column/tool.d.ts.map +1 -1
  66. package/dist/column/tool.js +33 -7
  67. package/dist/construction-dimension/definition.d.ts +4 -0
  68. package/dist/construction-dimension/definition.d.ts.map +1 -0
  69. package/dist/construction-dimension/definition.js +83 -0
  70. package/dist/construction-dimension/drawing-coordination.d.ts +7 -0
  71. package/dist/construction-dimension/drawing-coordination.d.ts.map +1 -0
  72. package/dist/construction-dimension/drawing-coordination.js +54 -0
  73. package/dist/construction-dimension/floorplan-affordances.d.ts +4 -0
  74. package/dist/construction-dimension/floorplan-affordances.d.ts.map +1 -0
  75. package/dist/construction-dimension/floorplan-affordances.js +114 -0
  76. package/dist/construction-dimension/floorplan-tool.d.ts +21 -0
  77. package/dist/construction-dimension/floorplan-tool.d.ts.map +1 -0
  78. package/dist/construction-dimension/floorplan-tool.js +557 -0
  79. package/dist/construction-dimension/floorplan.d.ts +3 -0
  80. package/dist/construction-dimension/floorplan.d.ts.map +1 -0
  81. package/dist/construction-dimension/floorplan.js +457 -0
  82. package/dist/construction-dimension/geometry.d.ts +31 -0
  83. package/dist/construction-dimension/geometry.d.ts.map +1 -0
  84. package/dist/construction-dimension/geometry.js +144 -0
  85. package/dist/construction-dimension/index.d.ts +4 -0
  86. package/dist/construction-dimension/index.d.ts.map +1 -0
  87. package/dist/construction-dimension/index.js +3 -0
  88. package/dist/construction-dimension/panel.d.ts +2 -0
  89. package/dist/construction-dimension/panel.d.ts.map +1 -0
  90. package/dist/construction-dimension/panel.js +128 -0
  91. package/dist/construction-dimension/parametrics.d.ts +3 -0
  92. package/dist/construction-dimension/parametrics.d.ts.map +1 -0
  93. package/dist/construction-dimension/parametrics.js +4 -0
  94. package/dist/construction-dimension/schema.d.ts +2 -0
  95. package/dist/construction-dimension/schema.d.ts.map +1 -0
  96. package/dist/construction-dimension/schema.js +1 -0
  97. package/dist/door/contextual-dimensions.d.ts +3 -0
  98. package/dist/door/contextual-dimensions.d.ts.map +1 -0
  99. package/dist/door/contextual-dimensions.js +7 -0
  100. package/dist/door/definition.d.ts.map +1 -1
  101. package/dist/door/definition.js +13 -8
  102. package/dist/door/floorplan-affordances.d.ts +2 -5
  103. package/dist/door/floorplan-affordances.d.ts.map +1 -1
  104. package/dist/door/floorplan-affordances.js +9 -18
  105. package/dist/door/floorplan.d.ts +3 -4
  106. package/dist/door/floorplan.d.ts.map +1 -1
  107. package/dist/door/floorplan.js +88 -6
  108. package/dist/door/move-tool.d.ts.map +1 -1
  109. package/dist/door/move-tool.js +2 -2
  110. package/dist/door/panel.d.ts.map +1 -1
  111. package/dist/door/panel.js +3 -1
  112. package/dist/door/schema.d.ts +1 -1
  113. package/dist/door/schema.d.ts.map +1 -1
  114. package/dist/door/schema.js +1 -1
  115. package/dist/door/tool.d.ts.map +1 -1
  116. package/dist/door/tool.js +63 -6
  117. package/dist/dormer/definition.d.ts.map +1 -1
  118. package/dist/dormer/definition.js +1 -0
  119. package/dist/dormer/tool.d.ts.map +1 -1
  120. package/dist/dormer/tool.js +23 -1
  121. package/dist/duct-segment/tool.d.ts.map +1 -1
  122. package/dist/duct-segment/tool.js +20 -9
  123. package/dist/duct-terminal/tool.d.ts.map +1 -1
  124. package/dist/duct-terminal/tool.js +9 -6
  125. package/dist/elevator/floorplan-affordances.d.ts +2 -6
  126. package/dist/elevator/floorplan-affordances.d.ts.map +1 -1
  127. package/dist/elevator/floorplan-affordances.js +13 -12
  128. package/dist/fence/actions/move-endpoint.d.ts.map +1 -1
  129. package/dist/fence/actions/move-endpoint.js +29 -1
  130. package/dist/fence/definition.d.ts.map +1 -1
  131. package/dist/fence/definition.js +67 -14
  132. package/dist/fence/floorplan-affordances.d.ts.map +1 -1
  133. package/dist/fence/floorplan-affordances.js +48 -11
  134. package/dist/fence/floorplan-move.d.ts.map +1 -1
  135. package/dist/fence/floorplan-move.js +38 -3
  136. package/dist/fence/geometry.d.ts.map +1 -1
  137. package/dist/fence/geometry.js +9 -1
  138. package/dist/fence/lift.d.ts +15 -0
  139. package/dist/fence/lift.d.ts.map +1 -0
  140. package/dist/fence/lift.js +23 -0
  141. package/dist/fence/move-tool.d.ts.map +1 -1
  142. package/dist/fence/move-tool.js +25 -5
  143. package/dist/fence/system.d.ts +3 -0
  144. package/dist/fence/system.d.ts.map +1 -0
  145. package/dist/fence/system.js +41 -0
  146. package/dist/fence/tool.d.ts.map +1 -1
  147. package/dist/fence/tool.js +103 -32
  148. package/dist/hvac-equipment/tool.d.ts.map +1 -1
  149. package/dist/hvac-equipment/tool.js +8 -3
  150. package/dist/index.d.ts +2 -0
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/index.js +6 -0
  153. package/dist/item/definition.d.ts.map +1 -1
  154. package/dist/item/definition.js +7 -1
  155. package/dist/item/floorplan-move.d.ts.map +1 -1
  156. package/dist/item/floorplan-move.js +50 -43
  157. package/dist/item/floorplan.d.ts +1 -0
  158. package/dist/item/floorplan.d.ts.map +1 -1
  159. package/dist/item/floorplan.js +49 -0
  160. package/dist/item/renderer.d.ts +3 -0
  161. package/dist/item/renderer.d.ts.map +1 -1
  162. package/dist/item/renderer.js +96 -8
  163. package/dist/level/definition.d.ts.map +1 -1
  164. package/dist/level/definition.js +7 -3
  165. package/dist/lineset/tool.d.ts.map +1 -1
  166. package/dist/lineset/tool.js +5 -1
  167. package/dist/liquid-line/tool.d.ts.map +1 -1
  168. package/dist/liquid-line/tool.js +7 -1
  169. package/dist/measurement/definition.d.ts.map +1 -1
  170. package/dist/measurement/definition.js +5 -0
  171. package/dist/measurement/floorplan.d.ts.map +1 -1
  172. package/dist/measurement/floorplan.js +11 -10
  173. package/dist/measurement/index.d.ts +1 -1
  174. package/dist/measurement/index.d.ts.map +1 -1
  175. package/dist/measurement/index.js +1 -1
  176. package/dist/measurement/resolve.d.ts +5 -0
  177. package/dist/measurement/resolve.d.ts.map +1 -1
  178. package/dist/measurement/resolve.js +2 -2
  179. package/dist/measurement/surface-query.js +5 -5
  180. package/dist/measurement/tool.d.ts +6 -1
  181. package/dist/measurement/tool.d.ts.map +1 -1
  182. package/dist/measurement/tool.js +7 -11
  183. package/dist/pipe-segment/tool.d.ts.map +1 -1
  184. package/dist/pipe-segment/tool.js +22 -19
  185. package/dist/roof-segment/floorplan-affordances.d.ts.map +1 -1
  186. package/dist/roof-segment/floorplan-affordances.js +14 -8
  187. package/dist/shared/construction-dimension-standards.d.ts +34 -0
  188. package/dist/shared/construction-dimension-standards.d.ts.map +1 -0
  189. package/dist/shared/construction-dimension-standards.js +18 -0
  190. package/dist/shared/construction-length.d.ts +10 -0
  191. package/dist/shared/construction-length.d.ts.map +1 -0
  192. package/dist/shared/construction-length.js +57 -0
  193. package/dist/shared/dimension-string.d.ts +20 -0
  194. package/dist/shared/dimension-string.d.ts.map +1 -0
  195. package/dist/shared/dimension-string.js +19 -0
  196. package/dist/shared/draft-axis-guides.d.ts +36 -0
  197. package/dist/shared/draft-axis-guides.d.ts.map +1 -0
  198. package/dist/shared/draft-axis-guides.js +107 -0
  199. package/dist/shared/move-roof-tool.d.ts.map +1 -1
  200. package/dist/shared/move-roof-tool.js +17 -3
  201. package/dist/shared/opening-documentation-fields.d.ts +16 -0
  202. package/dist/shared/opening-documentation-fields.d.ts.map +1 -0
  203. package/dist/shared/opening-documentation-fields.js +54 -0
  204. package/dist/shared/opening-documentation.d.ts +47 -0
  205. package/dist/shared/opening-documentation.d.ts.map +1 -0
  206. package/dist/shared/opening-documentation.js +300 -0
  207. package/dist/shared/opening-guides-runtime.d.ts.map +1 -1
  208. package/dist/shared/opening-guides-runtime.js +3 -2
  209. package/dist/shared/opening-placement-dimensions.d.ts.map +1 -1
  210. package/dist/shared/opening-placement-dimensions.js +14 -5
  211. package/dist/shared/wall-opening-ceiling.d.ts +30 -0
  212. package/dist/shared/wall-opening-ceiling.d.ts.map +1 -0
  213. package/dist/shared/wall-opening-ceiling.js +29 -0
  214. package/dist/shelf/floorplan-affordances.d.ts.map +1 -1
  215. package/dist/shelf/floorplan-affordances.js +10 -5
  216. package/dist/shelf/floorplan-move.d.ts +2 -16
  217. package/dist/shelf/floorplan-move.d.ts.map +1 -1
  218. package/dist/shelf/floorplan-move.js +19 -40
  219. package/dist/shelf/tool.d.ts.map +1 -1
  220. package/dist/shelf/tool.js +8 -3
  221. package/dist/site/recessed-slab-ground-holes.d.ts.map +1 -1
  222. package/dist/site/recessed-slab-ground-holes.js +1 -4
  223. package/dist/site/renderer.d.ts.map +1 -1
  224. package/dist/site/renderer.js +118 -28
  225. package/dist/site/terrain-drape.d.ts +91 -0
  226. package/dist/site/terrain-drape.d.ts.map +1 -0
  227. package/dist/site/terrain-drape.js +205 -0
  228. package/dist/site/terrain-geometry.d.ts +131 -0
  229. package/dist/site/terrain-geometry.d.ts.map +1 -0
  230. package/dist/site/terrain-geometry.js +259 -0
  231. package/dist/site/terrain-mesh.d.ts +49 -0
  232. package/dist/site/terrain-mesh.d.ts.map +1 -0
  233. package/dist/site/terrain-mesh.js +122 -0
  234. package/dist/site/terrain-renderer.d.ts +28 -0
  235. package/dist/site/terrain-renderer.d.ts.map +1 -0
  236. package/dist/site/terrain-renderer.js +69 -0
  237. package/dist/skylight/definition.d.ts.map +1 -1
  238. package/dist/skylight/definition.js +1 -0
  239. package/dist/skylight/tool.d.ts.map +1 -1
  240. package/dist/skylight/tool.js +16 -1
  241. package/dist/slab/definition.d.ts.map +1 -1
  242. package/dist/slab/definition.js +87 -10
  243. package/dist/slab/elevation-limit.d.ts +43 -0
  244. package/dist/slab/elevation-limit.d.ts.map +1 -0
  245. package/dist/slab/elevation-limit.js +124 -0
  246. package/dist/slab/floorplan-affordances.d.ts +16 -0
  247. package/dist/slab/floorplan-affordances.d.ts.map +1 -1
  248. package/dist/slab/geometry.d.ts.map +1 -1
  249. package/dist/slab/geometry.js +98 -5
  250. package/dist/slab/panel.d.ts.map +1 -1
  251. package/dist/slab/panel.js +82 -4
  252. package/dist/slab/parametrics.d.ts +1 -1
  253. package/dist/slab/parametrics.d.ts.map +1 -1
  254. package/dist/slab/parametrics.js +13 -1
  255. package/dist/slab/placement-ownership.d.ts +3 -0
  256. package/dist/slab/placement-ownership.d.ts.map +1 -0
  257. package/dist/slab/placement-ownership.js +3 -0
  258. package/dist/slab/quick-measurement.d.ts.map +1 -1
  259. package/dist/slab/quick-measurement.js +3 -2
  260. package/dist/slab/system.d.ts.map +1 -1
  261. package/dist/slab/system.js +14 -4
  262. package/dist/slab/tool.d.ts.map +1 -1
  263. package/dist/slab/tool.js +81 -23
  264. package/dist/solar-panel/definition.d.ts.map +1 -1
  265. package/dist/solar-panel/definition.js +1 -0
  266. package/dist/solar-panel/tool.d.ts.map +1 -1
  267. package/dist/solar-panel/tool.js +17 -1
  268. package/dist/spawn/definition.d.ts.map +1 -1
  269. package/dist/spawn/definition.js +1 -0
  270. package/dist/spawn/floorplan-affordances.d.ts.map +1 -1
  271. package/dist/spawn/floorplan-affordances.js +7 -4
  272. package/dist/spawn/floorplan-move.d.ts.map +1 -1
  273. package/dist/spawn/floorplan-move.js +8 -6
  274. package/dist/spawn/renderer.d.ts +4 -0
  275. package/dist/spawn/renderer.d.ts.map +1 -1
  276. package/dist/spawn/renderer.js +32 -18
  277. package/dist/spawn/tool.d.ts +2 -2
  278. package/dist/spawn/tool.d.ts.map +1 -1
  279. package/dist/spawn/tool.js +78 -11
  280. package/dist/stair/definition.d.ts.map +1 -1
  281. package/dist/stair/definition.js +15 -7
  282. package/dist/stair/destination.d.ts +16 -0
  283. package/dist/stair/destination.d.ts.map +1 -0
  284. package/dist/stair/destination.js +24 -0
  285. package/dist/stair/documentation.d.ts +8 -0
  286. package/dist/stair/documentation.d.ts.map +1 -0
  287. package/dist/stair/documentation.js +245 -0
  288. package/dist/stair/floor-stack.d.ts.map +1 -1
  289. package/dist/stair/floor-stack.js +38 -0
  290. package/dist/stair/floorplan-affordances.d.ts.map +1 -1
  291. package/dist/stair/floorplan-affordances.js +28 -9
  292. package/dist/stair/floorplan-move.d.ts +3 -5
  293. package/dist/stair/floorplan-move.d.ts.map +1 -1
  294. package/dist/stair/floorplan-move.js +14 -16
  295. package/dist/stair/floorplan.d.ts.map +1 -1
  296. package/dist/stair/floorplan.js +27 -14
  297. package/dist/stair/panel.d.ts.map +1 -1
  298. package/dist/stair/panel.js +35 -2
  299. package/dist/stair/renderer.d.ts.map +1 -1
  300. package/dist/stair/renderer.js +4 -3
  301. package/dist/structural-grid/coordination.d.ts +16 -0
  302. package/dist/structural-grid/coordination.d.ts.map +1 -0
  303. package/dist/structural-grid/coordination.js +105 -0
  304. package/dist/structural-grid/definition.d.ts +4 -0
  305. package/dist/structural-grid/definition.d.ts.map +1 -0
  306. package/dist/structural-grid/definition.js +51 -0
  307. package/dist/structural-grid/floorplan-tool.d.ts +16 -0
  308. package/dist/structural-grid/floorplan-tool.d.ts.map +1 -0
  309. package/dist/structural-grid/floorplan-tool.js +183 -0
  310. package/dist/structural-grid/floorplan.d.ts +3 -0
  311. package/dist/structural-grid/floorplan.d.ts.map +1 -0
  312. package/dist/structural-grid/floorplan.js +61 -0
  313. package/dist/structural-grid/index.d.ts +2 -0
  314. package/dist/structural-grid/index.d.ts.map +1 -0
  315. package/dist/structural-grid/index.js +1 -0
  316. package/dist/structural-grid/schema.d.ts +2 -0
  317. package/dist/structural-grid/schema.d.ts.map +1 -0
  318. package/dist/structural-grid/schema.js +1 -0
  319. package/dist/wall/construction-dimensions.d.ts +32 -0
  320. package/dist/wall/construction-dimensions.d.ts.map +1 -0
  321. package/dist/wall/construction-dimensions.js +1031 -0
  322. package/dist/wall/contextual-dimensions.d.ts +13 -0
  323. package/dist/wall/contextual-dimensions.d.ts.map +1 -0
  324. package/dist/wall/contextual-dimensions.js +221 -0
  325. package/dist/wall/definition.d.ts.map +1 -1
  326. package/dist/wall/definition.js +20 -1
  327. package/dist/wall/floorplan-affordances.d.ts.map +1 -1
  328. package/dist/wall/floorplan-affordances.js +23 -8
  329. package/dist/wall/floorplan-overrides.d.ts +9 -6
  330. package/dist/wall/floorplan-overrides.d.ts.map +1 -1
  331. package/dist/wall/floorplan-overrides.js +20 -11
  332. package/dist/wall/floorplan.d.ts +12 -3
  333. package/dist/wall/floorplan.d.ts.map +1 -1
  334. package/dist/wall/floorplan.js +121 -79
  335. package/dist/wall/measurement.d.ts.map +1 -1
  336. package/dist/wall/measurement.js +17 -4
  337. package/dist/wall/move-endpoint-tool.d.ts.map +1 -1
  338. package/dist/wall/move-endpoint-tool.js +74 -27
  339. package/dist/wall/move-shared.d.ts.map +1 -1
  340. package/dist/wall/move-shared.js +3 -2
  341. package/dist/wall/move-tool.d.ts.map +1 -1
  342. package/dist/wall/move-tool.js +15 -3
  343. package/dist/wall/paint.d.ts.map +1 -1
  344. package/dist/wall/paint.js +10 -6
  345. package/dist/wall/panel.d.ts.map +1 -1
  346. package/dist/wall/panel.js +32 -5
  347. package/dist/wall/parametrics.d.ts.map +1 -1
  348. package/dist/wall/parametrics.js +2 -0
  349. package/dist/wall/preview.d.ts +8 -0
  350. package/dist/wall/preview.d.ts.map +1 -0
  351. package/dist/wall/preview.js +39 -0
  352. package/dist/wall/quick-measurement.d.ts.map +1 -1
  353. package/dist/wall/quick-measurement.js +3 -2
  354. package/dist/wall/renderer.d.ts.map +1 -1
  355. package/dist/wall/renderer.js +7 -5
  356. package/dist/wall/system.d.ts +1 -1
  357. package/dist/wall/system.d.ts.map +1 -1
  358. package/dist/wall/system.js +37 -2
  359. package/dist/wall/tool.d.ts.map +1 -1
  360. package/dist/wall/tool.js +148 -96
  361. package/dist/wall/treatment-level-data.d.ts +14 -0
  362. package/dist/wall/treatment-level-data.d.ts.map +1 -0
  363. package/dist/wall/treatment-level-data.js +31 -0
  364. package/dist/wall/treatments.d.ts +8 -2
  365. package/dist/wall/treatments.d.ts.map +1 -1
  366. package/dist/wall/treatments.js +58 -15
  367. package/dist/window/contextual-dimensions.d.ts +3 -0
  368. package/dist/window/contextual-dimensions.d.ts.map +1 -0
  369. package/dist/window/contextual-dimensions.js +7 -0
  370. package/dist/window/definition.d.ts.map +1 -1
  371. package/dist/window/definition.js +15 -10
  372. package/dist/window/floorplan-affordances.d.ts +2 -5
  373. package/dist/window/floorplan-affordances.d.ts.map +1 -1
  374. package/dist/window/floorplan-affordances.js +9 -15
  375. package/dist/window/floorplan-move.d.ts.map +1 -1
  376. package/dist/window/floorplan-move.js +1 -1
  377. package/dist/window/floorplan.d.ts.map +1 -1
  378. package/dist/window/floorplan.js +7 -1
  379. package/dist/window/move-tool.d.ts.map +1 -1
  380. package/dist/window/move-tool.js +3 -3
  381. package/dist/window/panel.d.ts.map +1 -1
  382. package/dist/window/panel.js +4 -1
  383. package/dist/window/schema.d.ts +1 -1
  384. package/dist/window/schema.d.ts.map +1 -1
  385. package/dist/window/schema.js +1 -1
  386. package/dist/window/tool.d.ts.map +1 -1
  387. package/dist/window/tool.js +64 -7
  388. package/dist/window/window-math.d.ts +7 -3
  389. package/dist/window/window-math.d.ts.map +1 -1
  390. package/dist/window/window-math.js +8 -3
  391. package/dist/zone/contextual-dimensions.d.ts +3 -0
  392. package/dist/zone/contextual-dimensions.d.ts.map +1 -0
  393. package/dist/zone/contextual-dimensions.js +43 -0
  394. package/dist/zone/definition.d.ts.map +1 -1
  395. package/dist/zone/definition.js +9 -1
  396. package/dist/zone/floorplan-affordances.d.ts +36 -0
  397. package/dist/zone/floorplan-affordances.d.ts.map +1 -1
  398. package/dist/zone/floorplan.d.ts.map +1 -1
  399. package/dist/zone/floorplan.js +59 -3
  400. package/dist/zone/quantities-panel.d.ts.map +1 -1
  401. package/dist/zone/quantities-panel.js +48 -4
  402. package/dist/zone/room-clear-dimensions.d.ts +3 -0
  403. package/dist/zone/room-clear-dimensions.d.ts.map +1 -0
  404. package/dist/zone/room-clear-dimensions.js +406 -0
  405. package/dist/zone/room-documentation.d.ts +11 -0
  406. package/dist/zone/room-documentation.d.ts.map +1 -0
  407. package/dist/zone/room-documentation.js +97 -0
  408. package/package.json +7 -7
@@ -0,0 +1,1031 @@
1
+ import { getWallArcData, getWallChordFrame, getWallMidpointHandlePoint, getWallThickness, isCurvedWall, } from '@pascal-app/core';
2
+ import { getColumnFloorplanFootprint } from '../column/floorplan';
3
+ import { DEFAULT_CONSTRUCTION_DIMENSION_STANDARD, } from '../shared/construction-dimension-standards';
4
+ import { formatConstructionLength, } from '../shared/construction-length';
5
+ import { buildDimensionStringGeometry } from '../shared/dimension-string';
6
+ import { resolveOpeningDimensionDocumentation } from '../shared/opening-documentation';
7
+ export { formatConstructionLength } from '../shared/construction-length';
8
+ const MIN_SEGMENT_LENGTH = 0.02;
9
+ const FACADE_LINE_TOLERANCE = 0.03;
10
+ const FACADE_DIRECTION_TOLERANCE = 0.001;
11
+ const COLUMN_ROW_TOLERANCE = 0.05;
12
+ const EXTERIOR_CORNER_DATUM_POLICY = 'structural-face';
13
+ const TIER_ORDER = [
14
+ 'opening-widths',
15
+ 'openings',
16
+ 'partitions',
17
+ 'structure',
18
+ 'jogs',
19
+ 'overall',
20
+ 'structural-overall',
21
+ ];
22
+ export function buildLevelWallConstructionDimensionPlan(walls, nodes, standard = DEFAULT_CONSTRUCTION_DIMENSION_STANDARD) {
23
+ const dimensionsByWallId = new Map();
24
+ const wallNetworkById = buildWallNetworkIndex(walls);
25
+ const interiorWallIds = new Set(walls.flatMap((wall) => {
26
+ if (isCurvedWall(wall))
27
+ return [];
28
+ const network = wallNetworkById.get(wall.id) ?? [wall];
29
+ return shouldDimensionInteriorWall(wall, walls, network) ? [wall.id] : [];
30
+ }));
31
+ const exteriorMembers = walls.flatMap((wall) => {
32
+ if (isCurvedWall(wall) || interiorWallIds.has(wall.id))
33
+ return [];
34
+ const normal = exteriorNormal(wall);
35
+ if (!normal)
36
+ return [];
37
+ const network = wallNetworkById.get(wall.id) ?? [wall];
38
+ if (isFacadeOccluded(wall, normal, network))
39
+ return [];
40
+ return [{ wall, normal, tangent: [cleanZero(normal[1]), cleanZero(-normal[0])] }];
41
+ });
42
+ const columns = Object.values(nodes).filter((node) => node.type === 'column' && node.visible !== false);
43
+ const components = splitConnectedFacadeComponents(exteriorMembers);
44
+ for (const component of components) {
45
+ const directionGroups = groupFacadeMembersByDirection(component);
46
+ const componentColumns = columns.filter((column) => column.parentId === component[0]?.wall.parentId &&
47
+ nearestFacadeComponent(column, components) === component);
48
+ for (const directionMembers of directionGroups.values()) {
49
+ const representative = [...directionMembers].sort((left, right) => String(left.wall.id).localeCompare(String(right.wall.id)))[0];
50
+ if (!representative)
51
+ continue;
52
+ const { normal, tangent } = representative;
53
+ const wallProjections = directionMembers.flatMap(({ wall }) => [
54
+ dot(wall.start, tangent),
55
+ dot(wall.end, tangent),
56
+ ]);
57
+ const [extentStart, extentEnd] = facadeStructuralExtents(directionMembers, walls, tangent);
58
+ if (extentEnd - extentStart < MIN_SEGMENT_LENGTH)
59
+ continue;
60
+ const outerFaceCoordinate = Math.max(...directionMembers.map(({ wall }) => exteriorFaceCoordinate(wall, normal, EXTERIOR_CORNER_DATUM_POLICY)), ...curvedFacadeOuterFaceCoordinates(walls, directionMembers, normal, EXTERIOR_CORNER_DATUM_POLICY));
61
+ const pending = [];
62
+ const lineGroups = groupFacadeMembersByLine(directionMembers, normal);
63
+ let facadeRunCount = 0;
64
+ for (const groupedMembers of lineGroups.values()) {
65
+ const runs = splitFacadeRuns(groupedMembers);
66
+ facadeRunCount += runs.length;
67
+ for (const run of runs) {
68
+ appendFacadeRunDimensions(pending, run, walls, nodes, interiorWallIds, normal, tangent, standard);
69
+ }
70
+ }
71
+ if (lineGroups.size > 1 || facadeRunCount > lineGroups.size) {
72
+ const jogProjections = uniqueSorted(wallProjections);
73
+ appendProjectedChain(pending, jogProjections, 'jogs', (projection) => exteriorOriginAtProjection(directionMembers, projection, tangent, normal, EXTERIOR_CORNER_DATUM_POLICY));
74
+ }
75
+ const exteriorColumns = componentColumns.filter((column) => dot(columnPlanPoint(column), normal) + columnNormalHalfExtent(column, normal) >=
76
+ outerFaceCoordinate - FACADE_LINE_TOLERANCE);
77
+ const structureRow = outermostColumnRow(exteriorColumns, component, normal, tangent);
78
+ if (structureRow.length >= 2) {
79
+ const projections = uniqueSorted(structureRow.map((column) => dot(columnPlanPoint(column), tangent)));
80
+ appendProjectedChain(pending, projections, 'structure', (projection) => columnOriginAtProjection(structureRow, projection, tangent));
81
+ }
82
+ pending.push({
83
+ tier: 'overall',
84
+ start: exteriorOriginAtProjection(directionMembers, extentStart, tangent, normal, EXTERIOR_CORNER_DATUM_POLICY),
85
+ end: exteriorOriginAtProjection(directionMembers, extentEnd, tangent, normal, EXTERIOR_CORNER_DATUM_POLICY),
86
+ startProjection: extentStart,
87
+ endProjection: extentEnd,
88
+ });
89
+ const structuralProjections = structureRow.map((column) => dot(columnPlanPoint(column), tangent));
90
+ const structuralStart = Math.min(extentStart, ...structuralProjections);
91
+ const structuralEnd = Math.max(extentEnd, ...structuralProjections);
92
+ if (structureRow.length >= 2 &&
93
+ (structuralStart < extentStart - MIN_SEGMENT_LENGTH ||
94
+ structuralEnd > extentEnd + MIN_SEGMENT_LENGTH)) {
95
+ pending.push({
96
+ tier: 'structural-overall',
97
+ start: structuralStart < extentStart - MIN_SEGMENT_LENGTH
98
+ ? columnOriginAtProjection(structureRow, structuralStart, tangent)
99
+ : exteriorOriginAtProjection(directionMembers, extentStart, tangent, normal, EXTERIOR_CORNER_DATUM_POLICY),
100
+ end: structuralEnd > extentEnd + MIN_SEGMENT_LENGTH
101
+ ? columnOriginAtProjection(structureRow, structuralEnd, tangent)
102
+ : exteriorOriginAtProjection(directionMembers, extentEnd, tangent, normal, EXTERIOR_CORNER_DATUM_POLICY),
103
+ startProjection: structuralStart,
104
+ endProjection: structuralEnd,
105
+ });
106
+ }
107
+ const structuralFaceCoordinate = Math.max(outerFaceCoordinate, ...structureRow.map((column) => dot(columnPlanPoint(column), normal) + columnNormalHalfExtent(column, normal)));
108
+ dimensionsByWallId.set(representative.wall.id, finalizeDimensionTiers(pending, tangent, normal, structuralFaceCoordinate, standard));
109
+ }
110
+ }
111
+ for (const wall of walls) {
112
+ if (isCurvedWall(wall))
113
+ continue;
114
+ const openings = hostedOpeningsForWall(wall, nodes);
115
+ const roomSideNormal = interiorWallIds.has(wall.id)
116
+ ? undefined
117
+ : enclosedRoomSideNormal(wall, walls);
118
+ if (!interiorWallIds.has(wall.id) && (openings.length === 0 || roomSideNormal === null)) {
119
+ continue;
120
+ }
121
+ const planned = buildInteriorWallDimensions(wall, walls, openings, standard, roomSideNormal);
122
+ if (planned.length === 0)
123
+ continue;
124
+ dimensionsByWallId.set(wall.id, [...(dimensionsByWallId.get(wall.id) ?? []), ...planned]);
125
+ }
126
+ return dimensionsByWallId;
127
+ }
128
+ function buildInteriorWallDimensions(wall, walls, openings, standard, normalOverride) {
129
+ const dx = wall.end[0] - wall.start[0];
130
+ const dz = wall.end[1] - wall.start[1];
131
+ const wallLength = Math.hypot(dx, dz);
132
+ if (wallLength < MIN_SEGMENT_LENGTH)
133
+ return [];
134
+ const tangent = [dx / wallLength, dz / wallLength];
135
+ const [spanStart, spanEnd] = interiorWallClearSpan(wall, walls, tangent, wallLength, standard.datumPolicy);
136
+ if (spanEnd - spanStart < MIN_SEGMENT_LENGTH)
137
+ return [];
138
+ const normal = normalOverride ?? resolveInteriorDimensionNormal(wall, walls, tangent);
139
+ const datumDistance = wallDatumDistanceToward(wall, standard.datumPolicy, normal);
140
+ const pointAt = (along) => [
141
+ wall.start[0] + tangent[0] * along + normal[0] * datumDistance,
142
+ wall.start[1] + tangent[1] * along + normal[1] * datumDistance,
143
+ ];
144
+ const openingSpans = openings.flatMap((opening) => {
145
+ const halfWidth = Math.max(0, opening.width) / 2;
146
+ const start = clamp(opening.position[0] - halfWidth, spanStart, spanEnd);
147
+ const end = clamp(opening.position[0] + halfWidth, spanStart, spanEnd);
148
+ return end - start >= MIN_SEGMENT_LENGTH ? [[start, end]] : [];
149
+ });
150
+ const planned = [];
151
+ if (openingSpans.length > 0) {
152
+ const breakpoints = uniqueSorted([spanStart, spanEnd, ...openingSpans.flat()]);
153
+ for (let index = 0; index < breakpoints.length - 1; index++) {
154
+ const start = breakpoints[index];
155
+ const end = breakpoints[index + 1];
156
+ if (start === undefined || end === undefined || end - start < MIN_SEGMENT_LENGTH)
157
+ continue;
158
+ planned.push({
159
+ tier: 'interior',
160
+ start: pointAt(start),
161
+ end: pointAt(end),
162
+ offsetNormal: normal,
163
+ offsetDistance: standard.openingChainOffset,
164
+ });
165
+ }
166
+ }
167
+ planned.push({
168
+ tier: 'interior-overall',
169
+ start: pointAt(spanStart),
170
+ end: pointAt(spanEnd),
171
+ offsetNormal: normal,
172
+ offsetDistance: openingSpans.length > 0 ? standard.wallSpanOffset : standard.openingChainOffset,
173
+ });
174
+ return planned;
175
+ }
176
+ function interiorWallClearSpan(wall, walls, tangent, wallLength, datumPolicy) {
177
+ const insetAt = (endpoint, inward) => {
178
+ let inset = 0;
179
+ for (const candidate of walls) {
180
+ if (candidate.id === wall.id ||
181
+ isCurvedWall(candidate) ||
182
+ pointSegmentDistance(endpoint, candidate.start, candidate.end) > FACADE_LINE_TOLERANCE) {
183
+ continue;
184
+ }
185
+ const candidateDirection = subtract(candidate.end, candidate.start);
186
+ const candidateLength = Math.hypot(candidateDirection[0], candidateDirection[1]);
187
+ if (candidateLength < MIN_SEGMENT_LENGTH)
188
+ continue;
189
+ const candidateNormal = [
190
+ -candidateDirection[1] / candidateLength,
191
+ candidateDirection[0] / candidateLength,
192
+ ];
193
+ const crossing = Math.abs(dot(inward, candidateNormal));
194
+ if (crossing < FACADE_DIRECTION_TOLERANCE)
195
+ continue;
196
+ inset = Math.max(inset, maximumWallDatumDistance(candidate, datumPolicy) / crossing);
197
+ }
198
+ return inset;
199
+ };
200
+ const spanStart = clamp(insetAt(wall.start, tangent), 0, wallLength);
201
+ const spanEnd = clamp(wallLength - insetAt(wall.end, negate(tangent)), spanStart, wallLength);
202
+ return [spanStart, spanEnd];
203
+ }
204
+ export function renderPlannedConstructionDimensions(planned, unit, stroke, profile = 'editor', standard = DEFAULT_CONSTRUCTION_DIMENSION_STANDARD) {
205
+ return groupContiguousPlannedDimensions(planned).map((entries) => {
206
+ const first = entries[0];
207
+ return buildDimensionStringGeometry({
208
+ segments: entries.map((entry) => ({
209
+ witnessStart: entry.start,
210
+ witnessEnd: entry.end,
211
+ dimensionStart: entry.dimensionStart,
212
+ dimensionEnd: entry.dimensionEnd,
213
+ text: constructionDimensionText(entry.dimensionStart ?? entry.start, entry.dimensionEnd ?? entry.end, unit, profile, standard, entry.textPrefix),
214
+ })),
215
+ offsetNormal: first.offsetNormal,
216
+ offsetDistance: first.offsetDistance,
217
+ extensionStartGap: standard.extensionStartGap,
218
+ extensionOvershoot: standard.extensionOvershoot,
219
+ terminator: standard.terminator,
220
+ textPosition: standard.textPosition,
221
+ stroke,
222
+ });
223
+ });
224
+ }
225
+ function groupContiguousPlannedDimensions(planned) {
226
+ const groups = [];
227
+ for (const entry of planned) {
228
+ const group = groups.at(-1);
229
+ const previous = group?.at(-1);
230
+ if (group && previous && plannedDimensionsAreContiguous(previous, entry))
231
+ group.push(entry);
232
+ else
233
+ groups.push([entry]);
234
+ }
235
+ return groups;
236
+ }
237
+ function plannedDimensionsAreContiguous(previous, next) {
238
+ return (previous.tier === next.tier &&
239
+ distance(previous.offsetNormal, next.offsetNormal) <= 1e-6 &&
240
+ distance(previous.end, next.start) <= 1e-6 &&
241
+ distance(plannedDimensionEnd(previous), plannedDimensionStart(next)) <= 1e-6);
242
+ }
243
+ function plannedDimensionStart(entry) {
244
+ return entry.dimensionStart ?? addScaled(entry.start, entry.offsetNormal, entry.offsetDistance);
245
+ }
246
+ function plannedDimensionEnd(entry) {
247
+ return entry.dimensionEnd ?? addScaled(entry.end, entry.offsetNormal, entry.offsetDistance);
248
+ }
249
+ export function buildCurvedWallConstructionDimensions(wall, { unit, stroke, profile = 'editor', standard = DEFAULT_CONSTRUCTION_DIMENSION_STANDARD, siblings = [], }) {
250
+ const chord = getWallChordFrame(wall);
251
+ const midpoint = getWallMidpointHandlePoint(wall);
252
+ const curveVector = [midpoint.x - chord.midpoint.x, midpoint.y - chord.midpoint.y];
253
+ const curveDepth = Math.hypot(curveVector[0], curveVector[1]);
254
+ if (chord.length < MIN_SEGMENT_LENGTH || curveDepth < MIN_SEGMENT_LENGTH)
255
+ return [];
256
+ const curveDirection = [curveVector[0] / curveDepth, curveVector[1] / curveDepth];
257
+ const tangent = [chord.tangent.x, chord.tangent.y];
258
+ const datumDistance = wallDatumDistanceToward(wall, standard.datumPolicy, curveDirection);
259
+ const curveWitness = addScaled([midpoint.x, midpoint.y], curveDirection, datumDistance);
260
+ const chordWitness = addScaled(wall.end, curveDirection, datumDistance);
261
+ const connectedWalls = connectedWallComponent(wall, [wall, ...siblings]);
262
+ const forwardExtent = Math.max(...connectedWalls.flatMap((candidate) => [
263
+ dot(candidate.start, tangent),
264
+ dot(candidate.end, tangent),
265
+ ]));
266
+ const baselineProjection = forwardExtent + standard.firstGeneralTierOffset;
267
+ const dimensionStart = pointFromCoordinates(baselineProjection, dot(curveWitness, curveDirection), tangent, curveDirection);
268
+ const dimensionEnd = pointFromCoordinates(baselineProjection, dot(chordWitness, curveDirection), tangent, curveDirection);
269
+ return [
270
+ dimension(curveWitness, chordWitness, tangent, Math.max(0, dot(subtract(dimensionStart, curveWitness), tangent)), unit, stroke, dimensionStart, dimensionEnd, profile, standard),
271
+ ];
272
+ }
273
+ export function buildWallConstructionDimensions(wall, ctx, { unit, stroke, profile = 'editor', standard = DEFAULT_CONSTRUCTION_DIMENSION_STANDARD, }) {
274
+ if (isCurvedWall(wall))
275
+ return [];
276
+ const dx = wall.end[0] - wall.start[0];
277
+ const dz = wall.end[1] - wall.start[1];
278
+ const wallLength = Math.hypot(dx, dz);
279
+ if (wallLength < MIN_SEGMENT_LENGTH)
280
+ return [];
281
+ const sideIsClassified = wall.frontSide !== 'unknown' || wall.backSide !== 'unknown';
282
+ const isExterior = wall.frontSide === 'exterior' || wall.backSide === 'exterior';
283
+ if (sideIsClassified && !isExterior)
284
+ return [];
285
+ const dirX = dx / wallLength;
286
+ const dirZ = dz / wallLength;
287
+ const outwardNormal = resolveOutwardNormal(wall, ctx, dirX, dirZ);
288
+ const datumDistance = wallDatumDistanceToward(wall, standard.datumPolicy, outwardNormal);
289
+ const pointAt = (along) => [
290
+ wall.start[0] + dirX * along + outwardNormal[0] * datumDistance,
291
+ wall.start[1] + dirZ * along + outwardNormal[1] * datumDistance,
292
+ ];
293
+ const openings = ctx.children
294
+ .filter((child) => child.type === 'door' || child.type === 'window')
295
+ .flatMap((opening) => {
296
+ const halfWidth = Math.max(0, opening.width) / 2;
297
+ const start = clamp(opening.position[0] - halfWidth, 0, wallLength);
298
+ const end = clamp(opening.position[0] + halfWidth, 0, wallLength);
299
+ return end - start >= MIN_SEGMENT_LENGTH ? [start, end] : [];
300
+ });
301
+ const dimensions = [];
302
+ if (openings.length > 0) {
303
+ const breakpoints = uniqueSorted([0, wallLength, ...openings.flat()]);
304
+ for (let index = 0; index < breakpoints.length - 1; index++) {
305
+ const start = breakpoints[index];
306
+ const end = breakpoints[index + 1];
307
+ if (end - start < MIN_SEGMENT_LENGTH)
308
+ continue;
309
+ dimensions.push(dimension(pointAt(start), pointAt(end), outwardNormal, standard.openingChainOffset, unit, stroke, undefined, undefined, profile, standard));
310
+ }
311
+ }
312
+ dimensions.push(dimension(pointAt(0), pointAt(wallLength), outwardNormal, openings.length > 0 ? standard.wallSpanOffset : standard.openingChainOffset, unit, stroke, undefined, undefined, profile, standard));
313
+ return dimensions;
314
+ }
315
+ function dimension(start, end, offsetNormal, offsetDistance, unit, stroke, dimensionStart, dimensionEnd, profile = 'editor', standard = DEFAULT_CONSTRUCTION_DIMENSION_STANDARD, textPrefix) {
316
+ const measurementStart = dimensionStart ?? start;
317
+ const measurementEnd = dimensionEnd ?? end;
318
+ return buildDimensionStringGeometry({
319
+ segments: [
320
+ {
321
+ witnessStart: start,
322
+ witnessEnd: end,
323
+ dimensionStart,
324
+ dimensionEnd,
325
+ text: constructionDimensionText(measurementStart, measurementEnd, unit, profile, standard, textPrefix),
326
+ },
327
+ ],
328
+ offsetNormal,
329
+ offsetDistance,
330
+ extensionStartGap: standard.extensionStartGap,
331
+ extensionOvershoot: standard.extensionOvershoot,
332
+ terminator: standard.terminator,
333
+ textPosition: standard.textPosition,
334
+ stroke,
335
+ });
336
+ }
337
+ function constructionDimensionText(start, end, unit, profile, standard, prefix) {
338
+ const lengthText = formatConstructionLength(Math.hypot(end[0] - start[0], end[1] - start[1]), unit, profile, {
339
+ imperialPrecision: standard.imperialPrecision,
340
+ metricNotation: standard.metricNotation,
341
+ });
342
+ return prefix ? `${prefix} ${lengthText}` : lengthText;
343
+ }
344
+ function resolveOutwardNormal(wall, ctx, dirX, dirZ) {
345
+ const front = [cleanZero(-dirZ), cleanZero(dirX)];
346
+ if (wall.frontSide === 'exterior' && wall.backSide !== 'exterior')
347
+ return front;
348
+ if (wall.backSide === 'exterior' && wall.frontSide !== 'exterior')
349
+ return negate(front);
350
+ const walls = [
351
+ wall,
352
+ ...ctx.siblings.filter((sibling) => sibling.type === 'wall'),
353
+ ];
354
+ let sumX = 0;
355
+ let sumZ = 0;
356
+ for (const candidate of walls) {
357
+ sumX += candidate.start[0] + candidate.end[0];
358
+ sumZ += candidate.start[1] + candidate.end[1];
359
+ }
360
+ const centroidX = sumX / (walls.length * 2);
361
+ const centroidZ = sumZ / (walls.length * 2);
362
+ const midX = (wall.start[0] + wall.end[0]) / 2;
363
+ const midZ = (wall.start[1] + wall.end[1]) / 2;
364
+ return (midX - centroidX) * front[0] + (midZ - centroidZ) * front[1] >= 0 ? front : negate(front);
365
+ }
366
+ function exteriorNormal(wall) {
367
+ const dx = wall.end[0] - wall.start[0];
368
+ const dz = wall.end[1] - wall.start[1];
369
+ const length = Math.hypot(dx, dz);
370
+ if (length < MIN_SEGMENT_LENGTH)
371
+ return null;
372
+ const front = [cleanZero(-dz / length), cleanZero(dx / length)];
373
+ if (wall.frontSide === 'exterior' && wall.backSide !== 'exterior')
374
+ return front;
375
+ if (wall.backSide === 'exterior' && wall.frontSide !== 'exterior')
376
+ return negate(front);
377
+ return null;
378
+ }
379
+ function isClassifiedInteriorWall(wall) {
380
+ return wall.frontSide === 'interior' && wall.backSide === 'interior';
381
+ }
382
+ function hostedOpeningsForWall(wall, nodes) {
383
+ return Object.values(nodes).filter((node) => (node.type === 'door' || node.type === 'window') &&
384
+ node.visible !== false &&
385
+ (node.wallId ?? node.parentId) === wall.id);
386
+ }
387
+ function shouldDimensionInteriorWall(wall, walls, network) {
388
+ if (isClassifiedInteriorWall(wall))
389
+ return true;
390
+ const dx = wall.end[0] - wall.start[0];
391
+ const dz = wall.end[1] - wall.start[1];
392
+ const length = Math.hypot(dx, dz);
393
+ if (length < MIN_SEGMENT_LENGTH)
394
+ return false;
395
+ const tangent = [dx / length, dz / length];
396
+ const { frontClearance, backClearance } = interiorDimensionClearances(wall, walls, tangent);
397
+ if (frontClearance === null || backClearance === null)
398
+ return false;
399
+ const claimedExteriorNormal = exteriorNormal(wall);
400
+ return claimedExteriorNormal === null || isFacadeOccluded(wall, claimedExteriorNormal, network);
401
+ }
402
+ function enclosedRoomSideNormal(wall, walls) {
403
+ const outward = exteriorNormal(wall);
404
+ if (!outward)
405
+ return null;
406
+ const dx = wall.end[0] - wall.start[0];
407
+ const dz = wall.end[1] - wall.start[1];
408
+ const length = Math.hypot(dx, dz);
409
+ if (length < MIN_SEGMENT_LENGTH)
410
+ return null;
411
+ const tangent = [dx / length, dz / length];
412
+ const front = [cleanZero(-tangent[1]), cleanZero(tangent[0])];
413
+ const { frontClearance, backClearance } = interiorDimensionClearances(wall, walls, tangent);
414
+ const inward = negate(outward);
415
+ const inwardClearance = dot(inward, front) >= 0 ? frontClearance : backClearance;
416
+ return inwardClearance === null ? null : inward;
417
+ }
418
+ function resolveInteriorDimensionNormal(wall, walls, tangent) {
419
+ const front = [cleanZero(-tangent[1]), cleanZero(tangent[0])];
420
+ const back = negate(front);
421
+ const { frontClearance, backClearance } = interiorDimensionClearances(wall, walls, tangent);
422
+ if (frontClearance !== null && backClearance === null)
423
+ return front;
424
+ if (backClearance !== null && frontClearance === null)
425
+ return back;
426
+ if (frontClearance !== null && backClearance !== null) {
427
+ if (Math.abs(frontClearance - backClearance) > FACADE_LINE_TOLERANCE) {
428
+ return frontClearance > backClearance ? front : back;
429
+ }
430
+ }
431
+ const midpoint = [
432
+ (wall.start[0] + wall.end[0]) / 2,
433
+ (wall.start[1] + wall.end[1]) / 2,
434
+ ];
435
+ const centroid = wallNetworkCentroid(walls);
436
+ return dot(subtract(centroid, midpoint), front) >= 0 ? front : back;
437
+ }
438
+ function interiorDimensionClearances(wall, walls, tangent) {
439
+ const front = [cleanZero(-tangent[1]), cleanZero(tangent[0])];
440
+ const back = negate(front);
441
+ const midpoint = [
442
+ (wall.start[0] + wall.end[0]) / 2,
443
+ (wall.start[1] + wall.end[1]) / 2,
444
+ ];
445
+ const clearance = (normal) => {
446
+ let nearest = Number.POSITIVE_INFINITY;
447
+ for (const candidate of walls) {
448
+ if (candidate.id === wall.id)
449
+ continue;
450
+ const hit = isCurvedWall(candidate)
451
+ ? rayArcDistance(midpoint, normal, candidate)
452
+ : raySegmentDistance(midpoint, normal, candidate.start, candidate.end);
453
+ if (hit !== null)
454
+ nearest = Math.min(nearest, hit);
455
+ }
456
+ return Number.isFinite(nearest) ? nearest : null;
457
+ };
458
+ const frontClearance = clearance(front);
459
+ const backClearance = clearance(back);
460
+ return { frontClearance, backClearance };
461
+ }
462
+ function wallNetworkCentroid(walls) {
463
+ if (walls.length === 0)
464
+ return [0, 0];
465
+ let sumX = 0;
466
+ let sumY = 0;
467
+ for (const wall of walls) {
468
+ sumX += wall.start[0] + wall.end[0];
469
+ sumY += wall.start[1] + wall.end[1];
470
+ }
471
+ return [sumX / (walls.length * 2), sumY / (walls.length * 2)];
472
+ }
473
+ function buildWallNetworkIndex(walls) {
474
+ const straightWalls = walls.filter((wall) => !isCurvedWall(wall));
475
+ const unvisited = new Set(straightWalls);
476
+ const networkById = new Map();
477
+ while (unvisited.size > 0) {
478
+ const seed = unvisited.values().next().value;
479
+ if (!seed)
480
+ break;
481
+ unvisited.delete(seed);
482
+ const network = [seed];
483
+ const queue = [seed];
484
+ while (queue.length > 0) {
485
+ const current = queue.shift();
486
+ if (!current)
487
+ continue;
488
+ for (const candidate of unvisited) {
489
+ if (!wallSegmentsTouch(current, candidate))
490
+ continue;
491
+ unvisited.delete(candidate);
492
+ network.push(candidate);
493
+ queue.push(candidate);
494
+ }
495
+ }
496
+ for (const wall of network)
497
+ networkById.set(wall.id, network);
498
+ }
499
+ return networkById;
500
+ }
501
+ function wallSegmentsTouch(left, right) {
502
+ return (pointSegmentDistance(left.start, right.start, right.end) <= FACADE_LINE_TOLERANCE ||
503
+ pointSegmentDistance(left.end, right.start, right.end) <= FACADE_LINE_TOLERANCE ||
504
+ pointSegmentDistance(right.start, left.start, left.end) <= FACADE_LINE_TOLERANCE ||
505
+ pointSegmentDistance(right.end, left.start, left.end) <= FACADE_LINE_TOLERANCE ||
506
+ segmentIntersection(left.start, left.end, right.start, right.end) !== null);
507
+ }
508
+ function isFacadeOccluded(wall, outwardNormal, network) {
509
+ const halfThickness = (wall.thickness ?? 0.1) / 2;
510
+ const origin = addScaled([(wall.start[0] + wall.end[0]) / 2, (wall.start[1] + wall.end[1]) / 2], outwardNormal, halfThickness + FACADE_LINE_TOLERANCE);
511
+ return network.some((candidate) => candidate.id !== wall.id &&
512
+ raySegmentDistance(origin, outwardNormal, candidate.start, candidate.end) !== null);
513
+ }
514
+ function raySegmentDistance(rayOrigin, rayDirection, segmentStart, segmentEnd) {
515
+ const segmentDirection = subtract(segmentEnd, segmentStart);
516
+ const denominator = cross(rayDirection, segmentDirection);
517
+ if (Math.abs(denominator) < 1e-8)
518
+ return null;
519
+ const fromRay = subtract(segmentStart, rayOrigin);
520
+ const alongRay = cross(fromRay, segmentDirection) / denominator;
521
+ const alongSegment = cross(fromRay, rayDirection) / denominator;
522
+ if (alongRay <= FACADE_LINE_TOLERANCE || alongSegment < -1e-6 || alongSegment > 1 + 1e-6) {
523
+ return null;
524
+ }
525
+ return alongRay;
526
+ }
527
+ function rayArcDistance(rayOrigin, rayDirection, wall) {
528
+ const arc = getWallArcData(wall);
529
+ if (!arc)
530
+ return null;
531
+ const fromCenter = [rayOrigin[0] - arc.center.x, rayOrigin[1] - arc.center.y];
532
+ const directionLengthSquared = dot(rayDirection, rayDirection);
533
+ const linear = 2 * dot(fromCenter, rayDirection);
534
+ const constant = dot(fromCenter, fromCenter) - arc.radius * arc.radius;
535
+ const discriminant = linear * linear - 4 * directionLengthSquared * constant;
536
+ if (discriminant < 0 || directionLengthSquared < 1e-12)
537
+ return null;
538
+ const root = Math.sqrt(Math.max(0, discriminant));
539
+ const denominator = 2 * directionLengthSquared;
540
+ const hits = [(-linear - root) / denominator, (-linear + root) / denominator]
541
+ .filter((distance) => distance > FACADE_LINE_TOLERANCE)
542
+ .sort((left, right) => left - right);
543
+ for (const distance of hits) {
544
+ const point = [
545
+ rayOrigin[0] + rayDirection[0] * distance,
546
+ rayOrigin[1] + rayDirection[1] * distance,
547
+ ];
548
+ const angle = Math.atan2(point[1] - arc.center.y, point[0] - arc.center.x);
549
+ if (angleFallsOnArc(angle, arc.startAngle, arc.delta))
550
+ return distance;
551
+ }
552
+ return null;
553
+ }
554
+ function angleFallsOnArc(angle, startAngle, delta) {
555
+ const fullTurn = Math.PI * 2;
556
+ const positiveTurn = (value) => ((value % fullTurn) + fullTurn) % fullTurn;
557
+ const swept = delta >= 0 ? positiveTurn(angle - startAngle) : positiveTurn(startAngle - angle);
558
+ return swept <= Math.abs(delta) + 1e-8;
559
+ }
560
+ function splitConnectedFacadeComponents(members) {
561
+ const unvisited = new Set(members);
562
+ const components = [];
563
+ while (unvisited.size > 0) {
564
+ const seed = unvisited.values().next().value;
565
+ if (!seed)
566
+ break;
567
+ unvisited.delete(seed);
568
+ const component = [seed];
569
+ const queue = [seed];
570
+ while (queue.length > 0) {
571
+ const current = queue.shift();
572
+ if (!current)
573
+ continue;
574
+ for (const candidate of unvisited) {
575
+ if (!wallsTouch(current.wall, candidate.wall))
576
+ continue;
577
+ unvisited.delete(candidate);
578
+ component.push(candidate);
579
+ queue.push(candidate);
580
+ }
581
+ }
582
+ components.push(component);
583
+ }
584
+ return components;
585
+ }
586
+ function wallsTouch(left, right) {
587
+ return [left.start, left.end].some((leftPoint) => [right.start, right.end].some((rightPoint) => distance(leftPoint, rightPoint) <= FACADE_LINE_TOLERANCE));
588
+ }
589
+ function connectedWallComponent(wall, candidates) {
590
+ const unvisited = new Set(candidates.filter((candidate) => candidate.id !== wall.id && candidate.parentId === wall.parentId));
591
+ const component = [wall];
592
+ const queue = [wall];
593
+ while (queue.length > 0) {
594
+ const current = queue.shift();
595
+ if (!current)
596
+ continue;
597
+ for (const candidate of unvisited) {
598
+ if (!wallsTouch(current, candidate))
599
+ continue;
600
+ unvisited.delete(candidate);
601
+ component.push(candidate);
602
+ queue.push(candidate);
603
+ }
604
+ }
605
+ return component;
606
+ }
607
+ function groupFacadeMembersByDirection(members) {
608
+ const groups = new Map();
609
+ for (const member of members) {
610
+ const key = `${Math.round(member.normal[0] / FACADE_DIRECTION_TOLERANCE)},${Math.round(member.normal[1] / FACADE_DIRECTION_TOLERANCE)}`;
611
+ const group = groups.get(key);
612
+ if (group)
613
+ group.push(member);
614
+ else
615
+ groups.set(key, [member]);
616
+ }
617
+ return groups;
618
+ }
619
+ function groupFacadeMembersByLine(members, normal) {
620
+ const groups = new Map();
621
+ for (const member of members) {
622
+ const midpoint = [
623
+ (member.wall.start[0] + member.wall.end[0]) / 2,
624
+ (member.wall.start[1] + member.wall.end[1]) / 2,
625
+ ];
626
+ const key = Math.round(dot(midpoint, normal) / FACADE_LINE_TOLERANCE);
627
+ const group = groups.get(key);
628
+ if (group)
629
+ group.push(member);
630
+ else
631
+ groups.set(key, [member]);
632
+ }
633
+ return groups;
634
+ }
635
+ function appendFacadeRunDimensions(pending, members, walls, nodes, interiorWallIds, normal, tangent, standard) {
636
+ const [extentStart, extentEnd] = facadeStructuralExtents(members, walls, tangent);
637
+ if (extentEnd - extentStart < MIN_SEGMENT_LENGTH)
638
+ return;
639
+ const faceCoordinate = Math.max(...members.map(({ wall }) => exteriorFaceCoordinate(wall, normal, EXTERIOR_CORNER_DATUM_POLICY)));
640
+ const pointAt = (projection) => pointFromCoordinates(projection, faceCoordinate, tangent, normal);
641
+ const openingCenters = [];
642
+ const openingSpans = [];
643
+ for (const { wall } of members) {
644
+ const dx = wall.end[0] - wall.start[0];
645
+ const dz = wall.end[1] - wall.start[1];
646
+ const length = Math.hypot(dx, dz);
647
+ if (length < MIN_SEGMENT_LENGTH)
648
+ continue;
649
+ for (const opening of Object.values(nodes)) {
650
+ if (opening.type !== 'door' && opening.type !== 'window')
651
+ continue;
652
+ if (opening.visible === false)
653
+ continue;
654
+ if ((opening.wallId ?? opening.parentId) !== wall.id)
655
+ continue;
656
+ const along = clamp(opening.position[0], 0, length);
657
+ const center = [
658
+ wall.start[0] + (dx / length) * along,
659
+ wall.start[1] + (dz / length) * along,
660
+ ];
661
+ const documentation = resolveOpeningDimensionDocumentation(opening);
662
+ if (documentation.locationPolicy === 'centerline')
663
+ openingCenters.push(dot(center, tangent));
664
+ if (documentation.width === null)
665
+ continue;
666
+ const halfWidth = Math.max(0, documentation.width) / 2;
667
+ const startProjection = dot([
668
+ wall.start[0] + (dx / length) * clamp(along - halfWidth, 0, length),
669
+ wall.start[1] + (dz / length) * clamp(along - halfWidth, 0, length),
670
+ ], tangent);
671
+ const endProjection = dot([
672
+ wall.start[0] + (dx / length) * clamp(along + halfWidth, 0, length),
673
+ wall.start[1] + (dz / length) * clamp(along + halfWidth, 0, length),
674
+ ], tangent);
675
+ if (Math.abs(endProjection - startProjection) >= MIN_SEGMENT_LENGTH) {
676
+ openingSpans.push([
677
+ Math.min(startProjection, endProjection),
678
+ Math.max(startProjection, endProjection),
679
+ documentation.prefix,
680
+ ]);
681
+ }
682
+ }
683
+ }
684
+ for (const [startProjection, endProjection, textPrefix] of openingSpans.sort((left, right) => left[0] - right[0])) {
685
+ pending.push({
686
+ tier: 'opening-widths',
687
+ start: pointAt(startProjection),
688
+ end: pointAt(endProjection),
689
+ startProjection,
690
+ endProjection,
691
+ textPrefix,
692
+ });
693
+ }
694
+ appendReferenceTier(pending, openingCenters, extentStart, extentEnd, pointAt, 'openings');
695
+ const memberIds = new Set(members.map(({ wall }) => wall.id));
696
+ const partitionReferences = [];
697
+ for (const candidate of walls) {
698
+ if (memberIds.has(candidate.id) ||
699
+ isCurvedWall(candidate) ||
700
+ !interiorWallIds.has(candidate.id)) {
701
+ continue;
702
+ }
703
+ const intersections = members.flatMap(({ wall }) => facadePartitionFaceIntersections(wall, candidate, normal, standard.datumPolicy));
704
+ const references = intersections.map((point) => dot(point, tangent));
705
+ const canonicalStructuralFace = selectCanonicalWallFaceIntersection(intersections, candidate);
706
+ const selectedReferences = standard.intersectionReferencePolicy === 'both-faces'
707
+ ? uniqueSorted(references)
708
+ : standard.datumPolicy === 'structural-face' && canonicalStructuralFace
709
+ ? [dot(canonicalStructuralFace, tangent)]
710
+ : [Math.min(...references)];
711
+ for (const selectedReference of selectedReferences) {
712
+ if (Number.isFinite(selectedReference) &&
713
+ selectedReference > extentStart + MIN_SEGMENT_LENGTH &&
714
+ selectedReference < extentEnd - MIN_SEGMENT_LENGTH) {
715
+ partitionReferences.push(selectedReference);
716
+ }
717
+ }
718
+ }
719
+ appendReferenceTier(pending, partitionReferences, extentStart, extentEnd, pointAt, 'partitions');
720
+ }
721
+ function selectCanonicalWallFaceIntersection(intersections, wall) {
722
+ const direction = subtract(wall.end, wall.start);
723
+ const length = Math.hypot(direction[0], direction[1]);
724
+ if (length < MIN_SEGMENT_LENGTH)
725
+ return undefined;
726
+ let tangent = [direction[0] / length, direction[1] / length];
727
+ if (tangent[0] < -FACADE_DIRECTION_TOLERANCE ||
728
+ (Math.abs(tangent[0]) <= FACADE_DIRECTION_TOLERANCE && tangent[1] < 0)) {
729
+ tangent = negate(tangent);
730
+ }
731
+ const canonicalFaceNormal = [-tangent[1], tangent[0]];
732
+ return intersections.reduce((selected, intersection) => {
733
+ if (!selected)
734
+ return intersection;
735
+ return dot(intersection, canonicalFaceNormal) > dot(selected, canonicalFaceNormal)
736
+ ? intersection
737
+ : selected;
738
+ }, undefined);
739
+ }
740
+ function appendReferenceTier(pending, references, extentStart, extentEnd, pointAt, tier) {
741
+ const interiorReferences = uniqueSorted(references).filter((value) => value > extentStart + MIN_SEGMENT_LENGTH && value < extentEnd - MIN_SEGMENT_LENGTH);
742
+ if (interiorReferences.length === 0)
743
+ return;
744
+ appendProjectedChain(pending, [extentStart, ...interiorReferences, extentEnd], tier, pointAt);
745
+ }
746
+ function appendProjectedChain(pending, projections, tier, originAt) {
747
+ const breakpoints = uniqueSorted(projections);
748
+ for (let index = 0; index < breakpoints.length - 1; index++) {
749
+ const startProjection = breakpoints[index];
750
+ const endProjection = breakpoints[index + 1];
751
+ if (startProjection === undefined ||
752
+ endProjection === undefined ||
753
+ endProjection - startProjection < MIN_SEGMENT_LENGTH) {
754
+ continue;
755
+ }
756
+ pending.push({
757
+ tier,
758
+ start: originAt(startProjection),
759
+ end: originAt(endProjection),
760
+ startProjection,
761
+ endProjection,
762
+ });
763
+ }
764
+ }
765
+ function finalizeDimensionTiers(pending, tangent, normal, outerCoordinate, standard) {
766
+ const activeTiers = TIER_ORDER.filter((tier) => pending.some((entry) => entry.tier === tier));
767
+ const offsets = new Map();
768
+ activeTiers.forEach((tier, index) => {
769
+ const firstOffset = activeTiers[0] === 'opening-widths'
770
+ ? standard.firstOpeningWidthOffset
771
+ : standard.firstGeneralTierOffset;
772
+ offsets.set(tier, firstOffset + index * standard.tierSpacing);
773
+ });
774
+ return [...pending]
775
+ .sort((left, right) => {
776
+ const tierDelta = TIER_ORDER.indexOf(left.tier) - TIER_ORDER.indexOf(right.tier);
777
+ return tierDelta || left.startProjection - right.startProjection;
778
+ })
779
+ .map((entry) => {
780
+ const offset = offsets.get(entry.tier) ?? standard.firstGeneralTierOffset;
781
+ const baselineCoordinate = outerCoordinate + offset;
782
+ const dimensionStart = pointFromCoordinates(entry.startProjection, baselineCoordinate, tangent, normal);
783
+ const dimensionEnd = pointFromCoordinates(entry.endProjection, baselineCoordinate, tangent, normal);
784
+ return {
785
+ tier: entry.tier,
786
+ start: entry.start,
787
+ end: entry.end,
788
+ dimensionStart,
789
+ dimensionEnd,
790
+ offsetNormal: normal,
791
+ offsetDistance: Math.max(0, dot(subtract(dimensionStart, entry.start), normal)),
792
+ textPrefix: entry.textPrefix,
793
+ };
794
+ });
795
+ }
796
+ function facadeStructuralExtents(members, walls, tangent) {
797
+ const endpoints = members.flatMap(({ wall }) => [wall.start, wall.end]);
798
+ const centerlineProjections = endpoints.map((point) => dot(point, tangent));
799
+ const centerlineStart = Math.min(...centerlineProjections);
800
+ const centerlineEnd = Math.max(...centerlineProjections);
801
+ const structuralProjectionsAt = (targetProjection) => endpoints
802
+ .filter((endpoint) => Math.abs(dot(endpoint, tangent) - targetProjection) <= FACADE_LINE_TOLERANCE)
803
+ .flatMap((endpoint) => walls.flatMap((candidate) => {
804
+ if (isCurvedWall(candidate) ||
805
+ (distance(endpoint, candidate.start) > FACADE_LINE_TOLERANCE &&
806
+ distance(endpoint, candidate.end) > FACADE_LINE_TOLERANCE)) {
807
+ return [];
808
+ }
809
+ const direction = subtract(candidate.end, candidate.start);
810
+ const length = Math.hypot(direction[0], direction[1]);
811
+ if (length < MIN_SEGMENT_LENGTH)
812
+ return [];
813
+ const normal = [-direction[1] / length, direction[0] / length];
814
+ return wallDatumOffsets(candidate, EXTERIOR_CORNER_DATUM_POLICY).map((offset) => dot(addScaled(endpoint, normal, offset), tangent));
815
+ }));
816
+ return [
817
+ Math.min(centerlineStart, ...structuralProjectionsAt(centerlineStart)),
818
+ Math.max(centerlineEnd, ...structuralProjectionsAt(centerlineEnd)),
819
+ ];
820
+ }
821
+ function exteriorFaceCoordinate(wall, normal, datumPolicy) {
822
+ const midpoint = [
823
+ (wall.start[0] + wall.end[0]) / 2,
824
+ (wall.start[1] + wall.end[1]) / 2,
825
+ ];
826
+ return dot(midpoint, normal) + wallDatumDistanceToward(wall, datumPolicy, normal);
827
+ }
828
+ function curvedFacadeOuterFaceCoordinates(walls, members, normal, datumPolicy) {
829
+ const parentId = members[0]?.wall.parentId;
830
+ const memberEndpoints = members.flatMap(({ wall }) => [wall.start, wall.end]);
831
+ const touchesFacade = (point) => memberEndpoints.some((endpoint) => distance(point, endpoint) <= FACADE_LINE_TOLERANCE);
832
+ return walls.flatMap((wall) => {
833
+ if (wall.parentId !== parentId ||
834
+ !isCurvedWall(wall) ||
835
+ !touchesFacade(wall.start) ||
836
+ !touchesFacade(wall.end)) {
837
+ return [];
838
+ }
839
+ const midpoint = getWallMidpointHandlePoint(wall);
840
+ const outerCenterlineCoordinate = Math.max(dot(wall.start, normal), dot(wall.end, normal), dot([midpoint.x, midpoint.y], normal));
841
+ return [outerCenterlineCoordinate + wallDatumDistanceToward(wall, datumPolicy, normal)];
842
+ });
843
+ }
844
+ function exteriorOriginAtProjection(members, projection, tangent, normal, datumPolicy) {
845
+ const endpoint = members
846
+ .flatMap(({ wall }) => [
847
+ { point: wall.start, wall },
848
+ { point: wall.end, wall },
849
+ ])
850
+ .sort((left, right) => {
851
+ const projectionDelta = Math.abs(dot(left.point, tangent) - projection) -
852
+ Math.abs(dot(right.point, tangent) - projection);
853
+ return (projectionDelta ||
854
+ exteriorFaceCoordinate(right.wall, normal, datumPolicy) -
855
+ exteriorFaceCoordinate(left.wall, normal, datumPolicy));
856
+ })[0];
857
+ if (!endpoint)
858
+ return pointFromCoordinates(projection, 0, tangent, normal);
859
+ return pointFromCoordinates(projection, exteriorFaceCoordinate(endpoint.wall, normal, datumPolicy), tangent, normal);
860
+ }
861
+ function outermostColumnRow(columns, component, normal, tangent) {
862
+ if (columns.length < 2)
863
+ return [];
864
+ const centroid = facadeCentroid(component);
865
+ const outwardColumns = columns.filter((column) => dot(subtract(columnPlanPoint(column), centroid), normal) >= -COLUMN_ROW_TOLERANCE);
866
+ const sorted = [...outwardColumns].sort((left, right) => dot(columnPlanPoint(right), normal) - dot(columnPlanPoint(left), normal));
867
+ const outerCoordinate = sorted[0] ? dot(columnPlanPoint(sorted[0]), normal) : 0;
868
+ return sorted
869
+ .filter((column) => Math.abs(dot(columnPlanPoint(column), normal) - outerCoordinate) <= COLUMN_ROW_TOLERANCE)
870
+ .sort((left, right) => dot(columnPlanPoint(left), tangent) - dot(columnPlanPoint(right), tangent));
871
+ }
872
+ function columnOriginAtProjection(columns, projection, tangent) {
873
+ return columnPlanPoint([...columns].sort((left, right) => Math.abs(dot(columnPlanPoint(left), tangent) - projection) -
874
+ Math.abs(dot(columnPlanPoint(right), tangent) - projection))[0]);
875
+ }
876
+ function columnPlanPoint(column) {
877
+ return [column.position[0], column.position[2]];
878
+ }
879
+ function columnNormalHalfExtent(column, normal) {
880
+ const center = columnPlanPoint(column);
881
+ return Math.max(...getColumnFloorplanFootprint(column).map((point) => dot(subtract(point, center), normal)));
882
+ }
883
+ function nearestFacadeComponent(column, components) {
884
+ const point = columnPlanPoint(column);
885
+ return [...components]
886
+ .filter((component) => component[0]?.wall.parentId === column.parentId)
887
+ .sort((left, right) => distanceToFacadeComponent(point, left) - distanceToFacadeComponent(point, right))[0];
888
+ }
889
+ function distanceToFacadeComponent(point, component) {
890
+ return Math.min(...component.map(({ wall }) => pointSegmentDistance(point, wall.start, wall.end)));
891
+ }
892
+ function facadeCentroid(component) {
893
+ const points = component.flatMap(({ wall }) => [wall.start, wall.end]);
894
+ return [
895
+ points.reduce((sum, point) => sum + point[0], 0) / points.length,
896
+ points.reduce((sum, point) => sum + point[1], 0) / points.length,
897
+ ];
898
+ }
899
+ function pointFromCoordinates(tangentCoordinate, normalCoordinate, tangent, normal) {
900
+ return [
901
+ tangent[0] * tangentCoordinate + normal[0] * normalCoordinate,
902
+ tangent[1] * tangentCoordinate + normal[1] * normalCoordinate,
903
+ ];
904
+ }
905
+ function splitFacadeRuns(members) {
906
+ const tangent = members[0]?.tangent;
907
+ if (!tangent)
908
+ return [];
909
+ const sorted = [...members].sort((left, right) => {
910
+ const leftStart = Math.min(dot(left.wall.start, tangent), dot(left.wall.end, tangent));
911
+ const rightStart = Math.min(dot(right.wall.start, tangent), dot(right.wall.end, tangent));
912
+ return leftStart - rightStart;
913
+ });
914
+ const runs = [];
915
+ let runEnd = Number.NEGATIVE_INFINITY;
916
+ for (const member of sorted) {
917
+ const start = Math.min(dot(member.wall.start, tangent), dot(member.wall.end, tangent));
918
+ const end = Math.max(dot(member.wall.start, tangent), dot(member.wall.end, tangent));
919
+ const current = runs.at(-1);
920
+ if (!current || start > runEnd + FACADE_LINE_TOLERANCE) {
921
+ runs.push([member]);
922
+ runEnd = end;
923
+ }
924
+ else {
925
+ current.push(member);
926
+ runEnd = Math.max(runEnd, end);
927
+ }
928
+ }
929
+ return runs;
930
+ }
931
+ function facadePartitionFaceIntersections(facade, candidate, outwardNormal, datumPolicy) {
932
+ const halfThickness = wallDatumDistanceToward(facade, 'wall-face', outwardNormal);
933
+ const insideStart = [
934
+ facade.start[0] - outwardNormal[0] * halfThickness,
935
+ facade.start[1] - outwardNormal[1] * halfThickness,
936
+ ];
937
+ const insideEnd = [
938
+ facade.end[0] - outwardNormal[0] * halfThickness,
939
+ facade.end[1] - outwardNormal[1] * halfThickness,
940
+ ];
941
+ const dx = candidate.end[0] - candidate.start[0];
942
+ const dz = candidate.end[1] - candidate.start[1];
943
+ const length = Math.hypot(dx, dz);
944
+ if (length < MIN_SEGMENT_LENGTH)
945
+ return [];
946
+ const candidateNormal = [-dz / length, dx / length];
947
+ const candidateOffsets = wallDatumOffsets(candidate, datumPolicy);
948
+ return candidateOffsets.flatMap((offset) => {
949
+ const faceStart = addScaled(candidate.start, candidateNormal, offset);
950
+ const faceEnd = addScaled(candidate.end, candidateNormal, offset);
951
+ const intersection = segmentIntersection(insideStart, insideEnd, faceStart, faceEnd);
952
+ return intersection ? [intersection] : [];
953
+ });
954
+ }
955
+ function wallDatumOffsets(wall, policy) {
956
+ if (policy === 'centerline')
957
+ return [0];
958
+ return [wallDatumOffsetOnSide(wall, policy, -1), wallDatumOffsetOnSide(wall, policy, 1)];
959
+ }
960
+ function wallDatumOffsetOnSide(wall, policy, side) {
961
+ if (policy === 'centerline')
962
+ return 0;
963
+ return (getWallThickness(wall) / 2) * side;
964
+ }
965
+ function wallDatumDistanceToward(wall, policy, direction) {
966
+ if (policy === 'centerline')
967
+ return 0;
968
+ const wallDirection = subtract(wall.end, wall.start);
969
+ const length = Math.hypot(wallDirection[0], wallDirection[1]);
970
+ if (length < MIN_SEGMENT_LENGTH)
971
+ return 0;
972
+ const positiveNormal = [-wallDirection[1] / length, wallDirection[0] / length];
973
+ const side = dot(positiveNormal, direction) >= 0 ? 1 : -1;
974
+ return Math.abs(wallDatumOffsetOnSide(wall, policy, side));
975
+ }
976
+ function maximumWallDatumDistance(wall, policy) {
977
+ return Math.max(...wallDatumOffsets(wall, policy).map(Math.abs));
978
+ }
979
+ function segmentIntersection(aStart, aEnd, bStart, bEnd) {
980
+ const ax = aEnd[0] - aStart[0];
981
+ const ay = aEnd[1] - aStart[1];
982
+ const bx = bEnd[0] - bStart[0];
983
+ const by = bEnd[1] - bStart[1];
984
+ const denominator = ax * by - ay * bx;
985
+ if (Math.abs(denominator) < 1e-8)
986
+ return null;
987
+ const qx = bStart[0] - aStart[0];
988
+ const qy = bStart[1] - aStart[1];
989
+ const alongA = (qx * by - qy * bx) / denominator;
990
+ const alongB = (qx * ay - qy * ax) / denominator;
991
+ if (alongA < -1e-6 || alongA > 1 + 1e-6 || alongB < -1e-6 || alongB > 1 + 1e-6) {
992
+ return null;
993
+ }
994
+ return [aStart[0] + ax * alongA, aStart[1] + ay * alongA];
995
+ }
996
+ function pointSegmentDistance(point, start, end) {
997
+ const segment = subtract(end, start);
998
+ const lengthSquared = dot(segment, segment);
999
+ if (lengthSquared < 1e-12)
1000
+ return distance(point, start);
1001
+ const along = clamp(dot(subtract(point, start), segment) / lengthSquared, 0, 1);
1002
+ return distance(point, addScaled(start, segment, along));
1003
+ }
1004
+ function distance(left, right) {
1005
+ return Math.hypot(left[0] - right[0], left[1] - right[1]);
1006
+ }
1007
+ function subtract(left, right) {
1008
+ return [left[0] - right[0], left[1] - right[1]];
1009
+ }
1010
+ function addScaled(point, direction, distance) {
1011
+ return [point[0] + direction[0] * distance, point[1] + direction[1] * distance];
1012
+ }
1013
+ function dot(left, right) {
1014
+ return left[0] * right[0] + left[1] * right[1];
1015
+ }
1016
+ function cross(left, right) {
1017
+ return left[0] * right[1] - left[1] * right[0];
1018
+ }
1019
+ function negate(point) {
1020
+ return [cleanZero(-point[0]), cleanZero(-point[1])];
1021
+ }
1022
+ function cleanZero(value) {
1023
+ return Object.is(value, -0) ? 0 : value;
1024
+ }
1025
+ function uniqueSorted(values) {
1026
+ const sorted = [...values].sort((a, b) => a - b);
1027
+ return sorted.filter((value, index) => index === 0 || Math.abs(value - sorted[index - 1]) > 1e-6);
1028
+ }
1029
+ function clamp(value, min, max) {
1030
+ return Math.min(max, Math.max(min, value));
1031
+ }