diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 45e9265..30f4315 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -194,7 +194,8 @@ - + + LogisticsControlPanel.xaml diff --git a/src/PathPlanning/VoxelGridGenerator.cs b/src/PathPlanning/VoxelGridGenerator.cs new file mode 100644 index 0000000..48524b8 --- /dev/null +++ b/src/PathPlanning/VoxelGridGenerator.cs @@ -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 +{ + /// + /// 体素网格生成器 - 从 Navisworks BIM 模型生成 3D 体素网格 + /// 阶段 1 原型版本:使用简单的包围盒方法进行体素化 + /// + public class VoxelGridGenerator + { + /// + /// 从 BIM 模型生成体素网格(简化版 - 使用包围盒) + /// + /// 网格边界(世界坐标,模型单位) + /// 体素尺寸(米) + /// 车辆半径(米),用于膨胀障碍物 + /// 车辆高度(米),用于检测通行空间 + /// BIM 模型元素列表 + /// 生成的体素网格 + public VoxelGrid GenerateFromBIM( + BoundingBox3D bounds, + double voxelSizeMeters, + double vehicleRadiusMeters, + double vehicleHeightMeters, + IEnumerable 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; + } + + /// + /// 使用包围盒标记体素 + /// + /// 体素网格 + /// 包围盒(模型单位) + /// 是否为障碍物 + /// 元素类型 + /// 源模型元素 + /// 膨胀半径(模型单位),用于障碍物 + /// 标记的体素数量 + 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; + } + + /// + /// 检查点是否在包围盒内 + /// + 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; + } + + /// + /// 获取模型元素的物流类型 + /// + 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(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.障碍物; // 默认为障碍物 + } + + /// + /// 判断物流类型是否为障碍物 + /// + 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; // 未知类型默认为障碍物 + } + } + + /// + /// 快速测试方法 - 生成简单的测试场景体素网格 + /// + /// 测试用体素网格 + 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; + } + } +}