@tscircuit/schematic-trace-solver 0.0.117 → 0.0.119
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 +9 -1
- package/dist/index.js +164 -5
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +240 -7
- package/lib/solvers/AvailableNetOrientationSolver/types.ts +2 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +3 -3
- package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +51 -51
- package/tests/bug-reports/bug-report-20260731T052229Z/bug-report-20260731T052229Z.test.ts +18 -0
- package/tests/repros/__snapshots__/repro-power-group-rail-labels.snap.svg +58 -0
- package/tests/repros/__snapshots__/repro-rp2040-v3v3-label-placement.snap.svg +96 -0
- package/tests/repros/repro-power-group-rail-labels.test.ts +95 -0
- package/tests/repros/repro-rp2040-v3v3-label-placement.test.ts +102 -0
package/dist/index.d.ts
CHANGED
|
@@ -721,12 +721,13 @@ interface AvailableNetOrientationSolverParams {
|
|
|
721
721
|
type CandidateLabel = {
|
|
722
722
|
orientation: FacingDirection;
|
|
723
723
|
anchorPoint: Point;
|
|
724
|
+
connectorSource?: Point;
|
|
724
725
|
center: Point;
|
|
725
726
|
width: number;
|
|
726
727
|
height: number;
|
|
727
728
|
};
|
|
728
729
|
type CandidateStatus$2 = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "trace-clearance-violation" | "netlabel-collision";
|
|
729
|
-
type CandidatePhase = "rotate" | "trace-anchor" | "shift" | "lateral-shift";
|
|
730
|
+
type CandidatePhase = "rotate" | "trace-anchor" | "outward-trace-anchor" | "shift" | "lateral-shift";
|
|
730
731
|
type EvaluatedCandidate = CandidateLabel & {
|
|
731
732
|
status: CandidateStatus$2;
|
|
732
733
|
selected: boolean;
|
|
@@ -744,6 +745,8 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
744
745
|
currentLabelIndex: number | null;
|
|
745
746
|
currentLabel: NetLabelPlacement | null;
|
|
746
747
|
currentCandidateResults: EvaluatedCandidate[];
|
|
748
|
+
blockedStandaloneLabelIndex: number | null;
|
|
749
|
+
blockedStandaloneLabelTargetAnchorX: number | null;
|
|
747
750
|
private traceMap;
|
|
748
751
|
private chipObstacleSpatialIndex;
|
|
749
752
|
private maxSearchDistance;
|
|
@@ -766,6 +769,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
766
769
|
private hasTraceContinuingInOrientation;
|
|
767
770
|
private findValidRotatedCandidate;
|
|
768
771
|
private findValidTraceAnchorCandidate;
|
|
772
|
+
private findValidOutwardTraceAnchorCandidate;
|
|
769
773
|
private getTraceAnchorCandidatePoints;
|
|
770
774
|
private getAvailableOrientations;
|
|
771
775
|
private findValidShiftedCandidate;
|
|
@@ -799,6 +803,10 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
799
803
|
private getLabelTraceClearanceBounds;
|
|
800
804
|
private shouldCheckTraceClearanceForLabel;
|
|
801
805
|
private hasRoutedLabelOnSameNet;
|
|
806
|
+
private isOutwardHorizontalFallback;
|
|
807
|
+
private hasPortOnlyLabelOnSameNet;
|
|
808
|
+
private hasOppositeVerticalRailOnSameChipSide;
|
|
809
|
+
private getSingleChipSideForNetConnection;
|
|
802
810
|
private isPortOnlyLabel;
|
|
803
811
|
private intersectsAnyOtherNetLabel;
|
|
804
812
|
private sharesChipBoundary;
|
package/dist/index.js
CHANGED
|
@@ -7848,6 +7848,8 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
7848
7848
|
currentLabelIndex = null;
|
|
7849
7849
|
currentLabel = null;
|
|
7850
7850
|
currentCandidateResults = [];
|
|
7851
|
+
blockedStandaloneLabelIndex = null;
|
|
7852
|
+
blockedStandaloneLabelTargetAnchorX = null;
|
|
7851
7853
|
traceMap;
|
|
7852
7854
|
chipObstacleSpatialIndex;
|
|
7853
7855
|
maxSearchDistance;
|
|
@@ -7901,6 +7903,8 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
7901
7903
|
return this.isPortOnlyLabel(label) && this.isStandaloneSinglePinNetLabel(label) && orientations.length === 1 && isYOrientation(orientations[0]) && this.labelIntersectsDifferentNetTrace(label);
|
|
7902
7904
|
});
|
|
7903
7905
|
if (blockedStandaloneLabelIndex === void 0) return indices;
|
|
7906
|
+
this.blockedStandaloneLabelIndex = blockedStandaloneLabelIndex;
|
|
7907
|
+
this.blockedStandaloneLabelTargetAnchorX = null;
|
|
7904
7908
|
const blockedLabel = this.outputNetLabelPlacements[blockedStandaloneLabelIndex];
|
|
7905
7909
|
const requiredOrientation = this.getAvailableOrientations(blockedLabel)[0];
|
|
7906
7910
|
const chipSide = this.getChipSideForPoint(blockedLabel.anchorPoint);
|
|
@@ -7920,6 +7924,22 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
7920
7924
|
const bY = this.outputNetLabelPlacements[b].anchorPoint.y;
|
|
7921
7925
|
return requiredOrientation === "y+" ? bY - aY : aY - bY;
|
|
7922
7926
|
});
|
|
7927
|
+
const blockedLabelWidth = this.getNetLabelWidth(blockedLabel);
|
|
7928
|
+
let columnLabelIndex;
|
|
7929
|
+
if (blockedLabelWidth !== void 0) {
|
|
7930
|
+
columnLabelIndex = labelsToOrder.find((index) => {
|
|
7931
|
+
if (index === blockedStandaloneLabelIndex) return false;
|
|
7932
|
+
const label = this.outputNetLabelPlacements[index];
|
|
7933
|
+
return this.getNetLabelWidth(label) === blockedLabelWidth;
|
|
7934
|
+
});
|
|
7935
|
+
}
|
|
7936
|
+
if (columnLabelIndex !== void 0) {
|
|
7937
|
+
const columnLabel = this.outputNetLabelPlacements[columnLabelIndex];
|
|
7938
|
+
this.blockedStandaloneLabelTargetAnchorX = this.getSearchStartAnchor(
|
|
7939
|
+
columnLabel,
|
|
7940
|
+
requiredOrientation
|
|
7941
|
+
).x;
|
|
7942
|
+
}
|
|
7923
7943
|
for (let i = 0; i < affectedSlots.length; i++) {
|
|
7924
7944
|
indices[affectedSlots[i]] = labelsToOrder[i];
|
|
7925
7945
|
}
|
|
@@ -7975,9 +7995,20 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
7975
7995
|
findCorrectedCandidate(label, labelIndex) {
|
|
7976
7996
|
const orientations = this.getAvailableOrientations(label);
|
|
7977
7997
|
const requiredOrientation = orientations[0];
|
|
7978
|
-
const
|
|
7979
|
-
(connection) => connection.netId === label.netId
|
|
7998
|
+
const netConnection = this.inputProblem.netConnections.find(
|
|
7999
|
+
(connection) => connection.netId === label.netId
|
|
7980
8000
|
);
|
|
8001
|
+
const isTwoPinNet = netConnection?.pinIds.length === 2;
|
|
8002
|
+
const isDistanceSplitUpwardRail = (netConnection?.pinIds.length ?? 0) > 2 && requiredOrientation === "y+" && this.hasPortOnlyLabelOnSameNet(label);
|
|
8003
|
+
const isPairedSameSidePowerRail = isYOrientation(requiredOrientation) && this.hasOppositeVerticalRailOnSameChipSide(label, requiredOrientation);
|
|
8004
|
+
if (orientations.length === 1 && isYOrientation(requiredOrientation) && this.isOutwardHorizontalFallback(label) && this.hasTraceContinuingInOrientation(label, requiredOrientation) && (isDistanceSplitUpwardRail || isPairedSameSidePowerRail)) {
|
|
8005
|
+
const traceAnchorCandidate2 = this.findValidOutwardTraceAnchorCandidate(
|
|
8006
|
+
label,
|
|
8007
|
+
requiredOrientation,
|
|
8008
|
+
labelIndex
|
|
8009
|
+
);
|
|
8010
|
+
if (traceAnchorCandidate2) return traceAnchorCandidate2;
|
|
8011
|
+
}
|
|
7981
8012
|
if (isTwoPinNet && orientations.length === 1 && isYOrientation(requiredOrientation) && this.hasTraceContinuingInOrientation(label, requiredOrientation)) {
|
|
7982
8013
|
const alignedCandidate = this.findValidCandidateInShiftColumn({
|
|
7983
8014
|
label,
|
|
@@ -8079,6 +8110,72 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8079
8110
|
}
|
|
8080
8111
|
return null;
|
|
8081
8112
|
}
|
|
8113
|
+
findValidOutwardTraceAnchorCandidate(label, orientation, labelIndex) {
|
|
8114
|
+
const direction = dir(orientation);
|
|
8115
|
+
const candidatePoints = this.getTraceAnchorCandidatePoints(label).sort(
|
|
8116
|
+
(a, b) => {
|
|
8117
|
+
const aAlongDirection = a.x * direction.x + a.y * direction.y;
|
|
8118
|
+
const bAlongDirection = b.x * direction.x + b.y * direction.y;
|
|
8119
|
+
return bAlongDirection - aAlongDirection;
|
|
8120
|
+
}
|
|
8121
|
+
);
|
|
8122
|
+
for (const connectorSource of candidatePoints) {
|
|
8123
|
+
const preservedColumnAnchor = this.getSearchStartAnchor(
|
|
8124
|
+
label,
|
|
8125
|
+
orientation
|
|
8126
|
+
);
|
|
8127
|
+
const preservedColumnCandidate = this.createCandidate(
|
|
8128
|
+
label,
|
|
8129
|
+
{
|
|
8130
|
+
x: preservedColumnAnchor.x,
|
|
8131
|
+
y: connectorSource.y
|
|
8132
|
+
},
|
|
8133
|
+
orientation,
|
|
8134
|
+
connectorSource
|
|
8135
|
+
);
|
|
8136
|
+
const preservedColumnResult = this.evaluateCandidate(
|
|
8137
|
+
preservedColumnCandidate,
|
|
8138
|
+
label,
|
|
8139
|
+
labelIndex,
|
|
8140
|
+
"outward-trace-anchor"
|
|
8141
|
+
);
|
|
8142
|
+
this.currentCandidateResults.push(preservedColumnResult);
|
|
8143
|
+
if (preservedColumnResult.status === "valid") {
|
|
8144
|
+
preservedColumnResult.selected = true;
|
|
8145
|
+
return preservedColumnResult;
|
|
8146
|
+
}
|
|
8147
|
+
const outwardDirection = this.getPerpendicularOutwardDirection(
|
|
8148
|
+
connectorSource,
|
|
8149
|
+
orientation
|
|
8150
|
+
);
|
|
8151
|
+
if (outwardDirection.x === 0 && outwardDirection.y === 0) continue;
|
|
8152
|
+
for (let distance5 = LABEL_SEARCH_STEP; distance5 <= this.maxSearchDistance + EPS11; distance5 += LABEL_SEARCH_STEP) {
|
|
8153
|
+
const anchorPoint = {
|
|
8154
|
+
x: connectorSource.x + outwardDirection.x * distance5,
|
|
8155
|
+
y: connectorSource.y + outwardDirection.y * distance5
|
|
8156
|
+
};
|
|
8157
|
+
const candidate = this.createCandidate(
|
|
8158
|
+
label,
|
|
8159
|
+
anchorPoint,
|
|
8160
|
+
orientation,
|
|
8161
|
+
connectorSource
|
|
8162
|
+
);
|
|
8163
|
+
const result = this.evaluateCandidate(
|
|
8164
|
+
candidate,
|
|
8165
|
+
label,
|
|
8166
|
+
labelIndex,
|
|
8167
|
+
"outward-trace-anchor",
|
|
8168
|
+
distance5
|
|
8169
|
+
);
|
|
8170
|
+
this.currentCandidateResults.push(result);
|
|
8171
|
+
if (result.status === "valid") {
|
|
8172
|
+
result.selected = true;
|
|
8173
|
+
return result;
|
|
8174
|
+
}
|
|
8175
|
+
}
|
|
8176
|
+
}
|
|
8177
|
+
return null;
|
|
8178
|
+
}
|
|
8082
8179
|
getTraceAnchorCandidatePoints(label) {
|
|
8083
8180
|
const seen = /* @__PURE__ */ new Set();
|
|
8084
8181
|
const points = [];
|
|
@@ -8236,7 +8333,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8236
8333
|
]);
|
|
8237
8334
|
}
|
|
8238
8335
|
return getConnectorTracePath(
|
|
8239
|
-
label.anchorPoint,
|
|
8336
|
+
candidate.connectorSource ?? label.anchorPoint,
|
|
8240
8337
|
candidate.anchorPoint,
|
|
8241
8338
|
candidate.orientation
|
|
8242
8339
|
);
|
|
@@ -8270,13 +8367,17 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8270
8367
|
netLabelWidth: this.getNetLabelWidth(label),
|
|
8271
8368
|
netLabelHeight: this.getNetLabelHeight(label)
|
|
8272
8369
|
});
|
|
8273
|
-
|
|
8370
|
+
const searchStartAnchor = this.getSideOffsetAnchor({
|
|
8274
8371
|
anchorPoint,
|
|
8275
8372
|
labelAnchorPoint: label.anchorPoint,
|
|
8276
8373
|
orientation,
|
|
8277
8374
|
width,
|
|
8278
8375
|
height
|
|
8279
8376
|
});
|
|
8377
|
+
if (this.blockedStandaloneLabelIndex !== null && label === this.outputNetLabelPlacements[this.blockedStandaloneLabelIndex] && isYOrientation(orientation) && this.blockedStandaloneLabelTargetAnchorX !== null) {
|
|
8378
|
+
searchStartAnchor.x = this.blockedStandaloneLabelTargetAnchorX;
|
|
8379
|
+
}
|
|
8380
|
+
return searchStartAnchor;
|
|
8280
8381
|
}
|
|
8281
8382
|
getWickOffsetAnchor(anchorPoint, orientation) {
|
|
8282
8383
|
const direction = dir(orientation);
|
|
@@ -8366,7 +8467,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8366
8467
|
}
|
|
8367
8468
|
return this.getSearchDistanceLimit(label, orientation);
|
|
8368
8469
|
}
|
|
8369
|
-
createCandidate(label, anchorPoint, orientation) {
|
|
8470
|
+
createCandidate(label, anchorPoint, orientation, connectorSource) {
|
|
8370
8471
|
const { width, height } = getDimsForOrientation({
|
|
8371
8472
|
orientation,
|
|
8372
8473
|
netLabelWidth: this.getNetLabelWidth(label),
|
|
@@ -8375,6 +8476,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8375
8476
|
return {
|
|
8376
8477
|
orientation,
|
|
8377
8478
|
anchorPoint,
|
|
8479
|
+
connectorSource,
|
|
8378
8480
|
width,
|
|
8379
8481
|
height,
|
|
8380
8482
|
center: getCenterFromAnchor(anchorPoint, orientation, width, height)
|
|
@@ -8528,6 +8630,59 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8528
8630
|
}
|
|
8529
8631
|
return false;
|
|
8530
8632
|
}
|
|
8633
|
+
isOutwardHorizontalFallback(label) {
|
|
8634
|
+
const chipSide = this.getChipSideForPoint(label.anchorPoint);
|
|
8635
|
+
return chipSide === "left" && label.orientation === "x-" || chipSide === "right" && label.orientation === "x+";
|
|
8636
|
+
}
|
|
8637
|
+
hasPortOnlyLabelOnSameNet(label) {
|
|
8638
|
+
return this.outputNetLabelPlacements.some(
|
|
8639
|
+
(otherLabel) => otherLabel !== label && otherLabel.globalConnNetId === label.globalConnNetId && this.isPortOnlyLabel(otherLabel)
|
|
8640
|
+
);
|
|
8641
|
+
}
|
|
8642
|
+
hasOppositeVerticalRailOnSameChipSide(label, orientation) {
|
|
8643
|
+
const currentConnection = this.inputProblem.netConnections.find(
|
|
8644
|
+
(connection) => connection.netId === label.netId
|
|
8645
|
+
);
|
|
8646
|
+
if (!currentConnection) return false;
|
|
8647
|
+
const currentSide = this.getSingleChipSideForNetConnection(currentConnection);
|
|
8648
|
+
if (!currentSide) return false;
|
|
8649
|
+
const oppositeOrientation = orientation === "y+" ? "y-" : "y+";
|
|
8650
|
+
return this.inputProblem.netConnections.some((connection) => {
|
|
8651
|
+
if (connection === currentConnection || connection.pinIds.length < 2) {
|
|
8652
|
+
return false;
|
|
8653
|
+
}
|
|
8654
|
+
const availableOrientations = this.inputProblem.availableNetLabelOrientations[connection.netId] ?? [];
|
|
8655
|
+
if (availableOrientations.length !== 1 || availableOrientations[0] !== oppositeOrientation) {
|
|
8656
|
+
return false;
|
|
8657
|
+
}
|
|
8658
|
+
const connectionSide = this.getSingleChipSideForNetConnection(connection);
|
|
8659
|
+
return connectionSide?.chipId === currentSide.chipId && connectionSide.side === currentSide.side;
|
|
8660
|
+
});
|
|
8661
|
+
}
|
|
8662
|
+
getSingleChipSideForNetConnection(connection) {
|
|
8663
|
+
if (connection.pinIds.length < 2) return null;
|
|
8664
|
+
const pins = connection.pinIds.map((pinId) => this.pinMap[pinId]);
|
|
8665
|
+
if (pins.some((pin) => !pin)) return null;
|
|
8666
|
+
const chipIds = new Set(pins.map((pin) => pin.chipId));
|
|
8667
|
+
if (chipIds.size !== 1) return null;
|
|
8668
|
+
const chipId = pins[0].chipId;
|
|
8669
|
+
const chip = this.chipObstacleSpatialIndex.chips.find(
|
|
8670
|
+
(candidate) => candidate.chipId === chipId
|
|
8671
|
+
);
|
|
8672
|
+
if (!chip) return null;
|
|
8673
|
+
const sides = new Set(
|
|
8674
|
+
pins.map((pin) => {
|
|
8675
|
+
if (Math.abs(pin.x - chip.bounds.minX) <= EPS11) return "left";
|
|
8676
|
+
if (Math.abs(pin.x - chip.bounds.maxX) <= EPS11) return "right";
|
|
8677
|
+
return null;
|
|
8678
|
+
})
|
|
8679
|
+
);
|
|
8680
|
+
if (sides.size !== 1 || sides.has(null)) return null;
|
|
8681
|
+
return {
|
|
8682
|
+
chipId,
|
|
8683
|
+
side: [...sides][0]
|
|
8684
|
+
};
|
|
8685
|
+
}
|
|
8531
8686
|
isPortOnlyLabel(label) {
|
|
8532
8687
|
return label.mspConnectionPairIds.length === 0;
|
|
8533
8688
|
}
|
|
@@ -8536,6 +8691,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8536
8691
|
if (i === labelIndex) continue;
|
|
8537
8692
|
const label = this.outputNetLabelPlacements[i];
|
|
8538
8693
|
const otherBounds = getRectBounds(label.center, label.width, label.height);
|
|
8694
|
+
if (i === this.blockedStandaloneLabelIndex && isYOrientation(label.orientation)) {
|
|
8695
|
+
otherBounds.minX -= LABEL_SEARCH_STEP;
|
|
8696
|
+
otherBounds.maxX += LABEL_SEARCH_STEP;
|
|
8697
|
+
}
|
|
8539
8698
|
if (rectsOverlap2(bounds, otherBounds)) return true;
|
|
8540
8699
|
}
|
|
8541
8700
|
return false;
|
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
getRectBounds,
|
|
10
10
|
} from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
11
11
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
InputNetConnection,
|
|
14
|
+
InputPin,
|
|
15
|
+
InputProblem,
|
|
16
|
+
} from "lib/types/InputProblem"
|
|
13
17
|
import { dir, type FacingDirection } from "lib/utils/dir"
|
|
14
18
|
import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
|
|
15
19
|
import { EPS, LABEL_SEARCH_STEP, WICK_CLEARANCE } from "./constants"
|
|
@@ -51,6 +55,8 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
51
55
|
currentLabel: NetLabelPlacement | null = null
|
|
52
56
|
currentCandidateResults: EvaluatedCandidate[] = []
|
|
53
57
|
|
|
58
|
+
blockedStandaloneLabelIndex: number | null = null
|
|
59
|
+
blockedStandaloneLabelTargetAnchorX: number | null = null
|
|
54
60
|
private traceMap: Record<string, SolvedTracePath>
|
|
55
61
|
private chipObstacleSpatialIndex: ChipObstacleSpatialIndex
|
|
56
62
|
private maxSearchDistance: number
|
|
@@ -122,6 +128,8 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
122
128
|
)
|
|
123
129
|
})
|
|
124
130
|
if (blockedStandaloneLabelIndex === undefined) return indices
|
|
131
|
+
this.blockedStandaloneLabelIndex = blockedStandaloneLabelIndex
|
|
132
|
+
this.blockedStandaloneLabelTargetAnchorX = null
|
|
125
133
|
|
|
126
134
|
const blockedLabel =
|
|
127
135
|
this.outputNetLabelPlacements[blockedStandaloneLabelIndex]!
|
|
@@ -152,6 +160,23 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
152
160
|
const bY = this.outputNetLabelPlacements[b]!.anchorPoint.y
|
|
153
161
|
return requiredOrientation === "y+" ? bY - aY : aY - bY
|
|
154
162
|
})
|
|
163
|
+
const blockedLabelWidth = this.getNetLabelWidth(blockedLabel)
|
|
164
|
+
let columnLabelIndex: number | undefined
|
|
165
|
+
if (blockedLabelWidth !== undefined) {
|
|
166
|
+
columnLabelIndex = labelsToOrder.find((index) => {
|
|
167
|
+
if (index === blockedStandaloneLabelIndex) return false
|
|
168
|
+
const label = this.outputNetLabelPlacements[index]!
|
|
169
|
+
return this.getNetLabelWidth(label) === blockedLabelWidth
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
if (columnLabelIndex !== undefined) {
|
|
173
|
+
const columnLabel = this.outputNetLabelPlacements[columnLabelIndex]!
|
|
174
|
+
// Reuse the established same-width column instead of a nearby search step.
|
|
175
|
+
this.blockedStandaloneLabelTargetAnchorX = this.getSearchStartAnchor(
|
|
176
|
+
columnLabel,
|
|
177
|
+
requiredOrientation,
|
|
178
|
+
).x
|
|
179
|
+
}
|
|
155
180
|
for (let i = 0; i < affectedSlots.length; i++) {
|
|
156
181
|
indices[affectedSlots[i]!] = labelsToOrder[i]!
|
|
157
182
|
}
|
|
@@ -229,10 +254,35 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
229
254
|
private findCorrectedCandidate(label: NetLabelPlacement, labelIndex: number) {
|
|
230
255
|
const orientations = this.getAvailableOrientations(label)
|
|
231
256
|
const requiredOrientation = orientations[0]!
|
|
232
|
-
const
|
|
233
|
-
(connection) =>
|
|
234
|
-
connection.netId === label.netId && connection.pinIds.length === 2,
|
|
257
|
+
const netConnection = this.inputProblem.netConnections.find(
|
|
258
|
+
(connection) => connection.netId === label.netId,
|
|
235
259
|
)
|
|
260
|
+
const isTwoPinNet = netConnection?.pinIds.length === 2
|
|
261
|
+
const isDistanceSplitUpwardRail =
|
|
262
|
+
(netConnection?.pinIds.length ?? 0) > 2 &&
|
|
263
|
+
requiredOrientation === "y+" &&
|
|
264
|
+
this.hasPortOnlyLabelOnSameNet(label)
|
|
265
|
+
const isPairedSameSidePowerRail =
|
|
266
|
+
isYOrientation(requiredOrientation) &&
|
|
267
|
+
this.hasOppositeVerticalRailOnSameChipSide(label, requiredOrientation)
|
|
268
|
+
if (
|
|
269
|
+
orientations.length === 1 &&
|
|
270
|
+
isYOrientation(requiredOrientation) &&
|
|
271
|
+
this.isOutwardHorizontalFallback(label) &&
|
|
272
|
+
this.hasTraceContinuingInOrientation(label, requiredOrientation) &&
|
|
273
|
+
(isDistanceSplitUpwardRail || isPairedSameSidePowerRail)
|
|
274
|
+
) {
|
|
275
|
+
// Keep the established outward column, but attach at the furthest trace
|
|
276
|
+
// point in the required vertical direction. This places y+ labels above
|
|
277
|
+
// a rail and y- labels below it while using a short horizontal connector.
|
|
278
|
+
const traceAnchorCandidate = this.findValidOutwardTraceAnchorCandidate(
|
|
279
|
+
label,
|
|
280
|
+
requiredOrientation,
|
|
281
|
+
labelIndex,
|
|
282
|
+
)
|
|
283
|
+
if (traceAnchorCandidate) return traceAnchorCandidate
|
|
284
|
+
}
|
|
285
|
+
|
|
236
286
|
if (
|
|
237
287
|
isTwoPinNet &&
|
|
238
288
|
orientations.length === 1 &&
|
|
@@ -369,6 +419,86 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
369
419
|
return null
|
|
370
420
|
}
|
|
371
421
|
|
|
422
|
+
private findValidOutwardTraceAnchorCandidate(
|
|
423
|
+
label: NetLabelPlacement,
|
|
424
|
+
orientation: FacingDirection,
|
|
425
|
+
labelIndex: number,
|
|
426
|
+
) {
|
|
427
|
+
const direction = dir(orientation)
|
|
428
|
+
const candidatePoints = this.getTraceAnchorCandidatePoints(label).sort(
|
|
429
|
+
(a, b) => {
|
|
430
|
+
const aAlongDirection = a.x * direction.x + a.y * direction.y
|
|
431
|
+
const bAlongDirection = b.x * direction.x + b.y * direction.y
|
|
432
|
+
return bAlongDirection - aAlongDirection
|
|
433
|
+
},
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
for (const connectorSource of candidatePoints) {
|
|
437
|
+
const preservedColumnAnchor = this.getSearchStartAnchor(
|
|
438
|
+
label,
|
|
439
|
+
orientation,
|
|
440
|
+
)
|
|
441
|
+
const preservedColumnCandidate = this.createCandidate(
|
|
442
|
+
label,
|
|
443
|
+
{
|
|
444
|
+
x: preservedColumnAnchor.x,
|
|
445
|
+
y: connectorSource.y,
|
|
446
|
+
},
|
|
447
|
+
orientation,
|
|
448
|
+
connectorSource,
|
|
449
|
+
)
|
|
450
|
+
const preservedColumnResult = this.evaluateCandidate(
|
|
451
|
+
preservedColumnCandidate,
|
|
452
|
+
label,
|
|
453
|
+
labelIndex,
|
|
454
|
+
"outward-trace-anchor",
|
|
455
|
+
)
|
|
456
|
+
this.currentCandidateResults.push(preservedColumnResult)
|
|
457
|
+
if (preservedColumnResult.status === "valid") {
|
|
458
|
+
preservedColumnResult.selected = true
|
|
459
|
+
return preservedColumnResult
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const outwardDirection = this.getPerpendicularOutwardDirection(
|
|
463
|
+
connectorSource,
|
|
464
|
+
orientation,
|
|
465
|
+
)
|
|
466
|
+
if (outwardDirection.x === 0 && outwardDirection.y === 0) continue
|
|
467
|
+
|
|
468
|
+
for (
|
|
469
|
+
let distance = LABEL_SEARCH_STEP;
|
|
470
|
+
distance <= this.maxSearchDistance + EPS;
|
|
471
|
+
distance += LABEL_SEARCH_STEP
|
|
472
|
+
) {
|
|
473
|
+
const anchorPoint = {
|
|
474
|
+
x: connectorSource.x + outwardDirection.x * distance,
|
|
475
|
+
y: connectorSource.y + outwardDirection.y * distance,
|
|
476
|
+
}
|
|
477
|
+
const candidate = this.createCandidate(
|
|
478
|
+
label,
|
|
479
|
+
anchorPoint,
|
|
480
|
+
orientation,
|
|
481
|
+
connectorSource,
|
|
482
|
+
)
|
|
483
|
+
const result = this.evaluateCandidate(
|
|
484
|
+
candidate,
|
|
485
|
+
label,
|
|
486
|
+
labelIndex,
|
|
487
|
+
"outward-trace-anchor",
|
|
488
|
+
distance,
|
|
489
|
+
)
|
|
490
|
+
this.currentCandidateResults.push(result)
|
|
491
|
+
|
|
492
|
+
if (result.status === "valid") {
|
|
493
|
+
result.selected = true
|
|
494
|
+
return result
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return null
|
|
500
|
+
}
|
|
501
|
+
|
|
372
502
|
private getTraceAnchorCandidatePoints(label: NetLabelPlacement) {
|
|
373
503
|
const seen = new Set<string>()
|
|
374
504
|
const points: Point[] = []
|
|
@@ -571,7 +701,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
571
701
|
label: NetLabelPlacement,
|
|
572
702
|
candidate: Pick<
|
|
573
703
|
EvaluatedCandidate,
|
|
574
|
-
"anchorPoint" | "orientation" | "phase"
|
|
704
|
+
"anchorPoint" | "connectorSource" | "orientation" | "phase"
|
|
575
705
|
>,
|
|
576
706
|
) {
|
|
577
707
|
if (candidate.phase === "lateral-shift") {
|
|
@@ -591,7 +721,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
591
721
|
}
|
|
592
722
|
|
|
593
723
|
return getConnectorTracePath(
|
|
594
|
-
label.anchorPoint,
|
|
724
|
+
candidate.connectorSource ?? label.anchorPoint,
|
|
595
725
|
candidate.anchorPoint,
|
|
596
726
|
candidate.orientation,
|
|
597
727
|
)
|
|
@@ -637,13 +767,23 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
637
767
|
netLabelHeight: this.getNetLabelHeight(label),
|
|
638
768
|
})
|
|
639
769
|
|
|
640
|
-
|
|
770
|
+
const searchStartAnchor = this.getSideOffsetAnchor({
|
|
641
771
|
anchorPoint,
|
|
642
772
|
labelAnchorPoint: label.anchorPoint,
|
|
643
773
|
orientation,
|
|
644
774
|
width,
|
|
645
775
|
height,
|
|
646
776
|
})
|
|
777
|
+
if (
|
|
778
|
+
this.blockedStandaloneLabelIndex !== null &&
|
|
779
|
+
label ===
|
|
780
|
+
this.outputNetLabelPlacements[this.blockedStandaloneLabelIndex] &&
|
|
781
|
+
isYOrientation(orientation) &&
|
|
782
|
+
this.blockedStandaloneLabelTargetAnchorX !== null
|
|
783
|
+
) {
|
|
784
|
+
searchStartAnchor.x = this.blockedStandaloneLabelTargetAnchorX
|
|
785
|
+
}
|
|
786
|
+
return searchStartAnchor
|
|
647
787
|
}
|
|
648
788
|
|
|
649
789
|
private getWickOffsetAnchor(
|
|
@@ -779,6 +919,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
779
919
|
label: NetLabelPlacement,
|
|
780
920
|
anchorPoint: Point,
|
|
781
921
|
orientation: FacingDirection,
|
|
922
|
+
connectorSource?: Point,
|
|
782
923
|
): CandidateLabel {
|
|
783
924
|
const { width, height } = getDimsForOrientation({
|
|
784
925
|
orientation,
|
|
@@ -788,6 +929,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
788
929
|
return {
|
|
789
930
|
orientation,
|
|
790
931
|
anchorPoint,
|
|
932
|
+
connectorSource,
|
|
791
933
|
width,
|
|
792
934
|
height,
|
|
793
935
|
center: getCenterFromAnchor(anchorPoint, orientation, width, height),
|
|
@@ -998,6 +1140,89 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
998
1140
|
return false
|
|
999
1141
|
}
|
|
1000
1142
|
|
|
1143
|
+
private isOutwardHorizontalFallback(label: NetLabelPlacement) {
|
|
1144
|
+
const chipSide = this.getChipSideForPoint(label.anchorPoint)
|
|
1145
|
+
return (
|
|
1146
|
+
(chipSide === "left" && label.orientation === "x-") ||
|
|
1147
|
+
(chipSide === "right" && label.orientation === "x+")
|
|
1148
|
+
)
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
private hasPortOnlyLabelOnSameNet(label: NetLabelPlacement) {
|
|
1152
|
+
return this.outputNetLabelPlacements.some(
|
|
1153
|
+
(otherLabel) =>
|
|
1154
|
+
otherLabel !== label &&
|
|
1155
|
+
otherLabel.globalConnNetId === label.globalConnNetId &&
|
|
1156
|
+
this.isPortOnlyLabel(otherLabel),
|
|
1157
|
+
)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
private hasOppositeVerticalRailOnSameChipSide(
|
|
1161
|
+
label: NetLabelPlacement,
|
|
1162
|
+
orientation: "y+" | "y-",
|
|
1163
|
+
) {
|
|
1164
|
+
const currentConnection = this.inputProblem.netConnections.find(
|
|
1165
|
+
(connection) => connection.netId === label.netId,
|
|
1166
|
+
)
|
|
1167
|
+
if (!currentConnection) return false
|
|
1168
|
+
|
|
1169
|
+
const currentSide =
|
|
1170
|
+
this.getSingleChipSideForNetConnection(currentConnection)
|
|
1171
|
+
if (!currentSide) return false
|
|
1172
|
+
|
|
1173
|
+
const oppositeOrientation = orientation === "y+" ? "y-" : "y+"
|
|
1174
|
+
return this.inputProblem.netConnections.some((connection) => {
|
|
1175
|
+
if (connection === currentConnection || connection.pinIds.length < 2) {
|
|
1176
|
+
return false
|
|
1177
|
+
}
|
|
1178
|
+
const availableOrientations =
|
|
1179
|
+
this.inputProblem.availableNetLabelOrientations[connection.netId] ?? []
|
|
1180
|
+
if (
|
|
1181
|
+
availableOrientations.length !== 1 ||
|
|
1182
|
+
availableOrientations[0] !== oppositeOrientation
|
|
1183
|
+
) {
|
|
1184
|
+
return false
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
const connectionSide = this.getSingleChipSideForNetConnection(connection)
|
|
1188
|
+
return (
|
|
1189
|
+
connectionSide?.chipId === currentSide.chipId &&
|
|
1190
|
+
connectionSide.side === currentSide.side
|
|
1191
|
+
)
|
|
1192
|
+
})
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
private getSingleChipSideForNetConnection(
|
|
1196
|
+
connection: InputNetConnection,
|
|
1197
|
+
): { chipId: string; side: "left" | "right" } | null {
|
|
1198
|
+
if (connection.pinIds.length < 2) return null
|
|
1199
|
+
|
|
1200
|
+
const pins = connection.pinIds.map((pinId) => this.pinMap[pinId])
|
|
1201
|
+
if (pins.some((pin) => !pin)) return null
|
|
1202
|
+
const chipIds = new Set(pins.map((pin) => pin!.chipId))
|
|
1203
|
+
if (chipIds.size !== 1) return null
|
|
1204
|
+
|
|
1205
|
+
const chipId = pins[0]!.chipId
|
|
1206
|
+
const chip = this.chipObstacleSpatialIndex.chips.find(
|
|
1207
|
+
(candidate) => candidate.chipId === chipId,
|
|
1208
|
+
)
|
|
1209
|
+
if (!chip) return null
|
|
1210
|
+
|
|
1211
|
+
const sides = new Set(
|
|
1212
|
+
pins.map((pin) => {
|
|
1213
|
+
if (Math.abs(pin!.x - chip.bounds.minX) <= EPS) return "left"
|
|
1214
|
+
if (Math.abs(pin!.x - chip.bounds.maxX) <= EPS) return "right"
|
|
1215
|
+
return null
|
|
1216
|
+
}),
|
|
1217
|
+
)
|
|
1218
|
+
if (sides.size !== 1 || sides.has(null)) return null
|
|
1219
|
+
|
|
1220
|
+
return {
|
|
1221
|
+
chipId,
|
|
1222
|
+
side: [...sides][0] as "left" | "right",
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1001
1226
|
private isPortOnlyLabel(label: NetLabelPlacement) {
|
|
1002
1227
|
return label.mspConnectionPairIds.length === 0
|
|
1003
1228
|
}
|
|
@@ -1007,6 +1232,14 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1007
1232
|
if (i === labelIndex) continue
|
|
1008
1233
|
const label = this.outputNetLabelPlacements[i]!
|
|
1009
1234
|
const otherBounds = getRectBounds(label.center, label.width, label.height)
|
|
1235
|
+
if (
|
|
1236
|
+
i === this.blockedStandaloneLabelIndex &&
|
|
1237
|
+
isYOrientation(label.orientation)
|
|
1238
|
+
) {
|
|
1239
|
+
// Keep later labels one search step away from the blocked label.
|
|
1240
|
+
otherBounds.minX -= LABEL_SEARCH_STEP
|
|
1241
|
+
otherBounds.maxX += LABEL_SEARCH_STEP
|
|
1242
|
+
}
|
|
1010
1243
|
if (rectsOverlap(bounds, otherBounds)) return true
|
|
1011
1244
|
}
|
|
1012
1245
|
return false
|
|
@@ -22,6 +22,7 @@ export type ChipSide = "left" | "right" | "top" | "bottom"
|
|
|
22
22
|
export type CandidateLabel = {
|
|
23
23
|
orientation: FacingDirection
|
|
24
24
|
anchorPoint: Point
|
|
25
|
+
connectorSource?: Point
|
|
25
26
|
center: Point
|
|
26
27
|
width: number
|
|
27
28
|
height: number
|
|
@@ -38,6 +39,7 @@ export type CandidateStatus =
|
|
|
38
39
|
export type CandidatePhase =
|
|
39
40
|
| "rotate"
|
|
40
41
|
| "trace-anchor"
|
|
42
|
+
| "outward-trace-anchor"
|
|
41
43
|
| "shift"
|
|
42
44
|
| "lateral-shift"
|
|
43
45
|
|