@tscircuit/schematic-trace-solver 0.0.190 → 0.0.191

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 (28) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +428 -205
  3. package/lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts +29 -0
  4. package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +7 -0
  5. package/lib/solvers/LongDistancePairSolver/getParallelNetLabelPolicy.ts +98 -0
  6. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +24 -2
  7. package/lib/solvers/MspConnectionPairSolver/getParallelRailPairs.ts +178 -0
  8. package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +13 -0
  9. package/package.json +1 -1
  10. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +837 -799
  11. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +904 -866
  12. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +533 -524
  13. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
  14. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +94 -94
  15. package/tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.test.ts +13 -2
  16. package/tests/bug-reports/bug-report-20260902T092425Z/__snapshots__/bug-report-20260902T092425Z.snap.svg +2 -2
  17. package/tests/bug-reports/bug-report-20260902T092425Z/bug-report-20260902T092425Z.test.ts +16 -2
  18. package/tests/bug-reports/bug-report-20260905T041712Z/__snapshots__/bug-report-20260905T041712Z.snap.svg +2 -2
  19. package/tests/bug-reports/bug-report-20260907T110144Z/__snapshots__/bug-report-20260907T110144Z.snap.svg +19 -58
  20. package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.test.ts +2 -2
  21. package/tests/bug-reports/bug-report-20260907T145640Z/__snapshots__/bug-report-20260907T145640Z.snap.svg +917 -933
  22. package/tests/repros/__snapshots__/repro-smartwatch-power-sheet.snap.svg +111 -75
  23. package/tests/repros/repro-trellis-core-decoupling-snake-traces.test.ts +66 -0
  24. package/tests/solvers/InlineNetLabelSolver/local-trace-label-shifts.test.ts +62 -0
  25. package/tests/solvers/LongDistancePairSolver/__snapshots__/decoupling-mixed-supplies.snap.svg +112 -0
  26. package/tests/solvers/LongDistancePairSolver/__snapshots__/horizontal-rails.snap.svg +79 -0
  27. package/tests/solvers/LongDistancePairSolver/__snapshots__/vertical-rails.snap.svg +86 -0
  28. package/tests/solvers/LongDistancePairSolver/parallel-decoupling-labels.test.ts +505 -0
@@ -4,6 +4,8 @@ import type { InputProblem } from "lib/types/InputProblem"
4
4
  import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
5
5
  import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
6
6
  import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
7
+ import { minimizeTurnsWithFilteredLabels } from "lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels"
8
+ import { preservesLabelAnchors } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/preservesLabelAnchors"
7
9
  import { doesPathCoincideWithTraces } from "lib/utils/doesPathCoincideWithTraces"
8
10
  import { boundsOverlap, getTextBoxBounds } from "lib/utils/textBoxBounds"
9
11
  import { getAnchoredNetLabelRenderedBounds } from "./getAnchoredNetLabelRenderedBounds"
@@ -274,6 +276,33 @@ export function* getLocalTraceLabelShifts(
274
276
  }
275
277
  }
276
278
  if (blocked) continue
279
+ // A shifted leg can coincide with a redundant bend on another net.
280
+ // Try removing that bend with the existing cleanup, then validate
281
+ // both changed routes together below before yielding the proposal.
282
+ for (const [index, other] of traces.entries()) {
283
+ if (
284
+ other.globalConnNetId === trace.globalConnNetId ||
285
+ simplifyPath(other.tracePath).length <= 4 ||
286
+ !shiftedPaths.some((path) =>
287
+ doesPathCoincideWithTraces(path, [other]),
288
+ )
289
+ )
290
+ continue
291
+ const simplified = minimizeTurnsWithFilteredLabels({
292
+ inputProblem,
293
+ traces: [...traces, ...terminalTraces],
294
+ targetMspConnectionPairId: other.mspPairId,
295
+ allLabelPlacements: [...labels, ...fixedLabels],
296
+ mergedLabelNetIdMap: {},
297
+ paddingBuffer: CLEARANCE,
298
+ })
299
+ if (
300
+ simplifyPath(simplified.tracePath).length <
301
+ simplifyPath(other.tracePath).length &&
302
+ preservesLabelAnchors(labels, [other], [simplified])
303
+ )
304
+ traces[index] = simplified
305
+ }
277
306
  for (const [index, proposed] of traces.entries()) {
278
307
  const original = output.traces[index]!
279
308
  if (proposed === original) continue
@@ -14,6 +14,7 @@ import type {
14
14
  import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces"
15
15
  import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
16
16
  import { BaseSolver } from "../BaseSolver/BaseSolver"
17
+ import { getParallelNetLabelPolicy } from "./getParallelNetLabelPolicy"
17
18
  import { getGroundConnectionPolicy } from "../MspConnectionPairSolver/getGroundConnectionPolicy"
18
19
  import { isLabeledPeripheralConnection } from "../MspConnectionPairSolver/isLabeledPeripheralConnection"
19
20
  import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
@@ -180,6 +181,11 @@ export class LongDistancePairSolver extends BaseSolver {
180
181
 
181
182
  const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
182
183
  this.netConnMap = netConnMap
184
+ const canRecoverParallelPair = getParallelNetLabelPolicy(
185
+ inputProblem,
186
+ netConnMap,
187
+ primaryConnectedPinIds,
188
+ )
183
189
  const pinMap = new Map<PinId, InputPin & { chipId: string }>()
184
190
  for (const chip of inputProblem.chips) {
185
191
  this.chipMap[chip.chipId] = chip
@@ -226,6 +232,7 @@ export class LongDistancePairSolver extends BaseSolver {
226
232
  const targetPin = pinMap.get(otherPinId)
227
233
  if (!targetPin) return [] // Gracefully handle missing pins
228
234
  if (!canRouteGroundPair(sourcePin.pinId, targetPin.pinId)) return []
235
+ if (!canRecoverParallelPair(sourcePin, targetPin)) return []
229
236
  const isNamedTwoPinConnection = inputProblem.netConnections.some(
230
237
  (connection) =>
231
238
  connection.pinIds.length === 2 &&
@@ -0,0 +1,98 @@
1
+ import { ConnectivityMap } from "connectivity-map"
2
+ import type { InputPin, InputProblem, PinId } from "lib/types/InputProblem"
3
+ import { DEFAULT_MAX_MSP_PAIR_DISTANCE } from "../MspConnectionPairSolver/MspConnectionPairSolver"
4
+ import { getParallelRailPairs } from "../MspConnectionPairSolver/getParallelRailPairs"
5
+
6
+ /** Keep distant parallel rail/ground branches local during trace recovery. */
7
+ export const getParallelNetLabelPolicy = (
8
+ inputProblem: InputProblem,
9
+ netConnMap: ConnectivityMap,
10
+ connectedPinIds: ReadonlySet<PinId>,
11
+ ) => {
12
+ const maxDistance =
13
+ inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE
14
+ const { extendedRailPinIds, separatedRailPinIds } = getParallelRailPairs(
15
+ inputProblem,
16
+ netConnMap,
17
+ maxDistance,
18
+ )
19
+ const groundNetIds = new Set<string>()
20
+ const namedPinIds = new Set<PinId>()
21
+ for (const connection of inputProblem.netConnections) {
22
+ const netId = netConnMap.getNetConnectedToId(connection.netId)
23
+ if (!netId) continue
24
+ for (const pinId of connection.pinIds) namedPinIds.add(pinId)
25
+ if (connection.isGround) {
26
+ groundNetIds.add(netId)
27
+ }
28
+ }
29
+
30
+ // Older inputs omit symbol metadata. Identify parallel two-terminal
31
+ // branches by their two named nets, without inferring capacitor types or
32
+ // requiring identical pin positions, sizes, or orientations.
33
+ const banks = new Map<string, PinId[][]>()
34
+ for (const chip of inputProblem.chips) {
35
+ // Preserve branches that use source wires or already participate in
36
+ // local routing. This policy only selects standalone named-net branches.
37
+ if (
38
+ chip.pins.length !== 2 ||
39
+ !chip.pins.every((pin) => namedPinIds.has(pin.pinId)) ||
40
+ chip.pins.some(
41
+ (pin) =>
42
+ connectedPinIds.has(pin.pinId) && !extendedRailPinIds.has(pin.pinId),
43
+ )
44
+ )
45
+ continue
46
+ const [first, second] = chip.pins.map((pin) =>
47
+ netConnMap.getNetConnectedToId(pin.pinId),
48
+ )
49
+ if (
50
+ !first ||
51
+ !second ||
52
+ first === second ||
53
+ groundNetIds.has(first) === groundNetIds.has(second)
54
+ ) {
55
+ continue
56
+ }
57
+ const key = JSON.stringify([
58
+ chip.sectionId ?? null,
59
+ ...[first, second].sort(),
60
+ ])
61
+ const bank = banks.get(key) ?? []
62
+ bank.push(chip.pins.map((pin) => pin.pinId))
63
+ banks.set(key, bank)
64
+ }
65
+ const bankPinIds = new Set(
66
+ [...banks.values()].filter((bank) => bank.length > 1).flat(2),
67
+ )
68
+ // Both shared rails and terminals separated by a large gap stay local.
69
+ // Do not recover wires between them or to distant IC pins.
70
+ for (const pinId of [...extendedRailPinIds, ...separatedRailPinIds]) {
71
+ bankPinIds.add(pinId)
72
+ }
73
+
74
+ // Explicit source wires may be recovered even beyond the local distance.
75
+ // Sharing a net ID alone does not request a physical wire between branches.
76
+ const physicalConnMap = new ConnectivityMap({})
77
+ for (const connection of inputProblem.directConnections) {
78
+ if (connection.netLabelWidth === undefined) {
79
+ physicalConnMap.addConnections([connection.pinIds])
80
+ }
81
+ }
82
+
83
+ return (first: InputPin, second: InputPin): boolean => {
84
+ if (!bankPinIds.has(first.pinId) && !bankPinIds.has(second.pinId))
85
+ return true
86
+ if (
87
+ Math.abs(first.x - second.x) + Math.abs(first.y - second.y) <=
88
+ maxDistance
89
+ ) {
90
+ return true
91
+ }
92
+ const physicalNetId = physicalConnMap.getNetConnectedToId(first.pinId)
93
+ return (
94
+ physicalNetId !== undefined &&
95
+ physicalNetId === physicalConnMap.getNetConnectedToId(second.pinId)
96
+ )
97
+ }
98
+ }
@@ -17,6 +17,7 @@ import { getGroundConnectionPolicy } from "./getGroundConnectionPolicy"
17
17
  import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
18
18
  import { getLabeledConnectionRouteReason } from "./isLabeledPeripheralConnection"
19
19
  import { shouldSeparateGroundNetRows } from "./shouldSeparateGroundNetRows"
20
+ import { getParallelRailPairs, getRailPairKey } from "./getParallelRailPairs"
20
21
 
21
22
  export type MspConnectionPairId = string
22
23
  export const DEFAULT_MAX_MSP_PAIR_DISTANCE = 1
@@ -48,6 +49,7 @@ export class MspConnectionPairSolver extends BaseSolver {
48
49
  userNetIdByPinId: Record<string, string | undefined>
49
50
  directConnectionPinPairKeys: Set<string>
50
51
  private canRouteGroundPair: (firstPinId: PinId, secondPinId: PinId) => boolean
52
+ private parallelRailPairKeys: Set<string>
51
53
 
52
54
  constructor({ inputProblem }: { inputProblem: InputProblem }) {
53
55
  super()
@@ -61,6 +63,11 @@ export class MspConnectionPairSolver extends BaseSolver {
61
63
  getConnectivityMapsFromInputProblem(inputProblem)
62
64
  this.dcConnMap = directConnMap
63
65
  this.globalConnMap = netConnMap
66
+ this.parallelRailPairKeys = getParallelRailPairs(
67
+ inputProblem,
68
+ netConnMap,
69
+ this.maxMspPairDistance,
70
+ ).pairKeys
64
71
 
65
72
  this.pinMap = {}
66
73
  for (const chip of inputProblem.chips) {
@@ -145,6 +152,7 @@ export class MspConnectionPairSolver extends BaseSolver {
145
152
  })
146
153
  if (
147
154
  pairDistance > this.maxMspPairDistance &&
155
+ !this.parallelRailPairKeys.has(getRailPairKey(pin1!, pin2!)) &&
148
156
  !labeledConnectionRouteReason
149
157
  ) {
150
158
  // Too far apart; skip creating an MSP pair for this net
@@ -203,8 +211,9 @@ export class MspConnectionPairSolver extends BaseSolver {
203
211
  (pinId) => this.pinMap[pinId]!,
204
212
  )
205
213
  const msp = getOrthogonalMinimumSpanningTree(directlyConnectedPinObjects, {
206
- maxDistance: this.maxMspPairDistance,
207
214
  forbidEdge: (a, b) =>
215
+ (Math.abs(a.x - b.x) + Math.abs(a.y - b.y) > this.maxMspPairDistance &&
216
+ !this.parallelRailPairKeys.has(getRailPairKey(a.pinId, b.pinId))) ||
208
217
  !this.canRouteGroundPair(a.pinId, b.pinId) ||
209
218
  shouldSeparateGroundNetRows({
210
219
  netConnection,
@@ -222,7 +231,20 @@ export class MspConnectionPairSolver extends BaseSolver {
222
231
  }),
223
232
  })
224
233
 
225
- for (const [pin1, pin2] of msp) {
234
+ for (let [pin1, pin2] of msp) {
235
+ // Extended rails replace long-distance recovery, which visits pins in
236
+ // connectivity order. Preserve that order so an unchanged rail keeps
237
+ // its label at the same endpoint.
238
+ if (
239
+ this.parallelRailPairKeys.has(getRailPairKey(pin1!, pin2!)) &&
240
+ Math.abs(this.pinMap[pin1!]!.x - this.pinMap[pin2!]!.x) +
241
+ Math.abs(this.pinMap[pin1!]!.y - this.pinMap[pin2!]!.y) >
242
+ this.maxMspPairDistance &&
243
+ directlyConnectedPins.indexOf(pin1!) >
244
+ directlyConnectedPins.indexOf(pin2!)
245
+ ) {
246
+ ;[pin1, pin2] = [pin2, pin1]
247
+ }
226
248
  const p1Obj = this.pinMap[pin1!]!
227
249
  const p2Obj = this.pinMap[pin2!]!
228
250
  if (
@@ -0,0 +1,178 @@
1
+ import type { ConnectivityMap } from "connectivity-map"
2
+ import type { InputPin, InputProblem, PinId } from "lib/types/InputProblem"
3
+ import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
4
+
5
+ const EPS = 1e-6
6
+ // Component scale supplies a minimum allowance, including two-capacitor rows
7
+ // with no neighboring pitch to compare against.
8
+ const MAX_RAIL_SPACING_IN_TERMINAL_SPANS = 4
9
+ const MAX_RAIL_SPACING_MULTIPLIER = 2
10
+
11
+ export const getRailPairKey = (first: PinId, second: PinId) =>
12
+ JSON.stringify([first, second].sort())
13
+
14
+ /** Adjacent, outward-facing terminals can share a straight decoupling rail. */
15
+ export const getParallelRailPairs = (
16
+ inputProblem: InputProblem,
17
+ netConnMap: ConnectivityMap,
18
+ maxMspPairDistance: number,
19
+ ) => {
20
+ const groundNetIds = new Set<string>()
21
+ const namedPinIds = new Set<PinId>()
22
+ const wiredPinIds = new Set<PinId>()
23
+ for (const connection of inputProblem.netConnections) {
24
+ for (const pinId of connection.pinIds) namedPinIds.add(pinId)
25
+ const netId = netConnMap.getNetConnectedToId(connection.netId)
26
+ if (netId && connection.isGround) {
27
+ groundNetIds.add(netId)
28
+ }
29
+ }
30
+ for (const connection of inputProblem.directConnections) {
31
+ const pinIds =
32
+ connection.netLabelWidth === undefined ? wiredPinIds : namedPinIds
33
+ for (const pinId of connection.pinIds) pinIds.add(pinId)
34
+ }
35
+
36
+ const rows: Array<{
37
+ sectionId?: string
38
+ direction: string
39
+ coordinate: number
40
+ axis: "x" | "y"
41
+ pins: Array<InputPin & { chipId: string; oppositeCoordinate: number }>
42
+ }> = []
43
+ for (const chip of inputProblem.chips) {
44
+ if (
45
+ chip.pins.length !== 2 ||
46
+ (chip.symbolName && !chip.symbolName.startsWith("capacitor")) ||
47
+ !chip.pins.every((pin) => namedPinIds.has(pin.pinId)) ||
48
+ chip.pins.some((pin) => wiredPinIds.has(pin.pinId))
49
+ )
50
+ continue
51
+ const nets = chip.pins.map((pin) =>
52
+ netConnMap.getNetConnectedToId(pin.pinId),
53
+ )
54
+ if (
55
+ !nets[0] ||
56
+ !nets[1] ||
57
+ nets[0] === nets[1] ||
58
+ groundNetIds.has(nets[0]) === groundNetIds.has(nets[1])
59
+ )
60
+ continue
61
+ const directions = chip.pins.map(
62
+ (pin) => pin._facingDirection ?? getPinDirection(pin, chip),
63
+ )
64
+ if (
65
+ directions[0]![0] !== directions[1]![0] ||
66
+ directions[0] === directions[1]
67
+ )
68
+ continue
69
+ const axis = directions[0]![0] as "x" | "y"
70
+ const along = axis === "x" ? "y" : "x"
71
+ if (Math.abs(chip.pins[0]![along] - chip.pins[1]![along]) > EPS) continue
72
+
73
+ for (const [index, pin] of chip.pins.entries()) {
74
+ const direction = directions[index]!
75
+ let row = rows.find(
76
+ (candidate) =>
77
+ candidate.sectionId === chip.sectionId &&
78
+ candidate.direction === direction &&
79
+ Math.abs(candidate.coordinate - pin[axis]) <= EPS,
80
+ )
81
+ if (!row) {
82
+ row = {
83
+ sectionId: chip.sectionId,
84
+ direction,
85
+ coordinate: pin[axis],
86
+ axis,
87
+ pins: [],
88
+ }
89
+ rows.push(row)
90
+ }
91
+ row.pins.push({
92
+ ...pin,
93
+ chipId: chip.chipId,
94
+ oppositeCoordinate: chip.pins[1 - index]![axis],
95
+ })
96
+ }
97
+ }
98
+
99
+ const pairKeys = new Set<string>()
100
+ const extendedRailPinIds = new Set<PinId>()
101
+ const separatedRailPinIds = new Set<PinId>()
102
+ for (const row of rows) {
103
+ const along = row.axis === "x" ? "y" : "x"
104
+ row.pins.sort(
105
+ (a, b) => a[along] - b[along] || a.pinId.localeCompare(b.pinId),
106
+ )
107
+ for (let index = 1; index < row.pins.length; index++) {
108
+ const first = row.pins[index - 1]!
109
+ const second = row.pins[index]!
110
+ const spacing = second[along] - first[along]
111
+ // A regular row may be widely spaced. Split at gaps that are much
112
+ // larger than the neighboring pitch, rather than imposing a fixed cap.
113
+ const neighboringSpacings = [
114
+ first[along] - (row.pins[index - 2]?.[along] ?? first[along]),
115
+ (row.pins[index + 1]?.[along] ?? second[along]) - second[along],
116
+ ].filter((gap) => gap > EPS)
117
+ const maxRailSpacing = Math.max(
118
+ MAX_RAIL_SPACING_MULTIPLIER * maxMspPairDistance,
119
+ MAX_RAIL_SPACING_MULTIPLIER *
120
+ (neighboringSpacings.length ? Math.min(...neighboringSpacings) : 0),
121
+ MAX_RAIL_SPACING_IN_TERMINAL_SPANS *
122
+ Math.max(
123
+ Math.abs(first[row.axis] - first.oppositeCoordinate),
124
+ Math.abs(second[row.axis] - second.oppositeCoordinate),
125
+ ),
126
+ )
127
+ // A different supply in between ends the supply rail; GND can continue.
128
+ if (
129
+ spacing <= EPS ||
130
+ netConnMap.getNetConnectedToId(first.pinId) !==
131
+ netConnMap.getNetConnectedToId(second.pinId)
132
+ )
133
+ continue
134
+ if (spacing > maxRailSpacing + EPS) {
135
+ // Recovery must retain this separation even when each capacitor has
136
+ // a different supply and does not belong to a same-net branch bank.
137
+ separatedRailPinIds.add(first.pinId)
138
+ separatedRailPinIds.add(second.pinId)
139
+ continue
140
+ }
141
+ // A component between the terminals breaks the row. Leave obstacle
142
+ // detours to ordinary local routing instead of extending this exception.
143
+ const minAcross = Math.min(
144
+ first[row.axis],
145
+ second[row.axis],
146
+ first.oppositeCoordinate,
147
+ second.oppositeCoordinate,
148
+ )
149
+ const maxAcross = Math.max(
150
+ first[row.axis],
151
+ second[row.axis],
152
+ first.oppositeCoordinate,
153
+ second.oppositeCoordinate,
154
+ )
155
+ if (
156
+ inputProblem.chips.some((chip) => {
157
+ if (chip.chipId === first.chipId || chip.chipId === second.chipId)
158
+ return false
159
+ const acrossSize = row.axis === "x" ? chip.width : chip.height
160
+ const alongSize = along === "x" ? chip.width : chip.height
161
+ return (
162
+ chip.center[row.axis] + acrossSize / 2 >= minAcross - EPS &&
163
+ chip.center[row.axis] - acrossSize / 2 <= maxAcross + EPS &&
164
+ chip.center[along] + alongSize / 2 > first[along] + EPS &&
165
+ chip.center[along] - alongSize / 2 < second[along] - EPS
166
+ )
167
+ })
168
+ )
169
+ continue
170
+ pairKeys.add(getRailPairKey(first.pinId, second.pinId))
171
+ if (spacing > maxMspPairDistance) {
172
+ extendedRailPinIds.add(first.pinId)
173
+ extendedRailPinIds.add(second.pinId)
174
+ }
175
+ }
176
+ }
177
+ return { pairKeys, extendedRailPinIds, separatedRailPinIds }
178
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
2
  import type { GraphicsObject } from "graphics-debug"
3
3
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
4
+ import { getParallelNetLabelPolicy } from "lib/solvers/LongDistancePairSolver/getParallelNetLabelPolicy"
4
5
  import { doesPairCrossRestrictedCenterLines } from "lib/solvers/MspConnectionPairSolver/doesPairCrossRestrictedCenterLines"
5
6
  import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
6
7
  import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
@@ -84,6 +85,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
84
85
  private readonly recoveredTraceIds = new Set<string>()
85
86
  private chipMap: Record<ChipId, InputChip>
86
87
  private pinMap: Map<PinId, TraceRecoveryPin>
88
+ private canRecoverParallelPair: ReturnType<typeof getParallelNetLabelPolicy>
87
89
  private queuedCandidates: CandidatePair[]
88
90
  private currentCandidate: CandidatePair | null = null
89
91
  declare activeSubSolver: SchematicTraceSingleLineSolver2 | null
@@ -99,6 +101,15 @@ export class NetLabelToTraceSolver extends BaseSolver {
99
101
  )
100
102
  this.chipMap = chipMap
101
103
  this.pinMap = pinMap
104
+ this.canRecoverParallelPair = getParallelNetLabelPolicy(
105
+ this.inputProblem,
106
+ getConnectivityMapsFromInputProblem(this.inputProblem).netConnMap,
107
+ new Set(
108
+ input.traces
109
+ .filter((trace) => trace.pinIds.length > 1)
110
+ .flatMap((trace) => trace.pinIds),
111
+ ),
112
+ )
102
113
 
103
114
  this.queuedCandidates = this.buildCandidatePairs()
104
115
  this.stats.candidateCount = this.queuedCandidates.length
@@ -180,6 +191,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
180
191
  const firstPin = this.pinMap.get(firstLabel.pinIds[0]!)
181
192
  const secondPin = this.pinMap.get(secondLabel.pinIds[0]!)
182
193
  if (!firstPin || !secondPin) continue
194
+ if (!this.canRecoverParallelPair(firstPin, secondPin)) continue
183
195
  const perpendicularOffset = getPerpendicularOffset(
184
196
  firstPin,
185
197
  secondPin,
@@ -354,6 +366,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
354
366
  const firstPin = this.pinMap.get(firstPinId)
355
367
  const secondPin = this.pinMap.get(secondPinId)
356
368
  if (!firstPin || !secondPin) continue
369
+ if (!this.canRecoverParallelPair(firstPin, secondPin)) continue
357
370
  if (isDirectConnection && firstPin.chipId === secondPin.chipId)
358
371
  continue
359
372
  const perpendicularOffset = getPerpendicularOffset(
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.190",
8
+ "version": "0.0.191",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",