diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 4c2f019..47e48a8 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -207,6 +207,9 @@ AnimationControlView.xaml + + CollisionAnalysisDialog.xaml + SystemManagementView.xaml @@ -351,6 +354,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index f0aa907..a6cbe75 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using System.Windows; using System.Windows.Input; using Autodesk.Navisworks.Api; using Autodesk.Navisworks.Api.Clash; @@ -14,10 +15,24 @@ using NavisworksTransport.Core.Models; using NavisworksTransport.Core.Config; using NavisworksTransport.Commands; using NavisworksTransport.UI.WPF.Collections; +using NavisworksTransport.UI.WPF.Views; using NavisworksTransport.Utils; namespace NavisworksTransport.UI.WPF.ViewModels { + /// + /// 碰撞热点信息 - 用于分析高频碰撞物体 + /// + public class CollisionHotspotInfo + { + public ModelItem Object { get; set; } + public string ObjectName { get; set; } + public int CollisionCount { get; set; } + public double Percentage { get; set; } + public string Reason { get; set; } + public string RecommendedAction { get; set; } + } + /// /// 碰撞报告数据 /// @@ -2146,11 +2161,191 @@ namespace NavisworksTransport.UI.WPF.ViewModels UpdateMainStatus("已清除预计算碰撞结果高亮"); } + /// + /// 🔥 分析预计算碰撞结果,自动高亮并显示分析对话框 + /// + private async Task AnalyzeAndHighlightPrecomputedCollisionsAsync() + { + try + { + // 1. 获取预计算碰撞结果(从PathAnimationManager) + var allResults = _pathAnimationManager?.AllCollisionResults; + if (allResults == null || allResults.Count == 0) + { + LogManager.Info("[碰撞分析] 没有预计算碰撞结果,跳过分析"); + return; + } + + // 去重:按碰撞对象对去重(取每对的第一次出现) + var deduplicatedResults = allResults + .GroupBy(r => new { Item1 = r.Item1, Item2 = r.Item2 }) + .Select(g => g.First()) + .ToList(); + + LogManager.Info($"[碰撞分析] 开始分析 {allResults.Count} 个碰撞结果(去重后 {deduplicatedResults.Count} 个)"); + + // 2. 自动高亮所有预计算碰撞结果 + ModelHighlightHelper.ManageCollisionHighlightsByCategory( + ModelHighlightHelper.PrecomputeCollisionResultsCategory, + deduplicatedResults, + clearOtherCategories: false); + LogManager.Info($"[碰撞分析] 已高亮 {deduplicatedResults.Count} 个碰撞对象"); + + // 3. 分析碰撞热点(使用原始结果 - 按频率统计) + var hotspots = AnalyzeCollisionHotspots(allResults); + + if (hotspots.Count > 0) + { + LogManager.Info($"[碰撞分析] 发现 {hotspots.Count} 个高频碰撞物体"); + foreach (var hotspot in hotspots.Take(5)) + { + LogManager.Info($" - {hotspot.ObjectName}: {hotspot.CollisionCount} 次 ({hotspot.Percentage:F1}%) - {hotspot.Reason}"); + } + + // 4. 显示分析对话框(在UI线程)- 传递原始总数用于百分比计算 + await _uiStateManager.ExecuteUIUpdateAsync(() => + { + ShowCollisionAnalysisDialog(hotspots, allResults.Count); + }); + } + else + { + LogManager.Info("[碰撞分析] 没有发现明显的高频碰撞物体"); + } + } + catch (Exception ex) + { + LogManager.Error($"[碰撞分析] 分析预计算碰撞结果失败: {ex.Message}", ex); + } + } + + /// + /// 分析碰撞热点 - 找出高频碰撞的物体 + /// + private List AnalyzeCollisionHotspots(List results) + { + // 按被撞物体分组统计 + var objectStats = results + .Where(r => r.Item2 != null) + .GroupBy(r => r.Item2) + .Select(g => new + { + Object = g.Key, + Count = g.Count(), + Percentage = (double)g.Count() / results.Count * 100 + }) + .Where(x => x.Count >= 5 || x.Percentage > 10) // 只关注高频物体 + .OrderByDescending(x => x.Count) + .ToList(); + + var hotspots = new List(); + + foreach (var stat in objectStats) + { + var objectName = stat.Object.DisplayName ?? "未知对象"; + var reason = GetHotspotReason(objectName, stat.Object); + + hotspots.Add(new CollisionHotspotInfo + { + Object = stat.Object, + ObjectName = objectName, + CollisionCount = stat.Count, + Percentage = stat.Percentage, + Reason = reason, + RecommendedAction = reason.Contains("地面") || reason.Contains("楼板") || reason.Contains("墙体") + ? "建议排除" + : "需要检查" + }); + } + + return hotspots; + } + + /// + /// 获取热点原因分析 + /// + private string GetHotspotReason(string objectName, ModelItem item) + { + var name = objectName.ToLower(); + + // 检查名称特征 + if (name.Contains("地面") || name.Contains("楼板") || name.Contains("floor") || name.Contains("slab")) + return "地面/楼板 - 大面积平面物体,容易产生假阳性碰撞"; + + if (name.Contains("墙体") || name.Contains("wall") || name.Contains("外墙")) + return "墙体 - 沿路径延伸,容易产生假阳性碰撞"; + + if (name.Contains("楼梯") || name.Contains("stair")) + return "楼梯 - 结构复杂,可能产生多次碰撞检测"; + + if (name.Contains("屋顶") || name.Contains("roof") || name.Contains("天花板")) + return "屋顶/天花板 - 位于上方,可能产生假阳性碰撞"; + + // 检查包围盒大小 + try + { + var bbox = item.BoundingBox(); + var volume = (bbox.Max.X - bbox.Min.X) * (bbox.Max.Y - bbox.Min.Y) * (bbox.Max.Z - bbox.Min.Z); + if (volume > 1000) // 大于1000立方米 + return "大型物体 - 包围盒很大,容易与路径相交"; + } + catch { } + + return "未知原因 - 建议手动检查"; + } + + /// + /// 显示碰撞分析对话框 + /// + private void ShowCollisionAnalysisDialog(List hotspots, int totalCollisions) + { + try + { + var dialog = new CollisionAnalysisDialog(hotspots, totalCollisions); + + // 安全地设置 Owner,确保窗口已显示 + var mainWindow = System.Windows.Application.Current?.MainWindow; + if (mainWindow != null && mainWindow.IsVisible) + { + try + { + dialog.Owner = mainWindow; + } + catch (InvalidOperationException) + { + // Owner 设置失败,继续显示对话框(无 Owner) + LogManager.Debug("[碰撞分析] 无法设置对话框 Owner,将以独立窗口显示"); + } + } + + var result = dialog.ShowDialog(); + + if (result == true && dialog.ExcludedObjects.Count > 0) + { + // 用户选择了排除某些物体 + LogManager.Info($"[碰撞分析] 用户选择排除 {dialog.ExcludedObjects.Count} 个物体"); + + // TODO: 将排除的物体添加到排除列表,并重新生成动画 + // 这需要与 ClashDetectiveIntegration 集成 + + UpdateMainStatus($"已排除 {dialog.ExcludedObjects.Count} 个物体,请重新生成动画"); + } + else + { + UpdateMainStatus($"预计算碰撞分析完成,共 {totalCollisions} 个候选碰撞"); + } + } + catch (Exception ex) + { + LogManager.Error($"[碰撞分析] 显示分析对话框失败: {ex.Message}", ex); + } + } + private void ExecuteToggleWireframeMode() { try { - var doc = Application.ActiveDocument; + var doc = Autodesk.Navisworks.Api.Application.ActiveDocument; if (doc == null || doc.IsClear) { LogManager.Warning("[线框模式] 没有活动的文档"); @@ -2260,7 +2455,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels ModelIndex = objRecord.ModelIndex.Value, PathId = objRecord.PathId }; - modelItem = Application.ActiveDocument.Models.ResolvePathId(pathIdObj); + modelItem = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.ResolvePathId(pathIdObj); // 获取完整的节点树路径 if (modelItem != null) @@ -2460,7 +2655,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels return (0, null); } - var pathId = Application.ActiveDocument.Models.CreatePathId(SelectedAnimatedObject); + var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(SelectedAnimatedObject); return (pathId.ModelIndex, pathId.PathId); } catch (Exception ex) @@ -2609,7 +2804,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// /// 执行生成动画命令(同步执行,因为Navisworks API需要STA线程) /// - private void ExecuteGenerateAnimation() + private async void ExecuteGenerateAnimation() { // 保存当前光标 var oldCursor = Mouse.OverrideCursor; @@ -2773,6 +2968,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels RefreshManualTargetHighlights(forceHighlight: true); } + // 动画生成完成,先恢复光标再显示分析对话框 + Mouse.OverrideCursor = oldCursor; + oldCursor = null; // 标记已恢复,避免 finally 再次恢复 + + // 🔥 自动生成动画后,自动高亮预计算碰撞结果并显示分析对话框 + await AnalyzeAndHighlightPrecomputedCollisionsAsync(); + // 更新状态 UpdateAnimationButtonStates(); UpdateMainStatus("动画生成成功"); @@ -2786,8 +2988,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels } finally { - // 恢复光标 - Mouse.OverrideCursor = oldCursor; + // 恢复光标(如果还没有恢复) + if (oldCursor != null) + { + Mouse.OverrideCursor = oldCursor; + } } } @@ -3784,7 +3989,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels var validItems = GetValidManualCollisionTargets(pruneInvalidEntries: true); foreach (var item in validItems) { - var pathId = Application.ActiveDocument.Models.CreatePathId(item); + var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(item); await db.AddModelItemReferenceAsync( itemId, "BatchQueueItem", diff --git a/src/UI/WPF/Views/CollisionAnalysisDialog.xaml b/src/UI/WPF/Views/CollisionAnalysisDialog.xaml new file mode 100644 index 0000000..01bda65 --- /dev/null +++ b/src/UI/WPF/Views/CollisionAnalysisDialog.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + 提示: + 以下物体在预计算中产生了大量碰撞检测点。这些通常是地面、楼板、墙体等大面积物体。 + + 勾选"排除"可以将这些物体从碰撞检测中移除,显著减少检测时间。请确认这些物体确实不会与运动物体发生真实碰撞。 + + + + + + + + + + + + + + + + + + + + + + + + +