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(); }