diff --git a/.agents/skills/project-tools/SKILL.md b/.agents/skills/project-tools/SKILL.md index 95c05ff..cae0848 100644 --- a/.agents/skills/project-tools/SKILL.md +++ b/.agents/skills/project-tools/SKILL.md @@ -6,18 +6,35 @@ ## 工具类总览 -| 工具类 | 文件 | 用途 | 优先级 | -|--------|------|------|--------| -| **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` | 剖切操作 | ⭐ | +### 核心工具类(⭐⭐⭐ 必须) + +| 工具类 | 文件 | 用途 | +|--------|------|------| +| **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` | 2D地图↔3D世界坐标转换 | +| **ModelHighlightHelper** | `src/Utils/ModelHighlightHelper.cs` | 模型高亮显示 | +| **NavisworksSelectionHelper** | `src/Utils/NavisworksSelectionHelper.cs` | 选择集操作 | +| **ViewpointHelper** | `src/Utils/ViewpointHelper.cs` | 视点操作 | +| **NavisworksApiHelper** | `src/Utils/NavisworksApiHelper.cs` | API通用工具 | +| **VisibilityHelper** | `src/Utils/VisibilityHelper.cs` | 可见性管理 | + +### 专项工具类(⭐ 按需使用) + +| 工具类 | 文件 | 用途 | +|--------|------|------| +| **SectionClipHelper** | `src/Utils/SectionClipHelper.cs` | 剖切操作(性能优化) | +| **BoundingBoxGeometryUtils** | `src/Utils/BoundingBoxGeometryUtils.cs` | 包围盒几何计算 | +| **FloorDetector** | `src/Utils/FloorDetector.cs` | 楼层检测 | +| **GeometryCacheManager** | `src/Utils/GeometryCacheManager.cs` | 几何缓存管理 | ## 快速参考 @@ -58,13 +75,52 @@ LogManager.Warning("警告信息"); LogManager.Error("错误信息", exception); ``` +### 几何计算 + +```csharp +// ❌ 错误:自己实现距离计算 +double dx = p2.X - p1.X; +double dy = p2.Y - p1.Y; +double distance = Math.Sqrt(dx*dx + dy*dy); + +// ✅ 正确:使用 GeometryHelper +double distance = GeometryHelper.Distance(p1, p2); +double angle = GeometryHelper.AngleBetweenDegrees(v1, v2); +``` + +### 模型高亮 + +```csharp +// ✅ 使用 ModelHighlightHelper 高亮对象 +ModelHighlightHelper.HighlightItems("myCategory", modelItems); +ModelHighlightHelper.ClearCategory("myCategory"); +ModelHighlightHelper.ClearAllHighlights(); +``` + +### 视角调整 + +```csharp +// ✅ 使用 ViewpointHelper 调整视角 +ViewpointHelper.AdjustViewpointToPathCenter(path); +ViewpointHelper.FocusOnModelItem(modelItem, 45, 0.25); +Viewpoint saved = ViewpointHelper.SaveCurrentViewpoint(); +ViewpointHelper.RestoreViewpoint(saved); +``` + ## 详细文档 -- [DialogHelper 使用指南](utils/DialogHelper.md) -- [UnitsConverter 使用指南](utils/UnitsConverter.md) -- [LogManager 使用指南](utils/LogManager.md) -- [GeometryHelper 使用指南](utils/GeometryHelper.md) -- [ModelHighlightHelper 使用指南](utils/ModelHighlightHelper.md) +- [DialogHelper 使用指南](utils/DialogHelper.md) - 对话框Owner设置和置顶显示 +- [UnitsConverter 使用指南](utils/UnitsConverter.md) - 单位转换(极其重要) +- [LogManager 使用指南](utils/LogManager.md) - 日志记录 +- [GeometryHelper 使用指南](utils/GeometryHelper.md) - 3D几何计算 +- [PathHelper 使用指南](utils/PathHelper.md) - 文件路径工具 +- [CoordinateConverter 使用指南](utils/CoordinateConverter.md) - 2D/3D坐标转换 +- [ModelHighlightHelper 使用指南](utils/ModelHighlightHelper.md) - 模型高亮 +- [NavisworksSelectionHelper 使用指南](utils/NavisworksSelectionHelper.md) - 选择集操作 +- [ViewpointHelper 使用指南](utils/ViewpointHelper.md) - 视点操作 +- [SectionClipHelper 使用指南](utils/SectionClipHelper.md) - 剖切操作 +- [NavisworksApiHelper 使用指南](utils/NavisworksApiHelper.md) - API通用工具 +- [VisibilityHelper 使用指南](utils/VisibilityHelper.md) - 可见性管理 ## 使用检查清单 @@ -76,6 +132,12 @@ LogManager.Error("错误信息", exception); - [ ] 需要几何计算?→ 使用 **GeometryHelper** - [ ] 需要高亮模型?→ 使用 **ModelHighlightHelper** - [ ] 需要处理选择集?→ 使用 **NavisworksSelectionHelper** +- [ ] 需要调整视角?→ 使用 **ViewpointHelper** +- [ ] 需要坐标转换?→ 使用 **CoordinateConverter** +- [ ] 需要文件路径操作?→ 使用 **PathHelper** +- [ ] 需要剖切优化?→ 使用 **SectionClipHelper** +- [ ] 需要显示/隐藏对象?→ 使用 **VisibilityHelper** +- [ ] 需要线程安全操作?→ 使用 **NavisworksApiHelper** ## 禁止行为 @@ -86,6 +148,7 @@ LogManager.Error("错误信息", exception); 3. ❌ 使用 `Console.WriteLine` 输出日志 4. ❌ 自己实现距离/角度计算(已有 GeometryHelper) 5. ❌ 自己实现集合的线程安全包装(使用 ThreadSafeObservableCollection) +6. ❌ 自己实现缓存刷新逻辑(使用 NavisworksApiHelper.SafeCacheRefresh) ## 扩展阅读 diff --git a/.agents/skills/project-tools/utils/CoordinateConverter.md b/.agents/skills/project-tools/utils/CoordinateConverter.md new file mode 100644 index 0000000..642f7aa --- /dev/null +++ b/.agents/skills/project-tools/utils/CoordinateConverter.md @@ -0,0 +1,182 @@ +# CoordinateConverter 使用指南 + +## 文件位置 + +`src/Utils/CoordinateConverter.cs` + +## 用途 + +负责 2D 地图坐标与 3D 世界坐标之间的转换,用于导航地图和路径规划可视化。 + +## 核心概念 + +- **地图坐标(MapPoint2D)**:2D 像素坐标,原点在左上角,Y轴向下 +- **世界坐标(Point3D)**:3D 模型坐标,单位与文档单位一致 +- **通道边界(ChannelBounds)**:定义了有效转换的区域范围 + +## 构造函数 + +```csharp +// 使用通道边界和地图尺寸创建转换器 +var converter = new CoordinateConverter( + channelBounds: bounds, // ChannelBounds 对象 + mapWidth: 800, // 地图窗口宽度(像素) + mapHeight: 600 // 地图窗口高度(像素) +); +``` + +## 核心方法 + +### 坐标转换 + +```csharp +// 2D地图坐标 → 3D世界坐标 +MapPoint2D mapPoint = new MapPoint2D(400, 300); +Point3D worldPoint = converter.MapToWorld(mapPoint); + +// 3D世界坐标 → 2D地图坐标 +Point3D worldPoint = new Point3D(x, y, z); +MapPoint2D mapPoint = converter.WorldToMap(worldPoint); + +// 批量转换 +var worldPoints = converter.MapToWorldBatch(mapPoints); +var mapPoints = converter.WorldToMapBatch(worldPoints); +``` + +### 有效性检查 + +```csharp +// 检查地图坐标是否在有效范围内 +bool isValid = converter.IsValidMapPoint(mapPoint); + +// 检查世界坐标是否在通道范围内 +bool isValid = converter.IsValidWorldPoint(worldPoint); +``` + +### 距离计算 + +```csharp +// 计算两个地图点之间的像素距离 +double pixelDistance = converter.CalculateMapDistance(point1, point2); + +// 计算两个世界坐标点之间的距离(模型单位) +double worldDistance = converter.CalculateWorldDistance(point1, point2); +``` + +### 视图操作 + +```csharp +// 放大视图 +converter.ZoomIn(factor: 1.5, centerPoint: mapCenter); + +// 缩小视图 +converter.ZoomOut(factor: 1.5, centerPoint: mapCenter); + +// 平移视图 +converter.Pan(deltaX: 100, deltaY: -50); + +// 重置视图到最佳适应 +converter.ResetView(); + +// 计算最佳适应视图 +converter.CalculateBestFitView(); +``` + +### 尺寸更新 + +```csharp +// 更新地图尺寸(窗口大小变化时) +converter.UpdateMapSize(newWidth: 1024, newHeight: 768); + +// 更新通道边界 +converter.UpdateChannelBounds(newChannelBounds); +``` + +### 辅助方法 + +```csharp +// 获取地图中心点的世界坐标 +Point3D center = converter.GetMapCenterInWorld(); + +// 调整高程 +Point3D adjusted = converter.AdjustElevation(worldPoint, elevationOffset: 0.5); + +// 获取地图缩放比例 +converter.GetMapScale(out double scaleX, out double scaleY); + +// 获取转换器信息摘要 +string info = converter.ToString(); +``` + +## 属性 + +```csharp +// 地图尺寸 +converter.MapWidth = 800; +converter.MapHeight = 600; + +// 通道边界 +converter.ChannelBounds = newChannelBounds; + +// 默认高程(Z坐标) +converter.DefaultElevation = 10.0; + +// 缩放因子(限制范围 0.1 - 50.0) +converter.ZoomFactor = 2.0; + +// 偏移量 +converter.OffsetX = 100; +converter.OffsetY = 50; +``` + +## 使用示例 + +### 示例1:基本的坐标转换 + +```csharp +// 创建转换器 +var bounds = new ChannelBounds(minPoint, maxPoint); +var converter = new CoordinateConverter(bounds, 800, 600); + +// 用户在地图上点击的位置 +var clickPoint = new MapPoint2D(400, 300); + +// 转换为世界坐标进行路径规划 +Point3D worldPoint = converter.MapToWorld(clickPoint); +LogManager.Info($"点击位置对应世界坐标: ({worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2})"); +``` + +### 示例2:路径点批量转换 + +```csharp +// 将规划好的路径点转换为地图坐标显示 +var pathWorldPoints = pathRoute.Points.Select(p => p.Position); +var pathMapPoints = converter.WorldToMapBatch(pathWorldPoints); + +// 在地图上绘制路径 +foreach (var mapPoint in pathMapPoints) +{ + DrawPointOnMap(mapPoint.X, mapPoint.Y); +} +``` + +### 示例3:响应窗口大小变化 + +```csharp +// 地图控件大小变化时更新转换器 +void OnMapSizeChanged(double newWidth, double newHeight) +{ + converter.UpdateMapSize(newWidth, newHeight); + converter.CalculateBestFitView(); + + // 重绘地图... +} +``` + +## 注意事项 + +1. **Y轴翻转**:地图坐标系 Y 轴向下(屏幕坐标),世界坐标系 Y 轴向上,转换时自动处理 +2. **边距设置**:从配置读取 `MarginRatio`,默认 10%,确保内容不会贴边显示 +3. **Z坐标处理**:`MapToWorld` 返回的 Z 坐标使用 `DefaultElevation`,可通过 `AdjustElevation` 调整 +4. **缩放限制**:缩放因子限制在 0.1 - 50.0 范围内,防止过度缩放 +5. **线程安全**:此类的实例方法不是线程安全的,需要在单线程中使用或使用外部锁 diff --git a/.agents/skills/project-tools/utils/ModelHighlightHelper.md b/.agents/skills/project-tools/utils/ModelHighlightHelper.md new file mode 100644 index 0000000..db9e777 --- /dev/null +++ b/.agents/skills/project-tools/utils/ModelHighlightHelper.md @@ -0,0 +1,130 @@ +# ModelHighlightHelper 使用指南 + +## 文件位置 + +`src/Utils/ModelHighlightHelper.cs` + +## 用途 + +统一管理 Navisworks 临时高亮,支持按类别高亮与清除。使用不同颜色区分不同类型的对象(碰撞结果、通道预览、动画物体等)。 + +## 颜色定义 + +| 类别 | 颜色 | RGB | 用途 | +|------|------|-----|------| +| `PrecomputeCollisionResultsCategory` | Material Purple | (156, 39, 176) | 预计算碰撞结果 | +| `ManualTargetsCategory` | 橙色 | (255, 170, 0) | 手工指定对象 | +| `AnimatedObjectCategory` | Amber/Yellow | (255, 193, 7) | 动画物体 | +| `ClashDetectiveResultsCategory` | 红色 | Color.Red | ClashDetective碰撞结果 | +| `ChannelPreviewCategory` | 绿色 | Color.Green | 通道预览 | +| `excludedObjects` | Material Green | (76, 175, 80) | 排除对象 | + +## 核心方法 + +### 基础高亮操作 + +```csharp +// 高亮指定对象集合(使用类别默认颜色) +ModelHighlightHelper.HighlightItems("myCategory", modelItems); + +// 清除指定类别的高亮 +ModelHighlightHelper.ClearCategory("myCategory"); + +// 清除所有高亮 +ModelHighlightHelper.ClearAllHighlights(); + +// 清除碰撞相关高亮 +ModelHighlightHelper.ClearCollisionHighlights(); +``` + +### 碰撞结果高亮 + +```csharp +// 按类别管理碰撞高亮 +ModelHighlightHelper.ManageCollisionHighlightsByCategory( + "collisionResults", + collisionResultsList, + clearOtherCategories: false +); + +// 高亮ClashDetective结果 +ModelHighlightHelper.HighlightClashDetectiveResults( + testName, + getResultsFunc +); + +// 清除ClashDetective高亮 +ModelHighlightHelper.ClearClashDetectiveHighlights(); +``` + +### 获取高亮状态 + +```csharp +// 获取当前高亮快照 +var highlights = ModelHighlightHelper.GetActiveHighlightSnapshot(); +foreach (var info in highlights) +{ + LogManager.Info($"类别: {info.Category}, 颜色: {info.Color}, 数量: {info.Count}"); +} + +// 获取类别颜色 +Color color = ModelHighlightHelper.GetCategoryColor("myCategory"); +``` + +## 使用示例 + +### 示例1:高亮碰撞检测结果 + +```csharp +// 获取碰撞结果 +var collisions = collisionDetector.DetectCollisions(); + +// 高亮显示碰撞对象(使用预计算碰撞类别) +var collidingItems = collisions.Select(c => c.Item2).ToList(); +ModelHighlightHelper.HighlightItems( + ModelHighlightHelper.PrecomputeCollisionResultsCategory, + collidingItems +); + +// 操作完成后清除高亮 +ModelHighlightHelper.ClearCategory(ModelHighlightHelper.PrecomputeCollisionResultsCategory); +``` + +### 示例2:高亮通道预览 + +```csharp +// 高亮显示通道对象 +var channelItems = GetChannelItems(); +ModelHighlightHelper.HighlightItems( + ModelHighlightHelper.ChannelPreviewCategory, + channelItems +); + +// 清除时只清除通道预览,不影响其他高亮 +ModelHighlightHelper.ClearCategory(ModelHighlightHelper.ChannelPreviewCategory); +``` + +### 示例3:多类别管理 + +```csharp +// 同时高亮不同类型对象 +ModelHighlightHelper.HighlightItems("doors", doorItems); +ModelHighlightHelper.HighlightItems("elevators", elevatorItems); +ModelHighlightHelper.HighlightItems("stairs", stairItems); + +// 只清除门的高亮 +ModelHighlightHelper.ClearCategory("doors"); + +// 清除所有高亮 +ModelHighlightHelper.ClearAllHighlights(); +``` + +## 注意事项 + +1. **类别颜色自动管理**:每个类别有预定义颜色,也可以使用自定义类别(默认白色) +2. **自动去重**:同一对象在不同类别中可能被多次高亮,后设置的会覆盖先前的 +3. **线程安全**:内部使用锁保护,可从多线程调用 +4. **清除策略**: + - 清除单个类别:`ClearCategory()` + - 清除所有碰撞相关:`ClearCollisionHighlights()` + - 清除全部:`ClearAllHighlights()` diff --git a/.agents/skills/project-tools/utils/NavisworksApiHelper.md b/.agents/skills/project-tools/utils/NavisworksApiHelper.md new file mode 100644 index 0000000..4160a2c --- /dev/null +++ b/.agents/skills/project-tools/utils/NavisworksApiHelper.md @@ -0,0 +1,108 @@ +# NavisworksApiHelper 使用指南 + +## 文件位置 + +`src/Utils/NavisworksApiHelper.cs` + +## 用途 + +提供 Navisworks API 通用工具方法,包括缓存刷新、线程安全操作、模型项查找等。 + +## 核心方法 + +### 缓存刷新 + +```csharp +// 安全的缓存刷新(轻量级) +NavisworksApiHelper.SafeCacheRefresh("MyComponent"); + +// 线程安全的缓存刷新(自动确保在UI线程执行) +NavisworksApiHelper.SafeCacheRefreshUIThread("MyComponent"); +``` + +### 线程检查与执行 + +```csharp +// 检查当前是否在主UI线程 +bool isUIThread = NavisworksApiHelper.IsOnUIThread(); + +// 在UI线程执行操作(无返回值) +NavisworksApiHelper.ExecuteOnUIThread(() => +{ + // 这段代码确保在UI线程执行 + document.CurrentSelection.Clear(); +}); + +// 在UI线程执行操作(有返回值) +var result = NavisworksApiHelper.ExecuteOnUIThread(() => +{ + return document.CurrentSelection.SelectedItems.Count; +}); +``` + +### 模型项操作 + +```csharp +// 获取ModelItem的显示名称(安全) +string name = NavisworksApiHelper.GetModelItemName(modelItem); + +// 使用PathId查找ModelItem +ModelItem item = NavisworksApiHelper.FindModelItemByPathId(modelIndex, pathId); +``` + +## 使用示例 + +### 示例1:API操作后刷新缓存 + +```csharp +// 执行某些API操作 +document.Models.SetHidden(items, true); + +// 刷新缓存以确保状态同步 +NavisworksApiHelper.SafeCacheRefresh("VisibilityManager"); +``` + +### 示例2:确保在UI线程操作 + +```csharp +// 在后台线程中需要操作UI +Task.Run(() => +{ + // 后台计算... + var result = DoCalculation(); + + // 回到UI线程更新界面 + NavisworksApiHelper.ExecuteOnUIThread(() => + { + StatusText = $"计算完成: {result}"; + ProgressBar.Value = 100; + }); +}); +``` + +### 示例3:查找模型项 + +```csharp +// 从路径ID恢复模型项引用 +var item = NavisworksApiHelper.FindModelItemByPathId(0, "1/2/3/4"); +if (item != null) +{ + LogManager.Info($"找到模型项: {item.DisplayName}"); +} +``` + +### 示例4:获取模型项名称(安全) + +```csharp +// 安全的获取名称,处理空值和异常 +string name = NavisworksApiHelper.GetModelItemName(modelItem); +// 如果 modelItem 为 null,返回 "未知对象" +// 如果获取失败,返回 "获取失败" +``` + +## 注意事项 + +1. **缓存刷新机制**:使用空集合的 `SetHidden` 操作触发缓存更新,开销极小 +2. **线程安全**:`SafeCacheRefreshUIThread` 会自动检查当前线程,必要时切换到UI线程 +3. **异常处理**:所有方法内部都处理了异常,不会抛出错误 +4. **日志前缀**:传入的日志前缀用于标识调用来源,便于调试 diff --git a/.agents/skills/project-tools/utils/NavisworksSelectionHelper.md b/.agents/skills/project-tools/utils/NavisworksSelectionHelper.md new file mode 100644 index 0000000..00e5fdd --- /dev/null +++ b/.agents/skills/project-tools/utils/NavisworksSelectionHelper.md @@ -0,0 +1,202 @@ +# NavisworksSelectionHelper 使用指南 + +## 文件位置 + +`src/Utils/NavisworksSelectionHelper.cs` + +## 用途 + +提供 Navisworks 选择状态管理帮助功能,包括选择状态查询、格式化显示、事件订阅等。 + +## 核心方法 + +### 选择状态查询 + +```csharp +// 获取当前选择状态信息(同步) +SelectionStateResult result = NavisworksSelectionHelper.GetCurrentSelectionState(); + +// 获取当前选择状态信息(异步) +SelectionStateResult result = await NavisworksSelectionHelper.GetCurrentSelectionStateAsync(); + +// 快速检查是否有选中项 +bool hasSelection = NavisworksSelectionHelper.HasSelectedItems(); +``` + +### 设置选择 + +```csharp +// 设置单个模型项为选中状态 +bool success = NavisworksSelectionHelper.SetModelSelection(modelItem); + +// 设置多个模型项为选中状态 +bool success = NavisworksSelectionHelper.SetModelSelection(modelItems); +// 传入 null 或空集合会清除选择 +``` + +### 选择文本格式化 + +```csharp +// 格式化选择状态文本 +string text = NavisworksSelectionHelper.FormatSelectionText( + count: result.Count, + selectedItems: result.SelectedItems, + unitName: "个模型", // 单位名称 + maxDisplayCount: 3, // 最多显示名称数量 + maxTotalLength: 80 // 最大总长度 +); + +// 基于选择结果对象格式化 +string text = NavisworksSelectionHelper.FormatSelectionText( + selectionResult: result, + unitName: "个对象", + maxDisplayCount: 3, + maxTotalLength: 80 +); +``` + +### 选择事件订阅 + +```csharp +// 订阅选择变化事件 +var subscription = NavisworksSelectionHelper.SubscribeToSelectionChanges( + async (selectionResult) => + { + // 处理选择变化 + LogManager.Info($"选择变化: {selectionResult.Count} 个对象"); + }, + uiStateManager: _uiStateManager // 可选,用于确保UI线程执行 +); + +// 取消订阅(使用完释放) +subscription.Dispose(); +``` + +## SelectionStateResult 属性 + +```csharp +public class SelectionStateResult +{ + public bool Success { get; set; } // 操作是否成功 + public int Count { get; set; } // 选择数量 + public List SelectedItems { get; set; } // 选择的项目列表 + public bool HasSelection { get; set; } // 是否有选择 + public string ErrorMessage { get; set; } // 错误信息(失败时) +} +``` + +## 使用示例 + +### 示例1:检查并显示选择状态 + +```csharp +// 获取选择状态 +var result = NavisworksSelectionHelper.GetCurrentSelectionState(); + +if (!result.Success) +{ + LogManager.Error($"获取选择状态失败: {result.ErrorMessage}"); + return; +} + +// 格式化显示 +string statusText = NavisworksSelectionHelper.FormatSelectionText(result); +StatusLabel.Text = statusText; + +// 启用/禁用相关按钮 +EditButton.IsEnabled = result.Count == 1; +DeleteButton.IsEnabled = result.Count > 0; +``` + +### 示例2:同步UI选择状态 + +```csharp +// 在ViewModel中保持选择状态同步 +private void UpdateSelectionState() +{ + var result = NavisworksSelectionHelper.GetCurrentSelectionState(); + + if (result.Count > 0) + { + // 更新属性 + SelectedItems = result.SelectedItems; + SelectionText = NavisworksSelectionHelper.FormatSelectionText(result); + + // 如果有单个选择,显示详细信息 + if (result.Count == 1) + { + var item = result.SelectedItems[0]; + SelectedItemName = item.DisplayName; + } + } + else + { + SelectedItems = new List(); + SelectionText = "请选择模型对象"; + } +} +``` + +### 示例3:订阅选择变化事件 + +```csharp +public class MyViewModel : IDisposable +{ + private SelectionEventSubscription _selectionSubscription; + + public void Initialize() + { + // 订阅选择变化 + _selectionSubscription = NavisworksSelectionHelper.SubscribeToSelectionChanges( + OnSelectionChanged, + uiStateManager: _uiStateManager + ); + } + + private async Task OnSelectionChanged(SelectionStateResult result) + { + // 此方法在UI线程执行(通过uiStateManager) + if (result.HasSelection) + { + SelectionText = NavisworksSelectionHelper.FormatSelectionText(result); + await LoadSelectionDetails(result.SelectedItems); + } + else + { + SelectionText = "请在主界面中选择需要设置的对象"; + } + } + + public void Dispose() + { + _selectionSubscription?.Dispose(); + } +} +``` + +### 示例4:程序化设置选择 + +```csharp +// 选择特定对象 +var targetItem = FindModelItemById(id); +if (targetItem != null) +{ + bool success = NavisworksSelectionHelper.SetModelSelection(targetItem); + if (success) + { + // 聚焦到该对象 + ViewpointHelper.FocusOnModelItem(targetItem, 45, 0.25); + } +} + +// 清除选择 +NavisworksSelectionHelper.SetModelSelection(null); +``` + +## 注意事项 + +1. **线程安全**:`GetCurrentSelectionState` 等方法是线程安全的,但设置选择最好在UI线程执行 +2. **事件处理**:选择变化事件使用 `async void` 内部处理,确保不会阻塞UI +3. **订阅管理**:使用 `SelectionEventSubscription` 模式确保事件正确取消订阅,避免内存泄漏 +4. **格式化限制**:`FormatSelectionText` 会自动处理长名称截断和多个项目的显示 +5. **空值处理**:所有方法都处理了空值情况,不会抛出异常 diff --git a/.agents/skills/project-tools/utils/PathHelper.md b/.agents/skills/project-tools/utils/PathHelper.md new file mode 100644 index 0000000..3cbc7e1 --- /dev/null +++ b/.agents/skills/project-tools/utils/PathHelper.md @@ -0,0 +1,140 @@ +# PathHelper 使用指南 + +## 文件位置 + +`src/Utils/PathHelper.cs` + +## 用途 + +提供文件路径相关的通用方法,包括插件目录管理、文件名处理、截图生成等。 + +## 核心方法 + +### 目录操作 + +```csharp +// 获取插件目录路径 +string pluginDir = PathHelper.GetPluginDirectory(); +// 返回: C:\ProgramData\Autodesk\Navisworks Manage 2026\plugins\TransportPlugin + +// 获取截图目录路径 +string screenshotDir = PathHelper.GetScreenshotDirectory(); + +// 获取报告目录路径 +string reportDir = PathHelper.GetReportDirectory(); + +// 确保目录存在(不存在则创建) +PathHelper.EnsureDirectoryExists("C:\\MyFolder\\SubFolder"); +``` + +### 文件名处理 + +```csharp +// 清理文件名中的非法字符 +string safeName = PathHelper.SanitizeFileName("文件<名称>:非法*字符?"); +// 返回: "文件名称非法字符" + +// 生成带时间戳的文件名 +string fileName = PathHelper.GenerateTimestampedFileName("screenshot", "png"); +// 返回: "screenshot_20240225_143052.png" +``` + +### 路径计算 + +```csharp +// 计算从HTML文件到目标文件的相对路径 +string relativePath = PathHelper.GetRelativePath( + @"C:\reports\index.html", + @"C:\images\photo.png" +); +// 返回: "../images/photo.png" +``` + +### 图像格式转换 + +```csharp +// ImageFormat 转文件扩展名 +string ext = PathHelper.ImageFormatToExtension(ImageFormat.Jpeg); // "jpg" +string ext = PathHelper.ImageFormatToExtension(ImageFormat.Png); // "png" + +// 文件扩展名转 ImageFormat +ImageFormat format = PathHelper.ExtensionToImageFormat(".jpg"); // Jpeg +ImageFormat format = PathHelper.ExtensionToImageFormat(".png"); // Png +``` + +### 截图生成 + +```csharp +// 生成场景截图到默认目录 +string path = PathHelper.GenerateSceneScreenshot( + sceneName: "collision_view", + width: 1920, + height: 1080, + format: ImageFormat.Png, + prefix: "collision" +); +// 返回: 截图文件完整路径 + +// 生成场景截图到指定目录 +string path = PathHelper.GenerateSceneScreenshotToDirectory( + outputDirectory: @"C:\MyScreenshots", + sceneName: "path_view", + width: 1920, + height: 1080, + format: ImageFormat.Jpeg, + prefix: "path" +); +``` + +## 使用示例 + +### 示例1:生成带时间戳的报告文件 + +```csharp +// 生成报告文件名 +string reportDir = PathHelper.GetReportDirectory(); +PathHelper.EnsureDirectoryExists(reportDir); + +string safeRouteName = PathHelper.SanitizeFileName(route.Name); +string reportFile = PathHelper.GenerateTimestampedFileName( + $"collision_report_{safeRouteName}", + "html" +); +string fullPath = Path.Combine(reportDir, reportFile); + +// 保存报告... +``` + +### 示例2:批量处理文件名 + +```csharp +// 清理多个文件名 +var fileNames = new[] { "文件:1", "名称*2", "测试?3" }; +var safeNames = fileNames.Select(f => PathHelper.SanitizeFileName(f)); +// 结果: "文件1", "名称2", "测试3" +``` + +### 示例3:生成碰撞报告截图 + +```csharp +// 在显示碰撞结果后生成截图 +string screenshotPath = PathHelper.GenerateSceneScreenshot( + sceneName: $"collision_{collisionId}", + width: 1920, + height: 1080, + format: ImageFormat.Png, + prefix: "collision" +); + +if (!string.IsNullOrEmpty(screenshotPath)) +{ + LogManager.Info($"截图已保存: {screenshotPath}"); +} +``` + +## 注意事项 + +1. **目录自动创建**:`GenerateSceneScreenshot` 会自动创建必要的目录 +2. **非法字符处理**:`SanitizeFileName` 会移除所有 Windows 文件系统不支持的字符 +3. **时间戳格式**:使用 `yyyyMMdd_HHmmss` 格式,确保文件名唯一且可排序 +4. **相对路径**:`GetRelativePath` 返回的路径使用正斜杠(/),适合在 HTML 中使用 diff --git a/.agents/skills/project-tools/utils/SectionClipHelper.md b/.agents/skills/project-tools/utils/SectionClipHelper.md new file mode 100644 index 0000000..948a112 --- /dev/null +++ b/.agents/skills/project-tools/utils/SectionClipHelper.md @@ -0,0 +1,213 @@ +# SectionClipHelper 使用指南 + +## 文件位置 + +`src/Utils/SectionClipHelper.cs` + +## 用途 + +剖面盒辅助类,用于优化碰撞检测性能。通过设置视口剖面盒,只处理路径周围的对象,大幅减少需要检测的对象数量。 + +## 核心方法 + +### 设置剖面盒 + +```csharp +// 根据路径点列表设置剖面盒 +bool success = SectionClipHelper.SetClipBoxByPath( + pathPoints: points, + marginInMeters: 2.0, // 水平边距(米) + heightMarginInMeters: 1.0 // 高度边距(米) +); + +// 根据路径点设置剖面盒(支持分别设置上下边距) +bool success = SectionClipHelper.SetClipBoxByPathWithMargins( + pathPoints: points, + marginInMeters: 2.0, + heightMarginBottomInMeters: 0.5, // 底部边距 + heightMarginTopInMeters: 2.0 // 顶部边距 +); + +// 根据单个点设置剖面盒(用于吊装路径等) +bool success = SectionClipHelper.SetClipBoxByPoint( + centerPoint: point, + rangeMeters: 10.0, // 范围(米) + heightRangeMeters: 5.0 // 高度范围(米) +); + +// 根据楼层设置剖面盒 +bool success = SectionClipHelper.SetClipBoxByFloor( + floorItem: floor, + marginMeters: 1.0 +); +``` + +### 清除剖面盒 + +```csharp +// 清除剖面盒,显示全部模型 +SectionClipHelper.ClearClipBox(); +``` + +### 状态查询 + +```csharp +// 检查剖面盒是否已启用 +bool isEnabled = SectionClipHelper.IsClipBoxEnabled; + +// 获取当前剖面盒 +if (SectionClipHelper.TryGetCurrentClipBox(out BoundingBox3D clipBox)) +{ + LogManager.Info($"剖面盒范围: {clipBox.Min} - {clipBox.Max}"); +} +``` + +### 包含性测试 + +```csharp +// 测试点是否在剖面盒内 +bool isInside = SectionClipHelper.IsPointInClipBox(point); + +// 测试包围盒是否与剖面盒相交 +bool intersects = SectionClipHelper.IntersectsClipBox(itemBox); + +// 使用角点检测(排除大包围盒假阳性) +bool intersects = SectionClipHelper.IntersectsByCornerPoints(itemBox, clipBox); + +// 使用棱上自适应采样检测(长物体不会漏检) +bool intersects = SectionClipHelper.IntersectsByEdgeSampling( + itemBox, clipBox, minSamplesPerEdge: 3 +); +``` + +### 统计功能 + +```csharp +// 统计剖面盒内/外的对象数量 +SectionClipHelper.CountObjectsInClipBox( + out int totalCount, + out int insideCount, + out int outsideCount +); + +// 使用角点检测统计(排除大包围盒假阳性) +SectionClipHelper.CountObjectsInClipBoxByCorners( + out int totalCount, + out int insideCount, + out int outsideCount, + out int largeBoxFiltered, + debugDetails: detailsList // 可选的详细信息列表 +); + +// 测试相交检测(调试用) +SectionClipHelper.TestIntersection(); +``` + +## 使用示例 + +### 示例1:路径碰撞检测前设置剖面盒 + +```csharp +// 获取路径点 +var pathPoints = pathRoute.Points.Select(p => p.Position).ToList(); + +// 设置剖面盒 +bool success = SectionClipHelper.SetClipBoxByPath( + pathPoints, + marginInMeters: 2.0, // 路径周围2米 + heightMarginInMeters: 1.0 // 上下各1米 +); + +if (success) +{ + // 统计过滤效果 + SectionClipHelper.CountObjectsInClipBoxByCorners( + out int total, out int inside, out int outside, out int filtered + ); + LogManager.Info($"剖面盒过滤: 总数{total}, 盒内{inside}, 过滤{filtered}"); + + // 执行碰撞检测(只检测剖面盒内对象) + var collisions = collisionDetector.DetectCollisions(); +} + +// 完成后清除剖面盒 +SectionClipHelper.ClearClipBox(); +``` + +### 示例2:吊装路径场景 + +```csharp +// 吊装路径通常只需要关注起吊点周围区域 +if (liftPoint != null) +{ + SectionClipHelper.SetClipBoxByPoint( + liftPoint, + rangeMeters: 15.0, // 15米范围 + heightRangeMeters: 20.0 // 20米高度范围 + ); +} +``` + +### 示例3:精确过滤(排除大包围盒假阳性) + +```csharp +// 获取剖面盒 +if (!SectionClipHelper.TryGetCurrentClipBox(out var clipBox)) + return; + +// 遍历模型进行过滤 +foreach (var item in modelItems) +{ + var itemBox = item.BoundingBox(); + + // 先用包围盒快速排除 + if (!clipBox.Intersects(itemBox)) + continue; + + // 再用角点检测精确判断 + if (!SectionClipHelper.IntersectsByCornerPoints(itemBox, clipBox)) + { + // 包围盒相交但没有角点在盒内,可能是大包围盒假阳性 + LogManager.Debug($"跳过假阳性: {item.DisplayName}"); + continue; + } + + // 该对象真正与剖面盒相交,加入检测列表 + itemsToCheck.Add(item); +} +``` + +### 示例4:长物体检测(使用棱上采样) + +```csharp +// 对于长管道等物体,使用棱上采样确保不遗漏 +if (SectionClipHelper.TryGetCurrentClipBox(out var clipBox)) +{ + var itemBox = longPipeItem.BoundingBox(); + + // 使用棱上自适应采样(根据物体大小动态计算采样点) + if (SectionClipHelper.IntersectsByEdgeSampling(itemBox, clipBox, minSamplesPerEdge: 5)) + { + // 长管道与剖面盒相交 + itemsToCheck.Add(longPipeItem); + } +} +``` + +## 默认参数 + +```csharp +private const double DEFAULT_MARGIN_METERS = 2.0; // 默认水平边距 +private const double DEFAULT_HEIGHT_MARGIN_METERS = 1.0; // 默认高度边距 +``` + +## 注意事项 + +1. **性能优化**:剖面盒可以大幅减少碰撞检测的对象数量,提高性能 +2. **JSON设置**:使用 JSON 字符串方式设置剖面盒,避免 "Object is Read-Only" 错误 +3. **过滤策略**: + - 先用 `IntersectsClipBox` 快速排除 + - 再用 `IntersectsByCornerPoints` 排除大包围盒假阳性 + - 最后用 `IntersectsByEdgeSampling` 确保长物体不遗漏 +4. **清除责任**:使用完剖面盒后应调用 `ClearClipBox()` 恢复完整视图 +5. **统计功能**:使用 `CountObjectsInClipBoxByCorners` 查看过滤效果和假阳性数量 diff --git a/.agents/skills/project-tools/utils/ViewpointHelper.md b/.agents/skills/project-tools/utils/ViewpointHelper.md new file mode 100644 index 0000000..4d61625 --- /dev/null +++ b/.agents/skills/project-tools/utils/ViewpointHelper.md @@ -0,0 +1,144 @@ +# ViewpointHelper 使用指南 + +## 文件位置 + +`src/Utils/ViewpointHelper.cs` + +## 用途 + +提供 Navisworks 视角调整功能,用于自动调整摄像机位置和视角,支持路径居中显示、碰撞位置聚焦等。 + +## 核心方法 + +### 路径视角调整 + +```csharp +// 智能调整视角到路径中心 +ViewpointHelper.AdjustViewpointSmart(path, collisions); + +// 调整视角到路径中心,确保整个路径在视野内 +ViewpointHelper.AdjustViewpointToPathCenter(path); + +// 计算路径的包围盒 +BoundingBox3D bounds = ViewpointHelper.CalculatePathBoundingBox(path); +``` + +### 碰撞视角调整 + +```csharp +// 计算碰撞位置的包围盒 +BoundingBox3D bounds = ViewpointHelper.CalculateCollisionsBoundingBox(collisions); + +// 聚焦到碰撞对象(两个对象) +ViewpointHelper.FocusOnCollision(item1, item2, viewAngleDegrees: 45, targetViewRatio: 0.25); + +// 聚焦到指定位置 +ViewpointHelper.FocusOnPosition(center, targetSize, viewAngleDegrees: 45, targetViewRatio: 0.25); +``` + +### 模型元素聚焦 + +```csharp +// 聚焦到单个模型元素 +ViewpointHelper.FocusOnModelItem(modelItem, viewAngleDegrees: 45, targetViewRatio: 0.25); +// viewAngleDegrees: 视角角度(度) +// targetViewRatio: 目标占据视图比例(0.25 = 1/4) +``` + +### 视角保存与恢复 + +```csharp +// 保存当前视角 +Viewpoint savedViewpoint = ViewpointHelper.SaveCurrentViewpoint(); + +// 恢复视角 +ViewpointHelper.RestoreViewpoint(savedViewpoint); +``` + +## 使用示例 + +### 示例1:生成碰撞报告前调整视角 + +```csharp +// 保存当前视角 +Viewpoint originalViewpoint = ViewpointHelper.SaveCurrentViewpoint(); + +try +{ + // 调整视角到路径中心 + ViewpointHelper.AdjustViewpointToPathCenter(path); + + // 生成截图 + string screenshotPath = PathHelper.GenerateSceneScreenshot( + "collision_report", 1920, 1080, ImageFormat.Png + ); +} +finally +{ + // 恢复原始视角 + ViewpointHelper.RestoreViewpoint(originalViewpoint); +} +``` + +### 示例2:聚焦到碰撞位置 + +```csharp +// 检测到碰撞后,聚焦到碰撞对象 +if (collision.Item1 != null && collision.Item2 != null) +{ + ViewpointHelper.FocusOnCollision( + collision.Item1, + collision.Item2, + viewAngleDegrees: 45, // 45度视角 + targetViewRatio: 0.3 // 占据视图30% + ); + + // 高亮碰撞对象 + ModelHighlightHelper.HighlightItems("collision", new[] { collision.Item1, collision.Item2 }); +} +``` + +### 示例3:聚焦到特定模型元素 + +```csharp +// 用户选择了一个门对象 +var doorItem = selectedItems.FirstOrDefault(); +if (doorItem != null) +{ + // 聚焦到该门,45度斜上方视角 + ViewpointHelper.FocusOnModelItem(doorItem, 45, 0.25); + + LogManager.Info($"已聚焦到: {doorItem.DisplayName}"); +} +``` + +### 示例4:路径规划完成后调整视角 + +```csharp +// 路径规划完成 +if (pathRoute.Points.Count > 0) +{ + // 智能调整视角 + ViewpointHelper.AdjustViewpointSmart(pathRoute, collisionResults); + + // 高亮路径点 + var pathItems = pathRoute.Points.Select(p => p.AssociatedModelItem).Where(i => i != null); + ModelHighlightHelper.HighlightItems("pathPreview", pathItems); +} +``` + +## 视角参数说明 + +| 参数 | 说明 | 推荐值 | +|------|------|--------| +| `viewAngleDegrees` | 相机视角角度(度) | 45°(斜上方) | +| `targetViewRatio` | 目标占据视图比例 | 0.25(1/4视图) | +| `baseDimension` | 基准尺寸计算相机距离 | 路径长度或包围盒最大边 | + +## 注意事项 + +1. **视角保存**:在进行视角调整前建议保存当前视角,便于后续恢复 +2. **异常处理**:方法会抛出 `InvalidOperationException`(无活动文档)和 `ArgumentException`(参数错误),需要适当捕获 +3. **单位转换**:内部自动使用 `UnitsConverter` 进行米和模型单位的转换 +4. **标准视角**:`FocusOnModelItem` 和 `FocusOnPosition` 使用模型的标准前右上视角(`FrontRightTopViewVector`) +5. **性能考虑**:频繁调整视角可能影响性能,建议在关键操作后统一调整 diff --git a/.agents/skills/project-tools/utils/VisibilityHelper.md b/.agents/skills/project-tools/utils/VisibilityHelper.md new file mode 100644 index 0000000..8a6f60d --- /dev/null +++ b/.agents/skills/project-tools/utils/VisibilityHelper.md @@ -0,0 +1,112 @@ +# VisibilityHelper 使用指南 + +## 文件位置 + +`src/Utils/VisibilityHelper.cs` + +## 用途 + +可见性管理器,负责 ModelItem 可见性控制的核心业务逻辑,包括显示全部、隔离显示、设置可见性等。 + +## 核心方法 + +### 显示所有项目 + +```csharp +// 显示所有模型项目(重置所有隐藏状态) +bool success = VisibilityHelper.ShowAllItems(); +``` + +### 隔离显示 + +```csharp +// 仅显示指定的模型项集合,隐藏其他所有项 +bool success = VisibilityHelper.IsolateSpecificItems(itemsToShow); + +// 示例:隔离显示路径相关的对象 +var pathItems = GetPathRelatedItems(); +VisibilityHelper.IsolateSpecificItems(pathItems); +``` + +### 设置可见性 + +```csharp +// 设置指定模型项集合的可见性 +bool success = VisibilityHelper.SetItemsVisibility(items, isHidden: true); // 隐藏 +bool success = VisibilityHelper.SetItemsVisibility(items, isHidden: false); // 显示 +``` + +## 使用示例 + +### 示例1:隔离显示碰撞对象 + +```csharp +// 收集所有碰撞涉及的对象 +var collisionItems = new ModelItemCollection(); +foreach (var collision in collisionResults) +{ + if (collision.Item1 != null) collisionItems.Add(collision.Item1); + if (collision.Item2 != null) collisionItems.Add(collision.Item2); +} + +// 只显示碰撞对象 +VisibilityHelper.IsolateSpecificItems(collisionItems); + +// 高亮碰撞对象 +ModelHighlightHelper.HighlightItems("collisions", collisionItems); +``` + +### 示例2:分步显示/隐藏 + +```csharp +// 隐藏所有障碍物 +var obstacles = GetObstacleItems(); +VisibilityHelper.SetItemsVisibility(obstacles, isHidden: true); + +// 后续操作... + +// 重新显示障碍物 +VisibilityHelper.SetItemsVisibility(obstacles, isHidden: false); +``` + +### 示例3:完整的显示重置流程 + +```csharp +// 清除所有高亮 +ModelHighlightHelper.ClearAllHighlights(); + +// 显示所有项目 +VisibilityHelper.ShowAllItems(); + +// 清除剖面盒 +SectionClipHelper.ClearClipBox(); + +LogManager.Info("视图已重置"); +``` + +### 示例4:通道可视化 + +```csharp +// 只显示通道相关的对象 +var channelItems = GetChannelItems(); +VisibilityHelper.IsolateSpecificItems(channelItems); + +// 调整视角到通道 +ViewpointHelper.AdjustViewpointToPathCenter(channelPath); + +// 生成通道预览图 +string screenshot = PathHelper.GenerateSceneScreenshot( + "channel_view", 1920, 1080, ImageFormat.Png +); + +// 恢复显示所有 +VisibilityHelper.ShowAllItems(); +``` + +## 注意事项 + +1. **隔离显示原理**:`IsolateSpecificItems` 会先显示所有,然后隐藏不需要的项目 +2. **祖先节点处理**:隔离显示会自动包含选中项的所有祖先节点,确保路径完整 +3. **性能考虑**:大量项目的可见性操作可能影响性能,建议批量处理 +4. **与剖面盒配合**:可以与 `SectionClipHelper` 配合使用,进一步减少可见对象 +5. **错误处理**:所有方法都返回 `bool` 表示成功/失败,内部已处理异常