diff --git a/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts b/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts index 7d1e4d4..1759615 100644 --- a/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +++ b/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts @@ -8,9 +8,22 @@ import { generateSnipAndReconnectCandidates } from "./trySnipAndReconnect" import { generateFourPointDetourCandidates } from "./tryFourPointDetour" import { simplifyPath } from "../TraceCleanupSolver/simplifyPath" +import { generateMoveTraceSegmentsCandidates } from "./tryMoveTraceSegments" + +const getPathLength = (pts: Point[]) => { + let len = 0 + for (let i = 0; i < pts.length - 1; i++) { + const dx = pts[i + 1].x - pts[i].x + const dy = pts[i + 1].y - pts[i].y + len += Math.sqrt(dx * dx + dy * dy) + } + return len +} + export const generateRerouteCandidates = ({ trace, label, + problem, paddingBuffer, detourCount, }: { @@ -58,5 +71,31 @@ export const generateRerouteCandidates = ({ detourCount, }) - return [...fourPointCandidates, ...snipReconnectCandidates] + const moveTraceSegmentsCandidates = generateMoveTraceSegmentsCandidates({ + initialTrace, + label, + labelBounds, + paddingBuffer, + detourCount, + }) + + // Return candidates in order of preference: four-point, snip-reconnect, then move trace segments + const allCandidates = [ + ...fourPointCandidates, + ...snipReconnectCandidates, + ...moveTraceSegmentsCandidates, + ] + + // Sort by path length within each group, but keep move trace segments at the end + const sortedFourPoint = fourPointCandidates.sort( + (a, b) => getPathLength(a) - getPathLength(b), + ) + const sortedSnipReconnect = snipReconnectCandidates.sort( + (a, b) => getPathLength(a) - getPathLength(b), + ) + const sortedMoveTrace = moveTraceSegmentsCandidates.sort( + (a, b) => getPathLength(a) - getPathLength(b), + ) + + return [...sortedFourPoint, ...sortedSnipReconnect, ...sortedMoveTrace] } diff --git a/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryMoveTraceSegments.ts b/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryMoveTraceSegments.ts new file mode 100644 index 0000000..e2c8842 --- /dev/null +++ b/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryMoveTraceSegments.ts @@ -0,0 +1,53 @@ +import type { Point } from "@tscircuit/math-utils" +import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver" +import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver" +import type { Bounds } from "@tscircuit/math-utils" +import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions" + +export const generateMoveTraceSegmentsCandidates = ({ + initialTrace, + label, + labelBounds, + paddingBuffer, + detourCount, +}: { + initialTrace: SolvedTracePath + label: NetLabelPlacement + labelBounds: Bounds & { chipId: string } + paddingBuffer: number + detourCount: number +}): Point[][] => { + const candidates: Point[][] = [] + const path = initialTrace.tracePath + + // Only apply to simple traces (2 points = straight line) + if (path.length !== 2) { + return [] + } + + const p1 = path[0] + const p2 = path[1] + + // Only move segments that actually intersect with the label bounds + if (!segmentIntersectsRect(p1, p2, labelBounds)) { + return [] + } + + const isHorizontal = p1.y === p2.y + + if (isHorizontal) { + const newY = labelBounds.maxY + paddingBuffer + label.height / 2 + const newPath = JSON.parse(JSON.stringify(path)) + newPath[0].y = newY + newPath[1].y = newY + candidates.push(newPath) + } else { + const newX = labelBounds.maxX + paddingBuffer + label.width / 2 + const newPath = JSON.parse(JSON.stringify(path)) + newPath[0].x = newX + newPath[1].x = newX + candidates.push(newPath) + } + + return candidates +} diff --git a/tests/assets/repro.input.json b/tests/assets/repro.input.json new file mode 100644 index 0000000..d22a1c2 --- /dev/null +++ b/tests/assets/repro.input.json @@ -0,0 +1,57 @@ +{ + "netLabelPlacements": [ + { + "globalConnNetId": "connectivity_net1", + "dcConnNetId": "connectivity_net1", + "netId": ".L1 > .pin2 to .M1 > .drain", + "mspConnectionPairIds": ["L1.2-D1.1"], + "pinIds": ["L1.2", "D1.1"], + "orientation": "y+", + "anchorPoint": { + "x": 0.78, + "y": 2.97 + }, + "width": 0.2, + "height": 0.45, + "center": { + "x": 1.53, + "y": 2.97 + } + } + ], + "traces": [ + { + "mspPairId": "V1.1-L1.1", + "dcConnNetId": "connectivity_net0", + "globalConnNetId": "connectivity_net0", + "pins": [ + { + "pinId": "V1.1", + "x": -5.005, + "y": 2.54, + "chipId": "schematic_component_0", + "_facingDirection": "y+" + }, + { + "pinId": "L1.1", + "x": -0.58, + "y": 2.98, + "_facingDirection": "x-", + "chipId": "schematic_component_1" + } + ], + "tracePath": [ + { + "x": 0, + "y": 2.97 + }, + { + "x": 2, + "y": 2.97 + } + ], + "mspConnectionPairIds": ["V1.1-L1.1"], + "pinIds": ["V1.1", "L1.1"] + } + ] +} diff --git a/tests/examples/__snapshots__/example14.snap.svg b/tests/examples/__snapshots__/example14.snap.svg index 6ed2077..0e7de58 100644 --- a/tests/examples/__snapshots__/example14.snap.svg +++ b/tests/examples/__snapshots__/example14.snap.svg @@ -2,156 +2,195 @@ +x+" data-x="1.2000000000000002" data-y="-0.30000000000000004" cx="425" cy="337.5" r="3" fill="hsl(319, 100%, 50%, 0.8)" /> +x-" data-x="-1.2000000000000002" data-y="-0.30000000000000004" cx="215" cy="337.5" r="3" fill="hsl(320, 100%, 50%, 0.8)" /> +x+" data-x="1.2000000000000002" data-y="0.09999999999999998" cx="425" cy="302.5" r="3" fill="hsl(321, 100%, 50%, 0.8)" /> +x-" data-x="-1.2000000000000002" data-y="0.30000000000000004" cx="215" cy="285" r="3" fill="hsl(322, 100%, 50%, 0.8)" /> +x-" data-x="-1.2000000000000002" data-y="0.10000000000000003" cx="215" cy="302.5" r="3" fill="hsl(323, 100%, 50%, 0.8)" /> +x-" data-x="-1.2000000000000002" data-y="-0.09999999999999998" cx="215" cy="320" r="3" fill="hsl(324, 100%, 50%, 0.8)" /> +x+" data-x="1.2000000000000002" data-y="-0.10000000000000003" cx="425" cy="320" r="3" fill="hsl(325, 100%, 50%, 0.8)" /> +x+" data-x="1.2000000000000002" data-y="0.30000000000000004" cx="425" cy="285" r="3" fill="hsl(326, 100%, 50%, 0.8)" /> +x+" data-x="3" data-y="-0.10000000000000016" cx="582.5" cy="320" r="3" fill="hsl(226, 100%, 50%, 0.8)" /> +x-" data-x="1.9000000000000004" data-y="-0.10000000000000002" cx="486.25" cy="320" r="3" fill="hsl(227, 100%, 50%, 0.8)" /> +x+" data-x="1.2000000000000002" data-y="-1.2944553500000002" cx="425" cy="424.514843125" r="3" fill="hsl(107, 100%, 50%, 0.8)" /> +x-" data-x="0.10000000000000009" data-y="-1.2944553500000002" cx="328.75" cy="424.514843125" r="3" fill="hsl(108, 100%, 50%, 0.8)" /> +y+" data-x="-1.2000000000000002" data-y="-1.1500000000000001" cx="215" cy="411.875" r="3" fill="hsl(121, 100%, 50%, 0.8)" /> +y-" data-x="-1.2000000000000002" data-y="-2.25" cx="215" cy="508.125" r="3" fill="hsl(122, 100%, 50%, 0.8)" /> +x+" data-x="-1.9000000000000004" data-y="0.10000000000000002" cx="153.74999999999997" cy="302.5" r="3" fill="hsl(2, 100%, 50%, 0.8)" /> +x-" data-x="-3" data-y="0.10000000000000016" cx="57.5" cy="302.5" r="3" fill="hsl(3, 100%, 50%, 0.8)" /> +y-" data-x="1.2000000000000002" data-y="1.1500000000000001" cx="425" cy="210.625" r="3" fill="hsl(348, 100%, 50%, 0.8)" /> +y+" data-x="1.2000000000000002" data-y="2.25" cx="425" cy="114.375" r="3" fill="hsl(349, 100%, 50%, 0.8)" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -124,7 +124,7 @@ y+" data-x="1.2000000000000002" data-y="2.25" cx="425" cy="114.375" r="3" fill=" - +