@tscircuit/rectdiff 0.0.28 → 0.0.30
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/.github/workflows/bun-pver-release.yml +24 -45
- package/lib/RectDiffPipeline.ts +0 -46
- package/lib/fixtures/twoNodeExpansionFixture.ts +1 -1
- package/lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts +4 -2
- package/lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts +3 -1
- package/lib/solvers/GapFillSolver/GapFillSolverPipeline.ts +6 -2
- package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +5 -2
- package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +8 -5
- package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +6 -6
- package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +4 -4
- package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +1 -1
- package/lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts +1 -1
- package/lib/solvers/RectDiffSeedingSolver/longestFreeSpanAroundZ.ts +1 -1
- package/lib/types/capacity-mesh-types.ts +1 -1
- package/lib/types/srj-types.ts +0 -1
- package/lib/utils/expandRectFromSeed.ts +1 -1
- package/lib/utils/finalizeRects.ts +1 -1
- package/lib/utils/isFullyOccupiedAtPoint.ts +1 -1
- package/lib/utils/isSelfRect.ts +1 -1
- package/lib/utils/rectToTree.ts +2 -2
- package/lib/utils/renderObstacleClearance.ts +1 -1
- package/lib/utils/resizeSoftOverlaps.ts +1 -1
- package/lib/utils/sameTreeRect.ts +1 -1
- package/lib/utils/searchStrip.ts +1 -1
- package/package.json +12 -8
- package/pages/repro/merge-single-layer-node.page.tsx +17 -0
- package/tests/__snapshots__/board-outline.snap.svg +2 -2
- package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
- package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
- package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +1 -1
- package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +1 -1
- package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
- package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
- package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
- package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
- package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +1 -1
- package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
- package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
- package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +1 -1
- package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
- package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
- package/tests/solver/bugreport20-obstacle-clipping/__snapshots__/bugreport20-obstacle-clipping.snap.svg +1 -1
- package/tests/solver/bugreport21-board-outline/__snapshots__/bugreport21-board-outline.snap.svg +2 -2
- package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
- package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
- package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
- package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +1 -1
- package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
- package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
- package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
- package/tests/solver/no-better-path/__snapshots__/no-better-path.snap.svg +1 -1
- package/tests/solver/repros/merge-single-layer-node/merge-single-layer-node.json +861 -0
- package/tests/solver/{bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.test.ts → repros/merge-single-layer-node/merge-single-layer-node.test.ts} +7 -42
- package/tests/solver/transitivity/__snapshots__/transitivity.snap.svg +2 -2
- package/tsconfig.json +5 -1
- package/vite.config.ts +4 -0
- package/dist/index.d.ts +0 -427
- package/dist/index.js +0 -3319
- package/lib/solvers/AdjacentLayerContainmentMergeSolver/AdjacentLayerContainmentMergeSolver.ts +0 -456
- package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +0 -314
- package/pages/bugreports/bugreport50-multi-support-layer-merge.page.tsx +0 -19
- package/pages/pour.page.tsx +0 -18
- package/test-assets/bugreport49-634662.json +0 -412
- package/tests/outer-layer-containment-merge-solver.test.ts +0 -73
- package/tests/solver/bugreport49-634662/__snapshots__/bugreport49-634662.snap.svg +0 -44
- package/tests/solver/bugreport49-634662/bugreport49-634662.test.ts +0 -130
- package/tests/solver/bugreport50-multi-support-layer-merge/__snapshots__/bugreport50-multi-support-layer-merge.snap.svg +0 -44
- package/tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.json +0 -972
package/lib/solvers/AdjacentLayerContainmentMergeSolver/AdjacentLayerContainmentMergeSolver.ts
DELETED
|
@@ -1,456 +0,0 @@
|
|
|
1
|
-
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
|
-
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
-
import type { XYRect } from "lib/rectdiff-types"
|
|
4
|
-
import type { CapacityMeshNode } from "lib/types/capacity-mesh-types"
|
|
5
|
-
import type { SimpleRouteJson } from "lib/types/srj-types"
|
|
6
|
-
import { getColorForZLayer } from "lib/utils/getColorForZLayer"
|
|
7
|
-
import { EPS, overlaps, subtractRect2D } from "lib/utils/rectdiff-geometry"
|
|
8
|
-
|
|
9
|
-
type AdjacentLayerContainmentMergeSolverInput = {
|
|
10
|
-
meshNodes: CapacityMeshNode[]
|
|
11
|
-
simpleRouteJson: SimpleRouteJson
|
|
12
|
-
minFragmentArea?: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const DEFAULT_MIN_FRAGMENT_AREA = 0.2 ** 2
|
|
16
|
-
|
|
17
|
-
const nodeToRect = (node: CapacityMeshNode): XYRect => ({
|
|
18
|
-
x: node.center.x - node.width / 2,
|
|
19
|
-
y: node.center.y - node.height / 2,
|
|
20
|
-
width: node.width,
|
|
21
|
-
height: node.height,
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const rectArea = (rect: XYRect) => rect.width * rect.height
|
|
25
|
-
|
|
26
|
-
const cloneNode = (node: CapacityMeshNode): CapacityMeshNode => ({
|
|
27
|
-
...node,
|
|
28
|
-
center: { ...node.center },
|
|
29
|
-
availableZ: [...node.availableZ],
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
const cloneNodeWithRect = (
|
|
33
|
-
node: CapacityMeshNode,
|
|
34
|
-
rect: XYRect,
|
|
35
|
-
capacityMeshNodeId: string,
|
|
36
|
-
): CapacityMeshNode => ({
|
|
37
|
-
...node,
|
|
38
|
-
capacityMeshNodeId,
|
|
39
|
-
center: {
|
|
40
|
-
x: rect.x + rect.width / 2,
|
|
41
|
-
y: rect.y + rect.height / 2,
|
|
42
|
-
},
|
|
43
|
-
width: rect.width,
|
|
44
|
-
height: rect.height,
|
|
45
|
-
availableZ: [...node.availableZ],
|
|
46
|
-
layer: `z${node.availableZ.join(",")}`,
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const clonePromotedNodeWithRect = (
|
|
50
|
-
node: CapacityMeshNode,
|
|
51
|
-
rect: XYRect,
|
|
52
|
-
capacityMeshNodeId: string,
|
|
53
|
-
availableZ: number[],
|
|
54
|
-
): CapacityMeshNode => ({
|
|
55
|
-
...node,
|
|
56
|
-
capacityMeshNodeId,
|
|
57
|
-
center: {
|
|
58
|
-
x: rect.x + rect.width / 2,
|
|
59
|
-
y: rect.y + rect.height / 2,
|
|
60
|
-
},
|
|
61
|
-
width: rect.width,
|
|
62
|
-
height: rect.height,
|
|
63
|
-
availableZ: [...availableZ],
|
|
64
|
-
layer: `z${availableZ.join(",")}`,
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const isFreeNode = (node: CapacityMeshNode) =>
|
|
68
|
-
!node._containsObstacle && !node._containsTarget
|
|
69
|
-
|
|
70
|
-
const isSingletonNodeOnLayer = (node: CapacityMeshNode, z: number) =>
|
|
71
|
-
node.availableZ.length === 1 && node.availableZ[0] === z
|
|
72
|
-
|
|
73
|
-
const sameRect = (a: XYRect, b: XYRect) =>
|
|
74
|
-
Math.abs(a.x - b.x) <= EPS &&
|
|
75
|
-
Math.abs(a.y - b.y) <= EPS &&
|
|
76
|
-
Math.abs(a.width - b.width) <= EPS &&
|
|
77
|
-
Math.abs(a.height - b.height) <= EPS
|
|
78
|
-
|
|
79
|
-
const subtractRects = (target: XYRect, cutters: XYRect[]) => {
|
|
80
|
-
let remaining: XYRect[] = [target]
|
|
81
|
-
|
|
82
|
-
for (const cutter of cutters) {
|
|
83
|
-
if (remaining.length === 0) return remaining
|
|
84
|
-
|
|
85
|
-
const nextRemaining: XYRect[] = []
|
|
86
|
-
for (const piece of remaining) {
|
|
87
|
-
nextRemaining.push(...subtractRect2D(piece, cutter))
|
|
88
|
-
}
|
|
89
|
-
remaining = nextRemaining
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return remaining
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const isFullyCoveredByRects = (target: XYRect, coveringRects: XYRect[]) => {
|
|
96
|
-
return subtractRects(target, coveringRects).length === 0
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const sortAndDedupeCuts = (values: number[]) => {
|
|
100
|
-
const sorted = [...values].sort((a, b) => a - b)
|
|
101
|
-
const out: number[] = []
|
|
102
|
-
|
|
103
|
-
for (const value of sorted) {
|
|
104
|
-
const last = out[out.length - 1]
|
|
105
|
-
if (last == null || Math.abs(last - value) > EPS) {
|
|
106
|
-
out.push(value)
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return out
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const partitionRectByRects = (target: XYRect, supportRects: XYRect[]) => {
|
|
114
|
-
const xCuts = [target.x, target.x + target.width]
|
|
115
|
-
const yCuts = [target.y, target.y + target.height]
|
|
116
|
-
const targetMaxX = target.x + target.width
|
|
117
|
-
const targetMaxY = target.y + target.height
|
|
118
|
-
|
|
119
|
-
for (const rect of supportRects) {
|
|
120
|
-
const x0 = Math.max(target.x, rect.x)
|
|
121
|
-
const x1 = Math.min(targetMaxX, rect.x + rect.width)
|
|
122
|
-
const y0 = Math.max(target.y, rect.y)
|
|
123
|
-
const y1 = Math.min(targetMaxY, rect.y + rect.height)
|
|
124
|
-
|
|
125
|
-
if (x1 <= x0 + EPS || y1 <= y0 + EPS) continue
|
|
126
|
-
|
|
127
|
-
xCuts.push(x0, x1)
|
|
128
|
-
yCuts.push(y0, y1)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const xs = sortAndDedupeCuts(xCuts)
|
|
132
|
-
const ys = sortAndDedupeCuts(yCuts)
|
|
133
|
-
const cells: XYRect[] = []
|
|
134
|
-
|
|
135
|
-
for (let xi = 0; xi < xs.length - 1; xi++) {
|
|
136
|
-
const x0 = xs[xi]!
|
|
137
|
-
const x1 = xs[xi + 1]!
|
|
138
|
-
|
|
139
|
-
if (x1 <= x0 + EPS) continue
|
|
140
|
-
|
|
141
|
-
for (let yi = 0; yi < ys.length - 1; yi++) {
|
|
142
|
-
const y0 = ys[yi]!
|
|
143
|
-
const y1 = ys[yi + 1]!
|
|
144
|
-
|
|
145
|
-
if (y1 <= y0 + EPS) continue
|
|
146
|
-
|
|
147
|
-
cells.push({
|
|
148
|
-
x: x0,
|
|
149
|
-
y: y0,
|
|
150
|
-
width: x1 - x0,
|
|
151
|
-
height: y1 - y0,
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return cells
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const canMergeHorizontally = (a: XYRect, b: XYRect) =>
|
|
160
|
-
Math.abs(a.y - b.y) <= EPS &&
|
|
161
|
-
Math.abs(a.height - b.height) <= EPS &&
|
|
162
|
-
Math.abs(a.x + a.width - b.x) <= EPS
|
|
163
|
-
|
|
164
|
-
const canMergeVertically = (a: XYRect, b: XYRect) =>
|
|
165
|
-
Math.abs(a.x - b.x) <= EPS &&
|
|
166
|
-
Math.abs(a.width - b.width) <= EPS &&
|
|
167
|
-
Math.abs(a.y + a.height - b.y) <= EPS
|
|
168
|
-
|
|
169
|
-
const mergeTouchingRects = (rects: XYRect[]) => {
|
|
170
|
-
const out = rects.map((rect) => ({ ...rect }))
|
|
171
|
-
let changed = true
|
|
172
|
-
|
|
173
|
-
while (changed) {
|
|
174
|
-
changed = false
|
|
175
|
-
|
|
176
|
-
outer: for (let i = 0; i < out.length; i++) {
|
|
177
|
-
for (let j = i + 1; j < out.length; j++) {
|
|
178
|
-
const a = out[i]!
|
|
179
|
-
const b = out[j]!
|
|
180
|
-
|
|
181
|
-
if (canMergeHorizontally(a, b) || canMergeHorizontally(b, a)) {
|
|
182
|
-
const merged: XYRect = {
|
|
183
|
-
x: Math.min(a.x, b.x),
|
|
184
|
-
y: a.y,
|
|
185
|
-
width: a.width + b.width,
|
|
186
|
-
height: a.height,
|
|
187
|
-
}
|
|
188
|
-
out.splice(j, 1)
|
|
189
|
-
out.splice(i, 1, merged)
|
|
190
|
-
changed = true
|
|
191
|
-
break outer
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (canMergeVertically(a, b) || canMergeVertically(b, a)) {
|
|
195
|
-
const merged: XYRect = {
|
|
196
|
-
x: a.x,
|
|
197
|
-
y: Math.min(a.y, b.y),
|
|
198
|
-
width: a.width,
|
|
199
|
-
height: a.height + b.height,
|
|
200
|
-
}
|
|
201
|
-
out.splice(j, 1)
|
|
202
|
-
out.splice(i, 1, merged)
|
|
203
|
-
changed = true
|
|
204
|
-
break outer
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return out
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const isPromotableRect = (params: {
|
|
214
|
-
rect: XYRect
|
|
215
|
-
viaMinSize: number
|
|
216
|
-
minFragmentArea: number
|
|
217
|
-
}) => {
|
|
218
|
-
const { rect, viaMinSize, minFragmentArea } = params
|
|
219
|
-
|
|
220
|
-
return (
|
|
221
|
-
rect.width > EPS &&
|
|
222
|
-
rect.height > EPS &&
|
|
223
|
-
rectArea(rect) + EPS >= minFragmentArea &&
|
|
224
|
-
rect.width + EPS >= viaMinSize &&
|
|
225
|
-
rect.height + EPS >= viaMinSize
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const isResidualRect = (rect: XYRect, minFragmentArea: number) =>
|
|
230
|
-
rect.width > EPS &&
|
|
231
|
-
rect.height > EPS &&
|
|
232
|
-
rectArea(rect) + EPS >= minFragmentArea
|
|
233
|
-
|
|
234
|
-
const computePromotablePieces = (params: {
|
|
235
|
-
target: XYRect
|
|
236
|
-
supportRects: XYRect[]
|
|
237
|
-
viaMinSize: number
|
|
238
|
-
minFragmentArea: number
|
|
239
|
-
}) => {
|
|
240
|
-
const { target, supportRects, viaMinSize, minFragmentArea } = params
|
|
241
|
-
const overlappingSupports = supportRects.filter((rect) =>
|
|
242
|
-
overlaps(rect, target),
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
if (overlappingSupports.length === 0) return []
|
|
246
|
-
|
|
247
|
-
if (
|
|
248
|
-
isFullyCoveredByRects(target, overlappingSupports) &&
|
|
249
|
-
isPromotableRect({ rect: target, viaMinSize, minFragmentArea })
|
|
250
|
-
) {
|
|
251
|
-
return [target]
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const partitioned = partitionRectByRects(target, overlappingSupports)
|
|
255
|
-
const coveredPieces = partitioned.filter((piece) =>
|
|
256
|
-
isFullyCoveredByRects(piece, overlappingSupports),
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
if (coveredPieces.length === 0) return []
|
|
260
|
-
|
|
261
|
-
const mergedCoveredPieces = mergeTouchingRects(coveredPieces)
|
|
262
|
-
|
|
263
|
-
return mergedCoveredPieces.filter(
|
|
264
|
-
(piece) =>
|
|
265
|
-
isFullyCoveredByRects(piece, overlappingSupports) &&
|
|
266
|
-
isPromotableRect({ rect: piece, viaMinSize, minFragmentArea }),
|
|
267
|
-
)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export class AdjacentLayerContainmentMergeSolver extends BaseSolver {
|
|
271
|
-
private outputNodes: CapacityMeshNode[] = []
|
|
272
|
-
private promotedNodeIds = new Set<string>()
|
|
273
|
-
private residualNodeIds = new Set<string>()
|
|
274
|
-
|
|
275
|
-
constructor(private input: AdjacentLayerContainmentMergeSolverInput) {
|
|
276
|
-
super()
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
override _setup() {
|
|
280
|
-
this.outputNodes = this.input.meshNodes.map(cloneNode)
|
|
281
|
-
this.promotedNodeIds.clear()
|
|
282
|
-
this.residualNodeIds.clear()
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
override _step() {
|
|
286
|
-
this.outputNodes = this.processAdjacentLayerContainmentMerges()
|
|
287
|
-
this.solved = true
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private processAdjacentLayerContainmentMerges(): CapacityMeshNode[] {
|
|
291
|
-
const srj = this.input.simpleRouteJson
|
|
292
|
-
const layerCount = Math.max(1, srj.layerCount || 1)
|
|
293
|
-
|
|
294
|
-
if (layerCount < 2) {
|
|
295
|
-
return this.input.meshNodes.map(cloneNode)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0)
|
|
299
|
-
const minFragmentArea = Math.max(
|
|
300
|
-
EPS,
|
|
301
|
-
this.input.minFragmentArea ?? DEFAULT_MIN_FRAGMENT_AREA,
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
let workingNodes = this.input.meshNodes.map(cloneNode)
|
|
305
|
-
let nextResidualId = 0
|
|
306
|
-
let nextPromotedId = 0
|
|
307
|
-
|
|
308
|
-
for (let lowerZ = 0; lowerZ < layerCount - 1; lowerZ++) {
|
|
309
|
-
const upperZ = lowerZ + 1
|
|
310
|
-
const mutableNodes = workingNodes.filter(
|
|
311
|
-
(node) =>
|
|
312
|
-
isFreeNode(node) &&
|
|
313
|
-
(isSingletonNodeOnLayer(node, lowerZ) ||
|
|
314
|
-
isSingletonNodeOnLayer(node, upperZ)),
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
if (mutableNodes.length === 0) continue
|
|
318
|
-
|
|
319
|
-
const immutableNodes = workingNodes.filter(
|
|
320
|
-
(node) => !mutableNodes.includes(node),
|
|
321
|
-
)
|
|
322
|
-
const supportRectsByLayer = new Map<number, XYRect[]>()
|
|
323
|
-
|
|
324
|
-
supportRectsByLayer.set(
|
|
325
|
-
lowerZ,
|
|
326
|
-
mutableNodes
|
|
327
|
-
.filter((node) => isSingletonNodeOnLayer(node, lowerZ))
|
|
328
|
-
.map(nodeToRect),
|
|
329
|
-
)
|
|
330
|
-
supportRectsByLayer.set(
|
|
331
|
-
upperZ,
|
|
332
|
-
mutableNodes
|
|
333
|
-
.filter((node) => isSingletonNodeOnLayer(node, upperZ))
|
|
334
|
-
.map(nodeToRect),
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
const promotedRects: XYRect[] = []
|
|
338
|
-
const promotedNodes: CapacityMeshNode[] = []
|
|
339
|
-
const candidateNodes = mutableNodes
|
|
340
|
-
.filter((node) =>
|
|
341
|
-
isPromotableRect({
|
|
342
|
-
rect: nodeToRect(node),
|
|
343
|
-
viaMinSize,
|
|
344
|
-
minFragmentArea,
|
|
345
|
-
}),
|
|
346
|
-
)
|
|
347
|
-
.sort((a, b) => rectArea(nodeToRect(b)) - rectArea(nodeToRect(a)))
|
|
348
|
-
|
|
349
|
-
for (const candidate of candidateNodes) {
|
|
350
|
-
const candidateRect = nodeToRect(candidate)
|
|
351
|
-
const candidatePieces = subtractRects(
|
|
352
|
-
candidateRect,
|
|
353
|
-
promotedRects,
|
|
354
|
-
).filter((piece) => isResidualRect(piece, minFragmentArea))
|
|
355
|
-
const candidateZ = candidate.availableZ[0]!
|
|
356
|
-
const oppositeZ = candidateZ === lowerZ ? upperZ : lowerZ
|
|
357
|
-
const supportRects = supportRectsByLayer.get(oppositeZ) ?? []
|
|
358
|
-
|
|
359
|
-
for (const piece of candidatePieces) {
|
|
360
|
-
const promotablePieces = computePromotablePieces({
|
|
361
|
-
target: piece,
|
|
362
|
-
supportRects,
|
|
363
|
-
viaMinSize,
|
|
364
|
-
minFragmentArea,
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
for (const promotablePiece of promotablePieces) {
|
|
368
|
-
promotedRects.push(promotablePiece)
|
|
369
|
-
|
|
370
|
-
const promotedNode = clonePromotedNodeWithRect(
|
|
371
|
-
candidate,
|
|
372
|
-
promotablePiece,
|
|
373
|
-
`${candidate.capacityMeshNodeId}-adjacent-merge-${nextPromotedId++}`,
|
|
374
|
-
[lowerZ, upperZ],
|
|
375
|
-
)
|
|
376
|
-
promotedNodes.push(promotedNode)
|
|
377
|
-
this.promotedNodeIds.add(promotedNode.capacityMeshNodeId)
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const residualNodes: CapacityMeshNode[] = []
|
|
383
|
-
|
|
384
|
-
for (const node of mutableNodes) {
|
|
385
|
-
const nodeRect = nodeToRect(node)
|
|
386
|
-
const remainingPieces = subtractRects(nodeRect, promotedRects).filter(
|
|
387
|
-
(piece) => isResidualRect(piece, minFragmentArea),
|
|
388
|
-
)
|
|
389
|
-
|
|
390
|
-
if (
|
|
391
|
-
remainingPieces.length === 1 &&
|
|
392
|
-
sameRect(remainingPieces[0]!, nodeRect)
|
|
393
|
-
) {
|
|
394
|
-
residualNodes.push(node)
|
|
395
|
-
continue
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
for (const piece of remainingPieces) {
|
|
399
|
-
const residualNode = cloneNodeWithRect(
|
|
400
|
-
node,
|
|
401
|
-
piece,
|
|
402
|
-
`${node.capacityMeshNodeId}-adjacent-residual-${nextResidualId++}`,
|
|
403
|
-
)
|
|
404
|
-
residualNodes.push(residualNode)
|
|
405
|
-
this.residualNodeIds.add(residualNode.capacityMeshNodeId)
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
workingNodes = [...immutableNodes, ...promotedNodes, ...residualNodes]
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return workingNodes
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
override getOutput(): { outputNodes: CapacityMeshNode[] } {
|
|
416
|
-
return { outputNodes: this.outputNodes }
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
override visualize(): GraphicsObject {
|
|
420
|
-
return {
|
|
421
|
-
title: "AdjacentLayerContainmentMergeSolver",
|
|
422
|
-
coordinateSystem: "cartesian",
|
|
423
|
-
rects: this.outputNodes.map((node) => {
|
|
424
|
-
const colors = getColorForZLayer(node.availableZ)
|
|
425
|
-
const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId)
|
|
426
|
-
const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId)
|
|
427
|
-
|
|
428
|
-
return {
|
|
429
|
-
center: node.center,
|
|
430
|
-
width: node.width,
|
|
431
|
-
height: node.height,
|
|
432
|
-
stroke: isPromoted
|
|
433
|
-
? "rgba(245, 158, 11, 0.95)"
|
|
434
|
-
: isResidual
|
|
435
|
-
? "rgba(37, 99, 235, 0.95)"
|
|
436
|
-
: colors.stroke,
|
|
437
|
-
fill: node._containsObstacle
|
|
438
|
-
? "rgba(239, 68, 68, 0.35)"
|
|
439
|
-
: isPromoted
|
|
440
|
-
? "rgba(251, 191, 36, 0.28)"
|
|
441
|
-
: isResidual
|
|
442
|
-
? "rgba(59, 130, 246, 0.18)"
|
|
443
|
-
: colors.fill,
|
|
444
|
-
layer: `z${node.availableZ.join(",")}`,
|
|
445
|
-
label: [
|
|
446
|
-
`node ${node.capacityMeshNodeId}`,
|
|
447
|
-
`z:${node.availableZ.join(",")}`,
|
|
448
|
-
].join("\n"),
|
|
449
|
-
}
|
|
450
|
-
}),
|
|
451
|
-
points: [],
|
|
452
|
-
lines: [],
|
|
453
|
-
texts: [],
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|