diff --git a/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts b/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts index 9911d3949..66b1f782e 100644 --- a/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +++ b/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts @@ -1,23 +1,24 @@ +import type { ConnectivityMap } from "connectivity-map" import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem" import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver" import type { - InputProblem, + InputChip, InputPin, + InputProblem, PinId, - InputChip, } from "lib/types/InputProblem" +import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces" +import { createLabeledRailNetHelpers } from "lib/utils/labeledRailNets" +import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections" import { BaseSolver } from "../BaseSolver/BaseSolver" +import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver" import { SchematicTraceSingleLineSolver2 } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2" -import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces" import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem" -import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver" -import type { ConnectivityMap } from "connectivity-map" -import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections" const NEAREST_NEIGHBOR_COUNT = 3 const distance = (p1: InputPin, p2: InputPin) => { - return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)) + return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) } export class LongDistancePairSolver extends BaseSolver { @@ -73,6 +74,11 @@ export class LongDistancePairSolver extends BaseSolver { > = [] const addedPairKeys = new Set() + // Distant pairs on labeled rails stay label-connected; wiring them + // duplicates the net labels with a long bus trace (#670). + const { isLabeledRailNet, exceedsDirectWiringDistance } = + createLabeledRailNetHelpers(inputProblem) + for (const netId of Object.keys(netConnMap.netMap)) { const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId) if (allPinIdsInNet.length < 2) continue @@ -97,6 +103,11 @@ export class LongDistancePairSolver extends BaseSolver { }, ] }) + .filter( + (neighbor) => + !isLabeledRailNet(unconnectedPinId) || + !exceedsDirectWiringDistance(neighbor.distance), + ) .sort((a, b) => a.distance - b.distance) .slice(0, NEAREST_NEIGHBOR_COUNT) diff --git a/lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts b/lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts index 75ed46b41..0838d965a 100644 --- a/lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts +++ b/lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts @@ -20,6 +20,7 @@ import { import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem" import type { InputProblem, PinId } from "lib/types/InputProblem" import type { FacingDirection } from "lib/utils/dir" +import { createLabeledRailNetHelpers } from "lib/utils/labeledRailNets" const ROUTE_CLEARANCE = 0.2 const COORDINATE_TOLERANCE = 1e-9 @@ -469,6 +470,7 @@ export class UnroutedTraceRecoverySolver extends BaseSolver { private queuedConnectionPairs: MspConnectionPair[] private maxConnectionDistance: number private groundGlobalConnNetId?: string + private labeledRailNets: ReturnType public solvedUnroutedTraces: SolvedTracePath[] = [] constructor( @@ -490,6 +492,7 @@ export class UnroutedTraceRecoverySolver extends BaseSolver { ) this.groundGlobalConnNetId = netConnMap.getNetConnectedToId(GROUND_NET_ID) ?? undefined + this.labeledRailNets = createLabeledRailNetHelpers(this.inputProblem) } override getConstructorParams() { @@ -536,7 +539,25 @@ export class UnroutedTraceRecoverySolver extends BaseSolver { failedConnectionPairs: this.failedConnectionPairs, }) + // Pins on a labeled rail already carry a net label each. A recovery route + // that has to detour far around obstacles duplicates those labels with a + // long bus trace, which is exactly what #670 asks us to avoid. The pins can + // sit within `maxConnectionDistance` of each other and still only be + // reachable by a much longer path, so the routed length — not the + // straight-line pin distance — decides this. + const isLabeledRailPair = this.labeledRailNets.isLabeledRailNet( + connectionPair.pins[0].pinId, + ) + for (const tracePath of candidates) { + if ( + isLabeledRailPair && + this.labeledRailNets.exceedsDirectWiringDistance( + getPathLength(tracePath), + ) + ) { + continue + } if ( pathCollidesWithObstacles({ path: tracePath, diff --git a/lib/utils/labeledRailNets.ts b/lib/utils/labeledRailNets.ts new file mode 100644 index 000000000..10af0b614 --- /dev/null +++ b/lib/utils/labeledRailNets.ts @@ -0,0 +1,60 @@ +import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem" +import { DEFAULT_MAX_MSP_PAIR_DISTANCE } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver" +import type { InputProblem, PinId } from "lib/types/InputProblem" + +/** + * Nets that declare net-label orientations (power/ground rails and named buses + * like V3_3, GND, SDA) get a net label drawn at every pin once the net has more + * than two pins. Wiring a distant pair on such a net duplicates information + * that the labels already carry, and the resulting bus trace snakes across the + * schematic (#670). + * + * `isLabeledRailNet` reports whether a pin sits on such a net: it declares + * available net-label orientations and has more than two pins, so every pin + * really does get a label. A two-pin labeled net is excluded because one trace + * is cleaner than a floating label pair (see rotated-components-rail-label). + * + * `exceedsDirectWiringDistance` compares a length against `maxMspPairDistance`, + * the same threshold `MspConnectionPairSolver` uses to decide a pair is too far + * apart to wire directly. Callers pass whichever length is meaningful for them: + * the straight-line pin distance when choosing candidate pairs, or the routed + * path length when a detour around obstacles is what makes a trace long. + */ +export const createLabeledRailNetHelpers = (inputProblem: InputProblem) => { + const labeledNetIds = new Set( + Object.keys(inputProblem.availableNetLabelOrientations ?? {}), + ) + const maxPairDistance = + inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE + + const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem) + + const pinIds = new Set() + for (const chip of inputProblem.chips) { + for (const pin of chip.pins) pinIds.add(pin.pinId) + } + + const labeledNetCache = new Map() + + const netIsLabeled = (pinId: PinId): boolean => { + const netId = netConnMap.getNetConnectedToId(pinId) + if (!netId) return false + + const cached = labeledNetCache.get(netId) + if (cached !== undefined) return cached + + const idsInNet = netConnMap.getIdsConnectedToNet(netId) + const pinCount = idsInNet.filter((id) => pinIds.has(id)).length + const declaresLabels = idsInNet.some((id) => labeledNetIds.has(id)) + + const isLabeled = pinCount > 2 && declaresLabels + labeledNetCache.set(netId, isLabeled) + return isLabeled + } + + return { + isLabeledRailNet: (pinId: PinId): boolean => netIsLabeled(pinId), + exceedsDirectWiringDistance: (length: number): boolean => + length > maxPairDistance, + } +} diff --git a/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg b/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg index b98797851..729b0dd07 100644 --- a/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +++ b/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg @@ -1,346 +1,364 @@ - \ No newline at end of file diff --git a/tests/repros/__snapshots__/small-variant-resistor-facing-direction.snap.svg b/tests/repros/__snapshots__/small-variant-resistor-facing-direction.snap.svg index 8a5579786..4c5718b11 100644 --- a/tests/repros/__snapshots__/small-variant-resistor-facing-direction.snap.svg +++ b/tests/repros/__snapshots__/small-variant-resistor-facing-direction.snap.svg @@ -1,7 +1,8 @@ -