@tscircuit/schematic-trace-solver 0.0.48 → 0.0.50
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 +128 -1
- package/dist/index.js +1450 -42
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +682 -0
- package/lib/solvers/AvailableNetOrientationSolver/constants.ts +6 -0
- package/lib/solvers/AvailableNetOrientationSolver/geometry.ts +192 -0
- package/lib/solvers/AvailableNetOrientationSolver/traces.ts +43 -0
- package/lib/solvers/AvailableNetOrientationSolver/types.ts +44 -0
- package/lib/solvers/AvailableNetOrientationSolver/visualize.ts +123 -0
- 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/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +30 -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 +3 -58
- 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 +41 -30
- package/tests/examples/__snapshots__/example13.snap.svg +38 -16
- package/tests/examples/__snapshots__/example14.snap.svg +121 -48
- package/tests/examples/__snapshots__/example15.snap.svg +36 -18
- package/tests/examples/__snapshots__/example16.snap.svg +41 -27
- 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 +45 -131
- package/tests/examples/__snapshots__/example21.snap.svg +93 -65
- package/tests/examples/__snapshots__/example22.snap.svg +43 -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 +24 -18
- 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 +2 -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,192 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
3
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
4
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
5
|
+
import { EPS, TRACE_BOUNDARY_TOLERANCE } from "./constants"
|
|
6
|
+
import type { Bounds, ChipSide } from "./types"
|
|
7
|
+
|
|
8
|
+
export const isYOrientation = (
|
|
9
|
+
orientation: FacingDirection,
|
|
10
|
+
): orientation is "y+" | "y-" => orientation === "y+" || orientation === "y-"
|
|
11
|
+
|
|
12
|
+
export const isXOrientation = (
|
|
13
|
+
orientation: FacingDirection,
|
|
14
|
+
): orientation is "x+" | "x-" => orientation === "x+" || orientation === "x-"
|
|
15
|
+
|
|
16
|
+
export const rectsOverlap = (a: Bounds, b: Bounds) =>
|
|
17
|
+
Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS &&
|
|
18
|
+
Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS
|
|
19
|
+
|
|
20
|
+
export const rangesOverlap = (
|
|
21
|
+
minA: number,
|
|
22
|
+
maxA: number,
|
|
23
|
+
minB: number,
|
|
24
|
+
maxB: number,
|
|
25
|
+
) => Math.min(maxA, maxB) - Math.max(minA, minB) > EPS
|
|
26
|
+
|
|
27
|
+
export const traceCrossesBoundsInterior = (
|
|
28
|
+
bounds: Bounds,
|
|
29
|
+
traceMap: Record<string, SolvedTracePath>,
|
|
30
|
+
) => {
|
|
31
|
+
for (const trace of Object.values(traceMap)) {
|
|
32
|
+
const points = trace.tracePath
|
|
33
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
34
|
+
if (segmentCrossesBoundsInterior(points[i]!, points[i + 1]!, bounds)) {
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const segmentCrossesBoundsInterior = (p1: Point, p2: Point, bounds: Bounds) => {
|
|
44
|
+
const interiorBounds = {
|
|
45
|
+
minX: bounds.minX + TRACE_BOUNDARY_TOLERANCE,
|
|
46
|
+
minY: bounds.minY + TRACE_BOUNDARY_TOLERANCE,
|
|
47
|
+
maxX: bounds.maxX - TRACE_BOUNDARY_TOLERANCE,
|
|
48
|
+
maxY: bounds.maxY - TRACE_BOUNDARY_TOLERANCE,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
interiorBounds.minX >= interiorBounds.maxX ||
|
|
53
|
+
interiorBounds.minY >= interiorBounds.maxY
|
|
54
|
+
) {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (sameX(p1, p2)) {
|
|
59
|
+
if (
|
|
60
|
+
p1.x <= interiorBounds.minX + EPS ||
|
|
61
|
+
p1.x >= interiorBounds.maxX - EPS
|
|
62
|
+
) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
return rangesOverlap(
|
|
66
|
+
Math.min(p1.y, p2.y),
|
|
67
|
+
Math.max(p1.y, p2.y),
|
|
68
|
+
interiorBounds.minY,
|
|
69
|
+
interiorBounds.maxY,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (sameY(p1, p2)) {
|
|
74
|
+
if (
|
|
75
|
+
p1.y <= interiorBounds.minY + EPS ||
|
|
76
|
+
p1.y >= interiorBounds.maxY - EPS
|
|
77
|
+
) {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
return rangesOverlap(
|
|
81
|
+
Math.min(p1.x, p2.x),
|
|
82
|
+
Math.max(p1.x, p2.x),
|
|
83
|
+
interiorBounds.minX,
|
|
84
|
+
interiorBounds.maxX,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const tracePathCrossesAnyTrace = (
|
|
92
|
+
tracePath: Point[],
|
|
93
|
+
traceMap: Record<string, SolvedTracePath>,
|
|
94
|
+
) => {
|
|
95
|
+
for (const trace of Object.values(traceMap)) {
|
|
96
|
+
const points = trace.tracePath
|
|
97
|
+
for (let i = 0; i < tracePath.length - 1; i++) {
|
|
98
|
+
for (let j = 0; j < points.length - 1; j++) {
|
|
99
|
+
if (
|
|
100
|
+
segmentsStrictlyCross(
|
|
101
|
+
tracePath[i]!,
|
|
102
|
+
tracePath[i + 1]!,
|
|
103
|
+
points[j]!,
|
|
104
|
+
points[j + 1]!,
|
|
105
|
+
)
|
|
106
|
+
) {
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const segmentsStrictlyCross = (a1: Point, a2: Point, b1: Point, b2: Point) => {
|
|
117
|
+
if (sameX(a1, a2) && sameY(b1, b2)) {
|
|
118
|
+
return (
|
|
119
|
+
a1.x > Math.min(b1.x, b2.x) + EPS &&
|
|
120
|
+
a1.x < Math.max(b1.x, b2.x) - EPS &&
|
|
121
|
+
b1.y > Math.min(a1.y, a2.y) + EPS &&
|
|
122
|
+
b1.y < Math.max(a1.y, a2.y) - EPS
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (sameY(a1, a2) && sameX(b1, b2)) {
|
|
127
|
+
return (
|
|
128
|
+
b1.x > Math.min(a1.x, a2.x) + EPS &&
|
|
129
|
+
b1.x < Math.max(a1.x, a2.x) - EPS &&
|
|
130
|
+
a1.y > Math.min(b1.y, b2.y) + EPS &&
|
|
131
|
+
a1.y < Math.max(b1.y, b2.y) - EPS
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const getSideDistances = (point: Point, bounds: Bounds) =>
|
|
139
|
+
[
|
|
140
|
+
["left", Math.abs(point.x - bounds.minX)] as const,
|
|
141
|
+
["right", Math.abs(point.x - bounds.maxX)] as const,
|
|
142
|
+
["bottom", Math.abs(point.y - bounds.minY)] as const,
|
|
143
|
+
["top", Math.abs(point.y - bounds.maxY)] as const,
|
|
144
|
+
] satisfies Array<readonly [ChipSide, number]>
|
|
145
|
+
|
|
146
|
+
export const getConnectorTracePath = (
|
|
147
|
+
source: Point,
|
|
148
|
+
target: Point,
|
|
149
|
+
orientation: FacingDirection,
|
|
150
|
+
) =>
|
|
151
|
+
simplifyOrthogonalPath(
|
|
152
|
+
isYOrientation(orientation)
|
|
153
|
+
? [source, { x: target.x, y: source.y }, target]
|
|
154
|
+
: [source, { x: source.x, y: target.y }, target],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const simplifyOrthogonalPath = (path: Point[]) => {
|
|
158
|
+
const deduped = path.filter(
|
|
159
|
+
(point, index) => index === 0 || !pointsEqual(point, path[index - 1]!),
|
|
160
|
+
)
|
|
161
|
+
if (deduped.length < 3) return deduped
|
|
162
|
+
|
|
163
|
+
const simplified: Point[] = [deduped[0]!]
|
|
164
|
+
for (let i = 1; i < deduped.length - 1; i++) {
|
|
165
|
+
const prev = simplified[simplified.length - 1]!
|
|
166
|
+
const point = deduped[i]!
|
|
167
|
+
const next = deduped[i + 1]!
|
|
168
|
+
if (
|
|
169
|
+
(sameX(prev, point) && sameX(point, next)) ||
|
|
170
|
+
(sameY(prev, point) && sameY(point, next))
|
|
171
|
+
) {
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
simplified.push(point)
|
|
175
|
+
}
|
|
176
|
+
simplified.push(deduped[deduped.length - 1]!)
|
|
177
|
+
return simplified
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const pointsEqual = (a: Point, b: Point) => sameX(a, b) && sameY(a, b)
|
|
181
|
+
|
|
182
|
+
const sameX = (a: Point, b: Point) => Math.abs(a.x - b.x) <= EPS
|
|
183
|
+
|
|
184
|
+
const sameY = (a: Point, b: Point) => Math.abs(a.y - b.y) <= EPS
|
|
185
|
+
|
|
186
|
+
export const getMaxSearchDistance = (inputProblem: InputProblem) => {
|
|
187
|
+
const maxChipWidth = Math.max(
|
|
188
|
+
...inputProblem.chips.map((chip) => chip.width),
|
|
189
|
+
1,
|
|
190
|
+
)
|
|
191
|
+
return maxChipWidth * 3
|
|
192
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
2
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
3
|
+
import type { InputPin, InputProblem } from "lib/types/InputProblem"
|
|
4
|
+
import type { CandidateLabel } from "./types"
|
|
5
|
+
|
|
6
|
+
export const getPinMap = (inputProblem: InputProblem) => {
|
|
7
|
+
const pinMap: Record<string, InputPin & { chipId: string }> = {}
|
|
8
|
+
for (const chip of inputProblem.chips) {
|
|
9
|
+
for (const pin of chip.pins) {
|
|
10
|
+
pinMap[pin.pinId] = { ...pin, chipId: chip.chipId }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return pinMap
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const getTracePins = (
|
|
17
|
+
label: NetLabelPlacement,
|
|
18
|
+
pinMap: Record<string, InputPin & { chipId: string }>,
|
|
19
|
+
): SolvedTracePath["pins"] => {
|
|
20
|
+
const pins = label.pinIds.flatMap((pinId) => {
|
|
21
|
+
const pin = pinMap[pinId]
|
|
22
|
+
return pin ? [pin] : []
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (pins.length >= 2) return [pins[0]!, pins[1]!]
|
|
26
|
+
if (pins.length === 1) return [pins[0]!, pins[0]!]
|
|
27
|
+
|
|
28
|
+
const syntheticPin = {
|
|
29
|
+
pinId: `${label.globalConnNetId}-netlabel-anchor`,
|
|
30
|
+
x: label.anchorPoint.x,
|
|
31
|
+
y: label.anchorPoint.y,
|
|
32
|
+
chipId: "available-net-orientation",
|
|
33
|
+
}
|
|
34
|
+
return [syntheticPin, syntheticPin]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const toNetLabelPlacementPatch = (candidate: CandidateLabel) => ({
|
|
38
|
+
orientation: candidate.orientation,
|
|
39
|
+
anchorPoint: candidate.anchorPoint,
|
|
40
|
+
center: candidate.center,
|
|
41
|
+
width: candidate.width,
|
|
42
|
+
height: candidate.height,
|
|
43
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
6
|
+
|
|
7
|
+
export interface AvailableNetOrientationSolverParams {
|
|
8
|
+
inputProblem: InputProblem
|
|
9
|
+
traces: SolvedTracePath[]
|
|
10
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type Bounds = {
|
|
14
|
+
minX: number
|
|
15
|
+
minY: number
|
|
16
|
+
maxX: number
|
|
17
|
+
maxY: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ChipSide = "left" | "right" | "top" | "bottom"
|
|
21
|
+
|
|
22
|
+
export type CandidateLabel = {
|
|
23
|
+
orientation: FacingDirection
|
|
24
|
+
anchorPoint: Point
|
|
25
|
+
center: Point
|
|
26
|
+
width: number
|
|
27
|
+
height: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type CandidateStatus =
|
|
31
|
+
| "valid"
|
|
32
|
+
| "chip-collision"
|
|
33
|
+
| "trace-collision"
|
|
34
|
+
| "netlabel-collision"
|
|
35
|
+
|
|
36
|
+
export type CandidatePhase = "rotate" | "shift"
|
|
37
|
+
|
|
38
|
+
export type EvaluatedCandidate = CandidateLabel & {
|
|
39
|
+
status: CandidateStatus
|
|
40
|
+
selected: boolean
|
|
41
|
+
phase: CandidatePhase
|
|
42
|
+
distance?: number
|
|
43
|
+
outwardDistance?: number
|
|
44
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
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 { CANDIDATE_REJECTED_COLOR, CANDIDATE_SELECTED_COLOR } from "./constants"
|
|
8
|
+
import type { EvaluatedCandidate } from "./types"
|
|
9
|
+
|
|
10
|
+
export const visualizeAvailableNetOrientationSolver = (params: {
|
|
11
|
+
inputProblem: InputProblem
|
|
12
|
+
traces: SolvedTracePath[]
|
|
13
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
14
|
+
currentLabel: NetLabelPlacement | null
|
|
15
|
+
currentCandidateResults: EvaluatedCandidate[]
|
|
16
|
+
solved: boolean
|
|
17
|
+
}): GraphicsObject => {
|
|
18
|
+
const graphics = visualizeInputProblem(params.inputProblem)
|
|
19
|
+
ensureGraphicsArrays(graphics)
|
|
20
|
+
|
|
21
|
+
drawTraces(graphics, params.traces)
|
|
22
|
+
drawNetLabels(graphics, params.outputNetLabelPlacements)
|
|
23
|
+
drawCurrentLabel(graphics, params.currentLabel, params.solved)
|
|
24
|
+
drawCurrentCandidates(graphics, params.currentCandidateResults, params.solved)
|
|
25
|
+
|
|
26
|
+
return graphics
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const drawTraces = (graphics: GraphicsObject, traces: SolvedTracePath[]) => {
|
|
30
|
+
for (const trace of traces) {
|
|
31
|
+
graphics.lines!.push({
|
|
32
|
+
points: trace.tracePath,
|
|
33
|
+
strokeColor: "purple",
|
|
34
|
+
} as any)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const drawNetLabels = (
|
|
39
|
+
graphics: GraphicsObject,
|
|
40
|
+
labels: NetLabelPlacement[],
|
|
41
|
+
) => {
|
|
42
|
+
for (const label of labels) {
|
|
43
|
+
graphics.rects!.push({
|
|
44
|
+
center: label.center,
|
|
45
|
+
width: label.width,
|
|
46
|
+
height: label.height,
|
|
47
|
+
fill: getColorFromString(label.globalConnNetId, 0.35),
|
|
48
|
+
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
49
|
+
label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
|
|
50
|
+
} as any)
|
|
51
|
+
graphics.points!.push({
|
|
52
|
+
x: label.anchorPoint.x,
|
|
53
|
+
y: label.anchorPoint.y,
|
|
54
|
+
color: getColorFromString(label.globalConnNetId, 0.9),
|
|
55
|
+
label: `anchorPoint\norientation: ${label.orientation}`,
|
|
56
|
+
} as any)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const drawCurrentLabel = (
|
|
61
|
+
graphics: GraphicsObject,
|
|
62
|
+
currentLabel: NetLabelPlacement | null,
|
|
63
|
+
solved: boolean,
|
|
64
|
+
) => {
|
|
65
|
+
if (!currentLabel || solved) return
|
|
66
|
+
|
|
67
|
+
graphics.rects!.push({
|
|
68
|
+
center: currentLabel.center,
|
|
69
|
+
width: currentLabel.width,
|
|
70
|
+
height: currentLabel.height,
|
|
71
|
+
fill: "rgba(255, 0, 0, 0.2)",
|
|
72
|
+
strokeColor: CANDIDATE_REJECTED_COLOR,
|
|
73
|
+
label: `available orientation target\n${currentLabel.netId ?? currentLabel.globalConnNetId}`,
|
|
74
|
+
} as any)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const drawCurrentCandidates = (
|
|
78
|
+
graphics: GraphicsObject,
|
|
79
|
+
candidates: EvaluatedCandidate[],
|
|
80
|
+
solved: boolean,
|
|
81
|
+
) => {
|
|
82
|
+
if (solved) return
|
|
83
|
+
|
|
84
|
+
for (const candidate of candidates) {
|
|
85
|
+
const color = candidate.selected
|
|
86
|
+
? CANDIDATE_SELECTED_COLOR
|
|
87
|
+
: CANDIDATE_REJECTED_COLOR
|
|
88
|
+
const distanceLabel =
|
|
89
|
+
candidate.distance === undefined
|
|
90
|
+
? ""
|
|
91
|
+
: `\ndistance: ${candidate.distance.toFixed(3)}`
|
|
92
|
+
const outwardDistanceLabel =
|
|
93
|
+
candidate.outwardDistance === undefined || candidate.outwardDistance === 0
|
|
94
|
+
? ""
|
|
95
|
+
: `\noutward distance: ${candidate.outwardDistance.toFixed(3)}`
|
|
96
|
+
|
|
97
|
+
graphics.rects!.push({
|
|
98
|
+
center: candidate.center,
|
|
99
|
+
width: candidate.width,
|
|
100
|
+
height: candidate.height,
|
|
101
|
+
fill: candidate.selected
|
|
102
|
+
? "rgba(0, 0, 255, 0.2)"
|
|
103
|
+
: "rgba(255, 0, 0, 0.15)",
|
|
104
|
+
strokeColor: color,
|
|
105
|
+
strokeDash: candidate.selected ? undefined : "4 2",
|
|
106
|
+
label: `${candidate.selected ? "selected" : candidate.status} available orientation\nphase: ${candidate.phase}\norientation: ${candidate.orientation}${distanceLabel}${outwardDistanceLabel}`,
|
|
107
|
+
} as any)
|
|
108
|
+
graphics.points!.push({
|
|
109
|
+
x: candidate.anchorPoint.x,
|
|
110
|
+
y: candidate.anchorPoint.y,
|
|
111
|
+
color,
|
|
112
|
+
label: `candidate anchor\n${candidate.phase}\n${candidate.orientation}`,
|
|
113
|
+
} as any)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const ensureGraphicsArrays = (graphics: GraphicsObject) => {
|
|
118
|
+
if (!graphics.lines) graphics.lines = []
|
|
119
|
+
if (!graphics.points) graphics.points = []
|
|
120
|
+
if (!graphics.rects) graphics.rects = []
|
|
121
|
+
if (!graphics.circles) graphics.circles = []
|
|
122
|
+
if (!graphics.texts) graphics.texts = []
|
|
123
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
5
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
6
|
+
import {
|
|
7
|
+
detectTraceLabelOverlap,
|
|
8
|
+
type TraceLabelOverlap,
|
|
9
|
+
} from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
|
|
10
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
11
|
+
import { moveAttachedLabelsToReroutedTrace } from "./labelMovement"
|
|
12
|
+
import { findBestReroutePath } from "./reroute"
|
|
13
|
+
import type { Example28SolverParams, RerouteCandidateResult } from "./types"
|
|
14
|
+
import { visualizeExample28Solver } from "./visualize"
|
|
15
|
+
|
|
16
|
+
export class Example28Solver extends BaseSolver {
|
|
17
|
+
inputProblem: InputProblem
|
|
18
|
+
traces: SolvedTracePath[]
|
|
19
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
20
|
+
|
|
21
|
+
outputTraces: SolvedTracePath[]
|
|
22
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
23
|
+
queuedOverlaps: TraceLabelOverlap[] = []
|
|
24
|
+
currentOverlap: TraceLabelOverlap | null = null
|
|
25
|
+
currentCandidateResults: RerouteCandidateResult[] = []
|
|
26
|
+
|
|
27
|
+
private chipObstacles: ReturnType<typeof getObstacleRects>
|
|
28
|
+
|
|
29
|
+
constructor(params: Example28SolverParams) {
|
|
30
|
+
super()
|
|
31
|
+
this.inputProblem = params.inputProblem
|
|
32
|
+
this.traces = params.traces
|
|
33
|
+
this.netLabelPlacements = params.netLabelPlacements
|
|
34
|
+
this.outputTraces = [...params.traces]
|
|
35
|
+
this.outputNetLabelPlacements = [...params.netLabelPlacements]
|
|
36
|
+
this.chipObstacles = getObstacleRects(params.inputProblem)
|
|
37
|
+
this.initializeQueuedOverlaps()
|
|
38
|
+
this.currentOverlap = this.queuedOverlaps[0] ?? null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override getConstructorParams(): ConstructorParameters<
|
|
42
|
+
typeof Example28Solver
|
|
43
|
+
>[0] {
|
|
44
|
+
return {
|
|
45
|
+
inputProblem: this.inputProblem,
|
|
46
|
+
traces: this.traces,
|
|
47
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override _step() {
|
|
52
|
+
const overlap = this.queuedOverlaps.shift()
|
|
53
|
+
if (!overlap) {
|
|
54
|
+
this.finish()
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!this.isOverlapStillPresent(overlap)) {
|
|
59
|
+
this.currentOverlap = overlap
|
|
60
|
+
this.currentCandidateResults = []
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.rerouteOverlappingTrace(overlap)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private finish() {
|
|
68
|
+
this.currentOverlap = null
|
|
69
|
+
this.currentCandidateResults = []
|
|
70
|
+
this.solved = true
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private initializeQueuedOverlaps() {
|
|
74
|
+
const queuedOverlaps: TraceLabelOverlap[] = []
|
|
75
|
+
const seenPairs = new Set<string>()
|
|
76
|
+
|
|
77
|
+
for (const overlap of this.getTraceLabelOverlaps()) {
|
|
78
|
+
if (!this.shouldRerouteOverlap(overlap)) continue
|
|
79
|
+
|
|
80
|
+
const key = this.getOverlapKey(overlap)
|
|
81
|
+
if (seenPairs.has(key)) continue
|
|
82
|
+
|
|
83
|
+
seenPairs.add(key)
|
|
84
|
+
queuedOverlaps.push(overlap)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.queuedOverlaps = queuedOverlaps
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private getTraceLabelOverlaps() {
|
|
91
|
+
return detectTraceLabelOverlap({
|
|
92
|
+
traces: this.outputTraces,
|
|
93
|
+
netLabels: this.outputNetLabelPlacements,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private shouldRerouteOverlap(overlap: TraceLabelOverlap) {
|
|
98
|
+
return (
|
|
99
|
+
this.isXFacingLabel(overlap.label) &&
|
|
100
|
+
!this.hasExplicitOrientationConstraint(overlap.label)
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private isXFacingLabel(label: NetLabelPlacement) {
|
|
105
|
+
return label.orientation === "x+" || label.orientation === "x-"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private hasExplicitOrientationConstraint(label: NetLabelPlacement) {
|
|
109
|
+
const effectiveNetId = label.netId ?? label.globalConnNetId
|
|
110
|
+
return Object.hasOwn(
|
|
111
|
+
this.inputProblem.availableNetLabelOrientations,
|
|
112
|
+
effectiveNetId,
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private getOverlapKey(overlap: TraceLabelOverlap) {
|
|
117
|
+
const labelNetId = overlap.label.netId ?? overlap.label.globalConnNetId
|
|
118
|
+
return `${overlap.trace.mspPairId}:${labelNetId}:${overlap.label.anchorPoint.x}:${overlap.label.anchorPoint.y}`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private isOverlapStillPresent(overlap: TraceLabelOverlap) {
|
|
122
|
+
const currentTrace = this.getCurrentTrace(overlap.trace)
|
|
123
|
+
if (!currentTrace) return false
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
detectTraceLabelOverlap({
|
|
127
|
+
traces: [currentTrace],
|
|
128
|
+
netLabels: [overlap.label],
|
|
129
|
+
}).length > 0
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private getCurrentTrace(trace: SolvedTracePath) {
|
|
134
|
+
return this.outputTraces.find((t) => t.mspPairId === trace.mspPairId)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private rerouteOverlappingTrace(overlap: TraceLabelOverlap) {
|
|
138
|
+
this.currentOverlap = overlap
|
|
139
|
+
this.currentCandidateResults = []
|
|
140
|
+
|
|
141
|
+
const traceIndex = this.outputTraces.findIndex(
|
|
142
|
+
(trace) => trace.mspPairId === overlap.trace.mspPairId,
|
|
143
|
+
)
|
|
144
|
+
if (traceIndex === -1) return
|
|
145
|
+
|
|
146
|
+
const currentTrace = this.outputTraces[traceIndex]!
|
|
147
|
+
const rerouteResult = findBestReroutePath({
|
|
148
|
+
trace: currentTrace,
|
|
149
|
+
obstacleLabel: overlap.label,
|
|
150
|
+
inputProblem: this.inputProblem,
|
|
151
|
+
outputTraces: this.outputTraces,
|
|
152
|
+
outputNetLabelPlacements: this.outputNetLabelPlacements,
|
|
153
|
+
chipObstacles: this.chipObstacles,
|
|
154
|
+
})
|
|
155
|
+
this.currentCandidateResults = rerouteResult.candidateResults
|
|
156
|
+
|
|
157
|
+
if (!rerouteResult.bestPath) return
|
|
158
|
+
|
|
159
|
+
const originalTracePath = currentTrace.tracePath
|
|
160
|
+
this.outputTraces[traceIndex] = {
|
|
161
|
+
...currentTrace,
|
|
162
|
+
tracePath: rerouteResult.bestPath,
|
|
163
|
+
}
|
|
164
|
+
this.outputNetLabelPlacements = moveAttachedLabelsToReroutedTrace({
|
|
165
|
+
trace: currentTrace,
|
|
166
|
+
originalTracePath,
|
|
167
|
+
reroutedTracePath: rerouteResult.bestPath,
|
|
168
|
+
netLabelPlacements: this.outputNetLabelPlacements,
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
override visualize(): GraphicsObject {
|
|
173
|
+
return visualizeExample28Solver({
|
|
174
|
+
inputProblem: this.inputProblem,
|
|
175
|
+
outputTraces: this.outputTraces,
|
|
176
|
+
outputNetLabelPlacements: this.outputNetLabelPlacements,
|
|
177
|
+
solved: this.solved,
|
|
178
|
+
currentOverlap: this.currentOverlap,
|
|
179
|
+
currentCandidateResults: this.currentCandidateResults,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
}
|