@tscircuit/schematic-trace-solver 0.0.149 → 0.0.151

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 (162) hide show
  1. package/.agents/skills/schematic-trace-visualization/SKILL.md +7 -2
  2. package/README.md +6 -4
  3. package/dist/index.d.ts +64 -3
  4. package/dist/index.js +383 -66
  5. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +123 -84
  6. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +17 -1
  7. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +2 -0
  8. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts +2 -0
  9. package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +338 -0
  10. package/lib/solvers/NetLabelTraceRecovery/getTraceRecoveryConnectivityMaps.ts +33 -0
  11. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +15 -0
  12. package/lib/types/InputProblem.ts +27 -1
  13. package/package.json +8 -1
  14. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +751 -5
  15. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +660 -5
  16. package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +44 -5
  17. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +394 -5
  18. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +170 -5
  19. package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +79 -5
  20. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +219 -5
  21. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +65 -5
  22. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +142 -5
  23. package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +100 -5
  24. package/tests/bug-reports/bug-report-20260708T053736Z/__snapshots__/bug-report-20260708T053736Z.snap.svg +23 -5
  25. package/tests/bug-reports/bug-report-20260708T055430Z/__snapshots__/bug-report-20260708T055430Z.snap.svg +58 -5
  26. package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +86 -5
  27. package/tests/bug-reports/bug-report-20260716T144856Z/__snapshots__/bug-report-20260716T144856Z.snap.svg +226 -5
  28. package/tests/bug-reports/bug-report-20260717T022934Z/__snapshots__/bug-report-20260717T022934Z.snap.svg +191 -5
  29. package/tests/bug-reports/bug-report-20260717T031704Z/__snapshots__/bug-report-20260717T031704Z.snap.svg +30 -5
  30. package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +37 -5
  31. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +580 -2
  32. package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +44 -5
  33. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +107 -5
  34. package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +114 -5
  35. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +121 -5
  36. package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +51 -5
  37. package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +212 -5
  38. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +569 -5
  39. package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +37 -5
  40. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +289 -5
  41. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +289 -5
  42. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +149 -5
  43. package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +93 -5
  44. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +79 -5
  45. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +244 -2
  46. package/tests/examples/__snapshots__/example01.snap.svg +44 -5
  47. package/tests/examples/__snapshots__/example02.snap.svg +51 -5
  48. package/tests/examples/__snapshots__/example03.snap.svg +86 -5
  49. package/tests/examples/__snapshots__/example04.snap.svg +30 -5
  50. package/tests/examples/__snapshots__/example05.snap.svg +30 -5
  51. package/tests/examples/__snapshots__/example06.snap.svg +30 -5
  52. package/tests/examples/__snapshots__/example07.snap.svg +58 -5
  53. package/tests/examples/__snapshots__/example08.snap.svg +37 -5
  54. package/tests/examples/__snapshots__/example09.snap.svg +149 -5
  55. package/tests/examples/__snapshots__/example10.snap.svg +65 -5
  56. package/tests/examples/__snapshots__/example11.snap.svg +51 -5
  57. package/tests/examples/__snapshots__/example12.snap.svg +51 -5
  58. package/tests/examples/__snapshots__/example13.snap.svg +72 -5
  59. package/tests/examples/__snapshots__/example14.snap.svg +79 -5
  60. package/tests/examples/__snapshots__/example15.snap.svg +86 -5
  61. package/tests/examples/__snapshots__/example16.snap.svg +51 -5
  62. package/tests/examples/__snapshots__/example17.snap.svg +51 -5
  63. package/tests/examples/__snapshots__/example18.snap.svg +58 -5
  64. package/tests/examples/__snapshots__/example19.snap.svg +44 -5
  65. package/tests/examples/__snapshots__/example20.snap.svg +44 -5
  66. package/tests/examples/__snapshots__/example21.snap.svg +72 -5
  67. package/tests/examples/__snapshots__/example22.snap.svg +51 -5
  68. package/tests/examples/__snapshots__/example23.snap.svg +51 -5
  69. package/tests/examples/__snapshots__/example24.snap.svg +51 -5
  70. package/tests/examples/__snapshots__/example25.snap.svg +86 -5
  71. package/tests/examples/__snapshots__/example26.snap.svg +51 -5
  72. package/tests/examples/__snapshots__/example27.snap.svg +72 -5
  73. package/tests/examples/__snapshots__/example28.snap.svg +44 -5
  74. package/tests/examples/__snapshots__/example29.snap.svg +324 -5
  75. package/tests/examples/__snapshots__/example30.snap.svg +107 -5
  76. package/tests/examples/__snapshots__/example31.snap.svg +37 -5
  77. package/tests/examples/__snapshots__/example32.snap.svg +149 -5
  78. package/tests/examples/__snapshots__/example33.snap.svg +93 -5
  79. package/tests/examples/__snapshots__/example34.snap.svg +51 -5
  80. package/tests/examples/__snapshots__/example35.snap.svg +58 -5
  81. package/tests/examples/__snapshots__/example36.snap.svg +44 -5
  82. package/tests/examples/__snapshots__/example37.snap.svg +72 -5
  83. package/tests/examples/__snapshots__/example38.snap.svg +30 -5
  84. package/tests/examples/__snapshots__/example39.snap.svg +37 -5
  85. package/tests/examples/__snapshots__/example40.snap.svg +100 -5
  86. package/tests/examples/__snapshots__/example41.snap.svg +44 -5
  87. package/tests/examples/__snapshots__/example42.snap.svg +65 -5
  88. package/tests/examples/__snapshots__/example43.snap.svg +58 -5
  89. package/tests/examples/__snapshots__/example44.snap.svg +107 -5
  90. package/tests/examples/__snapshots__/example45.snap.svg +51 -5
  91. package/tests/examples/__snapshots__/example46.snap.svg +93 -5
  92. package/tests/examples/__snapshots__/example47.snap.svg +93 -5
  93. package/tests/examples/__snapshots__/example48.snap.svg +65 -5
  94. package/tests/examples/__snapshots__/example49.snap.svg +177 -5
  95. package/tests/examples/__snapshots__/example50.snap.svg +93 -5
  96. package/tests/examples/__snapshots__/example51.snap.svg +594 -21
  97. package/tests/fixtures/convertSolverOutputToCircuitJson.test.ts +219 -0
  98. package/tests/fixtures/convertSolverOutputToCircuitJson.ts +1058 -0
  99. package/tests/fixtures/matcher.ts +45 -8
  100. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +762 -2
  101. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +111 -2
  102. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +170 -5
  103. package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +65 -5
  104. package/tests/repros/__snapshots__/manufacturePartNumber-text-box.snap.svg +30 -5
  105. package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +79 -5
  106. package/tests/repros/__snapshots__/repro-atmega328p-fault-pullup.snap.svg +65 -5
  107. package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +149 -5
  108. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +55 -2
  109. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +160 -2
  110. package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +135 -5
  111. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +69 -2
  112. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +86 -5
  113. package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +44 -5
  114. package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +44 -5
  115. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +30 -5
  116. package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +335 -2
  117. package/tests/repros/__snapshots__/repro-ground-label-on-lower-rail-end.snap.svg +48 -2
  118. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +149 -5
  119. package/tests/repros/__snapshots__/repro-missing-trace-netlabel.snap.svg +72 -5
  120. package/tests/repros/__snapshots__/repro-netlabel-collision-687.snap.svg +100 -5
  121. package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +51 -5
  122. package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +90 -2
  123. package/tests/repros/__snapshots__/repro-power-group-rail-labels.snap.svg +37 -5
  124. package/tests/repros/__snapshots__/repro-rectifier-trace-overlap.snap.svg +44 -5
  125. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +237 -2
  126. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +72 -5
  127. package/tests/repros/__snapshots__/repro-rp2040-v3v3-label-placement.snap.svg +51 -5
  128. package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +30 -5
  129. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +79 -5
  130. package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +27 -2
  131. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1150 -5
  132. package/tests/repros/__snapshots__/repro-tps61222-trace-intersection.snap.svg +51 -5
  133. package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +30 -5
  134. package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +30 -5
  135. package/tests/repros/__snapshots__/repro-type-c-vbus-led-ground-label.snap.svg +44 -5
  136. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +685 -2
  137. package/tests/repros/__snapshots__/repro-vcc-pin1-detour.snap.svg +27 -2
  138. package/tests/repros/__snapshots__/repro129-host-custom-symbol-passives.snap.svg +51 -5
  139. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +100 -5
  140. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +107 -5
  141. package/tests/repros/__snapshots__/repro47-endpoint-obstacle-detour.snap.svg +51 -5
  142. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +104 -2
  143. package/tests/repros/__snapshots__/repro5-escape-padded-text-obstacles.snap.svg +30 -5
  144. package/tests/repros/__snapshots__/repro51-overlap-junction-crossing.snap.svg +65 -5
  145. package/tests/repros/__snapshots__/repro84-stacked-gnd-label-orientation.snap.svg +27 -2
  146. package/tests/repros/__snapshots__/small-variant-resistor-facing-direction.snap.svg +58 -5
  147. package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +37 -5
  148. package/tests/solvers/InlineNetLabelSolver/__snapshots__/fallback-net-label-width.snap.svg +25 -7
  149. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-anchored-label-clearance.snap.svg +34 -2
  150. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-port-stub.snap.svg +23 -5
  151. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-two-pin-net.snap.svg +23 -5
  152. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label01.snap.svg +23 -5
  153. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +23 -5
  154. package/tests/solvers/InlineNetLabelSolver/__snapshots__/terminal-stub-trace-clearance.snap.svg +23 -5
  155. package/tests/solvers/InlineNetLabelSolver/fallback-net-label-width.test.ts +10 -1
  156. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.snap.svg +23 -5
  157. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_repro47-short-opposing-pins.snap.svg +20 -2
  158. package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +44 -5
  159. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +79 -5
  160. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +44 -5
  161. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +23 -5
  162. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +30 -5
@@ -0,0 +1,338 @@
1
+ import {
2
+ doesSegmentIntersectRect,
3
+ getBoundFromCenteredRect,
4
+ type Point,
5
+ } from "@tscircuit/math-utils"
6
+ import type { GraphicsObject } from "graphics-debug"
7
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
8
+ import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
9
+ import { doesPairCrossRestrictedCenterLines } from "lib/solvers/MspConnectionPairSolver/doesPairCrossRestrictedCenterLines"
10
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
11
+ import {
12
+ getTraceRecoveryConnectivityMaps,
13
+ type TraceRecoveryPin,
14
+ } from "lib/solvers/NetLabelTraceRecovery/getTraceRecoveryConnectivityMaps"
15
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
16
+ import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
17
+ import type {
18
+ ChipId,
19
+ InputChip,
20
+ InputProblem,
21
+ PinId,
22
+ } from "lib/types/InputProblem"
23
+ import { arePinsInDifferentSchematicSections } from "lib/utils/arePinsInDifferentSchematicSections"
24
+ import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces"
25
+ import {
26
+ type InlineNetLabelPlacement,
27
+ type InlineNetLabelOutput,
28
+ visualizeInlineNetLabelOutput,
29
+ } from "../InlineNetLabelSolver/InlineNetLabelSolver"
30
+
31
+ type GlobalConnNetId = NetLabelPlacement["globalConnNetId"]
32
+
33
+ interface CandidatePair {
34
+ firstLabel: NetLabelPlacement
35
+ secondLabel: NetLabelPlacement
36
+ pins: [TraceRecoveryPin, TraceRecoveryPin]
37
+ perpendicularOffset: number
38
+ routeDistance: number
39
+ key: string
40
+ }
41
+
42
+ const AVAILABLE_NET_ORIENTATION_PREFIX = "available-net-orientation-"
43
+ const RECOVERED_TRACE_PREFIX = "net-label-to-trace-"
44
+
45
+ const getCanonicalPairKey = (firstPinId: PinId, secondPinId: PinId) =>
46
+ [firstPinId, secondPinId].sort().join("--")
47
+
48
+ export const pathIntersectsRenderedLabel = (
49
+ path: Point[],
50
+ label: NetLabelPlacement | InlineNetLabelPlacement,
51
+ ) => {
52
+ let width = label.width
53
+ let height = label.height
54
+ if ("axis" in label && label.axis === "y") {
55
+ width = label.height
56
+ height = label.width
57
+ }
58
+ const bounds = getBoundFromCenteredRect({
59
+ center: label.center,
60
+ width,
61
+ height,
62
+ })
63
+ for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
64
+ if (
65
+ doesSegmentIntersectRect(path[pathIndex]!, path[pathIndex + 1]!, bounds)
66
+ ) {
67
+ return true
68
+ }
69
+ }
70
+ return false
71
+ }
72
+
73
+ const getPerpendicularOffset = (
74
+ firstPin: TraceRecoveryPin,
75
+ secondPin: TraceRecoveryPin,
76
+ ) => {
77
+ const xDistance = Math.abs(firstPin.x - secondPin.x)
78
+ const yDistance = Math.abs(firstPin.y - secondPin.y)
79
+ if (xDistance >= yDistance) return yDistance
80
+ return xDistance
81
+ }
82
+
83
+ export class NetLabelToTraceSolver extends BaseSolver {
84
+ inputProblem: InputProblem
85
+
86
+ outputTraces: SolvedTracePath[]
87
+ outputNetLabelPlacements: NetLabelPlacement[]
88
+
89
+ private chipMap: Record<ChipId, InputChip>
90
+ private pinMap: Map<PinId, TraceRecoveryPin>
91
+ private queuedCandidates: CandidatePair[]
92
+ private currentCandidate: CandidatePair | null = null
93
+ declare activeSubSolver: SchematicTraceSingleLineSolver2 | null
94
+
95
+ constructor(private input: InlineNetLabelOutput) {
96
+ super()
97
+ this.inputProblem = input.inputProblem
98
+ this.outputTraces = [...input.traces]
99
+ this.outputNetLabelPlacements = [...input.netLabelPlacements]
100
+
101
+ const { chipMap, pinMap } = getTraceRecoveryConnectivityMaps(
102
+ this.inputProblem,
103
+ )
104
+ this.chipMap = chipMap
105
+ this.pinMap = pinMap
106
+
107
+ this.queuedCandidates = this.buildCandidatePairs()
108
+ this.stats.candidateCount = this.queuedCandidates.length
109
+ this.stats.recoveredTraceCount = 0
110
+ }
111
+
112
+ override getConstructorParams(): [InlineNetLabelOutput] {
113
+ return [this.input]
114
+ }
115
+
116
+ private isEligiblePortOnlyDirectConnectionLabel(
117
+ label: NetLabelPlacement,
118
+ groundGlobalConnNetId?: GlobalConnNetId,
119
+ ) {
120
+ if (
121
+ label.pinIds.length !== 1 ||
122
+ label.mspConnectionPairIds.length !== 0 ||
123
+ !label.netId ||
124
+ label.netId === "GND" ||
125
+ label.globalConnNetId === groundGlobalConnNetId
126
+ ) {
127
+ return false
128
+ }
129
+
130
+ const pinId = label.pinIds[0]!
131
+ return this.inputProblem.directConnections.some(
132
+ (connection) =>
133
+ connection.netId === label.netId && connection.pinIds.includes(pinId),
134
+ )
135
+ }
136
+
137
+ private buildCandidatePairs() {
138
+ const { netConnMap } = getConnectivityMapsFromInputProblem(
139
+ this.inputProblem,
140
+ )
141
+ const groundGlobalConnNetId =
142
+ netConnMap.getNetConnectedToId("GND") ?? undefined
143
+ const labelsByGlobalNet = new Map<GlobalConnNetId, NetLabelPlacement[]>()
144
+
145
+ for (const label of this.input.netLabelPlacements) {
146
+ if (
147
+ !this.isEligiblePortOnlyDirectConnectionLabel(
148
+ label,
149
+ groundGlobalConnNetId,
150
+ )
151
+ ) {
152
+ continue
153
+ }
154
+ const labels = labelsByGlobalNet.get(label.globalConnNetId) ?? []
155
+ labels.push(label)
156
+ labelsByGlobalNet.set(label.globalConnNetId, labels)
157
+ }
158
+
159
+ const candidates: CandidatePair[] = []
160
+ for (const labels of labelsByGlobalNet.values()) {
161
+ for (let firstIndex = 0; firstIndex < labels.length; firstIndex++) {
162
+ for (
163
+ let secondIndex = firstIndex + 1;
164
+ secondIndex < labels.length;
165
+ secondIndex++
166
+ ) {
167
+ const firstLabel = labels[firstIndex]!
168
+ const secondLabel = labels[secondIndex]!
169
+ const firstPin = this.pinMap.get(firstLabel.pinIds[0]!)
170
+ const secondPin = this.pinMap.get(secondLabel.pinIds[0]!)
171
+ if (!firstPin || !secondPin) continue
172
+ if (
173
+ arePinsInDifferentSchematicSections(
174
+ this.inputProblem,
175
+ firstPin,
176
+ secondPin,
177
+ )
178
+ ) {
179
+ continue
180
+ }
181
+ if (
182
+ doesPairCrossRestrictedCenterLines({
183
+ inputProblem: this.inputProblem,
184
+ chipMap: this.chipMap,
185
+ pinIdMap: this.pinMap,
186
+ p1: firstPin,
187
+ p2: secondPin,
188
+ })
189
+ ) {
190
+ continue
191
+ }
192
+
193
+ candidates.push({
194
+ firstLabel,
195
+ secondLabel,
196
+ pins: [firstPin, secondPin],
197
+ perpendicularOffset: getPerpendicularOffset(firstPin, secondPin),
198
+ routeDistance:
199
+ Math.abs(firstPin.x - secondPin.x) +
200
+ Math.abs(firstPin.y - secondPin.y),
201
+ key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
202
+ })
203
+ }
204
+ }
205
+ }
206
+
207
+ candidates.sort(
208
+ (first, second) =>
209
+ first.perpendicularOffset - second.perpendicularOffset ||
210
+ first.routeDistance - second.routeDistance ||
211
+ first.key.localeCompare(second.key),
212
+ )
213
+ return candidates
214
+ }
215
+
216
+ private isSupersededConnectorTrace(
217
+ trace: SolvedTracePath,
218
+ candidate: CandidatePair,
219
+ ) {
220
+ if (!trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_PREFIX)) {
221
+ return false
222
+ }
223
+ if (trace.pinIds.length !== 1) return false
224
+ return candidate.pins.some((pin) => pin.pinId === trace.pinIds[0])
225
+ }
226
+
227
+ private routeIntersectsRemainingLabels(
228
+ tracePath: Point[],
229
+ candidate: CandidatePair,
230
+ ) {
231
+ for (const label of this.outputNetLabelPlacements) {
232
+ if (label === candidate.firstLabel || label === candidate.secondLabel) {
233
+ continue
234
+ }
235
+ if (pathIntersectsRenderedLabel(tracePath, label)) return true
236
+ }
237
+ return this.input.inlineNetLabelPlacements.some((label) =>
238
+ pathIntersectsRenderedLabel(tracePath, label),
239
+ )
240
+ }
241
+
242
+ private tryAcceptCurrentRoute() {
243
+ const candidate = this.currentCandidate
244
+ const tracePath = this.activeSubSolver?.solvedTracePath
245
+ if (!candidate || !tracePath) return
246
+
247
+ const retainedTraces = this.outputTraces.filter(
248
+ (trace) => !this.isSupersededConnectorTrace(trace, candidate),
249
+ )
250
+ if (
251
+ doesTraceOverlapWithExistingTraces(tracePath, retainedTraces) ||
252
+ this.routeIntersectsRemainingLabels(tracePath, candidate)
253
+ ) {
254
+ return
255
+ }
256
+
257
+ const [firstPin, secondPin] = candidate.pins
258
+ const mspPairId = `${RECOVERED_TRACE_PREFIX}${candidate.key}`
259
+ const recoveredTrace: SolvedTracePath = {
260
+ mspPairId,
261
+ dcConnNetId: candidate.firstLabel.globalConnNetId,
262
+ globalConnNetId: candidate.firstLabel.globalConnNetId,
263
+ pins: [firstPin, secondPin],
264
+ tracePath,
265
+ mspConnectionPairIds: [mspPairId],
266
+ pinIds: [firstPin.pinId, secondPin.pinId],
267
+ }
268
+
269
+ this.outputTraces = [...retainedTraces, recoveredTrace]
270
+ this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
271
+ (label) =>
272
+ label !== candidate.firstLabel && label !== candidate.secondLabel,
273
+ )
274
+ this.stats.recoveredTraceCount++
275
+ }
276
+
277
+ override _step() {
278
+ if (this.activeSubSolver) {
279
+ this.activeSubSolver.step()
280
+ if (this.activeSubSolver.solved) {
281
+ this.tryAcceptCurrentRoute()
282
+ this.activeSubSolver = null
283
+ this.currentCandidate = null
284
+ } else if (this.activeSubSolver.failed) {
285
+ this.activeSubSolver = null
286
+ this.currentCandidate = null
287
+ }
288
+ return
289
+ }
290
+
291
+ let candidate = this.queuedCandidates.shift()
292
+ while (
293
+ candidate &&
294
+ (!this.outputNetLabelPlacements.includes(candidate.firstLabel) ||
295
+ !this.outputNetLabelPlacements.includes(candidate.secondLabel))
296
+ ) {
297
+ candidate = this.queuedCandidates.shift()
298
+ }
299
+
300
+ if (!candidate) {
301
+ this.solved = true
302
+ return
303
+ }
304
+
305
+ this.currentCandidate = candidate
306
+ this.activeSubSolver = new SchematicTraceSingleLineSolver2({
307
+ inputProblem: this.inputProblem,
308
+ pins: candidate.pins,
309
+ chipMap: this.chipMap,
310
+ })
311
+ }
312
+
313
+ getOutput() {
314
+ return {
315
+ traces: this.outputTraces,
316
+ netLabelPlacements: this.outputNetLabelPlacements,
317
+ inlineNetLabelPlacements: this.input.inlineNetLabelPlacements,
318
+ }
319
+ }
320
+
321
+ override visualize(): GraphicsObject {
322
+ if (this.activeSubSolver) return this.activeSubSolver.visualize()
323
+
324
+ const graphics = visualizeInlineNetLabelOutput({
325
+ inputProblem: this.inputProblem,
326
+ ...this.getOutput(),
327
+ })
328
+ for (const trace of this.outputTraces) {
329
+ if (!trace.mspPairId.startsWith(RECOVERED_TRACE_PREFIX)) continue
330
+ graphics.lines!.push({
331
+ points: trace.tracePath,
332
+ strokeColor: "green",
333
+ label: `recovered from net labels: ${trace.pinIds.join(" -> ")}`,
334
+ })
335
+ }
336
+ return graphics
337
+ }
338
+ }
@@ -0,0 +1,33 @@
1
+ import { getPinDirection } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
2
+ import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
3
+ import type {
4
+ ChipId,
5
+ InputChip,
6
+ InputProblem,
7
+ PinId,
8
+ } from "lib/types/InputProblem"
9
+ import type { FacingDirection } from "lib/utils/dir"
10
+
11
+ export type TraceRecoveryPin = MspConnectionPair["pins"][number] & {
12
+ _facingDirection: FacingDirection
13
+ }
14
+
15
+ export const getTraceRecoveryConnectivityMaps = (
16
+ inputProblem: InputProblem,
17
+ ) => {
18
+ const chipMap: Record<ChipId, InputChip> = {}
19
+ const pinMap = new Map<PinId, TraceRecoveryPin>()
20
+
21
+ for (const chip of inputProblem.chips) {
22
+ chipMap[chip.chipId] = chip
23
+ for (const pin of chip.pins) {
24
+ pinMap.set(pin.pinId, {
25
+ ...pin,
26
+ chipId: chip.chipId,
27
+ _facingDirection: pin._facingDirection ?? getPinDirection(pin, chip),
28
+ })
29
+ }
30
+ }
31
+
32
+ return { chipMap, pinMap }
33
+ }
@@ -35,6 +35,7 @@ import { UnroutedTraceRecoverySolver } from "../UnroutedTraceRecoverySolver/Unro
35
35
  import { SameNetJunctionAlignmentSolver } from "../SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver"
36
36
  import { TraceElbowTransitionSimplificationSolver } from "../TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver"
37
37
  import { InlineNetLabelSolver } from "../InlineNetLabelSolver/InlineNetLabelSolver"
38
+ import { NetLabelToTraceSolver } from "../NetLabelToTraceSolver/NetLabelToTraceSolver"
38
39
  import { findPerpendicularPathCrossings } from "../TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles"
39
40
 
40
41
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
@@ -103,6 +104,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
103
104
  finalTraceElbowTransitionSimplificationSolver?: TraceElbowTransitionSimplificationSolver
104
105
  sameNetJunctionAlignmentSolver?: SameNetJunctionAlignmentSolver
105
106
  inlineNetLabelSolver?: InlineNetLabelSolver
107
+ netLabelToTraceSolver?: NetLabelToTraceSolver
106
108
 
107
109
  startTimeOfPhase: Record<string, number>
108
110
  endTimeOfPhase: Record<string, number>
@@ -588,6 +590,19 @@ export class SchematicTracePipelineSolver extends BaseSolver {
588
590
  ]
589
591
  },
590
592
  ),
593
+ definePipelineStep(
594
+ "netLabelToTraceSolver",
595
+ NetLabelToTraceSolver,
596
+ (instance) => {
597
+ const inlineOutput = instance.inlineNetLabelSolver!.getOutput()
598
+ return [
599
+ {
600
+ inputProblem: instance.inputProblem,
601
+ ...inlineOutput,
602
+ },
603
+ ]
604
+ },
605
+ ),
591
606
  ]
592
607
 
593
608
  constructor(inputProblem: InputProblem, opts?: Options) {
@@ -24,6 +24,15 @@ export interface TextBoxes {
24
24
 
25
25
  export interface InputChip {
26
26
  chipId: ChipId
27
+
28
+ /**
29
+ * Exact directional/oriented schematic-symbol name when the caller has it.
30
+ * `center`, `width`, and `height` describe the solver obstacle and may be
31
+ * expanded to include reference/value or manufacturer-part-number text, so
32
+ * renderers should use this symbol's geometry for the component body.
33
+ */
34
+ symbolName?: string
35
+
27
36
  center: { x: number; y: number }
28
37
  width: number
29
38
  height: number
@@ -33,6 +42,14 @@ export interface InputChip {
33
42
  export interface InputDirectConnection {
34
43
  pinIds: [PinId, PinId]
35
44
  netId?: string
45
+
46
+ /**
47
+ * User-facing text to render for this net label. `netId` remains the stable
48
+ * connectivity identifier and may be an internal id. When this is omitted
49
+ * but a label width is provided, renderers should use a width-preserving
50
+ * placeholder instead of exposing `netId` as label text.
51
+ */
52
+ netLabelText?: string
36
53
  netLabelWidth?: number
37
54
 
38
55
  /**
@@ -58,7 +75,8 @@ export interface InputDirectConnection {
58
75
 
59
76
  /**
60
77
  * Extent of the inline net label along the trace. Falls back to
61
- * `netLabelWidth`, then to an estimate from the netId text.
78
+ * `netLabelWidth`, then to an estimate from `netLabelText` (or `netId` for
79
+ * backwards compatibility).
62
80
  */
63
81
  inlineNetLabelWidth?: number
64
82
 
@@ -72,6 +90,14 @@ export interface InputDirectConnection {
72
90
  export interface InputNetConnection {
73
91
  netId: string
74
92
  pinIds: Array<PinId>
93
+
94
+ /**
95
+ * User-facing text to render for this net label. `netId` remains the stable
96
+ * connectivity identifier and may be an internal id. When this is omitted
97
+ * but a label width is provided, renderers should use a width-preserving
98
+ * placeholder instead of exposing `netId` as label text.
99
+ */
100
+ netLabelText?: string
75
101
  netLabelWidth?: number
76
102
  fallbackNetLabelWidth?: number
77
103
  netLabelHeight?: number
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.149",
8
+ "version": "0.0.151",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",
@@ -17,10 +17,15 @@
17
17
  "devDependencies": {
18
18
  "@biomejs/biome": "^2.2.2",
19
19
  "@react-hook/resize-observer": "^2.0.2",
20
+ "@tscircuit/alphabet": "^0.0.26",
21
+ "@tscircuit/circuit-json-util": "^0.0.111",
20
22
  "@tscircuit/math-utils": "^0.0.36",
21
23
  "@types/bun": "^1.2.21",
22
24
  "bun-match-svg": "^0.0.13",
23
25
  "calculate-elbow": "^0.0.12",
26
+ "circuit-json": "^0.0.475",
27
+ "circuit-json-to-connectivity-map": "^0.0.29",
28
+ "circuit-to-svg": "^0.0.407",
24
29
  "connectivity-map": "^1.0.0",
25
30
  "flatbush": "^4.5.0",
26
31
  "graphics-debug": "^0.0.98",
@@ -28,6 +33,8 @@
28
33
  "react-cosmos": "^7.0.0",
29
34
  "react-cosmos-plugin-vite": "^7.0.0",
30
35
  "react-dom": "^19.1.1",
36
+ "schematic-symbols": "^0.0.241",
37
+ "stack-svgs": "^0.0.1",
31
38
  "tsup": "^8.5.0",
32
39
  "vite": "^7.1.3"
33
40
  },