NavisworksTransport/src/PathPlanning/GridMapGenerator.cs

519 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
using NavisworksTransport.PathPlanning;
namespace NavisworksTransport.PathPlanning
{
/// <summary>
/// 网格地图生成器
/// 负责将BIM模型转换为路径规划可用的网格地图
/// </summary>
public class GridMapGenerator
{
private readonly CategoryAttributeManager _categoryManager;
/// <summary>
/// 构造函数
/// </summary>
public GridMapGenerator()
{
_categoryManager = new CategoryAttributeManager();
}
/// <summary>
/// 从BIM模型生成网格地图
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <param name="bounds">规划区域边界</param>
/// <param name="cellSize">网格单元格大小(米)</param>
/// <param name="vehicleRadius">车辆半径(用于障碍物膨胀)</param>
/// <returns>生成的网格地图</returns>
public GridMap GenerateFromBIM(Document document, BoundingBox3D bounds, double cellSize = 0.5, double vehicleRadius = 1.0)
{
try
{
// 获取单位转换系数,将米转换为模型单位
double metersToModelUnits = GetMetersToModelUnitsConversionFactor();
double cellSizeInModelUnits = cellSize * metersToModelUnits;
double vehicleRadiusInModelUnits = vehicleRadius * metersToModelUnits;
LogManager.Info($"开始生成网格地图: 边界={bounds}");
LogManager.Info($"单元格大小: {cellSize}m = {cellSizeInModelUnits:F2}模型单位");
LogManager.Info($"车辆半径: {vehicleRadius}m = {vehicleRadiusInModelUnits:F2}模型单位");
LogManager.Info($"模型单位: {Application.ActiveDocument.Units}, 转换系数: {metersToModelUnits}");
// 创建基础网格地图(使用模型单位)
var gridMap = new GridMap(bounds, cellSizeInModelUnits);
LogManager.Info($"创建网格地图: {gridMap.Width}x{gridMap.Height} = {gridMap.Width * gridMap.Height}个单元格");
// 获取规划区域内的所有模型项
var modelItems = GetModelItemsInBounds(document, bounds);
LogManager.Info($"在规划区域内找到 {modelItems.Count} 个模型项");
// 识别障碍物
var obstacles = GetObstacleItems(modelItems);
LogManager.Info($"识别出 {obstacles.Count} 个障碍物");
// 标记障碍物单元格
MarkObstacleCells(gridMap, obstacles);
// 应用车辆尺寸膨胀(使用模型单位)
if (vehicleRadiusInModelUnits > 0)
{
ApplyVehicleInflation(gridMap, vehicleRadiusInModelUnits);
LogManager.Info($"已应用车辆膨胀,半径={vehicleRadius}m = {vehicleRadiusInModelUnits:F2}模型单位");
}
// 识别和标记特殊区域(门、通道等)
MarkSpecialAreas(gridMap, modelItems);
LogManager.Info($"网格地图生成完成: {gridMap.GetStatistics()}");
return gridMap;
}
catch (Exception ex)
{
LogManager.Error($"生成网格地图时发生错误: {ex.Message}");
throw new AutoPathPlanningException($"网格地图生成失败: {ex.Message}", ex);
}
}
/// <summary>
/// 获取指定边界内的所有模型项
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <param name="bounds">边界框</param>
/// <returns>模型项列表</returns>
private List<ModelItem> GetModelItemsInBounds(Document document, BoundingBox3D bounds)
{
var modelItems = new List<ModelItem>();
try
{
// 遍历所有模型的根项
foreach (var model in document.Models)
{
if (model.RootItem != null)
{
// 递归查找边界内的模型项
CollectItemsInBounds(model.RootItem, bounds, modelItems);
}
}
}
catch (Exception ex)
{
LogManager.Warning($"获取模型项时发生错误: {ex.Message}");
}
return modelItems;
}
/// <summary>
/// 递归收集边界内的模型项
/// </summary>
/// <param name="item">当前模型项</param>
/// <param name="bounds">边界框</param>
/// <param name="result">结果列表</param>
private void CollectItemsInBounds(ModelItem item, BoundingBox3D bounds, List<ModelItem> result)
{
try
{
// 检查当前项的包围盒是否与目标边界相交
var itemBounds = item.BoundingBox();
if (itemBounds != null && BoundingBoxIntersects(itemBounds, bounds))
{
// 如果有几何信息,添加到结果中
if (item.HasGeometry)
{
result.Add(item);
}
// 递归处理子项
foreach (var child in item.Children)
{
CollectItemsInBounds(child, bounds, result);
}
}
}
catch (Exception ex)
{
LogManager.Warning($"处理模型项 {item.DisplayName} 时发生错误: {ex.Message}");
}
}
/// <summary>
/// 检查两个包围盒是否相交
/// </summary>
/// <param name="box1">包围盒1</param>
/// <param name="box2">包围盒2</param>
/// <returns>是否相交</returns>
private bool BoundingBoxIntersects(BoundingBox3D box1, BoundingBox3D box2)
{
return !(box1.Max.X < box2.Min.X || box1.Min.X > box2.Max.X ||
box1.Max.Y < box2.Min.Y || box1.Min.Y > box2.Max.Y ||
box1.Max.Z < box2.Min.Z || box1.Min.Z > box2.Max.Z);
}
/// <summary>
/// 从模型项列表中识别障碍物
/// </summary>
/// <param name="modelItems">模型项列表</param>
/// <returns>障碍物列表</returns>
public List<ModelItem> GetObstacleItems(List<ModelItem> modelItems)
{
var obstacles = new List<ModelItem>();
foreach (var item in modelItems)
{
try
{
if (IsObstacle(item))
{
obstacles.Add(item);
}
}
catch (Exception ex)
{
LogManager.Warning($"检查模型项 {item.DisplayName} 是否为障碍物时发生错误: {ex.Message}");
}
}
return obstacles;
}
/// <summary>
/// 获取米转换为模型单位的转换系数
/// </summary>
private double GetMetersToModelUnitsConversionFactor()
{
try
{
var units = Application.ActiveDocument.Units;
switch (units)
{
case Units.Millimeters:
return 1000.0; // 1米 = 1000毫米
case Units.Centimeters:
return 100.0; // 1米 = 100厘米
case Units.Meters:
return 1.0; // 1米 = 1米
case Units.Inches:
return 39.37; // 1米 = 39.37英寸
case Units.Feet:
return 3.281; // 1米 = 3.281英尺
case Units.Kilometers:
return 0.001; // 1米 = 0.001公里
case Units.Micrometers:
return 1000000.0; // 1米 = 1000000微米
case Units.Microinches:
return 39370078.74; // 1米 = 39370078.74微英寸
case Units.Mils:
return 39370.08; // 1米 = 39370.08密尔
case Units.Yards:
return 1.094; // 1米 = 1.094码
case Units.Miles:
return 0.000621; // 1米 = 0.000621英里
default:
LogManager.Warning($"未知单位类型: {units},使用默认米单位");
return 1.0;
}
}
catch (Exception ex)
{
LogManager.Error($"获取单位转换系数失败: {ex.Message}");
return 1.0;
}
}
/// <summary>
/// 判断模型项是否为障碍物
/// </summary>
/// <param name="item">模型项</param>
/// <returns>是否为障碍物</returns>
private bool IsObstacle(ModelItem item)
{
try
{
// 1. 检查是否已有物流属性标记为障碍物
var logisticsType = CategoryAttributeManager.GetLogisticsElementType(item);
if (logisticsType == CategoryAttributeManager.LogisticsElementType.)
{
return true;
}
// 2. 基于属性名称的启发式识别
var displayName = item.DisplayName?.ToLower() ?? "";
var className = item.ClassName?.ToLower() ?? "";
// 首先检查可通行区域关键词 - 如果是可通行区域直接返回false
string[] traversableKeywords = { "asphalt", "沥青", "road", "道路", "path", "路径", "floor", "地面", "ground", "通道", "channel" };
if (traversableKeywords.Any(keyword => displayName.Contains(keyword) || className.Contains(keyword)))
{
return false; // 明确标识为可通行区域
}
// 常见障碍物关键词
string[] obstacleKeywords = { "墙", "wall", "柱", "column", "梁", "beam", "板", "slab", "栏杆", "railing" };
if (obstacleKeywords.Any(keyword => displayName.Contains(keyword) || className.Contains(keyword)))
{
return true;
}
// 3. 基于几何特征的启发式识别 - 仅在没有明确名称信息时使用
// 如果没有物流属性且名称不明确,保守处理:默认为可通行
return false; // 改为默认可通行,避免过度识别障碍物
}
catch (Exception ex)
{
LogManager.Warning($"判断障碍物时发生错误: {ex.Message}");
}
return false; // 出错时默认为可通行
}
/// <summary>
/// 基于几何特征判断是否可能是障碍物
/// </summary>
/// <param name="boundingBox">包围盒</param>
/// <returns>是否可能是障碍物</returns>
private bool IsLikelyObstacleByGeometry(BoundingBox3D boundingBox)
{
try
{
double width = boundingBox.Max.X - boundingBox.Min.X;
double height = boundingBox.Max.Y - boundingBox.Min.Y;
double depth = boundingBox.Max.Z - boundingBox.Min.Z;
// 忽略过小的对象
if (width < 0.1 && height < 0.1)
return false;
// 判断是否为墙体(薄且高)
if ((width < 0.5 && height > 1.0 && depth > 2.0) ||
(height < 0.5 && width > 1.0 && depth > 2.0))
{
return true;
}
// 判断是否为柱子(相对较小但较高)
if (width < 1.0 && height < 1.0 && depth > 2.0)
{
return true;
}
// 判断是否为大型结构元素
if ((width > 10.0 || height > 10.0) && depth > 0.5)
{
return true;
}
}
catch (Exception ex)
{
LogManager.Warning($"基于几何特征判断障碍物时发生错误: {ex.Message}");
}
return false;
}
/// <summary>
/// 在网格地图中标记障碍物单元格
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="obstacles">障碍物列表</param>
private void MarkObstacleCells(GridMap gridMap, List<ModelItem> obstacles)
{
int markedCells = 0;
foreach (var obstacle in obstacles)
{
try
{
var boundingBox = obstacle.BoundingBox();
if (boundingBox == null) continue;
// 将障碍物包围盒投影到网格
var minGrid = gridMap.WorldToGrid(boundingBox.Min);
var maxGrid = gridMap.WorldToGrid(boundingBox.Max);
// 标记相交的网格单元格为障碍物
for (int x = Math.Max(0, minGrid.X); x <= Math.Min(gridMap.Width - 1, maxGrid.X); x++)
{
for (int y = Math.Max(0, minGrid.Y); y <= Math.Min(gridMap.Height - 1, maxGrid.Y); y++)
{
var gridPos = new Point2D(x, y);
gridMap.SetCell(gridPos, false, double.MaxValue, ElementType.Obstacle);
markedCells++;
}
}
}
catch (Exception ex)
{
LogManager.Warning($"标记障碍物 {obstacle.DisplayName} 时发生错误: {ex.Message}");
}
}
LogManager.Info($"已标记 {markedCells} 个网格单元格为障碍物");
}
/// <summary>
/// 应用车辆尺寸膨胀
/// 在障碍物周围扩展不可通行区域,考虑车辆尺寸
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="vehicleRadius">车辆半径</param>
public void ApplyVehicleInflation(GridMap gridMap, double vehicleRadius)
{
if (vehicleRadius <= 0) return;
try
{
// 计算膨胀半径(网格单元格数)
int inflationRadius = (int)Math.Ceiling(vehicleRadius / gridMap.CellSize);
// 创建原始障碍物位置的副本
var originalObstacles = new bool[gridMap.Width, gridMap.Height];
for (int x = 0; x < gridMap.Width; x++)
{
for (int y = 0; y < gridMap.Height; y++)
{
originalObstacles[x, y] = !gridMap.Cells[x, y].IsWalkable;
}
}
int inflatedCells = 0;
// 对每个原始障碍物应用膨胀
for (int x = 0; x < gridMap.Width; x++)
{
for (int y = 0; y < gridMap.Height; y++)
{
if (originalObstacles[x, y])
{
// 在障碍物周围膨胀
for (int dx = -inflationRadius; dx <= inflationRadius; dx++)
{
for (int dy = -inflationRadius; dy <= inflationRadius; dy++)
{
int newX = x + dx;
int newY = y + dy;
if (newX >= 0 && newX < gridMap.Width &&
newY >= 0 && newY < gridMap.Height)
{
// 计算距离
double distance = Math.Sqrt(dx * dx + dy * dy) * gridMap.CellSize;
if (distance <= vehicleRadius)
{
var gridPos = new Point2D(newX, newY);
if (gridMap.IsWalkable(gridPos))
{
gridMap.SetCell(gridPos, false, double.MaxValue, ElementType.Obstacle);
inflatedCells++;
}
}
}
}
}
}
}
}
LogManager.Info($"车辆膨胀完成,膨胀半径={inflationRadius}格,新增障碍物单元格={inflatedCells}个");
}
catch (Exception ex)
{
LogManager.Error($"应用车辆膨胀时发生错误: {ex.Message}");
throw new AutoPathPlanningException($"车辆膨胀失败: {ex.Message}", ex);
}
}
/// <summary>
/// 标记特殊区域(门、通道等)
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="modelItems">模型项列表</param>
private void MarkSpecialAreas(GridMap gridMap, List<ModelItem> modelItems)
{
int doorCells = 0;
int channelCells = 0;
foreach (var item in modelItems)
{
try
{
var logisticsType = CategoryAttributeManager.GetLogisticsElementType(item);
ElementType cellType;
double cost;
bool isWalkable = true;
switch (logisticsType)
{
case CategoryAttributeManager.LogisticsElementType.:
cellType = ElementType.Door;
cost = 5.0; // 门的通过成本较高
doorCells++;
break;
case CategoryAttributeManager.LogisticsElementType.:
cellType = ElementType.Channel;
cost = 0.5; // 通道成本较低,优先选择
channelCells++;
break;
default:
continue; // 跳过其他类型
}
// 应用到网格
var boundingBox = item.BoundingBox();
if (boundingBox != null)
{
MarkCellsInBoundingBox(gridMap, boundingBox, isWalkable, cost, cellType);
}
}
catch (Exception ex)
{
LogManager.Warning($"标记特殊区域 {item.DisplayName} 时发生错误: {ex.Message}");
}
}
if (doorCells > 0)
LogManager.Info($"已标记 {doorCells} 个门单元格");
if (channelCells > 0)
LogManager.Info($"已标记 {channelCells} 个通道单元格");
}
/// <summary>
/// 在指定包围盒内标记网格单元格
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="boundingBox">包围盒</param>
/// <param name="isWalkable">是否可通行</param>
/// <param name="cost">通行成本</param>
/// <param name="cellType">单元格类型</param>
private void MarkCellsInBoundingBox(GridMap gridMap, BoundingBox3D boundingBox,
bool isWalkable, double cost, ElementType cellType)
{
var minGrid = gridMap.WorldToGrid(boundingBox.Min);
var maxGrid = gridMap.WorldToGrid(boundingBox.Max);
for (int x = Math.Max(0, minGrid.X); x <= Math.Min(gridMap.Width - 1, maxGrid.X); x++)
{
for (int y = Math.Max(0, minGrid.Y); y <= Math.Min(gridMap.Height - 1, maxGrid.Y); y++)
{
var gridPos = new Point2D(x, y);
// 只有当前单元格可通行时才更新(不覆盖障碍物)
if (gridMap.IsWalkable(gridPos))
{
gridMap.SetCell(gridPos, isWalkable, cost, cellType);
}
}
}
}
}
}