@tscircuit/fanout-solver 0.0.21 → 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 +15 -0
- 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 +4 -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 +42 -1
- package/lib/validate-routed-copper-drc.ts +23 -2
- package/package.json +2 -2
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
PreparedBus,
|
|
25
25
|
RoutedSegment,
|
|
26
26
|
} from "./types"
|
|
27
|
+
import { segmentIsLegalTerminalBodyEscape } from "./validate-routed-copper-drc"
|
|
27
28
|
|
|
28
29
|
const EPSILON = 1e-6
|
|
29
30
|
|
|
@@ -35,6 +36,16 @@ function getPointLayers(point: ConnectionPoint): string[] {
|
|
|
35
36
|
return "layer" in point ? [point.layer] : point.layers
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
function getPlanSegments(plan: FanoutRoutePlan): RoutedSegment[] {
|
|
40
|
+
return [...plan.segments, ...(plan.planeEndpointSegments ?? [])]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getPlanVias(plan: FanoutRoutePlan) {
|
|
44
|
+
return [plan.via, plan.planeEndpointVia].filter(
|
|
45
|
+
(via): via is NonNullable<FanoutRoutePlan["via"]> => Boolean(via),
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
function connectionPointsMatch(
|
|
39
50
|
first: ConnectionPoint,
|
|
40
51
|
second: ConnectionPoint,
|
|
@@ -322,6 +333,52 @@ function validatePlanStructure(params: {
|
|
|
322
333
|
)
|
|
323
334
|
}
|
|
324
335
|
|
|
336
|
+
if (
|
|
337
|
+
plan.planeEndpointTrace ||
|
|
338
|
+
plan.planeEndpointSegments ||
|
|
339
|
+
plan.planeEndpointVia
|
|
340
|
+
) {
|
|
341
|
+
if (
|
|
342
|
+
!plan.planeEndpointTrace ||
|
|
343
|
+
!plan.planeEndpointSegments ||
|
|
344
|
+
!plan.planeEndpointVia
|
|
345
|
+
) {
|
|
346
|
+
addIssue(
|
|
347
|
+
issues,
|
|
348
|
+
"trace-plan-mismatch",
|
|
349
|
+
`Plane endpoint geometry for ${plan.connectionName} is incomplete`,
|
|
350
|
+
plan,
|
|
351
|
+
)
|
|
352
|
+
} else {
|
|
353
|
+
const endpointSegments = extractTraceSegments({
|
|
354
|
+
trace: plan.planeEndpointTrace,
|
|
355
|
+
plan,
|
|
356
|
+
issues,
|
|
357
|
+
})
|
|
358
|
+
if (
|
|
359
|
+
endpointSegments.length !== plan.planeEndpointSegments.length ||
|
|
360
|
+
endpointSegments.some((segment, index) => {
|
|
361
|
+
const declared = plan.planeEndpointSegments?.[index]
|
|
362
|
+
return (
|
|
363
|
+
!declared ||
|
|
364
|
+
segment.layer !== declared.layer ||
|
|
365
|
+
Math.abs(segment.width - declared.width) > EPSILON ||
|
|
366
|
+
!pointsMatch(segment.start, declared.start) ||
|
|
367
|
+
!pointsMatch(segment.end, declared.end)
|
|
368
|
+
)
|
|
369
|
+
}) ||
|
|
370
|
+
distance(plan.planeEndpointVia.center, plan.targetPoint) <= EPSILON
|
|
371
|
+
) {
|
|
372
|
+
addIssue(
|
|
373
|
+
issues,
|
|
374
|
+
"trace-plan-mismatch",
|
|
375
|
+
`Trace ${plan.planeEndpointTrace.pcb_trace_id} does not encode a valid offset plane-to-endpoint dogbone`,
|
|
376
|
+
plan,
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
325
382
|
const firstRoutePoint = plan.trace.route.find(
|
|
326
383
|
(routePoint): routePoint is Extract<typeof routePoint, { x: number }> =>
|
|
327
384
|
"x" in routePoint && "y" in routePoint,
|
|
@@ -409,8 +466,12 @@ function plansHaveConnectedCopper(
|
|
|
409
466
|
first: FanoutRoutePlan,
|
|
410
467
|
second: FanoutRoutePlan,
|
|
411
468
|
): boolean {
|
|
412
|
-
|
|
413
|
-
|
|
469
|
+
const firstSegments = getPlanSegments(first)
|
|
470
|
+
const secondSegments = getPlanSegments(second)
|
|
471
|
+
const firstVias = getPlanVias(first)
|
|
472
|
+
const secondVias = getPlanVias(second)
|
|
473
|
+
for (const firstSegment of firstSegments) {
|
|
474
|
+
for (const secondSegment of secondSegments) {
|
|
414
475
|
if (
|
|
415
476
|
firstSegment.layer === secondSegment.layer &&
|
|
416
477
|
distanceSegmentToSegment(
|
|
@@ -424,41 +485,44 @@ function plansHaveConnectedCopper(
|
|
|
424
485
|
return true
|
|
425
486
|
}
|
|
426
487
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
488
|
+
for (const secondVia of secondVias) {
|
|
489
|
+
if (
|
|
490
|
+
secondVia.spanLayers.includes(firstSegment.layer) &&
|
|
491
|
+
distancePointToSegment(
|
|
492
|
+
secondVia.center,
|
|
493
|
+
firstSegment.start,
|
|
494
|
+
firstSegment.end,
|
|
495
|
+
) <=
|
|
496
|
+
secondVia.diameter / 2 + firstSegment.width / 2 + EPSILON
|
|
497
|
+
) {
|
|
498
|
+
return true
|
|
499
|
+
}
|
|
437
500
|
}
|
|
438
501
|
}
|
|
439
|
-
|
|
440
|
-
for (const secondSegment of
|
|
502
|
+
for (const firstVia of firstVias) {
|
|
503
|
+
for (const secondSegment of secondSegments) {
|
|
441
504
|
if (
|
|
442
|
-
|
|
505
|
+
firstVia.spanLayers.includes(secondSegment.layer) &&
|
|
443
506
|
distancePointToSegment(
|
|
444
|
-
|
|
507
|
+
firstVia.center,
|
|
445
508
|
secondSegment.start,
|
|
446
509
|
secondSegment.end,
|
|
447
510
|
) <=
|
|
448
|
-
|
|
511
|
+
firstVia.diameter / 2 + secondSegment.width / 2 + EPSILON
|
|
449
512
|
) {
|
|
450
513
|
return true
|
|
451
514
|
}
|
|
452
515
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
516
|
+
for (const secondVia of secondVias) {
|
|
517
|
+
if (
|
|
518
|
+
firstVia.spanLayers.some((layer) =>
|
|
519
|
+
secondVia.spanLayers.includes(layer),
|
|
520
|
+
) &&
|
|
521
|
+
distance(firstVia.center, secondVia.center) <=
|
|
522
|
+
(firstVia.diameter + secondVia.diameter) / 2 + EPSILON
|
|
523
|
+
) {
|
|
524
|
+
return true
|
|
525
|
+
}
|
|
462
526
|
}
|
|
463
527
|
}
|
|
464
528
|
return false
|
|
@@ -541,12 +605,9 @@ function validateClearances(params: {
|
|
|
541
605
|
}): void {
|
|
542
606
|
const { plans, inputSrj, clearance, issues } = params
|
|
543
607
|
for (const plan of plans) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
segmentIndex++
|
|
548
|
-
) {
|
|
549
|
-
const segment = plan.segments[segmentIndex]!
|
|
608
|
+
const segments = getPlanSegments(plan)
|
|
609
|
+
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
|
|
610
|
+
const segment = segments[segmentIndex]!
|
|
550
611
|
for (const obstacle of inputSrj.obstacles) {
|
|
551
612
|
if (!obstacle.layers.includes(segment.layer)) continue
|
|
552
613
|
if (
|
|
@@ -561,6 +622,16 @@ function validateClearances(params: {
|
|
|
561
622
|
) {
|
|
562
623
|
continue
|
|
563
624
|
}
|
|
625
|
+
if (
|
|
626
|
+
segmentIsLegalTerminalBodyEscape({
|
|
627
|
+
inputSrj,
|
|
628
|
+
segment,
|
|
629
|
+
bodyObstacle: obstacle,
|
|
630
|
+
connectionName: plan.connectionName,
|
|
631
|
+
})
|
|
632
|
+
) {
|
|
633
|
+
continue
|
|
634
|
+
}
|
|
564
635
|
const actual = distanceSegmentToObstacle(segment, obstacle)
|
|
565
636
|
const required = segment.width / 2 + clearance
|
|
566
637
|
if (actual < required - 1e-9) {
|
|
@@ -573,18 +644,16 @@ function validateClearances(params: {
|
|
|
573
644
|
}
|
|
574
645
|
}
|
|
575
646
|
}
|
|
576
|
-
|
|
647
|
+
for (const via of getPlanVias(plan)) {
|
|
577
648
|
for (const obstacle of inputSrj.obstacles) {
|
|
578
649
|
if (
|
|
579
|
-
!obstacle.layers.some((layer) =>
|
|
580
|
-
plan.via!.spanLayers.includes(layer),
|
|
581
|
-
) ||
|
|
650
|
+
!obstacle.layers.some((layer) => via.spanLayers.includes(layer)) ||
|
|
582
651
|
obstacleSharesElectricalNet(inputSrj, obstacle, plan.connectionName)
|
|
583
652
|
) {
|
|
584
653
|
continue
|
|
585
654
|
}
|
|
586
|
-
const actual = distancePointToObstacle(
|
|
587
|
-
const required =
|
|
655
|
+
const actual = distancePointToObstacle(via.center, obstacle)
|
|
656
|
+
const required = via.diameter / 2 + clearance
|
|
588
657
|
if (actual < required - 1e-9) {
|
|
589
658
|
addIssue(
|
|
590
659
|
issues,
|
|
@@ -614,8 +683,12 @@ function validateClearances(params: {
|
|
|
614
683
|
) {
|
|
615
684
|
continue
|
|
616
685
|
}
|
|
617
|
-
|
|
618
|
-
|
|
686
|
+
const firstSegments = getPlanSegments(first)
|
|
687
|
+
const secondSegments = getPlanSegments(second)
|
|
688
|
+
const firstVias = getPlanVias(first)
|
|
689
|
+
const secondVias = getPlanVias(second)
|
|
690
|
+
for (const firstSegment of firstSegments) {
|
|
691
|
+
for (const secondSegment of secondSegments) {
|
|
619
692
|
if (!segmentsAreClear(firstSegment, secondSegment, clearance)) {
|
|
620
693
|
addIssue(
|
|
621
694
|
issues,
|
|
@@ -626,37 +699,36 @@ function validateClearances(params: {
|
|
|
626
699
|
)
|
|
627
700
|
}
|
|
628
701
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
702
|
+
for (const secondVia of secondVias) {
|
|
703
|
+
if (
|
|
704
|
+
secondVia.spanLayers.includes(firstSegment.layer) &&
|
|
705
|
+
distancePointToSegment(
|
|
706
|
+
secondVia.center,
|
|
707
|
+
firstSegment.start,
|
|
708
|
+
firstSegment.end,
|
|
709
|
+
) <
|
|
710
|
+
secondVia.diameter / 2 + firstSegment.width / 2 + clearance - 1e-9
|
|
711
|
+
) {
|
|
712
|
+
addIssue(
|
|
713
|
+
issues,
|
|
714
|
+
"different-net-trace-via-clearance",
|
|
715
|
+
`Trace ${first.connectionName} violates via clearance to ${second.connectionName} on ${firstSegment.layer}`,
|
|
716
|
+
first,
|
|
717
|
+
second.connectionName,
|
|
718
|
+
)
|
|
719
|
+
}
|
|
645
720
|
}
|
|
646
721
|
}
|
|
647
|
-
|
|
648
|
-
for (const secondSegment of
|
|
722
|
+
for (const firstVia of firstVias) {
|
|
723
|
+
for (const secondSegment of secondSegments) {
|
|
649
724
|
if (
|
|
650
|
-
|
|
725
|
+
firstVia.spanLayers.includes(secondSegment.layer) &&
|
|
651
726
|
distancePointToSegment(
|
|
652
|
-
|
|
727
|
+
firstVia.center,
|
|
653
728
|
secondSegment.start,
|
|
654
729
|
secondSegment.end,
|
|
655
730
|
) <
|
|
656
|
-
|
|
657
|
-
secondSegment.width / 2 +
|
|
658
|
-
clearance -
|
|
659
|
-
1e-9
|
|
731
|
+
firstVia.diameter / 2 + secondSegment.width / 2 + clearance - 1e-9
|
|
660
732
|
) {
|
|
661
733
|
addIssue(
|
|
662
734
|
issues,
|
|
@@ -667,21 +739,22 @@ function validateClearances(params: {
|
|
|
667
739
|
)
|
|
668
740
|
}
|
|
669
741
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
742
|
+
for (const secondVia of secondVias) {
|
|
743
|
+
if (
|
|
744
|
+
firstVia.spanLayers.some((layer) =>
|
|
745
|
+
secondVia.spanLayers.includes(layer),
|
|
746
|
+
) &&
|
|
747
|
+
distance(firstVia.center, secondVia.center) <
|
|
748
|
+
(firstVia.diameter + secondVia.diameter) / 2 + clearance - 1e-9
|
|
749
|
+
) {
|
|
750
|
+
addIssue(
|
|
751
|
+
issues,
|
|
752
|
+
"different-net-via-clearance",
|
|
753
|
+
`Vias ${first.connectionName} and ${second.connectionName} violate clearance on an overlapping layer span`,
|
|
754
|
+
first,
|
|
755
|
+
second.connectionName,
|
|
756
|
+
)
|
|
757
|
+
}
|
|
685
758
|
}
|
|
686
759
|
}
|
|
687
760
|
}
|
|
@@ -19,7 +19,11 @@ import {
|
|
|
19
19
|
getConnectionNetKey,
|
|
20
20
|
obstacleSharesElectricalNet,
|
|
21
21
|
} from "./net-identity"
|
|
22
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
Point2D,
|
|
24
|
+
RoutedSegment,
|
|
25
|
+
SimpleRouteJsonWithFanoutPlanes,
|
|
26
|
+
} from "./types"
|
|
23
27
|
|
|
24
28
|
const EPSILON = 1e-6
|
|
25
29
|
|
|
@@ -64,11 +68,17 @@ interface ViaCopper {
|
|
|
64
68
|
layers: string[]
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
interface PlaneCopper {
|
|
72
|
+
type: "plane"
|
|
73
|
+
layer: string
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
type CopperPrimitive =
|
|
68
77
|
| EndpointCopper
|
|
69
78
|
| ObstacleCopper
|
|
70
79
|
| SegmentCopper
|
|
71
80
|
| ViaCopper
|
|
81
|
+
| PlaneCopper
|
|
72
82
|
|
|
73
83
|
function getPointLayers(point: ConnectionPoint): string[] {
|
|
74
84
|
return "layer" in point ? [point.layer] : point.layers
|
|
@@ -124,6 +134,21 @@ function extractTraceCopper(
|
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
function primitivesTouch(first: CopperPrimitive, second: CopperPrimitive) {
|
|
137
|
+
if (first.type === "plane" && second.type === "plane") {
|
|
138
|
+
return first.layer === second.layer
|
|
139
|
+
}
|
|
140
|
+
if (first.type === "plane" && second.type === "via") {
|
|
141
|
+
return second.layers.includes(first.layer)
|
|
142
|
+
}
|
|
143
|
+
if (first.type === "via" && second.type === "plane") {
|
|
144
|
+
return primitivesTouch(second, first)
|
|
145
|
+
}
|
|
146
|
+
if (first.type === "plane" && second.type === "segment") {
|
|
147
|
+
return first.layer === second.segment.layer
|
|
148
|
+
}
|
|
149
|
+
if (first.type === "segment" && second.type === "plane") {
|
|
150
|
+
return primitivesTouch(second, first)
|
|
151
|
+
}
|
|
127
152
|
if (first.type === "endpoint" && second.type === "endpoint") {
|
|
128
153
|
return (
|
|
129
154
|
layersOverlap(first.layers, second.layers) &&
|
|
@@ -265,6 +290,8 @@ export function validateOriginalEndpointConnectivity(params: {
|
|
|
265
290
|
routedSrj: SimpleRouteJson
|
|
266
291
|
}): OriginalEndpointConnectivityReport {
|
|
267
292
|
const { inputSrj, routedSrj } = params
|
|
293
|
+
const planeConnectivity = (routedSrj as SimpleRouteJsonWithFanoutPlanes)
|
|
294
|
+
.fanoutPlaneConnectivity
|
|
268
295
|
const layerNames = getCopperLayerNames(routedSrj.layerCount)
|
|
269
296
|
const connectionsByNet = new Map<string, SimpleRouteConnection[]>()
|
|
270
297
|
for (const connection of inputSrj.connections) {
|
|
@@ -303,10 +330,24 @@ export function validateOriginalEndpointConnectivity(params: {
|
|
|
303
330
|
traceBelongsToNet(inputSrj, trace, representativeConnection),
|
|
304
331
|
)
|
|
305
332
|
.flatMap((trace) => extractTraceCopper(trace, layerNames))
|
|
333
|
+
const planeCopper: PlaneCopper[] = [
|
|
334
|
+
...new Set(
|
|
335
|
+
(planeConnectivity ?? []).flatMap((plane) =>
|
|
336
|
+
connectionsShareElectricalNet(
|
|
337
|
+
inputSrj,
|
|
338
|
+
plane.connectionName,
|
|
339
|
+
representativeConnection.name,
|
|
340
|
+
)
|
|
341
|
+
? [plane.layer]
|
|
342
|
+
: [],
|
|
343
|
+
),
|
|
344
|
+
),
|
|
345
|
+
].map((layer) => ({ type: "plane", layer }))
|
|
306
346
|
const copper: CopperPrimitive[] = [
|
|
307
347
|
...endpointCopper,
|
|
308
348
|
...obstacleCopper,
|
|
309
349
|
...routeCopper,
|
|
350
|
+
...planeCopper,
|
|
310
351
|
]
|
|
311
352
|
const connectedCopper = new DisjointSet(copper.length)
|
|
312
353
|
for (let firstIndex = 0; firstIndex < copper.length; firstIndex++) {
|
|
@@ -23,6 +23,7 @@ export type RoutedCopperDrcIssueCode =
|
|
|
23
23
|
| "unknown-trace-connection"
|
|
24
24
|
| "unsupported-route-point"
|
|
25
25
|
| "disconnected-trace"
|
|
26
|
+
| "via-at-endpoint"
|
|
26
27
|
| "trace-obstacle-clearance"
|
|
27
28
|
| "via-obstacle-clearance"
|
|
28
29
|
| "different-net-trace-clearance"
|
|
@@ -61,7 +62,7 @@ interface TraceCopper {
|
|
|
61
62
|
vias: RoutedVia[]
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
function
|
|
65
|
+
export function segmentIsLegalTerminalBodyEscape(params: {
|
|
65
66
|
inputSrj: SimpleRouteJson
|
|
66
67
|
segment: RoutedSegment
|
|
67
68
|
bodyObstacle: SimpleRouteJson["obstacles"][number]
|
|
@@ -235,6 +236,14 @@ export function validateRoutedCopperDrc(params: {
|
|
|
235
236
|
const issues: RoutedCopperDrcIssue[] = []
|
|
236
237
|
const layerNames = getCopperLayerNames(routedSrj.layerCount)
|
|
237
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
|
+
)
|
|
238
247
|
|
|
239
248
|
for (const trace of routedSrj.traces ?? []) {
|
|
240
249
|
const connectionName = trace.connection_name
|
|
@@ -273,7 +282,7 @@ export function validateRoutedCopperDrc(params: {
|
|
|
273
282
|
continue
|
|
274
283
|
}
|
|
275
284
|
if (
|
|
276
|
-
|
|
285
|
+
segmentIsLegalTerminalBodyEscape({
|
|
277
286
|
inputSrj,
|
|
278
287
|
segment,
|
|
279
288
|
bodyObstacle: obstacle,
|
|
@@ -297,6 +306,18 @@ export function validateRoutedCopperDrc(params: {
|
|
|
297
306
|
}
|
|
298
307
|
}
|
|
299
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
|
+
}
|
|
300
321
|
for (const obstacle of inputSrj.obstacles) {
|
|
301
322
|
if (
|
|
302
323
|
!obstacle.layers.some((layer) => via.spanLayers.includes(layer)) ||
|
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",
|