diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index b197b70..f37662d 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -1689,47 +1689,46 @@ namespace NavisworksTransport { LogManager.Info($"[吊装路径] 正在完成吊装路径,处理终点和下降点"); - // 确保至少有起吊点、提升点和至少一个空中路径点 - if (CurrentRoute.Points.Count >= 3 && _hoistingGroundIntermediatePoints.Count > 0) + // 确保至少有起吊点、提升点 + if (CurrentRoute.Points.Count >= 2) { // 获取起吊点(用于获取地面Z坐标) var startPoint = CurrentRoute.Points.First(); - // 获取吊装高度(内部使用模型单位,如果未设置则默认3米) - double liftHeightModelUnits = CurrentRoute.LiftHeight > 0 ? CurrentRoute.LiftHeight : UnitsConverter.ConvertFromMeters(3.0); + // 获取最后一个路径点(用于获取当前的空中XY位置) + var lastPoint = CurrentRoute.Points.Last(); + LogManager.Info($"[吊装路径] 最后一个路径点: {lastPoint.Name}, 位置: ({lastPoint.Position.X:F2}, {lastPoint.Position.Y:F2}, {lastPoint.Position.Z:F2})"); - // 获取最后一个地面点击点(原始坐标,未被调整过) - var lastGroundClick = _hoistingGroundIntermediatePoints.Last(); - LogManager.Debug($"[吊装路径] 最后一个地面点击点: ({lastGroundClick.X:F2}, {lastGroundClick.Y:F2}, {lastGroundClick.Z:F2})"); + // 计算下一个索引 + int nextIndex = CurrentRoute.Points.Count; - // 删除最后一个空中路径点(它的坐标可能被调整过,不使用任何信息) - CurrentRoute.Points.RemoveAt(CurrentRoute.Points.Count - 1); - - // 创建落地点(终点):使用最后一个地面点击点的完整原始坐标(X、Y、Z) - var endPoint = new PathPoint( - new Point3D(lastGroundClick.X, lastGroundClick.Y, lastGroundClick.Z), - "落地点", - PathPointType.EndPoint); - endPoint.Direction = HoistingPointDirection.Vertical; - LogManager.Debug($"[吊装路径] 已创建落地点(终点): ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})"); - - // 向上生成下降点:在落地点正上方,Z坐标为吊装高度 + // 创建下降点:在落地点正上方,Z坐标为最后一个路径点的Z(保持当前高度) var descendPoint = new PathPoint( - new Point3D(endPoint.Position.X, endPoint.Position.Y, startPoint.Position.Z + liftHeightModelUnits), + new Point3D(lastPoint.Position.X, lastPoint.Position.Y, lastPoint.Position.Z), "下降点", PathPointType.WayPoint); descendPoint.Direction = HoistingPointDirection.Vertical; - LogManager.Debug($"[吊装路径] 已生成下降点(在终点正上方): ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})"); + descendPoint.Index = nextIndex++; + LogManager.Info($"[吊装路径] 已生成下降点(在终点正上方): ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})"); + + // 创建落地点(终点):使用最后一个路径点的XY,使用起点Z作为地面高度 + var endPoint = new PathPoint( + new Point3D(lastPoint.Position.X, lastPoint.Position.Y, startPoint.Position.Z), + "落地点", + PathPointType.EndPoint); + endPoint.Direction = HoistingPointDirection.Vertical; + endPoint.Index = nextIndex++; + LogManager.Info($"[吊装路径] 已创建落地点(终点): ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})"); // 添加下降点 CurrentRoute.Points.Add(descendPoint); // 添加落地点(终点) CurrentRoute.Points.Add(endPoint); - LogManager.Debug($"[吊装路径] 已添加落地点(终点): ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})"); + LogManager.Info($"[吊装路径] 已添加下降点和落地点,当前共 {CurrentRoute.Points.Count} 个路径点"); - // 优化路径点:处理斜线和清除多余点 - OptimizeRightAnglePathPoints(CurrentRoute); + // 注意:不需要再次调用 OrthogonalizePath,因为添加的只是垂直线段 + // 垂直线段不会形成斜线,所以不需要优化 // 注意:TotalLength 现在是计算属性,自动从几何数据计算,无需手动更新 @@ -1921,6 +1920,7 @@ namespace NavisworksTransport /// /// 检测两点之间是否为斜线,如果是则插入中间转折点或修正坐标 + /// 处理三种斜线:XY平面斜线、XZ平面斜线、YZ平面斜线 /// /// 路径点列表 /// 开始检查的索引 @@ -1935,134 +1935,193 @@ namespace NavisworksTransport var currentPoint = points[startIndex]; var nextPoint = points[startIndex + 1]; - 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})"); + // 使用 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})"); 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); - // 检测斜线(X和Y都变化) - bool isDiagonal = deltaX > 0.01 && deltaY > 0.01; + LogManager.Info($"[斜线处理] deltaX={deltaX:F2}, deltaY={deltaY:F2}, deltaZ={deltaZ:F2}"); - if (!isDiagonal) + // 检测三种斜线 + bool isXYDiagonal = deltaX > 0.01 && deltaY > 0.01 && deltaZ < 0.001; // XY平面斜线 + bool isXZDiagonal = deltaX > 0.01 && deltaZ > 0.001 && deltaY < 0.01; // XZ平面斜线 + 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}"); + + if (!isXYDiagonal && !isXZDiagonal && !isYZDiagonal && !isXYZDiagonal) { + LogManager.Info($"[斜线处理] 未检测到斜线,跳过处理"); return false; } - // 区分两种情况: - // 1. 真正的斜线:deltaX和deltaY都比较大(>2.0)→ 插入中间点 - // 2. 微小偏差:其中一个值很小(<0.5)→ 修改下一点坐标,使其与上一点对齐 - - if (deltaX > 2.0 && deltaY > 2.0) + // 处理XY平面斜线(水平斜线) + if (isXYDiagonal) { - // 真正的斜线:插入中间点 - LogManager.Debug($"[斜线处理] 检测到斜线,需要插入中间点 (deltaX={deltaX:F2}, deltaY={deltaY:F2})"); - - int intermediateIndex = points.Count > 0 ? points.Count : 1; - var intermediatePoint = new PathPoint( - new Point3D( - nextPoint.Position.X, // 使用新点的X - currentPoint.Position.Y, // 保持上一个点的Y - currentPoint.Position.Z // 保持吊装高度 - ), - $"路径点{intermediateIndex}", - PathPointType.WayPoint); - intermediatePoint.Direction = HoistingPointDirection.Longitudinal; // 纵向移动 - - int insertPosition = startIndex + 1; - points.Insert(insertPosition, intermediatePoint); - - // 更新所有点的索引 - for (int i = 0; i < points.Count; i++) - { - points[i].Index = i; - } - - LogManager.Debug($"[斜线处理] 已插入中间点: {intermediatePoint.Name}, 位置: ({intermediatePoint.Position.X:F2}, {intermediatePoint.Position.Y:F2}, {intermediatePoint.Position.Z:F2}) - 方向: 纵向"); - LogManager.Debug($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}"); - - return true; + return HandleXYDiagonal(points, startIndex, currentPoint, nextPoint, deltaX, deltaY); } - else if (deltaX < 0.5 && deltaY > 0.5) + + // 处理XZ平面斜线(垂直斜线:X和Z同时变化) + if (isXZDiagonal) { - // X方向微小偏差,Y方向移动:修改下一点坐标,保持X与上一点对齐(垂直线) - LogManager.Debug($"[斜线处理] 检测到X方向微小偏差 (deltaX={deltaX:F2}, deltaY={deltaY:F2}),修正下一点坐标使其垂直"); - - nextPoint.Position = new Point3D( - currentPoint.Position.X, // 保持上一个点的X - nextPoint.Position.Y, // 保持新点的Y - nextPoint.Position.Z); // 保持新点的Z - - LogManager.Debug($"[斜线处理] 已修正下一点 {nextPoint.Name} 坐标: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})"); - return true; + return HandleXZDiagonal(points, startIndex, currentPoint, nextPoint, deltaX, deltaZ); } - else if (deltaY < 0.5 && deltaX > 0.5) + + // 处理YZ平面斜线(垂直斜线:Y和Z同时变化) + if (isYZDiagonal) { - // Y方向微小偏差,X方向移动:修改下一点坐标,保持Y与上一点对齐(水平线) - LogManager.Debug($"[斜线处理] 检测到Y方向微小偏差 (deltaX={deltaX:F2}, deltaY={deltaY:F2}),修正下一点坐标使其水平"); - - nextPoint.Position = new Point3D( - nextPoint.Position.X, // 保持新点的X - currentPoint.Position.Y, // 保持上一个点的Y - nextPoint.Position.Z); // 保持新点的Z - - LogManager.Debug($"[斜线处理] 已修正下一点 {nextPoint.Name} 坐标: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})"); - return true; + return HandleYZDiagonal(points, startIndex, currentPoint, nextPoint, deltaY, deltaZ); + } + + // 处理XYZ空间斜线(X、Y、Z都变化) + if (isXYZDiagonal) + { + return HandleXYZDiagonal(points, startIndex, currentPoint, nextPoint, deltaX, deltaY, deltaZ); + } + + return false; + } + + /// + /// 处理XY平面斜线(水平斜线) + /// 策略:插入中间点将斜线拆分为直角转折,保持用户点击的坐标不变 + /// + private bool HandleXYDiagonal(IList points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaX, double deltaY) + { + LogManager.Debug($"[斜线处理] 检测到XY平面斜线 (deltaX={deltaX:F2}, deltaY={deltaY:F2})"); + + // 策略:插入中间点,将斜线拆分为 X-only → Y-only 的两段直角路径 + // 不修改用户点击的坐标,只插入转折点 + // 转折点:X用新点的X,Y用旧点的Y(先X方向移动,再Y方向移动) + return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint, + new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z), + HoistingPointDirection.Longitudinal); + } + + /// + /// 处理XZ平面斜线(垂直斜线:X和Z同时变化) + /// 策略:先水平移动(X变化,Z不变),再垂直移动(Z变化,X不变) + /// + private bool HandleXZDiagonal(IList points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaX, double deltaZ) + { + LogManager.Debug($"[斜线处理] 检测到XZ平面斜线 (deltaX={deltaX:F2}, deltaZ={deltaZ:F2})"); + + // 插入中间点:先水平移动(到新X,旧Z),目标点自然就是(新X,新Z) + return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint, + new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z), + HoistingPointDirection.Longitudinal); + } + + /// + /// 处理YZ平面斜线(垂直斜线:Y和Z同时变化) + /// 策略:先水平移动(Y变化,Z不变),再垂直移动(Z变化,Y不变) + /// + private bool HandleYZDiagonal(IList points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaY, double deltaZ) + { + LogManager.Debug($"[斜线处理] 检测到YZ平面斜线 (deltaY={deltaY:F2}, deltaZ={deltaZ:F2})"); + + // 插入中间点:先水平移动(到新Y,旧Z),目标点自然就是(新Y,新Z) + return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint, + new Point3D(currentPoint.Position.X, nextPoint.Position.Y, currentPoint.Position.Z), + HoistingPointDirection.Lateral); + } + + /// + /// 处理XYZ空间斜线(X、Y、Z都变化) + /// 策略:先水平移动(XY平面,Z不变),再垂直移动(Z变化,XY不变) + /// 水平移动时如果XY也是斜线,则进一步拆分 + /// + private bool HandleXYZDiagonal(IList points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaX, double deltaY, double deltaZ) + { + LogManager.Debug($"[斜线处理] 检测到XYZ空间斜线 (deltaX={deltaX:F2}, deltaY={deltaY:F2}, deltaZ={deltaZ:F2})"); + + // 分两步: + // 1. 先水平移动到目标XY(保持当前Z) + // 2. 再垂直移动到目标Z(保持XY不变) + + // 如果水平移动也是斜线(X和Y都变化),则先处理XY斜线 + if (deltaX > 0.01 && deltaY > 0.01) + { + // 插入中间点:先X方向移动(保持Y),到目标X后再处理Y和Z + return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint, + new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z), + HoistingPointDirection.Longitudinal); } else { - // 中等程度的偏差,插入中间点 - LogManager.Debug($"[斜线处理] 检测到中等程度斜线,需要插入中间点 (deltaX={deltaX:F2}, deltaY={deltaY:F2})"); - - int intermediateIndex = points.Count > 0 ? points.Count : 1; - var intermediatePoint = new PathPoint( - new Point3D( - nextPoint.Position.X, // 使用新点的X - currentPoint.Position.Y, // 保持上一个点的Y - currentPoint.Position.Z // 保持吊装高度 - ), - $"路径点{intermediateIndex}", - PathPointType.WayPoint); - intermediatePoint.Direction = HoistingPointDirection.Longitudinal; // 纵向移动 - - int insertPosition = startIndex + 1; - points.Insert(insertPosition, intermediatePoint); - - // 更新所有点的索引 - for (int i = 0; i < points.Count; i++) + // 水平方向只有一个轴变化,直接插入中间点处理垂直斜线 + // 选择变化较大的水平轴作为先移动的方向 + if (deltaX > deltaY) { - points[i].Index = i; + return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint, + new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z), + HoistingPointDirection.Longitudinal); + } + else + { + return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint, + new Point3D(currentPoint.Position.X, nextPoint.Position.Y, currentPoint.Position.Z), + HoistingPointDirection.Lateral); } - - LogManager.Debug($"[斜线处理] 已插入中间点: {intermediatePoint.Name}, 位置: ({intermediatePoint.Position.X:F2}, {intermediatePoint.Position.Y:F2}, {intermediatePoint.Position.Z:F2}) - 方向: 纵向"); - LogManager.Debug($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}"); - - return true; } } /// - /// 优化直角转折路径点:出现斜线时插入点,在同一直线上时清除多余点 + /// 插入中间点的辅助方法 /// - /// 要优化的路径 - /// 是否进行了优化 - public bool OptimizeRightAnglePathPoints(PathRoute route) + private bool InsertIntermediatePoint(IList points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, + Point3D intermediatePosition, HoistingPointDirection direction) + { + int intermediateIndex = points.Count > 0 ? points.Count : 1; + var intermediatePoint = new PathPoint(intermediatePosition, $"路径点{intermediateIndex}", PathPointType.WayPoint); + intermediatePoint.Direction = direction; + + int insertPosition = startIndex + 1; + points.Insert(insertPosition, intermediatePoint); + + // 更新所有点的索引 + for (int i = 0; i < points.Count; i++) + { + 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}"); + + return true; + } + + /// + /// 正交化路径:将斜线路径转换为轴对齐的直角路径 + /// 出现斜线时插入转折点,使所有线段平行于X、Y或Z轴 + /// + /// 要正交化的路径 + /// 开始正交化的索引,默认从0开始(处理所有点)。用于增量处理,避免修改已处理的点。 + /// 是否进行了正交化 + public bool OrthogonalizePath(PathRoute route, int startIndex = 0) { if (route == null || route.Points.Count < 2) { return false; } + // 确保startIndex有效 + if (startIndex < 0) startIndex = 0; + if (startIndex >= route.Points.Count - 1) return false; + bool hasChanges = false; var points = route.Points; double tolerance = 0.001; // 容差,用于判断是否在同一直线上 - double smallDeviation = 0.5; // 微小偏差阈值,用于修正坐标 double minDistance = 1.0; // 最小距离阈值,小于此距离不删除点 // 第一步:处理斜线,插入转折点或修正坐标 - // 注意:由于可能插入点,需要从后往前遍历或使用while循环 - int i = 0; + // 从startIndex开始,只处理新添加的点 + int i = startIndex; while (i < points.Count - 1) { int originalCount = points.Count; @@ -2074,7 +2133,7 @@ namespace NavisworksTransport if (points.Count > originalCount) { // 插入了点,继续检查当前索引(因为插入点可能需要进一步优化) - LogManager.Debug($"[直角路径优化] 索引{i}处插入了点,继续检查"); + LogManager.Info($"[直角路径优化] 索引{i}处插入了点,继续检查"); } else { @@ -2088,64 +2147,10 @@ namespace NavisworksTransport } } - // 第二步:处理微小转折(X或Y方向微小偏差) - for (i = 1; i < points.Count - 1; i++) - { - var prevPoint = points[i - 1]; - var currentPoint = points[i]; - var nextPoint = points[i + 1]; - - // 计算两个方向的偏差 - double deltaX1 = Math.Abs(currentPoint.Position.X - prevPoint.Position.X); - double deltaY1 = Math.Abs(currentPoint.Position.Y - prevPoint.Position.Y); - double deltaX2 = Math.Abs(nextPoint.Position.X - currentPoint.Position.X); - double deltaY2 = Math.Abs(nextPoint.Position.Y - currentPoint.Position.Y); - - // 检测微小转折:前一段和后一段都有微小偏差,但方向不同 - // 例如:前一段X变化很小,Y变化较大;后一段X变化较大,Y变化很小 - bool hasSmallTurn = false; - - // 前一段垂直,后一段水平 - if (deltaX1 < smallDeviation && deltaY1 > smallDeviation && - deltaY2 < smallDeviation && deltaX2 > smallDeviation) - { - hasSmallTurn = true; - } - // 前一段水平,后一段垂直 - else if (deltaY1 < smallDeviation && deltaX1 > smallDeviation && - deltaX2 < smallDeviation && deltaY2 > smallDeviation) - { - hasSmallTurn = true; - } - - if (hasSmallTurn && currentPoint.Type == PathPointType.WayPoint) - { - // 修正转折点坐标,使其完全垂直 - if (deltaX1 < smallDeviation) - { - // 前一段垂直:调整转折点X与上一点对齐 - currentPoint.Position = new Point3D( - prevPoint.Position.X, - currentPoint.Position.Y, - currentPoint.Position.Z); - LogManager.Debug($"[直角路径优化] 修正微小转折点 {i} X坐标: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})"); - hasChanges = true; - } - else - { - // 前一段水平:调整转折点Y与上一点对齐 - currentPoint.Position = new Point3D( - currentPoint.Position.X, - prevPoint.Position.Y, - currentPoint.Position.Z); - LogManager.Debug($"[直角路径优化] 修正微小转折点 {i} Y坐标: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})"); - hasChanges = true; - } - } - } - - // 第三步:清除在同一直线上的多余点 - i = 1; + // 第二步:清除在同一直线上的多余点 + // 只检查从startIndex开始的新插入点,避免修改已处理的路径 + int startForCollinear = Math.Max(1, startIndex); + i = startForCollinear; while (i < points.Count - 1) { var prevPoint = points[i - 1]; @@ -2166,7 +2171,20 @@ namespace NavisworksTransport continue; } - // 判断三个点是否在同一直线上(只检查X或Y方向相同) + // 判断三个点是否在同一直线上(需要在3D空间中共线) + // 首先检查Z坐标是否相同(在同一平面上) + double deltaZ1 = Math.Abs(currentPoint.Position.Z - prevPoint.Position.Z); + double deltaZ2 = Math.Abs(nextPoint.Position.Z - currentPoint.Position.Z); + bool sameZ = deltaZ1 < tolerance && deltaZ2 < tolerance; + + // 如果Z不同,这三个点一定不共线(一个是垂直过渡点) + if (!sameZ) + { + i++; + continue; + } + + // 在同一平面上,检查XY方向是否共线 bool sameX = Math.Abs(prevPoint.Position.X - currentPoint.Position.X) < tolerance && Math.Abs(currentPoint.Position.X - nextPoint.Position.X) < tolerance; bool sameY = Math.Abs(prevPoint.Position.Y - currentPoint.Position.Y) < tolerance && @@ -2185,7 +2203,7 @@ namespace NavisworksTransport // 删除中间点 points.RemoveAt(i); hasChanges = true; - LogManager.Debug($"[直角路径优化] 删除共线的多余点 {i}: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})"); + LogManager.Info($"[直角路径优化] 删除共线的多余点 {i}: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})"); // 不调整索引,因为删除了一个点,新位置的点会在下一次循环被检查 continue; @@ -2376,8 +2394,8 @@ namespace NavisworksTransport UpdateAerialPathRelatedPoints(route, pointIndex); } - // 吊装路径:优化路径点(处理斜线和清除多余点) - OptimizeRightAnglePathPoints(route); + // 吊装路径:正交化路径(处理斜线和清除多余点) + OrthogonalizePath(route); } else { @@ -2876,35 +2894,40 @@ namespace NavisworksTransport return null; } - // 获取吊装高度(LiftHeight内部是模型单位,转换为米传给方法) - double liftHeightMeters = CurrentRoute.LiftHeight > 0 - ? UnitsConverter.ConvertToMeters(CurrentRoute.LiftHeight) - : 3.0; + // 获取吊装高度(LiftHeight内部是模型单位) + double liftHeightModelUnits = CurrentRoute.LiftHeight > 0 + ? CurrentRoute.LiftHeight + : UnitsConverter.ConvertFromMeters(3.0); // 使用 AerialPathGenerator 生成空中路径点 var generator = new AerialPathGenerator(); confirmPoint = generator.GenerateSmartHoistingPoint( previousPoint, _previewPoint.Position, // 用户点击的地面位置 - liftHeightMeters, + liftHeightModelUnits, // 直接使用模型单位 CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1); LogManager.Info($"[预览点-吊装路径] 已生成空中路径点: {confirmPoint.Name}, 位置: ({confirmPoint.Position.X:F2}, {confirmPoint.Position.Y:F2}, {confirmPoint.Position.Z:F2})"); // 插入到正确位置 + int optimizeStartIndex; if (_previewInsertIndex >= 0 && _previewInsertIndex <= CurrentRoute.Points.Count) { CurrentRoute.InsertPoint(confirmPoint, _previewInsertIndex); LogManager.Info($"[预览点-吊装路径] 已插入到索引 {_previewInsertIndex}"); + // 从插入位置的前一个点开始优化 + optimizeStartIndex = Math.Max(0, _previewInsertIndex - 1); } else { + // 追加模式:记录添加前的点数 + optimizeStartIndex = Math.Max(0, CurrentRoute.Points.Count - 1); CurrentRoute.AddPoint(confirmPoint); LogManager.Info($"[预览点-吊装路径] 已追加到末尾"); } - // 调用路径优化方法处理斜线、微小转折和共线点 - OptimizeRightAnglePathPoints(CurrentRoute); + // 调用正交化方法处理斜线、微小转折和共线点(增量处理) + OrthogonalizePath(CurrentRoute, optimizeStartIndex); LogManager.Info($"[预览点-吊装路径] 路径优化完成,当前路径点数: {CurrentRoute.Points.Count}"); } else @@ -3611,70 +3634,132 @@ namespace NavisworksTransport } else { - // 后续点击:添加空中路径点 - LogManager.Debug($"[吊装模式] 用户点击: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})"); + // 后续点击:添加新的层级(每次都需要输入高度) + LogManager.Debug($"[吊装模式] 用户点击,准备添加新层级: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})"); - // 获取吊装高度(模型单位) - double liftHeightModelUnits = _hoistingLiftHeight; - if (liftHeightModelUnits <= 0) + // 记录地面点击位置,后续在UI线程中处理 + Point3D groundPoint = clickedPoint; + + // 在UI线程中弹出高度对话框并生成路径点 + _uiStateManager.QueueUIUpdate(() => { - liftHeightModelUnits = UnitsConverter.ConvertFromMeters(3.0); - LogManager.Warning($"[吊装模式] 吊装高度无效,使用默认值: 3.0米"); - } - - // 使用 AerialPathGenerator 生成单个空中路径点(智能方向判断) - try - { - var generator = new AerialPathGenerator(); - - // 获取上一个路径点(用于方向判断) - Point3D previousPoint; - if (CurrentRoute.Points.Count > 0) + try { - // 如果已有路径点,使用最后一个路径点 - previousPoint = CurrentRoute.Points.Last().Position; - } - else - { - // 如果还没有路径点(第一次添加中间点),使用提升点 - previousPoint = new Point3D( - _hoistingStartPoint.X, - _hoistingStartPoint.Y, - _hoistingStartPoint.Z + liftHeightModelUnits); - } + // 计算默认高度(上一次的高度,如果没有则使用3米) + double defaultHeightMeters = _hoistingLiftHeight > 0 + ? UnitsConverter.ConvertToMeters(_hoistingLiftHeight) + : 3.0; + + var heightDialog = new NavisworksTransport.UI.WPF.Views.AerialHeightDialog(defaultHeight: defaultHeightMeters); + bool? dialogResult = heightDialog.ShowDialog(); - // 生成空中路径点 - var aerialPoint = generator.GenerateSmartHoistingPoint( - previousPoint, - clickedPoint, - liftHeightModelUnits, - CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1); - - if (aerialPoint != null) - { - // 保存上一个路径点的索引(用于插入中间点) - int previousPointIndex = CurrentRoute.Points.Count - 1; - var previousPathPoint = CurrentRoute.Points[previousPointIndex]; - LogManager.Debug($"[吊装模式] 上一个路径点索引: {previousPointIndex}, 名称: {previousPathPoint.Name}, 位置: ({previousPathPoint.Position.X:F2}, {previousPathPoint.Position.Y:F2})"); - - // 记录原始地面点击位置 - _hoistingGroundIntermediatePoints.Add(clickedPoint); - LogManager.Debug($"[吊装模式] 已记录地面点击点: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})"); - - // 添加空中路径点(临时) - CurrentRoute.AddPoint(aerialPoint); - LogManager.Debug($"[吊装模式] 已添加空中路径点: {aerialPoint.Name}, 位置: ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}), 当前路径点数: {CurrentRoute.Points.Count}"); - - // 调用路径优化方法处理斜线、微小转折和共线点 - OptimizeRightAnglePathPoints(CurrentRoute); - LogManager.Debug($"[吊装模式] 路径优化完成,当前路径点数: {CurrentRoute.Points.Count}"); - for (int i = 0; i < CurrentRoute.Points.Count; i++) + // 如果用户取消,则只是跳过这次点击,继续等待下一次点击 + if (dialogResult != true) { - var p = CurrentRoute.Points[i]; - LogManager.Debug($" [{i}] {p.Name}: ({p.Position.X:F2}, {p.Position.Y:F2}, {p.Position.Z:F2})"); + LogManager.Info("[吊装模式] 用户取消了高度设置,继续等待点击"); + RaiseStatusChanged("已取消,继续点击添加层级或点击完成按钮", PathPlanningStatusType.Info); + return; } - LogManager.Debug($"[吊装模式] 已添加空中路径点: {aerialPoint.Name} ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}, {aerialPoint.Position.Z:F2}) - 方向: {aerialPoint.Direction}"); + // 检查用户是否选择"设为终点"(完成路径) + bool isSetAsEndPoint = heightDialog.IsSetAsEndPoint; + + // 获取用户设置的高度 + double liftHeightMetersInput = heightDialog.LiftHeightMeters; + double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMetersInput); + double previousHeight = _hoistingLiftHeight; + _hoistingLiftHeight = liftHeightModelUnits; + + // 更新路径的吊装高度(使用最新的高度) + if (CurrentRoute != null) + { + CurrentRoute.LiftHeight = liftHeightModelUnits; + } + + LogManager.Info($"[吊装模式] 用户设置高度: {liftHeightMetersInput:F2}米,上一高度: {UnitsConverter.ConvertToMeters(previousHeight):F2}米"); + + // 获取上一个路径点 + Point3D previousPoint; + if (CurrentRoute.Points.Count > 0) + { + previousPoint = CurrentRoute.Points.Last().Position; + var lastPoint = CurrentRoute.Points.Last(); + LogManager.Info($"[吊装模式] 上一路径点: {lastPoint.Name}, Z={lastPoint.Position.Z:F2}"); + } + else + { + previousPoint = new Point3D( + _hoistingStartPoint.X, + _hoistingStartPoint.Y, + _hoistingStartPoint.Z + liftHeightModelUnits); + LogManager.Info($"[吊装模式] 无上一路径点,使用起点+高度: Z={previousPoint.Z:F2}"); + } + + // 记录原始地面点击位置 + _hoistingGroundIntermediatePoints.Add(groundPoint); + LogManager.Debug($"[吊装模式] 已记录地面点击点: ({groundPoint.X:F2}, {groundPoint.Y:F2}, {groundPoint.Z:F2})"); + + // 检查高度是否变化(比较绝对高度) + // liftHeightModelUnits 是相对地面的高度,需要加上起点Z得到绝对高度 + double newAbsoluteHeight = _hoistingStartPoint.Z + liftHeightModelUnits; + double heightDiff = Math.Abs(newAbsoluteHeight - previousPoint.Z); + bool heightChanged = heightDiff > 0.001; + + LogManager.Info($"[吊装模式] 起点=({_hoistingStartPoint.X:F2}, {_hoistingStartPoint.Y:F2}, {_hoistingStartPoint.Z:F2})"); + LogManager.Info($"[吊装模式] 相对高度(米)={liftHeightMetersInput:F2}, 相对高度(模型)={liftHeightModelUnits:F2}"); + LogManager.Info($"[吊装模式] 新绝对高度={newAbsoluteHeight:F2}, 上一位置Z={previousPoint.Z:F2}"); + + // 步骤1:水平移动点(在旧高度上移动到新XY) + // 使用 AerialPathGenerator 生成水平路径点 + LogManager.Info($"[吊装模式] 生成水平路径点: previousPoint=({previousPoint.X:F2}, {previousPoint.Y:F2}, {previousPoint.Z:F2}), groundPoint=({groundPoint.X:F2}, {groundPoint.Y:F2}, {groundPoint.Z:F2})"); + + // 记录添加新点前的点数,用于增量优化 + int pointCountBefore = CurrentRoute.Points.Count; + + var generator = new AerialPathGenerator(); + var aerialPoint = generator.GenerateSmartHoistingPoint( + previousPoint, + groundPoint, + previousPoint.Z, // 保持旧高度 + CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1); + + if (aerialPoint != null) + { + // 添加水平移动路径点 + CurrentRoute.AddPoint(aerialPoint); + LogManager.Info($"[吊装模式] 已添加水平路径点: {aerialPoint.Name}, 位置: ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}, {aerialPoint.Position.Z:F2})"); + + // 更新上一个点为水平路径点 + previousPoint = aerialPoint.Position; + } + + // 步骤2:在添加垂直过渡点前,先正交化水平路径(此时所有点在同一高度,可以处理斜线) + // 使用增量处理,只处理新添加的点(从添加前最后一个点的索引开始) + int orthogonalizeStartIndex = Math.Max(0, pointCountBefore - 1); + OrthogonalizePath(CurrentRoute, orthogonalizeStartIndex); + + // 步骤3:如果需要,添加垂直过渡点 + if (heightChanged) + { + // 高度变化:垂直上升到新高度 + LogManager.Info($"[吊装模式] 高度变化,插入垂直过渡点: Z={previousPoint.Z:F2} -> Z={newAbsoluteHeight:F2}"); + + var verticalTransitionPoint = new Point3D( + previousPoint.X, + previousPoint.Y, + newAbsoluteHeight); + var verticalPoint = new PathPoint( + verticalTransitionPoint, + $"高度过渡{CurrentRoute.Points.Count}", + PathPointType.WayPoint); + verticalPoint.Direction = HoistingPointDirection.Vertical; + CurrentRoute.AddPoint(verticalPoint); + LogManager.Info($"[吊装模式] 已添加垂直过渡点: ({verticalTransitionPoint.X:F2}, {verticalTransitionPoint.Y:F2}, {verticalTransitionPoint.Z:F2})"); + } + else + { + LogManager.Info($"[吊装模式] 高度未变化,无需垂直过渡"); + } // 渲染路径 if (_renderPlugin != null) @@ -3682,15 +3767,28 @@ namespace NavisworksTransport _renderPlugin.RenderPath(CurrentRoute); } - // 提示用户可以继续添加或点击完成 - string directionText = aerialPoint.Direction == HoistingPointDirection.Longitudinal ? "纵向" : "横向"; - RaiseStatusChanged($"已添加{directionText}路径点,继续点击或点击完成按钮", PathPlanningStatusType.Info); + // 如果用户选择"设为终点",调用 FinishEditing 完成路径 + if (isSetAsEndPoint) + { + LogManager.Info("[吊装模式] 用户选择设为终点,调用 FinishEditing 完成路径"); + bool finished = FinishEditing(); + if (finished && CurrentRoute != null) + { + // 触发路径列表更新事件,通知UI刷新 + RaisePathPointsListUpdated(CurrentRoute, "设为终点完成路径"); + } + } + else + { + string transitionText = heightChanged ? "(含高度过渡)" : ""; + RaiseStatusChanged($"已添加层级路径点{transitionText}(高度: {liftHeightMetersInput:F2}米),继续点击添加新层级或点击完成按钮", PathPlanningStatusType.Info); + } } - } - catch (Exception ex) - { - LogManager.Error($"[吊装模式] 生成空中路径点失败: {ex.Message}", ex); - } + catch (Exception ex) + { + LogManager.Error($"[吊装模式] 添加层级失败: {ex.Message}", ex); + } + }, UIUpdatePriority.High); return; } diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs index e7203c3..803b77b 100644 --- a/src/Core/PathPointRenderPlugin.cs +++ b/src/Core/PathPointRenderPlugin.cs @@ -1494,16 +1494,9 @@ namespace NavisworksTransport } else if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting) { - if (isVerticalSegment) - { - // 吊装路径的垂直段(起吊段和下降段):通行空间底面在地面,不需要偏移 - verticalOffset = 0; - } - else - { - // 吊装的水平段:路径点是悬挂点位置,通行空间顶面在悬挂点,中心需要向上偏移半个高度 - verticalOffset = height / 2.0; - } + // 吊装路径:物体顶面中心就是路径点,通行空间整体向下移动半个高度 + // 这样通行空间顶面中心对齐路径点 + verticalOffset = -height / 2.0; } } @@ -1575,16 +1568,16 @@ namespace NavisworksTransport if (_showObjectSpace && Math.Abs(verticalOffset) > 0.001) { - // 通行空间模式且有垂直偏移时,沿着up向量方向平移 + // 通行空间模式且有垂直偏移时,沿Z轴平移(确保只有垂直偏移,没有水平偏移) adjustedStartPoint = new Point3D( - startPoint.X + up.X * verticalOffset, - startPoint.Y + up.Y * verticalOffset, - startPoint.Z + up.Z * verticalOffset + startPoint.X, + startPoint.Y, + startPoint.Z + verticalOffset ); adjustedEndPoint = new Point3D( - endPoint.X + up.X * verticalOffset, - endPoint.Y + up.Y * verticalOffset, - endPoint.Z + up.Z * verticalOffset + endPoint.X, + endPoint.Y, + endPoint.Z + verticalOffset ); } else @@ -1646,28 +1639,28 @@ namespace NavisworksTransport if (_showObjectSpace && Math.Abs(verticalOffset) > 0.001) { - // 通行空间模式且有垂直偏移时,沿着up向量方向平移 + // 通行空间模式且有垂直偏移时,沿Z轴平移(确保只有垂直偏移,没有水平偏移) adjustedStartPoint = new Point3D( - startPoint.X + up.X * verticalOffset, - startPoint.Y + up.Y * verticalOffset, - startPoint.Z + up.Z * verticalOffset + startPoint.X, + startPoint.Y, + startPoint.Z + verticalOffset ); adjustedEndPoint = new Point3D( - endPoint.X + up.X * verticalOffset, - endPoint.Y + up.Y * verticalOffset, - endPoint.Z + up.Z * verticalOffset + endPoint.X, + endPoint.Y, + endPoint.Z + verticalOffset ); - // 对 SampledPoints 应用垂直偏移(沿着up向量方向) + // 对 SampledPoints 应用垂直偏移(沿Z轴) adjustedSampledPoints = new List(); if (edge.SampledPoints != null) { foreach (var point in edge.SampledPoints) { adjustedSampledPoints.Add(new Point3D( - point.X + up.X * verticalOffset, - point.Y + up.Y * verticalOffset, - point.Z + up.Z * verticalOffset + point.X, + point.Y, + point.Z + verticalOffset )); } } @@ -1873,17 +1866,37 @@ namespace NavisworksTransport if (_showObjectSpace && Math.Abs(verticalOffset) > 0.001) { - // 通行空间模式且有垂直偏移时,沿着up向量方向平移 - adjustedStartPoint = new Point3D( - startPoint.Position.X + up.X * verticalOffset, - startPoint.Position.Y + up.Y * verticalOffset, - startPoint.Position.Z + up.Z * verticalOffset - ); - adjustedEndPoint = new Point3D( - endPoint.Position.X + up.X * verticalOffset, - endPoint.Position.Y + up.Y * verticalOffset, - endPoint.Position.Z + up.Z * verticalOffset - ); + // 通行空间模式且有垂直偏移时 + // 垂直空中路径:只沿Z轴偏移(没有水平偏移) + // 水平路径:沿up向量偏移(up是垂直的) + if (isVerticalSegment) + { + // 垂直段:只沿Z轴偏移 + adjustedStartPoint = new Point3D( + startPoint.Position.X, + startPoint.Position.Y, + startPoint.Position.Z + verticalOffset + ); + adjustedEndPoint = new Point3D( + endPoint.Position.X, + endPoint.Position.Y, + endPoint.Position.Z + verticalOffset + ); + } + else + { + // 水平段:沿up向量偏移(up是垂直的) + adjustedStartPoint = new Point3D( + startPoint.Position.X + up.X * verticalOffset, + startPoint.Position.Y + up.Y * verticalOffset, + startPoint.Position.Z + up.Z * verticalOffset + ); + adjustedEndPoint = new Point3D( + endPoint.Position.X + up.X * verticalOffset, + endPoint.Position.Y + up.Y * verticalOffset, + endPoint.Position.Z + up.Z * verticalOffset + ); + } } else { @@ -2789,6 +2802,7 @@ namespace NavisworksTransport // 直线段:单个长方体 // 设置颜色和透明度 graphics.Color(lineMarker.Color, lineMarker.Opacity); + RenderCuboidMarker( graphics, lineMarker.StartPoint, diff --git a/src/PathPlanning/AerialPathGenerator.cs b/src/PathPlanning/AerialPathGenerator.cs index 9502613..8130002 100644 --- a/src/PathPlanning/AerialPathGenerator.cs +++ b/src/PathPlanning/AerialPathGenerator.cs @@ -89,31 +89,30 @@ namespace NavisworksTransport.PathPlanning /// /// 上一个路径点 /// 用户点击的地面投影位置 - /// 吊装高度(米) + /// 目标高度(模型单位) /// 路径点序号 /// 生成的路径点 public PathPoint GenerateSmartHoistingPoint( Point3D previousPoint, Point3D clickedGroundPoint, - double liftHeightMeters, + double targetHeightModelUnits, int pointIndex) { if (previousPoint == null) throw new ArgumentNullException(nameof(previousPoint)); if (clickedGroundPoint == null) throw new ArgumentNullException(nameof(clickedGroundPoint)); - if (liftHeightMeters <= 0) - throw new ArgumentException("提升高度必须大于0", nameof(liftHeightMeters)); + // 注意:targetHeightModelUnits 是绝对高度,可以是负数(如果起点在地下) // 计算两个方向的距离 double deltaX = Math.Abs(clickedGroundPoint.X - previousPoint.X); double deltaY = Math.Abs(clickedGroundPoint.Y - previousPoint.Y); - // 直接使用地面点的完整XY值,不调整位置 + // 使用目标高度作为Z坐标,XY使用地面点击位置 Point3D newPoint = new Point3D( clickedGroundPoint.X, clickedGroundPoint.Y, - previousPoint.Z); // 使用上一个路径点的Z坐标(保持吊装高度) + targetHeightModelUnits); // 使用传入的目标高度 // 判断移动方向(仅用于记录,不影响位置) HoistingPointDirection direction; @@ -195,7 +194,7 @@ namespace NavisworksTransport.PathPlanning var newPoint = GenerateSmartHoistingPoint( previousPoint, groundIntermediatePoints[i], - liftHeightMeters, + liftHeightModelUnits, // 使用模型单位的高度 i + 1); pathPoints.Add(newPoint); diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index 669dd1f..b3d5746 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -2685,10 +2685,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels coreRoute.Points.Remove(corePoint); LogManager.Info($"已从Core数据删除路径点: {corePoint.Name}"); - // 吊装路径:优化路径点(处理斜线和清除多余点) + // 吊装路径:正交化路径(处理斜线和清除多余点) if (coreRoute.PathType == PathType.Hoisting) { - _pathPlanningManager?.OptimizeRightAnglePathPoints(coreRoute); + _pathPlanningManager?.OrthogonalizePath(coreRoute); } // 调用PathPlanningManager的3D删除方法 diff --git a/src/UI/WPF/Views/AerialHeightDialog.xaml b/src/UI/WPF/Views/AerialHeightDialog.xaml index 02ba713..692a6d2 100644 --- a/src/UI/WPF/Views/AerialHeightDialog.xaml +++ b/src/UI/WPF/Views/AerialHeightDialog.xaml @@ -55,17 +55,28 @@ -