@tscircuit/schematic-trace-solver 0.0.160 → 0.0.162
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 +5 -0
- package/dist/index.js +221 -29
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +210 -6
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +28 -21
- package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +3 -3
- package/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts +40 -0
- package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getFixedLabelCoordinate.ts +44 -13
- package/lib/types/InputProblem.ts +3 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +79 -79
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +85 -85
- package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +163 -163
- package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +547 -547
- package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +13 -13
- package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +267 -267
- package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +267 -267
- package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +109 -109
- package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +49 -49
- package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +103 -103
- package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +103 -103
- package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +163 -163
- package/tests/examples/__snapshots__/example51.snap.svg +568 -568
- package/tests/fixtures/convertSolverOutputToCircuitJson.test.ts +16 -1
- package/tests/fixtures/convertSolverOutputToCircuitJson.ts +23 -12
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +736 -736
- package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +79 -79
- package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +127 -127
- package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +115 -0
- package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +31 -31
- package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +121 -121
- package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +43 -43
- package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +19 -19
- package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +277 -0
- package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +19 -19
- package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +7 -7
- package/tests/repros/__snapshots__/repro-dc-power-isolated-routing.snap.svg +37 -37
- package/tests/repros/__snapshots__/repro-hdc2080-ground-label-alignment.snap.svg +137 -0
- package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +61 -61
- package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +115 -115
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +743 -743
- package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +43 -43
- package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +1 -1
- package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +17 -17
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1131 -1131
- package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +7 -7
- package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +7 -7
- package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +666 -666
- package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +73 -73
- package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +73 -73
- package/tests/repros/assets/repro-bluetooth-controller-ground-decoupling-groups.input.json +214 -0
- package/tests/repros/assets/repro-core-ground-inline-label-fallback.input.json +863 -0
- package/tests/repros/repro-bluetooth-controller-ground-decoupling-groups.test.ts +18 -0
- package/tests/repros/repro-core-ground-inline-label-fallback.test.ts +34 -0
- package/tests/repros/repro-hdc2080-ground-label-alignment.test.ts +239 -0
- package/tests/repros/repro-tida010076-j4-ground-bus.test.ts +14 -10
- package/tests/repros/repro-tida010076-page02.test.ts +2 -2
- package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +1 -1
- package/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts +57 -0
package/dist/index.d.ts
CHANGED
|
@@ -141,6 +141,8 @@ interface InputDirectConnection {
|
|
|
141
141
|
interface InputNetConnection {
|
|
142
142
|
netId: string;
|
|
143
143
|
pinIds: Array<PinId>;
|
|
144
|
+
/** True when the authoritative source net is electrical ground. */
|
|
145
|
+
isGround?: boolean;
|
|
144
146
|
/**
|
|
145
147
|
* User-facing text to render for this net label. `netId` remains the stable
|
|
146
148
|
* connectivity identifier and may be an internal id. When this is omitted
|
|
@@ -906,6 +908,9 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
906
908
|
private shouldCheckTraceClearanceForLabel;
|
|
907
909
|
private hasRoutedLabelOnSameNet;
|
|
908
910
|
private isOutwardHorizontalFallback;
|
|
911
|
+
private canPlaceVerticalLabelOnHostRail;
|
|
912
|
+
private getTextBlockedMultiPinRailTraces;
|
|
913
|
+
private findValidNearbyRailCandidate;
|
|
909
914
|
private hasPortOnlyLabelOnSameNet;
|
|
910
915
|
private hasOppositeVerticalRailOnSameChipSide;
|
|
911
916
|
private getSingleChipSideForNetConnection;
|
package/dist/index.js
CHANGED
|
@@ -539,6 +539,30 @@ var getLabeledConnectionRouteReason = ({
|
|
|
539
539
|
};
|
|
540
540
|
var isLabeledPeripheralConnection = (params) => getLabeledConnectionRouteReason(params) !== null;
|
|
541
541
|
|
|
542
|
+
// lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts
|
|
543
|
+
var SAME_RAIL_Y_TOLERANCE = 1e-6;
|
|
544
|
+
var MIN_GROUPED_RAIL_PIN_COUNT = 3;
|
|
545
|
+
function isGroupedHorizontalRail({
|
|
546
|
+
pin,
|
|
547
|
+
netPins
|
|
548
|
+
}) {
|
|
549
|
+
const sameRailPins = netPins.filter(
|
|
550
|
+
(otherPin) => Math.abs(otherPin.y - pin.y) <= SAME_RAIL_Y_TOLERANCE
|
|
551
|
+
);
|
|
552
|
+
const railChipIds = new Set(sameRailPins.map((railPin) => railPin.chipId));
|
|
553
|
+
return railChipIds.size >= MIN_GROUPED_RAIL_PIN_COUNT;
|
|
554
|
+
}
|
|
555
|
+
function shouldSeparateGroundNetRows({
|
|
556
|
+
netConnection,
|
|
557
|
+
netPins,
|
|
558
|
+
pin1,
|
|
559
|
+
pin2
|
|
560
|
+
}) {
|
|
561
|
+
if (!netConnection?.isGround || pin1.chipId === pin2.chipId) return false;
|
|
562
|
+
if (Math.abs(pin1.y - pin2.y) <= SAME_RAIL_Y_TOLERANCE) return false;
|
|
563
|
+
return isGroupedHorizontalRail({ pin: pin1, netPins }) && isGroupedHorizontalRail({ pin: pin2, netPins });
|
|
564
|
+
}
|
|
565
|
+
|
|
542
566
|
// lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts
|
|
543
567
|
var DEFAULT_MAX_MSP_PAIR_DISTANCE = 1;
|
|
544
568
|
var getPinPairKey = (pinIds) => [...pinIds].sort().join("::");
|
|
@@ -643,36 +667,41 @@ var MspConnectionPairSolver = class extends BaseSolver {
|
|
|
643
667
|
return;
|
|
644
668
|
}
|
|
645
669
|
const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1);
|
|
646
|
-
const
|
|
670
|
+
const userNetId2 = this.userNetIdByPinId[pin1] ?? this.userNetIdByPinId[pin2];
|
|
647
671
|
const suppressNetLabel = pairDistance > this.maxMspPairDistance && labeledConnectionRouteReason === "overlapping-fallback-labels";
|
|
648
672
|
this.addMspConnectionPair({
|
|
649
673
|
mspPairId: `${pin1}-${pin2}`,
|
|
650
674
|
dcConnNetId: dcNetId,
|
|
651
675
|
globalConnNetId,
|
|
652
|
-
userNetId,
|
|
676
|
+
userNetId: userNetId2,
|
|
653
677
|
suppressNetLabel,
|
|
654
678
|
pins: [p1, p2]
|
|
655
679
|
});
|
|
656
680
|
return;
|
|
657
681
|
}
|
|
658
682
|
const pinIdMap = new Map(Object.entries(this.pinMap));
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
683
|
+
const userNetId = this.userNetIdByPinId[directlyConnectedPins[0]];
|
|
684
|
+
const netConnection = this.inputProblem.netConnections.find(
|
|
685
|
+
(connection) => connection.netId === userNetId
|
|
686
|
+
);
|
|
687
|
+
const directlyConnectedPinObjects = directlyConnectedPins.map(
|
|
688
|
+
(pinId) => this.pinMap[pinId]
|
|
689
|
+
);
|
|
690
|
+
const msp = getOrthogonalMinimumSpanningTree(directlyConnectedPinObjects, {
|
|
691
|
+
maxDistance: this.maxMspPairDistance,
|
|
692
|
+
forbidEdge: (a, b) => !this.canRouteGroundPair(a.pinId, b.pinId) || shouldSeparateGroundNetRows({
|
|
693
|
+
netConnection,
|
|
694
|
+
netPins: directlyConnectedPinObjects,
|
|
695
|
+
pin1: a,
|
|
696
|
+
pin2: b
|
|
697
|
+
}) || arePinsInDifferentSchematicSections(this.inputProblem, a, b) || doesPairCrossRestrictedCenterLines({
|
|
698
|
+
inputProblem: this.inputProblem,
|
|
699
|
+
chipMap: this.chipMap,
|
|
700
|
+
pinIdMap,
|
|
701
|
+
p1: a,
|
|
702
|
+
p2: b
|
|
703
|
+
})
|
|
704
|
+
});
|
|
676
705
|
for (const [pin1, pin2] of msp) {
|
|
677
706
|
const p1Obj = this.pinMap[pin1];
|
|
678
707
|
const p2Obj = this.pinMap[pin2];
|
|
@@ -689,12 +718,12 @@ var MspConnectionPairSolver = class extends BaseSolver {
|
|
|
689
718
|
continue;
|
|
690
719
|
}
|
|
691
720
|
const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1);
|
|
692
|
-
const
|
|
721
|
+
const pairUserNetId = this.userNetIdByPinId[pin1] ?? this.userNetIdByPinId[pin2];
|
|
693
722
|
this.addMspConnectionPair({
|
|
694
723
|
mspPairId: `${pin1}-${pin2}`,
|
|
695
724
|
dcConnNetId: dcNetId,
|
|
696
725
|
globalConnNetId,
|
|
697
|
-
userNetId,
|
|
726
|
+
userNetId: pairUserNetId,
|
|
698
727
|
pins: [p1Obj, p2Obj]
|
|
699
728
|
});
|
|
700
729
|
}
|
|
@@ -5864,6 +5893,9 @@ var getLabelsLinkedToRailGroup = (group, netLabelPlacements, traces) => {
|
|
|
5864
5893
|
var getFixedLabelCoordinate = (group, netLabelPlacements, traces) => {
|
|
5865
5894
|
const traceMap = new Map(traces.map((trace) => [trace.mspPairId, trace]));
|
|
5866
5895
|
const groupTraceIds = new Set(group.map((segment) => segment.traceId));
|
|
5896
|
+
const groupIsWithinSingleComponent = [...groupTraceIds].every(
|
|
5897
|
+
(traceId) => traceMap.get(traceId)?.pins.every((pin) => pin.chipId === group[0].componentId) === true
|
|
5898
|
+
);
|
|
5867
5899
|
const groupHasOnlySingleRailTraces = [...groupTraceIds].every(
|
|
5868
5900
|
(traceId) => traceMap.get(traceId)?.tracePath.length === 4
|
|
5869
5901
|
);
|
|
@@ -5874,12 +5906,20 @@ var getFixedLabelCoordinate = (group, netLabelPlacements, traces) => {
|
|
|
5874
5906
|
netLabelPlacements,
|
|
5875
5907
|
traces
|
|
5876
5908
|
);
|
|
5877
|
-
const
|
|
5878
|
-
|
|
5909
|
+
const labelsOrientedAlongRail = new Set(
|
|
5910
|
+
linkedLabels.filter(
|
|
5911
|
+
(label) => orientation === "vertical" && (label.orientation === "y+" || label.orientation === "y-") || orientation === "horizontal" && (label.orientation === "x+" || label.orientation === "x-")
|
|
5912
|
+
)
|
|
5913
|
+
);
|
|
5914
|
+
const anchoringLabels = linkedLabels.filter((label) => {
|
|
5915
|
+
if (label.orientation !== group[0].componentFacingDirection && (!groupIsWithinSingleComponent || !labelsOrientedAlongRail.has(label))) {
|
|
5916
|
+
return false;
|
|
5917
|
+
}
|
|
5918
|
+
return label.mspConnectionPairIds.some((traceId) => {
|
|
5879
5919
|
const trace = traceMap.get(traceId);
|
|
5880
5920
|
return trace && tracePathContainsPoint(trace.tracePath, label.anchorPoint);
|
|
5881
|
-
})
|
|
5882
|
-
);
|
|
5921
|
+
});
|
|
5922
|
+
});
|
|
5883
5923
|
const coordinates = getDistinctCoordinates(
|
|
5884
5924
|
anchoringLabels.map(
|
|
5885
5925
|
(label) => orientation === "vertical" ? label.anchorPoint.x : label.anchorPoint.y
|
|
@@ -5896,12 +5936,21 @@ var getFixedLabelCoordinate = (group, netLabelPlacements, traces) => {
|
|
|
5896
5936
|
)
|
|
5897
5937
|
);
|
|
5898
5938
|
if (labelIsAnchoredToRoutedBackbone) return coordinate;
|
|
5939
|
+
if (anchoringLabels.some((label) => labelsOrientedAlongRail.has(label))) {
|
|
5940
|
+
const railGroupSpan = Math.max(...group.map((segment) => segment.maxAlong)) - Math.min(...group.map((segment) => segment.minAlong));
|
|
5941
|
+
const coordinateIsLocalToRailGroup = group.every(
|
|
5942
|
+
(segment) => Math.abs(segment.coordinate - coordinate) <= railGroupSpan + RAIL_ALIGNMENT_EPSILON
|
|
5943
|
+
);
|
|
5944
|
+
if (coordinateIsLocalToRailGroup) return coordinate;
|
|
5945
|
+
return null;
|
|
5946
|
+
}
|
|
5899
5947
|
const coordinateIsLocalToEveryTrace = group.every((segment) => {
|
|
5900
5948
|
const trace = traceMap.get(segment.traceId);
|
|
5901
5949
|
if (!trace) return false;
|
|
5902
5950
|
return Math.abs(segment.coordinate - coordinate) <= distance4(trace.pins[0], trace.pins[1]) + RAIL_ALIGNMENT_EPSILON;
|
|
5903
5951
|
});
|
|
5904
|
-
|
|
5952
|
+
if (coordinateIsLocalToEveryTrace) return coordinate;
|
|
5953
|
+
return null;
|
|
5905
5954
|
};
|
|
5906
5955
|
|
|
5907
5956
|
// lib/solvers/TraceCleanupSolver/sameNetRailAlignment/moveRailSegments.ts
|
|
@@ -8939,6 +8988,9 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8939
8988
|
const orientations = this.getAvailableOrientations(label);
|
|
8940
8989
|
if (orientations.length === 0) return false;
|
|
8941
8990
|
if (!orientations.includes(label.orientation)) return true;
|
|
8991
|
+
if (this.getTextBlockedMultiPinRailTraces(label, orientations[0])) {
|
|
8992
|
+
return true;
|
|
8993
|
+
}
|
|
8942
8994
|
if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false;
|
|
8943
8995
|
const bounds = getRectBounds(label.center, label.width, label.height);
|
|
8944
8996
|
return this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
|
|
@@ -9060,8 +9112,34 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9060
9112
|
);
|
|
9061
9113
|
const isTwoPinNet = netConnection?.pinIds.length === 2;
|
|
9062
9114
|
const isDistanceSplitVerticalRail = (netConnection?.pinIds.length ?? 0) > 2 && isYOrientation(requiredOrientation) && this.hasPortOnlyLabelOnSameNet(label);
|
|
9115
|
+
const textBlockedMultiPinRailTraces = this.getTextBlockedMultiPinRailTraces(
|
|
9116
|
+
label,
|
|
9117
|
+
requiredOrientation
|
|
9118
|
+
);
|
|
9063
9119
|
const isPairedSameSidePowerRail = isYOrientation(requiredOrientation) && this.hasOppositeVerticalRailOnSameChipSide(label, requiredOrientation);
|
|
9120
|
+
if (textBlockedMultiPinRailTraces) {
|
|
9121
|
+
const nearbyCandidate = this.findValidNearbyRailCandidate(
|
|
9122
|
+
label,
|
|
9123
|
+
requiredOrientation,
|
|
9124
|
+
labelIndex,
|
|
9125
|
+
textBlockedMultiPinRailTraces
|
|
9126
|
+
);
|
|
9127
|
+
if (nearbyCandidate) return nearbyCandidate;
|
|
9128
|
+
}
|
|
9064
9129
|
if (orientations.length === 1 && isYOrientation(requiredOrientation) && this.isOutwardHorizontalFallback(label) && this.hasTraceContinuingInOrientation(label, requiredOrientation) && (isDistanceSplitVerticalRail || isPairedSameSidePowerRail)) {
|
|
9130
|
+
const hostRailCandidate = this.findValidTraceAnchorCandidate(
|
|
9131
|
+
label,
|
|
9132
|
+
requiredOrientation,
|
|
9133
|
+
labelIndex
|
|
9134
|
+
);
|
|
9135
|
+
if (hostRailCandidate) {
|
|
9136
|
+
const canUseHostRail = this.canPlaceVerticalLabelOnHostRail(
|
|
9137
|
+
hostRailCandidate,
|
|
9138
|
+
label
|
|
9139
|
+
);
|
|
9140
|
+
if (canUseHostRail) return hostRailCandidate;
|
|
9141
|
+
hostRailCandidate.selected = false;
|
|
9142
|
+
}
|
|
9065
9143
|
const traceAnchorCandidate2 = this.findValidOutwardTraceAnchorCandidate(
|
|
9066
9144
|
label,
|
|
9067
9145
|
requiredOrientation,
|
|
@@ -9495,14 +9573,21 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9495
9573
|
maxSearchDistance,
|
|
9496
9574
|
outwardDistance,
|
|
9497
9575
|
phase = "shift",
|
|
9498
|
-
stopOnTraceCollision = true
|
|
9576
|
+
stopOnTraceCollision = true,
|
|
9577
|
+
connectorSource,
|
|
9578
|
+
startDistance = LABEL_SEARCH_STEP
|
|
9499
9579
|
} = params;
|
|
9500
|
-
for (let distance7 =
|
|
9580
|
+
for (let distance7 = startDistance; distance7 <= maxSearchDistance + EPS12; distance7 += LABEL_SEARCH_STEP) {
|
|
9501
9581
|
const anchorPoint = {
|
|
9502
9582
|
x: baseAnchor.x + direction.x * distance7,
|
|
9503
9583
|
y: baseAnchor.y + direction.y * distance7
|
|
9504
9584
|
};
|
|
9505
|
-
const candidate = this.createCandidate(
|
|
9585
|
+
const candidate = this.createCandidate(
|
|
9586
|
+
label,
|
|
9587
|
+
anchorPoint,
|
|
9588
|
+
orientation,
|
|
9589
|
+
connectorSource
|
|
9590
|
+
);
|
|
9506
9591
|
const result = this.evaluateCandidate(
|
|
9507
9592
|
candidate,
|
|
9508
9593
|
label,
|
|
@@ -9878,6 +9963,113 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9878
9963
|
const chipSide = this.getChipSideForPoint(label.anchorPoint);
|
|
9879
9964
|
return chipSide === "left" && label.orientation === "x-" || chipSide === "right" && label.orientation === "x+";
|
|
9880
9965
|
}
|
|
9966
|
+
canPlaceVerticalLabelOnHostRail(candidate, label) {
|
|
9967
|
+
const isOnHostRail = label.mspConnectionPairIds.some((traceId) => {
|
|
9968
|
+
const trace = this.traceMap[traceId];
|
|
9969
|
+
return trace && tracePathContainsPoint(trace.tracePath, candidate.anchorPoint);
|
|
9970
|
+
});
|
|
9971
|
+
if (!isOnHostRail) return false;
|
|
9972
|
+
const hostPin = this.pinMap[label.pinIds[0]];
|
|
9973
|
+
if (!hostPin) return false;
|
|
9974
|
+
if (label.pinIds.some(
|
|
9975
|
+
(pinId) => this.pinMap[pinId]?.chipId !== hostPin.chipId
|
|
9976
|
+
)) {
|
|
9977
|
+
return false;
|
|
9978
|
+
}
|
|
9979
|
+
const chip = this.chipObstacleSpatialIndex.chips.find(
|
|
9980
|
+
(chip2) => chip2.chipId === hostPin.chipId
|
|
9981
|
+
);
|
|
9982
|
+
if (!chip) return false;
|
|
9983
|
+
const labelBounds = getRectBounds(
|
|
9984
|
+
candidate.center,
|
|
9985
|
+
candidate.width,
|
|
9986
|
+
candidate.height
|
|
9987
|
+
);
|
|
9988
|
+
if (labelBounds.minY < chip.bounds.minY - EPS12 || labelBounds.maxY > chip.bounds.maxY + EPS12) {
|
|
9989
|
+
return false;
|
|
9990
|
+
}
|
|
9991
|
+
if (candidate.anchorPoint.x >= chip.bounds.minX - EPS12 && candidate.anchorPoint.x <= chip.bounds.maxX + EPS12) {
|
|
9992
|
+
return false;
|
|
9993
|
+
}
|
|
9994
|
+
return chip.pins.every(
|
|
9995
|
+
(pin) => label.pinIds.includes(pin.pinId) || pin.y <= labelBounds.minY + EPS12 || pin.y >= labelBounds.maxY - EPS12
|
|
9996
|
+
);
|
|
9997
|
+
}
|
|
9998
|
+
getTextBlockedMultiPinRailTraces(label, orientation) {
|
|
9999
|
+
if (!isYOrientation(orientation)) return null;
|
|
10000
|
+
const componentId = this.pinMap[label.pinIds[0]]?.chipId;
|
|
10001
|
+
const localTraces = Object.values(this.traceMap).filter(
|
|
10002
|
+
(trace) => trace.globalConnNetId === label.globalConnNetId && trace.pins.every((pin) => pin.chipId === componentId)
|
|
10003
|
+
);
|
|
10004
|
+
const connectedPinIds = new Set(label.pinIds);
|
|
10005
|
+
const connectedTraces = /* @__PURE__ */ new Set();
|
|
10006
|
+
for (let changed = true; changed; ) {
|
|
10007
|
+
changed = false;
|
|
10008
|
+
for (const trace of localTraces) {
|
|
10009
|
+
if (connectedTraces.has(trace)) continue;
|
|
10010
|
+
if (!trace.pinIds.some((pinId) => connectedPinIds.has(pinId))) continue;
|
|
10011
|
+
connectedTraces.add(trace);
|
|
10012
|
+
for (const pinId of trace.pinIds) connectedPinIds.add(pinId);
|
|
10013
|
+
changed = true;
|
|
10014
|
+
}
|
|
10015
|
+
}
|
|
10016
|
+
if (connectedPinIds.size <= 3) return null;
|
|
10017
|
+
const pins = [...connectedPinIds].map((pinId) => this.pinMap[pinId]);
|
|
10018
|
+
const direction = dir(orientation);
|
|
10019
|
+
const pinX = pins[0].x;
|
|
10020
|
+
if (pins.some((pin) => Math.abs(pin.x - pinX) > EPS12)) return null;
|
|
10021
|
+
const sidePins = this.inputProblem.chips.find((chip) => chip.chipId === componentId).pins.filter((pin) => Math.abs(pin.x - pinX) <= EPS12);
|
|
10022
|
+
const minY = Math.min(...pins.map((pin) => pin.y));
|
|
10023
|
+
const maxY = Math.max(...pins.map((pin) => pin.y));
|
|
10024
|
+
if (sidePins.some(
|
|
10025
|
+
(pin) => pin.y >= minY - EPS12 && pin.y <= maxY + EPS12 && !connectedPinIds.has(pin.pinId)
|
|
10026
|
+
) || orientation === "y-" && minY > Math.min(...sidePins.map((pin) => pin.y)) + EPS12 || orientation === "y+" && maxY < Math.max(...sidePins.map((pin) => pin.y)) - EPS12) {
|
|
10027
|
+
return null;
|
|
10028
|
+
}
|
|
10029
|
+
const outerPinPosition = Math.max(
|
|
10030
|
+
...pins.map((pin) => pin.x * direction.x + pin.y * direction.y)
|
|
10031
|
+
);
|
|
10032
|
+
const blockedByText = (this.inputProblem.textBoxes ?? []).some(
|
|
10033
|
+
(textBox) => textBox.chipId === componentId && textBox.center.x * direction.x + textBox.center.y * direction.y > outerPinPosition + EPS12
|
|
10034
|
+
);
|
|
10035
|
+
return blockedByText ? [...connectedTraces] : null;
|
|
10036
|
+
}
|
|
10037
|
+
findValidNearbyRailCandidate(label, orientation, labelIndex, localTraces) {
|
|
10038
|
+
if (!isYOrientation(orientation)) return null;
|
|
10039
|
+
const direction = dir(orientation);
|
|
10040
|
+
const connectorY = [
|
|
10041
|
+
...new Set(localTraces.flatMap((trace) => trace.pinIds))
|
|
10042
|
+
].map((pinId) => this.pinMap[pinId]).sort((a, b) => b.y * direction.y - a.y * direction.y)[0]?.y;
|
|
10043
|
+
if (connectorY === void 0) return null;
|
|
10044
|
+
const pinX = this.pinMap[label.pinIds[0]].x;
|
|
10045
|
+
const railXs = [
|
|
10046
|
+
...new Set(
|
|
10047
|
+
localTraces.flatMap((trace) => trace.tracePath.map((point) => point.x))
|
|
10048
|
+
)
|
|
10049
|
+
].filter((x) => Math.abs(x - pinX) > EPS12).sort((a, b) => Math.abs(a - pinX) - Math.abs(b - pinX));
|
|
10050
|
+
for (const x of railXs) {
|
|
10051
|
+
const connectorSource = { x, y: connectorY };
|
|
10052
|
+
if (!Object.values(this.traceMap).some(
|
|
10053
|
+
(trace) => trace.globalConnNetId === label.globalConnNetId && tracePathContainsPoint(trace.tracePath, connectorSource)
|
|
10054
|
+
)) {
|
|
10055
|
+
continue;
|
|
10056
|
+
}
|
|
10057
|
+
const candidate = this.findValidCandidateInShiftColumn({
|
|
10058
|
+
label,
|
|
10059
|
+
labelIndex,
|
|
10060
|
+
orientation,
|
|
10061
|
+
direction,
|
|
10062
|
+
baseAnchor: connectorSource,
|
|
10063
|
+
maxSearchDistance: this.maxSearchDistance,
|
|
10064
|
+
outwardDistance: 0,
|
|
10065
|
+
stopOnTraceCollision: false,
|
|
10066
|
+
connectorSource,
|
|
10067
|
+
startDistance: 0
|
|
10068
|
+
});
|
|
10069
|
+
if (candidate) return candidate;
|
|
10070
|
+
}
|
|
10071
|
+
return null;
|
|
10072
|
+
}
|
|
9881
10073
|
hasPortOnlyLabelOnSameNet(label) {
|
|
9882
10074
|
return this.outputNetLabelPlacements.some(
|
|
9883
10075
|
(otherLabel) => otherLabel !== label && otherLabel.globalConnNetId === label.globalConnNetId && this.isPortOnlyLabel(otherLabel)
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getDimsForOrientation,
|
|
9
9
|
getRectBounds,
|
|
10
10
|
} from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
11
|
+
import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
|
|
11
12
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
12
13
|
import type {
|
|
13
14
|
ChipId,
|
|
@@ -250,6 +251,9 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
250
251
|
const orientations = this.getAvailableOrientations(label)
|
|
251
252
|
if (orientations.length === 0) return false
|
|
252
253
|
if (!orientations.includes(label.orientation)) return true
|
|
254
|
+
if (this.getTextBlockedMultiPinRailTraces(label, orientations[0]!)) {
|
|
255
|
+
return true
|
|
256
|
+
}
|
|
253
257
|
if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false
|
|
254
258
|
|
|
255
259
|
const bounds = getRectBounds(label.center, label.width, label.height)
|
|
@@ -407,9 +411,22 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
407
411
|
(netConnection?.pinIds.length ?? 0) > 2 &&
|
|
408
412
|
isYOrientation(requiredOrientation) &&
|
|
409
413
|
this.hasPortOnlyLabelOnSameNet(label)
|
|
414
|
+
const textBlockedMultiPinRailTraces = this.getTextBlockedMultiPinRailTraces(
|
|
415
|
+
label,
|
|
416
|
+
requiredOrientation,
|
|
417
|
+
)
|
|
410
418
|
const isPairedSameSidePowerRail =
|
|
411
419
|
isYOrientation(requiredOrientation) &&
|
|
412
420
|
this.hasOppositeVerticalRailOnSameChipSide(label, requiredOrientation)
|
|
421
|
+
if (textBlockedMultiPinRailTraces) {
|
|
422
|
+
const nearbyCandidate = this.findValidNearbyRailCandidate(
|
|
423
|
+
label,
|
|
424
|
+
requiredOrientation,
|
|
425
|
+
labelIndex,
|
|
426
|
+
textBlockedMultiPinRailTraces,
|
|
427
|
+
)
|
|
428
|
+
if (nearbyCandidate) return nearbyCandidate
|
|
429
|
+
}
|
|
413
430
|
if (
|
|
414
431
|
orientations.length === 1 &&
|
|
415
432
|
isYOrientation(requiredOrientation) &&
|
|
@@ -417,9 +434,22 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
417
434
|
this.hasTraceContinuingInOrientation(label, requiredOrientation) &&
|
|
418
435
|
(isDistanceSplitVerticalRail || isPairedSameSidePowerRail)
|
|
419
436
|
) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
437
|
+
const hostRailCandidate = this.findValidTraceAnchorCandidate(
|
|
438
|
+
label,
|
|
439
|
+
requiredOrientation,
|
|
440
|
+
labelIndex,
|
|
441
|
+
)
|
|
442
|
+
if (hostRailCandidate) {
|
|
443
|
+
const canUseHostRail = this.canPlaceVerticalLabelOnHostRail(
|
|
444
|
+
hostRailCandidate,
|
|
445
|
+
label,
|
|
446
|
+
)
|
|
447
|
+
if (canUseHostRail) return hostRailCandidate
|
|
448
|
+
hostRailCandidate.selected = false
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Keep the established outward column when the direct rail end is not a
|
|
452
|
+
// clear segment of the connected component side.
|
|
423
453
|
const traceAnchorCandidate = this.findValidOutwardTraceAnchorCandidate(
|
|
424
454
|
label,
|
|
425
455
|
requiredOrientation,
|
|
@@ -785,7 +815,6 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
785
815
|
connectedPinIds.add(pinId)
|
|
786
816
|
}
|
|
787
817
|
}
|
|
788
|
-
|
|
789
818
|
// Multi-pin rails are routed as a chain of pairwise traces. Follow the
|
|
790
819
|
// pairs only when they share both a pin and an aligned vertical rail,
|
|
791
820
|
// which identifies one continuous rail. A shared pin alone can join
|
|
@@ -1012,6 +1041,8 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1012
1041
|
outwardDistance: number
|
|
1013
1042
|
phase?: CandidatePhase
|
|
1014
1043
|
stopOnTraceCollision?: boolean
|
|
1044
|
+
connectorSource?: Point
|
|
1045
|
+
startDistance?: number
|
|
1015
1046
|
}) {
|
|
1016
1047
|
const {
|
|
1017
1048
|
label,
|
|
@@ -1023,10 +1054,12 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1023
1054
|
outwardDistance,
|
|
1024
1055
|
phase = "shift",
|
|
1025
1056
|
stopOnTraceCollision = true,
|
|
1057
|
+
connectorSource,
|
|
1058
|
+
startDistance = LABEL_SEARCH_STEP,
|
|
1026
1059
|
} = params
|
|
1027
1060
|
|
|
1028
1061
|
for (
|
|
1029
|
-
let distance =
|
|
1062
|
+
let distance = startDistance;
|
|
1030
1063
|
distance <= maxSearchDistance + EPS;
|
|
1031
1064
|
distance += LABEL_SEARCH_STEP
|
|
1032
1065
|
) {
|
|
@@ -1034,7 +1067,12 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1034
1067
|
x: baseAnchor.x + direction.x * distance,
|
|
1035
1068
|
y: baseAnchor.y + direction.y * distance,
|
|
1036
1069
|
}
|
|
1037
|
-
const candidate = this.createCandidate(
|
|
1070
|
+
const candidate = this.createCandidate(
|
|
1071
|
+
label,
|
|
1072
|
+
anchorPoint,
|
|
1073
|
+
orientation,
|
|
1074
|
+
connectorSource,
|
|
1075
|
+
)
|
|
1038
1076
|
const result = this.evaluateCandidate(
|
|
1039
1077
|
candidate,
|
|
1040
1078
|
label,
|
|
@@ -1563,6 +1601,172 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1563
1601
|
)
|
|
1564
1602
|
}
|
|
1565
1603
|
|
|
1604
|
+
private canPlaceVerticalLabelOnHostRail(
|
|
1605
|
+
candidate: CandidateLabel,
|
|
1606
|
+
label: NetLabelPlacement,
|
|
1607
|
+
) {
|
|
1608
|
+
const isOnHostRail = label.mspConnectionPairIds.some((traceId) => {
|
|
1609
|
+
const trace = this.traceMap[traceId]
|
|
1610
|
+
return (
|
|
1611
|
+
trace && tracePathContainsPoint(trace.tracePath, candidate.anchorPoint)
|
|
1612
|
+
)
|
|
1613
|
+
})
|
|
1614
|
+
if (!isOnHostRail) return false
|
|
1615
|
+
|
|
1616
|
+
const hostPin = this.pinMap[label.pinIds[0]!]
|
|
1617
|
+
if (!hostPin) return false
|
|
1618
|
+
if (
|
|
1619
|
+
label.pinIds.some(
|
|
1620
|
+
(pinId) => this.pinMap[pinId]?.chipId !== hostPin.chipId,
|
|
1621
|
+
)
|
|
1622
|
+
) {
|
|
1623
|
+
return false
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
const chip = this.chipObstacleSpatialIndex.chips.find(
|
|
1627
|
+
(chip) => chip.chipId === hostPin.chipId,
|
|
1628
|
+
)
|
|
1629
|
+
if (!chip) return false
|
|
1630
|
+
|
|
1631
|
+
const labelBounds = getRectBounds(
|
|
1632
|
+
candidate.center,
|
|
1633
|
+
candidate.width,
|
|
1634
|
+
candidate.height,
|
|
1635
|
+
)
|
|
1636
|
+
if (
|
|
1637
|
+
labelBounds.minY < chip.bounds.minY - EPS ||
|
|
1638
|
+
labelBounds.maxY > chip.bounds.maxY + EPS
|
|
1639
|
+
) {
|
|
1640
|
+
return false
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
if (
|
|
1644
|
+
candidate.anchorPoint.x >= chip.bounds.minX - EPS &&
|
|
1645
|
+
candidate.anchorPoint.x <= chip.bounds.maxX + EPS
|
|
1646
|
+
) {
|
|
1647
|
+
return false
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
return chip.pins.every(
|
|
1651
|
+
(pin) =>
|
|
1652
|
+
label.pinIds.includes(pin.pinId) ||
|
|
1653
|
+
pin.y <= labelBounds.minY + EPS ||
|
|
1654
|
+
pin.y >= labelBounds.maxY - EPS,
|
|
1655
|
+
)
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
private getTextBlockedMultiPinRailTraces(
|
|
1659
|
+
label: NetLabelPlacement,
|
|
1660
|
+
orientation: FacingDirection,
|
|
1661
|
+
) {
|
|
1662
|
+
if (!isYOrientation(orientation)) return null
|
|
1663
|
+
const componentId = this.pinMap[label.pinIds[0]!]?.chipId
|
|
1664
|
+
const localTraces = Object.values(this.traceMap).filter(
|
|
1665
|
+
(trace) =>
|
|
1666
|
+
trace.globalConnNetId === label.globalConnNetId &&
|
|
1667
|
+
trace.pins.every((pin) => pin.chipId === componentId),
|
|
1668
|
+
)
|
|
1669
|
+
const connectedPinIds = new Set(label.pinIds)
|
|
1670
|
+
const connectedTraces = new Set<SolvedTracePath>()
|
|
1671
|
+
for (let changed = true; changed; ) {
|
|
1672
|
+
changed = false
|
|
1673
|
+
for (const trace of localTraces) {
|
|
1674
|
+
if (connectedTraces.has(trace)) continue
|
|
1675
|
+
if (!trace.pinIds.some((pinId) => connectedPinIds.has(pinId))) continue
|
|
1676
|
+
connectedTraces.add(trace)
|
|
1677
|
+
for (const pinId of trace.pinIds) connectedPinIds.add(pinId)
|
|
1678
|
+
changed = true
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
if (connectedPinIds.size <= 3) return null
|
|
1682
|
+
|
|
1683
|
+
const pins = [...connectedPinIds].map((pinId) => this.pinMap[pinId]!)
|
|
1684
|
+
const direction = dir(orientation)
|
|
1685
|
+
const pinX = pins[0]!.x
|
|
1686
|
+
if (pins.some((pin) => Math.abs(pin.x - pinX) > EPS)) return null
|
|
1687
|
+
const sidePins = this.inputProblem.chips
|
|
1688
|
+
.find((chip) => chip.chipId === componentId)!
|
|
1689
|
+
.pins.filter((pin) => Math.abs(pin.x - pinX) <= EPS)
|
|
1690
|
+
const minY = Math.min(...pins.map((pin) => pin.y))
|
|
1691
|
+
const maxY = Math.max(...pins.map((pin) => pin.y))
|
|
1692
|
+
if (
|
|
1693
|
+
sidePins.some(
|
|
1694
|
+
(pin) =>
|
|
1695
|
+
pin.y >= minY - EPS &&
|
|
1696
|
+
pin.y <= maxY + EPS &&
|
|
1697
|
+
!connectedPinIds.has(pin.pinId),
|
|
1698
|
+
) ||
|
|
1699
|
+
(orientation === "y-" &&
|
|
1700
|
+
minY > Math.min(...sidePins.map((pin) => pin.y)) + EPS) ||
|
|
1701
|
+
(orientation === "y+" &&
|
|
1702
|
+
maxY < Math.max(...sidePins.map((pin) => pin.y)) - EPS)
|
|
1703
|
+
) {
|
|
1704
|
+
return null
|
|
1705
|
+
}
|
|
1706
|
+
const outerPinPosition = Math.max(
|
|
1707
|
+
...pins.map((pin) => pin.x * direction.x + pin.y * direction.y),
|
|
1708
|
+
)
|
|
1709
|
+
const blockedByText = (this.inputProblem.textBoxes ?? []).some(
|
|
1710
|
+
(textBox) =>
|
|
1711
|
+
textBox.chipId === componentId &&
|
|
1712
|
+
textBox.center.x * direction.x + textBox.center.y * direction.y >
|
|
1713
|
+
outerPinPosition + EPS,
|
|
1714
|
+
)
|
|
1715
|
+
return blockedByText ? [...connectedTraces] : null
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
private findValidNearbyRailCandidate(
|
|
1719
|
+
label: NetLabelPlacement,
|
|
1720
|
+
orientation: FacingDirection,
|
|
1721
|
+
labelIndex: number,
|
|
1722
|
+
localTraces: SolvedTracePath[],
|
|
1723
|
+
) {
|
|
1724
|
+
if (!isYOrientation(orientation)) return null
|
|
1725
|
+
const direction = dir(orientation)
|
|
1726
|
+
const connectorY = [
|
|
1727
|
+
...new Set(localTraces.flatMap((trace) => trace.pinIds)),
|
|
1728
|
+
]
|
|
1729
|
+
.map((pinId) => this.pinMap[pinId]!)
|
|
1730
|
+
.sort((a, b) => b.y * direction.y - a.y * direction.y)[0]?.y
|
|
1731
|
+
if (connectorY === undefined) return null
|
|
1732
|
+
|
|
1733
|
+
const pinX = this.pinMap[label.pinIds[0]!]!.x
|
|
1734
|
+
const railXs = [
|
|
1735
|
+
...new Set(
|
|
1736
|
+
localTraces.flatMap((trace) => trace.tracePath.map((point) => point.x)),
|
|
1737
|
+
),
|
|
1738
|
+
]
|
|
1739
|
+
.filter((x) => Math.abs(x - pinX) > EPS)
|
|
1740
|
+
.sort((a, b) => Math.abs(a - pinX) - Math.abs(b - pinX))
|
|
1741
|
+
|
|
1742
|
+
for (const x of railXs) {
|
|
1743
|
+
const connectorSource = { x, y: connectorY }
|
|
1744
|
+
if (
|
|
1745
|
+
!Object.values(this.traceMap).some(
|
|
1746
|
+
(trace) =>
|
|
1747
|
+
trace.globalConnNetId === label.globalConnNetId &&
|
|
1748
|
+
tracePathContainsPoint(trace.tracePath, connectorSource),
|
|
1749
|
+
)
|
|
1750
|
+
) {
|
|
1751
|
+
continue
|
|
1752
|
+
}
|
|
1753
|
+
const candidate = this.findValidCandidateInShiftColumn({
|
|
1754
|
+
label,
|
|
1755
|
+
labelIndex,
|
|
1756
|
+
orientation,
|
|
1757
|
+
direction,
|
|
1758
|
+
baseAnchor: connectorSource,
|
|
1759
|
+
maxSearchDistance: this.maxSearchDistance,
|
|
1760
|
+
outwardDistance: 0,
|
|
1761
|
+
stopOnTraceCollision: false,
|
|
1762
|
+
connectorSource,
|
|
1763
|
+
startDistance: 0,
|
|
1764
|
+
})
|
|
1765
|
+
if (candidate) return candidate
|
|
1766
|
+
}
|
|
1767
|
+
return null
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1566
1770
|
private hasPortOnlyLabelOnSameNet(label: NetLabelPlacement) {
|
|
1567
1771
|
return this.outputNetLabelPlacements.some(
|
|
1568
1772
|
(otherLabel) =>
|