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.
Files changed (3) hide show
  1. package/dist/index.d.ts +38 -16
  2. package/dist/index.js +267 -161
  3. 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 IrlsSolverParams {
237
- /** Target points to minimize distance to */
238
- targetPoints: Point[];
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 using the Weiszfeld algorithm
252
- * to find the geometric median (point that minimizes sum of distances to target points).
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
- * This solver can be used as a subsolver in other optimization problems where
255
- * you need to find the optimal position that minimizes total distance to a set of points.
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 IrlsSolver extends BaseSolver {
258
- targetPoints: Point[];
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: IrlsSolverParams);
266
- getConstructorParams(): IrlsSolverParams;
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
- * Calculate total distance from current position to all target points
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?: 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
- _getPackedComponentBounds(params?: {
335
+ _getOutlineBoundsWithMargin(params?: {
314
336
  margin?: number;
315
337
  }): Bounds;
316
338
  _setup(): void;
317
339
  _step(): void;
318
340
  /**
319
- * Get target points from network connections, considering the component's rotation
341
+ * Get pad offset points and target point mappings for network connections
320
342
  */
321
- private getNetworkTargetPoints;
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
- 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
- }
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 union = allPadPolys[0];
232
- for (let i = 1; i < allPadPolys.length; i++) {
233
- union = Flatten.BooleanOperations.unify(union, allPadPolys[i]);
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/IrlsSolver.ts
1918
- var IrlsSolver = class extends BaseSolver {
1919
- targetPoints;
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.targetPoints = params.targetPoints;
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
- targetPoints: this.targetPoints,
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
- if (this.targetPoints.length === 0) {
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.targetPoints.length === 0) return;
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 targetPoint of this.targetPoints) {
1961
- const dx2 = currentX - targetPoint.x;
1962
- const dy2 = currentY - targetPoint.y;
1963
- const distance = Math.sqrt(dx2 * dx2 + dy2 * dy2);
1964
- let weight;
1965
- if (this.useSquaredDistance) {
1966
- weight = 1;
1967
- } else {
1968
- weight = distance < this.epsilon ? 1e6 : 1 / distance;
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
- * Calculate total distance from current position to all target points
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.currentPosition;
2000
- return this.targetPoints.reduce((sum, target) => {
2001
- const dx = pos.x - target.x;
2002
- const dy = pos.y - target.y;
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.targetPoints.length === 0) return 1;
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
- for (const point of this.targetPoints) {
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
- ...point,
2028
- color: "#4CAF50"
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
- _getPackedComponentBounds(params = {}) {
2217
- const bounds = this.packedComponents.reduce(
2218
- (acc, component) => {
2219
- const componentBounds = getComponentBounds(component, 0);
2220
- return {
2221
- minX: Math.min(acc.minX, componentBounds.minX),
2222
- minY: Math.min(acc.minY, componentBounds.minY),
2223
- maxX: Math.max(acc.maxX, componentBounds.maxX),
2224
- maxY: Math.max(acc.maxY, componentBounds.maxY)
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
- return bounds;
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 targetPoints = this.getNetworkTargetPoints();
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._getPackedComponentBounds({
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 IrlsSolver({
2318
- targetPoints,
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: true
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 target points from network connections, considering the component's rotation
2439
+ * Get pad offset points and target point mappings for network connections
2344
2440
  */
2345
- getNetworkTargetPoints() {
2346
- const targetPoints = [];
2441
+ getNetworkTargetPointMappings() {
2347
2442
  const rotatedPads = this.getRotatedComponentPads();
2348
- const componentNetworkIds = new Set(rotatedPads.map((pad) => pad.networkId));
2349
- for (const packedComponent of this.packedComponents) {
2350
- for (const pad of packedComponent.pads) {
2351
- if (componentNetworkIds.has(pad.networkId)) {
2352
- targetPoints.push(pad.absoluteCenter);
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 targetPoints;
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
- distance = this.calculateDistance(optimalPosition, rotation);
2711
- this.candidateResults.push({
2712
- segment: queuedSegment.segment,
2713
- rotation,
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
@@ -2,7 +2,7 @@
2
2
  "name": "calculate-packing",
3
3
  "main": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.0.22",
5
+ "version": "0.0.24",
6
6
  "description": "Calculate a packing layout with support for different strategy configurations",
7
7
  "scripts": {
8
8
  "start": "cosmos",