553 lines
20 KiB
C#
553 lines
20 KiB
C#
using Autodesk.Navisworks.Api;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
|
||
namespace NavisworksTransport
|
||
{
|
||
/// <summary>
|
||
/// 楼层检测器 - 负责从模型中识别和提取楼层信息
|
||
/// </summary>
|
||
public class FloorDetector
|
||
{
|
||
#region 常量定义
|
||
|
||
private const double DEFAULT_FLOOR_HEIGHT_THRESHOLD = 2.5; // 默认最小楼层高度(米)
|
||
private const double DEFAULT_ELEVATION_TOLERANCE = 0.5; // 默认高程容差(米)
|
||
|
||
// 常见的楼层属性名称
|
||
private readonly string[] COMMON_FLOOR_ATTRIBUTES = {
|
||
"Level", "Floor", "Storey", "楼层", "层", "Level Name", "Story",
|
||
"Building Level", "Floor Level", "Elevation", "Z", "Height"
|
||
};
|
||
|
||
#endregion
|
||
|
||
#region 公共方法
|
||
|
||
/// <summary>
|
||
/// 检测模型中的楼层信息
|
||
/// </summary>
|
||
/// <param name="items">要检测的模型元素集合</param>
|
||
/// <param name="attributeName">指定的楼层属性名称,为空时自动检测</param>
|
||
/// <returns>检测到的楼层信息列表</returns>
|
||
public List<ModelSplitterManager.FloorInfo> DetectFloors(ModelItemCollection items, string attributeName = null)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[FloorDetector] 开始检测楼层,元素数量: {items.Count}");
|
||
|
||
if (items == null || items.Count == 0)
|
||
{
|
||
LogManager.Warning("[FloorDetector] 输入的模型元素集合为空");
|
||
return new List<ModelSplitterManager.FloorInfo>();
|
||
}
|
||
|
||
List<ModelSplitterManager.FloorInfo> floors;
|
||
|
||
if (!string.IsNullOrEmpty(attributeName))
|
||
{
|
||
// 使用指定属性检测楼层
|
||
floors = DetectFloorsByAttribute(items, attributeName);
|
||
LogManager.Info($"[FloorDetector] 使用属性 '{attributeName}' 检测到 {floors.Count} 个楼层");
|
||
}
|
||
else
|
||
{
|
||
// 自动检测最佳楼层属性
|
||
string bestAttribute = FindBestFloorAttribute(items);
|
||
if (!string.IsNullOrEmpty(bestAttribute))
|
||
{
|
||
floors = DetectFloorsByAttribute(items, bestAttribute);
|
||
LogManager.Info($"[FloorDetector] 自动选择属性 '{bestAttribute}' 检测到 {floors.Count} 个楼层");
|
||
}
|
||
else
|
||
{
|
||
// 使用高程检测
|
||
floors = DetectFloorsByElevation(items);
|
||
LogManager.Info($"[FloorDetector] 使用高程检测到 {floors.Count} 个楼层");
|
||
}
|
||
}
|
||
|
||
// 验证和优化检测结果
|
||
floors = ValidateAndOptimizeFloors(floors);
|
||
|
||
LogManager.Info($"[FloorDetector] 楼层检测完成,最终识别 {floors.Count} 个楼层");
|
||
return floors;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[FloorDetector] 楼层检测失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取可用的楼层属性列表
|
||
/// </summary>
|
||
public List<string> GetAvailableFloorAttributes(ModelItemCollection items)
|
||
{
|
||
try
|
||
{
|
||
var availableAttributes = new HashSet<string>();
|
||
int sampleSize = Math.Min(100, items.Count); // 采样前100个元素
|
||
|
||
foreach (ModelItem item in items.Take(sampleSize))
|
||
{
|
||
foreach (PropertyCategory category in item.PropertyCategories)
|
||
{
|
||
foreach (DataProperty property in category.Properties)
|
||
{
|
||
string propName = property.DisplayName;
|
||
|
||
// 检查是否为楼层相关属性
|
||
if (IsFloorRelatedAttribute(propName))
|
||
{
|
||
availableAttributes.Add(propName);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
var result = availableAttributes.OrderBy(attr => attr).ToList();
|
||
LogManager.Info($"[FloorDetector] 找到 {result.Count} 个可用的楼层属性");
|
||
|
||
return result;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[FloorDetector] 获取楼层属性失败: {ex.Message}");
|
||
return new List<string>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据高程获取楼层信息
|
||
/// </summary>
|
||
public ModelSplitterManager.FloorInfo GetFloorByElevation(double elevation, List<ModelSplitterManager.FloorInfo> floors)
|
||
{
|
||
return floors?.FirstOrDefault(f =>
|
||
Math.Abs(f.Elevation - elevation) <= DEFAULT_ELEVATION_TOLERANCE);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法 - 基于属性的检测
|
||
|
||
private List<ModelSplitterManager.FloorInfo> DetectFloorsByAttribute(ModelItemCollection items, string attributeName)
|
||
{
|
||
var floorGroups = new Dictionary<string, List<ModelItem>>();
|
||
|
||
foreach (ModelItem item in items)
|
||
{
|
||
string floorValue = GetAttributeValue(item, attributeName);
|
||
if (!string.IsNullOrEmpty(floorValue))
|
||
{
|
||
if (!floorGroups.ContainsKey(floorValue))
|
||
{
|
||
floorGroups[floorValue] = new List<ModelItem>();
|
||
}
|
||
floorGroups[floorValue].Add(item);
|
||
}
|
||
}
|
||
|
||
var floors = new List<ModelSplitterManager.FloorInfo>();
|
||
foreach (var kvp in floorGroups)
|
||
{
|
||
var floorItems = new ModelItemCollection();
|
||
floorItems.AddRange(kvp.Value);
|
||
|
||
var floorInfo = new ModelSplitterManager.FloorInfo
|
||
{
|
||
FloorName = SanitizeFloorName(kvp.Key),
|
||
Items = floorItems,
|
||
Elevation = CalculateAverageElevation(floorItems),
|
||
Bounds = CalculateBounds(floorItems),
|
||
Properties = new Dictionary<string, object>
|
||
{
|
||
["OriginalAttributeValue"] = kvp.Key,
|
||
["AttributeName"] = attributeName,
|
||
["DetectionMethod"] = "Attribute"
|
||
}
|
||
};
|
||
|
||
floors.Add(floorInfo);
|
||
}
|
||
|
||
return floors.OrderBy(f => f.Elevation).ToList();
|
||
}
|
||
|
||
private string GetAttributeValue(ModelItem item, string attributeName)
|
||
{
|
||
try
|
||
{
|
||
foreach (PropertyCategory category in item.PropertyCategories)
|
||
{
|
||
foreach (DataProperty property in category.Properties)
|
||
{
|
||
if (string.Equals(property.DisplayName, attributeName, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return property.Value?.ToString()?.Trim();
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
catch
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法 - 基于高程的检测
|
||
|
||
private List<ModelSplitterManager.FloorInfo> DetectFloorsByElevation(ModelItemCollection items)
|
||
{
|
||
var elevationGroups = new Dictionary<double, List<ModelItem>>();
|
||
|
||
foreach (ModelItem item in items)
|
||
{
|
||
try
|
||
{
|
||
var bounds = item.BoundingBox();
|
||
if (bounds.HasVolume)
|
||
{
|
||
double elevation = bounds.Min.Z; // 使用Z坐标最小值作为高程
|
||
|
||
// 查找相近的楼层组
|
||
double floorElevation = FindNearestFloorElevation(elevationGroups.Keys, elevation);
|
||
|
||
if (floorElevation == double.MinValue)
|
||
{
|
||
// 创建新楼层组
|
||
elevationGroups[elevation] = new List<ModelItem> { item };
|
||
}
|
||
else
|
||
{
|
||
// 添加到现有楼层组
|
||
elevationGroups[floorElevation].Add(item);
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略无法获取边界框的元素
|
||
}
|
||
}
|
||
|
||
// 转换为FloorInfo列表
|
||
var floors = new List<ModelSplitterManager.FloorInfo>();
|
||
int floorIndex = 1;
|
||
|
||
foreach (var kvp in elevationGroups.OrderBy(x => x.Key))
|
||
{
|
||
var floorItems = new ModelItemCollection();
|
||
floorItems.AddRange(kvp.Value);
|
||
|
||
var floorInfo = new ModelSplitterManager.FloorInfo
|
||
{
|
||
FloorName = GenerateFloorName(floorIndex, kvp.Key),
|
||
Elevation = kvp.Key,
|
||
Items = floorItems,
|
||
Bounds = CalculateBounds(floorItems),
|
||
Properties = new Dictionary<string, object>
|
||
{
|
||
["DetectionMethod"] = "Elevation",
|
||
["ElevationTolerance"] = DEFAULT_ELEVATION_TOLERANCE,
|
||
["FloorIndex"] = floorIndex
|
||
}
|
||
};
|
||
|
||
floors.Add(floorInfo);
|
||
floorIndex++;
|
||
}
|
||
|
||
return floors;
|
||
}
|
||
|
||
private double FindNearestFloorElevation(IEnumerable<double> existingElevations, double targetElevation)
|
||
{
|
||
foreach (double elevation in existingElevations)
|
||
{
|
||
if (Math.Abs(elevation - targetElevation) <= DEFAULT_ELEVATION_TOLERANCE)
|
||
{
|
||
return elevation;
|
||
}
|
||
}
|
||
return double.MinValue;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法 - 属性检测和验证
|
||
|
||
private string FindBestFloorAttribute(ModelItemCollection items)
|
||
{
|
||
var attributeScores = new Dictionary<string, int>();
|
||
int sampleSize = Math.Min(200, items.Count); // 增加采样数量以提高准确性
|
||
|
||
foreach (ModelItem item in items.Take(sampleSize))
|
||
{
|
||
foreach (PropertyCategory category in item.PropertyCategories)
|
||
{
|
||
foreach (DataProperty property in category.Properties)
|
||
{
|
||
string propName = property.DisplayName;
|
||
|
||
if (IsFloorRelatedAttribute(propName))
|
||
{
|
||
string value = property.Value?.ToString()?.Trim();
|
||
if (!string.IsNullOrEmpty(value) && IsValidFloorValue(value))
|
||
{
|
||
// 根据属性名称的匹配度给分
|
||
int score = CalculateAttributeScore(propName);
|
||
if (attributeScores.ContainsKey(propName))
|
||
attributeScores[propName] = attributeScores[propName] + score;
|
||
else
|
||
attributeScores[propName] = score;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 返回得分最高的属性
|
||
var bestAttribute = attributeScores.OrderByDescending(kvp => kvp.Value).FirstOrDefault();
|
||
|
||
if (bestAttribute.Value > 0)
|
||
{
|
||
LogManager.Info($"[FloorDetector] 选择最佳楼层属性: {bestAttribute.Key} (得分: {bestAttribute.Value})");
|
||
return bestAttribute.Key;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private bool IsFloorRelatedAttribute(string attributeName)
|
||
{
|
||
if (string.IsNullOrEmpty(attributeName))
|
||
return false;
|
||
|
||
return COMMON_FLOOR_ATTRIBUTES.Any(attr =>
|
||
attributeName.IndexOf(attr, StringComparison.OrdinalIgnoreCase) >= 0);
|
||
}
|
||
|
||
private int CalculateAttributeScore(string attributeName)
|
||
{
|
||
// 根据属性名称的匹配度计算分数
|
||
string lowerName = attributeName.ToLower();
|
||
|
||
if (lowerName == "level" || lowerName == "floor" || lowerName == "楼层")
|
||
return 10;
|
||
if (lowerName.Contains("level") || lowerName.Contains("floor"))
|
||
return 8;
|
||
if (lowerName.Contains("storey") || lowerName.Contains("story"))
|
||
return 7;
|
||
if (lowerName.Contains("elevation"))
|
||
return 6;
|
||
if (lowerName.Contains("层"))
|
||
return 5;
|
||
|
||
return 3; // 默认分数
|
||
}
|
||
|
||
private bool IsValidFloorValue(string value)
|
||
{
|
||
if (string.IsNullOrEmpty(value))
|
||
return false;
|
||
|
||
// 检查是否为有效的楼层值
|
||
// 数字楼层(如 "1", "2", "B1")
|
||
if (System.Text.RegularExpressions.Regex.IsMatch(value, @"^[B]?\d+[F]?$",
|
||
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||
return true;
|
||
|
||
// 文字楼层(如 "Ground Floor", "First Floor")
|
||
if (value.ToLower().Contains("floor") || value.ToLower().Contains("level"))
|
||
return true;
|
||
|
||
// 中文楼层(如 "一层", "地下一层")
|
||
if (value.Contains("层"))
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法 - 结果验证和优化
|
||
|
||
private List<ModelSplitterManager.FloorInfo> ValidateAndOptimizeFloors(List<ModelSplitterManager.FloorInfo> floors)
|
||
{
|
||
if (floors == null || floors.Count == 0)
|
||
return floors;
|
||
|
||
var validatedFloors = new List<ModelSplitterManager.FloorInfo>();
|
||
|
||
foreach (var floor in floors)
|
||
{
|
||
// 验证楼层是否有效
|
||
if (IsValidFloor(floor))
|
||
{
|
||
// 优化楼层名称
|
||
floor.FloorName = OptimizeFloorName(floor.FloorName);
|
||
validatedFloors.Add(floor);
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning($"[FloorDetector] 跳过无效楼层: {floor.FloorName}");
|
||
}
|
||
}
|
||
|
||
// 按高程排序
|
||
validatedFloors = validatedFloors.OrderBy(f => f.Elevation).ToList();
|
||
|
||
// 重新编号(如果需要)
|
||
RenumberFloorsIfNeeded(validatedFloors);
|
||
|
||
return validatedFloors;
|
||
}
|
||
|
||
private bool IsValidFloor(ModelSplitterManager.FloorInfo floor)
|
||
{
|
||
// 检查楼层是否包含模型元素
|
||
if (floor.Items == null || floor.Items.Count == 0)
|
||
return false;
|
||
|
||
// 检查楼层名称是否有效
|
||
if (string.IsNullOrEmpty(floor.FloorName))
|
||
return false;
|
||
|
||
// 检查边界框是否有效 - 简化检查
|
||
// BoundingBox3D是结构体,总是有值
|
||
|
||
return true;
|
||
}
|
||
|
||
private string OptimizeFloorName(string originalName)
|
||
{
|
||
if (string.IsNullOrEmpty(originalName))
|
||
return "Unknown_Floor";
|
||
|
||
// 移除特殊字符,保留字母数字和下划线
|
||
string optimized = System.Text.RegularExpressions.Regex.Replace(originalName, @"[^\w\u4e00-\u9fa5]", "_");
|
||
|
||
// 移除多余的下划线
|
||
optimized = System.Text.RegularExpressions.Regex.Replace(optimized, @"_+", "_");
|
||
|
||
// 移除首尾下划线
|
||
optimized = optimized.Trim('_');
|
||
|
||
return string.IsNullOrEmpty(optimized) ? "Unknown_Floor" : optimized;
|
||
}
|
||
|
||
private void RenumberFloorsIfNeeded(List<ModelSplitterManager.FloorInfo> floors)
|
||
{
|
||
// 如果楼层名称都是自动生成的数字格式,重新编号
|
||
bool needsRenumbering = floors.All(f =>
|
||
System.Text.RegularExpressions.Regex.IsMatch(f.FloorName, @"^Floor_\d+$"));
|
||
|
||
if (needsRenumbering)
|
||
{
|
||
for (int i = 0; i < floors.Count; i++)
|
||
{
|
||
floors[i].FloorName = $"Floor_{i + 1:D2}";
|
||
floors[i].Properties["RenumberedIndex"] = i + 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法 - 辅助计算
|
||
|
||
private double CalculateAverageElevation(ModelItemCollection items)
|
||
{
|
||
if (items == null || items.Count == 0)
|
||
return 0.0;
|
||
|
||
double totalElevation = 0.0;
|
||
int validCount = 0;
|
||
|
||
foreach (ModelItem item in items)
|
||
{
|
||
try
|
||
{
|
||
var bounds = item.BoundingBox();
|
||
if (bounds.HasVolume)
|
||
{
|
||
totalElevation += bounds.Min.Z;
|
||
validCount++;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略无法获取边界框的元素
|
||
}
|
||
}
|
||
|
||
return validCount > 0 ? totalElevation / validCount : 0.0;
|
||
}
|
||
|
||
private BoundingBox3D CalculateBounds(ModelItemCollection items)
|
||
{
|
||
if (items == null || items.Count == 0)
|
||
return new BoundingBox3D();
|
||
|
||
BoundingBox3D combinedBounds = null;
|
||
|
||
foreach (ModelItem item in items)
|
||
{
|
||
try
|
||
{
|
||
var itemBounds = item.BoundingBox();
|
||
if (itemBounds.HasVolume)
|
||
{
|
||
combinedBounds = combinedBounds.Extend(itemBounds);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略无法获取边界框的元素
|
||
}
|
||
}
|
||
|
||
return combinedBounds ?? new BoundingBox3D();
|
||
}
|
||
|
||
private string SanitizeFloorName(string floorName)
|
||
{
|
||
if (string.IsNullOrEmpty(floorName))
|
||
return "Unknown_Floor";
|
||
|
||
// 替换空格和特殊字符为下划线
|
||
string sanitized = System.Text.RegularExpressions.Regex.Replace(floorName.Trim(), @"\s+", "_");
|
||
|
||
// 移除或替换其他特殊字符
|
||
sanitized = System.Text.RegularExpressions.Regex.Replace(sanitized, @"[^\w\u4e00-\u9fa5]", "_");
|
||
|
||
// 移除多余的下划线
|
||
sanitized = System.Text.RegularExpressions.Regex.Replace(sanitized, @"_+", "_");
|
||
|
||
// 移除首尾下划线
|
||
sanitized = sanitized.Trim('_');
|
||
|
||
return string.IsNullOrEmpty(sanitized) ? "Unknown_Floor" : sanitized;
|
||
}
|
||
|
||
private string GenerateFloorName(int floorIndex, double elevation)
|
||
{
|
||
if (elevation < 0)
|
||
{
|
||
return $"Basement_{Math.Abs((int)Math.Round(elevation)):D2}";
|
||
}
|
||
else
|
||
{
|
||
return $"Floor_{floorIndex:D2}";
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |