diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index 611b138..a6fff0a 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -6,6 +6,7 @@ 1. [ ](性能优化)用几何方法识别通道的坡度变化(侧面上表面轮廓线),给通道网格准确的z坐标 2. [ ](BUG)动画中的包围盒检测和ClashDetective检测结果不一致,是因为碰撞间隙不同造成的。(Autodesk官方建议保守测试+0公差解决碰撞检测结果不准确的问题) +3. [x] (功能)实现完整的路径点可视化编辑 ### [2025/08/29] diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index dcbe342..cb5a242 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -46,6 +46,15 @@ namespace NavisworksTransport // 路径点3D标记管理 private List _pathPointMarkers; + // 预览点管理 + private PathPoint _previewPoint = null; + private bool _isPreviewMode = false; + + /// + /// 获取当前是否在预览模式 + /// + public bool IsPreviewMode => _isPreviewMode; + // 静态引用,用于处理ToolPlugin事件 private static PathPlanningManager _activePathManager; @@ -1371,6 +1380,189 @@ namespace NavisworksTransport } } + /// + /// 设置预览点位置(仅用于预览,不添加到路径中) + /// + /// 3D世界坐标 + /// 点类型 + /// 预览点对象 + public PathPoint SetPreviewPoint(Point3D worldPoint, PathPointType? pointType = null) + { + // 确保在编辑状态下才能设置预览点 + if (!IsInEditableState) + { + RaiseErrorOccurred("不在编辑状态,无法设置预览点"); + return null; + } + + // 如果没有当前路径,则无法设置预览点 + if (CurrentRoute == null) + { + RaiseErrorOccurred("内部错误:当前路径丢失"); + return null; + } + + try + { + // 确定路径点类型 + PathPointType finalPointType; + if (pointType.HasValue) + { + finalPointType = pointType.Value; + } + else + { + // 自动判断类型:第一个点为起点,其余为路径点 + finalPointType = (CurrentRoute.Points.Count == 0) + ? PathPointType.StartPoint + : PathPointType.WayPoint; + } + + // 创建预览点(不添加到路径中) + _previewPoint = new PathPoint + { + Name = $"预览-{GeneratePointName(finalPointType)}", + Position = worldPoint, + Type = finalPointType + }; + + _isPreviewMode = true; + LogManager.Info($"预览点已设置: {_previewPoint.Name}, 位置: ({worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2})"); + + // 绘制预览点可视化(灰色) + try + { + DrawPreviewPointVisualization(_previewPoint); + LogManager.Info($"预览点3D可视化已更新: {_previewPoint.Name}"); + } + catch (Exception renderEx) + { + LogManager.Error($"绘制预览点3D可视化失败: {renderEx.Message}"); + } + + return _previewPoint; + } + catch (Exception ex) + { + RaiseErrorOccurred($"设置预览点失败: {ex.Message}", ex); + return null; + } + } + + /// + /// 确认预览点,将其添加到当前路径中 + /// + /// 添加的路径点,如果失败返回null + public PathPoint ConfirmPreviewPoint() + { + if (!_isPreviewMode || _previewPoint == null) + { + RaiseErrorOccurred("没有预览点可确认"); + return null; + } + + if (CurrentRoute == null) + { + RaiseErrorOccurred("内部错误:当前路径丢失"); + return null; + } + + try + { + // 创建正式的路径点 + var confirmPoint = new PathPoint + { + Name = GeneratePointName(_previewPoint.Type), + Position = _previewPoint.Position, + Type = _previewPoint.Type + }; + + // 添加到当前路径 + CurrentRoute.Points.Add(confirmPoint); + LogManager.Info($"路径点已确认添加: {confirmPoint.Name}, 位置: ({confirmPoint.Position.X:F2}, {confirmPoint.Position.Y:F2}, {confirmPoint.Position.Z:F2})"); + + // 清除预览状态 + ClearPreviewPoint(); + + // 绘制3D路径可视化 + try + { + DrawRouteVisualization(CurrentRoute, isAutoPath: false); + LogManager.Info($"手工路径3D可视化已更新: {confirmPoint.Name}"); + } + catch (Exception renderEx) + { + LogManager.Error($"绘制手工路径3D可视化失败: {renderEx.Message}"); + } + + // 触发路径点添加事件 + RaisePathPointOperation(PathPointOperationType.Added, confirmPoint, CurrentRoute); + + // 触发路径列表更新事件 + RaisePathPointsListUpdated(CurrentRoute, "确认添加预览点"); + + return confirmPoint; + } + catch (Exception ex) + { + RaiseErrorOccurred($"确认预览点失败: {ex.Message}", ex); + return null; + } + } + + /// + /// 清除预览点 + /// + public void ClearPreviewPoint() + { + if (_isPreviewMode && _previewPoint != null) + { + LogManager.Info($"清除预览点: {_previewPoint.Name}"); + + // 清除预览点可视化 + try + { + ClearPreviewPointVisualization(); + } + catch (Exception ex) + { + LogManager.Error($"清除预览点可视化失败: {ex.Message}"); + } + } + + _previewPoint = null; + _isPreviewMode = false; + } + + /// + /// 绘制预览点可视化(灰色) + /// + /// 预览点 + private void DrawPreviewPointVisualization(PathPoint previewPoint) + { + if (_renderPlugin != null && previewPoint != null) + { + // 先清除之前的预览点 + _renderPlugin.ClearPreviewPoint(); + + // 绘制新的灰色预览点 + _renderPlugin.RenderPreviewPoint(previewPoint); + LogManager.Info($"预览点可视化已绘制: {previewPoint.Name}"); + } + } + + /// + /// 清除预览点可视化 + /// + private void ClearPreviewPointVisualization() + { + if (_renderPlugin != null) + { + _renderPlugin.ClearPreviewPoint(); + LogManager.Info("预览点可视化已清除"); + } + } + /// /// 获取路径统计信息 /// @@ -1856,7 +2048,7 @@ namespace NavisworksTransport SearchAndSetTraversableChannels(); } - // 检查是否在可通行的物流模型内并添加路径点 + // 检查是否在可通行的物流模型内并处理点击 if (_selectedChannels != null && _selectedChannels.Any()) { bool isInTraversableLogisticsModel = IsItemInSelectedChannels(pickResult.ModelItem) || @@ -1866,29 +2058,48 @@ namespace NavisworksTransport if (isInTraversableLogisticsModel) { - // 手动路径编辑 - 添加路径点并绘制 - LogManager.WriteLog("[手动编辑] 调用AddPathPointIn3D添加路径点"); - var pathPoint = AddPathPointIn3D(pickResult.Point); - - if (pathPoint != null) + // 手动路径编辑 - 根据当前模式处理点击 + if (PathEditState == PathEditState.AddingPoints) { - LogManager.WriteLog($"[手动编辑] ✓ 路径点添加成功: {pathPoint.Name}"); + // 添加路径点模式 - 使用预览点 + LogManager.WriteLog("[手动编辑] 设置预览点位置"); + var previewPoint = SetPreviewPoint(pickResult.Point); + + if (previewPoint != null) + { + LogManager.WriteLog($"[手动编辑] ✓ 预览点已设置: {previewPoint.Name}"); + } + else + { + LogManager.WriteLog("[手动编辑] ✗ 预览点设置失败"); + } } else { - LogManager.WriteLog("[手动编辑] ✗ 路径点添加失败"); + // 其他编辑模式 - 保持原有逻辑 + LogManager.WriteLog("[手动编辑] 调用AddPathPointIn3D添加路径点"); + var pathPoint = AddPathPointIn3D(pickResult.Point); + + if (pathPoint != null) + { + LogManager.WriteLog($"[手动编辑] ✓ 路径点添加成功: {pathPoint.Name}"); + } + else + { + LogManager.WriteLog("[手动编辑] ✗ 路径点添加失败"); + } } } else { LogManager.WriteLog("[手动编辑] ✗ 点击位置不在可通行的物流模型内"); - OnStatusChanged("点击位置不是可通行的物流模型,请在可通行的物流模型上点击"); + RaiseErrorOccurred("点击位置不在物流通道内,请选择有效的物流路径位置"); } } else { - LogManager.WriteLog("[手动编辑] ✗ 没有可通行的物流模型"); - OnStatusChanged("没有找到任何可通行的物流模型,请先为模型设置可通行的物流属性"); + LogManager.WriteLog("[手动编辑] ✗ 未找到可通行的物流模型"); + RaiseErrorOccurred("未找到可通行的物流通道,请先选择或配置物流通道"); } } diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs index 9da4063..dea8d92 100644 --- a/src/Core/PathPointRenderPlugin.cs +++ b/src/Core/PathPointRenderPlugin.cs @@ -107,6 +107,9 @@ namespace NavisworksTransport // 为向后兼容保留的旧字段 private List _circleMarkers = new List(); + // 预览点标记 + private CircleMarker _previewMarker = null; + // 静态实例,用于外部访问 private static PathPointRenderPlugin _instance; @@ -186,20 +189,23 @@ namespace NavisworksTransport return; // 静默返回,避免日志泛滥 } - // 检查是否有路径需要渲染 + // 检查是否有路径或预览点需要渲染 int pathCount; + bool hasPreviewPoint; lock (_lockObject) { pathCount = _pathVisualizations.Count; + hasPreviewPoint = _previewMarker != null; } - if (pathCount == 0) return; + if (pathCount == 0 && !hasPreviewPoint) return; // 使用BeginModelContext确保正确的渲染上下文 graphics.BeginModelContext(); lock (_lockObject) { + // 渲染所有正式路径 foreach (var visualization in _pathVisualizations.Values) { // 渲染所有点标记 @@ -216,6 +222,13 @@ namespace NavisworksTransport graphics.Cylinder(lineMarker.StartPoint, lineMarker.EndPoint, lineMarker.Radius); } } + + // 渲染预览点(灰色) + if (_previewMarker != null && _previewMarker.IsVisible) + { + graphics.Color(_previewMarker.Color, 0.7); // 使用半透明效果 + graphics.Sphere(_previewMarker.Position, _previewMarker.Radius); + } } graphics.EndModelContext(); @@ -750,6 +763,82 @@ namespace NavisworksTransport // 由于新系统没有直接的序号对应关系,这个方法不再有效 } + /// + /// 渲染预览点(灰色显示) + /// + /// 预览点对象 + public void RenderPreviewPoint(PathPoint previewPoint) + { + try + { + if (previewPoint == null) + { + LogManager.WriteLog("[预览点渲染] 预览点为null,跳过渲染"); + return; + } + + lock (_lockObject) + { + LogManager.WriteLog($"[预览点渲染] 开始渲染预览点: {previewPoint.Name}"); + + // 获取适当的半径 + double radius = GetRadiusForPointType(previewPoint.Type); + + // 使用白色作为预览点颜色(灰色在Navisworks API中不可用) + Color previewColor = Color.White; + + // 创建预览点标记 + _previewMarker = new CircleMarker + { + Position = previewPoint.Position, + Radius = radius, + Color = previewColor, + IsVisible = true, + PathPoint = previewPoint + }; + + LogManager.WriteLog($"[预览点渲染] 预览点标记已创建: 位置({previewPoint.Position.X:F2}, {previewPoint.Position.Y:F2}, {previewPoint.Position.Z:F2}), 半径: {radius:F2}, 颜色: 灰色"); + + // 请求刷新视图 + RequestViewRefresh(); + } + } + catch (Exception ex) + { + LogManager.WriteLog($"[预览点渲染] 渲染预览点失败: {ex.Message}"); + LogManager.WriteLog($"[预览点渲染] 堆栈跟踪: {ex.StackTrace}"); + } + } + + /// + /// 清除预览点 + /// + public void ClearPreviewPoint() + { + try + { + lock (_lockObject) + { + if (_previewMarker != null) + { + LogManager.WriteLog($"[预览点渲染] 清除预览点: {_previewMarker.PathPoint?.Name ?? "未知"}"); + _previewMarker = null; + + // 请求刷新视图 + RequestViewRefresh(); + } + else + { + LogManager.WriteLog("[预览点渲染] 没有预览点需要清除"); + } + } + } + catch (Exception ex) + { + LogManager.WriteLog($"[预览点渲染] 清除预览点失败: {ex.Message}"); + } + } + #endregion #region 私有辅助方法 @@ -941,6 +1030,24 @@ namespace NavisworksTransport /// 创建时间 /// public DateTime CreatedTime { get; set; } + /// + /// 是否可见 + /// + public bool IsVisible { get; set; } + + /// + /// 位置(Center的别名,用于预览点兼容性) + /// + public Point3D Position + { + get { return Center; } + set { Center = value; } + } + + /// + /// 关联的路径点对象 + /// + public PathPoint PathPoint { get; set; } public override string ToString() { diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index 0ca0bd6..854628e 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -40,6 +40,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels private string _autoPathStartPoint = "未选择"; private string _autoPathEndPoint = "未选择"; private string _autoPathStatus = "就绪"; + private string _pathEditTipText = "提示:点击[添加路径点]后,在3D视图中点击可通行的物流模型来添加路径点"; private Point3D _startPoint3D; private Point3D _endPoint3D; private bool _hasStartPoint = false; @@ -165,6 +166,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels get => _autoPathStatus; set => SetProperty(ref _autoPathStatus, value); } + /// + /// 路径编辑提示文本 + /// + public string PathEditTipText + { + get => _pathEditTipText; + set => SetProperty(ref _pathEditTipText, value); + } public double VehicleLength { @@ -317,7 +326,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels (_pathPlanningManager?.PathEditState == PathEditState.Viewing); public bool CanExecuteEndEdit => (_pathPlanningManager?.PathEditState == PathEditState.Creating || - _pathPlanningManager?.PathEditState == PathEditState.Editing); + _pathPlanningManager?.PathEditState == PathEditState.Editing || + _pathPlanningManager?.PathEditState == PathEditState.AddingPoints); public bool CanExecuteClearPath => SelectedPathRoute != null && SelectedPathRoute.Points.Count > 0; @@ -370,21 +380,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels private void InitializeCommands() { - // 使用原有的RelayCommand模式初始化命令 NewPathCommand = new RelayCommand(async () => await ExecuteNewPathAsync()); - DeletePathCommand = new RelayCommand(async () => await ExecuteDeletePathAsync(), () => SelectedPathRoute != null); - RenamePathCommand = new RelayCommand(async () => await ExecuteRenamePathAsync(), () => SelectedPathRoute != null); - - StartEditCommand = new RelayCommand(async () => await ExecuteStartEditAsync(), () => CanExecuteStartEdit); + DeletePathCommand = new RelayCommand(async () => await ExecuteDeletePathAsync()); + RenamePathCommand = new RelayCommand(async () => await ExecuteRenamePathAsync()); + StartEditCommand = new RelayCommand(async () => await ExecuteAddPathPointAsync(), () => CanExecuteStartEdit); EndEditCommand = new RelayCommand(async () => await ExecuteEndEditAsync(), () => CanExecuteEndEdit); ClearPathCommand = new RelayCommand(async () => await ExecuteClearPathAsync(), () => CanExecuteClearPath); DeletePointCommand = new RelayCommand(async (point) => await ExecuteDeletePointAsync(point)); - SelectStartPointCommand = new RelayCommand(async () => await ExecuteSelectStartPointAsync()); SelectEndPointCommand = new RelayCommand(async () => await ExecuteSelectEndPointAsync()); AutoPlanPathCommand = new RelayCommand(async () => await ExecuteAutoPlanPathAsync(), () => CanExecuteAutoPlanPath); ClearAutoPathCommand = new RelayCommand(async () => await ExecuteClearAutoPathAsync()); - ImportPathCommand = new RelayCommand(async () => await ExecuteImportPathAsync()); ExportPathCommand = new RelayCommand(async () => await ExecuteExportPathAsync(), () => CanExecuteExportPath); SaveAsPathCommand = new RelayCommand(async () => await ExecuteSaveAsPathAsync(), () => CanExecuteSaveAsPath); @@ -849,7 +855,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels }, "重命名路径"); } - private async Task ExecuteStartEditAsync() + private async Task ExecuteAddPathPointAsync() { if (!CanExecuteStartEdit) return; @@ -861,10 +867,42 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (SelectedPathRoute != null) { SelectedPathRoute.IsActive = true; + AutoPathStatus = $"正在激活3D路径编辑模式: {SelectedPathRoute.Name}..."; + + // 启动PathPlanningManager的点击工具,这会设置正确的编辑状态 + if (_pathPlanningManager != null) + { + try + { + _pathPlanningManager.StartClickTool(PathPointType.WayPoint); + LogManager.Info($"已启动PathPlanningManager点击工具: {SelectedPathRoute.Name}"); + } + catch (Exception ex) + { + LogManager.Error($"启动点击工具失败: {ex.Message}"); + AutoPathStatus = "启动3D编辑工具失败,请重试"; + IsPathEditMode = false; + return; + } + } + else + { + AutoPathStatus = "路径规划管理器未初始化"; + LogManager.Error("添加路径点:PathPlanningManager未初始化"); + IsPathEditMode = false; + return; + } + AutoPathStatus = $"已进入3D路径编辑模式: {SelectedPathRoute.Name}"; + PathEditTipText = "✓ 预览模式已激活 - 在3D视图中点击设置灰色预览点,满意后点击[确认]添加到路径,继续编辑或点击[结束]完成"; + LogManager.Info($"开始添加路径点: {SelectedPathRoute.Name},已启动点击工具"); + + // 手动触发按钮状态更新 + OnPropertyChanged(nameof(CanExecuteStartEdit)); + OnPropertyChanged(nameof(CanExecuteEndEdit)); } }); - }, "开始编辑"); + }, "添加路径点"); } private async Task ExecuteEndEditAsync() @@ -873,33 +911,83 @@ namespace NavisworksTransport.UI.WPF.ViewModels await SafeExecuteAsync(async () => { - // 调用PathPlanningManager的FinishEditing方法 - // 这会自动将最后一个点设置为终点,并触发相关事件 bool success = false; + string operationMessage = ""; + bool wasInPreviewMode = false; + if (_pathPlanningManager != null) { - success = _pathPlanningManager.FinishEditing(); + // 记录是否在预览模式(因为确认预览点后会退出预览模式) + wasInPreviewMode = _pathPlanningManager.IsPreviewMode; + + if (wasInPreviewMode) + { + // 预览模式 - 先确认预览点,然后立即完成编辑 + var confirmedPoint = _pathPlanningManager.ConfirmPreviewPoint(); + if (confirmedPoint != null) + { + LogManager.Info($"[UI-预览模式] 预览点已确认: {confirmedPoint.Name},现在完成编辑"); + + // 立即完成编辑,不让用户继续添加点 + success = _pathPlanningManager.FinishEditing(); + operationMessage = success ? $"预览点已确认并完成编辑: {confirmedPoint.Name}" : "预览点确认后完成编辑失败"; + } + else + { + LogManager.Error("[UI-预览模式] 预览点确认失败"); + operationMessage = "预览点确认失败"; + success = false; + } + + LogManager.Info($"[UI-预览模式] {operationMessage}"); + } + else + { + // 普通模式 - 直接完成编辑 + success = _pathPlanningManager.FinishEditing(); + operationMessage = success ? "路径编辑完成" : "路径编辑完成失败"; + + LogManager.Info($"[UI-编辑模式] {operationMessage}"); + } } await _uiStateManager.ExecuteUIUpdateAsync(() => { - IsPathEditMode = false; - if (SelectedPathRoute != null) + if (success) { - SelectedPathRoute.IsActive = false; - if (success) + // 编辑完成成功 + if (SelectedPathRoute != null) { - AutoPathStatus = $"✅ 路径编辑完成: {SelectedPathRoute.Name} - 最后一个点已自动设置为终点"; + if (wasInPreviewMode) + { + AutoPathStatus = $"✅ 预览点已确认,路径编辑完成: {SelectedPathRoute.Name}"; + } + else + { + AutoPathStatus = $"✅ 路径编辑完成: {SelectedPathRoute.Name} - 最后一个点已自动设置为终点"; + } PathFileStatus = "已完成"; } - else + + LogManager.Info("[UI状态] 路径编辑完成,等待状态变更事件更新按钮"); + } + else + { + // 编辑完成失败 + if (SelectedPathRoute != null) { - AutoPathStatus = $"❌ 路径编辑完成失败: {SelectedPathRoute.Name}"; + AutoPathStatus = $"❌ 路径编辑失败: {SelectedPathRoute.Name}"; PathFileStatus = "编辑失败"; } + + LogManager.Error("[UI状态] 路径编辑失败"); } + + // 手动触发按钮状态更新 - 确保UI及时响应 + OnPropertyChanged(nameof(CanExecuteStartEdit)); + OnPropertyChanged(nameof(CanExecuteEndEdit)); }); - }, "结束编辑"); + }, "结束路径编辑"); } private async Task ExecuteClearPathAsync() @@ -1885,7 +1973,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels { LogManager.Info($"路径编辑状态变更: {e.PreviousState} -> {e.NewState}"); - IsPathEditMode = (e.NewState == PathEditState.Creating || e.NewState == PathEditState.Editing); + // 修复:包含 AddingPoints 状态 + IsPathEditMode = (e.NewState == PathEditState.Creating || + e.NewState == PathEditState.Editing || + e.NewState == PathEditState.AddingPoints); + + LogManager.Info($"[UI状态更新] IsPathEditMode 设为: {IsPathEditMode}"); // 更新状态提示 switch (e.NewState) @@ -1896,6 +1989,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels case PathEditState.Editing: AutoPathStatus = $"正在编辑路径: {e.EditingRoute?.Name} - 请在3D视图中点击设置路径点"; break; + case PathEditState.AddingPoints: + AutoPathStatus = $"正在添加路径点到: {e.EditingRoute?.Name} - 请在3D视图中点击设置路径点"; + break; case PathEditState.Viewing: AutoPathStatus = "路径编辑已完成"; break; @@ -1907,6 +2003,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 通知命令状态更新 OnPropertyChanged(nameof(CanExecuteStartEdit)); OnPropertyChanged(nameof(CanExecuteEndEdit)); + + LogManager.Info($"[UI状态更新] 按钮状态已通知更新 - CanExecuteStartEdit: {CanExecuteStartEdit}, CanExecuteEndEdit: {CanExecuteEndEdit}"); }, "处理编辑状态变更事件"); } catch (Exception ex) diff --git a/src/UI/WPF/Views/PathEditingView.xaml b/src/UI/WPF/Views/PathEditingView.xaml index 384d1ee..9fa92ef 100644 --- a/src/UI/WPF/Views/PathEditingView.xaml +++ b/src/UI/WPF/Views/PathEditingView.xaml @@ -241,7 +241,7 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管 -