@tscircuit/schematic-trace-solver 0.0.203 → 0.0.204

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 (30) hide show
  1. package/dist/index.js +18 -6
  2. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getFixedLabelCoordinate.ts +17 -8
  3. package/lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts +25 -3
  4. package/package.json +1 -1
  5. package/site/SchematicTracePipelineSolver/repro-smart-lock-ble-module.page.tsx +7 -0
  6. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +78 -60
  7. package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +62 -62
  8. package/tests/bug-reports/bug-report-20260708T055430Z/__snapshots__/bug-report-20260708T055430Z.snap.svg +2 -2
  9. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
  10. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +2 -2
  11. package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +10 -10
  12. package/tests/bug-reports/bug-report-20260905T041712Z/__snapshots__/bug-report-20260905T041712Z.snap.svg +10 -10
  13. package/tests/bug-reports/bug-report-20260907T110144Z/__snapshots__/bug-report-20260907T110144Z.snap.svg +9 -9
  14. package/tests/bug-reports/bug-report-20260907T145640Z/__snapshots__/bug-report-20260907T145640Z.snap.svg +8 -8
  15. package/tests/bug-reports/bug-report-20260911T195723Z/__snapshots__/bug-report-20260911T195723Z.snap.svg +19 -19
  16. package/tests/bug-reports/bug-report-20260916T054012Z/__snapshots__/bug-report-20260916T054012Z.snap.svg +292 -292
  17. package/tests/examples/__snapshots__/example30.snap.svg +123 -123
  18. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +18 -18
  19. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +2 -2
  20. package/tests/repros/__snapshots__/repro-am3352-sheet3-power-rails.snap.svg +2 -2
  21. package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +2 -2
  22. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +2 -2
  23. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +2 -2
  24. package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.snap.svg +7 -7
  25. package/tests/repros/__snapshots__/repro-smart-lock-ble-module.snap.svg +465 -0
  26. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +10 -10
  27. package/tests/repros/assets/repro-smart-lock-ble-module.input.json +2183 -0
  28. package/tests/repros/repro-smart-lock-ble-module.test.ts +69 -0
  29. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-fixed-backbone.test.ts +135 -0
  30. package/tests/solvers/TraceElbowTransitionSimplificationSolver/interior-staircase.test.ts +146 -0
@@ -0,0 +1,69 @@
1
+ import { expect, test } from "bun:test"
2
+ import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
3
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
4
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
5
+ import { detectTraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
6
+ import type { InputProblem } from "lib/types/InputProblem"
7
+ import "tests/fixtures/matcher"
8
+ import input from "./assets/repro-smart-lock-ble-module.input.json"
9
+
10
+ // Full ble_module sheet from imrishabh18/smart-lock v0.0.1.
11
+ // Reconstructed from deployed Circuit JSON with current core routing defaults.
12
+ test("repro smart-lock BLE module schematic sheet", async () => {
13
+ const solver = new SchematicTracePipelineSolver(
14
+ structuredClone(input) as unknown as InputProblem,
15
+ { hideRatsNet: true },
16
+ )
17
+ solver.solve()
18
+
19
+ expect(solver.solved).toBe(true)
20
+ expect(solver.failed).toBe(false)
21
+
22
+ const { traces, netLabelPlacements } =
23
+ solver.netLabelToTraceSolver!.getOutput()
24
+ const supplyLabel = netLabelPlacements.find((label) =>
25
+ label.mspConnectionPairIds.includes(
26
+ "schematic_port_246-schematic_port_243",
27
+ ),
28
+ )!
29
+ // C99, C104, C103, and C102 must share C101's labeled VDDS rail.
30
+ for (const traceId of [
31
+ "schematic_port_244-schematic_port_246",
32
+ "schematic_port_248-schematic_port_246",
33
+ "schematic_port_250-schematic_port_248",
34
+ "schematic_port_252-schematic_port_250",
35
+ ]) {
36
+ const trace = traces.find((trace) => trace.mspPairId === traceId)!
37
+ expect(trace.tracePath[1]!.y).toBeCloseTo(supplyLabel.anchorPoint.y)
38
+ expect(trace.tracePath[2]!.y).toBeCloseTo(supplyLabel.anchorPoint.y)
39
+ }
40
+
41
+ // The C92–L34 ground trace needs one offset around U1's label column.
42
+ const groundTrace = traces.find(
43
+ (trace) => trace.mspPairId === "schematic_port_291-schematic_port_297",
44
+ )!
45
+ const path = groundTrace.tracePath
46
+ expect(path).toHaveLength(6)
47
+ expect(path[2]!.x).toBeCloseTo(path[3]!.x)
48
+ expect(path[3]!.y).toBeCloseTo(path[4]!.y)
49
+ for (const [endpoint, neighbor, pin] of [
50
+ [path[0]!, path[1]!, groundTrace.pins[0]!],
51
+ [path.at(-1)!, path.at(-2)!, groundTrace.pins[1]!],
52
+ ] as const) {
53
+ expect(endpoint.x).toBeCloseTo(pin.x)
54
+ expect(endpoint.y).toBeCloseTo(pin.y)
55
+ expect(neighbor.x).toBeCloseTo(endpoint.x)
56
+ expect(neighbor.y).toBeLessThan(endpoint.y)
57
+ }
58
+ expect(
59
+ isPathCollidingWithObstacles(path, getObstacleRects(solver.inputProblem)),
60
+ ).toBe(false)
61
+ expect(
62
+ detectTraceLabelOverlap({
63
+ traces: [groundTrace],
64
+ netLabels: netLabelPlacements,
65
+ }),
66
+ ).toHaveLength(0)
67
+
68
+ await expect(solver).toMatchSolverSnapshot(import.meta.path)
69
+ })
@@ -0,0 +1,135 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { InputProblem } from "lib/types/InputProblem"
4
+ import { align, createTrace } from "./fixtures/alignSameNetRails"
5
+
6
+ const createFixture = () => {
7
+ const firstPin = {
8
+ pinId: "C1.1",
9
+ chipId: "C1",
10
+ x: -2,
11
+ y: 0,
12
+ _facingDirection: "y+" as const,
13
+ }
14
+ const sharedPin = {
15
+ pinId: "C2.1",
16
+ chipId: "C2",
17
+ x: 0,
18
+ y: 0,
19
+ _facingDirection: "y+" as const,
20
+ }
21
+ const supplyPin = {
22
+ pinId: "F1.1",
23
+ chipId: "F1",
24
+ x: 1,
25
+ y: 3,
26
+ _facingDirection: "x-" as const,
27
+ }
28
+ const problem: InputProblem = {
29
+ maxMspPairDistance: 2.4,
30
+ chips: [
31
+ {
32
+ chipId: "C1",
33
+ center: { x: -2, y: -0.5 },
34
+ width: 0.8,
35
+ height: 1,
36
+ pins: [firstPin],
37
+ },
38
+ {
39
+ chipId: "C2",
40
+ center: { x: 0, y: -0.5 },
41
+ width: 0.8,
42
+ height: 1,
43
+ pins: [sharedPin],
44
+ },
45
+ {
46
+ chipId: "F1",
47
+ center: { x: 1.5, y: 3 },
48
+ width: 1,
49
+ height: 0.8,
50
+ pins: [supplyPin],
51
+ },
52
+ ],
53
+ directConnections: [],
54
+ netConnections: [],
55
+ availableNetLabelOrientations: {},
56
+ }
57
+ const branch = createTrace(
58
+ "branch",
59
+ [
60
+ { x: -2, y: 0 },
61
+ { x: -2, y: 1 },
62
+ { x: 0, y: 1 },
63
+ { x: 0, y: 0 },
64
+ ],
65
+ [firstPin, sharedPin],
66
+ )
67
+ const backbone = createTrace(
68
+ "backbone",
69
+ [
70
+ { x: 0, y: 0 },
71
+ { x: 0, y: 2 },
72
+ { x: 0.5, y: 2 },
73
+ { x: 0.5, y: 3 },
74
+ { x: 1, y: 3 },
75
+ ],
76
+ [sharedPin, supplyPin],
77
+ )
78
+ const label: NetLabelPlacement = {
79
+ globalConnNetId: "power-net",
80
+ netId: "POWER",
81
+ mspConnectionPairIds: ["backbone"],
82
+ pinIds: backbone.pinIds,
83
+ orientation: "y+",
84
+ anchorPoint: { x: 0, y: 2 },
85
+ center: { x: 0, y: 2.1 },
86
+ width: 0.4,
87
+ height: 0.2,
88
+ }
89
+ return { problem, branch, backbone, label }
90
+ }
91
+
92
+ test("aligns a branch to a labeled backbone without moving its elbows", () => {
93
+ const { problem, branch, backbone, label } = createFixture()
94
+ const result = align([branch, backbone], {
95
+ inputProblem: problem,
96
+ netLabelPlacements: [label],
97
+ })
98
+
99
+ expect(result.traces[0]!.tracePath).toEqual([
100
+ { x: -2, y: 0 },
101
+ { x: -2, y: 2 },
102
+ { x: 0, y: 2 },
103
+ { x: 0, y: 0 },
104
+ ])
105
+ expect(result.traces[1]).toEqual(backbone)
106
+ })
107
+
108
+ test("keeps the branch below an obstacle on the fixed backbone coordinate", () => {
109
+ const { problem, branch, backbone, label } = createFixture()
110
+ problem.chips.push({
111
+ chipId: "obstacle",
112
+ center: { x: -1, y: 2 },
113
+ width: 0.4,
114
+ height: 0.4,
115
+ pins: [],
116
+ })
117
+ const result = align([branch, backbone], {
118
+ inputProblem: problem,
119
+ netLabelPlacements: [label],
120
+ })
121
+
122
+ expect(result.traces).toEqual([branch, backbone])
123
+ })
124
+
125
+ test("does not pull a multi-turn backbone toward a label on a different coordinate", () => {
126
+ const { problem, branch, backbone, label } = createFixture()
127
+ label.anchorPoint.y = 1.5
128
+ label.center.y = 1.6
129
+ const result = align([branch, backbone], {
130
+ inputProblem: problem,
131
+ netLabelPlacements: [label],
132
+ })
133
+
134
+ expect(result.traces).toEqual([branch, backbone])
135
+ })
@@ -0,0 +1,146 @@
1
+ import { expect, test } from "bun:test"
2
+ import { getPathLength } from "lib/solvers/Example28Solver/geometry"
3
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import { TraceElbowTransitionSimplificationSolver } from "lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver"
5
+ import type { InputProblem } from "lib/types/InputProblem"
6
+ import { createTrace } from "../TraceCleanupSolver/fixtures/alignSameNetRails"
7
+
8
+ const createFixture = () => {
9
+ const path = [
10
+ { x: 5, y: 0 },
11
+ { x: 5, y: -1 },
12
+ { x: 2.5, y: -1 },
13
+ { x: 2.5, y: 2 },
14
+ { x: 2, y: 2 },
15
+ { x: 2, y: 3 },
16
+ { x: 1, y: 3 },
17
+ { x: 1, y: 4 },
18
+ ]
19
+ const trace = createTrace("rerouted", path, [
20
+ { pinId: "C1.1", chipId: "C1", x: 5, y: 0, _facingDirection: "y-" },
21
+ { pinId: "L1.1", chipId: "L1", x: 1, y: 4, _facingDirection: "y-" },
22
+ ])
23
+ const labels: NetLabelPlacement[] = [
24
+ {
25
+ globalConnNetId: "signal",
26
+ netId: "SIGNAL",
27
+ mspConnectionPairIds: [],
28
+ pinIds: [],
29
+ orientation: "x+",
30
+ anchorPoint: { x: 0.5, y: 0.7 },
31
+ center: { x: 1.45, y: 0.7 },
32
+ width: 1.9,
33
+ height: 2.4,
34
+ },
35
+ {
36
+ globalConnNetId: "signal2",
37
+ netId: "SIGNAL2",
38
+ mspConnectionPairIds: [],
39
+ pinIds: [],
40
+ orientation: "x+",
41
+ anchorPoint: { x: 0.5, y: 2.45 },
42
+ center: { x: 1.2, y: 2.45 },
43
+ width: 1.4,
44
+ height: 0.9,
45
+ },
46
+ ]
47
+ const problem: InputProblem = {
48
+ chips: [],
49
+ directConnections: [],
50
+ netConnections: [],
51
+ availableNetLabelOrientations: {},
52
+ }
53
+ return {
54
+ inputProblem: problem,
55
+ traces: [trace],
56
+ completedReroutes: [
57
+ {
58
+ initialTrace: trace,
59
+ reroutedTracePath: path,
60
+ label: labels[0]!,
61
+ detourCount: 0,
62
+ },
63
+ ],
64
+ netLabelPlacements: labels,
65
+ paddingBuffer: 0.1,
66
+ }
67
+ }
68
+
69
+ test("merges successive label detours without changing length or terminal directions", () => {
70
+ const input = createFixture()
71
+ const solver = new TraceElbowTransitionSimplificationSolver(input)
72
+ solver.solve()
73
+ const path = solver.getOutput().traces[0]!.tracePath
74
+
75
+ expect(path).toEqual([
76
+ { x: 5, y: 0 },
77
+ { x: 5, y: -1 },
78
+ { x: 2.5, y: -1 },
79
+ { x: 2.5, y: 3 },
80
+ { x: 1, y: 3 },
81
+ { x: 1, y: 4 },
82
+ ])
83
+ expect(getPathLength(path)).toBeCloseTo(
84
+ getPathLength(input.traces[0]!.tracePath),
85
+ )
86
+ })
87
+
88
+ test.each([
89
+ "component",
90
+ "label",
91
+ "parallel_trace",
92
+ "crossing_trace",
93
+ "anchor",
94
+ ] as const)("preserves a necessary staircase blocked by %s", (blocker) => {
95
+ const input = createFixture()
96
+ if (blocker === "component") {
97
+ input.inputProblem.chips.push({
98
+ chipId: "obstacle",
99
+ center: { x: 2.5, y: 2.5 },
100
+ width: 0.2,
101
+ height: 0.4,
102
+ pins: [],
103
+ })
104
+ } else if (blocker === "parallel_trace" || blocker === "crossing_trace") {
105
+ const path =
106
+ blocker === "parallel_trace"
107
+ ? [
108
+ { x: 2.5, y: 2.2 },
109
+ { x: 2.5, y: 2.8 },
110
+ ]
111
+ : [
112
+ { x: 2.3, y: 2.5 },
113
+ { x: 2.7, y: 2.5 },
114
+ ]
115
+ input.traces.push(
116
+ createTrace(
117
+ "foreign",
118
+ path,
119
+ [
120
+ { pinId: "X1.1", chipId: "X1", ...path[0]! },
121
+ { pinId: "X2.1", chipId: "X2", ...path[1]! },
122
+ ],
123
+ "foreign",
124
+ ),
125
+ )
126
+ } else {
127
+ const x = blocker === "anchor" ? 2 : 2.5
128
+ input.netLabelPlacements.push({
129
+ globalConnNetId: blocker === "anchor" ? "power-net" : "foreign",
130
+ netId: "BLOCKER",
131
+ mspConnectionPairIds: ["rerouted"],
132
+ pinIds: [],
133
+ orientation: "x+",
134
+ anchorPoint: { x, y: 2.5 },
135
+ center: { x: x + 0.1, y: 2.5 },
136
+ width: 0.2,
137
+ height: 0.4,
138
+ })
139
+ }
140
+ const solver = new TraceElbowTransitionSimplificationSolver(input)
141
+ solver.solve()
142
+
143
+ expect(solver.getOutput().traces[0]!.tracePath).toEqual(
144
+ input.traces[0]!.tracePath,
145
+ )
146
+ })