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);
+ }
+
///
/// 物体状态快照,用于保存和恢复
///