@tscircuit/schematic-trace-solver 0.0.114 → 0.0.116
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 +22 -9
- package/dist/index.js +248 -3
- package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +71 -0
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +223 -0
- package/lib/solvers/SameNetJunctionAlignmentSolver/pathIntersectsAnyNetLabel.ts +22 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +9 -9
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts +25 -9
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +17 -0
- package/package.json +2 -2
- package/site/bug-reports/bug-report-20260730T061837Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.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-20260728T144234Z/bug-report-20260728T144234Z.test.ts +6 -0
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +123 -0
- package/tests/bug-reports/bug-report-20260730T061837Z/bug-report-20260730T061837Z.json +614 -0
- package/tests/bug-reports/bug-report-20260730T061837Z/bug-report-20260730T061837Z.test.ts +34 -0
- package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +110 -0
- package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +54 -0
- package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +56 -0
- package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +87 -0
- package/tests/repros/assets/board-15984-schematic-trace-input.json +518 -0
- package/tests/repros/assets/repro48-555-timer-vcc-rail-label.input.json +276 -0
- package/tests/repros/board-15984-same-net-junction.test.ts +30 -0
- package/tests/repros/repro-type-c-16pin-self-connection.test.ts +97 -0
- package/tests/repros/repro-type-c-16pin-to-resistor.test.ts +120 -0
- package/tests/repros/repro48-555-timer-vcc-rail-label.test.ts +23 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/text-box-obstacle-gap.test.ts +43 -0
package/dist/index.d.ts
CHANGED
|
@@ -146,17 +146,11 @@ type RectPadding = {
|
|
|
146
146
|
maxY?: number;
|
|
147
147
|
};
|
|
148
148
|
|
|
149
|
-
type
|
|
150
|
-
minX: number;
|
|
151
|
-
minY: number;
|
|
152
|
-
maxX: number;
|
|
153
|
-
maxY: number;
|
|
154
|
-
};
|
|
155
|
-
type ChipObstacleRect = RectBounds & {
|
|
149
|
+
type ChipObstacleRect = Bounds & {
|
|
156
150
|
kind: "chip";
|
|
157
151
|
chipId: string;
|
|
158
152
|
};
|
|
159
|
-
type TextBoxObstacleRect =
|
|
153
|
+
type TextBoxObstacleRect = Bounds & {
|
|
160
154
|
kind: "text_box";
|
|
161
155
|
textBox: NonNullable<InputProblem["textBoxes"]>[number];
|
|
162
156
|
};
|
|
@@ -1053,6 +1047,24 @@ declare class UnroutedTraceRecoverySolver extends BaseSolver {
|
|
|
1053
1047
|
visualize(): graphics_debug.GraphicsObject;
|
|
1054
1048
|
}
|
|
1055
1049
|
|
|
1050
|
+
interface SameNetJunctionAlignmentSolverInput {
|
|
1051
|
+
inputProblem: InputProblem;
|
|
1052
|
+
traces: SolvedTracePath[];
|
|
1053
|
+
netLabelPlacements: NetLabelPlacement[];
|
|
1054
|
+
}
|
|
1055
|
+
/** Turns separate same-net load traces into one shared rail with short junction branches. */
|
|
1056
|
+
declare class SameNetJunctionAlignmentSolver extends BaseSolver {
|
|
1057
|
+
private input;
|
|
1058
|
+
outputTraces: SolvedTracePath[];
|
|
1059
|
+
constructor(input: SameNetJunctionAlignmentSolverInput);
|
|
1060
|
+
_step(): void;
|
|
1061
|
+
getOutput(): {
|
|
1062
|
+
traces: SolvedTracePath[];
|
|
1063
|
+
netLabelPlacements: NetLabelPlacement[];
|
|
1064
|
+
};
|
|
1065
|
+
visualize(): GraphicsObject;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1056
1068
|
/**
|
|
1057
1069
|
* Pipeline solver that runs a series of solvers to find the best schematic layout.
|
|
1058
1070
|
* Coordinates the entire layout process from chip partitioning through final packing.
|
|
@@ -1087,13 +1099,14 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
1087
1099
|
netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver;
|
|
1088
1100
|
traceCleanupSolver2?: TraceCleanupSolver;
|
|
1089
1101
|
netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver;
|
|
1102
|
+
sameNetJunctionAlignmentSolver?: SameNetJunctionAlignmentSolver;
|
|
1090
1103
|
startTimeOfPhase: Record<string, number>;
|
|
1091
1104
|
endTimeOfPhase: Record<string, number>;
|
|
1092
1105
|
timeSpentOnPhase: Record<string, number>;
|
|
1093
1106
|
firstIterationOfPhase: Record<string, number>;
|
|
1094
1107
|
inputProblem: InputProblem;
|
|
1095
1108
|
hideRatsNet: boolean;
|
|
1096
|
-
pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof UnroutedTraceRecoverySolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof RailNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver> | PipelineStep<typeof NetLabelNetLabelCollisionSolver>)[];
|
|
1109
|
+
pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof UnroutedTraceRecoverySolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof RailNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver> | PipelineStep<typeof NetLabelNetLabelCollisionSolver> | PipelineStep<typeof SameNetJunctionAlignmentSolver>)[];
|
|
1097
1110
|
constructor(inputProblem: InputProblem, opts?: Options);
|
|
1098
1111
|
getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>;
|
|
1099
1112
|
currentPipelineStepIndex: number;
|
package/dist/index.js
CHANGED
|
@@ -1116,6 +1116,9 @@ var generateInternalSegmentCollisionDetours = ({
|
|
|
1116
1116
|
]).filter(hasOnlyNonzeroOrthogonalSegments);
|
|
1117
1117
|
};
|
|
1118
1118
|
|
|
1119
|
+
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
|
|
1120
|
+
import { boundsDistance } from "@tscircuit/math-utils";
|
|
1121
|
+
|
|
1119
1122
|
// lib/solvers/GuidelinesSolver/getInputChipBounds.ts
|
|
1120
1123
|
function getInputChipBounds(chip) {
|
|
1121
1124
|
const halfWidth = chip.width / 2;
|
|
@@ -1129,6 +1132,7 @@ function getInputChipBounds(chip) {
|
|
|
1129
1132
|
}
|
|
1130
1133
|
|
|
1131
1134
|
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
|
|
1135
|
+
var MIN_ROUTABLE_OBSTACLE_GAP = 0.05;
|
|
1132
1136
|
var isTextBoxObstacle = (obstacle) => obstacle.kind === "text_box";
|
|
1133
1137
|
var chipToRect = (chip) => {
|
|
1134
1138
|
const b = getInputChipBounds(chip);
|
|
@@ -1137,7 +1141,17 @@ var chipToRect = (chip) => {
|
|
|
1137
1141
|
var getObstacleRects = (problem, opts = {}) => {
|
|
1138
1142
|
const chipRects = problem.chips.map(chipToRect);
|
|
1139
1143
|
const textBoxRects = (problem.textBoxes ?? []).map((textBox) => {
|
|
1140
|
-
const
|
|
1144
|
+
const textBounds = getTextBoxBounds(textBox, opts.textBoxPadding);
|
|
1145
|
+
const attachedChipBounds = chipRects.find(
|
|
1146
|
+
(chip) => chip.chipId === textBox.chipId
|
|
1147
|
+
);
|
|
1148
|
+
const gap = attachedChipBounds ? boundsDistance(textBounds, attachedChipBounds) : Number.POSITIVE_INFINITY;
|
|
1149
|
+
const b = attachedChipBounds && gap > 0 && gap < MIN_ROUTABLE_OBSTACLE_GAP ? {
|
|
1150
|
+
minX: Math.min(textBounds.minX, attachedChipBounds.maxX),
|
|
1151
|
+
maxX: Math.max(textBounds.maxX, attachedChipBounds.minX),
|
|
1152
|
+
minY: Math.min(textBounds.minY, attachedChipBounds.maxY),
|
|
1153
|
+
maxY: Math.max(textBounds.maxY, attachedChipBounds.minY)
|
|
1154
|
+
} : textBounds;
|
|
1141
1155
|
return {
|
|
1142
1156
|
kind: "text_box",
|
|
1143
1157
|
textBox,
|
|
@@ -1207,9 +1221,9 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
1207
1221
|
this.textObstacles = new Set(this.obstacles.filter(isTextBoxObstacle));
|
|
1208
1222
|
const endpointChipIds = new Set(this.pins.map((pin) => pin.chipId));
|
|
1209
1223
|
this.endpointTextObstacles = new Set(
|
|
1210
|
-
|
|
1224
|
+
this.obstacles.filter(isTextBoxObstacle).filter(
|
|
1211
1225
|
(obstacle) => obstacle.textBox.chipId !== void 0 && endpointChipIds.has(obstacle.textBox.chipId)
|
|
1212
|
-
)
|
|
1226
|
+
)
|
|
1213
1227
|
);
|
|
1214
1228
|
const [pin1, pin2] = this.pins;
|
|
1215
1229
|
const directShortPath = calculateDirectShortPath(pin1, pin2);
|
|
@@ -10703,6 +10717,222 @@ var UnroutedTraceRecoverySolver = class extends BaseSolver {
|
|
|
10703
10717
|
}
|
|
10704
10718
|
};
|
|
10705
10719
|
|
|
10720
|
+
// lib/solvers/SameNetJunctionAlignmentSolver/pathIntersectsAnyNetLabel.ts
|
|
10721
|
+
var pathIntersectsAnyNetLabel = ({
|
|
10722
|
+
path,
|
|
10723
|
+
netLabelPlacements
|
|
10724
|
+
}) => {
|
|
10725
|
+
for (const label of netLabelPlacements) {
|
|
10726
|
+
const labelBounds = getRectBounds(label.center, label.width, label.height);
|
|
10727
|
+
for (let index = 0; index < path.length - 1; index++) {
|
|
10728
|
+
if (segmentIntersectsRect2(path[index], path[index + 1], labelBounds)) {
|
|
10729
|
+
return true;
|
|
10730
|
+
}
|
|
10731
|
+
}
|
|
10732
|
+
}
|
|
10733
|
+
return false;
|
|
10734
|
+
};
|
|
10735
|
+
|
|
10736
|
+
// lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts
|
|
10737
|
+
var MAX_ALIGNED_LOAD_PIN_OFFSET = 0.2;
|
|
10738
|
+
var getSharedPin = ({
|
|
10739
|
+
donorTrace,
|
|
10740
|
+
branchTrace
|
|
10741
|
+
}) => {
|
|
10742
|
+
const branchPinIds = new Set(branchTrace.pins.map((pin) => pin.pinId));
|
|
10743
|
+
return donorTrace.pins.find((pin) => branchPinIds.has(pin.pinId)) ?? null;
|
|
10744
|
+
};
|
|
10745
|
+
var getOtherPin = ({
|
|
10746
|
+
trace,
|
|
10747
|
+
sharedPin
|
|
10748
|
+
}) => trace.pins.find((pin) => pin.pinId !== sharedPin.pinId) ?? null;
|
|
10749
|
+
var getLongestHorizontalSegment = (trace) => {
|
|
10750
|
+
let longest = null;
|
|
10751
|
+
for (let index = 0; index < trace.tracePath.length - 1; index++) {
|
|
10752
|
+
const start = trace.tracePath[index];
|
|
10753
|
+
const end = trace.tracePath[index + 1];
|
|
10754
|
+
if (!isHorizontal2(start, end)) continue;
|
|
10755
|
+
if (!longest || Math.abs(end.x - start.x) > Math.abs(longest.end.x - longest.start.x)) {
|
|
10756
|
+
longest = { start, end };
|
|
10757
|
+
}
|
|
10758
|
+
}
|
|
10759
|
+
return longest;
|
|
10760
|
+
};
|
|
10761
|
+
var getJunctionPoint = ({
|
|
10762
|
+
segment,
|
|
10763
|
+
sharedPin
|
|
10764
|
+
}) => {
|
|
10765
|
+
const startDistance = Math.abs(segment.start.x - sharedPin.x);
|
|
10766
|
+
const endDistance = Math.abs(segment.end.x - sharedPin.x);
|
|
10767
|
+
if (startDistance <= endDistance) return segment.start;
|
|
10768
|
+
return segment.end;
|
|
10769
|
+
};
|
|
10770
|
+
var railIsOnFacingSide = ({
|
|
10771
|
+
railY,
|
|
10772
|
+
pin
|
|
10773
|
+
}) => {
|
|
10774
|
+
if (pin._facingDirection === "y+") return railY > pin.y;
|
|
10775
|
+
return false;
|
|
10776
|
+
};
|
|
10777
|
+
var getAlignedBranchPath = ({
|
|
10778
|
+
donorTrace,
|
|
10779
|
+
branchTrace
|
|
10780
|
+
}) => {
|
|
10781
|
+
const sharedPin = getSharedPin({ donorTrace, branchTrace });
|
|
10782
|
+
if (!sharedPin) return null;
|
|
10783
|
+
const donorOtherPin = getOtherPin({ trace: donorTrace, sharedPin });
|
|
10784
|
+
if (!donorOtherPin) return null;
|
|
10785
|
+
const otherPin = getOtherPin({ trace: branchTrace, sharedPin });
|
|
10786
|
+
if (!otherPin) return null;
|
|
10787
|
+
if (Math.abs(sharedPin.y - otherPin.y) > MAX_ALIGNED_LOAD_PIN_OFFSET) {
|
|
10788
|
+
return null;
|
|
10789
|
+
}
|
|
10790
|
+
const donorRail = getLongestHorizontalSegment(donorTrace);
|
|
10791
|
+
if (!donorRail) return null;
|
|
10792
|
+
const branchRail = getLongestHorizontalSegment(branchTrace);
|
|
10793
|
+
if (branchRail && nearlyEqual(branchRail.start.y, donorRail.start.y)) {
|
|
10794
|
+
return null;
|
|
10795
|
+
}
|
|
10796
|
+
if (!railIsOnFacingSide({ railY: donorRail.start.y, pin: otherPin })) {
|
|
10797
|
+
return null;
|
|
10798
|
+
}
|
|
10799
|
+
const junction = getJunctionPoint({ segment: donorRail, sharedPin });
|
|
10800
|
+
const extendsDonorRail = donorOtherPin.x < junction.x && otherPin.x > junction.x || donorOtherPin.x > junction.x && otherPin.x < junction.x;
|
|
10801
|
+
if (!extendsDonorRail) return null;
|
|
10802
|
+
const sharedToOther = simplifyPath([
|
|
10803
|
+
{ x: sharedPin.x, y: sharedPin.y },
|
|
10804
|
+
{ x: junction.x, y: sharedPin.y },
|
|
10805
|
+
{ x: junction.x, y: junction.y },
|
|
10806
|
+
{ x: otherPin.x, y: junction.y },
|
|
10807
|
+
{ x: otherPin.x, y: otherPin.y }
|
|
10808
|
+
]);
|
|
10809
|
+
if (branchTrace.pins[0].pinId === sharedPin.pinId) return sharedToOther;
|
|
10810
|
+
return [...sharedToOther].reverse();
|
|
10811
|
+
};
|
|
10812
|
+
var candidateIsClear = ({
|
|
10813
|
+
candidateTrace,
|
|
10814
|
+
traces,
|
|
10815
|
+
inputProblem,
|
|
10816
|
+
netLabelPlacements
|
|
10817
|
+
}) => {
|
|
10818
|
+
const obstacles = getObstacleRects(inputProblem);
|
|
10819
|
+
if (isPathCollidingWithObstacles(candidateTrace.tracePath, obstacles)) {
|
|
10820
|
+
return false;
|
|
10821
|
+
}
|
|
10822
|
+
const otherNetTraces = traces.filter(
|
|
10823
|
+
(trace) => trace.globalConnNetId !== candidateTrace.globalConnNetId
|
|
10824
|
+
);
|
|
10825
|
+
if (doesPathCoincideWithTraces(candidateTrace.tracePath, otherNetTraces)) {
|
|
10826
|
+
return false;
|
|
10827
|
+
}
|
|
10828
|
+
return !pathIntersectsAnyNetLabel({
|
|
10829
|
+
path: candidateTrace.tracePath,
|
|
10830
|
+
netLabelPlacements
|
|
10831
|
+
});
|
|
10832
|
+
};
|
|
10833
|
+
var alignSameNetJunctions = ({
|
|
10834
|
+
inputProblem,
|
|
10835
|
+
traces,
|
|
10836
|
+
netLabelPlacements
|
|
10837
|
+
}) => {
|
|
10838
|
+
let outputTraces = [...traces];
|
|
10839
|
+
const alignedBranchTraceIds = /* @__PURE__ */ new Set();
|
|
10840
|
+
let alignedJunctionCount = 0;
|
|
10841
|
+
for (const donorTraceId of traces.map((trace) => trace.mspPairId)) {
|
|
10842
|
+
const donorTrace = outputTraces.find(
|
|
10843
|
+
(trace) => trace.mspPairId === donorTraceId
|
|
10844
|
+
);
|
|
10845
|
+
for (const branchTrace of outputTraces) {
|
|
10846
|
+
if (alignedBranchTraceIds.has(branchTrace.mspPairId)) continue;
|
|
10847
|
+
if (donorTrace.mspPairId === branchTrace.mspPairId) continue;
|
|
10848
|
+
if (donorTrace.globalConnNetId !== branchTrace.globalConnNetId) continue;
|
|
10849
|
+
const candidatePath = getAlignedBranchPath({ donorTrace, branchTrace });
|
|
10850
|
+
if (!candidatePath) continue;
|
|
10851
|
+
const candidateTrace = { ...branchTrace, tracePath: candidatePath };
|
|
10852
|
+
const originalPair = [donorTrace, branchTrace];
|
|
10853
|
+
const candidatePair = [donorTrace, candidateTrace];
|
|
10854
|
+
const removesVisibleSegment = getVisibleTraceSegmentCount(candidatePair) < getVisibleTraceSegmentCount(originalPair);
|
|
10855
|
+
const shortensVisibleTrace = getVisibleTraceLength(candidatePair) < getVisibleTraceLength(originalPair) && !nearlyEqual(
|
|
10856
|
+
getVisibleTraceLength(candidatePair),
|
|
10857
|
+
getVisibleTraceLength(originalPair)
|
|
10858
|
+
);
|
|
10859
|
+
if (!removesVisibleSegment && !shortensVisibleTrace) {
|
|
10860
|
+
continue;
|
|
10861
|
+
}
|
|
10862
|
+
if (!candidateIsClear({
|
|
10863
|
+
candidateTrace,
|
|
10864
|
+
traces: outputTraces,
|
|
10865
|
+
inputProblem,
|
|
10866
|
+
netLabelPlacements
|
|
10867
|
+
})) {
|
|
10868
|
+
continue;
|
|
10869
|
+
}
|
|
10870
|
+
outputTraces = outputTraces.map((trace) => {
|
|
10871
|
+
if (trace.mspPairId === branchTrace.mspPairId) return candidateTrace;
|
|
10872
|
+
return trace;
|
|
10873
|
+
});
|
|
10874
|
+
alignedBranchTraceIds.add(branchTrace.mspPairId);
|
|
10875
|
+
alignedJunctionCount++;
|
|
10876
|
+
}
|
|
10877
|
+
}
|
|
10878
|
+
return { traces: outputTraces, alignedJunctionCount };
|
|
10879
|
+
};
|
|
10880
|
+
|
|
10881
|
+
// lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts
|
|
10882
|
+
var SameNetJunctionAlignmentSolver = class extends BaseSolver {
|
|
10883
|
+
input;
|
|
10884
|
+
outputTraces;
|
|
10885
|
+
constructor(input) {
|
|
10886
|
+
super();
|
|
10887
|
+
this.input = input;
|
|
10888
|
+
this.outputTraces = input.traces;
|
|
10889
|
+
}
|
|
10890
|
+
_step() {
|
|
10891
|
+
const result = alignSameNetJunctions(this.input);
|
|
10892
|
+
this.outputTraces = result.traces;
|
|
10893
|
+
this.stats.alignedJunctionCount = result.alignedJunctionCount;
|
|
10894
|
+
this.solved = true;
|
|
10895
|
+
}
|
|
10896
|
+
getOutput() {
|
|
10897
|
+
return {
|
|
10898
|
+
traces: this.outputTraces,
|
|
10899
|
+
netLabelPlacements: this.input.netLabelPlacements
|
|
10900
|
+
};
|
|
10901
|
+
}
|
|
10902
|
+
visualize() {
|
|
10903
|
+
const graphics = visualizeInputProblem(this.input.inputProblem);
|
|
10904
|
+
graphics.lines ??= [];
|
|
10905
|
+
graphics.rects ??= [];
|
|
10906
|
+
graphics.points ??= [];
|
|
10907
|
+
for (const trace of this.outputTraces) {
|
|
10908
|
+
graphics.lines.push({
|
|
10909
|
+
points: trace.tracePath,
|
|
10910
|
+
strokeColor: "purple"
|
|
10911
|
+
});
|
|
10912
|
+
}
|
|
10913
|
+
for (const label of this.input.netLabelPlacements) {
|
|
10914
|
+
const labelRect = {
|
|
10915
|
+
center: label.center,
|
|
10916
|
+
width: label.width,
|
|
10917
|
+
height: label.height,
|
|
10918
|
+
fill: getColorFromString(label.globalConnNetId, 0.35),
|
|
10919
|
+
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
10920
|
+
label: `netId: ${label.netId}
|
|
10921
|
+
globalConnNetId: ${label.globalConnNetId}`
|
|
10922
|
+
};
|
|
10923
|
+
graphics.rects.push(labelRect);
|
|
10924
|
+
graphics.points.push({
|
|
10925
|
+
x: label.anchorPoint.x,
|
|
10926
|
+
y: label.anchorPoint.y,
|
|
10927
|
+
color: getColorFromString(label.globalConnNetId, 0.9),
|
|
10928
|
+
label: `anchorPoint
|
|
10929
|
+
orientation: ${label.orientation}`
|
|
10930
|
+
});
|
|
10931
|
+
}
|
|
10932
|
+
return graphics;
|
|
10933
|
+
}
|
|
10934
|
+
};
|
|
10935
|
+
|
|
10706
10936
|
// lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
|
|
10707
10937
|
function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
|
|
10708
10938
|
return {
|
|
@@ -10733,6 +10963,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
10733
10963
|
netLabelTraceCollisionSolver;
|
|
10734
10964
|
traceCleanupSolver2;
|
|
10735
10965
|
netLabelNetLabelCollisionSolver;
|
|
10966
|
+
sameNetJunctionAlignmentSolver;
|
|
10736
10967
|
startTimeOfPhase;
|
|
10737
10968
|
endTimeOfPhase;
|
|
10738
10969
|
timeSpentOnPhase;
|
|
@@ -11005,6 +11236,20 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
11005
11236
|
netLabelPlacements: instance.netLabelTraceCollisionSolver.getOutput().netLabelPlacements
|
|
11006
11237
|
}
|
|
11007
11238
|
]
|
|
11239
|
+
),
|
|
11240
|
+
definePipelineStep(
|
|
11241
|
+
"sameNetJunctionAlignmentSolver",
|
|
11242
|
+
SameNetJunctionAlignmentSolver,
|
|
11243
|
+
(instance) => {
|
|
11244
|
+
const collisionOutput = instance.netLabelNetLabelCollisionSolver.getOutput();
|
|
11245
|
+
return [
|
|
11246
|
+
{
|
|
11247
|
+
inputProblem: instance.inputProblem,
|
|
11248
|
+
traces: instance.netLabelNetLabelCollisionSolver.traces,
|
|
11249
|
+
netLabelPlacements: collisionOutput.netLabelPlacements
|
|
11250
|
+
}
|
|
11251
|
+
];
|
|
11252
|
+
}
|
|
11008
11253
|
)
|
|
11009
11254
|
];
|
|
11010
11255
|
constructor(inputProblem, opts) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { GraphicsObject, Rect } from "graphics-debug"
|
|
2
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
6
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
8
|
+
import { alignSameNetJunctions } from "./alignSameNetJunctions"
|
|
9
|
+
|
|
10
|
+
interface SameNetJunctionAlignmentSolverInput {
|
|
11
|
+
inputProblem: InputProblem
|
|
12
|
+
traces: SolvedTracePath[]
|
|
13
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Turns separate same-net load traces into one shared rail with short junction branches. */
|
|
17
|
+
export class SameNetJunctionAlignmentSolver extends BaseSolver {
|
|
18
|
+
private input: SameNetJunctionAlignmentSolverInput
|
|
19
|
+
outputTraces: SolvedTracePath[]
|
|
20
|
+
|
|
21
|
+
constructor(input: SameNetJunctionAlignmentSolverInput) {
|
|
22
|
+
super()
|
|
23
|
+
this.input = input
|
|
24
|
+
this.outputTraces = input.traces
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override _step() {
|
|
28
|
+
const result = alignSameNetJunctions(this.input)
|
|
29
|
+
this.outputTraces = result.traces
|
|
30
|
+
this.stats.alignedJunctionCount = result.alignedJunctionCount
|
|
31
|
+
this.solved = true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getOutput() {
|
|
35
|
+
return {
|
|
36
|
+
traces: this.outputTraces,
|
|
37
|
+
netLabelPlacements: this.input.netLabelPlacements,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override visualize(): GraphicsObject {
|
|
42
|
+
const graphics = visualizeInputProblem(this.input.inputProblem)
|
|
43
|
+
graphics.lines ??= []
|
|
44
|
+
graphics.rects ??= []
|
|
45
|
+
graphics.points ??= []
|
|
46
|
+
for (const trace of this.outputTraces) {
|
|
47
|
+
graphics.lines.push({
|
|
48
|
+
points: trace.tracePath,
|
|
49
|
+
strokeColor: "purple",
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
for (const label of this.input.netLabelPlacements) {
|
|
53
|
+
const labelRect: Rect & { strokeColor: string } = {
|
|
54
|
+
center: label.center,
|
|
55
|
+
width: label.width,
|
|
56
|
+
height: label.height,
|
|
57
|
+
fill: getColorFromString(label.globalConnNetId, 0.35),
|
|
58
|
+
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
59
|
+
label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
|
|
60
|
+
}
|
|
61
|
+
graphics.rects.push(labelRect)
|
|
62
|
+
graphics.points.push({
|
|
63
|
+
x: label.anchorPoint.x,
|
|
64
|
+
y: label.anchorPoint.y,
|
|
65
|
+
color: getColorFromString(label.globalConnNetId, 0.9),
|
|
66
|
+
label: `anchorPoint\norientation: ${label.orientation}`,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
return graphics
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
+
import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
5
|
+
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
6
|
+
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
7
|
+
import {
|
|
8
|
+
getVisibleTraceLength,
|
|
9
|
+
getVisibleTraceSegmentCount,
|
|
10
|
+
isHorizontal,
|
|
11
|
+
nearlyEqual,
|
|
12
|
+
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
|
|
13
|
+
import type { InputPin, InputProblem } from "lib/types/InputProblem"
|
|
14
|
+
import { doesPathCoincideWithTraces } from "lib/utils/doesPathCoincideWithTraces"
|
|
15
|
+
import { pathIntersectsAnyNetLabel } from "./pathIntersectsAnyNetLabel"
|
|
16
|
+
|
|
17
|
+
interface AlignSameNetJunctionsInput {
|
|
18
|
+
inputProblem: InputProblem
|
|
19
|
+
traces: SolvedTracePath[]
|
|
20
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface HorizontalSegment {
|
|
24
|
+
start: Point
|
|
25
|
+
end: Point
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MAX_ALIGNED_LOAD_PIN_OFFSET = 0.2
|
|
29
|
+
|
|
30
|
+
const getSharedPin = ({
|
|
31
|
+
donorTrace,
|
|
32
|
+
branchTrace,
|
|
33
|
+
}: {
|
|
34
|
+
donorTrace: SolvedTracePath
|
|
35
|
+
branchTrace: SolvedTracePath
|
|
36
|
+
}): (InputPin & { chipId: string }) | null => {
|
|
37
|
+
const branchPinIds = new Set(branchTrace.pins.map((pin) => pin.pinId))
|
|
38
|
+
return donorTrace.pins.find((pin) => branchPinIds.has(pin.pinId)) ?? null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const getOtherPin = ({
|
|
42
|
+
trace,
|
|
43
|
+
sharedPin,
|
|
44
|
+
}: {
|
|
45
|
+
trace: SolvedTracePath
|
|
46
|
+
sharedPin: InputPin
|
|
47
|
+
}) => trace.pins.find((pin) => pin.pinId !== sharedPin.pinId) ?? null
|
|
48
|
+
|
|
49
|
+
const getLongestHorizontalSegment = (
|
|
50
|
+
trace: SolvedTracePath,
|
|
51
|
+
): HorizontalSegment | null => {
|
|
52
|
+
let longest: HorizontalSegment | null = null
|
|
53
|
+
for (let index = 0; index < trace.tracePath.length - 1; index++) {
|
|
54
|
+
const start = trace.tracePath[index]!
|
|
55
|
+
const end = trace.tracePath[index + 1]!
|
|
56
|
+
if (!isHorizontal(start, end)) continue
|
|
57
|
+
if (
|
|
58
|
+
!longest ||
|
|
59
|
+
Math.abs(end.x - start.x) > Math.abs(longest.end.x - longest.start.x)
|
|
60
|
+
) {
|
|
61
|
+
longest = { start, end }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return longest
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const getJunctionPoint = ({
|
|
68
|
+
segment,
|
|
69
|
+
sharedPin,
|
|
70
|
+
}: {
|
|
71
|
+
segment: HorizontalSegment
|
|
72
|
+
sharedPin: Point
|
|
73
|
+
}) => {
|
|
74
|
+
const startDistance = Math.abs(segment.start.x - sharedPin.x)
|
|
75
|
+
const endDistance = Math.abs(segment.end.x - sharedPin.x)
|
|
76
|
+
if (startDistance <= endDistance) return segment.start
|
|
77
|
+
return segment.end
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const railIsOnFacingSide = ({
|
|
81
|
+
railY,
|
|
82
|
+
pin,
|
|
83
|
+
}: {
|
|
84
|
+
railY: number
|
|
85
|
+
pin: InputPin
|
|
86
|
+
}) => {
|
|
87
|
+
if (pin._facingDirection === "y+") return railY > pin.y
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const getAlignedBranchPath = ({
|
|
92
|
+
donorTrace,
|
|
93
|
+
branchTrace,
|
|
94
|
+
}: {
|
|
95
|
+
donorTrace: SolvedTracePath
|
|
96
|
+
branchTrace: SolvedTracePath
|
|
97
|
+
}): Point[] | null => {
|
|
98
|
+
const sharedPin = getSharedPin({ donorTrace, branchTrace })
|
|
99
|
+
if (!sharedPin) return null
|
|
100
|
+
const donorOtherPin = getOtherPin({ trace: donorTrace, sharedPin })
|
|
101
|
+
if (!donorOtherPin) return null
|
|
102
|
+
const otherPin = getOtherPin({ trace: branchTrace, sharedPin })
|
|
103
|
+
if (!otherPin) return null
|
|
104
|
+
if (Math.abs(sharedPin.y - otherPin.y) > MAX_ALIGNED_LOAD_PIN_OFFSET) {
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const donorRail = getLongestHorizontalSegment(donorTrace)
|
|
109
|
+
if (!donorRail) return null
|
|
110
|
+
const branchRail = getLongestHorizontalSegment(branchTrace)
|
|
111
|
+
if (branchRail && nearlyEqual(branchRail.start.y, donorRail.start.y)) {
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
if (!railIsOnFacingSide({ railY: donorRail.start.y, pin: otherPin })) {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const junction = getJunctionPoint({ segment: donorRail, sharedPin })
|
|
119
|
+
const extendsDonorRail =
|
|
120
|
+
(donorOtherPin.x < junction.x && otherPin.x > junction.x) ||
|
|
121
|
+
(donorOtherPin.x > junction.x && otherPin.x < junction.x)
|
|
122
|
+
if (!extendsDonorRail) return null
|
|
123
|
+
|
|
124
|
+
const sharedToOther = simplifyPath([
|
|
125
|
+
{ x: sharedPin.x, y: sharedPin.y },
|
|
126
|
+
{ x: junction.x, y: sharedPin.y },
|
|
127
|
+
{ x: junction.x, y: junction.y },
|
|
128
|
+
{ x: otherPin.x, y: junction.y },
|
|
129
|
+
{ x: otherPin.x, y: otherPin.y },
|
|
130
|
+
])
|
|
131
|
+
|
|
132
|
+
if (branchTrace.pins[0].pinId === sharedPin.pinId) return sharedToOther
|
|
133
|
+
return [...sharedToOther].reverse()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const candidateIsClear = ({
|
|
137
|
+
candidateTrace,
|
|
138
|
+
traces,
|
|
139
|
+
inputProblem,
|
|
140
|
+
netLabelPlacements,
|
|
141
|
+
}: {
|
|
142
|
+
candidateTrace: SolvedTracePath
|
|
143
|
+
traces: SolvedTracePath[]
|
|
144
|
+
inputProblem: InputProblem
|
|
145
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
146
|
+
}) => {
|
|
147
|
+
const obstacles = getObstacleRects(inputProblem)
|
|
148
|
+
if (isPathCollidingWithObstacles(candidateTrace.tracePath, obstacles)) {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const otherNetTraces = traces.filter(
|
|
153
|
+
(trace) => trace.globalConnNetId !== candidateTrace.globalConnNetId,
|
|
154
|
+
)
|
|
155
|
+
if (doesPathCoincideWithTraces(candidateTrace.tracePath, otherNetTraces)) {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return !pathIntersectsAnyNetLabel({
|
|
160
|
+
path: candidateTrace.tracePath,
|
|
161
|
+
netLabelPlacements,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const alignSameNetJunctions = ({
|
|
166
|
+
inputProblem,
|
|
167
|
+
traces,
|
|
168
|
+
netLabelPlacements,
|
|
169
|
+
}: AlignSameNetJunctionsInput) => {
|
|
170
|
+
let outputTraces = [...traces]
|
|
171
|
+
const alignedBranchTraceIds = new Set<string>()
|
|
172
|
+
let alignedJunctionCount = 0
|
|
173
|
+
|
|
174
|
+
// Reuse each aligned branch as the rail for the next load in the chain.
|
|
175
|
+
for (const donorTraceId of traces.map((trace) => trace.mspPairId)) {
|
|
176
|
+
const donorTrace = outputTraces.find(
|
|
177
|
+
(trace) => trace.mspPairId === donorTraceId,
|
|
178
|
+
)!
|
|
179
|
+
for (const branchTrace of outputTraces) {
|
|
180
|
+
if (alignedBranchTraceIds.has(branchTrace.mspPairId)) continue
|
|
181
|
+
if (donorTrace.mspPairId === branchTrace.mspPairId) continue
|
|
182
|
+
if (donorTrace.globalConnNetId !== branchTrace.globalConnNetId) continue
|
|
183
|
+
|
|
184
|
+
const candidatePath = getAlignedBranchPath({ donorTrace, branchTrace })
|
|
185
|
+
if (!candidatePath) continue
|
|
186
|
+
const candidateTrace = { ...branchTrace, tracePath: candidatePath }
|
|
187
|
+
const originalPair = [donorTrace, branchTrace]
|
|
188
|
+
const candidatePair = [donorTrace, candidateTrace]
|
|
189
|
+
const removesVisibleSegment =
|
|
190
|
+
getVisibleTraceSegmentCount(candidatePair) <
|
|
191
|
+
getVisibleTraceSegmentCount(originalPair)
|
|
192
|
+
const shortensVisibleTrace =
|
|
193
|
+
getVisibleTraceLength(candidatePair) <
|
|
194
|
+
getVisibleTraceLength(originalPair) &&
|
|
195
|
+
!nearlyEqual(
|
|
196
|
+
getVisibleTraceLength(candidatePair),
|
|
197
|
+
getVisibleTraceLength(originalPair),
|
|
198
|
+
)
|
|
199
|
+
if (!removesVisibleSegment && !shortensVisibleTrace) {
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
if (
|
|
203
|
+
!candidateIsClear({
|
|
204
|
+
candidateTrace,
|
|
205
|
+
traces: outputTraces,
|
|
206
|
+
inputProblem,
|
|
207
|
+
netLabelPlacements,
|
|
208
|
+
})
|
|
209
|
+
) {
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
outputTraces = outputTraces.map((trace) => {
|
|
214
|
+
if (trace.mspPairId === branchTrace.mspPairId) return candidateTrace
|
|
215
|
+
return trace
|
|
216
|
+
})
|
|
217
|
+
alignedBranchTraceIds.add(branchTrace.mspPairId)
|
|
218
|
+
alignedJunctionCount++
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return { traces: outputTraces, alignedJunctionCount }
|
|
223
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
|
|
4
|
+
import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
5
|
+
|
|
6
|
+
export const pathIntersectsAnyNetLabel = ({
|
|
7
|
+
path,
|
|
8
|
+
netLabelPlacements,
|
|
9
|
+
}: {
|
|
10
|
+
path: Point[]
|
|
11
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
12
|
+
}) => {
|
|
13
|
+
for (const label of netLabelPlacements) {
|
|
14
|
+
const labelBounds = getRectBounds(label.center, label.width, label.height)
|
|
15
|
+
for (let index = 0; index < path.length - 1; index++) {
|
|
16
|
+
if (segmentIntersectsRect(path[index], path[index + 1], labelBounds)) {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false
|
|
22
|
+
}
|