@tscircuit/schematic-trace-solver 0.0.162 → 0.0.164
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/index.d.ts +2 -1
- package/dist/index.js +21 -6
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +21 -3
- package/lib/solvers/AvailableNetOrientationSolver/types.ts +1 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +15 -4
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +166 -166
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +26 -26
- package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +308 -0
- package/tests/repros/__snapshots__/repro-normal-net-label-branch-from-junction.snap.svg +93 -0
- package/tests/repros/__snapshots__/repro-pmp11282-endpoint-trace-through-component-text.snap.svg +76 -0
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +131 -113
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1681 -1690
- package/tests/repros/assets/repro-isolated-rs485-isow7841.input.json +1992 -0
- package/tests/repros/repro-isolated-rs485-isow7841.test.ts +21 -0
- package/tests/repros/repro-normal-net-label-branch-from-junction.test.ts +73 -0
- package/tests/repros/repro-pmp11282-endpoint-trace-through-component-text.test.ts +82 -0
- package/tests/repros/repro-pmp11282-isolated-dcdc.test.ts +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -825,7 +825,7 @@ type CandidateLabel = {
|
|
|
825
825
|
height: number;
|
|
826
826
|
};
|
|
827
827
|
type CandidateStatus$2 = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "trace-clearance-violation" | "netlabel-collision";
|
|
828
|
-
type CandidatePhase = "rotate" | "trace-anchor" | "outward-trace-anchor" | "shift" | "lateral-shift";
|
|
828
|
+
type CandidatePhase = "rotate" | "trace-anchor" | "connected-rail-shift" | "outward-trace-anchor" | "shift" | "lateral-shift";
|
|
829
829
|
type EvaluatedCandidate = CandidateLabel & {
|
|
830
830
|
status: CandidateStatus$2;
|
|
831
831
|
selected: boolean;
|
|
@@ -902,6 +902,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
902
902
|
private getNetLabelHeight;
|
|
903
903
|
private getCandidateStatus;
|
|
904
904
|
private isAcceptableTraceAnchorChipCollision;
|
|
905
|
+
private staysOutsideConnectedPinRow;
|
|
905
906
|
private getBoundsStatus;
|
|
906
907
|
private isTraceTooCloseToLabel;
|
|
907
908
|
private getLabelTraceClearanceBounds;
|
package/dist/index.js
CHANGED
|
@@ -1507,13 +1507,17 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
1507
1507
|
excludeRectsForSegment: (segIndex2) => {
|
|
1508
1508
|
const lastSegIndex2 = path.length - 2;
|
|
1509
1509
|
const excludedRects = /* @__PURE__ */ new Set();
|
|
1510
|
-
|
|
1510
|
+
const segmentStart = path[segIndex2];
|
|
1511
|
+
const segmentEnd = path[segIndex2 + 1];
|
|
1512
|
+
const oppositeTerminalPin = segIndex2 === 0 ? PB : segIndex2 === lastSegIndex2 ? PA : null;
|
|
1513
|
+
if (oppositeTerminalPin) {
|
|
1511
1514
|
for (const textObstacle of this.textObstacles) {
|
|
1512
|
-
|
|
1515
|
+
const textBounds = getTextBoxBounds(textObstacle.textBox);
|
|
1516
|
+
if (textObstacle.textBox.chipId !== oppositeTerminalPin.chipId || !segmentIntersectsRect(segmentStart, segmentEnd, textBounds)) {
|
|
1517
|
+
excludedRects.add(textObstacle);
|
|
1518
|
+
}
|
|
1513
1519
|
}
|
|
1514
1520
|
}
|
|
1515
|
-
const segmentStart = path[segIndex2];
|
|
1516
|
-
const segmentEnd = path[segIndex2 + 1];
|
|
1517
1521
|
const endpointEpsilon = 1e-9;
|
|
1518
1522
|
const segmentTouchesPin = (pin) => [segmentStart, segmentEnd].some(
|
|
1519
1523
|
(point) => Math.abs(point.x - pin.x) <= endpointEpsilon && Math.abs(point.y - pin.y) <= endpointEpsilon
|
|
@@ -8831,6 +8835,7 @@ function getPaddedBounds(bounds, padding) {
|
|
|
8831
8835
|
|
|
8832
8836
|
// lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts
|
|
8833
8837
|
var LABEL_TRACE_CLEARANCE2 = 0.1;
|
|
8838
|
+
var CONNECTED_PIN_ROW_OVERLAP_TOLERANCE = 0.1;
|
|
8834
8839
|
var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
8835
8840
|
inputProblem;
|
|
8836
8841
|
traces;
|
|
@@ -9852,7 +9857,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9852
9857
|
label
|
|
9853
9858
|
});
|
|
9854
9859
|
if (boundsStatus !== "valid") {
|
|
9855
|
-
if (phase !== "trace-anchor" || boundsStatus !== "chip-collision" || !this.isAcceptableTraceAnchorChipCollision(candidate, label, bounds)) {
|
|
9860
|
+
if (phase !== "trace-anchor" && phase !== "connected-rail-shift" || boundsStatus !== "chip-collision" || !this.isAcceptableTraceAnchorChipCollision(candidate, label, bounds) || phase === "connected-rail-shift" && !this.staysOutsideConnectedPinRow({ candidate, label, bounds })) {
|
|
9856
9861
|
return boundsStatus;
|
|
9857
9862
|
}
|
|
9858
9863
|
const nonChipBoundsStatus = this.getBoundsStatus({
|
|
@@ -9906,6 +9911,15 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9906
9911
|
return anchorPoint.y < chipBounds.minY - EPS12 || anchorPoint.y > chipBounds.maxY + EPS12;
|
|
9907
9912
|
});
|
|
9908
9913
|
}
|
|
9914
|
+
staysOutsideConnectedPinRow(params) {
|
|
9915
|
+
const { candidate, label, bounds } = params;
|
|
9916
|
+
const pin = this.pinMap[label.pinIds[0]];
|
|
9917
|
+
if (!pin || !isYOrientation(candidate.orientation)) return false;
|
|
9918
|
+
if (candidate.anchorPoint.x < pin.x) {
|
|
9919
|
+
return bounds.maxX <= pin.x + CONNECTED_PIN_ROW_OVERLAP_TOLERANCE + EPS12;
|
|
9920
|
+
}
|
|
9921
|
+
return bounds.minX >= pin.x - CONNECTED_PIN_ROW_OVERLAP_TOLERANCE - EPS12;
|
|
9922
|
+
}
|
|
9909
9923
|
getBoundsStatus(candidateBoundsCheck) {
|
|
9910
9924
|
const { bounds, labelIndex, label, ignoreChipCollisions } = candidateBoundsCheck;
|
|
9911
9925
|
if (!ignoreChipCollisions) {
|
|
@@ -10060,8 +10074,9 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
10060
10074
|
orientation,
|
|
10061
10075
|
direction,
|
|
10062
10076
|
baseAnchor: connectorSource,
|
|
10063
|
-
maxSearchDistance: this.
|
|
10077
|
+
maxSearchDistance: this.getSearchDistanceLimit(label, orientation),
|
|
10064
10078
|
outwardDistance: 0,
|
|
10079
|
+
phase: "connected-rail-shift",
|
|
10065
10080
|
stopOnTraceCollision: false,
|
|
10066
10081
|
connectorSource,
|
|
10067
10082
|
startDistance: 0
|
|
@@ -51,6 +51,7 @@ import { visualizeAvailableNetOrientationSolver } from "./visualize"
|
|
|
51
51
|
import { AvailableNetOrientationObstacleIndex } from "./AvailableNetOrientationObstacleIndex"
|
|
52
52
|
|
|
53
53
|
const LABEL_TRACE_CLEARANCE = 0.1
|
|
54
|
+
const CONNECTED_PIN_ROW_OVERLAP_TOLERANCE = 0.1
|
|
54
55
|
|
|
55
56
|
export class AvailableNetOrientationSolver extends BaseSolver {
|
|
56
57
|
inputProblem: InputProblem
|
|
@@ -1443,9 +1444,11 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1443
1444
|
})
|
|
1444
1445
|
if (boundsStatus !== "valid") {
|
|
1445
1446
|
if (
|
|
1446
|
-
phase !== "trace-anchor" ||
|
|
1447
|
+
(phase !== "trace-anchor" && phase !== "connected-rail-shift") ||
|
|
1447
1448
|
boundsStatus !== "chip-collision" ||
|
|
1448
|
-
!this.isAcceptableTraceAnchorChipCollision(candidate, label, bounds)
|
|
1449
|
+
!this.isAcceptableTraceAnchorChipCollision(candidate, label, bounds) ||
|
|
1450
|
+
(phase === "connected-rail-shift" &&
|
|
1451
|
+
!this.staysOutsideConnectedPinRow({ candidate, label, bounds }))
|
|
1449
1452
|
) {
|
|
1450
1453
|
return boundsStatus
|
|
1451
1454
|
}
|
|
@@ -1525,6 +1528,20 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1525
1528
|
})
|
|
1526
1529
|
}
|
|
1527
1530
|
|
|
1531
|
+
private staysOutsideConnectedPinRow(params: {
|
|
1532
|
+
candidate: CandidateLabel
|
|
1533
|
+
label: NetLabelPlacement
|
|
1534
|
+
bounds: Bounds
|
|
1535
|
+
}) {
|
|
1536
|
+
const { candidate, label, bounds } = params
|
|
1537
|
+
const pin = this.pinMap[label.pinIds[0]!]
|
|
1538
|
+
if (!pin || !isYOrientation(candidate.orientation)) return false
|
|
1539
|
+
if (candidate.anchorPoint.x < pin.x) {
|
|
1540
|
+
return bounds.maxX <= pin.x + CONNECTED_PIN_ROW_OVERLAP_TOLERANCE + EPS
|
|
1541
|
+
}
|
|
1542
|
+
return bounds.minX >= pin.x - CONNECTED_PIN_ROW_OVERLAP_TOLERANCE - EPS
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1528
1545
|
private getBoundsStatus(candidateBoundsCheck: {
|
|
1529
1546
|
bounds: Bounds
|
|
1530
1547
|
labelIndex: number
|
|
@@ -1756,8 +1773,9 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1756
1773
|
orientation,
|
|
1757
1774
|
direction,
|
|
1758
1775
|
baseAnchor: connectorSource,
|
|
1759
|
-
maxSearchDistance: this.
|
|
1776
|
+
maxSearchDistance: this.getSearchDistanceLimit(label, orientation),
|
|
1760
1777
|
outwardDistance: 0,
|
|
1778
|
+
phase: "connected-rail-shift",
|
|
1761
1779
|
stopOnTraceCollision: false,
|
|
1762
1780
|
connectorSource,
|
|
1763
1781
|
startDistance: 0,
|
|
@@ -325,15 +325,26 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
325
325
|
excludeRectsForSegment: (segIndex) => {
|
|
326
326
|
const lastSegIndex = path.length - 2
|
|
327
327
|
const excludedRects = new Set<ObstacleRect>()
|
|
328
|
+
const segmentStart = path[segIndex]!
|
|
329
|
+
const segmentEnd = path[segIndex + 1]!
|
|
328
330
|
|
|
329
|
-
|
|
331
|
+
const oppositeTerminalPin =
|
|
332
|
+
segIndex === 0 ? PB : segIndex === lastSegIndex ? PA : null
|
|
333
|
+
if (oppositeTerminalPin) {
|
|
330
334
|
for (const textObstacle of this.textObstacles) {
|
|
331
|
-
|
|
335
|
+
// Preserve the existing terminal-segment clearance exception, but
|
|
336
|
+
// do not let a segment that wraps around the opposite endpoint cut
|
|
337
|
+
// through that component's actual text.
|
|
338
|
+
const textBounds = getTextBoxBounds(textObstacle.textBox)
|
|
339
|
+
if (
|
|
340
|
+
textObstacle.textBox.chipId !== oppositeTerminalPin.chipId ||
|
|
341
|
+
!segmentIntersectsRect(segmentStart, segmentEnd, textBounds)
|
|
342
|
+
) {
|
|
343
|
+
excludedRects.add(textObstacle)
|
|
344
|
+
}
|
|
332
345
|
}
|
|
333
346
|
}
|
|
334
347
|
|
|
335
|
-
const segmentStart = path[segIndex]!
|
|
336
|
-
const segmentEnd = path[segIndex + 1]!
|
|
337
348
|
const endpointEpsilon = 1e-9
|
|
338
349
|
const segmentTouchesPin = (pin: MspConnectionPair["pins"][number]) =>
|
|
339
350
|
[segmentStart, segmentEnd].some(
|