diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6c3b3ff..078cd28 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,7 +13,8 @@ "Bash(./compile.bat)", "Bash(./tool/compile.bat:*)", "Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" NavisworksTransportPlugin.csproj /p:Configuration=Debug /p:Platform=AnyCPU /verbosity:minimal)", - "Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" NavisworksTransportPlugin.csproj /p:Configuration=Debug /p:Platform=AnyCPU)" + "Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" NavisworksTransportPlugin.csproj /p:Configuration=Debug /p:Platform=AnyCPU)", + "WebFetch(domain:forums.autodesk.com)" ], "deny": [] } diff --git a/CLAUDE.md b/CLAUDE.md index 9a91db5..47767bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,25 +11,22 @@ NavisworksTransport is a Navisworks 2026 plugin for logistics path planning and ## Build Commands ### 标准编译方式 (Windows) + - **推荐编译命令**: `./compile.bat` - 在项目根目录下运行的标准方式 - **重要说明**: 在Windows系统下必须使用 `./` 前缀来运行批处理文件,不要使用 `cmd /c compile.bat` 或其他复杂方式 -### 详细编译选项 -- **完整路径**: `tool\compile.bat` - 自动检测MSBuild (VS 2022 Community/Professional) 或回退到 `dotnet build` -- **直接编译**: `& "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" NavisworksTransportPlugin.csproj /p:Configuration=Debug /p:Platform=AnyCPU /verbosity:minimal` -- **目标平台**: .NET Framework 4.8, AnyCPU platform -- **输出文件**: `bin\Debug\NavisworksTransportPlugin.dll` -- **依赖管理**: RoyT.AStar 3.0.2 (通过 packages.config 管理) - ## Architecture Overview ### Dual Plugin Architecture + The system implements multiple Navisworks plugin types working together: + - **MainPlugin.cs**: Primary AddInPlugin with ribbon UI and DockPanePlugin integration - **PathClickToolPlugin.cs**: ToolPlugin for 3D mouse interaction and point placement - **PathPointRenderPlugin.cs**: RenderPlugin for 3D visualization overlay ### Core Management Layer + - **PathPlanningManager.cs**: Central coordinator for route planning with A* pathfinding support - **LogisticsAnimationManager.cs**: Enhanced animation system targeting Navisworks 2026 native components - **TimeLinerIntegrationManager.cs**: Bridge between custom animations and Navisworks TimeLiner @@ -38,6 +35,7 @@ The system implements multiple Navisworks plugin types working together: - **ModelSplitterManager.cs**: Model export and layer separation ### Data and Coordinate Systems + - **PathPlanningModels.cs**: Core data structures with event-driven state management - **PathDataManager.cs**: JSON serialization with migration support - **CoordinateConverter.cs**: 2D map overlay to 3D world coordinate transformation chains @@ -45,6 +43,7 @@ The system implements multiple Navisworks plugin types working together: - **FloorDetector.cs**: Automatic floor/level detection for multi-story logistics ### UI Architecture: WPF + WinForms Hybrid + - **WPF Components**: Modern MVVM-based controls in `src\UI\WPF\` - LogisticsControlPanel: Main docked interface - ViewModels with INotifyPropertyChanged pattern @@ -55,40 +54,48 @@ The system implements multiple Navisworks plugin types working together: ## Key Technical Details ### Navisworks API Integration Pattern + - **Dual API Strategy**: Native API (`Autodesk.Navisworks.Api`) for core functionality + COM API (`Autodesk.Navisworks.ComApi`) for attribute persistence and TimeLiner operations - **Plugin Architecture**: Three distinct plugin types registered in single assembly - **Event-Driven Design**: Global exception handling with `GlobalExceptionHandler` class - **Navisworks 2026 Focus**: Utilizes 2026-specific API features without backward compatibility constraints ### Pathfinding and Animation System + - **A* Algorithm**: RoyT.AStar library integration for optimal path calculation - **Animation Pipeline**: Transform-based movement with collision detection integration - **TimeLiner Bridge**: Synchronization between custom path animations and Navisworks timeline - **Real-time Collision**: ClashDetectiveIntegration for dynamic conflict detection during animation ### State Management and Persistence + - **Session State**: PathEditState enum (None, AddingPoints, EditingPath) with event callbacks - **Data Serialization**: JSON-based persistence with LogisticsAttributeChangedEventArgs for tracking - **Coordinate Mapping**: Multi-layer coordinate system supporting 2D overlay on 3D models ### Logistics Classification System + Eight predefined categories with inheritance from parent to child nodes: + - 门 (Doors), 电梯 (Elevators), 楼梯 (Stairs), 通道 (Channels) - 障碍物 (Obstacles), 装卸区 (Loading Zones), 停车区 (Parking), 检查点 (Checkpoints) ## Development Guidelines ### Language and Communication + - **使用中文进行所有交流和代码注释** - Primary language for user interaction and code documentation - 代码注释和文档说明使用中文 - All technical documentation in Chinese ### Package Management (Legacy Format) + - **Old-style csproj**: Uses `` with HintPath instead of PackageReference - **packages.config**: Manual NuGet package management (do NOT use `dotnet add package`) - **Manual package installation**: Download .nupkg files and extract to packages/ directory - **Path format**: `packages\{PackageId}.{Version}\lib\{TargetFramework}\{Assembly}.dll` ### Plugin Registration Patterns + ```csharp // Multi-plugin registration in single assembly [Plugin("NavisworksTransport.MainPlugin", "YourDeveloperID")] @@ -101,12 +108,14 @@ public class PathClickToolPlugin : ToolPlugin { } ``` ### Critical API Usage Patterns + - **Always reference Navisworks API documentation**: Check `doc\navisworks_api\` before implementing any Navisworks functionality - **COM API for persistence**: Use COM API for attribute operations that need to persist across sessions - **GlobalExceptionHandler**: Initialize in MainPlugin constructor for application-wide error handling - **Thread safety**: UI operations must be marshaled to main thread when called from background processes ### Navisworks 2026 Development Focus + - **Exclusive 2026 targeting**: No backward compatibility required - leverage 2026-specific features freely - **Legacy artifacts**: `src\Legacy\` contains reference code from 2017 version but is not actively maintained - **Modern animation system**: Use Navisworks 2026 native animation components instead of manual Transform manipulation @@ -121,12 +130,14 @@ public class PathClickToolPlugin : ToolPlugin { } **解决策略**: 1. **使用文档结构入口点**: + ```bash # 优先访问类成员列表 AllMembers_T_Autodesk_Navisworks_Api_ClassName.htm ``` 2. **精确文件名搜索**: + ```bash find . -name "*ClassName*" -o -name "*MethodName*" ``` @@ -137,6 +148,7 @@ public class PathClickToolPlugin : ToolPlugin { } - 利用文档间超链接导航 4. **搜索模式示例**: + ```bash # 搜索特定API grep -r "SaveFile\|Export.*nwd" --include="*.htm" doc/navisworks_api/ @@ -151,6 +163,7 @@ public class PathClickToolPlugin : ToolPlugin { } - 插件基类:`AllMembers_T_Autodesk_Navisworks_Api_Plugins_*.htm` **避免的搜索方式**: + - 避免在HTML内容中进行模糊搜索(标签干扰) - 不要依赖GUID格式的文件名 - 避免使用过于宽泛的搜索词 diff --git a/src/PathPlanning/ChannelHeightDetector.cs b/src/PathPlanning/ChannelHeightDetector.cs index 0f36662..f3b0e12 100644 --- a/src/PathPlanning/ChannelHeightDetector.cs +++ b/src/PathPlanning/ChannelHeightDetector.cs @@ -4,6 +4,10 @@ using System.Linq; using Autodesk.Navisworks.Api; using NavisworksTransport.Utils; +// COM API引用 +using ComBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge; +using COMApi = Autodesk.Navisworks.Api.Interop.ComApi; + namespace NavisworksTransport.PathPlanning { /// @@ -35,15 +39,17 @@ namespace NavisworksTransport.PathPlanning { try { - LogManager.Debug($"[高度检测] 开始检测位置 ({position.X:F2}, {position.Y:F2}, {position.Z:F2}) 的地面高度"); + LogManager.Info($"[高度检测] 🔍 开始检测位置 ({position.X:F2}, {position.Y:F2}, {position.Z:F2}) 的地面高度,通道数={channelItems?.Count() ?? 0}"); // 寻找包含该位置的通道 var containingChannel = FindContainingChannel(position, channelItems); if (containingChannel == null) { - LogManager.Warning($"[高度检测] 位置 ({position.X:F2}, {position.Y:F2}) 未找到包含的通道,使用原始Z坐标"); + LogManager.Info($"[高度检测] ❌ 位置 ({position.X:F2}, {position.Y:F2}) 未找到包含的通道,使用原始Z坐标: {position.Z:F2}"); return position.Z; } + + LogManager.Info($"[高度检测] ✅ 找到包含通道: {containingChannel.DisplayName}"); // 检查缓存 var cacheKey = GenerateCacheKey(containingChannel, position); @@ -109,14 +115,14 @@ namespace NavisworksTransport.PathPlanning { FloorHeight = bounds.Min.Z, CeilingHeight = bounds.Max.Z, - Type = DetermineChannelType(channel), + Type = ChannelType.Other, // 不再依赖类型判断 HeightProfile = new List() }; // 采样通道高度剖面 SampleChannelHeightProfile(channel, heightInfo); - LogManager.Info($"[高度检测] 通道分析完成: 地面高度={heightInfo.FloorHeight:F2}, 顶面高度={heightInfo.CeilingHeight:F2}, 类型={heightInfo.Type}"); + LogManager.Info($"[高度检测] 通道分析完成: 地面高度={heightInfo.FloorHeight:F2}, 顶面高度={heightInfo.CeilingHeight:F2}"); return heightInfo; } catch (Exception ex) @@ -249,20 +255,26 @@ namespace NavisworksTransport.PathPlanning /// 精确高度 private double CalculatePreciseHeightAtPosition(Point3D position, ChannelHeightInfo heightInfo, ModelItem channel) { - // 对于平面通道,返回顶面高度(车辆在通道表面运行) - if (heightInfo.Type == ChannelType.Corridor) + LogManager.Info($"[高度计算] 开始精确表面高度检测: 位置({position.X:F2}, {position.Y:F2})"); + + // 方法1: 尝试使用几何射线投射进行精确表面检测 + if (TryGeometricRaycast(position, channel, out double raycastHeight)) { - return heightInfo.CeilingHeight; + LogManager.Info($"[高度计算] ✅ 几何射线投射成功: {raycastHeight:F2}"); + return raycastHeight; } - - // 对于有高度变化的通道(楼梯、坡道),使用插值计算 - if (heightInfo.HeightProfile.Count > 1) + + // 方法2: 如果几何射线投射失败,尝试使用View API作为备选 + if (TryGetGeometricSurfaceHeight(position, channel, out double viewApiHeight)) { - return InterpolateHeightFromProfile(position, heightInfo.HeightProfile); + LogManager.Info($"[高度计算] ✅ View API备选检测成功: {viewApiHeight:F2}"); + return viewApiHeight; } - - // 默认返回顶面高度(车辆在通道表面运行) - return heightInfo.CeilingHeight; + + // 方法3: 最后使用几何分析 + var surfaceHeight = AnalyzeSurfaceHeightAtPosition(position, channel, heightInfo.FloorHeight, heightInfo.CeilingHeight); + LogManager.Info($"[高度计算] 📐 几何分析结果: {surfaceHeight:F2}"); + return surfaceHeight; } /// @@ -323,7 +335,7 @@ namespace NavisworksTransport.PathPlanning position.Y >= bbox.Min.Y && position.Y <= bbox.Max.Y) { // 位置在通道范围内,尝试分析表面高度 - var surfaceHeight = AnalyzeSurfaceHeightAtPosition(position, channel, bbox); + var surfaceHeight = AnalyzeSurfaceHeightAtPosition(position, channel, bbox.Min.Z, bbox.Max.Z); LogManager.Debug($"[射线投射] 表面高度分析结果: {surfaceHeight:F2}"); return surfaceHeight; } @@ -349,29 +361,43 @@ namespace NavisworksTransport.PathPlanning /// /// 位置 /// 通道模型 - /// 边界框 + /// 地面高度 + /// 顶面高度 /// 表面高度 - private double AnalyzeSurfaceHeightAtPosition(Point3D position, ModelItem channel, BoundingBox3D bbox) + private double AnalyzeSurfaceHeightAtPosition(Point3D position, ModelItem channel, double floorHeight, double ceilingHeight) { try { - // 方法1: 检查是否有几何细节可以分析 - if (TryGetGeometricSurfaceHeight(position, channel, out double geometricHeight)) + LogManager.Info($"[表面分析] 开始分析位置({position.X:F2}, {position.Y:F2})的实际表面高度"); + LogManager.Info($"[表面分析] 通道边界: 地面={floorHeight:F2}, 顶面={ceilingHeight:F2}"); + + // 尝试多种方法获取真实表面高度 + + // 方法1: 使用射线投射从上方检测表面 + var raycastHeight = TryRaycastFromAbove(position, channel, ceilingHeight); + if (raycastHeight.HasValue) { - LogManager.Debug($"[表面分析] 几何分析得到高度: {geometricHeight:F2}"); - return geometricHeight; + LogManager.Info($"[表面分析] ✅ 射线投射检测到表面高度: {raycastHeight.Value:F2}"); + return raycastHeight.Value; } - - // 方法2: 基于位置在通道中的相对位置进行智能估算 - var relativePosition = CalculateRelativePositionInChannel(position, bbox); - var estimatedHeight = EstimateHeightFromRelativePosition(relativePosition, bbox); - LogManager.Debug($"[表面分析] 相对位置估算高度: {estimatedHeight:F2}"); - return estimatedHeight; + + // 方法2: 使用多点采样获取局部表面高度 + var sampledHeight = TryMultiPointSampling(position, channel); + if (sampledHeight.HasValue) + { + LogManager.Info($"[表面分析] ✅ 多点采样检测到表面高度: {sampledHeight.Value:F2}"); + return sampledHeight.Value; + } + + // 方法3: 作为最后手段,返回几何中心高度 + var midHeight = (floorHeight + ceilingHeight) / 2.0; + LogManager.Warning($"[表面分析] ⚠️ 无法检测到精确表面,使用几何中心高度: {midHeight:F2}"); + return midHeight; } catch (Exception ex) { - LogManager.Warning($"[表面分析] 表面高度分析失败: {ex.Message},使用顶面高度"); - return bbox.Max.Z; + LogManager.Error($"[表面分析] 表面高度分析失败: {ex.Message},使用顶面高度"); + return ceilingHeight; } } @@ -578,6 +604,580 @@ namespace NavisworksTransport.PathPlanning { return _heightCache.Count; } + + /// + /// 尝试从上方进行射线投射检测表面高度 + /// + /// 目标位置 + /// 通道模型 + /// 最大高度 + /// 检测到的表面高度,如果失败则返回null + private double? TryRaycastFromAbove(Point3D position, ModelItem channel, double maxHeight) + { + try + { + LogManager.Info($"[射线投射] 从上方({position.X:F2}, {position.Y:F2}, {maxHeight:F2})向下投射射线"); + + // 获取当前活动视图 + var activeView = Application.ActiveDocument?.ActiveView; + if (activeView == null) + { + LogManager.Debug($"[射线投射] 无法获取活动视图"); + return null; + } + + // 从通道上方的点开始,向下投射多条射线 + var testHeights = new[] { maxHeight + 100, maxHeight + 50, maxHeight + 10 }; + + foreach (var testHeight in testHeights) + { + var testPoint = new Point3D(position.X, position.Y, testHeight); + + // 将3D点投影到屏幕 + if (TryProjectWorldToScreen(testPoint, activeView, out Point2D screenPoint)) + { + // 检查屏幕坐标是否在视图范围内 + if (screenPoint.X >= 0 && screenPoint.X < activeView.Width && + screenPoint.Y >= 0 && screenPoint.Y < activeView.Height) + { + // 使用PickItemFromPoint获取该屏幕位置的实际表面点 + var pickResult = activeView.PickItemFromPoint((int)screenPoint.X, (int)screenPoint.Y); + if (pickResult != null && IsModelItemRelatedToChannel(pickResult.ModelItem, channel)) + { + LogManager.Info($"[射线投射] ✅ 成功检测到表面点: Z={pickResult.Point.Z:F2}"); + return pickResult.Point.Z; + } + } + } + } + + LogManager.Debug($"[射线投射] 所有射线投射尝试均失败"); + return null; + } + catch (Exception ex) + { + LogManager.Warning($"[射线投射] 射线投射失败: {ex.Message}"); + return null; + } + } + + /// + /// 尝试COM API几何射线投射获取精确表面高度 + /// + /// 目标位置 + /// 通道模型 + /// 输出的表面高度 + /// 是否成功获取表面高度 + private bool TryGeometricRaycast(Point3D position, ModelItem channel, out double surfaceHeight) + { + surfaceHeight = 0.0; + try + { + LogManager.Info($"[COM几何射线] 开始从ModelItem提取三角形几何数据: {channel.DisplayName}"); + + // 使用COM API提取三角形几何数据 + var triangles = ExtractTrianglesFromModelItem(channel); + if (triangles.Count == 0) + { + LogManager.Warning($"[COM几何射线] 未能从ModelItem提取到三角形数据: {channel.DisplayName}"); + return false; + } + + LogManager.Info($"[COM几何射线] 成功提取到 {triangles.Count} 个三角形,开始射线-三角形交点计算"); + + // 执行垂直射线与三角形的交点检测 + var intersectionPoints = PerformRayTriangleIntersection(position, triangles); + + if (intersectionPoints.Count > 0) + { + // 选择最高的交点作为表面高度 + surfaceHeight = intersectionPoints.Max(); + LogManager.Info($"[COM几何射线] ✅ 射线-三角形交点检测成功: 找到 {intersectionPoints.Count} 个交点,最高点Z={surfaceHeight:F2}"); + return true; + } + else + { + LogManager.Warning($"[COM几何射线] 射线-三角形交点检测失败: 位置({position.X:F2}, {position.Y:F2})未与任何三角形相交"); + return false; + } + } + catch (Exception ex) + { + LogManager.Error($"[COM几何射线] COM API几何射线投射出错: {ex.Message}"); + return false; + } + } + + /// + /// 使用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; // 使用静态属性创建单位矩阵 + } + + /// + /// 执行射线与三角形的交点检测 + /// + /// 射线起点的XY坐标 + /// 三角形列表 + /// 交点的Z坐标列表 + private List PerformRayTriangleIntersection(Point3D position, List triangles) + { + var intersectionPoints = new List(); + + try + { + // 创建垂直向下的射线(从通道上方合理高度开始) + var maxZ = triangles.Count > 0 ? triangles.Max(t => Math.Max(Math.Max(t.V1.Z, t.V2.Z), t.V3.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)) + { + 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) + { + LogManager.Error($"[射线-三角形] 射线-三角形交点计算失败: {ex.Message}"); + return intersectionPoints; + } + } + + /// + /// 射线与三角形的交点检测算法(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; + } + } + + /// + /// 尝试多点采样获取表面高度 + /// + /// 中心位置 + /// 通道模型 + /// 采样得到的表面高度,如果失败则返回null + private double? TryMultiPointSampling(Point3D position, ModelItem channel) + { + try + { + LogManager.Info($"[多点采样] 在位置({position.X:F2}, {position.Y:F2})周围进行多点采样"); + + var activeView = Application.ActiveDocument?.ActiveView; + if (activeView == null) + { + LogManager.Debug($"[多点采样] 无法获取活动视图"); + return null; + } + + var validHeights = new List(); + var sampleRadius = 50.0; // 50单位半径内采样 + var samplePoints = new[] + { + new Point3D(position.X, position.Y, 0), // 中心点 + new Point3D(position.X + sampleRadius, position.Y, 0), // 右 + new Point3D(position.X - sampleRadius, position.Y, 0), // 左 + new Point3D(position.X, position.Y + sampleRadius, 0), // 上 + new Point3D(position.X, position.Y - sampleRadius, 0), // 下 + }; + + foreach (var samplePoint in samplePoints) + { + if (TryProjectWorldToScreen(samplePoint, activeView, out Point2D screenPoint)) + { + if (screenPoint.X >= 0 && screenPoint.X < activeView.Width && + screenPoint.Y >= 0 && screenPoint.Y < activeView.Height) + { + var pickResult = activeView.PickItemFromPoint((int)screenPoint.X, (int)screenPoint.Y); + if (pickResult != null && IsModelItemRelatedToChannel(pickResult.ModelItem, channel)) + { + validHeights.Add(pickResult.Point.Z); + LogManager.Debug($"[多点采样] 采样点({samplePoint.X:F1}, {samplePoint.Y:F1})高度: {pickResult.Point.Z:F2}"); + } + } + } + } + + if (validHeights.Count > 0) + { + // 使用中位数减少异常值影响 + validHeights.Sort(); + var medianHeight = validHeights[validHeights.Count / 2]; + LogManager.Info($"[多点采样] ✅ 采样成功,采样点数={validHeights.Count},中位数高度={medianHeight:F2}"); + return medianHeight; + } + + LogManager.Debug($"[多点采样] 没有找到有效的采样点"); + return null; + } + catch (Exception ex) + { + LogManager.Warning($"[多点采样] 多点采样失败: {ex.Message}"); + return null; + } + } } /// @@ -657,4 +1257,167 @@ 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/GridMap.cs b/src/PathPlanning/GridMap.cs index 12019fd..f7a0b81 100644 --- a/src/PathPlanning/GridMap.cs +++ b/src/PathPlanning/GridMap.cs @@ -199,28 +199,31 @@ namespace NavisworksTransport.PathPlanning { try { - // 临时禁用精确高度计算以防止网格生成时崩溃 - // TODO: 将精确高度计算改为按需计算,而不是在网格生成时 + LogManager.Info($"[网格地图] 开始计算高度: 位置({worldX:F2}, {worldY:F2}), 精确计算={EnablePreciseHeightCalculation}, 检测器={HeightDetector != null}, 通道数={ChannelItems?.Count() ?? 0}"); - /* // 如果启用精确高度计算且有必要的组件 if (EnablePreciseHeightCalculation && HeightDetector != null && ChannelItems != null) { + LogManager.Info($"[网格地图] ✅ 条件满足,使用精确高度计算"); var position = new Point3D(worldX, worldY, 0); // Z坐标先设为0,由高度检测器确定 var preciseHeight = HeightDetector.GetChannelFloorHeight(position, ChannelItems); - LogManager.Debug($"[网格地图] 精确高度计算: 位置({worldX:F2}, {worldY:F2}) -> 高度{preciseHeight:F2}"); + LogManager.Info($"[网格地图] 精确高度计算结果: 位置({worldX:F2}, {worldY:F2}) -> 高度{preciseHeight:F2}"); return preciseHeight; } - */ + LogManager.Info($"[网格地图] ❌ 条件不满足,使用传统线性插值"); // 使用传统的线性插值方法(稳定且高效) - return CalculateLegacyInterpolatedZ(worldX, worldY); + var legacyHeight = CalculateLegacyInterpolatedZ(worldX, worldY); + LogManager.Info($"[网格地图] 传统插值结果: 位置({worldX:F2}, {worldY:F2}) -> 高度{legacyHeight:F2}"); + return legacyHeight; } catch (Exception ex) { - LogManager.Warning($"[网格地图] 高度计算失败,使用传统插值: {ex.Message}"); - return CalculateLegacyInterpolatedZ(worldX, worldY); + LogManager.Error($"[网格地图] 高度计算失败,使用传统插值: {ex.Message}"); + var fallbackHeight = CalculateLegacyInterpolatedZ(worldX, worldY); + LogManager.Info($"[网格地图] 异常后回退结果: 位置({worldX:F2}, {worldY:F2}) -> 高度{fallbackHeight:F2}"); + return fallbackHeight; } } @@ -314,19 +317,17 @@ namespace NavisworksTransport.PathPlanning ChannelItems = channelItems?.ToList() ?? new List(); LogManager.Info($"[网格地图] 设置通道模型项: {(ChannelItems?.Count() ?? 0)} 个通道"); - // 临时禁用精确高度计算以防止网格生成时崩溃 - // 精确高度计算应该在路径生成后的优化阶段使用 - EnablePreciseHeightCalculation = false; - LogManager.Info("[网格地图] 精确高度计算已禁用(防止网格生成崩溃)"); - - /* // 如果有通道数据,启用精确高度计算 if (ChannelItems?.Any() == true) { EnablePreciseHeightCalculation = true; LogManager.Info("[网格地图] 精确高度计算已启用"); } - */ + else + { + EnablePreciseHeightCalculation = false; + LogManager.Info("[网格地图] 精确高度计算已禁用(无通道数据)"); + } } catch (Exception ex) { @@ -342,14 +343,8 @@ namespace NavisworksTransport.PathPlanning /// 是否启用精确计算 public void SetHeightCalculationMode(bool enablePrecise) { - // 临时强制禁用精确计算以防止崩溃 - EnablePreciseHeightCalculation = false; - LogManager.Info($"[网格地图] 高度计算模式: 传统模式(精确计算已临时禁用)"); - - /* EnablePreciseHeightCalculation = enablePrecise && ChannelItems?.Any() == true; LogManager.Info($"[网格地图] 高度计算模式: {(EnablePreciseHeightCalculation ? "精确模式" : "传统模式")}"); - */ } ///