@tscircuit/schematic-trace-solver 0.0.187 → 0.0.189
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 +42 -23
- package/dist/index.js +1053 -316
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +612 -230
- package/lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts +2 -6
- package/lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts +13 -0
- package/lib/solvers/InlineNetLabelSolver/getInlineLabelObstacles.ts +64 -0
- package/lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts +407 -0
- package/lib/solvers/InlineNetLabelSolver/getOutputLabelCollisions.ts +145 -0
- package/lib/solvers/InlineNetLabelSolver/isLabelRepresentedInline.ts +15 -0
- package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +61 -31
- package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +94 -16
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +4 -3
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +7 -7
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +3 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +5 -1
- package/lib/types/InputProblem.ts +2 -2
- package/package.json +1 -1
- package/site/SchematicTracePipelineSolver/repro-robot-controller-imu-tof.page.tsx +7 -0
- package/site/bug-reports/bug-report-20260907T110144Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260907T145640Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +18 -18
- package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.test.ts +21 -0
- package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +5 -38
- package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +3 -9
- package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +18 -30
- package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.test.ts +33 -0
- package/tests/bug-reports/bug-report-20260907T110144Z/__snapshots__/bug-report-20260907T110144Z.snap.svg +282 -0
- package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.json +1073 -0
- package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260907T145640Z/__snapshots__/bug-report-20260907T145640Z.snap.svg +1107 -0
- package/tests/bug-reports/bug-report-20260907T145640Z/bug-report-20260907T145640Z.json +3563 -0
- package/tests/bug-reports/bug-report-20260907T145640Z/bug-report-20260907T145640Z.test.ts +117 -0
- package/tests/repros/__snapshots__/repro-battery-management-bq24073.snap.svg +4 -4
- package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +20 -20
- package/tests/repros/__snapshots__/repro-esp12f-section.snap.svg +37 -55
- package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +44 -68
- package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +3 -9
- package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.snap.svg +199 -0
- package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.source.svg +33 -0
- package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +50 -74
- package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
- package/tests/repros/__snapshots__/rp2040-robot-controller-disconnected-gnd.snap.svg +103 -0
- package/tests/repros/assets/repro-robot-controller-imu-tof.input.json +540 -0
- package/tests/repros/assets/rp2040-robot-controller.input.json +349 -0
- package/tests/repros/repro-battery-management-bq24073.test.ts +13 -1
- package/tests/repros/repro-hub-usb-sheet.test.ts +25 -0
- package/tests/repros/repro-robot-controller-imu-tof.test.ts +83 -0
- package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +59 -13
- package/tests/repros/rp2040-robot-controller-disconnected-gnd.test.ts +59 -0
- package/tests/solvers/InlineNetLabelSolver/get-anchored-net-label-rendered-bounds.test.ts +24 -0
- package/tests/solvers/InlineNetLabelSolver/local-trace-label-shifts.test.ts +296 -0
- package/tests/solvers/InlineNetLabelSolver/multi-pin-connected-components.test.ts +7 -4
- package/tests/solvers/InlineNetLabelSolver/push-anchored-net-labels-away.test.ts +2 -1
- package/tests/solvers/InlineNetLabelSolver/shared-direct-and-named-connection.test.ts +62 -0
- package/tests/solvers/InlineNetLabelSolver/two-pin-terminal-stubs-atomic.test.ts +9 -3
- package/tests/solvers/TraceCleanupSolver/perpendicular-detours-preserve-endpoints.test.ts +60 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
3
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
4
|
+
import {
|
|
5
|
+
InlineNetLabelSolver,
|
|
6
|
+
type InlineNetLabelOutput,
|
|
7
|
+
} from "lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver"
|
|
8
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
9
|
+
import { getLocalTraceLabelShifts } from "lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts"
|
|
10
|
+
import {
|
|
11
|
+
getOutputLabelCollisions,
|
|
12
|
+
sameOutputLabelCollision,
|
|
13
|
+
} from "lib/solvers/InlineNetLabelSolver/getOutputLabelCollisions"
|
|
14
|
+
|
|
15
|
+
const trace = (net: string, path: Point[]): SolvedTracePath => ({
|
|
16
|
+
mspPairId: net,
|
|
17
|
+
mspConnectionPairIds: [net],
|
|
18
|
+
globalConnNetId: net,
|
|
19
|
+
dcConnNetId: net,
|
|
20
|
+
pinIds: [`${net}.1`, `${net}.2`],
|
|
21
|
+
pins: [
|
|
22
|
+
{ pinId: `${net}.1`, chipId: "A", ...path[0]! },
|
|
23
|
+
{ pinId: `${net}.2`, chipId: "B", ...path.at(-1)! },
|
|
24
|
+
],
|
|
25
|
+
tracePath: path,
|
|
26
|
+
})
|
|
27
|
+
const fixture = () => {
|
|
28
|
+
const input: InputProblem = {
|
|
29
|
+
chips: [],
|
|
30
|
+
netConnections: [],
|
|
31
|
+
directConnections: [],
|
|
32
|
+
availableNetLabelOrientations: {},
|
|
33
|
+
}
|
|
34
|
+
const output: Omit<InlineNetLabelOutput, "inputProblem"> = {
|
|
35
|
+
traces: [
|
|
36
|
+
trace("wire", [
|
|
37
|
+
{ x: 0, y: 1 },
|
|
38
|
+
{ x: 1, y: 1 },
|
|
39
|
+
{ x: 1, y: -1 },
|
|
40
|
+
{ x: 0, y: -1 },
|
|
41
|
+
]),
|
|
42
|
+
],
|
|
43
|
+
netLabelPlacements: [
|
|
44
|
+
{
|
|
45
|
+
globalConnNetId: "label",
|
|
46
|
+
netId: "label",
|
|
47
|
+
pinIds: ["label.1"],
|
|
48
|
+
mspConnectionPairIds: [],
|
|
49
|
+
orientation: "x+",
|
|
50
|
+
anchorPoint: { x: 0.8, y: 0 },
|
|
51
|
+
center: { x: 1, y: 0 },
|
|
52
|
+
width: 0.4,
|
|
53
|
+
height: 0.2,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
inlineNetLabelPlacements: [],
|
|
57
|
+
}
|
|
58
|
+
return { input, output }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const rotation of [0, 1, 2, 3])
|
|
62
|
+
test(`slides an existing leg without adding bends (rotation ${rotation})`, () => {
|
|
63
|
+
const { input, output } = fixture()
|
|
64
|
+
const rotate = (p: Point): Point => {
|
|
65
|
+
for (let i = 0; i < rotation; i++) p = { x: -p.y, y: p.x }
|
|
66
|
+
return p
|
|
67
|
+
}
|
|
68
|
+
output.traces[0]!.tracePath = output.traces[0]!.tracePath.map(rotate)
|
|
69
|
+
output.traces[0]!.pins = output.traces[0]!.pins.map((pin) => ({
|
|
70
|
+
...pin,
|
|
71
|
+
...rotate(pin),
|
|
72
|
+
})) as SolvedTracePath["pins"]
|
|
73
|
+
const label = output.netLabelPlacements[0]!
|
|
74
|
+
label.anchorPoint = rotate(label.anchorPoint)
|
|
75
|
+
label.center = rotate(label.center)
|
|
76
|
+
label.orientation = (["x+", "y+", "x-", "y-"] as const)[rotation]!
|
|
77
|
+
if (rotation % 2) [label.width, label.height] = [label.height, label.width]
|
|
78
|
+
const saved = structuredClone(output)
|
|
79
|
+
const proposals = [...getLocalTraceLabelShifts(input, output)]
|
|
80
|
+
expect(proposals.length).toBeGreaterThan(0)
|
|
81
|
+
for (const proposal of proposals) {
|
|
82
|
+
const path = proposal.traces[0]!.tracePath
|
|
83
|
+
expect(path).toHaveLength(4)
|
|
84
|
+
expect(path[0]).toEqual(output.traces[0]!.tracePath[0])
|
|
85
|
+
expect(path.at(-1)).toEqual(output.traces[0]!.tracePath.at(-1))
|
|
86
|
+
expect([...getOutputLabelCollisions(proposal)]).toEqual([])
|
|
87
|
+
}
|
|
88
|
+
expect(output).toEqual(saved)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test.each([
|
|
92
|
+
"chip",
|
|
93
|
+
"parallel wire",
|
|
94
|
+
"new crossing",
|
|
95
|
+
"junction",
|
|
96
|
+
"unconnected pin",
|
|
97
|
+
] as const)(
|
|
98
|
+
"does not force a detour through a blocked corridor: %s",
|
|
99
|
+
(obstacle) => {
|
|
100
|
+
const { input, output } = fixture()
|
|
101
|
+
if (obstacle === "chip")
|
|
102
|
+
input.chips.push(
|
|
103
|
+
...[0.75, 1.25].map((x) => ({
|
|
104
|
+
chipId: `C${x}`,
|
|
105
|
+
center: { x, y: 0 },
|
|
106
|
+
width: 0.08,
|
|
107
|
+
height: 0.5,
|
|
108
|
+
pins: [],
|
|
109
|
+
})),
|
|
110
|
+
)
|
|
111
|
+
if (obstacle === "parallel wire")
|
|
112
|
+
output.traces.push(
|
|
113
|
+
...[0.75, 1.25].map((x) =>
|
|
114
|
+
trace(`other${x}`, [
|
|
115
|
+
{ x, y: -2 },
|
|
116
|
+
{ x, y: 2 },
|
|
117
|
+
]),
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
if (obstacle === "new crossing")
|
|
121
|
+
output.traces.push(
|
|
122
|
+
trace("other", [
|
|
123
|
+
{ x: 0.6, y: 0 },
|
|
124
|
+
{ x: 0.9, y: 0 },
|
|
125
|
+
]),
|
|
126
|
+
trace("other2", [
|
|
127
|
+
{ x: 1.1, y: 0 },
|
|
128
|
+
{ x: 1.4, y: 0 },
|
|
129
|
+
]),
|
|
130
|
+
)
|
|
131
|
+
if (obstacle === "junction")
|
|
132
|
+
output.traces.push({
|
|
133
|
+
...trace("branch", [
|
|
134
|
+
{ x: 1, y: 0.5 },
|
|
135
|
+
{ x: 2, y: 0.5 },
|
|
136
|
+
]),
|
|
137
|
+
globalConnNetId: "wire",
|
|
138
|
+
})
|
|
139
|
+
if (obstacle === "unconnected pin")
|
|
140
|
+
input.chips.push({
|
|
141
|
+
chipId: "ports",
|
|
142
|
+
center: { x: 10, y: 10 },
|
|
143
|
+
width: 1,
|
|
144
|
+
height: 1,
|
|
145
|
+
pins: [0.79, 1.21].map((x) => ({ pinId: `NC${x}`, x, y: 0 })),
|
|
146
|
+
})
|
|
147
|
+
expect([...getLocalTraceLabelShifts(input, output)]).toEqual([])
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
test("moves a neighboring power label with its short connector to free a side corridor", () => {
|
|
152
|
+
const { input, output } = fixture()
|
|
153
|
+
input.chips.push({
|
|
154
|
+
chipId: "C",
|
|
155
|
+
center: { x: 0.75, y: 0 },
|
|
156
|
+
width: 0.08,
|
|
157
|
+
height: 0.5,
|
|
158
|
+
pins: [],
|
|
159
|
+
})
|
|
160
|
+
const power = {
|
|
161
|
+
...trace("power", [
|
|
162
|
+
{ x: 0, y: -0.7 },
|
|
163
|
+
{ x: 1.4, y: -0.7 },
|
|
164
|
+
{ x: 1.4, y: 0.8 },
|
|
165
|
+
]),
|
|
166
|
+
pinIds: ["power.1"],
|
|
167
|
+
}
|
|
168
|
+
power.pins = [power.pins[0], power.pins[0]]
|
|
169
|
+
output.traces.push(power)
|
|
170
|
+
output.netLabelPlacements.push({
|
|
171
|
+
globalConnNetId: "power",
|
|
172
|
+
netId: "power",
|
|
173
|
+
pinIds: ["power.1"],
|
|
174
|
+
mspConnectionPairIds: [],
|
|
175
|
+
orientation: "y+",
|
|
176
|
+
anchorPoint: { x: 1.4, y: 0.8 },
|
|
177
|
+
center: { x: 1.4, y: 0.95 },
|
|
178
|
+
width: 0.6,
|
|
179
|
+
height: 0.3,
|
|
180
|
+
})
|
|
181
|
+
const proposals = [...getLocalTraceLabelShifts(input, output)]
|
|
182
|
+
expect(proposals.length).toBeGreaterThan(0)
|
|
183
|
+
for (const proposal of proposals) {
|
|
184
|
+
expect(proposal.traces[0]!.tracePath).toHaveLength(4)
|
|
185
|
+
expect(proposal.traces[1]!.tracePath).toHaveLength(3)
|
|
186
|
+
expect(proposal.traces[1]!.tracePath[0]).toEqual(power.tracePath[0])
|
|
187
|
+
expect(proposal.netLabelPlacements[1]!.anchorPoint.x).toBeGreaterThan(1.4)
|
|
188
|
+
expect([...getOutputLabelCollisions(proposal)]).toEqual([])
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test("clears a moved rail's own tag and a neighboring power label in one proposal", () => {
|
|
193
|
+
const { input, output } = fixture()
|
|
194
|
+
input.chips.push({
|
|
195
|
+
chipId: "C",
|
|
196
|
+
center: { x: 0.75, y: 0 },
|
|
197
|
+
width: 0.08,
|
|
198
|
+
height: 0.5,
|
|
199
|
+
pins: [],
|
|
200
|
+
})
|
|
201
|
+
const power = {
|
|
202
|
+
...trace("power", [
|
|
203
|
+
{ x: 0, y: -0.8 },
|
|
204
|
+
{ x: 1.125, y: -0.8 },
|
|
205
|
+
{ x: 1.125, y: -0.6 },
|
|
206
|
+
]),
|
|
207
|
+
pinIds: ["power.1"],
|
|
208
|
+
}
|
|
209
|
+
power.pins = [power.pins[0], power.pins[0]]
|
|
210
|
+
output.traces.push(power)
|
|
211
|
+
output.netLabelPlacements.push(
|
|
212
|
+
{
|
|
213
|
+
globalConnNetId: "wire",
|
|
214
|
+
netId: "wire",
|
|
215
|
+
pinIds: ["wire.1", "wire.2"],
|
|
216
|
+
mspConnectionPairIds: [],
|
|
217
|
+
orientation: "x-",
|
|
218
|
+
anchorPoint: { x: 1, y: -0.5 },
|
|
219
|
+
center: { x: 0.8, y: -0.5 },
|
|
220
|
+
width: 0.4,
|
|
221
|
+
height: 0.2,
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
globalConnNetId: "power",
|
|
225
|
+
netId: "power",
|
|
226
|
+
pinIds: ["power.1"],
|
|
227
|
+
mspConnectionPairIds: [],
|
|
228
|
+
orientation: "y+",
|
|
229
|
+
anchorPoint: { x: 1.125, y: -0.6 },
|
|
230
|
+
center: { x: 1.125, y: -0.5 },
|
|
231
|
+
width: 0.15,
|
|
232
|
+
height: 0.2,
|
|
233
|
+
},
|
|
234
|
+
)
|
|
235
|
+
const saved = structuredClone(output)
|
|
236
|
+
const proposals = [...getLocalTraceLabelShifts(input, output)]
|
|
237
|
+
expect(proposals.length).toBeGreaterThan(0)
|
|
238
|
+
for (const proposal of proposals) {
|
|
239
|
+
expect(proposal.traces.map((trace) => trace.tracePath.length)).toEqual([
|
|
240
|
+
4, 3,
|
|
241
|
+
])
|
|
242
|
+
expect(proposal.netLabelPlacements[1]!.anchorPoint.x).toBeCloseTo(1.25)
|
|
243
|
+
expect(proposal.netLabelPlacements[2]!.anchorPoint.x).toBeGreaterThan(1.25)
|
|
244
|
+
expect([...getOutputLabelCollisions(proposal)]).toEqual([])
|
|
245
|
+
}
|
|
246
|
+
expect(output).toEqual(saved)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test("collision identity preserves opaque trace, net, and pin IDs", () => {
|
|
250
|
+
const first = fixture().output
|
|
251
|
+
first.traces[0]!.mspPairId = "a/b"
|
|
252
|
+
first.netLabelPlacements[0]!.globalConnNetId = "c"
|
|
253
|
+
first.netLabelPlacements[0]!.pinIds = ["p,q"]
|
|
254
|
+
const second = structuredClone(first)
|
|
255
|
+
second.traces[0]!.mspPairId = "a"
|
|
256
|
+
second.netLabelPlacements[0]!.globalConnNetId = "b/c"
|
|
257
|
+
const a = getOutputLabelCollisions(first)[0]!
|
|
258
|
+
const b = getOutputLabelCollisions(second)[0]!
|
|
259
|
+
expect(a).toMatchObject({
|
|
260
|
+
kind: "trace-label",
|
|
261
|
+
trace: { kind: "routed", id: "a/b" },
|
|
262
|
+
})
|
|
263
|
+
expect(sameOutputLabelCollision(a, b)).toBe(false)
|
|
264
|
+
const splitPins = structuredClone(first)
|
|
265
|
+
splitPins.netLabelPlacements[0]!.pinIds = ["p", "q"]
|
|
266
|
+
expect(
|
|
267
|
+
sameOutputLabelCollision(a, getOutputLabelCollisions(splitPins)[0]!),
|
|
268
|
+
).toBe(false)
|
|
269
|
+
expect(sameOutputLabelCollision(a, structuredClone(a))).toBe(true)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test("refinement steps candidates and retains output when they exhaust their budget", () => {
|
|
273
|
+
const { input, output } = fixture()
|
|
274
|
+
const solver = new InlineNetLabelSolver({
|
|
275
|
+
inputProblem: input,
|
|
276
|
+
...output,
|
|
277
|
+
resolveTraceCollisions: true,
|
|
278
|
+
})
|
|
279
|
+
let failedCandidates = 0
|
|
280
|
+
while (!solver.solved && !solver.failed) {
|
|
281
|
+
if (solver.activeSubSolver) {
|
|
282
|
+
const candidate = solver.activeSubSolver
|
|
283
|
+
expect(candidate.iterations).toBe(0)
|
|
284
|
+
candidate.MAX_ITERATIONS = 0
|
|
285
|
+
solver.step()
|
|
286
|
+
expect(candidate.failed).toBe(true)
|
|
287
|
+
failedCandidates++
|
|
288
|
+
} else solver.step()
|
|
289
|
+
}
|
|
290
|
+
expect(failedCandidates).toBeGreaterThan(0)
|
|
291
|
+
expect(solver.solved).toBe(true)
|
|
292
|
+
expect(solver.getOutput().traces).toEqual(output.traces)
|
|
293
|
+
expect(solver.getOutput().netLabelPlacements).toEqual(
|
|
294
|
+
output.netLabelPlacements,
|
|
295
|
+
)
|
|
296
|
+
})
|
|
@@ -175,9 +175,11 @@ test("a short routed component is retained when replacing it would strand an int
|
|
|
175
175
|
solver.solve()
|
|
176
176
|
|
|
177
177
|
const output = solver.getOutput()
|
|
178
|
-
expect(output.inlineNetLabelPlacements).
|
|
178
|
+
expect(output.inlineNetLabelPlacements.map((label) => label.pinIds)).toEqual([
|
|
179
|
+
["U1.1"],
|
|
180
|
+
])
|
|
179
181
|
expect(output.traces).toEqual([shortTrace])
|
|
180
|
-
expect(output.netLabelPlacements).toEqual(anchoredLabels)
|
|
182
|
+
expect(output.netLabelPlacements).toEqual([anchoredLabels[0]!])
|
|
181
183
|
})
|
|
182
184
|
|
|
183
185
|
test("a generated anchored-label connector becomes an inline terminal stub", () => {
|
|
@@ -218,8 +220,8 @@ test("a generated anchored-label connector becomes an inline terminal stub", ()
|
|
|
218
220
|
availableNetLabelOrientations: { SIGNAL: ["x-"] },
|
|
219
221
|
}
|
|
220
222
|
const generatedLabelConnector: SolvedTracePath = {
|
|
221
|
-
mspPairId: "
|
|
222
|
-
mspConnectionPairIds: ["
|
|
223
|
+
mspPairId: "opaque/connector:1",
|
|
224
|
+
mspConnectionPairIds: ["opaque/connector:1"],
|
|
223
225
|
dcConnNetId: "SIGNAL",
|
|
224
226
|
globalConnNetId: "SIGNAL",
|
|
225
227
|
userNetId: "SIGNAL",
|
|
@@ -237,6 +239,7 @@ test("a generated anchored-label connector becomes an inline terminal stub", ()
|
|
|
237
239
|
const solver = new InlineNetLabelSolver({
|
|
238
240
|
inputProblem,
|
|
239
241
|
traces: [generatedLabelConnector],
|
|
242
|
+
netLabelConnectorTraceIds: new Set([generatedLabelConnector.mspPairId]),
|
|
240
243
|
netLabelPlacements: [],
|
|
241
244
|
})
|
|
242
245
|
solver.solve()
|
|
@@ -76,6 +76,7 @@ test("pushes a regular endpoint label and its wick past nearby inline text", ()
|
|
|
76
76
|
traces,
|
|
77
77
|
netLabelPlacements,
|
|
78
78
|
inlineNetLabelPlacements,
|
|
79
|
+
netLabelConnectorTraceIds: new Set(traces.map((trace) => trace.mspPairId)),
|
|
79
80
|
})
|
|
80
81
|
|
|
81
82
|
expect(output.movedLabelCount).toBe(1)
|
|
@@ -289,7 +290,7 @@ test("moves a contiguous label row together and shoves an anchored obstacle", ()
|
|
|
289
290
|
expect(output.netLabelPlacements[2]!.center.x).toBeLessThan(-2.5)
|
|
290
291
|
expect(
|
|
291
292
|
output.traces.filter((trace) =>
|
|
292
|
-
trace.mspPairId
|
|
293
|
+
output.netLabelConnectorTraceIds.has(trace.mspPairId),
|
|
293
294
|
),
|
|
294
295
|
).toHaveLength(3)
|
|
295
296
|
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { InlineNetLabelSolver } from "lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver"
|
|
3
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
4
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
|
|
6
|
+
test.each(["CLOCK", ".A > .B"])(
|
|
7
|
+
"a named net and its direct pair %s produce one inline label",
|
|
8
|
+
(directName) => {
|
|
9
|
+
const connection = {
|
|
10
|
+
netId: "CLOCK",
|
|
11
|
+
pinIds: ["A.1", "B.1"] as [string, string],
|
|
12
|
+
allowInlineNetLabel: true,
|
|
13
|
+
inlineNetLabelWidth: 0.5,
|
|
14
|
+
inlineNetLabelHeight: 0.12,
|
|
15
|
+
}
|
|
16
|
+
const input: InputProblem = {
|
|
17
|
+
chips: [
|
|
18
|
+
{
|
|
19
|
+
chipId: "A",
|
|
20
|
+
center: { x: 0, y: 0 },
|
|
21
|
+
width: 1,
|
|
22
|
+
height: 1,
|
|
23
|
+
pins: [{ pinId: "A.1", x: 0.5, y: 0 }],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
chipId: "B",
|
|
27
|
+
center: { x: 3, y: 0 },
|
|
28
|
+
width: 1,
|
|
29
|
+
height: 1,
|
|
30
|
+
pins: [{ pinId: "B.1", x: 2.5, y: 0 }],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
directConnections: [{ ...connection, netId: directName }],
|
|
34
|
+
netConnections: [connection],
|
|
35
|
+
availableNetLabelOrientations: {},
|
|
36
|
+
}
|
|
37
|
+
const trace: SolvedTracePath = {
|
|
38
|
+
mspPairId: "pair",
|
|
39
|
+
mspConnectionPairIds: ["pair"],
|
|
40
|
+
globalConnNetId: "CLOCK",
|
|
41
|
+
dcConnNetId: "CLOCK",
|
|
42
|
+
pinIds: connection.pinIds,
|
|
43
|
+
pins: [
|
|
44
|
+
{ pinId: "A.1", chipId: "A", x: 0.5, y: 0 },
|
|
45
|
+
{ pinId: "B.1", chipId: "B", x: 2.5, y: 0 },
|
|
46
|
+
],
|
|
47
|
+
tracePath: [
|
|
48
|
+
{ x: 0.5, y: 0 },
|
|
49
|
+
{ x: 2.5, y: 0 },
|
|
50
|
+
],
|
|
51
|
+
}
|
|
52
|
+
const solver = new InlineNetLabelSolver({
|
|
53
|
+
inputProblem: input,
|
|
54
|
+
traces: [trace],
|
|
55
|
+
netLabelPlacements: [],
|
|
56
|
+
})
|
|
57
|
+
solver.solve()
|
|
58
|
+
expect(solver.getOutput().inlineNetLabelPlacements).toHaveLength(1)
|
|
59
|
+
expect(solver.getOutput().inlineNetLabelPlacements[0]!.netId).toBe("CLOCK")
|
|
60
|
+
expect(solver.getOutput().traces).toEqual([trace])
|
|
61
|
+
},
|
|
62
|
+
)
|
|
@@ -3,7 +3,7 @@ import { InlineNetLabelSolver } from "lib/solvers/InlineNetLabelSolver/InlineNet
|
|
|
3
3
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
4
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
5
5
|
|
|
6
|
-
test("
|
|
6
|
+
test("disconnected endpoints keep independent label styles when one is obstructed", () => {
|
|
7
7
|
const inputProblem: InputProblem = {
|
|
8
8
|
chips: [
|
|
9
9
|
{
|
|
@@ -79,6 +79,12 @@ test("two-pin terminal stubs fall back atomically when one endpoint is obstructe
|
|
|
79
79
|
})
|
|
80
80
|
solver.solve()
|
|
81
81
|
|
|
82
|
-
expect(solver.inlineNetLabelPlacements).
|
|
83
|
-
|
|
82
|
+
expect(solver.inlineNetLabelPlacements.map((label) => label.pinIds)).toEqual([
|
|
83
|
+
["U2.1"],
|
|
84
|
+
])
|
|
85
|
+
expect(solver.getOutput().netLabelPlacements).toEqual([
|
|
86
|
+
netLabelPlacements[0]!,
|
|
87
|
+
])
|
|
88
|
+
expect(netLabelPlacements).toHaveLength(2)
|
|
89
|
+
expect(solver.getOutput().traces).toHaveLength(0)
|
|
84
90
|
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
3
|
+
import { generatePerpendicularTraceDetours } from "lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates"
|
|
4
|
+
|
|
5
|
+
test("perpendicular detours keep both terminals for horizontal and vertical crossings", () => {
|
|
6
|
+
const crossings = [
|
|
7
|
+
{
|
|
8
|
+
path: [
|
|
9
|
+
{ x: 0, y: 0 },
|
|
10
|
+
{ x: 4, y: 0 },
|
|
11
|
+
],
|
|
12
|
+
obstacleStart: { x: 2, y: -1 },
|
|
13
|
+
obstacleEnd: { x: 2, y: 1 },
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
path: [
|
|
17
|
+
{ x: 0, y: 0 },
|
|
18
|
+
{ x: 0, y: 4 },
|
|
19
|
+
],
|
|
20
|
+
obstacleStart: { x: -1, y: 2 },
|
|
21
|
+
obstacleEnd: { x: 1, y: 2 },
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
for (const crossing of crossings) {
|
|
26
|
+
const start = crossing.path[0]!
|
|
27
|
+
const end = crossing.path[1]!
|
|
28
|
+
const trace: SolvedTracePath = {
|
|
29
|
+
mspPairId: "signal",
|
|
30
|
+
dcConnNetId: "signal",
|
|
31
|
+
globalConnNetId: "signal",
|
|
32
|
+
pins: [
|
|
33
|
+
{ ...start, pinId: "source", chipId: "source-chip" },
|
|
34
|
+
{ ...end, pinId: "destination", chipId: "destination-chip" },
|
|
35
|
+
],
|
|
36
|
+
pinIds: ["source", "destination"],
|
|
37
|
+
mspConnectionPairIds: ["signal"],
|
|
38
|
+
tracePath: crossing.path,
|
|
39
|
+
}
|
|
40
|
+
const candidates = generatePerpendicularTraceDetours({
|
|
41
|
+
trace,
|
|
42
|
+
segmentIndex: 0,
|
|
43
|
+
obstacleStart: crossing.obstacleStart,
|
|
44
|
+
obstacleEnd: crossing.obstacleEnd,
|
|
45
|
+
chipBounds: [],
|
|
46
|
+
clearance: 0.1,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
expect(candidates).toHaveLength(4)
|
|
50
|
+
for (const candidate of candidates) {
|
|
51
|
+
expect(candidate.path[0]).toEqual(start)
|
|
52
|
+
expect(candidate.path.at(-1)).toEqual(end)
|
|
53
|
+
for (let index = 1; index < candidate.path.length; index++) {
|
|
54
|
+
const previous = candidate.path[index - 1]!
|
|
55
|
+
const current = candidate.path[index]!
|
|
56
|
+
expect(previous.x === current.x || previous.y === current.y).toBe(true)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})
|