初步实现多层吊装路径

This commit is contained in:
tian 2026-02-24 03:57:33 +08:00
parent ae845bc571
commit edcbd0802b
19 changed files with 2315 additions and 104 deletions

View File

@ -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/` 下的详细文档
- [ ] 代码中使用了正确的工具类方法
- [ ] 没有重复实现已有功能

View File

@ -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)

View File

@ -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
{
/// <summary>
/// 设置网格大小
/// </summary>
/// <param name="cellSizeInMeters">网格单元大小(米)</param>
public void SetCellSize(double cellSizeInMeters)
{
// ✅ 转换为模型单位存储
double cellSize = UnitsConverter.ConvertFromMeters(cellSizeInMeters);
// 后续计算使用 cellSize模型单位
_grid = new GridMap(cellSize);
}
/// <summary>
/// 获取路径长度(用于显示)
/// </summary>
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<PathRoute> 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
{
/// <summary>
/// 计算路径总长度
/// </summary>
public double CalculateTotalLength(List<Point3D> points)
{
double totalLength = 0;
for (int i = 0; i < points.Count - 1; i++)
{
// ✅ 使用 GeometryHelper
totalLength += GeometryHelper.Distance(points[i], points[i + 1]);
}
return totalLength;
}
/// <summary>
/// 检查转弯是否过急
/// </summary>
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}");
}
}
```

View File

@ -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();
```
## 使用示例
### 示例1ViewModel中显示非模态对话框
```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
<!-- ❌ 错误 -->
<Window ...
Owner="{x:Static Application.Current.MainWindow}">
<!-- ✅ 正确XAML中不设置代码中设置 -->
<Window ...>
```

View File

@ -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)
```

View File

@ -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("[渲染] 路径显示完成");
```

View File

@ -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
/// <summary>
/// 生成网格地图
/// </summary>
/// <param name="cellSizeInMeters">网格单元大小(米)</param>
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); // 可能不同!
```

View File

@ -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常量控制最小点击间隔
- **日志系统**: 改进调试日志的详细程度
- 点击监听过程的详细日志
- 通道高亮过程的状态跟踪
- 异常堆栈信息记录
- **用户体验**: 优化交互反馈
- 改进状态消息显示选中通道数量
- 在状态栏显示详细的点信息
- 清除无效选择的自动处理

View File

@ -205,6 +205,7 @@
<Compile Include="src\PathPlanning\VoxelGridVisualizer.cs" />
<Compile Include="src\PathPlanning\RailGeometryHelper.cs" />
<Compile Include="src\PathPlanning\AerialPathGenerator.cs" />
<Compile Include="src\Commands\CreateAerialPathCommand.cs" />
<!-- UI - WPF -->
<Compile Include="src\UI\WPF\Views\LogisticsControlPanel.xaml.cs">
<DependentUpon>LogisticsControlPanel.xaml</DependentUpon>
@ -295,6 +296,7 @@
<!-- UI - WPF Commands and Models -->
<Compile Include="src\UI\WPF\Commands\RelayCommand.cs" />
<Compile Include="src\UI\WPF\Commands\LayerManagementCommands.cs" />
<Compile Include="src\UI\WPF\Converters\HoistingConverters.cs" />
<Compile Include="src\UI\WPF\Converters\BoolToVisibilityConverter.cs" />
<Compile Include="src\UI\WPF\Converters\BoolToBrushConverter.cs" />
<Compile Include="src\UI\WPF\Converters\BoolToOpacityConverter.cs" />
@ -306,6 +308,7 @@
<Compile Include="src\UI\WPF\Converters\NullToDashConverter.cs" />
<Compile Include="src\UI\WPF\Converters\PathToImageConverter.cs" />
<Compile Include="src\UI\WPF\Models\LogisticsModel.cs" />
<Compile Include="src\UI\WPF\Models\HoistingLevelItem.cs" />
<Compile Include="src\UI\WPF\Models\PathRouteViewModel.cs" />
<Compile Include="src\UI\WPF\Models\SplitPreviewItem.cs" />
<!-- Utils -->

View File

@ -2,6 +2,11 @@
## 功能点
### [2026/2/24]
1. [x] (优化)吊装路径支持
2. [x] (功能)
### [2026/2/14]
1. [x] (优化)实现完整的路径分析功能

View File

@ -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> ← 多层模式层级列表 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ HoistingLevel │
├─────────────────────────────────────────────────────────────┤
│ Index: int ← 层级序号 │
│ HeightInMeters: double ← 相对起吊点的高度 │
│ HorizontalTarget: Point3D ← 水平目标位置XY
│ IsRise: bool ← true=提升, false=下降 │
│ Description: string ← 层级描述 │
└─────────────────────────────────────────────────────────────┘
```
### 兼容性设计
| 组件 | 兼容性策略 |
|------|-----------|
| `PathRoute.Points` | `List<PathPoint>` 天然支持可变数量,无需修改 |
| `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`
### 阶段2UI对话框
**目标**:创建多层吊装路径输入对话框
**涉及文件**
1. `src/UI/WPF/Views/MultiLevelHoistingDialog.xaml` - 新建
2. `src/UI/WPF/Views/MultiLevelHoistingDialog.xaml.cs` - 新建
**功能需求**
- 显示层级列表(可添加/删除)
- 每个层级设置:高度、方向(提升/下降)、描述
- 水平目标位置通过3D点击获取
- 实时预览路径点数
### 阶段3ViewModel集成
**目标**:在 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<HoistingLevel> Levels { get; set; }
}
```
### AerialPathGenerator 新方法
```csharp
public List<PathPoint> GenerateMultiLevelHoistingPath(
Point3D startPoint,
Point3D endPoint,
List<HoistingLevel> levels)
```
## 测试要点
1. **向后兼容测试**:单层模式路径生成与之前完全一致
2. **多层路径测试**2层、3层、混合升降路径生成正确
3. **数据序列化测试**:多层路径保存/加载正确
4. **渲染测试**多层路径在3D视图中正确显示
5. **动画测试**:动画沿多层路径正确插值
## 风险分析
| 风险 | 可能性 | 影响 | 缓解措施 |
|------|-------|------|---------|
| 现有单层功能被破坏 | 低 | 高 | 保留原方法,添加新实现,充分测试 |
| 路径点过多影响性能 | 中 | 中 | 限制最大层级数如10层 |
| 数据库兼容性问题 | 低 | 高 | 验证现有表结构支持可变点数 |
## 版本记录
| 版本 | 日期 | 描述 |
|------|------|------|
| 1.0 | 2026-02-24 | 初始方案 |

View File

@ -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
/// <summary>
/// 吊装参数:提升高度(米)
/// 单层吊装模式使用,多层模式下该值被忽略
/// </summary>
public double LiftHeightMeters { get; set; }
@ -187,5 +226,25 @@ namespace NavisworksTransport.Commands
/// 空轨参数:点击点列表
/// </summary>
public List<Point3D> ClickedPoints { get; set; }
/// <summary>
/// 吊装路径模式(默认单层,向后兼容)
/// </summary>
public HoistingMode HoistingMode { get; set; } = HoistingMode.SingleLevel;
/// <summary>
/// 多层吊装:层级信息列表(仅多层模式使用)
/// </summary>
public List<HoistingLevel> HoistingLevels { get; set; }
/// <summary>
/// 默认构造函数
/// </summary>
public AerialPathParameters()
{
HoistingMode = HoistingMode.SingleLevel;
HoistingLevels = new List<HoistingLevel>();
ClickedPoints = new List<Point3D>();
}
}
}

View File

@ -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)
{

View File

@ -2530,4 +2530,101 @@ namespace NavisworksTransport
public string Version { get; set; } = "1.0";
public string Description { get; set; } = "";
}
#region
/// <summary>
/// 吊装路径模式枚举
/// </summary>
public enum HoistingMode
{
/// <summary>
/// 单层吊装(传统模式,向后兼容)
/// </summary>
SingleLevel,
/// <summary>
/// 多层阶梯式吊装
/// </summary>
MultiLevel
}
/// <summary>
/// 吊装层级信息 - 表示多层吊装中的一个水平移动层级
/// </summary>
[Serializable]
public class HoistingLevel
{
/// <summary>
/// 层级序号从0开始
/// </summary>
public int Index { get; set; }
/// <summary>
/// 该层级的高度(相对于起吊点的垂直高度,米)
/// </summary>
public double HeightInMeters { get; set; }
/// <summary>
/// 该层级的水平移动目标点XY位置Z坐标会被忽略
/// </summary>
public Point3D HorizontalTarget { get; set; }
/// <summary>
/// 运动方向true=从上一位置上升到此高度false=从上一位置下降到此高度
/// </summary>
public bool IsRise { get; set; }
/// <summary>
/// 层级描述(如"桁车移动层"、"避让层"等)
/// </summary>
public string Description { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedTime { get; set; }
/// <summary>
/// 默认构造函数
/// </summary>
public HoistingLevel()
{
Index = 0;
HeightInMeters = 0.0;
HorizontalTarget = new Point3D();
IsRise = true;
Description = string.Empty;
CreatedTime = DateTime.Now;
}
/// <summary>
/// 带参数构造函数
/// </summary>
/// <param name="index">层级序号</param>
/// <param name="heightInMeters">高度(米)</param>
/// <param name="horizontalTarget">水平目标点</param>
/// <param name="isRise">是否为上升</param>
/// <param name="description">描述</param>
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;
}
/// <summary>
/// 返回层级描述字符串
/// </summary>
public override string ToString()
{
string direction = IsRise ? "上升" : "下降";
return $"层级{Index + 1}: {direction}到{HeightInMeters:F2}米,目标({HorizontalTarget.X:F2}, {HorizontalTarget.Y:F2}) {(string.IsNullOrEmpty(Description) ? "" : $"- {Description}")}";
}
}
#endregion
}

View File

@ -221,5 +221,99 @@ namespace NavisworksTransport.PathPlanning
return pathPoints;
}
/// <summary>
/// 生成多层阶梯式吊装路径
/// 支持在多个高度层进行水平移动,可混合上升和下降
/// </summary>
/// <param name="startPoint">起点(地面起吊位置)</param>
/// <param name="endPoint">终点(地面落点位置)</param>
/// <param name="levels">层级信息列表(按顺序执行)</param>
/// <returns>路径点列表</returns>
public List<PathPoint> GenerateMultiLevelHoistingPath(
Point3D startPoint,
Point3D endPoint,
List<HoistingLevel> 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<PathPoint>();
// 点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;
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace NavisworksTransport.UI.WPF.Converters
{
/// <summary>
/// 索引加1转换器用于显示序号从1开始
/// </summary>
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();
}
}
/// <summary>
/// 方向到文本转换器
/// </summary>
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();
}
}
/// <summary>
/// 方向到颜色转换器
/// </summary>
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();
}
}
/// <summary>
/// 目标位置到文本转换器
/// </summary>
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();
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.UI.WPF.Models
{
/// <summary>
/// 层级数据项(支持属性变更通知)
/// </summary>
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));
}
}
}

View File

@ -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<HoistingLevelItem> _multiLevelItems = new ObservableCollection<HoistingLevelItem>();
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<HoistingLevelItem> 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<HoistingLevelItem>((item) => ExecuteDeleteMultiLevel(item));
PickMultiLevelTargetCommand = new RelayCommand<HoistingLevelItem>((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("路径规划管理器未初始化");
}
}, "新建吊装路径");
} /// <summary>
}
#region
/// <summary>
/// 执行新建多层阶梯式吊装路径命令 - 启用页签模式
/// </summary>
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("[多层吊装] 页签模式已启用");
}, "新建多层吊装路径");
}
/// <summary>
/// 执行选择多层吊装起点命令
/// </summary>
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("[多层吊装] 开始选择起点");
}, "选择多层吊装起点");
}
/// <summary>
/// 执行选择多层吊装终点命令
/// </summary>
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("[多层吊装] 开始选择终点");
}, "选择多层吊装终点");
}
/// <summary>
/// 执行添加层级命令
/// </summary>
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},请设置目标位置");
}
/// <summary>
/// 执行删除层级命令
/// </summary>
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}个层级");
}
/// <summary>
/// 执行拾取层级目标位置命令
/// </summary>
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}的目标位置");
}
/// <summary>
/// 执行创建多层吊装路径命令
/// </summary>
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<HoistingLevel>();
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<PathRoute> 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);
}
}, "创建多层吊装路径");
}
/// <summary>
/// 执行取消多层吊装命令
/// </summary>
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("[多层吊装] 页签模式已取消");
}
/// <summary>
/// 更新多层吊装统计信息
/// </summary>
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();
}
/// <summary>
/// 多层吊装路径鼠标点击处理
/// </summary>
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;
}
}
/// <summary>
/// 渲染层级目标位置标记
/// </summary>
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}目标标记已渲染");
}
/// <summary>
/// 渲染多层吊装路径的点标记
/// </summary>
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
/// <summary>
/// 提取并渲染所有空轨模型的基准路径
/// </summary>
private void ExtractAndRenderRailBaselinePaths()

View File

@ -30,6 +30,12 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
<converters:BoolToBrushConverter x:Key="BoolToBrushConverter"/>
<converters:BoolToOpacityConverter x:Key="BoolToOpacityConverter"/>
<!-- 多层吊装转换器 -->
<converters:IndexPlusOneConverter x:Key="IndexPlusOneConverter"/>
<converters:DirectionToTextConverter x:Key="DirectionToTextConverter"/>
<converters:DirectionToColorConverter x:Key="DirectionToColorConverter"/>
<converters:TargetToTextConverter x:Key="TargetToTextConverter"/>
<!-- 滑块样式 -->
<Style x:Key="SliderStyle" TargetType="Slider">
<Setter Property="Foreground" Value="{StaticResource NavisworksSecondaryBrush}"/>
@ -208,6 +214,10 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
Command="{Binding NewHoistingPathCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="创建吊装路径"/>
<Button Content="多层吊装"
Command="{Binding NewMultiLevelHoistingPathCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="创建多层阶梯式吊装路径"/>
<Button Content="删除"
Command="{Binding DeletePathCommand}"
Style="{StaticResource SecondaryButtonStyle}"
@ -260,7 +270,151 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
</StackPanel>
</Border>
<!-- 区域3: 路径编辑 -->
<!-- 区域3: 多层吊装路径 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10"
Visibility="{Binding IsMultiLevelHoistingMode, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel>
<Label Content="多层阶梯式吊装路径" Style="{StaticResource SectionHeaderStyle}"/>
<TextBlock Text="设置多个高度层级,支持上升和下降的阶梯式移动"
FontSize="10" Foreground="#FF666666" Margin="0,-5,0,5"/>
<!-- 起点/终点设置 -->
<Grid Margin="0,5,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 起点 -->
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Button Content="选择起点"
Command="{Binding SelectMultiLevelStartPointCommand}"
Style="{StaticResource SecondaryButtonStyle}"
ToolTip="在3D视图中点击选择地面起吊位置"/>
<TextBox Text="{Binding MultiLevelStartPointText, Mode=OneWay}"
Style="{StaticResource ReadOnlyTextBoxStyle}"
Width="100"/>
</StackPanel>
<!-- 终点 -->
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Content="选择终点"
Command="{Binding SelectMultiLevelEndPointCommand}"
Style="{StaticResource SecondaryButtonStyle}"
ToolTip="在3D视图中点击选择最终放置位置"/>
<TextBox Text="{Binding MultiLevelEndPointText, Mode=OneWay}"
Style="{StaticResource ReadOnlyTextBoxStyle}"
Width="100"/>
</StackPanel>
</Grid>
<!-- 层级列表标题 -->
<Grid Margin="0,10,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="层级列表" VerticalAlignment="Center" FontSize="11" Foreground="#FF666666"/>
<Button Grid.Column="1" Content="+ 添加层级"
Command="{Binding AddMultiLevelCommand}"
Style="{StaticResource SecondaryButtonStyle}"
Width="80" Height="24"/>
</Grid>
<!-- 层级列表 -->
<DataGrid ItemsSource="{Binding MultiLevelItems}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Single"
GridLinesVisibility="Horizontal"
AlternatingRowBackground="#FFF0F8FF"
HeadersVisibility="Column"
RowHeaderWidth="0"
Height="150">
<DataGrid.Columns>
<!-- 序号 -->
<DataGridTextColumn Header="序号" Binding="{Binding Index, Converter={StaticResource IndexPlusOneConverter}}" Width="45" IsReadOnly="True"/>
<!-- 高度 -->
<DataGridTemplateColumn Header="高度(m)" Width="80">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding HeightInMeters, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Width="70" Height="22" FontSize="10"
VerticalAlignment="Center" VerticalContentAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- 方向 -->
<DataGridTemplateColumn Header="方向" Width="55">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton Content="{Binding IsRise, Converter={StaticResource DirectionToTextConverter}}"
IsChecked="{Binding IsRise, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="50" Height="22" FontSize="10"
Background="{Binding IsRise, Converter={StaticResource DirectionToColorConverter}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- 目标位置 -->
<DataGridTemplateColumn Header="目标位置" Width="*" MinWidth="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding HorizontalTarget, Converter={StaticResource TargetToTextConverter}}"
VerticalAlignment="Center" Width="70" FontSize="10"/>
<Button Content="📍"
Command="{Binding DataContext.PickMultiLevelTargetCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
Width="24" Height="22" FontSize="10"
ToolTip="拾取目标位置"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- 操作 -->
<DataGridTemplateColumn Header="操作" Width="50">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="×"
Command="{Binding DataContext.DeleteMultiLevelCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
Width="24" Height="22"
Foreground="Red" FontWeight="Bold"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- 统计信息和创建按钮 -->
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding MultiLevelStatsText}"
VerticalAlignment="Center" FontSize="10" Foreground="#FF666666"/>
<Button Grid.Column="1" Content="创建路径"
Command="{Binding CreateMultiLevelPathCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanCreateMultiLevelPath}"/>
</Grid>
<!-- 取消按钮 -->
<Button Content="取消多层吊装"
Command="{Binding CancelMultiLevelHoistingCommand}"
Style="{StaticResource SecondaryButtonStyle}"
HorizontalAlignment="Left"
Margin="0,10,0,0"/>
</StackPanel>
</Border>
<!-- 区域4: 路径编辑 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10">
<StackPanel>
<Label Content="路径编辑" Style="{StaticResource SectionHeaderStyle}"/>