@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.
- package/dist/editor/actions.js +16 -1
- package/dist/editor/actions.js.map +1 -1
- package/dist/editor/linkActions.d.ts +5 -1
- package/dist/editor/linkActions.js +27 -5
- package/dist/editor/linkActions.js.map +1 -1
- package/dist/editor/refDim.d.ts +17 -0
- package/dist/editor/refDim.js +62 -0
- package/dist/editor/refDim.js.map +1 -0
- package/dist/model/types.d.ts +5 -0
- package/dist/render/draw.d.ts +2 -1
- package/dist/render/draw.js +150 -87
- package/dist/render/draw.js.map +1 -1
- package/dist/tools/dim.d.ts +9 -0
- package/dist/tools/dim.js +143 -41
- package/dist/tools/dim.js.map +1 -1
- package/dist/tools/line.js +5 -0
- package/dist/tools/line.js.map +1 -1
- package/package.json +1 -1
- package/src/editor/actions.ts +14 -1
- package/src/editor/linkActions.ts +25 -6
- package/src/editor/refDim.ts +61 -0
- package/src/model/types.ts +5 -0
- package/src/render/draw.ts +116 -67
- package/src/tools/dim.ts +140 -31
- package/src/tools/line.ts +4 -0
package/dist/render/draw.js
CHANGED
|
@@ -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
|
|
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 (
|
|
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
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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;
|