From ee33e430482b1e6f17291bb9dc0fe7a4afb7b2db Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Tue, 10 Feb 2026 18:39:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=A2=B0=E6=92=9E=E6=97=B6?= =?UTF-8?q?=E8=BF=90=E5=8A=A8=E7=89=A9=E4=BD=93=E4=BD=8D=E7=BD=AE=E5=92=8C?= =?UTF-8?q?=E6=9C=9D=E5=90=91=E8=AE=B0=E5=BD=95=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E5=9C=A8=E7=A2=B0=E6=92=9E=E6=8A=A5=E5=91=8A=E4=B8=AD=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E6=9F=A5=E7=9C=8B=E4=B8=A4=E8=80=85=E7=A2=B0=E6=92=9E?= =?UTF-8?q?=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/requirement/todo_features.md | 2 +- src/Core/Animation/PathAnimationManager.cs | 5 + .../Collision/ClashDetectiveIntegration.cs | 127 ++++++++++++++- src/Core/PathDatabase.cs | 38 ++++- .../ViewModels/CollisionReportViewModel.cs | 89 ++++++++++- .../WPF/Views/CollisionReportDialog.xaml.cs | 6 + src/Utils/ModelItemTransformHelper.cs | 147 ++++++++++++++++++ 7 files changed, 398 insertions(+), 16 deletions(-) diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index 54a25bd..c42d672 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -4,7 +4,7 @@ ### [2026/2/8] -1. [.] (功能)增加预计算结果分析和排除建议 +1. [x] (功能)增加预计算结果分析和排除建议 2. [ ] (优化)考虑在碰撞报告中,给每一个碰撞元素自动建立截图 3. [ ] (优化)考虑是否给截图按碰撞记录组织成子文件夹 4. [ ] (优化)研究如何利用ClashDetective的多线程(支持打开多线程) diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index 842235f..fea8d66 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -1100,6 +1100,7 @@ namespace NavisworksTransport.Core.Animation framePosition.Z + (virtualBoundingBox.Max.Z - virtualBoundingBox.Min.Z) / 2 ), Item2Position = GetObjectPosition(collider), + Item1YawRadians = yawRadians, // 记录运动物体朝向 HasPositionInfo = true }; @@ -1114,11 +1115,15 @@ namespace NavisworksTransport.Core.Animation // 统计碰撞信息 var framesWithCollision = _animationFrames.Count(f => f.HasCollision); var totalCollisions = _animationFrames.Sum(f => f.Collisions.Count); + // 检查碰撞结果是否包含位置信息 + var collisionsWithPosition = _allCollisionResults.Count(c => c.HasPositionInfo && c.Item1Position != null); + LogManager.Info($"=== 预计算完成 ==="); LogManager.Info($"总帧数: {_animationFrames.Count}"); LogManager.Info($"包含碰撞的帧: {framesWithCollision}"); LogManager.Info($"总碰撞次数: {totalCollisions}"); LogManager.Info($"记录的碰撞结果总数: {_allCollisionResults.Count} 个"); + LogManager.Info($"包含位置信息的碰撞: {collisionsWithPosition}/{_allCollisionResults.Count}"); // 🔥 Human-in-the-Loop: 记录排除统计 if (_excludedObjects.Count > 0) diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs index 5768d21..b25d017 100644 --- a/src/Core/Collision/ClashDetectiveIntegration.cs +++ b/src/Core/Collision/ClashDetectiveIntegration.cs @@ -140,6 +140,55 @@ namespace NavisworksTransport private Dictionary> _clashDetectiveResultsCache = new Dictionary>(); private readonly object _clashResultsCacheLock = new object(); + // 🔥 新增:记录ClashDetective确认碰撞时的位置信息 + // key: "Item1Id|Item2Id", value: 确认碰撞时的位置信息 + private Dictionary _confirmedCollisionPositions = new Dictionary(); + + /// + /// 碰撞位置信息(用于记录ClashDetective确认时的位置) + /// + private class CollisionPositionInfo + { + public Point3D Item1Position { get; set; } + public Point3D Item2Position { get; set; } + public double Item1YawRadians { get; set; } + public bool HasPositionInfo { get; set; } + } + + /// + /// 生成碰撞对象的唯一key(使用PathId,不使用InstanceGuid因为多数元素为0) + /// + private string GetCollisionObjectKey(ModelItem item1, ModelItem item2) + { + try + { + var doc = Application.ActiveDocument; + string key1 = "null"; + string key2 = "null"; + + if (item1 != null && doc != null) + { + var pathId = doc.Models.CreatePathId(item1); + key1 = $"{pathId.ModelIndex}:{pathId.PathId}"; + } + + if (item2 != null && doc != null) + { + var pathId = doc.Models.CreatePathId(item2); + key2 = $"{pathId.ModelIndex}:{pathId.PathId}"; + } + + return $"{key1}|{key2}"; + } + catch + { + // 回退到DisplayName(可能不唯一,但总比空key好) + var key1 = item1?.DisplayName ?? "null"; + var key2 = item2?.DisplayName ?? "null"; + return $"{key1}|{key2}"; + } + } + /// /// 获取去重后的预计算碰撞结果(第一次去重,按碰撞对象对去重) /// @@ -210,6 +259,7 @@ namespace NavisworksTransport LogManager.Info($"ClashDetective结果已保存到数据库,Id={resultId}, IsVirtualVehicle={isVirtualVehicle}"); // 保存被撞物体信息(只保存Item2,不保存车辆Item1) + // 同时保存碰撞时运动物体的位置和朝向,用于还原碰撞场景 var collisionObjects = new List(); foreach (var collision in clashResults) @@ -220,14 +270,28 @@ namespace NavisworksTransport // 使用 CreatePathId API 获取 ModelIndex 和 PathId var pathId = Application.ActiveDocument.Models.CreatePathId(collision.Item2); - collisionObjects.Add(new ClashDetectiveCollisionObjectRecord + var collisionRecord = new ClashDetectiveCollisionObjectRecord { ResultId = resultId, ModelIndex = pathId.ModelIndex, PathId = pathId.PathId, DisplayName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2), ObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2) - }); + }; + + // 保存碰撞时运动物体的位置和朝向(如果可用) + if (collision.HasPositionInfo && collision.Item1Position != null) + { + collisionRecord.Item1PosX = collision.Item1Position.X; + collisionRecord.Item1PosY = collision.Item1Position.Y; + collisionRecord.Item1PosZ = collision.Item1Position.Z; + collisionRecord.Item1YawRadians = collision.Item1YawRadians; + collisionRecord.HasPositionInfo = true; + + LogManager.Debug($"[保存碰撞对象] 记录运动物体位置: ({collisionRecord.Item1PosX:F2}, {collisionRecord.Item1PosY:F2}, {collisionRecord.Item1PosZ:F2}), 朝向: {collisionRecord.Item1YawRadians:F2} rad"); + } + + collisionObjects.Add(collisionRecord); } } @@ -392,9 +456,10 @@ namespace NavisworksTransport } } - // 3. 从数据库读取被撞物体信息 + // 3. 从数据库读取被撞物体信息(包含碰撞时运动物体的位置和朝向) var collisionObjectsSql = @" - SELECT ModelIndex, PathId, DisplayName, ObjectName + SELECT ModelIndex, PathId, DisplayName, ObjectName, + Item1PosX, Item1PosY, Item1PosZ, Item1YawRadians, HasPositionInfo FROM ClashDetectiveCollisionObjects WHERE ResultId = @resultId "; @@ -412,7 +477,12 @@ namespace NavisworksTransport ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null, PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null, DisplayName = reader["DisplayName"]?.ToString(), - ObjectName = reader["ObjectName"]?.ToString() + ObjectName = reader["ObjectName"]?.ToString(), + Item1PosX = reader["Item1PosX"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosX"]) : (double?)null, + Item1PosY = reader["Item1PosY"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosY"]) : (double?)null, + Item1PosZ = reader["Item1PosZ"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosZ"]) : (double?)null, + Item1YawRadians = reader["Item1YawRadians"] != DBNull.Value ? Convert.ToDouble(reader["Item1YawRadians"]) : (double?)null, + HasPositionInfo = reader["HasPositionInfo"] != DBNull.Value && Convert.ToInt32(reader["HasPositionInfo"]) == 1 }); } } @@ -465,6 +535,16 @@ namespace NavisworksTransport Distance = 0.0, CreatedTime = DateTime.Now }; + + // 恢复碰撞时运动物体的位置和朝向(如果可用) + if (obj.HasPositionInfo && obj.Item1PosX.HasValue && obj.Item1PosY.HasValue && obj.Item1PosZ.HasValue) + { + collisionResult.Item1Position = new Point3D(obj.Item1PosX.Value, obj.Item1PosY.Value, obj.Item1PosZ.Value); + collisionResult.Item1YawRadians = obj.Item1YawRadians ?? 0.0; + collisionResult.HasPositionInfo = true; + + LogManager.Debug($"[加载碰撞结果] 恢复运动物体位置: ({obj.Item1PosX:F2}, {obj.Item1PosY:F2}, {obj.Item1PosZ:F2}), 朝向: {obj.Item1YawRadians:F2} rad"); + } results.Add(collisionResult); } @@ -676,6 +756,9 @@ namespace NavisworksTransport LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对"); + // 🔥 初始化确认碰撞位置字典 + _confirmedCollisionPositions.Clear(); + // 缓存去重后的碰撞结果(每组取第一个) lock (_resultsLock) { @@ -796,6 +879,18 @@ namespace NavisworksTransport confirmedCount++; skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量 + // 🔥 关键:记录确认碰撞的候选帧位置信息,供后续使用 + // 使用Item2作为key,因为最终去重也是按Item2合并的 + var confirmedPositionKey = GetCollisionObjectKey(candidate.Item1, candidate.Item2); + _confirmedCollisionPositions[confirmedPositionKey] = new CollisionPositionInfo + { + Item1Position = candidate.Item1Position, + Item2Position = candidate.Item2Position, + Item1YawRadians = candidate.Item1YawRadians, + HasPositionInfo = candidate.HasPositionInfo + }; + LogManager.Debug($"[ClashDetective确认] 记录碰撞位置: {confirmedPositionKey}, 位置: ({candidate.Item1Position.X:F2}, {candidate.Item1Position.Y:F2}, {candidate.Item1Position.Z:F2})"); + int subResultIndex = 1; // 🔥 优化:预获取运动物体名称,避免在循环中重复获取 var animatedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animatedObject); @@ -873,6 +968,10 @@ namespace NavisworksTransport originalItem2 = clashResult.Item2; // 回退到原始对象 } + // 🔥 从确认碰撞位置字典中获取位置信息(ClashDetective实际验证通过的帧) + var positionKey = GetCollisionObjectKey(animatedObject, originalItem2); + _confirmedCollisionPositions.TryGetValue(positionKey, out var confirmedPosition); + var collisionResult = new CollisionResult { ClashGuid = clashResult.Guid, @@ -882,7 +981,12 @@ namespace NavisworksTransport Item2 = originalItem2, Center = clashResult.Center, Distance = clashResult.Distance, - CreatedTime = DateTime.Now + CreatedTime = DateTime.Now, + // 🔥 使用ClashDetective确认时的位置信息 + Item1Position = confirmedPosition?.Item1Position, + Item2Position = confirmedPosition?.Item2Position, + Item1YawRadians = confirmedPosition?.Item1YawRadians ?? 0, + HasPositionInfo = confirmedPosition != null }; clashResults.Add(collisionResult); } @@ -895,7 +999,9 @@ namespace NavisworksTransport .Select(g => g.First()) .ToList(); - LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对"); + // 🔥 日志:统计位置信息保留情况 + var withPositionCount = finalClashResults.Count(c => c.HasPositionInfo && c.Item1Position != null); + LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对,其中 {withPositionCount} 个包含位置信息"); // 🔥 优化:在去重后为结果设置详细的 DisplayName // 这样只对保留的结果进行名称计算,避免在子碰撞结果上浪费性能 @@ -961,6 +1067,10 @@ namespace NavisworksTransport LogManager.Info($"=== 使用预计算碰撞数据创建ClashDetective测试(容差: {detectionGap}米)==="); LogManager.Info($"[预计算数据] 共有 {precomputedCollisions.Count} 个碰撞记录"); + + // 检查预计算碰撞结果是否包含位置信息 + var collisionsWithPosition = precomputedCollisions.Count(c => c.HasPositionInfo && c.Item1Position != null); + LogManager.Info($"[预计算数据] 包含位置信息的碰撞: {collisionsWithPosition}/{precomputedCollisions.Count}"); // 直接使用所有预计算结果,只过滤有效对象(不去重) var collisionResults = precomputedCollisions @@ -1884,9 +1994,10 @@ namespace NavisworksTransport public double Distance { get; set; } public DateTime CreatedTime { get; set; } - // 位置信息用于恢复测试 + // 位置和朝向信息用于还原碰撞场景 public Point3D Item1Position { get; set; } public Point3D Item2Position { get; set; } + public double Item1YawRadians { get; set; } // 运动物体朝向(弧度) public bool HasPositionInfo { get; set; } // IEquatable 实现:基于碰撞对象进行去重 diff --git a/src/Core/PathDatabase.cs b/src/Core/PathDatabase.cs index 8a64b76..b531b35 100644 --- a/src/Core/PathDatabase.cs +++ b/src/Core/PathDatabase.cs @@ -195,6 +195,7 @@ namespace NavisworksTransport "); // 7. ClashDetective碰撞对象表 + // 扩展字段:记录碰撞时运动物体的位置和朝向,用于还原碰撞场景 ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS ClashDetectiveCollisionObjects ( Id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -203,6 +204,12 @@ namespace NavisworksTransport PathId TEXT, DisplayName TEXT, ObjectName TEXT, + -- 碰撞时运动物体的位置和朝向(用于还原碰撞场景) + Item1PosX REAL, + Item1PosY REAL, + Item1PosZ REAL, + Item1YawRadians REAL, + HasPositionInfo INTEGER DEFAULT 0, FOREIGN KEY(ResultId) REFERENCES ClashDetectiveResults(Id) ON DELETE CASCADE ) "); @@ -227,7 +234,7 @@ namespace NavisworksTransport // 9. 设置数据库版本(SQLite内置user_version) // 版本号格式:主版本*10000 + 次版本*100 + 修订号 // 例如:2.1.3 = 20103 - ExecuteNonQuery("PRAGMA user_version = 20100"); // v2.1.0 + ExecuteNonQuery("PRAGMA user_version = 20101"); // v2.1.1 - 增加碰撞时运动物体位置和朝向字段 // 创建索引 ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_reports_route ON CollisionReports(RouteId)"); @@ -1074,8 +1081,10 @@ namespace NavisworksTransport { var sql = @" INSERT INTO ClashDetectiveCollisionObjects - (ResultId, ModelIndex, PathId, DisplayName, ObjectName) - VALUES (@resultId, @modelIndex, @pathId, @displayName, @objectName) + (ResultId, ModelIndex, PathId, DisplayName, ObjectName, + Item1PosX, Item1PosY, Item1PosZ, Item1YawRadians, HasPositionInfo) + VALUES (@resultId, @modelIndex, @pathId, @displayName, @objectName, + @item1PosX, @item1PosY, @item1PosZ, @item1YawRadians, @hasPositionInfo) "; foreach (var obj in objects) @@ -1087,6 +1096,11 @@ namespace NavisworksTransport cmd.Parameters.AddWithValue("@pathId", obj.PathId ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@displayName", obj.DisplayName ?? ""); cmd.Parameters.AddWithValue("@objectName", obj.ObjectName ?? ""); + cmd.Parameters.AddWithValue("@item1PosX", obj.Item1PosX.HasValue ? (object)obj.Item1PosX.Value : DBNull.Value); + cmd.Parameters.AddWithValue("@item1PosY", obj.Item1PosY.HasValue ? (object)obj.Item1PosY.Value : DBNull.Value); + cmd.Parameters.AddWithValue("@item1PosZ", obj.Item1PosZ.HasValue ? (object)obj.Item1PosZ.Value : DBNull.Value); + cmd.Parameters.AddWithValue("@item1YawRadians", obj.Item1YawRadians.HasValue ? (object)obj.Item1YawRadians.Value : DBNull.Value); + cmd.Parameters.AddWithValue("@hasPositionInfo", obj.HasPositionInfo ? 1 : 0); cmd.ExecuteNonQuery(); } } @@ -1112,7 +1126,8 @@ namespace NavisworksTransport try { var sql = @" - SELECT Id, ResultId, ModelIndex, PathId, DisplayName, ObjectName + SELECT Id, ResultId, ModelIndex, PathId, DisplayName, ObjectName, + Item1PosX, Item1PosY, Item1PosZ, Item1YawRadians, HasPositionInfo FROM ClashDetectiveCollisionObjects WHERE ResultId = @resultId "; @@ -1131,7 +1146,12 @@ namespace NavisworksTransport ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null, PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null, DisplayName = reader["DisplayName"]?.ToString(), - ObjectName = reader["ObjectName"]?.ToString() + ObjectName = reader["ObjectName"]?.ToString(), + Item1PosX = reader["Item1PosX"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosX"]) : (double?)null, + Item1PosY = reader["Item1PosY"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosY"]) : (double?)null, + Item1PosZ = reader["Item1PosZ"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosZ"]) : (double?)null, + Item1YawRadians = reader["Item1YawRadians"] != DBNull.Value ? Convert.ToDouble(reader["Item1YawRadians"]) : (double?)null, + HasPositionInfo = reader["HasPositionInfo"] != DBNull.Value && Convert.ToInt32(reader["HasPositionInfo"]) == 1 }); } } @@ -2598,6 +2618,7 @@ namespace NavisworksTransport /// /// ClashDetective碰撞对象数据模型 + /// 包含碰撞时运动物体的位置和朝向信息,用于还原碰撞场景 /// public class ClashDetectiveCollisionObjectRecord { @@ -2607,6 +2628,13 @@ namespace NavisworksTransport public string PathId { get; set; } public string DisplayName { get; set; } public string ObjectName { get; set; } + + // 碰撞时运动物体的位置和朝向(用于还原碰撞场景) + public double? Item1PosX { get; set; } + public double? Item1PosY { get; set; } + public double? Item1PosZ { get; set; } + public double? Item1YawRadians { get; set; } + public bool HasPositionInfo { get; set; } } /// diff --git a/src/UI/WPF/ViewModels/CollisionReportViewModel.cs b/src/UI/WPF/ViewModels/CollisionReportViewModel.cs index af50f43..7ba40e9 100644 --- a/src/UI/WPF/ViewModels/CollisionReportViewModel.cs +++ b/src/UI/WPF/ViewModels/CollisionReportViewModel.cs @@ -89,6 +89,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels private double _detectionTolerance; private CollisionReportResult _currentReport; private ObservableCollection _excludedObjects; + + // 保存运动物体原始状态,用于碰撞查看后恢复 + private ModelItemTransformHelper.ObjectStateSnapshot _savedAnimatedObjectState; + private ModelItem _currentAnimatedObject; #endregion @@ -637,10 +641,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels collisionItems.Add(item); } - // 更新UI集合 + // 更新UI集合(按序号排序保持顺序) SafeExecute(() => { - foreach (var item in collisionItems.OrderBy(i => i.Title)) + foreach (var item in collisionItems.OrderBy(i => i.Index)) AnimationCollisions.Add(item); }, "更新碰撞报告项目列表", true); @@ -1229,6 +1233,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// /// 高亮并聚焦到碰撞项目中的被撞对象(Item2) + /// 同时将运动物体(Item1)移动到碰撞时的位置和朝向,还原碰撞场景 /// public void HighlightAndFocusCollisionItem(CollisionReportItem collisionItem) { @@ -1259,12 +1264,92 @@ namespace NavisworksTransport.UI.WPF.ViewModels LogManager.Info($"聚焦到被撞对象: {collisionItem.Title}"); } + + // 还原运动物体到碰撞时的位置和朝向 + LogManager.Debug($"[碰撞详情点击] Item1={collisionData.Item1?.DisplayName}, HasPositionInfo={collisionData.HasPositionInfo}, Item1Position={collisionData.Item1Position}"); + if (collisionData.Item1 != null && collisionData.HasPositionInfo && collisionData.Item1Position != null) + { + // 首次查看碰撞时保存运动物体状态 + if (_savedAnimatedObjectState == null || _currentAnimatedObject != collisionData.Item1) + { + _currentAnimatedObject = collisionData.Item1; + _savedAnimatedObjectState = ModelItemTransformHelper.SaveObjectState(collisionData.Item1); + LogManager.Info($"[碰撞查看] 已保存运动物体原始状态,供后续恢复使用"); + } + + RestoreAnimatedObjectToCollisionPosition(collisionData); + } + else + { + LogManager.Warning($"[碰撞详情点击] 无法还原运动物体位置: Item1={(collisionData.Item1 != null ? "有" : "无")}, HasPositionInfo={collisionData.HasPositionInfo}, Item1Position={(collisionData.Item1Position != null ? "有" : "无")}"); + } } catch (Exception ex) { LogManager.Error($"聚焦到被撞对象失败: {ex.Message}", ex); } } + + /// + /// 将运动物体还原到碰撞时的位置和朝向 + /// 使用ModelItemTransformHelper.MoveItemToPositionAndYaw从CAD原始状态精确定位 + /// + private void RestoreAnimatedObjectToCollisionPosition(CollisionResult collisionData) + { + try + { + var animatedObject = collisionData.Item1; + var targetPosition = collisionData.Item1Position; + var targetYaw = collisionData.Item1YawRadians; + + if (animatedObject == null || targetPosition == null) + { + LogManager.Warning("[还原碰撞位置] 运动物体或位置信息为空"); + return; + } + + // 计算目标底面位置(Item1Position存储的是包围盒中心,需要转换为底面) + var currentBounds = animatedObject.BoundingBox(); + double halfHeight = (currentBounds.Max.Z - currentBounds.Min.Z) / 2.0; + var targetGroundPosition = new Point3D( + targetPosition.X, + targetPosition.Y, + targetPosition.Z - halfHeight + ); + + // 使用工具方法从CAD原始状态移动到目标位置 + ModelItemTransformHelper.MoveItemToPositionAndYaw(animatedObject, targetGroundPosition, targetYaw); + + LogManager.Info($"[还原碰撞位置] 运动物体已移动到碰撞位置: ({targetGroundPosition.X:F2}, {targetGroundPosition.Y:F2}, {targetGroundPosition.Z:F2}), 朝向: {targetYaw * 180 / Math.PI:F2}°"); + } + catch (Exception ex) + { + LogManager.Error($"[还原碰撞位置] 失败: {ex.Message}", ex); + } + } + + /// + /// 恢复运动物体到原始状态(查看碰撞前保存的状态) + /// + public void RestoreAnimatedObjectToOriginalState() + { + try + { + if (_currentAnimatedObject != null && _savedAnimatedObjectState != null) + { + ModelItemTransformHelper.RestoreObjectState(_currentAnimatedObject, _savedAnimatedObjectState); + LogManager.Info("[碰撞查看] 运动物体已恢复到原始状态"); + + // 清除保存的状态 + _savedAnimatedObjectState = null; + _currentAnimatedObject = null; + } + } + catch (Exception ex) + { + LogManager.Error($"[恢复运动物体状态] 失败: {ex.Message}", ex); + } + } #endregion } diff --git a/src/UI/WPF/Views/CollisionReportDialog.xaml.cs b/src/UI/WPF/Views/CollisionReportDialog.xaml.cs index c6bb12c..7515ab0 100644 --- a/src/UI/WPF/Views/CollisionReportDialog.xaml.cs +++ b/src/UI/WPF/Views/CollisionReportDialog.xaml.cs @@ -134,6 +134,12 @@ namespace NavisworksTransport.UI.WPF.Views { try { + // 恢复运动物体到原始状态(查看碰撞前保存的状态) + if (_viewModel != null) + { + _viewModel.RestoreAnimatedObjectToOriginalState(); + } + // 清理资源 if (_viewModel != null) { diff --git a/src/Utils/ModelItemTransformHelper.cs b/src/Utils/ModelItemTransformHelper.cs index 9d9e30f..c9ded12 100644 --- a/src/Utils/ModelItemTransformHelper.cs +++ b/src/Utils/ModelItemTransformHelper.cs @@ -101,5 +101,152 @@ namespace NavisworksTransport.Utils var restoreTransform = Transform3D.CreateTranslation(restoreOffset); doc.Models.OverridePermanentTransform(modelItems, restoreTransform, false); } + + /// + /// 将物体从当前位置移动到指定位置和朝向(增量模式) + /// 适用于碰撞位置还原等场景 + /// + /// 要移动的物体 + /// 目标位置(地面位置,即包围盒底面) + /// 目标朝向(弧度,绕Z轴) + public static void MoveItemToPositionAndYaw(ModelItem item, Point3D targetPosition, double targetYaw) + { + var doc = Application.ActiveDocument; + var modelItems = new ModelItemCollection { item }; + + // 获取当前状态 + var currentBounds = item.BoundingBox(); + var currentGroundPos = new Point3D( + currentBounds.Center.X, + currentBounds.Center.Y, + currentBounds.Min.Z + ); + var currentYaw = GetYawFromTransform(item.Transform); + + // 计算位置和旋转增量 + var deltaPos = new Vector3D( + targetPosition.X - currentGroundPos.X, + targetPosition.Y - currentGroundPos.Y, + targetPosition.Z - currentGroundPos.Z + ); + double deltaYaw = targetYaw - currentYaw; + + // 应用增量变换(和ClashDetective中的做法一致) + Transform3D transform; + if (Math.Abs(deltaYaw) > 0.001) + { + // 有旋转:需要补偿绕原点旋转带来的位置偏移 + double cos = Math.Cos(deltaYaw); + double sin = Math.Sin(deltaYaw); + double rotatedX = currentGroundPos.X * cos - currentGroundPos.Y * sin; + double rotatedY = currentGroundPos.X * sin + currentGroundPos.Y * cos; + + var compensatedTranslation = new Vector3D( + targetPosition.X - rotatedX, + targetPosition.Y - rotatedY, + deltaPos.Z + ); + + var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)); + var components = identity.Factor(); + components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw); + components.Translation = compensatedTranslation; + transform = components.Combine(); + } + else + { + // 纯平移 + transform = Transform3D.CreateTranslation(deltaPos); + } + + doc.Models.OverridePermanentTransform(modelItems, transform, false); + } + + /// + /// 物体状态快照,用于保存和恢复 + /// + public class ObjectStateSnapshot + { + public Point3D Position { get; set; } + public double YawRadians { get; set; } + public Transform3D Transform { get; set; } + } + + /// + /// 保存物体当前状态 + /// + public static ObjectStateSnapshot SaveObjectState(ModelItem item) + { + if (item == null) return null; + + var bounds = item.BoundingBox(); + return new ObjectStateSnapshot + { + Position = new Point3D(bounds.Center.X, bounds.Center.Y, bounds.Min.Z), + YawRadians = GetYawFromTransform(item.Transform), + Transform = item.Transform + }; + } + + /// + /// 从状态快照恢复物体位置 + /// 先回到CAD原始位置,再移动到保存的位置 + /// + public static void RestoreObjectState(ModelItem item, ObjectStateSnapshot state) + { + if (item == null || state == null) return; + + var doc = Application.ActiveDocument; + var modelItems = new ModelItemCollection { item }; + + // 1. 先回到CAD原始位置 + doc.Models.ResetPermanentTransform(modelItems); + + // 2. 从CAD原始位置移动到保存的位置 + // 获取CAD原始状态 + var originalBounds = item.BoundingBox(); + var originalGroundPos = new Point3D( + originalBounds.Center.X, + originalBounds.Center.Y, + originalBounds.Min.Z + ); + var originalYaw = GetYawFromTransform(item.Transform); + + // 计算到保存位置的增量 + var deltaPos = new Vector3D( + state.Position.X - originalGroundPos.X, + state.Position.Y - originalGroundPos.Y, + state.Position.Z - originalGroundPos.Z + ); + double deltaYaw = state.YawRadians - originalYaw; + + // 应用增量变换 + Transform3D transform; + 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; + + var compensatedTranslation = new Vector3D( + state.Position.X - rotatedX, + state.Position.Y - rotatedY, + deltaPos.Z + ); + + var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)); + var components = identity.Factor(); + components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw); + components.Translation = compensatedTranslation; + transform = components.Combine(); + } + else + { + transform = Transform3D.CreateTranslation(deltaPos); + } + + doc.Models.OverridePermanentTransform(modelItems, transform, false); + } } }