@tscircuit/rectdiff 0.0.33 → 0.0.35
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/components/SolverDebugger3d.tsx +64 -10
- package/lib/RectDiffPipeline.ts +23 -0
- package/lib/fixtures/twoNodeExpansionFixture.ts +1 -1
- package/lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts +2 -2
- package/lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts +1 -1
- package/lib/solvers/GapFillSolver/GapFillSolverPipeline.ts +2 -2
- package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +311 -0
- 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 +1 -0
- 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/lib/utils/z-filter.ts +43 -0
- package/package.json +6 -2
- package/tests/solver/arduino-uno-inner2-ground-bottom-power/__snapshots__/arduino-uno-inner2-ground-bottom-power.snap.svg +1 -1
- package/tests/solver/arduino-uno-inner2-ground-inner1-power/__snapshots__/arduino-uno-inner2-ground-inner1-power.snap.svg +3 -3
- package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
- package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
- package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
- package/tests/solver/repros/merge-single-layer-node/__snapshots__/merge-single-layer-node.snap.svg +44 -0
- package/tests/z-filter.test.ts +33 -0
- package/dist/index.d.ts +0 -381
- package/dist/index.js +0 -2764
|
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
|
2
2
|
import { GenericSolverDebugger } from "@tscircuit/solver-utils/react"
|
|
3
3
|
import type { SimpleRouteJson } from "../lib/types/srj-types"
|
|
4
4
|
import type { CapacityMeshNode } from "../lib/types/capacity-mesh-types"
|
|
5
|
+
import { matchesExactZFilter, parseZFilterInput } from "../lib/utils/z-filter"
|
|
5
6
|
import * as THREE from "three"
|
|
6
7
|
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
|
|
7
8
|
import type { BaseSolver } from "@tscircuit/solver-utils"
|
|
@@ -168,6 +169,7 @@ const ThreeBoardView: React.FC<{
|
|
|
168
169
|
shrinkBoxes: boolean
|
|
169
170
|
boxShrinkAmount: number
|
|
170
171
|
showBorders: boolean
|
|
172
|
+
selectedZValues: number[] | null
|
|
171
173
|
}> = ({
|
|
172
174
|
nodes,
|
|
173
175
|
srj,
|
|
@@ -181,6 +183,7 @@ const ThreeBoardView: React.FC<{
|
|
|
181
183
|
shrinkBoxes,
|
|
182
184
|
boxShrinkAmount,
|
|
183
185
|
showBorders,
|
|
186
|
+
selectedZValues,
|
|
184
187
|
}) => {
|
|
185
188
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
186
189
|
const destroyRef = useRef<() => void>(() => {})
|
|
@@ -203,9 +206,17 @@ const ThreeBoardView: React.FC<{
|
|
|
203
206
|
|
|
204
207
|
const layerCount = layerNames.length || srj?.layerCount || 1
|
|
205
208
|
|
|
206
|
-
const
|
|
207
|
-
() =>
|
|
208
|
-
|
|
209
|
+
const filteredNodes = useMemo(
|
|
210
|
+
() =>
|
|
211
|
+
nodes.filter((node) =>
|
|
212
|
+
matchesExactZFilter(node.availableZ ?? [], selectedZValues),
|
|
213
|
+
),
|
|
214
|
+
[nodes, selectedZValues],
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
const filteredPrisms = useMemo(
|
|
218
|
+
() => buildPrismsFromNodes(filteredNodes, layerCount),
|
|
219
|
+
[filteredNodes, layerCount],
|
|
209
220
|
)
|
|
210
221
|
|
|
211
222
|
useEffect(() => {
|
|
@@ -372,6 +383,8 @@ const ThreeBoardView: React.FC<{
|
|
|
372
383
|
.map((name) => zIndexByLayerName.get(name))
|
|
373
384
|
.filter((z): z is number => typeof z === "number")
|
|
374
385
|
|
|
386
|
+
if (!matchesExactZFilter(zs, selectedZValues)) continue
|
|
387
|
+
|
|
375
388
|
for (const z of zs) {
|
|
376
389
|
if (z < 0 || z >= layerCount) continue
|
|
377
390
|
obstaclesGroup.add(
|
|
@@ -389,7 +402,7 @@ const ThreeBoardView: React.FC<{
|
|
|
389
402
|
|
|
390
403
|
// Output prisms from nodes (wireframe toggle like the experiment)
|
|
391
404
|
if (showOutput) {
|
|
392
|
-
for (const p of
|
|
405
|
+
for (const p of filteredPrisms) {
|
|
393
406
|
let box = p
|
|
394
407
|
if (shrinkBoxes && boxShrinkAmount > 0) {
|
|
395
408
|
const s = boxShrinkAmount
|
|
@@ -442,7 +455,7 @@ const ThreeBoardView: React.FC<{
|
|
|
442
455
|
z1: layerCount,
|
|
443
456
|
}
|
|
444
457
|
: (() => {
|
|
445
|
-
if (
|
|
458
|
+
if (filteredPrisms.length === 0) {
|
|
446
459
|
return {
|
|
447
460
|
minX: -10,
|
|
448
461
|
maxX: 10,
|
|
@@ -456,7 +469,7 @@ const ThreeBoardView: React.FC<{
|
|
|
456
469
|
minY = Infinity,
|
|
457
470
|
maxX = -Infinity,
|
|
458
471
|
maxY = -Infinity
|
|
459
|
-
for (const p of
|
|
472
|
+
for (const p of filteredPrisms) {
|
|
460
473
|
minX = Math.min(minX, p.minX)
|
|
461
474
|
maxX = Math.max(maxX, p.maxX)
|
|
462
475
|
minY = Math.min(minY, p.minY)
|
|
@@ -517,7 +530,7 @@ const ThreeBoardView: React.FC<{
|
|
|
517
530
|
}
|
|
518
531
|
}, [
|
|
519
532
|
srj,
|
|
520
|
-
|
|
533
|
+
filteredPrisms,
|
|
521
534
|
layerCount,
|
|
522
535
|
layerThickness,
|
|
523
536
|
height,
|
|
@@ -530,6 +543,7 @@ const ThreeBoardView: React.FC<{
|
|
|
530
543
|
shrinkBoxes,
|
|
531
544
|
boxShrinkAmount,
|
|
532
545
|
showBorders,
|
|
546
|
+
selectedZValues,
|
|
533
547
|
])
|
|
534
548
|
|
|
535
549
|
return (
|
|
@@ -572,10 +586,18 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
572
586
|
const [shrinkBoxes, setShrinkBoxes] = useState(true)
|
|
573
587
|
const [boxShrinkAmount, setBoxShrinkAmount] = useState(0.1)
|
|
574
588
|
const [showBorders, setShowBorders] = useState(true)
|
|
589
|
+
const [zFilterInput, setZFilterInput] = useState("")
|
|
575
590
|
|
|
576
591
|
// Mesh nodes state - updated when solver completes or during stepping
|
|
577
592
|
const [meshNodes, setMeshNodes] = useState<CapacityMeshNode[]>([])
|
|
578
593
|
|
|
594
|
+
const selectedZValues = useMemo(
|
|
595
|
+
() => parseZFilterInput(zFilterInput),
|
|
596
|
+
[zFilterInput],
|
|
597
|
+
)
|
|
598
|
+
const hasInvalidZFilter =
|
|
599
|
+
zFilterInput.trim().length > 0 && selectedZValues === null
|
|
600
|
+
|
|
579
601
|
// Update mesh nodes from solver output
|
|
580
602
|
const updateMeshNodes = useCallback(() => {
|
|
581
603
|
try {
|
|
@@ -601,11 +623,16 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
601
623
|
// Poll for updates during stepping (GenericSolverDebugger doesn't have onStep)
|
|
602
624
|
useEffect(() => {
|
|
603
625
|
const interval = setInterval(() => {
|
|
604
|
-
|
|
605
|
-
if (solver.solved || solver.stats?.placed > 0) {
|
|
626
|
+
if (solver.solved) {
|
|
606
627
|
updateMeshNodes()
|
|
628
|
+
clearInterval(interval)
|
|
629
|
+
return
|
|
607
630
|
}
|
|
608
|
-
|
|
631
|
+
|
|
632
|
+
if (solver.stats?.placed > 0) {
|
|
633
|
+
updateMeshNodes()
|
|
634
|
+
}
|
|
635
|
+
}, 100)
|
|
609
636
|
|
|
610
637
|
return () => clearInterval(interval)
|
|
611
638
|
}, [updateMeshNodes, solver])
|
|
@@ -724,6 +751,32 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
724
751
|
/>
|
|
725
752
|
Wireframe
|
|
726
753
|
</label>
|
|
754
|
+
|
|
755
|
+
<label
|
|
756
|
+
style={{
|
|
757
|
+
display: "inline-flex",
|
|
758
|
+
gap: 8,
|
|
759
|
+
alignItems: "center",
|
|
760
|
+
fontSize: 13,
|
|
761
|
+
}}
|
|
762
|
+
>
|
|
763
|
+
<span>Z Filter</span>
|
|
764
|
+
<input
|
|
765
|
+
type="text"
|
|
766
|
+
value={zFilterInput}
|
|
767
|
+
onChange={(e) => setZFilterInput(e.target.value)}
|
|
768
|
+
placeholder="1 or 1,2,3"
|
|
769
|
+
style={{
|
|
770
|
+
width: 120,
|
|
771
|
+
padding: "6px 10px",
|
|
772
|
+
borderRadius: 6,
|
|
773
|
+
border: `1px solid ${hasInvalidZFilter ? "#dc2626" : "#cbd5e1"}`,
|
|
774
|
+
background: "white",
|
|
775
|
+
fontSize: 13,
|
|
776
|
+
}}
|
|
777
|
+
title="Show only exact z matches. Example: 1 matches [1], 1,2,3 matches [1,2,3]."
|
|
778
|
+
/>
|
|
779
|
+
</label>
|
|
727
780
|
</>
|
|
728
781
|
)}
|
|
729
782
|
</div>
|
|
@@ -752,6 +805,7 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
752
805
|
shrinkBoxes={shrinkBoxes}
|
|
753
806
|
boxShrinkAmount={boxShrinkAmount}
|
|
754
807
|
showBorders={showBorders}
|
|
808
|
+
selectedZValues={selectedZValues}
|
|
755
809
|
/>
|
|
756
810
|
)}
|
|
757
811
|
</div>
|
package/lib/RectDiffPipeline.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { GridFill3DOptions, XYRect } from "./rectdiff-types"
|
|
|
8
8
|
import type { CapacityMeshNode } from "./types/capacity-mesh-types"
|
|
9
9
|
import type { GraphicsObject } from "graphics-debug"
|
|
10
10
|
import { GapFillSolverPipeline } from "./solvers/GapFillSolver/GapFillSolverPipeline"
|
|
11
|
+
import { OuterLayerContainmentMergeSolver } from "./solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver"
|
|
11
12
|
import { RectDiffGridSolverPipeline } from "./solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline"
|
|
12
13
|
import { createBaseVisualization } from "./rectdiff-visualization"
|
|
13
14
|
import { buildFinalRectDiffVisualization } from "./buildFinalRectDiffVisualization"
|
|
@@ -25,6 +26,7 @@ export interface RectDiffPipelineInput {
|
|
|
25
26
|
export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput> {
|
|
26
27
|
rectDiffGridSolverPipeline?: RectDiffGridSolverPipeline
|
|
27
28
|
gapFillSolver?: GapFillSolverPipeline
|
|
29
|
+
outerLayerContainmentMergeSolver?: OuterLayerContainmentMergeSolver
|
|
28
30
|
boardVoidRects: XYRect[] | undefined
|
|
29
31
|
zIndexByName?: Map<string, number>
|
|
30
32
|
layerNames?: string[]
|
|
@@ -69,6 +71,22 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
|
|
|
69
71
|
},
|
|
70
72
|
],
|
|
71
73
|
),
|
|
74
|
+
definePipelineStep(
|
|
75
|
+
"outerLayerContainmentMergeSolver",
|
|
76
|
+
OuterLayerContainmentMergeSolver,
|
|
77
|
+
(rectDiffPipeline: RectDiffPipeline) => [
|
|
78
|
+
{
|
|
79
|
+
meshNodes:
|
|
80
|
+
rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ??
|
|
81
|
+
rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput()
|
|
82
|
+
.meshNodes ??
|
|
83
|
+
[],
|
|
84
|
+
simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
|
|
85
|
+
zIndexByName: rectDiffPipeline.zIndexByName ?? new Map(),
|
|
86
|
+
obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
),
|
|
72
90
|
]
|
|
73
91
|
|
|
74
92
|
override _setup(): void {
|
|
@@ -100,6 +118,11 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
|
|
|
100
118
|
}
|
|
101
119
|
|
|
102
120
|
override getOutput(): { meshNodes: CapacityMeshNode[] } {
|
|
121
|
+
const outerLayerMergeOutput =
|
|
122
|
+
this.outerLayerContainmentMergeSolver?.getOutput()
|
|
123
|
+
if (outerLayerMergeOutput) {
|
|
124
|
+
return { meshNodes: outerLayerMergeOutput.outputNodes }
|
|
125
|
+
}
|
|
103
126
|
const gapFillOutput = this.gapFillSolver?.getOutput()
|
|
104
127
|
if (gapFillOutput) {
|
|
105
128
|
return { meshNodes: gapFillOutput.outputNodes }
|
|
@@ -2,7 +2,7 @@ import RBush from "rbush"
|
|
|
2
2
|
import type { RectDiffExpansionSolverInput } from "../solvers/RectDiffExpansionSolver/RectDiffExpansionSolver"
|
|
3
3
|
import type { SimpleRouteJson } from "../types/srj-types"
|
|
4
4
|
import type { XYRect } from "../rectdiff-types"
|
|
5
|
-
import type { RTreeRect } from "
|
|
5
|
+
import type { RTreeRect } from "../types/capacity-mesh-types"
|
|
6
6
|
import { buildZIndexMap } from "../solvers/RectDiffSeedingSolver/layers"
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
|
-
import type { CapacityMeshNode } from "
|
|
2
|
+
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
3
3
|
import type { SegmentWithAdjacentEmptySpace } from "./FindSegmentsWithAdjacentEmptySpaceSolver"
|
|
4
4
|
import type { GraphicsObject } from "graphics-debug"
|
|
5
5
|
import RBush from "rbush"
|
|
@@ -7,7 +7,7 @@ import { EDGE_MAP, EDGES } from "./edge-constants"
|
|
|
7
7
|
import { getBoundsFromCorners } from "./getBoundsFromCorners"
|
|
8
8
|
import type { Bounds } from "@tscircuit/math-utils"
|
|
9
9
|
import { midpoint, segmentToBoxMinDistance } from "@tscircuit/math-utils"
|
|
10
|
-
import type { XYRect } from "
|
|
10
|
+
import type { XYRect } from "../../rectdiff-types"
|
|
11
11
|
|
|
12
12
|
const EPS = 1e-4
|
|
13
13
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
2
|
import Flatbush from "flatbush"
|
|
3
3
|
import type { GraphicsObject, NinePointAnchor } from "graphics-debug"
|
|
4
|
-
import type { CapacityMeshNode } from "
|
|
4
|
+
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
5
5
|
import { projectToUncoveredSegments } from "./projectToUncoveredSegments"
|
|
6
6
|
import { EDGES } from "./edge-constants"
|
|
7
7
|
import { visuallyOffsetLine } from "./visuallyOffsetLine"
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
definePipelineStep,
|
|
4
4
|
type PipelineStep,
|
|
5
5
|
} from "@tscircuit/solver-utils"
|
|
6
|
-
import type { CapacityMeshNode } from "
|
|
6
|
+
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
7
7
|
import type { GraphicsObject } from "graphics-debug"
|
|
8
8
|
import { FindSegmentsWithAdjacentEmptySpaceSolver } from "./FindSegmentsWithAdjacentEmptySpaceSolver"
|
|
9
9
|
import { ExpandEdgesToEmptySpaceSolver } from "./ExpandEdgesToEmptySpaceSolver"
|
|
10
|
-
import type { XYRect } from "
|
|
10
|
+
import type { XYRect } from "../../rectdiff-types"
|
|
11
11
|
|
|
12
12
|
type GapFillSolverInput = {
|
|
13
13
|
meshNodes: CapacityMeshNode[]
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
+
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
4
|
+
import type { XYRect } from "../../rectdiff-types"
|
|
5
|
+
import type { Obstacle, SimpleRouteJson } from "../../types/srj-types"
|
|
6
|
+
import { obstacleToXYRect, obstacleZs } from "../RectDiffSeedingSolver/layers"
|
|
7
|
+
import { getColorForZLayer } from "../../utils/getColorForZLayer"
|
|
8
|
+
import { subtractRect2D, overlaps, EPS } from "../../utils/rectdiff-geometry"
|
|
9
|
+
import { padRect } from "../../utils/padRect"
|
|
10
|
+
|
|
11
|
+
type OuterLayerContainmentMergeSolverInput = {
|
|
12
|
+
meshNodes: CapacityMeshNode[]
|
|
13
|
+
simpleRouteJson: SimpleRouteJson
|
|
14
|
+
zIndexByName: Map<string, number>
|
|
15
|
+
obstacleClearance?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type ObstacleWithRect = {
|
|
19
|
+
obstacle: Obstacle
|
|
20
|
+
rect: XYRect
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const nodeToRect = (node: CapacityMeshNode): XYRect => ({
|
|
24
|
+
x: node.center.x - node.width / 2,
|
|
25
|
+
y: node.center.y - node.height / 2,
|
|
26
|
+
width: node.width,
|
|
27
|
+
height: node.height,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const rectArea = (rect: XYRect) => rect.width * rect.height
|
|
31
|
+
|
|
32
|
+
const cloneNode = (node: CapacityMeshNode): CapacityMeshNode => ({
|
|
33
|
+
...node,
|
|
34
|
+
center: { ...node.center },
|
|
35
|
+
availableZ: [...node.availableZ],
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const cloneNodeWithRect = (
|
|
39
|
+
node: CapacityMeshNode,
|
|
40
|
+
rect: XYRect,
|
|
41
|
+
capacityMeshNodeId: string,
|
|
42
|
+
): CapacityMeshNode => ({
|
|
43
|
+
...node,
|
|
44
|
+
capacityMeshNodeId,
|
|
45
|
+
center: {
|
|
46
|
+
x: rect.x + rect.width / 2,
|
|
47
|
+
y: rect.y + rect.height / 2,
|
|
48
|
+
},
|
|
49
|
+
width: rect.width,
|
|
50
|
+
height: rect.height,
|
|
51
|
+
availableZ: [...node.availableZ],
|
|
52
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const isFreeNode = (node: CapacityMeshNode) =>
|
|
56
|
+
!node._containsObstacle && !node._containsTarget
|
|
57
|
+
|
|
58
|
+
const isSingletonOuterNode = (node: CapacityMeshNode, outerZ: number) =>
|
|
59
|
+
node.availableZ.length === 1 && node.availableZ[0] === outerZ
|
|
60
|
+
|
|
61
|
+
const sameRect = (a: XYRect, b: XYRect) =>
|
|
62
|
+
Math.abs(a.x - b.x) <= EPS &&
|
|
63
|
+
Math.abs(a.y - b.y) <= EPS &&
|
|
64
|
+
Math.abs(a.width - b.width) <= EPS &&
|
|
65
|
+
Math.abs(a.height - b.height) <= EPS
|
|
66
|
+
|
|
67
|
+
const subtractRects = (target: XYRect, cutters: XYRect[]) => {
|
|
68
|
+
let remaining: XYRect[] = [target]
|
|
69
|
+
|
|
70
|
+
for (const cutter of cutters) {
|
|
71
|
+
if (remaining.length === 0) return remaining
|
|
72
|
+
|
|
73
|
+
const nextRemaining: XYRect[] = []
|
|
74
|
+
for (const piece of remaining) {
|
|
75
|
+
nextRemaining.push(...subtractRect2D(piece, cutter))
|
|
76
|
+
}
|
|
77
|
+
remaining = nextRemaining
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return remaining
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isFullyCoveredByRects = (target: XYRect, coveringRects: XYRect[]) => {
|
|
84
|
+
return subtractRects(target, coveringRects).length === 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class OuterLayerContainmentMergeSolver extends BaseSolver {
|
|
88
|
+
private outputNodes: CapacityMeshNode[] = []
|
|
89
|
+
private promotedNodeIds = new Set<string>()
|
|
90
|
+
private residualNodeIds = new Set<string>()
|
|
91
|
+
|
|
92
|
+
constructor(private input: OuterLayerContainmentMergeSolverInput) {
|
|
93
|
+
super()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override _setup() {
|
|
97
|
+
this.outputNodes = this.input.meshNodes.map(cloneNode)
|
|
98
|
+
this.promotedNodeIds.clear()
|
|
99
|
+
this.residualNodeIds.clear()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
override _step() {
|
|
103
|
+
this.outputNodes = this.processOuterLayerContainmentMerges()
|
|
104
|
+
this.solved = true
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private processOuterLayerContainmentMerges(): CapacityMeshNode[] {
|
|
108
|
+
const srj = this.input.simpleRouteJson
|
|
109
|
+
const layerCount = Math.max(1, srj.layerCount || 1)
|
|
110
|
+
if (layerCount < 3) {
|
|
111
|
+
return this.input.meshNodes.map(cloneNode)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const topZ = 0
|
|
115
|
+
const bottomZ = layerCount - 1
|
|
116
|
+
const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0)
|
|
117
|
+
const originalNodes = this.input.meshNodes.map(cloneNode)
|
|
118
|
+
const obstaclesByLayer = this.buildObstaclesByLayer(layerCount)
|
|
119
|
+
const mutableOuterNodes = originalNodes.filter(
|
|
120
|
+
(node) =>
|
|
121
|
+
isFreeNode(node) &&
|
|
122
|
+
(isSingletonOuterNode(node, topZ) ||
|
|
123
|
+
isSingletonOuterNode(node, bottomZ)),
|
|
124
|
+
)
|
|
125
|
+
const immutableNodes = originalNodes.filter(
|
|
126
|
+
(node) => !mutableOuterNodes.includes(node),
|
|
127
|
+
)
|
|
128
|
+
const freeSupportRectsByOuterLayer = new Map<number, XYRect[]>()
|
|
129
|
+
freeSupportRectsByOuterLayer.set(
|
|
130
|
+
topZ,
|
|
131
|
+
originalNodes
|
|
132
|
+
.filter((node) => isFreeNode(node) && node.availableZ.includes(topZ))
|
|
133
|
+
.map(nodeToRect),
|
|
134
|
+
)
|
|
135
|
+
freeSupportRectsByOuterLayer.set(
|
|
136
|
+
bottomZ,
|
|
137
|
+
originalNodes
|
|
138
|
+
.filter((node) => isFreeNode(node) && node.availableZ.includes(bottomZ))
|
|
139
|
+
.map(nodeToRect),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
const promotedNodes: CapacityMeshNode[] = []
|
|
143
|
+
const promotedRects: XYRect[] = []
|
|
144
|
+
const candidateNodes = mutableOuterNodes
|
|
145
|
+
.filter(
|
|
146
|
+
(node) =>
|
|
147
|
+
node.width + EPS >= viaMinSize && node.height + EPS >= viaMinSize,
|
|
148
|
+
)
|
|
149
|
+
.sort((a, b) => rectArea(nodeToRect(b)) - rectArea(nodeToRect(a)))
|
|
150
|
+
|
|
151
|
+
for (const candidate of candidateNodes) {
|
|
152
|
+
const candidateZ = candidate.availableZ[0]!
|
|
153
|
+
const oppositeZ = candidateZ === topZ ? bottomZ : topZ
|
|
154
|
+
const candidateRect = nodeToRect(candidate)
|
|
155
|
+
const oppositeSupportRects =
|
|
156
|
+
freeSupportRectsByOuterLayer.get(oppositeZ) ?? []
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
!this.isTransitCompatibleAcrossIntermediateLayers({
|
|
160
|
+
rect: candidateRect,
|
|
161
|
+
fromZ: candidateZ,
|
|
162
|
+
toZ: oppositeZ,
|
|
163
|
+
obstaclesByLayer,
|
|
164
|
+
})
|
|
165
|
+
) {
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
if (!isFullyCoveredByRects(candidateRect, oppositeSupportRects)) {
|
|
169
|
+
continue
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
promotedNodes.push({
|
|
173
|
+
...candidate,
|
|
174
|
+
availableZ: [topZ, bottomZ],
|
|
175
|
+
layer: `z${topZ},${bottomZ}`,
|
|
176
|
+
})
|
|
177
|
+
promotedRects.push(candidateRect)
|
|
178
|
+
this.promotedNodeIds.add(candidate.capacityMeshNodeId)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let nextResidualId = 0
|
|
182
|
+
const residualNodes: CapacityMeshNode[] = []
|
|
183
|
+
|
|
184
|
+
for (const node of mutableOuterNodes) {
|
|
185
|
+
if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const nodeRect = nodeToRect(node)
|
|
190
|
+
const remainingPieces = subtractRects(nodeRect, promotedRects)
|
|
191
|
+
|
|
192
|
+
if (
|
|
193
|
+
remainingPieces.length === 1 &&
|
|
194
|
+
sameRect(remainingPieces[0]!, nodeRect)
|
|
195
|
+
) {
|
|
196
|
+
residualNodes.push(node)
|
|
197
|
+
continue
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const piece of remainingPieces) {
|
|
201
|
+
const residualNode = cloneNodeWithRect(
|
|
202
|
+
node,
|
|
203
|
+
piece,
|
|
204
|
+
`${node.capacityMeshNodeId}-outer-merge-${nextResidualId++}`,
|
|
205
|
+
)
|
|
206
|
+
residualNodes.push(residualNode)
|
|
207
|
+
this.residualNodeIds.add(residualNode.capacityMeshNodeId)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return [...immutableNodes, ...promotedNodes, ...residualNodes]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private buildObstaclesByLayer(layerCount: number): ObstacleWithRect[][] {
|
|
215
|
+
const out = Array.from(
|
|
216
|
+
{ length: layerCount },
|
|
217
|
+
() => [] as ObstacleWithRect[],
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
for (const obstacle of this.input.simpleRouteJson.obstacles ?? []) {
|
|
221
|
+
const baseRect = obstacleToXYRect(obstacle)
|
|
222
|
+
if (!baseRect) continue
|
|
223
|
+
const rect = padRect(baseRect, this.input.obstacleClearance ?? 0)
|
|
224
|
+
const zLayers = obstacleZs(obstacle, this.input.zIndexByName)
|
|
225
|
+
|
|
226
|
+
for (const z of zLayers) {
|
|
227
|
+
if (z < 0 || z >= layerCount) continue
|
|
228
|
+
out[z]!.push({ obstacle, rect })
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return out
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private isTransitCompatibleAcrossIntermediateLayers(params: {
|
|
236
|
+
rect: XYRect
|
|
237
|
+
fromZ: number
|
|
238
|
+
toZ: number
|
|
239
|
+
obstaclesByLayer: ObstacleWithRect[][]
|
|
240
|
+
}) {
|
|
241
|
+
const { rect, fromZ, toZ, obstaclesByLayer } = params
|
|
242
|
+
const lo = Math.min(fromZ, toZ)
|
|
243
|
+
const hi = Math.max(fromZ, toZ)
|
|
244
|
+
|
|
245
|
+
if (hi - lo < 2) return false
|
|
246
|
+
|
|
247
|
+
for (let z = lo + 1; z < hi; z++) {
|
|
248
|
+
const overlapping = (obstaclesByLayer[z] ?? []).filter((entry) =>
|
|
249
|
+
overlaps(entry.rect, rect),
|
|
250
|
+
)
|
|
251
|
+
if (overlapping.length === 0) return false
|
|
252
|
+
|
|
253
|
+
const nonCopperOverlap = overlapping.some(
|
|
254
|
+
(entry) => !entry.obstacle.isCopperPour,
|
|
255
|
+
)
|
|
256
|
+
if (nonCopperOverlap) return false
|
|
257
|
+
|
|
258
|
+
const copperRects = overlapping
|
|
259
|
+
.filter((entry) => entry.obstacle.isCopperPour)
|
|
260
|
+
.map((entry) => entry.rect)
|
|
261
|
+
|
|
262
|
+
if (!isFullyCoveredByRects(rect, copperRects)) {
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return true
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
override getOutput(): { outputNodes: CapacityMeshNode[] } {
|
|
271
|
+
return { outputNodes: this.outputNodes }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
override visualize(): GraphicsObject {
|
|
275
|
+
return {
|
|
276
|
+
title: "OuterLayerContainmentMergeSolver",
|
|
277
|
+
coordinateSystem: "cartesian",
|
|
278
|
+
rects: this.outputNodes.map((node) => {
|
|
279
|
+
const colors = getColorForZLayer(node.availableZ)
|
|
280
|
+
const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId)
|
|
281
|
+
const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId)
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
center: node.center,
|
|
285
|
+
width: node.width,
|
|
286
|
+
height: node.height,
|
|
287
|
+
stroke: isPromoted
|
|
288
|
+
? "rgba(22, 163, 74, 0.95)"
|
|
289
|
+
: isResidual
|
|
290
|
+
? "rgba(37, 99, 235, 0.95)"
|
|
291
|
+
: colors.stroke,
|
|
292
|
+
fill: node._containsObstacle
|
|
293
|
+
? "rgba(239, 68, 68, 0.35)"
|
|
294
|
+
: isPromoted
|
|
295
|
+
? "rgba(34, 197, 94, 0.28)"
|
|
296
|
+
: isResidual
|
|
297
|
+
? "rgba(59, 130, 246, 0.18)"
|
|
298
|
+
: colors.fill,
|
|
299
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
300
|
+
label: [
|
|
301
|
+
`node ${node.capacityMeshNodeId}`,
|
|
302
|
+
`z:${node.availableZ.join(",")}`,
|
|
303
|
+
].join("\n"),
|
|
304
|
+
}
|
|
305
|
+
}),
|
|
306
|
+
points: [],
|
|
307
|
+
lines: [],
|
|
308
|
+
texts: [],
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
2
|
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
CapacityMeshNode,
|
|
5
|
+
RTreeRect,
|
|
6
|
+
} from "../../types/capacity-mesh-types"
|
|
4
7
|
import { expandRectFromSeed } from "../../utils/expandRectFromSeed"
|
|
5
8
|
import { finalizeRects } from "../../utils/finalizeRects"
|
|
6
9
|
import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
|
|
7
10
|
import { rectsToMeshNodes } from "./rectsToMeshNodes"
|
|
8
11
|
import type { XYRect, Candidate3D, Placed3D } from "../../rectdiff-types"
|
|
9
|
-
import type { Obstacle } from "
|
|
12
|
+
import type { Obstacle } from "../../types/srj-types"
|
|
10
13
|
import RBush from "rbush"
|
|
11
14
|
import { rectToTree } from "../../utils/rectToTree"
|
|
12
15
|
import { sameTreeRect } from "../../utils/sameTreeRect"
|
|
@@ -7,11 +7,14 @@ import type {
|
|
|
7
7
|
Obstacle,
|
|
8
8
|
SimpleRouteConnection,
|
|
9
9
|
SimpleRouteJson,
|
|
10
|
-
} from "
|
|
11
|
-
import type { GridFill3DOptions, XYRect } from "
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
} from "../../types/srj-types"
|
|
11
|
+
import type { GridFill3DOptions, XYRect } from "../../rectdiff-types"
|
|
12
|
+
import type {
|
|
13
|
+
CapacityMeshNode,
|
|
14
|
+
RTreeRect,
|
|
15
|
+
} from "../../types/capacity-mesh-types"
|
|
16
|
+
import { RectDiffSeedingSolver } from "../RectDiffSeedingSolver/RectDiffSeedingSolver"
|
|
17
|
+
import { RectDiffExpansionSolver } from "../RectDiffExpansionSolver/RectDiffExpansionSolver"
|
|
15
18
|
import type { GraphicsObject } from "graphics-debug"
|
|
16
19
|
import RBush from "rbush"
|
|
17
20
|
import { buildObstacleIndexesByLayer } from "./buildObstacleIndexes"
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type { SimpleRouteJson } from "
|
|
1
|
+
import type { SimpleRouteJson } from "../../types/srj-types"
|
|
2
2
|
import RBush from "rbush"
|
|
3
|
-
import { computeInverseRects } from "
|
|
3
|
+
import { computeInverseRects } from "../RectDiffSeedingSolver/computeInverseRects"
|
|
4
4
|
import {
|
|
5
5
|
buildZIndexMap,
|
|
6
6
|
obstacleToXYRect,
|
|
7
7
|
obstacleZs,
|
|
8
|
-
} from "
|
|
9
|
-
import type { XYRect } from "
|
|
10
|
-
import type { RTreeRect } from "
|
|
11
|
-
import { padRect } from "
|
|
8
|
+
} from "../RectDiffSeedingSolver/layers"
|
|
9
|
+
import type { XYRect } from "../../rectdiff-types"
|
|
10
|
+
import type { RTreeRect } from "../../types/capacity-mesh-types"
|
|
11
|
+
import { padRect } from "../../utils/padRect"
|
|
12
12
|
|
|
13
13
|
export const buildObstacleIndexesByLayer = (params: {
|
|
14
14
|
srj: SimpleRouteJson
|
|
@@ -16,12 +16,12 @@ 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 "../../utils/isFullyOccupiedAtPoint"
|
|
20
20
|
import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
|
|
21
|
-
import { getColorForZLayer } from "
|
|
21
|
+
import { getColorForZLayer } from "../../utils/getColorForZLayer"
|
|
22
22
|
import RBush from "rbush"
|
|
23
|
-
import type { RTreeRect } from "
|
|
24
|
-
import { rectToTree } from "
|
|
23
|
+
import type { RTreeRect } from "../../types/capacity-mesh-types"
|
|
24
|
+
import { rectToTree } from "../../utils/rectToTree"
|
|
25
25
|
|
|
26
26
|
export type RectDiffSeedingSolverInput = {
|
|
27
27
|
simpleRouteJson: SimpleRouteJson
|
|
@@ -3,7 +3,7 @@ import { EPS, distancePointToRectEdges } from "../../utils/rectdiff-geometry"
|
|
|
3
3
|
import { isFullyOccupiedAtPoint } from "../../utils/isFullyOccupiedAtPoint"
|
|
4
4
|
import { longestFreeSpanAroundZ } from "./longestFreeSpanAroundZ"
|
|
5
5
|
import type RBush from "rbush"
|
|
6
|
-
import type { RTreeRect } from "
|
|
6
|
+
import type { RTreeRect } from "../../types/capacity-mesh-types"
|
|
7
7
|
const quantize = (value: number, precision = 1e-6) =>
|
|
8
8
|
Math.round(value / precision) * precision
|
|
9
9
|
|