@tscircuit/schematic-trace-solver 0.0.47 → 0.0.49
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 +1 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +750 -49
- package/lib/solvers/Example28Solver/Example28Solver.ts +182 -0
- package/lib/solvers/Example28Solver/geometry.ts +246 -0
- package/lib/solvers/Example28Solver/labelMovement.ts +82 -0
- package/lib/solvers/Example28Solver/reroute.ts +368 -0
- package/lib/solvers/Example28Solver/types.ts +47 -0
- package/lib/solvers/Example28Solver/visualize.ts +96 -0
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +2 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts +12 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +16 -0
- package/package.json +1 -1
- package/site/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.page.tsx +2 -0
- package/site/TraceCleanupSolver/TraceCleanupSolver.page.tsx +23 -0
- package/site/TraceLabelOverlapAvoidanceSolver/MergedNetLabelObstacles.page.tsx +18 -0
- package/site/TraceLabelOverlapAvoidanceSolver/OverlapAvoidanceStepSolver.page.tsx +24 -0
- package/site/TraceLabelOverlapAvoidanceSolver/SingleOverlapSolver.page.tsx +10 -0
- package/site/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.page.tsx +146 -0
- package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView01.page.tsx +32 -0
- package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView02.page.tsx +32 -0
- package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView03.page.tsx +35 -0
- package/site/examples/example01.page.tsx +6 -0
- package/site/examples/example02.page.tsx +4 -180
- package/site/examples/example03.page.tsx +3 -212
- package/site/examples/example04.page.tsx +6 -0
- package/site/examples/example05.page.tsx +3 -179
- package/site/examples/example06.page.tsx +3 -57
- package/site/examples/example07.page.tsx +3 -132
- package/site/examples/example08.page.tsx +3 -91
- package/site/examples/example09.page.tsx +3 -414
- package/site/examples/example10.page.tsx +3 -75
- package/site/examples/example11.page.tsx +3 -116
- package/site/examples/example12.page.tsx +3 -94
- package/site/examples/example13.page.tsx +3 -216
- package/site/examples/example14.page.tsx +3 -207
- package/site/examples/example15.page.tsx +6 -0
- package/site/examples/example16.page.tsx +6 -0
- package/site/examples/example17.page.tsx +6 -0
- package/site/examples/example18.page.tsx +3 -178
- package/site/examples/example19.page.tsx +3 -166
- package/site/examples/example20.page.tsx +3 -100
- package/site/examples/example21.page.tsx +3 -174
- package/site/examples/example22.page.tsx +3 -105
- package/site/examples/example23.page.tsx +3 -134
- package/site/examples/example24.page.tsx +3 -125
- package/site/examples/example25.page.tsx +2 -0
- package/site/examples/example26.page.tsx +2 -0
- package/site/examples/example27.page.tsx +2 -0
- package/site/examples/example28.page.tsx +6 -0
- package/site/examples/example29.page.tsx +6 -0
- package/site/examples/example30.page.tsx +6 -0
- package/site/examples/example31.page.tsx +4 -0
- package/tests/assets/example01.json +109 -0
- package/tests/assets/example02.json +178 -0
- package/tests/assets/example03.json +210 -0
- package/tests/assets/example04.json +41 -0
- package/tests/assets/example05.json +175 -0
- package/tests/assets/example06.json +55 -0
- package/tests/assets/example07.json +130 -0
- package/tests/assets/example08.json +89 -0
- package/tests/assets/example09.json +412 -0
- package/tests/assets/example10.json +73 -0
- package/tests/assets/example11.json +114 -0
- package/tests/assets/example12.json +92 -0
- package/tests/assets/example13.json +214 -0
- package/tests/assets/example14.json +205 -0
- package/tests/assets/example15.json +618 -0
- package/tests/assets/example16.json +102 -0
- package/tests/assets/example17.json +160 -0
- package/tests/assets/example18.json +176 -0
- package/tests/assets/example19.json +164 -0
- package/tests/assets/example20.json +98 -0
- package/tests/assets/example21.json +172 -0
- package/tests/assets/example22.json +103 -0
- package/tests/assets/example23.json +132 -0
- package/tests/assets/example24.json +123 -0
- package/tests/assets/example25.json +146 -33
- package/tests/assets/example26.json +51 -1134
- package/tests/assets/example27.json +151 -91
- package/tests/assets/example28.json +56 -0
- package/tests/assets/example29.json +1206 -0
- package/tests/assets/example30.json +168 -0
- package/tests/assets/example31.json +46 -0
- package/tests/examples/__snapshots__/example01.snap.svg +12 -6
- package/tests/examples/__snapshots__/example02.snap.svg +16 -8
- package/tests/examples/__snapshots__/example03.snap.svg +36 -18
- package/tests/examples/__snapshots__/example04.snap.svg +8 -4
- package/tests/examples/__snapshots__/example05.snap.svg +4 -2
- package/tests/examples/__snapshots__/example06.snap.svg +4 -2
- package/tests/examples/__snapshots__/example07.snap.svg +20 -10
- package/tests/examples/__snapshots__/example08.snap.svg +16 -8
- package/tests/examples/__snapshots__/example09.snap.svg +80 -40
- package/tests/examples/__snapshots__/example10.snap.svg +16 -8
- package/tests/examples/__snapshots__/example11.snap.svg +24 -12
- package/tests/examples/__snapshots__/example12.snap.svg +16 -8
- package/tests/examples/__snapshots__/example13.snap.svg +32 -16
- package/tests/examples/__snapshots__/example14.snap.svg +115 -48
- package/tests/examples/__snapshots__/example15.snap.svg +36 -18
- package/tests/examples/__snapshots__/example16.snap.svg +16 -8
- package/tests/examples/__snapshots__/example17.snap.svg +16 -8
- package/tests/examples/__snapshots__/example18.snap.svg +20 -10
- package/tests/examples/__snapshots__/example19.snap.svg +16 -8
- package/tests/examples/__snapshots__/example20.snap.svg +43 -135
- package/tests/examples/__snapshots__/example21.snap.svg +34 -18
- package/tests/examples/__snapshots__/example22.snap.svg +37 -23
- package/tests/examples/__snapshots__/example23.snap.svg +62 -26
- package/tests/examples/__snapshots__/example24.snap.svg +50 -28
- package/tests/examples/__snapshots__/example25.snap.svg +175 -49
- package/tests/examples/__snapshots__/example26.snap.svg +16 -8
- package/tests/examples/__snapshots__/example27.snap.svg +24 -12
- package/tests/examples/__snapshots__/example28.snap.svg +29 -189
- package/tests/examples/__snapshots__/example29.snap.svg +184 -92
- package/tests/examples/__snapshots__/example30.snap.svg +48 -24
- package/tests/examples/__snapshots__/example31.snap.svg +88 -0
- package/tests/examples/example01.test.ts +2 -2
- package/tests/examples/example02.test.ts +2 -2
- package/tests/examples/example03.test.ts +2 -2
- package/tests/examples/example04.test.ts +2 -2
- package/tests/examples/example05.test.ts +2 -2
- package/tests/examples/example06.test.ts +2 -2
- package/tests/examples/example07.test.ts +2 -2
- package/tests/examples/example08.test.ts +2 -2
- package/tests/examples/example09.test.ts +2 -2
- package/tests/examples/example10.test.ts +2 -2
- package/tests/examples/{example11.test.tsx → example11.test.ts} +2 -2
- package/tests/examples/{example12.test.tsx → example12.test.ts} +2 -2
- package/tests/examples/{example13.test.tsx → example13.test.ts} +2 -2
- package/tests/examples/example14.test.ts +12 -0
- package/tests/examples/example15.test.ts +12 -0
- package/tests/examples/example16.test.ts +12 -0
- package/tests/examples/example17.test.ts +12 -0
- package/tests/examples/example18.test.ts +12 -0
- package/tests/examples/example19.test.ts +12 -0
- package/tests/examples/example20.test.ts +12 -0
- package/tests/examples/example21.test.ts +12 -0
- package/tests/examples/example22.test.ts +12 -0
- package/tests/examples/example23.test.ts +12 -0
- package/tests/examples/example24.test.ts +12 -0
- package/tests/examples/example25.test.ts +12 -0
- package/tests/examples/example26.test.ts +12 -0
- package/tests/examples/example27.test.ts +3 -232
- package/tests/examples/example28.test.ts +1 -2
- package/tests/examples/example29.test.ts +1 -2
- package/tests/examples/example30.test.ts +2 -2
- package/tests/examples/example31.test.ts +12 -0
- package/site/examples/example01-basic.page.tsx +0 -105
- package/site/examples/example04-single-symbol.page.tsx +0 -46
- package/site/examples/example15-rp2040-caps.page.tsx +0 -623
- package/site/examples/example16-core-repro51.page.tsx +0 -107
- package/site/examples/example17-straight-line-trace.page.tsx +0 -165
- package/tests/examples/example14.test.tsx +0 -11
- package/tests/examples/example15.test.tsx +0 -629
- package/tests/examples/example16.test.tsx +0 -113
- package/tests/examples/example17.test.tsx +0 -171
- package/tests/examples/example18.test.tsx +0 -187
- package/tests/examples/example19.test.tsx +0 -175
- package/tests/examples/example20.test.tsx +0 -190
- package/tests/examples/example21.test.tsx +0 -183
- package/tests/examples/example22.test.tsx +0 -109
- package/tests/examples/example23.test.tsx +0 -109
- package/tests/examples/example24.test.tsx +0 -114
- package/tests/examples/example25.test.tsx +0 -143
- package/tests/examples/example26.test.tsx +0 -134
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
4
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
6
|
+
import { detectTraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
|
|
7
|
+
import { generateRerouteCandidates } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace"
|
|
8
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
9
|
+
import { dir } from "lib/utils/dir"
|
|
10
|
+
import {
|
|
11
|
+
countPathIntersections,
|
|
12
|
+
getPathKey,
|
|
13
|
+
getPathLength,
|
|
14
|
+
isPathCollidingWithChipInterior,
|
|
15
|
+
} from "./geometry"
|
|
16
|
+
import type {
|
|
17
|
+
ChipObstacle,
|
|
18
|
+
RerouteCandidateResult,
|
|
19
|
+
TracePathScore,
|
|
20
|
+
} from "./types"
|
|
21
|
+
|
|
22
|
+
const LABEL_SIDE_CLEARANCE = 0.1
|
|
23
|
+
const LABEL_HUG_CLEARANCE = 0.001
|
|
24
|
+
|
|
25
|
+
export const findBestReroutePath = ({
|
|
26
|
+
trace,
|
|
27
|
+
obstacleLabel,
|
|
28
|
+
inputProblem,
|
|
29
|
+
outputTraces,
|
|
30
|
+
outputNetLabelPlacements,
|
|
31
|
+
chipObstacles,
|
|
32
|
+
}: {
|
|
33
|
+
trace: SolvedTracePath
|
|
34
|
+
obstacleLabel: NetLabelPlacement
|
|
35
|
+
inputProblem: InputProblem
|
|
36
|
+
outputTraces: SolvedTracePath[]
|
|
37
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
38
|
+
chipObstacles: ChipObstacle[]
|
|
39
|
+
}) => {
|
|
40
|
+
const candidateResults = generateRerouteCandidateResults({
|
|
41
|
+
trace,
|
|
42
|
+
label: obstacleLabel,
|
|
43
|
+
inputProblem,
|
|
44
|
+
outputTraces,
|
|
45
|
+
outputNetLabelPlacements,
|
|
46
|
+
chipObstacles,
|
|
47
|
+
})
|
|
48
|
+
const bestPath = selectBestReroutePath({
|
|
49
|
+
trace,
|
|
50
|
+
obstacleLabel,
|
|
51
|
+
outputTraces,
|
|
52
|
+
outputNetLabelPlacements,
|
|
53
|
+
candidateResults,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
markSelectedCandidate(candidateResults, bestPath)
|
|
57
|
+
|
|
58
|
+
return { bestPath, candidateResults }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const generateRerouteCandidateResults = ({
|
|
62
|
+
trace,
|
|
63
|
+
label,
|
|
64
|
+
inputProblem,
|
|
65
|
+
outputTraces,
|
|
66
|
+
outputNetLabelPlacements,
|
|
67
|
+
chipObstacles,
|
|
68
|
+
}: {
|
|
69
|
+
trace: SolvedTracePath
|
|
70
|
+
label: NetLabelPlacement
|
|
71
|
+
inputProblem: InputProblem
|
|
72
|
+
outputTraces: SolvedTracePath[]
|
|
73
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
74
|
+
chipObstacles: ChipObstacle[]
|
|
75
|
+
}) => {
|
|
76
|
+
const rawCandidates = generateCandidatePaths(trace, label, inputProblem)
|
|
77
|
+
const seen = new Set<string>()
|
|
78
|
+
const candidateResults: RerouteCandidateResult[] = []
|
|
79
|
+
|
|
80
|
+
for (const rawCandidate of rawCandidates) {
|
|
81
|
+
candidateResults.push(
|
|
82
|
+
createCandidateResult({
|
|
83
|
+
trace,
|
|
84
|
+
obstacleLabel: label,
|
|
85
|
+
path: simplifyPath(rawCandidate),
|
|
86
|
+
seen,
|
|
87
|
+
outputTraces,
|
|
88
|
+
outputNetLabelPlacements,
|
|
89
|
+
chipObstacles,
|
|
90
|
+
}),
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return candidateResults
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const generateCandidatePaths = (
|
|
98
|
+
trace: SolvedTracePath,
|
|
99
|
+
label: NetLabelPlacement,
|
|
100
|
+
inputProblem: InputProblem,
|
|
101
|
+
) => [
|
|
102
|
+
...generateLabelHugCandidates(trace, label),
|
|
103
|
+
...generateRerouteCandidates({
|
|
104
|
+
trace,
|
|
105
|
+
label,
|
|
106
|
+
problem: inputProblem,
|
|
107
|
+
paddingBuffer: LABEL_SIDE_CLEARANCE,
|
|
108
|
+
detourCount: 0,
|
|
109
|
+
}),
|
|
110
|
+
...generateEndpointDetourCandidates(trace, label),
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
const createCandidateResult = ({
|
|
114
|
+
trace,
|
|
115
|
+
obstacleLabel,
|
|
116
|
+
path,
|
|
117
|
+
seen,
|
|
118
|
+
outputTraces,
|
|
119
|
+
outputNetLabelPlacements,
|
|
120
|
+
chipObstacles,
|
|
121
|
+
}: {
|
|
122
|
+
trace: SolvedTracePath
|
|
123
|
+
obstacleLabel: NetLabelPlacement
|
|
124
|
+
path: Point[]
|
|
125
|
+
seen: Set<string>
|
|
126
|
+
outputTraces: SolvedTracePath[]
|
|
127
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
128
|
+
chipObstacles: ChipObstacle[]
|
|
129
|
+
}): RerouteCandidateResult => {
|
|
130
|
+
const key = getPathKey(path)
|
|
131
|
+
|
|
132
|
+
if (seen.has(key)) {
|
|
133
|
+
return {
|
|
134
|
+
path,
|
|
135
|
+
status: "duplicate",
|
|
136
|
+
selected: false,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
seen.add(key)
|
|
141
|
+
|
|
142
|
+
if (isPathCollidingWithChipInterior(path, chipObstacles)) {
|
|
143
|
+
return {
|
|
144
|
+
path,
|
|
145
|
+
status: "chip-collision",
|
|
146
|
+
selected: false,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
path,
|
|
152
|
+
score: scoreTracePath({
|
|
153
|
+
trace,
|
|
154
|
+
tracePath: path,
|
|
155
|
+
obstacleLabel,
|
|
156
|
+
outputTraces,
|
|
157
|
+
outputNetLabelPlacements,
|
|
158
|
+
}),
|
|
159
|
+
status: "valid",
|
|
160
|
+
selected: false,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const generateEndpointDetourCandidates = (
|
|
165
|
+
trace: SolvedTracePath,
|
|
166
|
+
label: NetLabelPlacement,
|
|
167
|
+
): Point[][] => {
|
|
168
|
+
const start = trace.tracePath[0]
|
|
169
|
+
const end = trace.tracePath[trace.tracePath.length - 1]
|
|
170
|
+
if (!start || !end) return []
|
|
171
|
+
|
|
172
|
+
const bounds = getRectBounds(label.center, label.width, label.height)
|
|
173
|
+
const padding = LABEL_SIDE_CLEARANCE
|
|
174
|
+
const labelDirection = dir(label.orientation)
|
|
175
|
+
const labelSideX =
|
|
176
|
+
labelDirection.x < 0 ? bounds.minX - padding : bounds.maxX + padding
|
|
177
|
+
const oppositeSideX =
|
|
178
|
+
labelDirection.x < 0 ? bounds.maxX + padding : bounds.minX - padding
|
|
179
|
+
const topY = bounds.maxY + padding
|
|
180
|
+
const bottomY = bounds.minY - padding
|
|
181
|
+
const candidates: Point[][] = []
|
|
182
|
+
|
|
183
|
+
for (const sideX of [labelSideX, oppositeSideX]) {
|
|
184
|
+
candidates.push(
|
|
185
|
+
[
|
|
186
|
+
start,
|
|
187
|
+
{ x: start.x, y: topY },
|
|
188
|
+
{ x: sideX, y: topY },
|
|
189
|
+
{ x: sideX, y: bottomY },
|
|
190
|
+
{ x: end.x, y: bottomY },
|
|
191
|
+
end,
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
start,
|
|
195
|
+
{ x: start.x, y: bottomY },
|
|
196
|
+
{ x: sideX, y: bottomY },
|
|
197
|
+
{ x: sideX, y: topY },
|
|
198
|
+
{ x: end.x, y: topY },
|
|
199
|
+
end,
|
|
200
|
+
],
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return candidates
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const generateLabelHugCandidates = (
|
|
208
|
+
trace: SolvedTracePath,
|
|
209
|
+
label: NetLabelPlacement,
|
|
210
|
+
): Point[][] => {
|
|
211
|
+
const start = trace.tracePath[0]
|
|
212
|
+
const end = trace.tracePath[trace.tracePath.length - 1]
|
|
213
|
+
if (!start || !end) return []
|
|
214
|
+
|
|
215
|
+
const labelDirection = dir(label.orientation)
|
|
216
|
+
if (labelDirection.x === 0) return []
|
|
217
|
+
|
|
218
|
+
const bounds = getRectBounds(label.center, label.width, label.height)
|
|
219
|
+
const sideX =
|
|
220
|
+
labelDirection.x < 0
|
|
221
|
+
? bounds.minX - LABEL_SIDE_CLEARANCE
|
|
222
|
+
: bounds.maxX + LABEL_SIDE_CLEARANCE
|
|
223
|
+
const topY = bounds.maxY + LABEL_HUG_CLEARANCE
|
|
224
|
+
const bottomY = bounds.minY - LABEL_HUG_CLEARANCE
|
|
225
|
+
const startsAboveEnd = start.y >= end.y
|
|
226
|
+
const firstY = startsAboveEnd ? topY : bottomY
|
|
227
|
+
const secondY = startsAboveEnd ? bottomY : topY
|
|
228
|
+
|
|
229
|
+
return [
|
|
230
|
+
[
|
|
231
|
+
start,
|
|
232
|
+
{ x: start.x, y: firstY },
|
|
233
|
+
{ x: sideX, y: firstY },
|
|
234
|
+
{ x: sideX, y: secondY },
|
|
235
|
+
{ x: end.x, y: secondY },
|
|
236
|
+
end,
|
|
237
|
+
],
|
|
238
|
+
[
|
|
239
|
+
start,
|
|
240
|
+
{ x: start.x, y: secondY },
|
|
241
|
+
{ x: sideX, y: secondY },
|
|
242
|
+
{ x: sideX, y: firstY },
|
|
243
|
+
{ x: end.x, y: firstY },
|
|
244
|
+
end,
|
|
245
|
+
],
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const selectBestReroutePath = ({
|
|
250
|
+
trace,
|
|
251
|
+
obstacleLabel,
|
|
252
|
+
outputTraces,
|
|
253
|
+
outputNetLabelPlacements,
|
|
254
|
+
candidateResults,
|
|
255
|
+
}: {
|
|
256
|
+
trace: SolvedTracePath
|
|
257
|
+
obstacleLabel: NetLabelPlacement
|
|
258
|
+
outputTraces: SolvedTracePath[]
|
|
259
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
260
|
+
candidateResults: RerouteCandidateResult[]
|
|
261
|
+
}) => {
|
|
262
|
+
let bestPath: Point[] | null = null
|
|
263
|
+
let bestScore = scoreTracePath({
|
|
264
|
+
trace,
|
|
265
|
+
tracePath: trace.tracePath,
|
|
266
|
+
obstacleLabel,
|
|
267
|
+
outputTraces,
|
|
268
|
+
outputNetLabelPlacements,
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
for (const candidate of candidateResults) {
|
|
272
|
+
if (candidate.status !== "valid" || !candidate.score) continue
|
|
273
|
+
if (!isBetterScore(candidate.score, bestScore)) continue
|
|
274
|
+
|
|
275
|
+
bestScore = candidate.score
|
|
276
|
+
bestPath = candidate.path
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return bestPath
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const markSelectedCandidate = (
|
|
283
|
+
candidateResults: RerouteCandidateResult[],
|
|
284
|
+
bestPath: Point[] | null,
|
|
285
|
+
) => {
|
|
286
|
+
if (!bestPath) return
|
|
287
|
+
|
|
288
|
+
for (const candidate of candidateResults) {
|
|
289
|
+
candidate.selected = candidate.path === bestPath
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const scoreTracePath = ({
|
|
294
|
+
trace,
|
|
295
|
+
tracePath,
|
|
296
|
+
obstacleLabel,
|
|
297
|
+
outputTraces,
|
|
298
|
+
outputNetLabelPlacements,
|
|
299
|
+
}: {
|
|
300
|
+
trace: SolvedTracePath
|
|
301
|
+
tracePath: Point[]
|
|
302
|
+
obstacleLabel: NetLabelPlacement
|
|
303
|
+
outputTraces: SolvedTracePath[]
|
|
304
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
305
|
+
}): TracePathScore => {
|
|
306
|
+
const candidateTrace = { ...trace, tracePath }
|
|
307
|
+
return {
|
|
308
|
+
labelIntersections: detectTraceLabelOverlap({
|
|
309
|
+
traces: [candidateTrace],
|
|
310
|
+
netLabels: outputNetLabelPlacements,
|
|
311
|
+
}).length,
|
|
312
|
+
labelHugDistance: getLabelHugDistance(tracePath, obstacleLabel),
|
|
313
|
+
traceIntersections: countTraceIntersections(candidateTrace, outputTraces),
|
|
314
|
+
pathLength: getPathLength(tracePath),
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const countTraceIntersections = (
|
|
319
|
+
trace: SolvedTracePath,
|
|
320
|
+
outputTraces: SolvedTracePath[],
|
|
321
|
+
) => {
|
|
322
|
+
let count = 0
|
|
323
|
+
for (const otherTrace of outputTraces) {
|
|
324
|
+
if (otherTrace.mspPairId === trace.mspPairId) continue
|
|
325
|
+
count += countPathIntersections(trace.tracePath, otherTrace.tracePath)
|
|
326
|
+
}
|
|
327
|
+
return count
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const isBetterScore = (score: TracePathScore, bestScore: TracePathScore) => {
|
|
331
|
+
if (score.labelIntersections !== bestScore.labelIntersections) {
|
|
332
|
+
return score.labelIntersections < bestScore.labelIntersections
|
|
333
|
+
}
|
|
334
|
+
if (score.labelHugDistance !== bestScore.labelHugDistance) {
|
|
335
|
+
return score.labelHugDistance < bestScore.labelHugDistance
|
|
336
|
+
}
|
|
337
|
+
if (score.traceIntersections !== bestScore.traceIntersections) {
|
|
338
|
+
return score.traceIntersections < bestScore.traceIntersections
|
|
339
|
+
}
|
|
340
|
+
return score.pathLength < bestScore.pathLength
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const getLabelHugDistance = (
|
|
344
|
+
tracePath: Point[],
|
|
345
|
+
obstacleLabel: NetLabelPlacement,
|
|
346
|
+
) => {
|
|
347
|
+
const bounds = getRectBounds(
|
|
348
|
+
obstacleLabel.center,
|
|
349
|
+
obstacleLabel.width,
|
|
350
|
+
obstacleLabel.height,
|
|
351
|
+
)
|
|
352
|
+
let distance = 0
|
|
353
|
+
|
|
354
|
+
for (const point of tracePath) {
|
|
355
|
+
distance += getPointDistanceFromRect(point, bounds)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return distance
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const getPointDistanceFromRect = (
|
|
362
|
+
point: Point,
|
|
363
|
+
rect: { minX: number; minY: number; maxX: number; maxY: number },
|
|
364
|
+
) => {
|
|
365
|
+
const dx = Math.max(rect.minX - point.x, 0, point.x - rect.maxX)
|
|
366
|
+
const dy = Math.max(rect.minY - point.y, 0, point.y - rect.maxY)
|
|
367
|
+
return dx + dy
|
|
368
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import type { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
4
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import type { TraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
|
|
6
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
|
+
|
|
8
|
+
export interface Example28SolverParams {
|
|
9
|
+
inputProblem: InputProblem
|
|
10
|
+
traces: SolvedTracePath[]
|
|
11
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type TracePathScore = {
|
|
15
|
+
labelIntersections: number
|
|
16
|
+
labelHugDistance: number
|
|
17
|
+
traceIntersections: number
|
|
18
|
+
pathLength: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SegmentOrientation = "horizontal" | "vertical"
|
|
22
|
+
|
|
23
|
+
export type PathSegment = {
|
|
24
|
+
start: Point
|
|
25
|
+
end: Point
|
|
26
|
+
orientation: SegmentOrientation
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ChipObstacle = ReturnType<typeof getObstacleRects>[number]
|
|
30
|
+
|
|
31
|
+
export type RerouteCandidateResult = {
|
|
32
|
+
path: Point[]
|
|
33
|
+
score?: TracePathScore
|
|
34
|
+
status: "valid" | "duplicate" | "chip-collision"
|
|
35
|
+
selected: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type Example28VisualizationState = {
|
|
39
|
+
inputProblem: InputProblem
|
|
40
|
+
outputTraces: SolvedTracePath[]
|
|
41
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
42
|
+
solved: boolean
|
|
43
|
+
currentOverlap: TraceLabelOverlap | null
|
|
44
|
+
currentCandidateResults: RerouteCandidateResult[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const EPS = 1e-9
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
3
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
4
|
+
import type {
|
|
5
|
+
Example28VisualizationState,
|
|
6
|
+
RerouteCandidateResult,
|
|
7
|
+
} from "./types"
|
|
8
|
+
|
|
9
|
+
const CANDIDATE_SELECTED_COLOR = "blue"
|
|
10
|
+
const CANDIDATE_REJECTED_COLOR = "red"
|
|
11
|
+
|
|
12
|
+
export const visualizeExample28Solver = (
|
|
13
|
+
state: Example28VisualizationState,
|
|
14
|
+
): GraphicsObject => {
|
|
15
|
+
const graphics = visualizeInputProblem(state.inputProblem)
|
|
16
|
+
|
|
17
|
+
drawTraces(graphics, state)
|
|
18
|
+
drawNetLabels(graphics, state)
|
|
19
|
+
|
|
20
|
+
if (!state.solved) {
|
|
21
|
+
drawCurrentCandidates(graphics, state.currentCandidateResults)
|
|
22
|
+
drawCurrentOverlap(graphics, state)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return graphics
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const drawTraces = (
|
|
29
|
+
graphics: GraphicsObject,
|
|
30
|
+
state: Example28VisualizationState,
|
|
31
|
+
) => {
|
|
32
|
+
for (const trace of state.outputTraces) {
|
|
33
|
+
graphics.lines!.push({
|
|
34
|
+
points: trace.tracePath,
|
|
35
|
+
strokeColor: "purple",
|
|
36
|
+
} as any)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const drawNetLabels = (
|
|
41
|
+
graphics: GraphicsObject,
|
|
42
|
+
state: Example28VisualizationState,
|
|
43
|
+
) => {
|
|
44
|
+
for (const label of state.outputNetLabelPlacements) {
|
|
45
|
+
const color = getColorFromString(label.globalConnNetId, 0.35)
|
|
46
|
+
graphics.rects!.push({
|
|
47
|
+
center: label.center,
|
|
48
|
+
width: label.width,
|
|
49
|
+
height: label.height,
|
|
50
|
+
fill: color,
|
|
51
|
+
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
52
|
+
label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
|
|
53
|
+
} as any)
|
|
54
|
+
graphics.points!.push({
|
|
55
|
+
...label.anchorPoint,
|
|
56
|
+
color: getColorFromString(label.globalConnNetId, 0.9),
|
|
57
|
+
label: `anchorPoint\norientation: ${label.orientation}`,
|
|
58
|
+
} as any)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const drawCurrentCandidates = (
|
|
63
|
+
graphics: GraphicsObject,
|
|
64
|
+
candidateResults: RerouteCandidateResult[],
|
|
65
|
+
) => {
|
|
66
|
+
for (const candidate of candidateResults) {
|
|
67
|
+
const scoreLabel = candidate.score
|
|
68
|
+
? `\nlabel intersections: ${candidate.score.labelIntersections}\ntrace intersections: ${candidate.score.traceIntersections}`
|
|
69
|
+
: ""
|
|
70
|
+
graphics.lines!.push({
|
|
71
|
+
points: candidate.path,
|
|
72
|
+
strokeColor: candidate.selected
|
|
73
|
+
? CANDIDATE_SELECTED_COLOR
|
|
74
|
+
: CANDIDATE_REJECTED_COLOR,
|
|
75
|
+
strokeDash: candidate.selected ? undefined : "4 2",
|
|
76
|
+
label: `${candidate.selected ? "selected" : candidate.status} reroute${scoreLabel}`,
|
|
77
|
+
} as any)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const drawCurrentOverlap = (
|
|
82
|
+
graphics: GraphicsObject,
|
|
83
|
+
state: Example28VisualizationState,
|
|
84
|
+
) => {
|
|
85
|
+
if (!state.currentOverlap) return
|
|
86
|
+
|
|
87
|
+
const label = state.currentOverlap.label
|
|
88
|
+
graphics.rects!.push({
|
|
89
|
+
center: label.center,
|
|
90
|
+
width: label.width,
|
|
91
|
+
height: label.height,
|
|
92
|
+
fill: "rgba(255, 0, 0, 0.2)",
|
|
93
|
+
strokeColor: CANDIDATE_REJECTED_COLOR,
|
|
94
|
+
label: `overlap target\n${label.netId ?? label.globalConnNetId}`,
|
|
95
|
+
} as any)
|
|
96
|
+
}
|
|
@@ -350,11 +350,13 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
350
350
|
height: p.height,
|
|
351
351
|
fill: getColorFromString(p.globalConnNetId, 0.35),
|
|
352
352
|
strokeColor: getColorFromString(p.globalConnNetId, 0.9),
|
|
353
|
+
label: `netId: ${p.netId}\nglobalConnNetId: ${p.globalConnNetId}`,
|
|
353
354
|
} as any)
|
|
354
355
|
graphics.points!.push({
|
|
355
356
|
x: p.anchorPoint.x,
|
|
356
357
|
y: p.anchorPoint.y,
|
|
357
358
|
color: getColorFromString(p.globalConnNetId, 0.9),
|
|
359
|
+
label: `anchorPoint\norientation: ${p.orientation}`,
|
|
358
360
|
} as any)
|
|
359
361
|
}
|
|
360
362
|
|
|
@@ -47,6 +47,14 @@ export function visualizeSingleNetLabelPlacementSolver(
|
|
|
47
47
|
: c.status === "trace-collision"
|
|
48
48
|
? "orange"
|
|
49
49
|
: "gray"
|
|
50
|
+
const candidateLabel =
|
|
51
|
+
c.status === "ok"
|
|
52
|
+
? "status: ok(valid net label candidate)"
|
|
53
|
+
: c.status === "chip-collision"
|
|
54
|
+
? "status: chip-collision"
|
|
55
|
+
: c.status === "trace-collision"
|
|
56
|
+
? "status: trace-collision"
|
|
57
|
+
: "status: parallel-to-segment"
|
|
50
58
|
|
|
51
59
|
graphics.rects!.push({
|
|
52
60
|
center: {
|
|
@@ -57,12 +65,14 @@ export function visualizeSingleNetLabelPlacementSolver(
|
|
|
57
65
|
height: c.height,
|
|
58
66
|
fill,
|
|
59
67
|
strokeColor: stroke,
|
|
68
|
+
label: `${candidateLabel}\norientation: ${c.orientation}`,
|
|
60
69
|
} as any)
|
|
61
70
|
|
|
62
71
|
graphics.points!.push({
|
|
63
72
|
x: c.anchor.x,
|
|
64
73
|
y: c.anchor.y,
|
|
65
74
|
color: stroke,
|
|
75
|
+
label: `anchor\norientation: ${c.orientation}`,
|
|
66
76
|
} as any)
|
|
67
77
|
}
|
|
68
78
|
|
|
@@ -75,11 +85,13 @@ export function visualizeSingleNetLabelPlacementSolver(
|
|
|
75
85
|
height: p.height,
|
|
76
86
|
fill: "rgba(0, 128, 255, 0.35)",
|
|
77
87
|
strokeColor: "blue",
|
|
88
|
+
label: `netId: ${p.netId}\nglobalConnNetId: ${p.globalConnNetId}`,
|
|
78
89
|
} as any)
|
|
79
90
|
graphics.points!.push({
|
|
80
91
|
x: p.anchorPoint.x,
|
|
81
92
|
y: p.anchorPoint.y,
|
|
82
93
|
color: "blue",
|
|
94
|
+
label: `anchor\norientation: ${p.orientation}`,
|
|
83
95
|
} as any)
|
|
84
96
|
}
|
|
85
97
|
|
|
@@ -20,6 +20,7 @@ import { expandChipsToFitPins } from "./expandChipsToFitPins"
|
|
|
20
20
|
import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePairSolver"
|
|
21
21
|
import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver"
|
|
22
22
|
import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver"
|
|
23
|
+
import { Example28Solver } from "../Example28Solver/Example28Solver"
|
|
23
24
|
|
|
24
25
|
type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
|
|
25
26
|
solverName: string
|
|
@@ -69,6 +70,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
69
70
|
labelMergingSolver?: MergedNetLabelObstacleSolver
|
|
70
71
|
traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver
|
|
71
72
|
traceCleanupSolver?: TraceCleanupSolver
|
|
73
|
+
example28Solver?: Example28Solver
|
|
72
74
|
|
|
73
75
|
startTimeOfPhase: Record<string, number>
|
|
74
76
|
endTimeOfPhase: Record<string, number>
|
|
@@ -224,6 +226,20 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
224
226
|
]
|
|
225
227
|
},
|
|
226
228
|
),
|
|
229
|
+
definePipelineStep("example28Solver", Example28Solver, (instance) => {
|
|
230
|
+
const traces =
|
|
231
|
+
instance.traceCleanupSolver?.getOutput().traces ??
|
|
232
|
+
instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces
|
|
233
|
+
|
|
234
|
+
return [
|
|
235
|
+
{
|
|
236
|
+
inputProblem: instance.inputProblem,
|
|
237
|
+
traces,
|
|
238
|
+
netLabelPlacements:
|
|
239
|
+
instance.netLabelPlacementSolver!.netLabelPlacements,
|
|
240
|
+
},
|
|
241
|
+
]
|
|
242
|
+
}),
|
|
227
243
|
]
|
|
228
244
|
|
|
229
245
|
constructor(inputProblem: InputProblem) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
|
|
3
|
+
import { TraceCleanupSolver } from "lib/solvers/TraceCleanupSolver/TraceCleanupSolver"
|
|
4
|
+
import inputData from "../../tests/assets/TraceCleanupSolver.test.input.json"
|
|
5
|
+
|
|
6
|
+
export default () => {
|
|
7
|
+
const solver = useMemo(
|
|
8
|
+
() =>
|
|
9
|
+
new TraceCleanupSolver({
|
|
10
|
+
...inputData,
|
|
11
|
+
targetTraceIds: new Set(inputData.targetTraceIds),
|
|
12
|
+
mergedLabelNetIdMap: Object.fromEntries(
|
|
13
|
+
Object.entries(inputData.mergedLabelNetIdMap).map(([k, v]) => [
|
|
14
|
+
k,
|
|
15
|
+
new Set(v as any),
|
|
16
|
+
]),
|
|
17
|
+
),
|
|
18
|
+
} as any),
|
|
19
|
+
[],
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return <GenericSolverDebugger solver={solver} />
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
|
|
3
|
+
import { MergedNetLabelObstacleSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver"
|
|
4
|
+
import inputData from "../../tests/assets/MergedNetLabelObstacles.test.input.json"
|
|
5
|
+
|
|
6
|
+
export default () => {
|
|
7
|
+
const solver = useMemo(
|
|
8
|
+
() =>
|
|
9
|
+
new MergedNetLabelObstacleSolver({
|
|
10
|
+
netLabelPlacements: inputData.netLabelPlacements as any,
|
|
11
|
+
inputProblem: inputData.inputProblem as any,
|
|
12
|
+
traces: inputData.traces as any,
|
|
13
|
+
}),
|
|
14
|
+
[],
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return <GenericSolverDebugger solver={solver} />
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
|
|
3
|
+
import { OverlapAvoidanceStepSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver"
|
|
4
|
+
import inputData from "../../tests/assets/OverlapAvoidanceStepSolver.test.input.json"
|
|
5
|
+
|
|
6
|
+
export default () => {
|
|
7
|
+
const solver = useMemo(
|
|
8
|
+
() =>
|
|
9
|
+
new OverlapAvoidanceStepSolver({
|
|
10
|
+
inputProblem: inputData.problem as any,
|
|
11
|
+
traces: inputData.traces as any,
|
|
12
|
+
netLabelPlacements: inputData.netLabelPlacements as any,
|
|
13
|
+
mergedLabelNetIdMap: Object.fromEntries(
|
|
14
|
+
Object.entries(inputData.mergedLabelNetIdMap).map(([k, v]) => [
|
|
15
|
+
k,
|
|
16
|
+
new Set(v as any),
|
|
17
|
+
]),
|
|
18
|
+
),
|
|
19
|
+
} as any),
|
|
20
|
+
[],
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return <GenericSolverDebugger solver={solver} />
|
|
24
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
|
|
3
|
+
import { SingleOverlapSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver"
|
|
4
|
+
import inputData from "../../tests/assets/SingleOverlapSolver.test.input.json"
|
|
5
|
+
|
|
6
|
+
export default () => {
|
|
7
|
+
const solver = useMemo(() => new SingleOverlapSolver(inputData as any), [])
|
|
8
|
+
|
|
9
|
+
return <GenericSolverDebugger solver={solver} />
|
|
10
|
+
}
|