完善碰撞报告高亮和截图保存机制

This commit is contained in:
tian 2026-01-26 15:10:03 +08:00
parent d5d50d7568
commit 1e1d740aef
7 changed files with 178 additions and 69 deletions

View File

@ -2,6 +2,10 @@
## 功能点
### [2026/1/26]
1. [ ] 优化完善碰撞报告截图保存机制自动导出html报告。
### [2026/1/18]
1. [x] 功能实现吊装路径支持3步吊运过程

View File

@ -40,9 +40,9 @@ namespace NavisworksTransport.Commands
public bool IncludeDetails { get; set; } = true;
/// <summary>
/// 是否自动高亮报告中的碰撞对象
/// 碰撞结果是否已经被高亮过了(用于避免重复高亮)
/// </summary>
public bool AutoHighlight { get; set; } = false;
public bool HasHighlighted { get; set; } = false;
public PathPlanningResult ValidateParameters()
{
@ -63,6 +63,9 @@ namespace NavisworksTransport.Commands
// 新增:被撞物体去重统计
public HashSet<string> UniqueCollidedObjects { get; set; } = new HashSet<string>();
// 新增:截图路径
public string ScreenshotPath { get; set; }
}
/// <summary>
@ -164,10 +167,11 @@ namespace NavisworksTransport.Commands
result.FrameRate = animationManager.AnimationFrameRate;
result.Duration = animationManager.AnimationDuration;
result.DetectionTolerance = animationManager.DetectionTolerance;
// PathName需要从动画管理器获取
result.PathName = animationManager.PathName ?? "未知路径";
}
// 从数据库获取路径名称(不依赖动画管理器的状态,使用同步方法避免死锁)
result.PathName = GetPathNameFromDatabase(targetTestName) ?? "未知路径";
LogManager.Info($"碰撞报告计数 - 检查点: {result.AnimationCollisions}, Clash Detective: {result.TotalCollisions}");
UpdateProgress(70, "生成报告内容...");
@ -177,50 +181,88 @@ namespace NavisworksTransport.Commands
UpdateProgress(90, "处理报告显示...");
// 自动高亮(如果启用
if (_parameters.AutoHighlight && collisionData.AllCollisions.Count > 0)
// 如果碰撞结果还没有高亮,则执行高亮(不清除之前的高亮,避免影响其他场景
if (!_parameters.HasHighlighted && collisionData.AllCollisions.Count > 0)
{
try
{
var highlightIntegration = ClashDetectiveIntegration.Instance;
if (highlightIntegration != null)
{
ModelHighlightHelper.HighlightCollisionResults(collisionData.AllCollisions, clearPrevious: true);
LogManager.Info($"自动高亮显示报告中的 {collisionData.AllCollisions.Count} 个碰撞对象");
ModelHighlightHelper.HighlightCollisionResults(collisionData.AllCollisions, clearPrevious: false);
LogManager.Info($"碰撞报告生成前高亮 {collisionData.AllCollisions.Count} 个碰撞对象");
}
}
catch (Exception ex)
{
LogManager.Error($"自动高亮报告对象失败: {ex.Message}");
LogManager.Error($"高亮碰撞对象失败: {ex.Message}");
}
}
UpdateProgress(95, "生成碰撞场景截图...");
// 自动生成默认截图1920x1080, JPG格式
try
{
string screenshotPath = PathHelper.GenerateSceneScreenshot(
result.PathName ?? "collision",
1920,
1080,
System.Drawing.Imaging.ImageFormat.Jpeg,
"collision"
);
// 检查是否已有截图路径(从数据库)
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
var existingScreenshotPath = pathDatabase != null ?
pathDatabase.GetClashDetectiveResultByTestName(targetTestName)?.ScreenshotPath : null;
if (screenshotPath != null)
{
result.ScreenshotPath = screenshotPath;
result.ScreenshotFormat = "JPG";
result.ScreenshotWidth = 1920;
result.ScreenshotHeight = 1080;
LogManager.Info($"碰撞场景截图已自动生成: {screenshotPath}");
}
}
catch (Exception ex)
if (!string.IsNullOrEmpty(existingScreenshotPath) && System.IO.File.Exists(existingScreenshotPath))
{
LogManager.Error($"自动生成碰撞场景截图失败: {ex.Message}");
// 截图失败不影响报告生成
// 使用数据库中保存的截图路径
result.ScreenshotPath = existingScreenshotPath;
result.ScreenshotFormat = "JPG";
result.ScreenshotWidth = 1920;
result.ScreenshotHeight = 1080;
LogManager.Info($"使用数据库中保存的截图: {existingScreenshotPath}");
}
else
{
// 生成新截图
try
{
string screenshotPath = PathHelper.GenerateSceneScreenshot(
result.PathName ?? "collision",
1920,
1080,
System.Drawing.Imaging.ImageFormat.Jpeg,
"collision"
);
if (screenshotPath != null)
{
result.ScreenshotPath = screenshotPath;
result.ScreenshotFormat = "JPG";
result.ScreenshotWidth = 1920;
result.ScreenshotHeight = 1080;
LogManager.Info($"碰撞场景截图已自动生成: {screenshotPath}");
// 保存截图路径到数据库
if (pathDatabase != null)
{
try
{
var updateSql = "UPDATE ClashDetectiveResults SET ScreenshotPath = @screenshotPath WHERE TestName = @testName";
using (var cmd = new System.Data.SQLite.SQLiteCommand(updateSql, pathDatabase._connection))
{
cmd.Parameters.AddWithValue("@screenshotPath", screenshotPath);
cmd.Parameters.AddWithValue("@testName", targetTestName);
cmd.ExecuteNonQuery();
}
LogManager.Info($"已保存截图路径到数据库: {screenshotPath}");
}
catch (Exception dbEx)
{
LogManager.Error($"保存截图路径到数据库失败: {dbEx.Message}");
// 数据库更新失败不影响报告生成
}
}
}
}
catch (Exception ex)
{
LogManager.Error($"自动生成碰撞场景截图失败: {ex.Message}");
// 截图失败不影响报告生成
}
}
UpdateProgress(100, "报告生成完成");
@ -299,6 +341,17 @@ namespace NavisworksTransport.Commands
// 直接使用缓存中的CollisionResult对象已经过复合对象处理和去重
allCollisions.AddRange(testCollisionsRaw);
// 从数据库获取测试信息(包括截图路径)
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase != null)
{
var testInfo = pathDatabase.GetClashDetectiveResultByTestName(targetTestName);
if (testInfo != null && !string.IsNullOrEmpty(testInfo.ScreenshotPath))
{
result.ScreenshotPath = testInfo.ScreenshotPath;
}
}
// 统计碰撞总数
result.ClashDetectiveCollisionCount = testCollisionsRaw.Count;
LogManager.Debug($"测试 '{targetTestName}' 包含 {testCollisionsRaw.Count} 个碰撞");
@ -359,6 +412,30 @@ namespace NavisworksTransport.Commands
}
}
/// <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.GetClashDetectiveResultByTestName(testName);
return testInfo?.PathName;
}
catch (Exception ex)
{
LogManager.Error($"从数据库获取路径名称失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 生成报告内容
/// </summary>
@ -573,13 +650,13 @@ namespace NavisworksTransport.Commands
/// <summary>
/// 创建综合报告命令
/// </summary>
public static GenerateCollisionReportCommand CreateComprehensive(bool autoHighlight = false)
public static GenerateCollisionReportCommand CreateComprehensive(bool hasHighlighted = false)
{
return new GenerateCollisionReportCommand(new CollisionReportParameters
{
Type = CollisionReportParameters.ReportType.Comprehensive,
IncludeDetails = true,
AutoHighlight = autoHighlight
HasHighlighted = hasHighlighted
});
}

View File

@ -298,30 +298,13 @@ namespace NavisworksTransport.Core
// 保存测试名称
item.ClashDetectiveTestName = testName;
// 🔥 高亮碰撞结果(用于批处理中的截图和报告生成)
if (!string.IsNullOrEmpty(testName))
{
try
{
var clashIntegration = ClashDetectiveIntegration.Instance;
var clashResults = clashIntegration.GetCurrentPathClashResults(testName);
if (clashResults != null && clashResults.Count > 0)
{
ModelHighlightHelper.HighlightClashDetectiveResults(testName, clashIntegration.GetCurrentPathClashResults);
}
}
catch (Exception highlightEx)
{
LogManager.Error($"[批处理队列] 高亮碰撞结果失败: {highlightEx.Message}");
}
}
// 🔥 生成碰撞报告(包括截图)
// 批处理检测结束时会自动高亮,所以 hasHighlighted=true
if (!string.IsNullOrEmpty(testName))
{
try
{
var reportCommand = new Commands.GenerateCollisionReportCommand();
var reportCommand = Commands.GenerateCollisionReportCommand.CreateComprehensive(hasHighlighted: false);
reportCommand.SetTestName(testName);
// 同步执行报告生成在UI线程中
@ -335,16 +318,6 @@ namespace NavisworksTransport.Core
}
}
// 🔥 清除高亮(报告生成后)
try
{
ModelHighlightHelper.ClearClashDetectiveHighlights();
}
catch (Exception clearEx)
{
LogManager.Error($"[批处理队列] 清除高亮失败: {clearEx.Message}");
}
return testName;
});

View File

@ -259,7 +259,7 @@ namespace NavisworksTransport
// 1. 从数据库读取测试信息
var testInfoSql = @"
SELECT Id, PathName, RouteId, IsVirtualVehicle, VehicleModelIndex, VehiclePathId,
VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight
VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight, ScreenshotPath
FROM ClashDetectiveResults
WHERE TestName = @testName
";
@ -282,7 +282,8 @@ namespace NavisworksTransport
VehiclePathId = reader["VehiclePathId"] != DBNull.Value ? reader["VehiclePathId"].ToString() : null,
VirtualVehicleLength = reader["VirtualVehicleLength"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleLength"]) : 0.0,
VirtualVehicleWidth = reader["VirtualVehicleWidth"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleWidth"]) : 0.0,
VirtualVehicleHeight = reader["VirtualVehicleHeight"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleHeight"]) : 0.0
VirtualVehicleHeight = reader["VirtualVehicleHeight"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleHeight"]) : 0.0,
ScreenshotPath = reader["ScreenshotPath"] != DBNull.Value ? reader["ScreenshotPath"].ToString() : null
};
}
}
@ -508,7 +509,7 @@ namespace NavisworksTransport
LogManager.Info($"[ClashDetective] 有效碰撞数量: {validCollisions.Count}");
// 第一步:创建主测试(不包含选择集,纯容器)
var mainTestName = $"碰撞检测_{DateTime.Now:MMdd_HHmmss}";
var mainTestName = $"碰撞检测_{DateTime.Now:MMdd_HHmmssfff}";
_currentTestName = mainTestName;
LogManager.Info($"[分组测试] 创建主测试: {mainTestName}");

View File

@ -186,6 +186,7 @@ namespace NavisworksTransport
VirtualVehicleLength REAL,
VirtualVehicleWidth REAL,
VirtualVehicleHeight REAL,
ScreenshotPath TEXT,
CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
@ -457,10 +458,10 @@ namespace NavisworksTransport
INSERT INTO ClashDetectiveResults
(TestName, PathName, RouteId, TestTime, CollisionCount, AnimationCollisionCount,
FrameRate, Duration, DetectionGap, AnimatedObjectName, IsVirtualVehicle, VehicleModelIndex, VehiclePathId,
VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight, CreatedAt)
VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight, ScreenshotPath, CreatedAt)
VALUES (@testName, @pathName, @routeId, @testTime, @collisionCount, @animationCollisionCount,
@frameRate, @duration, @detectionGap, @animatedObjectName, @isVirtualVehicle, @vehicleModelIndex, @vehiclePathId,
@virtualVehicleLength, @virtualVehicleWidth, @virtualVehicleHeight, @createdAt)
@virtualVehicleLength, @virtualVehicleWidth, @virtualVehicleHeight, @screenshotPath, @createdAt)
";
long newId = 0;
@ -482,6 +483,7 @@ namespace NavisworksTransport
cmd.Parameters.AddWithValue("@virtualVehicleLength", record.VirtualVehicleLength);
cmd.Parameters.AddWithValue("@virtualVehicleWidth", record.VirtualVehicleWidth);
cmd.Parameters.AddWithValue("@virtualVehicleHeight", record.VirtualVehicleHeight);
cmd.Parameters.AddWithValue("@screenshotPath", record.ScreenshotPath ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@createdAt", record.CreatedAt);
cmd.ExecuteNonQuery();
newId = _connection.LastInsertRowId;
@ -618,6 +620,55 @@ namespace NavisworksTransport
}
}
/// <summary>
/// 根据测试名称获取ClashDetective结果记录
/// </summary>
public ClashDetectiveResultRecord GetClashDetectiveResultByTestName(string testName)
{
try
{
var sql = @"
SELECT Id, TestName, PathName, RouteId, TestTime, CollisionCount, AnimationCollisionCount,
FrameRate, Duration, DetectionGap, AnimatedObjectName, CreatedAt, ScreenshotPath
FROM ClashDetectiveResults
WHERE TestName = @testName
";
using (var cmd = new SQLiteCommand(sql, _connection))
{
cmd.Parameters.AddWithValue("@testName", testName);
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
return new ClashDetectiveResultRecord
{
Id = Convert.ToInt32(reader["Id"]),
TestName = reader["TestName"].ToString(),
PathName = reader["PathName"].ToString(),
RouteId = reader["RouteId"]?.ToString(),
TestTime = Convert.ToDateTime(reader["TestTime"]),
CollisionCount = Convert.ToInt32(reader["CollisionCount"]),
AnimationCollisionCount = Convert.ToInt32(reader["AnimationCollisionCount"]),
FrameRate = reader["FrameRate"] != DBNull.Value ? Convert.ToInt32(reader["FrameRate"]) : 0,
Duration = reader["Duration"] != DBNull.Value ? Convert.ToDouble(reader["Duration"]) : 0.0,
DetectionGap = reader["DetectionGap"] != DBNull.Value ? Convert.ToDouble(reader["DetectionGap"]) : 0.0,
AnimatedObjectName = reader["AnimatedObjectName"]?.ToString(),
CreatedAt = Convert.ToDateTime(reader["CreatedAt"]),
ScreenshotPath = reader["ScreenshotPath"] != DBNull.Value ? reader["ScreenshotPath"].ToString() : null
};
}
}
}
}
catch (Exception ex)
{
LogManager.Error($"根据测试名称获取ClashDetective结果失败: {ex.Message}", ex);
}
return null;
}
/// <summary>
/// 保存ClashDetective碰撞对象
/// </summary>
@ -1862,6 +1913,7 @@ namespace NavisworksTransport
public double VirtualVehicleLength { get; set; }
public double VirtualVehicleWidth { get; set; }
public double VirtualVehicleHeight { get; set; }
public string ScreenshotPath { get; set; }
public DateTime CreatedAt { get; set; }
}

View File

@ -165,7 +165,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info($"[历史报告] 开始生成历史碰撞报告: {testName}");
// 直接使用现有的报告生成命令,但指定测试名称
var command = GenerateCollisionReportCommand.CreateComprehensive(autoHighlight: false);
// 常规检测已经高亮了,所以 hasHighlighted=true
var command = GenerateCollisionReportCommand.CreateComprehensive(hasHighlighted: true);
command.SetTestName(testName);
_ = command.ExecuteAsync();
}

View File

@ -329,7 +329,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info($"[批处理队列] 开始生成历史碰撞报告: {testName}");
// 复用碰撞历史列表中的报告按钮代码
var command = GenerateCollisionReportCommand.CreateComprehensive(autoHighlight: false);
// 历史报告需要重新高亮,所以 hasHighlighted=false
var command = GenerateCollisionReportCommand.CreateComprehensive(hasHighlighted: false);
command.SetTestName(testName);
await command.ExecuteAsync();
}