@tscircuit/schematic-trace-solver 0.0.115 → 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 CHANGED
@@ -1047,6 +1047,24 @@ declare class UnroutedTraceRecoverySolver extends BaseSolver {
1047
1047
  visualize(): graphics_debug.GraphicsObject;
1048
1048
  }
1049
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
+
1050
1068
  /**
1051
1069
  * Pipeline solver that runs a series of solvers to find the best schematic layout.
1052
1070
  * Coordinates the entire layout process from chip partitioning through final packing.
@@ -1081,13 +1099,14 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
1081
1099
  netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver;
1082
1100
  traceCleanupSolver2?: TraceCleanupSolver;
1083
1101
  netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver;
1102
+ sameNetJunctionAlignmentSolver?: SameNetJunctionAlignmentSolver;
1084
1103
  startTimeOfPhase: Record<string, number>;
1085
1104
  endTimeOfPhase: Record<string, number>;
1086
1105
  timeSpentOnPhase: Record<string, number>;
1087
1106
  firstIterationOfPhase: Record<string, number>;
1088
1107
  inputProblem: InputProblem;
1089
1108
  hideRatsNet: boolean;
1090
- 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>)[];
1091
1110
  constructor(inputProblem: InputProblem, opts?: Options);
1092
1111
  getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>;
1093
1112
  currentPipelineStepIndex: number;
package/dist/index.js CHANGED
@@ -10717,6 +10717,222 @@ var UnroutedTraceRecoverySolver = class extends BaseSolver {
10717
10717
  }
10718
10718
  };
10719
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
+
10720
10936
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
10721
10937
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
10722
10938
  return {
@@ -10747,6 +10963,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10747
10963
  netLabelTraceCollisionSolver;
10748
10964
  traceCleanupSolver2;
10749
10965
  netLabelNetLabelCollisionSolver;
10966
+ sameNetJunctionAlignmentSolver;
10750
10967
  startTimeOfPhase;
10751
10968
  endTimeOfPhase;
10752
10969
  timeSpentOnPhase;
@@ -11019,6 +11236,20 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11019
11236
  netLabelPlacements: instance.netLabelTraceCollisionSolver.getOutput().netLabelPlacements
11020
11237
  }
11021
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
+ }
11022
11253
  )
11023
11254
  ];
11024
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
+ }
@@ -28,6 +28,7 @@ import { TraceAnchoredNetLabelOverlapSolver } from "../TraceAnchoredNetLabelOver
28
28
  import { NetLabelTraceCollisionSolver } from "../NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver"
29
29
  import { NetLabelNetLabelCollisionSolver } from "../NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver"
30
30
  import { UnroutedTraceRecoverySolver } from "../UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver"
31
+ import { SameNetJunctionAlignmentSolver } from "../SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver"
31
32
 
32
33
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
33
34
  solverName: string
@@ -86,6 +87,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
86
87
  netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
87
88
  traceCleanupSolver2?: TraceCleanupSolver
88
89
  netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver
90
+ sameNetJunctionAlignmentSolver?: SameNetJunctionAlignmentSolver
89
91
 
90
92
  startTimeOfPhase: Record<string, number>
91
93
  endTimeOfPhase: Record<string, number>
@@ -402,6 +404,21 @@ export class SchematicTracePipelineSolver extends BaseSolver {
402
404
  },
403
405
  ],
404
406
  ),
407
+ definePipelineStep(
408
+ "sameNetJunctionAlignmentSolver",
409
+ SameNetJunctionAlignmentSolver,
410
+ (instance) => {
411
+ const collisionOutput =
412
+ instance.netLabelNetLabelCollisionSolver!.getOutput()
413
+ return [
414
+ {
415
+ inputProblem: instance.inputProblem,
416
+ traces: instance.netLabelNetLabelCollisionSolver!.traces,
417
+ netLabelPlacements: collisionOutput.netLabelPlacements,
418
+ },
419
+ ]
420
+ },
421
+ ),
405
422
  ]
406
423
 
407
424
  constructor(inputProblem: InputProblem, opts?: Options) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.115",
4
+ "version": "0.0.116",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260730T061837Z/bug-report-20260730T061837Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />