@tscircuit/schematic-trace-solver 0.0.196 → 0.0.198
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 +120 -7
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +42 -1
- package/lib/solvers/NetLabelToTraceSolver/getSimpleElbowPath.ts +31 -0
- package/lib/solvers/RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts +128 -2
- package/lib/solvers/RailNetLabelCornerPlacementSolver/types.ts +1 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +2 -0
- package/package.json +1 -1
- package/site/SchematicTracePipelineSolver/repro-am3352-sheet3-power-rails.page.tsx +7 -0
- package/site/bug-reports/bug-report-20260911T195723Z.page.tsx +4 -0
- package/site/bug-reports/enc-scl-hop.page.tsx +6 -0
- package/tests/assets/enc-scl-hop.json +185 -0
- package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +13 -31
- package/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg +25 -61
- package/tests/bug-reports/bug-report-20260911T195723Z/__snapshots__/bug-report-20260911T195723Z.snap.svg +559 -0
- package/tests/bug-reports/bug-report-20260911T195723Z/bug-report-20260911T195723Z.json +1792 -0
- package/tests/bug-reports/bug-report-20260911T195723Z/bug-report-20260911T195723Z.test.ts +35 -0
- package/tests/repros/__snapshots__/repro-am3352-sheet3-power-rails.snap.svg +187 -0
- package/tests/repros/__snapshots__/repro-mcp73831-charger-traces.snap.svg +5 -23
- package/tests/repros/__snapshots__/repro-normal-net-label-branch-from-junction.snap.svg +31 -31
- package/tests/repros/__snapshots__/repro-trellis-core-gnd-net-label-overlap.snap.svg +25 -61
- package/tests/repros/__snapshots__/rp2040-robot-controller-disconnected-gnd.snap.svg +12 -26
- package/tests/repros/assets/repro-am3352-sheet3-power-rails.input.json +698 -0
- package/tests/repros/repro-am3352-sheet3-power-rails.md +66 -0
- package/tests/repros/repro-am3352-sheet3-power-rails.test.ts +39 -0
- package/tests/repros/repro-normal-net-label-branch-from-junction.test.ts +13 -0
- package/tests/solvers/SchematicTracePipelineSolver/__snapshots__/enc-scl-hop.snap.svg +101 -0
- package/tests/solvers/SchematicTracePipelineSolver/__snapshots__/explicit-hop.snap.svg +101 -0
- package/tests/solvers/SchematicTracePipelineSolver/enc-scl-hop.test.ts +139 -0
package/dist/index.d.ts
CHANGED
|
@@ -963,6 +963,7 @@ interface RailNetLabelCornerPlacementSolverParams {
|
|
|
963
963
|
inputProblem: InputProblem;
|
|
964
964
|
traces: SolvedTracePath[];
|
|
965
965
|
netLabelPlacements: NetLabelPlacement[];
|
|
966
|
+
netLabelConnectorTraceIds?: ReadonlySet<string>;
|
|
966
967
|
}
|
|
967
968
|
type TraceCornerCandidate = {
|
|
968
969
|
anchorPoint: Point;
|
|
@@ -996,6 +997,7 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
996
997
|
private queuedCornerCandidates;
|
|
997
998
|
private shouldAdvanceToNextLabel;
|
|
998
999
|
private traceMap;
|
|
1000
|
+
private netLabelConnectorTraceIds;
|
|
999
1001
|
constructor(params: RailNetLabelCornerPlacementSolverParams);
|
|
1000
1002
|
getConstructorParams(): ConstructorParameters<typeof RailNetLabelCornerPlacementSolver>[0];
|
|
1001
1003
|
_step(): void;
|
|
@@ -1015,6 +1017,8 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
1015
1017
|
private intersectsAnyChip;
|
|
1016
1018
|
private intersectsAnyOtherNetLabel;
|
|
1017
1019
|
private getCornerCandidatesForLabel;
|
|
1020
|
+
private getStraightConnectorCandidates;
|
|
1021
|
+
private isComponentAdjacentCorner;
|
|
1018
1022
|
private isConfiguredRailLabel;
|
|
1019
1023
|
private isLabelCrossedByOtherNetTrace;
|
|
1020
1024
|
private pointsEqual;
|
|
@@ -1451,6 +1455,7 @@ declare class NetLabelToTraceSolver extends BaseSolver {
|
|
|
1451
1455
|
private isPortOnlyFallbackLabel;
|
|
1452
1456
|
private isDirectConnectionLabel;
|
|
1453
1457
|
private getMultiPinNetConnection;
|
|
1458
|
+
private getTwoPinNetConnection;
|
|
1454
1459
|
private buildCandidatePairs;
|
|
1455
1460
|
private buildRoutedComponentCandidates;
|
|
1456
1461
|
private isSupersededConnectorTrace;
|
package/dist/index.js
CHANGED
|
@@ -11181,6 +11181,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11181
11181
|
queuedCornerCandidates = [];
|
|
11182
11182
|
shouldAdvanceToNextLabel = false;
|
|
11183
11183
|
traceMap;
|
|
11184
|
+
netLabelConnectorTraceIds;
|
|
11184
11185
|
constructor(params) {
|
|
11185
11186
|
super();
|
|
11186
11187
|
this.inputProblem = params.inputProblem;
|
|
@@ -11190,6 +11191,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11190
11191
|
this.traceMap = Object.fromEntries(
|
|
11191
11192
|
params.traces.map((trace) => [trace.mspPairId, trace])
|
|
11192
11193
|
);
|
|
11194
|
+
this.netLabelConnectorTraceIds = params.netLabelConnectorTraceIds ?? /* @__PURE__ */ new Set();
|
|
11193
11195
|
this.queuedLabelIndices = this.getProcessableLabelIndices();
|
|
11194
11196
|
this.prepareNextLabel();
|
|
11195
11197
|
}
|
|
@@ -11197,7 +11199,8 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11197
11199
|
return {
|
|
11198
11200
|
inputProblem: this.inputProblem,
|
|
11199
11201
|
traces: this.traces,
|
|
11200
|
-
netLabelPlacements: this.netLabelPlacements
|
|
11202
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
11203
|
+
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds
|
|
11201
11204
|
};
|
|
11202
11205
|
}
|
|
11203
11206
|
_step() {
|
|
@@ -11248,7 +11251,10 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11248
11251
|
this.currentLabelIndex = labelIndex;
|
|
11249
11252
|
this.currentLabel = label;
|
|
11250
11253
|
this.currentCandidateResults = [];
|
|
11251
|
-
this.queuedCornerCandidates =
|
|
11254
|
+
this.queuedCornerCandidates = [
|
|
11255
|
+
...this.getStraightConnectorCandidates(label),
|
|
11256
|
+
...this.getCornerCandidatesForLabel(label)
|
|
11257
|
+
];
|
|
11252
11258
|
return true;
|
|
11253
11259
|
}
|
|
11254
11260
|
advanceToNextLabel() {
|
|
@@ -11330,7 +11336,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11330
11336
|
}
|
|
11331
11337
|
shouldProcessLabel(label) {
|
|
11332
11338
|
const isVerticalRailLabel = label.orientation === "y+" || label.orientation === "y-";
|
|
11333
|
-
return isVerticalRailLabel && this.getCornerCandidatesForLabel(label).length > 0;
|
|
11339
|
+
return isVerticalRailLabel && (this.getStraightConnectorCandidates(label).length > 0 || this.getCornerCandidatesForLabel(label).length > 0);
|
|
11334
11340
|
}
|
|
11335
11341
|
intersectsAnyChip(bounds) {
|
|
11336
11342
|
return this.inputProblem.chips.some(
|
|
@@ -11387,6 +11393,77 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11387
11393
|
...railAlignedCandidates.sort((a, b) => a.distance - b.distance)
|
|
11388
11394
|
];
|
|
11389
11395
|
}
|
|
11396
|
+
getStraightConnectorCandidates(label) {
|
|
11397
|
+
if (label.pinIds.length !== 2 || !this.isConfiguredRailLabel(label)) {
|
|
11398
|
+
return [];
|
|
11399
|
+
}
|
|
11400
|
+
const pins = this.inputProblem.chips.flatMap((chip) => chip.pins).filter((pin) => label.pinIds.includes(pin.pinId));
|
|
11401
|
+
if (pins.length !== 2 || pins[0]._facingDirection !== pins[1]._facingDirection || pins[0]._facingDirection !== "x+" && pins[0]._facingDirection !== "x-" || Math.abs(pins[0].y - pins[1].y) > EPS8) {
|
|
11402
|
+
return [];
|
|
11403
|
+
}
|
|
11404
|
+
const configuredOrientations = this.inputProblem.availableNetLabelOrientations[label.netId];
|
|
11405
|
+
if (configuredOrientations?.length !== 1 || configuredOrientations[0] !== label.orientation) {
|
|
11406
|
+
return [];
|
|
11407
|
+
}
|
|
11408
|
+
const netConnection = this.inputProblem.netConnections.find(
|
|
11409
|
+
(connection) => connection.netId === label.netId
|
|
11410
|
+
);
|
|
11411
|
+
if (netConnection?.isGround) return [];
|
|
11412
|
+
const connector = this.traces.find(
|
|
11413
|
+
(trace) => this.netLabelConnectorTraceIds.has(trace.mspPairId) && trace.globalConnNetId === label.globalConnNetId && label.pinIds.every((pinId) => trace.pinIds.includes(pinId)) && trace.tracePath.length >= 2 && (this.pointsEqual(trace.tracePath[0], label.anchorPoint) || this.pointsEqual(trace.tracePath.at(-1), label.anchorPoint))
|
|
11414
|
+
);
|
|
11415
|
+
if (!connector) return [];
|
|
11416
|
+
const sourcePoint = this.pointsEqual(
|
|
11417
|
+
connector.tracePath[0],
|
|
11418
|
+
label.anchorPoint
|
|
11419
|
+
) ? connector.tracePath.at(-1) : connector.tracePath[0];
|
|
11420
|
+
if (!this.isComponentAdjacentCorner(label, sourcePoint)) return [];
|
|
11421
|
+
const direction = label.orientation === "y+" ? 1 : -1;
|
|
11422
|
+
const maxSearchDistance = Math.min(
|
|
11423
|
+
getMaxSearchDistance(this.inputProblem),
|
|
11424
|
+
label.height * 2
|
|
11425
|
+
);
|
|
11426
|
+
for (let distance7 = WICK_CLEARANCE + LABEL_SEARCH_STEP; distance7 <= maxSearchDistance + EPS8; distance7 += LABEL_SEARCH_STEP) {
|
|
11427
|
+
const anchorPoint = {
|
|
11428
|
+
x: sourcePoint.x,
|
|
11429
|
+
y: sourcePoint.y + direction * distance7
|
|
11430
|
+
};
|
|
11431
|
+
const center = getCenterFromAnchor(
|
|
11432
|
+
anchorPoint,
|
|
11433
|
+
label.orientation,
|
|
11434
|
+
label.width,
|
|
11435
|
+
label.height
|
|
11436
|
+
);
|
|
11437
|
+
if (this.intersectsAnyChip(getRectBounds(center, label.width, label.height))) {
|
|
11438
|
+
continue;
|
|
11439
|
+
}
|
|
11440
|
+
return [
|
|
11441
|
+
{
|
|
11442
|
+
anchorPoint,
|
|
11443
|
+
traceId: connector.mspPairId,
|
|
11444
|
+
distance: getDistance(anchorPoint, label.anchorPoint),
|
|
11445
|
+
pinAligned: true,
|
|
11446
|
+
reroutedTracePath: [sourcePoint, anchorPoint]
|
|
11447
|
+
}
|
|
11448
|
+
];
|
|
11449
|
+
}
|
|
11450
|
+
return [];
|
|
11451
|
+
}
|
|
11452
|
+
isComponentAdjacentCorner(label, corner) {
|
|
11453
|
+
return label.mspConnectionPairIds.some((traceId) => {
|
|
11454
|
+
const trace = this.traceMap[traceId];
|
|
11455
|
+
if (!trace || trace.tracePath.length < 2) return false;
|
|
11456
|
+
return [
|
|
11457
|
+
{ point: trace.tracePath[0], neighbor: trace.tracePath[1] },
|
|
11458
|
+
{
|
|
11459
|
+
point: trace.tracePath.at(-1),
|
|
11460
|
+
neighbor: trace.tracePath.at(-2)
|
|
11461
|
+
}
|
|
11462
|
+
].some(
|
|
11463
|
+
({ point, neighbor }) => this.pointsEqual(neighbor, corner) && trace.pins.some((pin) => this.pointsEqual(pin, point))
|
|
11464
|
+
);
|
|
11465
|
+
});
|
|
11466
|
+
}
|
|
11390
11467
|
isConfiguredRailLabel(label) {
|
|
11391
11468
|
if (!label.netId) return false;
|
|
11392
11469
|
return this.inputProblem.availableNetLabelOrientations[label.netId]?.some(
|
|
@@ -17900,6 +17977,23 @@ var getTraceRecoveryConnectivityMaps = (inputProblem) => {
|
|
|
17900
17977
|
return { chipMap, pinMap };
|
|
17901
17978
|
};
|
|
17902
17979
|
|
|
17980
|
+
// lib/solvers/NetLabelToTraceSolver/getSimpleElbowPath.ts
|
|
17981
|
+
var getSimpleElbowPath = (first, second) => {
|
|
17982
|
+
const firstDirection = dir(first._facingDirection);
|
|
17983
|
+
const secondDirection = dir(second._facingDirection);
|
|
17984
|
+
if (firstDirection.x * secondDirection.x || firstDirection.y * secondDirection.y)
|
|
17985
|
+
return void 0;
|
|
17986
|
+
const elbow = firstDirection.x ? { x: second.x, y: first.y } : { x: first.x, y: second.y };
|
|
17987
|
+
for (const [pin, direction] of [
|
|
17988
|
+
[first, firstDirection],
|
|
17989
|
+
[second, secondDirection]
|
|
17990
|
+
]) {
|
|
17991
|
+
if ((elbow.x - pin.x) * direction.x + (elbow.y - pin.y) * direction.y <= 1e-6)
|
|
17992
|
+
return void 0;
|
|
17993
|
+
}
|
|
17994
|
+
return [{ x: first.x, y: first.y }, elbow, { x: second.x, y: second.y }];
|
|
17995
|
+
};
|
|
17996
|
+
|
|
17903
17997
|
// lib/solvers/NetLabelToTraceSolver/reduceTraceCrossings.ts
|
|
17904
17998
|
var EPS18 = 1e-9;
|
|
17905
17999
|
var isHorizontal4 = (start, end) => Math.abs(start.y - end.y) < EPS18;
|
|
@@ -18074,6 +18168,11 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
18074
18168
|
(connection) => connection.isGround === false && connection.pinIds.length > 2 && connection.netId === label.netId && connection.pinIds.includes(pinId)
|
|
18075
18169
|
);
|
|
18076
18170
|
}
|
|
18171
|
+
getTwoPinNetConnection(label) {
|
|
18172
|
+
return this.inputProblem.netConnections.find(
|
|
18173
|
+
(connection) => !connection.isGround && connection.pinIds.length === 2 && connection.netId === label.netId && connection.pinIds.includes(label.pinIds[0])
|
|
18174
|
+
);
|
|
18175
|
+
}
|
|
18077
18176
|
buildCandidatePairs() {
|
|
18078
18177
|
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
18079
18178
|
this.inputProblem
|
|
@@ -18086,7 +18185,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
18086
18185
|
}
|
|
18087
18186
|
const labelsByGlobalNet = /* @__PURE__ */ new Map();
|
|
18088
18187
|
for (const label of this.input.netLabelPlacements) {
|
|
18089
|
-
if (!this.isPortOnlyFallbackLabel(label, groundGlobalConnNetIds) || !this.isDirectConnectionLabel(label) && !this.getMultiPinNetConnection(label)) {
|
|
18188
|
+
if (!this.isPortOnlyFallbackLabel(label, groundGlobalConnNetIds) || !this.isDirectConnectionLabel(label) && !this.getMultiPinNetConnection(label) && !this.getTwoPinNetConnection(label)) {
|
|
18090
18189
|
continue;
|
|
18091
18190
|
}
|
|
18092
18191
|
const labels = labelsByGlobalNet.get(label.globalConnNetId) ?? [];
|
|
@@ -18110,7 +18209,9 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
18110
18209
|
const bothLabelsBelongToDirectConnections = this.isDirectConnectionLabel(firstLabel) && this.isDirectConnectionLabel(secondLabel);
|
|
18111
18210
|
const firstMultiPinNetConnection = this.getMultiPinNetConnection(firstLabel);
|
|
18112
18211
|
const secondMultiPinNetConnection = this.getMultiPinNetConnection(secondLabel);
|
|
18113
|
-
|
|
18212
|
+
const twoPinNetConnection = this.getTwoPinNetConnection(firstLabel);
|
|
18213
|
+
const simpleElbowPath = !bothLabelsBelongToDirectConnections && twoPinNetConnection && firstPin.chipId !== secondPin.chipId && twoPinNetConnection === this.getTwoPinNetConnection(secondLabel) ? getSimpleElbowPath(firstPin, secondPin) : void 0;
|
|
18214
|
+
if (!simpleElbowPath && !bothLabelsBelongToDirectConnections && (!firstMultiPinNetConnection || firstMultiPinNetConnection !== secondMultiPinNetConnection || perpendicularOffset > MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET)) {
|
|
18114
18215
|
continue;
|
|
18115
18216
|
}
|
|
18116
18217
|
if (arePinsInDifferentSchematicSections(
|
|
@@ -18136,7 +18237,8 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
18136
18237
|
perpendicularOffset,
|
|
18137
18238
|
routeDistance: Math.abs(firstPin.x - secondPin.x) + Math.abs(firstPin.y - secondPin.y),
|
|
18138
18239
|
key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
|
|
18139
|
-
recoveryMode: "fallback_labels"
|
|
18240
|
+
recoveryMode: "fallback_labels",
|
|
18241
|
+
simpleElbowPath
|
|
18140
18242
|
});
|
|
18141
18243
|
}
|
|
18142
18244
|
}
|
|
@@ -18298,6 +18400,16 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
18298
18400
|
(trace) => trace.globalConnNetId !== candidate.firstLabel.globalConnNetId
|
|
18299
18401
|
);
|
|
18300
18402
|
}
|
|
18403
|
+
if (candidate.simpleElbowPath) {
|
|
18404
|
+
tracePath = candidate.simpleElbowPath;
|
|
18405
|
+
if (findFirstCollision(tracePath, this.activeSubSolver.obstacles) || collisionTraces.reduce(
|
|
18406
|
+
(count, trace) => count + findPerpendicularPathCrossings(tracePath, trace.tracePath, {
|
|
18407
|
+
includeTerminalSegments: true
|
|
18408
|
+
}).length,
|
|
18409
|
+
0
|
|
18410
|
+
) > 1)
|
|
18411
|
+
return;
|
|
18412
|
+
}
|
|
18301
18413
|
if (doesTraceRecoveryPathConflict(tracePath, collisionTraces) || this.routeIntersectsRemainingLabels(tracePath, candidate)) {
|
|
18302
18414
|
return;
|
|
18303
18415
|
}
|
|
@@ -18675,7 +18787,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
18675
18787
|
traces: Object.values(
|
|
18676
18788
|
instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
|
|
18677
18789
|
),
|
|
18678
|
-
netLabelPlacements: instance.availableNetOrientationSolver.outputNetLabelPlacements
|
|
18790
|
+
netLabelPlacements: instance.availableNetOrientationSolver.outputNetLabelPlacements,
|
|
18791
|
+
netLabelConnectorTraceIds: instance.availableNetOrientationSolver.netLabelConnectorTraceIds
|
|
18679
18792
|
}
|
|
18680
18793
|
];
|
|
18681
18794
|
}
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
type InlineNetLabelPlacement,
|
|
28
28
|
visualizeInlineNetLabelOutput,
|
|
29
29
|
} from "../InlineNetLabelSolver/InlineNetLabelSolver"
|
|
30
|
+
import { getSimpleElbowPath } from "./getSimpleElbowPath"
|
|
31
|
+
import { findPerpendicularPathCrossings } from "../TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles"
|
|
30
32
|
import { reduceTraceCrossings } from "./reduceTraceCrossings"
|
|
31
33
|
|
|
32
34
|
type GlobalConnNetId = NetLabelPlacement["globalConnNetId"]
|
|
@@ -43,6 +45,7 @@ interface CandidatePair {
|
|
|
43
45
|
| "routed_components"
|
|
44
46
|
| "routed_direct_connection"
|
|
45
47
|
netConnectionPinIds?: PinId[]
|
|
48
|
+
simpleElbowPath?: Point[]
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
const RECOVERED_TRACE_PREFIX = "net-label-to-trace-"
|
|
@@ -153,6 +156,16 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
153
156
|
)
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
private getTwoPinNetConnection(label: NetLabelPlacement) {
|
|
160
|
+
return this.inputProblem.netConnections.find(
|
|
161
|
+
(connection) =>
|
|
162
|
+
!connection.isGround &&
|
|
163
|
+
connection.pinIds.length === 2 &&
|
|
164
|
+
connection.netId === label.netId &&
|
|
165
|
+
connection.pinIds.includes(label.pinIds[0]!),
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
156
169
|
private buildCandidatePairs() {
|
|
157
170
|
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
158
171
|
this.inputProblem,
|
|
@@ -169,7 +182,8 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
169
182
|
if (
|
|
170
183
|
!this.isPortOnlyFallbackLabel(label, groundGlobalConnNetIds) ||
|
|
171
184
|
(!this.isDirectConnectionLabel(label) &&
|
|
172
|
-
!this.getMultiPinNetConnection(label)
|
|
185
|
+
!this.getMultiPinNetConnection(label) &&
|
|
186
|
+
!this.getTwoPinNetConnection(label))
|
|
173
187
|
) {
|
|
174
188
|
continue
|
|
175
189
|
}
|
|
@@ -203,7 +217,16 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
203
217
|
this.getMultiPinNetConnection(firstLabel)
|
|
204
218
|
const secondMultiPinNetConnection =
|
|
205
219
|
this.getMultiPinNetConnection(secondLabel)
|
|
220
|
+
const twoPinNetConnection = this.getTwoPinNetConnection(firstLabel)
|
|
221
|
+
const simpleElbowPath =
|
|
222
|
+
!bothLabelsBelongToDirectConnections &&
|
|
223
|
+
twoPinNetConnection &&
|
|
224
|
+
firstPin.chipId !== secondPin.chipId &&
|
|
225
|
+
twoPinNetConnection === this.getTwoPinNetConnection(secondLabel)
|
|
226
|
+
? getSimpleElbowPath(firstPin, secondPin)
|
|
227
|
+
: undefined
|
|
206
228
|
if (
|
|
229
|
+
!simpleElbowPath &&
|
|
207
230
|
!bothLabelsBelongToDirectConnections &&
|
|
208
231
|
(!firstMultiPinNetConnection ||
|
|
209
232
|
firstMultiPinNetConnection !== secondMultiPinNetConnection ||
|
|
@@ -242,6 +265,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
242
265
|
Math.abs(firstPin.y - secondPin.y),
|
|
243
266
|
key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
|
|
244
267
|
recoveryMode: "fallback_labels",
|
|
268
|
+
simpleElbowPath,
|
|
245
269
|
})
|
|
246
270
|
}
|
|
247
271
|
}
|
|
@@ -474,6 +498,23 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
474
498
|
trace.globalConnNetId !== candidate.firstLabel.globalConnNetId,
|
|
475
499
|
)
|
|
476
500
|
}
|
|
501
|
+
// Only recover a named two-pin elbow when the shortest, direction-respecting
|
|
502
|
+
// path is clear. Do not replace labels with obstacle detours or many hops.
|
|
503
|
+
if (candidate.simpleElbowPath) {
|
|
504
|
+
tracePath = candidate.simpleElbowPath
|
|
505
|
+
if (
|
|
506
|
+
findFirstCollision(tracePath, this.activeSubSolver!.obstacles) ||
|
|
507
|
+
collisionTraces.reduce(
|
|
508
|
+
(count, trace) =>
|
|
509
|
+
count +
|
|
510
|
+
findPerpendicularPathCrossings(tracePath!, trace.tracePath, {
|
|
511
|
+
includeTerminalSegments: true,
|
|
512
|
+
}).length,
|
|
513
|
+
0,
|
|
514
|
+
) > 1
|
|
515
|
+
)
|
|
516
|
+
return
|
|
517
|
+
}
|
|
477
518
|
if (
|
|
478
519
|
doesTraceRecoveryPathConflict(tracePath, collisionTraces) ||
|
|
479
520
|
this.routeIntersectsRemainingLabels(tracePath, candidate)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { TraceRecoveryPin } from "lib/solvers/NetLabelTraceRecovery/getTraceRecoveryConnectivityMaps"
|
|
3
|
+
import { dir } from "lib/utils/dir"
|
|
4
|
+
|
|
5
|
+
/** The single elbow reached by extending both pins outwards, if it exists. */
|
|
6
|
+
export const getSimpleElbowPath = (
|
|
7
|
+
first: TraceRecoveryPin,
|
|
8
|
+
second: TraceRecoveryPin,
|
|
9
|
+
): Point[] | undefined => {
|
|
10
|
+
const firstDirection = dir(first._facingDirection)
|
|
11
|
+
const secondDirection = dir(second._facingDirection)
|
|
12
|
+
if (
|
|
13
|
+
firstDirection.x * secondDirection.x ||
|
|
14
|
+
firstDirection.y * secondDirection.y
|
|
15
|
+
)
|
|
16
|
+
return undefined
|
|
17
|
+
const elbow = firstDirection.x
|
|
18
|
+
? { x: second.x, y: first.y }
|
|
19
|
+
: { x: first.x, y: second.y }
|
|
20
|
+
for (const [pin, direction] of [
|
|
21
|
+
[first, firstDirection],
|
|
22
|
+
[second, secondDirection],
|
|
23
|
+
] as const) {
|
|
24
|
+
if (
|
|
25
|
+
(elbow.x - pin.x) * direction.x + (elbow.y - pin.y) * direction.y <=
|
|
26
|
+
1e-6
|
|
27
|
+
)
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
return [{ x: first.x, y: first.y }, elbow, { x: second.x, y: second.y }]
|
|
31
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { GraphicsObject } from "graphics-debug"
|
|
2
2
|
import type { Point } from "@tscircuit/math-utils"
|
|
3
3
|
import {
|
|
4
|
+
getMaxSearchDistance,
|
|
4
5
|
segmentCrossesBoundsInterior,
|
|
5
6
|
traceCrossesBoundsInterior,
|
|
6
7
|
} from "lib/solvers/AvailableNetOrientationSolver/geometry"
|
|
8
|
+
import {
|
|
9
|
+
LABEL_SEARCH_STEP,
|
|
10
|
+
WICK_CLEARANCE,
|
|
11
|
+
} from "lib/solvers/AvailableNetOrientationSolver/constants"
|
|
7
12
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
8
13
|
import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
|
|
9
14
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
@@ -51,6 +56,7 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
51
56
|
private queuedCornerCandidates: TraceCornerCandidate[] = []
|
|
52
57
|
private shouldAdvanceToNextLabel = false
|
|
53
58
|
private traceMap: Record<string, SolvedTracePath>
|
|
59
|
+
private netLabelConnectorTraceIds: ReadonlySet<string>
|
|
54
60
|
|
|
55
61
|
constructor(params: RailNetLabelCornerPlacementSolverParams) {
|
|
56
62
|
super()
|
|
@@ -61,6 +67,8 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
61
67
|
this.traceMap = Object.fromEntries(
|
|
62
68
|
params.traces.map((trace) => [trace.mspPairId, trace]),
|
|
63
69
|
)
|
|
70
|
+
this.netLabelConnectorTraceIds =
|
|
71
|
+
params.netLabelConnectorTraceIds ?? new Set()
|
|
64
72
|
this.queuedLabelIndices = this.getProcessableLabelIndices()
|
|
65
73
|
this.prepareNextLabel()
|
|
66
74
|
}
|
|
@@ -72,6 +80,7 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
72
80
|
inputProblem: this.inputProblem,
|
|
73
81
|
traces: this.traces,
|
|
74
82
|
netLabelPlacements: this.netLabelPlacements,
|
|
83
|
+
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
|
|
75
84
|
}
|
|
76
85
|
}
|
|
77
86
|
|
|
@@ -135,7 +144,10 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
135
144
|
this.currentLabelIndex = labelIndex
|
|
136
145
|
this.currentLabel = label
|
|
137
146
|
this.currentCandidateResults = []
|
|
138
|
-
this.queuedCornerCandidates =
|
|
147
|
+
this.queuedCornerCandidates = [
|
|
148
|
+
...this.getStraightConnectorCandidates(label),
|
|
149
|
+
...this.getCornerCandidatesForLabel(label),
|
|
150
|
+
]
|
|
139
151
|
|
|
140
152
|
return true
|
|
141
153
|
}
|
|
@@ -250,7 +262,9 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
250
262
|
const isVerticalRailLabel =
|
|
251
263
|
label.orientation === "y+" || label.orientation === "y-"
|
|
252
264
|
return (
|
|
253
|
-
isVerticalRailLabel &&
|
|
265
|
+
isVerticalRailLabel &&
|
|
266
|
+
(this.getStraightConnectorCandidates(label).length > 0 ||
|
|
267
|
+
this.getCornerCandidatesForLabel(label).length > 0)
|
|
254
268
|
)
|
|
255
269
|
}
|
|
256
270
|
|
|
@@ -323,6 +337,118 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
323
337
|
]
|
|
324
338
|
}
|
|
325
339
|
|
|
340
|
+
private getStraightConnectorCandidates(
|
|
341
|
+
label: NetLabelPlacement,
|
|
342
|
+
): TraceCornerCandidate[] {
|
|
343
|
+
if (label.pinIds.length !== 2 || !this.isConfiguredRailLabel(label)) {
|
|
344
|
+
return []
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const pins = this.inputProblem.chips
|
|
348
|
+
.flatMap((chip) => chip.pins)
|
|
349
|
+
.filter((pin) => label.pinIds.includes(pin.pinId))
|
|
350
|
+
if (
|
|
351
|
+
pins.length !== 2 ||
|
|
352
|
+
pins[0]!._facingDirection !== pins[1]!._facingDirection ||
|
|
353
|
+
(pins[0]!._facingDirection !== "x+" &&
|
|
354
|
+
pins[0]!._facingDirection !== "x-") ||
|
|
355
|
+
Math.abs(pins[0]!.y - pins[1]!.y) > EPS
|
|
356
|
+
) {
|
|
357
|
+
return []
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const configuredOrientations =
|
|
361
|
+
this.inputProblem.availableNetLabelOrientations[label.netId!]
|
|
362
|
+
if (
|
|
363
|
+
configuredOrientations?.length !== 1 ||
|
|
364
|
+
configuredOrientations[0] !== label.orientation
|
|
365
|
+
) {
|
|
366
|
+
return []
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const netConnection = this.inputProblem.netConnections.find(
|
|
370
|
+
(connection) => connection.netId === label.netId,
|
|
371
|
+
)
|
|
372
|
+
if (netConnection?.isGround) return []
|
|
373
|
+
|
|
374
|
+
const connector = this.traces.find(
|
|
375
|
+
(trace) =>
|
|
376
|
+
this.netLabelConnectorTraceIds.has(trace.mspPairId) &&
|
|
377
|
+
trace.globalConnNetId === label.globalConnNetId &&
|
|
378
|
+
label.pinIds.every((pinId) => trace.pinIds.includes(pinId)) &&
|
|
379
|
+
trace.tracePath.length >= 2 &&
|
|
380
|
+
(this.pointsEqual(trace.tracePath[0]!, label.anchorPoint) ||
|
|
381
|
+
this.pointsEqual(trace.tracePath.at(-1)!, label.anchorPoint)),
|
|
382
|
+
)
|
|
383
|
+
if (!connector) return []
|
|
384
|
+
|
|
385
|
+
const sourcePoint = this.pointsEqual(
|
|
386
|
+
connector.tracePath[0]!,
|
|
387
|
+
label.anchorPoint,
|
|
388
|
+
)
|
|
389
|
+
? connector.tracePath.at(-1)!
|
|
390
|
+
: connector.tracePath[0]!
|
|
391
|
+
if (!this.isComponentAdjacentCorner(label, sourcePoint)) return []
|
|
392
|
+
|
|
393
|
+
const direction = label.orientation === "y+" ? 1 : -1
|
|
394
|
+
const maxSearchDistance = Math.min(
|
|
395
|
+
getMaxSearchDistance(this.inputProblem),
|
|
396
|
+
label.height * 2,
|
|
397
|
+
)
|
|
398
|
+
for (
|
|
399
|
+
let distance = WICK_CLEARANCE + LABEL_SEARCH_STEP;
|
|
400
|
+
distance <= maxSearchDistance + EPS;
|
|
401
|
+
distance += LABEL_SEARCH_STEP
|
|
402
|
+
) {
|
|
403
|
+
const anchorPoint = {
|
|
404
|
+
x: sourcePoint.x,
|
|
405
|
+
y: sourcePoint.y + direction * distance,
|
|
406
|
+
}
|
|
407
|
+
const center = getCenterFromAnchor(
|
|
408
|
+
anchorPoint,
|
|
409
|
+
label.orientation,
|
|
410
|
+
label.width,
|
|
411
|
+
label.height,
|
|
412
|
+
)
|
|
413
|
+
if (
|
|
414
|
+
this.intersectsAnyChip(getRectBounds(center, label.width, label.height))
|
|
415
|
+
) {
|
|
416
|
+
continue
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return [
|
|
420
|
+
{
|
|
421
|
+
anchorPoint,
|
|
422
|
+
traceId: connector.mspPairId,
|
|
423
|
+
distance: getDistance(anchorPoint, label.anchorPoint),
|
|
424
|
+
pinAligned: true,
|
|
425
|
+
reroutedTracePath: [sourcePoint, anchorPoint],
|
|
426
|
+
},
|
|
427
|
+
]
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return []
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private isComponentAdjacentCorner(label: NetLabelPlacement, corner: Point) {
|
|
434
|
+
return label.mspConnectionPairIds.some((traceId) => {
|
|
435
|
+
const trace = this.traceMap[traceId]
|
|
436
|
+
if (!trace || trace.tracePath.length < 2) return false
|
|
437
|
+
|
|
438
|
+
return [
|
|
439
|
+
{ point: trace.tracePath[0]!, neighbor: trace.tracePath[1]! },
|
|
440
|
+
{
|
|
441
|
+
point: trace.tracePath.at(-1)!,
|
|
442
|
+
neighbor: trace.tracePath.at(-2)!,
|
|
443
|
+
},
|
|
444
|
+
].some(
|
|
445
|
+
({ point, neighbor }) =>
|
|
446
|
+
this.pointsEqual(neighbor, corner) &&
|
|
447
|
+
trace.pins.some((pin) => this.pointsEqual(pin, point)),
|
|
448
|
+
)
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
|
|
326
452
|
private isConfiguredRailLabel(label: NetLabelPlacement) {
|
|
327
453
|
if (!label.netId) return false
|
|
328
454
|
return this.inputProblem.availableNetLabelOrientations[label.netId]?.some(
|
|
@@ -402,6 +402,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
402
402
|
),
|
|
403
403
|
netLabelPlacements:
|
|
404
404
|
instance.availableNetOrientationSolver!.outputNetLabelPlacements,
|
|
405
|
+
netLabelConnectorTraceIds:
|
|
406
|
+
instance.availableNetOrientationSolver!.netLabelConnectorTraceIds,
|
|
405
407
|
},
|
|
406
408
|
]
|
|
407
409
|
},
|
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
2
|
+
import { PipelineDebugger } from "site/components/PipelineDebugger"
|
|
3
|
+
import input from "../../tests/repros/assets/repro-am3352-sheet3-power-rails.input.json"
|
|
4
|
+
|
|
5
|
+
export default () => (
|
|
6
|
+
<PipelineDebugger inputProblem={structuredClone(input) as InputProblem} />
|
|
7
|
+
)
|