矩形排版——CAD c#
矩形排版算法核心是 **“贪心策略 + 旋转优化”**,整体分 4 大步骤,从数据准备到最终放置,每一步都围绕 “尽可能紧凑、不浪费空间” 设计,具体步骤拆解如下:
步骤 1:数据初始化与预处理(为排版做准备)
这一步是 “整理材料”,明确 “大板材多大、要放哪些小零件”,并做排序优化。
- 定义数据结构:用
RectangleEntity类封装所有矩形(大板材和小零件),记录关键信息 —— 尺寸(宽 / 高)、位置(左下角坐标)、是否旋转、面积。 - 输入与赋值:通过 CAD 交互获取用户输入的 “大板材宽高” 和 “每个小零件的宽高”,分别创建板材对象和零件对象。
- 零件排序:将所有小零件按「面积降序」排列(贪心算法的核心)。因为先放大零件能减少 “大零件最后没地方放” 的浪费,比随机放或先放小零件更紧凑。
步骤 2:单个零件的放置尝试(核心逻辑)
对排序后的每个零件,先试 “不旋转”,放不下再试 “旋转”,确保用最适配的姿态找空位。
- 首次尝试:不旋转放置直接用零件原始宽高,去板材里找能容纳的空位。
- 二次尝试:旋转后放置如果不旋转放不下,调用
Rotate()方法交换零件的宽高(比如 10×20 的零件变成 20×10),再重新找空位。 - 失败处理:若旋转后仍放不下,说明当前零件无法放入板材,直接返回排版失败(提示用户调整尺寸)。
步骤 3:空位搜索与位置验证(确保不重叠、不超界)
这一步是 “找地方”,按固定规则搜索空位,并验证位置是否有效,避免零件重叠或超出板材。
3.1 空位搜索规则(贪心式找空位)
按 “左→右、上→下” 的优先级找空位,优先利用已排零件周围的空间,减少零散空隙:
- 场景 1:没有已排零件(第一个零件)直接放在板材左下角(板材
Position位置),只要零件尺寸≤板材尺寸即可。 - 场景 2:有已排零件遍历所有已排好的零件,尝试两个关键空位:
- 已排零件的右侧:零件的 X 坐标 = 已排零件的 X 坐标 + 已排零件宽度(对齐底部)。
- 已排零件的上侧:零件的 Y 坐标 = 已排零件的 Y 坐标 + 已排零件高度(对齐左侧)。
3.2 位置验证(2 个核心检查)
找到空位后,必须验证两个条件,确保位置有效:
- 不超出板材边界:零件的 “右下角坐标”(X + 宽、Y + 高)≤ 板材的 “右下角坐标”,避免超出板材。
- 不与已排零件重叠:用 “轴对齐矩形重叠判断”—— 两个矩形在 X 方向和 Y 方向都有交集才算重叠。只要不同时满足 “X 有交集” 和 “Y 有交集”,就不会重叠。
步骤 4:循环与结果输出(完成所有零件排版)
重复步骤 2-3,直到所有零件都放完,或遇到无法放置的零件为止:
- 循环执行:按零件排序顺序,逐个完成 “放置尝试→空位搜索→位置验证”,放好的零件加入 “已排零件列表”。
- 排版完成:所有零件都放完后,在 CAD 中绘制结果 —— 蓝色大矩形(板材)、彩色小矩形(零件,旋转为红色,不旋转为绿色),并标注零件旋转状态。
- 反馈用户:输出排版结果(如 “共放置 5 个零件,无浪费”),或失败提示(如 “第 3 个零件无法放入”)。
总结:算法逻辑链
整个流程可以简化为一句话:“大零件先放→不旋转先试→旋转再试→找已排零件的右侧 / 上侧空位→验证不超界不重叠→放完所有零件”
排版核心算法:贪心+旋转优化
public class RectangleNesting
{private readonly RectangleEntity _board; // 大板材private readonly List<RectangleEntity> _parts; // 待排版零件private List<RectangleEntity> _nestedParts; // 已排好的零件(结果)/// <summary>/// 构造函数:传入板材和零件列表/// </summary>public RectangleNesting(RectangleEntity board, List<RectangleEntity> parts){_board = board;// 零件按面积降序排序(贪心策略:先放大零件)_parts = parts.OrderByDescending(p => p.Area).ToList();_nestedParts = new List<RectangleEntity>();}/// <summary>/// 执行排版:核心逻辑/// </summary>/// <returns>排好的零件列表(null表示有零件无法放入)</returns>public List<RectangleEntity> DoNesting(){foreach (var part in _parts){bool isPlaced = false;// 先尝试「不旋转」放置if (TryPlacePart(part)){isPlaced = true;}// 不旋转放不下,尝试「旋转」后放置else{part.Rotate(); // 旋转零件if (TryPlacePart(part)){isPlaced = true;}// 旋转后仍放不下,恢复旋转状态并返回失败else{part.Rotate();return null;}}if (isPlaced){_nestedParts.Add(part);}}return _nestedParts;}/// <summary>/// 尝试放置单个零件:查找第一个能容纳的空白区域/// </summary>/// <param name="part">待放置零件</param>/// <returns>是否放置成功</returns>private bool TryPlacePart(RectangleEntity part){// 情况1:没有已排零件,直接放板材左上角if (!_nestedParts.Any()){// 检查零件是否超出板材尺寸if (part.Width <= _board.Width && part.Height <= _board.Height){part.Position = _board.Position; // 左下角对齐板材左下角return true;}return false;}// 情况2:已有排好的零件,按「左→右、上→下」找空位// 遍历所有已排零件的「右侧」和「上侧」空位(贪心常用空位搜索方向)foreach (var placedPart in _nestedParts){// 尝试放在已排零件的「右侧」Point3d rightPos = new Point3d(placedPart.Position.X + placedPart.Width, // X坐标=已排零件右边缘placedPart.Position.Y, // Y坐标=已排零件下边缘(对齐底部)0);if (IsPositionValid(part, rightPos)){part.Position = rightPos;return true;}// 尝试放在已排零件的「上侧」Point3d topPos = new Point3d(placedPart.Position.X, // X坐标=已排零件下边缘(对齐左侧)placedPart.Position.Y + placedPart.Height, // Y坐标=已排零件上边缘0);if (IsPositionValid(part, topPos)){part.Position = topPos;return true;}}// 所有空位都放不下return false;}/// <summary>/// 验证位置是否有效:1.不超出板材 2.不与已排零件重叠/// </summary>private bool IsPositionValid(RectangleEntity part, Point3d pos){// 1. 检查零件是否超出板材边界bool isWithinBoard =pos.X + part.Width <= _board.Position.X + _board.Width && // 右边缘不超板材pos.Y + part.Height <= _board.Position.Y + _board.Height; // 上边缘不超板材if (!isWithinBoard) return false;// 2. 检查零件是否与已排零件重叠(轴对齐矩形重叠判断)foreach (var placedPart in _nestedParts){// 重叠条件:两个矩形在X、Y方向都有交集bool isOverlapX = pos.X < placedPart.Position.X + placedPart.Width &&pos.X + part.Width > placedPart.Position.X;bool isOverlapY = pos.Y < placedPart.Position.Y + placedPart.Height &&pos.Y + part.Height > placedPart.Position.Y;if (isOverlapX && isOverlapY){return false; // 重叠,位置无效}}return true; // 位置有效}
}
