From edcbd0802b567029566853a10b5ea9232d154cff Mon Sep 17 00:00:00 2001
From: tian <11429339@qq.com>
Date: Tue, 24 Feb 2026 03:57:33 +0800
Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0=E5=A4=9A?=
=?UTF-8?q?=E5=B1=82=E5=90=8A=E8=A3=85=E8=B7=AF=E5=BE=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.agents/skills/project-tools/README.md | 48 ++
.agents/skills/project-tools/SKILL.md | 94 +++
.../project-tools/examples/common-usage.md | 178 +++++
.../project-tools/utils/DialogHelper.md | 127 ++++
.../project-tools/utils/GeometryHelper.md | 134 ++++
.../skills/project-tools/utils/LogManager.md | 144 ++++
.../project-tools/utils/UnitsConverter.md | 126 ++++
CHANGELOG.md | 184 +++---
TransportPlugin.csproj | 3 +
doc/requirement/todo_features.md | 5 +
doc/working/multi_level_hoisting_path.md | 188 ++++++
src/Commands/CreateAerialPathCommand.cs | 75 ++-
src/Core/PathPlanningManager.cs | 6 +-
src/Core/PathPlanningModels.cs | 97 +++
src/PathPlanning/AerialPathGenerator.cs | 94 +++
src/UI/WPF/Converters/HoistingConverters.cs | 81 +++
src/UI/WPF/Models/HoistingLevelItem.cs | 56 ++
src/UI/WPF/ViewModels/PathEditingViewModel.cs | 623 +++++++++++++++++-
src/UI/WPF/Views/PathEditingView.xaml | 156 ++++-
19 files changed, 2315 insertions(+), 104 deletions(-)
create mode 100644 .agents/skills/project-tools/README.md
create mode 100644 .agents/skills/project-tools/SKILL.md
create mode 100644 .agents/skills/project-tools/examples/common-usage.md
create mode 100644 .agents/skills/project-tools/utils/DialogHelper.md
create mode 100644 .agents/skills/project-tools/utils/GeometryHelper.md
create mode 100644 .agents/skills/project-tools/utils/LogManager.md
create mode 100644 .agents/skills/project-tools/utils/UnitsConverter.md
create mode 100644 doc/working/multi_level_hoisting_path.md
create mode 100644 src/UI/WPF/Converters/HoistingConverters.cs
create mode 100644 src/UI/WPF/Models/HoistingLevelItem.cs
diff --git a/.agents/skills/project-tools/README.md b/.agents/skills/project-tools/README.md
new file mode 100644
index 0000000..480387d
--- /dev/null
+++ b/.agents/skills/project-tools/README.md
@@ -0,0 +1,48 @@
+# Project Tools Skill
+
+## 概述
+
+本 skill 用于指导 NavisworksTransport 项目中工具类的正确使用。
+
+## 文件结构
+
+```
+project-tools/
+├── SKILL.md # 主文档:工具类总览和快速参考
+├── README.md # 本文件
+├── utils/ # 各工具类详细文档
+│ ├── DialogHelper.md # 对话框工具
+│ ├── UnitsConverter.md # 单位转换工具
+│ ├── LogManager.md # 日志工具
+│ └── GeometryHelper.md # 几何计算工具
+└── examples/ # 使用示例
+ └── common-usage.md # 常见场景示例
+```
+
+## 使用方法
+
+1. **编写代码前**:查看 `SKILL.md` 确定需要的工具类
+2. **具体使用时**:查看 `utils/` 下的详细文档
+3. **参考示例**:查看 `examples/` 下的实际代码
+
+## 核心原则
+
+> **优先复用现有工具类,禁止重复造轮子**
+
+### 必须使用的工具类(强制)
+
+| 场景 | 工具类 | 错误示例 |
+|------|--------|----------|
+| 对话框Owner设置 | DialogHelper | `dialog.Owner = Application.Current.MainWindow` |
+| 单位转换 | UnitsConverter | `value * 0.001` |
+| 日志记录 | LogManager | `Console.WriteLine(...)` |
+| 几何计算 | GeometryHelper | `Math.Sqrt(dx*dx + dy*dy)` |
+
+## 检查清单
+
+编写代码前,请确认:
+
+- [ ] 阅读了 `SKILL.md` 中相关工具类的说明
+- [ ] 查阅了 `utils/` 下的详细文档
+- [ ] 代码中使用了正确的工具类方法
+- [ ] 没有重复实现已有功能
diff --git a/.agents/skills/project-tools/SKILL.md b/.agents/skills/project-tools/SKILL.md
new file mode 100644
index 0000000..95c05ff
--- /dev/null
+++ b/.agents/skills/project-tools/SKILL.md
@@ -0,0 +1,94 @@
+# NavisworksTransport 项目工具类使用指南
+
+## 概述
+
+本skill记录 NavisworksTransport 项目中的所有工具类及其使用方法。在编写代码前,请务必查看此文档,**优先使用现有工具类,避免重复造轮子**。
+
+## 工具类总览
+
+| 工具类 | 文件 | 用途 | 优先级 |
+|--------|------|------|--------|
+| **DialogHelper** | `src/Utils/DialogHelper.cs` | 对话框Owner设置和置顶显示 | ⭐⭐⭐ 必须 |
+| **UnitsConverter** | `src/Utils/UnitsConverter.cs` | 单位转换(米↔模型单位) | ⭐⭐⭐ 必须 |
+| **LogManager** | `src/Utils/LogManager.cs` | 日志记录 | ⭐⭐⭐ 必须 |
+| **GeometryHelper** | `src/Utils/GeometryHelper.cs` | 3D几何计算 | ⭐⭐⭐ |
+| **PathHelper** | `src/Utils/PathHelper.cs` | 路径相关工具 | ⭐⭐⭐ |
+| **CoordinateConverter** | `src/Utils/CoordinateConverter.cs` | 坐标系转换 | ⭐⭐ |
+| **ModelHighlightHelper** | `src/Utils/ModelHighlightHelper.cs` | 模型高亮显示 | ⭐⭐ |
+| **NavisworksSelectionHelper** | `src/Utils/NavisworksSelectionHelper.cs` | 选择集操作 | ⭐⭐ |
+| **ViewpointHelper** | `src/Utils/ViewpointHelper.cs` | 视点操作 | ⭐⭐ |
+| **SectionClipHelper** | `src/Utils/SectionClipHelper.cs` | 剖切操作 | ⭐ |
+
+## 快速参考
+
+### 对话框开发
+
+```csharp
+// ❌ 错误:直接设置Owner,可能在Navisworks环境中失败
+var dialog = new MyDialog();
+dialog.Owner = Application.Current.MainWindow; // 可能抛出异常!
+dialog.Show();
+
+// ✅ 正确:使用 DialogHelper
+var dialog = new MyDialog();
+DialogHelper.SetOwnerSafely(dialog); // 自动处理Navisworks环境
+dialog.Show();
+```
+
+### 单位转换
+
+```csharp
+// ❌ 错误:硬编码转换因子
+double meters = modelUnits * 0.001; // 错误!不同文档单位不同
+
+// ✅ 正确:使用 UnitsConverter
+double meters = UnitsConverter.ConvertToMeters(modelUnits);
+double modelUnits = UnitsConverter.ConvertFromMeters(meters);
+```
+
+### 日志记录
+
+```csharp
+// ❌ 错误:使用Console或直接输出
+Console.WriteLine($"Error: {ex.Message}"); // 不可见!
+
+// ✅ 正确:使用 LogManager
+LogManager.Info("操作成功");
+LogManager.Warning("警告信息");
+LogManager.Error("错误信息", exception);
+```
+
+## 详细文档
+
+- [DialogHelper 使用指南](utils/DialogHelper.md)
+- [UnitsConverter 使用指南](utils/UnitsConverter.md)
+- [LogManager 使用指南](utils/LogManager.md)
+- [GeometryHelper 使用指南](utils/GeometryHelper.md)
+- [ModelHighlightHelper 使用指南](utils/ModelHighlightHelper.md)
+
+## 使用检查清单
+
+在编写新功能前,请检查:
+
+- [ ] 需要显示对话框?→ 使用 **DialogHelper**
+- [ ] 涉及单位转换?→ 使用 **UnitsConverter**
+- [ ] 需要记录日志?→ 使用 **LogManager**
+- [ ] 需要几何计算?→ 使用 **GeometryHelper**
+- [ ] 需要高亮模型?→ 使用 **ModelHighlightHelper**
+- [ ] 需要处理选择集?→ 使用 **NavisworksSelectionHelper**
+
+## 禁止行为
+
+以下行为在项目中是**禁止**的:
+
+1. ❌ 直接设置 `Window.Owner = Application.Current.MainWindow`
+2. ❌ 硬编码单位转换因子(如 `* 0.001`)
+3. ❌ 使用 `Console.WriteLine` 输出日志
+4. ❌ 自己实现距离/角度计算(已有 GeometryHelper)
+5. ❌ 自己实现集合的线程安全包装(使用 ThreadSafeObservableCollection)
+
+## 扩展阅读
+
+- 项目规范:`AGENTS.md`
+- UI开发规范:`doc/guide/design_principles.md`
+- Navisworks API 使用:`SKILL.md` (nw-api)
diff --git a/.agents/skills/project-tools/examples/common-usage.md b/.agents/skills/project-tools/examples/common-usage.md
new file mode 100644
index 0000000..9b6db70
--- /dev/null
+++ b/.agents/skills/project-tools/examples/common-usage.md
@@ -0,0 +1,178 @@
+# 常见使用场景示例
+
+## 场景1:创建并显示对话框
+
+```csharp
+using NavisworksTransport.Utils;
+
+public class MyViewModel
+{
+ private MyDialog _dialog;
+
+ public void ShowDialog()
+ {
+ // 防止重复打开
+ if (_dialog != null && _dialog.IsVisible)
+ {
+ _dialog.Activate();
+ return;
+ }
+
+ // 创建对话框
+ _dialog = new MyDialog();
+
+ // ✅ 使用 DialogHelper 设置 Owner
+ DialogHelper.SetOwnerSafely(_dialog);
+
+ // 显示非模态对话框
+ _dialog.Show();
+
+ // 清理引用
+ _dialog.Closed += (s, e) => _dialog = null;
+ }
+}
+```
+
+## 场景2:带单位转换的计算
+
+```csharp
+using NavisworksTransport.Utils;
+
+public class PathPlanner
+{
+ ///
+ /// 设置网格大小
+ ///
+ /// 网格单元大小(米)
+ public void SetCellSize(double cellSizeInMeters)
+ {
+ // ✅ 转换为模型单位存储
+ double cellSize = UnitsConverter.ConvertFromMeters(cellSizeInMeters);
+
+ // 后续计算使用 cellSize(模型单位)
+ _grid = new GridMap(cellSize);
+ }
+
+ ///
+ /// 获取路径长度(用于显示)
+ ///
+ public string GetPathLengthDisplay(PathRoute route)
+ {
+ // ✅ 转换为米显示
+ double lengthInMeters = UnitsConverter.ConvertToMeters(route.TotalLength);
+ return $"{lengthInMeters:F2} 米";
+ }
+}
+```
+
+## 场景3:完整的操作日志记录
+
+```csharp
+using NavisworksTransport.Utils;
+
+public class PathService
+{
+ public async Task CreatePathAsync(PathParameters parameters)
+ {
+ LogManager.Info("[路径创建] === 开始创建路径 ===");
+ LogManager.Info($"[路径创建] 参数: {parameters}");
+
+ try
+ {
+ // 验证参数
+ if (!ValidateParameters(parameters))
+ {
+ LogManager.Warning("[路径创建] 参数验证失败");
+ return null;
+ }
+
+ // 执行创建
+ var route = await GeneratePathAsync(parameters);
+
+ LogManager.Info($"[路径创建] 成功: {route.Name}, ID: {route.Id}");
+ LogManager.Info($"[路径创建] 长度: {route.TotalLength:F2} 米, 点数: {route.Points.Count}");
+
+ return route;
+ }
+ catch (Exception ex)
+ {
+ // ✅ 记录完整异常信息
+ LogManager.Error($"[路径创建] 失败: {ex.Message}", ex);
+ throw;
+ }
+ finally
+ {
+ LogManager.Info("[路径创建] === 创建完成 ===");
+ }
+ }
+}
+```
+
+## 场景4:几何计算
+
+```csharp
+using NavisworksTransport.Utils;
+
+public class PathAnalyzer
+{
+ ///
+ /// 计算路径总长度
+ ///
+ public double CalculateTotalLength(List points)
+ {
+ double totalLength = 0;
+
+ for (int i = 0; i < points.Count - 1; i++)
+ {
+ // ✅ 使用 GeometryHelper
+ totalLength += GeometryHelper.Distance(points[i], points[i + 1]);
+ }
+
+ return totalLength;
+ }
+
+ ///
+ /// 检查转弯是否过急
+ ///
+ public bool IsTurnTooSharp(Point3D prev, Point3D current, Point3D next, double maxAngle)
+ {
+ Vector3D v1 = GeometryHelper.Subtract(current, prev);
+ Vector3D v2 = GeometryHelper.Subtract(next, current);
+
+ double angle = GeometryHelper.AngleBetweenDegrees(v1, v2);
+
+ return angle > maxAngle;
+ }
+}
+```
+
+## 场景5:确认对话框
+
+```csharp
+using NavisworksTransport.Utils;
+using System.Windows;
+
+public class PathViewModel
+{
+ public void DeletePath(PathRoute route)
+ {
+ // ✅ 使用 DialogHelper 显示确认对话框
+ var result = DialogHelper.ShowMessageBox(
+ $"确定要删除路径 \"{route.Name}\" 吗?\n此操作不可撤销。",
+ "确认删除",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning
+ );
+
+ if (result != MessageBoxResult.Yes)
+ {
+ LogManager.Info($"[删除] 用户取消删除: {route.Name}");
+ return;
+ }
+
+ // 执行删除
+ _pathPlanningManager.DeleteRoute(route.Id);
+ LogManager.Info($"[删除] 路径已删除: {route.Name}");
+ }
+}
+```
diff --git a/.agents/skills/project-tools/utils/DialogHelper.md b/.agents/skills/project-tools/utils/DialogHelper.md
new file mode 100644
index 0000000..1a07698
--- /dev/null
+++ b/.agents/skills/project-tools/utils/DialogHelper.md
@@ -0,0 +1,127 @@
+# DialogHelper 使用指南
+
+## 文件位置
+
+`src/Utils/DialogHelper.cs`
+
+## 用途
+
+统一处理对话框的 Owner 设置和置顶显示,解决 Navisworks 插件环境中的窗口焦点问题。
+
+## 核心方法
+
+### 1. SetOwnerSafely - 安全设置Owner(推荐)
+
+```csharp
+// 场景:在 ViewModel 中创建对话框
+var dialog = new MyDialog();
+DialogHelper.SetOwnerSafely(dialog);
+dialog.Show(); // 或 dialog.ShowDialog();
+```
+
+**特点**:
+- 自动查找可用的 Owner 窗口
+- 处理 Navisworks 插件环境的特殊情况
+- 捕获异常,不会导致程序崩溃
+
+### 2. ShowDialog - 显示模态对话框(完整处理)
+
+```csharp
+// 场景:显示需要返回结果的模态对话框
+var dialog = new MyInputDialog();
+bool? result = DialogHelper.ShowDialog(dialog);
+```
+
+### 3. ShowMessageBox - 显示消息框
+
+```csharp
+// 场景:显示错误提示或确认对话框
+DialogHelper.ShowMessageBox(
+ "操作成功完成",
+ "提示",
+ MessageBoxButton.OK,
+ MessageBoxImage.Information
+);
+```
+
+### 4. SetWin32Owner - 强制置顶到Navisworks主窗口
+
+```csharp
+// 场景:对话框必须置顶到Navisworks主窗口(极少数情况)
+var dialog = new MyDialog();
+DialogHelper.SetWin32Owner(dialog);
+dialog.Show();
+```
+
+## 使用示例
+
+### 示例1:ViewModel中显示非模态对话框
+
+```csharp
+private MyDialog _myDialog;
+
+private void ShowMyDialog()
+{
+ if (_myDialog != null && _myDialog.IsVisible)
+ {
+ _myDialog.Activate();
+ return;
+ }
+
+ _myDialog = new MyDialog();
+ DialogHelper.SetOwnerSafely(_myDialog); // ✅ 使用工具方法
+ _myDialog.Show();
+
+ _myDialog.Closed += (s, e) => _myDialog = null;
+}
+```
+
+### 示例2:显示确认对话框
+
+```csharp
+private bool ConfirmDelete()
+{
+ var result = DialogHelper.ShowMessageBox(
+ "确定要删除此路径吗?",
+ "确认删除",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning
+ );
+
+ return result == MessageBoxResult.Yes;
+}
+```
+
+## 注意事项
+
+1. **不要在构造函数中设置 Owner**:
+ ```csharp
+ // ❌ 错误
+ public MyDialog()
+ {
+ InitializeComponent();
+ this.Owner = Application.Current.MainWindow; // 可能抛出异常
+ }
+
+ // ✅ 正确:在外部设置
+ var dialog = new MyDialog();
+ DialogHelper.SetOwnerSafely(dialog);
+ ```
+
+2. **非模态对话框也需要设置 Owner**:
+ ```csharp
+ // 即使是 Show() 而非 ShowDialog(),也需要设置 Owner
+ var dialog = new MyDialog();
+ DialogHelper.SetOwnerSafely(dialog); // 确保正确置顶
+ dialog.Show();
+ ```
+
+3. **XAML 中不要设置 Owner**:
+ ```xml
+
+
+
+
+
+ ```
diff --git a/.agents/skills/project-tools/utils/GeometryHelper.md b/.agents/skills/project-tools/utils/GeometryHelper.md
new file mode 100644
index 0000000..9795d82
--- /dev/null
+++ b/.agents/skills/project-tools/utils/GeometryHelper.md
@@ -0,0 +1,134 @@
+# GeometryHelper 使用指南
+
+## 文件位置
+
+`src/Utils/GeometryHelper.cs`
+
+## 用途
+
+3D 几何计算工具类,提供距离、角度、向量等常用几何计算。**禁止自己实现几何计算**,必须复用此类。
+
+## 核心方法
+
+### 距离计算
+
+```csharp
+// 两点间距离
+Point3D a = new Point3D(0, 0, 0);
+Point3D b = new Point3D(3, 4, 0);
+double distance = GeometryHelper.Distance(a, b); // 5.0
+
+// 2D距离(忽略Z坐标)
+double distance2D = GeometryHelper.Distance2D(a, b);
+
+// 距离的平方(性能优化,避免开方)
+double distanceSq = GeometryHelper.DistanceSquared(a, b);
+```
+
+### 向量运算
+
+```csharp
+// 向量减法(b - a)
+Vector3D vector = GeometryHelper.Subtract(b, a);
+
+// 向量长度
+double length = GeometryHelper.VectorLength(vector);
+
+// 单位向量
+Vector3D normalized = GeometryHelper.Normalize(vector);
+
+// 点积
+double dot = GeometryHelper.DotProduct(v1, v2);
+
+// 叉积
+Vector3D cross = GeometryHelper.CrossProduct(v1, v2);
+```
+
+### 角度计算
+
+```csharp
+// 向量夹角(弧度)
+double angle = GeometryHelper.AngleBetween(v1, v2);
+
+// 向量夹角(角度)
+double angleDegrees = GeometryHelper.AngleBetweenDegrees(v1, v2);
+
+// 三点形成的角度(以b为顶点)
+double angle = GeometryHelper.AngleAtVertex(a, b, c);
+```
+
+### 点与线的关系
+
+```csharp
+// 点到线段的最近点
+Point3D closestPoint = GeometryHelper.ProjectPointOnLineSegment(
+ point, lineStart, lineEnd);
+
+// 点到线段的距离
+double distance = GeometryHelper.DistancePointToLineSegment(
+ point, lineStart, lineEnd);
+
+// 点是否在线段上(含容差)
+bool isOnLine = GeometryHelper.IsPointOnLineSegment(
+ point, lineStart, lineEnd, tolerance: 0.001);
+```
+
+## 使用示例
+
+### 示例1:计算路径总长度
+
+```csharp
+// ❌ 错误:自己实现距离计算
+double dx = p2.X - p1.X;
+double dy = p2.Y - p1.Y;
+double dz = p2.Z - p1.Z;
+double distance = Math.Sqrt(dx*dx + dy*dy + dz*dz);
+
+// ✅ 正确:使用 GeometryHelper
+double distance = GeometryHelper.Distance(p1, p2);
+```
+
+### 示例2:检查三点是否共线
+
+```csharp
+public bool AreCollinear(Point3D a, Point3D b, Point3D c, double tolerance = 0.001)
+{
+ // 计算叉积,如果为0则共线
+ Vector3D ab = GeometryHelper.Subtract(b, a);
+ Vector3D ac = GeometryHelper.Subtract(c, a);
+ Vector3D cross = GeometryHelper.CrossProduct(ab, ac);
+
+ return GeometryHelper.VectorLength(cross) < tolerance;
+}
+```
+
+### 示例3:计算转弯角度
+
+```csharp
+public double CalculateTurnAngle(Point3D prev, Point3D current, Point3D next)
+{
+ // 入向量(从prev到current)
+ Vector3D v1 = GeometryHelper.Subtract(current, prev);
+
+ // 出向量(从current到next)
+ Vector3D v2 = GeometryHelper.Subtract(next, current);
+
+ // 计算夹角
+ double angle = GeometryHelper.AngleBetweenDegrees(v1, v2);
+
+ return angle; // 0-180度
+}
+```
+
+## 性能提示
+
+```csharp
+// 大量距离比较时,使用距离的平方避免开方
+// ✅ 更快
+double distSq = GeometryHelper.DistanceSquared(a, b);
+if (distSq < threshold * threshold)
+
+// ❌ 更慢(需要开方)
+double dist = GeometryHelper.Distance(a, b);
+if (dist < threshold)
+```
diff --git a/.agents/skills/project-tools/utils/LogManager.md b/.agents/skills/project-tools/utils/LogManager.md
new file mode 100644
index 0000000..acd28de
--- /dev/null
+++ b/.agents/skills/project-tools/utils/LogManager.md
@@ -0,0 +1,144 @@
+# LogManager 使用指南
+
+## 文件位置
+
+`src/Utils/LogManager.cs`
+
+## 用途
+
+统一的日志记录管理器。在 Navisworks 插件环境中,`Console.WriteLine` 不可见,必须使用 LogManager。
+
+## 日志级别
+
+```csharp
+LogManager.Debug("调试信息"); // 开发调试使用
+LogManager.Info("普通信息"); // 一般操作记录
+LogManager.Warning("警告信息"); // 需要注意但非错误
+LogManager.Error("错误信息"); // 错误(可选异常参数)
+LogManager.Fatal("致命错误"); // 严重错误
+```
+
+## 核心方法
+
+### 基本信息日志
+
+```csharp
+LogManager.Info("路径创建成功");
+LogManager.Info($"路径名称: {route.Name}, 长度: {route.TotalLength}");
+```
+
+### 带异常的错误日志
+
+```csharp
+try
+{
+ // 某些操作
+}
+catch (Exception ex)
+{
+ // ✅ 正确:记录异常详情
+ LogManager.Error($"操作失败: {ex.Message}", ex);
+}
+```
+
+### 调试日志(开发时)
+
+```csharp
+LogManager.Debug($"当前路径点数: {path.Points.Count}");
+LogManager.Debug($"网格大小: {grid.Width} x {grid.Height}");
+```
+
+## 使用示例
+
+### 示例1:记录操作开始和完成
+
+```csharp
+public void CreatePath()
+{
+ LogManager.Info("=== 开始创建路径 ===");
+
+ try
+ {
+ // 执行操作
+ var route = GeneratePath();
+
+ LogManager.Info($"路径创建成功: {route.Name}, ID: {route.Id}");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"路径创建失败: {ex.Message}", ex);
+ throw; // 重新抛出或处理
+ }
+
+ LogManager.Info("=== 创建路径完成 ===");
+}
+```
+
+### 示例2:记录重要状态变更
+
+```csharp
+public void SetStartPoint(Point3D point)
+{
+ _startPoint = point;
+
+ LogManager.Info($"起点已设置: ({point.X:F2}, {point.Y:F2}, {point.Z:F2})");
+
+ if (_endPoint != null)
+ {
+ double distance = GeometryHelper.Distance(point, _endPoint);
+ LogManager.Info($"起点终点距离: {distance:F2} 米");
+ }
+}
+```
+
+## 日志文件位置
+
+日志文件存储在:
+```
+C:\ProgramData\Autodesk\Navisworks Manage 2026\plugins\TransportPlugin\logs\
+```
+
+文件命名:
+- `debug.log` - 当前日志
+- `debug.log.1`, `debug.log.2` - 历史日志(自动轮转)
+
+## 注意事项
+
+1. **永远不要使用 Console.WriteLine**:
+ ```csharp
+ // ❌ 错误:在Navisworks中不可见
+ Console.WriteLine("Error occurred");
+
+ // ✅ 正确:写入日志文件
+ LogManager.Error("Error occurred");
+ ```
+
+2. **错误日志要包含异常**:
+ ```csharp
+ // ❌ 不够详细
+ LogManager.Error($"失败: {ex.Message}");
+
+ // ✅ 包含堆栈信息
+ LogManager.Error($"失败: {ex.Message}", ex);
+ ```
+
+3. **避免在循环中大量记录**:
+ ```csharp
+ // ❌ 可能导致日志文件过大
+ foreach (var point in points)
+ {
+ LogManager.Debug($"处理点: {point.Name}"); // 1000个点=1000条日志
+ }
+
+ // ✅ 只记录摘要
+ LogManager.Debug($"开始处理 {points.Count} 个点...");
+ // ... 处理 ...
+ LogManager.Debug($"点处理完成");
+ ```
+
+4. **使用类别前缀便于过滤**:
+ ```csharp
+ LogManager.Info("[路径规划] 开始生成");
+ LogManager.Info("[碰撞检测] 发现3个碰撞");
+ LogManager.Info("[渲染] 路径显示完成");
+ ```
diff --git a/.agents/skills/project-tools/utils/UnitsConverter.md b/.agents/skills/project-tools/utils/UnitsConverter.md
new file mode 100644
index 0000000..0027e11
--- /dev/null
+++ b/.agents/skills/project-tools/utils/UnitsConverter.md
@@ -0,0 +1,126 @@
+# UnitsConverter 使用指南
+
+## 文件位置
+
+`src/Utils/UnitsConverter.cs`
+
+## 用途
+
+处理模型单位与米制单位之间的转换。Navisworks 文档可以使用不同的单位(米、毫米、英尺等),必须通过此工具类进行转换。
+
+## 命名规范(极其重要)
+
+| 单位类型 | 命名规则 | 示例 |
+|---------|---------|------|
+| **米单位** | 变量名必须以 `InMeters` 结尾 | `lengthInMeters`, `heightInMeters` |
+| **模型单位** | 变量名**无后缀** | `length`, `height`, `cellSize` |
+
+```csharp
+// ✅ 正确
+public void SetSize(double lengthInMeters)
+{
+ double factor = UnitsConverter.GetMetersToUnitsConversionFactor(...);
+ double length = lengthInMeters * factor; // 模型单位,无后缀
+ boundingBox = new BoundingBox3D(0, 0, 0, length, width, height);
+}
+
+// ❌ 错误
+public void SetSize(double length) // 无法区分是米还是模型单位!
+{
+ double scale = length / baseSize; // 单位不明确!
+}
+```
+
+## 核心方法
+
+### 获取转换因子
+
+```csharp
+// 获取模型单位到米的转换因子(模型单位 × factor = 米)
+double factor = UnitsConverter.GetUnitsToMetersConversionFactor();
+
+// 获取米到模型单位的转换因子(米 × factor = 模型单位)
+double factor = UnitsConverter.GetMetersToUnitsConversionFactor();
+```
+
+### 直接转换值
+
+```csharp
+// 模型单位 → 米
+double meters = UnitsConverter.ConvertToMeters(distanceInModelUnits);
+
+// 米 → 模型单位
+double modelUnits = UnitsConverter.ConvertFromMeters(distanceInMeters);
+```
+
+### 带单位的转换(从文档)
+
+```csharp
+// 从当前文档获取单位信息
+Units units = Application.ActiveDocument.Units;
+
+// 使用特定单位进行转换
+double meters = UnitsConverter.ConvertToMeters(distance, units);
+double modelUnits = UnitsConverter.ConvertFromMeters(distanceInMeters, units);
+```
+
+## 使用示例
+
+### 示例1:读取配置并转换为模型单位
+
+```csharp
+// 配置中存储的是米
+double cellSizeInMeters = ConfigManager.Instance.Current.PathEditing.CellSizeMeters;
+
+// 转换为模型单位用于计算
+double cellSize = UnitsConverter.ConvertFromMeters(cellSizeInMeters);
+```
+
+### 示例2:显示长度给用户
+
+```csharp
+// 路径长度是模型单位
+double totalLength = route.TotalLength;
+
+// 转换为米显示
+double lengthInMeters = UnitsConverter.ConvertToMeters(totalLength);
+string displayText = $"总长度: {lengthInMeters:F2} 米";
+```
+
+### 示例3:完整的函数参数处理
+
+```csharp
+///
+/// 生成网格地图
+///
+/// 网格单元大小(米)
+public GridMap Generate(double cellSizeInMeters)
+{
+ // 转换为模型单位
+ double cellSize = UnitsConverter.ConvertFromMeters(cellSizeInMeters);
+
+ // 后续计算使用 cellSize(模型单位)
+ int gridX = (int)(bounds.Width / cellSize);
+ int gridY = (int)(bounds.Height / cellSize);
+
+ // ...
+}
+```
+
+## 常见错误
+
+```csharp
+// ❌ 错误1:硬编码转换因子
+private const double MM_TO_METERS = 0.001; // 危险!不同文档单位不同
+double meters = modelValue * MM_TO_METERS; // 只在毫米文档正确
+
+// ❌ 错误2:混淆命名
+double cellSize = 0.5; // 这是米还是模型单位?
+if (height > cellSize) // 单位不匹配的严重bug!
+
+// ❌ 错误3:混用不同文档的单位
+Units doc1Units = doc1.Units;
+Units doc2Units = doc2.Units;
+double value1 = ConvertToMeters(modelValue, doc1Units);
+double value2 = ConvertToMeters(modelValue, doc2Units); // 可能不同!
+```
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 722e971..a9142f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1778,6 +1778,98 @@ var stats = gridMap.GetHeightCalculationStats();
---
+## [0.1.4] - 2025-06-18
+
+### 修复
+
+- **点击监听问题**: 修复鼠标点击没有效果的问题
+ - 添加了点击状态跟踪机制,避免重复处理同一选择
+ - 增加最小点击间隔(500ms)防止误触
+ - 优化定时器间隔从100ms增加到200ms,减少资源消耗
+ - 在处理完点击后自动清除选择,为下次点击做准备
+- **通道高亮显示**: 修复通道自动高亮显示丢失的问题
+ - 在进入路径编辑模式前先清除之前的高亮
+ - 改用更鲜艳的LimeGreen颜色提高可见性
+ - 添加详细的高亮状态日志和错误处理
+ - 增强高亮结果验证机制
+- **重复路径点**: 修复点击3次却产生81条记录的问题
+ - 实现选择去重机制,相同选择在时间间隔内不重复处理
+ - 添加ModelItem对比逻辑
+ - 优化定时器触发频率
+- **点选详情显示**: 实现路径点列表中点选时的详情显示
+ - 在状态栏显示选中点的完整信息(名称、类型、坐标、创建时间)
+ - 修复PointsListBox_SelectedIndexChanged事件处理
+ - 添加点选择日志记录
+ - 实现地图中选中点的高亮重绘
+
+### 技术改进
+
+- **状态管理**: 增强点击状态跟踪和管理
+ - 添加_lastProcessedItem和_lastProcessedTime字段
+ - 实现MinClickInterval常量控制最小点击间隔
+- **日志系统**: 改进调试日志的详细程度
+ - 点击监听过程的详细日志
+ - 通道高亮过程的状态跟踪
+ - 异常堆栈信息记录
+- **用户体验**: 优化交互反馈
+ - 改进状态消息显示选中通道数量
+ - 在状态栏显示详细的点信息
+ - 清除无效选择的自动处理
+
+## 版本 [0.1.3] - 2025-06-18
+
+### 修复
+
+- **兼容性修复**: 修复C# 7.3兼容性问题,移除了nullable引用类型语法
+- **API兼容性**: 修复Navisworks 2017 API兼容性问题
+ - 替换了不存在的`View.RequestSavedViewpoint()`方法
+ - 使用`Model.RootItem.BoundingBox()`替代不存在的`Model.BoundingBox()`
+ - 实现了相机位置估算算法作为API限制的替代方案
+- **方法签名优化**: 将nullable返回类型改为out参数模式以提高兼容性
+ - `GetPreciseClickPoint()` 方法
+ - `GetRaycastClickPoint()` 方法
+ - `GetSurfacePointFromGeometry()` 方法
+ - `GetCameraPosition()` 方法
+- **射线投射算法**: 完善了射线与包围盒交点计算的错误处理
+
+### 技术改进
+
+- **错误处理**: 增强了所有射线投射相关方法的错误处理和日志记录
+- **后备方案**: 在精确坐标获取失败时提供包围盒中心点作为后备方案
+- **日志优化**: 改进了调试日志的详细程度和格式
+
+## 技术债务和改进计划
+
+### 当前已知问题
+
+- 暂无已知的功能性问题
+
+### 已完成功能 ✅
+
+1. ✅ **TimeLiner 集成**:已实现完整的 TimeLiner 集成功能(v0.1.9)
+ - 运输任务自动创建和管理
+ - 动画状态实时同步
+ - 完善的错误处理和降级机制
+
+### 下一步开发计划
+
+1. **路径规划功能**:实现 A* 算法的 3D 路径规划
+2. **动态碰撞检测增强**:进一步集成 Clash Detective 功能
+3. **动画和可视化优化**:添加更多路径动画效果和碰撞高亮显示
+4. **DELMIA导出**:实现结构化数据导出功能
+5. **TimeLiner 高级功能**:
+ - 支持任务时间线编辑
+ - 批量任务管理
+ - 任务导入导出功能
+
+### 性能优化目标
+
+- 大型模型的处理性能优化
+- 异步操作的用户界面响应性改进
+- 内存使用优化和资源清理机制完善
+
+---
+
## 版本 [0.1.2] - 2025-06-17
### 增加分层可见性控制功能
@@ -1844,95 +1936,3 @@ var stats = gridMap.GetHeightCalculationStats();
- ✅ 8种物流元素类型的按钮功能正常,属性设置准确
- ✅ COM API调用稳定,无内存泄漏或崩溃问题
- ✅ 编译通过,插件在Navisworks 2017中正常加载和运行
-
----
-
-## 版本 [0.1.3] - 2024-01-XX
-
-### 修复
-
-- **兼容性修复**: 修复C# 7.3兼容性问题,移除了nullable引用类型语法
-- **API兼容性**: 修复Navisworks 2017 API兼容性问题
- - 替换了不存在的`View.RequestSavedViewpoint()`方法
- - 使用`Model.RootItem.BoundingBox()`替代不存在的`Model.BoundingBox()`
- - 实现了相机位置估算算法作为API限制的替代方案
-- **方法签名优化**: 将nullable返回类型改为out参数模式以提高兼容性
- - `GetPreciseClickPoint()` 方法
- - `GetRaycastClickPoint()` 方法
- - `GetSurfacePointFromGeometry()` 方法
- - `GetCameraPosition()` 方法
-- **射线投射算法**: 完善了射线与包围盒交点计算的错误处理
-
-### 技术改进
-
-- **错误处理**: 增强了所有射线投射相关方法的错误处理和日志记录
-- **后备方案**: 在精确坐标获取失败时提供包围盒中心点作为后备方案
-- **日志优化**: 改进了调试日志的详细程度和格式
-
-## 技术债务和改进计划
-
-### 当前已知问题
-
-- 暂无已知的功能性问题
-
-### 已完成功能 ✅
-
-1. ✅ **TimeLiner 集成**:已实现完整的 TimeLiner 集成功能(v0.1.9)
- - 运输任务自动创建和管理
- - 动画状态实时同步
- - 完善的错误处理和降级机制
-
-### 下一步开发计划
-
-1. **路径规划功能**:实现 A* 算法的 3D 路径规划
-2. **动态碰撞检测增强**:进一步集成 Clash Detective 功能
-3. **动画和可视化优化**:添加更多路径动画效果和碰撞高亮显示
-4. **DELMIA导出**:实现结构化数据导出功能
-5. **TimeLiner 高级功能**:
- - 支持任务时间线编辑
- - 批量任务管理
- - 任务导入导出功能
-
-### 性能优化目标
-
-- 大型模型的处理性能优化
-- 异步操作的用户界面响应性改进
-- 内存使用优化和资源清理机制完善
-
-## [0.1.4] - 2024-01-XX
-
-### 修复
-
-- **点击监听问题**: 修复鼠标点击没有效果的问题
- - 添加了点击状态跟踪机制,避免重复处理同一选择
- - 增加最小点击间隔(500ms)防止误触
- - 优化定时器间隔从100ms增加到200ms,减少资源消耗
- - 在处理完点击后自动清除选择,为下次点击做准备
-- **通道高亮显示**: 修复通道自动高亮显示丢失的问题
- - 在进入路径编辑模式前先清除之前的高亮
- - 改用更鲜艳的LimeGreen颜色提高可见性
- - 添加详细的高亮状态日志和错误处理
- - 增强高亮结果验证机制
-- **重复路径点**: 修复点击3次却产生81条记录的问题
- - 实现选择去重机制,相同选择在时间间隔内不重复处理
- - 添加ModelItem对比逻辑
- - 优化定时器触发频率
-- **点选详情显示**: 实现路径点列表中点选时的详情显示
- - 在状态栏显示选中点的完整信息(名称、类型、坐标、创建时间)
- - 修复PointsListBox_SelectedIndexChanged事件处理
- - 添加点选择日志记录
- - 实现地图中选中点的高亮重绘
-
-### 技术改进
-
-- **状态管理**: 增强点击状态跟踪和管理
- - 添加_lastProcessedItem和_lastProcessedTime字段
- - 实现MinClickInterval常量控制最小点击间隔
-- **日志系统**: 改进调试日志的详细程度
- - 点击监听过程的详细日志
- - 通道高亮过程的状态跟踪
- - 异常堆栈信息记录
-- **用户体验**: 优化交互反馈
- - 改进状态消息显示选中通道数量
- - 在状态栏显示详细的点信息
- - 清除无效选择的自动处理
diff --git a/TransportPlugin.csproj b/TransportPlugin.csproj
index dd98d2b..91df4ae 100644
--- a/TransportPlugin.csproj
+++ b/TransportPlugin.csproj
@@ -205,6 +205,7 @@
+
LogisticsControlPanel.xaml
@@ -295,6 +296,7 @@
+
@@ -306,6 +308,7 @@
+
diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md
index 2b59250..48d87e9 100644
--- a/doc/requirement/todo_features.md
+++ b/doc/requirement/todo_features.md
@@ -2,6 +2,11 @@
## 功能点
+### [2026/2/24]
+
+1. [x] (优化)吊装路径支持
+2. [x] (功能)
+
### [2026/2/14]
1. [x] (优化)实现完整的路径分析功能
diff --git a/doc/working/multi_level_hoisting_path.md b/doc/working/multi_level_hoisting_path.md
new file mode 100644
index 0000000..48d0bc8
--- /dev/null
+++ b/doc/working/multi_level_hoisting_path.md
@@ -0,0 +1,188 @@
+# 多层阶梯式吊装路径开发方案
+
+## 概述
+
+本文档描述多层阶梯式吊装路径(Multi-Level Stepped Hoisting Path)的实现方案。该功能支持在多个水平高度进行正交移动,适用于车间桁车等复杂吊装场景。
+
+### 术语定义
+
+- **单层吊装路径**:传统的"起吊 → 平移 → 下降"三段式路径
+- **多层阶梯式吊装路径**:支持在多个高度层进行"提升/下降 → 平移"的阶梯式路径
+- **正交路径**:运动严格按 X/Y/Z 轴方向,每次只改变一个维度
+
+## 需求规格
+
+### 功能需求
+
+1. 支持在多个高度层进行水平移动
+2. 支持上升和下降过程(可混合)
+3. 每个层级可设置不同的水平目标位置
+4. 保持与现有单层吊装路径的向后兼容
+
+### 运动轨迹示例
+
+```
+单层吊装:
+地面起吊 → 提升高度H → 水平移动到终点 → 下降落地
+ ↑______________↓
+
+多层吊装(2层提升+1层下降):
+地面起吊 → 提升H1 → 水平移动到P1 →
+ 提升H2 → 水平移动到P2 →
+ 下降H3 → 水平移动到P3 →
+ 下降落地
+ ↑______↑______↓______↓
+```
+
+## 架构设计
+
+### 数据模型
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ AerialPathParameters │
+├─────────────────────────────────────────────────────────────┤
+│ PathType: PathType.Hoisting │
+│ StartPoint: Point3D ← 起吊点(地面) │
+│ EndPoint: Point3D ← 落点(地面) │
+│ LiftHeightMeters: double ← 单层模式使用(保留) │
+│ Mode: HoistingMode ← SingleLevel/MultiLevel │
+│ Levels: List ← 多层模式层级列表 │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ HoistingLevel │
+├─────────────────────────────────────────────────────────────┤
+│ Index: int ← 层级序号 │
+│ HeightInMeters: double ← 相对起吊点的高度 │
+│ HorizontalTarget: Point3D ← 水平目标位置(XY) │
+│ IsRise: bool ← true=提升, false=下降 │
+│ Description: string ← 层级描述 │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 兼容性设计
+
+| 组件 | 兼容性策略 |
+|------|-----------|
+| `PathRoute.Points` | `List` 天然支持可变数量,无需修改 |
+| `PathType.Hoisting` | 继续使用现有枚举值,无需新增 |
+| XML序列化 | 按属性序列化,点数不影响格式 |
+| 数据库存储 | 路径点表结构不变,仅插入行数变化 |
+| `AerialHeightDialog` | 保留现有对话框,单层模式继续使用 |
+
+## 实现计划
+
+### 阶段1:核心数据结构
+
+**目标**:创建数据模型,扩展参数类,实现多层路径生成器
+
+**涉及文件**:
+1. `src/Core/PathPlanningModels.cs` - 添加 `HoistingLevel` 类
+2. `src/Commands/CreateAerialPathCommand.cs` - 扩展参数和模式枚举
+3. `src/PathPlanning/AerialPathGenerator.cs` - 添加多层生成方法
+
+**实现步骤**:
+1. 在 `PathPlanningModels.cs` 中添加 `HoistingLevel` 类和 `HoistingMode` 枚举
+2. 在 `CreateAerialPathCommand.cs` 中扩展 `AerialPathParameters`
+3. 在 `AerialPathGenerator.cs` 中实现 `GenerateMultiLevelHoistingPath` 方法
+4. 修改 `CreateAerialPathCommand.ExecuteInternalAsync` 支持两种模式
+
+**向后兼容保证**:
+- 现有 `GenerateThreeStepHoistingPath` 方法保持不变
+- `AerialPathParameters.LiftHeightMeters` 继续存在
+- 新增 `Mode` 属性默认值为 `SingleLevel`
+
+### 阶段2:UI对话框
+
+**目标**:创建多层吊装路径输入对话框
+
+**涉及文件**:
+1. `src/UI/WPF/Views/MultiLevelHoistingDialog.xaml` - 新建
+2. `src/UI/WPF/Views/MultiLevelHoistingDialog.xaml.cs` - 新建
+
+**功能需求**:
+- 显示层级列表(可添加/删除)
+- 每个层级设置:高度、方向(提升/下降)、描述
+- 水平目标位置通过3D点击获取
+- 实时预览路径点数
+
+### 阶段3:ViewModel集成
+
+**目标**:在 PathEditingViewModel 中集成多层模式
+
+**涉及文件**:
+1. `src/UI/WPF/ViewModels/PathEditingViewModel.cs`
+
+**功能需求**:
+- 添加模式选择命令
+- 集成多层对话框
+- 支持在创建流程中动态添加层级
+
+## API规范
+
+### HoistingLevel 类
+
+```csharp
+public class HoistingLevel
+{
+ public int Index { get; set; }
+ public double HeightInMeters { get; set; }
+ public Point3D HorizontalTarget { get; set; }
+ public bool IsRise { get; set; }
+ public string Description { get; set; }
+}
+```
+
+### HoistingMode 枚举
+
+```csharp
+public enum HoistingMode
+{
+ SingleLevel, // 单层(默认,向后兼容)
+ MultiLevel // 多层阶梯式
+}
+```
+
+### AerialPathParameters 扩展
+
+```csharp
+public class AerialPathParameters
+{
+ // 现有字段保留...
+ public HoistingMode Mode { get; set; } = HoistingMode.SingleLevel;
+ public List Levels { get; set; }
+}
+```
+
+### AerialPathGenerator 新方法
+
+```csharp
+public List GenerateMultiLevelHoistingPath(
+ Point3D startPoint,
+ Point3D endPoint,
+ List levels)
+```
+
+## 测试要点
+
+1. **向后兼容测试**:单层模式路径生成与之前完全一致
+2. **多层路径测试**:2层、3层、混合升降路径生成正确
+3. **数据序列化测试**:多层路径保存/加载正确
+4. **渲染测试**:多层路径在3D视图中正确显示
+5. **动画测试**:动画沿多层路径正确插值
+
+## 风险分析
+
+| 风险 | 可能性 | 影响 | 缓解措施 |
+|------|-------|------|---------|
+| 现有单层功能被破坏 | 低 | 高 | 保留原方法,添加新实现,充分测试 |
+| 路径点过多影响性能 | 中 | 中 | 限制最大层级数(如10层) |
+| 数据库兼容性问题 | 低 | 高 | 验证现有表结构支持可变点数 |
+
+## 版本记录
+
+| 版本 | 日期 | 描述 |
+|------|------|------|
+| 1.0 | 2026-02-24 | 初始方案 |
diff --git a/src/Commands/CreateAerialPathCommand.cs b/src/Commands/CreateAerialPathCommand.cs
index f02d368..d26df2a 100644
--- a/src/Commands/CreateAerialPathCommand.cs
+++ b/src/Commands/CreateAerialPathCommand.cs
@@ -49,14 +49,35 @@ namespace NavisworksTransport.Commands
if (_parameters.PathType == PathType.Hoisting)
{
- // 吊装:使用 AerialPathGenerator 生成4个路径点
var generator = new AerialPathGenerator();
- pathPoints = generator.GenerateThreeStepHoistingPath(
- _parameters.StartPoint,
- _parameters.EndPoint,
- _parameters.LiftHeightMeters);
- LogManager.Info($"吊装路径: 起点=({_parameters.StartPoint.X:F2},{_parameters.StartPoint.Y:F2},{_parameters.StartPoint.Z:F2}), 终点=({_parameters.EndPoint.X:F2},{_parameters.EndPoint.Y:F2},{_parameters.EndPoint.Z:F2}), 提升高度={_parameters.LiftHeightMeters:F2}米");
+ if (_parameters.HoistingMode == HoistingMode.MultiLevel &&
+ _parameters.HoistingLevels != null &&
+ _parameters.HoistingLevels.Count > 0)
+ {
+ // 多层阶梯式吊装
+ pathPoints = generator.GenerateMultiLevelHoistingPath(
+ _parameters.StartPoint,
+ _parameters.EndPoint,
+ _parameters.HoistingLevels);
+
+ LogManager.Info($"多层吊装路径: 起点=({_parameters.StartPoint.X:F2},{_parameters.StartPoint.Y:F2},{_parameters.StartPoint.Z:F2}), " +
+ $"终点=({_parameters.EndPoint.X:F2},{_parameters.EndPoint.Y:F2},{_parameters.EndPoint.Z:F2}), " +
+ $"层级数={_parameters.HoistingLevels.Count}, 总点数={pathPoints.Count}");
+ }
+ else
+ {
+ // 单层吊装(传统模式,默认)
+ double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(_parameters.LiftHeightMeters);
+ pathPoints = generator.GenerateThreeStepHoistingPath(
+ _parameters.StartPoint,
+ _parameters.EndPoint,
+ liftHeightModelUnits);
+
+ LogManager.Info($"单层吊装路径: 起点=({_parameters.StartPoint.X:F2},{_parameters.StartPoint.Y:F2},{_parameters.StartPoint.Z:F2}), " +
+ $"终点=({_parameters.EndPoint.X:F2},{_parameters.EndPoint.Y:F2},{_parameters.EndPoint.Z:F2}), " +
+ $"提升高度={_parameters.LiftHeightMeters:F2}米");
+ }
}
else
{
@@ -119,8 +140,25 @@ namespace NavisworksTransport.Commands
if (_parameters.EndPoint == null)
return PathPlanningResult.ValidationFailure("终点不能为空");
- if (_parameters.LiftHeightMeters <= 0)
- return PathPlanningResult.ValidationFailure("提升高度必须大于0");
+ // 单层模式:检查 LiftHeightMeters
+ // 多层模式:检查 HoistingLevels
+ if (_parameters.HoistingMode == HoistingMode.SingleLevel)
+ {
+ if (_parameters.LiftHeightMeters <= 0)
+ return PathPlanningResult.ValidationFailure("提升高度必须大于0");
+ }
+ else if (_parameters.HoistingMode == HoistingMode.MultiLevel)
+ {
+ if (_parameters.HoistingLevels == null || _parameters.HoistingLevels.Count == 0)
+ return PathPlanningResult.ValidationFailure("请至少设置一个吊装层级");
+
+ // 检查每个层级的高度
+ foreach (var level in _parameters.HoistingLevels)
+ {
+ if (level.HeightInMeters <= 0)
+ return PathPlanningResult.ValidationFailure($"层级{level.Index + 1}的高度必须大于0");
+ }
+ }
}
else if (_parameters.PathType == PathType.Rail)
{
@@ -180,6 +218,7 @@ namespace NavisworksTransport.Commands
///
/// 吊装参数:提升高度(米)
+ /// 单层吊装模式使用,多层模式下该值被忽略
///
public double LiftHeightMeters { get; set; }
@@ -187,5 +226,25 @@ namespace NavisworksTransport.Commands
/// 空轨参数:点击点列表
///
public List ClickedPoints { get; set; }
+
+ ///
+ /// 吊装路径模式(默认单层,向后兼容)
+ ///
+ public HoistingMode HoistingMode { get; set; } = HoistingMode.SingleLevel;
+
+ ///
+ /// 多层吊装:层级信息列表(仅多层模式使用)
+ ///
+ public List HoistingLevels { get; set; }
+
+ ///
+ /// 默认构造函数
+ ///
+ public AerialPathParameters()
+ {
+ HoistingMode = HoistingMode.SingleLevel;
+ HoistingLevels = new List();
+ ClickedPoints = new List();
+ }
}
}
diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs
index 72dcfaa..b197b70 100644
--- a/src/Core/PathPlanningManager.cs
+++ b/src/Core/PathPlanningManager.cs
@@ -3980,8 +3980,10 @@ namespace NavisworksTransport
// 2. 重置为无活动工具状态
try
{
- // 使用Tool.None重置到无活动工具状态,让Navisworks处理默认导航
- Application.MainDocument.Tool.Value = Tool.None;
+ // 使用Tool.Select切换到选择工具,确保完全退出ToolPlugin模式
+ // Tool.None可能不足以释放ToolPlugin对键盘输入的拦截
+ Application.MainDocument.Tool.Value = Tool.Select;
+ LogManager.Debug("[ToolPlugin] 已重置工具状态为Select");
}
catch (Exception ex)
{
diff --git a/src/Core/PathPlanningModels.cs b/src/Core/PathPlanningModels.cs
index 73c37bb..ec31b81 100644
--- a/src/Core/PathPlanningModels.cs
+++ b/src/Core/PathPlanningModels.cs
@@ -2530,4 +2530,101 @@ namespace NavisworksTransport
public string Version { get; set; } = "1.0";
public string Description { get; set; } = "";
}
+
+ #region 多层阶梯式吊装路径数据模型
+
+ ///
+ /// 吊装路径模式枚举
+ ///
+ public enum HoistingMode
+ {
+ ///
+ /// 单层吊装(传统模式,向后兼容)
+ ///
+ SingleLevel,
+
+ ///
+ /// 多层阶梯式吊装
+ ///
+ MultiLevel
+ }
+
+ ///
+ /// 吊装层级信息 - 表示多层吊装中的一个水平移动层级
+ ///
+ [Serializable]
+ public class HoistingLevel
+ {
+ ///
+ /// 层级序号(从0开始)
+ ///
+ public int Index { get; set; }
+
+ ///
+ /// 该层级的高度(相对于起吊点的垂直高度,米)
+ ///
+ public double HeightInMeters { get; set; }
+
+ ///
+ /// 该层级的水平移动目标点(XY位置,Z坐标会被忽略)
+ ///
+ public Point3D HorizontalTarget { get; set; }
+
+ ///
+ /// 运动方向:true=从上一位置上升到此高度,false=从上一位置下降到此高度
+ ///
+ public bool IsRise { get; set; }
+
+ ///
+ /// 层级描述(如"桁车移动层"、"避让层"等)
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedTime { get; set; }
+
+ ///
+ /// 默认构造函数
+ ///
+ public HoistingLevel()
+ {
+ Index = 0;
+ HeightInMeters = 0.0;
+ HorizontalTarget = new Point3D();
+ IsRise = true;
+ Description = string.Empty;
+ CreatedTime = DateTime.Now;
+ }
+
+ ///
+ /// 带参数构造函数
+ ///
+ /// 层级序号
+ /// 高度(米)
+ /// 水平目标点
+ /// 是否为上升
+ /// 描述
+ public HoistingLevel(int index, double heightInMeters, Point3D horizontalTarget, bool isRise = true, string description = "")
+ {
+ Index = index;
+ HeightInMeters = heightInMeters;
+ HorizontalTarget = horizontalTarget ?? new Point3D();
+ IsRise = isRise;
+ Description = description ?? string.Empty;
+ CreatedTime = DateTime.Now;
+ }
+
+ ///
+ /// 返回层级描述字符串
+ ///
+ public override string ToString()
+ {
+ string direction = IsRise ? "上升" : "下降";
+ return $"层级{Index + 1}: {direction}到{HeightInMeters:F2}米,目标({HorizontalTarget.X:F2}, {HorizontalTarget.Y:F2}) {(string.IsNullOrEmpty(Description) ? "" : $"- {Description}")}";
+ }
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/PathPlanning/AerialPathGenerator.cs b/src/PathPlanning/AerialPathGenerator.cs
index 55823a3..9502613 100644
--- a/src/PathPlanning/AerialPathGenerator.cs
+++ b/src/PathPlanning/AerialPathGenerator.cs
@@ -221,5 +221,99 @@ namespace NavisworksTransport.PathPlanning
return pathPoints;
}
+
+ ///
+ /// 生成多层阶梯式吊装路径
+ /// 支持在多个高度层进行水平移动,可混合上升和下降
+ ///
+ /// 起点(地面起吊位置)
+ /// 终点(地面落点位置)
+ /// 层级信息列表(按顺序执行)
+ /// 路径点列表
+ public List GenerateMultiLevelHoistingPath(
+ Point3D startPoint,
+ Point3D endPoint,
+ List levels)
+ {
+ if (startPoint == null)
+ throw new ArgumentNullException(nameof(startPoint));
+ if (endPoint == null)
+ throw new ArgumentNullException(nameof(endPoint));
+ if (levels == null || levels.Count == 0)
+ throw new ArgumentException("至少需要指定一个层级", nameof(levels));
+
+ var pathPoints = new List();
+
+ // 点1:起吊点(物体初始位置)
+ var startPointPath = new PathPoint(
+ startPoint,
+ "起吊点",
+ PathPointType.StartPoint);
+ startPointPath.Direction = HoistingPointDirection.Vertical;
+ pathPoints.Add(startPointPath);
+
+ Point3D currentPoint = startPoint;
+
+ // 按层级顺序生成路径点
+ for (int i = 0; i < levels.Count; i++)
+ {
+ var level = levels[i];
+ double levelHeightModelUnits = UnitsConverter.ConvertFromMeters(level.HeightInMeters);
+ double currentHeight = startPoint.Z + levelHeightModelUnits;
+
+ // 垂直移动点(上升或下降到该层级高度)
+ var verticalPoint = new Point3D(
+ currentPoint.X,
+ currentPoint.Y,
+ currentHeight);
+
+ string verticalPointName = level.IsRise ? $"提升点{i + 1}" : $"下降点{i + 1}";
+ var verticalPathPoint = new PathPoint(
+ verticalPoint,
+ verticalPointName,
+ PathPointType.WayPoint);
+ verticalPathPoint.Direction = HoistingPointDirection.Vertical;
+ pathPoints.Add(verticalPathPoint);
+
+ // 水平移动点(移动到该层级的目标位置,保持高度)
+ var horizontalPoint = new Point3D(
+ level.HorizontalTarget.X,
+ level.HorizontalTarget.Y,
+ currentHeight);
+
+ string horizontalPointName = $"平移点{i + 1}";
+ var horizontalPathPoint = new PathPoint(
+ horizontalPoint,
+ horizontalPointName,
+ PathPointType.WayPoint);
+ horizontalPathPoint.Direction = HoistingPointDirection.Longitudinal;
+ pathPoints.Add(horizontalPathPoint);
+
+ // 更新当前点为水平移动后的位置
+ currentPoint = horizontalPoint;
+ }
+
+ // 最后:垂直下降到地面终点
+ var descendPoint = new Point3D(
+ currentPoint.X,
+ currentPoint.Y,
+ endPoint.Z);
+ var descendPathPoint = new PathPoint(
+ descendPoint,
+ "最终下降点",
+ PathPointType.WayPoint);
+ descendPathPoint.Direction = HoistingPointDirection.Vertical;
+ pathPoints.Add(descendPathPoint);
+
+ // 点N:落地点(物体最终位置)
+ var endPointPath = new PathPoint(
+ endPoint,
+ "落地点",
+ PathPointType.EndPoint);
+ endPointPath.Direction = HoistingPointDirection.Vertical;
+ pathPoints.Add(endPointPath);
+
+ return pathPoints;
+ }
}
}
\ No newline at end of file
diff --git a/src/UI/WPF/Converters/HoistingConverters.cs b/src/UI/WPF/Converters/HoistingConverters.cs
new file mode 100644
index 0000000..8275e14
--- /dev/null
+++ b/src/UI/WPF/Converters/HoistingConverters.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace NavisworksTransport.UI.WPF.Converters
+{
+ ///
+ /// 索引加1转换器(用于显示序号从1开始)
+ ///
+ public class IndexPlusOneConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is int index)
+ return index + 1;
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ ///
+ /// 方向到文本转换器
+ ///
+ public class DirectionToTextConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return (bool)value ? "上升" : "下降";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ ///
+ /// 方向到颜色转换器
+ ///
+ public class DirectionToColorConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return (bool)value ? new SolidColorBrush(Colors.LightGreen) : new SolidColorBrush(Colors.LightCoral);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ ///
+ /// 目标位置到文本转换器
+ ///
+ public class TargetToTextConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null)
+ return "未设置";
+
+ // 如果值是 Point3D 类型
+ var point = value as Autodesk.Navisworks.Api.Point3D;
+ if (point != null)
+ return $"({point.X:F1}, {point.Y:F1})";
+
+ return value.ToString();
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/UI/WPF/Models/HoistingLevelItem.cs b/src/UI/WPF/Models/HoistingLevelItem.cs
new file mode 100644
index 0000000..c92e7ce
--- /dev/null
+++ b/src/UI/WPF/Models/HoistingLevelItem.cs
@@ -0,0 +1,56 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Autodesk.Navisworks.Api;
+
+namespace NavisworksTransport.UI.WPF.Models
+{
+ ///
+ /// 层级数据项(支持属性变更通知)
+ ///
+ public class HoistingLevelItem : INotifyPropertyChanged
+ {
+ private int _index;
+ private double _heightInMeters;
+ private Point3D _horizontalTarget;
+ private bool _isRise = true;
+ private string _description;
+
+ public int Index
+ {
+ get => _index;
+ set { _index = value; OnPropertyChanged(nameof(Index)); }
+ }
+
+ public double HeightInMeters
+ {
+ get => _heightInMeters;
+ set { _heightInMeters = value; OnPropertyChanged(nameof(HeightInMeters)); }
+ }
+
+ public Point3D HorizontalTarget
+ {
+ get => _horizontalTarget;
+ set { _horizontalTarget = value; OnPropertyChanged(nameof(HorizontalTarget)); }
+ }
+
+ public bool IsRise
+ {
+ get => _isRise;
+ set { _isRise = value; OnPropertyChanged(nameof(IsRise)); }
+ }
+
+ public string Description
+ {
+ get => _description;
+ set { _description = value; OnPropertyChanged(nameof(Description)); }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs
index 2d035bf..669dd1f 100644
--- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs
+++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs
@@ -10,12 +10,15 @@ using System.Linq;
using System.IO;
using Microsoft.Win32;
using Autodesk.Navisworks.Api;
+using Autodesk.Navisworks.Api.Plugins;
using NavisApplication = Autodesk.Navisworks.Api.Application;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Config;
using NavisworksTransport.Commands;
using NavisworksTransport.UI.WPF.Views;
+using NavisworksTransport.UI.WPF.Models;
using NavisworksTransport.Utils;
+using NavisworksTransport;
namespace NavisworksTransport.UI.WPF.ViewModels
{
@@ -102,6 +105,20 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 资源清理管理
private bool _disposed = false;
+ // 多层吊装模式
+ private bool _isMultiLevelHoistingMode = false;
+ private ObservableCollection _multiLevelItems = new ObservableCollection();
+ private Point3D _multiLevelStartPoint;
+ private Point3D _multiLevelEndPoint;
+ private bool _hasMultiLevelStartPoint = false;
+ private bool _hasMultiLevelEndPoint = false;
+ private string _multiLevelStartPointText = "未选择";
+ private string _multiLevelEndPointText = "未选择";
+ private string _multiLevelStatsText = "层级数: 0 | 预估路径点数: 0";
+ private bool _isSelectingMultiLevelStartPoint = false;
+ private bool _isSelectingMultiLevelEndPoint = false;
+ private HoistingLevelItem _currentSelectingLevelTarget;
+
#endregion
#region 公共属性
@@ -334,6 +351,41 @@ namespace NavisworksTransport.UI.WPF.ViewModels
set => SetProperty(ref _autoPathEndPoint, value);
}
+ #region 多层吊装属性
+
+ public bool IsMultiLevelHoistingMode
+ {
+ get => _isMultiLevelHoistingMode;
+ set => SetProperty(ref _isMultiLevelHoistingMode, value);
+ }
+
+ public ObservableCollection MultiLevelItems
+ {
+ get => _multiLevelItems;
+ set => SetProperty(ref _multiLevelItems, value);
+ }
+
+ public string MultiLevelStartPointText
+ {
+ get => _multiLevelStartPointText;
+ set => SetProperty(ref _multiLevelStartPointText, value);
+ }
+
+ public string MultiLevelEndPointText
+ {
+ get => _multiLevelEndPointText;
+ set => SetProperty(ref _multiLevelEndPointText, value);
+ }
+
+ public string MultiLevelStatsText
+ {
+ get => _multiLevelStatsText;
+ set => SetProperty(ref _multiLevelStatsText, value);
+ }
+
+ public bool CanCreateMultiLevelPath => _hasMultiLevelStartPoint && _hasMultiLevelEndPoint && _multiLevelItems.Count > 0;
+
+ #endregion
public double ObjectLength
{
@@ -694,6 +746,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand NewPathCommand { get; private set; }
public ICommand NewRailPathCommand { get; private set; }
public ICommand NewHoistingPathCommand { get; private set; }
+ public ICommand NewMultiLevelHoistingPathCommand { get; private set; }
public ICommand DeletePathCommand { get; private set; }
public ICommand RenamePathCommand { get; private set; }
public ICommand StartEditCommand { get; private set; }
@@ -710,6 +763,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand ImportPathCommand { get; private set; }
public ICommand ExportPathCommand { get; private set; }
public ICommand SaveAsPathCommand { get; private set; }
+
+ // 多层吊装命令
+ public ICommand SelectMultiLevelStartPointCommand { get; private set; }
+ public ICommand SelectMultiLevelEndPointCommand { get; private set; }
+ public ICommand AddMultiLevelCommand { get; private set; }
+ public ICommand DeleteMultiLevelCommand { get; private set; }
+ public ICommand PickMultiLevelTargetCommand { get; private set; }
+ public ICommand CreateMultiLevelPathCommand { get; private set; }
+ public ICommand CancelMultiLevelHoistingCommand { get; private set; }
#endregion
@@ -945,6 +1007,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
NewPathCommand = new RelayCommand(async () => await ExecuteNewPathAsync(), () => CanExecuteNewPath);
NewRailPathCommand = new RelayCommand(async () => await ExecuteNewRailPathAsync(), () => CanExecuteNewRailPath);
NewHoistingPathCommand = new RelayCommand(async () => await ExecuteNewHoistingPathAsync());
+ NewMultiLevelHoistingPathCommand = new RelayCommand(async () => await ExecuteNewMultiLevelHoistingPathAsync());
+ SelectMultiLevelStartPointCommand = new RelayCommand(async () => await ExecuteSelectMultiLevelStartPointAsync());
+ SelectMultiLevelEndPointCommand = new RelayCommand(async () => await ExecuteSelectMultiLevelEndPointAsync());
+ AddMultiLevelCommand = new RelayCommand(() => ExecuteAddMultiLevel());
+ DeleteMultiLevelCommand = new RelayCommand((item) => ExecuteDeleteMultiLevel(item));
+ PickMultiLevelTargetCommand = new RelayCommand((item) => ExecutePickMultiLevelTarget(item));
+ CreateMultiLevelPathCommand = new RelayCommand(async () => await ExecuteCreateMultiLevelPathAsync(), () => CanCreateMultiLevelPath);
+ CancelMultiLevelHoistingCommand = new RelayCommand(() => ExecuteCancelMultiLevelHoisting());
DeletePathCommand = new RelayCommand(async () => await ExecuteDeletePathAsync());
RenamePathCommand = new RelayCommand(async () => await ExecuteRenamePathAsync());
StartEditCommand = new RelayCommand(async () => await ExecuteAddPathPointAsync(), () => CanExecuteStartEdit);
@@ -1208,7 +1278,558 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Error("路径规划管理器未初始化");
}
}, "新建吊装路径");
- } ///
+ }
+
+ #region 多层吊装页签模式实现
+
+ ///
+ /// 执行新建多层阶梯式吊装路径命令 - 启用页签模式
+ ///
+ private async Task ExecuteNewMultiLevelHoistingPathAsync()
+ {
+ await SafeExecuteAsync(() =>
+ {
+ UpdateMainStatus("正在创建多层阶梯式吊装路径...");
+
+ // 清除所有现有路径的可视化显示
+ if (PathPointRenderPlugin.Instance != null)
+ {
+ try
+ {
+ PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door");
+ LogManager.Info("新建多层吊装路径:已清除现有路径可视化显示(保留网格可视化)");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"新建多层吊装路径:清除现有路径可视化失败: {ex.Message}", ex);
+ throw;
+ }
+ }
+
+ if (_pathPlanningManager == null)
+ {
+ UpdateMainStatus("路径规划管理器未初始化");
+ LogManager.Error("路径规划管理器未初始化");
+ return;
+ }
+
+ // 启用多层吊装页签模式
+ IsMultiLevelHoistingMode = true;
+
+ // 清空集合和重置状态
+ MultiLevelItems.Clear();
+ _multiLevelStartPoint = null;
+ _multiLevelEndPoint = null;
+ _hasMultiLevelStartPoint = false;
+ _hasMultiLevelEndPoint = false;
+ MultiLevelStartPointText = "未选择";
+ MultiLevelEndPointText = "未选择";
+ UpdateMultiLevelStats();
+
+ // 重置选择状态
+ _isSelectingMultiLevelStartPoint = false;
+ _isSelectingMultiLevelEndPoint = false;
+ _currentSelectingLevelTarget = null;
+
+ // 清理之前的点击事件订阅
+ PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked;
+
+ UpdateMainStatus("多层吊装模式已启用 - 请设置起点、终点并添加层级");
+ LogManager.Info("[多层吊装] 页签模式已启用");
+
+ }, "新建多层吊装路径");
+ }
+
+ ///
+ /// 执行选择多层吊装起点命令
+ ///
+ private async Task ExecuteSelectMultiLevelStartPointAsync()
+ {
+ await SafeExecuteAsync(() =>
+ {
+ if (_pathPlanningManager == null) return;
+
+ // 清除之前的起点标记
+ if (_hasMultiLevelStartPoint && PathPointRenderPlugin.Instance != null)
+ {
+ var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == "MultiLevelStartPoint");
+ if (existingRoute != null)
+ {
+ PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id);
+ }
+ }
+
+ // 使用自动路径规划相同的模式
+ _pathPlanningManager.DisableMouseHandling();
+
+ // 设置状态
+ _isSelectingMultiLevelStartPoint = true;
+ _isSelectingMultiLevelEndPoint = false;
+ _currentSelectingLevelTarget = null;
+
+ // 订阅点击事件
+ PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked;
+ PathClickToolPlugin.MouseClicked += OnMultiLevelHoistingMouseClicked;
+
+ // 强制重新初始化ToolPlugin以获取鼠标焦点
+ ForceReinitializeToolPlugin(subscribeToEvents: false);
+
+ UpdateMainStatus("请在3D视图中点击选择起点(地面起吊位置)...");
+ LogManager.Info("[多层吊装] 开始选择起点");
+ }, "选择多层吊装起点");
+ }
+
+ ///
+ /// 执行选择多层吊装终点命令
+ ///
+ private async Task ExecuteSelectMultiLevelEndPointAsync()
+ {
+ await SafeExecuteAsync(() =>
+ {
+ if (_pathPlanningManager == null) return;
+
+ // 清除之前的终点标记
+ if (_hasMultiLevelEndPoint && PathPointRenderPlugin.Instance != null)
+ {
+ var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == "MultiLevelEndPoint");
+ if (existingRoute != null)
+ {
+ PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id);
+ }
+ }
+
+ // 使用自动路径规划相同的模式
+ _pathPlanningManager.DisableMouseHandling();
+
+ // 设置状态
+ _isSelectingMultiLevelStartPoint = false;
+ _isSelectingMultiLevelEndPoint = true;
+ _currentSelectingLevelTarget = null;
+
+ // 订阅点击事件
+ PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked;
+ PathClickToolPlugin.MouseClicked += OnMultiLevelHoistingMouseClicked;
+
+ // 强制重新初始化ToolPlugin以获取鼠标焦点
+ ForceReinitializeToolPlugin(subscribeToEvents: false);
+
+ UpdateMainStatus("请在3D视图中点击选择终点(地面落地位置)...");
+ LogManager.Info("[多层吊装] 开始选择终点");
+ }, "选择多层吊装终点");
+ }
+
+ ///
+ /// 执行添加层级命令
+ ///
+ private void ExecuteAddMultiLevel()
+ {
+ var level = new HoistingLevelItem
+ {
+ Index = MultiLevelItems.Count,
+ HeightInMeters = 3.0,
+ Description = $"层级{MultiLevelItems.Count + 1}"
+ };
+ MultiLevelItems.Add(level);
+ UpdateMultiLevelStats();
+
+ LogManager.Info($"[多层吊装] 添加层级{level.Index + 1}, 高度={level.HeightInMeters}m");
+ UpdateMainStatus($"已添加层级{level.Index + 1},请设置目标位置");
+ }
+
+ ///
+ /// 执行删除层级命令
+ ///
+ private void ExecuteDeleteMultiLevel(HoistingLevelItem item)
+ {
+ if (item == null) return;
+
+ // 清除该层级的可视化标记
+ if (PathPointRenderPlugin.Instance != null)
+ {
+ var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == $"MultiLevelLevel{item.Index}");
+ if (existingRoute != null)
+ {
+ PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id);
+ }
+ }
+
+ MultiLevelItems.Remove(item);
+
+ // 重新编号
+ for (int i = 0; i < MultiLevelItems.Count; i++)
+ {
+ MultiLevelItems[i].Index = i;
+ }
+
+ UpdateMultiLevelStats();
+ LogManager.Info($"[多层吊装] 删除层级,剩余{MultiLevelItems.Count}个层级");
+ }
+
+ ///
+ /// 执行拾取层级目标位置命令
+ ///
+ private void ExecutePickMultiLevelTarget(HoistingLevelItem item)
+ {
+ if (item == null || _pathPlanningManager == null) return;
+
+ // 清除之前的标记
+ if (PathPointRenderPlugin.Instance != null)
+ {
+ var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == $"MultiLevelLevel{item.Index}");
+ if (existingRoute != null)
+ {
+ PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id);
+ }
+ }
+
+ // 使用自动路径规划相同的模式
+ _pathPlanningManager.DisableMouseHandling();
+
+ // 设置状态
+ _isSelectingMultiLevelStartPoint = false;
+ _isSelectingMultiLevelEndPoint = false;
+ _currentSelectingLevelTarget = item;
+
+ // 订阅点击事件
+ PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked;
+ PathClickToolPlugin.MouseClicked += OnMultiLevelHoistingMouseClicked;
+
+ // 强制重新初始化ToolPlugin以获取鼠标焦点
+ ForceReinitializeToolPlugin(subscribeToEvents: false);
+
+ UpdateMainStatus($"请在3D视图中点击层级{item.Index + 1}的目标位置...");
+ LogManager.Info($"[多层吊装] 开始选择层级{item.Index + 1}的目标位置");
+ }
+
+ ///
+ /// 执行创建多层吊装路径命令
+ ///
+ private async Task ExecuteCreateMultiLevelPathAsync()
+ {
+ await SafeExecuteAsync(() =>
+ {
+ // 验证输入
+ if (!_hasMultiLevelStartPoint)
+ {
+ MessageBox.Show("请先选择起点", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ if (!_hasMultiLevelEndPoint)
+ {
+ MessageBox.Show("请先选择终点", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ if (MultiLevelItems.Count == 0)
+ {
+ MessageBox.Show("请至少添加一个层级", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ // 验证所有层级
+ foreach (var level in MultiLevelItems)
+ {
+ if (level.HeightInMeters <= 0)
+ {
+ MessageBox.Show($"层级{level.Index + 1}的高度必须大于0", "验证失败",
+ MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ if (level.HorizontalTarget == null)
+ {
+ MessageBox.Show($"请为层级{level.Index + 1}设置目标位置", "验证失败",
+ MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+ }
+
+ // 创建层级列表
+ var levels = new List();
+ foreach (var item in MultiLevelItems)
+ {
+ levels.Add(new HoistingLevel
+ {
+ Index = item.Index,
+ HeightInMeters = item.HeightInMeters,
+ HorizontalTarget = item.HorizontalTarget,
+ IsRise = true,
+ Description = item.Description
+ });
+ }
+
+ LogManager.Info($"[多层吊装] 创建路径:起点({_multiLevelStartPoint.X:F2},{_multiLevelStartPoint.Y:F2},{_multiLevelStartPoint.Z:F2}), " +
+ $"终点({_multiLevelEndPoint.X:F2},{_multiLevelEndPoint.Y:F2},{_multiLevelEndPoint.Z:F2}), 层级数={levels.Count}");
+
+ // 使用命令创建路径
+ var parameters = new AerialPathParameters
+ {
+ PathType = PathType.Hoisting,
+ HoistingMode = HoistingMode.MultiLevel,
+ StartPoint = _multiLevelStartPoint,
+ EndPoint = _multiLevelEndPoint,
+ HoistingLevels = levels
+ };
+
+ var command = new CreateAerialPathCommand(parameters);
+ var result = command.ExecuteAsync().Result;
+
+ if (result.IsSuccess && result is PathPlanningResult typedResult)
+ {
+ var route = typedResult.Data;
+ LogManager.Info($"[多层吊装] 路径创建成功: {route.Name}, 点数: {route.Points.Count}");
+
+ // 创建 ViewModel
+ var newPathViewModel = new PathRouteViewModel
+ {
+ Route = route,
+ IsActive = true,
+ PathType = PathType.Hoisting
+ };
+
+ var pathPoints = CreatePathPointViewModelsFromCoreRoute(route);
+ foreach (var point in pathPoints)
+ {
+ newPathViewModel.Points.Add(point);
+ }
+
+ PathRoutes.Add(newPathViewModel);
+ SelectedPathRoute = newPathViewModel;
+
+ UpdateMainStatus($"✓ 多层吊装路径创建成功: {route.Name} - {levels.Count}个层级, {route.Points.Count}个路径点");
+ OnPropertyChanged(nameof(PathRoutes));
+
+ // 退出多层吊装模式
+ ExecuteCancelMultiLevelHoisting();
+ }
+ else
+ {
+ UpdateMainStatus($"创建失败: {result.Message}");
+ MessageBox.Show($"创建多层吊装路径失败: {result.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }, "创建多层吊装路径");
+ }
+
+ ///
+ /// 执行取消多层吊装命令
+ ///
+ private void ExecuteCancelMultiLevelHoisting()
+ {
+ // 清理事件订阅
+ PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked;
+
+ // 清理可视化标记
+ if (PathPointRenderPlugin.Instance != null)
+ {
+ // 清理起点标记
+ var startRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == "MultiLevelStartPoint");
+ if (startRoute != null)
+ {
+ PathPointRenderPlugin.Instance.RemovePath(startRoute.Route.Id);
+ }
+
+ // 清理终点标记
+ var endRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == "MultiLevelEndPoint");
+ if (endRoute != null)
+ {
+ PathPointRenderPlugin.Instance.RemovePath(endRoute.Route.Id);
+ }
+
+ // 清理层级标记
+ for (int i = 0; i < MultiLevelItems.Count; i++)
+ {
+ var levelRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == $"MultiLevelLevel{i}");
+ if (levelRoute != null)
+ {
+ PathPointRenderPlugin.Instance.RemovePath(levelRoute.Route.Id);
+ }
+ }
+ }
+
+ // 重置状态
+ IsMultiLevelHoistingMode = false;
+ MultiLevelItems.Clear();
+ _multiLevelStartPoint = null;
+ _multiLevelEndPoint = null;
+ _hasMultiLevelStartPoint = false;
+ _hasMultiLevelEndPoint = false;
+ MultiLevelStartPointText = "未选择";
+ MultiLevelEndPointText = "未选择";
+ _isSelectingMultiLevelStartPoint = false;
+ _isSelectingMultiLevelEndPoint = false;
+ _currentSelectingLevelTarget = null;
+
+ // 恢复PathPlanningManager鼠标处理
+ _pathPlanningManager?.EnableMouseHandling();
+ _pathPlanningManager?.StopClickTool();
+
+ UpdateMultiLevelStats();
+ UpdateMainStatus("多层吊装模式已取消");
+ LogManager.Info("[多层吊装] 页签模式已取消");
+ }
+
+ ///
+ /// 更新多层吊装统计信息
+ ///
+ private void UpdateMultiLevelStats()
+ {
+ int levelCount = MultiLevelItems.Count;
+ int pointCount = levelCount > 0 ? 2 + levelCount * 2 + 2 : 0;
+ MultiLevelStatsText = $"层级数: {levelCount} | 预估路径点数: {pointCount}";
+
+ // 通知CanCreateMultiLevelPath变更
+ OnPropertyChanged(nameof(CanCreateMultiLevelPath));
+
+ // 通知命令可执行状态变更
+ System.Windows.Input.CommandManager.InvalidateRequerySuggested();
+ }
+
+ ///
+ /// 多层吊装路径鼠标点击处理
+ ///
+ private void OnMultiLevelHoistingMouseClicked(object sender, PickItemResult e)
+ {
+ if (e?.Point == null) return;
+
+ var point = e.Point;
+
+ if (_isSelectingMultiLevelStartPoint)
+ {
+ // 设置起点
+ _multiLevelStartPoint = point;
+ _hasMultiLevelStartPoint = true;
+ MultiLevelStartPointText = $"({point.X:F2}, {point.Y:F2}, {point.Z:F2})";
+
+ // 渲染起点标记
+ RenderMultiLevelPointMarker(point, isStart: true);
+
+ LogManager.Info($"[多层吊装] 起点已设置: ({point.X:F2}, {point.Y:F2}, {point.Z:F2})");
+ UpdateMainStatus($"起点已设置,请选择终点或添加层级");
+
+ // 清理事件订阅并停用ToolPlugin
+ PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked;
+ _pathPlanningManager?.EnableMouseHandling();
+ _pathPlanningManager?.StopClickTool();
+ _isSelectingMultiLevelStartPoint = false;
+
+ // 更新统计
+ UpdateMultiLevelStats();
+ }
+ else if (_isSelectingMultiLevelEndPoint)
+ {
+ // 设置终点
+ _multiLevelEndPoint = point;
+ _hasMultiLevelEndPoint = true;
+ MultiLevelEndPointText = $"({point.X:F2}, {point.Y:F2}, {point.Z:F2})";
+
+ // 渲染终点标记
+ RenderMultiLevelPointMarker(point, isStart: false);
+
+ LogManager.Info($"[多层吊装] 终点已设置: ({point.X:F2}, {point.Y:F2}, {point.Z:F2})");
+ UpdateMainStatus($"终点已设置,可以添加层级创建路径");
+
+ // 清理事件订阅并停用ToolPlugin
+ PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked;
+ _pathPlanningManager?.EnableMouseHandling();
+ _pathPlanningManager?.StopClickTool();
+ _isSelectingMultiLevelEndPoint = false;
+
+ // 更新统计
+ UpdateMultiLevelStats();
+ }
+ else if (_currentSelectingLevelTarget != null)
+ {
+ // 设置层级目标位置
+ var targetPoint = new Point3D(point.X, point.Y, point.Z);
+ _currentSelectingLevelTarget.HorizontalTarget = targetPoint;
+
+ // 渲染层级目标标记
+ RenderMultiLevelLevelMarker(targetPoint, _currentSelectingLevelTarget.Index);
+
+ LogManager.Info($"[多层吊装] 层级{_currentSelectingLevelTarget.Index + 1}目标位置已设置: ({point.X:F2}, {point.Y:F2})");
+ UpdateMainStatus($"层级{_currentSelectingLevelTarget.Index + 1}目标位置已设置,可以继续添加层级或点击创建路径");
+
+ // 清理事件订阅并停用ToolPlugin
+ PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked;
+ _pathPlanningManager?.EnableMouseHandling();
+ _pathPlanningManager?.StopClickTool();
+ _currentSelectingLevelTarget = null;
+ }
+ }
+
+ ///
+ /// 渲染层级目标位置标记
+ ///
+ private void RenderMultiLevelLevelMarker(Point3D point, int levelIndex)
+ {
+ if (PathPointRenderPlugin.Instance == null) return;
+
+ var routeName = $"MultiLevelLevel{levelIndex}";
+
+ // 移除之前的标记
+ var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == routeName);
+ if (existingRoute != null)
+ {
+ PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id);
+ }
+
+ // 创建临时路径
+ var route = new PathRoute(routeName)
+ {
+ Description = $"层级{levelIndex + 1}目标位置"
+ };
+ var pathPoint = new PathPoint
+ {
+ Name = $"层级{levelIndex + 1}",
+ Position = point,
+ Type = PathPointType.WayPoint
+ };
+ route.AddPoint(pathPoint);
+
+ // 渲染点(使用不同颜色区分层级)
+ PathPointRenderPlugin.Instance.RenderPointOnly(route);
+ LogManager.Info($"[多层吊装] 层级{levelIndex + 1}目标标记已渲染");
+ }
+
+ ///
+ /// 渲染多层吊装路径的点标记
+ ///
+ private void RenderMultiLevelPointMarker(Point3D point, bool isStart)
+ {
+ if (PathPointRenderPlugin.Instance == null) return;
+
+ var routeName = isStart ? "MultiLevelStartPoint" : "MultiLevelEndPoint";
+ var displayName = isStart ? "多层吊装起点" : "多层吊装终点";
+ var pointType = isStart ? PathPointType.StartPoint : PathPointType.EndPoint;
+
+ // 移除之前的标记
+ var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == routeName);
+ if (existingRoute != null)
+ {
+ PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id);
+ }
+
+ // 创建临时路径
+ var route = new PathRoute(routeName)
+ {
+ Description = displayName
+ };
+ var pathPoint = new PathPoint
+ {
+ Name = displayName,
+ Position = point,
+ Type = pointType
+ };
+ route.AddPoint(pathPoint);
+
+ // 渲染点
+ PathPointRenderPlugin.Instance.RenderPointOnly(route);
+ }
+
+ #endregion
+
+ ///
/// 提取并渲染所有空轨模型的基准路径
///
private void ExtractAndRenderRailBaselinePaths()
diff --git a/src/UI/WPF/Views/PathEditingView.xaml b/src/UI/WPF/Views/PathEditingView.xaml
index 058cf08..3cea8b0 100644
--- a/src/UI/WPF/Views/PathEditingView.xaml
+++ b/src/UI/WPF/Views/PathEditingView.xaml
@@ -30,6 +30,12 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
+
+
+
+
+
+