diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 1098c34..976af06 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -145,6 +145,8 @@ + + @@ -183,6 +185,7 @@ + LogisticsControlPanel.xaml @@ -232,6 +235,9 @@ EditCoordinatesWindow.xaml + + HoistingHeightDialog.xaml + @@ -345,6 +351,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer diff --git a/src/Commands/CommandManager.cs b/src/Commands/CommandManager.cs index e9f842c..2f9662a 100644 --- a/src/Commands/CommandManager.cs +++ b/src/Commands/CommandManager.cs @@ -302,7 +302,7 @@ namespace NavisworksTransport.Commands { if (args.Length < 1 || !(args[0] is PathRoute route)) throw new ArgumentException("StartAnimation 需要 PathRoute 参数"); - + var parameters = new StartAnimationParameters { TargetRoute = route, @@ -310,10 +310,19 @@ namespace NavisworksTransport.Commands Loop = args.Length > 2 && args[2] is bool v1 && v1, AutoStart = args.Length <= 3 || !(args[3] is bool v2) || v2 }; - + return new StartAnimationCommand(parameters); }); + // 6. 注册创建吊装路径命令 + RegisterCommand("CreateHoistingPath", (object[] args) => + { + if (args.Length < 1 || !(args[0] is HoistingPathParameters parameters)) + throw new ArgumentException("CreateHoistingPath 需要 HoistingPathParameters 参数"); + + return new CreateHoistingPathCommand(parameters); + }); + LogManager.Info("新的业务Commands注册完成"); } diff --git a/src/Commands/CreateHoistingPathCommand.cs b/src/Commands/CreateHoistingPathCommand.cs new file mode 100644 index 0000000..c69f6b2 --- /dev/null +++ b/src/Commands/CreateHoistingPathCommand.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Autodesk.Navisworks.Api; +using NavisworksTransport.Core; +using NavisworksTransport.PathPlanning; +using NavisworksTransport.Utils; +using NavisworksTransport.Core.Config; + +namespace NavisworksTransport.Commands +{ + /// + /// 创建吊装路径命令 + /// + public class CreateHoistingPathCommand : CommandBase + { + private readonly HoistingPathParameters _parameters; + private readonly PathPlanningManager _pathPlanningManager; + + /// + /// 构造函数 + /// + /// 吊装路径参数 + public CreateHoistingPathCommand(HoistingPathParameters parameters) + { + _parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); + _pathPlanningManager = PathPlanningManager.Instance; + } + + /// + /// 执行命令 + /// + protected override Task ExecuteInternalAsync(CancellationToken cancellationToken) + { + try + { + // 验证参数 + var validationResult = ValidateParameters(); + if (!validationResult.IsSuccess) + { + return Task.FromResult(validationResult); + } + + LogManager.Info("开始创建吊装路径..."); + + // 创建路径生成器 + var generator = new HoistingPathGenerator(); + var pathPoints = generator.GenerateHoistingPath( + _parameters.EndPoint, + _parameters.StartHeightMeters); + + var startPoint = pathPoints[0]; + var endPoint = pathPoints[1]; + + LogManager.Info($"吊装路径: 起点=({startPoint.X:F2},{startPoint.Y:F2},{startPoint.Z:F2}), 终点=({endPoint.X:F2},{endPoint.Y:F2},{endPoint.Z:F2}), 起点高度={_parameters.StartHeightMeters:F2}米"); + + // 创建路径对象 + var route = new PathRoute($"吊装路径_{DateTime.Now:HHmmss}") + { + PathType = PathType.Hoisting, + Points = pathPoints, + CreatedTime = DateTime.Now, + LastModified = DateTime.Now + }; + + // 设置车辆参数(从配置获取) + var config = ConfigManager.Instance.Current; + route.MaxVehicleLength = config.PathEditing.VehicleLengthMeters; + route.MaxVehicleWidth = config.PathEditing.VehicleWidthMeters; + route.MaxVehicleHeight = config.PathEditing.VehicleHeightMeters; + route.SafetyMargin = config.PathEditing.SafetyMarginMeters; + + // 计算总长度 + double distanceInModelUnits = GeometryHelper.CalculatePointDistance( + new Point3D(startPoint.X, startPoint.Y, startPoint.Z), + new Point3D(endPoint.X, endPoint.Y, endPoint.Z)); + route.TotalLength = UnitsConverter.ConvertToMeters(distanceInModelUnits); + + LogManager.Info($"吊装路径创建成功: {route.Name}, 路径点数: {pathPoints.Count}, 总长度: {route.TotalLength:F2}米"); + + // 保存路径到数据库 + _pathPlanningManager.AddRoute(route); + + var genericResult = PathPlanningResult.Success(route, "吊装路径创建成功"); + return Task.FromResult(genericResult); + } + catch (Exception ex) + { + LogManager.Error($"创建吊装路径失败: {ex.Message}", ex); + var genericResult = PathPlanningResult.Failure($"创建吊装路径失败: {ex.Message}", ex); + return Task.FromResult(genericResult); + } + } + + /// + /// 验证参数 + /// + protected override PathPlanningResult ValidateParameters() + { + if (_parameters.EndPoint == null) + return PathPlanningResult.ValidationFailure("终点不能为空"); + + if (_parameters.StartHeightMeters <= 0) + return PathPlanningResult.ValidationFailure("起点高度必须大于0"); + + return PathPlanningResult.Success("参数验证通过"); + } + } +} \ No newline at end of file diff --git a/src/Commands/HoistingPathParameters.cs b/src/Commands/HoistingPathParameters.cs new file mode 100644 index 0000000..d050afd --- /dev/null +++ b/src/Commands/HoistingPathParameters.cs @@ -0,0 +1,21 @@ +using Autodesk.Navisworks.Api; + +namespace NavisworksTransport.Commands +{ + /// + /// 吊装路径参数 + /// + public class HoistingPathParameters + { + /// + /// 终点(下层目标位置) + /// + public Point3D EndPoint { get; set; } + + /// + /// 起点高度(米,相对于终点向上) + /// 如果为0,则使用默认高度(3米) + /// + public double StartHeightMeters { get; set; } = 3.0; + } +} \ No newline at end of file diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index d12d6c6..f6f8a3d 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -411,10 +411,15 @@ namespace NavisworksTransport.Core.Animation // 计算朝向(使用前两个路径点的方向) double yaw = Math.Atan2(_pathPoints[1].Y - _pathPoints[0].Y, _pathPoints[1].X - _pathPoints[0].X); - + // 根据路径类型调整起点位置 Point3D startPosition = _pathPoints[0]; - if (_route?.PathType == PathType.Rail) + if (_route?.PathType == PathType.Hoisting) + { + // 吊装路径:路径点就是设备中心位置,无需调整 + LogManager.Debug($"[移动到起点] 吊装路径: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2})"); + } + else if (_route?.PathType == PathType.Rail) { // 空轨路径:points[0] 是空轨下表面位置,车辆顶面应该在这里 // 所以车辆底面 = points[0] - 车辆高度 @@ -431,7 +436,15 @@ namespace NavisworksTransport.Core.Animation // 使用 UpdateObjectPosition 统一处理移动和旋转 UpdateObjectPosition(startPosition, yaw); - LogManager.Info($"物体已初始化到路径起点并对齐朝向: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), yaw={yaw:F3}rad, 路径类型={(_route?.PathType == PathType.Rail ? "空轨" : "地面")}"); + string pathTypeName; + if (_route?.PathType == PathType.Hoisting) + pathTypeName = "吊装"; + else if (_route?.PathType == PathType.Rail) + pathTypeName = "空轨"; + else + pathTypeName = "地面"; + + LogManager.Info($"物体已初始化到路径起点并对齐朝向: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), yaw={yaw:F3}rad, 路径类型={pathTypeName}"); } catch (Exception ex) { @@ -450,13 +463,33 @@ namespace NavisworksTransport.Core.Animation int totalFrames = (int)(_animationDuration * _animationFrameRate); - // 🔥 判断路径类型:空轨路径使用Points,地面路径使用Edges + // 🔥 判断路径类型:吊装路径、空轨路径使用Points,地面路径使用Edges + bool isHoistingPath = _route.PathType == PathType.Hoisting; bool isRailPath = _route.PathType == PathType.Rail; List allSampledPoints = new List(); List segmentLengths = new List(); - if (isRailPath) + if (isHoistingPath) + { + // === 吊装路径:直接使用路径点进行线性插值 === + LogManager.Info($"[吊装路径] 使用路径点进行线性插值,路径点数: {_route.Points.Count}"); + + // 将路径点转换为Point3D列表 + foreach (var point in _route.Points) + { + allSampledPoints.Add(point.Position); + } + + // 计算每段长度 + for (int i = 0; i < allSampledPoints.Count - 1; i++) + { + double lengthInModelUnits = GeometryHelper.CalculatePointDistance(allSampledPoints[i], allSampledPoints[i + 1]); + double lengthInMeters = UnitsConverter.ConvertToMeters(lengthInModelUnits); + segmentLengths.Add(lengthInMeters); + } + } + else if (isRailPath) { // === 空轨路径:直接使用路径点进行线性插值 === LogManager.Info($"[空轨路径] 使用路径点进行线性插值,路径点数: {_route.Points.Count}"); @@ -495,7 +528,15 @@ namespace NavisworksTransport.Core.Animation } double totalLength = _route.TotalLength; - LogManager.Info($"路径总长度: {totalLength:F2}米, 采样点数: {allSampledPoints.Count}, 路径类型: {(isRailPath ? "空轨" : "地面")}"); + string pathTypeName; + if (_route.PathType == PathType.Hoisting) + pathTypeName = "吊装"; + else if (_route.PathType == PathType.Rail) + pathTypeName = "空轨"; + else + pathTypeName = "地面"; + + LogManager.Info($"路径总长度: {totalLength:F2}米, 采样点数: {allSampledPoints.Count}, 路径类型: {pathTypeName}"); // 2. 按帧数采样生成动画帧 _animationFrames = new List(); @@ -579,7 +620,35 @@ namespace NavisworksTransport.Core.Animation Point3D framePosition; double yawRadians; - if (isRailPath) + if (isHoistingPath) + { + // === 吊装路径:在路径点之间进行线性插值 === + int segmentIndex = FindSegmentForDistance(targetDistance, segmentLengths); + if (segmentIndex < 0 || segmentIndex >= segmentLengths.Count) + { + LogManager.Warning($"[吊装路径] 无法找到线段,目标距离:{targetDistance:F2}米"); + continue; + } + + double accumulatedLength = segmentLengths.Take(segmentIndex).Sum(); + double segmentProgress = (targetDistance - accumulatedLength) / segmentLengths[segmentIndex]; + + // 线性插值 + Point3D p1 = allSampledPoints[segmentIndex]; + Point3D p2 = allSampledPoints[segmentIndex + 1]; + framePosition = new Point3D( + p1.X + segmentProgress * (p2.X - p1.X), + p1.Y + segmentProgress * (p2.Y - p1.Y), + p1.Z + segmentProgress * (p2.Z - p1.Z) + ); + + // 计算朝向(使用当前线段的方向) + yawRadians = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X); + + // 吊装路径:路径点就是设备中心位置,无需调整 + LogManager.Debug($"[吊装路径] 设备位置: ({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2})"); + } + else if (isRailPath) { // === 空轨路径:在路径点之间进行线性插值 === int segmentIndex = FindSegmentForDistance(targetDistance, segmentLengths); @@ -691,8 +760,21 @@ namespace NavisworksTransport.Core.Animation ); } + // 吊装路径:排除下层楼面(不排除孔洞,需要检测设备能否通过孔洞) + ModelItem lowerFloorModel = null; + if (_route?.PathType == PathType.Hoisting) + { + lowerFloorModel = _route.LowerFloorModel; + } + foreach (var collider in nearbyObjects) { + // 吊装路径:跳过下层楼面模型 + if (lowerFloorModel != null && ReferenceEquals(collider, lowerFloorModel)) + { + continue; + } + var colliderBox = collider.BoundingBox(); // 使用包围盒距离检测方法 diff --git a/src/Core/PathCurveEngine.cs b/src/Core/PathCurveEngine.cs index 4c98dc0..9a7da6c 100644 --- a/src/Core/PathCurveEngine.cs +++ b/src/Core/PathCurveEngine.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Autodesk.Navisworks.Api; +using NavisworksTransport.Utils; namespace NavisworksTransport { @@ -503,7 +504,7 @@ namespace NavisworksTransport return; } - route.TotalLength = route.Edges.Sum(e => e.PhysicalLength); + route.TotalLength = UnitsConverter.ConvertToMeters(route.Edges.Sum(e => e.PhysicalLength)); } } } \ No newline at end of file diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index fe238fd..e9c28d2 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -50,6 +50,9 @@ namespace NavisworksTransport // 空轨吸附模式标志 private bool _enableRailSnapping = false; + // 吊装模式标志 + private bool _enableHoistingMode = false; + // 路径点3D标记管理 private List _pathPointMarkers; @@ -339,6 +342,11 @@ namespace NavisworksTransport [Obsolete("请使用新的强类型事件接口")] public event EventHandler PathPointsListUpdated_Legacy; + /// + /// 吊装终点选择事件 + /// + public event EventHandler HoistingEndPointSelected; + #endregion #region 事件触发方法 @@ -852,7 +860,7 @@ namespace NavisworksTransport /// 点击类型 public void StartClickTool(PathPointType pointType) { - StartClickTool(pointType, enableRailSnapping: false); + StartClickTool(pointType, enableRailSnapping: false, enableHoistingMode: false); } /// @@ -861,19 +869,31 @@ namespace NavisworksTransport /// 点击类型 /// 是否启用空轨吸附 public void StartClickTool(PathPointType pointType, bool enableRailSnapping) + { + StartClickTool(pointType, enableRailSnapping, enableHoistingMode: false); + } + + /// + /// 启动点击工具(支持空轨吸附和吊装模式) + /// + /// 点击类型 + /// 是否启用空轨吸附 + /// 是否启用吊装模式 + public void StartClickTool(PathPointType pointType, bool enableRailSnapping, bool enableHoistingMode) { try { CurrentPointType = pointType; _enableRailSnapping = enableRailSnapping; + _enableHoistingMode = enableHoistingMode; // 检查是否在自动路径模式 - 如果是,则不订阅PathPlanningManager的事件 bool shouldSubscribeToEvents = !IsInAutoPathMode; - LogManager.Info($"StartClickTool - 自动路径模式: {IsInAutoPathMode}, 订阅事件: {shouldSubscribeToEvents}, 空轨吸附: {enableRailSnapping}"); + LogManager.Info($"StartClickTool - 自动路径模式: {IsInAutoPathMode}, 订阅事件: {shouldSubscribeToEvents}, 空轨吸附: {enableRailSnapping}, 吊装模式: {enableHoistingMode}"); ActivateToolPlugin(shouldSubscribeToEvents); PathEditState = PathEditState.AddingPoints; - LogManager.Info($"点击工具已启动,类型: {pointType},事件订阅: {shouldSubscribeToEvents},空轨吸附: {enableRailSnapping}"); + LogManager.Info($"点击工具已启动,类型: {pointType},事件订阅: {shouldSubscribeToEvents},空轨吸附: {enableRailSnapping},吊装模式: {enableHoistingMode}"); } catch (Exception ex) { @@ -1533,6 +1553,9 @@ namespace NavisworksTransport /// 完成当前编辑并保存 /// /// 是否成功完成编辑 + /// + /// 完成路径编辑 + /// public bool FinishEditing() { if (!IsInEditableState) @@ -1543,20 +1566,29 @@ namespace NavisworksTransport try { - // 自动设置最后一个点为终点 + // 根据路径类型执行不同的完成逻辑 if (CurrentRoute != null && CurrentRoute.Points.Count > 1) { - var lastPoint = CurrentRoute.Points.Last(); - if (lastPoint.Type != PathPointType.StartPoint) // 起点不能是终点 + // 地面路径:自动设置最后一个点为终点 + if (CurrentRoute.PathType == PathType.Ground || CurrentRoute.PathType == PathType.Rail) { - lastPoint.Type = PathPointType.EndPoint; - lastPoint.Name = GeneratePointName(PathPointType.EndPoint); + var lastPoint = CurrentRoute.Points.Last(); + if (lastPoint.Type != PathPointType.StartPoint) // 起点不能是终点 + { + lastPoint.Type = PathPointType.EndPoint; + lastPoint.Name = GeneratePointName(PathPointType.EndPoint); - // 更新3D路径可视化 - // 重新渲染整个路径以反映类型变更 - _renderPlugin?.RenderPath(CurrentRoute); + // 更新3D路径可视化 + // 重新渲染整个路径以反映类型变更 + _renderPlugin?.RenderPath(CurrentRoute); - LogManager.Info($"已自动设置最后一个点为终点: {lastPoint.Name}"); + LogManager.Info($"[地面路径] 已自动设置最后一个点为终点: {lastPoint.Name}"); + } + } + // 吊装路径:已有明确的起点和终点,不需要修改 + else if (CurrentRoute.PathType == PathType.Hoisting) + { + LogManager.Info($"[吊装路径] 路径已包含明确的起点和终点,无需修改"); } } @@ -2694,8 +2726,17 @@ namespace NavisworksTransport { try { - // 如果启用了空轨吸附,先吸附到基准路径 Point3D clickedPoint = pickResult.Point; + + // 吊装模式处理 + if (_enableHoistingMode) + { + LogManager.Debug("[吊装模式] 处理吊装终点选择"); + HoistingEndPointSelected?.Invoke(this, clickedPoint); + return; + } + + // 如果启用了空轨吸附,先吸附到基准路径 if (_enableRailSnapping) { clickedPoint = SnapToRailBaseline(clickedPoint); diff --git a/src/Core/PathPlanningModels.cs b/src/Core/PathPlanningModels.cs index d404370..9c0f359 100644 --- a/src/Core/PathPlanningModels.cs +++ b/src/Core/PathPlanningModels.cs @@ -58,7 +58,12 @@ namespace NavisworksTransport /// /// 空轨路径 - 车辆悬挂在空轨下方运行 /// - Rail = 1 + Rail = 1, + + /// + /// 吊装路径 - 设备从孔洞垂直下降 + /// + Hoisting = 2 } /// @@ -567,6 +572,18 @@ namespace NavisworksTransport /// public PathType PathType { get; set; } = PathType.Ground; + /// + /// 吊装孔洞模型(仅吊装路径使用) + /// + [XmlIgnore] + public ModelItem HoleModel { get; set; } + + /// + /// 下层楼面模型(仅吊装路径使用,用于碰撞排除) + /// + [XmlIgnore] + public ModelItem LowerFloorModel { get; set; } + // 数据库分析相关属性 /// /// 碰撞数量(从数据库加载) diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs index 53d0547..3838e75 100644 --- a/src/Core/PathPointRenderPlugin.cs +++ b/src/Core/PathPointRenderPlugin.cs @@ -123,7 +123,12 @@ namespace NavisworksTransport /// /// 空轨基准路径样式(浅红色) /// - RailBaseline + RailBaseline, + + /// + /// 吊装路径样式(紫色) + /// + HoistingLine } /// @@ -1239,11 +1244,17 @@ namespace NavisworksTransport // 地面路径使用曲线化后的路径(Edges) // 空轨路径只显示控制点连线,不显示路径连线 + // 吊装路径使用控制点连线,不显示路径连线 if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail) { // 空轨路径:只显示控制点连线,不构建路径连线 LogManager.Debug($"[路径渲染] 空轨路径只显示控制点连线,不显示路径连线"); } + else if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting) + { + // 吊装路径:只显示控制点连线,不构建路径连线 + LogManager.Debug($"[路径渲染] 吊装路径只显示控制点连线,不显示路径连线"); + } else if (visualization.PathRoute.Edges != null && visualization.PathRoute.Edges.Count > 0) { // 地面路径:使用曲线化后的路径 @@ -1280,6 +1291,13 @@ namespace NavisworksTransport /// 排序后的路径点列表 private void BuildControlLines(PathVisualization visualization, List sortedPoints) { + // 根据路径类型选择连线样式 + RenderStyleName lineStyleName = RenderStyleName.PreviewLine; + if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting) + { + lineStyleName = RenderStyleName.HoistingLine; + } + // 构建控制点之间的连线(所有相邻控制点) for (int i = 0; i < sortedPoints.Count - 1; i++) { @@ -1290,7 +1308,7 @@ namespace NavisworksTransport { StartPoint = currentPoint.Position, EndPoint = nextPoint.Position, - Color = GetRenderStyle(RenderStyleName.PreviewLine).Color, + Color = GetRenderStyle(lineStyleName).Color, Radius = GetLineRadius() * 0.5, // 比实际路径细 SegmentType = PathSegmentType.Straight, FromIndex = currentPoint.Index, @@ -1676,6 +1694,9 @@ namespace NavisworksTransport case RenderStyleName.RailBaseline: return new RenderStyle(Color.FromByteRGB(255, 138, 128), 0.7); // 浅红色,30%透明 + case RenderStyleName.HoistingLine: + return new RenderStyle(Color.FromByteRGB(156, 39, 176), 0.8); // Material Purple吊装路径,20%透明 + default: return new RenderStyle(Color.White, 1.0); // 默认白色,完全不透明 } diff --git a/src/PathPlanning/HoistingPathGenerator.cs b/src/PathPlanning/HoistingPathGenerator.cs new file mode 100644 index 0000000..2a0fd91 --- /dev/null +++ b/src/PathPlanning/HoistingPathGenerator.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Autodesk.Navisworks.Api; +using NavisworksTransport.Utils; +using NavisworksTransport.Core.Config; + +namespace NavisworksTransport.PathPlanning +{ + /// + /// 吊装路径生成器 + /// 用于生成垂直吊装路径,只包含起点和终点两个点 + /// + public class HoistingPathGenerator + { + /// + /// 生成吊装路径点(只包含起点和终点) + /// + /// 终点(下层目标位置) + /// 起点高度(米,相对于终点向上) + /// 路径点列表(仅包含起点和终点) + public List GenerateHoistingPath( + Point3D endPoint, + double startHeightMeters) + { + if (endPoint == null) + throw new ArgumentNullException(nameof(endPoint), "终点不能为空"); + + if (startHeightMeters <= 0) + throw new ArgumentException("起点高度必须大于0", nameof(startHeightMeters)); + + var pathPoints = new List(); + + // 计算起点:从终点垂直向上 + var startHeightModelUnits = UnitsConverter.ConvertFromMeters(startHeightMeters); + var startPoint = new Point3D( + endPoint.X, + endPoint.Y, + endPoint.Z + startHeightModelUnits); + + // 只生成起点和终点两个点,不进行插值 + pathPoints.Add(new PathPoint( + startPoint, + "起点", + PathPointType.StartPoint)); + + pathPoints.Add(new PathPoint( + endPoint, + "终点", + PathPointType.EndPoint)); + + return pathPoints; + } + } +} \ No newline at end of file diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index b9c4c28..c6fdecc 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -635,6 +635,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels public ICommand NewPathCommand { get; private set; } public ICommand NewRailPathCommand { get; private set; } + public ICommand NewHoistingPathCommand { get; private set; } public ICommand DeletePathCommand { get; private set; } public ICommand RenamePathCommand { get; private set; } public ICommand StartEditCommand { get; private set; } @@ -802,6 +803,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels { NewPathCommand = new RelayCommand(async () => await ExecuteNewPathAsync(), () => CanExecuteNewPath); NewRailPathCommand = new RelayCommand(async () => await ExecuteNewRailPathAsync(), () => CanExecuteNewRailPath); + NewHoistingPathCommand = new RelayCommand(async () => await ExecuteNewHoistingPathAsync()); DeletePathCommand = new RelayCommand(async () => await ExecuteDeletePathAsync()); RenamePathCommand = new RelayCommand(async () => await ExecuteRenamePathAsync()); StartEditCommand = new RelayCommand(async () => await ExecuteAddPathPointAsync(), () => CanExecuteStartEdit); @@ -1004,6 +1006,147 @@ namespace NavisworksTransport.UI.WPF.ViewModels }, "新建空轨路径"); } + /// + /// 执行新建吊装路径命令 + /// + private async Task ExecuteNewHoistingPathAsync() + { + await SafeExecuteAsync(() => + { + UpdateMainStatus("正在创建吊装路径,请选择吊装终点位置..."); + + // 激活工具,等待用户选择终点位置 + if (_pathPlanningManager != null) + { + // 清除所有现有路径的可视化显示 + if (PathPointRenderPlugin.Instance != null) + { + try + { + PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); + LogManager.Info("新建吊装路径:已清除现有路径可视化显示(保留网格可视化)"); + } + catch (Exception ex) + { + LogManager.Error($"新建吊装路径:清除现有路径可视化失败: {ex.Message}", ex); + } + } + + // 强制重新初始化ToolPlugin以确保获得鼠标焦点 + if (!ForceReinitializeToolPlugin(subscribeToEvents: true)) + { + UpdateMainStatus("ToolPlugin初始化失败,请重试"); + LogManager.Error("新建吊装路径:ToolPlugin初始化失败"); + return; + } + + // 启动点击工具,等待用户选择终点位置 + _pathPlanningManager.StartClickTool(PathPointType.EndPoint, enableRailSnapping: false, enableHoistingMode: true); + + UpdateMainStatus("请在3D视图中点击选择吊装终点位置"); + LogManager.Info("开始新建吊装路径,等待用户选择终点位置"); + } + else + { + UpdateMainStatus("路径规划管理器未初始化"); + LogManager.Error("路径规划管理器未初始化"); + } + }, "新建吊装路径"); + } + + /// + /// 处理吊装路径终点选择 + /// + public void OnHoistingEndPointSelected(object sender, Point3D endPoint) + { + // 创建并显示对话框 + var dialog = new Views.HoistingHeightDialog(3.0); // 默认3米 + + if (dialog.ShowDialog() == true) + { + CreateHoistingPath(endPoint, dialog.StartHeightMeters); + } + else + { + UpdateMainStatus("已取消创建吊装路径"); + } + } + + /// + /// 创建吊装路径 + /// + private async void CreateHoistingPath(Point3D endPoint, double startHeightMeters) + { + await SafeExecuteAsync(async () => + { + try + { + var parameters = new HoistingPathParameters + { + EndPoint = endPoint, + StartHeightMeters = startHeightMeters + }; + + var command = new CreateHoistingPathCommand(parameters); + var result = await command.ExecuteAsync(); + + if (result.IsSuccess) + { + var genericResult = result as PathPlanningResult; + if (genericResult != null && genericResult.Data != null) + { + var route = genericResult.Data; + + // 创建对应的 WPF ViewModel + var newPathViewModel = new PathRouteViewModel + { + Id = route.Id, + Name = route.Name, + Description = route.Description, + IsActive = true, + PathType = PathType.Hoisting + }; + + // 转换路径点 + foreach (var corePoint in route.Points) + { + var wpfPoint = new PathPointViewModel + { + Id = corePoint.Id, + Name = corePoint.Name, + X = corePoint.X, + Y = corePoint.Y, + Z = corePoint.Z, + Type = corePoint.Type + }; + newPathViewModel.Points.Add(wpfPoint); + } + + // 添加到 UI 列表并选中 + PathRoutes.Add(newPathViewModel); + SelectedPathRoute = newPathViewModel; + + // 绘制路径可视化 + _pathPlanningManager.DrawRouteVisualization(route, isAutoPath: false); + + // 完成编辑,退出编辑状态 + _pathPlanningManager.FinishEditing(); + + UpdateMainStatus($"吊装路径创建成功: {route.Name}"); + LogManager.Info($"吊装路径创建成功: {route.Name}"); + } + } + } + catch (Exception ex) + { + UpdateMainStatus($"创建吊装路径失败: {ex.Message}"); + LogManager.Error($"创建吊装路径失败: {ex.Message}", ex); + MessageBox.Show($"创建吊装路径失败: {ex.Message}", "错误", + MessageBoxButton.OK, MessageBoxImage.Error); + } + }, "创建吊装路径"); + } + /// /// 提取并渲染所有空轨模型的基准路径 /// @@ -2710,6 +2853,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 添加错误事件订阅 - 用于处理自动路径规划失败等错误 _pathPlanningManager.ErrorOccurred += OnPathPlanningError; + // 订阅吊装终点选择事件 + _pathPlanningManager.HoistingEndPointSelected += OnHoistingEndPointSelected; + LogManager.Debug("PathEditingViewModel已成功订阅PathPlanningManager事件"); } catch (Exception ex) @@ -2732,6 +2878,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels _pathPlanningManager.CurrentRouteChanged -= OnCurrentRouteChanged; _pathPlanningManager.EditStateChanged -= OnEditStateChanged; _pathPlanningManager.RouteGenerated -= OnRouteGenerated; + _pathPlanningManager.HoistingEndPointSelected -= OnHoistingEndPointSelected; _pathPlanningManager.RoutesLoaded -= OnRoutesLoaded; _pathPlanningManager.ErrorOccurred -= OnPathPlanningError; diff --git a/src/UI/WPF/Views/EditCoordinatesWindow.xaml b/src/UI/WPF/Views/EditCoordinatesWindow.xaml index f1616c2..7d3afc6 100644 --- a/src/UI/WPF/Views/EditCoordinatesWindow.xaml +++ b/src/UI/WPF/Views/EditCoordinatesWindow.xaml @@ -5,6 +5,7 @@ WindowStartupLocation="CenterScreen" ResizeMode="NoResize" ShowInTaskbar="False" + Topmost="True" Background="White"> diff --git a/src/UI/WPF/Views/HoistingHeightDialog.xaml b/src/UI/WPF/Views/HoistingHeightDialog.xaml new file mode 100644 index 0000000..869bfc3 --- /dev/null +++ b/src/UI/WPF/Views/HoistingHeightDialog.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +