From 60bbd468d885b70e06a50df678f6a35a5fddf737 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sat, 31 Jan 2026 17:50:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=8A=E8=A3=85=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E5=A4=A9?= =?UTF-8?q?=E8=BD=A6=E6=A8=AA=E7=A7=BB=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Core/Animation/PathAnimationManager.cs | 33 +- src/Core/PathClickToolPlugin.cs | 7 + src/Core/PathPlanningManager.cs | 428 +++++++++++++++--- src/Core/PathPlanningModels.cs | 30 ++ src/Core/PathPointRenderPlugin.cs | 24 +- src/PathPlanning/AerialPathGenerator.cs | 186 +++++++- src/UI/WPF/ViewModels/PathEditingViewModel.cs | 2 +- 7 files changed, 628 insertions(+), 82 deletions(-) diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index 492e4a1..984f551 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -802,17 +802,24 @@ namespace NavisworksTransport.Core.Animation if (_route.PathType == PathType.Hoisting) { - // 吊装路径:有4个路径点,3个线段 - // 路径点0(起吊点):地面位置,物体底面应该在这里 - // 路径点1(提升点):悬挂点,物体顶面应该在这里 - // 路径点2(平移终点):悬挂点,物体顶面应该在这里 - // 路径点3(下降点):地面位置,物体底面应该在这里 + // 吊装路径:支持动态数量的路径点 + // 路径点结构: + // 点0(起吊点):地面位置 + // 点1(提升点):悬挂点 + // 点2...点N-2:中间悬挂点(用户添加的转向点) + // 点N-1(下降点):悬挂点 + // 点N(落地点):地面位置 // - // 线段0:起吊段(地面→悬挂点) - // 线段1:平移段(悬挂点→悬挂点) - // 线段2:下降段(悬挂点→地面) + // 线段结构: + // 线段0:起吊段(地面→悬挂点)- 垂直 + // 线段1...线段N-2:平移段(悬挂点→悬挂点)- 水平 + // 线段N-1:下降段(悬挂点→地面)- 垂直 - if (segmentIndex == 0) + int totalSegments = _pathPoints.Count - 1; + int firstSegment = 0; // 起吊段 + int lastSegment = totalSegments - 1; // 下降段 + + if (segmentIndex == firstSegment) { // 起吊段:从地面逐渐上升到悬挂点-车辆高度 // 进度0时(地面):物体底面在地面,不向下移动 @@ -821,13 +828,13 @@ namespace NavisworksTransport.Core.Animation framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - heightOffset); LogManager.Debug($"[吊装路径-起吊段] 进度={segmentProgress:F2}, 高度偏移={heightOffset:F2}, 物体底面Z={framePosition.Z:F2}"); } - else if (segmentIndex == 1) + else if (segmentIndex > firstSegment && segmentIndex < lastSegment) { - // 平移段:全程都是悬挂点,物体顶面在悬挂点,物体底面=悬挂点-车辆高度 + // 平移段(中间的所有线段):全程都是悬挂点,物体顶面在悬挂点 framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - vehicleHeight); - LogManager.Debug($"[吊装路径-平移段] 进度={segmentProgress:F2}, 物体底面Z={framePosition.Z:F2}"); + LogManager.Debug($"[吊装路径-平移段] 线段{segmentIndex}, 进度={segmentProgress:F2}, 物体底面Z={framePosition.Z:F2}"); } - else if (segmentIndex == 2) + else if (segmentIndex == lastSegment) { // 下降段:从悬挂点-车辆高度逐渐下降到地面 // 进度0时(悬挂点):物体顶面在悬挂点,物体底面=悬挂点-车辆高度 diff --git a/src/Core/PathClickToolPlugin.cs b/src/Core/PathClickToolPlugin.cs index f663ba4..c494ce1 100644 --- a/src/Core/PathClickToolPlugin.cs +++ b/src/Core/PathClickToolPlugin.cs @@ -44,6 +44,13 @@ namespace NavisworksTransport MouseClicked?.Invoke(sender, result); } + /// + /// 公开方法,允许外部代码手动触发ESC取消 + /// + public static void TriggerEscCancel() + { + // 在 PathPlanningManager 中通过键盘钩子或其他方式调用 + } /// /// 重写鼠标按下事件,获取精确的点击坐标 diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index 6480cb6..ae55b48 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -56,6 +56,15 @@ namespace NavisworksTransport // 吊装起点(用于吊装模式下的两次点击) private Point3D _hoistingStartPoint = null; + // 吊装终点 + private Point3D _hoistingEndPoint = null; + + // 吊装高度(米) + private double _hoistingLiftHeightMeters = 0; + + // 吊装中间点列表(用户点击的地面投影点) + private List _hoistingGroundIntermediatePoints = new List(); + // 路径点3D标记管理 private List _pathPointMarkers; @@ -911,7 +920,7 @@ namespace NavisworksTransport { // 安全地移除事件订阅,多次取消订阅同一处理程序是安全的 PathClickToolPlugin.MouseClicked -= OnToolPluginMouseClicked; - LogManager.Debug("[事件清理] 已安全移除PathPlanningManager.OnToolPluginMouseClicked订阅"); + LogManager.Debug("[事件清理] 已安全移除PathPlanningManager事件订阅"); } catch (Exception cleanupEx) { @@ -1208,6 +1217,33 @@ namespace NavisworksTransport { try { + // 从数据库加载路径数据(如果有的话) + if (route != null && !string.IsNullOrEmpty(route.Id) && _pathDatabase != null) + { + try + { + // 从数据库加载完整路径数据(包括路径点) + var loadedRoute = _pathDatabase.GetPathRouteSync(route.Id); + if (loadedRoute != null && loadedRoute.Points.Count > 0) + { + // 清空现有路径点并从数据库加载 + route.Points.Clear(); + foreach (var point in loadedRoute.Points) + { + route.Points.Add(point); + } + // 更新其他属性 + route.TotalLength = loadedRoute.TotalLength; + route.LiftHeightMeters = loadedRoute.LiftHeightMeters; + LogManager.Debug($"已从数据库加载路径数据: {route.Name}, 共 {loadedRoute.Points.Count} 个点"); + } + } + catch (Exception ex) + { + LogManager.Error($"从数据库加载路径数据失败: {ex.Message}", ex); + } + } + // 直接设置字段,绕过事件触发 var previousRoute = _currentRoute; _currentRoute = route; @@ -1256,8 +1292,13 @@ namespace NavisworksTransport /// 要编辑的路径 public void SwitchToEditingState(PathRoute route) { + if (route == null) + { + throw new ArgumentNullException(nameof(route)); + } + PathEditState = PathEditState.Editing; - EditingRoute = route ?? throw new ArgumentNullException(nameof(route)); + EditingRoute = route; SetCurrentRouteInternal(route, triggerEvent: true); // 智能管理ToolPlugin状态 @@ -1524,6 +1565,17 @@ namespace NavisworksTransport _editingRoute = newRoute; SetCurrentRouteInternal(newRoute, triggerEvent: true); + // 如果是吊装路径,清理吊装模式相关状态 + if (pathType == PathType.Hoisting) + { + _enableHoistingMode = false; // 将在StartClickTool中设置为true + _hoistingStartPoint = null; + _hoistingEndPoint = null; + _hoistingLiftHeightMeters = 0; + _hoistingGroundIntermediatePoints.Clear(); + LogManager.Debug($"[吊装模式] 已清理吊装模式状态,准备新建吊装路径"); + } + // 智能管理ToolPlugin状态 ManageToolPluginForEditState(); @@ -1588,10 +1640,74 @@ namespace NavisworksTransport LogManager.Info($"[{pathTypeName}] 已自动设置最后一个点为终点: {lastPoint.Name}"); } } - // 吊装路径:已有明确的起点和终点,不需要修改 + // 吊装路径:添加下降点和落地点 else if (CurrentRoute.PathType == PathType.Hoisting) { - LogManager.Info($"[吊装路径] 路径已包含明确的起点和终点,无需修改"); + LogManager.Info($"[吊装路径] 正在完成吊装路径,处理终点和下降点"); + + // 确保至少有起吊点、提升点和至少一个空中路径点 + if (CurrentRoute.Points.Count >= 3 && _hoistingGroundIntermediatePoints.Count > 0) + { + // 获取起吊点(用于获取地面Z坐标) + var startPoint = CurrentRoute.Points.First(); + + // 获取吊装高度 + double liftHeightMeters = CurrentRoute.LiftHeightMeters > 0 ? CurrentRoute.LiftHeightMeters : 3.0; + double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMeters); + + // 获取最后一个地面点击点(原始坐标,未被调整过) + var lastGroundClick = _hoistingGroundIntermediatePoints.Last(); + LogManager.Debug($"[吊装路径] 最后一个地面点击点: ({lastGroundClick.X:F2}, {lastGroundClick.Y:F2}, {lastGroundClick.Z:F2})"); + + // 删除最后一个空中路径点(它的坐标可能被调整过,不使用任何信息) + 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坐标为吊装高度 + var descendPoint = new PathPoint( + new Point3D(endPoint.Position.X, endPoint.Position.Y, startPoint.Position.Z + liftHeightModelUnits), + "下降点", + PathPointType.WayPoint); + descendPoint.Direction = HoistingPointDirection.Vertical; + LogManager.Debug($"[吊装路径] 已生成下降点(在终点正上方): ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.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})"); + + // 优化路径点:处理斜线和清除多余点 + OptimizeRightAnglePathPoints(CurrentRoute); + + // 重新计算路径长度 + CurrentRoute.RecalculateLength(); + + // 重新渲染路径 + _renderPlugin?.RenderPath(CurrentRoute); + + LogManager.Info($"[吊装路径] 已完成吊装路径,共 {CurrentRoute.Points.Count} 个路径点"); + + // 清理吊装模式临时变量 + _hoistingStartPoint = null; + _hoistingEndPoint = null; + _hoistingLiftHeightMeters = 0; + _hoistingGroundIntermediatePoints.Clear(); + _enableHoistingMode = false; + LogManager.Debug($"[吊装路径] 已清理吊装模式临时变量"); + } + else + { + LogManager.Warning($"[吊装路径] 路径点不足或没有地面点击记录,无法完成吊装路径"); + } } } @@ -1727,37 +1843,191 @@ namespace NavisworksTransport /// 修改的点的索引 private void UpdateAerialPathRelatedPoints(PathRoute route, int modifiedPointIndex) { - if (route.PathType != PathType.Hoisting || - route.Points.Count != 4) + if (route == null || route.PathType != PathType.Hoisting) { return; } var points = route.Points; + if (points.Count == 0) + { + return; + } // 修改起点(索引0):更新提升点(索引1)的X,Y坐标 - if (modifiedPointIndex == 0) + if (modifiedPointIndex == 0 && points.Count > 1) { var startPoint = points[0]; var liftPoint = points[1]; - liftPoint.X = startPoint.X; - liftPoint.Y = startPoint.Y; + liftPoint.Position = new Point3D(startPoint.Position.X, startPoint.Position.Y, liftPoint.Position.Z); + LogManager.Debug($"[吊装路径] 修改起点,更新提升点位置: ({liftPoint.Position.X:F2}, {liftPoint.Position.Y:F2}, {liftPoint.Position.Z:F2})"); } - // 修改终点(索引3):更新平移终点(索引2)的X,Y坐标 - else if (modifiedPointIndex == 3) + // 修改终点(最后一个点):更新下降点(倒数第二个点)的X,Y坐标 + else if (modifiedPointIndex == points.Count - 1 && points.Count > 1) { - var endPoint = points[3]; - var moveEndPoint = points[2]; - moveEndPoint.X = endPoint.X; - moveEndPoint.Y = endPoint.Y; + var endPoint = points[modifiedPointIndex]; + var descendPoint = points[modifiedPointIndex - 1]; + descendPoint.Position = new Point3D(endPoint.Position.X, endPoint.Position.Y, descendPoint.Position.Z); + LogManager.Debug($"[吊装路径] 修改终点,更新下降点位置: ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})"); } - // 修改起吊高度(通过起点或终点的Z坐标变化):更新提升点和平移终点的Z坐标 - else if (modifiedPointIndex == 0 || modifiedPointIndex == 3) + // 修改起吊高度(通过起点或终点的Z坐标变化):更新提升点和下降点的Z坐标 + else if (modifiedPointIndex == 0 || modifiedPointIndex == points.Count - 1) { - // 这个逻辑在EditLiftHeightCommand中处理 + var modifiedPoint = points[modifiedPointIndex]; + double liftHeightModelUnits = modifiedPoint.Position.Z - (modifiedPointIndex == 0 ? points[0].Position.Z : points[modifiedPointIndex - 1].Position.Z); + + // 更新提升点Z坐标 + if (points.Count > 1) + { + points[1].Position = new Point3D(points[1].Position.X, points[1].Position.Y, points[0].Position.Z + liftHeightModelUnits); + } + + // 更新下降点Z坐标 + if (points.Count > 2) + { + int descendPointIndex = points.Count - 2; + points[descendPointIndex].Position = new Point3D(points[descendPointIndex].Position.X, points[descendPointIndex].Position.Y, points[modifiedPointIndex].Position.Z + liftHeightModelUnits); + } + + LogManager.Debug($"[吊装路径] 修改起吊高度,更新提升点和下降点Z坐标"); } } + /// + /// 优化直角转折路径点:出现斜线时插入点,在同一直线上时清除多余点 + /// + /// 要优化的路径 + /// 是否进行了优化 + private bool OptimizeRightAnglePathPoints(PathRoute route) + { + if (route == null || route.Points.Count < 2) + { + return false; + } + + bool hasChanges = false; + var points = route.Points; + double tolerance = 0.001; // 容差,用于判断是否在同一直线上 + + // 第一步:处理斜线,插入转折点 + for (int i = 0; i < points.Count - 1; i++) + { + var currentPoint = points[i]; + var nextPoint = points[i + 1]; + + // 判断是否为斜线(X和Y都不同) + bool isDiagonal = (Math.Abs(currentPoint.Position.X - nextPoint.Position.X) > tolerance && + Math.Abs(currentPoint.Position.Y - nextPoint.Position.Y) > tolerance); + + if (isDiagonal) + { + // 插入中间转折点 + Point3D intermediatePosition; + HoistingPointDirection direction; + + // 查找提升点的Z坐标作为吊装高度 + double aerialHeight = currentPoint.Position.Z; + for (int j = 0; j < points.Count; j++) + { + if (points[j].Name == "提升点") + { + aerialHeight = points[j].Position.Z; + break; + } + } + + // 计算距离差 + double deltaX = Math.Abs(nextPoint.Position.X - currentPoint.Position.X); + double deltaY = Math.Abs(nextPoint.Position.Y - currentPoint.Position.Y); + + if (deltaX > deltaY) + { + // 纵向移动优先,先移动X + intermediatePosition = new Point3D( + nextPoint.Position.X, + currentPoint.Position.Y, + aerialHeight); + direction = HoistingPointDirection.Longitudinal; + } + else + { + // 横向移动优先,先移动Y + intermediatePosition = new Point3D( + currentPoint.Position.X, + nextPoint.Position.Y, + aerialHeight); + direction = HoistingPointDirection.Lateral; + } + + // 创建中间路径点 + var intermediatePoint = new PathPoint( + intermediatePosition, + $"中间点_{i + 1}", + PathPointType.WayPoint); + + // 设置方向属性 + intermediatePoint.Direction = direction; + + // 插入到两点之间 + points.Insert(i + 1, intermediatePoint); + hasChanges = true; + + LogManager.Debug($"[直角路径优化] 在斜线处插入转折点 {i + 1}: ({intermediatePosition.X:F2}, {intermediatePosition.Y:F2}, {intermediatePosition.Z:F2})"); + + // 跳过新插入的点 + i++; + } + } + + // 第二步:清除在同一直线上的多余点 + for (int i = 1; i < points.Count - 1; i++) + { + var prevPoint = points[i - 1]; + var currentPoint = points[i]; + var nextPoint = points[i + 1]; + + // 只删除普通的WayPoint,不删除起点、终点、起吊点、提升点、下降点等特殊点 + if (currentPoint.Type != PathPointType.WayPoint) + { + continue; + } + + // 额外检查:不删除提升点和下降点(通过名称识别) + if (currentPoint.Name == "提升点" || currentPoint.Name == "下降点") + { + continue; + } + + // 判断三个点是否在同一直线上(只检查X或Y方向相同) + 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 && + Math.Abs(currentPoint.Position.Y - nextPoint.Position.Y) < tolerance; + + bool isCollinear = sameX || sameY; + + if (isCollinear) + { + // 删除中间点 + points.RemoveAt(i); + hasChanges = true; + LogManager.Debug($"[直角路径优化] 删除共线的多余点 {i}: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})"); + + // 调整索引,因为删除了一个点 + i--; + } + } + + if (hasChanges) + { + // 重新计算路径长度 + route.RecalculateLength(); + LogManager.Info($"[直角路径优化] 路径点优化完成,当前点数: {points.Count}"); + } + + return hasChanges; + } + /// /// 更新路径点位置(保存修改) /// @@ -1782,6 +2052,12 @@ namespace NavisworksTransport // 吊装路径:自动更新关联点 UpdateAerialPathRelatedPoints(CurrentRoute, _editingPointIndex); + // 吊装路径:优化路径点(处理斜线和清除多余点) + if (CurrentRoute.PathType == PathType.Hoisting) + { + OptimizeRightAnglePathPoints(CurrentRoute); + } + // 核心修复:调用统一更新函数 CurrentRoute.RecalculateAndSaveRoute($"修改路径点 {pointToUpdate.Name} 位置"); @@ -2808,6 +3084,7 @@ namespace NavisworksTransport LogManager.Info("用户取消了吊装高度设置,清除起点并退出吊装模式"); _hoistingStartPoint = null; _enableHoistingMode = false; + _hoistingGroundIntermediatePoints.Clear(); PathPointRenderPlugin.Instance.ClearPreviewPoint(); StopClickTool(); RaiseStatusChanged("已取消创建吊装路径", PathPlanningStatusType.Info); @@ -2816,6 +3093,7 @@ namespace NavisworksTransport // 获取用户设置的吊装高度 double liftHeightMeters = heightDialog.LiftHeightMeters; + _hoistingLiftHeightMeters = liftHeightMeters; LogManager.Info($"用户设置的吊装高度: {liftHeightMeters:F2}米"); // 保存吊装高度到当前路径 @@ -2824,14 +3102,43 @@ namespace NavisworksTransport CurrentRoute.LiftHeightMeters = liftHeightMeters; } - // 提示用户点击终点 - RaiseStatusChanged($"吊装高度已设置为 {liftHeightMeters:F2}米,请在3D视图中点击设置终点", PathPlanningStatusType.Info); + // 立即生成起吊点和提升点 + try + { + var hoistingStartPoint = new PathPoint(_hoistingStartPoint, "起吊点", PathPointType.StartPoint); + hoistingStartPoint.Direction = HoistingPointDirection.Vertical; + CurrentRoute.AddPoint(hoistingStartPoint); + LogManager.Debug($"[吊装模式] 已添加起吊点: ({hoistingStartPoint.Position.X:F2}, {hoistingStartPoint.Position.Y:F2}, {hoistingStartPoint.Position.Z:F2})"); + + double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMeters); + var liftPoint = new PathPoint( + new Point3D(_hoistingStartPoint.X, _hoistingStartPoint.Y, _hoistingStartPoint.Z + liftHeightModelUnits), + "提升点", + PathPointType.WayPoint); + liftPoint.Direction = HoistingPointDirection.Vertical; + CurrentRoute.AddPoint(liftPoint); + LogManager.Debug($"[吊装模式] 已添加提升点: ({liftPoint.Position.X:F2}, {liftPoint.Position.Y:F2}, {liftPoint.Position.Z:F2})"); + + // 渲染路径 + if (_renderPlugin != null) + { + _renderPlugin.RenderPath(CurrentRoute); + } + } + catch (Exception ex) + { + LogManager.Error($"[吊装模式] 生成起吊点和提升点失败: {ex.Message}", ex); + } + + // 提示用户点击添加转向点 + RaiseStatusChanged($"吊装高度已设置为 {liftHeightMeters:F2}米,点击添加转向点(点击地面位置),完成后点击完成按钮", PathPlanningStatusType.Info); } catch (Exception ex) { LogManager.Error($"[吊装模式] 弹出吊装高度对话框失败: {ex.Message}", ex); _hoistingStartPoint = null; _enableHoistingMode = false; + _hoistingGroundIntermediatePoints.Clear(); PathPointRenderPlugin.Instance.ClearPreviewPoint(); StopClickTool(); RaiseStatusChanged("设置吊装高度失败,已取消创建吊装路径", PathPlanningStatusType.Warning); @@ -2842,67 +3149,72 @@ namespace NavisworksTransport } else { - // 第二次点击:记录终点,生成完整吊装路径 - Point3D endPoint = clickedPoint; - LogManager.Info($"[吊装模式] 已设置终点: ({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2}),开始生成吊装路径"); + // 后续点击:添加空中路径点 + LogManager.Info($"[吊装模式] 用户点击: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})"); // 获取吊装高度 - double liftHeightMeters = CurrentRoute?.LiftHeightMeters ?? 3.0; + double liftHeightMeters = _hoistingLiftHeightMeters; if (liftHeightMeters <= 0) { liftHeightMeters = 3.0; LogManager.Warning($"[吊装模式] 吊装高度无效,使用默认值: {liftHeightMeters}米"); } - // 使用 AerialPathGenerator 生成吊装路径 + // 使用 AerialPathGenerator 生成单个空中路径点(智能方向判断) try { var generator = new AerialPathGenerator(); - var pathPoints = generator.GenerateThreeStepHoistingPath( - _hoistingStartPoint, - endPoint, - liftHeightMeters); - - LogManager.Info($"[吊装模式] 已生成吊装路径,包含 {pathPoints.Count} 个路径点"); - - // 清除当前路径点 - CurrentRoute.Points.Clear(); - - // 添加生成的路径点(使用 AddPoint 方法以自动更新 TotalLength) - foreach (var point in pathPoints) + + // 获取上一个路径点(用于方向判断) + Point3D previousPoint; + if (CurrentRoute.Points.Count > 0) { - CurrentRoute.AddPoint(point); - LogManager.Debug($"[吊装模式] 添加路径点: {point.Name} ({point.Position.X:F2}, {point.Position.Y:F2}, {point.Position.Z:F2})"); - } - - // 渲染路径 - if (_renderPlugin != null) - { - _renderPlugin.RenderPath(CurrentRoute); - LogManager.Info($"[吊装模式] 已渲染吊装路径: {CurrentRoute.Name}"); + // 如果已有路径点,使用最后一个路径点 + previousPoint = CurrentRoute.Points.Last().Position; } else { - LogManager.Warning("[吊装模式] 渲染插件未初始化,无法渲染路径"); + // 如果还没有路径点(第一次添加中间点),使用提升点 + previousPoint = new Point3D( + _hoistingStartPoint.X, + _hoistingStartPoint.Y, + _hoistingStartPoint.Z + UnitsConverter.ConvertFromMeters(liftHeightMeters)); } - // 停止点击工具 - StopClickTool(); + // 生成空中路径点 + var aerialPoint = generator.GenerateSmartHoistingPoint( + previousPoint, + clickedPoint, + liftHeightMeters, + CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1); - // 清除吊装模式状态 - _enableHoistingMode = false; - _hoistingStartPoint = null; + if (aerialPoint != null) + { + // 记录原始地面点击位置 + _hoistingGroundIntermediatePoints.Add(clickedPoint); + LogManager.Debug($"[吊装模式] 已记录地面点击点: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})"); - // 提示用户 - RaiseStatusChanged("吊装路径创建成功", PathPlanningStatusType.Success); + // 添加到当前路径 + CurrentRoute.AddPoint(aerialPoint); + LogManager.Info($"[吊装模式] 已添加空中路径点: {aerialPoint.Name} ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}, {aerialPoint.Position.Z:F2}) - 方向: {aerialPoint.Direction}"); - LogManager.Info("[吊装模式] 吊装路径创建完成"); + // 优化路径点:处理斜线和清除多余点 + OptimizeRightAnglePathPoints(CurrentRoute); + + // 渲染路径 + if (_renderPlugin != null) + { + _renderPlugin.RenderPath(CurrentRoute); + } + + // 提示用户可以继续添加或点击完成 + string directionText = aerialPoint.Direction == HoistingPointDirection.Longitudinal ? "纵向" : "横向"; + RaiseStatusChanged($"已添加{directionText}路径点,继续点击或点击完成按钮", PathPlanningStatusType.Info); + } } catch (Exception ex) { - LogManager.Error($"[吊装模式] 生成吊装路径失败: {ex.Message}", ex); - System.Windows.MessageBox.Show($"生成吊装路径失败: {ex.Message}", "错误", - System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); + LogManager.Error($"[吊装模式] 生成空中路径点失败: {ex.Message}", ex); } return; diff --git a/src/Core/PathPlanningModels.cs b/src/Core/PathPlanningModels.cs index 2981854..917bed0 100644 --- a/src/Core/PathPlanningModels.cs +++ b/src/Core/PathPlanningModels.cs @@ -67,6 +67,28 @@ namespace NavisworksTransport Hoisting = 2 } + /// + /// 吊装路径点方向类型 + /// 用于标识吊装路径中每个路径点的移动方向 + /// + public enum HoistingPointDirection + { + /// + /// 垂直移动(起吊/下降) + /// + Vertical, + + /// + /// 纵向移动(X轴方向) + /// + Longitudinal, + + /// + /// 横向移动(Y轴方向) + /// + Lateral + } + /// /// 通道检测结果 /// @@ -297,6 +319,12 @@ namespace NavisworksTransport /// public double? CustomTurnRadius { get; set; } + /// + /// 方向类型(仅吊装路径使用) + /// 用于标识吊装路径点的移动方向:垂直、纵向(X轴)、横向(Y轴) + /// + public HoistingPointDirection Direction { get; set; } = HoistingPointDirection.Vertical; + /// /// 构造函数 /// @@ -311,6 +339,7 @@ namespace NavisworksTransport Notes = string.Empty; SpeedLimit = 0; CustomTurnRadius = null; + Direction = HoistingPointDirection.Vertical; } /// @@ -330,6 +359,7 @@ namespace NavisworksTransport Notes = string.Empty; SpeedLimit = 0; CustomTurnRadius = null; + Direction = HoistingPointDirection.Vertical; } /// diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs index c6baed2..fef07fc 100644 --- a/src/Core/PathPointRenderPlugin.cs +++ b/src/Core/PathPointRenderPlugin.cs @@ -649,6 +649,9 @@ namespace NavisworksTransport try { + // 清除所有其他路径的可视化,只保留当前路径 + ClearPathsExcept(pathRoute.Id); + var visualization = new PathVisualization { PathId = pathRoute.Id, @@ -1706,8 +1709,25 @@ namespace NavisworksTransport bool isVerticalSegment = false; if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting) { - // 吊装路径:0-1是起吊段(垂直),1-2是平移段(水平),2-3是下降段(垂直) - isVerticalSegment = (i == 0 || i == 2); + // 吊装路径:支持动态数量的路径点 + // 路径点结构: + // 点0(起吊点):地面位置 + // 点1(提升点):悬挂点 + // 点2...点N-2:中间悬挂点(用户添加的转向点) + // 点N-1(下降点):悬挂点 + // 点N(落地点):地面位置 + // + // 线段结构: + // 线段0:起吊段(垂直) + // 线段1...线段N-2:平移段(水平) + // 线段N-1:下降段(垂直) + + int totalSegments = sortedPoints.Count - 1; + int firstSegment = 0; // 起吊段 + int lastSegment = totalSegments - 1; // 下降段 + + // 第一个线段和最后一个线段是垂直的 + isVerticalSegment = (i == firstSegment || i == lastSegment); } // 计算通行空间参数(根据段类型) diff --git a/src/PathPlanning/AerialPathGenerator.cs b/src/PathPlanning/AerialPathGenerator.cs index e78d0d7..accc248 100644 --- a/src/PathPlanning/AerialPathGenerator.cs +++ b/src/PathPlanning/AerialPathGenerator.cs @@ -8,11 +8,19 @@ namespace NavisworksTransport.PathPlanning /// /// 空中路径生成器 /// 用于生成吊装路径(起点吊起 → 平移 → 终点吊下) + /// 支持智能方向判断和基于地面投影点的动态路径生成 /// public class AerialPathGenerator { + /// + /// 方向判断容差(米) + /// 当两个方向的距离差小于此值时,默认选择纵向 + /// + private const double DIRECTION_TOLERANCE = 0.001; + /// /// 生成吊装路径(起点吊起 → 平移 → 终点吊下) + /// 保留原有方法以保持向后兼容 /// /// 起点(吊起位置) /// 终点(下降位置) @@ -34,36 +42,198 @@ namespace NavisworksTransport.PathPlanning double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMeters); // 点1:起吊点(物体初始位置) - pathPoints.Add(new PathPoint( + var startPointPath = new PathPoint( startPoint, "起吊点", - PathPointType.StartPoint)); + PathPointType.StartPoint); + startPointPath.Direction = HoistingPointDirection.Vertical; + pathPoints.Add(startPointPath); // 点2:提升点(起点正上方,达到提升高度) var liftPoint = new Point3D( startPoint.X, startPoint.Y, startPoint.Z + liftHeightModelUnits); - pathPoints.Add(new PathPoint( + var liftPointPath = new PathPoint( liftPoint, "提升点", - PathPointType.WayPoint)); + PathPointType.WayPoint); + liftPointPath.Direction = HoistingPointDirection.Vertical; + pathPoints.Add(liftPointPath); // 点3:平移终点(终点正上方,保持提升高度) var moveEndPoint = new Point3D( endPoint.X, endPoint.Y, liftPoint.Z); // 使用提升点的Z坐标,保持提升高度一致 - pathPoints.Add(new PathPoint( + var moveEndPointPath = new PathPoint( moveEndPoint, "平移终点", - PathPointType.WayPoint)); + PathPointType.WayPoint); + moveEndPointPath.Direction = HoistingPointDirection.Vertical; + pathPoints.Add(moveEndPointPath); // 点4:下降点(终点位置,物体最终位置) - pathPoints.Add(new PathPoint( + var endPointPath = new PathPoint( endPoint, "下降点", - PathPointType.EndPoint)); + PathPointType.EndPoint); + endPointPath.Direction = HoistingPointDirection.Vertical; + pathPoints.Add(endPointPath); + + return pathPoints; + } + + /// + /// 判断移动方向并生成路径点 + /// 根据用户点击的地面投影位置和上一个路径点,自动判断纵向或横向移动 + /// + /// 上一个路径点 + /// 用户点击的地面投影位置 + /// 吊装高度(米) + /// 路径点序号 + /// 生成的路径点 + public PathPoint GenerateSmartHoistingPoint( + Point3D previousPoint, + Point3D clickedGroundPoint, + double liftHeightMeters, + 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)); + + double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMeters); + + // 计算两个方向的距离 + double deltaX = Math.Abs(clickedGroundPoint.X - previousPoint.X); + double deltaY = Math.Abs(clickedGroundPoint.Y - previousPoint.Y); + + Point3D newPoint; + HoistingPointDirection direction; + string pointName; + + // 判断移动方向 + if (Math.Abs(deltaX - deltaY) < DIRECTION_TOLERANCE) + { + // 距离相等,默认选择纵向 + newPoint = new Point3D( + clickedGroundPoint.X, + previousPoint.Y, + previousPoint.Z); // 使用上一个路径点的Z坐标(保持吊装高度) + direction = HoistingPointDirection.Longitudinal; + pointName = $"路径点{pointIndex}"; + } + else if (deltaX > deltaY) + { + // 更偏向纵向移动(X轴) + // 保持Y坐标不变,使用点击的X坐标 + newPoint = new Point3D( + clickedGroundPoint.X, + previousPoint.Y, + previousPoint.Z); // 使用上一个路径点的Z坐标(保持吊装高度) + direction = HoistingPointDirection.Longitudinal; + pointName = $"路径点{pointIndex}"; + } + else + { + // 更偏向横向移动(Y轴) + // 保持X坐标不变,使用点击的Y坐标 + newPoint = new Point3D( + previousPoint.X, + clickedGroundPoint.Y, + previousPoint.Z); // 使用上一个路径点的Z坐标(保持吊装高度) + direction = HoistingPointDirection.Lateral; + pointName = $"路径点{pointIndex}"; + } + + var pathPoint = new PathPoint(newPoint, pointName, PathPointType.WayPoint); + pathPoint.Direction = direction; + + return pathPoint; + } + + /// + /// 基于智能判断生成吊装路径 + /// 支持用户点击地面投影位置,系统自动判断方向并生成空中路径点 + /// + /// 地面起点(用户点击) + /// 地面终点(用户点击) + /// 吊装高度(米) + /// 用户点击的地面中间点列表 + /// 生成的路径点列表 + public List GenerateSmartHoistingPath( + Point3D startPoint, + Point3D endPoint, + double liftHeightMeters, + List groundIntermediatePoints) + { + if (startPoint == null) + throw new ArgumentNullException(nameof(startPoint)); + if (endPoint == null) + throw new ArgumentNullException(nameof(endPoint)); + if (liftHeightMeters <= 0) + throw new ArgumentException("提升高度必须大于0", nameof(liftHeightMeters)); + if (groundIntermediatePoints == null) + throw new ArgumentNullException(nameof(groundIntermediatePoints)); + + var pathPoints = new List(); + double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMeters); + + // 点1:起吊点(地面起点) + var startPathPoint = new PathPoint( + startPoint, + "起吊点", + PathPointType.StartPoint); + startPathPoint.Direction = HoistingPointDirection.Vertical; + pathPoints.Add(startPathPoint); + + // 点2:提升点(起点正上方,吊装高度) + var liftPoint = new Point3D( + startPoint.X, + startPoint.Y, + startPoint.Z + liftHeightModelUnits); + var liftPathPoint = new PathPoint( + liftPoint, + "提升点", + PathPointType.WayPoint); + liftPathPoint.Direction = HoistingPointDirection.Vertical; + pathPoints.Add(liftPathPoint); + + // 中间点(智能判断方向) + Point3D previousPoint = liftPoint; + + for (int i = 0; i < groundIntermediatePoints.Count; i++) + { + var newPoint = GenerateSmartHoistingPoint( + previousPoint, + groundIntermediatePoints[i], + liftHeightMeters, + i + 1); + + pathPoints.Add(newPoint); + previousPoint = newPoint.Position; + } + + // 最后一个空中点(用于垂直下降) + var lastAerialPoint = previousPoint; + var descendPathPoint = new PathPoint( + lastAerialPoint, + "下降点", + PathPointType.WayPoint); + descendPathPoint.Direction = HoistingPointDirection.Vertical; + pathPoints.Add(descendPathPoint); + + // 落地点(地面终点) + var endPathPoint = new PathPoint( + endPoint, + "落地点", + PathPointType.EndPoint); + endPathPoint.Direction = HoistingPointDirection.Vertical; + pathPoints.Add(endPathPoint); return pathPoints; } diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index 59c900f..abc2768 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -1066,7 +1066,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels _pathPlanningManager.StartClickTool(PathPointType.WayPoint, enableRailSnapping: false, enableHoistingMode: true); LogManager.Info($"已启动吊装路径点击工具: {newRoute.Name}"); - UpdateMainStatus($"已进入新建吊装路径模式: {newRoute.Name} - 请在3D视图中点击设置起点"); + UpdateMainStatus($"已进入新建吊装路径模式: {newRoute.Name} - 请在3D视图中点击吊装起点(地面位置)"); LogManager.Info($"开始新建吊装路径: {newRoute.Name},已强制重新初始化ToolPlugin获取鼠标焦点"); } else