@tscircuit/fanout-solver 0.0.36 → 0.0.37

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/lib/route-bus.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  connectionsShareElectricalNet,
23
23
  obstacleSharesElectricalNet,
24
24
  } from "./net-identity"
25
+ import { routeViaMinimalWinding } from "./route-via-minimal-winding"
25
26
  import type {
26
27
  Bounds,
27
28
  FanoutDirection,
@@ -90,6 +91,91 @@ function getLocalCornerSide(
90
91
  )
91
92
  }
92
93
 
94
+ function busUsesCoordinatedWindingChannel(bus: PreparedBus): boolean {
95
+ return Boolean(
96
+ bus.exitEdge &&
97
+ bus.termination.type === "boundary" &&
98
+ bus.connections.length > 0 &&
99
+ bus.connections.every(
100
+ (connection) => connection.hasExplicitLayeredExitTarget === true,
101
+ ),
102
+ )
103
+ }
104
+
105
+ function getStableConnectionIdentity(
106
+ connection: PreparedConnection["connection"],
107
+ ): string {
108
+ if (
109
+ "source_trace_id" in connection &&
110
+ typeof connection.source_trace_id === "string"
111
+ ) {
112
+ return connection.source_trace_id
113
+ }
114
+ return connection.name
115
+ }
116
+
117
+ function getWindingTargetRank(params: {
118
+ bus: PreparedBus
119
+ connection: PreparedConnection
120
+ boundaryDirection: FanoutDirection
121
+ layerNames: readonly string[]
122
+ }): { rank: number; connectionCount: number } {
123
+ const { bus, connection, boundaryDirection, layerNames } = params
124
+ const orderedConnections = bus.connections.toSorted((first, second) => {
125
+ const axisDifference =
126
+ getPerpendicularAxis(
127
+ first.exitTargetPoint ?? first.targetPoint,
128
+ boundaryDirection,
129
+ ) -
130
+ getPerpendicularAxis(
131
+ second.exitTargetPoint ?? second.targetPoint,
132
+ boundaryDirection,
133
+ )
134
+ if (axisDifference !== 0) return axisDifference
135
+
136
+ // Equal target coordinates came from distinct target layers and do not
137
+ // impose a physical order after this bus is collapsed onto one escape
138
+ // layer. Keep that free choice independent of allowed-layer ordering so
139
+ // the local fanout can choose a deterministic, via-minimal permutation.
140
+ const identityDifference = first.connection.name.localeCompare(
141
+ second.connection.name,
142
+ )
143
+ if (identityDifference !== 0) return identityDifference
144
+
145
+ const firstStableId = getStableConnectionIdentity(first.connection)
146
+ const secondStableId = getStableConnectionIdentity(second.connection)
147
+ const stableIdentityDifference = firstStableId.localeCompare(secondStableId)
148
+ if (stableIdentityDifference !== 0) return stableIdentityDifference
149
+
150
+ const firstLayer = first.exitTargetPoint?.layer
151
+ const secondLayer = second.exitTargetPoint?.layer
152
+ return (
153
+ layerNames.indexOf(firstLayer ?? "") -
154
+ layerNames.indexOf(secondLayer ?? "") ||
155
+ first.connectionIndex - second.connectionIndex
156
+ )
157
+ })
158
+ const rank = orderedConnections.findIndex(
159
+ (candidate) => candidate.connectionIndex === connection.connectionIndex,
160
+ )
161
+ if (rank < 0) {
162
+ throw new Error(
163
+ `FanoutSolver: connection "${connection.connection.name}" is missing from winding order`,
164
+ )
165
+ }
166
+ return { rank, connectionCount: orderedConnections.length }
167
+ }
168
+
169
+ function getWindingCrossoverLayer(params: {
170
+ bus: PreparedBus
171
+ escapeLayer: string
172
+ }): string | undefined {
173
+ const { bus, escapeLayer } = params
174
+ return (bus.routableEscapeLayers ?? bus.allowedLayers)?.find(
175
+ (layer) => layer !== escapeLayer,
176
+ )
177
+ }
178
+
93
179
  function getCornerTargetTrack(params: {
94
180
  bus: PreparedBus
95
181
  connection: PreparedConnection
@@ -97,6 +183,7 @@ function getCornerTargetTrack(params: {
97
183
  traceWidth: number
98
184
  viaDiameter: number
99
185
  clearance: number
186
+ layerNames: readonly string[]
100
187
  }): number {
101
188
  const {
102
189
  bus,
@@ -105,6 +192,7 @@ function getCornerTargetTrack(params: {
105
192
  traceWidth,
106
193
  viaDiameter,
107
194
  clearance,
195
+ layerNames,
108
196
  } = params
109
197
  const side = getCornerSide(bus)
110
198
  if (!side || !bus.exitEdge) {
@@ -121,17 +209,27 @@ function getCornerTargetTrack(params: {
121
209
  ? bus.sharedBoundary.maxY
122
210
  : bus.sharedBoundary.maxX
123
211
  const pitch = Math.max(traceWidth + clearance, viaDiameter + clearance)
124
- const bandCenter =
212
+ const baseBandCenter =
125
213
  boundaryMinimum +
126
214
  (boundaryMaximum - boundaryMinimum) * (side === "minimum" ? 0.25 : 0.75)
215
+ const windingTarget = busUsesCoordinatedWindingChannel(bus)
216
+ ? getWindingTargetRank({
217
+ bus,
218
+ connection,
219
+ boundaryDirection,
220
+ layerNames,
221
+ })
222
+ : undefined
127
223
  const bandConnectionCount = Math.max(
128
224
  bus.connections.length,
129
225
  bus.cornerBandConnectionCount ?? bus.connections.length,
226
+ cornerExitLaneOffset + (windingTarget?.connectionCount ?? 0),
130
227
  )
131
- const firstTrack = bandCenter - ((bandConnectionCount - 1) * pitch) / 2
132
- const rank = getConnectionRank(bus, connection)
228
+ const firstTrack = baseBandCenter - ((bandConnectionCount - 1) * pitch) / 2
229
+ const rank = windingTarget?.rank ?? getConnectionRank(bus, connection)
133
230
  const globalSlot = cornerExitLaneOffset + rank
134
231
  const reverseSlotOrder =
232
+ !windingTarget &&
135
233
  (side === "maximum") === directionSign(boundaryDirection) > 0
136
234
  const orientedSlot = reverseSlotOrder
137
235
  ? bandConnectionCount - 1 - globalSlot
@@ -390,12 +488,16 @@ function getPreferredTrack(params: {
390
488
  connection: PreparedConnection
391
489
  traceWidth: number
392
490
  }): number {
393
- const preferredTrack = getCornerSide(params.bus)
394
- ? getPerpendicularAxis(params.connection.sourcePoint, params.bus.direction)
395
- : getPerpendicularAxis(
396
- params.connection.exitTargetPoint ?? params.connection.targetPoint,
397
- params.bus.direction,
398
- )
491
+ const preferredTrack =
492
+ getCornerSide(params.bus) || busUsesCoordinatedWindingChannel(params.bus)
493
+ ? getPerpendicularAxis(
494
+ params.connection.sourcePoint,
495
+ params.bus.direction,
496
+ )
497
+ : getPerpendicularAxis(
498
+ params.connection.exitTargetPoint ?? params.connection.targetPoint,
499
+ params.bus.direction,
500
+ )
399
501
  const boundaryMinimum = isHorizontal(params.bus.direction)
400
502
  ? params.bus.sharedBoundary.minY
401
503
  : params.bus.sharedBoundary.minX
@@ -577,6 +679,57 @@ function chamferOrthogonalPolyline(
577
679
  return chamfered
578
680
  }
579
681
 
682
+ function getInitialViaPoint(params: {
683
+ preparedConnection: PreparedConnection
684
+ bus: PreparedBus
685
+ targetLayer: string
686
+ traceWidth: number
687
+ viaDiameter: number
688
+ clearance: number
689
+ viaHandedness: ViaHandedness
690
+ }): Point2D {
691
+ const {
692
+ preparedConnection,
693
+ bus,
694
+ targetLayer,
695
+ traceWidth,
696
+ viaDiameter,
697
+ clearance,
698
+ viaHandedness,
699
+ } = params
700
+ const sourcePoint = {
701
+ x: preparedConnection.sourcePoint.x,
702
+ y: preparedConnection.sourcePoint.y,
703
+ }
704
+ const targetUsesVia = targetLayer !== preparedConnection.sourceLayer
705
+ const directionalPitch = getDirectionalPitch(bus)
706
+ const perpendicularPitch = getPerpendicularPitch(bus)
707
+ const directionalPadSize = isHorizontal(bus.direction)
708
+ ? preparedConnection.sourceObstacle.width
709
+ : preparedConnection.sourceObstacle.height
710
+ const initialEscapeDistance =
711
+ targetUsesVia && !busIsOnOutwardComponentEdge(bus)
712
+ ? directionalPadSize >= directionalPitch
713
+ ? directionalPadSize / 2 + viaDiameter / 2 + clearance + 1e-3
714
+ : directionalPitch * 0.5
715
+ : !targetUsesVia
716
+ ? directionalPadSize / 2 + traceWidth / 2 + clearance + 1e-3
717
+ : Math.max(
718
+ directionalPitch * 0.5,
719
+ directionalPadSize / 2 +
720
+ (targetUsesVia ? viaDiameter : traceWidth) / 2 +
721
+ clearance +
722
+ 1e-3,
723
+ )
724
+ const viaAxis =
725
+ getAxis(sourcePoint, bus.direction) +
726
+ directionSign(bus.direction) * initialEscapeDistance
727
+ const viaPerpendicularAxis =
728
+ getPerpendicularAxis(sourcePoint, bus.direction) +
729
+ viaHandedness * perpendicularPitch * 0.5
730
+ return makePoint(viaAxis, viaPerpendicularAxis, bus.direction)
731
+ }
732
+
580
733
  function buildPlan(params: {
581
734
  preparedConnection: PreparedConnection
582
735
  bus: PreparedBus
@@ -619,36 +772,27 @@ function buildPlan(params: {
619
772
  x: preparedConnection.sourcePoint.x,
620
773
  y: preparedConnection.sourcePoint.y,
621
774
  }
775
+ const escapeLayer = targetLayer
776
+ const windingCrossoverLayer = busUsesCoordinatedWindingChannel(bus)
777
+ ? getWindingCrossoverLayer({
778
+ bus,
779
+ escapeLayer,
780
+ })
781
+ : undefined
782
+ const usesLayeredWindingChannel = Boolean(windingCrossoverLayer)
622
783
  const sign = directionSign(bus.direction)
623
784
  const directionalPitch = getDirectionalPitch(bus)
624
- const perpendicularPitch = getPerpendicularPitch(bus)
625
- const targetUsesVia = targetLayer !== preparedConnection.sourceLayer
626
- const directionalPadSize = isHorizontal(bus.direction)
627
- ? preparedConnection.sourceObstacle.width
628
- : preparedConnection.sourceObstacle.height
629
- const initialEscapeDistance =
630
- targetUsesVia && !busIsOnOutwardComponentEdge(bus)
631
- ? directionalPadSize >= directionalPitch
632
- ? directionalPadSize / 2 + viaDiameter / 2 + clearance + 1e-3
633
- : directionalPitch * 0.5
634
- : !targetUsesVia
635
- ? directionalPadSize / 2 + traceWidth / 2 + clearance + 1e-3
636
- : Math.max(
637
- directionalPitch * 0.5,
638
- directionalPadSize / 2 +
639
- (targetUsesVia ? viaDiameter : traceWidth) / 2 +
640
- clearance +
641
- 1e-3,
642
- )
643
- const viaAxis =
644
- getAxis(sourcePoint, bus.direction) + sign * initialEscapeDistance
645
- const sourcePerpendicularAxis = getPerpendicularAxis(
646
- sourcePoint,
647
- bus.direction,
648
- )
649
- const viaPerpendicularAxis =
650
- sourcePerpendicularAxis + viaHandedness * perpendicularPitch * 0.5
651
- const viaPoint = makePoint(viaAxis, viaPerpendicularAxis, bus.direction)
785
+ const viaPoint = getInitialViaPoint({
786
+ preparedConnection,
787
+ bus,
788
+ targetLayer,
789
+ traceWidth,
790
+ viaDiameter,
791
+ clearance,
792
+ viaHandedness,
793
+ })
794
+ const viaAxis = getAxis(viaPoint, bus.direction)
795
+ const viaPerpendicularAxis = getPerpendicularAxis(viaPoint, bus.direction)
652
796
  const spreadLaneDistance =
653
797
  viaDiameter / 2 +
654
798
  traceWidth / 2 +
@@ -675,7 +819,7 @@ function buildPlan(params: {
675
819
  : bus.direction
676
820
  const boundarySign = directionSign(boundaryDirection)
677
821
  const boundaryExitAxis = getExitAxis(bus, boundaryDirection)
678
- const cornerTrack =
822
+ const boundaryTargetTrack =
679
823
  cornerSide && bus.exitEdge
680
824
  ? getCornerTargetTrack({
681
825
  bus,
@@ -684,8 +828,15 @@ function buildPlan(params: {
684
828
  traceWidth,
685
829
  viaDiameter,
686
830
  clearance,
831
+ layerNames,
687
832
  })
688
- : track
833
+ : usesLayeredWindingChannel
834
+ ? getPerpendicularAxis(
835
+ preparedConnection.exitTargetPoint ??
836
+ preparedConnection.targetPoint,
837
+ boundaryDirection,
838
+ )
839
+ : track
689
840
  const connectionRank = getConnectionRank(bus, preparedConnection)
690
841
  const localCornerSide = getLocalCornerSide(bus)
691
842
  const localChannelLaneIndex =
@@ -704,11 +855,11 @@ function buildPlan(params: {
704
855
  boundarySign > 0
705
856
  ? globalBoundarySlot
706
857
  : boundaryBandConnectionCount - 1 - globalBoundarySlot
858
+ const channelPitch = usesLayeredWindingChannel
859
+ ? viaDiameter / 2 + traceWidth / 2 + clearance
860
+ : traceWidth + clearance
707
861
  const channelInset = (laneIndex: number) =>
708
- viaDiameter / 2 +
709
- traceWidth / 2 +
710
- clearance +
711
- laneIndex * (traceWidth + clearance)
862
+ viaDiameter / 2 + traceWidth / 2 + clearance + laneIndex * channelPitch
712
863
  const localChannelAxis =
713
864
  getExitAxis(bus, bus.direction) - sign * channelInset(localChannelLaneIndex)
714
865
  const boundaryChannelAxis =
@@ -725,13 +876,17 @@ function buildPlan(params: {
725
876
  )
726
877
  const boundaryChannelTargetPoint = makePoint(
727
878
  boundaryChannelAxis,
728
- cornerTrack,
879
+ boundaryTargetTrack,
729
880
  boundaryDirection,
730
881
  )
882
+ const windingInputTransitionPoint =
883
+ isHorizontal(bus.direction) !== isHorizontal(boundaryDirection)
884
+ ? localChannelTargetPoint
885
+ : makePoint(boundaryChannelAxis, track, boundaryDirection)
731
886
  const exitPoint = terminateAtVia
732
887
  ? viaPoint
733
- : cornerSide
734
- ? makePoint(boundaryExitAxis, cornerTrack, boundaryDirection)
888
+ : cornerSide || usesLayeredWindingChannel
889
+ ? makePoint(boundaryExitAxis, boundaryTargetTrack, boundaryDirection)
735
890
  : makePoint(exitAxis, track, bus.direction)
736
891
  const segments: RoutedSegment[] = []
737
892
  const route: SimplifiedPcbTrace["route"] = []
@@ -792,40 +947,110 @@ function buildPlan(params: {
792
947
  })
793
948
  }
794
949
 
795
- const targetLayerPoints = terminateAtVia
796
- ? [viaPoint]
797
- : cornerSide
798
- ? chamferOrthogonalPolyline(
799
- [
800
- viaPoint,
801
- ...(useNestedSpread ? [spreadPoint] : []),
802
- doglegPoint,
803
- localChannelSourcePoint,
804
- ...(isHorizontal(bus.direction) !== isHorizontal(boundaryDirection)
805
- ? [localChannelTargetPoint]
806
- : []),
807
- boundaryChannelTargetPoint,
808
- exitPoint,
809
- ],
810
- Math.max(traceWidth + clearance, traceWidth * 2),
811
- )
812
- : useNestedSpread
950
+ const appendLayerPath = (points: Point2D[], layer: string): void => {
951
+ for (let index = 1; index < points.length; index++) {
952
+ const previousPoint = points[index - 1]!
953
+ const nextPoint = points[index]!
954
+ appendSegment(segments, previousPoint, nextPoint, traceWidth, layer)
955
+ route.push({
956
+ route_type: "wire",
957
+ x: nextPoint.x,
958
+ y: nextPoint.y,
959
+ width: traceWidth,
960
+ layer,
961
+ })
962
+ }
963
+ }
964
+
965
+ const additionalVias: NonNullable<FanoutRoutePlan["additionalVias"]> = []
966
+ if (usesLayeredWindingChannel && windingCrossoverLayer) {
967
+ const escapePoints = chamferOrthogonalPolyline(
968
+ [
969
+ viaPoint,
970
+ ...(useNestedSpread ? [spreadPoint] : []),
971
+ doglegPoint,
972
+ ...(isHorizontal(bus.direction) !== isHorizontal(boundaryDirection)
973
+ ? [localChannelSourcePoint]
974
+ : []),
975
+ windingInputTransitionPoint,
976
+ ],
977
+ Math.max(traceWidth + clearance, traceWidth * 2),
978
+ )
979
+ appendLayerPath(escapePoints, escapeLayer)
980
+
981
+ const appendTransitionVia = (
982
+ center: Point2D,
983
+ fromLayer: string,
984
+ toLayer: string,
985
+ ): void => {
986
+ if (fromLayer === toLayer) return
987
+ additionalVias.push({
988
+ center,
989
+ diameter: viaDiameter,
990
+ holeDiameter: viaHoleDiameter,
991
+ fromLayer,
992
+ toLayer,
993
+ spanLayers: getLayerSpan(fromLayer, toLayer, layerNames),
994
+ })
995
+ route.push({
996
+ route_type: "via",
997
+ x: center.x,
998
+ y: center.y,
999
+ from_layer: fromLayer,
1000
+ to_layer: toLayer,
1001
+ via_diameter: viaDiameter,
1002
+ via_hole_diameter: viaHoleDiameter,
1003
+ })
1004
+ route.push({
1005
+ route_type: "wire",
1006
+ x: center.x,
1007
+ y: center.y,
1008
+ width: traceWidth,
1009
+ layer: toLayer,
1010
+ })
1011
+ }
1012
+
1013
+ appendTransitionVia(
1014
+ windingInputTransitionPoint,
1015
+ escapeLayer,
1016
+ windingCrossoverLayer,
1017
+ )
1018
+ appendLayerPath(
1019
+ [windingInputTransitionPoint, boundaryChannelTargetPoint],
1020
+ windingCrossoverLayer,
1021
+ )
1022
+ appendTransitionVia(
1023
+ boundaryChannelTargetPoint,
1024
+ windingCrossoverLayer,
1025
+ escapeLayer,
1026
+ )
1027
+ appendLayerPath([boundaryChannelTargetPoint, exitPoint], escapeLayer)
1028
+ } else {
1029
+ const targetLayerPoints = terminateAtVia
1030
+ ? [viaPoint]
1031
+ : cornerSide
813
1032
  ? chamferOrthogonalPolyline(
814
- [viaPoint, spreadPoint, doglegPoint, exitPoint],
1033
+ [
1034
+ viaPoint,
1035
+ ...(useNestedSpread ? [spreadPoint] : []),
1036
+ doglegPoint,
1037
+ localChannelSourcePoint,
1038
+ ...(isHorizontal(bus.direction) !==
1039
+ isHorizontal(boundaryDirection)
1040
+ ? [localChannelTargetPoint]
1041
+ : []),
1042
+ boundaryChannelTargetPoint,
1043
+ exitPoint,
1044
+ ],
815
1045
  Math.max(traceWidth + clearance, traceWidth * 2),
816
1046
  )
817
- : [viaPoint, doglegPoint, exitPoint]
818
- for (let index = 1; index < targetLayerPoints.length; index++) {
819
- const previousPoint = targetLayerPoints[index - 1]!
820
- const nextPoint = targetLayerPoints[index]!
821
- appendSegment(segments, previousPoint, nextPoint, traceWidth, targetLayer)
822
- route.push({
823
- route_type: "wire",
824
- x: nextPoint.x,
825
- y: nextPoint.y,
826
- width: traceWidth,
827
- layer: targetLayer,
828
- })
1047
+ : useNestedSpread
1048
+ ? chamferOrthogonalPolyline(
1049
+ [viaPoint, spreadPoint, doglegPoint, exitPoint],
1050
+ Math.max(traceWidth + clearance, traceWidth * 2),
1051
+ )
1052
+ : [viaPoint, doglegPoint, exitPoint]
1053
+ appendLayerPath(targetLayerPoints, escapeLayer)
829
1054
  }
830
1055
 
831
1056
  const outputIds = createFanoutOutputIds({
@@ -841,7 +1066,7 @@ function buildPlan(params: {
841
1066
  sourceObstacle: preparedConnection.sourceObstacle,
842
1067
  sourceLayer: preparedConnection.sourceLayer,
843
1068
  targetPoint: preparedConnection.targetPoint,
844
- targetLayer,
1069
+ targetLayer: escapeLayer,
845
1070
  termination: bus.termination,
846
1071
  direction: bus.direction,
847
1072
  ...(bus.exitEdge ? { exitEdge: bus.exitEdge } : {}),
@@ -864,6 +1089,7 @@ function buildPlan(params: {
864
1089
  },
865
1090
  segments,
866
1091
  via,
1092
+ ...(additionalVias.length > 0 ? { additionalVias } : {}),
867
1093
  length: segments.reduce(
868
1094
  (total, segment) => total + distance(segment.start, segment.end),
869
1095
  0,
@@ -1121,9 +1347,11 @@ function getPlanSegments(plan: FanoutRoutePlan): RoutedSegment[] {
1121
1347
  }
1122
1348
 
1123
1349
  function getPlanVias(plan: FanoutRoutePlan) {
1124
- return [plan.via, plan.planeEndpointVia].filter(
1125
- (via): via is NonNullable<FanoutRoutePlan["via"]> => Boolean(via),
1126
- )
1350
+ return [
1351
+ plan.via,
1352
+ ...(plan.additionalVias ?? []),
1353
+ plan.planeEndpointVia,
1354
+ ].filter((via): via is NonNullable<FanoutRoutePlan["via"]> => Boolean(via))
1127
1355
  }
1128
1356
 
1129
1357
  function planIsStaticallyClear(params: {
@@ -1612,6 +1840,75 @@ export function routeBusAlternatives(
1612
1840
  alternatives.push(plans)
1613
1841
  }
1614
1842
 
1843
+ if (busUsesCoordinatedWindingChannel(bus) && bus.exitEdge) {
1844
+ const boundaryDirection = getDirectionForExitEdge(bus.exitEdge)
1845
+ const boundaryExitAxis = getExitAxis(bus, boundaryDirection)
1846
+ const cornerSide = getCornerSide(bus)
1847
+ for (const viaHandedness of viaHandednesses) {
1848
+ const terminals = bus.connections.map((preparedConnection) => {
1849
+ const boundaryTrack = cornerSide
1850
+ ? getCornerTargetTrack({
1851
+ bus,
1852
+ connection: preparedConnection,
1853
+ cornerExitLaneOffset: cornerLaneOffsets.exit,
1854
+ traceWidth,
1855
+ viaDiameter,
1856
+ clearance,
1857
+ layerNames,
1858
+ })
1859
+ : getPerpendicularAxis(
1860
+ preparedConnection.exitTargetPoint ??
1861
+ preparedConnection.targetPoint,
1862
+ boundaryDirection,
1863
+ )
1864
+ return {
1865
+ connection: preparedConnection,
1866
+ viaPoint: getInitialViaPoint({
1867
+ preparedConnection,
1868
+ bus,
1869
+ targetLayer,
1870
+ traceWidth,
1871
+ viaDiameter,
1872
+ clearance,
1873
+ viaHandedness,
1874
+ }),
1875
+ exitPoint: makePoint(
1876
+ boundaryExitAxis,
1877
+ boundaryTrack,
1878
+ boundaryDirection,
1879
+ ),
1880
+ }
1881
+ })
1882
+ const viaMinimalPlans = routeViaMinimalWinding({
1883
+ srj,
1884
+ bus,
1885
+ targetLayer,
1886
+ terminals,
1887
+ acceptedPlans,
1888
+ layerNames,
1889
+ traceWidth,
1890
+ viaDiameter,
1891
+ viaHoleDiameter,
1892
+ clearance,
1893
+ allowSameNetMerges,
1894
+ })
1895
+ if (
1896
+ !viaMinimalPlans ||
1897
+ !fanoutPlansAreClear({
1898
+ plans: [...acceptedPlans, ...viaMinimalPlans],
1899
+ srj,
1900
+ sharedBoundary: bus.sharedBoundary,
1901
+ clearance,
1902
+ allowSameNetMerges,
1903
+ })
1904
+ ) {
1905
+ continue
1906
+ }
1907
+ addAlternative(viaMinimalPlans)
1908
+ if (alternatives.length >= maxAlternatives) return alternatives
1909
+ }
1910
+ }
1911
+
1615
1912
  const searchConnectionOrder = (
1616
1913
  connectionOrder: PreparedConnection[],
1617
1914
  viaHandedness: ViaHandedness,