@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.
- package/dist/index.d.ts +43 -9
- package/dist/index.js +807 -115
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +210 -6
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +536 -94
- package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +9 -7
- package/lib/solvers/InlineNetLabelSolver/pushInlineTerminalLabelsAwayFromAnchoredLabels.ts +295 -0
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +7 -35
- package/lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents.ts +60 -0
- package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getFixedLabelCoordinate.ts +44 -13
- package/lib/types/InputProblem.ts +5 -4
- 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 +600 -792
- package/tests/bug-reports/bug-report-20260826T072956Z/bug-report-20260826T072956Z.test.ts +66 -1
- 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 +106 -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/repros/repro-usb-power-vbus-label-detour.test.ts +14 -0
- package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +1 -1
- package/tests/solvers/InlineNetLabelSolver/multi-pin-connected-components.test.ts +252 -0
- package/tests/solvers/InlineNetLabelSolver/opposite-side-placement.test.ts +71 -0
- package/tests/solvers/InlineNetLabelSolver/push-anchored-net-labels-away.test.ts +8 -2
- package/tests/solvers/InlineNetLabelSolver/push-inline-terminal-labels-away.test.ts +91 -0
- 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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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 =
|
|
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(
|
|
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) =>
|