diff --git a/lib/RectDiffPipeline.ts b/lib/RectDiffPipeline.ts index 2e9c2c1..52edd89 100644 --- a/lib/RectDiffPipeline.ts +++ b/lib/RectDiffPipeline.ts @@ -3,19 +3,19 @@ import { definePipelineStep, type PipelineStep, } from "@tscircuit/solver-utils" -import type { SimpleRouteJson } from "./types/srj-types" -import type { GridFill3DOptions, XYRect } from "./rectdiff-types" -import type { CapacityMeshNode } from "./types/capacity-mesh-types" import type { GraphicsObject } from "graphics-debug" +import { mergeGraphics } from "graphics-debug" +import { buildFinalRectDiffVisualization } from "./buildFinalRectDiffVisualization" +import type { GridFill3DOptions, XYRect } from "./rectdiff-types" +import { createBaseVisualization } from "./rectdiff-visualization" import { GapFillSolverPipeline } from "./solvers/GapFillSolver/GapFillSolverPipeline" import { OuterLayerContainmentMergeSolver } from "./solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver" import { RectDiffGridSolverPipeline } from "./solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline" -import { createBaseVisualization } from "./rectdiff-visualization" -import { buildFinalRectDiffVisualization } from "./buildFinalRectDiffVisualization" import { computeInverseRects } from "./solvers/RectDiffSeedingSolver/computeInverseRects" import { buildZIndexMap } from "./solvers/RectDiffSeedingSolver/layers" +import type { CapacityMeshNode } from "./types/capacity-mesh-types" +import type { SimpleRouteJson } from "./types/srj-types" import { buildObstacleClearanceGraphics } from "./utils/renderObstacleClearance" -import { mergeGraphics } from "graphics-debug" export interface RectDiffPipelineInput { simpleRouteJson: SimpleRouteJson diff --git a/lib/solvers/MergeToMaximizeLargeMultiLayerNodes/MergeToMaximizeLargeMultiLayerNodesPipelineSolver.ts b/lib/solvers/MergeToMaximizeLargeMultiLayerNodes/MergeToMaximizeLargeMultiLayerNodesPipelineSolver.ts new file mode 100644 index 0000000..c0ad3aa --- /dev/null +++ b/lib/solvers/MergeToMaximizeLargeMultiLayerNodes/MergeToMaximizeLargeMultiLayerNodesPipelineSolver.ts @@ -0,0 +1,60 @@ +import { BaseSolver } from "@tscircuit/solver-utils" +import type { GraphicsObject } from "graphics-debug" +import type { Placed3D } from "../../rectdiff-types" +import type { CapacityMeshNode } from "../../types/capacity-mesh-types" +import type { Obstacle } from "../../types/srj-types" +import { rectsToMeshNodes } from "../RectDiffExpansionSolver/rectsToMeshNodes" +import { MergeToMaximizeLargeMultiLayerNodesSolver } from "./MergeToMaximizeLargeMultiLayerNodesSolver" + +export type MergeToMaximizeLargeMultiLayerNodesPipelineSolverInput = { + placed: Placed3D[] + obstacles: Obstacle[] + layerCount: number + zIndexByName: Map + obstacleClearance?: number +} + +export class MergeToMaximizeLargeMultiLayerNodesPipelineSolver extends BaseSolver { + private meshNodes: CapacityMeshNode[] = [] + + constructor( + private input: MergeToMaximizeLargeMultiLayerNodesPipelineSolverInput, + ) { + super() + } + + override _step() { + const mergeSolver = new MergeToMaximizeLargeMultiLayerNodesSolver( + this.input, + ) + mergeSolver.solve() + const rects = mergeSolver.getOutput().rects + this.meshNodes = rectsToMeshNodes(rects) + this.solved = true + } + + override getOutput(): { meshNodes: CapacityMeshNode[] } { + return { meshNodes: this.meshNodes } + } + + override visualize(): GraphicsObject { + return { + title: "MergeToMaximizeLargeMultiLayerNodes", + coordinateSystem: "cartesian", + rects: this.meshNodes.map((node) => ({ + center: node.center, + width: node.width, + height: node.height, + stroke: "rgba(124, 58, 237, 0.9)", + fill: node._containsObstacle + ? "rgba(239, 68, 68, 0.25)" + : "rgba(196, 181, 253, 0.25)", + layer: `z${node.availableZ.join(",")}`, + label: `z:${node.availableZ.join(",")}`, + })), + points: [], + lines: [], + texts: [], + } + } +} diff --git a/lib/solvers/MergeToMaximizeLargeMultiLayerNodes/MergeToMaximizeLargeMultiLayerNodesSolver.ts b/lib/solvers/MergeToMaximizeLargeMultiLayerNodes/MergeToMaximizeLargeMultiLayerNodesSolver.ts new file mode 100644 index 0000000..f551958 --- /dev/null +++ b/lib/solvers/MergeToMaximizeLargeMultiLayerNodes/MergeToMaximizeLargeMultiLayerNodesSolver.ts @@ -0,0 +1,198 @@ +import { BaseSolver } from "@tscircuit/solver-utils" +import type { Placed3D, Rect3d, XYRect } from "../../rectdiff-types" +import type { Obstacle } from "../../types/srj-types" +import { subtractRect2D } from "../../utils/rectdiff-geometry" +import { obstacleToXYRect, obstacleZs } from "../RectDiffSeedingSolver/layers" + +export type MergeToMaximizeLargeMultiLayerNodesSolverInput = { + placed: Placed3D[] + obstacles: Obstacle[] + layerCount: number + zIndexByName: Map + obstacleClearance?: number +} + +/** + * Merge strategy for multi-layer nodes: + * - First, compute the largest rectangles that are valid on ALL layers of a + * candidate placement (subtracting the union of per-layer obstacles). + * - Then, compute leftover per-layer rectangles after subtracting both the + * per-layer obstacles and the already-claimed common rectangles. + * + * This maximizes large multi-layer rectangles while preserving correctness. + */ +export class MergeToMaximizeLargeMultiLayerNodesSolver extends BaseSolver { + private outputRects: Rect3d[] = [] + + constructor(private input: MergeToMaximizeLargeMultiLayerNodesSolverInput) { + super() + } + + override _setup() { + this.outputRects = [] + } + + override _step() { + this.outputRects = this.compute() + this.solved = true + } + + private compute(): Rect3d[] { + const out: Rect3d[] = [] + const obstacleRectsByLayer = new Map() + const obstaclesByKey = new Map< + string, + { rect: XYRect; layers: Set } + >() + + const keyOfRect = (rect: XYRect) => + `${rect.x}:${rect.y}:${rect.width}:${rect.height}` + + const subtractMany = (parts: XYRect[], cutters: XYRect[]) => { + let remaining = parts + for (const cutter of cutters) { + if (remaining.length === 0) break + const nextParts: XYRect[] = [] + for (const part of remaining) { + nextParts.push(...subtractRect2D(part, cutter)) + } + remaining = nextParts + } + return remaining + } + + // Index obstacles by layer and also create merged obstacle rect entries. + for (const obstacle of this.input.obstacles ?? []) { + const baseRect = obstacleToXYRect(obstacle) + if (!baseRect) continue + + const rect = + this.input.obstacleClearance && this.input.obstacleClearance > 0 + ? { + x: baseRect.x - this.input.obstacleClearance, + y: baseRect.y - this.input.obstacleClearance, + width: baseRect.width + 2 * this.input.obstacleClearance, + height: baseRect.height + 2 * this.input.obstacleClearance, + } + : baseRect + + const zLayers = + obstacle.zLayers?.length && obstacle.zLayers.length > 0 + ? obstacle.zLayers + : obstacleZs(obstacle, this.input.zIndexByName) + + for (const layer of zLayers) { + const list = obstacleRectsByLayer.get(layer) + if (list) list.push(rect) + else obstacleRectsByLayer.set(layer, [rect]) + } + + const key = keyOfRect(rect) + let entry = obstaclesByKey.get(key) + if (!entry) { + entry = { rect, layers: new Set() } + obstaclesByKey.set(key, entry) + } + for (const layer of zLayers) { + entry.layers.add(layer) + } + } + + const freeRectsByKey = new Map< + string, + { rect: XYRect; layers: Set } + >() + + for (const placed of this.input.placed ?? []) { + const layers = Array.from(new Set(placed.zLayers)) + .filter((z) => z >= 0 && z < this.input.layerCount) + .sort((a, b) => a - b) + if (layers.length === 0) continue + + const claimedMultiParts: XYRect[] = [] + + const addFreePart = (rect: XYRect, zLayers: number[]) => { + const key = keyOfRect(rect) + let entry = freeRectsByKey.get(key) + if (!entry) { + entry = { rect, layers: new Set() } + freeRectsByKey.set(key, entry) + } + for (const z of zLayers) entry.layers.add(z) + } + + const unionBlockersForLayers = (zLayers: number[]) => { + const blockersByKey = new Map() + for (const z of zLayers) { + const blockers = obstacleRectsByLayer.get(z) ?? [] + for (const b of blockers) blockersByKey.set(keyOfRect(b), b) + } + return Array.from(blockersByKey.values()) + } + + const maxSpan = Math.min(this.input.layerCount, layers.length) + for (let span = maxSpan; span >= 2; span -= 1) { + for (let start = 0; start + span <= this.input.layerCount; start += 1) { + const spanLayers: number[] = [] + for (let z = start; z < start + span; z += 1) spanLayers.push(z) + if (!spanLayers.every((z) => layers.includes(z))) continue + + const blockers = unionBlockersForLayers(spanLayers) + let parts = subtractMany([placed.rect], blockers) + if (parts.length === 0) continue + if (claimedMultiParts.length > 0) { + parts = subtractMany(parts, claimedMultiParts) + if (parts.length === 0) continue + } + + for (const part of parts) { + addFreePart(part, spanLayers) + claimedMultiParts.push(part) + } + } + } + + for (const z of layers) { + let parts = subtractMany( + [placed.rect], + obstacleRectsByLayer.get(z) ?? [], + ) + if (parts.length === 0) continue + if (claimedMultiParts.length > 0) { + parts = subtractMany(parts, claimedMultiParts) + if (parts.length === 0) continue + } + for (const part of parts) addFreePart(part, [z]) + } + } + + for (const { rect, layers } of freeRectsByKey.values()) { + const layerList = Array.from(layers).sort((a, b) => a - b) + if (layerList.length === 0) continue + out.push({ + minX: rect.x, + minY: rect.y, + maxX: rect.x + rect.width, + maxY: rect.y + rect.height, + zLayers: layerList, + }) + } + + for (const { rect, layers } of obstaclesByKey.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), + isObstacle: true, + }) + } + + return out + } + + override getOutput(): { rects: Rect3d[] } { + return { rects: this.outputRects } + } +} diff --git a/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts b/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts index b37a364..998a21d 100644 --- a/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +++ b/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts @@ -1,18 +1,18 @@ import { BaseSolver } from "@tscircuit/solver-utils" import type { GraphicsObject } from "graphics-debug" +import RBush from "rbush" +import type { Candidate3D, Placed3D, XYRect } from "../../rectdiff-types" import type { CapacityMeshNode, RTreeRect, } from "../../types/capacity-mesh-types" -import { expandRectFromSeed } from "../../utils/expandRectFromSeed" -import { finalizeRects } from "../../utils/finalizeRects" -import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps" -import { rectsToMeshNodes } from "./rectsToMeshNodes" -import type { XYRect, Candidate3D, Placed3D } from "../../rectdiff-types" import type { Obstacle } from "../../types/srj-types" -import RBush from "rbush" +import { expandRectFromSeed } from "../../utils/expandRectFromSeed" +import { overlaps } from "../../utils/rectdiff-geometry" import { rectToTree } from "../../utils/rectToTree" +import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps" import { sameTreeRect } from "../../utils/sameTreeRect" +import { rectsToMeshNodes } from "./rectsToMeshNodes" export type RectDiffExpansionSolverInput = { layerNames: string[] @@ -45,7 +45,6 @@ export type RectDiffExpansionSolverInput = { */ export class RectDiffExpansionSolver extends BaseSolver { placedIndexByLayer: Array> = [] - _meshNodes: CapacityMeshNode[] = [] constructor(private input: RectDiffExpansionSolverInput) { super() } @@ -79,7 +78,7 @@ export class RectDiffExpansionSolver extends BaseSolver { this.stats.placed = this.input.placed.length if (this.input.expansionIndex >= this.input.placed.length) { - this.finalizeIfNeeded() + this.solved = true } } @@ -107,6 +106,7 @@ export class RectDiffExpansionSolver extends BaseSolver { zLayers: p.zLayers, }) + const rectToUse = expanded ?? oldRect if (expanded) { // Update placement + per-layer index (replace old rect object) this.input.placed[idx] = { rect: expanded, zLayers: p.zLayers } @@ -130,21 +130,85 @@ export class RectDiffExpansionSolver extends BaseSolver { ) } + this.expandPlacementLayers(idx, rectToUse) + this.input.expansionIndex += 1 } - private finalizeIfNeeded() { - if (this.solved) return + private expandPlacementLayers(idx: number, rect: XYRect) { + const placement = this.input.placed[idx] + if (!placement) return - const rects = finalizeRects({ - placed: this.input.placed, - obstacles: this.input.obstacles, - zIndexByName: this.input.zIndexByName, - boardVoidRects: this.input.boardVoidRects, - obstacleClearance: this.input.obstacleClearance, - }) - this._meshNodes = rectsToMeshNodes(rects) - this.solved = true + const currentLayers = placement.zLayers.slice().sort((a, b) => a - b) + if (currentLayers.length === 0) return + + const query = { + minX: rect.x, + minY: rect.y, + maxX: rect.x + rect.width, + maxY: rect.y + rect.height, + } + + const isLayerFree = (layer: number) => { + const placedIndex = this.placedIndexByLayer[layer] + if (placedIndex) { + const hits = placedIndex.search(query) + for (const hit of hits) { + if (!overlaps(rect, hit)) continue + if (hit.zLayers.length >= this.input.layerCount) return false + } + } + + return true + } + + const minLayer = currentLayers[0]! + const maxLayer = currentLayers[currentLayers.length - 1]! + let nextMin = minLayer + let nextMax = maxLayer + + while (nextMin - 1 >= 0 && isLayerFree(nextMin - 1)) { + nextMin -= 1 + } + + while (nextMax + 1 < this.input.layerCount && isLayerFree(nextMax + 1)) { + nextMax += 1 + } + + if (nextMin === minLayer && nextMax === maxLayer) return + + const nextLayers: number[] = [] + for (let z = nextMin; z <= nextMax; z += 1) { + nextLayers.push(z) + } + + for (const z of currentLayers) { + const tree = this.placedIndexByLayer[z] + if (tree) { + tree.remove(rectToTree(rect, { zLayers: currentLayers }), sameTreeRect) + tree.insert(rectToTree(rect, { zLayers: nextLayers })) + } + } + + for (const z of nextLayers) { + if (currentLayers.includes(z)) continue + const tree = this.placedIndexByLayer[z] + if (tree) { + tree.insert(rectToTree(rect, { zLayers: nextLayers })) + } + } + + this.input.placed[idx] = { rect, zLayers: nextLayers } + + resizeSoftOverlaps( + { + layerCount: this.input.layerCount, + placed: this.input.placed, + options: this.input.options, + placedIndexByLayer: this.placedIndexByLayer, + }, + idx, + ) } computeProgress(): number { @@ -156,25 +220,25 @@ export class RectDiffExpansionSolver extends BaseSolver { return Math.min(0.999, base + frac * (1 / (grids + 1))) } - override getOutput(): { meshNodes: CapacityMeshNode[] } { - if (this.solved) return { meshNodes: this._meshNodes } - - // Provide a live preview of the placements before finalization so debuggers - // can inspect intermediary states without forcing the solver to finish. - const previewNodes: CapacityMeshNode[] = this.input.placed.map( - (placement, idx) => ({ - capacityMeshNodeId: `expand-preview-${idx}`, - center: { - x: placement.rect.x + placement.rect.width / 2, - y: placement.rect.y + placement.rect.height / 2, - }, - width: placement.rect.width, - height: placement.rect.height, - availableZ: placement.zLayers.slice(), - layer: `z${placement.zLayers.join(",")}`, - }), + override getOutput(): { + placed: Placed3D[] + meshNodes: CapacityMeshNode[] + } { + // Expose placements for a downstream finalization/merge solver step. + // Also return a preview mesh as single-layer nodes for debuggers/UI. + const previewRects = this.input.placed.flatMap((p) => + p.zLayers.map((z) => ({ + minX: p.rect.x, + minY: p.rect.y, + maxX: p.rect.x + p.rect.width, + maxY: p.rect.y + p.rect.height, + zLayers: [z], + })), ) - return { meshNodes: previewNodes } + return { + placed: this.input.placed, + meshNodes: rectsToMeshNodes(previewRects), + } } /** Simple visualization of expanded placements. */ diff --git a/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts b/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts index c0e1420..787b478 100644 --- a/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +++ b/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts @@ -1,24 +1,25 @@ +import type { Bounds } from "@tscircuit/math-utils" import { BasePipelineSolver, definePipelineStep, type PipelineStep, } from "@tscircuit/solver-utils" -import type { - Obstacle, - SimpleRouteConnection, - SimpleRouteJson, -} from "../../types/srj-types" +import type { GraphicsObject } from "graphics-debug" +import type RBush from "rbush" import type { GridFill3DOptions, XYRect } from "../../rectdiff-types" import type { CapacityMeshNode, RTreeRect, } from "../../types/capacity-mesh-types" -import { RectDiffSeedingSolver } from "../RectDiffSeedingSolver/RectDiffSeedingSolver" +import type { + Obstacle, + SimpleRouteConnection, + SimpleRouteJson, +} from "../../types/srj-types" +import { MergeToMaximizeLargeMultiLayerNodesPipelineSolver } from "../MergeToMaximizeLargeMultiLayerNodes/MergeToMaximizeLargeMultiLayerNodesPipelineSolver" import { RectDiffExpansionSolver } from "../RectDiffExpansionSolver/RectDiffExpansionSolver" -import type { GraphicsObject } from "graphics-debug" -import RBush from "rbush" +import { RectDiffSeedingSolver } from "../RectDiffSeedingSolver/RectDiffSeedingSolver" import { buildObstacleIndexesByLayer } from "./buildObstacleIndexes" -import type { Bounds } from "@tscircuit/math-utils" export type RectDiffGridSolverPipelineInput = { bounds: Bounds @@ -37,6 +38,7 @@ export type RectDiffGridSolverPipelineInput = { export class RectDiffGridSolverPipeline extends BasePipelineSolver { rectDiffSeedingSolver?: RectDiffSeedingSolver rectDiffExpansionSolver?: RectDiffExpansionSolver + mergeToMaximizeLargeMultiLayerNodes?: MergeToMaximizeLargeMultiLayerNodesPipelineSolver private obstacleIndexByLayer: Array> private layerNames: string[] private zIndexByName: Map @@ -115,6 +117,25 @@ export class RectDiffGridSolverPipeline extends BasePipelineSolver { + const output = pipeline.rectDiffExpansionSolver?.getOutput() + if (!output) { + throw new Error("RectDiffExpansionSolver did not produce output") + } + return [ + { + placed: output.placed, + obstacles: pipeline.inputProblem.obstacles, + layerCount: pipeline.inputProblem.layerCount, + zIndexByName: pipeline.zIndexByName, + obstacleClearance: pipeline.inputProblem.obstacleClearance, + }, + ] + }, + ), ] override getConstructorParams() { @@ -122,8 +143,11 @@ export class RectDiffGridSolverPipeline extends BasePipelineSolver = [] - - if (span.length >= minMulti.minLayers) { - attempts.push({ - kind: "multi", - layers: span, - minReq: { width: minMulti.width, height: minMulti.height }, - }) - } - attempts.push({ - kind: "single", - layers: [cand.z], + // Default behavior: only place single-layer nodes during seeding. + // Multi-layer grouping is handled in later pipeline stages. + const rect = expandRectFromSeed({ + startX: cand.x, + startY: cand.y, + gridSize: grid, + bounds: this.bounds, + obsticalIndexByLayer: this.input.obstacleIndexByLayer, + placedIndexByLayer: this.placedIndexByLayer, + initialCellRatio, + maxAspectRatio, minReq: { width: minSingle.width, height: minSingle.height }, + zLayers: [cand.z], }) - - const ordered = preferMultiLayer ? attempts : attempts.reverse() - - for (const attempt of ordered) { - const rect = expandRectFromSeed({ - startX: cand.x, - startY: cand.y, - gridSize: grid, - bounds: this.bounds, - obsticalIndexByLayer: this.input.obstacleIndexByLayer, - placedIndexByLayer: this.placedIndexByLayer, - initialCellRatio, - maxAspectRatio, - minReq: attempt.minReq, - zLayers: attempt.layers, - }) - if (!rect) continue - + if (rect) { // Place the new node - const placed: Placed3D = { rect, zLayers: [...attempt.layers] } + const placed: Placed3D = { rect, zLayers: [cand.z] } const newIndex = this.placed.push(placed) - 1 - for (const z of attempt.layers) { + for (const z of placed.zLayers) { const idx = this.placedIndexByLayer[z] if (idx) { idx.insert(rectToTree(rect, { zLayers: placed.zLayers })) diff --git a/lib/utils/finalizeRects.ts b/lib/utils/finalizeRects.ts index da3dd55..b9ea3b1 100644 --- a/lib/utils/finalizeRects.ts +++ b/lib/utils/finalizeRects.ts @@ -1,62 +1,24 @@ -import type { Obstacle } from "../types/srj-types" import type { Placed3D, Rect3d, XYRect } from "../rectdiff-types" -import { - obstacleToXYRect, - obstacleZs, -} from "../solvers/RectDiffSeedingSolver/layers" +import { MergeToMaximizeLargeMultiLayerNodesSolver } from "../solvers/MergeToMaximizeLargeMultiLayerNodes/MergeToMaximizeLargeMultiLayerNodesSolver" +import type { Obstacle } from "../types/srj-types" export function finalizeRects(params: { placed: Placed3D[] obstacles: Obstacle[] boardVoidRects: XYRect[] zIndexByName: Map + layerCount?: number obstacleClearance?: number }): Rect3d[] { - // Convert all placed (free space) nodes to output format - const out: Rect3d[] = params.placed.map((p) => ({ - minX: p.rect.x, - minY: p.rect.y, - maxX: p.rect.x + p.rect.width, - maxY: p.rect.y + p.rect.height, - zLayers: [...p.zLayers].sort((a, b) => a - b), - })) - - const layersByKey = new Map }>() - - for (const obstacle of params.obstacles ?? []) { - const baseRect = obstacleToXYRect(obstacle) - if (!baseRect) continue - const rect = params.obstacleClearance - ? { - x: baseRect.x - params.obstacleClearance, - y: baseRect.y - params.obstacleClearance, - width: baseRect.width + 2 * params.obstacleClearance, - height: baseRect.height + 2 * params.obstacleClearance, - } - : baseRect - const zLayers = - 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), - isObstacle: true, - }) - } - - return out + const solver = new MergeToMaximizeLargeMultiLayerNodesSolver({ + placed: params.placed, + obstacles: params.obstacles, + layerCount: + params.layerCount ?? + Math.max(1, ...params.placed.flatMap((p) => p.zLayers.map((z) => z + 1))), + zIndexByName: params.zIndexByName, + obstacleClearance: params.obstacleClearance, + }) + solver.solve() + return solver.getOutput().rects } diff --git a/tests/__snapshots__/board-outline.snap.svg b/tests/__snapshots__/board-outline.snap.svg index 8f9b27f..814ea47 100644 --- a/tests/__snapshots__/board-outline.snap.svg +++ b/tests/__snapshots__/board-outline.snap.svg @@ -1,4 +1,4 @@ -