@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
@@ -1,28 +1,36 @@
1
- import { afterEdit, solveSketch, tryAddConstraint, undo } from '../app/edit';
1
+ import { afterEdit, holdFaces, solveSketch, tryAddConstraint, undo } from '../app/edit';
2
2
  import { ed } from '../app/state';
3
- import { CONS_LABEL } from '../model/constraints';
3
+ import { CONS_LABEL, measure } from '../model/constraints';
4
4
  import { dropHistory, pushHistory } from '../model/history';
5
- import { createsCycle, facingEdges, nearerEdge } from '../model/openings';
5
+ import { createsCycle, edgePoint, facingEdges, nearerEdge } from '../model/openings';
6
+ import { cabOfPoint } from '../model/faces';
6
7
  import { settings } from '../model/settings';
7
- import { connectedGeometry, curveInfo, deleteSel, mergePoints } from '../model/sketch';
8
- import { page, saveSoon, SK } from '../model/store';
8
+ import { connectedGeometry, curveInfo, deleteSel, joinPoints } from '../model/sketch';
9
+ import { page, planSK, replaceSK, saveSoon, SK } from '../model/store';
9
10
  import { fitView, requestDraw } from '../render/view';
10
11
  import { convertNote } from '../tools/annotate';
11
- import { dimAngle, dimCurve, dimLine, dimPointLine, dimPoints, dimTwoLines, editDim, toggleRef } from '../tools/dim';
12
+ import { cornerAngle, dimCurve, dimEdges, dimLine, dimPointLine, dimPoints, dimTwoLines, editDim, faceChoices, onChoice, setDimFace, toggleRef } from '../tools/dim';
13
+ import { edgeLabel, edgeOf } from '../model/planGeom';
12
14
  import { offsetPrompt, splitLine } from '../tools/modify';
15
+ import { filletable, filletPrompt } from '../tools/fillet';
16
+ import { chamferPrompt } from '../tools/chamfer';
13
17
  import { addOpeningDim, editOpeningDim, toggleOpeningDim } from '../tools/openings';
14
18
  import { setTool, tools } from '../tools/registry';
15
19
  import { fmtLen } from '../units';
16
- import { openElevation } from './elevation';
17
- import { editRoomName, openKeypad, openPalette, toast, updateCtx, updateTopBar } from './ports';
18
- import { cornerLines, selInfo } from './selection';
20
+ import { elevOpen, fitState, OPENING_SIZES, openElevation, openingLabel, overflow, restoreFit, TOO_SHORT, wallAction, wallBar, wallDelete, wallPlus } from './elevation';
21
+ import { pushedOut } from '../model/facesolve';
22
+ import { editRoomName, openKeypad, openPalette, toast, toggleImageOpacity, toggleImageRotation, toggleTileSize, updateCtx, updateTopBar } from './ports';
23
+ import { cornerLines, edgePair, selInfo } from './selection';
24
+ import { restackImage } from './project';
25
+ import { imageById } from '../model/images';
26
+ import { canOpenLink, canSaveDim, linkSizeVisible, openDimLink, openSelectedLink, saveDimToGroup, selectedLink, toggleLeader, unlinkDim } from './linkActions';
19
27
  /** Points a Lock on the current selection would pin (the origin is always fixed). */
20
28
  function lockTargets() {
21
29
  const sk = SK(), s = selInfo(), ids = new Set(s.points);
22
30
  s.lines.forEach(id => { ids.add(sk.lines[id].a); ids.add(sk.lines[id].b); });
23
31
  s.curves.forEach(id => { const ci = curveInfo(sk, id); if (ci)
24
32
  ids.add(ci.o.c); });
25
- return [...ids].filter(id => sk.points[id] && !sk.points[id].origin);
33
+ return [...ids].filter(id => sk.points[id] && !sk.points[id].origin && !sk.points[id].anchor);
26
34
  }
27
35
  /**
28
36
  * Lock is always the first action, set off by a divider, and shows its state:
@@ -34,7 +42,7 @@ function arrangeLock(items) {
34
42
  return items;
35
43
  const it = items[i];
36
44
  const sk = SK();
37
- const locked = it.act === 'imgLock' ? !!page().underlay?.locked : (() => { const l = lockTargets(); return l.length > 0 && l.every(id => sk.points[id].fixed); })();
45
+ const locked = it.act === 'imgLock' ? selImages().length > 0 && selImages().every(u => u.locked) : (() => { const l = lockTargets(); return l.length > 0 && l.every(id => sk.points[id].fixed); })();
38
46
  Object.assign(it, { ic: locked ? 'lock' : 'unlock', label: locked ? 'Locked' : 'Lock', on: undefined, locked });
39
47
  const rest = items.filter((_, k) => k !== i);
40
48
  const out = [it];
@@ -48,11 +56,21 @@ function arrangeLock(items) {
48
56
  out.pop();
49
57
  return out;
50
58
  }
59
+ /** What the bar calls one end of a door or window. */
60
+ function anchorLabel(id) {
61
+ return (planSK().openings[id.split('|')[0]]?.type === 'door' ? 'Door' : 'Window') + ' edge';
62
+ }
63
+ /** The selected images. */
64
+ const selImages = () => ed.sel.filter(x => x.type === 'underlay').map(x => imageById(x.id)).filter((u) => !!u);
65
+ /** The tools that place a door, window or cabinet (in the plan or the wall view), which select what they've placed. */
66
+ const PLACING = new Set(['door', 'window', 'wdoor', 'wwindow', 'cabinet']);
51
67
  function ctxItems() {
52
68
  const items = [], sk = SK(), s = selInfo();
53
69
  const add = (ic, label, act, o = {}) => items.push(Object.assign({ ic, label, act }, o));
54
70
  /** To wall (all the selected lines and curves) — or To line + Thickness when they're all walls already. */
55
71
  const wallItems = () => {
72
+ if (elevOpen())
73
+ return;
56
74
  const kinds = [...s.lines.map(id => sk.lines[id].kind), ...s.curves.map(id => curveInfo(sk, id)?.o.kind)];
57
75
  if (!kinds.length)
58
76
  return;
@@ -64,52 +82,118 @@ function ctxItems() {
64
82
  add('wall', 'To wall', 'kind');
65
83
  };
66
84
  if (ed.mode === 'annotate') {
67
- if (s.n === 1 && s.image) {
68
- const locked = !!page().underlay?.locked;
85
+ const ims = selImages();
86
+ if (ims.length && ims.length === s.n) {
87
+ // an image (or several): lock, its own scale, and which goes in front where they overlap
88
+ // (showing and hiding them all is the Layers menu's)
69
89
  add('lock', 'Lock', 'imgLock');
70
- if (!locked)
90
+ if (ims.length === 1 && !ims[0].locked)
71
91
  add('calib', 'Set scale', 'calibtool');
72
- add('eye', 'Hide image', 'toggleUnderlay');
92
+ if (ims.length === 1)
93
+ add('opacity', 'Opacity', 'imgOpacity');
94
+ // (turning moves it: not while it's locked, as it can't be moved or sized then)
95
+ if (ims.length === 1 && !ims[0].locked)
96
+ add('rotate', 'Rotate', 'imgRotate');
97
+ if (ims.length === 1 && page().underlays.length > 1) {
98
+ const at = page().underlays.indexOf(ims[0]), last = page().underlays.length - 1;
99
+ if (at < last)
100
+ add('tofront', 'To front', 'imgFront');
101
+ if (at > 0)
102
+ add('toback', 'To back', 'imgBack');
103
+ }
104
+ return items;
105
+ }
106
+ const lk = selectedLink();
107
+ if (lk) {
108
+ // a link's tile: opening it, its leader, its size (its name is on the tile, not here: the user's call, 2026-10-21)
109
+ if (canOpenLink(lk.link))
110
+ add('open', 'Open', 'linkOpen');
111
+ add('leader', 'Leader', 'linkLeader', { on: !!lk.leader });
112
+ // (its leader's colour, as all markup's, is the rail's colour dot's)
113
+ // (every tile on the page: a slider above the bar)
114
+ add('tilesize', 'Size', 'linkSize');
73
115
  return items;
74
116
  }
75
117
  if (s.notes.length) {
118
+ // (no Color: the rail's colour dot styles what's selected)
76
119
  const n = sk.notes.find(n => n.id === s.notes[0]);
77
- add('color', 'Color', 'palette');
78
120
  if (n && n.type === 'pen' && s.notes.length === 1)
79
121
  add('convert', 'To lines', 'convert');
122
+ if (linkSizeVisible())
123
+ add('tilesize', 'Size', 'linkSize');
80
124
  }
81
- else {
82
- add('color', 'Color', 'palette');
83
- if (page().underlay) {
84
- add('calib', 'Scale', 'calibtool');
85
- add('eye', settings.layers.underlay ? 'Hide image' : 'Show image', 'toggleUnderlay');
86
- }
87
- }
125
+ // nothing selected: no bar (the markup colour is the rail's colour dot, for what's selected too; the image's own
126
+ // options come with selecting it)
88
127
  return items;
89
128
  }
90
- if (ed.tool === 'line' || ed.tool === 'wall') {
129
+ if (ed.tool === 'line') {
91
130
  const st = tools[ed.tool].st;
92
- if (st.start) {
93
- add('typelen', 'Type length', 'typelen');
94
- if (st.chain.length >= 3)
95
- add('closeShape', 'Close', 'closeShape');
96
- add('finish', 'Finish', 'finish');
131
+ // drawing: no bar (tap the last point again to finish, the first to close the shape; Esc or Enter on a keyboard)
132
+ if (st.start)
97
133
  return items;
98
- }
99
134
  }
100
135
  if (ed.tool === 'dim') {
101
136
  add('dim', 'Driving', 'dimDrive', { on: ed.dimLayer === 'drive' });
102
137
  add('ref', 'Print', 'dimPrint', { on: ed.dimLayer === 'print' });
103
138
  return items;
104
139
  }
105
- // nothing selected: the bar stays empty (Fit and Icons live in the top bar; grid snap and auto-constrain in Settings)
106
- if (!s.n || ed.tool !== 'select')
140
+ // nothing selected: the bar stays empty (Fit and Icons live in the top bar; grid snap and auto-constrain in Settings).
141
+ // Just placed a door, window or cabinet (its tool still on): its bar, for its sizes
142
+ if (!s.n || (ed.tool !== 'select' && !PLACING.has(ed.tool)))
143
+ return items;
144
+ // the wall view's own things: a door or window, a cabinet, one of its dimension chips, the plan's edges
145
+ const wb = wallBar();
146
+ if (wb) {
147
+ if (wb.label)
148
+ items.push({ lbl: wb.label });
149
+ for (const it of wb.items)
150
+ add(it.ic, it.label, it.act);
107
151
  return items;
152
+ }
153
+ // on a wall face, walls don't exist: no To wall / Thickness
154
+ const wall = elevOpen();
108
155
  const P = s.points.length, L = s.lines.length, C = s.curves.length;
156
+ // Guide (dashed) is for lines: a wall is always solid (one that's a guide from before can still go back to Solid)
157
+ const allWalls = L + C > 0 && s.lines.every(id => sk.lines[id].kind === 'wall' && !sk.lines[id].constr) && s.curves.every(id => { const o = curveInfo(sk, id)?.o; return o?.kind === 'wall' && !o.constr; });
158
+ const guide = () => { if (!allWalls)
159
+ add('constr', 'Guide', 'constr'); };
160
+ // a wall's edge: named, with its wall view; with another edge, a line or a point, the distance between them (two
161
+ // edges of one wall: its thickness)
162
+ if (s.wfaces.length) {
163
+ // (a curved wall has no wall view: just its name)
164
+ if (s.n === 1) {
165
+ const [id, side] = edgeOf(s.wfaces[0]);
166
+ items.push({ lbl: edgeLabel(sk, id, side) });
167
+ if (sk.lines[id])
168
+ add('elev', 'Elevation', 'elev');
169
+ }
170
+ else if (edgePair(s)) {
171
+ const one = s.wfaces.length === 2 && edgeOf(s.wfaces[0])[0] === edgeOf(s.wfaces[1])[0];
172
+ add(one ? 'thick' : 'dim', one ? 'Thickness' : 'Distance', 'edgeDist');
173
+ }
174
+ return items;
175
+ }
109
176
  if (s.n === 1 && s.dims.length) {
110
177
  const c = sk.cons[s.dims[0]];
111
- if (c.layer !== 'print')
178
+ // linked to a measurement of the job: opening it, letting go of it — no text saying what it's linked to (the user's
179
+ // call, 2026-10-21; its chip is drawn linked), and no Reference (a measurement drives its line, or it isn't linked)
180
+ if (c.measurementRef) {
181
+ const t = c.measurementRef;
182
+ if (canOpenLink(t))
183
+ add('open', 'Open', 'dimLinkOpen');
184
+ add('unlink', 'Unlink', 'dimUnlink');
185
+ }
186
+ else if (canSaveDim(s.dims[0]))
187
+ add('savegroup', 'Save to group', 'dimSave');
188
+ // Reference frees what a dimension holds — a distance, say; not a cabinet's own width or height, which it keeps
189
+ const cabSize = Object.values(sk.cabs ?? {}).some(cb => cb.w === s.dims[0] || cb.h === s.dims[0]);
190
+ if (c.layer !== 'print' && !cabSize && !c.measurementRef)
112
191
  add('ref', c.driving === false ? 'Drive' : 'Reference', 'toggleref');
192
+ // measured on walls: which of their faces (the drawing stays as it is; the number becomes what it measures there)
193
+ const fc = faceChoices(sk, c);
194
+ if (fc.length && items.length)
195
+ items.push('sep');
196
+ fc.forEach((ch, i) => add(ch.icon, ch.short, 'dimFace' + i, { on: onChoice(c, ch) }));
113
197
  return items;
114
198
  }
115
199
  if (s.n === 1 && s.odims.length) {
@@ -125,53 +209,82 @@ function ctxItems() {
125
209
  add('rename', 'Rename', 'renameRoom');
126
210
  return items;
127
211
  }
212
+ // one end of a door or window: named; with another point, end or door/window, the distance between them
213
+ if (s.oanchors.length && s.n === 1) {
214
+ items.push({ lbl: anchorLabel(s.oanchors[0]) });
215
+ return items;
216
+ }
217
+ if (s.oanchors.length && s.n === 2 && s.oanchors.length + s.points.length + s.openings.length === 2) {
218
+ add('dim', 'Distance', 'anchorDist');
219
+ return items;
220
+ }
128
221
  // two doors/windows, or one and a point: the distance between them (locks the second one picked)
129
222
  if (s.n === 2 && (s.openings.length === 2 || (s.openings.length === 1 && s.points.length === 1))) {
130
223
  add('dim', 'Distance', 'opDist');
131
224
  return items;
132
225
  }
133
226
  if (s.n === 1 && s.openings.length) {
134
- // the yellow + sets the width; the type is fixed once placed (Door and Window are separate tools)
135
- if (sk.openings[s.openings[0]].type === 'door')
227
+ // its sizes by name, as in the wall view (no +: it isn't one dimension); a door's swing too. (The type is fixed
228
+ // once placed: Door and Window are separate tools.)
229
+ const o = sk.openings[s.openings[0]];
230
+ items.push({ lbl: openingLabel(o) });
231
+ for (const it of OPENING_SIZES)
232
+ add(it.ic, it.label, it.act);
233
+ if (o.type === 'door') {
234
+ items.push('sep');
136
235
  add('flip', 'Flip', 'opFlip');
236
+ }
137
237
  return items;
138
238
  }
139
239
  if (L === 1 && s.n === 1) {
140
240
  const l = sk.lines[s.lines[0]];
141
241
  add('h', 'Horizontal', 'h', { key: '⇧H' });
142
- add('v', 'Vertical', 'v', { key: '⇧V' });
143
- add('dim', 'Length', 'len');
242
+ add('v', 'Vertical', 'v', { key: '⇧V' }); // (its length: the yellow +)
144
243
  items.push('sep');
145
244
  add('lock', 'Lock', 'lock', { key: '⇧F' });
146
245
  add('split', 'Split', 'split');
147
246
  add('offset', 'Offset', 'offset');
148
- add('chain', 'Connected', 'chain');
149
- add('constr', l.constr ? 'Solid' : 'Guide', 'constr');
150
- if (l.kind === 'wall') {
151
- add('thick', 'Thickness', 'thick');
152
- add('line', 'To line', 'kind');
153
- }
247
+ if (l.constr)
248
+ add('constr', 'Solid', 'constr');
154
249
  else
155
- add('wall', 'To wall', 'kind');
156
- if (l.kind === 'wall' && !l.constr) {
157
- items.push('sep');
158
- add('elev', 'Elevation', 'elev');
250
+ guide();
251
+ if (!wall) {
252
+ if (l.kind === 'wall') {
253
+ add('thick', 'Thickness', 'thick');
254
+ add('line', 'To line', 'kind');
255
+ }
256
+ else
257
+ add('wall', 'To wall', 'kind');
258
+ if (l.kind === 'wall' && !l.constr) {
259
+ items.push('sep');
260
+ add('elev', 'Elevation', 'elev');
261
+ }
159
262
  }
160
263
  return items;
161
264
  }
162
265
  if (L === 2 && s.n === 2) {
266
+ // (no Angle here: the yellow + is the angle between them — or, parallel, the distance)
163
267
  add('par', 'Parallel', 'par', { key: '⇧P' });
164
268
  add('perp', 'Perpendicular', 'perp', { key: '⇧R' });
165
269
  add('col', 'Collinear', 'col', { key: '⇧L' });
166
270
  add('eq', 'Equal', 'eq', { key: '⇧E' });
167
- add('angle', 'Angle', 'angle');
271
+ // two lines that meet: round off (or cut off) the corner between them
272
+ if (filletCorner2()) {
273
+ add('fillet', 'Fillet', 'fillet');
274
+ add('chamfer', 'Chamfer', 'chamfer');
275
+ }
168
276
  wallItems();
169
277
  return items;
170
278
  }
279
+ // a cabinet's corner: nothing of its own (pick another point or line with it to dimension or constrain)
280
+ if (P === 1 && s.n === 1 && cabOfPoint(sk, s.points[0]))
281
+ return items;
171
282
  if (P === 1 && s.n === 1) {
172
- // a corner where exactly two lines meet: offer its angle
173
- if (cornerLines(sk, s.points[0]))
174
- add('angle', 'Angle', 'cornerAngle');
283
+ // a corner where exactly two lines meet: rounding it off or cutting it off (its angle is the yellow +)
284
+ if (filletable(sk, s.points[0])) {
285
+ add('fillet', 'Fillet', 'fillet');
286
+ add('chamfer', 'Chamfer', 'chamfer');
287
+ }
175
288
  add('lock', 'Lock', 'lock', { key: '⇧F' });
176
289
  return items;
177
290
  }
@@ -200,7 +313,9 @@ function ctxItems() {
200
313
  // a circle or arc: the yellow + sets its diameter
201
314
  if (C === 1 && s.n === 1) {
202
315
  const ci = curveInfo(sk, s.curves[0]);
203
- add('constr', 'Guide', 'constr');
316
+ guide();
317
+ if (wall)
318
+ return items;
204
319
  if (ci && ci.o.kind === 'wall') {
205
320
  add('thick', 'Thickness', 'thick');
206
321
  add('arc', 'To line', 'kind');
@@ -223,40 +338,91 @@ function ctxItems() {
223
338
  add('col', 'Collinear', 'col', { key: '⇧L' });
224
339
  add('eq', 'Equal', 'eq', { key: '⇧E' });
225
340
  items.push('sep');
226
- add('chain', 'Connected', 'chain');
227
341
  add('lock', 'Lock', 'lock');
228
- add('constr', 'Guide', 'constr');
342
+ guide();
229
343
  wallItems();
230
344
  return items;
231
345
  }
232
346
  if (L >= 2) {
233
347
  add('eq', 'Equal', 'eqall');
234
- add('chain', 'Connected', 'chain');
235
348
  add('lock', 'Lock', 'lock');
236
- add('constr', 'Guide', 'constr');
349
+ guide();
237
350
  wallItems();
238
351
  return items;
239
352
  }
240
353
  add('lock', 'Lock', 'lock');
241
354
  return items;
242
355
  }
356
+ /** The keypad for walls' thickness, finished face to face. */
357
+ function thicknessPrompt(walls) {
358
+ const sk = SK();
359
+ if (!walls.length)
360
+ return;
361
+ openKeypad({
362
+ title: 'Wall thickness', sub: 'Finished face to face', initial: fmtLen(walls[0].thick || 4.5), selectAll: true,
363
+ onDone: v => {
364
+ if (v <= 0)
365
+ return;
366
+ // a thicker wall's faces are shorter (its corners come in): not shorter than what's on them — and what's measured
367
+ // on walls' faces stays as measured, the centrelines moving to keep it
368
+ const was = overflow(sk), held = fitState(sk), had = walls.map(w => w.thick);
369
+ pushHistory();
370
+ walls.forEach(w => (w.thick = v));
371
+ const faces = holdFaces();
372
+ if (!faces || pushedOut(was, overflow(sk))) {
373
+ walls.forEach((w, i) => (w.thick = had[i]));
374
+ restoreFit(sk, held);
375
+ dropHistory();
376
+ toast(faces ? TOO_SHORT : 'The dimensions measured on walls’ faces can’t hold at that thickness.', { warn: true });
377
+ }
378
+ afterEdit();
379
+ },
380
+ });
381
+ }
382
+ /** Two selected lines that meet at a corner of just those two: that corner (to round it off). */
383
+ function filletCorner2() {
384
+ const sk = SK(), s = selInfo();
385
+ if (s.lines.length !== 2 || s.n !== 2)
386
+ return null;
387
+ const [a, b] = s.lines.map(id => sk.lines[id]);
388
+ const p = [a.a, a.b].find(x => x === b.a || x === b.b);
389
+ return p && filletable(sk, p) ? p : null;
390
+ }
243
391
  /** Buttons for the bottom bar: they adapt to the tool and the selection (Lock first, when offered). */
244
392
  export const barItems = () => arrangeLock(ctxItems());
245
393
  /** The yellow + only appears for something it can measure: not for constraint badges or spaces alone. */
394
+ const DRAW_TOOLS = new Set(['line', 'freehand', 'rect', 'circle', 'arc']);
246
395
  export function plusVisible() {
396
+ // markup isn't measured: Annotate has no + (notes are the rail's Text tool)
397
+ if (ed.mode === 'annotate')
398
+ return false;
399
+ const wb = wallBar();
400
+ if (wb)
401
+ return wb.plus;
402
+ // a shape just drawn (Rectangle, Circle, Arc keep their tool, the shape selected) gets no + of its own;
403
+ // a door or window just placed does (its width)
404
+ if (ed.mode === 'draft' && DRAW_TOOLS.has(ed.tool))
405
+ return false;
406
+ // one end of a door or window, alone: nothing to measure yet
407
+ if (ed.sel.length === 1 && ed.sel[0].type === 'oanchor')
408
+ return false;
409
+ // one door or window picked as a whole: its sizes are in the bar, by name — no single dimension for a +
410
+ // (two, or one and a point: the + is the distance between them)
411
+ if (ed.sel.length === 1 && ed.sel[0].type === 'opening')
412
+ return false;
247
413
  return ed.sel.length > 0 && !ed.sel.every(x => x.type === 'cons') && !ed.sel.every(x => x.type === 'room');
248
414
  }
249
- export const plusLabel = () => (ed.mode === 'annotate' ? 'Add a note' : 'Add a measurement');
250
- /** The yellow +: a note in Annotate; otherwise the measurement that fits the selection (opens the keypad). */
415
+ export const plusLabel = () => 'Add a measurement';
416
+ /** The yellow +: the measurement that fits the selection (opens the keypad). */
251
417
  export function plusAction() {
252
- if (ed.mode === 'annotate') {
253
- setTool('text');
418
+ if (ed.mode === 'annotate')
419
+ return;
420
+ if (wallPlus())
254
421
  return;
255
- }
256
422
  const s = selInfo(), sk = SK();
257
423
  if (s.dims.length === 1 && s.n === 1) {
258
424
  if (sk.cons[s.dims[0]]?.layer === 'print')
259
- toast('Print dimensions are labels only — drag to move them.');
425
+ toast('Print dimensions are labels only — drag to move them.', { warn: true });
260
426
  else
261
427
  editDim(s.dims[0]);
262
428
  }
@@ -264,12 +430,28 @@ export function plusAction() {
264
430
  const [op, part] = s.odims[0].split('|');
265
431
  editOpeningDim(op, part);
266
432
  }
433
+ // a wall's edge: its length on that face; with another edge, a line or a point, the distance from it
434
+ // (a curved wall's: its diameter across that face)
435
+ else if (s.wfaces.length === 1 && s.n === 1) {
436
+ const [id, side] = edgeOf(s.wfaces[0]);
437
+ if (sk.lines[id])
438
+ dimLine(id, false, side);
439
+ else
440
+ dimCurve(id, side);
441
+ }
442
+ else if (edgePair(s))
443
+ applyCtxAction('edgeDist');
267
444
  else if (s.lines.length === 1 && s.n === 1)
268
445
  dimLine(s.lines[0], false);
269
446
  else if (s.curves.length === 1 && s.n === 1)
270
447
  dimCurve(s.curves[0]);
271
448
  else if (s.openings.length === 1 && s.n === 1)
272
449
  editOpeningDim(s.openings[0], 'w');
450
+ // a corner where two lines meet: its angle
451
+ else if (s.points.length === 1 && s.n === 1 && cornerLines(sk, s.points[0]))
452
+ applyCtxAction('cornerAngle');
453
+ else if (s.n === 2 && s.oanchors.length)
454
+ applyCtxAction('anchorDist');
273
455
  else if (s.n === 2 && (s.openings.length === 2 || (s.openings.length === 1 && s.points.length === 1)))
274
456
  applyCtxAction('opDist');
275
457
  // two things selected: the dimension between them
@@ -285,29 +467,26 @@ export function plusAction() {
285
467
  export function deleteSelection() {
286
468
  if (!ed.sel.length)
287
469
  return;
288
- const items = ed.sel.slice(), sk = SK();
470
+ if (wallDelete())
471
+ return;
472
+ // (a wall's edge picked: the wall)
473
+ const sk = SK(), wallType = (id) => (sk.lines[id] ? 'line' : sk.circles[id] ? 'circle' : 'arc');
474
+ const items = ed.sel.map(x => (x.type === 'wface' ? { type: wallType(edgeOf(x.id)[0]), id: edgeOf(x.id)[0] } : x)).filter((x, i, a) => a.findIndex(y => y.type === x.type && y.id === x.id) === i);
289
475
  if (items.length === 1 && items.some(s => s.type === 'point' && sk.points[s.id] && sk.points[s.id].origin)) {
290
- toast('The origin can’t be deleted.');
476
+ toast('The origin can’t be deleted.', { warn: true });
291
477
  return;
292
478
  }
293
- // the image underlay isn't part of the sketch history, so its removal gets its own undo
294
- const removed = items.some(s => s.type === 'underlay') ? page().underlay : null;
479
+ // images are part of the page's undo history, so they go in the same step as the rest
480
+ const imgIds = new Set(items.filter(s => s.type === 'underlay').map(s => s.id));
295
481
  const rest = items.filter(s => s.type !== 'underlay');
296
- if (removed)
297
- page().underlay = null;
298
- if (rest.length) {
299
- pushHistory();
482
+ pushHistory();
483
+ if (imgIds.size)
484
+ page().underlays = page().underlays.filter(u => !imgIds.has(u.id));
485
+ if (rest.length)
300
486
  deleteSel(sk, rest);
301
- }
302
487
  ed.sel = [];
303
488
  afterEdit();
304
- toast(removed && !rest.length ? 'Image removed' : 'Deleted', {
305
- action: { label: 'Undo', fn: () => { if (rest.length)
306
- undo(); if (removed && !page().underlay) {
307
- page().underlay = removed;
308
- afterEdit();
309
- } } },
310
- });
489
+ toast(imgIds.size && !rest.length ? (imgIds.size > 1 ? 'Images removed' : 'Image removed') : 'Deleted', { action: { label: 'Undo', fn: () => undo() } });
311
490
  }
312
491
  function consFromSel(type) {
313
492
  const sk = SK(), s = selInfo(), L = s.lines.map(id => sk.lines[id]);
@@ -376,7 +555,7 @@ function applyMulti(type, list) {
376
555
  }
377
556
  if (!added && !failed.length) {
378
557
  dropHistory();
379
- toast('They’re already ' + ADJ[type] + '.');
558
+ toast('They’re already ' + ADJ[type] + '.', { warn: true });
380
559
  afterEdit();
381
560
  return;
382
561
  }
@@ -403,7 +582,7 @@ function applyCons(type) {
403
582
  }
404
583
  const c = consFromSel(type);
405
584
  if (!c) {
406
- toast('Select ' + (NEEDS[type] || 'geometry') + ' first.');
585
+ toast('Select ' + (NEEDS[type] || 'geometry') + ' first.', { warn: true });
407
586
  return;
408
587
  }
409
588
  pushHistory();
@@ -416,6 +595,8 @@ function applyCons(type) {
416
595
  }
417
596
  /** Run a bottom-bar action. `anchor` is the button pressed, for actions that open a popover next to it. */
418
597
  export function applyCtxAction(act, anchor) {
598
+ if (wallAction(act))
599
+ return;
419
600
  const sk = SK(), s = selInfo();
420
601
  switch (act) {
421
602
  case 'fit':
@@ -424,7 +605,21 @@ export function applyCtxAction(act, anchor) {
424
605
  case 'elev':
425
606
  if (s.lines.length === 1)
426
607
  openElevation(s.lines[0]);
608
+ // (an edge: seen from that face)
609
+ else if (s.wfaces.length === 1 && sk.lines[edgeOf(s.wfaces[0])[0]])
610
+ openElevation(...edgeOf(s.wfaces[0]));
611
+ return;
612
+ case 'edgeDist':
613
+ dimEdges(ed.sel, id => thicknessPrompt([sk.lines[id] ?? sk.circles[id] ?? sk.arcs[id]]));
427
614
  return;
615
+ case 'dimFace0':
616
+ case 'dimFace1':
617
+ case 'dimFace2': {
618
+ const ch = s.dims.length === 1 && sk.cons[s.dims[0]] ? faceChoices(sk, sk.cons[s.dims[0]])[+act.slice(7)] : undefined;
619
+ if (ch)
620
+ setDimFace(s.dims[0], ch);
621
+ return;
622
+ }
428
623
  case 'grid':
429
624
  settings.snapGrid = !settings.snapGrid;
430
625
  break;
@@ -470,6 +665,10 @@ export function applyCtxAction(act, anchor) {
470
665
  case 'len':
471
666
  if (s.lines.length === 1)
472
667
  dimLine(s.lines[0], false);
668
+ else if (s.wfaces.length === 1 && sk.lines[edgeOf(s.wfaces[0])[0]]) {
669
+ const [lid, side] = edgeOf(s.wfaces[0]);
670
+ dimLine(lid, false, side);
671
+ }
473
672
  return;
474
673
  case 'pdist':
475
674
  if (s.points.length === 2)
@@ -483,29 +682,23 @@ export function applyCtxAction(act, anchor) {
483
682
  if (s.curves.length === 1)
484
683
  dimCurve(s.curves[0]);
485
684
  return;
486
- case 'angle':
487
- if (s.lines.length === 2)
488
- dimAngle(s.lines[0], s.lines[1]);
489
- return;
490
- case 'cornerAngle': {
491
- const pair = cornerLines(sk, s.points[0]);
492
- if (!pair)
493
- return;
494
- // edit the corner's existing angle dimension rather than stacking a second one
495
- const ex = Object.keys(sk.cons).find(id => { const c = sk.cons[id]; return c.type === 'angle' && c.layer !== 'print' && ((c.l1 === pair[0] && c.l2 === pair[1]) || (c.l1 === pair[1] && c.l2 === pair[0])); });
496
- if (ex)
497
- editDim(ex);
498
- else
499
- dimAngle(pair[0], pair[1]);
685
+ case 'fillet':
686
+ case 'chamfer': {
687
+ const p = s.points.length === 1 ? s.points[0] : filletCorner2();
688
+ if (p)
689
+ (act === 'fillet' ? filletPrompt : chamferPrompt)(p);
500
690
  return;
501
691
  }
692
+ case 'cornerAngle':
693
+ cornerAngle(s.points[0]);
694
+ return;
502
695
  case 'merge': {
503
696
  pushHistory();
504
697
  const snap = JSON.stringify(sk);
505
- if (mergePoints(sk, s.points[0], s.points[1])) {
698
+ if (joinPoints(sk, s.points[0], s.points[1])) {
506
699
  const r = solveSketch();
507
700
  if (!r.ok) {
508
- page().sk = JSON.parse(snap);
701
+ replaceSK(JSON.parse(snap));
509
702
  dropHistory();
510
703
  toast('Can’t join those points — it conflicts with other constraints.', { warn: true });
511
704
  }
@@ -544,7 +737,8 @@ export function applyCtxAction(act, anchor) {
544
737
  }
545
738
  case 'constr':
546
739
  pushHistory();
547
- s.lines.forEach(id => (sk.lines[id].constr = !sk.lines[id].constr));
740
+ s.lines.forEach(id => { if (!sk.lines[id].anchor)
741
+ sk.lines[id].constr = !sk.lines[id].constr; });
548
742
  s.curves.forEach(id => { const ci = curveInfo(sk, id); if (ci)
549
743
  ci.o.constr = !ci.o.constr; });
550
744
  afterEdit();
@@ -556,26 +750,47 @@ export function applyCtxAction(act, anchor) {
556
750
  return;
557
751
  const toWall = !targets.every(t => t.kind === 'wall');
558
752
  pushHistory();
559
- targets.forEach(t => { t.kind = toWall ? 'wall' : 'line'; t.thick = t.thick || 4.5; });
753
+ targets.forEach(t => { t.kind = toWall ? 'wall' : 'line'; t.thick = t.thick || 4.5; if (toWall && t.constr)
754
+ t.constr = false; });
755
+ // made lines: what was measured on their faces is measured as they are now (nothing moves); the walls beside
756
+ // them keep theirs
757
+ if (!toWall) {
758
+ const ids = new Set([...s.lines, ...s.curves]);
759
+ for (const c of Object.values(sk.cons)) {
760
+ if (c.type === 'dist' && c.face && c.line && ids.has(c.line)) {
761
+ delete c.face;
762
+ if (c.driving !== false)
763
+ c.value = measure(sk, c);
764
+ }
765
+ if (c.type === 'diam' && c.face && c.c && ids.has(c.c)) {
766
+ delete c.face;
767
+ if (c.driving !== false)
768
+ c.value = measure(sk, c);
769
+ }
770
+ if (c.type === 'pldist' && c.faces && ((c.l && ids.has(c.l)) || (c.l2 && ids.has(c.l2)))) {
771
+ if (c.driving !== false)
772
+ c.value = measure(sk, c);
773
+ if (c.faces[0] && c.l && ids.has(c.l))
774
+ c.faces = [0, c.faces[1]];
775
+ if (c.faces[1] && c.l2 && ids.has(c.l2))
776
+ c.faces = [c.faces[0], 0];
777
+ if (!c.faces[0] && !c.faces[1])
778
+ delete c.faces;
779
+ }
780
+ }
781
+ }
782
+ holdFaces();
560
783
  afterEdit();
561
784
  toast(targets.length > 1 ? (toWall ? targets.length + ' items are now walls' : targets.length + ' items are now lines') : toWall ? 'Now a wall' : 'Now a line');
562
785
  return;
563
786
  }
564
- case 'thick': {
787
+ case 'thick':
565
788
  // straight and curved walls in the selection
566
- const walls = [
789
+ thicknessPrompt([
567
790
  ...s.lines.map(id => sk.lines[id]).filter(l => l.kind === 'wall'),
568
791
  ...s.curves.map(id => curveInfo(sk, id)?.o).filter((o) => !!o && o.kind === 'wall'),
569
- ];
570
- if (!walls.length)
571
- return;
572
- openKeypad({
573
- title: 'Wall thickness', sub: 'Finished face to face', initial: fmtLen(walls[0].thick || 4.5), selectAll: true,
574
- onDone: v => { if (v <= 0)
575
- return; pushHistory(); walls.forEach(w => (w.thick = v)); afterEdit(); },
576
- });
792
+ ]);
577
793
  return;
578
- }
579
794
  case 'eqall': {
580
795
  // lines mixed with other things: equal across just the lines
581
796
  if (s.lines.length < 2)
@@ -620,12 +835,60 @@ export function applyCtxAction(act, anchor) {
620
835
  }
621
836
  return;
622
837
  }
838
+ case 'anchorDist': {
839
+ // from one end of a door or window to a point, another end, or another door or window (its nearer end): the
840
+ // second one picked is placed from the first (the other way round if that would tie them in a loop)
841
+ const end = (id) => { const [op, edge] = id.split('|'); return { op, edge: edge }; };
842
+ const [first, second] = ed.sel;
843
+ if (!first || !second)
844
+ return;
845
+ if (first.type === 'point' || second.type === 'point') {
846
+ const e = end((first.type === 'oanchor' ? first : second).id), pt = (first.type === 'point' ? first : second).id;
847
+ addOpeningDim(e.op, e.edge, { pt });
848
+ return;
849
+ }
850
+ const nearer = (op, from) => {
851
+ const p = edgePoint(sk, from.op, from.edge), a = edgePoint(sk, op, 's0'), b = edgePoint(sk, op, 's1');
852
+ return !p || !a || !b || Math.hypot(a.x - p.x, a.y - p.y) <= Math.hypot(b.x - p.x, b.y - p.y) ? 's0' : 's1';
853
+ };
854
+ let a = first.type === 'oanchor' ? end(first.id) : null, b = second.type === 'oanchor' ? end(second.id) : null;
855
+ if (!a)
856
+ a = { op: first.id, edge: nearer(first.id, b) };
857
+ if (!b)
858
+ b = { op: second.id, edge: nearer(second.id, a) };
859
+ if (a.op !== b.op && createsCycle(sk, b.op, { op: a.op, edge: a.edge }))
860
+ [a, b] = [b, a];
861
+ addOpeningDim(b.op, b.edge, { op: a.op, edge: a.edge });
862
+ return;
863
+ }
623
864
  case 'palette':
624
865
  openPalette(anchor);
625
866
  return;
626
867
  case 'convert':
627
868
  convertNote(s.notes[0]);
628
869
  return;
870
+ case 'linkOpen':
871
+ openSelectedLink();
872
+ return;
873
+ case 'linkSize':
874
+ toggleTileSize(anchor);
875
+ return;
876
+ case 'dimLinkOpen':
877
+ if (s.dims.length === 1)
878
+ openDimLink(s.dims[0]);
879
+ return;
880
+ case 'dimUnlink':
881
+ if (s.dims.length === 1)
882
+ unlinkDim(s.dims[0]);
883
+ return;
884
+ case 'dimSave':
885
+ if (s.dims.length === 1)
886
+ void saveDimToGroup(s.dims[0]);
887
+ return;
888
+ case 'linkLeader':
889
+ if (s.notes.length === 1)
890
+ toggleLeader(s.notes[0]);
891
+ return;
629
892
  case 'calibtool':
630
893
  setTool('calib');
631
894
  return;
@@ -635,13 +898,28 @@ export function applyCtxAction(act, anchor) {
635
898
  ed.sel = ed.sel.filter(x => x.type !== 'underlay');
636
899
  break;
637
900
  case 'imgLock': {
638
- const u = page().underlay;
639
- if (!u)
901
+ const ims = selImages();
902
+ if (!ims.length)
640
903
  return;
904
+ const lock = !ims.every(u => u.locked), many = ims.length > 1;
641
905
  pushHistory();
642
- u.locked = !u.locked;
906
+ ims.forEach(u => { u.locked = lock; });
643
907
  afterEdit();
644
- toast(u.locked ? 'Image locked. It won’t move while you trace over it.' : 'Image unlocked. Drag it, or drag its handles to size it.');
908
+ toast(lock ? (many ? 'Images locked. They won’t move while you trace over them.' : 'Image locked. It won’t move while you trace over it.')
909
+ : many ? 'Images unlocked.' : 'Image unlocked. Drag it, or drag its handles to size it.');
910
+ return;
911
+ }
912
+ case 'imgOpacity':
913
+ toggleImageOpacity(anchor);
914
+ return;
915
+ case 'imgRotate':
916
+ toggleImageRotation(anchor);
917
+ return;
918
+ case 'imgFront':
919
+ case 'imgBack': {
920
+ const ims = selImages();
921
+ if (ims.length === 1)
922
+ restackImage(ims[0].id, act === 'imgFront' ? 'front' : 'back');
645
923
  return;
646
924
  }
647
925
  }