@tscircuit/schematic-trace-solver 0.0.160 → 0.0.162
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 +5 -0
- package/dist/index.js +221 -29
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +210 -6
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +28 -21
- package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +3 -3
- package/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts +40 -0
- package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getFixedLabelCoordinate.ts +44 -13
- package/lib/types/InputProblem.ts +3 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +79 -79
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +85 -85
- package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +163 -163
- package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +547 -547
- package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +13 -13
- package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +267 -267
- package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +267 -267
- package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +109 -109
- package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +49 -49
- package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +103 -103
- package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +103 -103
- package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +163 -163
- package/tests/examples/__snapshots__/example51.snap.svg +568 -568
- package/tests/fixtures/convertSolverOutputToCircuitJson.test.ts +16 -1
- package/tests/fixtures/convertSolverOutputToCircuitJson.ts +23 -12
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +736 -736
- package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +79 -79
- package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +127 -127
- package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +115 -0
- package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +31 -31
- package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +121 -121
- package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +43 -43
- package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +19 -19
- package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +277 -0
- package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +19 -19
- package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +7 -7
- package/tests/repros/__snapshots__/repro-dc-power-isolated-routing.snap.svg +37 -37
- package/tests/repros/__snapshots__/repro-hdc2080-ground-label-alignment.snap.svg +137 -0
- package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +61 -61
- package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +115 -115
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +743 -743
- package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +43 -43
- package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +1 -1
- package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +17 -17
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1131 -1131
- package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +7 -7
- package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +7 -7
- package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +666 -666
- package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +73 -73
- package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +73 -73
- package/tests/repros/assets/repro-bluetooth-controller-ground-decoupling-groups.input.json +214 -0
- package/tests/repros/assets/repro-core-ground-inline-label-fallback.input.json +863 -0
- package/tests/repros/repro-bluetooth-controller-ground-decoupling-groups.test.ts +18 -0
- package/tests/repros/repro-core-ground-inline-label-fallback.test.ts +34 -0
- package/tests/repros/repro-hdc2080-ground-label-alignment.test.ts +239 -0
- package/tests/repros/repro-tida010076-j4-ground-bus.test.ts +14 -10
- package/tests/repros/repro-tida010076-page02.test.ts +2 -2
- package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +1 -1
- package/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts +57 -0
|
@@ -16,6 +16,7 @@ import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInp
|
|
|
16
16
|
import { getGroundConnectionPolicy } from "./getGroundConnectionPolicy"
|
|
17
17
|
import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
|
|
18
18
|
import { getLabeledConnectionRouteReason } from "./isLabeledPeripheralConnection"
|
|
19
|
+
import { shouldSeparateGroundNetRows } from "./shouldSeparateGroundNetRows"
|
|
19
20
|
|
|
20
21
|
export type MspConnectionPairId = string
|
|
21
22
|
export const DEFAULT_MAX_MSP_PAIR_DISTANCE = 1
|
|
@@ -194,26 +195,32 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
194
195
|
PinId,
|
|
195
196
|
InputPin & { chipId: string }
|
|
196
197
|
>
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
maxDistance: this.maxMspPairDistance,
|
|
201
|
-
forbidEdge: (a, b) =>
|
|
202
|
-
!this.canRouteGroundPair(a.pinId, b.pinId) ||
|
|
203
|
-
arePinsInDifferentSchematicSections(
|
|
204
|
-
this.inputProblem,
|
|
205
|
-
a as InputPin & { chipId: string },
|
|
206
|
-
b as InputPin & { chipId: string },
|
|
207
|
-
) ||
|
|
208
|
-
doesPairCrossRestrictedCenterLines({
|
|
209
|
-
inputProblem: this.inputProblem,
|
|
210
|
-
chipMap: this.chipMap,
|
|
211
|
-
pinIdMap,
|
|
212
|
-
p1: a as InputPin & { chipId: string },
|
|
213
|
-
p2: b as InputPin & { chipId: string },
|
|
214
|
-
}),
|
|
215
|
-
},
|
|
198
|
+
const userNetId = this.userNetIdByPinId[directlyConnectedPins[0]!]
|
|
199
|
+
const netConnection = this.inputProblem.netConnections.find(
|
|
200
|
+
(connection) => connection.netId === userNetId,
|
|
216
201
|
)
|
|
202
|
+
const directlyConnectedPinObjects = directlyConnectedPins.map(
|
|
203
|
+
(pinId) => this.pinMap[pinId]!,
|
|
204
|
+
)
|
|
205
|
+
const msp = getOrthogonalMinimumSpanningTree(directlyConnectedPinObjects, {
|
|
206
|
+
maxDistance: this.maxMspPairDistance,
|
|
207
|
+
forbidEdge: (a, b) =>
|
|
208
|
+
!this.canRouteGroundPair(a.pinId, b.pinId) ||
|
|
209
|
+
shouldSeparateGroundNetRows({
|
|
210
|
+
netConnection,
|
|
211
|
+
netPins: directlyConnectedPinObjects,
|
|
212
|
+
pin1: a,
|
|
213
|
+
pin2: b,
|
|
214
|
+
}) ||
|
|
215
|
+
arePinsInDifferentSchematicSections(this.inputProblem, a, b) ||
|
|
216
|
+
doesPairCrossRestrictedCenterLines({
|
|
217
|
+
inputProblem: this.inputProblem,
|
|
218
|
+
chipMap: this.chipMap,
|
|
219
|
+
pinIdMap,
|
|
220
|
+
p1: a,
|
|
221
|
+
p2: b,
|
|
222
|
+
}),
|
|
223
|
+
})
|
|
217
224
|
|
|
218
225
|
for (const [pin1, pin2] of msp) {
|
|
219
226
|
const p1Obj = this.pinMap[pin1!]!
|
|
@@ -237,13 +244,13 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
237
244
|
}
|
|
238
245
|
|
|
239
246
|
const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1!)!
|
|
240
|
-
const
|
|
247
|
+
const pairUserNetId =
|
|
241
248
|
this.userNetIdByPinId[pin1!] ?? this.userNetIdByPinId[pin2!]
|
|
242
249
|
this.addMspConnectionPair({
|
|
243
250
|
mspPairId: `${pin1}-${pin2}`,
|
|
244
251
|
dcConnNetId: dcNetId,
|
|
245
252
|
globalConnNetId,
|
|
246
|
-
userNetId,
|
|
253
|
+
userNetId: pairUserNetId,
|
|
247
254
|
pins: [p1Obj, p2Obj],
|
|
248
255
|
})
|
|
249
256
|
}
|
|
@@ -16,11 +16,11 @@ import type { InputPin, PinId } from "lib/types/InputProblem"
|
|
|
16
16
|
* - When multiple candidates have the same distance, we pick the one
|
|
17
17
|
* whose pinId is lexicographically smallest (for stable output).
|
|
18
18
|
*/
|
|
19
|
-
export function getOrthogonalMinimumSpanningTree(
|
|
20
|
-
pins:
|
|
19
|
+
export function getOrthogonalMinimumSpanningTree<Pin extends InputPin>(
|
|
20
|
+
pins: Pin[],
|
|
21
21
|
opts: {
|
|
22
22
|
maxDistance?: number
|
|
23
|
-
forbidEdge?: (a:
|
|
23
|
+
forbidEdge?: (a: Pin, b: Pin) => boolean
|
|
24
24
|
} = {},
|
|
25
25
|
): Array<[PinId, PinId]> {
|
|
26
26
|
const n = pins.length
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { InputNetConnection, InputPin } from "lib/types/InputProblem"
|
|
2
|
+
|
|
3
|
+
const SAME_RAIL_Y_TOLERANCE = 1e-6
|
|
4
|
+
const MIN_GROUPED_RAIL_PIN_COUNT = 3
|
|
5
|
+
|
|
6
|
+
function isGroupedHorizontalRail({
|
|
7
|
+
pin,
|
|
8
|
+
netPins,
|
|
9
|
+
}: {
|
|
10
|
+
pin: InputPin & { chipId: string }
|
|
11
|
+
netPins: Array<InputPin & { chipId: string }>
|
|
12
|
+
}) {
|
|
13
|
+
// Multiple pins from one symbol do not establish a grouped component rail.
|
|
14
|
+
const sameRailPins = netPins.filter(
|
|
15
|
+
(otherPin) => Math.abs(otherPin.y - pin.y) <= SAME_RAIL_Y_TOLERANCE,
|
|
16
|
+
)
|
|
17
|
+
const railChipIds = new Set(sameRailPins.map((railPin) => railPin.chipId))
|
|
18
|
+
return railChipIds.size >= MIN_GROUPED_RAIL_PIN_COUNT
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function shouldSeparateGroundNetRows({
|
|
22
|
+
netConnection,
|
|
23
|
+
netPins,
|
|
24
|
+
pin1,
|
|
25
|
+
pin2,
|
|
26
|
+
}: {
|
|
27
|
+
netConnection?: InputNetConnection
|
|
28
|
+
netPins: Array<InputPin & { chipId: string }>
|
|
29
|
+
pin1: InputPin & { chipId: string }
|
|
30
|
+
pin2: InputPin & { chipId: string }
|
|
31
|
+
}) {
|
|
32
|
+
if (!netConnection?.isGround || pin1.chipId === pin2.chipId) return false
|
|
33
|
+
if (Math.abs(pin1.y - pin2.y) <= SAME_RAIL_Y_TOLERANCE) return false
|
|
34
|
+
|
|
35
|
+
// Net labels preserve ground connectivity without a cross-row MSP edge.
|
|
36
|
+
return (
|
|
37
|
+
isGroupedHorizontalRail({ pin: pin1, netPins }) &&
|
|
38
|
+
isGroupedHorizontalRail({ pin: pin2, netPins })
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -53,6 +53,12 @@ export const getFixedLabelCoordinate = (
|
|
|
53
53
|
) => {
|
|
54
54
|
const traceMap = new Map(traces.map((trace) => [trace.mspPairId, trace]))
|
|
55
55
|
const groupTraceIds = new Set(group.map((segment) => segment.traceId))
|
|
56
|
+
const groupIsWithinSingleComponent = [...groupTraceIds].every(
|
|
57
|
+
(traceId) =>
|
|
58
|
+
traceMap
|
|
59
|
+
.get(traceId)
|
|
60
|
+
?.pins.every((pin) => pin.chipId === group[0]!.componentId) === true,
|
|
61
|
+
)
|
|
56
62
|
const groupHasOnlySingleRailTraces = [...groupTraceIds].every(
|
|
57
63
|
(traceId) => traceMap.get(traceId)?.tracePath.length === 4,
|
|
58
64
|
)
|
|
@@ -66,16 +72,27 @@ export const getFixedLabelCoordinate = (
|
|
|
66
72
|
netLabelPlacements,
|
|
67
73
|
traces,
|
|
68
74
|
)
|
|
69
|
-
const
|
|
70
|
-
(
|
|
71
|
-
label
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}),
|
|
75
|
+
const labelsOrientedAlongRail = new Set(
|
|
76
|
+
linkedLabels.filter(
|
|
77
|
+
(label) =>
|
|
78
|
+
(orientation === "vertical" &&
|
|
79
|
+
(label.orientation === "y+" || label.orientation === "y-")) ||
|
|
80
|
+
(orientation === "horizontal" &&
|
|
81
|
+
(label.orientation === "x+" || label.orientation === "x-")),
|
|
82
|
+
),
|
|
78
83
|
)
|
|
84
|
+
const anchoringLabels = linkedLabels.filter((label) => {
|
|
85
|
+
if (
|
|
86
|
+
label.orientation !== group[0]!.componentFacingDirection &&
|
|
87
|
+
(!groupIsWithinSingleComponent || !labelsOrientedAlongRail.has(label))
|
|
88
|
+
) {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
return label.mspConnectionPairIds.some((traceId) => {
|
|
92
|
+
const trace = traceMap.get(traceId)
|
|
93
|
+
return trace && tracePathContainsPoint(trace.tracePath, label.anchorPoint)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
79
96
|
const coordinates = getDistinctCoordinates(
|
|
80
97
|
anchoringLabels.map((label) =>
|
|
81
98
|
orientation === "vertical" ? label.anchorPoint.x : label.anchorPoint.y,
|
|
@@ -95,11 +112,24 @@ export const getFixedLabelCoordinate = (
|
|
|
95
112
|
(traceId) => (traceMap.get(traceId)?.tracePath.length ?? 0) > 4,
|
|
96
113
|
),
|
|
97
114
|
)
|
|
98
|
-
// A multi-turn label trace is an intentional routed backbone, so its
|
|
99
|
-
// coordinate may propagate through the
|
|
100
|
-
// a single movable rail remain subject to the per-connection locality gate.
|
|
115
|
+
// A multi-turn label trace is an intentional routed backbone, so its
|
|
116
|
+
// coordinate may propagate through the connected rail group.
|
|
101
117
|
if (labelIsAnchoredToRoutedBackbone) return coordinate
|
|
102
118
|
|
|
119
|
+
// A label at a same-component rail end may set the shared bus coordinate.
|
|
120
|
+
if (anchoringLabels.some((label) => labelsOrientedAlongRail.has(label))) {
|
|
121
|
+
const railGroupSpan =
|
|
122
|
+
Math.max(...group.map((segment) => segment.maxAlong)) -
|
|
123
|
+
Math.min(...group.map((segment) => segment.minAlong))
|
|
124
|
+
const coordinateIsLocalToRailGroup = group.every(
|
|
125
|
+
(segment) =>
|
|
126
|
+
Math.abs(segment.coordinate - coordinate) <=
|
|
127
|
+
railGroupSpan + RAIL_ALIGNMENT_EPSILON,
|
|
128
|
+
)
|
|
129
|
+
if (coordinateIsLocalToRailGroup) return coordinate
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
|
|
103
133
|
// Rail cleanup is local: a label cannot pull a connection farther than the
|
|
104
134
|
// span of the pins that connection already joins.
|
|
105
135
|
const coordinateIsLocalToEveryTrace = group.every((segment) => {
|
|
@@ -110,5 +140,6 @@ export const getFixedLabelCoordinate = (
|
|
|
110
140
|
distance(trace.pins[0]!, trace.pins[1]!) + RAIL_ALIGNMENT_EPSILON
|
|
111
141
|
)
|
|
112
142
|
})
|
|
113
|
-
|
|
143
|
+
if (coordinateIsLocalToEveryTrace) return coordinate
|
|
144
|
+
return null
|
|
114
145
|
}
|
|
@@ -98,6 +98,9 @@ export interface InputNetConnection {
|
|
|
98
98
|
netId: string
|
|
99
99
|
pinIds: Array<PinId>
|
|
100
100
|
|
|
101
|
+
/** True when the authoritative source net is electrical ground. */
|
|
102
|
+
isGround?: boolean
|
|
103
|
+
|
|
101
104
|
/**
|
|
102
105
|
* User-facing text to render for this net label. `netId` remains the stable
|
|
103
106
|
* connectivity identifier and may be an internal id. When this is omitted
|