@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.
Files changed (30) hide show
  1. package/dist/index.d.ts +97 -12
  2. package/dist/index.js +714 -81
  3. package/lib/RectDiffPipeline.ts +79 -13
  4. package/lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts +284 -0
  5. package/lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts +213 -0
  6. package/lib/solvers/GapFillSolver/GapFillSolverPipeline.ts +129 -0
  7. package/lib/solvers/GapFillSolver/edge-constants.ts +48 -0
  8. package/lib/solvers/GapFillSolver/getBoundsFromCorners.ts +10 -0
  9. package/lib/solvers/GapFillSolver/projectToUncoveredSegments.ts +92 -0
  10. package/lib/solvers/GapFillSolver/visuallyOffsetLine.ts +32 -0
  11. package/lib/solvers/RectDiffSolver.ts +1 -33
  12. package/package.json +9 -6
  13. package/tests/board-outline.test.ts +1 -1
  14. package/tsconfig.json +4 -0
  15. package/vite.config.ts +6 -0
  16. package/lib/solvers/rectdiff/gapfill/detection/deduplicateGaps.ts +0 -28
  17. package/lib/solvers/rectdiff/gapfill/detection/findAllGaps.ts +0 -83
  18. package/lib/solvers/rectdiff/gapfill/detection/findGapsOnLayer.ts +0 -100
  19. package/lib/solvers/rectdiff/gapfill/detection/mergeUncoveredCells.ts +0 -75
  20. package/lib/solvers/rectdiff/gapfill/detection.ts +0 -3
  21. package/lib/solvers/rectdiff/gapfill/engine/addPlacement.ts +0 -27
  22. package/lib/solvers/rectdiff/gapfill/engine/calculateCoverage.ts +0 -44
  23. package/lib/solvers/rectdiff/gapfill/engine/findUncoveredPoints.ts +0 -43
  24. package/lib/solvers/rectdiff/gapfill/engine/getGapFillProgress.ts +0 -42
  25. package/lib/solvers/rectdiff/gapfill/engine/initGapFillState.ts +0 -57
  26. package/lib/solvers/rectdiff/gapfill/engine/stepGapFill.ts +0 -128
  27. package/lib/solvers/rectdiff/gapfill/engine/tryExpandGap.ts +0 -78
  28. package/lib/solvers/rectdiff/gapfill/engine.ts +0 -7
  29. package/lib/solvers/rectdiff/gapfill/types.ts +0 -60
  30. package/lib/solvers/rectdiff/subsolvers/GapFillSubSolver.ts +0 -253
@@ -1,253 +0,0 @@
1
- // lib/solvers/rectdiff/subsolvers/GapFillSubSolver.ts
2
- import { BaseSolver } from "@tscircuit/solver-utils"
3
- import type { GraphicsObject } from "graphics-debug"
4
- import type { XYRect, Placed3D } from "../types"
5
- import type {
6
- GapFillState,
7
- GapFillOptions,
8
- LayerContext,
9
- } from "../gapfill/types"
10
- import {
11
- initGapFillState,
12
- stepGapFill,
13
- getGapFillProgress,
14
- } from "../gapfill/engine"
15
-
16
- /**
17
- * A sub-solver that fills empty spaces (gaps) left by the main grid-based
18
- * placement algorithm.
19
- *
20
- * The preceding grid-based placement is fast but can leave irregular un-placed
21
- * areas. This solver maximizes board coverage by finding and filling these
22
- * gaps, which is critical for producing a high-quality capacity mesh.
23
- *
24
- * The core of the algorithm is its gap-detection phase. It works by first
25
- * collecting all unique x and y-coordinates from the edges of existing
26
- * obstacles and placed rectangles. This set of coordinates is supplemented by a
27
- * uniform grid based on the `scanResolution` parameter. Together, these form a
28
- * non-uniform grid of cells. The solver then tests the center of each cell for
29
- * coverage. Contiguous uncovered cells are merged into larger, maximal
30
- * rectangles, which become the candidate gaps to be filled.
31
- *
32
- * Once a prioritized list of gaps is generated (favoring larger, multi-layer
33
- * gaps), the solver iteratively attempts to fill each one by expanding a new
34
- * rectangle from a seed point until it collides with an existing boundary.
35
- *
36
- * The time complexity is dominated by the gap detection, which is approximately
37
- * O((N+1/R)^2 * B), where N is the number of objects, R is the scan
38
- * resolution, and B is the number of blockers. The algorithm's performance is
39
- * therefore highly dependent on the `scanResolution`. It is a heuristic
40
- * designed to be "fast enough" by avoiding a brute-force search, instead
41
- * relying on this grid-based cell checking to find significant gaps.
42
- */
43
- export class GapFillSubSolver extends BaseSolver {
44
- private state: GapFillState
45
- private layerCtx: LayerContext
46
-
47
- constructor(params: {
48
- placed: Placed3D[]
49
- options?: Partial<GapFillOptions>
50
- layerCtx: LayerContext
51
- }) {
52
- super()
53
- this.layerCtx = params.layerCtx
54
- this.state = initGapFillState(
55
- {
56
- placed: params.placed,
57
- options: params.options,
58
- },
59
- params.layerCtx,
60
- )
61
- }
62
-
63
- /**
64
- * Execute one step of the gap fill algorithm.
65
- * Each gap goes through four stages: scan for gaps, select a target gap,
66
- * expand a rectangle from seed point, then place the final result.
67
- */
68
- override _step() {
69
- const stillWorking = stepGapFill(this.state)
70
- if (!stillWorking) {
71
- this.solved = true
72
- }
73
- }
74
-
75
- /**
76
- * Calculate progress as a value between 0 and 1.
77
- * Accounts for iterations, gaps processed, and current stage within each gap.
78
- */
79
- computeProgress(): number {
80
- return getGapFillProgress(this.state)
81
- }
82
-
83
- /**
84
- * Get all placed rectangles including original ones plus newly created gap-fill rectangles.
85
- */
86
- getPlaced(): Placed3D[] {
87
- return this.state.placed
88
- }
89
-
90
- /**
91
- * Get placed rectangles organized by Z-layer for efficient layer-based operations.
92
- */
93
- getPlacedByLayer(): XYRect[][] {
94
- return this.state.placedByLayer
95
- }
96
-
97
- override getOutput() {
98
- return {
99
- placed: this.state.placed,
100
- placedByLayer: this.state.placedByLayer,
101
- filledCount: this.state.filledCount,
102
- }
103
- }
104
-
105
- /** Zen visualization: show four-stage gap filling process. */
106
- override visualize(): GraphicsObject {
107
- const rects: NonNullable<GraphicsObject["rects"]> = []
108
- const points: NonNullable<GraphicsObject["points"]> = []
109
-
110
- // Board bounds (subtle)
111
- rects.push({
112
- center: {
113
- x: this.layerCtx.bounds.x + this.layerCtx.bounds.width / 2,
114
- y: this.layerCtx.bounds.y + this.layerCtx.bounds.height / 2,
115
- },
116
- width: this.layerCtx.bounds.width,
117
- height: this.layerCtx.bounds.height,
118
- fill: "none",
119
- stroke: "#e5e7eb",
120
- label: "",
121
- })
122
-
123
- switch (this.state.stage) {
124
- case "scan": {
125
- // Stage 1: Show scanning/detection phase with light blue overlay
126
- rects.push({
127
- center: {
128
- x: this.layerCtx.bounds.x + this.layerCtx.bounds.width / 2,
129
- y: this.layerCtx.bounds.y + this.layerCtx.bounds.height / 2,
130
- },
131
- width: this.layerCtx.bounds.width,
132
- height: this.layerCtx.bounds.height,
133
- fill: "#dbeafe",
134
- stroke: "#3b82f6",
135
- label: "scanning",
136
- })
137
- break
138
- }
139
-
140
- case "select": {
141
- // Stage 2: Show the gap being targeted (red outline)
142
- if (this.state.currentGap) {
143
- rects.push({
144
- center: {
145
- x:
146
- this.state.currentGap.rect.x +
147
- this.state.currentGap.rect.width / 2,
148
- y:
149
- this.state.currentGap.rect.y +
150
- this.state.currentGap.rect.height / 2,
151
- },
152
- width: this.state.currentGap.rect.width,
153
- height: this.state.currentGap.rect.height,
154
- fill: "#fecaca",
155
- stroke: "#ef4444",
156
- label: "target gap",
157
- })
158
-
159
- // Show the seed point
160
- if (this.state.currentSeed) {
161
- points.push({
162
- x: this.state.currentSeed.x,
163
- y: this.state.currentSeed.y,
164
- color: "#dc2626",
165
- label: "seed",
166
- })
167
- }
168
- }
169
- break
170
- }
171
-
172
- case "expand": {
173
- // Stage 3: Show expansion attempt (yellow growing rectangle + seed)
174
- if (this.state.currentGap) {
175
- // Show gap outline (faded)
176
- rects.push({
177
- center: {
178
- x:
179
- this.state.currentGap.rect.x +
180
- this.state.currentGap.rect.width / 2,
181
- y:
182
- this.state.currentGap.rect.y +
183
- this.state.currentGap.rect.height / 2,
184
- },
185
- width: this.state.currentGap.rect.width,
186
- height: this.state.currentGap.rect.height,
187
- fill: "none",
188
- stroke: "#f87171",
189
- label: "",
190
- })
191
- }
192
-
193
- if (this.state.currentSeed) {
194
- // Show seed point
195
- points.push({
196
- x: this.state.currentSeed.x,
197
- y: this.state.currentSeed.y,
198
- color: "#f59e0b",
199
- label: "expanding",
200
- })
201
- }
202
-
203
- if (this.state.expandedRect) {
204
- // Show expanded rectangle
205
- rects.push({
206
- center: {
207
- x: this.state.expandedRect.x + this.state.expandedRect.width / 2,
208
- y: this.state.expandedRect.y + this.state.expandedRect.height / 2,
209
- },
210
- width: this.state.expandedRect.width,
211
- height: this.state.expandedRect.height,
212
- fill: "#fef3c7",
213
- stroke: "#f59e0b",
214
- label: "expanding",
215
- })
216
- }
217
- break
218
- }
219
-
220
- case "place": {
221
- // Stage 4: Show final placed rectangle (green)
222
- if (this.state.expandedRect) {
223
- rects.push({
224
- center: {
225
- x: this.state.expandedRect.x + this.state.expandedRect.width / 2,
226
- y: this.state.expandedRect.y + this.state.expandedRect.height / 2,
227
- },
228
- width: this.state.expandedRect.width,
229
- height: this.state.expandedRect.height,
230
- fill: "#bbf7d0",
231
- stroke: "#22c55e",
232
- label: "placed",
233
- })
234
- }
235
- break
236
- }
237
- }
238
-
239
- const stageNames = {
240
- scan: "scanning",
241
- select: "selecting",
242
- expand: "expanding",
243
- place: "placing",
244
- }
245
-
246
- return {
247
- title: `GapFill (${stageNames[this.state.stage]}): ${this.state.filledCount} filled`,
248
- coordinateSystem: "cartesian",
249
- rects,
250
- points,
251
- }
252
- }
253
- }