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删除方法