@tscircuit/schematic-trace-solver 0.0.188 → 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.
@@ -0,0 +1,349 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_39",
5
+ "center": {
6
+ "x": -30,
7
+ "y": 0
8
+ },
9
+ "width": 2.1000000000000014,
10
+ "height": 1,
11
+ "pins": [
12
+ {
13
+ "pinId": "schematic_port_159",
14
+ "x": -31.05,
15
+ "y": 0.4
16
+ },
17
+ {
18
+ "pinId": "schematic_port_160",
19
+ "x": -31.05,
20
+ "y": 0.2
21
+ },
22
+ {
23
+ "pinId": "schematic_port_161",
24
+ "x": -31.05,
25
+ "y": 0
26
+ },
27
+ {
28
+ "pinId": "schematic_port_162",
29
+ "x": -31.05,
30
+ "y": -0.20000000000000007
31
+ },
32
+ {
33
+ "pinId": "schematic_port_163",
34
+ "x": -31.05,
35
+ "y": -0.4
36
+ },
37
+ {
38
+ "pinId": "schematic_port_164",
39
+ "x": -28.95,
40
+ "y": -0.30000000000000004
41
+ },
42
+ {
43
+ "pinId": "schematic_port_165",
44
+ "x": -28.95,
45
+ "y": -0.10000000000000003
46
+ },
47
+ {
48
+ "pinId": "schematic_port_166",
49
+ "x": -28.95,
50
+ "y": 0.09999999999999998
51
+ },
52
+ {
53
+ "pinId": "schematic_port_167",
54
+ "x": -28.95,
55
+ "y": 0.30000000000000004
56
+ }
57
+ ],
58
+ "sectionId": "Motors"
59
+ },
60
+ {
61
+ "chipId": "schematic_component_40",
62
+ "center": {
63
+ "x": -30,
64
+ "y": 4
65
+ },
66
+ "width": 1.2000000000000028,
67
+ "height": 0.5999999999999996,
68
+ "pins": [
69
+ {
70
+ "pinId": "schematic_port_168",
71
+ "x": -29.4,
72
+ "y": 4.1
73
+ },
74
+ {
75
+ "pinId": "schematic_port_169",
76
+ "x": -29.4,
77
+ "y": 3.9
78
+ }
79
+ ],
80
+ "sectionId": "Motors"
81
+ },
82
+ {
83
+ "chipId": "schematic_component_41",
84
+ "center": {
85
+ "x": -32.8675,
86
+ "y": -4
87
+ },
88
+ "width": 1.1650000000000063,
89
+ "height": 0.7600000000000011,
90
+ "pins": [
91
+ {
92
+ "pinId": "schematic_port_170",
93
+ "x": -33,
94
+ "y": -3.619999999999999,
95
+ "_facingDirection": "y+"
96
+ },
97
+ {
98
+ "pinId": "schematic_port_171",
99
+ "x": -33,
100
+ "y": -4.380000000000001,
101
+ "_facingDirection": "y-"
102
+ }
103
+ ],
104
+ "sectionId": "Motors"
105
+ },
106
+ {
107
+ "chipId": "schematic_component_42",
108
+ "center": {
109
+ "x": -29.8075,
110
+ "y": -4
111
+ },
112
+ "width": 1.2849999999999966,
113
+ "height": 0.7600000000000011,
114
+ "pins": [
115
+ {
116
+ "pinId": "schematic_port_172",
117
+ "x": -30,
118
+ "y": -3.619999999999999,
119
+ "_facingDirection": "y+"
120
+ },
121
+ {
122
+ "pinId": "schematic_port_173",
123
+ "x": -30,
124
+ "y": -4.380000000000001,
125
+ "_facingDirection": "y-"
126
+ }
127
+ ],
128
+ "sectionId": "Motors"
129
+ }
130
+ ],
131
+ "directConnections": [],
132
+ "netConnections": [
133
+ {
134
+ "netId": "V3V3",
135
+ "netLabelWidth": 0.42,
136
+ "netLabelHeight": 0.6,
137
+ "pinIds": [
138
+ "schematic_port_166",
139
+ "schematic_port_172"
140
+ ]
141
+ },
142
+ {
143
+ "netId": "GND",
144
+ "netLabelWidth": 0.42,
145
+ "netLabelHeight": 0.48,
146
+ "pinIds": [
147
+ "schematic_port_162",
148
+ "schematic_port_167",
149
+ "schematic_port_171",
150
+ "schematic_port_173"
151
+ ]
152
+ },
153
+ {
154
+ "netId": "MA1",
155
+ "netLabelWidth": 0.48,
156
+ "pinIds": [
157
+ "schematic_port_164"
158
+ ]
159
+ },
160
+ {
161
+ "netId": "MA2",
162
+ "netLabelWidth": 0.48,
163
+ "pinIds": [
164
+ "schematic_port_163"
165
+ ]
166
+ },
167
+ {
168
+ "netId": "MOTOR_EN",
169
+ "netLabelWidth": 1.08,
170
+ "pinIds": [
171
+ "schematic_port_165"
172
+ ]
173
+ },
174
+ {
175
+ "netId": "VM",
176
+ "netLabelWidth": 0.42,
177
+ "netLabelHeight": 0.36,
178
+ "pinIds": [
179
+ "schematic_port_159",
180
+ "schematic_port_170"
181
+ ]
182
+ },
183
+ {
184
+ "netId": "OUT0A",
185
+ "netLabelWidth": 0.72,
186
+ "pinIds": [
187
+ "schematic_port_160",
188
+ "schematic_port_168"
189
+ ]
190
+ },
191
+ {
192
+ "netId": "OUT0B",
193
+ "netLabelWidth": 0.72,
194
+ "pinIds": [
195
+ "schematic_port_161",
196
+ "schematic_port_169"
197
+ ]
198
+ }
199
+ ],
200
+ "textBoxes": [
201
+ {
202
+ "chipId": "schematic_component_39",
203
+ "center": {
204
+ "x": -29.99,
205
+ "y": -0.63
206
+ },
207
+ "width": 1.3200000000000003,
208
+ "height": 0.17999999999999994,
209
+ "text": "DRV8837DSGR"
210
+ },
211
+ {
212
+ "chipId": "schematic_component_39",
213
+ "center": {
214
+ "x": -30.409999999999997,
215
+ "y": 0.615
216
+ },
217
+ "width": 0.5999999999999979,
218
+ "height": 0.25,
219
+ "text": "U_M0"
220
+ },
221
+ {
222
+ "chipId": "schematic_component_40",
223
+ "center": {
224
+ "x": -29.96,
225
+ "y": 4.414999999999999
226
+ },
227
+ "width": 0.5999999999999979,
228
+ "height": 0.2499999999999991,
229
+ "text": "J_M0"
230
+ }
231
+ ],
232
+ "availableNetLabelOrientations": {
233
+ "VM": [
234
+ "y+"
235
+ ],
236
+ "VSERVO": [
237
+ "y+"
238
+ ],
239
+ "V3V3": [
240
+ "y+"
241
+ ],
242
+ "VBUS": [
243
+ "y+"
244
+ ],
245
+ "GND": [
246
+ "y-"
247
+ ],
248
+ "MA1": [
249
+ "x-",
250
+ "x+"
251
+ ],
252
+ "MA2": [
253
+ "x-",
254
+ "x+"
255
+ ],
256
+ "MB1": [
257
+ "x-",
258
+ "x+"
259
+ ],
260
+ "MB2": [
261
+ "x-",
262
+ "x+"
263
+ ],
264
+ "EA": [
265
+ "x-",
266
+ "x+"
267
+ ],
268
+ "EB": [
269
+ "x-",
270
+ "x+"
271
+ ],
272
+ "EC": [
273
+ "x-",
274
+ "x+"
275
+ ],
276
+ "ED": [
277
+ "x-",
278
+ "x+"
279
+ ],
280
+ "SDA": [
281
+ "x-",
282
+ "x+"
283
+ ],
284
+ "SCL": [
285
+ "x-",
286
+ "x+"
287
+ ],
288
+ "SERVO1": [
289
+ "x-",
290
+ "x+"
291
+ ],
292
+ "SERVO2": [
293
+ "x-",
294
+ "x+"
295
+ ],
296
+ "SERVO3": [
297
+ "x-",
298
+ "x+"
299
+ ],
300
+ "MOTOR_EN": [
301
+ "x-",
302
+ "x+"
303
+ ],
304
+ "IMU_INT": [
305
+ "x-",
306
+ "x+"
307
+ ],
308
+ "GPIO15": [
309
+ "x-",
310
+ "x+"
311
+ ],
312
+ "GPIO16": [
313
+ "x-",
314
+ "x+"
315
+ ],
316
+ "GPIO17": [
317
+ "x-",
318
+ "x+"
319
+ ],
320
+ "OUT0A": [
321
+ "x-",
322
+ "x+"
323
+ ],
324
+ "OUT0B": [
325
+ "x-",
326
+ "x+"
327
+ ],
328
+ "OUT1A": [
329
+ "x-",
330
+ "x+"
331
+ ],
332
+ "OUT1B": [
333
+ "x-",
334
+ "x+"
335
+ ],
336
+ "VSYS": [
337
+ "y+"
338
+ ],
339
+ "ADC_VREF": [
340
+ "x-",
341
+ "x+"
342
+ ],
343
+ "V1V1": [
344
+ "y+"
345
+ ]
346
+ },
347
+ "maxMspPairDistance": 2.4,
348
+ "_hideRatsNet": false
349
+ }
@@ -0,0 +1,83 @@
1
+ import { expect, test } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import {
4
+ EPS,
5
+ tracePathContainsPoint,
6
+ } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
7
+ import type { InputProblem } from "lib/types/InputProblem"
8
+ import { convertSolverOutputToCircuitJson } from "tests/fixtures/convertSolverOutputToCircuitJson"
9
+ import "tests/fixtures/matcher"
10
+ import inputProblem from "./assets/repro-robot-controller-imu-tof.input.json"
11
+
12
+ // IMU/ToF section from ROVER, captured with @tscircuit/core 0.0.1861.
13
+ // Other sections are removed; retained placement and routing options are unchanged.
14
+ test("repro robot controller IMU and ToF trace routing", async () => {
15
+ expect([
16
+ ...new Set(inputProblem.chips.map((chip) => chip.sectionId)),
17
+ ]).toEqual(["07_IMU_AND_TOF"])
18
+
19
+ const solver = new SchematicTracePipelineSolver(
20
+ inputProblem as InputProblem,
21
+ { hideRatsNet: true },
22
+ )
23
+
24
+ solver.solve()
25
+
26
+ expect(solver.solved).toBe(true)
27
+ expect(solver.failed).toBe(false)
28
+ const sclConnection = inputProblem.netConnections.find(
29
+ (connection) => connection.netId === "I2C_SCL",
30
+ )!
31
+ const sclTrace = solver
32
+ .netLabelToTraceSolver!.getOutput()
33
+ .traces.find(
34
+ (trace) =>
35
+ trace.pinIds.includes("schematic_port_275") &&
36
+ trace.pinIds.includes("schematic_port_288"),
37
+ )!
38
+ const connectedSclPins = inputProblem.chips.flatMap((chip) =>
39
+ chip.pins.filter(
40
+ (pin) =>
41
+ sclConnection.pinIds.includes(pin.pinId) &&
42
+ tracePathContainsPoint(sclTrace.tracePath, pin),
43
+ ),
44
+ )
45
+ expect(connectedSclPins).toHaveLength(2)
46
+ const sdaLabel = solver
47
+ .inlineNetLabelSolver!.getOutput()
48
+ .inlineNetLabelPlacements.find((label) =>
49
+ label.pinIds.includes("schematic_port_276"),
50
+ )!
51
+ expect(sdaLabel.side).toBe("y+")
52
+ expect(sdaLabel.center.y).toBeGreaterThan(sdaLabel.anchorPoint.y)
53
+ const output = solver.netLabelToTraceSolver!.getOutput()
54
+ const connectorIds =
55
+ solver.inlineNetLabelSolver!.getOutput().netLabelConnectorTraceIds!
56
+ const connectorTraces = output.traces.filter((trace) =>
57
+ connectorIds.has(trace.mspPairId),
58
+ )
59
+ expect(connectorTraces).toHaveLength(4)
60
+ for (const trace of connectorTraces) {
61
+ const endpoint = trace.tracePath.at(-1)!
62
+ expect(
63
+ output.netLabelPlacements.some(
64
+ (label) =>
65
+ label.globalConnNetId === trace.globalConnNetId &&
66
+ Math.abs(label.anchorPoint.x - endpoint.x) < EPS &&
67
+ Math.abs(label.anchorPoint.y - endpoint.y) < EPS,
68
+ ),
69
+ ).toBe(true)
70
+ }
71
+ const circuitJson = convertSolverOutputToCircuitJson(solver)
72
+ expect(
73
+ circuitJson
74
+ .filter((element) => element.type === "schematic_text")
75
+ .map((element) => element.text),
76
+ ).toContain("I2C_SDA")
77
+ expect(
78
+ circuitJson
79
+ .filter((element) => element.type === "schematic_net_label")
80
+ .map((element) => element.text),
81
+ ).toContain("V3V3_IMU")
82
+ await expect(solver).toMatchSolverSnapshot(import.meta.path)
83
+ })
@@ -0,0 +1,59 @@
1
+ import { expect, test } from "bun:test"
2
+ import { convertCircuitJsonToSchematicSvg } from "circuit-to-svg"
3
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
4
+ import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
5
+ import type { InputProblem } from "lib/types/InputProblem"
6
+ import { convertSolverOutputToCircuitJson } from "tests/fixtures/convertSolverOutputToCircuitJson"
7
+ import { readFileSync } from "node:fs"
8
+
9
+ const inputProblem: InputProblem = JSON.parse(
10
+ readFileSync(
11
+ new URL("./assets/rp2040-robot-controller.input.json", import.meta.url),
12
+ "utf8",
13
+ ),
14
+ )
15
+
16
+ // Captured from core rendering https://tscircuit.com/mohan-bee/rp2040-robot-controller.
17
+ // U_M0 pin4 (GND) and pin9 (EP) share a generated ground-label connector.
18
+ test("rp2040 robot controller keeps GND connected after junction alignment", () => {
19
+ const solver = new SchematicTracePipelineSolver(inputProblem)
20
+ solver.solve()
21
+ expect(solver.solved).toBe(true)
22
+ expect(solver.failed).toBe(false)
23
+
24
+ const before = solver.netLabelNetLabelCollisionSolver!
25
+ const after = solver.inlineNetLabelSolver!.getOutput()
26
+ const groundLabel = after.netLabelPlacements.find((label) =>
27
+ label.pinIds.includes("schematic_port_162"),
28
+ )!
29
+ const beforeConnector = before.traces.find(
30
+ (trace) =>
31
+ solver.availableNetOrientationSolver!.netLabelConnectorTraceIds.has(
32
+ trace.mspPairId,
33
+ ) && trace.pinIds.includes("schematic_port_162"),
34
+ )!
35
+ const afterConnector = after.traces.find(
36
+ (trace) => trace.mspPairId === beforeConnector.mspPairId,
37
+ )!
38
+ const beforeGroundLabel = before
39
+ .getOutput()
40
+ .netLabelPlacements.find((label) =>
41
+ label.pinIds.includes("schematic_port_162"),
42
+ )!
43
+ expect(
44
+ tracePathContainsPoint(
45
+ beforeConnector.tracePath,
46
+ beforeGroundLabel.anchorPoint,
47
+ ),
48
+ ).toBe(true)
49
+ expect(
50
+ tracePathContainsPoint(afterConnector.tracePath, groundLabel.anchorPoint),
51
+ ).toBe(true)
52
+
53
+ expect(
54
+ convertCircuitJsonToSchematicSvg(convertSolverOutputToCircuitJson(solver), {
55
+ width: 1000,
56
+ height: 800,
57
+ }).replace(/[ \t]+$/gm, ""),
58
+ ).toMatchSvgSnapshot(import.meta.path)
59
+ })
@@ -16,6 +16,30 @@ const createPlacement = (
16
16
  ...overrides,
17
17
  })
18
18
 
19
+ test("orients named vertical tags while preserving width-only placeholders", () => {
20
+ for (const orientation of ["y+", "y-"] as const) {
21
+ const placement = createPlacement({
22
+ orientation,
23
+ width: 1.2,
24
+ height: 0.42,
25
+ center: { x: 2, y: 3 },
26
+ })
27
+ const placeholderBounds = getAnchoredNetLabelRenderedBounds(placement)
28
+ expect(placeholderBounds.maxX - placeholderBounds.minX).toBeCloseTo(1.2)
29
+ expect(placeholderBounds.maxY - placeholderBounds.minY).toBeCloseTo(0.42)
30
+
31
+ const namedBounds = getAnchoredNetLabelRenderedBounds({
32
+ ...placement,
33
+ netLabelText: "SUPPLY",
34
+ })
35
+ expect(namedBounds.minX).toBeCloseTo(1.79)
36
+ expect(namedBounds.maxX).toBeCloseTo(2.21)
37
+ expect(namedBounds.maxY - namedBounds.minY).toBeCloseTo(1.2)
38
+ if (orientation === "y+") expect(namedBounds.minY).toBeCloseTo(3)
39
+ else expect(namedBounds.maxY).toBeCloseTo(3)
40
+ }
41
+ })
42
+
19
43
  test("uses the horizontal renderer envelope for x-facing labels", () => {
20
44
  const horizontalBounds = getAnchoredNetLabelRenderedBounds(
21
45
  createPlacement({}),
@@ -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
+ })