From 2cd377210515adf2e63d13adb7cf9d1a39889a1a Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Thu, 4 Sep 2025 10:12:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=E8=8A=82=E7=82=B9=E5=85=B3=E7=B3=BB?= =?UTF-8?q?=E5=92=8C=E5=87=A0=E4=BD=95=E4=BD=93=E5=85=B3=E7=B3=BB=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=BB=8E=E5=8A=A8=E7=94=BB=E7=AE=A1=E7=90=86=E5=99=A8?= =?UTF-8?q?=E4=B8=AD=E6=8A=BD=E5=8F=96=E5=87=BA=E6=9D=A5=EF=BC=8C=E5=BD=A2?= =?UTF-8?q?=E6=88=90=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NavisworksTransportPlugin.csproj | 1 + doc/requirement/todo_features.md | 11 +- .../Animation/LogisticsAnimationManager.cs | 673 +---------------- src/Utils/GeometryAnalysisHelper.cs | 706 ++++++++++++++++++ 4 files changed, 723 insertions(+), 668 deletions(-) create mode 100644 src/Utils/GeometryAnalysisHelper.cs diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 47c3b13..2985205 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -242,6 +242,7 @@ + diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index 3f96818..1aa459c 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -2,12 +2,17 @@ ## 功能点 +### [2025/09/04] + +1. [x] (代码重构)将节点关系和几何体关系代码从动画管理器中抽取出来,形成工具类 +2. [ ] (BUG) 多层建筑动画碰撞有结果,但ClashDetective检测不出来 + ### [2025/09/03] 1. [x] (功能)实现时间标签的UI原型 2. [x] (功能)实现路径规划分析的UI原型 -3. [ ] (性能优化)增加直接采用包围盒的2.5D自动寻路算法,代替空间索引+高度扫描算法。 -4. [ ] (BUG)自动规划有时成功(过滤3个通道,找到26个障碍物),有时失败(过滤2个通道,只找到2个障碍物),连续自动规划,有时会崩溃。 +3. [x] (性能优化)增加直接采用包围盒的2.5D自动寻路算法,代替空间索引+高度扫描算法。 +4. [x] (BUG)自动规划有时成功(过滤3个通道,找到26个障碍物),有时失败(过滤2个通道,只找到2个障碍物),连续自动规划,有时会崩溃。 5. [ ] (BUG)还有厚度为0的障碍物(不一定是bug) 6. [ ] (性能优化) 用SearchAPI来搜索CategoryAttributeManager中的FilterByLogisticsType()、FilterTraversableItems()等方法 @@ -26,7 +31,7 @@ 1. [ ](BUG)路径导出,只有一条路径时,导出按钮不能点击;导出的内容有时不完整,只导出一个包含起点、一个路径点、终点的不存在的路径。 2. [ ](性能优化)垂直扫描处理器性能问题:COM API几何提取效率极低,23个候选项需要5715ms,复杂几何体比简单几何体效率低4倍,需要优化批量处理和并行机制。 -3. [ ](稳定性)修复并行任务未观察异常导致程序崩溃:AggregateException错误表明Task异常处理不当,需要加强并行处理的异常处理和Task生命周期管理。 +3. [x](稳定性)修复并行任务未观察异常导致程序崩溃:AggregateException错误表明Task异常处理不当,需要加强并行处理的异常处理和Task生命周期管理。 ### [2025/08/28] diff --git a/src/Core/Animation/LogisticsAnimationManager.cs b/src/Core/Animation/LogisticsAnimationManager.cs index fa23a9e..fbebed1 100644 --- a/src/Core/Animation/LogisticsAnimationManager.cs +++ b/src/Core/Animation/LogisticsAnimationManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Autodesk.Navisworks.Api; using NavisApplication = Autodesk.Navisworks.Api.Application; +using NavisworksTransport.Utils; namespace NavisworksTransport.Core.Animation { @@ -136,8 +137,8 @@ namespace NavisworksTransport.Core.Animation // 检查对象是否相同 if (!_currentCachedAnimationObject.Equals(animationObject)) { - string cachedObjectName = GetSafeDisplayName(_currentCachedAnimationObject); - string currentObjectName = GetSafeDisplayName(animationObject); + string cachedObjectName = GeometryAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); + string currentObjectName = GeometryAnalysisHelper.GetSafeDisplayName(animationObject); LogManager.Debug($"[缓存验证] 动画对象已变更: '{cachedObjectName}' -> '{currentObjectName}'"); return false; } @@ -170,7 +171,7 @@ namespace NavisworksTransport.Core.Animation try { // 安全访问DisplayName,防止WeakRef已释放的警告 - string objectName = GetSafeDisplayName(_currentCachedAnimationObject); + string objectName = GeometryAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); LogManager.Debug($"[缓存管理] 清除缓存: {objectName}"); } catch (Exception ex) @@ -203,7 +204,7 @@ namespace NavisworksTransport.Core.Animation } var age = DateTime.Now - _cacheCreatedTime; - string objectName = GetSafeDisplayName(_currentCachedAnimationObject); + string objectName = GeometryAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); var count = _cachedExclusionList?.Count ?? 0; return $"缓存状态: 对象='{objectName}', 排除数={count}, 年龄={age.TotalSeconds:F1}秒"; @@ -215,36 +216,6 @@ namespace NavisworksTransport.Core.Animation } - /// - /// 安全获取ModelItem的DisplayName,避免访问已释放对象的警告 - /// - /// ModelItem对象 - /// 安全的显示名称 - private 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 @@ -575,7 +546,7 @@ namespace NavisworksTransport.Core.Animation var exclusionList = new List(); // 1. 首先进行几何边界分析 - var geometryAnalysis = AnalyzeGeometricBoundary(animatedObject); + var geometryAnalysis = GeometryAnalysisHelper.AnalyzeGeometricBoundary(animatedObject); // 2. 根据分析结果确定排除范围 switch (geometryAnalysis.LogicalObjectScope) @@ -609,10 +580,10 @@ namespace NavisworksTransport.Core.Animation } // 3. 补充几何空间分析的相关对象 - SupplementSpatiallyRelatedObjects(animatedObject, exclusionList); + GeometryAnalysisHelper.SupplementSpatiallyRelatedObjects(animatedObject, exclusionList); // 4. 去重并验证 - exclusionList = exclusionList.Distinct().Where(item => IsModelItemValid(item)).ToList(); + exclusionList = exclusionList.Distinct().Where(item => GeometryAnalysisHelper.IsModelItemValid(item)).ToList(); LogManager.Info($"[排除列表构建] 最终排除列表包含 {exclusionList.Count} 个对象:"); foreach (var item in exclusionList.Take(10)) // 只记录前10个,避免日志过长 @@ -634,634 +605,6 @@ namespace NavisworksTransport.Core.Animation } } - /// - /// 分析选中对象的完整几何边界,识别逻辑物理对象的范围 - /// - /// 用户选中的对象 - /// 几何边界分析结果 - private 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); - } - } - - /// - /// 检查ModelItem是否仍然有效 - /// - 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; - } - } - - /// - /// 收集与选中对象相关的所有节点 - /// - private 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}"); - } - } - - /// - /// 收集祖先节点 - /// - private 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}"); - } - } - - /// - /// 收集后代节点 - /// - private 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}"); - } - } - - /// - /// 收集兄弟节点 - /// - private 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}"); - } - } - - /// - /// 计算包围盒体积 - /// - private 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; - } - } - - /// - /// 分析几何包含关系 - /// - private 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}"); - } - } - - /// - /// 定义逻辑物理对象边界 - /// - private 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; - } - } - - /// - /// 创建备用分析结果(当主分析失败时) - /// - private GeometricBoundaryAnalysis CreateFallbackAnalysis(ModelItem selectedObject) - { - return new GeometricBoundaryAnalysis - { - SelectedObject = selectedObject, - RootBoundingBox = selectedObject.BoundingBox(), - RelatedNodes = new List { selectedObject }, - LogicalObjectScope = LogicalObjectScope.SelectedNodeOnly, - LogicalRootNode = selectedObject, - ContainmentRelations = new Dictionary>() - }; - } - - /// - /// 补充空间相关的对象到排除列表 - /// - /// 动画对象 - /// 当前排除列表 - private 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}"); - } - } - - /// - /// 综合判断两个ModelItem是否属于同一个逻辑物理对象 - /// - /// 对象1 - /// 对象2 - /// 如果属于同一逻辑对象返回true - private 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; - } - } - - /// - /// 检查一个包围盒是否完全包含另一个包围盒 - /// - /// 外层包围盒 - /// 内层包围盒 - /// 容差值,默认1mm - /// 如果container完全包含contained返回true - private 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 - private 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 - private 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; - } - } - - /// - /// 计算两个3D点之间的距离 - /// - 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); - } - - /// - /// 检查两个包围盒是否相交(带容差) - /// - 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; - } - - #region 几何体归属分析数据结构 - - /// - /// 几何边界分析结果 - /// - private 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; } - } - - /// - /// 逻辑对象范围枚举 - /// - private enum LogicalObjectScope - { - /// - /// 仅选中节点本身 - /// - SelectedNodeOnly, - - /// - /// 选中节点及其所有后代 - /// - NodeAndDescendants, - - /// - /// 选中节点的父节点及其所有子树 - /// - ParentAndSiblings, - - /// - /// 整个逻辑组件(自动识别的边界) - /// - EntireLogicalComponent - } - - #endregion - - // 其他辅助方法将在下一步添加... - #endregion /// diff --git a/src/Utils/GeometryAnalysisHelper.cs b/src/Utils/GeometryAnalysisHelper.cs new file mode 100644 index 0000000..1f85447 --- /dev/null +++ b/src/Utils/GeometryAnalysisHelper.cs @@ -0,0 +1,706 @@ +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