@tscircuit/fanout-solver 0.0.38 → 0.0.39

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/types.ts CHANGED
@@ -9,6 +9,24 @@ import type {
9
9
  import type { OriginalEndpointConnectivityReport } from "./validate-original-endpoint-connectivity"
10
10
  import type { RoutedCopperDrcReport } from "./validate-routed-copper-drc"
11
11
 
12
+ type CapacityRoutePoint = SimplifiedPcbTrace["route"][number]
13
+
14
+ export type FanoutViaRoutePoint = Extract<
15
+ CapacityRoutePoint,
16
+ { route_type: "via" }
17
+ > & {
18
+ /** Physical copper layers occupied by the via barrel. */
19
+ layers?: string[]
20
+ }
21
+
22
+ export type FanoutRoutePoint =
23
+ | Exclude<CapacityRoutePoint, { route_type: "via" }>
24
+ | FanoutViaRoutePoint
25
+
26
+ export type FanoutSimplifiedPcbTrace = Omit<SimplifiedPcbTrace, "route"> & {
27
+ route: FanoutRoutePoint[]
28
+ }
29
+
12
30
  export type FanoutDirection = "left" | "right" | "up" | "down"
13
31
 
14
32
  export type FanoutEdge = "left" | "right" | "top" | "bottom"
@@ -171,6 +189,12 @@ export interface FanoutSolverOptions {
171
189
  viaHoleDiameter?: number
172
190
  clearance?: number
173
191
  compactBusTracks?: boolean
192
+ /**
193
+ * Permit vias whose physical barrel only spans their logical route-layer
194
+ * transition. Defaults to true for compatibility. Set false for boards that
195
+ * manufacture every routed via through the complete copper stack.
196
+ */
197
+ allowBlindAndBuriedVias?: boolean
174
198
  /** Allow branches belonging to the same electrical net to share copper. */
175
199
  allowSameNetMerges?: boolean
176
200
  singleLayerPushAndShove?: boolean
@@ -207,9 +231,9 @@ export interface FanoutAttemptSummary {
207
231
  }
208
232
 
209
233
  export interface FanoutSolverOutput {
210
- simpleRouteJson: SimpleRouteJson
211
- fanoutTraces: SimplifiedPcbTrace[]
212
- completionTraces: SimplifiedPcbTrace[]
234
+ simpleRouteJson: SimpleRouteJsonWithFanoutPlanes
235
+ fanoutTraces: FanoutSimplifiedPcbTrace[]
236
+ completionTraces: FanoutSimplifiedPcbTrace[]
213
237
  endpointCompletion?: FanoutEndpointCompletionReport
214
238
  planeTerminations: FanoutPlaneTermination[]
215
239
  busLayerAssignments: Readonly<Record<string, string>>
@@ -351,13 +375,13 @@ export interface FanoutRoutePlan {
351
375
  /** Lower/left or upper/right band reserved along `exitEdge`. */
352
376
  cornerBandSide?: "minimum" | "maximum"
353
377
  exitPoint: Point2D
354
- trace: SimplifiedPcbTrace
378
+ trace: FanoutSimplifiedPcbTrace
355
379
  segments: RoutedSegment[]
356
380
  via?: RoutedVia
357
381
  /** Additional layer transitions used by an explicit winding channel. */
358
382
  additionalVias?: RoutedVia[]
359
383
  /** Optional capacitor-side dogbone reserved and emitted with a plane escape. */
360
- planeEndpointTrace?: SimplifiedPcbTrace
384
+ planeEndpointTrace?: FanoutSimplifiedPcbTrace
361
385
  planeEndpointSegments?: RoutedSegment[]
362
386
  planeEndpointVia?: RoutedVia
363
387
  length: number
@@ -380,12 +404,16 @@ export interface FanoutPlaneConnectivity {
380
404
  layer: string
381
405
  }
382
406
 
383
- export type SimpleRouteJsonWithFanoutPlanes = SimpleRouteJson & {
407
+ export type SimpleRouteJsonWithFanoutPlanes = Omit<
408
+ SimpleRouteJson,
409
+ "traces"
410
+ > & {
411
+ traces?: FanoutSimplifiedPcbTrace[]
384
412
  fanoutPlaneConnectivity?: FanoutPlaneConnectivity[]
385
413
  }
386
414
 
387
415
  export interface AssignmentAttempt {
388
416
  summary: FanoutAttemptSummary
389
417
  plans: FanoutRoutePlan[]
390
- outputSrj: SimpleRouteJson
418
+ outputSrj: SimpleRouteJsonWithFanoutPlanes
391
419
  }
@@ -288,7 +288,17 @@ function validatePlanStructure(params: {
288
288
  plan,
289
289
  )
290
290
  }
291
- if (plan.segments.length === 0 || plan.length <= EPSILON) {
291
+ const isAllowedViaInPadPlaneTermination =
292
+ (inputSrj as SimpleRouteJson & { allowViaInPad?: boolean })
293
+ .allowViaInPad === true &&
294
+ plan.termination.type === "plane" &&
295
+ plan.via !== undefined &&
296
+ pointsMatch(plan.via.center, plan.sourcePoint) &&
297
+ pointsMatch(plan.exitPoint, plan.sourcePoint)
298
+ if (
299
+ !isAllowedViaInPadPlaneTermination &&
300
+ (plan.segments.length === 0 || plan.length <= EPSILON)
301
+ ) {
292
302
  addIssue(
293
303
  issues,
294
304
  "not-broken-out",
@@ -316,7 +326,10 @@ function validatePlanStructure(params: {
316
326
  plan,
317
327
  )
318
328
  }
319
- if (!pointsMatch(plan.segments[0]!.start, plan.sourcePoint)) {
329
+ if (
330
+ plan.segments[0] &&
331
+ !pointsMatch(plan.segments[0].start, plan.sourcePoint)
332
+ ) {
320
333
  addIssue(
321
334
  issues,
322
335
  "disconnected-trace",
@@ -324,7 +337,10 @@ function validatePlanStructure(params: {
324
337
  plan,
325
338
  )
326
339
  }
327
- if (!pointsMatch(plan.segments.at(-1)!.end, plan.exitPoint)) {
340
+ if (
341
+ plan.segments.at(-1) &&
342
+ !pointsMatch(plan.segments.at(-1)!.end, plan.exitPoint)
343
+ ) {
328
344
  addIssue(
329
345
  issues,
330
346
  "disconnected-trace",
@@ -667,9 +683,10 @@ function validateClearances(params: {
667
683
  plans: readonly FanoutRoutePlan[]
668
684
  inputSrj: SimpleRouteJson
669
685
  clearance: number
686
+ allowBlindAndBuriedVias: boolean
670
687
  issues: FanoutValidationIssue[]
671
688
  }): void {
672
- const { plans, inputSrj, clearance, issues } = params
689
+ const { plans, inputSrj, clearance, allowBlindAndBuriedVias, issues } = params
673
690
  for (const plan of plans) {
674
691
  const segments = getPlanSegments(plan)
675
692
  for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
@@ -731,7 +748,10 @@ function validateClearances(params: {
731
748
  }
732
749
  }
733
750
 
734
- for (const traceCopper of getAllRoutedTraceCopper(inputSrj)) {
751
+ for (const traceCopper of getAllRoutedTraceCopper(
752
+ inputSrj,
753
+ allowBlindAndBuriedVias,
754
+ )) {
735
755
  if (
736
756
  plan.connectionName === traceCopper.connectionName ||
737
757
  connectionsShareElectricalNet(
@@ -917,6 +937,7 @@ export function validateFanoutSolution(params: {
917
937
  preparedBuses: readonly PreparedBus[]
918
938
  sharedBoundary: Bounds
919
939
  clearance: number
940
+ allowBlindAndBuriedVias?: boolean
920
941
  }): FanoutValidationReport {
921
942
  const {
922
943
  inputSrj,
@@ -925,6 +946,7 @@ export function validateFanoutSolution(params: {
925
946
  preparedBuses,
926
947
  sharedBoundary,
927
948
  clearance,
949
+ allowBlindAndBuriedVias = true,
928
950
  } = params
929
951
  const issues: FanoutValidationIssue[] = []
930
952
  const plansByConnection = new Map<string, FanoutRoutePlan[]>()
@@ -996,7 +1018,13 @@ export function validateFanoutSolution(params: {
996
1018
  sharedBoundary,
997
1019
  issues,
998
1020
  })
999
- validateClearances({ plans, inputSrj, clearance, issues })
1021
+ validateClearances({
1022
+ plans,
1023
+ inputSrj,
1024
+ clearance,
1025
+ allowBlindAndBuriedVias,
1026
+ issues,
1027
+ })
1000
1028
 
1001
1029
  return {
1002
1030
  valid: issues.length === 0,
@@ -3,6 +3,7 @@ import type {
3
3
  SimplifiedPcbTrace,
4
4
  } from "@tscircuit/capacity-autorouter"
5
5
  import {
6
+ circleFitsInsideObstacle,
6
7
  distance,
7
8
  distancePointToObstacle,
8
9
  distancePointToSegment,
@@ -10,7 +11,7 @@ import {
10
11
  pointIsInsideObstacle,
11
12
  segmentsAreClear,
12
13
  } from "./geometry"
13
- import { getCopperLayerNames, getLayerSpan } from "./layer-names"
14
+ import { getCopperLayerNames, getRouteViaSpanLayers } from "./layer-names"
14
15
  import {
15
16
  connectionsShareElectricalNet,
16
17
  obstacleSharesElectricalNet,
@@ -120,9 +121,17 @@ function extractTraceCopper(params: {
120
121
  trace: SimplifiedPcbTrace
121
122
  connectionName: string
122
123
  layerNames: string[]
124
+ allowBlindAndBuriedVias: boolean
123
125
  issues: RoutedCopperDrcIssue[]
124
126
  }): TraceCopper {
125
- const { srj, trace, connectionName, layerNames, issues } = params
127
+ const {
128
+ srj,
129
+ trace,
130
+ connectionName,
131
+ layerNames,
132
+ allowBlindAndBuriedVias,
133
+ issues,
134
+ } = params
126
135
  const segments: RoutedSegment[] = []
127
136
  const vias: RoutedVia[] = []
128
137
  let previousWire:
@@ -134,11 +143,16 @@ function extractTraceCopper(params: {
134
143
 
135
144
  for (const routePoint of trace.route) {
136
145
  if (routePoint.route_type === "via") {
137
- const spanLayers = getLayerSpan(
138
- routePoint.from_layer,
139
- routePoint.to_layer,
146
+ const spanLayers = getRouteViaSpanLayers({
147
+ fromLayer: routePoint.from_layer,
148
+ toLayer: routePoint.to_layer,
149
+ layers:
150
+ "layers" in routePoint && Array.isArray(routePoint.layers)
151
+ ? (routePoint.layers as string[])
152
+ : undefined,
140
153
  layerNames,
141
- )
154
+ allowBlindAndBuriedVias,
155
+ })
142
156
  vias.push({
143
157
  center: { x: routePoint.x, y: routePoint.y },
144
158
  diameter:
@@ -231,8 +245,14 @@ export function validateRoutedCopperDrc(params: {
231
245
  inputSrj: SimpleRouteJson
232
246
  routedSrj: SimpleRouteJson
233
247
  clearance: number
248
+ allowBlindAndBuriedVias?: boolean
234
249
  }): RoutedCopperDrcReport {
235
- const { inputSrj, routedSrj, clearance } = params
250
+ const {
251
+ inputSrj,
252
+ routedSrj,
253
+ clearance,
254
+ allowBlindAndBuriedVias = true,
255
+ } = params
236
256
  const issues: RoutedCopperDrcIssue[] = []
237
257
  const layerNames = getCopperLayerNames(routedSrj.layerCount)
238
258
  const traceCopper: TraceCopper[] = []
@@ -267,6 +287,7 @@ export function validateRoutedCopperDrc(params: {
267
287
  trace,
268
288
  connectionName,
269
289
  layerNames,
290
+ allowBlindAndBuriedVias,
270
291
  issues,
271
292
  }),
272
293
  )
@@ -309,7 +330,25 @@ export function validateRoutedCopperDrc(params: {
309
330
  const coincidentEndpoint = originalAndRoutedEndpoints.find(
310
331
  ({ point }) => distance(via.center, point) <= EPSILON,
311
332
  )
312
- if (coincidentEndpoint) {
333
+ const isAllowedContainedViaInPad =
334
+ (inputSrj as SimpleRouteJson & { allowViaInPad?: boolean })
335
+ .allowViaInPad === true &&
336
+ inputSrj.obstacles.some(
337
+ (obstacle) =>
338
+ obstacle.layers.some((layer) => via.spanLayers.includes(layer)) &&
339
+ obstacleSharesElectricalNet(
340
+ inputSrj,
341
+ obstacle,
342
+ copper.connectionName,
343
+ ) &&
344
+ circleFitsInsideObstacle({
345
+ center: via.center,
346
+ diameter: via.diameter,
347
+ obstacle,
348
+ tolerance: EPSILON,
349
+ }),
350
+ )
351
+ if (coincidentEndpoint && !isAllowedContainedViaInPad) {
313
352
  addIssue(issues, {
314
353
  code: "via-at-endpoint",
315
354
  traceId: copper.trace.pcb_trace_id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",