From 322523bc77df69061ddb76080edab00ac078ddac Mon Sep 17 00:00:00 2001
From: tian <11429339@qq.com>
Date: Sun, 8 Feb 2026 08:46:16 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=A2=84=E8=AE=A1=E7=AE=97?=
=?UTF-8?q?=E7=A2=B0=E6=92=9E=E5=88=86=E6=9E=90=E5=AF=B9=E8=AF=9D=E6=A1=86?=
=?UTF-8?q?=E5=8F=8A=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF?=
=?UTF-8?q?=E6=8C=81=E9=AB=98=E9=A2=91=E7=A2=B0=E6=92=9E=E7=89=A9=E4=BD=93?=
=?UTF-8?q?=E7=9A=84=E6=8E=92=E9=99=A4=E5=BB=BA=E8=AE=AE=EF=BC=88=E5=88=9D?=
=?UTF-8?q?=E6=AD=A5=E5=AE=9E=E7=8E=B0=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
NavisworksTransportPlugin.csproj | 7 +
.../ViewModels/AnimationControlViewModel.cs | 219 +++++++++++++++++-
src/UI/WPF/Views/CollisionAnalysisDialog.xaml | 80 +++++++
.../WPF/Views/CollisionAnalysisDialog.xaml.cs | 84 +++++++
4 files changed, 383 insertions(+), 7 deletions(-)
create mode 100644 src/UI/WPF/Views/CollisionAnalysisDialog.xaml
create mode 100644 src/UI/WPF/Views/CollisionAnalysisDialog.xaml.cs
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提示:
+ 以下物体在预计算中产生了大量碰撞检测点。这些通常是地面、楼板、墙体等大面积物体。
+
+ 勾选"排除"可以将这些物体从碰撞检测中移除,显著减少检测时间。请确认这些物体确实不会与运动物体发生真实碰撞。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/UI/WPF/Views/CollisionAnalysisDialog.xaml.cs b/src/UI/WPF/Views/CollisionAnalysisDialog.xaml.cs
new file mode 100644
index 0000000..ee4e983
--- /dev/null
+++ b/src/UI/WPF/Views/CollisionAnalysisDialog.xaml.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using Autodesk.Navisworks.Api;
+using NavisworksTransport.UI.WPF.ViewModels;
+
+namespace NavisworksTransport.UI.WPF.Views
+{
+ public partial class CollisionAnalysisDialog : Window
+ {
+ private List _viewModels;
+ public List ExcludedObjects { get; private set; }
+
+ public CollisionAnalysisDialog(List hotspots, int totalCollisions)
+ {
+ InitializeComponent();
+ ExcludedObjects = new List();
+
+ StatsTextBlock.Text = $"共 {totalCollisions} 个候选碰撞,发现 {hotspots.Count} 个高频碰撞物体";
+
+ // 转换为视图模型
+ var viewModels = hotspots.Select(h => new HotspotViewModel
+ {
+ Object = h.Object,
+ ObjectName = h.ObjectName,
+ CollisionCount = h.CollisionCount,
+ Percentage = h.Percentage,
+ Reason = h.Reason,
+ IsExcluded = h.RecommendedAction == "建议排除"
+ }).ToList();
+
+ HotspotsDataGrid.ItemsSource = viewModels;
+ HotspotsDataGrid.SelectionChanged += HotspotsDataGrid_SelectionChanged;
+
+ _viewModels = viewModels;
+ }
+
+ private void HotspotsDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (HotspotsDataGrid.SelectedItem is HotspotViewModel vm)
+ {
+ SelectedObjectDetails.Text = $"物体: {vm.ObjectName}\n" +
+ $"碰撞次数: {vm.CollisionCount} ({vm.Percentage:F1}%)\n" +
+ $"分析: {vm.Reason}\n\n" +
+ $"建议: {(vm.IsExcluded ? "建议排除此物体" : "需要手动检查")}";
+ }
+ }
+
+ private void ExcludeAndRegenerateButton_Click(object sender, RoutedEventArgs e)
+ {
+ ExcludedObjects = _viewModels
+ .Where(vm => vm.IsExcluded)
+ .Select(vm => vm.Object)
+ .ToList();
+
+ DialogResult = true;
+ Close();
+ }
+
+ private void ContinueButton_Click(object sender, RoutedEventArgs e)
+ {
+ DialogResult = false;
+ Close();
+ }
+
+ private void CancelButton_Click(object sender, RoutedEventArgs e)
+ {
+ DialogResult = null;
+ Close();
+ }
+
+ private class HotspotViewModel
+ {
+ 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 bool IsExcluded { get; set; }
+ }
+ }
+}
\ No newline at end of file