using System; using System.Collections.Generic; using System.Data.SQLite; using System.IO; using System.Linq; using System.Threading.Tasks; using Autodesk.Navisworks.Api; using NavisworksTransport.Core.Config; using NavisworksTransport.Core.Models; using NavisworksTransport.Utils; using TimeTagProfile = NavisworksTransport.Core.Models.TimeTagProfile; using TimeTagEvent = NavisworksTransport.Core.Models.TimeTagEvent; using EventType = NavisworksTransport.Core.Models.EventType; using NavisworksTransport.Core.Database; namespace NavisworksTransport { /// /// 路径数据库管理器 - 简化版 /// 负责路径和碰撞数据的持久化存储 /// public class PathDatabase : IDisposable { internal SQLiteConnection _connection; private readonly string _dbPath; private BackupManager _backupManager; /// /// 数据库连接(供内部使用) /// internal SQLiteConnection Connection => _connection; /// /// 简单数据库备份管理器(直接备份文件,最可靠) /// public BackupManager Backup => _backupManager ?? (_backupManager = new BackupManager(this)); /// /// 初始化路径数据库 /// /// Navisworks文档路径 public PathDatabase(string documentPath) { // 数据库文件与nwd文件同名,扩展名为.db _dbPath = Path.ChangeExtension(documentPath, ".db"); InitializeDatabase(); } /// /// 初始化数据库连接和表结构 /// private void InitializeDatabase() { bool isNewDatabase = !File.Exists(_dbPath); _connection = new SQLiteConnection($"Data Source={_dbPath};Version=3;"); _connection.Open(); // 启用WAL模式(Write-Ahead Logging,支持并发读) ExecuteNonQuery("PRAGMA journal_mode=WAL"); ExecuteNonQuery("PRAGMA foreign_keys=ON"); // 创建数据库表结构 CreateTables(); EnsurePathRoutesTableExtended(); if (isNewDatabase) { LogManager.Info($"创建新数据库: {_dbPath}"); } else { LogManager.Info($"打开现有数据库: {_dbPath}"); } } /// /// 创建数据库表结构 /// private void CreateTables() { // 1. 路径基本信息表 // 注意:TotalLength 字段已从数据库中删除 // 路径长度由 PathRoute.TotalLength 计算属性实时计算: // - 地面路径:从 Edges.PhysicalLength 累加(支持圆弧) // - 空轨/吊装路径:从 Points 计算直线距离 ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS PathRoutes ( Id TEXT PRIMARY KEY, Name TEXT NOT NULL, EstimatedTime REAL, TurnRadius REAL, IsCurved INTEGER, MaxObjectLength REAL, MaxObjectWidth REAL, MaxObjectHeight REAL, SafetyMargin REAL, GridSize REAL, PathType INTEGER, LiftHeight REAL, RailMountMode INTEGER, RailPathDefinitionMode INTEGER, RailNormalOffset REAL, RailPreferredNormalX REAL, RailPreferredNormalY REAL, RailPreferredNormalZ REAL, CreatedTime DATETIME, LastModified DATETIME ) "); // 2. 碰撞报告表(替换原有的CollisionResults表) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS CollisionReports ( Id INTEGER PRIMARY KEY AUTOINCREMENT, RouteId TEXT, AnimatedObjectName TEXT, UniqueCollidedObjectsCount INTEGER, FrameRate INTEGER, Duration REAL, DetectionGap REAL, AnimationCollisionCount INTEGER, ClashDetectiveCollisionCount INTEGER, ReportTime DATETIME ) "); // 3. 分析结果表(扩展版,包含5维度评分) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS AnalysisResults ( RouteId TEXT PRIMARY KEY, CollisionCount INTEGER, SafetyScore REAL, EfficiencyScore REAL, OverallScore REAL, AnalysisTime DATETIME, Strategy TEXT, TurnDifficultyScore REAL, TortuosityScore REAL, RedundancyScore REAL, HotspotCount INTEGER DEFAULT 0, GroupId TEXT, GroupRanking INTEGER DEFAULT 0, FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE ) "); // 4. 路径点表(新增 CustomTurnRadius) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS PathPoints ( Id TEXT PRIMARY KEY, RouteId TEXT NOT NULL, SequenceNumber INTEGER NOT NULL, Name TEXT, X REAL, Y REAL, Z REAL, Type INTEGER, CustomTurnRadius REAL, FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE ) "); // 5. 路径边表(新增SequenceNumber字段) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS PathEdges ( Id TEXT PRIMARY KEY, RouteId TEXT NOT NULL, StartPointId TEXT NOT NULL, EndPointId TEXT NOT NULL, SegmentType INTEGER NOT NULL, PhysicalLength REAL NOT NULL, SequenceNumber INTEGER NOT NULL, Ts_X REAL, Ts_Y REAL, Ts_Z REAL, Te_X REAL, Te_Y REAL, Te_Z REAL, ArcCenter_X REAL, ArcCenter_Y REAL, ArcCenter_Z REAL, RequestedRadius REAL, ActualRadius REAL, DeflectionAngle REAL, ArcLength REAL, FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE ) "); // 6. 碰撞检测记录表 // 包含检测配置和检测结果(原 ClashDetectiveResults 表已合并) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS CollisionDetectionRecords ( Id INTEGER PRIMARY KEY AUTOINCREMENT, RouteId TEXT NOT NULL, CreatedTime DATETIME DEFAULT CURRENT_TIMESTAMP, -- 动画参数 FrameRate INTEGER NOT NULL DEFAULT 30, DurationSeconds REAL NOT NULL DEFAULT 10.0, -- 碰撞检测参数 DetectionTolerance REAL NOT NULL DEFAULT 0.05, -- 运动物体参数 IsVirtualObject INTEGER NOT NULL DEFAULT 0, AnimatedObjectName TEXT, ObjectModelIndex INTEGER, ObjectPathId TEXT, VirtualObjectLength REAL, VirtualObjectWidth REAL, VirtualObjectHeight REAL, -- 检测模式 DetectAllObjects INTEGER NOT NULL DEFAULT 1, -- 角度修正 ObjectRotationCorrection REAL DEFAULT 0.0, -- 备注/描述 Description TEXT, -- 碰撞结果字段(原 ClashDetectiveResults 表字段) TestName TEXT UNIQUE, -- ClashDetective测试名称 TestTime DATETIME, -- 碰撞测试完成时间 CollisionCount INTEGER DEFAULT 0, -- 总碰撞数 AnimationCollisionCount INTEGER DEFAULT 0, -- 动画碰撞数 FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE ) "); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_route ON CollisionDetectionRecords(RouteId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_time ON CollisionDetectionRecords(CreatedTime DESC)"); // 7. ClashDetective碰撞对象表 // 注意:外键关联到 CollisionDetectionRecords 表 // 扩展字段:记录碰撞时运动物体的位置和朝向,用于还原碰撞场景 ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS ClashDetectiveCollisionObjects ( Id INTEGER PRIMARY KEY AUTOINCREMENT, DetectionRecordId INTEGER NOT NULL, ModelIndex INTEGER, PathId TEXT, DisplayName TEXT, ObjectName TEXT, -- 碰撞时运动物体的动画跟踪点位置和姿态(用于还原碰撞场景) AnimatedObjectTrackedPosX REAL, AnimatedObjectTrackedPosY REAL, AnimatedObjectTrackedPosZ REAL, AnimatedObjectTrackedYawRadians REAL, AnimatedObjectTrackedRotA REAL, AnimatedObjectTrackedRotB REAL, AnimatedObjectTrackedRotC REAL, AnimatedObjectTrackedRotD REAL, AnimatedObjectHasTrackedRotation INTEGER DEFAULT 0, HasPositionInfo INTEGER DEFAULT 0, FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE ) "); // 8. 碰撞报告截图表(支持多张截图) // 注意:外键关联到 CollisionDetectionRecords 表 ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS CollisionReportScreenshots ( Id INTEGER PRIMARY KEY AUTOINCREMENT, DetectionRecordId INTEGER NOT NULL, FilePath TEXT NOT NULL, Format TEXT, Width INTEGER, Height INTEGER, CaptureTime DATETIME DEFAULT CURRENT_TIMESTAMP, Description TEXT, SortOrder INTEGER DEFAULT 0, FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE ) "); // 10. 时标配置表 ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS TimeTagProfiles ( Id TEXT PRIMARY KEY, RouteId TEXT NOT NULL, Name TEXT NOT NULL, DefaultSpeed REAL DEFAULT 1.0, TurnSpeedRatio REAL DEFAULT 0.5, CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE ) "); // 11. 时标事件表 ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS TimeTagEvents ( Id TEXT PRIMARY KEY, ProfileId TEXT NOT NULL, PointId TEXT, Distance REAL NOT NULL, Type TEXT NOT NULL, Name TEXT NOT NULL, WaitTime REAL DEFAULT 0, ActionTime REAL DEFAULT 0, CumulativeTime REAL DEFAULT 0, ArriveSpeed REAL, LeaveSpeed REAL, SortOrder INTEGER NOT NULL, FOREIGN KEY(ProfileId) REFERENCES TimeTagProfiles(Id) ON DELETE CASCADE ) "); // 12. 设置数据库版本(SQLite内置user_version) // 版本号格式:主版本*10000 + 次版本*100 + 修订号 // 例如:2.1.6 = 20106 ExecuteNonQuery("PRAGMA user_version = 20106"); // v2.1.6 - 删除无效的 RailReferenceToAnchorOffset 字段 // 创建索引 ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_reports_route ON CollisionReports(RouteId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_pathpoints_route ON PathPoints(RouteId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_pathedges_route ON PathEdges(RouteId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_timetag_profiles_route ON TimeTagProfiles(RouteId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_timetag_events_profile ON TimeTagEvents(ProfileId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_test_name ON CollisionDetectionRecords(TestName)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_test_time ON CollisionDetectionRecords(TestTime DESC)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_objects_detection ON ClashDetectiveCollisionObjects(DetectionRecordId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_objects_path ON ClashDetectiveCollisionObjects(PathId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_screenshots_detection ON CollisionReportScreenshots(DetectionRecordId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_screenshots_sort ON CollisionReportScreenshots(DetectionRecordId, SortOrder)"); // 9. 批处理队列表(简化版,只维护FIFO队列) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS BatchQueueItems ( Id INTEGER PRIMARY KEY AUTOINCREMENT, RouteId TEXT NOT NULL, Status TEXT NOT NULL, CreatedTime TEXT NOT NULL, StartTime TEXT, EndTime TEXT, ErrorMessage TEXT, FrameRate INTEGER NOT NULL, DurationSeconds REAL NOT NULL, DetectionTolerance REAL NOT NULL, IsVirtualObject INTEGER NOT NULL, VirtualObjectLength REAL, VirtualObjectWidth REAL, VirtualObjectHeight REAL, DetectAllObjects INTEGER NOT NULL DEFAULT 1, ClashDetectiveTestName TEXT, CollisionCount INTEGER, ObjectRotationCorrection REAL ) "); // 批处理表索引 ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_queue_status ON BatchQueueItems(Status)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_queue_created ON BatchQueueItems(CreatedTime)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_queue_route_id ON BatchQueueItems(RouteId)"); // 通用 ModelItem 引用表(用于存储运动物体、碰撞物体等) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS ModelItemReferences ( Id INTEGER PRIMARY KEY AUTOINCREMENT, ReferenceId INTEGER NOT NULL, ReferenceType TEXT NOT NULL, ModelIndex INTEGER NOT NULL, PathId TEXT NOT NULL, DisplayName TEXT, ObjectName TEXT, Role TEXT ) "); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_model_ref_reference ON ModelItemReferences(ReferenceId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_model_ref_type ON ModelItemReferences(ReferenceType)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_model_ref_path ON ModelItemReferences(PathId)"); // 10. 排除对象表(用于存储用户排除的碰撞检测对象) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS ExcludedObjects ( Id INTEGER PRIMARY KEY AUTOINCREMENT, RouteId TEXT, ModelIndex INTEGER NOT NULL, PathId TEXT NOT NULL, DisplayName TEXT, ObjectName TEXT, ExcludedTime DATETIME DEFAULT CURRENT_TIMESTAMP, Reason TEXT, IsGlobal INTEGER DEFAULT 0, FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE ) "); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_excluded_route ON ExcludedObjects(RouteId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_excluded_path ON ExcludedObjects(PathId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_excluded_global ON ExcludedObjects(IsGlobal)"); // 11. 碰撞报告排除对象关联表 ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS CollisionReportExcludedObjects ( Id INTEGER PRIMARY KEY AUTOINCREMENT, ReportId INTEGER NOT NULL, ExcludedObjectId INTEGER NOT NULL, FOREIGN KEY(ReportId) REFERENCES CollisionReports(Id) ON DELETE CASCADE, FOREIGN KEY(ExcludedObjectId) REFERENCES ExcludedObjects(Id) ON DELETE CASCADE ) "); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_report_excluded_report ON CollisionReportExcludedObjects(ReportId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_report_excluded_object ON CollisionReportExcludedObjects(ExcludedObjectId)"); // 12. 碰撞检测记录排除对象关联表(记录每次检测时排除的对象) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS ClashDetectiveExcludedObjects ( Id INTEGER PRIMARY KEY AUTOINCREMENT, DetectionRecordId INTEGER NOT NULL, ExcludedObjectId INTEGER NOT NULL, FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE, FOREIGN KEY(ExcludedObjectId) REFERENCES ExcludedObjects(Id) ON DELETE CASCADE ) "); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_excluded_detection ON ClashDetectiveExcludedObjects(DetectionRecordId)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_excluded_object ON ClashDetectiveExcludedObjects(ExcludedObjectId)"); // 13. 碰撞检测记录表(每次点击"生成动画"创建一条记录,保存当时的完整配置) // 先添加新列到 BatchQueueItems 表 try { ExecuteNonQuery("ALTER TABLE BatchQueueItems ADD COLUMN DetectionRecordId INTEGER"); LogManager.Info("[数据库] 已添加 DetectionRecordId 列到 BatchQueueItems 表"); } catch { // 列已存在,忽略错误 } // 14. 碰撞检测记录排除对象关联表(记录每次检测时的排除列表) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS CollisionDetectionExcludedObjects ( Id INTEGER PRIMARY KEY AUTOINCREMENT, DetectionRecordId INTEGER NOT NULL, ModelIndex INTEGER NOT NULL, PathId TEXT NOT NULL, DisplayName TEXT, ObjectName TEXT, Reason TEXT, FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE ) "); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_excluded_record ON CollisionDetectionExcludedObjects(DetectionRecordId)"); // 15. 碰撞检测记录手工目标关联表(记录每次检测时的手工碰撞目标) ExecuteNonQuery(@" CREATE TABLE IF NOT EXISTS CollisionDetectionManualTargets ( Id INTEGER PRIMARY KEY AUTOINCREMENT, DetectionRecordId INTEGER NOT NULL, ModelIndex INTEGER NOT NULL, PathId TEXT NOT NULL, DisplayName TEXT, ObjectName TEXT, FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE ) "); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_manual_record ON CollisionDetectionManualTargets(DetectionRecordId)"); } /// /// 保存路径基本信息 /// public void SavePathRoute(PathRoute route) { // 使用事务确保路径和路径点一起保存 using (var transaction = _connection.BeginTransaction()) { try { // 保存路径基本信息 // 注意:TotalLength 字段已从数据库中删除 // 路径长度由 PathRoute.TotalLength 计算属性实时从 Edges/Points 计算 var sql = @" INSERT OR REPLACE INTO PathRoutes (Id, Name, EstimatedTime, TurnRadius, IsCurved, MaxObjectLength, MaxObjectWidth, MaxObjectHeight, SafetyMargin, GridSize, PathType, LiftHeight, RailMountMode, RailPathDefinitionMode, RailNormalOffset, RailPreferredNormalX, RailPreferredNormalY, RailPreferredNormalZ, CreatedTime, LastModified) VALUES (@id, @name, @time, @turnRadius, @isCurved, @maxLength, @maxWidth, @maxHeight, @safetyMargin, @gridSize, @pathType, @liftHeightMeters, @railMountMode, @railPathDefinitionMode, @railNormalOffset, @railPreferredNormalX, @railPreferredNormalY, @railPreferredNormalZ, @created, @modified) "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", route.Id); cmd.Parameters.AddWithValue("@name", route.Name); cmd.Parameters.AddWithValue("@time", route.EstimatedTime); cmd.Parameters.AddWithValue("@turnRadius", route.TurnRadius); cmd.Parameters.AddWithValue("@isCurved", route.IsCurved ? 1 : 0); cmd.Parameters.AddWithValue("@maxLength", route.MaxObjectLength); cmd.Parameters.AddWithValue("@maxWidth", route.MaxObjectWidth); cmd.Parameters.AddWithValue("@maxHeight", route.MaxObjectHeight); cmd.Parameters.AddWithValue("@safetyMargin", route.SafetyMargin); cmd.Parameters.AddWithValue("@gridSize", route.GridSize); cmd.Parameters.AddWithValue("@pathType", (int)route.PathType); cmd.Parameters.AddWithValue("@liftHeightMeters", route.LiftHeight); cmd.Parameters.AddWithValue("@railMountMode", (int)route.RailMountMode); cmd.Parameters.AddWithValue("@railPathDefinitionMode", (int)route.RailPathDefinitionMode); cmd.Parameters.AddWithValue("@railNormalOffset", route.RailNormalOffset); cmd.Parameters.AddWithValue("@railPreferredNormalX", route.RailPreferredNormal != null ? (object)route.RailPreferredNormal.X : DBNull.Value); cmd.Parameters.AddWithValue("@railPreferredNormalY", route.RailPreferredNormal != null ? (object)route.RailPreferredNormal.Y : DBNull.Value); cmd.Parameters.AddWithValue("@railPreferredNormalZ", route.RailPreferredNormal != null ? (object)route.RailPreferredNormal.Z : DBNull.Value); cmd.Parameters.AddWithValue("@created", route.CreatedTime); cmd.Parameters.AddWithValue("@modified", DateTime.Now); cmd.ExecuteNonQuery(); } // 删除旧的路径点和边 DeletePathPoints(route.Id); DeletePathEdges(route.Id); // 保存新的路径点 if (route.Points != null && route.Points.Count > 0) { var pointSql = @" INSERT INTO PathPoints (Id, RouteId, SequenceNumber, Name, X, Y, Z, Type, CustomTurnRadius) VALUES (@id, @routeId, @seq, @name, @x, @y, @z, @type, @customRadius) "; for (int i = 0; i < route.Points.Count; i++) { var point = route.Points[i]; using (var cmd = new SQLiteCommand(pointSql, _connection)) { cmd.Parameters.AddWithValue("@id", point.Id); cmd.Parameters.AddWithValue("@routeId", route.Id); cmd.Parameters.AddWithValue("@seq", i); cmd.Parameters.AddWithValue("@name", point.Name ?? ""); cmd.Parameters.AddWithValue("@x", point.Position.X); cmd.Parameters.AddWithValue("@y", point.Position.Y); cmd.Parameters.AddWithValue("@z", point.Position.Z); cmd.Parameters.AddWithValue("@type", (int)point.Type); cmd.Parameters.AddWithValue("@customRadius", point.CustomTurnRadius.HasValue ? (object)point.CustomTurnRadius.Value : DBNull.Value); cmd.ExecuteNonQuery(); } } LogManager.Info($"保存路径: {route.Name},包含 {route.Points.Count} 个路径点"); } // 保存路径边 if (route.Edges != null && route.Edges.Count > 0) { for (int i = 0; i < route.Edges.Count; i++) { SavePathEdge(route.Id, route.Edges[i], i); } LogManager.Info($"保存路径: {route.Name},包含 {route.Edges.Count} 个路径边"); } transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); LogManager.Error($"保存路径失败: {ex.Message}", ex); throw; } } } /// /// 保存碰撞报告,返回报告ID /// public void SaveCollisionReport(string routeId, string pathName, string animatedObjectName, int uniqueCollidedObjectsCount, int frameRate, double duration, double detectionGap, int animationCollisionCount, int clashDetectiveCollisionCount) { try { var sql = @" INSERT INTO CollisionReports (RouteId, AnimatedObjectName, UniqueCollidedObjectsCount, FrameRate, Duration, DetectionGap, AnimationCollisionCount, ClashDetectiveCollisionCount, ReportTime) VALUES (@routeId, @animatedObjectName, @uniqueCollidedObjectsCount, @frameRate, @duration, @detectionGap, @animationCount, @clashCount, @reportTime) "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@routeId", routeId ?? ""); cmd.Parameters.AddWithValue("@animatedObjectName", animatedObjectName ?? "未知对象"); cmd.Parameters.AddWithValue("@uniqueCollidedObjectsCount", uniqueCollidedObjectsCount); cmd.Parameters.AddWithValue("@frameRate", frameRate); cmd.Parameters.AddWithValue("@duration", duration); cmd.Parameters.AddWithValue("@detectionGap", detectionGap); cmd.Parameters.AddWithValue("@animationCount", animationCollisionCount); cmd.Parameters.AddWithValue("@clashCount", clashDetectiveCollisionCount); cmd.Parameters.AddWithValue("@reportTime", DateTime.Now); cmd.ExecuteNonQuery(); } LogManager.Info($"碰撞报告已保存: 路径ID={routeId}, 碰撞构件={uniqueCollidedObjectsCount}, 动画碰撞={animationCollisionCount}, ClashDetective碰撞={clashDetectiveCollisionCount}"); } catch (Exception ex) { LogManager.Error($"保存碰撞报告失败: {ex.Message}", ex); throw; } } /// /// 更新碰撞报告的截图信息(更新CollisionDetectionRecords表) /// public void UpdateCollisionReportScreenshot(string routeId, string screenshotPath, string screenshotFormat, int screenshotWidth, int screenshotHeight) { try { // 获取该 RouteId 的最新记录Id var getIdSql = @" SELECT Id FROM CollisionDetectionRecords WHERE RouteId = @routeId ORDER BY CreatedTime DESC LIMIT 1 "; int? detectionRecordId = null; using (var cmd = new SQLiteCommand(getIdSql, _connection)) { cmd.Parameters.AddWithValue("@routeId", routeId ?? ""); var result = cmd.ExecuteScalar(); if (result != null && result != DBNull.Value) { detectionRecordId = Convert.ToInt32(result); } } if (!detectionRecordId.HasValue) { LogManager.Warning($"未找到需要更新的碰撞记录: RouteId={routeId}"); return; } // 保存到截图表 SaveCollisionReportScreenshot(detectionRecordId.Value, screenshotPath, screenshotFormat, screenshotWidth, screenshotHeight, 0); LogManager.Info($"碰撞报告截图已更新: RouteId={routeId}, 截图={screenshotPath}"); } catch (Exception ex) { LogManager.Error($"更新碰撞报告截图失败: {ex.Message}", ex); throw; } } /// /// 保存碰撞报告截图到截图表,返回新记录Id /// public int SaveCollisionReportScreenshot(int detectionRecordId, string filePath, string format, int width, int height, int sortOrder, string description = null) { try { var sql = @" INSERT INTO CollisionReportScreenshots (DetectionRecordId, FilePath, Format, Width, Height, CaptureTime, Description, SortOrder) VALUES (@detectionRecordId, @filePath, @format, @width, @height, @captureTime, @description, @sortOrder) "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId); cmd.Parameters.AddWithValue("@filePath", filePath ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@format", format ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@width", width); cmd.Parameters.AddWithValue("@height", height); cmd.Parameters.AddWithValue("@captureTime", DateTime.Now); cmd.Parameters.AddWithValue("@description", description ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@sortOrder", sortOrder); cmd.ExecuteNonQuery(); } // 获取新插入记录的Id int newId = (int)_connection.LastInsertRowId; LogManager.Debug($"碰撞报告截图已保存: Id={newId}, DetectionRecordId={detectionRecordId}, FilePath={filePath}"); return newId; } catch (Exception ex) { LogManager.Error($"保存碰撞报告截图失败: {ex.Message}", ex); throw; } } /// /// 获取碰撞报告的所有截图 /// public List GetCollisionReportScreenshots(int detectionRecordId) { var screenshots = new List(); try { var sql = @" SELECT Id, DetectionRecordId, FilePath, Format, Width, Height, CaptureTime, Description, SortOrder FROM CollisionReportScreenshots WHERE DetectionRecordId = @detectionRecordId ORDER BY SortOrder, CaptureTime "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { screenshots.Add(new CollisionReportScreenshotRecord { Id = Convert.ToInt32(reader["Id"]), DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]), FilePath = reader["FilePath"]?.ToString(), Format = reader["Format"]?.ToString(), Width = reader["Width"] != DBNull.Value ? Convert.ToInt32(reader["Width"]) : 0, Height = reader["Height"] != DBNull.Value ? Convert.ToInt32(reader["Height"]) : 0, CaptureTime = reader["CaptureTime"] != DBNull.Value ? Convert.ToDateTime(reader["CaptureTime"]) : DateTime.MinValue, Description = reader["Description"]?.ToString(), SortOrder = reader["SortOrder"] != DBNull.Value ? Convert.ToInt32(reader["SortOrder"]) : 0 }); } } } } catch (Exception ex) { LogManager.Error($"获取碰撞报告截图失败: {ex.Message}", ex); } return screenshots; } /// /// 删除碰撞报告截图 /// public void DeleteCollisionReportScreenshot(int screenshotId) { try { var sql = "DELETE FROM CollisionReportScreenshots WHERE Id = @id"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", screenshotId); cmd.ExecuteNonQuery(); } LogManager.Debug($"碰撞报告截图已删除: ScreenshotId={screenshotId}"); } catch (Exception ex) { LogManager.Error($"删除碰撞报告截图失败: {ex.Message}", ex); throw; } } /// /// 更新截图排序和描述 /// public void UpdateCollisionReportScreenshotMetadata(int screenshotId, int sortOrder, string description) { try { var sql = @" UPDATE CollisionReportScreenshots SET SortOrder = @sortOrder, Description = @description WHERE Id = @id "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", screenshotId); cmd.Parameters.AddWithValue("@sortOrder", sortOrder); cmd.Parameters.AddWithValue("@description", description ?? (object)DBNull.Value); cmd.ExecuteNonQuery(); } } catch (Exception ex) { LogManager.Error($"更新截图元数据失败: {ex.Message}", ex); throw; } } /// /// 更新路径名称(仅更新名称字段) /// public void UpdatePathName(string routeId, string newName) { if (string.IsNullOrEmpty(routeId) || string.IsNullOrEmpty(newName)) { LogManager.Warning("更新路径名称失败:routeId或newName为空"); return; } var sql = @"UPDATE PathRoutes SET Name = @name, LastModified = @modified WHERE Id = @id"; try { ExecuteNonQuery(sql, new { id = routeId, name = newName, modified = DateTime.Now }); LogManager.Info($"更新路径名称成功: {routeId} -> {newName}"); } catch (Exception ex) { LogManager.Error($"更新路径名称失败: {ex.Message}", ex); throw; } } /// /// 保存路径的碰撞结果(已废弃,保留以兼容旧代码) /// public void SaveCollisions(string routeId, List collisions) { // 先删除旧的碰撞数据 ExecuteNonQuery("DELETE FROM CollisionResults WHERE RouteId=@id", new { id = routeId }); if (collisions == null || collisions.Count == 0) return; // 批量插入新数据 using (var transaction = _connection.BeginTransaction()) { try { var sql = @" INSERT INTO CollisionResults (RouteId, Item1Name, Item2Name, CenterX, CenterY, CenterZ, Distance, DetectedTime) VALUES (@rid, @i1, @i2, @x, @y, @z, @dist, @time) "; foreach (var collision in collisions) { using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@rid", routeId); cmd.Parameters.AddWithValue("@i1", GetItemDisplayName(collision.Item1)); cmd.Parameters.AddWithValue("@i2", GetItemDisplayName(collision.Item2)); cmd.Parameters.AddWithValue("@x", collision.Center.X); cmd.Parameters.AddWithValue("@y", collision.Center.Y); cmd.Parameters.AddWithValue("@z", collision.Center.Z); cmd.Parameters.AddWithValue("@dist", collision.Distance); cmd.Parameters.AddWithValue("@time", DateTime.Now); cmd.ExecuteNonQuery(); } } transaction.Commit(); LogManager.Info($"保存碰撞结果: RouteId={routeId}, Count={collisions.Count}"); } catch (Exception ex) { transaction.Rollback(); LogManager.Error($"保存碰撞结果失败: {ex.Message}", ex); throw; } } } #region 碰撞检测记录查询 /// /// 获取路径的所有碰撞检测记录(包含碰撞结果) /// public List GetDetectionResultsByPath(string pathName) { var results = new List(); try { var sql = @" SELECT cdr.*, pr.Name AS PathName FROM CollisionDetectionRecords cdr INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id WHERE pr.Name = @pathName AND cdr.TestName IS NOT NULL ORDER BY cdr.TestTime DESC "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@pathName", pathName); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var record = ReadCollisionDetectionRecordFromReader(reader); results.Add(record); } } } LogManager.Debug($"获取到 {results.Count} 条碰撞检测记录"); } catch (Exception ex) { LogManager.Error($"获取碰撞检测记录失败: {ex.Message}", ex); } return results; } /// /// 获取路径最新的碰撞检测记录(包含碰撞结果) /// public CollisionDetectionRecord GetLatestDetectionResult(string pathName) { try { var sql = @" SELECT cdr.*, pr.Name AS PathName FROM CollisionDetectionRecords cdr INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id WHERE pr.Name = @pathName AND cdr.TestName IS NOT NULL ORDER BY cdr.TestTime DESC LIMIT 1 "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@pathName", pathName); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { return ReadCollisionDetectionRecordFromReader(reader); } } } } catch (Exception ex) { LogManager.Error($"获取最新碰撞检测记录失败: {ex.Message}", ex); } return null; } /// /// 根据ID获取碰撞检测记录 /// public CollisionDetectionRecord GetDetectionResultById(int id) { try { var sql = @" SELECT cdr.*, pr.Name AS PathName FROM CollisionDetectionRecords cdr INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id WHERE cdr.Id = @id "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", id); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { return ReadCollisionDetectionRecordFromReader(reader); } } } } catch (Exception ex) { LogManager.Error($"根据ID获取碰撞检测记录失败: {ex.Message}", ex); } return null; } /// /// 删除碰撞检测记录 /// public void DeleteDetectionResult(int id) { try { ExecuteNonQuery("DELETE FROM CollisionDetectionRecords WHERE Id=@id", new { id = id }); LogManager.Info($"已删除碰撞检测记录: Id={id}"); } catch (Exception ex) { LogManager.Error($"删除碰撞检测记录失败: {ex.Message}", ex); throw; } } /// /// 获取所有碰撞检测记录(包含碰撞结果) /// public List GetAllDetectionResults() { var results = new List(); try { var sql = @" SELECT cdr.*, pr.Name AS PathName FROM CollisionDetectionRecords cdr INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id WHERE cdr.TestName IS NOT NULL ORDER BY cdr.TestTime DESC "; using (var cmd = new SQLiteCommand(sql, _connection)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var record = ReadCollisionDetectionRecordFromReader(reader); results.Add(record); } } } catch (Exception ex) { LogManager.Error($"获取所有碰撞检测记录失败: {ex.Message}", ex); } return results; } /// /// 清空所有数据 /// public void ClearAllData() { try { // 按依赖关系顺序删除(先删除子表,再删除主表) var tables = new[] { "CollisionReportScreenshots", "ClashDetectiveCollisionObjects", "CollisionDetectionExcludedObjects", "CollisionDetectionManualTargets", "CollisionDetectionRecords", "PathEdges", "PathPoints", "AnalysisResults", "CollisionReports", "PathRoutes", "BatchQueueItems" }; foreach (var table in tables) { try { ExecuteNonQuery($"DELETE FROM {table}"); LogManager.Debug($"已清空表: {table}"); } catch (Exception ex) { LogManager.Warning($"清空表 {table} 失败(可能表不存在): {ex.Message}"); } } // 重置自增ID try { ExecuteNonQuery("DELETE FROM sqlite_sequence"); } catch (Exception ex) { LogManager.Debug($"[数据库] 重置自增ID失败: {ex.Message}"); } LogManager.Info("已清空所有数据表"); } catch (Exception ex) { LogManager.Error($"清空数据失败: {ex.Message}", ex); throw; } } /// /// 根据测试名称获取碰撞检测记录 /// public CollisionDetectionRecord GetDetectionResultByTestName(string testName) { try { var sql = @" SELECT cdr.*, pr.Name AS PathName FROM CollisionDetectionRecords cdr INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id WHERE cdr.TestName = @testName "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@testName", testName); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { return ReadCollisionDetectionRecordFromReader(reader); } } } } catch (Exception ex) { LogManager.Error($"根据测试名称获取碰撞检测记录失败: {ex.Message}", ex); } return null; } /// /// 保存ClashDetective碰撞对象 /// public void SaveClashDetectiveCollisionObjects(int detectionRecordId, List objects) { if (objects == null || objects.Count == 0) return; try { using (var transaction = _connection.BeginTransaction()) { var sql = @" INSERT INTO ClashDetectiveCollisionObjects (DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName, AnimatedObjectTrackedPosX, AnimatedObjectTrackedPosY, AnimatedObjectTrackedPosZ, AnimatedObjectTrackedYawRadians, AnimatedObjectTrackedRotA, AnimatedObjectTrackedRotB, AnimatedObjectTrackedRotC, AnimatedObjectTrackedRotD, AnimatedObjectHasTrackedRotation, HasPositionInfo) VALUES (@detectionRecordId, @modelIndex, @pathId, @displayName, @objectName, @trackedPosX, @trackedPosY, @trackedPosZ, @trackedYawRadians, @trackedRotA, @trackedRotB, @trackedRotC, @trackedRotD, @hasTrackedRotation, @hasPositionInfo) "; foreach (var obj in objects) { using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId); cmd.Parameters.AddWithValue("@modelIndex", obj.ModelIndex.HasValue ? (object)obj.ModelIndex.Value : DBNull.Value); cmd.Parameters.AddWithValue("@pathId", obj.PathId ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@displayName", obj.DisplayName ?? ""); cmd.Parameters.AddWithValue("@objectName", obj.ObjectName ?? ""); cmd.Parameters.AddWithValue("@trackedPosX", obj.AnimatedObjectTrackedPosX.HasValue ? (object)obj.AnimatedObjectTrackedPosX.Value : DBNull.Value); cmd.Parameters.AddWithValue("@trackedPosY", obj.AnimatedObjectTrackedPosY.HasValue ? (object)obj.AnimatedObjectTrackedPosY.Value : DBNull.Value); cmd.Parameters.AddWithValue("@trackedPosZ", obj.AnimatedObjectTrackedPosZ.HasValue ? (object)obj.AnimatedObjectTrackedPosZ.Value : DBNull.Value); cmd.Parameters.AddWithValue("@trackedYawRadians", obj.AnimatedObjectTrackedYawRadians.HasValue ? (object)obj.AnimatedObjectTrackedYawRadians.Value : DBNull.Value); cmd.Parameters.AddWithValue("@trackedRotA", obj.AnimatedObjectTrackedRotA.HasValue ? (object)obj.AnimatedObjectTrackedRotA.Value : DBNull.Value); cmd.Parameters.AddWithValue("@trackedRotB", obj.AnimatedObjectTrackedRotB.HasValue ? (object)obj.AnimatedObjectTrackedRotB.Value : DBNull.Value); cmd.Parameters.AddWithValue("@trackedRotC", obj.AnimatedObjectTrackedRotC.HasValue ? (object)obj.AnimatedObjectTrackedRotC.Value : DBNull.Value); cmd.Parameters.AddWithValue("@trackedRotD", obj.AnimatedObjectTrackedRotD.HasValue ? (object)obj.AnimatedObjectTrackedRotD.Value : DBNull.Value); cmd.Parameters.AddWithValue("@hasTrackedRotation", obj.AnimatedObjectHasTrackedRotation ? 1 : 0); cmd.Parameters.AddWithValue("@hasPositionInfo", obj.HasPositionInfo ? 1 : 0); cmd.ExecuteNonQuery(); } } transaction.Commit(); LogManager.Info($"已保存 {objects.Count} 个碰撞对象到数据库,DetectionRecordId={detectionRecordId}"); } } catch (Exception ex) { LogManager.Error($"保存ClashDetective碰撞对象失败: {ex.Message}", ex); throw; } } /// /// 获取指定碰撞检测记录的所有碰撞对象 /// public List GetClashDetectiveCollisionObjects(int detectionRecordId) { var results = new List(); try { var sql = @" SELECT Id, DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName, AnimatedObjectTrackedPosX, AnimatedObjectTrackedPosY, AnimatedObjectTrackedPosZ, AnimatedObjectTrackedYawRadians, AnimatedObjectTrackedRotA, AnimatedObjectTrackedRotB, AnimatedObjectTrackedRotC, AnimatedObjectTrackedRotD, AnimatedObjectHasTrackedRotation, HasPositionInfo FROM ClashDetectiveCollisionObjects WHERE DetectionRecordId = @detectionRecordId "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { results.Add(new ClashDetectiveCollisionObjectRecord { Id = Convert.ToInt32(reader["Id"]), DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]), ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null, PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null, DisplayName = reader["DisplayName"]?.ToString(), ObjectName = reader["ObjectName"]?.ToString(), AnimatedObjectTrackedPosX = reader["AnimatedObjectTrackedPosX"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedPosX"]) : (double?)null, AnimatedObjectTrackedPosY = reader["AnimatedObjectTrackedPosY"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedPosY"]) : (double?)null, AnimatedObjectTrackedPosZ = reader["AnimatedObjectTrackedPosZ"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedPosZ"]) : (double?)null, AnimatedObjectTrackedYawRadians = reader["AnimatedObjectTrackedYawRadians"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedYawRadians"]) : (double?)null, AnimatedObjectTrackedRotA = reader["AnimatedObjectTrackedRotA"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotA"]) : (double?)null, AnimatedObjectTrackedRotB = reader["AnimatedObjectTrackedRotB"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotB"]) : (double?)null, AnimatedObjectTrackedRotC = reader["AnimatedObjectTrackedRotC"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotC"]) : (double?)null, AnimatedObjectTrackedRotD = reader["AnimatedObjectTrackedRotD"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotD"]) : (double?)null, AnimatedObjectHasTrackedRotation = reader["AnimatedObjectHasTrackedRotation"] != DBNull.Value && Convert.ToInt32(reader["AnimatedObjectHasTrackedRotation"]) == 1, HasPositionInfo = reader["HasPositionInfo"] != DBNull.Value && Convert.ToInt32(reader["HasPositionInfo"]) == 1 }); } } } LogManager.Info($"[获取碰撞对象] 查询到 {results.Count} 个碰撞对象,DetectionRecordId={detectionRecordId}"); } catch (Exception ex) { LogManager.Error($"获取ClashDetective碰撞对象失败: {ex.Message}", ex); } return results; } /// /// 获取检测记录的排除对象列表 /// public List GetDetectionExcludedObjects(int detectionRecordId) { var results = new List(); try { var sql = @" SELECT Id, DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName, Reason FROM CollisionDetectionExcludedObjects WHERE DetectionRecordId = @detectionRecordId "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { results.Add(new CollisionDetectionExcludedObjectRecord { Id = Convert.ToInt32(reader["Id"]), DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]), ModelIndex = reader["ModelIndex"] == DBNull.Value ? 0 : Convert.ToInt32(reader["ModelIndex"]), PathId = reader["PathId"] == DBNull.Value ? null : reader["PathId"].ToString(), DisplayName = reader["DisplayName"]?.ToString(), ObjectName = reader["ObjectName"]?.ToString(), Reason = reader["Reason"]?.ToString() }); } } } } catch (Exception ex) { LogManager.Error($"获取排除对象列表失败: {ex.Message}", ex); } return results; } /// /// 获取检测记录的手工碰撞目标列表 /// public List GetDetectionManualTargets(int detectionRecordId) { var results = new List(); try { var sql = @" SELECT Id, DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName FROM CollisionDetectionManualTargets WHERE DetectionRecordId = @detectionRecordId "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { results.Add(new CollisionDetectionManualTargetRecord { Id = Convert.ToInt32(reader["Id"]), DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]), ModelIndex = reader["ModelIndex"] == DBNull.Value ? 0 : Convert.ToInt32(reader["ModelIndex"]), PathId = reader["PathId"] == DBNull.Value ? null : reader["PathId"].ToString(), DisplayName = reader["DisplayName"]?.ToString(), ObjectName = reader["ObjectName"]?.ToString() }); } } } } catch (Exception ex) { LogManager.Error($"获取手工目标列表失败: {ex.Message}", ex); } return results; } #endregion /// /// 通过基础参数查询匹配的检测记录(用于检查配置是否重复) /// 注意:数值参数使用容差匹配,返回的记录还需要进一步比较排除列表和手工目标 /// public List FindDetectionRecordsByBaseParams( string routeId, int frameRate, double durationSeconds, double detectionTolerance, bool isVirtualObject, double? virtualObjectLength, double? virtualObjectWidth, double? virtualObjectHeight, double objectRotationCorrection, bool isManualMode) { var results = new List(); try { // 使用容差匹配浮动数值参数 var sql = @" SELECT cdr.*, pr.Name AS PathName FROM CollisionDetectionRecords cdr INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id WHERE cdr.RouteId = @routeId AND cdr.FrameRate = @frameRate AND ABS(cdr.DurationSeconds - @duration) < 0.01 AND ABS(cdr.DetectionTolerance - @tolerance) < 0.001 AND cdr.IsVirtualObject = @isVirtual AND ABS(cdr.ObjectRotationCorrection - @rotation) < 0.01 AND cdr.DetectAllObjects = @detectAll AND cdr.TestName IS NOT NULL "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@routeId", routeId); cmd.Parameters.AddWithValue("@frameRate", frameRate); cmd.Parameters.AddWithValue("@duration", durationSeconds); cmd.Parameters.AddWithValue("@tolerance", detectionTolerance); cmd.Parameters.AddWithValue("@isVirtual", isVirtualObject ? 1 : 0); cmd.Parameters.AddWithValue("@rotation", objectRotationCorrection); cmd.Parameters.AddWithValue("@detectAll", isManualMode ? 0 : 1); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var record = ReadCollisionDetectionRecordFromReader(reader); // 虚拟物体尺寸比较(如果是虚拟物体) if (isVirtualObject && virtualObjectLength.HasValue) { if (!record.VirtualObjectLength.HasValue || Math.Abs(record.VirtualObjectLength.Value - virtualObjectLength.Value) > 0.01) continue; if (!record.VirtualObjectWidth.HasValue || Math.Abs(record.VirtualObjectWidth.Value - virtualObjectWidth.Value) > 0.01) continue; if (!record.VirtualObjectHeight.HasValue || Math.Abs(record.VirtualObjectHeight.Value - virtualObjectHeight.Value) > 0.01) continue; } results.Add(record); } } } LogManager.Debug($"[FindByBaseParams] 找到 {results.Count} 条基础参数匹配的记录"); } catch (Exception ex) { LogManager.Error($"通过基础参数查询检测记录失败: {ex.Message}", ex); } return results; } /// /// 保存分析评分结果 /// public void SaveAnalysisScore(string routeId, int collisionCount, double safetyScore, double efficiencyScore, string strategy) { var overallScore = (safetyScore + efficiencyScore) / 2.0; var sql = @" INSERT OR REPLACE INTO AnalysisResults (RouteId, CollisionCount, SafetyScore, EfficiencyScore, OverallScore, AnalysisTime, Strategy) VALUES (@id, @count, @safety, @eff, @overall, @time, @strategy) "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", routeId); cmd.Parameters.AddWithValue("@count", collisionCount); cmd.Parameters.AddWithValue("@safety", safetyScore); cmd.Parameters.AddWithValue("@eff", efficiencyScore); cmd.Parameters.AddWithValue("@overall", overallScore); cmd.Parameters.AddWithValue("@time", DateTime.Now); cmd.Parameters.AddWithValue("@strategy", strategy ?? "默认"); cmd.ExecuteNonQuery(); } } /// /// 获取路径的碰撞数量 /// public int GetCollisionCount(string routeId) { var sql = "SELECT COUNT(*) FROM CollisionResults WHERE RouteId=@id"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", routeId); var result = cmd.ExecuteScalar(); return Convert.ToInt32(result); } } /// /// 获取路径的分析结果 /// public PathAnalysisResult GetAnalysisResult(string routeId) { var sql = "SELECT * FROM AnalysisResults WHERE RouteId=@id"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", routeId); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { return new PathAnalysisResult { RouteId = reader["RouteId"].ToString(), CollisionCount = Convert.ToInt32(reader["CollisionCount"]), SafetyScore = Convert.ToDouble(reader["SafetyScore"]), EfficiencyScore = Convert.ToDouble(reader["EfficiencyScore"]), OverallScore = Convert.ToDouble(reader["OverallScore"]), AnalysisTime = Convert.ToDateTime(reader["AnalysisTime"]), Strategy = reader["Strategy"].ToString() }; } } } return null; } /// /// 获取所有路径的分析结果 /// public List GetAllAnalysisResults() { var results = new List(); var sql = "SELECT * FROM AnalysisResults ORDER BY OverallScore DESC"; using (var cmd = new SQLiteCommand(sql, _connection)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { results.Add(new PathAnalysisResult { RouteId = reader["RouteId"].ToString(), CollisionCount = Convert.ToInt32(reader["CollisionCount"]), SafetyScore = Convert.ToDouble(reader["SafetyScore"]), EfficiencyScore = Convert.ToDouble(reader["EfficiencyScore"]), OverallScore = Convert.ToDouble(reader["OverallScore"]), AnalysisTime = Convert.ToDateTime(reader["AnalysisTime"]), Strategy = reader["Strategy"].ToString() }); } } return results; } /// /// 删除路径及其相关数据 /// /// 路径ID public void DeletePathRoute(string routeId) { try { // 由于设置了外键级联删除,删除路径时会自动删除相关的碰撞结果和分析结果 var sql = "DELETE FROM PathRoutes WHERE Id = @id"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", routeId); int affected = cmd.ExecuteNonQuery(); LogManager.Info($"删除路径记录: ID={routeId}, 影响行数={affected}"); } } catch (Exception ex) { LogManager.Error($"删除路径失败: ID={routeId}, {ex.Message}", ex); throw; } } /// /// 获取所有路径基本信息 /// public List GetAllPathRoutes() { var routes = new List(); var sql = @"SELECT pr.*, ar.CollisionCount, ar.SafetyScore, ar.EfficiencyScore, ar.OverallScore FROM PathRoutes pr LEFT JOIN AnalysisResults ar ON pr.Id = ar.RouteId ORDER BY pr.LastModified DESC"; using (var cmd = new SQLiteCommand(sql, _connection)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { // 🔥 不使用默认值,数据缺失时抛出异常,避免隐藏bug // 注意:TotalLength 字段已废弃,不再从数据库加载(由计算属性实时提供) var route = new PathRoute { Id = reader["Id"].ToString(), Name = reader["Name"].ToString(), // TotalLength:不再从数据库加载,由 PathRoute.TotalLength 计算属性实时提供 EstimatedTime = Convert.ToDouble(reader["EstimatedTime"]), TurnRadius = Convert.ToDouble(reader["TurnRadius"]), IsCurved = Convert.ToInt32(reader["IsCurved"]) == 1, MaxObjectLength = Convert.ToDouble(reader["MaxObjectLength"]), MaxObjectWidth = Convert.ToDouble(reader["MaxObjectWidth"]), MaxObjectHeight = Convert.ToDouble(reader["MaxObjectHeight"]), SafetyMargin = Convert.ToDouble(reader["SafetyMargin"]), GridSize = Convert.ToDouble(reader["GridSize"]), PathType = (PathType)Convert.ToInt32(reader["PathType"]), LiftHeight = Convert.ToDouble(reader["LiftHeight"]), RailMountMode = (RailMountMode)Convert.ToInt32(reader["RailMountMode"]), RailPathDefinitionMode = (RailPathDefinitionMode)Convert.ToInt32(reader["RailPathDefinitionMode"]), RailNormalOffset = reader["RailNormalOffset"] != DBNull.Value ? Convert.ToDouble(reader["RailNormalOffset"]) : 0.0, CreatedTime = Convert.ToDateTime(reader["CreatedTime"]), LastModified = Convert.ToDateTime(reader["LastModified"]) }; if (!reader.IsDBNull(reader.GetOrdinal("RailPreferredNormalX")) && !reader.IsDBNull(reader.GetOrdinal("RailPreferredNormalY")) && !reader.IsDBNull(reader.GetOrdinal("RailPreferredNormalZ"))) { route.RailPreferredNormal = new Point3D( Convert.ToDouble(reader["RailPreferredNormalX"]), Convert.ToDouble(reader["RailPreferredNormalY"]), Convert.ToDouble(reader["RailPreferredNormalZ"])); } // 添加分析结果(如果存在) if (!reader.IsDBNull(reader.GetOrdinal("CollisionCount"))) { route.CollisionCount = Convert.ToInt32(reader["CollisionCount"]); route.SafetyScore = Convert.ToDouble(reader["SafetyScore"]); route.EfficiencyScore = Convert.ToDouble(reader["EfficiencyScore"]); route.OverallScore = Convert.ToDouble(reader["OverallScore"]); } routes.Add(route); } } // 为每个路径加载路径点和路径边 foreach (var route in routes) { LoadPathPoints(route); LoadPathEdges(route); } LogManager.Info($"从数据库加载了 {routes.Count} 条路径记录"); return routes; } /// /// 加载指定路径的路径点 /// private void LoadPathPoints(PathRoute route) { var sql = @"SELECT * FROM PathPoints WHERE RouteId=@routeId ORDER BY SequenceNumber"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@routeId", route.Id); using (var reader = cmd.ExecuteReader()) { int index = 0; while (reader.Read()) { var point = new PathPoint { Id = reader["Id"].ToString(), Name = reader["Name"].ToString(), Position = new Autodesk.Navisworks.Api.Point3D( Convert.ToDouble(reader["X"]), Convert.ToDouble(reader["Y"]), Convert.ToDouble(reader["Z"]) ), Type = (PathPointType)Convert.ToInt32(reader["Type"]), Index = index++ }; // 加载自定义转向半径 if (!reader.IsDBNull(reader.GetOrdinal("CustomTurnRadius"))) { point.CustomTurnRadius = Convert.ToDouble(reader["CustomTurnRadius"]); } route.Points.Add(point); } LogManager.Debug($"[LoadPathPoints] 路径 {route.Name} (Id={route.Id}) 加载了 {index} 个路径点"); } } } /// /// 保存路径边 /// private void SavePathEdge(string routeId, PathEdge edge, int sequenceNumber) { var sql = @" INSERT INTO PathEdges (Id, RouteId, StartPointId, EndPointId, SegmentType, PhysicalLength, SequenceNumber, Ts_X, Ts_Y, Ts_Z, Te_X, Te_Y, Te_Z, ArcCenter_X, ArcCenter_Y, ArcCenter_Z, RequestedRadius, ActualRadius, DeflectionAngle, ArcLength) VALUES (@id, @routeId, @startId, @endId, @segType, @length, @seqNum, @tsx, @tsy, @tsz, @tex, @tey, @tez, @acx, @acy, @acz, @reqR, @actR, @angle, @arcLen) "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", edge.Id); cmd.Parameters.AddWithValue("@routeId", routeId); cmd.Parameters.AddWithValue("@startId", edge.StartPointId ?? ""); cmd.Parameters.AddWithValue("@endId", edge.EndPointId ?? ""); cmd.Parameters.AddWithValue("@segType", (int)edge.SegmentType); cmd.Parameters.AddWithValue("@length", edge.PhysicalLength); cmd.Parameters.AddWithValue("@seqNum", sequenceNumber); // 直线段:保存起止点坐标 if (edge.SegmentType == PathSegmentType.Straight && edge.SampledPoints != null && edge.SampledPoints.Count >= 2) { cmd.Parameters.AddWithValue("@tsx", edge.SampledPoints.First().X); cmd.Parameters.AddWithValue("@tsy", edge.SampledPoints.First().Y); cmd.Parameters.AddWithValue("@tsz", edge.SampledPoints.First().Z); cmd.Parameters.AddWithValue("@tex", edge.SampledPoints.Last().X); cmd.Parameters.AddWithValue("@tey", edge.SampledPoints.Last().Y); cmd.Parameters.AddWithValue("@tez", edge.SampledPoints.Last().Z); // 圆弧参数为NULL cmd.Parameters.AddWithValue("@acx", DBNull.Value); cmd.Parameters.AddWithValue("@acy", DBNull.Value); cmd.Parameters.AddWithValue("@acz", DBNull.Value); cmd.Parameters.AddWithValue("@reqR", DBNull.Value); cmd.Parameters.AddWithValue("@actR", DBNull.Value); cmd.Parameters.AddWithValue("@angle", DBNull.Value); cmd.Parameters.AddWithValue("@arcLen", DBNull.Value); } else if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null) { // 圆弧段:保存圆弧轨迹数据 cmd.Parameters.AddWithValue("@tsx", edge.Trajectory.Ts.X); cmd.Parameters.AddWithValue("@tsy", edge.Trajectory.Ts.Y); cmd.Parameters.AddWithValue("@tsz", edge.Trajectory.Ts.Z); cmd.Parameters.AddWithValue("@tex", edge.Trajectory.Te.X); cmd.Parameters.AddWithValue("@tey", edge.Trajectory.Te.Y); cmd.Parameters.AddWithValue("@tez", edge.Trajectory.Te.Z); cmd.Parameters.AddWithValue("@acx", edge.Trajectory.ArcCenter.X); cmd.Parameters.AddWithValue("@acy", edge.Trajectory.ArcCenter.Y); cmd.Parameters.AddWithValue("@acz", edge.Trajectory.ArcCenter.Z); cmd.Parameters.AddWithValue("@reqR", edge.Trajectory.RequestedRadius); cmd.Parameters.AddWithValue("@actR", edge.Trajectory.ActualRadius); cmd.Parameters.AddWithValue("@angle", edge.Trajectory.DeflectionAngle); cmd.Parameters.AddWithValue("@arcLen", edge.Trajectory.ArcLength); } else { // 其他情况,所有参数为NULL cmd.Parameters.AddWithValue("@tsx", DBNull.Value); cmd.Parameters.AddWithValue("@tsy", DBNull.Value); cmd.Parameters.AddWithValue("@tsz", DBNull.Value); cmd.Parameters.AddWithValue("@tex", DBNull.Value); cmd.Parameters.AddWithValue("@tey", DBNull.Value); cmd.Parameters.AddWithValue("@tez", DBNull.Value); cmd.Parameters.AddWithValue("@acx", DBNull.Value); cmd.Parameters.AddWithValue("@acy", DBNull.Value); cmd.Parameters.AddWithValue("@acz", DBNull.Value); cmd.Parameters.AddWithValue("@reqR", DBNull.Value); cmd.Parameters.AddWithValue("@actR", DBNull.Value); cmd.Parameters.AddWithValue("@angle", DBNull.Value); cmd.Parameters.AddWithValue("@arcLen", DBNull.Value); } cmd.ExecuteNonQuery(); } } /// /// 删除路径点 /// private void DeletePathPoints(string routeId) { using (var cmd = new SQLiteCommand("DELETE FROM PathPoints WHERE RouteId = @routeId", _connection)) { cmd.Parameters.AddWithValue("@routeId", routeId); cmd.ExecuteNonQuery(); } } /// /// 删除路径边 /// private void DeletePathEdges(string routeId) { using (var cmd = new SQLiteCommand("DELETE FROM PathEdges WHERE RouteId = @routeId", _connection)) { cmd.Parameters.AddWithValue("@routeId", routeId); cmd.ExecuteNonQuery(); } } /// /// 加载路径边 /// private void LoadPathEdges(PathRoute route) { var sql = "SELECT * FROM PathEdges WHERE RouteId = @routeId ORDER BY SequenceNumber"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@routeId", route.Id); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var edge = new PathEdge { Id = reader["Id"].ToString(), StartPointId = reader["StartPointId"].ToString(), EndPointId = reader["EndPointId"].ToString(), SegmentType = (PathSegmentType)Convert.ToInt32(reader["SegmentType"]), PhysicalLength = Convert.ToDouble(reader["PhysicalLength"]) }; // 加载圆弧轨迹数据 if (edge.SegmentType == PathSegmentType.Arc) { edge.Trajectory = new ArcTrajectory { Ts = new Point3D( Convert.ToDouble(reader["Ts_X"]), Convert.ToDouble(reader["Ts_Y"]), Convert.ToDouble(reader["Ts_Z"])), Te = new Point3D( Convert.ToDouble(reader["Te_X"]), Convert.ToDouble(reader["Te_Y"]), Convert.ToDouble(reader["Te_Z"])), ArcCenter = new Point3D( Convert.ToDouble(reader["ArcCenter_X"]), Convert.ToDouble(reader["ArcCenter_Y"]), Convert.ToDouble(reader["ArcCenter_Z"])), RequestedRadius = Convert.ToDouble(reader["RequestedRadius"]), ActualRadius = Convert.ToDouble(reader["ActualRadius"]), DeflectionAngle = Convert.ToDouble(reader["DeflectionAngle"]), ArcLength = Convert.ToDouble(reader["ArcLength"]) }; } else if (edge.SegmentType == PathSegmentType.Straight) { // 直线段也加载Ts和Te(用于重新计算SampledPoints) if (!reader.IsDBNull(reader.GetOrdinal("Ts_X")) && !reader.IsDBNull(reader.GetOrdinal("Te_X"))) { edge.Trajectory = new ArcTrajectory { Ts = new Point3D( Convert.ToDouble(reader["Ts_X"]), Convert.ToDouble(reader["Ts_Y"]), Convert.ToDouble(reader["Ts_Z"])), Te = new Point3D( Convert.ToDouble(reader["Te_X"]), Convert.ToDouble(reader["Te_Y"]), Convert.ToDouble(reader["Te_Z"])), ArcCenter = new Point3D(0, 0, 0), // 直线段没有圆心 RequestedRadius = 0, ActualRadius = 0, DeflectionAngle = 0, ArcLength = 0 }; } } route.Edges.Add(edge); } } } // 注意:TotalLength 是计算属性,自动从 Edges/Points 实时计算,无需手动设置 // 重新计算SampledPoints(因为数据库中没有保存采样点) if (route.Edges.Count > 0) { double samplingStep = ConfigManager.Instance.Current.PathEditing.ArcSamplingStep; foreach (var edge in route.Edges) { if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null) { // 圆弧段:使用PathCurveEngine重新采样 edge.SampledPoints = PathCurveEngine.SampleArc(edge.Trajectory, samplingStep); } else if (edge.SegmentType == PathSegmentType.Straight) { // 直线段:从数据库加载的Ts和Te重新采样 Point3D startPoint = null; Point3D endPoint = null; // 尝试从Trajectory中获取(直线段也可能用Trajectory存储起止点) if (edge.Trajectory != null) { startPoint = edge.Trajectory.Ts; endPoint = edge.Trajectory.Te; } // 如果找到了起止点,重新采样 if (startPoint != null && endPoint != null) { int sampleCount = Math.Max(2, (int)Math.Ceiling(edge.PhysicalLength / samplingStep)); edge.SampledPoints = new List(); for (int i = 0; i <= sampleCount; i++) { double t = i / (double)sampleCount; Point3D sampledPoint = startPoint + (endPoint - startPoint) * t; edge.SampledPoints.Add(sampledPoint); } } } } } } #region 排除列表管理 /// /// 保存排除对象 /// public int SaveExcludedObject(ExcludedObjectRecord record) { try { // 检查是否已存在(根据PathId和RouteId) var checkSql = @" SELECT Id FROM ExcludedObjects WHERE PathId = @pathId AND (RouteId = @routeId OR (RouteId IS NULL AND @routeId IS NULL)) "; using (var checkCmd = new SQLiteCommand(checkSql, _connection)) { checkCmd.Parameters.AddWithValue("@pathId", record.PathId); checkCmd.Parameters.AddWithValue("@routeId", string.IsNullOrEmpty(record.RouteId) ? (object)DBNull.Value : record.RouteId); var existingId = checkCmd.ExecuteScalar(); if (existingId != null) { // 已存在,更新记录 var updateSql = @" UPDATE ExcludedObjects SET DisplayName = @displayName, ObjectName = @objectName, Reason = @reason, ExcludedTime = @excludedTime WHERE Id = @id "; using (var updateCmd = new SQLiteCommand(updateSql, _connection)) { updateCmd.Parameters.AddWithValue("@id", Convert.ToInt32(existingId)); updateCmd.Parameters.AddWithValue("@displayName", record.DisplayName ?? ""); updateCmd.Parameters.AddWithValue("@objectName", record.ObjectName ?? ""); updateCmd.Parameters.AddWithValue("@reason", record.Reason ?? ""); updateCmd.Parameters.AddWithValue("@excludedTime", record.ExcludedTime); updateCmd.ExecuteNonQuery(); } return Convert.ToInt32(existingId); } } // 插入新记录 var sql = @" INSERT INTO ExcludedObjects (RouteId, ModelIndex, PathId, DisplayName, ObjectName, ExcludedTime, Reason, IsGlobal) VALUES (@routeId, @modelIndex, @pathId, @displayName, @objectName, @excludedTime, @reason, @isGlobal) "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@routeId", string.IsNullOrEmpty(record.RouteId) ? (object)DBNull.Value : record.RouteId); cmd.Parameters.AddWithValue("@modelIndex", record.ModelIndex); cmd.Parameters.AddWithValue("@pathId", record.PathId); cmd.Parameters.AddWithValue("@displayName", record.DisplayName ?? ""); cmd.Parameters.AddWithValue("@objectName", record.ObjectName ?? ""); cmd.Parameters.AddWithValue("@excludedTime", record.ExcludedTime); cmd.Parameters.AddWithValue("@reason", record.Reason ?? ""); cmd.Parameters.AddWithValue("@isGlobal", record.IsGlobal ? 1 : 0); cmd.ExecuteNonQuery(); } int newId = (int)_connection.LastInsertRowId; LogManager.Debug($"排除对象已保存: Id={newId}, PathId={record.PathId}, Name={record.DisplayName}"); return newId; } catch (Exception ex) { LogManager.Error($"保存排除对象失败: {ex.Message}", ex); throw; } } /// /// 批量保存排除对象 /// public void SaveExcludedObjects(List records) { if (records == null || records.Count == 0) return; try { using (var transaction = _connection.BeginTransaction()) { foreach (var record in records) { SaveExcludedObject(record); } transaction.Commit(); } LogManager.Info($"批量保存 {records.Count} 个排除对象完成"); } catch (Exception ex) { LogManager.Error($"批量保存排除对象失败: {ex.Message}", ex); throw; } } /// /// 获取路径的排除对象列表 /// public List GetExcludedObjects(string routeId = null, bool includeGlobal = true) { var results = new List(); try { string sql; if (string.IsNullOrEmpty(routeId)) { // 获取全局排除对象 sql = "SELECT * FROM ExcludedObjects WHERE IsGlobal = 1 ORDER BY ExcludedTime DESC"; } else if (includeGlobal) { // 获取指定路径的排除对象 + 全局排除对象 sql = @" SELECT * FROM ExcludedObjects WHERE RouteId = @routeId OR IsGlobal = 1 ORDER BY ExcludedTime DESC "; } else { // 仅获取指定路径的排除对象 sql = "SELECT * FROM ExcludedObjects WHERE RouteId = @routeId ORDER BY ExcludedTime DESC"; } using (var cmd = new SQLiteCommand(sql, _connection)) { if (!string.IsNullOrEmpty(routeId)) { cmd.Parameters.AddWithValue("@routeId", routeId); } using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { results.Add(new ExcludedObjectRecord { Id = Convert.ToInt32(reader["Id"]), RouteId = reader["RouteId"]?.ToString(), ModelIndex = Convert.ToInt32(reader["ModelIndex"]), PathId = reader["PathId"].ToString(), DisplayName = reader["DisplayName"]?.ToString(), ObjectName = reader["ObjectName"]?.ToString(), ExcludedTime = Convert.ToDateTime(reader["ExcludedTime"]), Reason = reader["Reason"]?.ToString(), IsGlobal = Convert.ToInt32(reader["IsGlobal"]) == 1 }); } } } LogManager.Debug($"获取到 {results.Count} 个排除对象"); } catch (Exception ex) { LogManager.Error($"获取排除对象失败: {ex.Message}", ex); } return results; } /// /// 删除排除对象 /// public void DeleteExcludedObject(int id) { try { var sql = "DELETE FROM ExcludedObjects WHERE Id = @id"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", id); int affected = cmd.ExecuteNonQuery(); LogManager.Debug($"删除排除对象: Id={id}, 影响行数={affected}"); } } catch (Exception ex) { LogManager.Error($"删除排除对象失败: {ex.Message}", ex); throw; } } /// /// 根据PathId删除排除对象 /// public void DeleteExcludedObjectByPathId(string pathId, string routeId = null) { try { string sql; if (string.IsNullOrEmpty(routeId)) { sql = "DELETE FROM ExcludedObjects WHERE PathId = @pathId"; } else { sql = "DELETE FROM ExcludedObjects WHERE PathId = @pathId AND RouteId = @routeId"; } using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@pathId", pathId); if (!string.IsNullOrEmpty(routeId)) { cmd.Parameters.AddWithValue("@routeId", routeId); } int affected = cmd.ExecuteNonQuery(); LogManager.Debug($"删除排除对象: PathId={pathId}, RouteId={routeId}, 影响行数={affected}"); } } catch (Exception ex) { LogManager.Error($"删除排除对象失败: {ex.Message}", ex); throw; } } /// /// 清除路径的所有排除对象 /// public void ClearExcludedObjects(string routeId = null) { try { string sql; if (string.IsNullOrEmpty(routeId)) { sql = "DELETE FROM ExcludedObjects"; } else { sql = "DELETE FROM ExcludedObjects WHERE RouteId = @routeId"; } using (var cmd = new SQLiteCommand(sql, _connection)) { if (!string.IsNullOrEmpty(routeId)) { cmd.Parameters.AddWithValue("@routeId", routeId); } int affected = cmd.ExecuteNonQuery(); LogManager.Info($"清除排除对象: RouteId={routeId}, 影响行数={affected}"); } } catch (Exception ex) { LogManager.Error($"清除排除对象失败: {ex.Message}", ex); throw; } } /// /// 关联排除对象到碰撞报告 /// public void LinkExcludedObjectsToCollisionReport(int reportId, List excludedObjectIds) { if (excludedObjectIds == null || excludedObjectIds.Count == 0) return; try { using (var transaction = _connection.BeginTransaction()) { var sql = @" INSERT OR IGNORE INTO CollisionReportExcludedObjects (ReportId, ExcludedObjectId) VALUES (@reportId, @excludedObjectId) "; foreach (var objectId in excludedObjectIds) { using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@reportId", reportId); cmd.Parameters.AddWithValue("@excludedObjectId", objectId); cmd.ExecuteNonQuery(); } } transaction.Commit(); LogManager.Debug($"关联 {excludedObjectIds.Count} 个排除对象到碰撞报告: ReportId={reportId}"); } } catch (Exception ex) { LogManager.Error($"关联排除对象到碰撞报告失败: {ex.Message}", ex); throw; } } /// /// 获取碰撞报告的排除对象 /// public List GetExcludedObjectsForCollisionReport(int reportId) { var results = new List(); try { var sql = @" SELECT eo.* FROM ExcludedObjects eo INNER JOIN CollisionReportExcludedObjects creo ON eo.Id = creo.ExcludedObjectId WHERE creo.ReportId = @reportId ORDER BY eo.ExcludedTime DESC "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@reportId", reportId); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { results.Add(new ExcludedObjectRecord { Id = Convert.ToInt32(reader["Id"]), RouteId = reader["RouteId"]?.ToString(), ModelIndex = Convert.ToInt32(reader["ModelIndex"]), PathId = reader["PathId"].ToString(), DisplayName = reader["DisplayName"]?.ToString(), ObjectName = reader["ObjectName"]?.ToString(), ExcludedTime = Convert.ToDateTime(reader["ExcludedTime"]), Reason = reader["Reason"]?.ToString(), IsGlobal = Convert.ToInt32(reader["IsGlobal"]) == 1 }); } } } } catch (Exception ex) { LogManager.Error($"获取碰撞报告的排除对象失败: {ex.Message}", ex); } return results; } /// /// 关联排除对象到碰撞检测记录 /// public void LinkExcludedObjectsToDetectionRecord(int detectionRecordId, List excludedObjectIds) { if (excludedObjectIds == null || excludedObjectIds.Count == 0) return; try { using (var transaction = _connection.BeginTransaction()) { var sql = @" INSERT OR IGNORE INTO ClashDetectiveExcludedObjects (DetectionRecordId, ExcludedObjectId) VALUES (@detectionRecordId, @excludedObjectId) "; foreach (var objectId in excludedObjectIds) { using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId); cmd.Parameters.AddWithValue("@excludedObjectId", objectId); cmd.ExecuteNonQuery(); } } transaction.Commit(); LogManager.Debug($"关联 {excludedObjectIds.Count} 个排除对象到检测记录: DetectionRecordId={detectionRecordId}"); } } catch (Exception ex) { LogManager.Error($"关联排除对象到检测记录失败: {ex.Message}", ex); throw; } } /// /// 获取检测记录的排除对象 /// public List GetExcludedObjectsForDetectionRecord(int detectionRecordId) { var results = new List(); try { var sql = @" SELECT eo.* FROM ExcludedObjects eo INNER JOIN ClashDetectiveExcludedObjects cdeo ON eo.Id = cdeo.ExcludedObjectId WHERE cdeo.DetectionRecordId = @detectionRecordId ORDER BY eo.ExcludedTime DESC "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { results.Add(new ExcludedObjectRecord { Id = Convert.ToInt32(reader["Id"]), RouteId = reader["RouteId"]?.ToString(), ModelIndex = Convert.ToInt32(reader["ModelIndex"]), PathId = reader["PathId"].ToString(), DisplayName = reader["DisplayName"]?.ToString(), ObjectName = reader["ObjectName"]?.ToString(), ExcludedTime = Convert.ToDateTime(reader["ExcludedTime"]), Reason = reader["Reason"]?.ToString(), IsGlobal = Convert.ToInt32(reader["IsGlobal"]) == 1 }); } } } } catch (Exception ex) { LogManager.Error($"获取ClashDetective结果的排除对象失败: {ex.Message}", ex); } return results; } #endregion #region 碰撞检测记录 /// /// 保存碰撞检测记录 /// 每次点击"生成动画"创建一条记录 /// public int SaveCollisionDetectionRecord(CollisionDetectionRecord record) { try { var sql = @" INSERT INTO CollisionDetectionRecords ( RouteId, CreatedTime, FrameRate, DurationSeconds, DetectionTolerance, IsVirtualObject, AnimatedObjectName, ObjectModelIndex, ObjectPathId, VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight, DetectAllObjects, ObjectRotationCorrection, Description, TestName, TestTime, CollisionCount, AnimationCollisionCount ) VALUES ( @RouteId, @CreatedTime, @FrameRate, @DurationSeconds, @DetectionTolerance, @IsVirtualObject, @AnimatedObjectName, @ObjectModelIndex, @ObjectPathId, @VirtualObjectLength, @VirtualObjectWidth, @VirtualObjectHeight, @DetectAllObjects, @ObjectRotationCorrection, @Description, @TestName, @TestTime, @CollisionCount, @AnimationCollisionCount ); SELECT last_insert_rowid(); "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@RouteId", record.RouteId); cmd.Parameters.AddWithValue("@CreatedTime", record.CreatedTime); cmd.Parameters.AddWithValue("@FrameRate", record.FrameRate); cmd.Parameters.AddWithValue("@DurationSeconds", record.DurationSeconds); // 内部使用模型单位,写入数据库时转换为米单位 cmd.Parameters.AddWithValue("@DetectionTolerance", record.DetectionTolerance); // 模型单位,不转换 cmd.Parameters.AddWithValue("@IsVirtualObject", record.IsVirtualObject ? 1 : 0); cmd.Parameters.AddWithValue("@AnimatedObjectName", record.AnimatedObjectName ?? ""); cmd.Parameters.AddWithValue("@ObjectModelIndex", record.ObjectModelIndex ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@ObjectPathId", record.ObjectPathId ?? ""); cmd.Parameters.AddWithValue("@VirtualObjectLength", record.VirtualObjectLength ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@VirtualObjectWidth", record.VirtualObjectWidth ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@VirtualObjectHeight", record.VirtualObjectHeight ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@DetectAllObjects", record.DetectAllObjects ? 1 : 0); cmd.Parameters.AddWithValue("@ObjectRotationCorrection", record.ObjectRotationCorrection); cmd.Parameters.AddWithValue("@Description", record.Description ?? ""); cmd.Parameters.AddWithValue("@TestName", record.TestName ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@TestTime", record.TestTime ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@CollisionCount", record.CollisionCount); cmd.Parameters.AddWithValue("@AnimationCollisionCount", record.AnimationCollisionCount); var id = Convert.ToInt32(cmd.ExecuteScalar()); LogManager.Info($"保存碰撞检测记录: Id={id}, RouteId={record.RouteId}"); return id; } } catch (Exception ex) { LogManager.Error($"保存碰撞检测记录失败: {ex.Message}", ex); throw; } } /// /// 更新碰撞检测结果到检测记录 /// 在ClashDetective测试完成后调用,保存测试结果 /// public void UpdateCollisionDetectionResult(int detectionRecordId, string testName, int collisionCount, int animationCollisionCount) { try { var sql = @" UPDATE CollisionDetectionRecords SET TestName = @TestName, TestTime = @TestTime, CollisionCount = @CollisionCount, AnimationCollisionCount = @AnimationCollisionCount WHERE Id = @Id "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@Id", detectionRecordId); cmd.Parameters.AddWithValue("@TestName", testName ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@TestTime", DateTime.Now); cmd.Parameters.AddWithValue("@CollisionCount", collisionCount); cmd.Parameters.AddWithValue("@AnimationCollisionCount", animationCollisionCount); cmd.ExecuteNonQuery(); } LogManager.Info($"更新碰撞检测结果: DetectionRecordId={detectionRecordId}, TestName={testName}, CollisionCount={collisionCount}"); } catch (Exception ex) { LogManager.Error($"更新碰撞检测结果失败: {ex.Message}", ex); throw; } } /// /// 保存碰撞检测记录的排除对象 /// public void SaveCollisionDetectionExcludedObjects(int detectionRecordId, List objects) { if (objects == null || objects.Count == 0) return; try { using (var transaction = _connection.BeginTransaction()) { var sql = @" INSERT INTO CollisionDetectionExcludedObjects (DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName, Reason) VALUES (@DetectionRecordId, @ModelIndex, @PathId, @DisplayName, @ObjectName, @Reason) "; foreach (var obj in objects) { using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@DetectionRecordId", detectionRecordId); cmd.Parameters.AddWithValue("@ModelIndex", obj.ModelIndex); cmd.Parameters.AddWithValue("@PathId", obj.PathId); cmd.Parameters.AddWithValue("@DisplayName", obj.DisplayName ?? ""); cmd.Parameters.AddWithValue("@ObjectName", obj.ObjectName ?? ""); cmd.Parameters.AddWithValue("@Reason", obj.Reason ?? ""); cmd.ExecuteNonQuery(); } } transaction.Commit(); LogManager.Info($"保存检测记录排除对象: DetectionRecordId={detectionRecordId}, 数量={objects.Count}"); } } catch (Exception ex) { LogManager.Error($"保存检测记录排除对象失败: {ex.Message}", ex); throw; } } /// /// 保存碰撞检测记录的手工目标 /// public void SaveCollisionDetectionManualTargets(int detectionRecordId, List targets) { if (targets == null || targets.Count == 0) return; try { using (var transaction = _connection.BeginTransaction()) { var sql = @" INSERT INTO CollisionDetectionManualTargets (DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName) VALUES (@DetectionRecordId, @ModelIndex, @PathId, @DisplayName, @ObjectName) "; foreach (var target in targets) { using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@DetectionRecordId", detectionRecordId); cmd.Parameters.AddWithValue("@ModelIndex", target.ModelIndex); cmd.Parameters.AddWithValue("@PathId", target.PathId); cmd.Parameters.AddWithValue("@DisplayName", target.DisplayName ?? ""); cmd.Parameters.AddWithValue("@ObjectName", target.ObjectName ?? ""); cmd.ExecuteNonQuery(); } } transaction.Commit(); LogManager.Info($"保存检测记录手工目标: DetectionRecordId={detectionRecordId}, 数量={targets.Count}"); } } catch (Exception ex) { LogManager.Error($"保存检测记录手工目标失败: {ex.Message}", ex); throw; } } /// /// 获取碰撞检测记录 /// public CollisionDetectionRecord GetCollisionDetectionRecord(int id) { try { var sql = @" SELECT * FROM CollisionDetectionRecords WHERE Id = @Id "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@Id", id); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { return ReadCollisionDetectionRecordFromReader(reader); } } } } catch (Exception ex) { LogManager.Error($"获取碰撞检测记录失败: {ex.Message}", ex); } return null; } /// /// 获取路径的最新碰撞检测记录 /// public CollisionDetectionRecord GetLatestCollisionDetectionRecord(string routeId) { try { var sql = @" SELECT * FROM CollisionDetectionRecords WHERE RouteId = @RouteId ORDER BY CreatedTime DESC LIMIT 1 "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@RouteId", routeId); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { return ReadCollisionDetectionRecordFromReader(reader); } } } } catch (Exception ex) { LogManager.Error($"获取最新碰撞检测记录失败: {ex.Message}", ex); } return null; } /// /// 获取碰撞检测记录的排除对象 /// public List GetCollisionDetectionExcludedObjects(int detectionRecordId) { var results = new List(); try { var sql = @" SELECT * FROM CollisionDetectionExcludedObjects WHERE DetectionRecordId = @DetectionRecordId "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@DetectionRecordId", detectionRecordId); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { results.Add(new CollisionDetectionExcludedObjectRecord { Id = Convert.ToInt32(reader["Id"]), DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]), ModelIndex = Convert.ToInt32(reader["ModelIndex"]), PathId = reader["PathId"].ToString(), DisplayName = reader["DisplayName"]?.ToString(), ObjectName = reader["ObjectName"]?.ToString(), Reason = reader["Reason"]?.ToString() }); } } } } catch (Exception ex) { LogManager.Error($"获取检测记录排除对象失败: {ex.Message}", ex); } return results; } /// /// 获取碰撞检测记录的手工目标 /// public List GetCollisionDetectionManualTargets(int detectionRecordId) { var results = new List(); try { var sql = @" SELECT * FROM CollisionDetectionManualTargets WHERE DetectionRecordId = @DetectionRecordId "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@DetectionRecordId", detectionRecordId); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { results.Add(new CollisionDetectionManualTargetRecord { Id = Convert.ToInt32(reader["Id"]), DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]), ModelIndex = Convert.ToInt32(reader["ModelIndex"]), PathId = reader["PathId"].ToString(), DisplayName = reader["DisplayName"]?.ToString(), ObjectName = reader["ObjectName"]?.ToString() }); } } } } catch (Exception ex) { LogManager.Error($"获取检测记录手工目标失败: {ex.Message}", ex); } return results; } /// /// 从DataReader读取碰撞检测记录 /// private CollisionDetectionRecord ReadCollisionDetectionRecordFromReader(SQLiteDataReader reader) { return new CollisionDetectionRecord { Id = Convert.ToInt32(reader["Id"]), RouteId = reader["RouteId"].ToString(), CreatedTime = Convert.ToDateTime(reader["CreatedTime"]), FrameRate = Convert.ToInt32(reader["FrameRate"]), DurationSeconds = Convert.ToDouble(reader["DurationSeconds"]), // 数据库中存储米单位,读取时转换为模型单位 DetectionTolerance = Convert.ToDouble(reader["DetectionTolerance"]), // 模型单位,不转换 IsVirtualObject = Convert.ToInt32(reader["IsVirtualObject"]) == 1, AnimatedObjectName = reader["AnimatedObjectName"]?.ToString(), ObjectModelIndex = reader["ObjectModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ObjectModelIndex"]) : (int?)null, ObjectPathId = reader["ObjectPathId"]?.ToString(), VirtualObjectLength = reader["VirtualObjectLength"] != DBNull.Value ? Convert.ToDouble(reader["VirtualObjectLength"]) : (double?)null, VirtualObjectWidth = reader["VirtualObjectWidth"] != DBNull.Value ? Convert.ToDouble(reader["VirtualObjectWidth"]) : (double?)null, VirtualObjectHeight = reader["VirtualObjectHeight"] != DBNull.Value ? Convert.ToDouble(reader["VirtualObjectHeight"]) : (double?)null, DetectAllObjects = Convert.ToInt32(reader["DetectAllObjects"]) == 1, ObjectRotationCorrection = Convert.ToDouble(reader["ObjectRotationCorrection"]), Description = reader["Description"]?.ToString(), // 碰撞结果字段(原 ClashDetectiveResults 表字段) TestName = reader["TestName"]?.ToString(), TestTime = reader["TestTime"] != DBNull.Value ? (DateTime?)Convert.ToDateTime(reader["TestTime"]) : null, CollisionCount = reader["CollisionCount"] != DBNull.Value ? Convert.ToInt32(reader["CollisionCount"]) : 0, AnimationCollisionCount = reader["AnimationCollisionCount"] != DBNull.Value ? Convert.ToInt32(reader["AnimationCollisionCount"]) : 0 }; } #endregion /// /// 执行非查询SQL语句 /// private void ExecuteNonQuery(string sql, object parameters = null) { using (var cmd = new SQLiteCommand(sql, _connection)) { if (parameters != null) { var props = parameters.GetType().GetProperties(); foreach (var prop in props) { cmd.Parameters.AddWithValue($"@{prop.Name}", prop.GetValue(parameters)); } } cmd.ExecuteNonQuery(); } } /// /// 获取ModelItem的显示名称 /// private string GetItemDisplayName(ModelItem item) { if (item == null) return "未知对象"; try { return item.DisplayName ?? item.ToString(); } catch { return "未知对象"; } } #region 批处理任务管理 /// /// 根据测试名称获取碰撞检测记录(异步) /// public async Task GetDetectionResultByTestNameAsync(string testName) { return await Task.Run(() => { using (var cmd = new SQLiteCommand(_connection)) { cmd.CommandText = @" SELECT cdr.*, pr.Name AS PathName FROM CollisionDetectionRecords cdr INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id WHERE cdr.TestName = @TestName"; cmd.Parameters.AddWithValue("@TestName", testName); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { return ReadCollisionDetectionRecordFromReader(reader); } return null; } } }); } /// /// 异步获取路径信息 /// public async Task GetPathRouteAsync(string routeId) { return await Task.Run(() => { return GetPathRouteSync(routeId); }); } /// /// 同步获取路径信息(用于需要同步调用的场景) /// public PathRoute GetPathRouteSync(string routeId) { using (var cmd = new SQLiteCommand(_connection)) { // 注意:TotalLength 字段已从数据库中删除 cmd.CommandText = @" SELECT Id, Name, CreatedTime, LastModified, TurnRadius, IsCurved, MaxObjectLength, MaxObjectWidth, MaxObjectHeight, SafetyMargin, GridSize, PathType, LiftHeight, RailMountMode, RailPathDefinitionMode, RailNormalOffset, RailPreferredNormalX, RailPreferredNormalY, RailPreferredNormalZ FROM PathRoutes WHERE Id = @Id"; cmd.Parameters.AddWithValue("@Id", routeId); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { var route = new PathRoute(reader["Name"].ToString()) { Id = reader["Id"].ToString(), CreatedTime = DateTime.Parse(reader["CreatedTime"].ToString()), LastModified = DateTime.Parse(reader["LastModified"].ToString()), // TotalLength:由 PathRoute.TotalLength 计算属性实时提供 TurnRadius = Convert.ToDouble(reader["TurnRadius"]), IsCurved = Convert.ToInt32(reader["IsCurved"]) == 1, MaxObjectLength = Convert.ToDouble(reader["MaxObjectLength"]), MaxObjectWidth = Convert.ToDouble(reader["MaxObjectWidth"]), MaxObjectHeight = Convert.ToDouble(reader["MaxObjectHeight"]), SafetyMargin = Convert.ToDouble(reader["SafetyMargin"]), GridSize = Convert.ToDouble(reader["GridSize"]), PathType = (PathType)Convert.ToInt32(reader["PathType"]), LiftHeight = Convert.ToDouble(reader["LiftHeight"]), RailMountMode = (RailMountMode)Convert.ToInt32(reader["RailMountMode"]), RailPathDefinitionMode = (RailPathDefinitionMode)Convert.ToInt32(reader["RailPathDefinitionMode"]), RailNormalOffset = reader["RailNormalOffset"] != DBNull.Value ? Convert.ToDouble(reader["RailNormalOffset"]) : 0.0 }; if (reader["RailPreferredNormalX"] != DBNull.Value && reader["RailPreferredNormalY"] != DBNull.Value && reader["RailPreferredNormalZ"] != DBNull.Value) { route.RailPreferredNormal = new Point3D( Convert.ToDouble(reader["RailPreferredNormalX"]), Convert.ToDouble(reader["RailPreferredNormalY"]), Convert.ToDouble(reader["RailPreferredNormalZ"])); } // 加载路径点和路径边 LoadPathPoints(route); LoadPathEdges(route); return route; } } } return null; } #endregion /// /// 检查并扩展 PathRoutes 表结构 /// public void EnsurePathRoutesTableExtended() { try { var checkSql = "PRAGMA table_info(PathRoutes)"; var existingColumns = new HashSet(); using (var cmd = new SQLiteCommand(checkSql, _connection)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { existingColumns.Add(reader["name"].ToString().ToLower()); } } var columnsToAdd = new Dictionary { ["railmountmode"] = "ALTER TABLE PathRoutes ADD COLUMN RailMountMode INTEGER DEFAULT 0", ["railpathdefinitionmode"] = "ALTER TABLE PathRoutes ADD COLUMN RailPathDefinitionMode INTEGER DEFAULT 0", ["railnormaloffset"] = "ALTER TABLE PathRoutes ADD COLUMN RailNormalOffset REAL DEFAULT 0", ["railpreferrednormalx"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalX REAL", ["railpreferrednormaly"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalY REAL", ["railpreferrednormalz"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalZ REAL" }; foreach (var column in columnsToAdd) { if (!existingColumns.Contains(column.Key)) { ExecuteNonQuery(column.Value); LogManager.Info($"扩展 PathRoutes 表: 添加字段 {column.Key}"); } } } catch (Exception ex) { LogManager.Error($"扩展 PathRoutes 表失败: {ex.Message}", ex); } } #region 批处理队列管理(简化版) /// /// 创建批处理队列项 /// public async Task CreateBatchQueueItemAsync(BatchQueueItem item) { return await Task.Run(() => { using (var cmd = new SQLiteCommand(_connection)) { cmd.CommandText = @" INSERT INTO BatchQueueItems ( RouteId, Status, CreatedTime, StartTime, EndTime, ErrorMessage, FrameRate, DurationSeconds, DetectionTolerance, IsVirtualObject, VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight, DetectAllObjects, ClashDetectiveTestName, CollisionCount, ObjectRotationCorrection, DetectionRecordId ) VALUES ( @RouteId, @Status, @CreatedTime, @StartTime, @EndTime, @ErrorMessage, @FrameRate, @DurationSeconds, @DetectionTolerance, @IsVirtualObject, @VirtualObjectLength, @VirtualObjectWidth, @VirtualObjectHeight, @DetectAllObjects, @ClashDetectiveTestName, @CollisionCount, @ObjectRotationCorrection, @DetectionRecordId ); SELECT last_insert_rowid();"; cmd.Parameters.AddWithValue("@RouteId", item.RouteId); cmd.Parameters.AddWithValue("@Status", item.Status.ToString()); cmd.Parameters.AddWithValue("@CreatedTime", item.CreatedTime.ToString("o")); cmd.Parameters.AddWithValue("@StartTime", item.StartTime?.ToString("o") ?? ""); cmd.Parameters.AddWithValue("@EndTime", item.EndTime?.ToString("o") ?? ""); cmd.Parameters.AddWithValue("@ErrorMessage", item.ErrorMessage ?? ""); cmd.Parameters.AddWithValue("@FrameRate", item.FrameRate); cmd.Parameters.AddWithValue("@DurationSeconds", item.DurationSeconds); // 内部使用模型单位,写入数据库时转换为米单位 cmd.Parameters.AddWithValue("@DetectionTolerance", item.DetectionTolerance); // 模型单位,不转换 cmd.Parameters.AddWithValue("@IsVirtualObject", item.IsVirtualObject ? 1 : 0); cmd.Parameters.AddWithValue("@VirtualObjectLength", item.VirtualObjectLength); cmd.Parameters.AddWithValue("@VirtualObjectWidth", item.VirtualObjectWidth); cmd.Parameters.AddWithValue("@VirtualObjectHeight", item.VirtualObjectHeight); cmd.Parameters.AddWithValue("@DetectAllObjects", item.DetectAllObjects ? 1 : 0); cmd.Parameters.AddWithValue("@ClashDetectiveTestName", item.ClashDetectiveTestName ?? ""); cmd.Parameters.AddWithValue("@CollisionCount", item.CollisionCount ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@ObjectRotationCorrection", item.ObjectRotationCorrection); cmd.Parameters.AddWithValue("@DetectionRecordId", item.DetectionRecordId ?? (object)DBNull.Value); var queueItemId = Convert.ToInt32(cmd.ExecuteScalar()); // 注意:运动物体和碰撞检测目标都存储在 ModelItemReferences 表中 // 这些数据在调用 CreateBatchQueueItemAsync 之前插入 // Role = "MovingObject" 表示运动物体 // Role = "CollisionTarget" 表示碰撞检测目标 return queueItemId; } }); } /// /// 从 SQLiteDataReader 读取 BatchQueueItem 对象 /// private BatchQueueItem ReadBatchQueueItemFromReader(SQLiteDataReader reader) { var itemId = Convert.ToInt32(reader["Id"]); var isVirtualObject = Convert.ToBoolean(reader["IsVirtualObject"]); var item = new BatchQueueItem { Id = itemId, RouteId = reader["RouteId"].ToString(), PathRouteName = reader["PathRouteName"].ToString(), PathType = (PathType)Convert.ToInt32(reader["PathType"]), Status = (BatchQueueStatus)Enum.Parse(typeof(BatchQueueStatus), reader["Status"].ToString()), CreatedTime = DateTime.Parse(reader["CreatedTime"].ToString()), StartTime = reader["StartTime"].ToString() != "" ? (DateTime?)DateTime.Parse(reader["StartTime"].ToString()) : null, EndTime = reader["EndTime"].ToString() != "" ? (DateTime?)DateTime.Parse(reader["EndTime"].ToString()) : null, ErrorMessage = reader["ErrorMessage"].ToString(), FrameRate = Convert.ToInt32(reader["FrameRate"]), DurationSeconds = Convert.ToDouble(reader["DurationSeconds"]), // 数据库中存储米单位,读取时转换为模型单位 DetectionTolerance = Convert.ToDouble(reader["DetectionTolerance"]), // 模型单位,不转换 IsVirtualObject = isVirtualObject, VirtualObjectLength = Convert.ToDouble(reader["VirtualObjectLength"]), VirtualObjectWidth = Convert.ToDouble(reader["VirtualObjectWidth"]), VirtualObjectHeight = Convert.ToDouble(reader["VirtualObjectHeight"]), DetectAllObjects = Convert.ToBoolean(reader["DetectAllObjects"]), ClashDetectiveTestName = reader["ClashDetectiveTestName"].ToString(), CollisionCount = !Convert.IsDBNull(reader["CollisionCount"]) ? (int?)Convert.ToInt32(reader["CollisionCount"]) : null, ObjectRotationCorrection = Convert.ToDouble(reader["ObjectRotationCorrection"]), DetectionRecordId = !Convert.IsDBNull(reader["DetectionRecordId"]) ? (int?)Convert.ToInt32(reader["DetectionRecordId"]) : null }; // 填充 MovingObjectName 属性 if (!isVirtualObject) { // 从 ModelItemReferences 表查询运动物体名称 var movingObjectReferences = GetModelItemReferencesSync(itemId, "BatchQueueItem", "MovingObject"); if (movingObjectReferences.Count > 0) { item.MovingObjectName = movingObjectReferences[0].Item3; // Item3 是 DisplayName } } // 虚拟物体时,MovingObjectName 保持 null return item; } /// /// 获取批处理队列项列表 /// public async Task> GetBatchQueueItemsAsync(BatchQueueStatus statusFilter = BatchQueueStatus.All, int limit = 50, int offset = 0) { return await Task.Run(() => { var items = new List(); using (var cmd = new SQLiteCommand(_connection)) { var sql = @" SELECT bqi.Id, bqi.RouteId, pr.Name AS PathRouteName, pr.PathType, bqi.Status, bqi.CreatedTime, bqi.StartTime, bqi.EndTime, bqi.ErrorMessage, bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionTolerance, bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight, bqi.DetectAllObjects, bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection, bqi.DetectionRecordId FROM BatchQueueItems bqi INNER JOIN PathRoutes pr ON bqi.RouteId = pr.Id"; var conditions = new List(); // 只有当 statusFilter 不是 All 时才添加筛选条件 if (statusFilter != BatchQueueStatus.All) { conditions.Add("bqi.Status = @Status"); cmd.Parameters.AddWithValue("@Status", statusFilter.ToString()); } if (conditions.Count > 0) { sql += " WHERE " + string.Join(" AND ", conditions); } sql += " ORDER BY bqi.CreatedTime DESC"; if (limit > 0) { sql += " LIMIT @Limit"; cmd.Parameters.AddWithValue("@Limit", limit); } if (offset > 0) { sql += " OFFSET @Offset"; cmd.Parameters.AddWithValue("@Offset", offset); } cmd.CommandText = sql; using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { items.Add(ReadBatchQueueItemFromReader(reader)); } } } return items; }); } /// /// 获取批处理队列项 /// public async Task GetBatchQueueItemAsync(int itemId) { return await Task.Run(() => { using (var cmd = new SQLiteCommand(_connection)) { cmd.CommandText = @" SELECT bqi.Id, bqi.RouteId, pr.Name AS PathRouteName, pr.PathType, bqi.Status, bqi.CreatedTime, bqi.StartTime, bqi.EndTime, bqi.ErrorMessage, bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionTolerance, bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight, bqi.DetectAllObjects, bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection, bqi.DetectionRecordId FROM BatchQueueItems bqi INNER JOIN PathRoutes pr ON bqi.RouteId = pr.Id WHERE bqi.Id = @Id"; cmd.Parameters.AddWithValue("@Id", itemId); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { return ReadBatchQueueItemFromReader(reader); } } } return null; }); } /// /// 更新批处理队列项 /// public async Task UpdateBatchQueueItemAsync(BatchQueueItem item) { await Task.Run(() => { using (var cmd = new SQLiteCommand(_connection)) { cmd.CommandText = @" UPDATE BatchQueueItems SET Status = @Status, StartTime = @StartTime, EndTime = @EndTime, ErrorMessage = @ErrorMessage, ClashDetectiveTestName = @ClashDetectiveTestName, CollisionCount = @CollisionCount WHERE Id = @Id"; cmd.Parameters.AddWithValue("@Status", item.Status.ToString()); cmd.Parameters.AddWithValue("@StartTime", item.StartTime?.ToString("o") ?? ""); cmd.Parameters.AddWithValue("@EndTime", item.EndTime?.ToString("o") ?? ""); cmd.Parameters.AddWithValue("@ErrorMessage", item.ErrorMessage ?? ""); cmd.Parameters.AddWithValue("@ClashDetectiveTestName", item.ClashDetectiveTestName ?? ""); cmd.Parameters.AddWithValue("@CollisionCount", item.CollisionCount ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@Id", item.Id); cmd.ExecuteNonQuery(); } }); } /// /// 删除批处理队列项 /// public async Task DeleteBatchQueueItemAsync(int itemId) { await Task.Run(() => { using (var cmd = new SQLiteCommand(_connection)) { cmd.CommandText = "DELETE FROM BatchQueueItems WHERE Id = @Id"; cmd.Parameters.AddWithValue("@Id", itemId); cmd.ExecuteNonQuery(); } }); } /// /// 添加 ModelItem 引用到 ModelItemReferences 表 /// public async Task AddModelItemReferenceAsync(int referenceId, string referenceType, int modelIndex, string pathId, string displayName, string objectName, string role) { return await Task.Run(() => { using (var cmd = new SQLiteCommand(_connection)) { cmd.CommandText = @" INSERT INTO ModelItemReferences (ReferenceId, ReferenceType, ModelIndex, PathId, DisplayName, ObjectName, Role) VALUES (@ReferenceId, @ReferenceType, @ModelIndex, @PathId, @DisplayName, @ObjectName, @Role); SELECT last_insert_rowid();"; cmd.Parameters.AddWithValue("@ReferenceId", referenceId); cmd.Parameters.AddWithValue("@ReferenceType", referenceType); cmd.Parameters.AddWithValue("@ModelIndex", modelIndex); cmd.Parameters.AddWithValue("@PathId", pathId); cmd.Parameters.AddWithValue("@DisplayName", displayName ?? ""); cmd.Parameters.AddWithValue("@ObjectName", objectName ?? ""); cmd.Parameters.AddWithValue("@Role", role); return Convert.ToInt32(cmd.ExecuteScalar()); } }); } /// /// 获取指定 ReferenceId 和 Role 的所有 ModelItem 引用 /// public async Task> GetModelItemReferencesAsync(int referenceId, string referenceType, string role = null) { return await Task.Run(() => { var items = new List<(int, string, string, string)>(); using (var cmd = new SQLiteCommand(_connection)) { var sql = @" SELECT ModelIndex, PathId, DisplayName, ObjectName FROM ModelItemReferences WHERE ReferenceId = @ReferenceId AND ReferenceType = @ReferenceType"; if (!string.IsNullOrEmpty(role)) { sql += " AND Role = @Role"; cmd.Parameters.AddWithValue("@Role", role); } cmd.CommandText = sql; cmd.Parameters.AddWithValue("@ReferenceId", referenceId); cmd.Parameters.AddWithValue("@ReferenceType", referenceType); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { items.Add(( Convert.ToInt32(reader["ModelIndex"]), reader["PathId"].ToString(), reader["DisplayName"].ToString(), reader["ObjectName"].ToString() )); } } } return items; }); } /// /// 获取指定 ReferenceId 和 Role 的所有 ModelItem 引用(同步版本,用于在 Task.Run 内部调用) /// private List<(int ModelIndex, string PathId, string DisplayName, string ObjectName)> GetModelItemReferencesSync(int referenceId, string referenceType, string role = null) { var items = new List<(int, string, string, string)>(); using (var cmd = new SQLiteCommand(_connection)) { var sql = @" SELECT ModelIndex, PathId, DisplayName, ObjectName FROM ModelItemReferences WHERE ReferenceId = @ReferenceId AND ReferenceType = @ReferenceType"; if (!string.IsNullOrEmpty(role)) { sql += " AND Role = @Role"; cmd.Parameters.AddWithValue("@Role", role); } cmd.CommandText = sql; cmd.Parameters.AddWithValue("@ReferenceId", referenceId); cmd.Parameters.AddWithValue("@ReferenceType", referenceType); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { items.Add(( Convert.ToInt32(reader["ModelIndex"]), reader["PathId"].ToString(), reader["DisplayName"].ToString(), reader["ObjectName"].ToString() )); } } } return items; } /// /// 删除指定 ReferenceId 的所有 ModelItem 引用 /// public async Task DeleteModelItemReferencesAsync(int referenceId, string referenceType = null) { await Task.Run(() => { using (var cmd = new SQLiteCommand(_connection)) { var sql = "DELETE FROM ModelItemReferences WHERE ReferenceId = @ReferenceId"; if (!string.IsNullOrEmpty(referenceType)) { sql += " AND ReferenceType = @ReferenceType"; cmd.Parameters.AddWithValue("@ReferenceType", referenceType); } cmd.CommandText = sql; cmd.Parameters.AddWithValue("@ReferenceId", referenceId); cmd.ExecuteNonQuery(); } }); } #endregion /// /// 释放资源 /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { _connection?.Close(); _connection?.Dispose(); } } #region 路径分析扩展方法 /// /// 获取路径最新的ClashDetective检测结果 /// public CollisionDetectionRecord GetLatestDetectionResultByRouteId(string routeId) { try { var sql = @" SELECT cdr.*, pr.Name AS PathName FROM CollisionDetectionRecords cdr INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id WHERE cdr.RouteId = @routeId AND cdr.TestName IS NOT NULL ORDER BY cdr.TestTime DESC LIMIT 1 "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@routeId", routeId ?? ""); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { return ReadCollisionDetectionRecordFromReader(reader); } } } } catch (Exception ex) { LogManager.Error($"获取路径最新碰撞检测记录失败: {ex.Message}", ex); } return null; } /// /// 保存详细分析结果(扩展字段) /// public void SaveDetailedAnalysisResult(PathAnalysisResult result) { try { var sql = @" INSERT OR REPLACE INTO AnalysisResults (RouteId, CollisionCount, SafetyScore, EfficiencyScore, OverallScore, AnalysisTime, Strategy, TurnDifficultyScore, TortuosityScore, RedundancyScore, HotspotCount, GroupId, GroupRanking) VALUES (@routeId, @collisionCount, @safetyScore, @efficiencyScore, @overallScore, @analysisTime, @strategy, @turnDifficultyScore, @tortuosityScore, @redundancyScore, @hotspotCount, @groupId, @groupRanking) "; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@routeId", result.RouteId); cmd.Parameters.AddWithValue("@collisionCount", result.CollisionCount); cmd.Parameters.AddWithValue("@safetyScore", result.SafetyScore); cmd.Parameters.AddWithValue("@efficiencyScore", result.EfficiencyScore); cmd.Parameters.AddWithValue("@overallScore", result.OverallScore); cmd.Parameters.AddWithValue("@analysisTime", result.AnalysisTime); cmd.Parameters.AddWithValue("@strategy", result.Strategy ?? "平衡模式"); cmd.Parameters.AddWithValue("@turnDifficultyScore", result.TurnDifficultyScore); cmd.Parameters.AddWithValue("@tortuosityScore", result.TortuosityScore); cmd.Parameters.AddWithValue("@redundancyScore", result.RedundancyScore); cmd.Parameters.AddWithValue("@hotspotCount", result.HotspotCount); cmd.Parameters.AddWithValue("@groupId", result.GroupId ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@groupRanking", result.GroupRanking); cmd.ExecuteNonQuery(); } LogManager.Debug($"保存详细分析结果: RouteId={result.RouteId}, OverallScore={result.OverallScore:F1}"); } catch (Exception ex) { LogManager.Error($"保存详细分析结果失败: {ex.Message}", ex); throw; } } /// /// 检查并扩展 AnalysisResults 表结构(用于升级现有数据库) /// public void EnsureAnalysisResultsTableExtended() { try { // 检查表是否存在扩展字段 var checkSql = "PRAGMA table_info(AnalysisResults)"; var existingColumns = new HashSet(); using (var cmd = new SQLiteCommand(checkSql, _connection)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { existingColumns.Add(reader["name"].ToString().ToLower()); } } // 需要添加的字段 var columnsToAdd = new Dictionary { ["turndifficultyscore"] = "ALTER TABLE AnalysisResults ADD COLUMN TurnDifficultyScore REAL", ["tortuosityscore"] = "ALTER TABLE AnalysisResults ADD COLUMN TortuosityScore REAL", ["redundancyscore"] = "ALTER TABLE AnalysisResults ADD COLUMN RedundancyScore REAL", ["hotspotcount"] = "ALTER TABLE AnalysisResults ADD COLUMN HotspotCount INTEGER DEFAULT 0", ["groupid"] = "ALTER TABLE AnalysisResults ADD COLUMN GroupId TEXT", ["groupranking"] = "ALTER TABLE AnalysisResults ADD COLUMN GroupRanking INTEGER DEFAULT 0" }; foreach (var column in columnsToAdd) { if (!existingColumns.Contains(column.Key)) { try { ExecuteNonQuery(column.Value); LogManager.Info($"扩展 AnalysisResults 表: 添加字段 {column.Key}"); } catch (Exception ex) { LogManager.Warning($"添加字段 {column.Key} 可能已存在: {ex.Message}"); } } } } catch (Exception ex) { LogManager.Error($"扩展 AnalysisResults 表失败: {ex.Message}", ex); } } #endregion #region 时标管理 /// /// 保存时标配置 /// public void SaveTimeTagProfile(TimeTagProfile profile) { if (profile == null) return; try { using (var transaction = _connection.BeginTransaction()) { var profileSql = @" INSERT OR REPLACE INTO TimeTagProfiles (Id, RouteId, Name, DefaultSpeed, TurnSpeedRatio, CreatedAt) VALUES (@id, @routeId, @name, @defaultSpeed, @turnSpeedRatio, @createdAt) "; using (var cmd = new SQLiteCommand(profileSql, _connection)) { cmd.Parameters.AddWithValue("@id", profile.Id); cmd.Parameters.AddWithValue("@routeId", profile.RouteId); cmd.Parameters.AddWithValue("@name", profile.Name); cmd.Parameters.AddWithValue("@defaultSpeed", profile.DefaultSpeed); cmd.Parameters.AddWithValue("@turnSpeedRatio", profile.TurnSpeedRatio); cmd.Parameters.AddWithValue("@createdAt", profile.CreatedAt); cmd.ExecuteNonQuery(); } using (var cmd = new SQLiteCommand("DELETE FROM TimeTagEvents WHERE ProfileId = @profileId", _connection)) { cmd.Parameters.AddWithValue("@profileId", profile.Id); cmd.ExecuteNonQuery(); } var eventSql = @" INSERT INTO TimeTagEvents (Id, ProfileId, PointId, Distance, Type, Name, WaitTime, ActionTime, CumulativeTime, ArriveSpeed, LeaveSpeed, SortOrder) VALUES (@id, @profileId, @pointId, @distance, @type, @name, @waitTime, @actionTime, @cumulativeTime, @arriveSpeed, @leaveSpeed, @sortOrder) "; for (int i = 0; i < profile.Events.Count; i++) { var evt = profile.Events[i]; using (var cmd = new SQLiteCommand(eventSql, _connection)) { cmd.Parameters.AddWithValue("@id", evt.Id); cmd.Parameters.AddWithValue("@profileId", profile.Id); cmd.Parameters.AddWithValue("@pointId", string.IsNullOrEmpty(evt.PointId) ? (object)DBNull.Value : evt.PointId); cmd.Parameters.AddWithValue("@distance", evt.Distance); cmd.Parameters.AddWithValue("@type", evt.Type.ToString()); cmd.Parameters.AddWithValue("@name", evt.Name); cmd.Parameters.AddWithValue("@waitTime", evt.WaitTime); cmd.Parameters.AddWithValue("@actionTime", evt.ActionTime); cmd.Parameters.AddWithValue("@cumulativeTime", evt.CumulativeTime); cmd.Parameters.AddWithValue("@arriveSpeed", evt.ArriveSpeed.HasValue ? (object)evt.ArriveSpeed.Value : DBNull.Value); cmd.Parameters.AddWithValue("@leaveSpeed", evt.LeaveSpeed.HasValue ? (object)evt.LeaveSpeed.Value : DBNull.Value); cmd.Parameters.AddWithValue("@sortOrder", i); cmd.ExecuteNonQuery(); } } transaction.Commit(); LogManager.Info($"时标配置已保存: {profile.Name}"); } } catch (Exception ex) { LogManager.Error($"保存时标配置失败: {ex.Message}", ex); throw; } } /// /// 获取路径的所有时标配置 /// public List GetTimeTagProfiles(string routeId) { var profiles = new List(); try { var sql = "SELECT * FROM TimeTagProfiles WHERE RouteId = @routeId ORDER BY CreatedAt"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@routeId", routeId); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var profile = new TimeTagProfile { Id = reader["Id"].ToString(), RouteId = reader["RouteId"].ToString(), Name = reader["Name"].ToString(), DefaultSpeed = Convert.ToDouble(reader["DefaultSpeed"]), TurnSpeedRatio = Convert.ToDouble(reader["TurnSpeedRatio"]), CreatedAt = Convert.ToDateTime(reader["CreatedAt"]) }; LoadTimeTagEvents(profile); profiles.Add(profile); } } } } catch (Exception ex) { LogManager.Error($"获取时标配置列表失败: {ex.Message}", ex); } return profiles; } /// /// 根据ID获取时标配置 /// public TimeTagProfile GetTimeTagProfile(string profileId) { try { var sql = "SELECT * FROM TimeTagProfiles WHERE Id = @id"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@id", profileId); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { var profile = new TimeTagProfile { Id = reader["Id"].ToString(), RouteId = reader["RouteId"].ToString(), Name = reader["Name"].ToString(), DefaultSpeed = Convert.ToDouble(reader["DefaultSpeed"]), TurnSpeedRatio = Convert.ToDouble(reader["TurnSpeedRatio"]), CreatedAt = Convert.ToDateTime(reader["CreatedAt"]) }; LoadTimeTagEvents(profile); return profile; } } } } catch (Exception ex) { LogManager.Error($"获取时标配置失败: {ex.Message}", ex); } return null; } private void LoadTimeTagEvents(TimeTagProfile profile) { profile.Events = new List(); var sql = "SELECT * FROM TimeTagEvents WHERE ProfileId = @profileId ORDER BY SortOrder"; using (var cmd = new SQLiteCommand(sql, _connection)) { cmd.Parameters.AddWithValue("@profileId", profile.Id); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var evt = new TimeTagEvent { Id = reader["Id"].ToString(), ProfileId = reader["ProfileId"].ToString(), PointId = reader["PointId"] != DBNull.Value ? reader["PointId"].ToString() : null, Distance = Convert.ToDouble(reader["Distance"]), Type = (EventType)Enum.Parse(typeof(EventType), reader["Type"].ToString()), Name = reader["Name"].ToString(), WaitTime = Convert.ToDouble(reader["WaitTime"]), ActionTime = Convert.ToDouble(reader["ActionTime"]), CumulativeTime = Convert.ToDouble(reader["CumulativeTime"]), ArriveSpeed = reader["ArriveSpeed"] != DBNull.Value ? Convert.ToDouble(reader["ArriveSpeed"]) : (double?)null, LeaveSpeed = reader["LeaveSpeed"] != DBNull.Value ? Convert.ToDouble(reader["LeaveSpeed"]) : (double?)null }; profile.Events.Add(evt); } } } } /// /// 删除时标配置 /// public void DeleteTimeTagProfile(string profileId) { try { using (var cmd = new SQLiteCommand("DELETE FROM TimeTagProfiles WHERE Id = @id", _connection)) { cmd.Parameters.AddWithValue("@id", profileId); cmd.ExecuteNonQuery(); } LogManager.Info($"时标配置已删除: {profileId}"); } catch (Exception ex) { LogManager.Error($"删除时标配置失败: {ex.Message}", ex); throw; } } #endregion } /// /// ClashDetective碰撞对象数据模型 /// 包含碰撞时运动物体的位置和朝向信息,用于还原碰撞场景 /// public class ClashDetectiveCollisionObjectRecord { public int Id { get; set; } public int DetectionRecordId { get; set; } public int? ModelIndex { get; set; } public string PathId { get; set; } public string DisplayName { get; set; } public string ObjectName { get; set; } // 碰撞时运动物体的动画跟踪点位置和姿态(用于还原碰撞场景) public double? AnimatedObjectTrackedPosX { get; set; } public double? AnimatedObjectTrackedPosY { get; set; } public double? AnimatedObjectTrackedPosZ { get; set; } public double? AnimatedObjectTrackedYawRadians { get; set; } public double? AnimatedObjectTrackedRotA { get; set; } public double? AnimatedObjectTrackedRotB { get; set; } public double? AnimatedObjectTrackedRotC { get; set; } public double? AnimatedObjectTrackedRotD { get; set; } public bool AnimatedObjectHasTrackedRotation { get; set; } public bool HasPositionInfo { get; set; } } /// /// 碰撞检测记录 /// 每次点击"生成动画"创建一条记录,保存当时的完整配置和检测结果 /// 原 ClashDetectiveResults 表已合并到此表 /// public class CollisionDetectionRecord { public int Id { get; set; } public string RouteId { get; set; } public DateTime CreatedTime { get; set; } // 动画参数 public int FrameRate { get; set; } public double DurationSeconds { get; set; } // 碰撞检测参数(模型单位) public double DetectionTolerance { get; set; } // 运动物体参数 public bool IsVirtualObject { get; set; } public string AnimatedObjectName { get; set; } public int? ObjectModelIndex { get; set; } public string ObjectPathId { get; set; } public double? VirtualObjectLength { get; set; } public double? VirtualObjectWidth { get; set; } public double? VirtualObjectHeight { get; set; } // 检测模式 public bool DetectAllObjects { get; set; } // 角度修正 public double ObjectRotationCorrection { get; set; } // 备注 public string Description { get; set; } // 碰撞结果(原 ClashDetectiveResults 表字段) public string TestName { get; set; } // ClashDetective测试名称 public DateTime? TestTime { get; set; } // 碰撞测试完成时间 public int CollisionCount { get; set; } // 总碰撞数 public int AnimationCollisionCount { get; set; } // 动画碰撞数 // 关联数据(不存入数据库,查询时填充) public List ExcludedObjects { get; set; } public List ManualTargets { get; set; } } /// /// 碰撞检测记录排除对象 /// public class CollisionDetectionExcludedObjectRecord { public int Id { get; set; } public int DetectionRecordId { get; set; } public int ModelIndex { get; set; } public string PathId { get; set; } public string DisplayName { get; set; } public string ObjectName { get; set; } public string Reason { get; set; } } /// /// 碰撞检测记录手工目标 /// public class CollisionDetectionManualTargetRecord { public int Id { get; set; } public int DetectionRecordId { get; set; } public int ModelIndex { get; set; } public string PathId { get; set; } public string DisplayName { get; set; } public string ObjectName { get; set; } } /// /// 碰撞报告截图记录 /// public class CollisionReportScreenshotRecord { public int Id { get; set; } public int DetectionRecordId { get; set; } public string FilePath { get; set; } public string Format { get; set; } public int Width { get; set; } public int Height { get; set; } public DateTime CaptureTime { get; set; } public string Description { get; set; } public int SortOrder { get; set; } } /// /// 路径分析结果数据模型(扩展版) /// public class PathAnalysisResult { public string RouteId { get; set; } public int CollisionCount { get; set; } public double SafetyScore { get; set; } public double EfficiencyScore { get; set; } public double OverallScore { get; set; } public DateTime AnalysisTime { get; set; } public string Strategy { get; set; } // 扩展字段 public double TurnDifficultyScore { get; set; } public double TortuosityScore { get; set; } public double RedundancyScore { get; set; } public int HotspotCount { get; set; } public string GroupId { get; set; } public int GroupRanking { get; set; } } #region 排除列表数据模型 /// /// 排除对象记录 /// public class ExcludedObjectRecord { public int Id { get; set; } public string RouteId { get; set; } public int ModelIndex { get; set; } public string PathId { get; set; } public string DisplayName { get; set; } public string ObjectName { get; set; } public DateTime ExcludedTime { get; set; } public string Reason { get; set; } public bool IsGlobal { get; set; } } #endregion }