新增预计算碰撞分析对话框及相关功能,支持高频碰撞物体的排除建议(初步实现)
This commit is contained in:
parent
77a49265aa
commit
322523bc77
@ -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>
|
||||
|
||||
@ -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",
|
||||
|
||||
80
src/UI/WPF/Views/CollisionAnalysisDialog.xaml
Normal file
80
src/UI/WPF/Views/CollisionAnalysisDialog.xaml
Normal 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>
|
||||
84
src/UI/WPF/Views/CollisionAnalysisDialog.xaml.cs
Normal file
84
src/UI/WPF/Views/CollisionAnalysisDialog.xaml.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user