NavisworksTransport/src/Commands/GenerateCollisionReportCommand.cs

1143 lines
50 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.Core;
using NavisworksTransport.Core.Animation;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;
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>
/// 路径ID用于视角调整
/// </summary>
public string RouteId { get; set; } = string.Empty;
/// <summary>
/// 碰撞结果是否已经被高亮过了(用于避免重复高亮)
/// </summary>
public bool HasHighlighted { get; set; } = false;
/// <summary>
/// 是否显示报告窗口批处理模式设为false只生成报告不弹窗
/// </summary>
public bool ShowDialog { get; set; } = true;
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 int AnimationCollisions { get; set; } // 动画过程碰撞数量
public string MovingObjectInfo { get; set; } = string.Empty;
// 新增:被撞物体去重统计
public HashSet<string> UniqueCollidedObjects { get; set; } = new HashSet<string>();
}
/// <summary>
/// 碰撞报告截图项
/// </summary>
public class CollisionReportScreenshot
{
/// <summary>截图唯一标识UI用</summary>
public string Id { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>数据库记录Id用于删除</summary>
public int DatabaseId { get; set; }
/// <summary>截图文件路径</summary>
public string FilePath { get; set; }
/// <summary>图片格式 (PNG/JPG)</summary>
public string Format { get; set; }
/// <summary>图片宽度</summary>
public int Width { get; set; }
/// <summary>图片高度</summary>
public int Height { get; set; }
/// <summary>截图时间</summary>
public DateTime CaptureTime { get; set; } = DateTime.Now;
/// <summary>截图描述/备注</summary>
public string Description { get; set; }
/// <summary>排序顺序</summary>
public int SortOrder { get; set; }
}
/// <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 RouteId { get; set; } // 路径ID用于数据库关联
public int ResultId { get; set; } // 数据库记录Id用于关联截图
public string PathName { get; set; }
public int FrameRate { get; set; }
public double Duration { get; set; }
public double DetectionTolerance { get; set; }
// 截图列表 - 支持多张截图
public List<CollisionReportScreenshot> Screenshots { get; set; } = new List<CollisionReportScreenshot>();
// 排除对象列表
public List<ExcludedObjectRecord> ExcludedObjects { get; set; } = new List<ExcludedObjectRecord>();
}
/// <summary>
/// 生成碰撞报告Command
/// </summary>
public class GenerateCollisionReportCommand : CommandBase
{
private readonly CollisionReportParameters _parameters;
private string _specificTestName = null; // 指定的测试名称(用于历史报告)
private int? _detectionRecordId = null; // 检测记录ID优先使用
public GenerateCollisionReportCommand(CollisionReportParameters parameters = null)
: base("GenerateCollisionReport", "生成碰撞报告", "生成并显示详细的碰撞检测报告")
{
_parameters = parameters ?? new CollisionReportParameters();
}
/// <summary>
/// 设置要生成报告的测试名称
/// </summary>
public void SetTestName(string testName)
{
_specificTestName = testName;
}
/// <summary>
/// 设置检测记录ID优先使用ID查询
/// </summary>
public void SetDetectionRecordId(int? detectionRecordId)
{
_detectionRecordId = detectionRecordId;
}
protected override PathPlanningResult ValidateParameters()
{
return _parameters.ValidateParameters();
}
protected override async Task<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
var startTime = DateTime.UtcNow;
UpdateProgress(10, "开始生成碰撞报告...");
// 获取ClashDetectiveIntegration实例
var clashIntegration = ClashDetectiveIntegration.Instance;
// 优先使用检测记录ID其次使用测试名称
var targetTestName = _specificTestName ?? clashIntegration?.CurrentTestName;
// 获取检测记录信息优先使用ID查询
var testInfo = GetTestInfoFromDatabase();
if (testInfo == null && !string.IsNullOrEmpty(targetTestName))
{
// 如果ID查询失败且提供了测试名称尝试用名称查询
testInfo = GetTestInfoFromDatabaseByName(targetTestName);
}
if (testInfo == null)
{
return PathPlanningResult.Failure("无法找到检测记录,请先运行碰撞检测或选择历史记录");
}
// 使用实际的数据库记录中的TestName
targetTestName = testInfo.TestName ?? targetTestName;
var result = new CollisionReportResult();
try
{
UpdateProgress(30, "收集碰撞数据...");
// 收集所有碰撞数据及统计信息(同步执行,避免 COM 线程问题)
var collisionData = CollectAllCollisionData(targetTestName);
result.AllCollisions = collisionData.AllCollisions;
// 修改统计数据
result.AnimationCollisions = collisionData.AnimationCollisions;
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.DetectionTolerance = animationManager.DetectionTolerance;
}
// 从数据库记录填充路径信息
var db = PathPlanningManager.Instance?.GetPathDatabase();
var route = db?.GetPathRouteSync(testInfo.RouteId);
result.PathName = route?.Name ?? "未知路径";
result.RouteId = testInfo.RouteId;
result.ResultId = testInfo.Id;
LogManager.Info($"碰撞报告数据 - TestName={targetTestName}, ResultId={result.ResultId}, PathName={result.PathName}");
UpdateProgress(70, "生成报告内容...");
// 生成报告内容
result.ReportContent = GenerateReportContent(collisionData, _parameters.Type, _parameters.IncludeDetails);
UpdateProgress(90, "处理报告显示...");
// 如果碰撞结果还没有高亮,则执行高亮(不清除之前的高亮,避免影响其他场景)
if (!_parameters.HasHighlighted && collisionData.AllCollisions.Count > 0)
{
try
{
var highlightIntegration = ClashDetectiveIntegration.Instance;
if (highlightIntegration != null)
{
// 使用 ClashDetectiveResultsCategory深红色不是预计算高亮
ModelHighlightHelper.ManageCollisionHighlightsByCategory(
ModelHighlightHelper.ClashDetectiveResultsCategory,
collisionData.AllCollisions,
clearOtherCategories: false);
LogManager.Info($"碰撞报告生成前高亮 {collisionData.AllCollisions.Count} 个碰撞对象");
}
}
catch (Exception ex)
{
LogManager.Error($"高亮碰撞对象失败: {ex.Message}");
}
}
// 保存原始视角
Viewpoint originalViewpoint = null;
try
{
originalViewpoint = ViewpointHelper.SaveCurrentViewpoint();
LogManager.Info("原始视角已保存");
}
catch (Exception ex)
{
LogManager.Error($"保存原始视角失败: {ex.Message}");
}
// 智能调整视角(确保所有碰撞都在视野内)
try
{
// 从所有路径中查找当前路径
var allRoutes = PathPlanningManager.Instance.GetAllRoutes();
var pathRoute = allRoutes.FirstOrDefault(r => r.Id == _parameters.RouteId);
ViewpointHelper.AdjustViewpointSmart(pathRoute, collisionData.AllCollisions);
LogManager.Info("视角调整完成");
}
catch (Exception ex)
{
LogManager.Error($"自动调整视角失败: {ex.Message}");
// 视角调整失败不影响截图生成
}
UpdateProgress(95, "加载碰撞报告截图...");
// 加载多截图数据(从新的截图表)
bool loadedScreenshotsFromDatabase = false;
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase != null)
{
try
{
// 先获取结果记录以获取 DetectionRecordId
var testRecord = pathDatabase.GetDetectionResultByTestName(targetTestName);
if (testRecord != null)
{
LogManager.Debug($"加载截图 - 找到测试记录: TestName={targetTestName}, DetectionRecordId={testRecord.Id}");
// 从截图表加载所有截图
var screenshotRecords = pathDatabase.GetCollisionReportScreenshots(testRecord.Id);
LogManager.Debug($"加载截图 - 查询到 {screenshotRecords?.Count ?? 0} 张截图 (ResultId={testRecord.Id})");
if (screenshotRecords != null && screenshotRecords.Count > 0)
{
result.Screenshots = screenshotRecords.Select(r => new CollisionReportScreenshot
{
DatabaseId = r.Id, // 数据库主键,用于删除
FilePath = r.FilePath,
Format = r.Format,
Width = r.Width,
Height = r.Height,
CaptureTime = r.CaptureTime,
Description = r.Description,
SortOrder = r.SortOrder
}).ToList();
loadedScreenshotsFromDatabase = true;
LogManager.Info($"从数据库加载了 {result.Screenshots.Count} 张截图 (ResultId={testRecord.Id})");
}
// 加载排除对象列表
try
{
var excludedObjects = pathDatabase.GetExcludedObjectsForDetectionRecord(testRecord.Id);
if (excludedObjects != null && excludedObjects.Count > 0)
{
result.ExcludedObjects = excludedObjects;
LogManager.Info($"从数据库加载了 {excludedObjects.Count} 个排除对象 (ResultId={testRecord.Id})");
}
}
catch (Exception ex)
{
LogManager.Error($"加载排除对象列表失败: {ex.Message}");
}
}
}
catch (Exception ex)
{
LogManager.Error($"加载截图数据失败: {ex.Message}");
}
}
// 检查并创建路径子目录(用于存放该路径的所有截图)
string pathDir = System.IO.Path.Combine(PathHelper.GetScreenshotDirectory(),
string.Format("{0}_{1}", result.RouteId.Substring(0, 8), result.PathName ?? "collision"));
PathHelper.EnsureDirectoryExists(pathDir);
// 先生成默认截图(整条路径的概览)
if (result.Screenshots == null || result.Screenshots.Count == 0)
{
try
{
UpdateProgress(92, "生成默认路径截图...");
// 记录截图前虚拟物体位置和动画管理器状态。
// 不读取 ModelItem.Transform 的“实际朝向”,因为 override 后该值不可靠,容易误导排查。
var virtualObject = VirtualObjectManager.Instance.CurrentVirtualObject;
if (virtualObject != null)
{
var pam = PathAnimationManager.GetInstance();
var position = GetAnimatedObjectReportedPosition(virtualObject, pam);
LogManager.Info(string.Format("[默认截图前] 虚拟物体位置: ({0:F2},{1:F2},{2:F2}), PAM记录朝向: {3:F2}°, customRotation={4}",
position.X, position.Y, position.Z,
pam.CurrentYaw * 180 / Math.PI, pam.HasTrackedRotation));
}
else
{
LogManager.Warning("[默认截图前] 无法获取虚拟物体");
}
// 获取当前视窗尺寸
var generator = new NavigationMapGenerator();
var viewportSize = generator.GetCurrentViewportSize();
int screenshotWidth = viewportSize.Width;
int screenshotHeight = viewportSize.Height;
string screenshotPath = PathHelper.GenerateSceneScreenshotToDirectory(
pathDir,
"path",
screenshotWidth,
screenshotHeight,
System.Drawing.Imaging.ImageFormat.Jpeg,
"overview"
);
// 记录截图后虚拟物体位置和动画管理器状态。
virtualObject = VirtualObjectManager.Instance.CurrentVirtualObject;
if (virtualObject != null)
{
var pam = PathAnimationManager.GetInstance();
var position = GetAnimatedObjectReportedPosition(virtualObject, pam);
LogManager.Info(string.Format("[默认截图后] 虚拟物体位置: ({0:F2},{1:F2},{2:F2}), PAM记录朝向: {3:F2}°, customRotation={4}",
position.X, position.Y, position.Z,
pam.CurrentYaw * 180 / Math.PI, pam.HasTrackedRotation));
}
if (screenshotPath != null)
{
// 保存截图到数据库
int screenshotId = 0;
if (pathDatabase != null && result.ResultId > 0)
{
try
{
screenshotId = pathDatabase.SaveCollisionReportScreenshot(
result.ResultId,
screenshotPath,
"JPG",
screenshotWidth,
screenshotHeight,
0,
"路径概览"
);
LogManager.Info(string.Format("默认截图已保存到数据库: ScreenshotId={0}", screenshotId));
}
catch (Exception dbEx)
{
LogManager.Error(string.Format("保存默认截图到数据库失败: {0}", dbEx.Message));
}
}
result.Screenshots = new List<CollisionReportScreenshot>
{
new CollisionReportScreenshot
{
DatabaseId = screenshotId, // 设置数据库Id
FilePath = screenshotPath,
Format = "JPG",
Width = screenshotWidth,
Height = screenshotHeight,
CaptureTime = DateTime.Now,
Description = "路径概览",
SortOrder = 0
}
};
LogManager.Info(string.Format("生成默认截图: {0}", screenshotPath));
}
}
catch (Exception ex)
{
LogManager.Error(string.Format("生成默认截图失败: {0}", ex.Message));
}
}
// 再生成每个碰撞对的单独截图(需要移动物体到碰撞位置)
// 只有未从数据库加载截图时才生成
if (!loadedScreenshotsFromDatabase && collisionData.AllCollisions?.Count > 0)
{
try
{
UpdateProgress(94, string.Format("正在为 {0} 个碰撞对生成视角截图...", collisionData.AllCollisions.Count));
// 获取当前视窗尺寸
var generator2 = new NavigationMapGenerator();
var viewportSize2 = generator2.GetCurrentViewportSize();
int collisionScreenshotWidth = viewportSize2.Width;
int collisionScreenshotHeight = viewportSize2.Height;
int sortOrder = result.Screenshots?.Count ?? 0;
int successCount = 0;
// 保存动画物体原始状态(用于截图后恢复)
// 对受 PathAnimationManager 控制的对象,直接复用与 ClashDetective 相同的保存/恢复主链路,
// 避免报告生成额外引入第二套状态语义。
ModelItem animatedObject = null;
ModelItemTransformHelper.ObjectStateSnapshot savedObjectState = null;
bool restoreViaAnimationManager = false;
// 找到第一个有位置信息的碰撞来获取动画物体
foreach (var c in collisionData.AllCollisions)
{
if (c.Item1 != null && c.HasPositionInfo)
{
animatedObject = c.Item1;
var pam = PathAnimationManager.GetInstance();
restoreViaAnimationManager = pam != null && pam.ControlsAnimatedObject(animatedObject);
if (restoreViaAnimationManager)
{
pam.SaveObjectState(animatedObject);
LogManager.Info(string.Format("已通过动画主链路保存物体状态: {0}", animatedObject.DisplayName));
}
else
{
savedObjectState = CollisionSceneHelper.SaveAnimatedObjectState(animatedObject);
}
break;
}
}
for (int i = 0; i < collisionData.AllCollisions.Count; i++)
{
var collision = collisionData.AllCollisions[i];
try
{
// 使用通用方法移动物体到碰撞位置并聚焦
CollisionSceneHelper.MoveToCollisionAndFocus(collision, animatedObject);
// 生成截图
string screenshotPath = PathHelper.GenerateSceneScreenshotToDirectory(
pathDir, "collision", collisionScreenshotWidth, collisionScreenshotHeight,
System.Drawing.Imaging.ImageFormat.Jpeg, string.Format("collision_{0:D3}", i + 1));
if (screenshotPath != null)
{
string item1Name = collision.Item1 != null ? collision.Item1.DisplayName : "物体1";
string item2Name = collision.Item2 != null ? collision.Item2.DisplayName : "物体2";
string description = string.Format("碰撞对 {0}/{1}: {2} vs {3}", i + 1, collisionData.AllCollisions.Count, item1Name, item2Name);
// 先保存到数据库获取Id
int screenshotId = 0;
if (pathDatabase != null && result.ResultId > 0)
{
try
{
screenshotId = pathDatabase.SaveCollisionReportScreenshot(result.ResultId, screenshotPath, "JPG",
collisionScreenshotWidth, collisionScreenshotHeight, sortOrder, description);
}
catch (Exception dbEx)
{
LogManager.Error(string.Format("保存碰撞对截图到数据库失败: {0}", dbEx.Message));
}
}
var screenshot = new CollisionReportScreenshot
{
DatabaseId = screenshotId, // 设置数据库Id
FilePath = screenshotPath,
Format = "JPG",
Width = collisionScreenshotWidth,
Height = collisionScreenshotHeight,
CaptureTime = DateTime.Now,
Description = description,
SortOrder = sortOrder++
};
result.Screenshots.Add(screenshot);
successCount++;
}
// 截图后立即处理下一张,无需额外延迟
}
catch (Exception ex)
{
LogManager.Error(string.Format("生成碰撞对 {0} 截图失败: {1}", i + 1, ex.Message));
}
}
// 恢复动画物体到原始状态
if (animatedObject != null)
{
var pam = PathAnimationManager.GetInstance();
if (restoreViaAnimationManager && pam != null && pam.ControlsAnimatedObject(animatedObject))
{
pam.RestoreAnimatedObjectState(animatedObject);
LogManager.Info(string.Format("已通过动画主链路恢复物体状态: {0}", animatedObject.DisplayName));
}
else
{
CollisionSceneHelper.RestoreAnimatedObjectState(animatedObject, savedObjectState);
}
}
LogManager.Info(string.Format("已生成 {0}/{1} 个碰撞对视角截图", successCount, collisionData.AllCollisions.Count));
}
catch (Exception ex)
{
LogManager.Error(string.Format("生成碰撞对视角截图失败: {0}", ex.Message));
}
}
// 恢复原始视角
if (originalViewpoint != null)
{
try
{
ViewpointHelper.RestoreViewpoint(originalViewpoint);
LogManager.Info("原始视角已恢复");
}
catch (Exception ex)
{
LogManager.Error($"恢复原始视角失败: {ex.Message}");
}
}
UpdateProgress(100, "报告生成完成");
// 显示报告UI线程根据参数决定是否弹窗
if (_parameters.ShowDialog)
{
ShowReport(result);
}
// 🔥 自动导出HTML报告
try
{
string htmlPath = Utils.CollisionReportHtmlGenerator.AutoSaveHtmlReport(result);
if (!string.IsNullOrEmpty(htmlPath))
{
LogManager.Info($"HTML报告已自动导出: {htmlPath}");
}
}
catch (Exception ex)
{
LogManager.Error($"自动导出HTML报告失败: {ex.Message}");
// HTML导出失败不影响报告生成
}
}
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);
}
private static Point3D GetAnimatedObjectReportedPosition(ModelItem animatedObject, PathAnimationManager pam)
{
if (animatedObject == null)
{
return new Point3D(0, 0, 0);
}
if (pam != null && pam.ControlsAnimatedObject(animatedObject))
{
return pam.GetObjectCurrentPosition(animatedObject).Position;
}
var bounds = animatedObject.BoundingBox();
return bounds?.Center ?? new Point3D(0, 0, 0);
}
/// <summary>
/// 收集所有碰撞数据及统计信息
/// </summary>
private CollisionDataResult CollectAllCollisionData(string targetTestName = null)
{
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;
// 如果未提供targetTestName尝试从字段获取
if (string.IsNullOrEmpty(targetTestName))
{
targetTestName = _specificTestName ?? clashIntegration?.CurrentTestName;
}
if (string.IsNullOrEmpty(targetTestName))
{
throw new InvalidOperationException("无法确定测试名称,请先运行碰撞检测或选择历史记录");
}
// 获取碰撞结果(先检查缓存,没有则从数据库加载)
var testCollisionsRaw = clashIntegration.GetCurrentPathClashResults(targetTestName);
if (testCollisionsRaw == null || testCollisionsRaw.Count == 0)
{
LogManager.Info($"测试 '{targetTestName}' 没有碰撞结果,返回空数据");
result.AllCollisions = allCollisions;
result.ClashDetectiveCollisionCount = 0;
result.AnimationCollisions = 0;
result.UniqueCollidedObjects = new HashSet<string>();
result.BoundingBoxTestCount = 0;
result.MovingObjectInfo = "无碰撞";
return result;
}
LogManager.Info($"从缓存获取ClashDetective结果: {testCollisionsRaw.Count}个碰撞,测试名称:{targetTestName}");
// 直接使用缓存中的CollisionResult对象已经过复合对象处理和去重
allCollisions.AddRange(testCollisionsRaw);
// 统计碰撞总数
result.ClashDetectiveCollisionCount = testCollisionsRaw.Count;
LogManager.Debug($"测试 '{targetTestName}' 包含 {testCollisionsRaw.Count} 个碰撞");
// 统计被撞物体(缓存中已经是复合对象,不需要再去重)
var nameCountDict = new Dictionary<string, int>();
var uniqueCollidedObjectsList = new List<string>();
foreach (var collision in testCollisionsRaw)
{
if (collision.Item2 != null)
{
// Item2已经是容器对象在创建CollisionResult时已用FindNamedParentContainer处理
var item2Name = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2) ?? "未知对象";
// 为相同名称的对象添加编号区分
if (nameCountDict.ContainsKey(item2Name))
{
nameCountDict[item2Name]++;
uniqueCollidedObjectsList.Add($"{item2Name} #{nameCountDict[item2Name]}");
}
else
{
nameCountDict[item2Name] = 1;
uniqueCollidedObjectsList.Add(item2Name);
}
}
}
result.AllCollisions = allCollisions;
result.UniqueCollidedObjects = new HashSet<string>(uniqueCollidedObjectsList);
result.BoundingBoxTestCount = 0;
// 动画碰撞数量已弃用始终设为0预计算碰撞数现在作为参数传递
result.AnimationCollisions = 0;
// 从碰撞结果的Item1中获取运动构件信息
if (allCollisions.Count > 0 && allCollisions[0].Item1 != null)
{
// Item1已经是容器对象在创建CollisionResult时已用FindNamedParentContainer处理
var movingItemName = ModelItemAnalysisHelper.GetSafeDisplayName(allCollisions[0].Item1) ?? "未知运动构件";
result.MovingObjectInfo = $"运动构件: {movingItemName}";
}
else
{
result.MovingObjectInfo = "运动构件: 未知";
}
LogManager.Info($"收集完成 - Clash Detective权威结果: {result.ClashDetectiveCollisionCount}个, 碰撞对象: {allCollisions.Count}个, 被撞构件: {uniqueCollidedObjectsList.Count}个");
return result;
}
catch (Exception ex)
{
LogManager.Error($"收集碰撞数据失败: {ex.Message}");
result.AllCollisions = allCollisions;
return result;
}
}
/// <summary>
/// 从数据库获取路径名称同步方法避免在主线程中使用await造成死锁
/// </summary>
private string GetPathNameFromDatabase(string testName)
{
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase == null)
{
LogManager.Warning("无法获取PathDatabase实例");
return null;
}
try
{
var testInfo = pathDatabase.GetDetectionResultByTestName(testName);
if (testInfo != null)
{
// 通过 RouteId 获取路径名称
var route = pathDatabase.GetPathRouteSync(testInfo.RouteId);
return route?.Name;
}
return null;
}
catch (Exception ex)
{
LogManager.Error($"从数据库获取路径名称失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 从数据库获取测试信息优先使用检测记录ID
/// </summary>
private CollisionDetectionRecord GetTestInfoFromDatabase()
{
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase == null)
{
LogManager.Warning("无法获取PathDatabase实例");
return null;
}
// 优先使用检测记录ID查询
if (_detectionRecordId.HasValue)
{
try
{
var record = pathDatabase.GetDetectionResultById(_detectionRecordId.Value);
if (record != null)
{
LogManager.Info($"使用检测记录ID查询成功: Id={_detectionRecordId}");
return record;
}
}
catch (Exception ex)
{
LogManager.Error($"使用ID查询检测记录失败: {ex.Message}");
}
}
return null;
}
/// <summary>
/// 从数据库获取测试信息(按测试名称查询,作为后备)
/// </summary>
private CollisionDetectionRecord GetTestInfoFromDatabaseByName(string testName)
{
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase == null)
{
LogManager.Warning("无法获取PathDatabase实例");
return null;
}
if (string.IsNullOrEmpty(testName))
{
return null;
}
try
{
return pathDatabase.GetDetectionResultByTestName(testName);
}
catch (Exception ex)
{
LogManager.Error($"从数据库获取测试信息失败: {ex.Message}");
return null;
}
}
/// <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("=== 总体统计 ===");
report.AppendLine($"总碰撞数: {collisionData.ClashDetectiveCollisionCount}个");
report.AppendLine($"碰撞构件: {collisionData.UniqueCollidedObjects.Count}个");
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>
/// <param name="routeId">路径ID用于视角调整如果为空则使用全局CurrentRoute</param>
/// <param name="hasHighlighted">碰撞结果是否已经被高亮</param>
/// <param name="showDialog">是否显示报告窗口批处理模式设为false</param>
public static GenerateCollisionReportCommand CreateComprehensive(string routeId = null, bool hasHighlighted = false, bool showDialog = true)
{
// 如果没有传入路径ID则从全局CurrentRoute获取
if (string.IsNullOrEmpty(routeId))
{
try
{
var currentRoute = PathPlanningManager.Instance?.CurrentRoute;
if (currentRoute != null)
{
routeId = currentRoute.Id;
}
}
catch (Exception ex)
{
LogManager.Error($"获取当前路径ID失败: {ex.Message}");
}
}
return new GenerateCollisionReportCommand(new CollisionReportParameters
{
Type = CollisionReportParameters.ReportType.Comprehensive,
IncludeDetails = true,
HasHighlighted = hasHighlighted,
RouteId = routeId,
ShowDialog = showDialog
});
}
/// <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;
}
}
}
}