calculate-packing 0.0.22 → 0.0.24
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 +38 -16
- package/dist/index.js +267 -161
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -233,9 +233,16 @@ interface Point {
|
|
|
233
233
|
x: number;
|
|
234
234
|
y: number;
|
|
235
235
|
}
|
|
236
|
-
interface
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
interface OffsetPadPoint {
|
|
237
|
+
id: string;
|
|
238
|
+
offsetX: number;
|
|
239
|
+
offsetY: number;
|
|
240
|
+
}
|
|
241
|
+
interface MultiOffsetIrlsSolverParams {
|
|
242
|
+
/** Offset pad points relative to the current position */
|
|
243
|
+
offsetPadPoints: OffsetPadPoint[];
|
|
244
|
+
/** Map from offset pad ID to array of target points it should minimize distance to */
|
|
245
|
+
targetPointMap: Map<string, Point[]>;
|
|
239
246
|
/** Initial position for the algorithm */
|
|
240
247
|
initialPosition: Point;
|
|
241
248
|
/** Optional constraint function that maps a point to the nearest valid position */
|
|
@@ -248,22 +255,25 @@ interface IrlsSolverParams {
|
|
|
248
255
|
useSquaredDistance?: boolean;
|
|
249
256
|
}
|
|
250
257
|
/**
|
|
251
|
-
* IRLS (Iteratively Reweighted Least Squares) Solver
|
|
252
|
-
*
|
|
258
|
+
* Multi-Offset IRLS (Iteratively Reweighted Least Squares) Solver
|
|
259
|
+
* Extends the Weiszfeld algorithm to optimize a single position that minimizes
|
|
260
|
+
* the total distance from multiple offset pad points to their assigned target points.
|
|
253
261
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
262
|
+
* The offset pad points are positioned relative to the current position, and the
|
|
263
|
+
* algorithm finds the optimal position that minimizes the sum of distances from
|
|
264
|
+
* each offset pad to its assigned target points.
|
|
256
265
|
*/
|
|
257
|
-
declare class
|
|
258
|
-
|
|
266
|
+
declare class MultiOffsetIrlsSolver extends BaseSolver {
|
|
267
|
+
offsetPadPoints: OffsetPadPoint[];
|
|
268
|
+
targetPointMap: Map<string, Point[]>;
|
|
259
269
|
currentPosition: Point;
|
|
260
270
|
constraintFn?: (point: Point) => Point;
|
|
261
271
|
epsilon: number;
|
|
262
272
|
useSquaredDistance: boolean;
|
|
263
273
|
optimalPosition?: Point;
|
|
264
274
|
private readonly initialPosition;
|
|
265
|
-
constructor(params:
|
|
266
|
-
getConstructorParams():
|
|
275
|
+
constructor(params: MultiOffsetIrlsSolverParams);
|
|
276
|
+
getConstructorParams(): MultiOffsetIrlsSolverParams;
|
|
267
277
|
_setup(): void;
|
|
268
278
|
_step(): void;
|
|
269
279
|
/**
|
|
@@ -271,9 +281,21 @@ declare class IrlsSolver extends BaseSolver {
|
|
|
271
281
|
*/
|
|
272
282
|
getBestPosition(): Point;
|
|
273
283
|
/**
|
|
274
|
-
*
|
|
284
|
+
* Get the current absolute positions for all offset pad points
|
|
285
|
+
*/
|
|
286
|
+
getOffsetPadPositions(): Map<string, Point>;
|
|
287
|
+
/**
|
|
288
|
+
* Get the absolute position for a specific offset pad point
|
|
289
|
+
*/
|
|
290
|
+
getOffsetPadPosition(padId: string): Point | undefined;
|
|
291
|
+
/**
|
|
292
|
+
* Calculate total distance from current position to all assigned target points
|
|
275
293
|
*/
|
|
276
294
|
getTotalDistance(position?: Point): number;
|
|
295
|
+
/**
|
|
296
|
+
* Calculate total distance for a specific offset pad point
|
|
297
|
+
*/
|
|
298
|
+
getDistanceForPad(padId: string, position?: Point): number;
|
|
277
299
|
computeProgress(): number;
|
|
278
300
|
visualize(): GraphicsObject;
|
|
279
301
|
}
|
|
@@ -299,7 +321,7 @@ declare class OutlineSegmentCandidatePointSolver extends BaseSolver {
|
|
|
299
321
|
componentToPack: InputComponent;
|
|
300
322
|
viableBounds?: Bounds;
|
|
301
323
|
optimalPosition?: Point$3;
|
|
302
|
-
irlsSolver?:
|
|
324
|
+
irlsSolver?: MultiOffsetIrlsSolver;
|
|
303
325
|
constructor(params: {
|
|
304
326
|
outlineSegment: [Point$3, Point$3];
|
|
305
327
|
fullOutline: [Point$3, Point$3][];
|
|
@@ -310,15 +332,15 @@ declare class OutlineSegmentCandidatePointSolver extends BaseSolver {
|
|
|
310
332
|
componentToPack: InputComponent;
|
|
311
333
|
});
|
|
312
334
|
getConstructorParams(): ConstructorParameters<typeof OutlineSegmentCandidatePointSolver>[0];
|
|
313
|
-
|
|
335
|
+
_getOutlineBoundsWithMargin(params?: {
|
|
314
336
|
margin?: number;
|
|
315
337
|
}): Bounds;
|
|
316
338
|
_setup(): void;
|
|
317
339
|
_step(): void;
|
|
318
340
|
/**
|
|
319
|
-
* Get
|
|
341
|
+
* Get pad offset points and target point mappings for network connections
|
|
320
342
|
*/
|
|
321
|
-
private
|
|
343
|
+
private getNetworkTargetPointMappings;
|
|
322
344
|
/**
|
|
323
345
|
* Project a point onto the outline segment
|
|
324
346
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,43 +1,30 @@
|
|
|
1
1
|
// lib/PackSolver/checkOverlapWithPackedComponents.ts
|
|
2
|
+
import { computeDistanceBetweenBoxes } from "@tscircuit/math-utils";
|
|
2
3
|
function checkOverlapWithPackedComponents({
|
|
3
4
|
component,
|
|
4
5
|
packedComponents,
|
|
5
6
|
minGap
|
|
6
7
|
}) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
}
|
|
8
|
+
const allPackedPadBoxes = packedComponents.flatMap(
|
|
9
|
+
(c) => c.pads.map((p) => ({
|
|
10
|
+
center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
|
|
11
|
+
width: p.size.x,
|
|
12
|
+
height: p.size.y
|
|
13
|
+
}))
|
|
14
|
+
);
|
|
15
|
+
const newComponentPadBoxes = component.pads.map((p) => ({
|
|
16
|
+
center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
|
|
17
|
+
width: p.size.x,
|
|
18
|
+
height: p.size.y
|
|
19
|
+
}));
|
|
20
|
+
for (const newComponentPadBox of newComponentPadBoxes) {
|
|
21
|
+
for (const packedPadBox of allPackedPadBoxes) {
|
|
22
|
+
const { distance: boxDist } = computeDistanceBetweenBoxes(
|
|
23
|
+
newComponentPadBox,
|
|
24
|
+
packedPadBox
|
|
25
|
+
);
|
|
26
|
+
if (boxDist < minGap) {
|
|
27
|
+
return true;
|
|
41
28
|
}
|
|
42
29
|
}
|
|
43
30
|
}
|
|
@@ -195,6 +182,53 @@ function simplifyCollinearSegments(outline, tolerance = 1e-10) {
|
|
|
195
182
|
return simplified;
|
|
196
183
|
}
|
|
197
184
|
|
|
185
|
+
// lib/geometry/getComponentBounds.ts
|
|
186
|
+
var getComponentBounds = (component, minGap = 0) => {
|
|
187
|
+
const bounds = {
|
|
188
|
+
minX: Infinity,
|
|
189
|
+
maxX: -Infinity,
|
|
190
|
+
minY: Infinity,
|
|
191
|
+
maxY: -Infinity
|
|
192
|
+
};
|
|
193
|
+
component.pads.forEach((pad) => {
|
|
194
|
+
const hw = pad.size.x / 2;
|
|
195
|
+
const hh = pad.size.y / 2;
|
|
196
|
+
const localCorners = [
|
|
197
|
+
{ x: pad.offset.x - hw, y: pad.offset.y - hh },
|
|
198
|
+
{ x: pad.offset.x + hw, y: pad.offset.y - hh },
|
|
199
|
+
{ x: pad.offset.x + hw, y: pad.offset.y + hh },
|
|
200
|
+
{ x: pad.offset.x - hw, y: pad.offset.y + hh }
|
|
201
|
+
];
|
|
202
|
+
localCorners.forEach((corner) => {
|
|
203
|
+
const world = rotatePoint(
|
|
204
|
+
corner,
|
|
205
|
+
component.ccwRotationOffset * Math.PI / 180
|
|
206
|
+
);
|
|
207
|
+
const x = world.x + component.center.x;
|
|
208
|
+
const y = world.y + component.center.y;
|
|
209
|
+
bounds.minX = Math.min(bounds.minX, x);
|
|
210
|
+
bounds.maxX = Math.max(bounds.maxX, x);
|
|
211
|
+
bounds.minY = Math.min(bounds.minY, y);
|
|
212
|
+
bounds.maxY = Math.max(bounds.maxY, y);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
minX: bounds.minX - minGap,
|
|
217
|
+
maxX: bounds.maxX + minGap,
|
|
218
|
+
minY: bounds.minY - minGap,
|
|
219
|
+
maxY: bounds.maxY + minGap
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// lib/geometry/combineBounds.ts
|
|
224
|
+
var combineBounds = (bounds) => {
|
|
225
|
+
const minX = Math.min(...bounds.map((b) => b.minX));
|
|
226
|
+
const minY = Math.min(...bounds.map((b) => b.minY));
|
|
227
|
+
const maxX = Math.max(...bounds.map((b) => b.maxX));
|
|
228
|
+
const maxY = Math.max(...bounds.map((b) => b.maxY));
|
|
229
|
+
return { minX, minY, maxX, maxY };
|
|
230
|
+
};
|
|
231
|
+
|
|
198
232
|
// lib/constructOutlinesFromPackedComponents.ts
|
|
199
233
|
var createPadPolygons = (component, minGap) => {
|
|
200
234
|
return component.pads.map((pad) => {
|
|
@@ -222,16 +256,26 @@ var createPadPolygons = (component, minGap) => {
|
|
|
222
256
|
var constructOutlinesFromPackedComponents = (components, opts = {}) => {
|
|
223
257
|
const { minGap = 0 } = opts;
|
|
224
258
|
if (components.length === 0) return [];
|
|
259
|
+
const bounds = combineBounds(
|
|
260
|
+
components.map((c) => getComponentBounds(c, minGap))
|
|
261
|
+
);
|
|
225
262
|
const allPadPolys = [];
|
|
226
263
|
for (const component of components) {
|
|
227
264
|
const padPolys = createPadPolygons(component, minGap);
|
|
228
265
|
allPadPolys.push(...padPolys);
|
|
229
266
|
}
|
|
230
267
|
if (allPadPolys.length === 0) return [];
|
|
231
|
-
let
|
|
232
|
-
|
|
233
|
-
|
|
268
|
+
let A = new Flatten.Polygon(
|
|
269
|
+
new Flatten.Box(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)
|
|
270
|
+
);
|
|
271
|
+
let B = A.clone();
|
|
272
|
+
for (let i = 0; i < allPadPolys.length; i++) {
|
|
273
|
+
try {
|
|
274
|
+
A = Flatten.BooleanOperations.subtract(A, allPadPolys[i]);
|
|
275
|
+
} catch (e) {
|
|
276
|
+
}
|
|
234
277
|
}
|
|
278
|
+
const union = Flatten.BooleanOperations.subtract(B, A);
|
|
235
279
|
const outlines = [];
|
|
236
280
|
for (const face of union.faces) {
|
|
237
281
|
if (face.isHole) continue;
|
|
@@ -351,44 +395,6 @@ function findOptimalPointOnSegment({
|
|
|
351
395
|
};
|
|
352
396
|
}
|
|
353
397
|
|
|
354
|
-
// lib/geometry/getComponentBounds.ts
|
|
355
|
-
var getComponentBounds = (component, minGap = 0) => {
|
|
356
|
-
const bounds = {
|
|
357
|
-
minX: Infinity,
|
|
358
|
-
maxX: -Infinity,
|
|
359
|
-
minY: Infinity,
|
|
360
|
-
maxY: -Infinity
|
|
361
|
-
};
|
|
362
|
-
component.pads.forEach((pad) => {
|
|
363
|
-
const hw = pad.size.x / 2;
|
|
364
|
-
const hh = pad.size.y / 2;
|
|
365
|
-
const localCorners = [
|
|
366
|
-
{ x: pad.offset.x - hw, y: pad.offset.y - hh },
|
|
367
|
-
{ x: pad.offset.x + hw, y: pad.offset.y - hh },
|
|
368
|
-
{ x: pad.offset.x + hw, y: pad.offset.y + hh },
|
|
369
|
-
{ x: pad.offset.x - hw, y: pad.offset.y + hh }
|
|
370
|
-
];
|
|
371
|
-
localCorners.forEach((corner) => {
|
|
372
|
-
const world = rotatePoint(
|
|
373
|
-
corner,
|
|
374
|
-
component.ccwRotationOffset * Math.PI / 180
|
|
375
|
-
);
|
|
376
|
-
const x = world.x + component.center.x;
|
|
377
|
-
const y = world.y + component.center.y;
|
|
378
|
-
bounds.minX = Math.min(bounds.minX, x);
|
|
379
|
-
bounds.maxX = Math.max(bounds.maxX, x);
|
|
380
|
-
bounds.minY = Math.min(bounds.minY, y);
|
|
381
|
-
bounds.maxY = Math.max(bounds.maxY, y);
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
return {
|
|
385
|
-
minX: bounds.minX - minGap,
|
|
386
|
-
maxX: bounds.maxX + minGap,
|
|
387
|
-
minY: bounds.minY - minGap,
|
|
388
|
-
maxY: bounds.maxY + minGap
|
|
389
|
-
};
|
|
390
|
-
};
|
|
391
|
-
|
|
392
398
|
// lib/testing/createColorMapFromStrings.ts
|
|
393
399
|
var createColorMapFromStrings = (strings) => {
|
|
394
400
|
const colorMap = {};
|
|
@@ -1914,9 +1920,10 @@ var LargestRectOutsideOutlineFromPointSolver = class extends BaseSolver {
|
|
|
1914
1920
|
// lib/OutlineSegmentCandidatePointSolver/OutlineSegmentCandidatePointSolver.ts
|
|
1915
1921
|
import { clamp as clamp2 } from "@tscircuit/math-utils";
|
|
1916
1922
|
|
|
1917
|
-
// lib/solver-utils/
|
|
1918
|
-
var
|
|
1919
|
-
|
|
1923
|
+
// lib/solver-utils/MultiOffsetIrlsSolver.ts
|
|
1924
|
+
var MultiOffsetIrlsSolver = class extends BaseSolver {
|
|
1925
|
+
offsetPadPoints;
|
|
1926
|
+
targetPointMap;
|
|
1920
1927
|
currentPosition;
|
|
1921
1928
|
constraintFn;
|
|
1922
1929
|
epsilon;
|
|
@@ -1925,7 +1932,8 @@ var IrlsSolver = class extends BaseSolver {
|
|
|
1925
1932
|
initialPosition;
|
|
1926
1933
|
constructor(params) {
|
|
1927
1934
|
super();
|
|
1928
|
-
this.
|
|
1935
|
+
this.offsetPadPoints = [...params.offsetPadPoints];
|
|
1936
|
+
this.targetPointMap = new Map(params.targetPointMap);
|
|
1929
1937
|
this.initialPosition = { ...params.initialPosition };
|
|
1930
1938
|
this.currentPosition = { ...params.initialPosition };
|
|
1931
1939
|
this.constraintFn = params.constraintFn;
|
|
@@ -1935,7 +1943,8 @@ var IrlsSolver = class extends BaseSolver {
|
|
|
1935
1943
|
}
|
|
1936
1944
|
getConstructorParams() {
|
|
1937
1945
|
return {
|
|
1938
|
-
|
|
1946
|
+
offsetPadPoints: this.offsetPadPoints.map((pad) => ({ ...pad })),
|
|
1947
|
+
targetPointMap: new Map(this.targetPointMap),
|
|
1939
1948
|
initialPosition: this.initialPosition,
|
|
1940
1949
|
constraintFn: this.constraintFn,
|
|
1941
1950
|
epsilon: this.epsilon,
|
|
@@ -1946,30 +1955,43 @@ var IrlsSolver = class extends BaseSolver {
|
|
|
1946
1955
|
_setup() {
|
|
1947
1956
|
this.currentPosition = { ...this.initialPosition };
|
|
1948
1957
|
this.optimalPosition = void 0;
|
|
1949
|
-
|
|
1958
|
+
const hasTargets = Array.from(this.targetPointMap.values()).some(
|
|
1959
|
+
(targets) => targets.length > 0
|
|
1960
|
+
);
|
|
1961
|
+
if (!hasTargets || this.offsetPadPoints.length === 0) {
|
|
1950
1962
|
this.optimalPosition = { ...this.currentPosition };
|
|
1951
1963
|
this.solved = true;
|
|
1952
1964
|
}
|
|
1953
1965
|
}
|
|
1954
1966
|
_step() {
|
|
1955
|
-
if (this.
|
|
1967
|
+
if (this.offsetPadPoints.length === 0) return;
|
|
1956
1968
|
const { x: currentX, y: currentY } = this.currentPosition;
|
|
1957
1969
|
let weightedSumX = 0;
|
|
1958
1970
|
let weightedSumY = 0;
|
|
1959
1971
|
let totalWeight = 0;
|
|
1960
|
-
for (const
|
|
1961
|
-
const
|
|
1962
|
-
|
|
1963
|
-
const
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1972
|
+
for (const pad of this.offsetPadPoints) {
|
|
1973
|
+
const targetPoints = this.targetPointMap.get(pad.id) || [];
|
|
1974
|
+
if (targetPoints.length === 0) continue;
|
|
1975
|
+
const padX = currentX + pad.offsetX;
|
|
1976
|
+
const padY = currentY + pad.offsetY;
|
|
1977
|
+
for (const targetPoint of targetPoints) {
|
|
1978
|
+
const dx2 = padX - targetPoint.x;
|
|
1979
|
+
const dy2 = padY - targetPoint.y;
|
|
1980
|
+
const distance = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
|
1981
|
+
let weight;
|
|
1982
|
+
if (this.useSquaredDistance) {
|
|
1983
|
+
weight = 1;
|
|
1984
|
+
} else {
|
|
1985
|
+
weight = distance < this.epsilon ? 1e6 : 1 / distance;
|
|
1986
|
+
}
|
|
1987
|
+
const targetForCurrentPos = {
|
|
1988
|
+
x: targetPoint.x - pad.offsetX,
|
|
1989
|
+
y: targetPoint.y - pad.offsetY
|
|
1990
|
+
};
|
|
1991
|
+
weightedSumX += weight * targetForCurrentPos.x;
|
|
1992
|
+
weightedSumY += weight * targetForCurrentPos.y;
|
|
1993
|
+
totalWeight += weight;
|
|
1969
1994
|
}
|
|
1970
|
-
weightedSumX += weight * targetPoint.x;
|
|
1971
|
-
weightedSumY += weight * targetPoint.y;
|
|
1972
|
-
totalWeight += weight;
|
|
1973
1995
|
}
|
|
1974
1996
|
const newPosition = {
|
|
1975
1997
|
x: totalWeight > 0 ? weightedSumX / totalWeight : currentX,
|
|
@@ -1993,13 +2015,65 @@ var IrlsSolver = class extends BaseSolver {
|
|
|
1993
2015
|
return this.optimalPosition || this.currentPosition;
|
|
1994
2016
|
}
|
|
1995
2017
|
/**
|
|
1996
|
-
*
|
|
2018
|
+
* Get the current absolute positions for all offset pad points
|
|
2019
|
+
*/
|
|
2020
|
+
getOffsetPadPositions() {
|
|
2021
|
+
const currentPos = this.getBestPosition();
|
|
2022
|
+
const padPositions = /* @__PURE__ */ new Map();
|
|
2023
|
+
for (const pad of this.offsetPadPoints) {
|
|
2024
|
+
padPositions.set(pad.id, {
|
|
2025
|
+
x: currentPos.x + pad.offsetX,
|
|
2026
|
+
y: currentPos.y + pad.offsetY
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
return padPositions;
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Get the absolute position for a specific offset pad point
|
|
2033
|
+
*/
|
|
2034
|
+
getOffsetPadPosition(padId) {
|
|
2035
|
+
const positions = this.getOffsetPadPositions();
|
|
2036
|
+
return positions.get(padId);
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Calculate total distance from current position to all assigned target points
|
|
1997
2040
|
*/
|
|
1998
2041
|
getTotalDistance(position) {
|
|
1999
|
-
const pos = position || this.
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
const
|
|
2042
|
+
const pos = position || this.getBestPosition();
|
|
2043
|
+
let totalDistance = 0;
|
|
2044
|
+
for (const pad of this.offsetPadPoints) {
|
|
2045
|
+
const padPosition = {
|
|
2046
|
+
x: pos.x + pad.offsetX,
|
|
2047
|
+
y: pos.y + pad.offsetY
|
|
2048
|
+
};
|
|
2049
|
+
const targetPoints = this.targetPointMap.get(pad.id) || [];
|
|
2050
|
+
for (const target of targetPoints) {
|
|
2051
|
+
const dx = padPosition.x - target.x;
|
|
2052
|
+
const dy = padPosition.y - target.y;
|
|
2053
|
+
if (this.useSquaredDistance) {
|
|
2054
|
+
totalDistance += dx * dx + dy * dy;
|
|
2055
|
+
} else {
|
|
2056
|
+
totalDistance += Math.sqrt(dx * dx + dy * dy);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
return totalDistance;
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Calculate total distance for a specific offset pad point
|
|
2064
|
+
*/
|
|
2065
|
+
getDistanceForPad(padId, position) {
|
|
2066
|
+
const pos = position || this.getBestPosition();
|
|
2067
|
+
const pad = this.offsetPadPoints.find((p) => p.id === padId);
|
|
2068
|
+
if (!pad) return 0;
|
|
2069
|
+
const padPosition = {
|
|
2070
|
+
x: pos.x + pad.offsetX,
|
|
2071
|
+
y: pos.y + pad.offsetY
|
|
2072
|
+
};
|
|
2073
|
+
const targetPoints = this.targetPointMap.get(padId) || [];
|
|
2074
|
+
return targetPoints.reduce((sum, target) => {
|
|
2075
|
+
const dx = padPosition.x - target.x;
|
|
2076
|
+
const dy = padPosition.y - target.y;
|
|
2003
2077
|
if (this.useSquaredDistance) {
|
|
2004
2078
|
return sum + (dx * dx + dy * dy);
|
|
2005
2079
|
} else {
|
|
@@ -2008,7 +2082,7 @@ var IrlsSolver = class extends BaseSolver {
|
|
|
2008
2082
|
}, 0);
|
|
2009
2083
|
}
|
|
2010
2084
|
computeProgress() {
|
|
2011
|
-
if (this.
|
|
2085
|
+
if (this.offsetPadPoints.length === 0) return 1;
|
|
2012
2086
|
const initialDistance = this.getTotalDistance(this.initialPosition);
|
|
2013
2087
|
const currentDistance = this.getTotalDistance();
|
|
2014
2088
|
if (initialDistance === 0) return 1;
|
|
@@ -2022,26 +2096,52 @@ var IrlsSolver = class extends BaseSolver {
|
|
|
2022
2096
|
rects: [],
|
|
2023
2097
|
circles: []
|
|
2024
2098
|
};
|
|
2025
|
-
|
|
2099
|
+
const colors = [
|
|
2100
|
+
"#4CAF50",
|
|
2101
|
+
"#2196F3",
|
|
2102
|
+
"#FF9800",
|
|
2103
|
+
"#9C27B0",
|
|
2104
|
+
"#F44336",
|
|
2105
|
+
"#607D8B"
|
|
2106
|
+
];
|
|
2107
|
+
const currentPos = this.getBestPosition();
|
|
2108
|
+
for (let i = 0; i < this.offsetPadPoints.length; i++) {
|
|
2109
|
+
const pad = this.offsetPadPoints[i];
|
|
2110
|
+
if (!pad) continue;
|
|
2111
|
+
const color = colors[i % colors.length];
|
|
2112
|
+
const targetPoints = this.targetPointMap.get(pad.id) || [];
|
|
2113
|
+
const padPosition = {
|
|
2114
|
+
x: currentPos.x + pad.offsetX,
|
|
2115
|
+
y: currentPos.y + pad.offsetY
|
|
2116
|
+
};
|
|
2117
|
+
for (const point of targetPoints) {
|
|
2118
|
+
graphics.points.push({
|
|
2119
|
+
...point,
|
|
2120
|
+
color
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
for (const point of targetPoints) {
|
|
2124
|
+
graphics.lines.push({
|
|
2125
|
+
points: [padPosition, point],
|
|
2126
|
+
strokeColor: color
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2026
2129
|
graphics.points.push({
|
|
2027
|
-
|
|
2028
|
-
|
|
2130
|
+
x: padPosition.x,
|
|
2131
|
+
y: padPosition.y,
|
|
2132
|
+
color
|
|
2029
2133
|
});
|
|
2030
2134
|
}
|
|
2031
2135
|
graphics.points.push({
|
|
2032
2136
|
...this.currentPosition,
|
|
2033
2137
|
color: "#f44336"
|
|
2138
|
+
// Red like in IrlsSolver
|
|
2034
2139
|
});
|
|
2035
|
-
for (const point of this.targetPoints) {
|
|
2036
|
-
graphics.lines.push({
|
|
2037
|
-
points: [this.currentPosition, point],
|
|
2038
|
-
strokeColor: "#666"
|
|
2039
|
-
});
|
|
2040
|
-
}
|
|
2041
2140
|
if (this.optimalPosition) {
|
|
2042
2141
|
graphics.points.push({
|
|
2043
2142
|
...this.optimalPosition,
|
|
2044
2143
|
color: "rgba(76, 175, 80, 0.3)"
|
|
2144
|
+
// Semi-transparent green like in IrlsSolver
|
|
2045
2145
|
});
|
|
2046
2146
|
}
|
|
2047
2147
|
return graphics;
|
|
@@ -2213,31 +2313,27 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
|
|
|
2213
2313
|
componentToPack: this.componentToPack
|
|
2214
2314
|
};
|
|
2215
2315
|
}
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
},
|
|
2227
|
-
{ minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }
|
|
2228
|
-
);
|
|
2229
|
-
if (params?.margin) {
|
|
2230
|
-
return {
|
|
2231
|
-
minX: bounds.minX - params.margin,
|
|
2232
|
-
minY: bounds.minY - params.margin,
|
|
2233
|
-
maxX: bounds.maxX + params.margin,
|
|
2234
|
-
maxY: bounds.maxY + params.margin
|
|
2235
|
-
};
|
|
2316
|
+
_getOutlineBoundsWithMargin(params = {}) {
|
|
2317
|
+
let minX = Infinity;
|
|
2318
|
+
let minY = Infinity;
|
|
2319
|
+
let maxX = -Infinity;
|
|
2320
|
+
let maxY = -Infinity;
|
|
2321
|
+
for (const [p1, p2] of this.fullOutline) {
|
|
2322
|
+
minX = Math.min(minX, p1.x, p2.x);
|
|
2323
|
+
minY = Math.min(minY, p1.y, p2.y);
|
|
2324
|
+
maxX = Math.max(maxX, p1.x, p2.x);
|
|
2325
|
+
maxY = Math.max(maxY, p1.y, p2.y);
|
|
2236
2326
|
}
|
|
2237
|
-
|
|
2327
|
+
const margin = params.margin ?? 0;
|
|
2328
|
+
return {
|
|
2329
|
+
minX: minX - margin,
|
|
2330
|
+
minY: minY - margin,
|
|
2331
|
+
maxX: maxX + margin,
|
|
2332
|
+
maxY: maxY + margin
|
|
2333
|
+
};
|
|
2238
2334
|
}
|
|
2239
2335
|
_setup() {
|
|
2240
|
-
const
|
|
2336
|
+
const { offsetPadPoints, targetPointMap } = this.getNetworkTargetPointMappings();
|
|
2241
2337
|
const [p1, p2] = this.outlineSegment;
|
|
2242
2338
|
const constraintFn = (point) => {
|
|
2243
2339
|
const projectedPoint = this.projectPointOntoSegment(
|
|
@@ -2250,11 +2346,11 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
|
|
|
2250
2346
|
const componentBounds = getInputComponentBounds(this.componentToPack, {
|
|
2251
2347
|
rotationDegrees: this.componentRotationDegrees
|
|
2252
2348
|
});
|
|
2253
|
-
const packedComponentBoundsWithMargin = this.
|
|
2349
|
+
const packedComponentBoundsWithMargin = this._getOutlineBoundsWithMargin({
|
|
2254
2350
|
margin: Math.max(
|
|
2255
2351
|
componentBounds.maxX - componentBounds.minX,
|
|
2256
2352
|
componentBounds.maxY - componentBounds.minY
|
|
2257
|
-
) * 2
|
|
2353
|
+
) * 2 + this.minGap * 2
|
|
2258
2354
|
});
|
|
2259
2355
|
const largestRectSolverParams = {
|
|
2260
2356
|
fullOutline: this.fullOutline.flatMap(([p]) => p),
|
|
@@ -2314,14 +2410,14 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
|
|
|
2314
2410
|
x: (vp1.x + vp2.x) / 2,
|
|
2315
2411
|
y: (vp1.y + vp2.y) / 2
|
|
2316
2412
|
});
|
|
2317
|
-
this.irlsSolver = new
|
|
2318
|
-
|
|
2413
|
+
this.irlsSolver = new MultiOffsetIrlsSolver({
|
|
2414
|
+
offsetPadPoints,
|
|
2415
|
+
targetPointMap,
|
|
2319
2416
|
initialPosition,
|
|
2320
2417
|
constraintFn,
|
|
2321
2418
|
epsilon: 1e-6,
|
|
2322
2419
|
maxIterations: 50,
|
|
2323
|
-
useSquaredDistance:
|
|
2324
|
-
// this.packStrategy === "minimum_sum_squared_distance_to_network",
|
|
2420
|
+
useSquaredDistance: this.packStrategy === "minimum_sum_squared_distance_to_network"
|
|
2325
2421
|
});
|
|
2326
2422
|
}
|
|
2327
2423
|
_step() {
|
|
@@ -2340,20 +2436,28 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
|
|
|
2340
2436
|
}
|
|
2341
2437
|
}
|
|
2342
2438
|
/**
|
|
2343
|
-
* Get
|
|
2439
|
+
* Get pad offset points and target point mappings for network connections
|
|
2344
2440
|
*/
|
|
2345
|
-
|
|
2346
|
-
const targetPoints = [];
|
|
2441
|
+
getNetworkTargetPointMappings() {
|
|
2347
2442
|
const rotatedPads = this.getRotatedComponentPads();
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2443
|
+
const offsetPadPoints = rotatedPads.map((pad) => ({
|
|
2444
|
+
id: pad.padId,
|
|
2445
|
+
offsetX: pad.offset.x,
|
|
2446
|
+
offsetY: pad.offset.y
|
|
2447
|
+
}));
|
|
2448
|
+
const targetPointMap = /* @__PURE__ */ new Map();
|
|
2449
|
+
for (const pad of rotatedPads) {
|
|
2450
|
+
const targetPoints = [];
|
|
2451
|
+
for (const packedComponent of this.packedComponents) {
|
|
2452
|
+
for (const packedPad of packedComponent.pads) {
|
|
2453
|
+
if (packedPad.networkId === pad.networkId) {
|
|
2454
|
+
targetPoints.push(packedPad.absoluteCenter);
|
|
2455
|
+
}
|
|
2353
2456
|
}
|
|
2354
2457
|
}
|
|
2458
|
+
targetPointMap.set(pad.padId, targetPoints);
|
|
2355
2459
|
}
|
|
2356
|
-
return
|
|
2460
|
+
return { offsetPadPoints, targetPointMap };
|
|
2357
2461
|
}
|
|
2358
2462
|
/**
|
|
2359
2463
|
* Project a point onto the outline segment
|
|
@@ -2579,13 +2683,6 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
|
|
|
2579
2683
|
}
|
|
2580
2684
|
}
|
|
2581
2685
|
if (this.irlsSolver) {
|
|
2582
|
-
const targetPoints = this.getNetworkTargetPoints();
|
|
2583
|
-
for (const point of targetPoints) {
|
|
2584
|
-
graphics.points.push({
|
|
2585
|
-
...point,
|
|
2586
|
-
color: "#4CAF50"
|
|
2587
|
-
});
|
|
2588
|
-
}
|
|
2589
2686
|
const currentPos = this.irlsSolver.currentPosition;
|
|
2590
2687
|
graphics.points.push({
|
|
2591
2688
|
...currentPos,
|
|
@@ -2700,6 +2797,7 @@ var SingleComponentPackSolver = class extends BaseSolver {
|
|
|
2700
2797
|
this.currentRotationIndex = 0;
|
|
2701
2798
|
}
|
|
2702
2799
|
executeSegmentCandidatePhase() {
|
|
2800
|
+
console.log("executeSegmentCandidatePhase");
|
|
2703
2801
|
if (this.activeSubSolver?.solved || this.activeSubSolver?.failed) {
|
|
2704
2802
|
const queuedSegment = this.queuedOutlineSegments[this.currentSegmentIndex];
|
|
2705
2803
|
const rotation = queuedSegment.availableRotations[this.currentRotationIndex];
|
|
@@ -2707,15 +2805,23 @@ var SingleComponentPackSolver = class extends BaseSolver {
|
|
|
2707
2805
|
let optimalPosition;
|
|
2708
2806
|
if (this.activeSubSolver.solved && this.activeSubSolver.optimalPosition) {
|
|
2709
2807
|
optimalPosition = this.activeSubSolver.optimalPosition;
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
optimalPosition,
|
|
2715
|
-
distance,
|
|
2716
|
-
segmentIndex: queuedSegment.segmentIndex,
|
|
2717
|
-
rotationIndex: this.currentRotationIndex
|
|
2808
|
+
const hasOverlap = checkOverlapWithPackedComponents({
|
|
2809
|
+
component: this.createPackedComponent(optimalPosition, rotation),
|
|
2810
|
+
packedComponents: this.packedComponents,
|
|
2811
|
+
minGap: this.minGap
|
|
2718
2812
|
});
|
|
2813
|
+
console.log("hasOverlap", hasOverlap);
|
|
2814
|
+
if (!hasOverlap) {
|
|
2815
|
+
distance = this.calculateDistance(optimalPosition, rotation);
|
|
2816
|
+
this.candidateResults.push({
|
|
2817
|
+
segment: queuedSegment.segment,
|
|
2818
|
+
rotation,
|
|
2819
|
+
optimalPosition,
|
|
2820
|
+
distance,
|
|
2821
|
+
segmentIndex: queuedSegment.segmentIndex,
|
|
2822
|
+
rotationIndex: this.currentRotationIndex
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2719
2825
|
}
|
|
2720
2826
|
this.currentRotationIndex++;
|
|
2721
2827
|
this.activeSubSolver = void 0;
|
package/package.json
CHANGED