@tscircuit/rectdiff 0.0.27 → 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 -774
- 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 -311
- 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/solver/bugreport49-634662/__snapshots__/bugreport49-634662.snap.svg +0 -44
- package/tests/solver/bugreport49-634662/bugreport49-634662.test.ts +0 -134
- 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,312 +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 cloneNode2 = (node) => ({
|
|
1150
|
-
...node,
|
|
1151
|
-
center: { ...node.center },
|
|
1152
|
-
availableZ: [...node.availableZ]
|
|
1153
|
-
});
|
|
1154
|
-
var cloneNodeWithRect2 = (node, rect, capacityMeshNodeId) => ({
|
|
1155
|
-
...node,
|
|
1156
|
-
capacityMeshNodeId,
|
|
1157
|
-
center: {
|
|
1158
|
-
x: rect.x + rect.width / 2,
|
|
1159
|
-
y: rect.y + rect.height / 2
|
|
1160
|
-
},
|
|
1161
|
-
width: rect.width,
|
|
1162
|
-
height: rect.height,
|
|
1163
|
-
availableZ: [...node.availableZ],
|
|
1164
|
-
layer: `z${node.availableZ.join(",")}`
|
|
1165
|
-
});
|
|
1166
|
-
var isFreeNode2 = (node) => !node._containsObstacle && !node._containsTarget;
|
|
1167
|
-
var isSingletonOuterNode = (node, outerZ) => node.availableZ.length === 1 && node.availableZ[0] === outerZ;
|
|
1168
|
-
var sameRect2 = (a, b) => Math.abs(a.x - b.x) <= EPS && Math.abs(a.y - b.y) <= EPS && Math.abs(a.width - b.width) <= EPS && Math.abs(a.height - b.height) <= EPS;
|
|
1169
|
-
var subtractRects2 = (target, cutters) => {
|
|
1170
|
-
let remaining = [target];
|
|
1171
|
-
for (const cutter of cutters) {
|
|
1172
|
-
if (remaining.length === 0) return remaining;
|
|
1173
|
-
const nextRemaining = [];
|
|
1174
|
-
for (const piece of remaining) {
|
|
1175
|
-
nextRemaining.push(...subtractRect2D(piece, cutter));
|
|
1176
|
-
}
|
|
1177
|
-
remaining = nextRemaining;
|
|
1178
|
-
}
|
|
1179
|
-
return remaining;
|
|
1180
|
-
};
|
|
1181
|
-
var isFullyCoveredByRects2 = (target, coveringRects) => {
|
|
1182
|
-
return subtractRects2(target, coveringRects).length === 0;
|
|
1183
|
-
};
|
|
1184
|
-
var OuterLayerContainmentMergeSolver = class extends BaseSolver4 {
|
|
1185
|
-
constructor(input) {
|
|
1186
|
-
super();
|
|
1187
|
-
this.input = input;
|
|
1188
|
-
}
|
|
1189
|
-
input;
|
|
1190
|
-
outputNodes = [];
|
|
1191
|
-
promotedNodeIds = /* @__PURE__ */ new Set();
|
|
1192
|
-
residualNodeIds = /* @__PURE__ */ new Set();
|
|
1193
|
-
_setup() {
|
|
1194
|
-
this.outputNodes = this.input.meshNodes.map(cloneNode2);
|
|
1195
|
-
this.promotedNodeIds.clear();
|
|
1196
|
-
this.residualNodeIds.clear();
|
|
1197
|
-
}
|
|
1198
|
-
_step() {
|
|
1199
|
-
this.outputNodes = this.processOuterLayerContainmentMerges();
|
|
1200
|
-
this.solved = true;
|
|
1201
|
-
}
|
|
1202
|
-
processOuterLayerContainmentMerges() {
|
|
1203
|
-
const srj = this.input.simpleRouteJson;
|
|
1204
|
-
const layerCount = Math.max(1, srj.layerCount || 1);
|
|
1205
|
-
if (layerCount < 3) {
|
|
1206
|
-
return this.input.meshNodes.map(cloneNode2);
|
|
1207
|
-
}
|
|
1208
|
-
const topZ = 0;
|
|
1209
|
-
const bottomZ = layerCount - 1;
|
|
1210
|
-
const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0);
|
|
1211
|
-
const originalNodes = this.input.meshNodes.map(cloneNode2);
|
|
1212
|
-
const obstaclesByLayer = this.buildObstaclesByLayer(layerCount);
|
|
1213
|
-
const mutableOuterNodes = originalNodes.filter(
|
|
1214
|
-
(node) => isFreeNode2(node) && (isSingletonOuterNode(node, topZ) || isSingletonOuterNode(node, bottomZ))
|
|
1215
|
-
);
|
|
1216
|
-
const immutableNodes = originalNodes.filter(
|
|
1217
|
-
(node) => !mutableOuterNodes.includes(node)
|
|
1218
|
-
);
|
|
1219
|
-
const freeSupportRectsByOuterLayer = /* @__PURE__ */ new Map();
|
|
1220
|
-
freeSupportRectsByOuterLayer.set(
|
|
1221
|
-
topZ,
|
|
1222
|
-
originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(topZ)).map(nodeToRect2)
|
|
1223
|
-
);
|
|
1224
|
-
freeSupportRectsByOuterLayer.set(
|
|
1225
|
-
bottomZ,
|
|
1226
|
-
originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(bottomZ)).map(nodeToRect2)
|
|
1227
|
-
);
|
|
1228
|
-
const promotedNodes = [];
|
|
1229
|
-
const promotedRects = [];
|
|
1230
|
-
const candidateNodes = mutableOuterNodes.filter(
|
|
1231
|
-
(node) => node.width + EPS >= viaMinSize && node.height + EPS >= viaMinSize
|
|
1232
|
-
).sort((a, b) => rectArea2(nodeToRect2(b)) - rectArea2(nodeToRect2(a)));
|
|
1233
|
-
for (const candidate of candidateNodes) {
|
|
1234
|
-
const candidateZ = candidate.availableZ[0];
|
|
1235
|
-
const oppositeZ = candidateZ === topZ ? bottomZ : topZ;
|
|
1236
|
-
const candidateRect = nodeToRect2(candidate);
|
|
1237
|
-
const oppositeSupportRects = freeSupportRectsByOuterLayer.get(oppositeZ) ?? [];
|
|
1238
|
-
if (!this.isTransitCompatibleAcrossIntermediateLayers({
|
|
1239
|
-
rect: candidateRect,
|
|
1240
|
-
fromZ: candidateZ,
|
|
1241
|
-
toZ: oppositeZ,
|
|
1242
|
-
obstaclesByLayer
|
|
1243
|
-
})) {
|
|
1244
|
-
continue;
|
|
1245
|
-
}
|
|
1246
|
-
if (!isFullyCoveredByRects2(candidateRect, oppositeSupportRects)) {
|
|
1247
|
-
continue;
|
|
1248
|
-
}
|
|
1249
|
-
promotedNodes.push({
|
|
1250
|
-
...candidate,
|
|
1251
|
-
availableZ: [topZ, bottomZ],
|
|
1252
|
-
layer: `z${topZ},${bottomZ}`
|
|
1253
|
-
});
|
|
1254
|
-
promotedRects.push(candidateRect);
|
|
1255
|
-
this.promotedNodeIds.add(candidate.capacityMeshNodeId);
|
|
1256
|
-
}
|
|
1257
|
-
let nextResidualId = 0;
|
|
1258
|
-
const residualNodes = [];
|
|
1259
|
-
for (const node of mutableOuterNodes) {
|
|
1260
|
-
if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
|
|
1261
|
-
continue;
|
|
1262
|
-
}
|
|
1263
|
-
const nodeRect = nodeToRect2(node);
|
|
1264
|
-
const remainingPieces = subtractRects2(nodeRect, promotedRects);
|
|
1265
|
-
if (remainingPieces.length === 1 && sameRect2(remainingPieces[0], nodeRect)) {
|
|
1266
|
-
residualNodes.push(node);
|
|
1267
|
-
continue;
|
|
1268
|
-
}
|
|
1269
|
-
for (const piece of remainingPieces) {
|
|
1270
|
-
const residualNode = cloneNodeWithRect2(
|
|
1271
|
-
node,
|
|
1272
|
-
piece,
|
|
1273
|
-
`${node.capacityMeshNodeId}-outer-merge-${nextResidualId++}`
|
|
1274
|
-
);
|
|
1275
|
-
residualNodes.push(residualNode);
|
|
1276
|
-
this.residualNodeIds.add(residualNode.capacityMeshNodeId);
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
return [...immutableNodes, ...promotedNodes, ...residualNodes];
|
|
1280
|
-
}
|
|
1281
|
-
buildObstaclesByLayer(layerCount) {
|
|
1282
|
-
const out = Array.from(
|
|
1283
|
-
{ length: layerCount },
|
|
1284
|
-
() => []
|
|
1285
|
-
);
|
|
1286
|
-
for (const obstacle of this.input.simpleRouteJson.obstacles ?? []) {
|
|
1287
|
-
const baseRect = obstacleToXYRect(obstacle);
|
|
1288
|
-
if (!baseRect) continue;
|
|
1289
|
-
const rect = padRect(baseRect, this.input.obstacleClearance ?? 0);
|
|
1290
|
-
const zLayers = obstacleZs(obstacle, this.input.zIndexByName);
|
|
1291
|
-
for (const z of zLayers) {
|
|
1292
|
-
if (z < 0 || z >= layerCount) continue;
|
|
1293
|
-
out[z].push({ obstacle, rect });
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
return out;
|
|
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 });
|
|
1297
721
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
const lo = Math.min(fromZ, toZ);
|
|
1301
|
-
const hi = Math.max(fromZ, toZ);
|
|
1302
|
-
if (hi - lo < 2) return false;
|
|
1303
|
-
for (let z = lo + 1; z < hi; z++) {
|
|
1304
|
-
const overlapping = (obstaclesByLayer[z] ?? []).filter(
|
|
1305
|
-
(entry) => overlaps(entry.rect, rect)
|
|
1306
|
-
);
|
|
1307
|
-
if (overlapping.length === 0) return false;
|
|
1308
|
-
const nonCopperOverlap = overlapping.some(
|
|
1309
|
-
(entry) => !entry.obstacle.isCopperPour
|
|
1310
|
-
);
|
|
1311
|
-
if (nonCopperOverlap) return false;
|
|
1312
|
-
const copperRects = overlapping.filter((entry) => entry.obstacle.isCopperPour).map((entry) => entry.rect);
|
|
1313
|
-
if (!isFullyCoveredByRects2(rect, copperRects)) {
|
|
1314
|
-
return false;
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
return true;
|
|
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 });
|
|
1318
724
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
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 });
|
|
1321
728
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
title: "OuterLayerContainmentMergeSolver",
|
|
1325
|
-
coordinateSystem: "cartesian",
|
|
1326
|
-
rects: this.outputNodes.map((node) => {
|
|
1327
|
-
const colors = getColorForZLayer(node.availableZ);
|
|
1328
|
-
const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId);
|
|
1329
|
-
const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId);
|
|
1330
|
-
return {
|
|
1331
|
-
center: node.center,
|
|
1332
|
-
width: node.width,
|
|
1333
|
-
height: node.height,
|
|
1334
|
-
stroke: isPromoted ? "rgba(22, 163, 74, 0.95)" : isResidual ? "rgba(37, 99, 235, 0.95)" : colors.stroke,
|
|
1335
|
-
fill: node._containsObstacle ? "rgba(239, 68, 68, 0.35)" : isPromoted ? "rgba(34, 197, 94, 0.28)" : isResidual ? "rgba(59, 130, 246, 0.18)" : colors.fill,
|
|
1336
|
-
layer: `z${node.availableZ.join(",")}`,
|
|
1337
|
-
label: [
|
|
1338
|
-
`node ${node.capacityMeshNodeId}`,
|
|
1339
|
-
`z:${node.availableZ.join(",")}`
|
|
1340
|
-
].join("\n")
|
|
1341
|
-
};
|
|
1342
|
-
}),
|
|
1343
|
-
points: [],
|
|
1344
|
-
lines: [],
|
|
1345
|
-
texts: []
|
|
1346
|
-
};
|
|
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 });
|
|
1347
731
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
// lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
|
|
1351
|
-
import {
|
|
1352
|
-
BasePipelineSolver as BasePipelineSolver2,
|
|
1353
|
-
definePipelineStep as definePipelineStep2
|
|
1354
|
-
} from "@tscircuit/solver-utils";
|
|
1355
|
-
|
|
1356
|
-
// lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
|
|
1357
|
-
import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
|
|
732
|
+
return out.filter((r) => r.width > EPS4 && r.height > EPS4);
|
|
733
|
+
}
|
|
1358
734
|
|
|
1359
735
|
// lib/solvers/RectDiffSeedingSolver/isPointInPolygon.ts
|
|
1360
736
|
function isPointInPolygon(p, polygon) {
|
|
@@ -1417,7 +793,7 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1417
793
|
}
|
|
1418
794
|
const finalRects = [];
|
|
1419
795
|
rawRects.sort((a, b) => {
|
|
1420
|
-
if (Math.abs(a.y - b.y) >
|
|
796
|
+
if (Math.abs(a.y - b.y) > EPS4) return a.y - b.y;
|
|
1421
797
|
return a.x - b.x;
|
|
1422
798
|
});
|
|
1423
799
|
let current = null;
|
|
@@ -1426,9 +802,9 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1426
802
|
current = r;
|
|
1427
803
|
continue;
|
|
1428
804
|
}
|
|
1429
|
-
const sameY = Math.abs(current.y - r.y) <
|
|
1430
|
-
const sameHeight = Math.abs(current.height - r.height) <
|
|
1431
|
-
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;
|
|
1432
808
|
if (sameY && sameHeight && touchesX) {
|
|
1433
809
|
current.width += r.width;
|
|
1434
810
|
} else {
|
|
@@ -1438,7 +814,7 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1438
814
|
}
|
|
1439
815
|
if (current) finalRects.push(current);
|
|
1440
816
|
finalRects.sort((a, b) => {
|
|
1441
|
-
if (Math.abs(a.x - b.x) >
|
|
817
|
+
if (Math.abs(a.x - b.x) > EPS4) return a.x - b.x;
|
|
1442
818
|
return a.y - b.y;
|
|
1443
819
|
});
|
|
1444
820
|
const mergedVertical = [];
|
|
@@ -1448,9 +824,9 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1448
824
|
current = r;
|
|
1449
825
|
continue;
|
|
1450
826
|
}
|
|
1451
|
-
const sameX = Math.abs(current.x - r.x) <
|
|
1452
|
-
const sameWidth = Math.abs(current.width - r.width) <
|
|
1453
|
-
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;
|
|
1454
830
|
if (sameX && sameWidth && touchesY) {
|
|
1455
831
|
current.height += r.height;
|
|
1456
832
|
} else {
|
|
@@ -1462,6 +838,81 @@ function computeInverseRects(bounds, polygon) {
|
|
|
1462
838
|
return mergedVertical;
|
|
1463
839
|
}
|
|
1464
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
|
+
|
|
1465
916
|
// lib/utils/isSelfRect.ts
|
|
1466
917
|
var EPS5 = 1e-9;
|
|
1467
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;
|
|
@@ -1516,11 +967,11 @@ function maxExpandRight(params) {
|
|
|
1516
967
|
const { r, bounds, blockers, maxAspect } = params;
|
|
1517
968
|
let maxWidth = bounds.x + bounds.width - r.x;
|
|
1518
969
|
for (const b of blockers) {
|
|
1519
|
-
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;
|
|
1520
971
|
if (verticallyOverlaps) {
|
|
1521
972
|
if (gte(b.x, r.x + r.width)) {
|
|
1522
973
|
maxWidth = Math.min(maxWidth, b.x - r.x);
|
|
1523
|
-
} 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) {
|
|
1524
975
|
return 0;
|
|
1525
976
|
}
|
|
1526
977
|
}
|
|
@@ -1537,11 +988,11 @@ function maxExpandDown(params) {
|
|
|
1537
988
|
const { r, bounds, blockers, maxAspect } = params;
|
|
1538
989
|
let maxHeight = bounds.y + bounds.height - r.y;
|
|
1539
990
|
for (const b of blockers) {
|
|
1540
|
-
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;
|
|
1541
992
|
if (horizOverlaps) {
|
|
1542
993
|
if (gte(b.y, r.y + r.height)) {
|
|
1543
994
|
maxHeight = Math.min(maxHeight, b.y - r.y);
|
|
1544
|
-
} 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) {
|
|
1545
996
|
return 0;
|
|
1546
997
|
}
|
|
1547
998
|
}
|
|
@@ -1558,11 +1009,11 @@ function maxExpandLeft(params) {
|
|
|
1558
1009
|
const { r, bounds, blockers, maxAspect } = params;
|
|
1559
1010
|
let minX = bounds.x;
|
|
1560
1011
|
for (const b of blockers) {
|
|
1561
|
-
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;
|
|
1562
1013
|
if (verticallyOverlaps) {
|
|
1563
1014
|
if (lte(b.x + b.width, r.x)) {
|
|
1564
1015
|
minX = Math.max(minX, b.x + b.width);
|
|
1565
|
-
} else if (b.x < r.x +
|
|
1016
|
+
} else if (b.x < r.x + EPS4 && b.x + b.width > r.x - EPS4) {
|
|
1566
1017
|
return 0;
|
|
1567
1018
|
}
|
|
1568
1019
|
}
|
|
@@ -1579,11 +1030,11 @@ function maxExpandUp(params) {
|
|
|
1579
1030
|
const { r, bounds, blockers, maxAspect } = params;
|
|
1580
1031
|
let minY = bounds.y;
|
|
1581
1032
|
for (const b of blockers) {
|
|
1582
|
-
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;
|
|
1583
1034
|
if (horizOverlaps) {
|
|
1584
1035
|
if (lte(b.y + b.height, r.y)) {
|
|
1585
1036
|
minY = Math.max(minY, b.y + b.height);
|
|
1586
|
-
} else if (b.y < r.y +
|
|
1037
|
+
} else if (b.y < r.y + EPS4 && b.y + b.height > r.y - EPS4) {
|
|
1587
1038
|
return 0;
|
|
1588
1039
|
}
|
|
1589
1040
|
}
|
|
@@ -1608,7 +1059,7 @@ var toQueryRect = (params) => {
|
|
|
1608
1059
|
const minY = Math.max(bounds.y, rect.y);
|
|
1609
1060
|
const maxX = Math.min(bounds.x + bounds.width, rect.x + rect.width);
|
|
1610
1061
|
const maxY = Math.min(bounds.y + bounds.height, rect.y + rect.height);
|
|
1611
|
-
if (maxX <= minX +
|
|
1062
|
+
if (maxX <= minX + EPS4 || maxY <= minY + EPS4) return null;
|
|
1612
1063
|
return { minX, minY, maxX, maxY };
|
|
1613
1064
|
};
|
|
1614
1065
|
function expandRectFromSeed(params) {
|
|
@@ -1714,7 +1165,7 @@ function expandRectFromSeed(params) {
|
|
|
1714
1165
|
improved = true;
|
|
1715
1166
|
}
|
|
1716
1167
|
}
|
|
1717
|
-
if (r.width +
|
|
1168
|
+
if (r.width + EPS4 >= minReq.width && r.height + EPS4 >= minReq.height) {
|
|
1718
1169
|
const area = r.width * r.height;
|
|
1719
1170
|
if (area > bestArea) {
|
|
1720
1171
|
best = quantizeRect(r);
|
|
@@ -1808,7 +1259,7 @@ function computeCandidates3D(params) {
|
|
|
1808
1259
|
]);
|
|
1809
1260
|
for (let x = bounds.x; x < bounds.x + bounds.width; x += gridSize) {
|
|
1810
1261
|
for (let y = bounds.y; y < bounds.y + bounds.height; y += gridSize) {
|
|
1811
|
-
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) {
|
|
1812
1263
|
continue;
|
|
1813
1264
|
}
|
|
1814
1265
|
if (isFullyOccupiedAtPoint({
|
|
@@ -1880,7 +1331,7 @@ function computeUncoveredSegments(params) {
|
|
|
1880
1331
|
const s = quantize3(i.start);
|
|
1881
1332
|
const e = quantize3(i.end);
|
|
1882
1333
|
return { start: Math.min(s, e), end: Math.max(s, e) };
|
|
1883
|
-
}).filter((i) => i.end > i.start +
|
|
1334
|
+
}).filter((i) => i.end > i.start + EPS4);
|
|
1884
1335
|
if (normalizedIntervals.length === 0) {
|
|
1885
1336
|
const center = (lineStartQ + lineEndQ) / 2;
|
|
1886
1337
|
return [{ start: lineStartQ, end: lineEndQ, center }];
|
|
@@ -1890,7 +1341,7 @@ function computeUncoveredSegments(params) {
|
|
|
1890
1341
|
let current = { ...sorted[0] };
|
|
1891
1342
|
for (let i = 1; i < sorted.length; i++) {
|
|
1892
1343
|
const interval = sorted[i];
|
|
1893
|
-
if (interval.start <= current.end +
|
|
1344
|
+
if (interval.start <= current.end + EPS4) {
|
|
1894
1345
|
current.end = Math.max(current.end, interval.end);
|
|
1895
1346
|
} else {
|
|
1896
1347
|
merged.push(current);
|
|
@@ -1899,7 +1350,7 @@ function computeUncoveredSegments(params) {
|
|
|
1899
1350
|
}
|
|
1900
1351
|
merged.push(current);
|
|
1901
1352
|
const uncovered = [];
|
|
1902
|
-
if (merged[0].start > lineStartQ +
|
|
1353
|
+
if (merged[0].start > lineStartQ + EPS4) {
|
|
1903
1354
|
const start = lineStartQ;
|
|
1904
1355
|
const end = merged[0].start;
|
|
1905
1356
|
if (end - start >= minSegmentLength) {
|
|
@@ -1913,7 +1364,7 @@ function computeUncoveredSegments(params) {
|
|
|
1913
1364
|
uncovered.push({ start, end, center: (start + end) / 2 });
|
|
1914
1365
|
}
|
|
1915
1366
|
}
|
|
1916
|
-
if (merged[merged.length - 1].end < lineEndQ -
|
|
1367
|
+
if (merged[merged.length - 1].end < lineEndQ - EPS4) {
|
|
1917
1368
|
const start = merged[merged.length - 1].end;
|
|
1918
1369
|
const end = lineEndQ;
|
|
1919
1370
|
if (end - start >= minSegmentLength) {
|
|
@@ -1932,7 +1383,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
1932
1383
|
hardPlacedByLayer
|
|
1933
1384
|
} = params;
|
|
1934
1385
|
const out = [];
|
|
1935
|
-
const \u03B4 = Math.max(minSize * 0.15,
|
|
1386
|
+
const \u03B4 = Math.max(minSize * 0.15, EPS4 * 3);
|
|
1936
1387
|
const dedup = /* @__PURE__ */ new Set();
|
|
1937
1388
|
const hardRectsByLayer = Array.from(
|
|
1938
1389
|
{ length: layerCount },
|
|
@@ -1956,7 +1407,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
1956
1407
|
const { z } = p;
|
|
1957
1408
|
const x = qx;
|
|
1958
1409
|
const y = qy;
|
|
1959
|
-
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)
|
|
1960
1411
|
return;
|
|
1961
1412
|
if (fullyOcc({ x, y })) return;
|
|
1962
1413
|
const hard = hardRectsByLayer[z] ?? [];
|
|
@@ -2093,7 +1544,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
2093
1544
|
}
|
|
2094
1545
|
for (const b of blockers) {
|
|
2095
1546
|
const obLeftX = b.x - \u03B4;
|
|
2096
|
-
if (obLeftX > bounds.x +
|
|
1547
|
+
if (obLeftX > bounds.x + EPS4 && obLeftX < bounds.x + bounds.width - EPS4) {
|
|
2097
1548
|
const obLeftCovering = blockers.filter(
|
|
2098
1549
|
(bl) => bl !== b && bl.x <= obLeftX && bl.x + bl.width >= obLeftX
|
|
2099
1550
|
).map((bl) => ({
|
|
@@ -2111,7 +1562,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
2111
1562
|
}
|
|
2112
1563
|
}
|
|
2113
1564
|
const obRightX = b.x + b.width + \u03B4;
|
|
2114
|
-
if (obRightX > bounds.x +
|
|
1565
|
+
if (obRightX > bounds.x + EPS4 && obRightX < bounds.x + bounds.width - EPS4) {
|
|
2115
1566
|
const obRightCovering = blockers.filter(
|
|
2116
1567
|
(bl) => bl !== b && bl.x <= obRightX && bl.x + bl.width >= obRightX
|
|
2117
1568
|
).map((bl) => ({
|
|
@@ -2129,7 +1580,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
2129
1580
|
}
|
|
2130
1581
|
}
|
|
2131
1582
|
const obTopY = b.y - \u03B4;
|
|
2132
|
-
if (obTopY > bounds.y +
|
|
1583
|
+
if (obTopY > bounds.y + EPS4 && obTopY < bounds.y + bounds.height - EPS4) {
|
|
2133
1584
|
const obTopCovering = blockers.filter(
|
|
2134
1585
|
(bl) => bl !== b && bl.y <= obTopY && bl.y + bl.height >= obTopY
|
|
2135
1586
|
).map((bl) => ({
|
|
@@ -2147,7 +1598,7 @@ function computeEdgeCandidates3D(params) {
|
|
|
2147
1598
|
}
|
|
2148
1599
|
}
|
|
2149
1600
|
const obBottomY = b.y + b.height + \u03B4;
|
|
2150
|
-
if (obBottomY > bounds.y +
|
|
1601
|
+
if (obBottomY > bounds.y + EPS4 && obBottomY < bounds.y + bounds.height - EPS4) {
|
|
2151
1602
|
const obBottomCovering = blockers.filter(
|
|
2152
1603
|
(bl) => bl !== b && bl.y <= obBottomY && bl.y + bl.height >= obBottomY
|
|
2153
1604
|
).map((bl) => ({
|
|
@@ -2222,12 +1673,12 @@ function resizeSoftOverlaps(params, newIndex) {
|
|
|
2222
1673
|
params.options.minMulti.height
|
|
2223
1674
|
);
|
|
2224
1675
|
for (const p of parts) {
|
|
2225
|
-
if (p.width +
|
|
1676
|
+
if (p.width + EPS4 >= minW && p.height + EPS4 >= minH) {
|
|
2226
1677
|
toAdd.push({ rect: p, zLayers: sharedZ.slice() });
|
|
2227
1678
|
}
|
|
2228
1679
|
}
|
|
2229
1680
|
}
|
|
2230
|
-
const
|
|
1681
|
+
const sameRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
|
|
2231
1682
|
removeIdx.sort((a, b) => b - a).forEach((idx) => {
|
|
2232
1683
|
const rem = params.placed.splice(idx, 1)[0];
|
|
2233
1684
|
if (params.placedIndexByLayer) {
|
|
@@ -2236,7 +1687,7 @@ function resizeSoftOverlaps(params, newIndex) {
|
|
|
2236
1687
|
if (tree)
|
|
2237
1688
|
tree.remove(
|
|
2238
1689
|
rectToTree(rem.rect, { zLayers: rem.zLayers }),
|
|
2239
|
-
|
|
1690
|
+
sameRect
|
|
2240
1691
|
);
|
|
2241
1692
|
}
|
|
2242
1693
|
}
|
|
@@ -2254,9 +1705,23 @@ function resizeSoftOverlaps(params, newIndex) {
|
|
|
2254
1705
|
}
|
|
2255
1706
|
}
|
|
2256
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
|
+
|
|
2257
1722
|
// lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
|
|
2258
1723
|
import RBush3 from "rbush";
|
|
2259
|
-
var RectDiffSeedingSolver = class extends
|
|
1724
|
+
var RectDiffSeedingSolver = class extends BaseSolver3 {
|
|
2260
1725
|
constructor(input) {
|
|
2261
1726
|
super();
|
|
2262
1727
|
this.input = input;
|
|
@@ -2615,7 +2080,7 @@ z:${placement.zLayers.join(",")}`
|
|
|
2615
2080
|
};
|
|
2616
2081
|
|
|
2617
2082
|
// lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
|
|
2618
|
-
import { BaseSolver as
|
|
2083
|
+
import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
|
|
2619
2084
|
|
|
2620
2085
|
// lib/utils/finalizeRects.ts
|
|
2621
2086
|
function finalizeRects(params) {
|
|
@@ -2687,7 +2152,7 @@ import RBush4 from "rbush";
|
|
|
2687
2152
|
var sameTreeRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
|
|
2688
2153
|
|
|
2689
2154
|
// lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
|
|
2690
|
-
var RectDiffExpansionSolver = class extends
|
|
2155
|
+
var RectDiffExpansionSolver = class extends BaseSolver4 {
|
|
2691
2156
|
constructor(input) {
|
|
2692
2157
|
super();
|
|
2693
2158
|
this.input = input;
|
|
@@ -2833,6 +2298,19 @@ import "rbush";
|
|
|
2833
2298
|
|
|
2834
2299
|
// lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
|
|
2835
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
|
|
2836
2314
|
var buildObstacleIndexesByLayer = (params) => {
|
|
2837
2315
|
const { srj, boardVoidRects, obstacleClearance } = params;
|
|
2838
2316
|
const { layerNames, zIndexByName } = buildZIndexMap({
|
|
@@ -3176,8 +2654,6 @@ import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
|
|
|
3176
2654
|
var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
3177
2655
|
rectDiffGridSolverPipeline;
|
|
3178
2656
|
gapFillSolver;
|
|
3179
|
-
outerLayerContainmentMergeSolver;
|
|
3180
|
-
adjacentLayerContainmentMergeSolver;
|
|
3181
2657
|
boardVoidRects;
|
|
3182
2658
|
zIndexByName;
|
|
3183
2659
|
layerNames;
|
|
@@ -3213,28 +2689,6 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
|
3213
2689
|
}
|
|
3214
2690
|
}
|
|
3215
2691
|
]
|
|
3216
|
-
),
|
|
3217
|
-
definePipelineStep3(
|
|
3218
|
-
"outerLayerContainmentMergeSolver",
|
|
3219
|
-
OuterLayerContainmentMergeSolver,
|
|
3220
|
-
(rectDiffPipeline) => [
|
|
3221
|
-
{
|
|
3222
|
-
meshNodes: rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ?? rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
|
|
3223
|
-
simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
|
|
3224
|
-
zIndexByName: rectDiffPipeline.zIndexByName ?? /* @__PURE__ */ new Map(),
|
|
3225
|
-
obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance
|
|
3226
|
-
}
|
|
3227
|
-
]
|
|
3228
|
-
),
|
|
3229
|
-
definePipelineStep3(
|
|
3230
|
-
"adjacentLayerContainmentMergeSolver",
|
|
3231
|
-
AdjacentLayerContainmentMergeSolver,
|
|
3232
|
-
(rectDiffPipeline) => [
|
|
3233
|
-
{
|
|
3234
|
-
meshNodes: rectDiffPipeline.outerLayerContainmentMergeSolver?.getOutput().outputNodes ?? rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ?? rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
|
|
3235
|
-
simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson
|
|
3236
|
-
}
|
|
3237
|
-
]
|
|
3238
2692
|
)
|
|
3239
2693
|
];
|
|
3240
2694
|
_setup() {
|
|
@@ -3260,14 +2714,6 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
|
3260
2714
|
return [this.inputProblem];
|
|
3261
2715
|
}
|
|
3262
2716
|
getOutput() {
|
|
3263
|
-
const adjacentLayerMergeOutput = this.adjacentLayerContainmentMergeSolver?.getOutput();
|
|
3264
|
-
if (adjacentLayerMergeOutput) {
|
|
3265
|
-
return { meshNodes: adjacentLayerMergeOutput.outputNodes };
|
|
3266
|
-
}
|
|
3267
|
-
const outerLayerMergeOutput = this.outerLayerContainmentMergeSolver?.getOutput();
|
|
3268
|
-
if (outerLayerMergeOutput) {
|
|
3269
|
-
return { meshNodes: outerLayerMergeOutput.outputNodes };
|
|
3270
|
-
}
|
|
3271
2717
|
const gapFillOutput = this.gapFillSolver?.getOutput();
|
|
3272
2718
|
if (gapFillOutput) {
|
|
3273
2719
|
return { meshNodes: gapFillOutput.outputNodes };
|