@tscircuit/schematic-trace-solver 0.0.158 → 0.0.159
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 +1 -0
- package/dist/index.js +126 -3
- package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +15 -8
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +5 -0
- package/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts +83 -0
- package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +6 -1
- package/lib/solvers/SameNetJunctionAlignmentSolver/placeGroundRailLabelsAtOuterEnd.ts +122 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +71 -62
- package/tests/fixtures/parallel-ground-rail.ts +53 -0
- package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +34 -16
- package/tests/repros/repro-mspm0l1306-capacitor-symmetry.test.ts +40 -7
- package/tests/solvers/MspConnectionPairSolver/local-ground-branches.test.ts +105 -0
- package/tests/solvers/SameNetJunctionAlignmentSolver/ground-rail-label.test.ts +188 -0
package/dist/index.d.ts
CHANGED
|
@@ -202,6 +202,7 @@ declare class MspConnectionPairSolver extends BaseSolver {
|
|
|
202
202
|
}>;
|
|
203
203
|
userNetIdByPinId: Record<string, string | undefined>;
|
|
204
204
|
directConnectionPinPairKeys: Set<string>;
|
|
205
|
+
private canRouteGroundPair;
|
|
205
206
|
constructor({ inputProblem }: {
|
|
206
207
|
inputProblem: InputProblem;
|
|
207
208
|
});
|
package/dist/index.js
CHANGED
|
@@ -363,6 +363,48 @@ var getConnectivityMapsFromInputProblem = (inputProblem) => {
|
|
|
363
363
|
return { directConnMap, netConnMap };
|
|
364
364
|
};
|
|
365
365
|
|
|
366
|
+
// lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts
|
|
367
|
+
import { ConnectivityMap as ConnectivityMap2 } from "connectivity-map";
|
|
368
|
+
var MAX_LOCAL_GROUND_BRANCH_OFFSET = 1;
|
|
369
|
+
var getGroundConnectionPolicy = (inputProblem) => {
|
|
370
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
|
|
371
|
+
const groundNetId = netConnMap.getNetConnectedToId("GND");
|
|
372
|
+
if (!groundNetId) return () => true;
|
|
373
|
+
const physicalConnMap = new ConnectivityMap2({});
|
|
374
|
+
for (const connection of inputProblem.directConnections) {
|
|
375
|
+
physicalConnMap.addConnections([connection.pinIds]);
|
|
376
|
+
}
|
|
377
|
+
const pins = new Map(
|
|
378
|
+
inputProblem.chips.flatMap(
|
|
379
|
+
(chip) => chip.pins.map((pin) => [pin.pinId, { pin, chip }])
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
const groundFacingPins = [...pins.values()].filter(
|
|
383
|
+
({ pin, chip }) => chip.pins.length === 2 && netConnMap.getNetConnectedToId(pin.pinId) === groundNetId && (pin._facingDirection ?? getPinDirection(pin, chip)) === "y-"
|
|
384
|
+
);
|
|
385
|
+
const hasExplicitParallelGroundRail = groundFacingPins.some(({ pin: a }) => {
|
|
386
|
+
const group = physicalConnMap.getNetConnectedToId(a.pinId);
|
|
387
|
+
return group !== void 0 && groundFacingPins.some(
|
|
388
|
+
({ pin: b }) => a.pinId !== b.pinId && Math.abs(a.y - b.y) < 1e-6 && Math.abs(a.x - b.x) > 1e-6 && physicalConnMap.getNetConnectedToId(b.pinId) === group
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
if (!hasExplicitParallelGroundRail) return () => true;
|
|
392
|
+
const isGroundFacingTerminal = (pinId) => {
|
|
393
|
+
const entry = pins.get(pinId);
|
|
394
|
+
return entry?.chip.pins.length === 2 && !physicalConnMap.getNetConnectedToId(pinId) && (entry.pin._facingDirection ?? getPinDirection(entry.pin, entry.chip)) === "y-";
|
|
395
|
+
};
|
|
396
|
+
return (firstPinId, secondPinId) => {
|
|
397
|
+
if (!groundNetId || netConnMap.getNetConnectedToId(firstPinId) !== groundNetId || netConnMap.getNetConnectedToId(secondPinId) !== groundNetId) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
if (!isGroundFacingTerminal(firstPinId) && !isGroundFacingTerminal(secondPinId))
|
|
401
|
+
return true;
|
|
402
|
+
const first = pins.get(firstPinId)?.pin;
|
|
403
|
+
const second = pins.get(secondPinId)?.pin;
|
|
404
|
+
return !first || !second || Math.abs(first.y - second.y) <= MAX_LOCAL_GROUND_BRANCH_OFFSET + 1e-6;
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
|
|
366
408
|
// lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts
|
|
367
409
|
function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
|
|
368
410
|
const n = pins.length;
|
|
@@ -512,9 +554,11 @@ var MspConnectionPairSolver = class extends BaseSolver {
|
|
|
512
554
|
pinMap;
|
|
513
555
|
userNetIdByPinId;
|
|
514
556
|
directConnectionPinPairKeys;
|
|
557
|
+
canRouteGroundPair;
|
|
515
558
|
constructor({ inputProblem }) {
|
|
516
559
|
super();
|
|
517
560
|
this.inputProblem = inputProblem;
|
|
561
|
+
this.canRouteGroundPair = getGroundConnectionPolicy(inputProblem);
|
|
518
562
|
this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE;
|
|
519
563
|
const { directConnMap, netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
|
|
520
564
|
this.dcConnMap = directConnMap;
|
|
@@ -571,6 +615,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
|
|
|
571
615
|
const [pin1, pin2] = directlyConnectedPins;
|
|
572
616
|
const p1 = this.pinMap[pin1];
|
|
573
617
|
const p2 = this.pinMap[pin2];
|
|
618
|
+
if (!this.canRouteGroundPair(pin1, pin2)) return;
|
|
574
619
|
const pinPairKey = getPinPairKey([pin1, pin2]);
|
|
575
620
|
let pairDistance = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
|
|
576
621
|
if (this.directConnectionPinPairKeys.has(pinPairKey)) {
|
|
@@ -615,7 +660,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
|
|
|
615
660
|
directlyConnectedPins.map((p) => this.pinMap[p]).filter(Boolean),
|
|
616
661
|
{
|
|
617
662
|
maxDistance: this.maxMspPairDistance,
|
|
618
|
-
forbidEdge: (a, b) => arePinsInDifferentSchematicSections(
|
|
663
|
+
forbidEdge: (a, b) => !this.canRouteGroundPair(a.pinId, b.pinId) || arePinsInDifferentSchematicSections(
|
|
619
664
|
this.inputProblem,
|
|
620
665
|
a,
|
|
621
666
|
b
|
|
@@ -4827,6 +4872,7 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
4827
4872
|
super();
|
|
4828
4873
|
this.params = params;
|
|
4829
4874
|
const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
|
|
4875
|
+
const canRouteGroundPair = getGroundConnectionPolicy(inputProblem);
|
|
4830
4876
|
this.inputProblem = inputProblem;
|
|
4831
4877
|
this.allSolvedTraces = [...alreadySolvedTraces];
|
|
4832
4878
|
this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE;
|
|
@@ -4845,7 +4891,10 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
4845
4891
|
}
|
|
4846
4892
|
}
|
|
4847
4893
|
this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
|
|
4848
|
-
(connectionPair) =>
|
|
4894
|
+
(connectionPair) => canRouteGroundPair(
|
|
4895
|
+
connectionPair.pins[0].pinId,
|
|
4896
|
+
connectionPair.pins[1].pinId
|
|
4897
|
+
) && isLabeledPeripheralConnection({
|
|
4849
4898
|
inputProblem: this.inputProblem,
|
|
4850
4899
|
chipMap: this.chipMap,
|
|
4851
4900
|
pins: connectionPair.pins
|
|
@@ -4865,6 +4914,7 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
4865
4914
|
const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
|
|
4866
4915
|
const targetPin = pinMap.get(otherPinId);
|
|
4867
4916
|
if (!targetPin) return [];
|
|
4917
|
+
if (!canRouteGroundPair(sourcePin.pinId, targetPin.pinId)) return [];
|
|
4868
4918
|
const isNamedTwoPinConnection = inputProblem.netConnections.some(
|
|
4869
4919
|
(connection) => connection.pinIds.length === 2 && connection.pinIds.includes(sourcePin.pinId) && connection.pinIds.includes(targetPin.pinId)
|
|
4870
4920
|
);
|
|
@@ -12731,6 +12781,75 @@ var alignSameNetJunctions = ({
|
|
|
12731
12781
|
};
|
|
12732
12782
|
};
|
|
12733
12783
|
|
|
12784
|
+
// lib/solvers/SameNetJunctionAlignmentSolver/placeGroundRailLabelsAtOuterEnd.ts
|
|
12785
|
+
var placeGroundRailLabelsAtOuterEnd = ({
|
|
12786
|
+
inputProblem,
|
|
12787
|
+
traces,
|
|
12788
|
+
netLabelPlacements
|
|
12789
|
+
}) => {
|
|
12790
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
|
|
12791
|
+
const groundNetId = netConnMap.getNetConnectedToId("GND");
|
|
12792
|
+
if (!groundNetId) return netLabelPlacements;
|
|
12793
|
+
const chipMap = new Map(inputProblem.chips.map((chip) => [chip.chipId, chip]));
|
|
12794
|
+
const traceMap = Object.fromEntries(
|
|
12795
|
+
traces.map((trace) => [trace.mspPairId, trace])
|
|
12796
|
+
);
|
|
12797
|
+
const obstacles = getObstacleRects(inputProblem);
|
|
12798
|
+
const output = [...netLabelPlacements];
|
|
12799
|
+
for (const rail of traces) {
|
|
12800
|
+
if (rail.globalConnNetId !== groundNetId) continue;
|
|
12801
|
+
const [a, b] = rail.pins;
|
|
12802
|
+
if (a.chipId === b.chipId || !nearlyEqual(a.y, b.y) || ![a, b].every(
|
|
12803
|
+
(pin) => pin._facingDirection === "y-" && chipMap.get(pin.chipId)?.pins.length === 2
|
|
12804
|
+
))
|
|
12805
|
+
continue;
|
|
12806
|
+
const path = simplifyPath(rail.tracePath);
|
|
12807
|
+
if (path.length !== 4 || !isVertical2(path[0], path[1]) || !isHorizontal2(path[1], path[2]) || !isVertical2(path[2], path[3]) || path[1].y >= a.y)
|
|
12808
|
+
continue;
|
|
12809
|
+
for (const feed of traces) {
|
|
12810
|
+
if (feed.globalConnNetId !== groundNetId) continue;
|
|
12811
|
+
const shared = feed.pins.find((pin) => rail.pinIds.includes(pin.pinId));
|
|
12812
|
+
const icPin = feed.pins.find(
|
|
12813
|
+
(pin) => !rail.pinIds.includes(pin.pinId) && (chipMap.get(pin.chipId)?.pins.length ?? 0) > 2
|
|
12814
|
+
);
|
|
12815
|
+
if (!shared || !icPin || !nearlyEqual(icPin.y, path[1].y)) continue;
|
|
12816
|
+
const outer = shared.pinId === a.pinId ? b : a;
|
|
12817
|
+
if (Math.abs(outer.x - icPin.x) <= Math.abs(shared.x - icPin.x)) continue;
|
|
12818
|
+
const anchorPoint = { x: outer.x, y: path[1].y };
|
|
12819
|
+
if (!tracePathContainsPoint(rail.tracePath, anchorPoint)) continue;
|
|
12820
|
+
for (let index = 0; index < output.length; index++) {
|
|
12821
|
+
const label = output[index];
|
|
12822
|
+
if (label.globalConnNetId !== groundNetId || label.orientation !== "y-" || !label.mspConnectionPairIds.some(
|
|
12823
|
+
(id) => id === feed.mspPairId || id === rail.mspPairId
|
|
12824
|
+
) || !tracePathContainsPoint(feed.tracePath, label.anchorPoint) && !tracePathContainsPoint(rail.tracePath, label.anchorPoint))
|
|
12825
|
+
continue;
|
|
12826
|
+
const center = getCenterFromAnchor(
|
|
12827
|
+
anchorPoint,
|
|
12828
|
+
label.orientation,
|
|
12829
|
+
label.width,
|
|
12830
|
+
label.height
|
|
12831
|
+
);
|
|
12832
|
+
const bounds = getRectBounds(center, label.width, label.height);
|
|
12833
|
+
if (obstacles.some((obstacle) => rectsOverlap(bounds, obstacle)) || traceCrossesBoundsInterior(bounds, traceMap) || output.some(
|
|
12834
|
+
(other, otherIndex) => otherIndex !== index && rectsOverlap(
|
|
12835
|
+
bounds,
|
|
12836
|
+
getRectBounds(other.center, other.width, other.height)
|
|
12837
|
+
)
|
|
12838
|
+
))
|
|
12839
|
+
continue;
|
|
12840
|
+
output[index] = {
|
|
12841
|
+
...label,
|
|
12842
|
+
anchorPoint,
|
|
12843
|
+
center,
|
|
12844
|
+
mspConnectionPairIds: [rail.mspPairId],
|
|
12845
|
+
pinIds: [...rail.pinIds]
|
|
12846
|
+
};
|
|
12847
|
+
}
|
|
12848
|
+
}
|
|
12849
|
+
}
|
|
12850
|
+
return output;
|
|
12851
|
+
};
|
|
12852
|
+
|
|
12734
12853
|
// lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts
|
|
12735
12854
|
var SameNetJunctionAlignmentSolver = class extends BaseSolver {
|
|
12736
12855
|
input;
|
|
@@ -12745,7 +12864,11 @@ var SameNetJunctionAlignmentSolver = class extends BaseSolver {
|
|
|
12745
12864
|
_step() {
|
|
12746
12865
|
const result = alignSameNetJunctions(this.input);
|
|
12747
12866
|
this.outputTraces = result.traces;
|
|
12748
|
-
this.outputNetLabelPlacements =
|
|
12867
|
+
this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
|
|
12868
|
+
inputProblem: this.input.inputProblem,
|
|
12869
|
+
traces: result.traces,
|
|
12870
|
+
netLabelPlacements: result.netLabelPlacements
|
|
12871
|
+
});
|
|
12749
12872
|
this.stats.alignedJunctionCount = result.alignedJunctionCount;
|
|
12750
12873
|
this.solved = true;
|
|
12751
12874
|
}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
|
|
2
1
|
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { ConnectivityMap } from "connectivity-map"
|
|
3
|
+
import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_MAX_MSP_PAIR_DISTANCE,
|
|
5
6
|
type MspConnectionPair,
|
|
6
7
|
} from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
7
8
|
import type {
|
|
8
|
-
|
|
9
|
+
InputChip,
|
|
9
10
|
InputPin,
|
|
11
|
+
InputProblem,
|
|
10
12
|
PinId,
|
|
11
|
-
InputChip,
|
|
12
13
|
} from "lib/types/InputProblem"
|
|
13
|
-
import { BaseSolver } from "../BaseSolver/BaseSolver"
|
|
14
|
-
import { SchematicTraceSingleLineSolver2 } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
|
|
15
|
-
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
16
|
-
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
17
|
-
import type { ConnectivityMap } from "connectivity-map"
|
|
18
14
|
import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces"
|
|
19
15
|
import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
|
|
16
|
+
import { BaseSolver } from "../BaseSolver/BaseSolver"
|
|
17
|
+
import { getGroundConnectionPolicy } from "../MspConnectionPairSolver/getGroundConnectionPolicy"
|
|
20
18
|
import { isLabeledPeripheralConnection } from "../MspConnectionPairSolver/isLabeledPeripheralConnection"
|
|
19
|
+
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
20
|
+
import { SchematicTraceSingleLineSolver2 } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
|
|
21
|
+
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
21
22
|
|
|
22
23
|
const NEAREST_NEIGHBOR_COUNT = 3
|
|
23
24
|
|
|
@@ -163,6 +164,7 @@ export class LongDistancePairSolver extends BaseSolver {
|
|
|
163
164
|
|
|
164
165
|
const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } =
|
|
165
166
|
this.params
|
|
167
|
+
const canRouteGroundPair = getGroundConnectionPolicy(inputProblem)
|
|
166
168
|
|
|
167
169
|
this.inputProblem = inputProblem
|
|
168
170
|
this.allSolvedTraces = [...alreadySolvedTraces]
|
|
@@ -189,6 +191,10 @@ export class LongDistancePairSolver extends BaseSolver {
|
|
|
189
191
|
// new nearest-neighbor candidates.
|
|
190
192
|
this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
|
|
191
193
|
(connectionPair) =>
|
|
194
|
+
canRouteGroundPair(
|
|
195
|
+
connectionPair.pins[0].pinId,
|
|
196
|
+
connectionPair.pins[1].pinId,
|
|
197
|
+
) &&
|
|
192
198
|
isLabeledPeripheralConnection({
|
|
193
199
|
inputProblem: this.inputProblem,
|
|
194
200
|
chipMap: this.chipMap,
|
|
@@ -219,6 +225,7 @@ export class LongDistancePairSolver extends BaseSolver {
|
|
|
219
225
|
.flatMap((otherPinId) => {
|
|
220
226
|
const targetPin = pinMap.get(otherPinId)
|
|
221
227
|
if (!targetPin) return [] // Gracefully handle missing pins
|
|
228
|
+
if (!canRouteGroundPair(sourcePin.pinId, targetPin.pinId)) return []
|
|
222
229
|
const isNamedTwoPinConnection = inputProblem.netConnections.some(
|
|
223
230
|
(connection) =>
|
|
224
231
|
connection.pinIds.length === 2 &&
|
|
@@ -13,6 +13,7 @@ import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDiffer
|
|
|
13
13
|
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
14
14
|
import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
|
|
15
15
|
import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
|
|
16
|
+
import { getGroundConnectionPolicy } from "./getGroundConnectionPolicy"
|
|
16
17
|
import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
|
|
17
18
|
import { getLabeledConnectionRouteReason } from "./isLabeledPeripheralConnection"
|
|
18
19
|
|
|
@@ -45,11 +46,13 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
45
46
|
pinMap: Record<string, InputPin & { chipId: string }>
|
|
46
47
|
userNetIdByPinId: Record<string, string | undefined>
|
|
47
48
|
directConnectionPinPairKeys: Set<string>
|
|
49
|
+
private canRouteGroundPair: (firstPinId: PinId, secondPinId: PinId) => boolean
|
|
48
50
|
|
|
49
51
|
constructor({ inputProblem }: { inputProblem: InputProblem }) {
|
|
50
52
|
super()
|
|
51
53
|
|
|
52
54
|
this.inputProblem = inputProblem
|
|
55
|
+
this.canRouteGroundPair = getGroundConnectionPolicy(inputProblem)
|
|
53
56
|
this.maxMspPairDistance =
|
|
54
57
|
inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE
|
|
55
58
|
|
|
@@ -124,6 +127,7 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
124
127
|
const [pin1, pin2] = directlyConnectedPins
|
|
125
128
|
const p1 = this.pinMap[pin1!]!
|
|
126
129
|
const p2 = this.pinMap[pin2!]!
|
|
130
|
+
if (!this.canRouteGroundPair(pin1!, pin2!)) return
|
|
127
131
|
const pinPairKey = getPinPairKey([pin1!, pin2!])
|
|
128
132
|
// Explicit source traces are classified by straight-line distance when
|
|
129
133
|
// their input is created; named nets retain the orthogonal route metric.
|
|
@@ -195,6 +199,7 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
195
199
|
{
|
|
196
200
|
maxDistance: this.maxMspPairDistance,
|
|
197
201
|
forbidEdge: (a, b) =>
|
|
202
|
+
!this.canRouteGroundPair(a.pinId, b.pinId) ||
|
|
198
203
|
arePinsInDifferentSchematicSections(
|
|
199
204
|
this.inputProblem,
|
|
200
205
|
a as InputPin & { chipId: string },
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ConnectivityMap } from "connectivity-map"
|
|
2
|
+
import type { InputProblem, PinId } from "lib/types/InputProblem"
|
|
3
|
+
import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
|
|
4
|
+
import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
|
|
5
|
+
|
|
6
|
+
// Keep nearby ground connections, but do not extend the usual 1 mm local
|
|
7
|
+
// routing range vertically just because a sheet allows long signal traces.
|
|
8
|
+
const MAX_LOCAL_GROUND_BRANCH_OFFSET = 1
|
|
9
|
+
|
|
10
|
+
/** Avoid return wires between staggered, net-only ground-facing branches. */
|
|
11
|
+
export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
|
|
12
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
|
|
13
|
+
const groundNetId = netConnMap.getNetConnectedToId("GND")
|
|
14
|
+
if (!groundNetId) return () => true
|
|
15
|
+
// Net identifiers do not constitute physical edges: two separate direct
|
|
16
|
+
// connections may have the same netId without requesting a wire between them.
|
|
17
|
+
const physicalConnMap = new ConnectivityMap({})
|
|
18
|
+
for (const connection of inputProblem.directConnections) {
|
|
19
|
+
physicalConnMap.addConnections([connection.pinIds])
|
|
20
|
+
}
|
|
21
|
+
const pins = new Map(
|
|
22
|
+
inputProblem.chips.flatMap((chip) =>
|
|
23
|
+
chip.pins.map((pin) => [pin.pinId, { pin, chip }] as const),
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
const groundFacingPins = [...pins.values()].filter(
|
|
27
|
+
({ pin, chip }) =>
|
|
28
|
+
chip.pins.length === 2 &&
|
|
29
|
+
netConnMap.getNetConnectedToId(pin.pinId) === groundNetId &&
|
|
30
|
+
(pin._facingDirection ?? getPinDirection(pin, chip)) === "y-",
|
|
31
|
+
)
|
|
32
|
+
// A deliberately wired, level bank of two-pin loads already has its own
|
|
33
|
+
// shared return rail. Do not extend that rail to independent lower branches
|
|
34
|
+
// just to reduce the number of GND symbols. Other ground topologies retain
|
|
35
|
+
// their existing routing behavior.
|
|
36
|
+
const hasExplicitParallelGroundRail = groundFacingPins.some(({ pin: a }) => {
|
|
37
|
+
const group = physicalConnMap.getNetConnectedToId(a.pinId)
|
|
38
|
+
return (
|
|
39
|
+
group !== undefined &&
|
|
40
|
+
groundFacingPins.some(
|
|
41
|
+
({ pin: b }) =>
|
|
42
|
+
a.pinId !== b.pinId &&
|
|
43
|
+
Math.abs(a.y - b.y) < 1e-6 &&
|
|
44
|
+
Math.abs(a.x - b.x) > 1e-6 &&
|
|
45
|
+
physicalConnMap.getNetConnectedToId(b.pinId) === group,
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
if (!hasExplicitParallelGroundRail) return () => true
|
|
50
|
+
const isGroundFacingTerminal = (pinId: PinId) => {
|
|
51
|
+
const entry = pins.get(pinId)
|
|
52
|
+
return (
|
|
53
|
+
entry?.chip.pins.length === 2 &&
|
|
54
|
+
!physicalConnMap.getNetConnectedToId(pinId) &&
|
|
55
|
+
(entry.pin._facingDirection ?? getPinDirection(entry.pin, entry.chip)) ===
|
|
56
|
+
"y-"
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (firstPinId: PinId, secondPinId: PinId): boolean => {
|
|
61
|
+
if (
|
|
62
|
+
!groundNetId ||
|
|
63
|
+
netConnMap.getNetConnectedToId(firstPinId) !== groundNetId ||
|
|
64
|
+
netConnMap.getNetConnectedToId(secondPinId) !== groundNetId
|
|
65
|
+
) {
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
if (
|
|
69
|
+
!isGroundFacingTerminal(firstPinId) &&
|
|
70
|
+
!isGroundFacingTerminal(secondPinId)
|
|
71
|
+
)
|
|
72
|
+
return true
|
|
73
|
+
const first = pins.get(firstPinId)?.pin
|
|
74
|
+
const second = pins.get(secondPinId)?.pin
|
|
75
|
+
// Level ground pins can still form a shared decoupling rail. A staggered
|
|
76
|
+
// terminal would need a return detour; use its local GND label instead.
|
|
77
|
+
return (
|
|
78
|
+
!first ||
|
|
79
|
+
!second ||
|
|
80
|
+
Math.abs(first.y - second.y) <= MAX_LOCAL_GROUND_BRANCH_OFFSET + 1e-6
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -6,6 +6,7 @@ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/
|
|
|
6
6
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
7
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
8
8
|
import { alignSameNetJunctions } from "./alignSameNetJunctions"
|
|
9
|
+
import { placeGroundRailLabelsAtOuterEnd } from "./placeGroundRailLabelsAtOuterEnd"
|
|
9
10
|
|
|
10
11
|
interface SameNetJunctionAlignmentSolverInput {
|
|
11
12
|
inputProblem: InputProblem
|
|
@@ -29,7 +30,11 @@ export class SameNetJunctionAlignmentSolver extends BaseSolver {
|
|
|
29
30
|
override _step() {
|
|
30
31
|
const result = alignSameNetJunctions(this.input)
|
|
31
32
|
this.outputTraces = result.traces
|
|
32
|
-
this.outputNetLabelPlacements =
|
|
33
|
+
this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
|
|
34
|
+
inputProblem: this.input.inputProblem,
|
|
35
|
+
traces: result.traces,
|
|
36
|
+
netLabelPlacements: result.netLabelPlacements,
|
|
37
|
+
})
|
|
33
38
|
this.stats.alignedJunctionCount = result.alignedJunctionCount
|
|
34
39
|
this.solved = true
|
|
35
40
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { traceCrossesBoundsInterior } from "lib/solvers/AvailableNetOrientationSolver/geometry"
|
|
2
|
+
import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
|
|
3
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import {
|
|
5
|
+
getCenterFromAnchor,
|
|
6
|
+
getRectBounds,
|
|
7
|
+
} from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
8
|
+
import {
|
|
9
|
+
rectsOverlap,
|
|
10
|
+
tracePathContainsPoint,
|
|
11
|
+
} from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
|
|
12
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
13
|
+
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
14
|
+
import {
|
|
15
|
+
isHorizontal,
|
|
16
|
+
isVertical,
|
|
17
|
+
nearlyEqual,
|
|
18
|
+
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
|
|
19
|
+
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
20
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
21
|
+
|
|
22
|
+
/** Put a shared decoupling GND at the load end of its rail, away from the IC. */
|
|
23
|
+
export const placeGroundRailLabelsAtOuterEnd = ({
|
|
24
|
+
inputProblem,
|
|
25
|
+
traces,
|
|
26
|
+
netLabelPlacements,
|
|
27
|
+
}: {
|
|
28
|
+
inputProblem: InputProblem
|
|
29
|
+
traces: SolvedTracePath[]
|
|
30
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
31
|
+
}): NetLabelPlacement[] => {
|
|
32
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
|
|
33
|
+
const groundNetId = netConnMap.getNetConnectedToId("GND")
|
|
34
|
+
if (!groundNetId) return netLabelPlacements
|
|
35
|
+
const chipMap = new Map(inputProblem.chips.map((chip) => [chip.chipId, chip]))
|
|
36
|
+
const traceMap = Object.fromEntries(
|
|
37
|
+
traces.map((trace) => [trace.mspPairId, trace]),
|
|
38
|
+
)
|
|
39
|
+
const obstacles = getObstacleRects(inputProblem)
|
|
40
|
+
const output = [...netLabelPlacements]
|
|
41
|
+
|
|
42
|
+
for (const rail of traces) {
|
|
43
|
+
if (rail.globalConnNetId !== groundNetId) continue
|
|
44
|
+
const [a, b] = rail.pins
|
|
45
|
+
if (
|
|
46
|
+
a.chipId === b.chipId ||
|
|
47
|
+
!nearlyEqual(a.y, b.y) ||
|
|
48
|
+
![a, b].every(
|
|
49
|
+
(pin) =>
|
|
50
|
+
pin._facingDirection === "y-" &&
|
|
51
|
+
chipMap.get(pin.chipId)?.pins.length === 2,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
continue
|
|
55
|
+
const path = simplifyPath(rail.tracePath)
|
|
56
|
+
if (
|
|
57
|
+
path.length !== 4 ||
|
|
58
|
+
!isVertical(path[0]!, path[1]!) ||
|
|
59
|
+
!isHorizontal(path[1]!, path[2]!) ||
|
|
60
|
+
!isVertical(path[2]!, path[3]!) ||
|
|
61
|
+
path[1]!.y >= a.y
|
|
62
|
+
)
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
for (const feed of traces) {
|
|
66
|
+
if (feed.globalConnNetId !== groundNetId) continue
|
|
67
|
+
const shared = feed.pins.find((pin) => rail.pinIds.includes(pin.pinId))
|
|
68
|
+
const icPin = feed.pins.find(
|
|
69
|
+
(pin) =>
|
|
70
|
+
!rail.pinIds.includes(pin.pinId) &&
|
|
71
|
+
(chipMap.get(pin.chipId)?.pins.length ?? 0) > 2,
|
|
72
|
+
)
|
|
73
|
+
if (!shared || !icPin || !nearlyEqual(icPin.y, path[1]!.y)) continue
|
|
74
|
+
const outer = shared.pinId === a.pinId ? b : a
|
|
75
|
+
if (Math.abs(outer.x - icPin.x) <= Math.abs(shared.x - icPin.x)) continue
|
|
76
|
+
const anchorPoint = { x: outer.x, y: path[1]!.y }
|
|
77
|
+
if (!tracePathContainsPoint(rail.tracePath, anchorPoint)) continue
|
|
78
|
+
|
|
79
|
+
for (let index = 0; index < output.length; index++) {
|
|
80
|
+
const label = output[index]!
|
|
81
|
+
if (
|
|
82
|
+
label.globalConnNetId !== groundNetId ||
|
|
83
|
+
label.orientation !== "y-" ||
|
|
84
|
+
!label.mspConnectionPairIds.some(
|
|
85
|
+
(id) => id === feed.mspPairId || id === rail.mspPairId,
|
|
86
|
+
) ||
|
|
87
|
+
(!tracePathContainsPoint(feed.tracePath, label.anchorPoint) &&
|
|
88
|
+
!tracePathContainsPoint(rail.tracePath, label.anchorPoint))
|
|
89
|
+
)
|
|
90
|
+
continue
|
|
91
|
+
const center = getCenterFromAnchor(
|
|
92
|
+
anchorPoint,
|
|
93
|
+
label.orientation,
|
|
94
|
+
label.width,
|
|
95
|
+
label.height,
|
|
96
|
+
)
|
|
97
|
+
const bounds = getRectBounds(center, label.width, label.height)
|
|
98
|
+
if (
|
|
99
|
+
obstacles.some((obstacle) => rectsOverlap(bounds, obstacle)) ||
|
|
100
|
+
traceCrossesBoundsInterior(bounds, traceMap) ||
|
|
101
|
+
output.some(
|
|
102
|
+
(other, otherIndex) =>
|
|
103
|
+
otherIndex !== index &&
|
|
104
|
+
rectsOverlap(
|
|
105
|
+
bounds,
|
|
106
|
+
getRectBounds(other.center, other.width, other.height),
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
continue
|
|
111
|
+
output[index] = {
|
|
112
|
+
...label,
|
|
113
|
+
anchorPoint,
|
|
114
|
+
center,
|
|
115
|
+
mspConnectionPairIds: [rail.mspPairId],
|
|
116
|
+
pinIds: [...rail.pinIds],
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return output
|
|
122
|
+
}
|