@tscircuit/fanout-solver 0.0.24 → 0.0.25

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.
@@ -208,15 +208,14 @@ function getBusDistanceToBoundary(bus: PreparedBus): number {
208
208
  }
209
209
  }
210
210
 
211
- function busUsesOriginalEndpointTracks(bus: PreparedBus): boolean {
211
+ function busUsesDestinationGuidedTracks(bus: PreparedBus): boolean {
212
212
  const isHorizontal = bus.direction === "left" || bus.direction === "right"
213
213
  return bus.connections.some((connection) => {
214
+ const exitTargetPoint = connection.exitTargetPoint ?? connection.targetPoint
214
215
  const sourceTrack = isHorizontal
215
216
  ? connection.sourcePoint.y
216
217
  : connection.sourcePoint.x
217
- const targetTrack = isHorizontal
218
- ? connection.targetPoint.y
219
- : connection.targetPoint.x
218
+ const targetTrack = isHorizontal ? exitTargetPoint.y : exitTargetPoint.x
220
219
  return Math.abs(sourceTrack - targetTrack) > 1e-6
221
220
  })
222
221
  }
@@ -293,7 +292,7 @@ function createPreferredLayerAssignment(params: {
293
292
  )
294
293
  if (
295
294
  routableEscapeLayers.includes(sourceLayer) &&
296
- (busUsesOriginalEndpointTracks(bus) || busIsOnOutwardComponentEdge(bus))
295
+ (busUsesDestinationGuidedTracks(bus) || busIsOnOutwardComponentEdge(bus))
297
296
  ) {
298
297
  assignment[bus.busId] = sourceLayer
299
298
  } else if (viaLayers.length > 0) {
@@ -795,7 +794,7 @@ export class FanoutSolver extends BaseSolver {
795
794
  (count, bus) => {
796
795
  if (
797
796
  bus.termination.type !== "boundary" ||
798
- !busUsesOriginalEndpointTracks(bus)
797
+ !busUsesDestinationGuidedTracks(bus)
799
798
  ) {
800
799
  return count
801
800
  }
@@ -840,7 +839,7 @@ export class FanoutSolver extends BaseSolver {
840
839
  )
841
840
  }
842
841
  const sourceLayer = bus.connections[0]?.sourceLayer
843
- const preferSourceLayer = busUsesOriginalEndpointTracks(bus)
842
+ const preferSourceLayer = busUsesDestinationGuidedTracks(bus)
844
843
  const orderedLayers = candidateLayers.toSorted(
845
844
  (first, second) =>
846
845
  (layerLoads.get(first) ?? 0) - (layerLoads.get(second) ?? 0) ||
package/lib/index.ts CHANGED
@@ -35,6 +35,7 @@ export type {
35
35
  FanoutSolverOutput,
36
36
  FanoutValidationIssue,
37
37
  FanoutValidationReport,
38
+ Point2D,
38
39
  PreparedBus,
39
40
  SimpleRouteJsonWithFanoutPlanes,
40
41
  } from "./types"
@@ -643,6 +643,7 @@ function prepareConnection(params: {
643
643
  sourceGrid: ComponentGrid
644
644
  componentGrids: ComponentGrid[]
645
645
  termination: FanoutBusTermination
646
+ exitTargetPoint?: { x: number; y: number }
646
647
  }): PreparedConnection {
647
648
  const {
648
649
  connection,
@@ -650,6 +651,7 @@ function prepareConnection(params: {
650
651
  sourceGrid,
651
652
  componentGrids,
652
653
  termination,
654
+ exitTargetPoint,
653
655
  } = params
654
656
  for (
655
657
  let sourcePointIndex = 0;
@@ -671,6 +673,12 @@ function prepareConnection(params: {
671
673
  `FanoutSolver: connection "${connection.name}" has no source layer shared with its BGA pad`,
672
674
  )
673
675
  }
676
+ const targetPoint = chooseTargetPoint(
677
+ sourcePoint,
678
+ connection,
679
+ sourcePointIndex,
680
+ termination,
681
+ )
674
682
  return {
675
683
  connection,
676
684
  connectionIndex,
@@ -678,12 +686,8 @@ function prepareConnection(params: {
678
686
  sourcePointIndex,
679
687
  sourceLayer,
680
688
  sourceObstacle: sourceMatch.obstacle,
681
- targetPoint: chooseTargetPoint(
682
- sourcePoint,
683
- connection,
684
- sourcePointIndex,
685
- termination,
686
- ),
689
+ targetPoint,
690
+ exitTargetPoint: exitTargetPoint ?? targetPoint,
687
691
  }
688
692
  }
689
693
  throw new Error(
@@ -698,8 +702,10 @@ function inferDirection(
698
702
  let dx = 0
699
703
  let dy = 0
700
704
  for (const preparedConnection of connections) {
701
- dx += preparedConnection.targetPoint.x - preparedConnection.sourcePoint.x
702
- dy += preparedConnection.targetPoint.y - preparedConnection.sourcePoint.y
705
+ const exitTargetPoint =
706
+ preparedConnection.exitTargetPoint ?? preparedConnection.targetPoint
707
+ dx += exitTargetPoint.x - preparedConnection.sourcePoint.x
708
+ dy += exitTargetPoint.y - preparedConnection.sourcePoint.y
703
709
  }
704
710
  if (Math.abs(dx) < 1e-9 && Math.abs(dy) < 1e-9) {
705
711
  throw new Error(
@@ -941,6 +947,20 @@ export function prepareFanoutBuses(
941
947
  srj.connections.map((connection, index) => [connection.name, index]),
942
948
  )
943
949
  const resolvedBusInputs = resolveBusSpecs(srj, options).map((busSpec) => {
950
+ for (const [connectionName, point] of Object.entries(
951
+ busSpec.connectionExitTargets ?? {},
952
+ )) {
953
+ if (!busSpec.connectionNames.includes(connectionName)) {
954
+ throw new Error(
955
+ `FanoutSolver: connectionExitTargets contains connection "${connectionName}" outside bus "${busSpec.busId}"`,
956
+ )
957
+ }
958
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {
959
+ throw new Error(
960
+ `FanoutSolver: connectionExitTargets for connection "${connectionName}" must contain finite x and y coordinates`,
961
+ )
962
+ }
963
+ }
944
964
  const connections = busSpec.connectionNames.map((connectionName) => {
945
965
  const connectionIndex = connectionIndexByName.get(connectionName)
946
966
  if (connectionIndex === undefined) {
@@ -962,6 +982,7 @@ export function prepareFanoutBuses(
962
982
  sourceGrid,
963
983
  componentGrids,
964
984
  termination: busSpec.termination ?? { type: "boundary" },
985
+ exitTargetPoint: busSpec.connectionExitTargets?.[connection.name],
965
986
  }),
966
987
  )
967
988
  return { busSpec, sourceGrid, preparedConnections }
package/lib/route-bus.ts CHANGED
@@ -294,7 +294,7 @@ function getPreferredTrack(params: {
294
294
  connection: PreparedConnection
295
295
  }): number {
296
296
  return getPerpendicularAxis(
297
- params.connection.targetPoint,
297
+ params.connection.exitTargetPoint ?? params.connection.targetPoint,
298
298
  params.bus.direction,
299
299
  )
300
300
  }
@@ -197,7 +197,10 @@ function getCornerChannelPrefixes(params: {
197
197
  maximum: bus.componentBounds.maxX,
198
198
  }
199
199
  const directionalDistance = sign * (componentExitAxis - sourceAxis)
200
- const targetTrack = getPerpendicularAxis(connection.targetPoint, direction)
200
+ const targetTrack = getPerpendicularAxis(
201
+ connection.exitTargetPoint ?? connection.targetPoint,
202
+ direction,
203
+ )
201
204
  const lanePitch = traceWidth + clearance
202
205
  const exitAxis = getExitAxis(bus)
203
206
  const candidates = ([-1, 1] as const)
@@ -280,7 +283,7 @@ function completeCornerChannelRoute(params: {
280
283
  const minimumTrack = boundaryMinimum + traceWidth / 2
281
284
  const maximumTrack = boundaryMaximum - traceWidth / 2
282
285
  const targetTrack = getPerpendicularAxis(
283
- item.connection.targetPoint,
286
+ item.connection.exitTargetPoint ?? item.connection.targetPoint,
284
287
  direction,
285
288
  )
286
289
  const trackStep = lanePitch / 2
package/lib/types.ts CHANGED
@@ -63,6 +63,15 @@ export interface FanoutBusSpec extends SimpleRouteBus {
63
63
  sourceComponentId?: string
64
64
  direction?: FanoutDirection
65
65
  preferredExit?: FanoutBorderTarget
66
+ /**
67
+ * Preferred downstream routing point for each connection after it leaves the
68
+ * fanout boundary. This is routing guidance only and does not replace the
69
+ * connection's electrical endpoint in SimpleRouteJson.
70
+ *
71
+ * A caller coordinating two fanouts can pass the paired fanout's selected
72
+ * exit here so both sides aim toward the same track.
73
+ */
74
+ connectionExitTargets?: Readonly<Record<string, Point2D>>
66
75
  /**
67
76
  * Defaults to `{ type: "boundary" }`.
68
77
  *
@@ -209,6 +218,8 @@ export interface PreparedConnection {
209
218
  sourceLayer: string
210
219
  sourceObstacle: Obstacle
211
220
  targetPoint: ConnectionPoint
221
+ /** Preferred downstream point used to choose the boundary exit track. */
222
+ exitTargetPoint?: Point2D
212
223
  }
213
224
 
214
225
  export interface PreparedBus {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",