进行动态坐标系架构设计,增加自动检测模型坐标系探索按钮
This commit is contained in:
parent
bc39552ed2
commit
5fb18b5869
@ -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>
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
### [2026/1/28]
|
||||
|
||||
1. [ ] (优化)将ViewPonit的RenderStyle改成Shaded,以免影响高亮(考虑在显示碰撞时,改成Wireframe)
|
||||
2、[ ] (优化)修改架构适应模型坐标系的变化(Yup-Xright-Zfront)
|
||||
3、[ ] (优化)修改吊装路径适应桁车空中路线(纵向+平移,有吊绳)
|
||||
|
||||
### [2026/1/26]
|
||||
|
||||
|
||||
342
doc/working/coordinate-system-adaptation-design-simplified.md
Normal file
342
doc/working/coordinate-system-adaptation-design-simplified.md
Normal 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*
|
||||
*版本: 简化版*
|
||||
530
doc/working/coordinate-system-adaptation-design.md
Normal file
530
doc/working/coordinate-system-adaptation-design.md
Normal 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*
|
||||
*状态: 设计方案*
|
||||
136
doc/working/coordinate-system-detection-results.md
Normal file
136
doc/working/coordinate-system-detection-results.md
Normal 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 坐标系** |
|
||||
|
||||
### 模型2:Y-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*
|
||||
*状态: 检测方法已验证,等待实施适配*
|
||||
313
doc/working/navisworks-coordinate-system-api-exploration.md
Normal file
313
doc/working/navisworks-coordinate-system-api-exploration.md
Normal 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*
|
||||
*状态: 探索中*
|
||||
201
src/Commands/CoordinateSystemExplorerCommand.cs
Normal file
201
src/Commands/CoordinateSystemExplorerCommand.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 辅助方法
|
||||
|
||||
52
src/UI/WPF/Views/CoordinateSystemResultDialog.xaml
Normal file
52
src/UI/WPF/Views/CoordinateSystemResultDialog.xaml
Normal 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>
|
||||
78
src/UI/WPF/Views/CoordinateSystemResultDialog.xaml.cs
Normal file
78
src/UI/WPF/Views/CoordinateSystemResultDialog.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user