@tscircuit/rectdiff 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +97 -12
- package/dist/index.js +714 -81
- package/lib/RectDiffPipeline.ts +79 -13
- package/lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts +284 -0
- package/lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts +213 -0
- package/lib/solvers/GapFillSolver/GapFillSolverPipeline.ts +129 -0
- package/lib/solvers/GapFillSolver/edge-constants.ts +48 -0
- package/lib/solvers/GapFillSolver/getBoundsFromCorners.ts +10 -0
- package/lib/solvers/GapFillSolver/projectToUncoveredSegments.ts +92 -0
- package/lib/solvers/GapFillSolver/visuallyOffsetLine.ts +32 -0
- package/lib/solvers/RectDiffSolver.ts +1 -33
- package/package.json +9 -6
- package/tests/board-outline.test.ts +1 -1
- package/tsconfig.json +4 -0
- package/vite.config.ts +6 -0
- package/lib/solvers/rectdiff/gapfill/detection/deduplicateGaps.ts +0 -28
- package/lib/solvers/rectdiff/gapfill/detection/findAllGaps.ts +0 -83
- package/lib/solvers/rectdiff/gapfill/detection/findGapsOnLayer.ts +0 -100
- package/lib/solvers/rectdiff/gapfill/detection/mergeUncoveredCells.ts +0 -75
- package/lib/solvers/rectdiff/gapfill/detection.ts +0 -3
- package/lib/solvers/rectdiff/gapfill/engine/addPlacement.ts +0 -27
- package/lib/solvers/rectdiff/gapfill/engine/calculateCoverage.ts +0 -44
- package/lib/solvers/rectdiff/gapfill/engine/findUncoveredPoints.ts +0 -43
- package/lib/solvers/rectdiff/gapfill/engine/getGapFillProgress.ts +0 -42
- package/lib/solvers/rectdiff/gapfill/engine/initGapFillState.ts +0 -57
- package/lib/solvers/rectdiff/gapfill/engine/stepGapFill.ts +0 -128
- package/lib/solvers/rectdiff/gapfill/engine/tryExpandGap.ts +0 -78
- package/lib/solvers/rectdiff/gapfill/engine.ts +0 -7
- package/lib/solvers/rectdiff/gapfill/types.ts +0 -60
- package/lib/solvers/rectdiff/subsolvers/GapFillSubSolver.ts +0 -253
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
// lib/solvers/rectdiff/gapfill/detection/mergeUncoveredCells.ts
|
|
2
|
-
import type { XYRect } from "../../types"
|
|
3
|
-
import { EPS } from "../../geometry"
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Merge adjacent uncovered cells into larger rectangles using a greedy approach.
|
|
7
|
-
*/
|
|
8
|
-
export function mergeUncoveredCells(
|
|
9
|
-
cells: Array<{ x: number; y: number; w: number; h: number }>,
|
|
10
|
-
): XYRect[] {
|
|
11
|
-
if (cells.length === 0) return []
|
|
12
|
-
|
|
13
|
-
// Group cells by their left edge and width
|
|
14
|
-
const byXW = new Map<string, typeof cells>()
|
|
15
|
-
for (const c of cells) {
|
|
16
|
-
const key = `${c.x.toFixed(9)}|${c.w.toFixed(9)}`
|
|
17
|
-
const arr = byXW.get(key) ?? []
|
|
18
|
-
arr.push(c)
|
|
19
|
-
byXW.set(key, arr)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Within each vertical strip, merge adjacent cells
|
|
23
|
-
const verticalStrips: XYRect[] = []
|
|
24
|
-
for (const stripCells of byXW.values()) {
|
|
25
|
-
// Sort by y
|
|
26
|
-
stripCells.sort((a, b) => a.y - b.y)
|
|
27
|
-
|
|
28
|
-
let current: XYRect | null = null
|
|
29
|
-
for (const c of stripCells) {
|
|
30
|
-
if (!current) {
|
|
31
|
-
current = { x: c.x, y: c.y, width: c.w, height: c.h }
|
|
32
|
-
} else if (Math.abs(current.y + current.height - c.y) < EPS) {
|
|
33
|
-
// Adjacent vertically, merge
|
|
34
|
-
current.height += c.h
|
|
35
|
-
} else {
|
|
36
|
-
// Gap, save current and start new
|
|
37
|
-
verticalStrips.push(current)
|
|
38
|
-
current = { x: c.x, y: c.y, width: c.w, height: c.h }
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (current) verticalStrips.push(current)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Now try to merge horizontal strips with same y and height
|
|
45
|
-
const byYH = new Map<string, XYRect[]>()
|
|
46
|
-
for (const r of verticalStrips) {
|
|
47
|
-
const key = `${r.y.toFixed(9)}|${r.height.toFixed(9)}`
|
|
48
|
-
const arr = byYH.get(key) ?? []
|
|
49
|
-
arr.push(r)
|
|
50
|
-
byYH.set(key, arr)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const merged: XYRect[] = []
|
|
54
|
-
for (const rowRects of byYH.values()) {
|
|
55
|
-
// Sort by x
|
|
56
|
-
rowRects.sort((a, b) => a.x - b.x)
|
|
57
|
-
|
|
58
|
-
let current: XYRect | null = null
|
|
59
|
-
for (const r of rowRects) {
|
|
60
|
-
if (!current) {
|
|
61
|
-
current = { ...r }
|
|
62
|
-
} else if (Math.abs(current.x + current.width - r.x) < EPS) {
|
|
63
|
-
// Adjacent horizontally, merge
|
|
64
|
-
current.width += r.width
|
|
65
|
-
} else {
|
|
66
|
-
// Gap, save current and start new
|
|
67
|
-
merged.push(current)
|
|
68
|
-
current = { ...r }
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (current) merged.push(current)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return merged
|
|
75
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// lib/solvers/rectdiff/gapfill/engine/addPlacement.ts
|
|
2
|
-
import type { Placed3D, XYRect } from "../../types"
|
|
3
|
-
import type { GapFillState } from "../types"
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Add a new placement to the state.
|
|
7
|
-
*/
|
|
8
|
-
export function addPlacement(
|
|
9
|
-
state: GapFillState,
|
|
10
|
-
{
|
|
11
|
-
rect,
|
|
12
|
-
zLayers,
|
|
13
|
-
}: {
|
|
14
|
-
rect: XYRect
|
|
15
|
-
zLayers: number[]
|
|
16
|
-
},
|
|
17
|
-
): void {
|
|
18
|
-
const placed: Placed3D = { rect, zLayers: [...zLayers] }
|
|
19
|
-
state.placed.push(placed)
|
|
20
|
-
|
|
21
|
-
for (const z of zLayers) {
|
|
22
|
-
if (!state.placedByLayer[z]) {
|
|
23
|
-
state.placedByLayer[z] = []
|
|
24
|
-
}
|
|
25
|
-
state.placedByLayer[z]!.push(rect)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// lib/solvers/rectdiff/gapfill/engine/calculateCoverage.ts
|
|
2
|
-
import type { LayerContext } from "../types"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Calculate coverage percentage (0-1).
|
|
6
|
-
*/
|
|
7
|
-
export function calculateCoverage(
|
|
8
|
-
{ sampleResolution = 0.1 }: { sampleResolution?: number },
|
|
9
|
-
ctx: LayerContext,
|
|
10
|
-
): number {
|
|
11
|
-
const { bounds, layerCount, obstaclesByLayer, placedByLayer } = ctx
|
|
12
|
-
|
|
13
|
-
let totalPoints = 0
|
|
14
|
-
let coveredPoints = 0
|
|
15
|
-
|
|
16
|
-
for (let z = 0; z < layerCount; z++) {
|
|
17
|
-
const obstacles = obstaclesByLayer[z] ?? []
|
|
18
|
-
const placed = placedByLayer[z] ?? []
|
|
19
|
-
const allRects = [...obstacles, ...placed]
|
|
20
|
-
|
|
21
|
-
for (
|
|
22
|
-
let x = bounds.x;
|
|
23
|
-
x <= bounds.x + bounds.width;
|
|
24
|
-
x += sampleResolution
|
|
25
|
-
) {
|
|
26
|
-
for (
|
|
27
|
-
let y = bounds.y;
|
|
28
|
-
y <= bounds.y + bounds.height;
|
|
29
|
-
y += sampleResolution
|
|
30
|
-
) {
|
|
31
|
-
totalPoints++
|
|
32
|
-
|
|
33
|
-
const isCovered = allRects.some(
|
|
34
|
-
(r) =>
|
|
35
|
-
x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
if (isCovered) coveredPoints++
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return totalPoints > 0 ? coveredPoints / totalPoints : 1
|
|
44
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// lib/solvers/rectdiff/gapfill/engine/findUncoveredPoints.ts
|
|
2
|
-
import type { LayerContext } from "../types"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Find uncovered points for debugging gaps.
|
|
6
|
-
*/
|
|
7
|
-
export function findUncoveredPoints(
|
|
8
|
-
{ sampleResolution = 0.05 }: { sampleResolution?: number },
|
|
9
|
-
ctx: LayerContext,
|
|
10
|
-
): Array<{ x: number; y: number; z: number }> {
|
|
11
|
-
const { bounds, layerCount, obstaclesByLayer, placedByLayer } = ctx
|
|
12
|
-
|
|
13
|
-
const uncovered: Array<{ x: number; y: number; z: number }> = []
|
|
14
|
-
|
|
15
|
-
for (let z = 0; z < layerCount; z++) {
|
|
16
|
-
const obstacles = obstaclesByLayer[z] ?? []
|
|
17
|
-
const placed = placedByLayer[z] ?? []
|
|
18
|
-
const allRects = [...obstacles, ...placed]
|
|
19
|
-
|
|
20
|
-
for (
|
|
21
|
-
let x = bounds.x;
|
|
22
|
-
x <= bounds.x + bounds.width;
|
|
23
|
-
x += sampleResolution
|
|
24
|
-
) {
|
|
25
|
-
for (
|
|
26
|
-
let y = bounds.y;
|
|
27
|
-
y <= bounds.y + bounds.height;
|
|
28
|
-
y += sampleResolution
|
|
29
|
-
) {
|
|
30
|
-
const isCovered = allRects.some(
|
|
31
|
-
(r) =>
|
|
32
|
-
x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
if (!isCovered) {
|
|
36
|
-
uncovered.push({ x, y, z })
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return uncovered
|
|
43
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// lib/solvers/rectdiff/gapfill/engine/getGapFillProgress.ts
|
|
2
|
-
import type { GapFillState } from "../types"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Get progress as a number between 0 and 1.
|
|
6
|
-
* Accounts for four-stage processing (scan → select → expand → place for each gap).
|
|
7
|
-
*/
|
|
8
|
-
export function getGapFillProgress(state: GapFillState): number {
|
|
9
|
-
if (state.done) return 1
|
|
10
|
-
|
|
11
|
-
const iterationProgress = state.iteration / state.options.maxIterations
|
|
12
|
-
const gapProgress =
|
|
13
|
-
state.gapsFound.length > 0 ? state.gapIndex / state.gapsFound.length : 0
|
|
14
|
-
|
|
15
|
-
// Add sub-progress within current gap based on stage
|
|
16
|
-
let stageProgress = 0
|
|
17
|
-
switch (state.stage) {
|
|
18
|
-
case "scan":
|
|
19
|
-
stageProgress = 0
|
|
20
|
-
break
|
|
21
|
-
case "select":
|
|
22
|
-
stageProgress = 0.25
|
|
23
|
-
break
|
|
24
|
-
case "expand":
|
|
25
|
-
stageProgress = 0.5
|
|
26
|
-
break
|
|
27
|
-
case "place":
|
|
28
|
-
stageProgress = 0.75
|
|
29
|
-
break
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const gapStageProgress =
|
|
33
|
-
state.gapsFound.length > 0
|
|
34
|
-
? stageProgress / (state.gapsFound.length * 4) // 4 stages per gap
|
|
35
|
-
: 0
|
|
36
|
-
|
|
37
|
-
return Math.min(
|
|
38
|
-
0.999,
|
|
39
|
-
iterationProgress +
|
|
40
|
-
(gapProgress + gapStageProgress) / state.options.maxIterations,
|
|
41
|
-
)
|
|
42
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// lib/solvers/rectdiff/gapfill/engine/initGapFillState.ts
|
|
2
|
-
import type { Placed3D } from "../../types"
|
|
3
|
-
import type { GapFillState, GapFillOptions, LayerContext } from "../types"
|
|
4
|
-
|
|
5
|
-
const DEFAULT_OPTIONS: GapFillOptions = {
|
|
6
|
-
minWidth: 0.1,
|
|
7
|
-
minHeight: 0.1,
|
|
8
|
-
maxIterations: 10,
|
|
9
|
-
targetCoverage: 0.999,
|
|
10
|
-
scanResolution: 0.5,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Initialize the gap fill state from existing rectdiff state.
|
|
15
|
-
*/
|
|
16
|
-
export function initGapFillState(
|
|
17
|
-
{
|
|
18
|
-
placed,
|
|
19
|
-
options,
|
|
20
|
-
}: {
|
|
21
|
-
placed: Placed3D[]
|
|
22
|
-
options?: Partial<GapFillOptions>
|
|
23
|
-
},
|
|
24
|
-
ctx: LayerContext,
|
|
25
|
-
): GapFillState {
|
|
26
|
-
const opts = { ...DEFAULT_OPTIONS, ...options }
|
|
27
|
-
|
|
28
|
-
// Deep copy placed arrays to avoid mutation issues
|
|
29
|
-
const placedCopy = placed.map((p) => ({
|
|
30
|
-
rect: { ...p.rect },
|
|
31
|
-
zLayers: [...p.zLayers],
|
|
32
|
-
}))
|
|
33
|
-
|
|
34
|
-
const placedByLayerCopy = ctx.placedByLayer.map((layer) =>
|
|
35
|
-
layer.map((r) => ({ ...r })),
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
bounds: { ...ctx.bounds },
|
|
40
|
-
layerCount: ctx.layerCount,
|
|
41
|
-
obstaclesByLayer: ctx.obstaclesByLayer,
|
|
42
|
-
placed: placedCopy,
|
|
43
|
-
placedByLayer: placedByLayerCopy,
|
|
44
|
-
options: opts,
|
|
45
|
-
iteration: 0,
|
|
46
|
-
gapsFound: [],
|
|
47
|
-
gapIndex: 0,
|
|
48
|
-
done: false,
|
|
49
|
-
initialGapCount: 0,
|
|
50
|
-
filledCount: 0,
|
|
51
|
-
// Four-stage visualization state
|
|
52
|
-
stage: "scan",
|
|
53
|
-
currentGap: null,
|
|
54
|
-
currentSeed: null,
|
|
55
|
-
expandedRect: null,
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
// lib/solvers/rectdiff/gapfill/engine/stepGapFill.ts
|
|
2
|
-
import type { GapFillState } from "../types"
|
|
3
|
-
import { findAllGaps } from "../detection"
|
|
4
|
-
import { tryExpandGap } from "./tryExpandGap"
|
|
5
|
-
import { addPlacement } from "./addPlacement"
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Perform one step of gap filling with four-stage visualization.
|
|
9
|
-
* Stages: scan → select → expand → place
|
|
10
|
-
* Returns true if still working, false if done.
|
|
11
|
-
*/
|
|
12
|
-
export function stepGapFill(state: GapFillState): boolean {
|
|
13
|
-
if (state.done) return false
|
|
14
|
-
|
|
15
|
-
switch (state.stage) {
|
|
16
|
-
case "scan": {
|
|
17
|
-
// Stage 1: Gap detection/scanning
|
|
18
|
-
|
|
19
|
-
// Check if we need to find new gaps
|
|
20
|
-
if (
|
|
21
|
-
state.gapsFound.length === 0 ||
|
|
22
|
-
state.gapIndex >= state.gapsFound.length
|
|
23
|
-
) {
|
|
24
|
-
// Check if we've hit max iterations
|
|
25
|
-
if (state.iteration >= state.options.maxIterations) {
|
|
26
|
-
state.done = true
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Find new gaps
|
|
31
|
-
state.gapsFound = findAllGaps(
|
|
32
|
-
{
|
|
33
|
-
scanResolution: state.options.scanResolution,
|
|
34
|
-
minWidth: state.options.minWidth,
|
|
35
|
-
minHeight: state.options.minHeight,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
bounds: state.bounds,
|
|
39
|
-
layerCount: state.layerCount,
|
|
40
|
-
obstaclesByLayer: state.obstaclesByLayer,
|
|
41
|
-
placedByLayer: state.placedByLayer,
|
|
42
|
-
},
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
if (state.iteration === 0) {
|
|
46
|
-
state.initialGapCount = state.gapsFound.length
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
state.gapIndex = 0
|
|
50
|
-
state.iteration++
|
|
51
|
-
|
|
52
|
-
// If no gaps found, we're done
|
|
53
|
-
if (state.gapsFound.length === 0) {
|
|
54
|
-
state.done = true
|
|
55
|
-
return false
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Move to select stage
|
|
60
|
-
state.stage = "select"
|
|
61
|
-
return true
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
case "select": {
|
|
65
|
-
// Stage 2: Show the gap being targeted
|
|
66
|
-
if (state.gapIndex >= state.gapsFound.length) {
|
|
67
|
-
// No more gaps in this iteration, go back to scan
|
|
68
|
-
state.stage = "scan"
|
|
69
|
-
return true
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
state.currentGap = state.gapsFound[state.gapIndex]!
|
|
73
|
-
state.currentSeed = {
|
|
74
|
-
x: state.currentGap.centerX,
|
|
75
|
-
y: state.currentGap.centerY,
|
|
76
|
-
}
|
|
77
|
-
state.expandedRect = null
|
|
78
|
-
|
|
79
|
-
// Move to expand stage
|
|
80
|
-
state.stage = "expand"
|
|
81
|
-
return true
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
case "expand": {
|
|
85
|
-
// Stage 3: Show expansion attempt
|
|
86
|
-
if (!state.currentGap) {
|
|
87
|
-
// Shouldn't happen, but handle gracefully
|
|
88
|
-
state.stage = "select"
|
|
89
|
-
return true
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Try to expand from the current seed
|
|
93
|
-
const expandedRect = tryExpandGap(state, {
|
|
94
|
-
gap: state.currentGap,
|
|
95
|
-
seed: state.currentSeed!,
|
|
96
|
-
})
|
|
97
|
-
state.expandedRect = expandedRect
|
|
98
|
-
|
|
99
|
-
// Move to place stage
|
|
100
|
-
state.stage = "place"
|
|
101
|
-
return true
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
case "place": {
|
|
105
|
-
// Stage 4: Show the placed result
|
|
106
|
-
if (state.expandedRect && state.currentGap) {
|
|
107
|
-
// Actually place the rectangle
|
|
108
|
-
addPlacement(state, {
|
|
109
|
-
rect: state.expandedRect,
|
|
110
|
-
zLayers: state.currentGap.zLayers,
|
|
111
|
-
})
|
|
112
|
-
state.filledCount++
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Move to next gap and reset to select stage
|
|
116
|
-
state.gapIndex++
|
|
117
|
-
state.currentGap = null
|
|
118
|
-
state.currentSeed = null
|
|
119
|
-
state.expandedRect = null
|
|
120
|
-
state.stage = "select"
|
|
121
|
-
return true
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
default:
|
|
125
|
-
state.stage = "scan"
|
|
126
|
-
return true
|
|
127
|
-
}
|
|
128
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// lib/solvers/rectdiff/gapfill/engine/tryExpandGap.ts
|
|
2
|
-
import type { XYRect } from "../../types"
|
|
3
|
-
import type { GapFillState, GapRegion } from "../types"
|
|
4
|
-
import { expandRectFromSeed } from "../../geometry"
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Try to expand a rectangle from a seed point within the gap.
|
|
8
|
-
* Returns the expanded rectangle or null if expansion fails.
|
|
9
|
-
*/
|
|
10
|
-
export function tryExpandGap(
|
|
11
|
-
state: GapFillState,
|
|
12
|
-
{
|
|
13
|
-
gap,
|
|
14
|
-
seed,
|
|
15
|
-
}: {
|
|
16
|
-
gap: GapRegion
|
|
17
|
-
seed: { x: number; y: number }
|
|
18
|
-
},
|
|
19
|
-
): XYRect | null {
|
|
20
|
-
// Build blockers for the gap's z-layers
|
|
21
|
-
const blockers: XYRect[] = []
|
|
22
|
-
for (const z of gap.zLayers) {
|
|
23
|
-
blockers.push(...(state.obstaclesByLayer[z] ?? []))
|
|
24
|
-
blockers.push(...(state.placedByLayer[z] ?? []))
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Try to expand from the seed point
|
|
28
|
-
const rect = expandRectFromSeed({
|
|
29
|
-
startX: seed.x,
|
|
30
|
-
startY: seed.y,
|
|
31
|
-
gridSize: Math.min(gap.rect.width, gap.rect.height),
|
|
32
|
-
bounds: state.bounds,
|
|
33
|
-
blockers,
|
|
34
|
-
initialCellRatio: 0,
|
|
35
|
-
maxAspectRatio: null,
|
|
36
|
-
minReq: { width: state.options.minWidth, height: state.options.minHeight },
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
if (!rect) {
|
|
40
|
-
// Try additional seed points within the gap
|
|
41
|
-
const seeds = [
|
|
42
|
-
{ x: gap.rect.x + state.options.minWidth / 2, y: gap.centerY },
|
|
43
|
-
{
|
|
44
|
-
x: gap.rect.x + gap.rect.width - state.options.minWidth / 2,
|
|
45
|
-
y: gap.centerY,
|
|
46
|
-
},
|
|
47
|
-
{ x: gap.centerX, y: gap.rect.y + state.options.minHeight / 2 },
|
|
48
|
-
{
|
|
49
|
-
x: gap.centerX,
|
|
50
|
-
y: gap.rect.y + gap.rect.height - state.options.minHeight / 2,
|
|
51
|
-
},
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
for (const altSeed of seeds) {
|
|
55
|
-
const altRect = expandRectFromSeed({
|
|
56
|
-
startX: altSeed.x,
|
|
57
|
-
startY: altSeed.y,
|
|
58
|
-
gridSize: Math.min(gap.rect.width, gap.rect.height),
|
|
59
|
-
bounds: state.bounds,
|
|
60
|
-
blockers,
|
|
61
|
-
initialCellRatio: 0,
|
|
62
|
-
maxAspectRatio: null,
|
|
63
|
-
minReq: {
|
|
64
|
-
width: state.options.minWidth,
|
|
65
|
-
height: state.options.minHeight,
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
if (altRect) {
|
|
70
|
-
return altRect
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return null
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return rect
|
|
78
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// lib/solvers/rectdiff/gapfill/types.ts
|
|
2
|
-
import type { XYRect, Placed3D } from "../types"
|
|
3
|
-
|
|
4
|
-
export interface GapFillOptions {
|
|
5
|
-
/** Minimum width for gap-fill rectangles (can be smaller than main solver) */
|
|
6
|
-
minWidth: number
|
|
7
|
-
/** Minimum height for gap-fill rectangles */
|
|
8
|
-
minHeight: number
|
|
9
|
-
/** Maximum iterations to prevent infinite loops */
|
|
10
|
-
maxIterations: number
|
|
11
|
-
/** Target coverage percentage (0-1) to stop early */
|
|
12
|
-
targetCoverage: number
|
|
13
|
-
/** Grid resolution for gap detection */
|
|
14
|
-
scanResolution: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface GapRegion {
|
|
18
|
-
/** Bounding box of the gap */
|
|
19
|
-
rect: XYRect
|
|
20
|
-
/** Z-layers where this gap exists */
|
|
21
|
-
zLayers: number[]
|
|
22
|
-
/** Center point for seeding */
|
|
23
|
-
centerX: number
|
|
24
|
-
centerY: number
|
|
25
|
-
/** Approximate area of the gap */
|
|
26
|
-
area: number
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface GapFillState {
|
|
30
|
-
bounds: XYRect
|
|
31
|
-
layerCount: number
|
|
32
|
-
obstaclesByLayer: XYRect[][]
|
|
33
|
-
placed: Placed3D[]
|
|
34
|
-
placedByLayer: XYRect[][]
|
|
35
|
-
options: GapFillOptions
|
|
36
|
-
|
|
37
|
-
// Progress tracking
|
|
38
|
-
iteration: number
|
|
39
|
-
gapsFound: GapRegion[]
|
|
40
|
-
gapIndex: number
|
|
41
|
-
done: boolean
|
|
42
|
-
|
|
43
|
-
// Stats
|
|
44
|
-
initialGapCount: number
|
|
45
|
-
filledCount: number
|
|
46
|
-
|
|
47
|
-
// Four-stage visualization state
|
|
48
|
-
stage: "scan" | "select" | "expand" | "place"
|
|
49
|
-
currentGap: GapRegion | null
|
|
50
|
-
currentSeed: { x: number; y: number } | null
|
|
51
|
-
expandedRect: XYRect | null
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Context for layer-based operations shared across gap fill functions */
|
|
55
|
-
export interface LayerContext {
|
|
56
|
-
bounds: XYRect
|
|
57
|
-
layerCount: number
|
|
58
|
-
obstaclesByLayer: XYRect[][]
|
|
59
|
-
placedByLayer: XYRect[][]
|
|
60
|
-
}
|