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