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
|
{
|
/// <summary>
|
/// 货物放置服务,提供集装箱内货物摆放位置计算功能
|
/// </summary>
|
/// <remarks>
|
/// 主要功能包括: <br/>
|
/// 1. 根据货物尺寸自动计算最优摆放位置 <br/>
|
/// 2. 支持横向/纵向两种摆放方式 <br/>
|
/// 3. 考虑吸盘尺寸、容器边界等物理限制 <br/>
|
/// 4. 提供任务位置坐标转换功能 <br/>
|
/// <br/>
|
/// 核心参数: <br/>
|
/// - SPACING: 货物间最小间距 <br/>
|
/// - MaxRotateLength: 最大旋转长度限制 <br/>
|
/// - MaxY/MinY: Y轴坐标限制 <br/>
|
/// - SuctionLength/Width: 吸盘尺寸参数
|
/// </remarks>
|
public class PlaceBlockService
|
{
|
/// <summary>
|
/// 获取配置文件中"Spacing"键对应的整数值,表示间距值
|
/// </summary>
|
public static int SPACING = AppSettings.GetValue("Spacing").ObjToInt();
|
|
/// <summary>
|
/// 获取或设置最大旋转长度配置值
|
/// </summary>
|
public static int MaxRotateLength = AppSettings.GetValue("MaxRotateLength").ObjToInt();
|
|
/// <summary>
|
/// 最大Y坐标限制(毫米)
|
/// </summary>
|
public const int MaxY = 600;
|
|
/// <summary>
|
/// 最小横向Y坐标限制(毫米)
|
/// </summary>
|
public static int MinY = AppSettings.GetValue("MinY").ObjToInt();
|
|
/// <summary>
|
/// 旋转抓取Y轴偏移量(毫米)
|
/// </summary>
|
public static int RotateYOffset = AppSettings.GetValue("RotateYOffset").ObjToInt();
|
|
/// <summary>
|
/// 吸盘横向长度
|
/// </summary>
|
public const int SuctionLengthH = 920;
|
|
/// <summary>
|
/// 吸盘横向宽度
|
/// </summary>
|
public const int SuctionWidthH = 530;
|
|
/// <summary>
|
/// 吸盘纵向长度
|
/// </summary>
|
public const int SuctionLengthZ = 530;
|
|
/// <summary>
|
/// 吸盘纵向宽度
|
/// </summary>
|
public const int SuctionWidthZ = 130;
|
|
/// <summary>
|
/// 容器尺寸
|
/// </summary>
|
public ContainerSize ContainerSize { get; private set; }
|
|
/// <summary>
|
/// 已放置货物
|
/// </summary>
|
public List<PlacedBlock> PlacedBlocks { get; private set; }
|
|
/// <summary>
|
/// 容器地板的放置块实例
|
/// </summary>
|
private readonly PlacedBlock containerFloor;
|
|
/// <summary>
|
/// 初始化放置区块服务
|
/// </summary>
|
/// <param name="containerSize">容器尺寸</param>
|
/// <param name="placedBlocks">已放置区块列表,可选参数</param>
|
/// <remarks>
|
/// 构造函数会初始化容器尺寸和已放置区块列表。
|
/// 如果未提供placedBlocks或列表为空,将创建新的空列表。
|
/// 同时会创建表示容器底部的PlacedBlock对象。
|
/// </remarks>
|
public PlaceBlockService(ContainerSize containerSize, List<PlacedBlock>? placedBlocks = null)
|
{
|
containerSize.Length = containerSize.Length;
|
containerSize.Width = containerSize.Width;
|
ContainerSize = containerSize;
|
if (placedBlocks == null || placedBlocks.Count == 0)
|
{
|
PlacedBlocks = new List<PlacedBlock>();
|
}
|
else
|
{
|
PlacedBlocks = placedBlocks;
|
}
|
containerFloor = new PlacedBlock(new Point3D(0, 0, 0), ContainerSize.Length, ContainerSize.Width, 0);
|
|
//containerFloor = new PlacedBlock(new Point3D(SPACING, SPACING, 0), ContainerSize.Length - 2 * SPACING, ContainerSize.Width - 2 * SPACING, 0);
|
}
|
|
/// <summary>
|
/// 根据给定的长宽高寻找合适的放置位置
|
/// </summary>
|
/// <param name="length">块的长度</param>
|
/// <param name="width">块的宽度</param>
|
/// <param name="height">块的高度</param>
|
/// <returns>可放置的3D坐标点,若无法放置则返回null</returns>
|
/// <remarks>
|
/// 方法会自动处理长宽参数,确保length >= width
|
/// 在放置前会先检查块的有效性
|
/// </remarks>
|
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);
|
}
|
|
/// <summary>
|
/// 根据给定的长、宽、高和边缘值放置一个块,并返回可放置的位置
|
/// </summary>
|
/// <param name="length">块的长度</param>
|
/// <param name="width">块的宽度</param>
|
/// <param name="height">块的高度</param>
|
/// <param name="edge">边缘值</param>
|
/// <returns>可放置的位置信息,若块无效则返回null</returns>
|
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);
|
}
|
|
/// <summary>
|
/// 检查给定的长宽高是否构成有效的块体尺寸
|
/// </summary>
|
/// <param name="l">长度</param>
|
/// <param name="w">宽度</param>
|
/// <param name="h">高度</param>
|
/// <returns>如果尺寸有效返回true,否则返回false</returns>
|
/// <remarks>
|
/// 有效块体需满足:长宽高都大于0,且长宽不超过容器尺寸减去两倍间距,高度不超过容器高度
|
/// </remarks>
|
private bool IsValidBlock(int l, int w, int h)
|
{
|
return l > 0 && w > 0 && h > 0 &&
|
l <= ContainerSize.Length &&
|
w <= ContainerSize.Width &&
|
h < ContainerSize.Height;
|
}
|
|
//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;
|
//}
|
|
/// <summary>
|
/// 在容器中查找可放置指定尺寸块体的有效位置
|
/// </summary>
|
/// <param name="l">块体长度</param>
|
/// <param name="w">块体宽度</param>
|
/// <param name="h">块体高度</param>
|
/// <returns>可放置位置的3D坐标点,若无合适位置则返回null</returns>
|
/// <remarks>
|
/// 1. 通过分析已放置块体生成候选支撑层
|
/// 2. 考虑容器底部和支撑块的不同间隔规则
|
/// 3. 优先检查角落位置以提高搜索效率
|
/// </remarks>
|
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;
|
}
|
|
/// <summary>
|
/// 在容器中查找可堆叠位置
|
/// </summary>
|
/// <param name="l">物品长度</param>
|
/// <param name="w">物品宽度</param>
|
/// <param name="h">物品高度</param>
|
/// <param name="edge">边缘类型</param>
|
/// <returns>返回找到的有效任务位置,若找不到则返回null</returns>
|
/// <remarks>
|
/// 1. 生成候选支撑层(包含容器底部)
|
/// 2. 遍历每层支撑块计算有效叠放区域
|
/// 3. 考虑容器边界约束和间隔规则
|
/// 4. 优先检查角落位置以提高搜索效率
|
/// </remarks>
|
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;
|
|
int xEnd = support.Position.X + support.Length - l;
|
int yEnd = support.Position.Y + support.Width - w;
|
|
// 最终容器边界约束
|
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;
|
}
|
|
/// <summary>
|
/// 获取指定高度层的支撑块
|
/// </summary>
|
/// <param name="baseZ">需要支撑的高度层</param>
|
/// <returns>按面积从大到小排序的支撑块集合,当baseZ=0且无支撑块时返回容器底部</returns>
|
private IEnumerable<PlacedBlock> GetSupportBlocks(int baseZ)
|
{
|
var blocks = PlacedBlocks
|
.Where(b => b.Position.Z + b.Height == baseZ)
|
.OrderBy(b => b.Position.X)
|
.ThenBy(b => b.Position.Y)
|
/*.OrderByDescending(b => b.Length * b.Width)*/
|
.ToList();
|
|
// 当baseZ=0时添加容器底部支撑
|
if (baseZ == 0 && blocks.Count == 0)
|
{
|
return new List<PlacedBlock> { containerFloor };
|
}
|
|
return blocks;
|
}
|
|
/// <summary>
|
/// 检查指定位置是否有效,即该位置是否可以放置指定尺寸的块体
|
/// </summary>
|
/// <param name="pos">要检查的位置坐标</param>
|
/// <param name="l">块体长度</param>
|
/// <param name="w">块体宽度</param>
|
/// <param name="h">块体高度</param>
|
/// <returns>如果位置有效且不与其他已放置块体重叠则返回true,否则返回false</returns>
|
/// <remarks>
|
/// 检查条件包括:
|
/// 1. 是否超出容器尺寸限制
|
/// 2. X坐标是否超过1600限制
|
/// 3. 当X坐标超过MaxY且长度超过MaxRotateLength时的限制
|
/// 4. 是否与已放置的块体发生重叠(考虑SPACING间距)
|
/// </remarks>
|
private bool IsPositionValid(Point3D pos, int l, int w, int h)
|
{
|
if (pos.X + l > ContainerSize.Length ||
|
pos.Y + w > ContainerSize.Width)
|
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(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;
|
// });
|
//}
|
|
/// <summary>
|
/// 检查任务位置是否有效
|
/// </summary>
|
/// <param name="pos">要检查的任务位置</param>
|
/// <returns>如果Y轴坐标在有效范围内返回true,否则返回false</returns>
|
private bool IsPositionValid(TaskPosition pos)
|
{
|
return pos.PutPositionY <= MaxY && pos.PutPositionY >= 0 && pos.TakePositionY >= 0;
|
}
|
|
/// <summary>
|
/// 根据给定的三维坐标和尺寸参数计算任务位置信息
|
/// </summary>
|
/// <param name="point3D">起始三维坐标点</param>
|
/// <param name="length">物体长度</param>
|
/// <param name="width">物体宽度</param>
|
/// <param name="height">物体高度</param>
|
/// <param name="edge">边缘标识(0/1)</param>
|
/// <returns>包含取放货位置信息的TaskPosition对象</returns>
|
/// <remarks>
|
/// 该方法根据物体尺寸自动选择单吸盘或双吸盘模式,
|
/// 并计算吸盘中心点位置,同时处理边界条件限制。
|
/// 横向放置时最小Y坐标为155,纵向时为350,最大Y坐标为700。
|
/// </remarks>
|
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;
|
}
|
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;
|
}
|
|
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) + MinY;
|
}
|
else
|
{
|
int count = PlacedBlocks.Where(x => x.Position.Y == 10).Count();
|
putPositionY = point3D.X - 920 + 130 + RotateYOffset;
|
takePositionY = length - MinY - 130 - RotateYOffset;
|
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)
|
{
|
if(width > ContainerSize.Width)
|
{
|
takePositionX = 0;
|
putPositionX = point3D.Y;
|
}
|
else
|
{
|
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;
|
}
|
}
|
}
|