@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/README.md +7 -0
- package/lib/add-via-layer-metadata.ts +48 -0
- package/lib/complete-original-endpoints.ts +30 -2
- package/lib/fanout-solver.ts +636 -14
- package/lib/geometry.ts +21 -0
- package/lib/get-routed-trace-copper.ts +15 -6
- package/lib/index.ts +3 -0
- package/lib/layer-names.ts +40 -0
- package/lib/match-bus-lengths.ts +124 -14
- package/lib/match-component-dogbone-via-sites.ts +584 -0
- package/lib/route-bus.ts +872 -112
- package/lib/route-via-minimal-winding.ts +390 -74
- package/lib/types.ts +35 -7
- package/lib/validate-fanout-solution.ts +34 -6
- package/lib/validate-routed-copper-drc.ts +47 -8
- package/package.json +1 -1
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:
|
|
211
|
-
fanoutTraces:
|
|
212
|
-
completionTraces:
|
|
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:
|
|
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?:
|
|
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 =
|
|
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:
|
|
418
|
+
outputSrj: SimpleRouteJsonWithFanoutPlanes
|
|
391
419
|
}
|
|
@@ -288,7 +288,17 @@ function validatePlanStructure(params: {
|
|
|
288
288
|
plan,
|
|
289
289
|
)
|
|
290
290
|
}
|
|
291
|
-
|
|
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 (
|
|
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 (
|
|
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(
|
|
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({
|
|
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,
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
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,
|