@tscircuit/rectdiff 0.0.25 → 0.0.27

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 (42) hide show
  1. package/.github/workflows/bun-pver-release.yml +45 -24
  2. package/dist/index.d.ts +46 -0
  3. package/dist/index.js +778 -220
  4. package/lib/RectDiffPipeline.ts +46 -0
  5. package/lib/solvers/AdjacentLayerContainmentMergeSolver/AdjacentLayerContainmentMergeSolver.ts +456 -0
  6. package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +311 -0
  7. package/lib/types/srj-types.ts +1 -0
  8. package/package.json +2 -1
  9. package/pages/bugreports/bugreport50-multi-support-layer-merge.page.tsx +19 -0
  10. package/pages/pour.page.tsx +18 -0
  11. package/test-assets/bugreport49-634662.json +412 -0
  12. package/tests/__snapshots__/board-outline.snap.svg +2 -2
  13. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
  14. package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
  15. package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +1 -1
  16. package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +1 -1
  17. package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
  18. package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
  19. package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
  20. package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
  21. package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +1 -1
  22. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
  23. package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
  24. package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +1 -1
  25. package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
  26. package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
  27. package/tests/solver/bugreport20-obstacle-clipping/__snapshots__/bugreport20-obstacle-clipping.snap.svg +1 -1
  28. package/tests/solver/bugreport21-board-outline/__snapshots__/bugreport21-board-outline.snap.svg +2 -2
  29. package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
  30. package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
  31. package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
  32. package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +1 -1
  33. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
  34. package/tests/solver/bugreport49-634662/__snapshots__/bugreport49-634662.snap.svg +44 -0
  35. package/tests/solver/bugreport49-634662/bugreport49-634662.test.ts +134 -0
  36. package/tests/solver/bugreport50-multi-support-layer-merge/__snapshots__/bugreport50-multi-support-layer-merge.snap.svg +44 -0
  37. package/tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.json +972 -0
  38. package/tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.test.ts +125 -0
  39. package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
  40. package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
  41. package/tests/solver/no-better-path/__snapshots__/no-better-path.snap.svg +1 -1
  42. package/tests/solver/transitivity/__snapshots__/transitivity.snap.svg +2 -2
package/dist/index.js CHANGED
@@ -4,6 +4,388 @@ import {
4
4
  definePipelineStep as definePipelineStep3
5
5
  } from "@tscircuit/solver-utils";
6
6
 
7
+ // lib/solvers/AdjacentLayerContainmentMergeSolver/AdjacentLayerContainmentMergeSolver.ts
8
+ import { BaseSolver } from "@tscircuit/solver-utils";
9
+
10
+ // lib/utils/getColorForZLayer.ts
11
+ var getColorForZLayer = (zLayers) => {
12
+ const minZ = Math.min(...zLayers);
13
+ const colors = [
14
+ { fill: "#dbeafe", stroke: "#3b82f6" },
15
+ { fill: "#fef3c7", stroke: "#f59e0b" },
16
+ { fill: "#d1fae5", stroke: "#10b981" },
17
+ { fill: "#e9d5ff", stroke: "#a855f7" },
18
+ { fill: "#fed7aa", stroke: "#f97316" },
19
+ { fill: "#fecaca", stroke: "#ef4444" }
20
+ ];
21
+ return colors[minZ % colors.length];
22
+ };
23
+
24
+ // lib/utils/rectdiff-geometry.ts
25
+ var EPS = 1e-9;
26
+ var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
27
+ var gt = (a, b) => a > b + EPS;
28
+ var gte = (a, b) => a > b - EPS;
29
+ var lt = (a, b) => a < b - EPS;
30
+ var lte = (a, b) => a < b + EPS;
31
+ function overlaps(a, b) {
32
+ return !(a.x + a.width <= b.x + EPS || b.x + b.width <= a.x + EPS || a.y + a.height <= b.y + EPS || b.y + b.height <= a.y + EPS);
33
+ }
34
+ function containsPoint(r, p) {
35
+ return p.x >= r.x - EPS && p.x <= r.x + r.width + EPS && p.y >= r.y - EPS && p.y <= r.y + r.height + EPS;
36
+ }
37
+ function distancePointToRectEdges(p, r) {
38
+ const minX = r.x;
39
+ const maxX = r.x + r.width;
40
+ const minY = r.y;
41
+ const maxY = r.y + r.height;
42
+ if (p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY) {
43
+ return Math.min(p.x - minX, maxX - p.x, p.y - minY, maxY - p.y);
44
+ }
45
+ const dx = p.x < minX ? minX - p.x : p.x > maxX ? p.x - maxX : 0;
46
+ const dy = p.y < minY ? minY - p.y : p.y > maxY ? p.y - maxY : 0;
47
+ if (dx === 0) return dy;
48
+ if (dy === 0) return dx;
49
+ return Math.hypot(dx, dy);
50
+ }
51
+ function intersect1D(r1, r2) {
52
+ const lo = Math.max(r1[0], r2[0]);
53
+ const hi = Math.min(r1[1], r2[1]);
54
+ return hi > lo + EPS ? [lo, hi] : null;
55
+ }
56
+ function subtractRect2D(A, B) {
57
+ if (!overlaps(A, B)) return [A];
58
+ const Xi = intersect1D([A.x, A.x + A.width], [B.x, B.x + B.width]);
59
+ const Yi = intersect1D([A.y, A.y + A.height], [B.y, B.y + B.height]);
60
+ if (!Xi || !Yi) return [A];
61
+ const [X0, X1] = Xi;
62
+ const [Y0, Y1] = Yi;
63
+ const out = [];
64
+ if (X0 > A.x + EPS) {
65
+ out.push({ x: A.x, y: A.y, width: X0 - A.x, height: A.height });
66
+ }
67
+ if (A.x + A.width > X1 + EPS) {
68
+ out.push({ x: X1, y: A.y, width: A.x + A.width - X1, height: A.height });
69
+ }
70
+ const midW = Math.max(0, X1 - X0);
71
+ if (midW > EPS && Y0 > A.y + EPS) {
72
+ out.push({ x: X0, y: A.y, width: midW, height: Y0 - A.y });
73
+ }
74
+ if (midW > EPS && A.y + A.height > Y1 + EPS) {
75
+ out.push({ x: X0, y: Y1, width: midW, height: A.y + A.height - Y1 });
76
+ }
77
+ return out.filter((r) => r.width > EPS && r.height > EPS);
78
+ }
79
+
80
+ // lib/solvers/AdjacentLayerContainmentMergeSolver/AdjacentLayerContainmentMergeSolver.ts
81
+ var DEFAULT_MIN_FRAGMENT_AREA = 0.2 ** 2;
82
+ var nodeToRect = (node) => ({
83
+ x: node.center.x - node.width / 2,
84
+ y: node.center.y - node.height / 2,
85
+ width: node.width,
86
+ height: node.height
87
+ });
88
+ var rectArea = (rect) => rect.width * rect.height;
89
+ var cloneNode = (node) => ({
90
+ ...node,
91
+ center: { ...node.center },
92
+ availableZ: [...node.availableZ]
93
+ });
94
+ var cloneNodeWithRect = (node, rect, capacityMeshNodeId) => ({
95
+ ...node,
96
+ capacityMeshNodeId,
97
+ center: {
98
+ x: rect.x + rect.width / 2,
99
+ y: rect.y + rect.height / 2
100
+ },
101
+ width: rect.width,
102
+ height: rect.height,
103
+ availableZ: [...node.availableZ],
104
+ layer: `z${node.availableZ.join(",")}`
105
+ });
106
+ var clonePromotedNodeWithRect = (node, rect, capacityMeshNodeId, availableZ) => ({
107
+ ...node,
108
+ capacityMeshNodeId,
109
+ center: {
110
+ x: rect.x + rect.width / 2,
111
+ y: rect.y + rect.height / 2
112
+ },
113
+ width: rect.width,
114
+ height: rect.height,
115
+ availableZ: [...availableZ],
116
+ layer: `z${availableZ.join(",")}`
117
+ });
118
+ var isFreeNode = (node) => !node._containsObstacle && !node._containsTarget;
119
+ var isSingletonNodeOnLayer = (node, z) => node.availableZ.length === 1 && node.availableZ[0] === z;
120
+ var sameRect = (a, b) => Math.abs(a.x - b.x) <= EPS && Math.abs(a.y - b.y) <= EPS && Math.abs(a.width - b.width) <= EPS && Math.abs(a.height - b.height) <= EPS;
121
+ var subtractRects = (target, cutters) => {
122
+ let remaining = [target];
123
+ for (const cutter of cutters) {
124
+ if (remaining.length === 0) return remaining;
125
+ const nextRemaining = [];
126
+ for (const piece of remaining) {
127
+ nextRemaining.push(...subtractRect2D(piece, cutter));
128
+ }
129
+ remaining = nextRemaining;
130
+ }
131
+ return remaining;
132
+ };
133
+ var isFullyCoveredByRects = (target, coveringRects) => {
134
+ return subtractRects(target, coveringRects).length === 0;
135
+ };
136
+ var sortAndDedupeCuts = (values) => {
137
+ const sorted = [...values].sort((a, b) => a - b);
138
+ const out = [];
139
+ for (const value of sorted) {
140
+ const last = out[out.length - 1];
141
+ if (last == null || Math.abs(last - value) > EPS) {
142
+ out.push(value);
143
+ }
144
+ }
145
+ return out;
146
+ };
147
+ var partitionRectByRects = (target, supportRects) => {
148
+ const xCuts = [target.x, target.x + target.width];
149
+ const yCuts = [target.y, target.y + target.height];
150
+ const targetMaxX = target.x + target.width;
151
+ const targetMaxY = target.y + target.height;
152
+ for (const rect of supportRects) {
153
+ const x0 = Math.max(target.x, rect.x);
154
+ const x1 = Math.min(targetMaxX, rect.x + rect.width);
155
+ const y0 = Math.max(target.y, rect.y);
156
+ const y1 = Math.min(targetMaxY, rect.y + rect.height);
157
+ if (x1 <= x0 + EPS || y1 <= y0 + EPS) continue;
158
+ xCuts.push(x0, x1);
159
+ yCuts.push(y0, y1);
160
+ }
161
+ const xs = sortAndDedupeCuts(xCuts);
162
+ const ys = sortAndDedupeCuts(yCuts);
163
+ const cells = [];
164
+ for (let xi = 0; xi < xs.length - 1; xi++) {
165
+ const x0 = xs[xi];
166
+ const x1 = xs[xi + 1];
167
+ if (x1 <= x0 + EPS) continue;
168
+ for (let yi = 0; yi < ys.length - 1; yi++) {
169
+ const y0 = ys[yi];
170
+ const y1 = ys[yi + 1];
171
+ if (y1 <= y0 + EPS) continue;
172
+ cells.push({
173
+ x: x0,
174
+ y: y0,
175
+ width: x1 - x0,
176
+ height: y1 - y0
177
+ });
178
+ }
179
+ }
180
+ return cells;
181
+ };
182
+ var canMergeHorizontally = (a, b) => Math.abs(a.y - b.y) <= EPS && Math.abs(a.height - b.height) <= EPS && Math.abs(a.x + a.width - b.x) <= EPS;
183
+ var canMergeVertically = (a, b) => Math.abs(a.x - b.x) <= EPS && Math.abs(a.width - b.width) <= EPS && Math.abs(a.y + a.height - b.y) <= EPS;
184
+ var mergeTouchingRects = (rects) => {
185
+ const out = rects.map((rect) => ({ ...rect }));
186
+ let changed = true;
187
+ while (changed) {
188
+ changed = false;
189
+ outer: for (let i = 0; i < out.length; i++) {
190
+ for (let j = i + 1; j < out.length; j++) {
191
+ const a = out[i];
192
+ const b = out[j];
193
+ if (canMergeHorizontally(a, b) || canMergeHorizontally(b, a)) {
194
+ const merged = {
195
+ x: Math.min(a.x, b.x),
196
+ y: a.y,
197
+ width: a.width + b.width,
198
+ height: a.height
199
+ };
200
+ out.splice(j, 1);
201
+ out.splice(i, 1, merged);
202
+ changed = true;
203
+ break outer;
204
+ }
205
+ if (canMergeVertically(a, b) || canMergeVertically(b, a)) {
206
+ const merged = {
207
+ x: a.x,
208
+ y: Math.min(a.y, b.y),
209
+ width: a.width,
210
+ height: a.height + b.height
211
+ };
212
+ out.splice(j, 1);
213
+ out.splice(i, 1, merged);
214
+ changed = true;
215
+ break outer;
216
+ }
217
+ }
218
+ }
219
+ }
220
+ return out;
221
+ };
222
+ var isPromotableRect = (params) => {
223
+ const { rect, viaMinSize, minFragmentArea } = params;
224
+ return rect.width > EPS && rect.height > EPS && rectArea(rect) + EPS >= minFragmentArea && rect.width + EPS >= viaMinSize && rect.height + EPS >= viaMinSize;
225
+ };
226
+ var isResidualRect = (rect, minFragmentArea) => rect.width > EPS && rect.height > EPS && rectArea(rect) + EPS >= minFragmentArea;
227
+ var computePromotablePieces = (params) => {
228
+ const { target, supportRects, viaMinSize, minFragmentArea } = params;
229
+ const overlappingSupports = supportRects.filter(
230
+ (rect) => overlaps(rect, target)
231
+ );
232
+ if (overlappingSupports.length === 0) return [];
233
+ if (isFullyCoveredByRects(target, overlappingSupports) && isPromotableRect({ rect: target, viaMinSize, minFragmentArea })) {
234
+ return [target];
235
+ }
236
+ const partitioned = partitionRectByRects(target, overlappingSupports);
237
+ const coveredPieces = partitioned.filter(
238
+ (piece) => isFullyCoveredByRects(piece, overlappingSupports)
239
+ );
240
+ if (coveredPieces.length === 0) return [];
241
+ const mergedCoveredPieces = mergeTouchingRects(coveredPieces);
242
+ return mergedCoveredPieces.filter(
243
+ (piece) => isFullyCoveredByRects(piece, overlappingSupports) && isPromotableRect({ rect: piece, viaMinSize, minFragmentArea })
244
+ );
245
+ };
246
+ var AdjacentLayerContainmentMergeSolver = class extends BaseSolver {
247
+ constructor(input) {
248
+ super();
249
+ this.input = input;
250
+ }
251
+ input;
252
+ outputNodes = [];
253
+ promotedNodeIds = /* @__PURE__ */ new Set();
254
+ residualNodeIds = /* @__PURE__ */ new Set();
255
+ _setup() {
256
+ this.outputNodes = this.input.meshNodes.map(cloneNode);
257
+ this.promotedNodeIds.clear();
258
+ this.residualNodeIds.clear();
259
+ }
260
+ _step() {
261
+ this.outputNodes = this.processAdjacentLayerContainmentMerges();
262
+ this.solved = true;
263
+ }
264
+ processAdjacentLayerContainmentMerges() {
265
+ const srj = this.input.simpleRouteJson;
266
+ const layerCount = Math.max(1, srj.layerCount || 1);
267
+ if (layerCount < 2) {
268
+ return this.input.meshNodes.map(cloneNode);
269
+ }
270
+ const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0);
271
+ const minFragmentArea = Math.max(
272
+ EPS,
273
+ this.input.minFragmentArea ?? DEFAULT_MIN_FRAGMENT_AREA
274
+ );
275
+ let workingNodes = this.input.meshNodes.map(cloneNode);
276
+ let nextResidualId = 0;
277
+ let nextPromotedId = 0;
278
+ for (let lowerZ = 0; lowerZ < layerCount - 1; lowerZ++) {
279
+ const upperZ = lowerZ + 1;
280
+ const mutableNodes = workingNodes.filter(
281
+ (node) => isFreeNode(node) && (isSingletonNodeOnLayer(node, lowerZ) || isSingletonNodeOnLayer(node, upperZ))
282
+ );
283
+ if (mutableNodes.length === 0) continue;
284
+ const immutableNodes = workingNodes.filter(
285
+ (node) => !mutableNodes.includes(node)
286
+ );
287
+ const supportRectsByLayer = /* @__PURE__ */ new Map();
288
+ supportRectsByLayer.set(
289
+ lowerZ,
290
+ mutableNodes.filter((node) => isSingletonNodeOnLayer(node, lowerZ)).map(nodeToRect)
291
+ );
292
+ supportRectsByLayer.set(
293
+ upperZ,
294
+ mutableNodes.filter((node) => isSingletonNodeOnLayer(node, upperZ)).map(nodeToRect)
295
+ );
296
+ const promotedRects = [];
297
+ const promotedNodes = [];
298
+ const candidateNodes = mutableNodes.filter(
299
+ (node) => isPromotableRect({
300
+ rect: nodeToRect(node),
301
+ viaMinSize,
302
+ minFragmentArea
303
+ })
304
+ ).sort((a, b) => rectArea(nodeToRect(b)) - rectArea(nodeToRect(a)));
305
+ for (const candidate of candidateNodes) {
306
+ const candidateRect = nodeToRect(candidate);
307
+ const candidatePieces = subtractRects(
308
+ candidateRect,
309
+ promotedRects
310
+ ).filter((piece) => isResidualRect(piece, minFragmentArea));
311
+ const candidateZ = candidate.availableZ[0];
312
+ const oppositeZ = candidateZ === lowerZ ? upperZ : lowerZ;
313
+ const supportRects = supportRectsByLayer.get(oppositeZ) ?? [];
314
+ for (const piece of candidatePieces) {
315
+ const promotablePieces = computePromotablePieces({
316
+ target: piece,
317
+ supportRects,
318
+ viaMinSize,
319
+ minFragmentArea
320
+ });
321
+ for (const promotablePiece of promotablePieces) {
322
+ promotedRects.push(promotablePiece);
323
+ const promotedNode = clonePromotedNodeWithRect(
324
+ candidate,
325
+ promotablePiece,
326
+ `${candidate.capacityMeshNodeId}-adjacent-merge-${nextPromotedId++}`,
327
+ [lowerZ, upperZ]
328
+ );
329
+ promotedNodes.push(promotedNode);
330
+ this.promotedNodeIds.add(promotedNode.capacityMeshNodeId);
331
+ }
332
+ }
333
+ }
334
+ const residualNodes = [];
335
+ for (const node of mutableNodes) {
336
+ const nodeRect = nodeToRect(node);
337
+ const remainingPieces = subtractRects(nodeRect, promotedRects).filter(
338
+ (piece) => isResidualRect(piece, minFragmentArea)
339
+ );
340
+ if (remainingPieces.length === 1 && sameRect(remainingPieces[0], nodeRect)) {
341
+ residualNodes.push(node);
342
+ continue;
343
+ }
344
+ for (const piece of remainingPieces) {
345
+ const residualNode = cloneNodeWithRect(
346
+ node,
347
+ piece,
348
+ `${node.capacityMeshNodeId}-adjacent-residual-${nextResidualId++}`
349
+ );
350
+ residualNodes.push(residualNode);
351
+ this.residualNodeIds.add(residualNode.capacityMeshNodeId);
352
+ }
353
+ }
354
+ workingNodes = [...immutableNodes, ...promotedNodes, ...residualNodes];
355
+ }
356
+ return workingNodes;
357
+ }
358
+ getOutput() {
359
+ return { outputNodes: this.outputNodes };
360
+ }
361
+ visualize() {
362
+ return {
363
+ title: "AdjacentLayerContainmentMergeSolver",
364
+ coordinateSystem: "cartesian",
365
+ rects: this.outputNodes.map((node) => {
366
+ const colors = getColorForZLayer(node.availableZ);
367
+ const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId);
368
+ const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId);
369
+ return {
370
+ center: node.center,
371
+ width: node.width,
372
+ height: node.height,
373
+ stroke: isPromoted ? "rgba(245, 158, 11, 0.95)" : isResidual ? "rgba(37, 99, 235, 0.95)" : colors.stroke,
374
+ fill: node._containsObstacle ? "rgba(239, 68, 68, 0.35)" : isPromoted ? "rgba(251, 191, 36, 0.28)" : isResidual ? "rgba(59, 130, 246, 0.18)" : colors.fill,
375
+ layer: `z${node.availableZ.join(",")}`,
376
+ label: [
377
+ `node ${node.capacityMeshNodeId}`,
378
+ `z:${node.availableZ.join(",")}`
379
+ ].join("\n")
380
+ };
381
+ }),
382
+ points: [],
383
+ lines: [],
384
+ texts: []
385
+ };
386
+ }
387
+ };
388
+
7
389
  // lib/solvers/GapFillSolver/GapFillSolverPipeline.ts
8
390
  import {
9
391
  BasePipelineSolver,
@@ -11,14 +393,14 @@ import {
11
393
  } from "@tscircuit/solver-utils";
12
394
 
13
395
  // lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
14
- import { BaseSolver } from "@tscircuit/solver-utils";
396
+ import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
15
397
  import Flatbush from "flatbush";
16
398
 
17
399
  // lib/solvers/GapFillSolver/projectToUncoveredSegments.ts
18
- var EPS = 1e-4;
400
+ var EPS2 = 1e-4;
19
401
  function projectToUncoveredSegments(primaryEdge, overlappingEdges) {
20
- const isHorizontal = Math.abs(primaryEdge.start.y - primaryEdge.end.y) < EPS;
21
- const isVertical = Math.abs(primaryEdge.start.x - primaryEdge.end.x) < EPS;
402
+ const isHorizontal = Math.abs(primaryEdge.start.y - primaryEdge.end.y) < EPS2;
403
+ const isVertical = Math.abs(primaryEdge.start.x - primaryEdge.end.x) < EPS2;
22
404
  if (!isHorizontal && !isVertical) return [];
23
405
  const axis = isHorizontal ? "x" : "y";
24
406
  const perp = isHorizontal ? "y" : "x";
@@ -31,16 +413,16 @@ function projectToUncoveredSegments(primaryEdge, overlappingEdges) {
31
413
  const intervals = [];
32
414
  for (const e of overlappingEdges) {
33
415
  if (e === primaryEdge) continue;
34
- const eIsHorizontal = Math.abs(e.start.y - e.end.y) < EPS;
35
- const eIsVertical = Math.abs(e.start.x - e.end.x) < EPS;
416
+ const eIsHorizontal = Math.abs(e.start.y - e.end.y) < EPS2;
417
+ const eIsVertical = Math.abs(e.start.x - e.end.x) < EPS2;
36
418
  if (axis === "x" && !eIsHorizontal) continue;
37
419
  if (axis === "y" && !eIsVertical) continue;
38
- if (Math.abs(e.start[perp] - lineCoord) > EPS) continue;
420
+ if (Math.abs(e.start[perp] - lineCoord) > EPS2) continue;
39
421
  const eMin = Math.min(e.start[axis], e.end[axis]);
40
422
  const eMax = Math.max(e.start[axis], e.end[axis]);
41
423
  const s = clamp2(eMin);
42
424
  const t = clamp2(eMax);
43
- if (t - s > EPS) intervals.push({ s, e: t });
425
+ if (t - s > EPS2) intervals.push({ s, e: t });
44
426
  }
45
427
  if (intervals.length === 0) {
46
428
  return [
@@ -55,19 +437,19 @@ function projectToUncoveredSegments(primaryEdge, overlappingEdges) {
55
437
  const merged = [];
56
438
  for (const it of intervals) {
57
439
  const last = merged[merged.length - 1];
58
- if (!last || it.s > last.e + EPS) merged.push({ ...it });
440
+ if (!last || it.s > last.e + EPS2) merged.push({ ...it });
59
441
  else last.e = Math.max(last.e, it.e);
60
442
  }
61
443
  const uncovered = [];
62
444
  let cursor = pMin;
63
445
  for (const m of merged) {
64
- if (m.s > cursor + EPS) uncovered.push({ s: cursor, e: m.s });
446
+ if (m.s > cursor + EPS2) uncovered.push({ s: cursor, e: m.s });
65
447
  cursor = Math.max(cursor, m.e);
66
- if (cursor >= pMax - EPS) break;
448
+ if (cursor >= pMax - EPS2) break;
67
449
  }
68
- if (pMax > cursor + EPS) uncovered.push({ s: cursor, e: pMax });
450
+ if (pMax > cursor + EPS2) uncovered.push({ s: cursor, e: pMax });
69
451
  if (uncovered.length === 0) return [];
70
- return uncovered.filter((u) => u.e - u.s > EPS).map((u) => {
452
+ return uncovered.filter((u) => u.e - u.s > EPS2).map((u) => {
71
453
  const start = axis === "x" ? { x: u.s, y: lineCoord } : { x: lineCoord, y: u.s };
72
454
  const end = axis === "x" ? { x: u.e, y: lineCoord } : { x: lineCoord, y: u.e };
73
455
  return {
@@ -156,8 +538,8 @@ var visuallyOffsetLine = (line, options) => {
156
538
 
157
539
  // lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
158
540
  import "@tscircuit/math-utils";
159
- var EPS2 = 1e-4;
160
- var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver {
541
+ var EPS3 = 1e-4;
542
+ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver2 {
161
543
  constructor(input) {
162
544
  super();
163
545
  this.input = input;
@@ -175,7 +557,7 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver {
175
557
  ;
176
558
  [start, end] = [end, start];
177
559
  }
178
- if (Math.abs(start.x - end.x) < EPS2 && start.y > end.y) {
560
+ if (Math.abs(start.x - end.x) < EPS3 && start.y > end.y) {
179
561
  ;
180
562
  [start, end] = [end, start];
181
563
  }
@@ -202,6 +584,7 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver {
202
584
  }
203
585
  this.edgeSpatialIndex.finish();
204
586
  }
587
+ input;
205
588
  allEdges;
206
589
  unprocessedEdges = [];
207
590
  segmentsWithAdjacentEmptySpace = [];
@@ -220,10 +603,10 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver {
220
603
  const candidateEdge = this.unprocessedEdges.shift();
221
604
  this.lastCandidateEdge = candidateEdge;
222
605
  const nearbyEdges = this.edgeSpatialIndex.search(
223
- candidateEdge.start.x - EPS2,
224
- candidateEdge.start.y - EPS2,
225
- candidateEdge.end.x + EPS2,
226
- candidateEdge.end.y + EPS2
606
+ candidateEdge.start.x - EPS3,
607
+ candidateEdge.start.y - EPS3,
608
+ candidateEdge.end.x + EPS3,
609
+ candidateEdge.end.y + EPS3
227
610
  );
228
611
  const overlappingEdges = nearbyEdges.map((i) => this.allEdges[i]).filter((e) => e.z === candidateEdge.z);
229
612
  this.lastOverlappingEdges = overlappingEdges;
@@ -309,7 +692,7 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver {
309
692
  };
310
693
 
311
694
  // lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
312
- import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
695
+ import { BaseSolver as BaseSolver3 } from "@tscircuit/solver-utils";
313
696
  import RBush from "rbush";
314
697
 
315
698
  // lib/solvers/GapFillSolver/getBoundsFromCorners.ts
@@ -324,8 +707,8 @@ var getBoundsFromCorners = (corners) => {
324
707
 
325
708
  // lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
326
709
  import { segmentToBoxMinDistance } from "@tscircuit/math-utils";
327
- var EPS3 = 1e-4;
328
- var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
710
+ var EPS4 = 1e-4;
711
+ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver3 {
329
712
  constructor(input) {
330
713
  super();
331
714
  this.input = input;
@@ -361,6 +744,7 @@ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
361
744
  }))
362
745
  );
363
746
  }
747
+ input;
364
748
  unprocessedSegments = [];
365
749
  expandedSegments = [];
366
750
  lastSegment = null;
@@ -390,12 +774,12 @@ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
390
774
  let collidingNodes = null;
391
775
  let searchDistance = 1;
392
776
  const searchCorner1 = {
393
- x: segment.start.x + dx * EPS3 + normDeltaStartEnd.x * EPS3 * 10,
394
- y: segment.start.y + dy * EPS3 + normDeltaStartEnd.y * EPS3 * 10
777
+ x: segment.start.x + dx * EPS4 + normDeltaStartEnd.x * EPS4 * 10,
778
+ y: segment.start.y + dy * EPS4 + normDeltaStartEnd.y * EPS4 * 10
395
779
  };
396
780
  const searchCorner2 = {
397
- x: segment.end.x + dx * EPS3 - normDeltaStartEnd.x * EPS3 * 10,
398
- y: segment.end.y + dy * EPS3 - normDeltaStartEnd.y * EPS3 * 10
781
+ x: segment.end.x + dx * EPS4 - normDeltaStartEnd.x * EPS4 * 10,
782
+ y: segment.end.y + dy * EPS4 - normDeltaStartEnd.y * EPS4 * 10
399
783
  };
400
784
  this.lastSearchCorner1 = searchCorner1;
401
785
  this.lastSearchCorner2 = searchCorner2;
@@ -460,7 +844,7 @@ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
460
844
  }
461
845
  };
462
846
  this.lastExpandedSegment = expandedSegment;
463
- if (nodeWidth < EPS3 || nodeHeight < EPS3) {
847
+ if (nodeWidth < EPS4 || nodeHeight < EPS4) {
464
848
  return;
465
849
  }
466
850
  this.expandedSegments.push(expandedSegment);
@@ -665,70 +1049,312 @@ var GapFillSolverPipeline = class extends BasePipelineSolver {
665
1049
  }
666
1050
  };
667
1051
 
668
- // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
669
- import {
670
- BasePipelineSolver as BasePipelineSolver2,
671
- definePipelineStep as definePipelineStep2
672
- } from "@tscircuit/solver-utils";
673
-
674
- // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
675
- import { BaseSolver as BaseSolver3 } from "@tscircuit/solver-utils";
1052
+ // lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts
1053
+ import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
676
1054
 
677
- // lib/utils/rectdiff-geometry.ts
678
- var EPS4 = 1e-9;
679
- var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
680
- var gt = (a, b) => a > b + EPS4;
681
- var gte = (a, b) => a > b - EPS4;
682
- var lt = (a, b) => a < b - EPS4;
683
- var lte = (a, b) => a < b + EPS4;
684
- function overlaps(a, b) {
685
- return !(a.x + a.width <= b.x + EPS4 || b.x + b.width <= a.x + EPS4 || a.y + a.height <= b.y + EPS4 || b.y + b.height <= a.y + EPS4);
1055
+ // lib/solvers/RectDiffSeedingSolver/layers.ts
1056
+ function layerSortKey(n) {
1057
+ const L = n.toLowerCase();
1058
+ if (L === "top") return -1e6;
1059
+ if (L === "bottom") return 1e6;
1060
+ const m = /^inner(\d+)$/i.exec(L);
1061
+ if (m) return parseInt(m[1], 10) || 0;
1062
+ return 100 + L.charCodeAt(0);
686
1063
  }
687
- function containsPoint(r, p) {
688
- return p.x >= r.x - EPS4 && p.x <= r.x + r.width + EPS4 && p.y >= r.y - EPS4 && p.y <= r.y + r.height + EPS4;
1064
+ function canonicalizeLayerOrder(names) {
1065
+ return Array.from(new Set(names)).sort((a, b) => {
1066
+ const ka = layerSortKey(a);
1067
+ const kb = layerSortKey(b);
1068
+ if (ka !== kb) return ka - kb;
1069
+ return a.localeCompare(b);
1070
+ });
689
1071
  }
690
- function distancePointToRectEdges(p, r) {
691
- const minX = r.x;
692
- const maxX = r.x + r.width;
693
- const minY = r.y;
694
- const maxY = r.y + r.height;
695
- if (p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY) {
696
- return Math.min(p.x - minX, maxX - p.x, p.y - minY, maxY - p.y);
697
- }
698
- const dx = p.x < minX ? minX - p.x : p.x > maxX ? p.x - maxX : 0;
699
- const dy = p.y < minY ? minY - p.y : p.y > maxY ? p.y - maxY : 0;
700
- if (dx === 0) return dy;
701
- if (dy === 0) return dx;
702
- return Math.hypot(dx, dy);
1072
+ function buildZIndexMap(params) {
1073
+ const names = canonicalizeLayerOrder(
1074
+ (params.obstacles ?? []).flatMap((o) => o.layers ?? [])
1075
+ );
1076
+ const declaredLayerCount = Math.max(1, params.layerCount || names.length || 1);
1077
+ const fallback = Array.from(
1078
+ { length: declaredLayerCount },
1079
+ (_, i) => i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`
1080
+ );
1081
+ const ordered = [];
1082
+ const seen = /* @__PURE__ */ new Set();
1083
+ const push = (n) => {
1084
+ const key = n.toLowerCase();
1085
+ if (seen.has(key)) return;
1086
+ seen.add(key);
1087
+ ordered.push(n);
1088
+ };
1089
+ fallback.forEach(push);
1090
+ names.forEach(push);
1091
+ const layerNames = ordered.slice(0, declaredLayerCount);
1092
+ const clampIndex = (nameLower) => {
1093
+ if (layerNames.length <= 1) return 0;
1094
+ if (nameLower === "top") return 0;
1095
+ if (nameLower === "bottom") return layerNames.length - 1;
1096
+ const m = /^inner(\d+)$/i.exec(nameLower);
1097
+ if (m) {
1098
+ if (layerNames.length <= 2) return layerNames.length - 1;
1099
+ const parsed = parseInt(m[1], 10);
1100
+ const maxInner = layerNames.length - 2;
1101
+ const clampedInner = Math.min(
1102
+ maxInner,
1103
+ Math.max(1, Number.isFinite(parsed) ? parsed : 1)
1104
+ );
1105
+ return clampedInner;
1106
+ }
1107
+ return 0;
1108
+ };
1109
+ const map = /* @__PURE__ */ new Map();
1110
+ layerNames.forEach((n, i) => map.set(n.toLowerCase(), i));
1111
+ ordered.slice(layerNames.length).forEach((n) => {
1112
+ const key = n.toLowerCase();
1113
+ map.set(key, clampIndex(key));
1114
+ });
1115
+ return { layerNames, zIndexByName: map };
703
1116
  }
704
- function intersect1D(r1, r2) {
705
- const lo = Math.max(r1[0], r2[0]);
706
- const hi = Math.min(r1[1], r2[1]);
707
- return hi > lo + EPS4 ? [lo, hi] : null;
1117
+ function obstacleZs(ob, zIndexByName) {
1118
+ if (ob.zLayers?.length)
1119
+ return Array.from(new Set(ob.zLayers)).sort((a, b) => a - b);
1120
+ const fromNames = (ob.layers ?? []).map((n) => zIndexByName.get(n.toLowerCase())).filter((v) => typeof v === "number");
1121
+ return Array.from(new Set(fromNames)).sort((a, b) => a - b);
708
1122
  }
709
- function subtractRect2D(A, B) {
710
- if (!overlaps(A, B)) return [A];
711
- const Xi = intersect1D([A.x, A.x + A.width], [B.x, B.x + B.width]);
712
- const Yi = intersect1D([A.y, A.y + A.height], [B.y, B.y + B.height]);
713
- if (!Xi || !Yi) return [A];
714
- const [X0, X1] = Xi;
715
- const [Y0, Y1] = Yi;
716
- const out = [];
717
- if (X0 > A.x + EPS4) {
718
- out.push({ x: A.x, y: A.y, width: X0 - A.x, height: A.height });
1123
+ function obstacleToXYRect(ob) {
1124
+ const w = ob.width;
1125
+ const h = ob.height;
1126
+ if (typeof w !== "number" || typeof h !== "number") return null;
1127
+ return { x: ob.center.x - w / 2, y: ob.center.y - h / 2, width: w, height: h };
1128
+ }
1129
+
1130
+ // lib/utils/padRect.ts
1131
+ var padRect = (rect, clearance) => {
1132
+ if (!clearance || clearance <= 0) return rect;
1133
+ return {
1134
+ x: rect.x - clearance,
1135
+ y: rect.y - clearance,
1136
+ width: rect.width + 2 * clearance,
1137
+ height: rect.height + 2 * clearance
1138
+ };
1139
+ };
1140
+
1141
+ // lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts
1142
+ var nodeToRect2 = (node) => ({
1143
+ x: node.center.x - node.width / 2,
1144
+ y: node.center.y - node.height / 2,
1145
+ width: node.width,
1146
+ height: node.height
1147
+ });
1148
+ var rectArea2 = (rect) => rect.width * rect.height;
1149
+ var cloneNode2 = (node) => ({
1150
+ ...node,
1151
+ center: { ...node.center },
1152
+ availableZ: [...node.availableZ]
1153
+ });
1154
+ var cloneNodeWithRect2 = (node, rect, capacityMeshNodeId) => ({
1155
+ ...node,
1156
+ capacityMeshNodeId,
1157
+ center: {
1158
+ x: rect.x + rect.width / 2,
1159
+ y: rect.y + rect.height / 2
1160
+ },
1161
+ width: rect.width,
1162
+ height: rect.height,
1163
+ availableZ: [...node.availableZ],
1164
+ layer: `z${node.availableZ.join(",")}`
1165
+ });
1166
+ var isFreeNode2 = (node) => !node._containsObstacle && !node._containsTarget;
1167
+ var isSingletonOuterNode = (node, outerZ) => node.availableZ.length === 1 && node.availableZ[0] === outerZ;
1168
+ var sameRect2 = (a, b) => Math.abs(a.x - b.x) <= EPS && Math.abs(a.y - b.y) <= EPS && Math.abs(a.width - b.width) <= EPS && Math.abs(a.height - b.height) <= EPS;
1169
+ var subtractRects2 = (target, cutters) => {
1170
+ let remaining = [target];
1171
+ for (const cutter of cutters) {
1172
+ if (remaining.length === 0) return remaining;
1173
+ const nextRemaining = [];
1174
+ for (const piece of remaining) {
1175
+ nextRemaining.push(...subtractRect2D(piece, cutter));
1176
+ }
1177
+ remaining = nextRemaining;
1178
+ }
1179
+ return remaining;
1180
+ };
1181
+ var isFullyCoveredByRects2 = (target, coveringRects) => {
1182
+ return subtractRects2(target, coveringRects).length === 0;
1183
+ };
1184
+ var OuterLayerContainmentMergeSolver = class extends BaseSolver4 {
1185
+ constructor(input) {
1186
+ super();
1187
+ this.input = input;
719
1188
  }
720
- if (A.x + A.width > X1 + EPS4) {
721
- out.push({ x: X1, y: A.y, width: A.x + A.width - X1, height: A.height });
1189
+ input;
1190
+ outputNodes = [];
1191
+ promotedNodeIds = /* @__PURE__ */ new Set();
1192
+ residualNodeIds = /* @__PURE__ */ new Set();
1193
+ _setup() {
1194
+ this.outputNodes = this.input.meshNodes.map(cloneNode2);
1195
+ this.promotedNodeIds.clear();
1196
+ this.residualNodeIds.clear();
722
1197
  }
723
- const midW = Math.max(0, X1 - X0);
724
- if (midW > EPS4 && Y0 > A.y + EPS4) {
725
- out.push({ x: X0, y: A.y, width: midW, height: Y0 - A.y });
1198
+ _step() {
1199
+ this.outputNodes = this.processOuterLayerContainmentMerges();
1200
+ this.solved = true;
726
1201
  }
727
- if (midW > EPS4 && A.y + A.height > Y1 + EPS4) {
728
- out.push({ x: X0, y: Y1, width: midW, height: A.y + A.height - Y1 });
1202
+ processOuterLayerContainmentMerges() {
1203
+ const srj = this.input.simpleRouteJson;
1204
+ const layerCount = Math.max(1, srj.layerCount || 1);
1205
+ if (layerCount < 3) {
1206
+ return this.input.meshNodes.map(cloneNode2);
1207
+ }
1208
+ const topZ = 0;
1209
+ const bottomZ = layerCount - 1;
1210
+ const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0);
1211
+ const originalNodes = this.input.meshNodes.map(cloneNode2);
1212
+ const obstaclesByLayer = this.buildObstaclesByLayer(layerCount);
1213
+ const mutableOuterNodes = originalNodes.filter(
1214
+ (node) => isFreeNode2(node) && (isSingletonOuterNode(node, topZ) || isSingletonOuterNode(node, bottomZ))
1215
+ );
1216
+ const immutableNodes = originalNodes.filter(
1217
+ (node) => !mutableOuterNodes.includes(node)
1218
+ );
1219
+ const freeSupportRectsByOuterLayer = /* @__PURE__ */ new Map();
1220
+ freeSupportRectsByOuterLayer.set(
1221
+ topZ,
1222
+ originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(topZ)).map(nodeToRect2)
1223
+ );
1224
+ freeSupportRectsByOuterLayer.set(
1225
+ bottomZ,
1226
+ originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(bottomZ)).map(nodeToRect2)
1227
+ );
1228
+ const promotedNodes = [];
1229
+ const promotedRects = [];
1230
+ const candidateNodes = mutableOuterNodes.filter(
1231
+ (node) => node.width + EPS >= viaMinSize && node.height + EPS >= viaMinSize
1232
+ ).sort((a, b) => rectArea2(nodeToRect2(b)) - rectArea2(nodeToRect2(a)));
1233
+ for (const candidate of candidateNodes) {
1234
+ const candidateZ = candidate.availableZ[0];
1235
+ const oppositeZ = candidateZ === topZ ? bottomZ : topZ;
1236
+ const candidateRect = nodeToRect2(candidate);
1237
+ const oppositeSupportRects = freeSupportRectsByOuterLayer.get(oppositeZ) ?? [];
1238
+ if (!this.isTransitCompatibleAcrossIntermediateLayers({
1239
+ rect: candidateRect,
1240
+ fromZ: candidateZ,
1241
+ toZ: oppositeZ,
1242
+ obstaclesByLayer
1243
+ })) {
1244
+ continue;
1245
+ }
1246
+ if (!isFullyCoveredByRects2(candidateRect, oppositeSupportRects)) {
1247
+ continue;
1248
+ }
1249
+ promotedNodes.push({
1250
+ ...candidate,
1251
+ availableZ: [topZ, bottomZ],
1252
+ layer: `z${topZ},${bottomZ}`
1253
+ });
1254
+ promotedRects.push(candidateRect);
1255
+ this.promotedNodeIds.add(candidate.capacityMeshNodeId);
1256
+ }
1257
+ let nextResidualId = 0;
1258
+ const residualNodes = [];
1259
+ for (const node of mutableOuterNodes) {
1260
+ if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
1261
+ continue;
1262
+ }
1263
+ const nodeRect = nodeToRect2(node);
1264
+ const remainingPieces = subtractRects2(nodeRect, promotedRects);
1265
+ if (remainingPieces.length === 1 && sameRect2(remainingPieces[0], nodeRect)) {
1266
+ residualNodes.push(node);
1267
+ continue;
1268
+ }
1269
+ for (const piece of remainingPieces) {
1270
+ const residualNode = cloneNodeWithRect2(
1271
+ node,
1272
+ piece,
1273
+ `${node.capacityMeshNodeId}-outer-merge-${nextResidualId++}`
1274
+ );
1275
+ residualNodes.push(residualNode);
1276
+ this.residualNodeIds.add(residualNode.capacityMeshNodeId);
1277
+ }
1278
+ }
1279
+ return [...immutableNodes, ...promotedNodes, ...residualNodes];
729
1280
  }
730
- return out.filter((r) => r.width > EPS4 && r.height > EPS4);
731
- }
1281
+ buildObstaclesByLayer(layerCount) {
1282
+ const out = Array.from(
1283
+ { length: layerCount },
1284
+ () => []
1285
+ );
1286
+ for (const obstacle of this.input.simpleRouteJson.obstacles ?? []) {
1287
+ const baseRect = obstacleToXYRect(obstacle);
1288
+ if (!baseRect) continue;
1289
+ const rect = padRect(baseRect, this.input.obstacleClearance ?? 0);
1290
+ const zLayers = obstacleZs(obstacle, this.input.zIndexByName);
1291
+ for (const z of zLayers) {
1292
+ if (z < 0 || z >= layerCount) continue;
1293
+ out[z].push({ obstacle, rect });
1294
+ }
1295
+ }
1296
+ return out;
1297
+ }
1298
+ isTransitCompatibleAcrossIntermediateLayers(params) {
1299
+ const { rect, fromZ, toZ, obstaclesByLayer } = params;
1300
+ const lo = Math.min(fromZ, toZ);
1301
+ const hi = Math.max(fromZ, toZ);
1302
+ if (hi - lo < 2) return false;
1303
+ for (let z = lo + 1; z < hi; z++) {
1304
+ const overlapping = (obstaclesByLayer[z] ?? []).filter(
1305
+ (entry) => overlaps(entry.rect, rect)
1306
+ );
1307
+ if (overlapping.length === 0) return false;
1308
+ const nonCopperOverlap = overlapping.some(
1309
+ (entry) => !entry.obstacle.isCopperPour
1310
+ );
1311
+ if (nonCopperOverlap) return false;
1312
+ const copperRects = overlapping.filter((entry) => entry.obstacle.isCopperPour).map((entry) => entry.rect);
1313
+ if (!isFullyCoveredByRects2(rect, copperRects)) {
1314
+ return false;
1315
+ }
1316
+ }
1317
+ return true;
1318
+ }
1319
+ getOutput() {
1320
+ return { outputNodes: this.outputNodes };
1321
+ }
1322
+ visualize() {
1323
+ return {
1324
+ title: "OuterLayerContainmentMergeSolver",
1325
+ coordinateSystem: "cartesian",
1326
+ rects: this.outputNodes.map((node) => {
1327
+ const colors = getColorForZLayer(node.availableZ);
1328
+ const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId);
1329
+ const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId);
1330
+ return {
1331
+ center: node.center,
1332
+ width: node.width,
1333
+ height: node.height,
1334
+ stroke: isPromoted ? "rgba(22, 163, 74, 0.95)" : isResidual ? "rgba(37, 99, 235, 0.95)" : colors.stroke,
1335
+ fill: node._containsObstacle ? "rgba(239, 68, 68, 0.35)" : isPromoted ? "rgba(34, 197, 94, 0.28)" : isResidual ? "rgba(59, 130, 246, 0.18)" : colors.fill,
1336
+ layer: `z${node.availableZ.join(",")}`,
1337
+ label: [
1338
+ `node ${node.capacityMeshNodeId}`,
1339
+ `z:${node.availableZ.join(",")}`
1340
+ ].join("\n")
1341
+ };
1342
+ }),
1343
+ points: [],
1344
+ lines: [],
1345
+ texts: []
1346
+ };
1347
+ }
1348
+ };
1349
+
1350
+ // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
1351
+ import {
1352
+ BasePipelineSolver as BasePipelineSolver2,
1353
+ definePipelineStep as definePipelineStep2
1354
+ } from "@tscircuit/solver-utils";
1355
+
1356
+ // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
1357
+ import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
732
1358
 
733
1359
  // lib/solvers/RectDiffSeedingSolver/isPointInPolygon.ts
734
1360
  function isPointInPolygon(p, polygon) {
@@ -791,7 +1417,7 @@ function computeInverseRects(bounds, polygon) {
791
1417
  }
792
1418
  const finalRects = [];
793
1419
  rawRects.sort((a, b) => {
794
- if (Math.abs(a.y - b.y) > EPS4) return a.y - b.y;
1420
+ if (Math.abs(a.y - b.y) > EPS) return a.y - b.y;
795
1421
  return a.x - b.x;
796
1422
  });
797
1423
  let current = null;
@@ -800,9 +1426,9 @@ function computeInverseRects(bounds, polygon) {
800
1426
  current = r;
801
1427
  continue;
802
1428
  }
803
- const sameY = Math.abs(current.y - r.y) < EPS4;
804
- const sameHeight = Math.abs(current.height - r.height) < EPS4;
805
- const touchesX = Math.abs(current.x + current.width - r.x) < EPS4;
1429
+ const sameY = Math.abs(current.y - r.y) < EPS;
1430
+ const sameHeight = Math.abs(current.height - r.height) < EPS;
1431
+ const touchesX = Math.abs(current.x + current.width - r.x) < EPS;
806
1432
  if (sameY && sameHeight && touchesX) {
807
1433
  current.width += r.width;
808
1434
  } else {
@@ -812,7 +1438,7 @@ function computeInverseRects(bounds, polygon) {
812
1438
  }
813
1439
  if (current) finalRects.push(current);
814
1440
  finalRects.sort((a, b) => {
815
- if (Math.abs(a.x - b.x) > EPS4) return a.x - b.x;
1441
+ if (Math.abs(a.x - b.x) > EPS) return a.x - b.x;
816
1442
  return a.y - b.y;
817
1443
  });
818
1444
  const mergedVertical = [];
@@ -822,9 +1448,9 @@ function computeInverseRects(bounds, polygon) {
822
1448
  current = r;
823
1449
  continue;
824
1450
  }
825
- const sameX = Math.abs(current.x - r.x) < EPS4;
826
- const sameWidth = Math.abs(current.width - r.width) < EPS4;
827
- const touchesY = Math.abs(current.y + current.height - r.y) < EPS4;
1451
+ const sameX = Math.abs(current.x - r.x) < EPS;
1452
+ const sameWidth = Math.abs(current.width - r.width) < EPS;
1453
+ const touchesY = Math.abs(current.y + current.height - r.y) < EPS;
828
1454
  if (sameX && sameWidth && touchesY) {
829
1455
  current.height += r.height;
830
1456
  } else {
@@ -836,81 +1462,6 @@ function computeInverseRects(bounds, polygon) {
836
1462
  return mergedVertical;
837
1463
  }
838
1464
 
839
- // lib/solvers/RectDiffSeedingSolver/layers.ts
840
- function layerSortKey(n) {
841
- const L = n.toLowerCase();
842
- if (L === "top") return -1e6;
843
- if (L === "bottom") return 1e6;
844
- const m = /^inner(\d+)$/i.exec(L);
845
- if (m) return parseInt(m[1], 10) || 0;
846
- return 100 + L.charCodeAt(0);
847
- }
848
- function canonicalizeLayerOrder(names) {
849
- return Array.from(new Set(names)).sort((a, b) => {
850
- const ka = layerSortKey(a);
851
- const kb = layerSortKey(b);
852
- if (ka !== kb) return ka - kb;
853
- return a.localeCompare(b);
854
- });
855
- }
856
- function buildZIndexMap(params) {
857
- const names = canonicalizeLayerOrder(
858
- (params.obstacles ?? []).flatMap((o) => o.layers ?? [])
859
- );
860
- const declaredLayerCount = Math.max(1, params.layerCount || names.length || 1);
861
- const fallback = Array.from(
862
- { length: declaredLayerCount },
863
- (_, i) => i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`
864
- );
865
- const ordered = [];
866
- const seen = /* @__PURE__ */ new Set();
867
- const push = (n) => {
868
- const key = n.toLowerCase();
869
- if (seen.has(key)) return;
870
- seen.add(key);
871
- ordered.push(n);
872
- };
873
- fallback.forEach(push);
874
- names.forEach(push);
875
- const layerNames = ordered.slice(0, declaredLayerCount);
876
- const clampIndex = (nameLower) => {
877
- if (layerNames.length <= 1) return 0;
878
- if (nameLower === "top") return 0;
879
- if (nameLower === "bottom") return layerNames.length - 1;
880
- const m = /^inner(\d+)$/i.exec(nameLower);
881
- if (m) {
882
- if (layerNames.length <= 2) return layerNames.length - 1;
883
- const parsed = parseInt(m[1], 10);
884
- const maxInner = layerNames.length - 2;
885
- const clampedInner = Math.min(
886
- maxInner,
887
- Math.max(1, Number.isFinite(parsed) ? parsed : 1)
888
- );
889
- return clampedInner;
890
- }
891
- return 0;
892
- };
893
- const map = /* @__PURE__ */ new Map();
894
- layerNames.forEach((n, i) => map.set(n.toLowerCase(), i));
895
- ordered.slice(layerNames.length).forEach((n) => {
896
- const key = n.toLowerCase();
897
- map.set(key, clampIndex(key));
898
- });
899
- return { layerNames, zIndexByName: map };
900
- }
901
- function obstacleZs(ob, zIndexByName) {
902
- if (ob.zLayers?.length)
903
- return Array.from(new Set(ob.zLayers)).sort((a, b) => a - b);
904
- const fromNames = (ob.layers ?? []).map((n) => zIndexByName.get(n.toLowerCase())).filter((v) => typeof v === "number");
905
- return Array.from(new Set(fromNames)).sort((a, b) => a - b);
906
- }
907
- function obstacleToXYRect(ob) {
908
- const w = ob.width;
909
- const h = ob.height;
910
- if (typeof w !== "number" || typeof h !== "number") return null;
911
- return { x: ob.center.x - w / 2, y: ob.center.y - h / 2, width: w, height: h };
912
- }
913
-
914
1465
  // lib/utils/isSelfRect.ts
915
1466
  var EPS5 = 1e-9;
916
1467
  var isSelfRect = (params) => Math.abs(params.rect.x + params.rect.width / 2 - params.startX) < EPS5 && Math.abs(params.rect.y + params.rect.height / 2 - params.startY) < EPS5 && Math.abs(params.rect.width - params.initialW) < EPS5 && Math.abs(params.rect.height - params.initialH) < EPS5;
@@ -965,11 +1516,11 @@ function maxExpandRight(params) {
965
1516
  const { r, bounds, blockers, maxAspect } = params;
966
1517
  let maxWidth = bounds.x + bounds.width - r.x;
967
1518
  for (const b of blockers) {
968
- const verticallyOverlaps = r.y + r.height > b.y + EPS4 && b.y + b.height > r.y + EPS4;
1519
+ const verticallyOverlaps = r.y + r.height > b.y + EPS && b.y + b.height > r.y + EPS;
969
1520
  if (verticallyOverlaps) {
970
1521
  if (gte(b.x, r.x + r.width)) {
971
1522
  maxWidth = Math.min(maxWidth, b.x - r.x);
972
- } else if (b.x + b.width > r.x + r.width - EPS4 && b.x < r.x + r.width + EPS4) {
1523
+ } else if (b.x + b.width > r.x + r.width - EPS && b.x < r.x + r.width + EPS) {
973
1524
  return 0;
974
1525
  }
975
1526
  }
@@ -986,11 +1537,11 @@ function maxExpandDown(params) {
986
1537
  const { r, bounds, blockers, maxAspect } = params;
987
1538
  let maxHeight = bounds.y + bounds.height - r.y;
988
1539
  for (const b of blockers) {
989
- const horizOverlaps = r.x + r.width > b.x + EPS4 && b.x + b.width > r.x + EPS4;
1540
+ const horizOverlaps = r.x + r.width > b.x + EPS && b.x + b.width > r.x + EPS;
990
1541
  if (horizOverlaps) {
991
1542
  if (gte(b.y, r.y + r.height)) {
992
1543
  maxHeight = Math.min(maxHeight, b.y - r.y);
993
- } else if (b.y + b.height > r.y + r.height - EPS4 && b.y < r.y + r.height + EPS4) {
1544
+ } else if (b.y + b.height > r.y + r.height - EPS && b.y < r.y + r.height + EPS) {
994
1545
  return 0;
995
1546
  }
996
1547
  }
@@ -1007,11 +1558,11 @@ function maxExpandLeft(params) {
1007
1558
  const { r, bounds, blockers, maxAspect } = params;
1008
1559
  let minX = bounds.x;
1009
1560
  for (const b of blockers) {
1010
- const verticallyOverlaps = r.y + r.height > b.y + EPS4 && b.y + b.height > r.y + EPS4;
1561
+ const verticallyOverlaps = r.y + r.height > b.y + EPS && b.y + b.height > r.y + EPS;
1011
1562
  if (verticallyOverlaps) {
1012
1563
  if (lte(b.x + b.width, r.x)) {
1013
1564
  minX = Math.max(minX, b.x + b.width);
1014
- } else if (b.x < r.x + EPS4 && b.x + b.width > r.x - EPS4) {
1565
+ } else if (b.x < r.x + EPS && b.x + b.width > r.x - EPS) {
1015
1566
  return 0;
1016
1567
  }
1017
1568
  }
@@ -1028,11 +1579,11 @@ function maxExpandUp(params) {
1028
1579
  const { r, bounds, blockers, maxAspect } = params;
1029
1580
  let minY = bounds.y;
1030
1581
  for (const b of blockers) {
1031
- const horizOverlaps = r.x + r.width > b.x + EPS4 && b.x + b.width > r.x + EPS4;
1582
+ const horizOverlaps = r.x + r.width > b.x + EPS && b.x + b.width > r.x + EPS;
1032
1583
  if (horizOverlaps) {
1033
1584
  if (lte(b.y + b.height, r.y)) {
1034
1585
  minY = Math.max(minY, b.y + b.height);
1035
- } else if (b.y < r.y + EPS4 && b.y + b.height > r.y - EPS4) {
1586
+ } else if (b.y < r.y + EPS && b.y + b.height > r.y - EPS) {
1036
1587
  return 0;
1037
1588
  }
1038
1589
  }
@@ -1057,7 +1608,7 @@ var toQueryRect = (params) => {
1057
1608
  const minY = Math.max(bounds.y, rect.y);
1058
1609
  const maxX = Math.min(bounds.x + bounds.width, rect.x + rect.width);
1059
1610
  const maxY = Math.min(bounds.y + bounds.height, rect.y + rect.height);
1060
- if (maxX <= minX + EPS4 || maxY <= minY + EPS4) return null;
1611
+ if (maxX <= minX + EPS || maxY <= minY + EPS) return null;
1061
1612
  return { minX, minY, maxX, maxY };
1062
1613
  };
1063
1614
  function expandRectFromSeed(params) {
@@ -1163,7 +1714,7 @@ function expandRectFromSeed(params) {
1163
1714
  improved = true;
1164
1715
  }
1165
1716
  }
1166
- if (r.width + EPS4 >= minReq.width && r.height + EPS4 >= minReq.height) {
1717
+ if (r.width + EPS >= minReq.width && r.height + EPS >= minReq.height) {
1167
1718
  const area = r.width * r.height;
1168
1719
  if (area > bestArea) {
1169
1720
  best = quantizeRect(r);
@@ -1257,7 +1808,7 @@ function computeCandidates3D(params) {
1257
1808
  ]);
1258
1809
  for (let x = bounds.x; x < bounds.x + bounds.width; x += gridSize) {
1259
1810
  for (let y = bounds.y; y < bounds.y + bounds.height; y += gridSize) {
1260
- if (Math.abs(x - bounds.x) < EPS4 || Math.abs(y - bounds.y) < EPS4 || x > bounds.x + bounds.width - gridSize - EPS4 || y > bounds.y + bounds.height - gridSize - EPS4) {
1811
+ if (Math.abs(x - bounds.x) < EPS || Math.abs(y - bounds.y) < EPS || x > bounds.x + bounds.width - gridSize - EPS || y > bounds.y + bounds.height - gridSize - EPS) {
1261
1812
  continue;
1262
1813
  }
1263
1814
  if (isFullyOccupiedAtPoint({
@@ -1329,7 +1880,7 @@ function computeUncoveredSegments(params) {
1329
1880
  const s = quantize3(i.start);
1330
1881
  const e = quantize3(i.end);
1331
1882
  return { start: Math.min(s, e), end: Math.max(s, e) };
1332
- }).filter((i) => i.end > i.start + EPS4);
1883
+ }).filter((i) => i.end > i.start + EPS);
1333
1884
  if (normalizedIntervals.length === 0) {
1334
1885
  const center = (lineStartQ + lineEndQ) / 2;
1335
1886
  return [{ start: lineStartQ, end: lineEndQ, center }];
@@ -1339,7 +1890,7 @@ function computeUncoveredSegments(params) {
1339
1890
  let current = { ...sorted[0] };
1340
1891
  for (let i = 1; i < sorted.length; i++) {
1341
1892
  const interval = sorted[i];
1342
- if (interval.start <= current.end + EPS4) {
1893
+ if (interval.start <= current.end + EPS) {
1343
1894
  current.end = Math.max(current.end, interval.end);
1344
1895
  } else {
1345
1896
  merged.push(current);
@@ -1348,7 +1899,7 @@ function computeUncoveredSegments(params) {
1348
1899
  }
1349
1900
  merged.push(current);
1350
1901
  const uncovered = [];
1351
- if (merged[0].start > lineStartQ + EPS4) {
1902
+ if (merged[0].start > lineStartQ + EPS) {
1352
1903
  const start = lineStartQ;
1353
1904
  const end = merged[0].start;
1354
1905
  if (end - start >= minSegmentLength) {
@@ -1362,7 +1913,7 @@ function computeUncoveredSegments(params) {
1362
1913
  uncovered.push({ start, end, center: (start + end) / 2 });
1363
1914
  }
1364
1915
  }
1365
- if (merged[merged.length - 1].end < lineEndQ - EPS4) {
1916
+ if (merged[merged.length - 1].end < lineEndQ - EPS) {
1366
1917
  const start = merged[merged.length - 1].end;
1367
1918
  const end = lineEndQ;
1368
1919
  if (end - start >= minSegmentLength) {
@@ -1381,7 +1932,7 @@ function computeEdgeCandidates3D(params) {
1381
1932
  hardPlacedByLayer
1382
1933
  } = params;
1383
1934
  const out = [];
1384
- const \u03B4 = Math.max(minSize * 0.15, EPS4 * 3);
1935
+ const \u03B4 = Math.max(minSize * 0.15, EPS * 3);
1385
1936
  const dedup = /* @__PURE__ */ new Set();
1386
1937
  const hardRectsByLayer = Array.from(
1387
1938
  { length: layerCount },
@@ -1405,7 +1956,7 @@ function computeEdgeCandidates3D(params) {
1405
1956
  const { z } = p;
1406
1957
  const x = qx;
1407
1958
  const y = qy;
1408
- if (x < bounds.x + EPS4 || y < bounds.y + EPS4 || x > bounds.x + bounds.width - EPS4 || y > bounds.y + bounds.height - EPS4)
1959
+ if (x < bounds.x + EPS || y < bounds.y + EPS || x > bounds.x + bounds.width - EPS || y > bounds.y + bounds.height - EPS)
1409
1960
  return;
1410
1961
  if (fullyOcc({ x, y })) return;
1411
1962
  const hard = hardRectsByLayer[z] ?? [];
@@ -1542,7 +2093,7 @@ function computeEdgeCandidates3D(params) {
1542
2093
  }
1543
2094
  for (const b of blockers) {
1544
2095
  const obLeftX = b.x - \u03B4;
1545
- if (obLeftX > bounds.x + EPS4 && obLeftX < bounds.x + bounds.width - EPS4) {
2096
+ if (obLeftX > bounds.x + EPS && obLeftX < bounds.x + bounds.width - EPS) {
1546
2097
  const obLeftCovering = blockers.filter(
1547
2098
  (bl) => bl !== b && bl.x <= obLeftX && bl.x + bl.width >= obLeftX
1548
2099
  ).map((bl) => ({
@@ -1560,7 +2111,7 @@ function computeEdgeCandidates3D(params) {
1560
2111
  }
1561
2112
  }
1562
2113
  const obRightX = b.x + b.width + \u03B4;
1563
- if (obRightX > bounds.x + EPS4 && obRightX < bounds.x + bounds.width - EPS4) {
2114
+ if (obRightX > bounds.x + EPS && obRightX < bounds.x + bounds.width - EPS) {
1564
2115
  const obRightCovering = blockers.filter(
1565
2116
  (bl) => bl !== b && bl.x <= obRightX && bl.x + bl.width >= obRightX
1566
2117
  ).map((bl) => ({
@@ -1578,7 +2129,7 @@ function computeEdgeCandidates3D(params) {
1578
2129
  }
1579
2130
  }
1580
2131
  const obTopY = b.y - \u03B4;
1581
- if (obTopY > bounds.y + EPS4 && obTopY < bounds.y + bounds.height - EPS4) {
2132
+ if (obTopY > bounds.y + EPS && obTopY < bounds.y + bounds.height - EPS) {
1582
2133
  const obTopCovering = blockers.filter(
1583
2134
  (bl) => bl !== b && bl.y <= obTopY && bl.y + bl.height >= obTopY
1584
2135
  ).map((bl) => ({
@@ -1596,7 +2147,7 @@ function computeEdgeCandidates3D(params) {
1596
2147
  }
1597
2148
  }
1598
2149
  const obBottomY = b.y + b.height + \u03B4;
1599
- if (obBottomY > bounds.y + EPS4 && obBottomY < bounds.y + bounds.height - EPS4) {
2150
+ if (obBottomY > bounds.y + EPS && obBottomY < bounds.y + bounds.height - EPS) {
1600
2151
  const obBottomCovering = blockers.filter(
1601
2152
  (bl) => bl !== b && bl.y <= obBottomY && bl.y + bl.height >= obBottomY
1602
2153
  ).map((bl) => ({
@@ -1671,12 +2222,12 @@ function resizeSoftOverlaps(params, newIndex) {
1671
2222
  params.options.minMulti.height
1672
2223
  );
1673
2224
  for (const p of parts) {
1674
- if (p.width + EPS4 >= minW && p.height + EPS4 >= minH) {
2225
+ if (p.width + EPS >= minW && p.height + EPS >= minH) {
1675
2226
  toAdd.push({ rect: p, zLayers: sharedZ.slice() });
1676
2227
  }
1677
2228
  }
1678
2229
  }
1679
- const sameRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
2230
+ const sameRect3 = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
1680
2231
  removeIdx.sort((a, b) => b - a).forEach((idx) => {
1681
2232
  const rem = params.placed.splice(idx, 1)[0];
1682
2233
  if (params.placedIndexByLayer) {
@@ -1685,7 +2236,7 @@ function resizeSoftOverlaps(params, newIndex) {
1685
2236
  if (tree)
1686
2237
  tree.remove(
1687
2238
  rectToTree(rem.rect, { zLayers: rem.zLayers }),
1688
- sameRect
2239
+ sameRect3
1689
2240
  );
1690
2241
  }
1691
2242
  }
@@ -1703,27 +2254,14 @@ function resizeSoftOverlaps(params, newIndex) {
1703
2254
  }
1704
2255
  }
1705
2256
 
1706
- // lib/utils/getColorForZLayer.ts
1707
- var getColorForZLayer = (zLayers) => {
1708
- const minZ = Math.min(...zLayers);
1709
- const colors = [
1710
- { fill: "#dbeafe", stroke: "#3b82f6" },
1711
- { fill: "#fef3c7", stroke: "#f59e0b" },
1712
- { fill: "#d1fae5", stroke: "#10b981" },
1713
- { fill: "#e9d5ff", stroke: "#a855f7" },
1714
- { fill: "#fed7aa", stroke: "#f97316" },
1715
- { fill: "#fecaca", stroke: "#ef4444" }
1716
- ];
1717
- return colors[minZ % colors.length];
1718
- };
1719
-
1720
2257
  // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
1721
2258
  import RBush3 from "rbush";
1722
- var RectDiffSeedingSolver = class extends BaseSolver3 {
2259
+ var RectDiffSeedingSolver = class extends BaseSolver5 {
1723
2260
  constructor(input) {
1724
2261
  super();
1725
2262
  this.input = input;
1726
2263
  }
2264
+ input;
1727
2265
  // Engine fields (mirrors initState / engine.ts)
1728
2266
  srj;
1729
2267
  layerNames;
@@ -2077,7 +2615,7 @@ z:${placement.zLayers.join(",")}`
2077
2615
  };
2078
2616
 
2079
2617
  // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
2080
- import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
2618
+ import { BaseSolver as BaseSolver6 } from "@tscircuit/solver-utils";
2081
2619
 
2082
2620
  // lib/utils/finalizeRects.ts
2083
2621
  function finalizeRects(params) {
@@ -2149,11 +2687,12 @@ import RBush4 from "rbush";
2149
2687
  var sameTreeRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
2150
2688
 
2151
2689
  // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
2152
- var RectDiffExpansionSolver = class extends BaseSolver4 {
2690
+ var RectDiffExpansionSolver = class extends BaseSolver6 {
2153
2691
  constructor(input) {
2154
2692
  super();
2155
2693
  this.input = input;
2156
2694
  }
2695
+ input;
2157
2696
  placedIndexByLayer = [];
2158
2697
  _meshNodes = [];
2159
2698
  _setup() {
@@ -2294,19 +2833,6 @@ import "rbush";
2294
2833
 
2295
2834
  // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2296
2835
  import RBush5 from "rbush";
2297
-
2298
- // lib/utils/padRect.ts
2299
- var padRect = (rect, clearance) => {
2300
- if (!clearance || clearance <= 0) return rect;
2301
- return {
2302
- x: rect.x - clearance,
2303
- y: rect.y - clearance,
2304
- width: rect.width + 2 * clearance,
2305
- height: rect.height + 2 * clearance
2306
- };
2307
- };
2308
-
2309
- // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2310
2836
  var buildObstacleIndexesByLayer = (params) => {
2311
2837
  const { srj, boardVoidRects, obstacleClearance } = params;
2312
2838
  const { layerNames, zIndexByName } = buildZIndexMap({
@@ -2650,6 +3176,8 @@ import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
2650
3176
  var RectDiffPipeline = class extends BasePipelineSolver3 {
2651
3177
  rectDiffGridSolverPipeline;
2652
3178
  gapFillSolver;
3179
+ outerLayerContainmentMergeSolver;
3180
+ adjacentLayerContainmentMergeSolver;
2653
3181
  boardVoidRects;
2654
3182
  zIndexByName;
2655
3183
  layerNames;
@@ -2685,6 +3213,28 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2685
3213
  }
2686
3214
  }
2687
3215
  ]
3216
+ ),
3217
+ definePipelineStep3(
3218
+ "outerLayerContainmentMergeSolver",
3219
+ OuterLayerContainmentMergeSolver,
3220
+ (rectDiffPipeline) => [
3221
+ {
3222
+ meshNodes: rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ?? rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
3223
+ simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
3224
+ zIndexByName: rectDiffPipeline.zIndexByName ?? /* @__PURE__ */ new Map(),
3225
+ obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance
3226
+ }
3227
+ ]
3228
+ ),
3229
+ definePipelineStep3(
3230
+ "adjacentLayerContainmentMergeSolver",
3231
+ AdjacentLayerContainmentMergeSolver,
3232
+ (rectDiffPipeline) => [
3233
+ {
3234
+ meshNodes: rectDiffPipeline.outerLayerContainmentMergeSolver?.getOutput().outputNodes ?? rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ?? rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
3235
+ simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson
3236
+ }
3237
+ ]
2688
3238
  )
2689
3239
  ];
2690
3240
  _setup() {
@@ -2710,6 +3260,14 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2710
3260
  return [this.inputProblem];
2711
3261
  }
2712
3262
  getOutput() {
3263
+ const adjacentLayerMergeOutput = this.adjacentLayerContainmentMergeSolver?.getOutput();
3264
+ if (adjacentLayerMergeOutput) {
3265
+ return { meshNodes: adjacentLayerMergeOutput.outputNodes };
3266
+ }
3267
+ const outerLayerMergeOutput = this.outerLayerContainmentMergeSolver?.getOutput();
3268
+ if (outerLayerMergeOutput) {
3269
+ return { meshNodes: outerLayerMergeOutput.outputNodes };
3270
+ }
2713
3271
  const gapFillOutput = this.gapFillSolver?.getOutput();
2714
3272
  if (gapFillOutput) {
2715
3273
  return { meshNodes: gapFillOutput.outputNodes };