Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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: 0 additions & 1 deletion lib/RectDiffPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
}

override initialVisualize(): GraphicsObject {
console.log("RectDiffPipeline - initialVisualize")
const graphics = createBaseVisualization(
this.inputProblem.simpleRouteJson,
"RectDiffPipeline - Initial",
Expand Down
20 changes: 2 additions & 18 deletions lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { longestFreeSpanAroundZ } from "./longestFreeSpanAroundZ"
import { allLayerNode } from "../../utils/buildHardPlacedByLayer"
import { isFullyOccupiedAtPoint } from "lib/utils/isFullyOccupiedAtPoint"
import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
import { getColorForZLayer } from "lib/utils/getColorForZLayer"
import RBush from "rbush"
import type { RTreeRect } from "lib/types/capacity-mesh-types"

Expand Down Expand Up @@ -336,23 +337,6 @@ export class RectDiffSeedingSolver extends BaseSolver {
}
}

/** Get color based on z layer for visualization. */
private getColorForZLayer(zLayers: number[]): {
fill: string
stroke: string
} {
const minZ = Math.min(...zLayers)
const colors = [
{ fill: "#dbeafe", stroke: "#3b82f6" },
{ fill: "#fef3c7", stroke: "#f59e0b" },
{ fill: "#d1fae5", stroke: "#10b981" },
{ fill: "#e9d5ff", stroke: "#a855f7" },
{ fill: "#fed7aa", stroke: "#f97316" },
{ fill: "#fecaca", stroke: "#ef4444" },
] as const
return colors[minZ % colors.length]!
}

/** Visualization focused on the grid seeding phase. */
override visualize(): GraphicsObject {
const rects: NonNullable<GraphicsObject["rects"]> = []
Expand Down Expand Up @@ -460,7 +444,7 @@ export class RectDiffSeedingSolver extends BaseSolver {
// current placements (streaming) during grid fill
if (this.placed?.length) {
for (const placement of this.placed) {
const colors = this.getColorForZLayer(placement.zLayers)
const colors = getColorForZLayer(placement.zLayers)
rects.push({
center: {
x: placement.rect.x + placement.rect.width / 2,
Expand Down
17 changes: 17 additions & 0 deletions lib/utils/getColorForZLayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const getColorForZLayer = (
zLayers: number[],
): {
fill: string
stroke: string
} => {
const minZ = Math.min(...zLayers)
const colors = [
{ fill: "#dbeafe", stroke: "#3b82f6" },
{ fill: "#fef3c7", stroke: "#f59e0b" },
{ fill: "#d1fae5", stroke: "#10b981" },
{ fill: "#e9d5ff", stroke: "#a855f7" },
{ fill: "#fed7aa", stroke: "#f97316" },
{ fill: "#fecaca", stroke: "#ef4444" },
] as const
return colors[minZ % colors.length]!
}
130 changes: 130 additions & 0 deletions tests/fixtures/getPerLayerVisualizations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type { GraphicsObject, Line, Point, Rect } from "graphics-debug"

export function getPerLayerVisualizations(
graphics: GraphicsObject,
): Map<string, GraphicsObject> {
const rects = (graphics.rects ?? []) as NonNullable<Rect[]>
const lines = (graphics.lines ?? []) as NonNullable<Line[]>
const points = (graphics.points ?? []) as NonNullable<Point[]>

const zValues = new Set<number>()

const addZValuesFromLayer = (layer: string) => {
if (!layer.startsWith("z")) return
const rest = layer.slice(1)
if (!rest) return
for (const part of rest.split(",")) {
const value = Number.parseInt(part, 10)
if (!Number.isNaN(value)) zValues.add(value)
}
}

for (const rect of rects) addZValuesFromLayer(rect.layer!)
for (const line of lines) addZValuesFromLayer(line.layer!)
for (const point of points) addZValuesFromLayer(point.layer!)

const result = new Map<string, GraphicsObject>()
if (!zValues.size) return result

const sortedZ = Array.from(zValues).sort((a, b) => a - b)

const commonRects: NonNullable<Rect[]> = []
const perLayerRects: { layers: number[]; rect: Rect }[] = []

for (const rect of rects) {
const layer = rect.layer!
if (layer.startsWith("z")) {
const rest = layer.slice(1)
if (rest) {
const layers = rest
.split(",")
.map((part) => Number.parseInt(part, 10))
.filter((value) => !Number.isNaN(value))
if (layers.length) {
perLayerRects.push({ layers, rect })
continue
}
}
}
commonRects.push(rect)
}

const commonLines: NonNullable<Line[]> = []
const perLayerLines: { layers: number[]; line: Line }[] = []

for (const line of lines) {
const layer = line.layer!
if (layer.startsWith("z")) {
const rest = layer.slice(1)
if (rest) {
const layers = rest
.split(",")
.map((part) => Number.parseInt(part, 10))
.filter((value) => !Number.isNaN(value))
if (layers.length) {
perLayerLines.push({ layers, line })
continue
}
}
}
commonLines.push(line)
}

const commonPoints: NonNullable<Point[]> = []
const perLayerPoints: { layers: number[]; point: Point }[] = []

for (const point of points) {
const layer = point.layer!
if (layer.startsWith("z")) {
const rest = layer.slice(1)
if (rest) {
const layers = rest
.split(",")
.map((part) => Number.parseInt(part, 10))
.filter((value) => !Number.isNaN(value))
if (layers.length) {
perLayerPoints.push({ layers, point })
continue
}
}
}
commonPoints.push(point)
}

const allCombos: number[][] = [[]]
for (const z of sortedZ) {
const withZ = allCombos.map((combo) => [...combo, z])
allCombos.push(...withZ)
}

for (const combo of allCombos.filter((c) => c.length > 0)) {
const key = `z${combo.join(",")}`

const layerRects: NonNullable<Rect[]> = [...commonRects]
const layerLines: NonNullable<Line[]> = [...commonLines]
const layerPoints: NonNullable<Point[]> = [...commonPoints]

const intersects = (layers: number[]) =>
layers.some((layer) => combo.includes(layer))

for (const { layers, rect } of perLayerRects) {
if (intersects(layers)) layerRects.push(rect)
}
for (const { layers, line } of perLayerLines) {
if (intersects(layers)) layerLines.push(line)
}
for (const { layers, point } of perLayerPoints) {
if (intersects(layers)) layerPoints.push(point)
}

result.set(key, {
title: `${graphics.title ?? ""} - z${combo.join(",")}`,
coordinateSystem: graphics.coordinateSystem,
rects: layerRects,
lines: layerLines,
points: layerPoints,
})
}

return result
}
33 changes: 33 additions & 0 deletions tests/fixtures/makeCapacityMeshNodeWithLayerInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Rect } from "graphics-debug"
import type { CapacityMeshNode } from "lib/types/capacity-mesh-types"
import { getColorForZLayer } from "lib/utils/getColorForZLayer"

export const makeCapacityMeshNodeWithLayerInfo = (
nodes: CapacityMeshNode[],
): Map<string, Rect[]> => {
const map = new Map<string, Rect[]>()

for (const node of nodes) {
if (!node.availableZ.length) continue
const key = node.availableZ.join(",")
const colors = getColorForZLayer(node.availableZ)
const rect: Rect = {
center: node.center,
width: node.width,
height: node.height,
layer: `z${key}`,
stroke: "black",
fill: node._containsObstacle ? "red" : colors.fill,
label: "node",
}

const existing = map.get(key)
if (existing) {
existing.push(rect)
} else {
map.set(key, [rect])
}
}

return map
}
44 changes: 44 additions & 0 deletions tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 76 additions & 0 deletions tests/solver/rectDiffGridSolverPipeline.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { expect, test } from "bun:test"
import srj from "test-assets/bugreport11-b2de3c.json"
import {
getSvgFromGraphicsObject,
stackGraphicsVertically,
type GraphicsObject,
type Rect,
} from "graphics-debug"
import { RectDiffPipeline } from "lib/RectDiffPipeline"
import { makeCapacityMeshNodeWithLayerInfo } from "tests/fixtures/makeCapacityMeshNodeWithLayerInfo"

test("RectDiffPipeline mesh layer snapshots", async () => {
const solver = new RectDiffPipeline({
simpleRouteJson: srj.simple_route_json,
})

solver.solve()

const { meshNodes } = solver.getOutput()
const rectsByCombo = makeCapacityMeshNodeWithLayerInfo(meshNodes)
const allGraphicsObjects: GraphicsObject[] = []

// Generate a snapshot for each z-layer
for (const z of [0, 1, 2, 3]) {
const layerRects: Rect[] = []

for (const [key, rects] of rectsByCombo) {
const layers = key
.split(",")
.map((value) => Number.parseInt(value, 10))
.filter((value) => !Number.isNaN(value))

if (layers.includes(z)) {
layerRects.push(...rects)
}
}

let labelY = 0

if (layerRects.length > 0) {
let maxY = -Infinity

for (const rect of layerRects) {
const top = rect.center.y + rect.height

if (top > maxY) maxY = top
}

labelY = maxY
}

const graphics: GraphicsObject = {
title: `RectDiffPipeline - z${z}`,
texts: [
{
anchorSide: "top_right",
text: `Layer z=${z}`,
x: 0,
y: labelY,
fontSize: 1,
},
],
coordinateSystem: "cartesian",
rects: layerRects,
points: [],
lines: [],
}

allGraphicsObjects.push(graphics)
}

const svg = getSvgFromGraphicsObject(
stackGraphicsVertically(allGraphicsObjects),
)
await expect(svg).toMatchSvgSnapshot(import.meta.path)
})