@tscircuit/schematic-trace-solver 0.0.173 → 0.0.175
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 +2 -0
- package/dist/index.js +200 -74
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +12 -1
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +16 -1
- package/lib/solvers/InlineNetLabelSolver/restoreReroutesAroundSupersededLabels.ts +174 -0
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +2 -30
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +9 -0
- package/lib/utils/doesPathCoincideWithTraces.ts +17 -4
- package/lib/utils/pathIntersectsRenderedLabel.ts +33 -0
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260831T133233Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260901T055358Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260901T064117Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260901T134241Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +18 -18
- package/tests/bug-reports/bug-report-20260831T133233Z/__snapshots__/bug-report-20260831T133233Z.snap.svg +85 -0
- package/tests/bug-reports/bug-report-20260831T133233Z/bug-report-20260831T133233Z.json +172 -0
- package/tests/bug-reports/bug-report-20260831T133233Z/bug-report-20260831T133233Z.test.ts +25 -0
- package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +676 -0
- package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.json +2467 -0
- package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.test.ts +59 -0
- package/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg +134 -0
- package/tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.json +215 -0
- package/tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260901T134241Z/__snapshots__/bug-report-20260901T134241Z.snap.svg +115 -0
- package/tests/bug-reports/bug-report-20260901T134241Z/bug-report-20260901T134241Z.json +359 -0
- package/tests/bug-reports/bug-report-20260901T134241Z/bug-report-20260901T134241Z.test.ts +12 -0
- package/tests/repros/__snapshots__/repro-bq40z60-current-sense.snap.svg +94 -0
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +10 -10
- package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +87 -87
- package/tests/repros/__snapshots__/repro-tida-01389-hbridge.snap.svg +47 -153
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1625 -1625
- package/tests/repros/__snapshots__/repro-trellis-core-gnd-net-label-overlap.snap.svg +134 -0
- package/tests/repros/__snapshots__/repro-wireless-mouse-charger-section.snap.svg +164 -0
- package/tests/repros/assets/repro-bq40z60-current-sense.input.json +158 -0
- package/tests/repros/assets/repro-tida-01389-hbridge.input.json +67 -657
- package/tests/repros/assets/repro-trellis-core-gnd-net-label-overlap.input.json +178 -0
- package/tests/repros/assets/repro-wireless-mouse-charger-section.input.json +591 -0
- package/tests/repros/repro-bq40z60-current-sense.test.ts +18 -0
- package/tests/repros/repro-tida-01389-hbridge.test.ts +27 -6
- package/tests/repros/repro-trellis-core-gnd-net-label-overlap.test.ts +73 -0
- package/tests/repros/repro-wireless-mouse-charger-section.test.ts +16 -0
- package/tests/solvers/InlineNetLabelSolver/restore-reroutes-around-superseded-labels.test.ts +186 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
4
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
5
|
+
import "tests/fixtures/matcher"
|
|
6
|
+
import inputProblem from "./assets/repro-trellis-core-gnd-net-label-overlap.input.json"
|
|
7
|
+
|
|
8
|
+
const getLabelBounds = (label: NetLabelPlacement) => ({
|
|
9
|
+
minX: label.center.x - label.width / 2,
|
|
10
|
+
maxX: label.center.x + label.width / 2,
|
|
11
|
+
minY: label.center.y - label.height / 2,
|
|
12
|
+
maxY: label.center.y + label.height / 2,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const getOverlapArea = (
|
|
16
|
+
first: ReturnType<typeof getLabelBounds>,
|
|
17
|
+
second: ReturnType<typeof getLabelBounds>,
|
|
18
|
+
) => {
|
|
19
|
+
const width =
|
|
20
|
+
Math.min(first.maxX, second.maxX) - Math.max(first.minX, second.minX)
|
|
21
|
+
const height =
|
|
22
|
+
Math.min(first.maxY, second.maxY) - Math.max(first.minY, second.minY)
|
|
23
|
+
|
|
24
|
+
return width > 0 && height > 0 ? width * height : 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Captured from @tscircuit/core repro175. The dense T113 analog-pin column
|
|
28
|
+
// forces Net_U3F_VRA2 to fall back from its requested y+ rail orientation to
|
|
29
|
+
// the pin-facing x+ orientation. The fallback reports a 0.42 x 0.20 label box
|
|
30
|
+
// even though the rendered text is 1.56 wide. The pipeline then finishes with
|
|
31
|
+
// the U3-side GND label overlapping both VRA2 and P1V8.
|
|
32
|
+
//
|
|
33
|
+
// This test pins the current buggy output. A fix should change the two positive
|
|
34
|
+
// overlap assertions to expect 0 and update the visual snapshot.
|
|
35
|
+
test("repro Trellis Core GND label overlaps VRA2 and P1V8", () => {
|
|
36
|
+
const solver = new SchematicTracePipelineSolver(inputProblem as InputProblem)
|
|
37
|
+
|
|
38
|
+
solver.solve()
|
|
39
|
+
|
|
40
|
+
const labels =
|
|
41
|
+
solver.netLabelNetLabelCollisionSolver!.getOutput().netLabelPlacements
|
|
42
|
+
const vra2Label = labels.find(
|
|
43
|
+
(label) =>
|
|
44
|
+
label.netId === "Net_U3F_VRA2" &&
|
|
45
|
+
label.pinIds.includes("schematic_port_1"),
|
|
46
|
+
)!
|
|
47
|
+
const p1v8Label = labels.find(
|
|
48
|
+
(label) =>
|
|
49
|
+
label.netId === "P1V8" && label.pinIds.includes("schematic_port_0"),
|
|
50
|
+
)!
|
|
51
|
+
const groundLabel = labels.find(
|
|
52
|
+
(label) =>
|
|
53
|
+
label.netId === "GND" &&
|
|
54
|
+
label.pinIds.includes("schematic_port_2") &&
|
|
55
|
+
label.pinIds.includes("schematic_port_4"),
|
|
56
|
+
)!
|
|
57
|
+
|
|
58
|
+
expect(vra2Label).toBeDefined()
|
|
59
|
+
expect(p1v8Label).toBeDefined()
|
|
60
|
+
expect(groundLabel).toBeDefined()
|
|
61
|
+
expect(vra2Label).toMatchObject({
|
|
62
|
+
orientation: "x+",
|
|
63
|
+
width: 0.42,
|
|
64
|
+
height: 0.2,
|
|
65
|
+
})
|
|
66
|
+
expect(
|
|
67
|
+
getOverlapArea(getLabelBounds(groundLabel), getLabelBounds(vra2Label)),
|
|
68
|
+
).toBeGreaterThan(0)
|
|
69
|
+
expect(
|
|
70
|
+
getOverlapArea(getLabelBounds(groundLabel), getLabelBounds(p1v8Label)),
|
|
71
|
+
).toBeGreaterThan(0)
|
|
72
|
+
expect(solver).toMatchSolverSnapshot(import.meta.path)
|
|
73
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
3
|
+
import inputProblem from "./assets/repro-wireless-mouse-charger-section.input.json"
|
|
4
|
+
import "tests/fixtures/matcher"
|
|
5
|
+
|
|
6
|
+
// Generated from @tscircuit/core 0.0.1817 at commit 45888ed648781adfd34e7078c4d4b336133cbbe9.
|
|
7
|
+
// The circuit reproduces the wireless mouse's MCP73831 charger, charge LED
|
|
8
|
+
// loop, programming resistor, VBAT capacitor, and protected battery connector.
|
|
9
|
+
// The fixture is the solverParams payload emitted by core's solver:started event.
|
|
10
|
+
test("repro wireless mouse charger section", () => {
|
|
11
|
+
const solver = new SchematicTracePipelineSolver(inputProblem as any)
|
|
12
|
+
|
|
13
|
+
solver.solve()
|
|
14
|
+
|
|
15
|
+
expect(solver).toMatchSolverSnapshot(import.meta.path)
|
|
16
|
+
})
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { InlineNetLabelPlacement } from "lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver"
|
|
3
|
+
import { restoreReroutesAroundSupersededLabels } from "lib/solvers/InlineNetLabelSolver/restoreReroutesAroundSupersededLabels"
|
|
4
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
6
|
+
import type { CompletedTraceReroute } from "lib/solvers/TraceElbowTransitionSimplificationSolver/types"
|
|
7
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
8
|
+
|
|
9
|
+
const inputProblem: InputProblem = {
|
|
10
|
+
chips: [],
|
|
11
|
+
directConnections: [],
|
|
12
|
+
netConnections: [],
|
|
13
|
+
availableNetLabelOrientations: {},
|
|
14
|
+
textBoxes: [],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const initialPath = [
|
|
18
|
+
{ x: 0, y: 0 },
|
|
19
|
+
{ x: 4, y: 0 },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
const reroutedPath = [
|
|
23
|
+
{ x: 0, y: 0 },
|
|
24
|
+
{ x: 1.8, y: 0 },
|
|
25
|
+
{ x: 1.8, y: 0.5 },
|
|
26
|
+
{ x: 2.2, y: 0.5 },
|
|
27
|
+
{ x: 2.2, y: 0 },
|
|
28
|
+
{ x: 4, y: 0 },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const initialTrace: SolvedTracePath = {
|
|
32
|
+
mspPairId: "ground-trace",
|
|
33
|
+
dcConnNetId: "ground-global",
|
|
34
|
+
globalConnNetId: "ground-global",
|
|
35
|
+
userNetId: "GND",
|
|
36
|
+
pins: [
|
|
37
|
+
{ pinId: "ground-pin-1", chipId: "left", x: 0, y: 0 },
|
|
38
|
+
{ pinId: "ground-pin-2", chipId: "right", x: 4, y: 0 },
|
|
39
|
+
],
|
|
40
|
+
tracePath: initialPath,
|
|
41
|
+
mspConnectionPairIds: ["ground-trace"],
|
|
42
|
+
pinIds: ["ground-pin-1", "ground-pin-2"],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const supersededLabel: NetLabelPlacement = {
|
|
46
|
+
globalConnNetId: "label-global",
|
|
47
|
+
dcConnNetId: "label-global",
|
|
48
|
+
netId: "CC2",
|
|
49
|
+
mspConnectionPairIds: ["label-host"],
|
|
50
|
+
pinIds: ["label-pin-1", "label-pin-2"],
|
|
51
|
+
orientation: "y+",
|
|
52
|
+
anchorPoint: { x: 2, y: -0.2 },
|
|
53
|
+
center: { x: 2, y: 0 },
|
|
54
|
+
width: 0.2,
|
|
55
|
+
height: 0.4,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const clearInlineLabel: InlineNetLabelPlacement = {
|
|
59
|
+
globalConnNetId: "label-global",
|
|
60
|
+
netId: "CC2",
|
|
61
|
+
mspPairId: "label-host",
|
|
62
|
+
pinIds: ["label-pin-1", "label-pin-2"],
|
|
63
|
+
axis: "x",
|
|
64
|
+
anchorPoint: { x: 2, y: -0.5 },
|
|
65
|
+
center: { x: 2, y: -0.6 },
|
|
66
|
+
width: 0.4,
|
|
67
|
+
height: 0.1,
|
|
68
|
+
side: "y-",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const completedReroute: CompletedTraceReroute = {
|
|
72
|
+
initialTrace,
|
|
73
|
+
reroutedTracePath: reroutedPath,
|
|
74
|
+
label: supersededLabel,
|
|
75
|
+
detourCount: 0,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const makeReroutedTrace = (tracePath = reroutedPath): SolvedTracePath => ({
|
|
79
|
+
...initialTrace,
|
|
80
|
+
tracePath,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const restore = ({
|
|
84
|
+
traces = [makeReroutedTrace()],
|
|
85
|
+
netLabelPlacements = [],
|
|
86
|
+
inlineNetLabelPlacements = [clearInlineLabel],
|
|
87
|
+
}: {
|
|
88
|
+
traces?: SolvedTracePath[]
|
|
89
|
+
netLabelPlacements?: NetLabelPlacement[]
|
|
90
|
+
inlineNetLabelPlacements?: InlineNetLabelPlacement[]
|
|
91
|
+
} = {}) =>
|
|
92
|
+
restoreReroutesAroundSupersededLabels({
|
|
93
|
+
inputProblem,
|
|
94
|
+
traces,
|
|
95
|
+
netLabelPlacements,
|
|
96
|
+
inlineNetLabelPlacements,
|
|
97
|
+
completedReroutes: [completedReroute],
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test("restores the exact route after its blocking label is superseded", () => {
|
|
101
|
+
const result = restore()
|
|
102
|
+
|
|
103
|
+
expect(result.restoredTraceCount).toBe(1)
|
|
104
|
+
expect(result.traces[0]!.tracePath).toEqual(initialPath)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test("keeps the detour when inline conversion did not supersede the label", () => {
|
|
108
|
+
const result = restore({ inlineNetLabelPlacements: [] })
|
|
109
|
+
|
|
110
|
+
expect(result.restoredTraceCount).toBe(0)
|
|
111
|
+
expect(result.traces[0]!.tracePath).toEqual(reroutedPath)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test("keeps the detour when the final inline label occupies the old route", () => {
|
|
115
|
+
const result = restore({
|
|
116
|
+
inlineNetLabelPlacements: [
|
|
117
|
+
{
|
|
118
|
+
...clearInlineLabel,
|
|
119
|
+
anchorPoint: { x: 2, y: 0 },
|
|
120
|
+
center: { x: 2, y: 0 },
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
expect(result.restoredTraceCount).toBe(0)
|
|
126
|
+
expect(result.traces[0]!.tracePath).toEqual(reroutedPath)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test("keeps the detour when another trace crosses the old route", () => {
|
|
130
|
+
const blockingTrace: SolvedTracePath = {
|
|
131
|
+
...initialTrace,
|
|
132
|
+
mspPairId: "blocking-trace",
|
|
133
|
+
dcConnNetId: "blocking-global",
|
|
134
|
+
globalConnNetId: "blocking-global",
|
|
135
|
+
userNetId: "BLOCKING",
|
|
136
|
+
tracePath: [
|
|
137
|
+
{ x: 2, y: -1 },
|
|
138
|
+
{ x: 2, y: 1 },
|
|
139
|
+
],
|
|
140
|
+
}
|
|
141
|
+
const result = restore({ traces: [makeReroutedTrace(), blockingTrace] })
|
|
142
|
+
|
|
143
|
+
expect(result.restoredTraceCount).toBe(0)
|
|
144
|
+
expect(result.traces[0]!.tracePath).toEqual(reroutedPath)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test("allows an unchanged pre-existing trace crossing", () => {
|
|
148
|
+
const existingCrossing: SolvedTracePath = {
|
|
149
|
+
...initialTrace,
|
|
150
|
+
mspPairId: "existing-crossing",
|
|
151
|
+
dcConnNetId: "existing-crossing-global",
|
|
152
|
+
globalConnNetId: "existing-crossing-global",
|
|
153
|
+
userNetId: "EXISTING_CROSSING",
|
|
154
|
+
tracePath: [
|
|
155
|
+
{ x: 1, y: -1 },
|
|
156
|
+
{ x: 1, y: 1 },
|
|
157
|
+
],
|
|
158
|
+
}
|
|
159
|
+
const result = restore({ traces: [makeReroutedTrace(), existingCrossing] })
|
|
160
|
+
|
|
161
|
+
expect(result.restoredTraceCount).toBe(1)
|
|
162
|
+
expect(result.traces[0]!.tracePath).toEqual(initialPath)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test("keeps the detour when a retained anchored label occupies the old route", () => {
|
|
166
|
+
const retainedLabel: NetLabelPlacement = {
|
|
167
|
+
...supersededLabel,
|
|
168
|
+
globalConnNetId: "retained-label-global",
|
|
169
|
+
dcConnNetId: "retained-label-global",
|
|
170
|
+
netId: "RETAINED",
|
|
171
|
+
}
|
|
172
|
+
const result = restore({ netLabelPlacements: [retainedLabel] })
|
|
173
|
+
|
|
174
|
+
expect(result.restoredTraceCount).toBe(0)
|
|
175
|
+
expect(result.traces[0]!.tracePath).toEqual(reroutedPath)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test("does not overwrite a trace changed after the recorded reroute", () => {
|
|
179
|
+
const changedPath = reroutedPath.map((point) => ({ ...point }))
|
|
180
|
+
changedPath[2]!.y = 0.6
|
|
181
|
+
changedPath[3]!.y = 0.6
|
|
182
|
+
const result = restore({ traces: [makeReroutedTrace(changedPath)] })
|
|
183
|
+
|
|
184
|
+
expect(result.restoredTraceCount).toBe(0)
|
|
185
|
+
expect(result.traces[0]!.tracePath).toEqual(changedPath)
|
|
186
|
+
})
|