@tscircuit/schematic-trace-solver 0.0.183 → 0.0.185

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 (109) hide show
  1. package/dist/index.js +242 -24
  2. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +30 -12
  3. package/lib/solvers/SameNetJunctionAlignmentSolver/collapseRedundantSharedEndpointStubs.ts +267 -0
  4. package/lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts +1 -1
  5. package/lib/solvers/SameNetJunctionAlignmentSolver/getSharedPin.ts +13 -0
  6. package/lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts +12 -4
  7. package/package.json +1 -1
  8. package/site/bug-reports/bug-report-20260905T041712Z.page.tsx +4 -0
  9. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +2 -2
  10. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +2 -2
  11. package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +2 -2
  12. package/tests/bug-reports/bug-report-20260707T020342Z/bug-report-20260707T020342Z.test.ts +10 -0
  13. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +524 -533
  14. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +2 -2
  15. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +2 -2
  16. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +2 -2
  17. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +2 -2
  18. package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +2 -2
  19. package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +2 -2
  20. package/tests/bug-reports/bug-report-20260716T144856Z/__snapshots__/bug-report-20260716T144856Z.snap.svg +2 -2
  21. package/tests/bug-reports/bug-report-20260717T022934Z/__snapshots__/bug-report-20260717T022934Z.snap.svg +2 -2
  22. package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +2 -2
  23. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
  24. package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +2 -2
  25. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +2 -2
  26. package/tests/bug-reports/bug-report-20260728T144234Z/bug-report-20260728T144234Z.test.ts +2 -0
  27. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
  28. package/tests/bug-reports/bug-report-20260730T061837Z/bug-report-20260730T061837Z.test.ts +1 -1
  29. package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +2 -2
  30. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
  31. package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +2 -2
  32. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +2 -2
  33. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +2 -2
  34. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +2 -2
  35. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +2 -2
  36. package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +2 -2
  37. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +2 -2
  38. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +2 -2
  39. package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +2 -2
  40. package/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg +2 -2
  41. package/tests/bug-reports/bug-report-20260901T134241Z/__snapshots__/bug-report-20260901T134241Z.snap.svg +2 -2
  42. package/tests/bug-reports/bug-report-20260902T092425Z/__snapshots__/bug-report-20260902T092425Z.snap.svg +2 -2
  43. package/tests/bug-reports/bug-report-20260905T041712Z/__snapshots__/bug-report-20260905T041712Z.snap.svg +546 -0
  44. package/tests/bug-reports/bug-report-20260905T041712Z/bug-report-20260905T041712Z.json +2023 -0
  45. package/tests/bug-reports/bug-report-20260905T041712Z/bug-report-20260905T041712Z.test.ts +33 -0
  46. package/tests/examples/__snapshots__/example02.snap.svg +2 -2
  47. package/tests/examples/__snapshots__/example13.snap.svg +2 -2
  48. package/tests/examples/__snapshots__/example14.snap.svg +2 -2
  49. package/tests/examples/__snapshots__/example15.snap.svg +2 -2
  50. package/tests/examples/__snapshots__/example18.snap.svg +2 -2
  51. package/tests/examples/__snapshots__/example19.snap.svg +2 -2
  52. package/tests/examples/__snapshots__/example21.snap.svg +2 -2
  53. package/tests/examples/__snapshots__/example27.snap.svg +2 -2
  54. package/tests/examples/__snapshots__/example29.snap.svg +2 -2
  55. package/tests/examples/__snapshots__/example42.snap.svg +2 -2
  56. package/tests/examples/__snapshots__/example45.snap.svg +2 -2
  57. package/tests/examples/__snapshots__/example46.snap.svg +2 -2
  58. package/tests/examples/__snapshots__/example47.snap.svg +2 -2
  59. package/tests/examples/__snapshots__/example51.snap.svg +2 -2
  60. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +2 -2
  61. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +2 -2
  62. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +2 -2
  63. package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +2 -2
  64. package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +2 -2
  65. package/tests/repros/__snapshots__/repro-battery-management-bq24073.snap.svg +2 -2
  66. package/tests/repros/__snapshots__/repro-bc107a-common-emitter-routing.snap.svg +2 -2
  67. package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +2 -2
  68. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +2 -2
  69. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +2 -2
  70. package/tests/repros/__snapshots__/repro-board-8090-power-section.snap.svg +2 -2
  71. package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +2 -2
  72. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
  73. package/tests/repros/__snapshots__/repro-bq40z60-current-sense.snap.svg +2 -2
  74. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +2 -2
  75. package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +2 -2
  76. package/tests/repros/__snapshots__/repro-esp12f-section.snap.svg +508 -0
  77. package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +2 -2
  78. package/tests/repros/__snapshots__/repro-hdc2080-ground-label-alignment.snap.svg +2 -2
  79. package/tests/repros/__snapshots__/repro-hdc3020-ground-label-alignment.snap.svg +2 -2
  80. package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +2 -2
  81. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +2 -2
  82. package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +2 -2
  83. package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +2 -2
  84. package/tests/repros/__snapshots__/repro-pga300-c6-trace-alignment.snap.svg +2 -2
  85. package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +2 -2
  86. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +18 -18
  87. package/tests/repros/__snapshots__/repro-power-group-rail-labels.snap.svg +2 -2
  88. package/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg +2 -2
  89. package/tests/repros/__snapshots__/repro-rectifier-trace-overlap.snap.svg +2 -2
  90. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +2 -2
  91. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +2 -2
  92. package/tests/repros/__snapshots__/repro-smartwatch-power-sheet.snap.svg +2 -2
  93. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +2 -2
  94. package/tests/repros/__snapshots__/repro-tida-01389-hbridge.snap.svg +2 -2
  95. package/tests/repros/__snapshots__/repro-tida00553-j4-cell-divider.snap.svg +2 -2
  96. package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +2 -2
  97. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +2 -2
  98. package/tests/repros/__snapshots__/repro-tmp1075-ground-label-overlap.snap.svg +2 -2
  99. package/tests/repros/__snapshots__/repro-tps61222-trace-intersection.snap.svg +2 -2
  100. package/tests/repros/__snapshots__/repro-trellis-core-gnd-net-label-overlap.snap.svg +2 -2
  101. package/tests/repros/__snapshots__/repro-type-c-vbus-led-ground-label.snap.svg +2 -2
  102. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +2 -2
  103. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +2 -2
  104. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +2 -2
  105. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
  106. package/tests/repros/assets/repro-esp12f-status-section.input.json +1394 -0
  107. package/tests/repros/repro-esp12f-section.test.ts +12 -0
  108. package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-redundant-shared-endpoint-stubs.test.ts +101 -0
  109. package/tests/solvers/UnroutedTraceRecoverySolver/local-elbow-obstacles.test.ts +74 -0
@@ -0,0 +1,267 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { PinId } from "lib/types/InputProblem"
3
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
5
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
+ import {
7
+ nearlyEqual,
8
+ pointsEqual,
9
+ } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
10
+ import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
11
+ import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
12
+ import { getSharedPin } from "./getSharedPin"
13
+
14
+ const getSegmentAxis = (start: Point, end: Point) => {
15
+ if (nearlyEqual(start.x, end.x)) return "y" as const
16
+ if (nearlyEqual(start.y, end.y)) return "x" as const
17
+ return null
18
+ }
19
+
20
+ const getPathFromSharedPin = ({
21
+ trace,
22
+ sharedPinId,
23
+ }: {
24
+ trace: SolvedTracePath
25
+ sharedPinId: PinId
26
+ }) => {
27
+ const tracePath = simplifyPath(trace.tracePath)
28
+ if (trace.pins[0]?.pinId === sharedPinId) return tracePath
29
+ return tracePath.reverse()
30
+ }
31
+
32
+ const traceCanLoseSharedPrefix = ({
33
+ trace,
34
+ sharedPrefix,
35
+ netLabelPlacements,
36
+ }: {
37
+ trace: SolvedTracePath
38
+ sharedPrefix: Point[]
39
+ netLabelPlacements: NetLabelPlacement[]
40
+ }) => {
41
+ return !netLabelPlacements.some(
42
+ (label) =>
43
+ label.mspConnectionPairIds.includes(trace.mspPairId) &&
44
+ tracePathContainsPoint(sharedPrefix, label.anchorPoint),
45
+ )
46
+ }
47
+
48
+ const getTraceWithoutSharedPrefix = ({
49
+ trace,
50
+ pathFromSharedPin,
51
+ sharedPinId,
52
+ branchPoint,
53
+ }: {
54
+ trace: SolvedTracePath
55
+ pathFromSharedPin: Point[]
56
+ sharedPinId: PinId
57
+ branchPoint: Point
58
+ }) => {
59
+ const branchSegmentIndex = pathFromSharedPin.findIndex((point, index) => {
60
+ const nextPoint = pathFromSharedPin[index + 1]
61
+ if (!nextPoint) return false
62
+ return tracePathContainsPoint([point, nextPoint], branchPoint)
63
+ })
64
+ if (branchSegmentIndex < 0) return null
65
+ const pathFromJunction = simplifyPath([
66
+ branchPoint,
67
+ ...pathFromSharedPin.slice(branchSegmentIndex + 1),
68
+ ])
69
+ let tracePath = pathFromJunction
70
+ if (trace.pins[0]?.pinId !== sharedPinId) {
71
+ tracePath = pathFromJunction.reverse()
72
+ }
73
+ return { ...trace, tracePath }
74
+ }
75
+
76
+ export const collapseRedundantSharedEndpointStubs = ({
77
+ traces,
78
+ netLabelPlacements,
79
+ netLabelConnectorTraceIds,
80
+ multiPinNetPinIds,
81
+ }: {
82
+ traces: SolvedTracePath[]
83
+ netLabelPlacements: NetLabelPlacement[]
84
+ netLabelConnectorTraceIds: ReadonlySet<MspConnectionPairId>
85
+ multiPinNetPinIds: ReadonlySet<PinId>
86
+ }) => {
87
+ const outputTraces = [...traces]
88
+
89
+ for (let firstIndex = 0; firstIndex < outputTraces.length; firstIndex++) {
90
+ const firstTrace = outputTraces[firstIndex]!
91
+ if (netLabelConnectorTraceIds.has(firstTrace.mspPairId)) continue
92
+ for (
93
+ let secondIndex = firstIndex + 1;
94
+ secondIndex < outputTraces.length;
95
+ secondIndex++
96
+ ) {
97
+ const secondTrace = outputTraces[secondIndex]!
98
+ if (
99
+ netLabelConnectorTraceIds.has(secondTrace.mspPairId) ||
100
+ firstTrace.globalConnNetId !== secondTrace.globalConnNetId
101
+ ) {
102
+ continue
103
+ }
104
+ const sharedPin = getSharedPin({
105
+ donorTrace: firstTrace,
106
+ branchTrace: secondTrace,
107
+ })
108
+ if (!sharedPin || !multiPinNetPinIds.has(sharedPin.pinId)) continue
109
+ const firstPath = getPathFromSharedPin({
110
+ trace: firstTrace,
111
+ sharedPinId: sharedPin.pinId,
112
+ })
113
+ const secondPath = getPathFromSharedPin({
114
+ trace: secondTrace,
115
+ sharedPinId: sharedPin.pinId,
116
+ })
117
+ if (firstPath.length < 2 || secondPath.length < 2) continue
118
+
119
+ const departureAxis = getSegmentAxis(firstPath[0]!, firstPath[1]!)
120
+ const secondDepartureAxis = getSegmentAxis(secondPath[0]!, secondPath[1]!)
121
+ if (!departureAxis || departureAxis !== secondDepartureAxis) continue
122
+
123
+ const firstDepartureDirection =
124
+ firstPath[1]![departureAxis] - firstPath[0]![departureAxis]
125
+ const secondDepartureDirection =
126
+ secondPath[1]![departureAxis] - secondPath[0]![departureAxis]
127
+ if (
128
+ !pointsEqual(firstPath[1]!, secondPath[1]!) &&
129
+ Math.sign(firstDepartureDirection) ===
130
+ Math.sign(secondDepartureDirection)
131
+ ) {
132
+ let traceIndexToCollapse = secondIndex
133
+ let traceToCollapse = secondTrace
134
+ let pathToCollapse = secondPath
135
+ if (
136
+ Math.abs(firstDepartureDirection) < Math.abs(secondDepartureDirection)
137
+ ) {
138
+ traceIndexToCollapse = firstIndex
139
+ traceToCollapse = firstTrace
140
+ pathToCollapse = firstPath
141
+ }
142
+ const branchPoint = pathToCollapse[1]!
143
+ if (
144
+ !traceCanLoseSharedPrefix({
145
+ trace: traceToCollapse,
146
+ sharedPrefix: [pathToCollapse[0]!, branchPoint],
147
+ netLabelPlacements,
148
+ })
149
+ ) {
150
+ if (traceIndexToCollapse === firstIndex) {
151
+ traceIndexToCollapse = secondIndex
152
+ traceToCollapse = secondTrace
153
+ pathToCollapse = secondPath
154
+ } else {
155
+ traceIndexToCollapse = firstIndex
156
+ traceToCollapse = firstTrace
157
+ pathToCollapse = firstPath
158
+ }
159
+ }
160
+ if (
161
+ !traceCanLoseSharedPrefix({
162
+ trace: traceToCollapse,
163
+ sharedPrefix: [pathToCollapse[0]!, branchPoint],
164
+ netLabelPlacements,
165
+ })
166
+ ) {
167
+ continue
168
+ }
169
+ const collapsedTrace = getTraceWithoutSharedPrefix({
170
+ trace: traceToCollapse,
171
+ pathFromSharedPin: pathToCollapse,
172
+ sharedPinId: sharedPin.pinId,
173
+ branchPoint,
174
+ })
175
+ if (!collapsedTrace) continue
176
+ outputTraces[traceIndexToCollapse] = collapsedTrace
177
+ break
178
+ }
179
+ if (firstPath.length < 3 || secondPath.length < 3) continue
180
+ if (!pointsEqual(firstPath[1]!, secondPath[1]!)) continue
181
+
182
+ const railAxis = getSegmentAxis(firstPath[1]!, firstPath[2]!)
183
+ const secondRailAxis = getSegmentAxis(secondPath[1]!, secondPath[2]!)
184
+ if (
185
+ !railAxis ||
186
+ railAxis !== secondRailAxis ||
187
+ railAxis === departureAxis
188
+ ) {
189
+ continue
190
+ }
191
+ const firstRailDirection =
192
+ firstPath[2]![railAxis] - firstPath[1]![railAxis]
193
+ const secondRailDirection =
194
+ secondPath[2]![railAxis] - secondPath[1]![railAxis]
195
+ const railsHaveSameDirection =
196
+ Math.sign(firstRailDirection) === Math.sign(secondRailDirection)
197
+ const sharedStub = firstPath.slice(0, 2)
198
+ if (
199
+ !railsHaveSameDirection &&
200
+ !nearlyEqual(
201
+ Math.abs(firstRailDirection),
202
+ Math.abs(secondRailDirection),
203
+ ) &&
204
+ netLabelPlacements.some(
205
+ (label) =>
206
+ label.globalConnNetId === firstTrace.globalConnNetId &&
207
+ tracePathContainsPoint(sharedStub, label.anchorPoint),
208
+ )
209
+ ) {
210
+ continue
211
+ }
212
+ let traceIndexToCollapse = secondIndex
213
+ let traceToCollapse = secondTrace
214
+ let pathToCollapse = secondPath
215
+ let branchPoint = firstPath[1]!
216
+ if (
217
+ railsHaveSameDirection &&
218
+ Math.abs(firstRailDirection) < Math.abs(secondRailDirection)
219
+ ) {
220
+ traceIndexToCollapse = firstIndex
221
+ traceToCollapse = firstTrace
222
+ pathToCollapse = firstPath
223
+ branchPoint = firstPath[2]!
224
+ } else if (railsHaveSameDirection) {
225
+ branchPoint = secondPath[2]!
226
+ }
227
+ const sharedPrefix = [pathToCollapse[0]!, pathToCollapse[1]!, branchPoint]
228
+ if (
229
+ !traceCanLoseSharedPrefix({
230
+ trace: traceToCollapse,
231
+ sharedPrefix,
232
+ netLabelPlacements,
233
+ })
234
+ ) {
235
+ if (traceIndexToCollapse === firstIndex) {
236
+ traceIndexToCollapse = secondIndex
237
+ traceToCollapse = secondTrace
238
+ pathToCollapse = secondPath
239
+ } else {
240
+ traceIndexToCollapse = firstIndex
241
+ traceToCollapse = firstTrace
242
+ pathToCollapse = firstPath
243
+ }
244
+ }
245
+ if (
246
+ !traceCanLoseSharedPrefix({
247
+ trace: traceToCollapse,
248
+ sharedPrefix: [pathToCollapse[0]!, pathToCollapse[1]!, branchPoint],
249
+ netLabelPlacements,
250
+ })
251
+ ) {
252
+ continue
253
+ }
254
+ const collapsedTrace = getTraceWithoutSharedPrefix({
255
+ trace: traceToCollapse,
256
+ pathFromSharedPin: pathToCollapse,
257
+ sharedPinId: sharedPin.pinId,
258
+ branchPoint,
259
+ })
260
+ if (!collapsedTrace) continue
261
+ outputTraces[traceIndexToCollapse] = collapsedTrace
262
+ break
263
+ }
264
+ }
265
+
266
+ return outputTraces
267
+ }
@@ -11,7 +11,7 @@ import {
11
11
  pointsEqual,
12
12
  } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
13
13
  import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
14
- import { getSharedPin } from "./alignSameNetJunctions"
14
+ import { getSharedPin } from "./getSharedPin"
15
15
  import {
16
16
  pathEntersAnyNetLabel,
17
17
  pathIntersectsAnyNetLabel,
@@ -0,0 +1,13 @@
1
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
2
+ import type { InputPin } from "lib/types/InputProblem"
3
+
4
+ export const getSharedPin = ({
5
+ donorTrace,
6
+ branchTrace,
7
+ }: {
8
+ donorTrace: SolvedTracePath
9
+ branchTrace: SolvedTracePath
10
+ }): (InputPin & { chipId: string }) | null => {
11
+ const branchPinIds = new Set(branchTrace.pins.map((pin) => pin.pinId))
12
+ return donorTrace.pins.find((pin) => branchPinIds.has(pin.pinId)) ?? null
13
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
2
  import { distance, doSegmentsIntersect } from "@tscircuit/math-utils"
3
+ import { calculateElbow } from "calculate-elbow"
3
4
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
4
5
  import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
5
6
  import {
@@ -86,7 +87,7 @@ const getOuterBounds = (obstacles: ObstacleRect[]) => {
86
87
  }
87
88
  }
88
89
 
89
- const getPerimeterCandidates = ({
90
+ const getPinConnectionCandidates = ({
90
91
  connectionPair,
91
92
  obstacles,
92
93
  }: {
@@ -111,7 +112,14 @@ const getPerimeterCandidates = ({
111
112
  outerBounds.minX - ROUTE_CLEARANCE,
112
113
  outerBounds.maxX + ROUTE_CLEARANCE,
113
114
  ]
114
- const candidates: Point[][] = []
115
+ // Recovery uses actual text bounds, so try local elbows before the perimeter.
116
+ const candidates: Point[][] = [
117
+ calculateElbow(
118
+ { ...firstPin, facingDirection: firstPin._facingDirection! },
119
+ { ...secondPin, facingDirection: secondPin._facingDirection! },
120
+ { overshoot: ROUTE_CLEARANCE },
121
+ ),
122
+ ]
115
123
 
116
124
  for (const channelY of horizontalChannels) {
117
125
  candidates.push(
@@ -526,11 +534,11 @@ export class UnroutedTraceRecoverySolver extends BaseSolver {
526
534
  obstacles,
527
535
  maxConnectionDistance: this.maxConnectionDistance,
528
536
  })
529
- const perimeterCandidates = getPerimeterCandidates({
537
+ const pinConnectionCandidates = getPinConnectionCandidates({
530
538
  connectionPair,
531
539
  obstacles,
532
540
  })
533
- const candidates = [...junctionCandidates, ...perimeterCandidates]
541
+ const candidates = [...junctionCandidates, ...pinConnectionCandidates]
534
542
  const rejectComponentBoundaryTravel = hasParallelFailedConnection({
535
543
  connectionPair,
536
544
  failedConnectionPairs: this.failedConnectionPairs,
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
6
  },
7
7
  "main": "dist/index.js",
8
- "version": "0.0.183",
8
+ "version": "0.0.185",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260905T041712Z/bug-report-20260905T041712Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />