@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
package/lib/RectDiffPipeline.ts
CHANGED
|
@@ -60,7 +60,6 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
override initialVisualize(): GraphicsObject {
|
|
63
|
-
console.log("RectDiffPipeline - initialVisualize")
|
|
64
63
|
const graphics = createBaseVisualization(
|
|
65
64
|
this.inputProblem.simpleRouteJson,
|
|
66
65
|
"RectDiffPipeline - Initial",
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
2
|
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
-
import type { CapacityMeshNode } from "
|
|
3
|
+
import type { CapacityMeshNode, RTreeRect } from "lib/types/capacity-mesh-types"
|
|
4
4
|
import { expandRectFromSeed } from "../../utils/expandRectFromSeed"
|
|
5
5
|
import { finalizeRects } from "../../utils/finalizeRects"
|
|
6
6
|
import { allLayerNode } from "../../utils/buildHardPlacedByLayer"
|
|
7
7
|
import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
|
|
8
8
|
import { rectsToMeshNodes } from "./rectsToMeshNodes"
|
|
9
9
|
import type { XYRect, Candidate3D, Placed3D } from "../../rectdiff-types"
|
|
10
|
-
import type { SimpleRouteJson } from "
|
|
10
|
+
import type { SimpleRouteJson } from "lib/types/srj-types"
|
|
11
|
+
import {
|
|
12
|
+
buildZIndexMap,
|
|
13
|
+
obstacleToXYRect,
|
|
14
|
+
obstacleZs,
|
|
15
|
+
} from "../RectDiffSeedingSolver/layers"
|
|
16
|
+
import RBush from "rbush"
|
|
17
|
+
import { rectToTree } from "../../utils/rectToTree"
|
|
18
|
+
import { sameTreeRect } from "../../utils/sameTreeRect"
|
|
11
19
|
|
|
12
20
|
export type RectDiffExpansionSolverSnapshot = {
|
|
13
21
|
srj: SimpleRouteJson
|
|
@@ -19,12 +27,10 @@ export type RectDiffExpansionSolverSnapshot = {
|
|
|
19
27
|
// the engine only uses gridSizes here, other options are ignored
|
|
20
28
|
[key: string]: any
|
|
21
29
|
}
|
|
22
|
-
obstaclesByLayer: XYRect[][]
|
|
23
30
|
boardVoidRects: XYRect[]
|
|
24
31
|
gridIndex: number
|
|
25
32
|
candidates: Candidate3D[]
|
|
26
33
|
placed: Placed3D[]
|
|
27
|
-
placedByLayer: XYRect[][]
|
|
28
34
|
expansionIndex: number
|
|
29
35
|
edgeAnalysisDone: boolean
|
|
30
36
|
totalSeedsThisGrid: number
|
|
@@ -33,6 +39,7 @@ export type RectDiffExpansionSolverSnapshot = {
|
|
|
33
39
|
|
|
34
40
|
export type RectDiffExpansionSolverInput = {
|
|
35
41
|
initialSnapshot: RectDiffExpansionSolverSnapshot
|
|
42
|
+
obstacleIndexByLayer: Array<RBush<RTreeRect>>
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
/**
|
|
@@ -52,12 +59,11 @@ export class RectDiffExpansionSolver extends BaseSolver {
|
|
|
52
59
|
// the engine only uses gridSizes here, other options are ignored
|
|
53
60
|
[key: string]: any
|
|
54
61
|
}
|
|
55
|
-
private obstaclesByLayer!: XYRect[][]
|
|
56
62
|
private boardVoidRects!: XYRect[]
|
|
57
63
|
private gridIndex!: number
|
|
58
64
|
private candidates!: Candidate3D[]
|
|
59
65
|
private placed!: Placed3D[]
|
|
60
|
-
private
|
|
66
|
+
private placedIndexByLayer!: Array<RBush<RTreeRect>>
|
|
61
67
|
private expansionIndex!: number
|
|
62
68
|
private edgeAnalysisDone!: boolean
|
|
63
69
|
private totalSeedsThisGrid!: number
|
|
@@ -75,6 +81,44 @@ export class RectDiffExpansionSolver extends BaseSolver {
|
|
|
75
81
|
this.stats = {
|
|
76
82
|
gridIndex: this.gridIndex,
|
|
77
83
|
}
|
|
84
|
+
|
|
85
|
+
if (this.input.obstacleIndexByLayer) {
|
|
86
|
+
} else {
|
|
87
|
+
const { zIndexByName } = buildZIndexMap(this.srj)
|
|
88
|
+
this.input.obstacleIndexByLayer = Array.from(
|
|
89
|
+
{ length: this.layerCount },
|
|
90
|
+
() => new RBush<RTreeRect>(),
|
|
91
|
+
)
|
|
92
|
+
const insertObstacle = (rect: XYRect, z: number) => {
|
|
93
|
+
const tree = this.input.obstacleIndexByLayer[z]
|
|
94
|
+
if (tree) tree.insert(rectToTree(rect))
|
|
95
|
+
}
|
|
96
|
+
for (const voidRect of this.boardVoidRects ?? []) {
|
|
97
|
+
for (let z = 0; z < this.layerCount; z++) insertObstacle(voidRect, z)
|
|
98
|
+
}
|
|
99
|
+
for (const obstacle of this.srj.obstacles ?? []) {
|
|
100
|
+
const rect = obstacleToXYRect(obstacle as any)
|
|
101
|
+
if (!rect) continue
|
|
102
|
+
const zLayers =
|
|
103
|
+
obstacle.zLayers?.length && obstacle.zLayers.length > 0
|
|
104
|
+
? obstacle.zLayers
|
|
105
|
+
: obstacleZs(obstacle as any, zIndexByName)
|
|
106
|
+
zLayers.forEach((z) => {
|
|
107
|
+
if (z >= 0 && z < this.layerCount) insertObstacle(rect, z)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.placedIndexByLayer = Array.from(
|
|
113
|
+
{ length: this.layerCount },
|
|
114
|
+
() => new RBush<RTreeRect>(),
|
|
115
|
+
)
|
|
116
|
+
for (const placement of this.placed ?? []) {
|
|
117
|
+
for (const z of placement.zLayers) {
|
|
118
|
+
const tree = this.placedIndexByLayer[z]
|
|
119
|
+
if (tree) tree.insert(rectToTree(placement.rect))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
78
122
|
}
|
|
79
123
|
|
|
80
124
|
override _step() {
|
|
@@ -107,7 +151,8 @@ export class RectDiffExpansionSolver extends BaseSolver {
|
|
|
107
151
|
// HARD blockers only: obstacles on p.zLayers + full-stack nodes
|
|
108
152
|
const hardBlockers: XYRect[] = []
|
|
109
153
|
for (const z of p.zLayers) {
|
|
110
|
-
|
|
154
|
+
const obstacleTree = this.input.obstacleIndexByLayer[z]
|
|
155
|
+
if (obstacleTree) hardBlockers.push(...obstacleTree.all())
|
|
111
156
|
hardBlockers.push(...(hardPlacedByLayer[z] ?? []))
|
|
112
157
|
}
|
|
113
158
|
|
|
@@ -127,9 +172,11 @@ export class RectDiffExpansionSolver extends BaseSolver {
|
|
|
127
172
|
// Update placement + per-layer index (replace old rect object)
|
|
128
173
|
this.placed[idx] = { rect: expanded, zLayers: p.zLayers }
|
|
129
174
|
for (const z of p.zLayers) {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
175
|
+
const tree = this.placedIndexByLayer[z]
|
|
176
|
+
if (tree) {
|
|
177
|
+
tree.remove(rectToTree(oldRect), sameTreeRect)
|
|
178
|
+
tree.insert(rectToTree(expanded))
|
|
179
|
+
}
|
|
133
180
|
}
|
|
134
181
|
|
|
135
182
|
// Carve overlapped soft neighbors (respect full-stack nodes)
|
|
@@ -137,8 +184,8 @@ export class RectDiffExpansionSolver extends BaseSolver {
|
|
|
137
184
|
{
|
|
138
185
|
layerCount: this.layerCount,
|
|
139
186
|
placed: this.placed,
|
|
140
|
-
placedByLayer: this.placedByLayer,
|
|
141
187
|
options: this.options,
|
|
188
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
142
189
|
},
|
|
143
190
|
idx,
|
|
144
191
|
)
|
|
@@ -152,7 +199,7 @@ export class RectDiffExpansionSolver extends BaseSolver {
|
|
|
152
199
|
|
|
153
200
|
const rects = finalizeRects({
|
|
154
201
|
placed: this.placed,
|
|
155
|
-
|
|
202
|
+
srj: this.srj,
|
|
156
203
|
boardVoidRects: this.boardVoidRects,
|
|
157
204
|
})
|
|
158
205
|
this._meshNodes = rectsToMeshNodes(rects)
|
|
@@ -3,12 +3,14 @@ import {
|
|
|
3
3
|
definePipelineStep,
|
|
4
4
|
type PipelineStep,
|
|
5
5
|
} from "@tscircuit/solver-utils"
|
|
6
|
-
import type { SimpleRouteJson } from "
|
|
7
|
-
import type { GridFill3DOptions } from "
|
|
8
|
-
import type { CapacityMeshNode } from "
|
|
9
|
-
import { RectDiffSeedingSolver } from "
|
|
10
|
-
import { RectDiffExpansionSolver } from "
|
|
6
|
+
import type { SimpleRouteJson } from "lib/types/srj-types"
|
|
7
|
+
import type { GridFill3DOptions, XYRect } from "lib/rectdiff-types"
|
|
8
|
+
import type { CapacityMeshNode, RTreeRect } from "lib/types/capacity-mesh-types"
|
|
9
|
+
import { RectDiffSeedingSolver } from "lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver"
|
|
10
|
+
import { RectDiffExpansionSolver } from "lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver"
|
|
11
11
|
import type { GraphicsObject } from "graphics-debug"
|
|
12
|
+
import RBush from "rbush"
|
|
13
|
+
import { buildObstacleIndexes } from "./buildObstacleIndexes"
|
|
12
14
|
|
|
13
15
|
export type RectDiffGridSolverPipelineInput = {
|
|
14
16
|
simpleRouteJson: SimpleRouteJson
|
|
@@ -18,6 +20,17 @@ export type RectDiffGridSolverPipelineInput = {
|
|
|
18
20
|
export class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGridSolverPipelineInput> {
|
|
19
21
|
rectDiffSeedingSolver?: RectDiffSeedingSolver
|
|
20
22
|
rectDiffExpansionSolver?: RectDiffExpansionSolver
|
|
23
|
+
private boardVoidRects?: XYRect[]
|
|
24
|
+
private obstacleIndexByLayer: Array<RBush<RTreeRect>>
|
|
25
|
+
|
|
26
|
+
constructor(inputProblem: RectDiffGridSolverPipelineInput) {
|
|
27
|
+
super(inputProblem)
|
|
28
|
+
const { obstacleIndexByLayer, boardVoidRects } = buildObstacleIndexes(
|
|
29
|
+
inputProblem.simpleRouteJson,
|
|
30
|
+
)
|
|
31
|
+
this.obstacleIndexByLayer = obstacleIndexByLayer
|
|
32
|
+
this.boardVoidRects = boardVoidRects
|
|
33
|
+
}
|
|
21
34
|
|
|
22
35
|
override pipelineDef: PipelineStep<any>[] = [
|
|
23
36
|
definePipelineStep(
|
|
@@ -27,6 +40,8 @@ export class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGridS
|
|
|
27
40
|
{
|
|
28
41
|
simpleRouteJson: pipeline.inputProblem.simpleRouteJson,
|
|
29
42
|
gridOptions: pipeline.inputProblem.gridOptions,
|
|
43
|
+
obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
|
|
44
|
+
boardVoidRects: pipeline.boardVoidRects,
|
|
30
45
|
},
|
|
31
46
|
],
|
|
32
47
|
),
|
|
@@ -35,7 +50,11 @@ export class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGridS
|
|
|
35
50
|
RectDiffExpansionSolver,
|
|
36
51
|
(pipeline: RectDiffGridSolverPipeline) => [
|
|
37
52
|
{
|
|
38
|
-
initialSnapshot:
|
|
53
|
+
initialSnapshot: {
|
|
54
|
+
...pipeline.rectDiffSeedingSolver!.getOutput(),
|
|
55
|
+
boardVoidRects: pipeline.boardVoidRects ?? [],
|
|
56
|
+
},
|
|
57
|
+
obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
|
|
39
58
|
},
|
|
40
59
|
],
|
|
41
60
|
),
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { SimpleRouteJson } from "lib/types/srj-types"
|
|
2
|
+
import RBush from "rbush"
|
|
3
|
+
import { computeInverseRects } from "lib/solvers/RectDiffSeedingSolver/computeInverseRects"
|
|
4
|
+
import {
|
|
5
|
+
buildZIndexMap,
|
|
6
|
+
obstacleToXYRect,
|
|
7
|
+
obstacleZs,
|
|
8
|
+
} from "lib/solvers/RectDiffSeedingSolver/layers"
|
|
9
|
+
import type { XYRect } from "lib/rectdiff-types"
|
|
10
|
+
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
11
|
+
|
|
12
|
+
export const buildObstacleIndexes = (
|
|
13
|
+
srj: SimpleRouteJson,
|
|
14
|
+
): {
|
|
15
|
+
obstacleIndexByLayer: Array<RBush<RTreeRect>>
|
|
16
|
+
boardVoidRects: XYRect[]
|
|
17
|
+
} => {
|
|
18
|
+
const { layerNames, zIndexByName } = buildZIndexMap(srj)
|
|
19
|
+
const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1)
|
|
20
|
+
const bounds: XYRect = {
|
|
21
|
+
x: srj.bounds.minX,
|
|
22
|
+
y: srj.bounds.minY,
|
|
23
|
+
width: srj.bounds.maxX - srj.bounds.minX,
|
|
24
|
+
height: srj.bounds.maxY - srj.bounds.minY,
|
|
25
|
+
}
|
|
26
|
+
const obstacleIndexByLayer = Array.from(
|
|
27
|
+
{ length: layerCount },
|
|
28
|
+
() => new RBush<RTreeRect>(),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const insertObstacle = (rect: XYRect, z: number) => {
|
|
32
|
+
const treeRect = {
|
|
33
|
+
...rect,
|
|
34
|
+
minX: rect.x,
|
|
35
|
+
minY: rect.y,
|
|
36
|
+
maxX: rect.x + rect.width,
|
|
37
|
+
maxY: rect.y + rect.height,
|
|
38
|
+
}
|
|
39
|
+
obstacleIndexByLayer[z]?.insert(treeRect)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let boardVoidRects: XYRect[] = []
|
|
43
|
+
if (srj.outline && srj.outline.length > 2) {
|
|
44
|
+
boardVoidRects = computeInverseRects(bounds, srj.outline as any)
|
|
45
|
+
for (const voidRect of boardVoidRects) {
|
|
46
|
+
for (let z = 0; z < layerCount; z++) insertObstacle(voidRect, z)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const obstacle of srj.obstacles ?? []) {
|
|
51
|
+
const rect = obstacleToXYRect(obstacle as any)
|
|
52
|
+
if (!rect) continue
|
|
53
|
+
const zLayers = obstacleZs(obstacle as any, zIndexByName)
|
|
54
|
+
const invalidZs = zLayers.filter((z) => z < 0 || z >= layerCount)
|
|
55
|
+
if (invalidZs.length) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`RectDiff: obstacle uses z-layer indices ${invalidZs.join(",")} outside 0-${layerCount - 1}`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
(!obstacle.zLayers || obstacle.zLayers.length === 0) &&
|
|
62
|
+
zLayers.length
|
|
63
|
+
) {
|
|
64
|
+
obstacle.zLayers = zLayers
|
|
65
|
+
}
|
|
66
|
+
for (const z of zLayers) insertObstacle(rect, z)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { obstacleIndexByLayer, boardVoidRects }
|
|
70
|
+
}
|
|
@@ -16,12 +16,17 @@ import { computeCandidates3D } from "./computeCandidates3D"
|
|
|
16
16
|
import { computeEdgeCandidates3D } from "./computeEdgeCandidates3D"
|
|
17
17
|
import { longestFreeSpanAroundZ } from "./longestFreeSpanAroundZ"
|
|
18
18
|
import { allLayerNode } from "../../utils/buildHardPlacedByLayer"
|
|
19
|
-
import { isFullyOccupiedAtPoint } from "
|
|
19
|
+
import { isFullyOccupiedAtPoint } from "lib/utils/isFullyOccupiedAtPoint"
|
|
20
20
|
import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
|
|
21
|
+
import { getColorForZLayer } from "lib/utils/getColorForZLayer"
|
|
22
|
+
import RBush from "rbush"
|
|
23
|
+
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
21
24
|
|
|
22
25
|
export type RectDiffSeedingSolverInput = {
|
|
23
26
|
simpleRouteJson: SimpleRouteJson
|
|
27
|
+
obstacleIndexByLayer: Array<RBush<RTreeRect>>
|
|
24
28
|
gridOptions?: Partial<GridFill3DOptions>
|
|
29
|
+
boardVoidRects?: XYRect[]
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
/**
|
|
@@ -43,12 +48,11 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
43
48
|
gridSizes: number[]
|
|
44
49
|
maxMultiLayerSpan: number | undefined
|
|
45
50
|
}
|
|
46
|
-
private
|
|
47
|
-
private boardVoidRects!: XYRect[]
|
|
51
|
+
private boardVoidRects?: XYRect[]
|
|
48
52
|
private gridIndex!: number
|
|
49
53
|
private candidates!: Candidate3D[]
|
|
50
54
|
private placed!: Placed3D[]
|
|
51
|
-
private
|
|
55
|
+
private placedIndexByLayer!: Array<RBush<RTreeRect>>
|
|
52
56
|
private expansionIndex!: number
|
|
53
57
|
private edgeAnalysisDone!: boolean
|
|
54
58
|
private totalSeedsThisGrid!: number
|
|
@@ -72,42 +76,6 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
72
76
|
height: srj.bounds.maxY - srj.bounds.minY,
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
const obstaclesByLayer: XYRect[][] = Array.from(
|
|
76
|
-
{ length: layerCount },
|
|
77
|
-
() => [],
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
let boardVoidRects: XYRect[] = []
|
|
81
|
-
if (srj.outline && srj.outline.length > 2) {
|
|
82
|
-
boardVoidRects = computeInverseRects(bounds, srj.outline as any)
|
|
83
|
-
for (const voidR of boardVoidRects) {
|
|
84
|
-
for (let z = 0; z < layerCount; z++) {
|
|
85
|
-
obstaclesByLayer[z]!.push(voidR)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (const obstacle of srj.obstacles ?? []) {
|
|
91
|
-
const rect = obstacleToXYRect(obstacle as any)
|
|
92
|
-
if (!rect) continue
|
|
93
|
-
const zLayers = obstacleZs(obstacle as any, zIndexByName)
|
|
94
|
-
const invalidZs = zLayers.filter((z) => z < 0 || z >= layerCount)
|
|
95
|
-
if (invalidZs.length) {
|
|
96
|
-
throw new Error(
|
|
97
|
-
`RectDiff: obstacle uses z-layer indices ${invalidZs.join(",")} outside 0-${
|
|
98
|
-
layerCount - 1
|
|
99
|
-
}`,
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
if (
|
|
103
|
-
(!obstacle.zLayers || obstacle.zLayers.length === 0) &&
|
|
104
|
-
zLayers.length
|
|
105
|
-
) {
|
|
106
|
-
obstacle.zLayers = zLayers
|
|
107
|
-
}
|
|
108
|
-
for (const z of zLayers) obstaclesByLayer[z]!.push(rect)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
79
|
const trace = Math.max(0.01, srj.minTraceWidth || 0.15)
|
|
112
80
|
const defaults: Required<
|
|
113
81
|
Omit<GridFill3DOptions, "gridSizes" | "maxMultiLayerSpan">
|
|
@@ -137,22 +105,19 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
137
105
|
computeDefaultGridSizes(bounds),
|
|
138
106
|
}
|
|
139
107
|
|
|
140
|
-
const placedByLayer: XYRect[][] = Array.from(
|
|
141
|
-
{ length: layerCount },
|
|
142
|
-
() => [],
|
|
143
|
-
)
|
|
144
|
-
|
|
145
108
|
this.srj = srj
|
|
146
109
|
this.layerNames = layerNames
|
|
147
110
|
this.layerCount = layerCount
|
|
148
111
|
this.bounds = bounds
|
|
149
112
|
this.options = options
|
|
150
|
-
this.
|
|
151
|
-
this.boardVoidRects = boardVoidRects
|
|
113
|
+
this.boardVoidRects = this.input.boardVoidRects
|
|
152
114
|
this.gridIndex = 0
|
|
153
115
|
this.candidates = []
|
|
154
116
|
this.placed = []
|
|
155
|
-
this.
|
|
117
|
+
this.placedIndexByLayer = Array.from(
|
|
118
|
+
{ length: layerCount },
|
|
119
|
+
() => new RBush<RTreeRect>(),
|
|
120
|
+
)
|
|
156
121
|
this.expansionIndex = 0
|
|
157
122
|
this.edgeAnalysisDone = false
|
|
158
123
|
this.totalSeedsThisGrid = 0
|
|
@@ -198,9 +163,9 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
198
163
|
bounds: this.bounds,
|
|
199
164
|
gridSize: grid,
|
|
200
165
|
layerCount: this.layerCount,
|
|
201
|
-
obstaclesByLayer: this.obstaclesByLayer,
|
|
202
|
-
placedByLayer: this.placedByLayer,
|
|
203
166
|
hardPlacedByLayer,
|
|
167
|
+
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
168
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
204
169
|
})
|
|
205
170
|
this.totalSeedsThisGrid = this.candidates.length
|
|
206
171
|
this.consumedSeedsThisGrid = 0
|
|
@@ -220,8 +185,8 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
220
185
|
bounds: this.bounds,
|
|
221
186
|
minSize,
|
|
222
187
|
layerCount: this.layerCount,
|
|
223
|
-
|
|
224
|
-
|
|
188
|
+
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
189
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
225
190
|
hardPlacedByLayer,
|
|
226
191
|
})
|
|
227
192
|
this.edgeAnalysisDone = true
|
|
@@ -247,8 +212,8 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
247
212
|
layerCount: this.layerCount,
|
|
248
213
|
minSpan: minMulti.minLayers,
|
|
249
214
|
maxSpan: maxMultiLayerSpan,
|
|
250
|
-
|
|
251
|
-
|
|
215
|
+
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
216
|
+
additionalBlockersByLayer: hardPlacedByLayer,
|
|
252
217
|
})
|
|
253
218
|
|
|
254
219
|
const attempts: Array<{
|
|
@@ -276,8 +241,8 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
276
241
|
// HARD blockers only: obstacles on those layers + full-stack nodes
|
|
277
242
|
const hardBlockers: XYRect[] = []
|
|
278
243
|
for (const z of attempt.layers) {
|
|
279
|
-
|
|
280
|
-
|
|
244
|
+
const obstacleLayer = this.input.obstacleIndexByLayer[z]
|
|
245
|
+
if (obstacleLayer) hardBlockers.push(...obstacleLayer.all())
|
|
281
246
|
if (hardPlacedByLayer[z]) hardBlockers.push(...hardPlacedByLayer[z]!)
|
|
282
247
|
}
|
|
283
248
|
|
|
@@ -296,15 +261,26 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
296
261
|
// Place the new node
|
|
297
262
|
const placed: Placed3D = { rect, zLayers: [...attempt.layers] }
|
|
298
263
|
const newIndex = this.placed.push(placed) - 1
|
|
299
|
-
for (const z of attempt.layers)
|
|
264
|
+
for (const z of attempt.layers) {
|
|
265
|
+
const idx = this.placedIndexByLayer[z]
|
|
266
|
+
if (idx) {
|
|
267
|
+
idx.insert({
|
|
268
|
+
...rect,
|
|
269
|
+
minX: rect.x,
|
|
270
|
+
minY: rect.y,
|
|
271
|
+
maxX: rect.x + rect.width,
|
|
272
|
+
maxY: rect.y + rect.height,
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
}
|
|
300
276
|
|
|
301
277
|
// New: carve overlapped soft nodes
|
|
302
278
|
resizeSoftOverlaps(
|
|
303
279
|
{
|
|
304
280
|
layerCount: this.layerCount,
|
|
305
281
|
placed: this.placed,
|
|
306
|
-
placedByLayer: this.placedByLayer,
|
|
307
282
|
options: this.options,
|
|
283
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
308
284
|
},
|
|
309
285
|
newIndex,
|
|
310
286
|
)
|
|
@@ -312,14 +288,12 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
312
288
|
// New: relax candidate culling — only drop seeds that became fully occupied
|
|
313
289
|
this.candidates = this.candidates.filter(
|
|
314
290
|
(c) =>
|
|
315
|
-
!isFullyOccupiedAtPoint(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
{ x: c.x, y: c.y },
|
|
322
|
-
),
|
|
291
|
+
!isFullyOccupiedAtPoint({
|
|
292
|
+
layerCount: this.layerCount,
|
|
293
|
+
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
294
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
295
|
+
point: { x: c.x, y: c.y },
|
|
296
|
+
}),
|
|
323
297
|
)
|
|
324
298
|
|
|
325
299
|
return // processed one candidate
|
|
@@ -352,12 +326,10 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
352
326
|
layerCount: this.layerCount,
|
|
353
327
|
bounds: this.bounds,
|
|
354
328
|
options: this.options,
|
|
355
|
-
obstaclesByLayer: this.obstaclesByLayer,
|
|
356
329
|
boardVoidRects: this.boardVoidRects,
|
|
357
330
|
gridIndex: this.gridIndex,
|
|
358
331
|
candidates: this.candidates,
|
|
359
332
|
placed: this.placed,
|
|
360
|
-
placedByLayer: this.placedByLayer,
|
|
361
333
|
expansionIndex: this.expansionIndex,
|
|
362
334
|
edgeAnalysisDone: this.edgeAnalysisDone,
|
|
363
335
|
totalSeedsThisGrid: this.totalSeedsThisGrid,
|
|
@@ -365,23 +337,6 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
365
337
|
}
|
|
366
338
|
}
|
|
367
339
|
|
|
368
|
-
/** Get color based on z layer for visualization. */
|
|
369
|
-
private getColorForZLayer(zLayers: number[]): {
|
|
370
|
-
fill: string
|
|
371
|
-
stroke: string
|
|
372
|
-
} {
|
|
373
|
-
const minZ = Math.min(...zLayers)
|
|
374
|
-
const colors = [
|
|
375
|
-
{ fill: "#dbeafe", stroke: "#3b82f6" },
|
|
376
|
-
{ fill: "#fef3c7", stroke: "#f59e0b" },
|
|
377
|
-
{ fill: "#d1fae5", stroke: "#10b981" },
|
|
378
|
-
{ fill: "#e9d5ff", stroke: "#a855f7" },
|
|
379
|
-
{ fill: "#fed7aa", stroke: "#f97316" },
|
|
380
|
-
{ fill: "#fecaca", stroke: "#ef4444" },
|
|
381
|
-
] as const
|
|
382
|
-
return colors[minZ % colors.length]!
|
|
383
|
-
}
|
|
384
|
-
|
|
385
340
|
/** Visualization focused on the grid seeding phase. */
|
|
386
341
|
override visualize(): GraphicsObject {
|
|
387
342
|
const rects: NonNullable<GraphicsObject["rects"]> = []
|
|
@@ -489,7 +444,7 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
489
444
|
// current placements (streaming) during grid fill
|
|
490
445
|
if (this.placed?.length) {
|
|
491
446
|
for (const placement of this.placed) {
|
|
492
|
-
const colors =
|
|
447
|
+
const colors = getColorForZLayer(placement.zLayers)
|
|
493
448
|
rects.push({
|
|
494
449
|
center: {
|
|
495
450
|
x: placement.rect.x + placement.rect.width / 2,
|
|
@@ -2,6 +2,8 @@ import type { Candidate3D, XYRect } from "../../rectdiff-types"
|
|
|
2
2
|
import { EPS, distancePointToRectEdges } from "../../utils/rectdiff-geometry"
|
|
3
3
|
import { isFullyOccupiedAtPoint } from "../../utils/isFullyOccupiedAtPoint"
|
|
4
4
|
import { longestFreeSpanAroundZ } from "./longestFreeSpanAroundZ"
|
|
5
|
+
import type RBush from "rbush"
|
|
6
|
+
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Compute candidate seed points for a given grid size.
|
|
@@ -10,16 +12,16 @@ export function computeCandidates3D(params: {
|
|
|
10
12
|
bounds: XYRect
|
|
11
13
|
gridSize: number
|
|
12
14
|
layerCount: number
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
obstacleIndexByLayer: Array<RBush<RTreeRect> | undefined>
|
|
16
|
+
placedIndexByLayer: Array<RBush<RTreeRect> | undefined>
|
|
15
17
|
hardPlacedByLayer: XYRect[][]
|
|
16
18
|
}): Candidate3D[] {
|
|
17
19
|
const {
|
|
18
20
|
bounds,
|
|
19
21
|
gridSize,
|
|
20
22
|
layerCount,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
obstacleIndexByLayer,
|
|
24
|
+
placedIndexByLayer,
|
|
23
25
|
hardPlacedByLayer,
|
|
24
26
|
} = params
|
|
25
27
|
const out = new Map<string, Candidate3D>() // key by (x,y)
|
|
@@ -38,14 +40,12 @@ export function computeCandidates3D(params: {
|
|
|
38
40
|
|
|
39
41
|
// New rule: Only drop if EVERY layer is occupied (by obstacle or node)
|
|
40
42
|
if (
|
|
41
|
-
isFullyOccupiedAtPoint(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{ x, y },
|
|
48
|
-
)
|
|
43
|
+
isFullyOccupiedAtPoint({
|
|
44
|
+
layerCount,
|
|
45
|
+
obstacleIndexByLayer,
|
|
46
|
+
placedIndexByLayer,
|
|
47
|
+
point: { x, y },
|
|
48
|
+
})
|
|
49
49
|
)
|
|
50
50
|
continue
|
|
51
51
|
|
|
@@ -60,8 +60,8 @@ export function computeCandidates3D(params: {
|
|
|
60
60
|
layerCount,
|
|
61
61
|
minSpan: 1,
|
|
62
62
|
maxSpan: undefined,
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
obstacleIndexByLayer,
|
|
64
|
+
additionalBlockersByLayer: hardPlacedByLayer,
|
|
65
65
|
})
|
|
66
66
|
if (s.length > bestSpan.length) {
|
|
67
67
|
bestSpan = s
|
|
@@ -74,7 +74,7 @@ export function computeCandidates3D(params: {
|
|
|
74
74
|
|
|
75
75
|
// Distance heuristic against hard blockers only (obstacles + full-stack)
|
|
76
76
|
const hardAtZ = [
|
|
77
|
-
...(
|
|
77
|
+
...(obstacleIndexByLayer[anchorZ]?.all() ?? []),
|
|
78
78
|
...(hardPlacedByLayer[anchorZ] ?? []),
|
|
79
79
|
]
|
|
80
80
|
const d = Math.min(
|
|
@@ -2,6 +2,8 @@ import type { Candidate3D, XYRect } from "../../rectdiff-types"
|
|
|
2
2
|
import { EPS, distancePointToRectEdges } from "../../utils/rectdiff-geometry"
|
|
3
3
|
import { isFullyOccupiedAtPoint } from "../../utils/isFullyOccupiedAtPoint"
|
|
4
4
|
import { longestFreeSpanAroundZ } from "./longestFreeSpanAroundZ"
|
|
5
|
+
import type RBush from "rbush"
|
|
6
|
+
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Compute exact uncovered segments along a 1D line.
|
|
@@ -79,16 +81,16 @@ export function computeEdgeCandidates3D(params: {
|
|
|
79
81
|
bounds: XYRect
|
|
80
82
|
minSize: number
|
|
81
83
|
layerCount: number
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
obstacleIndexByLayer: Array<RBush<RTreeRect> | undefined>
|
|
85
|
+
placedIndexByLayer: Array<RBush<RTreeRect> | undefined>
|
|
84
86
|
hardPlacedByLayer: XYRect[][]
|
|
85
87
|
}): Candidate3D[] {
|
|
86
88
|
const {
|
|
87
89
|
bounds,
|
|
88
90
|
minSize,
|
|
89
91
|
layerCount,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
obstacleIndexByLayer,
|
|
93
|
+
placedIndexByLayer,
|
|
92
94
|
hardPlacedByLayer,
|
|
93
95
|
} = params
|
|
94
96
|
|
|
@@ -100,14 +102,12 @@ export function computeEdgeCandidates3D(params: {
|
|
|
100
102
|
`${p.z}|${p.x.toFixed(6)}|${p.y.toFixed(6)}`
|
|
101
103
|
|
|
102
104
|
function fullyOcc(p: { x: number; y: number }) {
|
|
103
|
-
return isFullyOccupiedAtPoint(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
p,
|
|
110
|
-
)
|
|
105
|
+
return isFullyOccupiedAtPoint({
|
|
106
|
+
layerCount,
|
|
107
|
+
obstacleIndexByLayer,
|
|
108
|
+
placedIndexByLayer,
|
|
109
|
+
point: p,
|
|
110
|
+
})
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
function pushIfFree(p: { x: number; y: number; z: number }) {
|
|
@@ -123,7 +123,7 @@ export function computeEdgeCandidates3D(params: {
|
|
|
123
123
|
|
|
124
124
|
// Distance uses obstacles + hard nodes (soft nodes ignored for ranking)
|
|
125
125
|
const hard = [
|
|
126
|
-
...(
|
|
126
|
+
...(obstacleIndexByLayer[z]?.all() ?? []),
|
|
127
127
|
...(hardPlacedByLayer[z] ?? []),
|
|
128
128
|
]
|
|
129
129
|
const d = Math.min(
|
|
@@ -145,15 +145,15 @@ export function computeEdgeCandidates3D(params: {
|
|
|
145
145
|
layerCount,
|
|
146
146
|
minSpan: 1,
|
|
147
147
|
maxSpan: undefined,
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
obstacleIndexByLayer,
|
|
149
|
+
additionalBlockersByLayer: hardPlacedByLayer,
|
|
150
150
|
})
|
|
151
151
|
out.push({ x, y, z, distance: d, zSpanLen: span.length, isEdgeSeed: true })
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
for (let z = 0; z < layerCount; z++) {
|
|
155
155
|
const blockers = [
|
|
156
|
-
...(
|
|
156
|
+
...(obstacleIndexByLayer[z]?.all() ?? []),
|
|
157
157
|
...(hardPlacedByLayer[z] ?? []),
|
|
158
158
|
]
|
|
159
159
|
|