refactor: 预计算碰撞分析改为手动入口(排除列表'碰撞热点分析'按钮)

背景:预计算分析起因是非手工指定模式耗时过长;剖面盒导出
(F1 副实例)解决后,动画后自动弹分析窗口已无必要且与
'非手工指定 = 检测所有物体'语义冲突(自动预选排除可能被误确认
导致遗漏)。

改动:
- 动画生成后不再自动调用预计算分析——直接保存检测记录
  (所有模式统一;批处理/测试不再弹窗闪烁)
- 新增'碰撞热点分析'按钮(排除列表区域,AnalyzeCollisionHotspotsCommand):
  手动触发分析当前动画的高频碰撞物体 → 弹窗供用户判断排除
  (保留原高亮/热点/排除并重新生成行为,不自动保存记录)
- 删除 AnalyzeAndHighlightPrecomputedCollisionsAsync(被手动版替代)

验证:集成测试 25/25;F1 批处理 46 秒(副实例无分析弹窗,0 次日志)
This commit is contained in:
tian 2026-08-07 09:17:19 +08:00
parent 75ade5ad08
commit 85eb10a1b1
2 changed files with 41 additions and 34 deletions

View File

@ -1036,6 +1036,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand RemoveExcludedObjectCommand { get; private set; } public ICommand RemoveExcludedObjectCommand { get; private set; }
public ICommand HighlightAllExcludedObjectsCommand { get; private set; } public ICommand HighlightAllExcludedObjectsCommand { get; private set; }
public ICommand ClearExcludedHighlightCommand { get; private set; } public ICommand ClearExcludedHighlightCommand { get; private set; }
public ICommand AnalyzeCollisionHotspotsCommand { get; private set; }
public ICommand HighlightPrecomputedCollisionResultsCommand { get; private set; } public ICommand HighlightPrecomputedCollisionResultsCommand { get; private set; }
public ICommand ClearPrecomputedCollisionHighlightsCommand { get; private set; } public ICommand ClearPrecomputedCollisionHighlightsCommand { get; private set; }
public ICommand ToggleWireframeModeCommand { get; private set; } public ICommand ToggleWireframeModeCommand { get; private set; }
@ -1313,6 +1314,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
RemoveExcludedObjectCommand = new RelayCommand<ExcludedObjectViewModel>(ExecuteRemoveExcludedObject, obj => obj != null); RemoveExcludedObjectCommand = new RelayCommand<ExcludedObjectViewModel>(ExecuteRemoveExcludedObject, obj => obj != null);
HighlightAllExcludedObjectsCommand = new RelayCommand(ExecuteHighlightAllExcludedObjects, () => HasExcludedObjects); HighlightAllExcludedObjectsCommand = new RelayCommand(ExecuteHighlightAllExcludedObjects, () => HasExcludedObjects);
ClearExcludedHighlightCommand = new RelayCommand(ExecuteClearExcludedHighlight, () => HasExcludedObjects); ClearExcludedHighlightCommand = new RelayCommand(ExecuteClearExcludedHighlight, () => HasExcludedObjects);
AnalyzeCollisionHotspotsCommand = new RelayCommand(
async () => await ExecuteCollisionHotspotAnalysisAsync(),
() => _pathAnimationManager?.AllCollisionResults?.Count > 0);
HighlightPrecomputedCollisionResultsCommand = new RelayCommand(ExecuteHighlightPrecomputedCollisionResults, () => HasClashDetectiveResults); HighlightPrecomputedCollisionResultsCommand = new RelayCommand(ExecuteHighlightPrecomputedCollisionResults, () => HasClashDetectiveResults);
ClearPrecomputedCollisionHighlightsCommand = new RelayCommand(ExecuteClearPrecomputedCollisionHighlights, () => HasClashDetectiveResults); ClearPrecomputedCollisionHighlightsCommand = new RelayCommand(ExecuteClearPrecomputedCollisionHighlights, () => HasClashDetectiveResults);
ToggleWireframeModeCommand = new RelayCommand(ExecuteToggleWireframeMode); ToggleWireframeModeCommand = new RelayCommand(ExecuteToggleWireframeMode);
@ -3619,31 +3623,27 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// <summary> /// <summary>
/// 🔥 分析预计算碰撞结果,自动高亮并显示分析对话框 /// 🔥 分析预计算碰撞结果,自动高亮并显示分析对话框
/// </summary> /// </summary>
private async Task AnalyzeAndHighlightPrecomputedCollisionsAsync() /// <summary>
/// 【手动入口】碰撞热点分析(排除列表区域按钮触发):
/// 分析当前动画的预计算碰撞结果,显示高频碰撞物体窗口——用户判断哪些物体需要排除。
/// 不自动保存检测记录(分析是决策辅助,不是动画流程的一部分)。
/// 说明:动画后不再自动弹出(非手工指定模式 = 检测所有物体,避免人工遗漏;
/// 批处理用户不在场无决策机会;排除建议由用户主动触发)。
/// </summary>
private async Task ExecuteCollisionHotspotAnalysisAsync()
{ {
try try
{ {
// 仅测试环境变量(副实例/测试进程)跳过预计算分析(历史行为);
// 批处理/测试请求自动模式下窗口会自动选择默认动作并关闭,业务流程完整执行
if (DialogHelper.IsTestAutomationModeEnabled)
{
LogManager.Info("[碰撞分析] 自动测试模式下跳过预计算碰撞分析");
SaveCollisionDetectionRecord();
return;
}
// 1. 获取预计算碰撞结果从PathAnimationManager现在缓存帧中已包含碰撞结果 // 1. 获取预计算碰撞结果从PathAnimationManager现在缓存帧中已包含碰撞结果
var allResults = _pathAnimationManager?.AllCollisionResults; var allResults = _pathAnimationManager?.AllCollisionResults;
if (allResults == null || allResults.Count == 0) if (allResults == null || allResults.Count == 0)
{ {
LogManager.Info("[碰撞分析] 没有预计算碰撞结果,跳过分析"); LogManager.Info("[碰撞热点分析] 无预计算碰撞结果(请先生成动画)");
// 🔥 没有预计算碰撞,配置已确定,直接保存检测记录 System.Windows.MessageBox.Show(
var recordId = SaveCollisionDetectionRecord(); "没有预计算碰撞结果。请先运行\"生成动画\",再进行碰撞热点分析。",
if (recordId.HasValue && IsReusedHistoryRecord(recordId.Value)) "碰撞热点分析",
{ System.Windows.MessageBoxButton.OK,
LogManager.Info("[碰撞分析] 用户选择使用历史记录,跳过动画生成"); System.Windows.MessageBoxImage.Information);
return;
}
return; return;
} }
@ -3653,21 +3653,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels
.Select(g => g.First()) .Select(g => g.First())
.ToList(); .ToList();
LogManager.Info($"[碰撞分析] 开始分析 {allResults.Count} 个碰撞结果(去重后 {deduplicatedResults.Count} 个)"); LogManager.Info($"[碰撞热点分析] 开始分析 {allResults.Count} 个碰撞结果(去重后 {deduplicatedResults.Count} 个)");
// 2. 自动高亮所有预计算碰撞结果 // 2. 高亮所有预计算碰撞结果
ModelHighlightHelper.ManageCollisionHighlightsByCategory( ModelHighlightHelper.ManageCollisionHighlightsByCategory(
ModelHighlightHelper.PrecomputeCollisionResultsCategory, ModelHighlightHelper.PrecomputeCollisionResultsCategory,
deduplicatedResults, deduplicatedResults,
clearOtherCategories: false); clearOtherCategories: false);
LogManager.Info($"[碰撞分析] 已高亮 {deduplicatedResults.Count} 个碰撞对象"); LogManager.Info($"[碰撞热点分析] 已高亮 {deduplicatedResults.Count} 个碰撞对象");
// 3. 分析碰撞热点(使用原始结果 - 按频率统计) // 3. 分析碰撞热点(使用原始结果 - 按频率统计)
var hotspots = AnalyzeCollisionHotspots(allResults); var hotspots = AnalyzeCollisionHotspots(allResults);
if (hotspots.Count > 0) if (hotspots.Count > 0)
{ {
LogManager.Info($"[碰撞分析] 发现 {hotspots.Count} 个高频碰撞物体"); LogManager.Info($"[碰撞热点分析] 发现 {hotspots.Count} 个高频碰撞物体");
foreach (var hotspot in hotspots.Take(5)) foreach (var hotspot in hotspots.Take(5))
{ {
LogManager.Info($" - {hotspot.ObjectName}: {hotspot.CollisionCount} 次 ({hotspot.Percentage:F1}%) - {hotspot.Reason}"); LogManager.Info($" - {hotspot.ObjectName}: {hotspot.CollisionCount} 次 ({hotspot.Percentage:F1}%) - {hotspot.Reason}");
@ -3681,20 +3681,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
} }
else else
{ {
LogManager.Info("[碰撞分析] 没有发现明显的高频碰撞物体"); LogManager.Info("[碰撞热点分析] 没有发现明显的高频碰撞物体");
// 🔥 有碰撞但无高频热点,配置已确定,保存检测记录 System.Windows.MessageBox.Show(
var recordId = SaveCollisionDetectionRecord(); "没有发现明显的高频碰撞物体(碰撞次数 > 100 或占比 > 50%)。",
if (recordId.HasValue && IsReusedHistoryRecord(recordId.Value)) "碰撞热点分析",
{ System.Windows.MessageBoxButton.OK,
// 用户选择使用历史记录,跳过后续流程 System.Windows.MessageBoxImage.Information);
LogManager.Info("[碰撞分析] 用户选择使用历史记录,跳过动画生成");
return;
}
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
LogManager.Error($"[碰撞分析] 分析预计算碰撞结果失败: {ex.Message}", ex); LogManager.Error($"[碰撞热点分析] 分析失败: {ex.Message}", ex);
} }
} }
@ -5024,8 +5021,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
Mouse.OverrideCursor = oldCursor; Mouse.OverrideCursor = oldCursor;
oldCursor = null; // 标记已恢复,避免 finally 再次恢复 oldCursor = null; // 标记已恢复,避免 finally 再次恢复
// 🔥 自动生成动画后,自动高亮预计算碰撞结果并显示分析对话框 // 🔥 动画生成后直接保存检测记录——不再自动弹出预计算碰撞分析窗口:
await AnalyzeAndHighlightPrecomputedCollisionsAsync(); // 非手工指定模式 = 检测所有物体(避免人工遗漏);排除建议由
// 排除列表区域的"碰撞热点分析"手动触发(用户主动决策)
var autoRecordId = SaveCollisionDetectionRecord();
if (autoRecordId.HasValue && IsReusedHistoryRecord(autoRecordId.Value))
{
LogManager.Info("[动画生成] 用户选择使用历史记录,跳过动画生成");
}
// 更新状态 // 更新状态
UpdateAnimationButtonStates(); UpdateAnimationButtonStates();

View File

@ -239,6 +239,10 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管
<Button Content="从选择添加" <Button Content="从选择添加"
Command="{Binding AddExcludedObjectsFromSelectionCommand}" Command="{Binding AddExcludedObjectsFromSelectionCommand}"
Style="{StaticResource SecondaryButtonStyle}"/> Style="{StaticResource SecondaryButtonStyle}"/>
<Button Content="碰撞热点分析"
Command="{Binding AnalyzeCollisionHotspotsCommand}"
Style="{StaticResource SecondaryButtonStyle}"
ToolTip="分析当前动画的高频碰撞物体,供手动决定是否排除"/>
<Button Content="清除所有" <Button Content="清除所有"
Command="{Binding ClearExcludedObjectsCommand}" Command="{Binding ClearExcludedObjectsCommand}"
Style="{StaticResource SecondaryButtonStyle}" Style="{StaticResource SecondaryButtonStyle}"