@tscircuit/fanout-solver 0.0.32 → 0.0.33
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.
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
distancePointToSegment,
|
|
11
11
|
pointIsInsideObstacle,
|
|
12
12
|
} from "./geometry"
|
|
13
|
-
import { getCopperLayerNames, getLayerSpan } from "./layer-names"
|
|
14
13
|
import { obstacleSharesElectricalNet } from "./net-identity"
|
|
15
14
|
import type {
|
|
16
15
|
FanoutEndpointCompletionReport,
|
|
@@ -593,58 +592,6 @@ function trimCompletedFanoutTails(params: {
|
|
|
593
592
|
return { ...fanoutSrj, traces }
|
|
594
593
|
}
|
|
595
594
|
|
|
596
|
-
function createFanoutCopperObstacles(srj: SimpleRouteJson): Obstacle[] {
|
|
597
|
-
const layerNames = getCopperLayerNames(srj.layerCount)
|
|
598
|
-
const obstacles: Obstacle[] = []
|
|
599
|
-
for (const trace of srj.traces ?? []) {
|
|
600
|
-
let previousWire:
|
|
601
|
-
| Extract<SimplifiedPcbTrace["route"][number], { route_type: "wire" }>
|
|
602
|
-
| undefined
|
|
603
|
-
for (const point of trace.route) {
|
|
604
|
-
if (point.route_type === "via") {
|
|
605
|
-
obstacles.push({
|
|
606
|
-
obstacleId: `endpoint-completion-keepout:${trace.pcb_trace_id}:via:${obstacles.length}`,
|
|
607
|
-
type: "rect",
|
|
608
|
-
center: { x: point.x, y: point.y },
|
|
609
|
-
width: point.via_diameter ?? srj.minViaPadDiameter ?? 0.3,
|
|
610
|
-
height: point.via_diameter ?? srj.minViaPadDiameter ?? 0.3,
|
|
611
|
-
layers: getLayerSpan(point.from_layer, point.to_layer, layerNames),
|
|
612
|
-
connectedTo: [trace.connection_name],
|
|
613
|
-
})
|
|
614
|
-
previousWire = undefined
|
|
615
|
-
continue
|
|
616
|
-
}
|
|
617
|
-
if (point.route_type !== "wire") {
|
|
618
|
-
previousWire = undefined
|
|
619
|
-
continue
|
|
620
|
-
}
|
|
621
|
-
if (previousWire?.layer === point.layer) {
|
|
622
|
-
const segmentLength = distance(previousWire, point)
|
|
623
|
-
if (segmentLength > EPSILON) {
|
|
624
|
-
obstacles.push({
|
|
625
|
-
obstacleId: `endpoint-completion-keepout:${trace.pcb_trace_id}:segment:${obstacles.length}`,
|
|
626
|
-
type: "rect",
|
|
627
|
-
center: {
|
|
628
|
-
x: (previousWire.x + point.x) / 2,
|
|
629
|
-
y: (previousWire.y + point.y) / 2,
|
|
630
|
-
},
|
|
631
|
-
width: segmentLength,
|
|
632
|
-
height: Math.max(previousWire.width, point.width),
|
|
633
|
-
ccwRotationDegrees:
|
|
634
|
-
(Math.atan2(point.y - previousWire.y, point.x - previousWire.x) *
|
|
635
|
-
180) /
|
|
636
|
-
Math.PI,
|
|
637
|
-
layers: [point.layer],
|
|
638
|
-
connectedTo: [trace.connection_name],
|
|
639
|
-
})
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
previousWire = point
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
return obstacles
|
|
646
|
-
}
|
|
647
|
-
|
|
648
595
|
function acceptDownstreamTraces(params: {
|
|
649
596
|
inputSrj: SimpleRouteJson
|
|
650
597
|
fanoutSrj: SimpleRouteJson
|
|
@@ -1092,11 +1039,8 @@ export function completeOriginalEndpoints(params: {
|
|
|
1092
1039
|
),
|
|
1093
1040
|
}))
|
|
1094
1041
|
.filter((bus) => bus.connectionNames.length > 0),
|
|
1095
|
-
obstacles:
|
|
1096
|
-
|
|
1097
|
-
...createFanoutCopperObstacles(fanoutSrj),
|
|
1098
|
-
],
|
|
1099
|
-
traces: [],
|
|
1042
|
+
obstacles: fanoutSrj.obstacles,
|
|
1043
|
+
traces: directSrj.traces,
|
|
1100
1044
|
}
|
|
1101
1045
|
try {
|
|
1102
1046
|
const candidates = routeDownstreamConnections(downstreamInput, { effort })
|
package/lib/fanout-solver.ts
CHANGED
|
@@ -374,6 +374,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
374
374
|
readonly attempts: FanoutAttemptSummary[] = []
|
|
375
375
|
readonly layerAssignments: Array<Readonly<Record<string, string>>>
|
|
376
376
|
readonly config: ResolvedFanoutConfig
|
|
377
|
+
private readonly routingSrj: SimpleRouteJson
|
|
377
378
|
private readonly escapeLayersByBusId: Readonly<
|
|
378
379
|
Record<string, readonly string[]>
|
|
379
380
|
>
|
|
@@ -404,8 +405,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
404
405
|
public readonly options: FanoutSolverOptions = {},
|
|
405
406
|
) {
|
|
406
407
|
super()
|
|
408
|
+
this.routingSrj = {
|
|
409
|
+
...inputSrj,
|
|
410
|
+
obstacles: [...inputSrj.obstacles],
|
|
411
|
+
}
|
|
407
412
|
this.config = resolveConfig(inputSrj, options)
|
|
408
|
-
this.preparedBuses = prepareFanoutBuses(
|
|
413
|
+
this.preparedBuses = prepareFanoutBuses(this.routingSrj, options)
|
|
409
414
|
for (const bus of this.preparedBuses) {
|
|
410
415
|
for (const allowedLayer of bus.allowedLayers ?? []) {
|
|
411
416
|
if (!this.config.layerNames.includes(allowedLayer)) {
|
|
@@ -468,7 +473,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
468
473
|
bus.busId,
|
|
469
474
|
getCandidateEscapeLayersForBus({
|
|
470
475
|
bus,
|
|
471
|
-
srj:
|
|
476
|
+
srj: this.routingSrj,
|
|
472
477
|
config: this.config,
|
|
473
478
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
474
479
|
}),
|
|
@@ -511,7 +516,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
511
516
|
return
|
|
512
517
|
}
|
|
513
518
|
this.endpointCompletion = completeOriginalEndpoints({
|
|
514
|
-
inputSrj: this.
|
|
519
|
+
inputSrj: this.routingSrj,
|
|
515
520
|
fanoutSrj: this.bestAttempt.outputSrj,
|
|
516
521
|
plans: this.bestAttempt.plans,
|
|
517
522
|
traceWidth: this.config.traceWidth,
|
|
@@ -563,7 +568,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
563
568
|
const isSingleLayerFanout = this.config.escapeLayers.length === 1
|
|
564
569
|
if (isSingleLayerFanout && this.config.singleLayerPushAndShove) {
|
|
565
570
|
const singleLayerParams = {
|
|
566
|
-
srj: this.
|
|
571
|
+
srj: this.routingSrj,
|
|
567
572
|
buses: this.preparedBuses,
|
|
568
573
|
traceWidth: this.config.traceWidth,
|
|
569
574
|
clearance: this.config.clearance,
|
|
@@ -628,7 +633,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
628
633
|
}
|
|
629
634
|
const currentBusBlockingCounts = new Map<string, number>()
|
|
630
635
|
const busPlans = routeBus({
|
|
631
|
-
srj: this.
|
|
636
|
+
srj: this.routingSrj,
|
|
632
637
|
bus,
|
|
633
638
|
targetLayer,
|
|
634
639
|
acceptedPlans: plans,
|
|
@@ -884,7 +889,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
884
889
|
for (const targetLayer of orderedLayers) {
|
|
885
890
|
const busAlternatives = routeBusAlternatives(
|
|
886
891
|
{
|
|
887
|
-
srj: this.
|
|
892
|
+
srj: this.routingSrj,
|
|
888
893
|
bus,
|
|
889
894
|
targetLayer,
|
|
890
895
|
acceptedPlans: state.plans,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SimpleRouteJson,
|
|
3
|
+
SimplifiedPcbTrace,
|
|
4
|
+
} from "@tscircuit/capacity-autorouter"
|
|
5
|
+
import { getCopperLayerNames, getLayerSpan } from "./layer-names"
|
|
6
|
+
import type { Point2D, RoutedSegment } from "./types"
|
|
7
|
+
|
|
8
|
+
export interface RoutedTraceVia {
|
|
9
|
+
center: Point2D
|
|
10
|
+
diameter: number
|
|
11
|
+
spanLayers: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RoutedTraceCopper {
|
|
15
|
+
trace: SimplifiedPcbTrace
|
|
16
|
+
connectionName: string
|
|
17
|
+
segments: RoutedSegment[]
|
|
18
|
+
vias: RoutedTraceVia[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getRoutedTraceCopper(
|
|
22
|
+
srj: SimpleRouteJson,
|
|
23
|
+
trace: SimplifiedPcbTrace,
|
|
24
|
+
): RoutedTraceCopper {
|
|
25
|
+
const layerNames = getCopperLayerNames(srj.layerCount)
|
|
26
|
+
const segments: RoutedSegment[] = []
|
|
27
|
+
const vias: RoutedTraceVia[] = []
|
|
28
|
+
let previousWire:
|
|
29
|
+
| Extract<SimplifiedPcbTrace["route"][number], { route_type: "wire" }>
|
|
30
|
+
| undefined
|
|
31
|
+
|
|
32
|
+
for (const routePoint of trace.route) {
|
|
33
|
+
if (routePoint.route_type === "via") {
|
|
34
|
+
vias.push({
|
|
35
|
+
center: { x: routePoint.x, y: routePoint.y },
|
|
36
|
+
diameter:
|
|
37
|
+
routePoint.via_diameter ??
|
|
38
|
+
srj.minViaPadDiameter ??
|
|
39
|
+
srj.min_via_pad_diameter ??
|
|
40
|
+
srj.minViaDiameter ??
|
|
41
|
+
srj.minTraceWidth,
|
|
42
|
+
spanLayers: getLayerSpan(
|
|
43
|
+
routePoint.from_layer,
|
|
44
|
+
routePoint.to_layer,
|
|
45
|
+
layerNames,
|
|
46
|
+
),
|
|
47
|
+
})
|
|
48
|
+
previousWire = undefined
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (routePoint.route_type !== "wire") {
|
|
53
|
+
previousWire = undefined
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
previousWire?.layer === routePoint.layer &&
|
|
59
|
+
(previousWire.x !== routePoint.x || previousWire.y !== routePoint.y)
|
|
60
|
+
) {
|
|
61
|
+
segments.push({
|
|
62
|
+
start: { x: previousWire.x, y: previousWire.y },
|
|
63
|
+
end: { x: routePoint.x, y: routePoint.y },
|
|
64
|
+
width: Math.max(previousWire.width, routePoint.width),
|
|
65
|
+
layer: routePoint.layer,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
previousWire = routePoint
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
trace,
|
|
73
|
+
connectionName: trace.connection_name,
|
|
74
|
+
segments,
|
|
75
|
+
vias,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getAllRoutedTraceCopper(
|
|
80
|
+
srj: SimpleRouteJson,
|
|
81
|
+
): RoutedTraceCopper[] {
|
|
82
|
+
return (srj.traces ?? []).map((trace) => getRoutedTraceCopper(srj, trace))
|
|
83
|
+
}
|
package/lib/net-identity.ts
CHANGED
|
@@ -62,12 +62,16 @@ function createElectricalNetIdentity(
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
for (const trace of srj.traces ?? []) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
const traceTokens = [
|
|
66
|
+
trace.connection_name,
|
|
67
|
+
trace.pcb_trace_id,
|
|
68
|
+
...(trace.connectsTo ?? []),
|
|
69
|
+
]
|
|
70
|
+
const knownNetKeys = getKnownNetKeys(tokenNetKeys, traceTokens)
|
|
71
|
+
if (knownNetKeys.size !== 1) continue
|
|
72
|
+
const netKey = [...knownNetKeys][0]!
|
|
73
|
+
connectionNetKeys.set(trace.connection_name, netKey)
|
|
74
|
+
for (const token of traceTokens) {
|
|
71
75
|
addTokenNet(tokenNetKeys, token, netKey)
|
|
72
76
|
}
|
|
73
77
|
}
|
package/lib/route-bus.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
segmentsAreClear,
|
|
12
12
|
} from "./geometry"
|
|
13
13
|
import { createFanoutOutputIds } from "./fanout-output-ids"
|
|
14
|
+
import { getAllRoutedTraceCopper } from "./get-routed-trace-copper"
|
|
14
15
|
import { getLayerSpan } from "./layer-names"
|
|
15
16
|
import {
|
|
16
17
|
connectionsShareElectricalNet,
|
|
@@ -1000,6 +1001,66 @@ function planIsStaticallyClear(params: {
|
|
|
1000
1001
|
}
|
|
1001
1002
|
}
|
|
1002
1003
|
|
|
1004
|
+
for (const traceCopper of getAllRoutedTraceCopper(srj)) {
|
|
1005
|
+
if (
|
|
1006
|
+
plan.connectionName === traceCopper.connectionName ||
|
|
1007
|
+
(allowSameNetMerges &&
|
|
1008
|
+
connectionsShareElectricalNet(
|
|
1009
|
+
srj,
|
|
1010
|
+
plan.connectionName,
|
|
1011
|
+
traceCopper.connectionName,
|
|
1012
|
+
))
|
|
1013
|
+
) {
|
|
1014
|
+
continue
|
|
1015
|
+
}
|
|
1016
|
+
for (const segment of getPlanSegments(plan)) {
|
|
1017
|
+
for (const existingSegment of traceCopper.segments) {
|
|
1018
|
+
if (!segmentsAreClear(segment, existingSegment, clearance)) {
|
|
1019
|
+
return false
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
for (const existingVia of traceCopper.vias) {
|
|
1023
|
+
if (
|
|
1024
|
+
existingVia.spanLayers.includes(segment.layer) &&
|
|
1025
|
+
distancePointToSegment(
|
|
1026
|
+
existingVia.center,
|
|
1027
|
+
segment.start,
|
|
1028
|
+
segment.end,
|
|
1029
|
+
) <
|
|
1030
|
+
existingVia.diameter / 2 + segment.width / 2 + clearance - 1e-9
|
|
1031
|
+
) {
|
|
1032
|
+
return false
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
for (const via of getPlanVias(plan)) {
|
|
1037
|
+
for (const existingSegment of traceCopper.segments) {
|
|
1038
|
+
if (
|
|
1039
|
+
via.spanLayers.includes(existingSegment.layer) &&
|
|
1040
|
+
distancePointToSegment(
|
|
1041
|
+
via.center,
|
|
1042
|
+
existingSegment.start,
|
|
1043
|
+
existingSegment.end,
|
|
1044
|
+
) <
|
|
1045
|
+
via.diameter / 2 + existingSegment.width / 2 + clearance - 1e-9
|
|
1046
|
+
) {
|
|
1047
|
+
return false
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
for (const existingVia of traceCopper.vias) {
|
|
1051
|
+
if (
|
|
1052
|
+
via.spanLayers.some((layer) =>
|
|
1053
|
+
existingVia.spanLayers.includes(layer),
|
|
1054
|
+
) &&
|
|
1055
|
+
distance(via.center, existingVia.center) <
|
|
1056
|
+
(via.diameter + existingVia.diameter) / 2 + clearance - 1e-9
|
|
1057
|
+
) {
|
|
1058
|
+
return false
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1003
1064
|
return true
|
|
1004
1065
|
}
|
|
1005
1066
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
connectionsShareElectricalNet,
|
|
16
16
|
obstacleSharesElectricalNet,
|
|
17
17
|
} from "./net-identity"
|
|
18
|
+
import { getAllRoutedTraceCopper } from "./get-routed-trace-copper"
|
|
18
19
|
import type {
|
|
19
20
|
Bounds,
|
|
20
21
|
FanoutRoutePlan,
|
|
@@ -664,6 +665,89 @@ function validateClearances(params: {
|
|
|
664
665
|
}
|
|
665
666
|
}
|
|
666
667
|
}
|
|
668
|
+
|
|
669
|
+
for (const traceCopper of getAllRoutedTraceCopper(inputSrj)) {
|
|
670
|
+
if (
|
|
671
|
+
plan.connectionName === traceCopper.connectionName ||
|
|
672
|
+
connectionsShareElectricalNet(
|
|
673
|
+
inputSrj,
|
|
674
|
+
plan.connectionName,
|
|
675
|
+
traceCopper.connectionName,
|
|
676
|
+
)
|
|
677
|
+
) {
|
|
678
|
+
continue
|
|
679
|
+
}
|
|
680
|
+
for (const segment of getPlanSegments(plan)) {
|
|
681
|
+
for (const existingSegment of traceCopper.segments) {
|
|
682
|
+
if (!segmentsAreClear(segment, existingSegment, clearance)) {
|
|
683
|
+
addIssue(
|
|
684
|
+
issues,
|
|
685
|
+
"different-net-trace-clearance",
|
|
686
|
+
`Trace ${plan.connectionName} violates routed trace ${traceCopper.trace.pcb_trace_id} on ${segment.layer}`,
|
|
687
|
+
plan,
|
|
688
|
+
traceCopper.connectionName,
|
|
689
|
+
)
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
for (const existingVia of traceCopper.vias) {
|
|
693
|
+
if (
|
|
694
|
+
existingVia.spanLayers.includes(segment.layer) &&
|
|
695
|
+
distancePointToSegment(
|
|
696
|
+
existingVia.center,
|
|
697
|
+
segment.start,
|
|
698
|
+
segment.end,
|
|
699
|
+
) <
|
|
700
|
+
existingVia.diameter / 2 + segment.width / 2 + clearance - 1e-9
|
|
701
|
+
) {
|
|
702
|
+
addIssue(
|
|
703
|
+
issues,
|
|
704
|
+
"different-net-trace-via-clearance",
|
|
705
|
+
`Trace ${plan.connectionName} violates a via in routed trace ${traceCopper.trace.pcb_trace_id} on ${segment.layer}`,
|
|
706
|
+
plan,
|
|
707
|
+
traceCopper.connectionName,
|
|
708
|
+
)
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
for (const via of getPlanVias(plan)) {
|
|
713
|
+
for (const existingSegment of traceCopper.segments) {
|
|
714
|
+
if (
|
|
715
|
+
via.spanLayers.includes(existingSegment.layer) &&
|
|
716
|
+
distancePointToSegment(
|
|
717
|
+
via.center,
|
|
718
|
+
existingSegment.start,
|
|
719
|
+
existingSegment.end,
|
|
720
|
+
) <
|
|
721
|
+
via.diameter / 2 + existingSegment.width / 2 + clearance - 1e-9
|
|
722
|
+
) {
|
|
723
|
+
addIssue(
|
|
724
|
+
issues,
|
|
725
|
+
"different-net-trace-via-clearance",
|
|
726
|
+
`Via ${plan.connectionName} violates routed trace ${traceCopper.trace.pcb_trace_id} on ${existingSegment.layer}`,
|
|
727
|
+
plan,
|
|
728
|
+
traceCopper.connectionName,
|
|
729
|
+
)
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
for (const existingVia of traceCopper.vias) {
|
|
733
|
+
if (
|
|
734
|
+
via.spanLayers.some((layer) =>
|
|
735
|
+
existingVia.spanLayers.includes(layer),
|
|
736
|
+
) &&
|
|
737
|
+
distance(via.center, existingVia.center) <
|
|
738
|
+
(via.diameter + existingVia.diameter) / 2 + clearance - 1e-9
|
|
739
|
+
) {
|
|
740
|
+
addIssue(
|
|
741
|
+
issues,
|
|
742
|
+
"different-net-via-clearance",
|
|
743
|
+
`Via ${plan.connectionName} violates a via in routed trace ${traceCopper.trace.pcb_trace_id}`,
|
|
744
|
+
plan,
|
|
745
|
+
traceCopper.connectionName,
|
|
746
|
+
)
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
667
751
|
}
|
|
668
752
|
|
|
669
753
|
for (let firstIndex = 0; firstIndex < plans.length; firstIndex++) {
|