1
z8018
2025-06-10 e46aa927d231af83724683c7286d9db503e24cf7
ÏîÄ¿´úÂë/WCS/WIDESEAWCS_Server/WIDESEAWCS_BasicInfoService/PlaceBlockService.cs
@@ -11,10 +11,32 @@
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>
@@ -26,6 +48,11 @@
        /// æœ€å°æ¨ªå‘Y坐标限制(毫米)
        /// </summary>
        public static int MinY = AppSettings.GetValue("MinY").ObjToInt();
        /// <summary>
        /// æ—‹è½¬æŠ“取Y轴偏移量(毫米)
        /// </summary>
        public static int RotateYOffset = AppSettings.GetValue("RotateYOffset").ObjToInt();
        /// <summary>
        /// å¸ç›˜æ¨ªå‘长度
@@ -57,8 +84,21 @@
        /// </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;
@@ -72,21 +112,22 @@
            {
                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);
            //containerFloor = new PlacedBlock(new Point3D(SPACING, SPACING, 0), ContainerSize.Length - 2 * SPACING, ContainerSize.Width - 2 * SPACING, 0);
        }
        /// <summary>
        /// ä¸»æ”¾ç½®æ–¹æ³•:尝试放置指定尺寸的货物
        /// æ ¹æ®ç»™å®šçš„长宽高寻找合适的放置位置
        /// </summary>
        /// <param name="length">货物长度(X轴方向)</param>
        /// <param name="width">货物宽度(Y轴方向)</param>
        /// <param name="height">货物高度(Z轴方向)</param>
        /// <returns>
        /// æˆåŠŸï¼šè¿”å›žå¯æ”¾ç½®ä½ç½®çš„å·¦ä¸‹å‰è§’Point3D坐标
        /// å¤±è´¥ï¼šè¿”回null(尺寸无效或空间不足)
        /// </returns>
        /// <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;
@@ -105,15 +146,13 @@
        }
        /// <summary>
        /// ä¸»æ”¾ç½®æ–¹æ³•:尝试放置指定尺寸的货物
        /// æ ¹æ®ç»™å®šçš„长、宽、高和边缘值放置一个块,并返回可放置的位置
        /// </summary>
        /// <param name="length">货物长度(X轴方向)</param>
        /// <param name="width">货物宽度(Y轴方向)</param>
        /// <param name="height">货物高度(Z轴方向)</param>
        /// <returns>
        /// æˆåŠŸï¼šè¿”å›žå¯æ”¾ç½®ä½ç½®çš„å·¦ä¸‹å‰è§’Point3D坐标
        /// å¤±è´¥ï¼šè¿”回null(尺寸无效或空间不足)
        /// </returns>
        /// <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;
@@ -132,35 +171,43 @@
        }
        /// <summary>
        /// éªŒè¯è´§ç‰©å°ºå¯¸æœ‰æ•ˆæ€§
        /// æ ¡éªŒè§„则:
        /// 1. å„维度尺寸必须大于等于50mm
        /// 2. å„维度尺寸不得超过容器对应维度(扣除间隔后)
        /// 3. é«˜åº¦å¿…é¡» <= å®¹å™¨å‰©ä½™é«˜åº¦
        /// </summary>
        /// <param name="l"></param>
        /// <param name="w"></param>
        /// <param name="h"></param>
        /// <returns></returns>
        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>
        /// åœ¨çŽ°æœ‰è´§ç‰©é¡¶éƒ¨å¯»æ‰¾å æ”¾ä½ç½®ï¼ˆå †å æ¨¡å¼ï¼‰
        /// å æ”¾æ¡ä»¶ï¼š
        /// - æ”¯æ’‘面积 >= è¢«æ”¯æ’‘面面积的70%
        /// - æ–°è´§ç‰©å®Œå…¨ä½äºŽæ”¯æ’‘货物上方
        /// - æ»¡è¶³é—´éš”要求
        /// æ£€æŸ¥ç»™å®šçš„长宽高是否构成有效的块体尺寸
        /// </summary>
        /// <param name="l">长度</param>
        /// <param name="w">宽度</param>
        /// <param name="h">高度</param>
        /// <returns></returns>
        /// <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)
        {
            // ç”Ÿæˆå€™é€‰æ”¯æ’‘层(包含容器底部)
@@ -230,16 +277,19 @@
        }
        /// <summary>
        /// åœ¨çŽ°æœ‰è´§ç‰©é¡¶éƒ¨å¯»æ‰¾å æ”¾ä½ç½®ï¼ˆå †å æ¨¡å¼ï¼‰
        /// å æ”¾æ¡ä»¶ï¼š
        /// - æ”¯æ’‘面积 >= è¢«æ”¯æ’‘面面积的70%
        /// - æ–°è´§ç‰©å®Œå…¨ä½äºŽæ”¯æ’‘货物上方
        /// - æ»¡è¶³é—´éš”要求
        /// åœ¨å®¹å™¨ä¸­æŸ¥æ‰¾å¯å †å ä½ç½®
        /// </summary>
        /// <param name="l">长度</param>
        /// <param name="w">宽度</param>
        /// <param name="h">高度</param>
        /// <returns></returns>
        /// <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)
        {
            // ç”Ÿæˆå€™é€‰æ”¯æ’‘层(包含容器底部)
@@ -263,25 +313,8 @@
                    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);
@@ -312,17 +345,18 @@
        }
        /// <summary>
        /// èŽ·å–æ”¯æ’‘å½“å‰è´§ç‰©çš„åº•å±‚è´§ç‰©åˆ—è¡¨
        /// ç‰¹æ®Šå¤„理:当baseZ=0时返回虚拟容器底部作为支撑
        /// æ”¯æ’‘条件:货物底面与支撑货物顶面接触且Z坐标匹配
        /// èŽ·å–æŒ‡å®šé«˜åº¦å±‚çš„æ”¯æ’‘å—
        /// </summary>
        /// <param name="baseZ">支撑块高度</param>
        /// <returns></returns>
        /// <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)
                /*.OrderByDescending(b => b.Length * b.Width)*/.ToList();
                .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)
@@ -334,24 +368,24 @@
        }
        /// <summary>
        /// éªŒè¯æŒ‡å®šä½ç½®æ˜¯å¦åˆæ³•
        /// æ ¡éªŒå†…容:
        /// 1. è¾¹ç•Œæ¡ä»¶ï¼šè´§ç‰©ä¸å¾—超出容器有效空间
        /// 2. ç¢°æ’žæ£€æµ‹ï¼šä¸Žå·²æ”¾ç½®è´§ç‰©æ— ç©ºé—´é‡å 
        /// 3. é—´éš”要求:保持最小间隔(SPACING常量)
        /// æ£€æŸ¥æŒ‡å®šä½ç½®æ˜¯å¦æœ‰æ•ˆï¼Œå³è¯¥ä½ç½®æ˜¯å¦å¯ä»¥æ”¾ç½®æŒ‡å®šå°ºå¯¸çš„块体
        /// </summary>
        /// <param name="pos">坐标</param>
        /// <param name="l">长度</param>
        /// <param name="w">宽度</param>
        /// <param name="h">高度</param>
        /// <returns></returns>
        /// <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 < SPACING ||
                pos.Y < SPACING ||
                pos.X + l > ContainerSize.Length - SPACING ||
                pos.Y + w > ContainerSize.Width - SPACING)
            if (pos.X + l > ContainerSize.Length ||
                pos.Y + w > ContainerSize.Width)
                return false;
            if (pos.X > 1600)
@@ -359,7 +393,6 @@
            if (pos.X > MaxY && l > MaxRotateLength) return false;
            // ä¸‰ç»´ç¢°æ’žæ£€æµ‹
            var newBlock = new PlacedBlock(pos, l, w, h);
            return !PlacedBlocks.Any(existing =>
            {
@@ -376,18 +409,61 @@
            });
        }
        //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)
        {
            // è¾¹ç•Œæ£€æŸ¥ï¼ˆå«å®¹å™¨è¾¹ç¼˜é—´éš”)
            //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;
        }
        /// <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)
        {
            //放货位置板材中心点
@@ -397,7 +473,6 @@
            Point3D takeCenter = new Point3D(length / 2, width / 2, height / 2);
            //吸盘长530 é—´éš”660  æœ€å¤§920 å¸ç›˜å®½130
            int positionR = 1;
            int takePositionX = 0;
@@ -420,7 +495,7 @@
                putPositionX = (putCenter.Y - deviceCenter.X);
                putPositionY = (putCenter.X - deviceCenter.Y);
                putPositionZ = point3D.Z; // putCenter.Z /*+ 10*/;
                putPositionZ = point3D.Z;
            }
            else//横向单吸
            {
@@ -434,10 +509,9 @@
                putPositionX = (putCenter.Y - deviceCenter.X);
                putPositionY = (putCenter.X - deviceCenter.Y);
                putPositionZ = point3D.Z; // putCenter.Z /*+ 10*/;
                putPositionZ = point3D.Z;
            }
            //1.如果取货位最小Y坐标小于155
            if (takePositionY <= MinY)
            {
                takePositionY = 0;
@@ -464,9 +538,8 @@
                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;
                    putPositionY = point3D.X - 920 + 130 + RotateYOffset;
                    takePositionY = length - MinY - 130 - RotateYOffset;
                    if (putPositionY < 0 && takePositionY + putPositionY >= 0)
                    {
                        takePositionY += putPositionY;
@@ -475,61 +548,6 @@
                    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)
            {
@@ -543,8 +561,16 @@
            }
            else if (positionR == 1 && edge == 1)
            {
                takePositionX = width - 530;
                putPositionX = point3D.Y + (width - 530);
                if(width > ContainerSize.Width)
                {
                    takePositionX = 0;
                    putPositionX = point3D.Y;
                }
                else
                {
                    takePositionX = width - 530;
                    putPositionX = point3D.Y + (width - 530);
                }
            }
            else if (positionR == 1 && edge == 0)
            {