using AngleSharp.Dom; using Furion.Logging; using OfficeOpenXml.FormulaParsing.Excel.Functions.Logical; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using WCS.Application.Entity; using static SKIT.FlurlHttpClient.Wechat.Api.Models.CustomServiceKfSessionGetWaitCaseResponse.Types; namespace WCS.Application.Util; public static class FourWayCarUtil { private static readonly ISqlSugarClient _db = SqlSugarSetup.ITenant.GetConnectionScope(SqlSugarConst.MainConfigId); private static readonly WcsTaskService _taskService = App.GetService(); /// /// 完善小车下发节点、节点命令 /// /// 集合 /// 移动类型 0:移动 1:移货 /// public static List GetCarPathUp(List list, int moveType) { for (int i = 0; i < list.Count; i++) { if (i >= 0 && i < list.Count - 1) { if (list[i].X == list[i + 1].X) { list[i].NodeCom = 2; } else if (list[i].Y == list[i + 1].Y) { list[i].NodeCom = 3; } } if (i > 0 && i < list.Count - 1) { if (list[i].Make != list[i - 1].Make || list[i].Make != list[i + 1].Make) { if (list[i].X != list[i - 1].X || list[i].X != list[i + 1].X) { list[i].IsSendPlc = true; } //list[i - 1].IsSendPlc = true; } } if (i == 0) { list[i].IsSendPlc = true; if (moveType == 0) { if (list[i + 1] == null) { continue; } } else { list[i].NodeCom = 1; } } if (i == list.Count - 1) { list[i].IsSendPlc = true; if (moveType == 0) { if (list[i].X == list[i - 1].X) { list[i].NodeCom = 2; } else if (list[i].Y == list[i - 1].Y) { list[i].NodeCom = 3; } } else { list[i].NodeCom = 4; } } } return list; } /// /// 获取小车移动的目标位置 /// /// 起始位置 /// 不能存放的储位字符串 /// public static string GetCarEndLocation(string startLocation, string noPathStr) { if (string.IsNullOrEmpty(startLocation)) { return null; } // 起始位置 CarModel start = new CarModel() { X = int.Parse(startLocation.Substring(0, 2)), Y = int.Parse(startLocation.Substring(2, 2)), Z = int.Parse(startLocation.Substring(4, 2)), NodeCom = 0 }; // 获取储位表信息存储到集合里 var layer = int.Parse(startLocation.Substring(4, 2)); // 获取当前层储位信息 var locationModels = _db.Queryable() .Where(m => m.WareHouseNo == "W01" && m.Layer == layer && m.IsDelete == false) .ToList(); #region MyRegion var list = locationModels.Where(m => !noPathStr.Contains(m.LocatNo)).OrderBy(m => Math.Abs(start.X - m.Row) + Math.Abs(start.Y - m.Column)).ToList(); var locateStr = ""; foreach (var item in list) { // 储位状态为损坏不可通行 储位状态为屏蔽为可通行不可存储托盘 if (item.Flag == "2") { continue; } locateStr = item.LocatNo; break; } return locateStr; #endregion } /// /// 获取任务号 /// /// public static int GetTaskNo() { var list = _db.Queryable().ToList(); var maxNo = list.Max(m => m.CarTaskNo); if (maxNo != null && maxNo > 0) { if (maxNo++ > 65535) { return 1; } return (int)maxNo++; } return 1; } public static bool AddCarTask(List data, List kXCarList, CarInfo assignCar, WcsTask waitTask,int moveType) { #region 获取适合执行当前任务的小车 生成路径(需考虑小车阻阻挡) var preId1 = "";//前置任务Id var executionPath1 = "";//交互路径 var executionPath2 = "";//交互路径 var path = "";//所有路径 var isOk = "1"; //是否完整路径 1完整 2 两条路径 for (int i = 0; i < data.Count; i++) { //路径节点 var pathXYZ = data[i].X.ToString().PadLeft(2, '0') + data[i].Y.ToString().PadLeft(2, '0') + data[i].Z.ToString().PadLeft(2, '0') + data[i].NodeCom.ToString(); var pathXYZ2 = data[i].X.ToString().PadLeft(2, '0') + data[i].Y.ToString().PadLeft(2, '0') + data[i].Z.ToString().PadLeft(2, '0'); path += pathXYZ + ";"; //获取等待或正在执行的任务中包含当前节点的小车任务 var taskList = _db.Queryable().Where(m => m.IsDelete == false && (m.Status == TaskStatusEnum.Wait || m.Status == TaskStatusEnum.Doing) && m.Path.Contains(pathXYZ2) && m.CarNo != assignCar.CarPlcIp).Select(m => m.Id).Distinct().ToList(); foreach (var item in taskList) { //判断如果是完整路径 记录交互路径 if (isOk == "1") { if (i == 0) { continue; } var pathXYZQian = data[i - 1].X.ToString().PadLeft(2, '0') + data[i - 1].Y.ToString().PadLeft(2, '0') + data[i - 1].Z.ToString().PadLeft(2, '0')+ data[i - 1].NodeCom.ToString(); var pathXYZQian2 = data[i - 1].X.ToString().PadLeft(2, '0') + data[i - 1].Y.ToString().PadLeft(2, '0') + data[i - 1].Z.ToString().PadLeft(2, '0'); if (!executionPath1.Contains(pathXYZQian2)) { executionPath1 += pathXYZQian + ";"; } executionPath2 += pathXYZQian + ";"; //判断添加前置任务Id if (!preId1.Contains(item + "")) { preId1 += item + ";"; } } isOk = "2"; } if (data[i].IsSendPlc) { if (isOk == "1") { executionPath1 += pathXYZ + ";"; } else { executionPath2 += pathXYZ + ";"; } } } #endregion #region 判断是否有空闲小车阻挡路径 3 var preId3 = "";//前置任务Id foreach (var item in kXCarList) { if (item == assignCar) { continue;//排除当前分配任务的小车 } //小车位置 var carXYZ = item.X.ToString().PadLeft(2, '0') + item.Y.ToString().PadLeft(2, '0') + item.Z.ToString().PadLeft(2, '0'); //分配的任务路径中 当前小车是否阻挡 if (path.Contains(carXYZ)) { //获取等待或正在执行的任务中包含当前节点的小车任务,不会有太多任务,同层有几个小车最多有几个任务 var taskList3 = _db.Queryable().Where(m => m.IsDelete == false && (m.Status == TaskStatusEnum.Wait || m.Status == TaskStatusEnum.Doing)).ToList(); var str3 = "";//所有已分配或执行的任务全路径之和 foreach (var item2 in taskList3) { str3 += item2.Path; } var endLocate3 = ""; var executionPath3 = ""; var path3 = ""; var datas3 = new List(); //查找目标位置 while (endLocate3 == "" || datas3.Count == 0 || datas3 == null) { endLocate3 = FourWayCarUtil.GetCarEndLocation(carXYZ, str3); var data3 = FourWayCarUtil.GetCarPath(carXYZ, endLocate3, "0"); datas3 = FourWayCarUtil.GetCarPathUp(data3, 0); } foreach (var itemPath in datas3) { var pathXYZ = itemPath.X.ToString().PadLeft(2, '0') + itemPath.Y.ToString().PadLeft(2, '0') + itemPath.Z.ToString().PadLeft(2, '0') + itemPath.NodeCom.ToString(); path3 += pathXYZ + ";"; if (itemPath.IsSendPlc) { executionPath3 += pathXYZ + ";"; } } WcsTask modTask = new WcsTask() { TaskNo = _taskService.GetTaskCode(), TaskType = TaskTypeEnum.Move, Type = PLCTypeEnum.ShuttleCar, StartLocate = carXYZ, EndLocate = endLocate3, PalletNo = "", Status = TaskStatusEnum.Wait, Levels = 999, Origin = "WCS" }; _db.Insertable(modTask).ExecuteCommand(); HubUtil.PublicTask(modTask.Adapt()); //移动小车 var carTaskYC = new WcsCarTasks() { TaskNo = modTask.TaskNo, PreId = "", ExecutionPath = executionPath3, Path = path3, CarNo = item.CarPlcIp, Status = TaskStatusEnum.Wait }; var idLong = _db.Insertable(carTaskYC).ExecuteReturnSnowflakeId(); preId3 += idLong + ";"; } } #endregion #region 判断现有任务中最终节点是否在当前分配路径中,如有 添加移走小车任务并加入前置任务 4 //获取等待或正在执行的任务中包含当前节点的小车任务,不会有太多任务,同层有几个小车最多有几个任务 var taskList4 = _db.Queryable().Where(m => m.IsDelete == false && (m.Status == TaskStatusEnum.Wait || m.Status == TaskStatusEnum.Doing) && m.CarNo!=assignCar.CarPlcIp).ToList(); var preId4 = "";//前置任务Id var str4 = "";//所有已分配或执行的任务全路径之和 foreach (var item in taskList4) { str4 += item.Path; } //判断现有任务中最终节点是否在当前分配路径中,如有 添加移走小车任务并加入前置任务 foreach (var item in taskList4) { var lastPathList = item.ExecutionPath.Split(';'); // a;b;c; 最后一个位是“”,所以lastPathList.Length - 2 var lastPath = lastPathList[lastPathList.Length - 2]; var lastPath2 = lastPath.Substring(0,6); //如果此此分配路径包含醉舞中最终节点路径,添加移走小车 if (path.Contains(lastPath2)) { var endLocate = ""; var executionPath4 = ""; var path4 = ""; var datas4 = new List(); //查找目标位置 while (endLocate == "" || datas4.Count == 0 || datas4 == null) { endLocate = FourWayCarUtil.GetCarEndLocation(lastPath, str4); var data4 = FourWayCarUtil.GetCarPath(lastPath, endLocate); datas4 = FourWayCarUtil.GetCarPathUp(data4, 0); } foreach (var itemPath in datas4) { var pathXYZ = itemPath.X.ToString().PadLeft(2, '0') + itemPath.Y.ToString().PadLeft(2, '0') + itemPath.Z.ToString().PadLeft(2, '0') + itemPath.NodeCom.ToString(); path4 += pathXYZ + ";"; if (itemPath.IsSendPlc) { executionPath4 += pathXYZ + ";"; } } WcsTask modTask = new WcsTask() { TaskNo = _taskService.GetTaskCode(), TaskType = TaskTypeEnum.Move, Type = PLCTypeEnum.ShuttleCar, StartLocate = lastPath, EndLocate = endLocate, PalletNo = "", Status = TaskStatusEnum.Wait, Levels = 999, Origin = "WCS" }; _db.Insertable(modTask).ExecuteCommand(); HubUtil.PublicTask(modTask.Adapt()); //移动小车 var carTaskYC = new WcsCarTasks() { TaskNo = modTask.TaskNo, PreId = "", ExecutionPath = executionPath4, Path = path4, CarNo = item.CarNo, Status = TaskStatusEnum.Wait }; var idLong = _db.Insertable(carTaskYC).ExecuteReturnSnowflakeId(); preId4 += idLong + ";"; } } #endregion #region 插入任务数据 改变任务状态 // 插入四向车任务表 var carTask1 = new WcsCarTasks() { TaskNo = waitTask.TaskNo, PreId = preId1 + preId3 + preId4, ExecutionPath = executionPath1, Path = path, CarNo = assignCar.CarPlcIp, Status = TaskStatusEnum.Wait }; _db.Insertable(carTask1).ExecuteCommand(); if (!string.IsNullOrWhiteSpace(executionPath1) && isOk == "2") { // 插入四向车任务表 var carTask2 = new WcsCarTasks() { TaskNo = waitTask.TaskNo, PreId = preId1, ExecutionPath = executionPath2, Path = path, CarNo = assignCar.CarPlcIp, Status = TaskStatusEnum.Wait }; _db.Insertable(carTask2).ExecuteCommand(); } if (moveType == 1) { // 改变总任务表状态 waitTask.Status = TaskStatusEnum.Doing; waitTask.UpdateTime = DateTime.Now; _db.Updateable(waitTask).ExecuteCommand(); HubUtil.PublicTask(waitTask.Adapt()); } return true; #endregion } /// /// 获取小车路径 /// /// 起始位置 /// 目标位置 /// 是否载货0:未载货 1:已载货 /// public static List GetCarPath(string startLocation, string endLocation, string isLoad = "0") { if (string.IsNullOrEmpty(startLocation) || string.IsNullOrEmpty(endLocation)) { return null; } // 起始位置 CarModel start = new CarModel() { X = int.Parse(startLocation.Substring(0, 2)), Y = int.Parse(startLocation.Substring(2, 2)), Z = int.Parse(startLocation.Substring(4, 2)), NodeCom = 0 }; // 目标位置 CarModel end = new CarModel() { X = int.Parse(endLocation.Substring(0, 2)), Y = int.Parse(endLocation.Substring(2, 2)), Z = int.Parse(endLocation.Substring(4, 2)), NodeCom = 0 }; // 获取储位表信息存储到集合里 var layer = int.Parse(startLocation.Substring(4, 2)); // 获取当前层储位信息 var locationModels = _db.Queryable() .Where(m => m.WareHouseNo == "W01" && m.Layer == layer && m.IsDelete == false) .ToList(); #region 使用算法计算小车路径 try { // 定义开发列表存储路径节点 var openSet = new SortedSet<(int fscore, CarModel pos)>(); // 定义关闭节点字典 var closeSet = new Dictionary(); // 定义上一位置与目标位置字典 var cameFrom = new Dictionary(); // 定义上一位置与目标位置的实际距离字典 var gScore = new Dictionary(); // 定义上一位置与目标位置的预估距离字典 var fScore = new Dictionary(); // 存储最优距离,及起始节点 openSet.Add((Heuristic(start, end), start)); gScore[start] = 0; fScore[start] = Heuristic(start, end); // 循环查找路径 while (openSet.Count > 0) { var current = openSet.Min.pos; openSet.Remove(openSet.Min); if (current.Equals(end)) { Log.Error(ReconstructPath(cameFrom, current).ToString()); return ReconstructPath(cameFrom, current); } // 存储小车可运行的方向 var validDirections = new List(); var currentLocation = locationModels.FirstOrDefault(m => m.Row == current.X && m.Column == current.Y); if (currentLocation.Make == "0"|| currentLocation.Make == "2") { // 主通道 validDirections.Add(new CarModel() { X = 1, Y = 0 }); // 右 validDirections.Add(new CarModel() { X = -1, Y = 0 }); // 左 validDirections.Add(new CarModel() { X = 0, Y = 1 }); // 下 validDirections.Add(new CarModel() { X = 0, Y = -1 }); // 上 } if (currentLocation.Make == "1") { // 子通道 // 先拆分出口 var outNode = currentLocation.AisleOne; if (string.IsNullOrEmpty(outNode)) { throw new Exception("当前位置没有维护出口!"); } int outX = int.Parse(outNode.Substring(0, 2)); int outY = int.Parse(outNode.Substring(2, 2)); if (current.X == outX) { validDirections.Add(new CarModel() { X = 0, Y = 1 }); // 下 validDirections.Add(new CarModel() { X = 0, Y = -1 }); // 上 } else { validDirections.Add(new CarModel() { X = 1, Y = 0 }); // 右 validDirections.Add(new CarModel() { X = -1, Y = 0 }); // 左 } } // 循环连接节点。 bool isNextNode = false; foreach (var dir in validDirections) { CarModel neighbor = new CarModel() { X = current.X + dir.X, Y = current.Y + dir.Y, Z = layer }; // 验证下一节点位置是否可通行并且判断是否被其他小车占用 // 判断下一节点是否关闭 if (closeSet.ContainsKey(neighbor)) { closeSet[neighbor] = neighbor; } // 当前节点 var currentModel = locationModels.FirstOrDefault(it => it.Row == current.X && it.Column == current.Y); // 下一节点 var locationModel = locationModels.FirstOrDefault(it => it.Row == neighbor.X && it.Column == neighbor.Y); // 不存在此位置信息 if (locationModel == null) { closeSet[neighbor] = neighbor; continue; } // 储位状态为损坏不可通行 储位状态为屏蔽为可通行不可存储托盘 if (locationModel.Flag == "2") { closeSet[neighbor] = neighbor; continue; } // 判断下一节点上是否有托盘 if (!string.IsNullOrEmpty(locationModel.PalletNo)) { // 判断小车是否载托盘盘 if (isLoad == "1") { closeSet[neighbor] = neighbor; // 小车上载托盘不可通行跳过 continue; } } // 优化项,验证下一节点是否被别的小车占用 liudl:在此添加优化代码 // 更新实际距离 int tentativeGScore = gScore[current] + 1; // 判断位置是否已包含在路径内 且 是否更近节点 if (!gScore.ContainsKey(neighbor) || tentativeGScore < gScore[neighbor]) { neighbor.IsSendPlc = false; neighbor.Make = locationModel.Make; current.Make = currentModel.Make; // 更新实际距离与预估距离 cameFrom[neighbor] = current; gScore[neighbor] = tentativeGScore; fScore[neighbor] = tentativeGScore + Heuristic(neighbor, end); openSet.Add((fScore[neighbor], neighbor)); isNextNode = true; } } if (!isNextNode) { closeSet[current] = current; } } #endregion } catch (Exception) { throw; } return null; } /// /// 计算曼哈顿距离 /// /// 起始位置 /// 目标位置 /// 位置距离 private static int Heuristic(CarModel start, CarModel end) { return Math.Abs(start.X - end.X) + Math.Abs(start.Y - end.Y); } /// /// 重构完整路径 /// /// /// /// private static List ReconstructPath(Dictionary cameFrom, CarModel current) { var path = new List { current }; while (cameFrom.ContainsKey(current)) { current = cameFrom[current]; path.Insert(0, current); } return path; } } public class CarModel : IComparable { /// /// 行=X /// public int X { get; set; } /// /// 列=Y /// public int Y { get; set; } /// /// 层=Z /// public int Z { get; set; } /// /// 节点命令 1:顶货 2:子通道运行 3:主通道运行 4:放货 /// public int NodeCom { get; set; } /// /// 是否下发plc /// public bool IsSendPlc { get; set; } /// /// 0通道 1 储位 /// public string Make { get; set; } public int CompareTo(CarModel other) { if (other == null) return 1; // 这里根据 X、Y、Z 坐标进行比较 int result = X.CompareTo(other.X); if (result != 0) return result; result = Y.CompareTo(other.Y); if (result != 0) return result; return Z.CompareTo(other.Z); } // 重写 Equals 方法 public override bool Equals(object obj) { if (obj == null || GetType() != obj.GetType()) return false; CarModel other = (CarModel)obj; return X == other.X && Y == other.Y && Z == other.Z; } // 重写 GetHashCode 方法 public override int GetHashCode() { return HashCode.Combine(X, Y, Z); } } public class CarInfo { /// /// 小车IP /// public string CarPlcIp { get; set; } /// /// 顺序等级 /// public int Level { get; set; } /// /// 行=X /// public int X { get; set; } /// /// 列=Y /// public int Y { get; set; } /// /// 层=Z /// public int Z { get; set; } }