diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj
index b7c524f..45e9265 100644
--- a/NavisworksTransportPlugin.csproj
+++ b/NavisworksTransportPlugin.csproj
@@ -190,6 +190,10 @@
+
+
+
+
diff --git a/src/PathPlanning/VoxelCell.cs b/src/PathPlanning/VoxelCell.cs
new file mode 100644
index 0000000..37e404e
--- /dev/null
+++ b/src/PathPlanning/VoxelCell.cs
@@ -0,0 +1,161 @@
+using System;
+using Autodesk.Navisworks.Api;
+using static NavisworksTransport.CategoryAttributeManager;
+
+namespace NavisworksTransport.PathPlanning
+{
+ ///
+ /// 体素单元类 - 表示3D网格中的单个体素
+ /// 用于体素网格路径规划,存储体素的属性和状态信息
+ ///
+ public class VoxelCell
+ {
+ ///
+ /// 体素类型(对应物流元素分类)
+ ///
+ public LogisticsElementType Type { get; set; }
+
+ ///
+ /// 是否可通行
+ /// true: 该体素为自由空间,可以通行
+ /// false: 该体素为障碍物或不可通行区域
+ ///
+ public bool IsPassable { get; set; }
+
+ ///
+ /// 到最近障碍物的距离(模型单位)
+ /// 使用Signed Distance Field (SDF)计算
+ /// 正值:自由空间,数值表示到最近障碍物的距离
+ /// 负值:障碍物内部,数值表示到障碍物表面的距离
+ /// 零值:障碍物表面
+ ///
+ public double Distance { get; set; }
+
+ ///
+ /// 速度限制(米/秒)
+ /// 根据体素所在区域的特性设置(如通道、电梯等)
+ /// 0.0 表示无速度限制(使用默认速度)
+ ///
+ public double SpeedLimit { get; set; }
+
+ ///
+ /// 源模型元素
+ /// 如果该体素对应某个BIM模型元素,存储其引用
+ /// 用于追溯体素属性的来源
+ ///
+ public ModelItem SourceItem { get; set; }
+
+ ///
+ /// 通行成本
+ /// 用于A*路径规划算法
+ /// 考虑因素:距离、速度限制、碰撞风险等
+ /// 值越大表示通行代价越高
+ ///
+ public double Cost { get; set; }
+
+ ///
+ /// 构造函数 - 创建默认的可通行体素
+ ///
+ public VoxelCell()
+ {
+ Type = LogisticsElementType.通道;
+ IsPassable = true;
+ Distance = double.MaxValue; // 初始化为无限远(远离障碍物)
+ SpeedLimit = 0.0; // 无速度限制
+ SourceItem = null;
+ Cost = 1.0; // 默认通行成本
+ }
+
+ ///
+ /// 构造函数 - 创建指定属性的体素
+ ///
+ /// 是否可通行
+ /// 体素类型
+ /// 到最近障碍物的距离
+ public VoxelCell(bool isPassable, LogisticsElementType type = LogisticsElementType.通道, double distance = double.MaxValue)
+ {
+ Type = type;
+ IsPassable = isPassable;
+ Distance = distance;
+ SpeedLimit = 0.0;
+ SourceItem = null;
+ Cost = isPassable ? 1.0 : double.MaxValue; // 不可通行区域成本为无穷大
+ }
+
+ ///
+ /// 克隆体素单元
+ ///
+ /// 体素单元的深拷贝
+ public VoxelCell Clone()
+ {
+ return new VoxelCell
+ {
+ Type = this.Type,
+ IsPassable = this.IsPassable,
+ Distance = this.Distance,
+ SpeedLimit = this.SpeedLimit,
+ SourceItem = this.SourceItem,
+ Cost = this.Cost
+ };
+ }
+
+ ///
+ /// 设置为障碍物体素
+ ///
+ /// 源模型元素
+ public void SetAsObstacle(ModelItem sourceItem = null)
+ {
+ IsPassable = false;
+ Type = LogisticsElementType.障碍物;
+ Distance = 0.0; // 障碍物表面距离为0
+ Cost = double.MaxValue;
+ SourceItem = sourceItem;
+ }
+
+ ///
+ /// 设置为自由空间体素
+ ///
+ /// 到最近障碍物的距离
+ public void SetAsFreeSpace(double distance = double.MaxValue)
+ {
+ IsPassable = true;
+ Type = LogisticsElementType.通道;
+ Distance = distance;
+ Cost = 1.0;
+ }
+
+ ///
+ /// 根据距离更新通行成本
+ /// 距离障碍物越近,成本越高(避免贴边行走)
+ ///
+ /// 安全边距(模型单位)
+ public void UpdateCostFromDistance(double safetyMargin)
+ {
+ if (!IsPassable)
+ {
+ Cost = double.MaxValue;
+ return;
+ }
+
+ if (Distance < safetyMargin)
+ {
+ // 在安全边距内,成本线性增加
+ double ratio = Distance / safetyMargin;
+ Cost = 1.0 + (1.0 - ratio) * 10.0; // 成本范围:1.0 ~ 11.0
+ }
+ else
+ {
+ Cost = 1.0; // 安全距离外,标准成本
+ }
+ }
+
+ ///
+ /// 返回体素单元的字符串表示
+ ///
+ public override string ToString()
+ {
+ string passableStr = IsPassable ? "可通行" : "障碍";
+ return $"VoxelCell[{passableStr}, Type={Type}, Dist={Distance:F2}, Cost={Cost:F2}]";
+ }
+ }
+}
diff --git a/src/PathPlanning/VoxelGrid.cs b/src/PathPlanning/VoxelGrid.cs
new file mode 100644
index 0000000..0a1fe0b
--- /dev/null
+++ b/src/PathPlanning/VoxelGrid.cs
@@ -0,0 +1,371 @@
+using System;
+using System.Collections.Generic;
+using Autodesk.Navisworks.Api;
+
+namespace NavisworksTransport.PathPlanning
+{
+ ///
+ /// 3D体素网格类 - 用于真3D路径规划
+ /// 提供完整的3D空间离散化表示,支持任意高度的物体和通道
+ ///
+ public class VoxelGrid
+ {
+ ///
+ /// 3D体素数组 [x, y, z]
+ /// 注意:索引顺序为 [宽度, 深度, 高度]
+ ///
+ private VoxelCell[,,] cells;
+
+ ///
+ /// 网格原点(世界坐标,模型单位)
+ /// 对应体素索引 (0, 0, 0) 的世界坐标位置
+ ///
+ public Point3D Origin { get; private set; }
+
+ ///
+ /// 单个体素的尺寸(模型单位)
+ /// 所有三个维度使用相同的体素尺寸(立方体体素)
+ ///
+ public double VoxelSize { get; private set; }
+
+ ///
+ /// X方向(宽度)的体素数量
+ ///
+ public int SizeX { get; private set; }
+
+ ///
+ /// Y方向(深度)的体素数量
+ ///
+ public int SizeY { get; private set; }
+
+ ///
+ /// Z方向(高度)的体素数量
+ ///
+ public int SizeZ { get; private set; }
+
+ ///
+ /// 网格边界框(世界坐标,模型单位)
+ ///
+ public BoundingBox3D Bounds { get; private set; }
+
+ ///
+ /// 总体素数量
+ ///
+ public int TotalVoxels => SizeX * SizeY * SizeZ;
+
+ ///
+ /// 构造函数 - 创建指定尺寸的体素网格
+ ///
+ /// 网格边界框(世界坐标,模型单位)
+ /// 体素尺寸(模型单位)
+ public VoxelGrid(BoundingBox3D bounds, double voxelSize)
+ {
+ if (voxelSize <= 0)
+ throw new ArgumentException("体素尺寸必须大于0", nameof(voxelSize));
+
+ VoxelSize = voxelSize;
+ Bounds = bounds;
+ Origin = bounds.Min;
+
+ // 计算每个维度需要的体素数量(向上取整以覆盖整个边界框)
+ double width = bounds.Max.X - bounds.Min.X;
+ double depth = bounds.Max.Y - bounds.Min.Y;
+ double height = bounds.Max.Z - bounds.Min.Z;
+
+ SizeX = (int)Math.Ceiling(width / voxelSize);
+ SizeY = (int)Math.Ceiling(depth / voxelSize);
+ SizeZ = (int)Math.Ceiling(height / voxelSize);
+
+ // 初始化体素数组(所有体素默认为可通行)
+ cells = new VoxelCell[SizeX, SizeY, SizeZ];
+ InitializeCells();
+ }
+
+ ///
+ /// 初始化所有体素单元
+ ///
+ private void InitializeCells()
+ {
+ for (int x = 0; x < SizeX; x++)
+ {
+ for (int y = 0; y < SizeY; y++)
+ {
+ for (int z = 0; z < SizeZ; z++)
+ {
+ cells[x, y, z] = new VoxelCell();
+ }
+ }
+ }
+ }
+
+ ///
+ /// 获取指定索引的体素单元
+ ///
+ /// X方向索引
+ /// Y方向索引
+ /// Z方向索引
+ /// 体素单元,如果索引越界返回null
+ public VoxelCell GetCell(int x, int y, int z)
+ {
+ if (!IsValidIndex(x, y, z))
+ return null;
+
+ return cells[x, y, z];
+ }
+
+ ///
+ /// 设置指定索引的体素单元
+ ///
+ /// X方向索引
+ /// Y方向索引
+ /// Z方向索引
+ /// 体素单元
+ /// 是否设置成功
+ public bool SetCell(int x, int y, int z, VoxelCell cell)
+ {
+ if (!IsValidIndex(x, y, z))
+ return false;
+
+ cells[x, y, z] = cell;
+ return true;
+ }
+
+ ///
+ /// 检查体素索引是否有效(在网格范围内)
+ ///
+ /// X方向索引
+ /// Y方向索引
+ /// Z方向索引
+ /// 索引是否有效
+ public bool IsValidIndex(int x, int y, int z)
+ {
+ return x >= 0 && x < SizeX &&
+ y >= 0 && y < SizeY &&
+ z >= 0 && z < SizeZ;
+ }
+
+ ///
+ /// 世界坐标转换为体素索引
+ /// 注意:体素索引代表体素的左下角,而不是中心点
+ ///
+ /// 世界坐标(模型单位)
+ /// 体素索引 (x, y, z)
+ public (int x, int y, int z) WorldToVoxel(Point3D worldPos)
+ {
+ int x = (int)Math.Floor((worldPos.X - Origin.X) / VoxelSize);
+ int y = (int)Math.Floor((worldPos.Y - Origin.Y) / VoxelSize);
+ int z = (int)Math.Floor((worldPos.Z - Origin.Z) / VoxelSize);
+
+ return (x, y, z);
+ }
+
+ ///
+ /// 体素索引转换为世界坐标(体素的左下角坐标)
+ ///
+ /// X方向索引
+ /// Y方向索引
+ /// Z方向索引
+ /// 世界坐标(模型单位)
+ public Point3D VoxelToWorld(int x, int y, int z)
+ {
+ double worldX = Origin.X + x * VoxelSize;
+ double worldY = Origin.Y + y * VoxelSize;
+ double worldZ = Origin.Z + z * VoxelSize;
+
+ return new Point3D(worldX, worldY, worldZ);
+ }
+
+ ///
+ /// 体素索引转换为世界坐标(体素的中心点坐标)
+ ///
+ /// X方向索引
+ /// Y方向索引
+ /// Z方向索引
+ /// 世界坐标(模型单位)
+ public Point3D VoxelToWorldCenter(int x, int y, int z)
+ {
+ double halfVoxel = VoxelSize / 2.0;
+ double worldX = Origin.X + x * VoxelSize + halfVoxel;
+ double worldY = Origin.Y + y * VoxelSize + halfVoxel;
+ double worldZ = Origin.Z + z * VoxelSize + halfVoxel;
+
+ return new Point3D(worldX, worldY, worldZ);
+ }
+
+ ///
+ /// 获取体素的6邻域(上下左右前后)
+ ///
+ /// X方向索引
+ /// Y方向索引
+ /// Z方向索引
+ /// 邻居体素索引列表
+ public List<(int x, int y, int z)> GetNeighbors6(int x, int y, int z)
+ {
+ var neighbors = new List<(int, int, int)>();
+
+ // 6个方向:右、左、后、前、上、下
+ int[,] directions = {
+ { 1, 0, 0 }, // +X 右
+ { -1, 0, 0 }, // -X 左
+ { 0, 1, 0 }, // +Y 后
+ { 0, -1, 0 }, // -Y 前
+ { 0, 0, 1 }, // +Z 上
+ { 0, 0, -1 } // -Z 下
+ };
+
+ for (int i = 0; i < 6; i++)
+ {
+ int nx = x + directions[i, 0];
+ int ny = y + directions[i, 1];
+ int nz = z + directions[i, 2];
+
+ if (IsValidIndex(nx, ny, nz))
+ {
+ neighbors.Add((nx, ny, nz));
+ }
+ }
+
+ return neighbors;
+ }
+
+ ///
+ /// 获取体素的26邻域(包括对角线方向)
+ ///
+ /// X方向索引
+ /// Y方向索引
+ /// Z方向索引
+ /// 邻居体素索引列表
+ public List<(int x, int y, int z)> GetNeighbors26(int x, int y, int z)
+ {
+ var neighbors = new List<(int, int, int)>();
+
+ // 26个方向:3x3x3立方体去掉中心点
+ for (int dx = -1; dx <= 1; dx++)
+ {
+ for (int dy = -1; dy <= 1; dy++)
+ {
+ for (int dz = -1; dz <= 1; dz++)
+ {
+ // 跳过中心点
+ if (dx == 0 && dy == 0 && dz == 0)
+ continue;
+
+ int nx = x + dx;
+ int ny = y + dy;
+ int nz = z + dz;
+
+ if (IsValidIndex(nx, ny, nz))
+ {
+ neighbors.Add((nx, ny, nz));
+ }
+ }
+ }
+ }
+
+ return neighbors;
+ }
+
+ ///
+ /// 获取两个体素之间的欧几里得距离(体素单位)
+ ///
+ /// 第一个体素的X索引
+ /// 第一个体素的Y索引
+ /// 第一个体素的Z索引
+ /// 第二个体素的X索引
+ /// 第二个体素的Y索引
+ /// 第二个体素的Z索引
+ /// 欧几里得距离(体素单位)
+ public double GetDistance(int x1, int y1, int z1, int x2, int y2, int z2)
+ {
+ int dx = x2 - x1;
+ int dy = y2 - y1;
+ int dz = z2 - z1;
+
+ return Math.Sqrt(dx * dx + dy * dy + dz * dz);
+ }
+
+ ///
+ /// 获取两个体素之间的曼哈顿距离(体素单位)
+ ///
+ /// 第一个体素的X索引
+ /// 第一个体素的Y索引
+ /// 第一个体素的Z索引
+ /// 第二个体素的X索引
+ /// 第二个体素的Y索引
+ /// 第二个体素的Z索引
+ /// 曼哈顿距离(体素单位)
+ public int GetManhattanDistance(int x1, int y1, int z1, int x2, int y2, int z2)
+ {
+ return Math.Abs(x2 - x1) + Math.Abs(y2 - y1) + Math.Abs(z2 - z1);
+ }
+
+ ///
+ /// 检查指定索引的体素是否可通行
+ ///
+ /// X方向索引
+ /// Y方向索引
+ /// Z方向索引
+ /// 是否可通行(索引越界返回false)
+ public bool IsPassable(int x, int y, int z)
+ {
+ var cell = GetCell(x, y, z);
+ return cell != null && cell.IsPassable;
+ }
+
+ ///
+ /// 检查世界坐标位置是否可通行
+ ///
+ /// 世界坐标(模型单位)
+ /// 是否可通行
+ public bool IsPassable(Point3D worldPos)
+ {
+ var (x, y, z) = WorldToVoxel(worldPos);
+ return IsPassable(x, y, z);
+ }
+
+ ///
+ /// 获取网格的统计信息
+ ///
+ /// (总体素数, 可通行体素数, 障碍物体素数)
+ public (int total, int passable, int obstacle) GetStatistics()
+ {
+ int total = TotalVoxels;
+ int passable = 0;
+ int obstacle = 0;
+
+ for (int x = 0; x < SizeX; x++)
+ {
+ for (int y = 0; y < SizeY; y++)
+ {
+ for (int z = 0; z < SizeZ; z++)
+ {
+ if (cells[x, y, z].IsPassable)
+ passable++;
+ else
+ obstacle++;
+ }
+ }
+ }
+
+ return (total, passable, obstacle);
+ }
+
+ ///
+ /// 清除所有体素(重置为可通行状态)
+ ///
+ public void Clear()
+ {
+ InitializeCells();
+ }
+
+ ///
+ /// 返回网格的字符串表示
+ ///
+ public override string ToString()
+ {
+ var (total, passable, obstacle) = GetStatistics();
+ double passableRatio = (double)passable / total * 100.0;
+ return $"VoxelGrid[{SizeX}x{SizeY}x{SizeZ}={total}体素, 可通行:{passableRatio:F1}%, 体素尺寸:{VoxelSize:F2}模型单位]";
+ }
+ }
+}