@tscircuit/schematic-trace-solver 0.0.190 → 0.0.192
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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +437 -211
- package/lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts +29 -0
- package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +7 -0
- package/lib/solvers/LongDistancePairSolver/getParallelNetLabelPolicy.ts +98 -0
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +24 -2
- package/lib/solvers/MspConnectionPairSolver/getParallelRailPairs.ts +178 -0
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +13 -0
- package/lib/solvers/SameNetJunctionAlignmentSolver/collapseRedundantSharedEndpointStubs.ts +12 -6
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +837 -799
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +904 -866
- package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +533 -524
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +94 -94
- package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.test.ts +13 -2
- package/tests/bug-reports/bug-report-20260902T092425Z/__snapshots__/bug-report-20260902T092425Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260902T092425Z/bug-report-20260902T092425Z.test.ts +16 -2
- package/tests/bug-reports/bug-report-20260905T041712Z/__snapshots__/bug-report-20260905T041712Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260907T083336Z/__snapshots__/bug-report-20260907T083336Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260907T110144Z/__snapshots__/bug-report-20260907T110144Z.snap.svg +19 -58
- package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.test.ts +2 -2
- package/tests/bug-reports/bug-report-20260907T145640Z/__snapshots__/bug-report-20260907T145640Z.snap.svg +917 -933
- package/tests/examples/__snapshots__/example13.snap.svg +2 -2
- package/tests/examples/__snapshots__/example15.snap.svg +2 -2
- package/tests/examples/__snapshots__/example51.snap.svg +2 -2
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +2 -2
- package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-esp12f-section.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-repeated-power-rail-junctions.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-smartwatch-power-sheet.snap.svg +111 -75
- package/tests/repros/__snapshots__/repro-tida00553-j4-cell-divider.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-type-c-vbus-led-ground-label.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +2 -2
- package/tests/repros/repro-trellis-core-decoupling-snake-traces.test.ts +66 -0
- package/tests/solvers/InlineNetLabelSolver/local-trace-label-shifts.test.ts +62 -0
- package/tests/solvers/LongDistancePairSolver/__snapshots__/decoupling-mixed-supplies.snap.svg +112 -0
- package/tests/solvers/LongDistancePairSolver/__snapshots__/horizontal-rails.snap.svg +79 -0
- package/tests/solvers/LongDistancePairSolver/__snapshots__/vertical-rails.snap.svg +86 -0
- package/tests/solvers/LongDistancePairSolver/parallel-decoupling-labels.test.ts +505 -0
- package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-redundant-shared-endpoint-stubs.test.ts +45 -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 (
|
|
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(
|
|
@@ -25,8 +25,13 @@ const getPathFromSharedPin = ({
|
|
|
25
25
|
sharedPinId: PinId
|
|
26
26
|
}) => {
|
|
27
27
|
const tracePath = simplifyPath(trace.tracePath)
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const sharedPin = trace.pins.find((pin) => pin.pinId === sharedPinId)
|
|
29
|
+
if (!sharedPin) return null
|
|
30
|
+
const pathFromSharedPin =
|
|
31
|
+
trace.pins[0]?.pinId === sharedPinId ? tracePath : tracePath.reverse()
|
|
32
|
+
return pointsEqual(pathFromSharedPin[0]!, sharedPin)
|
|
33
|
+
? pathFromSharedPin
|
|
34
|
+
: null
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
const traceCanLoseSharedPrefix = ({
|
|
@@ -87,13 +92,14 @@ export const collapseRedundantSharedEndpointStubs = ({
|
|
|
87
92
|
const outputTraces = [...traces]
|
|
88
93
|
|
|
89
94
|
for (let firstIndex = 0; firstIndex < outputTraces.length; firstIndex++) {
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
if (netLabelConnectorTraceIds.has(outputTraces[firstIndex]!.mspPairId))
|
|
96
|
+
continue
|
|
92
97
|
for (
|
|
93
98
|
let secondIndex = firstIndex + 1;
|
|
94
99
|
secondIndex < outputTraces.length;
|
|
95
100
|
secondIndex++
|
|
96
101
|
) {
|
|
102
|
+
const firstTrace = outputTraces[firstIndex]!
|
|
97
103
|
const secondTrace = outputTraces[secondIndex]!
|
|
98
104
|
if (
|
|
99
105
|
netLabelConnectorTraceIds.has(secondTrace.mspPairId) ||
|
|
@@ -114,6 +120,7 @@ export const collapseRedundantSharedEndpointStubs = ({
|
|
|
114
120
|
trace: secondTrace,
|
|
115
121
|
sharedPinId: sharedPin.pinId,
|
|
116
122
|
})
|
|
123
|
+
if (!firstPath || !secondPath) continue
|
|
117
124
|
if (firstPath.length < 2 || secondPath.length < 2) continue
|
|
118
125
|
|
|
119
126
|
const departureAxis = getSegmentAxis(firstPath[0]!, firstPath[1]!)
|
|
@@ -174,7 +181,7 @@ export const collapseRedundantSharedEndpointStubs = ({
|
|
|
174
181
|
})
|
|
175
182
|
if (!collapsedTrace) continue
|
|
176
183
|
outputTraces[traceIndexToCollapse] = collapsedTrace
|
|
177
|
-
|
|
184
|
+
continue
|
|
178
185
|
}
|
|
179
186
|
if (firstPath.length < 3 || secondPath.length < 3) continue
|
|
180
187
|
if (!pointsEqual(firstPath[1]!, secondPath[1]!)) continue
|
|
@@ -259,7 +266,6 @@ export const collapseRedundantSharedEndpointStubs = ({
|
|
|
259
266
|
})
|
|
260
267
|
if (!collapsedTrace) continue
|
|
261
268
|
outputTraces[traceIndexToCollapse] = collapsedTrace
|
|
262
|
-
break
|
|
263
269
|
}
|
|
264
270
|
}
|
|
265
271
|
|