@tscircuit/schematic-trace-solver 0.0.164 → 0.0.166

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.
Files changed (23) hide show
  1. package/dist/index.d.ts +5 -1
  2. package/dist/index.js +370 -24
  3. package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +225 -24
  4. package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +11 -5
  5. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +1 -1
  6. package/lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts +310 -0
  7. package/package.json +1 -1
  8. package/tests/assets/example51.json +6 -0
  9. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
  10. package/tests/examples/__snapshots__/example51.snap.svg +42 -78
  11. package/tests/examples/example51.test.ts +24 -0
  12. package/tests/fixtures/convertSolverOutputToCircuitJson.ts +22 -3
  13. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +2 -2
  14. package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +1 -15
  15. package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +2 -16
  16. package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +10 -10
  17. package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +18 -18
  18. package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +1 -15
  19. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +33 -47
  20. package/tests/repros/repro-board-648-esp12f-section.test.ts +3 -0
  21. package/tests/repros/repro-isolated-rs485-isow7841.test.ts +33 -1
  22. package/tests/repros/repro-nrf52810-clock-routing.test.ts +26 -1
  23. package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-same-net-cycles.test.ts +154 -0
@@ -9,5 +9,8 @@ test("board 648 ESP-12F power and boot section", () => {
9
9
 
10
10
  solver.solve()
11
11
 
12
+ expect(solver.sameNetJunctionAlignmentSolver?.stats.collapsedCycleCount).toBe(
13
+ 0,
14
+ )
12
15
  expect(solver).toMatchSolverSnapshot(import.meta.path)
13
16
  })
@@ -1,9 +1,18 @@
1
1
  import { expect, test } from "bun:test"
2
2
  import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
- import type { InputProblem } from "lib/types/InputProblem"
3
+ import type { InputProblem, PinId } from "lib/types/InputProblem"
4
4
  import "tests/fixtures/matcher"
5
5
  import inputProblemJson from "./assets/repro-isolated-rs485-isow7841.input.json"
6
6
 
7
+ const EXPECTED_D_P_RAIL_Y = 2.2
8
+ const REDUNDANT_D_P_RAIL_Y = 2.3
9
+ const EXPECTED_GND1_RAIL_Y = 1.76
10
+ const GND1_RAIL_PIN_IDS = ["schematic_port_35", "schematic_port_33"] satisfies [
11
+ PinId,
12
+ PinId,
13
+ ]
14
+ const POINT_EPSILON = 1e-6
15
+
7
16
  test("repro isolated RS-485 ISOW7841 schematic traces", () => {
8
17
  const inputProblem: InputProblem = JSON.parse(
9
18
  JSON.stringify(inputProblemJson),
@@ -17,5 +26,28 @@ test("repro isolated RS-485 ISOW7841 schematic traces", () => {
17
26
  expect(solver.schematicTraceLinesSolver?.failedConnectionPairs).toHaveLength(
18
27
  0,
19
28
  )
29
+ expect(solver.sameNetJunctionAlignmentSolver?.stats.collapsedCycleCount).toBe(
30
+ 2,
31
+ )
32
+ const dPLabel =
33
+ solver.sameNetJunctionAlignmentSolver?.outputNetLabelPlacements.find(
34
+ (label) => label.netId === "D_P",
35
+ )
36
+ expect(dPLabel?.anchorPoint.y).toBeCloseTo(EXPECTED_D_P_RAIL_Y)
37
+ const dPLabelHostTrace =
38
+ solver.sameNetJunctionAlignmentSolver?.outputTraces.find((trace) =>
39
+ dPLabel?.mspConnectionPairIds.includes(trace.mspPairId),
40
+ )
41
+ expect(
42
+ dPLabelHostTrace?.tracePath.some(
43
+ (point) => Math.abs(point.y - REDUNDANT_D_P_RAIL_Y) < POINT_EPSILON,
44
+ ),
45
+ ).toBe(false)
46
+ const gnd1RailTrace =
47
+ solver.sameNetJunctionAlignmentSolver?.outputTraces.find((trace) =>
48
+ GND1_RAIL_PIN_IDS.every((pinId) => trace.pinIds.includes(pinId)),
49
+ )
50
+ expect(gnd1RailTrace?.tracePath[1]?.y).toBeCloseTo(EXPECTED_GND1_RAIL_Y)
51
+ expect(gnd1RailTrace?.tracePath[2]?.y).toBeCloseTo(EXPECTED_GND1_RAIL_Y)
20
52
  expect(solver).toMatchSolverSnapshot(import.meta.path)
21
53
  })
@@ -1,13 +1,22 @@
1
1
  import { expect, test } from "bun:test"
2
2
  import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import type { InputProblem } from "lib/types/InputProblem"
3
4
  import "tests/fixtures/matcher"
4
5
  import inputProblem from "./assets/repro-nrf52810-clock-routing.input.json"
5
6
 
7
+ const EXPECTED_CRYSTAL_RAIL_Y_BY_TRACE_ID = new Map([
8
+ ["schematic_port_42-schematic_port_175", 4.99],
9
+ ["schematic_port_46-schematic_port_181", 5],
10
+ ])
11
+ const REDUNDANT_UPPER_RAIL_Y = 5.1
12
+ const POINT_EPSILON = 1e-6
13
+
6
14
  // Extracted from the nRF52810 clock-source schematic shown in the reproduction:
7
15
  // a four-pin 32 MHz crystal and a two-pin 32.768 kHz crystal, each connected
8
16
  // to an MCU schematic box, two load capacitors, and section-local GND labels.
9
17
  test("repro nRF52810 HF and LF crystal trace/net-label routing", () => {
10
- const solver = new SchematicTracePipelineSolver(inputProblem as any)
18
+ const solverInput: InputProblem = JSON.parse(JSON.stringify(inputProblem))
19
+ const solver = new SchematicTracePipelineSolver(solverInput)
11
20
 
12
21
  solver.solve()
13
22
 
@@ -24,5 +33,21 @@ test("repro nRF52810 HF and LF crystal trace/net-label routing", () => {
24
33
  expect(
25
34
  solver.sameNetJunctionAlignmentSolver?.getOutput().netLabelPlacements,
26
35
  ).toHaveLength(10)
36
+ expect(solver.sameNetJunctionAlignmentSolver?.stats.collapsedCycleCount).toBe(
37
+ 2,
38
+ )
39
+ for (const [traceId, expectedRailY] of EXPECTED_CRYSTAL_RAIL_Y_BY_TRACE_ID) {
40
+ const crystalLoadTrace =
41
+ solver.sameNetJunctionAlignmentSolver?.outputTraces.find(
42
+ (trace) => trace.mspPairId === traceId,
43
+ )
44
+ expect(crystalLoadTrace?.tracePath[0]?.y).toBeCloseTo(expectedRailY)
45
+ expect(crystalLoadTrace?.tracePath[1]?.y).toBeCloseTo(expectedRailY)
46
+ expect(
47
+ crystalLoadTrace?.tracePath.some(
48
+ (point) => Math.abs(point.y - REDUNDANT_UPPER_RAIL_Y) < POINT_EPSILON,
49
+ ),
50
+ ).toBe(false)
51
+ }
27
52
  expect(solver).toMatchSolverSnapshot(import.meta.path)
28
53
  })
@@ -0,0 +1,154 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { Point } from "@tscircuit/math-utils"
3
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import { collapseSameNetCycles } from "lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles"
5
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
+ import {
7
+ getVisibleTraceLength,
8
+ getVisibleTraceSegmentCount,
9
+ } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
10
+ import type { PinId } from "lib/types/InputProblem"
11
+
12
+ interface TraceFixtureInput {
13
+ mspPairId: string
14
+ pinIds: [PinId, PinId]
15
+ tracePath: Point[]
16
+ }
17
+
18
+ const createTraceFixture = ({
19
+ mspPairId,
20
+ pinIds,
21
+ tracePath,
22
+ }: TraceFixtureInput): SolvedTracePath => ({
23
+ mspPairId,
24
+ dcConnNetId: "same-net",
25
+ globalConnNetId: "same-net",
26
+ pins: [
27
+ {
28
+ pinId: pinIds[0],
29
+ chipId: `chip-${pinIds[0]}`,
30
+ ...tracePath[0]!,
31
+ },
32
+ {
33
+ pinId: pinIds[1],
34
+ chipId: `chip-${pinIds[1]}`,
35
+ ...tracePath.at(-1)!,
36
+ },
37
+ ],
38
+ tracePath,
39
+ mspConnectionPairIds: [mspPairId],
40
+ pinIds,
41
+ })
42
+
43
+ test("collapses a same-net cycle between a shared pin and crossing", () => {
44
+ const traces = [
45
+ createTraceFixture({
46
+ mspPairId: "upper-to-shared",
47
+ pinIds: ["upper", "shared"],
48
+ tracePath: [
49
+ { x: 8, y: 2 },
50
+ { x: 0, y: 2 },
51
+ { x: 0, y: 0 },
52
+ ],
53
+ }),
54
+ createTraceFixture({
55
+ mspPairId: "shared-to-lower",
56
+ pinIds: ["shared", "lower"],
57
+ tracePath: [
58
+ { x: 0, y: 0 },
59
+ { x: 6, y: 0 },
60
+ { x: 6, y: 4 },
61
+ { x: 8, y: 4 },
62
+ ],
63
+ }),
64
+ ]
65
+ const netLabelPlacement: NetLabelPlacement = {
66
+ globalConnNetId: "same-net",
67
+ dcConnNetId: "same-net",
68
+ mspConnectionPairIds: ["upper-to-shared"],
69
+ pinIds: ["upper", "shared"],
70
+ orientation: "x-",
71
+ anchorPoint: { x: 0, y: 2 },
72
+ width: 0.5,
73
+ height: 0.2,
74
+ center: { x: -0.25, y: 2 },
75
+ }
76
+
77
+ const result = collapseSameNetCycles({
78
+ traces,
79
+ netLabelPlacements: [netLabelPlacement],
80
+ })
81
+
82
+ expect(result.collapsedCycleCount).toBe(1)
83
+ expect(getVisibleTraceLength(traces)).toBe(22)
84
+ expect(getVisibleTraceLength(result.traces)).toBe(14)
85
+ expect(getVisibleTraceSegmentCount(result.traces)).toBe(4)
86
+ expect(result.traces[0]?.tracePath.at(-1)).toEqual({ x: 0, y: 0 })
87
+ expect(result.netLabelPlacements[0]?.anchorPoint).toEqual({ x: 0, y: 0 })
88
+ })
89
+
90
+ test("keeps a valid shared same-net branch", () => {
91
+ const traces = [
92
+ createTraceFixture({
93
+ mspPairId: "shared-to-branch",
94
+ pinIds: ["shared", "branch"],
95
+ tracePath: [
96
+ { x: 0, y: 0 },
97
+ { x: 4, y: 0 },
98
+ { x: 4, y: 2 },
99
+ ],
100
+ }),
101
+ createTraceFixture({
102
+ mspPairId: "shared-to-trunk",
103
+ pinIds: ["shared", "trunk"],
104
+ tracePath: [
105
+ { x: 0, y: 0 },
106
+ { x: 4, y: 0 },
107
+ { x: 8, y: 0 },
108
+ ],
109
+ }),
110
+ ]
111
+
112
+ const result = collapseSameNetCycles({
113
+ traces,
114
+ netLabelPlacements: [],
115
+ })
116
+
117
+ expect(result.collapsedCycleCount).toBe(0)
118
+ expect(result.traces).toEqual(traces)
119
+ })
120
+
121
+ test("does not lengthen a trace to reuse shared donor geometry", () => {
122
+ const traces = [
123
+ createTraceFixture({
124
+ mspPairId: "short-trace",
125
+ pinIds: ["short-end", "shared"],
126
+ tracePath: [
127
+ { x: 0, y: 0 },
128
+ { x: 0, y: -1 },
129
+ { x: 2, y: -1 },
130
+ { x: 2, y: 0 },
131
+ ],
132
+ }),
133
+ createTraceFixture({
134
+ mspPairId: "long-donor",
135
+ pinIds: ["shared", "donor-end"],
136
+ tracePath: [
137
+ { x: 2, y: 0 },
138
+ { x: 2, y: -1 },
139
+ { x: 4, y: -1 },
140
+ { x: 4, y: -2 },
141
+ { x: 1, y: -2 },
142
+ { x: 1, y: 1 },
143
+ { x: 5, y: 1 },
144
+ ],
145
+ }),
146
+ ]
147
+
148
+ const result = collapseSameNetCycles({
149
+ traces,
150
+ netLabelPlacements: [],
151
+ })
152
+
153
+ expect(result.traces[0]).toEqual(traces[0])
154
+ })