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)" />
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -181,12 +220,12 @@ y+" data-x="1.2000000000000002" data-y="2.25" cx="432" cy="110" r="3" fill="hsl(
// Calculate real coordinates using inverse transformation
const matrix = {
- "a": 93.33333333333333,
+ "a": 87.5,
"c": 0,
"e": 320,
"b": 0,
- "d": -93.33333333333333,
- "f": 320
+ "d": -87.5,
+ "f": 311.25
};
// Manually invert and apply the affine transform
// Since we only use translate and scale, we can directly compute:
diff --git a/tests/examples/example14.test.tsx b/tests/examples/example14.test.tsx
index ba22576..ba51675 100644
--- a/tests/examples/example14.test.tsx
+++ b/tests/examples/example14.test.tsx
@@ -2,7 +2,7 @@ import { expect, test } from "bun:test"
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
import { inputProblem } from "site/examples/example14.page"
-test.skip("example14 - should fail with net label placement collision error", () => {
+test("example14 - net label collision resolved with trace segment moving", () => {
const solver = new SchematicTracePipelineSolver(inputProblem)
solver.solve()
diff --git a/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/repro.snap.svg b/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/repro.snap.svg
new file mode 100644
index 0000000..5450c31
--- /dev/null
+++ b/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/repro.snap.svg
@@ -0,0 +1,207 @@
+
\ No newline at end of file
diff --git a/tests/solvers/TraceLabelOverlapAvoidanceSolver/repro.test.ts b/tests/solvers/TraceLabelOverlapAvoidanceSolver/repro.test.ts
new file mode 100644
index 0000000..00663bb
--- /dev/null
+++ b/tests/solvers/TraceLabelOverlapAvoidanceSolver/repro.test.ts
@@ -0,0 +1,18 @@
+import { expect } from "bun:test"
+import { test } from "bun:test"
+import { TraceLabelOverlapAvoidanceSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver"
+import inputProblem from "tests/assets/example25.json"
+import "tests/fixtures/matcher"
+import testInput from "tests/assets/repro.input.json"
+
+test("TraceLabelOverlapAvoidanceSolver snapshot", () => {
+ const solver = new TraceLabelOverlapAvoidanceSolver({
+ inputProblem: inputProblem as any,
+ netLabelPlacements: testInput.netLabelPlacements as any,
+ traces: testInput.traces as any,
+ })
+
+ solver.solve()
+
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
+})
diff --git a/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg b/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg
index e86c89d..134ae62 100644
--- a/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg
+++ b/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg
@@ -112,7 +112,7 @@ y+" data-x="1.2000000000000002" data-y="2.25" cx="425" cy="114.375" r="3" fill="
-
+
@@ -124,7 +124,7 @@ y+" data-x="1.2000000000000002" data-y="2.25" cx="425" cy="114.375" r="3" fill="
-
+