From 7cfa2d6c8d17414c7002a6bf20d291648879316d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:29:28 +0530 Subject: [PATCH 01/14] Add obstacle connectivity to mesh nodes --- lib/types/capacity-mesh-types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/types/capacity-mesh-types.ts b/lib/types/capacity-mesh-types.ts index dc4bdd4..3052470 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 + /** Electrical connection names carried by an obstacle-backed node. */ + _connectedTo?: string[] _targetConnectionName?: string _strawNode?: boolean _strawParentCapacityMeshNodeId?: CapacityMeshNodeId From 709b67d9dfda4b8ff191d24368572f195c51e53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:29:37 +0530 Subject: [PATCH 02/14] Carry connectivity through finalized rectangles --- lib/rectdiff-types.ts | 1 + 1 file changed, 1 insertion(+) 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 = { From 18a2fa41ecc3c2061b97d85ff253610053bdbc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:29:50 +0530 Subject: [PATCH 03/14] Preserve obstacle connectivity while finalizing rectangles --- lib/utils/finalizeRects.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/utils/finalizeRects.ts b/lib/utils/finalizeRects.ts index da3dd55..8b1983d 100644 --- a/lib/utils/finalizeRects.ts +++ b/lib/utils/finalizeRects.ts @@ -21,7 +21,10 @@ export function finalizeRects(params: { zLayers: [...p.zLayers].sort((a, b) => a - b), })) - const layersByKey = new Map }>() + const obstacleRectsByKey = new Map< + string, + { rect: XYRect; layers: Set; connectedTo: string[] } + >() for (const obstacle of params.obstacles ?? []) { const baseRect = obstacleToXYRect(obstacle) @@ -38,16 +41,23 @@ 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) + const connectedTo = [...new Set(obstacle.connectedTo)].sort() + const key = JSON.stringify([ + rect.x, + rect.y, + rect.width, + rect.height, + connectedTo, + ]) + let entry = obstacleRectsByKey.get(key) if (!entry) { - entry = { rect, layers: new Set() } - layersByKey.set(key, entry) + entry = { rect, layers: new Set(), connectedTo } + obstacleRectsByKey.set(key, entry) } - zLayers.forEach((layer: number) => entry!.layers.add(layer)) + zLayers.forEach((layer: number) => entry.layers.add(layer)) } - for (const { rect, layers } of layersByKey.values()) { + for (const { rect, layers, connectedTo } of obstacleRectsByKey.values()) { out.push({ minX: rect.x, minY: rect.y, @@ -55,6 +65,7 @@ export function finalizeRects(params: { maxY: rect.y + rect.height, zLayers: Array.from(layers).sort((a, b) => a - b), isObstacle: true, + connectedTo, }) } From c7740bf288cc3b77e88f44ea22300aed7b20da7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:29:58 +0530 Subject: [PATCH 04/14] Emit obstacle connectivity on mesh nodes --- lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts index 1599438..80da76e 100644 --- a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts +++ b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts @@ -17,6 +17,9 @@ export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { availableZ: r.zLayers.slice(), _containsObstacle: r.isObstacle, _containsTarget: r.isObstacle, + ...(r.connectedTo?.length + ? { _connectedTo: [...r.connectedTo] } + : {}), }) } From 386fb38a1ef16adda3e2c787710b5f84b166fa5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:30:27 +0530 Subject: [PATCH 05/14] Test obstacle connectivity preservation --- tests/obstacle-connectivity.test.ts | 73 +++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/obstacle-connectivity.test.ts diff --git a/tests/obstacle-connectivity.test.ts b/tests/obstacle-connectivity.test.ts new file mode 100644 index 0000000..98dce71 --- /dev/null +++ b/tests/obstacle-connectivity.test.ts @@ -0,0 +1,73 @@ +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("preserves obstacle connectivity without merging different nets across layers", () => { + 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", "net-shared"], + }, + ], + } + const pipeline = new RectDiffPipeline({ + simpleRouteJson: srj, + maxGapFillPasses: 1, + }) + + pipeline.solve() + + const meshNodes = pipeline.getOutput().meshNodes + const layerSpecificNodes = getObstacleNodesAt(meshNodes, 0, 0) + .map((node) => ({ + availableZ: node.availableZ, + connectedTo: node._connectedTo, + })) + .sort((a, b) => a.availableZ[0]! - b.availableZ[0]!) + + expect(layerSpecificNodes).toEqual([ + { availableZ: [0], connectedTo: ["net-top"] }, + { 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(["net-shared", "pad-shared"]) +}) From 8826efe6cbe291ffca0db284d73278e84e07eeba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:31:53 +0530 Subject: [PATCH 06/14] Format obstacle connectivity output --- lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts index 80da76e..883aeec 100644 --- a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts +++ b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts @@ -17,9 +17,7 @@ export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { availableZ: r.zLayers.slice(), _containsObstacle: r.isObstacle, _containsTarget: r.isObstacle, - ...(r.connectedTo?.length - ? { _connectedTo: [...r.connectedTo] } - : {}), + ...(r.connectedTo?.length ? { _connectedTo: [...r.connectedTo] } : {}), }) } From 953a79630f995a04be48dd14c7dd7b72b35987b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:33:38 +0530 Subject: [PATCH 07/14] Represent layer-aware obstacle connectivity --- lib/types/capacity-mesh-types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/types/capacity-mesh-types.ts b/lib/types/capacity-mesh-types.ts index 3052470..80a2064 100644 --- a/lib/types/capacity-mesh-types.ts +++ b/lib/types/capacity-mesh-types.ts @@ -20,8 +20,10 @@ export interface CapacityMeshNode { _completelyInsideObstacle?: boolean _containsObstacle?: boolean _containsTarget?: boolean - /** Electrical connection names carried by an obstacle-backed node. */ + /** Connection names shared by every available layer of an obstacle node. */ _connectedTo?: string[] + /** Exact obstacle connection names for each available z-layer. */ + _connectedToByZ?: Record _targetConnectionName?: string _strawNode?: boolean _strawParentCapacityMeshNodeId?: CapacityMeshNodeId From 26b0720ff5910d8ccd91c382fd74414a8477142c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:33:44 +0530 Subject: [PATCH 08/14] Track rectangle connectivity by layer --- lib/rectdiff-types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rectdiff-types.ts b/lib/rectdiff-types.ts index 52053e2..e0a91e3 100644 --- a/lib/rectdiff-types.ts +++ b/lib/rectdiff-types.ts @@ -9,6 +9,7 @@ export type Rect3d = { zLayers: number[] // sorted contiguous integers isObstacle?: boolean connectedTo?: string[] + connectedToByZ?: Record } export type GridFill3DOptions = { From be1fe4c5ea2a30a694d8ba12da1c213a8192e135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:34:10 +0530 Subject: [PATCH 09/14] Preserve obstacle connectivity without changing geometry --- lib/utils/finalizeRects.ts | 79 ++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/lib/utils/finalizeRects.ts b/lib/utils/finalizeRects.ts index 8b1983d..368a5b8 100644 --- a/lib/utils/finalizeRects.ts +++ b/lib/utils/finalizeRects.ts @@ -5,6 +5,37 @@ import { obstacleZs, } from "../solvers/RectDiffSeedingSolver/layers" +type ObstacleRectGroup = { + rect: XYRect + layers: Set + connectedToByZ: Map> +} + +const getSortedConnectionNames = ( + connectedTo: Iterable, +): string[] => [...new Set(connectedTo)].sort() + +const getSharedConnectionNames = ( + zLayers: number[], + connectedToByZ: Map>, +): string[] => { + const firstZ = zLayers[0] + if (firstZ === undefined) return [] + + const sharedNames = getSortedConnectionNames( + connectedToByZ.get(firstZ) ?? [], + ) + const allLayersMatch = zLayers.slice(1).every((z) => { + const layerNames = getSortedConnectionNames(connectedToByZ.get(z) ?? []) + return ( + layerNames.length === sharedNames.length && + layerNames.every((name, index) => name === sharedNames[index]) + ) + }) + + return allLayersMatch ? sharedNames : [] +} + export function finalizeRects(params: { placed: Placed3D[] obstacles: Obstacle[] @@ -21,10 +52,7 @@ export function finalizeRects(params: { zLayers: [...p.zLayers].sort((a, b) => a - b), })) - const obstacleRectsByKey = new Map< - string, - { rect: XYRect; layers: Set; connectedTo: string[] } - >() + const obstacleRectsByKey = new Map() for (const obstacle of params.obstacles ?? []) { const baseRect = obstacleToXYRect(obstacle) @@ -41,31 +69,48 @@ export function finalizeRects(params: { obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, params.zIndexByName) - const connectedTo = [...new Set(obstacle.connectedTo)].sort() - const key = JSON.stringify([ - rect.x, - rect.y, - rect.width, - rect.height, - connectedTo, - ]) + const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}` let entry = obstacleRectsByKey.get(key) if (!entry) { - entry = { rect, layers: new Set(), connectedTo } + entry = { + rect, + layers: new Set(), + connectedToByZ: new Map(), + } obstacleRectsByKey.set(key, entry) } - zLayers.forEach((layer: number) => entry.layers.add(layer)) + + for (const z of zLayers) { + entry.layers.add(z) + let connectionNames = entry.connectedToByZ.get(z) + if (!connectionNames) { + connectionNames = new Set() + entry.connectedToByZ.set(z, connectionNames) + } + for (const connectionName of obstacle.connectedTo) { + connectionNames.add(connectionName) + } + } } - for (const { rect, layers, connectedTo } of obstacleRectsByKey.values()) { + for (const { rect, layers, connectedToByZ } of obstacleRectsByKey.values()) { + const zLayers = Array.from(layers).sort((a, b) => a - b) + const connectedToByZRecord: Record = {} + for (const z of zLayers) { + connectedToByZRecord[z] = getSortedConnectionNames( + connectedToByZ.get(z) ?? [], + ) + } + 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, isObstacle: true, - connectedTo, + connectedTo: getSharedConnectionNames(zLayers, connectedToByZ), + connectedToByZ: connectedToByZRecord, }) } From bc30a507b1a8b592c0d39f904f8ea3284da10d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:34:30 +0530 Subject: [PATCH 10/14] Emit layer-aware obstacle connectivity --- .../RectDiffExpansionSolver/rectsToMeshNodes.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts index 883aeec..eaf82d9 100644 --- a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts +++ b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts @@ -1,5 +1,17 @@ -import type { CapacityMeshNode } from "../../types/capacity-mesh-types" import type { Rect3d } from "../../rectdiff-types" +import type { CapacityMeshNode } from "../../types/capacity-mesh-types" + +const cloneConnectedToByZ = ( + connectedToByZ: Record | undefined, +): Record | undefined => { + if (!connectedToByZ) return undefined + + const clone: Record = {} + for (const [z, connectionNames] of Object.entries(connectedToByZ)) { + clone[Number(z)] = [...connectionNames] + } + return clone +} export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { let id = 0 @@ -8,6 +20,8 @@ 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 + + const connectedToByZ = cloneConnectedToByZ(r.connectedToByZ) out.push({ capacityMeshNodeId: `cmn_${id++}`, center: { x: (r.minX + r.maxX) / 2, y: (r.minY + r.maxY) / 2 }, @@ -18,6 +32,7 @@ export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { _containsObstacle: r.isObstacle, _containsTarget: r.isObstacle, ...(r.connectedTo?.length ? { _connectedTo: [...r.connectedTo] } : {}), + ...(connectedToByZ ? { _connectedToByZ: connectedToByZ } : {}), }) } From 7e356ed15bf86a03a7619509c10b81ba150e9525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:34:51 +0530 Subject: [PATCH 11/14] Test layer-aware obstacle connectivity --- tests/obstacle-connectivity.test.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/obstacle-connectivity.test.ts b/tests/obstacle-connectivity.test.ts index 98dce71..b5b7b47 100644 --- a/tests/obstacle-connectivity.test.ts +++ b/tests/obstacle-connectivity.test.ts @@ -13,7 +13,7 @@ const getObstacleNodesAt = ( node._containsObstacle && node.center.x === x && node.center.y === y, ) -test("preserves obstacle connectivity without merging different nets across layers", () => { +test("preserves obstacle connectivity without conflating nets across layers", () => { const srj: SimpleRouteJson = { bounds: { minX: -5, maxX: 5, minY: -5, maxY: 5 }, connections: [], @@ -54,20 +54,21 @@ test("preserves obstacle connectivity without merging different nets across laye pipeline.solve() const meshNodes = pipeline.getOutput().meshNodes - const layerSpecificNodes = getObstacleNodesAt(meshNodes, 0, 0) - .map((node) => ({ - availableZ: node.availableZ, - connectedTo: node._connectedTo, - })) - .sort((a, b) => a.availableZ[0]! - b.availableZ[0]!) - - expect(layerSpecificNodes).toEqual([ - { availableZ: [0], connectedTo: ["net-top"] }, - { availableZ: [1], connectedTo: ["net-bottom"] }, - ]) + const layerSpecificNode = getObstacleNodesAt(meshNodes, 0, 0) + expect(layerSpecificNode).toHaveLength(1) + expect(layerSpecificNode[0]?.availableZ).toEqual([0, 1]) + expect(layerSpecificNode[0]?._connectedTo).toBeUndefined() + expect(layerSpecificNode[0]?._connectedToByZ).toEqual({ + 0: ["net-top"], + 1: ["net-bottom"], + }) const sharedNode = getObstacleNodesAt(meshNodes, 2, 0) expect(sharedNode).toHaveLength(1) expect(sharedNode[0]?.availableZ).toEqual([0, 1]) expect(sharedNode[0]?._connectedTo).toEqual(["net-shared", "pad-shared"]) + expect(sharedNode[0]?._connectedToByZ).toEqual({ + 0: ["net-shared", "pad-shared"], + 1: ["net-shared", "pad-shared"], + }) }) From bbee793ab1d633d1f85f97f19c5a27747f3225c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:35:54 +0530 Subject: [PATCH 12/14] Format layer-aware connectivity --- lib/utils/finalizeRects.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/utils/finalizeRects.ts b/lib/utils/finalizeRects.ts index 368a5b8..d0c6184 100644 --- a/lib/utils/finalizeRects.ts +++ b/lib/utils/finalizeRects.ts @@ -11,9 +11,8 @@ type ObstacleRectGroup = { connectedToByZ: Map> } -const getSortedConnectionNames = ( - connectedTo: Iterable, -): string[] => [...new Set(connectedTo)].sort() +const getSortedConnectionNames = (connectedTo: Iterable): string[] => + [...new Set(connectedTo)].sort() const getSharedConnectionNames = ( zLayers: number[], @@ -22,9 +21,7 @@ const getSharedConnectionNames = ( const firstZ = zLayers[0] if (firstZ === undefined) return [] - const sharedNames = getSortedConnectionNames( - connectedToByZ.get(firstZ) ?? [], - ) + const sharedNames = getSortedConnectionNames(connectedToByZ.get(firstZ) ?? []) const allLayersMatch = zLayers.slice(1).every((z) => { const layerNames = getSortedConnectionNames(connectedToByZ.get(z) ?? []) return ( From ba0f5e0356fa95572c44b2c5539b1a159bd6c7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Mon, 20 Jul 2026 08:52:40 +0530 Subject: [PATCH 13/14] Emit one mesh node per obstacle --- lib/rectdiff-types.ts | 1 - .../rectsToMeshNodes.ts | 14 ---- lib/types/capacity-mesh-types.ts | 4 +- lib/utils/finalizeRects.ts | 70 ++----------------- tests/obstacle-connectivity.test.ts | 32 +++++---- 5 files changed, 23 insertions(+), 98 deletions(-) diff --git a/lib/rectdiff-types.ts b/lib/rectdiff-types.ts index e0a91e3..52053e2 100644 --- a/lib/rectdiff-types.ts +++ b/lib/rectdiff-types.ts @@ -9,7 +9,6 @@ export type Rect3d = { zLayers: number[] // sorted contiguous integers isObstacle?: boolean connectedTo?: string[] - connectedToByZ?: Record } export type GridFill3DOptions = { diff --git a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts index eaf82d9..513ff34 100644 --- a/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts +++ b/lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts @@ -1,18 +1,6 @@ import type { Rect3d } from "../../rectdiff-types" import type { CapacityMeshNode } from "../../types/capacity-mesh-types" -const cloneConnectedToByZ = ( - connectedToByZ: Record | undefined, -): Record | undefined => { - if (!connectedToByZ) return undefined - - const clone: Record = {} - for (const [z, connectionNames] of Object.entries(connectedToByZ)) { - clone[Number(z)] = [...connectionNames] - } - return clone -} - export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { let id = 0 const out: CapacityMeshNode[] = [] @@ -21,7 +9,6 @@ export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { const h = Math.max(0, r.maxY - r.minY) if (w <= 0 || h <= 0 || r.zLayers.length === 0) continue - const connectedToByZ = cloneConnectedToByZ(r.connectedToByZ) out.push({ capacityMeshNodeId: `cmn_${id++}`, center: { x: (r.minX + r.maxX) / 2, y: (r.minY + r.maxY) / 2 }, @@ -32,7 +19,6 @@ export function rectsToMeshNodes(rects: Rect3d[]): CapacityMeshNode[] { _containsObstacle: r.isObstacle, _containsTarget: r.isObstacle, ...(r.connectedTo?.length ? { _connectedTo: [...r.connectedTo] } : {}), - ...(connectedToByZ ? { _connectedToByZ: connectedToByZ } : {}), }) } diff --git a/lib/types/capacity-mesh-types.ts b/lib/types/capacity-mesh-types.ts index 80a2064..ff38e9c 100644 --- a/lib/types/capacity-mesh-types.ts +++ b/lib/types/capacity-mesh-types.ts @@ -20,10 +20,8 @@ export interface CapacityMeshNode { _completelyInsideObstacle?: boolean _containsObstacle?: boolean _containsTarget?: boolean - /** Connection names shared by every available layer of an obstacle node. */ + /** Connection names copied from the source obstacle. */ _connectedTo?: string[] - /** Exact obstacle connection names for each available z-layer. */ - _connectedToByZ?: Record _targetConnectionName?: string _strawNode?: boolean _strawParentCapacityMeshNodeId?: CapacityMeshNodeId diff --git a/lib/utils/finalizeRects.ts b/lib/utils/finalizeRects.ts index d0c6184..d87889b 100644 --- a/lib/utils/finalizeRects.ts +++ b/lib/utils/finalizeRects.ts @@ -5,34 +5,6 @@ import { obstacleZs, } from "../solvers/RectDiffSeedingSolver/layers" -type ObstacleRectGroup = { - rect: XYRect - layers: Set - connectedToByZ: Map> -} - -const getSortedConnectionNames = (connectedTo: Iterable): string[] => - [...new Set(connectedTo)].sort() - -const getSharedConnectionNames = ( - zLayers: number[], - connectedToByZ: Map>, -): string[] => { - const firstZ = zLayers[0] - if (firstZ === undefined) return [] - - const sharedNames = getSortedConnectionNames(connectedToByZ.get(firstZ) ?? []) - const allLayersMatch = zLayers.slice(1).every((z) => { - const layerNames = getSortedConnectionNames(connectedToByZ.get(z) ?? []) - return ( - layerNames.length === sharedNames.length && - layerNames.every((name, index) => name === sharedNames[index]) - ) - }) - - return allLayersMatch ? sharedNames : [] -} - export function finalizeRects(params: { placed: Placed3D[] obstacles: Obstacle[] @@ -49,8 +21,9 @@ export function finalizeRects(params: { zLayers: [...p.zLayers].sort((a, b) => a - b), })) - const obstacleRectsByKey = 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 @@ -66,48 +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 = obstacleRectsByKey.get(key) - if (!entry) { - entry = { - rect, - layers: new Set(), - connectedToByZ: new Map(), - } - obstacleRectsByKey.set(key, entry) - } - - for (const z of zLayers) { - entry.layers.add(z) - let connectionNames = entry.connectedToByZ.get(z) - if (!connectionNames) { - connectionNames = new Set() - entry.connectedToByZ.set(z, connectionNames) - } - for (const connectionName of obstacle.connectedTo) { - connectionNames.add(connectionName) - } - } - } - - for (const { rect, layers, connectedToByZ } of obstacleRectsByKey.values()) { - const zLayers = Array.from(layers).sort((a, b) => a - b) - const connectedToByZRecord: Record = {} - for (const z of zLayers) { - connectedToByZRecord[z] = getSortedConnectionNames( - connectedToByZ.get(z) ?? [], - ) - } out.push({ minX: rect.x, minY: rect.y, maxX: rect.x + rect.width, maxY: rect.y + rect.height, - zLayers, + zLayers: [...new Set(zLayers)].sort((a, b) => a - b), isObstacle: true, - connectedTo: getSharedConnectionNames(zLayers, connectedToByZ), - connectedToByZ: connectedToByZRecord, + connectedTo: [...obstacle.connectedTo], }) } diff --git a/tests/obstacle-connectivity.test.ts b/tests/obstacle-connectivity.test.ts index b5b7b47..b953e4c 100644 --- a/tests/obstacle-connectivity.test.ts +++ b/tests/obstacle-connectivity.test.ts @@ -13,7 +13,7 @@ const getObstacleNodesAt = ( node._containsObstacle && node.center.x === x && node.center.y === y, ) -test("preserves obstacle connectivity without conflating nets across layers", () => { +test("creates one obstacle node per SRJ obstacle with its connectivity", () => { const srj: SimpleRouteJson = { bounds: { minX: -5, maxX: 5, minY: -5, maxY: 5 }, connections: [], @@ -42,7 +42,7 @@ test("preserves obstacle connectivity without conflating nets across layers", () width: 1, height: 1, layers: ["top", "bottom"], - connectedTo: ["pad-shared", "net-shared", "net-shared"], + connectedTo: ["pad-shared", "net-shared"], }, ], } @@ -54,21 +54,23 @@ test("preserves obstacle connectivity without conflating nets across layers", () pipeline.solve() const meshNodes = pipeline.getOutput().meshNodes - const layerSpecificNode = getObstacleNodesAt(meshNodes, 0, 0) - expect(layerSpecificNode).toHaveLength(1) - expect(layerSpecificNode[0]?.availableZ).toEqual([0, 1]) - expect(layerSpecificNode[0]?._connectedTo).toBeUndefined() - expect(layerSpecificNode[0]?._connectedToByZ).toEqual({ - 0: ["net-top"], - 1: ["net-bottom"], - }) + 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(["net-shared", "pad-shared"]) - expect(sharedNode[0]?._connectedToByZ).toEqual({ - 0: ["net-shared", "pad-shared"], - 1: ["net-shared", "pad-shared"], - }) + expect(sharedNode[0]?._connectedTo).toEqual(["pad-shared", "net-shared"]) }) From f07dbaf02f8f75d260b089b40570f12830bd83de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Mon, 20 Jul 2026 08:56:08 +0530 Subject: [PATCH 14/14] Update obstacle mesh snapshot --- .../bugreport37-srj18-sample002.snap.svg | 3650 +++++++++-------- 1 file changed, 1837 insertions(+), 1813 deletions(-) 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"/>