增加碰撞报告HTML生成器,支持自动导出HTML报告功能

This commit is contained in:
tian 2026-01-26 16:12:47 +08:00
parent 1e1d740aef
commit d0ade35042
4 changed files with 320 additions and 0 deletions

View File

@ -175,6 +175,7 @@
<Compile Include="src\Core\FloorAttributeManager.cs" />
<!-- Utilities -->
<Compile Include="src\Utils\ModelHighlightHelper.cs" />
<Compile Include="src\Utils\CollisionReportHtmlGenerator.cs" />
<!-- PathPlanning - Auto Path Planning -->
<Compile Include="src\PathPlanning\GridMap.cs" />
<Compile Include="src\PathPlanning\GridMapGenerator.cs" />

View File

@ -269,6 +269,21 @@ namespace NavisworksTransport.Commands
// 显示报告UI线程
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)
{

View File

@ -0,0 +1,296 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using NavisworksTransport.Commands;
namespace NavisworksTransport.Utils
{
/// <summary>
/// 碰撞报告HTML生成器
/// </summary>
public static class CollisionReportHtmlGenerator
{
/// <summary>
/// 生成HTML报告内容
/// </summary>
/// <param name="report">碰撞报告结果</param>
/// <param name="htmlFilePath">HTML文件路径用于计算截图相对路径</param>
/// <returns>HTML内容</returns>
public static string GenerateHtmlReport(CollisionReportResult report, string htmlFilePath)
{
if (report == null)
{
throw new ArgumentNullException(nameof(report));
}
var html = new StringBuilder();
var now = DateTime.Now;
// HTML头部和样式
html.AppendLine("<!DOCTYPE html>");
html.AppendLine("<html><head>");
html.AppendLine("<meta charset='UTF-8'>");
html.AppendLine("<title>NavisworksTransport 碰撞检测报告</title>");
html.AppendLine("<style>");
html.AppendLine("body { font-family: 'Microsoft YaHei', Arial, sans-serif; margin: 20px; line-height: 1.6; }");
html.AppendLine("h1 { color: #2c5aa0; text-align: center; border-bottom: 2px solid #2c5aa0; padding-bottom: 10px; }");
html.AppendLine("h2 { color: #2c5aa0; margin-top: 25px; }");
html.AppendLine(".summary { background: #f8fbff; padding: 15px; border-radius: 8px; margin: 15px 0; border-left: 4px solid #2c5aa0; }");
html.AppendLine(".stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin: 15px 0; }");
html.AppendLine(".stat-item { background: white; padding: 15px; border-radius: 6px; border: 1px solid #ddd; text-align: center; }");
html.AppendLine(".stat-label { font-size: 12px; color: #666; margin-bottom: 5px; }");
html.AppendLine(".stat-value { font-size: 18px; font-weight: bold; color: #2c5aa0; }");
html.AppendLine(".moving-object { background: #f0f8ff; padding: 15px; border-radius: 8px; text-align: center; margin: 15px 0; border: 1px solid #b0d4f1; }");
html.AppendLine(".collided-list { background: #f8fbff; padding: 15px; border-radius: 8px; margin: 15px 0; max-height: 200px; overflow-y: auto; }");
html.AppendLine(".list-item { margin: 5px 0; padding: 5px; border-left: 3px solid #2c5aa0; padding-left: 10px; }");
html.AppendLine(".recommendation { background: #fff3cd; padding: 15px; border-radius: 8px; border-left: 4px solid #ffc107; margin: 15px 0; }");
html.AppendLine(".collision-item { background: white; margin: 10px 0; padding: 15px; border-radius: 6px; border: 1px solid #eee; }");
html.AppendLine(".collision-title { font-weight: bold; color: #333; margin-bottom: 8px; }");
html.AppendLine(".collision-status { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 11px; color: white; margin-bottom: 8px; }");
html.AppendLine(".status-new { background-color: #ff6b6b; } .status-active { background-color: #ffa500; }");
html.AppendLine(".status-reviewed { background-color: #87ceeb; } .status-approved { background-color: #98fb98; } .status-resolved { background-color: #90ee90; }");
html.AppendLine(".screenshot-section { margin: 20px 0; padding: 15px; background-color: #f5f5f5; border-radius: 5px; }");
html.AppendLine(".screenshot-container { text-align: center; }");
html.AppendLine(".screenshot-image { max-width: 100%; height: auto; border: 1px solid #ddd; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }");
html.AppendLine(".screenshot-info { margin-top: 10px; font-size: 12px; color: #666; }");
html.AppendLine("</style>");
html.AppendLine("</head><body>");
// 报告标题
html.AppendLine($"<h1>NavisworksTransport 碰撞检测报告</h1>");
html.AppendLine($"<p style='text-align: center; color: #666;'>生成时间: {now:yyyy年MM月dd日 HH:mm:ss}</p>");
html.AppendLine($"<p style='text-align: center; color: #666;'>报告类型: 综合报告</p>");
html.AppendLine($"<p style='text-align: center; color: #666;'>路径名称: {report.PathName ?? ""}</p>");
// 总体统计
html.AppendLine("<div class='summary'>");
html.AppendLine("<h2>总体统计</h2>");
html.AppendLine("<div class='stats-grid'>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>总碰撞数</div>");
html.AppendLine($"<div class='stat-value'>{report.TotalCollisions}</div>");
html.AppendLine("</div>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>碰撞构件</div>");
html.AppendLine($"<div class='stat-value'>{report.UniqueCollidedObjectsCount}个</div>");
html.AppendLine("</div>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>检测点</div>");
html.AppendLine($"<div class='stat-value'>{report.AnimationCollisions}个</div>");
html.AppendLine("</div>");
html.AppendLine("</div>");
html.AppendLine("</div>");
// 动画参数
html.AppendLine("<h2>动画参数</h2>");
html.AppendLine("<div class='stats-grid'>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>帧率</div>");
html.AppendLine($"<div class='stat-value'>{report.FrameRate} FPS</div>");
html.AppendLine("</div>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>时长</div>");
html.AppendLine($"<div class='stat-value'>{report.Duration:F1} 秒</div>");
html.AppendLine("</div>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>检测容差</div>");
html.AppendLine($"<div class='stat-value'>{report.DetectionTolerance:F2} 米</div>");
html.AppendLine("</div>");
html.AppendLine("</div>");
// 碰撞场景截图
if (!string.IsNullOrEmpty(report.ScreenshotPath) && File.Exists(report.ScreenshotPath))
{
html.AppendLine("<h2>碰撞场景截图</h2>");
html.AppendLine("<div class='screenshot-section'>");
// 使用 PathHelper 计算相对路径
string relativePath = PathHelper.GetRelativePath(htmlFilePath, report.ScreenshotPath);
html.AppendLine("<div class='screenshot-container'>");
html.AppendLine($"<img src=\"{relativePath}\" alt=\"碰撞场景截图\" class=\"screenshot-image\"/>");
html.AppendLine("<div class='screenshot-info'>");
html.AppendLine($"<p>分辨率: {report.ScreenshotWidth} x {report.ScreenshotHeight}</p>");
html.AppendLine($"<p>格式: {report.ScreenshotFormat}</p>");
html.AppendLine("</div>");
html.AppendLine("</div>");
html.AppendLine("</div>");
}
// 运动构件信息
if (!string.IsNullOrEmpty(report.MovingObjectInfo))
{
html.AppendLine("<h2>运动构件信息</h2>");
html.AppendLine("<div class='moving-object'>");
html.AppendLine($"<strong>🚛 {report.MovingObjectInfo}</strong>");
html.AppendLine("</div>");
}
// 碰撞构件清单
if (report.CollidedObjectsList?.Count > 0)
{
html.AppendLine("<h2>碰撞构件清单</h2>");
html.AppendLine("<div class='collided-list'>");
int index = 1;
foreach (var objName in report.CollidedObjectsList.Take(50)) // 限制显示前50个
{
html.AppendLine($"<div class='list-item'>{index}. {objName}</div>");
index++;
}
if (report.CollidedObjectsList.Count > 50)
{
html.AppendLine($"<div class='list-item'><em>... 还有 {report.CollidedObjectsList.Count - 50} 个构件</em></div>");
}
html.AppendLine("</div>");
}
// 总结与建议
html.AppendLine("<h2>总结与建议</h2>");
html.AppendLine("<div class='recommendation'>");
// 根据碰撞数量生成总结信息
string summaryMessage;
if (report.TotalCollisions == 0)
{
summaryMessage = "✅ 未检测到碰撞,路径规划合理,可以安全通行。";
}
else if (report.TotalCollisions <= 5)
{
summaryMessage = $"⚠️ 检测到 {report.TotalCollisions} 个碰撞,建议优化路径或调整构件位置。";
}
else
{
summaryMessage = $"🔴 检测到 {report.TotalCollisions} 个碰撞,存在严重安全隐患,必须立即处理。";
}
html.AppendLine($"<p><strong>{summaryMessage}</strong></p>");
// 生成建议措施
html.AppendLine("<p><strong>建议措施:</strong></p>");
html.AppendLine("<ul>");
if (report.TotalCollisions == 0)
{
html.AppendLine("<li>保持当前路径规划方案</li>");
html.AppendLine("<li>定期检查构件位置变化</li>");
html.AppendLine("<li>建议进行现场复核确认</li>");
}
else
{
html.AppendLine("<li>检查碰撞构件的实际位置和尺寸</li>");
html.AppendLine("<li>优化路径规划,避开碰撞区域</li>");
html.AppendLine("<li>调整构件位置或尺寸</li>");
html.AppendLine("<li>考虑增加安全间隙</li>");
html.AppendLine("<li>进行现场勘察确认</li>");
}
html.AppendLine("</ul>");
html.AppendLine("</div>");
// 详细碰撞信息前10个
if (report.AllCollisions?.Count > 0)
{
html.AppendLine("<h2>碰撞详情</h2>");
int collisionIndex = 1;
foreach (var collision in report.AllCollisions.Take(10))
{
html.AppendLine("<div class='collision-item'>");
// 生成碰撞标题
string item1Name = collision.Item1 != null ? ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item1) : "未知对象";
string item2Name = collision.Item2 != null ? ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2) : "未知对象";
html.AppendLine($"<div class='collision-title'>{collisionIndex}. {item1Name} ↔ {item2Name}</div>");
// 碰撞距离
html.AppendLine($"<p><strong>碰撞距离:</strong> {collision.Distance:F3} 米</p>");
// 碰撞位置
if (collision.Center != null)
{
html.AppendLine($"<p><strong>碰撞位置:</strong> ({collision.Center.X:F2}, {collision.Center.Y:F2}, {collision.Center.Z:F2})</p>");
}
html.AppendLine("</div>");
collisionIndex++;
}
if (report.AllCollisions.Count > 10)
{
html.AppendLine($"<p><em>... 还有 {report.AllCollisions.Count - 10} 个碰撞未显示</em></p>");
}
}
// HTML结尾
html.AppendLine($"<hr style='margin: 30px 0;'>");
html.AppendLine($"<p style='text-align: center; color: #999; font-size: 12px;'>报告生成完成 - {now:HH:mm:ss}</p>");
html.AppendLine("</body></html>");
return html.ToString();
}
/// <summary>
/// 保存HTML报告到文件
/// </summary>
/// <param name="report">碰撞报告结果</param>
/// <param name="htmlFilePath">HTML文件保存路径</param>
/// <returns>是否保存成功</returns>
public static bool SaveHtmlReport(CollisionReportResult report, string htmlFilePath)
{
try
{
// 确保目录存在
string directory = Path.GetDirectoryName(htmlFilePath);
if (!string.IsNullOrEmpty(directory))
{
PathHelper.EnsureDirectoryExists(directory);
}
// 生成HTML内容
string htmlContent = GenerateHtmlReport(report, htmlFilePath);
// 保存到文件
File.WriteAllText(htmlFilePath, htmlContent, System.Text.Encoding.UTF8);
LogManager.Info($"HTML报告已保存: {htmlFilePath}");
return true;
}
catch (Exception ex)
{
LogManager.Error($"保存HTML报告失败: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 自动生成并保存HTML报告到默认目录
/// </summary>
/// <param name="report">碰撞报告结果</param>
/// <returns>HTML文件路径失败返回null</returns>
public static string AutoSaveHtmlReport(CollisionReportResult report)
{
try
{
// 生成文件名
string sanitizedName = PathHelper.SanitizeFileName(report.PathName ?? "未知路径");
string fileName = PathHelper.GenerateTimestampedFileName($"collision_{sanitizedName}", "html");
// 获取报告目录
string reportDir = PathHelper.GetReportDirectory();
string htmlFilePath = Path.Combine(reportDir, fileName);
// 保存报告
if (SaveHtmlReport(report, htmlFilePath))
{
return htmlFilePath;
}
return null;
}
catch (Exception ex)
{
LogManager.Error($"自动保存HTML报告失败: {ex.Message}", ex);
return null;
}
}
}
}

View File

@ -33,6 +33,14 @@ namespace NavisworksTransport.Utils
return Path.Combine(GetPluginDirectory(), "screenshots");
}
/// <summary>
/// 获取报告目录路径
/// </summary>
public static string GetReportDirectory()
{
return Path.Combine(GetPluginDirectory(), "reports");
}
/// <summary>
/// 确保目录存在,不存在则创建
/// </summary>