diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index ecb23c1..6fe07c5 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -11,8 +11,8 @@ ### [2026/2/8] 1. [x] (功能)增加预计算结果分析和排除建议 -2. [ ] (优化)考虑在碰撞报告中,给每一个碰撞元素自动建立截图 -3. [ ] (优化)考虑是否给截图按碰撞记录组织成子文件夹 +2. [x] (优化)在碰撞报告中,给每一个碰撞元素自动建立截图 +3. [x] (优化)截图按碰撞记录组织成子文件夹 4. [ ] (优化)研究如何利用ClashDetective的多线程(支持打开多线程) 5. [x] (优化)给每个检测到的碰撞对象增加单独视角(45度,右上方) 6. [x] (优化)点击碰撞列表的碰撞详情时,要把运动物体移动到位,查看碰撞情况 diff --git a/src/Core/PathDatabase.cs b/src/Core/PathDatabase.cs index 65df59f..5f2702e 100644 --- a/src/Core/PathDatabase.cs +++ b/src/Core/PathDatabase.cs @@ -851,7 +851,9 @@ namespace NavisworksTransport { var sql = @" SELECT cdr.Id, cdr.TestName, cdr.RouteId, pr.Name AS PathName, cdr.TestTime, cdr.CollisionCount, cdr.AnimationCollisionCount, - cdr.FrameRate, cdr.Duration, cdr.DetectionGap, cdr.AnimatedObjectName, cdr.CreatedAt + cdr.FrameRate, cdr.Duration, cdr.DetectionGap, cdr.AnimatedObjectName, cdr.IsVirtualObject, + cdr.ObjectModelIndex, cdr.ObjectPathId, cdr.VirtualObjectLength, cdr.VirtualObjectWidth, cdr.VirtualObjectHeight, + cdr.CreatedAt FROM ClashDetectiveResults cdr INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id WHERE pr.Name = @pathName @@ -878,6 +880,12 @@ namespace NavisworksTransport Duration = Convert.ToDouble(reader["Duration"]), DetectionGap = Convert.ToDouble(reader["DetectionGap"]), AnimatedObjectName = reader["AnimatedObjectName"].ToString(), + IsVirtualObject = Convert.ToBoolean(reader["IsVirtualObject"]), + ObjectModelIndex = !Convert.IsDBNull(reader["ObjectModelIndex"]) ? (int?)Convert.ToInt32(reader["ObjectModelIndex"]) : null, + ObjectPathId = reader["ObjectPathId"].ToString(), + VirtualObjectLength = Convert.ToDouble(reader["VirtualObjectLength"]), + VirtualObjectWidth = Convert.ToDouble(reader["VirtualObjectWidth"]), + VirtualObjectHeight = Convert.ToDouble(reader["VirtualObjectHeight"]), CreatedAt = Convert.ToDateTime(reader["CreatedAt"]) }); } diff --git a/src/Core/VirtualObjectManager.cs b/src/Core/VirtualObjectManager.cs index c10a3fe..34e7350 100644 --- a/src/Core/VirtualObjectManager.cs +++ b/src/Core/VirtualObjectManager.cs @@ -254,6 +254,22 @@ namespace NavisworksTransport.Core LogManager.Info("已重置虚拟物体变换"); } + /// + /// 重新应用当前记录的缩放(用于ResetPermanentTransform后恢复缩放) + /// + public void ReapplyCurrentScale() + { + if (_currentLengthMeters > 0 && _currentWidthMeters > 0 && _currentHeightMeters > 0) + { + LogManager.Info($"重新应用虚拟物体缩放: {_currentLengthMeters:F2}m × {_currentWidthMeters:F2}m × {_currentHeightMeters:F2}m"); + ScaleVirtualObject(_currentLengthMeters, _currentWidthMeters, _currentHeightMeters); + } + else + { + LogManager.Warning("无法重新应用缩放:当前尺寸记录无效"); + } + } + /// /// 缩放虚拟物体到目标尺寸 /// 使用DocumentModels.SetModelUnitsAndTransform方法进行模型级缩放 diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index 88537e7..bbfbdbc 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -177,6 +177,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels public ModelItem ModelItem { get; set; } public ICommand HighlightCommand { get; set; } public ICommand ClearHighlightCommand { get; set; } + + // 碰撞位置信息(用于还原碰撞场景) + public Point3D Item1Position { get; set; } + public double Item1YawRadians { get; set; } + public bool HasPositionInfo { get; set; } } /// @@ -197,7 +202,16 @@ namespace NavisworksTransport.UI.WPF.ViewModels } public ClashDetectiveResultRecord Record { get; } - public string TestTimeDisplay => Record.TestTime.ToString("MM-dd HH:mm:ss"); + + // 运动物体(从碰撞历史数据库中加载) + public ModelItem AnimatedObject { get; set; } + + // 虚拟物体尺寸(当IsVirtualObject=true时使用) + public double VirtualObjectLength { get; set; } + public double VirtualObjectWidth { get; set; } + public double VirtualObjectHeight { get; set; } + + public string TestTimeDisplay => Record.TestTime.ToString("MM-dd HH:mm:ss"); public string CollisionCountDisplay => $"{Record.CollisionCount}个"; public ICommand ViewCommand { get; } public ICommand ReportCommand { get; } @@ -286,6 +300,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels private readonly ClashDetectiveIntegration _clashIntegration; private readonly UIStateManager _uiStateManager; private PathPlanningManager _pathPlanningManager; + + // 碰撞构件查看时保存的动画物体状态(用于恢复) + private ModelItem _savedAnimatedObjectForCollision; + private ModelItemTransformHelper.ObjectStateSnapshot _savedAnimatedObjectStateForCollision; // 动画参数相关字段(从配置初始化) private ObservableCollection _availableFrameRates; @@ -831,6 +849,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels { if (_selectedClashDetectiveResult != value) { + // 切换前恢复动画物体状态 + if (_selectedClashDetectiveResult != null) + { + RestoreAnimatedObjectFromCollisionView(); + } + _selectedClashDetectiveResult = value; OnPropertyChanged(); } @@ -2372,20 +2396,50 @@ namespace NavisworksTransport.UI.WPF.ViewModels } /// - /// 聚焦到碰撞构件 + /// 聚焦到碰撞构件,并移动动画物体到碰撞位置 /// public void FocusOnCollisionObject(CollisionObjectViewModel collisionObject) { if (collisionObject?.ModelItem == null) return; + + // 从历史记录中获取运动物体 + var animatedObject = SelectedClashDetectiveResult?.AnimatedObject; + if (animatedObject == null) + { + LogManager.Warning($"[碰撞构件] 未找到运动物体,仅聚焦到被撞对象: {collisionObject.DisplayName}"); + // 仅聚焦,不移动物体 + CollisionSceneHelper.MoveToCollisionAndFocus( + collisionObject.ModelItem, + null, + collisionObject.Item1Position, + collisionObject.Item1YawRadians, + false); + return; + } try { - // 先高亮该对象 - var items = new List { collisionObject.ModelItem }; - ModelHighlightHelper.HighlightItems(ModelHighlightHelper.ClashDetectiveResultsCategory, items); + // 首次查看时保存动画物体状态 + if (_savedAnimatedObjectStateForCollision == null || _savedAnimatedObjectForCollision != animatedObject) + { + _savedAnimatedObjectForCollision = animatedObject; + _savedAnimatedObjectStateForCollision = CollisionSceneHelper.SaveAnimatedObjectState(animatedObject); + } - // 聚焦到对象(斜上方45度视角) - ViewpointHelper.FocusOnModelItem(collisionObject.ModelItem, viewAngleDegrees: 60.0, targetViewRatio: 0.25); + // 移动动画物体到碰撞位置并聚焦 + CollisionSceneHelper.MoveToCollisionAndFocus( + collisionObject.ModelItem, + animatedObject, + collisionObject.Item1Position, + collisionObject.Item1YawRadians, + collisionObject.HasPositionInfo); + + // 🔥 关键:如果是虚拟物体,重新应用缩放(因为MoveItemToPositionAndYaw会重置变换) + if (VirtualObjectManager.Instance.IsVirtualObjectActive && + VirtualObjectManager.Instance.CurrentVirtualObject == animatedObject) + { + VirtualObjectManager.Instance.ReapplyCurrentScale(); + } UpdateMainStatus($"已聚焦到碰撞构件: {collisionObject.DisplayName}"); LogManager.Info($"聚焦到碰撞构件: {collisionObject.DisplayName}"); @@ -2396,6 +2450,28 @@ namespace NavisworksTransport.UI.WPF.ViewModels UpdateMainStatus($"聚焦失败: {ex.Message}"); } } + + /// + /// 恢复动画物体到原始状态(失去焦点或切换历史记录时调用) + /// + public void RestoreAnimatedObjectFromCollisionView() + { + if (_savedAnimatedObjectForCollision != null && _savedAnimatedObjectStateForCollision != null) + { + CollisionSceneHelper.RestoreAnimatedObjectState(_savedAnimatedObjectForCollision, _savedAnimatedObjectStateForCollision); + LogManager.Info($"[碰撞构件] 运动物体已恢复到原始状态: {_savedAnimatedObjectForCollision.DisplayName}"); + } + + // 隐藏虚拟物体(如果之前显示的是虚拟物体) + if (VirtualObjectManager.Instance.IsVirtualObjectActive) + { + VirtualObjectManager.Instance.HideVirtualObject(); + LogManager.Info("[碰撞构件] 虚拟物体已隐藏"); + } + + _savedAnimatedObjectStateForCollision = null; + _savedAnimatedObjectForCollision = null; + } #endregion @@ -2987,6 +3063,48 @@ namespace NavisworksTransport.UI.WPF.ViewModels { var resultViewModel = new ClashDetectiveResultViewModel(record, RefreshClashDetectiveResultsList); + // 加载运动物体(Item1) + if (record.IsVirtualObject) + { + // 使用虚拟物体,设置历史记录中的尺寸(数据库中是模型单位) + resultViewModel.VirtualObjectLength = record.VirtualObjectLength; + resultViewModel.VirtualObjectWidth = record.VirtualObjectWidth; + resultViewModel.VirtualObjectHeight = record.VirtualObjectHeight; + + // 显示对应尺寸的虚拟物体(ShowVirtualObject需要米单位) + try + { + double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor(Autodesk.Navisworks.Api.Application.ActiveDocument.Units); + VirtualObjectManager.Instance.ShowVirtualObject( + record.VirtualObjectLength * unitsToMeters, + record.VirtualObjectWidth * unitsToMeters, + record.VirtualObjectHeight * unitsToMeters); + resultViewModel.AnimatedObject = VirtualObjectManager.Instance.CurrentVirtualObject; + LogManager.Info($"[碰撞历史] 使用虚拟物体(尺寸:{record.VirtualObjectLength:F2}x{record.VirtualObjectWidth:F2}x{record.VirtualObjectHeight:F2}): {resultViewModel.AnimatedObject?.DisplayName ?? "未找到"} for TestName={record.TestName}"); + } + catch (Exception ex) + { + LogManager.Warning($"[碰撞历史] 显示虚拟物体失败: {ex.Message}"); + } + } + else if (record.ObjectModelIndex.HasValue && !string.IsNullOrEmpty(record.ObjectPathId)) + { + try + { + var animatedObjectPathId = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId + { + ModelIndex = record.ObjectModelIndex.Value, + PathId = record.ObjectPathId + }; + resultViewModel.AnimatedObject = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.ResolvePathId(animatedObjectPathId); + LogManager.Info($"[碰撞历史] 已加载运动物体: {resultViewModel.AnimatedObject?.DisplayName ?? "未找到"} for TestName={record.TestName}"); + } + catch (Exception ex) + { + LogManager.Warning($"[碰撞历史] 无法加载运动物体: ModelIndex={record.ObjectModelIndex}, PathId={record.ObjectPathId}, 错误: {ex.Message}"); + } + } + // 同时加载该结果的碰撞构件清单 var collisionObjectRecords = pathDatabase.GetClashDetectiveCollisionObjects(record.Id); if (collisionObjectRecords != null) @@ -3033,7 +3151,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels ModelIndex = objRecord.ModelIndex, PathId = objRecord.PathId, ModelItem = modelItem, - HighlightCommand = new RelayCommand(() => resultViewModel.ExecuteHighlightCollisionObject(objViewModel)) + HighlightCommand = new RelayCommand(() => resultViewModel.ExecuteHighlightCollisionObject(objViewModel)), + // 填充碰撞位置信息 + Item1Position = objRecord.HasPositionInfo && objRecord.Item1PosX.HasValue ? + new Point3D(objRecord.Item1PosX.Value, objRecord.Item1PosY.Value, objRecord.Item1PosZ.Value) : null, + Item1YawRadians = objRecord.Item1YawRadians ?? 0, + HasPositionInfo = objRecord.HasPositionInfo }; resultViewModel.CollisionObjects.Add(objViewModel); } diff --git a/src/Utils/CollisionSceneHelper.cs b/src/Utils/CollisionSceneHelper.cs index bb9bd31..cd510a0 100644 --- a/src/Utils/CollisionSceneHelper.cs +++ b/src/Utils/CollisionSceneHelper.cs @@ -19,12 +19,23 @@ namespace NavisworksTransport.Utils public static void MoveToCollisionAndFocus(CollisionResult collision, ModelItem animatedObject) { if (collision == null) return; - + MoveToCollisionAndFocus(collision.Item2 ?? collision.Item1, animatedObject, + collision.Item1Position, collision.Item1YawRadians, collision.HasPositionInfo); + } + + /// + /// 将动画物体移动到碰撞位置并聚焦到碰撞场景(通用版本) + /// + /// 被撞对象 + /// 动画物体(运动物体) + /// 碰撞位置 + /// 碰撞朝向 + /// 是否有位置信息 + public static void MoveToCollisionAndFocus(ModelItem hitObject, ModelItem animatedObject, + Point3D item1Position, double item1YawRadians, bool hasPositionInfo) + { try { - // 获取被撞对象(Item2是运动物体碰撞的静态对象) - var hitObject = collision.Item2 ?? collision.Item1; - if (hitObject != null) { // 清除之前的高亮 @@ -40,25 +51,24 @@ namespace NavisworksTransport.Utils } // 移动动画物体到碰撞位置 - if (animatedObject != null && collision.Item1 == animatedObject && collision.HasPositionInfo && collision.Item1Position != null) + if (animatedObject != null && hasPositionInfo && item1Position != null) { // 计算目标底面位置(Item1Position存储的是包围盒中心,需要转换为底面) var bounds = animatedObject.BoundingBox(); double halfHeight = (bounds.Max.Z - bounds.Min.Z) / 2.0; var targetGroundPosition = new Point3D( - collision.Item1Position.X, - collision.Item1Position.Y, - collision.Item1Position.Z - halfHeight + item1Position.X, + item1Position.Y, + item1Position.Z - halfHeight ); // 使用工具方法从CAD原始状态移动到目标位置 - ModelItemTransformHelper.MoveItemToPositionAndYaw(animatedObject, targetGroundPosition, collision.Item1YawRadians); + ModelItemTransformHelper.MoveItemToPositionAndYaw(animatedObject, targetGroundPosition, item1YawRadians); LogManager.Info(string.Format("运动物体已移动到碰撞位置: ({0:F2}, {1:F2}, {2:F2}), 朝向: {3:F2}°", targetGroundPosition.X, targetGroundPosition.Y, targetGroundPosition.Z, - collision.Item1YawRadians * 180 / Math.PI)); + item1YawRadians * 180 / Math.PI)); } - } catch (Exception ex) { diff --git a/src/Utils/ModelItemTransformHelper.cs b/src/Utils/ModelItemTransformHelper.cs index 8ec156a..119f112 100644 --- a/src/Utils/ModelItemTransformHelper.cs +++ b/src/Utils/ModelItemTransformHelper.cs @@ -165,6 +165,79 @@ namespace NavisworksTransport.Utils doc.Models.OverridePermanentTransform(modelItems, transform, false); } + /// + /// 将物体移动到指定位置和朝向,同时保持缩放比例 + /// 专为虚拟物体设计,避免缩放被覆盖 + /// + /// 要移动的物体 + /// 目标位置(地面位置) + /// 目标朝向(弧度) + /// X方向缩放 + /// Y方向缩放 + /// Z方向缩放 + public static void MoveItemToPositionAndYawWithScale(ModelItem item, Point3D targetPosition, double targetYaw, + double scaleX, double scaleY, double scaleZ) + { + var doc = Application.ActiveDocument; + var modelItems = new ModelItemCollection { item }; + + // 重置到CAD原始状态 + doc.Models.ResetPermanentTransform(modelItems); + + // 获取CAD原始状态 + var originalBounds = item.BoundingBox(); + var originalGroundPos = new Point3D( + originalBounds.Center.X, + originalBounds.Center.Y, + originalBounds.Min.Z + ); + var originalYaw = GetYawFromTransform(item.Transform); + + // 计算从CAD原始位置到目标位置的增量 + var deltaPos = new Vector3D( + targetPosition.X - originalGroundPos.X, + targetPosition.Y - originalGroundPos.Y, + targetPosition.Z - originalGroundPos.Z + ); + double deltaYaw = targetYaw - originalYaw; + + // 构建变换组件 + var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)); + var components = identity.Factor(); + + // 应用缩放 + components.Scale = new Vector3D(scaleX, scaleY, scaleZ); + + // 应用旋转 + if (Math.Abs(deltaYaw) > 0.001) + { + components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw); + } + + // 计算平移(考虑旋转带来的位置偏移) + if (Math.Abs(deltaYaw) > 0.001) + { + double cos = Math.Cos(deltaYaw); + double sin = Math.Sin(deltaYaw); + double rotatedX = originalGroundPos.X * cos - originalGroundPos.Y * sin; + double rotatedY = originalGroundPos.X * sin + originalGroundPos.Y * cos; + + components.Translation = new Vector3D( + targetPosition.X - rotatedX, + targetPosition.Y - rotatedY, + deltaPos.Z + ); + } + else + { + components.Translation = deltaPos; + } + + // 应用组合变换 + Transform3D transform = components.Combine(); + doc.Models.OverridePermanentTransform(modelItems, transform, false); + } + /// /// 物体状态快照,用于保存和恢复 ///