用节点类型(是否只包含几何体)来进行节点包含判断,废掉包围盒的方式。

This commit is contained in:
tian 2025-09-04 12:31:29 +08:00
parent 2cd3772105
commit d75582d664
4 changed files with 298 additions and 789 deletions

View File

@ -242,7 +242,7 @@
<!-- Utils --> <!-- Utils -->
<Compile Include="src\Utils\CoordinateConverter.cs" /> <Compile Include="src\Utils\CoordinateConverter.cs" />
<Compile Include="src\Utils\FloorDetector.cs" /> <Compile Include="src\Utils\FloorDetector.cs" />
<Compile Include="src\Utils\GeometryAnalysisHelper.cs" /> <Compile Include="src\Utils\ModelItemAnalysisHelper.cs" />
<Compile Include="src\Utils\GeometryExtractor.cs" /> <Compile Include="src\Utils\GeometryExtractor.cs" />
<Compile Include="src\Utils\LogManager.cs" /> <Compile Include="src\Utils\LogManager.cs" />
<Compile Include="src\Utils\NavisworksApiHelper.cs" /> <Compile Include="src\Utils\NavisworksApiHelper.cs" />

View File

@ -64,7 +64,7 @@ namespace NavisworksTransport.Core.Animation
// 只构建特定于动画对象的逻辑排除列表 // 只构建特定于动画对象的逻辑排除列表
LogManager.Info("[缓存管理] 构建逻辑排除列表..."); LogManager.Info("[缓存管理] 构建逻辑排除列表...");
var exclusionList = BuildLogicalObjectExclusionList(animationObject); var exclusionList = ModelItemAnalysisHelper.CollectRelatedNodes(animationObject);
// 更新缓存 // 更新缓存
_currentCachedAnimationObject = animationObject; _currentCachedAnimationObject = animationObject;
@ -137,8 +137,8 @@ namespace NavisworksTransport.Core.Animation
// 检查对象是否相同 // 检查对象是否相同
if (!_currentCachedAnimationObject.Equals(animationObject)) if (!_currentCachedAnimationObject.Equals(animationObject))
{ {
string cachedObjectName = GeometryAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); string cachedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject);
string currentObjectName = GeometryAnalysisHelper.GetSafeDisplayName(animationObject); string currentObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animationObject);
LogManager.Debug($"[缓存验证] 动画对象已变更: '{cachedObjectName}' -> '{currentObjectName}'"); LogManager.Debug($"[缓存验证] 动画对象已变更: '{cachedObjectName}' -> '{currentObjectName}'");
return false; return false;
} }
@ -171,7 +171,7 @@ namespace NavisworksTransport.Core.Animation
try try
{ {
// 安全访问DisplayName防止WeakRef已释放的警告 // 安全访问DisplayName防止WeakRef已释放的警告
string objectName = GeometryAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject);
LogManager.Debug($"[缓存管理] 清除缓存: {objectName}"); LogManager.Debug($"[缓存管理] 清除缓存: {objectName}");
} }
catch (Exception ex) catch (Exception ex)
@ -204,7 +204,7 @@ namespace NavisworksTransport.Core.Animation
} }
var age = DateTime.Now - _cacheCreatedTime; var age = DateTime.Now - _cacheCreatedTime;
string objectName = GeometryAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject);
var count = _cachedExclusionList?.Count ?? 0; var count = _cachedExclusionList?.Count ?? 0;
return $"缓存状态: 对象='{objectName}', 排除数={count}, 年龄={age.TotalSeconds:F1}秒"; return $"缓存状态: 对象='{objectName}', 排除数={count}, 年龄={age.TotalSeconds:F1}秒";
@ -530,83 +530,6 @@ namespace NavisworksTransport.Core.Animation
} }
} }
#region ( ClashDetectiveIntegration )
/// <summary>
/// 构建逻辑物理对象的排除列表
/// </summary>
/// <param name="animatedObject">动画对象</param>
/// <returns>应该从碰撞检测中排除的所有对象列表</returns>
private List<ModelItem> BuildLogicalObjectExclusionList(ModelItem animatedObject)
{
try
{
LogManager.Debug($"[排除列表构建] 开始为动画对象构建排除列表: {animatedObject.DisplayName}");
var exclusionList = new List<ModelItem>();
// 1. 首先进行几何边界分析
var geometryAnalysis = GeometryAnalysisHelper.AnalyzeGeometricBoundary(animatedObject);
// 2. 根据分析结果确定排除范围
switch (geometryAnalysis.LogicalObjectScope)
{
case LogicalObjectScope.SelectedNodeOnly:
exclusionList.Add(animatedObject);
LogManager.Debug($"[排除列表构建] 使用仅选中节点策略");
break;
case LogicalObjectScope.NodeAndDescendants:
exclusionList.AddRange(animatedObject.DescendantsAndSelf.Where(d => d.HasGeometry));
LogManager.Debug($"[排除列表构建] 使用节点及后代策略,排除 {exclusionList.Count} 个对象");
break;
case LogicalObjectScope.ParentAndSiblings:
if (animatedObject.Parent != null)
{
exclusionList.AddRange(animatedObject.Parent.DescendantsAndSelf.Where(d => d.HasGeometry));
}
else
{
exclusionList.AddRange(animatedObject.DescendantsAndSelf.Where(d => d.HasGeometry));
}
LogManager.Debug($"[排除列表构建] 使用父节点及兄弟策略,排除 {exclusionList.Count} 个对象");
break;
case LogicalObjectScope.EntireLogicalComponent:
exclusionList.AddRange(geometryAnalysis.RelatedNodes.Where(n => n.HasGeometry));
LogManager.Debug($"[排除列表构建] 使用整个逻辑组件策略,排除 {exclusionList.Count} 个对象");
break;
}
// 3. 补充几何空间分析的相关对象
GeometryAnalysisHelper.SupplementSpatiallyRelatedObjects(animatedObject, exclusionList);
// 4. 去重并验证
exclusionList = exclusionList.Distinct().Where(item => GeometryAnalysisHelper.IsModelItemValid(item)).ToList();
LogManager.Info($"[排除列表构建] 最终排除列表包含 {exclusionList.Count} 个对象:");
foreach (var item in exclusionList.Take(10)) // 只记录前10个避免日志过长
{
LogManager.Debug($" - 排除对象: {item.DisplayName ?? ""}");
}
if (exclusionList.Count > 10)
{
LogManager.Debug($" - ... 以及其他 {exclusionList.Count - 10} 个对象");
}
return exclusionList;
}
catch (Exception ex)
{
LogManager.Error($"[排除列表构建] 构建失败: {ex.Message}", ex);
// 返回最基本的排除列表(仅动画对象本身)
return new List<ModelItem> { animatedObject };
}
}
#endregion
/// <summary> /// <summary>
/// 资源清理 /// 资源清理
/// </summary> /// </summary>

View File

@ -1,706 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils
{
/// <summary>
/// 几何体分析和包围盒操作工具类
/// 提供用于几何体归属分析、包围盒计算和空间关系判断的静态方法
/// </summary>
public static class GeometryAnalysisHelper
{
#region
/// <summary>
/// 计算包围盒体积
/// </summary>
/// <param name="bbox">包围盒</param>
/// <returns>包围盒的体积</returns>
public static double CalculateBoundingBoxVolume(BoundingBox3D bbox)
{
try
{
var width = Math.Abs(bbox.Max.X - bbox.Min.X);
var height = Math.Abs(bbox.Max.Y - bbox.Min.Y);
var depth = Math.Abs(bbox.Max.Z - bbox.Min.Z);
return width * height * depth;
}
catch
{
return 0.0;
}
}
/// <summary>
/// 检查一个包围盒是否完全包含另一个包围盒
/// </summary>
/// <param name="container">外层包围盒</param>
/// <param name="contained">内层包围盒</param>
/// <param name="tolerance">容差值默认1mm</param>
/// <returns>如果container完全包含contained返回true</returns>
public static bool BoundingBoxContains(BoundingBox3D container, BoundingBox3D contained, double tolerance = 0.001)
{
try
{
// 检查六个面是否都在容差范围内包含
var containsMinX = container.Min.X <= contained.Min.X + tolerance;
var containsMinY = container.Min.Y <= contained.Min.Y + tolerance;
var containsMinZ = container.Min.Z <= contained.Min.Z + tolerance;
var containsMaxX = container.Max.X >= contained.Max.X - tolerance;
var containsMaxY = container.Max.Y >= contained.Max.Y - tolerance;
var containsMaxZ = container.Max.Z >= contained.Max.Z - tolerance;
var result = containsMinX && containsMinY && containsMinZ &&
containsMaxX && containsMaxY && containsMaxZ;
if (result)
{
LogManager.Debug($"[包围盒包含] 检测到包含关系: " +
$"外层[{container.Min.X:F2},{container.Min.Y:F2},{container.Min.Z:F2}] - [{container.Max.X:F2},{container.Max.Y:F2},{container.Max.Z:F2}] " +
$"包含 内层[{contained.Min.X:F2},{contained.Min.Y:F2},{contained.Min.Z:F2}] - [{contained.Max.X:F2},{contained.Max.Y:F2},{contained.Max.Z:F2}]");
}
return result;
}
catch (Exception ex)
{
LogManager.Warning($"[包围盒包含] 检测异常: {ex.Message}");
return false;
}
}
/// <summary>
/// 检查两个包围盒是否有显著重叠(用于判断是否属于同一逻辑对象)
/// </summary>
/// <param name="bbox1">包围盒1</param>
/// <param name="bbox2">包围盒2</param>
/// <param name="overlapThreshold">重叠阈值0.0-1.0),表示重叠体积占较小包围盒体积的比例</param>
/// <returns>如果重叠程度超过阈值返回true</returns>
public static bool BoundingBoxesHaveSignificantOverlap(BoundingBox3D bbox1, BoundingBox3D bbox2, double overlapThreshold = 0.5)
{
try
{
// 计算重叠区域
var overlapMin = new Point3D(
Math.Max(bbox1.Min.X, bbox2.Min.X),
Math.Max(bbox1.Min.Y, bbox2.Min.Y),
Math.Max(bbox1.Min.Z, bbox2.Min.Z)
);
var overlapMax = new Point3D(
Math.Min(bbox1.Max.X, bbox2.Max.X),
Math.Min(bbox1.Max.Y, bbox2.Max.Y),
Math.Min(bbox1.Max.Z, bbox2.Max.Z)
);
// 检查是否有重叠
if (overlapMin.X >= overlapMax.X || overlapMin.Y >= overlapMax.Y || overlapMin.Z >= overlapMax.Z)
{
return false; // 没有重叠
}
// 计算重叠体积
var overlapVolume = (overlapMax.X - overlapMin.X) *
(overlapMax.Y - overlapMin.Y) *
(overlapMax.Z - overlapMin.Z);
// 计算两个包围盒的体积
var volume1 = CalculateBoundingBoxVolume(bbox1);
var volume2 = CalculateBoundingBoxVolume(bbox2);
var smallerVolume = Math.Min(volume1, volume2);
if (smallerVolume <= 0) return false;
var overlapRatio = overlapVolume / smallerVolume;
var hasSignificantOverlap = overlapRatio >= overlapThreshold;
if (hasSignificantOverlap)
{
LogManager.Debug($"[包围盒重叠] 检测到显著重叠: 重叠比例 {overlapRatio:F3} >= {overlapThreshold:F3}");
}
return hasSignificantOverlap;
}
catch (Exception ex)
{
LogManager.Warning($"[包围盒重叠] 检测异常: {ex.Message}");
return false;
}
}
/// <summary>
/// 检查两个对象是否在空间上非常接近(可能是同一逻辑对象的不同表示)
/// </summary>
/// <param name="bbox1">包围盒1</param>
/// <param name="bbox2">包围盒2</param>
/// <param name="proximityThreshold">接近阈值默认10cm</param>
/// <returns>如果两个对象非常接近返回true</returns>
public static bool BoundingBoxesAreVeryClose(BoundingBox3D bbox1, BoundingBox3D bbox2, double proximityThreshold = 0.1)
{
try
{
// 计算两个包围盒中心点的距离
var center1 = new Point3D(
(bbox1.Min.X + bbox1.Max.X) / 2,
(bbox1.Min.Y + bbox1.Max.Y) / 2,
(bbox1.Min.Z + bbox1.Max.Z) / 2
);
var center2 = new Point3D(
(bbox2.Min.X + bbox2.Max.X) / 2,
(bbox2.Min.Y + bbox2.Max.Y) / 2,
(bbox2.Min.Z + bbox2.Max.Z) / 2
);
var centerDistance = CalculatePointDistance(center1, center2);
var areClose = centerDistance <= proximityThreshold;
if (areClose)
{
LogManager.Debug($"[包围盒接近] 检测到接近对象: 中心距离 {centerDistance:F3}m <= {proximityThreshold:F3}m");
}
return areClose;
}
catch (Exception ex)
{
LogManager.Warning($"[包围盒接近] 检测异常: {ex.Message}");
return false;
}
}
/// <summary>
/// 检查两个包围盒是否相交(带容差)
/// </summary>
/// <param name="box1">包围盒1</param>
/// <param name="box2">包围盒2</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;
}
#endregion
#region
/// <summary>
/// 计算两个3D点之间的距离
/// </summary>
/// <param name="point1">点1</param>
/// <param name="point2">点2</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>
/// 检查ModelItem是否仍然有效
/// </summary>
/// <param name="item">要检查的ModelItem</param>
/// <returns>如果ModelItem有效返回true</returns>
public static 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>
/// 安全获取ModelItem的DisplayName避免访问已释放对象的警告
/// </summary>
/// <param name="item">ModelItem对象</param>
/// <returns>安全的显示名称</returns>
public static string GetSafeDisplayName(ModelItem item)
{
try
{
if (item == null)
{
return "NULL";
}
// 尝试访问DisplayName如果对象已被释放会抛出异常
return item.DisplayName ?? "未命名对象";
}
catch (System.ObjectDisposedException)
{
return "已释放对象";
}
catch (System.Runtime.InteropServices.COMException)
{
return "COM对象已释放";
}
catch (Exception ex)
{
return $"访问失败({ex.GetType().Name})";
}
}
#endregion
#region
/// <summary>
/// 综合判断两个ModelItem是否属于同一个逻辑物理对象
/// </summary>
/// <param name="item1">对象1</param>
/// <param name="item2">对象2</param>
/// <returns>如果属于同一逻辑对象返回true</returns>
public static bool BelongsToSameLogicalObject(ModelItem item1, ModelItem item2)
{
try
{
// 1. 引用相等检查
if (ReferenceEquals(item1, item2) || item1.Equals(item2))
{
LogManager.Debug($"[逻辑对象判断] 引用相等: {item1.DisplayName}");
return true;
}
// 2. 获取包围盒
var bbox1 = item1.BoundingBox();
var bbox2 = item2.BoundingBox();
// 3. 包含关系检查
if (BoundingBoxContains(bbox1, bbox2) || BoundingBoxContains(bbox2, bbox1))
{
LogManager.Debug($"[逻辑对象判断] 包含关系: {item1.DisplayName} <-> {item2.DisplayName}");
return true;
}
// 4. 显著重叠检查
if (BoundingBoxesHaveSignificantOverlap(bbox1, bbox2, 0.7)) // 70%重叠阈值
{
LogManager.Debug($"[逻辑对象判断] 显著重叠: {item1.DisplayName} <-> {item2.DisplayName}");
return true;
}
// 5. 空间接近检查(对于小对象)
var volume1 = CalculateBoundingBoxVolume(bbox1);
var volume2 = CalculateBoundingBoxVolume(bbox2);
var maxVolume = Math.Max(volume1, volume2);
// 如果是相对较小的对象体积小于1立方米使用更严格的接近检查
if (maxVolume < 1.0 && BoundingBoxesAreVeryClose(bbox1, bbox2, 0.05)) // 5cm阈值
{
LogManager.Debug($"[逻辑对象判断] 小对象接近: {item1.DisplayName} <-> {item2.DisplayName}");
return true;
}
return false;
}
catch (Exception ex)
{
LogManager.Warning($"[逻辑对象判断] 判断异常: {ex.Message}");
return false;
}
}
/// <summary>
/// 分析选中对象的完整几何边界,识别逻辑物理对象的范围
/// </summary>
/// <param name="selectedObject">用户选中的对象</param>
/// <returns>几何边界分析结果</returns>
public static GeometricBoundaryAnalysis AnalyzeGeometricBoundary(ModelItem selectedObject)
{
try
{
LogManager.Debug($"[几何边界分析] 开始分析对象: {selectedObject.DisplayName}");
var analysis = new GeometricBoundaryAnalysis
{
SelectedObject = selectedObject,
RootBoundingBox = selectedObject.BoundingBox(),
RelatedNodes = new List<ModelItem>()
};
// 1. 收集所有相关节点(向上和向下遍历)
CollectRelatedNodes(selectedObject, analysis);
// 2. 分析几何包含关系
AnalyzeGeometricContainment(analysis);
// 3. 建立逻辑物理对象边界
DefineLogicalObjectBoundary(analysis);
LogManager.Debug($"[几何边界分析] 完成,发现 {analysis.RelatedNodes.Count} 个相关节点");
LogManager.Debug($"[几何边界分析] 逻辑对象范围: {analysis.LogicalObjectScope}");
return analysis;
}
catch (Exception ex)
{
LogManager.Error($"[几何边界分析] 分析失败: {ex.Message}", ex);
return CreateFallbackAnalysis(selectedObject);
}
}
/// <summary>
/// 收集与选中对象相关的所有节点
/// </summary>
/// <param name="selectedObject">选中的对象</param>
/// <param name="analysis">分析结果对象</param>
public static void CollectRelatedNodes(ModelItem selectedObject, GeometricBoundaryAnalysis analysis)
{
try
{
// 添加自身
analysis.RelatedNodes.Add(selectedObject);
// 1. 向上收集:父节点、祖父节点等
CollectAncestorNodes(selectedObject, analysis);
// 2. 向下收集:子节点、孙节点等
CollectDescendantNodes(selectedObject, analysis);
// 3. 收集兄弟节点(同级节点)
CollectSiblingNodes(selectedObject, analysis);
// 去重
analysis.RelatedNodes = analysis.RelatedNodes.Distinct().ToList();
LogManager.Debug($"[收集相关节点] 总共收集到 {analysis.RelatedNodes.Count} 个节点");
}
catch (Exception ex)
{
LogManager.Warning($"[收集相关节点] 收集过程异常: {ex.Message}");
}
}
/// <summary>
/// 收集祖先节点
/// </summary>
/// <param name="item">起始节点</param>
/// <param name="analysis">分析结果对象</param>
public static void CollectAncestorNodes(ModelItem item, GeometricBoundaryAnalysis analysis)
{
try
{
var current = item.Parent;
int levels = 0;
while (current != null && levels < 10) // 最多向上10层避免无限循环
{
analysis.RelatedNodes.Add(current);
// 如果当前节点的包围盒显著大于原始对象,可能已经到了更大的组件
var currentBbox = current.BoundingBox();
var originalBbox = analysis.RootBoundingBox;
// 如果包围盒体积增大超过5倍可能已经超出了逻辑对象范围
var currentVolume = CalculateBoundingBoxVolume(currentBbox);
var originalVolume = CalculateBoundingBoxVolume(originalBbox);
if (currentVolume > originalVolume * 5.0)
{
LogManager.Debug($"[祖先节点收集] 在第{levels}层停止,包围盒体积增大过多");
break;
}
current = current.Parent;
levels++;
}
LogManager.Debug($"[祖先节点收集] 向上收集了 {levels} 层节点");
}
catch (Exception ex)
{
LogManager.Warning($"[祖先节点收集] 收集异常: {ex.Message}");
}
}
/// <summary>
/// 收集后代节点
/// </summary>
/// <param name="item">起始节点</param>
/// <param name="analysis">分析结果对象</param>
public static void CollectDescendantNodes(ModelItem item, GeometricBoundaryAnalysis analysis)
{
try
{
var descendants = item.DescendantsAndSelf.Where(d => d.HasGeometry).ToList();
analysis.RelatedNodes.AddRange(descendants);
LogManager.Debug($"[后代节点收集] 收集了 {descendants.Count} 个有几何体的后代节点");
}
catch (Exception ex)
{
LogManager.Warning($"[后代节点收集] 收集异常: {ex.Message}");
}
}
/// <summary>
/// 收集兄弟节点
/// </summary>
/// <param name="item">起始节点</param>
/// <param name="analysis">分析结果对象</param>
public static void CollectSiblingNodes(ModelItem item, GeometricBoundaryAnalysis analysis)
{
try
{
if (item.Parent != null)
{
var siblings = item.Parent.Children.Where(child =>
child.HasGeometry && !child.Equals(item)).ToList();
// 只收集包围盒有重叠或非常接近的兄弟节点
var itemBbox = item.BoundingBox();
var tolerance = 0.1; // 10cm容差
foreach (var sibling in siblings)
{
var siblingBbox = sibling.BoundingBox();
if (BoundingBoxesIntersectWithTolerance(itemBbox, siblingBbox, tolerance))
{
analysis.RelatedNodes.Add(sibling);
}
}
LogManager.Debug($"[兄弟节点收集] 收集了 {siblings.Count} 个相关兄弟节点");
}
}
catch (Exception ex)
{
LogManager.Warning($"[兄弟节点收集] 收集异常: {ex.Message}");
}
}
/// <summary>
/// 分析几何包含关系
/// </summary>
/// <param name="analysis">分析结果对象</param>
public static void AnalyzeGeometricContainment(GeometricBoundaryAnalysis analysis)
{
try
{
analysis.ContainmentRelations = new Dictionary<ModelItem, List<ModelItem>>();
foreach (var item in analysis.RelatedNodes)
{
analysis.ContainmentRelations[item] = new List<ModelItem>();
var itemBbox = item.BoundingBox();
foreach (var other in analysis.RelatedNodes)
{
if (item.Equals(other)) continue;
var otherBbox = other.BoundingBox();
if (BoundingBoxContains(itemBbox, otherBbox))
{
analysis.ContainmentRelations[item].Add(other);
}
}
}
LogManager.Debug($"[几何包含分析] 分析了 {analysis.RelatedNodes.Count} 个节点的包含关系");
}
catch (Exception ex)
{
LogManager.Warning($"[几何包含分析] 分析异常: {ex.Message}");
}
}
/// <summary>
/// 定义逻辑物理对象边界
/// </summary>
/// <param name="analysis">分析结果对象</param>
public static void DefineLogicalObjectBoundary(GeometricBoundaryAnalysis analysis)
{
try
{
// 策略:找到一个合适的"逻辑根节点"
// 优先级1. 有明确名称的节点 2. 包含关系合理的节点 3. 包围盒大小适中的节点
var candidates = analysis.RelatedNodes.Where(node =>
!string.IsNullOrEmpty(node.DisplayName) &&
node.HasGeometry).ToList();
if (candidates.Any())
{
// 选择包围盒最接近原始选择对象的有名称节点
var originalVolume = CalculateBoundingBoxVolume(analysis.RootBoundingBox);
var bestCandidate = candidates
.OrderBy(c => Math.Abs(CalculateBoundingBoxVolume(c.BoundingBox()) - originalVolume))
.First();
analysis.LogicalObjectScope = LogicalObjectScope.NodeAndDescendants;
analysis.LogicalRootNode = bestCandidate;
LogManager.Debug($"[逻辑边界定义] 选择逻辑根节点: {bestCandidate.DisplayName}");
}
else
{
// 没有合适的候选,使用原始选择
analysis.LogicalObjectScope = LogicalObjectScope.SelectedNodeOnly;
analysis.LogicalRootNode = analysis.SelectedObject;
LogManager.Debug($"[逻辑边界定义] 使用原始选择作为逻辑边界");
}
}
catch (Exception ex)
{
LogManager.Warning($"[逻辑边界定义] 定义异常: {ex.Message}");
analysis.LogicalObjectScope = LogicalObjectScope.SelectedNodeOnly;
analysis.LogicalRootNode = analysis.SelectedObject;
}
}
/// <summary>
/// 创建备用分析结果(当主分析失败时)
/// </summary>
/// <param name="selectedObject">选中的对象</param>
/// <returns>备用分析结果</returns>
public static GeometricBoundaryAnalysis CreateFallbackAnalysis(ModelItem selectedObject)
{
return new GeometricBoundaryAnalysis
{
SelectedObject = selectedObject,
RootBoundingBox = selectedObject.BoundingBox(),
RelatedNodes = new List<ModelItem> { selectedObject },
LogicalObjectScope = LogicalObjectScope.SelectedNodeOnly,
LogicalRootNode = selectedObject,
ContainmentRelations = new Dictionary<ModelItem, List<ModelItem>>()
};
}
/// <summary>
/// 补充空间相关的对象到排除列表
/// </summary>
/// <param name="animatedObject">动画对象</param>
/// <param name="exclusionList">当前排除列表</param>
public static void SupplementSpatiallyRelatedObjects(ModelItem animatedObject, List<ModelItem> exclusionList)
{
try
{
LogManager.Debug($"[空间关联补充] 开始补充空间相关对象");
var animatedBBox = animatedObject.BoundingBox();
var originalCount = exclusionList.Count;
// 获取所有有几何体的对象进行空间分析
var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(item => item.HasGeometry && !exclusionList.Contains(item))
.ToList();
foreach (var item in allItems)
{
try
{
// 使用 BelongsToSameLogicalObject 方法进行综合判断
if (BelongsToSameLogicalObject(animatedObject, item))
{
exclusionList.Add(item);
LogManager.Debug($"[空间关联补充] 添加空间相关对象: {item.DisplayName}");
}
}
catch (Exception itemEx)
{
LogManager.Warning($"[空间关联补充] 分析对象时异常 {item.DisplayName}: {itemEx.Message}");
}
}
var addedCount = exclusionList.Count - originalCount;
LogManager.Debug($"[空间关联补充] 补充了 {addedCount} 个空间相关对象");
}
catch (Exception ex)
{
LogManager.Warning($"[空间关联补充] 补充过程异常: {ex.Message}");
}
}
#endregion
}
#region
/// <summary>
/// 几何边界分析结果
/// </summary>
public class GeometricBoundaryAnalysis
{
/// <summary>
/// 用户选中的原始对象
/// </summary>
public ModelItem SelectedObject { get; set; }
/// <summary>
/// 原始对象的包围盒
/// </summary>
public BoundingBox3D RootBoundingBox { get; set; }
/// <summary>
/// 所有相关节点(父、子、兄弟等)
/// </summary>
public List<ModelItem> RelatedNodes { get; set; }
/// <summary>
/// 逻辑物理对象的范围定义
/// </summary>
public LogicalObjectScope LogicalObjectScope { get; set; }
/// <summary>
/// 逻辑对象的根节点
/// </summary>
public ModelItem LogicalRootNode { get; set; }
/// <summary>
/// 节点间的包含关系映射
/// </summary>
public Dictionary<ModelItem, List<ModelItem>> ContainmentRelations { get; set; }
}
/// <summary>
/// 逻辑对象范围枚举
/// </summary>
public enum LogicalObjectScope
{
/// <summary>
/// 仅选中节点本身
/// </summary>
SelectedNodeOnly,
/// <summary>
/// 选中节点及其所有后代
/// </summary>
NodeAndDescendants,
/// <summary>
/// 选中节点的父节点及其所有子树
/// </summary>
ParentAndSiblings,
/// <summary>
/// 整个逻辑组件(自动识别的边界)
/// </summary>
EntireLogicalComponent
}
#endregion
}

View File

@ -0,0 +1,292 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils
{
/// <summary>
/// 节点分析工具类
/// 提供用于节点归属分析的静态方法
/// </summary>
public static class ModelItemAnalysisHelper
{
#region
/// <summary>
/// 计算两个3D点之间的距离
/// </summary>
/// <param name="point1">点1</param>
/// <param name="point2">点2</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>
/// 检查ModelItem是否仍然有效
/// </summary>
/// <param name="item">要检查的ModelItem</param>
/// <returns>如果ModelItem有效返回true</returns>
public static 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>
/// 安全获取ModelItem的DisplayName避免访问已释放对象的警告
/// </summary>
/// <param name="item">ModelItem对象</param>
/// <returns>安全的显示名称</returns>
public static string GetSafeDisplayName(ModelItem item)
{
try
{
if (item == null)
{
return "NULL";
}
// 尝试访问DisplayName如果对象已被释放会抛出异常
return item.DisplayName ?? "未命名对象";
}
catch (System.ObjectDisposedException)
{
return "已释放对象";
}
catch (System.Runtime.InteropServices.COMException)
{
return "COM对象已释放";
}
catch (Exception ex)
{
return $"访问失败({ex.GetType().Name})";
}
}
#endregion
#region
/// <summary>
/// 收集与选中对象相关的所有节点
/// 基于节点类型的智能收集策略:
/// - 纯几何体:找父节点,收集父节点及其所有子节点
/// - 集合节点/混合节点:收集自身及所有子节点
/// - 空节点:仅收集自身
/// </summary>
/// <param name="selectedObject">选中的对象</param>
/// <returns>相关节点列表</returns>
public static List<ModelItem> CollectRelatedNodes(ModelItem selectedObject)
{
try
{
var relatedNodes = new List<ModelItem>();
// 添加自身
relatedNodes.Add(selectedObject);
// 判断节点类型
var itemType = GetModelItemType(selectedObject);
LogManager.Debug($"[收集相关节点] 开始收集,节点类型: {itemType}");
// 根据节点类型执行不同的收集策略
switch (itemType)
{
case ModelItemType.PureGeometry:
// 纯几何体:找父节点,收集父节点及其所有子节点
CollectFromParentNode(selectedObject, relatedNodes);
break;
case ModelItemType.GroupNode:
case ModelItemType.HybridNode:
// 集合节点或混合节点:收集自身及所有子节点
CollectFromCurrentNode(selectedObject, relatedNodes);
break;
case ModelItemType.EmptyNode:
// 空节点:仅收集自身(已在开头添加)
LogManager.Debug($"[收集相关节点] 空节点,仅收集自身");
break;
}
// 去重
relatedNodes = relatedNodes.Distinct().ToList();
LogManager.Debug($"[收集相关节点] 总共收集到 {relatedNodes.Count} 个节点");
return relatedNodes;
}
catch (Exception ex)
{
LogManager.Warning($"[收集相关节点] 收集过程异常: {ex.Message}");
return new List<ModelItem> { selectedObject }; // 异常时返回最基本的列表
}
}
/// <summary>
/// 判断模型节点的类型
/// </summary>
/// <param name="item">模型节点</param>
/// <returns>节点类型</returns>
private static ModelItemType GetModelItemType(ModelItem item)
{
try
{
bool hasGeometry = item.HasGeometry;
int childCount = item.Children?.Count() ?? 0;
if (hasGeometry && childCount == 0)
{
LogManager.Debug($"[节点类型判断] '{item.DisplayName}' -> 纯几何体节点");
return ModelItemType.PureGeometry;
}
else if (!hasGeometry && childCount > 0)
{
LogManager.Debug($"[节点类型判断] '{item.DisplayName}' -> 集合节点 (子节点数: {childCount})");
return ModelItemType.GroupNode;
}
else if (hasGeometry && childCount > 0)
{
LogManager.Debug($"[节点类型判断] '{item.DisplayName}' -> 混合节点 (子节点数: {childCount})");
return ModelItemType.HybridNode;
}
else
{
LogManager.Debug($"[节点类型判断] '{item.DisplayName}' -> 空节点");
return ModelItemType.EmptyNode;
}
}
catch (Exception ex)
{
LogManager.Warning($"[节点类型判断] 判断节点类型时异常 '{item.DisplayName}': {ex.Message}");
return ModelItemType.EmptyNode; // 异常时返回最保守的类型
}
}
/// <summary>
/// 从父节点收集相关节点(用于纯几何体节点)
/// </summary>
/// <param name="pureGeometryNode">纯几何体节点</param>
/// <param name="analysis">分析结果对象</param>
/// <summary>
/// 从父节点收集相关节点(用于纯几何体节点)
/// </summary>
/// <param name="pureGeometryNode">纯几何体节点</param>
/// <param name="relatedNodes">相关节点列表</param>
private static void CollectFromParentNode(ModelItem pureGeometryNode, List<ModelItem> relatedNodes)
{
try
{
if (pureGeometryNode.Parent != null)
{
var parent = pureGeometryNode.Parent;
LogManager.Debug($"[父节点收集] 找到父节点: '{parent.DisplayName}'");
// 添加父节点
relatedNodes.Add(parent);
// 收集父节点的所有子节点(包含几何体的)
if (parent.Children != null)
{
var siblings = parent.Children.Where(child => child.HasGeometry).ToList();
relatedNodes.AddRange(siblings);
LogManager.Debug($"[父节点收集] 从父节点 '{parent.DisplayName}' 收集了 {siblings.Count} 个有几何体的子节点");
}
}
else
{
// 没有父节点,可能是根节点,只收集自己的后代
LogManager.Debug($"[父节点收集] '{pureGeometryNode.DisplayName}' 没有父节点,收集自身后代");
CollectFromCurrentNode(pureGeometryNode, relatedNodes);
}
}
catch (Exception ex)
{
LogManager.Warning($"[父节点收集] 收集过程异常: {ex.Message}");
}
}
/// <summary>
/// 从当前节点收集相关节点(用于集合节点和混合节点)
/// </summary>
/// <param name="currentNode">当前节点(集合节点或混合节点)</param>
/// <param name="analysis">分析结果对象</param>
/// <summary>
/// 从当前节点收集相关节点(用于集合节点和混合节点)
/// </summary>
/// <param name="currentNode">当前节点(集合节点或混合节点)</param>
/// <param name="relatedNodes">相关节点列表</param>
private static void CollectFromCurrentNode(ModelItem currentNode, List<ModelItem> relatedNodes)
{
try
{
// 收集当前节点的所有后代节点(包含几何体的)
var descendants = currentNode.DescendantsAndSelf.Where(d => d.HasGeometry).ToList();
relatedNodes.AddRange(descendants);
LogManager.Debug($"[当前节点收集] 从节点 '{currentNode.DisplayName}' 收集了 {descendants.Count} 个有几何体的后代节点");
}
catch (Exception ex)
{
LogManager.Warning($"[当前节点收集] 收集过程异常: {ex.Message}");
}
}
#endregion
}
#region
/// <summary>
/// 模型节点类型枚举
/// </summary>
public enum ModelItemType
{
/// <summary>
/// 纯几何体节点HasGeometry = true, Children.Count = 0
/// </summary>
PureGeometry,
/// <summary>
/// 集合节点HasGeometry = false, Children.Count > 0
/// </summary>
GroupNode,
/// <summary>
/// 混合节点HasGeometry = true, Children.Count > 0
/// </summary>
HybridNode,
/// <summary>
/// 空节点HasGeometry = false, Children.Count = 0
/// </summary>
EmptyNode
}
#endregion
}