@tscircuit/fanout-solver 0.0.20 → 0.0.22
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 +25 -4
- package/lib/build-output.ts +30 -12
- package/lib/complete-original-endpoints.ts +1208 -0
- package/lib/fanout-solver.ts +150 -20
- package/lib/index.ts +15 -0
- package/lib/route-bus.ts +339 -87
- package/lib/route-single-layer-adaptive-exits.ts +1 -0
- package/lib/route-single-layer-push-shove.ts +1 -0
- package/lib/types.ts +50 -0
- package/lib/validate-fanout-solution.ts +152 -79
- package/lib/validate-original-endpoint-connectivity.ts +415 -0
- package/lib/validate-routed-copper-drc.ts +463 -0
- package/package.json +2 -2
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SimpleRouteJson,
|
|
3
|
+
SimplifiedPcbTrace,
|
|
4
|
+
} from "@tscircuit/capacity-autorouter"
|
|
5
|
+
import {
|
|
6
|
+
distance,
|
|
7
|
+
distancePointToObstacle,
|
|
8
|
+
distancePointToSegment,
|
|
9
|
+
distanceSegmentToObstacle,
|
|
10
|
+
pointIsInsideObstacle,
|
|
11
|
+
segmentsAreClear,
|
|
12
|
+
} from "./geometry"
|
|
13
|
+
import { getCopperLayerNames, getLayerSpan } from "./layer-names"
|
|
14
|
+
import {
|
|
15
|
+
connectionsShareElectricalNet,
|
|
16
|
+
obstacleSharesElectricalNet,
|
|
17
|
+
} from "./net-identity"
|
|
18
|
+
import type { Point2D, RoutedSegment } from "./types"
|
|
19
|
+
|
|
20
|
+
const EPSILON = 1e-6
|
|
21
|
+
|
|
22
|
+
export type RoutedCopperDrcIssueCode =
|
|
23
|
+
| "unknown-trace-connection"
|
|
24
|
+
| "unsupported-route-point"
|
|
25
|
+
| "disconnected-trace"
|
|
26
|
+
| "via-at-endpoint"
|
|
27
|
+
| "trace-obstacle-clearance"
|
|
28
|
+
| "via-obstacle-clearance"
|
|
29
|
+
| "different-net-trace-clearance"
|
|
30
|
+
| "different-net-trace-via-clearance"
|
|
31
|
+
| "different-net-via-clearance"
|
|
32
|
+
|
|
33
|
+
export interface RoutedCopperDrcIssue {
|
|
34
|
+
code: RoutedCopperDrcIssueCode
|
|
35
|
+
message: string
|
|
36
|
+
traceId: string
|
|
37
|
+
connectionName?: string
|
|
38
|
+
otherTraceId?: string
|
|
39
|
+
otherConnectionName?: string
|
|
40
|
+
layer?: string
|
|
41
|
+
obstacleId?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RoutedCopperDrcReport {
|
|
45
|
+
valid: boolean
|
|
46
|
+
checkedTraceCount: number
|
|
47
|
+
checkedSegmentCount: number
|
|
48
|
+
checkedViaCount: number
|
|
49
|
+
issues: RoutedCopperDrcIssue[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface RoutedVia {
|
|
53
|
+
center: Point2D
|
|
54
|
+
diameter: number
|
|
55
|
+
spanLayers: string[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface TraceCopper {
|
|
59
|
+
trace: SimplifiedPcbTrace
|
|
60
|
+
connectionName: string
|
|
61
|
+
segments: RoutedSegment[]
|
|
62
|
+
vias: RoutedVia[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function segmentIsLegalTerminalBodyEscape(params: {
|
|
66
|
+
inputSrj: SimpleRouteJson
|
|
67
|
+
segment: RoutedSegment
|
|
68
|
+
bodyObstacle: SimpleRouteJson["obstacles"][number]
|
|
69
|
+
connectionName: string
|
|
70
|
+
}): boolean {
|
|
71
|
+
const { inputSrj, segment, bodyObstacle, connectionName } = params
|
|
72
|
+
if (bodyObstacle.connectedTo.length > 0 || !bodyObstacle.componentId) {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
const sameNetComponentPads = inputSrj.obstacles.filter(
|
|
76
|
+
(obstacle) =>
|
|
77
|
+
obstacle.componentId === bodyObstacle.componentId &&
|
|
78
|
+
obstacle.layers.includes(segment.layer) &&
|
|
79
|
+
obstacleSharesElectricalNet(inputSrj, obstacle, connectionName),
|
|
80
|
+
)
|
|
81
|
+
for (const pad of sameNetComponentPads) {
|
|
82
|
+
for (const [terminal, other] of [
|
|
83
|
+
[segment.start, segment.end],
|
|
84
|
+
[segment.end, segment.start],
|
|
85
|
+
] as const) {
|
|
86
|
+
if (!pointIsInsideObstacle(terminal, pad, EPSILON)) continue
|
|
87
|
+
const outwardFromBody = {
|
|
88
|
+
x: terminal.x - bodyObstacle.center.x,
|
|
89
|
+
y: terminal.y - bodyObstacle.center.y,
|
|
90
|
+
}
|
|
91
|
+
const terminalToOther = {
|
|
92
|
+
x: other.x - terminal.x,
|
|
93
|
+
y: other.y - terminal.y,
|
|
94
|
+
}
|
|
95
|
+
if (
|
|
96
|
+
outwardFromBody.x * terminalToOther.x +
|
|
97
|
+
outwardFromBody.y * terminalToOther.y >
|
|
98
|
+
EPSILON
|
|
99
|
+
) {
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function pointsMatch(first: Point2D, second: Point2D): boolean {
|
|
108
|
+
return distance(first, second) <= EPSILON
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function addIssue(
|
|
112
|
+
issues: RoutedCopperDrcIssue[],
|
|
113
|
+
issue: RoutedCopperDrcIssue,
|
|
114
|
+
): void {
|
|
115
|
+
issues.push(issue)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function extractTraceCopper(params: {
|
|
119
|
+
srj: SimpleRouteJson
|
|
120
|
+
trace: SimplifiedPcbTrace
|
|
121
|
+
connectionName: string
|
|
122
|
+
layerNames: string[]
|
|
123
|
+
issues: RoutedCopperDrcIssue[]
|
|
124
|
+
}): TraceCopper {
|
|
125
|
+
const { srj, trace, connectionName, layerNames, issues } = params
|
|
126
|
+
const segments: RoutedSegment[] = []
|
|
127
|
+
const vias: RoutedVia[] = []
|
|
128
|
+
let previousWire:
|
|
129
|
+
| Extract<SimplifiedPcbTrace["route"][number], { route_type: "wire" }>
|
|
130
|
+
| undefined
|
|
131
|
+
let pendingVia:
|
|
132
|
+
| Extract<SimplifiedPcbTrace["route"][number], { route_type: "via" }>
|
|
133
|
+
| undefined
|
|
134
|
+
|
|
135
|
+
for (const routePoint of trace.route) {
|
|
136
|
+
if (routePoint.route_type === "via") {
|
|
137
|
+
const spanLayers = getLayerSpan(
|
|
138
|
+
routePoint.from_layer,
|
|
139
|
+
routePoint.to_layer,
|
|
140
|
+
layerNames,
|
|
141
|
+
)
|
|
142
|
+
vias.push({
|
|
143
|
+
center: { x: routePoint.x, y: routePoint.y },
|
|
144
|
+
diameter:
|
|
145
|
+
routePoint.via_diameter ?? srj.minViaPadDiameter ?? srj.minTraceWidth,
|
|
146
|
+
spanLayers,
|
|
147
|
+
})
|
|
148
|
+
if (
|
|
149
|
+
!previousWire ||
|
|
150
|
+
!pointsMatch(previousWire, routePoint) ||
|
|
151
|
+
previousWire.layer !== routePoint.from_layer
|
|
152
|
+
) {
|
|
153
|
+
addIssue(issues, {
|
|
154
|
+
code: "disconnected-trace",
|
|
155
|
+
traceId: trace.pcb_trace_id,
|
|
156
|
+
connectionName,
|
|
157
|
+
message: `Trace ${trace.pcb_trace_id} reaches a via without a matching ${routePoint.from_layer} wire endpoint`,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
pendingVia = routePoint
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (routePoint.route_type !== "wire") {
|
|
165
|
+
addIssue(issues, {
|
|
166
|
+
code: "unsupported-route-point",
|
|
167
|
+
traceId: trace.pcb_trace_id,
|
|
168
|
+
connectionName,
|
|
169
|
+
message: `Trace ${trace.pcb_trace_id} contains unsupported ${routePoint.route_type} geometry`,
|
|
170
|
+
})
|
|
171
|
+
previousWire = undefined
|
|
172
|
+
pendingVia = undefined
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (pendingVia) {
|
|
177
|
+
if (
|
|
178
|
+
!pointsMatch(routePoint, pendingVia) ||
|
|
179
|
+
routePoint.layer !== pendingVia.to_layer
|
|
180
|
+
) {
|
|
181
|
+
addIssue(issues, {
|
|
182
|
+
code: "disconnected-trace",
|
|
183
|
+
traceId: trace.pcb_trace_id,
|
|
184
|
+
connectionName,
|
|
185
|
+
message: `Trace ${trace.pcb_trace_id} does not continue from its via on ${pendingVia.to_layer}`,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
previousWire = routePoint
|
|
189
|
+
pendingVia = undefined
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (previousWire) {
|
|
194
|
+
if (previousWire.layer !== routePoint.layer) {
|
|
195
|
+
addIssue(issues, {
|
|
196
|
+
code: "disconnected-trace",
|
|
197
|
+
traceId: trace.pcb_trace_id,
|
|
198
|
+
connectionName,
|
|
199
|
+
message: `Trace ${trace.pcb_trace_id} changes from ${previousWire.layer} to ${routePoint.layer} without a via`,
|
|
200
|
+
})
|
|
201
|
+
} else if (!pointsMatch(previousWire, routePoint)) {
|
|
202
|
+
segments.push({
|
|
203
|
+
start: { x: previousWire.x, y: previousWire.y },
|
|
204
|
+
end: { x: routePoint.x, y: routePoint.y },
|
|
205
|
+
width: routePoint.width,
|
|
206
|
+
layer: routePoint.layer,
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
previousWire = routePoint
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (pendingVia) {
|
|
214
|
+
addIssue(issues, {
|
|
215
|
+
code: "disconnected-trace",
|
|
216
|
+
traceId: trace.pcb_trace_id,
|
|
217
|
+
connectionName,
|
|
218
|
+
message: `Trace ${trace.pcb_trace_id} ends at a via without a wire on ${pendingVia.to_layer}`,
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return { trace, connectionName, segments, vias }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Independently audits every emitted trace and via against the original SRJ
|
|
227
|
+
* obstacle field and every other emitted net. This deliberately does not rely
|
|
228
|
+
* on the solver's accepted route-plan objects.
|
|
229
|
+
*/
|
|
230
|
+
export function validateRoutedCopperDrc(params: {
|
|
231
|
+
inputSrj: SimpleRouteJson
|
|
232
|
+
routedSrj: SimpleRouteJson
|
|
233
|
+
clearance: number
|
|
234
|
+
}): RoutedCopperDrcReport {
|
|
235
|
+
const { inputSrj, routedSrj, clearance } = params
|
|
236
|
+
const issues: RoutedCopperDrcIssue[] = []
|
|
237
|
+
const layerNames = getCopperLayerNames(routedSrj.layerCount)
|
|
238
|
+
const traceCopper: TraceCopper[] = []
|
|
239
|
+
const originalAndRoutedEndpoints = [inputSrj, routedSrj].flatMap((srj) =>
|
|
240
|
+
srj.connections.flatMap((connection) =>
|
|
241
|
+
connection.pointsToConnect.map((point) => ({
|
|
242
|
+
connectionName: connection.name,
|
|
243
|
+
point,
|
|
244
|
+
})),
|
|
245
|
+
),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
for (const trace of routedSrj.traces ?? []) {
|
|
249
|
+
const connectionName = trace.connection_name
|
|
250
|
+
if (
|
|
251
|
+
!connectionName ||
|
|
252
|
+
!inputSrj.connections.some(
|
|
253
|
+
(connection) => connection.name === connectionName,
|
|
254
|
+
)
|
|
255
|
+
) {
|
|
256
|
+
addIssue(issues, {
|
|
257
|
+
code: "unknown-trace-connection",
|
|
258
|
+
traceId: trace.pcb_trace_id,
|
|
259
|
+
...(connectionName ? { connectionName } : {}),
|
|
260
|
+
message: `Trace ${trace.pcb_trace_id} does not identify an input connection`,
|
|
261
|
+
})
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
traceCopper.push(
|
|
265
|
+
extractTraceCopper({
|
|
266
|
+
srj: routedSrj,
|
|
267
|
+
trace,
|
|
268
|
+
connectionName,
|
|
269
|
+
layerNames,
|
|
270
|
+
issues,
|
|
271
|
+
}),
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for (const copper of traceCopper) {
|
|
276
|
+
for (const segment of copper.segments) {
|
|
277
|
+
for (const obstacle of inputSrj.obstacles) {
|
|
278
|
+
if (
|
|
279
|
+
!obstacle.layers.includes(segment.layer) ||
|
|
280
|
+
obstacleSharesElectricalNet(inputSrj, obstacle, copper.connectionName)
|
|
281
|
+
) {
|
|
282
|
+
continue
|
|
283
|
+
}
|
|
284
|
+
if (
|
|
285
|
+
segmentIsLegalTerminalBodyEscape({
|
|
286
|
+
inputSrj,
|
|
287
|
+
segment,
|
|
288
|
+
bodyObstacle: obstacle,
|
|
289
|
+
connectionName: copper.connectionName,
|
|
290
|
+
})
|
|
291
|
+
) {
|
|
292
|
+
continue
|
|
293
|
+
}
|
|
294
|
+
const actual = distanceSegmentToObstacle(segment, obstacle)
|
|
295
|
+
const required = segment.width / 2 + clearance
|
|
296
|
+
if (actual < required - EPSILON) {
|
|
297
|
+
addIssue(issues, {
|
|
298
|
+
code: "trace-obstacle-clearance",
|
|
299
|
+
traceId: copper.trace.pcb_trace_id,
|
|
300
|
+
connectionName: copper.connectionName,
|
|
301
|
+
layer: segment.layer,
|
|
302
|
+
obstacleId: obstacle.obstacleId,
|
|
303
|
+
message: `Trace ${copper.trace.pcb_trace_id} on ${segment.layer} is ${actual.toFixed(4)}mm from different-net obstacle ${obstacle.obstacleId}; ${required.toFixed(4)}mm is required`,
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
for (const via of copper.vias) {
|
|
309
|
+
const coincidentEndpoint = originalAndRoutedEndpoints.find(
|
|
310
|
+
({ point }) => distance(via.center, point) <= EPSILON,
|
|
311
|
+
)
|
|
312
|
+
if (coincidentEndpoint) {
|
|
313
|
+
addIssue(issues, {
|
|
314
|
+
code: "via-at-endpoint",
|
|
315
|
+
traceId: copper.trace.pcb_trace_id,
|
|
316
|
+
connectionName: copper.connectionName,
|
|
317
|
+
otherConnectionName: coincidentEndpoint.connectionName,
|
|
318
|
+
message: `Via in ${copper.trace.pcb_trace_id} is placed directly at an original or routed connection endpoint`,
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
for (const obstacle of inputSrj.obstacles) {
|
|
322
|
+
if (
|
|
323
|
+
!obstacle.layers.some((layer) => via.spanLayers.includes(layer)) ||
|
|
324
|
+
obstacleSharesElectricalNet(inputSrj, obstacle, copper.connectionName)
|
|
325
|
+
) {
|
|
326
|
+
continue
|
|
327
|
+
}
|
|
328
|
+
const actual = distancePointToObstacle(via.center, obstacle)
|
|
329
|
+
const required = via.diameter / 2 + clearance
|
|
330
|
+
if (actual < required - EPSILON) {
|
|
331
|
+
addIssue(issues, {
|
|
332
|
+
code: "via-obstacle-clearance",
|
|
333
|
+
traceId: copper.trace.pcb_trace_id,
|
|
334
|
+
connectionName: copper.connectionName,
|
|
335
|
+
obstacleId: obstacle.obstacleId,
|
|
336
|
+
message: `Via in ${copper.trace.pcb_trace_id} is ${actual.toFixed(4)}mm from different-net obstacle ${obstacle.obstacleId} on its layer span; ${required.toFixed(4)}mm is required`,
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
for (let firstIndex = 0; firstIndex < traceCopper.length; firstIndex++) {
|
|
344
|
+
const first = traceCopper[firstIndex]!
|
|
345
|
+
for (
|
|
346
|
+
let secondIndex = firstIndex + 1;
|
|
347
|
+
secondIndex < traceCopper.length;
|
|
348
|
+
secondIndex++
|
|
349
|
+
) {
|
|
350
|
+
const second = traceCopper[secondIndex]!
|
|
351
|
+
if (
|
|
352
|
+
connectionsShareElectricalNet(
|
|
353
|
+
inputSrj,
|
|
354
|
+
first.connectionName,
|
|
355
|
+
second.connectionName,
|
|
356
|
+
)
|
|
357
|
+
) {
|
|
358
|
+
continue
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const firstSegment of first.segments) {
|
|
362
|
+
for (const secondSegment of second.segments) {
|
|
363
|
+
if (segmentsAreClear(firstSegment, secondSegment, clearance)) continue
|
|
364
|
+
addIssue(issues, {
|
|
365
|
+
code: "different-net-trace-clearance",
|
|
366
|
+
traceId: first.trace.pcb_trace_id,
|
|
367
|
+
connectionName: first.connectionName,
|
|
368
|
+
otherTraceId: second.trace.pcb_trace_id,
|
|
369
|
+
otherConnectionName: second.connectionName,
|
|
370
|
+
layer: firstSegment.layer,
|
|
371
|
+
message: `Different-net traces ${first.trace.pcb_trace_id} and ${second.trace.pcb_trace_id} intersect or violate clearance on ${firstSegment.layer}`,
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
for (const secondVia of second.vias) {
|
|
375
|
+
if (
|
|
376
|
+
!secondVia.spanLayers.includes(firstSegment.layer) ||
|
|
377
|
+
distancePointToSegment(
|
|
378
|
+
secondVia.center,
|
|
379
|
+
firstSegment.start,
|
|
380
|
+
firstSegment.end,
|
|
381
|
+
) >=
|
|
382
|
+
secondVia.diameter / 2 +
|
|
383
|
+
firstSegment.width / 2 +
|
|
384
|
+
clearance -
|
|
385
|
+
EPSILON
|
|
386
|
+
) {
|
|
387
|
+
continue
|
|
388
|
+
}
|
|
389
|
+
addIssue(issues, {
|
|
390
|
+
code: "different-net-trace-via-clearance",
|
|
391
|
+
traceId: first.trace.pcb_trace_id,
|
|
392
|
+
connectionName: first.connectionName,
|
|
393
|
+
otherTraceId: second.trace.pcb_trace_id,
|
|
394
|
+
otherConnectionName: second.connectionName,
|
|
395
|
+
layer: firstSegment.layer,
|
|
396
|
+
message: `Trace ${first.trace.pcb_trace_id} violates via clearance to ${second.trace.pcb_trace_id} on ${firstSegment.layer}`,
|
|
397
|
+
})
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for (const firstVia of first.vias) {
|
|
402
|
+
for (const secondSegment of second.segments) {
|
|
403
|
+
if (
|
|
404
|
+
!firstVia.spanLayers.includes(secondSegment.layer) ||
|
|
405
|
+
distancePointToSegment(
|
|
406
|
+
firstVia.center,
|
|
407
|
+
secondSegment.start,
|
|
408
|
+
secondSegment.end,
|
|
409
|
+
) >=
|
|
410
|
+
firstVia.diameter / 2 +
|
|
411
|
+
secondSegment.width / 2 +
|
|
412
|
+
clearance -
|
|
413
|
+
EPSILON
|
|
414
|
+
) {
|
|
415
|
+
continue
|
|
416
|
+
}
|
|
417
|
+
addIssue(issues, {
|
|
418
|
+
code: "different-net-trace-via-clearance",
|
|
419
|
+
traceId: first.trace.pcb_trace_id,
|
|
420
|
+
connectionName: first.connectionName,
|
|
421
|
+
otherTraceId: second.trace.pcb_trace_id,
|
|
422
|
+
otherConnectionName: second.connectionName,
|
|
423
|
+
layer: secondSegment.layer,
|
|
424
|
+
message: `Via in ${first.trace.pcb_trace_id} violates trace clearance to ${second.trace.pcb_trace_id} on ${secondSegment.layer}`,
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
for (const secondVia of second.vias) {
|
|
428
|
+
if (
|
|
429
|
+
!firstVia.spanLayers.some((layer) =>
|
|
430
|
+
secondVia.spanLayers.includes(layer),
|
|
431
|
+
) ||
|
|
432
|
+
distance(firstVia.center, secondVia.center) >=
|
|
433
|
+
(firstVia.diameter + secondVia.diameter) / 2 + clearance - EPSILON
|
|
434
|
+
) {
|
|
435
|
+
continue
|
|
436
|
+
}
|
|
437
|
+
addIssue(issues, {
|
|
438
|
+
code: "different-net-via-clearance",
|
|
439
|
+
traceId: first.trace.pcb_trace_id,
|
|
440
|
+
connectionName: first.connectionName,
|
|
441
|
+
otherTraceId: second.trace.pcb_trace_id,
|
|
442
|
+
otherConnectionName: second.connectionName,
|
|
443
|
+
message: `Different-net vias in ${first.trace.pcb_trace_id} and ${second.trace.pcb_trace_id} violate clearance on an overlapping layer span`,
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
valid: issues.length === 0,
|
|
452
|
+
checkedTraceCount: routedSrj.traces?.length ?? 0,
|
|
453
|
+
checkedSegmentCount: traceCopper.reduce(
|
|
454
|
+
(count, copper) => count + copper.segments.length,
|
|
455
|
+
0,
|
|
456
|
+
),
|
|
457
|
+
checkedViaCount: traceCopper.reduce(
|
|
458
|
+
(count, copper) => count + copper.vias.length,
|
|
459
|
+
0,
|
|
460
|
+
),
|
|
461
|
+
issues,
|
|
462
|
+
}
|
|
463
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fanout-solver",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
|
|
5
5
|
"module": "lib/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@biomejs/biome": "2.5.5",
|
|
43
43
|
"@tsci/tscircuit.dataset-srj19-bga-passive-overlays": "github:tscircuit/dataset-srj19#c1206f3",
|
|
44
|
-
"@tsci/tscircuit.dataset-srj29-bga-decoupling": "github:tscircuit/dataset-srj29-bga-decoupling#
|
|
44
|
+
"@tsci/tscircuit.dataset-srj29-bga-decoupling": "github:tscircuit/dataset-srj29-bga-decoupling#bfdf7bc",
|
|
45
45
|
"@tscircuit/footprinter": "0.0.394",
|
|
46
46
|
"@types/bun": "1.3.14",
|
|
47
47
|
"@types/react": "19.2.14",
|