@tscircuit/fanout-solver 0.0.55 → 0.0.57

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
@@ -18,6 +18,7 @@ import {
18
18
  segmentsAreClear,
19
19
  } from "./geometry"
20
20
  import { getAllRoutedTraceCopper } from "./get-routed-trace-copper"
21
+ import { getDogboneSideVariants } from "./get-dogbone-side-variants"
21
22
  import { getViaSpanLayers } from "./layer-names"
22
23
  import { matchComponentDogboneViaSites } from "./match-component-dogbone-via-sites"
23
24
  import {
@@ -62,7 +63,7 @@ export interface RouteBusParams {
62
63
  fixedViaPointsByConnectionIndex?: ReadonlyMap<number, Point2D>
63
64
  reservedVias?: readonly ViaMinimalWindingReservedVia[]
64
65
  viaMinimalOnly?: boolean
65
- /** Permit a singleton to move its provisional via near the boundary. */
66
+ /** Permit a singleton or pair to move provisional vias near the boundary. */
66
67
  allowBoundarySideViaFallback?: boolean
67
68
  /** Reserve corner tuning space when selecting a boundary-side via. */
68
69
  preferCornerBoundaryVia?: boolean
@@ -2113,7 +2114,7 @@ function routePlaneTerminatedBus(
2113
2114
  otherPlans: [...acceptedPlans, ...fixedPlans],
2114
2115
  staticClearanceCache,
2115
2116
  blockingBusCounts,
2116
- cacheKey: `plane-fixed:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${candidateIndex}`,
2117
+ cacheKey: `plane-fixed:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${fixedViaPoint.x}:${fixedViaPoint.y}:${candidateIndex}`,
2117
2118
  srj,
2118
2119
  sharedBoundary: bus.sharedBoundary,
2119
2120
  clearance,
@@ -2604,6 +2605,7 @@ export function* routeBusAlternativesSteps(
2604
2605
  maximumRouteOrderAttempts?: number
2605
2606
  windingOrderIndex?: number
2606
2607
  preferTargetDirectedLaneBias?: boolean
2608
+ localDogboneRepair?: boolean
2607
2609
  }
2608
2610
  const maximumThroughAllRouteOrderAttempts = 24
2609
2611
  const windingTargetOrderCount = cornerSide
@@ -2836,6 +2838,48 @@ export function* routeBusAlternativesSteps(
2836
2838
  fixedViaPointsByConnectionIndex
2837
2839
  ? fixedViaTerminalPatterns
2838
2840
  : [...fixedViaTerminalPatterns, ...unreservedTerminalPatterns]
2841
+ if (
2842
+ !fixedViaPointsByConnectionIndex &&
2843
+ !allowBlindAndBuriedVias &&
2844
+ bus.connections.length > 1 &&
2845
+ bus.connections.every(
2846
+ (connection) => connection.sourceLayer !== targetLayer,
2847
+ ) &&
2848
+ viaHandednesses.includes(-1) &&
2849
+ viaHandednesses.includes(1)
2850
+ ) {
2851
+ // Keep the existing successful routes unchanged. Only after those site
2852
+ // assignments fail, try local inward dogbones that reopen a channel
2853
+ // closed by one or two pads in the same source row.
2854
+ const variants = getDogboneSideVariants(bus.connections, bus.direction)
2855
+ for (const handedness of viaHandednesses) {
2856
+ for (const flippedIndices of variants) {
2857
+ terminalPatterns.push({
2858
+ label: `local-dogbone-repair-${handedness}-${flippedIndices.join("-")}`,
2859
+ useViaInPad: false,
2860
+ getViaHandedness: () => handedness,
2861
+ maximumRouteOrderAttempts: 6,
2862
+ localDogboneRepair: true,
2863
+ getViaPoint: (connection) => {
2864
+ const point = getInitialViaPoint({
2865
+ preparedConnection: connection,
2866
+ bus,
2867
+ targetLayer,
2868
+ traceWidth,
2869
+ viaDiameter,
2870
+ clearance,
2871
+ viaHandedness: handedness,
2872
+ })
2873
+ if (!flippedIndices.includes(connection.connectionIndex))
2874
+ return point
2875
+ return bus.direction === "up" || bus.direction === "down"
2876
+ ? { x: point.x, y: 2 * connection.sourcePoint.y - point.y }
2877
+ : { x: 2 * connection.sourcePoint.x - point.x, y: point.y }
2878
+ },
2879
+ })
2880
+ }
2881
+ }
2882
+ }
2839
2883
  const seenTerminalSignatures = new Set<string>()
2840
2884
  for (const terminalPattern of terminalPatterns) {
2841
2885
  const terminals = bus.connections.map((preparedConnection) => {
@@ -2887,6 +2931,42 @@ export function* routeBusAlternativesSteps(
2887
2931
  const alignGridToPads =
2888
2932
  alignWindingGridToPads ||
2889
2933
  Boolean(terminalPattern.getViaPoint && !fixedViaPointsByConnectionIndex)
2934
+ if (
2935
+ terminalPattern.localDogboneRepair &&
2936
+ !matchComponentDogboneViaSites([bus], {
2937
+ viaDiameter,
2938
+ viaHoleDiameter,
2939
+ traceWidth,
2940
+ clearance,
2941
+ additionalObstacles: srj.obstacles,
2942
+ fixedViaPointsByConnectionIndex: new Map(
2943
+ terminals.map((terminal) => [
2944
+ terminal.connection.connectionIndex,
2945
+ terminal.viaPoint,
2946
+ ]),
2947
+ ),
2948
+ blockingSegments: acceptedPlans.flatMap((plan) =>
2949
+ getPlanSegments(plan).map((segment) => ({
2950
+ connectionIndex: plan.connectionIndex,
2951
+ segment,
2952
+ })),
2953
+ ),
2954
+ blockingVias: [
2955
+ ...acceptedPlans.flatMap((plan) =>
2956
+ getPlanVias(plan).map((via) => ({
2957
+ connectionIndex: plan.connectionIndex,
2958
+ center: via.center,
2959
+ diameter: via.diameter,
2960
+ spanLayers: via.spanLayers,
2961
+ })),
2962
+ ),
2963
+ // These are future connections, not members of the current bus.
2964
+ ...reservedVias.map(({ via }) => ({ connectionIndex: -1, ...via })),
2965
+ ],
2966
+ })
2967
+ ) {
2968
+ continue
2969
+ }
2890
2970
  const gridStepDivisor =
2891
2971
  terminalPattern.getViaPoint &&
2892
2972
  Math.min(bus.pitchX, bus.pitchY) -
@@ -2901,7 +2981,7 @@ export function* routeBusAlternativesSteps(
2901
2981
  )
2902
2982
  .join(
2903
2983
  "|",
2904
- )}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}:${gridStepDivisor}:${alignGridToPads}`
2984
+ )}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}:${gridStepDivisor}:${alignGridToPads}:${Boolean(terminalPattern.localDogboneRepair)}`
2905
2985
  if (seenTerminalSignatures.has(terminalSignature)) continue
2906
2986
  seenTerminalSignatures.add(terminalSignature)
2907
2987
  const windingSteps = routeViaMinimalWindingAlternativesSteps(
@@ -2921,6 +3001,7 @@ export function* routeBusAlternativesSteps(
2921
3001
  maximumRouteOrderAttempts: terminalPattern.maximumRouteOrderAttempts,
2922
3002
  adaptiveRouteOrder: adaptiveWindingRouteOrder,
2923
3003
  alignGridToPads,
3004
+ includeReverseTargetRotation: terminalPattern.localDogboneRepair,
2924
3005
  reservedVias,
2925
3006
  gridStepDivisor,
2926
3007
  preferTargetDirectedLaneBias:
@@ -2976,24 +3057,28 @@ export function* routeBusAlternativesSteps(
2976
3057
  }
2977
3058
  }
2978
3059
 
2979
- // A through-via does not have to sit next to the source pad. A singleton
3060
+ // A through-via does not have to sit next to the source pad. A narrow bus
2980
3061
  // embedded in another bus's source field can have every local dogbone site
2981
- // occupied while still having a clear top-layer escape. In that case, route
2982
- // to one via near the boundary and make the short final hop on the requested
2983
- // escape layer. This retains the one-via invariant and avoids weakening any
2984
- // clearance rule.
3062
+ // occupied while still having a clear source-layer escape. A pair first
3063
+ // relocates its two vias just outside the nearest package edge; a singleton
3064
+ // retains the boundary-side-via fallback. Both forms preserve one via per
3065
+ // signal without weakening any clearance rule.
2985
3066
  if (
2986
3067
  alternatives.length === 0 &&
2987
3068
  allowBoundarySideViaFallback &&
2988
3069
  fixedViaPointsByConnectionIndex &&
2989
- bus.connections.length === 1 &&
3070
+ bus.connections.length <= 2 &&
2990
3071
  bus.exitEdge &&
2991
- bus.connections[0]!.sourceLayer !== targetLayer
3072
+ bus.connections.every(
3073
+ (connection) =>
3074
+ connection.sourceLayer === bus.connections[0]!.sourceLayer &&
3075
+ connection.sourceLayer !== targetLayer,
3076
+ )
2992
3077
  ) {
2993
- const preparedConnection = bus.connections[0]!
3078
+ const sourceLayer = bus.connections[0]!.sourceLayer
2994
3079
  const boundaryDirection = getDirectionForExitEdge(bus.exitEdge)
2995
3080
  const boundaryExitAxis = getExitAxis(bus, boundaryDirection)
2996
- const finalTrack =
3081
+ const finalTracks = bus.connections.map((preparedConnection) =>
2997
3082
  preferCornerBoundaryVia && getCornerSide(bus)
2998
3083
  ? getCornerTargetTrack({
2999
3084
  bus,
@@ -3010,27 +3095,30 @@ export function* routeBusAlternativesSteps(
3010
3095
  bus,
3011
3096
  connection: preparedConnection,
3012
3097
  boundaryDirection,
3013
- })
3014
- const finalExitPoint = makePoint(
3015
- boundaryExitAxis,
3016
- finalTrack,
3017
- boundaryDirection,
3098
+ }),
3099
+ )
3100
+ const finalExitPoints = finalTracks.map((track) =>
3101
+ makePoint(boundaryExitAxis, track, boundaryDirection),
3018
3102
  )
3019
3103
  const sourceLayerSrj: SimpleRouteJson = {
3020
3104
  ...srj,
3021
- obstacles: srj.obstacles.map((obstacle) =>
3022
- obstacle === preparedConnection.sourceObstacle ||
3023
- (distance(obstacle.center, preparedConnection.sourcePoint) <= 1e-7 &&
3024
- obstacle.layers.includes(preparedConnection.sourceLayer))
3105
+ obstacles: srj.obstacles.map((obstacle) => {
3106
+ const connection = bus.connections.find(
3107
+ (connection) =>
3108
+ obstacle === connection.sourceObstacle ||
3109
+ (distance(obstacle.center, connection.sourcePoint) <= 1e-7 &&
3110
+ obstacle.layers.includes(connection.sourceLayer)),
3111
+ )
3112
+ return connection
3025
3113
  ? {
3026
3114
  ...obstacle,
3027
3115
  connectedTo: [
3028
3116
  ...obstacle.connectedTo,
3029
- preparedConnection.connection.name,
3117
+ connection.connection.name,
3030
3118
  ],
3031
3119
  }
3032
- : obstacle,
3033
- ),
3120
+ : obstacle
3121
+ }),
3034
3122
  }
3035
3123
  const insetStep = Math.max(
3036
3124
  viaDiameter / 2 + clearance,
@@ -3039,36 +3127,124 @@ export function* routeBusAlternativesSteps(
3039
3127
  const cornerSide = preferCornerBoundaryVia ? getCornerSide(bus) : undefined
3040
3128
  const boundaryViaCandidates = [1, 2, 3, 4, 5].flatMap((multiple) => {
3041
3129
  const inset = multiple * insetStep
3042
- const straight = makePoint(
3043
- boundaryExitAxis - directionSign(boundaryDirection) * inset,
3044
- finalTrack,
3045
- boundaryDirection,
3130
+ const straight = finalTracks.map((finalTrack) =>
3131
+ makePoint(
3132
+ boundaryExitAxis - directionSign(boundaryDirection) * inset,
3133
+ finalTrack,
3134
+ boundaryDirection,
3135
+ ),
3046
3136
  )
3047
3137
  if (!cornerSide) return [straight]
3048
3138
  // Approach corner exits diagonally to leave the adjacent pair's tuning
3049
3139
  // lane free of the through-via barrel. Retain the straight fallback.
3050
3140
  return [
3051
- makePoint(
3052
- boundaryExitAxis - directionSign(boundaryDirection) * inset,
3053
- finalTrack + (cornerSide === "maximum" ? inset : -inset),
3054
- boundaryDirection,
3141
+ finalTracks.map((finalTrack) =>
3142
+ makePoint(
3143
+ boundaryExitAxis - directionSign(boundaryDirection) * inset,
3144
+ finalTrack + (cornerSide === "maximum" ? inset : -inset),
3145
+ boundaryDirection,
3146
+ ),
3055
3147
  ),
3056
3148
  straight,
3057
3149
  ]
3058
3150
  })
3059
- for (const boundaryViaPoint of boundaryViaCandidates) {
3151
+ const sourceCenter = {
3152
+ x:
3153
+ bus.connections.reduce(
3154
+ (sum, connection) => sum + connection.sourcePoint.x,
3155
+ 0,
3156
+ ) / bus.connections.length,
3157
+ y:
3158
+ bus.connections.reduce(
3159
+ (sum, connection) => sum + connection.sourcePoint.y,
3160
+ 0,
3161
+ ) / bus.connections.length,
3162
+ }
3163
+ const directions = [
3164
+ { x: -1, y: 0, distance: sourceCenter.x - Math.min(...bus.xCoordinates) },
3165
+ { x: 1, y: 0, distance: Math.max(...bus.xCoordinates) - sourceCenter.x },
3166
+ { x: 0, y: -1, distance: sourceCenter.y - Math.min(...bus.yCoordinates) },
3167
+ { x: 0, y: 1, distance: Math.max(...bus.yCoordinates) - sourceCenter.y },
3168
+ ].toSorted((first, second) => first.distance - second.distance)
3169
+ const displacedViaCandidates =
3170
+ bus.connections.length === 2
3171
+ ? directions.flatMap((direction) =>
3172
+ [3].map((multiple) =>
3173
+ bus.connections.map((connection) => {
3174
+ const original = fixedViaPointsByConnectionIndex.get(
3175
+ connection.connectionIndex,
3176
+ )!
3177
+ return {
3178
+ x: original.x + direction.x * multiple * bus.pitchX,
3179
+ y: original.y + direction.y * multiple * bus.pitchY,
3180
+ }
3181
+ }),
3182
+ ),
3183
+ )
3184
+ : []
3185
+ const viaCandidates = [
3186
+ ...displacedViaCandidates.map((points) => ({
3187
+ points,
3188
+ boundarySide: false,
3189
+ })),
3190
+ ...boundaryViaCandidates.map((points) => ({
3191
+ points,
3192
+ boundarySide: true,
3193
+ })),
3194
+ ]
3195
+ for (const { points: boundaryViaPoints, boundarySide } of viaCandidates) {
3196
+ const boundaryVias = bus.connections.map((connection, index) => ({
3197
+ connectionName: connection.connection.name,
3198
+ via: {
3199
+ center: boundaryViaPoints[index]!,
3200
+ diameter: viaDiameter,
3201
+ spanLayers: getViaSpanLayers({
3202
+ fromLayer: sourceLayer,
3203
+ toLayer: targetLayer,
3204
+ layerNames,
3205
+ allowBlindAndBuriedVias,
3206
+ }),
3207
+ },
3208
+ }))
3209
+ if (
3210
+ boundaryVias.some((candidate, index) =>
3211
+ boundaryVias.some(
3212
+ (other, otherIndex) =>
3213
+ index !== otherIndex &&
3214
+ (distance(candidate.via.center, other.via.center) <
3215
+ viaDiameter + clearance - 1e-9 ||
3216
+ (boundarySide &&
3217
+ distancePointToSegment(
3218
+ candidate.via.center,
3219
+ other.via.center,
3220
+ finalExitPoints[otherIndex]!,
3221
+ ) <
3222
+ viaDiameter / 2 + traceWidth / 2 + clearance - 1e-9)),
3223
+ ),
3224
+ )
3225
+ )
3226
+ continue
3227
+ if (
3228
+ boundaryVias.some(({ via }) =>
3229
+ srj.obstacles.some(
3230
+ (obstacle) =>
3231
+ obstacle.layers.some((layer) => via.spanLayers.includes(layer)) &&
3232
+ distancePointToObstacle(via.center, obstacle) <
3233
+ via.diameter / 2 + clearance - 1e-9,
3234
+ ),
3235
+ )
3236
+ )
3237
+ continue
3060
3238
  const sourceLayerSteps = routeViaMinimalWindingAlternativesSteps(
3061
3239
  {
3062
3240
  srj: sourceLayerSrj,
3063
3241
  bus,
3064
- targetLayer: preparedConnection.sourceLayer,
3065
- terminals: [
3066
- {
3067
- connection: preparedConnection,
3068
- viaPoint: preparedConnection.sourcePoint,
3069
- exitPoint: boundaryViaPoint,
3070
- },
3071
- ],
3242
+ targetLayer: sourceLayer,
3243
+ terminals: bus.connections.map((preparedConnection, index) => ({
3244
+ connection: preparedConnection,
3245
+ viaPoint: preparedConnection.sourcePoint,
3246
+ exitPoint: boundaryViaPoints[index]!,
3247
+ })),
3072
3248
  acceptedPlans,
3073
3249
  layerNames,
3074
3250
  traceWidth,
@@ -3077,8 +3253,11 @@ export function* routeBusAlternativesSteps(
3077
3253
  clearance,
3078
3254
  allowBlindAndBuriedVias,
3079
3255
  allowSameNetMerges,
3080
- maximumRouteOrderAttempts: 3,
3081
- reservedVias,
3256
+ maximumRouteOrderAttempts: bus.connections.length === 1 ? 3 : 6,
3257
+ reservedVias:
3258
+ bus.connections.length > 1
3259
+ ? [...reservedVias, ...boundaryVias]
3260
+ : reservedVias,
3082
3261
  gridStepDivisor: 2,
3083
3262
  allowSourceLayerRouting: true,
3084
3263
  alignGridToPads: true,
@@ -3091,75 +3270,133 @@ export function* routeBusAlternativesSteps(
3091
3270
  yield {
3092
3271
  phase: "via-minimal-winding",
3093
3272
  busId: bus.busId,
3094
- targetLayer: preparedConnection.sourceLayer,
3273
+ targetLayer: sourceLayer,
3095
3274
  winding: sourceLayerResult.value,
3096
3275
  }
3097
3276
  sourceLayerResult = sourceLayerSteps.next()
3098
3277
  }
3099
- const sourceLayerPlan = sourceLayerResult.value[0]?.[0]
3100
- if (!sourceLayerPlan) continue
3101
- const targetSegment: RoutedSegment = {
3102
- start: boundaryViaPoint,
3103
- end: finalExitPoint,
3104
- width: traceWidth,
3105
- layer: targetLayer,
3106
- }
3107
- const via = {
3108
- center: boundaryViaPoint,
3109
- diameter: viaDiameter,
3110
- holeDiameter: viaHoleDiameter,
3111
- fromLayer: preparedConnection.sourceLayer,
3112
- toLayer: targetLayer,
3113
- spanLayers: getViaSpanLayers({
3114
- fromLayer: preparedConnection.sourceLayer,
3115
- toLayer: targetLayer,
3116
- layerNames,
3117
- allowBlindAndBuriedVias,
3118
- }),
3119
- }
3120
- const sourceRoute = sourceLayerPlan.trace.route.filter(
3121
- (item) => item.route_type === "wire",
3278
+ const sourceLayerPlans = sourceLayerResult.value[0]
3279
+ if (
3280
+ !sourceLayerPlans ||
3281
+ sourceLayerPlans.length !== bus.connections.length
3122
3282
  )
3123
- const plans: FanoutRoutePlan[] = [
3124
- {
3125
- ...sourceLayerPlan,
3126
- ...(preferCornerBoundaryVia
3127
- ? { sourceEscapeSegmentCount: sourceLayerPlan.segments.length }
3128
- : {}),
3129
- targetLayer,
3130
- exitPoint: finalExitPoint,
3131
- via,
3132
- segments: [...sourceLayerPlan.segments, targetSegment],
3133
- length:
3134
- sourceLayerPlan.length + distance(boundaryViaPoint, finalExitPoint),
3135
- trace: {
3136
- ...sourceLayerPlan.trace,
3137
- route: [
3138
- ...sourceRoute,
3139
- {
3140
- route_type: "via",
3141
- ...boundaryViaPoint,
3142
- from_layer: preparedConnection.sourceLayer,
3143
- to_layer: targetLayer,
3144
- via_diameter: viaDiameter,
3145
- via_hole_diameter: viaHoleDiameter,
3146
- },
3147
- {
3148
- route_type: "wire",
3149
- ...boundaryViaPoint,
3150
- width: traceWidth,
3151
- layer: targetLayer,
3152
- },
3153
- {
3154
- route_type: "wire",
3155
- ...finalExitPoint,
3156
- width: traceWidth,
3157
- layer: targetLayer,
3158
- },
3159
- ],
3283
+ continue
3284
+ let targetLayerPlans: FanoutRoutePlan[] | undefined
3285
+ if (!boundarySide) {
3286
+ const targetSteps = routeViaMinimalWindingAlternativesSteps(
3287
+ {
3288
+ srj,
3289
+ bus,
3290
+ targetLayer,
3291
+ terminals: bus.connections.map((connection, index) => ({
3292
+ connection,
3293
+ viaPoint: boundaryViaPoints[index]!,
3294
+ exitPoint: finalExitPoints[index]!,
3295
+ })),
3296
+ acceptedPlans,
3297
+ layerNames,
3298
+ traceWidth,
3299
+ viaDiameter,
3300
+ viaHoleDiameter,
3301
+ clearance,
3302
+ allowBlindAndBuriedVias,
3303
+ allowSameNetMerges,
3304
+ maximumRouteOrderAttempts: 6,
3305
+ reservedVias,
3306
+ gridStepDivisor: 2,
3307
+ alignGridToPads: true,
3160
3308
  },
3309
+ 1,
3310
+ includeVisualization,
3311
+ )
3312
+ let targetResult = targetSteps.next()
3313
+ while (!targetResult.done) {
3314
+ yield {
3315
+ phase: "via-minimal-winding",
3316
+ busId: bus.busId,
3317
+ targetLayer,
3318
+ winding: targetResult.value,
3319
+ }
3320
+ targetResult = targetSteps.next()
3321
+ }
3322
+ targetLayerPlans = targetResult.value[0]
3323
+ if (!targetLayerPlans) continue
3324
+ }
3325
+ const plans: FanoutRoutePlan[] = sourceLayerPlans.map(
3326
+ (sourceLayerPlan, index) => {
3327
+ const boundaryViaPoint = boundaryViaPoints[index]!
3328
+ const finalExitPoint = finalExitPoints[index]!
3329
+ const targetSegment: RoutedSegment = {
3330
+ start: boundaryViaPoint,
3331
+ end: finalExitPoint,
3332
+ width: traceWidth,
3333
+ layer: targetLayer,
3334
+ }
3335
+ const targetSegments = targetLayerPlans
3336
+ ? targetLayerPlans[index]!.segments.filter(
3337
+ (segment) => segment.layer === targetLayer,
3338
+ )
3339
+ : [targetSegment]
3340
+ const via = {
3341
+ center: boundaryViaPoint,
3342
+ diameter: viaDiameter,
3343
+ holeDiameter: viaHoleDiameter,
3344
+ fromLayer: sourceLayer,
3345
+ toLayer: targetLayer,
3346
+ spanLayers: getViaSpanLayers({
3347
+ fromLayer: sourceLayer,
3348
+ toLayer: targetLayer,
3349
+ layerNames,
3350
+ allowBlindAndBuriedVias,
3351
+ }),
3352
+ }
3353
+ const sourceRoute = sourceLayerPlan.trace.route.filter(
3354
+ (item) => item.route_type === "wire",
3355
+ )
3356
+ return {
3357
+ ...sourceLayerPlan,
3358
+ ...(preferCornerBoundaryVia || bus.connections.length > 1
3359
+ ? { sourceEscapeSegmentCount: sourceLayerPlan.segments.length }
3360
+ : {}),
3361
+ targetLayer,
3362
+ exitPoint: finalExitPoint,
3363
+ via,
3364
+ segments: [...sourceLayerPlan.segments, ...targetSegments],
3365
+ length:
3366
+ sourceLayerPlan.length +
3367
+ targetSegments.reduce(
3368
+ (sum, segment) => sum + distance(segment.start, segment.end),
3369
+ 0,
3370
+ ),
3371
+ trace: {
3372
+ ...sourceLayerPlan.trace,
3373
+ route: [
3374
+ ...sourceRoute,
3375
+ {
3376
+ route_type: "via",
3377
+ ...boundaryViaPoint,
3378
+ from_layer: sourceLayer,
3379
+ to_layer: targetLayer,
3380
+ via_diameter: viaDiameter,
3381
+ via_hole_diameter: viaHoleDiameter,
3382
+ },
3383
+ {
3384
+ route_type: "wire",
3385
+ ...boundaryViaPoint,
3386
+ width: traceWidth,
3387
+ layer: targetLayer,
3388
+ },
3389
+ ...targetSegments.map((segment) => ({
3390
+ route_type: "wire" as const,
3391
+ ...segment.end,
3392
+ width: traceWidth,
3393
+ layer: targetLayer,
3394
+ })),
3395
+ ],
3396
+ },
3397
+ }
3161
3398
  },
3162
- ]
3399
+ )
3163
3400
  const boundaryViaPlansAreClear = fanoutPlansAreClear({
3164
3401
  plans: [...acceptedPlans, ...plans],
3165
3402
  srj,
@@ -3168,35 +3405,39 @@ export function* routeBusAlternativesSteps(
3168
3405
  allowBlindAndBuriedVias,
3169
3406
  allowSameNetMerges,
3170
3407
  })
3171
- const reservedViasAreClear = reservedVias.every((reserved) => {
3172
- if (
3173
- reserved.connectionName === preparedConnection.connection.name ||
3174
- (allowSameNetMerges &&
3175
- connectionsShareElectricalNet(
3176
- srj,
3177
- reserved.connectionName,
3178
- preparedConnection.connection.name,
3179
- ))
3180
- )
3181
- return true
3182
- if (
3183
- via.spanLayers.some((layer) =>
3184
- reserved.via.spanLayers.includes(layer),
3185
- ) &&
3186
- distance(via.center, reserved.via.center) <
3187
- (via.diameter + reserved.via.diameter) / 2 + clearance - 1e-9
3188
- )
3189
- return false
3190
- return (
3191
- !reserved.via.spanLayers.includes(targetLayer) ||
3192
- distancePointToSegment(
3193
- reserved.via.center,
3194
- targetSegment.start,
3195
- targetSegment.end,
3196
- ) >=
3197
- reserved.via.diameter / 2 + traceWidth / 2 + clearance - 1e-9
3198
- )
3199
- })
3408
+ const reservedViasAreClear = plans.every((plan) =>
3409
+ reservedVias.every((reserved) => {
3410
+ const via = plan.via!
3411
+ if (
3412
+ reserved.connectionName === plan.connectionName ||
3413
+ (allowSameNetMerges &&
3414
+ connectionsShareElectricalNet(
3415
+ srj,
3416
+ reserved.connectionName,
3417
+ plan.connectionName,
3418
+ ))
3419
+ )
3420
+ return true
3421
+ if (
3422
+ via.spanLayers.some((layer) =>
3423
+ reserved.via.spanLayers.includes(layer),
3424
+ ) &&
3425
+ distance(via.center, reserved.via.center) <
3426
+ (via.diameter + reserved.via.diameter) / 2 + clearance - 1e-9
3427
+ )
3428
+ return false
3429
+ return plan.segments.every(
3430
+ (segment) =>
3431
+ !reserved.via.spanLayers.includes(segment.layer) ||
3432
+ distancePointToSegment(
3433
+ reserved.via.center,
3434
+ segment.start,
3435
+ segment.end,
3436
+ ) >=
3437
+ reserved.via.diameter / 2 + traceWidth / 2 + clearance - 1e-9,
3438
+ )
3439
+ }),
3440
+ )
3200
3441
  if (boundaryViaPlansAreClear && reservedViasAreClear) {
3201
3442
  addAlternative(plans)
3202
3443
  return alternatives