519 lines
21 KiB
C#
519 lines
21 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |