NavisworksTransport/src/Commands/GenerateCollisionReportCommand.cs

715 lines
28 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.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Clash;
using NavisworksTransport.Utils;
using NavisworksTransport.UI.WPF.Views;
namespace NavisworksTransport.Commands
{
/// <summary>
/// 碰撞报告参数类
/// </summary>
public class CollisionReportParameters
{
/// <summary>
/// 报告类型
/// </summary>
public enum ReportType
{
/// <summary>动画碰撞报告</summary>
Animation,
/// <summary>独立碰撞检测报告</summary>
Independent,
/// <summary>综合报告(包含所有类型)</summary>
Comprehensive
}
/// <summary>
/// 要生成的报告类型
/// </summary>
public ReportType Type { get; set; } = ReportType.Comprehensive;
/// <summary>
/// 是否包含详细信息
/// </summary>
public bool IncludeDetails { get; set; } = true;
/// <summary>
/// 是否自动高亮报告中的碰撞对象
/// </summary>
public bool AutoHighlight { get; set; } = false;
public PathPlanningResult ValidateParameters()
{
return PathPlanningResult.Success("参数验证通过");
}
}
/// <summary>
/// 碰撞数据收集结果
/// </summary>
public class CollisionDataResult
{
public List<CollisionResult> AllCollisions { get; set; } = new List<CollisionResult>();
public int BoundingBoxTestCount { get; set; } // 包围盒测试数量
public int ClashDetectiveCollisionCount { get; set; } // Clash Detective结果数量
public string MovingObjectInfo { get; set; } = string.Empty;
// 新增:被撞物体去重统计
public HashSet<string> UniqueCollidedObjects { get; set; } = new HashSet<string>();
}
/// <summary>
/// 碰撞报告结果类
/// </summary>
public class CollisionReportResult
{
public string ReportContent { get; set; }
public int TotalCollisions { get; set; }
public int AnimationCollisions { get; set; } // 检测点数量
public List<CollisionResult> AllCollisions { get; set; } = new List<CollisionResult>();
public long GenerationTimeMs { get; set; }
public string MovingObjectInfo { get; set; } = string.Empty;
// 新增:被撞物体清单
public List<string> CollidedObjectsList { get; set; } = new List<string>();
public int UniqueCollidedObjectsCount { get; set; }
// 新增:动画参数
public string PathName { get; set; } = "未知路径";
public int FrameRate { get; set; } = 30;
public double Duration { get; set; } = 10.0;
public double DetectionGap { get; set; } = 0.05;
}
/// <summary>
/// 生成碰撞报告Command
/// </summary>
public class GenerateCollisionReportCommand : CommandBase
{
#region
/// <summary>
/// 碰撞报告结果缓存 - 使用测试名称作为键
/// </summary>
private static readonly Dictionary<string, CollisionReportResult> _reportCache = new Dictionary<string, CollisionReportResult>();
/// <summary>
/// 缓存操作锁对象
/// </summary>
private static readonly object _cacheLock = new object();
#endregion
private readonly CollisionReportParameters _parameters;
public GenerateCollisionReportCommand(CollisionReportParameters parameters = null)
: base("GenerateCollisionReport", "生成碰撞报告", "生成并显示详细的碰撞检测报告")
{
_parameters = parameters ?? new CollisionReportParameters();
}
protected override PathPlanningResult ValidateParameters()
{
return _parameters.ValidateParameters();
}
protected override async Task<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
var startTime = DateTime.UtcNow;
UpdateProgress(10, "开始生成碰撞报告...");
// 检查缓存
var testNameKey = GetLatestTestNameForCache();
if (!string.IsNullOrEmpty(testNameKey))
{
lock (_cacheLock)
{
if (_reportCache.ContainsKey(testNameKey))
{
var cachedResult = _reportCache[testNameKey];
LogManager.Info($"使用缓存的碰撞报告: {testNameKey}");
UpdateProgress(100, "使用缓存报告");
// 显示缓存的报告
ShowReport(cachedResult);
var cachedMessage = cachedResult.TotalCollisions > 0 ?
$"碰撞报告显示完成(缓存):发现 {cachedResult.TotalCollisions} 个碰撞" :
"碰撞报告显示完成(缓存):未发现碰撞";
return PathPlanningResult<CollisionReportResult>.Success(cachedResult, cachedMessage);
}
else
{
LogManager.Info($"未找到缓存报告,开始生成新报告: {testNameKey}");
}
}
}
else
{
LogManager.Warning("无法获取测试名称,跳过缓存检查");
}
var result = new CollisionReportResult();
try
{
UpdateProgress(30, "收集碰撞数据...");
// 收集所有碰撞数据及统计信息(同步执行,避免 COM 线程问题)
var collisionData = CollectAllCollisionData();
result.AllCollisions = collisionData.AllCollisions;
// 修改统计数据
var clashIntegration = ClashDetectiveIntegration.Instance;
result.AnimationCollisions = clashIntegration?.AnimationCollisionCount ?? 0; // 检测点数量
result.TotalCollisions = collisionData.ClashDetectiveCollisionCount;
result.MovingObjectInfo = collisionData.MovingObjectInfo;
// 新增:被撞物体清单
result.CollidedObjectsList = collisionData.UniqueCollidedObjects.OrderBy(x => x).ToList();
result.UniqueCollidedObjectsCount = collisionData.UniqueCollidedObjects.Count;
// 新增:从动画管理器获取动画参数
var animationManager = Core.Animation.PathAnimationManager.GetInstance();
if (animationManager != null)
{
result.FrameRate = animationManager.AnimationFrameRate;
result.Duration = animationManager.AnimationDuration;
result.DetectionGap = animationManager.DetectionGap;
// PathName需要从动画管理器获取
result.PathName = animationManager.PathName ?? "未知路径";
}
LogManager.Info($"碰撞报告计数 - 检查点: {result.AnimationCollisions}, Clash Detective: {result.TotalCollisions}");
UpdateProgress(70, "生成报告内容...");
// 生成报告内容
result.ReportContent = GenerateReportContent(collisionData, _parameters.Type, _parameters.IncludeDetails);
UpdateProgress(90, "处理报告显示...");
// 自动高亮(如果启用)
if (_parameters.AutoHighlight && collisionData.AllCollisions.Count > 0)
{
try
{
var highlightIntegration = ClashDetectiveIntegration.Instance;
if (highlightIntegration != null)
{
var highlightColor = Color.Green; // 绿色 (Navisworks API中没有Orange)
highlightIntegration.ManageHighlightsByCategory(ModelHighlightHelper.CollisionResultsCategory, collisionData.AllCollisions, highlightColor, true);
LogManager.Info($"自动高亮显示报告中的 {collisionData.AllCollisions.Count} 个碰撞对象");
}
}
catch (Exception ex)
{
LogManager.Error($"自动高亮报告对象失败: {ex.Message}");
}
}
UpdateProgress(100, "报告生成完成");
// 显示报告UI线程
ShowReport(result);
// 将报告结果缓存起来
if (!string.IsNullOrEmpty(testNameKey))
{
lock (_cacheLock)
{
_reportCache[testNameKey] = result;
LogManager.Info($"碰撞报告已缓存: {testNameKey}, 缓存总数: {_reportCache.Count}");
}
}
}
catch (OperationCanceledException)
{
return PathPlanningResult.Failure("报告生成被取消");
}
catch (Exception ex)
{
LogManager.Error($"生成碰撞报告失败: {ex.Message}");
return PathPlanningResult.Failure($"报告生成失败: {ex.Message}");
}
result.GenerationTimeMs = (long)(DateTime.UtcNow - startTime).TotalMilliseconds;
var message = result.TotalCollisions > 0 ?
$"碰撞报告生成完成:发现 {result.TotalCollisions} 个碰撞" :
"碰撞报告生成完成:未发现碰撞";
return PathPlanningResult<CollisionReportResult>.Success(result, message);
}
#region
/// <summary>
/// 获取当前最新的物流碰撞检测测试名称作为缓存键
/// </summary>
/// <returns>测试名称,如果未找到则返回 null</returns>
private string GetLatestTestNameForCache()
{
try
{
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
var documentClash = doc.GetClash();
if (documentClash == null)
{
LogManager.Warning("无法获取 Clash Detective 文档,无法生成缓存键");
return null;
}
// 从动画管理器获取当前路径名称
var animationManager = Core.Animation.PathAnimationManager.GetInstance();
var currentPathName = animationManager?.PathName;
if (string.IsNullOrEmpty(currentPathName))
{
throw new InvalidOperationException("无法获取当前路径名称");
}
// 精确匹配当前路径的测试
var matchedTest = documentClash.TestsData.Tests.Cast<ClashTest>()
.Where(t => t.DisplayName.StartsWith("物流碰撞检测_") &&
t.DisplayName.Contains(currentPathName))
.OrderByDescending(t => t.DisplayName) // 同一路径可能有多个测试,取最新的
.FirstOrDefault();
if (matchedTest != null)
{
LogManager.Info($"找到当前路径的测试: {matchedTest.DisplayName}");
return matchedTest.DisplayName;
}
else
{
throw new InvalidOperationException($"未找到路径 '{currentPathName}' 的碰撞测试");
}
}
catch (Exception ex)
{
LogManager.Error($"获取测试名称作为缓存键失败: {ex.Message}");
throw; // 让错误暴露出来
}
}
/// <summary>
/// 清理过期缓存(可选功能)
/// </summary>
public static void ClearReportCache()
{
lock (_cacheLock)
{
var count = _reportCache.Count;
_reportCache.Clear();
LogManager.Info($"已清理碰撞报告缓存,共清理 {count} 个缓存项");
}
}
#endregion
/// <summary>
/// 收集所有碰撞数据及统计信息
/// </summary>
private CollisionDataResult CollectAllCollisionData()
{
var result = new CollisionDataResult();
var allCollisions = new List<CollisionResult>();
try
{
LogManager.Info("开始收集所有碰撞数据及统计信息...");
var clashIntegration = ClashDetectiveIntegration.Instance;
if (clashIntegration == null)
{
throw new InvalidOperationException("无法获取ClashDetectiveIntegration实例");
}
// 从动画管理器获取当前路径名称
var animationManager = Core.Animation.PathAnimationManager.GetInstance();
var currentPathName = animationManager?.PathName;
if (string.IsNullOrEmpty(currentPathName))
{
throw new InvalidOperationException("无法获取当前路径名称");
}
// 使用公共方法获取当前路径的测试
var matchedTest = clashIntegration.GetCurrentPathClashTest(currentPathName);
if (matchedTest == null)
{
throw new InvalidOperationException($"未找到路径 '{currentPathName}' 的碰撞测试");
}
LogManager.Info($"找到当前路径的测试: {matchedTest.DisplayName}");
// 使用ClashDetectiveIntegration的方法提取碰撞数据
var testCollisionsRaw = clashIntegration.ExtractClashResultsFromTest(matchedTest);
// 转换为CollisionResult对象
foreach (var clash in testCollisionsRaw)
{
var collision = new CollisionResult
{
ClashGuid = clash.Guid,
DisplayName = $"[{matchedTest.DisplayName}] {clash.DisplayName}",
Status = clash.Status,
Item1 = clash.Item1,
Item2 = clash.Item2,
Center = clash.Center,
Distance = clash.Distance,
CreatedTime = DateTime.Now
};
allCollisions.Add(collision);
}
// 统计匹配测试的碰撞总数
var totalCollisionCount = testCollisionsRaw.Count;
result.ClashDetectiveCollisionCount = totalCollisionCount;
LogManager.Debug($"匹配测试 '{matchedTest.DisplayName}' 包含 {totalCollisionCount} 个碰撞");
// 去重处理
var uniqueCollisions = allCollisions
.GroupBy(c => new { c.Item1, c.Item2, c.Center })
.Select(g => g.First())
.ToList();
// 新增:统计去重后的被撞物体
// 使用DictionaryModelItem作为键会自动调用Equals进行比较
var uniqueCollidedObjectsDict = new Dictionary<ModelItem, string>();
foreach (var collision in uniqueCollisions)
{
if (collision.Item2 != null)
{
// ModelItem作为字典的键会使用其Equals方法进行比较
if (!uniqueCollidedObjectsDict.ContainsKey(collision.Item2))
{
var item2Name = NavisworksApiHelper.GetModelItemNameWithAncestorFallback(collision.Item2);
uniqueCollidedObjectsDict[collision.Item2] = item2Name ?? "未知对象";
}
}
}
// 修复:保留所有碰撞对象,为相同名称的对象添加编号区分
var nameCountDict = new Dictionary<string, int>();
var uniqueCollidedObjectsList = new List<string>();
foreach (var name in uniqueCollidedObjectsDict.Values)
{
if (nameCountDict.ContainsKey(name))
{
nameCountDict[name]++;
uniqueCollidedObjectsList.Add($"{name} #{nameCountDict[name]}");
}
else
{
nameCountDict[name] = 1;
uniqueCollidedObjectsList.Add(name);
}
}
var uniqueCollidedObjectsSet = new HashSet<string>(uniqueCollidedObjectsList);
result.AllCollisions = uniqueCollisions;
result.UniqueCollidedObjects = uniqueCollidedObjectsSet;
result.BoundingBoxTestCount = 0;
// 从碰撞结果的Item1中获取运动构件信息
if (uniqueCollisions.Count > 0 && uniqueCollisions[0].Item1 != null)
{
var movingItem = uniqueCollisions[0].Item1;
var movingItemName = NavisworksApiHelper.GetModelItemNameWithAncestorFallback(movingItem) ?? "未知运动构件";
result.MovingObjectInfo = $"运动构件: {movingItemName}";
}
else
{
result.MovingObjectInfo = "运动构件: 未知";
}
LogManager.Info($"收集完成 - Clash Detective权威结果: {result.ClashDetectiveCollisionCount}个, 去重后碰撞对象: {uniqueCollisions.Count}个, 去重后被撞构件: {uniqueCollidedObjectsSet.Count}个");
return result;
}
catch (Exception ex)
{
LogManager.Error($"收集碰撞数据失败: {ex.Message}");
result.AllCollisions = allCollisions;
return result;
}
}
/// <summary>
/// 生成报告内容
/// </summary>
private string GenerateReportContent(CollisionDataResult collisionData,
CollisionReportParameters.ReportType reportType, bool includeDetails)
{
var report = new StringBuilder();
var now = DateTime.Now;
var collisions = collisionData.AllCollisions;
// 报告头部
report.AppendLine("========================================");
report.AppendLine(" NavisworksTransport 碰撞检测报告");
report.AppendLine("========================================");
report.AppendLine($"生成时间: {now:yyyy年MM月dd日 HH:mm:ss}");
report.AppendLine($"报告类型: {GetReportTypeName(reportType)}");
// 运动构件信息(原运动物体信息)
if (!string.IsNullOrEmpty(collisionData.MovingObjectInfo))
{
var movingInfo = collisionData.MovingObjectInfo.Replace("运动对象", "运动构件");
report.AppendLine($"{movingInfo}");
}
report.AppendLine();
// 总体统计
report.AppendLine("=== 总体统计 ===");
var clashIntegration = ClashDetectiveIntegration.Instance;
var animationCollisionCount = clashIntegration?.AnimationCollisionCount ?? 0;
report.AppendLine($"总碰撞数: {collisionData.ClashDetectiveCollisionCount}个");
report.AppendLine($"碰撞构件: {collisionData.UniqueCollidedObjects.Count}个");
report.AppendLine($"检测点: {animationCollisionCount}个");
report.AppendLine();
// 碰撞构件清单(新增)
if (collisionData.UniqueCollidedObjects?.Count > 0)
{
report.AppendLine("=== 碰撞构件清单 ===");
int index = 1;
foreach (var objName in collisionData.UniqueCollidedObjects.OrderBy(x => x).Take(20)) // 限制显示前20个
{
report.AppendLine($" {index}. {objName}");
index++;
}
if (collisionData.UniqueCollidedObjects.Count > 20)
{
report.AppendLine($" ... 还有 {collisionData.UniqueCollidedObjects.Count - 20} 个构件");
}
report.AppendLine();
}
// 详细信息
if (includeDetails && collisions.Count > 0)
{
report.AppendLine("=== 详细碰撞信息 ===");
report.AppendLine("--- 碰撞详情 ---");
foreach (var collision in collisions.Take(10)) // 限制显示数量
{
AppendCollisionDetail(report, collision);
}
if (collisions.Count > 10)
{
report.AppendLine($"... 还有 {collisions.Count - 10} 个碰撞未显示");
}
report.AppendLine();
}
// 建议和结论
report.AppendLine("=== 建议与结论 ===");
if (collisions.Count == 0)
{
report.AppendLine("✅ 未发现碰撞问题,路径规划良好。");
}
else
{
report.AppendLine($"⚠️ 发现 {collisions.Count} 个碰撞问题,建议采取以下措施:");
report.AppendLine(" 1. 调整路径规划,避开碰撞区域");
report.AppendLine(" 2. 检查车辆尺寸和安全边距设置");
report.AppendLine(" 3. 考虑增加临时障碍物标记");
report.AppendLine(" 4. 重新评估物流流程设计");
report.AppendLine(" 5. 建议进行更详细的碰撞检测分析");
}
report.AppendLine();
report.AppendLine("========================================");
report.AppendLine($"报告生成完成 - {now:HH:mm:ss}");
report.AppendLine("========================================");
return report.ToString();
}
/// <summary>
/// 添加碰撞详细信息到报告中
/// </summary>
private void AppendCollisionDetail(StringBuilder report, CollisionResult collision)
{
var item1Name = collision.Item1?.DisplayName ?? "未知对象";
var item2Name = collision.Item2?.DisplayName ?? "未知对象";
// 转换距离单位为米
var distanceInMeters = ConvertDistanceToMeters(collision.Distance);
report.AppendLine($"• {item1Name} ↔ {item2Name}");
report.AppendLine($" 状态: {GetStatusName(collision.Status)}, 距离: {distanceInMeters:F3}m");
// 转换位置坐标单位为米
var centerInMeters = ConvertPointToMeters(collision.Center);
report.AppendLine($" 位置: ({centerInMeters.X:F2}, {centerInMeters.Y:F2}, {centerInMeters.Z:F2})m");
report.AppendLine($" GUID: {collision.ClashGuid}");
report.AppendLine();
}
/// <summary>
/// 获取报告类型名称
/// </summary>
private string GetReportTypeName(CollisionReportParameters.ReportType reportType)
{
switch (reportType)
{
case CollisionReportParameters.ReportType.Animation:
return "动画碰撞报告";
case CollisionReportParameters.ReportType.Independent:
return "独立检测报告";
case CollisionReportParameters.ReportType.Comprehensive:
return "综合碰撞报告";
default:
return "未知类型";
}
}
/// <summary>
/// 获取碰撞状态名称
/// </summary>
private string GetStatusName(ClashResultStatus status)
{
switch (status)
{
case ClashResultStatus.New:
return "新发现";
case ClashResultStatus.Active:
return "活跃";
case ClashResultStatus.Reviewed:
return "已审阅";
case ClashResultStatus.Approved:
return "已批准";
case ClashResultStatus.Resolved:
return "已解决";
default:
return "未知";
}
}
/// <summary>
/// 显示报告
/// </summary>
private void ShowReport(CollisionReportResult reportResult)
{
try
{
// 确保在UI线程中执行
if (System.Windows.Application.Current != null)
{
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
{
// 已在UI线程上
ShowReportInternal(reportResult);
}
else
{
// 切换到UI线程
System.Windows.Application.Current.Dispatcher.Invoke(() => ShowReportInternal(reportResult));
}
}
else
{
// 如果WPF Application不存在回退到原来的方式
ShowReportInternal(reportResult);
}
}
catch (Exception ex)
{
LogManager.Error($"显示报告失败: {ex.Message}");
System.Windows.MessageBox.Show($"显示报告失败: {ex.Message}", "错误",
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
}
}
/// <summary>
/// 内部报告显示方法
/// </summary>
private void ShowReportInternal(CollisionReportResult reportResult)
{
try
{
// 使用新的WPF碰撞报告对话框
var reportDialog = CollisionReportDialog.ShowReport(reportResult);
if (reportDialog != null)
{
LogManager.Info("WPF碰撞报告对话框显示成功");
}
}
catch (Exception ex)
{
LogManager.Error($"显示WPF报告对话框失败: {ex.Message}");
}
}
/// <summary>
/// 创建综合报告命令
/// </summary>
public static GenerateCollisionReportCommand CreateComprehensive(bool autoHighlight = false)
{
return new GenerateCollisionReportCommand(new CollisionReportParameters
{
Type = CollisionReportParameters.ReportType.Comprehensive,
IncludeDetails = true,
AutoHighlight = autoHighlight
});
}
/// <summary>
/// 将距离转换为米
/// </summary>
private double ConvertDistanceToMeters(double distance)
{
try
{
var conversionFactor = UnitsConverter.GetUnitsToMetersConversionFactor();
return distance * conversionFactor;
}
catch (Exception ex)
{
LogManager.Warning($"距离单位转换失败: {ex.Message},使用原始值");
return distance;
}
}
/// <summary>
/// 将3D点坐标转换为米
/// </summary>
private Point3D ConvertPointToMeters(Point3D point)
{
try
{
var conversionFactor = UnitsConverter.GetUnitsToMetersConversionFactor();
return new Point3D(
point.X * conversionFactor,
point.Y * conversionFactor,
point.Z * conversionFactor
);
}
catch (Exception ex)
{
LogManager.Warning($"坐标单位转换失败: {ex.Message},使用原始值");
return point;
}
}
}
}