using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Autodesk.Navisworks.Api; using Autodesk.Navisworks.Api.Clash; using NavisworksTransport.Core; using NavisworksTransport.Core.Animation; using NavisworksTransport.Utils; using NavisworksTransport.Utils.CoordinateSystem; namespace NavisworksTransport { /// /// Clash Detective 集成管理器 /// 实现动态碰撞检测与Clash Detective窗口的联动 /// public class ClashDetectiveIntegration { // Windows API: 设置前台窗口 [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); private static ClashDetectiveIntegration _instance; private DocumentClash _documentClash; private List _currentCollisions; // 通道对象缓存,用于优化碰撞检测性能 private static HashSet _channelObjectsCache = null; private static readonly object _cacheLock = new object(); // 几何对象列表缓存,用于避免重复获取对象列表 // 使用 ModelItemCollection 而非 List 以避免昂贵的 ToList() 转换 // 🔥 优化:此缓存保持稳定,不包含运动物体排除逻辑 private static ModelItemCollection _allGeometryItemsCache = null; // 已过滤通道对象的几何对象列表缓存(供空间索引使用) // 🔥 优化:查询时动态过滤运动物体,不依赖此缓存的稳定性 private static List _nonChannelGeometryItemsCache = null; // 🔥 移动物体引用(用于从空间索引中排除移动物体及其所有子节点) private static ModelItem _animatedObject = null; // 🔥 新增:运动物体及其后代的快速查找集合 // 用于查询阶段动态过滤,避免重建基础缓存 private static HashSet _animatedObjectAndDescendants = new HashSet(); // 🔥 简化设计:只保留一个权威碰撞计数器 // 预计算碰撞数作为参数传递,不维护类级别状态 private int _clashDetectiveCollisionCount = 0; /// /// Clash Detective检测到的权威碰撞数量(去重后的实际碰撞数) /// public int ClashDetectiveCollisionCount { get { return _clashDetectiveCollisionCount; } } /// /// 【已弃用】预计算碰撞检测点数量现在作为参数传递,不再维护类级别状态 /// 此属性始终返回0,请从数据库查询或使用局部变量 /// [Obsolete("预计算碰撞数现在作为参数传递,不再维护类级别状态")] public int AnimationCollisionCount { get { return 0; } } /// /// 当前ClashDetective测试名称 /// public string CurrentTestName { get { return _currentTestName; } } private string _currentTestName; /// /// 设置当前测试名称(用于从数据库加载历史记录时) /// public void SetCurrentTestName(string testName) { _currentTestName = testName; LogManager.Info($"[ClashDetectiveIntegration] 设置当前测试名称: {testName}"); } // 🔥 检测取消标志 private bool _wasLastTestCanceled = false; /// /// 上次检测是否被取消 /// public bool WasLastTestCanceled { get { return _wasLastTestCanceled; } } /// /// 单例实例 /// public static ClashDetectiveIntegration Instance { get { if (_instance == null) { _instance = new ClashDetectiveIntegration(); } return _instance; } } /// /// 当前检测到的碰撞结果 /// public List CurrentCollisions { get { return _currentCollisions ?? new List(); } } /// /// 碰撞检测结果变化事件 /// public event EventHandler CollisionDetected; /// /// ClashDetective结果保存到数据库事件 /// public event EventHandler ClashDetectiveResultSaved; private ClashDetectiveIntegration() { _currentCollisions = new List(); // 自动初始化 Clash Detective 集成 try { LogManager.Info("初始化Clash Detective集成(.NET API模式)..."); // 直接使用.NET API获取Clash文档 _documentClash = Application.ActiveDocument.GetClash(); if (_documentClash != null) { LogManager.Info("成功获取.NET API Clash文档"); LogManager.Info("Clash Detective集成初始化成功(.NET API模式)"); } else { LogManager.Warning("无法获取Clash文档,Clash Detective可能未安装"); } } catch (Exception ex) { LogManager.Error($"初始化Clash Detective集成失败: {ex.Message}"); _documentClash = null; } } private List _deduplicatedCollisionResults = new List(); // 去重后的预计算碰撞结果 private readonly object _resultsLock = new object(); // ClashDetective测试结果缓存:pathName -> List // 使用自定义的CollisionResult类型,支持修改Item1和Item2 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 AnimatedObjectTrackedPosition { get; set; } public Point3D Item2Position { get; set; } public double AnimatedObjectTrackedYawRadians { get; set; } public Rotation3D AnimatedObjectTrackedRotation { get; set; } public bool AnimatedObjectHasTrackedRotation { 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}"; } } /// /// 获取去重后的预计算碰撞结果(第一次去重,按碰撞对象对去重) /// public List GetDeduplicatedCollisionResults() { lock (_resultsLock) { LogManager.Debug($"[GetDeduplicatedCollisionResults] 当前去重缓存数量: {_deduplicatedCollisionResults.Count}"); return new List(_deduplicatedCollisionResults); } } /// /// 保存ClashDetective结果到数据库 /// 注意:动画参数和物体配置通过 detectionRecordId 关联到 CollisionDetectionRecords 表 /// private void SaveClashDetectiveResultToDatabase(string routeId, List clashResults, int precomputedCollisionCount = 0, int? detectionRecordId = null) { try { var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase(); if (pathDatabase != null && detectionRecordId.HasValue) { // 更新碰撞检测结果到检测记录 pathDatabase.UpdateCollisionDetectionResult( detectionRecordId.Value, _currentTestName, clashResults.Count, precomputedCollisionCount ); LogManager.Info($"ClashDetective结果已保存到数据库,DetectionRecordId={detectionRecordId}, TestName={_currentTestName}"); // 保存被撞物体信息(只保存Item2,不保存物体Item1) // 同时保存碰撞时运动物体的位置和朝向,用于还原碰撞场景 var collisionObjects = new List(); foreach (var collision in clashResults) { // 只保存被撞到的物体(Item2) if (collision.Item2 != null) { // 使用 CreatePathId API 获取 ModelIndex 和 PathId var pathId = Application.ActiveDocument.Models.CreatePathId(collision.Item2); var collisionRecord = new ClashDetectiveCollisionObjectRecord { DetectionRecordId = detectionRecordId.Value, ModelIndex = pathId.ModelIndex, PathId = pathId.PathId, DisplayName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2), ObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2) }; // 保存碰撞时运动物体的位置和朝向(如果可用) if (collision.HasPositionInfo && collision.AnimatedObjectTrackedPosition != null) { collisionRecord.AnimatedObjectTrackedPosX = collision.AnimatedObjectTrackedPosition.X; collisionRecord.AnimatedObjectTrackedPosY = collision.AnimatedObjectTrackedPosition.Y; collisionRecord.AnimatedObjectTrackedPosZ = collision.AnimatedObjectTrackedPosition.Z; collisionRecord.AnimatedObjectTrackedYawRadians = collision.AnimatedObjectTrackedYawRadians; collisionRecord.AnimatedObjectTrackedRotA = collision.AnimatedObjectTrackedRotation?.A; collisionRecord.AnimatedObjectTrackedRotB = collision.AnimatedObjectTrackedRotation?.B; collisionRecord.AnimatedObjectTrackedRotC = collision.AnimatedObjectTrackedRotation?.C; collisionRecord.AnimatedObjectTrackedRotD = collision.AnimatedObjectTrackedRotation?.D; collisionRecord.AnimatedObjectHasTrackedRotation = collision.AnimatedObjectHasTrackedRotation; collisionRecord.HasPositionInfo = true; LogManager.Debug($"[保存碰撞对象] 记录运动物体位置: ({collisionRecord.AnimatedObjectTrackedPosX:F2}, {collisionRecord.AnimatedObjectTrackedPosY:F2}, {collisionRecord.AnimatedObjectTrackedPosZ:F2}), yaw={collisionRecord.AnimatedObjectTrackedYawRadians:F2} rad, customRotation={collisionRecord.AnimatedObjectHasTrackedRotation}"); } collisionObjects.Add(collisionRecord); } } if (collisionObjects.Count > 0) { pathDatabase.SaveClashDetectiveCollisionObjects(detectionRecordId.Value, collisionObjects); LogManager.Info($"已保存 {collisionObjects.Count} 个被撞物体到数据库"); } // 触发结果保存事件,通知UI刷新列表(从RouteId获取PathName) var pathName = GetPathNameByRouteId(routeId); ClashDetectiveResultSaved?.Invoke(this, new ClashDetectiveResultSavedEventArgs(pathName, _currentTestName, clashResults.Count)); } } catch (Exception ex) { LogManager.Error($"保存ClashDetective结果到数据库失败: {ex.Message}"); // 不影响主流程,继续执行 } } /// /// 根据RouteId获取路径名称 /// private string GetPathNameByRouteId(string routeId) { if (string.IsNullOrEmpty(routeId)) return "未知路径"; try { var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase(); if (pathDatabase != null) { var pathRoute = pathDatabase.GetPathRouteSync(routeId); return pathRoute?.Name ?? "未知路径"; } } catch (Exception ex) { LogManager.Error($"获取路径名称失败: {ex.Message}"); } return "未知路径"; } /// /// 从数据库加载ClashDetective碰撞结果 /// /// 测试名称 /// 碰撞结果列表,如果加载失败返回null public List GetClashDetectiveResultsFromDatabase(string testName) { try { var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase(); if (pathDatabase == null) { LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] PathDatabase不可用"); return null; } // 1. 从数据库读取测试信息(直接从 CollisionDetectionRecords 表查询) var detectionRecord = pathDatabase.GetDetectionResultByTestName(testName); if (detectionRecord == null) { LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 未找到测试记录: {testName}"); return null; } LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 找到测试记录: Id={detectionRecord.Id}, TestName={detectionRecord.TestName}, CollisionCount={detectionRecord.CollisionCount}"); // 2. 重建物体对象 ModelItem objectObject = null; if (detectionRecord.IsVirtualObject) { // 创建虚拟物体(保留现有变换如果尺寸相同) // 检查尺寸数据完整性 if (!detectionRecord.VirtualObjectLength.HasValue || !detectionRecord.VirtualObjectWidth.HasValue || !detectionRecord.VirtualObjectHeight.HasValue) { throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 虚拟物体记录缺少尺寸数据: TestName={testName}"); } var modelToMeters = UnitsConverter.GetUnitsToMetersConversionFactor(); objectObject = VirtualObjectManager.Instance.CreateVirtualObject( detectionRecord.VirtualObjectLength.Value * modelToMeters, detectionRecord.VirtualObjectWidth.Value * modelToMeters, detectionRecord.VirtualObjectHeight.Value * modelToMeters ) ?? throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 获取虚拟物体失败"); } else if (detectionRecord.ObjectModelIndex.HasValue && !string.IsNullOrEmpty(detectionRecord.ObjectPathId)) { // 通过 PathId 查找真实物体 try { var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId { ModelIndex = detectionRecord.ObjectModelIndex.Value, PathId = detectionRecord.ObjectPathId }; objectObject = Application.ActiveDocument.Models.ResolvePathId(pathIdObj) ?? throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法通过 PathId 找到物体对象: ModelIndex={detectionRecord.ObjectModelIndex}, PathId={detectionRecord.ObjectPathId}"); LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已找到真实物体: {ModelItemAnalysisHelper.GetSafeDisplayName(objectObject)}"); } catch (Exception ex) { throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] ResolvePathId 失败: ModelIndex={detectionRecord.ObjectModelIndex}, PathId={detectionRecord.ObjectPathId}", ex); } } if (objectObject == null) { throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法重建物体对象: IsVirtualObject={detectionRecord.IsVirtualObject}, ObjectModelIndex={detectionRecord.ObjectModelIndex}, ObjectPathId={detectionRecord.ObjectPathId}"); } // 如果是虚拟物体且是新创建的(不是已存在的),将其移动到路径起点 // 注意:如果虚拟物体已存在(动画刚结束),保持当前位置(终点),不要移动到起点 if (detectionRecord.IsVirtualObject && !string.IsNullOrEmpty(detectionRecord.RouteId) && !VirtualObjectManager.Instance.IsVirtualObjectActive) { try { var pathPlanningManager = PathPlanningManager.Instance; var route = pathPlanningManager.GetAllRoutes().FirstOrDefault(r => r.Id == detectionRecord.RouteId); if (route != null && route.Points != null && route.Points.Count > 0) { // 获取第一个点(起点) var startPoint = route.Points.FirstOrDefault(p => p.Type == PathPointType.StartPoint) ?? route.Points[0]; var startPointPosition = startPoint.Position; // 使用 PathAnimationManager 将物体移动到起点 var pathAnimationManager = PathAnimationManager.GetInstance(); bool moved = pathAnimationManager.MoveObjectToPathStart(objectObject, new List { startPointPosition, startPointPosition }); if (moved) { LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 虚拟物体已移动到路径起点: ({startPointPosition.X:F2}, {startPointPosition.Y:F2}, {startPointPosition.Z:F2})"); } else { LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 虚拟物体移动到路径起点失败"); } } } catch (Exception ex) { LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 移动虚拟物体到起点失败: {ex.Message}"); } } else if (detectionRecord.IsVirtualObject && VirtualObjectManager.Instance.IsVirtualObjectActive) { LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 虚拟物体已存在,保持当前位置(终点),不移动到起点"); } // 3. 从数据库读取被撞物体信息(包含碰撞时运动物体的位置和朝向) var collisionObjects = pathDatabase.GetClashDetectiveCollisionObjects(detectionRecord.Id); LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 从数据库读取碰撞对象: DetectionRecordId={detectionRecord.Id}, 找到 {collisionObjects.Count} 个对象"); // 4. 重建碰撞结果 var results = new List(); foreach (var obj in collisionObjects) { ModelItem collidedObject = null; // 优先使用 ResolvePathId API(新方式) if (obj.ModelIndex.HasValue && !string.IsNullOrEmpty(obj.PathId)) { try { var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId(); pathIdObj.ModelIndex = obj.ModelIndex.Value; pathIdObj.PathId = obj.PathId; collidedObject = Application.ActiveDocument.Models.ResolvePathId(pathIdObj); } catch (Exception ex) { throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] ResolvePathId 失败: ModelIndex={obj.ModelIndex}, PathId={obj.PathId}", ex); } } // 🔥 PathId 缺失/解析失败(F1 副实例映射未命中场景):按 DisplayName 在主模型查找(唯一匹配) if (collidedObject == null && !string.IsNullOrWhiteSpace(obj.ObjectName)) { collidedObject = TryFindSingleObjectByName(obj.ObjectName); if (collidedObject != null) { LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已按名称匹配碰撞对象: {obj.ObjectName}"); } } if (collidedObject == null) { // 🔥 容错:单个对象无法定位不阻断整个报告——跳过并警告(其余对象仍可显示) LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 跳过无法定位的碰撞对象: {obj.ObjectName} (ModelIndex={obj.ModelIndex}, PathId={obj.PathId})"); continue; } if (!ModelItemAnalysisHelper.IsModelItemValid(collidedObject)) { throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 找到的 ModelItem 无效: {obj.ModelIndex},{obj.PathId}"); } // 使用重建对象的 DisplayName,而不是数据库中保存的 DisplayName var collidedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collidedObject); var collisionResult = new CollisionResult { ClashGuid = Guid.NewGuid(), DisplayName = $"历史碰撞: {collidedObjectName}", Status = ClashResultStatus.Active, Item1 = objectObject, Item2 = collidedObject, Center = collidedObject.BoundingBox().Center, Distance = 0.0, CreatedTime = DateTime.Now }; // 恢复碰撞时运动物体的位置和朝向(如果可用) if (obj.HasPositionInfo && obj.AnimatedObjectTrackedPosX.HasValue && obj.AnimatedObjectTrackedPosY.HasValue && obj.AnimatedObjectTrackedPosZ.HasValue) { collisionResult.AnimatedObjectTrackedPosition = new Point3D(obj.AnimatedObjectTrackedPosX.Value, obj.AnimatedObjectTrackedPosY.Value, obj.AnimatedObjectTrackedPosZ.Value); collisionResult.AnimatedObjectTrackedYawRadians = obj.AnimatedObjectTrackedYawRadians ?? 0.0; collisionResult.AnimatedObjectHasTrackedRotation = obj.AnimatedObjectHasTrackedRotation; if (obj.AnimatedObjectHasTrackedRotation && obj.AnimatedObjectTrackedRotA.HasValue && obj.AnimatedObjectTrackedRotB.HasValue && obj.AnimatedObjectTrackedRotC.HasValue && obj.AnimatedObjectTrackedRotD.HasValue) { collisionResult.AnimatedObjectTrackedRotation = new Rotation3D( obj.AnimatedObjectTrackedRotA.Value, obj.AnimatedObjectTrackedRotB.Value, obj.AnimatedObjectTrackedRotC.Value, obj.AnimatedObjectTrackedRotD.Value); } collisionResult.HasPositionInfo = true; LogManager.Debug($"[加载碰撞结果] 恢复运动物体位置: ({obj.AnimatedObjectTrackedPosX:F2}, {obj.AnimatedObjectTrackedPosY:F2}, {obj.AnimatedObjectTrackedPosZ:F2}), yaw={obj.AnimatedObjectTrackedYawRadians:F2} rad, customRotation={obj.AnimatedObjectHasTrackedRotation}"); } results.Add(collisionResult); } LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 从数据库加载测试 '{testName}' 完成,重建了 {results.Count} 个碰撞结果"); return results; } catch (Exception ex) { LogManager.Error($"[LoadClashDetectiveResultsFromDatabase] 加载失败: {ex.Message}", ex); return null; } } /// /// 获取对象当前位置 /// private Point3D GetObjectPosition(ModelItem item) { try { if (item == null) return new Point3D(0, 0, 0); var bounds = item.BoundingBox(); if (bounds != null) { return new Point3D( (bounds.Min.X + bounds.Max.X) / 2, (bounds.Min.Y + bounds.Max.Y) / 2, (bounds.Min.Z + bounds.Max.Z) / 2 ); } return new Point3D(0, 0, 0); } catch (Exception ex) { LogManager.Error($"获取对象位置失败: {ex.Message}"); return new Point3D(0, 0, 0); } } /// /// 通过PathId向上查找匹配的原始对象 /// 从ClashDetective返回的几何体组件开始,向上遍历父节点, /// 直到找到与预计算记录中任一对象PathId匹配的父节点 /// private ModelItem FindMatchingObjectByPathId(ModelItem clashItem2, List precomputedCollisions) { try { if (precomputedCollisions == null || precomputedCollisions.Count == 0 || clashItem2 == null) return null; var doc = Application.ActiveDocument; if (doc?.Models == null) return null; // 1. 收集所有预计算记录中Item2的PathId(作为HashSet提高查找效率) var precomputedPathIds = new HashSet<(int ModelIndex, string PathId)>(); foreach (var precomputed in precomputedCollisions) { if (precomputed.Item2 != null) { try { var pathId = doc.Models.CreatePathId(precomputed.Item2); precomputedPathIds.Add((pathId.ModelIndex, pathId.PathId)); } catch (Exception ex) { LogManager.Warning($"[PathId匹配] 获取PathId失败: {ex.Message}"); } } } if (precomputedPathIds.Count == 0) { LogManager.Debug($"[PathId匹配] 预计算记录中没有有效的PathId"); return null; } // 2. 从ClashDetective返回的Item2开始向上遍历父节点 var current = clashItem2; int levels = 0; while (current != null && levels < 20) // 最多向上查找20层 { try { var currentPathId = doc.Models.CreatePathId(current); // 检查PathId是否在预计算记录中 if (precomputedPathIds.Contains((currentPathId.ModelIndex, currentPathId.PathId))) { LogManager.Debug($"[PathId匹配] 成功找到匹配的父节点: {ModelItemAnalysisHelper.GetSafeDisplayName(current)} (向上{levels}层)"); return current; } // 继续向上查找 current = current.Parent; levels++; } catch { // 如果当前节点无法获取PathId,继续向上 current = current.Parent; levels++; } } LogManager.Debug($"[PathId匹配] 未找到匹配的父节点,向上查找了{levels}层"); return null; } catch (Exception ex) { LogManager.Warning($"[PathId匹配] 查找失败: {ex.Message}"); return null; } } /// /// 运行ClashDetective测试并保存到数据库(公共方法,供批处理和非批处理调用) /// /// 碰撞分组、主测试、确认的碰撞数量、是否被取消 public (ClashResultGroup collisionGroup, ClashTest addedMainTest, int confirmedCount, bool wasCanceled) RunClashDetectiveTestsAndSaveToDatabase( List precomputedCollisions, double detectionGap, string routeId, ModelItem animatedObject, bool isVirtualObject, int frameRate, double duration, double virtualObjectLength, double virtualObjectWidth, double virtualObjectHeight, Progress progress = null, int precomputedCollisionCount = 0, int? detectionRecordId = null) { LogManager.Info($"[ClashDetective] 开始运行碰撞检测并保存到数据库(容差: {detectionGap}米)"); // 保存碰撞检测前的物体状态 if (animatedObject != null && IsModelItemValid(animatedObject)) { PathAnimationManager.GetInstance().SaveObjectState(animatedObject); } // 过滤有效的碰撞 var validCollisions = precomputedCollisions.Where(collision => collision.HasPositionInfo && IsModelItemValid(collision.Item1) && IsModelItemValid(collision.Item2) ).ToList(); LogManager.Info($"[ClashDetective] 有效碰撞数量: {validCollisions.Count}"); // 第一步:创建主测试(不包含选择集,纯容器) var mainTestName = $"碰撞检测_{DateTime.Now:MMdd_HHmmssfff}"; _currentTestName = mainTestName; LogManager.Info($"[分组测试] 创建主测试: {mainTestName}"); // 创建主测试 var mainTest = new ClashTest { DisplayName = mainTestName, TestType = ClashTestType.Hard, Tolerance = detectionGap, Guid = Guid.Empty, MergeComposites = true }; // 添加主测试到文档 _documentClash.TestsData.TestsAddCopy(mainTest); LogManager.Info($"[分组测试] 主测试已添加到文档"); // 获取添加后的测试对象引用 var addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest; if (addedMainTest == null) { LogManager.Error("[分组测试] 无法获取添加后的主测试对象"); return (null, null, 0, false); } // 如果没有有效碰撞,直接保存空记录并返回 if (validCollisions.Count == 0) { LogManager.Warning("[ClashDetective] 没有有效的碰撞,保存空碰撞记录"); // 重置碰撞计数器(重要:避免保留上次测试的值) _clashDetectiveCollisionCount = 0; // 保存到数据库(配置参数通过 detectionRecordId 关联获取) SaveClashDetectiveResultToDatabase( routeId, new List(), precomputedCollisionCount, detectionRecordId ); return (null, addedMainTest, 0, false); } // 第二步:创建分组并添加碰撞结果(应用智能去重) // 1. 分组:按碰撞对象对分组 var groupedCollisions = validCollisions .GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 }) .ToList(); LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对"); // 🔥 初始化确认碰撞位置字典 _confirmedCollisionPositions.Clear(); // 缓存去重后的碰撞结果(每组取第一个) lock (_resultsLock) { _deduplicatedCollisionResults.Clear(); foreach (var group in groupedCollisions) { _deduplicatedCollisionResults.Add(group.First()); } LogManager.Debug($"[去重缓存] 已缓存 {_deduplicatedCollisionResults.Count} 个去重后的碰撞结果"); } var collisionGroup = new ClashResultGroup { DisplayName = $"碰撞检测组 ({groupedCollisions.Count} 个唯一碰撞对)" }; LogManager.Info($"[分组测试] 创建碰撞分组: {collisionGroup.DisplayName}"); int confirmedCount = 0; int skippedCount = 0; int testCount = 0; // 检测计数器,用于进度日志 int totalTests = validCollisions.Count; // 总检测次数(预估) int totalGroups = groupedCollisions.Count; // 总组数 var doc = Application.ActiveDocument; // 初始化进度条 progress?.Update(0.0); // 收集所有临时测试,用于批量删除 var tempTestsToRemove = new List(); bool wasCanceled = false; // 2. 遍历每一组 for (int groupIndex = 0; groupIndex < groupedCollisions.Count; groupIndex++) { // 检查用户是否取消 if (progress != null && progress.IsCanceled) { LogManager.Info($"[分组测试] 用户取消操作,已处理 {groupIndex}/{groupedCollisions.Count} 组"); wasCanceled = true; break; } // 更新进度条(每5组更新一次,避免过于频繁) if (groupIndex % 5 == 0 || groupIndex == groupedCollisions.Count - 1) { double progressPercent = (double)groupIndex / totalGroups * 0.7; // 组进度占70% progress?.Update(progressPercent); } var group = groupedCollisions[groupIndex]; // 3. 排序:按距离/重叠深度排序,优先检测最严重的碰撞 // 注意:Distance通常越小(或负值越大)表示碰撞越深,具体取决于计算方式 // 这里假设Distance越小越严重 var sortedCandidates = group.OrderBy(c => c.Distance).ToList(); bool pairConfirmed = false; // 4. 验证即止:逐个检测候选帧 for (int i = 0; i < sortedCandidates.Count; i++) { var candidate = sortedCandidates[i]; try { var testAnimatedObject = candidate.Item1; var modelItems = new ModelItemCollection { testAnimatedObject }; var targetPosition = candidate.AnimatedObjectTrackedPosition; var targetYaw = candidate.AnimatedObjectTrackedYawRadians; var targetRotation = candidate.AnimatedObjectTrackedRotation; if (candidate.AnimatedObjectHasTrackedRotation && targetRotation != null) { LogManager.Info( $"[ClashDetective验证] 已跟踪姿态候选恢复: " + $"对象={testAnimatedObject.DisplayName}, " + $"目标跟踪点=({targetPosition.X:F3},{targetPosition.Y:F3},{targetPosition.Z:F3})"); var pam = PathAnimationManager.GetInstance(); if (pam != null && pam.ControlsAnimatedObject(testAnimatedObject)) { pam.MoveAnimatedObjectToPose( testAnimatedObject, targetPosition, targetYaw, targetRotation, true); } else { throw new InvalidOperationException( $"ClashDetective已跟踪姿态候选恢复失败:对象 {testAnimatedObject.DisplayName} 不受当前动画管理器控制。"); } } else { throw new InvalidOperationException( $"ClashDetective候选恢复缺少完整姿态:对象 {testAnimatedObject.DisplayName}," + $"目标跟踪点=({targetPosition.X:F3},{targetPosition.Y:F3},{targetPosition.Z:F3})," + $"TrackedYaw={targetYaw:F6}rad。"); } var tempTestName = $"临时验证_{confirmedCount + 1}_{i}_{DateTime.Now:HHmmss_fff}"; // 🔥 优化3:预配置 ClashTest,避免后续 CreateCopy 和 TestsEditTestFromCopy 调用 var tempTest = new ClashTest { DisplayName = tempTestName, TestType = ClashTestType.Hard, Tolerance = detectionGap, Guid = Guid.Empty, MergeComposites = true }; var selectionA = new ModelItemCollection { candidate.Item1 }; var selectionB = new ModelItemCollection { candidate.Item2 }; tempTest.SelectionA.Selection.CopyFrom(selectionA); tempTest.SelectionB.Selection.CopyFrom(selectionB); // 直接设置 PrimitiveTypes,避免后续的 CreateCopy 和 TestsEditTestFromCopy tempTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles; tempTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; _documentClash.TestsData.TestsAddCopy(tempTest); var addedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest; if (addedTempTest != null) { // 🔥 优化3:跳过了 addedTempTest.CreateCopy() 和 TestsEditTestFromCopy 调用 // 因为 PrimitiveTypes 已在创建 tempTest 时设置好 _documentClash.TestsData.TestsRunTest(addedTempTest); // 增加检测计数并打印进度日志(每50次更新一次进度条,每100次打印日志) testCount++; if (testCount % 50 == 0) { // 基于总检测次数计算进度(组进度占70%,检测进度占30%) double groupProgress = (double)(groupIndex + 1) / totalGroups * 0.7; double testProgress = (double)testCount / totalTests * 0.3; double totalProgress = Math.Min(groupProgress + testProgress, 0.99); progress?.Update(totalProgress); } if (testCount % 100 == 0) { double logProgressPercent = (double)testCount / totalTests * 100; LogManager.Info($"[ClashDetective进度] 已执行 {testCount}/{totalTests} 次检测 ({logProgressPercent:F1}%)"); } var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest; if (refreshedTempTest != null && refreshedTempTest.Children.Count > 0) { // !!!发现真实碰撞!!! pairConfirmed = true; confirmedCount++; skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量 // 🔥 关键:记录确认碰撞的候选帧位置信息,供后续使用 // 使用Item2作为key,因为最终去重也是按Item2合并的 var confirmedPositionKey = GetCollisionObjectKey(candidate.Item1, candidate.Item2); _confirmedCollisionPositions[confirmedPositionKey] = new CollisionPositionInfo { AnimatedObjectTrackedPosition = candidate.AnimatedObjectTrackedPosition, Item2Position = candidate.Item2Position, AnimatedObjectTrackedYawRadians = candidate.AnimatedObjectTrackedYawRadians, AnimatedObjectTrackedRotation = candidate.AnimatedObjectTrackedRotation, AnimatedObjectHasTrackedRotation = candidate.AnimatedObjectHasTrackedRotation, HasPositionInfo = candidate.HasPositionInfo }; LogManager.Debug($"[ClashDetective确认] 记录碰撞位置: {confirmedPositionKey}, 位置: ({candidate.AnimatedObjectTrackedPosition.X:F2}, {candidate.AnimatedObjectTrackedPosition.Y:F2}, {candidate.AnimatedObjectTrackedPosition.Z:F2})"); int subResultIndex = 1; // 🔥 优化:预获取运动物体名称,避免在循环中重复获取 var animatedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animatedObject); foreach (var child in refreshedTempTest.Children) { if (child is ClashResult result) { var copiedResult = result.CreateCopy() as ClashResult; copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID // 设置碰撞名称:运动物体名称直接使用预获取的,被撞物体查找父容器 var container2 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item2); var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(container2); var timeStamp = DateTime.Now.ToString("HHmmss"); copiedResult.DisplayName = $"物流碰撞#{confirmedCount:00}-{subResultIndex:00}_{timeStamp}: {animatedObjectName} ↔ {object2Name}"; collisionGroup.Children.Add(copiedResult); subResultIndex++; } } } // 🔥 优化4:收集测试用于批量删除,而不是立即删除 tempTestsToRemove.Add(refreshedTempTest ?? addedTempTest); if (pairConfirmed) { break; } } } catch (Exception itemEx) { LogManager.Error($"[ClashDetective] 候选检测失败: {itemEx.Message}"); } } } // 如果用户取消,直接返回,不执行后续操作(临时测试将在下方统一清理) if (wasCanceled) { LogManager.Info($"[分组测试] 检测已被用户取消,跳过结果处理和保存"); } // 🔥 优化4:批量删除所有临时测试 if (tempTestsToRemove.Count > 0) { LogManager.Debug($"[分组测试] 批量删除 {tempTestsToRemove.Count} 个临时测试"); foreach (var test in tempTestsToRemove) { try { _documentClash.TestsData.TestsRemove(test); } catch (Exception ex) { LogManager.Debug($"[分组测试] 删除临时测试失败: {ex.Message}"); } } } // 如果用户取消,直接返回,不执行后续操作 if (wasCanceled) { return (null, addedMainTest, confirmedCount, true); } // 完成进度条 progress?.Update(1.0); LogManager.Info($"[分组测试] ClashDetective检测完成: 执行 {testCount} 次检测, 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点"); // 第三步:处理碰撞结果 // 🔥 重要:ClashDetective 返回的是几何体级别的碰撞结果 // Item1 是移动物体的组件名(如"车轮1"、"车轮2"),需要统一为移动物体本身 // Item2 是被撞物体,通过PathId向上查找匹配预计算中的原始对象 var clashResults = new List(); foreach (var child in collisionGroup.Children) { if (child is ClashResult clashResult) { // 🔥 通过PathId向上查找匹配的原始对象 // 从ClashDetective返回的几何体组件开始,向上遍历父节点, // 直到找到与预计算记录中任一对象PathId匹配的父节点 ModelItem originalItem2 = FindMatchingObjectByPathId(clashResult.Item2, precomputedCollisions); if (originalItem2 == null) { originalItem2 = clashResult.Item2; // 回退到原始对象 } // 🔥 从确认碰撞位置字典中获取位置信息(ClashDetective实际验证通过的帧) var positionKey = GetCollisionObjectKey(animatedObject, originalItem2); _confirmedCollisionPositions.TryGetValue(positionKey, out var confirmedPosition); var collisionResult = new CollisionResult { ClashGuid = clashResult.Guid, DisplayName = clashResult.DisplayName, Status = clashResult.Status, Item1 = animatedObject, Item2 = originalItem2, Center = clashResult.Center, Distance = clashResult.Distance, CreatedTime = DateTime.Now, // 🔥 使用ClashDetective确认时的位置信息 AnimatedObjectTrackedPosition = confirmedPosition?.AnimatedObjectTrackedPosition, Item2Position = confirmedPosition?.Item2Position, AnimatedObjectTrackedYawRadians = confirmedPosition?.AnimatedObjectTrackedYawRadians ?? 0, AnimatedObjectTrackedRotation = confirmedPosition?.AnimatedObjectTrackedRotation, AnimatedObjectHasTrackedRotation = confirmedPosition?.AnimatedObjectHasTrackedRotation ?? false, HasPositionInfo = confirmedPosition != null }; clashResults.Add(collisionResult); } } // 🔥 新增:按碰撞对象对去重 // 原因:同一个移动物体与同一个被撞物体的多个组件碰撞,需要合并为一个记录 var finalClashResults = clashResults .GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 }) .Select(g => g.First()) .ToList(); // 🔥 日志:统计位置信息保留情况 var withPositionCount = finalClashResults.Count(c => c.HasPositionInfo && c.AnimatedObjectTrackedPosition != null); LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对,其中 {withPositionCount} 个包含位置信息"); // 🔥 优化:在去重后为结果设置详细的 DisplayName // 这样只对保留的结果进行名称计算,避免在子碰撞结果上浪费性能 foreach (var result in finalClashResults) { if (result.Item1 != null && result.Item2 != null) { var object1Name = ModelItemAnalysisHelper.GetSafeDisplayName(result.Item1); var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(result.Item2); result.DisplayName = $"物流碰撞: {object1Name} ↔ {object2Name}"; } } // 缓存最终结果 lock (_clashResultsCacheLock) { _clashDetectiveResultsCache[_currentTestName] = finalClashResults; } LogManager.Info($"[ClashDetective] 已缓存结果:{finalClashResults.Count}个碰撞,测试名称:{_currentTestName}"); // 更新碰撞计数器 _clashDetectiveCollisionCount = clashResults.Count; // 保存到数据库(使用去重后的结果,配置参数通过 detectionRecordId 关联获取) SaveClashDetectiveResultToDatabase(routeId, finalClashResults, precomputedCollisionCount, detectionRecordId); LogManager.Info($"[ClashDetective] 结果已保存到数据库"); // 第四步:将分组添加到主测试(由调用方完成) return (collisionGroup, addedMainTest, confirmedCount, false); } /// /// 动画结束后统一创建和运行ClashDetective碰撞测试 /// /// 预计算碰撞结果 /// 检测间隙容差 /// 路由ID /// 动画对象(如果是真实物体) /// 是否使用虚拟物体 /// 帧率 /// 动画时长 /// 虚拟物体长度(米) /// 虚拟物体宽度(米) /// 虚拟物体高度(米) /// 路径点列表(用于测试完成后恢复物体位置) /// /// 创建并运行ClashDetective碰撞测试 /// /// true = 检测完成,false = 用户取消 public bool CreateAllAnimationCollisionTests( List precomputedCollisions, double detectionGap, string routeId, ModelItem animatedObject, bool isVirtualObject, int frameRate, double duration, double virtualObjectLength, double virtualObjectWidth, double virtualObjectHeight, List pathPoints = null, int? detectionRecordId = null) { // 重置取消标志 _wasLastTestCanceled = false; try { LogManager.Info($"=== 使用预计算碰撞数据创建ClashDetective测试(容差: {detectionGap}米)==="); LogManager.Info($"[预计算数据] 共有 {precomputedCollisions.Count} 个碰撞记录"); // 检查预计算碰撞结果是否包含位置信息 var collisionsWithPosition = precomputedCollisions.Count(c => c.HasPositionInfo && c.AnimatedObjectTrackedPosition != null); LogManager.Info($"[预计算数据] 包含位置信息的碰撞: {collisionsWithPosition}/{precomputedCollisions.Count}"); // 直接使用所有预计算结果,只过滤有效对象(不去重) var collisionResults = precomputedCollisions .Where(result => IsModelItemValid(result.Item1) && IsModelItemValid(result.Item2)) .ToList(); // 记录预计算碰撞数量(作为参考值,不作为类状态) int precomputedCollisionCount = collisionResults.Count; LogManager.Info($"[预计算处理] 原始记录: {precomputedCollisions.Count},有效碰撞: {precomputedCollisionCount}"); // 获取动画对象 if (collisionResults.Count > 0) { animatedObject = collisionResults[0].Item1; } var doc = Application.ActiveDocument; // 调用公共方法运行ClashDetective测试并保存到数据库 Progress progress = Application.BeginProgress("碰撞检测数据分析中,请稍候..."); ClashTest addedMainTest = null; ClashResultGroup collisionGroup = null; int confirmedCount = 0; bool wasCanceled = false; try { var result = RunClashDetectiveTestsAndSaveToDatabase( precomputedCollisions, detectionGap, routeId, animatedObject, isVirtualObject, frameRate, duration, virtualObjectLength, virtualObjectWidth, virtualObjectHeight, progress, precomputedCollisionCount, detectionRecordId ); collisionGroup = result.collisionGroup; addedMainTest = result.addedMainTest; confirmedCount = result.confirmedCount; wasCanceled = result.wasCanceled; // 检查是否被取消 if (wasCanceled) { LogManager.Info("[CreateAllAnimationCollisionTests] 用户取消了碰撞检测"); // 设置取消标志 _wasLastTestCanceled = true; // 清空当前测试结果,避免取消后显示旧结果 ClearCurrentTestCache(); _currentTestName = null; _clashDetectiveCollisionCount = 0; // 恢复物体状态 if (animatedObject != null && IsModelItemValid(animatedObject)) { PathAnimationManager.GetInstance().RestoreAnimatedObjectState(animatedObject); } // 重置动画完成标志,允许重新运行动画 ResetAnimationCompletedFlag(routeId); } } finally { // 先激活 Navisworks 主窗口(避免关闭进度条后丢失焦点) try { var mainWindow = Autodesk.Navisworks.Api.Application.Gui.MainWindow; if (mainWindow != null) { // 使用 Windows API 激活窗口 SetForegroundWindow(mainWindow.Handle); LogManager.Debug("[ClashDetective] 已激活 Navisworks 主窗口"); } } catch (Exception ex) { LogManager.Debug($"[ClashDetective] 激活主窗口失败: {ex.Message}"); } // 关闭进度条 Application.EndProgress(); } // 如果取消,显示提示消息后返回 if (wasCanceled) { // 在 finally 之后显示消息对话框 ShowCancellationMessage(); return false; // 返回取消状态 } // UI操作:将分组添加到主测试 if (collisionGroup != null && addedMainTest != null && collisionGroup.Children.Count > 0) { _documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup); } else { LogManager.Warning("[分组测试] 分组为空(未检测到真实几何碰撞),未添加到主测试"); } // 恢复物体到碰撞检测前的状态 if (animatedObject != null && IsModelItemValid(animatedObject)) { PathAnimationManager.GetInstance().RestoreAnimatedObjectState(animatedObject); } // 检查是否成功创建了主测试 var finalMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName.Contains("碰撞检测")) as ClashTest; if (finalMainTest != null) { // 刷新Clash Detective窗口 RefreshClashDetectiveUI(); // 自动高亮ClashDetective结果 ModelHighlightHelper.HighlightClashDetectiveResults(_currentTestName, GetCurrentPathClashResults); LogManager.Info("自动高亮ClashDetective检测结果完成"); LogManager.Info($"=== 碰撞统计最终结果 ==="); LogManager.Info($"预计算碰撞检测点: {precomputedCollisionCount}个 (参考值)"); LogManager.Info($"Clash Detective权威结果: {_clashDetectiveCollisionCount}个碰撞 (权威数据)"); } // 🔥 无论是否有主测试,都触发碰撞检测完成事件,通知生成报告 // 使用ClashDetective权威结果,而不是预计算结果 // 如果权威结果为0个碰撞,传入空列表以触发祝贺对话框 var finalCollisions = _clashDetectiveCollisionCount > 0 ? collisionResults : new List(); LogManager.Info($"触发CollisionDetected事件,通知生成报告({_clashDetectiveCollisionCount}个碰撞,权威数据)"); var eventArgs = new CollisionDetectedEventArgs(finalCollisions); OnCollisionDetected(eventArgs); } catch (Exception ex) { LogManager.Error($"动画结束后创建测试失败: {ex.Message}"); } return !_wasLastTestCanceled; // 返回是否成功完成(未被取消) } /// /// 刷新Clash Detective UI - 安全版本 /// private void RefreshClashDetectiveUI() { try { // 检查文档是否有效 var doc = Application.ActiveDocument; if (doc == null || doc.IsClear) { LogManager.Warning("文档无效,跳过UI刷新"); return; } // 检查视图是否有效 if (doc.ActiveView != null) { try { doc.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All); LogManager.Info("Clash Detective UI已刷新"); } catch (ObjectDisposedException) { LogManager.Warning("视图对象已释放,跳过重绘"); } catch (Exception ex) { LogManager.Error($"重绘视图失败: {ex.Message}"); } } else { LogManager.Warning("活动视图无效,跳过UI刷新"); } } catch (ObjectDisposedException) { LogManager.Warning("文档或视图对象已释放,跳过UI刷新"); } catch (Exception ex) { LogManager.Error($"刷新Clash Detective UI失败: {ex.Message}"); } } /// /// 按 DisplayName 在模型树中查找唯一匹配对象(F1 副实例对象映射回主模型用)。 /// 多匹配(重名)返回 null,避免误配。 /// private static ModelItem TryFindSingleObjectByName(string displayName) { try { var document = Application.ActiveDocument; if (document == null || document.IsClear) { return null; } ModelItem found = null; foreach (var model in document.Models) { if (model.RootItem == null) { continue; } foreach (var item in model.RootItem.DescendantsAndSelf) { if (string.Equals(item.DisplayName, displayName, StringComparison.OrdinalIgnoreCase)) { if (found != null) { return null; // 重名 → 不匹配 } found = item; } } } return found; } catch { return null; } } /// /// 显示碰撞检测取消提示消息 /// private void ShowCancellationMessage() { // 自动模式下跳过取消提示(日志已记录,避免残留弹窗阻塞自动化) if (AutomationModeContext.IsAutomaticMode) { LogManager.Info("[ClashDetective] 自动模式下跳过检测取消提示框"); return; } try { // 获取 Navisworks 主窗口句柄作为父窗口 var mainWindow = System.Windows.Forms.Form.FromHandle( Autodesk.Navisworks.Api.Application.Gui.MainWindow.Handle); // 使用 Windows Forms 显示提示框,指定父窗口保持焦点 System.Windows.Forms.MessageBox.Show( mainWindow, "碰撞检测已取消。\n\n您可以重新运行动画来重新检测碰撞。", "检测取消", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information); LogManager.Info("[ClashDetective] 已显示取消提示消息"); } catch (Exception ex) { // 如果获取父窗口失败,使用默认方式 System.Windows.Forms.MessageBox.Show( "碰撞检测已取消。\n\n您可以重新运行动画来重新检测碰撞。", "检测取消", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information); LogManager.Info("[ClashDetective] 已显示取消提示消息(无父窗口)"); } } /// /// 重置动画完成标志,允许重新运行动画进行碰撞检测 /// /// 路径ID private void ResetAnimationCompletedFlag(string routeId) { try { // 清除动画配置哈希到记录ID的映射,允许重新检测 PathAnimationManager.ClearAllCollisionTestRecords(); LogManager.Info($"[ClashDetective] 已清除检测记录映射,允许重新检测。路径ID: {routeId}"); } catch (Exception ex) { LogManager.Error($"重置动画完成标志失败: {ex.Message}"); } } /// /// 检查ModelItem是否仍然有效 /// private bool IsModelItemValid(ModelItem item) { try { if (item == null) return false; // 尝试访问对象的属性来检查是否有效 var displayName = item.DisplayName; var hasGeometry = item.HasGeometry; // 额外检查:确保对象没有被释放 var boundingBox = item.BoundingBox(); return true; } catch (Exception ex) { LogManager.Debug($"ModelItem无效: {ex.Message}"); return false; } } /// /// 获取所有碰撞检测测试 /// /// 碰撞检测测试列表 public List GetClashTests() { if (_documentClash == null) { throw new InvalidOperationException("ClashDetective文档未初始化"); } return _documentClash.TestsData.Tests.Cast() .Where(t => t.DisplayName.Contains("碰撞检测")) .ToList(); } /// /// 获取当前路径的Clash结果 /// /// 路径名称 /// 当前路径的碰撞结果列表(如果没有结果返回空列表) public List GetCurrentPathClashResults(string testName) { lock (_clashResultsCacheLock) { // 先尝试从缓存获取 if (_clashDetectiveResultsCache.TryGetValue(testName, out var cachedResults)) { LogManager.Debug($"[ClashDetective结果] 从缓存获取 '{testName}' 的结果:{cachedResults.Count}个碰撞"); return cachedResults; } // 缓存中没有,尝试从数据库加载 LogManager.Info($"[ClashDetective结果] 缓存中没有 '{testName}',尝试从数据库加载"); var loadedResults = GetClashDetectiveResultsFromDatabase(testName); if (loadedResults != null && loadedResults.Count > 0) { // 缓存加载的结果 _clashDetectiveResultsCache[testName] = loadedResults; LogManager.Info($"[ClashDetective结果] 已从数据库加载 '{testName}' 的结果并缓存:{loadedResults.Count}个碰撞"); return loadedResults; } // 没有找到结果,返回空列表 LogManager.Info($"[ClashDetective结果] 未找到测试 '{testName}' 的碰撞结果,返回空列表"); return new List(); } } /// /// 获取当前测试的碰撞结果(便捷方法) /// /// 当前测试的碰撞结果列表 public List GetCurrentTestResults() { return GetCurrentPathClashResults(_currentTestName); } /// /// 清理当前测试的缓存 /// public void ClearCurrentTestCache() { if (string.IsNullOrEmpty(_currentTestName)) { return; } lock (_clashResultsCacheLock) { if (_clashDetectiveResultsCache.ContainsKey(_currentTestName)) { _clashDetectiveResultsCache.Remove(_currentTestName); LogManager.Info($"[ClashDetective缓存] 已清理当前测试 '{_currentTestName}' 的缓存"); } } } /// /// 清空取消标志 /// public void ClearWasLastTestCanceled() { if (_wasLastTestCanceled) { LogManager.Info("[ClashDetective] 清空取消标志"); _wasLastTestCanceled = false; } } /// /// 清空当前测试名称 /// public void ClearCurrentTestName() { if (!string.IsNullOrEmpty(_currentTestName)) { LogManager.Info($"[ClashDetective] 清空当前测试名称: {_currentTestName}"); _currentTestName = null; } } /// /// 从测试中提取碰撞结果 /// /// 测试 /// 碰撞结果列表 public List ExtractClashResultsFromTest(ClashTest clashTest) { var results = new List(); ExtractClashResultsFromTestRecursive(clashTest, results); return results; } /// /// 递归提取测试中的碰撞结果 /// /// 测试 /// 结果列表 private void ExtractClashResultsFromTestRecursive(ClashTest clashTest, List results) { foreach (var child in clashTest.Children) { if (child is ClashResult clashResult) { results.Add(clashResult); } else if (child is ClashResultGroup resultGroup) { ExtractClashResultsFromGroupRecursive(resultGroup, results); } } } /// /// 递归提取分组中的碰撞结果 /// /// 分组 /// 结果列表 private void ExtractClashResultsFromGroupRecursive(ClashResultGroup group, List results) { foreach (var child in group.Children) { if (child is ClashResult result) { results.Add(result); } else if (child is ClashResultGroup subGroup) { ExtractClashResultsFromGroupRecursive(subGroup, results); } } } /// /// 获取所有碰撞检测的碰撞结果 /// /// 碰撞结果列表 public List GetAllClashResults() { var tests = GetClashTests(); var allResults = new List(); foreach (var test in tests) { var results = ExtractClashResultsFromTest(test); allResults.AddRange(results); } return allResults; } /// /// 构建通道对象缓存,一次性扫描所有对象,避免重复的属性查询 /// public void BuildChannelObjectsCache() { lock (_cacheLock) { if (_channelObjectsCache != null) return; // 双重检查锁定 var cacheStopwatch = new System.Diagnostics.Stopwatch(); cacheStopwatch.Start(); _channelObjectsCache = new HashSet(); try { var document = Application.ActiveDocument; // 获取所有可通行的物流模型项 var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(); if (allChannelItems.Count == 0) { LogManager.Warning("[通道缓存] ⚠️ 未找到任何可通行的物流元素,请检查模型中的物流属性设置"); cacheStopwatch.Stop(); return; } // 🔥 优化:只提取复合对象(IsComposite = true) // 原因:通道对象通常是复合对象(如通道、楼梯、电梯) // 我们只需要检测复合对象,不需要检测其子节点 foreach (var channelItem in allChannelItems) { try { // 只添加复合对象 if (channelItem.IsComposite) { _channelObjectsCache.Add(channelItem); } else if (channelItem.HasGeometry) { // 非复合对象但有几何数据(独立实体节点):也添加 _channelObjectsCache.Add(channelItem); } } catch (Exception ex) { LogManager.Warning($"[通道收集] 处理通道节点 '{channelItem?.DisplayName ?? "NULL"}' 时出错: {ex.Message}"); // 出错时至少保证通道本身被添加 _channelObjectsCache.Add(channelItem); } } cacheStopwatch.Stop(); LogManager.Info($"通道对象缓存构建完成,耗时: {cacheStopwatch.ElapsedMilliseconds}ms"); LogManager.Info($" - 可通行物流根对象: {allChannelItems.Count} 个"); LogManager.Info($" - 缓存总对象数: {_channelObjectsCache.Count} 个"); } catch (Exception ex) { LogManager.Error($"构建通道对象缓存时发生错误: {ex.Message}", ex); _channelObjectsCache = new HashSet(); // 创建空缓存,避免重复构建 } } } /// /// 清除通道对象缓存,在模型变化时调用 /// public static void ClearChannelObjectsCache() { lock (_cacheLock) { _channelObjectsCache = null; LogManager.Debug("通道对象缓存已清除"); } } /// /// 设置移动物体(用于从空间索引中排除移动物体及其所有子节点) /// 🔥 优化:只更新运动物体后代集合,不重建基础缓存 /// /// 移动物体 public static void SetAnimatedObject(ModelItem animatedObject) { lock (_cacheLock) { _animatedObject = animatedObject; LogManager.Info($"移动物体已设置: {animatedObject?.DisplayName ?? "null"}"); // 🔥 优化:只重建运动物体后代集合,不清除基础缓存 _animatedObjectAndDescendants.Clear(); if (_animatedObject != null) { var sw = System.Diagnostics.Stopwatch.StartNew(); CollectAllDescendants(_animatedObject, _animatedObjectAndDescendants); sw.Stop(); LogManager.Info($"移动物体后代收集完成: {_animatedObjectAndDescendants.Count} 个对象,耗时 {sw.ElapsedMilliseconds}ms"); } // 🔥 关键:只清空派生缓存,不重建基础缓存 // 基础缓存 _allGeometryItemsCache 保持稳定 _nonChannelGeometryItemsCache = null; LogManager.Info("非通道几何对象缓存已标记为失效,将在下次访问时重新过滤"); } } /// /// 递归收集对象及其所有后代 /// private static void CollectAllDescendants(ModelItem item, HashSet collection) { if (item == null) return; collection.Add(item); // 限制递归深度,防止异常模型结构 try { foreach (var child in item.Children) { CollectAllDescendants(child, collection); } } catch (Exception ex) { LogManager.Warning($"收集对象 '{item.DisplayName}' 的后代时出错: {ex.Message}"); } } /// /// 清除移动物体引用(不清除基础缓存) /// 🔥 优化:用于虚拟物体切换时,只清除引用而不重建80秒的基础缓存 /// public static void ClearAnimatedObject() { lock (_cacheLock) { _animatedObject = null; _animatedObjectAndDescendants.Clear(); LogManager.Debug("移动物体引用已清除(基础缓存保留)"); } } /// /// 初始化碰撞检测缓存(用于预计算前的准备工作) /// 🔥 优化:基础缓存保持稳定,运动物体在查询阶段动态过滤 /// /// 移动物体 public static void InitializeCollisionDetectionCache(ModelItem animatedObject) { LogManager.Info("[碰撞检测] 开始初始化碰撞检测缓存..."); lock (_cacheLock) { // 1. 设置运动物体(使用优化后的方法) _animatedObject = animatedObject; LogManager.Info($"移动物体已设置: {_animatedObject?.DisplayName ?? "null"}"); // 2. 清空运动物体后代集合并重新收集 _animatedObjectAndDescendants.Clear(); if (_animatedObject != null) { CollectAllDescendants(_animatedObject, _animatedObjectAndDescendants); LogManager.Info($"运动物体后代收集完成: {_animatedObjectAndDescendants.Count} 个对象"); } // 3. 清空派生缓存(基础缓存保持稳定) _nonChannelGeometryItemsCache = null; // 4. 如果通道缓存不存在,清空它以便重建 if (_channelObjectsCache == null) { LogManager.Info("通道对象缓存不存在,将重新构建"); } } // 5. 构建基础缓存(如果不存在)- 使用双重检查锁定,不会重复构建 BuildNonHidddenGeometryItemsCache(); // 6. 构建通道对象缓存(如果不存在) if (_channelObjectsCache == null) { Instance.BuildChannelObjectsCache(); } LogManager.Info("[碰撞检测] 碰撞检测缓存初始化完成"); } /// /// 统一准备碰撞检测(整合常规处理和批处理的公共逻辑) /// 🔥 优化:基础缓存保持稳定,运动物体变化时快速更新 /// /// 运动物体 /// 是否为手工指定检测对象模式 /// 手工指定的检测目标(仅在手工模式下使用) /// 是否成功准备 public static bool PrepareCollisionDetection(ModelItem animatedObject, bool isManualMode, List manualTargets = null) { try { if (animatedObject == null) { LogManager.Warning("[碰撞检测] 准备失败:运动物体为空"); return false; } if (isManualMode) { // 手工模式:只设置移动物体,不构建全局缓存 LogManager.Info("[碰撞检测] 手工模式 - 跳过全局缓存初始化"); SetAnimatedObject(animatedObject); // 使用优化后的方法 return true; } else { // 全局模式:使用优化后的初始化 LogManager.Info("[碰撞检测] 全局模式 - 初始化碰撞检测缓存"); // 🔥 优化:如果基础缓存已存在,只更新运动物体 if (_allGeometryItemsCache != null && _channelObjectsCache != null) { LogManager.Info("[碰撞检测] 基础缓存已存在,仅更新运动物体"); SetAnimatedObject(animatedObject); return true; } // 否则执行完整初始化 InitializeCollisionDetectionCache(animatedObject); return true; } } catch (Exception ex) { LogManager.Error($"[碰撞检测] 准备碰撞检测失败: {ex.Message}"); return false; } } /// /// 获取通道对象缓存(供外部使用) /// /// 通道对象集合,如果缓存不存在则返回null public static HashSet GetChannelObjectsCache() { lock (_cacheLock) { return _channelObjectsCache; } } /// /// 构建几何对象列表缓存,一次性获取非隐藏几何对象 /// public static void BuildNonHidddenGeometryItemsCache() { lock (_cacheLock) { if (_allGeometryItemsCache != null) return; // 双重检查锁定 var cacheStopwatch = new System.Diagnostics.Stopwatch(); cacheStopwatch.Start(); try { // 优化方案:递归遍历 (Top-Down Traversal) // 原因:Search API 的 Hidden 属性不检查父级可见性,且 IsHidden 属性检查自身。 // 解决方案:统一走 ModelItemTreeWalker 深度遍历,如果遭遇 Hidden 节点则停止深入该分支。 // 这天然地利用了 Navisworks 的层级可见性规则。 _allGeometryItemsCache = new ModelItemCollection(); int visibleCount = 0; ModelItemTreeWalker.WalkModels( Application.ActiveDocument.Models, item => { // 🔥 优化:只提取复合对象(IsComposite = true)或独立实体节点(HasGeometry) // 原因:复合对象(如窗户)包含多个实体节点(玻璃、框架、把手) // 我们只需要检测复合对象,不需要检测其子节点 // Clash Detective API 会自动处理复合对象的子节点几何体 if (item.IsComposite) { // 复合对象:添加到缓存,不继续遍历子节点 _allGeometryItemsCache.Add(item); visibleCount++; return ModelItemWalkAction.Prune; } if (item.HasGeometry) { // 非复合对象但有几何数据(独立实体节点):添加到缓存 _allGeometryItemsCache.Add(item); visibleCount++; return ModelItemWalkAction.Prune; } // 空节点:继续遍历子节点 return ModelItemWalkAction.Continue; }); cacheStopwatch.Stop(); LogManager.Info($"复合对象列表缓存构建完成 (DFS遍历 + 自动剪枝),耗时: {cacheStopwatch.ElapsedMilliseconds}ms"); LogManager.Info($" - 缓存可见对象: {visibleCount}"); LogManager.Info($" - 策略: 深度优先搜索 (父节点隐藏则跳过分支)"); } catch (Exception ex) { LogManager.Error($"构建几何对象列表缓存时发生错误: {ex.Message}", ex); _allGeometryItemsCache = new ModelItemCollection(); // 创建空缓存,避免重复构建 } } } /// /// 获取复合对象缓存(供外部使用) /// /// 复合对象集合,如果缓存不存在则返回null public static ModelItemCollection GetAllGeometryItemsCache() { lock (_cacheLock) { return _allGeometryItemsCache; } } /// /// 获取已排除通道对象的几何对象缓存(供空间索引使用) /// /// 排除通道后的几何对象列表,如果缓存不存在则返回null public static List GetNonChannelGeometryItemsCache() { lock (_cacheLock) { // 如果已经有过滤后的缓存,直接返回 if (_nonChannelGeometryItemsCache != null) { return _nonChannelGeometryItemsCache; } // 如果原始缓存不存在,返回 null if (_allGeometryItemsCache == null || _channelObjectsCache == null) { return null; } // 第一次调用:构建并缓存过滤后的列表 LogManager.Info($"[空间索引] 构建非通道几何对象缓存..."); var sw = System.Diagnostics.Stopwatch.StartNew(); // 🔥 优化:动态排除通道对象和运动物体(及其后代) _nonChannelGeometryItemsCache = _allGeometryItemsCache .Where(item => !_channelObjectsCache.Contains(item)) .Where(item => !_animatedObjectAndDescendants.Contains(item)) // 🔥 动态排除运动物体 .ToList(); sw.Stop(); LogManager.Info($"[空间索引] 非通道几何对象缓存构建完成,耗时: {sw.ElapsedMilliseconds}ms,对象数: {_nonChannelGeometryItemsCache.Count}"); return _nonChannelGeometryItemsCache; } } /// /// 清除所有缓存,在模型变化时调用 /// public static void ClearAllCaches() { lock (_cacheLock) { _channelObjectsCache = null; _allGeometryItemsCache = null; _nonChannelGeometryItemsCache = null; _animatedObject = null; // 清除移动物体引用 _animatedObjectAndDescendants.Clear(); // 清除运动物体后代集合 } } /// /// 清理碰撞缓存 /// public void ClearCollisionCache() { try { LogManager.Info("[ClashDetectiveIntegration] 清理碰撞缓存"); // 清空当前碰撞列表 _currentCollisions?.Clear(); // 清空缓存的结果 lock (_resultsLock) { _deduplicatedCollisionResults?.Clear(); } // 清除对象缓存 ClearAllCaches(); // 重置权威碰撞计数器(预计算计数是局部变量,无需重置) _clashDetectiveCollisionCount = 0; LogManager.Info("[ClashDetectiveIntegration] 碰撞缓存已清理"); } catch (Exception ex) { LogManager.Error($"[ClashDetectiveIntegration] 清理碰撞缓存失败: {ex.Message}"); } } /// /// 清理资源 /// public void Cleanup() { try { LogManager.Info("开始Clash Detective集成资源清理..."); // 检查文档是否仍然有效,如果无效则跳过操作 var document = Application.ActiveDocument; if (document == null || document.IsClear) { LogManager.Info("文档已无效,跳过资源清理操作"); return; } try { ModelHighlightHelper.ClearAllHighlights(); LogManager.Info("已清除临时材质高亮"); } catch (Exception ex) { LogManager.Warning($"清除临时材质失败: {ex.Message}"); } // 清空内存中的结果 _currentCollisions?.Clear(); lock (_resultsLock) { _deduplicatedCollisionResults?.Clear(); } // 清理.NET API引用 _documentClash = null; LogManager.Info("Clash Detective集成资源清理完成"); } catch (Exception ex) { LogManager.Error($"清理Clash Detective资源时发生异常: {ex.Message}"); } } /// /// 批处理版本:创建并运行ClashDetective测试(不触发UI操作) /// 此方法专门用于批处理场景,不显示进度条,不触发UI刷新 /// public string CreateAllAnimationCollisionTestsForBatch( List precomputedCollisions, double detectionGap, string routeId, ModelItem animatedObject, bool isVirtualObject, int frameRate, double duration, double virtualObjectLength, double virtualObjectWidth, double virtualObjectHeight, List pathPoints = null, int? detectionRecordId = null) { try { LogManager.Info($"[批处理] 使用预计算碰撞数据创建ClashDetective测试(容差: {detectionGap}米)"); LogManager.Info($"[批处理] 共有 {precomputedCollisions.Count} 个碰撞记录"); // 记录预计算碰撞数量(作为参考值,不作为类状态) int precomputedCollisionCount = precomputedCollisions.Count; LogManager.Info($"[批处理] 预计算碰撞: {precomputedCollisionCount}"); // 调用公共方法运行ClashDetective测试并保存到数据库 var result = RunClashDetectiveTestsAndSaveToDatabase( precomputedCollisions, detectionGap, routeId, animatedObject, isVirtualObject, frameRate, duration, virtualObjectLength, virtualObjectWidth, virtualObjectHeight, null, precomputedCollisionCount, detectionRecordId ); var collisionGroup = result.collisionGroup; var addedMainTest = result.addedMainTest; var confirmedCount = result.confirmedCount; // 🔥 重要:更新ClashDetective碰撞计数为确认的碰撞数 _clashDetectiveCollisionCount = confirmedCount; LogManager.Info($"[批处理] 更新ClashDetective碰撞计数: {confirmedCount}"); // 添加分组到主测试(使用 TestsAddCopy 方法) if (collisionGroup != null && addedMainTest != null && collisionGroup.Children.Count > 0) { _documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup); LogManager.Info($"[批处理] 碰撞分组已添加到主测试,共 {confirmedCount} 个碰撞"); } else { LogManager.Warning("[批处理] 分组为空(未检测到真实几何碰撞),未添加到主测试"); } // 碰撞测试完成后,不再将物体恢复到路径起点(客户要求保持最终位置) // if (animatedObject != null && IsModelItemValid(animatedObject) && pathPoints != null && pathPoints.Count > 0) // { // try // { // PathAnimationManager.GetInstance().MoveObjectToPathStart(animatedObject, pathPoints); // LogManager.Info($"[批处理] 已将 {animatedObject.DisplayName} 恢复到路径起点位置"); // } // catch (Exception restoreEx) // { // LogManager.Error($"[批处理] 恢复物体到路径起点失败: {restoreEx.Message}"); // } // } return addedMainTest?.DisplayName; } catch (Exception ex) { LogManager.Error($"[批处理] 创建ClashDetective测试失败: {ex.Message}"); return null; } } /// /// 触发碰撞检测事件 /// private void OnCollisionDetected(CollisionDetectedEventArgs e) { CollisionDetected?.Invoke(this, e); } } /// /// 碰撞结果数据结构 /// public class CollisionResult : IEquatable { public Guid ClashGuid { get; set; } public string DisplayName { get; set; } public ClashResultStatus Status { get; set; } public string GridLocation { get; set; } public ModelItem Item1 { get; set; } public ModelItem Item2 { get; set; } public Point3D Center { get; set; } public double Distance { get; set; } public DateTime CreatedTime { get; set; } // 位置和朝向信息用于还原碰撞场景 public Point3D AnimatedObjectTrackedPosition { get; set; } public Point3D Item2Position { get; set; } public double AnimatedObjectTrackedYawRadians { get; set; } // 动画跟踪点对应的运动物体朝向(弧度) public Rotation3D AnimatedObjectTrackedRotation { get; set; } // 动画跟踪点对应的运动物体完整三维姿态 public bool AnimatedObjectHasTrackedRotation { get; set; } public bool HasPositionInfo { get; set; } // IEquatable 实现:基于碰撞对象进行去重 public bool Equals(CollisionResult other) { if (other == null) return false; // 使用 ModelItem.Equals 比较底层原生对象 bool item1Equal = (Item1 == null && other.Item1 == null) || (Item1 != null && Item1.Equals(other.Item1)); bool item2Equal = (Item2 == null && other.Item2 == null) || (Item2 != null && Item2.Equals(other.Item2)); return item1Equal && item2Equal; } public override bool Equals(object obj) { return Equals(obj as CollisionResult); } public override int GetHashCode() { unchecked { int hash = 17; hash = hash * 31 + (Item1?.GetHashCode() ?? 0); hash = hash * 31 + (Item2?.GetHashCode() ?? 0); return hash; } } } /// /// 碰撞检测事件参数 /// public class CollisionDetectedEventArgs : EventArgs { public List Results { get; private set; } public int CollisionCount { get; private set; } public CollisionDetectedEventArgs(List results) { Results = results; CollisionCount = results.Count; } } /// /// ClashDetective结果保存事件参数 /// public class ClashDetectiveResultSavedEventArgs : EventArgs { public string PathName { get; private set; } public string TestName { get; private set; } public int CollisionCount { get; private set; } public ClashDetectiveResultSavedEventArgs(string pathName, string testName, int collisionCount) { PathName = pathName; TestName = testName; CollisionCount = collisionCount; } } }