calculate-packing 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +49 -28
- package/dist/index.js +1090 -736
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,92 +1,124 @@
|
|
|
1
|
-
// lib/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
if ("computeProgress" in this) {
|
|
44
|
-
this.progress = this.computeProgress();
|
|
1
|
+
// lib/PackSolver/checkOverlapWithPackedComponents.ts
|
|
2
|
+
function checkOverlapWithPackedComponents({
|
|
3
|
+
component,
|
|
4
|
+
packedComponents,
|
|
5
|
+
minGap
|
|
6
|
+
}) {
|
|
7
|
+
for (const componentPad of component.pads) {
|
|
8
|
+
for (const packedComponent of packedComponents) {
|
|
9
|
+
for (const packedPad of packedComponent.pads) {
|
|
10
|
+
const comp1Bounds = {
|
|
11
|
+
left: componentPad.absoluteCenter.x - componentPad.size.x / 2,
|
|
12
|
+
right: componentPad.absoluteCenter.x + componentPad.size.x / 2,
|
|
13
|
+
bottom: componentPad.absoluteCenter.y - componentPad.size.y / 2,
|
|
14
|
+
top: componentPad.absoluteCenter.y + componentPad.size.y / 2
|
|
15
|
+
};
|
|
16
|
+
const comp2Bounds = {
|
|
17
|
+
left: packedPad.absoluteCenter.x - packedPad.size.x / 2,
|
|
18
|
+
right: packedPad.absoluteCenter.x + packedPad.size.x / 2,
|
|
19
|
+
bottom: packedPad.absoluteCenter.y - packedPad.size.y / 2,
|
|
20
|
+
top: packedPad.absoluteCenter.y + packedPad.size.y / 2
|
|
21
|
+
};
|
|
22
|
+
const xOverlap = comp1Bounds.right > comp2Bounds.left && comp2Bounds.right > comp1Bounds.left;
|
|
23
|
+
const yOverlap = comp1Bounds.top > comp2Bounds.bottom && comp2Bounds.top > comp1Bounds.bottom;
|
|
24
|
+
if (xOverlap && yOverlap) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (!xOverlap || !yOverlap) {
|
|
28
|
+
const xGap = xOverlap ? 0 : Math.min(
|
|
29
|
+
Math.abs(comp1Bounds.left - comp2Bounds.right),
|
|
30
|
+
Math.abs(comp2Bounds.left - comp1Bounds.right)
|
|
31
|
+
);
|
|
32
|
+
const yGap = yOverlap ? 0 : Math.min(
|
|
33
|
+
Math.abs(comp1Bounds.bottom - comp2Bounds.top),
|
|
34
|
+
Math.abs(comp2Bounds.bottom - comp1Bounds.top)
|
|
35
|
+
);
|
|
36
|
+
const actualGap = Math.max(xGap, yGap);
|
|
37
|
+
if (actualGap < minGap) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
45
42
|
}
|
|
46
43
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// lib/math/computeNearestPointOnSegmentForSegmentSet.ts
|
|
48
|
+
var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
49
|
+
var add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
50
|
+
var mul = (a, s) => ({ x: a.x * s, y: a.y * s });
|
|
51
|
+
var dot = (a, b) => a.x * b.x + a.y * b.y;
|
|
52
|
+
var clamp = (v, lo = 0, hi = 1) => Math.max(lo, Math.min(hi, v));
|
|
53
|
+
function closestPointOnSegAToSegB(segA, segB) {
|
|
54
|
+
const [p, q] = segA;
|
|
55
|
+
const [r, s] = segB;
|
|
56
|
+
const u = sub(q, p);
|
|
57
|
+
const v = sub(s, r);
|
|
58
|
+
const w0 = sub(p, r);
|
|
59
|
+
const a = dot(u, u);
|
|
60
|
+
const b = dot(u, v);
|
|
61
|
+
const c = dot(v, v);
|
|
62
|
+
const d = dot(u, w0);
|
|
63
|
+
const e = dot(v, w0);
|
|
64
|
+
const EPS = 1e-12;
|
|
65
|
+
const D = a * c - b * b;
|
|
66
|
+
let sN;
|
|
67
|
+
let tN;
|
|
68
|
+
let sD = D;
|
|
69
|
+
let tD = D;
|
|
70
|
+
if (D < EPS) {
|
|
71
|
+
sN = 0;
|
|
72
|
+
sD = 1;
|
|
73
|
+
tN = e;
|
|
74
|
+
tD = c;
|
|
75
|
+
} else {
|
|
76
|
+
sN = b * e - c * d;
|
|
77
|
+
tN = a * e - b * d;
|
|
78
|
+
if (sN < 0) {
|
|
79
|
+
sN = 0;
|
|
80
|
+
tN = e;
|
|
81
|
+
tD = c;
|
|
82
|
+
} else if (sN > sD) {
|
|
83
|
+
sN = sD;
|
|
84
|
+
tN = e + b;
|
|
85
|
+
tD = c;
|
|
58
86
|
}
|
|
59
|
-
const endTime = Date.now();
|
|
60
|
-
this.timeToSolve = endTime - startTime;
|
|
61
|
-
}
|
|
62
|
-
visualize() {
|
|
63
|
-
return {
|
|
64
|
-
lines: [],
|
|
65
|
-
points: [],
|
|
66
|
-
rects: [],
|
|
67
|
-
circles: []
|
|
68
|
-
};
|
|
69
87
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
if (tN < 0) {
|
|
89
|
+
tN = 0;
|
|
90
|
+
sN = clamp(-d, 0, a);
|
|
91
|
+
sD = a;
|
|
92
|
+
} else if (tN > tD) {
|
|
93
|
+
tN = tD;
|
|
94
|
+
sN = clamp(-d + b, 0, a);
|
|
95
|
+
sD = a;
|
|
76
96
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
const sParam = sD > EPS ? sN / sD : 0;
|
|
98
|
+
const closestA = add(p, mul(u, sParam));
|
|
99
|
+
const tParam = tD > EPS ? tN / tD : 0;
|
|
100
|
+
const closestB = add(r, mul(v, tParam));
|
|
101
|
+
const diff = sub(closestA, closestB);
|
|
102
|
+
return { pointA: closestA, paramA: sParam, dist2: dot(diff, diff) };
|
|
103
|
+
}
|
|
104
|
+
function computeNearestPointOnSegmentForSegmentSet(segmentA, segmentSet) {
|
|
105
|
+
if (!segmentSet.length)
|
|
106
|
+
throw new Error("segmentSet must contain at least one segment");
|
|
107
|
+
let bestPoint = segmentA[0];
|
|
108
|
+
let bestDist2 = Number.POSITIVE_INFINITY;
|
|
109
|
+
for (const segB of segmentSet) {
|
|
110
|
+
const { pointA, dist2 } = closestPointOnSegAToSegB(segmentA, segB);
|
|
111
|
+
if (dist2 < bestDist2) {
|
|
112
|
+
bestDist2 = dist2;
|
|
113
|
+
bestPoint = pointA;
|
|
114
|
+
if (bestDist2 === 0) break;
|
|
115
|
+
}
|
|
88
116
|
}
|
|
89
|
-
};
|
|
117
|
+
return { nearestPoint: bestPoint, dist: Math.sqrt(bestDist2) };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// lib/constructOutlinesFromPackedComponents.ts
|
|
121
|
+
import Flatten from "@flatten-js/core";
|
|
90
122
|
|
|
91
123
|
// lib/math/rotatePoint.ts
|
|
92
124
|
var rotatePoint = (point, angle, origin = { x: 0, y: 0 }) => {
|
|
@@ -118,7 +150,10 @@ var getComponentBounds = (component, minGap = 0) => {
|
|
|
118
150
|
{ x: pad.offset.x - hw, y: pad.offset.y + hh }
|
|
119
151
|
];
|
|
120
152
|
localCorners.forEach((corner) => {
|
|
121
|
-
const world = rotatePoint(
|
|
153
|
+
const world = rotatePoint(
|
|
154
|
+
corner,
|
|
155
|
+
component.ccwRotationOffset * Math.PI / 180
|
|
156
|
+
);
|
|
122
157
|
const x = world.x + component.center.x;
|
|
123
158
|
const y = world.y + component.center.y;
|
|
124
159
|
bounds.minX = Math.min(bounds.minX, x);
|
|
@@ -136,7 +171,6 @@ var getComponentBounds = (component, minGap = 0) => {
|
|
|
136
171
|
};
|
|
137
172
|
|
|
138
173
|
// lib/constructOutlinesFromPackedComponents.ts
|
|
139
|
-
import Flatten from "@flatten-js/core";
|
|
140
174
|
var constructOutlinesFromPackedComponents = (components, opts = {}) => {
|
|
141
175
|
const { minGap = 0 } = opts;
|
|
142
176
|
if (components.length === 0) return [];
|
|
@@ -175,6 +209,107 @@ var constructOutlinesFromPackedComponents = (components, opts = {}) => {
|
|
|
175
209
|
return outlines;
|
|
176
210
|
};
|
|
177
211
|
|
|
212
|
+
// lib/PackSolver/computeSumDistanceForPosition.ts
|
|
213
|
+
function computeSumDistanceForPosition({
|
|
214
|
+
component,
|
|
215
|
+
position,
|
|
216
|
+
targetNetworkId,
|
|
217
|
+
packedComponents,
|
|
218
|
+
useSquaredDistance = false
|
|
219
|
+
}) {
|
|
220
|
+
const componentPadsOnNetwork = component.pads.filter(
|
|
221
|
+
(pad) => pad.networkId === targetNetworkId
|
|
222
|
+
);
|
|
223
|
+
if (componentPadsOnNetwork.length === 0) return 0;
|
|
224
|
+
const packedPadsOnNetwork = packedComponents.flatMap(
|
|
225
|
+
(component2) => component2.pads.filter((pad) => pad.networkId === targetNetworkId)
|
|
226
|
+
);
|
|
227
|
+
if (packedPadsOnNetwork.length === 0) return 0;
|
|
228
|
+
let sumDistance = 0;
|
|
229
|
+
for (const componentPad of componentPadsOnNetwork) {
|
|
230
|
+
const padPosition = {
|
|
231
|
+
x: position.x + componentPad.offset.x,
|
|
232
|
+
y: position.y + componentPad.offset.y
|
|
233
|
+
};
|
|
234
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
235
|
+
for (const packedPad of packedPadsOnNetwork) {
|
|
236
|
+
const dx = padPosition.x - packedPad.absoluteCenter.x;
|
|
237
|
+
const dy = padPosition.y - packedPad.absoluteCenter.y;
|
|
238
|
+
const distance = useSquaredDistance ? dx * dx + dy * dy : Math.hypot(dx, dy);
|
|
239
|
+
if (distance < minDistance) {
|
|
240
|
+
minDistance = distance;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
sumDistance += minDistance === Number.POSITIVE_INFINITY ? 0 : minDistance;
|
|
244
|
+
}
|
|
245
|
+
return sumDistance;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// lib/PackSolver/findOptimalPointOnSegment.ts
|
|
249
|
+
function findOptimalPointOnSegment({
|
|
250
|
+
p1,
|
|
251
|
+
p2,
|
|
252
|
+
component,
|
|
253
|
+
networkId,
|
|
254
|
+
packedComponents,
|
|
255
|
+
useSquaredDistance = false
|
|
256
|
+
}) {
|
|
257
|
+
const candidatePoints = [];
|
|
258
|
+
const tolerance = 1e-6;
|
|
259
|
+
let left = 0;
|
|
260
|
+
let right = 1;
|
|
261
|
+
const interpolatePoint = (t) => ({
|
|
262
|
+
x: p1.x + t * (p2.x - p1.x),
|
|
263
|
+
y: p1.y + t * (p2.y - p1.y)
|
|
264
|
+
});
|
|
265
|
+
const evaluateDistance = (t) => {
|
|
266
|
+
const point = interpolatePoint(t);
|
|
267
|
+
const distance = computeSumDistanceForPosition({
|
|
268
|
+
component,
|
|
269
|
+
position: point,
|
|
270
|
+
targetNetworkId: networkId,
|
|
271
|
+
packedComponents,
|
|
272
|
+
useSquaredDistance
|
|
273
|
+
});
|
|
274
|
+
candidatePoints.push({
|
|
275
|
+
...point,
|
|
276
|
+
networkId,
|
|
277
|
+
distance
|
|
278
|
+
});
|
|
279
|
+
return distance;
|
|
280
|
+
};
|
|
281
|
+
while (right - left > tolerance) {
|
|
282
|
+
const leftThird = left + (right - left) / 3;
|
|
283
|
+
const rightThird = right - (right - left) / 3;
|
|
284
|
+
const leftDistance = evaluateDistance(leftThird);
|
|
285
|
+
const rightDistance = evaluateDistance(rightThird);
|
|
286
|
+
if (leftDistance > rightDistance) {
|
|
287
|
+
left = leftThird;
|
|
288
|
+
} else {
|
|
289
|
+
right = rightThird;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const optimalT = (left + right) / 2;
|
|
293
|
+
const optimalPoint = interpolatePoint(optimalT);
|
|
294
|
+
const optimalDistance = computeSumDistanceForPosition({
|
|
295
|
+
component,
|
|
296
|
+
position: optimalPoint,
|
|
297
|
+
targetNetworkId: networkId,
|
|
298
|
+
packedComponents,
|
|
299
|
+
useSquaredDistance
|
|
300
|
+
});
|
|
301
|
+
candidatePoints.push({
|
|
302
|
+
...optimalPoint,
|
|
303
|
+
networkId,
|
|
304
|
+
distance: optimalDistance
|
|
305
|
+
});
|
|
306
|
+
return {
|
|
307
|
+
point: optimalPoint,
|
|
308
|
+
distance: optimalDistance,
|
|
309
|
+
candidatePoints
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
178
313
|
// lib/testing/createColorMapFromStrings.ts
|
|
179
314
|
var createColorMapFromStrings = (strings) => {
|
|
180
315
|
const colorMap = {};
|
|
@@ -205,7 +340,7 @@ var getGraphicsFromPackOutput = (packOutput) => {
|
|
|
205
340
|
fill: "rgba(0,0,0,0.25)",
|
|
206
341
|
label: [
|
|
207
342
|
component.componentId,
|
|
208
|
-
`ccwRotationOffset: ${
|
|
343
|
+
`ccwRotationOffset: ${component.ccwRotationOffset.toFixed(1)}\xB0`
|
|
209
344
|
].join("\n")
|
|
210
345
|
};
|
|
211
346
|
rects.push(rect);
|
|
@@ -241,20 +376,6 @@ var getGraphicsFromPackOutput = (packOutput) => {
|
|
|
241
376
|
};
|
|
242
377
|
};
|
|
243
378
|
|
|
244
|
-
// lib/PackSolver/setPackedComponentPadCenters.ts
|
|
245
|
-
var setPackedComponentPadCenters = (packedComponent) => {
|
|
246
|
-
packedComponent.pads = packedComponent.pads.map((pad) => ({
|
|
247
|
-
...pad,
|
|
248
|
-
absoluteCenter: (() => {
|
|
249
|
-
const rotated = rotatePoint(pad.offset, packedComponent.ccwRotationOffset);
|
|
250
|
-
return {
|
|
251
|
-
x: packedComponent.center.x + rotated.x,
|
|
252
|
-
y: packedComponent.center.y + rotated.y
|
|
253
|
-
};
|
|
254
|
-
})()
|
|
255
|
-
}));
|
|
256
|
-
};
|
|
257
|
-
|
|
258
379
|
// lib/PackSolver/getSegmentsFromPad.ts
|
|
259
380
|
var getSegmentsFromPad = (pad, { padding = 0 } = {}) => {
|
|
260
381
|
const segments = [];
|
|
@@ -279,408 +400,494 @@ var getSegmentsFromPad = (pad, { padding = 0 } = {}) => {
|
|
|
279
400
|
return segments;
|
|
280
401
|
};
|
|
281
402
|
|
|
282
|
-
// lib/
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
tD = c;
|
|
317
|
-
} else if (sN > sD) {
|
|
318
|
-
sN = sD;
|
|
319
|
-
tN = e + b;
|
|
320
|
-
tD = c;
|
|
321
|
-
}
|
|
403
|
+
// lib/PackSolver/computeGlobalCenter.ts
|
|
404
|
+
function computeGlobalCenter(packedComponents) {
|
|
405
|
+
if (!packedComponents.length) return { x: 0, y: 0 };
|
|
406
|
+
const sum = packedComponents.reduce(
|
|
407
|
+
(acc, component) => ({
|
|
408
|
+
x: acc.x + component.center.x,
|
|
409
|
+
y: acc.y + component.center.y
|
|
410
|
+
}),
|
|
411
|
+
{ x: 0, y: 0 }
|
|
412
|
+
);
|
|
413
|
+
return {
|
|
414
|
+
x: sum.x / packedComponents.length,
|
|
415
|
+
y: sum.y / packedComponents.length
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// lib/PackSolver/findBestPointForDisconnected.ts
|
|
420
|
+
function findBestPointForDisconnected({
|
|
421
|
+
outlines,
|
|
422
|
+
direction,
|
|
423
|
+
packedComponents
|
|
424
|
+
}) {
|
|
425
|
+
const points = outlines.flatMap(
|
|
426
|
+
(outline) => outline.map(([p1, p2]) => ({
|
|
427
|
+
x: (p1.x + p2.x) / 2,
|
|
428
|
+
y: (p1.y + p2.y) / 2
|
|
429
|
+
}))
|
|
430
|
+
);
|
|
431
|
+
if (!points.length) return { x: 0, y: 0 };
|
|
432
|
+
if (direction !== "nearest_to_center") {
|
|
433
|
+
const extreme = direction === "left" || direction === "down" ? Math.min : Math.max;
|
|
434
|
+
const key = direction === "left" || direction === "right" ? "x" : "y";
|
|
435
|
+
const target = extreme(...points.map((p) => p[key]));
|
|
436
|
+
return points.find((p) => p[key] === target);
|
|
322
437
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
} else if (tN > tD) {
|
|
328
|
-
tN = tD;
|
|
329
|
-
sN = clamp(-d + b, 0, a);
|
|
330
|
-
sD = a;
|
|
331
|
-
}
|
|
332
|
-
const sParam = sD > EPS ? sN / sD : 0;
|
|
333
|
-
const closestA = add(p, mul(u, sParam));
|
|
334
|
-
const tParam = tD > EPS ? tN / tD : 0;
|
|
335
|
-
const closestB = add(r, mul(v, tParam));
|
|
336
|
-
const diff = sub(closestA, closestB);
|
|
337
|
-
return { pointA: closestA, paramA: sParam, dist2: dot(diff, diff) };
|
|
338
|
-
}
|
|
339
|
-
function computeNearestPointOnSegmentForSegmentSet(segmentA, segmentSet) {
|
|
340
|
-
if (!segmentSet.length)
|
|
341
|
-
throw new Error("segmentSet must contain at least one segment");
|
|
342
|
-
let bestPoint = segmentA[0];
|
|
343
|
-
let bestDist2 = Number.POSITIVE_INFINITY;
|
|
344
|
-
for (const segB of segmentSet) {
|
|
345
|
-
const { pointA, dist2 } = closestPointOnSegAToSegB(segmentA, segB);
|
|
346
|
-
if (dist2 < bestDist2) {
|
|
347
|
-
bestDist2 = dist2;
|
|
348
|
-
bestPoint = pointA;
|
|
349
|
-
if (bestDist2 === 0) break;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return { nearestPoint: bestPoint, dist: Math.sqrt(bestDist2) };
|
|
438
|
+
const center = computeGlobalCenter(packedComponents);
|
|
439
|
+
return points.reduce(
|
|
440
|
+
(best, point) => Math.hypot(point.x - center.x, point.y - center.y) < Math.hypot(best.x - center.x, best.y - center.y) ? point : best
|
|
441
|
+
);
|
|
353
442
|
}
|
|
354
443
|
|
|
355
|
-
// lib/PackSolver/
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const minCenterDistanceX = minGap + (componentWidth + packedWidth) / 2;
|
|
372
|
-
const minCenterDistanceY = minGap + (componentHeight + packedHeight) / 2;
|
|
373
|
-
const packedCenterX = packedComp.center.x;
|
|
374
|
-
const packedCenterY = packedComp.center.y;
|
|
375
|
-
const leftBound = packedCenterX - minCenterDistanceX;
|
|
376
|
-
const rightBound = packedCenterX + minCenterDistanceX;
|
|
377
|
-
const bottomBound = packedCenterY - minCenterDistanceY;
|
|
378
|
-
const topBound = packedCenterY + minCenterDistanceY;
|
|
379
|
-
if (Math.abs(packedCenterX - initialCenter.x) > Math.abs(packedCenterY - initialCenter.y)) {
|
|
380
|
-
if (packedCenterX < initialCenter.x) {
|
|
381
|
-
minX = Math.max(minX, rightBound);
|
|
382
|
-
} else {
|
|
383
|
-
maxX = Math.min(maxX, leftBound);
|
|
384
|
-
}
|
|
385
|
-
} else {
|
|
386
|
-
if (packedCenterY < initialCenter.y) {
|
|
387
|
-
minY = Math.max(minY, topBound);
|
|
388
|
-
} else {
|
|
389
|
-
maxY = Math.min(maxY, bottomBound);
|
|
444
|
+
// lib/PackSolver/setPackedComponentPadCenters.ts
|
|
445
|
+
var setPackedComponentPadCenters = (packedComponent) => {
|
|
446
|
+
packedComponent.pads = packedComponent.pads.map((pad) => {
|
|
447
|
+
const rotated = rotatePoint(
|
|
448
|
+
pad.offset,
|
|
449
|
+
packedComponent.ccwRotationOffset * Math.PI / 180
|
|
450
|
+
);
|
|
451
|
+
const normalizedRotation = (packedComponent.ccwRotationOffset % 360 + 360) % 360;
|
|
452
|
+
const shouldSwapDimensions = normalizedRotation === 90 || normalizedRotation === 270;
|
|
453
|
+
return {
|
|
454
|
+
...pad,
|
|
455
|
+
size: shouldSwapDimensions ? { x: pad.size.y, y: pad.size.x } : pad.size,
|
|
456
|
+
// Keep original dimensions for 0°/180° rotations
|
|
457
|
+
absoluteCenter: {
|
|
458
|
+
x: packedComponent.center.x + rotated.x,
|
|
459
|
+
y: packedComponent.center.y + rotated.y
|
|
390
460
|
}
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// lib/PackSolver/placeComponentAtPoint.ts
|
|
466
|
+
function placeComponentAtPoint({
|
|
467
|
+
component,
|
|
468
|
+
point,
|
|
469
|
+
candidateAngles,
|
|
470
|
+
checkOverlap
|
|
471
|
+
}) {
|
|
472
|
+
const evaluatedPositionShadows = [];
|
|
473
|
+
for (const angle of candidateAngles) {
|
|
474
|
+
const pads = component.pads.map((pad) => {
|
|
475
|
+
const rotatedOffset = rotatePoint(pad.offset, angle * Math.PI / 180);
|
|
476
|
+
const normalizedRotation = (angle % 360 + 360) % 360;
|
|
477
|
+
const shouldSwapDimensions = normalizedRotation === 90 || normalizedRotation === 270;
|
|
478
|
+
return {
|
|
479
|
+
...pad,
|
|
480
|
+
size: shouldSwapDimensions ? { x: pad.size.y, y: pad.size.x } : pad.size,
|
|
481
|
+
absoluteCenter: {
|
|
482
|
+
x: point.x + rotatedOffset.x,
|
|
483
|
+
y: point.y + rotatedOffset.y
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
});
|
|
487
|
+
const candidate = {
|
|
488
|
+
...component,
|
|
489
|
+
center: point,
|
|
490
|
+
ccwRotationOffset: angle,
|
|
491
|
+
pads
|
|
492
|
+
};
|
|
493
|
+
evaluatedPositionShadows.push(candidate);
|
|
494
|
+
if (!checkOverlap(candidate)) {
|
|
495
|
+
Object.assign(component, candidate);
|
|
496
|
+
setPackedComponentPadCenters(component);
|
|
497
|
+
return evaluatedPositionShadows;
|
|
391
498
|
}
|
|
392
499
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
maxY = Math.min(maxY, initialCenter.y + maxTranslation);
|
|
398
|
-
return { minX, maxX, minY, maxY };
|
|
500
|
+
component.center = point;
|
|
501
|
+
component.ccwRotationOffset = 0;
|
|
502
|
+
setPackedComponentPadCenters(component);
|
|
503
|
+
return evaluatedPositionShadows;
|
|
399
504
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
505
|
+
|
|
506
|
+
// lib/PackSolver/placeComponentDisconnected.ts
|
|
507
|
+
function placeComponentDisconnected({
|
|
508
|
+
component,
|
|
509
|
+
outlines,
|
|
510
|
+
direction,
|
|
511
|
+
packedComponents,
|
|
512
|
+
candidateAngles,
|
|
513
|
+
checkOverlap
|
|
514
|
+
}) {
|
|
515
|
+
const targetPoint = findBestPointForDisconnected({
|
|
516
|
+
outlines,
|
|
517
|
+
direction,
|
|
518
|
+
packedComponents
|
|
519
|
+
});
|
|
520
|
+
return placeComponentAtPoint({
|
|
521
|
+
component,
|
|
522
|
+
point: targetPoint,
|
|
523
|
+
candidateAngles,
|
|
524
|
+
checkOverlap
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// lib/PackSolver/RotationSelector.ts
|
|
529
|
+
function selectOptimalRotation(options) {
|
|
530
|
+
const {
|
|
531
|
+
component,
|
|
532
|
+
candidatePoints,
|
|
533
|
+
packedComponents,
|
|
534
|
+
minGap,
|
|
535
|
+
useSquaredDistance,
|
|
536
|
+
checkOverlap
|
|
537
|
+
} = options;
|
|
538
|
+
const candidateAngles = getCandidateAngles(component);
|
|
539
|
+
let globalBestCandidate = null;
|
|
540
|
+
for (const angle of candidateAngles) {
|
|
541
|
+
let bestForThisRotation = null;
|
|
542
|
+
const packedPads = packedComponents.flatMap((c) => c.pads);
|
|
543
|
+
for (const candidatePoint of candidatePoints) {
|
|
544
|
+
const networkId = candidatePoint.networkId;
|
|
545
|
+
const componentPadsOnNetwork = component.pads.filter(
|
|
546
|
+
(p) => p.networkId === networkId
|
|
420
547
|
);
|
|
421
|
-
if (
|
|
422
|
-
|
|
548
|
+
if (componentPadsOnNetwork.length > 0) {
|
|
549
|
+
const firstPad = componentPadsOnNetwork[0];
|
|
550
|
+
const rotatedPadOffset = rotatePoint(
|
|
551
|
+
firstPad.offset,
|
|
552
|
+
angle * Math.PI / 180
|
|
553
|
+
);
|
|
554
|
+
const initialCenter = {
|
|
555
|
+
x: candidatePoint.x - rotatedPadOffset.x,
|
|
556
|
+
y: candidatePoint.y - rotatedPadOffset.y
|
|
557
|
+
};
|
|
558
|
+
const tempComponent = {
|
|
559
|
+
...component,
|
|
560
|
+
center: initialCenter,
|
|
561
|
+
ccwRotationOffset: (angle % 360 + 360) % 360,
|
|
562
|
+
pads: component.pads.map((p) => ({
|
|
563
|
+
...p,
|
|
564
|
+
absoluteCenter: { x: 0, y: 0 }
|
|
565
|
+
// Will be set by setPackedComponentPadCenters
|
|
566
|
+
}))
|
|
567
|
+
};
|
|
568
|
+
const transformedPads = tempComponent.pads.map((p) => {
|
|
569
|
+
const rotatedOffset = rotatePoint(p.offset, angle * Math.PI / 180);
|
|
570
|
+
const normalizedRotation = (angle % 360 + 360) % 360;
|
|
571
|
+
const shouldSwapDimensions = normalizedRotation === 90 || normalizedRotation === 270;
|
|
572
|
+
return {
|
|
573
|
+
...p,
|
|
574
|
+
size: shouldSwapDimensions ? { x: p.size.y, y: p.size.x } : p.size,
|
|
575
|
+
absoluteCenter: {
|
|
576
|
+
x: initialCenter.x + rotatedOffset.x,
|
|
577
|
+
y: initialCenter.y + rotatedOffset.y
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
});
|
|
581
|
+
tempComponent.pads = transformedPads;
|
|
582
|
+
if (!checkOverlap(tempComponent)) {
|
|
583
|
+
let cost = 0;
|
|
584
|
+
for (const tp of transformedPads) {
|
|
585
|
+
const sameNetPads = packedPads.filter(
|
|
586
|
+
(pp) => pp.networkId === tp.networkId
|
|
587
|
+
);
|
|
588
|
+
if (!sameNetPads.length) continue;
|
|
589
|
+
let bestD = Infinity;
|
|
590
|
+
for (const pp of sameNetPads) {
|
|
591
|
+
const dx = tp.absoluteCenter.x - pp.absoluteCenter.x;
|
|
592
|
+
const dy = tp.absoluteCenter.y - pp.absoluteCenter.y;
|
|
593
|
+
const d = useSquaredDistance ? dx * dx + dy * dy : Math.hypot(dx, dy);
|
|
594
|
+
if (d < bestD) bestD = d;
|
|
595
|
+
}
|
|
596
|
+
cost += bestD === Infinity ? 0 : bestD;
|
|
597
|
+
}
|
|
598
|
+
if (!bestForThisRotation || cost < bestForThisRotation.cost) {
|
|
599
|
+
bestForThisRotation = {
|
|
600
|
+
center: initialCenter,
|
|
601
|
+
angle: (angle % 360 + 360) % 360,
|
|
602
|
+
cost,
|
|
603
|
+
pads: component.pads.map((p) => ({
|
|
604
|
+
...p,
|
|
605
|
+
absoluteCenter: { x: 0, y: 0 }
|
|
606
|
+
}))
|
|
607
|
+
// Return original pads structure
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const centerTrial = {
|
|
613
|
+
...component,
|
|
614
|
+
center: { x: candidatePoint.x, y: candidatePoint.y },
|
|
615
|
+
ccwRotationOffset: (angle % 360 + 360) % 360,
|
|
616
|
+
pads: component.pads.map((p) => {
|
|
617
|
+
const rotatedOffset = rotatePoint(p.offset, angle * Math.PI / 180);
|
|
618
|
+
const normalizedRotation = (angle % 360 + 360) % 360;
|
|
619
|
+
const shouldSwapDimensions = normalizedRotation === 90 || normalizedRotation === 270;
|
|
620
|
+
return {
|
|
621
|
+
...p,
|
|
622
|
+
size: shouldSwapDimensions ? { x: p.size.y, y: p.size.x } : p.size,
|
|
623
|
+
absoluteCenter: {
|
|
624
|
+
x: candidatePoint.x + rotatedOffset.x,
|
|
625
|
+
y: candidatePoint.y + rotatedOffset.y
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
})
|
|
629
|
+
};
|
|
630
|
+
if (!checkOverlap(centerTrial)) {
|
|
631
|
+
let centerCost = 0;
|
|
632
|
+
for (const tp of centerTrial.pads) {
|
|
633
|
+
const sameNetPads = packedPads.filter(
|
|
634
|
+
(pp) => pp.networkId === tp.networkId
|
|
635
|
+
);
|
|
636
|
+
if (!sameNetPads.length) continue;
|
|
637
|
+
let bestD = Infinity;
|
|
638
|
+
for (const pp of sameNetPads) {
|
|
639
|
+
const dx = tp.absoluteCenter.x - pp.absoluteCenter.x;
|
|
640
|
+
const dy = tp.absoluteCenter.y - pp.absoluteCenter.y;
|
|
641
|
+
const d = useSquaredDistance ? dx * dx + dy * dy : Math.hypot(dx, dy);
|
|
642
|
+
if (d < bestD) bestD = d;
|
|
643
|
+
}
|
|
644
|
+
centerCost += bestD === Infinity ? 0 : bestD;
|
|
645
|
+
}
|
|
646
|
+
if (!bestForThisRotation || centerCost < bestForThisRotation.cost) {
|
|
647
|
+
bestForThisRotation = {
|
|
648
|
+
center: centerTrial.center,
|
|
649
|
+
angle: (angle % 360 + 360) % 360,
|
|
650
|
+
cost: centerCost,
|
|
651
|
+
pads: component.pads.map((p) => ({
|
|
652
|
+
...p,
|
|
653
|
+
absoluteCenter: { x: 0, y: 0 }
|
|
654
|
+
}))
|
|
655
|
+
// Return original pads structure
|
|
656
|
+
};
|
|
657
|
+
}
|
|
423
658
|
}
|
|
424
659
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const tempComponent = {
|
|
431
|
-
...component,
|
|
432
|
-
center: candidateCenter,
|
|
433
|
-
pads: component.pads.map((p) => ({
|
|
434
|
-
...p,
|
|
435
|
-
absoluteCenter: {
|
|
436
|
-
x: candidateCenter.x + p.offset.x,
|
|
437
|
-
y: candidateCenter.y + p.offset.y
|
|
660
|
+
if (bestForThisRotation && (!globalBestCandidate || bestForThisRotation.cost < globalBestCandidate.cost)) {
|
|
661
|
+
if (component.componentId === "C6") {
|
|
662
|
+
console.log(
|
|
663
|
+
`C6 rotation ${angle}\xB0: best cost=${bestForThisRotation.cost.toFixed(3)} ${!globalBestCandidate ? "[FIRST ROTATION]" : "[NEW BEST ROTATION]"}`
|
|
664
|
+
);
|
|
438
665
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
x: (componentBounds.minX + componentBounds.maxX) / 2,
|
|
445
|
-
y: (componentBounds.minY + componentBounds.maxY) / 2
|
|
446
|
-
},
|
|
447
|
-
width: componentBounds.maxX - componentBounds.minX,
|
|
448
|
-
height: componentBounds.maxY - componentBounds.minY
|
|
449
|
-
};
|
|
450
|
-
for (const packedComp of packedComponents) {
|
|
451
|
-
const packedBounds = getComponentBounds(packedComp, 0);
|
|
452
|
-
const packedBox = {
|
|
453
|
-
center: {
|
|
454
|
-
x: (packedBounds.minX + packedBounds.maxX) / 2,
|
|
455
|
-
y: (packedBounds.minY + packedBounds.maxY) / 2
|
|
456
|
-
},
|
|
457
|
-
width: packedBounds.maxX - packedBounds.minX,
|
|
458
|
-
height: packedBounds.maxY - packedBounds.minY
|
|
459
|
-
};
|
|
460
|
-
const centerDistance = Math.hypot(
|
|
461
|
-
componentBox.center.x - packedBox.center.x,
|
|
462
|
-
componentBox.center.y - packedBox.center.y
|
|
463
|
-
);
|
|
464
|
-
const minRequiredDistance = minGap + (componentBox.width + packedBox.width) / 2;
|
|
465
|
-
if (centerDistance < minRequiredDistance) {
|
|
466
|
-
return true;
|
|
666
|
+
globalBestCandidate = bestForThisRotation;
|
|
667
|
+
} else if (bestForThisRotation && component.componentId === "C6") {
|
|
668
|
+
console.log(
|
|
669
|
+
`C6 rotation ${angle}\xB0: cost=${bestForThisRotation.cost.toFixed(3)} [WORSE THAN ${globalBestCandidate?.cost.toFixed(3)}]`
|
|
670
|
+
);
|
|
467
671
|
}
|
|
468
672
|
}
|
|
469
|
-
return
|
|
470
|
-
}
|
|
471
|
-
function clampToBounds(p, b) {
|
|
472
|
-
return {
|
|
473
|
-
x: Math.min(Math.max(p.x, b.minX), b.maxX),
|
|
474
|
-
y: Math.min(Math.max(p.y, b.minY), b.maxY)
|
|
475
|
-
};
|
|
673
|
+
return globalBestCandidate;
|
|
476
674
|
}
|
|
477
|
-
function
|
|
478
|
-
|
|
479
|
-
const dy = a.y - b.y;
|
|
480
|
-
return Math.hypot(dx, dy);
|
|
675
|
+
function getCandidateAngles(c) {
|
|
676
|
+
return (c.availableRotationDegrees ?? [0, 90, 180, 270]).map((d) => d % 360);
|
|
481
677
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
678
|
+
|
|
679
|
+
// lib/PackSolver/sortComponentQueue.ts
|
|
680
|
+
function sortComponentQueue({
|
|
681
|
+
components,
|
|
682
|
+
packOrderStrategy,
|
|
683
|
+
packFirst = []
|
|
684
|
+
}) {
|
|
685
|
+
const packFirstMap = /* @__PURE__ */ new Map();
|
|
686
|
+
packFirst.forEach((componentId, index) => {
|
|
687
|
+
packFirstMap.set(componentId, index);
|
|
688
|
+
});
|
|
689
|
+
return [...components].sort((a, b) => {
|
|
690
|
+
const aPackFirstIndex = packFirstMap.get(a.componentId);
|
|
691
|
+
const bPackFirstIndex = packFirstMap.get(b.componentId);
|
|
692
|
+
if (aPackFirstIndex !== void 0 && bPackFirstIndex !== void 0) {
|
|
693
|
+
return aPackFirstIndex - bPackFirstIndex;
|
|
497
694
|
}
|
|
498
|
-
|
|
499
|
-
|
|
695
|
+
if (aPackFirstIndex !== void 0) return -1;
|
|
696
|
+
if (bPackFirstIndex !== void 0) return 1;
|
|
697
|
+
if (packOrderStrategy === "largest_to_smallest") {
|
|
698
|
+
return b.pads.length - a.pads.length;
|
|
699
|
+
}
|
|
700
|
+
return a.pads.length - b.pads.length;
|
|
701
|
+
});
|
|
500
702
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
703
|
+
|
|
704
|
+
// lib/solver-utils/BaseSolver.ts
|
|
705
|
+
var BaseSolver = class {
|
|
706
|
+
MAX_ITERATIONS = 1e3;
|
|
707
|
+
solved = false;
|
|
708
|
+
failed = false;
|
|
709
|
+
iterations = 0;
|
|
710
|
+
progress = 0;
|
|
711
|
+
error = null;
|
|
712
|
+
activeSubSolver;
|
|
713
|
+
failedSubSolvers;
|
|
714
|
+
timeToSolve;
|
|
715
|
+
stats = {};
|
|
716
|
+
_setupDone = false;
|
|
717
|
+
setup() {
|
|
718
|
+
if (this._setupDone) return;
|
|
719
|
+
this._setup();
|
|
720
|
+
this._setupDone = true;
|
|
721
|
+
}
|
|
722
|
+
/** DO NOT OVERRIDE! Override _step() instead */
|
|
723
|
+
step() {
|
|
724
|
+
if (!this._setupDone) {
|
|
725
|
+
this.setup();
|
|
726
|
+
}
|
|
727
|
+
if (this.solved) return;
|
|
728
|
+
if (this.failed) return;
|
|
729
|
+
this.iterations++;
|
|
730
|
+
try {
|
|
731
|
+
this._step();
|
|
732
|
+
} catch (e) {
|
|
733
|
+
this.error = `${this.constructor.name} error: ${e}`;
|
|
734
|
+
console.error(this.error);
|
|
735
|
+
this.failed = true;
|
|
736
|
+
throw e;
|
|
737
|
+
}
|
|
738
|
+
if (!this.solved && this.iterations > this.MAX_ITERATIONS) {
|
|
739
|
+
this.tryFinalAcceptance();
|
|
740
|
+
}
|
|
741
|
+
if (!this.solved && this.iterations > this.MAX_ITERATIONS) {
|
|
742
|
+
this.error = `${this.constructor.name} ran out of iterations`;
|
|
743
|
+
console.error(this.error);
|
|
744
|
+
this.failed = true;
|
|
507
745
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
let den = 0;
|
|
511
|
-
for (const p of targets) {
|
|
512
|
-
const d = dist(c, p);
|
|
513
|
-
const w = 1 / Math.max(d, eps);
|
|
514
|
-
numX += p.x * w;
|
|
515
|
-
numY += p.y * w;
|
|
516
|
-
den += w;
|
|
746
|
+
if ("computeProgress" in this) {
|
|
747
|
+
this.progress = this.computeProgress();
|
|
517
748
|
}
|
|
518
|
-
if (den === 0) break;
|
|
519
|
-
const next = clampToBounds({ x: numX / den, y: numY / den }, bounds);
|
|
520
|
-
if (dist(next, c) < tol) return next;
|
|
521
|
-
c = next;
|
|
522
749
|
}
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
function optimizeTranslationForMinimumSum(context) {
|
|
526
|
-
const { component, initialCenter, packedComponents, minGap } = context;
|
|
527
|
-
const bounds = computeTranslationBounds(
|
|
528
|
-
component,
|
|
529
|
-
initialCenter,
|
|
530
|
-
packedComponents,
|
|
531
|
-
minGap
|
|
532
|
-
);
|
|
533
|
-
if (bounds.minX >= bounds.maxX || bounds.minY >= bounds.maxY) {
|
|
534
|
-
return initialCenter;
|
|
750
|
+
_setup() {
|
|
535
751
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
let best = null;
|
|
546
|
-
for (let j = 0; j < packedPads.length; j++) {
|
|
547
|
-
const pp = packedPads[j];
|
|
548
|
-
if (!pp || pp.networkId !== pad.networkId) continue;
|
|
549
|
-
const d = Math.hypot(
|
|
550
|
-
padAbs.x - pp.absoluteCenter.x,
|
|
551
|
-
padAbs.y - pp.absoluteCenter.y
|
|
552
|
-
);
|
|
553
|
-
if (!best || d < dist(padAbs, best.p)) {
|
|
554
|
-
best = {
|
|
555
|
-
p: {
|
|
556
|
-
x: pp.absoluteCenter.x - pad.offset.x,
|
|
557
|
-
y: pp.absoluteCenter.y - pad.offset.y
|
|
558
|
-
},
|
|
559
|
-
key: `${pad.networkId}:${j}`
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
if (best) results.push({ targets: best.p, key: best.key });
|
|
752
|
+
_step() {
|
|
753
|
+
}
|
|
754
|
+
getConstructorParams() {
|
|
755
|
+
throw new Error("getConstructorParams not implemented");
|
|
756
|
+
}
|
|
757
|
+
solve() {
|
|
758
|
+
const startTime = Date.now();
|
|
759
|
+
while (!this.solved && !this.failed) {
|
|
760
|
+
this.step();
|
|
564
761
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
y: (bounds.minY + bounds.maxY) / 2
|
|
762
|
+
const endTime = Date.now();
|
|
763
|
+
this.timeToSolve = endTime - startTime;
|
|
764
|
+
}
|
|
765
|
+
visualize() {
|
|
766
|
+
return {
|
|
767
|
+
lines: [],
|
|
768
|
+
points: [],
|
|
769
|
+
rects: [],
|
|
770
|
+
circles: []
|
|
575
771
|
};
|
|
576
|
-
center = backtrackToFeasible(
|
|
577
|
-
center,
|
|
578
|
-
mid,
|
|
579
|
-
(p) => !checkOverlap(component, p, packedComponents, minGap)
|
|
580
|
-
);
|
|
581
772
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
targets,
|
|
589
|
-
center,
|
|
590
|
-
bounds,
|
|
591
|
-
100,
|
|
592
|
-
1e-4
|
|
593
|
-
);
|
|
594
|
-
let next = proposed;
|
|
595
|
-
if (checkOverlap(component, next, packedComponents, minGap)) {
|
|
596
|
-
next = backtrackToFeasible(
|
|
597
|
-
center,
|
|
598
|
-
proposed,
|
|
599
|
-
(p) => !checkOverlap(component, p, packedComponents, minGap)
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
if (signature === prevAssignSignature && dist(next, center) < tolMove) {
|
|
603
|
-
center = next;
|
|
604
|
-
break;
|
|
605
|
-
}
|
|
606
|
-
prevAssignSignature = signature;
|
|
607
|
-
center = next;
|
|
773
|
+
/**
|
|
774
|
+
* Called when the solver is about to fail, but we want to see if we have an
|
|
775
|
+
* "acceptable" or "passable" solution. Mostly used for optimizers that
|
|
776
|
+
* have an aggressive early stopping criterion.
|
|
777
|
+
*/
|
|
778
|
+
tryFinalAcceptance() {
|
|
608
779
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
)
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
780
|
+
/**
|
|
781
|
+
* A lightweight version of the visualize method that can be used to stream
|
|
782
|
+
* progress
|
|
783
|
+
*/
|
|
784
|
+
preview() {
|
|
785
|
+
return {
|
|
786
|
+
lines: [],
|
|
787
|
+
points: [],
|
|
788
|
+
rects: [],
|
|
789
|
+
circles: []
|
|
790
|
+
};
|
|
617
791
|
}
|
|
618
|
-
|
|
619
|
-
}
|
|
792
|
+
};
|
|
620
793
|
|
|
621
|
-
// lib/PackSolver/
|
|
622
|
-
var
|
|
794
|
+
// lib/PackSolver/PhasedPackSolver.ts
|
|
795
|
+
var PhasedPackSolver = class extends BaseSolver {
|
|
623
796
|
packInput;
|
|
624
797
|
unpackedComponentQueue;
|
|
625
798
|
packedComponents;
|
|
799
|
+
// Phase management
|
|
800
|
+
currentPhase = "idle";
|
|
801
|
+
currentComponent;
|
|
802
|
+
phaseData = {};
|
|
803
|
+
// Legacy compatibility
|
|
626
804
|
lastBestPointsResult;
|
|
627
805
|
lastEvaluatedPositionShadows;
|
|
806
|
+
lastCandidatePoints;
|
|
628
807
|
constructor(input) {
|
|
629
808
|
super();
|
|
630
809
|
this.packInput = input;
|
|
631
810
|
}
|
|
632
811
|
_setup() {
|
|
633
812
|
const { components, packOrderStrategy, packFirst = [] } = this.packInput;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
this.unpackedComponentQueue = [...components].sort((a, b) => {
|
|
639
|
-
const aPackFirstIndex = packFirstMap.get(a.componentId);
|
|
640
|
-
const bPackFirstIndex = packFirstMap.get(b.componentId);
|
|
641
|
-
if (aPackFirstIndex !== void 0 && bPackFirstIndex !== void 0) {
|
|
642
|
-
return aPackFirstIndex - bPackFirstIndex;
|
|
643
|
-
}
|
|
644
|
-
if (aPackFirstIndex !== void 0) return -1;
|
|
645
|
-
if (bPackFirstIndex !== void 0) return 1;
|
|
646
|
-
if (packOrderStrategy === "largest_to_smallest") {
|
|
647
|
-
return b.pads.length - a.pads.length;
|
|
648
|
-
}
|
|
649
|
-
return a.pads.length - b.pads.length;
|
|
813
|
+
this.unpackedComponentQueue = sortComponentQueue({
|
|
814
|
+
components,
|
|
815
|
+
packOrderStrategy,
|
|
816
|
+
packFirst
|
|
650
817
|
});
|
|
651
818
|
this.packedComponents = [];
|
|
819
|
+
this.currentPhase = "idle";
|
|
820
|
+
this.phaseData = {};
|
|
652
821
|
}
|
|
653
822
|
_step() {
|
|
654
823
|
if (this.solved) return;
|
|
824
|
+
switch (this.currentPhase) {
|
|
825
|
+
case "idle":
|
|
826
|
+
if (this.unpackedComponentQueue.length === 0) {
|
|
827
|
+
this.solved = true;
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
this.currentComponent = this.unpackedComponentQueue.shift();
|
|
831
|
+
if (!this.currentComponent) {
|
|
832
|
+
this.solved = true;
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (this.packedComponents.length === 0) {
|
|
836
|
+
this.placeFirstComponent();
|
|
837
|
+
this.currentComponent = void 0;
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
this.currentPhase = "show_candidate_points";
|
|
841
|
+
this.computeCandidatePoints();
|
|
842
|
+
break;
|
|
843
|
+
case "show_candidate_points":
|
|
844
|
+
this.currentPhase = "show_rotations";
|
|
845
|
+
this.computeRotationTrials();
|
|
846
|
+
break;
|
|
847
|
+
case "show_rotations":
|
|
848
|
+
this.currentPhase = "show_final_placement";
|
|
849
|
+
this.selectBestRotation();
|
|
850
|
+
break;
|
|
851
|
+
case "show_final_placement":
|
|
852
|
+
this.finalizeComponentPlacement();
|
|
853
|
+
this.currentPhase = "idle";
|
|
854
|
+
this.phaseData = {};
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
placeFirstComponent() {
|
|
859
|
+
if (!this.currentComponent) return;
|
|
860
|
+
const newPackedComponent = {
|
|
861
|
+
...this.currentComponent,
|
|
862
|
+
center: { x: 0, y: 0 },
|
|
863
|
+
ccwRotationOffset: 0,
|
|
864
|
+
pads: this.currentComponent.pads.map((p) => ({
|
|
865
|
+
...p,
|
|
866
|
+
absoluteCenter: { x: 0, y: 0 }
|
|
867
|
+
}))
|
|
868
|
+
};
|
|
869
|
+
const candidateAngles = this.getCandidateAngles(newPackedComponent);
|
|
870
|
+
newPackedComponent.ccwRotationOffset = ((candidateAngles[0] ?? 0) % 360 + 360) % 360;
|
|
871
|
+
setPackedComponentPadCenters(newPackedComponent);
|
|
872
|
+
this.packedComponents.push(newPackedComponent);
|
|
873
|
+
this.currentComponent = void 0;
|
|
874
|
+
}
|
|
875
|
+
computeCandidatePoints() {
|
|
876
|
+
if (!this.currentComponent) return;
|
|
655
877
|
const {
|
|
656
878
|
minGap = 0,
|
|
657
879
|
disconnectedPackDirection = "nearest_to_center",
|
|
658
880
|
packPlacementStrategy = "shortest_connection_along_outline"
|
|
659
881
|
} = this.packInput;
|
|
660
|
-
if (this.unpackedComponentQueue.length === 0) {
|
|
661
|
-
this.solved = true;
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
const next = this.unpackedComponentQueue.shift();
|
|
665
|
-
if (!next) {
|
|
666
|
-
this.solved = true;
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
882
|
const newPackedComponent = {
|
|
670
|
-
...
|
|
883
|
+
...this.currentComponent,
|
|
671
884
|
center: { x: 0, y: 0 },
|
|
672
885
|
ccwRotationOffset: 0,
|
|
673
|
-
pads:
|
|
886
|
+
pads: this.currentComponent.pads.map((p) => ({
|
|
674
887
|
...p,
|
|
675
888
|
absoluteCenter: { x: 0, y: 0 }
|
|
676
889
|
}))
|
|
677
890
|
};
|
|
678
|
-
if (this.packedComponents.length === 0) {
|
|
679
|
-
newPackedComponent.center = { x: 0, y: 0 };
|
|
680
|
-
setPackedComponentPadCenters(newPackedComponent);
|
|
681
|
-
this.packedComponents.push(newPackedComponent);
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
684
891
|
const padMargins = newPackedComponent.pads.map(
|
|
685
892
|
(p) => Math.max(p.size.x, p.size.y) / 2
|
|
686
893
|
);
|
|
@@ -689,6 +896,7 @@ var PackSolver = class extends BaseSolver {
|
|
|
689
896
|
this.packedComponents,
|
|
690
897
|
{ minGap: minGap + additionalGap }
|
|
691
898
|
);
|
|
899
|
+
this.phaseData.outlines = outlines;
|
|
692
900
|
const networkIdsInPackedComponents = new Set(
|
|
693
901
|
this.packedComponents.flatMap((c) => c.pads.map((p) => p.networkId))
|
|
694
902
|
);
|
|
@@ -701,55 +909,86 @@ var PackSolver = class extends BaseSolver {
|
|
|
701
909
|
)
|
|
702
910
|
);
|
|
703
911
|
if (sharedNetworkIds.size === 0) {
|
|
704
|
-
this.
|
|
705
|
-
|
|
912
|
+
this.phaseData.candidatePoints = [];
|
|
913
|
+
this.phaseData.goodCandidates = [];
|
|
914
|
+
const shadows = placeComponentDisconnected({
|
|
915
|
+
component: newPackedComponent,
|
|
706
916
|
outlines,
|
|
707
|
-
disconnectedPackDirection
|
|
708
|
-
|
|
709
|
-
|
|
917
|
+
direction: disconnectedPackDirection,
|
|
918
|
+
packedComponents: this.packedComponents,
|
|
919
|
+
candidateAngles: this.getCandidateAngles(newPackedComponent),
|
|
920
|
+
checkOverlap: (comp) => this.checkOverlapWithPackedComponents(comp)
|
|
921
|
+
});
|
|
922
|
+
this.phaseData.selectedRotation = newPackedComponent;
|
|
923
|
+
this.phaseData.rotationTrials = shadows.map((s) => ({
|
|
924
|
+
...s,
|
|
925
|
+
cost: 0,
|
|
926
|
+
anchorType: "center",
|
|
927
|
+
hasOverlap: false
|
|
928
|
+
}));
|
|
710
929
|
return;
|
|
711
930
|
}
|
|
931
|
+
const candidatePoints = [];
|
|
932
|
+
const goodCandidates = [];
|
|
933
|
+
let smallestDistance = Number.POSITIVE_INFINITY;
|
|
934
|
+
const segmentBestPoints = /* @__PURE__ */ new Map();
|
|
935
|
+
const getSegmentKey = (segment) => {
|
|
936
|
+
const [p1, p2] = segment;
|
|
937
|
+
return `${p1.x.toFixed(6)},${p1.y.toFixed(6)}-${p2.x.toFixed(6)},${p2.y.toFixed(6)}`;
|
|
938
|
+
};
|
|
712
939
|
const networkIdToAlreadyPackedSegments = /* @__PURE__ */ new Map();
|
|
713
940
|
for (const sharedNetworkId of sharedNetworkIds) {
|
|
714
|
-
|
|
941
|
+
const segments = [];
|
|
715
942
|
for (const packedComponent of this.packedComponents) {
|
|
716
943
|
for (const pad of packedComponent.pads) {
|
|
717
|
-
if (pad.networkId
|
|
718
|
-
|
|
719
|
-
|
|
944
|
+
if (pad.networkId === sharedNetworkId) {
|
|
945
|
+
segments.push(...getSegmentsFromPad(pad));
|
|
946
|
+
}
|
|
720
947
|
}
|
|
721
948
|
}
|
|
949
|
+
networkIdToAlreadyPackedSegments.set(sharedNetworkId, segments);
|
|
722
950
|
}
|
|
723
|
-
|
|
724
|
-
let bestPoints = [];
|
|
725
|
-
if (packPlacementStrategy === "minimum_sum_distance_to_network") {
|
|
951
|
+
if (packPlacementStrategy === "minimum_sum_distance_to_network" || packPlacementStrategy === "minimum_sum_squared_distance_to_network") {
|
|
726
952
|
for (const outline of outlines) {
|
|
727
953
|
for (const outlineSegment of outline) {
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
{
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
954
|
+
const [p1, p2] = outlineSegment;
|
|
955
|
+
for (const sharedNetworkId of sharedNetworkIds) {
|
|
956
|
+
const {
|
|
957
|
+
point: optimalPoint,
|
|
958
|
+
distance: optimalDistance,
|
|
959
|
+
candidatePoints: searchPoints
|
|
960
|
+
} = findOptimalPointOnSegment({
|
|
961
|
+
p1,
|
|
962
|
+
p2,
|
|
963
|
+
component: newPackedComponent,
|
|
964
|
+
networkId: sharedNetworkId,
|
|
965
|
+
packedComponents: this.packedComponents,
|
|
966
|
+
useSquaredDistance: packPlacementStrategy === "minimum_sum_squared_distance_to_network"
|
|
967
|
+
});
|
|
968
|
+
for (const searchPoint of searchPoints) {
|
|
969
|
+
candidatePoints.push(searchPoint);
|
|
970
|
+
}
|
|
971
|
+
const segmentKey = getSegmentKey(outlineSegment);
|
|
972
|
+
const currentSegmentBest = segmentBestPoints.get(segmentKey);
|
|
973
|
+
if (!currentSegmentBest || optimalDistance < currentSegmentBest.distance) {
|
|
974
|
+
segmentBestPoints.set(segmentKey, {
|
|
975
|
+
point: { ...optimalPoint, networkId: sharedNetworkId },
|
|
976
|
+
distance: optimalDistance
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
if (optimalDistance < smallestDistance + 1e-6) {
|
|
980
|
+
if (optimalDistance < smallestDistance - 1e-6) {
|
|
981
|
+
goodCandidates.length = 0;
|
|
982
|
+
goodCandidates.push({
|
|
983
|
+
...optimalPoint,
|
|
984
|
+
networkId: sharedNetworkId
|
|
985
|
+
});
|
|
986
|
+
smallestDistance = optimalDistance;
|
|
987
|
+
} else {
|
|
988
|
+
goodCandidates.push({
|
|
989
|
+
...optimalPoint,
|
|
990
|
+
networkId: sharedNetworkId
|
|
991
|
+
});
|
|
753
992
|
}
|
|
754
993
|
}
|
|
755
994
|
}
|
|
@@ -768,17 +1007,32 @@ var PackSolver = class extends BaseSolver {
|
|
|
768
1007
|
outlineSegment,
|
|
769
1008
|
alreadyPackedSegments
|
|
770
1009
|
);
|
|
1010
|
+
candidatePoints.push({
|
|
1011
|
+
...nearestPointOnOutlineToAlreadyPackedSegments,
|
|
1012
|
+
networkId: sharedNetworkId,
|
|
1013
|
+
distance: outlineToAlreadyPackedSegmentsDist
|
|
1014
|
+
});
|
|
1015
|
+
const segmentKey = getSegmentKey(outlineSegment);
|
|
1016
|
+
const currentSegmentBest = segmentBestPoints.get(segmentKey);
|
|
1017
|
+
if (!currentSegmentBest || outlineToAlreadyPackedSegmentsDist < currentSegmentBest.distance) {
|
|
1018
|
+
segmentBestPoints.set(segmentKey, {
|
|
1019
|
+
point: {
|
|
1020
|
+
...nearestPointOnOutlineToAlreadyPackedSegments,
|
|
1021
|
+
networkId: sharedNetworkId
|
|
1022
|
+
},
|
|
1023
|
+
distance: outlineToAlreadyPackedSegmentsDist
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
771
1026
|
if (outlineToAlreadyPackedSegmentsDist < smallestDistance + 1e-6) {
|
|
772
1027
|
if (outlineToAlreadyPackedSegmentsDist < smallestDistance - 1e-6) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
];
|
|
1028
|
+
goodCandidates.length = 0;
|
|
1029
|
+
goodCandidates.push({
|
|
1030
|
+
...nearestPointOnOutlineToAlreadyPackedSegments,
|
|
1031
|
+
networkId: sharedNetworkId
|
|
1032
|
+
});
|
|
779
1033
|
smallestDistance = outlineToAlreadyPackedSegmentsDist;
|
|
780
1034
|
} else {
|
|
781
|
-
|
|
1035
|
+
goodCandidates.push({
|
|
782
1036
|
...nearestPointOnOutlineToAlreadyPackedSegments,
|
|
783
1037
|
networkId: sharedNetworkId
|
|
784
1038
|
});
|
|
@@ -788,212 +1042,216 @@ var PackSolver = class extends BaseSolver {
|
|
|
788
1042
|
}
|
|
789
1043
|
}
|
|
790
1044
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
(p) => p.networkId === networkId
|
|
800
|
-
);
|
|
801
|
-
const candidateAngles = this.getCandidateAngles(newPackedComponent);
|
|
802
|
-
let bestCandidate = null;
|
|
803
|
-
const packedPads = this.packedComponents.flatMap((c) => c.pads);
|
|
804
|
-
for (const angle of candidateAngles) {
|
|
805
|
-
const firstPad = newPadsConnectedToNetworkId[0];
|
|
806
|
-
if (!firstPad) continue;
|
|
807
|
-
const rotatedOffset = rotatePoint(firstPad.offset, angle);
|
|
808
|
-
const candidateCenter = {
|
|
809
|
-
x: bestPoint.x - rotatedOffset.x,
|
|
810
|
-
y: bestPoint.y - rotatedOffset.y
|
|
811
|
-
};
|
|
812
|
-
const transformedPads = newPackedComponent.pads.map((p) => {
|
|
813
|
-
const ro = rotatePoint(p.offset, angle);
|
|
814
|
-
return {
|
|
815
|
-
...p,
|
|
816
|
-
absoluteCenter: {
|
|
817
|
-
x: candidateCenter.x + ro.x,
|
|
818
|
-
y: candidateCenter.y + ro.y
|
|
819
|
-
}
|
|
820
|
-
};
|
|
821
|
-
});
|
|
822
|
-
const tempComponent = {
|
|
823
|
-
...newPackedComponent,
|
|
824
|
-
center: candidateCenter,
|
|
825
|
-
ccwRotationOffset: angle,
|
|
826
|
-
pads: transformedPads
|
|
827
|
-
};
|
|
828
|
-
this.lastEvaluatedPositionShadows?.push({ ...tempComponent });
|
|
829
|
-
if (this.checkOverlapWithPackedComponents(tempComponent)) continue;
|
|
830
|
-
let cost = 0;
|
|
831
|
-
if (packPlacementStrategy === "minimum_sum_distance_to_network") {
|
|
832
|
-
const optimizedCenter = optimizeTranslationForMinimumSum({
|
|
833
|
-
component: tempComponent,
|
|
834
|
-
initialCenter: candidateCenter,
|
|
835
|
-
packedComponents: this.packedComponents,
|
|
836
|
-
minGap
|
|
837
|
-
});
|
|
838
|
-
const optimizedTransformedPads = newPackedComponent.pads.map((p) => {
|
|
839
|
-
const ro = rotatePoint(p.offset, angle);
|
|
840
|
-
return {
|
|
841
|
-
...p,
|
|
842
|
-
absoluteCenter: {
|
|
843
|
-
x: optimizedCenter.x + ro.x,
|
|
844
|
-
y: optimizedCenter.y + ro.y
|
|
845
|
-
}
|
|
1045
|
+
for (const sharedNetworkId of sharedNetworkIds) {
|
|
1046
|
+
for (const outline of outlines) {
|
|
1047
|
+
for (const outlineSegment of outline) {
|
|
1048
|
+
const [p1, p2] = outlineSegment;
|
|
1049
|
+
for (let t = 0; t <= 1; t += 0.2) {
|
|
1050
|
+
const sampledPoint = {
|
|
1051
|
+
x: p1.x + t * (p2.x - p1.x),
|
|
1052
|
+
y: p1.y + t * (p2.y - p1.y)
|
|
846
1053
|
};
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (optimizedCenter.x !== candidateCenter.x || optimizedCenter.y !== candidateCenter.y) {
|
|
851
|
-
this.lastEvaluatedPositionShadows?.push({ ...tempComponent });
|
|
852
|
-
}
|
|
853
|
-
if (this.checkOverlapWithPackedComponents(tempComponent)) continue;
|
|
854
|
-
for (const tp of optimizedTransformedPads) {
|
|
855
|
-
const sameNetPads = packedPads.filter(
|
|
856
|
-
(pp) => pp.networkId === tp.networkId
|
|
1054
|
+
let distance = 0;
|
|
1055
|
+
const componentPadsOnNetwork = newPackedComponent.pads.filter(
|
|
1056
|
+
(p) => p.networkId === sharedNetworkId
|
|
857
1057
|
);
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1058
|
+
for (const componentPad of componentPadsOnNetwork) {
|
|
1059
|
+
let minDist = Number.POSITIVE_INFINITY;
|
|
1060
|
+
for (const packedComponent of this.packedComponents) {
|
|
1061
|
+
for (const packedPad of packedComponent.pads) {
|
|
1062
|
+
if (packedPad.networkId === sharedNetworkId) {
|
|
1063
|
+
const dx = sampledPoint.x + componentPad.offset.x - packedPad.absoluteCenter.x;
|
|
1064
|
+
const dy = sampledPoint.y + componentPad.offset.y - packedPad.absoluteCenter.y;
|
|
1065
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1066
|
+
minDist = Math.min(minDist, dist);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
distance += minDist;
|
|
865
1071
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
)
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
const d = Math.hypot(dx, dy);
|
|
879
|
-
if (d < bestD) bestD = d;
|
|
1072
|
+
const point = {
|
|
1073
|
+
...sampledPoint,
|
|
1074
|
+
networkId: sharedNetworkId,
|
|
1075
|
+
distance
|
|
1076
|
+
};
|
|
1077
|
+
candidatePoints.push(point);
|
|
1078
|
+
if (distance < smallestDistance) {
|
|
1079
|
+
smallestDistance = distance;
|
|
1080
|
+
goodCandidates.length = 0;
|
|
1081
|
+
goodCandidates.push(point);
|
|
1082
|
+
} else if (distance === smallestDistance) {
|
|
1083
|
+
goodCandidates.push(point);
|
|
880
1084
|
}
|
|
881
|
-
cost += bestD;
|
|
882
1085
|
}
|
|
883
1086
|
}
|
|
884
|
-
if (!bestCandidate || cost < bestCandidate.cost) {
|
|
885
|
-
const finalCenter = packPlacementStrategy === "minimum_sum_distance_to_network" ? tempComponent.center : candidateCenter;
|
|
886
|
-
bestCandidate = { center: finalCenter, angle, cost };
|
|
887
|
-
}
|
|
888
1087
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
x: bestPoint.x - firstPad.offset.x,
|
|
897
|
-
y: bestPoint.y - firstPad.offset.y
|
|
898
|
-
};
|
|
899
|
-
newPackedComponent.center = candidateCenter;
|
|
900
|
-
newPackedComponent.ccwRotationOffset = 0;
|
|
1088
|
+
}
|
|
1089
|
+
for (const [, segmentBest] of segmentBestPoints) {
|
|
1090
|
+
const isAlreadyIncluded = goodCandidates.some(
|
|
1091
|
+
(gc) => Math.abs(gc.x - segmentBest.point.x) < 1e-6 && Math.abs(gc.y - segmentBest.point.y) < 1e-6 && gc.networkId === segmentBest.point.networkId
|
|
1092
|
+
);
|
|
1093
|
+
if (!isAlreadyIncluded) {
|
|
1094
|
+
goodCandidates.push(segmentBest.point);
|
|
901
1095
|
}
|
|
902
|
-
setPackedComponentPadCenters(newPackedComponent);
|
|
903
1096
|
}
|
|
904
|
-
|
|
905
|
-
this.
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1097
|
+
this.phaseData.candidatePoints = candidatePoints;
|
|
1098
|
+
this.phaseData.goodCandidates = goodCandidates;
|
|
1099
|
+
this.phaseData.bestDistance = smallestDistance;
|
|
1100
|
+
this.lastCandidatePoints = candidatePoints;
|
|
1101
|
+
this.lastBestPointsResult = { goodCandidates, distance: smallestDistance };
|
|
909
1102
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
width: b.maxX - b.minX,
|
|
921
|
-
height: b.maxY - b.minY
|
|
1103
|
+
computeRotationTrials() {
|
|
1104
|
+
if (!this.currentComponent || !this.phaseData.goodCandidates) return;
|
|
1105
|
+
const newPackedComponent = {
|
|
1106
|
+
...this.currentComponent,
|
|
1107
|
+
center: { x: 0, y: 0 },
|
|
1108
|
+
ccwRotationOffset: 0,
|
|
1109
|
+
pads: this.currentComponent.pads.map((p) => ({
|
|
1110
|
+
...p,
|
|
1111
|
+
absoluteCenter: { x: 0, y: 0 }
|
|
1112
|
+
}))
|
|
922
1113
|
};
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1114
|
+
const rotationTrials = [];
|
|
1115
|
+
const candidateAngles = this.getCandidateAngles(newPackedComponent);
|
|
1116
|
+
const allCandidatePoints = [...this.phaseData.goodCandidates];
|
|
1117
|
+
if (this.phaseData.outlines) {
|
|
1118
|
+
for (const networkId of new Set(
|
|
1119
|
+
this.phaseData.goodCandidates.map((p) => p.networkId)
|
|
1120
|
+
)) {
|
|
1121
|
+
for (const outline of this.phaseData.outlines) {
|
|
1122
|
+
for (const outlineSegment of outline) {
|
|
1123
|
+
const [p1, p2] = outlineSegment;
|
|
1124
|
+
for (let t = 0; t <= 1; t += 0.2) {
|
|
1125
|
+
allCandidatePoints.push({
|
|
1126
|
+
x: p1.x + t * (p2.x - p1.x),
|
|
1127
|
+
y: p1.y + t * (p2.y - p1.y),
|
|
1128
|
+
networkId
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
934
1133
|
}
|
|
935
1134
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1135
|
+
const useSquaredDistance = this.packInput.packPlacementStrategy === "minimum_sum_squared_distance_to_network";
|
|
1136
|
+
const result = selectOptimalRotation({
|
|
1137
|
+
component: newPackedComponent,
|
|
1138
|
+
candidatePoints: allCandidatePoints,
|
|
1139
|
+
packedComponents: this.packedComponents,
|
|
1140
|
+
minGap: this.packInput.minGap ?? 0,
|
|
1141
|
+
useSquaredDistance,
|
|
1142
|
+
checkOverlap: (comp) => this.checkOverlapWithPackedComponents(comp)
|
|
1143
|
+
});
|
|
1144
|
+
for (const angle of candidateAngles) {
|
|
1145
|
+
for (const point of this.phaseData.goodCandidates) {
|
|
1146
|
+
const componentPadsOnNetwork = newPackedComponent.pads.filter(
|
|
1147
|
+
(p) => p.networkId === point.networkId
|
|
1148
|
+
);
|
|
1149
|
+
if (componentPadsOnNetwork.length > 0) {
|
|
1150
|
+
const firstPad = componentPadsOnNetwork[0];
|
|
1151
|
+
const rotatedPadOffset = rotatePoint(
|
|
1152
|
+
firstPad.offset,
|
|
1153
|
+
angle * Math.PI / 180
|
|
1154
|
+
);
|
|
1155
|
+
const componentCenter = {
|
|
1156
|
+
x: point.x - rotatedPadOffset.x,
|
|
1157
|
+
y: point.y - rotatedPadOffset.y
|
|
1158
|
+
};
|
|
1159
|
+
const trial = { ...newPackedComponent };
|
|
1160
|
+
trial.center = componentCenter;
|
|
1161
|
+
trial.ccwRotationOffset = (angle % 360 + 360) % 360;
|
|
1162
|
+
setPackedComponentPadCenters(trial);
|
|
1163
|
+
const hasOverlap = this.checkOverlapWithPackedComponents(trial);
|
|
1164
|
+
let cost = 0;
|
|
1165
|
+
for (const pad of trial.pads) {
|
|
1166
|
+
let minDist = Number.POSITIVE_INFINITY;
|
|
1167
|
+
for (const packedComp of this.packedComponents) {
|
|
1168
|
+
for (const packedPad of packedComp.pads) {
|
|
1169
|
+
if (packedPad.networkId === pad.networkId) {
|
|
1170
|
+
const dx = pad.absoluteCenter.x - packedPad.absoluteCenter.x;
|
|
1171
|
+
const dy = pad.absoluteCenter.y - packedPad.absoluteCenter.y;
|
|
1172
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1173
|
+
minDist = Math.min(minDist, dist);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (minDist < Number.POSITIVE_INFINITY) {
|
|
1178
|
+
cost += useSquaredDistance ? minDist * minDist : minDist;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
rotationTrials.push({
|
|
1182
|
+
...trial,
|
|
1183
|
+
cost,
|
|
1184
|
+
anchorType: "pad",
|
|
1185
|
+
anchorPadId: firstPad.padId,
|
|
1186
|
+
hasOverlap
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
const centerTrial = { ...newPackedComponent };
|
|
1190
|
+
centerTrial.center = { x: point.x, y: point.y };
|
|
1191
|
+
centerTrial.ccwRotationOffset = (angle % 360 + 360) % 360;
|
|
1192
|
+
setPackedComponentPadCenters(centerTrial);
|
|
1193
|
+
const centerHasOverlap = this.checkOverlapWithPackedComponents(centerTrial);
|
|
1194
|
+
let centerCost = 0;
|
|
1195
|
+
for (const pad of centerTrial.pads) {
|
|
1196
|
+
let minDist = Number.POSITIVE_INFINITY;
|
|
1197
|
+
for (const packedComp of this.packedComponents) {
|
|
1198
|
+
for (const packedPad of packedComp.pads) {
|
|
1199
|
+
if (packedPad.networkId === pad.networkId) {
|
|
1200
|
+
const dx = pad.absoluteCenter.x - packedPad.absoluteCenter.x;
|
|
1201
|
+
const dy = pad.absoluteCenter.y - packedPad.absoluteCenter.y;
|
|
1202
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1203
|
+
minDist = Math.min(minDist, dist);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (minDist < Number.POSITIVE_INFINITY) {
|
|
1208
|
+
centerCost += useSquaredDistance ? minDist * minDist : minDist;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
rotationTrials.push({
|
|
1212
|
+
...centerTrial,
|
|
1213
|
+
cost: centerCost,
|
|
1214
|
+
anchorType: "center",
|
|
1215
|
+
hasOverlap: centerHasOverlap
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
962
1218
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1219
|
+
this.phaseData.rotationTrials = rotationTrials;
|
|
1220
|
+
if (result) {
|
|
1221
|
+
const selectedComponent = { ...newPackedComponent };
|
|
1222
|
+
selectedComponent.center = result.center;
|
|
1223
|
+
selectedComponent.ccwRotationOffset = result.angle;
|
|
1224
|
+
selectedComponent.pads = result.pads;
|
|
1225
|
+
setPackedComponentPadCenters(selectedComponent);
|
|
1226
|
+
this.phaseData.selectedRotation = selectedComponent;
|
|
1227
|
+
} else {
|
|
1228
|
+
this.phaseData.selectedRotation = void 0;
|
|
1229
|
+
}
|
|
1230
|
+
this.lastEvaluatedPositionShadows = rotationTrials;
|
|
967
1231
|
}
|
|
968
|
-
|
|
969
|
-
this.
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
ccwRotationOffset: ang,
|
|
979
|
-
pads
|
|
1232
|
+
selectBestRotation() {
|
|
1233
|
+
if (!this.phaseData.selectedRotation && this.currentComponent) {
|
|
1234
|
+
const newPackedComponent = {
|
|
1235
|
+
...this.currentComponent,
|
|
1236
|
+
center: { x: 5, y: 5 },
|
|
1237
|
+
ccwRotationOffset: 0,
|
|
1238
|
+
pads: this.currentComponent.pads.map((p) => ({
|
|
1239
|
+
...p,
|
|
1240
|
+
absoluteCenter: { x: 0, y: 0 }
|
|
1241
|
+
}))
|
|
980
1242
|
};
|
|
981
|
-
this.
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
1243
|
+
const candidateAngles = this.getCandidateAngles(newPackedComponent);
|
|
1244
|
+
newPackedComponent.ccwRotationOffset = ((candidateAngles[0] ?? 0) % 360 + 360) % 360;
|
|
1245
|
+
setPackedComponentPadCenters(newPackedComponent);
|
|
1246
|
+
this.phaseData.selectedRotation = newPackedComponent;
|
|
987
1247
|
}
|
|
988
|
-
comp.center = pt;
|
|
989
|
-
comp.ccwRotationOffset = 0;
|
|
990
|
-
setPackedComponentPadCenters(comp);
|
|
991
1248
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
this.
|
|
1249
|
+
finalizeComponentPlacement() {
|
|
1250
|
+
if (!this.phaseData.selectedRotation) return;
|
|
1251
|
+
this.packedComponents.push(this.phaseData.selectedRotation);
|
|
1252
|
+
this.currentComponent = void 0;
|
|
995
1253
|
}
|
|
996
|
-
/** Visualize the current packing state
|
|
1254
|
+
/** Visualize the current packing state based on the current phase */
|
|
997
1255
|
visualize() {
|
|
998
1256
|
const graphics = getGraphicsFromPackOutput({
|
|
999
1257
|
components: this.packedComponents ?? [],
|
|
@@ -1004,6 +1262,15 @@ var PackSolver = class extends BaseSolver {
|
|
|
1004
1262
|
});
|
|
1005
1263
|
graphics.points ??= [];
|
|
1006
1264
|
graphics.lines ??= [];
|
|
1265
|
+
graphics.rects ??= [];
|
|
1266
|
+
if (graphics.rects) {
|
|
1267
|
+
for (const rect of graphics.rects) {
|
|
1268
|
+
if (rect.fill === "rgba(0,0,0,0.25)") {
|
|
1269
|
+
rect.fill = "rgba(100,100,100,0.5)";
|
|
1270
|
+
rect.stroke = "#333333";
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1007
1274
|
const outlines = constructOutlinesFromPackedComponents(
|
|
1008
1275
|
this.packedComponents ?? [],
|
|
1009
1276
|
{
|
|
@@ -1020,76 +1287,161 @@ var PackSolver = class extends BaseSolver {
|
|
|
1020
1287
|
)
|
|
1021
1288
|
)
|
|
1022
1289
|
);
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1290
|
+
graphics.texts ??= [];
|
|
1291
|
+
graphics.texts.push({
|
|
1292
|
+
text: `Phase: ${this.currentPhase}`,
|
|
1293
|
+
x: 0,
|
|
1294
|
+
y: 5,
|
|
1295
|
+
fontSize: 0.3
|
|
1296
|
+
});
|
|
1297
|
+
if (this.currentComponent) {
|
|
1298
|
+
graphics.texts.push({
|
|
1299
|
+
text: `Packing: ${this.currentComponent.componentId}`,
|
|
1300
|
+
x: 0,
|
|
1301
|
+
y: 4.5,
|
|
1302
|
+
fontSize: 0.25
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
switch (this.currentPhase) {
|
|
1306
|
+
case "show_candidate_points":
|
|
1307
|
+
this.visualizeCandidatePoints(graphics);
|
|
1308
|
+
break;
|
|
1309
|
+
case "show_rotations":
|
|
1310
|
+
this.visualizeRotationTrials(graphics);
|
|
1311
|
+
break;
|
|
1312
|
+
case "show_final_placement":
|
|
1313
|
+
this.visualizeFinalPlacement(graphics);
|
|
1314
|
+
break;
|
|
1315
|
+
case "idle":
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
return graphics;
|
|
1319
|
+
}
|
|
1320
|
+
visualizeCandidatePoints(graphics) {
|
|
1321
|
+
if (this.phaseData.candidatePoints) {
|
|
1322
|
+
for (const candidatePoint of this.phaseData.candidatePoints) {
|
|
1323
|
+
graphics.points.push({
|
|
1324
|
+
x: candidatePoint.x,
|
|
1325
|
+
y: candidatePoint.y,
|
|
1326
|
+
label: `d=${candidatePoint.distance.toFixed(3)}`,
|
|
1327
|
+
fill: "rgba(255,165,0,0.6)",
|
|
1328
|
+
// Orange color for candidate points
|
|
1329
|
+
radius: 0.02
|
|
1032
1330
|
});
|
|
1033
|
-
for (const shadowPad of shadow.pads) {
|
|
1034
|
-
graphics.rects.push({
|
|
1035
|
-
center: shadowPad.absoluteCenter,
|
|
1036
|
-
width: shadowPad.size.x,
|
|
1037
|
-
height: shadowPad.size.y,
|
|
1038
|
-
fill: "rgba(0,0,255,0.5)"
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
1331
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1332
|
+
}
|
|
1333
|
+
if (this.phaseData.goodCandidates) {
|
|
1334
|
+
for (const goodCandidate of this.phaseData.goodCandidates) {
|
|
1335
|
+
graphics.points.push({
|
|
1336
|
+
x: goodCandidate.x,
|
|
1337
|
+
y: goodCandidate.y,
|
|
1338
|
+
label: `BEST (d=${this.phaseData.bestDistance?.toFixed(3)})`,
|
|
1339
|
+
fill: "rgba(0,255,0,0.8)",
|
|
1340
|
+
// Green color for best points
|
|
1341
|
+
radius: 0.03
|
|
1342
|
+
});
|
|
1343
|
+
const crossSize = 0.2;
|
|
1344
|
+
graphics.lines.push(
|
|
1345
|
+
{
|
|
1346
|
+
points: [
|
|
1347
|
+
{
|
|
1348
|
+
x: goodCandidate.x - crossSize,
|
|
1349
|
+
y: goodCandidate.y - crossSize
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
x: goodCandidate.x + crossSize,
|
|
1353
|
+
y: goodCandidate.y + crossSize
|
|
1354
|
+
}
|
|
1355
|
+
],
|
|
1356
|
+
stroke: "#00AA00"
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
points: [
|
|
1360
|
+
{
|
|
1361
|
+
x: goodCandidate.x - crossSize,
|
|
1362
|
+
y: goodCandidate.y + crossSize
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
x: goodCandidate.x + crossSize,
|
|
1366
|
+
y: goodCandidate.y - crossSize
|
|
1367
|
+
}
|
|
1368
|
+
],
|
|
1369
|
+
stroke: "#00AA00"
|
|
1370
|
+
}
|
|
1371
|
+
);
|
|
1052
1372
|
}
|
|
1053
1373
|
}
|
|
1054
|
-
|
|
1374
|
+
}
|
|
1375
|
+
visualizeRotationTrials(graphics) {
|
|
1376
|
+
if (!this.phaseData.rotationTrials) return;
|
|
1377
|
+
for (const trial of this.phaseData.rotationTrials) {
|
|
1378
|
+
const rotationOffset = 0.02 * (trial.ccwRotationOffset / 90);
|
|
1379
|
+
const anchorInfo = trial.anchorType === "pad" ? `pad: ${trial.anchorPadId}` : "center";
|
|
1380
|
+
const overlapText = trial.hasOverlap ? "\nOVERLAP" : "";
|
|
1381
|
+
graphics.points.push({
|
|
1382
|
+
x: trial.center.x + rotationOffset,
|
|
1383
|
+
y: trial.center.y + rotationOffset,
|
|
1384
|
+
label: `${trial.ccwRotationOffset}\xB0 (cost: ${trial.cost.toFixed(3)}, anchor: ${anchorInfo})${overlapText}`,
|
|
1385
|
+
fill: "rgba(0,255,255,0.8)",
|
|
1386
|
+
radius: 0.05
|
|
1387
|
+
});
|
|
1388
|
+
for (const pad of trial.pads) {
|
|
1389
|
+
const padColor = trial.hasOverlap ? { fill: "rgba(255,165,0,0.15)", stroke: "rgba(255,165,0,0.4)" } : { fill: "rgba(0,0,255,0.15)", stroke: "rgba(0,0,255,0.4)" };
|
|
1390
|
+
graphics.rects.push({
|
|
1391
|
+
center: pad.absoluteCenter,
|
|
1392
|
+
width: pad.size.x,
|
|
1393
|
+
height: pad.size.y,
|
|
1394
|
+
fill: padColor.fill,
|
|
1395
|
+
stroke: padColor.stroke,
|
|
1396
|
+
strokeWidth: 0.01
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
visualizeFinalPlacement(graphics) {
|
|
1402
|
+
if (!this.phaseData.selectedRotation) return;
|
|
1403
|
+
const component = this.phaseData.selectedRotation;
|
|
1404
|
+
const bounds = getComponentBounds(component, 0);
|
|
1405
|
+
graphics.rects.push({
|
|
1406
|
+
center: component.center,
|
|
1407
|
+
width: bounds.maxX - bounds.minX,
|
|
1408
|
+
height: bounds.maxY - bounds.minY,
|
|
1409
|
+
fill: "rgba(0,255,0,0.3)",
|
|
1410
|
+
stroke: "#00FF00",
|
|
1411
|
+
strokeWidth: 0.05,
|
|
1412
|
+
label: `PLACED at ${component.ccwRotationOffset}\xB0`
|
|
1413
|
+
});
|
|
1414
|
+
for (const pad of component.pads) {
|
|
1415
|
+
graphics.rects.push({
|
|
1416
|
+
center: pad.absoluteCenter,
|
|
1417
|
+
width: pad.size.x,
|
|
1418
|
+
height: pad.size.y,
|
|
1419
|
+
fill: "rgba(0,255,0,0.7)"
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
getConstructorParams() {
|
|
1424
|
+
return [this.packInput];
|
|
1055
1425
|
}
|
|
1056
1426
|
getResult() {
|
|
1057
1427
|
return this.packedComponents;
|
|
1058
1428
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
for (const componentPad of componentPadsOnNetwork) {
|
|
1070
|
-
const padPosition = {
|
|
1071
|
-
x: position.x + componentPad.offset.x,
|
|
1072
|
-
y: position.y + componentPad.offset.y
|
|
1073
|
-
};
|
|
1074
|
-
let minDistance = Number.POSITIVE_INFINITY;
|
|
1075
|
-
for (const packedPad of packedPadsOnNetwork) {
|
|
1076
|
-
const distance = Math.hypot(
|
|
1077
|
-
padPosition.x - packedPad.absoluteCenter.x,
|
|
1078
|
-
padPosition.y - packedPad.absoluteCenter.y
|
|
1079
|
-
);
|
|
1080
|
-
if (distance < minDistance) {
|
|
1081
|
-
minDistance = distance;
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
sumDistance += minDistance === Number.POSITIVE_INFINITY ? 0 : minDistance;
|
|
1085
|
-
}
|
|
1086
|
-
return sumDistance;
|
|
1429
|
+
/* ---------- small helpers ------------------------------------------------ */
|
|
1430
|
+
getCandidateAngles(c) {
|
|
1431
|
+
return (c.availableRotationDegrees ?? [0, 90, 180, 270]).map((d) => d % 360);
|
|
1432
|
+
}
|
|
1433
|
+
checkOverlapWithPackedComponents(component) {
|
|
1434
|
+
return checkOverlapWithPackedComponents({
|
|
1435
|
+
component,
|
|
1436
|
+
packedComponents: this.packedComponents,
|
|
1437
|
+
minGap: this.packInput.minGap ?? 0
|
|
1438
|
+
});
|
|
1087
1439
|
}
|
|
1088
1440
|
};
|
|
1089
1441
|
|
|
1090
1442
|
// lib/pack.ts
|
|
1091
1443
|
var pack = (input) => {
|
|
1092
|
-
const solver = new
|
|
1444
|
+
const solver = new PhasedPackSolver(input);
|
|
1093
1445
|
solver.solve();
|
|
1094
1446
|
return {
|
|
1095
1447
|
...input,
|
|
@@ -1322,6 +1674,8 @@ var convertCircuitJsonToPackOutput = (circuitJson, opts = {}) => {
|
|
|
1322
1674
|
var convertPackOutputToPackInput = (packed) => {
|
|
1323
1675
|
const strippedComponents = packed.components.map((pc) => ({
|
|
1324
1676
|
componentId: pc.componentId,
|
|
1677
|
+
availableRotationDegrees: pc.availableRotationDegrees,
|
|
1678
|
+
// Preserve rotation constraints
|
|
1325
1679
|
pads: pc.pads.map(({ absoluteCenter: _ac, ...rest }) => rest)
|
|
1326
1680
|
}));
|
|
1327
1681
|
return {
|
|
@@ -1331,7 +1685,7 @@ var convertPackOutputToPackInput = (packed) => {
|
|
|
1331
1685
|
};
|
|
1332
1686
|
};
|
|
1333
1687
|
export {
|
|
1334
|
-
|
|
1688
|
+
PhasedPackSolver,
|
|
1335
1689
|
convertCircuitJsonToPackOutput,
|
|
1336
1690
|
convertPackOutputToPackInput,
|
|
1337
1691
|
getGraphicsFromPackOutput,
|