calculate-packing 0.0.36 → 0.0.38

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
@@ -92,6 +92,7 @@ declare const convertCircuitJsonToPackOutput: (circuitJson: CircuitJson, opts?:
92
92
  top: number;
93
93
  bottom: number;
94
94
  }>;
95
+ obstacles?: InputObstacle[];
95
96
  }) => PackOutput;
96
97
 
97
98
  declare const getGraphicsFromPackOutput: (packOutput: PackOutput) => GraphicsObject;
@@ -390,11 +391,6 @@ declare class OutlineSegmentCandidatePointSolver extends BaseSolver {
390
391
  * Adjust position to avoid component bounds crossing to the inside of the outline
391
392
  */
392
393
  private adjustPositionForOutlineCollision;
393
- /**
394
- * Get the outward normal for the current segment by determining which side
395
- * is farther from the outline centroid
396
- */
397
- private getOutwardNormal;
398
394
  visualize(): GraphicsObject;
399
395
  }
400
396
 
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;
@@ -966,6 +998,89 @@ function pointInOutline(p, segments, rule = "even-odd") {
966
998
  }
967
999
  }
968
1000
 
1001
+ // lib/OutlineSegmentCandidatePointSolver/getOutwardNormal.ts
1002
+ function getOutwardNormal(outlineSegment, fullOutline) {
1003
+ const [p1, p2] = outlineSegment;
1004
+ const dx = p2.x - p1.x;
1005
+ const dy = p2.y - p1.y;
1006
+ const len = Math.hypot(dx, dy);
1007
+ if (len === 0) {
1008
+ return { x: 0, y: 1 };
1009
+ }
1010
+ const dirX = dx / len;
1011
+ const dirY = dy / len;
1012
+ const left = { x: -dirY, y: dirX };
1013
+ const right = { x: dirY, y: -dirX };
1014
+ const mid = {
1015
+ x: (p1.x + p2.x) / 2,
1016
+ y: (p1.y + p2.y) / 2
1017
+ };
1018
+ const bbox = getOutlineBoundsWithMargin(fullOutline);
1019
+ const scale = Math.max(bbox.maxX - bbox.minX, bbox.maxY - bbox.minY) || 1;
1020
+ const testDistance = Math.max(1e-4, 1e-3 * scale);
1021
+ const testLeft = {
1022
+ x: mid.x + left.x * testDistance,
1023
+ y: mid.y + left.y * testDistance
1024
+ };
1025
+ const testRight = {
1026
+ x: mid.x + right.x * testDistance,
1027
+ y: mid.y + right.y * testDistance
1028
+ };
1029
+ const locLeft = pointInOutline(testLeft, fullOutline);
1030
+ if (locLeft === "outside") {
1031
+ return left;
1032
+ }
1033
+ const locRight = pointInOutline(testRight, fullOutline);
1034
+ if (locRight === "outside") {
1035
+ return right;
1036
+ }
1037
+ const verts = [];
1038
+ if (fullOutline.length > 0) {
1039
+ verts.push(fullOutline[0][0]);
1040
+ for (const seg of fullOutline) {
1041
+ verts.push(seg[1]);
1042
+ }
1043
+ }
1044
+ const signedArea = (() => {
1045
+ let a = 0;
1046
+ for (let i = 0; i < verts.length; i++) {
1047
+ const v1 = verts[i];
1048
+ const v2 = verts[(i + 1) % verts.length];
1049
+ a += v1.x * v2.y - v2.x * v1.y;
1050
+ }
1051
+ return a / 2;
1052
+ })();
1053
+ if (Math.abs(signedArea) > 1e-12) {
1054
+ return signedArea > 0 ? right : left;
1055
+ }
1056
+ const center = {
1057
+ x: (bbox.minX + bbox.maxX) / 2,
1058
+ y: (bbox.minY + bbox.maxY) / 2
1059
+ };
1060
+ const away = { x: mid.x - center.x, y: mid.y - center.y };
1061
+ const dotLeft = left.x * away.x + left.y * away.y;
1062
+ const dotRight = right.x * away.x + right.y * away.y;
1063
+ return dotRight >= dotLeft ? right : left;
1064
+ }
1065
+ function getOutlineBoundsWithMargin(fullOutline, margin = 0) {
1066
+ let minX = Infinity;
1067
+ let minY = Infinity;
1068
+ let maxX = -Infinity;
1069
+ let maxY = -Infinity;
1070
+ for (const [p1, p2] of fullOutline) {
1071
+ minX = Math.min(minX, p1.x, p2.x);
1072
+ minY = Math.min(minY, p1.y, p2.y);
1073
+ maxX = Math.max(maxX, p1.x, p2.x);
1074
+ maxY = Math.max(maxY, p1.y, p2.y);
1075
+ }
1076
+ return {
1077
+ minX: minX - margin,
1078
+ minY: minY - margin,
1079
+ maxX: maxX + margin,
1080
+ maxY: maxY + margin
1081
+ };
1082
+ }
1083
+
969
1084
  // lib/LargestRectOutsideOutlineFromPointSolver.ts
970
1085
  var LargestRectOutsideOutlineFromPointSolver = class extends BaseSolver {
971
1086
  fullOutline;
@@ -1356,7 +1471,10 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
1356
1471
  );
1357
1472
  return this.adjustPositionForOutlineCollision(projectedPoint);
1358
1473
  };
1359
- const outwardNormal = this.getOutwardNormal();
1474
+ const outwardNormal = getOutwardNormal(
1475
+ this.outlineSegment,
1476
+ this.fullOutline
1477
+ );
1360
1478
  const componentBounds = getInputComponentBounds(this.componentToPack, {
1361
1479
  rotationDegrees: this.componentRotationDegrees
1362
1480
  });
@@ -1556,7 +1674,10 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
1556
1674
  adjustPositionForOutlineCollision(center) {
1557
1675
  const tempComponent = this.createTemporaryPackedComponent(center);
1558
1676
  const bounds = getComponentBounds(tempComponent, 0);
1559
- const outwardNormal = this.getOutwardNormal();
1677
+ const outwardNormal = getOutwardNormal(
1678
+ this.outlineSegment,
1679
+ this.fullOutline
1680
+ );
1560
1681
  const isHorizontalNormal = Math.abs(outwardNormal.x) > Math.abs(outwardNormal.y);
1561
1682
  const isVerticalNormal = !isHorizontalNormal;
1562
1683
  if (isHorizontalNormal) {
@@ -1591,45 +1712,6 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver {
1591
1712
  }
1592
1713
  throw new Error("unreachable");
1593
1714
  }
1594
- /**
1595
- * Get the outward normal for the current segment by determining which side
1596
- * is farther from the outline centroid
1597
- */
1598
- getOutwardNormal() {
1599
- const [p1, p2] = this.outlineSegment;
1600
- const segmentX = p2.x - p1.x;
1601
- const segmentY = p2.y - p1.y;
1602
- const segmentLength = Math.hypot(segmentX, segmentY);
1603
- if (segmentLength === 0) {
1604
- return { x: 0, y: 1 };
1605
- }
1606
- const segmentDirX = segmentX / segmentLength;
1607
- const segmentDirY = segmentY / segmentLength;
1608
- const normal1X = -segmentDirY;
1609
- const normal1Y = segmentDirX;
1610
- const normal2X = segmentDirY;
1611
- const normal2Y = -segmentDirX;
1612
- const segmentMidpoint = {
1613
- x: (p1.x + p2.x) / 2,
1614
- y: (p1.y + p2.y) / 2
1615
- };
1616
- const testDistance = 1e-4;
1617
- const testPoint1 = {
1618
- x: segmentMidpoint.x + normal1X * testDistance,
1619
- y: segmentMidpoint.y + normal1Y * testDistance
1620
- };
1621
- const testPoint2 = {
1622
- x: segmentMidpoint.x + normal2X * testDistance,
1623
- y: segmentMidpoint.y + normal2Y * testDistance
1624
- };
1625
- if (pointInOutline(testPoint1, this.fullOutline) === "outside") {
1626
- return { x: normal1X, y: normal1Y };
1627
- }
1628
- if (pointInOutline(testPoint2, this.fullOutline) === "outside") {
1629
- return { x: normal2X, y: normal2Y };
1630
- }
1631
- throw new Error("No outward normal found");
1632
- }
1633
1715
  visualize() {
1634
1716
  const graphics = {
1635
1717
  lines: [],
@@ -1764,6 +1846,18 @@ var getGraphicsFromPackOutput = (packOutput) => {
1764
1846
  )
1765
1847
  );
1766
1848
  const colorMap = createColorMapFromStrings(allNetworkIds);
1849
+ if (packOutput.obstacles && packOutput.obstacles.length > 0) {
1850
+ for (const obstacle of packOutput.obstacles) {
1851
+ rects.push({
1852
+ center: { x: obstacle.absoluteCenter.x, y: obstacle.absoluteCenter.y },
1853
+ width: obstacle.width,
1854
+ height: obstacle.height,
1855
+ fill: "rgba(0,0,0,0.1)",
1856
+ stroke: "#555",
1857
+ label: obstacle.obstacleId
1858
+ });
1859
+ }
1860
+ }
1767
1861
  for (const component of packOutput.components) {
1768
1862
  const bounds = getComponentBounds(component);
1769
1863
  const width = bounds.maxX - bounds.minX;
@@ -1850,6 +1944,7 @@ function checkOverlapWithPackedComponents({
1850
1944
  }
1851
1945
 
1852
1946
  // lib/SingleComponentPackSolver/SingleComponentPackSolver.ts
1947
+ import { computeDistanceBetweenBoxes as computeDistanceBetweenBoxes2 } from "@tscircuit/math-utils";
1853
1948
  var SingleComponentPackSolver = class extends BaseSolver {
1854
1949
  componentToPack;
1855
1950
  packedComponents;
@@ -1904,17 +1999,34 @@ var SingleComponentPackSolver = class extends BaseSolver {
1904
1999
  const availableRotations2 = this.componentToPack.availableRotationDegrees ?? [0, 90, 180, 270];
1905
2000
  const position = { x: 0, y: 0 };
1906
2001
  const rotation = availableRotations2[0] ?? 0;
1907
- this.outputPackedComponent = this.createPackedComponent(
1908
- position,
1909
- rotation
1910
- );
1911
- this.solved = true;
1912
- return;
2002
+ const candidate = this.createPackedComponent(position, rotation);
2003
+ const tooCloseToObstacles = (this.obstacles ?? []).some((obs) => {
2004
+ const obsBox = {
2005
+ center: { x: obs.absoluteCenter.x, y: obs.absoluteCenter.y },
2006
+ width: obs.width,
2007
+ height: obs.height
2008
+ };
2009
+ return candidate.pads.some((p) => {
2010
+ const padBox = {
2011
+ center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
2012
+ width: p.size.x,
2013
+ height: p.size.y
2014
+ };
2015
+ const { distance } = computeDistanceBetweenBoxes2(padBox, obsBox);
2016
+ return distance + 1e-6 < this.minGap;
2017
+ });
2018
+ });
2019
+ if (!tooCloseToObstacles) {
2020
+ this.outputPackedComponent = candidate;
2021
+ this.solved = true;
2022
+ return;
2023
+ }
1913
2024
  }
1914
2025
  this.outlines = constructOutlinesFromPackedComponents(
1915
2026
  this.packedComponents,
1916
2027
  {
1917
- minGap: this.minGap
2028
+ minGap: this.minGap,
2029
+ obstacles: this.obstacles
1918
2030
  }
1919
2031
  );
1920
2032
  const availableRotations = this.componentToPack.availableRotationDegrees ?? [0, 90, 180, 270];
@@ -1944,11 +2056,33 @@ var SingleComponentPackSolver = class extends BaseSolver {
1944
2056
  let optimalPosition;
1945
2057
  if (this.activeSubSolver.solved && this.activeSubSolver.optimalPosition) {
1946
2058
  optimalPosition = this.activeSubSolver.optimalPosition;
2059
+ const candidateComponent = this.createPackedComponent(
2060
+ optimalPosition,
2061
+ rotation
2062
+ );
1947
2063
  const { hasOverlap, gapDistance } = checkOverlapWithPackedComponents({
1948
- component: this.createPackedComponent(optimalPosition, rotation),
2064
+ component: candidateComponent,
1949
2065
  packedComponents: this.packedComponents,
1950
2066
  minGap: this.minGap
1951
2067
  });
2068
+ let minObstacleGapDistance = Infinity;
2069
+ const tooCloseToObstacles = (this.obstacles ?? []).some((obs) => {
2070
+ const obsBox = {
2071
+ center: { x: obs.absoluteCenter.x, y: obs.absoluteCenter.y },
2072
+ width: obs.width,
2073
+ height: obs.height
2074
+ };
2075
+ return candidateComponent.pads.some((p) => {
2076
+ const padBox = {
2077
+ center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
2078
+ width: p.size.x,
2079
+ height: p.size.y
2080
+ };
2081
+ const { distance: distance2 } = computeDistanceBetweenBoxes2(padBox, obsBox);
2082
+ minObstacleGapDistance = Math.min(minObstacleGapDistance, distance2);
2083
+ return distance2 + 1e-6 < this.minGap;
2084
+ });
2085
+ });
1952
2086
  distance = this.calculateDistance(optimalPosition, rotation);
1953
2087
  if (hasOverlap) {
1954
2088
  this.rejectedCandidates.push({
@@ -1960,6 +2094,16 @@ var SingleComponentPackSolver = class extends BaseSolver {
1960
2094
  rotationIndex: this.currentRotationIndex,
1961
2095
  gapDistance
1962
2096
  });
2097
+ } else if (tooCloseToObstacles) {
2098
+ this.rejectedCandidates.push({
2099
+ segment: queuedSegment.segment,
2100
+ rotation,
2101
+ optimalPosition,
2102
+ distance,
2103
+ segmentIndex: queuedSegment.segmentIndex,
2104
+ rotationIndex: this.currentRotationIndex,
2105
+ gapDistance: minObstacleGapDistance
2106
+ });
1963
2107
  } else {
1964
2108
  this.candidateResults.push({
1965
2109
  segment: queuedSegment.segment,
@@ -2192,6 +2336,7 @@ gap_distance=${candidate.gapDistance}`,
2192
2336
  };
2193
2337
 
2194
2338
  // lib/PackSolver2/PackSolver2.ts
2339
+ import { computeDistanceBetweenBoxes as computeDistanceBetweenBoxes3 } from "@tscircuit/math-utils";
2195
2340
  var PackSolver2 = class extends BaseSolver {
2196
2341
  packInput;
2197
2342
  unpackedComponentQueue = [];
@@ -2225,7 +2370,41 @@ var PackSolver2 = class extends BaseSolver {
2225
2370
  }))
2226
2371
  };
2227
2372
  setPackedComponentPadCenters(newPackedComponent);
2228
- this.packedComponents.push(newPackedComponent);
2373
+ const obstacles = this.packInput.obstacles ?? [];
2374
+ const tooCloseToObstacles = obstacles.some((obs) => {
2375
+ const obsBox = {
2376
+ center: { x: obs.absoluteCenter.x, y: obs.absoluteCenter.y },
2377
+ width: obs.width,
2378
+ height: obs.height
2379
+ };
2380
+ return newPackedComponent.pads.some((p) => {
2381
+ const padBox = {
2382
+ center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
2383
+ width: p.size.x,
2384
+ height: p.size.y
2385
+ };
2386
+ const { distance } = computeDistanceBetweenBoxes3(padBox, obsBox);
2387
+ return distance + 1e-6 < this.packInput.minGap;
2388
+ });
2389
+ });
2390
+ if (!tooCloseToObstacles) {
2391
+ this.packedComponents.push(newPackedComponent);
2392
+ return;
2393
+ }
2394
+ const fallbackSolver = new SingleComponentPackSolver({
2395
+ packedComponents: [],
2396
+ componentToPack: firstComponentToPack,
2397
+ packPlacementStrategy: this.packInput.packPlacementStrategy,
2398
+ minGap: this.packInput.minGap,
2399
+ obstacles
2400
+ });
2401
+ fallbackSolver.solve();
2402
+ const result = fallbackSolver.getResult();
2403
+ if (result) {
2404
+ this.packedComponents.push(result);
2405
+ } else {
2406
+ this.packedComponents.push(newPackedComponent);
2407
+ }
2229
2408
  }
2230
2409
  _step() {
2231
2410
  if (this.solved || this.failed) return;
@@ -2562,7 +2741,8 @@ var convertCircuitJsonToPackOutput = (circuitJson, opts = {}) => {
2562
2741
  components: [],
2563
2742
  minGap: 0,
2564
2743
  packOrderStrategy: "largest_to_smallest",
2565
- packPlacementStrategy: "shortest_connection_along_outline"
2744
+ packPlacementStrategy: "shortest_connection_along_outline",
2745
+ obstacles: opts.obstacles
2566
2746
  };
2567
2747
  const tree = getCircuitJsonTree(circuitJson, {
2568
2748
  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.36",
5
+ "version": "0.0.38",
6
6
  "description": "Calculate a packing layout with support for different strategy configurations",
7
7
  "scripts": {
8
8
  "start": "cosmos",