using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using OfficeOpenXml.FormulaParsing.Excel.Functions.Math; using WIDESEAWCS_Core.Helper; using WIDESEAWCS_DTO.PlacedBlockDTO; using WIDESEAWCS_DTO.TaskInfo; using WIDESEAWCS_IBasicInfoService; namespace WIDESEAWCS_BasicInfoService { public class PlaceBlockService { public static int SPACING = AppSettings.GetValue("Spacing").ObjToInt(); public static int MaxRotateLength = AppSettings.GetValue("MaxRotateLength").ObjToInt(); /// /// 最大Y坐标限制(毫米) /// public const int MaxY = 600; /// /// 最小横向Y坐标限制(毫米) /// public static int MinY = AppSettings.GetValue("MinY").ObjToInt(); /// /// 吸盘横向长度 /// public const int SuctionLengthH = 920; /// /// 吸盘横向宽度 /// public const int SuctionWidthH = 530; /// /// 吸盘纵向长度 /// public const int SuctionLengthZ = 530; /// /// 吸盘纵向宽度 /// public const int SuctionWidthZ = 130; /// /// 容器尺寸 /// public ContainerSize ContainerSize { get; private set; } /// /// 已放置货物 /// public List PlacedBlocks { get; private set; } private readonly PlacedBlock containerFloor; public PlaceBlockService(ContainerSize containerSize, List? placedBlocks = null) { containerSize.Length = containerSize.Length; containerSize.Width = containerSize.Width; ContainerSize = containerSize; if (placedBlocks == null || placedBlocks.Count == 0) { PlacedBlocks = new List(); } else { PlacedBlocks = placedBlocks; } containerFloor = new PlacedBlock(new Point3D(SPACING, SPACING, 0), ContainerSize.Length - 2 * SPACING, ContainerSize.Width - 2 * SPACING, 0); } /// /// 主放置方法:尝试放置指定尺寸的货物 /// /// 货物长度(X轴方向) /// 货物宽度(Y轴方向) /// 货物高度(Z轴方向) /// /// 成功:返回可放置位置的左下前角Point3D坐标 /// 失败:返回null(尺寸无效或空间不足) /// public Point3D? PlaceBlock(int length, int width, int height) { int tempLength = length; int tempWidth = width; if (length < width) { length = tempWidth; width = tempLength; } if (!IsValidBlock(length, width, height)) return null; return FindStackablePosition(length, width, height); } /// /// 主放置方法:尝试放置指定尺寸的货物 /// /// 货物长度(X轴方向) /// 货物宽度(Y轴方向) /// 货物高度(Z轴方向) /// /// 成功:返回可放置位置的左下前角Point3D坐标 /// 失败:返回null(尺寸无效或空间不足) /// public TaskPosition? PlaceBlock(int length, int width, int height, int edge) { int tempLength = length; int tempWidth = width; if (length < width) { length = tempWidth; width = tempLength; } if (!IsValidBlock(length, width, height)) return null; return FindStackablePosition(length, width, height, edge); } /// /// 验证货物尺寸有效性 /// 校验规则: /// 1. 各维度尺寸必须大于等于50mm /// 2. 各维度尺寸不得超过容器对应维度(扣除间隔后) /// 3. 高度必须 <= 容器剩余高度 /// /// /// /// /// private bool IsValidBlock(int l, int w, int h) { return l > 0 && w > 0 && h > 0 && l <= ContainerSize.Length - 2 * SPACING && w <= ContainerSize.Width - 2 * SPACING && h < ContainerSize.Height; } /// /// 在现有货物顶部寻找叠放位置(堆叠模式) /// 叠放条件: /// - 支撑面积 >= 被支撑面面积的70% /// - 新货物完全位于支撑货物上方 /// - 满足间隔要求 /// /// 长度 /// 宽度 /// 高度 /// private Point3D? FindStackablePosition(int l, int w, int h) { // 生成候选支撑层(包含容器底部) var candidateLayers = PlacedBlocks .Select(b => b.Position.Z + b.Height) .Append(0) .Distinct() .OrderBy(z => z) .ToList(); foreach (var baseZ in candidateLayers) { if (baseZ + h > ContainerSize.Height) continue; // 获取当前层的支撑块(包含虚拟容器底部) var supports = GetSupportBlocks(baseZ); foreach (var support in supports) { // 计算有效叠放区域(修正间隔逻辑) int xStart = support.Position.X; int yStart = support.Position.Y; // 容器底部支撑必须内缩间隔(即使尺寸相同) if (support == containerFloor) { xStart = support.Position.X; yStart = support.Position.Y; } int xEnd = support.Position.X + support.Length - l; int yEnd = support.Position.Y + support.Width - w; // 普通支撑块仅在尺寸不同时加间隔 if (support != containerFloor && (l != support.Length && w != support.Width)) { xStart += SPACING; yStart += SPACING; xEnd -= SPACING; yEnd -= SPACING; } // 最终容器边界约束 xEnd = Math.Min(xEnd, ContainerSize.Length - l - SPACING); yEnd = Math.Min(yEnd, ContainerSize.Width - w - SPACING); if (xStart > xEnd || yStart > yEnd) continue; // 优化搜索:优先角落位置 for (int x = xStart; x <= xEnd; x += 10) { for (int y = yStart; y <= yEnd; y += 10) { var candidate = new Point3D(x, y, baseZ); if (IsPositionValid(candidate, l, w, h)) { var placed = new PlacedBlock(candidate, l, w, h); //PlacedBlocks.Add(placed); return candidate; } } } } } return null; } /// /// 在现有货物顶部寻找叠放位置(堆叠模式) /// 叠放条件: /// - 支撑面积 >= 被支撑面面积的70% /// - 新货物完全位于支撑货物上方 /// - 满足间隔要求 /// /// 长度 /// 宽度 /// 高度 /// private TaskPosition? FindStackablePosition(int l, int w, int h, int edge) { // 生成候选支撑层(包含容器底部) var candidateLayers = PlacedBlocks .Select(b => b.Position.Z + b.Height) .Append(0) .Distinct() .OrderBy(z => z) .ToList(); foreach (var baseZ in candidateLayers) { if (baseZ + h > ContainerSize.Height) continue; // 获取当前层的支撑块(包含虚拟容器底部) var supports = GetSupportBlocks(baseZ); foreach (var support in supports) { // 计算有效叠放区域(修正间隔逻辑) int xStart = support.Position.X; int yStart = support.Position.Y; // 容器底部支撑必须内缩间隔(即使尺寸相同) if (support == containerFloor) { xStart = support.Position.X; yStart = support.Position.Y; } int xEnd = support.Position.X + support.Length - l; int yEnd = support.Position.Y + support.Width - w; // 普通支撑块仅在尺寸不同时加间隔 if (support != containerFloor && (l != support.Length && w != support.Width)) { xStart += SPACING; yStart += SPACING; xEnd -= SPACING; yEnd -= SPACING; } // 最终容器边界约束 xEnd = Math.Min(xEnd, ContainerSize.Length - l); yEnd = Math.Min(yEnd, ContainerSize.Width - w); if (xStart > xEnd || yStart > yEnd) continue; // 优化搜索:优先角落位置 for (int x = xStart; x <= xEnd; x += 10) { for (int y = yStart; y <= yEnd; y += 10) { var candidate = new Point3D(x, y, baseZ); if (IsPositionValid(candidate, l, w, h)) { TaskPosition taskPosition = GetTaskPosition(candidate, l, w, h, edge); if (IsPositionValid(taskPosition)) { return taskPosition; } } } } } } return null; } /// /// 获取支撑当前货物的底层货物列表 /// 特殊处理:当baseZ=0时返回虚拟容器底部作为支撑 /// 支撑条件:货物底面与支撑货物顶面接触且Z坐标匹配 /// /// 支撑块高度 /// private IEnumerable GetSupportBlocks(int baseZ) { var blocks = PlacedBlocks .Where(b => b.Position.Z + b.Height == baseZ) /*.OrderByDescending(b => b.Length * b.Width)*/.ToList(); // 当baseZ=0时添加容器底部支撑 if (baseZ == 0 && blocks.Count == 0) { return new List { containerFloor }; } return blocks; } /// /// 验证指定位置是否合法 /// 校验内容: /// 1. 边界条件:货物不得超出容器有效空间 /// 2. 碰撞检测:与已放置货物无空间重叠 /// 3. 间隔要求:保持最小间隔(SPACING常量) /// /// 坐标 /// 长度 /// 宽度 /// 高度 /// private bool IsPositionValid(Point3D pos, int l, int w, int h) { // 边界检查(含容器边缘间隔) if (pos.X < SPACING || pos.Y < SPACING || pos.X + l > ContainerSize.Length - SPACING || pos.Y + w > ContainerSize.Width - SPACING) return false; if (pos.X > 1600) return false; if (pos.X > MaxY && l > MaxRotateLength) return false; // 三维碰撞检测 var newBlock = new PlacedBlock(pos, l, w, h); return !PlacedBlocks.Any(existing => { bool xOverlap = newBlock.Position.X < existing.Position.X + existing.Length + SPACING && newBlock.Position.X + newBlock.Length + SPACING > existing.Position.X; bool yOverlap = newBlock.Position.Y < existing.Position.Y + existing.Width + SPACING && newBlock.Position.Y + newBlock.Width + SPACING > existing.Position.Y; bool zOverlap = newBlock.Position.Z < existing.Position.Z + existing.Height && newBlock.Position.Z + h > existing.Position.Z; return xOverlap && yOverlap && zOverlap; }); } private bool IsPositionValid(TaskPosition pos) { // 边界检查(含容器边缘间隔) //if (pos.X < SPACING || // pos.Y < SPACING || // pos.X + l > ContainerSize.Length - SPACING || // pos.Y + w > ContainerSize.Width - SPACING) // return false; return pos.PutPositionY <= MaxY && pos.PutPositionY >= 0 && pos.TakePositionY >= 0; } public TaskPosition GetTaskPosition(Point3D point3D, int length, int width, int height, int edge) { //放货位置板材中心点 Point3D putCenter = new Point3D(point3D.X + length / 2, point3D.Y + width / 2, point3D.Z + height / 2); //取货位置板材中心点 Point3D takeCenter = new Point3D(length / 2, width / 2, height / 2); //吸盘长530 间隔660 最大920 吸盘宽130 int positionR = 1; int takePositionX = 0; int takePositionY = 0; int takePositionZ = 0; int putPositionX = 0; int putPositionY = 0; int putPositionZ = 0; //1.如果长度大于920,宽度大于等于300,则可以使用双吸盘横向吸取 if (length > 920) //横向双吸 { //吸盘尺寸 Point3D deviceCenter = new Point3D(530 / 2, 920 / 2, 0); positionR = 1; takePositionX = (takeCenter.Y - deviceCenter.X); takePositionY = (takeCenter.X - deviceCenter.Y); takePositionZ = 10; putPositionX = (putCenter.Y - deviceCenter.X); putPositionY = (putCenter.X - deviceCenter.Y); putPositionZ = point3D.Z; // putCenter.Z /*+ 10*/; } else//横向单吸 { //吸盘尺寸 Point3D deviceCenter = new Point3D(530 / 2, 130 / 2, 0); positionR = 1; takePositionX = (takeCenter.Y - deviceCenter.X); takePositionY = (takeCenter.X - deviceCenter.Y); takePositionZ = 10; putPositionX = (putCenter.Y - deviceCenter.X); putPositionY = (putCenter.X - deviceCenter.Y); putPositionZ = point3D.Z; // putCenter.Z /*+ 10*/; } //1.如果取货位最小Y坐标小于155 if (takePositionY <= MinY) { takePositionY = 0; putPositionY = point3D.X + MinY; } else { takePositionY -= MinY; } if (putPositionY > MaxY) { int moreY = putPositionY - MaxY; if (takePositionY - moreY > 0) { takePositionY -= moreY; putPositionY = MaxY; } else if (Math.Abs(takePositionY - moreY) < SPACING) { takePositionY = 0; putPositionY = Math.Abs(takePositionY - moreY); } else { int count = PlacedBlocks.Where(x => x.Position.Y == 10).Count(); //putPositionY -= (920 - 130 + takePositionY - (count + 1) * SPACING * 2); putPositionY = point3D.X - 920 + 130; takePositionY = length - MinY - 130; if (putPositionY < 0 && takePositionY + putPositionY >= 0) { takePositionY += putPositionY; putPositionY = 0; } positionR = 2; } } //横向时,最小Y坐标为155,纵向时,最小Y坐标为350。最大Y坐标为700 //if (positionR == 1 && putPositionY < MinY) //{ // takePositionY = 0; // putPositionY = point3D.X + MinY; //} //else if (positionR == 1 && putPositionY >= MinY && putPositionY <= MaxY) //{ // if (takePositionY >= MinY) // takePositionY -= MinY; // else // { // putPositionY += MinY - takePositionY; // takePositionY = 0; // } //} //else if (positionR == 1 && putPositionY > MaxY && putPositionY < 1700) //{ // int moreY = putPositionY - MaxY; // if (takePositionY - moreY - MinY > 0) // { // takePositionY -= moreY + MinY; // putPositionY = MaxY; // } // else if (Math.Abs(takePositionY - moreY - MinY) < SPACING) // { // if (takePositionY - moreY - MinY > 0) // { // takePositionY -= moreY + MinY; // } // else // { // takePositionY = 0; // } // putPositionY = MaxY; // } // else // { // int count = PlacedBlocks.Where(x => x.Position.Y == 10).Count(); // //putPositionY -= (920 - 130 + takePositionY - (count + 1) * SPACING * 2); // putPositionY = point3D.X - 920 + 130; // takePositionY = length - MinY - 130; // if (putPositionY < 0 && takePositionY + putPositionY >= 0) // { // takePositionY += putPositionY; // putPositionY = 0; // } // positionR = 2; // } //} if (positionR == 2 && edge == 0) { takePositionX = width - 530; putPositionX = point3D.Y; } else if (positionR == 2 && edge == 1) { takePositionX = 0; putPositionX = point3D.Y + width - 530; } else if (positionR == 1 && edge == 1) { takePositionX = width - 530; putPositionX = point3D.Y + (width - 530); } else if (positionR == 1 && edge == 0) { if (putPositionX < 0) { takePositionX = 0; putPositionX = point3D.Y; } } if (takePositionY < 0 && Math.Abs(takePositionY) < SPACING) { takePositionY = 0; } TaskPosition taskPosition = new TaskPosition() { PositionR = positionR, TakePositionX = takePositionX, TakePositionY = takePositionY, TakePositionZ = takePositionZ, PutPositionX = putPositionX, PutPositionY = putPositionY, PutPositionZ = putPositionZ, TakeCenterPositionX = takeCenter.X, TakeCenterPositionY = takeCenter.Y, TakeCenterPositionZ = takeCenter.Z, PutCenterPositionX = putCenter.X, PutCenterPositionY = putCenter.Y, PutCenterPositionZ = putCenter.Z, PositionX = point3D.X, PositionY = point3D.Y, PositionZ = point3D.Z }; return taskPosition; } } }