@tscircuit/rectdiff 0.0.39 → 0.0.41
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-test.yml +1 -1
- package/benchmark.sh +61 -0
- package/global.d.ts +7 -0
- package/lib/RectDiffPipeline.ts +0 -27
- package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +4 -108
- package/package.json +3 -1
- package/scripts/benchmark/benchmark-child/constants.ts +6 -0
- package/scripts/benchmark/benchmark-child/createSolver.ts +34 -0
- package/scripts/benchmark/benchmark-child/getAutorouterPipeline4ModulePath.ts +7 -0
- package/scripts/benchmark/benchmark-child/handleWorkerTaskLine.ts +64 -0
- package/scripts/benchmark/benchmark-child/importRuntimeModule.ts +8 -0
- package/scripts/benchmark/benchmark-child/types.ts +20 -0
- package/scripts/benchmark/benchmark-child/writeWorkerResultMessage.ts +10 -0
- package/scripts/benchmark/benchmark-types.ts +34 -0
- package/scripts/benchmark/benchmark.child.ts +11 -0
- package/scripts/benchmark/index.ts +413 -0
- package/tests/benchmark-rectdiff-override.test.ts +41 -0
- package/tests/benchmark-results.test.ts +30 -0
- package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +3 -3
- package/tests/solver/arduino-uno-inner2-ground-bottom-power/__snapshots__/arduino-uno-inner2-ground-bottom-power.snap.svg +3 -3
- package/tests/solver/arduino-uno-inner2-ground-inner1-power/__snapshots__/arduino-uno-inner2-ground-inner1-power.snap.svg +1 -1
- package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
- package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +3 -3
- package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.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 +3 -3
- package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +3 -3
- package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +2 -2
- package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
- package/tests/solver/bugreport18-1b2d06/__snapshots__/bugreport18-1b2d06.snap.svg +3 -3
- package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
- package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
- package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +2 -2
- package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
- package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +3 -3
- package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +2 -2
- package/tests/solver/interaction/__snapshots__/interaction.snap.svg +3 -3
- 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/offboardconnects01/__snapshots__/offboardconnects01.snap.svg +1 -1
- package/tests/solver/pcb_trace_id-should-return-root-connection-name/__snapshots__/pcb_trace_id-should-return-root-connection-name.snap.svg +3 -3
- package/tests/solver/repros/merge-single-layer-node/__snapshots__/merge-single-layer-node.snap.svg +3 -3
- package/tests/solver/transitivity/__snapshots__/transitivity.snap.svg +2 -2
- package/lib/math/layers/getUnionZ.ts +0 -6
- package/lib/math/layers/getZLayerName.ts +0 -6
- package/lib/math/layers/getZSpanMask.ts +0 -6
- package/lib/math/layers/hasContiguousZSpan.ts +0 -11
- package/lib/math/rects/intersectRects.ts +0 -28
- package/lib/math/rects/mergeRects.ts +0 -12
- package/lib/math/rects/rectArea.ts +0 -7
- package/lib/math/rects/rectContainsRect.ts +0 -18
- package/lib/math/rects/rectsTouchOrOverlap.ts +0 -12
- package/lib/math/rects/subtractRects.ts +0 -23
- package/lib/solvers/SparseMultilayerPromotionSolver/SparseMultilayerPromotionSolver.ts +0 -134
- package/lib/solvers/SparseMultilayerPromotionSolver/cloneNode.ts +0 -15
- package/lib/solvers/SparseMultilayerPromotionSolver/cloneNodeWithRect.ts +0 -34
- package/lib/solvers/SparseMultilayerPromotionSolver/createResidualNodes.ts +0 -42
- package/lib/solvers/SparseMultilayerPromotionSolver/findBestCoalesceCandidate.ts +0 -98
- package/lib/solvers/SparseMultilayerPromotionSolver/findBestPromotionCandidate.ts +0 -72
- package/lib/solvers/SparseMultilayerPromotionSolver/getUsableMultilayerVolumeShare.ts +0 -34
- package/lib/solvers/SparseMultilayerPromotionSolver/isFreeNode.ts +0 -8
- package/lib/solvers/SparseMultilayerPromotionSolver/nodeToRect.ts +0 -13
- package/lib/solvers/SparseMultilayerPromotionSolver/solvers/CoalesceMultilayerTilesSolver.ts +0 -104
- package/lib/solvers/SparseMultilayerPromotionSolver/solvers/PromoteSparseMultilayerCoverageSolver.ts +0 -148
- package/lib/solvers/SparseMultilayerPromotionSolver/solvers/TrimContainedSingleLayerCoverageSolver.ts +0 -137
- package/lib/solvers/SparseMultilayerPromotionSolver/types.ts +0 -23
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BasePipelineSolver,
|
|
3
|
-
definePipelineStep,
|
|
4
|
-
type PipelineStep,
|
|
5
|
-
} from "@tscircuit/solver-utils"
|
|
6
|
-
import type { GraphicsObject } from "graphics-debug"
|
|
7
|
-
import type {
|
|
8
|
-
CapacityMeshNode,
|
|
9
|
-
CapacityMeshNodeId,
|
|
10
|
-
} from "../../types/capacity-mesh-types"
|
|
11
|
-
import { getZLayerName } from "../../math/layers/getZLayerName"
|
|
12
|
-
import { getColorForZLayer } from "../../utils/getColorForZLayer"
|
|
13
|
-
import { CoalesceMultilayerTilesSolver } from "./solvers/CoalesceMultilayerTilesSolver"
|
|
14
|
-
import { PromoteSparseMultilayerCoverageSolver } from "./solvers/PromoteSparseMultilayerCoverageSolver"
|
|
15
|
-
import { TrimContainedSingleLayerCoverageSolver } from "./solvers/TrimContainedSingleLayerCoverageSolver"
|
|
16
|
-
import type { SparseMultilayerPromotionInput } from "./types"
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* This pipeline makes shared multilayer regions easier to use.
|
|
20
|
-
* It grows shared space, removes redundant leftovers, and combines small tiles.
|
|
21
|
-
*/
|
|
22
|
-
export class SparseMultilayerPromotionSolver extends BasePipelineSolver<SparseMultilayerPromotionInput> {
|
|
23
|
-
coalesceMultilayerTilesSolver?: CoalesceMultilayerTilesSolver
|
|
24
|
-
promoteSparseMultilayerCoverageSolver?: PromoteSparseMultilayerCoverageSolver
|
|
25
|
-
trimContainedSingleLayerCoverageSolver?: TrimContainedSingleLayerCoverageSolver
|
|
26
|
-
|
|
27
|
-
override pipelineDef: PipelineStep<any>[] = [
|
|
28
|
-
definePipelineStep(
|
|
29
|
-
"promoteSparseMultilayerCoverageSolver",
|
|
30
|
-
PromoteSparseMultilayerCoverageSolver,
|
|
31
|
-
(solver: SparseMultilayerPromotionSolver) => [
|
|
32
|
-
{
|
|
33
|
-
meshNodes: solver.inputProblem.meshNodes,
|
|
34
|
-
minRectSize: Math.max(
|
|
35
|
-
solver.inputProblem.simpleRouteJson.minViaDiameter ?? 0,
|
|
36
|
-
solver.inputProblem.simpleRouteJson.minTraceWidth ?? 0,
|
|
37
|
-
),
|
|
38
|
-
promotionTargetShare: solver.inputProblem.promotionTargetShare,
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
),
|
|
42
|
-
definePipelineStep(
|
|
43
|
-
"trimContainedSingleLayerCoverageSolver",
|
|
44
|
-
TrimContainedSingleLayerCoverageSolver,
|
|
45
|
-
(solver: SparseMultilayerPromotionSolver) => [
|
|
46
|
-
{
|
|
47
|
-
meshNodes:
|
|
48
|
-
solver.promoteSparseMultilayerCoverageSolver?.getOutput()
|
|
49
|
-
.outputNodes ?? solver.inputProblem.meshNodes,
|
|
50
|
-
minRectSize: Math.max(
|
|
51
|
-
solver.inputProblem.simpleRouteJson.minViaDiameter ?? 0,
|
|
52
|
-
solver.inputProblem.simpleRouteJson.minTraceWidth ?? 0,
|
|
53
|
-
),
|
|
54
|
-
promotionTargetShare: solver.inputProblem.promotionTargetShare,
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
),
|
|
58
|
-
definePipelineStep(
|
|
59
|
-
"coalesceMultilayerTilesSolver",
|
|
60
|
-
CoalesceMultilayerTilesSolver,
|
|
61
|
-
(solver: SparseMultilayerPromotionSolver) => [
|
|
62
|
-
{
|
|
63
|
-
meshNodes:
|
|
64
|
-
solver.trimContainedSingleLayerCoverageSolver?.getOutput()
|
|
65
|
-
.outputNodes ??
|
|
66
|
-
solver.promoteSparseMultilayerCoverageSolver?.getOutput()
|
|
67
|
-
.outputNodes ??
|
|
68
|
-
solver.inputProblem.meshNodes,
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
),
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
override getConstructorParams() {
|
|
75
|
-
return [this.inputProblem]
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
override getOutput(): { outputNodes: CapacityMeshNode[] } {
|
|
79
|
-
return {
|
|
80
|
-
outputNodes:
|
|
81
|
-
this.coalesceMultilayerTilesSolver?.getOutput().outputNodes ??
|
|
82
|
-
this.trimContainedSingleLayerCoverageSolver?.getOutput().outputNodes ??
|
|
83
|
-
this.promoteSparseMultilayerCoverageSolver?.getOutput().outputNodes ??
|
|
84
|
-
this.inputProblem.meshNodes,
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
override finalVisualize(): GraphicsObject {
|
|
89
|
-
const promotedIds = new Set<CapacityMeshNodeId>([
|
|
90
|
-
...(this.promoteSparseMultilayerCoverageSolver?.getOutput()
|
|
91
|
-
.promotedNodeIds ?? []),
|
|
92
|
-
...(this.coalesceMultilayerTilesSolver?.getOutput().promotedNodeIds ??
|
|
93
|
-
[]),
|
|
94
|
-
])
|
|
95
|
-
const residualIds = new Set<CapacityMeshNodeId>([
|
|
96
|
-
...(this.promoteSparseMultilayerCoverageSolver?.getOutput()
|
|
97
|
-
.residualNodeIds ?? []),
|
|
98
|
-
...(this.trimContainedSingleLayerCoverageSolver?.getOutput()
|
|
99
|
-
.residualNodeIds ?? []),
|
|
100
|
-
])
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
title: "SparseMultilayerPromotionSolver",
|
|
104
|
-
coordinateSystem: "cartesian",
|
|
105
|
-
rects: this.getOutput().outputNodes.map((node) => {
|
|
106
|
-
const colors = getColorForZLayer(node.availableZ)
|
|
107
|
-
const isPromoted = promotedIds.has(node.capacityMeshNodeId)
|
|
108
|
-
const isResidual = residualIds.has(node.capacityMeshNodeId)
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
center: node.center,
|
|
112
|
-
width: node.width,
|
|
113
|
-
height: node.height,
|
|
114
|
-
stroke: isPromoted
|
|
115
|
-
? "rgba(168, 85, 247, 0.95)"
|
|
116
|
-
: isResidual
|
|
117
|
-
? "rgba(14, 116, 144, 0.95)"
|
|
118
|
-
: colors.stroke,
|
|
119
|
-
fill: node._containsObstacle
|
|
120
|
-
? "rgba(239, 68, 68, 0.35)"
|
|
121
|
-
: isPromoted
|
|
122
|
-
? "rgba(192, 132, 252, 0.28)"
|
|
123
|
-
: isResidual
|
|
124
|
-
? "rgba(34, 211, 238, 0.18)"
|
|
125
|
-
: colors.fill,
|
|
126
|
-
layer: getZLayerName({ availableZ: node.availableZ }),
|
|
127
|
-
}
|
|
128
|
-
}),
|
|
129
|
-
points: [],
|
|
130
|
-
lines: [],
|
|
131
|
-
texts: [],
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Make a safe copy of a node.
|
|
5
|
-
* Later steps can change the copy without touching the original.
|
|
6
|
-
*/
|
|
7
|
-
export const cloneNode = ({
|
|
8
|
-
node,
|
|
9
|
-
}: {
|
|
10
|
-
node: CapacityMeshNode
|
|
11
|
-
}): CapacityMeshNode => ({
|
|
12
|
-
...node,
|
|
13
|
-
center: { ...node.center },
|
|
14
|
-
availableZ: [...node.availableZ],
|
|
15
|
-
})
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { XYRect } from "../../rectdiff-types"
|
|
2
|
-
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
3
|
-
import { getZLayerName } from "../../math/layers/getZLayerName"
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Copy a node and replace its shape.
|
|
7
|
-
* The layer span can also be replaced when needed.
|
|
8
|
-
*/
|
|
9
|
-
export const cloneNodeWithRect = ({
|
|
10
|
-
rect,
|
|
11
|
-
templateNode,
|
|
12
|
-
availableZ,
|
|
13
|
-
capacityMeshNodeId,
|
|
14
|
-
}: {
|
|
15
|
-
availableZ?: number[]
|
|
16
|
-
capacityMeshNodeId: string
|
|
17
|
-
rect: XYRect
|
|
18
|
-
templateNode: CapacityMeshNode
|
|
19
|
-
}): CapacityMeshNode => {
|
|
20
|
-
const nextAvailableZ = availableZ ?? templateNode.availableZ
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
...templateNode,
|
|
24
|
-
capacityMeshNodeId,
|
|
25
|
-
center: {
|
|
26
|
-
x: rect.x + rect.width / 2,
|
|
27
|
-
y: rect.y + rect.height / 2,
|
|
28
|
-
},
|
|
29
|
-
width: rect.width,
|
|
30
|
-
height: rect.height,
|
|
31
|
-
availableZ: [...nextAvailableZ],
|
|
32
|
-
layer: getZLayerName({ availableZ: nextAvailableZ }),
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { subtractRect2D } from "../../utils/rectdiff-geometry"
|
|
2
|
-
import type {
|
|
3
|
-
CapacityMeshNode,
|
|
4
|
-
CapacityMeshNodeId,
|
|
5
|
-
} from "../../types/capacity-mesh-types"
|
|
6
|
-
import type { XYRect } from "../../rectdiff-types"
|
|
7
|
-
import { cloneNodeWithRect } from "./cloneNodeWithRect"
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Cut one rectangle out of a node and return the remaining pieces.
|
|
11
|
-
* Each remaining piece is turned back into a node.
|
|
12
|
-
*/
|
|
13
|
-
export const createResidualNodes = ({
|
|
14
|
-
cutRect,
|
|
15
|
-
getNextResidualId,
|
|
16
|
-
idPrefix,
|
|
17
|
-
node,
|
|
18
|
-
onResidualNodeIdCreated,
|
|
19
|
-
}: {
|
|
20
|
-
cutRect: XYRect
|
|
21
|
-
getNextResidualId: () => number
|
|
22
|
-
idPrefix: string
|
|
23
|
-
node: CapacityMeshNode
|
|
24
|
-
onResidualNodeIdCreated: (nodeId: CapacityMeshNodeId) => void
|
|
25
|
-
}) =>
|
|
26
|
-
subtractRect2D(
|
|
27
|
-
{
|
|
28
|
-
x: node.center.x - node.width / 2,
|
|
29
|
-
y: node.center.y - node.height / 2,
|
|
30
|
-
width: node.width,
|
|
31
|
-
height: node.height,
|
|
32
|
-
},
|
|
33
|
-
cutRect,
|
|
34
|
-
).map((rect) => {
|
|
35
|
-
const residualNode = cloneNodeWithRect({
|
|
36
|
-
templateNode: node,
|
|
37
|
-
rect,
|
|
38
|
-
capacityMeshNodeId: `${idPrefix}-${getNextResidualId()}`,
|
|
39
|
-
})
|
|
40
|
-
onResidualNodeIdCreated(residualNode.capacityMeshNodeId)
|
|
41
|
-
return residualNode
|
|
42
|
-
})
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { getZSpanMask } from "../../math/layers/getZSpanMask"
|
|
2
|
-
import { mergeRects } from "../../math/rects/mergeRects"
|
|
3
|
-
import { rectArea } from "../../math/rects/rectArea"
|
|
4
|
-
import { rectContainsRect } from "../../math/rects/rectContainsRect"
|
|
5
|
-
import { rectsTouchOrOverlap } from "../../math/rects/rectsTouchOrOverlap"
|
|
6
|
-
import { subtractRects } from "../../math/rects/subtractRects"
|
|
7
|
-
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
8
|
-
import { isFreeNode } from "./isFreeNode"
|
|
9
|
-
import { nodeToRect } from "./nodeToRect"
|
|
10
|
-
import type { CoalesceCandidate } from "./types"
|
|
11
|
-
|
|
12
|
-
const areRectsAlignedForMerge = ({
|
|
13
|
-
a,
|
|
14
|
-
b,
|
|
15
|
-
}: {
|
|
16
|
-
a: ReturnType<typeof nodeToRect>
|
|
17
|
-
b: ReturnType<typeof nodeToRect>
|
|
18
|
-
}) => {
|
|
19
|
-
const sameVerticalBand =
|
|
20
|
-
Math.abs(a.x - b.x) <= Number.EPSILON &&
|
|
21
|
-
Math.abs(a.width - b.width) <= Number.EPSILON &&
|
|
22
|
-
a.y <= b.y + b.height + Number.EPSILON &&
|
|
23
|
-
b.y <= a.y + a.height + Number.EPSILON
|
|
24
|
-
|
|
25
|
-
const sameHorizontalBand =
|
|
26
|
-
Math.abs(a.y - b.y) <= Number.EPSILON &&
|
|
27
|
-
Math.abs(a.height - b.height) <= Number.EPSILON &&
|
|
28
|
-
a.x <= b.x + b.width + Number.EPSILON &&
|
|
29
|
-
b.x <= a.x + a.width + Number.EPSILON
|
|
30
|
-
|
|
31
|
-
return sameVerticalBand || sameHorizontalBand
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Find a good merge for nearby shared tiles.
|
|
36
|
-
* Larger, more useful merges are preferred.
|
|
37
|
-
*/
|
|
38
|
-
export const findBestCoalesceCandidate = ({
|
|
39
|
-
nodes,
|
|
40
|
-
}: {
|
|
41
|
-
nodes: CapacityMeshNode[]
|
|
42
|
-
}): CoalesceCandidate | null => {
|
|
43
|
-
let best: CoalesceCandidate | null = null
|
|
44
|
-
const nodesBySpan = new Map<
|
|
45
|
-
number,
|
|
46
|
-
Array<{ node: CapacityMeshNode; rect: ReturnType<typeof nodeToRect> }>
|
|
47
|
-
>()
|
|
48
|
-
|
|
49
|
-
for (const node of nodes) {
|
|
50
|
-
if (!isFreeNode({ node }) || node.availableZ.length <= 1) continue
|
|
51
|
-
const spanKey = getZSpanMask({ availableZ: node.availableZ })
|
|
52
|
-
const entries = nodesBySpan.get(spanKey) ?? []
|
|
53
|
-
entries.push({ node, rect: nodeToRect({ node }) })
|
|
54
|
-
nodesBySpan.set(spanKey, entries)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
for (const entries of nodesBySpan.values()) {
|
|
58
|
-
for (let i = 0; i < entries.length; i++) {
|
|
59
|
-
const a = entries[i]!
|
|
60
|
-
|
|
61
|
-
for (let j = i + 1; j < entries.length; j++) {
|
|
62
|
-
const b = entries[j]!
|
|
63
|
-
if (
|
|
64
|
-
!areRectsAlignedForMerge({ a: a.rect, b: b.rect }) &&
|
|
65
|
-
!rectsTouchOrOverlap({ a: a.rect, b: b.rect })
|
|
66
|
-
) {
|
|
67
|
-
continue
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const mergedRect = mergeRects({ a: a.rect, b: b.rect })
|
|
71
|
-
const absorbedEntries = entries.filter((entry) =>
|
|
72
|
-
rectContainsRect({ inner: entry.rect, outer: mergedRect }),
|
|
73
|
-
)
|
|
74
|
-
if (absorbedEntries.length < 2) continue
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
subtractRects({
|
|
78
|
-
target: mergedRect,
|
|
79
|
-
cutters: absorbedEntries.map((entry) => entry.rect),
|
|
80
|
-
}).length > 0
|
|
81
|
-
) {
|
|
82
|
-
continue
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const score = rectArea({ rect: mergedRect }) * absorbedEntries.length
|
|
86
|
-
if (!best || score > best.score) {
|
|
87
|
-
best = {
|
|
88
|
-
rect: mergedRect,
|
|
89
|
-
absorbedNodes: absorbedEntries.map((entry) => entry.node),
|
|
90
|
-
score,
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return best
|
|
98
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { hasContiguousZSpan } from "../../math/layers/hasContiguousZSpan"
|
|
2
|
-
import { getUnionZ } from "../../math/layers/getUnionZ"
|
|
3
|
-
import { intersectRects } from "../../math/rects/intersectRects"
|
|
4
|
-
import { rectArea } from "../../math/rects/rectArea"
|
|
5
|
-
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
6
|
-
import { EPS } from "../../utils/rectdiff-geometry"
|
|
7
|
-
import { isFreeNode } from "./isFreeNode"
|
|
8
|
-
import { nodeToRect } from "./nodeToRect"
|
|
9
|
-
import type { PromotionCandidate } from "./types"
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Find a good overlap to turn into shared space.
|
|
13
|
-
* Larger overlaps are preferred.
|
|
14
|
-
*/
|
|
15
|
-
export const findBestPromotionCandidate = ({
|
|
16
|
-
minRectSize,
|
|
17
|
-
nodes,
|
|
18
|
-
}: {
|
|
19
|
-
minRectSize: number
|
|
20
|
-
nodes: CapacityMeshNode[]
|
|
21
|
-
}): PromotionCandidate | null => {
|
|
22
|
-
let best: PromotionCandidate | null = null
|
|
23
|
-
|
|
24
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
25
|
-
const sourceNode = nodes[i]!
|
|
26
|
-
if (
|
|
27
|
-
!isFreeNode({ node: sourceNode }) ||
|
|
28
|
-
sourceNode.availableZ.length !== 1
|
|
29
|
-
) {
|
|
30
|
-
continue
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
for (let j = 0; j < nodes.length; j++) {
|
|
34
|
-
if (i === j) continue
|
|
35
|
-
|
|
36
|
-
const targetNode = nodes[j]!
|
|
37
|
-
if (!isFreeNode({ node: targetNode })) continue
|
|
38
|
-
|
|
39
|
-
const unionZ = getUnionZ({
|
|
40
|
-
a: sourceNode.availableZ,
|
|
41
|
-
b: targetNode.availableZ,
|
|
42
|
-
})
|
|
43
|
-
if (unionZ.length <= targetNode.availableZ.length) continue
|
|
44
|
-
if (!hasContiguousZSpan({ zValues: unionZ })) continue
|
|
45
|
-
|
|
46
|
-
const overlapRect = intersectRects({
|
|
47
|
-
a: nodeToRect({ node: sourceNode }),
|
|
48
|
-
b: nodeToRect({ node: targetNode }),
|
|
49
|
-
})
|
|
50
|
-
if (!overlapRect) continue
|
|
51
|
-
if (
|
|
52
|
-
overlapRect.width + EPS < minRectSize ||
|
|
53
|
-
overlapRect.height + EPS < minRectSize
|
|
54
|
-
) {
|
|
55
|
-
continue
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const area = rectArea({ rect: overlapRect })
|
|
59
|
-
if (!best || area > best.area) {
|
|
60
|
-
best = {
|
|
61
|
-
rect: overlapRect,
|
|
62
|
-
sourceNode,
|
|
63
|
-
targetNode,
|
|
64
|
-
unionZ,
|
|
65
|
-
area,
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return best
|
|
72
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { EPS } from "../../utils/rectdiff-geometry"
|
|
2
|
-
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Measure how much usable space is shared across multiple layers.
|
|
6
|
-
* Obstacle-only space is not counted.
|
|
7
|
-
*/
|
|
8
|
-
export const getUsableMultilayerVolumeShare = ({
|
|
9
|
-
nodes,
|
|
10
|
-
}: {
|
|
11
|
-
nodes: CapacityMeshNode[]
|
|
12
|
-
}) => {
|
|
13
|
-
let totalVolume = 0
|
|
14
|
-
let obstacleVolume = 0
|
|
15
|
-
let multilayerVolume = 0
|
|
16
|
-
|
|
17
|
-
for (const node of nodes) {
|
|
18
|
-
const volume = node.width * node.height * node.availableZ.length
|
|
19
|
-
totalVolume += volume
|
|
20
|
-
|
|
21
|
-
if (node._containsObstacle) {
|
|
22
|
-
obstacleVolume += volume
|
|
23
|
-
continue
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (node.availableZ.length > 1) {
|
|
27
|
-
multilayerVolume += volume
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const usableVolume = totalVolume - obstacleVolume
|
|
32
|
-
if (usableVolume <= EPS) return 0
|
|
33
|
-
return multilayerVolume / usableVolume
|
|
34
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Check whether a node is plain free space.
|
|
5
|
-
* Obstacles and targets are excluded.
|
|
6
|
-
*/
|
|
7
|
-
export const isFreeNode = ({ node }: { node: CapacityMeshNode }) =>
|
|
8
|
-
!node._containsObstacle && !node._containsTarget
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { XYRect } from "../../rectdiff-types"
|
|
2
|
-
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Convert a node into a rectangle.
|
|
6
|
-
* This keeps geometry code simple.
|
|
7
|
-
*/
|
|
8
|
-
export const nodeToRect = ({ node }: { node: CapacityMeshNode }): XYRect => ({
|
|
9
|
-
x: node.center.x - node.width / 2,
|
|
10
|
-
y: node.center.y - node.height / 2,
|
|
11
|
-
width: node.width,
|
|
12
|
-
height: node.height,
|
|
13
|
-
})
|
package/lib/solvers/SparseMultilayerPromotionSolver/solvers/CoalesceMultilayerTilesSolver.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
|
-
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
-
import type {
|
|
4
|
-
CapacityMeshNode,
|
|
5
|
-
CapacityMeshNodeId,
|
|
6
|
-
} from "../../../types/capacity-mesh-types"
|
|
7
|
-
import { getZLayerName } from "../../../math/layers/getZLayerName"
|
|
8
|
-
import { getColorForZLayer } from "../../../utils/getColorForZLayer"
|
|
9
|
-
import { cloneNode } from "../cloneNode"
|
|
10
|
-
import { cloneNodeWithRect } from "../cloneNodeWithRect"
|
|
11
|
-
import { findBestCoalesceCandidate } from "../findBestCoalesceCandidate"
|
|
12
|
-
|
|
13
|
-
type CoalesceMultilayerTilesSolverInput = {
|
|
14
|
-
meshNodes: CapacityMeshNode[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Merge small shared tiles into larger regions.
|
|
19
|
-
* It runs until no useful merge remains.
|
|
20
|
-
*/
|
|
21
|
-
export class CoalesceMultilayerTilesSolver extends BaseSolver {
|
|
22
|
-
private nextMergedId = 0
|
|
23
|
-
private outputNodes: CapacityMeshNode[] = []
|
|
24
|
-
private promotedNodeIds = new Set<CapacityMeshNodeId>()
|
|
25
|
-
private workingNodes: CapacityMeshNode[] = []
|
|
26
|
-
|
|
27
|
-
constructor(private input: CoalesceMultilayerTilesSolverInput) {
|
|
28
|
-
super()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
override _setup() {
|
|
32
|
-
this.nextMergedId = 0
|
|
33
|
-
this.promotedNodeIds.clear()
|
|
34
|
-
this.workingNodes = this.input.meshNodes.map((node) => cloneNode({ node }))
|
|
35
|
-
this.outputNodes = [...this.workingNodes]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
override _step() {
|
|
39
|
-
const candidate = findBestCoalesceCandidate({ nodes: this.workingNodes })
|
|
40
|
-
if (!candidate) {
|
|
41
|
-
this.outputNodes = [...this.workingNodes]
|
|
42
|
-
this.solved = true
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const absorbedNodeIds = new Set<CapacityMeshNodeId>(
|
|
47
|
-
candidate.absorbedNodes.map((node) => node.capacityMeshNodeId),
|
|
48
|
-
)
|
|
49
|
-
const templateNode = this.workingNodes.find((node) =>
|
|
50
|
-
absorbedNodeIds.has(node.capacityMeshNodeId),
|
|
51
|
-
)
|
|
52
|
-
if (!templateNode) {
|
|
53
|
-
this.outputNodes = [...this.workingNodes]
|
|
54
|
-
this.solved = true
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const mergedNode = cloneNodeWithRect({
|
|
59
|
-
templateNode,
|
|
60
|
-
rect: candidate.rect,
|
|
61
|
-
capacityMeshNodeId: `sparse-coalesced-${this.nextMergedId++}`,
|
|
62
|
-
})
|
|
63
|
-
this.promotedNodeIds.add(mergedNode.capacityMeshNodeId)
|
|
64
|
-
|
|
65
|
-
this.workingNodes = [
|
|
66
|
-
...this.workingNodes.filter(
|
|
67
|
-
(node) => !absorbedNodeIds.has(node.capacityMeshNodeId),
|
|
68
|
-
),
|
|
69
|
-
mergedNode,
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
this.outputNodes = [...this.workingNodes]
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
override getOutput() {
|
|
76
|
-
return {
|
|
77
|
-
outputNodes: this.outputNodes,
|
|
78
|
-
promotedNodeIds: this.promotedNodeIds,
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
override visualize(): GraphicsObject {
|
|
83
|
-
return {
|
|
84
|
-
title: "CoalesceMultilayerTilesSolver",
|
|
85
|
-
coordinateSystem: "cartesian",
|
|
86
|
-
rects: this.outputNodes.map((node) => {
|
|
87
|
-
const colors = getColorForZLayer(node.availableZ)
|
|
88
|
-
const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId)
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
center: node.center,
|
|
92
|
-
width: node.width,
|
|
93
|
-
height: node.height,
|
|
94
|
-
stroke: isPromoted ? "rgba(168, 85, 247, 0.95)" : colors.stroke,
|
|
95
|
-
fill: isPromoted ? "rgba(192, 132, 252, 0.28)" : colors.fill,
|
|
96
|
-
layer: getZLayerName({ availableZ: node.availableZ }),
|
|
97
|
-
}
|
|
98
|
-
}),
|
|
99
|
-
points: [],
|
|
100
|
-
lines: [],
|
|
101
|
-
texts: [],
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|