diff --git a/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts b/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts index f36adf92a..8f830cc83 100644 --- a/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +++ b/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts @@ -11,6 +11,7 @@ import { getColorFromString } from "lib/utils/getColorFromString" import { getConnectivityMapsFromInputProblem } from "../MspConnectionPairSolver/getConnectivityMapFromInputProblem" import { getNetLabelWidthForConnection } from "lib/utils/getNetLabelWidthForConnection" import { getTraceConnectedPinComponents } from "lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents" +import { normalizeHorizontalFallbackBoundsCollidingWithGroundLabels } from "./normalizeCollidingHorizontalFallbackBounds" /** * A group of traces that have at least one overlapping segment and @@ -71,6 +72,7 @@ export interface NetLabelPlacement { export class NetLabelPlacementSolver extends BaseSolver { inputProblem: InputProblem inputTraceMap: Record + normalizeHorizontalFallbackBoundsOnCompletion: boolean overlappingSameNetTraceGroups: Array @@ -86,10 +88,13 @@ export class NetLabelPlacementSolver extends BaseSolver { constructor(params: { inputProblem: InputProblem inputTraceMap: Record + normalizeHorizontalFallbackBoundsOnCompletion?: boolean }) { super() this.inputProblem = params.inputProblem this.inputTraceMap = params.inputTraceMap + this.normalizeHorizontalFallbackBoundsOnCompletion = + params.normalizeHorizontalFallbackBoundsOnCompletion ?? false this.overlappingSameNetTraceGroups = this.computeOverlappingSameNetTraceGroups() @@ -337,6 +342,13 @@ export class NetLabelPlacementSolver extends BaseSolver { this.queuedOverlappingSameNetTraceGroups.shift() if (!nextOverlappingSameNetTraceGroup) { + if (this.normalizeHorizontalFallbackBoundsOnCompletion) { + this.netLabelPlacements = + normalizeHorizontalFallbackBoundsCollidingWithGroundLabels({ + inputProblem: this.inputProblem, + netLabelPlacements: this.netLabelPlacements, + }) + } this.solved = true return } diff --git a/lib/solvers/NetLabelPlacementSolver/normalizeCollidingHorizontalFallbackBounds.ts b/lib/solvers/NetLabelPlacementSolver/normalizeCollidingHorizontalFallbackBounds.ts new file mode 100644 index 000000000..a66f87bdd --- /dev/null +++ b/lib/solvers/NetLabelPlacementSolver/normalizeCollidingHorizontalFallbackBounds.ts @@ -0,0 +1,166 @@ +import type { InputNetConnection, InputProblem } from "lib/types/InputProblem" +import { boundsOverlap } from "lib/utils/textBoxBounds" +import type { NetLabelPlacement } from "./NetLabelPlacementSolver" +import { + getCenterFromAnchor, + getDimsForOrientation, + getRectBounds, +} from "./SingleNetLabelPlacementSolver/geometry" + +const getRenderedHorizontalFallbackPlacement = (params: { + inputProblem: InputProblem + netLabelPlacement: NetLabelPlacement +}): NetLabelPlacement | null => { + const { inputProblem, netLabelPlacement } = params + if (netLabelPlacement.mspConnectionPairIds.length > 0) return null + if ( + netLabelPlacement.orientation !== "x+" && + netLabelPlacement.orientation !== "x-" + ) { + return null + } + + const effectiveNetId = + netLabelPlacement.netId ?? netLabelPlacement.globalConnNetId + const requestedOrientations = + inputProblem.availableNetLabelOrientations[effectiveNetId] ?? [] + if ( + requestedOrientations.length === 0 || + requestedOrientations.some( + (orientation) => orientation !== "y+" && orientation !== "y-", + ) + ) { + return null + } + + const netConnection = netLabelPlacement.netId + ? inputProblem.netConnections.find( + (connection) => connection.netId === netLabelPlacement.netId, + ) + : inputProblem.netConnections.find((connection) => + connection.pinIds.some((pinId) => + netLabelPlacement.pinIds.includes(pinId), + ), + ) + const renderedHorizontalWidth = netConnection?.netLabelHeight + if ( + renderedHorizontalWidth === undefined || + renderedHorizontalWidth <= + netLabelPlacement.width + netLabelPlacement.height + 1e-9 + ) { + return null + } + + const { width, height } = getDimsForOrientation({ + orientation: netLabelPlacement.orientation, + netLabelWidth: renderedHorizontalWidth, + netLabelHeight: netLabelPlacement.height, + }) + const previousBaseCenter = getCenterFromAnchor( + netLabelPlacement.anchorPoint, + netLabelPlacement.orientation, + netLabelPlacement.width, + netLabelPlacement.height, + ) + const renderedBaseCenter = getCenterFromAnchor( + netLabelPlacement.anchorPoint, + netLabelPlacement.orientation, + width, + height, + ) + + return { + ...netLabelPlacement, + width, + height, + center: { + x: + renderedBaseCenter.x + + netLabelPlacement.center.x - + previousBaseCenter.x, + y: + renderedBaseCenter.y + + netLabelPlacement.center.y - + previousBaseCenter.y, + }, + } +} + +const getNetConnectionForPlacement = ( + inputProblem: InputProblem, + netLabelPlacement: NetLabelPlacement, +): InputNetConnection | undefined => + netLabelPlacement.netId + ? inputProblem.netConnections.find( + (connection) => connection.netId === netLabelPlacement.netId, + ) + : inputProblem.netConnections.find((connection) => + connection.pinIds.some((pinId) => + netLabelPlacement.pinIds.includes(pinId), + ), + ) + +/** + * Corrects the bounds of horizontal fallbacks from vertical-only rails when + * their rendered text would overlap a ground label. Ground rails are handled + * specially downstream, so their collision checks need the rendered width. + */ +export const normalizeHorizontalFallbackBoundsCollidingWithGroundLabels = + (params: { + inputProblem: InputProblem + netLabelPlacements: NetLabelPlacement[] + }) => { + const { inputProblem, netLabelPlacements } = params + const renderedFallbackPlacements = netLabelPlacements.map( + (netLabelPlacement) => + getRenderedHorizontalFallbackPlacement({ + inputProblem, + netLabelPlacement, + }), + ) + + return netLabelPlacements.map((netLabelPlacement, placementIndex) => { + const renderedFallbackPlacement = + renderedFallbackPlacements[placementIndex] + if (!renderedFallbackPlacement) return netLabelPlacement + + const renderedBounds = getRectBounds( + renderedFallbackPlacement.center, + renderedFallbackPlacement.width, + renderedFallbackPlacement.height, + ) + const intersectsDifferentNetLabel = netLabelPlacements.some( + (otherNetLabelPlacement, otherPlacementIndex) => { + if (otherPlacementIndex === placementIndex) return false + if ( + otherNetLabelPlacement.globalConnNetId === + netLabelPlacement.globalConnNetId + ) { + return false + } + if ( + getNetConnectionForPlacement(inputProblem, otherNetLabelPlacement) + ?.isGround !== true + ) { + return false + } + + const effectiveOtherPlacement = + renderedFallbackPlacements[otherPlacementIndex] ?? + otherNetLabelPlacement + return boundsOverlap( + renderedBounds, + getRectBounds( + effectiveOtherPlacement.center, + effectiveOtherPlacement.width, + effectiveOtherPlacement.height, + ), + ) + }, + ) + + return intersectsDifferentNetLabel + ? renderedFallbackPlacement + : netLabelPlacement + }) + } diff --git a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts index 1a8aa4246..c08a2b6f7 100644 --- a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +++ b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts @@ -208,6 +208,7 @@ export class SchematicTracePipelineSolver extends BaseSolver { (p) => [p.mspPairId, p], ), ), + normalizeHorizontalFallbackBoundsOnCompletion: true, }, ], { @@ -346,6 +347,7 @@ export class SchematicTracePipelineSolver extends BaseSolver { inputTraceMap: Object.fromEntries( traces.map((trace: SolvedTracePath) => [trace.mspPairId, trace]), ), + normalizeHorizontalFallbackBoundsOnCompletion: true, }, ] }, diff --git a/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg b/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg index b786de188..4898c8516 100644 --- a/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg +++ b/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg @@ -1,36 +1,38 @@ -