From cb7e1c0e174fa0a149aa44c978336b1ace74fc00 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Wed, 25 Feb 2026 01:37:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=8A=E8=A3=85=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E6=B0=B4=E5=B9=B3=E7=8E=AF=E5=BD=A2=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Core/PathPlanningManager.cs | 321 +++++++++++++++++- src/UI/WPF/ViewModels/PathEditingViewModel.cs | 4 + 2 files changed, 317 insertions(+), 8 deletions(-) diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index f37662d..e61ac37 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -1732,6 +1732,11 @@ namespace NavisworksTransport // 注意:TotalLength 现在是计算属性,自动从几何数据计算,无需手动更新 + // 检测并移除同一高度层的矩形环路 + LogManager.Info($"[吊装路径] 开始检测矩形环路,当前路径点数: {CurrentRoute?.Points?.Count ?? 0}"); + bool loopsRemoved = RemoveRectangularLoops(CurrentRoute); + LogManager.Info($"[吊装路径] 矩形环路检测完成,是否移除环路: {loopsRemoved}"); + // 重新渲染路径 _renderPlugin?.RenderPath(CurrentRoute); @@ -1936,15 +1941,15 @@ namespace NavisworksTransport var nextPoint = points[startIndex + 1]; // 使用 Info 级别日志以便调试 - LogManager.Info($"[斜线处理] 检查索引 {startIndex} 和 {startIndex + 1} 之间的连线"); - LogManager.Info($"[斜线处理] 当前点 [{startIndex}]: {currentPoint.Name}, 位置: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})"); - LogManager.Info($"[斜线处理] 下一点 [{startIndex + 1}]: {nextPoint.Name}, 位置: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})"); + LogManager.Debug($"[斜线处理] 检查索引 {startIndex} 和 {startIndex + 1} 之间的连线"); + LogManager.Debug($"[斜线处理] 当前点 [{startIndex}]: {currentPoint.Name}, 位置: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})"); + LogManager.Debug($"[斜线处理] 下一点 [{startIndex + 1}]: {nextPoint.Name}, 位置: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})"); double deltaX = Math.Abs(nextPoint.Position.X - currentPoint.Position.X); double deltaY = Math.Abs(nextPoint.Position.Y - currentPoint.Position.Y); double deltaZ = Math.Abs(nextPoint.Position.Z - currentPoint.Position.Z); - LogManager.Info($"[斜线处理] deltaX={deltaX:F2}, deltaY={deltaY:F2}, deltaZ={deltaZ:F2}"); + LogManager.Debug($"[斜线处理] deltaX={deltaX:F2}, deltaY={deltaY:F2}, deltaZ={deltaZ:F2}"); // 检测三种斜线 bool isXYDiagonal = deltaX > 0.01 && deltaY > 0.01 && deltaZ < 0.001; // XY平面斜线 @@ -1952,11 +1957,11 @@ namespace NavisworksTransport bool isYZDiagonal = deltaY > 0.01 && deltaZ > 0.001 && deltaX < 0.01; // YZ平面斜线 bool isXYZDiagonal = deltaX > 0.01 && deltaY > 0.01 && deltaZ > 0.001; // XYZ空间斜线 - LogManager.Info($"[斜线处理] 斜线检测结果: XY={isXYDiagonal}, XZ={isXZDiagonal}, YZ={isYZDiagonal}, XYZ={isXYZDiagonal}"); + LogManager.Debug($"[斜线处理] 斜线检测结果: XY={isXYDiagonal}, XZ={isXZDiagonal}, YZ={isYZDiagonal}, XYZ={isXYZDiagonal}"); if (!isXYDiagonal && !isXZDiagonal && !isYZDiagonal && !isXYZDiagonal) { - LogManager.Info($"[斜线处理] 未检测到斜线,跳过处理"); + LogManager.Debug($"[斜线处理] 未检测到斜线,跳过处理"); return false; } @@ -2090,8 +2095,8 @@ namespace NavisworksTransport points[i].Index = i; } - LogManager.Info($"[斜线处理] 已插入中间点: {intermediatePoint.Name}, 位置: ({intermediatePoint.Position.X:F2}, {intermediatePoint.Position.Y:F2}, {intermediatePoint.Position.Z:F2}) - 方向: {direction}"); - LogManager.Info($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}"); + LogManager.Debug($"[斜线处理] 已插入中间点: {intermediatePoint.Name}, 位置: ({intermediatePoint.Position.X:F2}, {intermediatePoint.Position.Y:F2}, {intermediatePoint.Position.Z:F2}) - 方向: {direction}"); + LogManager.Debug($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}"); return true; } @@ -2226,6 +2231,302 @@ namespace NavisworksTransport return hasChanges; } + /// + /// 检测并移除各水平层内的矩形环路 + /// 限制条件: + /// 1. 只处理同一水平层内的点(Z坐标必须完全相同,只考虑浮点数精度误差) + /// 2. 不允许处理起点(索引0)和终点(最后一个点) + /// 3. 不允许跨层检测(不同Z高度的线段不互相检测) + /// 4. 支持多层吊装路径(多个不同高度的水平层) + /// 算法原理:按Z坐标分组(严格相等),对每个水平层分别检测正交路径中的相交线段 + /// + /// 路径 + /// 是否移除了环路 + public bool RemoveRectangularLoops(PathRoute route) + { + if (route == null || route.Points.Count < 4) + { + return false; + } + + bool hasChanges = false; + var points = route.Points; + // 浮点数精度容差,用于判断Z坐标是否相等 + const double epsilon = 1e-9; + + LogManager.Debug($"[环路检测] 开始检测,路径点数: {points.Count}"); + + // 按Z坐标分组,找出所有水平层(排除起点和终点) + // 使用严格相等比较(只考虑浮点数精度误差) + var layers = new Dictionary>(); + for (int i = 1; i < points.Count - 1; i++) // 跳过起点(0)和终点(Count-1) + { + double z = points[i].Position.Z; + + // 查找是否已有相同Z坐标的层(考虑浮点数精度) + bool found = false; + foreach (var key in layers.Keys) + { + if (Math.Abs(z - key) < epsilon) + { + layers[key].Add(i); + found = true; + break; + } + } + + if (!found) + { + layers[z] = new List { i }; + } + } + + LogManager.Debug($"[环路检测] 发现{layers.Count}个水平层"); + + // 按Z坐标从高到低排序处理 + var sortedLayers = layers.OrderByDescending(l => l.Key).ToList(); + int totalDeletedPoints = 0; // 记录已删除的点数,用于更新后续层的索引 + + // 对每个水平层分别进行环路检测 + for (int layerIdx = 0; layerIdx < sortedLayers.Count; layerIdx++) + { + var layer = sortedLayers[layerIdx]; + double layerZ = layer.Key; + var layerIndices = layer.Value.OrderBy(idx => idx).ToList(); + + // 更新索引:减去之前层删除的点数 + for (int i = 0; i < layerIndices.Count; i++) + { + layerIndices[i] -= totalDeletedPoints; + } + + // 过滤掉无效索引(小于0或超出范围) + layerIndices = layerIndices.Where(idx => idx >= 0 && idx < points.Count).ToList(); + + if (layerIndices.Count < 2) + { + LogManager.Debug($"[环路检测] 层 Z={layerZ:F2} 剩余点太少,跳过"); + continue; // 该层点太少,无法形成环路 + } + + LogManager.Debug($"[环路检测] 处理水平层 Z={layerZ:F2},包含{layerIndices.Count}个,索引范围: {layerIndices.First()}-{layerIndices.Last()}"); + + // 对该层进行环路检测 + int pointsBefore = points.Count; + bool layerHasChanges = RemoveLoopsInLayer(points, layerIndices, epsilon); + int pointsDeleted = pointsBefore - points.Count; + totalDeletedPoints += pointsDeleted; + + if (layerHasChanges) + { + hasChanges = true; + } + } + + if (hasChanges) + { + LogManager.Debug($"[环路检测] 完成,当前路径点数: {points.Count}"); + } + + return hasChanges; + } + + /// + /// 对单个水平层进行环路检测和移除 + /// + /// 路径点列表 + /// 该层的点索引列表(已排序) + /// 浮点数精度容差 + /// 是否移除了环路 + private bool RemoveLoopsInLayer(List points, List layerIndices, double epsilon) + { + if (layerIndices.Count < 2) + { + return false; + } + + bool hasChanges = false; + double layerZ = points[layerIndices[0]].Position.Z; + + // 在该层内进行环路检测 + // n和m是layerIndices列表中的索引位置 + int n = 0; // 第一个线段的起始索引(在layerIndices中的位置) + int m = 2; // 第二个线段相对于n的偏移(在layerIndices中的位置) + + while (n + m + 1 < layerIndices.Count) + { + // 获取实际的路径点索引 + int idx1 = layerIndices[n]; // 线段1起点 + int idx2 = layerIndices[n + 1]; // 线段1终点 + int idx3 = layerIndices[n + m]; // 线段2起点 + int idx4 = layerIndices[n + m + 1]; // 线段2终点 + + // 获取线段坐标 + var p1 = points[idx1].Position; + var p2 = points[idx2].Position; + var p3 = points[idx3].Position; + var p4 = points[idx4].Position; + + // 确保所有点都在同一层(水平线段,Z坐标严格相同,只考虑浮点数精度) + if (Math.Abs(p1.Z - layerZ) > epsilon || + Math.Abs(p2.Z - layerZ) > epsilon || + Math.Abs(p3.Z - layerZ) > epsilon || + Math.Abs(p4.Z - layerZ) > epsilon) + { + m++; + if (n + m + 1 >= layerIndices.Count) + { + m = 2; + n++; + } + continue; + } + + // 检测两条正交线段是否相交 + Point3D intersection; + if (GetOrthogonalSegmentIntersection(p1, p2, p3, p4, out intersection)) + { + LogManager.Debug($"[环路检测] 层Z={layerZ:F2} 发现相交: 线段{idx1}({points[idx1].Name}-{points[idx2].Name}) 与 线段{idx3}({points[idx3].Name}-{points[idx4].Name})"); + LogManager.Debug($"[环路检测] 交点: ({intersection.X:F4}, {intersection.Y:F4}, {intersection.Z:F4})"); + + // 检查中间点是否包含特殊点 + bool containsSpecialPoint = false; + for (int k = idx1 + 1; k <= idx3; k++) + { + if (points[k].Type != PathPointType.WayPoint || + points[k].Name == "提升点" || + points[k].Name == "下降点" || + points[k].Name == "起吊点" || + points[k].Name == "落地点") + { + containsSpecialPoint = true; + break; + } + } + + if (containsSpecialPoint) + { + LogManager.Debug("[环路检测] 环路包含特殊点,跳过"); + m++; + if (n + m + 1 >= layerIndices.Count) + { + m = 2; + n++; + } + continue; + } + + // 用交点替换 points[idx2] + points[idx2].Position = intersection; + points[idx2].Name = "路径点"; + + // 删除中间点 (idx2+1 到 idx3) + int d = idx3 - idx2; // 要删除的点数 + for (int i = idx3 + 1; i < points.Count; i++) + { + points[i - d] = points[i]; + } + // 移除末尾多余的点 + points.RemoveRange(points.Count - d, d); + + // 更新layerIndices(因为删除了点) + for (int i = 0; i < layerIndices.Count; i++) + { + if (layerIndices[i] > idx2 && layerIndices[i] <= idx3) + { + // 这些点被删除了,标记为-1 + layerIndices[i] = -1; + } + else if (layerIndices[i] > idx3) + { + // 这些点的索引需要向前移动 + layerIndices[i] -= d; + } + } + layerIndices.RemoveAll(idx => idx == -1); + + // 从交点重新开始检测 + m = 2; + hasChanges = true; + LogManager.Info($"[环路优化] 层Z={layerZ:F2} 已移除环路,删除{d}个点"); + continue; + } + + m++; + if (n + m + 1 >= layerIndices.Count) + { + m = 2; + n++; + } + } + + return hasChanges; + } + + /// + /// 计算两条正交线段的交点 + /// + /// 是否相交(线段内部相交,非端点) + private bool GetOrthogonalSegmentIntersection(Point3D a1, Point3D a2, Point3D b1, Point3D b2, out Point3D intersection) + { + intersection = new Point3D(0, 0, 0); + + // 判断线段A是水平还是垂直 + bool aIsHorizontal = Math.Abs(a1.Y - a2.Y) < 0.001; + bool aIsVertical = Math.Abs(a1.X - a2.X) < 0.001; + + // 判断线段B是水平还是垂直 + bool bIsHorizontal = Math.Abs(b1.Y - b2.Y) < 0.001; + bool bIsVertical = Math.Abs(b1.X - b2.X) < 0.001; + + // 必须一条水平一条垂直才可能形成矩形环路 + if ((aIsHorizontal && bIsHorizontal) || (aIsVertical && bIsVertical)) + { + return false; + } + + // 确保a是水平,b是垂直 + if (aIsVertical && bIsHorizontal) + { + // 交换 + var temp1 = a1; var temp2 = a2; + a1 = b1; a2 = b2; + b1 = temp1; b2 = temp2; + aIsHorizontal = true; aIsVertical = false; + bIsHorizontal = false; bIsVertical = true; + } + + if (!aIsHorizontal || !bIsVertical) + { + return false; + } + + // 水平线段A: y = a1.Y, x范围 [min(a1.X,a2.X), max(a1.X,a2.X)] + // 垂直线段B: x = b1.X, y范围 [min(b1.Y,b2.Y), max(b1.Y,b2.Y)] + + double aY = a1.Y; + double bX = b1.X; + double aMinX = Math.Min(a1.X, a2.X); + double aMaxX = Math.Max(a1.X, a2.X); + double bMinY = Math.Min(b1.Y, b2.Y); + double bMaxY = Math.Max(b1.Y, b2.Y); + + // 检查垂直线段的X是否在水平线段的X范围内(不包括端点) + // 且水平线段的Y是否在垂直线段的Y范围内(不包括端点) + const double eps = 0.001; + + bool xInRange = bX > aMinX + eps && bX < aMaxX - eps; + bool yInRange = aY > bMinY + eps && aY < bMaxY - eps; + + if (xInRange && yInRange) + { + intersection = new Point3D(bX, aY, a1.Z); + return true; + } + + return false; + } + /// /// 更新路径点位置(保存修改) /// @@ -2396,6 +2697,10 @@ namespace NavisworksTransport // 吊装路径:正交化路径(处理斜线和清除多余点) OrthogonalizePath(route); + + // 吊装路径:检测并移除矩形环路 + LogManager.Info($"[吊装路径] 修改路径点后检测矩形环路"); + RemoveRectangularLoops(route); } else { diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index b3d5746..58d036c 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -2689,6 +2689,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (coreRoute.PathType == PathType.Hoisting) { _pathPlanningManager?.OrthogonalizePath(coreRoute); + + // 吊装路径:检测并移除矩形环路 + LogManager.Info($"[吊装路径] 删除路径点后检测矩形环路"); + _pathPlanningManager?.RemoveRectangularLoops(coreRoute); } // 调用PathPlanningManager的3D删除方法