From 5fb18b5869a7097f03acf141d99a83d2d87ebeec Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Fri, 30 Jan 2026 23:39:21 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E5=8A=A8=E6=80=81=E5=9D=90?= =?UTF-8?q?=E6=A0=87=E7=B3=BB=E6=9E=B6=E6=9E=84=E8=AE=BE=E8=AE=A1=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=87=AA=E5=8A=A8=E6=A3=80=E6=B5=8B=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=9D=90=E6=A0=87=E7=B3=BB=E6=8E=A2=E7=B4=A2=E6=8C=89?= =?UTF-8?q?=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NavisworksTransportPlugin.csproj | 7 + doc/requirement/todo_features.md | 2 + ...ate-system-adaptation-design-simplified.md | 342 +++++++++++ .../coordinate-system-adaptation-design.md | 530 ++++++++++++++++++ .../coordinate-system-detection-results.md | 136 +++++ ...works-coordinate-system-api-exploration.md | 313 +++++++++++ .../CoordinateSystemExplorerCommand.cs | 201 +++++++ .../ViewModels/SystemManagementViewModel.cs | 293 ++++++++++ .../Views/CoordinateSystemResultDialog.xaml | 52 ++ .../CoordinateSystemResultDialog.xaml.cs | 78 +++ src/UI/WPF/Views/SystemManagementView.xaml | 6 + 11 files changed, 1960 insertions(+) create mode 100644 doc/working/coordinate-system-adaptation-design-simplified.md create mode 100644 doc/working/coordinate-system-adaptation-design.md create mode 100644 doc/working/coordinate-system-detection-results.md create mode 100644 doc/working/navisworks-coordinate-system-api-exploration.md create mode 100644 src/Commands/CoordinateSystemExplorerCommand.cs create mode 100644 src/UI/WPF/Views/CoordinateSystemResultDialog.xaml create mode 100644 src/UI/WPF/Views/CoordinateSystemResultDialog.xaml.cs diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index ea0e432..75584ab 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -245,6 +245,9 @@ EditRotationWindow.xaml + + CoordinateSystemResultDialog.xaml + @@ -312,6 +315,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index 8e533a9..71777e0 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -5,6 +5,8 @@ ### [2026/1/28] 1. [ ] (优化)将ViewPonit的RenderStyle改成Shaded,以免影响高亮(考虑在显示碰撞时,改成Wireframe) +2、[ ] (优化)修改架构适应模型坐标系的变化(Yup-Xright-Zfront) +3、[ ] (优化)修改吊装路径适应桁车空中路线(纵向+平移,有吊绳) ### [2026/1/26] diff --git a/doc/working/coordinate-system-adaptation-design-simplified.md b/doc/working/coordinate-system-adaptation-design-simplified.md new file mode 100644 index 0000000..f5fc9e3 --- /dev/null +++ b/doc/working/coordinate-system-adaptation-design-simplified.md @@ -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; + + /// + /// 当前坐标系 + /// + public ICoordinateSystem Current => _current; + + /// + /// 初始化/重新检测坐标系 + /// + 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}"); + } + + /// + /// 自动检测坐标系(基于 Document.UpVector) + /// + 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(); + } + } + + /// + /// 手动切换坐标系(用于系统管理界面) + /// + 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 + + +``` + +**ViewModel 实现**: + +```csharp +public ObservableCollection CoordinateSystemOptions { get; } = + new() { "AutoDetect", "ZUp", "YUp" }; + +public string SelectedCoordinateSystem +{ + get => _selectedCoordinateSystem; + set + { + if (SetProperty(ref _selectedCoordinateSystem, value)) + { + // 解析并应用新坐标系 + if (Enum.TryParse(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* +*版本: 简化版* diff --git a/doc/working/coordinate-system-adaptation-design.md b/doc/working/coordinate-system-adaptation-design.md new file mode 100644 index 0000000..7365df2 --- /dev/null +++ b/doc/working/coordinate-system-adaptation-design.md @@ -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 +{ + /// + /// 支持的坐标系类型 + /// + public enum CoordinateSystemType + { + /// + /// Z轴向上 (标准Navisworks坐标系: Z-up, X-right, Y-back) + /// + ZUp, + + /// + /// Y轴向上 (常见于Revit等: Y-up, X-right, Z-front) + /// + YUp, + + /// + /// 自动检测 + /// + AutoDetect + } + + /// + /// 轴定义 + /// + 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 +{ + /// + /// 坐标系接口 - 抽象不同坐标系的差异 + /// + public interface ICoordinateSystem + { + /// + /// 坐标系类型 + /// + CoordinateSystemType Type { get; } + + /// + /// 获取向上轴的索引 (0=X, 1=Y, 2=Z) + /// + int UpAxisIndex { get; } + + /// + /// 获取水平面主轴索引 (通常是X) + /// + int PrimaryHorizontalAxisIndex { get; } + + /// + /// 获取水平面次轴索引 + /// + int SecondaryHorizontalAxisIndex { get; } + + /// + /// 获取点的高度值(统一抽象) + /// + double GetElevation(Point3D point); + + /// + /// 设置点的高度值,返回新点 + /// + Point3D SetElevation(Point3D point, double elevation); + + /// + /// 获取水平面坐标(返回Vector2D或Tuple) + /// + (double h1, double h2) GetHorizontalCoords(Point3D point); + + /// + /// 从水平面坐标和高度构建3D点 + /// + Point3D CreatePoint(double h1, double h2, double elevation); + + /// + /// 获取垂直方向向量 + /// + Vector3D UpVector { get; } + + /// + /// 获取网格平面(用于2D网格的轴对应) + /// 返回两个轴的索引 (axis1, axis2) + /// + (int axis1, int axis2) GridPlaneAxes { get; } + + /// + /// 将外部点转换为内部标准表示(如果必要) + /// + Point3D ToInternal(Point3D externalPoint); + + /// + /// 将内部点转换为外部表示 + /// + Point3D ToExternal(Point3D internalPoint); + + /// + /// 获取用于垂直扫描的方向向量(通常是-UpVector) + /// + Vector3D VerticalScanDirection { get; } + + /// + /// 获取包围盒的高度范围 + /// + (double min, double max) GetHeightRange(BoundingBox3D bounds); + + /// + /// 获取包围盒的水平范围 + /// + (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 +{ + /// + /// Z轴向上坐标系 (Navisworks默认) + /// X = Right, Y = Back, Z = Up + /// + 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 +{ + /// + /// Y轴向上坐标系 (Revit默认等) + /// X = Right, Y = Up, Z = Front + /// + 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 +{ + /// + /// 坐标系管理器 - 全局访问点和自动检测 + /// + public class CoordinateSystemManager + { + private static readonly Lazy _instance = + new Lazy(() => new CoordinateSystemManager()); + public static CoordinateSystemManager Instance => _instance.Value; + + private ICoordinateSystem _current; + private CoordinateSystemType _configuredType = CoordinateSystemType.AutoDetect; + + private CoordinateSystemManager() + { + // 默认使用Z-up + _current = new ZUpCoordinateSystem(); + } + + /// + /// 当前活动的坐标系 + /// + public ICoordinateSystem Current => _current; + + /// + /// 配置坐标系类型 + /// + 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; + } + } + + /// + /// 自动检测坐标系 + /// 基于模型数据的统计分析 + /// + 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(); + } + } + + /// + /// 通过分析建筑元素检测坐标系 + /// + 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* +*状态: 设计方案* diff --git a/doc/working/coordinate-system-detection-results.md b/doc/working/coordinate-system-detection-results.md new file mode 100644 index 0000000..d5e33b1 --- /dev/null +++ b/doc/working/coordinate-system-detection-results.md @@ -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* +*状态: 检测方法已验证,等待实施适配* diff --git a/doc/working/navisworks-coordinate-system-api-exploration.md b/doc/working/navisworks-coordinate-system-api-exploration.md new file mode 100644 index 0000000..9777a44 --- /dev/null +++ b/doc/working/navisworks-coordinate-system-api-exploration.md @@ -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* +*状态: 探索中* diff --git a/src/Commands/CoordinateSystemExplorerCommand.cs b/src/Commands/CoordinateSystemExplorerCommand.cs new file mode 100644 index 0000000..ad14490 --- /dev/null +++ b/src/Commands/CoordinateSystemExplorerCommand.cs @@ -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 +{ + /// + /// 坐标系探索命令 - 用于探索 Navisworks API 中的坐标系相关信息 + /// + [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; + } + } + } +} diff --git a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs index 5fb20c1..97e9976 100644 --- a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs +++ b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs @@ -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 } } + /// + /// 执行坐标系探索命令 + /// + 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 辅助方法 diff --git a/src/UI/WPF/Views/CoordinateSystemResultDialog.xaml b/src/UI/WPF/Views/CoordinateSystemResultDialog.xaml new file mode 100644 index 0000000..c39fe65 --- /dev/null +++ b/src/UI/WPF/Views/CoordinateSystemResultDialog.xaml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + +