用射线法计算通道表面高度
This commit is contained in:
parent
7d97dd1f86
commit
b261efcaae
@ -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": []
|
||||
}
|
||||
|
||||
27
CLAUDE.md
27
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 `<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格式的文件名
|
||||
- 避免使用过于宽泛的搜索词
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user