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"/> - - -