@tscircuit/rectdiff 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +16 -12
- package/dist/index.js +272 -141
- package/lib/RectDiffPipeline.ts +0 -1
- package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +59 -12
- package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +25 -6
- package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +70 -0
- package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +41 -86
- package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +15 -15
- package/lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts +16 -16
- package/lib/solvers/RectDiffSeedingSolver/longestFreeSpanAroundZ.ts +17 -9
- package/lib/types/capacity-mesh-types.ts +9 -0
- package/lib/utils/finalizeRects.ts +25 -20
- package/lib/utils/getColorForZLayer.ts +17 -0
- package/lib/utils/isFullyOccupiedAtPoint.ts +23 -16
- package/lib/utils/rectToTree.ts +10 -0
- package/lib/utils/resizeSoftOverlaps.ts +36 -7
- package/lib/utils/sameTreeRect.ts +7 -0
- package/package.json +1 -1
- package/tests/examples/example01.test.tsx +18 -1
- package/tests/fixtures/getPerLayerVisualizations.ts +130 -0
- package/tests/fixtures/makeCapacityMeshNodeWithLayerInfo.ts +33 -0
- package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +44 -0
- package/tests/solver/rectDiffGridSolverPipeline.test.ts +88 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
1
2
|
import type { XYRect } from "../../rectdiff-types"
|
|
2
3
|
import { clamp, containsPoint } from "../../utils/rectdiff-geometry"
|
|
4
|
+
import type RBush from "rbush"
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Find the longest contiguous free span around z (optionally capped).
|
|
@@ -11,8 +13,8 @@ export function longestFreeSpanAroundZ(params: {
|
|
|
11
13
|
layerCount: number
|
|
12
14
|
minSpan: number
|
|
13
15
|
maxSpan: number | undefined
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
obstacleIndexByLayer: Array<RBush<RTreeRect> | undefined>
|
|
17
|
+
additionalBlockersByLayer?: XYRect[][]
|
|
16
18
|
}): number[] {
|
|
17
19
|
const {
|
|
18
20
|
x,
|
|
@@ -21,16 +23,22 @@ export function longestFreeSpanAroundZ(params: {
|
|
|
21
23
|
layerCount,
|
|
22
24
|
minSpan,
|
|
23
25
|
maxSpan,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
obstacleIndexByLayer,
|
|
27
|
+
additionalBlockersByLayer,
|
|
26
28
|
} = params
|
|
27
29
|
|
|
28
30
|
const isFreeAt = (layer: number) => {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const query = {
|
|
32
|
+
minX: x,
|
|
33
|
+
minY: y,
|
|
34
|
+
maxX: x,
|
|
35
|
+
maxY: y,
|
|
36
|
+
}
|
|
37
|
+
const obstacleIdx = obstacleIndexByLayer[layer]
|
|
38
|
+
if (obstacleIdx && obstacleIdx.search(query).length > 0) return false
|
|
39
|
+
|
|
40
|
+
const extras = additionalBlockersByLayer?.[layer] ?? []
|
|
41
|
+
return !extras.some((b) => containsPoint(b, { x, y }))
|
|
34
42
|
}
|
|
35
43
|
let lo = z
|
|
36
44
|
let hi = z
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { XYRect } from "lib/rectdiff-types"
|
|
2
|
+
|
|
1
3
|
export type CapacityMeshNodeId = string
|
|
2
4
|
|
|
3
5
|
export interface CapacityMesh {
|
|
@@ -31,3 +33,10 @@ export interface CapacityMeshEdge {
|
|
|
31
33
|
capacityMeshEdgeId: string
|
|
32
34
|
nodeIds: [CapacityMeshNodeId, CapacityMeshNodeId]
|
|
33
35
|
}
|
|
36
|
+
|
|
37
|
+
export type RTreeRect = XYRect & {
|
|
38
|
+
minX: number
|
|
39
|
+
minY: number
|
|
40
|
+
maxX: number
|
|
41
|
+
maxY: number
|
|
42
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { Placed3D, Rect3d, XYRect } from "../rectdiff-types"
|
|
2
|
+
import type { SimpleRouteJson } from "../types/srj-types"
|
|
3
|
+
import {
|
|
4
|
+
buildZIndexMap,
|
|
5
|
+
obstacleToXYRect,
|
|
6
|
+
obstacleZs,
|
|
7
|
+
} from "../solvers/RectDiffSeedingSolver/layers"
|
|
2
8
|
|
|
3
9
|
export function finalizeRects(params: {
|
|
4
10
|
placed: Placed3D[]
|
|
5
|
-
|
|
11
|
+
srj: SimpleRouteJson
|
|
6
12
|
boardVoidRects: XYRect[]
|
|
7
13
|
}): Rect3d[] {
|
|
8
14
|
// Convert all placed (free space) nodes to output format
|
|
@@ -14,33 +20,32 @@ export function finalizeRects(params: {
|
|
|
14
20
|
zLayers: [...p.zLayers].sort((a, b) => a - b),
|
|
15
21
|
}))
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Obstacles are stored per-layer in `obstaclesByLayer`, but we want to emit
|
|
20
|
-
* single 3D nodes for multi-layer obstacles if they share the same rect.
|
|
21
|
-
* We use the `XYRect` object reference identity to group layers.
|
|
22
|
-
*/
|
|
23
|
-
const layersByObstacleRect = new Map<XYRect, number[]>()
|
|
23
|
+
const { zIndexByName } = buildZIndexMap(params.srj)
|
|
24
|
+
const layersByKey = new Map<string, { rect: XYRect; layers: Set<number> }>()
|
|
24
25
|
|
|
25
|
-
params.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
for (const obstacle of params.srj.obstacles ?? []) {
|
|
27
|
+
const rect = obstacleToXYRect(obstacle as any)
|
|
28
|
+
if (!rect) continue
|
|
29
|
+
const zLayers =
|
|
30
|
+
obstacle.zLayers?.length && obstacle.zLayers.length > 0
|
|
31
|
+
? obstacle.zLayers
|
|
32
|
+
: obstacleZs(obstacle as any, zIndexByName)
|
|
33
|
+
const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`
|
|
34
|
+
let entry = layersByKey.get(key)
|
|
35
|
+
if (!entry) {
|
|
36
|
+
entry = { rect, layers: new Set() }
|
|
37
|
+
layersByKey.set(key, entry)
|
|
30
38
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Append obstacle nodes to the output
|
|
34
|
-
const voidSet = new Set(params.boardVoidRects || [])
|
|
35
|
-
for (const [rect, layerIndices] of layersByObstacleRect.entries()) {
|
|
36
|
-
if (voidSet.has(rect)) continue // Skip void rects
|
|
39
|
+
zLayers.forEach((layer) => entry!.layers.add(layer))
|
|
40
|
+
}
|
|
37
41
|
|
|
42
|
+
for (const { rect, layers } of layersByKey.values()) {
|
|
38
43
|
out.push({
|
|
39
44
|
minX: rect.x,
|
|
40
45
|
minY: rect.y,
|
|
41
46
|
maxX: rect.x + rect.width,
|
|
42
47
|
maxY: rect.y + rect.height,
|
|
43
|
-
zLayers:
|
|
48
|
+
zLayers: Array.from(layers).sort((a, b) => a - b),
|
|
44
49
|
isObstacle: true,
|
|
45
50
|
})
|
|
46
51
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const getColorForZLayer = (
|
|
2
|
+
zLayers: number[],
|
|
3
|
+
): {
|
|
4
|
+
fill: string
|
|
5
|
+
stroke: string
|
|
6
|
+
} => {
|
|
7
|
+
const minZ = Math.min(...zLayers)
|
|
8
|
+
const colors = [
|
|
9
|
+
{ fill: "#dbeafe", stroke: "#3b82f6" },
|
|
10
|
+
{ fill: "#fef3c7", stroke: "#f59e0b" },
|
|
11
|
+
{ fill: "#d1fae5", stroke: "#10b981" },
|
|
12
|
+
{ fill: "#e9d5ff", stroke: "#a855f7" },
|
|
13
|
+
{ fill: "#fed7aa", stroke: "#f97316" },
|
|
14
|
+
{ fill: "#fecaca", stroke: "#ef4444" },
|
|
15
|
+
] as const
|
|
16
|
+
return colors[minZ % colors.length]!
|
|
17
|
+
}
|
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
1
|
+
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
2
|
+
import RBush from "rbush"
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
): boolean {
|
|
4
|
+
export type OccupancyParams = {
|
|
5
|
+
layerCount: number
|
|
6
|
+
obstacleIndexByLayer: Array<RBush<RTreeRect> | undefined>
|
|
7
|
+
placedIndexByLayer: Array<RBush<RTreeRect> | undefined>
|
|
8
|
+
point: { x: number; y: number }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isFullyOccupiedAtPoint(params: OccupancyParams): boolean {
|
|
12
|
+
const query = {
|
|
13
|
+
minX: params.point.x,
|
|
14
|
+
minY: params.point.y,
|
|
15
|
+
maxX: params.point.x,
|
|
16
|
+
maxY: params.point.y,
|
|
17
|
+
}
|
|
12
18
|
for (let z = 0; z < params.layerCount; z++) {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
const obstacleIdx = params.obstacleIndexByLayer[z]
|
|
20
|
+
const hasObstacle = !!obstacleIdx && obstacleIdx.search(query).length > 0
|
|
21
|
+
|
|
22
|
+
const placedIdx = params.placedIndexByLayer[z]
|
|
23
|
+
const hasPlaced = !!placedIdx && placedIdx.search(query).length > 0
|
|
24
|
+
|
|
25
|
+
if (!hasObstacle && !hasPlaced) return false
|
|
19
26
|
}
|
|
20
27
|
return true
|
|
21
28
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { XYRect } from "lib/rectdiff-types"
|
|
2
|
+
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
3
|
+
|
|
4
|
+
export const rectToTree = (rect: XYRect): RTreeRect => ({
|
|
5
|
+
...rect,
|
|
6
|
+
minX: rect.x,
|
|
7
|
+
minY: rect.y,
|
|
8
|
+
maxX: rect.x + rect.width,
|
|
9
|
+
maxY: rect.y + rect.height,
|
|
10
|
+
})
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
1
2
|
import type { Placed3D, XYRect } from "../rectdiff-types"
|
|
2
3
|
import { overlaps, subtractRect2D, EPS } from "./rectdiff-geometry"
|
|
4
|
+
import type RBush from "rbush"
|
|
3
5
|
|
|
4
6
|
export function resizeSoftOverlaps(
|
|
5
7
|
params: {
|
|
6
8
|
layerCount: number
|
|
7
9
|
placed: Placed3D[]
|
|
8
|
-
placedByLayer: XYRect[][]
|
|
9
10
|
options: any
|
|
11
|
+
placedIndexByLayer?: Array<RBush<RTreeRect> | undefined>
|
|
10
12
|
},
|
|
11
13
|
newIndex: number,
|
|
12
14
|
) {
|
|
@@ -54,21 +56,48 @@ export function resizeSoftOverlaps(
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
// Remove
|
|
59
|
+
// Remove fully overlapped nodes and keep indexes in sync
|
|
60
|
+
const rectToTree = (rect: XYRect): RTreeRect => ({
|
|
61
|
+
...rect,
|
|
62
|
+
minX: rect.x,
|
|
63
|
+
minY: rect.y,
|
|
64
|
+
maxX: rect.x + rect.width,
|
|
65
|
+
maxY: rect.y + rect.height,
|
|
66
|
+
})
|
|
67
|
+
const sameRect = (a: RTreeRect, b: RTreeRect) =>
|
|
68
|
+
a.minX === b.minX &&
|
|
69
|
+
a.minY === b.minY &&
|
|
70
|
+
a.maxX === b.maxX &&
|
|
71
|
+
a.maxY === b.maxY
|
|
72
|
+
|
|
58
73
|
removeIdx
|
|
59
74
|
.sort((a, b) => b - a)
|
|
60
75
|
.forEach((idx) => {
|
|
61
76
|
const rem = params.placed.splice(idx, 1)[0]!
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
if (params.placedIndexByLayer) {
|
|
78
|
+
for (const z of rem.zLayers) {
|
|
79
|
+
const tree = params.placedIndexByLayer[z]
|
|
80
|
+
if (tree) tree.remove(rectToTree(rem.rect), sameRect)
|
|
81
|
+
}
|
|
66
82
|
}
|
|
67
83
|
})
|
|
68
84
|
|
|
69
85
|
// Add replacements
|
|
70
86
|
for (const p of toAdd) {
|
|
71
87
|
params.placed.push(p)
|
|
72
|
-
for (const z of p.zLayers)
|
|
88
|
+
for (const z of p.zLayers) {
|
|
89
|
+
if (params.placedIndexByLayer) {
|
|
90
|
+
const idx = params.placedIndexByLayer[z]
|
|
91
|
+
if (idx) {
|
|
92
|
+
idx.insert({
|
|
93
|
+
...p.rect,
|
|
94
|
+
minX: p.rect.x,
|
|
95
|
+
minY: p.rect.y,
|
|
96
|
+
maxX: p.rect.x + p.rect.width,
|
|
97
|
+
maxY: p.rect.y + p.rect.height,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
73
102
|
}
|
|
74
103
|
}
|
package/package.json
CHANGED
|
@@ -2,6 +2,11 @@ import { expect, test } from "bun:test"
|
|
|
2
2
|
import simpleRouteJson from "../../test-assets/example01.json"
|
|
3
3
|
import { RectDiffPipeline } from "../../lib/RectDiffPipeline"
|
|
4
4
|
import { getSvgFromGraphicsObject } from "graphics-debug"
|
|
5
|
+
import {
|
|
6
|
+
buildZIndexMap,
|
|
7
|
+
obstacleToXYRect,
|
|
8
|
+
obstacleZs,
|
|
9
|
+
} from "lib/solvers/RectDiffSeedingSolver/layers"
|
|
5
10
|
|
|
6
11
|
test.skip("example01", () => {
|
|
7
12
|
const solver = new RectDiffPipeline({ simpleRouteJson })
|
|
@@ -17,7 +22,19 @@ test.skip("example01", () => {
|
|
|
17
22
|
const step = 0.004
|
|
18
23
|
const layerCount = simpleRouteJson.layerCount || 2
|
|
19
24
|
const state = (solver as any).state
|
|
20
|
-
const
|
|
25
|
+
const { zIndexByName } = buildZIndexMap(simpleRouteJson as any)
|
|
26
|
+
const obstacles = Array.from({ length: layerCount }, () => [] as any[])
|
|
27
|
+
for (const obstacle of simpleRouteJson.obstacles ?? []) {
|
|
28
|
+
const rect = obstacleToXYRect(obstacle as any)
|
|
29
|
+
if (!rect) continue
|
|
30
|
+
const zLayers =
|
|
31
|
+
obstacle.zLayers?.length && obstacle.zLayers.length > 0
|
|
32
|
+
? obstacle.zLayers
|
|
33
|
+
: obstacleZs(obstacle as any, zIndexByName)
|
|
34
|
+
zLayers.forEach((z: number) => {
|
|
35
|
+
if (z >= 0 && z < layerCount) obstacles[z]!.push(rect)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
21
38
|
const placed = state.placed
|
|
22
39
|
|
|
23
40
|
const gapPoints: Array<{ x: number; y: number; z: number }> = []
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { GraphicsObject, Line, Point, Rect } from "graphics-debug"
|
|
2
|
+
|
|
3
|
+
export function getPerLayerVisualizations(
|
|
4
|
+
graphics: GraphicsObject,
|
|
5
|
+
): Map<string, GraphicsObject> {
|
|
6
|
+
const rects = (graphics.rects ?? []) as NonNullable<Rect[]>
|
|
7
|
+
const lines = (graphics.lines ?? []) as NonNullable<Line[]>
|
|
8
|
+
const points = (graphics.points ?? []) as NonNullable<Point[]>
|
|
9
|
+
|
|
10
|
+
const zValues = new Set<number>()
|
|
11
|
+
|
|
12
|
+
const addZValuesFromLayer = (layer: string) => {
|
|
13
|
+
if (!layer.startsWith("z")) return
|
|
14
|
+
const rest = layer.slice(1)
|
|
15
|
+
if (!rest) return
|
|
16
|
+
for (const part of rest.split(",")) {
|
|
17
|
+
const value = Number.parseInt(part, 10)
|
|
18
|
+
if (!Number.isNaN(value)) zValues.add(value)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const rect of rects) addZValuesFromLayer(rect.layer!)
|
|
23
|
+
for (const line of lines) addZValuesFromLayer(line.layer!)
|
|
24
|
+
for (const point of points) addZValuesFromLayer(point.layer!)
|
|
25
|
+
|
|
26
|
+
const result = new Map<string, GraphicsObject>()
|
|
27
|
+
if (!zValues.size) return result
|
|
28
|
+
|
|
29
|
+
const sortedZ = Array.from(zValues).sort((a, b) => a - b)
|
|
30
|
+
|
|
31
|
+
const commonRects: NonNullable<Rect[]> = []
|
|
32
|
+
const perLayerRects: { layers: number[]; rect: Rect }[] = []
|
|
33
|
+
|
|
34
|
+
for (const rect of rects) {
|
|
35
|
+
const layer = rect.layer!
|
|
36
|
+
if (layer.startsWith("z")) {
|
|
37
|
+
const rest = layer.slice(1)
|
|
38
|
+
if (rest) {
|
|
39
|
+
const layers = rest
|
|
40
|
+
.split(",")
|
|
41
|
+
.map((part) => Number.parseInt(part, 10))
|
|
42
|
+
.filter((value) => !Number.isNaN(value))
|
|
43
|
+
if (layers.length) {
|
|
44
|
+
perLayerRects.push({ layers, rect })
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
commonRects.push(rect)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const commonLines: NonNullable<Line[]> = []
|
|
53
|
+
const perLayerLines: { layers: number[]; line: Line }[] = []
|
|
54
|
+
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
const layer = line.layer!
|
|
57
|
+
if (layer.startsWith("z")) {
|
|
58
|
+
const rest = layer.slice(1)
|
|
59
|
+
if (rest) {
|
|
60
|
+
const layers = rest
|
|
61
|
+
.split(",")
|
|
62
|
+
.map((part) => Number.parseInt(part, 10))
|
|
63
|
+
.filter((value) => !Number.isNaN(value))
|
|
64
|
+
if (layers.length) {
|
|
65
|
+
perLayerLines.push({ layers, line })
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
commonLines.push(line)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const commonPoints: NonNullable<Point[]> = []
|
|
74
|
+
const perLayerPoints: { layers: number[]; point: Point }[] = []
|
|
75
|
+
|
|
76
|
+
for (const point of points) {
|
|
77
|
+
const layer = point.layer!
|
|
78
|
+
if (layer.startsWith("z")) {
|
|
79
|
+
const rest = layer.slice(1)
|
|
80
|
+
if (rest) {
|
|
81
|
+
const layers = rest
|
|
82
|
+
.split(",")
|
|
83
|
+
.map((part) => Number.parseInt(part, 10))
|
|
84
|
+
.filter((value) => !Number.isNaN(value))
|
|
85
|
+
if (layers.length) {
|
|
86
|
+
perLayerPoints.push({ layers, point })
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
commonPoints.push(point)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const allCombos: number[][] = [[]]
|
|
95
|
+
for (const z of sortedZ) {
|
|
96
|
+
const withZ = allCombos.map((combo) => [...combo, z])
|
|
97
|
+
allCombos.push(...withZ)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const combo of allCombos.filter((c) => c.length > 0)) {
|
|
101
|
+
const key = `z${combo.join(",")}`
|
|
102
|
+
|
|
103
|
+
const layerRects: NonNullable<Rect[]> = [...commonRects]
|
|
104
|
+
const layerLines: NonNullable<Line[]> = [...commonLines]
|
|
105
|
+
const layerPoints: NonNullable<Point[]> = [...commonPoints]
|
|
106
|
+
|
|
107
|
+
const intersects = (layers: number[]) =>
|
|
108
|
+
layers.some((layer) => combo.includes(layer))
|
|
109
|
+
|
|
110
|
+
for (const { layers, rect } of perLayerRects) {
|
|
111
|
+
if (intersects(layers)) layerRects.push(rect)
|
|
112
|
+
}
|
|
113
|
+
for (const { layers, line } of perLayerLines) {
|
|
114
|
+
if (intersects(layers)) layerLines.push(line)
|
|
115
|
+
}
|
|
116
|
+
for (const { layers, point } of perLayerPoints) {
|
|
117
|
+
if (intersects(layers)) layerPoints.push(point)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
result.set(key, {
|
|
121
|
+
title: `${graphics.title ?? ""} - z${combo.join(",")}`,
|
|
122
|
+
coordinateSystem: graphics.coordinateSystem,
|
|
123
|
+
rects: layerRects,
|
|
124
|
+
lines: layerLines,
|
|
125
|
+
points: layerPoints,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Rect } from "graphics-debug"
|
|
2
|
+
import type { CapacityMeshNode } from "lib/types/capacity-mesh-types"
|
|
3
|
+
import { getColorForZLayer } from "lib/utils/getColorForZLayer"
|
|
4
|
+
|
|
5
|
+
export const makeCapacityMeshNodeWithLayerInfo = (
|
|
6
|
+
nodes: CapacityMeshNode[],
|
|
7
|
+
): Map<string, Rect[]> => {
|
|
8
|
+
const map = new Map<string, Rect[]>()
|
|
9
|
+
|
|
10
|
+
for (const node of nodes) {
|
|
11
|
+
if (!node.availableZ.length) continue
|
|
12
|
+
const key = node.availableZ.join(",")
|
|
13
|
+
const colors = getColorForZLayer(node.availableZ)
|
|
14
|
+
const rect: Rect = {
|
|
15
|
+
center: node.center,
|
|
16
|
+
width: node.width,
|
|
17
|
+
height: node.height,
|
|
18
|
+
layer: `z${key}`,
|
|
19
|
+
stroke: "black",
|
|
20
|
+
fill: node._containsObstacle ? "red" : colors.fill,
|
|
21
|
+
label: "node",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const existing = map.get(key)
|
|
25
|
+
if (existing) {
|
|
26
|
+
existing.push(rect)
|
|
27
|
+
} else {
|
|
28
|
+
map.set(key, [rect])
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return map
|
|
33
|
+
}
|