diff --git a/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts b/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts index e0a3daf36..6c572bd4a 100644 --- a/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +++ b/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts @@ -1,18 +1,18 @@ import type { GraphicsObject } from "graphics-debug" +import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex" import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver" -import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver" -import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver" -import type { InputProblem } from "lib/types/InputProblem" -import type { FacingDirection } from "lib/utils/dir" +import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver" +import { rectIntersectsAnyTrace } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions" import { - getDimsForOrientation, getCenterFromAnchor, + getDimsForOrientation, getRectBounds, } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry" -import { rectIntersectsAnyTrace } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions" -import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex" +import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem" +import type { InputProblem } from "lib/types/InputProblem" +import type { FacingDirection } from "lib/utils/dir" import { getColorFromString } from "lib/utils/getColorFromString" import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds" @@ -147,8 +147,24 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver { return getRectBounds(label.center, label.width, label.height) } + /** + * Identifies a specific *pair of labels*, not merely the pair of nets they + * belong to. + * + * Keying on `globalConnNetId` alone collapses every label pair drawn from + * the same two nets into one key — on the larger boards a single net pair + * can account for over a hundred label pairs. Giving up on one of them then + * suppressed all the others, so overlaps that were never examined looked + * like overlaps the search had rejected. + * + * `pinIds` identifies the individual label, so this stays stable across + * relocations of the same label while still separating distinct pairs. + */ private collisionKey(a: NetLabelPlacement, b: NetLabelPlacement) { - return [a.globalConnNetId, b.globalConnNetId].sort().join("::") + const labelKey = (label: NetLabelPlacement) => + `${label.globalConnNetId}#${[...label.pinIds].sort().join(",")}` + + return [labelKey(a), labelKey(b)].sort().join("::") } private findNextCollidingPair(): diff --git a/tests/solvers/NetLabelNetLabelCollisionSolver/collision-key-identifies-label-pair.test.ts b/tests/solvers/NetLabelNetLabelCollisionSolver/collision-key-identifies-label-pair.test.ts new file mode 100644 index 000000000..b6bb04c16 --- /dev/null +++ b/tests/solvers/NetLabelNetLabelCollisionSolver/collision-key-identifies-label-pair.test.ts @@ -0,0 +1,47 @@ +import { expect, test } from "bun:test" +import { NetLabelNetLabelCollisionSolver } from "lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver" +import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver" + +const label = (globalConnNetId: string, pinIds: string[]) => + ({ + globalConnNetId, + pinIds, + mspConnectionPairIds: [], + orientation: "x+", + center: { x: 0, y: 0 }, + width: 1, + height: 1, + }) as unknown as NetLabelPlacement + +const collisionKey = (a: NetLabelPlacement, b: NetLabelPlacement) => + ( + NetLabelNetLabelCollisionSolver.prototype as unknown as { + collisionKey: (a: NetLabelPlacement, b: NetLabelPlacement) => string + } + ).collisionKey(a, b) + +test("distinct label pairs on the same nets get distinct collision keys", () => { + // Two separate pairs of labels, both drawn from netA and netB. Keying on the + // net ids alone would collapse them into one key, so abandoning the search + // for the first pair would silently suppress the second — on the larger + // boards a single net pair covers over a hundred label pairs. + const first = collisionKey(label("netA", ["p1"]), label("netB", ["p2"])) + const second = collisionKey(label("netA", ["p3"]), label("netB", ["p4"])) + + expect(first).not.toBe(second) +}) + +test("collision key is stable regardless of argument order", () => { + const a = label("netA", ["p1"]) + const b = label("netB", ["p2"]) + + expect(collisionKey(a, b)).toBe(collisionKey(b, a)) +}) + +test("collision key is stable regardless of pin id order", () => { + const a = label("netA", ["p1", "p2"]) + const b = label("netA", ["p2", "p1"]) + const other = label("netB", ["p3"]) + + expect(collisionKey(a, other)).toBe(collisionKey(b, other)) +})