diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj
index 3db57b8..f7d2066 100644
--- a/NavisworksTransportPlugin.csproj
+++ b/NavisworksTransportPlugin.csproj
@@ -161,9 +161,9 @@
-
-
+
+
@@ -197,6 +197,7 @@
+
diff --git a/doc/working/voxel_pathfinding_task_tracker.md b/doc/working/voxel_pathfinding_task_tracker.md
index f84be49..8993b40 100644
--- a/doc/working/voxel_pathfinding_task_tracker.md
+++ b/doc/working/voxel_pathfinding_task_tracker.md
@@ -1330,3 +1330,28 @@ git push origin --delete feature/voxel-pathfinding
**文档版本**: v1.0
**最后更新**: 2025-10-12
**维护者**: NavisworksTransport 开发团队
+
+[2025-10-12 17:59:43.199] [INFO] [体素路径规划测试] 体素网格生成完成: 155×82×10 = 127,100 个体素
+[2025-10-12 17:59:43.200] [INFO] [体素路径规划测试] 体素统计: 总数=127,100, 可通行=83,935 (66.0%), 障碍物=43,165 (34.0%)
+[2025-10-12 17:59:43.200] [INFO] [体素路径规划测试] === 可通行体素样本(世界坐标)===
+[2025-10-12 17:59:43.201] [INFO] [体素路径规划测试] 体素( 50, 0, 0) -> 世界( -160.64, -62.32, 35.40)
+[2025-10-12 17:59:43.201] [INFO] [体素路径规划测试] 体素( 60, 0, 0) -> 世界( -144.24, -62.32, 35.40)
+[2025-10-12 17:59:43.202] [INFO] [体素路径规划测试] 体素( 70, 0, 0) -> 世界( -127.83, -62.32, 35.40)
+[2025-10-12 17:59:43.202] [INFO] [体素路径规划测试] 体素( 80, 0, 0) -> 世界( -111.43, -62.32, 35.40)
+[2025-10-12 17:59:43.202] [INFO] [体素路径规划测试] 体素( 90, 0, 0) -> 世界( -95.02, -62.32, 35.40)
+[2025-10-12 17:59:43.202] [INFO] [体素路径规划测试] 体素(100, 0, 0) -> 世界( -78.62, -62.32, 35.40)
+[2025-10-12 17:59:43.203] [INFO] [体素路径规划测试] 体素(110, 0, 0) -> 世界( -62.21, -62.32, 35.40)
+[2025-10-12 17:59:43.203] [INFO] [体素路径规划测试] 体素(120, 0, 0) -> 世界( -45.81, -62.32, 35.40)
+[2025-10-12 17:59:43.203] [INFO] [体素路径规划测试] 体素(130, 0, 0) -> 世界( -29.40, -62.32, 35.40)
+[2025-10-12 17:59:43.203] [INFO] [体素路径规划测试] 体素(140, 0, 0) -> 世界( -13.00, -62.32, 35.40)
+[2025-10-12 17:59:43.204] [INFO] [体素路径规划测试] 体素(150, 0, 0) -> 世界( 3.41, -62.32, 35.40)
+[2025-10-12 17:59:43.204] [INFO] [体素路径规划测试] 体素( 50, 10, 0) -> 世界( -160.64, -45.91, 35.40)
+[2025-10-12 17:59:43.204] [INFO] [体素路径规划测试] 体素( 60, 10, 0) -> 世界( -144.24, -45.91, 35.40)
+[2025-10-12 17:59:43.205] [INFO] [体素路径规划测试] 体素( 70, 10, 0) -> 世界( -127.83, -45.91, 35.40)
+[2025-10-12 17:59:43.205] [INFO] [体素路径规划测试] 体素( 80, 10, 0) -> 世界( -111.43, -45.91, 35.40)
+[2025-10-12 17:59:43.205] [INFO] [体素路径规划测试] 体素( 90, 10, 0) -> 世界( -95.02, -45.91, 35.40)
+[2025-10-12 17:59:43.205] [INFO] [体素路径规划测试] 体素(100, 10, 0) -> 世界( -78.62, -45.91, 35.40)
+[2025-10-12 17:59:43.206] [INFO] [体素路径规划测试] 体素(110, 10, 0) -> 世界( -62.21, -45.91, 35.40)
+[2025-10-12 17:59:43.206] [INFO] [体素路径规划测试] 体素(120, 10, 0) -> 世界( -45.81, -45.91, 35.40)
+[2025-10-12 17:59:43.206] [INFO] [体素路径规划测试] 体素(130, 10, 0) -> 世界( -29.40, -45.91, 35.40)
+[2025-10-12 17:59:43.206] [INFO] [体素路径规划测试] 已打印 20 个可通行体素样本
diff --git a/src/Commands/VoxelGridTestCommand.cs b/src/Commands/VoxelGridTestCommand.cs
deleted file mode 100644
index 2d280fb..0000000
--- a/src/Commands/VoxelGridTestCommand.cs
+++ /dev/null
@@ -1,395 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Autodesk.Navisworks.Api;
-using NavisworksTransport.Core;
-using NavisworksTransport.PathPlanning;
-using NavisworksTransport.Utils;
-
-namespace NavisworksTransport.Commands
-{
- ///
- /// 体素网格测试命令
- /// 对整个模型空间建立体素网格,所有模型对象被标记为障碍物
- ///
- public class VoxelGridTestCommand : CommandBase
- {
- private readonly Document _document;
- private readonly double _cellSize;
- private readonly int _samplingRate;
-
- public VoxelGridTestCommand(Document document, double cellSize = 0.5, int samplingRate = 2)
- : base("VoxelGridTest", "体素网格测试", "对整个模型空间建立体素网格")
- {
- _document = document ?? throw new ArgumentNullException(nameof(document));
- _cellSize = cellSize;
- _samplingRate = samplingRate;
- }
-
- protected override PathPlanningResult ValidateParameters()
- {
- if (_document == null)
- {
- return PathPlanningResult.ValidationFailure("Navisworks 文档对象为空");
- }
-
- if (_cellSize <= 0)
- {
- return PathPlanningResult.ValidationFailure("体素大小必须大于0");
- }
-
- return PathPlanningResult.Success("参数验证通过");
- }
-
- protected override async Task ExecuteInternalAsync(CancellationToken cancellationToken)
- {
- try
- {
- LogInfo("开始体素网格测试 - 对整个模型空间体素化");
-
- // *** 单位转换:在入口处一次性转换所有米制参数为模型单位 ***
- double metersToModelUnitsConversionFactor = UnitsConverter.GetMetersToUnitsConversionFactor(_document.Units);
- double cellSizeInModelUnits = _cellSize * metersToModelUnitsConversionFactor;
- LogInfo($"体素大小: {_cellSize}米 = {cellSizeInModelUnits:F4}模型单位");
-
- UpdateProgress(5, "正在获取模型空间范围...");
-
- // 步骤1: 获取整个模型空间的包围盒
- BoundingBox3D spaceBounds = GetSpaceBounds(_document);
-
- if (spaceBounds == null)
- {
- return PathPlanningResult.Failure("无法获取模型空间范围");
- }
-
- LogInfo($"空间范围: Min={spaceBounds.Min}, Max={spaceBounds.Max}");
-
- UpdateProgress(10, "正在创建体素网格...");
-
- // 步骤2: 创建覆盖整个空间的体素网格(使用模型单位)
- var voxelGrid = new VoxelGrid(spaceBounds, cellSizeInModelUnits);
- LogInfo($"创建了体素网格: {voxelGrid.SizeX}×{voxelGrid.SizeY}×{voxelGrid.SizeZ} = {voxelGrid.TotalVoxels} 个体素");
-
- UpdateProgress(20, "正在初始化所有体素为自由空间...");
-
- // 步骤3: 初始化所有体素为自由空间
- InitializeAllVoxelsAsFree(voxelGrid);
- LogInfo("所有体素已初始化为自由空间");
-
- UpdateProgress(30, "正在收集场景中的所有模型对象...");
-
- // 步骤4: 收集场景中所有带几何信息的对象
- var allModelItems = GetAllModelItems(_document);
- LogInfo($"场景中共有 {allModelItems.Count} 个带几何信息的模型对象");
-
- UpdateProgress(40, "正在标记障碍物体素...");
-
- // 步骤5: 遍历所有对象,标记被占据的体素为障碍物
- int processedCount = 0;
- foreach (var item in allModelItems)
- {
- MarkObstacleVoxels(voxelGrid, item);
- processedCount++;
-
- // 每处理10%的对象更新一次进度
- if (processedCount % Math.Max(1, allModelItems.Count / 10) == 0)
- {
- int progress = 40 + (int)((processedCount / (double)allModelItems.Count) * 40);
- UpdateProgress(progress, $"正在标记障碍物... ({processedCount}/{allModelItems.Count})");
- }
-
- ThrowIfCancellationRequested(cancellationToken);
- }
-
- LogInfo($"已处理 {processedCount} 个模型对象");
-
- UpdateProgress(80, "正在统计体素分布...");
-
- // 步骤6: 统计结果
- var (total, passable, obstacle) = voxelGrid.GetStatistics();
- LogInfo($"体素统计: 总数={total}, 自由空间={passable} ({passable*100.0/total:F1}%), 障碍物={obstacle} ({obstacle*100.0/total:F1}%)");
-
- UpdateProgress(85, "正在生成可视化数据...");
-
- // 步骤7: 生成可视化
- var voxelPoints = VoxelGridVisualizer.VisualizeAsPoints(
- voxelGrid,
- samplingRate: _samplingRate,
- showPassableOnly: false
- );
-
- LogInfo($"生成了 {voxelPoints.Count} 个可视化点(采样率: {_samplingRate})");
-
- // 步骤8: 转换为 PathRoute 以便使用现有渲染系统
- var visualRoute = VoxelGridVisualizer.ConvertToPathRoute(
- voxelPoints,
- $"VoxelGrid_Space_{DateTime.Now:HHmmss}"
- );
-
- LogInfo($"可视化路径已创建: {visualRoute.Name}");
-
- UpdateProgress(90, "正在渲染体素网格...");
-
- // 步骤9: 调用 PathPointRenderPlugin 进行渲染
- var renderPlugin = PathPointRenderPlugin.Instance;
- if (renderPlugin != null)
- {
- // 设置网格大小(体素大小),让渲染器根据体素大小计算球体半径
- renderPlugin.SetGridSize(_cellSize);
- LogInfo($"已设置体素网格大小: {_cellSize}米");
-
- renderPlugin.RenderPointOnly(visualRoute);
- LogInfo("✓ 体素网格已成功渲染到3D视图");
- }
- else
- {
- LogWarning("PathPointRenderPlugin 实例不可用,无法渲染体素网格");
- }
-
- UpdateProgress(95, "正在生成报告...");
-
- // 步骤9: 生成文本报告
- var report = VoxelGridVisualizer.GenerateVisualizationReport(voxelGrid);
- LogInfo("=== 体素网格报告 ===");
- LogInfo(report);
-
- UpdateProgress(100, "体素网格测试完成");
-
- return PathPlanningResult.Success($"体素网格测试完成\n\n{report}");
- }
- catch (Exception ex)
- {
- LogError($"体素网格测试失败: {ex.Message}", ex);
- return PathPlanningResult.Failure($"体素网格测试失败: {ex.Message}", ex);
- }
- }
-
- #region 私有辅助方法
-
- ///
- /// 获取整个模型空间的包围盒
- ///
- private BoundingBox3D GetSpaceBounds(Document document)
- {
- try
- {
- // 计算所有模型的总包围盒
- LogInfo("正在计算所有模型的总包围盒...");
- return CalculateBoundsFromAllModels(document);
- }
- catch (Exception ex)
- {
- LogError($"获取空间范围失败: {ex.Message}", ex);
- return null;
- }
- }
-
- ///
- /// 计算所有模型的总包围盒
- ///
- private BoundingBox3D CalculateBoundsFromAllModels(Document document)
- {
- BoundingBox3D totalBounds = null;
-
- foreach (var model in document.Models)
- {
- if (model.RootItem != null)
- {
- var modelBounds = model.RootItem.BoundingBox();
- if (modelBounds != null)
- {
- if (totalBounds == null)
- {
- totalBounds = modelBounds;
- }
- else
- {
- totalBounds = ExpandBounds(totalBounds, modelBounds);
- }
- }
- }
- }
-
- return totalBounds;
- }
-
- ///
- /// 扩展包围盒以包含另一个包围盒
- ///
- private BoundingBox3D ExpandBounds(BoundingBox3D bounds1, BoundingBox3D bounds2)
- {
- var min = new Point3D(
- Math.Min(bounds1.Min.X, bounds2.Min.X),
- Math.Min(bounds1.Min.Y, bounds2.Min.Y),
- Math.Min(bounds1.Min.Z, bounds2.Min.Z)
- );
- var max = new Point3D(
- Math.Max(bounds1.Max.X, bounds2.Max.X),
- Math.Max(bounds1.Max.Y, bounds2.Max.Y),
- Math.Max(bounds1.Max.Z, bounds2.Max.Z)
- );
- return new BoundingBox3D(min, max);
- }
-
- ///
- /// 获取场景中所有带几何信息的模型对象
- ///
- private List GetAllModelItems(Document document)
- {
- var items = new List();
-
- // 遍历文档中的所有模型
- foreach (var model in document.Models)
- {
- if (model.RootItem != null)
- {
- CollectAllItems(model.RootItem, items);
- }
- }
-
- return items;
- }
-
- ///
- /// 递归收集所有带几何信息的对象
- ///
- private void CollectAllItems(ModelItem item, List result)
- {
- // 如果当前对象有几何信息,加入结果
- if (item.HasGeometry)
- {
- result.Add(item);
- }
-
- // 递归处理所有子对象
- foreach (var child in item.Children)
- {
- CollectAllItems(child, result);
- }
- }
-
- ///
- /// 初始化所有体素为自由空间
- ///
- private void InitializeAllVoxelsAsFree(VoxelGrid grid)
- {
- for (int x = 0; x < grid.SizeX; x++)
- {
- for (int y = 0; y < grid.SizeY; y++)
- {
- for (int z = 0; z < grid.SizeZ; z++)
- {
- var cell = grid.GetCell(x, y, z);
- cell.SetAsFreeSpace();
- }
- }
- }
- }
-
- ///
- /// 标记被模型对象占据的体素为障碍物(基于精确几何体检测)
- ///
- private void MarkObstacleVoxels(VoxelGrid grid, ModelItem item)
- {
- try
- {
- // 先用包围盒快速过滤,找到可能相交的体素候选集
- var itemBounds = item.BoundingBox();
- if (itemBounds == null)
- return;
-
- var candidateVoxels = grid.GetVoxelsInBounds(itemBounds);
-
- // 对每个候选体素进行精确几何体检测
- foreach (var (x, y, z) in candidateVoxels)
- {
- // 获取体素的世界坐标中心点
- var voxelCenter = grid.VoxelToWorldCenter(x, y, z);
-
- // 检查体素中心点是否在模型几何体内部或表面
- if (IsPointInsideOrOnGeometry(item, voxelCenter))
- {
- var cell = grid.GetCell(x, y, z);
- if (cell != null)
- {
- cell.SetAsObstacle();
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogWarning($"标记对象 {item.DisplayName} 的体素时出错: {ex.Message}");
- }
- }
-
- ///
- /// 检查点是否在模型几何体内部或表面(使用真正的三角形几何体检测)
- ///
- private bool IsPointInsideOrOnGeometry(ModelItem item, Point3D point)
- {
- try
- {
- // 方法1:使用包围盒快速排除(优化性能)
- var bounds = item.BoundingBox();
- if (bounds == null)
- return false;
-
- // 扩展包围盒一点点(避免边界精度问题)
- double tolerance = 0.001; // 很小的容差
- if (point.X < bounds.Min.X - tolerance || point.X > bounds.Max.X + tolerance ||
- point.Y < bounds.Min.Y - tolerance || point.Y > bounds.Max.Y + tolerance ||
- point.Z < bounds.Min.Z - tolerance || point.Z > bounds.Max.Z + tolerance)
- {
- return false;
- }
-
- // 方法2:使用精确几何体检测 - 光线投射法(Ray Casting)
- // 从点发出射线,计算与三角形表面的交点数
- // 奇数个交点 = 点在内部,偶数个交点 = 点在外部
- var triangles = GeometryHelper.ExtractTriangles(item);
- if (triangles != null && triangles.Count > 0)
- {
- int intersectionCount = CountRayTriangleIntersections(point, triangles);
- return (intersectionCount % 2) == 1; // 奇数=内部,偶数=外部
- }
-
- // 如果无法获取三角形数据,使用包围盒判断(保守策略)
- return true;
- }
- catch (Exception ex)
- {
- LogWarning($"几何体检测失败: {ex.Message}");
- // 如果精确检测失败,保守地认为点在几何体内
- return true;
- }
- }
-
- ///
- /// 计算从点发出的射线与三角形的交点数量
- /// 使用X轴正方向射线
- ///
- private int CountRayTriangleIntersections(Point3D point, List triangles)
- {
- int intersectionCount = 0;
-
- // 射线:从点出发,沿X轴正方向
- var rayOrigin = point;
- var rayDirection = new Point3D(1, 0, 0);
-
- foreach (var triangle in triangles)
- {
- if (GeometryHelper.RayTriangleIntersect(rayOrigin, rayDirection, triangle, out double intersectionZ))
- {
- intersectionCount++;
- }
- }
-
- return intersectionCount;
- }
-
- #endregion
- }
-}
diff --git a/src/Commands/VoxelPathFindingTestCommand.cs b/src/Commands/VoxelPathFindingTestCommand.cs
new file mode 100644
index 0000000..b804be6
--- /dev/null
+++ b/src/Commands/VoxelPathFindingTestCommand.cs
@@ -0,0 +1,388 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Autodesk.Navisworks.Api;
+using NavisworksTransport.Core;
+using NavisworksTransport.PathPlanning;
+using NavisworksTransport.Utils;
+
+namespace NavisworksTransport.Commands
+{
+ ///
+ /// 体素网格路径规划测试命令
+ /// 测试VoxelPathFinder的3D A*路径规划功能
+ ///
+ public class VoxelPathFindingTestCommand : CommandBase
+ {
+ private readonly Document _document;
+ private readonly double _cellSize;
+ private readonly double _vehicleRadius;
+ private readonly double _vehicleHeight;
+ private readonly Point3D _startPoint;
+ private readonly Point3D _endPoint;
+
+ public VoxelPathFindingTestCommand(
+ Document document,
+ double cellSize,
+ double vehicleRadius,
+ double vehicleHeight,
+ Point3D startPoint,
+ Point3D endPoint)
+ : base("VoxelPathFindingTest", "体素路径规划测试", "测试VoxelPathFinder的3D A*路径规划")
+ {
+ _document = document ?? throw new ArgumentNullException(nameof(document));
+ _cellSize = cellSize;
+ _vehicleRadius = vehicleRadius;
+ _vehicleHeight = vehicleHeight;
+ _startPoint = startPoint;
+ _endPoint = endPoint;
+ }
+
+ protected override PathPlanningResult ValidateParameters()
+ {
+ if (_document == null)
+ {
+ return PathPlanningResult.ValidationFailure("Navisworks 文档对象为空");
+ }
+
+ if (_cellSize <= 0)
+ {
+ return PathPlanningResult.ValidationFailure("体素大小必须大于0");
+ }
+
+ return PathPlanningResult.Success("参数验证通过");
+ }
+
+ protected override async Task ExecuteInternalAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ LogInfo("=== 开始体素路径规划测试 ===");
+ LogInfo($"体素大小: {_cellSize}米");
+ LogInfo($"车辆半径: {_vehicleRadius}米");
+ LogInfo($"车辆高度: {_vehicleHeight}米");
+ LogInfo($"起点: ({_startPoint.X:F2}, {_startPoint.Y:F2}, {_startPoint.Z:F2})");
+ LogInfo($"终点: ({_endPoint.X:F2}, {_endPoint.Y:F2}, {_endPoint.Z:F2})");
+
+ UpdateProgress(5, "正在获取模型空间范围...");
+
+ // 步骤1: 获取模型空间包围盒
+ BoundingBox3D spaceBounds = GetSpaceBounds(_document);
+ if (spaceBounds == null)
+ {
+ return PathPlanningResult.Failure("无法获取模型空间范围");
+ }
+
+ LogInfo($"空间范围: Min={spaceBounds.Min}, Max={spaceBounds.Max}");
+
+ UpdateProgress(15, "正在收集模型对象...");
+
+ // 步骤2: 收集所有模型对象
+ var allModelItems = GetAllModelItems(_document);
+ LogInfo($"场景中共有 {allModelItems.Count} 个带几何信息的模型对象");
+
+ if (allModelItems.Count == 0)
+ {
+ return PathPlanningResult.Failure("场景中没有找到任何带几何信息的模型对象");
+ }
+
+ UpdateProgress(25, "正在生成体素网格...");
+
+ // 步骤3: 生成体素网格
+ var generator = new VoxelGridGenerator();
+ var voxelGrid = generator.GenerateFromBIMWithSDF(
+ spaceBounds,
+ _cellSize,
+ _vehicleRadius,
+ _vehicleHeight,
+ allModelItems);
+
+ LogInfo($"体素网格生成完成: {voxelGrid.SizeX}×{voxelGrid.SizeY}×{voxelGrid.SizeZ} = {voxelGrid.TotalVoxels:N0} 个体素");
+
+ var (total, passable, obstacle) = voxelGrid.GetStatistics();
+ LogInfo($"体素统计: 总数={total:N0}, 可通行={passable:N0} ({passable*100.0/total:F1}%), 障碍物={obstacle:N0} ({obstacle*100.0/total:F1}%)");
+
+ // 打印一些可通行体素的世界坐标样本,帮助选择合适的起点终点
+ LogInfo("=== 可通行体素样本(优先中间区域和较高楼层)===");
+ int sampleCount = 0;
+ int maxSamples = 30;
+
+ // 从中间区域和较高楼层开始采样
+ int midX = voxelGrid.SizeX / 2;
+ int midY = voxelGrid.SizeY / 2;
+
+ // 从高到低遍历楼层
+ for (int z = voxelGrid.SizeZ - 1; z >= 0 && sampleCount < maxSamples; z--)
+ {
+ // 以中心为起点螺旋采样
+ for (int radius = 0; radius < Math.Max(voxelGrid.SizeX, voxelGrid.SizeY) / 2 && sampleCount < maxSamples; radius += 15)
+ {
+ // 上下左右4个方向采样
+ int[][] offsets = new int[][]
+ {
+ new int[] {radius, 0}, // 右
+ new int[] {-radius, 0}, // 左
+ new int[] {0, radius}, // 上
+ new int[] {0, -radius}, // 下
+ new int[] {radius, radius}, // 右上
+ new int[] {-radius, radius}, // 左上
+ new int[] {radius, -radius}, // 右下
+ new int[] {-radius, -radius} // 左下
+ };
+
+ foreach (var offset in offsets)
+ {
+ if (sampleCount >= maxSamples) break;
+
+ int x = midX + offset[0];
+ int y = midY + offset[1];
+
+ if (x >= 0 && x < voxelGrid.SizeX && y >= 0 && y < voxelGrid.SizeY)
+ {
+ if (voxelGrid.IsPassable(x, y, z))
+ {
+ var worldPos = voxelGrid.VoxelToWorld(x, y, z);
+ LogInfo($" 体素({x,3}, {y,3}, {z,2}) -> 世界({worldPos.X,8:F2}, {worldPos.Y,8:F2}, {worldPos.Z,8:F2})");
+ sampleCount++;
+ }
+ }
+ }
+ }
+ }
+ LogInfo($"已打印 {sampleCount} 个可通行体素样本(优先中间区域和高楼层)");
+ LogInfo("=====================================");
+
+ UpdateProgress(50, "正在规划路径...");
+
+ // 步骤4: 创建路径查找器
+ var pathFinder = new VoxelPathFinder(voxelGrid);
+
+ // 步骤5: 配置路径规划参数
+ var config = new VoxelPathFinder.PathFindingConfig
+ {
+ NeighborType = VoxelPathFinder.NeighborType.TwentySix, // 26邻域
+ HeuristicType = VoxelPathFinder.HeuristicType.Euclidean, // 欧几里得距离
+ HeuristicWeight = 1.0, // 不加权
+ VerticalCostFactor = 1.5, // 垂直移动成本1.5倍
+ DiagonalCostFactor = Math.Sqrt(2), // 对角线移动成本√2
+ MaxSearchNodes = 100000 // 最大搜索10万个节点
+ };
+
+ // 步骤6: 执行路径规划
+ LogInfo("开始A*路径搜索...");
+ var pathStartTime = System.Diagnostics.Stopwatch.StartNew();
+
+ var path = pathFinder.FindPath(_startPoint, _endPoint, config);
+
+ pathStartTime.Stop();
+
+ if (path == null || path.Count == 0)
+ {
+ LogError("路径规划失败:未找到有效路径");
+ return PathPlanningResult.Failure("未找到从起点到终点的路径");
+ }
+
+ LogInfo($"✓ 路径规划成功!");
+ LogInfo($" 路径点数: {path.Count}");
+ LogInfo($" 规划耗时: {pathStartTime.ElapsedMilliseconds} ms");
+
+ UpdateProgress(70, "正在计算路径统计信息...");
+
+ // 步骤7: 计算路径统计
+ double pathLength = CalculatePathLength(path);
+ double directDistance = CalculateDistance(_startPoint, _endPoint);
+ double pathRatio = pathLength / directDistance;
+
+ LogInfo($"路径统计:");
+ LogInfo($" 路径总长: {pathLength:F2} 模型单位");
+ LogInfo($" 直线距离: {directDistance:F2} 模型单位");
+ LogInfo($" 路径比率: {pathRatio:F2} (越接近1越优)");
+
+ UpdateProgress(80, "正在可视化路径...");
+
+ // 步骤8: 可视化路径
+ var visualRoute = CreatePathRoute(path, "VoxelPath_Test");
+ var renderPlugin = PathPointRenderPlugin.Instance;
+ if (renderPlugin != null)
+ {
+ renderPlugin.SetGridSize(_cellSize);
+ renderPlugin.RenderPointOnly(visualRoute);
+ LogInfo("✓ 路径已渲染到3D视图");
+ }
+ else
+ {
+ LogWarning("PathPointRenderPlugin 实例不可用,无法渲染路径");
+ }
+
+ UpdateProgress(100, "体素路径规划测试完成");
+
+ string report = GenerateReport(voxelGrid, path, pathLength, directDistance, pathStartTime.ElapsedMilliseconds);
+
+ return PathPlanningResult.Success($"体素路径规划测试完成\n\n{report}");
+ }
+ catch (Exception ex)
+ {
+ LogError($"体素路径规划测试失败: {ex.Message}", ex);
+ return PathPlanningResult.Failure($"体素路径规划测试失败: {ex.Message}", ex);
+ }
+ }
+
+ #region 辅助方法
+
+ private BoundingBox3D GetSpaceBounds(Document document)
+ {
+ try
+ {
+ BoundingBox3D totalBounds = null;
+
+ foreach (var model in document.Models)
+ {
+ if (model.RootItem != null)
+ {
+ var modelBounds = model.RootItem.BoundingBox();
+ if (modelBounds != null)
+ {
+ if (totalBounds == null)
+ {
+ totalBounds = modelBounds;
+ }
+ else
+ {
+ totalBounds = ExpandBounds(totalBounds, modelBounds);
+ }
+ }
+ }
+ }
+
+ return totalBounds;
+ }
+ catch (Exception ex)
+ {
+ LogError($"获取空间范围失败: {ex.Message}", ex);
+ return null;
+ }
+ }
+
+ private BoundingBox3D ExpandBounds(BoundingBox3D bounds1, BoundingBox3D bounds2)
+ {
+ var min = new Point3D(
+ Math.Min(bounds1.Min.X, bounds2.Min.X),
+ Math.Min(bounds1.Min.Y, bounds2.Min.Y),
+ Math.Min(bounds1.Min.Z, bounds2.Min.Z)
+ );
+ var max = new Point3D(
+ Math.Max(bounds1.Max.X, bounds2.Max.X),
+ Math.Max(bounds1.Max.Y, bounds2.Max.Y),
+ Math.Max(bounds1.Max.Z, bounds2.Max.Z)
+ );
+ return new BoundingBox3D(min, max);
+ }
+
+ private List GetAllModelItems(Document document)
+ {
+ var items = new List();
+
+ foreach (var model in document.Models)
+ {
+ if (model.RootItem != null)
+ {
+ CollectAllItems(model.RootItem, items);
+ }
+ }
+
+ return items;
+ }
+
+ private void CollectAllItems(ModelItem item, List result)
+ {
+ if (item.HasGeometry)
+ {
+ result.Add(item);
+ }
+
+ foreach (var child in item.Children)
+ {
+ CollectAllItems(child, result);
+ }
+ }
+
+ private double CalculatePathLength(List path)
+ {
+ if (path == null || path.Count < 2)
+ return 0;
+
+ double length = 0;
+ for (int i = 1; i < path.Count; i++)
+ {
+ length += CalculateDistance(path[i - 1], path[i]);
+ }
+
+ return length;
+ }
+
+ private double CalculateDistance(Point3D p1, Point3D p2)
+ {
+ double dx = p2.X - p1.X;
+ double dy = p2.Y - p1.Y;
+ double dz = p2.Z - p1.Z;
+ return Math.Sqrt(dx * dx + dy * dy + dz * dz);
+ }
+
+ private PathRoute CreatePathRoute(List path, string routeName)
+ {
+ // 将Point3D列表转换为PathPoint列表
+ var pathPoints = new List();
+ foreach (var pt in path)
+ {
+ pathPoints.Add(new PathPoint
+ {
+ Position = pt
+ });
+ }
+
+ var route = new PathRoute
+ {
+ Id = Guid.NewGuid().ToString(),
+ Name = routeName,
+ Points = pathPoints,
+ IsComplete = true,
+ CreatedTime = DateTime.Now
+ };
+
+ return route;
+ }
+
+ private string GenerateReport(VoxelGrid voxelGrid, List path, double pathLength, double directDistance, long planningTimeMs)
+ {
+ var (total, passable, obstacle) = voxelGrid.GetStatistics();
+
+ return $@"================================
+ 体素路径规划测试报告
+================================
+
+【体素网格信息】
+网格尺寸: {voxelGrid.SizeX} × {voxelGrid.SizeY} × {voxelGrid.SizeZ}
+体素大小: {voxelGrid.VoxelSize:F4} 模型单位
+总体素数: {total:N0}
+可通行体素: {passable:N0} ({passable*100.0/total:F1}%)
+障碍物体素: {obstacle:N0} ({obstacle*100.0/total:F1}%)
+
+【路径规划结果】
+路径点数: {path.Count}
+路径总长: {pathLength:F2} 模型单位
+直线距离: {directDistance:F2} 模型单位
+路径比率: {pathLength/directDistance:F2} (1.0为最优直线)
+规划耗时: {planningTimeMs} ms
+
+【性能评估】
+✅ 路径规划成功
+✅ 性能: {(planningTimeMs < 1000 ? "优秀" : planningTimeMs < 5000 ? "良好" : "需优化")} ({planningTimeMs}ms)
+✅ 路径质量: {(pathLength/directDistance < 1.5 ? "优秀" : pathLength/directDistance < 2.0 ? "良好" : "一般")}
+
+================================";
+ }
+
+ #endregion
+ }
+}
diff --git a/src/PathPlanning/VoxelGrid.cs b/src/PathPlanning/VoxelGrid.cs
index e94e63d..89de332 100644
--- a/src/PathPlanning/VoxelGrid.cs
+++ b/src/PathPlanning/VoxelGrid.cs
@@ -435,9 +435,8 @@ namespace NavisworksTransport.PathPlanning
float upperBound = (SizeX + SizeY + SizeZ) * (float)VoxelSize;
var distanceGrid = new g4.DenseGrid3f(SizeX, SizeY, SizeZ, upperBound);
- // 3. 初始化距离图:障碍物=0, 其他=upperBound
+ // 3. 初始化距离图:障碍物=0, 可通行区域=upperBound
int obstacleCount = 0;
- int doorCount = 0;
for (int x = 0; x < SizeX; x++)
{
@@ -447,19 +446,13 @@ namespace NavisworksTransport.PathPlanning
{
var cell = cells[x, y, z];
- // 门类型不参与膨胀(设为upperBound,不会被扩散)
- if (cell.Type == CategoryAttributeManager.LogisticsElementType.门)
- {
- distanceGrid[x, y, z] = upperBound;
- doorCount++;
- }
- // 障碍物(非门):距离=0
- else if (!cell.IsPassable)
+ // 简单逻辑:只有障碍物(IsPassable=false)才设置为膨胀源(distance=0)
+ // 所有可通行区域(门、楼梯、通道等IsPassable=true)都不膨胀
+ if (!cell.IsPassable)
{
distanceGrid[x, y, z] = 0f;
obstacleCount++;
}
- // 可通行区域:距离=upperBound
else
{
distanceGrid[x, y, z] = upperBound;
@@ -468,49 +461,87 @@ namespace NavisworksTransport.PathPlanning
}
}
- LogManager.Info($"[体素膨胀] 初始化距离图完成 - 障碍物: {obstacleCount}, 门: {doorCount}");
+ LogManager.Info($"[体素膨胀] 初始化完成 - 障碍物体素: {obstacleCount}");
- // 4. Fast Sweeping传播距离(复用g4的sweep算法)
- var sweepStopwatch = System.Diagnostics.Stopwatch.StartNew();
- PerformFastSweeping(distanceGrid, (float)VoxelSize);
- sweepStopwatch.Stop();
+ // 4. 迭代膨胀:只在XY平面(水平方向)膨胀,不在Z方向膨胀
+ // 使用简单的形态学膨胀,每次迭代向XY方向扩展1个体素
+ LogManager.Info($"[体素膨胀] 开始迭代膨胀(只在XY平面)...");
+ var inflationStopwatch = System.Diagnostics.Stopwatch.StartNew();
- LogManager.Info($"[体素膨胀] Fast Sweeping完成,耗时: {sweepStopwatch.ElapsedMilliseconds}ms");
+ int inflationRadius = (int)Math.Ceiling(inflationRadiusInModelUnits / VoxelSize);
+ LogManager.Info($"[体素膨胀] 膨胀半径: {inflationRadius} 个体素");
- // 5. 应用膨胀:标记 0 < distance <= inflationRadius 的体素为障碍物
- int inflatedCount = 0;
- float thresholdDistance = (float)inflationRadiusInModelUnits;
+ int totalInflated = 0;
- for (int x = 0; x < SizeX; x++)
+ // 迭代膨胀 inflationRadius 次
+ for (int iteration = 0; iteration < inflationRadius; iteration++)
{
- for (int y = 0; y < SizeY; y++)
+ // 记录本次迭代要膨胀的体素(避免在遍历时修改)
+ var toInflate = new List<(int x, int y, int z)>();
+
+ // 遍历所有体素,找到障碍物的XY邻域
+ for (int x = 0; x < SizeX; x++)
{
- for (int z = 0; z < SizeZ; z++)
+ for (int y = 0; y < SizeY; y++)
{
- var cell = cells[x, y, z];
-
- // 保护门:门类型不被修改
- if (cell.Type == CategoryAttributeManager.LogisticsElementType.门)
- continue;
-
- float dist = distanceGrid[x, y, z];
-
- // 膨胀条件:0 < distance <= inflationRadius
- // distance = 0 的是原障碍物,不修改
- // distance > inflationRadius 的保持可通行
- if (dist > 0 && dist <= thresholdDistance)
+ for (int z = 0; z < SizeZ; z++)
{
- cell.SetAsObstacle();
- cell.Type = CategoryAttributeManager.LogisticsElementType.障碍物;
- cells[x, y, z] = cell;
- inflatedCount++;
+ // 只处理障碍物体素
+ if (!cells[x, y, z].IsPassable)
+ {
+ // 检查XY平面的4个邻居(上下左右),不检查Z方向
+ int[] dx = { -1, 1, 0, 0 };
+ int[] dy = { 0, 0, -1, 1 };
+
+ for (int dir = 0; dir < 4; dir++)
+ {
+ int nx = x + dx[dir];
+ int ny = y + dy[dir];
+ int nz = z; // Z保持不变,不向上下膨胀
+
+ // 检查边界
+ if (nx >= 0 && nx < SizeX && ny >= 0 && ny < SizeY)
+ {
+ // 如果邻居是可通行的,标记为待膨胀
+ if (cells[nx, ny, nz].IsPassable)
+ {
+ toInflate.Add((nx, ny, nz));
+ }
+ }
+ }
+ }
}
}
}
+
+ // 应用膨胀
+ int iterationCount = 0;
+ foreach (var (x, y, z) in toInflate)
+ {
+ // 再次检查,避免重复标记
+ if (cells[x, y, z].IsPassable)
+ {
+ cells[x, y, z].SetAsObstacle();
+ cells[x, y, z].Type = CategoryAttributeManager.LogisticsElementType.障碍物;
+ iterationCount++;
+ }
+ }
+
+ totalInflated += iterationCount;
+ LogManager.Info($"[体素膨胀] 第 {iteration + 1}/{inflationRadius} 次迭代:膨胀了 {iterationCount} 个体素");
+
+ // 如果本次没有膨胀任何体素,提前结束
+ if (iterationCount == 0)
+ {
+ LogManager.Info($"[体素膨胀] 第 {iteration + 1} 次迭代无新增膨胀,提前结束");
+ break;
+ }
}
- LogManager.Info($"[体素膨胀] 完成 - 膨胀体素数: {inflatedCount}");
- return inflatedCount;
+ inflationStopwatch.Stop();
+ LogManager.Info($"[体素膨胀] 迭代膨胀完成,耗时: {inflationStopwatch.ElapsedMilliseconds}ms");
+ LogManager.Info($"[体素膨胀] 总膨胀体素数: {totalInflated}");
+ return totalInflated;
}
///
diff --git a/src/PathPlanning/VoxelPathFinder.cs b/src/PathPlanning/VoxelPathFinder.cs
new file mode 100644
index 0000000..c327874
--- /dev/null
+++ b/src/PathPlanning/VoxelPathFinder.cs
@@ -0,0 +1,410 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Autodesk.Navisworks.Api;
+using NavisworksTransport.Utils;
+
+namespace NavisworksTransport.PathPlanning
+{
+ ///
+ /// 3D体素网格路径查找器
+ /// 基于A*算法的真3D路径规划实现
+ ///
+ public class VoxelPathFinder
+ {
+ private readonly VoxelGrid _voxelGrid;
+
+ ///
+ /// 邻域类型
+ ///
+ public enum NeighborType
+ {
+ /// 6邻域(上下左右前后)
+ Six = 6,
+
+ /// 26邻域(包括对角线)
+ TwentySix = 26
+ }
+
+ ///
+ /// 启发式函数类型
+ ///
+ public enum HeuristicType
+ {
+ /// 欧几里得距离
+ Euclidean,
+
+ /// 曼哈顿距离
+ Manhattan,
+
+ /// 切比雪夫距离
+ Chebyshev
+ }
+
+ ///
+ /// 路径查找配置
+ ///
+ public class PathFindingConfig
+ {
+ /// 邻域类型(默认26邻域)
+ public NeighborType NeighborType { get; set; } = NeighborType.TwentySix;
+
+ /// 启发式函数类型(默认欧几里得)
+ public HeuristicType HeuristicType { get; set; } = HeuristicType.Euclidean;
+
+ /// 启发式权重(>1加速搜索但可能不是最优路径)
+ public double HeuristicWeight { get; set; } = 1.0;
+
+ /// 垂直移动额外成本系数(鼓励水平移动)
+ public double VerticalCostFactor { get; set; } = 1.5;
+
+ /// 对角线移动成本系数
+ public double DiagonalCostFactor { get; set; } = Math.Sqrt(2);
+
+ /// 最大搜索节点数(防止无限循环)
+ public int MaxSearchNodes { get; set; } = 100000;
+ }
+
+ ///
+ /// A*节点
+ ///
+ private class AStarNode : IComparable
+ {
+ public int X { get; set; }
+ public int Y { get; set; }
+ public int Z { get; set; }
+
+ /// 从起点到当前节点的实际成本
+ public double GCost { get; set; }
+
+ /// 从当前节点到终点的启发式估计成本
+ public double HCost { get; set; }
+
+ /// 总成本 F = G + H
+ public double FCost => GCost + HCost;
+
+ /// 父节点
+ public AStarNode Parent { get; set; }
+
+ public int CompareTo(AStarNode other)
+ {
+ int fCompare = FCost.CompareTo(other.FCost);
+ if (fCompare != 0) return fCompare;
+ // F相同时,比较H(优先选择离终点近的)
+ return HCost.CompareTo(other.HCost);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is AStarNode other)
+ {
+ return X == other.X && Y == other.Y && Z == other.Z;
+ }
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ // .NET Framework 4.8不支持HashCode.Combine,使用传统方式
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 31 + X.GetHashCode();
+ hash = hash * 31 + Y.GetHashCode();
+ hash = hash * 31 + Z.GetHashCode();
+ return hash;
+ }
+ }
+ }
+
+ ///
+ /// 构造函数
+ ///
+ /// 体素网格
+ public VoxelPathFinder(VoxelGrid voxelGrid)
+ {
+ _voxelGrid = voxelGrid ?? throw new ArgumentNullException(nameof(voxelGrid));
+ }
+
+ ///
+ /// 查找3D路径
+ ///
+ /// 起点世界坐标
+ /// 终点世界坐标
+ /// 路径查找配置
+ /// 路径点列表(世界坐标),如果无路径则返回null
+ public List FindPath(Point3D start, Point3D end, PathFindingConfig config = null)
+ {
+ config = config ?? new PathFindingConfig();
+
+ // 转换为体素坐标
+ var startVoxel = _voxelGrid.WorldToVoxel(start);
+ var endVoxel = _voxelGrid.WorldToVoxel(end);
+
+ LogManager.Info($"[VoxelPathFinder] 开始路径规划");
+ LogManager.Info($" 起点: 世界{start} -> 体素({startVoxel.x}, {startVoxel.y}, {startVoxel.z})");
+ LogManager.Info($" 终点: 世界{end} -> 体素({endVoxel.x}, {endVoxel.y}, {endVoxel.z})");
+
+ // 验证起终点
+ if (!ValidatePoint(startVoxel.x, startVoxel.y, startVoxel.z, "起点"))
+ return null;
+
+ if (!ValidatePoint(endVoxel.x, endVoxel.y, endVoxel.z, "终点"))
+ return null;
+
+ // 执行A*搜索
+ var pathVoxels = FindPathAStar(startVoxel, endVoxel, config);
+
+ if (pathVoxels == null || pathVoxels.Count == 0)
+ {
+ LogManager.Warning("[VoxelPathFinder] 未找到路径");
+ return null;
+ }
+
+ // 转换为世界坐标
+ var pathWorld = pathVoxels.Select(v => _voxelGrid.VoxelToWorld(v.X, v.Y, v.Z)).ToList();
+
+ LogManager.Info($"[VoxelPathFinder] 路径找到!长度: {pathVoxels.Count}个体素, {CalculatePathLength(pathWorld):F2}米");
+
+ return pathWorld;
+ }
+
+ ///
+ /// A*路径搜索核心算法
+ ///
+ private List FindPathAStar((int x, int y, int z) start, (int x, int y, int z) end, PathFindingConfig config)
+ {
+ var startTime = System.Diagnostics.Stopwatch.StartNew();
+
+ // 开放列表(使用SortedSet作为优先队列)
+ var openSet = new SortedSet();
+
+ // 已访问节点集合(快速查找)
+ var closedSet = new HashSet<(int, int, int)>();
+
+ // 节点字典(快速访问G成本)
+ var nodeDict = new Dictionary<(int, int, int), AStarNode>();
+
+ // 创建起始节点
+ var startNode = new AStarNode
+ {
+ X = start.x,
+ Y = start.y,
+ Z = start.z,
+ GCost = 0,
+ HCost = CalculateHeuristic(start, end, config.HeuristicType)
+ };
+
+ openSet.Add(startNode);
+ nodeDict[(start.x, start.y, start.z)] = startNode;
+
+ int nodesExpanded = 0;
+
+ while (openSet.Count > 0)
+ {
+ // 检查节点数限制
+ if (++nodesExpanded > config.MaxSearchNodes)
+ {
+ LogManager.Warning($"[VoxelPathFinder] 搜索节点数超过限制 {config.MaxSearchNodes}");
+ break;
+ }
+
+ // 获取F值最小的节点
+ var current = openSet.Min;
+ openSet.Remove(current);
+
+ // 到达终点
+ if (current.X == end.x && current.Y == end.y && current.Z == end.z)
+ {
+ startTime.Stop();
+ LogManager.Info($"[VoxelPathFinder] A*搜索完成: 耗时{startTime.ElapsedMilliseconds}ms, 扩展节点{nodesExpanded}个");
+ return ReconstructPath(current);
+ }
+
+ closedSet.Add((current.X, current.Y, current.Z));
+
+ // 遍历邻居
+ var neighbors = GetNeighbors(current.X, current.Y, current.Z, config.NeighborType);
+
+ foreach (var (nx, ny, nz) in neighbors)
+ {
+ // 跳过已访问的节点
+ if (closedSet.Contains((nx, ny, nz)))
+ continue;
+
+ // 跳过不可通行的节点
+ if (!_voxelGrid.IsPassable(nx, ny, nz))
+ continue;
+
+ // 计算移动成本
+ double moveCost = CalculateMoveCost(current.X, current.Y, current.Z, nx, ny, nz, config);
+ double tentativeG = current.GCost + moveCost;
+
+ // 检查是否找到更好的路径
+ if (!nodeDict.TryGetValue((nx, ny, nz), out var neighbor))
+ {
+ // 新节点
+ neighbor = new AStarNode
+ {
+ X = nx,
+ Y = ny,
+ Z = nz,
+ GCost = tentativeG,
+ HCost = CalculateHeuristic((nx, ny, nz), end, config.HeuristicType) * config.HeuristicWeight,
+ Parent = current
+ };
+
+ nodeDict[(nx, ny, nz)] = neighbor;
+ openSet.Add(neighbor);
+ }
+ else if (tentativeG < neighbor.GCost)
+ {
+ // 找到更好的路径,更新节点
+ openSet.Remove(neighbor);
+ neighbor.GCost = tentativeG;
+ neighbor.Parent = current;
+ openSet.Add(neighbor);
+ }
+ }
+ }
+
+ startTime.Stop();
+ LogManager.Warning($"[VoxelPathFinder] 未找到路径: 耗时{startTime.ElapsedMilliseconds}ms, 扩展节点{nodesExpanded}个");
+ return null;
+ }
+
+ ///
+ /// 重建路径(从终点回溯到起点)
+ ///
+ private List ReconstructPath(AStarNode endNode)
+ {
+ var path = new List();
+ var current = endNode;
+
+ while (current != null)
+ {
+ path.Add(current);
+ current = current.Parent;
+ }
+
+ path.Reverse();
+ return path;
+ }
+
+ ///
+ /// 获取邻居节点
+ ///
+ private List<(int x, int y, int z)> GetNeighbors(int x, int y, int z, NeighborType neighborType)
+ {
+ if (neighborType == NeighborType.Six)
+ {
+ return _voxelGrid.GetNeighbors6(x, y, z);
+ }
+ else
+ {
+ return _voxelGrid.GetNeighbors26(x, y, z);
+ }
+ }
+
+ ///
+ /// 计算移动成本
+ ///
+ private double CalculateMoveCost(int x1, int y1, int z1, int x2, int y2, int z2, PathFindingConfig config)
+ {
+ double dx = x2 - x1;
+ double dy = y2 - y1;
+ double dz = z2 - z1;
+
+ // 基础欧几里得距离
+ double baseCost = Math.Sqrt(dx * dx + dy * dy + dz * dz);
+
+ // 垂直移动额外成本
+ if (dz != 0)
+ {
+ baseCost *= config.VerticalCostFactor;
+ }
+
+ // 对角线移动
+ if (dx != 0 && dy != 0 && dz == 0)
+ {
+ baseCost *= config.DiagonalCostFactor;
+ }
+
+ // 考虑体素类型的速度限制
+ var cell = _voxelGrid.GetCell(x2, y2, z2);
+ if (cell.SpeedLimit > 0 && cell.SpeedLimit < 1.0)
+ {
+ baseCost /= cell.SpeedLimit; // 速度限制越低,成本越高
+ }
+
+ return baseCost;
+ }
+
+ ///
+ /// 计算启发式估计
+ ///
+ private double CalculateHeuristic((int x, int y, int z) from, (int x, int y, int z) to, HeuristicType heuristicType)
+ {
+ int dx = Math.Abs(to.x - from.x);
+ int dy = Math.Abs(to.y - from.y);
+ int dz = Math.Abs(to.z - from.z);
+
+ switch (heuristicType)
+ {
+ case HeuristicType.Euclidean:
+ return Math.Sqrt(dx * dx + dy * dy + dz * dz);
+
+ case HeuristicType.Manhattan:
+ return dx + dy + dz;
+
+ case HeuristicType.Chebyshev:
+ return Math.Max(Math.Max(dx, dy), dz);
+
+ default:
+ return Math.Sqrt(dx * dx + dy * dy + dz * dz);
+ }
+ }
+
+ ///
+ /// 验证点是否有效
+ ///
+ private bool ValidatePoint(int x, int y, int z, string pointName)
+ {
+ if (!_voxelGrid.IsValidIndex(x, y, z))
+ {
+ LogManager.Error($"[VoxelPathFinder] {pointName}超出体素网格范围: ({x}, {y}, {z})");
+ return false;
+ }
+
+ if (!_voxelGrid.IsPassable(x, y, z))
+ {
+ LogManager.Error($"[VoxelPathFinder] {pointName}不可通行: ({x}, {y}, {z})");
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// 计算路径长度
+ ///
+ private double CalculatePathLength(List path)
+ {
+ if (path == null || path.Count < 2)
+ return 0;
+
+ double length = 0;
+ for (int i = 1; i < path.Count; i++)
+ {
+ var p1 = path[i - 1];
+ var p2 = path[i];
+ double dx = p2.X - p1.X;
+ double dy = p2.Y - p1.Y;
+ double dz = p2.Z - p1.Z;
+ length += Math.Sqrt(dx * dx + dy * dy + dz * dz);
+ }
+
+ return length;
+ }
+ }
+}
diff --git a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs
index 8a3d995..a05f1af 100644
--- a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs
+++ b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs
@@ -228,8 +228,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand DiagnosticCommand { get; private set; }
// 功能测试命令
- public ICommand TestVoxelGridCommand { get; private set; }
public ICommand TestVoxelGridSDFCommand { get; private set; }
+ public ICommand TestVoxelPathFindingCommand { get; private set; }
#endregion
@@ -331,8 +331,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
DiagnosticCommand = new RelayCommand(() => ExecuteDiagnostic());
// 功能测试命令
- TestVoxelGridCommand = new RelayCommand(() => ExecuteTestVoxelGrid());
TestVoxelGridSDFCommand = new RelayCommand(() => ExecuteTestVoxelGridSDF());
+ TestVoxelPathFindingCommand = new RelayCommand(() => ExecuteTestVoxelPathFinding());
LogManager.Info("系统管理命令初始化完成");
}
@@ -850,81 +850,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
- ///
- /// 执行体素网格测试
- ///
- private async void ExecuteTestVoxelGrid()
- {
- await SafeExecuteAsync(async () =>
- {
- try
- {
- UpdateMainStatus("正在执行体素网格测试...");
- LogManager.Info("开始体素网格测试");
-
- // 获取当前文档
- var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
- if (document == null)
- {
- System.Windows.MessageBox.Show(
- "未找到活动文档,请先打开一个 Navisworks 模型",
- "体素网格测试",
- System.Windows.MessageBoxButton.OK,
- System.Windows.MessageBoxImage.Warning);
- UpdateMainStatus("体素网格测试取消:无活动文档");
- return;
- }
-
- // 创建并执行体素网格测试命令(对整个空间体素化,无需选中对象)
- // 使用系统配置的网格大小参数
- double cellSizeMeters = ConfigManager.Instance.Current.PathEditing.CellSizeMeters;
- LogManager.Info($"使用系统配置的体素大小: {cellSizeMeters}米");
-
- var testCommand = new VoxelGridTestCommand(
- document,
- cellSize: cellSizeMeters, // 使用系统配置的体素大小
- samplingRate: 1 // 采样率 1(显示所有体素)
- );
-
- var result = await testCommand.ExecuteAsync();
-
- if (result.IsSuccess)
- {
- // 显示测试结果(简化版,不依赖Data属性)
- System.Windows.MessageBox.Show(
- $"体素网格测试成功!\n\n{result.Message}\n\n详细信息请查看日志。",
- "体素网格测试结果",
- System.Windows.MessageBoxButton.OK,
- System.Windows.MessageBoxImage.Information);
-
- UpdateMainStatus("体素网格测试完成");
- LogManager.Info($"体素网格测试成功: {result.Message}");
- }
- else
- {
- System.Windows.MessageBox.Show(
- $"体素网格测试失败:\n{result.Message}",
- "体素网格测试",
- System.Windows.MessageBoxButton.OK,
- System.Windows.MessageBoxImage.Error);
-
- UpdateMainStatus($"体素网格测试失败: {result.Message}");
- LogManager.Error($"体素网格测试失败: {result.Message}");
- }
- }
- catch (Exception ex)
- {
- LogManager.Error($"体素网格测试异常: {ex.Message}", ex);
- System.Windows.MessageBox.Show(
- $"体素网格测试出现异常:\n{ex.Message}",
- "错误",
- System.Windows.MessageBoxButton.OK,
- System.Windows.MessageBoxImage.Error);
- UpdateMainStatus($"体素网格测试异常: {ex.Message}");
- }
- }, "体素网格测试");
- }
-
///
/// 执行体素网格 SDF 测试(使用 MeshSignedDistanceGrid)
///
@@ -1002,6 +927,99 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}, "体素网格 SDF 测试");
}
+ ///
+ /// 执行体素路径规划测试(3D A*)
+ ///
+ private async void ExecuteTestVoxelPathFinding()
+ {
+ await SafeExecuteAsync(async () =>
+ {
+ try
+ {
+ UpdateMainStatus("正在执行体素路径规划测试...");
+ LogManager.Info("开始体素路径规划测试(3D A*)");
+
+ // 获取当前文档
+ var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
+ if (document == null)
+ {
+ System.Windows.MessageBox.Show(
+ "未找到活动文档,请先打开一个 Navisworks 模型",
+ "体素路径规划测试",
+ System.Windows.MessageBoxButton.OK,
+ System.Windows.MessageBoxImage.Warning);
+ UpdateMainStatus("体素路径规划测试取消:无活动文档");
+ return;
+ }
+
+ // 使用系统配置的网格大小参数
+ double cellSizeMeters = ConfigManager.Instance.Current.PathEditing.CellSizeMeters;
+ LogManager.Info($"使用系统配置的体素大小: {cellSizeMeters}米");
+
+ // 根据可通行体素样本选择测试起点和终点
+ // 起点:体素(77, 41, 9) -> 世界(-116.35, 4.94, 50.16) - 模型中心、最高层
+ // 终点:体素(107, 11, 8) -> 世界(-67.13, -44.27, 48.52) - 偏右下、低一层
+ // 注意:VoxelToWorld返回的是体素左下角坐标,需要加上半个体素尺寸偏移到中心点
+ double voxelSizeInModelUnits = cellSizeMeters * UnitsConverter.GetMetersToUnitsConversionFactor(document.Units);
+ double halfVoxel = voxelSizeInModelUnits / 2.0;
+
+ // 起点:模型中心、最高层Z=9
+ var startPoint = new Autodesk.Navisworks.Api.Point3D(-165.56 + halfVoxel, -44.27 + halfVoxel, 50.16 + halfVoxel);
+ // 终点:偏右下、低一层Z=8
+ var endPoint = new Autodesk.Navisworks.Api.Point3D(-116.35 + halfVoxel, 0 + halfVoxel, 45 + halfVoxel);
+
+ LogManager.Info($"测试起点: ({startPoint.X:F2}, {startPoint.Y:F2}, {startPoint.Z:F2})");
+ LogManager.Info($"测试终点: ({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2})");
+
+ // 创建并执行体素路径规划测试命令
+ var testCommand = new VoxelPathFindingTestCommand(
+ document,
+ cellSize: cellSizeMeters, // 体素大小(米)
+ vehicleRadius: 0.6, // 车辆半径 0.6米
+ vehicleHeight: 1.8, // 车辆高度 1.8米
+ startPoint: startPoint, // 起点
+ endPoint: endPoint // 终点
+ );
+
+ var result = await testCommand.ExecuteAsync();
+
+ if (result.IsSuccess)
+ {
+ // 显示测试结果
+ System.Windows.MessageBox.Show(
+ $"体素路径规划测试成功!\n\n{result.Message}\n\n详细信息请查看日志和3D视图中的路径可视化。",
+ "体素路径规划测试结果",
+ System.Windows.MessageBoxButton.OK,
+ System.Windows.MessageBoxImage.Information);
+
+ UpdateMainStatus("体素路径规划测试完成");
+ LogManager.Info($"体素路径规划测试成功: {result.Message}");
+ }
+ else
+ {
+ System.Windows.MessageBox.Show(
+ $"体素路径规划测试失败:\n{result.Message}",
+ "体素路径规划测试",
+ System.Windows.MessageBoxButton.OK,
+ System.Windows.MessageBoxImage.Error);
+
+ UpdateMainStatus($"体素路径规划测试失败: {result.Message}");
+ LogManager.Error($"体素路径规划测试失败: {result.Message}");
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"体素路径规划测试异常: {ex.Message}", ex);
+ System.Windows.MessageBox.Show(
+ $"体素路径规划测试出现异常:\n{ex.Message}",
+ "错误",
+ System.Windows.MessageBoxButton.OK,
+ System.Windows.MessageBoxImage.Error);
+ UpdateMainStatus($"体素路径规划测试异常: {ex.Message}");
+ }
+ }, "体素路径规划测试");
+ }
+
#endregion
#region 辅助方法
diff --git a/src/UI/WPF/Views/SystemManagementView.xaml b/src/UI/WPF/Views/SystemManagementView.xaml
index ac707e8..c220668 100644
--- a/src/UI/WPF/Views/SystemManagementView.xaml
+++ b/src/UI/WPF/Views/SystemManagementView.xaml
@@ -230,23 +230,6 @@ NavisworksTransport 系统管理页签视图 - 采用与其他页签一致的Nav
Margin="0,5,0,10"
TextWrapping="Wrap"/>
-
-
-
-
-
-
-
+
+
+
+
+
+
+