3741 lines
169 KiB
C#
3741 lines
169 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 路径数据库管理器 - 简化版
|
||
/// 负责路径和碰撞数据的持久化存储
|
||
/// </summary>
|
||
public class PathDatabase : IDisposable
|
||
{
|
||
internal SQLiteConnection _connection;
|
||
private readonly string _dbPath;
|
||
private BackupManager _backupManager;
|
||
|
||
/// <summary>
|
||
/// 数据库连接(供内部使用)
|
||
/// </summary>
|
||
internal SQLiteConnection Connection => _connection;
|
||
|
||
/// <summary>
|
||
/// 简单数据库备份管理器(直接备份文件,最可靠)
|
||
/// </summary>
|
||
public BackupManager Backup => _backupManager ?? (_backupManager = new BackupManager(this));
|
||
|
||
/// <summary>
|
||
/// 初始化路径数据库
|
||
/// </summary>
|
||
/// <param name="documentPath">Navisworks文档路径</param>
|
||
public PathDatabase(string documentPath)
|
||
{
|
||
// 数据库文件与nwd文件同名,扩展名为.db
|
||
_dbPath = Path.ChangeExtension(documentPath, ".db");
|
||
|
||
InitializeDatabase();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化数据库连接和表结构
|
||
/// </summary>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建数据库表结构
|
||
/// </summary>
|
||
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)");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存路径基本信息
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存碰撞报告,返回报告ID
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新碰撞报告的截图信息(更新CollisionDetectionRecords表)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存碰撞报告截图到截图表,返回新记录Id
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取碰撞报告的所有截图
|
||
/// </summary>
|
||
public List<CollisionReportScreenshotRecord> GetCollisionReportScreenshots(int detectionRecordId)
|
||
{
|
||
var screenshots = new List<CollisionReportScreenshotRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除碰撞报告截图
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新截图排序和描述
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新路径名称(仅更新名称字段)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存路径的碰撞结果(已废弃,保留以兼容旧代码)
|
||
/// </summary>
|
||
public void SaveCollisions(string routeId, List<CollisionResult> 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 碰撞检测记录查询
|
||
|
||
/// <summary>
|
||
/// 获取路径的所有碰撞检测记录(包含碰撞结果)
|
||
/// </summary>
|
||
public List<CollisionDetectionRecord> GetDetectionResultsByPath(string pathName)
|
||
{
|
||
var results = new List<CollisionDetectionRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取路径最新的碰撞检测记录(包含碰撞结果)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据ID获取碰撞检测记录
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除碰撞检测记录
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有碰撞检测记录(包含碰撞结果)
|
||
/// </summary>
|
||
public List<CollisionDetectionRecord> GetAllDetectionResults()
|
||
{
|
||
var results = new List<CollisionDetectionRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空所有数据
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据测试名称获取碰撞检测记录
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存ClashDetective碰撞对象
|
||
/// </summary>
|
||
public void SaveClashDetectiveCollisionObjects(int detectionRecordId, List<ClashDetectiveCollisionObjectRecord> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定碰撞检测记录的所有碰撞对象
|
||
/// </summary>
|
||
public List<ClashDetectiveCollisionObjectRecord> GetClashDetectiveCollisionObjects(int detectionRecordId)
|
||
{
|
||
var results = new List<ClashDetectiveCollisionObjectRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取检测记录的排除对象列表
|
||
/// </summary>
|
||
public List<CollisionDetectionExcludedObjectRecord> GetDetectionExcludedObjects(int detectionRecordId)
|
||
{
|
||
var results = new List<CollisionDetectionExcludedObjectRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取检测记录的手工碰撞目标列表
|
||
/// </summary>
|
||
public List<CollisionDetectionManualTargetRecord> GetDetectionManualTargets(int detectionRecordId)
|
||
{
|
||
var results = new List<CollisionDetectionManualTargetRecord>();
|
||
|
||
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
|
||
|
||
/// <summary>
|
||
/// 通过基础参数查询匹配的检测记录(用于检查配置是否重复)
|
||
/// 注意:数值参数使用容差匹配,返回的记录还需要进一步比较排除列表和手工目标
|
||
/// </summary>
|
||
public List<CollisionDetectionRecord> 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<CollisionDetectionRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存分析评分结果
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取路径的碰撞数量
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取路径的分析结果
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有路径的分析结果
|
||
/// </summary>
|
||
public List<PathAnalysisResult> GetAllAnalysisResults()
|
||
{
|
||
var results = new List<PathAnalysisResult>();
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除路径及其相关数据
|
||
/// </summary>
|
||
/// <param name="routeId">路径ID</param>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有路径基本信息
|
||
/// </summary>
|
||
public List<PathRoute> GetAllPathRoutes()
|
||
{
|
||
var routes = new List<PathRoute>();
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载指定路径的路径点
|
||
/// </summary>
|
||
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} 个路径点");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存路径边
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除路径点
|
||
/// </summary>
|
||
private void DeletePathPoints(string routeId)
|
||
{
|
||
using (var cmd = new SQLiteCommand("DELETE FROM PathPoints WHERE RouteId = @routeId", _connection))
|
||
{
|
||
cmd.Parameters.AddWithValue("@routeId", routeId);
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除路径边
|
||
/// </summary>
|
||
private void DeletePathEdges(string routeId)
|
||
{
|
||
using (var cmd = new SQLiteCommand("DELETE FROM PathEdges WHERE RouteId = @routeId", _connection))
|
||
{
|
||
cmd.Parameters.AddWithValue("@routeId", routeId);
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载路径边
|
||
/// </summary>
|
||
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<Point3D>();
|
||
for (int i = 0; i <= sampleCount; i++)
|
||
{
|
||
double t = i / (double)sampleCount;
|
||
Point3D sampledPoint = startPoint + (endPoint - startPoint) * t;
|
||
edge.SampledPoints.Add(sampledPoint);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#region 排除列表管理
|
||
|
||
/// <summary>
|
||
/// 保存排除对象
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量保存排除对象
|
||
/// </summary>
|
||
public void SaveExcludedObjects(List<ExcludedObjectRecord> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取路径的排除对象列表
|
||
/// </summary>
|
||
public List<ExcludedObjectRecord> GetExcludedObjects(string routeId = null, bool includeGlobal = true)
|
||
{
|
||
var results = new List<ExcludedObjectRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除排除对象
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据PathId删除排除对象
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除路径的所有排除对象
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关联排除对象到碰撞报告
|
||
/// </summary>
|
||
public void LinkExcludedObjectsToCollisionReport(int reportId, List<int> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取碰撞报告的排除对象
|
||
/// </summary>
|
||
public List<ExcludedObjectRecord> GetExcludedObjectsForCollisionReport(int reportId)
|
||
{
|
||
var results = new List<ExcludedObjectRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关联排除对象到碰撞检测记录
|
||
/// </summary>
|
||
public void LinkExcludedObjectsToDetectionRecord(int detectionRecordId, List<int> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取检测记录的排除对象
|
||
/// </summary>
|
||
public List<ExcludedObjectRecord> GetExcludedObjectsForDetectionRecord(int detectionRecordId)
|
||
{
|
||
var results = new List<ExcludedObjectRecord>();
|
||
|
||
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 碰撞检测记录
|
||
|
||
/// <summary>
|
||
/// 保存碰撞检测记录
|
||
/// 每次点击"生成动画"创建一条记录
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新碰撞检测结果到检测记录
|
||
/// 在ClashDetective测试完成后调用,保存测试结果
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存碰撞检测记录的排除对象
|
||
/// </summary>
|
||
public void SaveCollisionDetectionExcludedObjects(int detectionRecordId, List<CollisionDetectionExcludedObjectRecord> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存碰撞检测记录的手工目标
|
||
/// </summary>
|
||
public void SaveCollisionDetectionManualTargets(int detectionRecordId, List<CollisionDetectionManualTargetRecord> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取碰撞检测记录
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取路径的最新碰撞检测记录
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取碰撞检测记录的排除对象
|
||
/// </summary>
|
||
public List<CollisionDetectionExcludedObjectRecord> GetCollisionDetectionExcludedObjects(int detectionRecordId)
|
||
{
|
||
var results = new List<CollisionDetectionExcludedObjectRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取碰撞检测记录的手工目标
|
||
/// </summary>
|
||
public List<CollisionDetectionManualTargetRecord> GetCollisionDetectionManualTargets(int detectionRecordId)
|
||
{
|
||
var results = new List<CollisionDetectionManualTargetRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从DataReader读取碰撞检测记录
|
||
/// </summary>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 执行非查询SQL语句
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取ModelItem的显示名称
|
||
/// </summary>
|
||
private string GetItemDisplayName(ModelItem item)
|
||
{
|
||
if (item == null)
|
||
return "未知对象";
|
||
|
||
try
|
||
{
|
||
return item.DisplayName ?? item.ToString();
|
||
}
|
||
catch
|
||
{
|
||
return "未知对象";
|
||
}
|
||
}
|
||
|
||
#region 批处理任务管理
|
||
|
||
/// <summary>
|
||
/// 根据测试名称获取碰撞检测记录(异步)
|
||
/// </summary>
|
||
public async Task<CollisionDetectionRecord> 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;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步获取路径信息
|
||
/// </summary>
|
||
public async Task<PathRoute> GetPathRouteAsync(string routeId)
|
||
{
|
||
return await Task.Run(() =>
|
||
{
|
||
return GetPathRouteSync(routeId);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步获取路径信息(用于需要同步调用的场景)
|
||
/// </summary>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 检查并扩展 PathRoutes 表结构
|
||
/// </summary>
|
||
public void EnsurePathRoutesTableExtended()
|
||
{
|
||
try
|
||
{
|
||
var checkSql = "PRAGMA table_info(PathRoutes)";
|
||
var existingColumns = new HashSet<string>();
|
||
|
||
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<string, string>
|
||
{
|
||
["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 批处理队列管理(简化版)
|
||
|
||
/// <summary>
|
||
/// 创建批处理队列项
|
||
/// </summary>
|
||
public async Task<int> 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;
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 SQLiteDataReader 读取 BatchQueueItem 对象
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取批处理队列项列表
|
||
/// </summary>
|
||
public async Task<List<BatchQueueItem>> GetBatchQueueItemsAsync(BatchQueueStatus statusFilter = BatchQueueStatus.All, int limit = 50, int offset = 0)
|
||
{
|
||
return await Task.Run(() =>
|
||
{
|
||
var items = new List<BatchQueueItem>();
|
||
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<string>();
|
||
|
||
// 只有当 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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取批处理队列项
|
||
/// </summary>
|
||
public async Task<BatchQueueItem> 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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新批处理队列项
|
||
/// </summary>
|
||
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();
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除批处理队列项
|
||
/// </summary>
|
||
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();
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加 ModelItem 引用到 ModelItemReferences 表
|
||
/// </summary>
|
||
public async Task<int> 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());
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定 ReferenceId 和 Role 的所有 ModelItem 引用
|
||
/// </summary>
|
||
public async Task<List<(int ModelIndex, string PathId, string DisplayName, string ObjectName)>> 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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定 ReferenceId 和 Role 的所有 ModelItem 引用(同步版本,用于在 Task.Run 内部调用)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除指定 ReferenceId 的所有 ModelItem 引用
|
||
/// </summary>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 释放资源
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
Dispose(true);
|
||
GC.SuppressFinalize(this);
|
||
}
|
||
|
||
protected virtual void Dispose(bool disposing)
|
||
{
|
||
if (disposing)
|
||
{
|
||
_connection?.Close();
|
||
_connection?.Dispose();
|
||
}
|
||
}
|
||
|
||
#region 路径分析扩展方法
|
||
|
||
/// <summary>
|
||
/// 获取路径最新的ClashDetective检测结果
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存详细分析结果(扩展字段)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查并扩展 AnalysisResults 表结构(用于升级现有数据库)
|
||
/// </summary>
|
||
public void EnsureAnalysisResultsTableExtended()
|
||
{
|
||
try
|
||
{
|
||
// 检查表是否存在扩展字段
|
||
var checkSql = "PRAGMA table_info(AnalysisResults)";
|
||
var existingColumns = new HashSet<string>();
|
||
|
||
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<string, string>
|
||
{
|
||
["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 时标管理
|
||
|
||
/// <summary>
|
||
/// 保存时标配置
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取路径的所有时标配置
|
||
/// </summary>
|
||
public List<TimeTagProfile> GetTimeTagProfiles(string routeId)
|
||
{
|
||
var profiles = new List<TimeTagProfile>();
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据ID获取时标配置
|
||
/// </summary>
|
||
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<TimeTagEvent>();
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除时标配置
|
||
/// </summary>
|
||
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
|
||
}
|
||
|
||
/// <summary>
|
||
/// ClashDetective碰撞对象数据模型
|
||
/// 包含碰撞时运动物体的位置和朝向信息,用于还原碰撞场景
|
||
/// </summary>
|
||
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; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碰撞检测记录
|
||
/// 每次点击"生成动画"创建一条记录,保存当时的完整配置和检测结果
|
||
/// 原 ClashDetectiveResults 表已合并到此表
|
||
/// </summary>
|
||
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<CollisionDetectionExcludedObjectRecord> ExcludedObjects { get; set; }
|
||
public List<CollisionDetectionManualTargetRecord> ManualTargets { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碰撞检测记录排除对象
|
||
/// </summary>
|
||
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; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碰撞检测记录手工目标
|
||
/// </summary>
|
||
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; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碰撞报告截图记录
|
||
/// </summary>
|
||
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; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 路径分析结果数据模型(扩展版)
|
||
/// </summary>
|
||
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 排除列表数据模型
|
||
|
||
/// <summary>
|
||
/// 排除对象记录
|
||
/// </summary>
|
||
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
|
||
|
||
|
||
}
|