修改了通道网格生成的高度设置,能在斜面上生成网格,可视化也对了

This commit is contained in:
tian 2025-09-28 16:20:04 +08:00
parent cd74f857a2
commit 8a95820fca
17 changed files with 88 additions and 717 deletions

View File

@ -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模式设计好方案和任务清单并征得我同意。
- 网格坐标代表的是网格单元的左下角,而不是中心点!

View File

@ -163,8 +163,6 @@
<Compile Include="src\PathPlanning\ChannelBasedGridBuilder.cs" />
<Compile Include="src\PathPlanning\VerticalScanProcessor.cs" />
<Compile Include="src\PathPlanning\PathOptimizer.cs" />
<Compile Include="src\PathPlanning\GridCacheKey.cs" />
<Compile Include="src\PathPlanning\GridCache.cs" />
<Compile Include="src\PathPlanning\GridMapCacheKey.cs" />
<Compile Include="src\PathPlanning\GridMapCache.cs" />

View File

@ -2,6 +2,14 @@
## 功能点
### [2025/09/28]
1. [x] 优化识别表面不平的通道给网格正确的表面z值
### [2025/09/27]
1. [x] (优化)为生成的网格地图增加缓存,提高路径规划性能
### [2025/09/15]
1. [x] (功能)修改碰撞检测报告,增加碰撞构件的数量和清单

View File

@ -1113,7 +1113,7 @@ namespace NavisworksTransport
var document = Application.ActiveDocument;
// 获取所有可通行的物流模型项
var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document);
var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems();
if (allChannelItems.Count == 0)
{

View File

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

View File

@ -115,7 +115,7 @@ namespace NavisworksTransport
}
// 准备属性字典
var properties = new System.Collections.Generic.Dictionary<string, string>
var properties = new Dictionary<string, string>
{
{ LogisticsProperties.TYPE, elementType.ToString() },
{ LogisticsProperties.TRAVERSABLE, isTraversable ? "是" : "否" },
@ -126,7 +126,7 @@ namespace NavisworksTransport
};
// 准备内部名称字典
var propertyInternalNames = new System.Collections.Generic.Dictionary<string, string>
var propertyInternalNames = new Dictionary<string, string>
{
{ LogisticsProperties.TYPE, elementType.ToString() + "_Internal" },
{ LogisticsProperties.TRAVERSABLE, "Traversable_Internal" },
@ -212,22 +212,11 @@ namespace NavisworksTransport
/// <param name="elementType">物流元素类型</param>
/// <param name="document">文档如果为null则使用ActiveDocument</param>
/// <returns>指定类型的物流项目列表</returns>
public static List<ModelItem> GetLogisticsItemsByType(LogisticsElementType elementType, Document document = null)
public static List<ModelItem> GetLogisticsItemsByType(LogisticsElementType elementType)
{
try
{
// 如果没有提供文档使用ActiveDocument
if (document == null)
document = Application.ActiveDocument;
// 检查文档有效性
if (document?.Models == null)
{
LogManager.Warning($"[CategoryAttributeManager] 无法获取文档模型");
return new List<ModelItem>();
}
// 直接使用 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
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <returns>具有物流属性的模型项集合</returns>
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
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <returns>可通行的物流模型项集合</returns>
public static ModelItemCollection GetAllTraversableLogisticsItems(Document document)
public static ModelItemCollection GetAllTraversableLogisticsItems()
{
var allLogisticsItems = GetAllLogisticsItems(document);
var allLogisticsItems = GetAllLogisticsItems();
return FilterTraversableItems(allLogisticsItems);
}

View File

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

View File

@ -56,16 +56,13 @@ namespace NavisworksTransport.PathPlanning
/// <param name="gridSize">网格单元格大小</param>
/// <param name="document">当前文档</param>
/// <returns>通道覆盖信息</returns>
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;
}
/// <summary>
/// 将三角形光栅化到网格
@ -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<HeightInterval>(),
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
}
/// <summary>
/// 设置网格单元为通道类型
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="gridPos">网格位置</param>
/// <param name="relatedItem">关联的模型物品</param>
private void SetCellAsChannel(GridMap gridMap, GridPoint2D gridPos, ModelItem relatedItem, double channelHeight = 0.0)
{
// 使用GridMap提供的方法设置通道单元格传递实际高度
gridMap.SetCellAsChannel(gridPos, ChannelType.Corridor, relatedItem, channelHeight);
}
/// <summary>
@ -275,50 +256,6 @@ namespace NavisworksTransport.PathPlanning
};
}
/// <summary>
/// 分析通道几何类型
/// </summary>
/// <param name="triangles">三角形列表</param>
/// <returns>通道几何类型</returns>
private ChannelGeometryType AnalyzeChannelType(List<Triangle3D> 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;
}
/// <summary>
/// 格式化边界框信息用于日志
/// </summary>

View File

@ -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}");

View File

@ -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
{
/// <summary>
/// 缓存项,包含网格和访问时间信息
/// </summary>
public class GridCacheItem
{
/// <summary>
/// 缓存的A*网格
/// </summary>
public Grid Grid { get; set; }
/// <summary>
/// 最后访问时间
/// </summary>
public DateTime LastAccessTime { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedTime { get; set; }
/// <summary>
/// 访问次数
/// </summary>
public int AccessCount { get; set; }
/// <summary>
/// 构造函数
/// </summary>
public GridCacheItem(Grid grid)
{
Grid = grid ?? throw new ArgumentNullException(nameof(grid));
LastAccessTime = DateTime.Now;
CreatedTime = DateTime.Now;
AccessCount = 1;
}
/// <summary>
/// 记录访问
/// </summary>
public void RecordAccess()
{
LastAccessTime = DateTime.Now;
AccessCount++;
}
}
/// <summary>
/// 网格缓存统计信息
/// </summary>
public class GridCacheStatistics
{
/// <summary>
/// 缓存命中次数
/// </summary>
public long HitCount { get; set; }
/// <summary>
/// 缓存未命中次数
/// </summary>
public long MissCount { get; set; }
/// <summary>
/// 当前缓存项数量
/// </summary>
public int CurrentCount { get; set; }
/// <summary>
/// 最大缓存项数量
/// </summary>
public int MaxCapacity { get; set; }
/// <summary>
/// 被驱逐的缓存项数量
/// </summary>
public long EvictedCount { get; set; }
/// <summary>
/// 缓存命中率
/// </summary>
public double HitRate => HitCount + MissCount > 0 ? (double)HitCount / (HitCount + MissCount) : 0.0;
/// <summary>
/// 缓存使用率
/// </summary>
public double UsageRate => MaxCapacity > 0 ? (double)CurrentCount / MaxCapacity : 0.0;
/// <summary>
/// 重置统计信息
/// </summary>
public void Reset()
{
HitCount = 0;
MissCount = 0;
EvictedCount = 0;
}
/// <summary>
/// 生成统计报告
/// </summary>
public string GenerateReport()
{
return $"网格缓存统计 - 命中率: {HitRate:P2} ({HitCount}命中/{MissCount}未命中), " +
$"使用率: {UsageRate:P2} ({CurrentCount}/{MaxCapacity}), " +
$"驱逐次数: {EvictedCount}";
}
}
/// <summary>
/// 网格缓存管理器
/// 使用LRU策略管理A*网格缓存,支持线程安全访问
/// </summary>
public class GridCache
{
private readonly ConcurrentDictionary<GridCacheKey, GridCacheItem> _cache;
private readonly object _cleanupLock = new object();
private readonly int _maxCapacity;
private readonly GridCacheStatistics _statistics;
/// <summary>
/// 获取缓存统计信息
/// </summary>
public GridCacheStatistics Statistics => _statistics;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="maxCapacity">最大缓存容量默认20个网格</param>
public GridCache(int maxCapacity = 20)
{
if (maxCapacity <= 0)
throw new ArgumentException("缓存容量必须大于0", nameof(maxCapacity));
_maxCapacity = maxCapacity;
_cache = new ConcurrentDictionary<GridCacheKey, GridCacheItem>();
_statistics = new GridCacheStatistics { MaxCapacity = maxCapacity };
LogManager.Info($"[网格缓存] 初始化完成,最大容量: {_maxCapacity}");
}
/// <summary>
/// 获取缓存的网格
/// </summary>
/// <param name="key">缓存键</param>
/// <returns>网格对象如果不存在则返回null</returns>
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;
}
/// <summary>
/// 添加网格到缓存
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="grid">A*网格</param>
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();
}
}
/// <summary>
/// 检查缓存中是否包含指定键
/// </summary>
/// <param name="key">缓存键</param>
/// <returns>是否包含</returns>
public bool ContainsKey(GridCacheKey key)
{
return key != null && _cache.ContainsKey(key);
}
/// <summary>
/// 清除所有缓存
/// </summary>
public void Clear()
{
lock (_cleanupLock)
{
var count = _cache.Count;
_cache.Clear();
_statistics.CurrentCount = 0;
_statistics.Reset();
LogManager.Info($"[网格缓存] 清除所有缓存,共清除 {count} 项");
}
}
/// <summary>
/// 获取缓存的所有键
/// </summary>
/// <returns>键列表</returns>
public IList<GridCacheKey> GetKeys()
{
return _cache.Keys.ToList();
}
/// <summary>
/// 清理缓存使用LRU策略移除最少使用的项
/// </summary>
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}");
}
}
/// <summary>
/// 生成缓存详细报告
/// </summary>
/// <returns>报告字符串</returns>
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();
}
}
/// <summary>
/// 全局网格缓存管理器(单例)
/// </summary>
public static class GlobalGridCache
{
private static readonly Lazy<GridCache> _instance = new Lazy<GridCache>(() => new GridCache());
/// <summary>
/// 获取全局缓存实例
/// </summary>
public static GridCache Instance => _instance.Value;
/// <summary>
/// 重置全局缓存(主要用于测试)
/// </summary>
public static void Reset()
{
_instance.Value.Clear();
}
}
}

View File

@ -1,185 +0,0 @@
using System;
using System.Security.Cryptography;
using System.Text;
namespace NavisworksTransport.PathPlanning
{
/// <summary>
/// 网格缓存键,用于标识唯一的网格配置
/// 包含所有影响网格生成的因素:网格地图内容、车辆高度、路径策略、网格尺寸
/// </summary>
public class GridCacheKey : IEquatable<GridCacheKey>
{
/// <summary>
/// 网格地图内容哈希值
/// </summary>
public string GridMapHash { get; set; }
/// <summary>
/// 车辆高度(模型单位)
/// </summary>
public double VehicleHeight { get; set; }
/// <summary>
/// 路径规划策略
/// </summary>
public PathStrategy Strategy { get; set; }
/// <summary>
/// 网格单元大小(模型单位)
/// </summary>
public double CellSize { get; set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="gridMapHash">网格地图哈希</param>
/// <param name="vehicleHeight">车辆高度</param>
/// <param name="strategy">路径策略</param>
/// <param name="cellSize">网格尺寸</param>
public GridCacheKey(string gridMapHash, double vehicleHeight, PathStrategy strategy, double cellSize)
{
GridMapHash = gridMapHash ?? throw new ArgumentNullException(nameof(gridMapHash));
VehicleHeight = vehicleHeight;
Strategy = strategy;
CellSize = cellSize;
}
/// <summary>
/// 检查两个缓存键是否相等
/// </summary>
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;
}
/// <summary>
/// 重写Equals方法
/// </summary>
public override bool Equals(object obj)
{
return Equals(obj as GridCacheKey);
}
/// <summary>
/// 计算哈希码用于Dictionary键
/// </summary>
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;
}
}
/// <summary>
/// 生成可读的字符串表示
/// </summary>
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}]";
}
/// <summary>
/// 根据GridMap和参数生成缓存键
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="vehicleHeight">车辆高度</param>
/// <param name="strategy">路径策略</param>
/// <returns>缓存键</returns>
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);
}
/// <summary>
/// 计算GridMap的内容哈希值
/// 基于网格尺寸、单元格可通行性、高度约束等关键数据
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <returns>SHA256哈希字符串</returns>
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}";
}
}
}
}

View File

@ -205,10 +205,11 @@ namespace NavisworksTransport.PathPlanning
/// <param name="gridPosition">网格坐标</param>
/// <param name="z">Z坐标值</param>
/// <returns>世界3D坐标点</returns>
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;
}
/// <summary>
/// 设置网格单元为通道类型 - A*改进方案新增方法
/// </summary>
/// <param name="gridPosition">网格坐标</param>
/// <param name="channelType">通道类型</param>
/// <param name="relatedItem">关联的模型物品</param>
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<HeightInterval>(),
ChannelType = channelType,
WorldPosition = worldPos,
RelatedModelItem = relatedItem
};
cell.Cost = cell.GetCost(); // 用GetCost方法计算通道成本
Cells[gridPosition.X, gridPosition.Y] = cell;
}
/// <summary>
/// 添加可通行高度区间到指定网格单元

View File

@ -125,14 +125,11 @@ namespace NavisworksTransport.PathPlanning
/// <param name="safetyMargin">安全间隙(米)</param>
/// <param name="vehicleHeight">车辆高度(米)</param>
/// <returns>缓存键</returns>
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、属性、几何等信息
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <returns>通道数据哈希</returns>
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();

View File

@ -53,12 +53,12 @@ namespace NavisworksTransport.PathPlanning
/// <param name="planningEndPoint">规划终点</param>
/// <param name="vehicleHeight">车辆高度(米)</param>
/// <returns>生成的网格地图</returns>
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快得多
/// </summary>
private List<ModelItem> GetGeometryItemsUsingSearchAPI(Document document)
private List<ModelItem> 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
/// <param name="gridMap">网格地图</param>
/// <param name="channelItems">通道模型项列表</param>
/// <param name="scanHeightInModelUnits">扫描高度范围(模型单位)</param>
private void ProcessObstaclesWithBoundingBoxOptimized(Document document, GridMap gridMap, HashSet<ModelItem> channelItems, double scanHeightInModelUnits)
private void ProcessObstaclesWithBoundingBoxOptimized(GridMap gridMap, HashSet<ModelItem> channelItems, double scanHeightInModelUnits)
{
try
{
@ -1156,7 +1156,7 @@ namespace NavisworksTransport.PathPlanning
var channelItemsSet = channelItems ?? new HashSet<ModelItem>();
// 阶段1Search API预筛选一次性批量获取几何体项目
var geometryItems = GetGeometryItemsUsingSearchAPI(document);
var geometryItems = GetGeometryItemsUsingSearchAPI();
LogManager.Info($"[高性能障碍物处理] 输入统计 - 几何体项目: {geometryItems.Count}, 通道元素: {channelItemsSet.Count}");

View File

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

View File

@ -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<LogisticsModel>();
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)
{

View File

@ -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
}
/// <summary>
/// 优化的三角形提取方法
/// 提取三角形
/// </summary>
/// <param name="modelItem">模型项</param>
/// <returns>三角形集合</returns>
public static List<Triangle3D> ExtractTrianglesOptimized(ModelItem modelItem)
public static List<Triangle3D> ExtractTriangles(ModelItem modelItem)
{
var triangles = new List<Triangle3D>();