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 路径编辑页签视图 - 采用与动画控制和分层管 + + + + + +