@reekon-tools/boldr-cad 1.7.0 → 1.8.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.
@@ -11,6 +11,7 @@ import { settings } from '../model/settings';
11
11
  import { curveInfo, frameMemo, lineBetween, roomValid, withLineIndex } from '../model/sketch';
12
12
  import { page, planSK, SK } from '../model/store';
13
13
  import { elev, elevOpen } from '../editor/elevation';
14
+ import { ghostDim } from '../editor/refDim';
14
15
  import { cabPoints } from '../model/faces';
15
16
  import { drawKeyPlan, drawWallBackdrop, drawWallChips, drawWallSelection } from './elevation';
16
17
  import { fmtAng, fmtArea, fmtLen } from '../units';
@@ -84,7 +85,10 @@ export function chip(c, text, cx, cy, opts) {
84
85
  if (border) {
85
86
  c.lineWidth = border[1] || 1;
86
87
  c.strokeStyle = border[0];
88
+ if (opts.dash)
89
+ c.setLineDash(opts.dash);
87
90
  c.stroke();
91
+ c.setLineDash([]);
88
92
  }
89
93
  c.fillStyle = fg;
90
94
  c.textAlign = 'center';
@@ -785,8 +789,9 @@ function dimLive(sk, id, d) {
785
789
  }
786
790
  /**
787
791
  * Driving/reference dimension chips. (Lengths are added from the bottom bar's + / Length, not on-canvas prompts.) What's
788
- * been entered (blue, or red when it can't be met) always shows; a reference only while it or what it measures is
789
- * selected or hovered.
792
+ * been entered (blue, or red when it can't be met) always shows, and so does a reference kept by hand (Make reference:
793
+ * editor/refDim.ts); a reference the drawing set only while it or what it measures is selected or hovered. Then what the
794
+ * + would make for the selection, as it measures now: the ghost.
790
795
  */
791
796
  function drawDims(c, T, sk) {
792
797
  if (!settings.layers.dims)
@@ -795,95 +800,111 @@ function drawDims(c, T, sk) {
795
800
  const d = sk.cons[id];
796
801
  if (!isDim(d) || d.elev)
797
802
  continue; // the wall view's own dimensions are its chips
798
- const A = dimAnchor(sk, d);
799
- if (!A)
800
- continue;
801
- const ref = d.driving === false, val = ref ? measure(sk, d) : d.value;
802
- const selected = isSel('dim', id) || isSel('cons', id);
803
803
  const bad = ed.highlight.has(id) || ed.ana.bad.has(id);
804
- if (ref && !bad && !dimLive(sk, id, d))
805
- continue;
806
- // a measurement of the job sets it: the link badge before its value (chip()), on the chip's own colour — red once
807
- // the job's measurement is gone
808
- const linked = !!d.measurementRef && !ref && !bad, gone = linked && linkInfoOf(d.measurementRef)?.exists === false;
809
- let bg = ref ? TH.ref : TH.blue, fg = ref ? TH.reftext : '#fff';
810
- if (bad || gone) {
811
- bg = TH.red;
812
- fg = '#fff';
813
- }
814
- const border = selected ? [TH.yellow, 2.5] : null;
815
- const mark = linked ? 'link' : undefined;
816
- const wrap = (s) => (ref ? '(' : '') + s + (ref ? ')' : '');
817
- if (d.type === 'angle' && 'vtx' in A) {
818
- const [vx, vy] = S(T, A.vtx.x, A.vtx.y);
819
- // pulled off its corner: its arc out where the label is, the label riding on it; else a little arc at the
820
- // corner, the label out along the bisector
821
- const moved = angleLabel(sk, d), R = moved ? moved.r * T.s : 22;
822
- if (A.u1 && A.u2) {
823
- // arc between the two lines, so the angle reads as belonging to that corner
824
- const t1 = Math.atan2(-A.u1[1], A.u1[0]), t2 = Math.atan2(-A.u2[1], A.u2[0]);
825
- let sweep = t2 - t1;
826
- while (sweep > Math.PI)
827
- sweep -= 2 * Math.PI;
828
- while (sweep < -Math.PI)
829
- sweep += 2 * Math.PI;
830
- // (blue, as every other dimension is — the user's call, 2026-10-29; purple is the inference guides')
831
- c.save();
832
- c.strokeStyle = bad ? TH.red : ref ? TH.chipline : TH.blue;
833
- c.lineWidth = 1.2;
834
- if (ref)
835
- c.setLineDash([3, 3]);
836
- c.beginPath();
837
- c.arc(vx, vy, R, t1, t1 + sweep, sweep < 0);
838
- c.stroke();
839
- // (pulled out past where a line ends: an extension line along it, out to the arc)
840
- c.lineWidth = 1;
841
- for (const [u, len] of [[A.u1, A.len1], [A.u2, A.len2]]) {
842
- const from = (len ?? 0) * T.s;
843
- if (moved && R > from + 2) {
844
- c.beginPath();
845
- c.moveTo(vx + u[0] * from, vy - u[1] * from);
846
- c.lineTo(vx + u[0] * (R + 4), vy - u[1] * (R + 4));
847
- c.stroke();
848
- }
849
- }
850
- c.restore();
851
- }
852
- const text = wrap(fmtAng(val)), opts = { bg, fg, border, kind: 'dim', id, mark, ...dimChip() };
853
- // (left where it was drawn: 50 px out along the bisector, or as far as its corners stay clear of the corner — a
854
- // linked chip is wider)
855
- const sz = chipSize(c, text, opts), out = Math.max(50, Math.hypot(sz.w, sz.h) / 2 + 28);
856
- const [lx, ly] = moved ? [vx + Math.cos(moved.ang) * R, vy - Math.sin(moved.ang) * R] : [vx + A.bis[0] * out, vy - A.bis[1] * out];
857
- chip(c, text, lx, ly, opts);
858
- continue;
859
- }
860
- if (d.type === 'diam' && 'o' in A) {
861
- drawDiameter(c, T, A.o, A.r, d, d.rad ? 'R ' + wrap(fmtLen(val / 2)) : 'Ø ' + wrap(fmtLen(val)), bad ? TH.red : ref ? TH.chipline : TH.blue, { bg, fg, border, kind: 'dim', id, mark, ...dimChip() }, ref);
862
- continue;
863
- }
864
- if (!('a' in A))
804
+ if (d.driving === false && !d.kept && !bad && !dimLive(sk, id, d))
865
805
  continue;
866
- const [mx, my] = S(T, A.m.x, A.m.y);
867
- const lineColor = bad ? TH.red : ref ? TH.chipline : TH.blue;
868
- if ((d.type === 'dist' || d.type === 'pldist') && A.nx != null && Math.abs((d.off || 0) * T.s) > 10) {
869
- drawDimLeaders(c, T, A.a, A.b, A.nx, A.ny, d.off, lineColor, ref);
870
- }
871
- else if (d.type === 'pldist' || !d.line) {
872
- // (dashed only when it's a reference: a dashed line means the number holds nothing, wherever it's drawn)
873
- const [ax, ay] = S(T, A.a.x, A.a.y), [bx, by] = S(T, A.b.x, A.b.y);
806
+ drawDim(c, T, sk, d, id, bad);
807
+ }
808
+ const g = ghostDim();
809
+ if (g)
810
+ drawDim(c, T, sk, g, null, false);
811
+ }
812
+ /**
813
+ * One dimension: driving blue, a kept reference solid grey in brackets, red when it can't be met. Faded — the canvas's
814
+ * own colour inside a dashed grey outline, grey text, no brackets — when it shows only while it's selected: the ghost
815
+ * (`id` null) and a reference the drawing set. Its lines are dashed when it holds nothing.
816
+ */
817
+ function drawDim(c, T, sk, d, id, bad) {
818
+ const A = dimAnchor(sk, d);
819
+ if (!A)
820
+ return;
821
+ const ref = d.driving === false, val = ref ? measure(sk, d) : d.value;
822
+ const grey = ref || id === null, faded = !bad && (id === null || (ref && !d.kept));
823
+ const selected = id !== null && (isSel('dim', id) || isSel('cons', id));
824
+ // a measurement of the job sets it: the link badge before its value (chip()), on the chip's own colour — red once
825
+ // the job's measurement is gone
826
+ const linked = !!d.measurementRef && !ref && !bad, gone = linked && linkInfoOf(d.measurementRef)?.exists === false;
827
+ let bg = faded ? TH.bg : ref ? TH.ref : TH.blue, fg = faded ? TH.ink2 : ref ? TH.reftext : '#fff';
828
+ if (bad || gone) {
829
+ bg = TH.red;
830
+ fg = '#fff';
831
+ }
832
+ const border = selected ? [TH.yellow, 2.5] : faded ? [TH.chipline, 1.2] : null;
833
+ // (the ghost is a readout, never a tap target: it sits on what it measures, which a press there is for)
834
+ const opts = {
835
+ bg, fg, border, ...(id === null ? {} : { kind: 'dim', id }),
836
+ mark: linked ? 'link' : undefined, dash: faded && !selected ? [3, 2] : undefined, ...dimChip(),
837
+ };
838
+ const wrap = (s) => (ref && !faded ? '(' + s + ')' : s);
839
+ const lineColor = bad ? TH.red : grey ? TH.chipline : TH.blue;
840
+ if (d.type === 'angle' && 'vtx' in A) {
841
+ const [vx, vy] = S(T, A.vtx.x, A.vtx.y);
842
+ // pulled off its corner: its arc out where the label is, the label riding on it; else a little arc at the
843
+ // corner, the label out along the bisector
844
+ const moved = angleLabel(sk, d), R = moved ? moved.r * T.s : 22;
845
+ if (A.u1 && A.u2) {
846
+ // arc between the two lines, so the angle reads as belonging to that corner
847
+ const t1 = Math.atan2(-A.u1[1], A.u1[0]), t2 = Math.atan2(-A.u2[1], A.u2[0]);
848
+ let sweep = t2 - t1;
849
+ while (sweep > Math.PI)
850
+ sweep -= 2 * Math.PI;
851
+ while (sweep < -Math.PI)
852
+ sweep += 2 * Math.PI;
853
+ // (blue, as every other dimension is — the user's call, 2026-10-29; purple is the inference guides')
874
854
  c.save();
875
- if (ref)
876
- c.setLineDash([3, 3]);
877
- c.strokeStyle = bad ? TH.red : ref ? TH.chipline : TH.blue;
855
+ c.strokeStyle = lineColor;
878
856
  c.lineWidth = 1.2;
857
+ if (grey)
858
+ c.setLineDash([3, 3]);
879
859
  c.beginPath();
880
- c.moveTo(ax, ay);
881
- c.lineTo(bx, by);
860
+ c.arc(vx, vy, R, t1, t1 + sweep, sweep < 0);
882
861
  c.stroke();
862
+ // (pulled out past where a line ends: an extension line along it, out to the arc)
863
+ c.lineWidth = 1;
864
+ for (const [u, len] of [[A.u1, A.len1], [A.u2, A.len2]]) {
865
+ const from = (len ?? 0) * T.s;
866
+ if (moved && R > from + 2) {
867
+ c.beginPath();
868
+ c.moveTo(vx + u[0] * from, vy - u[1] * from);
869
+ c.lineTo(vx + u[0] * (R + 4), vy - u[1] * (R + 4));
870
+ c.stroke();
871
+ }
872
+ }
883
873
  c.restore();
884
874
  }
885
- chip(c, wrap(fmtLen(val)), mx, my, { bg, fg, border, kind: 'dim', id, mark, ...dimChip() });
875
+ const text = wrap(fmtAng(val));
876
+ // (left where it was drawn: 50 px out along the bisector, or as far as its corners stay clear of the corner — a
877
+ // linked chip is wider)
878
+ const sz = chipSize(c, text, opts), out = Math.max(50, Math.hypot(sz.w, sz.h) / 2 + 28);
879
+ const [lx, ly] = moved ? [vx + Math.cos(moved.ang) * R, vy - Math.sin(moved.ang) * R] : [vx + A.bis[0] * out, vy - A.bis[1] * out];
880
+ chip(c, text, lx, ly, opts);
881
+ return;
882
+ }
883
+ if (d.type === 'diam' && 'o' in A) {
884
+ drawDiameter(c, T, A.o, A.r, d, d.rad ? 'R ' + wrap(fmtLen(val / 2)) : 'Ø ' + wrap(fmtLen(val)), lineColor, opts, grey);
885
+ return;
886
886
  }
887
+ if (!('a' in A))
888
+ return;
889
+ const [mx, my] = S(T, A.m.x, A.m.y);
890
+ if ((d.type === 'dist' || d.type === 'pldist') && A.nx != null && Math.abs((d.off || 0) * T.s) > 10) {
891
+ drawDimLeaders(c, T, A.a, A.b, A.nx, A.ny, d.off, lineColor, grey);
892
+ }
893
+ else if (d.type === 'pldist' || !d.line) {
894
+ // (dashed only when it holds nothing: a dashed line means the number holds nothing, wherever it's drawn)
895
+ const [ax, ay] = S(T, A.a.x, A.a.y), [bx, by] = S(T, A.b.x, A.b.y);
896
+ c.save();
897
+ if (grey)
898
+ c.setLineDash([3, 3]);
899
+ c.strokeStyle = lineColor;
900
+ c.lineWidth = 1.2;
901
+ c.beginPath();
902
+ c.moveTo(ax, ay);
903
+ c.lineTo(bx, by);
904
+ c.stroke();
905
+ c.restore();
906
+ }
907
+ chip(c, wrap(fmtLen(val)), mx, my, opts);
887
908
  }
888
909
  /**
889
910
  * Door/window dimensions, on a line just outside the wall:
@@ -1306,9 +1327,10 @@ function badgeSpots(T, sk, c) {
1306
1327
  if (lid)
1307
1328
  onLine(lid, 0.22, -1);
1308
1329
  else {
1330
+ // two points no line joins: on the dashed line drawn between them (pointPairLink), halfway
1309
1331
  const a = P(c.a), b = P(c.b);
1310
1332
  const [x1, y1] = S(T, a.x, a.y), [x2, y2] = S(T, b.x, b.y);
1311
- out.push([(x1 + x2) / 2 + 10, (y1 + y2) / 2 - 10]);
1333
+ out.push([(x1 + x2) / 2, (y1 + y2) / 2]);
1312
1334
  }
1313
1335
  }
1314
1336
  else if (['par', 'perp', 'col', 'eq', 'angle'].includes(c.type)) {
@@ -1342,19 +1364,60 @@ function badgeSpots(T, sk, c) {
1342
1364
  }
1343
1365
  return out;
1344
1366
  }
1367
+ /**
1368
+ * Horizontal or Vertical between two points no line joins: the two points it ties, for drawing a dashed line between
1369
+ * them that its badge sits on (the user's call, 2026-11-05: the badge "is floating so u have no idea what it is related
1370
+ * to"). Null for any other constraint.
1371
+ */
1372
+ function pointPairLink(sk, k) {
1373
+ if ((k.type !== 'h' && k.type !== 'v') || lineBetween(sk, k.a, k.b))
1374
+ return null;
1375
+ const a = sk.points[k.a], b = sk.points[k.b];
1376
+ return a && b ? [a, b] : null;
1377
+ }
1345
1378
  function drawBadges(c, T, sk) {
1346
1379
  const { W, H } = cvs;
1347
1380
  const selIds = new Set(ed.sel.map(s => s.id)), selPts = selPoints(sk);
1381
+ const shown = [];
1348
1382
  for (const id in sk.cons) {
1349
1383
  const k = sk.cons[id];
1350
- if (isDim(k) || k.elev)
1351
- continue;
1352
- const g = GLYPH[k.type];
1353
- if (!g)
1384
+ if (isDim(k) || k.elev || !GLYPH[k.type])
1354
1385
  continue;
1355
1386
  const related = selIds.has(id) || [k.l, k.l1, k.l2, k.c, k.c1, k.c2].some(x => x && selIds.has(x)) || [k.a, k.b, k.p].some(x => x && selPts.has(x));
1356
1387
  if (!settings.showCons && !related && !ed.highlight.has(id))
1357
1388
  continue;
1389
+ shown.push(id);
1390
+ }
1391
+ // first the lines between the points a two-point Horizontal or Vertical ties: faint and dashed, yellow with a ring
1392
+ // on each point while it's selected, red when it can't be met — under every badge
1393
+ for (const id of shown) {
1394
+ const ends = pointPairLink(sk, sk.cons[id]);
1395
+ if (!ends)
1396
+ continue;
1397
+ const [x1, y1] = S(T, ends[0].x, ends[0].y), [x2, y2] = S(T, ends[1].x, ends[1].y);
1398
+ if (hyp(x2 - x1, y2 - y1) < 24)
1399
+ continue;
1400
+ const selected = isSel('cons', id), bad = ed.highlight.has(id) || ed.ana.bad.has(id);
1401
+ c.save();
1402
+ c.strokeStyle = bad ? TH.red : selected ? TH.yellow : TH.badgeline;
1403
+ c.lineWidth = selected || bad ? 1.5 : 1;
1404
+ c.setLineDash([4, 4]);
1405
+ c.beginPath();
1406
+ c.moveTo(x1, y1);
1407
+ c.lineTo(x2, y2);
1408
+ c.stroke();
1409
+ c.setLineDash([]);
1410
+ if (selected)
1411
+ for (const [x, y] of [[x1, y1], [x2, y2]]) {
1412
+ c.beginPath();
1413
+ c.arc(x, y, 8, 0, Math.PI * 2);
1414
+ c.lineWidth = 2.5;
1415
+ c.stroke();
1416
+ }
1417
+ c.restore();
1418
+ }
1419
+ for (const id of shown) {
1420
+ const k = sk.cons[id], g = GLYPH[k.type];
1358
1421
  for (const [x, y] of badgeSpots(T, sk, k)) {
1359
1422
  if (x < -20 || y < -20 || x > W + 20 || y > H + 20)
1360
1423
  continue;