用射线法计算通道表面高度

This commit is contained in:
tian 2025-08-15 15:31:41 +08:00
parent 7d97dd1f86
commit b261efcaae
4 changed files with 830 additions and 58 deletions

View File

@ -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": []
}

View File

@ -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 `<Reference Include>` 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格式的文件名
- 避免使用过于宽泛的搜索词

View File

@ -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
{
/// <summary>
@ -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<HeightSample>()
};
// 采样通道高度剖面
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
/// <returns>精确高度</returns>
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;
}
/// <summary>
@ -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
/// </summary>
/// <param name="position">位置</param>
/// <param name="channel">通道模型</param>
/// <param name="bbox">边界框</param>
/// <param name="floorHeight">地面高度</param>
/// <param name="ceilingHeight">顶面高度</param>
/// <returns>表面高度</returns>
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;
}
/// <summary>
/// 尝试从上方进行射线投射检测表面高度
/// </summary>
/// <param name="position">目标位置</param>
/// <param name="channel">通道模型</param>
/// <param name="maxHeight">最大高度</param>
/// <returns>检测到的表面高度如果失败则返回null</returns>
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;
}
}
/// <summary>
/// 尝试COM API几何射线投射获取精确表面高度
/// </summary>
/// <param name="position">目标位置</param>
/// <param name="channel">通道模型</param>
/// <param name="surfaceHeight">输出的表面高度</param>
/// <returns>是否成功获取表面高度</returns>
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;
}
}
/// <summary>
/// 使用COM API从ModelItem中提取三角形几何数据
/// </summary>
/// <param name="modelItem">模型项</param>
/// <returns>三角形列表</returns>
private List<Triangle3D> ExtractTrianglesFromModelItem(ModelItem modelItem)
{
var allTriangles = new List<Triangle3D>();
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;
}
}
/// <summary>
/// 递归提取ModelItem及其子项的几何数据
/// </summary>
private void ExtractTrianglesRecursive(ModelItem modelItem, GeometryExtractorCallback callback, List<Triangle3D> 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}");
}
}
/// <summary>
/// 从单个有几何数据的ModelItem中提取三角形
/// </summary>
private void ExtractTrianglesFromGeometry(ModelItem modelItem, GeometryExtractorCallback callback, List<Triangle3D> 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}");
}
}
/// <summary>
/// 获取Fragment的变换矩阵
/// </summary>
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; // 使用静态属性创建单位矩阵
}
/// <summary>
/// 执行射线与三角形的交点检测
/// </summary>
/// <param name="position">射线起点的XY坐标</param>
/// <param name="triangles">三角形列表</param>
/// <returns>交点的Z坐标列表</returns>
private List<double> PerformRayTriangleIntersection(Point3D position, List<Triangle3D> triangles)
{
var intersectionPoints = new List<double>();
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;
}
}
/// <summary>
/// 射线与三角形的交点检测算法Möller-Trumbore算法
/// </summary>
/// <param name="rayOrigin">射线起点</param>
/// <param name="rayDirection">射线方向</param>
/// <param name="triangle">三角形</param>
/// <param name="intersectionZ">交点的Z坐标</param>
/// <returns>是否相交</returns>
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;
}
}
/// <summary>
/// 计算两点之间的向量
/// </summary>
private Point3D SubtractPoints(Point3D a, Point3D b)
{
return new Point3D(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
}
/// <summary>
/// 计算两个向量的叉积
/// </summary>
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
);
}
/// <summary>
/// 计算两个向量的点积
/// </summary>
private double DotProduct(Point3D a, Point3D b)
{
return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
}
private List<double> FindRayModelSurfaceIntersections(Point3D position, ModelItem channel)
{
var intersections = new List<double>();
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;
}
/// <summary>
/// 从边界框估算表面高度
/// </summary>
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;
}
}
/// <summary>
/// 尝试多点采样获取表面高度
/// </summary>
/// <param name="position">中心位置</param>
/// <param name="channel">通道模型</param>
/// <returns>采样得到的表面高度如果失败则返回null</returns>
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<double>();
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;
}
}
}
/// <summary>
@ -657,4 +1257,167 @@ namespace NavisworksTransport.PathPlanning
/// </summary>
Other
}
/// <summary>
/// 三角形数据结构
/// </summary>
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;
}
}
/// <summary>
/// COM API几何提取回调类
/// 用于从ModelItem中提取真实的三角形几何数据
/// </summary>
public class GeometryExtractorCallback : COMApi.InwSimplePrimitivesCB
{
private readonly List<Triangle3D> _triangles;
private Matrix4 _transformMatrix;
public List<Triangle3D> Triangles => _triangles;
public GeometryExtractorCallback()
{
_triangles = new List<Triangle3D>();
_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();
}
}
/// <summary>
/// 4x4变换矩阵结构
/// </summary>
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;
}
/// <summary>
/// 创建单位矩阵
/// </summary>
public static Matrix4 Identity
{
get
{
return new Matrix4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
}
}
/// <summary>
/// 应用变换到点坐标
/// </summary>
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);
}
}
}

View File

@ -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<ModelItem>();
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
/// <param name="enablePrecise">是否启用精确计算</param>
public void SetHeightCalculationMode(bool enablePrecise)
{
// 临时强制禁用精确计算以防止崩溃
EnablePreciseHeightCalculation = false;
LogManager.Info($"[网格地图] 高度计算模式: 传统模式(精确计算已临时禁用)");
/*
EnablePreciseHeightCalculation = enablePrecise && ChannelItems?.Any() == true;
LogManager.Info($"[网格地图] 高度计算模式: {(EnablePreciseHeightCalculation ? "" : "")}");
*/
}
/// <summary>