@reekon-tools/boldr-cad 0.1.0 → 1.0.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 (547) hide show
  1. package/README.md +81 -9
  2. package/dist/app/edit.d.ts +12 -1
  3. package/dist/app/edit.js +96 -11
  4. package/dist/app/edit.js.map +1 -1
  5. package/dist/app/state.d.ts +31 -4
  6. package/dist/app/state.js +31 -2
  7. package/dist/app/state.js.map +1 -1
  8. package/dist/data/memory.d.ts +5 -0
  9. package/dist/data/memory.js +2 -2
  10. package/dist/data/memory.js.map +1 -1
  11. package/dist/data/provider.d.ts +19 -7
  12. package/dist/data/sync.d.ts +7 -2
  13. package/dist/data/sync.js +115 -64
  14. package/dist/data/sync.js.map +1 -1
  15. package/dist/editor/actions.d.ts +1 -2
  16. package/dist/editor/actions.js +391 -113
  17. package/dist/editor/actions.js.map +1 -1
  18. package/dist/editor/confirm.d.ts +23 -0
  19. package/dist/editor/confirm.js +35 -0
  20. package/dist/editor/confirm.js.map +1 -0
  21. package/dist/editor/elevation.d.ts +191 -90
  22. package/dist/editor/elevation.js +812 -616
  23. package/dist/editor/elevation.js.map +1 -1
  24. package/dist/editor/face.d.ts +73 -0
  25. package/dist/editor/face.js +269 -0
  26. package/dist/editor/face.js.map +1 -0
  27. package/dist/editor/files.d.ts +6 -0
  28. package/dist/editor/files.js +50 -4
  29. package/dist/editor/files.js.map +1 -1
  30. package/dist/editor/gestures.d.ts +18 -3
  31. package/dist/editor/gestures.js +145 -30
  32. package/dist/editor/gestures.js.map +1 -1
  33. package/dist/editor/keypad.d.ts +19 -4
  34. package/dist/editor/keypad.js +39 -9
  35. package/dist/editor/keypad.js.map +1 -1
  36. package/dist/editor/keys.js +36 -4
  37. package/dist/editor/keys.js.map +1 -1
  38. package/dist/editor/laser.d.ts +20 -4
  39. package/dist/editor/laser.js +104 -21
  40. package/dist/editor/laser.js.map +1 -1
  41. package/dist/editor/linkActions.d.ts +49 -0
  42. package/dist/editor/linkActions.js +210 -0
  43. package/dist/editor/linkActions.js.map +1 -0
  44. package/dist/editor/linkFollow.d.ts +2 -0
  45. package/dist/editor/linkFollow.js +53 -0
  46. package/dist/editor/linkFollow.js.map +1 -0
  47. package/dist/editor/linkWrite.d.ts +13 -0
  48. package/dist/editor/linkWrite.js +84 -0
  49. package/dist/editor/linkWrite.js.map +1 -0
  50. package/dist/editor/links.d.ts +84 -0
  51. package/dist/editor/links.js +130 -0
  52. package/dist/editor/links.js.map +1 -0
  53. package/dist/editor/markup.d.ts +29 -3
  54. package/dist/editor/markup.js +95 -10
  55. package/dist/editor/markup.js.map +1 -1
  56. package/dist/editor/mode.d.ts +40 -2
  57. package/dist/editor/mode.js +71 -4
  58. package/dist/editor/mode.js.map +1 -1
  59. package/dist/editor/ports.d.ts +36 -6
  60. package/dist/editor/ports.js +10 -1
  61. package/dist/editor/ports.js.map +1 -1
  62. package/dist/editor/project.d.ts +38 -6
  63. package/dist/editor/project.js +167 -27
  64. package/dist/editor/project.js.map +1 -1
  65. package/dist/editor/selection.d.ts +10 -0
  66. package/dist/editor/selection.js +62 -8
  67. package/dist/editor/selection.js.map +1 -1
  68. package/dist/editor/status.js +3 -0
  69. package/dist/editor/status.js.map +1 -1
  70. package/dist/editor/tooltips.d.ts +5 -0
  71. package/dist/editor/tooltips.js +48 -0
  72. package/dist/editor/tooltips.js.map +1 -0
  73. package/dist/editor/view3d.d.ts +17 -0
  74. package/dist/editor/view3d.js +135 -0
  75. package/dist/editor/view3d.js.map +1 -0
  76. package/dist/editor/view3dState.d.ts +39 -0
  77. package/dist/editor/view3dState.js +44 -0
  78. package/dist/editor/view3dState.js.map +1 -0
  79. package/dist/export/dxf.js +1 -1
  80. package/dist/export/dxf.js.map +1 -1
  81. package/dist/export/pdf.js +17 -12
  82. package/dist/export/pdf.js.map +1 -1
  83. package/dist/export/plan.d.ts +23 -6
  84. package/dist/export/plan.js +127 -44
  85. package/dist/export/plan.js.map +1 -1
  86. package/dist/index.d.ts +5 -0
  87. package/dist/index.js +2 -0
  88. package/dist/index.js.map +1 -1
  89. package/dist/model/constraints.js +15 -4
  90. package/dist/model/constraints.js.map +1 -1
  91. package/dist/model/demo.js +3 -3
  92. package/dist/model/demo.js.map +1 -1
  93. package/dist/model/elevation.d.ts +6 -9
  94. package/dist/model/elevation.js +5 -13
  95. package/dist/model/elevation.js.map +1 -1
  96. package/dist/model/faces.d.ts +58 -0
  97. package/dist/model/faces.js +197 -0
  98. package/dist/model/faces.js.map +1 -0
  99. package/dist/model/facesolve.d.ts +72 -0
  100. package/dist/model/facesolve.js +369 -0
  101. package/dist/model/facesolve.js.map +1 -0
  102. package/dist/model/history.d.ts +3 -2
  103. package/dist/model/history.js +17 -11
  104. package/dist/model/history.js.map +1 -1
  105. package/dist/model/images.d.ts +18 -0
  106. package/dist/model/images.js +26 -0
  107. package/dist/model/images.js.map +1 -0
  108. package/dist/model/links.d.ts +46 -0
  109. package/dist/model/links.js +73 -0
  110. package/dist/model/links.js.map +1 -0
  111. package/dist/model/markup.d.ts +46 -0
  112. package/dist/model/markup.js +71 -0
  113. package/dist/model/markup.js.map +1 -0
  114. package/dist/model/measurement.d.ts +11 -0
  115. package/dist/model/measurement.js +24 -0
  116. package/dist/model/measurement.js.map +1 -0
  117. package/dist/model/openings.d.ts +4 -0
  118. package/dist/model/openings.js +4 -0
  119. package/dist/model/openings.js.map +1 -1
  120. package/dist/model/planGeom.d.ts +72 -2
  121. package/dist/model/planGeom.js +184 -10
  122. package/dist/model/planGeom.js.map +1 -1
  123. package/dist/model/project.js +3 -2
  124. package/dist/model/project.js.map +1 -1
  125. package/dist/model/projectfile.js +2 -1
  126. package/dist/model/projectfile.js.map +1 -1
  127. package/dist/model/rooms.js +2 -2
  128. package/dist/model/rooms.js.map +1 -1
  129. package/dist/model/settings.d.ts +6 -0
  130. package/dist/model/settings.js +2 -2
  131. package/dist/model/settings.js.map +1 -1
  132. package/dist/model/sketch.d.ts +14 -0
  133. package/dist/model/sketch.js +143 -24
  134. package/dist/model/sketch.js.map +1 -1
  135. package/dist/model/smooth.d.ts +6 -0
  136. package/dist/model/smooth.js +52 -0
  137. package/dist/model/smooth.js.map +1 -0
  138. package/dist/model/store.d.ts +12 -0
  139. package/dist/model/store.js +35 -3
  140. package/dist/model/store.js.map +1 -1
  141. package/dist/model/types.d.ts +173 -8
  142. package/dist/model/wallfaces.d.ts +32 -0
  143. package/dist/model/wallfaces.js +133 -20
  144. package/dist/model/wallfaces.js.map +1 -1
  145. package/dist/model/wallshape.d.ts +15 -0
  146. package/dist/model/wallshape.js +89 -0
  147. package/dist/model/wallshape.js.map +1 -0
  148. package/dist/react/BottomBar.d.ts +9 -5
  149. package/dist/react/BottomBar.js +213 -24
  150. package/dist/react/BottomBar.js.map +1 -1
  151. package/dist/react/Buttons.d.ts +13 -2
  152. package/dist/react/Buttons.js +37 -5
  153. package/dist/react/Buttons.js.map +1 -1
  154. package/dist/react/CadEditor.d.ts +46 -3
  155. package/dist/react/CadEditor.js +77 -20
  156. package/dist/react/CadEditor.js.map +1 -1
  157. package/dist/react/ConfirmSheet.d.ts +4 -0
  158. package/dist/react/ConfirmSheet.js +31 -0
  159. package/dist/react/ConfirmSheet.js.map +1 -0
  160. package/dist/react/ConflictSheet.d.ts +2 -1
  161. package/dist/react/ConflictSheet.js +16 -8
  162. package/dist/react/ConflictSheet.js.map +1 -1
  163. package/dist/react/Drawer.d.ts +8 -2
  164. package/dist/react/Drawer.js +167 -38
  165. package/dist/react/Drawer.js.map +1 -1
  166. package/dist/react/Duck.d.ts +13 -0
  167. package/dist/react/Duck.js +45 -0
  168. package/dist/react/Duck.js.map +1 -0
  169. package/dist/react/Empty3D.d.ts +2 -0
  170. package/dist/react/Empty3D.js +39 -0
  171. package/dist/react/Empty3D.js.map +1 -0
  172. package/dist/react/FitScroll.d.ts +6 -0
  173. package/dist/react/FitScroll.js +14 -0
  174. package/dist/react/FitScroll.js.map +1 -0
  175. package/dist/react/Icon.native.d.ts +6 -0
  176. package/dist/react/Icon.native.js +4 -0
  177. package/dist/react/Icon.native.js.map +1 -1
  178. package/dist/react/Icon.web.d.ts +6 -0
  179. package/dist/react/Icon.web.js +4 -0
  180. package/dist/react/Icon.web.js.map +1 -1
  181. package/dist/react/KeypadSheet.d.ts +9 -2
  182. package/dist/react/KeypadSheet.js +174 -42
  183. package/dist/react/KeypadSheet.js.map +1 -1
  184. package/dist/react/LaserTray.js +8 -5
  185. package/dist/react/LaserTray.js.map +1 -1
  186. package/dist/react/MarkupPanel.d.ts +8 -0
  187. package/dist/react/MarkupPanel.js +67 -0
  188. package/dist/react/MarkupPanel.js.map +1 -0
  189. package/dist/react/PageThumb.native.d.ts +7 -2
  190. package/dist/react/PageThumb.native.js +19 -7
  191. package/dist/react/PageThumb.native.js.map +1 -1
  192. package/dist/react/PageThumb.web.d.ts +3 -2
  193. package/dist/react/PageThumb.web.js +4 -4
  194. package/dist/react/PageThumb.web.js.map +1 -1
  195. package/dist/react/PlanCanvas.native.d.ts +1 -1
  196. package/dist/react/PlanCanvas.native.js +134 -36
  197. package/dist/react/PlanCanvas.native.js.map +1 -1
  198. package/dist/react/PlanCanvas.web.js +16 -5
  199. package/dist/react/PlanCanvas.web.js.map +1 -1
  200. package/dist/react/Popovers.d.ts +12 -6
  201. package/dist/react/Popovers.js +53 -26
  202. package/dist/react/Popovers.js.map +1 -1
  203. package/dist/react/Rail.d.ts +24 -0
  204. package/dist/react/Rail.js +324 -34
  205. package/dist/react/Rail.js.map +1 -1
  206. package/dist/react/ShapeMenu.d.ts +7 -0
  207. package/dist/react/ShapeMenu.js +58 -0
  208. package/dist/react/ShapeMenu.js.map +1 -0
  209. package/dist/react/Sheet.d.ts +14 -4
  210. package/dist/react/Sheet.js +54 -9
  211. package/dist/react/Sheet.js.map +1 -1
  212. package/dist/react/Slider.d.ts +18 -0
  213. package/dist/react/Slider.js +27 -0
  214. package/dist/react/Slider.js.map +1 -0
  215. package/dist/react/Toast.js +14 -6
  216. package/dist/react/Toast.js.map +1 -1
  217. package/dist/react/ToolTip.d.ts +7 -0
  218. package/dist/react/ToolTip.js +43 -0
  219. package/dist/react/ToolTip.js.map +1 -0
  220. package/dist/react/TopBar.d.ts +6 -3
  221. package/dist/react/TopBar.js +48 -17
  222. package/dist/react/TopBar.js.map +1 -1
  223. package/dist/react/Tween.d.ts +7 -0
  224. package/dist/react/Tween.js +26 -0
  225. package/dist/react/Tween.js.map +1 -0
  226. package/dist/react/Tween.native.d.ts +6 -0
  227. package/dist/react/Tween.native.js +43 -0
  228. package/dist/react/Tween.native.js.map +1 -0
  229. package/dist/react/View3D.native.d.ts +4 -0
  230. package/dist/react/View3D.native.js +103 -0
  231. package/dist/react/View3D.native.js.map +1 -0
  232. package/dist/react/View3D.web.d.ts +4 -0
  233. package/dist/react/View3D.web.js +151 -0
  234. package/dist/react/View3D.web.js.map +1 -0
  235. package/dist/react/View3DHost.d.ts +143 -0
  236. package/dist/react/View3DHost.js +377 -0
  237. package/dist/react/View3DHost.js.map +1 -0
  238. package/dist/react/corners.d.ts +7 -0
  239. package/dist/react/corners.js +51 -0
  240. package/dist/react/corners.js.map +1 -0
  241. package/dist/react/easing.d.ts +15 -0
  242. package/dist/react/easing.js +10 -0
  243. package/dist/react/easing.js.map +1 -0
  244. package/dist/react/host.d.ts +7 -2
  245. package/dist/react/host.js +8 -3
  246. package/dist/react/host.js.map +1 -1
  247. package/dist/react/hostKeypad.d.ts +56 -0
  248. package/dist/react/hostKeypad.js +5 -0
  249. package/dist/react/hostKeypad.js.map +1 -0
  250. package/dist/react/motion.d.ts +77 -0
  251. package/dist/react/motion.js +173 -0
  252. package/dist/react/motion.js.map +1 -0
  253. package/dist/react/ports.js +90 -8
  254. package/dist/react/ports.js.map +1 -1
  255. package/dist/react/saving.d.ts +15 -0
  256. package/dist/react/saving.js +76 -0
  257. package/dist/react/saving.js.map +1 -0
  258. package/dist/react/store.d.ts +38 -1
  259. package/dist/react/store.js +31 -1
  260. package/dist/react/store.js.map +1 -1
  261. package/dist/react/theme.d.ts +10 -0
  262. package/dist/react/theme.js +8 -0
  263. package/dist/react/theme.js.map +1 -1
  264. package/dist/react/topRow.d.ts +22 -0
  265. package/dist/react/topRow.js +61 -0
  266. package/dist/react/topRow.js.map +1 -0
  267. package/dist/react/touch.d.ts +6 -0
  268. package/dist/react/touch.js +9 -0
  269. package/dist/react/touch.js.map +1 -0
  270. package/dist/render/ctx.d.ts +8 -0
  271. package/dist/render/draw.d.ts +33 -1
  272. package/dist/render/draw.js +593 -215
  273. package/dist/render/draw.js.map +1 -1
  274. package/dist/render/elevation.d.ts +65 -43
  275. package/dist/render/elevation.js +414 -140
  276. package/dist/render/elevation.js.map +1 -1
  277. package/dist/render/figmaIcons.d.ts +12 -0
  278. package/dist/render/figmaIcons.js +21 -0
  279. package/dist/render/figmaIcons.js.map +1 -0
  280. package/dist/render/hit.d.ts +22 -2
  281. package/dist/render/hit.js +168 -25
  282. package/dist/render/hit.js.map +1 -1
  283. package/dist/render/icons.js +69 -10
  284. package/dist/render/icons.js.map +1 -1
  285. package/dist/render/linkTile.d.ts +15 -0
  286. package/dist/render/linkTile.js +369 -0
  287. package/dist/render/linkTile.js.map +1 -0
  288. package/dist/render/markup.d.ts +11 -0
  289. package/dist/render/markup.js +112 -0
  290. package/dist/render/markup.js.map +1 -0
  291. package/dist/render/navigate.d.ts +88 -0
  292. package/dist/render/navigate.js +100 -0
  293. package/dist/render/navigate.js.map +1 -0
  294. package/dist/render/snap.d.ts +20 -1
  295. package/dist/render/snap.js +118 -25
  296. package/dist/render/snap.js.map +1 -1
  297. package/dist/render/squircle.d.ts +8 -0
  298. package/dist/render/squircle.js +71 -0
  299. package/dist/render/squircle.js.map +1 -0
  300. package/dist/render/view.d.ts +115 -2
  301. package/dist/render/view.js +244 -19
  302. package/dist/render/view.js.map +1 -1
  303. package/dist/skia/SkiaDrawCtx.d.ts +30 -2
  304. package/dist/skia/SkiaDrawCtx.js +198 -43
  305. package/dist/skia/SkiaDrawCtx.js.map +1 -1
  306. package/dist/skia/host.d.ts +18 -2
  307. package/dist/skia/host.js +46 -1
  308. package/dist/skia/host.js.map +1 -1
  309. package/dist/solver/equations.js +37 -2
  310. package/dist/solver/equations.js.map +1 -1
  311. package/dist/solver/gauss-newton.d.ts +17 -0
  312. package/dist/solver/gauss-newton.js +144 -36
  313. package/dist/solver/gauss-newton.js.map +1 -1
  314. package/dist/solver/linalg.d.ts +45 -2
  315. package/dist/solver/linalg.js +164 -10
  316. package/dist/solver/linalg.js.map +1 -1
  317. package/dist/solver/types.d.ts +5 -0
  318. package/dist/tools/annotate.d.ts +57 -1
  319. package/dist/tools/annotate.js +64 -43
  320. package/dist/tools/annotate.js.map +1 -1
  321. package/dist/tools/chamfer.d.ts +12 -0
  322. package/dist/tools/chamfer.js +114 -0
  323. package/dist/tools/chamfer.js.map +1 -0
  324. package/dist/tools/corner.d.ts +38 -0
  325. package/dist/tools/corner.js +99 -0
  326. package/dist/tools/corner.js.map +1 -0
  327. package/dist/tools/dim.d.ts +86 -9
  328. package/dist/tools/dim.js +581 -96
  329. package/dist/tools/dim.js.map +1 -1
  330. package/dist/tools/elevation.js +1 -1
  331. package/dist/tools/elevation.js.map +1 -1
  332. package/dist/tools/fillet.d.ts +9 -0
  333. package/dist/tools/fillet.js +86 -0
  334. package/dist/tools/fillet.js.map +1 -0
  335. package/dist/tools/freehand.d.ts +3 -2
  336. package/dist/tools/freehand.js +19 -12
  337. package/dist/tools/freehand.js.map +1 -1
  338. package/dist/tools/index.js +14 -4
  339. package/dist/tools/index.js.map +1 -1
  340. package/dist/tools/line.d.ts +8 -3
  341. package/dist/tools/line.js +66 -30
  342. package/dist/tools/line.js.map +1 -1
  343. package/dist/tools/modify.d.ts +13 -0
  344. package/dist/tools/modify.js +105 -25
  345. package/dist/tools/modify.js.map +1 -1
  346. package/dist/tools/openings.js +22 -15
  347. package/dist/tools/openings.js.map +1 -1
  348. package/dist/tools/registry.d.ts +8 -0
  349. package/dist/tools/registry.js +42 -2
  350. package/dist/tools/registry.js.map +1 -1
  351. package/dist/tools/select.d.ts +77 -3
  352. package/dist/tools/select.js +560 -74
  353. package/dist/tools/select.js.map +1 -1
  354. package/dist/tools/shapes.d.ts +14 -0
  355. package/dist/tools/shapes.js +154 -75
  356. package/dist/tools/shapes.js.map +1 -1
  357. package/dist/units/format.d.ts +10 -2
  358. package/dist/units/format.js +18 -3
  359. package/dist/units/format.js.map +1 -1
  360. package/dist/view3d/build.d.ts +35 -0
  361. package/dist/view3d/build.js +630 -0
  362. package/dist/view3d/build.js.map +1 -0
  363. package/dist/view3d/camera.d.ts +60 -0
  364. package/dist/view3d/camera.js +84 -0
  365. package/dist/view3d/camera.js.map +1 -0
  366. package/dist/view3d/fatLines.d.ts +11 -0
  367. package/dist/view3d/fatLines.js +83 -0
  368. package/dist/view3d/fatLines.js.map +1 -0
  369. package/dist/view3d/lines.d.ts +17 -0
  370. package/dist/view3d/lines.js +271 -0
  371. package/dist/view3d/lines.js.map +1 -0
  372. package/dist/view3d/mesh.d.ts +51 -0
  373. package/dist/view3d/mesh.js +101 -0
  374. package/dist/view3d/mesh.js.map +1 -0
  375. package/dist/view3d/poly.d.ts +16 -0
  376. package/dist/view3d/poly.js +109 -0
  377. package/dist/view3d/poly.js.map +1 -0
  378. package/dist/view3d/viewer.d.ts +86 -0
  379. package/dist/view3d/viewer.js +350 -0
  380. package/dist/view3d/viewer.js.map +1 -0
  381. package/package.json +15 -2
  382. package/src/app/edit.ts +83 -12
  383. package/src/app/main.ts +11 -5
  384. package/src/app/state.ts +38 -5
  385. package/src/data/memory.ts +2 -2
  386. package/src/data/provider.ts +15 -7
  387. package/src/data/sync.ts +87 -50
  388. package/src/editor/actions.ts +292 -83
  389. package/src/editor/confirm.ts +58 -0
  390. package/src/editor/elevation.ts +698 -478
  391. package/src/editor/face.ts +227 -0
  392. package/src/editor/files.ts +40 -4
  393. package/src/editor/gestures.ts +147 -31
  394. package/src/editor/keypad.ts +43 -11
  395. package/src/editor/keys.ts +25 -5
  396. package/src/editor/laser.ts +98 -20
  397. package/src/editor/linkActions.ts +194 -0
  398. package/src/editor/linkFollow.ts +43 -0
  399. package/src/editor/linkWrite.ts +77 -0
  400. package/src/editor/links.ts +181 -0
  401. package/src/editor/markup.ts +92 -8
  402. package/src/editor/mode.ts +83 -7
  403. package/src/editor/ports.ts +37 -6
  404. package/src/editor/project.ts +155 -26
  405. package/src/editor/selection.ts +54 -8
  406. package/src/editor/status.ts +2 -0
  407. package/src/editor/tooltips.ts +47 -0
  408. package/src/editor/view3d.ts +119 -0
  409. package/src/editor/view3dState.ts +61 -0
  410. package/src/export/dxf.ts +1 -1
  411. package/src/export/pdf.ts +15 -7
  412. package/src/export/plan.ts +119 -35
  413. package/src/index.ts +5 -0
  414. package/src/input/keyboard.ts +2 -4
  415. package/src/model/constraints.ts +13 -4
  416. package/src/model/demo.ts +3 -3
  417. package/src/model/elevation.ts +6 -16
  418. package/src/model/faces.ts +153 -0
  419. package/src/model/facesolve.ts +322 -0
  420. package/src/model/history.ts +19 -11
  421. package/src/model/images.ts +35 -0
  422. package/src/model/links.ts +77 -0
  423. package/src/model/markup.ts +80 -0
  424. package/src/model/measurement.ts +26 -0
  425. package/src/model/openings.ts +6 -0
  426. package/src/model/planGeom.ts +174 -8
  427. package/src/model/project.ts +3 -2
  428. package/src/model/projectfile.ts +2 -1
  429. package/src/model/rooms.ts +1 -1
  430. package/src/model/settings.ts +8 -2
  431. package/src/model/sketch.ts +101 -14
  432. package/src/model/smooth.ts +51 -0
  433. package/src/model/store.ts +35 -4
  434. package/src/model/types.ts +170 -8
  435. package/src/model/wallfaces.ts +126 -17
  436. package/src/model/wallshape.ts +97 -0
  437. package/src/react/BottomBar.tsx +263 -46
  438. package/src/react/Buttons.tsx +50 -9
  439. package/src/react/CadEditor.tsx +133 -30
  440. package/src/react/ConfirmSheet.tsx +38 -0
  441. package/src/react/ConflictSheet.tsx +19 -12
  442. package/src/react/Drawer.tsx +213 -110
  443. package/src/react/Duck.tsx +50 -0
  444. package/src/react/Empty3D.tsx +32 -0
  445. package/src/react/FitScroll.tsx +19 -0
  446. package/src/react/Icon.native.tsx +5 -0
  447. package/src/react/Icon.web.tsx +5 -0
  448. package/src/react/KeypadSheet.tsx +253 -60
  449. package/src/react/LaserTray.tsx +12 -8
  450. package/src/react/MarkupPanel.tsx +150 -0
  451. package/src/react/PageThumb.native.tsx +18 -7
  452. package/src/react/PageThumb.web.tsx +4 -4
  453. package/src/react/PlanCanvas.native.tsx +151 -34
  454. package/src/react/PlanCanvas.web.tsx +15 -5
  455. package/src/react/Popovers.tsx +116 -51
  456. package/src/react/Rail.tsx +360 -27
  457. package/src/react/ShapeMenu.tsx +66 -0
  458. package/src/react/Sheet.tsx +56 -11
  459. package/src/react/Slider.tsx +48 -0
  460. package/src/react/Toast.tsx +16 -9
  461. package/src/react/ToolTip.tsx +49 -0
  462. package/src/react/TopBar.tsx +97 -39
  463. package/src/react/Tween.native.tsx +36 -0
  464. package/src/react/Tween.tsx +24 -0
  465. package/src/react/View3D.native.tsx +131 -0
  466. package/src/react/View3D.web.tsx +131 -0
  467. package/src/react/View3DHost.ts +367 -0
  468. package/src/react/corners.ts +52 -0
  469. package/src/react/easing.ts +14 -0
  470. package/src/react/host.ts +8 -3
  471. package/src/react/hostKeypad.ts +66 -0
  472. package/src/react/motion.tsx +239 -0
  473. package/src/react/ports.ts +78 -9
  474. package/src/react/saving.ts +80 -0
  475. package/src/react/store.ts +35 -3
  476. package/src/react/theme.ts +8 -0
  477. package/src/react/topRow.ts +71 -0
  478. package/src/react/touch.ts +10 -0
  479. package/src/render/ctx.ts +8 -0
  480. package/src/render/draw.ts +437 -175
  481. package/src/render/elevation.ts +349 -143
  482. package/src/render/figmaIcons.ts +22 -0
  483. package/src/render/hit.ts +140 -21
  484. package/src/render/icons.ts +69 -10
  485. package/src/render/linkTile.ts +297 -0
  486. package/src/render/markup.ts +91 -0
  487. package/src/render/navigate.ts +114 -0
  488. package/src/render/snap.ts +105 -22
  489. package/src/render/squircle.ts +73 -0
  490. package/src/render/view.ts +255 -19
  491. package/src/skia/SkiaDrawCtx.ts +163 -41
  492. package/src/skia/host.ts +46 -1
  493. package/src/solver/equations.ts +37 -2
  494. package/src/solver/gauss-newton.ts +125 -28
  495. package/src/solver/linalg.ts +141 -5
  496. package/src/solver/types.ts +5 -0
  497. package/src/styles.css +91 -42
  498. package/src/tools/annotate.ts +58 -29
  499. package/src/tools/chamfer.ts +103 -0
  500. package/src/tools/corner.ts +99 -0
  501. package/src/tools/dim.ts +524 -92
  502. package/src/tools/elevation.ts +1 -1
  503. package/src/tools/fillet.ts +82 -0
  504. package/src/tools/freehand.ts +20 -16
  505. package/src/tools/index.ts +17 -4
  506. package/src/tools/line.ts +59 -23
  507. package/src/tools/modify.ts +86 -24
  508. package/src/tools/openings.ts +8 -13
  509. package/src/tools/registry.ts +27 -2
  510. package/src/tools/select.ts +424 -75
  511. package/src/tools/shapes.ts +119 -58
  512. package/src/ui/ctx.ts +2 -1
  513. package/src/ui/domPorts.ts +23 -4
  514. package/src/ui/drawer.ts +6 -26
  515. package/src/ui/elevation.ts +10 -101
  516. package/src/ui/keypad.ts +8 -14
  517. package/src/ui/laser.ts +2 -2
  518. package/src/ui/menu.ts +63 -17
  519. package/src/ui/noteinput.ts +3 -3
  520. package/src/ui/opacity.ts +108 -0
  521. package/src/ui/palette.ts +67 -12
  522. package/src/ui/rail.ts +21 -7
  523. package/src/ui/topbar.ts +4 -14
  524. package/src/units/format.ts +18 -3
  525. package/src/view3d/build.ts +607 -0
  526. package/src/view3d/camera.ts +113 -0
  527. package/src/view3d/fatLines.ts +83 -0
  528. package/src/view3d/lines.ts +258 -0
  529. package/src/view3d/mesh.ts +111 -0
  530. package/src/view3d/poly.ts +104 -0
  531. package/src/view3d/viewer.ts +348 -0
  532. package/dist/react/ElevCanvas.native.d.ts +0 -3
  533. package/dist/react/ElevCanvas.native.js +0 -58
  534. package/dist/react/ElevCanvas.native.js.map +0 -1
  535. package/dist/react/ElevCanvas.web.d.ts +0 -4
  536. package/dist/react/ElevCanvas.web.js +0 -44
  537. package/dist/react/ElevCanvas.web.js.map +0 -1
  538. package/dist/react/ElevationSheet.d.ts +0 -2
  539. package/dist/react/ElevationSheet.js +0 -38
  540. package/dist/react/ElevationSheet.js.map +0 -1
  541. package/dist/render/elevationView.d.ts +0 -3
  542. package/dist/render/elevationView.js +0 -100
  543. package/dist/render/elevationView.js.map +0 -1
  544. package/src/react/ElevCanvas.native.tsx +0 -68
  545. package/src/react/ElevCanvas.web.tsx +0 -42
  546. package/src/react/ElevationSheet.tsx +0 -88
  547. package/src/render/elevationView.ts +0 -73
@@ -0,0 +1,607 @@
1
+ import { openingHeights, toU, wallHeight } from '../model/elevation';
2
+ import { faceKeyParts } from '../model/faces';
3
+ import { faceFrame, UPPER_Z } from '../model/facesolve';
4
+ import { hyp, normAng } from '../model/geom';
5
+ import { edgeS, openingSpan } from '../model/openings';
6
+ import { curveFrame, wallThick } from '../model/planGeom';
7
+ import { roomGeom } from '../model/rooms';
8
+ import { settings } from '../model/settings';
9
+ import { roomValid, withLineIndex } from '../model/sketch';
10
+ import type { Opening, Sketch, XY } from '../model/types';
11
+ import { wallPieces } from '../model/wallshape';
12
+ import { boxLines, cellLines, mergeIv, raiseIn, type Cell3 } from './lines';
13
+ import { PartBuilder, type Mat3, type Part3, type V3 } from './mesh';
14
+ import { area, clipBand, clipHalf, clipSpan, tidy } from './poly';
15
+
16
+ /*
17
+ The 3D view's model, made again from the plan whenever it changes: each wall stood up to its height with its doors
18
+ and windows cut through it, curved walls as bands, each space's floor, and the cabinets from the wall views. Plain
19
+ triangles (no three.js here), so it's tested like the rest of the model.
20
+
21
+ Walls of different heights: each goes up to its own height, and where walls meet the joint goes up to the tallest of
22
+ them — the taller wall runs on through the corner and the shorter one stops against it, as it would be framed. A
23
+ wall ending on the middle of another stops at that wall's face, and runs on over it only where it's the taller.
24
+
25
+ Cutaway: the walls between the camera and the middle of the plan drop to CUT_H, so the rooms can be seen into. A
26
+ cabinet hung above the cut goes with its wall.
27
+ */
28
+
29
+ /** How low the walls in front drop in a cutaway, inches: under most windows, over a counter. */
30
+ export const CUT_H = 36;
31
+
32
+ /** A countertop's thickness, on a base cabinet. */
33
+ const COUNTER = 1.5;
34
+
35
+ /** The walls between the camera and `c`, the middle of the plan, are cut down to `h`. */
36
+ export interface Cut3 {
37
+ /** the middle of the plan */
38
+ c: XY;
39
+ /** level, from there toward the camera (unit length) */
40
+ d: XY;
41
+ h: number;
42
+ }
43
+
44
+ export interface Scene3 {
45
+ parts: Part3[];
46
+ /** the box it all fits in, [x, y, z] */
47
+ min: V3;
48
+ max: V3;
49
+ /** what the cutaway lowered: straight walls by id, stretches of curved ones as "<id>#<n>" */
50
+ lowered: string[];
51
+ /** the outlines of the walls and what's on them, as line segments (x, y, z of each end) */
52
+ lines: number[];
53
+ }
54
+
55
+ /** Whether a stretch of wall at `m` (facing `n`, either way) stands between the camera and the plan's middle. */
56
+ export function inFront(cut: Cut3, m: XY, n: XY): boolean {
57
+ return (m.x - cut.c.x) * cut.d.x + (m.y - cut.c.y) * cut.d.y > 0 && Math.abs(n.x * cut.d.x + n.y * cut.d.y) > 0.35;
58
+ }
59
+
60
+ interface Wall3 { id: string; A: XY; B: XY; u: XY; L: number; t: number; h: number; full: number }
61
+
62
+ interface Opening3 { id: string; type: Opening['type']; s0: number; s1: number; sill: number; head: number; flip: boolean }
63
+
64
+ /** The plan's straight walls, each at its height (cut down when it's in front of a cutaway). */
65
+ function straightWalls(sk: Sketch, cut: Cut3 | null, lowered: string[]): Map<string, Wall3> {
66
+ const out = new Map<string, Wall3>();
67
+ for (const [id, l] of Object.entries(sk.lines)) {
68
+ if (l.kind !== 'wall' || l.constr) continue;
69
+ const A = sk.points[l.a], B = sk.points[l.b];
70
+ if (!A || !B) continue;
71
+ const L = hyp(B.x - A.x, B.y - A.y);
72
+ if (L < 1e-6) continue;
73
+ const u = { x: (B.x - A.x) / L, y: (B.y - A.y) / L }, full = wallHeight(l);
74
+ let h = full;
75
+ if (cut && full > cut.h && inFront(cut, { x: (A.x + B.x) / 2, y: (A.y + B.y) / 2 }, { x: -u.y, y: u.x })) {
76
+ h = cut.h;
77
+ lowered.push(id);
78
+ }
79
+ out.set(id, { id, A, B, u, L, t: l.thick, h, full });
80
+ }
81
+ return out;
82
+ }
83
+
84
+ /**
85
+ * The curved walls: each one's centre, radius, start angle and sweep, thickness, the height of each stretch, an arc's
86
+ * end points, and how an end that meets straight walls is cut (on a mitre to them, set by the joints).
87
+ */
88
+ interface Curve3 {
89
+ id: string; o: XY; r: number; t0: number; sweep: number; arc: boolean; t: number; hs: number[];
90
+ /** an arc's points at its start (t0) and its end */
91
+ pa?: string; pb?: string;
92
+ start?: Mitre3; end?: Mitre3;
93
+ }
94
+ /** An end cut on a mitre: through its end point P, from its inside corner to its outside one. */
95
+ interface Mitre3 { P: XY; inner: XY; outer: XY }
96
+
97
+ function curvedWalls(sk: Sketch, cut: Cut3 | null, lowered: string[]): Curve3[] {
98
+ const out: Curve3[] = [], full = settings.wallH;
99
+ for (const id of [...Object.keys(sk.circles), ...Object.keys(sk.arcs)]) {
100
+ const c = sk.circles[id] ?? sk.arcs[id];
101
+ if (!c || c.kind !== 'wall' || c.constr) continue;
102
+ const f = curveFrame(sk, id);
103
+ if (!f || f.r < 1e-3) continue;
104
+ const t0 = f.arc ? f.arc.t0 : 0, sweep = f.arc ? normAng(f.arc.t1 - f.arc.t0) : 2 * Math.PI;
105
+ if (sweep < 1e-6) continue;
106
+ // a stretch every 4° or so
107
+ const n = Math.max(f.arc ? 2 : 12, Math.ceil(sweep / (Math.PI / 45))), hs: number[] = [];
108
+ for (let i = 0; i < n; i++) {
109
+ const tm = t0 + (sweep * (i + 0.5)) / n, rad = { x: Math.cos(tm), y: Math.sin(tm) };
110
+ const low = !!cut && full > cut.h && inFront(cut, { x: f.o.x + rad.x * f.r, y: f.o.y + rad.y * f.r }, rad);
111
+ if (low) lowered.push(id + '#' + i);
112
+ hs.push(low ? cut!.h : full);
113
+ }
114
+ const a = f.arc ? sk.arcs[id] : null;
115
+ out.push({ id, o: f.o, r: f.r, t0, sweep, arc: !!f.arc, t: wallThick(c), hs, ...(a ? { pa: a.a, pb: a.b } : {}) });
116
+ }
117
+ return out;
118
+ }
119
+
120
+ /** Which walls a cutaway from `cut` lowers (as Scene3.lowered), without building anything: to tell when it changes. */
121
+ export function loweredBy(sk: Sketch, cut: Cut3 | null): string[] {
122
+ const lowered: string[] = [];
123
+ straightWalls(sk, cut, lowered);
124
+ curvedWalls(sk, cut, lowered);
125
+ return lowered;
126
+ }
127
+
128
+ const near = (p: XY, q: XY) => hyp(p.x - q.x, p.y - q.y) < 1e-6;
129
+ const sideways = (a: XY, b: XY) => Math.abs(a.x * b.y - a.y * b.x) > 0.02;
130
+ /** The same polygon (the same corners, whichever it starts from). */
131
+ const samePoly = (p: XY[], q: XY[]) => p.length === q.length && p.every(a => q.some(b => near(a, b)));
132
+
133
+ /** Each wall's doors and windows (as the plan lists them): asked of each wall, once for all of them. */
134
+ function openingsByWall(sk: Sketch): Map<string, [string, Opening][]> {
135
+ const out = new Map<string, [string, Opening][]>();
136
+ for (const e of Object.entries(sk.openings)) (out.get(e[1].line) ?? out.set(e[1].line, []).get(e[1].line)!).push(e);
137
+ return out;
138
+ }
139
+
140
+ /**
141
+ * The doors and windows in wall `w` (`mine`, from openingsByWall): where each runs along it and its sill and head (none
142
+ * above the wall's top).
143
+ */
144
+ function openingsIn(sk: Sketch, w: Wall3, mine: [string, Opening][]): Opening3[] {
145
+ const out: Opening3[] = [];
146
+ for (const [id, o] of mine) {
147
+ const g = openingSpan(sk, o);
148
+ if (!g) continue;
149
+ const a = edgeS(o, g.w, 's0'), b = edgeS(o, g.w, 's1');
150
+ const s0 = Math.max(0, Math.min(a, b)), s1 = Math.min(w.L, Math.max(a, b));
151
+ if (s1 - s0 < 0.01) continue;
152
+ const { sill, head } = openingHeights(o);
153
+ out.push({ id, type: o.type, s0, s1, sill: Math.min(sill, w.h), head: Math.min(head, w.h), flip: !!o.flip });
154
+ }
155
+ return out.sort((p, q) => p.s0 - q.s0);
156
+ }
157
+
158
+ /**
159
+ * Wall `w` stood up over its outline `poly` (counter-clockwise), its doors and windows cut through: its top (dark: a
160
+ * cut through the wall — not over an opening that runs up to it), its ends, its two long faces with a hole for each
161
+ * opening, and each opening's sides, sill and head (the wall's own faces). Where an opening's edge meets a face, both
162
+ * have a corner there, so they join without a seam.
163
+ */
164
+ function standWall(b: PartBuilder, w: Wall3, poly: XY[], ops: Opening3[]): void {
165
+ const holes = ops.filter(o => o.head - o.sill > 0.01);
166
+ let from = -1e9;
167
+ for (const [s0, s1] of [...holes.filter(o => o.head >= w.h - 1e-6).map(o => [o.s0, o.s1]), [1e9, 1e9]]) {
168
+ if (s0 > from + 1e-6) {
169
+ const top = tidy(clipSpan(poly, w.A, w.u, from, s0));
170
+ if (top.length >= 3 && area(top) > 1e-6) b.cap('top', top, w.h);
171
+ }
172
+ from = Math.max(from, s1);
173
+ }
174
+ const nx = -w.u.y, ny = w.u.x, off = (p: XY) => (p.x - w.A.x) * nx + (p.y - w.A.y) * ny;
175
+ const sOf = (p: XY) => (p.x - w.A.x) * w.u.x + (p.y - w.A.y) * w.u.y;
176
+ /** each long face: the point on it at s along the wall */
177
+ const faces: ((s: number) => XY)[] = [];
178
+ for (let i = 0; i < poly.length; i++) {
179
+ const pa = poly[i], pb = poly[(i + 1) % poly.length], dx = pb.x - pa.x, dy = pb.y - pa.y, L = Math.hypot(dx, dy);
180
+ if (L < 1e-9) continue;
181
+ const out: V3 = [dy / L, -dx / L, 0], k = off(pa);
182
+ const long = holes.length > 0 && Math.abs(dx * w.u.y - dy * w.u.x) < 1e-6 * L && Math.abs(Math.abs(k) - w.t / 2) < 1e-6 && Math.abs(off(pb) - k) < 1e-6;
183
+ if (!long) { b.quad('wall', [pa.x, pa.y, 0], [pb.x, pb.y, 0], [pb.x, pb.y, w.h], [pa.x, pa.y, w.h], out); continue; }
184
+ const sa = sOf(pa), sb = sOf(pb);
185
+ const at = (s: number): XY => (s === sa ? pa : s === sb ? pb : { x: pa.x + dx * ((s - sa) / (sb - sa)), y: pa.y + dy * ((s - sa) / (sb - sa)) });
186
+ faces.push(at);
187
+ longFace(b, w.h, Math.min(sa, sb), Math.max(sa, sb), holes, at, out);
188
+ }
189
+ if (faces.length !== 2) return;
190
+ const [f, g] = faces, P = (face: (s: number) => XY, s: number, z: number): V3 => { const p = face(s); return [p.x, p.y, z]; };
191
+ for (const o of holes) {
192
+ // its sides face into it; under a window its sill, over it (and over a door) its head
193
+ b.quad('wall', P(f, o.s0, o.sill), P(g, o.s0, o.sill), P(g, o.s0, o.head), P(f, o.s0, o.head), [w.u.x, w.u.y, 0]);
194
+ b.quad('wall', P(f, o.s1, o.sill), P(g, o.s1, o.sill), P(g, o.s1, o.head), P(f, o.s1, o.head), [-w.u.x, -w.u.y, 0]);
195
+ if (o.sill > 0.01) b.quad('wall', P(f, o.s0, o.sill), P(g, o.s0, o.sill), P(g, o.s1, o.sill), P(f, o.s1, o.sill), [0, 0, 1]);
196
+ if (o.head < w.h - 0.01) b.quad('wall', P(f, o.s0, o.head), P(g, o.s0, o.head), P(g, o.s1, o.head), P(f, o.s1, o.head), [0, 0, -1]);
197
+ }
198
+ }
199
+
200
+ /**
201
+ * One long face of a wall, lo to hi along it (`at`: the point on it at s), with a hole for each opening: strips
202
+ * between the openings' edges — full height, or under a sill and over a head — each with a corner wherever the strip
203
+ * beside it, or the opening's own side, has one on the edge they share.
204
+ */
205
+ function longFace(b: PartBuilder, h: number, lo: number, hi: number, holes: Opening3[], at: (s: number) => XY, out: V3): void {
206
+ const hs = holes.filter(o => o.s1 > lo + 1e-6 && o.s0 < hi - 1e-6);
207
+ const cuts = [...new Set([lo, hi, ...hs.flatMap(o => [Math.max(lo, o.s0), Math.min(hi, o.s1)])])].sort((p, q) => p - q);
208
+ /** the heights the edge at s has corners at: the sill and head of an opening whose side it is */
209
+ const edgeZ = (s: number) => hs.filter(o => o.s0 === s || o.s1 === s).flatMap(o => [o.sill, o.head]);
210
+ const P = (s: number, z: number): V3 => { const p = at(s); return [p.x, p.y, z]; };
211
+ for (let i = 0; i + 1 < cuts.length; i++) {
212
+ const p = cuts[i], q = cuts[i + 1];
213
+ if (q - p < 1e-6) continue;
214
+ const o = hs.find(o => o.s0 <= p + 1e-6 && o.s1 >= q - 1e-6);
215
+ for (const [z0, z1] of o ? [[0, o.sill], [o.head, h]] : [[0, h]]) {
216
+ if (z1 - z0 < 1e-6) continue;
217
+ const within = (z: number) => z > z0 + 1e-6 && z < z1 - 1e-6;
218
+ const up = [...new Set(edgeZ(q).filter(within))].sort((a, c) => a - c), down = [...new Set(edgeZ(p).filter(within))].sort((a, c) => c - a);
219
+ b.fan('wall', [P(p, z0), P(q, z0), ...up.map(z => P(q, z)), P(q, z1), P(p, z1), ...down.map(z => P(p, z))], out);
220
+ }
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Wall `w` seen from above for its outlines: its outline cut across at its openings' sides, each part solid as the
226
+ * wall is there (under a sill and over a head, at an opening).
227
+ */
228
+ function wallCells(w: Wall3, poly: XY[], ops: Opening3[]): Cell3[] {
229
+ const cuts = [...new Set(ops.flatMap(o => [o.s0, o.s1]))].sort((p, q) => p - q), at = [-1e9, ...cuts, 1e9], out: Cell3[] = [];
230
+ for (let i = 0; i + 1 < at.length; i++) {
231
+ const s0 = at[i], s1 = at[i + 1];
232
+ if (s1 - s0 < 1e-6) continue;
233
+ const slice = tidy(clipSpan(poly, w.A, w.u, s0, s1));
234
+ if (slice.length < 3 || area(slice) < 1e-6) continue;
235
+ const o = ops.find(o => o.s0 <= s0 + 1e-6 && o.s1 >= s1 - 1e-6);
236
+ out.push({ poly: slice, iv: mergeIv(o ? [[0, o.sill], [o.head, w.h]] : [[0, w.h]]) });
237
+ }
238
+ return out;
239
+ }
240
+
241
+ /** A door's frame, inches: how thick its lining is, how wide and deep its casings are, how thick its leaf, how far it's open. */
242
+ const FRAME = 0.75, CASING = 2.5, CASING_OUT = 0.625, LEAF = 1.375, OPEN = (30 * Math.PI) / 180;
243
+
244
+ /**
245
+ * A door, so it reads as one: the floor run on through the doorway, a frame lining its sides and head, casings round
246
+ * it on both faces (as high as the wall goes), and its leaf — hinged at its first side on the face it swings to, as the
247
+ * plan draws it, and open a little. Its outlines go on `lines`.
248
+ */
249
+ function doorPart(w: Wall3, o: Opening3, lines: number[]): PartBuilder {
250
+ const b = new PartBuilder('opening', o.id), half = w.t / 2, head = Math.min(o.head, w.h), nx = -w.u.y, ny = w.u.x;
251
+ const P = (s: number, k: number): XY => ({ x: w.A.x + w.u.x * s + nx * k, y: w.A.y + w.u.y * s + ny * k });
252
+ const rect = (s0: number, s1: number, k0: number, k1: number) => [P(s0, k0), P(s1, k0), P(s1, k1), P(s0, k1)];
253
+ const box = (poly: XY[], z0: number, z1: number, mat: Mat3) => {
254
+ const pp = tidy(poly);
255
+ if (z1 - z0 < 1e-3 || pp.length < 3 || area(pp) < 1e-6) return;
256
+ b.prism(pp, z0, z1, mat, mat);
257
+ for (const v of boxLines(pp, z0, z1)) lines.push(v);
258
+ };
259
+ b.cap('floor', tidy(rect(o.s0, o.s1, -half, half)), 0);
260
+ box(rect(o.s0, o.s0 + FRAME, -half, half), 0, head, 'frame');
261
+ box(rect(o.s1 - FRAME, o.s1, -half, half), 0, head, 'frame');
262
+ box(rect(o.s0 + FRAME, o.s1 - FRAME, -half, half), head - FRAME, head, 'frame');
263
+ const top = Math.min(head + CASING, w.h);
264
+ for (const k of [1, -1]) {
265
+ const k0 = k * half, k1 = k * (half + CASING_OUT);
266
+ box(rect(o.s0 - CASING, o.s0, k0, k1), 0, top, 'frame');
267
+ box(rect(o.s1, o.s1 + CASING, k0, k1), 0, top, 'frame');
268
+ box(rect(o.s0, o.s1, k0, k1), head, top, 'frame');
269
+ }
270
+ const f = o.flip ? -1 : 1, W = o.s1 - o.s0 - 2 * FRAME, H = head - FRAME - 0.5;
271
+ if (W > 2 && H > 2) {
272
+ // (turned about its hinge, at the frame on the face it swings to, toward that face)
273
+ const hs = o.s0 + FRAME, hk = f * half, c = Math.cos(f * OPEN), sn = Math.sin(f * OPEN);
274
+ const R = (s: number, k: number): XY => P(hs + (s - hs) * c - (k - hk) * sn, hk + (s - hs) * sn + (k - hk) * c);
275
+ box([R(hs, hk), R(hs + W, hk), R(hs + W, hk - f * LEAF), R(hs, hk - f * LEAF)], 0.5, 0.5 + H, 'door');
276
+ }
277
+ return b;
278
+ }
279
+
280
+ /** A window's glass on its wall's centreline, or a doorway's (unseen) face to tap. */
281
+ function openingPart(w: Wall3, o: Opening3): Part3 | null {
282
+ const p = new PartBuilder('opening', o.id), n: V3 = [-w.u.y, w.u.x, 0];
283
+ const a = { x: w.A.x + w.u.x * o.s0, y: w.A.y + w.u.y * o.s0 }, c = { x: w.A.x + w.u.x * o.s1, y: w.A.y + w.u.y * o.s1 };
284
+ const z0 = o.type === 'window' ? o.sill : 0, z1 = o.head;
285
+ if (z1 - z0 < 0.01) return null;
286
+ p.quad(o.type === 'window' ? 'glass' : 'pick', [a.x, a.y, z0], [c.x, c.y, z0], [c.x, c.y, z1], [a.x, a.y, z1], n);
287
+ return p;
288
+ }
289
+
290
+ /** What an edge of a curved wall's stretch is: across it at its start or end, or along its outside or inside face. */
291
+ type Edge3 = 'a' | 'e' | 'out' | 'in';
292
+ /** One stretch of a curved wall seen from above (counter-clockwise), what each of its edges is, and its height. */
293
+ interface Seg3 { pts: XY[]; edges: Edge3[]; h: number }
294
+
295
+ /** A curved wall's stretches: each from its inside to its outside face, the first and last cut on a mitre where straight walls meet them. */
296
+ function curveSegments(c: Curve3): Seg3[] {
297
+ const n = c.hs.length, ri = Math.max(0.05, c.r - c.t / 2), ro = c.r + c.t / 2, out: Seg3[] = [];
298
+ const P = (r: number, th: number): XY => ({ x: c.o.x + Math.cos(th) * r, y: c.o.y + Math.sin(th) * r });
299
+ for (let i = 0; i < n; i++) {
300
+ const a = c.t0 + (c.sweep * i) / n, e = c.t0 + (c.sweep * (i + 1)) / n, pts: XY[] = [], edges: Edge3[] = [];
301
+ // (each point with what the edge from it to the next one is)
302
+ const go = (p: XY, edge: Edge3) => { pts.push(p); edges.push(edge); };
303
+ if (i === 0 && c.start) { go(c.start.inner, 'a'); go(c.start.P, 'a'); go(c.start.outer, 'out'); } else { go(P(ri, a), 'a'); go(P(ro, a), 'out'); }
304
+ if (i === n - 1 && c.end) { go(c.end.outer, 'e'); go(c.end.P, 'e'); go(c.end.inner, 'in'); } else { go(P(ro, e), 'e'); go(P(ri, e), 'in'); }
305
+ out.push({ pts, edges, h: c.hs[i] });
306
+ }
307
+ return out;
308
+ }
309
+
310
+ /**
311
+ * A curved wall from its stretches: their tops, their faces inside and out (shaded round), and across them where one
312
+ * steps down to the next (cut away) and at an arc's ends.
313
+ */
314
+ function standCurve(c: Curve3, segs: Seg3[]): PartBuilder {
315
+ const b = new PartBuilder('curve', c.id), n = segs.length;
316
+ const radial = (p: XY, k: number): V3 => { const dx = p.x - c.o.x, dy = p.y - c.o.y, L = Math.hypot(dx, dy) || 1; return [(dx / L) * k, (dy / L) * k, 0]; };
317
+ segs.forEach((g, i) => {
318
+ const h = g.h, prev = i > 0 ? segs[i - 1].h : c.arc ? 0 : segs[n - 1].h, next = i < n - 1 ? segs[i + 1].h : c.arc ? 0 : segs[0].h;
319
+ b.cap('top', g.pts, h);
320
+ for (let k = 0; k < g.pts.length; k++) {
321
+ const p = g.pts[k], q = g.pts[(k + 1) % g.pts.length], edge = g.edges[k], dx = q.x - p.x, dy = q.y - p.y, L = Math.hypot(dx, dy);
322
+ if (L < 1e-9) continue;
323
+ const flat: V3 = [dy / L, -dx / L, 0];
324
+ if (edge === 'out' || edge === 'in') {
325
+ const kk = edge === 'out' ? 1 : -1;
326
+ b.quad('wall', [p.x, p.y, 0], [q.x, q.y, 0], [q.x, q.y, h], [p.x, p.y, h], flat, [radial(p, kk), radial(q, kk), radial(q, kk), radial(p, kk)]);
327
+ continue;
328
+ }
329
+ const below = edge === 'a' ? prev : next;
330
+ if (h > below + 1e-6) b.quad('wall', [p.x, p.y, below], [q.x, q.y, below], [q.x, q.y, h], [p.x, p.y, h], flat);
331
+ }
332
+ });
333
+ return b;
334
+ }
335
+
336
+ /** Where two lines (a point and a direction each) cross; for parallel ones, the middle of the points if they're one line. */
337
+ function cross(p1: XY, d1: XY, p2: XY, d2: XY): XY | null {
338
+ const c = d1.x * d2.y - d1.y * d2.x;
339
+ if (Math.abs(c) < 1e-9) return Math.abs((p2.x - p1.x) * d1.y - (p2.y - p1.y) * d1.x) < 1e-6 ? { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 } : null;
340
+ const l = ((p2.x - p1.x) * d2.y - (p2.y - p1.y) * d2.x) / c;
341
+ return { x: p1.x + d1.x * l, y: p1.y + d1.y * l };
342
+ }
343
+
344
+ /** What meets at a joint: a wall's end (`key`), the way it runs away from the joint, and how thick it is. */
345
+ interface End3 { key: string; u: XY; t: number }
346
+
347
+ /**
348
+ * Where the sides of what meets at P cross, as model/wallshape.ts works it out for straight walls: each end's corners,
349
+ * left and right as seen going away from P; and a bevel where two meet so sharply the corner would run far out.
350
+ */
351
+ function jointCorners(P: XY, list: End3[]): { corners: Map<string, { l: XY; r: XY }>; bevels: { pts: XY[]; keys: [string, string] }[] } {
352
+ const at = (p: XY, n: XY, k: number): XY => ({ x: p.x + n.x * k, y: p.y + n.y * k });
353
+ const sorted = [...list].sort((p, q) => Math.atan2(p.u.y, p.u.x) - Math.atan2(q.u.y, q.u.x)), n = sorted.length;
354
+ const ccw = new Map<number, XY>(), cw = new Map<number, XY>(), bevels: { pts: XY[]; keys: [string, string] }[] = [];
355
+ for (let i = 0; i < n; i++) {
356
+ const a = sorted[i], b = sorted[(i + 1) % n];
357
+ const ea = at(P, { x: -a.u.y, y: a.u.x }, a.t / 2), eb = at(P, { x: b.u.y, y: -b.u.x }, b.t / 2);
358
+ const c = cross(ea, a.u, eb, b.u);
359
+ if (c && hyp(c.x - P.x, c.y - P.y) <= 2 * Math.max(a.t, b.t)) { ccw.set(i, c); cw.set((i + 1) % n, c); continue; }
360
+ ccw.set(i, ea); cw.set((i + 1) % n, eb);
361
+ if (hyp(ea.x - eb.x, ea.y - eb.y) > 1e-6) bevels.push({ pts: [P, ea, eb], keys: [a.key, b.key] });
362
+ }
363
+ const corners = new Map<string, { l: XY; r: XY }>();
364
+ sorted.forEach((e, i) => corners.set(e.key, { l: ccw.get(i)!, r: cw.get(i)! }));
365
+ return { corners, bevels };
366
+ }
367
+
368
+ /**
369
+ * A straight wall's outline as model/wallshape.ts gives it, read back into its ends' corners (left and right as seen
370
+ * going away from each end) and whether each end runs to its point (a joint).
371
+ */
372
+ function outlineEnds(pts: XY[], jointA: boolean, jointB: boolean): { a: { l: XY; r: XY; joint: boolean }; b: { l: XY; r: XY; joint: boolean } } {
373
+ const k = jointB ? 3 : 2;
374
+ return { a: { r: pts[0], l: pts[k + 1], joint: jointA }, b: { l: pts[1], r: pts[k], joint: jointB } };
375
+ }
376
+
377
+ export interface Build3Opts {
378
+ /** cut away the walls in front (null: every wall at its full height) */
379
+ cut?: Cut3 | null;
380
+ }
381
+
382
+ /**
383
+ * The 3D view of plan `sk`. (With the plan's lines indexed for it: each space asks which lines join its corners, and
384
+ * scanning every line for each made the build cost the square of the plan.)
385
+ */
386
+ export function buildScene(sk: Sketch, opts: Build3Opts = {}): Scene3 {
387
+ return withLineIndex(sk, () => build(sk, opts));
388
+ }
389
+
390
+ function build(sk: Sketch, opts: Build3Opts): Scene3 {
391
+ const cut = opts.cut ?? null, lowered: string[] = [], parts: Part3[] = [];
392
+ const walls = straightWalls(sk, cut, lowered), curves = curvedWalls(sk, cut, lowered);
393
+ const wallParts = new Map<string, PartBuilder>();
394
+ const partOf = (id: string) => {
395
+ let b = wallParts.get(id);
396
+ if (!b) wallParts.set(id, (b = new PartBuilder('wall', id)));
397
+ return b;
398
+ };
399
+
400
+ // what meets at each point: the straight walls with an end there, and whether a curved one ends there too
401
+ const ends = new Map<string, string[]>(), curvedEnd = new Set<string>();
402
+ for (const [id, l] of Object.entries(sk.lines)) {
403
+ if (!walls.has(id)) continue;
404
+ for (const p of [l.a, l.b]) (ends.get(p) ?? ends.set(p, []).get(p)!).push(id);
405
+ }
406
+ for (const a of Object.values(sk.arcs)) if (a.kind === 'wall' && !a.constr) { curvedEnd.add(a.a); curvedEnd.add(a.b); }
407
+
408
+ const pieces = wallPieces(sk, w => !w.constr), outline = new Map<string, XY[]>();
409
+ for (const pc of pieces) if (pc.id && walls.has(pc.id)) outline.set(pc.id, tidy(pc.pts));
410
+
411
+ // where straight walls meet a curved wall's end: cut on a mitre, as two straight walls meet (the curve taken as the
412
+ // line it leaves its end along) — not run on into it, which left slivers and a cluster of edges
413
+ const curveEnds = new Map<string, { c: Curve3; start: boolean }[]>();
414
+ for (const c of curves) for (const [pid, start] of [[c.pa, true], [c.pb, false]] as const) if (pid) (curveEnds.get(pid) ?? curveEnds.set(pid, []).get(pid)!).push({ c, start });
415
+ const jointAt = (pid: string) => (ends.get(pid)?.length ?? 0) > 1 && !curvedEnd.has(pid);
416
+ type JointEnd = End3 & { h: number; wall?: Wall3; curve?: Curve3; start?: boolean };
417
+ const curveJoints: { P: XY; list: JointEnd[] }[] = [];
418
+ const curveBevels: { pts: XY[]; owner: string; curve: boolean; h: number }[] = [];
419
+ for (const [pid, onCurve] of curveEnds) {
420
+ const P = sk.points[pid], straight = (ends.get(pid) ?? []).map(id => walls.get(id)!);
421
+ if (!P || straight.length + onCurve.length < 2) continue;
422
+ const list: JointEnd[] = [
423
+ ...straight.map(w => ({ key: w.id, u: near(w.A, P) ? w.u : { x: -w.u.x, y: -w.u.y }, t: w.t, h: w.h, wall: w })),
424
+ ...onCurve.map(({ c, start }) => {
425
+ const th = start ? c.t0 : c.t0 + c.sweep;
426
+ return { key: c.id + (start ? '>' : '<'), u: start ? { x: -Math.sin(th), y: Math.cos(th) } : { x: Math.sin(th), y: -Math.cos(th) }, t: c.t, h: c.hs[start ? 0 : c.hs.length - 1], curve: c, start };
427
+ }),
428
+ ];
429
+ // (a straight wall running on smoothly into the curve already meets its square end)
430
+ if (list.length === 2 && Math.abs(list[0].u.x * list[1].u.y - list[0].u.y * list[1].u.x) < 0.02 && list[0].u.x * list[1].u.x + list[0].u.y * list[1].u.y < 0) continue;
431
+ const { corners, bevels } = jointCorners(P, list);
432
+ for (const w of straight) {
433
+ const l = sk.lines[w.id], pc = pieces.find(q => q.id === w.id);
434
+ if (!pc) continue;
435
+ const e = outlineEnds(pc.pts, jointAt(l.a), jointAt(l.b)), k = corners.get(w.id)!;
436
+ if (l.a === pid) e.a = { ...k, joint: true }; else e.b = { ...k, joint: true };
437
+ pc.pts = [e.a.r, e.b.l, ...(e.b.joint ? [w.B] : []), e.b.r, e.a.l, ...(e.a.joint ? [w.A] : [])];
438
+ outline.set(w.id, tidy(pc.pts));
439
+ }
440
+ for (const { c, start } of onCurve) {
441
+ const k = corners.get(c.id + (start ? '>' : '<'))!;
442
+ // (going away from its start along the curve, its left is the inside; away from its end, the outside)
443
+ if (start) c.start = { P, inner: k.l, outer: k.r }; else c.end = { P, inner: k.r, outer: k.l };
444
+ }
445
+ for (const bv of bevels) {
446
+ const owner = list.find(x => x.key === bv.keys[0] && x.wall) ?? list.find(x => x.key === bv.keys[1] && x.wall) ?? list.find(x => x.key === bv.keys[0])!;
447
+ curveBevels.push({ pts: bv.pts, owner: owner.wall ? owner.wall.id : owner.curve!.id, curve: !owner.wall, h: Math.max(...list.filter(x => bv.keys.includes(x.key)).map(x => x.h)) });
448
+ }
449
+ curveJoints.push({ P, list });
450
+ }
451
+
452
+ // a wall ending on the middle of another (a T) stops at that wall's face; where it's the taller, it runs on over it
453
+ const over: { stub: Wall3; at: Wall3; P: XY }[] = [];
454
+ for (const [pid, ids] of ends) {
455
+ if (ids.length !== 1 || curvedEnd.has(pid)) continue;
456
+ const P = sk.points[pid], stub = walls.get(ids[0])!;
457
+ for (const w of walls.values()) {
458
+ if (w.id === stub.id || !sideways(stub.u, w.u)) continue;
459
+ const s = (P.x - w.A.x) * w.u.x + (P.y - w.A.y) * w.u.y, off = (P.x - w.A.x) * -w.u.y + (P.y - w.A.y) * w.u.x;
460
+ if (s <= 0.5 || s >= w.L - 0.5 || Math.abs(off) > Math.max(0.5, w.t / 4)) continue;
461
+ const poly = outline.get(stub.id);
462
+ if (poly) {
463
+ // keep the stub's side of the wall's near face
464
+ const far = near(stub.A, P) ? stub.B : stub.A, nx = -w.u.y, ny = w.u.x;
465
+ const k = Math.sign((far.x - P.x) * nx + (far.y - P.y) * ny) || 1;
466
+ const trimmed = tidy(clipHalf(poly, -k * nx, -k * ny, -k * (nx * P.x + ny * P.y) - w.t / 2));
467
+ if (trimmed.length >= 3) outline.set(stub.id, trimmed);
468
+ }
469
+ if (stub.h > w.h + 1e-6) over.push({ stub, at: w, P });
470
+ break;
471
+ }
472
+ }
473
+
474
+ // the walls, with their doors and windows; each wall's cells for the outlines, and the lines round its windows' glass
475
+ const cellsOf = new Map<string, Cell3[]>(), lines: number[] = [], byWall = openingsByWall(sk);
476
+ const raise = (id: string, o: XY, u: XY, half: number, top: number) => cellsOf.set(id, (cellsOf.get(id) ?? []).flatMap(c => raiseIn(c, o, u, half, top)));
477
+ for (const w of walls.values()) {
478
+ const poly = outline.get(w.id);
479
+ if (!poly) continue;
480
+ const ops = openingsIn(sk, w, byWall.get(w.id) ?? []);
481
+ standWall(partOf(w.id), w, poly, ops);
482
+ cellsOf.set(w.id, wallCells(w, poly, ops));
483
+ for (const o of ops) {
484
+ const p = openingPart(w, o);
485
+ if (p) parts.push(p);
486
+ if (o.type === 'door' && o.head > 1) parts.push(doorPart(w, o, lines));
487
+ if (o.type === 'window' && o.head - o.sill > 0.01) {
488
+ const a = { x: w.A.x + w.u.x * o.s0, y: w.A.y + w.u.y * o.s0 }, c = { x: w.A.x + w.u.x * o.s1, y: w.A.y + w.u.y * o.s1 };
489
+ lines.push(a.x, a.y, o.sill, c.x, c.y, o.sill, c.x, c.y, o.sill, c.x, c.y, o.head, c.x, c.y, o.head, a.x, a.y, o.head, a.x, a.y, o.head, a.x, a.y, o.sill);
490
+ }
491
+ }
492
+ }
493
+ // a sharp corner's bevel: as tall as the tallest wall there
494
+ for (const pc of pieces) {
495
+ if (pc.id || !walls.has(pc.of)) continue;
496
+ const P = pc.pts[0], h = Math.max(...[...walls.values()].filter(w => near(w.A, P) || near(w.B, P)).map(w => w.h), walls.get(pc.of)!.h);
497
+ partOf(pc.of).prism(tidy(pc.pts), 0, h, 'wall', 'top');
498
+ cellsOf.get(pc.of)?.push({ poly: tidy(pc.pts), iv: [[0, h]] });
499
+ }
500
+ // where walls meet, the joint goes up to the tallest of them: a shorter wall's share of the corner, inside the
501
+ // taller one's thickness, rises with the taller (walls in line just step)
502
+ for (const [pid, ids] of ends) {
503
+ if (ids.length < 2 || curvedEnd.has(pid)) continue;
504
+ const P = sk.points[pid];
505
+ for (const lo of ids) {
506
+ const a = walls.get(lo)!, poly = outline.get(lo);
507
+ if (!poly) continue;
508
+ // the tallest first; two walls in line through the joint share one band, so it rises once, with the taller
509
+ const taller = ids.map(id => walls.get(id)!).filter(b => b.h > a.h + 1e-6 && sideways(a.u, b.u)).sort((p, q) => q.h - p.h);
510
+ const raised: XY[][] = [];
511
+ for (const b of taller) {
512
+ raise(lo, P, b.u, b.t / 2, b.h);
513
+ const share = tidy(clipBand(poly, P, b.u, b.t / 2));
514
+ if (share.length < 3 || area(share) <= 1e-4 || raised.some(r => samePoly(r, share))) continue;
515
+ raised.push(share);
516
+ partOf(b.id).prism(share, a.h, b.h, 'wall', 'top');
517
+ }
518
+ }
519
+ }
520
+ for (const { stub, at: w, P } of over) {
521
+ // across the whole thickness of the wall it meets, as wide as the stub
522
+ const R = 4 * (stub.t + w.t), sq = [{ x: P.x - R, y: P.y - R }, { x: P.x + R, y: P.y - R }, { x: P.x + R, y: P.y + R }, { x: P.x - R, y: P.y + R }];
523
+ const cross = tidy(clipBand(clipBand(sq, P, stub.u, stub.t / 2), P, w.u, w.t / 2));
524
+ if (cross.length >= 3) partOf(stub.id).prism(cross, w.h, stub.h, 'wall', 'top');
525
+ raise(w.id, P, stub.u, stub.t / 2, stub.h);
526
+ }
527
+ // the curved walls, by their stretches
528
+ const curveParts = new Map<string, PartBuilder>(), segsOf = new Map<string, Seg3[]>();
529
+ for (const c of curves) {
530
+ const segs = curveSegments(c);
531
+ segsOf.set(c.id, segs);
532
+ curveParts.set(c.id, standCurve(c, segs));
533
+ cellsOf.set(c.id, segs.map(g => ({ poly: g.pts, iv: [[0, g.h]] as [number, number][] })));
534
+ }
535
+ // a sharp joint's bevel at a curved wall's end, as tall as the taller of the two
536
+ for (const bv of curveBevels) {
537
+ const poly = tidy(bv.pts);
538
+ (bv.curve ? curveParts.get(bv.owner)! : partOf(bv.owner)).prism(poly, 0, bv.h, 'wall', 'top');
539
+ cellsOf.get(bv.owner)?.push({ poly, iv: [[0, bv.h]] });
540
+ }
541
+ // and where they meet straight walls of another height: the joint goes up to the taller, as at any joint
542
+ for (const { P, list } of curveJoints) {
543
+ for (const lo of list) {
544
+ const own = lo.wall ? outline.get(lo.wall.id) : segsOf.get(lo.curve!.id)?.[lo.start ? 0 : segsOf.get(lo.curve!.id)!.length - 1].pts;
545
+ if (!own) continue;
546
+ for (const hi of list) {
547
+ if (hi === lo || hi.h <= lo.h + 1e-6 || !sideways(lo.u, hi.u)) continue;
548
+ const share = tidy(clipBand(own, P, hi.u, hi.t / 2));
549
+ if (share.length < 3 || area(share) <= 1e-4) continue;
550
+ (hi.wall ? partOf(hi.wall.id) : curveParts.get(hi.curve!.id)!).prism(share, lo.h, hi.h, 'wall', 'top');
551
+ raise(lo.wall ? lo.wall.id : lo.curve!.id, P, hi.u, hi.t / 2, hi.h);
552
+ }
553
+ }
554
+ }
555
+ for (const b of wallParts.values()) if (!b.empty) parts.unshift(b);
556
+ for (const b of curveParts.values()) parts.push(b);
557
+ for (const v of cellLines([...cellsOf.values()].flat())) lines.push(v);
558
+
559
+ // each space's floor, to the inside faces of its walls
560
+ for (const [rid, r] of Object.entries(sk.rooms)) {
561
+ if (!roomValid(sk, r)) continue;
562
+ const poly = tidy(roomGeom(sk, r).inner);
563
+ if (poly.length < 3 || area(poly) < 1) continue;
564
+ const b = new PartBuilder('floor', rid);
565
+ b.cap('floor', poly, 0);
566
+ if (!b.empty) parts.push(b);
567
+ }
568
+
569
+ // the cabinets on each wall face: out from the face by their depth; a base cabinet's top is its counter
570
+ for (const [key, face] of Object.entries(sk.faces ?? {})) {
571
+ if (!face.cabs || !Object.keys(face.cabs).length) continue;
572
+ const { lid, side } = faceKeyParts(key), f = faceFrame(sk, lid, side), w = walls.get(lid);
573
+ if (!f || !w) continue;
574
+ const nx = -f.uy * f.side, ny = f.ux * f.side;
575
+ const P = (s: number, k: number): XY => ({ x: f.A.x + f.ux * s + nx * k, y: f.A.y + f.uy * s + ny * k });
576
+ for (const [cid, cb] of Object.entries(face.cabs)) {
577
+ const ps = [cb.bl, cb.br, cb.tr, cb.tl].map(p => face.points[p]);
578
+ if (ps.some(p => !p)) continue;
579
+ const sa = toU(f, Math.min(...ps.map(p => p.x))), sb = toU(f, Math.max(...ps.map(p => p.x)));
580
+ const s0 = Math.min(sa, sb), s1 = Math.max(sa, sb), z0 = Math.max(0, Math.min(...ps.map(p => p.y))), z1 = Math.max(...ps.map(p => p.y));
581
+ if (s1 - s0 < 0.1 || z1 - z0 < 0.1) continue;
582
+ // (hung above a wall that's cut away: it goes with the wall)
583
+ if (w.h < w.full && z0 >= w.h - 1e-6) continue;
584
+ const poly = tidy([P(s0, f.t / 2), P(s1, f.t / 2), P(s1, f.t / 2 + cb.depth), P(s0, f.t / 2 + cb.depth)]);
585
+ const b = new PartBuilder('cab', key + '#' + cid), base = z0 < UPPER_Z && z1 - z0 > 4 * COUNTER;
586
+ if (base) {
587
+ b.prism(poly, z0, z1 - COUNTER, 'cab', null);
588
+ b.prism(poly, z1 - COUNTER, z1, 'counter', 'counter', null);
589
+ } else b.prism(poly, z0, z1, 'cab', 'cab');
590
+ parts.push(b);
591
+ for (const v of boxLines(poly, z0, z1, base ? [z1 - COUNTER] : [])) lines.push(v);
592
+ }
593
+ }
594
+
595
+ const min: V3 = [Infinity, Infinity, Infinity], max: V3 = [-Infinity, -Infinity, -Infinity];
596
+ for (const p of parts) {
597
+ for (let i = 0; i < p.pos.length; i += 3) {
598
+ for (let k = 0; k < 3; k++) {
599
+ const v = p.pos[i + k];
600
+ if (v < min[k]) min[k] = v;
601
+ if (v > max[k]) max[k] = v;
602
+ }
603
+ }
604
+ }
605
+ if (!isFinite(min[0])) return { parts, min: [0, 0, 0], max: [0, 0, 0], lowered, lines };
606
+ return { parts, min, max, lowered, lines };
607
+ }