diff --git a/lib/solvers/PackInnerPartitionsSolver/ParallelAlignedPassiveSolver.ts b/lib/solvers/PackInnerPartitionsSolver/ParallelAlignedPassiveSolver.ts index 0e4bf71..fc9a279 100644 --- a/lib/solvers/PackInnerPartitionsSolver/ParallelAlignedPassiveSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/ParallelAlignedPassiveSolver.ts @@ -41,6 +41,7 @@ import { getBoundsCenter, getBoundsFromPoints, } from "@tscircuit/math-utils" +import { applyDirectPassiveTraceClearance } from "../../utils/offsetCollinearConnections" const CLEARANCE_EPSILON = 1e-6 const MAX_RESOLVE_ITERATIONS = 16 @@ -106,11 +107,18 @@ export class ParallelAlignedPassiveSolver extends BaseSolver { for (const [chipId, placement] of Object.entries(base.chipPlacements)) { placements[chipId] = { ...placement } } - for (const passiveGroup of findSameSidePassiveGroups( - this.partitionInputProblem, - )) { + const passiveGroups = findSameSidePassiveGroups(this.partitionInputProblem) + for (const passiveGroup of passiveGroups) { this.reflowPassiveGroup(placements, passiveGroup) } + applyDirectPassiveTraceClearance({ + inputProblem: this.partitionInputProblem, + connectedPinsByPinId: this.pinIdToStronglyConnectedPins, + chipPlacements: placements, + rigidChipGroups: passiveGroups.map( + (passiveGroup) => passiveGroup.passiveChipIds, + ), + }) return { chipPlacements: placements, groupPlacements: base.groupPlacements } } diff --git a/lib/utils/offsetCollinearConnections.ts b/lib/utils/offsetCollinearConnections.ts index ae03d29..756fac6 100644 --- a/lib/utils/offsetCollinearConnections.ts +++ b/lib/utils/offsetCollinearConnections.ts @@ -108,31 +108,38 @@ const placementHasClearance = ({ return true } -const tryOffsetChip = ({ - chipId, +const tryOffsetChips = ({ + chipIds, dx, dy, chipPlacements, inputProblem, }: { - chipId: ChipId + chipIds: ChipId[] dx: number dy: number chipPlacements: Record inputProblem: InputProblem }): void => { - const placement = chipPlacements[chipId] - if (!placement) return + const placements = chipIds + .map((chipId) => chipPlacements[chipId]) + .filter((placement): placement is Placement => Boolean(placement)) + if (placements.length !== chipIds.length) return - placement.x += dx - placement.y += dy - if ( - !placementHasClearance({ + for (const placement of placements) { + placement.x += dx + placement.y += dy + } + const allChipsHaveClearance = chipIds.every((chipId) => + placementHasClearance({ chipId, chipPlacements, inputProblem, - }) - ) { + }), + ) + if (allChipsHaveClearance) return + + for (const placement of placements) { placement.x -= dx placement.y -= dy } @@ -142,10 +149,12 @@ export const applyDirectPassiveTraceClearance = ({ inputProblem, connectedPinsByPinId, chipPlacements, + rigidChipGroups = [], }: { inputProblem: InputProblem connectedPinsByPinId: Record chipPlacements: Record + rigidChipGroups?: ChipId[][] }): void => { const pinOwnerMap = createPinOwnerMap(inputProblem) const chipCount = Object.keys(inputProblem.chipMap).length @@ -183,10 +192,16 @@ export const applyDirectPassiveTraceClearance = ({ inputProblem, placement: connectedPlacement, }) + const rigidChipGroup = rigidChipGroups.find((chipIds) => + chipIds.includes(connectedChip.chipId), + ) + const chipIdsToOffset = rigidChipGroup ?? [connectedChip.chipId] if (pinsShareX && pinsAreVerticallyOriented) { - tryOffsetChip({ - chipId: connectedChip.chipId, + // A rigid row keeps its intentional vertical anchor trace straight. + if (rigidChipGroup) continue + tryOffsetChips({ + chipIds: [connectedChip.chipId], dx: -TRACE_CLEARANCE, dy: 0, chipPlacements, @@ -205,8 +220,8 @@ export const applyDirectPassiveTraceClearance = ({ const isGroundedLoad = otherPinId && pinConnectsToGround(inputProblem, otherPinId) if (pinsShareY && (isSingleVerticalPassive || isGroundedLoad)) { - tryOffsetChip({ - chipId: connectedChip.chipId, + tryOffsetChips({ + chipIds: chipIdsToOffset, dx: 0, dy: -TRACE_CLEARANCE, chipPlacements, @@ -243,8 +258,8 @@ export const offsetChipAnchoredGroundedLoadConnections = ({ continue } - tryOffsetChip({ - chipId: groundedLoadPair.upperChip.chipId, + tryOffsetChips({ + chipIds: [groundedLoadPair.upperChip.chipId], dx: -TRACE_CLEARANCE, dy: 0, chipPlacements, diff --git a/tests/offsetCollinearConnections.test.ts b/tests/offsetCollinearConnections.test.ts index 4e51c19..0c067ba 100644 --- a/tests/offsetCollinearConnections.test.ts +++ b/tests/offsetCollinearConnections.test.ts @@ -6,6 +6,9 @@ import { rotatePinOffset } from "../lib/utils/rotatePinOffset" import chipPortInput from "./assets/chip-port-without-portarrangement.input.json" import repro44Input from "./assets/repro44-e2e-pack-and-schematic.input.json" import bootResetInput from "./assets/schematic-section-rp2040-boot-reset.input.json" +import polarizedCapacitorInput from "./assets/polarized-capacitor-auto-layout.input.json" +import bq24074RightResistorsInput from "./assets/repro-bq24074-right-resistors.input.json" +import bq24074BottomResistorsInput from "./assets/repro-bq24074-bottom-resistors.input.json" const getAbsolutePinPosition = ( inputProblem: InputProblem, @@ -68,3 +71,36 @@ test("keeps a standalone rail-to-ground chain in one line", () => { expect(resistorPin.x).toBeCloseTo(switchPin.x) }) + +test("preserves direct trace clearance after reflowing a passive row", () => { + const inputProblem = polarizedCapacitorInput as InputProblem + const outputLayout = solve(inputProblem) + const mainPin = getAbsolutePinPosition(inputProblem, outputLayout, "U1.1") + const c1Pin = getAbsolutePinPosition(inputProblem, outputLayout, "C1.1") + const c2Pin = getAbsolutePinPosition(inputProblem, outputLayout, "C2.1") + + expect(c1Pin.y - mainPin.y).toBeCloseTo(-0.2) + expect(c2Pin.y - mainPin.y).toBeCloseTo(-0.2) + expect(c1Pin.y).toBeCloseTo(c2Pin.y) +}) + +test("offsets a reflowed passive row as a rigid group", () => { + const inputProblem = bq24074RightResistorsInput as InputProblem + const outputLayout = solve(inputProblem) + const mainPin = getAbsolutePinPosition(inputProblem, outputLayout, "U1.14") + const r3Pin = getAbsolutePinPosition(inputProblem, outputLayout, "R3.1") + const rowY = outputLayout.chipPlacements.R3!.y + + expect(r3Pin.y - mainPin.y).toBeCloseTo(-0.2) + expect(outputLayout.chipPlacements.R1!.y).toBeCloseTo(rowY) + expect(outputLayout.chipPlacements.R2!.y).toBeCloseTo(rowY) +}) + +test("preserves a straight vertical connection to a reflowed bottom row", () => { + const inputProblem = bq24074BottomResistorsInput as InputProblem + const outputLayout = solve(inputProblem) + const mainPin = getAbsolutePinPosition(inputProblem, outputLayout, "U1.12") + const r2Pin = getAbsolutePinPosition(inputProblem, outputLayout, "R2.1") + + expect(r2Pin.x).toBeCloseTo(mainPin.x) +}) diff --git a/tests/repros/__snapshots__/polarized-capacitor-auto-layout.snap.svg b/tests/repros/__snapshots__/polarized-capacitor-auto-layout.snap.svg index eb8b643..6c06c81 100644 --- a/tests/repros/__snapshots__/polarized-capacitor-auto-layout.snap.svg +++ b/tests/repros/__snapshots__/polarized-capacitor-auto-layout.snap.svg @@ -1,4 +1,4 @@ -C1C2U1 + ]]> \ No newline at end of file diff --git a/tests/repros/__snapshots__/repro-bq24074-right-resistors.snap.svg b/tests/repros/__snapshots__/repro-bq24074-right-resistors.snap.svg index ce68ae0..b682a22 100644 --- a/tests/repros/__snapshots__/repro-bq24074-right-resistors.snap.svg +++ b/tests/repros/__snapshots__/repro-bq24074-right-resistors.snap.svg @@ -1,4 +1,4 @@ -U1 [fixed]R1R2R3