@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
package/src/export/pdf.ts CHANGED
@@ -103,7 +103,7 @@ class Recorder implements Plan {
103
103
  else if (p.t === 'arc') for (const q of arcPoints(p.c, p.r, p.s, p.e, 16)) add(q.x, q.y);
104
104
  else if (p.t === 'text') add(p.p.x, p.p.y, p.h * Math.max(1, p.s.length * 0.3));
105
105
  else if (p.t === 'fill') for (const q of p.pts) add(q.x, q.y);
106
- else if (p.shape.kind === 'line') { add(p.shape.a.x, p.shape.a.y, p.thick / 2); add(p.shape.b.x, p.shape.b.y, p.thick / 2); }
106
+ else if (p.shape.kind === 'polys') for (const q of p.shape.polys.flat()) add(q.x, q.y);
107
107
  else add(p.shape.c.x, p.shape.c.y, p.shape.r + p.thick / 2);
108
108
  }
109
109
  return x0 <= x1 ? { x0, y0, x1, y1 } : null;
@@ -145,8 +145,12 @@ const STYLE: Record<string, { w: number; rgb: [number, number, number]; dash?: s
145
145
  SPACES: { w: 0.4, rgb: [0.1, 0.1, 0.1] },
146
146
  DIMENSIONS: { w: 0.3, rgb: [0.2, 0.2, 0.2] },
147
147
  MARKUP: { w: 0.8, rgb: [0.85, 0.12, 0.1] },
148
+ // (the palette's blue, as a new link's leader is)
149
+ LINKS: { w: 0.6, rgb: [0.012, 0.392, 0.957] },
150
+ CABINETS: { w: 0.45, rgb: [0.35, 0.35, 0.35] },
151
+ CABINETS_UPPER: { w: 0.45, rgb: [0.35, 0.35, 0.35], dash: '[4 2.5] 0' },
148
152
  };
149
- const FILL: Record<string, [number, number, number]> = { SPACES: [0.94, 0.95, 0.97], KNOCKOUT: [1, 1, 1] };
153
+ const FILL: Record<string, [number, number, number]> = { SPACES: [0.94, 0.95, 0.97], KNOCKOUT: [1, 1, 1], CABINETS: [0.88, 0.88, 0.88] };
150
154
  const WALL_RGB: [number, number, number] = [0.22, 0.22, 0.24];
151
155
  const rgb = (c: [number, number, number]) => c.map(f).join(' ');
152
156
 
@@ -189,11 +193,15 @@ export function sketchToPdf(sk: Sketch, opts: PdfOptions = {}): string {
189
193
  const q = p.pts.map(T);
190
194
  c.push(rgb(FILL[p.layer] || FILL.SPACES) + ' rg ' + q.map((v, i) => f(v.x) + ' ' + f(v.y) + (i ? ' l' : ' m')).join(' ') + ' h f');
191
195
  } else if (p.t === 'wall') {
192
- // a solid band of the wall's thickness: a fat stroke with square ends (joints were extended already)
193
- c.push('q 0 J ' + f(p.thick * s) + ' w ' + rgb(WALL_RGB) + ' RG []0 d');
196
+ // a straight wall: its outline, filled (cut to fit its joints); a curve: a fat stroke of the wall's thickness
194
197
  const sh = p.shape;
195
- if (sh.kind === 'line') { const a = T(sh.a), b = T(sh.b); c.push(f(a.x) + ' ' + f(a.y) + ' m ' + f(b.x) + ' ' + f(b.y) + ' l S'); }
196
- else if (sh.kind === 'arc') c.push(arcPath(T(sh.c), sh.r * s, sh.startDeg, sh.endDeg, true) + 'S');
198
+ if (sh.kind === 'polys') {
199
+ const path = sh.polys.filter(q => q.length >= 3).map(q => q.map(T).map((v, i) => f(v.x) + ' ' + f(v.y) + (i ? ' l' : ' m')).join(' ') + ' h').join(' ');
200
+ if (path) c.push(rgb(WALL_RGB) + ' rg ' + path + ' f');
201
+ continue;
202
+ }
203
+ c.push('q 0 J ' + f(p.thick * s) + ' w ' + rgb(WALL_RGB) + ' RG []0 d');
204
+ if (sh.kind === 'arc') c.push(arcPath(T(sh.c), sh.r * s, sh.startDeg, sh.endDeg, true) + 'S');
197
205
  else c.push(arcPath(T(sh.c), sh.r * s, 0, 360, true) + 'h S');
198
206
  c.push('Q');
199
207
  stroke = '';
@@ -210,7 +218,7 @@ export function sketchToPdf(sk: Sketch, opts: PdfOptions = {}): string {
210
218
  } else {
211
219
  const size = Math.min(14, Math.max(5, p.h * s));
212
220
  const room = p.layer === 'SPACES', bold = room && p.h >= 6;
213
- text(T(p.p), size, p.s, { bold, rot: p.rot, rgb: p.layer === 'MARKUP' ? STYLE.MARKUP.rgb : p.layer === 'DIMENSIONS' ? [0.15, 0.15, 0.15] : undefined });
221
+ text(T(p.p), size, p.s, { bold, rot: p.rot, rgb: p.layer === 'MARKUP' || p.layer === 'LINKS' ? STYLE[p.layer].rgb : p.layer === 'DIMENSIONS' ? [0.15, 0.15, 0.15] : undefined });
214
222
  }
215
223
  }
216
224
  if (!bb) text({ x: ox, y: oy }, 12, 'Nothing drawn on this page yet', { rgb: [0.5, 0.5, 0.5] });
@@ -2,19 +2,28 @@ import { isDim, measure } from '../model/constraints';
2
2
  import { hyp, norm2, pointInPoly } from '../model/geom';
3
3
  import { edgeS, measureOpeningDim, openingSpan, refPoint, refS } from '../model/openings';
4
4
  import { roomGeom } from '../model/rooms';
5
- import { curveInfo, roomValid } from '../model/sketch';
6
- import type { Sketch, XY } from '../model/types';
5
+ import { smoothPolyline } from '../model/smooth';
6
+ import { roomValid } from '../model/sketch';
7
+ import { dimEnds, dimRadius } from '../model/planGeom';
8
+ import { wallPieces } from '../model/wallshape';
9
+ import { cabinetFootprints } from '../model/facesolve';
10
+ import { isSegment, noteLook, noteTextIn, shapeOutline } from '../model/markup';
11
+ import type { LinkTarget, Note, Sketch, XY } from '../model/types';
12
+ import { isLink, linkInfoOf, linkName, linkSide } from '../model/links';
7
13
  import { fmtAng, fmtArea, fmtLen } from '../units';
8
14
 
9
15
  /*
10
16
  What a page looks like on paper, independent of file format: the exporters (DXF, PDF) implement `Plan`
11
17
  and `emitDrawing` walks the sketch once, in drawing order (room shading, walls, openings, labels,
12
- dimensions, markup). World units are inches, y up.
18
+ dimensions, markup — links' tiles on a LINKS layer of their own). World units are inches, y up.
13
19
  */
14
20
 
15
- /** A solid wall to draw: a straight run (already extended into its joints) or a curve. */
21
+ /**
22
+ * A solid wall to draw: the straight walls' outlines (cut to fit their joints, see model/wallshape.ts — one shape, so
23
+ * no seams show where they meet), or a curve.
24
+ */
16
25
  export type WallShape =
17
- | { kind: 'line'; a: XY; b: XY }
26
+ | { kind: 'polys'; polys: XY[][] }
18
27
  | { kind: 'arc'; c: XY; r: number; startDeg: number; endDeg: number }
19
28
  | { kind: 'circle'; c: XY; r: number };
20
29
 
@@ -34,8 +43,70 @@ export interface Plan {
34
43
  export interface DrawOptions {
35
44
  /** include dimensions (driving, reference, print, door/window) — default true */
36
45
  dims?: boolean;
37
- /** include markup (pen, arrows, clouds, notes) — default true */
46
+ /** include markup (pen, lines, arrows, shapes, clouds, notes) — default true */
38
47
  markup?: boolean;
48
+ /** include cabinets' footprints (from the walls' views) — default true */
49
+ cabinets?: boolean;
50
+ /**
51
+ * What a link's tile says on paper: its name, and a measurement's value (default: what the host last said, or what
52
+ * it was when it was linked).
53
+ */
54
+ linkLabel?: (t: LinkTarget) => { name: string; value?: string };
55
+ }
56
+
57
+ /** What a link's tile says on paper, unless the exporter's told otherwise. */
58
+ const defaultLinkLabel = (t: LinkTarget): { name: string; value?: string } => {
59
+ const info = linkInfoOf(t);
60
+ return { name: (info?.exists === false ? 'Removed: ' : '') + linkName(t), value: t.kind === 'measurement' ? info?.text ?? t.text : undefined };
61
+ };
62
+ const KIND_WORD: Record<string, string> = { photo: 'Photo', pdf: 'PDF', note: 'Note', calculator: 'Calculator', label: 'Label', drawing: 'Drawing' };
63
+
64
+ /**
65
+ * A name on paper, where nothing can be measured (a DXF has no fonts): about `perLine` characters a line, broken at a
66
+ * space where it can be, on two lines at most — the second cut short if even that's too little.
67
+ */
68
+ export function wrapName(text: string, perLine: number): string[] {
69
+ if (text.length <= perLine) return [text];
70
+ const sp = text.lastIndexOf(' ', perLine), cut = sp > 0 ? sp : perLine, next = sp > 0 ? sp + 1 : perLine;
71
+ const first = text.slice(0, cut).trimEnd();
72
+ let rest = text.slice(next).trim();
73
+ if (rest.length > perLine) rest = rest.slice(0, Math.max(1, perLine - 1)).trimEnd() + '…';
74
+ return rest ? [first, rest] : [first];
75
+ }
76
+
77
+ /** How many characters of a tile's name go across its box (84 of its 96, at text 11 high: about 0.55 of that a character). */
78
+ const NAME_PER_LINE = Math.floor(84 / (0.55 * 11));
79
+
80
+ /** A link's tile on the LINKS layer: its box, what it says, and its leader to what it points at (with its arrowheads). */
81
+ function emitLink(d: Plan, n: Note & { link: LinkTarget }, label: NonNullable<DrawOptions['linkLabel']>): void {
82
+ const L = 'LINKS', x = n.x ?? 0, y = n.y ?? 0, side = linkSide(n), h = side / 2;
83
+ const c = [{ x: x - h, y: y - h }, { x: x + h, y: y - h }, { x: x + h, y: y + h }, { x: x - h, y: y + h }];
84
+ for (let i = 0; i < 4; i++) d.line(L, c[i], c[(i + 1) % 4]);
85
+ // laid out as the card is on the screen (96 across): a measurement's value in the middle and its name below; a
86
+ // file's kind where its picture is, and its name below
87
+ const u = side / 96, { name, value } = label(n.link);
88
+ if (n.link.kind === 'measurement') {
89
+ if (value) d.text(L, { x, y: y + h - 46 * u }, 18 * u, value);
90
+ d.text(L, { x, y: y + h - 75 * u }, 11 * u, wrapName(name, NAME_PER_LINE).length > 1 ? name.slice(0, NAME_PER_LINE - 1).trimEnd() + '…' : name);
91
+ } else {
92
+ const kind = KIND_WORD[linkInfoOf(n.link)?.fileType ?? n.link.fileType ?? ''] ?? 'File';
93
+ d.text(L, { x, y: y + h - 37 * u }, 11 * u, kind);
94
+ // its name across its box (on paper it can't zoom in to show more): two lines, centred in the strip under the picture
95
+ const lines = wrapName(name, NAME_PER_LINE);
96
+ if (lines.length === 1) d.text(L, { x, y: y + h - 81 * u }, 11 * u, lines[0]);
97
+ else lines.forEach((l, i) => d.text(L, { x, y: y + h - (82 + (i ? 1 : -1) * 6.875) * u }, 11 * u, l));
98
+ }
99
+ if (!n.leader) return;
100
+ const [tx, ty] = n.leader, dx = tx - x, dy = ty - y, m = Math.max(Math.abs(dx), Math.abs(dy));
101
+ if (m <= h) return;
102
+ const s = { x: x + (dx * h) / m, y: y + (dy * h) / m }, tip = { x: tx, y: ty }, look = noteLook(n);
103
+ d.line(L, s, tip);
104
+ const head = (from: XY, to: XY) => {
105
+ const a = Math.atan2(to.y - from.y, to.x - from.x), hl = 5;
106
+ for (const k of [-0.45, 0.45]) d.line(L, to, { x: to.x - hl * Math.cos(a + k), y: to.y - hl * Math.sin(a + k) });
107
+ };
108
+ if (look.endCap === 'arrow') head(s, tip);
109
+ if (look.startCap === 'arrow') head(tip, s);
39
110
  }
40
111
 
41
112
  /** Text height for dimension values and labels, inches. */
@@ -90,20 +161,20 @@ export function emitDrawing(sk: Sketch, d: Plan, opts: DrawOptions = {}): void {
90
161
  // room shading first, so walls and labels sit on top
91
162
  if (d.fill) for (const id in sk.rooms) { const r = sk.rooms[id]; if (roomValid(sk, r)) d.fill('SPACES', roomGeom(sk, r).inner); }
92
163
 
93
- // walls: how many walls meet at each point (straight walls extend into their joints so corners close)
94
- const deg2: Record<string, number> = {};
95
- const bump = (p: string) => (deg2[p] = (deg2[p] || 0) + 1);
96
- for (const id in sk.lines) { const l = sk.lines[id]; if (l.kind === 'wall' && !l.constr) { bump(l.a); bump(l.b); } }
97
- for (const id in sk.arcs) { const a = sk.arcs[id]; if (a.kind === 'wall' && !a.constr) { bump(a.a); bump(a.b); } }
164
+ // cabinets' footprints, under the walls: base cabinets filled on CABINETS, wall (upper) cabinets on CABINETS_UPPER (dashed)
165
+ if (opts.cabinets !== false) for (const fp of cabinetFootprints(sk)) {
166
+ const layer = fp.upper ? 'CABINETS_UPPER' : 'CABINETS';
167
+ if (!fp.upper && d.fill) d.fill('CABINETS', fp.pts);
168
+ fp.pts.forEach((p, i) => d.line(layer, p, fp.pts[(i + 1) % fp.pts.length]));
169
+ }
98
170
 
99
- // geometry
171
+ // geometry: straight walls as their outlines, cut to fit where they meet (as on screen)
172
+ const pieces = d.wall ? wallPieces(sk, w => !w.constr) : [];
173
+ if (pieces.length) d.wall!({ kind: 'polys', polys: pieces.map(p => p.pts) }, 0);
100
174
  for (const id in sk.lines) {
101
175
  const l = sk.lines[id], A = P(l.a), B = P(l.b);
102
- if (l.kind === 'wall' && !l.constr && d.wall) {
103
- const L = hyp(B.x - A.x, B.y - A.y) || 1e-9, ux = (B.x - A.x) / L, uy = (B.y - A.y) / L, e = l.thick / 2;
104
- const ea = deg2[l.a] > 1 ? e : 0, eb = deg2[l.b] > 1 ? e : 0;
105
- d.wall({ kind: 'line', a: { x: A.x - ux * ea, y: A.y - uy * ea }, b: { x: B.x + ux * eb, y: B.y + uy * eb } }, l.thick);
106
- } else d.line(l.constr ? 'CONSTRUCTION' : l.kind === 'wall' ? 'WALLS' : 'LINES', A, B);
176
+ if (l.kind === 'wall' && !l.constr && d.wall) continue;
177
+ d.line(l.constr ? 'CONSTRUCTION' : l.kind === 'wall' ? 'WALLS' : 'LINES', A, B);
107
178
  }
108
179
  for (const id in sk.circles) {
109
180
  const c = sk.circles[id];
@@ -159,26 +230,31 @@ export function emitDrawing(sk: Sketch, d: Plan, opts: DrawOptions = {}): void {
159
230
  const v = c.driving === false ? measure(sk, c) : c.value!;
160
231
  const wrap = (s: string) => (ref ? '(' + s + ')' : s);
161
232
  if (c.type === 'dist') {
162
- const a = P(c.a!), b = P(c.b!);
163
- if (!a || !b || hyp(b.x - a.x, b.y - a.y) < 1e-6) continue;
233
+ // (on a wall's face: its corners)
234
+ const e = dimEnds(sk, c);
235
+ if (!e) continue;
236
+ const { a, b } = e;
237
+ if (hyp(b.x - a.x, b.y - a.y) < 1e-6) continue;
164
238
  const n = norm2(-(b.y - a.y), b.x - a.x);
165
239
  // a label sitting on its line in the app goes out to the side: overall wall lengths sit outside the
166
240
  // door/window string (architectural order: openings close to the wall, overall dimensions further out)
167
241
  const off = c.off || (c.line ? WALL_DIM_OFF * outsideSide(sk, c.line) : 0);
168
242
  linearDim(d, a, b, n, off, wrap(fmtLen(v)));
169
243
  } else if (c.type === 'pldist') {
170
- const l = sk.lines[c.l!], p = P(c.p!), A = P(l.a), B = P(l.b);
171
- const t = ((p.x - A.x) * (B.x - A.x) + (p.y - A.y) * (B.y - A.y)) / ((B.x - A.x) ** 2 + (B.y - A.y) ** 2 || 1);
172
- const f = { x: A.x + (B.x - A.x) * t, y: A.y + (B.y - A.y) * t };
173
- const n = norm2(-(f.y - p.y), f.x - p.x);
174
- linearDim(d, p, f, n, 0, wrap(fmtLen(v)));
244
+ // (between walls' faces: face to face)
245
+ const e = dimEnds(sk, c);
246
+ if (!e) continue;
247
+ const n = norm2(-(e.b.y - e.a.y), e.b.x - e.a.x);
248
+ linearDim(d, e.a, e.b, n, 0, wrap(fmtLen(v)));
175
249
  } else if (c.type === 'diam') {
176
- const ci = curveInfo(sk, c.c);
177
- if (!ci) continue;
178
- const o = P(ci.o.c), r = ci.kind === 'circle' ? ci.o.r : hyp(P(ci.o.a).x - o.x, P(ci.o.a).y - o.y);
250
+ // (across a curved wall's face: on that face)
251
+ const g = dimRadius(sk, c);
252
+ if (!g) continue;
253
+ const { o, r } = g;
179
254
  const a = c.ang ?? Math.PI / 3, u = [Math.cos(a), Math.sin(a)];
180
- d.line('DIMENSIONS', { x: o.x - u[0] * r, y: o.y - u[1] * r }, { x: o.x + u[0] * (r + 12), y: o.y + u[1] * (r + 12) });
181
- d.text('DIMENSIONS', { x: o.x + u[0] * (r + 12) + (u[0] >= 0 ? 14 : -14), y: o.y + u[1] * (r + 12) }, TEXT_H, 'Ø ' + wrap(fmtLen(v)));
255
+ // a radius (a fillet's) from the centre out through the arc; a diameter right across
256
+ d.line('DIMENSIONS', c.rad ? o : { x: o.x - u[0] * r, y: o.y - u[1] * r }, { x: o.x + u[0] * (r + 12), y: o.y + u[1] * (r + 12) });
257
+ d.text('DIMENSIONS', { x: o.x + u[0] * (r + 12) + (u[0] >= 0 ? 14 : -14), y: o.y + u[1] * (r + 12) }, TEXT_H, c.rad ? 'R ' + wrap(fmtLen(v / 2)) : 'Ø ' + wrap(fmtLen(v)));
182
258
  } else if (c.type === 'angle') {
183
259
  const l1 = sk.lines[c.l1!], l2 = sk.lines[c.l2!];
184
260
  const sh = [l1.a, l1.b].find(p => p === l2.a || p === l2.b);
@@ -208,19 +284,27 @@ export function emitDrawing(sk: Sketch, d: Plan, opts: DrawOptions = {}): void {
208
284
  if (opts.markup !== false) {
209
285
  for (const nt of sk.notes) {
210
286
  const L = 'MARKUP';
211
- if ((nt.type === 'pen' || nt.type === 'arrow') && nt.pts) {
212
- for (let i = 1; i < nt.pts.length; i++) d.line(L, { x: nt.pts[i - 1][0], y: nt.pts[i - 1][1] }, { x: nt.pts[i][0], y: nt.pts[i][1] });
213
- if (nt.type === 'arrow' && nt.pts.length > 1) {
214
- const [p, q] = [nt.pts[nt.pts.length - 2], nt.pts[nt.pts.length - 1]];
287
+ if (isLink(nt)) { emitLink(d, nt, opts.linkLabel ?? defaultLinkLabel); continue; }
288
+ const outline = shapeOutline(nt);
289
+ if (outline) {
290
+ for (let i = 0; i < outline.length; i++) { const a = outline[i], b = outline[(i + 1) % outline.length]; d.line(L, { x: a[0], y: a[1] }, { x: b[0], y: b[1] }); }
291
+ } else if ((nt.type === 'pen' || isSegment(nt)) && nt.pts) {
292
+ // (a pen stroke's curve, as drawn, in pieces within a hundredth of an inch of it)
293
+ const run = smoothPolyline(nt.pts, 0.01);
294
+ for (let i = 1; i < run.length; i++) d.line(L, { x: run[i - 1][0], y: run[i - 1][1] }, { x: run[i][0], y: run[i][1] });
295
+ const look = noteLook(nt), n = nt.pts.length;
296
+ const head = (p: [number, number], q: [number, number]) => {
215
297
  const a = Math.atan2(q[1] - p[1], q[0] - p[0]), h = 5;
216
298
  for (const s of [-0.45, 0.45]) d.line(L, { x: q[0], y: q[1] }, { x: q[0] - h * Math.cos(a + s), y: q[1] - h * Math.sin(a + s) });
217
- }
299
+ };
300
+ if (n > 1 && look.endCap === 'arrow') head(nt.pts[n - 2], nt.pts[n - 1]);
301
+ if (n > 1 && look.startCap === 'arrow') head(nt.pts[1], nt.pts[0]);
218
302
  } else if (nt.type === 'cloud' && nt.pts) {
219
303
  const [[x0, y0], [x1, y1]] = nt.pts;
220
304
  const c = [{ x: x0, y: y0 }, { x: x1, y: y0 }, { x: x1, y: y1 }, { x: x0, y: y1 }];
221
305
  for (let i = 0; i < 4; i++) d.line(L, c[i], c[(i + 1) % 4]);
222
306
  } else if (nt.type === 'text' && nt.text) {
223
- d.text(L, { x: nt.x!, y: nt.y! }, 5, nt.text);
307
+ d.text(L, { x: nt.x!, y: nt.y! }, noteTextIn(nt), nt.text);
224
308
  }
225
309
  }
226
310
  }
package/src/index.ts CHANGED
@@ -10,4 +10,9 @@ export type { CadDataProvider, CadFileMeta, CadMeasurements, CadMeasurementValue
10
10
  export { memoryProvider } from './data/memory';
11
11
  export { setFileSaver } from './react/ports';
12
12
  export { laserReading, setLaserConnected } from './react/host';
13
+ export type { HostKeypad, HostKeypadExtra, HostKeypadRequest, MeasurementList, MeasurementListPage } from './react/hostKeypad';
14
+ export type { CadLinkPick, CadLinks, CadSaveMeasurement, MeasurementKind } from './editor/links';
15
+ export type { LinkInfo, LinkTarget } from './model/types';
16
+ export { linkKey } from './model/links';
17
+ export { hasUnsavedChanges as cadHasUnsavedChanges, saveDrawing as saveCadDrawing, type CadSaveState } from './react/saving';
13
18
  export type { SkiaFonts } from './skia/SkiaDrawCtx';
@@ -1,8 +1,6 @@
1
- import { elevKeyDown, elevOpen } from '../editor/elevation';
2
- import { keypadOpen } from '../editor/keypad';
3
1
  import { keyDown, keyUp, type KeyIn } from '../editor/keys';
4
2
 
5
- /* DOM key events, handed to editor/keys.ts (or to the wall view's shortcuts while it's open). */
3
+ /* DOM key events, handed to editor/keys.ts. */
6
4
 
7
5
  const keyIn = (e: KeyboardEvent): KeyIn => ({ key: e.key, shift: e.shiftKey, mod: e.metaKey || e.ctrlKey, alt: e.altKey });
8
6
 
@@ -11,7 +9,7 @@ export function initKeyboard(): void {
11
9
  const tag = (e.target && (e.target as HTMLElement).tagName) || '';
12
10
  if (tag === 'INPUT' || tag === 'TEXTAREA') return;
13
11
  const k = keyIn(e);
14
- if (!keypadOpen() && elevOpen() ? elevKeyDown(k) : keyDown(k)) e.preventDefault();
12
+ if (keyDown(k)) e.preventDefault();
15
13
  });
16
14
  document.addEventListener('keyup', e => keyUp(keyIn(e)));
17
15
  }
@@ -1,6 +1,7 @@
1
1
  import { fmtAng, fmtLen } from '../units';
2
2
  import { hyp } from './geom';
3
3
  import { curveInfo } from './sketch';
4
+ import { faceLength } from './wallfaces';
4
5
  import type { Analysis, Constraint, ConsType, Line, Sketch } from './types';
5
6
 
6
7
  export const isDim = (c: Constraint): boolean => ['dist', 'pldist', 'diam', 'angle'].includes(c.type);
@@ -13,7 +14,7 @@ export const CONS_LABEL: Record<ConsType, string> = {
13
14
 
14
15
  export function consLabel(c: Constraint): string {
15
16
  if (c.type === 'dist' || c.type === 'pldist') return (c.type === 'dist' ? (c.line ? 'Length ' : 'Distance ') : 'Offset ') + fmtLen(c.value!);
16
- if (c.type === 'diam') return 'Diameter ' + fmtLen(c.value!);
17
+ if (c.type === 'diam') return c.rad ? 'Radius ' + fmtLen(c.value! / 2) : 'Diameter ' + fmtLen(c.value!);
17
18
  if (c.type === 'angle') return 'Angle ' + fmtAng(c.value!);
18
19
  return CONS_LABEL[c.type] || c.type;
19
20
  }
@@ -26,18 +27,26 @@ export function measure(sk: Sketch, c: Constraint): number {
26
27
  const a = P(c.a!), b = P(c.b!);
27
28
  if (c.kind === 'h') return Math.abs(b.x - a.x);
28
29
  if (c.kind === 'v') return Math.abs(b.y - a.y);
30
+ // along a wall's face, corner to corner
31
+ if (c.face && c.line && sk.lines[c.line]?.kind === 'wall') return faceLength(sk, c.line, c.face);
29
32
  return hyp(b.x - a.x, b.y - a.y);
30
33
  }
31
34
  case 'pldist': {
32
35
  const l = sk.lines[c.l!], p = P(c.p!), a = P(l.a), b = P(l.b);
33
- return Math.abs(((b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x)) / (hyp(b.x - a.x, b.y - a.y) || 1e-12));
36
+ const d = Math.abs(((b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x)) / (hyp(b.x - a.x, b.y - a.y) || 1e-12));
37
+ if (!c.faces) return d;
38
+ // between faces: less half of each wall's thickness for a face toward the other, more for a far face
39
+ const th = (id?: string) => { const w = id ? sk.lines[id] : undefined; return w && w.kind === 'wall' ? w.thick : 0; };
40
+ return d - (c.faces[0] * th(c.l) + c.faces[1] * th(c.l2)) / 2;
34
41
  }
35
42
  case 'diam': {
36
43
  const ci = curveInfo(sk, c.c);
37
44
  if (!ci) return 0;
38
- if (ci.kind === 'circle') return 2 * ci.o.r;
45
+ // (across a curved wall's face: its centreline's, plus its thickness for the outer face, less for the inner)
46
+ const off = c.face && ci.o.kind === 'wall' ? c.face * (ci.o.thick || 4.5) : 0;
47
+ if (ci.kind === 'circle') return 2 * ci.o.r + off;
39
48
  const o = P(ci.o.c), s = P(ci.o.a);
40
- return 2 * hyp(s.x - o.x, s.y - o.y);
49
+ return 2 * hyp(s.x - o.x, s.y - o.y) + off;
41
50
  }
42
51
  case 'angle': {
43
52
  const l1 = sk.lines[c.l1!], l2 = sk.lines[c.l2!];
package/src/model/demo.ts CHANGED
@@ -15,8 +15,8 @@ export function demoSketch(): Sketch {
15
15
  C({ type: 'h', a: p4, b: p5 }); C({ type: 'v', a: p5, b: p6 }); C({ type: 'h', a: p6, b: p1 });
16
16
  C({ type: 'dist', a: p1, b: p2, kind: 'al', value: 240, line: l1, driving: true, off: 0 });
17
17
  C({ type: 'dist', a: p6, b: p1, kind: 'al', value: 168, line: l6, driving: true, off: 0 });
18
- sk.openings[uid('o')] = { line: l6, at: 42, w: 36, type: 'door', flip: true };
19
- sk.openings[uid('o')] = { line: l1, at: 150, w: 48, type: 'window', flip: false };
18
+ sk.openings[uid('o')] = { line: l6, at: 42, w: 36, wSet: false, type: 'door', flip: true };
19
+ sk.openings[uid('o')] = { line: l1, at: 150, w: 48, wSet: true, type: 'window', flip: false };
20
20
  sk.rooms[uid('r')] = { loop: [p1, p6, p5, p4, p3, p2], name: 'Living room' };
21
21
  sk.seq.room = 2;
22
22
  sk.notes.push({ id: uid('n'), type: 'arrow', pts: [[214, 196], [128, 158]], color: '#F070EE' });
@@ -25,7 +25,7 @@ export function demoSketch(): Sketch {
25
25
  }
26
26
 
27
27
  export function newPage(name: string, sk?: Sketch): Page {
28
- return { id: uid('pg'), name, sk: sk || emptySketch(), underlay: null, view: null };
28
+ return { id: uid('pg'), name, sk: sk || emptySketch(), underlays: [], view: null };
29
29
  }
30
30
 
31
31
  export function seedProject(): Project {
@@ -3,7 +3,8 @@ import { edgeS, openingSpan } from './openings';
3
3
  import { roomGeom } from './rooms';
4
4
  import { settings } from './settings';
5
5
  import { roomValid } from './sketch';
6
- import type { ElevLine, Line, Opening, OpeningEdge, Sketch, XY } from './types';
6
+ import { faceKey } from './faces';
7
+ import type { Line, Opening, OpeningEdge, Sketch, XY } from './types';
7
8
  import { wallFace } from './wallfaces';
8
9
 
9
10
  /*
@@ -106,19 +107,8 @@ export function elevOpenings(sk: Sketch, f: ElevFrame): ElevOpening[] {
106
107
  return out.sort((p, q) => p.u0 - q.u0);
107
108
  }
108
109
 
109
- /** Key of a wall face's elevation drawing in `Sketch.elev`. */
110
- export const elevKey = (lid: string, side: number): string => lid + '|' + (side > 0 ? 1 : -1);
110
+ /** Key of a wall face's drawing in `Sketch.faces`. */
111
+ export const elevKey = faceKey;
111
112
 
112
- /** Lines drawn on the face this frame looks at (live array; empty if none). */
113
- export const elevLines = (sk: Sketch, f: ElevFrame): ElevLine[] => sk.elev?.[elevKey(f.lid, f.side)] ?? [];
114
-
115
- /** Add a line to the face this frame looks at; a and b in view coordinates [u, z]. Returns its id. */
116
- export function addElevLine(sk: Sketch, f: ElevFrame, a: [number, number], b: [number, number], guide: boolean, id: string): string {
117
- const k = elevKey(f.lid, f.side);
118
- sk.elev = sk.elev || {};
119
- (sk.elev[k] = sk.elev[k] || []).push({ id, a: [toU(f, a[0]), a[1]], b: [toU(f, b[0]), b[1]], ...(guide ? { guide: true } : {}) });
120
- return id;
121
- }
122
-
123
- /** A drawn line's ends in view coordinates [u, z]. */
124
- export const elevLineUZ = (f: ElevFrame, l: ElevLine): [[number, number], [number, number]] => [[toU(f, l.a[0]), l.a[1]], [toU(f, l.b[0]), l.b[1]]];
113
+ /** A face point (x = s, y = z) in view coordinates [u, z]. */
114
+ export const faceUZ = (f: ElevFrame, p: XY): [number, number] => [toU(f, p.x), p.y];
@@ -0,0 +1,153 @@
1
+ import { onSessionReset } from './session';
2
+ import type { Constraint, Sketch, WallFace } from './types';
3
+
4
+ /*
5
+ What's drawn on wall faces (Sketch.faces): the parts that don't need the wall's geometry — keys, the plan's
6
+ anchors by name, and following a wall that's split. Keeping a face in step with the plan (its anchors, solving
7
+ it) is in model/facesolve.ts.
8
+ */
9
+
10
+ /** Key of a wall face in `Sketch.faces`: the wall line and the side it's seen from (1: left of A → B, -1: right). */
11
+ export const faceKey = (lid: string, side: number): string => lid + '|' + (side > 0 ? 1 : -1);
12
+ export const faceKeyParts = (key: string): { lid: string; side: 1 | -1 } => {
13
+ const i = key.lastIndexOf('|');
14
+ return { lid: key.slice(0, i), side: key.slice(i + 1) === '-1' ? -1 : 1 };
15
+ };
16
+
17
+ /** The wall face being edited (its key), or null while the plan is: SK() then gives that face's sketch. */
18
+ export const faceEdit = { key: null as string | null };
19
+ onSessionReset(() => { faceEdit.key = null; });
20
+
21
+ /** Whether a face has nothing on it but the plan's anchors (and isn't open): it isn't kept. */
22
+ export const faceEmpty = (face: Sketch): boolean =>
23
+ Object.values(face.points).every(p => p.anchor) && Object.values(face.cons).every(c => c.anchor) && !Object.keys(face.circles).length;
24
+
25
+ export const emptyFace = (): WallFace => ({ points: {}, lines: {}, circles: {}, arcs: {}, cons: {}, rooms: {}, openings: {}, notes: [], seq: { room: 1 }, cabs: {} });
26
+
27
+ /** The face drawing for `key`; made (empty) when `create` and there's none yet. */
28
+ export function faceOf(sk: Sketch, key: string, create: true): WallFace;
29
+ export function faceOf(sk: Sketch, key: string, create?: boolean): WallFace | null;
30
+ export function faceOf(sk: Sketch, key: string, create = false): WallFace | null {
31
+ const f = sk.faces?.[key];
32
+ if (f) return f;
33
+ if (!create) return null;
34
+ sk.faces = sk.faces || {};
35
+ return (sk.faces[key] = emptyFace());
36
+ }
37
+
38
+ /**
39
+ * The plan's anchors on a face, by name. Points: "@a0" / "@aH" the corner at the wall's start end (A), at the floor
40
+ * and at the ceiling, "@b0" / "@bH" the one at its far end; "@o:<opening>:<edge>:0" / ":1" a door's or window's
41
+ * corner (its s0 or s1 edge, at its sill or head). Edges: "@floor", "@ceil", "@ea" / "@eb" (the wall's ends),
42
+ * "@o:<opening>:sill" / ":head" / ":s0" / ":s1".
43
+ */
44
+ export const cornerAnchor = (end: 'a' | 'b', top = false): string => '@' + end + (top ? 'H' : '0');
45
+ export const openingAnchor = (op: string, edge: 's0' | 's1', top = false): string => '@o:' + op + ':' + edge + ':' + (top ? 1 : 0);
46
+ export const FLOOR = '@floor', CEILING = '@ceil';
47
+ export const endEdge = (end: 'a' | 'b'): string => '@e' + end;
48
+ export const openingEdge = (op: string, side: 'sill' | 'head' | 's0' | 's1'): string => '@o:' + op + ':' + side;
49
+
50
+ /** Whether a point or edge of the face is one of the plan's anchors. */
51
+ export const isAnchorPt = (face: Sketch, id: string): boolean => !!face.points[id]?.anchor;
52
+ export const isAnchorLine = (face: Sketch, id: string): boolean => !!face.lines[id]?.anchor;
53
+
54
+ /** Every constraint in the sketch — the plan's and each wall face's — with its id. */
55
+ export function allCons(sk: Sketch): [string, Constraint][] {
56
+ const out = Object.entries(sk.cons);
57
+ if (sk.faces) for (const key in sk.faces) out.push(...Object.entries(sk.faces[key].cons));
58
+ return out;
59
+ }
60
+
61
+ /** The wall face a constraint is on (null for the plan's own). */
62
+ export function faceOfCons(sk: Sketch, cid: string): { key: string; face: WallFace } | null {
63
+ if (sk.faces) for (const key in sk.faces) if (sk.faces[key].cons[cid]) return { key, face: sk.faces[key] };
64
+ return null;
65
+ }
66
+
67
+ /** A cabinet's corners. */
68
+ export const cabPoints = (cb: { bl: string; br: string; tr: string; tl: string }): string[] => [cb.bl, cb.br, cb.tr, cb.tl];
69
+
70
+ /** The door or window whose corner (a wall face's anchor) `pid` is, if it is one. */
71
+ export const openingOfPoint = (pid: string): string | null => /^@o:([^:]+):s[01]:[01]$/.exec(pid)?.[1] ?? null;
72
+
73
+ /** The cabinet whose corner `pid` is, if it is one. */
74
+ export const cabOfPoint = (sk: Sketch, pid: string): string | null => {
75
+ for (const [id, cb] of Object.entries((sk as WallFace).cabs ?? {})) if (cabPoints(cb).includes(pid)) return id;
76
+ return null;
77
+ };
78
+
79
+ /** Drop cabinets missing a corner, with their edges. */
80
+ export function gcCabinets(face: WallFace): void {
81
+ if (!face.cabs) { face.cabs = {}; return; }
82
+ for (const id in face.cabs) {
83
+ const cb = face.cabs[id];
84
+ if (cabPoints(cb).some(p => !face.points[p])) {
85
+ delete face.cabs[id];
86
+ for (const l in face.lines) if (face.lines[l].cab === id) delete face.lines[l];
87
+ continue;
88
+ }
89
+ for (const k of ['w', 'h'] as const) if (cb[k] && !face.cons[cb[k]!]) delete cb[k];
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Wall `lid` (L inches long) was split at its middle; the new wall `nid` runs from there to the old end. What's
95
+ * drawn past the middle moves to the new wall's faces (each group of joined things as a whole, by where its middle is).
96
+ * Constraints that would join the two walls' faces are dropped. Across a face, x runs from the wall's start (A) seen
97
+ * from side -1 and from its end (B) seen from side 1, so positions are recounted for the shorter walls.
98
+ */
99
+ export function splitFaces(sk: Sketch, lid: string, nid: string, L: number): void {
100
+ if (!sk.faces) return;
101
+ const half = L / 2;
102
+ for (const side of [1, -1] as const) {
103
+ const key = faceKey(lid, side), face: WallFace | undefined = sk.faces[key];
104
+ if (!face) continue;
105
+ const mirror = side === 1, sOf = (u: number) => (mirror ? L - u : u);
106
+ delete face.wall; // its corners are recounted here: the next placing records them afresh
107
+ // groups of points joined by lines and curves
108
+ const group = new Map<string, number>(), links = new Map<string, string[]>();
109
+ const link = (a: string, b: string) => { (links.get(a) ?? links.set(a, []).get(a)!).push(b); (links.get(b) ?? links.set(b, []).get(b)!).push(a); };
110
+ for (const l of Object.values(face.lines)) if (!l.anchor) link(l.a, l.b);
111
+ for (const a of Object.values(face.arcs)) { link(a.c, a.a); link(a.c, a.b); }
112
+ let n = 0;
113
+ for (const id in face.points) {
114
+ if (group.has(id) || face.points[id].anchor) continue;
115
+ const stack = [id];
116
+ group.set(id, n);
117
+ while (stack.length) for (const q of links.get(stack.pop()!) ?? []) if (!group.has(q) && !face.points[q].anchor) { group.set(q, n); stack.push(q); }
118
+ n++;
119
+ }
120
+ const sum = new Array<number>(n).fill(0), cnt = new Array<number>(n).fill(0);
121
+ for (const [id, g] of group) { sum[g] += sOf(face.points[id].x); cnt[g]++; }
122
+ const moved = new Set([...group].filter(([, g]) => sum[g] / cnt[g] > half).map(([id]) => id));
123
+ const to = faceOf(sk, faceKey(nid, side), true);
124
+ for (const id in face.points) {
125
+ const p = face.points[id];
126
+ if (p.anchor) continue;
127
+ const s = sOf(p.x);
128
+ if (moved.has(id)) { to.points[id] = { ...p, x: mirror ? half - (s - half) : s - half }; delete face.points[id]; }
129
+ else if (mirror) p.x = half - s;
130
+ }
131
+ const usesMoved = (ids: (string | undefined)[]) => ids.some(v => !!v && moved.has(v));
132
+ for (const id in face.lines) { const l = face.lines[id]; if (!l.anchor && usesMoved([l.a, l.b])) { to.lines[id] = l; delete face.lines[id]; } }
133
+ for (const id in face.circles) if (moved.has(face.circles[id].c)) { to.circles[id] = face.circles[id]; delete face.circles[id]; }
134
+ for (const id in face.arcs) if (moved.has(face.arcs[id].c)) { to.arcs[id] = face.arcs[id]; delete face.arcs[id]; }
135
+ for (const id in face.cabs ?? {}) if (moved.has(face.cabs[id].bl)) { to.cabs[id] = face.cabs[id]; delete face.cabs[id]; }
136
+ // what moved still hanging on anchors takes a copy of them along (the new wall's own replace them, or let go)
137
+ for (const l of Object.values(to.lines)) for (const p of [l.a, l.b]) if (face.points[p]?.anchor && !to.points[p]) to.points[p] = { ...face.points[p] };
138
+ for (const id in face.cons) {
139
+ const c: Constraint = face.cons[id];
140
+ const pts = (['a', 'b', 'p'] as const).map(k => c[k]).filter((v): v is string => !!v && !face.points[v]?.anchor);
141
+ const ls = (['l', 'l1', 'l2', 'line'] as const).map(k => c[k]).filter((v): v is string => !!v && !face.lines[v]?.anchor);
142
+ const cs = (['c', 'c1', 'c2'] as const).map(k => c[k]).filter((v): v is string => !!v);
143
+ const inNew = (v: string) => moved.has(v) || !!to.lines[v] || !!to.circles[v] || !!to.arcs[v];
144
+ const refs = [...pts, ...ls, ...cs];
145
+ if (!refs.some(inNew)) continue;
146
+ if (refs.every(inNew)) {
147
+ to.cons[id] = c;
148
+ for (const k of ['a', 'b', 'p'] as const) { const v = c[k]; if (v && face.points[v]?.anchor && !to.points[v]) to.points[v] = { ...face.points[v] }; }
149
+ }
150
+ delete face.cons[id];
151
+ }
152
+ }
153
+ }