@tscircuit/schematic-trace-solver 0.0.150 → 0.0.151
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 +6 -4
- package/dist/index.d.ts +36 -2
- package/dist/index.js +359 -64
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +111 -82
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +338 -0
- package/lib/solvers/NetLabelTraceRecovery/getTraceRecoveryConnectivityMaps.ts +33 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +15 -0
- package/package.json +1 -1
- package/tests/examples/__snapshots__/example51.snap.svg +5 -17
- package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Bounds, Point } from "@tscircuit/math-utils"
|
|
2
|
-
import type { GraphicsObject, Rect } from "graphics-debug"
|
|
2
|
+
import type { GraphicsObject, Rect, Text } from "graphics-debug"
|
|
3
3
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
4
4
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
5
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
@@ -92,6 +92,13 @@ interface InlineNetLabelSolverInput {
|
|
|
92
92
|
netLabelPlacements: NetLabelPlacement[]
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
export interface InlineNetLabelOutput {
|
|
96
|
+
inputProblem: InputProblem
|
|
97
|
+
traces: SolvedTracePath[]
|
|
98
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
99
|
+
inlineNetLabelPlacements: InlineNetLabelPlacement[]
|
|
100
|
+
}
|
|
101
|
+
|
|
95
102
|
type InlineEligibleConnection = InputDirectConnection | InputNetConnection
|
|
96
103
|
|
|
97
104
|
const getPinPairKey = (pinIds: readonly string[]) =>
|
|
@@ -646,95 +653,117 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
646
653
|
// Mirrors the previous pipeline stage's visualization so that a problem
|
|
647
654
|
// with no inline labels renders identically, then layers the inline labels
|
|
648
655
|
// on top.
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
656
|
+
return visualizeInlineNetLabelOutput({
|
|
657
|
+
inputProblem: this.inputProblem,
|
|
658
|
+
...this.getOutput(),
|
|
659
|
+
})
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export const visualizeInlineNetLabelOutput = ({
|
|
664
|
+
inputProblem,
|
|
665
|
+
traces,
|
|
666
|
+
netLabelPlacements,
|
|
667
|
+
inlineNetLabelPlacements,
|
|
668
|
+
}: InlineNetLabelOutput): GraphicsObject => {
|
|
669
|
+
const graphics = visualizeInputProblem(inputProblem)
|
|
670
|
+
graphics.lines ??= []
|
|
671
|
+
graphics.rects ??= []
|
|
672
|
+
graphics.points ??= []
|
|
673
|
+
graphics.texts ??= []
|
|
674
|
+
|
|
675
|
+
for (const trace of traces) {
|
|
676
|
+
graphics.lines.push({
|
|
677
|
+
points: trace.tracePath,
|
|
678
|
+
strokeColor: "purple",
|
|
679
|
+
})
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
for (const label of netLabelPlacements) {
|
|
683
|
+
graphics.rects.push({
|
|
684
|
+
center: label.center,
|
|
685
|
+
width: label.width,
|
|
686
|
+
height: label.height,
|
|
687
|
+
fill: getColorFromString(label.globalConnNetId, 0.35),
|
|
688
|
+
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
689
|
+
label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
|
|
690
|
+
} as Rect & { strokeColor: string })
|
|
691
|
+
graphics.points.push({
|
|
692
|
+
x: label.anchorPoint.x,
|
|
693
|
+
y: label.anchorPoint.y,
|
|
694
|
+
color: getColorFromString(label.globalConnNetId, 0.9),
|
|
695
|
+
label: `anchorPoint\norientation: ${label.orientation}`,
|
|
696
|
+
})
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
for (const inlineLabel of inlineNetLabelPlacements) {
|
|
700
|
+
if (inlineLabel.stubTracePath) {
|
|
657
701
|
graphics.lines.push({
|
|
658
|
-
points:
|
|
702
|
+
points: inlineLabel.stubTracePath,
|
|
659
703
|
strokeColor: "purple",
|
|
660
704
|
})
|
|
661
705
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
height: label.height,
|
|
669
|
-
fill: getColorFromString(label.globalConnNetId, 0.35),
|
|
670
|
-
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
671
|
-
label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
|
|
672
|
-
} as Rect & { strokeColor: string })
|
|
673
|
-
graphics.points.push({
|
|
674
|
-
x: label.anchorPoint.x,
|
|
675
|
-
y: label.anchorPoint.y,
|
|
676
|
-
color: getColorFromString(label.globalConnNetId, 0.9),
|
|
677
|
-
label: `anchorPoint\norientation: ${label.orientation}`,
|
|
678
|
-
})
|
|
706
|
+
const isHorizontal = inlineLabel.axis === "x"
|
|
707
|
+
let renderedWidth = inlineLabel.width
|
|
708
|
+
let renderedHeight = inlineLabel.height
|
|
709
|
+
if (!isHorizontal) {
|
|
710
|
+
renderedWidth = inlineLabel.height
|
|
711
|
+
renderedHeight = inlineLabel.width
|
|
679
712
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
713
|
+
graphics.rects.push({
|
|
714
|
+
center: inlineLabel.center,
|
|
715
|
+
width: renderedWidth,
|
|
716
|
+
height: renderedHeight,
|
|
717
|
+
fill: getColorFromString(inlineLabel.globalConnNetId, 0.35),
|
|
718
|
+
strokeColor: "green",
|
|
719
|
+
label: [
|
|
720
|
+
`INLINE netId: ${inlineLabel.netId}`,
|
|
721
|
+
`axis: ${inlineLabel.axis}`,
|
|
722
|
+
`side: ${inlineLabel.side}`,
|
|
723
|
+
].join("\n"),
|
|
724
|
+
} as Rect & { strokeColor: string })
|
|
725
|
+
|
|
726
|
+
let textX = inlineLabel.center.x
|
|
727
|
+
let textY = inlineLabel.center.y
|
|
728
|
+
let anchorSide: Text["anchorSide"] = "center"
|
|
729
|
+
let rotation: Text["rotation"]
|
|
730
|
+
if (inlineLabel.stubTracePath) {
|
|
731
|
+
const stubStart = inlineLabel.stubTracePath[0]
|
|
732
|
+
const stubEnd = inlineLabel.stubTracePath[1]
|
|
733
|
+
let labelOffset = inlineLabel.width / 2
|
|
734
|
+
if (stubEnd[inlineLabel.axis] > stubStart[inlineLabel.axis]) {
|
|
735
|
+
labelOffset = -inlineLabel.width / 2
|
|
736
|
+
anchorSide = "center_left"
|
|
737
|
+
} else {
|
|
738
|
+
anchorSide = "center_right"
|
|
739
|
+
}
|
|
740
|
+
if (inlineLabel.axis === "x") {
|
|
741
|
+
textX += labelOffset
|
|
742
|
+
} else {
|
|
743
|
+
textY += labelOffset
|
|
687
744
|
}
|
|
688
|
-
const isHorizontal = inlineLabel.axis === "x"
|
|
689
|
-
graphics.rects.push({
|
|
690
|
-
center: inlineLabel.center,
|
|
691
|
-
width: isHorizontal ? inlineLabel.width : inlineLabel.height,
|
|
692
|
-
height: isHorizontal ? inlineLabel.height : inlineLabel.width,
|
|
693
|
-
fill: getColorFromString(inlineLabel.globalConnNetId, 0.35),
|
|
694
|
-
strokeColor: "green",
|
|
695
|
-
label: [
|
|
696
|
-
`INLINE netId: ${inlineLabel.netId}`,
|
|
697
|
-
`axis: ${inlineLabel.axis}`,
|
|
698
|
-
`side: ${inlineLabel.side}`,
|
|
699
|
-
].join("\n"),
|
|
700
|
-
} as Rect & { strokeColor: string })
|
|
701
|
-
graphics.texts.push({
|
|
702
|
-
x:
|
|
703
|
-
inlineLabel.stubTracePath && inlineLabel.axis === "x"
|
|
704
|
-
? inlineLabel.center.x +
|
|
705
|
-
(inlineLabel.stubTracePath[1].x > inlineLabel.stubTracePath[0].x
|
|
706
|
-
? -inlineLabel.width / 2
|
|
707
|
-
: inlineLabel.width / 2)
|
|
708
|
-
: inlineLabel.center.x,
|
|
709
|
-
y:
|
|
710
|
-
inlineLabel.stubTracePath && inlineLabel.axis === "y"
|
|
711
|
-
? inlineLabel.center.y +
|
|
712
|
-
(inlineLabel.stubTracePath[1].y > inlineLabel.stubTracePath[0].y
|
|
713
|
-
? -inlineLabel.width / 2
|
|
714
|
-
: inlineLabel.width / 2)
|
|
715
|
-
: inlineLabel.center.y,
|
|
716
|
-
text: inlineLabel.netLabelText ?? inlineLabel.netId ?? "",
|
|
717
|
-
color: "green",
|
|
718
|
-
fontSize: inlineLabel.height,
|
|
719
|
-
anchorSide: inlineLabel.stubTracePath
|
|
720
|
-
? inlineLabel.stubTracePath[1][inlineLabel.axis] >
|
|
721
|
-
inlineLabel.stubTracePath[0][inlineLabel.axis]
|
|
722
|
-
? "center_left"
|
|
723
|
-
: "center_right"
|
|
724
|
-
: "center",
|
|
725
|
-
// Vertical labels read bottom-to-top, alongside the wire they name.
|
|
726
|
-
rotation: inlineLabel.axis === "y" ? 90 : undefined,
|
|
727
|
-
})
|
|
728
|
-
graphics.points.push({
|
|
729
|
-
x: inlineLabel.anchorPoint.x,
|
|
730
|
-
y: inlineLabel.anchorPoint.y,
|
|
731
|
-
color: "green",
|
|
732
|
-
label: `inline anchor\n${inlineLabel.netLabelText ?? inlineLabel.netId}`,
|
|
733
|
-
})
|
|
734
745
|
}
|
|
735
|
-
|
|
736
|
-
|
|
746
|
+
if (inlineLabel.axis === "y") rotation = 90
|
|
747
|
+
|
|
748
|
+
graphics.texts.push({
|
|
749
|
+
x: textX,
|
|
750
|
+
y: textY,
|
|
751
|
+
text: inlineLabel.netLabelText ?? inlineLabel.netId ?? "",
|
|
752
|
+
color: "green",
|
|
753
|
+
fontSize: inlineLabel.height,
|
|
754
|
+
anchorSide,
|
|
755
|
+
// Vertical labels read bottom-to-top, alongside the wire they name.
|
|
756
|
+
rotation,
|
|
757
|
+
})
|
|
758
|
+
graphics.points.push({
|
|
759
|
+
x: inlineLabel.anchorPoint.x,
|
|
760
|
+
y: inlineLabel.anchorPoint.y,
|
|
761
|
+
color: "green",
|
|
762
|
+
label: `inline anchor\n${inlineLabel.netLabelText ?? inlineLabel.netId}`,
|
|
763
|
+
})
|
|
737
764
|
}
|
|
765
|
+
|
|
766
|
+
return graphics
|
|
738
767
|
}
|
|
739
768
|
|
|
740
769
|
/**
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import {
|
|
2
|
+
doesSegmentIntersectRect,
|
|
3
|
+
getBoundFromCenteredRect,
|
|
4
|
+
type Point,
|
|
5
|
+
} from "@tscircuit/math-utils"
|
|
6
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
7
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
8
|
+
import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
|
|
9
|
+
import { doesPairCrossRestrictedCenterLines } from "lib/solvers/MspConnectionPairSolver/doesPairCrossRestrictedCenterLines"
|
|
10
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
11
|
+
import {
|
|
12
|
+
getTraceRecoveryConnectivityMaps,
|
|
13
|
+
type TraceRecoveryPin,
|
|
14
|
+
} from "lib/solvers/NetLabelTraceRecovery/getTraceRecoveryConnectivityMaps"
|
|
15
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
16
|
+
import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
|
|
17
|
+
import type {
|
|
18
|
+
ChipId,
|
|
19
|
+
InputChip,
|
|
20
|
+
InputProblem,
|
|
21
|
+
PinId,
|
|
22
|
+
} from "lib/types/InputProblem"
|
|
23
|
+
import { arePinsInDifferentSchematicSections } from "lib/utils/arePinsInDifferentSchematicSections"
|
|
24
|
+
import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces"
|
|
25
|
+
import {
|
|
26
|
+
type InlineNetLabelPlacement,
|
|
27
|
+
type InlineNetLabelOutput,
|
|
28
|
+
visualizeInlineNetLabelOutput,
|
|
29
|
+
} from "../InlineNetLabelSolver/InlineNetLabelSolver"
|
|
30
|
+
|
|
31
|
+
type GlobalConnNetId = NetLabelPlacement["globalConnNetId"]
|
|
32
|
+
|
|
33
|
+
interface CandidatePair {
|
|
34
|
+
firstLabel: NetLabelPlacement
|
|
35
|
+
secondLabel: NetLabelPlacement
|
|
36
|
+
pins: [TraceRecoveryPin, TraceRecoveryPin]
|
|
37
|
+
perpendicularOffset: number
|
|
38
|
+
routeDistance: number
|
|
39
|
+
key: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const AVAILABLE_NET_ORIENTATION_PREFIX = "available-net-orientation-"
|
|
43
|
+
const RECOVERED_TRACE_PREFIX = "net-label-to-trace-"
|
|
44
|
+
|
|
45
|
+
const getCanonicalPairKey = (firstPinId: PinId, secondPinId: PinId) =>
|
|
46
|
+
[firstPinId, secondPinId].sort().join("--")
|
|
47
|
+
|
|
48
|
+
export const pathIntersectsRenderedLabel = (
|
|
49
|
+
path: Point[],
|
|
50
|
+
label: NetLabelPlacement | InlineNetLabelPlacement,
|
|
51
|
+
) => {
|
|
52
|
+
let width = label.width
|
|
53
|
+
let height = label.height
|
|
54
|
+
if ("axis" in label && label.axis === "y") {
|
|
55
|
+
width = label.height
|
|
56
|
+
height = label.width
|
|
57
|
+
}
|
|
58
|
+
const bounds = getBoundFromCenteredRect({
|
|
59
|
+
center: label.center,
|
|
60
|
+
width,
|
|
61
|
+
height,
|
|
62
|
+
})
|
|
63
|
+
for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
|
|
64
|
+
if (
|
|
65
|
+
doesSegmentIntersectRect(path[pathIndex]!, path[pathIndex + 1]!, bounds)
|
|
66
|
+
) {
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const getPerpendicularOffset = (
|
|
74
|
+
firstPin: TraceRecoveryPin,
|
|
75
|
+
secondPin: TraceRecoveryPin,
|
|
76
|
+
) => {
|
|
77
|
+
const xDistance = Math.abs(firstPin.x - secondPin.x)
|
|
78
|
+
const yDistance = Math.abs(firstPin.y - secondPin.y)
|
|
79
|
+
if (xDistance >= yDistance) return yDistance
|
|
80
|
+
return xDistance
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class NetLabelToTraceSolver extends BaseSolver {
|
|
84
|
+
inputProblem: InputProblem
|
|
85
|
+
|
|
86
|
+
outputTraces: SolvedTracePath[]
|
|
87
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
88
|
+
|
|
89
|
+
private chipMap: Record<ChipId, InputChip>
|
|
90
|
+
private pinMap: Map<PinId, TraceRecoveryPin>
|
|
91
|
+
private queuedCandidates: CandidatePair[]
|
|
92
|
+
private currentCandidate: CandidatePair | null = null
|
|
93
|
+
declare activeSubSolver: SchematicTraceSingleLineSolver2 | null
|
|
94
|
+
|
|
95
|
+
constructor(private input: InlineNetLabelOutput) {
|
|
96
|
+
super()
|
|
97
|
+
this.inputProblem = input.inputProblem
|
|
98
|
+
this.outputTraces = [...input.traces]
|
|
99
|
+
this.outputNetLabelPlacements = [...input.netLabelPlacements]
|
|
100
|
+
|
|
101
|
+
const { chipMap, pinMap } = getTraceRecoveryConnectivityMaps(
|
|
102
|
+
this.inputProblem,
|
|
103
|
+
)
|
|
104
|
+
this.chipMap = chipMap
|
|
105
|
+
this.pinMap = pinMap
|
|
106
|
+
|
|
107
|
+
this.queuedCandidates = this.buildCandidatePairs()
|
|
108
|
+
this.stats.candidateCount = this.queuedCandidates.length
|
|
109
|
+
this.stats.recoveredTraceCount = 0
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
override getConstructorParams(): [InlineNetLabelOutput] {
|
|
113
|
+
return [this.input]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private isEligiblePortOnlyDirectConnectionLabel(
|
|
117
|
+
label: NetLabelPlacement,
|
|
118
|
+
groundGlobalConnNetId?: GlobalConnNetId,
|
|
119
|
+
) {
|
|
120
|
+
if (
|
|
121
|
+
label.pinIds.length !== 1 ||
|
|
122
|
+
label.mspConnectionPairIds.length !== 0 ||
|
|
123
|
+
!label.netId ||
|
|
124
|
+
label.netId === "GND" ||
|
|
125
|
+
label.globalConnNetId === groundGlobalConnNetId
|
|
126
|
+
) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const pinId = label.pinIds[0]!
|
|
131
|
+
return this.inputProblem.directConnections.some(
|
|
132
|
+
(connection) =>
|
|
133
|
+
connection.netId === label.netId && connection.pinIds.includes(pinId),
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private buildCandidatePairs() {
|
|
138
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
139
|
+
this.inputProblem,
|
|
140
|
+
)
|
|
141
|
+
const groundGlobalConnNetId =
|
|
142
|
+
netConnMap.getNetConnectedToId("GND") ?? undefined
|
|
143
|
+
const labelsByGlobalNet = new Map<GlobalConnNetId, NetLabelPlacement[]>()
|
|
144
|
+
|
|
145
|
+
for (const label of this.input.netLabelPlacements) {
|
|
146
|
+
if (
|
|
147
|
+
!this.isEligiblePortOnlyDirectConnectionLabel(
|
|
148
|
+
label,
|
|
149
|
+
groundGlobalConnNetId,
|
|
150
|
+
)
|
|
151
|
+
) {
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
const labels = labelsByGlobalNet.get(label.globalConnNetId) ?? []
|
|
155
|
+
labels.push(label)
|
|
156
|
+
labelsByGlobalNet.set(label.globalConnNetId, labels)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const candidates: CandidatePair[] = []
|
|
160
|
+
for (const labels of labelsByGlobalNet.values()) {
|
|
161
|
+
for (let firstIndex = 0; firstIndex < labels.length; firstIndex++) {
|
|
162
|
+
for (
|
|
163
|
+
let secondIndex = firstIndex + 1;
|
|
164
|
+
secondIndex < labels.length;
|
|
165
|
+
secondIndex++
|
|
166
|
+
) {
|
|
167
|
+
const firstLabel = labels[firstIndex]!
|
|
168
|
+
const secondLabel = labels[secondIndex]!
|
|
169
|
+
const firstPin = this.pinMap.get(firstLabel.pinIds[0]!)
|
|
170
|
+
const secondPin = this.pinMap.get(secondLabel.pinIds[0]!)
|
|
171
|
+
if (!firstPin || !secondPin) continue
|
|
172
|
+
if (
|
|
173
|
+
arePinsInDifferentSchematicSections(
|
|
174
|
+
this.inputProblem,
|
|
175
|
+
firstPin,
|
|
176
|
+
secondPin,
|
|
177
|
+
)
|
|
178
|
+
) {
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
if (
|
|
182
|
+
doesPairCrossRestrictedCenterLines({
|
|
183
|
+
inputProblem: this.inputProblem,
|
|
184
|
+
chipMap: this.chipMap,
|
|
185
|
+
pinIdMap: this.pinMap,
|
|
186
|
+
p1: firstPin,
|
|
187
|
+
p2: secondPin,
|
|
188
|
+
})
|
|
189
|
+
) {
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
candidates.push({
|
|
194
|
+
firstLabel,
|
|
195
|
+
secondLabel,
|
|
196
|
+
pins: [firstPin, secondPin],
|
|
197
|
+
perpendicularOffset: getPerpendicularOffset(firstPin, secondPin),
|
|
198
|
+
routeDistance:
|
|
199
|
+
Math.abs(firstPin.x - secondPin.x) +
|
|
200
|
+
Math.abs(firstPin.y - secondPin.y),
|
|
201
|
+
key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
candidates.sort(
|
|
208
|
+
(first, second) =>
|
|
209
|
+
first.perpendicularOffset - second.perpendicularOffset ||
|
|
210
|
+
first.routeDistance - second.routeDistance ||
|
|
211
|
+
first.key.localeCompare(second.key),
|
|
212
|
+
)
|
|
213
|
+
return candidates
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private isSupersededConnectorTrace(
|
|
217
|
+
trace: SolvedTracePath,
|
|
218
|
+
candidate: CandidatePair,
|
|
219
|
+
) {
|
|
220
|
+
if (!trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_PREFIX)) {
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
if (trace.pinIds.length !== 1) return false
|
|
224
|
+
return candidate.pins.some((pin) => pin.pinId === trace.pinIds[0])
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private routeIntersectsRemainingLabels(
|
|
228
|
+
tracePath: Point[],
|
|
229
|
+
candidate: CandidatePair,
|
|
230
|
+
) {
|
|
231
|
+
for (const label of this.outputNetLabelPlacements) {
|
|
232
|
+
if (label === candidate.firstLabel || label === candidate.secondLabel) {
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
if (pathIntersectsRenderedLabel(tracePath, label)) return true
|
|
236
|
+
}
|
|
237
|
+
return this.input.inlineNetLabelPlacements.some((label) =>
|
|
238
|
+
pathIntersectsRenderedLabel(tracePath, label),
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private tryAcceptCurrentRoute() {
|
|
243
|
+
const candidate = this.currentCandidate
|
|
244
|
+
const tracePath = this.activeSubSolver?.solvedTracePath
|
|
245
|
+
if (!candidate || !tracePath) return
|
|
246
|
+
|
|
247
|
+
const retainedTraces = this.outputTraces.filter(
|
|
248
|
+
(trace) => !this.isSupersededConnectorTrace(trace, candidate),
|
|
249
|
+
)
|
|
250
|
+
if (
|
|
251
|
+
doesTraceOverlapWithExistingTraces(tracePath, retainedTraces) ||
|
|
252
|
+
this.routeIntersectsRemainingLabels(tracePath, candidate)
|
|
253
|
+
) {
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const [firstPin, secondPin] = candidate.pins
|
|
258
|
+
const mspPairId = `${RECOVERED_TRACE_PREFIX}${candidate.key}`
|
|
259
|
+
const recoveredTrace: SolvedTracePath = {
|
|
260
|
+
mspPairId,
|
|
261
|
+
dcConnNetId: candidate.firstLabel.globalConnNetId,
|
|
262
|
+
globalConnNetId: candidate.firstLabel.globalConnNetId,
|
|
263
|
+
pins: [firstPin, secondPin],
|
|
264
|
+
tracePath,
|
|
265
|
+
mspConnectionPairIds: [mspPairId],
|
|
266
|
+
pinIds: [firstPin.pinId, secondPin.pinId],
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this.outputTraces = [...retainedTraces, recoveredTrace]
|
|
270
|
+
this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
|
|
271
|
+
(label) =>
|
|
272
|
+
label !== candidate.firstLabel && label !== candidate.secondLabel,
|
|
273
|
+
)
|
|
274
|
+
this.stats.recoveredTraceCount++
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
override _step() {
|
|
278
|
+
if (this.activeSubSolver) {
|
|
279
|
+
this.activeSubSolver.step()
|
|
280
|
+
if (this.activeSubSolver.solved) {
|
|
281
|
+
this.tryAcceptCurrentRoute()
|
|
282
|
+
this.activeSubSolver = null
|
|
283
|
+
this.currentCandidate = null
|
|
284
|
+
} else if (this.activeSubSolver.failed) {
|
|
285
|
+
this.activeSubSolver = null
|
|
286
|
+
this.currentCandidate = null
|
|
287
|
+
}
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let candidate = this.queuedCandidates.shift()
|
|
292
|
+
while (
|
|
293
|
+
candidate &&
|
|
294
|
+
(!this.outputNetLabelPlacements.includes(candidate.firstLabel) ||
|
|
295
|
+
!this.outputNetLabelPlacements.includes(candidate.secondLabel))
|
|
296
|
+
) {
|
|
297
|
+
candidate = this.queuedCandidates.shift()
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!candidate) {
|
|
301
|
+
this.solved = true
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.currentCandidate = candidate
|
|
306
|
+
this.activeSubSolver = new SchematicTraceSingleLineSolver2({
|
|
307
|
+
inputProblem: this.inputProblem,
|
|
308
|
+
pins: candidate.pins,
|
|
309
|
+
chipMap: this.chipMap,
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
getOutput() {
|
|
314
|
+
return {
|
|
315
|
+
traces: this.outputTraces,
|
|
316
|
+
netLabelPlacements: this.outputNetLabelPlacements,
|
|
317
|
+
inlineNetLabelPlacements: this.input.inlineNetLabelPlacements,
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
override visualize(): GraphicsObject {
|
|
322
|
+
if (this.activeSubSolver) return this.activeSubSolver.visualize()
|
|
323
|
+
|
|
324
|
+
const graphics = visualizeInlineNetLabelOutput({
|
|
325
|
+
inputProblem: this.inputProblem,
|
|
326
|
+
...this.getOutput(),
|
|
327
|
+
})
|
|
328
|
+
for (const trace of this.outputTraces) {
|
|
329
|
+
if (!trace.mspPairId.startsWith(RECOVERED_TRACE_PREFIX)) continue
|
|
330
|
+
graphics.lines!.push({
|
|
331
|
+
points: trace.tracePath,
|
|
332
|
+
strokeColor: "green",
|
|
333
|
+
label: `recovered from net labels: ${trace.pinIds.join(" -> ")}`,
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
return graphics
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getPinDirection } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
|
|
2
|
+
import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
3
|
+
import type {
|
|
4
|
+
ChipId,
|
|
5
|
+
InputChip,
|
|
6
|
+
InputProblem,
|
|
7
|
+
PinId,
|
|
8
|
+
} from "lib/types/InputProblem"
|
|
9
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
10
|
+
|
|
11
|
+
export type TraceRecoveryPin = MspConnectionPair["pins"][number] & {
|
|
12
|
+
_facingDirection: FacingDirection
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getTraceRecoveryConnectivityMaps = (
|
|
16
|
+
inputProblem: InputProblem,
|
|
17
|
+
) => {
|
|
18
|
+
const chipMap: Record<ChipId, InputChip> = {}
|
|
19
|
+
const pinMap = new Map<PinId, TraceRecoveryPin>()
|
|
20
|
+
|
|
21
|
+
for (const chip of inputProblem.chips) {
|
|
22
|
+
chipMap[chip.chipId] = chip
|
|
23
|
+
for (const pin of chip.pins) {
|
|
24
|
+
pinMap.set(pin.pinId, {
|
|
25
|
+
...pin,
|
|
26
|
+
chipId: chip.chipId,
|
|
27
|
+
_facingDirection: pin._facingDirection ?? getPinDirection(pin, chip),
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { chipMap, pinMap }
|
|
33
|
+
}
|
|
@@ -35,6 +35,7 @@ import { UnroutedTraceRecoverySolver } from "../UnroutedTraceRecoverySolver/Unro
|
|
|
35
35
|
import { SameNetJunctionAlignmentSolver } from "../SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver"
|
|
36
36
|
import { TraceElbowTransitionSimplificationSolver } from "../TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver"
|
|
37
37
|
import { InlineNetLabelSolver } from "../InlineNetLabelSolver/InlineNetLabelSolver"
|
|
38
|
+
import { NetLabelToTraceSolver } from "../NetLabelToTraceSolver/NetLabelToTraceSolver"
|
|
38
39
|
import { findPerpendicularPathCrossings } from "../TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles"
|
|
39
40
|
|
|
40
41
|
type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
|
|
@@ -103,6 +104,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
103
104
|
finalTraceElbowTransitionSimplificationSolver?: TraceElbowTransitionSimplificationSolver
|
|
104
105
|
sameNetJunctionAlignmentSolver?: SameNetJunctionAlignmentSolver
|
|
105
106
|
inlineNetLabelSolver?: InlineNetLabelSolver
|
|
107
|
+
netLabelToTraceSolver?: NetLabelToTraceSolver
|
|
106
108
|
|
|
107
109
|
startTimeOfPhase: Record<string, number>
|
|
108
110
|
endTimeOfPhase: Record<string, number>
|
|
@@ -588,6 +590,19 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
588
590
|
]
|
|
589
591
|
},
|
|
590
592
|
),
|
|
593
|
+
definePipelineStep(
|
|
594
|
+
"netLabelToTraceSolver",
|
|
595
|
+
NetLabelToTraceSolver,
|
|
596
|
+
(instance) => {
|
|
597
|
+
const inlineOutput = instance.inlineNetLabelSolver!.getOutput()
|
|
598
|
+
return [
|
|
599
|
+
{
|
|
600
|
+
inputProblem: instance.inputProblem,
|
|
601
|
+
...inlineOutput,
|
|
602
|
+
},
|
|
603
|
+
]
|
|
604
|
+
},
|
|
605
|
+
),
|
|
591
606
|
]
|
|
592
607
|
|
|
593
608
|
constructor(inputProblem: InputProblem, opts?: Options) {
|