@tscircuit/schematic-trace-solver 0.0.159 → 0.0.161

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 (71) hide show
  1. package/dist/index.d.ts +43 -9
  2. package/dist/index.js +807 -115
  3. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +210 -6
  4. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +536 -94
  5. package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +9 -7
  6. package/lib/solvers/InlineNetLabelSolver/pushInlineTerminalLabelsAwayFromAnchoredLabels.ts +295 -0
  7. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +7 -35
  8. package/lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents.ts +60 -0
  9. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getFixedLabelCoordinate.ts +44 -13
  10. package/lib/types/InputProblem.ts +5 -4
  11. package/package.json +1 -1
  12. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +1 -1
  13. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +1 -1
  14. package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +79 -79
  15. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +85 -85
  16. package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +1 -1
  17. package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +163 -163
  18. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +547 -547
  19. package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +13 -13
  20. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +267 -267
  21. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +267 -267
  22. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +109 -109
  23. package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +1 -1
  24. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +49 -49
  25. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +1 -1
  26. package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +103 -103
  27. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +103 -103
  28. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +600 -792
  29. package/tests/bug-reports/bug-report-20260826T072956Z/bug-report-20260826T072956Z.test.ts +66 -1
  30. package/tests/examples/__snapshots__/example51.snap.svg +568 -568
  31. package/tests/fixtures/convertSolverOutputToCircuitJson.test.ts +16 -1
  32. package/tests/fixtures/convertSolverOutputToCircuitJson.ts +23 -12
  33. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +736 -736
  34. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +79 -79
  35. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +127 -127
  36. package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +106 -0
  37. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +31 -31
  38. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +121 -121
  39. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +43 -43
  40. package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +19 -19
  41. package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +277 -0
  42. package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +19 -19
  43. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +7 -7
  44. package/tests/repros/__snapshots__/repro-dc-power-isolated-routing.snap.svg +37 -37
  45. package/tests/repros/__snapshots__/repro-hdc2080-ground-label-alignment.snap.svg +137 -0
  46. package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +61 -61
  47. package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +115 -115
  48. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +743 -743
  49. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +43 -43
  50. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +1 -1
  51. package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +17 -17
  52. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1131 -1131
  53. package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +7 -7
  54. package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +7 -7
  55. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +666 -666
  56. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +73 -73
  57. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +73 -73
  58. package/tests/repros/assets/repro-bluetooth-controller-ground-decoupling-groups.input.json +214 -0
  59. package/tests/repros/assets/repro-core-ground-inline-label-fallback.input.json +863 -0
  60. package/tests/repros/repro-bluetooth-controller-ground-decoupling-groups.test.ts +18 -0
  61. package/tests/repros/repro-core-ground-inline-label-fallback.test.ts +34 -0
  62. package/tests/repros/repro-hdc2080-ground-label-alignment.test.ts +239 -0
  63. package/tests/repros/repro-tida010076-j4-ground-bus.test.ts +14 -10
  64. package/tests/repros/repro-tida010076-page02.test.ts +2 -2
  65. package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +14 -0
  66. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +1 -1
  67. package/tests/solvers/InlineNetLabelSolver/multi-pin-connected-components.test.ts +252 -0
  68. package/tests/solvers/InlineNetLabelSolver/opposite-side-placement.test.ts +71 -0
  69. package/tests/solvers/InlineNetLabelSolver/push-anchored-net-labels-away.test.ts +8 -2
  70. package/tests/solvers/InlineNetLabelSolver/push-inline-terminal-labels-away.test.ts +91 -0
  71. package/tests/solvers/InlineNetLabelSolver/two-pin-terminal-stubs-atomic.test.ts +7 -1
@@ -8,6 +8,7 @@ import {
8
8
  getDimsForOrientation,
9
9
  getRectBounds,
10
10
  } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
11
+ import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
11
12
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
12
13
  import type {
13
14
  ChipId,
@@ -250,6 +251,9 @@ export class AvailableNetOrientationSolver extends BaseSolver {
250
251
  const orientations = this.getAvailableOrientations(label)
251
252
  if (orientations.length === 0) return false
252
253
  if (!orientations.includes(label.orientation)) return true
254
+ if (this.getTextBlockedMultiPinRailTraces(label, orientations[0]!)) {
255
+ return true
256
+ }
253
257
  if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false
254
258
 
255
259
  const bounds = getRectBounds(label.center, label.width, label.height)
@@ -407,9 +411,22 @@ export class AvailableNetOrientationSolver extends BaseSolver {
407
411
  (netConnection?.pinIds.length ?? 0) > 2 &&
408
412
  isYOrientation(requiredOrientation) &&
409
413
  this.hasPortOnlyLabelOnSameNet(label)
414
+ const textBlockedMultiPinRailTraces = this.getTextBlockedMultiPinRailTraces(
415
+ label,
416
+ requiredOrientation,
417
+ )
410
418
  const isPairedSameSidePowerRail =
411
419
  isYOrientation(requiredOrientation) &&
412
420
  this.hasOppositeVerticalRailOnSameChipSide(label, requiredOrientation)
421
+ if (textBlockedMultiPinRailTraces) {
422
+ const nearbyCandidate = this.findValidNearbyRailCandidate(
423
+ label,
424
+ requiredOrientation,
425
+ labelIndex,
426
+ textBlockedMultiPinRailTraces,
427
+ )
428
+ if (nearbyCandidate) return nearbyCandidate
429
+ }
413
430
  if (
414
431
  orientations.length === 1 &&
415
432
  isYOrientation(requiredOrientation) &&
@@ -417,9 +434,22 @@ export class AvailableNetOrientationSolver extends BaseSolver {
417
434
  this.hasTraceContinuingInOrientation(label, requiredOrientation) &&
418
435
  (isDistanceSplitVerticalRail || isPairedSameSidePowerRail)
419
436
  ) {
420
- // Keep the established outward column, but attach at the furthest trace
421
- // point in the required vertical direction. This places y+ labels above
422
- // a rail and y- labels below it while using a short horizontal connector.
437
+ const hostRailCandidate = this.findValidTraceAnchorCandidate(
438
+ label,
439
+ requiredOrientation,
440
+ labelIndex,
441
+ )
442
+ if (hostRailCandidate) {
443
+ const canUseHostRail = this.canPlaceVerticalLabelOnHostRail(
444
+ hostRailCandidate,
445
+ label,
446
+ )
447
+ if (canUseHostRail) return hostRailCandidate
448
+ hostRailCandidate.selected = false
449
+ }
450
+
451
+ // Keep the established outward column when the direct rail end is not a
452
+ // clear segment of the connected component side.
423
453
  const traceAnchorCandidate = this.findValidOutwardTraceAnchorCandidate(
424
454
  label,
425
455
  requiredOrientation,
@@ -785,7 +815,6 @@ export class AvailableNetOrientationSolver extends BaseSolver {
785
815
  connectedPinIds.add(pinId)
786
816
  }
787
817
  }
788
-
789
818
  // Multi-pin rails are routed as a chain of pairwise traces. Follow the
790
819
  // pairs only when they share both a pin and an aligned vertical rail,
791
820
  // which identifies one continuous rail. A shared pin alone can join
@@ -1012,6 +1041,8 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1012
1041
  outwardDistance: number
1013
1042
  phase?: CandidatePhase
1014
1043
  stopOnTraceCollision?: boolean
1044
+ connectorSource?: Point
1045
+ startDistance?: number
1015
1046
  }) {
1016
1047
  const {
1017
1048
  label,
@@ -1023,10 +1054,12 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1023
1054
  outwardDistance,
1024
1055
  phase = "shift",
1025
1056
  stopOnTraceCollision = true,
1057
+ connectorSource,
1058
+ startDistance = LABEL_SEARCH_STEP,
1026
1059
  } = params
1027
1060
 
1028
1061
  for (
1029
- let distance = LABEL_SEARCH_STEP;
1062
+ let distance = startDistance;
1030
1063
  distance <= maxSearchDistance + EPS;
1031
1064
  distance += LABEL_SEARCH_STEP
1032
1065
  ) {
@@ -1034,7 +1067,12 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1034
1067
  x: baseAnchor.x + direction.x * distance,
1035
1068
  y: baseAnchor.y + direction.y * distance,
1036
1069
  }
1037
- const candidate = this.createCandidate(label, anchorPoint, orientation)
1070
+ const candidate = this.createCandidate(
1071
+ label,
1072
+ anchorPoint,
1073
+ orientation,
1074
+ connectorSource,
1075
+ )
1038
1076
  const result = this.evaluateCandidate(
1039
1077
  candidate,
1040
1078
  label,
@@ -1563,6 +1601,172 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1563
1601
  )
1564
1602
  }
1565
1603
 
1604
+ private canPlaceVerticalLabelOnHostRail(
1605
+ candidate: CandidateLabel,
1606
+ label: NetLabelPlacement,
1607
+ ) {
1608
+ const isOnHostRail = label.mspConnectionPairIds.some((traceId) => {
1609
+ const trace = this.traceMap[traceId]
1610
+ return (
1611
+ trace && tracePathContainsPoint(trace.tracePath, candidate.anchorPoint)
1612
+ )
1613
+ })
1614
+ if (!isOnHostRail) return false
1615
+
1616
+ const hostPin = this.pinMap[label.pinIds[0]!]
1617
+ if (!hostPin) return false
1618
+ if (
1619
+ label.pinIds.some(
1620
+ (pinId) => this.pinMap[pinId]?.chipId !== hostPin.chipId,
1621
+ )
1622
+ ) {
1623
+ return false
1624
+ }
1625
+
1626
+ const chip = this.chipObstacleSpatialIndex.chips.find(
1627
+ (chip) => chip.chipId === hostPin.chipId,
1628
+ )
1629
+ if (!chip) return false
1630
+
1631
+ const labelBounds = getRectBounds(
1632
+ candidate.center,
1633
+ candidate.width,
1634
+ candidate.height,
1635
+ )
1636
+ if (
1637
+ labelBounds.minY < chip.bounds.minY - EPS ||
1638
+ labelBounds.maxY > chip.bounds.maxY + EPS
1639
+ ) {
1640
+ return false
1641
+ }
1642
+
1643
+ if (
1644
+ candidate.anchorPoint.x >= chip.bounds.minX - EPS &&
1645
+ candidate.anchorPoint.x <= chip.bounds.maxX + EPS
1646
+ ) {
1647
+ return false
1648
+ }
1649
+
1650
+ return chip.pins.every(
1651
+ (pin) =>
1652
+ label.pinIds.includes(pin.pinId) ||
1653
+ pin.y <= labelBounds.minY + EPS ||
1654
+ pin.y >= labelBounds.maxY - EPS,
1655
+ )
1656
+ }
1657
+
1658
+ private getTextBlockedMultiPinRailTraces(
1659
+ label: NetLabelPlacement,
1660
+ orientation: FacingDirection,
1661
+ ) {
1662
+ if (!isYOrientation(orientation)) return null
1663
+ const componentId = this.pinMap[label.pinIds[0]!]?.chipId
1664
+ const localTraces = Object.values(this.traceMap).filter(
1665
+ (trace) =>
1666
+ trace.globalConnNetId === label.globalConnNetId &&
1667
+ trace.pins.every((pin) => pin.chipId === componentId),
1668
+ )
1669
+ const connectedPinIds = new Set(label.pinIds)
1670
+ const connectedTraces = new Set<SolvedTracePath>()
1671
+ for (let changed = true; changed; ) {
1672
+ changed = false
1673
+ for (const trace of localTraces) {
1674
+ if (connectedTraces.has(trace)) continue
1675
+ if (!trace.pinIds.some((pinId) => connectedPinIds.has(pinId))) continue
1676
+ connectedTraces.add(trace)
1677
+ for (const pinId of trace.pinIds) connectedPinIds.add(pinId)
1678
+ changed = true
1679
+ }
1680
+ }
1681
+ if (connectedPinIds.size <= 3) return null
1682
+
1683
+ const pins = [...connectedPinIds].map((pinId) => this.pinMap[pinId]!)
1684
+ const direction = dir(orientation)
1685
+ const pinX = pins[0]!.x
1686
+ if (pins.some((pin) => Math.abs(pin.x - pinX) > EPS)) return null
1687
+ const sidePins = this.inputProblem.chips
1688
+ .find((chip) => chip.chipId === componentId)!
1689
+ .pins.filter((pin) => Math.abs(pin.x - pinX) <= EPS)
1690
+ const minY = Math.min(...pins.map((pin) => pin.y))
1691
+ const maxY = Math.max(...pins.map((pin) => pin.y))
1692
+ if (
1693
+ sidePins.some(
1694
+ (pin) =>
1695
+ pin.y >= minY - EPS &&
1696
+ pin.y <= maxY + EPS &&
1697
+ !connectedPinIds.has(pin.pinId),
1698
+ ) ||
1699
+ (orientation === "y-" &&
1700
+ minY > Math.min(...sidePins.map((pin) => pin.y)) + EPS) ||
1701
+ (orientation === "y+" &&
1702
+ maxY < Math.max(...sidePins.map((pin) => pin.y)) - EPS)
1703
+ ) {
1704
+ return null
1705
+ }
1706
+ const outerPinPosition = Math.max(
1707
+ ...pins.map((pin) => pin.x * direction.x + pin.y * direction.y),
1708
+ )
1709
+ const blockedByText = (this.inputProblem.textBoxes ?? []).some(
1710
+ (textBox) =>
1711
+ textBox.chipId === componentId &&
1712
+ textBox.center.x * direction.x + textBox.center.y * direction.y >
1713
+ outerPinPosition + EPS,
1714
+ )
1715
+ return blockedByText ? [...connectedTraces] : null
1716
+ }
1717
+
1718
+ private findValidNearbyRailCandidate(
1719
+ label: NetLabelPlacement,
1720
+ orientation: FacingDirection,
1721
+ labelIndex: number,
1722
+ localTraces: SolvedTracePath[],
1723
+ ) {
1724
+ if (!isYOrientation(orientation)) return null
1725
+ const direction = dir(orientation)
1726
+ const connectorY = [
1727
+ ...new Set(localTraces.flatMap((trace) => trace.pinIds)),
1728
+ ]
1729
+ .map((pinId) => this.pinMap[pinId]!)
1730
+ .sort((a, b) => b.y * direction.y - a.y * direction.y)[0]?.y
1731
+ if (connectorY === undefined) return null
1732
+
1733
+ const pinX = this.pinMap[label.pinIds[0]!]!.x
1734
+ const railXs = [
1735
+ ...new Set(
1736
+ localTraces.flatMap((trace) => trace.tracePath.map((point) => point.x)),
1737
+ ),
1738
+ ]
1739
+ .filter((x) => Math.abs(x - pinX) > EPS)
1740
+ .sort((a, b) => Math.abs(a - pinX) - Math.abs(b - pinX))
1741
+
1742
+ for (const x of railXs) {
1743
+ const connectorSource = { x, y: connectorY }
1744
+ if (
1745
+ !Object.values(this.traceMap).some(
1746
+ (trace) =>
1747
+ trace.globalConnNetId === label.globalConnNetId &&
1748
+ tracePathContainsPoint(trace.tracePath, connectorSource),
1749
+ )
1750
+ ) {
1751
+ continue
1752
+ }
1753
+ const candidate = this.findValidCandidateInShiftColumn({
1754
+ label,
1755
+ labelIndex,
1756
+ orientation,
1757
+ direction,
1758
+ baseAnchor: connectorSource,
1759
+ maxSearchDistance: this.maxSearchDistance,
1760
+ outwardDistance: 0,
1761
+ stopOnTraceCollision: false,
1762
+ connectorSource,
1763
+ startDistance: 0,
1764
+ })
1765
+ if (candidate) return candidate
1766
+ }
1767
+ return null
1768
+ }
1769
+
1566
1770
  private hasPortOnlyLabelOnSameNet(label: NetLabelPlacement) {
1567
1771
  return this.outputNetLabelPlacements.some(
1568
1772
  (otherLabel) =>