@tscircuit/schematic-trace-solver 0.0.159 → 0.0.160

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.
@@ -1,12 +1,77 @@
1
1
  import { expect, test } from "bun:test"
2
+ import { estimateInlineNetLabelWidth } from "lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver"
2
3
  import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
4
+ import type { InputProblem } from "lib/types/InputProblem"
3
5
  import inputProblem from "./bug-report-20260826T072956Z.json"
4
6
  import "tests/fixtures/matcher"
5
7
 
8
+ const multiPinSignalNetIds = [
9
+ "ISNS_A",
10
+ "ISNS_B",
11
+ "ISNS_C",
12
+ "MOT_A",
13
+ "MOT_B",
14
+ "MOT_C",
15
+ "S1_N",
16
+ "S1_P",
17
+ "S2_N",
18
+ "S2_P",
19
+ "S3_N",
20
+ "S3_P",
21
+ "SL_A",
22
+ "SL_B",
23
+ "SL_C",
24
+ ]
25
+
6
26
  test("bug-report-20260826T072956Z", () => {
7
- const solver = new SchematicTracePipelineSolver(inputProblem as any)
27
+ const problem = structuredClone(inputProblem) as unknown as InputProblem
28
+ for (const connection of problem.netConnections) {
29
+ if (!multiPinSignalNetIds.includes(connection.netId)) continue
30
+ connection.allowInlineNetLabel = true
31
+ connection.inlineNetLabelHeight = 0.12
32
+ connection.inlineNetLabelWidth = estimateInlineNetLabelWidth(
33
+ connection.netId,
34
+ connection.inlineNetLabelHeight,
35
+ )
36
+ }
37
+
38
+ const solver = new SchematicTracePipelineSolver(problem, {
39
+ hideRatsNet: true,
40
+ })
8
41
 
9
42
  solver.solve()
10
43
 
44
+ const output = solver.inlineNetLabelSolver!.getOutput()
45
+ const expectedInlineNetIds = ["GH_B", ...multiPinSignalNetIds]
46
+ expect(
47
+ new Set(
48
+ output.inlineNetLabelPlacements
49
+ .filter((placement) =>
50
+ expectedInlineNetIds.includes(placement.netId ?? ""),
51
+ )
52
+ .map((placement) => placement.netId),
53
+ ),
54
+ ).toEqual(new Set(expectedInlineNetIds))
55
+ expect(
56
+ output.netLabelPlacements.filter((placement) =>
57
+ expectedInlineNetIds.includes(placement.netId ?? ""),
58
+ ),
59
+ ).toHaveLength(0)
60
+ expect(
61
+ output.inlineNetLabelPlacements.some(
62
+ (placement) => placement.netId === "GH_B" && placement.side === "y-",
63
+ ),
64
+ ).toBe(true)
65
+ for (const supersededConnectorTraceId of [
66
+ "schematic_port_152-schematic_port_150",
67
+ "schematic_port_151-schematic_port_148",
68
+ "schematic_port_149-schematic_port_147",
69
+ ]) {
70
+ expect(
71
+ output.traces.some(
72
+ (trace) => trace.mspPairId === supersededConnectorTraceId,
73
+ ),
74
+ ).toBe(false)
75
+ }
11
76
  expect(solver).toMatchSolverSnapshot(import.meta.path)
12
77
  })
@@ -37,6 +37,13 @@ test("routes the usb power vbus connection without a label detour", () => {
37
37
  label.pinIds.includes("schematic_port_54") &&
38
38
  label.pinIds.includes("schematic_port_162"),
39
39
  )
40
+ const inlineOutput = solver.inlineNetLabelSolver!.getOutput()
41
+ const usbHighSpeedInlineLabels = inlineOutput.inlineNetLabelPlacements.filter(
42
+ (placement) => placement.netId?.startsWith("USB_HS_"),
43
+ )
44
+ const usbHighSpeedAnchoredLabels = inlineOutput.netLabelPlacements.filter(
45
+ (placement) => placement.netId?.startsWith("USB_HS_"),
46
+ )
40
47
 
41
48
  expect(gndLabel?.orientation).toBe("y-")
42
49
  expect(gndLabel?.anchorPoint.x).toBeCloseTo(-2.34)
@@ -53,5 +60,12 @@ test("routes the usb power vbus connection without a label detour", () => {
53
60
  { x: 12.8, y: -5.8 },
54
61
  { x: 13, y: -5.8 },
55
62
  ])
63
+ // The available inline side of USB_HS_DM overlaps the retained USB_HS_DP
64
+ // tag. Keep both differential-pair nets anchored instead of producing a
65
+ // mixed, overlapping representation.
66
+ expect(usbHighSpeedInlineLabels).toHaveLength(0)
67
+ expect(
68
+ usbHighSpeedAnchoredLabels.map((placement) => placement.netId).sort(),
69
+ ).toEqual(["USB_HS_DM", "USB_HS_DM", "USB_HS_DP", "USB_HS_DP"])
56
70
  expect(solver).toMatchSolverSnapshot(import.meta.path)
57
71
  })
@@ -0,0 +1,252 @@
1
+ import { expect, test } from "bun:test"
2
+ import { InlineNetLabelSolver } from "lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver"
3
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import type { InputProblem } from "lib/types/InputProblem"
6
+
7
+ test("a multi-pin net labels routed and disconnected components inline", () => {
8
+ const inputProblem: InputProblem = {
9
+ chips: [
10
+ {
11
+ chipId: "J1",
12
+ center: { x: 0, y: 0 },
13
+ width: 1,
14
+ height: 1,
15
+ pins: [
16
+ {
17
+ pinId: "J1.1",
18
+ x: -0.5,
19
+ y: 0.2,
20
+ _facingDirection: "x-",
21
+ },
22
+ {
23
+ pinId: "J1.2",
24
+ x: -0.5,
25
+ y: -0.2,
26
+ _facingDirection: "x-",
27
+ },
28
+ ],
29
+ },
30
+ {
31
+ chipId: "U1",
32
+ center: { x: 2, y: 0 },
33
+ width: 1,
34
+ height: 1,
35
+ pins: [
36
+ {
37
+ pinId: "U1.1",
38
+ x: 2.5,
39
+ y: 0,
40
+ _facingDirection: "x+",
41
+ },
42
+ ],
43
+ },
44
+ ],
45
+ directConnections: [],
46
+ netConnections: [
47
+ {
48
+ netId: "MOTOR",
49
+ pinIds: ["J1.1", "J1.2", "U1.1"],
50
+ allowInlineNetLabel: true,
51
+ inlineNetLabelWidth: 0.6,
52
+ inlineNetLabelHeight: 0.12,
53
+ },
54
+ ],
55
+ availableNetLabelOrientations: { MOTOR: ["x-", "x+"] },
56
+ }
57
+ const shortConnectorTrace: SolvedTracePath = {
58
+ mspPairId: "J1.1-J1.2",
59
+ mspConnectionPairIds: ["J1.1-J1.2"],
60
+ dcConnNetId: "MOTOR",
61
+ globalConnNetId: "MOTOR",
62
+ userNetId: "MOTOR",
63
+ pins: [
64
+ { pinId: "J1.1", chipId: "J1", x: -0.5, y: 0.2 },
65
+ { pinId: "J1.2", chipId: "J1", x: -0.5, y: -0.2 },
66
+ ],
67
+ pinIds: ["J1.1", "J1.2"],
68
+ tracePath: [
69
+ { x: -0.5, y: 0.2 },
70
+ { x: -0.8, y: 0.2 },
71
+ { x: -0.8, y: -0.2 },
72
+ { x: -0.5, y: -0.2 },
73
+ ],
74
+ }
75
+
76
+ const solver = new InlineNetLabelSolver({
77
+ inputProblem,
78
+ traces: [shortConnectorTrace],
79
+ netLabelPlacements: [],
80
+ })
81
+ solver.solve()
82
+
83
+ const output = solver.getOutput()
84
+ expect(output.inlineNetLabelPlacements).toHaveLength(3)
85
+ expect(
86
+ output.inlineNetLabelPlacements.every(
87
+ (placement) => placement.stubTracePath !== undefined,
88
+ ),
89
+ ).toBe(true)
90
+ expect(output.traces).toHaveLength(0)
91
+ expect(output.netLabelPlacements).toHaveLength(0)
92
+ })
93
+
94
+ test("a short routed component is retained when replacing it would strand an intermediate pin", () => {
95
+ const inputProblem: InputProblem = {
96
+ chips: [
97
+ {
98
+ chipId: "J1",
99
+ center: { x: 0, y: 0 },
100
+ width: 1,
101
+ height: 1,
102
+ pins: [
103
+ { pinId: "J1.1", x: -0.5, y: 0.2, _facingDirection: "x-" },
104
+ { pinId: "J1.2", x: -0.5, y: -0.2, _facingDirection: "x-" },
105
+ ],
106
+ },
107
+ {
108
+ chipId: "U1",
109
+ center: { x: 2, y: 0 },
110
+ width: 1,
111
+ height: 1,
112
+ pins: [{ pinId: "U1.1", x: 2.5, y: 0, _facingDirection: "x+" }],
113
+ },
114
+ ],
115
+ directConnections: [{ pinIds: ["J1.1", "J1.2"] }],
116
+ netConnections: [
117
+ {
118
+ netId: "MOTOR",
119
+ pinIds: ["J1.1", "U1.1"],
120
+ allowInlineNetLabel: true,
121
+ inlineNetLabelWidth: 0.6,
122
+ inlineNetLabelHeight: 0.12,
123
+ },
124
+ ],
125
+ availableNetLabelOrientations: { MOTOR: ["x-", "x+"] },
126
+ }
127
+ const shortTrace: SolvedTracePath = {
128
+ mspPairId: "J1.1-J1.2",
129
+ mspConnectionPairIds: ["J1.1-J1.2"],
130
+ dcConnNetId: "MOTOR",
131
+ globalConnNetId: "MOTOR",
132
+ userNetId: "MOTOR",
133
+ pins: [
134
+ { pinId: "J1.1", chipId: "J1", x: -0.5, y: 0.2 },
135
+ { pinId: "J1.2", chipId: "J1", x: -0.5, y: -0.2 },
136
+ ],
137
+ pinIds: ["J1.1", "J1.2"],
138
+ tracePath: [
139
+ { x: -0.5, y: 0.2 },
140
+ { x: -0.8, y: 0.2 },
141
+ { x: -0.8, y: -0.2 },
142
+ { x: -0.5, y: -0.2 },
143
+ ],
144
+ }
145
+ const anchoredLabels: NetLabelPlacement[] = [
146
+ {
147
+ globalConnNetId: "MOTOR",
148
+ netId: "MOTOR",
149
+ mspConnectionPairIds: ["J1.1-J1.2"],
150
+ pinIds: ["J1.1", "J1.2"],
151
+ orientation: "x-",
152
+ anchorPoint: { x: -0.8, y: 0 },
153
+ center: { x: -1.2, y: 0 },
154
+ width: 0.6,
155
+ height: 0.12,
156
+ },
157
+ {
158
+ globalConnNetId: "MOTOR",
159
+ netId: "MOTOR",
160
+ mspConnectionPairIds: [],
161
+ pinIds: ["U1.1"],
162
+ orientation: "x+",
163
+ anchorPoint: { x: 2.5, y: 0 },
164
+ center: { x: 2.9, y: 0 },
165
+ width: 0.6,
166
+ height: 0.12,
167
+ },
168
+ ]
169
+
170
+ const solver = new InlineNetLabelSolver({
171
+ inputProblem,
172
+ traces: [shortTrace],
173
+ netLabelPlacements: anchoredLabels,
174
+ })
175
+ solver.solve()
176
+
177
+ const output = solver.getOutput()
178
+ expect(output.inlineNetLabelPlacements).toHaveLength(0)
179
+ expect(output.traces).toEqual([shortTrace])
180
+ expect(output.netLabelPlacements).toEqual(anchoredLabels)
181
+ })
182
+
183
+ test("a generated anchored-label connector becomes an inline terminal stub", () => {
184
+ const inputProblem: InputProblem = {
185
+ chips: [
186
+ {
187
+ chipId: "U1",
188
+ center: { x: 0, y: 0 },
189
+ width: 1,
190
+ height: 1,
191
+ pins: [{ pinId: "U1.1", x: -0.5, y: 0, _facingDirection: "x-" }],
192
+ },
193
+ {
194
+ chipId: "U2",
195
+ center: { x: 3, y: 0 },
196
+ width: 1,
197
+ height: 1,
198
+ pins: [{ pinId: "U2.1", x: 2.5, y: 0, _facingDirection: "x-" }],
199
+ },
200
+ {
201
+ chipId: "U3",
202
+ center: { x: 6, y: 0 },
203
+ width: 1,
204
+ height: 1,
205
+ pins: [{ pinId: "U3.1", x: 5.5, y: 0, _facingDirection: "x-" }],
206
+ },
207
+ ],
208
+ directConnections: [],
209
+ netConnections: [
210
+ {
211
+ netId: "SIGNAL",
212
+ pinIds: ["U1.1", "U2.1", "U3.1"],
213
+ allowInlineNetLabel: true,
214
+ inlineNetLabelWidth: 0.6,
215
+ inlineNetLabelHeight: 0.12,
216
+ },
217
+ ],
218
+ availableNetLabelOrientations: { SIGNAL: ["x-"] },
219
+ }
220
+ const generatedLabelConnector: SolvedTracePath = {
221
+ mspPairId: "available-net-orientation-0-SIGNAL",
222
+ mspConnectionPairIds: ["available-net-orientation-0-SIGNAL"],
223
+ dcConnNetId: "SIGNAL",
224
+ globalConnNetId: "SIGNAL",
225
+ userNetId: "SIGNAL",
226
+ pins: [
227
+ { pinId: "U1.1", chipId: "U1", x: -0.5, y: 0 },
228
+ { pinId: "U1.1", chipId: "U1", x: -0.5, y: 0 },
229
+ ],
230
+ pinIds: ["U1.1"],
231
+ tracePath: [
232
+ { x: -0.5, y: 0 },
233
+ { x: -1.5, y: 0 },
234
+ ],
235
+ }
236
+
237
+ const solver = new InlineNetLabelSolver({
238
+ inputProblem,
239
+ traces: [generatedLabelConnector],
240
+ netLabelPlacements: [],
241
+ })
242
+ solver.solve()
243
+
244
+ const output = solver.getOutput()
245
+ expect(output.inlineNetLabelPlacements).toHaveLength(3)
246
+ expect(
247
+ output.inlineNetLabelPlacements.every(
248
+ (placement) => placement.stubTracePath !== undefined,
249
+ ),
250
+ ).toBe(true)
251
+ expect(output.traces).toHaveLength(0)
252
+ })
@@ -0,0 +1,71 @@
1
+ import { expect, test } from "bun:test"
2
+ import { InlineNetLabelSolver } from "lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import type { InputProblem } from "lib/types/InputProblem"
5
+
6
+ test("an inline label tries the opposite side when the preferred side is blocked", () => {
7
+ const inputProblem: InputProblem = {
8
+ chips: [
9
+ {
10
+ chipId: "U1",
11
+ center: { x: -2, y: 0 },
12
+ width: 1,
13
+ height: 1,
14
+ pins: [{ pinId: "U1.1", x: -1.5, y: 0 }],
15
+ },
16
+ {
17
+ chipId: "U2",
18
+ center: { x: 2, y: 0 },
19
+ width: 1,
20
+ height: 1,
21
+ pins: [{ pinId: "U2.1", x: 1.5, y: 0 }],
22
+ },
23
+ ],
24
+ directConnections: [
25
+ {
26
+ netId: "SIGNAL",
27
+ pinIds: ["U1.1", "U2.1"],
28
+ allowInlineNetLabel: true,
29
+ inlineNetLabelWidth: 0.8,
30
+ inlineNetLabelHeight: 0.12,
31
+ },
32
+ ],
33
+ netConnections: [],
34
+ textBoxes: [
35
+ {
36
+ center: { x: 0, y: 0.11 },
37
+ width: 3,
38
+ height: 0.12,
39
+ text: "preferred-side obstacle",
40
+ },
41
+ ],
42
+ availableNetLabelOrientations: { SIGNAL: ["x-", "x+"] },
43
+ }
44
+ const trace: SolvedTracePath = {
45
+ mspPairId: "U1.1-U2.1",
46
+ mspConnectionPairIds: ["U1.1-U2.1"],
47
+ dcConnNetId: "SIGNAL",
48
+ globalConnNetId: "SIGNAL",
49
+ userNetId: "SIGNAL",
50
+ pins: [
51
+ { pinId: "U1.1", chipId: "U1", x: -1.5, y: 0 },
52
+ { pinId: "U2.1", chipId: "U2", x: 1.5, y: 0 },
53
+ ],
54
+ pinIds: ["U1.1", "U2.1"],
55
+ tracePath: [
56
+ { x: -1.5, y: 0 },
57
+ { x: 1.5, y: 0 },
58
+ ],
59
+ }
60
+
61
+ const solver = new InlineNetLabelSolver({
62
+ inputProblem,
63
+ traces: [trace],
64
+ netLabelPlacements: [],
65
+ })
66
+ solver.solve()
67
+
68
+ expect(solver.inlineNetLabelPlacements).toHaveLength(1)
69
+ expect(solver.inlineNetLabelPlacements[0]!.side).toBe("y-")
70
+ expect(solver.inlineNetLabelPlacements[0]!.center.y).toBeLessThan(0)
71
+ })
@@ -11,9 +11,15 @@ test("pushes a regular endpoint label and its wick past nearby inline text", ()
11
11
  {
12
12
  chipId: "U1",
13
13
  center: { x: 0, y: 0 },
14
- width: 1,
14
+ // The routing box includes component text, so the real pins can sit
15
+ // inside it. A generated connector may legitimately cross that
16
+ // expanded owner box on its way out from the pin.
17
+ width: 2,
15
18
  height: 1,
16
- pins: [],
19
+ pins: [
20
+ { pinId: "U1.1", x: -0.5, y: 0.2, _facingDirection: "x-" },
21
+ { pinId: "U1.2", x: -0.5, y: 0, _facingDirection: "x-" },
22
+ ],
17
23
  },
18
24
  ],
19
25
  directConnections: [],
@@ -0,0 +1,91 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { InlineNetLabelPlacement } from "lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver"
3
+ import { pushInlineTerminalLabelsAwayFromAnchoredLabels } from "lib/solvers/InlineNetLabelSolver/pushInlineTerminalLabelsAwayFromAnchoredLabels"
4
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
5
+ import type { InputProblem } from "lib/types/InputProblem"
6
+
7
+ test("moves an inline terminal row outward past a retained anchored label", () => {
8
+ const inputProblem: InputProblem = {
9
+ chips: [
10
+ {
11
+ chipId: "J1",
12
+ center: { x: 0, y: 0 },
13
+ width: 1,
14
+ height: 1,
15
+ pins: [
16
+ { pinId: "J1.A0", x: -0.5, y: 0.2, _facingDirection: "x-" },
17
+ { pinId: "J1.SCLK", x: -0.5, y: 0, _facingDirection: "x-" },
18
+ { pinId: "J1.MISO", x: -0.5, y: -0.2, _facingDirection: "x-" },
19
+ ],
20
+ },
21
+ ],
22
+ directConnections: [],
23
+ netConnections: [],
24
+ availableNetLabelOrientations: {},
25
+ }
26
+ const anchoredNetLabelPlacements: NetLabelPlacement[] = [
27
+ {
28
+ globalConnNetId: "A0",
29
+ netId: "A0",
30
+ mspConnectionPairIds: [],
31
+ pinIds: ["J1.A0"],
32
+ orientation: "x-",
33
+ anchorPoint: { x: -0.5, y: 0.2 },
34
+ center: { x: -0.75, y: 0.2 },
35
+ width: 0.5,
36
+ height: 0.2,
37
+ },
38
+ ]
39
+ const inlineNetLabelPlacements: InlineNetLabelPlacement[] = [
40
+ {
41
+ globalConnNetId: "SCLK",
42
+ netId: "SCLK",
43
+ pinIds: ["J1.SCLK"],
44
+ stubTracePath: [
45
+ { x: -0.5, y: 0 },
46
+ { x: -1.1, y: 0 },
47
+ ],
48
+ axis: "x",
49
+ anchorPoint: { x: -0.8, y: 0 },
50
+ center: { x: -0.8, y: 0.11 },
51
+ width: 0.4,
52
+ height: 0.12,
53
+ side: "y+",
54
+ },
55
+ {
56
+ globalConnNetId: "MISO",
57
+ netId: "MISO",
58
+ pinIds: ["J1.MISO"],
59
+ stubTracePath: [
60
+ { x: -0.5, y: -0.2 },
61
+ { x: -1.1, y: -0.2 },
62
+ ],
63
+ axis: "x",
64
+ anchorPoint: { x: -0.8, y: -0.2 },
65
+ center: { x: -0.8, y: -0.09 },
66
+ width: 0.4,
67
+ height: 0.12,
68
+ side: "y+",
69
+ },
70
+ ]
71
+
72
+ const output = pushInlineTerminalLabelsAwayFromAnchoredLabels({
73
+ inputProblem,
74
+ traces: [],
75
+ anchoredNetLabelPlacements,
76
+ inlineNetLabelPlacements,
77
+ })
78
+
79
+ expect(output.movedGroupCount).toBe(1)
80
+ expect(output.inlineNetLabelPlacements[0]!.center.x).toBeLessThan(-0.8)
81
+ expect(output.inlineNetLabelPlacements[1]!.center.x).toBe(
82
+ output.inlineNetLabelPlacements[0]!.center.x,
83
+ )
84
+ expect(output.inlineNetLabelPlacements[0]!.stubTracePath![0]).toEqual({
85
+ x: -0.5,
86
+ y: 0,
87
+ })
88
+ expect(output.inlineNetLabelPlacements[0]!.stubTracePath![1]!.x).toBeLessThan(
89
+ -1.1,
90
+ )
91
+ })
@@ -36,7 +36,13 @@ test("two-pin terminal stubs fall back atomically when one endpoint is obstructe
36
36
  center: { x: -0.85, y: 0.11 },
37
37
  width: 1.1,
38
38
  height: 0.12,
39
- text: "obstacle",
39
+ text: "upper obstacle",
40
+ },
41
+ {
42
+ center: { x: -0.85, y: -0.11 },
43
+ width: 1.1,
44
+ height: 0.12,
45
+ text: "lower obstacle",
40
46
  },
41
47
  ],
42
48
  availableNetLabelOrientations: { NET_SIGNAL: ["x-", "x+"] },