diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 2985205..9a63776 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -242,7 +242,7 @@ - + diff --git a/src/Core/Animation/LogisticsAnimationManager.cs b/src/Core/Animation/LogisticsAnimationManager.cs index fbebed1..ad8c480 100644 --- a/src/Core/Animation/LogisticsAnimationManager.cs +++ b/src/Core/Animation/LogisticsAnimationManager.cs @@ -64,7 +64,7 @@ namespace NavisworksTransport.Core.Animation // 只构建特定于动画对象的逻辑排除列表 LogManager.Info("[缓存管理] 构建逻辑排除列表..."); - var exclusionList = BuildLogicalObjectExclusionList(animationObject); + var exclusionList = ModelItemAnalysisHelper.CollectRelatedNodes(animationObject); // 更新缓存 _currentCachedAnimationObject = animationObject; @@ -137,8 +137,8 @@ namespace NavisworksTransport.Core.Animation // 检查对象是否相同 if (!_currentCachedAnimationObject.Equals(animationObject)) { - string cachedObjectName = GeometryAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); - string currentObjectName = GeometryAnalysisHelper.GetSafeDisplayName(animationObject); + string cachedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); + string currentObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animationObject); LogManager.Debug($"[缓存验证] 动画对象已变更: '{cachedObjectName}' -> '{currentObjectName}'"); return false; } @@ -171,7 +171,7 @@ namespace NavisworksTransport.Core.Animation try { // 安全访问DisplayName,防止WeakRef已释放的警告 - string objectName = GeometryAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); + string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); LogManager.Debug($"[缓存管理] 清除缓存: {objectName}"); } catch (Exception ex) @@ -204,7 +204,7 @@ namespace NavisworksTransport.Core.Animation } var age = DateTime.Now - _cacheCreatedTime; - string objectName = GeometryAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); + string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); var count = _cachedExclusionList?.Count ?? 0; return $"缓存状态: 对象='{objectName}', 排除数={count}, 年龄={age.TotalSeconds:F1}秒"; @@ -530,83 +530,6 @@ namespace NavisworksTransport.Core.Animation } } - #region 几何分析功能 (从 ClashDetectiveIntegration 移动而来) - - /// - /// 构建逻辑物理对象的排除列表 - /// - /// 动画对象 - /// 应该从碰撞检测中排除的所有对象列表 - private List BuildLogicalObjectExclusionList(ModelItem animatedObject) - { - try - { - LogManager.Debug($"[排除列表构建] 开始为动画对象构建排除列表: {animatedObject.DisplayName}"); - - var exclusionList = new List(); - - // 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 { animatedObject }; - } - } - - #endregion - /// /// 资源清理 /// diff --git a/src/Utils/GeometryAnalysisHelper.cs b/src/Utils/GeometryAnalysisHelper.cs deleted file mode 100644 index 1f85447..0000000 --- a/src/Utils/GeometryAnalysisHelper.cs +++ /dev/null @@ -1,706 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Autodesk.Navisworks.Api; - -namespace NavisworksTransport.Utils -{ - /// - /// 几何体分析和包围盒操作工具类 - /// 提供用于几何体归属分析、包围盒计算和空间关系判断的静态方法 - /// - public static class GeometryAnalysisHelper - { - #region 包围盒操作方法 - - /// - /// 计算包围盒体积 - /// - /// 包围盒 - /// 包围盒的体积 - 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; - } - } - - /// - /// 检查一个包围盒是否完全包含另一个包围盒 - /// - /// 外层包围盒 - /// 内层包围盒 - /// 容差值,默认1mm - /// 如果container完全包含contained返回true - 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; - } - } - - /// - /// 检查两个包围盒是否有显著重叠(用于判断是否属于同一逻辑对象) - /// - /// 包围盒1 - /// 包围盒2 - /// 重叠阈值(0.0-1.0),表示重叠体积占较小包围盒体积的比例 - /// 如果重叠程度超过阈值返回true - 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; - } - } - - /// - /// 检查两个对象是否在空间上非常接近(可能是同一逻辑对象的不同表示) - /// - /// 包围盒1 - /// 包围盒2 - /// 接近阈值(米),默认10cm - /// 如果两个对象非常接近返回true - 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; - } - } - - /// - /// 检查两个包围盒是否相交(带容差) - /// - /// 包围盒1 - /// 包围盒2 - /// 容差值 - /// 如果两个包围盒相交返回true - 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 辅助方法 - - /// - /// 计算两个3D点之间的距离 - /// - /// 点1 - /// 点2 - /// 两点间的距离 - 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); - } - - /// - /// 检查ModelItem是否仍然有效 - /// - /// 要检查的ModelItem - /// 如果ModelItem有效返回true - 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; - } - } - - /// - /// 安全获取ModelItem的DisplayName,避免访问已释放对象的警告 - /// - /// ModelItem对象 - /// 安全的显示名称 - 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 几何体归属分析方法 - - /// - /// 综合判断两个ModelItem是否属于同一个逻辑物理对象 - /// - /// 对象1 - /// 对象2 - /// 如果属于同一逻辑对象返回true - 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; - } - } - - /// - /// 分析选中对象的完整几何边界,识别逻辑物理对象的范围 - /// - /// 用户选中的对象 - /// 几何边界分析结果 - public static GeometricBoundaryAnalysis AnalyzeGeometricBoundary(ModelItem selectedObject) - { - try - { - LogManager.Debug($"[几何边界分析] 开始分析对象: {selectedObject.DisplayName}"); - - var analysis = new GeometricBoundaryAnalysis - { - SelectedObject = selectedObject, - RootBoundingBox = selectedObject.BoundingBox(), - RelatedNodes = new List() - }; - - // 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); - } - } - - /// - /// 收集与选中对象相关的所有节点 - /// - /// 选中的对象 - /// 分析结果对象 - 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}"); - } - } - - /// - /// 收集祖先节点 - /// - /// 起始节点 - /// 分析结果对象 - 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}"); - } - } - - /// - /// 收集后代节点 - /// - /// 起始节点 - /// 分析结果对象 - 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}"); - } - } - - /// - /// 收集兄弟节点 - /// - /// 起始节点 - /// 分析结果对象 - 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}"); - } - } - - /// - /// 分析几何包含关系 - /// - /// 分析结果对象 - public static void AnalyzeGeometricContainment(GeometricBoundaryAnalysis analysis) - { - try - { - analysis.ContainmentRelations = new Dictionary>(); - - foreach (var item in analysis.RelatedNodes) - { - analysis.ContainmentRelations[item] = new List(); - 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}"); - } - } - - /// - /// 定义逻辑物理对象边界 - /// - /// 分析结果对象 - 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; - } - } - - /// - /// 创建备用分析结果(当主分析失败时) - /// - /// 选中的对象 - /// 备用分析结果 - public static GeometricBoundaryAnalysis CreateFallbackAnalysis(ModelItem selectedObject) - { - return new GeometricBoundaryAnalysis - { - SelectedObject = selectedObject, - RootBoundingBox = selectedObject.BoundingBox(), - RelatedNodes = new List { selectedObject }, - LogicalObjectScope = LogicalObjectScope.SelectedNodeOnly, - LogicalRootNode = selectedObject, - ContainmentRelations = new Dictionary>() - }; - } - - /// - /// 补充空间相关的对象到排除列表 - /// - /// 动画对象 - /// 当前排除列表 - public static void SupplementSpatiallyRelatedObjects(ModelItem animatedObject, List 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 数据结构定义 - - /// - /// 几何边界分析结果 - /// - public class GeometricBoundaryAnalysis - { - /// - /// 用户选中的原始对象 - /// - public ModelItem SelectedObject { get; set; } - - /// - /// 原始对象的包围盒 - /// - public BoundingBox3D RootBoundingBox { get; set; } - - /// - /// 所有相关节点(父、子、兄弟等) - /// - public List RelatedNodes { get; set; } - - /// - /// 逻辑物理对象的范围定义 - /// - public LogicalObjectScope LogicalObjectScope { get; set; } - - /// - /// 逻辑对象的根节点 - /// - public ModelItem LogicalRootNode { get; set; } - - /// - /// 节点间的包含关系映射 - /// - public Dictionary> ContainmentRelations { get; set; } - } - - /// - /// 逻辑对象范围枚举 - /// - public enum LogicalObjectScope - { - /// - /// 仅选中节点本身 - /// - SelectedNodeOnly, - - /// - /// 选中节点及其所有后代 - /// - NodeAndDescendants, - - /// - /// 选中节点的父节点及其所有子树 - /// - ParentAndSiblings, - - /// - /// 整个逻辑组件(自动识别的边界) - /// - EntireLogicalComponent - } - - #endregion -} \ No newline at end of file diff --git a/src/Utils/ModelItemAnalysisHelper.cs b/src/Utils/ModelItemAnalysisHelper.cs new file mode 100644 index 0000000..e642360 --- /dev/null +++ b/src/Utils/ModelItemAnalysisHelper.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Autodesk.Navisworks.Api; + +namespace NavisworksTransport.Utils +{ + /// + /// 节点分析工具类 + /// 提供用于节点归属分析的静态方法 + /// + public static class ModelItemAnalysisHelper + { + + #region 辅助方法 + + /// + /// 计算两个3D点之间的距离 + /// + /// 点1 + /// 点2 + /// 两点间的距离 + 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); + } + + /// + /// 检查ModelItem是否仍然有效 + /// + /// 要检查的ModelItem + /// 如果ModelItem有效返回true + 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; + } + } + + /// + /// 安全获取ModelItem的DisplayName,避免访问已释放对象的警告 + /// + /// ModelItem对象 + /// 安全的显示名称 + 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 节点归属分析方法 + + /// + /// 收集与选中对象相关的所有节点 + /// 基于节点类型的智能收集策略: + /// - 纯几何体:找父节点,收集父节点及其所有子节点 + /// - 集合节点/混合节点:收集自身及所有子节点 + /// - 空节点:仅收集自身 + /// + /// 选中的对象 + /// 相关节点列表 + public static List CollectRelatedNodes(ModelItem selectedObject) + { + try + { + var relatedNodes = new List(); + + // 添加自身 + 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 { selectedObject }; // 异常时返回最基本的列表 + } + } + + /// + /// 判断模型节点的类型 + /// + /// 模型节点 + /// 节点类型 + 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; // 异常时返回最保守的类型 + } + } + + + /// + /// 从父节点收集相关节点(用于纯几何体节点) + /// + /// 纯几何体节点 + /// 分析结果对象 + /// + /// 从父节点收集相关节点(用于纯几何体节点) + /// + /// 纯几何体节点 + /// 相关节点列表 + private static void CollectFromParentNode(ModelItem pureGeometryNode, List 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}"); + } + } + + + /// + /// 从当前节点收集相关节点(用于集合节点和混合节点) + /// + /// 当前节点(集合节点或混合节点) + /// 分析结果对象 + /// + /// 从当前节点收集相关节点(用于集合节点和混合节点) + /// + /// 当前节点(集合节点或混合节点) + /// 相关节点列表 + private static void CollectFromCurrentNode(ModelItem currentNode, List 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 数据结构定义 + + /// + /// 模型节点类型枚举 + /// + public enum ModelItemType + { + /// + /// 纯几何体节点:HasGeometry = true, Children.Count = 0 + /// + PureGeometry, + + /// + /// 集合节点:HasGeometry = false, Children.Count > 0 + /// + GroupNode, + + /// + /// 混合节点:HasGeometry = true, Children.Count > 0 + /// + HybridNode, + + /// + /// 空节点:HasGeometry = false, Children.Count = 0 + /// + EmptyNode + } + + #endregion +} \ No newline at end of file