@tscircuit/rectdiff 0.0.13 → 0.0.14
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 -10
- package/dist/index.js +257 -126
- 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 +39 -68
- 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/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
|
@@ -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,16 @@ 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 RBush from "rbush"
|
|
22
|
+
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
21
23
|
|
|
22
24
|
export type RectDiffSeedingSolverInput = {
|
|
23
25
|
simpleRouteJson: SimpleRouteJson
|
|
26
|
+
obstacleIndexByLayer: Array<RBush<RTreeRect>>
|
|
24
27
|
gridOptions?: Partial<GridFill3DOptions>
|
|
28
|
+
boardVoidRects?: XYRect[]
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/**
|
|
@@ -43,12 +47,11 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
43
47
|
gridSizes: number[]
|
|
44
48
|
maxMultiLayerSpan: number | undefined
|
|
45
49
|
}
|
|
46
|
-
private
|
|
47
|
-
private boardVoidRects!: XYRect[]
|
|
50
|
+
private boardVoidRects?: XYRect[]
|
|
48
51
|
private gridIndex!: number
|
|
49
52
|
private candidates!: Candidate3D[]
|
|
50
53
|
private placed!: Placed3D[]
|
|
51
|
-
private
|
|
54
|
+
private placedIndexByLayer!: Array<RBush<RTreeRect>>
|
|
52
55
|
private expansionIndex!: number
|
|
53
56
|
private edgeAnalysisDone!: boolean
|
|
54
57
|
private totalSeedsThisGrid!: number
|
|
@@ -72,42 +75,6 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
72
75
|
height: srj.bounds.maxY - srj.bounds.minY,
|
|
73
76
|
}
|
|
74
77
|
|
|
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
78
|
const trace = Math.max(0.01, srj.minTraceWidth || 0.15)
|
|
112
79
|
const defaults: Required<
|
|
113
80
|
Omit<GridFill3DOptions, "gridSizes" | "maxMultiLayerSpan">
|
|
@@ -137,22 +104,19 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
137
104
|
computeDefaultGridSizes(bounds),
|
|
138
105
|
}
|
|
139
106
|
|
|
140
|
-
const placedByLayer: XYRect[][] = Array.from(
|
|
141
|
-
{ length: layerCount },
|
|
142
|
-
() => [],
|
|
143
|
-
)
|
|
144
|
-
|
|
145
107
|
this.srj = srj
|
|
146
108
|
this.layerNames = layerNames
|
|
147
109
|
this.layerCount = layerCount
|
|
148
110
|
this.bounds = bounds
|
|
149
111
|
this.options = options
|
|
150
|
-
this.
|
|
151
|
-
this.boardVoidRects = boardVoidRects
|
|
112
|
+
this.boardVoidRects = this.input.boardVoidRects
|
|
152
113
|
this.gridIndex = 0
|
|
153
114
|
this.candidates = []
|
|
154
115
|
this.placed = []
|
|
155
|
-
this.
|
|
116
|
+
this.placedIndexByLayer = Array.from(
|
|
117
|
+
{ length: layerCount },
|
|
118
|
+
() => new RBush<RTreeRect>(),
|
|
119
|
+
)
|
|
156
120
|
this.expansionIndex = 0
|
|
157
121
|
this.edgeAnalysisDone = false
|
|
158
122
|
this.totalSeedsThisGrid = 0
|
|
@@ -198,9 +162,9 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
198
162
|
bounds: this.bounds,
|
|
199
163
|
gridSize: grid,
|
|
200
164
|
layerCount: this.layerCount,
|
|
201
|
-
obstaclesByLayer: this.obstaclesByLayer,
|
|
202
|
-
placedByLayer: this.placedByLayer,
|
|
203
165
|
hardPlacedByLayer,
|
|
166
|
+
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
167
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
204
168
|
})
|
|
205
169
|
this.totalSeedsThisGrid = this.candidates.length
|
|
206
170
|
this.consumedSeedsThisGrid = 0
|
|
@@ -220,8 +184,8 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
220
184
|
bounds: this.bounds,
|
|
221
185
|
minSize,
|
|
222
186
|
layerCount: this.layerCount,
|
|
223
|
-
|
|
224
|
-
|
|
187
|
+
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
188
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
225
189
|
hardPlacedByLayer,
|
|
226
190
|
})
|
|
227
191
|
this.edgeAnalysisDone = true
|
|
@@ -247,8 +211,8 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
247
211
|
layerCount: this.layerCount,
|
|
248
212
|
minSpan: minMulti.minLayers,
|
|
249
213
|
maxSpan: maxMultiLayerSpan,
|
|
250
|
-
|
|
251
|
-
|
|
214
|
+
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
215
|
+
additionalBlockersByLayer: hardPlacedByLayer,
|
|
252
216
|
})
|
|
253
217
|
|
|
254
218
|
const attempts: Array<{
|
|
@@ -276,8 +240,8 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
276
240
|
// HARD blockers only: obstacles on those layers + full-stack nodes
|
|
277
241
|
const hardBlockers: XYRect[] = []
|
|
278
242
|
for (const z of attempt.layers) {
|
|
279
|
-
|
|
280
|
-
|
|
243
|
+
const obstacleLayer = this.input.obstacleIndexByLayer[z]
|
|
244
|
+
if (obstacleLayer) hardBlockers.push(...obstacleLayer.all())
|
|
281
245
|
if (hardPlacedByLayer[z]) hardBlockers.push(...hardPlacedByLayer[z]!)
|
|
282
246
|
}
|
|
283
247
|
|
|
@@ -296,15 +260,26 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
296
260
|
// Place the new node
|
|
297
261
|
const placed: Placed3D = { rect, zLayers: [...attempt.layers] }
|
|
298
262
|
const newIndex = this.placed.push(placed) - 1
|
|
299
|
-
for (const z of attempt.layers)
|
|
263
|
+
for (const z of attempt.layers) {
|
|
264
|
+
const idx = this.placedIndexByLayer[z]
|
|
265
|
+
if (idx) {
|
|
266
|
+
idx.insert({
|
|
267
|
+
...rect,
|
|
268
|
+
minX: rect.x,
|
|
269
|
+
minY: rect.y,
|
|
270
|
+
maxX: rect.x + rect.width,
|
|
271
|
+
maxY: rect.y + rect.height,
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
}
|
|
300
275
|
|
|
301
276
|
// New: carve overlapped soft nodes
|
|
302
277
|
resizeSoftOverlaps(
|
|
303
278
|
{
|
|
304
279
|
layerCount: this.layerCount,
|
|
305
280
|
placed: this.placed,
|
|
306
|
-
placedByLayer: this.placedByLayer,
|
|
307
281
|
options: this.options,
|
|
282
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
308
283
|
},
|
|
309
284
|
newIndex,
|
|
310
285
|
)
|
|
@@ -312,14 +287,12 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
312
287
|
// New: relax candidate culling — only drop seeds that became fully occupied
|
|
313
288
|
this.candidates = this.candidates.filter(
|
|
314
289
|
(c) =>
|
|
315
|
-
!isFullyOccupiedAtPoint(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
{ x: c.x, y: c.y },
|
|
322
|
-
),
|
|
290
|
+
!isFullyOccupiedAtPoint({
|
|
291
|
+
layerCount: this.layerCount,
|
|
292
|
+
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
293
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
294
|
+
point: { x: c.x, y: c.y },
|
|
295
|
+
}),
|
|
323
296
|
)
|
|
324
297
|
|
|
325
298
|
return // processed one candidate
|
|
@@ -352,12 +325,10 @@ export class RectDiffSeedingSolver extends BaseSolver {
|
|
|
352
325
|
layerCount: this.layerCount,
|
|
353
326
|
bounds: this.bounds,
|
|
354
327
|
options: this.options,
|
|
355
|
-
obstaclesByLayer: this.obstaclesByLayer,
|
|
356
328
|
boardVoidRects: this.boardVoidRects,
|
|
357
329
|
gridIndex: this.gridIndex,
|
|
358
330
|
candidates: this.candidates,
|
|
359
331
|
placed: this.placed,
|
|
360
|
-
placedByLayer: this.placedByLayer,
|
|
361
332
|
expansionIndex: this.expansionIndex,
|
|
362
333
|
edgeAnalysisDone: this.edgeAnalysisDone,
|
|
363
334
|
totalSeedsThisGrid: this.totalSeedsThisGrid,
|
|
@@ -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
|
|
|
@@ -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
|