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

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

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Autodesk.Navisworks.Api; using Autodesk.Navisworks.Api;
using NavisworksTransport.Core.Animation;
using NavisworksTransport.Core.Collision; using NavisworksTransport.Core.Collision;
using NavisworksTransport.Core.Models; using NavisworksTransport.Core.Models;
@ -507,6 +508,19 @@ namespace NavisworksTransport.Core
// 统一准备碰撞检测(根据模式自动决定是否构建全局缓存) // 统一准备碰撞检测(根据模式自动决定是否构建全局缓存)
ClashDetectiveIntegration.PrepareCollisionDetection(animatedObject, isManualMode, manualDetectionTargets); 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调用 // 在主线程执行Navisworks API调用
var result = await UIStateManager.Instance.ExecuteUIUpdateAsync(() => var result = await UIStateManager.Instance.ExecuteUIUpdateAsync(() =>
{ {
@ -522,6 +536,7 @@ namespace NavisworksTransport.Core
CollisionDetectionEnabled = true, CollisionDetectionEnabled = true,
ReportGenerationEnabled = true ReportGenerationEnabled = true
}; };
var frames = _processor.PrecomputeFrames( var frames = _processor.PrecomputeFrames(
pathRoute, pathRoute,
animatedObject, animatedObject,
@ -533,7 +548,8 @@ namespace NavisworksTransport.Core
config.DurationSeconds, config.DurationSeconds,
config.DetectionToleranceMeters, config.DetectionToleranceMeters,
manualDetectionTargets, manualDetectionTargets,
item.ObjectRotationCorrection item.ObjectRotationCorrection,
excludedObjects
); );
// 检查预计算是否成功 // 检查预计算是否成功
@ -561,7 +577,8 @@ namespace NavisworksTransport.Core
item.VirtualObjectLength, item.VirtualObjectLength,
item.VirtualObjectWidth, item.VirtualObjectWidth,
item.VirtualObjectHeight, item.VirtualObjectHeight,
pathPoints pathPoints,
item.DetectionRecordId
); );
// 🔥 使用 ClashDetective 确认的碰撞数,而不是预计算的碰撞数 // 🔥 使用 ClashDetective 确认的碰撞数,而不是预计算的碰撞数
@ -720,7 +737,331 @@ namespace NavisworksTransport.Core
await _database.DeleteBatchQueueItemAsync(itemId); 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> /// <summary>

View File

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

View File

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

View File

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

View File

@ -200,7 +200,9 @@ namespace NavisworksTransport
VirtualObjectLength REAL, VirtualObjectLength REAL,
VirtualObjectWidth REAL, VirtualObjectWidth REAL,
VirtualObjectHeight 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_result ON ClashDetectiveExcludedObjects(ResultId)");
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_excluded_object ON ClashDetectiveExcludedObjects(ExcludedObjectId)"); 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> /// <summary>
@ -801,10 +874,10 @@ namespace NavisworksTransport
INSERT INTO ClashDetectiveResults INSERT INTO ClashDetectiveResults
(TestName, RouteId, TestTime, CollisionCount, AnimationCollisionCount, (TestName, RouteId, TestTime, CollisionCount, AnimationCollisionCount,
FrameRate, Duration, DetectionGap, AnimatedObjectName, IsVirtualObject, ObjectModelIndex, ObjectPathId, FrameRate, Duration, DetectionGap, AnimatedObjectName, IsVirtualObject, ObjectModelIndex, ObjectPathId,
VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight, CreatedAt) VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight, CreatedAt, DetectionRecordId)
VALUES (@testName, @routeId, @testTime, @collisionCount, @animationCollisionCount, VALUES (@testName, @routeId, @testTime, @collisionCount, @animationCollisionCount,
@frameRate, @duration, @detectionGap, @animatedObjectName, @isVirtualObject, @objectModelIndex, @objectPathId, @frameRate, @duration, @detectionGap, @animatedObjectName, @isVirtualObject, @objectModelIndex, @objectPathId,
@virtualObjectLength, @virtualObjectWidth, @virtualObjectHeight, @createdAt) @virtualObjectLength, @virtualObjectWidth, @virtualObjectHeight, @createdAt, @detectionRecordId)
"; ";
long newId = 0; long newId = 0;
@ -826,6 +899,7 @@ namespace NavisworksTransport
cmd.Parameters.AddWithValue("@virtualObjectWidth", record.VirtualObjectWidth); cmd.Parameters.AddWithValue("@virtualObjectWidth", record.VirtualObjectWidth);
cmd.Parameters.AddWithValue("@virtualObjectHeight", record.VirtualObjectHeight); cmd.Parameters.AddWithValue("@virtualObjectHeight", record.VirtualObjectHeight);
cmd.Parameters.AddWithValue("@createdAt", record.CreatedAt); cmd.Parameters.AddWithValue("@createdAt", record.CreatedAt);
cmd.Parameters.AddWithValue("@detectionRecordId", record.DetectionRecordId ?? (object)DBNull.Value);
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
newId = _connection.LastInsertRowId; newId = _connection.LastInsertRowId;
} }
@ -2110,6 +2184,321 @@ namespace NavisworksTransport
#endregion #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> /// <summary>
/// 执行非查询SQL语句 /// 执行非查询SQL语句
/// </summary> /// </summary>
@ -2275,14 +2664,16 @@ namespace NavisworksTransport
FrameRate, DurationSeconds, DetectionToleranceMeters, FrameRate, DurationSeconds, DetectionToleranceMeters,
IsVirtualObject, VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight, IsVirtualObject, VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight,
DetectAllObjects, DetectAllObjects,
ClashDetectiveTestName, CollisionCount, ObjectRotationCorrection ClashDetectiveTestName, CollisionCount, ObjectRotationCorrection,
DetectionRecordId
) )
VALUES ( VALUES (
@RouteId, @Status, @CreatedTime, @StartTime, @EndTime, @ErrorMessage, @RouteId, @Status, @CreatedTime, @StartTime, @EndTime, @ErrorMessage,
@FrameRate, @DurationSeconds, @DetectionToleranceMeters, @FrameRate, @DurationSeconds, @DetectionToleranceMeters,
@IsVirtualObject, @VirtualObjectLength, @VirtualObjectWidth, @VirtualObjectHeight, @IsVirtualObject, @VirtualObjectLength, @VirtualObjectWidth, @VirtualObjectHeight,
@DetectAllObjects, @DetectAllObjects,
@ClashDetectiveTestName, @CollisionCount, @ObjectRotationCorrection @ClashDetectiveTestName, @CollisionCount, @ObjectRotationCorrection,
@DetectionRecordId
); );
SELECT last_insert_rowid();"; SELECT last_insert_rowid();";
@ -2303,6 +2694,7 @@ namespace NavisworksTransport
cmd.Parameters.AddWithValue("@ClashDetectiveTestName", item.ClashDetectiveTestName ?? ""); cmd.Parameters.AddWithValue("@ClashDetectiveTestName", item.ClashDetectiveTestName ?? "");
cmd.Parameters.AddWithValue("@CollisionCount", item.CollisionCount ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@CollisionCount", item.CollisionCount ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@ObjectRotationCorrection", item.ObjectRotationCorrection); cmd.Parameters.AddWithValue("@ObjectRotationCorrection", item.ObjectRotationCorrection);
cmd.Parameters.AddWithValue("@DetectionRecordId", item.DetectionRecordId ?? (object)DBNull.Value);
var queueItemId = Convert.ToInt32(cmd.ExecuteScalar()); var queueItemId = Convert.ToInt32(cmd.ExecuteScalar());
@ -2345,7 +2737,8 @@ namespace NavisworksTransport
DetectAllObjects = Convert.ToBoolean(reader["DetectAllObjects"]), DetectAllObjects = Convert.ToBoolean(reader["DetectAllObjects"]),
ClashDetectiveTestName = reader["ClashDetectiveTestName"].ToString(), ClashDetectiveTestName = reader["ClashDetectiveTestName"].ToString(),
CollisionCount = !Convert.IsDBNull(reader["CollisionCount"]) ? (int?)Convert.ToInt32(reader["CollisionCount"]) : null, 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 属性 // 填充 MovingObjectName 属性
@ -2378,7 +2771,8 @@ namespace NavisworksTransport
bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionToleranceMeters, bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionToleranceMeters,
bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight, bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight,
bqi.DetectAllObjects, bqi.DetectAllObjects,
bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection,
bqi.DetectionRecordId
FROM BatchQueueItems bqi FROM BatchQueueItems bqi
INNER JOIN PathRoutes pr ON bqi.RouteId = pr.Id"; INNER JOIN PathRoutes pr ON bqi.RouteId = pr.Id";
var conditions = new List<string>(); var conditions = new List<string>();
@ -2437,7 +2831,8 @@ namespace NavisworksTransport
bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionToleranceMeters, bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionToleranceMeters,
bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight, bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight,
bqi.DetectAllObjects, bqi.DetectAllObjects,
bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection,
bqi.DetectionRecordId
FROM BatchQueueItems bqi FROM BatchQueueItems bqi
INNER JOIN PathRoutes pr ON bqi.RouteId = pr.Id INNER JOIN PathRoutes pr ON bqi.RouteId = pr.Id
WHERE bqi.Id = @Id"; WHERE bqi.Id = @Id";
@ -3030,6 +3425,7 @@ namespace NavisworksTransport
public double VirtualObjectWidth { get; set; } public double VirtualObjectWidth { get; set; }
public double VirtualObjectHeight { get; set; } public double VirtualObjectHeight { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public int? DetectionRecordId { get; set; } // 关联到检测记录
} }
/// <summary> /// <summary>
@ -3053,6 +3449,73 @@ namespace NavisworksTransport
public bool HasPositionInfo { get; set; } 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>
/// 碰撞报告截图记录 /// 碰撞报告截图记录
/// </summary> /// </summary>

View File

@ -2023,8 +2023,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 获取路由ID // 获取路由ID
string routeId = CurrentPathRoute?.Id ?? ""; string routeId = CurrentPathRoute?.Id ?? "";
// 先保存排除对象到数据库确保排除对象有记录ID // 注意:检测记录(包括排除列表)已在生成动画时保存到 CollisionDetectionRecords 表
_pathAnimationManager?.SaveExcludedObjectsToDatabase(pathDatabase, routeId); // 这里只保存碰撞报告,不重复保存排除列表
// 从报告中获取所有需要的数据 // 从报告中获取所有需要的数据
await Task.Run(() => await Task.Run(() =>
@ -2979,6 +2979,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 用户选择直接继续(无排除对象) // 用户选择直接继续(无排除对象)
LogManager.Info("[碰撞分析] 用户选择直接继续生成"); LogManager.Info("[碰撞分析] 用户选择直接继续生成");
UpdateMainStatus($"预计算碰撞分析完成,共 {totalCollisions} 个候选碰撞"); UpdateMainStatus($"预计算碰撞分析完成,共 {totalCollisions} 个候选碰撞");
// 🔥 保存检测记录到数据库(唯一保存位置:生成动画完成后)
SaveCollisionDetectionRecord();
} }
} }
catch (Exception ex) 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() private void ExecuteToggleWireframeMode()
{ {
try try
@ -4568,8 +4707,49 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return; 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}"); 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 var queueItem = new BatchQueueItem
{ {
@ -4594,7 +4774,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
DetectAllObjects = !IsManualCollisionTargetEnabled, 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}, " + LogManager.Info($"[批处理] 已创建队列项: {CurrentPathRoute.Name}, " +
$"虚拟物体: {queueItem.IsVirtualObject}, " + $"虚拟物体: {queueItem.IsVirtualObject}, " +
$"帧率: {queueItem.FrameRate}, " + $"帧率: {queueItem.FrameRate}, " +

View File

@ -96,8 +96,6 @@
<!-- 热点列表 --> <!-- 热点列表 -->
<Border Grid.Row="3" BorderBrush="{StaticResource NavisworksLightBrush}" <Border Grid.Row="3" BorderBrush="{StaticResource NavisworksLightBrush}"
BorderThickness="1" CornerRadius="3" Margin="0,0,0,12"> BorderThickness="1" CornerRadius="3" Margin="0,0,0,12">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<DataGrid x:Name="HotspotsDataGrid" <DataGrid x:Name="HotspotsDataGrid"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"
@ -107,8 +105,6 @@
HeadersVisibility="Column" HeadersVisibility="Column"
RowBackground="White" RowBackground="White"
AlternatingRowBackground="{StaticResource NavisworksBackgroundBrush}" AlternatingRowBackground="{StaticResource NavisworksBackgroundBrush}"
MinHeight="350"
MaxHeight="450"
SelectionChanged="HotspotsDataGrid_SelectionChanged"> SelectionChanged="HotspotsDataGrid_SelectionChanged">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="序号" Width="45" <DataGridTextColumn Header="序号" Width="45"
@ -161,7 +157,6 @@
</DataGridTextColumn> </DataGridTextColumn>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</ScrollViewer>
</Border> </Border>
<!-- 选中物体详情 --> <!-- 选中物体详情 -->