@tscircuit/rectdiff 0.0.28 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/bun-pver-release.yml +24 -45
- package/dist/index.d.ts +0 -46
- package/dist/index.js +220 -775
- package/lib/RectDiffPipeline.ts +0 -46
- package/lib/types/srj-types.ts +0 -1
- package/package.json +1 -2
- package/pages/repro/merge-single-layer-node.page.tsx +17 -0
- package/tests/__snapshots__/board-outline.snap.svg +2 -2
- package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
- package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
- package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +1 -1
- package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +1 -1
- package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
- package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
- package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
- package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
- package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +1 -1
- package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
- package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
- package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +1 -1
- package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
- package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
- package/tests/solver/bugreport20-obstacle-clipping/__snapshots__/bugreport20-obstacle-clipping.snap.svg +1 -1
- package/tests/solver/bugreport21-board-outline/__snapshots__/bugreport21-board-outline.snap.svg +2 -2
- package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
- package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
- package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
- package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +1 -1
- package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
- package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
- package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
- package/tests/solver/no-better-path/__snapshots__/no-better-path.snap.svg +1 -1
- package/tests/solver/repros/merge-single-layer-node/merge-single-layer-node.json +861 -0
- package/tests/solver/{bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.test.ts → repros/merge-single-layer-node/merge-single-layer-node.test.ts} +7 -42
- package/tests/solver/transitivity/__snapshots__/transitivity.snap.svg +2 -2
- package/tsconfig.json +5 -1
- package/vite.config.ts +4 -0
- package/lib/solvers/AdjacentLayerContainmentMergeSolver/AdjacentLayerContainmentMergeSolver.ts +0 -456
- package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +0 -314
- package/pages/bugreports/bugreport50-multi-support-layer-merge.page.tsx +0 -19
- package/pages/pour.page.tsx +0 -18
- package/test-assets/bugreport49-634662.json +0 -412
- package/tests/outer-layer-containment-merge-solver.test.ts +0 -73
- package/tests/solver/bugreport49-634662/__snapshots__/bugreport49-634662.snap.svg +0 -44
- package/tests/solver/bugreport49-634662/bugreport49-634662.test.ts +0 -130
- package/tests/solver/bugreport50-multi-support-layer-merge/__snapshots__/bugreport50-multi-support-layer-merge.snap.svg +0 -44
- package/tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.json +0 -972
package/dist/index.js
CHANGED
|
@@ -4,388 +4,6 @@ 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
|
-
|
|
389
7
|
// lib/solvers/GapFillSolver/GapFillSolverPipeline.ts
|
|
390
8
|
import {
|
|
391
9
|
BasePipelineSolver,
|
|
@@ -393,14 +11,14 @@ import {
|
|
|
393
11
|
} from "@tscircuit/solver-utils";
|
|
394
12
|
|
|
395
13
|
// lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
|
|
396
|
-
import { BaseSolver
|
|
14
|
+
import { BaseSolver } from "@tscircuit/solver-utils";
|
|
397
15
|
import Flatbush from "flatbush";
|
|
398
16
|
|
|
399
17
|
// lib/solvers/GapFillSolver/projectToUncoveredSegments.ts
|
|
400
|
-
var
|
|
18
|
+
var EPS = 1e-4;
|
|
401
19
|
function projectToUncoveredSegments(primaryEdge, overlappingEdges) {
|
|
402
|
-
const isHorizontal = Math.abs(primaryEdge.start.y - primaryEdge.end.y) <
|
|
403
|
-
const isVertical = Math.abs(primaryEdge.start.x - primaryEdge.end.x) <
|
|
20
|
+
const isHorizontal = Math.abs(primaryEdge.start.y - primaryEdge.end.y) < EPS;
|
|
21
|
+
const isVertical = Math.abs(primaryEdge.start.x - primaryEdge.end.x) < EPS;
|
|
404
22
|
if (!isHorizontal && !isVertical) return [];
|
|
405
23
|
const axis = isHorizontal ? "x" : "y";
|
|
406
24
|
const perp = isHorizontal ? "y" : "x";
|
|
@@ -413,16 +31,16 @@ function projectToUncoveredSegments(primaryEdge, overlappingEdges) {
|
|
|
413
31
|
const intervals = [];
|
|
414
32
|
for (const e of overlappingEdges) {
|
|
415
33
|
if (e === primaryEdge) continue;
|
|
416
|
-
const eIsHorizontal = Math.abs(e.start.y - e.end.y) <
|
|
417
|
-
const eIsVertical = Math.abs(e.start.x - e.end.x) <
|
|
34
|
+
const eIsHorizontal = Math.abs(e.start.y - e.end.y) < EPS;
|
|
35
|
+
const eIsVertical = Math.abs(e.start.x - e.end.x) < EPS;
|
|
418
36
|
if (axis === "x" && !eIsHorizontal) continue;
|
|
419
37
|
if (axis === "y" && !eIsVertical) continue;
|
|
420
|
-
if (Math.abs(e.start[perp] - lineCoord) >
|
|
38
|
+
if (Math.abs(e.start[perp] - lineCoord) > EPS) continue;
|
|
421
39
|
const eMin = Math.min(e.start[axis], e.end[axis]);
|
|
422
40
|
const eMax = Math.max(e.start[axis], e.end[axis]);
|
|
423
41
|
const s = clamp2(eMin);
|
|
424
42
|
const t = clamp2(eMax);
|
|
425
|
-
if (t - s >
|
|
43
|
+
if (t - s > EPS) intervals.push({ s, e: t });
|
|
426
44
|
}
|
|
427
45
|
if (intervals.length === 0) {
|
|
428
46
|
return [
|
|
@@ -437,19 +55,19 @@ function projectToUncoveredSegments(primaryEdge, overlappingEdges) {
|
|
|
437
55
|
const merged = [];
|
|
438
56
|
for (const it of intervals) {
|
|
439
57
|
const last = merged[merged.length - 1];
|
|
440
|
-
if (!last || it.s > last.e +
|
|
58
|
+
if (!last || it.s > last.e + EPS) merged.push({ ...it });
|
|
441
59
|
else last.e = Math.max(last.e, it.e);
|
|
442
60
|
}
|
|
443
61
|
const uncovered = [];
|
|
444
62
|
let cursor = pMin;
|
|
445
63
|
for (const m of merged) {
|
|
446
|
-
if (m.s > cursor +
|
|
64
|
+
if (m.s > cursor + EPS) uncovered.push({ s: cursor, e: m.s });
|
|
447
65
|
cursor = Math.max(cursor, m.e);
|
|
448
|
-
if (cursor >= pMax -
|
|
66
|
+
if (cursor >= pMax - EPS) break;
|
|
449
67
|
}
|
|
450
|
-
if (pMax > cursor +
|
|
68
|
+
if (pMax > cursor + EPS) uncovered.push({ s: cursor, e: pMax });
|
|
451
69
|
if (uncovered.length === 0) return [];
|
|
452
|
-
return uncovered.filter((u) => u.e - u.s >
|
|
70
|
+
return uncovered.filter((u) => u.e - u.s > EPS).map((u) => {
|
|
453
71
|
const start = axis === "x" ? { x: u.s, y: lineCoord } : { x: lineCoord, y: u.s };
|
|
454
72
|
const end = axis === "x" ? { x: u.e, y: lineCoord } : { x: lineCoord, y: u.e };
|
|
455
73
|
return {
|
|
@@ -538,8 +156,8 @@ var visuallyOffsetLine = (line, options) => {
|
|
|
538
156
|
|
|
539
157
|
// lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
|
|
540
158
|
import "@tscircuit/math-utils";
|
|
541
|
-
var
|
|
542
|
-
var FindSegmentsWithAdjacentEmptySpaceSolver = class extends
|
|
159
|
+
var EPS2 = 1e-4;
|
|
160
|
+
var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver {
|
|
543
161
|
constructor(input) {
|
|
544
162
|
super();
|
|
545
163
|
this.input = input;
|
|
@@ -557,7 +175,7 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver2 {
|
|
|
557
175
|
;
|
|
558
176
|
[start, end] = [end, start];
|
|
559
177
|
}
|
|
560
|
-
if (Math.abs(start.x - end.x) <
|
|
178
|
+
if (Math.abs(start.x - end.x) < EPS2 && start.y > end.y) {
|
|
561
179
|
;
|
|
562
180
|
[start, end] = [end, start];
|
|
563
181
|
}
|
|
@@ -603,10 +221,10 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver2 {
|
|
|
603
221
|
const candidateEdge = this.unprocessedEdges.shift();
|
|
604
222
|
this.lastCandidateEdge = candidateEdge;
|
|
605
223
|
const nearbyEdges = this.edgeSpatialIndex.search(
|
|
606
|
-
candidateEdge.start.x -
|
|
607
|
-
candidateEdge.start.y -
|
|
608
|
-
candidateEdge.end.x +
|
|
609
|
-
candidateEdge.end.y +
|
|
224
|
+
candidateEdge.start.x - EPS2,
|
|
225
|
+
candidateEdge.start.y - EPS2,
|
|
226
|
+
candidateEdge.end.x + EPS2,
|
|
227
|
+
candidateEdge.end.y + EPS2
|
|
610
228
|
);
|
|
611
229
|
const overlappingEdges = nearbyEdges.map((i) => this.allEdges[i]).filter((e) => e.z === candidateEdge.z);
|
|
612
230
|
this.lastOverlappingEdges = overlappingEdges;
|
|
@@ -692,7 +310,7 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver2 {
|
|
|
692
310
|
};
|
|
693
311
|
|
|
694
312
|
// lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
|
|
695
|
-
import { BaseSolver as
|
|
313
|
+
import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
|
|
696
314
|
import RBush from "rbush";
|
|
697
315
|
|
|
698
316
|
// lib/solvers/GapFillSolver/getBoundsFromCorners.ts
|
|
@@ -707,8 +325,8 @@ var getBoundsFromCorners = (corners) => {
|
|
|
707
325
|
|
|
708
326
|
// lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
|
|
709
327
|
import { segmentToBoxMinDistance } from "@tscircuit/math-utils";
|
|
710
|
-
var
|
|
711
|
-
var ExpandEdgesToEmptySpaceSolver = class extends
|
|
328
|
+
var EPS3 = 1e-4;
|
|
329
|
+
var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
|
|
712
330
|
constructor(input) {
|
|
713
331
|
super();
|
|
714
332
|
this.input = input;
|
|
@@ -774,12 +392,12 @@ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver3 {
|
|
|
774
392
|
let collidingNodes = null;
|
|
775
393
|
let searchDistance = 1;
|
|
776
394
|
const searchCorner1 = {
|
|
777
|
-
x: segment.start.x + dx *
|
|
778
|
-
y: segment.start.y + dy *
|
|
395
|
+
x: segment.start.x + dx * EPS3 + normDeltaStartEnd.x * EPS3 * 10,
|
|
396
|
+
y: segment.start.y + dy * EPS3 + normDeltaStartEnd.y * EPS3 * 10
|
|
779
397
|
};
|
|
780
398
|
const searchCorner2 = {
|
|
781
|
-
x: segment.end.x + dx *
|
|
782
|
-
y: segment.end.y + dy *
|
|
399
|
+
x: segment.end.x + dx * EPS3 - normDeltaStartEnd.x * EPS3 * 10,
|
|
400
|
+
y: segment.end.y + dy * EPS3 - normDeltaStartEnd.y * EPS3 * 10
|
|
783
401
|
};
|
|
784
402
|
this.lastSearchCorner1 = searchCorner1;
|
|
785
403
|
this.lastSearchCorner2 = searchCorner2;
|
|
@@ -844,7 +462,7 @@ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver3 {
|
|
|
844
462
|
}
|
|
845
463
|
};
|
|
846
464
|
this.lastExpandedSegment = expandedSegment;
|
|
847
|
-
if (nodeWidth <
|
|
465
|
+
if (nodeWidth < EPS3 || nodeHeight < EPS3) {
|
|
848
466
|
return;
|
|
849
467
|
}
|
|
850
468
|
this.expandedSegments.push(expandedSegment);
|
|
@@ -1049,313 +667,70 @@ var GapFillSolverPipeline = class extends BasePipelineSolver {
|
|
|
1049
667
|
}
|
|
1050
668
|
};
|
|
1051
669
|
|
|
1052
|
-
// lib/solvers/
|
|
1053
|
-
import {
|
|
670
|
+
// lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
|
|
671
|
+
import {
|
|
672
|
+
BasePipelineSolver as BasePipelineSolver2,
|
|
673
|
+
definePipelineStep as definePipelineStep2
|
|
674
|
+
} from "@tscircuit/solver-utils";
|
|
1054
675
|
|
|
1055
|
-
// lib/solvers/RectDiffSeedingSolver/
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
const kb = layerSortKey(b);
|
|
1068
|
-
if (ka !== kb) return ka - kb;
|
|
1069
|
-
return a.localeCompare(b);
|
|
1070
|
-
});
|
|
676
|
+
// lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
|
|
677
|
+
import { BaseSolver as BaseSolver3 } from "@tscircuit/solver-utils";
|
|
678
|
+
|
|
679
|
+
// lib/utils/rectdiff-geometry.ts
|
|
680
|
+
var EPS4 = 1e-9;
|
|
681
|
+
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
682
|
+
var gt = (a, b) => a > b + EPS4;
|
|
683
|
+
var gte = (a, b) => a > b - EPS4;
|
|
684
|
+
var lt = (a, b) => a < b - EPS4;
|
|
685
|
+
var lte = (a, b) => a < b + EPS4;
|
|
686
|
+
function overlaps(a, b) {
|
|
687
|
+
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);
|
|
1071
688
|
}
|
|
1072
|
-
function
|
|
1073
|
-
|
|
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 };
|
|
689
|
+
function containsPoint(r, p) {
|
|
690
|
+
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;
|
|
1116
691
|
}
|
|
1117
|
-
function
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const
|
|
1121
|
-
|
|
692
|
+
function distancePointToRectEdges(p, r) {
|
|
693
|
+
const minX = r.x;
|
|
694
|
+
const maxX = r.x + r.width;
|
|
695
|
+
const minY = r.y;
|
|
696
|
+
const maxY = r.y + r.height;
|
|
697
|
+
if (p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY) {
|
|
698
|
+
return Math.min(p.x - minX, maxX - p.x, p.y - minY, maxY - p.y);
|
|
699
|
+
}
|
|
700
|
+
const dx = p.x < minX ? minX - p.x : p.x > maxX ? p.x - maxX : 0;
|
|
701
|
+
const dy = p.y < minY ? minY - p.y : p.y > maxY ? p.y - maxY : 0;
|
|
702
|
+
if (dx === 0) return dy;
|
|
703
|
+
if (dy === 0) return dx;
|
|
704
|
+
return Math.hypot(dx, dy);
|
|
1122
705
|
}
|
|
1123
|
-
function
|
|
1124
|
-
const
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
return { x: ob.center.x - w / 2, y: ob.center.y - h / 2, width: w, height: h };
|
|
706
|
+
function intersect1D(r1, r2) {
|
|
707
|
+
const lo = Math.max(r1[0], r2[0]);
|
|
708
|
+
const hi = Math.min(r1[1], r2[1]);
|
|
709
|
+
return hi > lo + EPS4 ? [lo, hi] : null;
|
|
1128
710
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
return
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
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 MIN_OUTER_LAYER_MERGE_AREA_MM2 = 1;
|
|
1150
|
-
var cloneNode2 = (node) => ({
|
|
1151
|
-
...node,
|
|
1152
|
-
center: { ...node.center },
|
|
1153
|
-
availableZ: [...node.availableZ]
|
|
1154
|
-
});
|
|
1155
|
-
var cloneNodeWithRect2 = (node, rect, capacityMeshNodeId) => ({
|
|
1156
|
-
...node,
|
|
1157
|
-
capacityMeshNodeId,
|
|
1158
|
-
center: {
|
|
1159
|
-
x: rect.x + rect.width / 2,
|
|
1160
|
-
y: rect.y + rect.height / 2
|
|
1161
|
-
},
|
|
1162
|
-
width: rect.width,
|
|
1163
|
-
height: rect.height,
|
|
1164
|
-
availableZ: [...node.availableZ],
|
|
1165
|
-
layer: `z${node.availableZ.join(",")}`
|
|
1166
|
-
});
|
|
1167
|
-
var isFreeNode2 = (node) => !node._containsObstacle && !node._containsTarget;
|
|
1168
|
-
var isSingletonOuterNode = (node, outerZ) => node.availableZ.length === 1 && node.availableZ[0] === outerZ;
|
|
1169
|
-
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;
|
|
1170
|
-
var subtractRects2 = (target, cutters) => {
|
|
1171
|
-
let remaining = [target];
|
|
1172
|
-
for (const cutter of cutters) {
|
|
1173
|
-
if (remaining.length === 0) return remaining;
|
|
1174
|
-
const nextRemaining = [];
|
|
1175
|
-
for (const piece of remaining) {
|
|
1176
|
-
nextRemaining.push(...subtractRect2D(piece, cutter));
|
|
1177
|
-
}
|
|
1178
|
-
remaining = nextRemaining;
|
|
1179
|
-
}
|
|
1180
|
-
return remaining;
|
|
1181
|
-
};
|
|
1182
|
-
var isFullyCoveredByRects2 = (target, coveringRects) => {
|
|
1183
|
-
return subtractRects2(target, coveringRects).length === 0;
|
|
1184
|
-
};
|
|
1185
|
-
var OuterLayerContainmentMergeSolver = class extends BaseSolver4 {
|
|
1186
|
-
constructor(input) {
|
|
1187
|
-
super();
|
|
1188
|
-
this.input = input;
|
|
1189
|
-
}
|
|
1190
|
-
input;
|
|
1191
|
-
outputNodes = [];
|
|
1192
|
-
promotedNodeIds = /* @__PURE__ */ new Set();
|
|
1193
|
-
residualNodeIds = /* @__PURE__ */ new Set();
|
|
1194
|
-
_setup() {
|
|
1195
|
-
this.outputNodes = this.input.meshNodes.map(cloneNode2);
|
|
1196
|
-
this.promotedNodeIds.clear();
|
|
1197
|
-
this.residualNodeIds.clear();
|
|
1198
|
-
}
|
|
1199
|
-
_step() {
|
|
1200
|
-
this.outputNodes = this.processOuterLayerContainmentMerges();
|
|
1201
|
-
this.solved = true;
|
|
1202
|
-
}
|
|
1203
|
-
processOuterLayerContainmentMerges() {
|
|
1204
|
-
const srj = this.input.simpleRouteJson;
|
|
1205
|
-
const layerCount = Math.max(1, srj.layerCount || 1);
|
|
1206
|
-
if (layerCount < 3) {
|
|
1207
|
-
return this.input.meshNodes.map(cloneNode2);
|
|
1208
|
-
}
|
|
1209
|
-
const topZ = 0;
|
|
1210
|
-
const bottomZ = layerCount - 1;
|
|
1211
|
-
const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0);
|
|
1212
|
-
const originalNodes = this.input.meshNodes.map(cloneNode2);
|
|
1213
|
-
const obstaclesByLayer = this.buildObstaclesByLayer(layerCount);
|
|
1214
|
-
const mutableOuterNodes = originalNodes.filter(
|
|
1215
|
-
(node) => isFreeNode2(node) && (isSingletonOuterNode(node, topZ) || isSingletonOuterNode(node, bottomZ))
|
|
1216
|
-
);
|
|
1217
|
-
const immutableNodes = originalNodes.filter(
|
|
1218
|
-
(node) => !mutableOuterNodes.includes(node)
|
|
1219
|
-
);
|
|
1220
|
-
const freeSupportRectsByOuterLayer = /* @__PURE__ */ new Map();
|
|
1221
|
-
freeSupportRectsByOuterLayer.set(
|
|
1222
|
-
topZ,
|
|
1223
|
-
originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(topZ)).map(nodeToRect2)
|
|
1224
|
-
);
|
|
1225
|
-
freeSupportRectsByOuterLayer.set(
|
|
1226
|
-
bottomZ,
|
|
1227
|
-
originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(bottomZ)).map(nodeToRect2)
|
|
1228
|
-
);
|
|
1229
|
-
const promotedNodes = [];
|
|
1230
|
-
const promotedRects = [];
|
|
1231
|
-
const candidateNodes = mutableOuterNodes.filter(
|
|
1232
|
-
(node) => node.width + EPS >= viaMinSize && node.height + EPS >= viaMinSize && rectArea2(nodeToRect2(node)) > MIN_OUTER_LAYER_MERGE_AREA_MM2 + EPS
|
|
1233
|
-
).sort((a, b) => rectArea2(nodeToRect2(b)) - rectArea2(nodeToRect2(a)));
|
|
1234
|
-
for (const candidate of candidateNodes) {
|
|
1235
|
-
const candidateZ = candidate.availableZ[0];
|
|
1236
|
-
const oppositeZ = candidateZ === topZ ? bottomZ : topZ;
|
|
1237
|
-
const candidateRect = nodeToRect2(candidate);
|
|
1238
|
-
const oppositeSupportRects = freeSupportRectsByOuterLayer.get(oppositeZ) ?? [];
|
|
1239
|
-
if (!this.isTransitCompatibleAcrossIntermediateLayers({
|
|
1240
|
-
rect: candidateRect,
|
|
1241
|
-
fromZ: candidateZ,
|
|
1242
|
-
toZ: oppositeZ,
|
|
1243
|
-
obstaclesByLayer
|
|
1244
|
-
})) {
|
|
1245
|
-
continue;
|
|
1246
|
-
}
|
|
1247
|
-
if (!isFullyCoveredByRects2(candidateRect, oppositeSupportRects)) {
|
|
1248
|
-
continue;
|
|
1249
|
-
}
|
|
1250
|
-
promotedNodes.push({
|
|
1251
|
-
...candidate,
|
|
1252
|
-
availableZ: [topZ, bottomZ],
|
|
1253
|
-
layer: `z${topZ},${bottomZ}`
|
|
1254
|
-
});
|
|
1255
|
-
promotedRects.push(candidateRect);
|
|
1256
|
-
this.promotedNodeIds.add(candidate.capacityMeshNodeId);
|
|
1257
|
-
}
|
|
1258
|
-
let nextResidualId = 0;
|
|
1259
|
-
const residualNodes = [];
|
|
1260
|
-
for (const node of mutableOuterNodes) {
|
|
1261
|
-
if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
|
|
1262
|
-
continue;
|
|
1263
|
-
}
|
|
1264
|
-
const nodeRect = nodeToRect2(node);
|
|
1265
|
-
const remainingPieces = subtractRects2(nodeRect, promotedRects);
|
|
1266
|
-
if (remainingPieces.length === 1 && sameRect2(remainingPieces[0], nodeRect)) {
|
|
1267
|
-
residualNodes.push(node);
|
|
1268
|
-
continue;
|
|
1269
|
-
}
|
|
1270
|
-
for (const piece of remainingPieces) {
|
|
1271
|
-
const residualNode = cloneNodeWithRect2(
|
|
1272
|
-
node,
|
|
1273
|
-
piece,
|
|
1274
|
-
`${node.capacityMeshNodeId}-outer-merge-${nextResidualId++}`
|
|
1275
|
-
);
|
|
1276
|
-
residualNodes.push(residualNode);
|
|
1277
|
-
this.residualNodeIds.add(residualNode.capacityMeshNodeId);
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
return [...immutableNodes, ...promotedNodes, ...residualNodes];
|
|
1281
|
-
}
|
|
1282
|
-
buildObstaclesByLayer(layerCount) {
|
|
1283
|
-
const out = Array.from(
|
|
1284
|
-
{ length: layerCount },
|
|
1285
|
-
() => []
|
|
1286
|
-
);
|
|
1287
|
-
for (const obstacle of this.input.simpleRouteJson.obstacles ?? []) {
|
|
1288
|
-
const baseRect = obstacleToXYRect(obstacle);
|
|
1289
|
-
if (!baseRect) continue;
|
|
1290
|
-
const rect = padRect(baseRect, this.input.obstacleClearance ?? 0);
|
|
1291
|
-
const zLayers = obstacleZs(obstacle, this.input.zIndexByName);
|
|
1292
|
-
for (const z of zLayers) {
|
|
1293
|
-
if (z < 0 || z >= layerCount) continue;
|
|
1294
|
-
out[z].push({ obstacle, rect });
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
return out;
|
|
711
|
+
function subtractRect2D(A, B) {
|
|
712
|
+
if (!overlaps(A, B)) return [A];
|
|
713
|
+
const Xi = intersect1D([A.x, A.x + A.width], [B.x, B.x + B.width]);
|
|
714
|
+
const Yi = intersect1D([A.y, A.y + A.height], [B.y, B.y + B.height]);
|
|
715
|
+
if (!Xi || !Yi) return [A];
|
|
716
|
+
const [X0, X1] = Xi;
|
|
717
|
+
const [Y0, Y1] = Yi;
|
|
718
|
+
const out = [];
|
|
719
|
+
if (X0 > A.x + EPS4) {
|
|
720
|
+
out.push({ x: A.x, y: A.y, width: X0 - A.x, height: A.height });
|
|
1298
721
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
const lo = Math.min(fromZ, toZ);
|
|
1302
|
-
const hi = Math.max(fromZ, toZ);
|
|
1303
|
-
if (hi - lo < 2) return false;
|
|
1304
|
-
for (let z = lo + 1; z < hi; z++) {
|
|
1305
|
-
const overlapping = (obstaclesByLayer[z] ?? []).filter(
|
|
1306
|
-
(entry) => overlaps(entry.rect, rect)
|
|
1307
|
-
);
|
|
1308
|
-
if (overlapping.length === 0) return false;
|
|
1309
|
-
const nonCopperOverlap = overlapping.some(
|
|
1310
|
-
(entry) => !entry.obstacle.isCopperPour
|
|
1311
|
-
);
|
|
1312
|
-
if (nonCopperOverlap) return false;
|
|
1313
|
-
const copperRects = overlapping.filter((entry) => entry.obstacle.isCopperPour).map((entry) => entry.rect);
|
|
1314
|
-
if (!isFullyCoveredByRects2(rect, copperRects)) {
|
|
1315
|
-
return false;
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
return true;
|
|
722
|
+
if (A.x + A.width > X1 + EPS4) {
|
|
723
|
+
out.push({ x: X1, y: A.y, width: A.x + A.width - X1, height: A.height });
|
|
1319
724
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
725
|
+
const midW = Math.max(0, X1 - X0);
|
|
726
|
+
if (midW > EPS4 && Y0 > A.y + EPS4) {
|
|
727
|
+
out.push({ x: X0, y: A.y, width: midW, height: Y0 - A.y });
|
|
1322
728
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
title: "OuterLayerContainmentMergeSolver",
|
|
1326
|
-
coordinateSystem: "cartesian",
|
|
1327
|
-
rects: this.outputNodes.map((node) => {
|
|
1328
|
-
const colors = getColorForZLayer(node.availableZ);
|
|
1329
|
-
const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId);
|
|
1330
|
-
const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId);
|
|
1331
|
-
return {
|
|
1332
|
-
center: node.center,
|
|
1333
|
-
width: node.width,
|
|
1334
|
-
height: node.height,
|
|
1335
|
-
stroke: isPromoted ? "rgba(22, 163, 74, 0.95)" : isResidual ? "rgba(37, 99, 235, 0.95)" : colors.stroke,
|
|
1336
|
-
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,
|
|
1337
|
-
layer: `z${node.availableZ.join(",")}`,
|
|
1338
|
-
label: [
|
|
1339
|
-
`node ${node.capacityMeshNodeId}`,
|
|
1340
|
-
`z:${node.availableZ.join(",")}`
|
|
1341
|
-
].join("\n")
|
|
1342
|
-
};
|
|
1343
|
-
}),
|
|
1344
|
-
points: [],
|
|
1345
|
-
lines: [],
|
|
1346
|
-
texts: []
|
|
1347
|
-
};
|
|
729
|
+
if (midW > EPS4 && A.y + A.height > Y1 + EPS4) {
|
|
730
|
+
out.push({ x: X0, y: Y1, width: midW, height: A.y + A.height - Y1 });
|
|
1348
731
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
// lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
|
|
1352
|
-
import {
|
|
1353
|
-
BasePipelineSolver as BasePipelineSolver2,
|
|
1354
|
-
definePipelineStep as definePipelineStep2
|
|
1355
|
-
} from "@tscircuit/solver-utils";
|
|
1356
|
-
|
|
1357
|
-
// lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
|
|
1358
|
-
import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
|
|
732
|
+
return out.filter((r) => r.width > EPS4 && r.height > EPS4);
|
|
733
|
+
}
|
|
1359
734
|
|
|
1360
735
|
// lib/solvers/RectDiffSeedingSolver/isPointInPolygon.ts
|
|
1361
736
|
function isPointInPolygon(p, polygon) {
|
|
@@ -1418,7 +793,7 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1418
793
|
}
|
|
1419
794
|
const finalRects = [];
|
|
1420
795
|
rawRects.sort((a, b) => {
|
|
1421
|
-
if (Math.abs(a.y - b.y) >
|
|
796
|
+
if (Math.abs(a.y - b.y) > EPS4) return a.y - b.y;
|
|
1422
797
|
return a.x - b.x;
|
|
1423
798
|
});
|
|
1424
799
|
let current = null;
|
|
@@ -1427,9 +802,9 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1427
802
|
current = r;
|
|
1428
803
|
continue;
|
|
1429
804
|
}
|
|
1430
|
-
const sameY = Math.abs(current.y - r.y) <
|
|
1431
|
-
const sameHeight = Math.abs(current.height - r.height) <
|
|
1432
|
-
const touchesX = Math.abs(current.x + current.width - r.x) <
|
|
805
|
+
const sameY = Math.abs(current.y - r.y) < EPS4;
|
|
806
|
+
const sameHeight = Math.abs(current.height - r.height) < EPS4;
|
|
807
|
+
const touchesX = Math.abs(current.x + current.width - r.x) < EPS4;
|
|
1433
808
|
if (sameY && sameHeight && touchesX) {
|
|
1434
809
|
current.width += r.width;
|
|
1435
810
|
} else {
|
|
@@ -1439,7 +814,7 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1439
814
|
}
|
|
1440
815
|
if (current) finalRects.push(current);
|
|
1441
816
|
finalRects.sort((a, b) => {
|
|
1442
|
-
if (Math.abs(a.x - b.x) >
|
|
817
|
+
if (Math.abs(a.x - b.x) > EPS4) return a.x - b.x;
|
|
1443
818
|
return a.y - b.y;
|
|
1444
819
|
});
|
|
1445
820
|
const mergedVertical = [];
|
|
@@ -1449,9 +824,9 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1449
824
|
current = r;
|
|
1450
825
|
continue;
|
|
1451
826
|
}
|
|
1452
|
-
const sameX = Math.abs(current.x - r.x) <
|
|
1453
|
-
const sameWidth = Math.abs(current.width - r.width) <
|
|
1454
|
-
const touchesY = Math.abs(current.y + current.height - r.y) <
|
|
827
|
+
const sameX = Math.abs(current.x - r.x) < EPS4;
|
|
828
|
+
const sameWidth = Math.abs(current.width - r.width) < EPS4;
|
|
829
|
+
const touchesY = Math.abs(current.y + current.height - r.y) < EPS4;
|
|
1455
830
|
if (sameX && sameWidth && touchesY) {
|
|
1456
831
|
current.height += r.height;
|
|
1457
832
|
} else {
|
|
@@ -1463,6 +838,81 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1463
838
|
return mergedVertical;
|
|
1464
839
|
}
|
|
1465
840
|
|
|
841
|
+
// lib/solvers/RectDiffSeedingSolver/layers.ts
|
|
842
|
+
function layerSortKey(n) {
|
|
843
|
+
const L = n.toLowerCase();
|
|
844
|
+
if (L === "top") return -1e6;
|
|
845
|
+
if (L === "bottom") return 1e6;
|
|
846
|
+
const m = /^inner(\d+)$/i.exec(L);
|
|
847
|
+
if (m) return parseInt(m[1], 10) || 0;
|
|
848
|
+
return 100 + L.charCodeAt(0);
|
|
849
|
+
}
|
|
850
|
+
function canonicalizeLayerOrder(names) {
|
|
851
|
+
return Array.from(new Set(names)).sort((a, b) => {
|
|
852
|
+
const ka = layerSortKey(a);
|
|
853
|
+
const kb = layerSortKey(b);
|
|
854
|
+
if (ka !== kb) return ka - kb;
|
|
855
|
+
return a.localeCompare(b);
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
function buildZIndexMap(params) {
|
|
859
|
+
const names = canonicalizeLayerOrder(
|
|
860
|
+
(params.obstacles ?? []).flatMap((o) => o.layers ?? [])
|
|
861
|
+
);
|
|
862
|
+
const declaredLayerCount = Math.max(1, params.layerCount || names.length || 1);
|
|
863
|
+
const fallback = Array.from(
|
|
864
|
+
{ length: declaredLayerCount },
|
|
865
|
+
(_, i) => i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`
|
|
866
|
+
);
|
|
867
|
+
const ordered = [];
|
|
868
|
+
const seen = /* @__PURE__ */ new Set();
|
|
869
|
+
const push = (n) => {
|
|
870
|
+
const key = n.toLowerCase();
|
|
871
|
+
if (seen.has(key)) return;
|
|
872
|
+
seen.add(key);
|
|
873
|
+
ordered.push(n);
|
|
874
|
+
};
|
|
875
|
+
fallback.forEach(push);
|
|
876
|
+
names.forEach(push);
|
|
877
|
+
const layerNames = ordered.slice(0, declaredLayerCount);
|
|
878
|
+
const clampIndex = (nameLower) => {
|
|
879
|
+
if (layerNames.length <= 1) return 0;
|
|
880
|
+
if (nameLower === "top") return 0;
|
|
881
|
+
if (nameLower === "bottom") return layerNames.length - 1;
|
|
882
|
+
const m = /^inner(\d+)$/i.exec(nameLower);
|
|
883
|
+
if (m) {
|
|
884
|
+
if (layerNames.length <= 2) return layerNames.length - 1;
|
|
885
|
+
const parsed = parseInt(m[1], 10);
|
|
886
|
+
const maxInner = layerNames.length - 2;
|
|
887
|
+
const clampedInner = Math.min(
|
|
888
|
+
maxInner,
|
|
889
|
+
Math.max(1, Number.isFinite(parsed) ? parsed : 1)
|
|
890
|
+
);
|
|
891
|
+
return clampedInner;
|
|
892
|
+
}
|
|
893
|
+
return 0;
|
|
894
|
+
};
|
|
895
|
+
const map = /* @__PURE__ */ new Map();
|
|
896
|
+
layerNames.forEach((n, i) => map.set(n.toLowerCase(), i));
|
|
897
|
+
ordered.slice(layerNames.length).forEach((n) => {
|
|
898
|
+
const key = n.toLowerCase();
|
|
899
|
+
map.set(key, clampIndex(key));
|
|
900
|
+
});
|
|
901
|
+
return { layerNames, zIndexByName: map };
|
|
902
|
+
}
|
|
903
|
+
function obstacleZs(ob, zIndexByName) {
|
|
904
|
+
if (ob.zLayers?.length)
|
|
905
|
+
return Array.from(new Set(ob.zLayers)).sort((a, b) => a - b);
|
|
906
|
+
const fromNames = (ob.layers ?? []).map((n) => zIndexByName.get(n.toLowerCase())).filter((v) => typeof v === "number");
|
|
907
|
+
return Array.from(new Set(fromNames)).sort((a, b) => a - b);
|
|
908
|
+
}
|
|
909
|
+
function obstacleToXYRect(ob) {
|
|
910
|
+
const w = ob.width;
|
|
911
|
+
const h = ob.height;
|
|
912
|
+
if (typeof w !== "number" || typeof h !== "number") return null;
|
|
913
|
+
return { x: ob.center.x - w / 2, y: ob.center.y - h / 2, width: w, height: h };
|
|
914
|
+
}
|
|
915
|
+
|
|
1466
916
|
// lib/utils/isSelfRect.ts
|
|
1467
917
|
var EPS5 = 1e-9;
|
|
1468
918
|
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;
|
|
@@ -1517,11 +967,11 @@ function maxExpandRight(params) {
|
|
|
1517
967
|
const { r, bounds, blockers, maxAspect } = params;
|
|
1518
968
|
let maxWidth = bounds.x + bounds.width - r.x;
|
|
1519
969
|
for (const b of blockers) {
|
|
1520
|
-
const verticallyOverlaps = r.y + r.height > b.y +
|
|
970
|
+
const verticallyOverlaps = r.y + r.height > b.y + EPS4 && b.y + b.height > r.y + EPS4;
|
|
1521
971
|
if (verticallyOverlaps) {
|
|
1522
972
|
if (gte(b.x, r.x + r.width)) {
|
|
1523
973
|
maxWidth = Math.min(maxWidth, b.x - r.x);
|
|
1524
|
-
} else if (b.x + b.width > r.x + r.width -
|
|
974
|
+
} else if (b.x + b.width > r.x + r.width - EPS4 && b.x < r.x + r.width + EPS4) {
|
|
1525
975
|
return 0;
|
|
1526
976
|
}
|
|
1527
977
|
}
|
|
@@ -1538,11 +988,11 @@ function maxExpandDown(params) {
|
|
|
1538
988
|
const { r, bounds, blockers, maxAspect } = params;
|
|
1539
989
|
let maxHeight = bounds.y + bounds.height - r.y;
|
|
1540
990
|
for (const b of blockers) {
|
|
1541
|
-
const horizOverlaps = r.x + r.width > b.x +
|
|
991
|
+
const horizOverlaps = r.x + r.width > b.x + EPS4 && b.x + b.width > r.x + EPS4;
|
|
1542
992
|
if (horizOverlaps) {
|
|
1543
993
|
if (gte(b.y, r.y + r.height)) {
|
|
1544
994
|
maxHeight = Math.min(maxHeight, b.y - r.y);
|
|
1545
|
-
} else if (b.y + b.height > r.y + r.height -
|
|
995
|
+
} else if (b.y + b.height > r.y + r.height - EPS4 && b.y < r.y + r.height + EPS4) {
|
|
1546
996
|
return 0;
|
|
1547
997
|
}
|
|
1548
998
|
}
|
|
@@ -1559,11 +1009,11 @@ function maxExpandLeft(params) {
|
|
|
1559
1009
|
const { r, bounds, blockers, maxAspect } = params;
|
|
1560
1010
|
let minX = bounds.x;
|
|
1561
1011
|
for (const b of blockers) {
|
|
1562
|
-
const verticallyOverlaps = r.y + r.height > b.y +
|
|
1012
|
+
const verticallyOverlaps = r.y + r.height > b.y + EPS4 && b.y + b.height > r.y + EPS4;
|
|
1563
1013
|
if (verticallyOverlaps) {
|
|
1564
1014
|
if (lte(b.x + b.width, r.x)) {
|
|
1565
1015
|
minX = Math.max(minX, b.x + b.width);
|
|
1566
|
-
} else if (b.x < r.x +
|
|
1016
|
+
} else if (b.x < r.x + EPS4 && b.x + b.width > r.x - EPS4) {
|
|
1567
1017
|
return 0;
|
|
1568
1018
|
}
|
|
1569
1019
|
}
|
|
@@ -1580,11 +1030,11 @@ function maxExpandUp(params) {
|
|
|
1580
1030
|
const { r, bounds, blockers, maxAspect } = params;
|
|
1581
1031
|
let minY = bounds.y;
|
|
1582
1032
|
for (const b of blockers) {
|
|
1583
|
-
const horizOverlaps = r.x + r.width > b.x +
|
|
1033
|
+
const horizOverlaps = r.x + r.width > b.x + EPS4 && b.x + b.width > r.x + EPS4;
|
|
1584
1034
|
if (horizOverlaps) {
|
|
1585
1035
|
if (lte(b.y + b.height, r.y)) {
|
|
1586
1036
|
minY = Math.max(minY, b.y + b.height);
|
|
1587
|
-
} else if (b.y < r.y +
|
|
1037
|
+
} else if (b.y < r.y + EPS4 && b.y + b.height > r.y - EPS4) {
|
|
1588
1038
|
return 0;
|
|
1589
1039
|
}
|
|
1590
1040
|
}
|
|
@@ -1609,7 +1059,7 @@ var toQueryRect = (params) => {
|
|
|
1609
1059
|
const minY = Math.max(bounds.y, rect.y);
|
|
1610
1060
|
const maxX = Math.min(bounds.x + bounds.width, rect.x + rect.width);
|
|
1611
1061
|
const maxY = Math.min(bounds.y + bounds.height, rect.y + rect.height);
|
|
1612
|
-
if (maxX <= minX +
|
|
1062
|
+
if (maxX <= minX + EPS4 || maxY <= minY + EPS4) return null;
|
|
1613
1063
|
return { minX, minY, maxX, maxY };
|
|
1614
1064
|
};
|
|
1615
1065
|
function expandRectFromSeed(params) {
|
|
@@ -1715,7 +1165,7 @@ function expandRectFromSeed(params) {
|
|
|
1715
1165
|
improved = true;
|
|
1716
1166
|
}
|
|
1717
1167
|
}
|
|
1718
|
-
if (r.width +
|
|
1168
|
+
if (r.width + EPS4 >= minReq.width && r.height + EPS4 >= minReq.height) {
|
|
1719
1169
|
const area = r.width * r.height;
|
|
1720
1170
|
if (area > bestArea) {
|
|
1721
1171
|
best = quantizeRect(r);
|
|
@@ -1809,7 +1259,7 @@ function computeCandidates3D(params) {
|
|
|
1809
1259
|
]);
|
|
1810
1260
|
for (let x = bounds.x; x < bounds.x + bounds.width; x += gridSize) {
|
|
1811
1261
|
for (let y = bounds.y; y < bounds.y + bounds.height; y += gridSize) {
|
|
1812
|
-
if (Math.abs(x - bounds.x) <
|
|
1262
|
+
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) {
|
|
1813
1263
|
continue;
|
|
1814
1264
|
}
|
|
1815
1265
|
if (isFullyOccupiedAtPoint({
|
|
@@ -1881,7 +1331,7 @@ function computeUncoveredSegments(params) {
|
|
|
1881
1331
|
const s = quantize3(i.start);
|
|
1882
1332
|
const e = quantize3(i.end);
|
|
1883
1333
|
return { start: Math.min(s, e), end: Math.max(s, e) };
|
|
1884
|
-
}).filter((i) => i.end > i.start +
|
|
1334
|
+
}).filter((i) => i.end > i.start + EPS4);
|
|
1885
1335
|
if (normalizedIntervals.length === 0) {
|
|
1886
1336
|
const center = (lineStartQ + lineEndQ) / 2;
|
|
1887
1337
|
return [{ start: lineStartQ, end: lineEndQ, center }];
|
|
@@ -1891,7 +1341,7 @@ function computeUncoveredSegments(params) {
|
|
|
1891
1341
|
let current = { ...sorted[0] };
|
|
1892
1342
|
for (let i = 1; i < sorted.length; i++) {
|
|
1893
1343
|
const interval = sorted[i];
|
|
1894
|
-
if (interval.start <= current.end +
|
|
1344
|
+
if (interval.start <= current.end + EPS4) {
|
|
1895
1345
|
current.end = Math.max(current.end, interval.end);
|
|
1896
1346
|
} else {
|
|
1897
1347
|
merged.push(current);
|
|
@@ -1900,7 +1350,7 @@ function computeUncoveredSegments(params) {
|
|
|
1900
1350
|
}
|
|
1901
1351
|
merged.push(current);
|
|
1902
1352
|
const uncovered = [];
|
|
1903
|
-
if (merged[0].start > lineStartQ +
|
|
1353
|
+
if (merged[0].start > lineStartQ + EPS4) {
|
|
1904
1354
|
const start = lineStartQ;
|
|
1905
1355
|
const end = merged[0].start;
|
|
1906
1356
|
if (end - start >= minSegmentLength) {
|
|
@@ -1914,7 +1364,7 @@ function computeUncoveredSegments(params) {
|
|
|
1914
1364
|
uncovered.push({ start, end, center: (start + end) / 2 });
|
|
1915
1365
|
}
|
|
1916
1366
|
}
|
|
1917
|
-
if (merged[merged.length - 1].end < lineEndQ -
|
|
1367
|
+
if (merged[merged.length - 1].end < lineEndQ - EPS4) {
|
|
1918
1368
|
const start = merged[merged.length - 1].end;
|
|
1919
1369
|
const end = lineEndQ;
|
|
1920
1370
|
if (end - start >= minSegmentLength) {
|
|
@@ -1933,7 +1383,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
1933
1383
|
hardPlacedByLayer
|
|
1934
1384
|
} = params;
|
|
1935
1385
|
const out = [];
|
|
1936
|
-
const \u03B4 = Math.max(minSize * 0.15,
|
|
1386
|
+
const \u03B4 = Math.max(minSize * 0.15, EPS4 * 3);
|
|
1937
1387
|
const dedup = /* @__PURE__ */ new Set();
|
|
1938
1388
|
const hardRectsByLayer = Array.from(
|
|
1939
1389
|
{ length: layerCount },
|
|
@@ -1957,7 +1407,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
1957
1407
|
const { z } = p;
|
|
1958
1408
|
const x = qx;
|
|
1959
1409
|
const y = qy;
|
|
1960
|
-
if (x < bounds.x +
|
|
1410
|
+
if (x < bounds.x + EPS4 || y < bounds.y + EPS4 || x > bounds.x + bounds.width - EPS4 || y > bounds.y + bounds.height - EPS4)
|
|
1961
1411
|
return;
|
|
1962
1412
|
if (fullyOcc({ x, y })) return;
|
|
1963
1413
|
const hard = hardRectsByLayer[z] ?? [];
|
|
@@ -2094,7 +1544,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
2094
1544
|
}
|
|
2095
1545
|
for (const b of blockers) {
|
|
2096
1546
|
const obLeftX = b.x - \u03B4;
|
|
2097
|
-
if (obLeftX > bounds.x +
|
|
1547
|
+
if (obLeftX > bounds.x + EPS4 && obLeftX < bounds.x + bounds.width - EPS4) {
|
|
2098
1548
|
const obLeftCovering = blockers.filter(
|
|
2099
1549
|
(bl) => bl !== b && bl.x <= obLeftX && bl.x + bl.width >= obLeftX
|
|
2100
1550
|
).map((bl) => ({
|
|
@@ -2112,7 +1562,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
2112
1562
|
}
|
|
2113
1563
|
}
|
|
2114
1564
|
const obRightX = b.x + b.width + \u03B4;
|
|
2115
|
-
if (obRightX > bounds.x +
|
|
1565
|
+
if (obRightX > bounds.x + EPS4 && obRightX < bounds.x + bounds.width - EPS4) {
|
|
2116
1566
|
const obRightCovering = blockers.filter(
|
|
2117
1567
|
(bl) => bl !== b && bl.x <= obRightX && bl.x + bl.width >= obRightX
|
|
2118
1568
|
).map((bl) => ({
|
|
@@ -2130,7 +1580,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
2130
1580
|
}
|
|
2131
1581
|
}
|
|
2132
1582
|
const obTopY = b.y - \u03B4;
|
|
2133
|
-
if (obTopY > bounds.y +
|
|
1583
|
+
if (obTopY > bounds.y + EPS4 && obTopY < bounds.y + bounds.height - EPS4) {
|
|
2134
1584
|
const obTopCovering = blockers.filter(
|
|
2135
1585
|
(bl) => bl !== b && bl.y <= obTopY && bl.y + bl.height >= obTopY
|
|
2136
1586
|
).map((bl) => ({
|
|
@@ -2148,7 +1598,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
2148
1598
|
}
|
|
2149
1599
|
}
|
|
2150
1600
|
const obBottomY = b.y + b.height + \u03B4;
|
|
2151
|
-
if (obBottomY > bounds.y +
|
|
1601
|
+
if (obBottomY > bounds.y + EPS4 && obBottomY < bounds.y + bounds.height - EPS4) {
|
|
2152
1602
|
const obBottomCovering = blockers.filter(
|
|
2153
1603
|
(bl) => bl !== b && bl.y <= obBottomY && bl.y + bl.height >= obBottomY
|
|
2154
1604
|
).map((bl) => ({
|
|
@@ -2223,12 +1673,12 @@ function resizeSoftOverlaps(params, newIndex) {
|
|
|
2223
1673
|
params.options.minMulti.height
|
|
2224
1674
|
);
|
|
2225
1675
|
for (const p of parts) {
|
|
2226
|
-
if (p.width +
|
|
1676
|
+
if (p.width + EPS4 >= minW && p.height + EPS4 >= minH) {
|
|
2227
1677
|
toAdd.push({ rect: p, zLayers: sharedZ.slice() });
|
|
2228
1678
|
}
|
|
2229
1679
|
}
|
|
2230
1680
|
}
|
|
2231
|
-
const
|
|
1681
|
+
const sameRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
|
|
2232
1682
|
removeIdx.sort((a, b) => b - a).forEach((idx) => {
|
|
2233
1683
|
const rem = params.placed.splice(idx, 1)[0];
|
|
2234
1684
|
if (params.placedIndexByLayer) {
|
|
@@ -2237,7 +1687,7 @@ function resizeSoftOverlaps(params, newIndex) {
|
|
|
2237
1687
|
if (tree)
|
|
2238
1688
|
tree.remove(
|
|
2239
1689
|
rectToTree(rem.rect, { zLayers: rem.zLayers }),
|
|
2240
|
-
|
|
1690
|
+
sameRect
|
|
2241
1691
|
);
|
|
2242
1692
|
}
|
|
2243
1693
|
}
|
|
@@ -2255,9 +1705,23 @@ function resizeSoftOverlaps(params, newIndex) {
|
|
|
2255
1705
|
}
|
|
2256
1706
|
}
|
|
2257
1707
|
|
|
1708
|
+
// lib/utils/getColorForZLayer.ts
|
|
1709
|
+
var getColorForZLayer = (zLayers) => {
|
|
1710
|
+
const minZ = Math.min(...zLayers);
|
|
1711
|
+
const colors = [
|
|
1712
|
+
{ fill: "#dbeafe", stroke: "#3b82f6" },
|
|
1713
|
+
{ fill: "#fef3c7", stroke: "#f59e0b" },
|
|
1714
|
+
{ fill: "#d1fae5", stroke: "#10b981" },
|
|
1715
|
+
{ fill: "#e9d5ff", stroke: "#a855f7" },
|
|
1716
|
+
{ fill: "#fed7aa", stroke: "#f97316" },
|
|
1717
|
+
{ fill: "#fecaca", stroke: "#ef4444" }
|
|
1718
|
+
];
|
|
1719
|
+
return colors[minZ % colors.length];
|
|
1720
|
+
};
|
|
1721
|
+
|
|
2258
1722
|
// lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
|
|
2259
1723
|
import RBush3 from "rbush";
|
|
2260
|
-
var RectDiffSeedingSolver = class extends
|
|
1724
|
+
var RectDiffSeedingSolver = class extends BaseSolver3 {
|
|
2261
1725
|
constructor(input) {
|
|
2262
1726
|
super();
|
|
2263
1727
|
this.input = input;
|
|
@@ -2616,7 +2080,7 @@ z:${placement.zLayers.join(",")}`
|
|
|
2616
2080
|
};
|
|
2617
2081
|
|
|
2618
2082
|
// lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
|
|
2619
|
-
import { BaseSolver as
|
|
2083
|
+
import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
|
|
2620
2084
|
|
|
2621
2085
|
// lib/utils/finalizeRects.ts
|
|
2622
2086
|
function finalizeRects(params) {
|
|
@@ -2688,7 +2152,7 @@ import RBush4 from "rbush";
|
|
|
2688
2152
|
var sameTreeRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
|
|
2689
2153
|
|
|
2690
2154
|
// lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
|
|
2691
|
-
var RectDiffExpansionSolver = class extends
|
|
2155
|
+
var RectDiffExpansionSolver = class extends BaseSolver4 {
|
|
2692
2156
|
constructor(input) {
|
|
2693
2157
|
super();
|
|
2694
2158
|
this.input = input;
|
|
@@ -2834,6 +2298,19 @@ import "rbush";
|
|
|
2834
2298
|
|
|
2835
2299
|
// lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
|
|
2836
2300
|
import RBush5 from "rbush";
|
|
2301
|
+
|
|
2302
|
+
// lib/utils/padRect.ts
|
|
2303
|
+
var padRect = (rect, clearance) => {
|
|
2304
|
+
if (!clearance || clearance <= 0) return rect;
|
|
2305
|
+
return {
|
|
2306
|
+
x: rect.x - clearance,
|
|
2307
|
+
y: rect.y - clearance,
|
|
2308
|
+
width: rect.width + 2 * clearance,
|
|
2309
|
+
height: rect.height + 2 * clearance
|
|
2310
|
+
};
|
|
2311
|
+
};
|
|
2312
|
+
|
|
2313
|
+
// lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
|
|
2837
2314
|
var buildObstacleIndexesByLayer = (params) => {
|
|
2838
2315
|
const { srj, boardVoidRects, obstacleClearance } = params;
|
|
2839
2316
|
const { layerNames, zIndexByName } = buildZIndexMap({
|
|
@@ -3177,8 +2654,6 @@ import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
|
|
|
3177
2654
|
var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
3178
2655
|
rectDiffGridSolverPipeline;
|
|
3179
2656
|
gapFillSolver;
|
|
3180
|
-
outerLayerContainmentMergeSolver;
|
|
3181
|
-
adjacentLayerContainmentMergeSolver;
|
|
3182
2657
|
boardVoidRects;
|
|
3183
2658
|
zIndexByName;
|
|
3184
2659
|
layerNames;
|
|
@@ -3214,28 +2689,6 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
|
3214
2689
|
}
|
|
3215
2690
|
}
|
|
3216
2691
|
]
|
|
3217
|
-
),
|
|
3218
|
-
definePipelineStep3(
|
|
3219
|
-
"outerLayerContainmentMergeSolver",
|
|
3220
|
-
OuterLayerContainmentMergeSolver,
|
|
3221
|
-
(rectDiffPipeline) => [
|
|
3222
|
-
{
|
|
3223
|
-
meshNodes: rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ?? rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
|
|
3224
|
-
simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
|
|
3225
|
-
zIndexByName: rectDiffPipeline.zIndexByName ?? /* @__PURE__ */ new Map(),
|
|
3226
|
-
obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance
|
|
3227
|
-
}
|
|
3228
|
-
]
|
|
3229
|
-
),
|
|
3230
|
-
definePipelineStep3(
|
|
3231
|
-
"adjacentLayerContainmentMergeSolver",
|
|
3232
|
-
AdjacentLayerContainmentMergeSolver,
|
|
3233
|
-
(rectDiffPipeline) => [
|
|
3234
|
-
{
|
|
3235
|
-
meshNodes: rectDiffPipeline.outerLayerContainmentMergeSolver?.getOutput().outputNodes ?? rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ?? rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
|
|
3236
|
-
simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson
|
|
3237
|
-
}
|
|
3238
|
-
]
|
|
3239
2692
|
)
|
|
3240
2693
|
];
|
|
3241
2694
|
_setup() {
|
|
@@ -3261,14 +2714,6 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
|
3261
2714
|
return [this.inputProblem];
|
|
3262
2715
|
}
|
|
3263
2716
|
getOutput() {
|
|
3264
|
-
const adjacentLayerMergeOutput = this.adjacentLayerContainmentMergeSolver?.getOutput();
|
|
3265
|
-
if (adjacentLayerMergeOutput) {
|
|
3266
|
-
return { meshNodes: adjacentLayerMergeOutput.outputNodes };
|
|
3267
|
-
}
|
|
3268
|
-
const outerLayerMergeOutput = this.outerLayerContainmentMergeSolver?.getOutput();
|
|
3269
|
-
if (outerLayerMergeOutput) {
|
|
3270
|
-
return { meshNodes: outerLayerMergeOutput.outputNodes };
|
|
3271
|
-
}
|
|
3272
2717
|
const gapFillOutput = this.gapFillSolver?.getOutput();
|
|
3273
2718
|
if (gapFillOutput) {
|
|
3274
2719
|
return { meshNodes: gapFillOutput.outputNodes };
|