@tscircuit/schematic-trace-solver 0.0.185 → 0.0.186
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 +10 -0
- package/dist/index.js +94 -8
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +129 -10
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg +59 -71
- package/tests/bug-reports/bug-report-20260902T092425Z/__snapshots__/bug-report-20260902T092425Z.snap.svg +28 -28
- package/tests/bug-reports/bug-report-20260902T092425Z/bug-report-20260902T092425Z.test.ts +39 -0
- package/tests/repros/__snapshots__/repro-trellis-core-gnd-net-label-overlap.snap.svg +59 -71
- package/tests/repros/repro-trellis-core-gnd-net-label-overlap.test.ts +58 -13
package/dist/index.d.ts
CHANGED
|
@@ -936,6 +936,16 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
936
936
|
private hasOppositeVerticalRailOnSameChipSide;
|
|
937
937
|
private getSingleChipSideForNetConnection;
|
|
938
938
|
private isPortOnlyLabel;
|
|
939
|
+
private getNetConnectionForLabel;
|
|
940
|
+
private isGroundLabel;
|
|
941
|
+
/**
|
|
942
|
+
* A vertical-only input stores its text extent in netLabelHeight. If its
|
|
943
|
+
* initial port-only placement is horizontal, use that extent for collision
|
|
944
|
+
* checks without changing the placement dimensions returned by the solver.
|
|
945
|
+
*/
|
|
946
|
+
private getHorizontalConstraintMismatchBounds;
|
|
947
|
+
private includeHorizontalConstraintMismatches;
|
|
948
|
+
private getLabelBoundsForCollisionWithGround;
|
|
939
949
|
private shouldIgnorePendingCrowdedLabel;
|
|
940
950
|
private intersectsAnyOtherNetLabel;
|
|
941
951
|
private sharesChipBoundary;
|
package/dist/index.js
CHANGED
|
@@ -9413,6 +9413,17 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9413
9413
|
if (this.getTextBlockedMultiPinRailTraces(label, orientations[0])) {
|
|
9414
9414
|
return true;
|
|
9415
9415
|
}
|
|
9416
|
+
const hasSingleVerticalOrientation = orientations.length === 1 && isYOrientation(orientations[0]);
|
|
9417
|
+
if (hasSingleVerticalOrientation && this.isGroundLabel(label)) {
|
|
9418
|
+
const bounds2 = getRectBounds(label.center, label.width, label.height);
|
|
9419
|
+
const overlapsHorizontalConstraintMismatch = this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
|
|
9420
|
+
if (otherIndex === labelIndex) return false;
|
|
9421
|
+
if (otherLabel.globalConnNetId === label.globalConnNetId) return false;
|
|
9422
|
+
const otherBounds = this.getHorizontalConstraintMismatchBounds(otherLabel);
|
|
9423
|
+
return otherBounds ? rectsOverlap2(bounds2, otherBounds) : false;
|
|
9424
|
+
});
|
|
9425
|
+
if (overlapsHorizontalConstraintMismatch) return true;
|
|
9426
|
+
}
|
|
9416
9427
|
if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false;
|
|
9417
9428
|
const bounds = getRectBounds(label.center, label.width, label.height);
|
|
9418
9429
|
return this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
|
|
@@ -10386,15 +10397,17 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
10386
10397
|
return "text-collision";
|
|
10387
10398
|
}
|
|
10388
10399
|
}
|
|
10389
|
-
|
|
10390
|
-
connectorTrace
|
|
10391
|
-
|
|
10400
|
+
const nearbyLabelIndices = this.includeHorizontalConstraintMismatches(
|
|
10401
|
+
this.obstacleIndex.getLabelIndicesNearTracePath(connectorTrace),
|
|
10402
|
+
this.isGroundLabel(label)
|
|
10403
|
+
);
|
|
10404
|
+
for (const i of nearbyLabelIndices) {
|
|
10392
10405
|
if (i === labelIndex) continue;
|
|
10393
10406
|
if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
|
|
10394
10407
|
const otherLabel = this.outputNetLabelPlacements[i];
|
|
10395
10408
|
if (tracePathIntersectsBounds(
|
|
10396
10409
|
connectorTrace,
|
|
10397
|
-
|
|
10410
|
+
this.getLabelBoundsForCollisionWithGround(otherLabel, label)
|
|
10398
10411
|
)) {
|
|
10399
10412
|
return "netlabel-collision";
|
|
10400
10413
|
}
|
|
@@ -10445,7 +10458,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
10445
10458
|
if (this.isTraceTooCloseToLabel(bounds, label)) {
|
|
10446
10459
|
return "trace-clearance-violation";
|
|
10447
10460
|
}
|
|
10448
|
-
if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
|
|
10461
|
+
if (this.intersectsAnyOtherNetLabel(bounds, labelIndex, label)) {
|
|
10449
10462
|
return "netlabel-collision";
|
|
10450
10463
|
}
|
|
10451
10464
|
return "valid";
|
|
@@ -10645,22 +10658,95 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
10645
10658
|
isPortOnlyLabel(label) {
|
|
10646
10659
|
return label.mspConnectionPairIds.length === 0;
|
|
10647
10660
|
}
|
|
10661
|
+
getNetConnectionForLabel(label) {
|
|
10662
|
+
return label.netId ? this.inputProblem.netConnections.find(
|
|
10663
|
+
(connection) => connection.netId === label.netId
|
|
10664
|
+
) : this.inputProblem.netConnections.find(
|
|
10665
|
+
(connection) => connection.pinIds.some((pinId) => label.pinIds.includes(pinId))
|
|
10666
|
+
);
|
|
10667
|
+
}
|
|
10668
|
+
isGroundLabel(label) {
|
|
10669
|
+
return this.getNetConnectionForLabel(label)?.isGround === true;
|
|
10670
|
+
}
|
|
10671
|
+
/**
|
|
10672
|
+
* A vertical-only input stores its text extent in netLabelHeight. If its
|
|
10673
|
+
* initial port-only placement is horizontal, use that extent for collision
|
|
10674
|
+
* checks without changing the placement dimensions returned by the solver.
|
|
10675
|
+
*/
|
|
10676
|
+
getHorizontalConstraintMismatchBounds(label) {
|
|
10677
|
+
if (!this.isPortOnlyLabel(label)) return null;
|
|
10678
|
+
if (label.orientation !== "x+" && label.orientation !== "x-") return null;
|
|
10679
|
+
const effectiveNetId = label.netId ?? label.globalConnNetId;
|
|
10680
|
+
const orientations = this.inputProblem.availableNetLabelOrientations[effectiveNetId] ?? [];
|
|
10681
|
+
if (orientations.length === 0 || orientations.some((orientation) => !isYOrientation(orientation))) {
|
|
10682
|
+
return null;
|
|
10683
|
+
}
|
|
10684
|
+
const renderedWidth = this.getNetConnectionForLabel(label)?.netLabelHeight;
|
|
10685
|
+
if (renderedWidth === void 0 || renderedWidth <= label.width + label.height + EPS13) {
|
|
10686
|
+
return null;
|
|
10687
|
+
}
|
|
10688
|
+
const placementBaseCenter = getCenterFromAnchor(
|
|
10689
|
+
label.anchorPoint,
|
|
10690
|
+
label.orientation,
|
|
10691
|
+
label.width,
|
|
10692
|
+
label.height
|
|
10693
|
+
);
|
|
10694
|
+
const renderedBaseCenter = getCenterFromAnchor(
|
|
10695
|
+
label.anchorPoint,
|
|
10696
|
+
label.orientation,
|
|
10697
|
+
renderedWidth,
|
|
10698
|
+
label.height
|
|
10699
|
+
);
|
|
10700
|
+
return getRectBounds(
|
|
10701
|
+
{
|
|
10702
|
+
x: renderedBaseCenter.x + label.center.x - placementBaseCenter.x,
|
|
10703
|
+
y: renderedBaseCenter.y + label.center.y - placementBaseCenter.y
|
|
10704
|
+
},
|
|
10705
|
+
renderedWidth,
|
|
10706
|
+
label.height
|
|
10707
|
+
);
|
|
10708
|
+
}
|
|
10709
|
+
includeHorizontalConstraintMismatches(labelIndices, include) {
|
|
10710
|
+
if (!include) return [...labelIndices].sort((a, b) => a - b);
|
|
10711
|
+
const result = new Set(labelIndices);
|
|
10712
|
+
for (let index = 0; index < this.outputNetLabelPlacements.length; index++) {
|
|
10713
|
+
if (this.getHorizontalConstraintMismatchBounds(
|
|
10714
|
+
this.outputNetLabelPlacements[index]
|
|
10715
|
+
)) {
|
|
10716
|
+
result.add(index);
|
|
10717
|
+
}
|
|
10718
|
+
}
|
|
10719
|
+
return [...result].sort((a, b) => a - b);
|
|
10720
|
+
}
|
|
10721
|
+
getLabelBoundsForCollisionWithGround(label, currentLabel) {
|
|
10722
|
+
if (this.isGroundLabel(currentLabel)) {
|
|
10723
|
+
const adjustedBounds = this.getHorizontalConstraintMismatchBounds(label);
|
|
10724
|
+
if (adjustedBounds) return adjustedBounds;
|
|
10725
|
+
}
|
|
10726
|
+
return getRectBounds(label.center, label.width, label.height);
|
|
10727
|
+
}
|
|
10648
10728
|
shouldIgnorePendingCrowdedLabel(labelIndex, otherLabelIndex) {
|
|
10649
10729
|
return this.crowdedPortOnlyLabelIndices.has(labelIndex) && this.crowdedPortOnlyLabelIndices.has(otherLabelIndex) && this.queuedLabelIndices.includes(otherLabelIndex);
|
|
10650
10730
|
}
|
|
10651
|
-
intersectsAnyOtherNetLabel(bounds, labelIndex) {
|
|
10731
|
+
intersectsAnyOtherNetLabel(bounds, labelIndex, currentLabel) {
|
|
10652
10732
|
const searchBounds = {
|
|
10653
10733
|
minX: bounds.minX - LABEL_SEARCH_STEP,
|
|
10654
10734
|
minY: bounds.minY - LABEL_SEARCH_STEP,
|
|
10655
10735
|
maxX: bounds.maxX + LABEL_SEARCH_STEP,
|
|
10656
10736
|
maxY: bounds.maxY + LABEL_SEARCH_STEP
|
|
10657
10737
|
};
|
|
10658
|
-
const nearbyLabelIndices = this.
|
|
10738
|
+
const nearbyLabelIndices = this.includeHorizontalConstraintMismatches(
|
|
10739
|
+
this.obstacleIndex.getLabelIndicesInBounds(searchBounds),
|
|
10740
|
+
this.isGroundLabel(currentLabel)
|
|
10741
|
+
);
|
|
10659
10742
|
for (const i of nearbyLabelIndices) {
|
|
10660
10743
|
if (i === labelIndex) continue;
|
|
10661
10744
|
if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
|
|
10662
10745
|
const label = this.outputNetLabelPlacements[i];
|
|
10663
|
-
const otherBounds =
|
|
10746
|
+
const otherBounds = this.getLabelBoundsForCollisionWithGround(
|
|
10747
|
+
label,
|
|
10748
|
+
currentLabel
|
|
10749
|
+
);
|
|
10664
10750
|
if (i === this.blockedStandaloneLabelIndex && isYOrientation(label.orientation)) {
|
|
10665
10751
|
otherBounds.minX -= LABEL_SEARCH_STEP;
|
|
10666
10752
|
otherBounds.maxX += LABEL_SEARCH_STEP;
|
|
@@ -268,6 +268,22 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
268
268
|
if (this.getTextBlockedMultiPinRailTraces(label, orientations[0]!)) {
|
|
269
269
|
return true
|
|
270
270
|
}
|
|
271
|
+
|
|
272
|
+
const hasSingleVerticalOrientation =
|
|
273
|
+
orientations.length === 1 && isYOrientation(orientations[0]!)
|
|
274
|
+
if (hasSingleVerticalOrientation && this.isGroundLabel(label)) {
|
|
275
|
+
const bounds = getRectBounds(label.center, label.width, label.height)
|
|
276
|
+
const overlapsHorizontalConstraintMismatch =
|
|
277
|
+
this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
|
|
278
|
+
if (otherIndex === labelIndex) return false
|
|
279
|
+
if (otherLabel.globalConnNetId === label.globalConnNetId) return false
|
|
280
|
+
const otherBounds =
|
|
281
|
+
this.getHorizontalConstraintMismatchBounds(otherLabel)
|
|
282
|
+
return otherBounds ? rectsOverlap(bounds, otherBounds) : false
|
|
283
|
+
})
|
|
284
|
+
if (overlapsHorizontalConstraintMismatch) return true
|
|
285
|
+
}
|
|
286
|
+
|
|
271
287
|
if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false
|
|
272
288
|
|
|
273
289
|
const bounds = getRectBounds(label.center, label.width, label.height)
|
|
@@ -1627,16 +1643,18 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1627
1643
|
}
|
|
1628
1644
|
}
|
|
1629
1645
|
|
|
1630
|
-
|
|
1631
|
-
connectorTrace,
|
|
1632
|
-
|
|
1646
|
+
const nearbyLabelIndices = this.includeHorizontalConstraintMismatches(
|
|
1647
|
+
this.obstacleIndex.getLabelIndicesNearTracePath(connectorTrace),
|
|
1648
|
+
this.isGroundLabel(label),
|
|
1649
|
+
)
|
|
1650
|
+
for (const i of nearbyLabelIndices) {
|
|
1633
1651
|
if (i === labelIndex) continue
|
|
1634
1652
|
if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
|
|
1635
1653
|
const otherLabel = this.outputNetLabelPlacements[i]!
|
|
1636
1654
|
if (
|
|
1637
1655
|
tracePathIntersectsBounds(
|
|
1638
1656
|
connectorTrace,
|
|
1639
|
-
|
|
1657
|
+
this.getLabelBoundsForCollisionWithGround(otherLabel, label),
|
|
1640
1658
|
)
|
|
1641
1659
|
) {
|
|
1642
1660
|
return "netlabel-collision"
|
|
@@ -1719,7 +1737,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1719
1737
|
if (this.isTraceTooCloseToLabel(bounds, label)) {
|
|
1720
1738
|
return "trace-clearance-violation"
|
|
1721
1739
|
}
|
|
1722
|
-
if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
|
|
1740
|
+
if (this.intersectsAnyOtherNetLabel(bounds, labelIndex, label)) {
|
|
1723
1741
|
return "netlabel-collision"
|
|
1724
1742
|
}
|
|
1725
1743
|
return "valid"
|
|
@@ -2020,6 +2038,99 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
2020
2038
|
return label.mspConnectionPairIds.length === 0
|
|
2021
2039
|
}
|
|
2022
2040
|
|
|
2041
|
+
private getNetConnectionForLabel(label: NetLabelPlacement) {
|
|
2042
|
+
return label.netId
|
|
2043
|
+
? this.inputProblem.netConnections.find(
|
|
2044
|
+
(connection) => connection.netId === label.netId,
|
|
2045
|
+
)
|
|
2046
|
+
: this.inputProblem.netConnections.find((connection) =>
|
|
2047
|
+
connection.pinIds.some((pinId) => label.pinIds.includes(pinId)),
|
|
2048
|
+
)
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
private isGroundLabel(label: NetLabelPlacement) {
|
|
2052
|
+
return this.getNetConnectionForLabel(label)?.isGround === true
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
/**
|
|
2056
|
+
* A vertical-only input stores its text extent in netLabelHeight. If its
|
|
2057
|
+
* initial port-only placement is horizontal, use that extent for collision
|
|
2058
|
+
* checks without changing the placement dimensions returned by the solver.
|
|
2059
|
+
*/
|
|
2060
|
+
private getHorizontalConstraintMismatchBounds(label: NetLabelPlacement) {
|
|
2061
|
+
if (!this.isPortOnlyLabel(label)) return null
|
|
2062
|
+
if (label.orientation !== "x+" && label.orientation !== "x-") return null
|
|
2063
|
+
|
|
2064
|
+
const effectiveNetId = label.netId ?? label.globalConnNetId
|
|
2065
|
+
const orientations =
|
|
2066
|
+
this.inputProblem.availableNetLabelOrientations[effectiveNetId] ?? []
|
|
2067
|
+
if (
|
|
2068
|
+
orientations.length === 0 ||
|
|
2069
|
+
orientations.some((orientation) => !isYOrientation(orientation))
|
|
2070
|
+
) {
|
|
2071
|
+
return null
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
const renderedWidth = this.getNetConnectionForLabel(label)?.netLabelHeight
|
|
2075
|
+
if (
|
|
2076
|
+
renderedWidth === undefined ||
|
|
2077
|
+
renderedWidth <= label.width + label.height + EPS
|
|
2078
|
+
) {
|
|
2079
|
+
return null
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
const placementBaseCenter = getCenterFromAnchor(
|
|
2083
|
+
label.anchorPoint,
|
|
2084
|
+
label.orientation,
|
|
2085
|
+
label.width,
|
|
2086
|
+
label.height,
|
|
2087
|
+
)
|
|
2088
|
+
const renderedBaseCenter = getCenterFromAnchor(
|
|
2089
|
+
label.anchorPoint,
|
|
2090
|
+
label.orientation,
|
|
2091
|
+
renderedWidth,
|
|
2092
|
+
label.height,
|
|
2093
|
+
)
|
|
2094
|
+
return getRectBounds(
|
|
2095
|
+
{
|
|
2096
|
+
x: renderedBaseCenter.x + label.center.x - placementBaseCenter.x,
|
|
2097
|
+
y: renderedBaseCenter.y + label.center.y - placementBaseCenter.y,
|
|
2098
|
+
},
|
|
2099
|
+
renderedWidth,
|
|
2100
|
+
label.height,
|
|
2101
|
+
)
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
private includeHorizontalConstraintMismatches(
|
|
2105
|
+
labelIndices: number[],
|
|
2106
|
+
include: boolean,
|
|
2107
|
+
) {
|
|
2108
|
+
if (!include) return [...labelIndices].sort((a, b) => a - b)
|
|
2109
|
+
|
|
2110
|
+
const result = new Set(labelIndices)
|
|
2111
|
+
for (let index = 0; index < this.outputNetLabelPlacements.length; index++) {
|
|
2112
|
+
if (
|
|
2113
|
+
this.getHorizontalConstraintMismatchBounds(
|
|
2114
|
+
this.outputNetLabelPlacements[index]!,
|
|
2115
|
+
)
|
|
2116
|
+
) {
|
|
2117
|
+
result.add(index)
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
return [...result].sort((a, b) => a - b)
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
private getLabelBoundsForCollisionWithGround(
|
|
2124
|
+
label: NetLabelPlacement,
|
|
2125
|
+
currentLabel: NetLabelPlacement,
|
|
2126
|
+
) {
|
|
2127
|
+
if (this.isGroundLabel(currentLabel)) {
|
|
2128
|
+
const adjustedBounds = this.getHorizontalConstraintMismatchBounds(label)
|
|
2129
|
+
if (adjustedBounds) return adjustedBounds
|
|
2130
|
+
}
|
|
2131
|
+
return getRectBounds(label.center, label.width, label.height)
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2023
2134
|
private shouldIgnorePendingCrowdedLabel(
|
|
2024
2135
|
labelIndex: number,
|
|
2025
2136
|
otherLabelIndex: number,
|
|
@@ -2031,22 +2142,30 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
2031
2142
|
)
|
|
2032
2143
|
}
|
|
2033
2144
|
|
|
2034
|
-
private intersectsAnyOtherNetLabel(
|
|
2145
|
+
private intersectsAnyOtherNetLabel(
|
|
2146
|
+
bounds: Bounds,
|
|
2147
|
+
labelIndex: number,
|
|
2148
|
+
currentLabel: NetLabelPlacement,
|
|
2149
|
+
) {
|
|
2035
2150
|
const searchBounds = {
|
|
2036
2151
|
minX: bounds.minX - LABEL_SEARCH_STEP,
|
|
2037
2152
|
minY: bounds.minY - LABEL_SEARCH_STEP,
|
|
2038
2153
|
maxX: bounds.maxX + LABEL_SEARCH_STEP,
|
|
2039
2154
|
maxY: bounds.maxY + LABEL_SEARCH_STEP,
|
|
2040
2155
|
}
|
|
2041
|
-
const nearbyLabelIndices = this.
|
|
2042
|
-
.getLabelIndicesInBounds(searchBounds)
|
|
2043
|
-
.
|
|
2156
|
+
const nearbyLabelIndices = this.includeHorizontalConstraintMismatches(
|
|
2157
|
+
this.obstacleIndex.getLabelIndicesInBounds(searchBounds),
|
|
2158
|
+
this.isGroundLabel(currentLabel),
|
|
2159
|
+
)
|
|
2044
2160
|
|
|
2045
2161
|
for (const i of nearbyLabelIndices) {
|
|
2046
2162
|
if (i === labelIndex) continue
|
|
2047
2163
|
if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
|
|
2048
2164
|
const label = this.outputNetLabelPlacements[i]!
|
|
2049
|
-
const otherBounds =
|
|
2165
|
+
const otherBounds = this.getLabelBoundsForCollisionWithGround(
|
|
2166
|
+
label,
|
|
2167
|
+
currentLabel,
|
|
2168
|
+
)
|
|
2050
2169
|
if (
|
|
2051
2170
|
i === this.blockedStandaloneLabelIndex &&
|
|
2052
2171
|
isYOrientation(label.orientation)
|