新增预计算碰撞分析对话框及相关功能,支持高频碰撞物体的排除建议(初步实现)

This commit is contained in:
tian 2026-02-08 08:46:16 +08:00
parent 77a49265aa
commit 322523bc77
4 changed files with 383 additions and 7 deletions

View File

@ -207,6 +207,9 @@
<Compile Include="src\UI\WPF\Views\AnimationControlView.xaml.cs">
<DependentUpon>AnimationControlView.xaml</DependentUpon>
</Compile>
<Compile Include="src\UI\WPF\Views\CollisionAnalysisDialog.xaml.cs">
<DependentUpon>CollisionAnalysisDialog.xaml</DependentUpon>
</Compile>
<Compile Include="src\UI\WPF\Views\SystemManagementView.xaml.cs">
<DependentUpon>SystemManagementView.xaml</DependentUpon>
</Compile>
@ -351,6 +354,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="src\UI\WPF\Views\CollisionAnalysisDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="src\UI\WPF\Views\SystemManagementView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@ -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
{
/// <summary>
/// 碰撞热点信息 - 用于分析高频碰撞物体
/// </summary>
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; }
}
/// <summary>
/// 碰撞报告数据
/// </summary>
@ -2146,11 +2161,191 @@ namespace NavisworksTransport.UI.WPF.ViewModels
UpdateMainStatus("已清除预计算碰撞结果高亮");
}
/// <summary>
/// 🔥 分析预计算碰撞结果,自动高亮并显示分析对话框
/// </summary>
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);
}
}
/// <summary>
/// 分析碰撞热点 - 找出高频碰撞的物体
/// </summary>
private List<CollisionHotspotInfo> AnalyzeCollisionHotspots(List<CollisionResult> 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<CollisionHotspotInfo>();
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;
}
/// <summary>
/// 获取热点原因分析
/// </summary>
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 "未知原因 - 建议手动检查";
}
/// <summary>
/// 显示碰撞分析对话框
/// </summary>
private void ShowCollisionAnalysisDialog(List<CollisionHotspotInfo> 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
/// <summary>
/// 执行生成动画命令同步执行因为Navisworks API需要STA线程
/// </summary>
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",

View File

@ -0,0 +1,80 @@
<Window x:Class="NavisworksTransport.UI.WPF.Views.CollisionAnalysisDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="预计算碰撞分析 - 排除建议"
Height="500" Width="700"
WindowStartupLocation="CenterOwner"
ResizeMode="CanResize">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题和统计 -->
<StackPanel Grid.Row="0" Margin="0,0,0,10">
<TextBlock Text="预计算碰撞结果分析"
FontSize="18" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBlock x:Name="StatsTextBlock"
Text="正在分析..."
Foreground="Gray"/>
</StackPanel>
<!-- 说明 -->
<Border Grid.Row="1" Background="#FFFEF3CD" BorderBrush="#FFE0C97F"
BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
<TextBlock TextWrapping="Wrap">
<Run FontWeight="Bold">提示:</Run>
<Run>以下物体在预计算中产生了大量碰撞检测点。这些通常是地面、楼板、墙体等大面积物体。</Run>
<LineBreak/>
<Run>勾选"排除"可以将这些物体从碰撞检测中移除,显著减少检测时间。请确认这些物体确实不会与运动物体发生真实碰撞。</Run>
</TextBlock>
</Border>
<!-- 热点列表 -->
<DataGrid x:Name="HotspotsDataGrid" Grid.Row="2"
AutoGenerateColumns="False"
CanUserAddRows="False"
SelectionMode="Single"
GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="排除" Width="50"
Binding="{Binding IsExcluded, Mode=TwoWay}"/>
<DataGridTextColumn Header="物体名称" Width="*"
Binding="{Binding ObjectName}" IsReadOnly="True"/>
<DataGridTextColumn Header="碰撞次数" Width="80"
Binding="{Binding CollisionCount}" IsReadOnly="True"/>
<DataGridTextColumn Header="占比" Width="60"
Binding="{Binding Percentage, StringFormat={}{0:F1}%}" IsReadOnly="True"/>
<DataGridTextColumn Header="原因分析" Width="200"
Binding="{Binding Reason}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
<!-- 选中物体详情 -->
<Border Grid.Row="3" Background="#FFF8F9FA" BorderBrush="#FFDEE2E6"
BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,10">
<StackPanel>
<TextBlock Text="选中物体详情" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBlock x:Name="SelectedObjectDetails" Text="请选择一个物体查看详情"
Foreground="Gray" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<!-- 按钮 -->
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="排除选中并重新生成" Width="140" Margin="0,0,10,0"
Click="ExcludeAndRegenerateButton_Click"
Background="#FFDC3545" Foreground="White"/>
<Button Content="忽略并继续" Width="100" Margin="0,0,10,0"
Click="ContinueButton_Click"/>
<Button Content="取消" Width="80" Click="CancelButton_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -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<HotspotViewModel> _viewModels;
public List<ModelItem> ExcludedObjects { get; private set; }
public CollisionAnalysisDialog(List<CollisionHotspotInfo> hotspots, int totalCollisions)
{
InitializeComponent();
ExcludedObjects = new List<ModelItem>();
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; }
}
}
}