@reekon-tools/boldr-cad 1.4.0 → 1.6.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 (311) hide show
  1. package/README.md +13 -3
  2. package/dist/app/edit.d.ts +3 -3
  3. package/dist/app/edit.js +26 -1
  4. package/dist/app/edit.js.map +1 -1
  5. package/dist/app/state.d.ts +6 -8
  6. package/dist/app/state.js +0 -1
  7. package/dist/app/state.js.map +1 -1
  8. package/dist/data/sync.d.ts +1 -1
  9. package/dist/data/sync.js +7 -2
  10. package/dist/data/sync.js.map +1 -1
  11. package/dist/editor/actions.d.ts +8 -0
  12. package/dist/editor/actions.js +255 -181
  13. package/dist/editor/actions.js.map +1 -1
  14. package/dist/editor/elevation.d.ts +4 -1
  15. package/dist/editor/elevation.js +16 -17
  16. package/dist/editor/elevation.js.map +1 -1
  17. package/dist/editor/gestures.d.ts +21 -0
  18. package/dist/editor/gestures.js +92 -14
  19. package/dist/editor/gestures.js.map +1 -1
  20. package/dist/editor/keys.js +4 -3
  21. package/dist/editor/keys.js.map +1 -1
  22. package/dist/editor/laser.d.ts +4 -2
  23. package/dist/editor/laser.js +4 -2
  24. package/dist/editor/laser.js.map +1 -1
  25. package/dist/editor/linkActions.d.ts +49 -12
  26. package/dist/editor/linkActions.js +164 -35
  27. package/dist/editor/linkActions.js.map +1 -1
  28. package/dist/editor/links.d.ts +13 -0
  29. package/dist/editor/links.js +7 -1
  30. package/dist/editor/links.js.map +1 -1
  31. package/dist/editor/mode.d.ts +2 -1
  32. package/dist/editor/mode.js +8 -5
  33. package/dist/editor/mode.js.map +1 -1
  34. package/dist/editor/project.js +1 -2
  35. package/dist/editor/project.js.map +1 -1
  36. package/dist/editor/scaleGate.d.ts +13 -0
  37. package/dist/editor/scaleGate.js +27 -0
  38. package/dist/editor/scaleGate.js.map +1 -0
  39. package/dist/editor/selection.js +13 -9
  40. package/dist/editor/selection.js.map +1 -1
  41. package/dist/editor/tooltips.js +10 -0
  42. package/dist/editor/tooltips.js.map +1 -1
  43. package/dist/editor/view3d.d.ts +4 -1
  44. package/dist/editor/view3d.js +6 -2
  45. package/dist/editor/view3d.js.map +1 -1
  46. package/dist/export/pdf.d.ts +5 -2
  47. package/dist/export/pdf.js +12 -8
  48. package/dist/export/pdf.js.map +1 -1
  49. package/dist/export/plan.d.ts +1 -1
  50. package/dist/export/plan.js +14 -9
  51. package/dist/export/plan.js.map +1 -1
  52. package/dist/index.d.ts +1 -0
  53. package/dist/index.js +1 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/model/constraints.js +2 -0
  56. package/dist/model/constraints.js.map +1 -1
  57. package/dist/model/demo.d.ts +1 -0
  58. package/dist/model/demo.js +2 -1
  59. package/dist/model/demo.js.map +1 -1
  60. package/dist/model/elevation.d.ts +1 -1
  61. package/dist/model/elevation.js +3 -3
  62. package/dist/model/elevation.js.map +1 -1
  63. package/dist/model/geom.d.ts +2 -0
  64. package/dist/model/geom.js +11 -0
  65. package/dist/model/geom.js.map +1 -1
  66. package/dist/model/history.js +4 -1
  67. package/dist/model/history.js.map +1 -1
  68. package/dist/model/links.d.ts +10 -2
  69. package/dist/model/links.js +15 -3
  70. package/dist/model/links.js.map +1 -1
  71. package/dist/model/measurement.d.ts +2 -2
  72. package/dist/model/measurement.js +3 -3
  73. package/dist/model/measurement.js.map +1 -1
  74. package/dist/model/openings.d.ts +10 -0
  75. package/dist/model/openings.js +18 -0
  76. package/dist/model/openings.js.map +1 -1
  77. package/dist/model/planGeom.d.ts +37 -2
  78. package/dist/model/planGeom.js +72 -9
  79. package/dist/model/planGeom.js.map +1 -1
  80. package/dist/model/project.js +5 -2
  81. package/dist/model/project.js.map +1 -1
  82. package/dist/model/rooms.d.ts +18 -5
  83. package/dist/model/rooms.js +178 -73
  84. package/dist/model/rooms.js.map +1 -1
  85. package/dist/model/scale.d.ts +30 -0
  86. package/dist/model/scale.js +123 -0
  87. package/dist/model/scale.js.map +1 -0
  88. package/dist/model/settings.d.ts +0 -1
  89. package/dist/model/settings.js +1 -1
  90. package/dist/model/settings.js.map +1 -1
  91. package/dist/model/sketch.d.ts +26 -2
  92. package/dist/model/sketch.js +77 -22
  93. package/dist/model/sketch.js.map +1 -1
  94. package/dist/model/stroke.js +2 -2
  95. package/dist/model/trim.d.ts +54 -0
  96. package/dist/model/trim.js +219 -0
  97. package/dist/model/trim.js.map +1 -0
  98. package/dist/model/types.d.ts +62 -9
  99. package/dist/react/BottomBar.d.ts +2 -1
  100. package/dist/react/BottomBar.js +144 -46
  101. package/dist/react/BottomBar.js.map +1 -1
  102. package/dist/react/CadEditor.js +18 -9
  103. package/dist/react/CadEditor.js.map +1 -1
  104. package/dist/react/Drawer.js +6 -4
  105. package/dist/react/Drawer.js.map +1 -1
  106. package/dist/react/Duck.js.map +1 -1
  107. package/dist/react/KeypadSheet.js.map +1 -1
  108. package/dist/react/MarkupPanel.js +1 -1
  109. package/dist/react/PlanCanvas.native.js +54 -7
  110. package/dist/react/PlanCanvas.native.js.map +1 -1
  111. package/dist/react/PlanCanvas.web.js +17 -5
  112. package/dist/react/PlanCanvas.web.js.map +1 -1
  113. package/dist/react/Popovers.js +14 -3
  114. package/dist/react/Popovers.js.map +1 -1
  115. package/dist/react/Rail.js +1 -1
  116. package/dist/react/ShapeMenu.js +3 -2
  117. package/dist/react/ShapeMenu.js.map +1 -1
  118. package/dist/react/Sheet.d.ts +2 -2
  119. package/dist/react/Sheet.js.map +1 -1
  120. package/dist/react/ToolTip.js +3 -2
  121. package/dist/react/ToolTip.js.map +1 -1
  122. package/dist/react/TopBar.js +4 -3
  123. package/dist/react/TopBar.js.map +1 -1
  124. package/dist/react/barScroll.d.ts +23 -0
  125. package/dist/react/barScroll.js +47 -0
  126. package/dist/react/barScroll.js.map +1 -0
  127. package/dist/react/corners.d.ts +6 -2
  128. package/dist/react/corners.js.map +1 -1
  129. package/dist/react/editorBox.d.ts +8 -0
  130. package/dist/react/editorBox.js +10 -0
  131. package/dist/react/editorBox.js.map +1 -0
  132. package/dist/react/host.d.ts +1 -1
  133. package/dist/react/host.js +1 -1
  134. package/dist/react/host.js.map +1 -1
  135. package/dist/react/motion.d.ts +1 -1
  136. package/dist/react/motion.js +1 -1
  137. package/dist/react/motion.js.map +1 -1
  138. package/dist/react/pageLock.native.d.ts +2 -0
  139. package/dist/react/pageLock.native.js +2 -0
  140. package/dist/react/pageLock.native.js.map +1 -1
  141. package/dist/react/pageLock.web.d.ts +6 -0
  142. package/dist/react/pageLock.web.js +12 -3
  143. package/dist/react/pageLock.web.js.map +1 -1
  144. package/dist/react/ports.js +8 -3
  145. package/dist/react/ports.js.map +1 -1
  146. package/dist/react/saving.d.ts +7 -1
  147. package/dist/react/saving.js +10 -2
  148. package/dist/react/saving.js.map +1 -1
  149. package/dist/react/store.d.ts +5 -2
  150. package/dist/react/store.js +4 -1
  151. package/dist/react/store.js.map +1 -1
  152. package/dist/render/draw.d.ts +21 -4
  153. package/dist/render/draw.js +203 -151
  154. package/dist/render/draw.js.map +1 -1
  155. package/dist/render/elevation.js +3 -3
  156. package/dist/render/elevation.js.map +1 -1
  157. package/dist/render/figmaIcons.d.ts +6 -0
  158. package/dist/render/figmaIcons.js +16 -1
  159. package/dist/render/figmaIcons.js.map +1 -1
  160. package/dist/render/hit.d.ts +8 -1
  161. package/dist/render/hit.js +23 -4
  162. package/dist/render/hit.js.map +1 -1
  163. package/dist/render/icons.js +27 -20
  164. package/dist/render/icons.js.map +1 -1
  165. package/dist/render/lens.d.ts +27 -0
  166. package/dist/render/lens.js +41 -0
  167. package/dist/render/lens.js.map +1 -0
  168. package/dist/render/linkTile.d.ts +20 -2
  169. package/dist/render/linkTile.js +476 -246
  170. package/dist/render/linkTile.js.map +1 -1
  171. package/dist/render/snap.d.ts +10 -1
  172. package/dist/render/snap.js +87 -15
  173. package/dist/render/snap.js.map +1 -1
  174. package/dist/render/svgPath.d.ts +29 -0
  175. package/dist/render/svgPath.js +114 -0
  176. package/dist/render/svgPath.js.map +1 -0
  177. package/dist/render/tileIcons.d.ts +18 -0
  178. package/dist/render/tileIcons.js +67 -0
  179. package/dist/render/tileIcons.js.map +1 -0
  180. package/dist/render/view.d.ts +18 -1
  181. package/dist/render/view.js +38 -6
  182. package/dist/render/view.js.map +1 -1
  183. package/dist/skia/SkiaDrawCtx.d.ts +8 -0
  184. package/dist/skia/SkiaDrawCtx.js +27 -5
  185. package/dist/skia/SkiaDrawCtx.js.map +1 -1
  186. package/dist/solver/equations.d.ts +6 -0
  187. package/dist/solver/equations.js +53 -22
  188. package/dist/solver/equations.js.map +1 -1
  189. package/dist/solver/gauss-newton.js +6 -1
  190. package/dist/solver/gauss-newton.js.map +1 -1
  191. package/dist/tools/annotate.d.ts +0 -2
  192. package/dist/tools/annotate.js +1 -22
  193. package/dist/tools/annotate.js.map +1 -1
  194. package/dist/tools/chamfer.js +4 -0
  195. package/dist/tools/chamfer.js.map +1 -1
  196. package/dist/tools/dim.d.ts +3 -3
  197. package/dist/tools/dim.js +26 -65
  198. package/dist/tools/dim.js.map +1 -1
  199. package/dist/tools/fillet.js +4 -0
  200. package/dist/tools/fillet.js.map +1 -1
  201. package/dist/tools/freehand.js +1 -4
  202. package/dist/tools/freehand.js.map +1 -1
  203. package/dist/tools/line.js +15 -11
  204. package/dist/tools/line.js.map +1 -1
  205. package/dist/tools/modify.d.ts +32 -4
  206. package/dist/tools/modify.js +253 -98
  207. package/dist/tools/modify.js.map +1 -1
  208. package/dist/tools/registry.d.ts +7 -0
  209. package/dist/tools/registry.js +14 -1
  210. package/dist/tools/registry.js.map +1 -1
  211. package/dist/tools/select.d.ts +30 -8
  212. package/dist/tools/select.js +165 -34
  213. package/dist/tools/select.js.map +1 -1
  214. package/dist/tools/shapes.d.ts +1 -1
  215. package/dist/tools/shapes.js +12 -5
  216. package/dist/tools/shapes.js.map +1 -1
  217. package/dist/view3d/build.js +9 -8
  218. package/dist/view3d/build.js.map +1 -1
  219. package/package.json +9 -9
  220. package/src/app/edit.ts +25 -1
  221. package/src/app/state.ts +6 -8
  222. package/src/data/sync.ts +7 -2
  223. package/src/editor/actions.ts +213 -122
  224. package/src/editor/elevation.ts +13 -16
  225. package/src/editor/gestures.ts +83 -13
  226. package/src/editor/keys.ts +3 -2
  227. package/src/editor/laser.ts +7 -4
  228. package/src/editor/linkActions.ts +169 -31
  229. package/src/editor/links.ts +17 -1
  230. package/src/editor/mode.ts +8 -5
  231. package/src/editor/project.ts +1 -2
  232. package/src/editor/scaleGate.ts +32 -0
  233. package/src/editor/selection.ts +13 -7
  234. package/src/editor/tooltips.ts +9 -0
  235. package/src/editor/view3d.ts +6 -2
  236. package/src/export/pdf.ts +7 -4
  237. package/src/export/plan.ts +14 -10
  238. package/src/index.ts +1 -0
  239. package/src/model/constraints.ts +1 -0
  240. package/src/model/demo.ts +2 -1
  241. package/src/model/elevation.ts +4 -4
  242. package/src/model/geom.ts +10 -0
  243. package/src/model/history.ts +4 -1
  244. package/src/model/links.ts +14 -3
  245. package/src/model/measurement.ts +3 -3
  246. package/src/model/openings.ts +18 -0
  247. package/src/model/planGeom.ts +70 -10
  248. package/src/model/project.ts +6 -2
  249. package/src/model/rooms.ts +151 -63
  250. package/src/model/scale.ts +107 -0
  251. package/src/model/settings.ts +1 -2
  252. package/src/model/sketch.ts +62 -11
  253. package/src/model/stroke.ts +2 -2
  254. package/src/model/trim.ts +188 -0
  255. package/src/model/types.ts +57 -9
  256. package/src/react/BottomBar.tsx +177 -52
  257. package/src/react/CadEditor.tsx +19 -9
  258. package/src/react/Drawer.tsx +17 -12
  259. package/src/react/Duck.tsx +3 -3
  260. package/src/react/KeypadSheet.tsx +2 -2
  261. package/src/react/MarkupPanel.tsx +1 -1
  262. package/src/react/PlanCanvas.native.tsx +60 -6
  263. package/src/react/PlanCanvas.web.tsx +17 -5
  264. package/src/react/Popovers.tsx +24 -9
  265. package/src/react/Rail.tsx +1 -1
  266. package/src/react/ShapeMenu.tsx +3 -2
  267. package/src/react/Sheet.tsx +3 -3
  268. package/src/react/ToolTip.tsx +3 -2
  269. package/src/react/TopBar.tsx +4 -3
  270. package/src/react/barScroll.ts +57 -0
  271. package/src/react/corners.ts +6 -3
  272. package/src/react/editorBox.ts +20 -0
  273. package/src/react/host.ts +1 -1
  274. package/src/react/motion.tsx +6 -6
  275. package/src/react/pageLock.native.ts +2 -0
  276. package/src/react/pageLock.web.ts +12 -3
  277. package/src/react/ports.ts +7 -2
  278. package/src/react/saving.ts +10 -2
  279. package/src/react/store.ts +5 -2
  280. package/src/render/draw.ts +169 -118
  281. package/src/render/elevation.ts +5 -4
  282. package/src/render/figmaIcons.ts +17 -1
  283. package/src/render/hit.ts +21 -4
  284. package/src/render/icons.ts +27 -20
  285. package/src/render/lens.ts +52 -0
  286. package/src/render/linkTile.ts +436 -190
  287. package/src/render/snap.ts +87 -17
  288. package/src/render/svgPath.ts +95 -0
  289. package/src/render/tileIcons.ts +75 -0
  290. package/src/render/view.ts +43 -7
  291. package/src/skia/SkiaDrawCtx.ts +26 -4
  292. package/src/solver/equations.ts +52 -22
  293. package/src/solver/gauss-newton.ts +4 -1
  294. package/src/styles.css +3 -2
  295. package/src/tools/annotate.ts +1 -18
  296. package/src/tools/chamfer.ts +3 -0
  297. package/src/tools/dim.ts +26 -52
  298. package/src/tools/fillet.ts +3 -0
  299. package/src/tools/freehand.ts +1 -3
  300. package/src/tools/line.ts +12 -11
  301. package/src/tools/modify.ts +210 -70
  302. package/src/tools/registry.ts +18 -1
  303. package/src/tools/select.ts +144 -36
  304. package/src/tools/shapes.ts +9 -4
  305. package/src/ui/ctx.ts +4 -3
  306. package/src/ui/drawer.ts +8 -2
  307. package/src/view3d/build.ts +11 -10
  308. package/dist/react/LaserTray.d.ts +0 -4
  309. package/dist/react/LaserTray.js +0 -26
  310. package/dist/react/LaserTray.js.map +0 -1
  311. package/src/react/LaserTray.tsx +0 -42
@@ -0,0 +1,188 @@
1
+ import { circleCircle, distSeg, hyp, lineCircleT, normAng, projT, segInter } from './geom';
2
+ import { arcAngles } from './planGeom';
3
+ import type { Sketch, XY } from './types';
4
+
5
+ /*
6
+ Trim's geometry (the tool and the cutting: tools/modify.ts). An item — a line (a wall, a guide), an arc or a circle — is
7
+ cut wherever something crosses it, and wherever a point something else is attached at lies on it: another line's or
8
+ arc's end resting on it (a T, an arc drawn into a corner), a point held on it. A tap takes away the piece between the
9
+ cuts either side of it. Nothing here edits the sketch.
10
+ */
11
+
12
+ export type TrimKind = 'line' | 'arc' | 'circle';
13
+ export interface TrimItem { kind: TrimKind; id: string }
14
+
15
+ /**
16
+ * A cut along an item: where (`at` — a line's 0..1 from A to B; an arc's angle round from its start, 0 to its sweep; a
17
+ * circle's angle, 0 to 2π; counter-clockwise) and what makes it: a point already there (`pt` — the trimmed end is that
18
+ * point, so what meets there stays joined), or a crossing (`line` / `curve` — a new point, held on what it crosses).
19
+ */
20
+ export interface Cut { at: number; x: number; y: number; pt?: string; line?: string; curve?: string }
21
+
22
+ /** The piece of an item between two cuts. Null: the item's own end — a circle has none, so either null is all of it. */
23
+ export interface TrimPiece { item: TrimItem; lo: Cut | null; hi: Cut | null }
24
+
25
+ /** A curve's circle, and the part of it that's there: from t0, counter-clockwise, `sweep` round (a circle's: all of it). */
26
+ interface Round { o: XY; r: number; t0: number; sweep: number }
27
+
28
+ function roundOf(sk: Sketch, it: TrimItem): Round | null {
29
+ if (it.kind === 'circle') {
30
+ const ci = sk.circles[it.id], o = ci && sk.points[ci.c];
31
+ return o ? { o, r: ci.r, t0: 0, sweep: 2 * Math.PI } : null;
32
+ }
33
+ const a = it.kind === 'arc' ? sk.arcs[it.id] : undefined;
34
+ if (!a || !sk.points[a.c] || !sk.points[a.a] || !sk.points[a.b]) return null;
35
+ const g = arcAngles(sk, a);
36
+ return { o: g.o, r: g.r, t0: g.t0, sweep: g.full ? 2 * Math.PI : normAng(g.t1 - g.t0) };
37
+ }
38
+
39
+ /** How far round from a round's start the direction to `p` is (radians, counter-clockwise). */
40
+ const roundAt = (R: Round, p: XY): number => normAng(Math.atan2(p.y - R.o.y, p.x - R.o.x) - R.t0);
41
+
42
+ /** How far `p` is from an item, as drawn (a wall: from its faces in); Infinity off its ends, or if it's gone. */
43
+ export function distTo(sk: Sketch, it: TrimItem, p: XY): number {
44
+ if (it.kind === 'line') {
45
+ const l = sk.lines[it.id], A = l && sk.points[l.a], B = l && sk.points[l.b];
46
+ if (!A || !B) return Infinity;
47
+ return Math.max(0, distSeg(p, A, B) - (l.kind === 'wall' ? l.thick / 2 : 0));
48
+ }
49
+ const R = roundOf(sk, it), o = it.kind === 'circle' ? sk.circles[it.id] : sk.arcs[it.id];
50
+ if (!R || !o || (it.kind === 'arc' && roundAt(R, p) > R.sweep)) return Infinity;
51
+ return Math.max(0, Math.abs(hyp(p.x - R.o.x, p.y - R.o.y) - R.r) - (o.kind === 'wall' ? (o.thick || 4.5) / 2 : 0));
52
+ }
53
+
54
+ /** The point on an item (on its centreline, within its ends) nearest `p`: where on it a tap beside it is. */
55
+ export function nearestOn(sk: Sketch, it: TrimItem, p: XY): XY {
56
+ if (it.kind === 'line') {
57
+ const l = sk.lines[it.id], A = sk.points[l.a], B = sk.points[l.b], t = Math.max(0, Math.min(1, projT(A, B, p)));
58
+ return { x: A.x + (B.x - A.x) * t, y: A.y + (B.y - A.y) * t };
59
+ }
60
+ const R = roundOf(sk, it);
61
+ if (!R) return p;
62
+ const d = hyp(p.x - R.o.x, p.y - R.o.y) || 1;
63
+ return { x: R.o.x + ((p.x - R.o.x) / d) * R.r, y: R.o.y + ((p.y - R.o.y) / d) * R.r };
64
+ }
65
+
66
+ /**
67
+ * The line, arc or circle nearest `p`, within `tol`: what a trim there is of. Not a wall face's own edges (the plan's)
68
+ * or a cabinet's: those are whole things, not pieces to cut away.
69
+ */
70
+ export function trimItemAt(sk: Sketch, p: XY, tol: number): TrimItem | null {
71
+ const best = { it: null as TrimItem | null, d: tol };
72
+ const consider = (it: TrimItem) => { const d = distTo(sk, it, p); if (d <= best.d) { best.it = it; best.d = d; } };
73
+ for (const id in sk.lines) if (!sk.lines[id].anchor && !sk.lines[id].cab) consider({ kind: 'line', id });
74
+ for (const id in sk.arcs) consider({ kind: 'arc', id });
75
+ for (const id in sk.circles) consider({ kind: 'circle', id });
76
+ return best.it;
77
+ }
78
+
79
+ /**
80
+ * Every cut along an item, in order. `tol` (inches): a point this near it is on it; a cut this near its own end isn't
81
+ * one; cuts this near each other are one (a point there wins over a crossing).
82
+ */
83
+ export function cutsOn(sk: Sketch, it: TrimItem, tol: number): Cut[] {
84
+ const pt = (id: string): XY | undefined => sk.points[id];
85
+ let A: XY | undefined, B: XY | undefined, len = 0, R: Round | null = null;
86
+ if (it.kind === 'line') {
87
+ const l = sk.lines[it.id];
88
+ A = l && pt(l.a); B = l && pt(l.b);
89
+ if (!A || !B) return [];
90
+ len = hyp(B.x - A.x, B.y - A.y);
91
+ if (len < 1e-9) return [];
92
+ } else {
93
+ R = roundOf(sk, it);
94
+ if (!R || R.r < 1e-9) return [];
95
+ }
96
+ const raw: Cut[] = [];
97
+ /** Where `p` is along the item, if it's inside it (clear of the item's own ends). */
98
+ const along = (p: XY): number | null => {
99
+ if (A && B) { const t = projT(A, B, p); return t * len > tol && (1 - t) * len > tol ? t : null; }
100
+ const s = roundAt(R!, p);
101
+ return it.kind === 'circle' || (s < R!.sweep && s * R!.r > tol && (R!.sweep - s) * R!.r > tol) ? s : null;
102
+ };
103
+ const add = (p: XY, by: Omit<Cut, 'at' | 'x' | 'y'>) => { const at = along(p); if (at != null) raw.push({ at, x: p.x, y: p.y, ...by }); };
104
+
105
+ // what crosses it: lines…
106
+ for (const id in sk.lines) {
107
+ if (it.kind === 'line' && id === it.id) continue;
108
+ const l = sk.lines[id], C = pt(l.a), D = pt(l.b);
109
+ if (!C || !D) continue;
110
+ if (A && B) { const r = segInter(A, B, C, D); if (r) add(r, { line: id }); }
111
+ else for (const t of lineCircleT(C, D, R!.o, R!.r)) add({ x: C.x + (D.x - C.x) * t, y: C.y + (D.y - C.y) * t }, { line: id });
112
+ }
113
+ // …and arcs and circles (an arc only where it's drawn)
114
+ const curves: TrimItem[] = [...Object.keys(sk.circles).map(id => ({ kind: 'circle' as const, id })), ...Object.keys(sk.arcs).map(id => ({ kind: 'arc' as const, id }))];
115
+ for (const cv of curves) {
116
+ if (cv.kind === it.kind && cv.id === it.id) continue;
117
+ const Q = roundOf(sk, cv);
118
+ if (!Q) continue;
119
+ const hits = A && B ? lineCircleT(A, B, Q.o, Q.r).map(t => ({ x: A!.x + (B!.x - A!.x) * t, y: A!.y + (B!.y - A!.y) * t })) : circleCircle(R!.o, R!.r, Q.o, Q.r);
120
+ for (const p of hits) if (cv.kind === 'circle' || roundAt(Q, p) <= Q.sweep + 1e-9) add(p, { curve: cv.id });
121
+ }
122
+ // the points on it that something else is attached at: other lines' and arcs' ends, points held on it
123
+ const held = new Set<string>();
124
+ for (const id in sk.lines) if (!(it.kind === 'line' && id === it.id)) { held.add(sk.lines[id].a); held.add(sk.lines[id].b); }
125
+ for (const id in sk.arcs) if (!(it.kind === 'arc' && id === it.id)) { held.add(sk.arcs[id].a); held.add(sk.arcs[id].b); }
126
+ for (const c of Object.values(sk.cons)) if (c.p && ((c.type === 'online' && !c.face && c.l === it.id) || (c.type === 'oncircle' && c.c === it.id))) held.add(c.p);
127
+ for (const id of held) {
128
+ const P = pt(id);
129
+ if (!P) continue;
130
+ const off = A && B ? distSeg(P, A, B) : Math.abs(hyp(P.x - R!.o.x, P.y - R!.o.y) - R!.r);
131
+ if (off <= tol) add(P, { pt: id });
132
+ }
133
+
134
+ raw.sort((a, b) => a.at - b.at);
135
+ const scale = A && B ? len : R!.r, out: Cut[] = [];
136
+ for (const c of raw) {
137
+ const last = out[out.length - 1];
138
+ if (last && (c.at - last.at) * scale <= tol) { if (c.pt && !last.pt) out[out.length - 1] = c; continue; }
139
+ out.push(c);
140
+ }
141
+ // (round a circle, the last cut and the first can be one place)
142
+ if (it.kind === 'circle' && out.length > 1 && (2 * Math.PI - out[out.length - 1].at + out[0].at) * scale <= tol) {
143
+ const last = out.pop()!;
144
+ if (last.pt && !out[0].pt) out[0] = last;
145
+ }
146
+ return out;
147
+ }
148
+
149
+ /** The piece of an item around `p` (on it): between the cuts either side of it. */
150
+ export function pieceAt(sk: Sketch, it: TrimItem, p: XY, tol: number): TrimPiece {
151
+ const cuts = cutsOn(sk, it, tol);
152
+ let at: number;
153
+ if (it.kind === 'line') { const l = sk.lines[it.id]; at = projT(sk.points[l.a], sk.points[l.b], p); }
154
+ else { const R = roundOf(sk, it); at = R ? roundAt(R, p) : 0; }
155
+ if (it.kind === 'circle') {
156
+ // round a circle, the cut before it (from the last, if none is) and the one after (the first, if none is)
157
+ if (cuts.length < 2) return { item: it, lo: null, hi: null };
158
+ let lo = cuts[cuts.length - 1], hi = cuts[0];
159
+ for (const c of cuts) if (c.at <= at) lo = c;
160
+ for (const c of cuts) if (c.at > at) { hi = c; break; }
161
+ return { item: it, lo, hi };
162
+ }
163
+ let lo: Cut | null = null, hi: Cut | null = null;
164
+ for (const c of cuts) { if (c.at <= at) lo = c; else if (!hi) hi = c; }
165
+ return { item: it, lo, hi };
166
+ }
167
+
168
+ /** A piece as it's drawn: a segment, or a circle's arc from t0 counter-clockwise to t1 (radians). */
169
+ export type PieceShape = { a: XY; b: XY } | { o: XY; r: number; t0: number; t1: number };
170
+
171
+ export function pieceShape(sk: Sketch, pc: TrimPiece): PieceShape | null {
172
+ const it = pc.item;
173
+ if (it.kind === 'line') {
174
+ const l = sk.lines[it.id], A = l && sk.points[l.a], B = l && sk.points[l.b];
175
+ return A && B ? { a: pc.lo ?? A, b: pc.hi ?? B } : null;
176
+ }
177
+ const R = roundOf(sk, it);
178
+ if (!R) return null;
179
+ if (it.kind === 'circle') {
180
+ if (!pc.lo || !pc.hi) return { o: R.o, r: R.r, t0: 0, t1: 2 * Math.PI };
181
+ return { o: R.o, r: R.r, t0: pc.lo.at, t1: pc.hi.at > pc.lo.at ? pc.hi.at : pc.hi.at + 2 * Math.PI };
182
+ }
183
+ return { o: R.o, r: R.r, t0: R.t0 + (pc.lo?.at ?? 0), t1: R.t0 + (pc.hi?.at ?? R.sweep) };
184
+ }
185
+
186
+ /** One key per piece: a stroke takes each piece once, however many times it crosses it. */
187
+ export const pieceKey = (pc: TrimPiece): string =>
188
+ pc.item.kind + ':' + pc.item.id + ':' + (pc.lo ? pc.lo.at.toFixed(9) : 's') + ':' + (pc.hi ? pc.hi.at.toFixed(9) : 'e');
@@ -45,25 +45,29 @@ export interface Constraint {
45
45
  /**
46
46
  * dist, a wall's length: measured along this face of it — +1 the face to the left of its line's A → B, −1 the right
47
47
  * — corner to corner, as a tape measure along it reads. diam, a curved wall's: across its outer face (+1) or its
48
- * inner face (−1). Unset, its centreline.
48
+ * inner face (−1). online, a point on a straight wall: on this face of it, half its thickness off the centreline.
49
+ * Unset, its centreline.
49
50
  */
50
51
  face?: 1 | -1;
51
52
  /**
52
53
  * pldist between two walls (`l`, and `l2`, the wall `p` is on): which face of each it's measured from — +1 the face
53
- * toward the other, −1 the far one, 0 the centreline; unset, both centrelines.
54
+ * toward the other, −1 the far one, 0 the centreline; unset, both centrelines. eq: each line's length along this face
55
+ * of it, corner to corner, as `face` is for a wall's length (+1 the left of its A → B, −1 the right, 0 as drawn).
54
56
  */
55
57
  faces?: [number, number];
56
58
  value?: number;
57
59
  /** false = reference dimension (shown in parentheses, never drives geometry) */
58
60
  driving?: boolean;
59
- /** label offset from the measured span (diameters: how far past the circle the label sits) */
61
+ /**
62
+ * label offset from the measured span, across it (a distance between parallel lines or walls: along them); a
63
+ * diameter's, how far past the circle the label sits; an angle's, its arc's radius once pulled off its corner
64
+ */
60
65
  off?: number;
61
- /** diameters: direction of the diameter line, radians (default 60°) */
66
+ /** diameters: direction of the diameter line, radians (default 60°); an angle's label, from its corner (default: the bisector) */
62
67
  ang?: number;
63
68
  /** diameters: shown, entered and measured as a radius (a fillet's) — `value` is still the diameter */
64
69
  rad?: boolean;
65
70
  sign?: number;
66
- layer?: 'print';
67
71
  /**
68
72
  * the drawing's own BOLDR measurement this driving dimension's value is kept in — made automatically by drawings
69
73
  * from before Save to group, and still kept in step for them; no new ones are made
@@ -88,7 +92,11 @@ export interface Constraint {
88
92
  anchor?: boolean;
89
93
  }
90
94
 
91
- /** A room: its corner points, and (for loops with curves) the line or arc id after each corner. */
95
+ /**
96
+ * A room: its corner points, and (for loops with curves) the edge after each corner — a line's or an arc's id, or a
97
+ * circle's with the way round it ('<id>+' counter-clockwise, '<id>-' clockwise). Round a whole circle: no corners,
98
+ * loop [] and edges ['<id>+'] (model/rooms.ts).
99
+ */
92
100
  export interface Room { loop: string[]; name: string; edges?: string[] }
93
101
  /** An opening's two anchor points: its edges on the wall centerline (s0 nearer the line's start). */
94
102
  export type OpeningEdge = 's0' | 's1';
@@ -126,7 +134,10 @@ export interface Opening {
126
134
  */
127
135
  wSet?: boolean;
128
136
  type: 'door' | 'window';
137
+ /** a door: it swings to the right of its line's start → end (unset or false: to its left) */
129
138
  flip: boolean;
139
+ /** a door: the jamb it hangs on — its s1 edge (toward its line's end), or unset: its s0 edge. See flipDoor. */
140
+ hinge?: OpeningEdge;
130
141
  dims?: OpeningDim[];
131
142
  /** where the width dimension sits (as OpeningDim.off); unset = automatic */
132
143
  wOff?: number;
@@ -175,8 +186,9 @@ export interface LinkTarget {
175
186
  }
176
187
 
177
188
  /**
178
- * What a link is now, as the host says (LinkTarget has what it was when it was linked). A file's kind, for its icon:
179
- * 'photo', 'pdf', 'document', 'note', 'calculator', 'label', 'drawing' or anything else (a plain page).
189
+ * What a link is now, as the host says (LinkTarget has what it was when it was linked). A file's kind, for its tile
190
+ * (render/linkTile.ts tileTypeOf): 'photo', 'drawing' (a sketch: with its picture, a picture too), 'pdf', 'note',
191
+ * 'calculator' (a construction calculator), 'conversion', 'label', 'cad', or anything else (a document).
180
192
  */
181
193
  export interface LinkInfo {
182
194
  /** false once the host has looked and it's gone — never while it's still loading */
@@ -189,11 +201,18 @@ export interface LinkInfo {
189
201
  mkind?: string;
190
202
  /** µm for a length, degrees for an angle */
191
203
  value?: number;
192
- /** the value as the host writes it */
204
+ /**
205
+ * What its tile shows as text, written the host's way: a measurement's value; a note's text — its blocks a blank line
206
+ * apart, a line within one for each of its lines, a list item's starting "• " and a numbered one's with its number
207
+ * ("1. "), each line wrapped to the tile, as many as fit (12 at most); a conversion's units, from and to, a line each,
208
+ * then the value typed as the conversion screen shows it (5' 3 1/2", 2 lbs 5 oz), if any
209
+ */
193
210
  text?: string;
194
211
  /** whether it can be opened from the drawing, and its measurement changed from it (unset: yes) */
195
212
  canOpen?: boolean;
196
213
  canEdit?: boolean;
214
+ /** whether it can be shown in a pane beside the drawing, when the host has one (CadLinks.openBeside; unset: yes) */
215
+ canOpenBeside?: boolean;
197
216
  }
198
217
  export interface Note {
199
218
  id: string;
@@ -277,6 +296,19 @@ export interface Sketch {
277
296
  faces?: Record<string, WallFace>;
278
297
  /** earlier versions' lines on wall elevations (same keys): moved into `faces` when the drawing loads */
279
298
  elev?: Record<string, ElevLine[]>;
299
+ /**
300
+ * A new page has no scale until its first length is entered (see model/scale.ts): what's drawn before it is a
301
+ * sketch, its inches only placeholders. Set on pages made since (newPage); a page without it has its scale.
302
+ */
303
+ unscaled?: boolean;
304
+ /** what the first length scaled the sketch by (its view zoomed by the inverse, so nothing moved): undone, it zooms back */
305
+ scaledBy?: number;
306
+ /**
307
+ * The size the page's tiles were first placed at, inches on the drawing: what the Tile size slider calls 100%. It
308
+ * scales with the page when the first length gives it its scale, as the tiles do, so the percent holds. Unset on a
309
+ * page from before it (its tiles' size now is 100%, kept once the slider's used).
310
+ */
311
+ tileRef?: number;
280
312
  }
281
313
 
282
314
  /** Photo or plan traced under the drawing. (x, y) is its top-left corner in world inches. */
@@ -294,6 +326,22 @@ export interface Underlay {
294
326
  rot?: number;
295
327
  /** locked images can't be moved, resized or turned (they still show, and can be unlocked) */
296
328
  locked?: boolean;
329
+ /**
330
+ * Cropped by its side handles (the user's call, 2026-11-03: they crop, never stretch): what's trimmed off each side of
331
+ * the picture, as a share of it (0 to 1 — l and r across it, t and b down it, as it's seen before it's turned). x, y,
332
+ * w, h are the part that shows; the picture is drawn whole past them and clipped, so a side dragged back out brings
333
+ * it back. Unset: all of it shows.
334
+ */
335
+ crop?: { l: number; t: number; r: number; b: number };
336
+ /**
337
+ * A photo of the job's, expanded from its tile (the user's call, 2026-10-26: "it expands the image to full size + shows
338
+ * a link icon in the corner", as Skia's annotation does): its picture is the one the host shows it as (LinkInfo.thumbUrl;
339
+ * `src` is left empty, so nothing of it is uploaded or kept with the drawing), the badge in its top-right corner opens
340
+ * it, and Expand in its bar folds it back into its tile. An image from the computer has none.
341
+ */
342
+ link?: LinkTarget;
343
+ /** an expanded photo's tile as it was (its look and its leader), to fold back into */
344
+ tile?: Omit<Note, 'x' | 'y' | 'link'>;
297
345
  }
298
346
  export interface View { cx: number; cy: number; s: number }
299
347
  /** underlays: the page's photos and plans, drawn in order (the last on top) */