@tscircuit/rectdiff 0.0.39 → 0.0.40

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.
Files changed (53) hide show
  1. package/.github/workflows/bun-test.yml +1 -1
  2. package/lib/RectDiffPipeline.ts +0 -27
  3. package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +4 -108
  4. package/package.json +1 -1
  5. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +3 -3
  6. package/tests/solver/arduino-uno-inner2-ground-bottom-power/__snapshots__/arduino-uno-inner2-ground-bottom-power.snap.svg +3 -3
  7. package/tests/solver/arduino-uno-inner2-ground-inner1-power/__snapshots__/arduino-uno-inner2-ground-inner1-power.snap.svg +1 -1
  8. package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
  9. package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +3 -3
  10. package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
  11. package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
  12. package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
  13. package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +3 -3
  14. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +3 -3
  15. package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +2 -2
  16. package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
  17. package/tests/solver/bugreport18-1b2d06/__snapshots__/bugreport18-1b2d06.snap.svg +3 -3
  18. package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
  19. package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
  20. package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +2 -2
  21. package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
  22. package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +3 -3
  23. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +2 -2
  24. package/tests/solver/interaction/__snapshots__/interaction.snap.svg +3 -3
  25. package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
  26. package/tests/solver/no-better-path/__snapshots__/no-better-path.snap.svg +1 -1
  27. package/tests/solver/offboardconnects01/__snapshots__/offboardconnects01.snap.svg +1 -1
  28. package/tests/solver/pcb_trace_id-should-return-root-connection-name/__snapshots__/pcb_trace_id-should-return-root-connection-name.snap.svg +3 -3
  29. package/tests/solver/repros/merge-single-layer-node/__snapshots__/merge-single-layer-node.snap.svg +3 -3
  30. package/tests/solver/transitivity/__snapshots__/transitivity.snap.svg +2 -2
  31. package/lib/math/layers/getUnionZ.ts +0 -6
  32. package/lib/math/layers/getZLayerName.ts +0 -6
  33. package/lib/math/layers/getZSpanMask.ts +0 -6
  34. package/lib/math/layers/hasContiguousZSpan.ts +0 -11
  35. package/lib/math/rects/intersectRects.ts +0 -28
  36. package/lib/math/rects/mergeRects.ts +0 -12
  37. package/lib/math/rects/rectArea.ts +0 -7
  38. package/lib/math/rects/rectContainsRect.ts +0 -18
  39. package/lib/math/rects/rectsTouchOrOverlap.ts +0 -12
  40. package/lib/math/rects/subtractRects.ts +0 -23
  41. package/lib/solvers/SparseMultilayerPromotionSolver/SparseMultilayerPromotionSolver.ts +0 -134
  42. package/lib/solvers/SparseMultilayerPromotionSolver/cloneNode.ts +0 -15
  43. package/lib/solvers/SparseMultilayerPromotionSolver/cloneNodeWithRect.ts +0 -34
  44. package/lib/solvers/SparseMultilayerPromotionSolver/createResidualNodes.ts +0 -42
  45. package/lib/solvers/SparseMultilayerPromotionSolver/findBestCoalesceCandidate.ts +0 -98
  46. package/lib/solvers/SparseMultilayerPromotionSolver/findBestPromotionCandidate.ts +0 -72
  47. package/lib/solvers/SparseMultilayerPromotionSolver/getUsableMultilayerVolumeShare.ts +0 -34
  48. package/lib/solvers/SparseMultilayerPromotionSolver/isFreeNode.ts +0 -8
  49. package/lib/solvers/SparseMultilayerPromotionSolver/nodeToRect.ts +0 -13
  50. package/lib/solvers/SparseMultilayerPromotionSolver/solvers/CoalesceMultilayerTilesSolver.ts +0 -104
  51. package/lib/solvers/SparseMultilayerPromotionSolver/solvers/PromoteSparseMultilayerCoverageSolver.ts +0 -148
  52. package/lib/solvers/SparseMultilayerPromotionSolver/solvers/TrimContainedSingleLayerCoverageSolver.ts +0 -137
  53. 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
- })
@@ -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
- }