@reekon-tools/boldr-cad 1.0.0 → 1.2.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 (85) hide show
  1. package/README.md +38 -8
  2. package/dist/data/memory.d.ts +4 -1
  3. package/dist/data/memory.js +11 -6
  4. package/dist/data/memory.js.map +1 -1
  5. package/dist/data/provider.d.ts +11 -3
  6. package/dist/data/sync.d.ts +7 -1
  7. package/dist/data/sync.js +18 -2
  8. package/dist/data/sync.js.map +1 -1
  9. package/dist/editor/actions.js +13 -1
  10. package/dist/editor/actions.js.map +1 -1
  11. package/dist/editor/elevation.js +1 -1
  12. package/dist/editor/elevation.js.map +1 -1
  13. package/dist/editor/face.js +2 -2
  14. package/dist/editor/face.js.map +1 -1
  15. package/dist/editor/keypad.d.ts +1 -1
  16. package/dist/editor/keypad.js +24 -7
  17. package/dist/editor/keypad.js.map +1 -1
  18. package/dist/editor/ports.d.ts +2 -0
  19. package/dist/editor/ports.js.map +1 -1
  20. package/dist/editor/project.d.ts +2 -2
  21. package/dist/editor/project.js +7 -4
  22. package/dist/editor/project.js.map +1 -1
  23. package/dist/index.d.ts +3 -2
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/model/planGeom.js +2 -2
  27. package/dist/model/planGeom.js.map +1 -1
  28. package/dist/react/CadEditor.d.ts +8 -1
  29. package/dist/react/CadEditor.js +3 -2
  30. package/dist/react/CadEditor.js.map +1 -1
  31. package/dist/react/Popovers.js +2 -2
  32. package/dist/react/Popovers.js.map +1 -1
  33. package/dist/react/host.d.ts +10 -0
  34. package/dist/react/host.js +19 -1
  35. package/dist/react/host.js.map +1 -1
  36. package/dist/tools/chamfer.js +3 -3
  37. package/dist/tools/chamfer.js.map +1 -1
  38. package/dist/tools/dim.js +4 -4
  39. package/dist/tools/dim.js.map +1 -1
  40. package/dist/tools/elevation.js +4 -4
  41. package/dist/tools/elevation.js.map +1 -1
  42. package/dist/tools/fillet.js +1 -1
  43. package/dist/tools/fillet.js.map +1 -1
  44. package/dist/tools/index.d.ts +1 -0
  45. package/dist/tools/index.js +3 -0
  46. package/dist/tools/index.js.map +1 -1
  47. package/dist/tools/openings.js +3 -3
  48. package/dist/tools/openings.js.map +1 -1
  49. package/dist/tools/survey.d.ts +24 -0
  50. package/dist/tools/survey.js +233 -0
  51. package/dist/tools/survey.js.map +1 -0
  52. package/dist/units/format.d.ts +1 -1
  53. package/dist/units/format.js +9 -3
  54. package/dist/units/format.js.map +1 -1
  55. package/dist/units/parse.d.ts +2 -1
  56. package/dist/units/parse.js +5 -4
  57. package/dist/units/parse.js.map +1 -1
  58. package/dist/units/prefs.d.ts +9 -1
  59. package/dist/units/prefs.js +6 -0
  60. package/dist/units/prefs.js.map +1 -1
  61. package/package.json +1 -1
  62. package/src/data/memory.ts +13 -8
  63. package/src/data/provider.ts +10 -3
  64. package/src/data/sync.ts +20 -3
  65. package/src/editor/actions.ts +14 -3
  66. package/src/editor/elevation.ts +1 -1
  67. package/src/editor/face.ts +2 -2
  68. package/src/editor/keypad.ts +24 -7
  69. package/src/editor/ports.ts +2 -0
  70. package/src/editor/project.ts +7 -5
  71. package/src/index.ts +3 -2
  72. package/src/model/planGeom.ts +2 -2
  73. package/src/react/CadEditor.tsx +10 -5
  74. package/src/react/Popovers.tsx +3 -3
  75. package/src/react/host.ts +19 -1
  76. package/src/tools/chamfer.ts +3 -3
  77. package/src/tools/dim.ts +4 -4
  78. package/src/tools/elevation.ts +4 -4
  79. package/src/tools/fillet.ts +1 -1
  80. package/src/tools/index.ts +4 -0
  81. package/src/tools/openings.ts +3 -3
  82. package/src/tools/survey.ts +216 -0
  83. package/src/units/format.ts +6 -3
  84. package/src/units/parse.ts +4 -3
  85. package/src/units/prefs.ts +13 -1
@@ -0,0 +1,233 @@
1
+ import { afterEdit, analyze, tryAddConstraint } from '../app/edit';
2
+ import { ed } from '../app/state';
3
+ import { measure } from '../model/constraints';
4
+ import { hyp } from '../model/geom';
5
+ import { pushHistory } from '../model/history';
6
+ import { maybeRoom, roomGeom } from '../model/rooms';
7
+ import { onSessionReset, sessionState } from '../model/session';
8
+ import { addLine, addPoint, gc, lineBetween, pinnedPoints } from '../model/sketch';
9
+ import { page, SK, store } from '../model/store';
10
+ import { requestDraw, requestOverlayDraw, S, V } from '../render/view';
11
+ import { fmtArea, fmtLen } from '../units';
12
+ import { elevOpen } from '../editor/elevation';
13
+ import { keypadOpen } from '../editor/keypad';
14
+ import { drawsWalls, setMode } from '../editor/mode';
15
+ import { toast, updateCtx, updateHint } from '../editor/ports';
16
+ import { close3d } from '../editor/view3d';
17
+ import { is3d } from '../editor/view3dState';
18
+ import { anchorMark, setTool } from './registry';
19
+ /*
20
+ The templator: points measured by a device in the host app (BOLDR's T1R) build the drawing directly. Each point joins
21
+ the last with a line (or a wall, with the Walls toggle on), and the drawing holds what was measured: a driving
22
+ length on every edge, and a driving angle at every corner. That fixes the shape's FORM — a later drag moves it
23
+ whole, it doesn't flex it — while leaving it free to be moved and turned into place. On a closed shape the last
24
+ edge and the corners either side of it are already set by the rest, so they come out as reference dimensions
25
+ (grey, in parentheses) rather than being refused: rule 3, and the closing edge's length still shows.
26
+
27
+ Where it lands: the device measures from itself, not from the page. The first point taken on a page puts its frame
28
+ there — that point at the middle of the screen — and every later point keeps its true place relative to it, for
29
+ the rest of the session. Kept per page and not per run on purpose: a cutout taken after its outline, from the same
30
+ setup, has to land where it really is relative to the outline, or templating it was pointless.
31
+
32
+ Points come in on the plan's own axes — x to the right, y up (render/view.ts's S) — and are placed as given. Which
33
+ way a device's angle turns is the host's business, not the editor's: the host turns its device's frame into this
34
+ one, and that's where a mirror gets in or doesn't. BOLDR's T1R turns clockwise, so the app negates y
35
+ (templatorPlanInches); passing its x = r·cos θ, y = r·sin θ straight in drew every template mirrored (seen on a device,
36
+ 2026-10-28).
37
+ */
38
+ /** How close a point has to land to the first one to close the shape: about 50 mm, inside the device's own error. */
39
+ export const SURVEY_CLOSE_IN = 2;
40
+ /** A point this close to the last one is the same point taken twice (a double press), not a new corner. */
41
+ export const SURVEY_SAME_IN = 1 / 16;
42
+ /** Each page's frame for the session: what's added to a device point to place it on the plan. */
43
+ const frames = sessionState(() => ({ byPage: {} }));
44
+ const fresh = () => ({ chain: [], captures: {} });
45
+ const withCapture = (capture) => (capture !== undefined ? { capture } : {});
46
+ /** A driving length on line `lid`, as the two points it joins are now (so nothing moves). */
47
+ function holdLength(lid, a, b, capture) {
48
+ const sk = SK(), pa = sk.points[a], pb = sk.points[b];
49
+ if (!pa || !pb)
50
+ return;
51
+ tryAddConstraint({ type: 'dist', a, b, kind: 'al', value: hyp(pb.x - pa.x, pb.y - pa.y), line: lid, driving: true, off: 0, ...withCapture(capture) }, { silent: true });
52
+ }
53
+ /**
54
+ * A corner this close to dead straight (or folded right back) is held as Collinear, not as an angle: the angle's
55
+ * equation turns on |cross|, whose slope vanishes there, so the solver reads a 180° angle as adding nothing (a
56
+ * reference dimension) and the corner is left as a hinge. Collinear says the same thing and holds it. 1e-4° is far
57
+ * outside where that happens (about 2e-6°) and far inside any real capture's spread, so a measured corner is never
58
+ * squared off by it — over a 30" edge it's a micrometre.
59
+ */
60
+ const STRAIGHT_DEG = 1e-4;
61
+ /** A driving angle at the corner where `l1` and `l2` meet, as it is now (Collinear, if it's straight). */
62
+ function holdAngle(l1, l2, capture) {
63
+ if (!l1 || !l2 || l1 === l2)
64
+ return;
65
+ const c = { type: 'angle', l1, l2, value: 0, driving: true };
66
+ c.value = measure(SK(), c);
67
+ if (c.value < STRAIGHT_DEG || c.value > 180 - STRAIGHT_DEG) {
68
+ tryAddConstraint({ type: 'col', l1, l2 }, { silent: true });
69
+ return;
70
+ }
71
+ tryAddConstraint({ ...c, ...withCapture(capture) }, { silent: true });
72
+ }
73
+ export const surveyTool = {
74
+ st: fresh(),
75
+ /** Picked up (by a point arriving with another tool in hand): a new run. */
76
+ enter() { this.st = fresh(); },
77
+ /**
78
+ * Put down by cancelTool. Deliberately NOT the end of the run: undo cancels the tool in hand before it steps back,
79
+ * and ending the run there would leave a gap after every bad point undone. The run ends on Finish, Close shape,
80
+ * Esc or Enter (key, below) — which also put the templator down — or when another tool is picked up; either way the
81
+ * next point starts a new run (enter).
82
+ */
83
+ reset() { ed.preview = null; },
84
+ busy() { return false; },
85
+ /** Esc and Enter finish the run here, before the generic Esc (which can't tell itself from undo's cancel). */
86
+ key(e) {
87
+ if ((e.key === 'Escape' || e.key === 'Enter') && this.st.chain.length) {
88
+ this.finish();
89
+ return true;
90
+ }
91
+ return false;
92
+ },
93
+ /**
94
+ * End the run: the shape stays as drawn, and the templator is put down (back to Select). Left in hand it would
95
+ * swallow every tap — it has no pointer handling, its points come from the device — so the shape just made couldn't
96
+ * be selected, and a phone's bar, which drops the "Templator" label, wouldn't even say why. The next point picks it
97
+ * up again, in the same frame. A run of one point leaves nothing (a point on its own isn't a shape).
98
+ */
99
+ finish() {
100
+ const first = this.st.chain[0];
101
+ if (first)
102
+ pinnedPoints.delete(first);
103
+ this.st = fresh();
104
+ ed.preview = null;
105
+ if (store.project) {
106
+ gc(SK());
107
+ analyze();
108
+ }
109
+ if (ed.tool === 'survey')
110
+ setTool('select');
111
+ updateCtx();
112
+ updateHint();
113
+ requestDraw();
114
+ },
115
+ /** Close the run back to its first point (the bar's Close shape, or a point taken there). */
116
+ closeShape() {
117
+ if (liveChain().length < 3)
118
+ return;
119
+ pushHistory();
120
+ closeRun(undefined);
121
+ afterEdit();
122
+ },
123
+ hint() {
124
+ return this.st.chain.length
125
+ ? 'Take the next point with your templator. Take one back at the first point to close the shape.'
126
+ : 'Take points with your templator: each one joins the last.';
127
+ },
128
+ };
129
+ /** The run's points that are still in the drawing (an undo since can have taken the last few back). */
130
+ function liveChain() {
131
+ const sk = SK(), st = surveyTool.st;
132
+ const keep = st.chain.filter(id => sk.points[id]);
133
+ if (keep.length !== st.chain.length)
134
+ st.chain = keep;
135
+ return keep;
136
+ }
137
+ /** The run's first and last points, marked: where the next point joins, and where one would close the shape. */
138
+ function showRun() {
139
+ const sk = SK(), ch = surveyTool.st.chain;
140
+ const ends = [ch[0], ch[ch.length - 1]].filter((id, i, a) => id && sk.points[id] && a.indexOf(id) === i);
141
+ ed.preview = ends.length ? (c, T) => { for (const id of ends) {
142
+ const p = sk.points[id];
143
+ if (p) {
144
+ const [x, y] = S(T, p.x, p.y);
145
+ anchorMark(c, x, y);
146
+ }
147
+ } } : null;
148
+ requestOverlayDraw();
149
+ }
150
+ /** Close the run: the last edge back to the first point, its length, the two corners it makes, and a space. */
151
+ function closeRun(capture) {
152
+ const sk = SK(), st = surveyTool.st, ch = st.chain;
153
+ const first = ch[0], last = ch[ch.length - 1];
154
+ const lid = addLine(sk, last, first, { kind: drawsWalls() ? 'wall' : 'line', constr: !drawsWalls() && ed.drawConstr });
155
+ // (already set by the rest of the shape: these come out as reference dimensions)
156
+ holdLength(lid, last, first, capture ?? st.captures[first]);
157
+ holdAngle(lineBetween(sk, ch[ch.length - 2], last), lid, st.captures[last]);
158
+ holdAngle(lid, lineBetween(sk, first, ch[1]), st.captures[first]);
159
+ const n = ch.length, rid = drawsWalls() || !ed.drawConstr ? maybeRoom(sk, ch.slice()) : null;
160
+ surveyTool.finish();
161
+ toast(rid ? sk.rooms[rid].name + ' added — ' + fmtArea(roomGeom(sk, sk.rooms[rid]).area) : 'Closed shape, ' + n + ' sides');
162
+ }
163
+ /**
164
+ * A point from the host's device, in inches, on the plan's axes — y up (see the note at the top). Starts the
165
+ * templator if it isn't in hand, and the run's frame on a page with none yet. False when the drawing can't take it
166
+ * now (the toast says why).
167
+ */
168
+ export function takeSurveyPoint(x, y, capture) {
169
+ if (ed.readOnly)
170
+ return false;
171
+ if (elevOpen()) {
172
+ toast('Back out of the wall view to take templator points on the plan.', { warn: true });
173
+ return false;
174
+ }
175
+ if (keypadOpen()) {
176
+ toast('Close the keypad, then take the point again.', { warn: true });
177
+ return false;
178
+ }
179
+ // (the templator works on the plan: from the 3D tab or Annotate, back to Draft)
180
+ if (is3d())
181
+ close3d();
182
+ if (ed.mode !== 'draft')
183
+ setMode('draft');
184
+ if (ed.tool !== 'survey')
185
+ setTool('survey');
186
+ const sk = SK(), st = surveyTool.st, ch = liveChain();
187
+ const pg = page().id;
188
+ if (!frames.byPage[pg]) {
189
+ const v = V();
190
+ frames.byPage[pg] = { dx: v.cx - x, dy: v.cy - y };
191
+ }
192
+ const f = frames.byPage[pg], wx = x + f.dx, wy = y + f.dy;
193
+ const last = ch.length ? sk.points[ch[ch.length - 1]] : null;
194
+ if (last && hyp(wx - last.x, wy - last.y) < SURVEY_SAME_IN) {
195
+ toast('Same point as the last one — move the templator, then take the next.', { warn: true });
196
+ return false;
197
+ }
198
+ if (ch.length >= 3) {
199
+ const p0 = sk.points[ch[0]];
200
+ if (hyp(wx - p0.x, wy - p0.y) < SURVEY_CLOSE_IN) {
201
+ pushHistory();
202
+ closeRun(capture);
203
+ afterEdit();
204
+ return true;
205
+ }
206
+ }
207
+ pushHistory();
208
+ const id = addPoint(sk, wx, wy);
209
+ if (capture !== undefined)
210
+ st.captures[id] = capture;
211
+ if (!ch.length) {
212
+ // (on its own until the next point, garbage collection would take it: kept until the run ends)
213
+ pinnedPoints.add(id);
214
+ }
215
+ else {
216
+ const prev = ch[ch.length - 1];
217
+ const lid = addLine(sk, prev, id, { kind: drawsWalls() ? 'wall' : 'line', constr: !drawsWalls() && ed.drawConstr });
218
+ holdLength(lid, prev, id, capture);
219
+ if (ch.length >= 2)
220
+ holdAngle(lineBetween(sk, ch[ch.length - 2], prev), lid, st.captures[prev]);
221
+ }
222
+ st.chain.push(id);
223
+ ed.sel = [];
224
+ afterEdit();
225
+ showRun();
226
+ updateHint();
227
+ const n = st.chain.length, a = sk.points[st.chain[n - 2]];
228
+ toast(n === 1 ? 'Templator: point 1, at the middle of the screen' : 'Point ' + n + ' · ' + fmtLen(hyp(wx - a.x, wy - a.y)));
229
+ return true;
230
+ }
231
+ // a new session (another drawing): every page's frame and the run start again
232
+ onSessionReset(() => { surveyTool.st = fresh(); });
233
+ //# sourceMappingURL=survey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"survey.js","sourceRoot":"","sources":["../../src/tools/survey.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ export declare const hostUnits: () => UnitPrefs | null;
9
9
  /** Lengths read the host's way, rather than in units picked in the editor. */
10
10
  export declare const followingHostUnits: () => boolean;
11
11
  export declare function setOwnUnits(v: boolean): void;
12
- /** Format a length in inches: 150.5 → 12'-6 1/2" (ft-in), 150 1/2" (in), 3,823 mm (mm). */
12
+ /** Format a length in inches: 150.5 → 12'-6 1/2" (ft-in), 150 1/2" (in), 12.54' (ft), 3,823 mm (mm), 382.27 cm (cm), 3.823 m (m). */
13
13
  export declare function fmtLen(v: number, u?: UnitPrefs): string;
14
14
  /** Format an area given in square inches. */
15
15
  export declare function fmtArea(sqin: number, u?: UnitPrefs): string;
@@ -1,4 +1,4 @@
1
- import { getUnitPrefs } from './prefs';
1
+ import { getUnitPrefs, isMetric } from './prefs';
2
2
  let custom = null;
3
3
  let hostPrefs = null;
4
4
  /** The person picked the editor's own units in Settings over the host's. */
@@ -17,13 +17,19 @@ export const hostUnits = () => (custom ? hostPrefs ?? { unit: 'ftin', denom: 16
17
17
  /** Lengths read the host's way, rather than in units picked in the editor. */
18
18
  export const followingHostUnits = () => !!custom && !own;
19
19
  export function setOwnUnits(v) { own = v; }
20
- /** Format a length in inches: 150.5 → 12'-6 1/2" (ft-in), 150 1/2" (in), 3,823 mm (mm). */
20
+ /** Format a length in inches: 150.5 → 12'-6 1/2" (ft-in), 150 1/2" (in), 12.54' (ft), 3,823 mm (mm), 382.27 cm (cm), 3.823 m (m). */
21
21
  export function fmtLen(v, u = getUnitPrefs()) {
22
22
  v = Math.abs(v);
23
23
  if (custom && !own && u === getUnitPrefs())
24
24
  return custom(v);
25
25
  if (u.unit === 'mm')
26
26
  return Math.round(v * 25.4).toLocaleString() + ' mm';
27
+ if (u.unit === 'cm')
28
+ return (v * 2.54).toFixed(2) + ' cm';
29
+ if (u.unit === 'm')
30
+ return (v * 0.0254).toFixed(3) + ' m';
31
+ if (u.unit === 'ft')
32
+ return (v / 12).toFixed(2) + "'";
27
33
  const d = u.denom;
28
34
  const t = Math.round(v * d) / d;
29
35
  let ft = u.unit === 'ftin' ? Math.floor(t / 12 + 1e-9) : 0;
@@ -50,7 +56,7 @@ export function fmtLen(v, u = getUnitPrefs()) {
50
56
  }
51
57
  /** Format an area given in square inches. */
52
58
  export function fmtArea(sqin, u = getUnitPrefs()) {
53
- if (u.unit === 'mm')
59
+ if (isMetric(u.unit))
54
60
  return (sqin * 0.00064516).toFixed(2) + ' m²';
55
61
  return (sqin / 144).toFixed(1) + ' sq ft';
56
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/units/format.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/units/format.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,8 @@ export declare function evalExpr(src: string): number;
4
4
  /**
5
5
  * Parse a length and return inches, or null if it can't be read.
6
6
  * Accepts 12' 6 1/2", 12ft 6in, 150.5, 3.2m, 25mm, and arithmetic (10'+6).
7
- * Bare numbers are inches in ft-in / in mode and millimetres in mm mode.
7
+ * Bare numbers are read in the current unit: inches in ft-in and in modes, feet in ft, millimetres, centimetres or
8
+ * metres in those.
8
9
  */
9
10
  export declare function parseLen(str: string | null | undefined, u?: UnitPrefs): number | null;
10
11
  /** Parse a plain number (degrees etc.), allowing arithmetic and a trailing °. */
@@ -1,4 +1,4 @@
1
- import { getUnitPrefs } from './prefs';
1
+ import { bareScale, getUnitPrefs } from './prefs';
2
2
  /** Tiny recursive-descent evaluator for + - * / and parentheses. Throws on bad input. */
3
3
  export function evalExpr(src) {
4
4
  const t = src.replace(/\s+/g, '');
@@ -56,7 +56,8 @@ export function evalExpr(src) {
56
56
  /**
57
57
  * Parse a length and return inches, or null if it can't be read.
58
58
  * Accepts 12' 6 1/2", 12ft 6in, 150.5, 3.2m, 25mm, and arithmetic (10'+6).
59
- * Bare numbers are inches in ft-in / in mode and millimetres in mm mode.
59
+ * Bare numbers are read in the current unit: inches in ft-in and in modes, feet in ft, millimetres, centimetres or
60
+ * metres in those.
60
61
  */
61
62
  export function parseLen(str, u = getUnitPrefs()) {
62
63
  if (str == null)
@@ -82,8 +83,8 @@ export function parseLen(str, u = getUnitPrefs()) {
82
83
  }
83
84
  if (!isFinite(v))
84
85
  return null;
85
- if (u.unit === 'mm' && !hasUnit)
86
- v /= 25.4;
86
+ if (!hasUnit)
87
+ v *= bareScale(u.unit);
87
88
  return v;
88
89
  }
89
90
  /** Parse a plain number (degrees etc.), allowing arithmetic and a trailing °. */
@@ -1 +1 @@
1
- {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/units/parse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/units/parse.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,18 @@
1
- export type Unit = 'ftin' | 'in' | 'mm';
1
+ export type Unit = 'ftin' | 'in' | 'ft' | 'mm' | 'cm' | 'm';
2
2
  /** Display preferences. Geometry is always stored in inches; these only change how values read. */
3
3
  export interface UnitPrefs {
4
4
  unit: Unit;
5
5
  /** Fraction precision for imperial display: 16 → 1/16". */
6
6
  denom: number;
7
+ /** Whether lengths are typed and read with fractions (the fraction keys, Precision). Unset: feet-inches and inches are. */
8
+ fractions?: boolean;
7
9
  }
8
10
  /** Point the formatters at a live prefs object (the app passes its settings object). */
9
11
  export declare function useUnitPrefs(p: UnitPrefs): void;
10
12
  export declare function getUnitPrefs(): UnitPrefs;
13
+ /** Metric units: bare numbers, the grid and areas are metric. */
14
+ export declare const isMetric: (unit: Unit) => boolean;
15
+ /** Inches per unit typed as a bare number: what a number with no unit means in `unit`. */
16
+ export declare const bareScale: (unit: Unit) => number;
17
+ /** Whether lengths read with fractions (the fraction keys, Precision): as the prefs say, else for feet-inches and inches. */
18
+ export declare const showFractions: (u?: UnitPrefs) => boolean;
@@ -6,4 +6,10 @@ export function useUnitPrefs(p) {
6
6
  export function getUnitPrefs() {
7
7
  return current;
8
8
  }
9
+ /** Metric units: bare numbers, the grid and areas are metric. */
10
+ export const isMetric = (unit) => unit === 'mm' || unit === 'cm' || unit === 'm';
11
+ /** Inches per unit typed as a bare number: what a number with no unit means in `unit`. */
12
+ export const bareScale = (unit) => unit === 'ft' ? 12 : unit === 'mm' ? 1 / 25.4 : unit === 'cm' ? 1 / 2.54 : unit === 'm' ? 1 / 0.0254 : 1;
13
+ /** Whether lengths read with fractions (the fraction keys, Precision): as the prefs say, else for feet-inches and inches. */
14
+ export const showFractions = (u = getUnitPrefs()) => u.fractions ?? (u.unit === 'ftin' || u.unit === 'in');
9
15
  //# sourceMappingURL=prefs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefs.js","sourceRoot":"","sources":["../../src/units/prefs.ts"],"names":[],"mappings":"AASA,IAAI,OAAO,GAAc,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAErD,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAAC,CAAY;IACvC,OAAO,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"prefs.js","sourceRoot":"","sources":["../../src/units/prefs.ts"],"names":[],"mappings":"AAWA,IAAI,OAAO,GAAc,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAErD,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAAC,CAAY;IACvC,OAAO,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,iEAAiE;AACjE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC;AAEhG,0FAA0F;AAC1F,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAU,EAAU,EAAE,CAC9C,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3G,6HAA6H;AAC7H,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,GAAc,YAAY,EAAE,EAAW,EAAE,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-cad",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "BOLDR's 2D layout editor (React Native, iOS/Android/web) and how its drawings are kept in BOLDR.",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",
@@ -4,17 +4,20 @@ import type { CadDataProvider, CadFileMeta, CadMeasurementValue, CadPageDoc } fr
4
4
 
5
5
  const copy = <T,>(v: T): T => JSON.parse(JSON.stringify(v)) as T;
6
6
 
7
- export function memoryProvider(name = 'Test drawing', meta: CadFileMeta | null = null, pages: Record<string, CadPageDoc> = {}) {
8
- const fileSubs = new Set<(f: { name: string; meta: CadFileMeta | null } | null) => void>();
9
- const pageSubs = new Set<(p: Record<string, CadPageDoc>) => void>();
7
+ export function memoryProvider(name = 'Test drawing', meta: CadFileMeta | null = null, pages: Record<string, CadPageDoc> = {}, opts: { fromCache?: boolean } = {}) {
8
+ // (fromCache: what's here is the cache's word, as a device offline hears it, until confirm() brings the server's)
9
+ let fromCache = !!opts.fromCache;
10
+ const snap = () => ({ fromCache });
11
+ const fileSubs = new Set<(f: { name: string; meta: CadFileMeta | null } | null, m?: { fromCache: boolean }) => void>();
12
+ const pageSubs = new Set<(p: Record<string, CadPageDoc>, m?: { fromCache: boolean }) => void>();
10
13
  const state = { name, meta: meta && copy(meta), pages: copy(pages), writes: 0, uploads: 0, thumbnail: null as string | null, files: {} as Record<string, string>, measurements: {} as Record<string, CadMeasurementValue>, placed: [] as string[], placedLinked: [] as { groupId: string; id: string; columnId?: string }[] };
11
14
  const measSubs = new Set<(v: Record<string, CadMeasurementValue>) => void>();
12
15
  const emitMeas = () => measSubs.forEach(cb => cb(copy(state.measurements)));
13
- const emitFile = () => fileSubs.forEach(cb => cb({ name: state.name, meta: state.meta && copy(state.meta) }));
14
- const emitPages = () => pageSubs.forEach(cb => cb(copy(state.pages)));
16
+ const emitFile = () => fileSubs.forEach(cb => cb({ name: state.name, meta: state.meta && copy(state.meta) }, snap()));
17
+ const emitPages = () => pageSubs.forEach(cb => cb(copy(state.pages), snap()));
15
18
  const provider: CadDataProvider = {
16
- subscribeFile(cb) { fileSubs.add(cb); queueMicrotask(() => cb({ name: state.name, meta: state.meta && copy(state.meta) })); return () => { fileSubs.delete(cb); }; },
17
- subscribePages(cb) { pageSubs.add(cb); queueMicrotask(() => cb(copy(state.pages))); return () => { pageSubs.delete(cb); }; },
19
+ subscribeFile(cb) { fileSubs.add(cb); queueMicrotask(() => cb({ name: state.name, meta: state.meta && copy(state.meta) }, snap())); return () => { fileSubs.delete(cb); }; },
20
+ subscribePages(cb) { pageSubs.add(cb); queueMicrotask(() => cb(copy(state.pages), snap())); return () => { pageSubs.delete(cb); }; },
18
21
  async writeMeta(m) { state.meta = copy(m); state.writes++; emitFile(); },
19
22
  async writePage(id, p) { state.pages[id] = copy(p); state.writes++; emitPages(); },
20
23
  async deletePage(id) { delete state.pages[id]; state.writes++; emitPages(); },
@@ -33,5 +36,7 @@ export function memoryProvider(name = 'Test drawing', meta: CadFileMeta | null =
33
36
  const remote = (id: string, p: CadPageDoc) => { state.pages[id] = copy(p); emitPages(); };
34
37
  /** Change a measurement as someone else would (e.g. in the job's measurement list). */
35
38
  const remoteMeasurement = (id: string, value: number) => { state.measurements[id] = { ...state.measurements[id], value }; emitMeas(); };
36
- return { provider, state, remote, remoteMeasurement };
39
+ /** The server's word after a start from the cache: everything is sent again, confirmed. */
40
+ const confirm = () => { fromCache = false; emitFile(); emitPages(); };
41
+ return { provider, state, remote, remoteMeasurement, confirm };
37
42
  }
@@ -31,11 +31,18 @@ export interface CadPageDoc {
31
31
  rev?: { clientId: string; seq: number };
32
32
  }
33
33
 
34
+ /**
35
+ * Where a snapshot came from: the local cache, which may be stale and, offline, empty for what was never read on this
36
+ * device, or the server. Unset: the server. Firestore delivers the cache's answer first when it's offline; subscribe
37
+ * with metadata changes included, so the server's confirmation of an empty collection still arrives.
38
+ */
39
+ export interface CadSnapshotMeta { fromCache: boolean }
40
+
34
41
  export interface CadDataProvider {
35
- /** The file's name and drawing record (null record: not a drawing yet). Returns an unsubscribe function. */
36
- subscribeFile(cb: (file: { name: string; meta: CadFileMeta | null } | null) => void): () => void;
42
+ /** The file's name and drawing record (null record: not a drawing yet; null file: no such file). Returns an unsubscribe function. */
43
+ subscribeFile(cb: (file: { name: string; meta: CadFileMeta | null } | null, meta?: CadSnapshotMeta) => void): () => void;
37
44
  /** Every page of the drawing, by id. Returns an unsubscribe function. */
38
- subscribePages(cb: (pages: Record<string, CadPageDoc>) => void): () => void;
45
+ subscribePages(cb: (pages: Record<string, CadPageDoc>, meta?: CadSnapshotMeta) => void): () => void;
39
46
  writeMeta(meta: CadFileMeta): Promise<void>;
40
47
  writePage(id: string, page: CadPageDoc): Promise<void>;
41
48
  deletePage(id: string): Promise<void>;
package/src/data/sync.ts CHANGED
@@ -30,10 +30,16 @@ export class NotACadDrawing extends Error {
30
30
  }
31
31
 
32
32
  export interface CadSyncOptions {
33
- /** start a new drawing when the file has no drawing record yet (a file just created for it) */
33
+ /**
34
+ * The file was just created here: start its first page without waiting for the server's word on its pages (offline,
35
+ * the cache says there are none, as it says of any drawing never opened on this device). Also starts a new drawing
36
+ * when the file has no drawing record yet.
37
+ */
34
38
  initialize?: boolean;
35
39
  /** a write or upload failed */
36
40
  onError?: (e: unknown) => void;
41
+ /** closes the drawing while it's still opening (the person left): the promise rejects and nothing is written */
42
+ signal?: AbortSignal;
37
43
  }
38
44
 
39
45
  export interface CadSync {
@@ -72,6 +78,8 @@ const newClientId = () => Date.now().toString(36) + '-' + Math.floor(Math.random
72
78
  export function openCadDrawing(provider: CadDataProvider, opts: CadSyncOptions = {}): Promise<CadSync> {
73
79
  const clientId = newClientId();
74
80
  let seq = 0, file: { name: string; meta: CadFileMeta | null } | null | undefined, pages: Record<string, CadPageDoc> | undefined;
81
+ /** whether the latest file and pages snapshots are the cache's word rather than the server's */
82
+ let fileFromCache = false, pagesFromCache = false;
75
83
  /** what each page looked like when last saved or taken in (to tell local edits from remote ones) */
76
84
  const saved = new Map<string, string>();
77
85
  /** removed pages whose delete is queued or in flight */
@@ -245,6 +253,11 @@ export function openCadDrawing(provider: CadDataProvider, opts: CadSyncOptions =
245
253
  let ready = false;
246
254
  const tryOpen = () => {
247
255
  if (ready || file === undefined || pages === undefined) return;
256
+ // the cache's word isn't final: a file it hasn't got, or a drawing whose pages it hasn't got, may well be on the
257
+ // server — wait for its answer (a file just created here is fresh: its first page is made without waiting)
258
+ if (file === null && fileFromCache) return;
259
+ const fresh = !!opts.initialize && !file?.meta?.pageOrder.length;
260
+ if (file && !Object.keys(pages).length && pagesFromCache && !fresh) return;
248
261
  ready = true;
249
262
  if (file === null) { stop(); reject(new NotACadDrawing('missing')); return; }
250
263
  if (!file.meta && !opts.initialize) { stop(); reject(new NotACadDrawing('legacy')); return; }
@@ -277,9 +290,13 @@ export function openCadDrawing(provider: CadDataProvider, opts: CadSyncOptions =
277
290
  failures: () => failures,
278
291
  });
279
292
  };
280
- const unsubFile = provider.subscribeFile(f => { file = f; if (ready) takeIn(); else tryOpen(); });
281
- const unsubPages = provider.subscribePages(p => { pages = p; if (ready) takeIn(); else tryOpen(); });
293
+ const unsubFile = provider.subscribeFile((f, m) => { file = f; fileFromCache = !!m?.fromCache; if (ready) takeIn(); else tryOpen(); });
294
+ const unsubPages = provider.subscribePages((p, m) => { pages = p; pagesFromCache = !!m?.fromCache; if (ready) takeIn(); else tryOpen(); });
282
295
  unsubMeas = meas ? meas.subscribe(takeInMeasurements) : null;
283
296
  function stop() { stopped = true; unsubFile(); unsubPages(); unsubMeas?.(); }
297
+ // left while still opening: let go of the drawing, nothing written
298
+ const abort = () => { if (ready) return; ready = true; stop(); reject(new Error('closed')); };
299
+ if (opts.signal?.aborted) abort();
300
+ else opts.signal?.addEventListener('abort', abort, { once: true });
284
301
  });
285
302
  }
@@ -13,6 +13,7 @@ import { convertNote } from '../tools/annotate';
13
13
  import { cornerAngle, dimCurve, dimEdges, dimLine, dimPointLine, dimPoints, dimTwoLines, editDim, faceChoices, onChoice, setDimFace, toggleRef } from '../tools/dim';
14
14
  import { edgeLabel, edgeOf } from '../model/planGeom';
15
15
  import type { LineTool } from '../tools/line';
16
+ import type { SurveyTool } from '../tools/survey';
16
17
  import { offsetPrompt, splitLine } from '../tools/modify';
17
18
  import { filletable, filletPrompt } from '../tools/fillet';
18
19
  import { chamferPrompt } from '../tools/chamfer';
@@ -128,6 +129,15 @@ function ctxItems(): CtxEntry[] {
128
129
  // drawing: no bar (tap the last point again to finish, the first to close the shape; Esc or Enter on a keyboard)
129
130
  if (st.start) return items;
130
131
  }
132
+ if (ed.tool === 'survey') {
133
+ // The templator: its points come from the device, not from taps on the drawing — so unlike Line, nothing on the
134
+ // canvas can finish or close a run, and the bar has to. (A phone's bar drops the label; the toasts name it there.)
135
+ const n = (tools.survey as SurveyTool).st.chain.length;
136
+ items.push({ lbl: 'Templator' });
137
+ if (n >= 3) add('closeShape', 'Close shape', 'closeShape');
138
+ if (n >= 1) add('finish', 'Finish', 'finish');
139
+ return items;
140
+ }
131
141
  if (ed.tool === 'dim') { add('dim', 'Driving', 'dimDrive', { on: ed.dimLayer === 'drive' }); add('ref', 'Print', 'dimPrint', { on: ed.dimLayer === 'print' }); return items; }
132
142
  // nothing selected: the bar stays empty (Fit and Icons live in the top bar; grid snap and auto-constrain in Settings).
133
143
  // Just placed a door, window or cabinet (its tool still on): its bar, for its sizes
@@ -258,7 +268,7 @@ function thicknessPrompt(walls: { thick?: number }[]): void {
258
268
  const sk = SK();
259
269
  if (!walls.length) return;
260
270
  openKeypad({
261
- title: 'Wall thickness', sub: 'Finished face to face', initial: fmtLen(walls[0].thick || 4.5), selectAll: true,
271
+ title: 'Wall thickness', sub: 'Finished face to face', initial: fmtLen(walls[0].thick || 4.5), value: walls[0].thick || 4.5, selectAll: true,
262
272
  onDone: v => {
263
273
  if (v <= 0) return;
264
274
  // a thicker wall's faces are shorter (its corners come in): not shorter than what's on them — and what's measured
@@ -455,8 +465,9 @@ export function applyCtxAction(act: string, anchor?: unknown): void {
455
465
  break;
456
466
  case 'showcons': settings.showCons = !settings.showCons; updateTopBar(); break;
457
467
  case 'typelen': (tools[ed.tool] as LineTool).typeLength(''); return;
458
- case 'closeShape': (tools[ed.tool] as LineTool).closeShape(); updateCtx(); return;
459
- case 'finish': (tools[ed.tool] as LineTool).finish(); updateCtx(); return;
468
+ // (the Line tool's, or the templator's: both close and finish a run of points)
469
+ case 'closeShape': (tools[ed.tool] as LineTool | SurveyTool).closeShape(); updateCtx(); return;
470
+ case 'finish': (tools[ed.tool] as LineTool | SurveyTool).finish(); updateCtx(); return;
460
471
  case 'dimDrive': ed.dimLayer = 'drive'; break;
461
472
  case 'dimPrint': ed.dimLayer = 'print'; break;
462
473
  case 'h': case 'v': case 'par': case 'perp': case 'col': case 'eq': case 'mid': case 'online': case 'oncircle': case 'tan': case 'eqr': case 'conc':
@@ -646,7 +646,7 @@ export const heightTool = placingTool({
646
646
  },
647
647
  place: (_u, z) => {
648
648
  const q = 1 / settings.denom, start = clamp(Math.round(z / q) * q, 0, elev.frame!.H);
649
- openKeypad({ title: 'Height line', sub: 'Floor to the line — it runs across the whole wall', initial: fmtLen(start), selectAll: true, laser: true, onDone: v => { addHeightLine(v); } });
649
+ openKeypad({ title: 'Height line', sub: 'Floor to the line — it runs across the whole wall', initial: fmtLen(start), value: start, selectAll: true, laser: true, onDone: v => { addHeightLine(v); } });
650
650
  },
651
651
  hint: 'Tap the wall at a height, then enter it: a level line across the wall (a counter, a rail).',
652
652
  });
@@ -157,7 +157,7 @@ export function dimensionFace(kind: 'h' | 'v', a: string, b: string, title: stri
157
157
  if (!face.points[a] || !face.points[b]) return;
158
158
  const cur = measure(face, { type: 'dist', kind, a, b });
159
159
  openKeypad({
160
- title, initial: fmtLen(cur), selectAll: true, laser: true,
160
+ title, initial: fmtLen(cur), value: cur, selectAll: true, laser: true,
161
161
  onDone: v => {
162
162
  if (v < 0) return;
163
163
  const face = curFace(), pa = face.points[a], pb = face.points[b];
@@ -204,7 +204,7 @@ export function editCabinetDepth(cabId: string): void {
204
204
  const cb = curFace().cabs?.[cabId];
205
205
  if (!cb) return;
206
206
  openKeypad({
207
- title: 'Cabinet depth', sub: 'From the wall out into the room', initial: fmtLen(cb.depth), selectAll: true, laser: true,
207
+ title: 'Cabinet depth', sub: 'From the wall out into the room', initial: fmtLen(cb.depth), value: cb.depth, selectAll: true, laser: true,
208
208
  onDone: v => { if (v <= 0) return; pushHistory(); cb.depth = v; cb.depthSet = true; afterEdit(); },
209
209
  });
210
210
  }