@reekon-tools/boldr-cad 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (555) hide show
  1. package/README.md +99 -16
  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 +9 -1
  9. package/dist/data/memory.js +13 -8
  10. package/dist/data/memory.js.map +1 -1
  11. package/dist/data/provider.d.ts +30 -10
  12. package/dist/data/sync.d.ts +14 -3
  13. package/dist/data/sync.js +133 -66
  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 +20 -5
  34. package/dist/editor/keypad.js +62 -15
  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 +38 -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 +170 -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 +7 -1
  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 +54 -4
  155. package/dist/react/CadEditor.js +79 -21
  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 +582 -97
  329. package/dist/tools/dim.js.map +1 -1
  330. package/dist/tools/elevation.js +5 -5
  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 +25 -18
  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 +11 -3
  358. package/dist/units/format.js +27 -6
  359. package/dist/units/format.js.map +1 -1
  360. package/dist/units/parse.d.ts +2 -1
  361. package/dist/units/parse.js +5 -4
  362. package/dist/units/parse.js.map +1 -1
  363. package/dist/units/prefs.d.ts +9 -1
  364. package/dist/units/prefs.js +6 -0
  365. package/dist/units/prefs.js.map +1 -1
  366. package/dist/view3d/build.d.ts +35 -0
  367. package/dist/view3d/build.js +630 -0
  368. package/dist/view3d/build.js.map +1 -0
  369. package/dist/view3d/camera.d.ts +60 -0
  370. package/dist/view3d/camera.js +84 -0
  371. package/dist/view3d/camera.js.map +1 -0
  372. package/dist/view3d/fatLines.d.ts +11 -0
  373. package/dist/view3d/fatLines.js +83 -0
  374. package/dist/view3d/fatLines.js.map +1 -0
  375. package/dist/view3d/lines.d.ts +17 -0
  376. package/dist/view3d/lines.js +271 -0
  377. package/dist/view3d/lines.js.map +1 -0
  378. package/dist/view3d/mesh.d.ts +51 -0
  379. package/dist/view3d/mesh.js +101 -0
  380. package/dist/view3d/mesh.js.map +1 -0
  381. package/dist/view3d/poly.d.ts +16 -0
  382. package/dist/view3d/poly.js +109 -0
  383. package/dist/view3d/poly.js.map +1 -0
  384. package/dist/view3d/viewer.d.ts +86 -0
  385. package/dist/view3d/viewer.js +350 -0
  386. package/dist/view3d/viewer.js.map +1 -0
  387. package/package.json +15 -2
  388. package/src/app/edit.ts +83 -12
  389. package/src/app/main.ts +11 -5
  390. package/src/app/state.ts +38 -5
  391. package/src/data/memory.ts +15 -10
  392. package/src/data/provider.ts +25 -10
  393. package/src/data/sync.ts +107 -53
  394. package/src/editor/actions.ts +292 -83
  395. package/src/editor/confirm.ts +58 -0
  396. package/src/editor/elevation.ts +698 -478
  397. package/src/editor/face.ts +227 -0
  398. package/src/editor/files.ts +40 -4
  399. package/src/editor/gestures.ts +147 -31
  400. package/src/editor/keypad.ts +67 -18
  401. package/src/editor/keys.ts +25 -5
  402. package/src/editor/laser.ts +98 -20
  403. package/src/editor/linkActions.ts +194 -0
  404. package/src/editor/linkFollow.ts +43 -0
  405. package/src/editor/linkWrite.ts +77 -0
  406. package/src/editor/links.ts +181 -0
  407. package/src/editor/markup.ts +92 -8
  408. package/src/editor/mode.ts +83 -7
  409. package/src/editor/ports.ts +39 -6
  410. package/src/editor/project.ts +157 -26
  411. package/src/editor/selection.ts +54 -8
  412. package/src/editor/status.ts +2 -0
  413. package/src/editor/tooltips.ts +47 -0
  414. package/src/editor/view3d.ts +119 -0
  415. package/src/editor/view3dState.ts +61 -0
  416. package/src/export/dxf.ts +1 -1
  417. package/src/export/pdf.ts +15 -7
  418. package/src/export/plan.ts +119 -35
  419. package/src/index.ts +7 -1
  420. package/src/input/keyboard.ts +2 -4
  421. package/src/model/constraints.ts +13 -4
  422. package/src/model/demo.ts +3 -3
  423. package/src/model/elevation.ts +6 -16
  424. package/src/model/faces.ts +153 -0
  425. package/src/model/facesolve.ts +322 -0
  426. package/src/model/history.ts +19 -11
  427. package/src/model/images.ts +35 -0
  428. package/src/model/links.ts +77 -0
  429. package/src/model/markup.ts +80 -0
  430. package/src/model/measurement.ts +26 -0
  431. package/src/model/openings.ts +6 -0
  432. package/src/model/planGeom.ts +174 -8
  433. package/src/model/project.ts +3 -2
  434. package/src/model/projectfile.ts +2 -1
  435. package/src/model/rooms.ts +1 -1
  436. package/src/model/settings.ts +8 -2
  437. package/src/model/sketch.ts +101 -14
  438. package/src/model/smooth.ts +51 -0
  439. package/src/model/store.ts +35 -4
  440. package/src/model/types.ts +170 -8
  441. package/src/model/wallfaces.ts +126 -17
  442. package/src/model/wallshape.ts +97 -0
  443. package/src/react/BottomBar.tsx +263 -46
  444. package/src/react/Buttons.tsx +50 -9
  445. package/src/react/CadEditor.tsx +142 -34
  446. package/src/react/ConfirmSheet.tsx +38 -0
  447. package/src/react/ConflictSheet.tsx +19 -12
  448. package/src/react/Drawer.tsx +213 -110
  449. package/src/react/Duck.tsx +50 -0
  450. package/src/react/Empty3D.tsx +32 -0
  451. package/src/react/FitScroll.tsx +19 -0
  452. package/src/react/Icon.native.tsx +5 -0
  453. package/src/react/Icon.web.tsx +5 -0
  454. package/src/react/KeypadSheet.tsx +253 -60
  455. package/src/react/LaserTray.tsx +12 -8
  456. package/src/react/MarkupPanel.tsx +150 -0
  457. package/src/react/PageThumb.native.tsx +18 -7
  458. package/src/react/PageThumb.web.tsx +4 -4
  459. package/src/react/PlanCanvas.native.tsx +151 -34
  460. package/src/react/PlanCanvas.web.tsx +15 -5
  461. package/src/react/Popovers.tsx +116 -51
  462. package/src/react/Rail.tsx +360 -27
  463. package/src/react/ShapeMenu.tsx +66 -0
  464. package/src/react/Sheet.tsx +56 -11
  465. package/src/react/Slider.tsx +48 -0
  466. package/src/react/Toast.tsx +16 -9
  467. package/src/react/ToolTip.tsx +49 -0
  468. package/src/react/TopBar.tsx +97 -39
  469. package/src/react/Tween.native.tsx +36 -0
  470. package/src/react/Tween.tsx +24 -0
  471. package/src/react/View3D.native.tsx +131 -0
  472. package/src/react/View3D.web.tsx +131 -0
  473. package/src/react/View3DHost.ts +367 -0
  474. package/src/react/corners.ts +52 -0
  475. package/src/react/easing.ts +14 -0
  476. package/src/react/host.ts +8 -3
  477. package/src/react/hostKeypad.ts +66 -0
  478. package/src/react/motion.tsx +239 -0
  479. package/src/react/ports.ts +78 -9
  480. package/src/react/saving.ts +80 -0
  481. package/src/react/store.ts +35 -3
  482. package/src/react/theme.ts +8 -0
  483. package/src/react/topRow.ts +71 -0
  484. package/src/react/touch.ts +10 -0
  485. package/src/render/ctx.ts +8 -0
  486. package/src/render/draw.ts +437 -175
  487. package/src/render/elevation.ts +349 -143
  488. package/src/render/figmaIcons.ts +22 -0
  489. package/src/render/hit.ts +140 -21
  490. package/src/render/icons.ts +69 -10
  491. package/src/render/linkTile.ts +297 -0
  492. package/src/render/markup.ts +91 -0
  493. package/src/render/navigate.ts +114 -0
  494. package/src/render/snap.ts +105 -22
  495. package/src/render/squircle.ts +73 -0
  496. package/src/render/view.ts +255 -19
  497. package/src/skia/SkiaDrawCtx.ts +163 -41
  498. package/src/skia/host.ts +46 -1
  499. package/src/solver/equations.ts +37 -2
  500. package/src/solver/gauss-newton.ts +125 -28
  501. package/src/solver/linalg.ts +141 -5
  502. package/src/solver/types.ts +5 -0
  503. package/src/styles.css +91 -42
  504. package/src/tools/annotate.ts +58 -29
  505. package/src/tools/chamfer.ts +103 -0
  506. package/src/tools/corner.ts +99 -0
  507. package/src/tools/dim.ts +525 -93
  508. package/src/tools/elevation.ts +5 -5
  509. package/src/tools/fillet.ts +82 -0
  510. package/src/tools/freehand.ts +20 -16
  511. package/src/tools/index.ts +17 -4
  512. package/src/tools/line.ts +59 -23
  513. package/src/tools/modify.ts +86 -24
  514. package/src/tools/openings.ts +11 -16
  515. package/src/tools/registry.ts +27 -2
  516. package/src/tools/select.ts +424 -75
  517. package/src/tools/shapes.ts +119 -58
  518. package/src/ui/ctx.ts +2 -1
  519. package/src/ui/domPorts.ts +23 -4
  520. package/src/ui/drawer.ts +6 -26
  521. package/src/ui/elevation.ts +10 -101
  522. package/src/ui/keypad.ts +8 -14
  523. package/src/ui/laser.ts +2 -2
  524. package/src/ui/menu.ts +63 -17
  525. package/src/ui/noteinput.ts +3 -3
  526. package/src/ui/opacity.ts +108 -0
  527. package/src/ui/palette.ts +67 -12
  528. package/src/ui/rail.ts +21 -7
  529. package/src/ui/topbar.ts +4 -14
  530. package/src/units/format.ts +24 -6
  531. package/src/units/parse.ts +4 -3
  532. package/src/units/prefs.ts +13 -1
  533. package/src/view3d/build.ts +607 -0
  534. package/src/view3d/camera.ts +113 -0
  535. package/src/view3d/fatLines.ts +83 -0
  536. package/src/view3d/lines.ts +258 -0
  537. package/src/view3d/mesh.ts +111 -0
  538. package/src/view3d/poly.ts +104 -0
  539. package/src/view3d/viewer.ts +348 -0
  540. package/dist/react/ElevCanvas.native.d.ts +0 -3
  541. package/dist/react/ElevCanvas.native.js +0 -58
  542. package/dist/react/ElevCanvas.native.js.map +0 -1
  543. package/dist/react/ElevCanvas.web.d.ts +0 -4
  544. package/dist/react/ElevCanvas.web.js +0 -44
  545. package/dist/react/ElevCanvas.web.js.map +0 -1
  546. package/dist/react/ElevationSheet.d.ts +0 -2
  547. package/dist/react/ElevationSheet.js +0 -38
  548. package/dist/react/ElevationSheet.js.map +0 -1
  549. package/dist/render/elevationView.d.ts +0 -3
  550. package/dist/render/elevationView.js +0 -100
  551. package/dist/render/elevationView.js.map +0 -1
  552. package/src/react/ElevCanvas.native.tsx +0 -68
  553. package/src/react/ElevCanvas.web.tsx +0 -42
  554. package/src/react/ElevationSheet.tsx +0 -88
  555. package/src/render/elevationView.ts +0 -73
@@ -3,7 +3,7 @@ import { openingHeights, wallHeight } from '../model/elevation';
3
3
  import { pushHistory } from '../model/history';
4
4
  import { uid } from '../model/ids';
5
5
  import { driveCheck, edgeS, openingSpan, refS, rerootLock, sameRef } from '../model/openings';
6
- import { SK } from '../model/store';
6
+ import { planSK as SK } from '../model/store';
7
7
  import type { Opening, OpeningDim, OpeningEdge, OpeningRef } from '../model/types';
8
8
  import { fmtLen } from '../units';
9
9
  import { editPlacedOpeningDim, fits, overDefined } from './openings';
@@ -25,7 +25,7 @@ export function editSill(opId: string): void {
25
25
  if (!l) return;
26
26
  const { sill, head } = openingHeights(o), h = head - sill, H = wallHeight(l);
27
27
  openKeypad({
28
- title: Name(o) + ' sill height', sub: 'Floor to the bottom of the ' + nameOf(o) + ' — locks its height above the floor', initial: fmtLen(sill), selectAll: true, laser: true,
28
+ title: Name(o) + ' sill height', sub: 'Floor to the bottom of the ' + nameOf(o) + ' — locks its height above the floor', initial: fmtLen(sill), value: sill, selectAll: true, laser: true,
29
29
  onDone: v => {
30
30
  if (v < 0) return;
31
31
  if (v + h > H + 1e-6) {
@@ -46,7 +46,7 @@ export function editOpeningHeight(opId: string): void {
46
46
  if (!l) return;
47
47
  const { sill, head } = openingHeights(o), H = wallHeight(l);
48
48
  openKeypad({
49
- title: Name(o) + ' height', sub: 'Bottom to top of the ' + nameOf(o), initial: fmtLen(head - sill), selectAll: true, laser: true,
49
+ title: Name(o) + ' height', sub: 'Bottom to top of the ' + nameOf(o), initial: fmtLen(head - sill), value: head - sill, selectAll: true, laser: true,
50
50
  onDone: v => {
51
51
  if (v <= 0) return;
52
52
  if (sill + v > H + 1e-6) {
@@ -67,7 +67,7 @@ export function editWallHeight(lid: string): void {
67
67
  if (!l) return;
68
68
  const tallest = Math.max(0, ...Object.values(sk.openings).filter(o => o.line === lid).map(o => openingHeights(o).head));
69
69
  openKeypad({
70
- title: 'Wall height', sub: 'Floor to ceiling, this wall only', initial: fmtLen(wallHeight(l)), selectAll: true, laser: true,
70
+ title: 'Wall height', sub: 'Floor to ceiling, this wall only', initial: fmtLen(wallHeight(l)), value: wallHeight(l), selectAll: true, laser: true,
71
71
  onDone: v => {
72
72
  if (v <= 0) return;
73
73
  if (v < tallest - 1e-6) { toast('A door or window here reaches ' + fmtLen(tallest) + '. Enter ' + fmtLen(tallest) + ' or more, or lower it first.', { warn: true }); return; }
@@ -96,7 +96,7 @@ export function editElevationPosition(opId: string, edge: OpeningEdge, ref: Open
96
96
  const d: OpeningDim = { id: uid('od'), edge, ref, value: cur, sign: e >= r ? 1 : -1, driving: true };
97
97
  openKeypad({
98
98
  title, sub: 'Locks the ' + nameOf(o) + '’s position',
99
- initial: fmtLen(cur), selectAll: true, laser: true,
99
+ initial: fmtLen(cur), value: cur, selectAll: true, laser: true,
100
100
  onDone: v => {
101
101
  if (v < 0) return;
102
102
  if (!fits(o, d, v)) return;
@@ -0,0 +1,82 @@
1
+ import { afterEdit, tryAddConstraint } from '../app/edit';
2
+ import { ed } from '../app/state';
3
+ import { dropHistory, pushHistory } from '../model/history';
4
+ import { uid } from '../model/ids';
5
+ import { addPoint } from '../model/sketch';
6
+ import { replaceSK, SK } from '../model/store';
7
+ import type { Sketch } from '../model/types';
8
+ import { requestDraw } from '../render/view';
9
+ import { solver } from '../solver';
10
+ import { fmtLen } from '../units';
11
+ import { openKeypad, toast, updateCtx } from '../editor/ports';
12
+ import { cornerAt, cutCorner } from './corner';
13
+
14
+ /*
15
+ Fillet: round off a corner where two lines (or walls) meet. The corner becomes an arc tangent to both, the lines
16
+ stop where it starts (tools/corner.ts), and a driving radius holds it. Two walls get a curved wall of the same
17
+ thickness, and a space around the corner follows the curve.
18
+ */
19
+
20
+ /** The corner's two lines, if it can be rounded off. */
21
+ export function filletable(sk: Sketch, pid: string): [string, string] | null {
22
+ const k = cornerAt(sk, pid);
23
+ return k ? [k.l1, k.l2] : null;
24
+ }
25
+
26
+ /** The biggest radius that fits the corner (the arc would reach the end of the shorter line). */
27
+ export function maxFillet(sk: Sketch, pid: string): number {
28
+ const k = cornerAt(sk, pid);
29
+ return k ? Math.min(k.len1, k.len2) * Math.tan(k.th / 2) : 0;
30
+ }
31
+
32
+ /** The Fillet action: the keypad for the radius, then the corner is rounded off. */
33
+ export function filletPrompt(pid: string): void {
34
+ const sk = SK();
35
+ if (!cornerAt(sk, pid)) { toast('Select a corner where two lines meet to round it off.', { warn: true }); return; }
36
+ const most = maxFillet(sk, pid), start = Math.min(6, most * 0.5);
37
+ openKeypad({
38
+ title: 'Fillet radius', sub: 'Up to ' + fmtLen(most) + ' fits this corner', initial: fmtLen(Math.round(start * 16) / 16), value: Math.round(start * 16) / 16, selectAll: true, laser: true,
39
+ onDone: r => { filletCorner(pid, r); },
40
+ });
41
+ }
42
+
43
+ /** Round off corner `pid` with an arc of radius `r`. Returns the arc's id, or null (and says why) if it can't. */
44
+ export function filletCorner(pid: string, r: number): string | null {
45
+ const sk = SK(), k = cornerAt(sk, pid);
46
+ if (!k) { toast('Select a corner where two lines meet to round it off.', { warn: true }); return null; }
47
+ if (!(r > 0)) return null;
48
+ const d = r / Math.tan(k.th / 2);
49
+ if (d >= k.len1 - 1e-6 || d >= k.len2 - 1e-6) {
50
+ toast('That radius doesn’t fit this corner: ' + fmtLen(maxFillet(sk, pid)) + ' at most.', { warn: true });
51
+ return null;
52
+ }
53
+ pushHistory();
54
+ const before = JSON.stringify(sk), L1 = sk.lines[k.l1], L2 = sk.lines[k.l2], P = { ...k.P };
55
+ // the centre on the bisector, the radius away from both lines
56
+ const bx = k.u1.x + k.u2.x, by = k.u1.y + k.u2.y, bl = Math.hypot(bx, by) || 1, far = r / Math.sin(k.th / 2);
57
+ const c = addPoint(sk, P.x + (bx / bl) * far, P.y + (by / bl) * far), wall = L1.kind === 'wall' && L2.kind === 'wall';
58
+ const cut = cutCorner(sk, k, d, d, (t1, t2) => {
59
+ const id = uid('ar'), C = sk.points[c], A = sk.points[t1], B = sk.points[t2];
60
+ // counter-clockwise from its start to its end: the short way round, from one line to the other
61
+ const ccw = (A.x - C.x) * (B.y - C.y) - (A.y - C.y) * (B.x - C.x) > 0;
62
+ sk.arcs[id] = { c, a: ccw ? t1 : t2, b: ccw ? t2 : t1, constr: L1.constr && L2.constr, ...(wall ? { kind: 'wall' as const, thick: L1.thick } : {}) };
63
+ return id;
64
+ });
65
+ // tangent to both lines (on the side the centre is), and the radius
66
+ const side = (lid: string) => { const l = sk.lines[lid], A = sk.points[l.a], B = sk.points[l.b], o = sk.points[c]; return (B.x - A.x) * (o.y - A.y) - (B.y - A.y) * (o.x - A.x) < 0 ? -1 : 1; };
67
+ for (const cn of [...cut.cons, { type: 'tan' as const, l: k.l1, c: cut.edge, sign: side(k.l1) }, { type: 'tan' as const, l: k.l2, c: cut.edge, sign: side(k.l2) }]) tryAddConstraint(cn, { silent: true });
68
+ // its radius, the leader out through the middle of the arc (toward the old corner)
69
+ tryAddConstraint({ type: 'diam', c: cut.edge, value: 2 * r, driving: true, rad: true, ang: Math.atan2(P.y - sk.points[c].y, P.x - sk.points[c].x) }, { silent: true });
70
+ if (!solver.solve(sk).ok) {
71
+ replaceSK(JSON.parse(before) as Sketch);
72
+ dropHistory();
73
+ toast('That corner can’t be rounded off with the constraints it has.', { warn: true });
74
+ afterEdit();
75
+ return null;
76
+ }
77
+ ed.sel = [{ type: 'arc', id: cut.edge }];
78
+ afterEdit();
79
+ updateCtx(); requestDraw();
80
+ toast('Corner rounded off: ' + fmtLen(r) + ' radius');
81
+ return cut.edge;
82
+ }
@@ -1,6 +1,6 @@
1
1
  import { afterEdit, tryAddConstraint } from '../app/edit';
2
2
  import { ed } from '../app/state';
3
- import type { PEv } from './registry';
3
+ import { wallBand, type PEv } from './registry';
4
4
  import { hyp } from '../model/geom';
5
5
  import { dropHistory, pushHistory } from '../model/history';
6
6
  import { maybeRoom } from '../model/rooms';
@@ -8,10 +8,12 @@ import { settings } from '../model/settings';
8
8
  import { addLine, addPoint } from '../model/sketch';
9
9
  import { deg, JOIN_PX, simplifyStroke, SQUARE_DEG, squareUp, turn } from '../model/stroke';
10
10
  import { SK } from '../model/store';
11
- import type { Constraint, XY } from '../model/types';
11
+ import type { Constraint, LineKind, XY } from '../model/types';
12
12
  import { TH } from '../render/theme';
13
- import { mainT, requestDraw, S, s2w } from '../render/view';
13
+ import { mainT, requestDraw, requestOverlayDraw, S, s2w } from '../render/view';
14
14
  import { toast } from '../editor/ports';
15
+ import { intoWall } from '../editor/elevation';
16
+ import { drawsWalls } from '../editor/mode';
15
17
 
16
18
  /*
17
19
  Draw to shape: sketch freehand, and on release the stroke becomes the line segments it most likely meant —
@@ -25,7 +27,7 @@ import { toast } from '../editor/ports';
25
27
  * Turn a stroke (world points) into lines in the current sketch, as one edit the caller wraps in history.
26
28
  * Returns the new line ids, or null if the stroke was too small to mean anything.
27
29
  */
28
- export function strokeToLines(stroke: XY[], opts: { constr?: boolean } = {}): { lines: string[]; closed: boolean } | null {
30
+ export function strokeToLines(stroke: XY[], opts: { constr?: boolean; kind?: LineKind } = {}): { lines: string[]; closed: boolean } | null {
29
31
  const sk = SK(), T = mainT(), simp = simplifyStroke(stroke, T.s);
30
32
  if (!simp) return null;
31
33
  const closed = simp.closed, pts = squareUp(simp.pts, closed);
@@ -37,7 +39,7 @@ export function strokeToLines(stroke: XY[], opts: { constr?: boolean } = {}): {
37
39
  };
38
40
  const ids = pts.map((p, i) => (!closed && (i === 0 || i === pts.length - 1) && near(p)) || addPoint(sk, p.x, p.y));
39
41
  const segs = closed ? ids.length : ids.length - 1, lines: string[] = [];
40
- for (let i = 0; i < segs; i++) lines.push(addLine(sk, ids[i], ids[(i + 1) % ids.length], { constr: !!opts.constr }));
42
+ for (let i = 0; i < segs; i++) lines.push(addLine(sk, ids[i], ids[(i + 1) % ids.length], { kind: opts.kind, constr: !!opts.constr }));
41
43
  if (settings.autoCons) {
42
44
  // level / plumb segments, then square corners that aren't already square by H + V
43
45
  const axis: ('h' | 'v' | null)[] = [];
@@ -65,7 +67,7 @@ export const freehandTool = {
65
67
  busy() { return !!this.st; },
66
68
  hover() {},
67
69
  down(ev: PEv) {
68
- const [x, y] = s2w(ev.sx, ev.sy);
70
+ const [x, y] = intoWall(...s2w(ev.sx, ev.sy)); // (in the wall view, inside the wall)
69
71
  this.st = { pts: [{ x, y }], last: [ev.sx, ev.sy] };
70
72
  this.prev();
71
73
  },
@@ -73,32 +75,34 @@ export const freehandTool = {
73
75
  if (!this.st) return;
74
76
  if (hyp(ev.sx - this.st.last[0], ev.sy - this.st.last[1]) < 2) return;
75
77
  this.st.last = [ev.sx, ev.sy];
76
- const [x, y] = s2w(ev.sx, ev.sy);
78
+ const [x, y] = intoWall(...s2w(ev.sx, ev.sy));
77
79
  this.st.pts.push({ x, y });
78
80
  this.prev();
79
81
  },
80
82
  prev() {
81
83
  const pts = this.st!.pts;
82
84
  ed.preview = (c, T) => {
83
- c.save(); c.strokeStyle = ed.drawConstr ? TH.constr : TH.ink; c.lineWidth = 2.5; c.lineCap = 'round'; c.lineJoin = 'round';
84
- if (ed.drawConstr) c.setLineDash([6, 5]);
85
- c.beginPath();
86
- pts.forEach((p, i) => { const [x, y] = S(T, p.x, p.y); i ? c.lineTo(x, y) : c.moveTo(x, y); });
85
+ const wall = drawsWalls(), trace = () => pts.forEach((p, i) => { const [x, y] = S(T, p.x, p.y); i ? c.lineTo(x, y) : c.moveTo(x, y); });
86
+ if (wall) wallBand(c, T, trace);
87
+ c.save(); c.strokeStyle = ed.drawConstr && !wall ? TH.constr : TH.ink; c.lineWidth = 2.5; c.lineCap = 'round'; c.lineJoin = 'round';
88
+ if (ed.drawConstr && !wall) c.setLineDash([6, 5]);
89
+ c.beginPath(); trace();
87
90
  c.stroke(); c.restore();
88
91
  };
89
- requestDraw();
92
+ requestOverlayDraw();
90
93
  },
91
94
  up() {
92
95
  if (!this.st) return;
93
96
  const pts = this.st.pts;
94
97
  this.reset();
95
98
  pushHistory();
96
- const r = strokeToLines(pts, { constr: ed.drawConstr });
99
+ // (walls with the Walls toggle on; a wall is never a guide)
100
+ const wall = drawsWalls(), r = strokeToLines(pts, wall ? { kind: 'wall' } : { constr: ed.drawConstr });
97
101
  if (!r) { dropHistory(); requestDraw(); return; }
98
- ed.sel = r.lines.map(id => ({ type: 'line' as const, id }));
102
+ ed.sel = [];
99
103
  afterEdit();
100
- const n = r.lines.length;
101
- toast((r.closed ? 'Closed shape, ' : '') + n + ' line' + (n > 1 ? 's' : '') + '. Drag points or add constraints to adjust — select a line and tap + for its length.');
104
+ const n = r.lines.length, what = wall ? 'wall' : 'line';
105
+ toast((r.closed ? 'Closed shape, ' : '') + n + ' ' + what + (n > 1 ? 's' : '') + '. Drag points or add constraints to adjust — select a ' + what + ' and tap + for its length.');
102
106
  },
103
107
  hint() { return 'Draw freehand. When you lift, it becomes straight lines — then adjust with constraints and lengths.'; },
104
108
  };
@@ -1,4 +1,7 @@
1
- import { makeAselectTool, withHandles, arrowTool, calibTool, cloudTool, penTool, textTool } from './annotate';
1
+ import {
2
+ makeAselectTool, withHandles, arrowTool, calibTool, cloudTool, markCircleTool, markLineTool, markRectTool, markTriangleTool,
3
+ penTool, textTool,
4
+ } from './annotate';
2
5
  import { dimTool } from './dim';
3
6
  import { freehandTool } from './freehand';
4
7
  import { makeLineTool, type LineTool } from './line';
@@ -7,6 +10,7 @@ import { ed } from '../app/state';
7
10
  import { tools } from './registry';
8
11
  import { selectTool } from './select';
9
12
  import { arcTool, circleTool, rectTool } from './shapes';
13
+ import { cabinetTool, heightTool, wallDoorTool, wallWindowTool } from '../editor/elevation';
10
14
 
11
15
  export { tools, setTool, cancelTool } from './registry';
12
16
  export type { LineTool } from './line';
@@ -15,9 +19,8 @@ export type { LineTool } from './line';
15
19
  export function registerTools(): void {
16
20
  Object.assign(tools, {
17
21
  select: selectTool,
18
- line: makeLineTool('line'),
22
+ line: makeLineTool(),
19
23
  freehand: freehandTool,
20
- wall: makeLineTool('wall'),
21
24
  rect: rectTool,
22
25
  circle: circleTool,
23
26
  arc: arcTool,
@@ -33,9 +36,19 @@ export function registerTools(): void {
33
36
  pen: withHandles(penTool),
34
37
  arrow: withHandles(arrowTool),
35
38
  cloud: withHandles(cloudTool),
39
+ // the markup shapes (Arrow's group on the rail): 'm' for markup, apart from Draft's Line, Rectangle and Circle
40
+ mline: withHandles(markLineTool),
41
+ mrect: withHandles(markRectTool),
42
+ mtri: withHandles(markTriangleTool),
43
+ mcircle: withHandles(markCircleTool),
36
44
  text: textTool,
37
45
  calib: calibTool,
46
+ // the wall view's own
47
+ wdoor: wallDoorTool,
48
+ wwindow: wallWindowTool,
49
+ cabinet: cabinetTool,
50
+ height: heightTool,
38
51
  });
39
52
  }
40
53
 
41
- export const lineTool = (t: string): LineTool | null => (t === 'line' || t === 'wall' ? (tools[t] as LineTool) : null);
54
+ export const lineTool = (t: string): LineTool | null => (t === 'line' ? (tools[t] as LineTool) : null);
package/src/tools/line.ts CHANGED
@@ -5,15 +5,18 @@ import { pushHistory } from '../model/history';
5
5
  import { roomGeom, maybeRoom } from '../model/rooms';
6
6
  import { settings } from '../model/settings';
7
7
  import { addLine, gc, pinnedPoints } from '../model/sketch';
8
- import { SK } from '../model/store';
8
+ import { SK, store } from '../model/store';
9
9
  import type { LineKind } from '../model/types';
10
10
  import { TH } from '../render/theme';
11
- import { snapAt } from '../render/snap';
12
- import { requestDraw, S } from '../render/view';
11
+ import { shiftSnap, snap45, snapAt } from '../render/snap';
12
+ import { requestDraw, requestOverlayDraw, S } from '../render/view';
13
13
  import { fmtArea } from '../units';
14
- import { commitPoint, applyPending, lengthPreview, PT, rememberHover, type PEv, type Tool } from './registry';
14
+ import { commitPoint, applyPending, lengthPreview, PT, rememberHover, wallBand, type PEv, type Tool } from './registry';
15
15
  import { openKeypad, toast, updateCtx, updateHint } from '../editor/ports';
16
16
  import type { KeyIn } from '../editor/keys';
17
+ import { intoWall, PAST_WALL } from '../editor/elevation';
18
+ import { drawsWalls } from '../editor/mode';
19
+ import { splitWallsAtEnds } from './modify';
17
20
 
18
21
  export interface LineState {
19
22
  start: string | null;
@@ -21,6 +24,8 @@ export interface LineState {
21
24
  prev: string | null;
22
25
  chain: string[];
23
26
  down: { sx: number; sy: number } | null;
27
+ /** where a press that may drag out the first segment snapped (the segment's start, if it drags) */
28
+ downSnap: Snap | null;
24
29
  dir: [number, number];
25
30
  }
26
31
 
@@ -31,9 +36,13 @@ export interface LineTool extends Tool {
31
36
  finish(): void;
32
37
  }
33
38
 
34
- /** Line and wall tools: tap-tap chains, drag for one segment, type a length, close back to the start. */
35
- export function makeLineTool(kind: LineKind): LineTool {
36
- const fresh = (dir: [number, number]): LineState => ({ start: null, first: null, prev: null, chain: [], down: null, dir });
39
+ /**
40
+ * The Line tool: tap-tap chains, drag for one segment, type a length, close back to the start. With the rail's Walls
41
+ * toggle on it draws walls (along their centre), as every drawing tool does.
42
+ */
43
+ export function makeLineTool(): LineTool {
44
+ const kindNow = (): LineKind => (drawsWalls() ? 'wall' : 'line');
45
+ const fresh = (dir: [number, number]): LineState => ({ start: null, first: null, prev: null, chain: [], down: null, downSnap: null, dir });
37
46
  const t = {
38
47
  st: fresh([1, 0]),
39
48
  reset() {
@@ -41,44 +50,65 @@ export function makeLineTool(kind: LineKind): LineTool {
41
50
  if (had) pinnedPoints.delete(had);
42
51
  this.st = fresh(this.st ? this.st.dir : [1, 0]);
43
52
  ed.snapState = null; ed.preview = null;
44
- if (had) { gc(SK()); analyze(); requestDraw(); }
53
+ // tidy the drawing it was building on — unless a new session cleared it (left the editor mid-chain)
54
+ if (had && store.project) { gc(SK()); analyze(); requestDraw(); }
45
55
  updateCtx(); updateHint();
46
56
  },
47
57
  busy() { return !!this.st.start; },
48
58
  opts(ev: PEv) { return { from: this.st.start, prevLine: this.st.prev, touch: ev.touch }; },
49
- hover(ev: PEv) { ed.snapState = snapAt(ev.sx, ev.sy, this.opts(ev)); rememberHover(ed.snapState); this.setPreview(); requestDraw(); },
50
- setPreview() {
59
+ /** where the pointer snaps to (with Shift, from the line's start: level, plumb or 45°) */
60
+ at(ev: PEv): Snap {
61
+ const s = snapAt(ev.sx, ev.sy, this.opts(ev)), from = this.st.start ? null : this.dragFrom(ev);
62
+ if (!ev.shift) return s;
63
+ if (this.st.start) return shiftSnap(this.st.start, s);
64
+ if (from) { const q = snap45(from, s); return { x: q.x, y: q.y, kind: 'free', guides: [] }; }
65
+ return s;
66
+ },
67
+ /** dragging out the first segment: where it starts (null for a press that hasn't moved, or a hover) */
68
+ dragFrom(ev: PEv): Snap | null {
69
+ const st = this.st;
70
+ return st.down && st.downSnap && hyp(ev.sx - st.down.sx, ev.sy - st.down.sy) > 12 ? st.downSnap : null;
71
+ },
72
+ hover(ev: PEv) { ed.snapState = this.at(ev); rememberHover(ed.snapState); this.setPreview(this.st.start ? null : this.dragFrom(ev)); requestOverlayDraw(); },
73
+ /** the segment being drawn: from the last point (or, dragging out the first one, from where the drag began) */
74
+ setPreview(from: Snap | null = null) {
51
75
  const st = this.st;
52
- if (!st.start || !ed.snapState) { ed.preview = null; return; }
53
- const a = PT(st.start), b = { x: ed.snapState.x, y: ed.snapState.y };
76
+ if ((!st.start && !from) || !ed.snapState) { ed.preview = null; return; }
77
+ const a = st.start ? PT(st.start) : { x: from!.x, y: from!.y }, b = { x: ed.snapState.x, y: ed.snapState.y };
54
78
  if (hyp(b.x - a.x, b.y - a.y) > 1e-6) st.dir = norm2(b.x - a.x, b.y - a.y);
55
79
  ed.preview = (c, T) => {
56
- const [x1, y1] = S(T, a.x, a.y), [x2, y2] = S(T, b.x, b.y);
80
+ const [x1, y1] = S(T, a.x, a.y), [x2, y2] = S(T, b.x, b.y), wall = kindNow() === 'wall';
81
+ if (wall) wallBand(c, T, () => { c.moveTo(x1, y1); c.lineTo(x2, y2); });
57
82
  c.save(); c.lineCap = 'butt';
58
- if (kind === 'wall') { c.strokeStyle = 'rgba(0,102,255,.25)'; c.lineWidth = 4.5 * T.s; c.beginPath(); c.moveTo(x1, y1); c.lineTo(x2, y2); c.stroke(); }
59
83
  c.strokeStyle = TH.blue; c.lineWidth = 2;
60
- if (ed.drawConstr) c.setLineDash([6, 5]);
84
+ if (ed.drawConstr && !wall) c.setLineDash([6, 5]);
61
85
  c.beginPath(); c.moveTo(x1, y1); c.lineTo(x2, y2); c.stroke(); c.restore();
62
86
  lengthPreview(c, T, a, b);
63
87
  };
64
88
  },
65
- down(ev: PEv) { this.st.down = { sx: ev.sx, sy: ev.sy }; this.hover(ev); },
89
+ down(ev: PEv) {
90
+ this.st.down = { sx: ev.sx, sy: ev.sy };
91
+ this.st.downSnap = this.st.start ? null : snapAt(ev.sx, ev.sy, { touch: ev.touch });
92
+ this.hover(ev);
93
+ },
66
94
  move(ev: PEv) { this.hover(ev); },
67
95
  up(ev: PEv) {
68
96
  const st = this.st;
69
97
  const dragged = !!st.down && hyp(ev.sx - st.down.sx, ev.sy - st.down.sy) > 12;
70
98
  if (!st.start) {
71
99
  pushHistory();
72
- const s0 = dragged ? snapAt(st.down!.sx, st.down!.sy, { touch: ev.touch }) : snapAt(ev.sx, ev.sy, { touch: ev.touch });
100
+ const s0 = dragged ? st.downSnap ?? snapAt(st.down!.sx, st.down!.sy, { touch: ev.touch }) : snapAt(ev.sx, ev.sy, { touch: ev.touch });
73
101
  const a = commitPoint(s0);
74
102
  applyPending(a.cons);
75
103
  st.start = a.id; st.first = a.id; st.chain = [a.id];
104
+ // (drawing: nothing left highlighted — neither what was selected before nor, as it goes, what's drawn)
105
+ ed.sel = [];
76
106
  pinnedPoints.clear(); pinnedPoints.add(a.id);
77
- if (dragged) this.segmentTo(snapAt(ev.sx, ev.sy, this.opts(ev)));
107
+ if (dragged) this.segmentTo(this.at(ev));
78
108
  afterEdit(); this.hover(ev); updateCtx(); updateHint();
79
109
  return;
80
110
  }
81
- const s = snapAt(ev.sx, ev.sy, this.opts(ev));
111
+ const s = this.at(ev);
82
112
  if (s.kind === 'point' && s.ref === st.start) { this.finish(); return; }
83
113
  pushHistory(); this.segmentTo(s); afterEdit();
84
114
  if (this.st.start) this.hover(ev);
@@ -87,10 +117,16 @@ export function makeLineTool(kind: LineKind): LineTool {
87
117
  const st = this.st;
88
118
  const a = PT(st.start!);
89
119
  if (hyp(s.x - a.x, s.y - a.y) < 1e-6) return;
120
+ // in the wall view: not past the wall (a typed length that would run out of it)
121
+ const [ix, iy] = intoWall(s.x, s.y);
122
+ if (Math.abs(ix - s.x) > 1e-6 || Math.abs(iy - s.y) > 1e-6) { toast(PAST_WALL, { warn: true }); return; }
90
123
  const b = commitPoint(s);
91
124
  if (b.id === st.start) return;
92
- const lid = addLine(SK(), st.start!, b.id, { kind, constr: ed.drawConstr });
125
+ // walls are always solid (the construction-lines toggle is for lines)
126
+ const kind = kindNow(), lid = addLine(SK(), st.start!, b.id, { kind, constr: kind !== 'wall' && ed.drawConstr });
93
127
  applyPending(b.cons);
128
+ // (a wall ending partway along another wall splits that one there)
129
+ splitWallsAtEnds(SK(), lid);
94
130
  if (settings.autoCons) {
95
131
  for (const g of s.guides || []) {
96
132
  if (g.kind === 'H') tryAddConstraint({ type: 'h', a: g.from, b: b.id }, { silent: true });
@@ -105,7 +141,7 @@ export function makeLineTool(kind: LineKind): LineTool {
105
141
  if (closed) {
106
142
  const loop = st.chain.slice(0, -1);
107
143
  const sk = SK();
108
- const rid = kind === 'wall' || !ed.drawConstr ? maybeRoom(sk, loop) : null;
144
+ const rid = kindNow() === 'wall' || !ed.drawConstr ? maybeRoom(sk, loop) : null;
109
145
  this.reset();
110
146
  if (rid) toast(sk.rooms[rid].name + ' added — ' + fmtArea(roomGeom(sk, sk.rooms[rid]).area));
111
147
  }
@@ -150,8 +186,8 @@ export function makeLineTool(kind: LineKind): LineTool {
150
186
  return false;
151
187
  },
152
188
  hint() {
153
- if (this.st.start) return ed.lastPointerTouch ? 'Tap the next point. Tap <b>Type length</b> to enter an exact value.' : 'Click the next point, or just type a length and press Enter. Esc finishes.';
154
- return kind === 'wall' ? 'Draw wall centerlines. Tap to start — walls are 4 1/2" thick; change it after.' : 'Tap to place points. Drawing back to the start closes the shape.';
189
+ if (this.st.start) return ed.lastPointerTouch ? 'Tap the next point. Tap the last point again to finish, or the first to close the shape.' : 'Click the next point (hold Shift for level, plumb or 45°), or just type a length and press Enter. Esc finishes.';
190
+ return kindNow() === 'wall' ? 'Draw wall centerlines. Tap to start — walls are 4 1/2" thick; change it after.' : 'Tap to place points. Drawing back to the start closes the shape.';
155
191
  },
156
192
  };
157
193
  return t;
@@ -1,15 +1,17 @@
1
- import { afterEdit, solveOrRevert, tryAddConstraint } from '../app/edit';
1
+ import { afterEdit, holdFaces, solveOrRevert, tryAddConstraint, undo } from '../app/edit';
2
2
  import { ed } from '../app/state';
3
+ import { splitFaces } from '../model/faces';
3
4
  import { clamp, hyp, lineCircleT, norm2, projT, segInter } from '../model/geom';
4
5
  import { dropHistory, pushHistory } from '../model/history';
5
6
  import { uid } from '../model/ids';
6
7
  import { findFace, maybeRoom, roomGeom } from '../model/rooms';
7
8
  import { addLine, addPoint } from '../model/sketch';
8
9
  import { SK } from '../model/store';
9
- import type { Constraint } from '../model/types';
10
+ import type { Constraint, Sketch } from '../model/types';
10
11
  import { hitAny } from '../render/hit';
11
12
  import { outsideSide } from '../model/planGeom';
12
13
  import { mainT, requestDraw, s2w } from '../render/view';
14
+ import { placeTargets, snapSlide } from '../editor/elevation';
13
15
  import { fmtArea, fmtLen } from '../units';
14
16
  import type { PEv } from './registry';
15
17
  import { openKeypad, toast } from '../editor/ports';
@@ -141,20 +143,31 @@ export const openingTool = {
141
143
  move(ev: PEv) { this.hover(ev); },
142
144
  up(ev: PEv) {
143
145
  const h = hitAny(ev.sx, ev.sy, { touch: ev.touch, only: 'geom' });
144
- if (!h || h.type !== 'line') { toast('Tap a wall to place it.'); return; }
146
+ if (!h || h.type !== 'line') { toast('Tap a wall to place it.', { warn: true }); return; }
145
147
  const sk = SK(), l = sk.lines[h.id], A = sk.points[l.a], B = sk.points[l.b];
148
+ // doors and windows are a wall's: not on a guide; a plain line becomes a wall to take one
149
+ if (l.constr) { toast('Doors and windows go in walls — that’s a construction line.', { warn: true }); return; }
150
+ const toWall = l.kind !== 'wall';
146
151
  const [wx, wy] = s2w(ev.sx, ev.sy);
147
152
  const L = hyp(B.x - A.x, B.y - A.y), w = 36;
148
- if (L < w + 2) { toast('That wall is too short for a ' + fmtLen(w) + ' ' + ed.openingType + '.', { warn: true }); return; }
149
- const at = clamp(projT(A, B, { x: wx, y: wy }) * L, w / 2, L - w / 2);
153
+ if (L < w + 2) { toast('That ' + (toWall ? 'line' : 'wall') + ' is too short for a ' + fmtLen(w) + ' ' + ed.openingType + '.', { warn: true }); return; }
154
+ // (snapped, as one slid along the wall is: an edge to the wall's ends, its corners, another door's or window's edge)
155
+ const at0 = projT(A, B, { x: wx, y: wy }) * L;
156
+ const at = clamp(at0 + snapSlide(at0, w, 0, placeTargets(sk, h.id), (ev.touch ? 16 : 10) / mainT().s), w / 2, L - w / 2);
150
157
  const side = (B.x - A.x) * (wy - A.y) - (B.y - A.y) * (wx - A.x) >= 0;
151
158
  pushHistory();
159
+ if (toWall) { l.kind = 'wall'; l.thick = l.thick || 4.5; }
152
160
  const id = uid('o');
153
- sk.openings[id] = { line: h.id, at, w, type: ed.openingType, flip: !side };
154
- ed.sel = [{ type: 'opening', id }];
161
+ sk.openings[id] = { line: h.id, at, w, wSet: false, type: ed.openingType, flip: !side };
162
+ // (placed, not selected: tap it to size it, flip it or slide it)
163
+ ed.sel = [];
164
+ // (a line made a wall: what's measured on the faces of the walls it meets holds)
165
+ if (toWall) holdFaces();
155
166
  afterEdit();
167
+ // (one undo step takes back both)
168
+ if (toWall) toast('Made that line a wall for the ' + ed.openingType + '.', { action: { label: 'Undo', fn: undo } });
156
169
  },
157
- hint() { return 'Tap a wall to place a 3\'-0" ' + ed.openingType + '. Slide it along the wall with Select.'; },
170
+ hint() { return 'Tap a wall to place a 3\'-0" ' + ed.openingType + ' (a line you tap becomes a wall). Slide it along the wall with Select.'; },
158
171
  };
159
172
 
160
173
  export const roomTool = {
@@ -169,8 +182,8 @@ export const roomTool = {
169
182
  if (!face) { toast('Tap inside a closed shape. That spot isn’t enclosed by lines.', { warn: true }); return; }
170
183
  pushHistory();
171
184
  const id = maybeRoom(sk, face.loop, face.edges);
172
- if (!id) { dropHistory(); toast('That area is too small for a space.'); return; }
173
- ed.sel = [{ type: 'room', id }];
185
+ if (!id) { dropHistory(); toast('That area is too small for a space.', { warn: true }); return; }
186
+ ed.sel = [];
174
187
  afterEdit();
175
188
  toast(sk.rooms[id].name + ' — ' + fmtArea(roomGeom(sk, sk.rooms[id]).area));
176
189
  },
@@ -185,44 +198,93 @@ export const roomTool = {
185
198
  export function splitLine(lid: string): string | null {
186
199
  const sk = SK(), l = sk.lines[lid];
187
200
  if (!l) return null;
201
+ const A = sk.points[l.a], B = sk.points[l.b];
202
+ if (hyp(B.x - A.x, B.y - A.y) < 1e-6) return null;
203
+ pushHistory();
204
+ const nid = splitLineAt(sk, lid, addPoint(sk, (A.x + B.x) / 2, (A.y + B.y) / 2));
205
+ ed.sel = nid ? [{ type: 'line', id: lid }, { type: 'line', id: nid }] : [];
206
+ afterEdit();
207
+ toast('Split into two lines. Drag the middle point to move the split.');
208
+ return nid;
209
+ }
210
+
211
+ /**
212
+ * Split line `lid` at point `pid`, which lies on it between its ends: it runs to pid, and a new line (returned) runs on
213
+ * from there to its old end — the same kind, thickness and height. The two stay collinear and keep its constraints
214
+ * (see splitLine), its spaces, its doors and windows (each on the piece it's on, measured from where it was) and what's
215
+ * drawn on its faces. Part of an edit: the caller pushes history and runs afterEdit.
216
+ */
217
+ export function splitLineAt(sk: Sketch, lid: string, pid: string): string | null {
218
+ const l = sk.lines[lid], P = sk.points[pid];
219
+ if (!l || !P) return null;
188
220
  const A = sk.points[l.a], B = sk.points[l.b], L = hyp(B.x - A.x, B.y - A.y);
189
221
  if (L < 1e-6) return null;
190
- pushHistory();
191
- const oldA = l.a, oldB = l.b, side = outsideSide(sk, lid);
192
- const m = addPoint(sk, (A.x + B.x) / 2, (A.y + B.y) / 2);
193
- l.b = m;
194
- const nid = addLine(sk, m, oldB, { kind: l.kind, constr: l.constr, thick: l.thick });
222
+ const s = clamp(projT(A, B, P), 0, 1) * L, oldA = l.a, oldB = l.b, side = outsideSide(sk, lid);
223
+ l.b = pid;
224
+ const nid = addLine(sk, pid, oldB, { kind: l.kind, constr: l.constr, thick: l.thick });
225
+ if (l.height != null) sk.lines[nid].height = l.height;
195
226
  const copies: Constraint[] = [];
196
227
  for (const id in sk.cons) {
197
228
  const c = sk.cons[id];
198
229
  const onAB = (c.a === oldA && c.b === oldB) || (c.a === oldB && c.b === oldA);
199
- if ((c.type === 'h' || c.type === 'v') && onAB) { c.a = oldA; c.b = m; copies.push({ type: c.type, a: m, b: oldB }); }
230
+ if ((c.type === 'h' || c.type === 'v') && onAB) { c.a = oldA; c.b = pid; copies.push({ type: c.type, a: pid, b: oldB }); }
200
231
  else if ((c.type === 'par' || c.type === 'perp' || c.type === 'col') && (c.l1 === lid || c.l2 === lid)) copies.push(Object.assign({}, c, c.l1 === lid ? { l1: nid } : { l2: nid }));
201
232
  else if (c.type === 'dist' && c.line === lid) {
202
233
  // still the full end-to-end distance; moved out beside the wall so the new middle point stays reachable
203
234
  c.line = null;
204
235
  if (!c.off) c.off = (30 / mainT().s) * side * (c.a === oldA ? 1 : -1);
236
+ // (one measured on the wall's face no longer has one wall to be measured along: end to end, as it is now)
237
+ if (c.face) { delete c.face; if (c.driving !== false) c.value = L; }
205
238
  }
206
239
  else if (c.type === 'mid' && c.l === lid) delete sk.cons[id]; // "midpoint of" no longer means the same line
240
+ // (the point it's split at is an end of both pieces now, not on the line)
241
+ else if (c.type === 'online' && c.p === pid && c.l === lid) delete sk.cons[id];
207
242
  }
208
243
  for (const c of copies) tryAddConstraint(c, { silent: true });
209
244
  tryAddConstraint({ type: 'col', l1: lid, l2: nid }, { silent: true });
210
- // rooms: put the new point into any loop that ran along this line
245
+ // rooms: put the split point into any loop that ran along this line
211
246
  for (const id in sk.rooms) {
212
247
  const r = sk.rooms[id], loop = r.loop;
213
248
  for (let i = 0; i < loop.length; i++) {
214
249
  const p = loop[i], q = loop[(i + 1) % loop.length];
215
250
  if (r.edges ? r.edges[i] !== lid : !((p === oldA && q === oldB) || (p === oldB && q === oldA))) continue;
216
- loop.splice(i + 1, 0, m);
217
- // edges follow the walk direction: oldA→m is the original line, m→oldB the new one
251
+ loop.splice(i + 1, 0, pid);
252
+ // edges follow the walk direction: oldA→pid is the original line, pid→oldB the new one
218
253
  if (r.edges) r.edges.splice(i, 1, ...(p === oldA ? [lid, nid] : [nid, lid]));
219
254
  break;
220
255
  }
221
256
  }
222
- // doors/windows past the split move to the second piece
223
- for (const id in sk.openings) { const o = sk.openings[id]; if (o.line === lid && o.at > L / 2) { o.line = nid; o.at -= L / 2; } }
224
- ed.sel = [{ type: 'line', id: lid }, { type: 'line', id: nid }];
225
- afterEdit();
226
- toast('Split into two lines. Drag the middle point to move the split.');
257
+ for (const id in sk.openings) {
258
+ const o = sk.openings[id];
259
+ // doors/windows past the split move to the second piece
260
+ if (o.line === lid && o.at > s) { o.line = nid; o.at -= s; }
261
+ // and what's measured from the far corner of the wall is measured from that corner still — the new piece's
262
+ for (const d of o.dims ?? []) if ('face' in d.ref && d.ref.line === lid && d.ref.face === 'b') d.ref = { ...d.ref, line: nid };
263
+ }
264
+ // and so does what's drawn on the wall's faces past it
265
+ splitFaces(sk, lid, nid, s);
227
266
  return nid;
228
267
  }
268
+
269
+ /**
270
+ * A wall drawn to (or from) partway along another wall splits that one there, so they meet at a corner they share — as
271
+ * three walls meeting at a point do. Lines never split anything, and nothing splits a line. Part of an edit.
272
+ */
273
+ export function splitWallsAtEnds(sk: Sketch, lid: string): void {
274
+ const l = sk.lines[lid];
275
+ if (!l || l.kind !== 'wall' || l.constr) return;
276
+ for (const pid of [l.a, l.b]) {
277
+ const P = sk.points[pid];
278
+ if (!P) continue;
279
+ for (const [wid, w] of Object.entries(sk.lines)) {
280
+ if (wid === lid || w.kind !== 'wall' || w.constr || w.a === pid || w.b === pid) continue;
281
+ const A = sk.points[w.a], B = sk.points[w.b], L = hyp(B.x - A.x, B.y - A.y);
282
+ if (!A || !B || L < 1e-6) continue;
283
+ const t = projT(A, B, P), off = Math.abs((P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x)) / L;
284
+ // (on it, and an inch or more from either end)
285
+ if (off > 1e-3 || t * L < 1 || (1 - t) * L < 1) continue;
286
+ splitLineAt(sk, wid, pid);
287
+ break;
288
+ }
289
+ }
290
+ }