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;
}
}
}