增加检测记录表,完善批处理过程

This commit is contained in:
tian 2026-02-18 12:29:09 +08:00
parent 749eb07cef
commit dc1380d7fe
9 changed files with 1066 additions and 45 deletions

View File

@ -124,6 +124,7 @@ namespace NavisworksTransport.Core.Animation
// === 碰撞测试优化 ===
private string _currentAnimationHash; // 当前动画配置的哈希值
private int? _currentDetectionRecordId; // 当前检测记录ID关联CollisionDetectionRecords表
// === 动画播放机制 ===
private double _frameInterval; // 帧间隔(毫秒)
@ -174,6 +175,15 @@ namespace NavisworksTransport.Core.Animation
/// </summary>
public ModelItem AnimatedObject => _animatedObject;
/// <summary>
/// 当前检测记录ID关联CollisionDetectionRecords表
/// </summary>
public int? CurrentDetectionRecordId
{
get => _currentDetectionRecordId;
set => _currentDetectionRecordId = value;
}
// --- 新增事件 ---
/// <summary>
/// 当动画状态发生改变时触发
@ -1655,7 +1665,8 @@ namespace NavisworksTransport.Core.Animation
_virtualObjectLength,
_virtualObjectWidth,
_virtualObjectHeight,
_pathPoints
_pathPoints,
_currentDetectionRecordId
);
}
finally

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Core.Animation;
using NavisworksTransport.Core.Collision;
using NavisworksTransport.Core.Models;
@ -507,6 +508,19 @@ namespace NavisworksTransport.Core
// 统一准备碰撞检测(根据模式自动决定是否构建全局缓存)
ClashDetectiveIntegration.PrepareCollisionDetection(animatedObject, isManualMode, manualDetectionTargets);
// 🔥 从数据库加载该队列项的检测记录(包括排除列表和手工目标)
var detectionRecord = await LoadDetectionRecordForQueueItemAsync(item);
var excludedObjects = detectionRecord?.ExcludedObjects ?? new List<ModelItem>();
var manualTargetsFromRecord = detectionRecord?.ManualTargets ?? new List<ModelItem>();
LogManager.Info($"[批处理] 队列项 {item.Id} (DetectionRecordId={item.DetectionRecordId}) 加载了 {excludedObjects.Count} 个排除对象, {manualTargetsFromRecord.Count} 个手工目标");
// 如果检测记录中有手工目标,使用检测记录中的(优先级高于队列项中的)
if (manualTargetsFromRecord.Count > 0)
{
manualDetectionTargets = manualTargetsFromRecord;
isManualMode = true;
}
// 在主线程执行Navisworks API调用
var result = await UIStateManager.Instance.ExecuteUIUpdateAsync(() =>
{
@ -522,6 +536,7 @@ namespace NavisworksTransport.Core
CollisionDetectionEnabled = true,
ReportGenerationEnabled = true
};
var frames = _processor.PrecomputeFrames(
pathRoute,
animatedObject,
@ -533,7 +548,8 @@ namespace NavisworksTransport.Core
config.DurationSeconds,
config.DetectionToleranceMeters,
manualDetectionTargets,
item.ObjectRotationCorrection
item.ObjectRotationCorrection,
excludedObjects
);
// 检查预计算是否成功
@ -561,7 +577,8 @@ namespace NavisworksTransport.Core
item.VirtualObjectLength,
item.VirtualObjectWidth,
item.VirtualObjectHeight,
pathPoints
pathPoints,
item.DetectionRecordId
);
// 🔥 使用 ClashDetective 确认的碰撞数,而不是预计算的碰撞数
@ -720,7 +737,331 @@ namespace NavisworksTransport.Core
await _database.DeleteBatchQueueItemAsync(itemId);
}
/// <summary>
/// 检查并加载排除列表(已废弃,使用 LoadExcludedObjectsForQueueItemAsync 代替)
/// </summary>
/// <returns>排除列表</returns>
private List<ModelItem> CheckAndLoadExcludedObjects()
{
try
{
// 首先检查 PathAnimationManager 中是否已有排除列表
var animationManager = PathAnimationManager.GetInstance();
var existingExcludedObjects = animationManager.GetExcludedObjects();
if (existingExcludedObjects != null && existingExcludedObjects.Count > 0)
{
LogManager.Info($"[批处理队列] 使用 PathAnimationManager 中的排除列表,共 {existingExcludedObjects.Count} 个物体");
return existingExcludedObjects.ToList();
}
// 从数据库加载排除列表(全局排除列表)
if (_database != null)
{
var excludedRecords = _database.GetExcludedObjects(routeId: null, includeGlobal: true);
if (excludedRecords != null && excludedRecords.Count > 0)
{
// 将记录转换为 ModelItem
var excludedObjects = new List<ModelItem>();
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
foreach (var record in excludedRecords)
{
try
{
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId
{
ModelIndex = record.ModelIndex,
PathId = record.PathId
};
var modelItem = doc.Models.ResolvePathId(pathIdObj);
if (modelItem != null)
{
excludedObjects.Add(modelItem);
}
}
catch (Exception ex)
{
LogManager.Warning($"[批处理队列] 加载排除对象失败: {record.ObjectName}, 错误: {ex.Message}");
}
}
if (excludedObjects.Count > 0)
{
LogManager.Info($"[批处理队列] 从数据库加载排除列表,共 {excludedObjects.Count} 个物体");
return excludedObjects;
}
}
}
// 排除列表为空,提示用户
LogManager.Warning("[批处理队列] 排除列表为空,需要用户确认");
bool shouldContinue = false;
System.Windows.Application.Current?.Dispatcher.Invoke(() =>
{
var result = System.Windows.MessageBox.Show(
"排除列表为空。对于复杂路径,建议先运行\"生成动画\"进行预计算碰撞分析,设置排除列表后再进行批处理。\n\n" +
"• 是:继续批处理(不使用排除列表)\n" +
"• 否:取消批处理,先去设置排除列表\n\n" +
"提示:排除列表可以显著减少复杂路径的碰撞检测时间。",
"批处理确认",
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Warning
);
shouldContinue = (result == System.Windows.MessageBoxResult.Yes);
});
if (shouldContinue)
{
LogManager.Info("[批处理队列] 用户选择继续批处理(不使用排除列表)");
return new List<ModelItem>(); // 返回空列表,允许继续
}
else
{
LogManager.Info("[批处理队列] 用户选择取消批处理,去设置排除列表");
return null;
}
}
catch (Exception ex)
{
LogManager.Error($"[批处理队列] 检查排除列表失败: {ex.Message}");
// 发生错误时,允许继续(不阻止批处理)
return new List<ModelItem>();
}
}
/// <summary>
/// 获取批处理用的排除列表(已废弃,使用 LoadExcludedObjectsForQueueItemAsync 代替)
/// </summary>
private List<ModelItem> GetExcludedObjectsForBatchProcessing()
{
try
{
// 首先检查 PathAnimationManager 中是否已有排除列表
var animationManager = PathAnimationManager.GetInstance();
var existingExcludedObjects = animationManager.GetExcludedObjects();
if (existingExcludedObjects != null && existingExcludedObjects.Count > 0)
{
return existingExcludedObjects.ToList();
}
// 从数据库加载排除列表(全局排除列表)
if (_database != null)
{
var excludedRecords = _database.GetExcludedObjects(routeId: null, includeGlobal: true);
if (excludedRecords != null && excludedRecords.Count > 0)
{
var excludedObjects = new List<ModelItem>();
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
foreach (var record in excludedRecords)
{
try
{
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId
{
ModelIndex = record.ModelIndex,
PathId = record.PathId
};
var modelItem = doc.Models.ResolvePathId(pathIdObj);
if (modelItem != null)
{
excludedObjects.Add(modelItem);
}
}
catch (Exception ex)
{
LogManager.Warning($"[批处理队列] 加载排除对象失败: {record.ObjectName}, 错误: {ex.Message}");
}
}
return excludedObjects;
}
}
return new List<ModelItem>();
}
catch (Exception ex)
{
LogManager.Error($"[批处理队列] 获取排除列表失败: {ex.Message}");
return new List<ModelItem>();
}
}
/// <summary>
/// 从数据库加载指定队列项的排除列表
/// 从 ExcludedObjects 表读取根据队列项对应的路径ID (RouteId) 获取
/// </summary>
private async Task<List<ModelItem>> LoadExcludedObjectsForQueueItemAsync(BatchQueueItem item)
{
try
{
if (_database == null)
{
LogManager.Warning($"[批处理队列] 数据库未初始化,无法加载队列项 {item.Id} 的排除列表");
return new List<ModelItem>();
}
// 从 ExcludedObjects 表加载该路径的排除对象
// 排除列表在生成动画时已保存,按 RouteId 关联
var excludedRecords = await Task.Run(() =>
_database.GetExcludedObjects(item.RouteId, includeGlobal: true));
if (excludedRecords == null || excludedRecords.Count == 0)
{
LogManager.Info($"[批处理队列] 队列项 {item.Id} (RouteId={item.RouteId}) 没有排除对象");
return new List<ModelItem>();
}
var excludedObjects = new List<ModelItem>();
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
foreach (var record in excludedRecords)
{
try
{
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId
{
ModelIndex = record.ModelIndex,
PathId = record.PathId
};
var modelItem = doc.Models.ResolvePathId(pathIdObj);
if (modelItem != null)
{
excludedObjects.Add(modelItem);
}
else
{
LogManager.Warning($"[批处理队列] 无法通过 PathId 找到排除对象: ModelIndex={record.ModelIndex}, PathId={record.PathId}");
}
}
catch (Exception ex)
{
LogManager.Warning($"[批处理队列] 加载排除对象失败: {record.ObjectName}, 错误: {ex.Message}");
}
}
LogManager.Info($"[批处理队列] 队列项 {item.Id} 加载了 {excludedObjects.Count}/{excludedRecords.Count} 个排除对象");
return excludedObjects;
}
catch (Exception ex)
{
LogManager.Error($"[批处理队列] 加载队列项 {item.Id} 的排除列表失败: {ex.Message}");
return new List<ModelItem>();
}
}
/// <summary>
/// 加载队列项关联的检测记录(包含排除列表和手工目标)
/// </summary>
private async Task<DetectionRecordData> LoadDetectionRecordForQueueItemAsync(BatchQueueItem item)
{
try
{
if (_database == null || !item.DetectionRecordId.HasValue)
{
LogManager.Warning($"[批处理队列] 队列项 {item.Id} 没有关联的检测记录");
return null;
}
// 获取检测记录
var record = await Task.Run(() => _database.GetCollisionDetectionRecord(item.DetectionRecordId.Value));
if (record == null)
{
LogManager.Warning($"[批处理队列] 未找到检测记录 (Id={item.DetectionRecordId})");
return null;
}
var result = new DetectionRecordData
{
RecordId = record.Id,
RouteId = record.RouteId,
FrameRate = record.FrameRate,
DurationSeconds = record.DurationSeconds,
DetectionToleranceMeters = record.DetectionToleranceMeters,
IsVirtualObject = record.IsVirtualObject,
DetectAllObjects = record.DetectAllObjects,
ObjectRotationCorrection = record.ObjectRotationCorrection,
ExcludedObjects = new List<ModelItem>(),
ManualTargets = new List<ModelItem>()
};
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
// 加载排除列表
var excludedRecords = await Task.Run(() => _database.GetCollisionDetectionExcludedObjects(record.Id));
foreach (var excludedRecord in excludedRecords)
{
try
{
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId
{
ModelIndex = excludedRecord.ModelIndex,
PathId = excludedRecord.PathId
};
var modelItem = doc.Models.ResolvePathId(pathIdObj);
if (modelItem != null)
{
result.ExcludedObjects.Add(modelItem);
}
}
catch (Exception ex)
{
LogManager.Warning($"[批处理队列] 加载排除对象失败: {excludedRecord.ObjectName}, {ex.Message}");
}
}
// 加载手工目标
var manualTargetRecords = await Task.Run(() => _database.GetCollisionDetectionManualTargets(record.Id));
foreach (var targetRecord in manualTargetRecords)
{
try
{
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId
{
ModelIndex = targetRecord.ModelIndex,
PathId = targetRecord.PathId
};
var modelItem = doc.Models.ResolvePathId(pathIdObj);
if (modelItem != null)
{
result.ManualTargets.Add(modelItem);
}
}
catch (Exception ex)
{
LogManager.Warning($"[批处理队列] 加载手工目标失败: {targetRecord.ObjectName}, {ex.Message}");
}
}
LogManager.Info($"[批处理队列] 加载检测记录 (Id={record.Id}): 排除对象={result.ExcludedObjects.Count}, 手工目标={result.ManualTargets.Count}");
return result;
}
catch (Exception ex)
{
LogManager.Error($"[批处理队列] 加载检测记录失败: {ex.Message}");
return null;
}
}
}
/// <summary>
/// 检测记录数据(用于批处理)
/// </summary>
public class DetectionRecordData
{
public int RecordId { get; set; }
public string RouteId { get; set; }
public int FrameRate { get; set; }
public double DurationSeconds { get; set; }
public double DetectionToleranceMeters { get; set; }
public bool IsVirtualObject { get; set; }
public bool DetectAllObjects { get; set; }
public double ObjectRotationCorrection { get; set; }
public List<ModelItem> ExcludedObjects { get; set; }
public List<ModelItem> ManualTargets { get; set; }
}
/// <summary>

View File

@ -34,7 +34,8 @@ namespace NavisworksTransport.Core.Collision
double duration,
double detectionGap,
List<ModelItem> manualDetectionTargets = null,
double objectRotationCorrection = 0.0)
double objectRotationCorrection = 0.0,
List<ModelItem> excludedObjects = null)
{
try
{
@ -52,6 +53,13 @@ namespace NavisworksTransport.Core.Collision
_animationManager.SetManualCollisionTargets(manualDetectionTargets);
}
// 设置排除列表(关键:批处理时使用排除列表)
if (excludedObjects != null && excludedObjects.Count > 0)
{
_animationManager.SetExcludedObjectsAndClearCache(excludedObjects);
LogManager.Info($"[批处理] 已设置排除列表,共 {excludedObjects.Count} 个物体");
}
// 调用预计算方法内部不涉及UI操作
var frames = _animationManager.PrecomputeAnimationFramesInternal();
@ -81,7 +89,8 @@ namespace NavisworksTransport.Core.Collision
double virtualObjectLength,
double virtualObjectWidth,
double virtualObjectHeight,
List<Point3D> pathPoints = null)
List<Point3D> pathPoints = null,
int? detectionRecordId = null)
{
try
{
@ -99,7 +108,8 @@ namespace NavisworksTransport.Core.Collision
virtualObjectLength,
virtualObjectWidth,
virtualObjectHeight,
pathPoints
pathPoints,
detectionRecordId
);
LogManager.Info($"[批处理] ClashDetective测试创建完成 - 测试名称: {testName}");

View File

@ -224,7 +224,7 @@ namespace NavisworksTransport
int frameRate, double duration, double detectionGap,
ModelItem animatedObject, bool isVirtualObject,
double virtualObjectLength, double virtualObjectWidth, double virtualObjectHeight,
int precomputedCollisionCount = 0)
int precomputedCollisionCount = 0, int? detectionRecordId = null)
{
try
{
@ -269,7 +269,8 @@ namespace NavisworksTransport
VirtualObjectLength = virtualObjectLength,
VirtualObjectWidth = virtualObjectWidth,
VirtualObjectHeight = virtualObjectHeight,
CreatedAt = DateTime.Now
CreatedAt = DateTime.Now,
DetectionRecordId = detectionRecordId // 关联到检测记录
};
var resultId = pathDatabase.SaveClashDetectiveResult(record);
LogManager.Info($"ClashDetective结果已保存到数据库Id={resultId}, IsVirtualObject={isVirtualObject}");
@ -699,7 +700,8 @@ namespace NavisworksTransport
double virtualObjectWidth,
double virtualObjectHeight,
Progress progress = null,
int precomputedCollisionCount = 0)
int precomputedCollisionCount = 0,
int? detectionRecordId = null)
{
LogManager.Info($"[ClashDetective] 开始运行碰撞检测并保存到数据库(容差: {detectionGap}米)");
@ -765,7 +767,8 @@ namespace NavisworksTransport
virtualObjectLength,
virtualObjectWidth,
virtualObjectHeight,
precomputedCollisionCount
precomputedCollisionCount,
detectionRecordId
);
return (null, addedMainTest, 0, false);
@ -1122,7 +1125,7 @@ namespace NavisworksTransport
// 保存到数据库(使用去重后的结果)
SaveClashDetectiveResultToDatabase(routeId, finalClashResults, frameRate, duration, detectionGap, animatedObject, isVirtualObject,
virtualObjectLength, virtualObjectWidth, virtualObjectHeight, precomputedCollisionCount);
virtualObjectLength, virtualObjectWidth, virtualObjectHeight, precomputedCollisionCount, detectionRecordId);
LogManager.Info($"[ClashDetective] 结果已保存到数据库");
@ -1159,7 +1162,8 @@ namespace NavisworksTransport
double virtualObjectLength,
double virtualObjectWidth,
double virtualObjectHeight,
List<Point3D> pathPoints = null)
List<Point3D> pathPoints = null,
int? detectionRecordId = null)
{
// 重置取消标志
_wasLastTestCanceled = false;
@ -1212,7 +1216,8 @@ namespace NavisworksTransport
virtualObjectWidth,
virtualObjectHeight,
progress,
precomputedCollisionCount
precomputedCollisionCount,
detectionRecordId
);
collisionGroup = result.collisionGroup;
addedMainTest = result.addedMainTest;
@ -2127,7 +2132,8 @@ namespace NavisworksTransport
double virtualObjectLength,
double virtualObjectWidth,
double virtualObjectHeight,
List<Point3D> pathPoints = null)
List<Point3D> pathPoints = null,
int? detectionRecordId = null)
{
try
{
@ -2152,7 +2158,8 @@ namespace NavisworksTransport
virtualObjectWidth,
virtualObjectHeight,
null,
precomputedCollisionCount
precomputedCollisionCount,
detectionRecordId
);
var collisionGroup = result.collisionGroup;

View File

@ -24,6 +24,8 @@ namespace NavisworksTransport.Core.Collision
/// <param name="duration">持续时间(秒)</param>
/// <param name="detectionGap">检测间隙(米)</param>
/// <param name="manualDetectionTargets">手动指定的检测目标(可选)</param>
/// <param name="objectRotationCorrection">物体旋转修正角度(度)</param>
/// <param name="excludedObjects">排除列表(可选)</param>
/// <returns>动画帧列表</returns>
List<AnimationFrame> PrecomputeFrames(
PathRoute route,
@ -36,7 +38,8 @@ namespace NavisworksTransport.Core.Collision
double duration,
double detectionGap,
List<ModelItem> manualDetectionTargets = null,
double objectRotationCorrection = 0.0);
double objectRotationCorrection = 0.0,
List<ModelItem> excludedObjects = null);
/// <summary>
/// 创建并运行ClashDetective测试纯计算无UI操作
@ -54,6 +57,7 @@ namespace NavisworksTransport.Core.Collision
/// <param name="virtualObjectWidth">虚拟物体宽度(米)</param>
/// <param name="virtualObjectHeight">虚拟物体高度(米)</param>
/// <param name="pathPoints">路径点列表(用于测试完成后恢复物体位置)</param>
/// <param name="detectionRecordId">检测记录ID关联CollisionDetectionRecords表</param>
/// <returns>ClashDetective测试名称</returns>
string CreateAndRunClashDetectiveTest(
List<CollisionResult> precomputedCollisions,
@ -67,6 +71,7 @@ namespace NavisworksTransport.Core.Collision
double virtualObjectLength,
double virtualObjectWidth,
double virtualObjectHeight,
List<Point3D> pathPoints = null);
List<Point3D> pathPoints = null,
int? detectionRecordId = null);
}
}

View File

@ -37,6 +37,9 @@ namespace NavisworksTransport.Core.Models
// 角度修正配置
public double ObjectRotationCorrection { get; set; }
// 关联的检测记录ID每次生成动画时创建
public int? DetectionRecordId { get; set; }
// 结果
public string ClashDetectiveTestName { get; set; }
public int? CollisionCount { get; set; }

View File

@ -200,7 +200,9 @@ namespace NavisworksTransport
VirtualObjectLength REAL,
VirtualObjectWidth REAL,
VirtualObjectHeight REAL,
CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
DetectionRecordId INTEGER,
FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE SET NULL
)
");
@ -381,6 +383,77 @@ namespace NavisworksTransport
");
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_excluded_result ON ClashDetectiveExcludedObjects(ResultId)");
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
{
// 列已存在,忽略错误
}
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,
DurationSeconds REAL NOT NULL,
--
DetectionToleranceMeters REAL NOT NULL,
--
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,
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)");
// 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>
@ -801,10 +874,10 @@ namespace NavisworksTransport
INSERT INTO ClashDetectiveResults
(TestName, RouteId, TestTime, CollisionCount, AnimationCollisionCount,
FrameRate, Duration, DetectionGap, AnimatedObjectName, IsVirtualObject, ObjectModelIndex, ObjectPathId,
VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight, CreatedAt)
VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight, CreatedAt, DetectionRecordId)
VALUES (@testName, @routeId, @testTime, @collisionCount, @animationCollisionCount,
@frameRate, @duration, @detectionGap, @animatedObjectName, @isVirtualObject, @objectModelIndex, @objectPathId,
@virtualObjectLength, @virtualObjectWidth, @virtualObjectHeight, @createdAt)
@virtualObjectLength, @virtualObjectWidth, @virtualObjectHeight, @createdAt, @detectionRecordId)
";
long newId = 0;
@ -826,6 +899,7 @@ namespace NavisworksTransport
cmd.Parameters.AddWithValue("@virtualObjectWidth", record.VirtualObjectWidth);
cmd.Parameters.AddWithValue("@virtualObjectHeight", record.VirtualObjectHeight);
cmd.Parameters.AddWithValue("@createdAt", record.CreatedAt);
cmd.Parameters.AddWithValue("@detectionRecordId", record.DetectionRecordId ?? (object)DBNull.Value);
cmd.ExecuteNonQuery();
newId = _connection.LastInsertRowId;
}
@ -2110,6 +2184,321 @@ namespace NavisworksTransport
#endregion
#region
/// <summary>
/// 保存碰撞检测记录
/// 每次点击"生成动画"创建一条记录
/// </summary>
public int SaveCollisionDetectionRecord(CollisionDetectionRecord record)
{
try
{
var sql = @"
INSERT INTO CollisionDetectionRecords (
RouteId, CreatedTime, FrameRate, DurationSeconds, DetectionToleranceMeters,
IsVirtualObject, AnimatedObjectName, ObjectModelIndex, ObjectPathId,
VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight,
DetectAllObjects, ObjectRotationCorrection, Description
)
VALUES (
@RouteId, @CreatedTime, @FrameRate, @DurationSeconds, @DetectionToleranceMeters,
@IsVirtualObject, @AnimatedObjectName, @ObjectModelIndex, @ObjectPathId,
@VirtualObjectLength, @VirtualObjectWidth, @VirtualObjectHeight,
@DetectAllObjects, @ObjectRotationCorrection, @Description
);
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("@DetectionToleranceMeters", record.DetectionToleranceMeters);
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 ?? "");
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>
/// 保存碰撞检测记录的排除对象
/// </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"]),
DetectionToleranceMeters = Convert.ToDouble(reader["DetectionToleranceMeters"]),
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()
};
}
#endregion
/// <summary>
/// 执行非查询SQL语句
/// </summary>
@ -2275,14 +2664,16 @@ namespace NavisworksTransport
FrameRate, DurationSeconds, DetectionToleranceMeters,
IsVirtualObject, VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight,
DetectAllObjects,
ClashDetectiveTestName, CollisionCount, ObjectRotationCorrection
ClashDetectiveTestName, CollisionCount, ObjectRotationCorrection,
DetectionRecordId
)
VALUES (
@RouteId, @Status, @CreatedTime, @StartTime, @EndTime, @ErrorMessage,
@FrameRate, @DurationSeconds, @DetectionToleranceMeters,
@IsVirtualObject, @VirtualObjectLength, @VirtualObjectWidth, @VirtualObjectHeight,
@DetectAllObjects,
@ClashDetectiveTestName, @CollisionCount, @ObjectRotationCorrection
@ClashDetectiveTestName, @CollisionCount, @ObjectRotationCorrection,
@DetectionRecordId
);
SELECT last_insert_rowid();";
@ -2303,6 +2694,7 @@ namespace NavisworksTransport
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());
@ -2345,7 +2737,8 @@ namespace NavisworksTransport
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"])
ObjectRotationCorrection = Convert.ToDouble(reader["ObjectRotationCorrection"]),
DetectionRecordId = !Convert.IsDBNull(reader["DetectionRecordId"]) ? (int?)Convert.ToInt32(reader["DetectionRecordId"]) : null
};
// 填充 MovingObjectName 属性
@ -2378,7 +2771,8 @@ namespace NavisworksTransport
bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionToleranceMeters,
bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight,
bqi.DetectAllObjects,
bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection
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>();
@ -2437,7 +2831,8 @@ namespace NavisworksTransport
bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionToleranceMeters,
bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight,
bqi.DetectAllObjects,
bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection
bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection,
bqi.DetectionRecordId
FROM BatchQueueItems bqi
INNER JOIN PathRoutes pr ON bqi.RouteId = pr.Id
WHERE bqi.Id = @Id";
@ -3030,6 +3425,7 @@ namespace NavisworksTransport
public double VirtualObjectWidth { get; set; }
public double VirtualObjectHeight { get; set; }
public DateTime CreatedAt { get; set; }
public int? DetectionRecordId { get; set; } // 关联到检测记录
}
/// <summary>
@ -3053,6 +3449,73 @@ namespace NavisworksTransport
public bool HasPositionInfo { get; set; }
}
/// <summary>
/// 碰撞检测记录
/// 每次点击"生成动画"创建一条记录,保存当时的完整配置
/// </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 DetectionToleranceMeters { 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; }
// 关联数据(不存入数据库,查询时填充)
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>

View File

@ -2023,8 +2023,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 获取路由ID
string routeId = CurrentPathRoute?.Id ?? "";
// 先保存排除对象到数据库确保排除对象有记录ID
_pathAnimationManager?.SaveExcludedObjectsToDatabase(pathDatabase, routeId);
// 注意:检测记录(包括排除列表)已在生成动画时保存到 CollisionDetectionRecords 表
// 这里只保存碰撞报告,不重复保存排除列表
// 从报告中获取所有需要的数据
await Task.Run(() =>
@ -2979,6 +2979,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 用户选择直接继续(无排除对象)
LogManager.Info("[碰撞分析] 用户选择直接继续生成");
UpdateMainStatus($"预计算碰撞分析完成,共 {totalCollisions} 个候选碰撞");
// 🔥 保存检测记录到数据库(唯一保存位置:生成动画完成后)
SaveCollisionDetectionRecord();
}
}
catch (Exception ex)
@ -2987,6 +2990,142 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
/// <summary>
/// 保存检测记录到数据库
/// 在生成动画完成后调用,创建检测记录并保存当时的完整配置
/// </summary>
private int? SaveCollisionDetectionRecord()
{
try
{
var pathDatabase = _pathPlanningManager?.GetPathDatabase();
if (pathDatabase == null)
{
LogManager.Warning("[检测记录] 数据库未初始化,无法保存检测记录");
return null;
}
string routeId = CurrentPathRoute?.Id ?? "";
// 创建检测记录
var record = new CollisionDetectionRecord
{
RouteId = routeId,
CreatedTime = DateTime.Now,
FrameRate = _animationFrameRate,
DurationSeconds = AnimationDuration,
DetectionToleranceMeters = _detectionTolerance,
IsVirtualObject = UseVirtualObject,
AnimatedObjectName = UseVirtualObject ? "虚拟物体" : SelectedAnimatedObject?.DisplayName,
DetectAllObjects = !IsManualCollisionTargetEnabled,
ObjectRotationCorrection = _objectRotationCorrection,
Description = $"动画生成于 {DateTime.Now:yyyy-MM-dd HH:mm:ss}"
};
// 虚拟物体尺寸
if (UseVirtualObject)
{
record.VirtualObjectLength = VirtualObjectLength;
record.VirtualObjectWidth = VirtualObjectWidth;
record.VirtualObjectHeight = VirtualObjectHeight;
}
// 真实物体信息
else if (SelectedAnimatedObject != null)
{
try
{
var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(SelectedAnimatedObject);
record.ObjectModelIndex = pathId.ModelIndex;
record.ObjectPathId = pathId.PathId;
}
catch (Exception ex)
{
LogManager.Warning($"[检测记录] 获取运动物体PathId失败: {ex.Message}");
}
}
// 保存检测记录
int recordId = pathDatabase.SaveCollisionDetectionRecord(record);
LogManager.Info($"[检测记录] 已创建记录 (Id={recordId})");
// 🔥 设置当前检测记录ID到PathAnimationManager供碰撞结果保存时关联使用
_pathAnimationManager.CurrentDetectionRecordId = recordId;
// 保存排除列表
var excludedObjects = _pathAnimationManager?.GetExcludedObjects();
if (excludedObjects != null && excludedObjects.Count > 0)
{
var excludedRecords = new List<CollisionDetectionExcludedObjectRecord>();
foreach (var obj in excludedObjects)
{
if (obj == null) continue;
try
{
var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(obj);
excludedRecords.Add(new CollisionDetectionExcludedObjectRecord
{
DetectionRecordId = recordId,
ModelIndex = pathId.ModelIndex,
PathId = pathId.PathId,
DisplayName = obj.DisplayName,
ObjectName = obj.DisplayName,
Reason = "预计算碰撞分析排除"
});
}
catch (Exception ex)
{
LogManager.Warning($"[检测记录] 准备排除对象记录失败: {obj.DisplayName}, {ex.Message}");
}
}
if (excludedRecords.Count > 0)
{
pathDatabase.SaveCollisionDetectionExcludedObjects(recordId, excludedRecords);
LogManager.Info($"[检测记录] 已保存 {excludedRecords.Count} 个排除对象");
}
}
// 保存手工目标
if (IsManualCollisionTargetEnabled && _manualCollisionTargets != null && _manualCollisionTargets.Count > 0)
{
var manualTargetRecords = new List<CollisionDetectionManualTargetRecord>();
foreach (var target in _manualCollisionTargets)
{
if (target?.ModelItem == null) continue;
try
{
var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(target.ModelItem);
manualTargetRecords.Add(new CollisionDetectionManualTargetRecord
{
DetectionRecordId = recordId,
ModelIndex = pathId.ModelIndex,
PathId = pathId.PathId,
DisplayName = target.ModelItem.DisplayName,
ObjectName = target.ModelItem.DisplayName
});
}
catch (Exception ex)
{
LogManager.Warning($"[检测记录] 准备手工目标记录失败: {target.DisplayName}, {ex.Message}");
}
}
if (manualTargetRecords.Count > 0)
{
pathDatabase.SaveCollisionDetectionManualTargets(recordId, manualTargetRecords);
LogManager.Info($"[检测记录] 已保存 {manualTargetRecords.Count} 个手工目标");
}
}
return recordId;
}
catch (Exception ex)
{
LogManager.Error($"[检测记录] 保存失败: {ex.Message}");
return null;
}
}
private void ExecuteToggleWireframeMode()
{
try
@ -4568,8 +4707,49 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return;
}
// 🔥 检查排除列表是否已设置
var excludedObjects = _pathAnimationManager?.GetExcludedObjects();
if (excludedObjects == null || excludedObjects.Count == 0)
{
LogManager.Warning("[批处理] 排除列表为空,提示用户");
var result = System.Windows.MessageBox.Show(
"排除列表为空。对于复杂路径,建议先运行\"生成动画\"进行预计算碰撞分析,设置排除列表后再添加到批处理。\n\n" +
"• 是:继续添加(不推荐,可能检测时间过长)\n" +
"• 否:取消添加,先去设置排除列表\n\n" +
"提示:排除列表可以显著减少复杂路径的碰撞检测时间。",
"添加到批处理确认",
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Warning
);
if (result == System.Windows.MessageBoxResult.No)
{
LogManager.Info("[批处理] 用户选择取消添加,去设置排除列表");
return;
}
LogManager.Info("[批处理] 用户选择继续添加(不使用排除列表)");
}
LogManager.Info($"[批处理] 添加路径到批处理队列: {CurrentPathRoute.Name}");
// 🔥 获取该路径最新的检测记录ID
int? detectionRecordId = null;
var pathDatabase = _pathPlanningManager?.GetPathDatabase();
if (pathDatabase != null)
{
var latestRecord = pathDatabase.GetLatestCollisionDetectionRecord(CurrentPathRoute.Id);
detectionRecordId = latestRecord?.Id;
if (detectionRecordId.HasValue)
{
LogManager.Info($"[批处理] 关联检测记录 (Id={detectionRecordId})");
}
else
{
LogManager.Warning("[批处理] 未找到检测记录,请先运行\"生成动画\"");
}
}
// 创建批处理队列项
var queueItem = new BatchQueueItem
{
@ -4594,7 +4774,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
DetectAllObjects = !IsManualCollisionTargetEnabled,
// 角度修正配置
ObjectRotationCorrection = _objectRotationCorrection
ObjectRotationCorrection = _objectRotationCorrection,
// 🔥 关联的检测记录ID
DetectionRecordId = detectionRecordId
};
// 调用批处理管理器添加到队列
@ -4669,6 +4852,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
// 🔥 注意:排除列表和手工目标已通过 DetectionRecordId 关联到 CollisionDetectionRecords 表
// 不需要再保存到 ModelItemReferences 表
LogManager.Info($"[批处理] 已创建队列项: {CurrentPathRoute.Name}, " +
$"虚拟物体: {queueItem.IsVirtualObject}, " +
$"帧率: {queueItem.FrameRate}, " +

View File

@ -96,20 +96,16 @@
<!-- 热点列表 -->
<Border Grid.Row="3" BorderBrush="{StaticResource NavisworksLightBrush}"
BorderThickness="1" CornerRadius="3" Margin="0,0,0,12">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<DataGrid x:Name="HotspotsDataGrid"
AutoGenerateColumns="False"
CanUserAddRows="False"
SelectionMode="Single"
GridLinesVisibility="Horizontal"
BorderThickness="0"
HeadersVisibility="Column"
RowBackground="White"
AlternatingRowBackground="{StaticResource NavisworksBackgroundBrush}"
MinHeight="350"
MaxHeight="450"
SelectionChanged="HotspotsDataGrid_SelectionChanged">
<DataGrid x:Name="HotspotsDataGrid"
AutoGenerateColumns="False"
CanUserAddRows="False"
SelectionMode="Single"
GridLinesVisibility="Horizontal"
BorderThickness="0"
HeadersVisibility="Column"
RowBackground="White"
AlternatingRowBackground="{StaticResource NavisworksBackgroundBrush}"
SelectionChanged="HotspotsDataGrid_SelectionChanged">
<DataGrid.Columns>
<DataGridTextColumn Header="序号" Width="45"
Binding="{Binding Index}" IsReadOnly="True">
@ -160,8 +156,7 @@
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</DataGrid>
</Border>
<!-- 选中物体详情 -->