@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.
Files changed (63) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.js +221 -29
  3. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +210 -6
  4. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +28 -21
  5. package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +3 -3
  6. package/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts +40 -0
  7. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getFixedLabelCoordinate.ts +44 -13
  8. package/lib/types/InputProblem.ts +3 -0
  9. package/package.json +1 -1
  10. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +1 -1
  11. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +1 -1
  12. package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +79 -79
  13. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +85 -85
  14. package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +1 -1
  15. package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +163 -163
  16. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +547 -547
  17. package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +13 -13
  18. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +267 -267
  19. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +267 -267
  20. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +109 -109
  21. package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +1 -1
  22. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +49 -49
  23. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +1 -1
  24. package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +103 -103
  25. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +103 -103
  26. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +163 -163
  27. package/tests/examples/__snapshots__/example51.snap.svg +568 -568
  28. package/tests/fixtures/convertSolverOutputToCircuitJson.test.ts +16 -1
  29. package/tests/fixtures/convertSolverOutputToCircuitJson.ts +23 -12
  30. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +736 -736
  31. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +79 -79
  32. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +127 -127
  33. package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +115 -0
  34. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +31 -31
  35. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +121 -121
  36. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +43 -43
  37. package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +19 -19
  38. package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +277 -0
  39. package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +19 -19
  40. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +7 -7
  41. package/tests/repros/__snapshots__/repro-dc-power-isolated-routing.snap.svg +37 -37
  42. package/tests/repros/__snapshots__/repro-hdc2080-ground-label-alignment.snap.svg +137 -0
  43. package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +61 -61
  44. package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +115 -115
  45. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +743 -743
  46. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +43 -43
  47. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +1 -1
  48. package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +17 -17
  49. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1131 -1131
  50. package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +7 -7
  51. package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +7 -7
  52. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +666 -666
  53. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +73 -73
  54. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +73 -73
  55. package/tests/repros/assets/repro-bluetooth-controller-ground-decoupling-groups.input.json +214 -0
  56. package/tests/repros/assets/repro-core-ground-inline-label-fallback.input.json +863 -0
  57. package/tests/repros/repro-bluetooth-controller-ground-decoupling-groups.test.ts +18 -0
  58. package/tests/repros/repro-core-ground-inline-label-fallback.test.ts +34 -0
  59. package/tests/repros/repro-hdc2080-ground-label-alignment.test.ts +239 -0
  60. package/tests/repros/repro-tida010076-j4-ground-bus.test.ts +14 -10
  61. package/tests/repros/repro-tida010076-page02.test.ts +2 -2
  62. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +1 -1
  63. 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 msp = getOrthogonalMinimumSpanningTree(
198
- directlyConnectedPins.map((p) => this.pinMap[p]!).filter(Boolean),
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 userNetId =
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: InputPin[],
19
+ export function getOrthogonalMinimumSpanningTree<Pin extends InputPin>(
20
+ pins: Pin[],
21
21
  opts: {
22
22
  maxDistance?: number
23
- forbidEdge?: (a: InputPin, b: InputPin) => boolean
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 anchoringLabels = linkedLabels.filter(
70
- (label) =>
71
- label.orientation === group[0]!.componentFacingDirection &&
72
- label.mspConnectionPairIds.some((traceId) => {
73
- const trace = traceMap.get(traceId)
74
- return (
75
- trace && tracePathContainsPoint(trace.tracePath, label.anchorPoint)
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 rail
99
- // coordinate may propagate through the local component chain. Labels riding
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
- return coordinateIsLocalToEveryTrace ? coordinate : null
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
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.160",
8
+ "version": "0.0.162",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",