进行动态坐标系架构设计,增加自动检测模型坐标系探索按钮

This commit is contained in:
tian 2026-01-30 23:39:21 +08:00
parent bc39552ed2
commit 5fb18b5869
11 changed files with 1960 additions and 0 deletions

View File

@ -245,6 +245,9 @@
<Compile Include="src\UI\WPF\Views\EditRotationWindow.xaml.cs">
<DependentUpon>EditRotationWindow.xaml</DependentUpon>
</Compile>
<Compile Include="src\UI\WPF\Views\CoordinateSystemResultDialog.xaml.cs">
<DependentUpon>CoordinateSystemResultDialog.xaml</DependentUpon>
</Compile>
<!-- UI - WPF ViewModels -->
<Compile Include="src\UI\WPF\ViewModels\ViewModelBase.cs" />
<Compile Include="src\UI\WPF\ViewModels\LogisticsControlViewModel.cs" />
@ -312,6 +315,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="src\UI\WPF\Views\CoordinateSystemResultDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="src\UI\WPF\Views\ModelSettingsView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@ -5,6 +5,8 @@
### [2026/1/28]
1. [ ] 优化将ViewPonit的RenderStyle改成Shaded以免影响高亮考虑在显示碰撞时改成Wireframe
2、[ ] 优化修改架构适应模型坐标系的变化Yup-Xright-Zfront
3、[ ] (优化)修改吊装路径适应桁车空中路线(纵向+平移,有吊绳)
### [2026/1/26]

View File

@ -0,0 +1,342 @@
# 坐标系动态适配设计方案(简化版)
## 问题背景
客户模型坐标系与插件默认坐标系不同:
- **插件默认**: Z-up (Z轴向上)
- **客户模型**: Y-up (Y轴向上常见于Revit导出)
这导致网格生成、高度检测、路径规划等功能出现问题。
---
## 简化检测方案
### 核心原则
**单一检测源**: 使用 `Document.UpVector` 作为唯一检测依据
```csharp
var upVector = Application.ActiveDocument.UpVector;
if (!upVector.IsZero)
{
// 使用 Document.UpVector 判断坐标系
if (Math.Abs(upVector.Y) > 0.9)
return CoordinateSystemType.YUp;
else if (Math.Abs(upVector.Z) > 0.9)
return CoordinateSystemType.ZUp;
}
else
{
// 未定义,使用默认配置
return ConfigManager.Instance.Current.CoordinateSystem.Type;
}
```
---
## 实现架构
### 1. 坐标系类型枚举
```csharp
// src/Utils/CoordinateSystem/CoordinateSystemType.cs
public enum CoordinateSystemType
{
AutoDetect, // 自动检测(使用 Document.UpVector
ZUp, // 强制 Z-Up
YUp // 强制 Y-Up
}
```
### 2. 简化坐标系管理器
```csharp
// src/Utils/CoordinateSystem/CoordinateSystemManager.cs
public class CoordinateSystemManager
{
public static CoordinateSystemManager Instance { get; } = new();
private ICoordinateSystem _current;
private CoordinateSystemType _configuredType;
/// <summary>
/// 当前坐标系
/// </summary>
public ICoordinateSystem Current => _current;
/// <summary>
/// 初始化/重新检测坐标系
/// </summary>
public void Initialize()
{
_configuredType = ConfigManager.Instance.Current.CoordinateSystem.Type;
switch (_configuredType)
{
case CoordinateSystemType.AutoDetect:
_current = AutoDetect();
break;
case CoordinateSystemType.ZUp:
_current = new ZUpCoordinateSystem();
break;
case CoordinateSystemType.YUp:
_current = new YUpCoordinateSystem();
break;
}
LogManager.Info($"[坐标系] 初始化完成: {_current.Type}");
}
/// <summary>
/// 自动检测坐标系(基于 Document.UpVector
/// </summary>
private ICoordinateSystem AutoDetect()
{
try
{
var doc = Application.ActiveDocument;
if (doc == null) return new ZUpCoordinateSystem();
var upVector = doc.UpVector;
if (!upVector.IsZero)
{
if (Math.Abs(upVector.Y) > 0.9)
{
LogManager.Info("[坐标系检测] Document.UpVector 表明 Y-Up 坐标系");
return new YUpCoordinateSystem();
}
else if (Math.Abs(upVector.Z) > 0.9)
{
LogManager.Info("[坐标系检测] Document.UpVector 表明 Z-Up 坐标系");
return new ZUpCoordinateSystem();
}
}
LogManager.Warning("[坐标系检测] Document.UpVector 未定义,使用默认 Z-Up");
return new ZUpCoordinateSystem();
}
catch (Exception ex)
{
LogManager.Error($"[坐标系检测] 检测失败: {ex.Message}");
return new ZUpCoordinateSystem();
}
}
/// <summary>
/// 手动切换坐标系(用于系统管理界面)
/// </summary>
public void SetCoordinateSystem(CoordinateSystemType type)
{
switch (type)
{
case CoordinateSystemType.ZUp:
_current = new ZUpCoordinateSystem();
break;
case CoordinateSystemType.YUp:
_current = new YUpCoordinateSystem();
break;
default:
_current = AutoDetect();
break;
}
LogManager.Info($"[坐标系] 手动切换为: {_current.Type}");
}
}
```
---
## 配置文件
```toml
# default_config.toml
[coordinate_system]
# 坐标系类型: "AutoDetect"(推荐), "ZUp", "YUp"
# AutoDetect 将使用 Document.UpVector 自动检测
type = "AutoDetect"
```
---
## 系统管理界面
在系统管理页签添加坐标系设置:
```xml
<!-- 添加到 SystemManagementView.xaml -->
<ComboBox ItemsSource="{Binding CoordinateSystemOptions}"
SelectedItem="{Binding SelectedCoordinateSystem}"
ToolTip="选择坐标系AutoDetect 将自动检测"/>
```
**ViewModel 实现**:
```csharp
public ObservableCollection<string> CoordinateSystemOptions { get; } =
new() { "AutoDetect", "ZUp", "YUp" };
public string SelectedCoordinateSystem
{
get => _selectedCoordinateSystem;
set
{
if (SetProperty(ref _selectedCoordinateSystem, value))
{
// 解析并应用新坐标系
if (Enum.TryParse<CoordinateSystemType>(value, out var type))
{
CoordinateSystemManager.Instance.SetCoordinateSystem(type);
LogManager.Info($"坐标系已切换为: {value}");
}
}
}
}
```
---
## 使用流程
### 场景1: 自动检测成功
1. 打开模型
2. 插件自动检测 `Document.UpVector`
3. 检测到 `(0,1,0)` → 自动使用 Y-Up 坐标系
4. 用户无感知,功能正常工作
### 场景2: 自动检测失败UpVector 为 Zero
1. 打开模型
2. 插件检测到 `Document.UpVector.IsZero`
3. 回退到默认 Z-Up记录警告日志
4. 用户发现功能异常
5. 打开系统管理 → 手动切换坐标系为 Y-Up
6. 功能恢复正常
### 场景3: 用户强制指定
1. 用户提前知道模型坐标系
2. 在系统管理中设置坐标系为 Y-Up
3. 打开模型,直接使用指定坐标系
---
## 需要修改的模块
| 模块 | 修改内容 | 优先级 |
|------|----------|--------|
| **CoordinateSystemManager** | 新建,实现简化检测逻辑 | P0 |
| **ICoordinateSystem** | 接口及 ZUp/YUp 实现 | P0 |
| **SystemManagementView** | 添加坐标系选择下拉框 | P0 |
| **GridMap** | 使用坐标系抽象 | P1 |
| **GridMapGenerator** | 垂直扫描方向适配 | P1 |
| **其他模块** | 逐步替换直接坐标访问 | P2 |
---
## 实施步骤
### 阶段1: 核心实现1周
1. 创建坐标系统抽象层
2. 实现简化检测逻辑
3. 添加系统管理界面配置
### 阶段2: 核心模块适配1周
1. 修改 GridMap 使用坐标系抽象
2. 修改 GridMapGenerator
3. 测试验证
### 阶段3: 全面适配1周
1. 逐步替换其他模块
2. 完善测试
3. 文档更新
---
## 关键代码示例
### 坐标系抽象接口
```csharp
public interface ICoordinateSystem
{
CoordinateSystemType Type { get; }
// 获取高度值(统一抽象)
double GetElevation(Point3D point);
// 设置高度值
Point3D SetElevation(Point3D point, double elevation);
// 获取水平面坐标
(double h1, double h2) GetHorizontalCoords(Point3D point);
// 创建3D点
Point3D CreatePoint(double h1, double h2, double elevation);
// 向上向量
Vector3D UpVector { get; }
// 垂直扫描方向(用于障碍物检测)
Vector3D VerticalScanDirection { get; }
}
```
### Z-Up 实现
```csharp
public class ZUpCoordinateSystem : ICoordinateSystem
{
public CoordinateSystemType Type => CoordinateSystemType.ZUp;
public Vector3D UpVector => new Vector3D(0, 0, 1);
public Vector3D VerticalScanDirection => new Vector3D(0, 0, -1);
public double GetElevation(Point3D point) => point.Z;
public Point3D SetElevation(Point3D point, double elevation) =>
new Point3D(point.X, point.Y, elevation);
public (double h1, double h2) GetHorizontalCoords(Point3D point) =>
(point.X, point.Y);
public Point3D CreatePoint(double h1, double h2, double elevation) =>
new Point3D(h1, h2, elevation);
}
```
### Y-Up 实现
```csharp
public class YUpCoordinateSystem : ICoordinateSystem
{
public CoordinateSystemType Type => CoordinateSystemType.YUp;
public Vector3D UpVector => new Vector3D(0, 1, 0);
public Vector3D VerticalScanDirection => new Vector3D(0, -1, 0);
public double GetElevation(Point3D point) => point.Y;
public Point3D SetElevation(Point3D point, double elevation) =>
new Point3D(point.X, elevation, point.Z);
public (double h1, double h2) GetHorizontalCoords(Point3D point) =>
(point.X, point.Z);
public Point3D CreatePoint(double h1, double h2, double elevation) =>
new Point3D(h1, elevation, h2);
}
```
---
## 优势
1. **简单可靠**: 基于 API 提供的 UpVector无需猜测
2. **用户可控**: 自动检测失败时可手动干预
3. **向后兼容**: 默认行为不变,不影响现有用户
4. **易于维护**: 代码简洁,逻辑清晰
---
*文档更新时间: 2026-01-30*
*版本: 简化版*

View File

@ -0,0 +1,530 @@
# 坐标系动态适配设计方案
## 问题背景
客户模型坐标系与插件默认坐标系不同:
- **插件默认**: Z-up (Z轴向上, X向右, Y向后)
- **客户模型**: Y-up (Y轴向上, X向右, Z向前)
这导致网格生成、高度检测、碰撞检测、路径渲染等功能出现问题。
---
## 架构设计
### 整体架构
```
┌─────────────────────────────────────────────────────────────┐
│ 业务逻辑层 │
│ (PathPlanning, Collision Detection, Animation, etc.) │
├─────────────────────────────────────────────────────────────┤
│ 坐标系抽象层 │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ICoordinateSystem │ │ CoordinateSystemManager │ │
│ └─────────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 具体实现层 │
│ ┌───────────────┐ ┌───────────────┐ ┌─────────────────┐ │
│ │ ZUpCoordinateSystem │ │ YUpCoordinateSystem │ │...│
│ └───────────────┘ └───────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌───────┴───────┐
▼ ▼
[Navisworks API] [自定义逻辑]
```
---
## 核心组件实现
### 1. 坐标系类型枚举
**文件**: `src/Utils/CoordinateSystem/CoordinateSystemType.cs`
```csharp
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// 支持的坐标系类型
/// </summary>
public enum CoordinateSystemType
{
/// <summary>
/// Z轴向上 (标准Navisworks坐标系: Z-up, X-right, Y-back)
/// </summary>
ZUp,
/// <summary>
/// Y轴向上 (常见于Revit等: Y-up, X-right, Z-front)
/// </summary>
YUp,
/// <summary>
/// 自动检测
/// </summary>
AutoDetect
}
/// <summary>
/// 轴定义
/// </summary>
public enum Axis
{
Horizontal1, // X轴对应通常是Right
Horizontal2, // Z/Y轴对应通常是Front/Back
Vertical // Y/Z轴对应通常是Up
}
}
```
---
### 2. 坐标系接口
**文件**: `src/Utils/CoordinateSystem/ICoordinateSystem.cs`
```csharp
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// 坐标系接口 - 抽象不同坐标系的差异
/// </summary>
public interface ICoordinateSystem
{
/// <summary>
/// 坐标系类型
/// </summary>
CoordinateSystemType Type { get; }
/// <summary>
/// 获取向上轴的索引 (0=X, 1=Y, 2=Z)
/// </summary>
int UpAxisIndex { get; }
/// <summary>
/// 获取水平面主轴索引 (通常是X)
/// </summary>
int PrimaryHorizontalAxisIndex { get; }
/// <summary>
/// 获取水平面次轴索引
/// </summary>
int SecondaryHorizontalAxisIndex { get; }
/// <summary>
/// 获取点的高度值(统一抽象)
/// </summary>
double GetElevation(Point3D point);
/// <summary>
/// 设置点的高度值,返回新点
/// </summary>
Point3D SetElevation(Point3D point, double elevation);
/// <summary>
/// 获取水平面坐标返回Vector2D或Tuple
/// </summary>
(double h1, double h2) GetHorizontalCoords(Point3D point);
/// <summary>
/// 从水平面坐标和高度构建3D点
/// </summary>
Point3D CreatePoint(double h1, double h2, double elevation);
/// <summary>
/// 获取垂直方向向量
/// </summary>
Vector3D UpVector { get; }
/// <summary>
/// 获取网格平面用于2D网格的轴对应
/// 返回两个轴的索引 (axis1, axis2)
/// </summary>
(int axis1, int axis2) GridPlaneAxes { get; }
/// <summary>
/// 将外部点转换为内部标准表示(如果必要)
/// </summary>
Point3D ToInternal(Point3D externalPoint);
/// <summary>
/// 将内部点转换为外部表示
/// </summary>
Point3D ToExternal(Point3D internalPoint);
/// <summary>
/// 获取用于垂直扫描的方向向量(通常是-UpVector
/// </summary>
Vector3D VerticalScanDirection { get; }
/// <summary>
/// 获取包围盒的高度范围
/// </summary>
(double min, double max) GetHeightRange(BoundingBox3D bounds);
/// <summary>
/// 获取包围盒的水平范围
/// </summary>
(double min1, double max1, double min2, double max2) GetHorizontalRange(BoundingBox3D bounds);
}
}
```
---
### 3. Z-Up 坐标系实现
**文件**: `src/Utils/CoordinateSystem/ZUpCoordinateSystem.cs`
```csharp
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// Z轴向上坐标系 (Navisworks默认)
/// X = Right, Y = Back, Z = Up
/// </summary>
public class ZUpCoordinateSystem : ICoordinateSystem
{
public CoordinateSystemType Type => CoordinateSystemType.ZUp;
public int UpAxisIndex => 2; // Z
public int PrimaryHorizontalAxisIndex => 0; // X
public int SecondaryHorizontalAxisIndex => 1; // Y
public Vector3D UpVector => new Vector3D(0, 0, 1);
public Vector3D VerticalScanDirection => new Vector3D(0, 0, -1);
public (int axis1, int axis2) GridPlaneAxes => (0, 1); // X, Y
public double GetElevation(Point3D point) => point.Z;
public Point3D SetElevation(Point3D point, double elevation) =>
new Point3D(point.X, point.Y, elevation);
public (double h1, double h2) GetHorizontalCoords(Point3D point) =>
(point.X, point.Y);
public Point3D CreatePoint(double h1, double h2, double elevation) =>
new Point3D(h1, h2, elevation);
public Point3D ToInternal(Point3D externalPoint) => externalPoint;
public Point3D ToExternal(Point3D internalPoint) => internalPoint;
public (double min, double max) GetHeightRange(BoundingBox3D bounds) =>
(bounds.Min.Z, bounds.Max.Z);
public (double min1, double max1, double min2, double max2) GetHorizontalRange(BoundingBox3D bounds) =>
(bounds.Min.X, bounds.Max.X, bounds.Min.Y, bounds.Max.Y);
}
}
```
---
### 4. Y-Up 坐标系实现
**文件**: `src/Utils/CoordinateSystem/YUpCoordinateSystem.cs`
```csharp
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// Y轴向上坐标系 (Revit默认等)
/// X = Right, Y = Up, Z = Front
/// </summary>
public class YUpCoordinateSystem : ICoordinateSystem
{
public CoordinateSystemType Type => CoordinateSystemType.YUp;
public int UpAxisIndex => 1; // Y
public int PrimaryHorizontalAxisIndex => 0; // X
public int SecondaryHorizontalAxisIndex => 2; // Z
public Vector3D UpVector => new Vector3D(0, 1, 0);
public Vector3D VerticalScanDirection => new Vector3D(0, -1, 0);
public (int axis1, int axis2) GridPlaneAxes => (0, 2); // X, Z
public double GetElevation(Point3D point) => point.Y;
public Point3D SetElevation(Point3D point, double elevation) =>
new Point3D(point.X, elevation, point.Z);
public (double h1, double h2) GetHorizontalCoords(Point3D point) =>
(point.X, point.Z);
public Point3D CreatePoint(double h1, double h2, double elevation) =>
new Point3D(h1, elevation, h2);
public Point3D ToInternal(Point3D externalPoint) => new Point3D(
externalPoint.X,
externalPoint.Z,
externalPoint.Y); // 转换为内部Z-up表示
public Point3D ToExternal(Point3D internalPoint) => new Point3D(
internalPoint.X,
internalPoint.Z,
internalPoint.Y); // 从内部Z-up转换回来
public (double min, double max) GetHeightRange(BoundingBox3D bounds) =>
(bounds.Min.Y, bounds.Max.Y);
public (double min1, double max1, double min2, double max2) GetHorizontalRange(BoundingBox3D bounds) =>
(bounds.Min.X, bounds.Max.X, bounds.Min.Z, bounds.Max.Z);
}
}
```
---
### 5. 坐标系管理器
**文件**: `src/Utils/CoordinateSystem/CoordinateSystemManager.cs`
```csharp
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// 坐标系管理器 - 全局访问点和自动检测
/// </summary>
public class CoordinateSystemManager
{
private static readonly Lazy<CoordinateSystemManager> _instance =
new Lazy<CoordinateSystemManager>(() => new CoordinateSystemManager());
public static CoordinateSystemManager Instance => _instance.Value;
private ICoordinateSystem _current;
private CoordinateSystemType _configuredType = CoordinateSystemType.AutoDetect;
private CoordinateSystemManager()
{
// 默认使用Z-up
_current = new ZUpCoordinateSystem();
}
/// <summary>
/// 当前活动的坐标系
/// </summary>
public ICoordinateSystem Current => _current;
/// <summary>
/// 配置坐标系类型
/// </summary>
public void Configure(CoordinateSystemType type)
{
_configuredType = type;
switch (type)
{
case CoordinateSystemType.ZUp:
_current = new ZUpCoordinateSystem();
LogManager.Info("[坐标系管理器] 配置为 Z-Up 坐标系");
break;
case CoordinateSystemType.YUp:
_current = new YUpCoordinateSystem();
LogManager.Info("[坐标系管理器] 配置为 Y-Up 坐标系");
break;
case CoordinateSystemType.AutoDetect:
_current = AutoDetectCoordinateSystem();
break;
}
}
/// <summary>
/// 自动检测坐标系
/// 基于模型数据的统计分析
/// </summary>
private ICoordinateSystem AutoDetectCoordinateSystem()
{
try
{
var doc = Application.ActiveDocument;
if (doc == null || doc.Models.Count == 0)
{
LogManager.Warning("[坐标系管理器] 无法自动检测使用默认Z-Up");
return new ZUpCoordinateSystem();
}
// 获取模型整体包围盒
var sceneBounds = doc.Models[0].RootItem.BoundingBox();
// 策略1: 分析模型边界在Y和Z方向的分布
// 如果Z方向的跨度明显小于X和Y可能是Y-up建筑通常更高而非更深
double xSpan = sceneBounds.Max.X - sceneBounds.Min.X;
double ySpan = sceneBounds.Max.Y - sceneBounds.Min.Y;
double zSpan = sceneBounds.Max.Z - sceneBounds.Min.Z;
LogManager.Info($"[坐标系检测] 模型跨度: X={xSpan:F2}, Y={ySpan:F2}, Z={zSpan:F2}");
// 启发式规则如果Y跨度远大于Z跨度可能是Y-up
if (ySpan > zSpan * 3 && ySpan > xSpan * 0.5)
{
LogManager.Info("[坐标系检测] 检测到 Y-Up 坐标系 (Y跨度显著)");
return new YUpCoordinateSystem();
}
// 策略2: 分析典型建筑元素(楼板、墙)的方向
var coordinateSystem = AnalyzeBuildingElements();
if (coordinateSystem != null) return coordinateSystem;
LogManager.Info("[坐标系检测] 使用默认 Z-Up 坐标系");
return new ZUpCoordinateSystem();
}
catch (Exception ex)
{
LogManager.Error($"[坐标系检测] 自动检测失败: {ex.Message}");
return new ZUpCoordinateSystem();
}
}
/// <summary>
/// 通过分析建筑元素检测坐标系
/// </summary>
private ICoordinateSystem AnalyzeBuildingElements()
{
// 实现:检查楼板等水平元素的法向量
// 如果主要水平面的法向量在Y方向则是Y-up
// 简化实现:可以根据项目需求扩展
return null;
}
}
}
```
---
## 影响范围分析
### 需要修改的模块
| 模块 | 修改策略 | 工作量 | 优先级 |
|------|----------|--------|--------|
| **GridMap** | 使用 `ICoordinateSystem` 替代直接的 `.X/.Y/.Z` 访问 | 中等 | P0 |
| **GridMapGenerator** | 垂直扫描方向使用 `VerticalScanDirection` | 中等 | P0 |
| **AutoPathFinder** | 高度计算抽象化 | 中等 | P0 |
| **PathPointRenderPlugin** | 渲染时坐标转换 | 较小 | P1 |
| **GeometryHelper** | 几何提取时考虑坐标系 | 中等 | P1 |
| **ChannelHeightDetector** | 垂直射线方向适配 | 较小 | P0 |
| **Animation/TimeLiner** | 车辆移动方向适配 | 中等 | P2 |
| **SlopeAnalyzer** | 坡度计算适配 | 较小 | P1 |
---
## 配置文件支持
`default_config.toml` 中添加坐标系配置:
```toml
[coordinate_system]
# 坐标系类型: "ZUp", "YUp", "AutoDetect"
type = "AutoDetect"
# 手动指定时的轴映射(可选,用于特殊坐标系)
# up_axis = "Y" # 或 "Z"
# right_axis = "X"
# front_axis = "Z"
```
---
## 代码修改示例
### GridMap.cs 修改示例
**修改前**:
```csharp
public Point3D GridToWorld3D(GridPoint2D gridPosition)
{
var world2D = GridToWorld2D(gridPosition);
var cell = Cells[gridPosition.X, gridPosition.Y];
double z = 0;
if (cell.HeightLayers != null && cell.HeightLayers.Count > 0)
{
z = cell.HeightLayers[0].Z; // ❌ 直接访问Z
}
return new Point3D(world2D.X, world2D.Y, z);
}
```
**修改后**:
```csharp
public Point3D GridToWorld3D(GridPoint2D gridPosition)
{
var cs = CoordinateSystemManager.Instance.Current;
var world2D = GridToWorld2D(gridPosition);
var cell = Cells[gridPosition.X, gridPosition.Y];
double elevation = 0;
if (cell.HeightLayers != null && cell.HeightLayers.Count > 0)
{
elevation = cell.HeightLayers[0].Elevation; // ✅ 使用抽象的高度
}
// 使用坐标系创建点
var (h1, h2) = cs.GetHorizontalCoords(world2D);
return cs.CreatePoint(h1, h2, elevation);
}
```
---
## 实施路线图
### 阶段1核心适配P0- 1-2周
1. 创建坐标系抽象层ICoordinateSystem + 实现类)
2. 修改 GridMap 和 GridMapGenerator
3. 修改 ChannelHeightDetector 的垂直扫描
4. 添加配置支持
5. 基础测试验证
### 阶段2完整适配P1- 1周
1. 修改 AutoPathFinder
2. 修改 GeometryHelper
3. 修改 PathPointRenderPlugin
4. 修改 SlopeAnalyzer
### 阶段3优化完善P2- 1周
1. 动画系统适配
2. 性能优化(缓存转换结果)
3. 完整测试覆盖
4. 文档更新
---
## 测试策略
1. **准备测试模型**
- Z-up 坐标系的模型(现有)
- Y-up 坐标系的模型(客户提供或创建)
2. **验证功能**
- 网格生成正确性
- 路径规划结果一致性
- 高度检测准确性
- 渲染显示正确性
3. **回归测试**
- 确保Z-up模型仍然正常工作
- Y-up模型功能完整
---
## 注意事项
1. **向后兼容**: 默认保持 Z-up 行为,确保现有用户不受影响
2. **性能**: 坐标转换可能带来轻微性能开销,可通过缓存优化
3. **文档**: 更新 AGENTS.md 和 README.md说明坐标系配置方法
4. **日志**: 在关键位置添加坐标系检测和使用的日志,便于调试
---
*文档创建时间: 2026-01-30*
*作者: AI Assistant*
*状态: 设计方案*

View File

@ -0,0 +1,136 @@
# 坐标系检测结果分析
## 测试模型对比
### 模型1标准 Z-Up 模型 (Floor2_mobile.nwd)
| 指标 | 值 |
|------|-----|
| WorldUpVector | (0, 0, 1) |
| Transform.Rotation | 0° (无旋转) |
| 包围盒 Min | Z=-33.51 |
| 包围盒 Max | Z=88.00 |
| **判定** | ✅ **Z-Up 坐标系** |
### 模型2Y-Up 模型 (Floor2_mobile_yup.nwf)
| 指标 | 值 |
|------|-----|
| WorldUpVector | **(0, 1, 0)** |
| Transform.Rotation | **270° 绕 X 轴** |
| 包围盒 Min | Y=-33.51 |
| 包围盒 Max | Y=100.56 |
| **判定** | ✅ **Y-Up 坐标系** |
---
## 关键发现
### 1. WorldUpVector 是最可靠的判断依据
```
Z-Up 模型: WorldUpVector = (0.0000, 0.0000, 1.0000)
Y-Up 模型: WorldUpVector = (0.0000, 1.0000, 0.0000)
```
**结论**`Viewpoint.WorldUpVector` 直接反映了模型的原始坐标系,无需分析包围盒。
### 2. Navisworks 的自动转换机制
当导入 Y-Up 模型时Navisworks 会:
1. **自动旋转模型**:绕 X 轴旋转 270°将 Y-Up 转为 Z-Up 显示
2. **保留原始信息**:通过 `WorldUpVector` 记录原始坐标系
这使得:
- 视觉上所有模型都是 Z-Up
- 但 `WorldUpVector` 保持原始坐标系
- 插件需要据此适配内部计算
### 3. 包围盒分析的局限性
Y-Up 模型经过旋转后:
- 原始 Y 轴(高度)变成了 Z 轴
- 所以包围盒跨度分析会显示 Z 跨度大
- 容易产生误判
---
## 正确的检测方法(已实施)
```csharp
// 方法1使用 WorldUpVector推荐
var worldUp = doc.CurrentViewpoint.Value.WorldUpVector;
if (Math.Abs(worldUp.Z) > 0.9)
return CoordinateSystemType.ZUp; // (0, 0, 1)
else if (Math.Abs(worldUp.Y) > 0.9)
return CoordinateSystemType.YUp; // (0, 1, 0)
else if (Math.Abs(worldUp.X) > 0.9)
return CoordinateSystemType.XUp; // (1, 0, 0) 罕见
// 方法2辅助检查 Transform 旋转
var rotation = model.RootItem.Transform.Factor().Rotation.ToAxisAndAngle();
if (Math.Abs(rotation.Axis.X) > 0.9 &&
(Math.Abs(rotation.Angle - Math.PI/2) < 0.1 || Math.Abs(rotation.Angle - 3*Math.PI/2) < 0.1))
{
// 90° 或 270° 绕 X 轴旋转 → Y-Up 模型
}
```
---
## 实施建议
### 1. 更新 CoordinateSystemManager
使用 `WorldUpVector` 作为主要检测依据:
```csharp
private ICoordinateSystem AutoDetectCoordinateSystem()
{
var doc = Application.ActiveDocument;
var worldUp = doc.CurrentViewpoint.Value.WorldUpVector;
if (Math.Abs(worldUp.Y) > 0.9)
{
LogManager.Info("[坐标系检测] WorldUpVector 表明 Y-Up 坐标系");
return new YUpCoordinateSystem();
}
// 默认为 Z-Up
return new ZUpCoordinateSystem();
}
```
### 2. 用户确认机制
检测后提示用户确认:
```
检测到模型使用 Y-Up 坐标系(来自 Revit 等软件)
是否启用坐标系适配?
[是] [否] [保持默认]
```
### 3. 配置文件预设置
`default_config.toml` 中:
```toml
[coordinate_system]
# 可选值: "AutoDetect"(推荐), "ZUp", "YUp"
type = "AutoDetect"
```
---
## 下一步行动
1. ✅ **已完成**:坐标系探索按钮
2. ✅ **已完成**可靠的检测方法WorldUpVector
3. ⏳ **待实施**:更新 `CoordinateSystemManager` 使用新方法
4. ⏳ **待实施**:实现坐标系抽象层适配
5. ⏳ **待实施**:修改 GridMap 等核心模块
---
*文档更新时间: 2026-01-30*
*状态: 检测方法已验证,等待实施适配*

View File

@ -0,0 +1,313 @@
# Navisworks API 坐标系探索文档
## 概述
本文档记录 Navisworks API 中关于坐标系获取的探索结果,包括已验证的方法和待验证的潜在方法。
---
## 已验证的 API
### 1. Viewpoint.WorldUpVector
**文件**: `src/Utils/ViewpointHelper.cs` (第119行)
```csharp
// 获取/设置视角的向上向量
newViewpoint.WorldUpVector = new UnitVector3D(0, 1, 0);
```
**说明**:
- 这是视图级别的向上向量,不是模型级别的坐标系定义
- 用于控制相机的朝向
- **局限性**: 可能不同于模型的实际坐标系
---
### 2. Model.Transform
**文件**: `src/Commands/ReadTransformTestCommand.cs` (第30行)
```csharp
// 获取模型项的变换矩阵
var transform = item.Transform;
var components = transform.Factor();
```
**说明**:
- 获取单个模型项的局部变换
- 包含 Translation, Rotation, Scale
- **局限性**: 这是模型项级别的变换,不是全局坐标系定义
---
### 3. COM API - GetLocalToWorldMatrix
**文件**: `src/Utils/GeometryHelper.cs` (第270行)
```csharp
// 从 COM API 获取局部到世界的变换矩阵
var transform = (ComApi.InwLTransform3f3)(object)fragment.GetLocalToWorldMatrix();
```
**说明**:
- 用于几何片段的世界坐标变换
- **局限性**: 几何级别的变换,不是坐标系定义
---
## 待验证的潜在方法
### 方法1: Document 级别的 Orientation 设置
Navisworks 界面中可以通过以下路径设置坐标系:
```
File Options → Orientation → Up Vector / North Vector
```
**可能的 API 路径**:
#### A. COM API 方式 (最有可能)
```csharp
// 伪代码 - 待验证
var comDocument = ComApiBridge.ToInwOpState(Application.ActiveDocument);
// 或
var fileOptions = comDocument.FileOptions;
var orientation = fileOptions.Orientation;
var upVector = orientation.UpVector; // 可能是 Vector3D 或类似类型
var northVector = orientation.NorthVector;
```
#### B. .NET API 方式 (可能不存在)
```csharp
// 伪代码 - 待验证
var doc = Application.ActiveDocument;
// 可能的属性路径:
// doc.Options.Orientation.UpVector
// doc.FileOptions.Orientation.UpVector
// doc.Settings.Orientation.UpVector
```
**验证建议**:
1. 使用 Object Browser 查看 `Autodesk.Navisworks.Api` 中的相关类
2. 检查 `Document`, `DocumentOptions`, `FileOptions` 等类
3. 查看 COM API 的 `InwOpState` 或相关接口
---
### 方法2: Model 级别的 Transform
**可能的 API**:
```csharp
// 伪代码 - 待验证
foreach (Model model in Application.ActiveDocument.Models)
{
// 模型可能有一个根级变换
var modelTransform = model.Transform;
var orientation = modelTransform.Factor().Rotation;
// 从旋转矩阵推导坐标系
}
```
**验证建议**:
1. 检查 `Model` 类的属性
2. 对比不同坐标系模型的 `RootItem.Transform`
---
### 方法3: 通过场景包围盒分析
**思路**: 通过分析模型的包围盒特征推断坐标系
```csharp
// 启发式检测算法
public CoordinateSystemType DetectByBoundingBox()
{
var sceneBounds = doc.Models[0].RootItem.BoundingBox();
double xSpan = sceneBounds.Max.X - sceneBounds.Min.X;
double ySpan = sceneBounds.Max.Y - sceneBounds.Min.Y;
double zSpan = sceneBounds.Max.Z - sceneBounds.Min.Z;
// 建筑通常更高而非更深
if (ySpan > zSpan * 3 && ySpan > xSpan * 0.5)
{
return CoordinateSystemType.YUp; // Y轴向上
}
return CoordinateSystemType.ZUp; // 默认 Z轴向上
}
```
**局限性**:
- 不精确,只是启发式猜测
- 某些建筑可能不符合常规(如横向发展的建筑)
---
### 方法4: 通过典型元素分析
**思路**: 分析楼板、墙等建筑元素的法向量
```csharp
// 伪代码
var floorItems = FindItemsByCategory("楼板");
foreach (var floor in floorItems)
{
// 获取几何并计算法向量
var triangles = ExtractTriangles(floor);
var normal = CalculateAverageNormal(triangles);
// 如果法向量主要在 Y 方向,则是 Y-up
if (Math.Abs(normal.Y) > 0.9)
{
return CoordinateSystemType.YUp;
}
}
```
**局限性**:
- 需要解析几何,计算量大
- 需要正确识别楼板元素
---
## 推荐探索代码
创建一个测试命令来探索 API
```csharp
using System;
using System.Windows;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Plugins;
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
namespace NavisworksTransport.Commands
{
[Plugin("CoordinateSystemExplorer", "NavisworksTransport", DisplayName = "坐标系探索")]
[AddInPlugin(AddInLocation.AddIn)]
public class CoordinateSystemExplorerCommand : AddInPlugin
{
public override int Execute(params string[] parameters)
{
var doc = Application.ActiveDocument;
var sb = new System.Text.StringBuilder();
sb.AppendLine("=== 坐标系探索 ===\n");
// 1. 检查 Document 级别的选项
sb.AppendLine("1. Document 级别信息:");
sb.AppendLine($" 文档名称: {doc.FileName}");
sb.AppendLine($" 模型数量: {doc.Models.Count}");
sb.AppendLine();
// 2. 检查每个模型的信息
sb.AppendLine("2. Model 级别信息:");
foreach (Model model in doc.Models)
{
sb.AppendLine($" 模型: {model.Title}");
sb.AppendLine($" - RootItem.Name: {model.RootItem.DisplayName}");
sb.AppendLine($" - RootItem.Transform: {model.RootItem.Transform}");
var bbox = model.RootItem.BoundingBox();
sb.AppendLine($" - 包围盒: [{bbox.Min.X:F2}, {bbox.Min.Y:F2}, {bbox.Min.Z:F2}] - [{bbox.Max.X:F2}, {bbox.Max.Y:F2}, {bbox.Max.Z:F2}]");
sb.AppendLine();
}
// 3. 检查 COM API
sb.AppendLine("3. COM API 探索:");
try
{
var comState = ComApiBridge.ToInwOpState(doc);
// 尝试访问 FileOptions 或 Orientation
// 注意:这部分需要根据实际 COM API 结构调整
sb.AppendLine($" COM State 类型: {comState.GetType().Name}");
}
catch (Exception ex)
{
sb.AppendLine($" COM API 访问失败: {ex.Message}");
}
sb.AppendLine();
// 4. 检查 Viewpoint
sb.AppendLine("4. Viewpoint 信息:");
var vp = doc.CurrentViewpoint.Value;
sb.AppendLine($" WorldUpVector: ({vp.WorldUpVector.X}, {vp.WorldUpVector.Y}, {vp.WorldUpVector.Z})");
sb.AppendLine($" Position: ({vp.Position.X:F2}, {vp.Position.Y:F2}, {vp.Position.Z:F2})");
sb.AppendLine();
// 5. 包围盒分析
sb.AppendLine("5. 包围盒分析:");
var sceneBounds = doc.Models[0].RootItem.BoundingBox();
double xSpan = sceneBounds.Max.X - sceneBounds.Min.X;
double ySpan = sceneBounds.Max.Y - sceneBounds.Min.Y;
double zSpan = sceneBounds.Max.Z - sceneBounds.Min.Z;
sb.AppendLine($" X 跨度: {xSpan:F2}");
sb.AppendLine($" Y 跨度: {ySpan:F2}");
sb.AppendLine($" Z 跨度: {zSpan:F2}");
sb.AppendLine($" 检测建议: {(ySpan > zSpan * 3 ? "可能是 Y-Up" : "可能是 Z-Up")}");
// 显示结果
MessageBox.Show(sb.ToString(), "坐标系探索结果");
LogManager.Info(sb.ToString());
return 0;
}
}
}
```
---
## 下一步行动建议
### 立即行动
1. **创建探索命令**
- 实现上面的 `CoordinateSystemExplorerCommand`
- 在客户模型上运行,记录结果
- 在标准 Z-up 模型上运行,对比结果
2. **Object Browser 检查**
- 打开 Visual Studio 的 Object Browser
- 查看 `Autodesk.Navisworks.Api` 程序集
- 搜索关键词: `Orientation`, `FileOptions`, `UpVector`, `North`
3. **COM API 文档检查**
- 打开 `doc\navisworks_api\COM\documentation\NavisWorksCOM.chm`
- 搜索 `FileOptions`, `Orientation`
### 短期行动
1. **如果找到 API**
- 更新 `CoordinateSystemManager` 使用官方 API
- 简化自动检测逻辑
2. **如果没有找到 API**
- 完善启发式检测算法
- 增加用户手动配置选项
- 考虑通过 UI 提示用户确认检测到的坐标系
---
## 相关资源
### 内部文档
- `doc\navisworks_api\NET\documentation\NET API.chm`
- `doc\navisworks_api\COM\documentation\NavisWorksCOM.chm`
### 外部链接
- [Autodesk Forum: Setting File Options Up- and North-orientation](https://forums.autodesk.com/t5/navisworks-api-forum/setting-file-options-up-and-north-orientation-using-navisworks/td-p/11795216)
- [How to change model orientation in Navisworks](https://www.autodesk.com/support/technical/article/caas/sfdcarticles/sfdcarticles/How-to-change-model-orientation-in-Navisworks.html)
### 参考文件
- `src/Utils/ViewpointHelper.cs` - Viewpoint.WorldUpVector 使用示例
- `src/Commands/ReadTransformTestCommand.cs` - Transform 读取示例
- `src/Utils/GeometryHelper.cs` - COM API Transform 使用示例
---
*文档创建时间: 2026-01-30*
*状态: 探索中*

View File

@ -0,0 +1,201 @@
using System;
using System.Linq;
using System.Text;
using System.Windows;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Plugins;
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
using NavisworksTransport.Utils;
namespace NavisworksTransport.Commands
{
/// <summary>
/// 坐标系探索命令 - 用于探索 Navisworks API 中的坐标系相关信息
/// </summary>
[Plugin("CoordinateSystemExplorer", "NavisworksTransport", DisplayName = "坐标系探索")]
[AddInPlugin(AddInLocation.AddIn)]
public class CoordinateSystemExplorerCommand : AddInPlugin
{
public override int Execute(params string[] parameters)
{
try
{
var doc = Application.ActiveDocument;
if (doc == null || doc.IsClear)
{
MessageBox.Show("没有活动的文档!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return 1;
}
var sb = new StringBuilder();
sb.AppendLine("=== Navisworks 坐标系探索 ===\n");
// 1. Document 级别信息
sb.AppendLine("【1. Document 级别信息】");
sb.AppendLine($"文档名称: {doc.FileName}");
sb.AppendLine($"文档标题: {doc.Title}");
sb.AppendLine($"模型数量: {doc.Models.Count}");
sb.AppendLine();
// 2. Model 级别信息
sb.AppendLine("【2. Model 级别信息】");
int modelIndex = 0;
foreach (Model model in doc.Models)
{
sb.AppendLine($"模型 [{modelIndex}]: {model.Title}");
sb.AppendLine($" - 源文件名: {model.SourceFileName}");
sb.AppendLine($" - 源格式: {model.SourceFileFormat}");
var rootItem = model.RootItem;
if (rootItem != null)
{
sb.AppendLine($" - RootItem.DisplayName: {rootItem.DisplayName}");
sb.AppendLine($" - RootItem.Transform: {rootItem.Transform}");
var components = rootItem.Transform.Factor();
sb.AppendLine($" - Transform.Translation: ({components.Translation.X:F4}, {components.Translation.Y:F4}, {components.Translation.Z:F4})");
sb.AppendLine($" - Transform.Rotation.Axis: ({components.Rotation.Axis.X:F4}, {components.Rotation.Axis.Y:F4}, {components.Rotation.Axis.Z:F4})");
sb.AppendLine($" - Transform.Rotation.Angle: {components.Rotation.Angle:F6} rad ({components.Rotation.Angle * 180 / Math.PI:F2}°)");
sb.AppendLine($" - Transform.Scale: ({components.Scale.X:F4}, {components.Scale.Y:F4}, {components.Scale.Z:F4})");
var bbox = rootItem.BoundingBox();
sb.AppendLine($" - 包围盒 Min: ({bbox.Min.X:F4}, {bbox.Min.Y:F4}, {bbox.Min.Z:F4})");
sb.AppendLine($" - 包围盒 Max: ({bbox.Max.X:F4}, {bbox.Max.Y:F4}, {bbox.Max.Z:F4})");
double xSpan = bbox.Max.X - bbox.Min.X;
double ySpan = bbox.Max.Y - bbox.Min.Y;
double zSpan = bbox.Max.Z - bbox.Min.Z;
sb.AppendLine($" - 跨度: X={xSpan:F2}, Y={ySpan:F2}, Z={zSpan:F2}");
sb.AppendLine($" - 坐标系推测: {(ySpan > zSpan * 2 ? " Y-Up" : " Z-Up")}");
}
sb.AppendLine();
modelIndex++;
}
// 3. 当前选择项的 Transform 详情
sb.AppendLine("【3. 当前选择项信息】");
var selection = doc.CurrentSelection.SelectedItems;
if (selection.Count > 0)
{
var item = selection.First();
sb.AppendLine($"选中项: {item.DisplayName}");
sb.AppendLine($" - ClassName: {item.ClassName}");
sb.AppendLine($" - ClassDisplayName: {item.ClassDisplayName}");
sb.AppendLine($" - HasGeometry: {item.HasGeometry}");
var transform = item.Transform;
var comp = transform.Factor();
sb.AppendLine($" - Transform.Translation: ({comp.Translation.X:F4}, {comp.Translation.Y:F4}, {comp.Translation.Z:F4})");
sb.AppendLine($" - Transform.Rotation.Axis: ({comp.Rotation.Axis.X:F4}, {comp.Rotation.Axis.Y:F4}, {comp.Rotation.Axis.Z:F4})");
sb.AppendLine($" - Transform.Rotation.Angle: {comp.Rotation.Angle:F6} rad");
var itemBbox = item.BoundingBox();
sb.AppendLine($" - 包围盒 Center: ({itemBbox.Center.X:F4}, {itemBbox.Center.Y:F4}, {itemBbox.Center.Z:F4})");
}
else
{
sb.AppendLine("当前没有选择项");
}
sb.AppendLine();
// 4. Viewpoint 信息
sb.AppendLine("【4. Viewpoint 信息】");
var vp = doc.CurrentViewpoint.Value;
sb.AppendLine($"WorldUpVector: ({vp.WorldUpVector.X:F4}, {vp.WorldUpVector.Y:F4}, {vp.WorldUpVector.Z:F4})");
sb.AppendLine($"Position: ({vp.Position.X:F4}, {vp.Position.Y:F4}, {vp.Position.Z:F4})");
sb.AppendLine($"Rotation.Axis: ({vp.Rotation.Axis.X:F4}, {vp.Rotation.Axis.Y:F4}, {vp.Rotation.Axis.Z:F4})");
sb.AppendLine($"Rotation.Angle: {vp.Rotation.Angle:F6} rad");
sb.AppendLine();
// 5. COM API 探索
sb.AppendLine("【5. COM API 探索】");
try
{
var comState = ComApiBridge.ToInwOpState(doc);
sb.AppendLine($"COM State 类型: {comState.GetType().FullName}");
// 尝试获取模型的 COM 表示
if (doc.Models.Count > 0)
{
var firstModel = doc.Models[0];
// 注意:这里可能需要不同的方法来获取 COM 模型对象
sb.AppendLine($"尝试访问 COM 模型对象...");
// 通过选择获取 COM 对象
var modelCollection = new ModelItemCollection { firstModel.RootItem };
var comSelection = ComApiBridge.ToInwOpSelection(modelCollection);
sb.AppendLine($"COM Selection 路径数: {comSelection.Paths().Count}");
// 释放 COM 对象
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
}
sb.AppendLine("COM API 访问成功");
}
catch (Exception ex)
{
sb.AppendLine($"COM API 访问失败: {ex.Message}");
sb.AppendLine($"堆栈: {ex.StackTrace}");
}
sb.AppendLine();
// 6. 坐标系推测总结
sb.AppendLine("【6. 坐标系推测总结】");
if (doc.Models.Count > 0)
{
var rootBounds = doc.Models[0].RootItem.BoundingBox();
double xSpan = rootBounds.Max.X - rootBounds.Min.X;
double ySpan = rootBounds.Max.Y - rootBounds.Min.Y;
double zSpan = rootBounds.Max.Z - rootBounds.Min.Z;
sb.AppendLine($"模型跨度分析:");
sb.AppendLine($" - X (左右): {xSpan:F2} 单位");
sb.AppendLine($" - Y (前后/上下): {ySpan:F2} 单位");
sb.AppendLine($" - Z (上下/前后): {zSpan:F2} 单位");
sb.AppendLine();
sb.AppendLine($"启发式判断:");
if (ySpan > zSpan * 3)
{
sb.AppendLine($" - Y 跨度显著大于 Z 跨度 ({ySpan/zSpan:F1}x)");
sb.AppendLine($" - 推测: Y-Up 坐标系 (Y轴向上)");
}
else if (zSpan > ySpan * 3)
{
sb.AppendLine($" - Z 跨度显著大于 Y 跨度 ({zSpan/ySpan:F1}x)");
sb.AppendLine($" - 推测: Z-Up 坐标系 (Z轴向上)");
}
else
{
sb.AppendLine($" - Y 和 Z 跨度相近");
sb.AppendLine($" - 无法确定坐标系,可能是特殊模型或 Z-Up");
}
}
// 显示结果
string result = sb.ToString();
// 保存到日志
LogManager.Info(result);
// 显示对话框(如果内容太长,可能需要截断)
const int maxLength = 4000;
string displayText = result.Length > maxLength
? result.Substring(0, maxLength) + "\n\n... (内容已截断,请查看完整日志)"
: result;
MessageBox.Show(displayText, "坐标系探索结果", MessageBoxButton.OK, MessageBoxImage.Information);
return 0;
}
catch (Exception ex)
{
string errorMsg = $"坐标系探索失败: {ex.Message}\n{ex.StackTrace}";
LogManager.Error(errorMsg);
MessageBox.Show(errorMsg, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return 1;
}
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.ObjectModel;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using NavisworksTransport.UI.WPF.Collections;
@ -116,6 +117,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand TestVoxelGridSDFCommand { get; private set; }
public ICommand TestVoxelPathFindingCommand { get; private set; }
public ICommand ReadTransformTestCommand { get; private set; }
public ICommand CoordinateSystemExplorerCommand { get; private set; }
#endregion
@ -220,6 +222,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
TestVoxelGridSDFCommand = new RelayCommand(() => ExecuteTestVoxelGridSDF());
TestVoxelPathFindingCommand = new RelayCommand(() => ExecuteTestVoxelPathFinding());
ReadTransformTestCommand = new RelayCommand(() => ExecuteReadTransformTest());
CoordinateSystemExplorerCommand = new RelayCommand(() => ExecuteCoordinateSystemExplorer());
LogManager.Info("系统管理命令初始化完成");
}
@ -845,6 +848,296 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
/// <summary>
/// 执行坐标系探索命令
/// </summary>
private void ExecuteCoordinateSystemExplorer()
{
SafeExecute(() =>
{
try
{
UpdateMainStatus("正在执行坐标系探索...");
LogManager.Info("开始坐标系探索");
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (doc == null || doc.IsClear)
{
System.Windows.MessageBox.Show(
"没有活动的文档!请先打开一个模型。",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error);
UpdateMainStatus("坐标系探索失败:无活动文档");
return;
}
var sb = new StringBuilder();
sb.AppendLine("=== Navisworks 坐标系探索 ===\n");
// 1. Document 级别信息
sb.AppendLine("【1. Document 级别信息】");
sb.AppendLine($"文档名称: {doc.FileName}");
sb.AppendLine($"文档标题: {doc.Title}");
sb.AppendLine($"模型数量: {doc.Models.Count}");
sb.AppendLine();
// 2. Model 级别信息
sb.AppendLine("【2. Model 级别信息】");
int modelIndex = 0;
foreach (var model in doc.Models)
{
sb.AppendLine($"模型 [{modelIndex}]:");
var rootItem = model.RootItem;
if (rootItem != null)
{
sb.AppendLine($" - RootItem.DisplayName: {rootItem.DisplayName}");
var components = rootItem.Transform.Factor();
sb.AppendLine($" - Transform.Translation: ({components.Translation.X:F4}, {components.Translation.Y:F4}, {components.Translation.Z:F4})");
var rotation = components.Rotation;
// Rotation3D 使用 ToAxisAndAngle() 方法获取轴和角度
var axisAngle = rotation.ToAxisAndAngle();
sb.AppendLine($" - Transform.Rotation.Axis: ({axisAngle.Axis.X:F4}, {axisAngle.Axis.Y:F4}, {axisAngle.Axis.Z:F4})");
sb.AppendLine($" - Transform.Rotation.Angle: {axisAngle.Angle:F6} rad ({axisAngle.Angle * 180 / Math.PI:F2}°)");
sb.AppendLine($" - Transform.Scale: ({components.Scale.X:F4}, {components.Scale.Y:F4}, {components.Scale.Z:F4})");
var bbox = rootItem.BoundingBox();
sb.AppendLine($" - 包围盒 Min: ({bbox.Min.X:F4}, {bbox.Min.Y:F4}, {bbox.Min.Z:F4})");
sb.AppendLine($" - 包围盒 Max: ({bbox.Max.X:F4}, {bbox.Max.Y:F4}, {bbox.Max.Z:F4})");
double xSpan = bbox.Max.X - bbox.Min.X;
double ySpan = bbox.Max.Y - bbox.Min.Y;
double zSpan = bbox.Max.Z - bbox.Min.Z;
sb.AppendLine($" - 跨度: X={xSpan:F2}, Y={ySpan:F2}, Z={zSpan:F2}");
sb.AppendLine($" - 坐标系推测: {(ySpan > zSpan * 2 ? " Y-Up" : " Z-Up")}");
}
sb.AppendLine();
modelIndex++;
}
// 3. Document 坐标系向量 (直接来自模型)
sb.AppendLine("【3. Document 坐标系向量 (模型原始)】");
var docUp = doc.UpVector;
var docRight = doc.RightVector;
var docFront = doc.FrontVector;
if (docUp.IsZero)
{
sb.AppendLine("UpVector: (0, 0, 0) - 未定义");
}
else
{
sb.AppendLine($"UpVector: ({docUp.X:F4}, {docUp.Y:F4}, {docUp.Z:F4})");
}
if (docRight.IsZero)
{
sb.AppendLine("RightVector: (0, 0, 0) - 未定义");
}
else
{
sb.AppendLine($"RightVector: ({docRight.X:F4}, {docRight.Y:F4}, {docRight.Z:F4})");
}
if (docFront.IsZero)
{
sb.AppendLine("FrontVector: (0, 0, 0) - 未定义");
}
else
{
sb.AppendLine($"FrontVector: ({docFront.X:F4}, {docFront.Y:F4}, {docFront.Z:F4})");
}
sb.AppendLine();
// 4. Viewpoint 信息 (Navisworks 处理后的)
sb.AppendLine("【4. Viewpoint 信息 (Navisworks 处理后)】");
var vp = doc.CurrentViewpoint.Value;
sb.AppendLine($"WorldUpVector: ({vp.WorldUpVector.X:F4}, {vp.WorldUpVector.Y:F4}, {vp.WorldUpVector.Z:F4})");
sb.AppendLine($"Position: ({vp.Position.X:F4}, {vp.Position.Y:F4}, {vp.Position.Z:F4})");
var vpRotation = vp.Rotation.ToAxisAndAngle();
sb.AppendLine($"Rotation.Axis: ({vpRotation.Axis.X:F4}, {vpRotation.Axis.Y:F4}, {vpRotation.Axis.Z:F4})");
sb.AppendLine($"Rotation.Angle: {vpRotation.Angle:F6} rad");
sb.AppendLine();
// 5. 坐标系推测总结
sb.AppendLine("【5. 坐标系推测总结】");
// 方法1: 优先使用 Document.UpVector 判断(模型原始定义)
sb.AppendLine($"方法1 - Document.UpVector (原始):");
if (!docUp.IsZero)
{
sb.AppendLine($" 值: ({docUp.X:F4}, {docUp.Y:F4}, {docUp.Z:F4})");
if (Math.Abs(docUp.Z) > 0.9)
{
sb.AppendLine($" ✅ 判定: Z-Up 坐标系 (原始)");
}
else if (Math.Abs(docUp.Y) > 0.9)
{
sb.AppendLine($" ✅ 判定: Y-Up 坐标系 (原始)");
}
else if (Math.Abs(docUp.X) > 0.9)
{
sb.AppendLine($" ⚠️ 判定: X-Up 坐标系 (罕见)");
}
}
else
{
sb.AppendLine($" 未定义 (Zero),需使用 Viewpoint.WorldUpVector");
}
sb.AppendLine();
// 方法2: 使用 WorldUpVector 判断Navisworks 处理后的,最可靠)
var worldUp = doc.CurrentViewpoint.Value.WorldUpVector;
sb.AppendLine($"方法2 - Viewpoint.WorldUpVector (推荐):");
sb.AppendLine($" 值: ({worldUp.X:F4}, {worldUp.Y:F4}, {worldUp.Z:F4})");
string detectedCoordinateSystem = "Unknown";
if (Math.Abs(worldUp.Z) > 0.9)
{
sb.AppendLine($" ✅ 判定: Z-Up 坐标系 (Z轴向上)");
sb.AppendLine($" 说明: 这是 Navisworks 默认坐标系,插件无需特殊配置");
detectedCoordinateSystem = "ZUp";
}
else if (Math.Abs(worldUp.Y) > 0.9)
{
sb.AppendLine($" ✅ 判定: Y-Up 坐标系 (Y轴向上)");
sb.AppendLine($" 说明: 通常是 Revit 等软件导出的模型");
sb.AppendLine($" 建议: 在 default_config.toml 中设置坐标系为 YUp");
detectedCoordinateSystem = "YUp";
}
else if (Math.Abs(worldUp.X) > 0.9)
{
sb.AppendLine($" ⚠️ 判定: X-Up 坐标系 (罕见)");
sb.AppendLine($" 说明: 非标准坐标系,需要手动适配");
detectedCoordinateSystem = "XUp";
}
else
{
sb.AppendLine($" ❓ 判定: 无法确定(非标准向上向量)");
}
sb.AppendLine();
// 方法3: 通过模型 Transform 旋转判断(辅助验证)
sb.AppendLine($"方法3 - 模型 Transform 旋转:");
if (doc.Models.Count > 0)
{
var rootItem = doc.Models[0].RootItem;
if (rootItem != null)
{
var components = rootItem.Transform.Factor();
var rotation = components.Rotation.ToAxisAndAngle();
double angleDegrees = rotation.Angle * 180 / Math.PI;
sb.AppendLine($" 旋转轴: ({rotation.Axis.X:F4}, {rotation.Axis.Y:F4}, {rotation.Axis.Z:F4})");
sb.AppendLine($" 旋转角: {angleDegrees:F2}°");
// 检查是否有 90° 或 270° 绕 X 轴旋转Y-Up 模型的典型特征)
if (Math.Abs(rotation.Axis.X) > 0.9 &&
(Math.Abs(angleDegrees - 90) < 5 || Math.Abs(angleDegrees - 270) < 5))
{
sb.AppendLine($" ✅ 发现 X 轴旋转 ~{angleDegrees:F0}°,这是 Y-Up 导入的典型特征");
}
else if (Math.Abs(angleDegrees) < 1)
{
sb.AppendLine($" 无显著旋转,符合 Z-Up 模型特征");
}
}
}
sb.AppendLine();
// 方法4: 包围盒跨度分析(启发式)
sb.AppendLine($"方法4 - 包围盒跨度分析(启发式):");
if (doc.Models.Count > 0)
{
var rootBounds = doc.Models[0].RootItem.BoundingBox();
double xSpan = rootBounds.Max.X - rootBounds.Min.X;
double ySpan = rootBounds.Max.Y - rootBounds.Min.Y;
double zSpan = rootBounds.Max.Z - rootBounds.Min.Z;
sb.AppendLine($" X 跨度: {xSpan:F2} 单位");
sb.AppendLine($" Y 跨度: {ySpan:F2} 单位");
sb.AppendLine($" Z 跨度: {zSpan:F2} 单位");
// 注意经过旋转后Y-Up 模型在 Navisworks 中显示为 Z-Up
// 所以这里看到的是转换后的坐标
if (zSpan > ySpan * 1.5)
{
sb.AppendLine($" Z 跨度大于 Y 跨度,显示为 Z-Up 方向");
}
else if (ySpan > zSpan * 1.5)
{
sb.AppendLine($" Y 跨度大于 Z 跨度,可能未完全转换");
}
}
sb.AppendLine();
// 最终建议
sb.AppendLine($"【最终建议】");
if (detectedCoordinateSystem == "YUp")
{
sb.AppendLine($"⚠️ 检测到 Y-Up 坐标系!");
sb.AppendLine($"当前插件使用 Z-Up 假设,在此模型上可能出现问题。");
sb.AppendLine($"建议:实施坐标系动态适配功能。");
}
else if (detectedCoordinateSystem == "ZUp")
{
sb.AppendLine($"✅ 标准 Z-Up 坐标系,插件应正常工作。");
}
// 显示和记录结果
string result = sb.ToString();
LogManager.Info(result);
// 使用可复制的对话框显示结果
var resultDialog = new NavisworksTransport.UI.WPF.Views.CoordinateSystemResultDialog
{
Title = "坐标系探索结果",
ResultText = result
};
// 尝试设置对话框所有者
try
{
var mainWindow = System.Windows.Application.Current?.MainWindow;
if (mainWindow != null && mainWindow.IsLoaded)
{
resultDialog.Owner = mainWindow;
}
else
{
foreach (System.Windows.Window window in System.Windows.Application.Current.Windows)
{
if (window.IsActive && window.IsLoaded)
{
resultDialog.Owner = window;
break;
}
}
}
}
catch { }
resultDialog.ShowDialog();
UpdateMainStatus("坐标系探索完成");
LogManager.Info("坐标系探索成功完成");
}
catch (Exception ex)
{
LogManager.Error($"坐标系探索异常: {ex.Message}", ex);
System.Windows.MessageBox.Show(
$"坐标系探索出现异常:\n{ex.Message}",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error);
UpdateMainStatus($"坐标系探索异常: {ex.Message}");
}
}, "坐标系探索");
}
#endregion
#region

View File

@ -0,0 +1,52 @@
<Window x:Class="NavisworksTransport.UI.WPF.Views.CoordinateSystemResultDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="坐标系探索结果"
Height="600" Width="700"
WindowStartupLocation="CenterOwner"
ResizeMode="CanResize">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题 -->
<TextBlock Grid.Row="0"
Text="坐标系探索结果 - 可复制以下内容"
FontWeight="Bold"
FontSize="14"
Margin="0,0,0,10"/>
<!-- 可复制的文本框 -->
<TextBox Grid.Row="1"
x:Name="ResultTextBox"
IsReadOnly="True"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
FontFamily="Consolas, monospace"
FontSize="11"
TextWrapping="Wrap"
AcceptsReturn="True"
Padding="5"/>
<!-- 按钮 -->
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0,10,0,0">
<Button Content="复制到剪贴板"
Click="CopyButton_Click"
Width="120"
Height="28"
Margin="0,0,10,0"/>
<Button Content="确定"
Click="OkButton_Click"
Width="80"
Height="28"
IsDefault="True"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,78 @@
using System.Windows;
using System.Windows.Controls;
namespace NavisworksTransport.UI.WPF.Views
{
/// <summary>
/// 坐标系探索结果对话框 - 支持文本选择和复制
/// </summary>
public partial class CoordinateSystemResultDialog : Window
{
public CoordinateSystemResultDialog()
{
InitializeComponent();
}
/// <summary>
/// 设置要显示的结果文本
/// </summary>
public string ResultText
{
get => ResultTextBox.Text;
set => ResultTextBox.Text = value;
}
/// <summary>
/// 复制按钮点击事件
/// </summary>
private void CopyButton_Click(object sender, RoutedEventArgs e)
{
try
{
Clipboard.SetText(ResultTextBox.Text);
MessageBox.Show("内容已复制到剪贴板!", "复制成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (System.Exception ex)
{
MessageBox.Show($"复制失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// 确定按钮点击事件
/// </summary>
private void OkButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
/// <summary>
/// 静态方法,方便调用显示结果
/// </summary>
public static void ShowResult(string title, string content, Window owner = null)
{
var dialog = new CoordinateSystemResultDialog
{
Title = title,
ResultText = content
};
if (owner != null)
{
dialog.Owner = owner;
}
else
{
// 尝试设置当前活动窗口为所有者
try
{
dialog.Owner = System.Windows.Application.Current?.MainWindow;
}
catch { }
}
dialog.ShowDialog();
}
}
}

View File

@ -174,6 +174,12 @@ NavisworksTransport 系统管理页签视图 - 采用与其他页签一致的Nav
Command="{Binding ReadTransformTestCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="读取选中对象的Transform信息包括旋转角度"/>
<!-- 坐标系探索按钮 -->
<Button Content="坐标系探索"
Command="{Binding CoordinateSystemExplorerCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="探索当前文档的坐标系信息用于适配Y-up坐标系模型"/>
</StackPanel>
</StackPanel>
</Border>