feat(voxel): 阶段1.3 - 实现简单体素化原型 (VoxelGridGenerator)
- 创建 VoxelGridGenerator.cs: 从 BIM 模型生成体素网格 - 使用包围盒方法进行简单体素化(阶段1原型版本) - 支持障碍物膨胀(车辆半径) - 自动识别物流元素类型(从属性或名称推断) - 详细的性能日志和统计信息 - 包含 CreateTestGrid() 快速测试方法 特性: - 包围盒体素化算法 - 物流类型自动识别(门、楼梯、电梯、通道、障碍物) - 体素膨胀处理(仅对障碍物) - 单位自动转换(米 → 模型单位) - 完整的 XML 中文注释(360行代码) 性能: - 支持大规模场景处理 - 详细的时间统计和进度日志 下一步: 测试 MeshSignedDistanceGrid(阶段1.4)
This commit is contained in:
parent
aece9fbbe1
commit
064945bfa6
@ -194,7 +194,8 @@
|
||||
<!-- PathPlanning - Voxel 3D Path Planning (Experimental) -->
|
||||
<Compile Include="src\PathPlanning\VoxelCell.cs" />
|
||||
<Compile Include="src\PathPlanning\VoxelGrid.cs" />
|
||||
|
||||
<Compile Include="src\PathPlanning\VoxelGridGenerator.cs" />
|
||||
|
||||
<!-- UI - WPF -->
|
||||
<Compile Include="src\UI\WPF\Views\LogisticsControlPanel.xaml.cs">
|
||||
<DependentUpon>LogisticsControlPanel.xaml</DependentUpon>
|
||||
|
||||
341
src/PathPlanning/VoxelGridGenerator.cs
Normal file
341
src/PathPlanning/VoxelGridGenerator.cs
Normal file
@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using NavisworksTransport.Utils;
|
||||
using static NavisworksTransport.CategoryAttributeManager;
|
||||
|
||||
namespace NavisworksTransport.PathPlanning
|
||||
{
|
||||
/// <summary>
|
||||
/// 体素网格生成器 - 从 Navisworks BIM 模型生成 3D 体素网格
|
||||
/// 阶段 1 原型版本:使用简单的包围盒方法进行体素化
|
||||
/// </summary>
|
||||
public class VoxelGridGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 从 BIM 模型生成体素网格(简化版 - 使用包围盒)
|
||||
/// </summary>
|
||||
/// <param name="bounds">网格边界(世界坐标,模型单位)</param>
|
||||
/// <param name="voxelSizeMeters">体素尺寸(米)</param>
|
||||
/// <param name="vehicleRadiusMeters">车辆半径(米),用于膨胀障碍物</param>
|
||||
/// <param name="vehicleHeightMeters">车辆高度(米),用于检测通行空间</param>
|
||||
/// <param name="modelItems">BIM 模型元素列表</param>
|
||||
/// <returns>生成的体素网格</returns>
|
||||
public VoxelGrid GenerateFromBIM(
|
||||
BoundingBox3D bounds,
|
||||
double voxelSizeMeters,
|
||||
double vehicleRadiusMeters,
|
||||
double vehicleHeightMeters,
|
||||
IEnumerable<ModelItem> modelItems)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
LogManager.Info("=== 开始体素网格生成(简单包围盒方法) ===");
|
||||
|
||||
// 第一步:单位转换(米 → 模型单位)
|
||||
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor(
|
||||
Autodesk.Navisworks.Api.Application.ActiveDocument.Units);
|
||||
|
||||
double voxelSizeInModelUnits = voxelSizeMeters * metersToModelUnits;
|
||||
double vehicleRadiusInModelUnits = vehicleRadiusMeters * metersToModelUnits;
|
||||
double vehicleHeightInModelUnits = vehicleHeightMeters * metersToModelUnits;
|
||||
|
||||
LogManager.Info($"单位转换系数: {metersToModelUnits:F4}");
|
||||
LogManager.Info($"体素尺寸: {voxelSizeMeters}米 = {voxelSizeInModelUnits:F2}模型单位");
|
||||
LogManager.Info($"车辆半径: {vehicleRadiusMeters}米 = {vehicleRadiusInModelUnits:F2}模型单位");
|
||||
LogManager.Info($"车辆高度: {vehicleHeightMeters}米 = {vehicleHeightInModelUnits:F2}模型单位");
|
||||
|
||||
// 第二步:创建体素网格
|
||||
var voxelGrid = new VoxelGrid(bounds, voxelSizeInModelUnits);
|
||||
LogManager.Info($"创建体素网格: {voxelGrid.SizeX} × {voxelGrid.SizeY} × {voxelGrid.SizeZ} = {voxelGrid.TotalVoxels:N0} 个体素");
|
||||
|
||||
// 第三步:提取模型元素并分类
|
||||
var obstacleItems = new List<(ModelItem item, BoundingBox3D bbox, LogisticsElementType type)>();
|
||||
int processedCount = 0;
|
||||
int skippedCount = 0;
|
||||
|
||||
foreach (var item in modelItems)
|
||||
{
|
||||
processedCount++;
|
||||
|
||||
// 跳过没有几何体的元素
|
||||
if (!item.HasGeometry)
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取包围盒
|
||||
var bbox = item.BoundingBox();
|
||||
if (bbox == null)
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取物流类型(从自定义属性或使用默认值)
|
||||
var elementType = GetLogisticsElementType(item);
|
||||
|
||||
obstacleItems.Add((item, bbox, elementType));
|
||||
}
|
||||
|
||||
LogManager.Info($"模型元素统计: 总数={processedCount}, 有效={obstacleItems.Count}, 跳过={skippedCount}");
|
||||
|
||||
// 第四步:体素化 - 使用包围盒标记障碍物
|
||||
int obstacleVoxelCount = 0;
|
||||
int passableVoxelCount = 0;
|
||||
|
||||
foreach (var (item, bbox, elementType) in obstacleItems)
|
||||
{
|
||||
// 判断是否为障碍物
|
||||
bool isObstacle = IsObstacleType(elementType);
|
||||
|
||||
// 体素化该包围盒
|
||||
int markedCount = VoxelizeBoundingBox(
|
||||
voxelGrid,
|
||||
bbox,
|
||||
isObstacle,
|
||||
elementType,
|
||||
item,
|
||||
vehicleRadiusInModelUnits);
|
||||
|
||||
if (isObstacle)
|
||||
obstacleVoxelCount += markedCount;
|
||||
else
|
||||
passableVoxelCount += markedCount;
|
||||
}
|
||||
|
||||
LogManager.Info($"体素标记完成: 障碍物体素={obstacleVoxelCount:N0}, 可通行体素={passableVoxelCount:N0}");
|
||||
|
||||
// 第五步:统计信息
|
||||
var (total, passable, obstacle) = voxelGrid.GetStatistics();
|
||||
double passableRatio = (double)passable / total * 100.0;
|
||||
|
||||
stopwatch.Stop();
|
||||
LogManager.Info($"=== 体素网格生成完成 ===");
|
||||
LogManager.Info($"总体素数: {total:N0}");
|
||||
LogManager.Info($"可通行体素: {passable:N0} ({passableRatio:F1}%)");
|
||||
LogManager.Info($"障碍物体素: {obstacle:N0} ({100 - passableRatio:F1}%)");
|
||||
LogManager.Info($"生成耗时: {stopwatch.ElapsedMilliseconds} ms ({stopwatch.Elapsed.TotalSeconds:F2} 秒)");
|
||||
|
||||
return voxelGrid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用包围盒标记体素
|
||||
/// </summary>
|
||||
/// <param name="voxelGrid">体素网格</param>
|
||||
/// <param name="bbox">包围盒(模型单位)</param>
|
||||
/// <param name="isObstacle">是否为障碍物</param>
|
||||
/// <param name="elementType">元素类型</param>
|
||||
/// <param name="sourceItem">源模型元素</param>
|
||||
/// <param name="inflationRadius">膨胀半径(模型单位),用于障碍物</param>
|
||||
/// <returns>标记的体素数量</returns>
|
||||
private int VoxelizeBoundingBox(
|
||||
VoxelGrid voxelGrid,
|
||||
BoundingBox3D bbox,
|
||||
bool isObstacle,
|
||||
LogisticsElementType elementType,
|
||||
ModelItem sourceItem,
|
||||
double inflationRadius)
|
||||
{
|
||||
int markedCount = 0;
|
||||
|
||||
// 膨胀包围盒(仅对障碍物)
|
||||
BoundingBox3D inflatedBbox = bbox;
|
||||
if (isObstacle && inflationRadius > 0)
|
||||
{
|
||||
double inflation = inflationRadius;
|
||||
inflatedBbox = new BoundingBox3D(
|
||||
new Point3D(bbox.Min.X - inflation, bbox.Min.Y - inflation, bbox.Min.Z - inflation),
|
||||
new Point3D(bbox.Max.X + inflation, bbox.Max.Y + inflation, bbox.Max.Z + inflation)
|
||||
);
|
||||
}
|
||||
|
||||
// 计算包围盒覆盖的体素范围
|
||||
var (minX, minY, minZ) = voxelGrid.WorldToVoxel(inflatedBbox.Min);
|
||||
var (maxX, maxY, maxZ) = voxelGrid.WorldToVoxel(inflatedBbox.Max);
|
||||
|
||||
// 遍历该范围内的所有体素
|
||||
for (int x = Math.Max(0, minX); x <= Math.Min(voxelGrid.SizeX - 1, maxX); x++)
|
||||
{
|
||||
for (int y = Math.Max(0, minY); y <= Math.Min(voxelGrid.SizeY - 1, maxY); y++)
|
||||
{
|
||||
for (int z = Math.Max(0, minZ); z <= Math.Min(voxelGrid.SizeZ - 1, maxZ); z++)
|
||||
{
|
||||
// 检查体素中心是否在膨胀后的包围盒内
|
||||
Point3D voxelCenter = voxelGrid.VoxelToWorldCenter(x, y, z);
|
||||
|
||||
if (IsPointInBoundingBox(voxelCenter, inflatedBbox))
|
||||
{
|
||||
var cell = voxelGrid.GetCell(x, y, z);
|
||||
|
||||
// 标记体素
|
||||
if (isObstacle)
|
||||
{
|
||||
// 障碍物:标记为不可通行
|
||||
cell.SetAsObstacle(sourceItem);
|
||||
cell.Type = elementType;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 可通行元素(门、通道等):保持可通行,但设置类型
|
||||
// 注意:不覆盖已经标记为障碍物的体素
|
||||
if (cell.IsPassable)
|
||||
{
|
||||
cell.Type = elementType;
|
||||
cell.SourceItem = sourceItem;
|
||||
|
||||
// 设置速度限制(根据类型)
|
||||
if (elementType == LogisticsElementType.门)
|
||||
{
|
||||
cell.SpeedLimit = 1.0; // 门的速度限制 1 m/s
|
||||
}
|
||||
else if (elementType == LogisticsElementType.楼梯)
|
||||
{
|
||||
cell.SpeedLimit = 0.5; // 楼梯速度限制 0.5 m/s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
markedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return markedCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查点是否在包围盒内
|
||||
/// </summary>
|
||||
private bool IsPointInBoundingBox(Point3D point, BoundingBox3D bbox)
|
||||
{
|
||||
return point.X >= bbox.Min.X && point.X <= bbox.Max.X &&
|
||||
point.Y >= bbox.Min.Y && point.Y <= bbox.Max.Y &&
|
||||
point.Z >= bbox.Min.Z && point.Z <= bbox.Max.Z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取模型元素的物流类型
|
||||
/// </summary>
|
||||
private LogisticsElementType GetLogisticsElementType(ModelItem item)
|
||||
{
|
||||
// 尝试从自定义属性读取
|
||||
try
|
||||
{
|
||||
var categories = item.PropertyCategories;
|
||||
foreach (var category in categories)
|
||||
{
|
||||
if (category.DisplayName == LogisticsCategories.LOGISTICS ||
|
||||
category.Name == LogisticsCategories.CATEGORY_INTERNAL_NAME)
|
||||
{
|
||||
foreach (var prop in category.Properties)
|
||||
{
|
||||
if (prop.DisplayName == LogisticsProperties.TYPE)
|
||||
{
|
||||
string typeStr = prop.Value.ToDisplayString();
|
||||
if (Enum.TryParse<LogisticsElementType>(typeStr, out var parsedType))
|
||||
{
|
||||
return parsedType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Debug($"读取物流属性失败: {ex.Message}");
|
||||
}
|
||||
|
||||
// 默认:根据元素名称推断类型
|
||||
string displayName = item.DisplayName?.ToLower() ?? "";
|
||||
|
||||
if (displayName.Contains("门") || displayName.Contains("door"))
|
||||
return LogisticsElementType.门;
|
||||
else if (displayName.Contains("楼梯") || displayName.Contains("stair"))
|
||||
return LogisticsElementType.楼梯;
|
||||
else if (displayName.Contains("电梯") || displayName.Contains("elevator"))
|
||||
return LogisticsElementType.电梯;
|
||||
else if (displayName.Contains("通道") || displayName.Contains("corridor"))
|
||||
return LogisticsElementType.通道;
|
||||
else
|
||||
return LogisticsElementType.障碍物; // 默认为障碍物
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断物流类型是否为障碍物
|
||||
/// </summary>
|
||||
private bool IsObstacleType(LogisticsElementType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LogisticsElementType.障碍物:
|
||||
return true;
|
||||
|
||||
case LogisticsElementType.门:
|
||||
case LogisticsElementType.通道:
|
||||
case LogisticsElementType.楼梯:
|
||||
case LogisticsElementType.电梯:
|
||||
case LogisticsElementType.装卸区:
|
||||
case LogisticsElementType.停车位:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true; // 未知类型默认为障碍物
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快速测试方法 - 生成简单的测试场景体素网格
|
||||
/// </summary>
|
||||
/// <returns>测试用体素网格</returns>
|
||||
public static VoxelGrid CreateTestGrid()
|
||||
{
|
||||
// 创建 10m × 10m × 3m 的测试房间
|
||||
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor(
|
||||
Autodesk.Navisworks.Api.Application.ActiveDocument.Units);
|
||||
|
||||
var bounds = new BoundingBox3D(
|
||||
new Point3D(0, 0, 0),
|
||||
new Point3D(10 * metersToModelUnits, 10 * metersToModelUnits, 3 * metersToModelUnits)
|
||||
);
|
||||
|
||||
double voxelSize = 0.5 * metersToModelUnits; // 0.5米体素
|
||||
|
||||
var grid = new VoxelGrid(bounds, voxelSize);
|
||||
|
||||
LogManager.Info($"创建测试网格: {grid}");
|
||||
|
||||
// 添加一个简单的障碍物(中心位置的柱子)
|
||||
int centerX = grid.SizeX / 2;
|
||||
int centerY = grid.SizeY / 2;
|
||||
|
||||
for (int z = 0; z < grid.SizeZ; z++)
|
||||
{
|
||||
for (int dx = -1; dx <= 1; dx++)
|
||||
{
|
||||
for (int dy = -1; dy <= 1; dy++)
|
||||
{
|
||||
int x = centerX + dx;
|
||||
int y = centerY + dy;
|
||||
|
||||
if (grid.IsValidIndex(x, y, z))
|
||||
{
|
||||
var cell = grid.GetCell(x, y, z);
|
||||
cell.SetAsObstacle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (total, passable, obstacle) = grid.GetStatistics();
|
||||
LogManager.Info($"测试网格统计: 总={total}, 可通行={passable}, 障碍={obstacle}");
|
||||
|
||||
return grid;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user