Skip to content

Commit 955302d

Browse files
committed
feat: add findCloseSameNetTraceGroups for close same-net trace detection (#378)
- Add findCloseSameNetTraceGroups sub-solver that groups same-net traces by endpoint proximity (default threshold: 0.5mm) - Wire into TraceCleanupSolver init and output - Add focused test coverage for same-net grouping - All 62 tests pass, typecheck clean, build clean, format clean Addresses #378
1 parent e19191b commit 955302d

3 files changed

Lines changed: 121 additions & 1 deletion

File tree

lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ interface TraceCleanupSolverInput {
2020

2121
import { UntangleTraceSubsolver } from "./sub-solver/UntangleTraceSubsolver"
2222
import { is4PointRectangle } from "./is4PointRectangle"
23+
import {
24+
findCloseSameNetTraceGroups,
25+
type CloseSameNetTraceGroup,
26+
} from "./sub-solver/findCloseSameNetTraceGroups"
2327

2428
/**
2529
* Represents the different stages or steps within the trace cleanup pipeline.
@@ -42,6 +46,7 @@ export class TraceCleanupSolver extends BaseSolver {
4246
private outputTraces: SolvedTracePath[]
4347
private traceIdQueue: string[]
4448
private tracesMap: Map<string, SolvedTracePath>
49+
private closeSameNetTraceGroups: CloseSameNetTraceGroup[] = []
4550
private pipelineStep: PipelineStep = "untangling_traces"
4651
private activeTraceId: string | null = null // New property
4752
override activeSubSolver: BaseSolver | null = null
@@ -54,6 +59,9 @@ export class TraceCleanupSolver extends BaseSolver {
5459
this.traceIdQueue = Array.from(
5560
solverInput.allTraces.map((e) => e.mspPairId),
5661
)
62+
this.closeSameNetTraceGroups = findCloseSameNetTraceGroups(
63+
solverInput.allTraces,
64+
)
5765
}
5866

5967
override _step() {
@@ -149,6 +157,7 @@ export class TraceCleanupSolver extends BaseSolver {
149157
getOutput() {
150158
return {
151159
traces: this.outputTraces,
160+
closeSameNetTraceGroups: this.closeSameNetTraceGroups,
152161
}
153162
}
154163

@@ -171,10 +180,11 @@ export class TraceCleanupSolver extends BaseSolver {
171180
for (const trace of this.outputTraces) {
172181
const line: Line = {
173182
points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
174-
strokeColor: trace.mspPairId === this.activeTraceId ? "red" : "blue", // Highlight active trace
183+
strokeColor: trace.mspPairId === this.activeTraceId ? "red" : "blue",
175184
}
176185
graphics.lines!.push(line)
177186
}
187+
178188
return graphics
179189
}
180190
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type { SolvedTracePath } from "../../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
2+
3+
export interface CloseSameNetTraceGroup {
4+
netId: string
5+
traceIds: string[]
6+
maxEndpointDistance: number
7+
}
8+
9+
const distance = (a: { x: number; y: number }, b: { x: number; y: number }) =>
10+
Math.hypot(a.x - b.x, a.y - b.y)
11+
12+
const getTraceEndpoints = (trace: SolvedTracePath) => {
13+
const start = trace.tracePath[0]
14+
const end = trace.tracePath[trace.tracePath.length - 1]
15+
return { start, end }
16+
}
17+
18+
/**
19+
* Finds same-net traces whose endpoints are already close enough that they are
20+
* likely candidates for a later merge/join phase.
21+
*
22+
* This is intentionally conservative: it does not mutate paths, it only groups
23+
* traces so the pipeline can decide whether to combine them.
24+
*/
25+
export const findCloseSameNetTraceGroups = (
26+
traces: SolvedTracePath[],
27+
maxEndpointDistance = 0.5,
28+
): CloseSameNetTraceGroup[] => {
29+
const groupedByNet = new Map<string, SolvedTracePath[]>()
30+
31+
for (const trace of traces) {
32+
const netId = trace.userNetId ?? trace.globalConnNetId ?? trace.dcConnNetId
33+
const current = groupedByNet.get(netId) ?? []
34+
current.push(trace)
35+
groupedByNet.set(netId, current)
36+
}
37+
38+
const groups: CloseSameNetTraceGroup[] = []
39+
40+
for (const [netId, netTraces] of groupedByNet.entries()) {
41+
if (netTraces.length < 2) continue
42+
43+
for (let i = 0; i < netTraces.length; i++) {
44+
for (let j = i + 1; j < netTraces.length; j++) {
45+
const left = netTraces[i]
46+
const right = netTraces[j]
47+
const a = getTraceEndpoints(left)
48+
const b = getTraceEndpoints(right)
49+
50+
const distances = [
51+
distance(a.start, b.start),
52+
distance(a.start, b.end),
53+
distance(a.end, b.start),
54+
distance(a.end, b.end),
55+
]
56+
const minDistance = Math.min(...distances)
57+
58+
if (minDistance <= maxEndpointDistance) {
59+
groups.push({
60+
netId,
61+
traceIds: [left.mspPairId as string, right.mspPairId as string],
62+
maxEndpointDistance: minDistance,
63+
})
64+
}
65+
}
66+
}
67+
}
68+
69+
return groups.sort((a, b) => a.maxEndpointDistance - b.maxEndpointDistance)
70+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { expect, test } from "bun:test"
2+
import { findCloseSameNetTraceGroups } from "lib/solvers/TraceCleanupSolver/sub-solver/findCloseSameNetTraceGroups"
3+
4+
const trace = (
5+
id: string,
6+
netId: string,
7+
path: Array<{ x: number; y: number }>,
8+
) =>
9+
({
10+
mspPairId: id,
11+
userNetId: netId,
12+
globalConnNetId: `${netId}-global`,
13+
dcConnNetId: `${netId}-dc`,
14+
tracePath: path,
15+
}) as any
16+
17+
test("findCloseSameNetTraceGroups groups close same-net traces by endpoint distance", () => {
18+
const groups = findCloseSameNetTraceGroups(
19+
[
20+
trace("a", "N1", [
21+
{ x: 0, y: 0 },
22+
{ x: 1, y: 0 },
23+
]),
24+
trace("b", "N1", [
25+
{ x: 1.15, y: 0 },
26+
{ x: 2, y: 0 },
27+
]),
28+
trace("c", "N2", [
29+
{ x: 10, y: 10 },
30+
{ x: 11, y: 10 },
31+
]),
32+
],
33+
0.2,
34+
)
35+
36+
expect(groups).toHaveLength(1)
37+
expect(groups[0].netId).toBe("N1")
38+
expect(groups[0].traceIds).toEqual(["a", "b"])
39+
expect(groups[0].maxEndpointDistance).toBeLessThanOrEqual(0.2)
40+
})

0 commit comments

Comments
 (0)