diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md
index 292e28e..69de1ef 100644
--- a/doc/requirement/todo_features.md
+++ b/doc/requirement/todo_features.md
@@ -2,6 +2,10 @@
## 功能点
+### [2026/1/26]
+
+1. [ ] (优化)完善碰撞报告截图保存机制,自动导出html报告。
+
### [2026/1/18]
1. [x] (功能)实现吊装路径,支持3步吊运过程
diff --git a/src/Commands/GenerateCollisionReportCommand.cs b/src/Commands/GenerateCollisionReportCommand.cs
index 0035ab3..ad17679 100644
--- a/src/Commands/GenerateCollisionReportCommand.cs
+++ b/src/Commands/GenerateCollisionReportCommand.cs
@@ -40,9 +40,9 @@ namespace NavisworksTransport.Commands
public bool IncludeDetails { get; set; } = true;
///
- /// 是否自动高亮报告中的碰撞对象
+ /// 碰撞结果是否已经被高亮过了(用于避免重复高亮)
///
- public bool AutoHighlight { get; set; } = false;
+ public bool HasHighlighted { get; set; } = false;
public PathPlanningResult ValidateParameters()
{
@@ -63,6 +63,9 @@ namespace NavisworksTransport.Commands
// 新增:被撞物体去重统计
public HashSet UniqueCollidedObjects { get; set; } = new HashSet();
+
+ // 新增:截图路径
+ public string ScreenshotPath { get; set; }
}
///
@@ -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
}
}
+ ///
+ /// 从数据库获取路径名称(同步方法,避免在主线程中使用await造成死锁)
+ ///
+ 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;
+ }
+ }
+
///
/// 生成报告内容
///
@@ -573,13 +650,13 @@ namespace NavisworksTransport.Commands
///
/// 创建综合报告命令
///
- 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
});
}
diff --git a/src/Core/BatchQueueManager.cs b/src/Core/BatchQueueManager.cs
index 026d54e..ec27bff 100644
--- a/src/Core/BatchQueueManager.cs
+++ b/src/Core/BatchQueueManager.cs
@@ -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;
});
diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs
index 68a38bd..6db70d5 100644
--- a/src/Core/Collision/ClashDetectiveIntegration.cs
+++ b/src/Core/Collision/ClashDetectiveIntegration.cs
@@ -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}");
diff --git a/src/Core/PathDatabase.cs b/src/Core/PathDatabase.cs
index 1ecc2ed..d259f2e 100644
--- a/src/Core/PathDatabase.cs
+++ b/src/Core/PathDatabase.cs
@@ -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
}
}
+ ///
+ /// 根据测试名称获取ClashDetective结果记录
+ ///
+ 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;
+ }
+
///
/// 保存ClashDetective碰撞对象
///
@@ -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; }
}
diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
index 6a352fa..b1a984b 100644
--- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
+++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
@@ -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();
}
diff --git a/src/UI/WPF/ViewModels/BatchTaskManagementViewModel.cs b/src/UI/WPF/ViewModels/BatchTaskManagementViewModel.cs
index fed7a75..96f18f8 100644
--- a/src/UI/WPF/ViewModels/BatchTaskManagementViewModel.cs
+++ b/src/UI/WPF/ViewModels/BatchTaskManagementViewModel.cs
@@ -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();
}