diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj
index 91e6054..d2779bd 100644
--- a/NavisworksTransportPlugin.csproj
+++ b/NavisworksTransportPlugin.csproj
@@ -196,6 +196,7 @@
+
diff --git a/src/PathPlanning/VoxelGridVisualizer.cs b/src/PathPlanning/VoxelGridVisualizer.cs
new file mode 100644
index 0000000..fb72dae
--- /dev/null
+++ b/src/PathPlanning/VoxelGridVisualizer.cs
@@ -0,0 +1,388 @@
+using System;
+using System.Collections.Generic;
+using Autodesk.Navisworks.Api;
+using NavisworksTransport.Utils;
+using static NavisworksTransport.CategoryAttributeManager;
+
+namespace NavisworksTransport.PathPlanning
+{
+ ///
+ /// 体素网格可视化器 - 在 Navisworks 3D 视图中显示体素网格
+ /// 阶段 1.5:体素可视化验证
+ ///
+ public class VoxelGridVisualizer
+ {
+ ///
+ /// 可视化体素网格(简化版本 - 使用小球表示体素)
+ ///
+ /// 体素网格
+ /// 采样率(1=全部显示,2=每隔一个显示,以此类推)
+ /// 是否只显示可通行体素
+ /// 生成的路径点列表,可用于PathPointRenderPlugin渲染
+ public static List VisualizeAsPoints(
+ VoxelGrid voxelGrid,
+ int samplingRate = 2,
+ bool showPassableOnly = false)
+ {
+ LogManager.Info("=== 开始体素网格可视化 ===");
+ LogManager.Info($"网格尺寸: {voxelGrid.SizeX} × {voxelGrid.SizeY} × {voxelGrid.SizeZ}");
+ LogManager.Info($"采样率: {samplingRate} (每{samplingRate}个体素显示1个)");
+ LogManager.Info($"仅显示可通行: {showPassableOnly}");
+
+ var points = new List();
+ int totalVoxels = 0;
+ int visualizedVoxels = 0;
+
+ try
+ {
+ for (int x = 0; x < voxelGrid.SizeX; x += samplingRate)
+ {
+ for (int y = 0; y < voxelGrid.SizeY; y += samplingRate)
+ {
+ for (int z = 0; z < voxelGrid.SizeZ; z += samplingRate)
+ {
+ totalVoxels++;
+
+ var cell = voxelGrid.GetCell(x, y, z);
+ if (cell == null) continue;
+
+ // 过滤:如果只显示可通行体素,跳过不可通行的
+ if (showPassableOnly && !cell.IsPassable)
+ continue;
+
+ // 获取体素中心点的世界坐标
+ var worldPos = voxelGrid.VoxelToWorldCenter(x, y, z);
+
+ // 创建路径点表示体素
+ var point = new PathPoint
+ {
+ Index = visualizedVoxels,
+ Position = worldPos,
+ Name = GetVoxelDisplayName(cell, x, y, z),
+ Type = PathPointType.WayPoint,
+ Notes = $"VoxelType:{cell.Type}"
+ };
+
+ points.Add(point);
+ visualizedVoxels++;
+ }
+ }
+ }
+
+ LogManager.Info($"总体素数: {voxelGrid.TotalVoxels:N0}");
+ LogManager.Info($"采样后体素数: {totalVoxels:N0}");
+ LogManager.Info($"可视化体素数: {visualizedVoxels:N0}");
+ LogManager.Info("=== 体素网格可视化完成 ===");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"体素网格可视化失败: {ex.Message}");
+ LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
+ }
+
+ return points;
+ }
+
+ ///
+ /// 可视化体素网格的切片(指定Z高度)
+ ///
+ /// 体素网格
+ /// Z方向索引(高度层)
+ /// 生成的路径点列表
+ public static List VisualizeSlice(VoxelGrid voxelGrid, int zIndex)
+ {
+ LogManager.Info($"=== 可视化体素网格切片 (Z={zIndex}) ===");
+
+ var points = new List();
+ int visualizedCount = 0;
+
+ try
+ {
+ if (!voxelGrid.IsValidIndex(0, 0, zIndex))
+ {
+ LogManager.Warning($"无效的 Z 索引: {zIndex},有效范围: 0 ~ {voxelGrid.SizeZ - 1}");
+ return points;
+ }
+
+ for (int x = 0; x < voxelGrid.SizeX; x++)
+ {
+ for (int y = 0; y < voxelGrid.SizeY; y++)
+ {
+ var cell = voxelGrid.GetCell(x, y, zIndex);
+ if (cell == null) continue;
+
+ // 获取体素中心点的世界坐标
+ var worldPos = voxelGrid.VoxelToWorldCenter(x, y, zIndex);
+
+ // 创建路径点表示体素
+ var point = new PathPoint
+ {
+ Index = visualizedCount,
+ Position = worldPos,
+ Name = GetVoxelDisplayName(cell, x, y, zIndex),
+ Type = PathPointType.WayPoint,
+ Notes = $"VoxelType:{cell.Type}"
+ };
+
+ points.Add(point);
+ visualizedCount++;
+ }
+ }
+
+ LogManager.Info($"切片 Z={zIndex} 可视化体素数: {visualizedCount:N0}");
+ LogManager.Info("=== 切片可视化完成 ===");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"切片可视化失败: {ex.Message}");
+ LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
+ }
+
+ return points;
+ }
+
+ ///
+ /// 可视化体素网格的边界(只显示障碍物表面)
+ ///
+ /// 体素网格
+ /// 生成的路径点列表
+ public static List VisualizeBoundary(VoxelGrid voxelGrid)
+ {
+ LogManager.Info("=== 可视化体素网格边界(障碍物表面) ===");
+
+ var points = new List();
+ int visualizedCount = 0;
+
+ try
+ {
+ for (int x = 0; x < voxelGrid.SizeX; x++)
+ {
+ for (int y = 0; y < voxelGrid.SizeY; y++)
+ {
+ for (int z = 0; z < voxelGrid.SizeZ; z++)
+ {
+ var cell = voxelGrid.GetCell(x, y, z);
+ if (cell == null || cell.IsPassable) continue;
+
+ // 检查是否是边界体素(至少有一个相邻体素是可通行的)
+ if (IsBoundaryVoxel(voxelGrid, x, y, z))
+ {
+ var worldPos = voxelGrid.VoxelToWorldCenter(x, y, z);
+
+ var point = new PathPoint
+ {
+ Index = visualizedCount,
+ Position = worldPos,
+ Name = GetVoxelDisplayName(cell, x, y, z),
+ Type = PathPointType.WayPoint,
+ Notes = $"VoxelType:{cell.Type},Boundary:true"
+ };
+
+ points.Add(point);
+ visualizedCount++;
+ }
+ }
+ }
+ }
+
+ LogManager.Info($"边界体素数: {visualizedCount:N0}");
+ LogManager.Info("=== 边界可视化完成 ===");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"边界可视化失败: {ex.Message}");
+ LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
+ }
+
+ return points;
+ }
+
+ ///
+ /// 判断体素是否为边界体素
+ ///
+ private static bool IsBoundaryVoxel(VoxelGrid voxelGrid, int x, int y, int z)
+ {
+ // 获取 6 邻域
+ var neighbors = voxelGrid.GetNeighbors6(x, y, z);
+
+ // 如果至少有一个邻居是可通行的,则当前体素是边界
+ foreach (var (nx, ny, nz) in neighbors)
+ {
+ if (voxelGrid.IsPassable(nx, ny, nz))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 获取体素显示名称
+ ///
+ private static string GetVoxelDisplayName(VoxelCell cell, int x, int y, int z)
+ {
+ string typeStr = cell.IsPassable ? "通行" : "障碍";
+ return $"体素({x},{y},{z})-{typeStr}-{cell.Type}";
+ }
+
+ ///
+ /// 将体素列表转换为 PathRoute,以便使用 PathPointRenderPlugin 渲染
+ ///
+ /// 体素点列表
+ /// 路径名称
+ /// PathRoute 对象
+ public static PathRoute ConvertToPathRoute(List voxelPoints, string routeName = "体素网格可视化")
+ {
+ var route = new PathRoute
+ {
+ Id = $"voxel_visualization_{Guid.NewGuid():N}",
+ Name = routeName,
+ Points = voxelPoints,
+ IsComplete = true,
+ CreatedTime = DateTime.Now
+ };
+
+ return route;
+ }
+
+ ///
+ /// 快速可视化测试
+ ///
+ /// 体素网格
+ public static void QuickVisualizationTest(VoxelGrid voxelGrid)
+ {
+ LogManager.Info("=== 开始快速可视化测试 ===");
+
+ try
+ {
+ // 统计信息
+ var (total, passable, obstacle) = voxelGrid.GetStatistics();
+ LogManager.Info($"体素统计: 总数={total:N0}, 可通行={passable:N0}, 障碍={obstacle:N0}");
+
+ // 测试1:可视化底层切片(Z=0)
+ LogManager.Info("[测试 1] 可视化底层切片 (Z=0)");
+ var slicePoints = VisualizeSlice(voxelGrid, 0);
+ LogManager.Info($"底层切片点数: {slicePoints.Count}");
+
+ // 测试2:可视化中间层切片(Z=中间)
+ int midZ = voxelGrid.SizeZ / 2;
+ LogManager.Info($"[测试 2] 可视化中间层切片 (Z={midZ})");
+ var midSlicePoints = VisualizeSlice(voxelGrid, midZ);
+ LogManager.Info($"中间层切片点数: {midSlicePoints.Count}");
+
+ // 测试3:可视化边界
+ LogManager.Info("[测试 3] 可视化边界(障碍物表面)");
+ var boundaryPoints = VisualizeBoundary(voxelGrid);
+ LogManager.Info($"边界点数: {boundaryPoints.Count}");
+
+ // 测试4:采样可视化(每隔2个体素显示1个)
+ LogManager.Info("[测试 4] 采样可视化(采样率=2)");
+ var sampledPoints = VisualizeAsPoints(voxelGrid, 2, false);
+ LogManager.Info($"采样点数: {sampledPoints.Count}");
+
+ LogManager.Info("=== 快速可视化测试完成 ===");
+ LogManager.Info("提示:使用 PathPointRenderPlugin 渲染这些点");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"快速可视化测试失败: {ex.Message}");
+ LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
+ }
+ }
+
+ ///
+ /// 生成可视化报告(文本格式)
+ ///
+ /// 体素网格
+ /// 报告文本
+ public static string GenerateVisualizationReport(VoxelGrid voxelGrid)
+ {
+ var report = new System.Text.StringBuilder();
+
+ report.AppendLine("================================");
+ report.AppendLine(" 体素网格可视化报告");
+ report.AppendLine("================================");
+ report.AppendLine();
+
+ // 基本信息
+ report.AppendLine("【基本信息】");
+ report.AppendLine($"网格尺寸: {voxelGrid.SizeX} × {voxelGrid.SizeY} × {voxelGrid.SizeZ}");
+ report.AppendLine($"体素尺寸: {voxelGrid.VoxelSize:F4} 模型单位");
+ report.AppendLine($"总体素数: {voxelGrid.TotalVoxels:N0}");
+ report.AppendLine($"网格边界: {voxelGrid.Bounds.Min} ~ {voxelGrid.Bounds.Max}");
+ report.AppendLine();
+
+ // 统计信息
+ var (total, passable, obstacle) = voxelGrid.GetStatistics();
+ double passableRatio = (double)passable / total * 100.0;
+ double obstacleRatio = (double)obstacle / total * 100.0;
+
+ report.AppendLine("【统计信息】");
+ report.AppendLine($"总体素数: {total:N0}");
+ report.AppendLine($"可通行体素: {passable:N0} ({passableRatio:F1}%)");
+ report.AppendLine($"障碍物体素: {obstacle:N0} ({obstacleRatio:F1}%)");
+ report.AppendLine();
+
+ // 类型分布
+ report.AppendLine("【体素类型分布】");
+ var typeDistribution = AnalyzeTypeDistribution(voxelGrid);
+ foreach (var kvp in typeDistribution)
+ {
+ double ratio = (double)kvp.Value / total * 100.0;
+ report.AppendLine($" {kvp.Key}: {kvp.Value:N0} ({ratio:F1}%)");
+ }
+ report.AppendLine();
+
+ // 可视化建议
+ report.AppendLine("【可视化建议】");
+ if (total > 1000000)
+ {
+ report.AppendLine(" ⚠️ 体素数量很大,建议使用采样率 >= 5");
+ report.AppendLine(" 或者只可视化边界/切片");
+ }
+ else if (total > 100000)
+ {
+ report.AppendLine(" ⚠️ 体素数量较大,建议使用采样率 >= 3");
+ }
+ else
+ {
+ report.AppendLine(" ✅ 体素数量适中,可以使用采样率 1-2");
+ }
+ report.AppendLine();
+
+ report.AppendLine("================================");
+
+ return report.ToString();
+ }
+
+ ///
+ /// 分析体素类型分布
+ ///
+ private static Dictionary AnalyzeTypeDistribution(VoxelGrid voxelGrid)
+ {
+ var distribution = new Dictionary();
+
+ for (int x = 0; x < voxelGrid.SizeX; x++)
+ {
+ for (int y = 0; y < voxelGrid.SizeY; y++)
+ {
+ for (int z = 0; z < voxelGrid.SizeZ; z++)
+ {
+ var cell = voxelGrid.GetCell(x, y, z);
+ if (cell == null) continue;
+
+ if (!distribution.ContainsKey(cell.Type))
+ {
+ distribution[cell.Type] = 0;
+ }
+
+ distribution[cell.Type]++;
+ }
+ }
+ }
+
+ return distribution;
+ }
+ }
+}