diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index cdf52a7..ae53a65 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -2212,6 +2212,32 @@ namespace NavisworksTransport.UI.WPF.ViewModels UpdateMainStatus("已清除手工对象高亮"); } + /// + /// 聚焦到手工指定的对象 + /// + public void FocusOnManualTarget(ManualCollisionTargetViewModel target) + { + if (target?.ModelItem == null) return; + + try + { + // 先高亮该对象 + var items = new List { target.ModelItem }; + ModelHighlightHelper.HighlightItems(ManualTargetsHighlightCategory, items); + + // 聚焦到对象(斜上方45度视角) + ViewpointHelper.FocusOnModelItem(target.ModelItem, viewAngleDegrees: 45.0, targetViewRatio: 0.33); + + UpdateMainStatus($"已聚焦到: {target.DisplayName}"); + LogManager.Info($"聚焦到手工指定对象: {target.DisplayName}"); + } + catch (Exception ex) + { + LogManager.Error($"聚焦到手工指定对象失败: {ex.Message}", ex); + UpdateMainStatus($"聚焦失败: {ex.Message}"); + } + } + #endregion #region 检测排除对象命令 diff --git a/src/UI/WPF/Views/AnimationControlView.xaml b/src/UI/WPF/Views/AnimationControlView.xaml index 5a5ae75..79c3a19 100644 --- a/src/UI/WPF/Views/AnimationControlView.xaml +++ b/src/UI/WPF/Views/AnimationControlView.xaml @@ -186,7 +186,8 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管 MinHeight="60" MaxHeight="200" BorderBrush="#FFE2E8F0" - BorderThickness="1"> + BorderThickness="1" + MouseDoubleClick="OnManualTargetDoubleClick"> diff --git a/src/UI/WPF/Views/AnimationControlView.xaml.cs b/src/UI/WPF/Views/AnimationControlView.xaml.cs index a5f84ba..b422c1c 100644 --- a/src/UI/WPF/Views/AnimationControlView.xaml.cs +++ b/src/UI/WPF/Views/AnimationControlView.xaml.cs @@ -154,5 +154,28 @@ namespace NavisworksTransport.UI.WPF.Views LogManager.Error($"[AnimationControlView] 处理排除对象选择变化失败: {ex.Message}", ex); } } + + /// + /// 手工指定对象列表双击事件处理 - 聚焦到对象 + /// + private void OnManualTargetDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + try + { + if (ViewModel == null) return; + + // 获取双击的对象 + var listView = sender as ListView; + if (listView?.SelectedItem is ManualCollisionTargetViewModel selectedTarget) + { + // 聚焦到对象 + ViewModel.FocusOnManualTarget(selectedTarget); + } + } + catch (Exception ex) + { + LogManager.Error($"[AnimationControlView] 处理手工指定对象双击失败: {ex.Message}", ex); + } + } } } \ No newline at end of file diff --git a/src/Utils/ViewpointHelper.cs b/src/Utils/ViewpointHelper.cs index 0805d38..97dd9a0 100644 --- a/src/Utils/ViewpointHelper.cs +++ b/src/Utils/ViewpointHelper.cs @@ -276,5 +276,235 @@ namespace NavisworksTransport.Utils { System.Threading.Thread.Sleep(VIEW_UPDATE_DELAY_MS); } + + #region 模型元素聚焦方法 + + /// + /// 聚焦到指定模型元素,使用斜上方45度视角 + /// 相机会 positioned 在元素斜上方,使元素占据视图约1/3宽度 + /// + /// 要聚焦的模型元素 + /// 视角角度(默认45度斜上方) + /// 目标占据视图比例(默认1/3) + public static void FocusOnModelItem(ModelItem modelItem, double viewAngleDegrees = 45.0, double targetViewRatio = 0.25) + { + if (modelItem == null) + { + throw new ArgumentNullException(nameof(modelItem)); + } + + Document doc = Application.ActiveDocument; + if (doc == null || doc.IsClear) + { + throw new InvalidOperationException("没有活动的文档"); + } + + try + { + // 1. 获取模型元素的包围盒 + BoundingBox3D boundingBox = modelItem.BoundingBox(); + if (boundingBox == null || boundingBox.Size == new Vector3D(0, 0, 0)) + { + LogManager.Warning($"模型元素 {modelItem.DisplayName} 没有有效的包围盒,使用默认视角"); + return; + } + + // 2. 计算包围盒中心和尺寸 + Point3D center = new Point3D( + (boundingBox.Min.X + boundingBox.Max.X) / 2, + (boundingBox.Min.Y + boundingBox.Max.Y) / 2, + (boundingBox.Min.Z + boundingBox.Max.Z) / 2 + ); + + double maxDimension = Math.Max( + boundingBox.Max.X - boundingBox.Min.X, + Math.Max(boundingBox.Max.Y - boundingBox.Min.Y, boundingBox.Max.Z - boundingBox.Min.Z) + ); + + LogManager.Info($"聚焦到模型元素: {modelItem.DisplayName}, 最大边: {maxDimension:F2}"); + + // 3. 计算相机位置(使用模型标准视角方向) + Point3D cameraPosition = CalculateCameraPosition(center, maxDimension, viewAngleDegrees, targetViewRatio); + + // 4. 创建视角并设置相机 + Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy(); + newViewpoint.Position = cameraPosition; + + // 获取模型的标准视角向量 + Vector3D viewDirection = doc.FrontRightTopViewVector; + viewDirection.Normalize(); + + // 使用 AlignDirection 设置相机朝向(看向目标方向) + // 方向是从相机指向目标 + Vector3D lookDirection = new Vector3D( + center.X - cameraPosition.X, + center.Y - cameraPosition.Y, + center.Z - cameraPosition.Z + ); + lookDirection.Normalize(); + newViewpoint.AlignDirection(lookDirection); + + // 使用 AlignUp 设置相机的上方向(基于模型的标准上方向) + Vector3D upVector = doc.FrontRightTopViewUpVector; + upVector.Normalize(); + newViewpoint.AlignUp(upVector); + + // 5. 应用视角(不使用 ZoomBox,让相机距离决定视野比例) + doc.CurrentViewpoint.CopyFrom(newViewpoint); + + LogManager.Info($"视角已调整: 标准前右上视角, 目标占据视图{targetViewRatio:P0}"); + } + catch (Exception ex) + { + LogManager.Error($"聚焦到模型元素失败: {ex.Message}", ex); + throw; + } + } + + /// + /// 聚焦到多个模型元素(如碰撞中的两个对象) + /// + /// 第一个模型元素(运动物体) + /// 第二个模型元素(被撞对象) + /// 视角角度(默认45度斜上方) + public static void FocusOnCollision(ModelItem item1, ModelItem item2, double viewAngleDegrees = 45.0) + { + if (item1 == null && item2 == null) + { + throw new ArgumentException("至少需要一个模型元素"); + } + + try + { + // 计算两个元素的联合包围盒 + BoundingBox3D combinedBounds = null; + + if (item1 != null) + { + combinedBounds = item1.BoundingBox(); + } + + if (item2 != null) + { + if (combinedBounds == null) + { + combinedBounds = item2.BoundingBox(); + } + else + { + BoundingBox3D item2Box = item2.BoundingBox(); + combinedBounds.Extend(item2Box.Min); + combinedBounds.Extend(item2Box.Max); + } + } + + if (combinedBounds == null || combinedBounds.Size == new Vector3D(0, 0, 0)) + { + LogManager.Warning("碰撞对象没有有效的包围盒"); + return; + } + + // 使用联合包围盒的中心点和尺寸 + Point3D center = new Point3D( + (combinedBounds.Min.X + combinedBounds.Max.X) / 2, + (combinedBounds.Min.Y + combinedBounds.Max.Y) / 2, + (combinedBounds.Min.Z + combinedBounds.Max.Z) / 2 + ); + + double maxDimension = Math.Max( + combinedBounds.Max.X - combinedBounds.Min.X, + Math.Max(combinedBounds.Max.Y - combinedBounds.Min.Y, combinedBounds.Max.Z - combinedBounds.Min.Z) + ); + + FocusOnPosition(center, maxDimension, viewAngleDegrees); + + LogManager.Info($"已聚焦到碰撞位置: {item1?.DisplayName} ↔ {item2?.DisplayName}"); + } + catch (Exception ex) + { + LogManager.Error($"聚焦到碰撞位置失败: {ex.Message}", ex); + throw; + } + } + + /// + /// 聚焦到指定位置和尺寸 + /// + /// 目标中心点 + /// 目标尺寸(最大边) + /// 视角角度 + public static void FocusOnPosition(Point3D center, double targetSize, double viewAngleDegrees = 45.0) + { + Document doc = Application.ActiveDocument; + if (doc == null || doc.IsClear) + { + throw new InvalidOperationException("没有活动的文档"); + } + + try + { + // 计算相机位置(使用模型标准视角方向) + Point3D cameraPosition = CalculateCameraPosition(center, targetSize, viewAngleDegrees, 0.25); + + // 创建视角并设置相机 + Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy(); + newViewpoint.Position = cameraPosition; + + // 获取模型的标准视角向量 + Vector3D viewDirection = doc.FrontRightTopViewVector; + viewDirection.Normalize(); + + // 使用 AlignDirection 设置相机朝向 + Vector3D lookDirection = new Vector3D( + center.X - cameraPosition.X, + center.Y - cameraPosition.Y, + center.Z - cameraPosition.Z + ); + lookDirection.Normalize(); + newViewpoint.AlignDirection(lookDirection); + + // 使用 AlignUp 设置相机的上方向 + Vector3D upVector = doc.FrontRightTopViewUpVector; + upVector.Normalize(); + newViewpoint.AlignUp(upVector); + + // 应用视角(不使用 ZoomBox,让相机距离决定视野比例) + doc.CurrentViewpoint.CopyFrom(newViewpoint); + } + catch (Exception ex) + { + LogManager.Error($"聚焦到位置失败: {ex.Message}", ex); + throw; + } + } + + /// + /// 计算相机位置(使用模型标准前右上视角) + /// 基于 Navisworks 的标准 FrontRightTopViewVector 确定相机方向 + /// + private static Point3D CalculateCameraPosition(Point3D center, double targetSize, double viewAngleDegrees, double targetViewRatio) + { + // 计算相机距离(目标占据视图 targetViewRatio) + double fovRadians = 0.785398; // Navisworks 默认 FOV 45度 + double cameraDistance = (targetSize / 2.0) / Math.Tan(fovRadians / 2.0) / targetViewRatio; + cameraDistance *= 1.2; // 添加边距 + + // 获取模型的标准前右上视角向量 + // 这个向量定义了模型的"正方向",基于模型坐标系 + Vector3D viewDirection = Application.ActiveDocument.FrontRightTopViewVector; + + // 归一化方向向量 + viewDirection.Normalize(); + + // 沿标准视角方向偏移相机位置 + // 相机位置 = 目标中心 - 方向向量 * 距离 + return new Point3D( + center.X - viewDirection.X * cameraDistance, + center.Y - viewDirection.Y * cameraDistance, + center.Z - viewDirection.Z * cameraDistance + ); + } + + #endregion } } \ No newline at end of file