NavisworksTransport/FloorDetector.cs
2025-07-18 13:32:50 +08:00

553 lines
20 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 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
}
}