@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/src/tools/dim.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { addOpeningDim } from './openings';
|
|
|
24
24
|
import type { PEv } from './registry';
|
|
25
25
|
import { keypadFresh, keypadOpen, keypadSetReading } from '../editor/keypad';
|
|
26
26
|
import { advanceAfterReading } from '../editor/advance';
|
|
27
|
-
import { cornerLines } from '../editor/selection';
|
|
27
|
+
import { cornerLines, edgePair, selInfo } from '../editor/selection';
|
|
28
28
|
import { closeKeypad, kpTyped, openKeypad, showConflict, toast, updateCtx, updateHint, type KeypadSpec } from '../editor/ports';
|
|
29
29
|
import { noScaleYet, pendingLabel, startingValue } from '../editor/scaleGate';
|
|
30
30
|
|
|
@@ -165,7 +165,7 @@ export function dimLine(lid: string, side?: 1 | -1): void {
|
|
|
165
165
|
const wall = faced(sk, lid);
|
|
166
166
|
const ex = lengthDimOf(sk, lid);
|
|
167
167
|
if (ex) { editDim(ex, undefined, false, wall && side ? { face: side } : undefined); return; }
|
|
168
|
-
let face: 0 | 1 | -1 = wall ?
|
|
168
|
+
let face: 0 | 1 | -1 = wall ? firstFace(sk, lid, side) : 0;
|
|
169
169
|
const len = () => (face ? faceLength(sk, lid, face) : lineLen(sk, l));
|
|
170
170
|
// (while it's being entered, what it measures is what's highlighted: the edge, or the wall)
|
|
171
171
|
const was = ed.sel;
|
|
@@ -196,7 +196,7 @@ export function dimLine(lid: string, side?: 1 | -1): void {
|
|
|
196
196
|
/** The line's length dimension, driving, measured where the keypad says (`extra`: what it's from). Whether it took. */
|
|
197
197
|
function addLength(v: number, extra: Partial<Constraint>): boolean {
|
|
198
198
|
pushHistory();
|
|
199
|
-
const r = tryAddConstraint({
|
|
199
|
+
const r = tryAddConstraint({ ...lengthDim(sk, lid, face), value: v, ...extra });
|
|
200
200
|
// a measurement of the job drives a line, or nothing: not one whose length the drawing already sets
|
|
201
201
|
let took = r.ok;
|
|
202
202
|
if (r.ok && r.ref && extra.measurementRef) {
|
|
@@ -383,9 +383,102 @@ export function dimEdgePoint(lid: string, side: 1 | -1, p: string): void {
|
|
|
383
383
|
const sk = SK(), l = sk.lines[lid];
|
|
384
384
|
// (one of the wall's own ends is no distance from it)
|
|
385
385
|
if (!l || p === l.a || p === l.b) { toast('Pick a point off the wall to measure from its edge.', { warn: true }); return; }
|
|
386
|
-
dimPointLine(p, lid, { faces:
|
|
386
|
+
dimPointLine(p, lid, { faces: edgePointFaces(sk, lid, side, p), title: 'Distance from the edge' });
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
/** From a wall's edge to a point: measured from that face of the wall. */
|
|
390
|
+
const edgePointFaces = (sk: Sketch, lid: string, side: 1 | -1, p: string): [number, number] => [faceToward(sk, lid, sk.points[p], side), 0];
|
|
391
|
+
|
|
392
|
+
/* What the + measures, as a dimension not made yet: the builders above make these, and the grey number shows them. */
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The face a wall's length (a curved wall's diameter) is first measured on: `side`, the edge picked; picked whole, the
|
|
396
|
+
* side a space is on (the side it was tapped on, between two); its centreline with no space beside it.
|
|
397
|
+
*/
|
|
398
|
+
const firstFace = (sk: Sketch, id: string, side?: 1 | -1): 0 | 1 | -1 =>
|
|
399
|
+
side ?? defaultFace(sk, id, ed.wallTap?.id === id ? ed.wallTap.side : undefined) ?? 0;
|
|
400
|
+
|
|
401
|
+
/** Line `lid`'s length, measured on `face` (a wall's; 0 along the line). Its label sits off the side it's measured on. */
|
|
402
|
+
function lengthDim(sk: Sketch, lid: string, face: 0 | 1 | -1): Constraint {
|
|
403
|
+
const l = sk.lines[lid];
|
|
404
|
+
return { type: 'dist', a: l.a, b: l.b, kind: 'al', line: lid, driving: true, off: face ? (face * 30) / V().s : 0, ...(face ? { face } : {}) };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** A circle's or arc's diameter, across `face` (a curved wall's; 0 its centreline). */
|
|
408
|
+
const diamDim = (id: string, face: 0 | 1 | -1): Constraint => ({ type: 'diam', c: id, driving: true, ...(face ? { face } : {}) });
|
|
409
|
+
|
|
410
|
+
/** A circle's or arc's diameter dimension, if it has one. */
|
|
411
|
+
const diamDimOf = (sk: Sketch, id: string): string | undefined => Object.keys(sk.cons).find(k => sk.cons[k].type === 'diam' && sk.cons[k].c === id);
|
|
412
|
+
|
|
413
|
+
/** The angle between two lines. */
|
|
414
|
+
const angleDim = (l1: string, l2: string): Constraint => ({ type: 'angle', l1, l2, value: 0, driving: true });
|
|
415
|
+
|
|
416
|
+
/** The straight distance between two points. */
|
|
417
|
+
const pointsDim = (a: string, b: string): Constraint => ({ type: 'dist', a, b, kind: 'al', driving: true, off: 0 });
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Point `p`'s distance from line `lid`, square to it, on its side of it. On a wall, from `faces[0]` (see dimPointLine) —
|
|
421
|
+
* by default the face toward the point where a space is against it; `l2`, the wall the point is the end of.
|
|
422
|
+
*/
|
|
423
|
+
function pointLineDim(sk: Sketch, p: string, lid: string, opts: { faces?: [number, number]; l2?: string } = {}): Constraint {
|
|
424
|
+
const l = sk.lines[lid], P = sk.points[p], A = sk.points[l.a], B = sk.points[l.b];
|
|
425
|
+
const sd = ((B.x - A.x) * (P.y - A.y) - (B.y - A.y) * (P.x - A.x)) / (hyp(B.x - A.x, B.y - A.y) || 1);
|
|
426
|
+
const c: Constraint = { type: 'pldist', p, l: lid, value: 0, sign: sd < 0 ? -1 : 1, driving: true, ...(opts.l2 ? { l2: opts.l2 } : {}) };
|
|
427
|
+
applyFace(c, { faces: opts.faces ?? [faceToward(sk, lid, P), 0] });
|
|
428
|
+
return c;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* What the yellow + measures for the selection, as the dimension it would make (`c`, its value unset), with the one
|
|
433
|
+
* already there for it (`existing`), which the + edits instead. Null where it makes no dimension of the plan: nothing it
|
|
434
|
+
* measures, a door's or window's sizes (their own dimensions), a wall's thickness, the wall view (wallPlus).
|
|
435
|
+
*/
|
|
436
|
+
export function selectionDim(): { c: Constraint; existing?: string } | null {
|
|
437
|
+
const sk = SK(), s = selInfo();
|
|
438
|
+
if (s.dims.length || s.odims.length || s.openings.length || s.oanchors.length) return null;
|
|
439
|
+
if (s.wfaces.length === 1 && s.n === 1) {
|
|
440
|
+
const [id, side] = edgeOf(s.wfaces[0]);
|
|
441
|
+
if (sk.lines[id]) return { c: lengthDim(sk, id, side), existing: lengthDimOf(sk, id) ?? undefined };
|
|
442
|
+
return { c: diamDim(id, side), existing: diamDimOf(sk, id) };
|
|
443
|
+
}
|
|
444
|
+
if (edgePair(s)) {
|
|
445
|
+
const edges = s.wfaces.map(edgeOf), other = ed.sel.find(x => x.type !== 'wface');
|
|
446
|
+
if (edges.length === 2) {
|
|
447
|
+
const [[a, sa], [b, sb]] = edges;
|
|
448
|
+
return a !== b && sk.lines[a] && sk.lines[b] ? twoLinesDim(sk, a, b, { [a]: sa, [b]: sb }) : null;
|
|
449
|
+
}
|
|
450
|
+
const [lid, side] = edges[0], l = sk.lines[lid];
|
|
451
|
+
if (other?.type === 'line') return twoLinesDim(sk, lid, other.id, { [lid]: side });
|
|
452
|
+
if (other?.type !== 'point' || !l || other.id === l.a || other.id === l.b) return null;
|
|
453
|
+
return { c: pointLineDim(sk, other.id, lid, { faces: edgePointFaces(sk, lid, side, other.id) }), existing: pointLineDimOf(sk, other.id, lid) };
|
|
454
|
+
}
|
|
455
|
+
if (s.lines.length === 1 && s.n === 1) {
|
|
456
|
+
const lid = s.lines[0];
|
|
457
|
+
return { c: lengthDim(sk, lid, faced(sk, lid) ? firstFace(sk, lid) : 0), existing: lengthDimOf(sk, lid) ?? undefined };
|
|
458
|
+
}
|
|
459
|
+
if (s.curves.length === 1 && s.n === 1) {
|
|
460
|
+
const id = s.curves[0];
|
|
461
|
+
return { c: diamDim(id, wallOf(sk, id) ? firstFace(sk, id) : 0), existing: diamDimOf(sk, id) };
|
|
462
|
+
}
|
|
463
|
+
if (s.points.length === 1 && s.n === 1) return cornerDim(sk, s.points[0]);
|
|
464
|
+
if (s.n === 2 && s.lines.length === 2) return twoLinesDim(sk, s.lines[0], s.lines[1]);
|
|
465
|
+
if (s.n === 2 && s.points.length === 2) {
|
|
466
|
+
const [a, b] = s.points;
|
|
467
|
+
const existing = Object.keys(sk.cons).find(id => { const c = sk.cons[id]; return c.type === 'dist' && !c.line && ((c.a === a && c.b === b) || (c.a === b && c.b === a)); });
|
|
468
|
+
return { c: pointsDim(a, b), existing };
|
|
469
|
+
}
|
|
470
|
+
if (s.n === 2 && s.points.length === 1 && s.lines.length === 1) {
|
|
471
|
+
const [p] = s.points, [lid] = s.lines, l = sk.lines[lid];
|
|
472
|
+
// (one of the line's own ends is no distance from it)
|
|
473
|
+
if (!l || p === l.a || p === l.b) return null;
|
|
474
|
+
return { c: pointLineDim(sk, p, lid), existing: pointLineDimOf(sk, p, lid) };
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** A distance from point `p` to line `lid` already in the drawing. */
|
|
480
|
+
const pointLineDimOf = (sk: Sketch, p: string, lid: string): string | undefined => Object.keys(sk.cons).find(id => sk.cons[id].type === 'pldist' && sk.cons[id].p === p && sk.cons[id].l === lid);
|
|
481
|
+
|
|
389
482
|
/**
|
|
390
483
|
* Two things picked, at least one a wall's edge: the distance between them — two edges of one wall, its thickness
|
|
391
484
|
* (`thickness` opens it); of two walls, face to face; an edge and a line, from that face; an edge and a point, from it.
|
|
@@ -417,7 +510,7 @@ export function dimPoints(a: string, b: string): void {
|
|
|
417
510
|
title: 'Distance', ...startingValue(L), selectAll: true, laser: true,
|
|
418
511
|
onDone: (v, capture) => {
|
|
419
512
|
pushHistory();
|
|
420
|
-
const r = tryAddConstraint({
|
|
513
|
+
const r = tryAddConstraint({ ...pointsDim(a, b), value: v, ...(capture !== undefined ? { capture } : {}) });
|
|
421
514
|
if (!r.ok) dropHistory();
|
|
422
515
|
ed.sel = [];
|
|
423
516
|
afterEdit();
|
|
@@ -432,8 +525,28 @@ export function dimPoints(a: string, b: string): void {
|
|
|
432
525
|
* or, picked whole, each wall's room side (see faceToward).
|
|
433
526
|
*/
|
|
434
527
|
export function dimTwoLines(l1: string, l2: string, sides: Record<string, 0 | 1 | -1> = {}): void {
|
|
435
|
-
const sk = SK(),
|
|
436
|
-
if (!
|
|
528
|
+
const sk = SK(), d = twoLinesDim(sk, l1, l2, sides);
|
|
529
|
+
if (!d) return;
|
|
530
|
+
if (d.existing) {
|
|
531
|
+
const c = sk.cons[d.existing];
|
|
532
|
+
// (the edges picked: it's measured between them now)
|
|
533
|
+
editDim(d.existing, undefined, false, c.type === 'pldist' && Object.keys(sides).length ? d.facesFrom(c.l!, c.p!) : undefined);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (d.c.type === 'angle') { dimAngle(l1, l2); return; }
|
|
537
|
+
const f = d.facesFrom(l1, d.c.p!);
|
|
538
|
+
dimPointLine(d.c.p!, l1, { keepParallel: l2, title: 'Distance between lines', faces: f.faces, l2: f.l2 });
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* What the + measures between two lines: their distance if they're parallel — square to the first, from the end of the
|
|
543
|
+
* second that sits most squarely across from it, between the faces `sides` says (see dimTwoLines) — otherwise the angle
|
|
544
|
+
* between them. `existing`: a dimension already between them; `facesFrom`: the faces between line `l` and the other,
|
|
545
|
+
* from point `p` on the other.
|
|
546
|
+
*/
|
|
547
|
+
function twoLinesDim(sk: Sketch, l1: string, l2: string, sides: Record<string, 0 | 1 | -1> = {}): { c: Constraint; existing?: string; facesFrom: (l: string, p: string) => FaceChoice } | null {
|
|
548
|
+
const L1 = sk.lines[l1], L2 = sk.lines[l2];
|
|
549
|
+
if (!L1 || !L2) return null;
|
|
437
550
|
const [ax, ay] = norm2(sk.points[L1.b].x - sk.points[L1.a].x, sk.points[L1.b].y - sk.points[L1.a].y);
|
|
438
551
|
const [bx, by] = norm2(sk.points[L2.b].x - sk.points[L2.a].x, sk.points[L2.b].y - sk.points[L2.a].y);
|
|
439
552
|
const parallel = Math.abs(ax * by - ay * bx) < 0.02; // within about a degree
|
|
@@ -444,24 +557,16 @@ export function dimTwoLines(l1: string, l2: string, sides: Record<string, 0 | 1
|
|
|
444
557
|
if (kind === 'angle') return (c.l1 === l1 && c.l2 === l2) || (c.l1 === l2 && c.l2 === l1);
|
|
445
558
|
return (c.l === l1 && (c.p === L2.a || c.p === L2.b)) || (c.l === l2 && (c.p === L1.a || c.p === L1.b));
|
|
446
559
|
});
|
|
447
|
-
/** The faces between line `l` (the one measured square to) and the other, from point `p` on the other. */
|
|
448
560
|
const facesFrom = (l: string, p: string): FaceChoice => {
|
|
449
561
|
const o = l === l1 ? l2 : l1, ol = sk.lines[o], on = sk.lines[l];
|
|
450
562
|
return { faces: [faceToward(sk, l, sk.points[p], sides[l], true), faceToward(sk, o, sk.points[on.a], sides[o], true)], ...(faced(sk, o) && ol ? { l2: o } : {}) };
|
|
451
563
|
};
|
|
452
|
-
if (
|
|
453
|
-
const c = sk.cons[existing];
|
|
454
|
-
// (the edges picked: it's measured between them now)
|
|
455
|
-
editDim(existing, undefined, false, c.type === 'pldist' && Object.keys(sides).length ? facesFrom(c.l!, c.p!) : undefined);
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
if (!parallel) { dimAngle(l1, l2); return; }
|
|
459
|
-
// measure from the end of the second line that sits most squarely across from the first
|
|
564
|
+
if (!parallel) return { c: angleDim(l1, l2), existing, facesFrom };
|
|
460
565
|
const A = sk.points[L1.a], B = sk.points[L1.b], len2 = (B.x - A.x) ** 2 + (B.y - A.y) ** 2 || 1;
|
|
461
566
|
const t = (pid: string) => { const P = sk.points[pid]; return ((P.x - A.x) * (B.x - A.x) + (P.y - A.y) * (B.y - A.y)) / len2; };
|
|
462
567
|
const p = Math.abs(t(L2.a) - 0.5) <= Math.abs(t(L2.b) - 0.5) ? L2.a : L2.b;
|
|
463
568
|
const f = facesFrom(l1, p);
|
|
464
|
-
|
|
569
|
+
return { c: pointLineDim(sk, p, l1, { faces: f.faces, l2: f.l2 }), existing, facesFrom };
|
|
465
570
|
}
|
|
466
571
|
|
|
467
572
|
/**
|
|
@@ -471,11 +576,7 @@ export function dimTwoLines(l1: string, l2: string, sides: Record<string, 0 | 1
|
|
|
471
576
|
* switch them.
|
|
472
577
|
*/
|
|
473
578
|
export function dimPointLine(p: string, lid: string, opts: { keepParallel?: string; title?: string; faces?: [number, number]; l2?: string } = {}): void {
|
|
474
|
-
const sk = SK(),
|
|
475
|
-
const sd = ((B.x - A.x) * (P.y - A.y) - (B.y - A.y) * (P.x - A.x)) / (hyp(B.x - A.x, B.y - A.y) || 1);
|
|
476
|
-
const sign = sd < 0 ? -1 : 1;
|
|
477
|
-
const c: Constraint = { type: 'pldist', p, l: lid, value: 0, sign, driving: true, ...(opts.l2 ? { l2: opts.l2 } : {}) };
|
|
478
|
-
applyFace(c, { faces: opts.faces ?? [faceToward(sk, lid, P), 0] });
|
|
579
|
+
const sk = SK(), c = pointLineDim(sk, p, lid, opts);
|
|
479
580
|
const choices = faceChoices(sk, c);
|
|
480
581
|
ed.sel = [];
|
|
481
582
|
ed.preview = (cx, T) => { const e = dimEnds(sk, c); if (e) drawPendingDim(cx, T, { from: e.a, to: e.b, a: e.a, b: e.b, nx: 0, ny: 0, off: 0, label: pendingLabel(kpTyped(), measure(sk, c)) }); };
|
|
@@ -508,9 +609,10 @@ export function dimPointLine(p: string, lid: string, opts: { keepParallel?: stri
|
|
|
508
609
|
*/
|
|
509
610
|
export function dimCurve(id: string, side?: 1 | -1): void {
|
|
510
611
|
const sk = SK(), wall = !!wallOf(sk, id);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
612
|
+
const ex = diamDimOf(sk, id);
|
|
613
|
+
if (ex) { editDim(ex, undefined, false, wall && side ? { face: side } : undefined); return; }
|
|
614
|
+
let face: 0 | 1 | -1 = wall ? firstFace(sk, id, side) : 0;
|
|
615
|
+
const now = () => measure(sk, diamDim(id, face));
|
|
514
616
|
// (while it's being entered, what it measures across is what's highlighted: the edge, or the wall)
|
|
515
617
|
const was = ed.sel, type = sk.circles[id] ? 'circle' as const : 'arc' as const;
|
|
516
618
|
const show = () => { ed.sel = face ? [{ type: 'wface', id: edgeId(id, face) }] : [{ type, id }]; requestDraw(); };
|
|
@@ -525,7 +627,7 @@ export function dimCurve(id: string, side?: 1 | -1): void {
|
|
|
525
627
|
onDone: v => {
|
|
526
628
|
if (v <= 0) return;
|
|
527
629
|
pushHistory();
|
|
528
|
-
const r = tryAddConstraint({
|
|
630
|
+
const r = tryAddConstraint({ ...diamDim(id, face), value: v });
|
|
529
631
|
if (!r.ok) dropHistory();
|
|
530
632
|
// (as a length: the dimension made, nothing left highlighted)
|
|
531
633
|
ed.sel = [];
|
|
@@ -537,16 +639,23 @@ export function dimCurve(id: string, side?: 1 | -1): void {
|
|
|
537
639
|
|
|
538
640
|
/** A corner's angle (two lines meeting at `pid`): its angle dimension's keypad, or a new one's. False if it isn't a corner. */
|
|
539
641
|
export function cornerAngle(pid: string, done?: () => void, modeless = false): boolean {
|
|
540
|
-
const sk = SK(),
|
|
541
|
-
if (!
|
|
642
|
+
const sk = SK(), d = cornerDim(sk, pid);
|
|
643
|
+
if (!d) return false;
|
|
542
644
|
// edit the corner's existing angle dimension rather than stacking a second one
|
|
543
|
-
|
|
544
|
-
if (ex) editDim(ex, done, modeless); else dimAngle(pair[0], pair[1], done, modeless);
|
|
645
|
+
if (d.existing) editDim(d.existing, done, modeless); else dimAngle(d.c.l1!, d.c.l2!, done, modeless);
|
|
545
646
|
return true;
|
|
546
647
|
}
|
|
547
648
|
|
|
649
|
+
/** A corner's angle (two lines meeting at `pid`), as the + measures it; `existing`, its angle dimension. Null if it isn't a corner. */
|
|
650
|
+
function cornerDim(sk: Sketch, pid: string): { c: Constraint; existing?: string } | null {
|
|
651
|
+
const pair = cornerLines(sk, pid);
|
|
652
|
+
if (!pair) return null;
|
|
653
|
+
const existing = Object.keys(sk.cons).find(id => { const c = sk.cons[id]; return c.type === 'angle' && ((c.l1 === pair[0] && c.l2 === pair[1]) || (c.l1 === pair[1] && c.l2 === pair[0])); });
|
|
654
|
+
return { c: angleDim(pair[0], pair[1]), existing };
|
|
655
|
+
}
|
|
656
|
+
|
|
548
657
|
export function dimAngle(l1: string, l2: string, done?: () => void, modeless = false): void {
|
|
549
|
-
const c
|
|
658
|
+
const c = angleDim(l1, l2);
|
|
550
659
|
const cur = measure(SK(), c);
|
|
551
660
|
/** The angle dimension, driving, at `v` degrees (`extra`: what it's from). */
|
|
552
661
|
const addAngle = (v: number, extra: Partial<Constraint>) => {
|
package/src/tools/line.ts
CHANGED
|
@@ -128,6 +128,10 @@ export function makeLineTool(): LineTool {
|
|
|
128
128
|
splitWallsAtEnds(SK(), lid);
|
|
129
129
|
if (settings.autoCons) {
|
|
130
130
|
for (const g of s.guides || []) {
|
|
131
|
+
// level or plumb from the line's own start: the line itself is. Lined up with any other point ("Aligned"),
|
|
132
|
+
// only placed there — never tied to it (the user's call, 2026-11-05: a V between two unconnected points
|
|
133
|
+
// "just auto made" sat in space between them, and nothing said what it tied)
|
|
134
|
+
if ((g.kind === 'H' || g.kind === 'V') && g.from !== st.start) continue;
|
|
131
135
|
if (g.kind === 'H') tryAddConstraint({ type: 'h', a: g.from, b: b.id }, { silent: true });
|
|
132
136
|
else if (g.kind === 'V') tryAddConstraint({ type: 'v', a: g.from, b: b.id }, { silent: true });
|
|
133
137
|
else if (g.kind === 'perp' && st.prev) tryAddConstraint({ type: 'perp', l1: st.prev, l2: lid }, { silent: true });
|