Skip to content
Open
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
80 changes: 80 additions & 0 deletions lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { Obstacle } from "../../types/srj-types"
import RBush from "rbush"
import { rectToTree } from "../../utils/rectToTree"
import { sameTreeRect } from "../../utils/sameTreeRect"
import { overlaps } from "../../utils/rectdiff-geometry"

export type RectDiffExpansionSolverInput = {
layerNames: string[]
Expand Down Expand Up @@ -107,6 +108,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 }
Expand All @@ -130,9 +132,87 @@ export class RectDiffExpansionSolver extends BaseSolver {
)
}

this.expandPlacementLayers(idx, rectToUse)

this.input.expansionIndex += 1
}

private expandPlacementLayers(idx: number, rect: XYRect) {
const placement = this.input.placed[idx]
if (!placement) return

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,
)
}

private finalizeIfNeeded() {
if (this.solved) return

Expand Down
25 changes: 17 additions & 8 deletions lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ export class RectDiffSeedingSolver extends BaseSolver {
maxAspectRatio: 3,
minSingle: { width: 2 * trace, height: 2 * trace },
minMulti: {
width: 4 * trace,
height: 4 * trace,
width: 2 * trace,
height: 2 * trace,
minLayers: Math.min(2, Math.max(1, srj.layerCount || 1)),
},
preferMultiLayer: true,
Expand Down Expand Up @@ -243,19 +243,28 @@ export class RectDiffSeedingSolver extends BaseSolver {
}> = []

if (span.length >= minMulti.minLayers) {
attempts.push({
kind: "multi",
layers: span,
minReq: { width: minMulti.width, height: minMulti.height },
})
const spanIndex = span.indexOf(cand.z)
let lo = 0
let hi = span.length - 1

while (hi - lo + 1 >= minMulti.minLayers) {
attempts.push({
kind: "multi",
layers: span.slice(lo, hi + 1),
minReq: { width: minMulti.width, height: minMulti.height },
})
if (hi - lo + 1 === minMulti.minLayers) break
if (spanIndex - lo > hi - spanIndex) lo += 1
else hi -= 1
}
}
attempts.push({
kind: "single",
layers: [cand.z],
minReq: { width: minSingle.width, height: minSingle.height },
})

const ordered = preferMultiLayer ? attempts : attempts.reverse()
const ordered = preferMultiLayer ? attempts : attempts.slice().reverse()

for (const attempt of ordered) {
const rect = expandRectFromSeed({
Expand Down
58 changes: 50 additions & 8 deletions lib/utils/finalizeRects.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Obstacle } from "../types/srj-types"
import type { Placed3D, Rect3d, XYRect } from "../rectdiff-types"
import { subtractRect2D } from "./rectdiff-geometry"
import {
obstacleToXYRect,
obstacleZs,
Expand All @@ -12,14 +13,8 @@ export function finalizeRects(params: {
zIndexByName: Map<string, 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 out: Rect3d[] = []
const obstacleRectsByLayer = new Map<number, XYRect[]>()

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

Expand All @@ -38,6 +33,11 @@ export function finalizeRects(params: {
obstacle.zLayers?.length && obstacle.zLayers.length > 0
? obstacle.zLayers
: obstacleZs(obstacle, params.zIndexByName)
for (const layer of zLayers) {
const list = obstacleRectsByLayer.get(layer)
if (list) list.push(rect)
else obstacleRectsByLayer.set(layer, [rect])
}
const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`
let entry = layersByKey.get(key)
if (!entry) {
Expand All @@ -47,6 +47,48 @@ export function finalizeRects(params: {
zLayers.forEach((layer: number) => entry!.layers.add(layer))
}

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

for (const placed of params.placed) {
for (const layer of placed.zLayers) {
let parts: XYRect[] = [placed.rect]
const blockers = obstacleRectsByLayer.get(layer) ?? []
for (const blocker of blockers) {
const nextParts: XYRect[] = []
for (const part of parts) {
nextParts.push(...subtractRect2D(part, blocker))
}
parts = nextParts
if (parts.length === 0) break
}

for (const part of parts) {
const key = `${part.x}:${part.y}:${part.width}:${part.height}`
let entry = freeRectsByKey.get(key)
if (!entry) {
entry = { rect: part, layers: new Set() }
freeRectsByKey.set(key, entry)
}
entry.layers.add(layer)
}
}
}

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 layersByKey.values()) {
out.push({
minX: rect.x,
Expand Down
4 changes: 2 additions & 2 deletions tests/__snapshots__/board-outline.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading