NavisworksTransport/src/Core/Collision/ClashDetectiveIntegration.cs

1751 lines
77 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Clash;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Animation;
using NavisworksTransport.Utils;
namespace NavisworksTransport
{
/// <summary>
/// Clash Detective 集成管理器
/// 实现动态碰撞检测与Clash Detective窗口的联动
/// </summary>
public class ClashDetectiveIntegration
{
private static ClashDetectiveIntegration _instance;
private DocumentClash _documentClash;
private List<ClashResult> _currentCollisions;
// 通道对象缓存,用于优化碰撞检测性能
private static HashSet<ModelItem> _channelObjectsCache = null;
private static readonly object _cacheLock = new object();
// 几何对象列表缓存,用于避免重复获取对象列表
// 使用 ModelItemCollection 而非 List<ModelItem> 以避免昂贵的 ToList() 转换
private static ModelItemCollection _allGeometryItemsCache = null;
// 已过滤通道对象的几何对象列表缓存(供空间索引使用)
private static List<ModelItem> _nonChannelGeometryItemsCache = null;
// 🔥 移动物体引用(用于从空间索引中排除移动物体及其所有子节点)
private static ModelItem _animatedObject = null;
// 碰撞检测计数器
private int _animationCollisionCount = 0; // 动画过程中简单包围盒检测的碰撞数量
private int _clashDetectiveCollisionCount = 0; // Clash Detective最终检测的碰撞数量
/// <summary>
/// 动画过程中检测到的碰撞数量(仅供参考统计)
/// </summary>
public int AnimationCollisionCount
{
get { return _animationCollisionCount; }
}
/// <summary>
/// Clash Detective检测到的权威碰撞数量
/// </summary>
public int ClashDetectiveCollisionCount
{
get { return _clashDetectiveCollisionCount; }
}
/// <summary>
/// 当前ClashDetective测试名称
/// </summary>
public string CurrentTestName
{
get { return _currentTestName; }
}
private string _currentTestName;
/// <summary>
/// 单例实例
/// </summary>
public static ClashDetectiveIntegration Instance
{
get
{
if (_instance == null)
{
_instance = new ClashDetectiveIntegration();
}
return _instance;
}
}
/// <summary>
/// 当前检测到的碰撞结果
/// </summary>
public List<ClashResult> CurrentCollisions
{
get { return _currentCollisions ?? new List<ClashResult>(); }
}
/// <summary>
/// 碰撞检测结果变化事件
/// </summary>
public event EventHandler<CollisionDetectedEventArgs> CollisionDetected;
/// <summary>
/// ClashDetective结果保存到数据库事件
/// </summary>
public event EventHandler<ClashDetectiveResultSavedEventArgs> ClashDetectiveResultSaved;
private ClashDetectiveIntegration()
{
_currentCollisions = new List<ClashResult>();
// 自动初始化 Clash Detective 集成
try
{
LogManager.Info("初始化Clash Detective集成.NET API模式...");
// 直接使用.NET API获取Clash文档
_documentClash = Application.ActiveDocument.GetClash();
if (_documentClash != null)
{
LogManager.Info("成功获取.NET API Clash文档");
LogManager.Info("Clash Detective集成初始化成功.NET API模式");
}
else
{
LogManager.Warning("无法获取Clash文档Clash Detective可能未安装");
}
}
catch (Exception ex)
{
LogManager.Error($"初始化Clash Detective集成失败: {ex.Message}");
_documentClash = null;
}
}
private List<CollisionResult> _deduplicatedCollisionResults = new List<CollisionResult>(); // 去重后的预计算碰撞结果
private readonly object _resultsLock = new object();
// ClashDetective测试结果缓存pathName -> List<CollisionResult>
// 使用自定义的CollisionResult类型支持修改Item1和Item2
private Dictionary<string, List<CollisionResult>> _clashDetectiveResultsCache = new Dictionary<string, List<CollisionResult>>();
private readonly object _clashResultsCacheLock = new object();
/// <summary>
/// 获取去重后的预计算碰撞结果(第一次去重,按碰撞对象对去重)
/// </summary>
public List<CollisionResult> GetDeduplicatedCollisionResults()
{
lock (_resultsLock)
{
LogManager.Debug($"[GetDeduplicatedCollisionResults] 当前去重缓存数量: {_deduplicatedCollisionResults.Count}");
return new List<CollisionResult>(_deduplicatedCollisionResults);
}
}
/// <summary>
/// 保存ClashDetective结果到数据库
/// </summary>
private void SaveClashDetectiveResultToDatabase(string routeId, List<CollisionResult> clashResults,
int frameRate, double duration, double detectionGap,
ModelItem animatedObject, bool isVirtualVehicle,
double virtualVehicleLength, double virtualVehicleWidth, double virtualVehicleHeight)
{
try
{
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase != null)
{
// 获取动画对象名称和路径信息
string animatedObjectName = "未知对象";
int? vehicleModelIndex = null;
string vehiclePathId = null;
if (!isVirtualVehicle && animatedObject != null)
{
animatedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animatedObject);
// 获取真实车辆的 PathId 信息
var pathId = Application.ActiveDocument.Models.CreatePathId(animatedObject);
vehicleModelIndex = pathId.ModelIndex;
vehiclePathId = pathId.PathId;
}
else if (isVirtualVehicle)
{
animatedObjectName = "虚拟车辆";
}
// 打印虚拟车辆尺寸(用于调试)
LogManager.Info($"[SaveClashDetectiveResultToDatabase] IsVirtualVehicle={isVirtualVehicle}, 虚拟车辆尺寸: Length={virtualVehicleLength:F2}m, Width={virtualVehicleWidth:F2}m, Height={virtualVehicleHeight:F2}m");
var record = new ClashDetectiveResultRecord
{
TestName = _currentTestName,
RouteId = routeId ?? "",
TestTime = DateTime.Now,
CollisionCount = clashResults.Count,
AnimationCollisionCount = _animationCollisionCount,
FrameRate = frameRate,
Duration = duration,
DetectionGap = detectionGap,
AnimatedObjectName = animatedObjectName,
IsVirtualVehicle = isVirtualVehicle,
VehicleModelIndex = vehicleModelIndex,
VehiclePathId = vehiclePathId,
VirtualVehicleLength = virtualVehicleLength,
VirtualVehicleWidth = virtualVehicleWidth,
VirtualVehicleHeight = virtualVehicleHeight,
CreatedAt = DateTime.Now
};
var resultId = pathDatabase.SaveClashDetectiveResult(record);
LogManager.Info($"ClashDetective结果已保存到数据库Id={resultId}, IsVirtualVehicle={isVirtualVehicle}");
// 保存被撞物体信息只保存Item2不保存车辆Item1
var collisionObjects = new List<ClashDetectiveCollisionObjectRecord>();
foreach (var collision in clashResults)
{
// 只保存被撞到的物体Item2
if (collision.Item2 != null)
{
// 使用 CreatePathId API 获取 ModelIndex 和 PathId
var pathId = Application.ActiveDocument.Models.CreatePathId(collision.Item2);
collisionObjects.Add(new ClashDetectiveCollisionObjectRecord
{
ResultId = resultId,
ModelIndex = pathId.ModelIndex,
PathId = pathId.PathId,
DisplayName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2),
ObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2)
});
}
}
if (collisionObjects.Count > 0)
{
pathDatabase.SaveClashDetectiveCollisionObjects(resultId, collisionObjects);
LogManager.Info($"已保存 {collisionObjects.Count} 个被撞物体到数据库");
}
// 触发结果保存事件通知UI刷新列表从RouteId获取PathName
var pathName = GetPathNameByRouteId(routeId);
ClashDetectiveResultSaved?.Invoke(this, new ClashDetectiveResultSavedEventArgs(pathName, _currentTestName, clashResults.Count));
}
}
catch (Exception ex)
{
LogManager.Error($"保存ClashDetective结果到数据库失败: {ex.Message}");
// 不影响主流程,继续执行
}
}
/// <summary>
/// 根据RouteId获取路径名称
/// </summary>
private string GetPathNameByRouteId(string routeId)
{
if (string.IsNullOrEmpty(routeId))
return "未知路径";
try
{
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase != null)
{
var pathRoute = pathDatabase.GetPathRouteSync(routeId);
return pathRoute?.Name ?? "未知路径";
}
}
catch (Exception ex)
{
LogManager.Error($"获取路径名称失败: {ex.Message}");
}
return "未知路径";
}
/// <summary>
/// 从数据库加载ClashDetective碰撞结果
/// </summary>
/// <param name="testName">测试名称</param>
/// <returns>碰撞结果列表如果加载失败返回null</returns>
public List<CollisionResult> GetClashDetectiveResultsFromDatabase(string testName)
{
try
{
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase == null)
{
LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] PathDatabase不可用");
return null;
}
// 1. 从数据库读取测试信息添加JOIN获取PathName
var testInfoSql = @"
SELECT cdr.Id, pr.Name AS PathName, cdr.RouteId, cdr.IsVirtualVehicle, cdr.VehicleModelIndex, cdr.VehiclePathId,
cdr.VirtualVehicleLength, cdr.VirtualVehicleWidth, cdr.VirtualVehicleHeight, cdr.ScreenshotPath
FROM ClashDetectiveResults cdr
INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
WHERE cdr.TestName = @testName
";
ClashDetectiveResultRecord testInfo = null;
using (var cmd = new System.Data.SQLite.SQLiteCommand(testInfoSql, pathDatabase._connection))
{
cmd.Parameters.AddWithValue("@testName", testName);
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
testInfo = new ClashDetectiveResultRecord
{
Id = Convert.ToInt32(reader["Id"]),
PathName = reader["PathName"].ToString(),
RouteId = reader["RouteId"]?.ToString(),
IsVirtualVehicle = Convert.ToBoolean(reader["IsVirtualVehicle"]),
VehicleModelIndex = reader["VehicleModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["VehicleModelIndex"]) : (int?)null,
VehiclePathId = reader["VehiclePathId"] != DBNull.Value ? reader["VehiclePathId"].ToString() : null,
VirtualVehicleLength = reader["VirtualVehicleLength"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleLength"]) : 0.0,
VirtualVehicleWidth = reader["VirtualVehicleWidth"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleWidth"]) : 0.0,
VirtualVehicleHeight = reader["VirtualVehicleHeight"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleHeight"]) : 0.0,
ScreenshotPath = reader["ScreenshotPath"] != DBNull.Value ? reader["ScreenshotPath"].ToString() : null
};
}
}
}
if (testInfo == null)
{
LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 未找到测试记录: {testName}");
return null;
}
// 2. 重建车辆对象
ModelItem vehicleObject = null;
if (testInfo.IsVirtualVehicle)
{
// 显示虚拟车辆
VirtualVehicleManager.Instance.ShowVirtualVehicle(
testInfo.VirtualVehicleLength,
testInfo.VirtualVehicleWidth,
testInfo.VirtualVehicleHeight
);
vehicleObject = VirtualVehicleManager.Instance.CurrentVirtualVehicle
?? throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 显示虚拟车辆失败");
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已显示虚拟车辆: {testInfo.VirtualVehicleLength}×{testInfo.VirtualVehicleWidth}×{testInfo.VirtualVehicleHeight}m");
}
else if (testInfo.VehicleModelIndex.HasValue && !string.IsNullOrEmpty(testInfo.VehiclePathId))
{
// 通过 PathId 查找真实车辆
try
{
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId
{
ModelIndex = testInfo.VehicleModelIndex.Value,
PathId = testInfo.VehiclePathId
};
vehicleObject = Application.ActiveDocument.Models.ResolvePathId(pathIdObj) ?? throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法通过 PathId 找到车辆对象: ModelIndex={testInfo.VehicleModelIndex}, PathId={testInfo.VehiclePathId}");
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已找到真实车辆: {ModelItemAnalysisHelper.GetSafeDisplayName(vehicleObject)}");
}
catch (Exception ex)
{
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] ResolvePathId 失败: ModelIndex={testInfo.VehicleModelIndex}, PathId={testInfo.VehiclePathId}", ex);
}
}
if (vehicleObject == null)
{
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法重建车辆对象: IsVirtualVehicle={testInfo.IsVirtualVehicle}, VehicleModelIndex={testInfo.VehicleModelIndex}, VehiclePathId={testInfo.VehiclePathId}");
}
// 如果是虚拟车辆,将其移动到路径起点
if (testInfo.IsVirtualVehicle && !string.IsNullOrEmpty(testInfo.RouteId))
{
try
{
var pathPlanningManager = PathPlanningManager.Instance;
var route = pathPlanningManager.GetAllRoutes().FirstOrDefault(r => r.Id == testInfo.RouteId);
if (route != null && route.Points != null && route.Points.Count > 0)
{
// 获取第一个点(起点)
var startPoint = route.Points.FirstOrDefault(p => p.Type == PathPointType.StartPoint) ?? route.Points[0];
var startPointPosition = startPoint.Position;
// 使用 PathAnimationManager 将车辆移动到起点
var pathAnimationManager = Core.Animation.PathAnimationManager.GetInstance();
pathAnimationManager.MoveVehicleToPathStart(vehicleObject, new List<Point3D> { startPointPosition, startPointPosition });
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 虚拟车辆已移动到路径起点: ({startPointPosition.X:F2}, {startPointPosition.Y:F2}, {startPointPosition.Z:F2})");
}
}
catch (Exception ex)
{
LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 移动虚拟车辆到起点失败: {ex.Message}");
}
}
// 3. 从数据库读取被撞物体信息
var collisionObjectsSql = @"
SELECT ModelIndex, PathId, DisplayName, ObjectName
FROM ClashDetectiveCollisionObjects
WHERE ResultId = @resultId
";
var collisionObjects = new List<ClashDetectiveCollisionObjectRecord>();
using (var cmd = new System.Data.SQLite.SQLiteCommand(collisionObjectsSql, pathDatabase._connection))
{
cmd.Parameters.AddWithValue("@resultId", testInfo.Id);
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
collisionObjects.Add(new ClashDetectiveCollisionObjectRecord
{
ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null,
PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null,
DisplayName = reader["DisplayName"]?.ToString(),
ObjectName = reader["ObjectName"]?.ToString()
});
}
}
}
// 4. 重建碰撞结果
var results = new List<CollisionResult>();
foreach (var obj in collisionObjects)
{
ModelItem collidedObject = null;
// 优先使用 ResolvePathId API新方式
if (obj.ModelIndex.HasValue && !string.IsNullOrEmpty(obj.PathId))
{
try
{
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId();
pathIdObj.ModelIndex = obj.ModelIndex.Value;
pathIdObj.PathId = obj.PathId;
collidedObject = Application.ActiveDocument.Models.ResolvePathId(pathIdObj);
}
catch (Exception ex)
{
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] ResolvePathId 失败: ModelIndex={obj.ModelIndex}, PathId={obj.PathId}", ex);
}
}
if (collidedObject == null)
{
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法通过路径找到车辆对象: {obj.ModelIndex},{obj.PathId}");
}
if (!ModelItemAnalysisHelper.IsModelItemValid(collidedObject))
{
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 找到的 ModelItem 无效: {obj.ModelIndex},{obj.PathId}");
}
// 使用重建对象的 DisplayName而不是数据库中保存的 DisplayName
var collidedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collidedObject);
var collisionResult = new CollisionResult
{
ClashGuid = Guid.NewGuid(),
DisplayName = $"历史碰撞: {collidedObjectName}",
Status = ClashResultStatus.Active,
Item1 = vehicleObject,
Item2 = collidedObject,
Center = collidedObject.BoundingBox().Center,
Distance = 0.0,
CreatedTime = DateTime.Now
};
results.Add(collisionResult);
}
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 从数据库加载测试 '{testName}' 完成,重建了 {results.Count} 个碰撞结果");
return results;
}
catch (Exception ex)
{
LogManager.Error($"[LoadClashDetectiveResultsFromDatabase] 加载失败: {ex.Message}", ex);
return null;
}
}
/// <summary>
/// 获取对象当前位置
/// </summary>
private Point3D GetObjectPosition(ModelItem item)
{
try
{
if (item == null) return new Point3D(0, 0, 0);
var bounds = item.BoundingBox();
if (bounds != null)
{
return new Point3D(
(bounds.Min.X + bounds.Max.X) / 2,
(bounds.Min.Y + bounds.Max.Y) / 2,
(bounds.Min.Z + bounds.Max.Z) / 2
);
}
return new Point3D(0, 0, 0);
}
catch (Exception ex)
{
LogManager.Error($"获取对象位置失败: {ex.Message}");
return new Point3D(0, 0, 0);
}
}
/// <summary>
/// 运行ClashDetective测试并保存到数据库公共方法供批处理和非批处理调用
/// </summary>
/// <returns>碰撞分组、主测试、确认的碰撞数量</returns>
public (ClashResultGroup collisionGroup, ClashTest addedMainTest, int confirmedCount) RunClashDetectiveTestsAndSaveToDatabase(
List<CollisionResult> precomputedCollisions,
double detectionGap,
string routeId,
ModelItem animatedObject,
bool isVirtualVehicle,
int frameRate,
double duration,
double virtualVehicleLength,
double virtualVehicleWidth,
double virtualVehicleHeight,
Progress progress = null)
{
LogManager.Info($"[ClashDetective] 开始运行碰撞检测并保存到数据库(容差: {detectionGap}米)");
// 过滤有效的碰撞
var validCollisions = precomputedCollisions.Where(collision =>
collision.HasPositionInfo &&
IsModelItemValid(collision.Item1) &&
IsModelItemValid(collision.Item2)
).ToList();
LogManager.Info($"[ClashDetective] 有效碰撞数量: {validCollisions.Count}");
// 第一步:创建主测试(不包含选择集,纯容器)
var mainTestName = $"碰撞检测_{DateTime.Now:MMdd_HHmmssfff}";
_currentTestName = mainTestName;
LogManager.Info($"[分组测试] 创建主测试: {mainTestName}");
// 创建主测试
var mainTest = new ClashTest
{
DisplayName = mainTestName,
TestType = ClashTestType.Hard,
Tolerance = detectionGap,
Guid = Guid.Empty,
MergeComposites = true
};
// 添加主测试到文档
_documentClash.TestsData.TestsAddCopy(mainTest);
LogManager.Info($"[分组测试] 主测试已添加到文档");
// 获取添加后的测试对象引用
var addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest;
if (addedMainTest == null)
{
LogManager.Error("[分组测试] 无法获取添加后的主测试对象");
return (null, null, 0);
}
// 如果没有有效碰撞,直接保存空记录并返回
if (validCollisions.Count == 0)
{
LogManager.Warning("[ClashDetective] 没有有效的碰撞,保存空碰撞记录");
// 重置碰撞计数器(重要:避免保留上次测试的值)
_clashDetectiveCollisionCount = 0;
// 保存到数据库
SaveClashDetectiveResultToDatabase(
routeId,
new List<CollisionResult>(),
frameRate,
duration,
detectionGap,
animatedObject,
isVirtualVehicle,
virtualVehicleLength,
virtualVehicleWidth,
virtualVehicleHeight
);
return (null, addedMainTest, 0);
}
// 第二步:创建分组并添加碰撞结果(应用智能去重)
// 1. 分组:按碰撞对象对分组
var groupedCollisions = validCollisions
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
.ToList();
LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对");
// 缓存去重后的碰撞结果(每组取第一个)
lock (_resultsLock)
{
_deduplicatedCollisionResults.Clear();
foreach (var group in groupedCollisions)
{
_deduplicatedCollisionResults.Add(group.First());
}
LogManager.Debug($"[去重缓存] 已缓存 {_deduplicatedCollisionResults.Count} 个去重后的碰撞结果");
}
var collisionGroup = new ClashResultGroup
{
DisplayName = $"碰撞检测组 ({groupedCollisions.Count} 个唯一碰撞对)"
};
LogManager.Info($"[分组测试] 创建碰撞分组: {collisionGroup.DisplayName}");
int confirmedCount = 0;
int skippedCount = 0;
var doc = Application.ActiveDocument;
// 收集所有临时测试,用于批量删除
var tempTestsToRemove = new List<ClashTest>();
// 2. 遍历每一组
for (int groupIndex = 0; groupIndex < groupedCollisions.Count; groupIndex++)
{
// 检查用户是否取消
if (progress != null && progress.IsCanceled)
{
LogManager.Info($"[分组测试] 用户取消操作,已处理 {groupIndex}/{groupedCollisions.Count} 组");
break;
}
var group = groupedCollisions[groupIndex];
// 3. 排序:按距离/重叠深度排序,优先检测最严重的碰撞
// 注意Distance通常越小或负值越大表示碰撞越深具体取决于计算方式
// 这里假设Distance越小越严重
var sortedCandidates = group.OrderBy(c => c.Distance).ToList();
bool pairConfirmed = false;
// 4. 验证即止:逐个检测候选帧
for (int i = 0; i < sortedCandidates.Count; i++)
{
var candidate = sortedCandidates[i];
try
{
var testAnimatedObject = candidate.Item1;
var modelItems = new ModelItemCollection { testAnimatedObject };
var targetPosition = candidate.Item1Position;
var currentBounds = testAnimatedObject.BoundingBox();
var currentPos = new Point3D(
(currentBounds.Min.X + currentBounds.Max.X) / 2,
(currentBounds.Min.Y + currentBounds.Max.Y) / 2,
(currentBounds.Min.Z + currentBounds.Max.Z) / 2
);
var offset = new Vector3D(
targetPosition.X - currentPos.X,
targetPosition.Y - currentPos.Y,
targetPosition.Z - currentPos.Z
);
var transform = Transform3D.CreateTranslation(offset);
doc.Models.OverridePermanentTransform(modelItems, transform, false);
var tempTestName = $"临时验证_{confirmedCount + 1}_{i}_{DateTime.Now:HHmmss_fff}";
// 🔥 优化3预配置 ClashTest避免后续 CreateCopy 和 TestsEditTestFromCopy 调用
var tempTest = new ClashTest
{
DisplayName = tempTestName,
TestType = ClashTestType.Hard,
Tolerance = detectionGap,
Guid = Guid.Empty,
MergeComposites = true
};
var selectionA = new ModelItemCollection { candidate.Item1 };
var selectionB = new ModelItemCollection { candidate.Item2 };
tempTest.SelectionA.Selection.CopyFrom(selectionA);
tempTest.SelectionB.Selection.CopyFrom(selectionB);
// 直接设置 PrimitiveTypes避免后续的 CreateCopy 和 TestsEditTestFromCopy
tempTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles;
tempTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
_documentClash.TestsData.TestsAddCopy(tempTest);
var addedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
if (addedTempTest != null)
{
// 🔥 优化3跳过了 addedTempTest.CreateCopy() 和 TestsEditTestFromCopy 调用
// 因为 PrimitiveTypes 已在创建 tempTest 时设置好
_documentClash.TestsData.TestsRunTest(addedTempTest);
var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
if (refreshedTempTest != null && refreshedTempTest.Children.Count > 0)
{
// !!!发现真实碰撞!!!
pairConfirmed = true;
confirmedCount++;
skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量
int subResultIndex = 1;
// 🔥 优化:预获取运动物体名称,避免在循环中重复获取
var animatedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animatedObject);
foreach (var child in refreshedTempTest.Children)
{
if (child is ClashResult result)
{
var copiedResult = result.CreateCopy() as ClashResult;
copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID
// 设置碰撞名称:运动物体名称直接使用预获取的,被撞物体查找父容器
var container2 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item2);
var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(container2);
var timeStamp = DateTime.Now.ToString("HHmmss");
copiedResult.DisplayName = $"物流碰撞#{confirmedCount:00}-{subResultIndex:00}_{timeStamp}: {animatedObjectName} ↔ {object2Name}";
collisionGroup.Children.Add(copiedResult);
subResultIndex++;
}
}
}
// 🔥 优化4收集测试用于批量删除而不是立即删除
tempTestsToRemove.Add(refreshedTempTest ?? addedTempTest);
if (pairConfirmed)
{
break;
}
}
}
catch (Exception itemEx)
{
LogManager.Error($"[ClashDetective] 候选检测失败: {itemEx.Message}");
}
}
}
// 🔥 优化4批量删除所有临时测试
if (tempTestsToRemove.Count > 0)
{
LogManager.Debug($"[分组测试] 批量删除 {tempTestsToRemove.Count} 个临时测试");
foreach (var test in tempTestsToRemove)
{
try
{
_documentClash.TestsData.TestsRemove(test);
}
catch (Exception ex)
{
LogManager.Debug($"[分组测试] 删除临时测试失败: {ex.Message}");
}
}
}
LogManager.Info($"[分组测试] ClashDetective检测完成: 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点");
// 第三步:处理碰撞结果
// 🔥 重要ClashDetective 返回的是几何体级别的碰撞结果
// Item1 是移动物体的组件名(如"车轮1"、"车轮2"),需要统一为移动物体本身
// Item2 是被撞物体,需要向上查找有意义的父级容器
var clashResults = new List<CollisionResult>();
foreach (var child in collisionGroup.Children)
{
if (child is ClashResult clashResult)
{
var compositeItem1 = animatedObject;
var compositeItem2 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item2);
var collisionResult = new CollisionResult
{
ClashGuid = clashResult.Guid,
DisplayName = clashResult.DisplayName,
Status = clashResult.Status,
Item1 = compositeItem1,
Item2 = compositeItem2,
Center = clashResult.Center,
Distance = clashResult.Distance,
CreatedTime = DateTime.Now
};
clashResults.Add(collisionResult);
}
}
// 🔥 新增:按碰撞对象对去重
// 原因:同一个移动物体与同一个被撞物体的多个组件碰撞,需要合并为一个记录
var finalClashResults = clashResults
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
.Select(g => g.First())
.ToList();
LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对");
// 🔥 优化:在去重后为结果设置详细的 DisplayName
// 这样只对保留的结果进行名称计算,避免在子碰撞结果上浪费性能
foreach (var result in finalClashResults)
{
if (result.Item1 != null && result.Item2 != null)
{
var object1Name = ModelItemAnalysisHelper.GetSafeDisplayName(result.Item1);
var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(result.Item2);
result.DisplayName = $"物流碰撞: {object1Name} ↔ {object2Name}";
}
}
// 缓存最终结果
lock (_clashResultsCacheLock)
{
_clashDetectiveResultsCache[_currentTestName] = finalClashResults;
}
LogManager.Info($"[ClashDetective] 已缓存结果:{finalClashResults.Count}个碰撞,测试名称:{_currentTestName}");
// 更新碰撞计数器
_clashDetectiveCollisionCount = clashResults.Count;
// 保存到数据库(使用去重后的结果)
SaveClashDetectiveResultToDatabase(routeId, finalClashResults, frameRate, duration, detectionGap, animatedObject, isVirtualVehicle,
virtualVehicleLength, virtualVehicleWidth, virtualVehicleHeight);
LogManager.Info($"[ClashDetective] 结果已保存到数据库");
// 第四步:将分组添加到主测试(由调用方完成)
return (collisionGroup, addedMainTest, confirmedCount);
}
/// <summary>
/// 动画结束后统一创建和运行ClashDetective碰撞测试
/// </summary>
/// <param name="precomputedCollisions">预计算碰撞结果</param>
/// <param name="detectionGap">检测间隙容差</param>
/// <param name="routeId">路由ID</param>
/// <param name="animatedObject">动画对象(如果是真实车辆)</param>
/// <param name="isVirtualVehicle">是否使用虚拟车辆</param>
/// <param name="frameRate">帧率</param>
/// <param name="duration">动画时长</param>
/// <param name="virtualVehicleLength">虚拟车辆长度(米)</param>
/// <param name="virtualVehicleWidth">虚拟车辆宽度(米)</param>
/// <param name="virtualVehicleHeight">虚拟车辆高度(米)</param>
/// <param name="pathPoints">路径点列表(用于测试完成后恢复物体位置)</param>
public void CreateAllAnimationCollisionTests(
List<CollisionResult> precomputedCollisions,
double detectionGap,
string routeId,
ModelItem animatedObject,
bool isVirtualVehicle,
int frameRate,
double duration,
double virtualVehicleLength,
double virtualVehicleWidth,
double virtualVehicleHeight,
List<Point3D> pathPoints = null)
{
try
{
LogManager.Info($"=== 使用预计算碰撞数据创建ClashDetective测试容差: {detectionGap}米)===");
LogManager.Info($"[预计算数据] 共有 {precomputedCollisions.Count} 个碰撞记录");
// 直接使用所有预计算结果,只过滤有效对象(不去重)
var collisionResults = precomputedCollisions
.Where(result => IsModelItemValid(result.Item1) && IsModelItemValid(result.Item2))
.ToList();
// 记录动画过程中的碰撞数量
_animationCollisionCount = collisionResults.Count;
LogManager.Info($"[预计算处理] 原始记录: {precomputedCollisions.Count},有效碰撞: {_animationCollisionCount}");
// 获取动画对象
if (collisionResults.Count > 0)
{
animatedObject = collisionResults[0].Item1;
}
var doc = Application.ActiveDocument;
// 调用公共方法运行ClashDetective测试并保存到数据库
Progress progress = Application.BeginProgress("碰撞检测数据分析中,请稍候...");
ClashTest addedMainTest = null;
ClashResultGroup collisionGroup = null;
int confirmedCount = 0;
try
{
var result = RunClashDetectiveTestsAndSaveToDatabase(
precomputedCollisions,
detectionGap,
routeId,
animatedObject,
isVirtualVehicle,
frameRate,
duration,
virtualVehicleLength,
virtualVehicleWidth,
virtualVehicleHeight,
progress
);
collisionGroup = result.collisionGroup;
addedMainTest = result.addedMainTest;
confirmedCount = result.confirmedCount;
}
finally
{
Application.EndProgress();
}
// UI操作将分组添加到主测试
if (collisionGroup != null && addedMainTest != null && collisionGroup.Children.Count > 0)
{
_documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup);
}
else
{
LogManager.Warning("[分组测试] 分组为空(未检测到真实几何碰撞),未添加到主测试");
}
// 碰撞测试完成后,将物体恢复到路径起点位置(如果提供了路径点)
if (animatedObject != null && IsModelItemValid(animatedObject) && pathPoints != null && pathPoints.Count > 0)
{
try
{
PathAnimationManager.GetInstance().MoveVehicleToPathStart(animatedObject, pathPoints);
LogManager.Info($"已将 {animatedObject.DisplayName} 恢复到路径起点位置");
}
catch (Exception restoreEx)
{
LogManager.Error($"恢复物体到路径起点失败: {restoreEx.Message}");
}
}
// 检查是否成功创建了主测试
var finalMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName.Contains("碰撞检测")) as ClashTest;
if (finalMainTest != null)
{
// 刷新Clash Detective窗口
RefreshClashDetectiveUI();
// 自动高亮ClashDetective结果
ModelHighlightHelper.HighlightClashDetectiveResults(_currentTestName, GetCurrentPathClashResults);
LogManager.Info("自动高亮ClashDetective检测结果完成");
LogManager.Info($"=== 碰撞统计最终结果 ===");
LogManager.Info($"动画过程包围盒检测: {_animationCollisionCount}个碰撞 (参考值)");
LogManager.Info($"Clash Detective权威结果: {_clashDetectiveCollisionCount}个碰撞 (权威数据)");
}
// 🔥 无论是否有主测试,都触发碰撞检测完成事件,通知生成报告
// 使用ClashDetective权威结果而不是预计算结果
// 如果权威结果为0个碰撞传入空列表以触发祝贺对话框
var finalCollisions = _clashDetectiveCollisionCount > 0 ? collisionResults : new List<CollisionResult>();
LogManager.Info($"触发CollisionDetected事件通知生成报告{_clashDetectiveCollisionCount}个碰撞,权威数据)");
var eventArgs = new CollisionDetectedEventArgs(finalCollisions);
OnCollisionDetected(eventArgs);
}
catch (Exception ex)
{
LogManager.Error($"动画结束后创建测试失败: {ex.Message}");
}
}
/// <summary>
/// 刷新Clash Detective UI - 安全版本
/// </summary>
private void RefreshClashDetectiveUI()
{
try
{
// 检查文档是否有效
var doc = Application.ActiveDocument;
if (doc == null || doc.IsClear)
{
LogManager.Warning("文档无效跳过UI刷新");
return;
}
// 检查视图是否有效
if (doc.ActiveView != null)
{
try
{
doc.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
LogManager.Info("Clash Detective UI已刷新");
}
catch (ObjectDisposedException)
{
LogManager.Warning("视图对象已释放,跳过重绘");
}
catch (Exception ex)
{
LogManager.Error($"重绘视图失败: {ex.Message}");
}
}
else
{
LogManager.Warning("活动视图无效跳过UI刷新");
}
}
catch (ObjectDisposedException)
{
LogManager.Warning("文档或视图对象已释放跳过UI刷新");
}
catch (Exception ex)
{
LogManager.Error($"刷新Clash Detective UI失败: {ex.Message}");
}
}
/// <summary>
/// 检查ModelItem是否仍然有效
/// </summary>
private bool IsModelItemValid(ModelItem item)
{
try
{
if (item == null)
return false;
// 尝试访问对象的属性来检查是否有效
var displayName = item.DisplayName;
var hasGeometry = item.HasGeometry;
// 额外检查:确保对象没有被释放
var boundingBox = item.BoundingBox();
return true;
}
catch (Exception ex)
{
LogManager.Debug($"ModelItem无效: {ex.Message}");
return false;
}
}
/// <summary>
/// 获取所有碰撞检测测试
/// </summary>
/// <returns>碰撞检测测试列表</returns>
public List<ClashTest> GetClashTests()
{
if (_documentClash == null)
{
throw new InvalidOperationException("ClashDetective文档未初始化");
}
return _documentClash.TestsData.Tests.Cast<ClashTest>()
.Where(t => t.DisplayName.Contains("碰撞检测"))
.ToList();
}
/// <summary>
/// 获取当前路径的Clash结果
/// </summary>
/// <param name="pathName">路径名称</param>
/// <returns>当前路径的碰撞结果列表(如果没有结果返回空列表)</returns>
public List<CollisionResult> GetCurrentPathClashResults(string testName)
{
lock (_clashResultsCacheLock)
{
// 先尝试从缓存获取
if (_clashDetectiveResultsCache.TryGetValue(testName, out var cachedResults))
{
LogManager.Debug($"[ClashDetective结果] 从缓存获取 '{testName}' 的结果:{cachedResults.Count}个碰撞");
return cachedResults;
}
// 缓存中没有,尝试从数据库加载
LogManager.Info($"[ClashDetective结果] 缓存中没有 '{testName}',尝试从数据库加载");
var loadedResults = GetClashDetectiveResultsFromDatabase(testName);
if (loadedResults != null && loadedResults.Count > 0)
{
// 缓存加载的结果
_clashDetectiveResultsCache[testName] = loadedResults;
LogManager.Info($"[ClashDetective结果] 已从数据库加载 '{testName}' 的结果并缓存:{loadedResults.Count}个碰撞");
return loadedResults;
}
// 没有找到结果,返回空列表
LogManager.Info($"[ClashDetective结果] 未找到测试 '{testName}' 的碰撞结果,返回空列表");
return new List<CollisionResult>();
}
}
/// <summary>
/// 获取当前测试的碰撞结果(便捷方法)
/// </summary>
/// <returns>当前测试的碰撞结果列表</returns>
public List<CollisionResult> GetCurrentTestResults()
{
return GetCurrentPathClashResults(_currentTestName);
}
/// <summary>
/// 清理当前测试的缓存
/// </summary>
public void ClearCurrentTestCache()
{
if (string.IsNullOrEmpty(_currentTestName))
{
return;
}
lock (_clashResultsCacheLock)
{
if (_clashDetectiveResultsCache.ContainsKey(_currentTestName))
{
_clashDetectiveResultsCache.Remove(_currentTestName);
LogManager.Info($"[ClashDetective缓存] 已清理当前测试 '{_currentTestName}' 的缓存");
}
}
}
/// <summary>
/// 清空当前测试名称
/// </summary>
public void ClearCurrentTestName()
{
if (!string.IsNullOrEmpty(_currentTestName))
{
LogManager.Info($"[ClashDetective] 清空当前测试名称: {_currentTestName}");
_currentTestName = null;
}
}
/// <summary>
/// 设置当前测试名称
/// </summary>
public void SetCurrentTestName(string testName)
{
_currentTestName = testName;
LogManager.Info($"[ClashDetective] 设置当前测试名称: {_currentTestName}");
}
/// <summary>
/// 从测试中提取碰撞结果
/// </summary>
/// <param name="clashTest">测试</param>
/// <returns>碰撞结果列表</returns>
public List<ClashResult> ExtractClashResultsFromTest(ClashTest clashTest)
{
var results = new List<ClashResult>();
ExtractClashResultsFromTestRecursive(clashTest, results);
return results;
}
/// <summary>
/// 递归提取测试中的碰撞结果
/// </summary>
/// <param name="clashTest">测试</param>
/// <param name="results">结果列表</param>
private void ExtractClashResultsFromTestRecursive(ClashTest clashTest, List<ClashResult> results)
{
foreach (var child in clashTest.Children)
{
if (child is ClashResult clashResult)
{
results.Add(clashResult);
}
else if (child is ClashResultGroup resultGroup)
{
ExtractClashResultsFromGroupRecursive(resultGroup, results);
}
}
}
/// <summary>
/// 递归提取分组中的碰撞结果
/// </summary>
/// <param name="group">分组</param>
/// <param name="results">结果列表</param>
private void ExtractClashResultsFromGroupRecursive(ClashResultGroup group, List<ClashResult> results)
{
foreach (var child in group.Children)
{
if (child is ClashResult result)
{
results.Add(result);
}
else if (child is ClashResultGroup subGroup)
{
ExtractClashResultsFromGroupRecursive(subGroup, results);
}
}
}
/// <summary>
/// 获取所有碰撞检测的碰撞结果
/// </summary>
/// <returns>碰撞结果列表</returns>
public List<ClashResult> GetAllClashResults()
{
var tests = GetClashTests();
var allResults = new List<ClashResult>();
foreach (var test in tests)
{
var results = ExtractClashResultsFromTest(test);
allResults.AddRange(results);
}
return allResults;
}
/// <summary>
/// 构建通道对象缓存,一次性扫描所有对象,避免重复的属性查询
/// </summary>
public void BuildChannelObjectsCache()
{
lock (_cacheLock)
{
if (_channelObjectsCache != null) return; // 双重检查锁定
var cacheStopwatch = new System.Diagnostics.Stopwatch();
cacheStopwatch.Start();
_channelObjectsCache = new HashSet<ModelItem>();
try
{
var document = Application.ActiveDocument;
// 获取所有可通行的物流模型项
var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems();
if (allChannelItems.Count == 0)
{
LogManager.Warning("[通道缓存] ⚠️ 未找到任何可通行的物流元素,请检查模型中的物流属性设置");
cacheStopwatch.Stop();
return;
}
// 🔥 优化只提取复合对象IsComposite = true
// 原因:通道对象通常是复合对象(如通道、楼梯、电梯)
// 我们只需要检测复合对象,不需要检测其子节点
foreach (var channelItem in allChannelItems)
{
try
{
// 只添加复合对象
if (channelItem.IsComposite)
{
_channelObjectsCache.Add(channelItem);
}
else if (channelItem.HasGeometry)
{
// 非复合对象但有几何数据(独立实体节点):也添加
_channelObjectsCache.Add(channelItem);
}
}
catch (Exception ex)
{
LogManager.Warning($"[通道收集] 处理通道节点 '{channelItem?.DisplayName ?? "NULL"}' 时出错: {ex.Message}");
// 出错时至少保证通道本身被添加
_channelObjectsCache.Add(channelItem);
}
}
cacheStopwatch.Stop();
LogManager.Info($"通道对象缓存构建完成,耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
LogManager.Info($" - 可通行物流根对象: {allChannelItems.Count} 个");
LogManager.Info($" - 缓存总对象数: {_channelObjectsCache.Count} 个");
}
catch (Exception ex)
{
LogManager.Error($"构建通道对象缓存时发生错误: {ex.Message}", ex);
_channelObjectsCache = new HashSet<ModelItem>(); // 创建空缓存,避免重复构建
}
}
}
/// <summary>
/// 清除通道对象缓存,在模型变化时调用
/// </summary>
public static void ClearChannelObjectsCache()
{
lock (_cacheLock)
{
_channelObjectsCache = null;
LogManager.Debug("通道对象缓存已清除");
}
}
/// <summary>
/// 设置移动物体(用于从空间索引中排除移动物体及其所有子节点)
/// </summary>
/// <param name="animatedObject">移动物体</param>
public static void SetAnimatedObject(ModelItem animatedObject)
{
lock (_cacheLock)
{
_animatedObject = animatedObject;
LogManager.Info($"移动物体已设置: {animatedObject?.DisplayName ?? "null"}");
// 🔥 关键:清除几何对象缓存,强制重新构建以排除移动物体
// 原因BuildNonHidddenGeometryItemsCache 使用双重检查锁定,只构建一次
// 如果在第一次构建时移动物体为 null摩托车会被添加到缓存
// 之后即使设置了移动物体,缓存也不会重新构建
_allGeometryItemsCache = null;
_nonChannelGeometryItemsCache = null;
LogManager.Info("几何对象缓存已清除,将重新构建以排除移动物体");
// 🔥 立即重新构建几何对象缓存(排除移动物体)
BuildNonHidddenGeometryItemsCache();
LogManager.Info("几何对象缓存已重新构建完成");
}
}
/// <summary>
/// 清除移动物体引用
/// </summary>
public static void ClearAnimatedObject()
{
lock (_cacheLock)
{
_animatedObject = null;
LogManager.Debug("移动物体引用已清除");
}
}
/// <summary>
/// 初始化碰撞检测缓存(用于预计算前的准备工作)
/// </summary>
/// <param name="animatedObject">移动物体</param>
public static void InitializeCollisionDetectionCache(ModelItem animatedObject)
{
LogManager.Info("[碰撞检测] 开始初始化碰撞检测缓存...");
// 1. 清除所有缓存
ClearAllCaches();
// 2. 构建几何对象缓存(包含移动物体)
BuildNonHidddenGeometryItemsCache();
// 3. 构建通道对象缓存
Instance.BuildChannelObjectsCache();
// 4. 设置移动物体并重新构建几何对象缓存(排除移动物体)
SetAnimatedObject(animatedObject);
LogManager.Info("[碰撞检测] 碰撞检测缓存初始化完成");
}
/// <summary>
/// 获取通道对象缓存(供外部使用)
/// </summary>
/// <returns>通道对象集合如果缓存不存在则返回null</returns>
public static HashSet<ModelItem> GetChannelObjectsCache()
{
lock (_cacheLock)
{
return _channelObjectsCache;
}
}
/// <summary>
/// 构建几何对象列表缓存,一次性获取非隐藏几何对象
/// </summary>
public static void BuildNonHidddenGeometryItemsCache()
{
lock (_cacheLock)
{
if (_allGeometryItemsCache != null) return; // 双重检查锁定
var cacheStopwatch = new System.Diagnostics.Stopwatch();
cacheStopwatch.Start();
try
{
// 优化方案:递归遍历 (Top-Down Traversal)
// 原因Search API 的 Hidden 属性不检查父级可见性,且 IsHidden 属性检查自身。
// 解决方案:使用堆栈进行深度优先搜索(DFS),如果遭遇 Hidden 节点则停止深入该分支。
// 这天然地利用了 Navisworks 的层级可见性规则。
_allGeometryItemsCache = new ModelItemCollection();
int visibleCount = 0;
// 使用 Stack 模拟递归避免栈溢出,并保持对 .NET 版本的兼容性
// Stack 初始容量设为 1000 以减少扩容
var stack = new Stack<ModelItem>(1000);
// 将所有模型的根节点压入栈
foreach (var model in Application.ActiveDocument.Models)
{
stack.Push(model.RootItem);
}
while (stack.Count > 0)
{
var item = stack.Pop();
// 关键剪枝:如果节点显式隐藏,则跳过其整个分支(它的所有子节点都不可见)
if (item.IsHidden)
continue;
// 🔥 优化:排除移动物体及其所有子节点
// 算法原理:使用 continue 跳过当前节点,算法会自动跳过其所有子节点
// 因为对于复合对象和有几何体的节点,算法会添加到缓存但不继续遍历子节点
// 对于空节点才会继续遍历子节点,所以 continue 可以有效排除整个子树
if (_animatedObject != null && item.Equals(_animatedObject))
{
LogManager.Debug($"空间索引构建:跳过移动物体节点: {item.DisplayName}");
continue;
}
// 🔥 优化只提取复合对象IsComposite = true
// 原因:复合对象(如窗户)包含多个实体节点(玻璃、框架、把手)
// 我们只需要检测复合对象,不需要检测其子节点
// Clash Detective API 会自动处理复合对象的子节点几何体
if (item.IsComposite)
{
// 复合对象:添加到缓存,不继续遍历子节点
_allGeometryItemsCache.Add(item);
visibleCount++;
}
else if (item.HasGeometry)
{
// 非复合对象但有几何数据(独立实体节点):添加到缓存
_allGeometryItemsCache.Add(item);
visibleCount++;
}
else
{
// 空节点:继续遍历子节点
foreach (var child in item.Children)
{
stack.Push(child);
}
}
}
cacheStopwatch.Stop();
LogManager.Info($"复合对象列表缓存构建完成 (DFS遍历 + 自动剪枝),耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
LogManager.Info($" - 缓存可见对象: {visibleCount}");
LogManager.Info($" - 策略: 深度优先搜索 (父节点隐藏则跳过分支)");
}
catch (Exception ex)
{
LogManager.Error($"构建几何对象列表缓存时发生错误: {ex.Message}", ex);
_allGeometryItemsCache = new ModelItemCollection(); // 创建空缓存,避免重复构建
}
}
}
/// <summary>
/// 获取复合对象缓存(供外部使用)
/// </summary>
/// <returns>复合对象集合如果缓存不存在则返回null</returns>
public static ModelItemCollection GetAllGeometryItemsCache()
{
lock (_cacheLock)
{
return _allGeometryItemsCache;
}
}
/// <summary>
/// 获取已排除通道对象的几何对象缓存(供空间索引使用)
/// </summary>
/// <returns>排除通道后的几何对象列表如果缓存不存在则返回null</returns>
public static List<ModelItem> GetNonChannelGeometryItemsCache()
{
lock (_cacheLock)
{
// 如果已经有过滤后的缓存,直接返回
if (_nonChannelGeometryItemsCache != null)
{
return _nonChannelGeometryItemsCache;
}
// 如果原始缓存不存在,返回 null
if (_allGeometryItemsCache == null || _channelObjectsCache == null)
{
return null;
}
// 第一次调用:构建并缓存过滤后的列表
LogManager.Info($"[空间索引] 构建非通道几何对象缓存...");
var sw = System.Diagnostics.Stopwatch.StartNew();
_nonChannelGeometryItemsCache = _allGeometryItemsCache
.Where(item => !_channelObjectsCache.Contains(item))
.ToList();
sw.Stop();
LogManager.Info($"[空间索引] 非通道几何对象缓存构建完成,耗时: {sw.ElapsedMilliseconds}ms对象数: {_nonChannelGeometryItemsCache.Count}");
return _nonChannelGeometryItemsCache;
}
}
/// <summary>
/// 清除所有缓存,在模型变化时调用
/// </summary>
public static void ClearAllCaches()
{
lock (_cacheLock)
{
_channelObjectsCache = null;
_allGeometryItemsCache = null;
_nonChannelGeometryItemsCache = null;
_animatedObject = null; // 清除移动物体引用
}
}
/// <summary>
/// 清理碰撞缓存
/// </summary>
public void ClearCollisionCache()
{
try
{
LogManager.Info("[ClashDetectiveIntegration] 清理碰撞缓存");
// 清空当前碰撞列表
_currentCollisions?.Clear();
// 清空缓存的结果
lock (_resultsLock)
{
_deduplicatedCollisionResults?.Clear();
}
// 清除对象缓存
ClearAllCaches();
// 重置计数器
_animationCollisionCount = 0;
_clashDetectiveCollisionCount = 0;
LogManager.Info("[ClashDetectiveIntegration] 碰撞缓存已清理");
}
catch (Exception ex)
{
LogManager.Error($"[ClashDetectiveIntegration] 清理碰撞缓存失败: {ex.Message}");
}
}
/// <summary>
/// 清理资源
/// </summary>
public void Cleanup()
{
try
{
LogManager.Info("开始Clash Detective集成资源清理...");
// 检查文档是否仍然有效,如果无效则跳过操作
var document = Application.ActiveDocument;
if (document == null || document.IsClear)
{
LogManager.Info("文档已无效,跳过资源清理操作");
return;
}
try
{
ModelHighlightHelper.ClearAllHighlights();
LogManager.Info("已清除临时材质高亮");
}
catch (Exception ex)
{
LogManager.Warning($"清除临时材质失败: {ex.Message}");
}
// 清空内存中的结果
_currentCollisions?.Clear();
lock (_resultsLock)
{
_deduplicatedCollisionResults?.Clear();
}
// 清理.NET API引用
_documentClash = null;
LogManager.Info("Clash Detective集成资源清理完成");
}
catch (Exception ex)
{
LogManager.Error($"清理Clash Detective资源时发生异常: {ex.Message}");
}
}
/// <summary>
/// 批处理版本创建并运行ClashDetective测试不触发UI操作
/// 此方法专门用于批处理场景不显示进度条不触发UI刷新
/// </summary>
public string CreateAllAnimationCollisionTestsForBatch(
List<CollisionResult> precomputedCollisions,
double detectionGap,
string routeId,
ModelItem animatedObject,
bool isVirtualVehicle,
int frameRate,
double duration,
double virtualVehicleLength,
double virtualVehicleWidth,
double virtualVehicleHeight,
List<Point3D> pathPoints = null)
{
try
{
LogManager.Info($"[批处理] 使用预计算碰撞数据创建ClashDetective测试容差: {detectionGap}米)");
LogManager.Info($"[批处理] 共有 {precomputedCollisions.Count} 个碰撞记录");
// 记录动画过程中的碰撞数量
_animationCollisionCount = precomputedCollisions.Count;
LogManager.Info($"[批处理] 预计算碰撞: {_animationCollisionCount}");
// 调用公共方法运行ClashDetective测试并保存到数据库
var result = RunClashDetectiveTestsAndSaveToDatabase(
precomputedCollisions,
detectionGap,
routeId,
animatedObject,
isVirtualVehicle,
frameRate,
duration,
virtualVehicleLength,
virtualVehicleWidth,
virtualVehicleHeight,
null
);
var collisionGroup = result.collisionGroup;
var addedMainTest = result.addedMainTest;
var confirmedCount = result.confirmedCount;
// 添加分组到主测试(使用 TestsAddCopy 方法)
if (collisionGroup != null && addedMainTest != null && collisionGroup.Children.Count > 0)
{
_documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup);
LogManager.Info($"[批处理] 碰撞分组已添加到主测试,共 {confirmedCount} 个碰撞");
}
else
{
LogManager.Warning("[批处理] 分组为空(未检测到真实几何碰撞),未添加到主测试");
}
// 碰撞测试完成后,将物体恢复到路径起点位置(如果提供了路径点)
if (animatedObject != null && IsModelItemValid(animatedObject) && pathPoints != null && pathPoints.Count > 0)
{
try
{
PathAnimationManager.GetInstance().MoveVehicleToPathStart(animatedObject, pathPoints);
LogManager.Info($"[批处理] 已将 {animatedObject.DisplayName} 恢复到路径起点位置");
}
catch (Exception restoreEx)
{
LogManager.Error($"[批处理] 恢复物体到路径起点失败: {restoreEx.Message}");
}
}
return addedMainTest?.DisplayName;
}
catch (Exception ex)
{
LogManager.Error($"[批处理] 创建ClashDetective测试失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 触发碰撞检测事件
/// </summary>
private void OnCollisionDetected(CollisionDetectedEventArgs e)
{
CollisionDetected?.Invoke(this, e);
}
}
/// <summary>
/// 碰撞结果数据结构
/// </summary>
public class CollisionResult : IEquatable<CollisionResult>
{
public Guid ClashGuid { get; set; }
public string DisplayName { get; set; }
public ClashResultStatus Status { get; set; }
public string GridLocation { get; set; }
public ModelItem Item1 { get; set; }
public ModelItem Item2 { get; set; }
public Point3D Center { get; set; }
public double Distance { get; set; }
public DateTime CreatedTime { get; set; }
// 位置信息用于恢复测试
public Point3D Item1Position { get; set; }
public Point3D Item2Position { get; set; }
public bool HasPositionInfo { get; set; }
// IEquatable<CollisionResult> 实现:基于碰撞对象进行去重
public bool Equals(CollisionResult other)
{
if (other == null) return false;
// 使用 ModelItem.Equals 比较底层原生对象
bool item1Equal = (Item1 == null && other.Item1 == null) ||
(Item1 != null && Item1.Equals(other.Item1));
bool item2Equal = (Item2 == null && other.Item2 == null) ||
(Item2 != null && Item2.Equals(other.Item2));
return item1Equal && item2Equal;
}
public override bool Equals(object obj)
{
return Equals(obj as CollisionResult);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + (Item1?.GetHashCode() ?? 0);
hash = hash * 31 + (Item2?.GetHashCode() ?? 0);
return hash;
}
}
}
/// <summary>
/// 碰撞检测事件参数
/// </summary>
public class CollisionDetectedEventArgs : EventArgs
{
public List<CollisionResult> Results { get; private set; }
public int CollisionCount { get; private set; }
public CollisionDetectedEventArgs(List<CollisionResult> results)
{
Results = results;
CollisionCount = results.Count;
}
}
/// <summary>
/// ClashDetective结果保存事件参数
/// </summary>
public class ClashDetectiveResultSavedEventArgs : EventArgs
{
public string PathName { get; private set; }
public string TestName { get; private set; }
public int CollisionCount { get; private set; }
public ClashDetectiveResultSavedEventArgs(string pathName, string testName, int collisionCount)
{
PathName = pathName;
TestName = testName;
CollisionCount = collisionCount;
}
}
}