diff --git a/.claude/settings.local.json b/.claude/settings.local.json index fa823c5..00fd7f1 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -69,6 +69,65 @@ "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\examples\\PlugIns\\ClashDetective\\SimpleUI/**)", "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\examples\\PlugIns\\ClashDetective\\SimpleUI/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\examples\\PlugIns\\ClashDetective\\ClashGrouper/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\examples\\PlugIns\\ClashDetective\\ClashGrouper/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\examples\\PlugIns\\ClashDetective\\ClashGrouper/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\UI\\WPF\\ViewModels/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Commands/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\documentation\\NET API\\html/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\examples\\PlugIns\\ClashDetective\\ClashGrouper/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", + "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)", "Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)" ], "deny": [], diff --git a/src/Commands/ViewCollisionReportCommand.cs b/src/Commands/ViewCollisionReportCommand.cs index 363c628..11977aa 100644 --- a/src/Commands/ViewCollisionReportCommand.cs +++ b/src/Commands/ViewCollisionReportCommand.cs @@ -116,9 +116,10 @@ namespace NavisworksTransport.Commands var collisionData = CollectAllCollisionData(); result.AllCollisions = collisionData.AllCollisions; - // 直接使用收集到的统计数字,无需参数传递! - result.AnimationCollisions = collisionData.BoundingBoxTestCount; // 包围盒测试数量 - result.IndependentCollisions = collisionData.ClashDetectiveCollisionCount; // Clash Detective结果数量 + // 🔧 修复:使用正确的统计数据源 + var clashIntegration = ClashDetectiveIntegration.Instance; + result.AnimationCollisions = clashIntegration?.AnimationCollisionCount ?? 0; // 动画过程缓存的碰撞数量 + result.IndependentCollisions = collisionData.ClashDetectiveCollisionCount; // Clash Detective权威结果数量 result.TotalCollisions = collisionData.ClashDetectiveCollisionCount; // 总数以权威结果为准 result.MovingObjectInfo = collisionData.MovingObjectInfo; // 运动物体信息 @@ -200,24 +201,45 @@ namespace NavisworksTransport.Commands return result; } - int animationTestCount = 0; // 包围盒测试数量 int totalCollisionCount = 0; // Clash Detective总碰撞数 - // 遍历所有测试,区分动画测试和统计信息 - foreach (var test in documentClash.TestsData.Tests) + // 🔧 修复:只统计最新的物流路径碰撞汇总测试,避免重复累积 + var latestTest = documentClash.TestsData.Tests.Cast() + .Where(t => t.DisplayName.StartsWith("物流路径碰撞汇总_")) + .OrderByDescending(t => t.DisplayName) + .FirstOrDefault(); + + if (latestTest != null) { - if (test is ClashTest clashTest) + LogManager.Info($"找到最新的物流路径测试: {latestTest.DisplayName}"); + + // 提取最新测试的碰撞数据 + var testCollisions = ExtractCollisionsFromTest(latestTest); + allCollisions.AddRange(testCollisions); + + // 统计最新测试的碰撞总数 + totalCollisionCount = GetTotalClashResultCountFromTest(latestTest); + LogManager.Debug($"最新测试 '{latestTest.DisplayName}' 包含 {totalCollisionCount} 个碰撞"); + } + else + { + LogManager.Info("未找到物流路径碰撞汇总测试"); + + // 备用方案:收集所有动画相关测试(兼容旧版本) + foreach (var test in documentClash.TestsData.Tests) { - var testCollisions = ExtractCollisionsFromTest(clashTest); - allCollisions.AddRange(testCollisions); - - // 统计信息:如果是动画相关测试,记录包围盒测试数量 - if (clashTest.DisplayName.Contains("动画路径碰撞")) + if (test is ClashTest clashTest) { - animationTestCount = documentClash.TestsData.Tests.Cast() - .Where(t => t.DisplayName.Contains("动画路径碰撞")) - .Count(); - totalCollisionCount += clashTest.Children.Count; + if (clashTest.DisplayName.Contains("动画路径碰撞") || + clashTest.DisplayName.Contains("动画碰撞")) + { + var testCollisions = ExtractCollisionsFromTest(clashTest); + allCollisions.AddRange(testCollisions); + + var testTotalCount = GetTotalClashResultCountFromTest(clashTest); + totalCollisionCount += testTotalCount; + LogManager.Debug($"备用统计 - 测试 '{clashTest.DisplayName}' 包含 {testTotalCount} 个碰撞"); + } } } } @@ -229,10 +251,10 @@ namespace NavisworksTransport.Commands .ToList(); result.AllCollisions = uniqueCollisions; - result.BoundingBoxTestCount = animationTestCount; - result.ClashDetectiveCollisionCount = totalCollisionCount > 0 ? totalCollisionCount : uniqueCollisions.Count; + result.BoundingBoxTestCount = 0; // 不再在这里统计,由ClashDetectiveIntegration提供 + result.ClashDetectiveCollisionCount = totalCollisionCount; - LogManager.Info($"收集完成 - 包围盒测试: {result.BoundingBoxTestCount}个, Clash Detective: {result.ClashDetectiveCollisionCount}个, 去重后碰撞: {uniqueCollisions.Count}个"); + LogManager.Info($"收集完成 - Clash Detective权威结果: {result.ClashDetectiveCollisionCount}个, 去重后碰撞对象: {uniqueCollisions.Count}个"); return result; } @@ -304,6 +326,58 @@ namespace NavisworksTransport.Commands return results; } + /// + /// 递归统计测试中的碰撞结果总数(包括分组内的结果) + /// + /// 要统计的测试 + /// 碰撞结果总数 + private int GetTotalClashResultCountFromTest(ClashTest test) + { + if (test == null) return 0; + + int totalCount = 0; + foreach (var child in test.Children) + { + if (child is ClashResult) + { + totalCount++; + } + else if (child is ClashResultGroup group) + { + // 递归统计分组内的结果 + totalCount += CountClashResultsInGroup(group); + } + } + + return totalCount; + } + + /// + /// 递归统计分组内的碰撞结果数量 + /// + /// 分组 + /// 分组内的碰撞结果总数 + private int CountClashResultsInGroup(ClashResultGroup group) + { + if (group == null) return 0; + + int count = 0; + foreach (var child in group.Children) + { + if (child is ClashResult) + { + count++; + } + else if (child is ClashResultGroup nestedGroup) + { + // 递归处理嵌套分组 + count += CountClashResultsInGroup(nestedGroup); + } + } + + return count; + } + /// /// 生成报告内容 /// @@ -331,9 +405,12 @@ namespace NavisworksTransport.Commands // 总体统计 report.AppendLine("=== 总体统计 ==="); - // 使用收集到的统计数字 - report.AppendLine($"包围盒测试发现: {collisionData.BoundingBoxTestCount}个碰撞 (快速粗略检测结果)"); - report.AppendLine($"Clash Detective确认: {collisionData.ClashDetectiveCollisionCount}个碰撞 (精确权威检测结果)"); + // 🔧 修复:获取正确的动画碰撞数量 + var clashIntegration = ClashDetectiveIntegration.Instance; + var animationCollisionCount = clashIntegration?.AnimationCollisionCount ?? 0; + + report.AppendLine($"动画过程检测: {animationCollisionCount}个碰撞 (包围盒快速检测结果)"); + report.AppendLine($"Clash Detective权威结果: {collisionData.ClashDetectiveCollisionCount}个碰撞 (精确几何体检测结果)"); report.AppendLine($"总碰撞数量: {collisionData.ClashDetectiveCollisionCount}个 (以权威结果为准)"); report.AppendLine(); diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs index c81494f..68bf6e6 100644 --- a/src/Core/Collision/ClashDetectiveIntegration.cs +++ b/src/Core/Collision/ClashDetectiveIntegration.cs @@ -111,9 +111,6 @@ namespace NavisworksTransport { LogManager.Info("成功获取.NET API Clash文档"); - // 创建动态碰撞检测测试 - SetupDynamicClashTest(); - _isInitialized = true; if (_clashElement != null) @@ -177,86 +174,6 @@ namespace NavisworksTransport } } - /// - /// 设置动态碰撞检测测试 - /// - private void SetupDynamicClashTest() - { - try - { - // 清理所有动画相关的测试(包括之前创建的"动画路径碰撞_X"测试) - var existingAnimationTests = _documentClash.TestsData.Tests - .Where(t => t.DisplayName.StartsWith("动态运输路径碰撞检测") || - t.DisplayName.StartsWith("动画路径碰撞")) - .ToList(); - - if (existingAnimationTests.Any()) - { - LogManager.Info($"发现 {existingAnimationTests.Count} 个需要清理的动画测试"); - int removedCount = 0; - - foreach (var test in existingAnimationTests) - { - if (test is ClashTest testToRemove) - { - try - { - _documentClash.TestsData.TestsRemove(testToRemove); - removedCount++; - LogManager.Debug($"已删除测试: {test.DisplayName}"); - } - catch (Exception ex) - { - LogManager.Warning($"删除测试失败 {test.DisplayName}: {ex.Message}"); - } - } - } - - LogManager.Info($"成功清理了 {removedCount} 个旧的动画碰撞测试"); - } - else - { - LogManager.Info("未发现需要清理的旧动画测试"); - } - - // 创建新的碰撞测试 - _dynamicClashTest = new ClashTest(); - _dynamicClashTest.DisplayName = "动态运输路径碰撞检测"; - _dynamicClashTest.TestType = ClashTestType.Hard; - _dynamicClashTest.Tolerance = 0.01; // 1cm容差 - - // 设置几何类型:包含面、线和点以获得最全面的碰撞检测 - _dynamicClashTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; - _dynamicClashTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; - - LogManager.Debug("动态碰撞测试几何类型设置: 包含面、线、点"); - - // 设置测试规则 - 移除在2017版本中不可用的规则设置 - // _dynamicClashTest.Rules.Add(ClashRule.IgnoreWithinSameFile); - // _dynamicClashTest.Rules.Add(ClashRule.IgnoreWithinSameLayer); - - // 添加到文档,并获取返回的测试对象 - _documentClash.TestsData.TestsAddCopy(_dynamicClashTest); - - // 重新获取添加后的测试对象 - var addedTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); - if (addedTest is ClashTest clashTest) - { - _dynamicClashTest = clashTest; - LogManager.Info("动态碰撞检测测试创建成功,使用返回的测试对象"); - } - else - { - LogManager.Warning("无法获取添加后的测试对象,继续使用原始对象"); - } - - LogManager.Info("动态碰撞检测测试创建成功"); - } - catch (Exception ex) - { - LogManager.Error($"设置动态碰撞检测测试失败: {ex.Message}"); - } - } /// /// 执行动态碰撞检测 @@ -330,7 +247,7 @@ namespace NavisworksTransport private DateTime _lastCollisionTestTime = DateTime.MinValue; private readonly TimeSpan _minTestInterval = TimeSpan.FromMilliseconds(500); // 最小间隔500ms - /// + /// /// 缓存碰撞结果(动画过程中使用)- 现在包含位置信息用于恢复测试 /// 使用精确的碰撞检测算法替代简化检测 /// @@ -492,6 +409,10 @@ namespace NavisworksTransport { LogManager.Info($"=== 动画结束,开始创建ClashDetective碰撞测试(容差: {detectionGap}米) ==="); + // 🔧 修复:在处理前记录动画过程中的碰撞数量 + _animationCollisionCount = _cachedResults.Count; + LogManager.Info($"记录动画过程碰撞数量: {_animationCollisionCount}"); + if (_documentClash == null || _cachedResults.Count == 0) { LogManager.Info($"没有缓存的碰撞结果需要处理 - _documentClash: {_documentClash != null}, _cachedResults.Count: {_cachedResults.Count}"); @@ -597,182 +518,261 @@ namespace NavisworksTransport LogManager.Debug($"[去重分析-唯一{i+1:00}] 去重键: Item1GUID='{key1Guid}', Item2GUID='{key2Guid}'"); } - int createdCount = 1; // 从1开始编号 var doc = Autodesk.Navisworks.Api.Application.ActiveDocument; - // 使用位置恢复方案:为每个碰撞单独创建测试并恢复位置 - LogManager.Info("=== 开始位置恢复方案:为每个碰撞恢复对象位置 ==="); + // 使用分组方案:创建一个主测试和一个包含所有碰撞结果的组 + LogManager.Info("=== 开始分组方案:创建主测试和碰撞分组 ==="); - foreach (var collisionGroup in uniqueCollisions) + // 过滤有效的碰撞 + var validCollisions = uniqueCollisions.Where(cGroup => { - try + var collision = cGroup.Collision; + return collision.HasPositionInfo && + IsModelItemValid(collision.Item1) && + IsModelItemValid(collision.Item2); + }).ToList(); + + if (validCollisions.Count == 0) + { + LogManager.Warning("没有有效的碰撞可以创建测试"); + return; + } + + LogManager.Info($"[分组测试] 有效碰撞数量: {validCollisions.Count}"); + + // 🔧 修复:清理之前的物流路径碰撞汇总测试,避免重复累积 + LogManager.Info("[分组测试] 开始清理历史测试..."); + try + { + var oldTests = _documentClash.TestsData.Tests.Cast() + .Where(t => t.DisplayName.StartsWith("物流路径碰撞汇总_")) + .ToList(); + + if (oldTests.Count > 0) { - var collision = collisionGroup.Collision; + LogManager.Info($"[分组测试] 找到 {oldTests.Count} 个历史测试需要清理"); - // 检查是否有位置信息 - if (!collision.HasPositionInfo) + // 从后往前删除,避免索引变化问题 + for (int i = oldTests.Count - 1; i >= 0; i--) { - LogManager.Warning($"跳过无位置信息的碰撞: {collision.Item1?.DisplayName} <-> {collision.Item2?.DisplayName}"); - continue; - } - - // 确保对象仍然有效 - if (!IsModelItemValid(collision.Item1) || !IsModelItemValid(collision.Item2)) - { - LogManager.Warning($"跳过无效对象: {collision.Item1?.DisplayName} <-> {collision.Item2?.DisplayName}"); - continue; - } - - var testName = $"动画路径碰撞_{createdCount:0}_{DateTime.Now:HHmmss}"; - - LogManager.Info($"=== [测试创建-{createdCount:00}] 开始创建测试: {testName} ==="); - LogManager.Info($"[测试创建-{createdCount:00}] 动画对象: {collision.Item1?.DisplayName ?? "NULL"}"); - LogManager.Info($"[测试创建-{createdCount:00}] 碰撞对象: {collision.Item2?.DisplayName ?? "NULL"}"); - LogManager.Info($"[测试创建-{createdCount:00}] 动画对象位置: ({collision.Item1Position.X:F3},{collision.Item1Position.Y:F3},{collision.Item1Position.Z:F3})"); - LogManager.Info($"[测试创建-{createdCount:00}] 碰撞对象位置: ({collision.Item2Position.X:F3},{collision.Item2Position.Y:F3},{collision.Item2Position.Z:F3})"); - LogManager.Info($"[测试创建-{createdCount:00}] 原始距离: {collision.Distance:F4}"); - LogManager.Info($"[测试创建-{createdCount:00}] 创建时间: {collision.CreatedTime:HH:mm:ss.fff}"); - LogManager.Info($"[测试创建-{createdCount:00}] 使用容差: {detectionGap}米"); - - // 将对象移动到碰撞位置 - var testAnimatedObject = collision.Item1; - var modelItems = new ModelItemCollection { testAnimatedObject }; - var targetPosition = collision.Item1Position; - - // 计算当前位置到目标位置的偏移 - var currentBounds = testAnimatedObject.BoundingBox(); - var currentPos = new Point3D( - (currentBounds.Min.X + currentBounds.Max.X) / 2, - (currentBounds.Min.Y + currentBounds.Max.Y) / 2, - (currentBounds.Min.Z + currentBounds.Max.Z) / 2 - ); - - var offset = new Vector3D( - targetPosition.X - currentPos.X, - targetPosition.Y - currentPos.Y, - targetPosition.Z - currentPos.Z - ); - - var transform = Transform3D.CreateTranslation(offset); - doc.Models.OverridePermanentTransform(modelItems, transform, false); - - LogManager.Info($"[测试创建-{createdCount:00}] 已将 {testAnimatedObject.DisplayName} 移动到碰撞位置: ({targetPosition.X:F3},{targetPosition.Y:F3},{targetPosition.Z:F3})"); - LogManager.Debug($"[测试创建-{createdCount:00}] 移动偏移量: ({offset.X:F3},{offset.Y:F3},{offset.Z:F3})"); - - try - { - // 创建新的碰撞测试 - var collisionTest = new ClashTest - { - DisplayName = testName, - TestType = ClashTestType.HardConservative, // 保守硬碰撞 - Tolerance = detectionGap, // 使用传入的检测间隙参数(保守硬碰撞,建议为0,结果准确) - Guid = Guid.Empty - }; - - - LogManager.Debug($"[测试创建-{createdCount:00}] 几何类型设置: 包含面、线、点"); - - // 设置选择集A - var selectionA = new ModelItemCollection - { - collision.Item1 - }; - - // 设置几何类型:包含面、线和点以获得最全面的碰撞检测 - collisionTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; - collisionTest.SelectionA.Selection.CopyFrom(selectionA); - LogManager.Debug($"[测试创建-{createdCount:00}] 选择集A设置: {collision.Item1?.DisplayName ?? "NULL"} (数量: {selectionA.Count})"); - - // 设置选择集B - var selectionB = new ModelItemCollection - { - collision.Item2 - }; - - collisionTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; - collisionTest.SelectionB.Selection.CopyFrom(selectionB); - LogManager.Debug($"[测试创建-{createdCount:00}] 选择集B设置: {collision.Item2?.DisplayName ?? "NULL"} (数量: {selectionB.Count})"); - + var oldTest = oldTests[i]; try { - _documentClash.TestsData.TestsAddCopy(collisionTest); + _documentClash.TestsData.TestsRemove(oldTest); + LogManager.Info($"[分组测试] 已删除历史测试: {oldTest.DisplayName}"); } - catch (Exception addEx) + catch (Exception testDeleteEx) { - LogManager.Error($"添加测试失败: {addEx.GetType().Name}: {addEx.Message}"); - throw; + LogManager.Warning($"[分组测试] 删除测试失败: {oldTest.DisplayName} - {testDeleteEx.Message}"); } + } + } + else + { + LogManager.Info("[分组测试] 未发现需要清理的历史测试"); + } + } + catch (Exception cleanupEx) + { + LogManager.Warning($"[分组测试] 清理历史测试时出现警告: {cleanupEx.Message}"); + } + + // 创建主测试名称 + var mainTestName = $"物流路径碰撞汇总_{DateTime.Now:yyyyMMdd_HHmmss}"; + LogManager.Info($"[分组测试] 创建主测试: {mainTestName}"); + + try + { + // 使用事务确保操作的原子性 + using (var transaction = doc.BeginTransaction("创建分组碰撞测试")) + { + // 第一步:创建主测试(不包含选择集,纯容器) + var mainTest = new ClashTest + { + DisplayName = mainTestName, + TestType = ClashTestType.HardConservative, + Tolerance = detectionGap, + Guid = Guid.Empty + }; + + // 添加主测试到文档 + _documentClash.TestsData.TestsAddCopy(mainTest); + LogManager.Info($"[分组测试] 主测试已添加到文档"); + + // 获取添加后的测试对象引用 + var addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest; + if (addedMainTest == null) + { + LogManager.Error("无法获取添加后的主测试对象"); + return; + } + + // 第二步:创建分组并添加碰撞结果 + var collisionGroup = new ClashResultGroup + { + DisplayName = $"动画路径碰撞组 ({validCollisions.Count} 个碰撞点位)" + }; + + LogManager.Info($"[分组测试] 创建碰撞分组: {collisionGroup.DisplayName}"); + + int resultCount = 0; + foreach (var collisionData in validCollisions) + { + var collision = collisionData.Collision; + resultCount++; - // 获取添加后的测试并运行 - var addedTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == testName) as ClashTest; - - if (addedTest != null) + try { - try + LogManager.Info($"[分组测试-{resultCount:00}] 处理碰撞: {collision.Item1?.DisplayName} <-> {collision.Item2?.DisplayName}"); + + // 临时移动动画对象到碰撞位置以执行测试 + var testAnimatedObject = collision.Item1; + var modelItems = new ModelItemCollection { testAnimatedObject }; + var targetPosition = collision.Item1Position; + + // 计算移动偏移 + var currentBounds = testAnimatedObject.BoundingBox(); + var currentPos = new Point3D( + (currentBounds.Min.X + currentBounds.Max.X) / 2, + (currentBounds.Min.Y + currentBounds.Max.Y) / 2, + (currentBounds.Min.Z + currentBounds.Max.Z) / 2 + ); + + var offset = new Vector3D( + targetPosition.X - currentPos.X, + targetPosition.Y - currentPos.Y, + targetPosition.Z - currentPos.Z + ); + + var transform = Transform3D.CreateTranslation(offset); + doc.Models.OverridePermanentTransform(modelItems, transform, false); + + // 创建临时测试以获取真实碰撞结果 + var tempTestName = $"临时测试_{resultCount}_{DateTime.Now:HHmmss_fff}"; + var tempTest = new ClashTest { - LogManager.Info($"[测试创建-{createdCount:00}] 开始运行测试..."); - _documentClash.TestsData.TestsRunTest(addedTest); - - // 重新获取测试对象,避免访问已释放的对象 - var refreshedTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == testName) as ClashTest; - if (refreshedTest != null) + DisplayName = tempTestName, + TestType = ClashTestType.HardConservative, + Tolerance = detectionGap, + Guid = Guid.Empty + }; + + // 设置选择集 + var selectionA = new ModelItemCollection { collision.Item1 }; + var selectionB = new ModelItemCollection { collision.Item2 }; + + tempTest.SelectionA.Selection.CopyFrom(selectionA); + tempTest.SelectionB.Selection.CopyFrom(selectionB); + + // 添加并运行临时测试 + _documentClash.TestsData.TestsAddCopy(tempTest); + var addedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest; + + if (addedTempTest != null) + { + // 使用正确的PrimitiveTypes设置模式 + try { - LogManager.Info($"[测试创建-{createdCount:00}] 测试运行完成 - 碰撞数量: {refreshedTest.Children.Count}"); + var copyTest = addedTempTest.CreateCopy() as ClashTest; + copyTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; + copyTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; + _documentClash.TestsData.TestsEditTestFromCopy(addedTempTest, copyTest); + LogManager.Debug($"[分组测试-{resultCount:00}] 几何类型设置完成"); + } + catch (Exception geomEx) + { + LogManager.Warning($"设置几何类型失败: {geomEx.Message}"); + } + + // 运行测试 + _documentClash.TestsData.TestsRunTest(addedTempTest); + + // 获取刷新后的测试结果 + var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest; + if (refreshedTempTest != null && refreshedTempTest.Children.Count > 0) + { + LogManager.Info($"[分组测试-{resultCount:00}] 临时测试检测到 {refreshedTempTest.Children.Count} 个碰撞"); - // 🔍 详细分析测试结果 - if (refreshedTest.Children.Count > 0) + // 将碰撞结果复制到分组中,并设置唯一名称 + int subResultIndex = 1; + foreach (var child in refreshedTempTest.Children) { - LogManager.Info($"[测试创建-{createdCount:00}] 检测到碰撞,分析结果:"); - foreach (var child in refreshedTest.Children.Take(3)) // 只显示前3个结果 + if (child is ClashResult result) { - if (child is ClashResult result) - { - LogManager.Info($" 碰撞对象: {result.Item1?.DisplayName ?? "空"} <-> {result.Item2?.DisplayName ?? "空"}"); - LogManager.Info($" 状态: {result.Status}, 距离: {result.Distance:F4}"); - LogManager.Info($" 中心点: ({result.Center.X:F3},{result.Center.Y:F3},{result.Center.Z:F3})"); - } + var copiedResult = result.CreateCopy() as ClashResult; + copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID + + // 🔧 设置唯一且有意义的碰撞名称 + var object1Name = collision.Item1?.DisplayName ?? "未知对象"; + var object2Name = collision.Item2?.DisplayName ?? "未知对象"; + var timeStamp = DateTime.Now.ToString("HHmmss"); + copiedResult.DisplayName = $"路径碰撞#{resultCount:00}-{subResultIndex:00}_{timeStamp}: {object1Name} ↔ {object2Name}"; + + collisionGroup.Children.Add(copiedResult); + LogManager.Debug($"[分组测试-{resultCount:00}] 复制碰撞结果到分组: {copiedResult.DisplayName}"); + subResultIndex++; } - if (refreshedTest.Children.Count > 3) - { - LogManager.Info($" ... 还有 {refreshedTest.Children.Count - 3} 个碰撞结果"); - } - } - else - { - LogManager.Warning($"[测试创建-{createdCount:00}] 测试未检测到碰撞!这可能表示:"); - LogManager.Warning($" 1. 对象移动后不再碰撞"); - LogManager.Warning($" 2. 对象在测试时无效"); - LogManager.Warning($" 3. Clash Detective测试设置问题"); } } else { - LogManager.Error($"[测试创建-{createdCount:00}] 运行后无法获取刷新的测试对象"); + LogManager.Warning($"[分组测试-{resultCount:00}] 临时测试未检测到碰撞,创建手动结果"); + + // 如果没有检测到碰撞,创建基于缓存数据的手动结果 + var object1Name = collision.Item1?.DisplayName ?? "未知对象"; + var object2Name = collision.Item2?.DisplayName ?? "未知对象"; + var timeStamp = DateTime.Now.ToString("HHmmss"); + var manualResult = new ClashResult + { + DisplayName = $"手动路径碰撞#{resultCount:00}_{timeStamp}: {object1Name} ↔ {object2Name}", + Status = ClashResultStatus.New, + Distance = collision.Distance, + Center = collision.Center, + Guid = Guid.NewGuid() + }; + + collisionGroup.Children.Add(manualResult); + } + + // 删除临时测试 + try + { + _documentClash.TestsData.TestsRemove(refreshedTempTest ?? addedTempTest); + LogManager.Debug($"[分组测试-{resultCount:00}] 临时测试已清理"); + } + catch (Exception cleanEx) + { + LogManager.Warning($"清理临时测试失败: {cleanEx.Message}"); } } - catch (Exception runEx) - { - LogManager.Error($"运行测试失败: {runEx.Message}"); - } + + } + catch (Exception resultEx) + { + LogManager.Error($"[分组测试-{resultCount:00}] 处理碰撞结果失败: {resultEx.Message}"); } } - catch (Exception createEx) + + // 第三步:将分组添加到主测试 + if (collisionGroup.Children.Count > 0) { - LogManager.Error($"创建测试失败 - 异常类型: {createEx.GetType().Name}: {createEx.Message}"); + _documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup); + LogManager.Info($"[分组测试] 分组已添加到主测试,包含 {collisionGroup.Children.Count} 个碰撞结果"); + } + else + { + LogManager.Warning("[分组测试] 分组为空,未添加到主测试"); } - createdCount++; - - // 小延迟确保测试完成 - System.Threading.Thread.Sleep(100); - - } - catch (Exception createEx) - { - LogManager.Error($"创建单个测试失败: {createEx.Message}"); + // 提交事务 + transaction.Commit(); + LogManager.Info("[分组测试] 事务已提交,测试创建完成"); } } + catch (Exception groupEx) + { + LogManager.Error($"[分组测试] 创建分组测试失败: {groupEx.Message}"); + } // 关键修复:碰撞测试完成后,将物体恢复到动画终点位置 if (animatedObject != null && IsModelItemValid(animatedObject)) @@ -809,9 +809,11 @@ namespace NavisworksTransport } } - LogManager.Info($"=== 位置恢复方案完成:成功创建并运行 {createdCount - 1} 个碰撞测试(容差: {detectionGap}米) ==="); + LogManager.Info($"=== 分组方案完成:成功创建分组碰撞测试(容差: {detectionGap}米) ==="); - if (createdCount > 1) + // 检查是否成功创建了主测试 + var finalMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName.Contains("物流路径碰撞汇总")) as ClashTest; + if (finalMainTest != null) { // 刷新Clash Detective窗口 RefreshClashDetectiveUI(); @@ -836,25 +838,50 @@ namespace NavisworksTransport LogManager.Error($"动画结束后自动高亮失败: {highlightEx.Message}"); } - // 更新Clash Detective碰撞计数器 - 统计实际创建的测试数量 + // 更新Clash Detective碰撞计数器 - 统计分组测试中的碰撞数量 var finalClashDetectiveCount = 0; if (_documentClash != null) { - // 计算所有动画相关测试的碰撞总数 - var animationTests = _documentClash.TestsData.Tests.Cast() - .Where(t => t.DisplayName.Contains("动画路径碰撞") || t.DisplayName.Contains("动画碰撞")) + // 计算物流路径碰撞汇总测试的碰撞总数 + var logisticsTests = _documentClash.TestsData.Tests.Cast() + .Where(t => t.DisplayName.Contains("物流路径碰撞汇总") || + t.DisplayName.Contains("动画路径碰撞") || + t.DisplayName.Contains("动画碰撞")) .ToList(); - finalClashDetectiveCount = animationTests.Sum(t => t.Children.Count); - LogManager.Info($"统计Clash Detective最终碰撞数量: {animationTests.Count}个测试,总共{finalClashDetectiveCount}个碰撞"); + finalClashDetectiveCount = logisticsTests.Sum(t => GetTotalClashResultCount(t)); + LogManager.Info($"统计Clash Detective最终碰撞数量: {logisticsTests.Count}个测试,总共{finalClashDetectiveCount}个碰撞"); + + // 详细分析主测试结构 + if (finalMainTest != null) + { + LogManager.Info($"[分组统计] 主测试: {finalMainTest.DisplayName}"); + LogManager.Info($"[分组统计] 主测试直接子项数量: {finalMainTest.Children.Count}"); + + foreach (var child in finalMainTest.Children) + { + if (child is ClashResultGroup group) + { + LogManager.Info($"[分组统计] 分组: {group.DisplayName}, 包含 {group.Children.Count} 个结果"); + } + else if (child is ClashResult result) + { + LogManager.Info($"[分组统计] 直接结果: {result.DisplayName}"); + } + } + } } _clashDetectiveCollisionCount = finalClashDetectiveCount; - LogManager.Info($"Clash Detective碰撞计数器已更新: {_clashDetectiveCollisionCount}"); + LogManager.Info($"=== 碰撞统计最终结果 ==="); + LogManager.Info($"动画过程包围盒检测: {_animationCollisionCount}个碰撞 (参考值)"); + LogManager.Info($"Clash Detective权威结果: {_clashDetectiveCollisionCount}个碰撞 (最终权威数据)"); + LogManager.Info($"统计说明: 动画过程使用简单包围盒检测,Clash Detective使用精确几何体检测"); + LogManager.Info($"=== 统计分离完成 ==="); // 清空缓存 _cachedResults.Clear(); - LogManager.Info("=== 动画碰撞测试(位置恢复方案)完成 ==="); + LogManager.Info("=== 动画碰撞测试(分组方案)完成 ==="); } } catch (Exception ex) @@ -1356,95 +1383,6 @@ namespace NavisworksTransport return DateTime.Now - _lastTestCreationTime > TimeSpan.FromSeconds(5); } - - /// - /// 确保动态测试存在 - /// - private void EnsureDynamicTestExists() - { - try - { - if (_dynamicClashTest == null) - { - // 查找现有的动态测试 - var existingTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); - if (existingTest is ClashTest clashTest) - { - _dynamicClashTest = clashTest; - LogManager.Debug("找到现有动态测试"); - } - else - { - LogManager.Info("动态测试不存在,创建新测试"); - SetupDynamicClashTest(); - } - } - } - catch (Exception ex) - { - LogManager.Error($"确保动态测试存在失败: {ex.Message}"); - } - } - - /// - /// 安全地更新选择集 - /// - private bool SafeUpdateSelections(ModelItem animatedObject, ModelItemCollection excludeObjects, bool isRetry = false) - { - try - { - // 创建新的选择集 - var selectionA = new ModelItemCollection(); - selectionA.Add(animatedObject); - - var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf - .Where(item => item.HasGeometry && !item.Equals(animatedObject)); - - if (excludeObjects != null) - { - allItems = allItems.Where(item => !excludeObjects.Contains(item)); - } - - var selectionB = new ModelItemCollection(); - selectionB.AddRange(allItems); - - // 尝试更新选择集 - _dynamicClashTest.SelectionA.Selection.Clear(); - _dynamicClashTest.SelectionA.Selection.CopyFrom(selectionA); - - _dynamicClashTest.SelectionB.Selection.Clear(); - _dynamicClashTest.SelectionB.Selection.CopyFrom(selectionB); - - LogManager.Debug($"选择集更新成功: A={selectionA.Count}, B={selectionB.Count}"); - return true; - } - catch (Exception ex) - { - LogManager.Error($"更新选择集失败: {ex.Message}"); - - // 只在第一次失败时尝试重新创建测试,避免无限递归 - if (!isRetry && ex.Message.Contains("Read-Only")) - { - try - { - LogManager.Info("尝试重新创建动态测试(仅重试一次)"); - SetupDynamicClashTest(); - return SafeUpdateSelections(animatedObject, excludeObjects, true); // 重试一次,标记为重试 - } - catch (Exception retryEx) - { - LogManager.Error($"重新创建测试也失败: {retryEx.Message}"); - return false; - } - } - else - { - LogManager.Error($"选择集更新失败,{(isRetry ? "重试后仍然失败" : "跳过重试")}"); - return false; - } - } - } - /// /// 创建临时碰撞测试 /// @@ -1502,100 +1440,6 @@ namespace NavisworksTransport } } - /// - /// 确保测试仍然存在并且有效 - /// - private void EnsureTestExists() - { - try - { - // 检查测试是否仍然在文档中 - var existingTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); - - if (existingTest == null) - { - LogManager.Info("动态测试不存在,重新创建..."); - SetupDynamicClashTest(); - } - else if (existingTest is ClashTest clashTest) - { - // 检查测试是否可写 - try - { - // 尝试访问选择集来测试是否可写 - var testSelection = clashTest.SelectionA.Selection; - _dynamicClashTest = clashTest; - LogManager.Debug("动态测试已确认存在并可写"); - } - catch (Exception readOnlyEx) - { - if (readOnlyEx.Message.Contains("Read-Only")) - { - LogManager.Warning("现有测试为只读,重新创建新测试"); - SetupDynamicClashTest(); - } - else - { - throw; - } - } - } - } - catch (Exception ex) - { - LogManager.Error($"确保测试存在失败: {ex.Message}"); - // 重新创建测试 - SetupDynamicClashTest(); - } - } - - /// - /// 安全地运行碰撞检测 - /// - private void RunCollisionDetection() - { - try - { - // 验证测试是否可以运行 - if (_dynamicClashTest == null) - { - LogManager.Error("动态测试为空,无法运行"); - return; - } - - // 检查测试是否在文档的测试列表中 - var testExists = _documentClash.TestsData.Tests.Any(t => t.DisplayName == _dynamicClashTest.DisplayName); - if (!testExists) - { - LogManager.Info("测试不在文档中,重新添加..."); - _documentClash.TestsData.TestsAddCopy(_dynamicClashTest); - } - - // 运行碰撞检测 - _documentClash.TestsData.TestsRunTest(_dynamicClashTest); - LogManager.Debug($"碰撞检测运行完成,结果数量: {_dynamicClashTest.Children.Count}"); - } - catch (Exception ex) - { - LogManager.Error($"运行碰撞检测失败: {ex.Message}"); - // 如果运行失败,尝试重新创建测试 - LogManager.Info("尝试重新创建测试..."); - SetupDynamicClashTest(); - - // 再次尝试运行 - try - { - _documentClash.TestsData.TestsRunTest(_dynamicClashTest); - LogManager.Info("重新创建测试后运行成功"); - } - catch (Exception retryEx) - { - LogManager.Error($"重试运行测试也失败: {retryEx.Message}"); - throw; // 重新抛出异常,让上层处理 - } - } - } - /// /// 更新碰撞测试的选择集 /// @@ -1694,182 +1538,6 @@ namespace NavisworksTransport return results; } - /// - /// 同步结果到Clash Detective窗口 - /// - private void SyncToClashDetectiveWindow() - { - try - { - LogManager.Info("开始同步结果到Clash Detective窗口..."); - - // 强制刷新Clash Detective窗口显示 - RefreshClashDetectiveWindow(); - - // 确保测试结果在界面中可见 - EnsureTestVisibility(); - - LogManager.Info("同步到Clash Detective窗口完成"); - } - catch (Exception ex) - { - LogManager.Error($"同步到Clash Detective窗口失败: {ex.Message}"); - } - } - - /// - /// 刷新Clash Detective窗口(修复版 - 避免UI阻塞) - /// - private void RefreshClashDetectiveWindow() - { - try - { - LogManager.Info("开始刷新Clash Detective窗口..."); - - // 检查文档是否有效 - if (Application.ActiveDocument == null) - { - LogManager.Warning("文档已失效,跳过刷新"); - return; - } - - // 使用后台任务执行,避免UI阻塞 - System.Threading.Tasks.Task.Run(() => - { - try - { - // 方法1: 通过COM API刷新(如果可用) - if (_clashElement != null) - { - try - { - _clashElement.RunAllTests(); - LogManager.Info("通过COM API刷新Clash Detective窗口成功"); - } - catch (Exception ex) - { - LogManager.Warning($"COM API刷新失败: {ex.Message}"); - } - } - - // 方法2: 通过.NET API刷新(主要方法) - if (_documentClash != null) - { - try - { - // 重新运行我们的测试 - var currentTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); - if (currentTest != null && currentTest is ClashTest clashTest) - { - _documentClash.TestsData.TestsRunTest(clashTest); - LogManager.Info($"通过.NET API刷新测试结果,当前结果数: {clashTest.Children.Count}"); - } - else - { - LogManager.Info("动态测试不存在,重新创建..."); - // 在后台任务中重新创建 - SetupDynamicClashTest(); - } - } - catch (Exception ex) - { - LogManager.Warning($".NET API刷新失败: {ex.Message}"); - } - } - - // 方法3: 强制重绘视图 - try - { - // 使用延迟重绘,避免立即操作 - System.Threading.Thread.Sleep(100); - Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All); - LogManager.Info("强制重绘视图成功"); - } - catch (Exception ex) - { - LogManager.Warning($"视图重绘失败: {ex.Message}"); - } - - // 方法4: 尝试手动触发Clash Detective UI更新 - try - { - // 使用延迟操作,给UI时间处理 - System.Threading.Thread.Sleep(100); - - // 强制保存和重新加载文档状态 - var doc = Application.ActiveDocument; - if (doc != null && doc.CurrentSelection != null) - { - var tempSelection = doc.CurrentSelection.SelectedItems; - doc.CurrentSelection.Clear(); - if (tempSelection.Count > 0) - { - doc.CurrentSelection.CopyFrom(tempSelection); - } - LogManager.Info("手动触发UI更新成功"); - } - } - catch (Exception ex) - { - LogManager.Warning($"手动UI更新失败: {ex.Message}"); - } - - LogManager.Info("Clash Detective窗口刷新完成"); - - } - catch (ObjectDisposedException ex) - { - LogManager.Warning($"刷新时对象已释放: {ex.Message}"); - } - catch (Exception ex) - { - LogManager.Error($"后台刷新失败: {ex.Message}"); - } - }); - - } - catch (Exception ex) - { - LogManager.Error($"刷新Clash Detective窗口启动失败: {ex.Message}"); - } - } - - /// - /// 确保测试在界面中可见 - /// - private void EnsureTestVisibility() - { - try - { - // 检查测试是否已添加到文档 - var existingTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); - - if (existingTest == null) - { - LogManager.Warning("动态碰撞检测测试未找到,重新创建..."); - SetupDynamicClashTest(); - } - else if (existingTest is ClashTest clashTest) - { - LogManager.Info($"找到测试: {clashTest.DisplayName}, 结果数量: {clashTest.Children.Count}"); - - // 确保测试有结果 - if (clashTest.Children.Count > 0) - { - LogManager.Info("测试已有结果,应该在Clash Detective窗口中可见"); - } - else - { - LogManager.Info("测试无结果,可能需要重新运行"); - } - } - } - catch (Exception ex) - { - LogManager.Error($"确保测试可见性失败: {ex.Message}"); - } - } - /// /// 高亮显示碰撞对象 /// @@ -2517,70 +2185,6 @@ namespace NavisworksTransport throw new NotImplementedException("方法已迁移到工具类"); } - /// - /// 获取当前测试状态信息(用于调试和验证) - /// - /// 测试状态信息 - public string GetTestStatusInfo() - { - try - { - if (!_isInitialized) - { - return "Clash Detective 集成未初始化"; - } - - if (_documentClash == null) - { - return "无法访问 Clash Detective 文档"; - } - - var testInfo = new StringBuilder(); - testInfo.AppendLine("=== Clash Detective 集成状态 ==="); - testInfo.AppendLine($"初始化状态: {(_isInitialized ? "已初始化" : "未初始化")}"); - testInfo.AppendLine($"COM API 连接: {(_clashElement != null ? "已连接" : "未连接")}"); - testInfo.AppendLine($"文档 Clash 对象: {(_documentClash != null ? "可用" : "不可用")}"); - - // 检查测试列表 - var tests = _documentClash.TestsData.Tests; - testInfo.AppendLine($"总测试数量: {tests.Count}"); - - var dynamicTest = tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); - if (dynamicTest != null && dynamicTest is ClashTest clashTest) - { - testInfo.AppendLine($"动态测试: 已找到"); - testInfo.AppendLine($"测试类型: {clashTest.TestType}"); - testInfo.AppendLine($"容差: {clashTest.Tolerance}"); - testInfo.AppendLine($"结果数量: {clashTest.Children.Count}"); - - if (clashTest.Children.Count > 0) - { - testInfo.AppendLine("最近结果:"); - int count = 0; - foreach (var child in clashTest.Children) - { - if (child is ClashResult result && count < 3) - { - testInfo.AppendLine($" - {result.DisplayName}: {result.Status}"); - count++; - } - } - } - } - else - { - testInfo.AppendLine($"动态测试: 未找到"); - } - - testInfo.AppendLine("=== 状态信息结束 ==="); - return testInfo.ToString(); - } - catch (Exception ex) - { - return $"获取测试状态失败: {ex.Message}"; - } - } - /// /// 测试容器映射功能是否正常工作 /// @@ -2666,6 +2270,58 @@ namespace NavisworksTransport // 已删除:ForceShowTestResults 和 CreateDemoTest - 不再需要这些已废弃的方法 + /// + /// 递归计算测试中的碰撞结果总数(包括分组内的结果) + /// + /// 要统计的测试 + /// 碰撞结果总数 + private int GetTotalClashResultCount(ClashTest test) + { + if (test == null) return 0; + + int totalCount = 0; + foreach (var child in test.Children) + { + if (child is ClashResult) + { + totalCount++; + } + else if (child is ClashResultGroup group) + { + // 递归统计分组内的结果 + totalCount += CountClashResultsInGroup(group); + } + } + + return totalCount; + } + + /// + /// 递归统计分组内的碰撞结果数量 + /// + /// 分组 + /// 分组内的碰撞结果总数 + private int CountClashResultsInGroup(ClashResultGroup group) + { + if (group == null) return 0; + + int count = 0; + foreach (var child in group.Children) + { + if (child is ClashResult) + { + count++; + } + else if (child is ClashResultGroup nestedGroup) + { + // 递归处理嵌套分组 + count += CountClashResultsInGroup(nestedGroup); + } + } + + return count; + } + /// /// 清理资源 /// diff --git a/src/Core/Collision/ClashDetectiveIntegration_copy.cs b/src/Core/Collision/ClashDetectiveIntegration_copy.cs new file mode 100644 index 0000000..c81494f --- /dev/null +++ b/src/Core/Collision/ClashDetectiveIntegration_copy.cs @@ -0,0 +1,2788 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Autodesk.Navisworks.Api; +using Autodesk.Navisworks.Api.Clash; +using ComApi = Autodesk.Navisworks.Api.Interop.ComApi; +using ComApiBridge = Autodesk.Navisworks.Api.ComApi; +using NavisworksTransport.UI.WPF; +using NavisworksTransport.Utils; + +namespace NavisworksTransport +{ + /// + /// Clash Detective 集成管理器 + /// 实现动态碰撞检测与Clash Detective窗口的联动 + /// + public class ClashDetectiveIntegration + { + private static ClashDetectiveIntegration _instance; + private ComApi.InwOpClashElement _clashElement; + private ComApi.InwOpState10 _state; + private DocumentClash _documentClash; + private ClashTest _dynamicClashTest; + private List _currentCollisions; + private bool _isInitialized = false; + // 通道对象缓存,用于优化碰撞检测性能 + private static HashSet _channelObjectsCache = null; + private static readonly object _cacheLock = new object(); + + // 几何对象列表缓存,用于避免重复获取对象列表 + private static List _allGeometryItemsCache = null; + + // 碰撞检测计数器 + private int _animationCollisionCount = 0; // 动画过程中简单包围盒检测的碰撞数量 + private int _clashDetectiveCollisionCount = 0; // Clash Detective最终检测的碰撞数量 + + /// + /// 动画过程中检测到的碰撞数量(仅供参考统计) + /// + public int AnimationCollisionCount + { + get { return _animationCollisionCount; } + } + + /// + /// Clash Detective检测到的权威碰撞数量 + /// + public int ClashDetectiveCollisionCount + { + get { return _clashDetectiveCollisionCount; } + } + + /// + /// 单例实例 + /// + 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; + + private ClashDetectiveIntegration() + { + _currentCollisions = new List(); + _cachedResults = new List(); + + // 延迟初始化,避免在清理时无意中触发 + // Initialize()方法现在需要手动调用 + } + + /// + /// 初始化Clash Detective集成 + /// + public void Initialize() + { + try + { + LogManager.Info("初始化Clash Detective集成..."); + + // 获取COM API状态 + _state = ComApiBridge.ComApiBridge.State; + + // 查找Clash Detective插件 + _clashElement = FindClashDetectivePlugin(); + + // 尝试获取.NET API的Clash文档(无论是否找到COM插件) + try + { + _documentClash = Application.ActiveDocument.GetClash(); + if (_documentClash != null) + { + LogManager.Info("成功获取.NET API Clash文档"); + + // 创建动态碰撞检测测试 + SetupDynamicClashTest(); + + _isInitialized = true; + + if (_clashElement != null) + { + LogManager.Info("Clash Detective集成初始化成功(完整功能)"); + } + else + { + LogManager.Info("Clash Detective集成初始化成功(.NET API模式)"); + } + } + else + { + LogManager.Warning("无法获取Clash文档,Clash Detective可能未安装"); + _isInitialized = false; + } + } + catch (Exception ex) + { + LogManager.Error($"获取.NET API Clash文档失败: {ex.Message}"); + _isInitialized = false; + } + } + catch (Exception ex) + { + LogManager.Error($"初始化Clash Detective集成失败: {ex.Message}"); + _isInitialized = false; + } + } + + /// + /// 查找Clash Detective插件(简化版本 - 跳过无效的COM API枚举) + /// + private ComApi.InwOpClashElement FindClashDetectivePlugin() + { + try + { + LogManager.Info("跳过COM API插件枚举,直接使用.NET API访问Clash Detective..."); + + // 直接尝试通过.NET API访问,避免枚举大量无效COM插件 + try + { + var documentClash = Application.ActiveDocument.GetClash(); + if (documentClash != null) + { + LogManager.Info("通过.NET API成功访问Clash Detective功能"); + } + } + catch (Exception ex) + { + LogManager.Error($"通过.NET API访问Clash Detective失败: {ex.Message}"); + } + + LogManager.Info("将使用.NET API替代方案,跳过COM插件查找"); + return null; + } + catch (Exception ex) + { + LogManager.Error($"查找Clash Detective插件失败: {ex.Message}"); + return null; + } + } + + /// + /// 设置动态碰撞检测测试 + /// + private void SetupDynamicClashTest() + { + try + { + // 清理所有动画相关的测试(包括之前创建的"动画路径碰撞_X"测试) + var existingAnimationTests = _documentClash.TestsData.Tests + .Where(t => t.DisplayName.StartsWith("动态运输路径碰撞检测") || + t.DisplayName.StartsWith("动画路径碰撞")) + .ToList(); + + if (existingAnimationTests.Any()) + { + LogManager.Info($"发现 {existingAnimationTests.Count} 个需要清理的动画测试"); + int removedCount = 0; + + foreach (var test in existingAnimationTests) + { + if (test is ClashTest testToRemove) + { + try + { + _documentClash.TestsData.TestsRemove(testToRemove); + removedCount++; + LogManager.Debug($"已删除测试: {test.DisplayName}"); + } + catch (Exception ex) + { + LogManager.Warning($"删除测试失败 {test.DisplayName}: {ex.Message}"); + } + } + } + + LogManager.Info($"成功清理了 {removedCount} 个旧的动画碰撞测试"); + } + else + { + LogManager.Info("未发现需要清理的旧动画测试"); + } + + // 创建新的碰撞测试 + _dynamicClashTest = new ClashTest(); + _dynamicClashTest.DisplayName = "动态运输路径碰撞检测"; + _dynamicClashTest.TestType = ClashTestType.Hard; + _dynamicClashTest.Tolerance = 0.01; // 1cm容差 + + // 设置几何类型:包含面、线和点以获得最全面的碰撞检测 + _dynamicClashTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; + _dynamicClashTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; + + LogManager.Debug("动态碰撞测试几何类型设置: 包含面、线、点"); + + // 设置测试规则 - 移除在2017版本中不可用的规则设置 + // _dynamicClashTest.Rules.Add(ClashRule.IgnoreWithinSameFile); + // _dynamicClashTest.Rules.Add(ClashRule.IgnoreWithinSameLayer); + + // 添加到文档,并获取返回的测试对象 + _documentClash.TestsData.TestsAddCopy(_dynamicClashTest); + + // 重新获取添加后的测试对象 + var addedTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); + if (addedTest is ClashTest clashTest) + { + _dynamicClashTest = clashTest; + LogManager.Info("动态碰撞检测测试创建成功,使用返回的测试对象"); + } + else + { + LogManager.Warning("无法获取添加后的测试对象,继续使用原始对象"); + } + + LogManager.Info("动态碰撞检测测试创建成功"); + } + catch (Exception ex) + { + LogManager.Error($"设置动态碰撞检测测试失败: {ex.Message}"); + } + } + + /// + /// 执行动态碰撞检测 + /// + /// 动画对象 + /// 排除对象 + /// 检测间隙(米) + /// 碰撞结果 + public List DetectCollisions(ModelItem animatedObject, + ModelItemCollection excludeObjects = null, double detectionGap = 0.2) + { + try + { + // 使用简化检测模式 + LogManager.Debug($"使用简化检测模式进行碰撞检测,检测间隙: {detectionGap}米"); + var results = DetectCollisionsSimple(animatedObject, excludeObjects, detectionGap); + + // 在动画过程中不创建Clash Detective测试,避免影响性能 + // 只有在动画结束后手动测试时才创建完整的测试 + + // 触发事件 + OnCollisionDetected(new CollisionDetectedEventArgs(results)); + + LogManager.Debug($"碰撞检测完成,发现 {results.Count} 个碰撞"); + return results; + } + catch (Exception ex) + { + LogManager.Error($"碰撞检测失败: {ex.Message}"); + // 回退到简化检测 + return DetectCollisionsSimple(animatedObject, excludeObjects, detectionGap); + } + } + +/// + /// 创建碰撞快照(动画结束后一次性更新结果) + /// + public void CreateCollisionSnapshot(List results) + { + try + { + if (_documentClash == null) + return; + + // 缓存结果,等动画结束后统一处理 + CacheCollisionResults(results); + } + catch (Exception ex) + { + LogManager.Error($"创建实时碰撞快照失败: {ex.Message}"); + } + } + + private List _cachedResults = new List(); + + // 高亮管理字段 + private Dictionary _activeHighlights = new Dictionary(); + private readonly object _highlightLock = new object(); + + /// + /// 缓存碰撞结果 + /// + private void CacheCollisionResults(List results) + { + if (results != null && results.Count > 0) + { + _cachedResults.AddRange(results); + } + } + + private DateTime _lastCollisionTestTime = DateTime.MinValue; + private readonly TimeSpan _minTestInterval = TimeSpan.FromMilliseconds(500); // 最小间隔500ms + + /// + /// 缓存碰撞结果(动画过程中使用)- 现在包含位置信息用于恢复测试 + /// 使用精确的碰撞检测算法替代简化检测 + /// + public void CacheCollisionDuringAnimation(ModelItem animatedObject, Point3D animatedObjectPosition, ModelItem collisionObject, Point3D collisionObjectPosition = null) + { + try + { + // 🔍 添加碰撞缓存开始的详细日志 + LogManager.Debug($"=== [碰撞缓存-开始] ==="); + LogManager.Debug($"[碰撞缓存-开始] 动画对象: {animatedObject?.DisplayName ?? "NULL"}"); + LogManager.Debug($"[碰撞缓存-开始] 碰撞对象: {collisionObject?.DisplayName ?? "NULL"}"); + + // 🔍 对象验证详情 + if (animatedObject == null) + { + LogManager.Error($"[碰撞缓存-错误] 动画对象为NULL!"); + } + + if (collisionObject == null) + { + LogManager.Error($"[碰撞缓存-错误] 碰撞对象为NULL!"); + } + + // 🔍 位置信息详情 + LogManager.Debug($"[碰撞缓存-位置] 动画对象位置: ({animatedObjectPosition.X:F3},{animatedObjectPosition.Y:F3},{animatedObjectPosition.Z:F3})"); + + if (collisionObjectPosition != null) + { + LogManager.Debug($"[碰撞缓存-位置] 碰撞对象位置: ({collisionObjectPosition.X:F3},{collisionObjectPosition.Y:F3},{collisionObjectPosition.Z:F3})"); + } + else + { + LogManager.Debug($"[碰撞缓存-位置] 碰撞对象位置: NULL (将自动计算)"); + } + + if (!IsModelItemValid(animatedObject) || !IsModelItemValid(collisionObject)) + { + LogManager.Warning($"[诊断-无效对象] 对象验证失败,退出缓存过程"); + return; + } + + // 🔧 智能容器映射:将几何体子对象映射到有名称的容器对象 + var mappedAnimatedObject = GetCollisionObjectWithValidIdentity(animatedObject); + var mappedCollisionObject = GetCollisionObjectWithValidIdentity(collisionObject); + + // 检查是否进行了容器映射 + bool hasMapping1 = !mappedAnimatedObject.Equals(animatedObject); + bool hasMapping2 = !mappedCollisionObject.Equals(collisionObject); + + LogManager.Info($"[容器映射] 动画对象: '{animatedObject.DisplayName}' -> '{mappedAnimatedObject.DisplayName}' (映射: {hasMapping1})"); + LogManager.Info($"[容器映射] 碰撞对象: '{collisionObject.DisplayName}' -> '{mappedCollisionObject.DisplayName}' (映射: {hasMapping2})"); + + // 使用包围盒碰撞检测算法 + var animatedBoundingBox = animatedObject.BoundingBox(); + var collisionBoundingBox = collisionObject.BoundingBox(); + + LogManager.Debug($"[碰撞缓存-包围盒] 动画对象包围盒: Min({animatedBoundingBox.Min.X:F3},{animatedBoundingBox.Min.Y:F3},{animatedBoundingBox.Min.Z:F3}) Max({animatedBoundingBox.Max.X:F3},{animatedBoundingBox.Max.Y:F3},{animatedBoundingBox.Max.Z:F3})"); + LogManager.Debug($"[碰撞缓存-包围盒] 碰撞对象包围盒: Min({collisionBoundingBox.Min.X:F3},{collisionBoundingBox.Min.Y:F3},{collisionBoundingBox.Min.Z:F3}) Max({collisionBoundingBox.Max.X:F3},{collisionBoundingBox.Max.Y:F3},{collisionBoundingBox.Max.Z:F3})"); + + if (BoundingBoxGeometryUtils.BoundingBoxesIntersect(animatedBoundingBox, collisionBoundingBox)) + { + // 🔍 计算并验证位置信息 + var finalCollisionPosition = collisionObjectPosition ?? GetObjectPosition(collisionObject); + + LogManager.Debug($"[碰撞缓存-位置计算] 最终使用的碰撞对象位置: ({finalCollisionPosition.X:F3},{finalCollisionPosition.Y:F3},{finalCollisionPosition.Z:F3})"); + + // 🔍 检查位置是否相同(可能的自碰撞标志) + var positionDistance = Math.Sqrt( + Math.Pow(animatedObjectPosition.X - finalCollisionPosition.X, 2) + + Math.Pow(animatedObjectPosition.Y - finalCollisionPosition.Y, 2) + + Math.Pow(animatedObjectPosition.Z - finalCollisionPosition.Z, 2)); + + LogManager.Debug($"[碰撞缓存-距离计算] 动画对象与碰撞对象的位置距离: {positionDistance:F4}"); + + if (positionDistance < 0.001) // 位置基本相同 + { + LogManager.Warning($"[诊断-位置警告] 两个对象位置几乎相同 (距离: {positionDistance:F6}),这可能是自碰撞的标志!"); + } + + // 创建碰撞结果 + var collisionDistance = BoundingBoxGeometryUtils.CalculateDistance(animatedBoundingBox, collisionBoundingBox); + var collisionCenter = BoundingBoxGeometryUtils.CalculateCenter(animatedBoundingBox, collisionBoundingBox); + + LogManager.Debug($"[碰撞缓存-距离] 包围盒碰撞距离: {collisionDistance:F4}"); + LogManager.Debug($"[碰撞缓存-中心点] 碰撞中心: ({collisionCenter.X:F3},{collisionCenter.Y:F3},{collisionCenter.Z:F3})"); + + var collision = new CollisionResult + { + ClashGuid = Guid.NewGuid(), + DisplayName = $"精确碰撞: {mappedAnimatedObject.DisplayName} <-> {mappedCollisionObject.DisplayName}", + Status = ClashResultStatus.New, + Item1 = mappedAnimatedObject, // 记录容器对象 + Item2 = mappedCollisionObject, // 记录容器对象 + OriginalItem1 = animatedObject, // 保存原始几何体对象 + OriginalItem2 = collisionObject, // 保存原始几何体对象 + HasContainerMapping = hasMapping1 || hasMapping2, // 标记是否进行了映射 + CreatedTime = DateTime.Now, + Distance = collisionDistance, + Center = collisionCenter, + Item1Position = animatedObjectPosition, + Item2Position = finalCollisionPosition, + HasPositionInfo = true + }; + + // 去重处理:避免重复缓存相同的碰撞对(基于容器对象) + var existing = _cachedResults.FirstOrDefault(r => + r.Item1.Equals(mappedAnimatedObject) && r.Item2.Equals(mappedCollisionObject)); + + if (existing == null) + { + _cachedResults.Add(collision); + } + } + else + { + LogManager.Debug($"[碰撞缓存-包围盒] 包围盒不相交,跳过缓存"); + } + } + catch (Exception ex) + { + LogManager.Error($"缓存碰撞失败: {ex.Message}"); + LogManager.Error($"[诊断-异常堆栈] {ex.StackTrace}"); + } + } + + /// + /// 获取对象当前位置 + /// + 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); + } + } + + /// + /// 动画结束后统一创建和运行ClashDetective碰撞测试 + /// + public void CreateAllAnimationCollisionTests(double detectionGap = 0.05) + { + try + { + LogManager.Info($"=== 动画结束,开始创建ClashDetective碰撞测试(容差: {detectionGap}米) ==="); + + if (_documentClash == null || _cachedResults.Count == 0) + { + LogManager.Info($"没有缓存的碰撞结果需要处理 - _documentClash: {_documentClash != null}, _cachedResults.Count: {_cachedResults.Count}"); + return; + } + + LogManager.Info($"[缓存分析-统计] 共有 {_cachedResults.Count} 个原始缓存结果"); + + // 🔍 详细分析每个缓存项 + LogManager.Info($"[缓存分析-原始] 开始分析所有原始缓存结果:"); + for (int i = 0; i < _cachedResults.Count; i++) + { + var item = _cachedResults[i]; + LogManager.Info($"[缓存分析-{i+1:00}] 对象: {item.Item1?.DisplayName ?? "NULL"} <-> {item.Item2?.DisplayName ?? "NULL"}"); + LogManager.Info($"[缓存分析-{i+1:00}] 时间: {item.CreatedTime:HH:mm:ss.fff}"); + LogManager.Info($"[缓存分析-{i+1:00}] 动画位置: ({item.Item1Position.X:F3},{item.Item1Position.Y:F3},{item.Item1Position.Z:F3})"); + LogManager.Info($"[缓存分析-{i+1:00}] 碰撞位置: ({item.Item2Position.X:F3},{item.Item2Position.Y:F3},{item.Item2Position.Z:F3})"); + LogManager.Info($"[缓存分析-{i+1:00}] 距离: {item.Distance:F4}"); + LogManager.Info($"[缓存分析-{i+1:00}] GUID: {item.ClashGuid}"); + LogManager.Info($"[缓存分析-{i+1:00}] HasPositionInfo: {item.HasPositionInfo}"); + + // 验证对象有效性 + var item1Valid = IsModelItemValid(item.Item1); + var item2Valid = IsModelItemValid(item.Item2); + LogManager.Info($"[缓存分析-{i+1:00}] 对象有效性: Item1={item1Valid}, Item2={item2Valid}"); + + if (!item1Valid) + { + LogManager.Warning($"[缓存分析-{i+1:00}] Item1无效: {item.Item1?.DisplayName ?? "NULL"}"); + } + if (!item2Valid) + { + LogManager.Warning($"[缓存分析-{i+1:00}] Item2无效: {item.Item2?.DisplayName ?? "NULL"}"); + } + } + + // 获取动画对象和当前动画终点位置(用于后续恢复) + ModelItem animatedObject = null; + Point3D animationEndPosition = new Point3D(0, 0, 0); + + if (_cachedResults.Count > 0) + { + animatedObject = _cachedResults[0].Item1; + + // 记录当前位置(应该是动画终点) + if (animatedObject != null && IsModelItemValid(animatedObject)) + { + var bounds = animatedObject.BoundingBox(); + animationEndPosition = new Point3D( + (bounds.Min.X + bounds.Max.X) / 2, + (bounds.Min.Y + bounds.Max.Y) / 2, + (bounds.Min.Z + bounds.Max.Z) / 2 + ); + LogManager.Info($"记录动画终点位置: ({animationEndPosition.X:F2},{animationEndPosition.Y:F2},{animationEndPosition.Z:F2})"); + } + } + + // 🔍 去重处理:按对象对分组,并详细记录过程 + LogManager.Info($"[去重分析-开始] 开始对 {_cachedResults.Count} 个缓存结果进行去重"); + + var uniqueCollisions = _cachedResults + .GroupBy(r => new { r.Item1, r.Item2 }) + .Select(g => new + { + Collision = g.First(), + Count = g.Count(), + FirstTime = g.Min(r => r.CreatedTime), + LastTime = g.Max(r => r.CreatedTime), + AllItems = g.ToList() // 保留所有项用于分析 + }) + .ToList(); + + LogManager.Info($"[去重分析-结果] 去重后得到 {uniqueCollisions.Count} 个唯一碰撞对"); + LogManager.Info($"[去重分析-效率] 去重前: {_cachedResults.Count}, 去重后: {uniqueCollisions.Count}, 压缩率: {(1.0 - (double)uniqueCollisions.Count / _cachedResults.Count) * 100:F1}%"); + + // 🔍 分析每个唯一碰撞对的详情 + for (int i = 0; i < uniqueCollisions.Count; i++) + { + var group = uniqueCollisions[i]; + LogManager.Info($"[去重分析-唯一{i+1:00}] 碰撞对: {group.Collision.Item1?.DisplayName ?? "NULL"} <-> {group.Collision.Item2?.DisplayName ?? "NULL"}"); + LogManager.Info($"[去重分析-唯一{i+1:00}] 重复次数: {group.Count}"); + LogManager.Info($"[去重分析-唯一{i+1:00}] 时间范围: {group.FirstTime:HH:mm:ss.fff} ~ {group.LastTime:HH:mm:ss.fff}"); + LogManager.Info($"[去重分析-唯一{i+1:00}] 选择记录: 时间={group.Collision.CreatedTime:HH:mm:ss.fff}, 距离={group.Collision.Distance:F4}"); + + // 如果有重复,分析所有重复项 + if (group.Count > 1) + { + LogManager.Info($"[去重分析-唯一{i+1:00}] 重复项详情:"); + for (int j = 0; j < group.AllItems.Count; j++) + { + var dupItem = group.AllItems[j]; + LogManager.Info($" [{j+1}] 时间: {dupItem.CreatedTime:HH:mm:ss.fff}, 距离: {dupItem.Distance:F4}, GUID: {dupItem.ClashGuid}"); + } + } + + // 验证去重键的具体值 + var key1Name = group.Collision.Item1?.DisplayName ?? "NULL"; + var key2Name = group.Collision.Item2?.DisplayName ?? "NULL"; + var key1Guid = group.Collision.Item1?.InstanceGuid.ToString() ?? "NULL"; + var key2Guid = group.Collision.Item2?.InstanceGuid.ToString() ?? "NULL"; + + LogManager.Debug($"[去重分析-唯一{i+1:00}] 去重键: Item1Name='{key1Name}', Item2Name='{key2Name}'"); + LogManager.Debug($"[去重分析-唯一{i+1:00}] 去重键: Item1GUID='{key1Guid}', Item2GUID='{key2Guid}'"); + } + + int createdCount = 1; // 从1开始编号 + var doc = Autodesk.Navisworks.Api.Application.ActiveDocument; + + // 使用位置恢复方案:为每个碰撞单独创建测试并恢复位置 + LogManager.Info("=== 开始位置恢复方案:为每个碰撞恢复对象位置 ==="); + + foreach (var collisionGroup in uniqueCollisions) + { + try + { + var collision = collisionGroup.Collision; + + // 检查是否有位置信息 + if (!collision.HasPositionInfo) + { + LogManager.Warning($"跳过无位置信息的碰撞: {collision.Item1?.DisplayName} <-> {collision.Item2?.DisplayName}"); + continue; + } + + // 确保对象仍然有效 + if (!IsModelItemValid(collision.Item1) || !IsModelItemValid(collision.Item2)) + { + LogManager.Warning($"跳过无效对象: {collision.Item1?.DisplayName} <-> {collision.Item2?.DisplayName}"); + continue; + } + + var testName = $"动画路径碰撞_{createdCount:0}_{DateTime.Now:HHmmss}"; + + LogManager.Info($"=== [测试创建-{createdCount:00}] 开始创建测试: {testName} ==="); + LogManager.Info($"[测试创建-{createdCount:00}] 动画对象: {collision.Item1?.DisplayName ?? "NULL"}"); + LogManager.Info($"[测试创建-{createdCount:00}] 碰撞对象: {collision.Item2?.DisplayName ?? "NULL"}"); + LogManager.Info($"[测试创建-{createdCount:00}] 动画对象位置: ({collision.Item1Position.X:F3},{collision.Item1Position.Y:F3},{collision.Item1Position.Z:F3})"); + LogManager.Info($"[测试创建-{createdCount:00}] 碰撞对象位置: ({collision.Item2Position.X:F3},{collision.Item2Position.Y:F3},{collision.Item2Position.Z:F3})"); + LogManager.Info($"[测试创建-{createdCount:00}] 原始距离: {collision.Distance:F4}"); + LogManager.Info($"[测试创建-{createdCount:00}] 创建时间: {collision.CreatedTime:HH:mm:ss.fff}"); + LogManager.Info($"[测试创建-{createdCount:00}] 使用容差: {detectionGap}米"); + + // 将对象移动到碰撞位置 + var testAnimatedObject = collision.Item1; + var modelItems = new ModelItemCollection { testAnimatedObject }; + var targetPosition = collision.Item1Position; + + // 计算当前位置到目标位置的偏移 + var currentBounds = testAnimatedObject.BoundingBox(); + var currentPos = new Point3D( + (currentBounds.Min.X + currentBounds.Max.X) / 2, + (currentBounds.Min.Y + currentBounds.Max.Y) / 2, + (currentBounds.Min.Z + currentBounds.Max.Z) / 2 + ); + + var offset = new Vector3D( + targetPosition.X - currentPos.X, + targetPosition.Y - currentPos.Y, + targetPosition.Z - currentPos.Z + ); + + var transform = Transform3D.CreateTranslation(offset); + doc.Models.OverridePermanentTransform(modelItems, transform, false); + + LogManager.Info($"[测试创建-{createdCount:00}] 已将 {testAnimatedObject.DisplayName} 移动到碰撞位置: ({targetPosition.X:F3},{targetPosition.Y:F3},{targetPosition.Z:F3})"); + LogManager.Debug($"[测试创建-{createdCount:00}] 移动偏移量: ({offset.X:F3},{offset.Y:F3},{offset.Z:F3})"); + + try + { + // 创建新的碰撞测试 + var collisionTest = new ClashTest + { + DisplayName = testName, + TestType = ClashTestType.HardConservative, // 保守硬碰撞 + Tolerance = detectionGap, // 使用传入的检测间隙参数(保守硬碰撞,建议为0,结果准确) + Guid = Guid.Empty + }; + + + LogManager.Debug($"[测试创建-{createdCount:00}] 几何类型设置: 包含面、线、点"); + + // 设置选择集A + var selectionA = new ModelItemCollection + { + collision.Item1 + }; + + // 设置几何类型:包含面、线和点以获得最全面的碰撞检测 + collisionTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; + collisionTest.SelectionA.Selection.CopyFrom(selectionA); + LogManager.Debug($"[测试创建-{createdCount:00}] 选择集A设置: {collision.Item1?.DisplayName ?? "NULL"} (数量: {selectionA.Count})"); + + // 设置选择集B + var selectionB = new ModelItemCollection + { + collision.Item2 + }; + + collisionTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; + collisionTest.SelectionB.Selection.CopyFrom(selectionB); + LogManager.Debug($"[测试创建-{createdCount:00}] 选择集B设置: {collision.Item2?.DisplayName ?? "NULL"} (数量: {selectionB.Count})"); + + try + { + _documentClash.TestsData.TestsAddCopy(collisionTest); + } + catch (Exception addEx) + { + LogManager.Error($"添加测试失败: {addEx.GetType().Name}: {addEx.Message}"); + throw; + } + + // 获取添加后的测试并运行 + var addedTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == testName) as ClashTest; + + if (addedTest != null) + { + try + { + LogManager.Info($"[测试创建-{createdCount:00}] 开始运行测试..."); + _documentClash.TestsData.TestsRunTest(addedTest); + + // 重新获取测试对象,避免访问已释放的对象 + var refreshedTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == testName) as ClashTest; + if (refreshedTest != null) + { + LogManager.Info($"[测试创建-{createdCount:00}] 测试运行完成 - 碰撞数量: {refreshedTest.Children.Count}"); + + // 🔍 详细分析测试结果 + if (refreshedTest.Children.Count > 0) + { + LogManager.Info($"[测试创建-{createdCount:00}] 检测到碰撞,分析结果:"); + foreach (var child in refreshedTest.Children.Take(3)) // 只显示前3个结果 + { + if (child is ClashResult result) + { + LogManager.Info($" 碰撞对象: {result.Item1?.DisplayName ?? "空"} <-> {result.Item2?.DisplayName ?? "空"}"); + LogManager.Info($" 状态: {result.Status}, 距离: {result.Distance:F4}"); + LogManager.Info($" 中心点: ({result.Center.X:F3},{result.Center.Y:F3},{result.Center.Z:F3})"); + } + } + if (refreshedTest.Children.Count > 3) + { + LogManager.Info($" ... 还有 {refreshedTest.Children.Count - 3} 个碰撞结果"); + } + } + else + { + LogManager.Warning($"[测试创建-{createdCount:00}] 测试未检测到碰撞!这可能表示:"); + LogManager.Warning($" 1. 对象移动后不再碰撞"); + LogManager.Warning($" 2. 对象在测试时无效"); + LogManager.Warning($" 3. Clash Detective测试设置问题"); + } + } + else + { + LogManager.Error($"[测试创建-{createdCount:00}] 运行后无法获取刷新的测试对象"); + } + } + catch (Exception runEx) + { + LogManager.Error($"运行测试失败: {runEx.Message}"); + } + } + } + catch (Exception createEx) + { + LogManager.Error($"创建测试失败 - 异常类型: {createEx.GetType().Name}: {createEx.Message}"); + } + + createdCount++; + + // 小延迟确保测试完成 + System.Threading.Thread.Sleep(100); + + } + catch (Exception createEx) + { + LogManager.Error($"创建单个测试失败: {createEx.Message}"); + } + } + + // 关键修复:碰撞测试完成后,将物体恢复到动画终点位置 + if (animatedObject != null && IsModelItemValid(animatedObject)) + { + try + { + LogManager.Info("=== 开始恢复物体到动画终点位置 ==="); + + var modelItems = new ModelItemCollection { animatedObject }; + + // 计算当前位置到动画终点的偏移 + var currentBounds = animatedObject.BoundingBox(); + var currentPos = new Point3D( + (currentBounds.Min.X + currentBounds.Max.X) / 2, + (currentBounds.Min.Y + currentBounds.Max.Y) / 2, + (currentBounds.Min.Z + currentBounds.Max.Z) / 2 + ); + + var restoreOffset = new Vector3D( + animationEndPosition.X - currentPos.X, + animationEndPosition.Y - currentPos.Y, + animationEndPosition.Z - currentPos.Z + ); + + var restoreTransform = Transform3D.CreateTranslation(restoreOffset); + doc.Models.OverridePermanentTransform(modelItems, restoreTransform, false); + + LogManager.Info($"已将 {animatedObject.DisplayName} 恢复到动画终点位置: ({animationEndPosition.X:F2},{animationEndPosition.Y:F2},{animationEndPosition.Z:F2})"); + LogManager.Info("=== 物体位置恢复完成 ==="); + } + catch (Exception restoreEx) + { + LogManager.Error($"恢复物体到动画终点失败: {restoreEx.Message}"); + } + } + + LogManager.Info($"=== 位置恢复方案完成:成功创建并运行 {createdCount - 1} 个碰撞测试(容差: {detectionGap}米) ==="); + + if (createdCount > 1) + { + // 刷新Clash Detective窗口 + RefreshClashDetectiveUI(); + + // 自动高亮所有碰撞结果(使用类别管理) + try + { + // 使用过滤后的缓存结果进行高亮 + var validResults = _cachedResults.Where(r => + IsModelItemValid(r.Item1) && IsModelItemValid(r.Item2)).ToList(); + + if (validResults.Count > 0) + { + // 使用类别管理的高亮方式 + var darkRed = Color.Red; // 红色(暗红色在Navisworks API中不可用) + ManageHighlightsByCategory("animation", validResults, darkRed, true); + LogManager.Info($"自动高亮显示 {validResults.Count} 个动画结束后的碰撞结果(暗红色)"); + } + } + catch (Exception highlightEx) + { + LogManager.Error($"动画结束后自动高亮失败: {highlightEx.Message}"); + } + + // 更新Clash Detective碰撞计数器 - 统计实际创建的测试数量 + var finalClashDetectiveCount = 0; + if (_documentClash != null) + { + // 计算所有动画相关测试的碰撞总数 + var animationTests = _documentClash.TestsData.Tests.Cast() + .Where(t => t.DisplayName.Contains("动画路径碰撞") || t.DisplayName.Contains("动画碰撞")) + .ToList(); + + finalClashDetectiveCount = animationTests.Sum(t => t.Children.Count); + LogManager.Info($"统计Clash Detective最终碰撞数量: {animationTests.Count}个测试,总共{finalClashDetectiveCount}个碰撞"); + } + + _clashDetectiveCollisionCount = finalClashDetectiveCount; + LogManager.Info($"Clash Detective碰撞计数器已更新: {_clashDetectiveCollisionCount}"); + + // 清空缓存 + _cachedResults.Clear(); + LogManager.Info("=== 动画碰撞测试(位置恢复方案)完成 ==="); + } + } + catch (Exception ex) + { + LogManager.Error($"动画结束后创建测试失败: {ex.Message}"); + } + } + + /* + /// + /// 动画结束后创建最终碰撞结果汇总 - 使用官方示例方法(已注释,改为实时添加) + /// + public void CreateFinalCollisionSummary() + { + try + { + LogManager.Info("=== 开始创建最终碰撞结果汇总(官方方法) ==="); + LogManager.Info($"步骤1: 检查前置条件 - _documentClash是否存在: {_documentClash != null}"); + LogManager.Info($"步骤1: 检查前置条件 - 缓存结果数量: {_cachedResults.Count}"); + + if (_documentClash == null || _cachedResults.Count == 0) + { + LogManager.Info("前置条件不满足,退出创建"); + return; + } + + LogManager.Info($"步骤2: 开始处理 {_cachedResults.Count} 个碰撞结果"); + + // 去重处理 + LogManager.Info("步骤3: 开始去重处理..."); + var uniqueCollisions = _cachedResults + .GroupBy(r => new { r.Item1, r.Item2 }) + .Select(g => g.First()) + .ToList(); + LogManager.Info($"步骤3: 去重后碰撞数量: {uniqueCollisions.Count}"); + + if (!uniqueCollisions.Any()) + { + LogManager.Info("去重后无碰撞,退出创建"); + return; + } + + // 使用事务处理,避免只读错误 + using (var transaction = Autodesk.Navisworks.Api.Application.ActiveDocument.BeginTransaction("创建碰撞汇总")) + { + try + { + var testName = $"物流路径碰撞汇总_{DateTime.Now:yyyyMMdd_HHmmss}"; + LogManager.Info($"步骤4: 创建测试对象 - 测试名称: {testName}"); + + // 创建新的测试(使用CreateCopy避免只读问题) + var templateTest = new ClashTest(); + templateTest.DisplayName = testName; + templateTest.TestType = ClashTestType.Hard; + templateTest.Tolerance = 0.01; + templateTest.Guid = Guid.Empty; // 让系统生成新GUID + + // 为每个唯一碰撞创建测试 + int resultCount = 0; + + foreach (var collision in uniqueCollisions) + { + try + { + if (!IsModelItemValid(collision.Item1) || !IsModelItemValid(collision.Item2)) + { + LogManager.Warning("跳过无效的碰撞对象"); + continue; + } + + // 为每个碰撞创建单独的测试(官方推荐方法) + var collisionTest = (ClashTest)templateTest.CreateCopyWithoutChildren(); + collisionTest.DisplayName = $"碰撞_{resultCount + 1}: {collision.Item1.DisplayName} 与 {collision.Item2.DisplayName}"; + + // 设置选择集A:第一个对象 + var selectionA = new ModelItemCollection(); + selectionA.Add(collision.Item1); + collisionTest.SelectionA.Selection.CopyFrom(selectionA); + + // 设置选择集B:第二个对象 + var selectionB = new ModelItemCollection(); + selectionB.Add(collision.Item2); + collisionTest.SelectionB.Selection.CopyFrom(selectionB); + + // 使用官方方法添加测试 + _documentClash.TestsData.TestsAddCopy(collisionTest); + resultCount++; + + LogManager.Debug($"已创建碰撞测试 {resultCount}: {collision.Item1.DisplayName} <-> {collision.Item2.DisplayName}"); + } + catch (Exception ex) + { + LogManager.Error($"创建碰撞测试失败: {ex.Message}"); + } + } + + LogManager.Info($"步骤7: 碰撞测试创建完成,共 {resultCount} 个测试"); + + // 提交事务 + transaction.Commit(); + + // 运行所有新创建的测试 + RefreshClashDetectiveUI(); + + // 清空缓存 + _cachedResults.Clear(); + LogManager.Info("=== 最终碰撞结果汇总创建完成 ==="); + } + catch (Exception) + { + // Navisworks 2017没有RollBack方法,不提交事务即可回滚 + throw; + } + } + } + catch (Exception ex) + { + LogManager.Error($"创建最终碰撞汇总失败 - {ex.GetType().Name}: {ex.Message}"); + LogManager.Error($"创建最终碰撞汇总失败 - 堆栈跟踪: {ex.StackTrace}"); + } + } + */ + + + + + /// + /// 刷新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}"); + } + } + + /// + /// 更新现有测试的选择集 + /// + private void UpdateExistingTest(ClashTest existingTest, List validCollisions) + { + try + { + if (existingTest == null || !validCollisions.Any()) + return; + + // 重新构建选择集 + var selectionA = new ModelItemCollection(); + selectionA.Add(validCollisions[0].Item1); // 动画对象 + existingTest.SelectionA.Selection.CopyFrom(selectionA); + + var selectionB = new ModelItemCollection(); + foreach (var collision in validCollisions) + { + if (!selectionB.Contains(collision.Item2)) + { + selectionB.Add(collision.Item2); + } + } + existingTest.SelectionB.Selection.CopyFrom(selectionB); + + // 运行测试以更新结果 + System.Threading.Tasks.Task.Run(() => + { + try + { + _documentClash.TestsData.TestsRunTest(existingTest); + LogManager.Info($"更新动画碰撞测试: {validCollisions.Count} 个碰撞对象"); + } + catch (Exception ex) + { + LogManager.Error($"更新测试失败: {ex.Message}"); + } + }); + } + catch (Exception ex) + { + LogManager.Error($"更新现有测试失败: {ex.Message}"); + } + } + + /// + /// 创建新的动画碰撞测试 + /// + private void CreateNewAnimationTest(string testName, List validCollisions) + { + try + { + if (!validCollisions.Any()) + return; + + var snapshotTest = new ClashTest(); + snapshotTest.DisplayName = testName; + snapshotTest.TestType = ClashTestType.Hard; + snapshotTest.Tolerance = 0.01; + + // 设置选择集 - 动画对象 + var selectionA = new ModelItemCollection(); + selectionA.Add(validCollisions[0].Item1); + snapshotTest.SelectionA.Selection.CopyFrom(selectionA); + + // 设置选择集B - 所有碰撞对象 + var selectionB = new ModelItemCollection(); + foreach (var collision in validCollisions) + { + if (!selectionB.Contains(collision.Item2)) + { + selectionB.Add(collision.Item2); + } + } + snapshotTest.SelectionB.Selection.CopyFrom(selectionB); + + // 添加到文档 + _documentClash.TestsData.TestsAddCopy(snapshotTest); + + // 运行测试 + System.Threading.Tasks.Task.Run(() => + { + try + { + var addedTest = _documentClash.TestsData.Tests + .FirstOrDefault(t => t.DisplayName == testName) as ClashTest; + + if (addedTest != null) + { + _documentClash.TestsData.TestsRunTest(addedTest); + LogManager.Info($"创建动画碰撞测试: {testName}, 碰撞数量: {validCollisions.Count}"); + } + } + catch (Exception ex) + { + LogManager.Error($"运行新测试失败: {ex.Message}"); + } + }); + } + catch (Exception ex) + { + LogManager.Error($"创建新测试失败: {ex.Message}"); + } + } + + + /// + /// 更新主测试记录(避免频繁创建新测试) + /// + private void UpdateMainClashTest(List results) + { + try + { + if (_documentClash == null || results.Count == 0) + return; + + // 限制创建测试的频率 - 只在有实际碰撞且距离最近的测试超过一定时间时创建 + if (ShouldCreateNewTest()) + { + var firstCollision = results[0]; + if (firstCollision.Item1 != null && firstCollision.Item2 != null) + { + // 检查对象是否仍然有效 + if (IsModelItemValid(firstCollision.Item1) && IsModelItemValid(firstCollision.Item2)) + { + // 创建一个快照测试来展示实际碰撞 + var timestamp = DateTime.Now.ToString("HH:mm:ss"); + var testName = $"动画碰撞快照_{timestamp}"; + + var snapshotTest = new ClashTest(); + snapshotTest.DisplayName = testName; + snapshotTest.TestType = ClashTestType.Hard; + snapshotTest.Tolerance = 0.01; + + var selectionA = new ModelItemCollection(); + selectionA.Add(firstCollision.Item1); + snapshotTest.SelectionA.Selection.CopyFrom(selectionA); + + var selectionB = new ModelItemCollection(); + selectionB.Add(firstCollision.Item2); + snapshotTest.SelectionB.Selection.CopyFrom(selectionB); + + // 添加并运行测试 + _documentClash.TestsData.TestsAddCopy(snapshotTest); + + // 重新获取并运行测试,使用异步方式避免阻塞UI + var addedTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == testName); + if (addedTest is ClashTest runTest) + { + // 使用后台任务运行测试,避免阻塞UI + System.Threading.Tasks.Task.Run(() => + { + try + { + _documentClash.TestsData.TestsRunTest(runTest); + LogManager.Info($"已创建并运行碰撞快照: {testName},展示 {firstCollision.Item1.DisplayName} <-> {firstCollision.Item2.DisplayName},结果数量: {runTest.Children.Count}"); + } + catch (Exception ex) + { + LogManager.Error($"异步运行测试失败: {ex.Message}"); + } + }); + } + + // 更新最后创建测试的时间 + _lastTestCreationTime = DateTime.Now; + } + else + { + LogManager.Warning("碰撞对象已被释放,跳过测试创建"); + } + } + } + } + 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; + } + } + + /// + /// 增强的自碰撞检测逻辑 - 多重验证确保不会检测对象与自身的碰撞 + /// + /// 动画对象 + /// 检测对象 + /// 如果是同一对象返回true + private bool IsSameObjectAdvanced(ModelItem obj1, ModelItem obj2) + { + try + { + // 1. 首要检查:对象引用相等 + if (ReferenceEquals(obj1, obj2)) + { + LogManager.Debug($"[自碰撞检测] 引用相等检测: 同一对象 {obj1.DisplayName}"); + return true; + } + + // 2. Equals方法检查(Navisworks标准检查) + if (obj1.Equals(obj2)) + { + LogManager.Debug($"[自碰撞检测] Equals方法检测: 同一对象 {obj1.DisplayName}"); + return true; + } + + // 3. InstanceGuid检查 - 但要考虑无效GUID的情况 + var guid1 = obj1.InstanceGuid; + var guid2 = obj2.InstanceGuid; + var isValidGuid1 = guid1 != Guid.Empty; + var isValidGuid2 = guid2 != Guid.Empty; + + // 只有当两个GUID都有效且相同时,才认为是同一对象 + if (isValidGuid1 && isValidGuid2 && guid1 == guid2) + { + LogManager.Debug($"[自碰撞检测] 有效GUID检测: 同一对象 {obj1.DisplayName} (GUID: {guid1})"); + return true; + } + + // 4. 如果GUID无效,使用DisplayName + 包围盒位置组合检查 + if (!isValidGuid1 || !isValidGuid2) + { + var name1 = obj1.DisplayName ?? ""; + var name2 = obj2.DisplayName ?? ""; + + // 名称相同且都有几何体,进一步检查位置 + if (name1 == name2 && !string.IsNullOrEmpty(name1) && obj1.HasGeometry && obj2.HasGeometry) + { + try + { + var bbox1 = obj1.BoundingBox(); + var bbox2 = obj2.BoundingBox(); + + // 检查包围盒是否完全相同(精确到小数点后3位) + var centerDistance = BoundingBoxGeometryUtils.CalculatePointDistance( + new Point3D((bbox1.Min.X + bbox1.Max.X) / 2, (bbox1.Min.Y + bbox1.Max.Y) / 2, (bbox1.Min.Z + bbox1.Max.Z) / 2), + new Point3D((bbox2.Min.X + bbox2.Max.X) / 2, (bbox2.Min.Y + bbox2.Max.Y) / 2, (bbox2.Min.Z + bbox2.Max.Z) / 2) + ); + + // 如果中心点距离非常小(小于1mm),认为是同一对象 + if (centerDistance < 0.001) // 1mm以内认为是同一位置 + { + LogManager.Debug($"[自碰撞检测] 位置+名称检测: 同一对象 {name1} (中心距离: {centerDistance:F6})"); + return true; + } + } + catch (Exception ex) + { + LogManager.Warning($"[自碰撞检测] 包围盒比较失败: {ex.Message}"); + } + } + } + + // 5. 最后兜底:检查空DisplayName的情况 + var displayName1 = obj1.DisplayName ?? ""; + var displayName2 = obj2.DisplayName ?? ""; + + // 如果一个对象有名称,另一个没有,但GUID相同且无效,可能是同一对象的不同表示 + if ((string.IsNullOrEmpty(displayName1) && !string.IsNullOrEmpty(displayName2)) || + (!string.IsNullOrEmpty(displayName1) && string.IsNullOrEmpty(displayName2))) + { + if (guid1 == guid2 && guid1 == Guid.Empty) + { + LogManager.Debug($"[自碰撞检测] 空名称+无效GUID检测: 疑似同一对象 '{displayName1}' vs '{displayName2}'"); + return true; + } + } + + return false; + } + catch (Exception ex) + { + LogManager.Warning($"[自碰撞检测] 检测过程出错: {ex.Message}"); + return false; // 出错时保守处理,不跳过 + } + } + + /// + /// 计算两个3D点之间的距离 + /// + // 此方法已迁移到 BoundingBoxGeometryUtils.CalculatePointDistance + [System.Obsolete("请使用 BoundingBoxGeometryUtils.CalculatePointDistance 替代", true)] + private double CalculatePointDistance(Point3D point1, Point3D point2) + { + throw new NotImplementedException("方法已迁移到工具类"); + } + + #region 智能排除策略逻辑 + + // 注意:智能排除策略相关代码已移至LogisticsAnimationManager + // ClashDetectiveIntegration现在专注于纯碰撞检测功能 + + #endregion + + private DateTime _lastTestCreationTime = DateTime.MinValue; + + /// + /// 判断是否应该创建新测试(限制创建频率) + /// + private bool ShouldCreateNewTest() + { + // 每5秒最多创建一个测试 + return DateTime.Now - _lastTestCreationTime > TimeSpan.FromSeconds(5); + } + + + /// + /// 确保动态测试存在 + /// + private void EnsureDynamicTestExists() + { + try + { + if (_dynamicClashTest == null) + { + // 查找现有的动态测试 + var existingTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); + if (existingTest is ClashTest clashTest) + { + _dynamicClashTest = clashTest; + LogManager.Debug("找到现有动态测试"); + } + else + { + LogManager.Info("动态测试不存在,创建新测试"); + SetupDynamicClashTest(); + } + } + } + catch (Exception ex) + { + LogManager.Error($"确保动态测试存在失败: {ex.Message}"); + } + } + + /// + /// 安全地更新选择集 + /// + private bool SafeUpdateSelections(ModelItem animatedObject, ModelItemCollection excludeObjects, bool isRetry = false) + { + try + { + // 创建新的选择集 + var selectionA = new ModelItemCollection(); + selectionA.Add(animatedObject); + + var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf + .Where(item => item.HasGeometry && !item.Equals(animatedObject)); + + if (excludeObjects != null) + { + allItems = allItems.Where(item => !excludeObjects.Contains(item)); + } + + var selectionB = new ModelItemCollection(); + selectionB.AddRange(allItems); + + // 尝试更新选择集 + _dynamicClashTest.SelectionA.Selection.Clear(); + _dynamicClashTest.SelectionA.Selection.CopyFrom(selectionA); + + _dynamicClashTest.SelectionB.Selection.Clear(); + _dynamicClashTest.SelectionB.Selection.CopyFrom(selectionB); + + LogManager.Debug($"选择集更新成功: A={selectionA.Count}, B={selectionB.Count}"); + return true; + } + catch (Exception ex) + { + LogManager.Error($"更新选择集失败: {ex.Message}"); + + // 只在第一次失败时尝试重新创建测试,避免无限递归 + if (!isRetry && ex.Message.Contains("Read-Only")) + { + try + { + LogManager.Info("尝试重新创建动态测试(仅重试一次)"); + SetupDynamicClashTest(); + return SafeUpdateSelections(animatedObject, excludeObjects, true); // 重试一次,标记为重试 + } + catch (Exception retryEx) + { + LogManager.Error($"重新创建测试也失败: {retryEx.Message}"); + return false; + } + } + else + { + LogManager.Error($"选择集更新失败,{(isRetry ? "重试后仍然失败" : "跳过重试")}"); + return false; + } + } + } + + /// + /// 创建临时碰撞测试 + /// + /// 动画对象 + /// 排除对象 + /// 临时测试对象 + private ClashTest CreateTemporaryClashTest(ModelItem animatedObject, ModelItemCollection excludeObjects) + { + try + { + // 创建临时测试 + var tempTest = new ClashTest(); + tempTest.DisplayName = $"临时碰撞检测_{DateTime.Now:HHmmss}"; + tempTest.TestType = ClashTestType.Hard; + tempTest.Tolerance = 0.01; // 1cm容差 + + // 设置选择集A:动画对象 + var selectionA = new ModelItemCollection(); + selectionA.Add(animatedObject); + tempTest.SelectionA.Selection.CopyFrom(selectionA); + + // 设置选择集B:所有其他对象(排除指定对象) + var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf + .Where(item => item.HasGeometry && !item.Equals(animatedObject)); + + if (excludeObjects != null) + { + allItems = allItems.Where(item => !excludeObjects.Contains(item)); + } + + var selectionB = new ModelItemCollection(); + selectionB.AddRange(allItems); + tempTest.SelectionB.Selection.CopyFrom(selectionB); + + // 添加到文档 + _documentClash.TestsData.TestsAddCopy(tempTest); + + // 重新获取添加后的测试对象 + var addedTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTest.DisplayName); + if (addedTest is ClashTest clashTest) + { + LogManager.Debug($"创建临时测试成功: {clashTest.DisplayName}, 选择集A={selectionA.Count}, B={selectionB.Count}"); + return clashTest; + } + else + { + LogManager.Error("无法获取添加后的临时测试对象"); + return null; + } + } + catch (Exception ex) + { + LogManager.Error($"创建临时碰撞测试失败: {ex.Message}"); + return null; + } + } + + /// + /// 确保测试仍然存在并且有效 + /// + private void EnsureTestExists() + { + try + { + // 检查测试是否仍然在文档中 + var existingTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); + + if (existingTest == null) + { + LogManager.Info("动态测试不存在,重新创建..."); + SetupDynamicClashTest(); + } + else if (existingTest is ClashTest clashTest) + { + // 检查测试是否可写 + try + { + // 尝试访问选择集来测试是否可写 + var testSelection = clashTest.SelectionA.Selection; + _dynamicClashTest = clashTest; + LogManager.Debug("动态测试已确认存在并可写"); + } + catch (Exception readOnlyEx) + { + if (readOnlyEx.Message.Contains("Read-Only")) + { + LogManager.Warning("现有测试为只读,重新创建新测试"); + SetupDynamicClashTest(); + } + else + { + throw; + } + } + } + } + catch (Exception ex) + { + LogManager.Error($"确保测试存在失败: {ex.Message}"); + // 重新创建测试 + SetupDynamicClashTest(); + } + } + + /// + /// 安全地运行碰撞检测 + /// + private void RunCollisionDetection() + { + try + { + // 验证测试是否可以运行 + if (_dynamicClashTest == null) + { + LogManager.Error("动态测试为空,无法运行"); + return; + } + + // 检查测试是否在文档的测试列表中 + var testExists = _documentClash.TestsData.Tests.Any(t => t.DisplayName == _dynamicClashTest.DisplayName); + if (!testExists) + { + LogManager.Info("测试不在文档中,重新添加..."); + _documentClash.TestsData.TestsAddCopy(_dynamicClashTest); + } + + // 运行碰撞检测 + _documentClash.TestsData.TestsRunTest(_dynamicClashTest); + LogManager.Debug($"碰撞检测运行完成,结果数量: {_dynamicClashTest.Children.Count}"); + } + catch (Exception ex) + { + LogManager.Error($"运行碰撞检测失败: {ex.Message}"); + // 如果运行失败,尝试重新创建测试 + LogManager.Info("尝试重新创建测试..."); + SetupDynamicClashTest(); + + // 再次尝试运行 + try + { + _documentClash.TestsData.TestsRunTest(_dynamicClashTest); + LogManager.Info("重新创建测试后运行成功"); + } + catch (Exception retryEx) + { + LogManager.Error($"重试运行测试也失败: {retryEx.Message}"); + throw; // 重新抛出异常,让上层处理 + } + } + } + + /// + /// 更新碰撞测试的选择集 + /// + private void UpdateClashTestSelections(ModelItem animatedObject, + ModelItemCollection excludeObjects) + { + try + { + // 选择集A:动画对象 + var selectionA = new ModelItemCollection(); + selectionA.Add(animatedObject); + _dynamicClashTest.SelectionA.Selection.CopyFrom(selectionA); + + // 选择集B:所有其他对象(排除指定对象) + var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf + .Where(item => item.HasGeometry && !item.Equals(animatedObject)); + + if (excludeObjects != null) + { + allItems = allItems.Where(item => !excludeObjects.Contains(item)); + } + + var selectionB = new ModelItemCollection(); + selectionB.AddRange(allItems); + _dynamicClashTest.SelectionB.Selection.CopyFrom(selectionB); + + LogManager.Debug($"更新碰撞测试选择集: A={selectionA.Count}, B={selectionB.Count}"); + } + catch (Exception ex) + { + LogManager.Error($"更新碰撞测试选择集失败: {ex.Message}"); + } + } + + /// + /// 处理碰撞检测结果 + /// + private List ProcessClashResults(ClashTest clashTest) + { + var results = new List(); + + try + { + foreach (var child in clashTest.Children) + { + if (child is ClashResult clashResult) + { + var result = new CollisionResult + { + ClashGuid = clashResult.Guid, + DisplayName = clashResult.DisplayName, + Status = clashResult.Status, + GridLocation = clashResult.DisplayName, // 使用DisplayName代替GridLocation + Item1 = clashResult.Item1, + Item2 = clashResult.Item2, + Center = clashResult.Center, + Distance = clashResult.Distance, + CreatedTime = DateTime.Now + }; + + results.Add(result); + } + else if (child is ClashResultGroup resultGroup) + { + // 处理分组结果 + foreach (var groupChild in resultGroup.Children) + { + if (groupChild is ClashResult groupResult) + { + var result = new CollisionResult + { + ClashGuid = groupResult.Guid, + DisplayName = groupResult.DisplayName, + Status = groupResult.Status, + GridLocation = groupResult.DisplayName, // 使用DisplayName代替GridLocation + Item1 = groupResult.Item1, + Item2 = groupResult.Item2, + Center = groupResult.Center, + Distance = groupResult.Distance, + CreatedTime = DateTime.Now + }; + + results.Add(result); + } + } + } + } + + LogManager.Debug($"处理碰撞结果: {results.Count} 个碰撞"); + } + catch (Exception ex) + { + LogManager.Error($"处理碰撞结果失败: {ex.Message}"); + } + + return results; + } + + /// + /// 同步结果到Clash Detective窗口 + /// + private void SyncToClashDetectiveWindow() + { + try + { + LogManager.Info("开始同步结果到Clash Detective窗口..."); + + // 强制刷新Clash Detective窗口显示 + RefreshClashDetectiveWindow(); + + // 确保测试结果在界面中可见 + EnsureTestVisibility(); + + LogManager.Info("同步到Clash Detective窗口完成"); + } + catch (Exception ex) + { + LogManager.Error($"同步到Clash Detective窗口失败: {ex.Message}"); + } + } + + /// + /// 刷新Clash Detective窗口(修复版 - 避免UI阻塞) + /// + private void RefreshClashDetectiveWindow() + { + try + { + LogManager.Info("开始刷新Clash Detective窗口..."); + + // 检查文档是否有效 + if (Application.ActiveDocument == null) + { + LogManager.Warning("文档已失效,跳过刷新"); + return; + } + + // 使用后台任务执行,避免UI阻塞 + System.Threading.Tasks.Task.Run(() => + { + try + { + // 方法1: 通过COM API刷新(如果可用) + if (_clashElement != null) + { + try + { + _clashElement.RunAllTests(); + LogManager.Info("通过COM API刷新Clash Detective窗口成功"); + } + catch (Exception ex) + { + LogManager.Warning($"COM API刷新失败: {ex.Message}"); + } + } + + // 方法2: 通过.NET API刷新(主要方法) + if (_documentClash != null) + { + try + { + // 重新运行我们的测试 + var currentTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); + if (currentTest != null && currentTest is ClashTest clashTest) + { + _documentClash.TestsData.TestsRunTest(clashTest); + LogManager.Info($"通过.NET API刷新测试结果,当前结果数: {clashTest.Children.Count}"); + } + else + { + LogManager.Info("动态测试不存在,重新创建..."); + // 在后台任务中重新创建 + SetupDynamicClashTest(); + } + } + catch (Exception ex) + { + LogManager.Warning($".NET API刷新失败: {ex.Message}"); + } + } + + // 方法3: 强制重绘视图 + try + { + // 使用延迟重绘,避免立即操作 + System.Threading.Thread.Sleep(100); + Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All); + LogManager.Info("强制重绘视图成功"); + } + catch (Exception ex) + { + LogManager.Warning($"视图重绘失败: {ex.Message}"); + } + + // 方法4: 尝试手动触发Clash Detective UI更新 + try + { + // 使用延迟操作,给UI时间处理 + System.Threading.Thread.Sleep(100); + + // 强制保存和重新加载文档状态 + var doc = Application.ActiveDocument; + if (doc != null && doc.CurrentSelection != null) + { + var tempSelection = doc.CurrentSelection.SelectedItems; + doc.CurrentSelection.Clear(); + if (tempSelection.Count > 0) + { + doc.CurrentSelection.CopyFrom(tempSelection); + } + LogManager.Info("手动触发UI更新成功"); + } + } + catch (Exception ex) + { + LogManager.Warning($"手动UI更新失败: {ex.Message}"); + } + + LogManager.Info("Clash Detective窗口刷新完成"); + + } + catch (ObjectDisposedException ex) + { + LogManager.Warning($"刷新时对象已释放: {ex.Message}"); + } + catch (Exception ex) + { + LogManager.Error($"后台刷新失败: {ex.Message}"); + } + }); + + } + catch (Exception ex) + { + LogManager.Error($"刷新Clash Detective窗口启动失败: {ex.Message}"); + } + } + + /// + /// 确保测试在界面中可见 + /// + private void EnsureTestVisibility() + { + try + { + // 检查测试是否已添加到文档 + var existingTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); + + if (existingTest == null) + { + LogManager.Warning("动态碰撞检测测试未找到,重新创建..."); + SetupDynamicClashTest(); + } + else if (existingTest is ClashTest clashTest) + { + LogManager.Info($"找到测试: {clashTest.DisplayName}, 结果数量: {clashTest.Children.Count}"); + + // 确保测试有结果 + if (clashTest.Children.Count > 0) + { + LogManager.Info("测试已有结果,应该在Clash Detective窗口中可见"); + } + else + { + LogManager.Info("测试无结果,可能需要重新运行"); + } + } + } + catch (Exception ex) + { + LogManager.Error($"确保测试可见性失败: {ex.Message}"); + } + } + + /// + /// 高亮显示碰撞对象 + /// + /// 碰撞结果 + /// 是否清除之前的高亮 + public void HighlightCollisions(List results, bool clearPrevious = true) + { + HighlightCollisions(results, Color.Red, clearPrevious); + } + + /// + /// 高亮显示碰撞对象(支持自定义颜色) + /// + /// 碰撞结果 + /// 高亮颜色 + /// 是否清除之前的高亮 + public void HighlightCollisions(List results, Color highlightColor, bool clearPrevious = true) + { + try + { + var doc = Application.ActiveDocument; + + // 根据参数决定是否清除之前的高亮 + if (clearPrevious) + { + doc.Models.ResetAllTemporaryMaterials(); + } + + if (results != null && results.Count > 0) + { + var collidingItems = new ModelItemCollection(); + + foreach (var result in results) + { + if (result.Item1 != null && !collidingItems.Contains(result.Item1)) + collidingItems.Add(result.Item1); + if (result.Item2 != null && !collidingItems.Contains(result.Item2)) + collidingItems.Add(result.Item2); + } + + // 高亮碰撞对象(使用指定颜色) + doc.Models.OverrideTemporaryColor(collidingItems, highlightColor); + + LogManager.Info($"高亮显示 {collidingItems.Count} 个碰撞对象,颜色: {highlightColor}"); + } + else + { + LogManager.Debug("没有碰撞结果需要高亮"); + } + } + catch (Exception ex) + { + LogManager.Error($"高亮显示碰撞对象失败: {ex.Message}"); + } + } + + /// + /// 清除所有高亮 + /// + public void ClearHighlights() + { + try + { + lock (_highlightLock) + { + var doc = Application.ActiveDocument; + doc.Models.ResetAllTemporaryMaterials(); + _activeHighlights.Clear(); + LogManager.Info("已清除所有碰撞高亮"); + } + } + catch (Exception ex) + { + LogManager.Error($"清除高亮失败: {ex.Message}"); + } + } + + /// + /// 按类别管理高亮显示 + /// + /// 高亮类别(如"animation", "independent", "report"等) + /// 碰撞结果 + /// 高亮颜色 + /// 是否清除其他类别的高亮 + public void ManageHighlightsByCategory(string category, List results, + Color highlightColor, bool clearOtherCategories = false) + { + try + { + lock (_highlightLock) + { + var doc = Application.ActiveDocument; + + // 清除其他类别的高亮(如果需要) + if (clearOtherCategories) + { + doc.Models.ResetAllTemporaryMaterials(); + _activeHighlights.Clear(); + } + else if (_activeHighlights.ContainsKey(category)) + { + // 只清除当前类别的高亮 + var existingItems = _activeHighlights[category]; + // Navisworks不支持按对象清除高亮,需要全部重置后重新应用其他类别 + doc.Models.ResetAllTemporaryMaterials(); + _activeHighlights.Remove(category); + + // 重新应用其他类别的高亮 + foreach (var kvp in _activeHighlights) + { + // 这里需要根据类别使用不同颜色重新高亮 + var categoryColor = GetCategoryColor(kvp.Key); + doc.Models.OverrideTemporaryColor(kvp.Value, categoryColor); + } + } + + if (results != null && results.Count > 0) + { + var collidingItems = new ModelItemCollection(); + + foreach (var result in results) + { + if (result.Item1 != null && !collidingItems.Contains(result.Item1)) + collidingItems.Add(result.Item1); + if (result.Item2 != null && !collidingItems.Contains(result.Item2)) + collidingItems.Add(result.Item2); + } + + // 应用高亮 + doc.Models.OverrideTemporaryColor(collidingItems, highlightColor); + + // 记录活跃高亮 + _activeHighlights[category] = collidingItems; + + LogManager.Info($"类别 '{category}' 高亮显示 {collidingItems.Count} 个碰撞对象,颜色: {highlightColor}"); + } + else + { + LogManager.Debug($"类别 '{category}' 没有碰撞结果需要高亮"); + } + } + } + catch (Exception ex) + { + LogManager.Error($"按类别管理高亮失败 [{category}]: {ex.Message}"); + } + } + + /// + /// 根据类别获取默认颜色 + /// + private Color GetCategoryColor(string category) + { + switch (category.ToLower()) + { + case "animation": + case "runtime": + return Color.Red; // 动画过程:红色 + case "independent": + case "static": + return Color.Red; // 独立检测:红色 (Navisworks API中没有DarkRed) + case "report": + return Color.Green; // 报告查看:绿色 (Navisworks API中没有Orange) + case "preview": + return Color.Blue; // 预览:蓝色 (Navisworks API中没有Yellow) + default: + return Color.White; // 未知类别:白色 (Navisworks API中没有Magenta) + } + } + + /// + /// 获取当前活跃高亮信息 + /// + public string GetActiveHighlightInfo() + { + try + { + lock (_highlightLock) + { + if (_activeHighlights.Count == 0) + { + return "当前无活跃高亮"; + } + + var info = new StringBuilder(); + info.AppendLine("=== 当前活跃高亮 ==="); + + foreach (var kvp in _activeHighlights) + { + var category = kvp.Key; + var items = kvp.Value; + var color = GetCategoryColor(category); + + info.AppendLine($"类别: {category}"); + info.AppendLine($" 对象数量: {items.Count}"); + info.AppendLine($" 颜色: {color}"); + info.AppendLine(); + } + + return info.ToString(); + } + } + catch (Exception ex) + { + LogManager.Error($"获取高亮信息失败: {ex.Message}"); + return "获取高亮信息失败"; + } + } + + /// + /// 基于包围盒的快速碰撞检测(不用Clash Detective) + /// + private List DetectCollisionsSimple(ModelItem animatedObject, + ModelItemCollection excludeObjects, double detectionGap) + { + var results = new List(); + + try + { + LogManager.Debug($"开始快速碰撞检测,动画对象: {animatedObject.DisplayName}"); + + var animatedBoundingBox = animatedObject.BoundingBox(); + LogManager.Debug($"动画对象包围盒: Min({animatedBoundingBox.Min.X:F2}, {animatedBoundingBox.Min.Y:F2}, {animatedBoundingBox.Min.Z:F2}) Max({animatedBoundingBox.Max.X:F2}, {animatedBoundingBox.Max.Y:F2}, {animatedBoundingBox.Max.Z:F2})"); + + // 性能分析计时器 + var exclusionStopwatch = new System.Diagnostics.Stopwatch(); + var getAllItemsStopwatch = new System.Diagnostics.Stopwatch(); + var excludeLogicStopwatch = new System.Diagnostics.Stopwatch(); + var boundingBoxStopwatch = new System.Diagnostics.Stopwatch(); + var intersectionStopwatch = new System.Diagnostics.Stopwatch(); + var distanceStopwatch = new System.Diagnostics.Stopwatch(); + + // 计时:构建排除列表 + exclusionStopwatch.Start(); + var exclusionList = new List(); + + // 1. 排除动画对象本身及其所有子对象 + exclusionList.AddRange(animatedObject.DescendantsAndSelf.Where(d => d.HasGeometry)); + + // 2. 合并用户指定的排除对象 + if (excludeObjects != null) + { + foreach (var item in excludeObjects) + { + if (!exclusionList.Contains(item)) + { + exclusionList.Add(item); + } + } + } + exclusionStopwatch.Stop(); + LogManager.Debug($"构建排除列表完成,耗时: {exclusionStopwatch.ElapsedMilliseconds}ms,排除对象: {exclusionList.Count} 个"); + + // 🔥 使用预构建的缓存获取对象列表 + getAllItemsStopwatch.Start(); + List itemList; + + lock (_cacheLock) + { + if (_allGeometryItemsCache != null) + { + // 从缓存中过滤掉动画对象本身 + itemList = _allGeometryItemsCache.Where(item => !item.Equals(animatedObject)).ToList(); + LogManager.Debug($"使用缓存获取对象列表,缓存对象总数: {_allGeometryItemsCache.Count},过滤后: {itemList.Count}"); + } + else + { + // 缓存不存在,回退到实时获取(不应该发生,但保险起见) + LogManager.Warning("几何对象缓存不存在,回退到实时获取(这可能影响性能)"); + var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf + .Where(item => item.HasGeometry && !item.Equals(animatedObject)); + itemList = allItems.ToList(); + } + } + + getAllItemsStopwatch.Stop(); + LogManager.Debug($"获取对象列表完成,耗时: {getAllItemsStopwatch.ElapsedMilliseconds}ms,对象总数: {itemList.Count}"); + + int checkedCount = 0; + int excludedCount = 0; + int boundingBoxCallCount = 0; + int intersectionCallCount = 0; + int distanceCallCount = 0; + + foreach (var item in itemList) + { + try + { + // 计时:排除逻辑检查(现在使用缓存,应该很快) + excludeLogicStopwatch.Start(); + bool shouldExclude = ShouldExcludeFromCollisionDetectionSimple(item, exclusionList); + excludeLogicStopwatch.Stop(); + + if (shouldExclude) + { + excludedCount++; + continue; + } + + // 计时:包围盒获取 + boundingBoxStopwatch.Start(); + var itemBoundingBox = item.BoundingBox(); + boundingBoxStopwatch.Stop(); + boundingBoxCallCount++; + + // 使用UI设置的检测间隙作为碰撞容差 + var tolerance = detectionGap; + + // 计时:相交检测 + intersectionStopwatch.Start(); + bool intersects = BoundingBoxGeometryUtils.BoundingBoxesIntersectWithTolerance(animatedBoundingBox, itemBoundingBox, tolerance); + intersectionStopwatch.Stop(); + intersectionCallCount++; + + // 检查是否在容差范围内接近 + if (intersects) + { + // 计时:距离计算 + distanceStopwatch.Start(); + var distance = BoundingBoxGeometryUtils.CalculateDistance(animatedBoundingBox, itemBoundingBox); + distanceStopwatch.Stop(); + distanceCallCount++; + + // 只有真正相交(距离为0)或在检测间隙内的才算碰撞 + if (distance <= tolerance) + { + // 🔧 智能容器映射:使用有名称的容器对象而非几何体子对象 + var mappedAnimatedObject = GetCollisionObjectWithValidIdentity(animatedObject); + var mappedCollisionObject = GetCollisionObjectWithValidIdentity(item); + + // 检查是否进行了容器映射 + bool hasMapping1 = !mappedAnimatedObject.Equals(animatedObject); + bool hasMapping2 = !mappedCollisionObject.Equals(item); + + LogManager.Info($"[容器映射] 原始对象: {animatedObject.DisplayName} -> 容器对象: {mappedAnimatedObject.DisplayName} (映射: {hasMapping1})"); + LogManager.Info($"[容器映射] 原始对象: {item.DisplayName} -> 容器对象: {mappedCollisionObject.DisplayName} (映射: {hasMapping2})"); + + var result = new CollisionResult + { + ClashGuid = Guid.NewGuid(), + DisplayName = $"纯碰撞检测: {mappedAnimatedObject.DisplayName} <-> {mappedCollisionObject.DisplayName}", + Status = ClashResultStatus.New, + Item1 = mappedAnimatedObject, // 使用映射后的容器对象 + Item2 = mappedCollisionObject, // 使用映射后的容器对象 + OriginalItem1 = animatedObject, // 保存原始几何体对象 + OriginalItem2 = item, // 保存原始几何体对象 + HasContainerMapping = hasMapping1 || hasMapping2, // 标记是否进行了映射 + CreatedTime = DateTime.Now, + Distance = distance, + Center = BoundingBoxGeometryUtils.CalculateCenter(animatedBoundingBox, itemBoundingBox) + }; + + results.Add(result); + LogManager.Info($"检测到碰撞: {mappedAnimatedObject.DisplayName} <-> {mappedCollisionObject.DisplayName},距离: {result.Distance:F2}"); + } + } + + checkedCount++; + } + catch (Exception itemEx) + { + LogManager.Warning($"检查单个对象碰撞时出错 {item.DisplayName}: {itemEx.Message}"); + } + } + + if (results.Count == 0) + { + LogManager.Info($"未发现碰撞"); + } + + // 更新动画碰撞计数器 + _animationCollisionCount = results.Count; + LogManager.Info($"动画碰撞计数器已更新: {_animationCollisionCount}"); + } + catch (Exception ex) + { + LogManager.Error($"纯碰撞检测失败: {ex.Message}", ex); + } + + return results; + } + + /// + /// 简化的排除判断逻辑(不依赖LogisticsAnimationManager) + /// + /// 要测试的对象 + /// 排除列表 + /// 如果应该排除返回true + private bool ShouldExcludeFromCollisionDetectionSimple(ModelItem testObject, List exclusionList) + { + try + { + // 1. 基本排除列表检查 + if (exclusionList != null && exclusionList.Contains(testObject)) + { + return true; + } + + // 2. 通道对象检查(保留原有逻辑) + if (IsChannelObject(testObject)) + { + return true; + } + + return false; + } + catch (Exception ex) + { + LogManager.Warning($"简化排除判断异常: {ex.Message}"); + return false; // 出错时保守处理,不排除 + } + } + + /// + /// 检查物体是否为通道物体(通道物体在碰撞检测时不应该变红) + /// + private bool IsChannelObject(ModelItem item) + { + try + { + // 使用缓存的通道对象集合 + if (_channelObjectsCache == null) + { + BuildChannelObjectsCache(); + } + + // 直接查询缓存,避免递归和重复属性查询 + return _channelObjectsCache.Contains(item); + } + catch (Exception ex) + { + LogManager.Warning($"检查通道物体时出错 {item.DisplayName}: {ex.Message}"); + return false; + } + } + + /// + /// 构建通道对象缓存,一次性扫描所有对象,避免重复的属性查询 + /// + /// + /// 构建通道对象缓存,一次性扫描所有对象,避免重复的属性查询 + /// + 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; + LogManager.Debug("[通道缓存] 开始使用SearchAPI构建通道对象缓存"); + + // 🔥 一行调用:直接获取所有可通行的物流模型项(使用优化后的SearchAPI) + var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document); + LogManager.Info($"[通道缓存] 使用SearchAPI直接获取到 {allChannelItems.Count} 个可通行物流元素"); + + if (allChannelItems.Count == 0) + { + LogManager.Warning("[通道缓存] ⚠️ 未找到任何可通行的物流元素,请检查模型中的物流属性设置"); + cacheStopwatch.Stop(); + return; + } + + // 智能收集通道相关节点(包括父节点、同级节点、子节点等) + foreach (var channelItem in allChannelItems) + { + try + { + LogManager.Debug($"[通道收集] 开始处理通道节点: '{channelItem.DisplayName}'"); + + // 🔥 直接使用 ModelItemAnalysisHelper 的方法收集相关节点 + var itemRelatedNodes = ModelItemAnalysisHelper.CollectRelatedNodes(channelItem); + + // 将结果添加到缓存中 + foreach (var node in itemRelatedNodes) + { + _channelObjectsCache.Add(node); + } + + LogManager.Debug($"[通道收集] 从 '{channelItem.DisplayName}' 收集到 {itemRelatedNodes.Count} 个相关节点"); + } + 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} 个"); + + // 列出找到的通道根对象 + if (allChannelItems.Count > 0) + { + LogManager.Info("[通道缓存] 找到的可通行物流对象列表:"); + for (int i = 0; i < Math.Min(allChannelItems.Count, 10); i++) // 最多显示前10个 + { + var item = allChannelItems[i]; + LogManager.Info($" {i + 1}. {item.DisplayName} (HasGeometry: {item.HasGeometry})"); + } + if (allChannelItems.Count > 10) + { + LogManager.Info($" ... 还有 {allChannelItems.Count - 10} 个对象(省略显示)"); + } + } + } + catch (Exception ex) + { + LogManager.Error($"构建通道对象缓存时发生错误: {ex.Message}", ex); + _channelObjectsCache = new HashSet(); // 创建空缓存,避免重复构建 + } + } + } + + /// + /// 清除通道对象缓存,在模型变化时调用 + /// + public static void ClearChannelObjectsCache() + { + lock (_cacheLock) + { + _channelObjectsCache = null; + LogManager.Debug("通道对象缓存已清除"); + } + } + + + /// + /// 构建几何对象列表缓存,一次性获取所有几何对象 + /// + /// + /// 构建几何对象列表缓存,一次性获取所有几何对象 + /// + public static void BuildAllGeometryItemsCache() + { + lock (_cacheLock) + { + if (_allGeometryItemsCache != null) return; // 双重检查锁定 + + var cacheStopwatch = new System.Diagnostics.Stopwatch(); + cacheStopwatch.Start(); + + try + { + var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf + .Where(item => item.HasGeometry); + + _allGeometryItemsCache = allItems.ToList(); + + cacheStopwatch.Stop(); + LogManager.Info($"几何对象列表缓存构建完成,耗时: {cacheStopwatch.ElapsedMilliseconds}ms"); + LogManager.Info($" - 缓存对象总数: {_allGeometryItemsCache.Count} 个"); + } + catch (Exception ex) + { + LogManager.Error($"构建几何对象列表缓存时发生错误: {ex.Message}", ex); + _allGeometryItemsCache = new List(); // 创建空缓存,避免重复构建 + } + } + } + + /// + /// 清除所有缓存,在模型变化时调用 + /// + public static void ClearAllCaches() + { + lock (_cacheLock) + { + _channelObjectsCache = null; + _allGeometryItemsCache = null; + LogManager.Debug("所有对象缓存已清除"); + } + } + + /// + /// 增强的碰撞对象信息,包含容器映射 + /// + /// 原始碰撞对象(可能是几何体子对象) + /// 包含完整标识信息的对象 + private static ModelItem GetCollisionObjectWithValidIdentity(ModelItem originalItem) + { + if (originalItem == null) return null; + + // 如果原对象已经有有效名称,直接返回 + if (!string.IsNullOrEmpty(originalItem.DisplayName) && originalItem.DisplayName.Trim().Length > 0) + { + return originalItem; + } + + // 否则查找有名称的父级容器 + var containerObject = ModelItemAnalysisHelper.FindNamedParentContainer(originalItem); + + LogManager.Info($"[碰撞对象映射] 原对象: '{originalItem.DisplayName}' -> 容器对象: '{containerObject?.DisplayName}'"); + + return containerObject ?? originalItem; + } + + /// + /// 计算两个包围盒之间的最短距离(真实碰撞距离) + /// + // 此方法已迁移到 BoundingBoxGeometryUtils.CalculateDistance + [System.Obsolete("请使用 BoundingBoxGeometryUtils.CalculateDistance 替代", true)] + private double CalculateDistance(BoundingBox3D box1, BoundingBox3D box2) + { + throw new NotImplementedException("方法已迁移到工具类"); + } + + + /// + /// 计算两个包围盒之间的中心点 + /// + // 此方法已迁移到 BoundingBoxGeometryUtils.CalculateCenter + [System.Obsolete("请使用 BoundingBoxGeometryUtils.CalculateCenter 替代", true)] + private Point3D CalculateCenter(BoundingBox3D box1, BoundingBox3D box2) + { + throw new NotImplementedException("方法已迁移到工具类"); + } + + /// + /// 检查两个包围盒是否相交(带容差) + /// + // 此方法已迁移到 BoundingBoxGeometryUtils.BoundingBoxesIntersectWithTolerance + [System.Obsolete("请使用 BoundingBoxGeometryUtils.BoundingBoxesIntersectWithTolerance 替代", true)] + private bool BoundingBoxesIntersectWithTolerance(BoundingBox3D box1, BoundingBox3D box2, double tolerance) + { + throw new NotImplementedException("方法已迁移到工具类"); + } + + /// + /// 检查两个包围盒是否相交 + /// + // 此方法已迁移到 BoundingBoxGeometryUtils.BoundingBoxesIntersect + [System.Obsolete("请使用 BoundingBoxGeometryUtils.BoundingBoxesIntersect 替代", true)] + private bool BoundingBoxesIntersect(BoundingBox3D box1, BoundingBox3D box2) + { + throw new NotImplementedException("方法已迁移到工具类"); + } + + /// + /// 获取当前测试状态信息(用于调试和验证) + /// + /// 测试状态信息 + public string GetTestStatusInfo() + { + try + { + if (!_isInitialized) + { + return "Clash Detective 集成未初始化"; + } + + if (_documentClash == null) + { + return "无法访问 Clash Detective 文档"; + } + + var testInfo = new StringBuilder(); + testInfo.AppendLine("=== Clash Detective 集成状态 ==="); + testInfo.AppendLine($"初始化状态: {(_isInitialized ? "已初始化" : "未初始化")}"); + testInfo.AppendLine($"COM API 连接: {(_clashElement != null ? "已连接" : "未连接")}"); + testInfo.AppendLine($"文档 Clash 对象: {(_documentClash != null ? "可用" : "不可用")}"); + + // 检查测试列表 + var tests = _documentClash.TestsData.Tests; + testInfo.AppendLine($"总测试数量: {tests.Count}"); + + var dynamicTest = tests.FirstOrDefault(t => t.DisplayName == "动态运输路径碰撞检测"); + if (dynamicTest != null && dynamicTest is ClashTest clashTest) + { + testInfo.AppendLine($"动态测试: 已找到"); + testInfo.AppendLine($"测试类型: {clashTest.TestType}"); + testInfo.AppendLine($"容差: {clashTest.Tolerance}"); + testInfo.AppendLine($"结果数量: {clashTest.Children.Count}"); + + if (clashTest.Children.Count > 0) + { + testInfo.AppendLine("最近结果:"); + int count = 0; + foreach (var child in clashTest.Children) + { + if (child is ClashResult result && count < 3) + { + testInfo.AppendLine($" - {result.DisplayName}: {result.Status}"); + count++; + } + } + } + } + else + { + testInfo.AppendLine($"动态测试: 未找到"); + } + + testInfo.AppendLine("=== 状态信息结束 ==="); + return testInfo.ToString(); + } + catch (Exception ex) + { + return $"获取测试状态失败: {ex.Message}"; + } + } + + /// + /// 测试容器映射功能是否正常工作 + /// + public void TestContainerMapping() + { + try + { + LogManager.Info("=== [容器映射测试] 开始测试容器映射功能 ==="); + + var testResults = new List(); + + // 获取一些测试对象 + var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf + .Where(item => item.HasGeometry).Take(10).ToList(); + + LogManager.Info($"[容器映射测试] 找到 {allItems.Count} 个几何体对象进行测试"); + + foreach (var item in allItems) + { + try + { + var originalName = item.DisplayName ?? "无名称"; + var mappedObject = GetCollisionObjectWithValidIdentity(item); + var mappedName = mappedObject?.DisplayName ?? "无名称"; + + bool hasMapping = !mappedObject.Equals(item); + + testResults.Add($"原始: '{originalName}' -> 映射: '{mappedName}' (映射: {hasMapping})"); + + LogManager.Info($"[容器映射测试] {testResults.Last()}"); + + // 验证映射对象的有效性 + if (hasMapping) + { + var isValidOriginal = IsModelItemValid(item); + var isValidMapped = IsModelItemValid(mappedObject); + LogManager.Info($" - 原始对象有效性: {isValidOriginal}"); + LogManager.Info($" - 映射对象有效性: {isValidMapped}"); + LogManager.Info($" - 原始对象GUID: {item.InstanceGuid}"); + LogManager.Info($" - 映射对象GUID: {mappedObject.InstanceGuid}"); + } + } + catch (Exception itemEx) + { + LogManager.Warning($"[容器映射测试] 测试单个对象失败: {itemEx.Message}"); + } + } + + LogManager.Info("=== [容器映射测试] 测试结果汇总 ==="); + int mappingCount = testResults.Count(r => r.Contains("映射: True")); + LogManager.Info($"测试对象总数: {testResults.Count}"); + LogManager.Info($"发生映射的对象: {mappingCount}"); + LogManager.Info($"映射成功率: {(mappingCount * 100.0 / Math.Max(testResults.Count, 1)):F1}%"); + + // 测试碰撞结果数据结构 + var testCollision = new CollisionResult + { + ClashGuid = Guid.NewGuid(), + DisplayName = "测试碰撞", + Item1 = allItems.FirstOrDefault(), + Item2 = allItems.Skip(1).FirstOrDefault(), + OriginalItem1 = allItems.FirstOrDefault(), + OriginalItem2 = allItems.Skip(1).FirstOrDefault(), + HasContainerMapping = true, + HasPositionInfo = true + }; + + LogManager.Info($"[容器映射测试] CollisionResult数据结构测试:"); + LogManager.Info($" - GetValidItem1: {testCollision.GetValidItem1()?.DisplayName ?? "NULL"}"); + LogManager.Info($" - GetValidItem2: {testCollision.GetValidItem2()?.DisplayName ?? "NULL"}"); + LogManager.Info($" - GetOriginalItem1: {testCollision.GetOriginalItem1()?.DisplayName ?? "NULL"}"); + LogManager.Info($" - GetOriginalItem2: {testCollision.GetOriginalItem2()?.DisplayName ?? "NULL"}"); + LogManager.Info($" - HasContainerMapping: {testCollision.HasContainerMapping}"); + + LogManager.Info("=== [容器映射测试] 测试完成 ==="); + } + catch (Exception ex) + { + LogManager.Error($"[容器映射测试] 测试失败: {ex.Message}"); + LogManager.Error($"[容器映射测试] 堆栈跟踪: {ex.StackTrace}"); + } + } + + // 已删除:ForceShowTestResults 和 CreateDemoTest - 不再需要这些已废弃的方法 + + /// + /// 清理资源 + /// + public void Cleanup() + { + try + { + LogManager.Info("开始Clash Detective集成资源清理..."); + + // 检查文档是否仍然有效,如果无效则跳过操作 + var document = Application.ActiveDocument; + if (document == null || document.IsClear) + { + LogManager.Info("文档已无效,跳过资源清理操作"); + return; + } + + // 清除临时高亮 + try + { + document.Models.ResetAllTemporaryMaterials(); + LogManager.Info("已清除临时材质高亮"); + } + catch (ObjectDisposedException) + { + LogManager.Info("文档对象已释放,跳过材质清理"); + } + catch (Exception ex) + { + LogManager.Warning($"清除临时材质失败: {ex.Message}"); + } + + // 清空内存中的结果 + _currentCollisions?.Clear(); + _cachedResults?.Clear(); + + // 清理高亮管理器 + lock (_highlightLock) + { + _activeHighlights?.Clear(); + } + + // 清理引用,防止内存泄漏 + _clashElement = null; + _dynamicClashTest = null; + _documentClash = null; + _state = null; + + _isInitialized = false; + + LogManager.Info("Clash Detective集成资源清理完成"); + } + catch (Exception ex) + { + LogManager.Error($"清理Clash Detective资源时发生异常: {ex.Message}"); + } + } + + // 已删除:ForceCloseClashDetectiveDialogs - 不再需要强制关闭对话框 + + // 已删除:CleanupSnapshotTests - 不再需要,使用动画结束后汇总 + + /// + /// 触发碰撞检测事件 + /// + private void OnCollisionDetected(CollisionDetectedEventArgs e) + { + CollisionDetected?.Invoke(this, e); + } + } + + /// + /// 碰撞结果数据结构 + /// + public class CollisionResult + { + 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 Item1Position { get; set; } + public Point3D Item2Position { get; set; } + public bool HasPositionInfo { get; set; } + + // 📦 容器映射信息:记录原始几何体对象引用 + public ModelItem OriginalItem1 { get; set; } // 原始几何体对象 + public ModelItem OriginalItem2 { get; set; } // 原始几何体对象 + public bool HasContainerMapping { get; set; } // 标识是否进行了容器映射 + + // 📍 获取最终用于ClashDetective的有效对象 + public ModelItem GetValidItem1() => Item1; // 容器对象,用于ClashDetective选择集 + public ModelItem GetValidItem2() => Item2; // 容器对象,用于ClashDetective选择集 + + // 📍 获取原始几何体对象(用于精确位置信息) + public ModelItem GetOriginalItem1() => OriginalItem1 ?? Item1; + public ModelItem GetOriginalItem2() => OriginalItem2 ?? Item2; + } + + /// + /// 碰撞检测事件参数 + /// + 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; + } + } +} \ No newline at end of file diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index 876b173..60ea622 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -1271,26 +1271,32 @@ namespace NavisworksTransport.UI.WPF.ViewModels PathName = CurrentPathRoute?.Name ?? "未知路径" }; - // 获取所有动画相关的碰撞测试 + // 获取所有动画相关的碰撞测试(包括新的分组测试) var animationTests = documentClash.TestsData.Tests.Cast() - .Where(t => t.DisplayName.Contains("动画路径碰撞")) + .Where(t => t.DisplayName.Contains("动画路径碰撞") || + t.DisplayName.Contains("物流路径碰撞汇总") || + t.DisplayName.Contains("动画碰撞")) .ToList(); LogManager.Info($"找到 {animationTests.Count} 个动画碰撞测试"); foreach (var test in animationTests) { + // 递归统计测试中的碰撞总数(包括分组内的) + var totalCollisionCount = GetTotalClashResultCountForReport(test); + var testInfo = new CollisionTestInfo { TestName = test.DisplayName, TestType = test.TestType.ToString(), Tolerance = test.Tolerance, - CollisionCount = test.Children.Count, + CollisionCount = totalCollisionCount, Status = test.Status.ToString() }; - // 获取每个碰撞的详细信息 - foreach (var clashResult in test.Children.Cast()) + // 递归获取每个碰撞的详细信息(包括分组内的) + var allClashResults = GetAllClashResultsFromTest(test); + foreach (var clashResult in allClashResults) { var collision = new CollisionDetailInfo { @@ -1352,8 +1358,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels { if (reportData.IsValid) { - // 直接使用报告数据统计 - CollisionSummary = $"动画检测: {reportData.TotalTests}个 | Clash Detective权威结果: {reportData.TotalCollisions}个"; + // 🔧 修复:显示动画过程缓存的碰撞数量,而非测试数量 + var animationCollisionCount = _clashIntegration?.AnimationCollisionCount ?? 0; + CollisionSummary = $"动画碰撞: {animationCollisionCount}个 | Clash Detective权威结果: {reportData.TotalCollisions}个"; } else { @@ -1573,6 +1580,102 @@ namespace NavisworksTransport.UI.WPF.ViewModels } } + /// + /// 递归统计测试中的碰撞结果总数(包括分组内的结果)- 用于报告 + /// + private int GetTotalClashResultCountForReport(ClashTest test) + { + if (test == null) return 0; + + int totalCount = 0; + foreach (var child in test.Children) + { + if (child is ClashResult) + { + totalCount++; + } + else if (child is ClashResultGroup group) + { + // 递归统计分组内的结果 + totalCount += CountClashResultsInGroupForReport(group); + } + } + + return totalCount; + } + + /// + /// 递归统计分组内的碰撞结果数量 - 用于报告 + /// + private int CountClashResultsInGroupForReport(ClashResultGroup group) + { + if (group == null) return 0; + + int count = 0; + foreach (var child in group.Children) + { + if (child is ClashResult) + { + count++; + } + else if (child is ClashResultGroup nestedGroup) + { + // 递归处理嵌套分组 + count += CountClashResultsInGroupForReport(nestedGroup); + } + } + + return count; + } + + /// + /// 递归获取测试中的所有碰撞结果(包括分组内的)- 用于报告 + /// + private List GetAllClashResultsFromTest(ClashTest test) + { + var results = new List(); + if (test == null) return results; + + foreach (var child in test.Children) + { + if (child is ClashResult result) + { + results.Add(result); + } + else if (child is ClashResultGroup group) + { + // 递归获取分组内的结果 + results.AddRange(GetAllClashResultsFromGroup(group)); + } + } + + return results; + } + + /// + /// 递归获取分组内的所有碰撞结果 - 用于报告 + /// + private List GetAllClashResultsFromGroup(ClashResultGroup group) + { + var results = new List(); + if (group == null) return results; + + foreach (var child in group.Children) + { + if (child is ClashResult result) + { + results.Add(result); + } + else if (child is ClashResultGroup nestedGroup) + { + // 递归处理嵌套分组 + results.AddRange(GetAllClashResultsFromGroup(nestedGroup)); + } + } + + return results; + } + #endregion } } \ No newline at end of file