@tscircuit/schematic-trace-solver 0.0.50 → 0.0.52
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/dist/index.d.ts +120 -3
- package/dist/index.js +1149 -51
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +30 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver.ts +331 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/candidates.ts +429 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/geometry.ts +250 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/types.ts +47 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/visualize.ts +125 -0
- package/lib/solvers/VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts +268 -0
- package/lib/solvers/VccNetLabelCornerPlacementSolver/geometry.ts +59 -0
- package/lib/solvers/VccNetLabelCornerPlacementSolver/types.ts +37 -0
- package/lib/solvers/VccNetLabelCornerPlacementSolver/visualize.ts +114 -0
- package/package.json +1 -1
- package/tests/examples/__snapshots__/example12.snap.svg +2 -2
- package/tests/examples/__snapshots__/example14.snap.svg +68 -68
- package/tests/examples/__snapshots__/example17.snap.svg +2 -2
- package/tests/examples/__snapshots__/example30.snap.svg +59 -59
- package/tests/examples/__snapshots__/example31.snap.svg +12 -12
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
5
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
6
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
7
|
+
import type { LabelCandidate, LabelOverlap } from "./types"
|
|
8
|
+
|
|
9
|
+
const CANDIDATE_SELECTED_COLOR = "blue"
|
|
10
|
+
const CANDIDATE_REJECTED_COLOR = "red"
|
|
11
|
+
|
|
12
|
+
export const visualizeTraceAnchoredNetLabelOverlapSolver = (state: {
|
|
13
|
+
inputProblem: InputProblem
|
|
14
|
+
traces: SolvedTracePath[]
|
|
15
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
16
|
+
currentOverlap: LabelOverlap | null
|
|
17
|
+
currentCandidateResults: LabelCandidate[]
|
|
18
|
+
solved: boolean
|
|
19
|
+
}): GraphicsObject => {
|
|
20
|
+
const graphics = visualizeInputProblem(state.inputProblem)
|
|
21
|
+
ensureGraphicsArrays(graphics)
|
|
22
|
+
|
|
23
|
+
drawTraces(graphics, state.traces)
|
|
24
|
+
drawNetLabels(graphics, state.outputNetLabelPlacements)
|
|
25
|
+
|
|
26
|
+
if (!state.solved) {
|
|
27
|
+
drawCurrentOverlap(graphics, state)
|
|
28
|
+
drawCurrentCandidates(graphics, state.currentCandidateResults)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return graphics
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const drawTraces = (graphics: GraphicsObject, traces: SolvedTracePath[]) => {
|
|
35
|
+
for (const trace of traces) {
|
|
36
|
+
graphics.lines!.push({
|
|
37
|
+
points: trace.tracePath,
|
|
38
|
+
strokeColor: "purple",
|
|
39
|
+
} as any)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const drawNetLabels = (
|
|
44
|
+
graphics: GraphicsObject,
|
|
45
|
+
labels: NetLabelPlacement[],
|
|
46
|
+
) => {
|
|
47
|
+
for (const label of labels) {
|
|
48
|
+
graphics.rects!.push({
|
|
49
|
+
center: label.center,
|
|
50
|
+
width: label.width,
|
|
51
|
+
height: label.height,
|
|
52
|
+
fill: getColorFromString(label.globalConnNetId, 0.35),
|
|
53
|
+
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
54
|
+
label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
|
|
55
|
+
} as any)
|
|
56
|
+
graphics.points!.push({
|
|
57
|
+
x: label.anchorPoint.x,
|
|
58
|
+
y: label.anchorPoint.y,
|
|
59
|
+
color: getColorFromString(label.globalConnNetId, 0.9),
|
|
60
|
+
label: `anchorPoint\norientation: ${label.orientation}`,
|
|
61
|
+
} as any)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const drawCurrentOverlap = (
|
|
66
|
+
graphics: GraphicsObject,
|
|
67
|
+
state: {
|
|
68
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
69
|
+
currentOverlap: LabelOverlap | null
|
|
70
|
+
},
|
|
71
|
+
) => {
|
|
72
|
+
if (!state.currentOverlap) return
|
|
73
|
+
|
|
74
|
+
for (const labelIndex of [
|
|
75
|
+
state.currentOverlap.firstLabelIndex,
|
|
76
|
+
state.currentOverlap.secondLabelIndex,
|
|
77
|
+
]) {
|
|
78
|
+
const label = state.outputNetLabelPlacements[labelIndex]
|
|
79
|
+
if (!label) continue
|
|
80
|
+
|
|
81
|
+
graphics.rects!.push({
|
|
82
|
+
center: label.center,
|
|
83
|
+
width: label.width,
|
|
84
|
+
height: label.height,
|
|
85
|
+
fill: "rgba(255, 0, 0, 0.2)",
|
|
86
|
+
strokeColor: CANDIDATE_REJECTED_COLOR,
|
|
87
|
+
label: `netlabel overlap target\n${label.netId ?? label.globalConnNetId}`,
|
|
88
|
+
} as any)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const drawCurrentCandidates = (
|
|
93
|
+
graphics: GraphicsObject,
|
|
94
|
+
candidates: LabelCandidate[],
|
|
95
|
+
) => {
|
|
96
|
+
for (const candidate of candidates) {
|
|
97
|
+
const color = candidate.selected
|
|
98
|
+
? CANDIDATE_SELECTED_COLOR
|
|
99
|
+
: CANDIDATE_REJECTED_COLOR
|
|
100
|
+
graphics.rects!.push({
|
|
101
|
+
center: candidate.center,
|
|
102
|
+
width: candidate.width,
|
|
103
|
+
height: candidate.height,
|
|
104
|
+
fill: candidate.selected
|
|
105
|
+
? "rgba(0, 0, 255, 0.2)"
|
|
106
|
+
: "rgba(255, 0, 0, 0.15)",
|
|
107
|
+
strokeColor: color,
|
|
108
|
+
strokeDash: candidate.selected ? undefined : "4 2",
|
|
109
|
+
label: `${candidate.selected ? "selected" : candidate.status} netlabel overlap candidate\ntrace: ${candidate.traceId}\ndistance: ${candidate.distanceFromOriginal.toFixed(3)}`,
|
|
110
|
+
} as any)
|
|
111
|
+
graphics.points!.push({
|
|
112
|
+
...candidate.anchorPoint,
|
|
113
|
+
color,
|
|
114
|
+
label: `candidate anchor\n${candidate.status}`,
|
|
115
|
+
} as any)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const ensureGraphicsArrays = (graphics: GraphicsObject) => {
|
|
120
|
+
if (!graphics.lines) graphics.lines = []
|
|
121
|
+
if (!graphics.points) graphics.points = []
|
|
122
|
+
if (!graphics.rects) graphics.rects = []
|
|
123
|
+
if (!graphics.circles) graphics.circles = []
|
|
124
|
+
if (!graphics.texts) graphics.texts = []
|
|
125
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import { traceCrossesBoundsInterior } from "lib/solvers/AvailableNetOrientationSolver/geometry"
|
|
3
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
4
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
|
+
import {
|
|
6
|
+
getCenterFromAnchor,
|
|
7
|
+
getRectBounds,
|
|
8
|
+
} from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
9
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
10
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
11
|
+
import {
|
|
12
|
+
getDistance,
|
|
13
|
+
getTraceCorners,
|
|
14
|
+
isTraceLine,
|
|
15
|
+
rectsOverlap,
|
|
16
|
+
tracePathContainsPoint,
|
|
17
|
+
} from "./geometry"
|
|
18
|
+
import type {
|
|
19
|
+
Bounds,
|
|
20
|
+
CornerCandidateStatus,
|
|
21
|
+
EvaluatedCornerCandidate,
|
|
22
|
+
TraceCornerCandidate,
|
|
23
|
+
VccNetLabelCornerPlacementSolverParams,
|
|
24
|
+
} from "./types"
|
|
25
|
+
import { visualizeVccNetLabelCornerPlacementSolver } from "./visualize"
|
|
26
|
+
|
|
27
|
+
export class VccNetLabelCornerPlacementSolver extends BaseSolver {
|
|
28
|
+
inputProblem: InputProblem
|
|
29
|
+
traces: SolvedTracePath[]
|
|
30
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
31
|
+
|
|
32
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
33
|
+
queuedLabelIndices: number[] = []
|
|
34
|
+
currentLabelIndex: number | null = null
|
|
35
|
+
currentLabel: NetLabelPlacement | null = null
|
|
36
|
+
currentCandidateResults: EvaluatedCornerCandidate[] = []
|
|
37
|
+
|
|
38
|
+
private queuedCornerCandidates: TraceCornerCandidate[] = []
|
|
39
|
+
private shouldAdvanceToNextLabel = false
|
|
40
|
+
private traceMap: Record<string, SolvedTracePath>
|
|
41
|
+
|
|
42
|
+
constructor(params: VccNetLabelCornerPlacementSolverParams) {
|
|
43
|
+
super()
|
|
44
|
+
this.inputProblem = params.inputProblem
|
|
45
|
+
this.traces = params.traces
|
|
46
|
+
this.netLabelPlacements = params.netLabelPlacements
|
|
47
|
+
this.outputNetLabelPlacements = [...params.netLabelPlacements]
|
|
48
|
+
this.traceMap = Object.fromEntries(
|
|
49
|
+
params.traces.map((trace) => [trace.mspPairId, trace]),
|
|
50
|
+
)
|
|
51
|
+
this.queuedLabelIndices = this.getProcessableLabelIndices()
|
|
52
|
+
this.prepareNextLabel()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override getConstructorParams(): ConstructorParameters<
|
|
56
|
+
typeof VccNetLabelCornerPlacementSolver
|
|
57
|
+
>[0] {
|
|
58
|
+
return {
|
|
59
|
+
inputProblem: this.inputProblem,
|
|
60
|
+
traces: this.traces,
|
|
61
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override _step() {
|
|
66
|
+
if (this.shouldAdvanceToNextLabel) {
|
|
67
|
+
this.advanceToNextLabel()
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const label = this.currentLabel
|
|
72
|
+
const labelIndex = this.currentLabelIndex
|
|
73
|
+
if (label === null || labelIndex === null) {
|
|
74
|
+
this.finish()
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const candidate = this.queuedCornerCandidates.shift()
|
|
79
|
+
if (!candidate) {
|
|
80
|
+
this.shouldAdvanceToNextLabel = true
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = this.evaluateCornerCandidate(label, labelIndex, candidate)
|
|
85
|
+
this.currentCandidateResults.push(result)
|
|
86
|
+
|
|
87
|
+
if (result.status !== "valid") return
|
|
88
|
+
|
|
89
|
+
result.selected = true
|
|
90
|
+
this.applyCandidate(labelIndex, label, result)
|
|
91
|
+
this.shouldAdvanceToNextLabel = true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getOutput() {
|
|
95
|
+
return {
|
|
96
|
+
netLabelPlacements: this.outputNetLabelPlacements,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private getProcessableLabelIndices() {
|
|
101
|
+
const labelIndices: number[] = []
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
|
|
104
|
+
const label = this.outputNetLabelPlacements[i]!
|
|
105
|
+
if (!this.shouldProcessLabel(label)) continue
|
|
106
|
+
|
|
107
|
+
labelIndices.push(i)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return labelIndices
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private prepareNextLabel() {
|
|
114
|
+
const labelIndex = this.queuedLabelIndices.shift()
|
|
115
|
+
if (labelIndex === undefined) {
|
|
116
|
+
this.clearCurrentLabel()
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const label = this.outputNetLabelPlacements[labelIndex]!
|
|
121
|
+
this.currentLabelIndex = labelIndex
|
|
122
|
+
this.currentLabel = label
|
|
123
|
+
this.currentCandidateResults = []
|
|
124
|
+
this.queuedCornerCandidates = this.getCornerCandidatesForLabel(label)
|
|
125
|
+
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private advanceToNextLabel() {
|
|
130
|
+
this.shouldAdvanceToNextLabel = false
|
|
131
|
+
if (!this.prepareNextLabel()) this.finish()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private clearCurrentLabel() {
|
|
135
|
+
this.currentLabelIndex = null
|
|
136
|
+
this.currentLabel = null
|
|
137
|
+
this.currentCandidateResults = []
|
|
138
|
+
this.queuedCornerCandidates = []
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private finish() {
|
|
142
|
+
this.clearCurrentLabel()
|
|
143
|
+
this.solved = true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private evaluateCornerCandidate(
|
|
147
|
+
label: NetLabelPlacement,
|
|
148
|
+
labelIndex: number,
|
|
149
|
+
candidate: TraceCornerCandidate,
|
|
150
|
+
): EvaluatedCornerCandidate {
|
|
151
|
+
const center = getCenterFromAnchor(
|
|
152
|
+
candidate.anchorPoint,
|
|
153
|
+
label.orientation,
|
|
154
|
+
label.width,
|
|
155
|
+
label.height,
|
|
156
|
+
)
|
|
157
|
+
const bounds = getRectBounds(center, label.width, label.height)
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
...candidate,
|
|
161
|
+
center,
|
|
162
|
+
width: label.width,
|
|
163
|
+
height: label.height,
|
|
164
|
+
status: this.getCandidateStatus(bounds, labelIndex),
|
|
165
|
+
selected: false,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private getCandidateStatus(
|
|
170
|
+
bounds: Bounds,
|
|
171
|
+
labelIndex: number,
|
|
172
|
+
): CornerCandidateStatus {
|
|
173
|
+
if (this.intersectsAnyChip(bounds)) return "chip-collision"
|
|
174
|
+
if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
|
|
175
|
+
return "trace-collision"
|
|
176
|
+
}
|
|
177
|
+
if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
|
|
178
|
+
return "netlabel-collision"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return "valid"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private applyCandidate(
|
|
185
|
+
labelIndex: number,
|
|
186
|
+
label: NetLabelPlacement,
|
|
187
|
+
candidate: EvaluatedCornerCandidate,
|
|
188
|
+
) {
|
|
189
|
+
this.outputNetLabelPlacements[labelIndex] = {
|
|
190
|
+
...label,
|
|
191
|
+
anchorPoint: candidate.anchorPoint,
|
|
192
|
+
center: candidate.center,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private shouldProcessLabel(label: NetLabelPlacement) {
|
|
197
|
+
return (
|
|
198
|
+
label.netId === "VCC" &&
|
|
199
|
+
this.getCornerCandidatesForLabel(label).length > 0
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private intersectsAnyChip(bounds: Bounds) {
|
|
204
|
+
return this.inputProblem.chips.some((chip) =>
|
|
205
|
+
rectsOverlap(bounds, getRectBounds(chip.center, chip.width, chip.height)),
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private intersectsAnyOtherNetLabel(bounds: Bounds, labelIndex: number) {
|
|
210
|
+
return this.outputNetLabelPlacements.some((label, index) => {
|
|
211
|
+
if (index === labelIndex) return false
|
|
212
|
+
return rectsOverlap(
|
|
213
|
+
bounds,
|
|
214
|
+
getRectBounds(label.center, label.width, label.height),
|
|
215
|
+
)
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private getCornerCandidatesForLabel(label: NetLabelPlacement) {
|
|
220
|
+
const candidates: TraceCornerCandidate[] = []
|
|
221
|
+
const seenCornerKeys = new Set<string>()
|
|
222
|
+
|
|
223
|
+
for (const trace of this.getTraceLinesForLabel(label)) {
|
|
224
|
+
for (const anchorPoint of getTraceCorners(trace.tracePath)) {
|
|
225
|
+
const key = `${anchorPoint.x}:${anchorPoint.y}`
|
|
226
|
+
if (seenCornerKeys.has(key)) continue
|
|
227
|
+
seenCornerKeys.add(key)
|
|
228
|
+
candidates.push({
|
|
229
|
+
anchorPoint,
|
|
230
|
+
traceId: trace.mspPairId,
|
|
231
|
+
distance: getDistance(anchorPoint, label.anchorPoint),
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return candidates.sort((a, b) => a.distance - b.distance)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private getTraceLinesForLabel(label: NetLabelPlacement) {
|
|
240
|
+
return this.traces.filter(
|
|
241
|
+
(trace) => isTraceLine(trace) && this.isTraceForLabel(trace, label),
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private isTraceForLabel(trace: SolvedTracePath, label: NetLabelPlacement) {
|
|
246
|
+
if (!tracePathContainsPoint(trace.tracePath, label.anchorPoint)) {
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const traceIds = new Set(label.mspConnectionPairIds)
|
|
251
|
+
if (traceIds.size > 0) {
|
|
252
|
+
return traceIds.has(trace.mspPairId)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return trace.globalConnNetId === label.globalConnNetId
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
override visualize(): GraphicsObject {
|
|
259
|
+
return visualizeVccNetLabelCornerPlacementSolver({
|
|
260
|
+
inputProblem: this.inputProblem,
|
|
261
|
+
traces: this.traces,
|
|
262
|
+
outputNetLabelPlacements: this.outputNetLabelPlacements,
|
|
263
|
+
currentLabel: this.currentLabel,
|
|
264
|
+
currentCandidateResults: this.currentCandidateResults,
|
|
265
|
+
solved: this.solved,
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
3
|
+
import type { Bounds } from "./types"
|
|
4
|
+
|
|
5
|
+
export const EPS = 1e-6
|
|
6
|
+
|
|
7
|
+
export const isTraceLine = (trace: SolvedTracePath) =>
|
|
8
|
+
getUniquePinCount(trace.pinIds) >= 2
|
|
9
|
+
|
|
10
|
+
export const rectsOverlap = (a: Bounds, b: Bounds) =>
|
|
11
|
+
Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS &&
|
|
12
|
+
Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS
|
|
13
|
+
|
|
14
|
+
export const getTraceCorners = (path: Point[]) => {
|
|
15
|
+
const corners: Point[] = []
|
|
16
|
+
for (let i = 1; i < path.length - 1; i++) {
|
|
17
|
+
const previousPoint = path[i - 1]!
|
|
18
|
+
const cornerPoint = path[i]!
|
|
19
|
+
const nextPoint = path[i + 1]!
|
|
20
|
+
if (isTraceCorner(previousPoint, cornerPoint, nextPoint)) {
|
|
21
|
+
corners.push(cornerPoint)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return corners
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const tracePathContainsPoint = (path: Point[], point: Point) => {
|
|
28
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
29
|
+
if (isPointOnSegment(point, path[i]!, path[i + 1]!)) return true
|
|
30
|
+
}
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const getDistance = (a: Point, b: Point) =>
|
|
35
|
+
Math.abs(a.x - b.x) + Math.abs(a.y - b.y)
|
|
36
|
+
|
|
37
|
+
const getUniquePinCount = (pinIds: string[]) => new Set(pinIds).size
|
|
38
|
+
|
|
39
|
+
const isTraceCorner = (a: Point, b: Point, c: Point) =>
|
|
40
|
+
getSegmentOrientation(a, b) !== getSegmentOrientation(b, c)
|
|
41
|
+
|
|
42
|
+
const isPointOnSegment = (point: Point, start: Point, end: Point) => {
|
|
43
|
+
if (getSegmentOrientation(start, end) === "horizontal") {
|
|
44
|
+
return (
|
|
45
|
+
Math.abs(point.y - start.y) <= EPS &&
|
|
46
|
+
point.x >= Math.min(start.x, end.x) - EPS &&
|
|
47
|
+
point.x <= Math.max(start.x, end.x) + EPS
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
Math.abs(point.x - start.x) <= EPS &&
|
|
53
|
+
point.y >= Math.min(start.y, end.y) - EPS &&
|
|
54
|
+
point.y <= Math.max(start.y, end.y) + EPS
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const getSegmentOrientation = (a: Point, b: Point) =>
|
|
59
|
+
Math.abs(a.y - b.y) <= EPS ? "horizontal" : "vertical"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
5
|
+
|
|
6
|
+
export interface VccNetLabelCornerPlacementSolverParams {
|
|
7
|
+
inputProblem: InputProblem
|
|
8
|
+
traces: SolvedTracePath[]
|
|
9
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type Bounds = {
|
|
13
|
+
minX: number
|
|
14
|
+
minY: number
|
|
15
|
+
maxX: number
|
|
16
|
+
maxY: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type TraceCornerCandidate = {
|
|
20
|
+
anchorPoint: Point
|
|
21
|
+
traceId: string
|
|
22
|
+
distance: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type CornerCandidateStatus =
|
|
26
|
+
| "valid"
|
|
27
|
+
| "chip-collision"
|
|
28
|
+
| "trace-collision"
|
|
29
|
+
| "netlabel-collision"
|
|
30
|
+
|
|
31
|
+
export type EvaluatedCornerCandidate = TraceCornerCandidate & {
|
|
32
|
+
center: Point
|
|
33
|
+
width: number
|
|
34
|
+
height: number
|
|
35
|
+
status: CornerCandidateStatus
|
|
36
|
+
selected: boolean
|
|
37
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
5
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
6
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
7
|
+
import type { EvaluatedCornerCandidate } from "./types"
|
|
8
|
+
|
|
9
|
+
const CANDIDATE_SELECTED_COLOR = "blue"
|
|
10
|
+
const CANDIDATE_REJECTED_COLOR = "red"
|
|
11
|
+
|
|
12
|
+
export const visualizeVccNetLabelCornerPlacementSolver = (state: {
|
|
13
|
+
inputProblem: InputProblem
|
|
14
|
+
traces: SolvedTracePath[]
|
|
15
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
16
|
+
currentLabel: NetLabelPlacement | null
|
|
17
|
+
currentCandidateResults: EvaluatedCornerCandidate[]
|
|
18
|
+
solved: boolean
|
|
19
|
+
}): GraphicsObject => {
|
|
20
|
+
const graphics = visualizeInputProblem(state.inputProblem)
|
|
21
|
+
ensureGraphicsArrays(graphics)
|
|
22
|
+
|
|
23
|
+
drawTraces(graphics, state.traces)
|
|
24
|
+
drawNetLabels(graphics, state.outputNetLabelPlacements)
|
|
25
|
+
|
|
26
|
+
if (!state.solved) {
|
|
27
|
+
drawCurrentLabel(graphics, state.currentLabel)
|
|
28
|
+
drawCurrentCandidates(graphics, state.currentCandidateResults)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return graphics
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const drawTraces = (graphics: GraphicsObject, traces: SolvedTracePath[]) => {
|
|
35
|
+
for (const trace of traces) {
|
|
36
|
+
graphics.lines!.push({
|
|
37
|
+
points: trace.tracePath,
|
|
38
|
+
strokeColor: "purple",
|
|
39
|
+
} as any)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const drawNetLabels = (
|
|
44
|
+
graphics: GraphicsObject,
|
|
45
|
+
labels: NetLabelPlacement[],
|
|
46
|
+
) => {
|
|
47
|
+
for (const label of labels) {
|
|
48
|
+
graphics.rects!.push({
|
|
49
|
+
center: label.center,
|
|
50
|
+
width: label.width,
|
|
51
|
+
height: label.height,
|
|
52
|
+
fill: getColorFromString(label.globalConnNetId, 0.35),
|
|
53
|
+
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
54
|
+
label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
|
|
55
|
+
} as any)
|
|
56
|
+
graphics.points!.push({
|
|
57
|
+
x: label.anchorPoint.x,
|
|
58
|
+
y: label.anchorPoint.y,
|
|
59
|
+
color: getColorFromString(label.globalConnNetId, 0.9),
|
|
60
|
+
label: `anchorPoint\norientation: ${label.orientation}`,
|
|
61
|
+
} as any)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const drawCurrentLabel = (
|
|
66
|
+
graphics: GraphicsObject,
|
|
67
|
+
currentLabel: NetLabelPlacement | null,
|
|
68
|
+
) => {
|
|
69
|
+
if (!currentLabel) return
|
|
70
|
+
|
|
71
|
+
graphics.rects!.push({
|
|
72
|
+
center: currentLabel.center,
|
|
73
|
+
width: currentLabel.width,
|
|
74
|
+
height: currentLabel.height,
|
|
75
|
+
fill: "rgba(255, 0, 0, 0.2)",
|
|
76
|
+
strokeColor: CANDIDATE_REJECTED_COLOR,
|
|
77
|
+
label: `trace-line VCC target\n${currentLabel.netId ?? currentLabel.globalConnNetId}`,
|
|
78
|
+
} as any)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const drawCurrentCandidates = (
|
|
82
|
+
graphics: GraphicsObject,
|
|
83
|
+
candidates: EvaluatedCornerCandidate[],
|
|
84
|
+
) => {
|
|
85
|
+
for (const candidate of candidates) {
|
|
86
|
+
const color = candidate.selected
|
|
87
|
+
? CANDIDATE_SELECTED_COLOR
|
|
88
|
+
: CANDIDATE_REJECTED_COLOR
|
|
89
|
+
graphics.rects!.push({
|
|
90
|
+
center: candidate.center,
|
|
91
|
+
width: candidate.width,
|
|
92
|
+
height: candidate.height,
|
|
93
|
+
fill: candidate.selected
|
|
94
|
+
? "rgba(0, 0, 255, 0.2)"
|
|
95
|
+
: "rgba(255, 0, 0, 0.15)",
|
|
96
|
+
strokeColor: color,
|
|
97
|
+
strokeDash: candidate.selected ? undefined : "4 2",
|
|
98
|
+
label: `${candidate.selected ? "selected" : candidate.status} trace corner\ntrace: ${candidate.traceId}\ndistance: ${candidate.distance.toFixed(3)}`,
|
|
99
|
+
} as any)
|
|
100
|
+
graphics.points!.push({
|
|
101
|
+
...candidate.anchorPoint,
|
|
102
|
+
color,
|
|
103
|
+
label: `candidate anchor\n${candidate.status}`,
|
|
104
|
+
} as any)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const ensureGraphicsArrays = (graphics: GraphicsObject) => {
|
|
109
|
+
if (!graphics.lines) graphics.lines = []
|
|
110
|
+
if (!graphics.points) graphics.points = []
|
|
111
|
+
if (!graphics.rects) graphics.rects = []
|
|
112
|
+
if (!graphics.circles) graphics.circles = []
|
|
113
|
+
if (!graphics.texts) graphics.texts = []
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -30,7 +30,7 @@ x+" data-x="3.41" data-y="0.01999999999999985" cx="576.1069836552749" cy="222.85
|
|
|
30
30
|
</g>
|
|
31
31
|
<g>
|
|
32
32
|
<circle data-type="point" data-label="anchorPoint
|
|
33
|
-
orientation: y+" data-x="1.
|
|
33
|
+
orientation: y+" data-x="1.7049999999999998" data-y="0.2" cx="373.4323922734027" cy="201.4588202080238" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
34
34
|
</g>
|
|
35
35
|
<g>
|
|
36
36
|
<circle data-type="point" data-label="anchorPoint
|
|
@@ -82,7 +82,7 @@ orientation: y-" data-x="3.511" data-y="-0.4310000000000001" cx="588.11292719167
|
|
|
82
82
|
</g>
|
|
83
83
|
<g>
|
|
84
84
|
<rect data-type="rect" data-label="netId: VCC
|
|
85
|
-
globalConnNetId: connectivity_net0" data-x="1.
|
|
85
|
+
globalConnNetId: connectivity_net0" data-x="1.7049999999999998" data-y="0.42500000000000004" x="361.54531946508166" y="147.96699257057952" width="23.77414561664193" height="53.49182763744429" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008412500000000002" />
|
|
86
86
|
</g>
|
|
87
87
|
<g>
|
|
88
88
|
<rect data-type="rect" data-label="netId: OUT
|