diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj
index ffa1060..680d9b3 100644
--- a/NavisworksTransportPlugin.csproj
+++ b/NavisworksTransportPlugin.csproj
@@ -163,6 +163,10 @@
+
+
+
+
diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs
index 033026f..2a31e6b 100644
--- a/src/PathPlanning/AutoPathFinder.cs
+++ b/src/PathPlanning/AutoPathFinder.cs
@@ -6,7 +6,6 @@ using Roy_T.AStar.Grids;
using Roy_T.AStar.Primitives;
using Roy_T.AStar.Paths;
using NavisworksTransport.Utils;
-using NavisworksTransport.Core;
namespace NavisworksTransport.PathPlanning
{
@@ -117,6 +116,9 @@ namespace NavisworksTransport.PathPlanning
// 路径优化 - 去除共线点
pathResult = OptimizePath(pathResult, gridMap);
+ // 记录缓存统计信息
+ LogCacheStatistics();
+
return pathResult;
}
catch (Exception ex)
@@ -1214,8 +1216,6 @@ namespace NavisworksTransport.PathPlanning
int gridX = gridPos.X;
int gridY = gridPos.Y;
- LogManager.Info($"[高度检查] 检查点 ({point.X:F2}, {point.Y:F2}, {point.Z:F2}) -> 网格坐标 ({gridX}, {gridY})");
-
// 确保坐标在有效范围内
if (gridX < 0 || gridX >= gridMap.Width || gridY < 0 || gridY >= gridMap.Height)
{
@@ -1240,7 +1240,6 @@ namespace NavisworksTransport.PathPlanning
// 将绝对坐标转换为相对于网格底面的坐标
double relativeZ = point.Z - cell.WorldPosition.Z;
bool containsHeight = interval.Contains(relativeZ);
- LogManager.Info($"[高度检查] 区间 {interval}: 跨度={interval.GetSpan():F2}模型单位 (需要≥{vehicleHeight:F2}模型单位): {spanOk}, 包含相对Z={relativeZ:F2}: {containsHeight}");
if (spanOk && containsHeight)
{
@@ -1433,10 +1432,6 @@ namespace NavisworksTransport.PathPlanning
doorPoints++;
LogManager.Info($"[路径高度验证] 检查路径点{i}(门网格): ({point.X:F2}, {point.Y:F2}, {point.Z:F2}) -> 网格({gridPos.X},{gridPos.Y})");
}
- else
- {
- LogManager.Info($"[路径高度验证] 检查路径点{i}({cell.CellType}): ({point.X:F2}, {point.Y:F2}, {point.Z:F2}) -> 网格({gridPos.X},{gridPos.Y})");
- }
// 进行高度约束检查
bool isPassable = IsPointPassableAtHeight(point, gridMap, vehicleHeight);
@@ -1879,5 +1874,43 @@ namespace NavisworksTransport.PathPlanning
return finalSpeed;
}
+
+ ///
+ /// 获取GridMap缓存统计信息
+ ///
+ /// 缓存统计报告
+ public static string GetGridMapCacheStatistics()
+ {
+ return GridMapGenerator.GetCacheStatistics();
+ }
+
+ ///
+ /// 获取GridMap缓存详细统计报告
+ ///
+ /// 详细统计报告
+ public static string GetDetailedGridMapCacheReport()
+ {
+ return GridMapGenerator.GetDetailedCacheReport();
+ }
+
+ ///
+ /// 清除GridMap缓存
+ ///
+ public static void ClearGridMapCache()
+ {
+ GridMapGenerator.ClearCache();
+ }
+
+ ///
+ /// 记录路径规划完成时的缓存统计
+ ///
+ private void LogCacheStatistics()
+ {
+ var stats = GlobalGridMapCache.Instance.Statistics;
+ if (stats.HitCount + stats.MissCount > 0)
+ {
+ LogManager.Info($"[GridMap缓存统计] {stats.GenerateReport()}");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/PathPlanning/GridCache.cs b/src/PathPlanning/GridCache.cs
new file mode 100644
index 0000000..c40b7ef
--- /dev/null
+++ b/src/PathPlanning/GridCache.cs
@@ -0,0 +1,331 @@
+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
new file mode 100644
index 0000000..bfcb542
--- /dev/null
+++ b/src/PathPlanning/GridCacheKey.cs
@@ -0,0 +1,185 @@
+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/GridMapCache.cs b/src/PathPlanning/GridMapCache.cs
new file mode 100644
index 0000000..584fdc7
--- /dev/null
+++ b/src/PathPlanning/GridMapCache.cs
@@ -0,0 +1,347 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace NavisworksTransport.PathPlanning
+{
+ ///
+ /// GridMap缓存项,包含网格地图和访问时间信息
+ ///
+ public class GridMapCacheItem
+ {
+ ///
+ /// 缓存的网格地图
+ ///
+ public GridMap GridMap { get; set; }
+
+ ///
+ /// 最后访问时间
+ ///
+ public DateTime LastAccessTime { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedTime { get; set; }
+
+ ///
+ /// 访问次数
+ ///
+ public int AccessCount { get; set; }
+
+ ///
+ /// 构造函数
+ ///
+ public GridMapCacheItem(GridMap gridMap)
+ {
+ GridMap = gridMap ?? throw new ArgumentNullException(nameof(gridMap));
+ LastAccessTime = DateTime.Now;
+ CreatedTime = DateTime.Now;
+ AccessCount = 1;
+ }
+
+ ///
+ /// 记录访问
+ ///
+ public void RecordAccess()
+ {
+ LastAccessTime = DateTime.Now;
+ AccessCount++;
+ }
+ }
+
+ ///
+ /// GridMap缓存统计信息
+ ///
+ public class GridMapCacheStatistics
+ {
+ ///
+ /// 缓存命中次数
+ ///
+ 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 long TotalTimeSavedMs { 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;
+ TotalTimeSavedMs = 0;
+ }
+
+ ///
+ /// 生成统计报告
+ ///
+ public string GenerateReport()
+ {
+ return $"GridMap缓存统计 - 命中率: {HitRate:P2} ({HitCount}命中/{MissCount}未命中), " +
+ $"使用率: {UsageRate:P2} ({CurrentCount}/{MaxCapacity}), " +
+ $"驱逐次数: {EvictedCount}, 节省时间: {TotalTimeSavedMs/1000.0:F1}秒";
+ }
+ }
+
+ ///
+ /// GridMap缓存管理器
+ /// 使用LRU策略管理GridMap缓存,支持线程安全访问
+ ///
+ public class GridMapCache
+ {
+ private readonly ConcurrentDictionary _cache;
+ private readonly object _cleanupLock = new object();
+ private readonly int _maxCapacity;
+ private readonly GridMapCacheStatistics _statistics;
+
+ ///
+ /// 获取缓存统计信息
+ ///
+ public GridMapCacheStatistics Statistics => _statistics;
+
+ ///
+ /// 构造函数
+ ///
+ /// 最大缓存容量,默认10个GridMap
+ public GridMapCache(int maxCapacity = 10)
+ {
+ if (maxCapacity <= 0)
+ throw new ArgumentException("缓存容量必须大于0", nameof(maxCapacity));
+
+ _maxCapacity = maxCapacity;
+ _cache = new ConcurrentDictionary();
+ _statistics = new GridMapCacheStatistics { MaxCapacity = maxCapacity };
+
+ LogManager.Info($"[GridMap缓存] 初始化完成,最大容量: {_maxCapacity}");
+ }
+
+ ///
+ /// 获取缓存的GridMap
+ ///
+ /// 缓存键
+ /// 预估的构建时间(用于统计节省时间)
+ /// GridMap对象,如果不存在则返回null
+ public GridMap Get(GridMapCacheKey key, long estimatedBuildTimeMs = 0)
+ {
+ if (key == null)
+ return null;
+
+ if (_cache.TryGetValue(key, out var item))
+ {
+ // 记录访问
+ item.RecordAccess();
+ _statistics.HitCount++;
+
+ // 累计节省时间
+ if (estimatedBuildTimeMs > 0)
+ {
+ _statistics.TotalTimeSavedMs += estimatedBuildTimeMs;
+ }
+
+ LogManager.Info($"[GridMap缓存] 命中 - {key},访问次数: {item.AccessCount}");
+ return item.GridMap;
+ }
+
+ _statistics.MissCount++;
+ LogManager.Info($"[GridMap缓存] 未命中 - {key}");
+ return null;
+ }
+
+ ///
+ /// 添加GridMap到缓存
+ ///
+ /// 缓存键
+ /// GridMap对象
+ public void Put(GridMapCacheKey key, GridMap gridMap)
+ {
+ if (key == null || gridMap == null)
+ return;
+
+ var item = new GridMapCacheItem(gridMap);
+
+ // 添加或更新缓存项
+ _cache.AddOrUpdate(key, item, (k, existing) =>
+ {
+ LogManager.Debug($"[GridMap缓存] 更新现有项 - {key}");
+ return item;
+ });
+
+ // 更新统计信息
+ _statistics.CurrentCount = _cache.Count;
+
+ LogManager.Info($"[GridMap缓存] 添加 - {key},当前容量: {_cache.Count}/{_maxCapacity}");
+ LogManager.Info($"[GridMap缓存] GridMap统计: {gridMap.GetStatistics()}");
+
+ // 检查是否需要清理
+ if (_cache.Count > _maxCapacity)
+ {
+ CleanupCache();
+ }
+ }
+
+ ///
+ /// 检查缓存中是否包含指定键
+ ///
+ /// 缓存键
+ /// 是否包含
+ public bool ContainsKey(GridMapCacheKey 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($"[GridMap缓存] 清除所有缓存,共清除 {count} 项");
+ }
+ }
+
+ ///
+ /// 获取缓存的所有键
+ ///
+ /// 键列表
+ public IList GetKeys()
+ {
+ return _cache.Keys.ToList();
+ }
+
+ ///
+ /// 清理缓存,使用LRU策略移除最少使用的项
+ ///
+ private void CleanupCache()
+ {
+ lock (_cleanupLock)
+ {
+ // 双重检查,避免重复清理
+ if (_cache.Count <= _maxCapacity)
+ return;
+
+ var targetCount = Math.Max(1, (int)(_maxCapacity * 0.7)); // 清理到70%容量
+ var itemsToRemove = _cache.Count - targetCount;
+
+ LogManager.Info($"[GridMap缓存] 开始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($"[GridMap缓存] 移除 - {item.Key},最后访问: {item.Value.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
+ }
+ }
+
+ _statistics.CurrentCount = _cache.Count;
+ LogManager.Info($"[GridMap缓存] 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(5) // 只显示最近5项
+ .ToList();
+
+ foreach (var item in sortedItems)
+ {
+ var gridStats = item.Value.GridMap?.GetStatistics() ?? "N/A";
+ report.AppendLine($" {item.Key}");
+ report.AppendLine($" 访问{item.Value.AccessCount}次,最后访问: {item.Value.LastAccessTime:MM-dd HH:mm:ss}");
+ report.AppendLine($" GridMap: {gridStats}");
+ }
+
+ if (_cache.Count > 5)
+ {
+ report.AppendLine($" ... 还有 {_cache.Count - 5} 项");
+ }
+ }
+
+ return report.ToString();
+ }
+ }
+
+ ///
+ /// 全局GridMap缓存管理器(单例)
+ ///
+ public static class GlobalGridMapCache
+ {
+ private static readonly Lazy _instance = new Lazy(() => new GridMapCache());
+
+ ///
+ /// 获取全局缓存实例
+ ///
+ public static GridMapCache Instance => _instance.Value;
+
+ ///
+ /// 重置全局缓存(主要用于测试)
+ ///
+ public static void Reset()
+ {
+ _instance.Value.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/PathPlanning/GridMapCacheKey.cs b/src/PathPlanning/GridMapCacheKey.cs
new file mode 100644
index 0000000..a85d0a8
--- /dev/null
+++ b/src/PathPlanning/GridMapCacheKey.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using Autodesk.Navisworks.Api;
+
+namespace NavisworksTransport.PathPlanning
+{
+ ///
+ /// GridMap缓存键,用于标识唯一的GridMap生成配置
+ /// 基于通道数据、车辆尺寸、网格尺寸等关键参数
+ ///
+ public class GridMapCacheKey : IEquatable
+ {
+ ///
+ /// 通道数据哈希值(所有可通行物流构件)
+ ///
+ public string ChannelDataHash { get; set; }
+
+ ///
+ /// 边界范围哈希
+ ///
+ public string BoundsHash { get; set; }
+
+ ///
+ /// 网格单元大小(模型单位)
+ ///
+ public double CellSize { get; set; }
+
+ ///
+ /// 车辆半径(米)
+ ///
+ public double VehicleRadius { get; set; }
+
+ ///
+ /// 安全间隙(米)
+ ///
+ public double SafetyMargin { get; set; }
+
+ ///
+ /// 车辆高度(米)
+ ///
+ public double VehicleHeight { get; set; }
+
+ ///
+ /// 构造函数
+ ///
+ public GridMapCacheKey(string channelDataHash, string boundsHash, double cellSize,
+ double vehicleRadius, double safetyMargin, double vehicleHeight)
+ {
+ ChannelDataHash = channelDataHash ?? throw new ArgumentNullException(nameof(channelDataHash));
+ BoundsHash = boundsHash ?? throw new ArgumentNullException(nameof(boundsHash));
+ CellSize = cellSize;
+ VehicleRadius = vehicleRadius;
+ SafetyMargin = safetyMargin;
+ VehicleHeight = vehicleHeight;
+ }
+
+ ///
+ /// 检查两个缓存键是否相等
+ ///
+ public bool Equals(GridMapCacheKey other)
+ {
+ if (other == null) return false;
+ if (ReferenceEquals(this, other)) return true;
+
+ return ChannelDataHash == other.ChannelDataHash &&
+ BoundsHash == other.BoundsHash &&
+ Math.Abs(CellSize - other.CellSize) < 1e-6 &&
+ Math.Abs(VehicleRadius - other.VehicleRadius) < 1e-6 &&
+ Math.Abs(SafetyMargin - other.SafetyMargin) < 1e-6 &&
+ Math.Abs(VehicleHeight - other.VehicleHeight) < 1e-6;
+ }
+
+ ///
+ /// 重写Equals方法
+ ///
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as GridMapCacheKey);
+ }
+
+ ///
+ /// 计算哈希码,用于Dictionary键
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + (ChannelDataHash?.GetHashCode() ?? 0);
+ hash = hash * 23 + (BoundsHash?.GetHashCode() ?? 0);
+ hash = hash * 23 + CellSize.GetHashCode();
+ hash = hash * 23 + VehicleRadius.GetHashCode();
+ hash = hash * 23 + SafetyMargin.GetHashCode();
+ hash = hash * 23 + VehicleHeight.GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// 生成可读的字符串表示
+ ///
+ public override string ToString()
+ {
+ var channelPreview = ChannelDataHash != null ?
+ ChannelDataHash.Substring(0, Math.Min(8, ChannelDataHash.Length)) :
+ "NULL";
+ var boundsPreview = BoundsHash != null ?
+ BoundsHash.Substring(0, Math.Min(8, BoundsHash.Length)) :
+ "NULL";
+
+ return $"GridMapKey[Ch={channelPreview}, Bounds={boundsPreview}, " +
+ $"Cell={CellSize:F2}, VR={VehicleRadius:F1}m, SM={SafetyMargin:F1}m, VH={VehicleHeight:F1}m]";
+ }
+
+ ///
+ /// 根据参数生成GridMap缓存键
+ ///
+ /// Navisworks文档
+ /// 边界范围
+ /// 网格大小
+ /// 车辆半径(米)
+ /// 安全间隙(米)
+ /// 车辆高度(米)
+ /// 缓存键
+ public static GridMapCacheKey CreateFrom(Document document, BoundingBox3D bounds,
+ double cellSize, double vehicleRadius,
+ double safetyMargin, double vehicleHeight)
+ {
+ if (document == null)
+ throw new ArgumentNullException(nameof(document));
+
+ var channelDataHash = ComputeChannelDataHash(document);
+ var boundsHash = ComputeBoundsHash(bounds);
+
+ return new GridMapCacheKey(channelDataHash, boundsHash, cellSize,
+ vehicleRadius, safetyMargin, vehicleHeight);
+ }
+
+ ///
+ /// 计算通道数据哈希值
+ /// 基于所有可通行物流构件的ID、属性、几何等信息
+ ///
+ /// Navisworks文档
+ /// 通道数据哈希
+ private static string ComputeChannelDataHash(Document document)
+ {
+ try
+ {
+ using (var sha256 = SHA256.Create())
+ {
+ var hashBuilder = new StringBuilder();
+
+ // 获取所有可通行的物流模型项
+ var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document);
+
+ // 按InstanceGuid排序确保一致性
+ var sortedItems = allChannelItems.OrderBy(item => item.InstanceGuid.ToString()).ToList();
+
+ hashBuilder.Append($"ChannelCount:{sortedItems.Count}|");
+
+ foreach (var item in sortedItems)
+ {
+ // 基本标识信息
+ hashBuilder.Append($"ID:{item.InstanceGuid}|");
+ hashBuilder.Append($"DisplayName:{item.DisplayName ?? "NULL"}|");
+
+ // 几何信息(包围盒)
+ var bbox = item.BoundingBox();
+ if (bbox != null)
+ {
+ hashBuilder.Append($"BBox:{bbox.Min.X:F3},{bbox.Min.Y:F3},{bbox.Min.Z:F3}-");
+ hashBuilder.Append($"{bbox.Max.X:F3},{bbox.Max.Y:F3},{bbox.Max.Z:F3}|");
+ }
+
+ // 物流类型属性
+ var logisticsType = CategoryAttributeManager.GetLogisticsElementType(item);
+ hashBuilder.Append($"LogType:{logisticsType}|");
+
+ // Transform信息(如果有变换)
+ var transform = item.Transform;
+ if (transform != null)
+ {
+ // 只包含关键的变换信息
+ hashBuilder.Append($"TX:{transform.Translation.X:F3},{transform.Translation.Y:F3},{transform.Translation.Z:F3}|");
+ }
+ }
+
+ // 计算最终哈希
+ 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 $"CHANNEL_HASH_FAILED_{DateTime.Now.Ticks}";
+ }
+ }
+
+ ///
+ /// 计算边界范围哈希值
+ ///
+ /// 边界范围
+ /// 边界哈希
+ private static string ComputeBoundsHash(BoundingBox3D bounds)
+ {
+ try
+ {
+ using (var sha256 = SHA256.Create())
+ {
+ var boundsString = $"Bounds:{bounds.Min.X:F6},{bounds.Min.Y:F6},{bounds.Min.Z:F6}-" +
+ $"{bounds.Max.X:F6},{bounds.Max.Y:F6},{bounds.Max.Z:F6}";
+
+ var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(boundsString));
+
+ 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 $"BOUNDS_HASH_FAILED_{DateTime.Now.Ticks}";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs
index f2024fe..0476995 100644
--- a/src/PathPlanning/GridMapGenerator.cs
+++ b/src/PathPlanning/GridMapGenerator.cs
@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
-using NavisworksTransport.Core;
namespace NavisworksTransport.PathPlanning
{
@@ -60,7 +57,30 @@ namespace NavisworksTransport.PathPlanning
{
try
{
- LogManager.Info("【生成网格地图】开始生成网格地图");
+ // 生成缓存键
+ var cacheKey = GridMapCacheKey.CreateFrom(document, bounds, cellSize, vehicleRadius, safetyMargin, vehicleHeight);
+
+ // 尝试从缓存获取
+ var cachedGridMap = GlobalGridMapCache.Instance.Get(cacheKey, 5000); // 预估生成需要5秒
+ if (cachedGridMap != null)
+ {
+ LogManager.Info($"【GridMap缓存】使用缓存网格地图 - {cacheKey}");
+ LogManager.Info($"【GridMap缓存】缓存GridMap统计: {cachedGridMap.GetStatistics()}");
+
+ // 更新规划起终点信息(这些信息可能会变化,不影响核心网格结构)
+ if (planningStartPoint != default(Point3D) && planningEndPoint != default(Point3D))
+ {
+ cachedGridMap.PlanningStartPoint = planningStartPoint;
+ cachedGridMap.PlanningEndPoint = planningEndPoint;
+ cachedGridMap.HasPlanningPoints = true;
+ }
+
+ return cachedGridMap;
+ }
+
+ // 缓存未命中,生成新的GridMap
+ LogManager.Info("【生成网格地图】缓存未命中,开始生成新网格地图");
+ LogManager.Info($"【生成网格地图】缓存键: {cacheKey}");
var startTime = DateTime.Now;
// 第一步:统一转换所有米制参数为模型单位
@@ -150,6 +170,11 @@ namespace NavisworksTransport.PathPlanning
var elapsed = (DateTime.Now - startTime).TotalMilliseconds;
LogManager.Info($"【生成网格地图】网格地图生成完成: {channelCoverage.GridMap.GetStatistics()}");
LogManager.Info($"【生成网格地图】总耗时: {elapsed:F1}ms");
+
+ // 将生成的GridMap添加到缓存
+ GlobalGridMapCache.Instance.Put(cacheKey, channelCoverage.GridMap);
+ LogManager.Info($"【GridMap缓存】网格地图已缓存,缓存键: {cacheKey}");
+
return channelCoverage.GridMap;
}
catch (Exception ex)
@@ -159,6 +184,33 @@ namespace NavisworksTransport.PathPlanning
}
}
+ ///
+ /// 获取GridMap缓存统计信息
+ ///
+ /// 缓存统计报告
+ public static string GetCacheStatistics()
+ {
+ return GlobalGridMapCache.Instance.Statistics.GenerateReport();
+ }
+
+ ///
+ /// 获取GridMap缓存详细统计报告
+ ///
+ /// 详细统计报告
+ public static string GetDetailedCacheReport()
+ {
+ return GlobalGridMapCache.Instance.GenerateDetailedReport();
+ }
+
+ ///
+ /// 清除GridMap缓存
+ ///
+ public static void ClearCache()
+ {
+ GlobalGridMapCache.Instance.Clear();
+ LogManager.Info("[GridMap缓存] 手动清除所有缓存");
+ }
+
///
/// 处理门元素,将其设置为可通行网格
///