You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

2D堆叠优化:如何正确旋转末行/列矩形以最大化托盘空间利用率

2D堆叠优化:如何正确旋转末行/列矩形以最大化托盘空间利用率

我完全懂你现在的困扰——在托盘上规整堆叠盒子时,最后总会剩下一块空间,明明看起来能放下更多盒子,却因为没找到正确的旋转和放置方式浪费了。先看看你现有的实现,咱们一步步来优化它。

首先,你已经定义了Rect类来处理矩形的属性和碰撞检测,还有Pallet类来管理托盘和填充逻辑。不过先提个小问题:原Rect类里的collides_with方法逻辑有点问题,轴对齐矩形的碰撞检测应该用更简洁准确的判断,我先帮你修正这个,避免后续添加盒子时误判碰撞。

修正后的Rect类

from typing import List, Tuple
import matplotlib.pyplot as plt
import matplotlib.patches as patches

class Rect:
    def __init__(self, corner: Tuple[float, float], size: Tuple[float, float]):
        self.length = max(size)
        self.width  = min(size)
        self.center = (corner[0] + self.length/2, corner[1] + self.width/2)
        self.size = (self.length, self.width)

    @property
    def min_corner(self) -> Tuple[float, float]:
        return (self.center[0] - self.size[0]/2, self.center[1] - self.size[1]/2)

    @property
    def max_corner(self) -> Tuple[float, float]:
        return (self.center[0] + self.size[0]/2, self.center[1] + self.size[1]/2)

    @property
    def area(self) -> float:
        return self.length * self.width

    def collides_with(self, other: 'Rect') -> bool:
        """Checks if this rectangle collides with another rectangle."""
        self_min_x, self_min_y = self.min_corner
        self_max_x, self_max_y = self.max_corner

        other_min_x, other_min_y = other.min_corner
        other_max_x, other_max_y = other.max_corner

        # 轴对齐矩形碰撞检测:判断两个矩形是否存在重叠
        # 不重叠的四种情况:一个在另一个的左、右、上、下方,取反就是重叠
        return not (
            self_max_x <= other_min_x
            or self_min_x >= other_max_x
            or self_max_y <= other_min_y
            or self_min_y >= other_max_y
        )

    def get_patch(self):
        """Returns a matplotlib Rectangle patch for visualization."""
        x, y = self.min_corner
        rect_width, rect_height = self.size
        return patches.Rectangle(
            (x, y),
            rect_width,
            rect_height,
            edgecolor='red',
            facecolor='lightgreen',
            linewidth=1
        )

优化后的Pallet类

接下来重点优化fill_with_rects方法,让它在常规填充后自动检查剩余空间,尝试用旋转后的盒子(交换原尺寸的长和宽)来填补空隙。核心思路是:

  • 先按原尺寸铺满托盘
  • 计算水平和垂直方向的剩余空间
  • 判断剩余空间是否能容纳旋转后的盒子,如果可以就批量添加
class Pallet:
    def __init__(self, size: Tuple[float, float]):
        self.size = size
        self.length = max(size)
        self.width  = min(size)
        self.rects: List[Rect] = []

    def add_rect(self, rect: Rect) -> bool:
        """Attempts to add a rectangle to the pallet. Returns True if successful, False otherwise."""
        if rect.area > self.length * self.width:
            return False

        # 检查矩形是否超出托盘边界
        max_corner = rect.max_corner
        min_corner = rect.min_corner
        x_max, y_max = max_corner
        x_min, y_min = min_corner
        if (not (0 <= x_max <= self.length and 0 <= y_max <= self.width)) or (x_min < 0 or y_min < 0):
            print("Out of Pallet")
            return False

        for r in self.rects:
            if r.collides_with(rect):
                print("Collision")
                return False

        self.rects.append(rect)
        return True

    def fill_with_rects(self, rect_size: Tuple[float, float]):
        rect_length = rect_size[0]
        rect_width = rect_size[1]
        # 旋转后的盒子尺寸:交换原长和宽
        rotated_size = (rect_width, rect_length)
        rotated_length = rotated_size[0]
        rotated_width = rotated_size[1]

        # 第一步:按原尺寸常规填充托盘
        rows_x = int(self.length // rect_length)
        cols_y = int(self.width // rect_width)

        for i in range(rows_x):
            for j in range(cols_y):
                cx = rect_length * i
                cy = rect_width * j
                box = Rect(corner=(cx, cy), size=(rect_length, rect_width))
                self.add_rect(box)

        # 第二步:检查水平剩余空间,尝试添加旋转后的盒子
        remaining_x = self.length - rows_x * rect_length
        if remaining_x >= rotated_length:
            # 计算垂直方向能放多少个旋转后的盒子
            rotated_cols = int(self.width // rotated_width)
            for j in range(rotated_cols):
                cx = rows_x * rect_length
                cy = rotated_width * j
                box = Rect(corner=(cx, cy), size=rotated_size)
                self.add_rect(box)

        # 第三步:检查垂直剩余空间,尝试添加旋转后的盒子(可选,根据需求开启)
        remaining_y = self.width - cols_y * rect_width
        if remaining_y >= rotated_length:
            # 计算水平方向能放多少个旋转后的盒子
            rotated_rows = int(self.length // rotated_width)
            for i in range(rotated_rows):
                cx = rotated_width * i
                cy = cols_y * rect_width
                box = Rect(corner=(cx, cy), size=rotated_size)
                self.add_rect(box)

    def visualize(self):
        fig, ax = plt.subplots(figsize=(10, 8))
        ax.set_xlim(0, self.length)
        ax.set_ylim(0, self.width)
        ax.set_aspect('equal')
        for box in self.rects:
            box_patch = box.get_patch()
            ax.add_patch(box_patch)
        ax.set_xlabel("Pallet Length")
        ax.set_ylabel("Pallet Width")

        plt.grid(True)
        plt.show()

测试代码

用你原来的测试逻辑运行,现在就能看到剩余空间被旋转后的盒子填满了:

if __name__ == "__main__":
    # Filling a pallet
    pallet = Pallet(size=(120, 100))
    pallet.fill_with_rects((32, 17))
    print("Number of rectangles in pallet:", len(pallet.rects))
    pallet.visualize()

效果对比

  • 当前原代码结果:托盘上按32x17的尺寸排列了3行(120//32=3)、5列(100//17=5),共15个盒子,右侧剩余120-3*32=24的空间,没法放下32长度的盒子,但足够放下旋转后的17长度的盒子。
  • 优化后期望结果:在右侧剩余空间里多放一列旋转后的盒子(17x32),总共能放15+5=20个盒子,完美利用了剩余空间。

备注:内容来源于stack exchange,提问作者Bilal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 18:04:33