NavisworksTransport/src/UI/WPF/ViewModels/CollisionReportViewModel.cs

1435 lines
52 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
using NavisworksTransport.Commands;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Animation;
using System.IO;
using System.Text;
using Microsoft.Win32;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using NavisworksTransport.UI.WPF.Views;
using ImageFormat = System.Drawing.Imaging.ImageFormat;
namespace NavisworksTransport.UI.WPF.ViewModels
{
/// <summary>
/// 碰撞报告显示状态枚举
/// </summary>
public enum CollisionReportStatus
{
/// <summary>正在生成报告</summary>
Generating,
/// <summary>报告已生成</summary>
Generated,
/// <summary>生成失败</summary>
Failed,
/// <summary>空报告</summary>
Empty
}
/// <summary>
/// 碰撞报告项目UI模型
/// </summary>
public class CollisionReportItem
{
public int Index { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string StatusText { get; set; }
public string StatusColor { get; set; }
public string Details { get; set; }
public string Item1FullPath { get; set; }
public string Item2FullPath { get; set; }
public CollisionResult CollisionData { get; set; }
}
/// <summary>
/// 碰撞报告统计信息UI模型
/// </summary>
public class CollisionReportStatistics
{
public int TotalCollisions { get; set; }
public int AnimationCollisions { get; set; }
public int NewCollisions { get; set; }
public int ActiveCollisions { get; set; }
public int ReviewedCollisions { get; set; }
public int ApprovedCollisions { get; set; }
public int ResolvedCollisions { get; set; }
public string GenerationTime { get; set; }
public string ReportType { get; set; }
}
/// <summary>
/// 碰撞报告对话框ViewModel
/// </summary>
public class CollisionReportViewModel : ViewModelBase
{
#region
private CollisionReportStatus _reportStatus;
private string _reportTitle;
private CollisionReportStatistics _statistics;
private ObservableCollection<CollisionReportItem> _animationCollisions;
private string _progressMessage;
private int _progressPercentage;
private bool _isGenerating;
private bool _hasCollisions;
private string _summaryMessage;
private string _recommendationMessage;
private string _movingObjectInfo;
private ObservableCollection<string> _collidedObjectsList;
private int _uniqueCollidedObjectsCount;
private string _pathName;
private int _frameRate;
private double _duration;
private double _detectionTolerance;
private string _testName;
private DateTime? _testTime;
private CollisionReportResult _currentReport;
private ObservableCollection<ExcludedObjectRecord> _excludedObjects;
// 保存运动物体原始状态,用于碰撞查看后恢复
private ModelItemTransformHelper.ObjectStateSnapshot _savedAnimatedObjectState;
private ModelItem _currentAnimatedObject;
#endregion
#region
/// <summary>
/// 报告状态
/// </summary>
public CollisionReportStatus ReportStatus
{
get => _reportStatus;
set => SetProperty(ref _reportStatus, value);
}
/// <summary>
/// 报告标题
/// </summary>
public string ReportTitle
{
get => _reportTitle;
set => SetProperty(ref _reportTitle, value);
}
/// <summary>
/// 统计信息
/// </summary>
public CollisionReportStatistics Statistics
{
get => _statistics;
set => SetProperty(ref _statistics, value);
}
/// <summary>
/// 动画过程碰撞列表
/// </summary>
public ObservableCollection<CollisionReportItem> AnimationCollisions
{
get => _animationCollisions;
set => SetProperty(ref _animationCollisions, value);
}
/// <summary>
/// 进度消息
/// </summary>
public string ProgressMessage
{
get => _progressMessage;
set => SetProperty(ref _progressMessage, value);
}
/// <summary>
/// 进度百分比
/// </summary>
public int ProgressPercentage
{
get => _progressPercentage;
set => SetProperty(ref _progressPercentage, value);
}
/// <summary>
/// 是否正在生成报告
/// </summary>
public bool IsGenerating
{
get => _isGenerating;
set
{
if (SetProperty(ref _isGenerating, value))
{
// 刷新命令状态
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
}
}
}
/// <summary>
/// 是否有碰撞
/// </summary>
public bool HasCollisions
{
get => _hasCollisions;
set
{
if (SetProperty(ref _hasCollisions, value))
{
// 刷新命令状态
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
}
}
}
/// <summary>
/// 总结消息
/// </summary>
public string SummaryMessage
{
get => _summaryMessage;
set => SetProperty(ref _summaryMessage, value);
}
/// <summary>
/// 建议消息
/// </summary>
public string RecommendationMessage
{
get => _recommendationMessage;
set => SetProperty(ref _recommendationMessage, value);
}
/// <summary>
/// 运动构件信息
/// </summary>
public string MovingObjectInfo
{
get => _movingObjectInfo;
set => SetProperty(ref _movingObjectInfo, value);
}
/// <summary>
/// 被撞物体清单
/// </summary>
public ObservableCollection<string> CollidedObjectsList
{
get => _collidedObjectsList;
set => SetProperty(ref _collidedObjectsList, value);
}
/// <summary>
/// 去重后的被撞物体总数
/// </summary>
public int UniqueCollidedObjectsCount
{
get => _uniqueCollidedObjectsCount;
set => SetProperty(ref _uniqueCollidedObjectsCount, value);
}
/// <summary>
/// 路径名称
/// </summary>
public string PathName
{
get => _pathName;
set => SetProperty(ref _pathName, value);
}
/// <summary>
/// 测试名称(来自检测记录)
/// </summary>
public string TestName
{
get => _testName;
set => SetProperty(ref _testName, value);
}
/// <summary>
/// 测试时间(来自检测记录)
/// </summary>
public DateTime? TestTime
{
get => _testTime;
set => SetProperty(ref _testTime, value);
}
/// <summary>
/// 测试时间显示字符串
/// </summary>
public string TestTimeDisplay => _testTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "未知时间";
/// <summary>
/// 动画帧率
/// </summary>
public int FrameRate
{
get => _frameRate;
set => SetProperty(ref _frameRate, value);
}
/// <summary>
/// 动画时长
/// </summary>
public double Duration
{
get => _duration;
set => SetProperty(ref _duration, value);
}
/// <summary>
/// 检测容差
/// </summary>
public double DetectionTolerance
{
get => _detectionTolerance;
set => SetProperty(ref _detectionTolerance, value);
}
/// <summary>
/// 当前报告结果
/// </summary>
public CollisionReportResult CurrentReport
{
get => _currentReport;
set => SetProperty(ref _currentReport, value);
}
/// <summary>
/// 截图预览图像源(兼容旧版本,返回当前选中的截图)
/// </summary>
public BitmapImage ScreenshotPreviewSource => GetScreenshotBitmap(SelectedScreenshot);
/// <summary>
/// 截图列表
/// </summary>
private ObservableCollection<CollisionReportScreenshot> _screenshots = new ObservableCollection<CollisionReportScreenshot>();
public ObservableCollection<CollisionReportScreenshot> Screenshots
{
get => _screenshots;
set
{
if (SetProperty(ref _screenshots, value))
{
OnPropertyChanged(nameof(HasScreenshots));
OnPropertyChanged(nameof(ScreenshotPreviewSource));
}
}
}
/// <summary>
/// 当前选中的截图
/// </summary>
private CollisionReportScreenshot _selectedScreenshot;
public CollisionReportScreenshot SelectedScreenshot
{
get => _selectedScreenshot;
set
{
if (SetProperty(ref _selectedScreenshot, value))
{
OnPropertyChanged(nameof(ScreenshotPreviewSource));
}
}
}
/// <summary>
/// 是否有截图
/// </summary>
public bool HasScreenshots => Screenshots?.Count > 0;
/// <summary>
/// 截图数量
/// </summary>
public int ScreenshotCount => Screenshots?.Count ?? 0;
/// <summary>
/// 排除对象列表
/// </summary>
public ObservableCollection<ExcludedObjectRecord> ExcludedObjects
{
get => _excludedObjects;
set
{
if (SetProperty(ref _excludedObjects, value))
{
OnPropertyChanged(nameof(HasExcludedObjects));
OnPropertyChanged(nameof(ExcludedObjectsCount));
}
}
}
/// <summary>
/// 是否有排除对象
/// </summary>
public bool HasExcludedObjects => ExcludedObjects?.Count > 0;
/// <summary>
/// 排除对象数量
/// </summary>
public int ExcludedObjectsCount => ExcludedObjects?.Count ?? 0;
/// <summary>
/// 获取截图的BitmapImage
/// </summary>
private BitmapImage GetScreenshotBitmap(CollisionReportScreenshot screenshot)
{
if (screenshot == null || string.IsNullOrEmpty(screenshot.FilePath) || !File.Exists(screenshot.FilePath))
{
return null;
}
try
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(screenshot.FilePath);
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
catch (Exception ex)
{
LogManager.Error($"加载截图预览失败: {ex.Message}");
return null;
}
}
#endregion
#region
/// <summary>
/// 导出报告命令
/// </summary>
public ICommand ExportReportCommand { get; }
/// <summary>
/// 关闭窗口命令
/// </summary>
public ICommand CloseCommand { get; }
/// <summary>
/// 添加截图命令
/// </summary>
public ICommand AddScreenshotCommand { get; }
/// <summary>
/// 删除截图命令
/// </summary>
public ICommand DeleteScreenshotCommand { get; }
/// <summary>
/// 选择截图命令
/// </summary>
public ICommand SelectScreenshotCommand { get; }
/// <summary>
/// 截图设置命令
/// </summary>
public ICommand ScreenshotSettingsCommand { get; }
/// <summary>
/// 高亮碰撞对象命令
/// </summary>
public ICommand HighlightCollisionCommand { get; }
#endregion
#region
// 默认截图设置0表示未初始化将在构造函数中设置为当前视窗尺寸
private int _screenshotWidth = 0;
private int _screenshotHeight = 0;
private ImageFormat _screenshotFormat = ImageFormat.Png;
/// <summary>
/// 截图宽度
/// </summary>
public int ScreenshotWidthSetting
{
get => _screenshotWidth;
set => SetProperty(ref _screenshotWidth, value);
}
/// <summary>
/// 截图高度
/// </summary>
public int ScreenshotHeightSetting
{
get => _screenshotHeight;
set => SetProperty(ref _screenshotHeight, value);
}
/// <summary>
/// 截图格式
/// </summary>
public ImageFormat ScreenshotFormatSetting
{
get => _screenshotFormat;
set => SetProperty(ref _screenshotFormat, value);
}
#endregion
#region
public CollisionReportViewModel()
{
// 初始化集合
AnimationCollisions = new ObservableCollection<CollisionReportItem>();
CollidedObjectsList = new ObservableCollection<string>();
ExcludedObjects = new ObservableCollection<ExcludedObjectRecord>();
Statistics = new CollisionReportStatistics();
// 初始化命令
ExportReportCommand = new RelayCommand(async () => await ExportReportAsync(), () => !IsGenerating);
CloseCommand = new RelayCommand(CloseWindow);
HighlightCollisionCommand = new RelayCommand<CollisionReportItem>(HighlightCollision, item => item?.CollisionData != null);
AddScreenshotCommand = new RelayCommand(ExecuteAddScreenshot, CanExecuteAddScreenshot);
DeleteScreenshotCommand = new RelayCommand<CollisionReportScreenshot>(ExecuteDeleteScreenshot, s => s != null);
SelectScreenshotCommand = new RelayCommand<CollisionReportScreenshot>(ExecuteSelectScreenshot);
ScreenshotSettingsCommand = new RelayCommand(ExecuteScreenshotSettings);
// 初始化状态
ReportStatus = CollisionReportStatus.Generating;
ReportTitle = "NavisworksTransport - 碰撞检测报告";
IsGenerating = false;
HasCollisions = false;
}
/// <summary>
/// 初始化截图设置为当前视窗尺寸
/// </summary>
private void InitializeScreenshotSettings()
{
var generator = new Core.NavigationMapGenerator();
var viewportSize = generator.GetCurrentViewportSize();
_screenshotWidth = viewportSize.Width;
_screenshotHeight = viewportSize.Height;
LogManager.Info($"截图设置已初始化为当前视窗尺寸: {_screenshotWidth}x{_screenshotHeight}");
}
#endregion
#region
/// <summary>
/// 初始化报告数据
/// </summary>
/// <param name="reportResult">报告结果</param>
public void InitializeReport(CollisionReportResult reportResult)
{
if (reportResult == null)
{
SetEmptyReport();
return;
}
// 保存当前报告结果
CurrentReport = reportResult;
// 初始化截图设置(此时文档已准备好)
if (ScreenshotWidthSetting == 0 || ScreenshotHeightSetting == 0)
{
InitializeScreenshotSettings();
}
try
{
IsGenerating = true;
ReportStatus = CollisionReportStatus.Generating;
ProgressMessage = "正在解析报告数据...";
ProgressPercentage = 0;
// 清空现有数据
AnimationCollisions.Clear();
CollidedObjectsList.Clear();
Screenshots.Clear();
ExcludedObjects.Clear();
// 加载截图列表
if (reportResult.Screenshots?.Count > 0)
{
foreach (var screenshot in reportResult.Screenshots.OrderBy(s => s.SortOrder))
{
Screenshots.Add(screenshot);
}
// 默认选中第一张
SelectedScreenshot = Screenshots.FirstOrDefault();
LogManager.Info($"碰撞报告加载了 {Screenshots.Count} 张截图");
}
// 设置运动构件信息
MovingObjectInfo = reportResult.MovingObjectInfo;
// 设置被撞物体清单和计数
UniqueCollidedObjectsCount = reportResult.UniqueCollidedObjectsCount;
if (reportResult.CollidedObjectsList != null)
{
foreach (var objName in reportResult.CollidedObjectsList)
{
CollidedObjectsList.Add(objName);
}
}
// 加载排除对象列表
if (reportResult.ExcludedObjects?.Count > 0)
{
foreach (var excludedObj in reportResult.ExcludedObjects)
{
ExcludedObjects.Add(excludedObj);
}
LogManager.Info($"碰撞报告加载了 {ExcludedObjects.Count} 个排除对象");
}
// 设置动画参数
PathName = reportResult.PathName;
FrameRate = reportResult.FrameRate;
Duration = reportResult.Duration;
DetectionTolerance = reportResult.DetectionTolerance;
// 🔥 从数据库查询测试名称和时间
LoadTestInfoFromDatabase(reportResult.ResultId);
// 更新统计信息
UpdateStatistics(reportResult);
// 分类并添加碰撞项
ProcessCollisionData(reportResult.AllCollisions);
// 生成总结和建议
GenerateSummaryAndRecommendations(reportResult);
// 更新状态
HasCollisions = reportResult.TotalCollisions > 0;
ReportStatus = HasCollisions ? CollisionReportStatus.Generated : CollisionReportStatus.Empty;
ProgressMessage = "报告生成完成";
ProgressPercentage = 100;
LogManager.Info($"碰撞报告ViewModel初始化完成发现 {reportResult.TotalCollisions} 个碰撞");
}
catch (Exception ex)
{
LogManager.Error($"初始化碰撞报告失败: {ex.Message}");
SetFailedReport(ex.Message);
}
finally
{
IsGenerating = false;
}
}
/// <summary>
/// 更新进度
/// </summary>
/// <param name="percentage">进度百分比</param>
/// <param name="message">进度消息</param>
public void UpdateProgress(int percentage, string message)
{
SafeExecute(() =>
{
ProgressPercentage = Math.Max(0, Math.Min(100, percentage));
ProgressMessage = message ?? "";
}, "更新报告生成进度", true);
}
#endregion
#region
/// <summary>
/// 从数据库加载测试信息(测试名称和时间)
/// </summary>
private void LoadTestInfoFromDatabase(int resultId)
{
try
{
if (resultId <= 0)
{
LogManager.Debug("[LoadTestInfoFromDatabase] ResultId无效跳过查询");
return;
}
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase == null)
{
LogManager.Warning("[LoadTestInfoFromDatabase] PathDatabase不可用");
return;
}
// 使用ResultId查询检测记录
var record = pathDatabase.GetDetectionResultById(resultId);
if (record != null)
{
TestName = record.TestName ?? "未命名测试";
TestTime = record.TestTime;
LogManager.Info($"[LoadTestInfoFromDatabase] 加载测试信息: Name={TestName}, Time={TestTimeDisplay}");
}
else
{
LogManager.Warning($"[LoadTestInfoFromDatabase] 未找到检测记录: ResultId={resultId}");
TestName = "未知测试";
TestTime = null;
}
}
catch (Exception ex)
{
LogManager.Error($"[LoadTestInfoFromDatabase] 加载测试信息失败: {ex.Message}");
TestName = "未知测试";
TestTime = null;
}
}
/// <summary>
/// 更新统计信息
/// </summary>
private void UpdateStatistics(CollisionReportResult reportResult)
{
Statistics = new CollisionReportStatistics
{
TotalCollisions = reportResult.TotalCollisions,
AnimationCollisions = reportResult.AnimationCollisions,
GenerationTime = $"{reportResult.GenerationTimeMs}ms",
ReportType = "综合碰撞报告"
};
// 计算状态统计
if (reportResult.AllCollisions?.Count > 0)
{
Statistics.NewCollisions = reportResult.AllCollisions.Count(c => c.Status == Autodesk.Navisworks.Api.Clash.ClashResultStatus.New);
Statistics.ActiveCollisions = reportResult.AllCollisions.Count(c => c.Status == Autodesk.Navisworks.Api.Clash.ClashResultStatus.Active);
Statistics.ReviewedCollisions = reportResult.AllCollisions.Count(c => c.Status == Autodesk.Navisworks.Api.Clash.ClashResultStatus.Reviewed);
Statistics.ApprovedCollisions = reportResult.AllCollisions.Count(c => c.Status == Autodesk.Navisworks.Api.Clash.ClashResultStatus.Approved);
Statistics.ResolvedCollisions = reportResult.AllCollisions.Count(c => c.Status == Autodesk.Navisworks.Api.Clash.ClashResultStatus.Resolved);
}
}
/// <summary>
/// 处理碰撞数据
/// </summary>
private void ProcessCollisionData(List<CollisionResult> collisions)
{
if (collisions == null || collisions.Count == 0)
return;
var collisionItems = new List<CollisionReportItem>();
int index = 1;
foreach (var collision in collisions)
{
var item = CreateCollisionReportItem(collision, index++);
collisionItems.Add(item);
}
// 更新UI集合按序号排序保持顺序
SafeExecute(() =>
{
foreach (var item in collisionItems.OrderBy(i => i.Index))
AnimationCollisions.Add(item);
}, "更新碰撞报告项目列表", true);
}
/// <summary>
/// 创建碰撞报告项目
/// </summary>
private CollisionReportItem CreateCollisionReportItem(CollisionResult collision, int index)
{
// 获取元素名称和全路径
var item1Name = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item1);
var item2Name = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2);
var item1Path = GetModelItemFullPath(collision.Item1);
var item2Path = GetModelItemFullPath(collision.Item2);
var title = $"{item1Name} ↔ {item2Name}";
var item = new CollisionReportItem
{
Index = index,
Title = string.IsNullOrEmpty(title.Trim().Replace("↔", "").Trim()) ? "未命名碰撞" : title,
StatusText = GetStatusDisplayText(collision.Status),
StatusColor = GetStatusColor(collision.Status),
Details = $"位置: ({collision.Center.X:F2}, {collision.Center.Y:F2}, {collision.Center.Z:F2})",
Item1FullPath = $"🚛 运动物体: {item1Path}",
Item2FullPath = $"💥 被撞对象: {item2Path}",
CollisionData = collision
};
return item;
}
/// <summary>
/// 获取ModelItem的完整层级路径
/// </summary>
private string GetModelItemFullPath(ModelItem item)
{
if (item == null) return "未知对象";
try
{
var pathParts = new List<string>();
var current = item;
int levels = 0;
while (current != null && levels < 20)
{
var name = ModelItemAnalysisHelper.GetSafeDisplayName(current);
if (!string.IsNullOrEmpty(name) && name != "未命名对象")
{
pathParts.Insert(0, name);
}
current = current.Parent;
levels++;
}
return pathParts.Count > 0 ? string.Join(" > ", pathParts) : "未知对象";
}
catch
{
return ModelItemAnalysisHelper.GetSafeDisplayName(item);
}
}
/// <summary>
/// 获取状态显示文本
/// </summary>
private string GetStatusDisplayText(Autodesk.Navisworks.Api.Clash.ClashResultStatus status)
{
switch (status)
{
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.New:
return "新发现";
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.Active:
return "活跃";
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.Reviewed:
return "已审阅";
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.Approved:
return "已批准";
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.Resolved:
return "已解决";
default:
return "未知";
}
}
/// <summary>
/// 获取状态颜色
/// </summary>
private string GetStatusColor(Autodesk.Navisworks.Api.Clash.ClashResultStatus status)
{
switch (status)
{
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.New:
return "#FF6B6B"; // 红色 - 新问题
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.Active:
return "#FFA500"; // 橙色 - 活跃
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.Reviewed:
return "#87CEEB"; // 天蓝色 - 已审阅
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.Approved:
return "#98FB98"; // 浅绿色 - 已批准
case Autodesk.Navisworks.Api.Clash.ClashResultStatus.Resolved:
return "#90EE90"; // 绿色 - 已解决
default:
return "#D3D3D3"; // 灰色 - 未知
}
}
/// <summary>
/// 生成总结和建议
/// </summary>
private void GenerateSummaryAndRecommendations(CollisionReportResult reportResult)
{
if (reportResult.TotalCollisions == 0)
{
SummaryMessage = "✅ 未发现碰撞问题,路径规划良好。";
RecommendationMessage = "当前路径设计合理,无需调整。建议定期进行碰撞检测以确保持续的安全性。";
}
else
{
SummaryMessage = $"⚠️ 发现 {reportResult.TotalCollisions} 个碰撞问题,需要采取措施处理。";
var recommendations = new List<string>
{
"1. 调整路径规划,避开碰撞区域",
"2. 检查物体尺寸和安全边距设置",
"3. 考虑增加临时障碍物标记",
"4. 重新评估物流流程设计",
"5. 建议进行更详细的碰撞检测分析"
};
RecommendationMessage = string.Join("\n", recommendations);
}
}
/// <summary>
/// 设置空报告状态
/// </summary>
private void SetEmptyReport()
{
ReportStatus = CollisionReportStatus.Empty;
IsGenerating = false;
HasCollisions = false;
SummaryMessage = "📄 暂无碰撞数据";
RecommendationMessage = "请先运行碰撞检测或动画播放以生成碰撞数据。";
Statistics = new CollisionReportStatistics { ReportType = "空报告" };
}
/// <summary>
/// 设置失败报告状态
/// </summary>
private void SetFailedReport(string errorMessage)
{
ReportStatus = CollisionReportStatus.Failed;
IsGenerating = false;
HasCollisions = false;
SummaryMessage = "❌ 报告生成失败";
RecommendationMessage = $"错误信息: {errorMessage}";
Statistics = new CollisionReportStatistics { ReportType = "生成失败" };
}
#endregion
#region
/// <summary>
/// 导出报告
/// </summary>
private async Task ExportReportAsync()
{
try
{
var saveFileDialog = new SaveFileDialog
{
Title = "导出碰撞报告",
Filter = "HTML报告 (*.html)|*.html|碰撞报告 (*.txt)|*.txt|所有文件 (*.*)|*.*",
DefaultExt = "html",
FileName = $"NavisworksTransport_CollisionReport_{DateTime.Now:yyyyMMdd_HHmmss}"
};
if (saveFileDialog.ShowDialog() == true)
{
await Task.Run(() =>
{
var content = GenerateExportContent(Path.GetExtension(saveFileDialog.FileName).ToLower(), saveFileDialog.FileName);
File.WriteAllText(saveFileDialog.FileName, content, Encoding.UTF8);
});
LogManager.Info($"碰撞报告已导出到: {saveFileDialog.FileName}");
// 这里可以显示导出成功的提示
}
}
catch (Exception ex)
{
LogManager.Error($"导出碰撞报告失败: {ex.Message}");
}
}
/// <summary>
/// 自动导出报告到默认目录(关闭窗口时调用)
/// </summary>
public async Task AutoExportReportAsync()
{
try
{
if (CurrentReport == null || !HasCollisions) return;
// 生成默认文件名和路径
var sanitizedName = PathHelper.SanitizeFileName(CurrentReport.PathName ?? "未知路径");
var fileName = $"NavisworksTransport_CollisionReport_{sanitizedName}_{DateTime.Now:yyyyMMdd_HHmmss}.html";
var reportDir = PathHelper.GetReportDirectory();
var filePath = Path.Combine(reportDir, fileName);
await Task.Run(() =>
{
var content = GenerateHtmlReport(filePath);
File.WriteAllText(filePath, content, Encoding.UTF8);
});
LogManager.Info($"报告已自动导出到: {filePath}");
}
catch (Exception ex)
{
LogManager.Error($"自动导出报告失败: {ex.Message}");
}
}
/// <summary>
/// 生成导出内容
/// </summary>
private string GenerateExportContent(string fileExtension, string filePath = null)
{
if (fileExtension == ".html")
{
return GenerateHtmlReport(filePath);
}
else
{
return GenerateTextReport();
}
}
/// <summary>
/// 生成文本报告
/// </summary>
private string GenerateTextReport()
{
var report = new StringBuilder();
var now = DateTime.Now;
// 报告头部
report.AppendLine("========================================");
report.AppendLine(" NavisworksTransport 碰撞检测报告");
report.AppendLine("========================================");
report.AppendLine($"生成时间: {now:yyyy年MM月dd日 HH:mm:ss}");
report.AppendLine($"报告类型: {Statistics.ReportType}");
report.AppendLine();
// 检测记录信息
report.AppendLine("=== 检测记录 ===");
report.AppendLine($"测试名称: {TestName ?? ""}");
report.AppendLine($"检测时间: {TestTimeDisplay}");
report.AppendLine($"路径名称: {PathName}");
report.AppendLine();
// 总体统计
report.AppendLine("=== 总体统计 ===");
report.AppendLine($"总碰撞数: {Statistics.TotalCollisions}");
report.AppendLine($"碰撞构件: {UniqueCollidedObjectsCount}个");
report.AppendLine($"检测点: {Statistics.AnimationCollisions}个");
report.AppendLine();
// 动画参数
report.AppendLine("=== 动画参数 ===");
report.AppendLine($"帧率: {FrameRate} FPS");
report.AppendLine($"时长: {Duration:F1} 秒");
report.AppendLine($"检测容差: {DetectionTolerance:F2} 米");
report.AppendLine();
// 运动构件信息
if (!string.IsNullOrEmpty(MovingObjectInfo))
{
report.AppendLine($"{MovingObjectInfo}");
}
report.AppendLine();
// 被撞物体清单
if (CollidedObjectsList?.Count > 0)
{
report.AppendLine("=== 碰撞构件清单 ===");
int index = 1;
foreach (var objName in CollidedObjectsList.Take(20)) // 限制显示前20个
{
report.AppendLine($" {index}. {objName}");
index++;
}
if (CollidedObjectsList.Count > 20)
{
report.AppendLine($" ... 还有 {CollidedObjectsList.Count - 20} 个构件");
}
report.AppendLine();
}
// 详细碰撞信息
if (HasCollisions)
{
AddCollisionDetailsToTextReport(report, "碰撞详情", AnimationCollisions);
}
// 总结和建议
report.AppendLine("=== 总结与建议 ===");
report.AppendLine(SummaryMessage);
report.AppendLine();
report.AppendLine("建议措施:");
report.AppendLine(RecommendationMessage);
report.AppendLine();
report.AppendLine("========================================");
report.AppendLine($"报告生成完成 - {now:HH:mm:ss}");
report.AppendLine("========================================");
return report.ToString();
}
/// <summary>
/// 添加碰撞详细信息到文本报告
/// </summary>
private void AddCollisionDetailsToTextReport(StringBuilder report, string sectionTitle, ObservableCollection<CollisionReportItem> items)
{
if (items?.Count > 0)
{
report.AppendLine($"--- {sectionTitle} ---");
foreach (var item in items.Take(10)) // 限制显示数量
{
report.AppendLine($"• {item.Title}");
report.AppendLine($" 状态: {item.StatusText}");
report.AppendLine($" {item.Details.Replace("\n", "\n ")}");
report.AppendLine();
}
if (items.Count > 10)
{
report.AppendLine($"... 还有 {items.Count - 10} 个{sectionTitle}未显示");
}
report.AppendLine();
}
}
/// <summary>
/// 生成HTML报告 - 统一使用 CollisionReportHtmlGenerator
/// </summary>
private string GenerateHtmlReport(string htmlFilePath = null)
{
// 确保 CurrentReport 的截图列表与 ViewModel 同步
if (CurrentReport != null && Screenshots != null)
{
CurrentReport.Screenshots = Screenshots.ToList();
}
return CollisionReportHtmlGenerator.GenerateHtmlReport(CurrentReport, htmlFilePath);
}
/// <summary>
/// 高亮碰撞对象
/// </summary>
private void HighlightCollision(CollisionReportItem item)
{
try
{
if (item?.CollisionData == null)
return;
// 使用 ClashDetectiveResultsCategory深红色不是预计算高亮
var collisions = new List<CollisionResult> { item.CollisionData };
ModelHighlightHelper.ManageCollisionHighlightsByCategory(
ModelHighlightHelper.ClashDetectiveResultsCategory,
collisions,
clearOtherCategories: true);
LogManager.Info($"高亮碰撞对象: {item.Title}");
}
catch (Exception ex)
{
LogManager.Error($"高亮碰撞对象失败: {ex.Message}");
}
}
/// <summary>
/// 关闭窗口
/// </summary>
private void CloseWindow()
{
// 这个方法会由View中的代码调用
LogManager.Info("关闭碰撞报告窗口");
}
/// <summary>
/// 执行添加新截图(使用默认设置,不弹出对话框)
/// </summary>
private void ExecuteAddScreenshot()
{
try
{
// 构建路径相关的截图目录与GenerateCollisionReportCommand保持一致
// 格式: screenshots/{RouteId前8位}_{PathName}/
string routeId = CurrentReport.RouteId ?? "unknown";
string pathName = CurrentReport.PathName ?? "collision";
string pathDirName = string.Format("{0}_{1}",
routeId.Length >= 8 ? routeId.Substring(0, 8) : routeId,
PathHelper.SanitizeFileName(pathName));
string pathScreenshotDir = System.IO.Path.Combine(
PathHelper.GetScreenshotDirectory(),
pathDirName);
// 使用路径相关的目录生成截图
string screenshotPath = PathHelper.GenerateSceneScreenshotToDirectory(
pathScreenshotDir,
pathName,
ScreenshotWidthSetting,
ScreenshotHeightSetting,
ScreenshotFormatSetting,
"collision"
);
if (screenshotPath != null)
{
// 创建截图对象
var screenshot = new CollisionReportScreenshot
{
FilePath = screenshotPath,
Format = PathHelper.ImageFormatToExtension(ScreenshotFormatSetting).ToUpper(),
Width = ScreenshotWidthSetting,
Height = ScreenshotHeightSetting,
SortOrder = Screenshots.Count
};
// 添加到列表
Screenshots.Add(screenshot);
SelectedScreenshot = screenshot;
// 同步到报告数据
if (CurrentReport.Screenshots == null)
CurrentReport.Screenshots = new List<CollisionReportScreenshot>();
CurrentReport.Screenshots.Add(screenshot);
// 保存到数据库
try
{
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
// 使用 ResultId 精确关联到当前报告记录
if (pathDatabase != null && CurrentReport.ResultId > 0)
{
int dbId = pathDatabase.SaveCollisionReportScreenshot(
CurrentReport.ResultId,
screenshotPath,
screenshot.Format,
screenshot.Width,
screenshot.Height,
screenshot.SortOrder
);
screenshot.DatabaseId = dbId; // 保存数据库Id用于后续删除
LogManager.Info($"新截图已保存到数据库: DatabaseId={dbId}, ResultId={CurrentReport.ResultId}, Path={screenshotPath}");
}
else
{
LogManager.Warning($"无法保存截图到数据库: ResultId={CurrentReport.ResultId}, RouteId={CurrentReport.RouteId}");
}
}
catch (Exception dbEx)
{
LogManager.Error($"保存新截图到数据库失败: {dbEx.Message}");
}
OnPropertyChanged(nameof(HasScreenshots));
OnPropertyChanged(nameof(ScreenshotCount));
LogManager.Info($"已添加新截图,当前共 {Screenshots.Count} 张");
}
}
catch (Exception ex)
{
LogManager.Error($"添加截图失败: {ex.Message}");
System.Windows.MessageBox.Show($"添加截图失败: {ex.Message}", "错误",
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
}
}
/// <summary>
/// 检查是否可以添加截图
/// </summary>
private bool CanExecuteAddScreenshot()
{
return CurrentReport != null;
}
/// <summary>
/// 执行截图设置 - 打开对话框修改截图参数
/// </summary>
private void ExecuteScreenshotSettings()
{
try
{
var dialog = new GenerateNavigationMapDialog();
dialog.Title = "截图设置";
// 设置当前值,默认优先使用当前视窗尺寸
dialog.SetInitialValues(ScreenshotWidthSetting, ScreenshotHeightSetting, ScreenshotFormatSetting, preferCurrentViewport: true);
DialogHelper.SetOwnerSafely(dialog);
if (dialog.ShowDialog() == true)
{
// 更新设置
ScreenshotWidthSetting = dialog.ImageWidth;
ScreenshotHeightSetting = dialog.ImageHeight;
ScreenshotFormatSetting = dialog.ImageFormat;
LogManager.Info($"截图设置已更新: {ScreenshotWidthSetting}x{ScreenshotHeightSetting}, {ScreenshotFormatSetting}");
}
}
catch (Exception ex)
{
LogManager.Error($"打开截图设置失败: {ex.Message}");
}
}
/// <summary>
/// 执行删除截图
/// </summary>
private void ExecuteDeleteScreenshot(CollisionReportScreenshot screenshot)
{
if (screenshot == null) return;
try
{
var result = System.Windows.MessageBox.Show(
"确定要删除这张截图吗?",
"确认删除",
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Question);
if (result != System.Windows.MessageBoxResult.Yes) return;
// 删除物理文件(如果存在)
if (!string.IsNullOrEmpty(screenshot.FilePath) && System.IO.File.Exists(screenshot.FilePath))
{
try
{
System.IO.File.Delete(screenshot.FilePath);
LogManager.Debug($"已删除截图文件: {screenshot.FilePath}");
}
catch (Exception fileEx)
{
LogManager.Error($"删除截图文件失败: {fileEx.Message}");
// 继续删除数据库记录,不中断流程
}
}
// 从数据库删除如果知道DatabaseId
LogManager.Info($"开始删除截图DatabaseId={screenshot.DatabaseId}, FilePath={screenshot.FilePath}");
if (screenshot.DatabaseId > 0)
{
try
{
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase != null)
{
// 删除前查询数量
var beforeScreenshots = pathDatabase.GetCollisionReportScreenshots(CurrentReport.ResultId);
var beforeCount = beforeScreenshots?.Count ?? 0;
var targetExists = beforeScreenshots?.Any(s => s.Id == screenshot.DatabaseId) ?? false;
pathDatabase.DeleteCollisionReportScreenshot(screenshot.DatabaseId);
// 删除后验证
var afterScreenshots = pathDatabase.GetCollisionReportScreenshots(CurrentReport.ResultId);
var afterCount = afterScreenshots?.Count ?? 0;
var targetStillExists = afterScreenshots?.Any(s => s.Id == screenshot.DatabaseId) ?? false;
LogManager.Info($"数据库删除验证: 删除前={beforeCount}张(目标存在:{targetExists}), 删除后={afterCount}张(目标仍存在:{targetStillExists}), 删除Id={screenshot.DatabaseId}");
}
else
{
LogManager.Error("无法获取PathDatabase数据库删除失败");
}
}
catch (Exception dbEx)
{
LogManager.Error($"从数据库删除截图失败: {dbEx.Message}");
}
}
else
{
LogManager.Warning($"截图DatabaseId为0跳过数据库删除");
}
// 从列表移除
Screenshots.Remove(screenshot);
// 从报告数据移除
CurrentReport.Screenshots?.Remove(screenshot);
// 更新选中项
if (SelectedScreenshot == screenshot)
{
SelectedScreenshot = Screenshots.FirstOrDefault();
}
// 重新排序并更新数据库
for (int i = 0; i < Screenshots.Count; i++)
{
Screenshots[i].SortOrder = i;
}
OnPropertyChanged(nameof(HasScreenshots));
OnPropertyChanged(nameof(ScreenshotCount));
LogManager.Info($"已删除截图,剩余 {Screenshots.Count} 张");
}
catch (Exception ex)
{
LogManager.Error($"删除截图失败: {ex.Message}");
}
}
/// <summary>
/// 执行选择截图
/// </summary>
private void ExecuteSelectScreenshot(CollisionReportScreenshot screenshot)
{
if (screenshot != null)
{
SelectedScreenshot = screenshot;
LogManager.Debug($"选中截图: {screenshot.FilePath}");
}
}
/// <summary>
/// 高亮并聚焦到碰撞项目中的被撞对象Item2
/// 同时将运动物体Item1移动到碰撞时的位置和朝向还原碰撞场景
/// </summary>
public void HighlightAndFocusCollisionItem(CollisionReportItem collisionItem)
{
if (collisionItem?.CollisionData == null) return;
try
{
var collisionData = collisionItem.CollisionData;
// 首次查看碰撞时保存运动物体状态
if (collisionData.Item1 != null && collisionData.HasPositionInfo && collisionData.Item1Position != null)
{
if (_savedAnimatedObjectState == null || _currentAnimatedObject != collisionData.Item1)
{
_currentAnimatedObject = collisionData.Item1;
_savedAnimatedObjectState = CollisionSceneHelper.SaveAnimatedObjectState(collisionData.Item1);
}
}
// 🔥 关键修复:如果运动物体是虚拟物体,确保它是可见的
if (_currentAnimatedObject != null &&
VirtualObjectManager.Instance.IsVirtualObjectActive &&
VirtualObjectManager.Instance.CurrentVirtualObject == _currentAnimatedObject)
{
// 虚拟物体已存在但可能被隐藏,需要显示
// 使用默认尺寸 2x1x2 米ShowVirtualObject 内部会处理尺寸更新)
VirtualObjectManager.Instance.ShowVirtualObject(2.0, 1.0, 2.0);
LogManager.Debug("[碰撞报告] 虚拟物体已确保可见");
}
// 使用通用方法移动并聚焦到碰撞位置
CollisionSceneHelper.MoveToCollisionAndFocus(collisionData, _currentAnimatedObject);
}
catch (Exception ex)
{
LogManager.Error($"聚焦到被撞对象失败: {ex.Message}", ex);
}
}
/// <summary>
/// 恢复运动物体到原始状态(查看碰撞前保存的状态)
/// </summary>
public void RestoreAnimatedObjectToOriginalState()
{
CollisionSceneHelper.RestoreAnimatedObjectState(_currentAnimatedObject, _savedAnimatedObjectState);
// 清除保存的状态
_savedAnimatedObjectState = null;
_currentAnimatedObject = null;
}
#endregion
}
}