diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index a3b7627..d734751 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -2,6 +2,11 @@ ## 功能点 +### [2026/1/13] + +1. [ ] (优化)检查API调用的线程安全问题,提高插件的稳定性 +2. [ ] (优化)减少ClashDetective检测部分的耗时 + ### [2026/1/6] 1. [x] (BUG)虚拟车辆模型每次点击都重建 diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs index 0fa8166..e79b445 100644 --- a/src/Core/Collision/ClashDetectiveIntegration.cs +++ b/src/Core/Collision/ClashDetectiveIntegration.cs @@ -614,7 +614,11 @@ namespace NavisworksTransport _currentTestName = mainTestName; LogManager.Info($"[分组测试] 创建主测试: {mainTestName}"); + // 移除事务以优化性能(事务开销约30-65%) + // 对于碰撞检测场景,事务的必要性很低,即使失败影响也很小 + ClashTest addedMainTest = null; Progress progress = null; + try { // 开始进度条(ClashDetective本身会显示进度,这里只提供提示信息) @@ -622,244 +626,236 @@ namespace NavisworksTransport "碰撞检测数据分析中,请稍候..." ); - // 使用事务确保操作的原子性 - using (var transaction = doc.BeginTransaction("创建分组碰撞测试")) + // 第一步:创建主测试(不包含选择集,纯容器) + var mainTest = new ClashTest { - // 第一步:创建主测试(不包含选择集,纯容器) - var mainTest = new ClashTest - { - DisplayName = mainTestName, - // HardConservative 模式的缺点是性能低。如果要提高性能,可以用Hard模式 - TestType = ClashTestType.Hard, - Tolerance = detectionGap, - Guid = Guid.Empty, - MergeComposites = true - }; + DisplayName = mainTestName, + // HardConservative 模式的缺点是性能低。如果要提高性能,可以用Hard模式 + TestType = ClashTestType.Hard, + Tolerance = detectionGap, + Guid = Guid.Empty, + MergeComposites = true + }; - // 添加主测试到文档 - _documentClash.TestsData.TestsAddCopy(mainTest); - LogManager.Info($"[分组测试] 主测试已添加到文档"); + // 添加主测试到文档 + _documentClash.TestsData.TestsAddCopy(mainTest); + LogManager.Info($"[分组测试] 主测试已添加到文档"); - // 获取添加后的测试对象引用 - var addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest; - if (addedMainTest == null) + // 获取添加后的测试对象引用 + addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest; + if (addedMainTest == null) + { + LogManager.Error("无法获取添加后的主测试对象"); + return; + } + + // 第二步:创建分组并添加碰撞结果(应用智能去重) + // 1. 分组:按碰撞对象对分组 + var groupedCollisions = validCollisions + .GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 }) + .ToList(); + + LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对"); + + // 缓存去重后的碰撞结果(每组取第一个) + lock (_resultsLock) + { + _deduplicatedCollisionResults.Clear(); + foreach (var group in groupedCollisions) { - LogManager.Error("无法获取添加后的主测试对象"); - return; + _deduplicatedCollisionResults.Add(group.First()); + } + LogManager.Debug($"[去重缓存] 已缓存 {_deduplicatedCollisionResults.Count} 个去重后的碰撞结果"); + } + + var collisionGroup = new ClashResultGroup + { + DisplayName = $"碰撞检测组 ({groupedCollisions.Count} 个唯一碰撞对)" + }; + + LogManager.Info($"[分组测试] 创建碰撞分组: {collisionGroup.DisplayName}"); + + int confirmedCount = 0; + int skippedCount = 0; + + // 2. 遍历每一组 + for (int groupIndex = 0; groupIndex < groupedCollisions.Count; groupIndex++) + { + // 检查用户是否取消 + if (progress.IsCanceled) + { + LogManager.Info($"[分组测试] 用户取消操作,已处理 {groupIndex}/{groupedCollisions.Count} 组"); + break; } + var group = groupedCollisions[groupIndex]; - // 第二步:创建分组并添加碰撞结果(应用智能去重) - // 1. 分组:按碰撞对象对分组 - var groupedCollisions = validCollisions - .GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 }) - .ToList(); + // 3. 排序:按距离/重叠深度排序,优先检测最严重的碰撞 + // 注意:Distance通常越小(或负值越大)表示碰撞越深,具体取决于计算方式 + // 这里假设Distance越小越严重 + var sortedCandidates = group.OrderBy(c => c.Distance).ToList(); - LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对"); + bool pairConfirmed = false; - // 缓存去重后的碰撞结果(每组取第一个) - lock (_resultsLock) + // 4. 验证即止:逐个检测候选帧 + for (int i = 0; i < sortedCandidates.Count; i++) { - _deduplicatedCollisionResults.Clear(); - foreach (var group in groupedCollisions) + var candidate = sortedCandidates[i]; + + try { - _deduplicatedCollisionResults.Add(group.First()); - } - LogManager.Debug($"[去重缓存] 已缓存 {_deduplicatedCollisionResults.Count} 个去重后的碰撞结果"); - } + // 临时移动动画对象到碰撞位置以执行测试 + var testAnimatedObject = candidate.Item1; + var modelItems = new ModelItemCollection { testAnimatedObject }; + var targetPosition = candidate.Item1Position; - var collisionGroup = new ClashResultGroup - { - DisplayName = $"碰撞检测组 ({groupedCollisions.Count} 个唯一碰撞对)" - }; + // 计算移动偏移 + 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 + ); - LogManager.Info($"[分组测试] 创建碰撞分组: {collisionGroup.DisplayName}"); + var offset = new Vector3D( + targetPosition.X - currentPos.X, + targetPosition.Y - currentPos.Y, + targetPosition.Z - currentPos.Z + ); - int confirmedCount = 0; - int skippedCount = 0; + var transform = Transform3D.CreateTranslation(offset); + doc.Models.OverridePermanentTransform(modelItems, transform, false); - // 2. 遍历每一组 - for (int groupIndex = 0; groupIndex < groupedCollisions.Count; groupIndex++) - { - // 检查用户是否取消 - if (progress.IsCanceled) - { - LogManager.Info($"[分组测试] 用户取消操作,已处理 {groupIndex}/{groupedCollisions.Count} 组"); - break; - } - - var group = groupedCollisions[groupIndex]; - - // 3. 排序:按距离/重叠深度排序,优先检测最严重的碰撞 - // 注意:Distance通常越小(或负值越大)表示碰撞越深,具体取决于计算方式 - // 这里假设Distance越小越严重 - var sortedCandidates = group.OrderBy(c => c.Distance).ToList(); - - bool pairConfirmed = false; - - // 4. 验证即止:逐个检测候选帧 - for (int i = 0; i < sortedCandidates.Count; i++) - { - var candidate = sortedCandidates[i]; - - try + // 创建临时测试以获取真实碰撞结果 + var tempTestName = $"临时验证_{confirmedCount + 1}_{i}_{DateTime.Now:HHmmss_fff}"; + var tempTest = new ClashTest { - // 临时移动动画对象到碰撞位置以执行测试 - var testAnimatedObject = candidate.Item1; - var modelItems = new ModelItemCollection { testAnimatedObject }; - var targetPosition = candidate.Item1Position; + DisplayName = tempTestName, + TestType = ClashTestType.HardConservative, + Tolerance = detectionGap, + Guid = Guid.Empty, + MergeComposites = true + }; - // 计算移动偏移 - 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 selectionA = new ModelItemCollection { candidate.Item1 }; + var selectionB = new ModelItemCollection { candidate.Item2 }; - var offset = new Vector3D( - targetPosition.X - currentPos.X, - targetPosition.Y - currentPos.Y, - targetPosition.Z - currentPos.Z - ); + tempTest.SelectionA.Selection.CopyFrom(selectionA); + tempTest.SelectionB.Selection.CopyFrom(selectionB); - var transform = Transform3D.CreateTranslation(offset); - doc.Models.OverridePermanentTransform(modelItems, transform, false); + // 添加并运行临时测试 + _documentClash.TestsData.TestsAddCopy(tempTest); + var addedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest; - // 创建临时测试以获取真实碰撞结果 - var tempTestName = $"临时验证_{confirmedCount + 1}_{i}_{DateTime.Now:HHmmss_fff}"; - var tempTest = new ClashTest + if (addedTempTest != null) + { + // 设置几何类型(非关键操作,失败不影响继续执行) + 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); + + // 运行测试 + _documentClash.TestsData.TestsRunTest(addedTempTest); + + // 获取刷新后的测试结果 + var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest; + if (refreshedTempTest != null && refreshedTempTest.Children.Count > 0) { - DisplayName = tempTestName, - TestType = ClashTestType.HardConservative, - Tolerance = detectionGap, - Guid = Guid.Empty, - MergeComposites = true - }; + // !!!发现真实碰撞!!! + pairConfirmed = true; + confirmedCount++; + skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量 - // 设置选择集 - var selectionA = new ModelItemCollection { candidate.Item1 }; - var selectionB = new ModelItemCollection { candidate.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 + // 将碰撞结果复制到分组中 + int subResultIndex = 1; + foreach (var child in refreshedTempTest.Children) { - 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); - } - 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) - { - // !!!发现真实碰撞!!! - pairConfirmed = true; - confirmedCount++; - skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量 - - // 将碰撞结果复制到分组中 - int subResultIndex = 1; - foreach (var child in refreshedTempTest.Children) + if (child is ClashResult result) { - if (child is ClashResult result) - { - var copiedResult = result.CreateCopy() as ClashResult; - copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID + var copiedResult = result.CreateCopy() as ClashResult; + copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID - // 设置唯一且有意义的碰撞名称 - // 先找到有意义的容器对象,再获取其名称 - var container1 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item1); - var container2 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item2); - var object1Name = ModelItemAnalysisHelper.GetSafeDisplayName(container1); - var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(container2); + // 设置唯一且有意义的碰撞名称 + // 先找到有意义的容器对象,再获取其名称 + var container1 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item1); + var container2 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item2); + var object1Name = ModelItemAnalysisHelper.GetSafeDisplayName(container1); + var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(container2); - var timeStamp = DateTime.Now.ToString("HHmmss"); - copiedResult.DisplayName = $"物流碰撞#{confirmedCount:00}-{subResultIndex:00}_{timeStamp}: {object1Name} ↔ {object2Name}"; + var timeStamp = DateTime.Now.ToString("HHmmss"); + copiedResult.DisplayName = $"物流碰撞#{confirmedCount:00}-{subResultIndex:00}_{timeStamp}: {object1Name} ↔ {object2Name}"; - collisionGroup.Children.Add(copiedResult); - subResultIndex++; - } + collisionGroup.Children.Add(copiedResult); + subResultIndex++; } } + } - // 删除临时测试 - try - { - _documentClash.TestsData.TestsRemove(refreshedTempTest ?? addedTempTest); - } - catch (Exception cleanEx) - { - LogManager.Warning($"清理临时测试失败: {cleanEx.Message}"); - } + // 清理临时测试 + _documentClash.TestsData.TestsRemove(refreshedTempTest ?? addedTempTest); - // 如果已确认碰撞,跳出当前候选循环,不再检测该组剩余帧 - if (pairConfirmed) - { - break; - } + // 如果已确认碰撞,跳出当前候选循环,不再检测该组剩余帧 + if (pairConfirmed) + { + break; } } - catch (Exception itemEx) - { - LogManager.Error($"候选检测失败: {itemEx.Message}"); - } + } + catch (Exception itemEx) + { + LogManager.Error($"候选检测失败: {itemEx.Message}"); } } + } - LogManager.Info($"[分组测试] ClashDetective检测完成: 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点"); + LogManager.Info($"[分组测试] ClashDetective检测完成: 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点"); - // 第三步:处理碰撞结果 - var clashResults = MergeCompositeCollisions(collisionGroup); + // 第三步:处理碰撞结果 + var clashResults = MergeCompositeCollisions(collisionGroup); - // 缓存结果 - lock (_clashResultsCacheLock) - { - _clashDetectiveResultsCache[_currentTestName] = clashResults; - } - LogManager.Info($"已缓存ClashDetective结果:{clashResults.Count}个碰撞,测试名称:{_currentTestName}"); + // 缓存结果 + lock (_clashResultsCacheLock) + { + _clashDetectiveResultsCache[_currentTestName] = clashResults; + } + LogManager.Info($"已缓存ClashDetective结果:{clashResults.Count}个碰撞,测试名称:{_currentTestName}"); - // 更新碰撞计数器 - _clashDetectiveCollisionCount = clashResults.Count; + // 更新碰撞计数器 + _clashDetectiveCollisionCount = clashResults.Count; - // 保存到数据库 - SaveClashDetectiveResultToDatabase(pathName, routeId, clashResults, frameRate, duration, detectionGap, animatedObject, isVirtualVehicle, - virtualVehicleLength, virtualVehicleWidth, virtualVehicleHeight); + // 保存到数据库 + SaveClashDetectiveResultToDatabase(pathName, routeId, clashResults, frameRate, duration, detectionGap, animatedObject, isVirtualVehicle, + virtualVehicleLength, virtualVehicleWidth, virtualVehicleHeight); - // 第四步:将分组添加到主测试 - if (collisionGroup.Children.Count > 0) - { - _documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup); - } - else - { - LogManager.Warning("[分组测试] 分组为空(未检测到真实几何碰撞),未添加到主测试"); - } - - // 提交事务 - transaction.Commit(); + // 第四步:将分组添加到主测试 + if (collisionGroup.Children.Count > 0) + { + _documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup); + } + else + { + LogManager.Warning("[分组测试] 分组为空(未检测到真实几何碰撞),未添加到主测试"); } } - catch (Exception groupEx) + catch (Exception ex) { - LogManager.Error($"[分组测试] 创建分组测试失败: {groupEx.Message}"); + LogManager.Error($"[分组测试] 创建分组测试失败: {ex.Message}"); + + // 异常时清理已创建的主测试 + if (addedMainTest != null) + { + try + { + _documentClash.TestsData.TestsRemove(addedMainTest); + LogManager.Info("[分组测试] 已清理主测试"); + } + catch (Exception cleanupEx) + { + LogManager.Warning($"[分组测试] 清理主测试失败: {cleanupEx.Message}"); + } + } } finally { @@ -910,15 +906,8 @@ namespace NavisworksTransport RefreshClashDetectiveUI(); // 自动高亮ClashDetective结果 - try - { - ModelHighlightHelper.HighlightClashDetectiveResults(_currentTestName, GetCurrentPathClashResults); - LogManager.Info("自动高亮ClashDetective检测结果完成"); - } - catch (Exception clashHighlightEx) - { - LogManager.Error($"自动高亮ClashDetective结果失败: {clashHighlightEx.Message}"); - } + ModelHighlightHelper.HighlightClashDetectiveResults(_currentTestName, GetCurrentPathClashResults); + LogManager.Info("自动高亮ClashDetective检测结果完成"); LogManager.Info($"=== 碰撞统计最终结果 ==="); LogManager.Info($"动画过程包围盒检测: {_animationCollisionCount}个碰撞 (参考值)");