calculate-packing 0.0.37 → 0.0.39

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 CHANGED
@@ -56,6 +56,12 @@ type PackPlacementStrategy = "shortest_connection_along_outline" | "minimum_sum_
56
56
  interface PackInput {
57
57
  components: InputComponent[];
58
58
  obstacles?: InputObstacle[];
59
+ bounds?: {
60
+ minX: number;
61
+ minY: number;
62
+ maxX: number;
63
+ maxY: number;
64
+ };
59
65
  minGap: number;
60
66
  packOrderStrategy: "largest_to_smallest";
61
67
  packPlacementStrategy: PackPlacementStrategy;
@@ -92,6 +98,7 @@ declare const convertCircuitJsonToPackOutput: (circuitJson: CircuitJson, opts?:
92
98
  top: number;
93
99
  bottom: number;
94
100
  }>;
101
+ obstacles?: InputObstacle[];
95
102
  }) => PackOutput;
96
103
 
97
104
  declare const getGraphicsFromPackOutput: (packOutput: PackOutput) => GraphicsObject;
@@ -351,6 +358,7 @@ declare class OutlineSegmentCandidatePointSolver extends BaseSolver {
351
358
  packedComponents: PackedComponent[];
352
359
  componentToPack: InputComponent;
353
360
  viableBounds?: Bounds;
361
+ globalBounds?: Bounds;
354
362
  optimalPosition?: Point$2;
355
363
  irlsSolver?: MultiOffsetIrlsSolver;
356
364
  twoPhaseIrlsSolver?: TwoPhaseIrlsSolver;
@@ -363,6 +371,7 @@ declare class OutlineSegmentCandidatePointSolver extends BaseSolver {
363
371
  packedComponents: PackedComponent[];
364
372
  componentToPack: InputComponent;
365
373
  obstacles?: InputObstacle[];
374
+ globalBounds?: Bounds;
366
375
  });
367
376
  getConstructorParams(): ConstructorParameters<typeof OutlineSegmentCandidatePointSolver>[0];
368
377
  _getOutlineBoundsWithMargin(params?: {
@@ -439,12 +448,14 @@ declare class SingleComponentPackSolver extends BaseSolver {
439
448
  }>;
440
449
  bestCandidate?: CandidateResult;
441
450
  outputPackedComponent?: PackedComponent;
451
+ bounds?: Bounds;
442
452
  constructor(params: {
443
453
  componentToPack: InputComponent;
444
454
  packedComponents: PackedComponent[];
445
455
  packPlacementStrategy: PackPlacementStrategy;
446
456
  minGap?: number;
447
457
  obstacles?: InputObstacle[];
458
+ bounds?: Bounds;
448
459
  });
449
460
  _setup(): void;
450
461
  _step(): void;
@@ -464,6 +475,7 @@ declare class SingleComponentPackSolver extends BaseSolver {
464
475
  packPlacementStrategy: PackPlacementStrategy;
465
476
  minGap: number;
466
477
  obstacles: InputObstacle[];
478
+ bounds: Bounds | undefined;
467
479
  };
468
480
  }
469
481
 
package/dist/index.js CHANGED
@@ -203,17 +203,49 @@ var createPadPolygons = (component, minGap) => {
203
203
  return { poly, bbox };
204
204
  });
205
205
  };
206
+ var createObstaclePolygons = (obstacles, minGap) => {
207
+ return obstacles.map((obs) => {
208
+ const hw = obs.width / 2 + minGap;
209
+ const hh = obs.height / 2 + minGap;
210
+ const cx = obs.absoluteCenter.x;
211
+ const cy = obs.absoluteCenter.y;
212
+ const worldCorners = [
213
+ { x: cx - hw, y: cy - hh },
214
+ { x: cx + hw, y: cy - hh },
215
+ { x: cx + hw, y: cy + hh },
216
+ { x: cx - hw, y: cy + hh }
217
+ ];
218
+ const arr = worldCorners.map(({ x, y }) => [x, y]);
219
+ const poly = new Flatten.Polygon(arr);
220
+ const xs = worldCorners.map((p) => p.x);
221
+ const ys = worldCorners.map((p) => p.y);
222
+ const bbox = {
223
+ minX: Math.min(...xs),
224
+ minY: Math.min(...ys),
225
+ maxX: Math.max(...xs),
226
+ maxY: Math.max(...ys)
227
+ };
228
+ return { poly, bbox };
229
+ });
230
+ };
206
231
  var constructOutlinesFromPackedComponents = (components, opts = {}) => {
207
- const { minGap = 0 } = opts;
208
- if (components.length === 0) return [];
209
- const bounds = combineBounds(
210
- components.map((c) => getComponentBounds(c, minGap))
211
- );
232
+ const { minGap = 0, obstacles = [] } = opts;
233
+ if (components.length === 0 && obstacles.length === 0) return [];
234
+ const componentBounds = components.map((c) => getComponentBounds(c, minGap));
235
+ const obstacleBounds = obstacles.map((o) => ({
236
+ minX: o.absoluteCenter.x - o.width / 2 - minGap,
237
+ minY: o.absoluteCenter.y - o.height / 2 - minGap,
238
+ maxX: o.absoluteCenter.x + o.width / 2 + minGap,
239
+ maxY: o.absoluteCenter.y + o.height / 2 + minGap
240
+ }));
241
+ const bounds = combineBounds([...componentBounds, ...obstacleBounds]);
212
242
  const allPadShapes = [];
213
243
  for (const component of components) {
214
244
  const padShapes = createPadPolygons(component, minGap);
215
245
  allPadShapes.push(...padShapes);
216
246
  }
247
+ const obstacleShapes = createObstaclePolygons(obstacles, minGap);
248
+ allPadShapes.push(...obstacleShapes);
217
249
  if (allPadShapes.length === 0) return [];
218
250
  const areaOfBox = (b) => Math.max(0, b.maxX - b.minX) * Math.max(0, b.maxY - b.minY);
219
251
  const containsBox = (outer, inner, eps = 1e-9) => outer.minX - eps <= inner.minX && outer.minY - eps <= inner.minY && outer.maxX + eps >= inner.maxX && outer.maxY + eps >= inner.maxY;
@@ -1384,6 +1416,7 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
1384
1416
  packedComponents;
1385
1417
  componentToPack;
1386
1418
  viableBounds;
1419
+ globalBounds;
1387
1420
  optimalPosition;
1388
1421
  irlsSolver;
1389
1422
  twoPhaseIrlsSolver;
@@ -1397,6 +1430,7 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
1397
1430
  this.packedComponents = params.packedComponents;
1398
1431
  this.componentToPack = params.componentToPack;
1399
1432
  this.obstacles = params.obstacles ?? [];
1433
+ this.globalBounds = params.globalBounds;
1400
1434
  }
1401
1435
  getConstructorParams() {
1402
1436
  return {
@@ -1407,7 +1441,8 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
1407
1441
  minGap: this.minGap,
1408
1442
  packedComponents: this.packedComponents,
1409
1443
  componentToPack: this.componentToPack,
1410
- obstacles: this.obstacles
1444
+ obstacles: this.obstacles,
1445
+ globalBounds: this.globalBounds
1411
1446
  };
1412
1447
  }
1413
1448
  _getOutlineBoundsWithMargin(params = {}) {
@@ -1699,6 +1734,19 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
1699
1734
  });
1700
1735
  }
1701
1736
  }
1737
+ if (this.globalBounds) {
1738
+ graphics.lines.push({
1739
+ points: [
1740
+ { x: this.globalBounds.minX, y: this.globalBounds.minY },
1741
+ { x: this.globalBounds.minX, y: this.globalBounds.maxY },
1742
+ { x: this.globalBounds.maxX, y: this.globalBounds.maxY },
1743
+ { x: this.globalBounds.maxX, y: this.globalBounds.minY },
1744
+ { x: this.globalBounds.minX, y: this.globalBounds.minY }
1745
+ ],
1746
+ strokeColor: "rgba(255,0,255,0.5)",
1747
+ strokeDash: "2 2"
1748
+ });
1749
+ }
1702
1750
  if (this.viableBounds) {
1703
1751
  graphics.rects.push({
1704
1752
  center: {
@@ -1814,6 +1862,18 @@ var getGraphicsFromPackOutput = (packOutput) => {
1814
1862
  )
1815
1863
  );
1816
1864
  const colorMap = createColorMapFromStrings(allNetworkIds);
1865
+ if (packOutput.obstacles && packOutput.obstacles.length > 0) {
1866
+ for (const obstacle of packOutput.obstacles) {
1867
+ rects.push({
1868
+ center: { x: obstacle.absoluteCenter.x, y: obstacle.absoluteCenter.y },
1869
+ width: obstacle.width,
1870
+ height: obstacle.height,
1871
+ fill: "rgba(0,0,0,0.1)",
1872
+ stroke: "#555",
1873
+ label: obstacle.obstacleId
1874
+ });
1875
+ }
1876
+ }
1817
1877
  for (const component of packOutput.components) {
1818
1878
  const bounds = getComponentBounds(component);
1819
1879
  const width = bounds.maxX - bounds.minX;
@@ -1900,6 +1960,7 @@ function checkOverlapWithPackedComponents({
1900
1960
  }
1901
1961
 
1902
1962
  // lib/SingleComponentPackSolver/SingleComponentPackSolver.ts
1963
+ import { computeDistanceBetweenBoxes as computeDistanceBetweenBoxes2 } from "@tscircuit/math-utils";
1903
1964
  var SingleComponentPackSolver = class extends BaseSolver {
1904
1965
  componentToPack;
1905
1966
  packedComponents;
@@ -1917,6 +1978,7 @@ var SingleComponentPackSolver = class extends BaseSolver {
1917
1978
  rejectedCandidates = [];
1918
1979
  bestCandidate;
1919
1980
  outputPackedComponent;
1981
+ bounds;
1920
1982
  constructor(params) {
1921
1983
  super();
1922
1984
  this.componentToPack = params.componentToPack;
@@ -1924,6 +1986,7 @@ var SingleComponentPackSolver = class extends BaseSolver {
1924
1986
  this.packPlacementStrategy = params.packPlacementStrategy;
1925
1987
  this.minGap = params.minGap ?? 0;
1926
1988
  this.obstacles = params.obstacles ?? [];
1989
+ this.bounds = params.bounds;
1927
1990
  }
1928
1991
  _setup() {
1929
1992
  super._setup();
@@ -1954,17 +2017,34 @@ var SingleComponentPackSolver = class extends BaseSolver {
1954
2017
  const availableRotations2 = this.componentToPack.availableRotationDegrees ?? [0, 90, 180, 270];
1955
2018
  const position = { x: 0, y: 0 };
1956
2019
  const rotation = availableRotations2[0] ?? 0;
1957
- this.outputPackedComponent = this.createPackedComponent(
1958
- position,
1959
- rotation
1960
- );
1961
- this.solved = true;
1962
- return;
2020
+ const candidate = this.createPackedComponent(position, rotation);
2021
+ const tooCloseToObstacles = (this.obstacles ?? []).some((obs) => {
2022
+ const obsBox = {
2023
+ center: { x: obs.absoluteCenter.x, y: obs.absoluteCenter.y },
2024
+ width: obs.width,
2025
+ height: obs.height
2026
+ };
2027
+ return candidate.pads.some((p) => {
2028
+ const padBox = {
2029
+ center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
2030
+ width: p.size.x,
2031
+ height: p.size.y
2032
+ };
2033
+ const { distance } = computeDistanceBetweenBoxes2(padBox, obsBox);
2034
+ return distance + 1e-6 < this.minGap;
2035
+ });
2036
+ });
2037
+ if (!tooCloseToObstacles) {
2038
+ this.outputPackedComponent = candidate;
2039
+ this.solved = true;
2040
+ return;
2041
+ }
1963
2042
  }
1964
2043
  this.outlines = constructOutlinesFromPackedComponents(
1965
2044
  this.packedComponents,
1966
2045
  {
1967
- minGap: this.minGap
2046
+ minGap: this.minGap,
2047
+ obstacles: this.obstacles
1968
2048
  }
1969
2049
  );
1970
2050
  const availableRotations = this.componentToPack.availableRotationDegrees ?? [0, 90, 180, 270];
@@ -1994,11 +2074,33 @@ var SingleComponentPackSolver = class extends BaseSolver {
1994
2074
  let optimalPosition;
1995
2075
  if (this.activeSubSolver.solved && this.activeSubSolver.optimalPosition) {
1996
2076
  optimalPosition = this.activeSubSolver.optimalPosition;
2077
+ const candidateComponent = this.createPackedComponent(
2078
+ optimalPosition,
2079
+ rotation
2080
+ );
1997
2081
  const { hasOverlap, gapDistance } = checkOverlapWithPackedComponents({
1998
- component: this.createPackedComponent(optimalPosition, rotation),
2082
+ component: candidateComponent,
1999
2083
  packedComponents: this.packedComponents,
2000
2084
  minGap: this.minGap
2001
2085
  });
2086
+ let minObstacleGapDistance = Infinity;
2087
+ const tooCloseToObstacles = (this.obstacles ?? []).some((obs) => {
2088
+ const obsBox = {
2089
+ center: { x: obs.absoluteCenter.x, y: obs.absoluteCenter.y },
2090
+ width: obs.width,
2091
+ height: obs.height
2092
+ };
2093
+ return candidateComponent.pads.some((p) => {
2094
+ const padBox = {
2095
+ center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
2096
+ width: p.size.x,
2097
+ height: p.size.y
2098
+ };
2099
+ const { distance: distance2 } = computeDistanceBetweenBoxes2(padBox, obsBox);
2100
+ minObstacleGapDistance = Math.min(minObstacleGapDistance, distance2);
2101
+ return distance2 + 1e-6 < this.minGap;
2102
+ });
2103
+ });
2002
2104
  distance = this.calculateDistance(optimalPosition, rotation);
2003
2105
  if (hasOverlap) {
2004
2106
  this.rejectedCandidates.push({
@@ -2010,6 +2112,16 @@ var SingleComponentPackSolver = class extends BaseSolver {
2010
2112
  rotationIndex: this.currentRotationIndex,
2011
2113
  gapDistance
2012
2114
  });
2115
+ } else if (tooCloseToObstacles) {
2116
+ this.rejectedCandidates.push({
2117
+ segment: queuedSegment.segment,
2118
+ rotation,
2119
+ optimalPosition,
2120
+ distance,
2121
+ segmentIndex: queuedSegment.segmentIndex,
2122
+ rotationIndex: this.currentRotationIndex,
2123
+ gapDistance: minObstacleGapDistance
2124
+ });
2013
2125
  } else {
2014
2126
  this.candidateResults.push({
2015
2127
  segment: queuedSegment.segment,
@@ -2044,7 +2156,8 @@ var SingleComponentPackSolver = class extends BaseSolver {
2044
2156
  minGap: this.minGap,
2045
2157
  packedComponents: this.packedComponents,
2046
2158
  componentToPack: this.componentToPack,
2047
- obstacles: this.obstacles
2159
+ obstacles: this.obstacles,
2160
+ globalBounds: this.bounds
2048
2161
  });
2049
2162
  this.activeSubSolver.setup();
2050
2163
  break;
@@ -2130,6 +2243,19 @@ var SingleComponentPackSolver = class extends BaseSolver {
2130
2243
  });
2131
2244
  }
2132
2245
  }
2246
+ if (this.bounds) {
2247
+ graphics.lines.push({
2248
+ points: [
2249
+ { x: this.bounds.minX, y: this.bounds.minY },
2250
+ { x: this.bounds.minX, y: this.bounds.maxY },
2251
+ { x: this.bounds.maxX, y: this.bounds.maxY },
2252
+ { x: this.bounds.maxX, y: this.bounds.minY },
2253
+ { x: this.bounds.minX, y: this.bounds.minY }
2254
+ ],
2255
+ strokeColor: "rgba(0,0,0,0.5)",
2256
+ strokeDash: "2 2"
2257
+ });
2258
+ }
2133
2259
  switch (this.currentPhase) {
2134
2260
  case "outline":
2135
2261
  this.visualizeOutlinePhase(graphics);
@@ -2236,12 +2362,14 @@ gap_distance=${candidate.gapDistance}`,
2236
2362
  packedComponents: this.packedComponents,
2237
2363
  packPlacementStrategy: this.packPlacementStrategy,
2238
2364
  minGap: this.minGap,
2239
- obstacles: this.obstacles
2365
+ obstacles: this.obstacles,
2366
+ bounds: this.bounds
2240
2367
  };
2241
2368
  }
2242
2369
  };
2243
2370
 
2244
2371
  // lib/PackSolver2/PackSolver2.ts
2372
+ import { computeDistanceBetweenBoxes as computeDistanceBetweenBoxes3 } from "@tscircuit/math-utils";
2245
2373
  var PackSolver2 = class extends BaseSolver {
2246
2374
  packInput;
2247
2375
  unpackedComponentQueue = [];
@@ -2275,7 +2403,42 @@ var PackSolver2 = class extends BaseSolver {
2275
2403
  }))
2276
2404
  };
2277
2405
  setPackedComponentPadCenters(newPackedComponent);
2278
- this.packedComponents.push(newPackedComponent);
2406
+ const obstacles = this.packInput.obstacles ?? [];
2407
+ const tooCloseToObstacles = obstacles.some((obs) => {
2408
+ const obsBox = {
2409
+ center: { x: obs.absoluteCenter.x, y: obs.absoluteCenter.y },
2410
+ width: obs.width,
2411
+ height: obs.height
2412
+ };
2413
+ return newPackedComponent.pads.some((p) => {
2414
+ const padBox = {
2415
+ center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
2416
+ width: p.size.x,
2417
+ height: p.size.y
2418
+ };
2419
+ const { distance } = computeDistanceBetweenBoxes3(padBox, obsBox);
2420
+ return distance + 1e-6 < this.packInput.minGap;
2421
+ });
2422
+ });
2423
+ if (!tooCloseToObstacles) {
2424
+ this.packedComponents.push(newPackedComponent);
2425
+ return;
2426
+ }
2427
+ const fallbackSolver = new SingleComponentPackSolver({
2428
+ packedComponents: [],
2429
+ componentToPack: firstComponentToPack,
2430
+ packPlacementStrategy: this.packInput.packPlacementStrategy,
2431
+ minGap: this.packInput.minGap,
2432
+ obstacles,
2433
+ bounds: this.packInput.bounds
2434
+ });
2435
+ fallbackSolver.solve();
2436
+ const result = fallbackSolver.getResult();
2437
+ if (result) {
2438
+ this.packedComponents.push(result);
2439
+ } else {
2440
+ this.packedComponents.push(newPackedComponent);
2441
+ }
2279
2442
  }
2280
2443
  _step() {
2281
2444
  if (this.solved || this.failed) return;
@@ -2302,7 +2465,8 @@ var PackSolver2 = class extends BaseSolver {
2302
2465
  componentToPack: this.componentToPack,
2303
2466
  packPlacementStrategy: this.packInput.packPlacementStrategy,
2304
2467
  minGap: this.packInput.minGap,
2305
- obstacles: this.packInput.obstacles ?? []
2468
+ obstacles: this.packInput.obstacles ?? [],
2469
+ bounds: this.packInput.bounds
2306
2470
  });
2307
2471
  this.activeSubSolver.setup();
2308
2472
  }
@@ -2357,6 +2521,19 @@ var PackSolver2 = class extends BaseSolver {
2357
2521
  });
2358
2522
  }
2359
2523
  }
2524
+ if (this.packInput.bounds) {
2525
+ graphics.lines.push({
2526
+ points: [
2527
+ { x: this.packInput.bounds.minX, y: this.packInput.bounds.minY },
2528
+ { x: this.packInput.bounds.minX, y: this.packInput.bounds.maxY },
2529
+ { x: this.packInput.bounds.maxX, y: this.packInput.bounds.maxY },
2530
+ { x: this.packInput.bounds.maxX, y: this.packInput.bounds.minY },
2531
+ { x: this.packInput.bounds.minX, y: this.packInput.bounds.minY }
2532
+ ],
2533
+ strokeColor: "rgba(0,0,0,0.5)",
2534
+ strokeDash: "2 2"
2535
+ });
2536
+ }
2360
2537
  if (this.packedComponents.length === 0) {
2361
2538
  for (const component of this.unpackedComponentQueue) {
2362
2539
  for (const pad of component.pads) {
@@ -2612,7 +2789,8 @@ var convertCircuitJsonToPackOutput = (circuitJson, opts = {}) => {
2612
2789
  components: [],
2613
2790
  minGap: 0,
2614
2791
  packOrderStrategy: "largest_to_smallest",
2615
- packPlacementStrategy: "shortest_connection_along_outline"
2792
+ packPlacementStrategy: "shortest_connection_along_outline",
2793
+ obstacles: opts.obstacles
2616
2794
  };
2617
2795
  const tree = getCircuitJsonTree(circuitJson, {
2618
2796
  source_group_id: opts.source_group_id
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.37",
5
+ "version": "0.0.39",
6
6
  "description": "Calculate a packing layout with support for different strategy configurations",
7
7
  "scripts": {
8
8
  "start": "cosmos",