diff --git a/lib/rectdiff-types.ts b/lib/rectdiff-types.ts index 92beae7..52053e2 100644 --- a/lib/rectdiff-types.ts +++ b/lib/rectdiff-types.ts @@ -8,6 +8,7 @@ export type Rect3d = { maxY: number zLayers: number[] // sorted contiguous integers isObstacle?: boolean + connectedTo?: string[] } export type GridFill3DOptions = { diff --git a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts index 1599438..513ff34 100644 --- a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts +++ b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts @@ -1,5 +1,5 @@ -import type { CapacityMeshNode } from "../../types/capacity-mesh-types" import type { Rect3d } from "../../rectdiff-types" +import type { CapacityMeshNode } from "../../types/capacity-mesh-types" export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { let id = 0 @@ -8,6 +8,7 @@ export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { const w = Math.max(0, r.maxX - r.minX) const h = Math.max(0, r.maxY - r.minY) if (w <= 0 || h <= 0 || r.zLayers.length === 0) continue + out.push({ capacityMeshNodeId: `cmn_${id++}`, center: { x: (r.minX + r.maxX) / 2, y: (r.minY + r.maxY) / 2 }, @@ -17,6 +18,7 @@ export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { availableZ: r.zLayers.slice(), _containsObstacle: r.isObstacle, _containsTarget: r.isObstacle, + ...(r.connectedTo?.length ? { _connectedTo: [...r.connectedTo] } : {}), }) } diff --git a/lib/types/capacity-mesh-types.ts b/lib/types/capacity-mesh-types.ts index dc4bdd4..ff38e9c 100644 --- a/lib/types/capacity-mesh-types.ts +++ b/lib/types/capacity-mesh-types.ts @@ -20,6 +20,8 @@ export interface CapacityMeshNode { _completelyInsideObstacle?: boolean _containsObstacle?: boolean _containsTarget?: boolean + /** Connection names copied from the source obstacle. */ + _connectedTo?: string[] _targetConnectionName?: string _strawNode?: boolean _strawParentCapacityMeshNodeId?: CapacityMeshNodeId diff --git a/lib/utils/finalizeRects.ts b/lib/utils/finalizeRects.ts index da3dd55..d87889b 100644 --- a/lib/utils/finalizeRects.ts +++ b/lib/utils/finalizeRects.ts @@ -21,8 +21,9 @@ export function finalizeRects(params: { zLayers: [...p.zLayers].sort((a, b) => a - b), })) - const layersByKey = new Map }>() - + // NOTE: Obstacle nodes are emitted one-for-one from SRJ. Do not group them + // only by geometry: distinct obstacles can share XY bounds while belonging + // to different layers or nets. for (const obstacle of params.obstacles ?? []) { const baseRect = obstacleToXYRect(obstacle) if (!baseRect) continue @@ -38,23 +39,15 @@ export function finalizeRects(params: { obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, params.zIndexByName) - const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}` - let entry = layersByKey.get(key) - if (!entry) { - entry = { rect, layers: new Set() } - layersByKey.set(key, entry) - } - zLayers.forEach((layer: number) => entry!.layers.add(layer)) - } - for (const { rect, layers } of layersByKey.values()) { out.push({ minX: rect.x, minY: rect.y, maxX: rect.x + rect.width, maxY: rect.y + rect.height, - zLayers: Array.from(layers).sort((a, b) => a - b), + zLayers: [...new Set(zLayers)].sort((a, b) => a - b), isObstacle: true, + connectedTo: [...obstacle.connectedTo], }) } diff --git a/tests/obstacle-connectivity.test.ts b/tests/obstacle-connectivity.test.ts new file mode 100644 index 0000000..b953e4c --- /dev/null +++ b/tests/obstacle-connectivity.test.ts @@ -0,0 +1,76 @@ +import { expect, test } from "bun:test" +import { RectDiffPipeline } from "../lib/RectDiffPipeline" +import type { CapacityMeshNode } from "../lib/types/capacity-mesh-types" +import type { SimpleRouteJson } from "../lib/types/srj-types" + +const getObstacleNodesAt = ( + nodes: CapacityMeshNode[], + x: number, + y: number, +): CapacityMeshNode[] => + nodes.filter( + (node) => + node._containsObstacle && node.center.x === x && node.center.y === y, + ) + +test("creates one obstacle node per SRJ obstacle with its connectivity", () => { + const srj: SimpleRouteJson = { + bounds: { minX: -5, maxX: 5, minY: -5, maxY: 5 }, + connections: [], + minTraceWidth: 0.2, + layerCount: 2, + obstacles: [ + { + type: "rect", + center: { x: 0, y: 0 }, + width: 1, + height: 1, + layers: ["top"], + connectedTo: ["net-top"], + }, + { + type: "rect", + center: { x: 0, y: 0 }, + width: 1, + height: 1, + layers: ["bottom"], + connectedTo: ["net-bottom"], + }, + { + type: "rect", + center: { x: 2, y: 0 }, + width: 1, + height: 1, + layers: ["top", "bottom"], + connectedTo: ["pad-shared", "net-shared"], + }, + ], + } + const pipeline = new RectDiffPipeline({ + simpleRouteJson: srj, + maxGapFillPasses: 1, + }) + + pipeline.solve() + + const meshNodes = pipeline.getOutput().meshNodes + const layerSpecificNodes = getObstacleNodesAt(meshNodes, 0, 0) + expect(layerSpecificNodes).toHaveLength(2) + expect(layerSpecificNodes).toContainEqual( + expect.objectContaining({ + availableZ: [0], + _connectedTo: ["net-top"], + }), + ) + expect(layerSpecificNodes).toContainEqual( + expect.objectContaining({ + availableZ: [1], + _connectedTo: ["net-bottom"], + }), + ) + + const sharedNode = getObstacleNodesAt(meshNodes, 2, 0) + expect(sharedNode).toHaveLength(1) + expect(sharedNode[0]?.availableZ).toEqual([0, 1]) + expect(sharedNode[0]?._connectedTo).toEqual(["pad-shared", "net-shared"]) +}) diff --git a/tests/solver/bugreport37-srj18-sample002/__snapshots__/bugreport37-srj18-sample002.snap.svg b/tests/solver/bugreport37-srj18-sample002/__snapshots__/bugreport37-srj18-sample002.snap.svg index eb7dda6..4bb6538 100644 --- a/tests/solver/bugreport37-srj18-sample002/__snapshots__/bugreport37-srj18-sample002.snap.svg +++ b/tests/solver/bugreport37-srj18-sample002/__snapshots__/bugreport37-srj18-sample002.snap.svg @@ -1532,292 +1532,296 @@ z:0" data-x="-43.284400000000005" data-y="11.1928" x="82.92216216216212" y="255. z:0" data-x="-43.284400000000005" data-y="10.1928" x="82.92216216216212" y="261.3902702702703" width="6.216216216216253" height="1.621621621621614" fill="rgba(100, 100, 100, 0.1)" stroke="rgba(0, 0, 0, 0.3)" stroke-width="0.185"/>