From 8a95820fca3ae1d1b8f61e83688ecaf7fda06a53 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sun, 28 Sep 2025 16:20:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E9=80=9A=E9=81=93?= =?UTF-8?q?=E7=BD=91=E6=A0=BC=E7=94=9F=E6=88=90=E7=9A=84=E9=AB=98=E5=BA=A6?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=EF=BC=8C=E8=83=BD=E5=9C=A8=E6=96=9C=E9=9D=A2?= =?UTF-8?q?=E4=B8=8A=E7=94=9F=E6=88=90=E7=BD=91=E6=A0=BC=EF=BC=8C=E5=8F=AF?= =?UTF-8?q?=E8=A7=86=E5=8C=96=E4=B9=9F=E5=AF=B9=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 9 - NavisworksTransportPlugin.csproj | 2 - doc/requirement/todo_features.md | 8 + .../Collision/ClashDetectiveIntegration.cs | 2 +- src/Core/PathPlanningManager.cs | 11 +- .../Properties/CategoryAttributeManager.cs | 29 +- src/PathPlanning/AutoPathFinder.cs | 6 +- src/PathPlanning/ChannelBasedGridBuilder.cs | 131 ++----- src/PathPlanning/ChannelHeightDetector.cs | 2 +- src/PathPlanning/GridCache.cs | 331 ------------------ src/PathPlanning/GridCacheKey.cs | 185 ---------- src/PathPlanning/GridMap.cs | 39 +-- src/PathPlanning/GridMapCacheKey.cs | 12 +- src/PathPlanning/GridMapGenerator.cs | 26 +- src/PathPlanning/VerticalScanProcessor.cs | 2 +- .../WPF/ViewModels/ModelSettingsViewModel.cs | 4 +- src/Utils/GeometryHelper.cs | 6 +- 17 files changed, 88 insertions(+), 717 deletions(-) delete mode 100644 src/PathPlanning/GridCache.cs delete mode 100644 src/PathPlanning/GridCacheKey.cs diff --git a/CLAUDE.md b/CLAUDE.md index 5fcc41b..d86598a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,14 +80,6 @@ Eight predefined categories with inheritance from parent to child nodes: - 门 (Doors), 电梯 (Elevators), 楼梯 (Stairs), 通道 (Channels) - 障碍物 (Obstacles), 装卸区 (Loading Zones), 停车区 (Parking), 检查点 (Checkpoints) -## Development Guidelines - -### Claude Code Work Delegation Principle - -- **任何具体的开发工作,全部让子代理完成** - All concrete development work must be delegated to specialized sub-agents -- Claude Code主要负责需求理解、任务分解和工作协调,不直接进行代码编写 -- 使用专业子代理处理具体实现:`navisworks-feature-developer`、`navisworks-api-researcher`、`navisworks-ui-designer`、`technical-architect`、`project-task-manager` - ### Language and Communication - **使用中文进行所有交流和代码注释** - Primary language for user interaction and code documentation @@ -203,6 +195,5 @@ public class PathClickToolPlugin : ToolPlugin { } - **2026 Features**: Test advanced animation capabilities, enhanced collision detection, and improved model handling - 在编码中,不要用回退或向后兼容的思路和步骤 - 程序的日志在:C:\ProgramData\Autodesk\Navisworks Manage 2026\NavisworksTransport\logs\debug.log -- 不要搞向后兼容 - 使用agent完成任务前,一定要先用Plan模式设计好方案和任务清单,并征得我同意。 - 网格坐标代表的是网格单元的左下角,而不是中心点! \ No newline at end of file diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 680d9b3..99521aa 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -163,8 +163,6 @@ - - diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index f1158dd..1412e2e 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -2,6 +2,14 @@ ## 功能点 +### [2025/09/28] + +1. [x] (优化)识别表面不平的通道,给网格正确的表面z值 + +### [2025/09/27] + +1. [x] (优化)为生成的网格地图增加缓存,提高路径规划性能 + ### [2025/09/15] 1. [x] (功能)修改碰撞检测报告,增加碰撞构件的数量和清单 diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs index 8a97b41..a0e8f9b 100644 --- a/src/Core/Collision/ClashDetectiveIntegration.cs +++ b/src/Core/Collision/ClashDetectiveIntegration.cs @@ -1113,7 +1113,7 @@ namespace NavisworksTransport var document = Application.ActiveDocument; // 获取所有可通行的物流模型项 - var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document); + var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(); if (allChannelItems.Count == 0) { diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index 68a5101..74a07a1 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -879,7 +879,6 @@ namespace NavisworksTransport try { gridMap = gridMapGenerator.GenerateFromBIM( - document, bounds, gridSize, vehicleRadius, @@ -1125,7 +1124,7 @@ namespace NavisworksTransport var document = Application.ActiveDocument; if (document != null) { - var allLogisticsItems = CategoryAttributeManager.GetAllLogisticsItems(document); + var allLogisticsItems = CategoryAttributeManager.GetAllLogisticsItems(); var channelItems = CategoryAttributeManager.FilterByLogisticsType(allLogisticsItems, CategoryAttributeManager.LogisticsElementType.通道); _walkableAreas.AddRange(channelItems); LogManager.Info($"[SelectChannels] 通过CategoryAttributeManager直接筛选到 {channelItems.Count} 个通道"); @@ -2239,7 +2238,7 @@ namespace NavisworksTransport LogManager.Info("[通道自动选择] 开始搜索可通行的物流模型"); // 使用CategoryAttributeManager统一接口获取物流项目 - var allLogisticsItemsCollection = CategoryAttributeManager.GetAllLogisticsItems(document); + var allLogisticsItemsCollection = CategoryAttributeManager.GetAllLogisticsItems(); LogManager.Info($"[通道自动选择] CategoryAttributeManager找到 {allLogisticsItemsCollection.Count} 个具有物流属性的模型项"); // 筛选出可通行的物流模型 @@ -2562,7 +2561,7 @@ namespace NavisworksTransport if (document?.Models != null) { // 使用CategoryAttributeManager统一接口获取物流项目 - var logisticsItemsCollection = CategoryAttributeManager.GetAllLogisticsItems(document); + var logisticsItemsCollection = CategoryAttributeManager.GetAllLogisticsItems(); LogManager.WriteLog($"[搜索通道] CategoryAttributeManager找到 {logisticsItemsCollection.Count} 个物流模型"); if (logisticsItemsCollection.Count > 0) @@ -2873,7 +2872,7 @@ namespace NavisworksTransport var document = Application.ActiveDocument; if (document != null) { - var foundChannelsCollection = CategoryAttributeManager.GetAllLogisticsItems(document); + var foundChannelsCollection = CategoryAttributeManager.GetAllLogisticsItems(); foreach (ModelItem item in foundChannelsCollection) { channelItems.Add(item); @@ -3291,7 +3290,7 @@ namespace NavisworksTransport // 计算网格单元格的世界中心位置 var gridPos = new NavisworksTransport.PathPlanning.GridPoint2D(x, y); - var gridCorner = gridMap.GridToWorld3D(gridPos, cell.WorldPosition.Z); + var gridCorner = gridMap.GridToWorld3D(gridPos); var gridCenter = new Point3D( gridCorner.X + gridMap.CellSize / 2, gridCorner.Y + gridMap.CellSize / 2, diff --git a/src/Core/Properties/CategoryAttributeManager.cs b/src/Core/Properties/CategoryAttributeManager.cs index d5c72b3..2c42b32 100644 --- a/src/Core/Properties/CategoryAttributeManager.cs +++ b/src/Core/Properties/CategoryAttributeManager.cs @@ -115,7 +115,7 @@ namespace NavisworksTransport } // 准备属性字典 - var properties = new System.Collections.Generic.Dictionary + var properties = new Dictionary { { LogisticsProperties.TYPE, elementType.ToString() }, { LogisticsProperties.TRAVERSABLE, isTraversable ? "是" : "否" }, @@ -126,7 +126,7 @@ namespace NavisworksTransport }; // 准备内部名称字典 - var propertyInternalNames = new System.Collections.Generic.Dictionary + var propertyInternalNames = new Dictionary { { LogisticsProperties.TYPE, elementType.ToString() + "_Internal" }, { LogisticsProperties.TRAVERSABLE, "Traversable_Internal" }, @@ -212,22 +212,11 @@ namespace NavisworksTransport /// 物流元素类型 /// 文档,如果为null则使用ActiveDocument /// 指定类型的物流项目列表 - public static List GetLogisticsItemsByType(LogisticsElementType elementType, Document document = null) + public static List GetLogisticsItemsByType(LogisticsElementType elementType) { try { - // 如果没有提供文档,使用ActiveDocument - if (document == null) - document = Application.ActiveDocument; - - // 检查文档有效性 - if (document?.Models == null) - { - LogManager.Warning($"[CategoryAttributeManager] 无法获取文档模型"); - return new List(); - } - - // 直接使用 Search API 搜索整个文档 + // 使用 Search API 搜索当前活动文档 using (var search = new Search()) { // 搜索整个文档的根项及其所有后代 @@ -246,7 +235,7 @@ namespace NavisworksTransport .EqualValue(VariantData.FromDisplayString(elementType.ToString()))); // 执行搜索并返回列表 - return search.FindAll(document, false).ToList(); + return search.FindAll(Application.ActiveDocument, false).ToList(); } } catch (Exception ex) @@ -358,7 +347,7 @@ namespace NavisworksTransport /// /// Navisworks文档 /// 具有物流属性的模型项集合 - public static ModelItemCollection GetAllLogisticsItems(Document document) + public static ModelItemCollection GetAllLogisticsItems() { try { @@ -369,7 +358,7 @@ namespace NavisworksTransport searchConditions.Clear(); var hasLogisticsCondition = SearchCondition.HasCategoryByDisplayName(LogisticsCategories.LOGISTICS); searchConditions.Add(hasLogisticsCondition); - return search.FindAll(document, false); + return search.FindAll(Application.ActiveDocument, false); } } catch (Exception ex) @@ -384,9 +373,9 @@ namespace NavisworksTransport /// /// Navisworks文档 /// 可通行的物流模型项集合 - public static ModelItemCollection GetAllTraversableLogisticsItems(Document document) + public static ModelItemCollection GetAllTraversableLogisticsItems() { - var allLogisticsItems = GetAllLogisticsItems(document); + var allLogisticsItems = GetAllLogisticsItems(); return FilterTraversableItems(allLogisticsItems); } diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs index 2a31e6b..301a9a3 100644 --- a/src/PathPlanning/AutoPathFinder.cs +++ b/src/PathPlanning/AutoPathFinder.cs @@ -1354,12 +1354,12 @@ namespace NavisworksTransport.PathPlanning var actualEndGridY = (int)Math.Floor(lastNode.Position.Y / cellSizeInMeters); var actualEndGrid = new GridPoint2D(actualEndGridX, actualEndGridY); var actualEndCell = gridMap.GetCell(actualEndGrid); - var actualEndWorld = gridMap.GridToWorld3D(actualEndGrid, actualEndCell?.WorldPosition.Z ?? 0); + var actualEndWorld = gridMap.GridToWorld3D(actualEndGrid); var originalEndCell = gridMap.GetCell(endGrid); - var originalEndWorld = gridMap.GridToWorld3D(endGrid, originalEndCell?.WorldPosition.Z ?? 0); + var originalEndWorld = gridMap.GridToWorld3D(endGrid); var startWorldCell = gridMap.GetCell(startGrid); - var startWorld = gridMap.GridToWorld3D(startGrid, startWorldCell?.WorldPosition.Z ?? 0); + var startWorld = gridMap.GridToWorld3D(startGrid); var totalDistance = CalculateDistance(startWorld, originalEndWorld); var actualDistance = CalculateDistance(startWorld, actualEndWorld); diff --git a/src/PathPlanning/ChannelBasedGridBuilder.cs b/src/PathPlanning/ChannelBasedGridBuilder.cs index ae48cd8..922e34b 100644 --- a/src/PathPlanning/ChannelBasedGridBuilder.cs +++ b/src/PathPlanning/ChannelBasedGridBuilder.cs @@ -56,16 +56,13 @@ namespace NavisworksTransport.PathPlanning /// 网格单元格大小 /// 当前文档 /// 通道覆盖信息 - public ChannelCoverage BuildChannelCoverage(double gridSize, Document document = null) + public ChannelCoverage BuildChannelCoverage(double gridSize) { - if (document == null) - document = Application.ActiveDocument; - LogManager.Info("[通道网格构建器] 开始构建通道覆盖网格"); // 1. 获取所有通道物品 var channelItems = CategoryAttributeManager.GetLogisticsItemsByType( - CategoryAttributeManager.LogisticsElementType.通道, document); + CategoryAttributeManager.LogisticsElementType.通道); if (!channelItems.Any()) { @@ -119,34 +116,13 @@ namespace NavisworksTransport.PathPlanning LogManager.Info($"[通道网格构建器] 投影通道: {channel.DisplayName}"); // 提取三角形几何 - var triangles = GeometryHelper.ExtractTrianglesOptimized(channel); + var triangles = GeometryHelper.ExtractTriangles(channel); if (triangles.Count == 0) { LogManager.Warning($"[通道网格构建器] 未能从 '{channel.DisplayName}' 提取到三角形"); return; } - - // 分析通道类型 - var channelType = AnalyzeChannelType(triangles); - - // 统计三角形方向分布(用于调试) - int upFaces = 0, inclinedFaces = 0, downFaces = 0, sideFaces = 0; - foreach (var triangle in triangles) - { - var normal = ComputeTriangleNormal(triangle); - if (normal.Z > 0.7) - upFaces++; - else if (normal.Z > 0.2) - inclinedFaces++; - else if (normal.Z < -0.2) - downFaces++; - else - sideFaces++; - } - - LogManager.Info($"[通道网格构建器] 通道类型: {channelType}"); - LogManager.Info($"[通道网格构建器] 三角形分布: 朝上={upFaces}, 朝上斜面={inclinedFaces}, 朝下={downFaces}, 侧面={sideFaces}, 总计={triangles.Count}"); - + // 统一投影处理 int processedTriangles = 0; foreach (var triangle in triangles) @@ -154,18 +130,11 @@ namespace NavisworksTransport.PathPlanning var normal = ComputeTriangleNormal(triangle); // 根据法向量决定处理方式 - if (normal.Z > 0.7) // 朝上的水平面 + if (normal.Z > 0) // 朝上的面 { RasterizeTriangleToGrid(gridMap, triangle); processedTriangles++; } - else if (normal.Z > 0.2) // 可行走的斜面(楼梯、坡道) - { - // 对于斜面,也进行投影,适用于楼梯等倾斜通道 - // [TODO] 斜面应该用不同的计算顶面高度方法 - RasterizeTriangleToGrid(gridMap, triangle); - processedTriangles++; - } // 垂直面和朝下的面不投影 } @@ -205,6 +174,15 @@ namespace NavisworksTransport.PathPlanning return normal; } + private double GetHeightAtPoint(Triangle3D triangle, double x, double y) + { + var normal = ComputeTriangleNormal(triangle); + + // 使用平面方程计算点(x,y)在三角形平面上的Z值 + double height = triangle.Point1.Z - (normal.X * (x - triangle.Point1.X) + normal.Y * (y - triangle.Point1.Y)) / normal.Z; + return height; + } + /// /// 将三角形光栅化到网格 @@ -222,9 +200,6 @@ namespace NavisworksTransport.PathPlanning var minGrid = gridMap.WorldToGrid(new Point3D(minX, minY, 0)); var maxGrid = gridMap.WorldToGrid(new Point3D(maxX, maxY, 0)); - // 计算三角形的平均高度作为通道顶面高度 - double triangleHeight = (triangle.Point1.Z + triangle.Point2.Z + triangle.Point3.Z) / 3.0; - // 遍历边界框内的所有网格点 for (int x = minGrid.X; x <= maxGrid.X; x++) { @@ -233,13 +208,30 @@ namespace NavisworksTransport.PathPlanning var gridPos = new GridPoint2D(x, y); if (gridMap.IsValidGridPosition(gridPos)) { - var worldPos = gridMap.GridToWorld3D(gridPos, 0); // 2D检测,Z坐标不重要 + var worldPos = gridMap.GridToWorld3D(gridPos); // 检查点是否在三角形内部 if (GeometryHelper.IsPointInTriangle2D(worldPos, triangle)) { - // 传递实际的三角形高度 - SetCellAsChannel(gridMap, gridPos, null, triangleHeight); + // 计算网格中心点在三角形平面上的精确高度 + double gridCenterHeight = GetHeightAtPoint(triangle, worldPos.X, worldPos.Y); + + // 调试日志:打印生成的网格坐标和高度 + LogManager.Debug($"[调试] 生成通道网格: ({gridPos.X}, {gridPos.Y}) 高度: {gridCenterHeight:F2}"); + + // 直接设置网格属性 + var cell = new GridCell + { + IsWalkable = true, + CellType = CategoryAttributeManager.LogisticsElementType.通道, + IsInChannel = true, + PassableHeights = new List(), + ChannelType = ChannelType.Corridor, + WorldPosition = new Point3D(worldPos.X, worldPos.Y, gridCenterHeight), + RelatedModelItem = null + }; + cell.Cost = cell.GetCost(); + gridMap.Cells[gridPos.X, gridPos.Y] = cell; } } } @@ -247,17 +239,6 @@ namespace NavisworksTransport.PathPlanning } - /// - /// 设置网格单元为通道类型 - /// - /// 网格地图 - /// 网格位置 - /// 关联的模型物品 - private void SetCellAsChannel(GridMap gridMap, GridPoint2D gridPos, ModelItem relatedItem, double channelHeight = 0.0) - { - // 使用GridMap提供的方法设置通道单元格,传递实际高度 - gridMap.SetCellAsChannel(gridPos, ChannelType.Corridor, relatedItem, channelHeight); - } /// @@ -275,50 +256,6 @@ namespace NavisworksTransport.PathPlanning }; } - /// - /// 分析通道几何类型 - /// - /// 三角形列表 - /// 通道几何类型 - private ChannelGeometryType AnalyzeChannelType(List triangles) - { - if (triangles.Count == 0) - return ChannelGeometryType.Complex; - - int upwardHorizontalCount = 0; // 朝上的水平面 - int upwardInclinedCount = 0; // 朝上的斜面 - int upwardCount = 0; // 所有朝上的面 - - foreach (var triangle in triangles) - { - var normal = ComputeTriangleNormal(triangle); - - // 只统计朝上的面(与投影逻辑一致) - if (normal.Z > 0.7) - { - upwardHorizontalCount++; - upwardCount++; - } - else if (normal.Z > 0.2) // 朝上的斜面 - { - upwardInclinedCount++; - upwardCount++; - } - // 忽略朝下和侧面的三角形 - } - - // 基于朝上三角形的比例判断类型 - if (upwardCount == 0) - return ChannelGeometryType.Complex; - - if (upwardHorizontalCount > upwardCount * 0.6) - return ChannelGeometryType.Horizontal; // 主要是水平面 - else if (upwardInclinedCount > upwardCount * 0.3) - return ChannelGeometryType.Inclined; // 有较多斜面 - else - return ChannelGeometryType.Complex; - } - /// /// 格式化边界框信息用于日志 /// diff --git a/src/PathPlanning/ChannelHeightDetector.cs b/src/PathPlanning/ChannelHeightDetector.cs index 30f6b2f..4cc5c20 100644 --- a/src/PathPlanning/ChannelHeightDetector.cs +++ b/src/PathPlanning/ChannelHeightDetector.cs @@ -655,7 +655,7 @@ namespace NavisworksTransport.PathPlanning LogManager.Info($"[COM几何射线] 开始从ModelItem提取三角形几何数据: {channel.DisplayName}"); // 使用COM API提取三角形几何数据 - var triangles = GeometryHelper.ExtractTrianglesOptimized(channel); + var triangles = GeometryHelper.ExtractTriangles(channel); if (triangles.Count == 0) { LogManager.Warning($"[COM几何射线] 未能从ModelItem提取到三角形数据: {channel.DisplayName}"); diff --git a/src/PathPlanning/GridCache.cs b/src/PathPlanning/GridCache.cs deleted file mode 100644 index c40b7ef..0000000 --- a/src/PathPlanning/GridCache.cs +++ /dev/null @@ -1,331 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using Roy_T.AStar.Grids; - -namespace NavisworksTransport.PathPlanning -{ - /// - /// 缓存项,包含网格和访问时间信息 - /// - public class GridCacheItem - { - /// - /// 缓存的A*网格 - /// - public Grid Grid { get; set; } - - /// - /// 最后访问时间 - /// - public DateTime LastAccessTime { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreatedTime { get; set; } - - /// - /// 访问次数 - /// - public int AccessCount { get; set; } - - /// - /// 构造函数 - /// - public GridCacheItem(Grid grid) - { - Grid = grid ?? throw new ArgumentNullException(nameof(grid)); - LastAccessTime = DateTime.Now; - CreatedTime = DateTime.Now; - AccessCount = 1; - } - - /// - /// 记录访问 - /// - public void RecordAccess() - { - LastAccessTime = DateTime.Now; - AccessCount++; - } - } - - /// - /// 网格缓存统计信息 - /// - public class GridCacheStatistics - { - /// - /// 缓存命中次数 - /// - public long HitCount { get; set; } - - /// - /// 缓存未命中次数 - /// - public long MissCount { get; set; } - - /// - /// 当前缓存项数量 - /// - public int CurrentCount { get; set; } - - /// - /// 最大缓存项数量 - /// - public int MaxCapacity { get; set; } - - /// - /// 被驱逐的缓存项数量 - /// - public long EvictedCount { get; set; } - - /// - /// 缓存命中率 - /// - public double HitRate => HitCount + MissCount > 0 ? (double)HitCount / (HitCount + MissCount) : 0.0; - - /// - /// 缓存使用率 - /// - public double UsageRate => MaxCapacity > 0 ? (double)CurrentCount / MaxCapacity : 0.0; - - /// - /// 重置统计信息 - /// - public void Reset() - { - HitCount = 0; - MissCount = 0; - EvictedCount = 0; - } - - /// - /// 生成统计报告 - /// - public string GenerateReport() - { - return $"网格缓存统计 - 命中率: {HitRate:P2} ({HitCount}命中/{MissCount}未命中), " + - $"使用率: {UsageRate:P2} ({CurrentCount}/{MaxCapacity}), " + - $"驱逐次数: {EvictedCount}"; - } - } - - /// - /// 网格缓存管理器 - /// 使用LRU策略管理A*网格缓存,支持线程安全访问 - /// - public class GridCache - { - private readonly ConcurrentDictionary _cache; - private readonly object _cleanupLock = new object(); - private readonly int _maxCapacity; - private readonly GridCacheStatistics _statistics; - - /// - /// 获取缓存统计信息 - /// - public GridCacheStatistics Statistics => _statistics; - - /// - /// 构造函数 - /// - /// 最大缓存容量,默认20个网格 - public GridCache(int maxCapacity = 20) - { - if (maxCapacity <= 0) - throw new ArgumentException("缓存容量必须大于0", nameof(maxCapacity)); - - _maxCapacity = maxCapacity; - _cache = new ConcurrentDictionary(); - _statistics = new GridCacheStatistics { MaxCapacity = maxCapacity }; - - LogManager.Info($"[网格缓存] 初始化完成,最大容量: {_maxCapacity}"); - } - - /// - /// 获取缓存的网格 - /// - /// 缓存键 - /// 网格对象,如果不存在则返回null - public Grid Get(GridCacheKey key) - { - if (key == null) - return null; - - if (_cache.TryGetValue(key, out var item)) - { - // 记录访问 - item.RecordAccess(); - _statistics.HitCount++; - - LogManager.Debug($"[网格缓存] 命中 - {key},访问次数: {item.AccessCount}"); - return item.Grid; - } - - _statistics.MissCount++; - LogManager.Debug($"[网格缓存] 未命中 - {key}"); - return null; - } - - /// - /// 添加网格到缓存 - /// - /// 缓存键 - /// A*网格 - public void Put(GridCacheKey key, Grid grid) - { - if (key == null || grid == null) - return; - - var item = new GridCacheItem(grid); - - // 添加或更新缓存项 - _cache.AddOrUpdate(key, item, (k, existing) => - { - LogManager.Debug($"[网格缓存] 更新现有项 - {key}"); - return item; - }); - - // 更新统计信息 - _statistics.CurrentCount = _cache.Count; - - LogManager.Debug($"[网格缓存] 添加 - {key},当前容量: {_cache.Count}/{_maxCapacity}"); - - // 检查是否需要清理 - if (_cache.Count > _maxCapacity) - { - CleanupCache(); - } - } - - /// - /// 检查缓存中是否包含指定键 - /// - /// 缓存键 - /// 是否包含 - public bool ContainsKey(GridCacheKey key) - { - return key != null && _cache.ContainsKey(key); - } - - /// - /// 清除所有缓存 - /// - public void Clear() - { - lock (_cleanupLock) - { - var count = _cache.Count; - _cache.Clear(); - _statistics.CurrentCount = 0; - _statistics.Reset(); - - LogManager.Info($"[网格缓存] 清除所有缓存,共清除 {count} 项"); - } - } - - /// - /// 获取缓存的所有键 - /// - /// 键列表 - public IList GetKeys() - { - return _cache.Keys.ToList(); - } - - /// - /// 清理缓存,使用LRU策略移除最少使用的项 - /// - private void CleanupCache() - { - lock (_cleanupLock) - { - // 双重检查,避免重复清理 - if (_cache.Count <= _maxCapacity) - return; - - var targetCount = (int)(_maxCapacity * 0.8); // 清理到80%容量 - var itemsToRemove = _cache.Count - targetCount; - - LogManager.Info($"[网格缓存] 开始LRU清理,当前: {_cache.Count},目标: {targetCount},需移除: {itemsToRemove}"); - - // 按最后访问时间排序,移除最久未访问的项 - var sortedItems = _cache - .OrderBy(kvp => kvp.Value.LastAccessTime) - .ThenBy(kvp => kvp.Value.AccessCount) // 相同时间的情况下,优先移除访问次数少的 - .Take(itemsToRemove) - .ToList(); - - int removedCount = 0; - foreach (var item in sortedItems) - { - if (_cache.TryRemove(item.Key, out _)) - { - removedCount++; - _statistics.EvictedCount++; - LogManager.Debug($"[网格缓存] 移除 - {item.Key},最后访问: {item.Value.LastAccessTime:yyyy-MM-dd HH:mm:ss}"); - } - } - - _statistics.CurrentCount = _cache.Count; - LogManager.Info($"[网格缓存] LRU清理完成,实际移除: {removedCount},当前容量: {_cache.Count}"); - } - } - - /// - /// 生成缓存详细报告 - /// - /// 报告字符串 - public string GenerateDetailedReport() - { - var report = new System.Text.StringBuilder(); - - report.AppendLine(_statistics.GenerateReport()); - - if (_cache.Count > 0) - { - report.AppendLine($"\n当前缓存项详情:"); - var sortedItems = _cache - .OrderByDescending(kvp => kvp.Value.LastAccessTime) - .Take(10) // 只显示最近10项 - .ToList(); - - foreach (var item in sortedItems) - { - report.AppendLine($" {item.Key} - 访问{item.Value.AccessCount}次,最后访问: {item.Value.LastAccessTime:MM-dd HH:mm:ss}"); - } - - if (_cache.Count > 10) - { - report.AppendLine($" ... 还有 {_cache.Count - 10} 项"); - } - } - - return report.ToString(); - } - } - - /// - /// 全局网格缓存管理器(单例) - /// - public static class GlobalGridCache - { - private static readonly Lazy _instance = new Lazy(() => new GridCache()); - - /// - /// 获取全局缓存实例 - /// - public static GridCache Instance => _instance.Value; - - /// - /// 重置全局缓存(主要用于测试) - /// - public static void Reset() - { - _instance.Value.Clear(); - } - } -} \ No newline at end of file diff --git a/src/PathPlanning/GridCacheKey.cs b/src/PathPlanning/GridCacheKey.cs deleted file mode 100644 index bfcb542..0000000 --- a/src/PathPlanning/GridCacheKey.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.Security.Cryptography; -using System.Text; - -namespace NavisworksTransport.PathPlanning -{ - /// - /// 网格缓存键,用于标识唯一的网格配置 - /// 包含所有影响网格生成的因素:网格地图内容、车辆高度、路径策略、网格尺寸 - /// - public class GridCacheKey : IEquatable - { - /// - /// 网格地图内容哈希值 - /// - public string GridMapHash { get; set; } - - /// - /// 车辆高度(模型单位) - /// - public double VehicleHeight { get; set; } - - /// - /// 路径规划策略 - /// - public PathStrategy Strategy { get; set; } - - /// - /// 网格单元大小(模型单位) - /// - public double CellSize { get; set; } - - /// - /// 构造函数 - /// - /// 网格地图哈希 - /// 车辆高度 - /// 路径策略 - /// 网格尺寸 - public GridCacheKey(string gridMapHash, double vehicleHeight, PathStrategy strategy, double cellSize) - { - GridMapHash = gridMapHash ?? throw new ArgumentNullException(nameof(gridMapHash)); - VehicleHeight = vehicleHeight; - Strategy = strategy; - CellSize = cellSize; - } - - /// - /// 检查两个缓存键是否相等 - /// - public bool Equals(GridCacheKey other) - { - if (other == null) return false; - if (ReferenceEquals(this, other)) return true; - - return GridMapHash == other.GridMapHash && - Math.Abs(VehicleHeight - other.VehicleHeight) < 1e-6 && - Strategy == other.Strategy && - Math.Abs(CellSize - other.CellSize) < 1e-6; - } - - /// - /// 重写Equals方法 - /// - public override bool Equals(object obj) - { - return Equals(obj as GridCacheKey); - } - - /// - /// 计算哈希码,用于Dictionary键 - /// - public override int GetHashCode() - { - unchecked - { - int hash = 17; - hash = hash * 23 + (GridMapHash?.GetHashCode() ?? 0); - hash = hash * 23 + VehicleHeight.GetHashCode(); - hash = hash * 23 + Strategy.GetHashCode(); - hash = hash * 23 + CellSize.GetHashCode(); - return hash; - } - } - - /// - /// 生成可读的字符串表示 - /// - public override string ToString() - { - var hashPreview = GridMapHash != null ? - GridMapHash.Substring(0, Math.Min(8, GridMapHash.Length)) : - "NULL"; - return $"GridCacheKey[Hash={hashPreview}, " + - $"Height={VehicleHeight:F2}, Strategy={Strategy}, CellSize={CellSize:F2}]"; - } - - /// - /// 根据GridMap和参数生成缓存键 - /// - /// 网格地图 - /// 车辆高度 - /// 路径策略 - /// 缓存键 - public static GridCacheKey CreateFrom(GridMap gridMap, double vehicleHeight, PathStrategy strategy) - { - if (gridMap == null) - throw new ArgumentNullException(nameof(gridMap)); - - var gridMapHash = ComputeGridMapHash(gridMap); - return new GridCacheKey(gridMapHash, vehicleHeight, strategy, gridMap.CellSize); - } - - /// - /// 计算GridMap的内容哈希值 - /// 基于网格尺寸、单元格可通行性、高度约束等关键数据 - /// - /// 网格地图 - /// SHA256哈希字符串 - private static string ComputeGridMapHash(GridMap gridMap) - { - try - { - using (var sha256 = SHA256.Create()) - { - var hashBuilder = new StringBuilder(); - - // 基础属性 - hashBuilder.Append($"W:{gridMap.Width}|H:{gridMap.Height}|CS:{gridMap.CellSize:F6}|"); - hashBuilder.Append($"OX:{gridMap.Origin.X:F6}|OY:{gridMap.Origin.Y:F6}|OZ:{gridMap.Origin.Z:F6}|"); - - // 遍历所有网格单元 - for (int x = 0; x < gridMap.Width; x++) - { - for (int y = 0; y < gridMap.Height; y++) - { - var cell = gridMap.Cells[x, y]; - - // 基本属性 - hashBuilder.Append($"[{x},{y}]:{(cell.IsWalkable ? "1" : "0")}|{cell.CellType}|"); - - // 世界位置 - hashBuilder.Append($"WP:{cell.WorldPosition.X:F3},{cell.WorldPosition.Y:F3},{cell.WorldPosition.Z:F3}|"); - - // 高度约束区间 - if (cell.PassableHeights != null && cell.PassableHeights.Count > 0) - { - hashBuilder.Append("PH:"); - foreach (var interval in cell.PassableHeights) - { - hashBuilder.Append($"{interval.MinZ:F3}-{interval.MaxZ:F3},"); - } - hashBuilder.Append("|"); - } - else - { - hashBuilder.Append("PH:NONE|"); - } - } - } - - // 计算哈希 - var hashInput = hashBuilder.ToString(); - var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(hashInput)); - - // 转换为十六进制字符串 - var hexBuilder = new StringBuilder(); - foreach (byte b in hashBytes) - { - hexBuilder.Append(b.ToString("x2")); - } - - return hexBuilder.ToString(); - } - } - catch (Exception ex) - { - // 如果哈希计算失败,返回基于时间戳的唯一标识 - // 这样可以确保缓存不会错误命中,但会失去缓存效果 - LogManager.Warning($"[网格哈希] 计算网格哈希失败: {ex.Message},使用时间戳标识"); - return $"HASH_FAILED_{DateTime.Now.Ticks}"; - } - } - } -} \ No newline at end of file diff --git a/src/PathPlanning/GridMap.cs b/src/PathPlanning/GridMap.cs index 66f1fb2..a8f0cb8 100644 --- a/src/PathPlanning/GridMap.cs +++ b/src/PathPlanning/GridMap.cs @@ -205,10 +205,11 @@ namespace NavisworksTransport.PathPlanning /// 网格坐标 /// Z坐标值 /// 世界3D坐标点 - public Point3D GridToWorld3D(GridPoint2D gridPosition, double z) + public Point3D GridToWorld3D(GridPoint2D gridPosition) { var world2D = GridToWorld2D(gridPosition); - return new Point3D(world2D.X, world2D.Y, z); + var cell = Cells[gridPosition.X, gridPosition.Y]; + return new Point3D(world2D.X, world2D.Y, cell.WorldPosition.Z); } @@ -249,8 +250,7 @@ namespace NavisworksTransport.PathPlanning if (!IsValidGridPosition(gridPosition)) return; - var existingZ = Cells[gridPosition.X, gridPosition.Y].WorldPosition.Z; - var worldPos = GridToWorld3D(gridPosition, existingZ); + var worldPos = GridToWorld3D(gridPosition); var cell = new GridCell { IsWalkable = isWalkable, @@ -277,37 +277,6 @@ namespace NavisworksTransport.PathPlanning Cells[gridPosition.X, gridPosition.Y] = cell; } - /// - /// 设置网格单元为通道类型 - A*改进方案新增方法 - /// - /// 网格坐标 - /// 通道类型 - /// 关联的模型物品 - public void SetCellAsChannel(GridPoint2D gridPosition, ChannelType channelType = ChannelType.Corridor, ModelItem relatedItem = null, double channelHeight = 0.0) - { - if (!IsValidGridPosition(gridPosition)) - return; - - // 使用传入的通道高度,如果没有提供则使用现有的Z坐标或边界最大Z - double actualZ = channelHeight > 0 ? channelHeight : - (Cells[gridPosition.X, gridPosition.Y].WorldPosition.Z > 0 ? - Cells[gridPosition.X, gridPosition.Y].WorldPosition.Z : - Bounds.Max.Z); - - var worldPos = GridToWorld3D(gridPosition, actualZ); - var cell = new GridCell - { - IsWalkable = true, - CellType = CategoryAttributeManager.LogisticsElementType.通道, - IsInChannel = true, - PassableHeights = new List(), - ChannelType = channelType, - WorldPosition = worldPos, - RelatedModelItem = relatedItem - }; - cell.Cost = cell.GetCost(); // 用GetCost方法计算通道成本 - Cells[gridPosition.X, gridPosition.Y] = cell; - } /// /// 添加可通行高度区间到指定网格单元 diff --git a/src/PathPlanning/GridMapCacheKey.cs b/src/PathPlanning/GridMapCacheKey.cs index a85d0a8..244a51d 100644 --- a/src/PathPlanning/GridMapCacheKey.cs +++ b/src/PathPlanning/GridMapCacheKey.cs @@ -125,14 +125,11 @@ namespace NavisworksTransport.PathPlanning /// 安全间隙(米) /// 车辆高度(米) /// 缓存键 - public static GridMapCacheKey CreateFrom(Document document, BoundingBox3D bounds, + public static GridMapCacheKey CreateFrom(BoundingBox3D bounds, double cellSize, double vehicleRadius, double safetyMargin, double vehicleHeight) { - if (document == null) - throw new ArgumentNullException(nameof(document)); - - var channelDataHash = ComputeChannelDataHash(document); + var channelDataHash = ComputeChannelDataHash(); var boundsHash = ComputeBoundsHash(bounds); return new GridMapCacheKey(channelDataHash, boundsHash, cellSize, @@ -143,9 +140,8 @@ namespace NavisworksTransport.PathPlanning /// 计算通道数据哈希值 /// 基于所有可通行物流构件的ID、属性、几何等信息 /// - /// Navisworks文档 /// 通道数据哈希 - private static string ComputeChannelDataHash(Document document) + private static string ComputeChannelDataHash() { try { @@ -154,7 +150,7 @@ namespace NavisworksTransport.PathPlanning var hashBuilder = new StringBuilder(); // 获取所有可通行的物流模型项 - var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document); + var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(); // 按InstanceGuid排序确保一致性 var sortedItems = allChannelItems.OrderBy(item => item.InstanceGuid.ToString()).ToList(); diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs index 0476995..b7addde 100644 --- a/src/PathPlanning/GridMapGenerator.cs +++ b/src/PathPlanning/GridMapGenerator.cs @@ -53,12 +53,12 @@ namespace NavisworksTransport.PathPlanning /// 规划终点 /// 车辆高度(米) /// 生成的网格地图 - public GridMap GenerateFromBIM(Document document, BoundingBox3D bounds, double cellSize, double vehicleRadius, double safetyMargin, Point3D planningStartPoint, Point3D planningEndPoint, double vehicleHeight) + public GridMap GenerateFromBIM(BoundingBox3D bounds, double cellSize, double vehicleRadius, double safetyMargin, Point3D planningStartPoint, Point3D planningEndPoint, double vehicleHeight) { try { // 生成缓存键 - var cacheKey = GridMapCacheKey.CreateFrom(document, bounds, cellSize, vehicleRadius, safetyMargin, vehicleHeight); + var cacheKey = GridMapCacheKey.CreateFrom(bounds, cellSize, vehicleRadius, safetyMargin, vehicleHeight); // 尝试从缓存获取 var cachedGridMap = GlobalGridMapCache.Instance.Get(cacheKey, 5000); // 预估生成需要5秒 @@ -102,7 +102,7 @@ namespace NavisworksTransport.PathPlanning // 1. 使用通道构建器构建基础通道覆盖网格 LogManager.Info("【生成网格地图】步骤1: 构建通道覆盖网格"); - var channelCoverage = _channelBuilder.BuildChannelCoverage(cellSizeInModelUnits, document); + var channelCoverage = _channelBuilder.BuildChannelCoverage(cellSizeInModelUnits); LogManager.Info($"【生成网格地图】通道覆盖构建完成: {channelCoverage.GetStatistics()}"); LogManager.Info($"【阶段1完成】通道覆盖网格统计: {channelCoverage.GridMap.GetStatistics()}"); @@ -111,7 +111,7 @@ namespace NavisworksTransport.PathPlanning LogManager.Info("【生成网格地图】步骤1.5: 智能收集通道相关节点"); // 直接获取所有可通行的物流模型项 - var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document); + var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(); LogManager.Info($"【生成网格地图】直接获取到 {allChannelItems.Count} 个可通行物流元素"); // 智能收集可通行模型相关节点(包括父节点、同级节点等) @@ -120,7 +120,7 @@ namespace NavisworksTransport.PathPlanning // 2. 直接遍历处理障碍物(包围盒检测) - 使用高性能优化版本 LogManager.Info("【生成网格地图】步骤2: 高性能包围盒遍历处理障碍物"); - ProcessObstaclesWithBoundingBoxOptimized(document, channelCoverage.GridMap, channelRelatedItems, scanHeightInModelUnits); + ProcessObstaclesWithBoundingBoxOptimized(channelCoverage.GridMap, channelRelatedItems, scanHeightInModelUnits); LogManager.Info($"【阶段2完成】障碍物处理后网格统计: {channelCoverage.GridMap.GetStatistics()}"); @@ -292,8 +292,8 @@ namespace NavisworksTransport.PathPlanning var cell = gridMap.Cells[x, y]; // 更新门网格的Z坐标为门底部位置 - var worldPos = gridMap.GridToWorld3D(gridPos, doorBottomZ); - cell.WorldPosition = worldPos; + var world2D = gridMap.GridToWorld2D(gridPos); + cell.WorldPosition = new Point3D(world2D.X, world2D.Y, doorBottomZ); // 将门的高度范围存储在PassableHeights中供后续使用(使用相对坐标) if (doorHeight > 0) @@ -407,7 +407,7 @@ namespace NavisworksTransport.PathPlanning if (cell.CellType == CategoryAttributeManager.LogisticsElementType.通道 && cell.IsInChannel) { // 使用网格的实际Z坐标 - var worldPos = gridMap.GridToWorld3D(new GridPoint2D(x, y), cell.WorldPosition.Z); + var worldPos = gridMap.GridToWorld3D(new GridPoint2D(x, y)); // 重要修复:使用通道顶面作为垂直扫描的起点,而不是底面 // 从通道构建器的日志可知通道总边界MaxZ = 38.8,这是正确的扫描起点 @@ -942,7 +942,7 @@ namespace NavisworksTransport.PathPlanning /// 使用Search API一次性获取所有有几何体的模型项 /// 这比逐项调用HasGeometry快得多 /// - private List GetGeometryItemsUsingSearchAPI(Document document) + private List GetGeometryItemsUsingSearchAPI() { try { @@ -950,7 +950,7 @@ namespace NavisworksTransport.PathPlanning var startTime = DateTime.Now; // 统计模型总数 - var totalModelItems = document.Models.RootItemDescendantsAndSelf.Count(); + var totalModelItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf.Count(); LogManager.Info($"[Search API优化] 模型总数统计: {totalModelItems} 个模型项"); var search = new Search(); @@ -963,7 +963,7 @@ namespace NavisworksTransport.PathPlanning search.Locations = SearchLocations.DescendantsAndSelf; // 一次性获取所有有几何体的项目 - var geometryItems = search.FindAll(document, false); + var geometryItems = search.FindAll(Application.ActiveDocument, false); var searchElapsed = (DateTime.Now - startTime).TotalMilliseconds; var filteredByGeometry = totalModelItems - geometryItems.Count(); @@ -1146,7 +1146,7 @@ namespace NavisworksTransport.PathPlanning /// 网格地图 /// 通道模型项列表 /// 扫描高度范围(模型单位) - private void ProcessObstaclesWithBoundingBoxOptimized(Document document, GridMap gridMap, HashSet channelItems, double scanHeightInModelUnits) + private void ProcessObstaclesWithBoundingBoxOptimized(GridMap gridMap, HashSet channelItems, double scanHeightInModelUnits) { try { @@ -1156,7 +1156,7 @@ namespace NavisworksTransport.PathPlanning var channelItemsSet = channelItems ?? new HashSet(); // 阶段1:Search API预筛选(一次性批量获取几何体项目) - var geometryItems = GetGeometryItemsUsingSearchAPI(document); + var geometryItems = GetGeometryItemsUsingSearchAPI(); LogManager.Info($"[高性能障碍物处理] 输入统计 - 几何体项目: {geometryItems.Count}, 通道元素: {channelItemsSet.Count}"); diff --git a/src/PathPlanning/VerticalScanProcessor.cs b/src/PathPlanning/VerticalScanProcessor.cs index 3480b89..62d15d5 100644 --- a/src/PathPlanning/VerticalScanProcessor.cs +++ b/src/PathPlanning/VerticalScanProcessor.cs @@ -842,7 +842,7 @@ namespace NavisworksTransport.PathPlanning return null; // 直接提取模型项的三角形几何数据 - var triangles = GeometryHelper.ExtractTrianglesOptimized(item); + var triangles = GeometryHelper.ExtractTriangles(item); if (triangles == null || triangles.Count == 0) { return null; diff --git a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs index 76cda16..3a4a6f2 100644 --- a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs +++ b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs @@ -716,7 +716,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels { // 直接使用 CategoryAttributeManager 的 API var document = NavisApplication.ActiveDocument; - var logisticsItems = CategoryAttributeManager.GetAllLogisticsItems(document); + var logisticsItems = CategoryAttributeManager.GetAllLogisticsItems(); var models = new List(); foreach (var item in logisticsItems) @@ -1121,7 +1121,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels return; } - var logisticsItems = CategoryAttributeManager.GetAllLogisticsItems(document); + var logisticsItems = CategoryAttributeManager.GetAllLogisticsItems(); if (logisticsItems == null || logisticsItems.Count == 0) { diff --git a/src/Utils/GeometryHelper.cs b/src/Utils/GeometryHelper.cs index 131b671..5bea062 100644 --- a/src/Utils/GeometryHelper.cs +++ b/src/Utils/GeometryHelper.cs @@ -100,7 +100,7 @@ namespace NavisworksTransport } // 使用优化的几何提取方法 - var triangles = ExtractTrianglesOptimized(targetItem); + var triangles = ExtractTriangles(targetItem); LogManager.WriteLog($"提取到 {triangles.Count} 个三角形"); if (triangles.Count > 0) @@ -124,11 +124,11 @@ namespace NavisworksTransport } /// - /// 优化的三角形提取方法 + /// 提取三角形 /// /// 模型项 /// 三角形集合 - public static List ExtractTrianglesOptimized(ModelItem modelItem) + public static List ExtractTriangles(ModelItem modelItem) { var triangles = new List();