重构了碰撞代码,抽取包围盒几何计算到Uitls

This commit is contained in:
tian 2025-09-04 17:14:04 +08:00
parent 4411618662
commit 41cac3dedd
6 changed files with 410 additions and 245 deletions

View File

@ -58,7 +58,18 @@
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\design\\2026/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\design\\2026/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\examples\\PlugIns\\SearchComparisonPlugIn/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api/**)"
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\examples\\PlugIns\\ClashDetective\\SimpleUI/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\documentation\\NET API\\html/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\documentation\\NET API\\html/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\documentation\\NET API\\html/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\documentation\\NET API\\html/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\documentation\\NET API\\html/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\doc\\navisworks_api\\NET\\examples\\PlugIns\\ClashDetective\\SimpleUI/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)",
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)"
],
"deny": [],
"additionalDirectories": [

View File

@ -240,6 +240,7 @@
<Compile Include="src\UI\WPF\Models\SplitPreviewItem.cs" />
<!-- Utils -->
<Compile Include="src\Utils\BoundingBoxGeometryUtils.cs" />
<Compile Include="src\Utils\CoordinateConverter.cs" />
<Compile Include="src\Utils\FloorDetector.cs" />
<Compile Include="src\Utils\ModelItemAnalysisHelper.cs" />

View File

@ -1490,3 +1490,61 @@ public async Task SafeCacheRefreshAsync()
- `BasicDockPanePlugin.cs` - 基础插件结构
- `DatabaseDockPane/Models.cs` - 数据库操作示例
- `ClashDetective` 相关示例 - 高级功能示例
碰撞检测模式说明
Hard硬碰撞
- 检测模型几何体(多边形、线条和/或点)之间发生碰撞的情况,碰撞距离大于设定的容差值
HardConservative保守硬碰撞
- 与Hard模式相同但额外尝试检测那些多边形、线条和点虽然不相交但它们所代表的体积可能重叠的情况
- 举例:两个相同大小的立方体,方向相同,沿其中一个面的垂直方向偏移很短的距离。在这种情况下,体积明显重
叠,但没有三角形或边缘彼此穿过
- 所有"碰撞"都属于以下情况之一:
- (i) 面仅仅接触另一个面的边缘
- (ii) 共面的面在2D空间中重叠但在3D中没有相交
- (iii) 边仅仅接触另一个边的端点
- (iv) 共轴的边在1D中重叠但同样不是以3D方式检测到的
- HardConservative碰撞检测增加了检测这些复杂情况的逻辑但可能会产生误报
Clearance间隙检测
- 检测几何体之间的最小分离距离小于指定容差的情况
- 例如可用于检测是否有足够空间放置CAD文件中未建模的保温材料
Duplicate重复检测
- 检测模型中完全相同的几何体出现多次的情况
- 当合并包含相同第三方文件的两个文件时可能发生这种情况
- 超出容差范围的重复几何体被视为不同对象,不会被报告
这些不同的检测模式为不同的工程应用场景提供了精确的碰撞分析能力。
容差值说明
对于Hard硬碰撞和HardConservative保守硬碰撞测试
- 容差值是在干涉被认定为碰撞之前允许的重叠程度
- 这允许您忽略数值近似问题和可以在现场解决的轻微碰撞
对于Clearance间隙检测测试
- 容差值是所需的最小间隙距离,低于此值的分离将被视为碰撞
对于Duplicate重复检测测试
- 容差值是相同几何体被视为重复的最大距离
- 超出此距离的物体将被忽略,理论上它们虽然相同但是真正独立的实例
简单来说:
- Hard/HardConservative容差 = 允许的最大重叠距离
- Clearance容差 = 要求的最小间隙距离
- Duplicate容差 = 识别重复的最大距离
这种设计让用户可以根据实际工程需求调整检测的敏感度。
设置几何类型:包含面、线和点以获得最全面的碰撞检测
```csharp
collisionTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
collisionTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
```

View File

@ -7,6 +7,7 @@ using Autodesk.Navisworks.Api.Clash;
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi;
using NavisworksTransport.UI.WPF;
using NavisworksTransport.Utils;
namespace NavisworksTransport
{
@ -224,6 +225,12 @@ namespace NavisworksTransport
_dynamicClashTest.TestType = ClashTestType.Hard;
_dynamicClashTest.Tolerance = 0.01; // 1cm容差
// 设置几何类型:包含面、线和点以获得最全面的碰撞检测
_dynamicClashTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
_dynamicClashTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
LogManager.Debug("动态碰撞测试几何类型设置: 包含面、线、点");
// 设置测试规则 - 移除在2017版本中不可用的规则设置
// _dynamicClashTest.Rules.Add(ClashRule.IgnoreWithinSameFile);
// _dynamicClashTest.Rules.Add(ClashRule.IgnoreWithinSameLayer);
@ -337,24 +344,12 @@ namespace NavisworksTransport
LogManager.Debug($"[碰撞缓存-开始] 碰撞对象: {collisionObject?.DisplayName ?? "NULL"}");
// 🔍 对象验证详情
if (animatedObject != null)
{
LogManager.Debug($"[碰撞缓存-GUID] 动画对象GUID: {animatedObject.InstanceGuid}");
LogManager.Debug($"[碰撞缓存-验证] 动画对象HasGeometry: {animatedObject.HasGeometry}");
LogManager.Debug($"[碰撞缓存-验证] 动画对象IsModelItemValid: {IsModelItemValid(animatedObject)}");
}
else
if (animatedObject == null)
{
LogManager.Error($"[碰撞缓存-错误] 动画对象为NULL");
}
if (collisionObject != null)
{
LogManager.Debug($"[碰撞缓存-GUID] 碰撞对象GUID: {collisionObject.InstanceGuid}");
LogManager.Debug($"[碰撞缓存-验证] 碰撞对象HasGeometry: {collisionObject.HasGeometry}");
LogManager.Debug($"[碰撞缓存-验证] 碰撞对象IsModelItemValid: {IsModelItemValid(collisionObject)}");
}
else
if (collisionObject == null)
{
LogManager.Error($"[碰撞缓存-错误] 碰撞对象为NULL");
}
@ -371,28 +366,6 @@ namespace NavisworksTransport
LogManager.Debug($"[碰撞缓存-位置] 碰撞对象位置: NULL (将自动计算)");
}
// 🔍 检查是否为自碰撞
if (animatedObject != null && collisionObject != null)
{
var isEquals = animatedObject.Equals(collisionObject);
var isSameGuid = animatedObject.InstanceGuid == collisionObject.InstanceGuid;
var isSameName = animatedObject.DisplayName == collisionObject.DisplayName;
if (isEquals || isSameGuid || isSameName)
{
LogManager.Warning($"[诊断-自碰撞警告] CacheCollision检测到可疑自碰撞:");
LogManager.Warning($" Equals: {isEquals}");
LogManager.Warning($" SameGuid: {isSameGuid}");
LogManager.Warning($" SameName: {isSameName}");
if (isEquals)
{
LogManager.Error($"[诊断-严重错误] animatedObject.Equals(collisionObject) = true! 这是明确的自碰撞,应该跳过!");
return; // 直接返回,不处理自碰撞
}
}
}
if (!IsModelItemValid(animatedObject) || !IsModelItemValid(collisionObject))
{
LogManager.Warning($"[诊断-无效对象] 对象验证失败,退出缓存过程");
@ -410,14 +383,14 @@ namespace NavisworksTransport
LogManager.Info($"[容器映射] 动画对象: '{animatedObject.DisplayName}' -> '{mappedAnimatedObject.DisplayName}' (映射: {hasMapping1})");
LogManager.Info($"[容器映射] 碰撞对象: '{collisionObject.DisplayName}' -> '{mappedCollisionObject.DisplayName}' (映射: {hasMapping2})");
// 使用精确的碰撞检测算法(基于原始几何体)
// 使用包围盒碰撞检测算法
var animatedBoundingBox = animatedObject.BoundingBox();
var collisionBoundingBox = collisionObject.BoundingBox();
LogManager.Debug($"[碰撞缓存-包围盒] 动画对象包围盒: Min({animatedBoundingBox.Min.X:F3},{animatedBoundingBox.Min.Y:F3},{animatedBoundingBox.Min.Z:F3}) Max({animatedBoundingBox.Max.X:F3},{animatedBoundingBox.Max.Y:F3},{animatedBoundingBox.Max.Z:F3})");
LogManager.Debug($"[碰撞缓存-包围盒] 碰撞对象包围盒: Min({collisionBoundingBox.Min.X:F3},{collisionBoundingBox.Min.Y:F3},{collisionBoundingBox.Min.Z:F3}) Max({collisionBoundingBox.Max.X:F3},{collisionBoundingBox.Max.Y:F3},{collisionBoundingBox.Max.Z:F3})");
if (BoundingBoxesIntersect(animatedBoundingBox, collisionBoundingBox))
if (BoundingBoxGeometryUtils.BoundingBoxesIntersect(animatedBoundingBox, collisionBoundingBox))
{
// 🔍 计算并验证位置信息
var finalCollisionPosition = collisionObjectPosition ?? GetObjectPosition(collisionObject);
@ -437,9 +410,9 @@ namespace NavisworksTransport
LogManager.Warning($"[诊断-位置警告] 两个对象位置几乎相同 (距离: {positionDistance:F6}),这可能是自碰撞的标志!");
}
// 创建精确的碰撞结果(使用原始几何体计算,但记录容器对象)
var collisionDistance = CalculateDistance(animatedBoundingBox, collisionBoundingBox);
var collisionCenter = CalculateCenter(animatedBoundingBox, collisionBoundingBox);
// 创建碰撞结果
var collisionDistance = BoundingBoxGeometryUtils.CalculateDistance(animatedBoundingBox, collisionBoundingBox);
var collisionCenter = BoundingBoxGeometryUtils.CalculateCenter(animatedBoundingBox, collisionBoundingBox);
LogManager.Debug($"[碰撞缓存-距离] 包围盒碰撞距离: {collisionDistance:F4}");
LogManager.Debug($"[碰撞缓存-中心点] 碰撞中心: ({collisionCenter.X:F3},{collisionCenter.Y:F3},{collisionCenter.Z:F3})");
@ -461,17 +434,6 @@ namespace NavisworksTransport
Item2Position = finalCollisionPosition,
HasPositionInfo = true
};
LogManager.Debug($"[碰撞缓存-结果] 创建碰撞结果:");
LogManager.Debug($" GUID: {collision.ClashGuid}");
LogManager.Debug($" DisplayName: {collision.DisplayName}");
LogManager.Debug($" Item1: {collision.Item1?.DisplayName ?? "NULL"}");
LogManager.Debug($" Item2: {collision.Item2?.DisplayName ?? "NULL"}");
LogManager.Debug($" OriginalItem1: {collision.OriginalItem1?.DisplayName ?? "NULL"}");
LogManager.Debug($" OriginalItem2: {collision.OriginalItem2?.DisplayName ?? "NULL"}");
LogManager.Debug($" HasContainerMapping: {collision.HasContainerMapping}");
LogManager.Debug($" Distance: {collision.Distance:F4}");
LogManager.Debug($" HasPositionInfo: {collision.HasPositionInfo}");
// 去重处理:避免重复缓存相同的碰撞对(基于容器对象)
var existing = _cachedResults.FirstOrDefault(r =>
@ -480,19 +442,6 @@ namespace NavisworksTransport
if (existing == null)
{
_cachedResults.Add(collision);
LogManager.Info($"[碰撞缓存-成功] 缓存精确碰撞: {mappedAnimatedObject.DisplayName} <-> {mappedCollisionObject.DisplayName}");
LogManager.Info($" 时间: {collision.CreatedTime:HH:mm:ss}");
LogManager.Info($" 动画位置: ({collision.Item1Position.X:F3},{collision.Item1Position.Y:F3},{collision.Item1Position.Z:F3})");
LogManager.Info($" 碰撞位置: ({collision.Item2Position.X:F3},{collision.Item2Position.Y:F3},{collision.Item2Position.Z:F3})");
LogManager.Info($" 包围盒距离: {collision.Distance:F4}");
LogManager.Info($" 容器映射: {collision.HasContainerMapping}");
LogManager.Info($" 当前缓存总数: {_cachedResults.Count}");
}
else
{
LogManager.Debug($"[碰撞缓存-跳过] 重复碰撞: {mappedAnimatedObject.DisplayName} <-> {mappedCollisionObject.DisplayName}");
LogManager.Debug($" 原有记录时间: {existing.CreatedTime:HH:mm:ss}");
LogManager.Debug($" 本次尝试时间: {DateTime.Now:HH:mm:ss}");
}
}
else
@ -535,16 +484,13 @@ namespace NavisworksTransport
}
/// <summary>
/// 动画结束后统一创建和运行所有碰撞测试 - 基于官方示例
/// </summary>
/// <summary>
/// 动画结束后统一创建和运行所有碰撞测试 - 基于官方示例
/// 动画结束后统一创建和运行ClashDetective碰撞测试
/// </summary>
public void CreateAllAnimationCollisionTests(double detectionGap = 0.05)
{
try
{
LogManager.Info($"=== 动画结束,开始创建所有碰撞测试(容差: {detectionGap}米) ===");
LogManager.Info($"=== 动画结束,开始创建ClashDetective碰撞测试(容差: {detectionGap}米) ===");
if (_documentClash == null || _cachedResults.Count == 0)
{
@ -716,25 +662,36 @@ namespace NavisworksTransport
try
{
// 创建新的碰撞测试
var collisionTest = new ClashTest();
var collisionTest = new ClashTest
{
DisplayName = testName,
TestType = ClashTestType.HardConservative, // 保守硬碰撞
Tolerance = detectionGap, // 使用传入的检测间隙参数保守硬碰撞建议为0结果准确
Guid = Guid.Empty
};
collisionTest.DisplayName = testName;
collisionTest.TestType = ClashTestType.Hard;
collisionTest.Tolerance = detectionGap; // 使用传入的检测间隙参数
collisionTest.Guid = Guid.Empty;
LogManager.Debug($"[测试创建-{createdCount:00}] 几何类型设置: 包含面、线、点");
// 设置选择集A
var selectionA = new ModelItemCollection();
selectionA.Add(collision.Item1);
var selectionA = new ModelItemCollection
{
collision.Item1
};
// 设置几何类型:包含面、线和点以获得最全面的碰撞检测
collisionTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
collisionTest.SelectionA.Selection.CopyFrom(selectionA);
LogManager.Debug($"[测试创建-{createdCount:00}] 选择集A设置: {collision.Item1?.DisplayName ?? "NULL"} (数量: {selectionA.Count})");
// 设置选择集B
var selectionB = new ModelItemCollection();
selectionB.Add(collision.Item2);
var selectionB = new ModelItemCollection
{
collision.Item2
};
collisionTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
collisionTest.SelectionB.Selection.CopyFrom(selectionB);
LogManager.Debug($"[测试创建-{createdCount:00}] 选择集B设置: {collision.Item2?.DisplayName ?? "NULL"} (数量: {selectionB.Count})");
try
@ -1328,7 +1285,7 @@ namespace NavisworksTransport
var bbox2 = obj2.BoundingBox();
// 检查包围盒是否完全相同精确到小数点后3位
var centerDistance = CalculatePointDistance(
var centerDistance = BoundingBoxGeometryUtils.CalculatePointDistance(
new Point3D((bbox1.Min.X + bbox1.Max.X) / 2, (bbox1.Min.Y + bbox1.Max.Y) / 2, (bbox1.Min.Z + bbox1.Max.Z) / 2),
new Point3D((bbox2.Min.X + bbox2.Max.X) / 2, (bbox2.Min.Y + bbox2.Max.Y) / 2, (bbox2.Min.Z + bbox2.Max.Z) / 2)
);
@ -1374,12 +1331,11 @@ namespace NavisworksTransport
/// <summary>
/// 计算两个3D点之间的距离
/// </summary>
// 此方法已迁移到 BoundingBoxGeometryUtils.CalculatePointDistance
[System.Obsolete("请使用 BoundingBoxGeometryUtils.CalculatePointDistance 替代", true)]
private double CalculatePointDistance(Point3D point1, Point3D point2)
{
var dx = point1.X - point2.X;
var dy = point1.Y - point2.Y;
var dz = point1.Z - point2.Z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
throw new NotImplementedException("方法已迁移到工具类");
}
#region
@ -2124,7 +2080,7 @@ namespace NavisworksTransport
}
/// <summary>
/// 简化的碰撞检测当Clash Detective不可用时
/// 基于包围盒的快速碰撞检测不用Clash Detective
/// </summary>
private List<CollisionResult> DetectCollisionsSimple(ModelItem animatedObject,
ModelItemCollection excludeObjects, double detectionGap)
@ -2133,7 +2089,7 @@ namespace NavisworksTransport
try
{
LogManager.Debug($"开始碰撞检测,动画对象: {animatedObject.DisplayName}");
LogManager.Debug($"开始快速碰撞检测,动画对象: {animatedObject.DisplayName}");
var animatedBoundingBox = animatedObject.BoundingBox();
LogManager.Debug($"动画对象包围盒: Min({animatedBoundingBox.Min.X:F2}, {animatedBoundingBox.Min.Y:F2}, {animatedBoundingBox.Min.Z:F2}) Max({animatedBoundingBox.Max.X:F2}, {animatedBoundingBox.Max.Y:F2}, {animatedBoundingBox.Max.Z:F2})");
@ -2224,7 +2180,7 @@ namespace NavisworksTransport
// 计时:相交检测
intersectionStopwatch.Start();
bool intersects = BoundingBoxesIntersectWithTolerance(animatedBoundingBox, itemBoundingBox, tolerance);
bool intersects = BoundingBoxGeometryUtils.BoundingBoxesIntersectWithTolerance(animatedBoundingBox, itemBoundingBox, tolerance);
intersectionStopwatch.Stop();
intersectionCallCount++;
@ -2233,7 +2189,7 @@ namespace NavisworksTransport
{
// 计时:距离计算
distanceStopwatch.Start();
var distance = CalculateDistance(animatedBoundingBox, itemBoundingBox);
var distance = BoundingBoxGeometryUtils.CalculateDistance(animatedBoundingBox, itemBoundingBox);
distanceStopwatch.Stop();
distanceCallCount++;
@ -2263,7 +2219,7 @@ namespace NavisworksTransport
HasContainerMapping = hasMapping1 || hasMapping2, // 标记是否进行了映射
CreatedTime = DateTime.Now,
Distance = distance,
Center = CalculateCenter(animatedBoundingBox, itemBoundingBox)
Center = BoundingBoxGeometryUtils.CalculateCenter(animatedBoundingBox, itemBoundingBox)
};
results.Add(result);
@ -2366,94 +2322,67 @@ namespace NavisworksTransport
cacheStopwatch.Start();
_channelObjectsCache = new HashSet<ModelItem>();
var tempChannelObjects = new List<ModelItem>();
try
{
// 🔥 修复搜索所有对象不过滤HasGeometry以支持容器型通道节点
LogManager.Debug("[通道缓存] 开始搜索所有模型对象(包括容器节点)");
var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf;
var allItemsList = allItems.ToList();
LogManager.Debug($"[通道缓存] 总对象数量: {allItemsList.Count}");
var document = Application.ActiveDocument;
LogManager.Debug("[通道缓存] 开始使用SearchAPI构建通道对象缓存");
int checkedCount = 0;
int channelFound = 0;
// 🔥 一行调用直接获取所有可通行的物流模型项使用优化后的SearchAPI
var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document);
LogManager.Info($"[通道缓存] 使用SearchAPI直接获取到 {allChannelItems.Count} 个可通行物流元素");
// 第一轮:找出所有标记为通道的对象(包括容器节点)
foreach (var item in allItemsList)
if (allChannelItems.Count == 0)
{
try
{
checkedCount++;
// 添加调试信息
if (item.DisplayName == "Plaza Surround" || item.DisplayName.Contains("Plaza"))
{
LogManager.Debug($"[通道缓存-调试] 检查对象: {item.DisplayName}, HasGeometry: {item.HasGeometry}");
}
var elementType = CategoryAttributeManager.GetLogisticsElementType(item);
if (elementType == CategoryAttributeManager.LogisticsElementType.)
{
channelFound++;
tempChannelObjects.Add(item);
_channelObjectsCache.Add(item);
LogManager.Info($"[通道缓存] 发现通道对象: {item.DisplayName}, HasGeometry: {item.HasGeometry}");
}
else if (item.DisplayName == "Plaza Surround" || item.DisplayName.Contains("Plaza"))
{
LogManager.Debug($"[通道缓存-调试] {item.DisplayName} 的物流类型: {elementType}");
}
}
catch (Exception ex)
{
LogManager.Warning($"检查对象物流类型时出错 {item.DisplayName}: {ex.Message}");
}
LogManager.Warning("[通道缓存] ⚠️ 未找到任何可通行的物流元素,请检查模型中的物流属性设置");
cacheStopwatch.Stop();
return;
}
LogManager.Info($"[通道缓存] 第一轮搜索完成: 检查了 {checkedCount} 个对象,找到 {channelFound} 个通道根对象");
// 第二轮:添加通道对象的所有子对象(只添加有几何体的子对象到缓存)
int childrenAdded = 0;
foreach (var channelObject in tempChannelObjects)
// 智能收集通道相关节点(包括父节点、同级节点、子节点等)
foreach (var channelItem in allChannelItems)
{
try
{
LogManager.Debug($"[通道缓存] 处理通道对象的子对象: {channelObject.DisplayName}");
LogManager.Debug($"[通道收集] 开始处理通道节点: '{channelItem.DisplayName}'");
foreach (var descendant in channelObject.DescendantsAndSelf)
// 🔥 直接使用 ModelItemAnalysisHelper 的方法收集相关节点
var itemRelatedNodes = ModelItemAnalysisHelper.CollectRelatedNodes(channelItem);
// 将结果添加到缓存中
foreach (var node in itemRelatedNodes)
{
if (descendant.HasGeometry && _channelObjectsCache.Add(descendant))
{
childrenAdded++;
LogManager.Debug($"[通道缓存] 添加通道子对象: {descendant.DisplayName}");
}
_channelObjectsCache.Add(node);
}
LogManager.Debug($"[通道收集] 从 '{channelItem.DisplayName}' 收集到 {itemRelatedNodes.Count} 个相关节点");
}
catch (Exception ex)
{
LogManager.Warning($"添加通道子对象时出错 {channelObject.DisplayName}: {ex.Message}");
LogManager.Warning($"[通道收集] 处理通道节点 '{channelItem?.DisplayName ?? "NULL"}' 时出错: {ex.Message}");
// 出错时至少保证通道本身被添加
_channelObjectsCache.Add(channelItem);
}
}
cacheStopwatch.Stop();
LogManager.Info($"通道对象缓存构建完成,耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
LogManager.Info($" - 通道根对象: {tempChannelObjects.Count} 个");
LogManager.Info($" - 新增子对象: {childrenAdded} 个");
LogManager.Info($" - 缓存总对象: {_channelObjectsCache.Count} 个");
LogManager.Info($" - 可通行物流根对象: {allChannelItems.Count} 个");
LogManager.Info($" - 缓存总对象数: {_channelObjectsCache.Count} 个");
// 列出找到的通道根对象
if (tempChannelObjects.Count > 0)
if (allChannelItems.Count > 0)
{
LogManager.Info("[通道缓存] 找到的通道根对象列表:");
for (int i = 0; i < tempChannelObjects.Count; i++)
LogManager.Info("[通道缓存] 找到的可通行物流对象列表:");
for (int i = 0; i < Math.Min(allChannelItems.Count, 10); i++) // 最多显示前10个
{
LogManager.Info($" {i + 1}. {tempChannelObjects[i].DisplayName} (HasGeometry: {tempChannelObjects[i].HasGeometry})");
var item = allChannelItems[i];
LogManager.Info($" {i + 1}. {item.DisplayName} (HasGeometry: {item.HasGeometry})");
}
if (allChannelItems.Count > 10)
{
LogManager.Info($" ... 还有 {allChannelItems.Count - 10} 个对象(省略显示)");
}
}
else
{
LogManager.Warning("[通道缓存] ⚠️ 未找到任何通道根对象,请检查模型中的物流属性设置");
}
}
catch (Exception ex)
@ -2463,7 +2392,7 @@ namespace NavisworksTransport
}
}
}
/// <summary>
/// 清除通道对象缓存,在模型变化时调用
/// </summary>
@ -2523,45 +2452,6 @@ namespace NavisworksTransport
LogManager.Debug("所有对象缓存已清除");
}
}
/// <summary>
/// 查找具有有效名称的父级容器对象,用于解决几何体子对象无名称问题
/// </summary>
/// <param name="geometryItem">几何体对象(可能无名称)</param>
/// <returns>有名称的容器对象,如果找不到则返回原对象</returns>
private static ModelItem FindNamedParentContainer(ModelItem geometryItem)
{
if (geometryItem == null) return null;
try
{
var current = geometryItem;
int levels = 0; // 防止无限递归
while (current != null && levels < 10) // 最多向上查找10层
{
// 检查当前对象是否有有效名称
if (!string.IsNullOrEmpty(current.DisplayName) && current.DisplayName.Trim().Length > 0)
{
LogManager.Debug($"[容器映射] 几何体 -> 容器: '{geometryItem.DisplayName}' -> '{current.DisplayName}' (向上{levels}层)");
return current;
}
current = current.Parent;
levels++;
}
// 如果没有找到有名称的父级,返回原对象
LogManager.Warning($"[容器映射] 未找到有名称的父级容器,使用原对象: {geometryItem.DisplayName}");
return geometryItem;
}
catch (Exception ex)
{
LogManager.Warning($"[容器映射] 查找父级容器时出错: {ex.Message}");
return geometryItem; // 出错时返回原对象
}
}
/// <summary>
/// 增强的碰撞对象信息,包含容器映射
@ -2579,7 +2469,7 @@ namespace NavisworksTransport
}
// 否则查找有名称的父级容器
var containerObject = FindNamedParentContainer(originalItem);
var containerObject = ModelItemAnalysisHelper.FindNamedParentContainer(originalItem);
LogManager.Info($"[碰撞对象映射] 原对象: '{originalItem.DisplayName}' -> 容器对象: '{containerObject?.DisplayName}'");
@ -2589,77 +2479,42 @@ namespace NavisworksTransport
/// <summary>
/// 计算两个包围盒之间的最短距离(真实碰撞距离)
/// </summary>
// 此方法已迁移到 BoundingBoxGeometryUtils.CalculateDistance
[System.Obsolete("请使用 BoundingBoxGeometryUtils.CalculateDistance 替代", true)]
private double CalculateDistance(BoundingBox3D box1, BoundingBox3D box2)
{
// 计算每个轴上的最短距离
double xDistance = 0;
if (box1.Max.X < box2.Min.X)
xDistance = box2.Min.X - box1.Max.X;
else if (box2.Max.X < box1.Min.X)
xDistance = box1.Min.X - box2.Max.X;
double yDistance = 0;
if (box1.Max.Y < box2.Min.Y)
yDistance = box2.Min.Y - box1.Max.Y;
else if (box2.Max.Y < box1.Min.Y)
yDistance = box1.Min.Y - box2.Max.Y;
double zDistance = 0;
if (box1.Max.Z < box2.Min.Z)
zDistance = box2.Min.Z - box1.Max.Z;
else if (box2.Max.Z < box1.Min.Z)
zDistance = box1.Min.Z - box2.Max.Z;
// 如果包围盒相交返回0否则返回3D空间中的最短距离
if (xDistance == 0 && yDistance == 0 && zDistance == 0)
return 0.0; // 真正相交
return Math.Sqrt(xDistance * xDistance + yDistance * yDistance + zDistance * zDistance);
throw new NotImplementedException("方法已迁移到工具类");
}
/// <summary>
/// 计算两个包围盒之间的中心点
/// </summary>
// 此方法已迁移到 BoundingBoxGeometryUtils.CalculateCenter
[System.Obsolete("请使用 BoundingBoxGeometryUtils.CalculateCenter 替代", true)]
private Point3D CalculateCenter(BoundingBox3D box1, BoundingBox3D box2)
{
var center1 = new Point3D(
(box1.Min.X + box1.Max.X) / 2,
(box1.Min.Y + box1.Max.Y) / 2,
(box1.Min.Z + box1.Max.Z) / 2
);
var center2 = new Point3D(
(box2.Min.X + box2.Max.X) / 2,
(box2.Min.Y + box2.Max.Y) / 2,
(box2.Min.Z + box2.Max.Z) / 2
);
return new Point3D(
(center1.X + center2.X) / 2,
(center1.Y + center2.Y) / 2,
(center1.Z + center2.Z) / 2
);
throw new NotImplementedException("方法已迁移到工具类");
}
/// <summary>
/// 检查两个包围盒是否相交(带容差)
/// </summary>
// 此方法已迁移到 BoundingBoxGeometryUtils.BoundingBoxesIntersectWithTolerance
[System.Obsolete("请使用 BoundingBoxGeometryUtils.BoundingBoxesIntersectWithTolerance 替代", true)]
private bool BoundingBoxesIntersectWithTolerance(BoundingBox3D box1, BoundingBox3D box2, double tolerance)
{
return box1.Min.X <= box2.Max.X + tolerance && box1.Max.X >= box2.Min.X - tolerance &&
box1.Min.Y <= box2.Max.Y + tolerance && box1.Max.Y >= box2.Min.Y - tolerance &&
box1.Min.Z <= box2.Max.Z + tolerance && box1.Max.Z >= box2.Min.Z - tolerance;
throw new NotImplementedException("方法已迁移到工具类");
}
/// <summary>
/// 检查两个包围盒是否相交
/// </summary>
// 此方法已迁移到 BoundingBoxGeometryUtils.BoundingBoxesIntersect
[System.Obsolete("请使用 BoundingBoxGeometryUtils.BoundingBoxesIntersect 替代", true)]
private bool BoundingBoxesIntersect(BoundingBox3D box1, BoundingBox3D box2)
{
return box1.Min.X <= box2.Max.X && box1.Max.X >= box2.Min.X &&
box1.Min.Y <= box2.Max.Y && box1.Max.Y >= box2.Min.Y &&
box1.Min.Z <= box2.Max.Z && box1.Max.Z >= box2.Min.Z;
throw new NotImplementedException("方法已迁移到工具类");
}
/// <summary>

View File

@ -0,0 +1,177 @@
using System;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils
{
/// <summary>
/// 包围盒几何计算工具类
/// 提供包围盒之间的距离计算、中心点计算、相交检测等几何运算方法
/// </summary>
public static class BoundingBoxGeometryUtils
{
/// <summary>
/// 计算两个包围盒之间的最短距离(真实碰撞距离)
/// </summary>
/// <param name="box1">第一个包围盒</param>
/// <param name="box2">第二个包围盒</param>
/// <returns>两个包围盒之间的最短距离如果相交则返回0</returns>
public static double CalculateDistance(BoundingBox3D box1, BoundingBox3D box2)
{
// 计算每个轴上的最短距离
double xDistance = 0;
if (box1.Max.X < box2.Min.X)
xDistance = box2.Min.X - box1.Max.X;
else if (box2.Max.X < box1.Min.X)
xDistance = box1.Min.X - box2.Max.X;
double yDistance = 0;
if (box1.Max.Y < box2.Min.Y)
yDistance = box2.Min.Y - box1.Max.Y;
else if (box2.Max.Y < box1.Min.Y)
yDistance = box1.Min.Y - box2.Max.Y;
double zDistance = 0;
if (box1.Max.Z < box2.Min.Z)
zDistance = box2.Min.Z - box1.Max.Z;
else if (box2.Max.Z < box1.Min.Z)
zDistance = box1.Min.Z - box2.Max.Z;
// 如果包围盒相交返回0否则返回3D空间中的最短距离
if (xDistance == 0 && yDistance == 0 && zDistance == 0)
return 0.0; // 真正相交
return Math.Sqrt(xDistance * xDistance + yDistance * yDistance + zDistance * zDistance);
}
/// <summary>
/// 计算两个包围盒之间的中心点
/// </summary>
/// <param name="box1">第一个包围盒</param>
/// <param name="box2">第二个包围盒</param>
/// <returns>两个包围盒中心点的中点</returns>
public static Point3D CalculateCenter(BoundingBox3D box1, BoundingBox3D box2)
{
var center1 = new Point3D(
(box1.Min.X + box1.Max.X) / 2,
(box1.Min.Y + box1.Max.Y) / 2,
(box1.Min.Z + box1.Max.Z) / 2
);
var center2 = new Point3D(
(box2.Min.X + box2.Max.X) / 2,
(box2.Min.Y + box2.Max.Y) / 2,
(box2.Min.Z + box2.Max.Z) / 2
);
return new Point3D(
(center1.X + center2.X) / 2,
(center1.Y + center2.Y) / 2,
(center1.Z + center2.Z) / 2
);
}
/// <summary>
/// 检查两个包围盒是否相交(带容差)
/// </summary>
/// <param name="box1">第一个包围盒</param>
/// <param name="box2">第二个包围盒</param>
/// <param name="tolerance">容差值</param>
/// <returns>如果包围盒在容差范围内相交则返回true</returns>
public static bool BoundingBoxesIntersectWithTolerance(BoundingBox3D box1, BoundingBox3D box2, double tolerance)
{
return box1.Min.X <= box2.Max.X + tolerance && box1.Max.X >= box2.Min.X - tolerance &&
box1.Min.Y <= box2.Max.Y + tolerance && box1.Max.Y >= box2.Min.Y - tolerance &&
box1.Min.Z <= box2.Max.Z + tolerance && box1.Max.Z >= box2.Min.Z - tolerance;
}
/// <summary>
/// 检查两个包围盒是否相交
/// </summary>
/// <param name="box1">第一个包围盒</param>
/// <param name="box2">第二个包围盒</param>
/// <returns>如果包围盒相交则返回true</returns>
public static bool BoundingBoxesIntersect(BoundingBox3D box1, BoundingBox3D box2)
{
return box1.Min.X <= box2.Max.X && box1.Max.X >= box2.Min.X &&
box1.Min.Y <= box2.Max.Y && box1.Max.Y >= box2.Min.Y &&
box1.Min.Z <= box2.Max.Z && box1.Max.Z >= box2.Min.Z;
}
/// <summary>
/// 计算两个3D点之间的距离
/// </summary>
/// <param name="point1">第一个点</param>
/// <param name="point2">第二个点</param>
/// <returns>两点之间的欧几里得距离</returns>
public static double CalculatePointDistance(Point3D point1, Point3D point2)
{
var dx = point1.X - point2.X;
var dy = point1.Y - point2.Y;
var dz = point1.Z - point2.Z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
/// <summary>
/// 获取包围盒的中心点
/// </summary>
/// <param name="boundingBox">包围盒</param>
/// <returns>包围盒的中心点</returns>
public static Point3D GetBoundingBoxCenter(BoundingBox3D boundingBox)
{
return new Point3D(
(boundingBox.Min.X + boundingBox.Max.X) / 2,
(boundingBox.Min.Y + boundingBox.Max.Y) / 2,
(boundingBox.Min.Z + boundingBox.Max.Z) / 2
);
}
/// <summary>
/// 获取包围盒的体积
/// </summary>
/// <param name="boundingBox">包围盒</param>
/// <returns>包围盒的体积</returns>
public static double GetBoundingBoxVolume(BoundingBox3D boundingBox)
{
var width = boundingBox.Max.X - boundingBox.Min.X;
var height = boundingBox.Max.Y - boundingBox.Min.Y;
var depth = boundingBox.Max.Z - boundingBox.Min.Z;
return width * height * depth;
}
/// <summary>
/// 扩展包围盒(在各个方向上增加指定的边距)
/// </summary>
/// <param name="boundingBox">原始包围盒</param>
/// <param name="margin">边距值</param>
/// <returns>扩展后的包围盒</returns>
public static BoundingBox3D ExpandBoundingBox(BoundingBox3D boundingBox, double margin)
{
return new BoundingBox3D(
new Point3D(
boundingBox.Min.X - margin,
boundingBox.Min.Y - margin,
boundingBox.Min.Z - margin
),
new Point3D(
boundingBox.Max.X + margin,
boundingBox.Max.Y + margin,
boundingBox.Max.Z + margin
)
);
}
/// <summary>
/// 检查点是否在包围盒内
/// </summary>
/// <param name="point">要检查的点</param>
/// <param name="boundingBox">包围盒</param>
/// <param name="tolerance">容差值</param>
/// <returns>如果点在包围盒内则返回true</returns>
public static bool IsPointInBoundingBox(Point3D point, BoundingBox3D boundingBox, double tolerance = 0.0)
{
return point.X >= boundingBox.Min.X - tolerance && point.X <= boundingBox.Max.X + tolerance &&
point.Y >= boundingBox.Min.Y - tolerance && point.Y <= boundingBox.Max.Y + tolerance &&
point.Z >= boundingBox.Min.Z - tolerance && point.Z <= boundingBox.Max.Z + tolerance;
}
}
}

View File

@ -55,6 +55,68 @@ namespace NavisworksTransport.Utils
}
}
/// <summary>
/// 智能查找有意义的父级容器
/// 基于节点类型进行判断:纯几何体或空节点向上查找,有意义的节点直接返回
/// </summary>
/// <param name="geometryItem">几何体对象</param>
/// <returns>有意义的容器,如果没找到则返回原对象</returns>
public static ModelItem FindNamedParentContainer(ModelItem geometryItem)
{
if (geometryItem == null) return null;
try
{
var current = geometryItem;
int levels = 0; // 防止无限递归
while (current != null && levels < 10) // 最多向上查找10层
{
var nodeType = GetModelItemType(current);
var displayName = GetSafeDisplayName(current);
LogManager.Debug($"[容器查找] 检查节点: '{displayName}' (类型:{nodeType}, 层级:{levels})");
// 根据节点类型决策
switch (nodeType)
{
case ModelItemType.GroupNode:
case ModelItemType.HybridNode:
// 集合节点或混合节点:有意义的容器,停止查找
if (!string.IsNullOrEmpty(displayName))
{
LogManager.Debug($"[智能容器映射] 找到有意义容器: '{GetSafeDisplayName(geometryItem)}' -> '{displayName}' (向上{levels}层, 类型:{nodeType})");
return current;
}
else
{
LogManager.Debug($"[容器查找] {nodeType}节点无名称,继续向上查找");
}
break;
case ModelItemType.PureGeometry:
case ModelItemType.EmptyNode:
// 纯几何体或空节点:通常无意义,需要继续向上查找父节点
LogManager.Debug($"[容器查找] {nodeType}节点,向上查找父节点");
break;
}
// 继续向上查找
current = current.Parent;
levels++;
}
// 如果没有找到合适的容器,返回原对象
LogManager.Warning($"[智能容器映射] 未找到有意义的父级容器,使用原对象: {GetSafeDisplayName(geometryItem)}");
return geometryItem;
}
catch (Exception ex)
{
LogManager.Warning($"[智能容器映射] 查找父级容器时出错: {ex.Message}");
return geometryItem; // 出错时返回原对象
}
}
/// <summary>
/// 安全获取ModelItem的DisplayName避免访问已释放对象的警告
/// </summary>
@ -103,10 +165,11 @@ namespace NavisworksTransport.Utils
{
try
{
var relatedNodes = new List<ModelItem>();
// 添加自身
relatedNodes.Add(selectedObject);
var relatedNodes = new List<ModelItem>
{
// 添加自身
selectedObject
};
// 判断节点类型
var itemType = GetModelItemType(selectedObject);