Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/rectdiff-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type Rect3d = {
maxY: number
zLayers: number[] // sorted contiguous integers
isObstacle?: boolean
connectedTo?: string[]
}

export type GridFill3DOptions = {
Expand Down
4 changes: 3 additions & 1 deletion lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 },
Expand All @@ -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] } : {}),
})
}

Expand Down
2 changes: 2 additions & 0 deletions lib/types/capacity-mesh-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 5 additions & 12 deletions lib/utils/finalizeRects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ export function finalizeRects(params: {
zLayers: [...p.zLayers].sort((a, b) => a - b),
}))

const layersByKey = new Map<string, { rect: XYRect; layers: Set<number> }>()

// 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
Expand All @@ -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],
})
}

Expand Down
76 changes: 76 additions & 0 deletions tests/obstacle-connectivity.test.ts
Original file line number Diff line number Diff line change
@@ -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"])
})
Loading
Loading