diff --git a/doc/design/2026/NavisworksAPI使用方法.md b/doc/design/2026/NavisworksAPI使用方法.md index 310e98b..7790f3b 100644 --- a/doc/design/2026/NavisworksAPI使用方法.md +++ b/doc/design/2026/NavisworksAPI使用方法.md @@ -1646,61 +1646,66 @@ using Autodesk.Navisworks.Api.Controls; } ``` -### Tool Enumeration +### Tool Enumeration -Member name Description - None No active tool - Select Select - SelectBox Select Box - RedlineFreehand Redline Freehand - RedlineLine Redline Line - RedlineEllipse Redline Ellipse - RedlineCloud Redline Cloud - RedlineLineString Redline Line String - RedlineTag Redline Tag - RedlineText Redline Text - RedlineErase Redline Erase - RedlineArrow Redline Arrow - MeasurePointToPoint Measure Point To Point - MeasurePointToMultiplePoints Measure Point To Multiple Points - MeasurePointLine Measure Point Line - MeasureAccumulate Measure Accumulate - MeasureAngle Measure Angle - MeasureArea Measure Area - MeasureSingle - BasicViewObjectWheel Basic View Object Wheel - BasicTourBuildingWheel Basic Tour Building Wheel - FullNavigationWheel Full Navigation Wheel - MiniViewObjectWheel Mini View Object Wheel - MiniTourBuildingWheel Mini Tour Building Wheel - MiniFullNavigationWheel Mini Full Navigation Wheel - Full2DNavigationWheel Full 2D Navigation Wheel - CommonPan Pan common across all Autodesk products - CommonZoom Zoom common across all Autodesk products - CommonZoomWindow Zoom Window common across all Autodesk products - CommonOrbit Orbit common across all Autodesk products - CommonFreeOrbit Free Orbit common across all Autodesk products - CommonConstrainedOrbit Constrained Orbit common across all Autodesk products - CommonLookAt Look At common across all Autodesk products - CommonLookAround Look Around common across all Autodesk products - CommonWalk Walk common across all Autodesk products - CommonCenter Center common across all Autodesk products - NavigateFixed Camera fixed in place - NavigateFreeLookAround Classic Navisworks Free Look Around (Swivel) - NavigateFreeOrbit Classic Navisworks Free Orbit (Examine) - NavigateWalk Classic Navisworks Walk - NavigateFly Classic Navisworks Fly - NavigateConstrainedOrbit Classic Navisworks Constrained Orbit (Turntable) - NavigateZoom Classic Navisworks Zoom - NavigatePan Classic Navisworks Pan - NavigateConstrainedPan Classic Navisworks Constrained Pan - NavigateLookAround Clasic Navisworks Look Around (Swivel) - NavigateOrbit Classic Navisworks Orbit - NavigateZoomWindow Classic Navisworks Zoom Window (Zoom Box) - CustomToolPlugin Functionality is provided by a ToolPlugin +● Tool 枚举值 + | 成员名称 | 描述 | + |------------------------------|------------------------| + | None | 无活动工具 | + | Select | 选择工具 | + | SelectBox | 框选工具 | + | RedlineFreehand | 红线手绘工具 | + | RedlineLine | 红线直线工具 | + | RedlineEllipse | 红线椭圆工具 | + | RedlineCloud | 红线云形标注工具 | + | RedlineLineString | 红线连续线工具 | + | RedlineTag | 红线标签工具 | + | RedlineText | 红线文本工具 | + | RedlineErase | 红线擦除工具 | + | RedlineArrow | 红线箭头工具 | + | MeasurePointToPoint | 点到点测量工具 | + | MeasurePointToMultiplePoints | 点到多点测量工具 | + | MeasurePointLine | 点线测量工具 | + | MeasureAccumulate | 累积测量工具 | + | MeasureAngle | 角度测量工具 | + | MeasureArea | 面积测量工具 | + | MeasureSingle | 单点测量工具 | + | BasicViewObjectWheel | 基础视图对象导航轮 | + | BasicTourBuildingWheel | 基础建筑巡游导航轮 | + | FullNavigationWheel | 完整导航轮 | + | MiniViewObjectWheel | 迷你视图对象导航轮 | + | MiniTourBuildingWheel | 迷你建筑巡游导航轮 | + | MiniFullNavigationWheel | 迷你完整导航轮 | + | Full2DNavigationWheel | 完整2D导航轮 | + | CommonPan | 通用平移(所有Autodesk产品通用) | + | CommonZoom | 通用缩放(所有Autodesk产品通用) | + | CommonZoomWindow | 通用窗口缩放(所有Autodesk产品通用) | + | CommonOrbit | 通用轨道(所有Autodesk产品通用) | + | CommonFreeOrbit | 通用自由轨道(所有Autodesk产品通用) | + | CommonConstrainedOrbit | 通用约束轨道(所有Autodesk产品通用) | + | CommonLookAt | 通用看向(所有Autodesk产品通用) | + | CommonLookAround | 通用环视(所有Autodesk产品通用) | + | CommonWalk | 通用漫游(所有Autodesk产品通用) | + | CommonCenter | 通用居中(所有Autodesk产品通用) | + | NavigateFixed | 固定相机位置 | + | NavigateFreeLookAround | 经典Navisworks自由环视(旋转) | + | NavigateFreeOrbit | 经典Navisworks自由轨道(检查) | + | NavigateWalk | 经典Navisworks漫游 | + | NavigateFly | 经典Navisworks飞行 | + | NavigateConstrainedOrbit | 经典Navisworks约束轨道(转盘) | + | NavigateZoom | 经典Navisworks缩放 | + | NavigatePan | 经典Navisworks平移 | + | NavigateConstrainedPan | 经典Navisworks约束平移 | + | NavigateLookAround | 经典Navisworks环视(旋转) | + | NavigateOrbit | 经典Navisworks轨道 | + | NavigateZoomWindow | 经典Navisworks窗口缩放(缩放框) | + | CustomToolPlugin | 由ToolPlugin提供的自定义功能 | -### Cursor枚举成员: + 这个枚举列出了Navisworks中所有可用的工具类型,包括导航工具、测量工具、标注工具等。其中CustomToolPlugin是用于自定 + 义工具插件的特殊值。 + +### Cursor枚举成员 | 成员名称 | 描述 | |-----------------|----------------------------| @@ -1753,4 +1758,4 @@ public class PathClickToolPlugin : ToolPlugin return Cursor.MarkupSnapping; } } -``` \ No newline at end of file +``` diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs index 4e76112..b9061f7 100644 --- a/src/Core/Collision/ClashDetectiveIntegration.cs +++ b/src/Core/Collision/ClashDetectiveIntegration.cs @@ -675,8 +675,8 @@ namespace NavisworksTransport try { var copyTest = addedTempTest.CreateCopy() as ClashTest; - copyTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; - copyTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points; + copyTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines ; + copyTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines ; _documentClash.TestsData.TestsEditTestFromCopy(addedTempTest, copyTest); LogManager.Debug($"[分组测试-{resultCount:00}] 几何类型设置完成"); } diff --git a/src/PathPlanning/ChannelBasedGridBuilder.cs b/src/PathPlanning/ChannelBasedGridBuilder.cs index ce165ee..6dec996 100644 --- a/src/PathPlanning/ChannelBasedGridBuilder.cs +++ b/src/PathPlanning/ChannelBasedGridBuilder.cs @@ -3,13 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Autodesk.Navisworks.Api; -using NavisworksTransport.Core; using NavisworksTransport.Utils; -// COM API引用 -using ComBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge; -using COMApi = Autodesk.Navisworks.Api.Interop.ComApi; - namespace NavisworksTransport.PathPlanning { /// @@ -61,7 +56,7 @@ namespace NavisworksTransport.PathPlanning LogManager.Info($"[通道网格构建器] 找到 {channelItems.Count} 个通道物品"); // 2. 计算通道总边界 - var totalBounds = CalculateTotalBounds(channelItems); + var totalBounds = BoundingBoxGeometryUtils.CalculateTotalBounds(channelItems); LogManager.Info($"[通道网格构建器] 通道总边界: {FormatBounds(totalBounds)}"); // 3. 创建网格地图 @@ -107,7 +102,7 @@ namespace NavisworksTransport.PathPlanning _categoryManager = new CategoryAttributeManager(); } - // 优先使用CategoryAttributeManager获取标记为"通道"的物品 + // 使用CategoryAttributeManager获取标记为"通道"的物品 var document = Application.ActiveDocument; if (document?.Models != null) { @@ -125,20 +120,9 @@ namespace NavisworksTransport.PathPlanning } } - // 如果没有用户标记的通道,回退到自动检测 - LogManager.Warning("[通道网格构建器] 未找到用户标记的通道物品,使用自动检测"); - var autoDetectedChannels = new List(); - - foreach (ModelItem item in allItems) - { - if (IsChannelItem(item)) - { - autoDetectedChannels.Add(item); - } - } - - LogManager.Info($"[通道网格构建器] 自动检测到 {autoDetectedChannels.Count} 个通道物品"); - return autoDetectedChannels; + // 如果没有用户标记的通道,直接返回空列表 + LogManager.Warning("[通道网格构建器] 未找到用户标记的通道物品,返回空列表"); + return new List(); } catch (Exception ex) { @@ -147,32 +131,6 @@ namespace NavisworksTransport.PathPlanning } } - /// - /// 判断物品是否为通道 - /// - /// 模型物品 - /// 是否为通道 - private bool IsChannelItem(ModelItem item) - { - try - { - // 优先使用物流属性判断 - if (CategoryAttributeManager.HasLogisticsAttributes(item)) - { - var elementType = CategoryAttributeManager.GetLogisticsElementType(item); - return elementType == CategoryAttributeManager.LogisticsElementType.通道; - } - - // 备用:通过AutoPathObstacleDetector判断是否可通行 - return AutoPathObstacleDetector.IsPassable(item); - } - catch (Exception ex) - { - LogManager.Warning($"[通道网格构建器] 判断通道时发生错误 '{item.DisplayName}': {ex.Message}"); - return false; - } - } - /// /// 将通道投影到网格 /// 核心创新:智能投影策略 - 薄通道优先使用顶面提取,复杂通道使用射线扫描 @@ -189,7 +147,7 @@ namespace NavisworksTransport.PathPlanning // 智能投影策略选择 if (thickness < 1.5) // 薄通道优先使用顶面提取 { - var triangles = ExtractTrianglesFromModelItem(channel); + var triangles = GeometryExtractor.ExtractTrianglesOptimized(channel); var topFaceCount = CountTopFaces(triangles); var topFaceRatio = triangles.Count > 0 ? (double)topFaceCount / triangles.Count : 0; @@ -217,7 +175,7 @@ namespace NavisworksTransport.PathPlanning { if (triangles == null) { - triangles = ExtractTrianglesFromModelItem(item); + triangles = GeometryExtractor.ExtractTrianglesOptimized(item); } int projectedTriangles = 0; @@ -245,17 +203,13 @@ namespace NavisworksTransport.PathPlanning /// 网格地图 private void ProjectUsingRayCasting(ModelItem item, GridMap gridMap) { - var triangles = ExtractTrianglesFromModelItem(item); + var triangles = GeometryExtractor.ExtractTrianglesOptimized(item); if (triangles.Count == 0) { LogManager.Warning($"[通道网格构建器] 未能从 '{item.DisplayName}' 提取到三角形"); return; } - // 使用空间哈希加速(稍后实现TriangleSpatialHash时启用) - // var spatialHash = new TriangleSpatialHash(); - // spatialHash.Build(triangles, gridMap.CellSize); - var bbox = item.BoundingBox(); var minGrid = gridMap.WorldToGrid(new Point3D(bbox.Min.X, bbox.Min.Y, bbox.Min.Z)); var maxGrid = gridMap.WorldToGrid(new Point3D(bbox.Max.X, bbox.Max.Y, bbox.Max.Z)); @@ -284,7 +238,7 @@ namespace NavisworksTransport.PathPlanning // 检查射线是否击中任何三角形 foreach (var triangle in triangles) { - if (RayTriangleIntersect(rayOrigin, rayDirection, triangle, out double intersectionZ)) + if (GeometryExtractor.RayTriangleIntersect(rayOrigin, rayDirection, triangle, out double intersectionZ)) { if (intersectionZ >= bbox.Min.Z && intersectionZ <= bbox.Max.Z) { @@ -299,243 +253,10 @@ namespace NavisworksTransport.PathPlanning LogManager.Info($"[通道网格构建器] 射线投影:{hitCount} 个网格单元"); } - /// - /// 提取模型物品的三角形(复用ChannelHeightDetector的现有功能) - /// - /// 模型物品 - /// 三角形列表 - private List ExtractTrianglesFromModelItem(ModelItem modelItem) - { - try - { - LogManager.Info($"[通道网格构建器] 提取三角形: {modelItem.DisplayName}"); - - // 直接调用已有的三角形提取功能,复用ChannelHeightDetector的代码逻辑 - var allTriangles = new List(); - - // 创建几何提取回调 - var callback = new GeometryExtractorCallback(); - - // 递归处理模型项及其子项 - ExtractTrianglesRecursive(modelItem, callback, allTriangles); - - LogManager.Info($"[通道网格构建器] 从ModelItem {modelItem.DisplayName} 总共提取到 {allTriangles.Count} 个三角形"); - return allTriangles; - } - catch (Exception ex) - { - LogManager.Error($"[通道网格构建器] 提取三角形失败: {ex.Message}"); - return new List(); - } - } - /// - /// 递归提取ModelItem及其子项的几何数据 - /// - private void ExtractTrianglesRecursive(ModelItem modelItem, GeometryExtractorCallback callback, List allTriangles) - { - try - { - // 如果当前项有几何数据,提取它 - if (modelItem.HasGeometry) - { - ExtractTrianglesFromGeometry(modelItem, callback, allTriangles); - } - // 递归处理子项 - foreach (ModelItem child in modelItem.Children) - { - ExtractTrianglesRecursive(child, callback, allTriangles); - } - } - catch (Exception ex) - { - LogManager.Warning($"[通道网格构建器] 处理ModelItem {modelItem.DisplayName} 时出错: {ex.Message}"); - } - } - /// - /// 从单个有几何数据的ModelItem中提取三角形 - /// - private void ExtractTrianglesFromGeometry(ModelItem modelItem, GeometryExtractorCallback callback, List allTriangles) - { - try - { - // 基于ChannelHeightDetector的方法:直接从COM状态获取节点并遍历fragments - var comState = ComBridge.State; - var comObject = ComBridge.ToInwOaPath(modelItem); - - if (comObject == null) - { - return; - } - // 检查路径是否有效并获取节点 - var nodesColl = comObject.Nodes(); - if (nodesColl.Count > 0) - { - var comNode = nodesColl[nodesColl.Count] as COMApi.InwOaNode; // 获取最后一个节点并转换类型 - if (comNode != null && comNode.IsGeometry) - { - var fragCount = comNode.Fragments().Count; - - for (long fragIndex = 1; fragIndex <= fragCount; fragIndex++) - { - var fragsColl = comNode.Fragments(); - var fragment = fragsColl[fragIndex] as COMApi.InwOaFragment3; - - if (fragment != null) - { - // 获取变换矩阵 - var transformMatrix = GetTransformMatrix(fragment); - callback.SetTransformMatrix(transformMatrix); - - // 清空回调中的三角形 - callback.Clear(); - - // 生成几何图元 - 使用正确的枚举值 - fragment.GenerateSimplePrimitives(COMApi.nwEVertexProperty.eNORMAL, callback); - - // 将提取的三角形添加到总列表 - allTriangles.AddRange(callback.Triangles); - } - } - } - } - } - catch (Exception ex) - { - LogManager.Warning($"[通道网格构建器] 从 {modelItem.DisplayName} 提取几何时出错: {ex.Message}"); - } - } - - /// - /// 获取Fragment的变换矩阵 - /// - private Matrix4 GetTransformMatrix(COMApi.InwOaFragment3 fragment) - { - try - { - var transform = fragment.GetLocalToWorldMatrix(); - if (transform != null) - { - object matrixArrayObj = transform.Matrix; - if (matrixArrayObj is Array matrixArray && matrixArray.Length >= 16) - { - var matrix = new double[16]; - matrixArray.CopyTo(matrix, 0); - - // Navisworks使用列主序矩阵,需要转置 - return new Matrix4( - matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15] - ); - } - } - } - catch (Exception ex) - { - LogManager.Warning($"[通道网格构建器] 获取变换矩阵失败: {ex.Message}"); - } - - return Matrix4.Identity; // 使用静态属性创建单位矩阵 - } - - /// - /// 射线-三角形相交测试(复用ChannelHeightDetector的现有功能) - /// - /// 射线起点 - /// 射线方向 - /// 三角形 - /// 交点Z坐标 - /// 是否相交 - private bool RayTriangleIntersect(Point3D rayOrigin, Point3D rayDirection, Triangle3D triangle, out double intersectionZ) - { - intersectionZ = 0.0; - const double EPSILON = 0.0000001; - - try - { - // 使用Möller-Trumbore算法进行射线-三角形相交测试 - // 计算三角形的两条边 - var edge1 = SubtractPoints(triangle.V2, triangle.V1); - var edge2 = SubtractPoints(triangle.V3, triangle.V1); - - // 计算射线方向与edge2的叉积 - var h = CrossProduct(rayDirection, edge2); - var a = DotProduct(edge1, h); - - // 如果a接近0,射线与三角形平行 - if (a > -EPSILON && a < EPSILON) - { - return false; - } - - var f = 1.0 / a; - var s = SubtractPoints(rayOrigin, triangle.V1); - var u = f * DotProduct(s, h); - - if (u < 0.0 || u > 1.0) - { - return false; - } - - var q = CrossProduct(s, edge1); - var v = f * DotProduct(rayDirection, q); - - if (v < 0.0 || u + v > 1.0) - { - return false; - } - - // 计算t值(射线参数) - var t = f * DotProduct(edge2, q); - - if (t > EPSILON) // 射线相交 - { - // 计算交点 - intersectionZ = rayOrigin.Z + t * rayDirection.Z; - return true; - } - - return false; // 线段相交但射线不相交 - } - catch (Exception ex) - { - LogManager.Warning($"[通道网格构建器] 射线-三角形交点计算出错: {ex.Message}"); - return false; - } - } - - /// - /// 计算两点之间的向量 - /// - private Point3D SubtractPoints(Point3D a, Point3D b) - { - return new Point3D(a.X - b.X, a.Y - b.Y, a.Z - b.Z); - } - - /// - /// 计算两个向量的叉积 - /// - private Point3D CrossProduct(Point3D a, Point3D b) - { - return new Point3D( - a.Y * b.Z - a.Z * b.Y, - a.Z * b.X - a.X * b.Z, - a.X * b.Y - a.Y * b.X - ); - } - - /// - /// 计算两个向量的点积 - /// - private double DotProduct(Point3D a, Point3D b) - { - return a.X * b.X + a.Y * b.Y + a.Z * b.Z; - } /// /// 计算三角形法向量 @@ -545,14 +266,14 @@ namespace NavisworksTransport.PathPlanning private Point3D ComputeTriangleNormal(Triangle3D triangle) { var edge1 = new Point3D( - triangle.V2.X - triangle.V1.X, - triangle.V2.Y - triangle.V1.Y, - triangle.V2.Z - triangle.V1.Z); + triangle.Point2.X - triangle.Point1.X, + triangle.Point2.Y - triangle.Point1.Y, + triangle.Point2.Z - triangle.Point1.Z); var edge2 = new Point3D( - triangle.V3.X - triangle.V1.X, - triangle.V3.Y - triangle.V1.Y, - triangle.V3.Z - triangle.V1.Z); + triangle.Point3.X - triangle.Point1.X, + triangle.Point3.Y - triangle.Point1.Y, + triangle.Point3.Z - triangle.Point1.Z); // 计算叉积 var normal = new Point3D( @@ -597,10 +318,10 @@ namespace NavisworksTransport.PathPlanning private void RasterizeTriangleToGrid(GridMap gridMap, Triangle3D triangle) { // 获取三角形的2D投影边界框 - var minX = Math.Min(triangle.V1.X, Math.Min(triangle.V2.X, triangle.V3.X)); - var maxX = Math.Max(triangle.V1.X, Math.Max(triangle.V2.X, triangle.V3.X)); - var minY = Math.Min(triangle.V1.Y, Math.Min(triangle.V2.Y, triangle.V3.Y)); - var maxY = Math.Max(triangle.V1.Y, Math.Max(triangle.V2.Y, triangle.V3.Y)); + var minX = Math.Min(triangle.Point1.X, Math.Min(triangle.Point2.X, triangle.Point3.X)); + var maxX = Math.Max(triangle.Point1.X, Math.Max(triangle.Point2.X, triangle.Point3.X)); + var minY = Math.Min(triangle.Point1.Y, Math.Min(triangle.Point2.Y, triangle.Point3.Y)); + var maxY = Math.Max(triangle.Point1.Y, Math.Max(triangle.Point2.Y, triangle.Point3.Y)); var minGrid = gridMap.WorldToGrid(new Point3D(minX, minY, 0)); var maxGrid = gridMap.WorldToGrid(new Point3D(maxX, maxY, 0)); @@ -616,7 +337,7 @@ namespace NavisworksTransport.PathPlanning var worldPos = gridMap.GridToWorld(gridPos); // 检查点是否在三角形内部 - if (IsPointInTriangle2D(worldPos, triangle)) + if (GeometryExtractor.IsPointInTriangle2D(worldPos, triangle)) { SetCellAsChannel(gridMap, gridPos, null); } @@ -625,34 +346,6 @@ namespace NavisworksTransport.PathPlanning } } - /// - /// 检查2D点是否在三角形内部 - /// - /// 测试点 - /// 三角形 - /// 是否在内部 - private bool IsPointInTriangle2D(Point3D point, Triangle3D triangle) - { - // 使用重心坐标法判断点是否在三角形内 - var v0x = triangle.V3.X - triangle.V1.X; - var v0y = triangle.V3.Y - triangle.V1.Y; - var v1x = triangle.V2.X - triangle.V1.X; - var v1y = triangle.V2.Y - triangle.V1.Y; - var v2x = point.X - triangle.V1.X; - var v2y = point.Y - triangle.V1.Y; - - var dot00 = v0x * v0x + v0y * v0y; - var dot01 = v0x * v1x + v0y * v1y; - var dot02 = v0x * v2x + v0y * v2y; - var dot11 = v1x * v1x + v1y * v1y; - var dot12 = v1x * v2x + v1y * v2y; - - var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); - var u = (dot11 * dot02 - dot01 * dot12) * invDenom; - var v = (dot00 * dot12 - dot01 * dot02) * invDenom; - - return (u >= 0) && (v >= 0) && (u + v <= 1); - } /// /// 设置网格单元为通道类型 @@ -666,41 +359,6 @@ namespace NavisworksTransport.PathPlanning gridMap.SetCellAsChannel(gridPos, ChannelType.Corridor, relatedItem); } - /// - /// 计算通道总边界 - /// - /// 通道物品列表 - /// 总边界框 - private BoundingBox3D CalculateTotalBounds(List channelItems) - { - if (!channelItems.Any()) - { - return new BoundingBox3D(new Point3D(0, 0, 0), new Point3D(0, 0, 0)); - } - - var firstBounds = channelItems.First().BoundingBox(); - double minX = firstBounds.Min.X; - double minY = firstBounds.Min.Y; - double minZ = firstBounds.Min.Z; - double maxX = firstBounds.Max.X; - double maxY = firstBounds.Max.Y; - double maxZ = firstBounds.Max.Z; - - foreach (var item in channelItems.Skip(1)) - { - var bounds = item.BoundingBox(); - minX = Math.Min(minX, bounds.Min.X); - minY = Math.Min(minY, bounds.Min.Y); - minZ = Math.Min(minZ, bounds.Min.Z); - maxX = Math.Max(maxX, bounds.Max.X); - maxY = Math.Max(maxY, bounds.Max.Y); - maxZ = Math.Max(maxZ, bounds.Max.Z); - } - - return new BoundingBox3D( - new Point3D(minX, minY, minZ), - new Point3D(maxX, maxY, maxZ)); - } /// /// 创建空的通道覆盖 diff --git a/src/PathPlanning/ChannelHeightDetector.cs b/src/PathPlanning/ChannelHeightDetector.cs index c7ebc88..bcc86df 100644 --- a/src/PathPlanning/ChannelHeightDetector.cs +++ b/src/PathPlanning/ChannelHeightDetector.cs @@ -252,37 +252,6 @@ namespace NavisworksTransport.PathPlanning } } - /// - /// 确定通道类型 - /// - /// 通道模型项 - /// 通道类型 - private ChannelType DetermineChannelType(ModelItem channel) - { - var displayName = channel.DisplayName?.ToLower() ?? ""; - var className = channel.ClassDisplayName?.ToLower() ?? ""; - - if (displayName.Contains("楼梯") || displayName.Contains("stair") || - className.Contains("楼梯") || className.Contains("stair")) - { - return ChannelType.Stairs; - } - - if (displayName.Contains("坡道") || displayName.Contains("ramp") || - className.Contains("坡道") || className.Contains("ramp")) - { - return ChannelType.Ramp; - } - - if (displayName.Contains("电梯") || displayName.Contains("elevator") || - className.Contains("电梯") || className.Contains("elevator")) - { - return ChannelType.Elevator; - } - - return ChannelType.Corridor; // 默认为走廊 - } - /// /// 采样通道高度剖面 /// @@ -503,7 +472,7 @@ namespace NavisworksTransport.PathPlanning LogManager.Debug($"[几何分析] PickItemFromPoint结果: 模型={pickResult.ModelItem?.DisplayName ?? "NULL"}, 3D点=({pickResult.Point.X:F2}, {pickResult.Point.Y:F2}, {pickResult.Point.Z:F2})"); // 验证拾取到的模型是否为目标通道(或其子项) - if (IsModelItemRelatedToChannel(pickResult.ModelItem, channel)) + if (pickResult.ModelItem.Equals(channel) || ModelItemAnalysisHelper.IsChildOf(pickResult.ModelItem, channel)) { height = pickResult.Point.Z; LogManager.Info($"[几何分析] ✅ 使用投影法获得精确表面高度: {height:F2},通道: {channel.DisplayName}"); @@ -579,50 +548,6 @@ namespace NavisworksTransport.PathPlanning return new Point3D(relativeX, relativeY, relativeZ); } - /// - /// 检查模型项是否与目标通道相关(同一模型或父子关系) - /// - private bool IsModelItemRelatedToChannel(ModelItem pickedItem, ModelItem targetChannel) - { - if (pickedItem == null || targetChannel == null) - return false; - - // 直接匹配 - if (pickedItem.InstanceGuid == targetChannel.InstanceGuid) - return true; - - // 检查是否为同一父对象下的子项(简化的层次关系检查) - try - { - // 比较显示名称(对于复合模型的情况) - var pickedName = pickedItem.DisplayName?.ToLower() ?? ""; - var targetName = targetChannel.DisplayName?.ToLower() ?? ""; - - if (!string.IsNullOrEmpty(pickedName) && !string.IsNullOrEmpty(targetName)) - { - // 如果名称相似或包含关系,认为是相关的 - if (pickedName.Contains(targetName) || targetName.Contains(pickedName)) - { - return true; - } - } - - // 检查几何边界是否重叠(作为相关性的另一个指标) - var pickedBounds = pickedItem.BoundingBox(); - var targetBounds = targetChannel.BoundingBox(); - - if (pickedBounds != null && targetBounds != null) - { - return BoundingBoxesOverlap(pickedBounds, targetBounds); - } - } - catch (Exception ex) - { - LogManager.Debug($"[模型关系检查] 检查模型关系时发生错误: {ex.Message}"); - } - - return false; - } /// /// 检查两个边界框是否重叠 @@ -634,17 +559,6 @@ namespace NavisworksTransport.PathPlanning bbox1.Max.Z < bbox2.Min.Z || bbox2.Max.Z < bbox1.Min.Z); } - /// - /// 根据相对位置估算高度(作为备用方法) - /// - private double EstimateHeightFromRelativePosition(Point3D relativePos, BoundingBox3D bbox) - { - // 保守估算:对于大多数通道,表面高度接近顶面 - // 优先使用精确方法,这里作为最后的备用方案 - var estimatedRatio = 0.95; // 假设表面在95%高度位置(更保守的估算) - return bbox.Min.Z + (bbox.Max.Z - bbox.Min.Z) * estimatedRatio; - } - /// /// 生成缓存键 /// @@ -714,7 +628,7 @@ namespace NavisworksTransport.PathPlanning { // 使用PickItemFromPoint获取该屏幕位置的实际表面点 var pickResult = activeView.PickItemFromPoint((int)screenPoint.X, (int)screenPoint.Y); - if (pickResult != null && IsModelItemRelatedToChannel(pickResult.ModelItem, channel)) + if (pickResult != null && (pickResult.ModelItem.Equals(channel) || ModelItemAnalysisHelper.IsChildOf(pickResult.ModelItem, channel))) { LogManager.Info($"[射线投射] ✅ 成功检测到表面点: Z={pickResult.Point.Z:F2}"); return pickResult.Point.Z; @@ -748,7 +662,7 @@ namespace NavisworksTransport.PathPlanning LogManager.Info($"[COM几何射线] 开始从ModelItem提取三角形几何数据: {channel.DisplayName}"); // 使用COM API提取三角形几何数据 - var triangles = ExtractTrianglesFromModelItem(channel); + var triangles = GeometryExtractor.ExtractTrianglesOptimized(channel); if (triangles.Count == 0) { LogManager.Warning($"[COM几何射线] 未能从ModelItem提取到三角形数据: {channel.DisplayName}"); @@ -780,153 +694,6 @@ namespace NavisworksTransport.PathPlanning } } - /// - /// 使用COM API从ModelItem中提取三角形几何数据 - /// - /// 模型项 - /// 三角形列表 - private List ExtractTrianglesFromModelItem(ModelItem modelItem) - { - var allTriangles = new List(); - - try - { - // 创建几何提取回调 - var callback = new GeometryExtractorCallback(); - - // 递归处理模型项及其子项 - ExtractTrianglesRecursive(modelItem, callback, allTriangles); - - //LogManager.Info($"[COM几何提取] 从ModelItem {modelItem.DisplayName} 总共提取到 {allTriangles.Count} 个三角形"); - return allTriangles; - } - catch (Exception ex) - { - LogManager.Error($"[COM几何提取] 提取三角形几何数据失败: {ex.Message}"); - return allTriangles; - } - } - - /// - /// 递归提取ModelItem及其子项的几何数据 - /// - private void ExtractTrianglesRecursive(ModelItem modelItem, GeometryExtractorCallback callback, List allTriangles) - { - try - { - // 如果当前项有几何数据,提取它 - if (modelItem.HasGeometry) - { - ExtractTrianglesFromGeometry(modelItem, callback, allTriangles); - } - - // 递归处理子项 - foreach (ModelItem child in modelItem.Children) - { - ExtractTrianglesRecursive(child, callback, allTriangles); - } - } - catch (Exception ex) - { - LogManager.Warning($"[COM几何提取] 处理ModelItem {modelItem.DisplayName} 时出错: {ex.Message}"); - } - } - - /// - /// 从单个有几何数据的ModelItem中提取三角形 - /// - private void ExtractTrianglesFromGeometry(ModelItem modelItem, GeometryExtractorCallback callback, List allTriangles) - { - try - { - // 基于示例代码的方法:直接从COM状态获取节点并遍历fragments - var comState = ComBridge.State; - var comObject = ComBridge.ToInwOaPath(modelItem); - - if (comObject == null) - { - //LogManager.Debug($"[COM几何提取] 无法转换ModelItem为COM对象: {modelItem.DisplayName}"); - return; - } - - // 检查路径是否有效并获取节点 - var nodesColl = comObject.Nodes(); - if (nodesColl.Count > 0) - { - var comNode = nodesColl[nodesColl.Count] as COMApi.InwOaNode; // 获取最后一个节点并转换类型 - if (comNode != null && comNode.IsGeometry) - { - var fragCount = comNode.Fragments().Count; - //LogManager.Debug($"[COM几何提取] 找到 {fragCount} 个fragments在 {modelItem.DisplayName}"); - - for (long fragIndex = 1; fragIndex <= fragCount; fragIndex++) - { - var fragsColl = comNode.Fragments(); - var fragment = fragsColl[fragIndex] as COMApi.InwOaFragment3; - - if (fragment != null) - { - // 获取变换矩阵 - var transformMatrix = GetTransformMatrix(fragment); - callback.SetTransformMatrix(transformMatrix); - - // 清空回调中的三角形 - callback.Clear(); - - // 生成几何图元 - 使用正确的枚举值 - fragment.GenerateSimplePrimitives(COMApi.nwEVertexProperty.eNORMAL, callback); - - // 将提取的三角形添加到总列表 - allTriangles.AddRange(callback.Triangles); - - //LogManager.Debug($"[COM几何提取] 从fragment {fragIndex} 提取到 {callback.Triangles.Count} 个三角形"); - } - } - } - } - } - catch (Exception ex) - { - LogManager.Warning($"[COM几何提取] 从 {modelItem.DisplayName} 提取几何时出错: {ex.Message}"); - } - } - - /// - /// 获取Fragment的变换矩阵 - /// - private Matrix4 GetTransformMatrix(COMApi.InwOaFragment3 fragment) - { - try - { - var transform = fragment.GetLocalToWorldMatrix(); - if (transform != null) - { - object matrixArrayObj = transform.Matrix; - if (matrixArrayObj is Array matrixArray && matrixArray.Length >= 16) - { - var matrix = new double[16]; - matrixArray.CopyTo(matrix, 0); - - //LogManager.Debug($"[COM几何提取] 获取到变换矩阵: [{matrix[0]:F2},{matrix[1]:F2},{matrix[2]:F2},{matrix[3]:F2}] [{matrix[4]:F2},{matrix[5]:F2},{matrix[6]:F2},{matrix[7]:F2}] [{matrix[8]:F2},{matrix[9]:F2},{matrix[10]:F2},{matrix[11]:F2}] [{matrix[12]:F2},{matrix[13]:F2},{matrix[14]:F2},{matrix[15]:F2}]"); - - // Navisworks使用列主序矩阵,需要转置 - return new Matrix4( - matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15] - ); - } - } - } - catch (Exception ex) - { - LogManager.Warning($"[COM几何提取] 获取变换矩阵失败: {ex.Message}"); - } - - return Matrix4.Identity; // 使用静态属性创建单位矩阵 - } - /// /// 执行射线与三角形的交点检测 /// @@ -940,35 +707,20 @@ namespace NavisworksTransport.PathPlanning try { // 创建垂直向下的射线(从通道上方合理高度开始) - var maxZ = triangles.Count > 0 ? triangles.Max(t => Math.Max(Math.Max(t.V1.Z, t.V2.Z), t.V3.Z)) : 0; + var maxZ = triangles.Count > 0 ? triangles.Max(t => Math.Max(Math.Max(t.Point1.Z, t.Point2.Z), t.Point3.Z)) : 0; var rayOrigin = new Point3D(position.X, position.Y, maxZ + 1000.0); // 从最高点上方1km开始 var rayDirection = new Point3D(0, 0, -1); // 向下 - - //LogManager.Debug($"[射线-三角形] 射线起点: ({rayOrigin.X:F2}, {rayOrigin.Y:F2}, {rayOrigin.Z:F2}), 方向: (0, 0, -1), 三角形最高点: {maxZ:F2}"); - + int intersectionCount = 0; foreach (var triangle in triangles) { - if (RayTriangleIntersect(rayOrigin, rayDirection, triangle, out double intersectionZ)) + if (GeometryExtractor.RayTriangleIntersect(rayOrigin, rayDirection, triangle, out double intersectionZ)) { intersectionPoints.Add(intersectionZ); intersectionCount++; - //LogManager.Debug($"[射线-三角形] 第{intersectionCount}个交点: Z={intersectionZ:F2}, 三角形V1({triangle.V1.X:F2},{triangle.V1.Y:F2},{triangle.V1.Z:F2})"); } } - if (intersectionCount == 0) - { - LogManager.Warning($"[射线-三角形] 详细分析: 射线({rayOrigin.X:F2},{rayOrigin.Y:F2},{rayOrigin.Z:F2})→(0,0,-1) 与{triangles.Count}个三角形均无交点"); - // 输出前3个三角形的详细信息用于调试 - for (int i = 0; i < Math.Min(3, triangles.Count); i++) - { - var tri = triangles[i]; - //LogManager.Debug($"[射线-三角形] 三角形{i}: V1({tri.V1.X:F2},{tri.V1.Y:F2},{tri.V1.Z:F2}) V2({tri.V2.X:F2},{tri.V2.Y:F2},{tri.V2.Z:F2}) V3({tri.V3.X:F2},{tri.V3.Y:F2},{tri.V3.Z:F2})"); - } - } - - //LogManager.Debug($"[射线-三角形] 位置({position.X:F2}, {position.Y:F2}) 检测到 {intersectionPoints.Count} 个交点"); return intersectionPoints; } catch (Exception ex) @@ -978,213 +730,6 @@ namespace NavisworksTransport.PathPlanning } } - /// - /// 射线与三角形的交点检测算法(Möller-Trumbore算法) - /// - /// 射线起点 - /// 射线方向 - /// 三角形 - /// 交点的Z坐标 - /// 是否相交 - private bool RayTriangleIntersect(Point3D rayOrigin, Point3D rayDirection, Triangle3D triangle, out double intersectionZ) - { - intersectionZ = 0.0; - const double EPSILON = 0.0000001; - - try - { - // 计算三角形的两条边 - var edge1 = SubtractPoints(triangle.V2, triangle.V1); - var edge2 = SubtractPoints(triangle.V3, triangle.V1); - - // 计算射线方向与edge2的叉积 - var h = CrossProduct(rayDirection, edge2); - var a = DotProduct(edge1, h); - - // 如果a接近0,射线与三角形平行 - if (a > -EPSILON && a < EPSILON) - { - //LogManager.Debug($"[Möller-Trumbore] 射线与三角形平行,a={a:F8}"); - return false; - } - - var f = 1.0 / a; - var s = SubtractPoints(rayOrigin, triangle.V1); - var u = f * DotProduct(s, h); - - if (u < 0.0 || u > 1.0) - { - //LogManager.Debug($"[Möller-Trumbore] u值超出范围,u={u:F6}"); - return false; - } - - var q = CrossProduct(s, edge1); - var v = f * DotProduct(rayDirection, q); - - if (v < 0.0 || u + v > 1.0) - { - //LogManager.Debug($"[Möller-Trumbore] v值超出范围,v={v:F6}, u+v={u + v:F6}"); - return false; - } - - // 计算t值(射线参数) - var t = f * DotProduct(edge2, q); - - if (t > EPSILON) // 射线相交 - { - // 计算交点 - intersectionZ = rayOrigin.Z + t * rayDirection.Z; - //LogManager.Debug($"[Möller-Trumbore] ✅ 找到交点: t={t:F6}, intersectionZ={intersectionZ:F2}, 三角形V1.Z={triangle.V1.Z:F2}"); - return true; - } - else - { - LogManager.Debug($"[Möller-Trumbore] t值无效,t={t:F6}"); - } - - return false; // 线段相交但射线不相交 - } - catch (Exception ex) - { - LogManager.Warning($"[射线-三角形] 交点计算出错: {ex.Message}"); - return false; - } - } - - /// - /// 计算两点之间的向量 - /// - private Point3D SubtractPoints(Point3D a, Point3D b) - { - return new Point3D(a.X - b.X, a.Y - b.Y, a.Z - b.Z); - } - - /// - /// 计算两个向量的叉积 - /// - private Point3D CrossProduct(Point3D a, Point3D b) - { - return new Point3D( - a.Y * b.Z - a.Z * b.Y, - a.Z * b.X - a.X * b.Z, - a.X * b.Y - a.Y * b.X - ); - } - - /// - /// 计算两个向量的点积 - /// - private double DotProduct(Point3D a, Point3D b) - { - return a.X * b.X + a.Y * b.Y + a.Z * b.Z; - } - - private List FindRayModelSurfaceIntersections(Point3D position, ModelItem channel) - { - var intersections = new List(); - var bbox = channel.BoundingBox(); - - try - { - var view = Autodesk.Navisworks.Api.Application.ActiveDocument.ActiveView; - if (view == null) - { - LogManager.Warning("无法获取当前视图,射线投射失败"); - return intersections; - } - - // 在通道包围盒范围内的多个Z层采样点 - int zSamples = 10; - double zStep = (bbox.Max.Z - bbox.Min.Z) / zSamples; - - for (int i = 0; i <= zSamples; i++) - { - double testZ = bbox.Min.Z + i * zStep; - var testPoint = new Point3D(position.X, position.Y, testZ); - - // 将3D点投影到屏幕坐标 - var projectionResult = view.ProjectPoint(testPoint, false, false); - if (projectionResult != null) - { - // 使用屏幕坐标进行pick操作,获取实际表面点 - var pickResult = view.PickItemFromPoint((int)projectionResult.X, (int)projectionResult.Y); - - if (pickResult != null && pickResult.ModelItem != null) - { - // 检查是否命中目标通道或其子项 - if (IsChannelOrChildItem(pickResult.ModelItem, channel)) - { - // 获取实际表面点的高度 - var surfacePoint = pickResult.Point; - LogManager.Debug($"射线投射命中通道表面: Z={surfacePoint.Z:F2}, 原测试点Z={testZ:F2}"); - intersections.Add(surfacePoint.Z); - } - } - } - } - - // 去重并排序 - intersections = intersections.Distinct().OrderBy(z => z).ToList(); - LogManager.Debug($"模型表面射线投射找到 {intersections.Count} 个交点"); - - return intersections; - } - catch (Exception ex) - { - LogManager.Error($"模型表面射线投射出错: {ex.Message}"); - return intersections; - } - } - - private bool IsChannelOrChildItem(ModelItem pickedItem, ModelItem targetChannel) - { - if (pickedItem == null || targetChannel == null) - return false; - - // 检查是否是目标通道本身 - if (pickedItem.InstanceGuid == targetChannel.InstanceGuid) - return true; - - // 检查是否是目标通道的子项 - var parent = pickedItem.Parent; - while (parent != null) - { - if (parent.InstanceGuid == targetChannel.InstanceGuid) - return true; - parent = parent.Parent; - } - - return false; - } - - /// - /// 从边界框估算表面高度 - /// - private double? EstimateSurfaceHeightFromBounds(Point3D position, BoundingBox3D bbox) - { - try - { - // 检查位置是否在通道的XY范围内 - if (position.X >= bbox.Min.X && position.X <= bbox.Max.X && - position.Y >= bbox.Min.Y && position.Y <= bbox.Max.Y) - { - // 对于大多数通道类型,表面高度接近顶部 - // 使用90%的高度作为保守估算 - var estimatedHeight = bbox.Min.Z + (bbox.Max.Z - bbox.Min.Z) * 0.9; - LogManager.Debug($"[边界框估算] 位置在通道范围内,估算表面高度: {estimatedHeight:F2}"); - return estimatedHeight; - } - - LogManager.Debug($"[边界框估算] 位置不在通道XY范围内"); - return null; - } - catch (Exception ex) - { - LogManager.Warning($"[边界框估算] 估算失败: {ex.Message}"); - return null; - } - } - /// /// 尝试多点采样获取表面高度 /// @@ -1223,7 +768,7 @@ namespace NavisworksTransport.PathPlanning screenPoint.Y >= 0 && screenPoint.Y < activeView.Height) { var pickResult = activeView.PickItemFromPoint((int)screenPoint.X, (int)screenPoint.Y); - if (pickResult != null && IsModelItemRelatedToChannel(pickResult.ModelItem, channel)) + if (pickResult != null && (pickResult.ModelItem.Equals(channel) || ModelItemAnalysisHelper.IsChildOf(pickResult.ModelItem, channel))) { validHeights.Add(pickResult.Point.Z); LogManager.Debug($"[多点采样] 采样点({samplePoint.X:F1}, {samplePoint.Y:F1})高度: {pickResult.Point.Z:F2}"); @@ -1330,166 +875,4 @@ namespace NavisworksTransport.PathPlanning Other } - /// - /// 三角形数据结构 - /// - public class Triangle3D - { - public Point3D V1 { get; set; } - public Point3D V2 { get; set; } - public Point3D V3 { get; set; } - - public Triangle3D(Point3D v1, Point3D v2, Point3D v3) - { - V1 = v1; - V2 = v2; - V3 = v3; - } - } - - /// - /// COM API几何提取回调类 - /// 用于从ModelItem中提取真实的三角形几何数据 - /// - public class GeometryExtractorCallback : COMApi.InwSimplePrimitivesCB - { - private readonly List _triangles; - private Matrix4 _transformMatrix; - - public List Triangles => _triangles; - - public GeometryExtractorCallback() - { - _triangles = new List(); - _transformMatrix = Matrix4.Identity; // 使用静态属性创建单位矩阵 - } - - public void SetTransformMatrix(Matrix4 matrix) - { - _transformMatrix = matrix; - } - - public void Line(COMApi.InwSimpleVertex v1, COMApi.InwSimpleVertex v2) - { - // 我们只关心三角形,忽略线段 - } - - public void Point(COMApi.InwSimpleVertex v1) - { - // 我们只关心三角形,忽略点 - } - - public void SnapPoint(COMApi.InwSimpleVertex v1) - { - // 我们只关心三角形,忽略捕捉点 - } - - public void Triangle(COMApi.InwSimpleVertex v1, COMApi.InwSimpleVertex v2, COMApi.InwSimpleVertex v3) - { - try - { - // 提取顶点坐标 - var vertex1 = ConvertVertex(v1); - var vertex2 = ConvertVertex(v2); - var vertex3 = ConvertVertex(v3); - - // 应用变换矩阵到顶点坐标 - var originalVertex1 = vertex1; - vertex1 = _transformMatrix.Transform(vertex1); - vertex2 = _transformMatrix.Transform(vertex2); - vertex3 = _transformMatrix.Transform(vertex3); - - //LogManager.Debug($"[COM几何提取] 局部坐标: V1({originalVertex1.X:F2},{originalVertex1.Y:F2},{originalVertex1.Z:F2})"); - //LogManager.Debug($"[COM几何提取] 变换后世界坐标: V1({vertex1.X:F2},{vertex1.Y:F2},{vertex1.Z:F2}) V2({vertex2.X:F2},{vertex2.Y:F2},{vertex2.Z:F2}) V3({vertex3.X:F2},{vertex3.Y:F2},{vertex3.Z:F2})"); - - // 创建三角形并添加到列表 - var triangle = new Triangle3D(vertex1, vertex2, vertex3); - _triangles.Add(triangle); - } - catch (Exception ex) - { - LogManager.Warning($"[COM几何提取] 处理三角形时出错: {ex.Message}"); - } - } - - private Point3D ConvertVertex(COMApi.InwSimpleVertex vertex) - { - // 从COM API顶点获取坐标 - object coordObj = vertex.coord; - if (coordObj is Array coordinates && coordinates.Length >= 3) - { - var coords = new double[3]; - coordinates.CopyTo(coords, 0); - return new Point3D(coords[0], coords[1], coords[2]); - } - - return new Point3D(0, 0, 0); - } - - public void Clear() - { - _triangles.Clear(); - } - - } - - /// - /// 4x4变换矩阵结构 - /// - public struct Matrix4 - { - public double m11, m12, m13, m14; - public double m21, m22, m23, m24; - public double m31, m32, m33, m34; - public double m41, m42, m43, m44; - - public Matrix4(double _m11, double _m12, double _m13, double _m14, - double _m21, double _m22, double _m23, double _m24, - double _m31, double _m32, double _m33, double _m34, - double _m41, double _m42, double _m43, double _m44) - { - m11 = _m11; m12 = _m12; m13 = _m13; m14 = _m14; - m21 = _m21; m22 = _m22; m23 = _m23; m24 = _m24; - m31 = _m31; m32 = _m32; m33 = _m33; m34 = _m34; - m41 = _m41; m42 = _m42; m43 = _m43; m44 = _m44; - } - - /// - /// 创建单位矩阵 - /// - public static Matrix4 Identity - { - get - { - return new Matrix4( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ); - } - } - - /// - /// 应用变换到点坐标 - /// - public Point3D Transform(Point3D point) - { - // 4x4矩阵变换,假设w=1 - var x = m11 * point.X + m12 * point.Y + m13 * point.Z + m14; - var y = m21 * point.X + m22 * point.Y + m23 * point.Z + m24; - var z = m31 * point.X + m32 * point.Y + m33 * point.Z + m34; - var w = m41 * point.X + m42 * point.Y + m43 * point.Z + m44; - - // 透视除法(如果w不是1) - if (Math.Abs(w - 1.0) > 0.0001) - { - x /= w; - y /= w; - z /= w; - } - - return new Point3D(x, y, z); - } - } } \ No newline at end of file diff --git a/src/PathPlanning/VerticalScanProcessor.cs b/src/PathPlanning/VerticalScanProcessor.cs index 15d4a3e..b88c49d 100644 --- a/src/PathPlanning/VerticalScanProcessor.cs +++ b/src/PathPlanning/VerticalScanProcessor.cs @@ -843,43 +843,11 @@ namespace NavisworksTransport.PathPlanning { try { - // 使用包围盒进行快速垂直相交检测 - var bbox = item.BoundingBox(); - if (bbox == null) - return null; - - // 检查XY平面投影是否包含扫描点 - bool intersectsXY = (basePoint.X >= bbox.Min.X && basePoint.X <= bbox.Max.X && - basePoint.Y >= bbox.Min.Y && basePoint.Y <= bbox.Max.Y); - - if (intersectsXY) - { - // 计算Z轴相交区间 - double scanMinZ = basePoint.Z; - double scanMaxZ = basePoint.Z + scanHeight; - - double intersectionMinZ = Math.Max(bbox.Min.Z, scanMinZ); - double intersectionMaxZ = Math.Min(bbox.Max.Z, scanMaxZ); - - if (intersectionMaxZ > intersectionMinZ) - { - return new IntersectionData - { - MinZ = intersectionMinZ, - MaxZ = intersectionMaxZ, - IntersectionPoint = new Point3D(basePoint.X, basePoint.Y, (intersectionMinZ + intersectionMaxZ) / 2) - }; - } - } - - return null; - - /* 原有射线法实现 - 保留作为参考 if (!item.HasGeometry) return null; - // 直接提取模型项的三角形几何数据,不做包围盒预筛选 - var triangles = ExtractTrianglesFromModelItem(item); + // 直接提取模型项的三角形几何数据 + var triangles = GeometryExtractor.ExtractTrianglesOptimized(item); if (triangles == null || triangles.Count == 0) { return null; @@ -914,174 +882,14 @@ namespace NavisworksTransport.PathPlanning } return null; - */ } catch (Exception ex) { - LogManager.Debug($"【垂直扫描处理器】 包围盒相交测试异常: {item.DisplayName}, {ex.Message}"); + LogManager.Debug($"【垂直扫描处理器】 射线相交测试异常: {item.DisplayName}, {ex.Message}"); return null; } } - /// - /// 提取模型项的三角形几何数据(复用自ChannelHeightDetector) - /// - /// 模型项 - /// 三角形列表 - private List ExtractTrianglesFromModelItem(ModelItem modelItem) - { - LogManager.Info($"[几何提取] 开始处理: {modelItem.DisplayName}"); - var stopwatch = System.Diagnostics.Stopwatch.StartNew(); - var allTriangles = new List(); - - try - { - // 创建几何提取回调 - var callback = new GeometryExtractorCallback(); - - // 递归处理模型项及其子项 - ExtractTrianglesRecursive(modelItem, callback, allTriangles); - - stopwatch.Stop(); - LogManager.Info($"[几何提取] 完成处理: {modelItem.DisplayName}, 耗时: {stopwatch.ElapsedMilliseconds}ms, 三角形数量: {allTriangles.Count}"); - - return allTriangles; - } - catch (Exception ex) - { - stopwatch.Stop(); - LogManager.Error($"[几何提取] 处理失败: {modelItem.DisplayName}, 耗时: {stopwatch.ElapsedMilliseconds}ms, 错误: {ex.Message}"); - return allTriangles; - } - } - - /// - /// 递归提取三角形(复用自ChannelHeightDetector) - /// - private void ExtractTrianglesRecursive(ModelItem modelItem, GeometryExtractorCallback callback, List allTriangles) - { - try - { - // 如果当前项有几何数据,尝试提取 - if (modelItem.HasGeometry) - { - ExtractTrianglesFromGeometry(modelItem, callback, allTriangles); - } - - // 递归处理子项 - if (modelItem.Children != null) - { - foreach (var child in modelItem.Children) - { - ExtractTrianglesRecursive(child, callback, allTriangles); - } - } - } - catch (Exception ex) - { - LogManager.Debug($"【垂直扫描处理器】 递归提取三角形异常: {ex.Message}"); - } - } - - /// - /// 从单个有几何数据的ModelItem中提取三角形(复用自ChannelHeightDetector) - /// - private void ExtractTrianglesFromGeometry(ModelItem modelItem, GeometryExtractorCallback callback, List allTriangles) - { - try - { - // 基于示例代码的方法:直接从COM状态获取节点并遍历fragments - var comState = ComBridge.State; - var comObject = ComBridge.ToInwOaPath(modelItem); - - if (comObject == null) - { - return; - } - - // 检查路径是否有效并获取节点 - var nodesColl = comObject.Nodes(); - if (nodesColl.Count > 0) - { - var comNode = nodesColl[nodesColl.Count] as COMApi.InwOaNode; // 获取最后一个节点并转换类型 - if (comNode != null && comNode.IsGeometry) - { - var fragCount = comNode.Fragments().Count; - LogManager.Info($"[COM API] 开始片段处理: {modelItem.DisplayName}, 片段数量: {fragCount}"); - - for (long fragIndex = 1; fragIndex <= fragCount; fragIndex++) - { - var fragStopwatch = System.Diagnostics.Stopwatch.StartNew(); - - var fragsColl = comNode.Fragments(); - var fragment = fragsColl[fragIndex] as COMApi.InwOaFragment3; - - if (fragment != null) - { - // 获取变换矩阵 - var transformMatrix = GetTransformMatrix(fragment); - callback.SetTransformMatrix(transformMatrix); - - // 清空回调中的三角形 - callback.ClearTriangles(); - - // 记录GenerateSimplePrimitives调用前的三角形数量 - int trianglesBeforeGeneration = allTriangles.Count; - - // 生成几何图元 - 使用正确的枚举值 - fragment.GenerateSimplePrimitives(COMApi.nwEVertexProperty.eNORMAL, callback); - - fragStopwatch.Stop(); - int newTriangles = callback.Triangles.Count; - LogManager.Info($"[COM API] 片段 {fragIndex}/{fragCount} 处理完成, 耗时: {fragStopwatch.ElapsedMilliseconds}ms, 新增三角形: {newTriangles}"); - - // 将提取的三角形添加到总列表 - allTriangles.AddRange(callback.Triangles); - } - } - } - } - } - catch (Exception ex) - { - LogManager.Debug($"【垂直扫描处理器】 从 {modelItem.DisplayName} 提取几何时出错: {ex.Message}"); - } - } - - /// - /// 获取Fragment的变换矩阵(复用自ChannelHeightDetector) - /// - private Matrix4 GetTransformMatrix(COMApi.InwOaFragment3 fragment) - { - try - { - var transform = fragment.GetLocalToWorldMatrix(); - if (transform != null) - { - object matrixArrayObj = transform.Matrix; - if (matrixArrayObj is Array matrixArray && matrixArray.Length >= 16) - { - var matrix = new double[16]; - matrixArray.CopyTo(matrix, 0); - - // Navisworks使用列主序矩阵,需要转置 - return new Matrix4( - matrix[0], matrix[4], matrix[8], matrix[12], - matrix[1], matrix[5], matrix[9], matrix[13], - matrix[2], matrix[6], matrix[10], matrix[14], - matrix[3], matrix[7], matrix[11], matrix[15] - ); - } - } - } - catch (Exception ex) - { - LogManager.Debug($"【垂直扫描处理器】 获取变换矩阵失败: {ex.Message}"); - } - - return Matrix4.Identity; - } - /// /// 执行垂直射线与三角形相交检测 /// @@ -1101,7 +909,7 @@ namespace NavisworksTransport.PathPlanning foreach (var triangle in triangles) { - if (RayTriangleIntersect(rayOrigin, rayDirection, triangle, out double intersectionZ)) + if (GeometryExtractor.RayTriangleIntersect(rayOrigin, rayDirection, triangle, out double intersectionZ)) { // 检查相交点是否在扫描范围内 if (intersectionZ >= basePoint.Z && intersectionZ <= basePoint.Z + scanHeight) @@ -1120,98 +928,6 @@ namespace NavisworksTransport.PathPlanning } } - /// - /// 射线-三角形相交算法(Möller-Trumbore算法,复用自ChannelHeightDetector) - /// - /// 射线起点 - /// 射线方向 - /// 三角形 - /// 输出相交点Z坐标 - /// 是否相交 - private bool RayTriangleIntersect(Point3D rayOrigin, Point3D rayDirection, Triangle3D triangle, out double intersectionZ) - { - intersectionZ = 0.0; - const double EPSILON = 0.0000001; - - try - { - // 计算三角形的两条边 - var edge1 = SubtractPoints(triangle.V2, triangle.V1); - var edge2 = SubtractPoints(triangle.V3, triangle.V1); - - // 计算射线方向与edge2的叉积 - var h = CrossProduct(rayDirection, edge2); - var a = DotProduct(edge1, h); - - // 如果a接近0,射线与三角形平行 - if (a > -EPSILON && a < EPSILON) - { - return false; - } - - var f = 1.0 / a; - var s = SubtractPoints(rayOrigin, triangle.V1); - var u = f * DotProduct(s, h); - - if (u < 0.0 || u > 1.0) - { - return false; - } - - var q = CrossProduct(s, edge1); - var v = f * DotProduct(rayDirection, q); - - if (v < 0.0 || u + v > 1.0) - { - return false; - } - - // 计算t值(射线参数) - var t = f * DotProduct(edge2, q); - - if (t > EPSILON) // 射线相交 - { - // 计算交点 - intersectionZ = rayOrigin.Z + t * rayDirection.Z; - return true; - } - - return false; // 线段相交但射线不相交 - } - catch (Exception ex) - { - LogManager.Debug($"【垂直扫描处理器】 射线相交计算出错: {ex.Message}"); - return false; - } - } - - /// - /// 向量减法 - /// - private Point3D SubtractPoints(Point3D p1, Point3D p2) - { - return new Point3D(p1.X - p2.X, p1.Y - p2.Y, p1.Z - p2.Z); - } - - /// - /// 向量叉积 - /// - private Point3D CrossProduct(Point3D v1, Point3D v2) - { - return new Point3D( - v1.Y * v2.Z - v1.Z * v2.Y, - v1.Z * v2.X - v1.X * v2.Z, - v1.X * v2.Y - v1.Y * v2.X - ); - } - - /// - /// 向量点积 - /// - private double DotProduct(Point3D v1, Point3D v2) - { - return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z; - } /// /// 检查模型项是否类似地面/楼板 @@ -1334,108 +1050,6 @@ namespace NavisworksTransport.PathPlanning public bool IsPassable { get; set; } } - /// - /// 三角形数据结构(复用自ChannelHeightDetector) - /// - public class Triangle3D - { - public Point3D V1 { get; set; } - public Point3D V2 { get; set; } - public Point3D V3 { get; set; } - - public Triangle3D(Point3D v1, Point3D v2, Point3D v3) - { - V1 = v1; - V2 = v2; - V3 = v3; - } - } - - /// - /// 几何提取回调类(复用自ChannelHeightDetector) - /// - public class GeometryExtractorCallback : COMApi.InwSimplePrimitivesCB - { - private readonly List _triangles; - private Matrix4 _transformMatrix; - - public List Triangles => _triangles; - - public GeometryExtractorCallback() - { - _triangles = new List(); - _transformMatrix = Matrix4.Identity; - } - - public void SetTransformMatrix(Matrix4 matrix) - { - _transformMatrix = matrix; - } - - public void Line(COMApi.InwSimpleVertex v1, COMApi.InwSimpleVertex v2) - { - // 我们只关心三角形,忽略线段 - } - - public void Point(COMApi.InwSimpleVertex v1) - { - // 我们只关心三角形,忽略点 - } - - public void SnapPoint(COMApi.InwSimpleVertex v1) - { - // 我们只关心三角形,忽略捕捉点 - } - - public void Triangle(COMApi.InwSimpleVertex v1, COMApi.InwSimpleVertex v2, COMApi.InwSimpleVertex v3) - { - try - { - // 提取顶点坐标 - var vertex1 = ConvertVertex(v1); - var vertex2 = ConvertVertex(v2); - var vertex3 = ConvertVertex(v3); - - // 应用变换矩阵到顶点坐标 - vertex1 = _transformMatrix.Transform(vertex1); - vertex2 = _transformMatrix.Transform(vertex2); - vertex3 = _transformMatrix.Transform(vertex3); - - // 创建三角形并添加到列表 - var triangle = new Triangle3D(vertex1, vertex2, vertex3); - _triangles.Add(triangle); - } - catch (Exception ex) - { - LogManager.Debug($"【垂直扫描处理器】 处理三角形时出错: {ex.Message}"); - } - } - - private Point3D ConvertVertex(COMApi.InwSimpleVertex vertex) - { - // 从COM API顶点获取坐标 - object coordObj = vertex.coord; - if (coordObj is Array coordinates && coordinates.Length >= 3) - { - var coords = new double[3]; - coordinates.CopyTo(coords, 0); - return new Point3D(coords[0], coords[1], coords[2]); - } - - return new Point3D(0, 0, 0); - } - - public void ClearTriangles() - { - _triangles.Clear(); - } - - public List GetTriangles() - { - return new List(_triangles); - } - } - #endregion } } \ No newline at end of file diff --git a/src/Utils/BoundingBoxGeometryUtils.cs b/src/Utils/BoundingBoxGeometryUtils.cs index 7dc1677..99c70f9 100644 --- a/src/Utils/BoundingBoxGeometryUtils.cs +++ b/src/Utils/BoundingBoxGeometryUtils.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Autodesk.Navisworks.Api; namespace NavisworksTransport.Utils @@ -97,19 +99,6 @@ namespace NavisworksTransport.Utils box1.Min.Z <= box2.Max.Z && box1.Max.Z >= box2.Min.Z; } - /// - /// 计算两个3D点之间的距离 - /// - /// 第一个点 - /// 第二个点 - /// 两点之间的欧几里得距离 - 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); - } /// /// 获取包围盒的中心点 @@ -173,5 +162,42 @@ namespace NavisworksTransport.Utils point.Y >= boundingBox.Min.Y - tolerance && point.Y <= boundingBox.Max.Y + tolerance && point.Z >= boundingBox.Min.Z - tolerance && point.Z <= boundingBox.Max.Z + tolerance; } + + /// + /// 计算模型项列表的总边界 + /// + /// 模型项列表 + /// 总边界框 + public static BoundingBox3D CalculateTotalBounds(IEnumerable modelItems) + { + var itemList = modelItems?.ToList(); + if (itemList == null || !itemList.Any()) + { + return new BoundingBox3D(new Point3D(0, 0, 0), new Point3D(0, 0, 0)); + } + + var firstBounds = itemList.First().BoundingBox(); + double minX = firstBounds.Min.X; + double minY = firstBounds.Min.Y; + double minZ = firstBounds.Min.Z; + double maxX = firstBounds.Max.X; + double maxY = firstBounds.Max.Y; + double maxZ = firstBounds.Max.Z; + + foreach (var item in itemList.Skip(1)) + { + var bounds = item.BoundingBox(); + minX = Math.Min(minX, bounds.Min.X); + minY = Math.Min(minY, bounds.Min.Y); + minZ = Math.Min(minZ, bounds.Min.Z); + maxX = Math.Max(maxX, bounds.Max.X); + maxY = Math.Max(maxY, bounds.Max.Y); + maxZ = Math.Max(maxZ, bounds.Max.Z); + } + + return new BoundingBox3D( + new Point3D(minX, minY, minZ), + new Point3D(maxX, maxY, maxZ)); + } } } \ No newline at end of file diff --git a/src/Utils/GeometryExtractor.cs b/src/Utils/GeometryExtractor.cs index 6880167..bbb6272 100644 --- a/src/Utils/GeometryExtractor.cs +++ b/src/Utils/GeometryExtractor.cs @@ -127,7 +127,7 @@ namespace NavisworksTransport /// /// 模型项 /// 三角形集合 - private static List ExtractTrianglesOptimized(ModelItem modelItem) + public static List ExtractTrianglesOptimized(ModelItem modelItem) { var triangles = new List(); @@ -179,7 +179,7 @@ namespace NavisworksTransport /// /// COM 选择 /// 唯一片段信息集合 - private static List GetUniqueFragments(ComApi.InwOpSelection selection) + public static List GetUniqueFragments(ComApi.InwOpSelection selection) { var fragmentMap = new Dictionary(); @@ -803,6 +803,142 @@ namespace NavisworksTransport return 0; } } + + /// + /// 射线与三角形相交检测(Möller-Trumbore算法) + /// + /// 射线起点 + /// 射线方向 + /// 三角形 + /// 相交点的Z坐标 + /// 是否相交 + public static bool RayTriangleIntersect(Point3D rayOrigin, Point3D rayDirection, Triangle3D triangle, out double intersectionZ) + { + intersectionZ = 0.0; + const double EPSILON = 0.0000001; + + try + { + // 计算三角形的两条边 + var edge1 = SubtractPoints(triangle.Point2, triangle.Point1); + var edge2 = SubtractPoints(triangle.Point3, triangle.Point1); + + // 计算射线方向与edge2的叉积 + var h = CrossProduct(rayDirection, edge2); + var a = DotProduct(edge1, h); + + // 如果a接近0,射线与三角形平行 + if (a > -EPSILON && a < EPSILON) + { + return false; + } + + var f = 1.0 / a; + var s = SubtractPoints(rayOrigin, triangle.Point1); + var u = f * DotProduct(s, h); + + if (u < 0.0 || u > 1.0) + { + return false; + } + + var q = CrossProduct(s, edge1); + var v = f * DotProduct(rayDirection, q); + + if (v < 0.0 || u + v > 1.0) + { + return false; + } + + // 计算t值(射线参数) + var t = f * DotProduct(edge2, q); + + if (t > EPSILON) // 射线相交 + { + // 计算交点 + intersectionZ = rayOrigin.Z + t * rayDirection.Z; + return true; + } + + return false; // 线段相交但射线不相交 + } + catch (Exception ex) + { + LogManager.WriteLog($"射线-三角形相交计算出错: {ex.Message}"); + return false; + } + } + + /// + /// 点减法运算 + /// + public static Point3D SubtractPoints(Point3D a, Point3D b) + { + return new Point3D(a.X - b.X, a.Y - b.Y, a.Z - b.Z); + } + + /// + /// 向量叉积运算 + /// + public static Point3D CrossProduct(Point3D a, Point3D b) + { + return new Point3D( + a.Y * b.Z - a.Z * b.Y, + a.Z * b.X - a.X * b.Z, + a.X * b.Y - a.Y * b.X + ); + } + + /// + /// 向量点积运算 + /// + public static double DotProduct(Point3D a, Point3D b) + { + return a.X * b.X + a.Y * b.Y + a.Z * b.Z; + } + + /// + /// 检查2D点是否在三角形内部(使用重心坐标法) + /// + /// 测试点 + /// 三角形 + /// 是否在内部 + public static bool IsPointInTriangle2D(Point3D point, Triangle3D triangle) + { + // 使用重心坐标法判断点是否在三角形内 + var v0x = triangle.Point3.X - triangle.Point1.X; + var v0y = triangle.Point3.Y - triangle.Point1.Y; + var v1x = triangle.Point2.X - triangle.Point1.X; + var v1y = triangle.Point2.Y - triangle.Point1.Y; + var v2x = point.X - triangle.Point1.X; + var v2y = point.Y - triangle.Point1.Y; + + var dot00 = v0x * v0x + v0y * v0y; + var dot01 = v0x * v1x + v0y * v1y; + var dot02 = v0x * v2x + v0y * v2y; + var dot11 = v1x * v1x + v1y * v1y; + var dot12 = v1x * v2x + v1y * v2y; + + var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + var u = (dot11 * dot02 - dot01 * dot12) * invDenom; + var v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + return (u >= 0) && (v >= 0) && (u + v <= 1); + } + + /// + /// 计算两个3D点之间的距离 + /// + /// 第一个点 + /// 第二个点 + /// 两点之间的欧几里得距离 + 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); + } } /// diff --git a/src/Utils/ModelItemAnalysisHelper.cs b/src/Utils/ModelItemAnalysisHelper.cs index bc16c43..c96cc9f 100644 --- a/src/Utils/ModelItemAnalysisHelper.cs +++ b/src/Utils/ModelItemAnalysisHelper.cs @@ -15,19 +15,27 @@ namespace NavisworksTransport.Utils #region 辅助方法 /// - /// 计算两个3D点之间的距离 + /// 检查一个ModelItem是否是另一个ModelItem的子节点 /// - /// 点1 - /// 点2 - /// 两点间的距离 - public static double CalculatePointDistance(Point3D point1, Point3D point2) + /// 子节点 + /// 父节点 + /// 如果child是parent的子节点返回true + public static bool IsChildOf(ModelItem child, ModelItem parent) { - 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); + if (child == null || parent == null) + return false; + + var currentParent = child.Parent; + while (currentParent != null) + { + if (currentParent.Equals(parent)) + return true; + currentParent = currentParent.Parent; + } + return false; } + /// /// 检查ModelItem是否仍然有效 ///