@tscircuit/schematic-trace-solver 0.0.96 → 0.0.98

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 (45) hide show
  1. package/dist/index.d.ts +9 -0
  2. package/dist/index.js +986 -479
  3. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +55 -14
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +31 -0
  5. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +45 -1
  6. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +55 -13
  7. package/lib/solvers/TraceCleanupSolver/alignSameNetRails.ts +66 -0
  8. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/evaluateRailGroup.ts +124 -0
  9. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry.ts +108 -0
  10. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getComponentSideRailSegments.ts +121 -0
  11. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getRailGroups.ts +114 -0
  12. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/moveRailSegments.ts +27 -0
  13. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/preservesLabelAnchors.ts +30 -0
  14. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/scoreRailAlignment.ts +67 -0
  15. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/types.ts +34 -0
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +1 -60
  17. package/lib/utils/doesPathCoincideWithTraces.ts +61 -0
  18. package/package.json +1 -1
  19. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +1 -1
  20. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +1 -1
  21. package/tests/examples/__snapshots__/example02.snap.svg +1 -1
  22. package/tests/examples/__snapshots__/example14.snap.svg +1 -1
  23. package/tests/examples/__snapshots__/example18.snap.svg +1 -1
  24. package/tests/examples/__snapshots__/example46.snap.svg +1 -1
  25. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +1 -1
  26. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +178 -0
  27. package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +54 -0
  28. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +1 -1
  29. package/tests/repros/assets/repro-rp2040-gamepad-trace-alignment.input.json +237 -0
  30. package/tests/repros/assets/repro-rp2040-zero-crystal-fallback-netlabels.input.json +121 -0
  31. package/tests/repros/repro-rp2040-gamepad-trace-alignment.test.ts +38 -0
  32. package/tests/repros/repro-rp2040-zero-crystal-fallback-netlabels.test.ts +16 -0
  33. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-intersects-rect-interior.test.ts +15 -0
  34. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-boundary.test.ts +18 -0
  35. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-interior.test.ts +18 -0
  36. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-component-scope.test.ts +38 -0
  37. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-eligible-traces.test.ts +28 -0
  38. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-horizontal.test.ts +62 -0
  39. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-label-anchor.test.ts +36 -0
  40. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-label-junction.test.ts +42 -0
  41. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-obstacle.test.ts +28 -0
  42. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-pipeline-label-connector.test.ts +52 -0
  43. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-vertical.test.ts +25 -0
  44. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-visible-length.test.ts +40 -0
  45. package/tests/solvers/TraceCleanupSolver/fixtures/alignSameNetRails.ts +89 -0
@@ -0,0 +1,237 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "J2",
5
+ "center": {
6
+ "x": 0,
7
+ "y": 0
8
+ },
9
+ "width": 2.54,
10
+ "height": 11.684,
11
+ "pins": [
12
+ { "pinId": "J2.1", "x": -1.778, "y": 2.413 },
13
+ { "pinId": "J2.2", "x": -1.778, "y": 2.159 },
14
+ { "pinId": "J2.3", "x": -1.778, "y": 1.905 },
15
+ { "pinId": "J2.4", "x": -1.778, "y": 1.651 },
16
+ { "pinId": "J2.5", "x": -1.778, "y": 1.397 },
17
+ { "pinId": "J2.6", "x": -1.778, "y": 1.143 },
18
+ { "pinId": "J2.7", "x": -1.778, "y": 0.889 },
19
+ { "pinId": "J2.8", "x": -1.778, "y": 0.635 },
20
+ { "pinId": "J2.9", "x": -1.778, "y": 0.381 },
21
+ { "pinId": "J2.10", "x": -1.778, "y": 0.127 },
22
+ { "pinId": "J2.11", "x": -1.778, "y": -0.127 },
23
+ { "pinId": "J2.12", "x": -1.778, "y": -0.381 },
24
+ { "pinId": "J2.13", "x": -1.778, "y": -0.635 },
25
+ { "pinId": "J2.14", "x": -1.778, "y": -0.889 },
26
+ { "pinId": "J2.15", "x": -1.778, "y": -1.143 },
27
+ { "pinId": "J2.16", "x": -1.778, "y": -1.397 },
28
+ { "pinId": "J2.17", "x": -1.778, "y": -1.651 },
29
+ { "pinId": "J2.18", "x": -1.778, "y": -1.905 },
30
+ { "pinId": "J2.19", "x": -1.778, "y": -2.159 },
31
+ { "pinId": "J2.20", "x": -1.778, "y": -2.413 },
32
+ { "pinId": "J2.21", "x": 1.778, "y": -2.413 },
33
+ { "pinId": "J2.22", "x": 1.778, "y": -2.159 },
34
+ { "pinId": "J2.23", "x": 1.778, "y": -1.905 },
35
+ { "pinId": "J2.24", "x": 1.778, "y": -1.651 },
36
+ { "pinId": "J2.25", "x": 1.778, "y": -1.397 },
37
+ { "pinId": "J2.26", "x": 1.778, "y": -1.143 },
38
+ { "pinId": "J2.27", "x": 1.778, "y": -0.889 },
39
+ { "pinId": "J2.28", "x": 1.778, "y": -0.635 },
40
+ { "pinId": "J2.29", "x": 1.778, "y": -0.381 },
41
+ { "pinId": "J2.30", "x": 1.778, "y": -0.127 },
42
+ { "pinId": "J2.31", "x": 1.778, "y": 0.127 },
43
+ { "pinId": "J2.32", "x": 1.778, "y": 0.381 },
44
+ { "pinId": "J2.33", "x": 1.778, "y": 0.635 },
45
+ { "pinId": "J2.34", "x": 1.778, "y": 0.889 },
46
+ { "pinId": "J2.35", "x": 1.778, "y": 1.143 },
47
+ { "pinId": "J2.36", "x": 1.778, "y": 1.397 },
48
+ { "pinId": "J2.37", "x": 1.778, "y": 1.651 },
49
+ { "pinId": "J2.38", "x": 1.778, "y": 1.905 },
50
+ { "pinId": "J2.39", "x": 1.778, "y": 2.159 },
51
+ { "pinId": "J2.40", "x": 1.778, "y": 2.413 },
52
+ { "pinId": "J2.TP1", "x": -0.762, "y": 6.35 },
53
+ { "pinId": "J2.TP2", "x": -0.508, "y": 6.35 },
54
+ { "pinId": "J2.TP3", "x": -0.254, "y": 6.35 },
55
+ { "pinId": "J2.TP4", "x": 0, "y": 6.35 },
56
+ { "pinId": "J2.TP5", "x": 0.254, "y": 6.35 },
57
+ { "pinId": "J2.TP6", "x": 0.508, "y": 6.35 },
58
+ { "pinId": "J2.D1", "x": -0.254, "y": -6.35 },
59
+ { "pinId": "J2.D2", "x": 0, "y": -6.35 },
60
+ { "pinId": "J2.D3", "x": 0.254, "y": -6.35 }
61
+ ]
62
+ },
63
+ {
64
+ "chipId": "USB1",
65
+ "center": {
66
+ "x": -8.5,
67
+ "y": 2.3
68
+ },
69
+ "width": 1.5,
70
+ "height": 3.6,
71
+ "pins": [
72
+ { "pinId": "USB1.1", "x": -7.75, "y": 3.86 },
73
+ { "pinId": "USB1.2", "x": -7.75, "y": 3.6 },
74
+ { "pinId": "USB1.3", "x": -7.75, "y": 3.34 },
75
+ { "pinId": "USB1.4", "x": -7.75, "y": 3.08 },
76
+ { "pinId": "USB1.5", "x": -7.75, "y": 2.82 },
77
+ { "pinId": "USB1.6", "x": -7.75, "y": 2.56 },
78
+ { "pinId": "USB1.7", "x": -7.75, "y": 2.3 },
79
+ { "pinId": "USB1.8", "x": -7.75, "y": 2.04 },
80
+ { "pinId": "USB1.9", "x": -7.75, "y": 1.78 },
81
+ { "pinId": "USB1.10", "x": -7.75, "y": 1.52 },
82
+ { "pinId": "USB1.11", "x": -7.75, "y": 1.26 },
83
+ { "pinId": "USB1.12", "x": -7.75, "y": 1 },
84
+ { "pinId": "USB1.13", "x": -7.75, "y": 0.74 }
85
+ ]
86
+ },
87
+ {
88
+ "chipId": "R1",
89
+ "center": {
90
+ "x": -5.4,
91
+ "y": 1.2
92
+ },
93
+ "width": 0.5,
94
+ "height": 1,
95
+ "pins": [
96
+ { "pinId": "R1.1", "x": -5.4, "y": 1.75 },
97
+ { "pinId": "R1.2", "x": -5.4, "y": 0.65 }
98
+ ]
99
+ },
100
+ {
101
+ "chipId": "R2",
102
+ "center": {
103
+ "x": -4,
104
+ "y": 1.2
105
+ },
106
+ "width": 0.5,
107
+ "height": 1,
108
+ "pins": [
109
+ { "pinId": "R2.1", "x": -4, "y": 1.75 },
110
+ { "pinId": "R2.2", "x": -4, "y": 0.65 }
111
+ ]
112
+ }
113
+ ],
114
+ "directConnections": [
115
+ {
116
+ "pinIds": ["USB1.4", "R1.1"],
117
+ "netId": "USB1.CC1 to R1.1"
118
+ },
119
+ {
120
+ "pinIds": ["USB1.10", "R2.1"],
121
+ "netId": "USB1.CC2 to R2.1"
122
+ }
123
+ ],
124
+ "netConnections": [
125
+ {
126
+ "netId": "GNDPWR",
127
+ "pinIds": [
128
+ "USB1.1",
129
+ "USB1.12",
130
+ "USB1.13",
131
+ "R1.2",
132
+ "R2.2",
133
+ "J2.TP1"
134
+ ],
135
+ "netLabelWidth": 0.42,
136
+ "netLabelHeight": 0.66
137
+ },
138
+ {
139
+ "netId": "V5",
140
+ "pinIds": ["USB1.2", "USB1.11", "J2.40"],
141
+ "netLabelWidth": 0.42,
142
+ "netLabelHeight": 0.3
143
+ },
144
+ {
145
+ "netId": "D-",
146
+ "pinIds": ["USB1.5", "USB1.7"],
147
+ "netLabelWidth": 0.3
148
+ },
149
+ {
150
+ "netId": "D+",
151
+ "pinIds": ["USB1.6", "USB1.8"],
152
+ "netLabelWidth": 0.3
153
+ },
154
+ { "netId": "SDA", "pinIds": ["J2.1"], "netLabelWidth": 0.36 },
155
+ { "netId": "SCL", "pinIds": ["J2.2"], "netLabelWidth": 0.36 },
156
+ { "netId": "UP", "pinIds": ["J2.4"], "netLabelWidth": 0.3 },
157
+ { "netId": "DOWN", "pinIds": ["J2.5"], "netLabelWidth": 0.48 },
158
+ { "netId": "RIGHT", "pinIds": ["J2.6"], "netLabelWidth": 0.54 },
159
+ { "netId": "LEFT", "pinIds": ["J2.7"], "netLabelWidth": 0.48 },
160
+ { "netId": "B1", "pinIds": ["J2.9"], "netLabelWidth": 0.3 },
161
+ { "netId": "B2", "pinIds": ["J2.10"], "netLabelWidth": 0.3 },
162
+ { "netId": "R2", "pinIds": ["J2.11"], "netLabelWidth": 0.3 },
163
+ { "netId": "L2", "pinIds": ["J2.12"], "netLabelWidth": 0.3 },
164
+ { "netId": "B3", "pinIds": ["J2.14"], "netLabelWidth": 0.3 },
165
+ { "netId": "B4", "pinIds": ["J2.15"], "netLabelWidth": 0.3 },
166
+ { "netId": "R1", "pinIds": ["J2.16"], "netLabelWidth": 0.3 },
167
+ { "netId": "L1", "pinIds": ["J2.17"], "netLabelWidth": 0.3 },
168
+ { "netId": "TURBO", "pinIds": ["J2.19"], "netLabelWidth": 0.6 },
169
+ {
170
+ "netId": "LED-TURBO",
171
+ "pinIds": ["J2.20"],
172
+ "netLabelWidth": 0.9
173
+ },
174
+ { "netId": "S1", "pinIds": ["J2.21"], "netLabelWidth": 0.3 },
175
+ { "netId": "S2", "pinIds": ["J2.22"], "netLabelWidth": 0.3 },
176
+ { "netId": "L3", "pinIds": ["J2.24"], "netLabelWidth": 0.3 },
177
+ { "netId": "R3", "pinIds": ["J2.25"], "netLabelWidth": 0.3 },
178
+ { "netId": "A1", "pinIds": ["J2.26"], "netLabelWidth": 0.3 },
179
+ { "netId": "A2", "pinIds": ["J2.27"], "netLabelWidth": 0.3 },
180
+ { "netId": "R5", "pinIds": ["J2.31"], "netLabelWidth": 0.3 },
181
+ { "netId": "L5", "pinIds": ["J2.32"], "netLabelWidth": 0.3 },
182
+ {
183
+ "netId": "LED-RGB",
184
+ "pinIds": ["J2.34"],
185
+ "netLabelWidth": 0.66
186
+ },
187
+ {
188
+ "netId": "GND",
189
+ "pinIds": [
190
+ "J2.3",
191
+ "J2.8",
192
+ "J2.13",
193
+ "J2.18",
194
+ "J2.23",
195
+ "J2.28",
196
+ "J2.33",
197
+ "J2.38",
198
+ "J2.D2"
199
+ ],
200
+ "netLabelWidth": 0.42,
201
+ "netLabelHeight": 0.48
202
+ }
203
+ ],
204
+ "availableNetLabelOrientations": {
205
+ "SDA": ["x-"],
206
+ "SCL": ["x-"],
207
+ "UP": ["x-"],
208
+ "DOWN": ["x-"],
209
+ "RIGHT": ["x-"],
210
+ "LEFT": ["x-"],
211
+ "B1": ["x-"],
212
+ "B2": ["x-"],
213
+ "R2": ["x-"],
214
+ "L2": ["x-"],
215
+ "B3": ["x-"],
216
+ "B4": ["x-"],
217
+ "R1": ["x-"],
218
+ "L1": ["x-"],
219
+ "TURBO": ["x-"],
220
+ "LED-TURBO": ["x-"],
221
+ "S1": ["x+"],
222
+ "S2": ["x+"],
223
+ "L3": ["x+"],
224
+ "R3": ["x+"],
225
+ "A1": ["x+"],
226
+ "A2": ["x+"],
227
+ "R5": ["x+"],
228
+ "L5": ["x+"],
229
+ "LED-RGB": ["x+"],
230
+ "GNDPWR": ["y-"],
231
+ "V5": ["y+"],
232
+ "D-": ["x+"],
233
+ "D+": ["x+"],
234
+ "GND": ["y-"]
235
+ },
236
+ "maxMspPairDistance": 8
237
+ }
@@ -0,0 +1,121 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": {
6
+ "x": 1.105,
7
+ "y": 0.67
8
+ },
9
+ "width": 0.9699999999999998,
10
+ "height": 0.6,
11
+ "pins": [
12
+ {
13
+ "pinId": "R8.1",
14
+ "x": 1.07,
15
+ "y": 0.97
16
+ },
17
+ {
18
+ "pinId": "R8.2",
19
+ "x": 1.07,
20
+ "y": 0.37000000000000005
21
+ }
22
+ ]
23
+ },
24
+ {
25
+ "chipId": "schematic_component_1",
26
+ "center": {
27
+ "x": 1.1725,
28
+ "y": -0.66
29
+ },
30
+ "width": 1.045,
31
+ "height": 0.76,
32
+ "pins": [
33
+ {
34
+ "pinId": "C17.1",
35
+ "x": 1.1,
36
+ "y": -0.28,
37
+ "_facingDirection": "y+"
38
+ },
39
+ {
40
+ "pinId": "C17.2",
41
+ "x": 1.1,
42
+ "y": -1.04,
43
+ "_facingDirection": "y-"
44
+ }
45
+ ]
46
+ },
47
+ {
48
+ "chipId": "schematic_component_2",
49
+ "center": {
50
+ "x": -1.07,
51
+ "y": 0.47
52
+ },
53
+ "width": 2.0000000000000004,
54
+ "height": 0.6000000000000001,
55
+ "pins": [
56
+ {
57
+ "pinId": "X1.1",
58
+ "x": -2.0700000000000003,
59
+ "y": 0.57
60
+ },
61
+ {
62
+ "pinId": "X1.2",
63
+ "x": -2.0700000000000003,
64
+ "y": 0.37
65
+ },
66
+ {
67
+ "pinId": "X1.3",
68
+ "x": -0.06999999999999984,
69
+ "y": 0.37
70
+ },
71
+ {
72
+ "pinId": "X1.4",
73
+ "x": -0.06999999999999984,
74
+ "y": 0.57
75
+ }
76
+ ]
77
+ }
78
+ ],
79
+ "directConnections": [
80
+ {
81
+ "pinIds": [
82
+ "X1.3",
83
+ "C17.1"
84
+ ],
85
+ "netId": ".X1 > .XTAL2 to C17.pin1"
86
+ },
87
+ {
88
+ "pinIds": [
89
+ "X1.3",
90
+ "R8.2"
91
+ ],
92
+ "netId": ".X1 > .XTAL2 to R8.pin2"
93
+ }
94
+ ],
95
+ "netConnections": [],
96
+ "textBoxes": [
97
+ {
98
+ "chipId": "schematic_component_2",
99
+ "center": {
100
+ "x": -1.0100000000000002,
101
+ "y": 0.039999999999999925
102
+ },
103
+ "width": 1.3199999999999998,
104
+ "height": 0.18,
105
+ "text": "ABM8_272_T3"
106
+ },
107
+ {
108
+ "chipId": "schematic_component_2",
109
+ "center": {
110
+ "x": -1.5500000000000003,
111
+ "y": 0.885
112
+ },
113
+ "width": 0.3600000000000001,
114
+ "height": 0.25,
115
+ "text": "X1"
116
+ }
117
+ ],
118
+ "availableNetLabelOrientations": {},
119
+ "maxMspPairDistance": 2.4,
120
+ "_hideRatsNet": false
121
+ }
@@ -0,0 +1,38 @@
1
+ import { expect, test } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import "tests/fixtures/matcher"
4
+ import inputProblem from "./assets/repro-rp2040-gamepad-trace-alignment.input.json"
5
+
6
+ test("reproduces RP2040 gamepad trace routing", () => {
7
+ const solver = new SchematicTracePipelineSolver(inputProblem as any, {
8
+ hideRatsNet: true,
9
+ })
10
+
11
+ solver.solve()
12
+
13
+ const finalTraces = solver
14
+ .netLabelTraceCollisionSolver!.getOutput()
15
+ .traces.filter((trace) => trace.userNetId === "GND")
16
+ const leftRailXs = new Set<number>()
17
+ const rightRailXs = new Set<number>()
18
+ for (const trace of finalTraces) {
19
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
20
+ const start = trace.tracePath[i]!
21
+ const end = trace.tracePath[i + 1]!
22
+ if (Math.abs(start.x - end.x) > 1e-6) continue
23
+ if (start.x < -1.778 - 1e-6) leftRailXs.add(start.x)
24
+ if (start.x > 1.778 + 1e-6) rightRailXs.add(start.x)
25
+ }
26
+ }
27
+
28
+ expect(leftRailXs.size).toBe(1)
29
+ expect(rightRailXs.size).toBe(1)
30
+ expect(
31
+ solver.traceCleanupSolver2!.stats.alignedRailGroupCount,
32
+ ).toBeGreaterThanOrEqual(2)
33
+ expect(
34
+ solver.traceCleanupSolver2!.stats.alignedTraceCount,
35
+ ).toBeGreaterThanOrEqual(8)
36
+
37
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
38
+ })
@@ -0,0 +1,16 @@
1
+ import { expect, test } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import "tests/fixtures/matcher"
4
+ import inputProblem from "./assets/repro-rp2040-zero-crystal-fallback-netlabels.input.json"
5
+
6
+ test("rp2040-zero crystal connection routes both branches", () => {
7
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
+
9
+ solver.solve()
10
+
11
+ expect(solver.schematicTraceLinesSolver?.solvedTracePaths).toHaveLength(2)
12
+ expect(solver.schematicTraceLinesSolver?.failedConnectionPairs).toHaveLength(
13
+ 0,
14
+ )
15
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
16
+ })
@@ -0,0 +1,15 @@
1
+ import { expect, test } from "bun:test"
2
+ import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
3
+
4
+ const rect = {
5
+ minX: 0,
6
+ maxX: 2,
7
+ minY: 0,
8
+ maxY: 2,
9
+ }
10
+
11
+ test("interior crossings remain collisions", () => {
12
+ expect(segmentIntersectsRect({ x: -1, y: 1 }, { x: 1, y: 1 }, rect)).toBe(
13
+ true,
14
+ )
15
+ })
@@ -0,0 +1,18 @@
1
+ import { expect, test } from "bun:test"
2
+ import { segmentOverlapsRectBoundary } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
3
+
4
+ const rect = {
5
+ minX: 0,
6
+ maxX: 2,
7
+ minY: 0,
8
+ maxY: 2,
9
+ }
10
+
11
+ test("detects segments that overlap rectangle boundaries", () => {
12
+ expect(
13
+ segmentOverlapsRectBoundary({ x: -1, y: 0 }, { x: 1, y: 0 }, rect),
14
+ ).toBe(true)
15
+ expect(
16
+ segmentOverlapsRectBoundary({ x: 2, y: -1 }, { x: 2, y: 1 }, rect),
17
+ ).toBe(true)
18
+ })
@@ -0,0 +1,18 @@
1
+ import { expect, test } from "bun:test"
2
+ import { segmentOverlapsRectBoundary } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
3
+
4
+ const rect = {
5
+ minX: 0,
6
+ maxX: 2,
7
+ minY: 0,
8
+ maxY: 2,
9
+ }
10
+
11
+ test("does not treat interior crossings as boundary overlaps", () => {
12
+ expect(
13
+ segmentOverlapsRectBoundary({ x: -1, y: 1 }, { x: 1, y: 1 }, rect),
14
+ ).toBe(false)
15
+ expect(
16
+ segmentOverlapsRectBoundary({ x: 1, y: -1 }, { x: 1, y: 1 }, rect),
17
+ ).toBe(false)
18
+ })
@@ -0,0 +1,38 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
+ import type { InputProblem } from "lib/types/InputProblem"
4
+ import {
5
+ align,
6
+ getVerticalRailTraces,
7
+ verticalProblem,
8
+ } from "./fixtures/alignSameNetRails"
9
+
10
+ test("does not combine rails from different component sides", () => {
11
+ const traces = getVerticalRailTraces()
12
+ traces[1] = {
13
+ ...traces[1]!,
14
+ pins: traces[1]!.pins.map((item) => ({ ...item, chipId: "U2" })) as [
15
+ SolvedTracePath["pins"][0],
16
+ SolvedTracePath["pins"][1],
17
+ ],
18
+ }
19
+ const inputProblem: InputProblem = {
20
+ ...verticalProblem,
21
+ chips: [
22
+ ...verticalProblem.chips,
23
+ {
24
+ ...verticalProblem.chips[0]!,
25
+ chipId: "U2",
26
+ pins: verticalProblem.chips[0]!.pins.map((item) => ({
27
+ ...item,
28
+ pinId: item.pinId.replace("U1", "U2"),
29
+ })),
30
+ },
31
+ ],
32
+ }
33
+
34
+ const result = align(traces, { inputProblem })
35
+
36
+ expect(result.alignedRailGroupCount).toBe(0)
37
+ expect(result.traces).toEqual(traces)
38
+ })
@@ -0,0 +1,28 @@
1
+ import { expect, test } from "bun:test"
2
+ import {
3
+ align,
4
+ createTrace,
5
+ getVerticalPin,
6
+ getVerticalRailTraces,
7
+ } from "./fixtures/alignSameNetRails"
8
+
9
+ test("uses routed-trace provenance to exclude generated label connectors", () => {
10
+ const realTrace = getVerticalRailTraces()[0]!
11
+ const connector = createTrace(
12
+ "label-connector",
13
+ [
14
+ { x: -1, y: 0 },
15
+ { x: -5, y: 0 },
16
+ { x: -5, y: -2 },
17
+ { x: -1, y: -2 },
18
+ ],
19
+ [getVerticalPin("U1.2"), getVerticalPin("U1.3")],
20
+ )
21
+
22
+ const result = align([realTrace, connector], {
23
+ eligibleTraceIds: new Set([realTrace.mspPairId]),
24
+ })
25
+
26
+ expect(result.alignedRailGroupCount).toBe(0)
27
+ expect(result.traces).toEqual([realTrace, connector])
28
+ })
@@ -0,0 +1,62 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+ import {
4
+ align,
5
+ createTrace,
6
+ verticalProblem,
7
+ } from "./fixtures/alignSameNetRails"
8
+
9
+ test("applies the same component-side rule to horizontal rails", () => {
10
+ const inputProblem: InputProblem = {
11
+ ...verticalProblem,
12
+ chips: [
13
+ {
14
+ chipId: "U2",
15
+ center: { x: 0, y: 0 },
16
+ width: 6,
17
+ height: 2,
18
+ pins: [
19
+ { pinId: "U2.1", x: -2, y: 1, _facingDirection: "y+" },
20
+ { pinId: "U2.2", x: 0, y: 1, _facingDirection: "y+" },
21
+ { pinId: "U2.3", x: 2, y: 1, _facingDirection: "y+" },
22
+ ],
23
+ },
24
+ ],
25
+ }
26
+ const inputPins = inputProblem.chips[0]!.pins.map((inputPin) => ({
27
+ ...inputPin,
28
+ chipId: "U2",
29
+ }))
30
+ const traces = [
31
+ createTrace(
32
+ "left",
33
+ [
34
+ { x: -2, y: 1 },
35
+ { x: -2, y: 2 },
36
+ { x: 0, y: 2 },
37
+ { x: 0, y: 1 },
38
+ ],
39
+ [inputPins[0]!, inputPins[1]!],
40
+ ),
41
+ createTrace(
42
+ "right",
43
+ [
44
+ { x: 0, y: 1 },
45
+ { x: 0, y: 3 },
46
+ { x: 2, y: 3 },
47
+ { x: 2, y: 1 },
48
+ ],
49
+ [inputPins[1]!, inputPins[2]!],
50
+ ),
51
+ ]
52
+
53
+ const result = align(traces, { inputProblem })
54
+
55
+ expect(result.alignedRailGroupCount).toBe(1)
56
+ expect(result.traces[1]!.tracePath).toEqual([
57
+ { x: 0, y: 1 },
58
+ { x: 0, y: 2 },
59
+ { x: 2, y: 2 },
60
+ { x: 2, y: 1 },
61
+ ])
62
+ })
@@ -0,0 +1,36 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import { align, getVerticalRailTraces } from "./fixtures/alignSameNetRails"
4
+
5
+ test("preserves existing label anchors", () => {
6
+ const traces = getVerticalRailTraces()
7
+ const labels: NetLabelPlacement[] = [
8
+ {
9
+ globalConnNetId: "power-net",
10
+ netId: "POWER",
11
+ mspConnectionPairIds: ["upper"],
12
+ pinIds: ["U1.1", "U1.2"],
13
+ orientation: "x+",
14
+ anchorPoint: { x: -2, y: 1 },
15
+ center: { x: -1.8, y: 1 },
16
+ width: 0.4,
17
+ height: 0.2,
18
+ },
19
+ {
20
+ globalConnNetId: "power-net",
21
+ netId: "POWER",
22
+ mspConnectionPairIds: ["lower"],
23
+ pinIds: ["U1.2", "U1.3"],
24
+ orientation: "x+",
25
+ anchorPoint: { x: -3, y: -1 },
26
+ center: { x: -2.8, y: -1 },
27
+ width: 0.4,
28
+ height: 0.2,
29
+ },
30
+ ]
31
+
32
+ const result = align(traces, { netLabelPlacements: labels })
33
+
34
+ expect(result.alignedRailGroupCount).toBe(0)
35
+ expect(result.traces).toEqual(traces)
36
+ })
@@ -0,0 +1,42 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import {
4
+ align,
5
+ createTrace,
6
+ getVerticalRailTraces,
7
+ } from "./fixtures/alignSameNetRails"
8
+
9
+ test("preserves every trace joined at a net-label anchor", () => {
10
+ const rails = getVerticalRailTraces()
11
+ const anchorKeeper = createTrace(
12
+ "anchor-keeper",
13
+ [
14
+ { x: -3, y: -1 },
15
+ { x: -4, y: -1 },
16
+ ],
17
+ [
18
+ { pinId: "X1.1", chipId: "X1", x: -3, y: -1 },
19
+ { pinId: "X2.1", chipId: "X2", x: -4, y: -1 },
20
+ ],
21
+ )
22
+ const traces = [...rails, anchorKeeper]
23
+ const label: NetLabelPlacement = {
24
+ globalConnNetId: "power-net",
25
+ netId: "POWER",
26
+ mspConnectionPairIds: [anchorKeeper.mspPairId],
27
+ pinIds: anchorKeeper.pinIds,
28
+ orientation: "x-",
29
+ anchorPoint: { x: -3, y: -1 },
30
+ center: { x: -3.2, y: -1 },
31
+ width: 0.4,
32
+ height: 0.2,
33
+ }
34
+
35
+ const result = align(traces, {
36
+ netLabelPlacements: [label],
37
+ eligibleTraceIds: new Set(rails.map((trace) => trace.mspPairId)),
38
+ })
39
+
40
+ expect(result.alignedRailGroupCount).toBe(0)
41
+ expect(result.traces).toEqual(traces)
42
+ })