@tscircuit/rectdiff 0.0.26 → 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 (35) hide show
  1. package/dist/index.d.ts +21 -0
  2. package/dist/index.js +487 -160
  3. package/lib/RectDiffPipeline.ts +23 -0
  4. package/lib/solvers/AdjacentLayerContainmentMergeSolver/AdjacentLayerContainmentMergeSolver.ts +456 -0
  5. package/package.json +1 -1
  6. package/pages/bugreports/bugreport50-multi-support-layer-merge.page.tsx +19 -0
  7. package/tests/__snapshots__/board-outline.snap.svg +2 -2
  8. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
  9. package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
  10. package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +1 -1
  11. package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +1 -1
  12. package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
  13. package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
  14. package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
  15. package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
  16. package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +1 -1
  17. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
  18. package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
  19. package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +1 -1
  20. package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
  21. package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
  22. package/tests/solver/bugreport20-obstacle-clipping/__snapshots__/bugreport20-obstacle-clipping.snap.svg +1 -1
  23. package/tests/solver/bugreport21-board-outline/__snapshots__/bugreport21-board-outline.snap.svg +2 -2
  24. package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
  25. package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
  26. package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
  27. package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +1 -1
  28. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
  29. package/tests/solver/bugreport50-multi-support-layer-merge/__snapshots__/bugreport50-multi-support-layer-merge.snap.svg +44 -0
  30. package/tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.json +972 -0
  31. package/tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.test.ts +125 -0
  32. package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
  33. package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
  34. package/tests/solver/no-better-path/__snapshots__/no-better-path.snap.svg +1 -1
  35. 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
  }
@@ -221,10 +603,10 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver {
221
603
  const candidateEdge = this.unprocessedEdges.shift();
222
604
  this.lastCandidateEdge = candidateEdge;
223
605
  const nearbyEdges = this.edgeSpatialIndex.search(
224
- candidateEdge.start.x - EPS2,
225
- candidateEdge.start.y - EPS2,
226
- candidateEdge.end.x + EPS2,
227
- candidateEdge.end.y + EPS2
606
+ candidateEdge.start.x - EPS3,
607
+ candidateEdge.start.y - EPS3,
608
+ candidateEdge.end.x + EPS3,
609
+ candidateEdge.end.y + EPS3
228
610
  );
229
611
  const overlappingEdges = nearbyEdges.map((i) => this.allEdges[i]).filter((e) => e.z === candidateEdge.z);
230
612
  this.lastOverlappingEdges = overlappingEdges;
@@ -310,7 +692,7 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver {
310
692
  };
311
693
 
312
694
  // lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
313
- import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
695
+ import { BaseSolver as BaseSolver3 } from "@tscircuit/solver-utils";
314
696
  import RBush from "rbush";
315
697
 
316
698
  // lib/solvers/GapFillSolver/getBoundsFromCorners.ts
@@ -325,8 +707,8 @@ var getBoundsFromCorners = (corners) => {
325
707
 
326
708
  // lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
327
709
  import { segmentToBoxMinDistance } from "@tscircuit/math-utils";
328
- var EPS3 = 1e-4;
329
- var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
710
+ var EPS4 = 1e-4;
711
+ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver3 {
330
712
  constructor(input) {
331
713
  super();
332
714
  this.input = input;
@@ -392,12 +774,12 @@ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
392
774
  let collidingNodes = null;
393
775
  let searchDistance = 1;
394
776
  const searchCorner1 = {
395
- x: segment.start.x + dx * EPS3 + normDeltaStartEnd.x * EPS3 * 10,
396
- 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
397
779
  };
398
780
  const searchCorner2 = {
399
- x: segment.end.x + dx * EPS3 - normDeltaStartEnd.x * EPS3 * 10,
400
- 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
401
783
  };
402
784
  this.lastSearchCorner1 = searchCorner1;
403
785
  this.lastSearchCorner2 = searchCorner2;
@@ -462,7 +844,7 @@ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
462
844
  }
463
845
  };
464
846
  this.lastExpandedSegment = expandedSegment;
465
- if (nodeWidth < EPS3 || nodeHeight < EPS3) {
847
+ if (nodeWidth < EPS4 || nodeHeight < EPS4) {
466
848
  return;
467
849
  }
468
850
  this.expandedSegments.push(expandedSegment);
@@ -668,7 +1050,7 @@ var GapFillSolverPipeline = class extends BasePipelineSolver {
668
1050
  };
669
1051
 
670
1052
  // lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts
671
- import { BaseSolver as BaseSolver3 } from "@tscircuit/solver-utils";
1053
+ import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
672
1054
 
673
1055
  // lib/solvers/RectDiffSeedingSolver/layers.ts
674
1056
  function layerSortKey(n) {
@@ -745,76 +1127,6 @@ function obstacleToXYRect(ob) {
745
1127
  return { x: ob.center.x - w / 2, y: ob.center.y - h / 2, width: w, height: h };
746
1128
  }
747
1129
 
748
- // lib/utils/getColorForZLayer.ts
749
- var getColorForZLayer = (zLayers) => {
750
- const minZ = Math.min(...zLayers);
751
- const colors = [
752
- { fill: "#dbeafe", stroke: "#3b82f6" },
753
- { fill: "#fef3c7", stroke: "#f59e0b" },
754
- { fill: "#d1fae5", stroke: "#10b981" },
755
- { fill: "#e9d5ff", stroke: "#a855f7" },
756
- { fill: "#fed7aa", stroke: "#f97316" },
757
- { fill: "#fecaca", stroke: "#ef4444" }
758
- ];
759
- return colors[minZ % colors.length];
760
- };
761
-
762
- // lib/utils/rectdiff-geometry.ts
763
- var EPS4 = 1e-9;
764
- var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
765
- var gt = (a, b) => a > b + EPS4;
766
- var gte = (a, b) => a > b - EPS4;
767
- var lt = (a, b) => a < b - EPS4;
768
- var lte = (a, b) => a < b + EPS4;
769
- function overlaps(a, b) {
770
- 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);
771
- }
772
- function containsPoint(r, p) {
773
- 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;
774
- }
775
- function distancePointToRectEdges(p, r) {
776
- const minX = r.x;
777
- const maxX = r.x + r.width;
778
- const minY = r.y;
779
- const maxY = r.y + r.height;
780
- if (p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY) {
781
- return Math.min(p.x - minX, maxX - p.x, p.y - minY, maxY - p.y);
782
- }
783
- const dx = p.x < minX ? minX - p.x : p.x > maxX ? p.x - maxX : 0;
784
- const dy = p.y < minY ? minY - p.y : p.y > maxY ? p.y - maxY : 0;
785
- if (dx === 0) return dy;
786
- if (dy === 0) return dx;
787
- return Math.hypot(dx, dy);
788
- }
789
- function intersect1D(r1, r2) {
790
- const lo = Math.max(r1[0], r2[0]);
791
- const hi = Math.min(r1[1], r2[1]);
792
- return hi > lo + EPS4 ? [lo, hi] : null;
793
- }
794
- function subtractRect2D(A, B) {
795
- if (!overlaps(A, B)) return [A];
796
- const Xi = intersect1D([A.x, A.x + A.width], [B.x, B.x + B.width]);
797
- const Yi = intersect1D([A.y, A.y + A.height], [B.y, B.y + B.height]);
798
- if (!Xi || !Yi) return [A];
799
- const [X0, X1] = Xi;
800
- const [Y0, Y1] = Yi;
801
- const out = [];
802
- if (X0 > A.x + EPS4) {
803
- out.push({ x: A.x, y: A.y, width: X0 - A.x, height: A.height });
804
- }
805
- if (A.x + A.width > X1 + EPS4) {
806
- out.push({ x: X1, y: A.y, width: A.x + A.width - X1, height: A.height });
807
- }
808
- const midW = Math.max(0, X1 - X0);
809
- if (midW > EPS4 && Y0 > A.y + EPS4) {
810
- out.push({ x: X0, y: A.y, width: midW, height: Y0 - A.y });
811
- }
812
- if (midW > EPS4 && A.y + A.height > Y1 + EPS4) {
813
- out.push({ x: X0, y: Y1, width: midW, height: A.y + A.height - Y1 });
814
- }
815
- return out.filter((r) => r.width > EPS4 && r.height > EPS4);
816
- }
817
-
818
1130
  // lib/utils/padRect.ts
819
1131
  var padRect = (rect, clearance) => {
820
1132
  if (!clearance || clearance <= 0) return rect;
@@ -827,19 +1139,19 @@ var padRect = (rect, clearance) => {
827
1139
  };
828
1140
 
829
1141
  // lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts
830
- var nodeToRect = (node) => ({
1142
+ var nodeToRect2 = (node) => ({
831
1143
  x: node.center.x - node.width / 2,
832
1144
  y: node.center.y - node.height / 2,
833
1145
  width: node.width,
834
1146
  height: node.height
835
1147
  });
836
- var rectArea = (rect) => rect.width * rect.height;
837
- var cloneNode = (node) => ({
1148
+ var rectArea2 = (rect) => rect.width * rect.height;
1149
+ var cloneNode2 = (node) => ({
838
1150
  ...node,
839
1151
  center: { ...node.center },
840
1152
  availableZ: [...node.availableZ]
841
1153
  });
842
- var cloneNodeWithRect = (node, rect, capacityMeshNodeId) => ({
1154
+ var cloneNodeWithRect2 = (node, rect, capacityMeshNodeId) => ({
843
1155
  ...node,
844
1156
  capacityMeshNodeId,
845
1157
  center: {
@@ -851,10 +1163,10 @@ var cloneNodeWithRect = (node, rect, capacityMeshNodeId) => ({
851
1163
  availableZ: [...node.availableZ],
852
1164
  layer: `z${node.availableZ.join(",")}`
853
1165
  });
854
- var isFreeNode = (node) => !node._containsObstacle && !node._containsTarget;
1166
+ var isFreeNode2 = (node) => !node._containsObstacle && !node._containsTarget;
855
1167
  var isSingletonOuterNode = (node, outerZ) => node.availableZ.length === 1 && node.availableZ[0] === outerZ;
856
- var sameRect = (a, b) => Math.abs(a.x - b.x) <= EPS4 && Math.abs(a.y - b.y) <= EPS4 && Math.abs(a.width - b.width) <= EPS4 && Math.abs(a.height - b.height) <= EPS4;
857
- var subtractRects = (target, cutters) => {
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) => {
858
1170
  let remaining = [target];
859
1171
  for (const cutter of cutters) {
860
1172
  if (remaining.length === 0) return remaining;
@@ -866,10 +1178,10 @@ var subtractRects = (target, cutters) => {
866
1178
  }
867
1179
  return remaining;
868
1180
  };
869
- var isFullyCoveredByRects = (target, coveringRects) => {
870
- return subtractRects(target, coveringRects).length === 0;
1181
+ var isFullyCoveredByRects2 = (target, coveringRects) => {
1182
+ return subtractRects2(target, coveringRects).length === 0;
871
1183
  };
872
- var OuterLayerContainmentMergeSolver = class extends BaseSolver3 {
1184
+ var OuterLayerContainmentMergeSolver = class extends BaseSolver4 {
873
1185
  constructor(input) {
874
1186
  super();
875
1187
  this.input = input;
@@ -879,7 +1191,7 @@ var OuterLayerContainmentMergeSolver = class extends BaseSolver3 {
879
1191
  promotedNodeIds = /* @__PURE__ */ new Set();
880
1192
  residualNodeIds = /* @__PURE__ */ new Set();
881
1193
  _setup() {
882
- this.outputNodes = this.input.meshNodes.map(cloneNode);
1194
+ this.outputNodes = this.input.meshNodes.map(cloneNode2);
883
1195
  this.promotedNodeIds.clear();
884
1196
  this.residualNodeIds.clear();
885
1197
  }
@@ -891,15 +1203,15 @@ var OuterLayerContainmentMergeSolver = class extends BaseSolver3 {
891
1203
  const srj = this.input.simpleRouteJson;
892
1204
  const layerCount = Math.max(1, srj.layerCount || 1);
893
1205
  if (layerCount < 3) {
894
- return this.input.meshNodes.map(cloneNode);
1206
+ return this.input.meshNodes.map(cloneNode2);
895
1207
  }
896
1208
  const topZ = 0;
897
1209
  const bottomZ = layerCount - 1;
898
1210
  const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0);
899
- const originalNodes = this.input.meshNodes.map(cloneNode);
1211
+ const originalNodes = this.input.meshNodes.map(cloneNode2);
900
1212
  const obstaclesByLayer = this.buildObstaclesByLayer(layerCount);
901
1213
  const mutableOuterNodes = originalNodes.filter(
902
- (node) => isFreeNode(node) && (isSingletonOuterNode(node, topZ) || isSingletonOuterNode(node, bottomZ))
1214
+ (node) => isFreeNode2(node) && (isSingletonOuterNode(node, topZ) || isSingletonOuterNode(node, bottomZ))
903
1215
  );
904
1216
  const immutableNodes = originalNodes.filter(
905
1217
  (node) => !mutableOuterNodes.includes(node)
@@ -907,21 +1219,21 @@ var OuterLayerContainmentMergeSolver = class extends BaseSolver3 {
907
1219
  const freeSupportRectsByOuterLayer = /* @__PURE__ */ new Map();
908
1220
  freeSupportRectsByOuterLayer.set(
909
1221
  topZ,
910
- originalNodes.filter((node) => isFreeNode(node) && node.availableZ.includes(topZ)).map(nodeToRect)
1222
+ originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(topZ)).map(nodeToRect2)
911
1223
  );
912
1224
  freeSupportRectsByOuterLayer.set(
913
1225
  bottomZ,
914
- originalNodes.filter((node) => isFreeNode(node) && node.availableZ.includes(bottomZ)).map(nodeToRect)
1226
+ originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(bottomZ)).map(nodeToRect2)
915
1227
  );
916
1228
  const promotedNodes = [];
917
1229
  const promotedRects = [];
918
1230
  const candidateNodes = mutableOuterNodes.filter(
919
- (node) => node.width + EPS4 >= viaMinSize && node.height + EPS4 >= viaMinSize
920
- ).sort((a, b) => rectArea(nodeToRect(b)) - rectArea(nodeToRect(a)));
1231
+ (node) => node.width + EPS >= viaMinSize && node.height + EPS >= viaMinSize
1232
+ ).sort((a, b) => rectArea2(nodeToRect2(b)) - rectArea2(nodeToRect2(a)));
921
1233
  for (const candidate of candidateNodes) {
922
1234
  const candidateZ = candidate.availableZ[0];
923
1235
  const oppositeZ = candidateZ === topZ ? bottomZ : topZ;
924
- const candidateRect = nodeToRect(candidate);
1236
+ const candidateRect = nodeToRect2(candidate);
925
1237
  const oppositeSupportRects = freeSupportRectsByOuterLayer.get(oppositeZ) ?? [];
926
1238
  if (!this.isTransitCompatibleAcrossIntermediateLayers({
927
1239
  rect: candidateRect,
@@ -931,7 +1243,7 @@ var OuterLayerContainmentMergeSolver = class extends BaseSolver3 {
931
1243
  })) {
932
1244
  continue;
933
1245
  }
934
- if (!isFullyCoveredByRects(candidateRect, oppositeSupportRects)) {
1246
+ if (!isFullyCoveredByRects2(candidateRect, oppositeSupportRects)) {
935
1247
  continue;
936
1248
  }
937
1249
  promotedNodes.push({
@@ -948,14 +1260,14 @@ var OuterLayerContainmentMergeSolver = class extends BaseSolver3 {
948
1260
  if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
949
1261
  continue;
950
1262
  }
951
- const nodeRect = nodeToRect(node);
952
- const remainingPieces = subtractRects(nodeRect, promotedRects);
953
- if (remainingPieces.length === 1 && sameRect(remainingPieces[0], nodeRect)) {
1263
+ const nodeRect = nodeToRect2(node);
1264
+ const remainingPieces = subtractRects2(nodeRect, promotedRects);
1265
+ if (remainingPieces.length === 1 && sameRect2(remainingPieces[0], nodeRect)) {
954
1266
  residualNodes.push(node);
955
1267
  continue;
956
1268
  }
957
1269
  for (const piece of remainingPieces) {
958
- const residualNode = cloneNodeWithRect(
1270
+ const residualNode = cloneNodeWithRect2(
959
1271
  node,
960
1272
  piece,
961
1273
  `${node.capacityMeshNodeId}-outer-merge-${nextResidualId++}`
@@ -998,7 +1310,7 @@ var OuterLayerContainmentMergeSolver = class extends BaseSolver3 {
998
1310
  );
999
1311
  if (nonCopperOverlap) return false;
1000
1312
  const copperRects = overlapping.filter((entry) => entry.obstacle.isCopperPour).map((entry) => entry.rect);
1001
- if (!isFullyCoveredByRects(rect, copperRects)) {
1313
+ if (!isFullyCoveredByRects2(rect, copperRects)) {
1002
1314
  return false;
1003
1315
  }
1004
1316
  }
@@ -1042,7 +1354,7 @@ import {
1042
1354
  } from "@tscircuit/solver-utils";
1043
1355
 
1044
1356
  // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
1045
- import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
1357
+ import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
1046
1358
 
1047
1359
  // lib/solvers/RectDiffSeedingSolver/isPointInPolygon.ts
1048
1360
  function isPointInPolygon(p, polygon) {
@@ -1105,7 +1417,7 @@ function computeInverseRects(bounds, polygon) {
1105
1417
  }
1106
1418
  const finalRects = [];
1107
1419
  rawRects.sort((a, b) => {
1108
- 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;
1109
1421
  return a.x - b.x;
1110
1422
  });
1111
1423
  let current = null;
@@ -1114,9 +1426,9 @@ function computeInverseRects(bounds, polygon) {
1114
1426
  current = r;
1115
1427
  continue;
1116
1428
  }
1117
- const sameY = Math.abs(current.y - r.y) < EPS4;
1118
- const sameHeight = Math.abs(current.height - r.height) < EPS4;
1119
- 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;
1120
1432
  if (sameY && sameHeight && touchesX) {
1121
1433
  current.width += r.width;
1122
1434
  } else {
@@ -1126,7 +1438,7 @@ function computeInverseRects(bounds, polygon) {
1126
1438
  }
1127
1439
  if (current) finalRects.push(current);
1128
1440
  finalRects.sort((a, b) => {
1129
- 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;
1130
1442
  return a.y - b.y;
1131
1443
  });
1132
1444
  const mergedVertical = [];
@@ -1136,9 +1448,9 @@ function computeInverseRects(bounds, polygon) {
1136
1448
  current = r;
1137
1449
  continue;
1138
1450
  }
1139
- const sameX = Math.abs(current.x - r.x) < EPS4;
1140
- const sameWidth = Math.abs(current.width - r.width) < EPS4;
1141
- 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;
1142
1454
  if (sameX && sameWidth && touchesY) {
1143
1455
  current.height += r.height;
1144
1456
  } else {
@@ -1204,11 +1516,11 @@ function maxExpandRight(params) {
1204
1516
  const { r, bounds, blockers, maxAspect } = params;
1205
1517
  let maxWidth = bounds.x + bounds.width - r.x;
1206
1518
  for (const b of blockers) {
1207
- 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;
1208
1520
  if (verticallyOverlaps) {
1209
1521
  if (gte(b.x, r.x + r.width)) {
1210
1522
  maxWidth = Math.min(maxWidth, b.x - r.x);
1211
- } 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) {
1212
1524
  return 0;
1213
1525
  }
1214
1526
  }
@@ -1225,11 +1537,11 @@ function maxExpandDown(params) {
1225
1537
  const { r, bounds, blockers, maxAspect } = params;
1226
1538
  let maxHeight = bounds.y + bounds.height - r.y;
1227
1539
  for (const b of blockers) {
1228
- 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;
1229
1541
  if (horizOverlaps) {
1230
1542
  if (gte(b.y, r.y + r.height)) {
1231
1543
  maxHeight = Math.min(maxHeight, b.y - r.y);
1232
- } 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) {
1233
1545
  return 0;
1234
1546
  }
1235
1547
  }
@@ -1246,11 +1558,11 @@ function maxExpandLeft(params) {
1246
1558
  const { r, bounds, blockers, maxAspect } = params;
1247
1559
  let minX = bounds.x;
1248
1560
  for (const b of blockers) {
1249
- 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;
1250
1562
  if (verticallyOverlaps) {
1251
1563
  if (lte(b.x + b.width, r.x)) {
1252
1564
  minX = Math.max(minX, b.x + b.width);
1253
- } 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) {
1254
1566
  return 0;
1255
1567
  }
1256
1568
  }
@@ -1267,11 +1579,11 @@ function maxExpandUp(params) {
1267
1579
  const { r, bounds, blockers, maxAspect } = params;
1268
1580
  let minY = bounds.y;
1269
1581
  for (const b of blockers) {
1270
- 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;
1271
1583
  if (horizOverlaps) {
1272
1584
  if (lte(b.y + b.height, r.y)) {
1273
1585
  minY = Math.max(minY, b.y + b.height);
1274
- } 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) {
1275
1587
  return 0;
1276
1588
  }
1277
1589
  }
@@ -1296,7 +1608,7 @@ var toQueryRect = (params) => {
1296
1608
  const minY = Math.max(bounds.y, rect.y);
1297
1609
  const maxX = Math.min(bounds.x + bounds.width, rect.x + rect.width);
1298
1610
  const maxY = Math.min(bounds.y + bounds.height, rect.y + rect.height);
1299
- if (maxX <= minX + EPS4 || maxY <= minY + EPS4) return null;
1611
+ if (maxX <= minX + EPS || maxY <= minY + EPS) return null;
1300
1612
  return { minX, minY, maxX, maxY };
1301
1613
  };
1302
1614
  function expandRectFromSeed(params) {
@@ -1402,7 +1714,7 @@ function expandRectFromSeed(params) {
1402
1714
  improved = true;
1403
1715
  }
1404
1716
  }
1405
- if (r.width + EPS4 >= minReq.width && r.height + EPS4 >= minReq.height) {
1717
+ if (r.width + EPS >= minReq.width && r.height + EPS >= minReq.height) {
1406
1718
  const area = r.width * r.height;
1407
1719
  if (area > bestArea) {
1408
1720
  best = quantizeRect(r);
@@ -1496,7 +1808,7 @@ function computeCandidates3D(params) {
1496
1808
  ]);
1497
1809
  for (let x = bounds.x; x < bounds.x + bounds.width; x += gridSize) {
1498
1810
  for (let y = bounds.y; y < bounds.y + bounds.height; y += gridSize) {
1499
- 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) {
1500
1812
  continue;
1501
1813
  }
1502
1814
  if (isFullyOccupiedAtPoint({
@@ -1568,7 +1880,7 @@ function computeUncoveredSegments(params) {
1568
1880
  const s = quantize3(i.start);
1569
1881
  const e = quantize3(i.end);
1570
1882
  return { start: Math.min(s, e), end: Math.max(s, e) };
1571
- }).filter((i) => i.end > i.start + EPS4);
1883
+ }).filter((i) => i.end > i.start + EPS);
1572
1884
  if (normalizedIntervals.length === 0) {
1573
1885
  const center = (lineStartQ + lineEndQ) / 2;
1574
1886
  return [{ start: lineStartQ, end: lineEndQ, center }];
@@ -1578,7 +1890,7 @@ function computeUncoveredSegments(params) {
1578
1890
  let current = { ...sorted[0] };
1579
1891
  for (let i = 1; i < sorted.length; i++) {
1580
1892
  const interval = sorted[i];
1581
- if (interval.start <= current.end + EPS4) {
1893
+ if (interval.start <= current.end + EPS) {
1582
1894
  current.end = Math.max(current.end, interval.end);
1583
1895
  } else {
1584
1896
  merged.push(current);
@@ -1587,7 +1899,7 @@ function computeUncoveredSegments(params) {
1587
1899
  }
1588
1900
  merged.push(current);
1589
1901
  const uncovered = [];
1590
- if (merged[0].start > lineStartQ + EPS4) {
1902
+ if (merged[0].start > lineStartQ + EPS) {
1591
1903
  const start = lineStartQ;
1592
1904
  const end = merged[0].start;
1593
1905
  if (end - start >= minSegmentLength) {
@@ -1601,7 +1913,7 @@ function computeUncoveredSegments(params) {
1601
1913
  uncovered.push({ start, end, center: (start + end) / 2 });
1602
1914
  }
1603
1915
  }
1604
- if (merged[merged.length - 1].end < lineEndQ - EPS4) {
1916
+ if (merged[merged.length - 1].end < lineEndQ - EPS) {
1605
1917
  const start = merged[merged.length - 1].end;
1606
1918
  const end = lineEndQ;
1607
1919
  if (end - start >= minSegmentLength) {
@@ -1620,7 +1932,7 @@ function computeEdgeCandidates3D(params) {
1620
1932
  hardPlacedByLayer
1621
1933
  } = params;
1622
1934
  const out = [];
1623
- const \u03B4 = Math.max(minSize * 0.15, EPS4 * 3);
1935
+ const \u03B4 = Math.max(minSize * 0.15, EPS * 3);
1624
1936
  const dedup = /* @__PURE__ */ new Set();
1625
1937
  const hardRectsByLayer = Array.from(
1626
1938
  { length: layerCount },
@@ -1644,7 +1956,7 @@ function computeEdgeCandidates3D(params) {
1644
1956
  const { z } = p;
1645
1957
  const x = qx;
1646
1958
  const y = qy;
1647
- 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)
1648
1960
  return;
1649
1961
  if (fullyOcc({ x, y })) return;
1650
1962
  const hard = hardRectsByLayer[z] ?? [];
@@ -1781,7 +2093,7 @@ function computeEdgeCandidates3D(params) {
1781
2093
  }
1782
2094
  for (const b of blockers) {
1783
2095
  const obLeftX = b.x - \u03B4;
1784
- if (obLeftX > bounds.x + EPS4 && obLeftX < bounds.x + bounds.width - EPS4) {
2096
+ if (obLeftX > bounds.x + EPS && obLeftX < bounds.x + bounds.width - EPS) {
1785
2097
  const obLeftCovering = blockers.filter(
1786
2098
  (bl) => bl !== b && bl.x <= obLeftX && bl.x + bl.width >= obLeftX
1787
2099
  ).map((bl) => ({
@@ -1799,7 +2111,7 @@ function computeEdgeCandidates3D(params) {
1799
2111
  }
1800
2112
  }
1801
2113
  const obRightX = b.x + b.width + \u03B4;
1802
- if (obRightX > bounds.x + EPS4 && obRightX < bounds.x + bounds.width - EPS4) {
2114
+ if (obRightX > bounds.x + EPS && obRightX < bounds.x + bounds.width - EPS) {
1803
2115
  const obRightCovering = blockers.filter(
1804
2116
  (bl) => bl !== b && bl.x <= obRightX && bl.x + bl.width >= obRightX
1805
2117
  ).map((bl) => ({
@@ -1817,7 +2129,7 @@ function computeEdgeCandidates3D(params) {
1817
2129
  }
1818
2130
  }
1819
2131
  const obTopY = b.y - \u03B4;
1820
- if (obTopY > bounds.y + EPS4 && obTopY < bounds.y + bounds.height - EPS4) {
2132
+ if (obTopY > bounds.y + EPS && obTopY < bounds.y + bounds.height - EPS) {
1821
2133
  const obTopCovering = blockers.filter(
1822
2134
  (bl) => bl !== b && bl.y <= obTopY && bl.y + bl.height >= obTopY
1823
2135
  ).map((bl) => ({
@@ -1835,7 +2147,7 @@ function computeEdgeCandidates3D(params) {
1835
2147
  }
1836
2148
  }
1837
2149
  const obBottomY = b.y + b.height + \u03B4;
1838
- if (obBottomY > bounds.y + EPS4 && obBottomY < bounds.y + bounds.height - EPS4) {
2150
+ if (obBottomY > bounds.y + EPS && obBottomY < bounds.y + bounds.height - EPS) {
1839
2151
  const obBottomCovering = blockers.filter(
1840
2152
  (bl) => bl !== b && bl.y <= obBottomY && bl.y + bl.height >= obBottomY
1841
2153
  ).map((bl) => ({
@@ -1910,12 +2222,12 @@ function resizeSoftOverlaps(params, newIndex) {
1910
2222
  params.options.minMulti.height
1911
2223
  );
1912
2224
  for (const p of parts) {
1913
- if (p.width + EPS4 >= minW && p.height + EPS4 >= minH) {
2225
+ if (p.width + EPS >= minW && p.height + EPS >= minH) {
1914
2226
  toAdd.push({ rect: p, zLayers: sharedZ.slice() });
1915
2227
  }
1916
2228
  }
1917
2229
  }
1918
- const sameRect2 = (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;
1919
2231
  removeIdx.sort((a, b) => b - a).forEach((idx) => {
1920
2232
  const rem = params.placed.splice(idx, 1)[0];
1921
2233
  if (params.placedIndexByLayer) {
@@ -1924,7 +2236,7 @@ function resizeSoftOverlaps(params, newIndex) {
1924
2236
  if (tree)
1925
2237
  tree.remove(
1926
2238
  rectToTree(rem.rect, { zLayers: rem.zLayers }),
1927
- sameRect2
2239
+ sameRect3
1928
2240
  );
1929
2241
  }
1930
2242
  }
@@ -1944,7 +2256,7 @@ function resizeSoftOverlaps(params, newIndex) {
1944
2256
 
1945
2257
  // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
1946
2258
  import RBush3 from "rbush";
1947
- var RectDiffSeedingSolver = class extends BaseSolver4 {
2259
+ var RectDiffSeedingSolver = class extends BaseSolver5 {
1948
2260
  constructor(input) {
1949
2261
  super();
1950
2262
  this.input = input;
@@ -2303,7 +2615,7 @@ z:${placement.zLayers.join(",")}`
2303
2615
  };
2304
2616
 
2305
2617
  // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
2306
- import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
2618
+ import { BaseSolver as BaseSolver6 } from "@tscircuit/solver-utils";
2307
2619
 
2308
2620
  // lib/utils/finalizeRects.ts
2309
2621
  function finalizeRects(params) {
@@ -2375,7 +2687,7 @@ import RBush4 from "rbush";
2375
2687
  var sameTreeRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
2376
2688
 
2377
2689
  // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
2378
- var RectDiffExpansionSolver = class extends BaseSolver5 {
2690
+ var RectDiffExpansionSolver = class extends BaseSolver6 {
2379
2691
  constructor(input) {
2380
2692
  super();
2381
2693
  this.input = input;
@@ -2865,6 +3177,7 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2865
3177
  rectDiffGridSolverPipeline;
2866
3178
  gapFillSolver;
2867
3179
  outerLayerContainmentMergeSolver;
3180
+ adjacentLayerContainmentMergeSolver;
2868
3181
  boardVoidRects;
2869
3182
  zIndexByName;
2870
3183
  layerNames;
@@ -2912,6 +3225,16 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2912
3225
  obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance
2913
3226
  }
2914
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
+ ]
2915
3238
  )
2916
3239
  ];
2917
3240
  _setup() {
@@ -2937,6 +3260,10 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2937
3260
  return [this.inputProblem];
2938
3261
  }
2939
3262
  getOutput() {
3263
+ const adjacentLayerMergeOutput = this.adjacentLayerContainmentMergeSolver?.getOutput();
3264
+ if (adjacentLayerMergeOutput) {
3265
+ return { meshNodes: adjacentLayerMergeOutput.outputNodes };
3266
+ }
2940
3267
  const outerLayerMergeOutput = this.outerLayerContainmentMergeSolver?.getOutput();
2941
3268
  if (outerLayerMergeOutput) {
2942
3269
  return { meshNodes: outerLayerMergeOutput.outputNodes };