@tscircuit/rectdiff 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.github/workflows/bun-pver-release.yml +24 -45
  2. package/lib/RectDiffPipeline.ts +0 -46
  3. package/lib/fixtures/twoNodeExpansionFixture.ts +1 -1
  4. package/lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts +4 -2
  5. package/lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts +3 -1
  6. package/lib/solvers/GapFillSolver/GapFillSolverPipeline.ts +6 -2
  7. package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +5 -2
  8. package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +8 -5
  9. package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +6 -6
  10. package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +4 -4
  11. package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +1 -1
  12. package/lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts +1 -1
  13. package/lib/solvers/RectDiffSeedingSolver/longestFreeSpanAroundZ.ts +1 -1
  14. package/lib/types/capacity-mesh-types.ts +1 -1
  15. package/lib/types/srj-types.ts +0 -1
  16. package/lib/utils/expandRectFromSeed.ts +1 -1
  17. package/lib/utils/finalizeRects.ts +1 -1
  18. package/lib/utils/isFullyOccupiedAtPoint.ts +1 -1
  19. package/lib/utils/isSelfRect.ts +1 -1
  20. package/lib/utils/rectToTree.ts +2 -2
  21. package/lib/utils/renderObstacleClearance.ts +1 -1
  22. package/lib/utils/resizeSoftOverlaps.ts +1 -1
  23. package/lib/utils/sameTreeRect.ts +1 -1
  24. package/lib/utils/searchStrip.ts +1 -1
  25. package/package.json +12 -8
  26. package/pages/repro/merge-single-layer-node.page.tsx +17 -0
  27. package/tests/__snapshots__/board-outline.snap.svg +2 -2
  28. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
  29. package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
  30. package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +1 -1
  31. package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +1 -1
  32. package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
  33. package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
  34. package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
  35. package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
  36. package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +1 -1
  37. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
  38. package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
  39. package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +1 -1
  40. package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
  41. package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
  42. package/tests/solver/bugreport20-obstacle-clipping/__snapshots__/bugreport20-obstacle-clipping.snap.svg +1 -1
  43. package/tests/solver/bugreport21-board-outline/__snapshots__/bugreport21-board-outline.snap.svg +2 -2
  44. package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
  45. package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
  46. package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
  47. package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +1 -1
  48. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
  49. package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
  50. package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
  51. package/tests/solver/no-better-path/__snapshots__/no-better-path.snap.svg +1 -1
  52. package/tests/solver/repros/merge-single-layer-node/merge-single-layer-node.json +861 -0
  53. 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
  54. package/tests/solver/transitivity/__snapshots__/transitivity.snap.svg +2 -2
  55. package/tsconfig.json +5 -1
  56. package/vite.config.ts +4 -0
  57. package/dist/index.d.ts +0 -427
  58. package/dist/index.js +0 -3319
  59. package/lib/solvers/AdjacentLayerContainmentMergeSolver/AdjacentLayerContainmentMergeSolver.ts +0 -456
  60. package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +0 -314
  61. package/pages/bugreports/bugreport50-multi-support-layer-merge.page.tsx +0 -19
  62. package/pages/pour.page.tsx +0 -18
  63. package/test-assets/bugreport49-634662.json +0 -412
  64. package/tests/outer-layer-containment-merge-solver.test.ts +0 -73
  65. package/tests/solver/bugreport49-634662/__snapshots__/bugreport49-634662.snap.svg +0 -44
  66. package/tests/solver/bugreport49-634662/bugreport49-634662.test.ts +0 -130
  67. package/tests/solver/bugreport50-multi-support-layer-merge/__snapshots__/bugreport50-multi-support-layer-merge.snap.svg +0 -44
  68. package/tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.json +0 -972
package/dist/index.js DELETED
@@ -1,3319 +0,0 @@
1
- // lib/RectDiffPipeline.ts
2
- import {
3
- BasePipelineSolver as BasePipelineSolver3,
4
- definePipelineStep as definePipelineStep3
5
- } from "@tscircuit/solver-utils";
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
- // lib/solvers/GapFillSolver/GapFillSolverPipeline.ts
390
- import {
391
- BasePipelineSolver,
392
- definePipelineStep
393
- } from "@tscircuit/solver-utils";
394
-
395
- // lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
396
- import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
397
- import Flatbush from "flatbush";
398
-
399
- // lib/solvers/GapFillSolver/projectToUncoveredSegments.ts
400
- var EPS2 = 1e-4;
401
- function projectToUncoveredSegments(primaryEdge, overlappingEdges) {
402
- const isHorizontal = Math.abs(primaryEdge.start.y - primaryEdge.end.y) < EPS2;
403
- const isVertical = Math.abs(primaryEdge.start.x - primaryEdge.end.x) < EPS2;
404
- if (!isHorizontal && !isVertical) return [];
405
- const axis = isHorizontal ? "x" : "y";
406
- const perp = isHorizontal ? "y" : "x";
407
- const lineCoord = primaryEdge.start[perp];
408
- const p0 = primaryEdge.start[axis];
409
- const p1 = primaryEdge.end[axis];
410
- const pMin = Math.min(p0, p1);
411
- const pMax = Math.max(p0, p1);
412
- const clamp2 = (v) => Math.max(pMin, Math.min(pMax, v));
413
- const intervals = [];
414
- for (const e of overlappingEdges) {
415
- if (e === primaryEdge) continue;
416
- const eIsHorizontal = Math.abs(e.start.y - e.end.y) < EPS2;
417
- const eIsVertical = Math.abs(e.start.x - e.end.x) < EPS2;
418
- if (axis === "x" && !eIsHorizontal) continue;
419
- if (axis === "y" && !eIsVertical) continue;
420
- if (Math.abs(e.start[perp] - lineCoord) > EPS2) continue;
421
- const eMin = Math.min(e.start[axis], e.end[axis]);
422
- const eMax = Math.max(e.start[axis], e.end[axis]);
423
- const s = clamp2(eMin);
424
- const t = clamp2(eMax);
425
- if (t - s > EPS2) intervals.push({ s, e: t });
426
- }
427
- if (intervals.length === 0) {
428
- return [
429
- {
430
- ...primaryEdge,
431
- start: { ...primaryEdge.start },
432
- end: { ...primaryEdge.end }
433
- }
434
- ];
435
- }
436
- intervals.sort((a, b) => a.s - b.s);
437
- const merged = [];
438
- for (const it of intervals) {
439
- const last = merged[merged.length - 1];
440
- if (!last || it.s > last.e + EPS2) merged.push({ ...it });
441
- else last.e = Math.max(last.e, it.e);
442
- }
443
- const uncovered = [];
444
- let cursor = pMin;
445
- for (const m of merged) {
446
- if (m.s > cursor + EPS2) uncovered.push({ s: cursor, e: m.s });
447
- cursor = Math.max(cursor, m.e);
448
- if (cursor >= pMax - EPS2) break;
449
- }
450
- if (pMax > cursor + EPS2) uncovered.push({ s: cursor, e: pMax });
451
- if (uncovered.length === 0) return [];
452
- return uncovered.filter((u) => u.e - u.s > EPS2).map((u) => {
453
- const start = axis === "x" ? { x: u.s, y: lineCoord } : { x: lineCoord, y: u.s };
454
- const end = axis === "x" ? { x: u.e, y: lineCoord } : { x: lineCoord, y: u.e };
455
- return {
456
- parent: primaryEdge.parent,
457
- facingDirection: primaryEdge.facingDirection,
458
- start,
459
- end,
460
- z: primaryEdge.z
461
- };
462
- });
463
- }
464
-
465
- // lib/solvers/GapFillSolver/edge-constants.ts
466
- var EDGES = [
467
- {
468
- facingDirection: "x-",
469
- dx: -1,
470
- dy: 0,
471
- startX: -0.5,
472
- startY: 0.5,
473
- endX: -0.5,
474
- endY: -0.5
475
- },
476
- {
477
- facingDirection: "x+",
478
- dx: 1,
479
- dy: 0,
480
- startX: 0.5,
481
- startY: 0.5,
482
- endX: 0.5,
483
- endY: -0.5
484
- },
485
- {
486
- facingDirection: "y-",
487
- dx: 0,
488
- dy: -1,
489
- startX: -0.5,
490
- startY: -0.5,
491
- endX: 0.5,
492
- endY: -0.5
493
- },
494
- {
495
- facingDirection: "y+",
496
- dx: 0,
497
- dy: 1,
498
- startX: 0.5,
499
- startY: 0.5,
500
- endX: -0.5,
501
- endY: 0.5
502
- }
503
- ];
504
- var EDGE_MAP = {
505
- "x-": EDGES.find((e) => e.facingDirection === "x-"),
506
- "x+": EDGES.find((e) => e.facingDirection === "x+"),
507
- "y-": EDGES.find((e) => e.facingDirection === "y-"),
508
- "y+": EDGES.find((e) => e.facingDirection === "y+")
509
- };
510
-
511
- // lib/solvers/GapFillSolver/visuallyOffsetLine.ts
512
- var OFFSET_DIR_MAP = {
513
- "x-": {
514
- x: -1,
515
- y: 0
516
- },
517
- "x+": {
518
- x: 1,
519
- y: 0
520
- },
521
- "y-": {
522
- x: 0,
523
- y: -1
524
- },
525
- "y+": {
526
- x: 0,
527
- y: 1
528
- }
529
- };
530
- var visuallyOffsetLine = (line, options) => {
531
- const { dir, amt } = options;
532
- const offset = OFFSET_DIR_MAP[dir];
533
- return line.map((p) => ({
534
- x: p.x + offset.x * amt,
535
- y: p.y + offset.y * amt
536
- }));
537
- };
538
-
539
- // lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
540
- import "@tscircuit/math-utils";
541
- var EPS3 = 1e-4;
542
- var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver2 {
543
- constructor(input) {
544
- super();
545
- this.input = input;
546
- for (const node of this.input.meshNodes) {
547
- for (const edge of EDGES) {
548
- let start = {
549
- x: node.center.x + node.width * edge.startX,
550
- y: node.center.y + node.height * edge.startY
551
- };
552
- let end = {
553
- x: node.center.x + node.width * edge.endX,
554
- y: node.center.y + node.height * edge.endY
555
- };
556
- if (start.x > end.x) {
557
- ;
558
- [start, end] = [end, start];
559
- }
560
- if (Math.abs(start.x - end.x) < EPS3 && start.y > end.y) {
561
- ;
562
- [start, end] = [end, start];
563
- }
564
- for (const z of node.availableZ) {
565
- this.unprocessedEdges.push({
566
- parent: node,
567
- start,
568
- end,
569
- facingDirection: edge.facingDirection,
570
- z
571
- });
572
- }
573
- }
574
- }
575
- this.allEdges = [...this.unprocessedEdges];
576
- this.edgeSpatialIndex = new Flatbush(this.allEdges.length);
577
- for (const edge of this.allEdges) {
578
- this.edgeSpatialIndex.add(
579
- edge.start.x,
580
- edge.start.y,
581
- edge.end.x,
582
- edge.end.y
583
- );
584
- }
585
- this.edgeSpatialIndex.finish();
586
- }
587
- input;
588
- allEdges;
589
- unprocessedEdges = [];
590
- segmentsWithAdjacentEmptySpace = [];
591
- edgeSpatialIndex;
592
- lastCandidateEdge = null;
593
- lastOverlappingEdges = null;
594
- lastUncoveredSegments = null;
595
- _step() {
596
- if (this.unprocessedEdges.length === 0) {
597
- this.solved = true;
598
- this.lastCandidateEdge = null;
599
- this.lastOverlappingEdges = null;
600
- this.lastUncoveredSegments = null;
601
- return;
602
- }
603
- const candidateEdge = this.unprocessedEdges.shift();
604
- this.lastCandidateEdge = candidateEdge;
605
- const nearbyEdges = this.edgeSpatialIndex.search(
606
- candidateEdge.start.x - EPS3,
607
- candidateEdge.start.y - EPS3,
608
- candidateEdge.end.x + EPS3,
609
- candidateEdge.end.y + EPS3
610
- );
611
- const overlappingEdges = nearbyEdges.map((i) => this.allEdges[i]).filter((e) => e.z === candidateEdge.z);
612
- this.lastOverlappingEdges = overlappingEdges;
613
- const uncoveredSegments = projectToUncoveredSegments(
614
- candidateEdge,
615
- overlappingEdges
616
- );
617
- this.lastUncoveredSegments = uncoveredSegments;
618
- this.segmentsWithAdjacentEmptySpace.push(...uncoveredSegments);
619
- }
620
- getOutput() {
621
- return {
622
- segmentsWithAdjacentEmptySpace: this.segmentsWithAdjacentEmptySpace
623
- };
624
- }
625
- visualize() {
626
- const graphics = {
627
- title: "FindSegmentsWithAdjacentEmptySpace",
628
- coordinateSystem: "cartesian",
629
- rects: [],
630
- points: [],
631
- lines: [],
632
- circles: [],
633
- arrows: [],
634
- texts: []
635
- };
636
- for (const node of this.input.meshNodes) {
637
- graphics.rects.push({
638
- center: node.center,
639
- width: node.width,
640
- height: node.height,
641
- stroke: "rgba(0, 0, 0, 0.1)"
642
- });
643
- }
644
- for (const unprocessedEdge of this.unprocessedEdges) {
645
- graphics.lines.push({
646
- points: visuallyOffsetLine(
647
- [unprocessedEdge.start, unprocessedEdge.end],
648
- { dir: unprocessedEdge.facingDirection, amt: -0.1 }
649
- ),
650
- strokeColor: "rgba(0, 0, 255, 0.5)",
651
- strokeDash: "5 5"
652
- });
653
- }
654
- for (const edge of this.segmentsWithAdjacentEmptySpace) {
655
- graphics.lines.push({
656
- points: [edge.start, edge.end],
657
- strokeColor: "rgba(0,255,0, 0.5)"
658
- });
659
- }
660
- if (this.lastCandidateEdge) {
661
- graphics.lines.push({
662
- points: [this.lastCandidateEdge.start, this.lastCandidateEdge.end],
663
- strokeColor: "blue"
664
- });
665
- }
666
- if (this.lastOverlappingEdges) {
667
- for (const edge of this.lastOverlappingEdges) {
668
- graphics.lines.push({
669
- points: visuallyOffsetLine([edge.start, edge.end], {
670
- dir: edge.facingDirection,
671
- amt: 0.05
672
- }),
673
- strokeColor: "red",
674
- strokeDash: "2 2"
675
- });
676
- }
677
- }
678
- if (this.lastUncoveredSegments) {
679
- for (const edge of this.lastUncoveredSegments) {
680
- graphics.lines.push({
681
- points: visuallyOffsetLine([edge.start, edge.end], {
682
- dir: edge.facingDirection,
683
- amt: -0.05
684
- }),
685
- strokeColor: "green",
686
- strokeDash: "2 2"
687
- });
688
- }
689
- }
690
- return graphics;
691
- }
692
- };
693
-
694
- // lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
695
- import { BaseSolver as BaseSolver3 } from "@tscircuit/solver-utils";
696
- import RBush from "rbush";
697
-
698
- // lib/solvers/GapFillSolver/getBoundsFromCorners.ts
699
- var getBoundsFromCorners = (corners) => {
700
- return {
701
- minX: Math.min(...corners.map((c) => c.x)),
702
- minY: Math.min(...corners.map((c) => c.y)),
703
- maxX: Math.max(...corners.map((c) => c.x)),
704
- maxY: Math.max(...corners.map((c) => c.y))
705
- };
706
- };
707
-
708
- // lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
709
- import { segmentToBoxMinDistance } from "@tscircuit/math-utils";
710
- var EPS4 = 1e-4;
711
- var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver3 {
712
- constructor(input) {
713
- super();
714
- this.input = input;
715
- this.unprocessedSegments = [...this.input.segmentsWithAdjacentEmptySpace];
716
- this.rectSpatialIndex = new RBush();
717
- this.rectSpatialIndex.load(
718
- this.input.boardVoid?.boardVoidRects.map((rect, index) => ({
719
- capacityMeshNodeId: `void-rect-${index}`,
720
- center: {
721
- x: rect.x + rect.width / 2,
722
- y: rect.y + rect.height / 2
723
- },
724
- width: rect.width,
725
- height: rect.height,
726
- availableZ: Array.from(
727
- { length: this.input.boardVoid?.layerCount || 0 },
728
- (_, i) => i
729
- ),
730
- layer: "void",
731
- minX: rect.x,
732
- minY: rect.y,
733
- maxX: rect.x + rect.width,
734
- maxY: rect.y + rect.height
735
- })) || []
736
- );
737
- this.rectSpatialIndex.load(
738
- this.input.inputMeshNodes.map((n) => ({
739
- ...n,
740
- minX: n.center.x - n.width / 2,
741
- minY: n.center.y - n.height / 2,
742
- maxX: n.center.x + n.width / 2,
743
- maxY: n.center.y + n.height / 2
744
- }))
745
- );
746
- }
747
- input;
748
- unprocessedSegments = [];
749
- expandedSegments = [];
750
- lastSegment = null;
751
- lastSearchBounds = null;
752
- lastCollidingNodes = null;
753
- lastSearchCorner1 = null;
754
- lastSearchCorner2 = null;
755
- lastExpandedSegment = null;
756
- rectSpatialIndex;
757
- _step() {
758
- if (this.unprocessedSegments.length === 0) {
759
- this.solved = true;
760
- return;
761
- }
762
- const segment = this.unprocessedSegments.shift();
763
- this.lastSegment = segment;
764
- const { dx, dy } = EDGE_MAP[segment.facingDirection];
765
- const deltaStartEnd = {
766
- x: segment.end.x - segment.start.x,
767
- y: segment.end.y - segment.start.y
768
- };
769
- const segLength = Math.sqrt(deltaStartEnd.x ** 2 + deltaStartEnd.y ** 2);
770
- const normDeltaStartEnd = {
771
- x: deltaStartEnd.x / segLength,
772
- y: deltaStartEnd.y / segLength
773
- };
774
- let collidingNodes = null;
775
- let searchDistance = 1;
776
- const searchCorner1 = {
777
- x: segment.start.x + dx * EPS4 + normDeltaStartEnd.x * EPS4 * 10,
778
- y: segment.start.y + dy * EPS4 + normDeltaStartEnd.y * EPS4 * 10
779
- };
780
- const searchCorner2 = {
781
- x: segment.end.x + dx * EPS4 - normDeltaStartEnd.x * EPS4 * 10,
782
- y: segment.end.y + dy * EPS4 - normDeltaStartEnd.y * EPS4 * 10
783
- };
784
- this.lastSearchCorner1 = searchCorner1;
785
- this.lastSearchCorner2 = searchCorner2;
786
- while ((!collidingNodes || collidingNodes.length === 0) && searchDistance < 1e3) {
787
- const searchBounds = getBoundsFromCorners([
788
- searchCorner1,
789
- searchCorner2,
790
- {
791
- x: searchCorner1.x + dx * searchDistance,
792
- y: searchCorner1.y + dy * searchDistance
793
- },
794
- {
795
- x: searchCorner2.x + dx * searchDistance,
796
- y: searchCorner2.y + dy * searchDistance
797
- }
798
- ]);
799
- this.lastSearchBounds = searchBounds;
800
- collidingNodes = this.rectSpatialIndex.search(searchBounds).filter((n) => n.availableZ.includes(segment.z)).filter(
801
- (n) => n.capacityMeshNodeId !== segment.parent.capacityMeshNodeId
802
- );
803
- searchDistance *= 4;
804
- }
805
- if (!collidingNodes || collidingNodes.length === 0) {
806
- return;
807
- }
808
- this.lastCollidingNodes = collidingNodes;
809
- let smallestDistance = Infinity;
810
- for (const node of collidingNodes) {
811
- const distance = segmentToBoxMinDistance(segment.start, segment.end, node);
812
- if (distance < smallestDistance) {
813
- smallestDistance = distance;
814
- }
815
- }
816
- const expandDistance = smallestDistance;
817
- const nodeBounds = getBoundsFromCorners([
818
- segment.start,
819
- segment.end,
820
- {
821
- x: segment.start.x + dx * expandDistance,
822
- y: segment.start.y + dy * expandDistance
823
- },
824
- {
825
- x: segment.end.x + dx * expandDistance,
826
- y: segment.end.y + dy * expandDistance
827
- }
828
- ]);
829
- const nodeCenter = {
830
- x: (nodeBounds.minX + nodeBounds.maxX) / 2,
831
- y: (nodeBounds.minY + nodeBounds.maxY) / 2
832
- };
833
- const nodeWidth = nodeBounds.maxX - nodeBounds.minX;
834
- const nodeHeight = nodeBounds.maxY - nodeBounds.minY;
835
- const expandedSegment = {
836
- segment,
837
- newNode: {
838
- capacityMeshNodeId: `new-${segment.parent.capacityMeshNodeId}-${this.expandedSegments.length}`,
839
- center: nodeCenter,
840
- width: nodeWidth,
841
- height: nodeHeight,
842
- availableZ: [segment.z],
843
- layer: segment.parent.layer
844
- }
845
- };
846
- this.lastExpandedSegment = expandedSegment;
847
- if (nodeWidth < EPS4 || nodeHeight < EPS4) {
848
- return;
849
- }
850
- this.expandedSegments.push(expandedSegment);
851
- this.rectSpatialIndex.insert({
852
- ...expandedSegment.newNode,
853
- ...nodeBounds
854
- });
855
- }
856
- getOutput() {
857
- return {
858
- expandedSegments: this.expandedSegments
859
- };
860
- }
861
- visualize() {
862
- const graphics = {
863
- title: "ExpandEdgesToEmptySpace",
864
- coordinateSystem: "cartesian",
865
- rects: [],
866
- points: [],
867
- lines: [],
868
- circles: [],
869
- arrows: [],
870
- texts: []
871
- };
872
- for (const node of this.input.inputMeshNodes) {
873
- graphics.rects.push({
874
- center: node.center,
875
- width: node.width,
876
- height: node.height,
877
- stroke: "rgba(0, 0, 0, 0.1)",
878
- layer: `z${node.availableZ.join(",")}`,
879
- label: [
880
- `node ${node.capacityMeshNodeId}`,
881
- `z:${node.availableZ.join(",")}`
882
- ].join("\n")
883
- });
884
- }
885
- for (const { newNode } of this.expandedSegments) {
886
- graphics.rects.push({
887
- center: newNode.center,
888
- width: newNode.width,
889
- height: newNode.height,
890
- fill: "green",
891
- label: `expandedSegment (z=${newNode.availableZ.join(",")})`,
892
- layer: `z${newNode.availableZ.join(",")}`
893
- });
894
- }
895
- if (this.lastSegment) {
896
- graphics.lines.push({
897
- points: [this.lastSegment.start, this.lastSegment.end],
898
- strokeColor: "rgba(0, 0, 255, 0.5)"
899
- });
900
- }
901
- if (this.lastSearchBounds) {
902
- graphics.rects.push({
903
- center: {
904
- x: (this.lastSearchBounds.minX + this.lastSearchBounds.maxX) / 2,
905
- y: (this.lastSearchBounds.minY + this.lastSearchBounds.maxY) / 2
906
- },
907
- width: this.lastSearchBounds.maxX - this.lastSearchBounds.minX,
908
- height: this.lastSearchBounds.maxY - this.lastSearchBounds.minY,
909
- fill: "rgba(0, 0, 255, 0.25)"
910
- });
911
- }
912
- if (this.lastSearchCorner1 && this.lastSearchCorner2) {
913
- graphics.points.push({
914
- x: this.lastSearchCorner1.x,
915
- y: this.lastSearchCorner1.y,
916
- color: "rgba(0, 0, 255, 0.5)",
917
- label: ["searchCorner1", `z=${this.lastSegment?.z}`].join("\n")
918
- });
919
- graphics.points.push({
920
- x: this.lastSearchCorner2.x,
921
- y: this.lastSearchCorner2.y,
922
- color: "rgba(0, 0, 255, 0.5)",
923
- label: ["searchCorner2", `z=${this.lastSegment?.z}`].join("\n")
924
- });
925
- }
926
- if (this.lastExpandedSegment) {
927
- graphics.rects.push({
928
- center: this.lastExpandedSegment.newNode.center,
929
- width: this.lastExpandedSegment.newNode.width,
930
- height: this.lastExpandedSegment.newNode.height,
931
- fill: "purple",
932
- label: `expandedSegment (z=${this.lastExpandedSegment.segment.z})`
933
- });
934
- }
935
- if (this.lastCollidingNodes) {
936
- for (const node of this.lastCollidingNodes) {
937
- graphics.rects.push({
938
- center: node.center,
939
- width: node.width,
940
- height: node.height,
941
- fill: "rgba(255, 0, 0, 0.5)"
942
- });
943
- }
944
- }
945
- return graphics;
946
- }
947
- };
948
-
949
- // lib/solvers/GapFillSolver/GapFillSolverPipeline.ts
950
- var GapFillSolverPipeline = class extends BasePipelineSolver {
951
- findSegmentsWithAdjacentEmptySpaceSolver;
952
- expandEdgesToEmptySpaceSolver;
953
- pipelineDef = [
954
- definePipelineStep(
955
- "findSegmentsWithAdjacentEmptySpaceSolver",
956
- FindSegmentsWithAdjacentEmptySpaceSolver,
957
- (gapFillPipeline) => [
958
- {
959
- meshNodes: gapFillPipeline.inputProblem.meshNodes
960
- }
961
- ],
962
- {
963
- onSolved: () => {
964
- }
965
- }
966
- ),
967
- definePipelineStep(
968
- "expandEdgesToEmptySpaceSolver",
969
- ExpandEdgesToEmptySpaceSolver,
970
- (gapFillPipeline) => [
971
- {
972
- inputMeshNodes: gapFillPipeline.inputProblem.meshNodes,
973
- segmentsWithAdjacentEmptySpace: gapFillPipeline.findSegmentsWithAdjacentEmptySpaceSolver.getOutput().segmentsWithAdjacentEmptySpace,
974
- boardVoid: gapFillPipeline.inputProblem.boardVoid
975
- }
976
- ],
977
- {
978
- onSolved: () => {
979
- }
980
- }
981
- )
982
- ];
983
- getOutput() {
984
- const expandedSegments = this.expandEdgesToEmptySpaceSolver?.getOutput().expandedSegments ?? [];
985
- const expandedNodes = expandedSegments.map((es) => es.newNode);
986
- return {
987
- outputNodes: [...this.inputProblem.meshNodes, ...expandedNodes]
988
- };
989
- }
990
- initialVisualize() {
991
- const graphics = {
992
- title: "GapFillSolverPipeline - Initial",
993
- coordinateSystem: "cartesian",
994
- rects: [],
995
- points: [],
996
- lines: [],
997
- circles: [],
998
- arrows: [],
999
- texts: []
1000
- };
1001
- for (const node of this.inputProblem.meshNodes) {
1002
- graphics.rects.push({
1003
- center: node.center,
1004
- width: node.width,
1005
- height: node.height,
1006
- stroke: "rgba(0, 0, 0, 0.3)",
1007
- fill: "rgba(100, 100, 100, 0.1)",
1008
- layer: `z${node.availableZ.join(",")}`,
1009
- label: [
1010
- `node ${node.capacityMeshNodeId}`,
1011
- `z:${node.availableZ.join(",")}`
1012
- ].join("\n")
1013
- });
1014
- }
1015
- return graphics;
1016
- }
1017
- finalVisualize() {
1018
- const graphics = {
1019
- title: "GapFillSolverPipeline - Final",
1020
- coordinateSystem: "cartesian",
1021
- rects: [],
1022
- points: [],
1023
- lines: [],
1024
- circles: [],
1025
- arrows: [],
1026
- texts: []
1027
- };
1028
- const { outputNodes } = this.getOutput();
1029
- const expandedSegments = this.expandEdgesToEmptySpaceSolver?.getOutput().expandedSegments ?? [];
1030
- const expandedNodeIds = new Set(
1031
- expandedSegments.map((es) => es.newNode.capacityMeshNodeId)
1032
- );
1033
- for (const node of outputNodes) {
1034
- const isExpanded = expandedNodeIds.has(node.capacityMeshNodeId);
1035
- graphics.rects.push({
1036
- center: node.center,
1037
- width: node.width,
1038
- height: node.height,
1039
- stroke: isExpanded ? "rgba(0, 128, 0, 0.8)" : "rgba(0, 0, 0, 0.3)",
1040
- fill: isExpanded ? "rgba(0, 200, 0, 0.3)" : "rgba(100, 100, 100, 0.1)",
1041
- layer: `z${node.availableZ.join(",")}`,
1042
- label: [
1043
- `${isExpanded ? "[expanded] " : ""}node ${node.capacityMeshNodeId}`,
1044
- `z:${node.availableZ.join(",")}`
1045
- ].join("\n")
1046
- });
1047
- }
1048
- return graphics;
1049
- }
1050
- };
1051
-
1052
- // lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts
1053
- import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
1054
-
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);
1063
- }
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
- });
1071
- }
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 };
1116
- }
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);
1122
- }
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 MIN_OUTER_LAYER_MERGE_AREA_MM2 = 1;
1150
- var cloneNode2 = (node) => ({
1151
- ...node,
1152
- center: { ...node.center },
1153
- availableZ: [...node.availableZ]
1154
- });
1155
- var cloneNodeWithRect2 = (node, rect, capacityMeshNodeId) => ({
1156
- ...node,
1157
- capacityMeshNodeId,
1158
- center: {
1159
- x: rect.x + rect.width / 2,
1160
- y: rect.y + rect.height / 2
1161
- },
1162
- width: rect.width,
1163
- height: rect.height,
1164
- availableZ: [...node.availableZ],
1165
- layer: `z${node.availableZ.join(",")}`
1166
- });
1167
- var isFreeNode2 = (node) => !node._containsObstacle && !node._containsTarget;
1168
- var isSingletonOuterNode = (node, outerZ) => node.availableZ.length === 1 && node.availableZ[0] === outerZ;
1169
- var sameRect2 = (a, b) => Math.abs(a.x - b.x) <= EPS && Math.abs(a.y - b.y) <= EPS && Math.abs(a.width - b.width) <= EPS && Math.abs(a.height - b.height) <= EPS;
1170
- var subtractRects2 = (target, cutters) => {
1171
- let remaining = [target];
1172
- for (const cutter of cutters) {
1173
- if (remaining.length === 0) return remaining;
1174
- const nextRemaining = [];
1175
- for (const piece of remaining) {
1176
- nextRemaining.push(...subtractRect2D(piece, cutter));
1177
- }
1178
- remaining = nextRemaining;
1179
- }
1180
- return remaining;
1181
- };
1182
- var isFullyCoveredByRects2 = (target, coveringRects) => {
1183
- return subtractRects2(target, coveringRects).length === 0;
1184
- };
1185
- var OuterLayerContainmentMergeSolver = class extends BaseSolver4 {
1186
- constructor(input) {
1187
- super();
1188
- this.input = input;
1189
- }
1190
- input;
1191
- outputNodes = [];
1192
- promotedNodeIds = /* @__PURE__ */ new Set();
1193
- residualNodeIds = /* @__PURE__ */ new Set();
1194
- _setup() {
1195
- this.outputNodes = this.input.meshNodes.map(cloneNode2);
1196
- this.promotedNodeIds.clear();
1197
- this.residualNodeIds.clear();
1198
- }
1199
- _step() {
1200
- this.outputNodes = this.processOuterLayerContainmentMerges();
1201
- this.solved = true;
1202
- }
1203
- processOuterLayerContainmentMerges() {
1204
- const srj = this.input.simpleRouteJson;
1205
- const layerCount = Math.max(1, srj.layerCount || 1);
1206
- if (layerCount < 3) {
1207
- return this.input.meshNodes.map(cloneNode2);
1208
- }
1209
- const topZ = 0;
1210
- const bottomZ = layerCount - 1;
1211
- const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0);
1212
- const originalNodes = this.input.meshNodes.map(cloneNode2);
1213
- const obstaclesByLayer = this.buildObstaclesByLayer(layerCount);
1214
- const mutableOuterNodes = originalNodes.filter(
1215
- (node) => isFreeNode2(node) && (isSingletonOuterNode(node, topZ) || isSingletonOuterNode(node, bottomZ))
1216
- );
1217
- const immutableNodes = originalNodes.filter(
1218
- (node) => !mutableOuterNodes.includes(node)
1219
- );
1220
- const freeSupportRectsByOuterLayer = /* @__PURE__ */ new Map();
1221
- freeSupportRectsByOuterLayer.set(
1222
- topZ,
1223
- originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(topZ)).map(nodeToRect2)
1224
- );
1225
- freeSupportRectsByOuterLayer.set(
1226
- bottomZ,
1227
- originalNodes.filter((node) => isFreeNode2(node) && node.availableZ.includes(bottomZ)).map(nodeToRect2)
1228
- );
1229
- const promotedNodes = [];
1230
- const promotedRects = [];
1231
- const candidateNodes = mutableOuterNodes.filter(
1232
- (node) => node.width + EPS >= viaMinSize && node.height + EPS >= viaMinSize && rectArea2(nodeToRect2(node)) > MIN_OUTER_LAYER_MERGE_AREA_MM2 + EPS
1233
- ).sort((a, b) => rectArea2(nodeToRect2(b)) - rectArea2(nodeToRect2(a)));
1234
- for (const candidate of candidateNodes) {
1235
- const candidateZ = candidate.availableZ[0];
1236
- const oppositeZ = candidateZ === topZ ? bottomZ : topZ;
1237
- const candidateRect = nodeToRect2(candidate);
1238
- const oppositeSupportRects = freeSupportRectsByOuterLayer.get(oppositeZ) ?? [];
1239
- if (!this.isTransitCompatibleAcrossIntermediateLayers({
1240
- rect: candidateRect,
1241
- fromZ: candidateZ,
1242
- toZ: oppositeZ,
1243
- obstaclesByLayer
1244
- })) {
1245
- continue;
1246
- }
1247
- if (!isFullyCoveredByRects2(candidateRect, oppositeSupportRects)) {
1248
- continue;
1249
- }
1250
- promotedNodes.push({
1251
- ...candidate,
1252
- availableZ: [topZ, bottomZ],
1253
- layer: `z${topZ},${bottomZ}`
1254
- });
1255
- promotedRects.push(candidateRect);
1256
- this.promotedNodeIds.add(candidate.capacityMeshNodeId);
1257
- }
1258
- let nextResidualId = 0;
1259
- const residualNodes = [];
1260
- for (const node of mutableOuterNodes) {
1261
- if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
1262
- continue;
1263
- }
1264
- const nodeRect = nodeToRect2(node);
1265
- const remainingPieces = subtractRects2(nodeRect, promotedRects);
1266
- if (remainingPieces.length === 1 && sameRect2(remainingPieces[0], nodeRect)) {
1267
- residualNodes.push(node);
1268
- continue;
1269
- }
1270
- for (const piece of remainingPieces) {
1271
- const residualNode = cloneNodeWithRect2(
1272
- node,
1273
- piece,
1274
- `${node.capacityMeshNodeId}-outer-merge-${nextResidualId++}`
1275
- );
1276
- residualNodes.push(residualNode);
1277
- this.residualNodeIds.add(residualNode.capacityMeshNodeId);
1278
- }
1279
- }
1280
- return [...immutableNodes, ...promotedNodes, ...residualNodes];
1281
- }
1282
- buildObstaclesByLayer(layerCount) {
1283
- const out = Array.from(
1284
- { length: layerCount },
1285
- () => []
1286
- );
1287
- for (const obstacle of this.input.simpleRouteJson.obstacles ?? []) {
1288
- const baseRect = obstacleToXYRect(obstacle);
1289
- if (!baseRect) continue;
1290
- const rect = padRect(baseRect, this.input.obstacleClearance ?? 0);
1291
- const zLayers = obstacleZs(obstacle, this.input.zIndexByName);
1292
- for (const z of zLayers) {
1293
- if (z < 0 || z >= layerCount) continue;
1294
- out[z].push({ obstacle, rect });
1295
- }
1296
- }
1297
- return out;
1298
- }
1299
- isTransitCompatibleAcrossIntermediateLayers(params) {
1300
- const { rect, fromZ, toZ, obstaclesByLayer } = params;
1301
- const lo = Math.min(fromZ, toZ);
1302
- const hi = Math.max(fromZ, toZ);
1303
- if (hi - lo < 2) return false;
1304
- for (let z = lo + 1; z < hi; z++) {
1305
- const overlapping = (obstaclesByLayer[z] ?? []).filter(
1306
- (entry) => overlaps(entry.rect, rect)
1307
- );
1308
- if (overlapping.length === 0) return false;
1309
- const nonCopperOverlap = overlapping.some(
1310
- (entry) => !entry.obstacle.isCopperPour
1311
- );
1312
- if (nonCopperOverlap) return false;
1313
- const copperRects = overlapping.filter((entry) => entry.obstacle.isCopperPour).map((entry) => entry.rect);
1314
- if (!isFullyCoveredByRects2(rect, copperRects)) {
1315
- return false;
1316
- }
1317
- }
1318
- return true;
1319
- }
1320
- getOutput() {
1321
- return { outputNodes: this.outputNodes };
1322
- }
1323
- visualize() {
1324
- return {
1325
- title: "OuterLayerContainmentMergeSolver",
1326
- coordinateSystem: "cartesian",
1327
- rects: this.outputNodes.map((node) => {
1328
- const colors = getColorForZLayer(node.availableZ);
1329
- const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId);
1330
- const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId);
1331
- return {
1332
- center: node.center,
1333
- width: node.width,
1334
- height: node.height,
1335
- stroke: isPromoted ? "rgba(22, 163, 74, 0.95)" : isResidual ? "rgba(37, 99, 235, 0.95)" : colors.stroke,
1336
- fill: node._containsObstacle ? "rgba(239, 68, 68, 0.35)" : isPromoted ? "rgba(34, 197, 94, 0.28)" : isResidual ? "rgba(59, 130, 246, 0.18)" : colors.fill,
1337
- layer: `z${node.availableZ.join(",")}`,
1338
- label: [
1339
- `node ${node.capacityMeshNodeId}`,
1340
- `z:${node.availableZ.join(",")}`
1341
- ].join("\n")
1342
- };
1343
- }),
1344
- points: [],
1345
- lines: [],
1346
- texts: []
1347
- };
1348
- }
1349
- };
1350
-
1351
- // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
1352
- import {
1353
- BasePipelineSolver as BasePipelineSolver2,
1354
- definePipelineStep as definePipelineStep2
1355
- } from "@tscircuit/solver-utils";
1356
-
1357
- // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
1358
- import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
1359
-
1360
- // lib/solvers/RectDiffSeedingSolver/isPointInPolygon.ts
1361
- function isPointInPolygon(p, polygon) {
1362
- let inside = false;
1363
- for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
1364
- const xi = polygon[i].x, yi = polygon[i].y;
1365
- const xj = polygon[j].x, yj = polygon[j].y;
1366
- const intersect = yi > p.y !== yj > p.y && p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi;
1367
- if (intersect) inside = !inside;
1368
- }
1369
- return inside;
1370
- }
1371
-
1372
- // lib/solvers/RectDiffSeedingSolver/computeInverseRects.ts
1373
- function simplifyPolygon(polygon, precision) {
1374
- const round = (v) => Math.round(v / precision) * precision;
1375
- const seen = /* @__PURE__ */ new Set();
1376
- const result = [];
1377
- for (const p of polygon) {
1378
- const rx = round(p.x);
1379
- const ry = round(p.y);
1380
- const key = `${rx},${ry}`;
1381
- if (!seen.has(key)) {
1382
- seen.add(key);
1383
- result.push({ x: rx, y: ry });
1384
- }
1385
- }
1386
- return result;
1387
- }
1388
- function computeInverseRects(bounds, polygon) {
1389
- if (!polygon || polygon.length < 3) return [];
1390
- const MAX_POLYGON_POINTS = 100;
1391
- const workingPolygon = polygon.length > MAX_POLYGON_POINTS ? simplifyPolygon(
1392
- polygon,
1393
- Math.max(bounds.width, bounds.height) / MAX_POLYGON_POINTS
1394
- ) : polygon;
1395
- const xs = /* @__PURE__ */ new Set([bounds.x, bounds.x + bounds.width]);
1396
- const ys = /* @__PURE__ */ new Set([bounds.y, bounds.y + bounds.height]);
1397
- for (const p of workingPolygon) {
1398
- xs.add(p.x);
1399
- ys.add(p.y);
1400
- }
1401
- const xSorted = Array.from(xs).sort((a, b) => a - b);
1402
- const ySorted = Array.from(ys).sort((a, b) => a - b);
1403
- const rawRects = [];
1404
- for (let i = 0; i < xSorted.length - 1; i++) {
1405
- for (let j = 0; j < ySorted.length - 1; j++) {
1406
- const x0 = xSorted[i];
1407
- const x1 = xSorted[i + 1];
1408
- const y0 = ySorted[j];
1409
- const y1 = ySorted[j + 1];
1410
- const cx = (x0 + x1) / 2;
1411
- const cy = (y0 + y1) / 2;
1412
- if (cx >= bounds.x && cx <= bounds.x + bounds.width && cy >= bounds.y && cy <= bounds.y + bounds.height) {
1413
- if (!isPointInPolygon({ x: cx, y: cy }, polygon)) {
1414
- rawRects.push({ x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
1415
- }
1416
- }
1417
- }
1418
- }
1419
- const finalRects = [];
1420
- rawRects.sort((a, b) => {
1421
- if (Math.abs(a.y - b.y) > EPS) return a.y - b.y;
1422
- return a.x - b.x;
1423
- });
1424
- let current = null;
1425
- for (const r of rawRects) {
1426
- if (!current) {
1427
- current = r;
1428
- continue;
1429
- }
1430
- const sameY = Math.abs(current.y - r.y) < EPS;
1431
- const sameHeight = Math.abs(current.height - r.height) < EPS;
1432
- const touchesX = Math.abs(current.x + current.width - r.x) < EPS;
1433
- if (sameY && sameHeight && touchesX) {
1434
- current.width += r.width;
1435
- } else {
1436
- finalRects.push(current);
1437
- current = r;
1438
- }
1439
- }
1440
- if (current) finalRects.push(current);
1441
- finalRects.sort((a, b) => {
1442
- if (Math.abs(a.x - b.x) > EPS) return a.x - b.x;
1443
- return a.y - b.y;
1444
- });
1445
- const mergedVertical = [];
1446
- current = null;
1447
- for (const r of finalRects) {
1448
- if (!current) {
1449
- current = r;
1450
- continue;
1451
- }
1452
- const sameX = Math.abs(current.x - r.x) < EPS;
1453
- const sameWidth = Math.abs(current.width - r.width) < EPS;
1454
- const touchesY = Math.abs(current.y + current.height - r.y) < EPS;
1455
- if (sameX && sameWidth && touchesY) {
1456
- current.height += r.height;
1457
- } else {
1458
- mergedVertical.push(current);
1459
- current = r;
1460
- }
1461
- }
1462
- if (current) mergedVertical.push(current);
1463
- return mergedVertical;
1464
- }
1465
-
1466
- // lib/utils/isSelfRect.ts
1467
- var EPS5 = 1e-9;
1468
- 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;
1469
-
1470
- // lib/utils/searchStrip.ts
1471
- var searchStripRight = ({
1472
- rect,
1473
- bounds
1474
- }) => ({
1475
- x: rect.x,
1476
- y: rect.y,
1477
- width: bounds.x + bounds.width - rect.x,
1478
- height: rect.height
1479
- });
1480
- var searchStripDown = ({
1481
- rect,
1482
- bounds
1483
- }) => ({
1484
- x: rect.x,
1485
- y: rect.y,
1486
- width: rect.width,
1487
- height: bounds.y + bounds.height - rect.y
1488
- });
1489
- var searchStripLeft = ({
1490
- rect,
1491
- bounds
1492
- }) => ({
1493
- x: bounds.x,
1494
- y: rect.y,
1495
- width: rect.x - bounds.x,
1496
- height: rect.height
1497
- });
1498
- var searchStripUp = ({
1499
- rect,
1500
- bounds
1501
- }) => ({
1502
- x: rect.x,
1503
- y: bounds.y,
1504
- width: rect.width,
1505
- height: rect.y - bounds.y
1506
- });
1507
-
1508
- // lib/utils/expandRectFromSeed.ts
1509
- var quantize = (value, precision = 1e-6) => Math.round(value / precision) * precision;
1510
- var quantizeRect = (rect) => ({
1511
- x: quantize(rect.x),
1512
- y: quantize(rect.y),
1513
- width: quantize(rect.width),
1514
- height: quantize(rect.height)
1515
- });
1516
- function maxExpandRight(params) {
1517
- const { r, bounds, blockers, maxAspect } = params;
1518
- let maxWidth = bounds.x + bounds.width - r.x;
1519
- for (const b of blockers) {
1520
- const verticallyOverlaps = r.y + r.height > b.y + EPS && b.y + b.height > r.y + EPS;
1521
- if (verticallyOverlaps) {
1522
- if (gte(b.x, r.x + r.width)) {
1523
- maxWidth = Math.min(maxWidth, b.x - r.x);
1524
- } else if (b.x + b.width > r.x + r.width - EPS && b.x < r.x + r.width + EPS) {
1525
- return 0;
1526
- }
1527
- }
1528
- }
1529
- let e = Math.max(0, maxWidth - r.width);
1530
- if (e <= 0) return 0;
1531
- if (maxAspect != null) {
1532
- const w = r.width, h = r.height;
1533
- if (w >= h) e = Math.min(e, maxAspect * h - w);
1534
- }
1535
- return Math.max(0, e);
1536
- }
1537
- function maxExpandDown(params) {
1538
- const { r, bounds, blockers, maxAspect } = params;
1539
- let maxHeight = bounds.y + bounds.height - r.y;
1540
- for (const b of blockers) {
1541
- const horizOverlaps = r.x + r.width > b.x + EPS && b.x + b.width > r.x + EPS;
1542
- if (horizOverlaps) {
1543
- if (gte(b.y, r.y + r.height)) {
1544
- maxHeight = Math.min(maxHeight, b.y - r.y);
1545
- } else if (b.y + b.height > r.y + r.height - EPS && b.y < r.y + r.height + EPS) {
1546
- return 0;
1547
- }
1548
- }
1549
- }
1550
- let e = Math.max(0, maxHeight - r.height);
1551
- if (e <= 0) return 0;
1552
- if (maxAspect != null) {
1553
- const w = r.width, h = r.height;
1554
- if (h >= w) e = Math.min(e, maxAspect * w - h);
1555
- }
1556
- return Math.max(0, e);
1557
- }
1558
- function maxExpandLeft(params) {
1559
- const { r, bounds, blockers, maxAspect } = params;
1560
- let minX = bounds.x;
1561
- for (const b of blockers) {
1562
- const verticallyOverlaps = r.y + r.height > b.y + EPS && b.y + b.height > r.y + EPS;
1563
- if (verticallyOverlaps) {
1564
- if (lte(b.x + b.width, r.x)) {
1565
- minX = Math.max(minX, b.x + b.width);
1566
- } else if (b.x < r.x + EPS && b.x + b.width > r.x - EPS) {
1567
- return 0;
1568
- }
1569
- }
1570
- }
1571
- let e = Math.max(0, r.x - minX);
1572
- if (e <= 0) return 0;
1573
- if (maxAspect != null) {
1574
- const w = r.width, h = r.height;
1575
- if (w >= h) e = Math.min(e, maxAspect * h - w);
1576
- }
1577
- return Math.max(0, e);
1578
- }
1579
- function maxExpandUp(params) {
1580
- const { r, bounds, blockers, maxAspect } = params;
1581
- let minY = bounds.y;
1582
- for (const b of blockers) {
1583
- const horizOverlaps = r.x + r.width > b.x + EPS && b.x + b.width > r.x + EPS;
1584
- if (horizOverlaps) {
1585
- if (lte(b.y + b.height, r.y)) {
1586
- minY = Math.max(minY, b.y + b.height);
1587
- } else if (b.y < r.y + EPS && b.y + b.height > r.y - EPS) {
1588
- return 0;
1589
- }
1590
- }
1591
- }
1592
- let e = Math.max(0, r.y - minY);
1593
- if (e <= 0) return 0;
1594
- if (maxAspect != null) {
1595
- const w = r.width, h = r.height;
1596
- if (h >= w) e = Math.min(e, maxAspect * w - h);
1597
- }
1598
- return Math.max(0, e);
1599
- }
1600
- var addBlocker = (params) => {
1601
- const { rect, seen, blockers } = params;
1602
- if (seen.has(rect)) return;
1603
- seen.add(rect);
1604
- blockers.push(rect);
1605
- };
1606
- var toQueryRect = (params) => {
1607
- const { bounds, rect } = params;
1608
- const minX = Math.max(bounds.x, rect.x);
1609
- const minY = Math.max(bounds.y, rect.y);
1610
- const maxX = Math.min(bounds.x + bounds.width, rect.x + rect.width);
1611
- const maxY = Math.min(bounds.y + bounds.height, rect.y + rect.height);
1612
- if (maxX <= minX + EPS || maxY <= minY + EPS) return null;
1613
- return { minX, minY, maxX, maxY };
1614
- };
1615
- function expandRectFromSeed(params) {
1616
- const {
1617
- startX,
1618
- startY,
1619
- gridSize,
1620
- bounds,
1621
- obsticalIndexByLayer,
1622
- placedIndexByLayer,
1623
- initialCellRatio,
1624
- maxAspectRatio,
1625
- minReq
1626
- } = params;
1627
- const minSide = Math.max(1e-9, gridSize * initialCellRatio);
1628
- const initialW = Math.max(minSide, minReq.width);
1629
- const initialH = Math.max(minSide, minReq.height);
1630
- const blockers = [];
1631
- const seen = /* @__PURE__ */ new Set();
1632
- const totalLayers = placedIndexByLayer.length;
1633
- const collectBlockers = (searchRect) => {
1634
- const query = toQueryRect({ bounds, rect: searchRect });
1635
- if (!query) return;
1636
- for (const z of params.zLayers) {
1637
- const blockersIndex = obsticalIndexByLayer[z];
1638
- if (blockersIndex) {
1639
- for (const entry of blockersIndex.search(query))
1640
- addBlocker({ rect: entry, seen, blockers });
1641
- }
1642
- const placedLayer = placedIndexByLayer[z];
1643
- if (placedLayer) {
1644
- for (const entry of placedLayer.search(query)) {
1645
- const isFullStack = entry.zLayers.length >= totalLayers;
1646
- if (!isFullStack) continue;
1647
- if (isSelfRect({
1648
- rect: entry,
1649
- startX,
1650
- startY,
1651
- initialW,
1652
- initialH
1653
- }))
1654
- continue;
1655
- addBlocker({ rect: entry, seen, blockers });
1656
- }
1657
- }
1658
- }
1659
- };
1660
- const strategies = [
1661
- { ox: 0, oy: 0 },
1662
- { ox: -initialW, oy: 0 },
1663
- { ox: 0, oy: -initialH },
1664
- { ox: -initialW, oy: -initialH },
1665
- { ox: -initialW / 2, oy: -initialH / 2 }
1666
- ];
1667
- let best = null;
1668
- let bestArea = 0;
1669
- STRATS: for (const s of strategies) {
1670
- let r = {
1671
- x: startX + s.ox,
1672
- y: startY + s.oy,
1673
- width: initialW,
1674
- height: initialH
1675
- };
1676
- collectBlockers(r);
1677
- if (lt(r.x, bounds.x) || lt(r.y, bounds.y) || gt(r.x + r.width, bounds.x + bounds.width) || gt(r.y + r.height, bounds.y + bounds.height)) {
1678
- continue;
1679
- }
1680
- for (const b of blockers) if (overlaps(r, b)) continue STRATS;
1681
- const MIN_EXPANSION = 1e-6;
1682
- const MAX_ITERATIONS = 1e3;
1683
- let improved = true;
1684
- let iterations = 0;
1685
- while (improved && iterations < MAX_ITERATIONS) {
1686
- iterations++;
1687
- improved = false;
1688
- const commonParams = { bounds, blockers, maxAspect: maxAspectRatio };
1689
- collectBlockers(searchStripRight({ rect: r, bounds }));
1690
- const eR = maxExpandRight({ ...commonParams, r });
1691
- if (eR > MIN_EXPANSION) {
1692
- r = { ...r, width: r.width + eR };
1693
- collectBlockers(r);
1694
- improved = true;
1695
- }
1696
- collectBlockers(searchStripDown({ rect: r, bounds }));
1697
- const eD = maxExpandDown({ ...commonParams, r });
1698
- if (eD > MIN_EXPANSION) {
1699
- r = { ...r, height: r.height + eD };
1700
- collectBlockers(r);
1701
- improved = true;
1702
- }
1703
- collectBlockers(searchStripLeft({ rect: r, bounds }));
1704
- const eL = maxExpandLeft({ ...commonParams, r });
1705
- if (eL > MIN_EXPANSION) {
1706
- r = { x: r.x - eL, y: r.y, width: r.width + eL, height: r.height };
1707
- collectBlockers(r);
1708
- improved = true;
1709
- }
1710
- collectBlockers(searchStripUp({ rect: r, bounds }));
1711
- const eU = maxExpandUp({ ...commonParams, r });
1712
- if (eU > MIN_EXPANSION) {
1713
- r = { x: r.x, y: r.y - eU, width: r.width, height: r.height + eU };
1714
- collectBlockers(r);
1715
- improved = true;
1716
- }
1717
- }
1718
- if (r.width + EPS >= minReq.width && r.height + EPS >= minReq.height) {
1719
- const area = r.width * r.height;
1720
- if (area > bestArea) {
1721
- best = quantizeRect(r);
1722
- bestArea = area;
1723
- }
1724
- }
1725
- }
1726
- return best;
1727
- }
1728
-
1729
- // lib/solvers/RectDiffSeedingSolver/computeDefaultGridSizes.ts
1730
- function computeDefaultGridSizes(bounds) {
1731
- const ref = Math.max(bounds.width, bounds.height);
1732
- return [ref / 8, ref / 16, ref / 32];
1733
- }
1734
-
1735
- // lib/utils/isFullyOccupiedAtPoint.ts
1736
- import "rbush";
1737
- function isFullyOccupiedAtPoint(params) {
1738
- const query = {
1739
- minX: params.point.x,
1740
- minY: params.point.y,
1741
- maxX: params.point.x,
1742
- maxY: params.point.y
1743
- };
1744
- for (let z = 0; z < params.layerCount; z++) {
1745
- const obstacleIdx = params.obstacleIndexByLayer[z];
1746
- const hasObstacle = !!obstacleIdx && obstacleIdx.collides(query);
1747
- const placedIdx = params.placedIndexByLayer[z];
1748
- const hasPlaced = !!placedIdx && placedIdx.collides(query);
1749
- if (!hasObstacle && !hasPlaced) return false;
1750
- }
1751
- return true;
1752
- }
1753
-
1754
- // lib/solvers/RectDiffSeedingSolver/longestFreeSpanAroundZ.ts
1755
- function longestFreeSpanAroundZ(params) {
1756
- const {
1757
- x,
1758
- y,
1759
- z,
1760
- layerCount,
1761
- minSpan,
1762
- maxSpan,
1763
- obstacleIndexByLayer,
1764
- additionalBlockersByLayer
1765
- } = params;
1766
- const isFreeAt = (layer) => {
1767
- const query = {
1768
- minX: x,
1769
- minY: y,
1770
- maxX: x,
1771
- maxY: y
1772
- };
1773
- const obstacleIdx = obstacleIndexByLayer[layer];
1774
- if (obstacleIdx && obstacleIdx.collides(query)) return false;
1775
- const extras = additionalBlockersByLayer?.[layer] ?? [];
1776
- return !extras.some((b) => containsPoint(b, { x, y }));
1777
- };
1778
- let lo = z;
1779
- let hi = z;
1780
- while (lo - 1 >= 0 && isFreeAt(lo - 1)) lo--;
1781
- while (hi + 1 < layerCount && isFreeAt(hi + 1)) hi++;
1782
- if (typeof maxSpan === "number") {
1783
- const target = clamp(maxSpan, 1, layerCount);
1784
- while (hi - lo + 1 > target) {
1785
- if (z - lo > hi - z) lo++;
1786
- else hi--;
1787
- }
1788
- }
1789
- const res = [];
1790
- for (let i = lo; i <= hi; i++) res.push(i);
1791
- return res.length >= minSpan ? res : [];
1792
- }
1793
-
1794
- // lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts
1795
- var quantize2 = (value, precision = 1e-6) => Math.round(value / precision) * precision;
1796
- function computeCandidates3D(params) {
1797
- const {
1798
- bounds,
1799
- gridSize,
1800
- layerCount,
1801
- obstacleIndexByLayer,
1802
- placedIndexByLayer,
1803
- hardPlacedByLayer
1804
- } = params;
1805
- const out = /* @__PURE__ */ new Map();
1806
- const hardRectsByLayer = Array.from({ length: layerCount }, (_, z) => [
1807
- ...obstacleIndexByLayer[z]?.all() ?? [],
1808
- ...hardPlacedByLayer[z] ?? []
1809
- ]);
1810
- for (let x = bounds.x; x < bounds.x + bounds.width; x += gridSize) {
1811
- for (let y = bounds.y; y < bounds.y + bounds.height; y += gridSize) {
1812
- 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) {
1813
- continue;
1814
- }
1815
- if (isFullyOccupiedAtPoint({
1816
- layerCount,
1817
- obstacleIndexByLayer,
1818
- placedIndexByLayer,
1819
- point: { x, y }
1820
- }))
1821
- continue;
1822
- let bestSpan = [];
1823
- let bestZ = 0;
1824
- for (let z = 0; z < layerCount; z++) {
1825
- const s = longestFreeSpanAroundZ({
1826
- x,
1827
- y,
1828
- z,
1829
- layerCount,
1830
- minSpan: 1,
1831
- maxSpan: void 0,
1832
- obstacleIndexByLayer,
1833
- additionalBlockersByLayer: hardPlacedByLayer
1834
- });
1835
- if (s.length > bestSpan.length) {
1836
- bestSpan = s;
1837
- bestZ = z;
1838
- }
1839
- }
1840
- const anchorZ = bestSpan.length ? bestSpan[Math.floor(bestSpan.length / 2)] : bestZ;
1841
- const hardAtZ = hardRectsByLayer[anchorZ] ?? [];
1842
- let d = distancePointToRectEdges({ x, y }, bounds);
1843
- for (const blocker of hardAtZ) {
1844
- d = Math.min(d, distancePointToRectEdges({ x, y }, blocker));
1845
- }
1846
- const distance = quantize2(d);
1847
- const k = `${x.toFixed(6)}|${y.toFixed(6)}`;
1848
- const cand = {
1849
- x,
1850
- y,
1851
- z: anchorZ,
1852
- distance,
1853
- zSpanLen: bestSpan.length
1854
- };
1855
- const prev = out.get(k);
1856
- if (!prev || cand.zSpanLen > (prev.zSpanLen ?? 0) || cand.zSpanLen === prev.zSpanLen && cand.distance > prev.distance) {
1857
- out.set(k, cand);
1858
- }
1859
- }
1860
- }
1861
- const arr = Array.from(out.values());
1862
- arr.sort(
1863
- (a, b) => b.zSpanLen - a.zSpanLen || b.distance - a.distance || a.z - b.z || a.x - b.x || a.y - b.y
1864
- );
1865
- return arr;
1866
- }
1867
-
1868
- // lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts
1869
- var quantize3 = (value, precision = 1e-6) => Math.round(value / precision) * precision;
1870
- var toRect = (rect) => "minX" in rect ? {
1871
- x: rect.minX,
1872
- y: rect.minY,
1873
- width: rect.maxX - rect.minX,
1874
- height: rect.maxY - rect.minY
1875
- } : rect;
1876
- function computeUncoveredSegments(params) {
1877
- const { lineStart, lineEnd, coveringIntervals, minSegmentLength } = params;
1878
- const lineStartQ = quantize3(lineStart);
1879
- const lineEndQ = quantize3(lineEnd);
1880
- const normalizedIntervals = coveringIntervals.map((i) => {
1881
- const s = quantize3(i.start);
1882
- const e = quantize3(i.end);
1883
- return { start: Math.min(s, e), end: Math.max(s, e) };
1884
- }).filter((i) => i.end > i.start + EPS);
1885
- if (normalizedIntervals.length === 0) {
1886
- const center = (lineStartQ + lineEndQ) / 2;
1887
- return [{ start: lineStartQ, end: lineEndQ, center }];
1888
- }
1889
- const sorted = [...normalizedIntervals].sort((a, b) => a.start - b.start);
1890
- const merged = [];
1891
- let current = { ...sorted[0] };
1892
- for (let i = 1; i < sorted.length; i++) {
1893
- const interval = sorted[i];
1894
- if (interval.start <= current.end + EPS) {
1895
- current.end = Math.max(current.end, interval.end);
1896
- } else {
1897
- merged.push(current);
1898
- current = { ...interval };
1899
- }
1900
- }
1901
- merged.push(current);
1902
- const uncovered = [];
1903
- if (merged[0].start > lineStartQ + EPS) {
1904
- const start = lineStartQ;
1905
- const end = merged[0].start;
1906
- if (end - start >= minSegmentLength) {
1907
- uncovered.push({ start, end, center: (start + end) / 2 });
1908
- }
1909
- }
1910
- for (let i = 0; i < merged.length - 1; i++) {
1911
- const start = merged[i].end;
1912
- const end = merged[i + 1].start;
1913
- if (end - start >= minSegmentLength) {
1914
- uncovered.push({ start, end, center: (start + end) / 2 });
1915
- }
1916
- }
1917
- if (merged[merged.length - 1].end < lineEndQ - EPS) {
1918
- const start = merged[merged.length - 1].end;
1919
- const end = lineEndQ;
1920
- if (end - start >= minSegmentLength) {
1921
- uncovered.push({ start, end, center: (start + end) / 2 });
1922
- }
1923
- }
1924
- return uncovered;
1925
- }
1926
- function computeEdgeCandidates3D(params) {
1927
- const {
1928
- bounds,
1929
- minSize,
1930
- layerCount,
1931
- obstacleIndexByLayer,
1932
- placedIndexByLayer,
1933
- hardPlacedByLayer
1934
- } = params;
1935
- const out = [];
1936
- const \u03B4 = Math.max(minSize * 0.15, EPS * 3);
1937
- const dedup = /* @__PURE__ */ new Set();
1938
- const hardRectsByLayer = Array.from(
1939
- { length: layerCount },
1940
- (_, z) => [
1941
- ...obstacleIndexByLayer[z]?.all() ?? [],
1942
- ...hardPlacedByLayer[z] ?? []
1943
- ].map(toRect)
1944
- );
1945
- const key = (p) => `${p.z}|${p.x.toFixed(6)}|${p.y.toFixed(6)}`;
1946
- function fullyOcc(p) {
1947
- return isFullyOccupiedAtPoint({
1948
- layerCount,
1949
- obstacleIndexByLayer,
1950
- placedIndexByLayer,
1951
- point: p
1952
- });
1953
- }
1954
- function pushIfFree(p) {
1955
- const qx = quantize3(p.x);
1956
- const qy = quantize3(p.y);
1957
- const { z } = p;
1958
- const x = qx;
1959
- const y = qy;
1960
- if (x < bounds.x + EPS || y < bounds.y + EPS || x > bounds.x + bounds.width - EPS || y > bounds.y + bounds.height - EPS)
1961
- return;
1962
- if (fullyOcc({ x, y })) return;
1963
- const hard = hardRectsByLayer[z] ?? [];
1964
- let d = distancePointToRectEdges({ x, y }, bounds);
1965
- for (const blocker of hard) {
1966
- d = Math.min(d, distancePointToRectEdges({ x, y }, blocker));
1967
- }
1968
- const distance = quantize3(d);
1969
- const k = key({ x, y, z });
1970
- if (dedup.has(k)) return;
1971
- dedup.add(k);
1972
- const span = longestFreeSpanAroundZ({
1973
- x,
1974
- y,
1975
- z,
1976
- layerCount,
1977
- minSpan: 1,
1978
- maxSpan: void 0,
1979
- obstacleIndexByLayer,
1980
- additionalBlockersByLayer: hardPlacedByLayer
1981
- });
1982
- out.push({
1983
- x,
1984
- y,
1985
- z,
1986
- distance,
1987
- zSpanLen: span.length,
1988
- isEdgeSeed: true
1989
- });
1990
- }
1991
- for (let z = 0; z < layerCount; z++) {
1992
- const blockers = (hardRectsByLayer[z] ?? []).map((b) => ({
1993
- x: quantize3(b.x),
1994
- y: quantize3(b.y),
1995
- width: quantize3(b.width),
1996
- height: quantize3(b.height)
1997
- }));
1998
- const corners = [
1999
- { x: bounds.x + \u03B4, y: bounds.y + \u03B4 },
2000
- // top-left
2001
- { x: bounds.x + bounds.width - \u03B4, y: bounds.y + \u03B4 },
2002
- // top-right
2003
- { x: bounds.x + \u03B4, y: bounds.y + bounds.height - \u03B4 },
2004
- // bottom-left
2005
- { x: bounds.x + bounds.width - \u03B4, y: bounds.y + bounds.height - \u03B4 }
2006
- // bottom-right
2007
- ];
2008
- for (const corner of corners) {
2009
- pushIfFree({ x: corner.x, y: corner.y, z });
2010
- }
2011
- const topY = bounds.y + \u03B4;
2012
- const topCovering = blockers.filter((b) => b.y <= topY && b.y + b.height >= topY).map((b) => ({
2013
- start: Math.max(bounds.x, b.x),
2014
- end: Math.min(bounds.x + bounds.width, b.x + b.width)
2015
- }));
2016
- const topUncovered = computeUncoveredSegments({
2017
- lineStart: bounds.x + \u03B4,
2018
- lineEnd: bounds.x + bounds.width - \u03B4,
2019
- coveringIntervals: topCovering,
2020
- minSegmentLength: minSize * 0.5
2021
- });
2022
- for (const seg of topUncovered) {
2023
- const segLen = seg.end - seg.start;
2024
- if (segLen >= minSize) {
2025
- pushIfFree({ x: seg.center, y: topY, z });
2026
- if (segLen > minSize * 1.5) {
2027
- pushIfFree({ x: seg.start + minSize * 0.4, y: topY, z });
2028
- pushIfFree({ x: seg.end - minSize * 0.4, y: topY, z });
2029
- }
2030
- }
2031
- }
2032
- const bottomY = bounds.y + bounds.height - \u03B4;
2033
- const bottomCovering = blockers.filter((b) => b.y <= bottomY && b.y + b.height >= bottomY).map((b) => ({
2034
- start: Math.max(bounds.x, b.x),
2035
- end: Math.min(bounds.x + bounds.width, b.x + b.width)
2036
- }));
2037
- const bottomUncovered = computeUncoveredSegments({
2038
- lineStart: bounds.x + \u03B4,
2039
- lineEnd: bounds.x + bounds.width - \u03B4,
2040
- coveringIntervals: bottomCovering,
2041
- minSegmentLength: minSize * 0.5
2042
- });
2043
- for (const seg of bottomUncovered) {
2044
- const segLen = seg.end - seg.start;
2045
- if (segLen >= minSize) {
2046
- pushIfFree({ x: seg.center, y: bottomY, z });
2047
- if (segLen > minSize * 1.5) {
2048
- pushIfFree({ x: seg.start + minSize * 0.4, y: bottomY, z });
2049
- pushIfFree({ x: seg.end - minSize * 0.4, y: bottomY, z });
2050
- }
2051
- }
2052
- }
2053
- const leftX = bounds.x + \u03B4;
2054
- const leftCovering = blockers.filter((b) => b.x <= leftX && b.x + b.width >= leftX).map((b) => ({
2055
- start: Math.max(bounds.y, b.y),
2056
- end: Math.min(bounds.y + bounds.height, b.y + b.height)
2057
- }));
2058
- const leftUncovered = computeUncoveredSegments({
2059
- lineStart: bounds.y + \u03B4,
2060
- lineEnd: bounds.y + bounds.height - \u03B4,
2061
- coveringIntervals: leftCovering,
2062
- minSegmentLength: minSize * 0.5
2063
- });
2064
- for (const seg of leftUncovered) {
2065
- const segLen = seg.end - seg.start;
2066
- if (segLen >= minSize) {
2067
- pushIfFree({ x: leftX, y: seg.center, z });
2068
- if (segLen > minSize * 1.5) {
2069
- pushIfFree({ x: leftX, y: seg.start + minSize * 0.4, z });
2070
- pushIfFree({ x: leftX, y: seg.end - minSize * 0.4, z });
2071
- }
2072
- }
2073
- }
2074
- const rightX = bounds.x + bounds.width - \u03B4;
2075
- const rightCovering = blockers.filter((b) => b.x <= rightX && b.x + b.width >= rightX).map((b) => ({
2076
- start: Math.max(bounds.y, b.y),
2077
- end: Math.min(bounds.y + bounds.height, b.y + b.height)
2078
- }));
2079
- const rightUncovered = computeUncoveredSegments({
2080
- lineStart: bounds.y + \u03B4,
2081
- lineEnd: bounds.y + bounds.height - \u03B4,
2082
- coveringIntervals: rightCovering,
2083
- minSegmentLength: minSize * 0.5
2084
- });
2085
- for (const seg of rightUncovered) {
2086
- const segLen = seg.end - seg.start;
2087
- if (segLen >= minSize) {
2088
- pushIfFree({ x: rightX, y: seg.center, z });
2089
- if (segLen > minSize * 1.5) {
2090
- pushIfFree({ x: rightX, y: seg.start + minSize * 0.4, z });
2091
- pushIfFree({ x: rightX, y: seg.end - minSize * 0.4, z });
2092
- }
2093
- }
2094
- }
2095
- for (const b of blockers) {
2096
- const obLeftX = b.x - \u03B4;
2097
- if (obLeftX > bounds.x + EPS && obLeftX < bounds.x + bounds.width - EPS) {
2098
- const obLeftCovering = blockers.filter(
2099
- (bl) => bl !== b && bl.x <= obLeftX && bl.x + bl.width >= obLeftX
2100
- ).map((bl) => ({
2101
- start: Math.max(b.y, bl.y),
2102
- end: Math.min(b.y + b.height, bl.y + bl.height)
2103
- }));
2104
- const obLeftUncovered = computeUncoveredSegments({
2105
- lineStart: b.y,
2106
- lineEnd: b.y + b.height,
2107
- coveringIntervals: obLeftCovering,
2108
- minSegmentLength: minSize * 0.5
2109
- });
2110
- for (const seg of obLeftUncovered) {
2111
- pushIfFree({ x: obLeftX, y: seg.center, z });
2112
- }
2113
- }
2114
- const obRightX = b.x + b.width + \u03B4;
2115
- if (obRightX > bounds.x + EPS && obRightX < bounds.x + bounds.width - EPS) {
2116
- const obRightCovering = blockers.filter(
2117
- (bl) => bl !== b && bl.x <= obRightX && bl.x + bl.width >= obRightX
2118
- ).map((bl) => ({
2119
- start: Math.max(b.y, bl.y),
2120
- end: Math.min(b.y + b.height, bl.y + bl.height)
2121
- }));
2122
- const obRightUncovered = computeUncoveredSegments({
2123
- lineStart: b.y,
2124
- lineEnd: b.y + b.height,
2125
- coveringIntervals: obRightCovering,
2126
- minSegmentLength: minSize * 0.5
2127
- });
2128
- for (const seg of obRightUncovered) {
2129
- pushIfFree({ x: obRightX, y: seg.center, z });
2130
- }
2131
- }
2132
- const obTopY = b.y - \u03B4;
2133
- if (obTopY > bounds.y + EPS && obTopY < bounds.y + bounds.height - EPS) {
2134
- const obTopCovering = blockers.filter(
2135
- (bl) => bl !== b && bl.y <= obTopY && bl.y + bl.height >= obTopY
2136
- ).map((bl) => ({
2137
- start: Math.max(b.x, bl.x),
2138
- end: Math.min(b.x + b.width, bl.x + bl.width)
2139
- }));
2140
- const obTopUncovered = computeUncoveredSegments({
2141
- lineStart: b.x,
2142
- lineEnd: b.x + b.width,
2143
- coveringIntervals: obTopCovering,
2144
- minSegmentLength: minSize * 0.5
2145
- });
2146
- for (const seg of obTopUncovered) {
2147
- pushIfFree({ x: seg.center, y: obTopY, z });
2148
- }
2149
- }
2150
- const obBottomY = b.y + b.height + \u03B4;
2151
- if (obBottomY > bounds.y + EPS && obBottomY < bounds.y + bounds.height - EPS) {
2152
- const obBottomCovering = blockers.filter(
2153
- (bl) => bl !== b && bl.y <= obBottomY && bl.y + bl.height >= obBottomY
2154
- ).map((bl) => ({
2155
- start: Math.max(b.x, bl.x),
2156
- end: Math.min(b.x + b.width, bl.x + bl.width)
2157
- }));
2158
- const obBottomUncovered = computeUncoveredSegments({
2159
- lineStart: b.x,
2160
- lineEnd: b.x + b.width,
2161
- coveringIntervals: obBottomCovering,
2162
- minSegmentLength: minSize * 0.5
2163
- });
2164
- for (const seg of obBottomUncovered) {
2165
- pushIfFree({ x: seg.center, y: obBottomY, z });
2166
- }
2167
- }
2168
- }
2169
- }
2170
- out.sort(
2171
- (a, b) => b.zSpanLen - a.zSpanLen || b.distance - a.distance || a.z - b.z || a.x - b.x || a.y - b.y
2172
- );
2173
- return out;
2174
- }
2175
-
2176
- // lib/utils/buildHardPlacedByLayer.ts
2177
- function allLayerNode(params) {
2178
- const out = Array.from({ length: params.layerCount }, () => []);
2179
- for (const p of params.placed) {
2180
- if (p.zLayers.length >= params.layerCount) {
2181
- for (const z of p.zLayers) out[z].push(p.rect);
2182
- }
2183
- }
2184
- return out;
2185
- }
2186
-
2187
- // lib/utils/rectToTree.ts
2188
- var rectToTree = (rect, opts) => ({
2189
- ...rect,
2190
- minX: rect.x,
2191
- minY: rect.y,
2192
- maxX: rect.x + rect.width,
2193
- maxY: rect.y + rect.height,
2194
- zLayers: opts.zLayers
2195
- });
2196
-
2197
- // lib/utils/resizeSoftOverlaps.ts
2198
- function resizeSoftOverlaps(params, newIndex) {
2199
- const newcomer = params.placed[newIndex];
2200
- const { rect: newR, zLayers: newZs } = newcomer;
2201
- const layerCount = params.layerCount;
2202
- const removeIdx = [];
2203
- const toAdd = [];
2204
- for (let i = 0; i < params.placed.length; i++) {
2205
- if (i === newIndex) continue;
2206
- const old = params.placed[i];
2207
- if (old.zLayers.length >= layerCount) continue;
2208
- const sharedZ = old.zLayers.filter((z) => newZs.includes(z));
2209
- if (sharedZ.length === 0) continue;
2210
- if (!overlaps(old.rect, newR)) continue;
2211
- const parts = subtractRect2D(old.rect, newR);
2212
- removeIdx.push(i);
2213
- const unaffectedZ = old.zLayers.filter((z) => !newZs.includes(z));
2214
- if (unaffectedZ.length > 0) {
2215
- toAdd.push({ rect: old.rect, zLayers: unaffectedZ });
2216
- }
2217
- const minW = Math.min(
2218
- params.options.minSingle.width,
2219
- params.options.minMulti.width
2220
- );
2221
- const minH = Math.min(
2222
- params.options.minSingle.height,
2223
- params.options.minMulti.height
2224
- );
2225
- for (const p of parts) {
2226
- if (p.width + EPS >= minW && p.height + EPS >= minH) {
2227
- toAdd.push({ rect: p, zLayers: sharedZ.slice() });
2228
- }
2229
- }
2230
- }
2231
- const sameRect3 = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
2232
- removeIdx.sort((a, b) => b - a).forEach((idx) => {
2233
- const rem = params.placed.splice(idx, 1)[0];
2234
- if (params.placedIndexByLayer) {
2235
- for (const z of rem.zLayers) {
2236
- const tree = params.placedIndexByLayer[z];
2237
- if (tree)
2238
- tree.remove(
2239
- rectToTree(rem.rect, { zLayers: rem.zLayers }),
2240
- sameRect3
2241
- );
2242
- }
2243
- }
2244
- });
2245
- for (const p of toAdd) {
2246
- params.placed.push(p);
2247
- for (const z of p.zLayers) {
2248
- if (params.placedIndexByLayer) {
2249
- const idx = params.placedIndexByLayer[z];
2250
- if (idx) {
2251
- idx.insert(rectToTree(p.rect, { zLayers: p.zLayers.slice() }));
2252
- }
2253
- }
2254
- }
2255
- }
2256
- }
2257
-
2258
- // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
2259
- import RBush3 from "rbush";
2260
- var RectDiffSeedingSolver = class extends BaseSolver5 {
2261
- constructor(input) {
2262
- super();
2263
- this.input = input;
2264
- }
2265
- input;
2266
- // Engine fields (mirrors initState / engine.ts)
2267
- srj;
2268
- layerNames;
2269
- layerCount;
2270
- bounds;
2271
- options;
2272
- boardVoidRects;
2273
- gridIndex;
2274
- candidates;
2275
- placed;
2276
- placedIndexByLayer;
2277
- hardPlacedByLayer;
2278
- expansionIndex;
2279
- edgeAnalysisDone;
2280
- totalSeedsThisGrid;
2281
- consumedSeedsThisGrid;
2282
- _setup() {
2283
- const srj = this.input.simpleRouteJson;
2284
- const opts = this.input.gridOptions ?? {};
2285
- const precomputed = this.input.layerNames && this.input.zIndexByName;
2286
- const { layerNames, zIndexByName } = precomputed ? {
2287
- layerNames: this.input.layerNames,
2288
- zIndexByName: this.input.zIndexByName
2289
- } : buildZIndexMap({
2290
- obstacles: srj.obstacles,
2291
- layerCount: srj.layerCount
2292
- });
2293
- const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1);
2294
- const bounds = {
2295
- x: srj.bounds.minX,
2296
- y: srj.bounds.minY,
2297
- width: srj.bounds.maxX - srj.bounds.minX,
2298
- height: srj.bounds.maxY - srj.bounds.minY
2299
- };
2300
- const trace = Math.max(0.01, srj.minTraceWidth || 0.15);
2301
- const defaults = {
2302
- gridSizes: [],
2303
- initialCellRatio: 0.2,
2304
- maxAspectRatio: 3,
2305
- minSingle: { width: 2 * trace, height: 2 * trace },
2306
- minMulti: {
2307
- width: 4 * trace,
2308
- height: 4 * trace,
2309
- minLayers: Math.min(2, Math.max(1, srj.layerCount || 1))
2310
- },
2311
- preferMultiLayer: true,
2312
- maxMultiLayerSpan: void 0
2313
- };
2314
- const options = {
2315
- ...defaults,
2316
- ...opts,
2317
- gridSizes: opts.gridSizes ?? // re-use the helper that was previously in engine
2318
- computeDefaultGridSizes(bounds)
2319
- };
2320
- this.srj = srj;
2321
- this.layerNames = layerNames;
2322
- this.layerCount = layerCount;
2323
- this.bounds = bounds;
2324
- this.options = options;
2325
- this.boardVoidRects = this.input.boardVoidRects;
2326
- this.gridIndex = 0;
2327
- this.candidates = [];
2328
- this.placed = [];
2329
- this.placedIndexByLayer = Array.from(
2330
- { length: layerCount },
2331
- () => new RBush3()
2332
- );
2333
- this.hardPlacedByLayer = Array.from({ length: layerCount }, () => []);
2334
- this.expansionIndex = 0;
2335
- this.edgeAnalysisDone = false;
2336
- this.totalSeedsThisGrid = 0;
2337
- this.consumedSeedsThisGrid = 0;
2338
- this.stats = {
2339
- gridIndex: this.gridIndex
2340
- };
2341
- }
2342
- /** Exactly ONE grid candidate step per call. */
2343
- _step() {
2344
- this._stepGrid();
2345
- this.stats.gridIndex = this.gridIndex;
2346
- this.stats.placed = this.placed.length;
2347
- }
2348
- /**
2349
- * One micro-step during the GRID phase: handle exactly one candidate.
2350
- */
2351
- _stepGrid() {
2352
- const {
2353
- gridSizes,
2354
- initialCellRatio,
2355
- maxAspectRatio,
2356
- minSingle,
2357
- minMulti,
2358
- preferMultiLayer,
2359
- maxMultiLayerSpan
2360
- } = this.options;
2361
- const grid = gridSizes[this.gridIndex];
2362
- if (this.candidates.length === 0 && this.consumedSeedsThisGrid === 0) {
2363
- this.candidates = computeCandidates3D({
2364
- bounds: this.bounds,
2365
- gridSize: grid,
2366
- layerCount: this.layerCount,
2367
- hardPlacedByLayer: this.hardPlacedByLayer,
2368
- obstacleIndexByLayer: this.input.obstacleIndexByLayer,
2369
- placedIndexByLayer: this.placedIndexByLayer
2370
- });
2371
- this.totalSeedsThisGrid = this.candidates.length;
2372
- this.consumedSeedsThisGrid = 0;
2373
- }
2374
- if (this.consumedSeedsThisGrid >= this.candidates.length) {
2375
- if (this.gridIndex + 1 < gridSizes.length) {
2376
- this.gridIndex += 1;
2377
- this.candidates = [];
2378
- this.totalSeedsThisGrid = 0;
2379
- this.consumedSeedsThisGrid = 0;
2380
- return;
2381
- } else {
2382
- if (!this.edgeAnalysisDone) {
2383
- const minSize = Math.min(minSingle.width, minSingle.height);
2384
- this.candidates = computeEdgeCandidates3D({
2385
- bounds: this.bounds,
2386
- minSize,
2387
- layerCount: this.layerCount,
2388
- obstacleIndexByLayer: this.input.obstacleIndexByLayer,
2389
- placedIndexByLayer: this.placedIndexByLayer,
2390
- hardPlacedByLayer: this.hardPlacedByLayer
2391
- });
2392
- this.edgeAnalysisDone = true;
2393
- this.totalSeedsThisGrid = this.candidates.length;
2394
- this.consumedSeedsThisGrid = 0;
2395
- return;
2396
- }
2397
- this.candidates = [];
2398
- this.solved = true;
2399
- this.expansionIndex = 0;
2400
- return;
2401
- }
2402
- }
2403
- const cand = this.candidates[this.consumedSeedsThisGrid++];
2404
- if (isFullyOccupiedAtPoint({
2405
- layerCount: this.layerCount,
2406
- obstacleIndexByLayer: this.input.obstacleIndexByLayer,
2407
- placedIndexByLayer: this.placedIndexByLayer,
2408
- point: { x: cand.x, y: cand.y }
2409
- })) {
2410
- return;
2411
- }
2412
- const span = longestFreeSpanAroundZ({
2413
- x: cand.x,
2414
- y: cand.y,
2415
- z: cand.z,
2416
- layerCount: this.layerCount,
2417
- minSpan: minMulti.minLayers,
2418
- maxSpan: maxMultiLayerSpan,
2419
- obstacleIndexByLayer: this.input.obstacleIndexByLayer,
2420
- additionalBlockersByLayer: this.hardPlacedByLayer
2421
- });
2422
- const attempts = [];
2423
- if (span.length >= minMulti.minLayers) {
2424
- attempts.push({
2425
- kind: "multi",
2426
- layers: span,
2427
- minReq: { width: minMulti.width, height: minMulti.height }
2428
- });
2429
- }
2430
- attempts.push({
2431
- kind: "single",
2432
- layers: [cand.z],
2433
- minReq: { width: minSingle.width, height: minSingle.height }
2434
- });
2435
- const ordered = preferMultiLayer ? attempts : attempts.reverse();
2436
- for (const attempt of ordered) {
2437
- const rect = expandRectFromSeed({
2438
- startX: cand.x,
2439
- startY: cand.y,
2440
- gridSize: grid,
2441
- bounds: this.bounds,
2442
- obsticalIndexByLayer: this.input.obstacleIndexByLayer,
2443
- placedIndexByLayer: this.placedIndexByLayer,
2444
- initialCellRatio,
2445
- maxAspectRatio,
2446
- minReq: attempt.minReq,
2447
- zLayers: attempt.layers
2448
- });
2449
- if (!rect) continue;
2450
- const placed = { rect, zLayers: [...attempt.layers] };
2451
- const newIndex = this.placed.push(placed) - 1;
2452
- for (const z of attempt.layers) {
2453
- const idx = this.placedIndexByLayer[z];
2454
- if (idx) {
2455
- idx.insert(rectToTree(rect, { zLayers: placed.zLayers }));
2456
- }
2457
- }
2458
- resizeSoftOverlaps(
2459
- {
2460
- layerCount: this.layerCount,
2461
- placed: this.placed,
2462
- options: this.options,
2463
- placedIndexByLayer: this.placedIndexByLayer
2464
- },
2465
- newIndex
2466
- );
2467
- this.hardPlacedByLayer = allLayerNode({
2468
- layerCount: this.layerCount,
2469
- placed: this.placed
2470
- });
2471
- return;
2472
- }
2473
- }
2474
- /** Compute solver progress (0 to 1) during GRID phase. */
2475
- computeProgress() {
2476
- if (this.solved) {
2477
- return 1;
2478
- }
2479
- const grids = this.options.gridSizes.length;
2480
- const g = this.gridIndex;
2481
- const base = g / (grids + 1);
2482
- const denom = Math.max(1, this.totalSeedsThisGrid);
2483
- const frac = denom ? this.consumedSeedsThisGrid / denom : 1;
2484
- return Math.min(0.999, base + frac * (1 / (grids + 1)));
2485
- }
2486
- /**
2487
- * Output the intermediate RectDiff engine data to feed into the
2488
- * expansion phase solver.
2489
- */
2490
- getOutput() {
2491
- return {
2492
- layerNames: this.layerNames,
2493
- layerCount: this.layerCount,
2494
- bounds: this.bounds,
2495
- options: this.options,
2496
- boardVoidRects: this.boardVoidRects,
2497
- gridIndex: this.gridIndex,
2498
- candidates: this.candidates,
2499
- placed: this.placed,
2500
- expansionIndex: this.expansionIndex,
2501
- edgeAnalysisDone: this.edgeAnalysisDone,
2502
- totalSeedsThisGrid: this.totalSeedsThisGrid,
2503
- consumedSeedsThisGrid: this.consumedSeedsThisGrid,
2504
- obstacles: this.srj.obstacles,
2505
- obstacleClearance: this.input.obstacleClearance
2506
- };
2507
- }
2508
- /** Visualization focused on the grid seeding phase. */
2509
- visualize() {
2510
- const rects = [];
2511
- const points = [];
2512
- const lines = [];
2513
- const srj = this.srj ?? this.input.simpleRouteJson;
2514
- const boardBounds = {
2515
- minX: srj.bounds.minX,
2516
- maxX: srj.bounds.maxX,
2517
- minY: srj.bounds.minY,
2518
- maxY: srj.bounds.maxY
2519
- };
2520
- if (srj.outline && srj.outline.length > 1) {
2521
- lines.push({
2522
- points: [...srj.outline, srj.outline[0]],
2523
- strokeColor: "#111827",
2524
- strokeWidth: 0.01,
2525
- label: "outline"
2526
- });
2527
- } else {
2528
- rects.push({
2529
- center: {
2530
- x: (boardBounds.minX + boardBounds.maxX) / 2,
2531
- y: (boardBounds.minY + boardBounds.maxY) / 2
2532
- },
2533
- width: boardBounds.maxX - boardBounds.minX,
2534
- height: boardBounds.maxY - boardBounds.minY,
2535
- fill: "none",
2536
- stroke: "#111827",
2537
- label: "board"
2538
- });
2539
- }
2540
- for (const obstacle of srj.obstacles ?? []) {
2541
- if (obstacle.type === "rect" || obstacle.type === "oval") {
2542
- rects.push({
2543
- center: { x: obstacle.center.x, y: obstacle.center.y },
2544
- width: obstacle.width,
2545
- height: obstacle.height,
2546
- fill: "#fee2e2",
2547
- stroke: "#ef4444",
2548
- layer: "obstacle",
2549
- label: "obstacle"
2550
- });
2551
- }
2552
- }
2553
- if (this.boardVoidRects) {
2554
- let outlineBBox = null;
2555
- if (srj.outline && srj.outline.length > 0) {
2556
- const xs = srj.outline.map((p) => p.x);
2557
- const ys = srj.outline.map((p) => p.y);
2558
- const minX = Math.min(...xs);
2559
- const minY = Math.min(...ys);
2560
- outlineBBox = {
2561
- x: minX,
2562
- y: minY,
2563
- width: Math.max(...xs) - minX,
2564
- height: Math.max(...ys) - minY
2565
- };
2566
- }
2567
- for (const r of this.boardVoidRects) {
2568
- if (outlineBBox && !overlaps(r, outlineBBox)) {
2569
- continue;
2570
- }
2571
- rects.push({
2572
- center: { x: r.x + r.width / 2, y: r.y + r.height / 2 },
2573
- width: r.width,
2574
- height: r.height,
2575
- fill: "rgba(0, 0, 0, 0.5)",
2576
- stroke: "none",
2577
- label: "void"
2578
- });
2579
- }
2580
- }
2581
- if (this.candidates?.length) {
2582
- for (const cand of this.candidates) {
2583
- points.push({
2584
- x: cand.x,
2585
- y: cand.y,
2586
- label: `z:${cand.z}`
2587
- });
2588
- }
2589
- }
2590
- if (this.placed?.length) {
2591
- for (const placement of this.placed) {
2592
- const colors = getColorForZLayer(placement.zLayers);
2593
- rects.push({
2594
- center: {
2595
- x: placement.rect.x + placement.rect.width / 2,
2596
- y: placement.rect.y + placement.rect.height / 2
2597
- },
2598
- width: placement.rect.width,
2599
- height: placement.rect.height,
2600
- fill: colors.fill,
2601
- stroke: colors.stroke,
2602
- layer: `z${placement.zLayers.join(",")}`,
2603
- label: `free
2604
- z:${placement.zLayers.join(",")}`
2605
- });
2606
- }
2607
- }
2608
- return {
2609
- title: "RectDiff Grid",
2610
- coordinateSystem: "cartesian",
2611
- rects,
2612
- points,
2613
- lines
2614
- };
2615
- }
2616
- };
2617
-
2618
- // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
2619
- import { BaseSolver as BaseSolver6 } from "@tscircuit/solver-utils";
2620
-
2621
- // lib/utils/finalizeRects.ts
2622
- function finalizeRects(params) {
2623
- const out = params.placed.map((p) => ({
2624
- minX: p.rect.x,
2625
- minY: p.rect.y,
2626
- maxX: p.rect.x + p.rect.width,
2627
- maxY: p.rect.y + p.rect.height,
2628
- zLayers: [...p.zLayers].sort((a, b) => a - b)
2629
- }));
2630
- const layersByKey = /* @__PURE__ */ new Map();
2631
- for (const obstacle of params.obstacles ?? []) {
2632
- const baseRect = obstacleToXYRect(obstacle);
2633
- if (!baseRect) continue;
2634
- const rect = params.obstacleClearance ? {
2635
- x: baseRect.x - params.obstacleClearance,
2636
- y: baseRect.y - params.obstacleClearance,
2637
- width: baseRect.width + 2 * params.obstacleClearance,
2638
- height: baseRect.height + 2 * params.obstacleClearance
2639
- } : baseRect;
2640
- const zLayers = obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, params.zIndexByName);
2641
- const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`;
2642
- let entry = layersByKey.get(key);
2643
- if (!entry) {
2644
- entry = { rect, layers: /* @__PURE__ */ new Set() };
2645
- layersByKey.set(key, entry);
2646
- }
2647
- zLayers.forEach((layer) => entry.layers.add(layer));
2648
- }
2649
- for (const { rect, layers } of layersByKey.values()) {
2650
- out.push({
2651
- minX: rect.x,
2652
- minY: rect.y,
2653
- maxX: rect.x + rect.width,
2654
- maxY: rect.y + rect.height,
2655
- zLayers: Array.from(layers).sort((a, b) => a - b),
2656
- isObstacle: true
2657
- });
2658
- }
2659
- return out;
2660
- }
2661
-
2662
- // lib/solvers/RectDiffExpansionSolver/rectsToMeshNodes.ts
2663
- function rectsToMeshNodes(rects) {
2664
- let id = 0;
2665
- const out = [];
2666
- for (const r of rects) {
2667
- const w = Math.max(0, r.maxX - r.minX);
2668
- const h = Math.max(0, r.maxY - r.minY);
2669
- if (w <= 0 || h <= 0 || r.zLayers.length === 0) continue;
2670
- out.push({
2671
- capacityMeshNodeId: `cmn_${id++}`,
2672
- center: { x: (r.minX + r.maxX) / 2, y: (r.minY + r.maxY) / 2 },
2673
- width: w,
2674
- height: h,
2675
- layer: "top",
2676
- availableZ: r.zLayers.slice(),
2677
- _containsObstacle: r.isObstacle,
2678
- _containsTarget: r.isObstacle
2679
- });
2680
- }
2681
- return out;
2682
- }
2683
-
2684
- // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
2685
- import RBush4 from "rbush";
2686
-
2687
- // lib/utils/sameTreeRect.ts
2688
- var sameTreeRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
2689
-
2690
- // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
2691
- var RectDiffExpansionSolver = class extends BaseSolver6 {
2692
- constructor(input) {
2693
- super();
2694
- this.input = input;
2695
- }
2696
- input;
2697
- placedIndexByLayer = [];
2698
- _meshNodes = [];
2699
- _setup() {
2700
- this.stats = {
2701
- gridIndex: this.input.gridIndex
2702
- };
2703
- this.placedIndexByLayer = Array.from(
2704
- { length: this.input.layerCount },
2705
- () => new RBush4()
2706
- );
2707
- for (const placement of this.input.placed) {
2708
- for (const z of placement.zLayers) {
2709
- const placedIndex = this.placedIndexByLayer[z];
2710
- if (placedIndex)
2711
- placedIndex.insert(
2712
- rectToTree(placement.rect, { zLayers: placement.zLayers })
2713
- );
2714
- }
2715
- }
2716
- }
2717
- _step() {
2718
- if (this.solved) return;
2719
- this._stepExpansion();
2720
- this.stats.gridIndex = this.input.gridIndex;
2721
- this.stats.placed = this.input.placed.length;
2722
- if (this.input.expansionIndex >= this.input.placed.length) {
2723
- this.finalizeIfNeeded();
2724
- }
2725
- }
2726
- _stepExpansion() {
2727
- if (this.input.expansionIndex >= this.input.placed.length) {
2728
- return;
2729
- }
2730
- const idx = this.input.expansionIndex;
2731
- const p = this.input.placed[idx];
2732
- const lastGrid = this.input.options.gridSizes[this.input.options.gridSizes.length - 1];
2733
- const oldRect = p.rect;
2734
- const expanded = expandRectFromSeed({
2735
- startX: p.rect.x + p.rect.width / 2,
2736
- startY: p.rect.y + p.rect.height / 2,
2737
- gridSize: lastGrid,
2738
- bounds: this.input.bounds,
2739
- obsticalIndexByLayer: this.input.obstacleIndexByLayer,
2740
- placedIndexByLayer: this.placedIndexByLayer,
2741
- initialCellRatio: 0,
2742
- maxAspectRatio: null,
2743
- minReq: { width: p.rect.width, height: p.rect.height },
2744
- zLayers: p.zLayers
2745
- });
2746
- if (expanded) {
2747
- this.input.placed[idx] = { rect: expanded, zLayers: p.zLayers };
2748
- for (const z of p.zLayers) {
2749
- const tree = this.placedIndexByLayer[z];
2750
- if (tree) {
2751
- tree.remove(rectToTree(oldRect, { zLayers: p.zLayers }), sameTreeRect);
2752
- tree.insert(rectToTree(expanded, { zLayers: p.zLayers }));
2753
- }
2754
- }
2755
- resizeSoftOverlaps(
2756
- {
2757
- layerCount: this.input.layerCount,
2758
- placed: this.input.placed,
2759
- options: this.input.options,
2760
- placedIndexByLayer: this.placedIndexByLayer
2761
- },
2762
- idx
2763
- );
2764
- }
2765
- this.input.expansionIndex += 1;
2766
- }
2767
- finalizeIfNeeded() {
2768
- if (this.solved) return;
2769
- const rects = finalizeRects({
2770
- placed: this.input.placed,
2771
- obstacles: this.input.obstacles,
2772
- zIndexByName: this.input.zIndexByName,
2773
- boardVoidRects: this.input.boardVoidRects,
2774
- obstacleClearance: this.input.obstacleClearance
2775
- });
2776
- this._meshNodes = rectsToMeshNodes(rects);
2777
- this.solved = true;
2778
- }
2779
- computeProgress() {
2780
- if (this.solved) return 1;
2781
- const grids = this.input.options.gridSizes.length;
2782
- const base = grids / (grids + 1);
2783
- const denom = Math.max(1, this.input.placed.length);
2784
- const frac = denom ? this.input.expansionIndex / denom : 1;
2785
- return Math.min(0.999, base + frac * (1 / (grids + 1)));
2786
- }
2787
- getOutput() {
2788
- if (this.solved) return { meshNodes: this._meshNodes };
2789
- const previewNodes = this.input.placed.map(
2790
- (placement, idx) => ({
2791
- capacityMeshNodeId: `expand-preview-${idx}`,
2792
- center: {
2793
- x: placement.rect.x + placement.rect.width / 2,
2794
- y: placement.rect.y + placement.rect.height / 2
2795
- },
2796
- width: placement.rect.width,
2797
- height: placement.rect.height,
2798
- availableZ: placement.zLayers.slice(),
2799
- layer: `z${placement.zLayers.join(",")}`
2800
- })
2801
- );
2802
- return { meshNodes: previewNodes };
2803
- }
2804
- /** Simple visualization of expanded placements. */
2805
- visualize() {
2806
- const rects = [];
2807
- for (const placement of this.input.placed ?? []) {
2808
- rects.push({
2809
- center: {
2810
- x: placement.rect.x + placement.rect.width / 2,
2811
- y: placement.rect.y + placement.rect.height / 2
2812
- },
2813
- width: placement.rect.width,
2814
- height: placement.rect.height,
2815
- stroke: "rgba(37, 99, 235, 0.9)",
2816
- fill: "rgba(191, 219, 254, 0.5)",
2817
- layer: `z${placement.zLayers.join(",")}`,
2818
- label: `expanded
2819
- z:${placement.zLayers.join(",")}`
2820
- });
2821
- }
2822
- return {
2823
- title: "RectDiff Expansion",
2824
- coordinateSystem: "cartesian",
2825
- rects,
2826
- points: [],
2827
- lines: []
2828
- };
2829
- }
2830
- };
2831
-
2832
- // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
2833
- import "rbush";
2834
-
2835
- // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2836
- import RBush5 from "rbush";
2837
- var buildObstacleIndexesByLayer = (params) => {
2838
- const { srj, boardVoidRects, obstacleClearance } = params;
2839
- const { layerNames, zIndexByName } = buildZIndexMap({
2840
- obstacles: srj.obstacles,
2841
- layerCount: srj.layerCount
2842
- });
2843
- const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1);
2844
- const bounds = {
2845
- x: srj.bounds.minX,
2846
- y: srj.bounds.minY,
2847
- width: srj.bounds.maxX - srj.bounds.minX,
2848
- height: srj.bounds.maxY - srj.bounds.minY
2849
- };
2850
- const obstacleIndexByLayer = Array.from(
2851
- { length: layerCount },
2852
- () => new RBush5()
2853
- );
2854
- const insertObstacle = (rect, z) => {
2855
- const treeRect = {
2856
- ...rect,
2857
- minX: rect.x,
2858
- minY: rect.y,
2859
- maxX: rect.x + rect.width,
2860
- maxY: rect.y + rect.height,
2861
- zLayers: [z]
2862
- };
2863
- obstacleIndexByLayer[z]?.insert(treeRect);
2864
- };
2865
- if (srj.outline && srj.outline.length > 2) {
2866
- for (const voidRect of boardVoidRects ?? []) {
2867
- for (let z = 0; z < layerCount; z++) insertObstacle(voidRect, z);
2868
- }
2869
- }
2870
- for (const obstacle of srj.obstacles ?? []) {
2871
- const rectBase = obstacleToXYRect(obstacle);
2872
- if (!rectBase) continue;
2873
- const rect = padRect(rectBase, obstacleClearance ?? 0);
2874
- const zLayers = obstacleZs(obstacle, zIndexByName);
2875
- const invalidZs = zLayers.filter((z) => z < 0 || z >= layerCount);
2876
- if (invalidZs.length) {
2877
- throw new Error(
2878
- `RectDiff: obstacle uses z-layer indices ${invalidZs.join(",")} outside 0-${layerCount - 1}`
2879
- );
2880
- }
2881
- if ((!obstacle.zLayers || obstacle.zLayers.length === 0) && zLayers.length) {
2882
- obstacle.zLayers = zLayers;
2883
- }
2884
- for (const z of zLayers) insertObstacle(rect, z);
2885
- }
2886
- return { obstacleIndexByLayer, layerNames, zIndexByName };
2887
- };
2888
-
2889
- // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
2890
- var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2891
- rectDiffSeedingSolver;
2892
- rectDiffExpansionSolver;
2893
- obstacleIndexByLayer;
2894
- layerNames;
2895
- zIndexByName;
2896
- constructor(inputProblem) {
2897
- super(inputProblem);
2898
- const { obstacleIndexByLayer, layerNames, zIndexByName } = buildObstacleIndexesByLayer({
2899
- srj: {
2900
- bounds: inputProblem.bounds,
2901
- obstacles: inputProblem.obstacles,
2902
- connections: inputProblem.connections,
2903
- outline: inputProblem.outline?.outline,
2904
- layerCount: inputProblem.layerCount,
2905
- minTraceWidth: inputProblem.minTraceWidth
2906
- },
2907
- boardVoidRects: inputProblem.boardVoidRects,
2908
- obstacleClearance: inputProblem.obstacleClearance
2909
- });
2910
- this.obstacleIndexByLayer = obstacleIndexByLayer;
2911
- this.layerNames = inputProblem.layerNames ?? layerNames;
2912
- this.zIndexByName = inputProblem.zIndexByName ?? zIndexByName;
2913
- }
2914
- pipelineDef = [
2915
- definePipelineStep2(
2916
- "rectDiffSeedingSolver",
2917
- RectDiffSeedingSolver,
2918
- (pipeline) => [
2919
- {
2920
- simpleRouteJson: {
2921
- bounds: pipeline.inputProblem.bounds,
2922
- obstacles: pipeline.inputProblem.obstacles,
2923
- connections: pipeline.inputProblem.connections,
2924
- outline: pipeline.inputProblem.outline?.outline,
2925
- layerCount: pipeline.inputProblem.layerCount,
2926
- minTraceWidth: pipeline.inputProblem.minTraceWidth
2927
- },
2928
- gridOptions: pipeline.inputProblem.gridOptions,
2929
- obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
2930
- boardVoidRects: pipeline.inputProblem.boardVoidRects,
2931
- layerNames: pipeline.layerNames,
2932
- zIndexByName: pipeline.zIndexByName,
2933
- obstacleClearance: pipeline.inputProblem.obstacleClearance
2934
- }
2935
- ]
2936
- ),
2937
- definePipelineStep2(
2938
- "rectDiffExpansionSolver",
2939
- RectDiffExpansionSolver,
2940
- (pipeline) => {
2941
- const output = pipeline.rectDiffSeedingSolver?.getOutput();
2942
- if (!output) {
2943
- throw new Error("RectDiffSeedingSolver did not produce output");
2944
- }
2945
- return [
2946
- {
2947
- layerNames: output.layerNames ?? [],
2948
- boardVoidRects: pipeline.inputProblem.boardVoidRects ?? [],
2949
- layerCount: pipeline.inputProblem.layerCount,
2950
- bounds: output.bounds,
2951
- candidates: output.candidates,
2952
- consumedSeedsThisGrid: output.placed.length,
2953
- totalSeedsThisGrid: output.candidates.length,
2954
- placed: output.placed,
2955
- edgeAnalysisDone: output.edgeAnalysisDone,
2956
- gridIndex: output.gridIndex,
2957
- expansionIndex: output.expansionIndex,
2958
- obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
2959
- options: output.options,
2960
- zIndexByName: pipeline.zIndexByName,
2961
- layerNamesCanonical: pipeline.layerNames,
2962
- obstacles: pipeline.inputProblem.obstacles,
2963
- obstacleClearance: pipeline.inputProblem.obstacleClearance
2964
- }
2965
- ];
2966
- }
2967
- )
2968
- ];
2969
- getConstructorParams() {
2970
- return [this.inputProblem];
2971
- }
2972
- getOutput() {
2973
- if (this.rectDiffExpansionSolver) {
2974
- return this.rectDiffExpansionSolver.getOutput();
2975
- }
2976
- if (this.rectDiffSeedingSolver) {
2977
- const snapshot = this.rectDiffSeedingSolver.getOutput();
2978
- const meshNodes = snapshot.placed.map(
2979
- (placement, idx) => ({
2980
- capacityMeshNodeId: `grid-${idx}`,
2981
- center: {
2982
- x: placement.rect.x + placement.rect.width / 2,
2983
- y: placement.rect.y + placement.rect.height / 2
2984
- },
2985
- width: placement.rect.width,
2986
- height: placement.rect.height,
2987
- availableZ: placement.zLayers,
2988
- layer: `z${placement.zLayers.join(",")}`
2989
- })
2990
- );
2991
- return { meshNodes };
2992
- }
2993
- return { meshNodes: [] };
2994
- }
2995
- visualize() {
2996
- if (this.rectDiffExpansionSolver) {
2997
- return this.rectDiffExpansionSolver.visualize();
2998
- }
2999
- if (this.rectDiffSeedingSolver) {
3000
- return this.rectDiffSeedingSolver.visualize();
3001
- }
3002
- return {
3003
- title: "RectDiff Grid Pipeline",
3004
- coordinateSystem: "cartesian",
3005
- rects: [],
3006
- points: [],
3007
- lines: []
3008
- };
3009
- }
3010
- };
3011
-
3012
- // lib/rectdiff-visualization.ts
3013
- function createBaseVisualization(srj, title = "RectDiff") {
3014
- const rects = [];
3015
- const lines = [];
3016
- const boardBounds = {
3017
- minX: srj.bounds.minX,
3018
- maxX: srj.bounds.maxX,
3019
- minY: srj.bounds.minY,
3020
- maxY: srj.bounds.maxY
3021
- };
3022
- if (srj.outline && srj.outline.length > 1) {
3023
- lines.push({
3024
- points: [...srj.outline, srj.outline[0]],
3025
- strokeColor: "#111827",
3026
- strokeWidth: 0.01,
3027
- label: "outline"
3028
- });
3029
- } else {
3030
- rects.push({
3031
- center: {
3032
- x: (boardBounds.minX + boardBounds.maxX) / 2,
3033
- y: (boardBounds.minY + boardBounds.maxY) / 2
3034
- },
3035
- width: boardBounds.maxX - boardBounds.minX,
3036
- height: boardBounds.maxY - boardBounds.minY,
3037
- fill: "none",
3038
- stroke: "#111827",
3039
- label: "board"
3040
- });
3041
- }
3042
- for (const obstacle of srj.obstacles ?? []) {
3043
- if (obstacle.type === "rect" || obstacle.type === "oval") {
3044
- const layerLabel = (obstacle.zLayers ?? []).join(",") || "all";
3045
- rects.push({
3046
- center: { x: obstacle.center.x, y: obstacle.center.y },
3047
- width: obstacle.width,
3048
- height: obstacle.height,
3049
- fill: "#fee2e2",
3050
- stroke: "#ef4444",
3051
- layer: "obstacle",
3052
- label: `obstacle
3053
- z:${layerLabel}`
3054
- });
3055
- }
3056
- }
3057
- return {
3058
- title,
3059
- coordinateSystem: "cartesian",
3060
- rects,
3061
- points: [],
3062
- lines
3063
- };
3064
- }
3065
-
3066
- // lib/buildFinalRectDiffVisualization.ts
3067
- import { mergeGraphics } from "graphics-debug";
3068
-
3069
- // lib/utils/buildOutlineGraphics.ts
3070
- var buildOutlineGraphics = ({
3071
- srj
3072
- }) => {
3073
- const hasOutline = srj.outline && srj.outline.length > 1;
3074
- const lines = hasOutline ? [
3075
- {
3076
- points: [...srj.outline, srj.outline[0]],
3077
- strokeColor: "#111827",
3078
- strokeWidth: 0.1,
3079
- label: "outline"
3080
- }
3081
- ] : [
3082
- {
3083
- points: [
3084
- { x: srj.bounds.minX, y: srj.bounds.minY },
3085
- { x: srj.bounds.maxX, y: srj.bounds.minY },
3086
- { x: srj.bounds.maxX, y: srj.bounds.maxY },
3087
- { x: srj.bounds.minX, y: srj.bounds.maxY },
3088
- { x: srj.bounds.minX, y: srj.bounds.minY }
3089
- ],
3090
- strokeColor: "#111827",
3091
- strokeWidth: 0.1,
3092
- label: "bounds"
3093
- }
3094
- ];
3095
- return {
3096
- title: "SimpleRoute Outline",
3097
- coordinateSystem: "cartesian",
3098
- lines
3099
- };
3100
- };
3101
-
3102
- // lib/utils/renderObstacleClearance.ts
3103
- var buildObstacleClearanceGraphics = (params) => {
3104
- const { srj, clearance } = params;
3105
- const c = clearance ?? 0;
3106
- if (c <= 0) {
3107
- return {
3108
- title: "Obstacle Clearance",
3109
- coordinateSystem: "cartesian",
3110
- rects: []
3111
- };
3112
- }
3113
- const rects = [];
3114
- for (const obstacle of srj.obstacles ?? []) {
3115
- if (obstacle.type !== "rect" && obstacle.type !== "oval") continue;
3116
- const expanded = {
3117
- x: obstacle.center.x - obstacle.width / 2 - c,
3118
- y: obstacle.center.y - obstacle.height / 2 - c,
3119
- width: obstacle.width + 2 * c,
3120
- height: obstacle.height + 2 * c
3121
- };
3122
- rects.push({
3123
- center: {
3124
- x: expanded.x + expanded.width / 2,
3125
- y: expanded.y + expanded.height / 2
3126
- },
3127
- width: expanded.width,
3128
- height: expanded.height,
3129
- stroke: "rgba(202, 138, 4, 0.9)",
3130
- fill: "rgba(234, 179, 8, 0.15)",
3131
- layer: "obstacle-clearance",
3132
- label: `clearance
3133
- z:${(obstacle.zLayers ?? []).join(",") || "all"}`
3134
- });
3135
- }
3136
- return {
3137
- title: "Obstacle Clearance",
3138
- coordinateSystem: "cartesian",
3139
- rects
3140
- };
3141
- };
3142
-
3143
- // lib/buildFinalRectDiffVisualization.ts
3144
- var buildFinalRectDiffVisualization = ({
3145
- srj,
3146
- meshNodes,
3147
- obstacleClearance
3148
- }) => {
3149
- const outline = buildOutlineGraphics({ srj });
3150
- const clearance = buildObstacleClearanceGraphics({
3151
- srj,
3152
- clearance: obstacleClearance
3153
- });
3154
- const rects = meshNodes.map((node) => ({
3155
- center: node.center,
3156
- width: node.width,
3157
- height: node.height,
3158
- stroke: getColorForZLayer(node.availableZ).stroke,
3159
- fill: node._containsObstacle ? "#fca5a5" : getColorForZLayer(node.availableZ).fill,
3160
- layer: `z${node.availableZ.join(",")}`,
3161
- label: `node ${node.capacityMeshNodeId}
3162
- z:${node.availableZ.join(",")}`
3163
- }));
3164
- const nodesGraphic = {
3165
- title: "RectDiffPipeline - Final",
3166
- coordinateSystem: "cartesian",
3167
- rects,
3168
- lines: [],
3169
- points: [],
3170
- texts: []
3171
- };
3172
- return mergeGraphics(mergeGraphics(nodesGraphic, outline), clearance);
3173
- };
3174
-
3175
- // lib/RectDiffPipeline.ts
3176
- import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
3177
- var RectDiffPipeline = class extends BasePipelineSolver3 {
3178
- rectDiffGridSolverPipeline;
3179
- gapFillSolver;
3180
- outerLayerContainmentMergeSolver;
3181
- adjacentLayerContainmentMergeSolver;
3182
- boardVoidRects;
3183
- zIndexByName;
3184
- layerNames;
3185
- pipelineDef = [
3186
- definePipelineStep3(
3187
- "rectDiffGridSolverPipeline",
3188
- RectDiffGridSolverPipeline,
3189
- (rectDiffPipeline) => [
3190
- {
3191
- bounds: rectDiffPipeline.inputProblem.simpleRouteJson.bounds,
3192
- obstacles: rectDiffPipeline.inputProblem.simpleRouteJson.obstacles,
3193
- connections: rectDiffPipeline.inputProblem.simpleRouteJson.connections,
3194
- outline: rectDiffPipeline.inputProblem.simpleRouteJson.outline ? { outline: rectDiffPipeline.inputProblem.simpleRouteJson.outline } : void 0,
3195
- layerCount: rectDiffPipeline.inputProblem.simpleRouteJson.layerCount,
3196
- gridOptions: rectDiffPipeline.inputProblem.gridOptions,
3197
- boardVoidRects: rectDiffPipeline.boardVoidRects,
3198
- layerNames: rectDiffPipeline.layerNames,
3199
- zIndexByName: rectDiffPipeline.zIndexByName,
3200
- minTraceWidth: rectDiffPipeline.inputProblem.simpleRouteJson.minTraceWidth,
3201
- obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance
3202
- }
3203
- ]
3204
- ),
3205
- definePipelineStep3(
3206
- "gapFillSolver",
3207
- GapFillSolverPipeline,
3208
- (rectDiffPipeline) => [
3209
- {
3210
- meshNodes: rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
3211
- boardVoid: {
3212
- boardVoidRects: rectDiffPipeline.boardVoidRects || [],
3213
- layerCount: rectDiffPipeline.inputProblem.simpleRouteJson.layerCount || 0
3214
- }
3215
- }
3216
- ]
3217
- ),
3218
- definePipelineStep3(
3219
- "outerLayerContainmentMergeSolver",
3220
- OuterLayerContainmentMergeSolver,
3221
- (rectDiffPipeline) => [
3222
- {
3223
- meshNodes: rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ?? rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
3224
- simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
3225
- zIndexByName: rectDiffPipeline.zIndexByName ?? /* @__PURE__ */ new Map(),
3226
- obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance
3227
- }
3228
- ]
3229
- ),
3230
- definePipelineStep3(
3231
- "adjacentLayerContainmentMergeSolver",
3232
- AdjacentLayerContainmentMergeSolver,
3233
- (rectDiffPipeline) => [
3234
- {
3235
- meshNodes: rectDiffPipeline.outerLayerContainmentMergeSolver?.getOutput().outputNodes ?? rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ?? rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
3236
- simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson
3237
- }
3238
- ]
3239
- )
3240
- ];
3241
- _setup() {
3242
- const { zIndexByName, layerNames } = buildZIndexMap({
3243
- obstacles: this.inputProblem.simpleRouteJson.obstacles,
3244
- layerCount: this.inputProblem.simpleRouteJson.layerCount
3245
- });
3246
- this.zIndexByName = zIndexByName;
3247
- this.layerNames = layerNames;
3248
- if (this.inputProblem.simpleRouteJson.outline) {
3249
- this.boardVoidRects = computeInverseRects(
3250
- {
3251
- x: this.inputProblem.simpleRouteJson.bounds.minX,
3252
- y: this.inputProblem.simpleRouteJson.bounds.minY,
3253
- width: this.inputProblem.simpleRouteJson.bounds.maxX - this.inputProblem.simpleRouteJson.bounds.minX,
3254
- height: this.inputProblem.simpleRouteJson.bounds.maxY - this.inputProblem.simpleRouteJson.bounds.minY
3255
- },
3256
- this.inputProblem.simpleRouteJson.outline ?? []
3257
- );
3258
- }
3259
- }
3260
- getConstructorParams() {
3261
- return [this.inputProblem];
3262
- }
3263
- getOutput() {
3264
- const adjacentLayerMergeOutput = this.adjacentLayerContainmentMergeSolver?.getOutput();
3265
- if (adjacentLayerMergeOutput) {
3266
- return { meshNodes: adjacentLayerMergeOutput.outputNodes };
3267
- }
3268
- const outerLayerMergeOutput = this.outerLayerContainmentMergeSolver?.getOutput();
3269
- if (outerLayerMergeOutput) {
3270
- return { meshNodes: outerLayerMergeOutput.outputNodes };
3271
- }
3272
- const gapFillOutput = this.gapFillSolver?.getOutput();
3273
- if (gapFillOutput) {
3274
- return { meshNodes: gapFillOutput.outputNodes };
3275
- }
3276
- if (this.rectDiffGridSolverPipeline) {
3277
- return this.rectDiffGridSolverPipeline.getOutput();
3278
- }
3279
- return { meshNodes: [] };
3280
- }
3281
- initialVisualize() {
3282
- const base = createBaseVisualization(
3283
- this.inputProblem.simpleRouteJson,
3284
- "RectDiffPipeline - Initial"
3285
- );
3286
- const clearance = buildObstacleClearanceGraphics({
3287
- srj: this.inputProblem.simpleRouteJson,
3288
- clearance: this.inputProblem.obstacleClearance
3289
- });
3290
- const initialNodes = this.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [];
3291
- const nodeRects = {
3292
- title: "Initial Nodes",
3293
- coordinateSystem: "cartesian",
3294
- rects: initialNodes.map((node) => ({
3295
- center: node.center,
3296
- width: node.width,
3297
- height: node.height,
3298
- stroke: "rgba(0, 0, 0, 0.3)",
3299
- fill: "rgba(100, 100, 100, 0.1)",
3300
- layer: `z${node.availableZ.join(",")}`,
3301
- label: [
3302
- `node ${node.capacityMeshNodeId}`,
3303
- `z:${node.availableZ.join(",")}`
3304
- ].join("\n")
3305
- }))
3306
- };
3307
- return mergeGraphics2(mergeGraphics2(base, clearance), nodeRects);
3308
- }
3309
- finalVisualize() {
3310
- return buildFinalRectDiffVisualization({
3311
- srj: this.inputProblem.simpleRouteJson,
3312
- meshNodes: this.getOutput().meshNodes,
3313
- obstacleClearance: this.inputProblem.obstacleClearance
3314
- });
3315
- }
3316
- };
3317
- export {
3318
- RectDiffPipeline
3319
- };