实现自定义类别功能

This commit is contained in:
tian 2026-02-19 14:46:34 +08:00
parent 76d277b6c2
commit f9df83ba5b
12 changed files with 1153 additions and 174 deletions

View File

@ -140,6 +140,7 @@
<!-- Core - Configuration Management -->
<Compile Include="src\Core\Config\SystemConfig.cs" />
<Compile Include="src\Core\Config\ConfigManager.cs" />
<Compile Include="src\Core\Config\CustomCategoryConfig.cs" />
<Compile Include="src\Core\Database\BackupManager.cs" />
<Compile Include="src\Core\Services\TimeTagCalculator.cs" />
<Compile Include="src\Core\Services\TimeTagService.cs" />
@ -476,6 +477,11 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>resources\default_config.toml</Link>
</None>
<!-- Custom Categories Configuration File -->
<None Include="resources\default_custom_categories.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>resources\default_custom_categories.toml</Link>
</None>
<!-- Plugin Name File (in resources folder) -->
<None Include="resources\TransportPlugin.name.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@ -0,0 +1,77 @@
# 自定义物流类别配置文件
# 位置: %ProgramData%/Autodesk/Navisworks Manage 2026/plugins/TransportPlugin/custom_categories.toml
# 说明: 本文件定义用户自定义的物流类别,与模型文件无关,可跨项目共享
# 类别属性说明:
# id: 唯一标识(英文,用于存储和内部识别)
# display_name: 显示名称(中文)
# icon: 图标标识(可选,默认 Box
# traversable: 是否默认可通行(可选,默认 true
# priority: 优先级 1-10可选默认 5
# weight: 寻路权重(可选,默认 1.0,越低越优先)
# color: UI显示颜色可选默认 #9E9E9E
# height_limit_meters: 默认高度限制米数(可选,默认 3.0
# speed_limit_meters_per_second: 默认速度限制米/秒(可选,默认 1.0
# width_limit_meters: 默认宽度限制米数(可选,默认 3.0
# 示例自定义类别(可根据需要添加更多)
[[category]]
id = "ramp"
display_name = "坡道"
icon = "Ramp"
traversable = true
priority = 4
weight = 1.5
color = "#FF9800"
height_limit_meters = 3.0
speed_limit_meters_per_second = 0.5
width_limit_meters = 4.0
[[category]]
id = "tunnel"
display_name = "隧道"
icon = "Tunnel"
traversable = true
priority = 3
weight = 1.8
color = "#795548"
height_limit_meters = 2.5
speed_limit_meters_per_second = 0.3
width_limit_meters = 2.5
[[category]]
id = "restricted_area"
display_name = "限制区域"
icon = "Warning"
traversable = false
priority = 1
weight = 999999.0
color = "#F44336"
height_limit_meters = 0.0
speed_limit_meters_per_second = 0.0
width_limit_meters = 0.0
[[category]]
id = "temporary_storage"
display_name = "临时堆放区"
icon = "Package"
traversable = true
priority = 6
weight = 1.2
color = "#FFC107"
height_limit_meters = 5.0
speed_limit_meters_per_second = 0.8
width_limit_meters = 5.0
[[category]]
id = "cleanroom"
display_name = "洁净室"
icon = "Shield"
traversable = true
priority = 2
weight = 2.0
color = "#E1F5FE"
height_limit_meters = 3.5
speed_limit_meters_per_second = 0.4
width_limit_meters = 3.0

View File

@ -0,0 +1,619 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using Tomlyn;
using Tomlyn.Model;
namespace NavisworksTransport.Core.Config
{
/// <summary>
/// 自定义物流类别配置
/// 从用户级 TOML 配置文件加载,独立于模型数据库
/// </summary>
public class CustomCategoryConfig
{
/// <summary>
/// 配置文件路径(用户级,与模型无关)
/// </summary>
public static string ConfigFilePath
{
get
{
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"Autodesk",
"Navisworks Manage 2026",
"plugins",
"TransportPlugin",
"custom_categories.toml"
);
}
}
/// <summary>
/// 配置目录
/// </summary>
public static string ConfigDirectory => Path.GetDirectoryName(ConfigFilePath);
/// <summary>
/// 自定义类别列表
/// </summary>
public List<CustomCategoryDefinition> Categories { get; set; } = new List<CustomCategoryDefinition>();
/// <summary>
/// 获取所有类别(包括内置和自定义)
/// </summary>
public IEnumerable<CategoryDefinition> GetAllCategories()
{
// 内置类别
foreach (var builtIn in BuiltInCategories.All)
yield return builtIn;
// 自定义类别
foreach (var custom in Categories)
yield return custom;
}
/// <summary>
/// 根据ID获取类别定义
/// </summary>
public CategoryDefinition GetCategory(string id)
{
if (string.IsNullOrEmpty(id))
return null;
// 先查内置
var builtIn = BuiltInCategories.GetById(id);
if (builtIn != null)
return builtIn;
// 再查自定义
return Categories.FirstOrDefault(c =>
c.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// 检查是否为内置类别
/// </summary>
public bool IsBuiltIn(string id)
{
return BuiltInCategories.GetById(id) != null;
}
/// <summary>
/// 检查类别是否存在
/// </summary>
public bool CategoryExists(string id)
{
return GetCategory(id) != null;
}
/// <summary>
/// 从 TOML 文件加载配置
/// </summary>
public static CustomCategoryConfig LoadFromFile()
{
var config = new CustomCategoryConfig();
try
{
if (!File.Exists(ConfigFilePath))
{
// 配置文件不存在,创建默认配置
config = CreateDefaultConfig();
config.SaveToFile();
return config;
}
var tomlContent = File.ReadAllText(ConfigFilePath);
var tomlTable = Toml.ToModel(tomlContent);
if (tomlTable.TryGetValue("category", out var categoriesObj) &&
categoriesObj is TomlTableArray categoriesArray)
{
foreach (var categoryTable in categoriesArray)
{
var category = ParseCategoryFromToml(categoryTable);
if (category != null)
config.Categories.Add(category);
}
}
LogManager.Info($"[CustomCategoryConfig] 已加载 {config.Categories.Count} 个自定义类别");
}
catch (Exception ex)
{
LogManager.Error($"[CustomCategoryConfig] 加载配置失败: {ex.Message}");
// 加载失败时返回空配置
config = new CustomCategoryConfig();
}
return config;
}
/// <summary>
/// 保存配置到 TOML 文件
/// </summary>
public void SaveToFile()
{
try
{
Directory.CreateDirectory(ConfigDirectory);
var tomlContent = GenerateTomlContent();
File.WriteAllText(ConfigFilePath, tomlContent);
LogManager.Info($"[CustomCategoryConfig] 配置已保存: {ConfigFilePath}");
}
catch (Exception ex)
{
LogManager.Error($"[CustomCategoryConfig] 保存配置失败: {ex.Message}");
}
}
/// <summary>
/// 默认配置文件路径(插件资源目录)
/// </summary>
public static string DefaultConfigFilePath
{
get
{
return Path.Combine(
Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
"resources",
"default_custom_categories.toml"
);
}
}
/// <summary>
/// 创建默认配置
/// 尝试从默认配置文件加载示例,如果失败则返回空配置
/// </summary>
private static CustomCategoryConfig CreateDefaultConfig()
{
var config = new CustomCategoryConfig
{
Categories = new List<CustomCategoryDefinition>()
};
try
{
// 尝试从默认配置文件加载示例
if (File.Exists(DefaultConfigFilePath))
{
var tomlContent = File.ReadAllText(DefaultConfigFilePath);
var tomlTable = Toml.ToModel(tomlContent);
if (tomlTable.TryGetValue("category", out var categoriesObj) &&
categoriesObj is TomlTableArray categoriesArray)
{
foreach (var categoryTable in categoriesArray)
{
var category = ParseCategoryFromToml(categoryTable);
if (category != null)
config.Categories.Add(category);
}
}
LogManager.Info($"[CustomCategoryConfig] 已从默认配置文件加载 {config.Categories.Count} 个示例类别");
}
}
catch (Exception ex)
{
LogManager.Warning($"[CustomCategoryConfig] 从默认配置文件加载失败: {ex.Message},将使用空配置");
}
return config;
}
/// <summary>
/// 从 TOML 表解析类别定义
/// </summary>
private static CustomCategoryDefinition ParseCategoryFromToml(TomlTable table)
{
try
{
var category = new CustomCategoryDefinition
{
Id = table.GetString("id"),
DisplayName = table.GetString("display_name"),
Icon = table.GetString("icon", "Box"),
Traversable = table.GetBool("traversable", true),
Priority = table.GetInt("priority", 5),
Weight = table.GetDouble("weight", 1.0),
DisplayColor = ParseColor(table.GetString("color", "#9E9E9E")),
Defaults = new CategoryDefaultProperties
{
HeightLimitMeters = table.GetDouble("height_limit_meters", 3.0),
SpeedLimitMetersPerSecond = table.GetDouble("speed_limit_meters_per_second", 1.0),
WidthLimitMeters = table.GetDouble("width_limit_meters", 3.0)
}
};
// 验证必填字段
if (string.IsNullOrEmpty(category.Id) || string.IsNullOrEmpty(category.DisplayName))
{
LogManager.Warning("[CustomCategoryConfig] 类别定义缺少必填字段 id 或 display_name");
return null;
}
// 检查ID是否与内置类别冲突
if (BuiltInCategories.GetById(category.Id) != null)
{
LogManager.Warning($"[CustomCategoryConfig] 自定义类别ID '{category.Id}' 与内置类别冲突,已跳过");
return null;
}
return category;
}
catch (Exception ex)
{
LogManager.Error($"[CustomCategoryConfig] 解析类别定义失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 生成 TOML 内容
/// </summary>
private string GenerateTomlContent()
{
var sb = new System.Text.StringBuilder();
sb.AppendLine("# 自定义物流类别配置文件");
sb.AppendLine("# 位置: %ProgramData%/Autodesk/Navisworks Manage 2026/plugins/TransportPlugin/custom_categories.toml");
sb.AppendLine("# 说明: 本文件定义用户自定义的物流类别,与模型文件无关,可跨项目共享");
sb.AppendLine();
sb.AppendLine("# 类别属性说明:");
sb.AppendLine("# id: 唯一标识(英文,用于存储和内部识别)");
sb.AppendLine("# display_name: 显示名称(中文)");
sb.AppendLine("# icon: 图标标识(可选,默认 Box");
sb.AppendLine("# traversable: 是否默认可通行(可选,默认 true");
sb.AppendLine("# priority: 优先级 1-10可选默认 5");
sb.AppendLine("# weight: 寻路权重(可选,默认 1.0,越低越优先)");
sb.AppendLine("# color: UI显示颜色可选默认 #9E9E9E");
sb.AppendLine("# height_limit_meters: 默认高度限制米数(可选,默认 3.0");
sb.AppendLine("# speed_limit_meters_per_second: 默认速度限制米/秒(可选,默认 1.0");
sb.AppendLine("# width_limit_meters: 默认宽度限制米数(可选,默认 3.0");
sb.AppendLine();
foreach (var category in Categories)
{
sb.AppendLine("[[category]]");
sb.AppendLine($"id = \"{EscapeTomlString(category.Id)}\"");
sb.AppendLine($"display_name = \"{EscapeTomlString(category.DisplayName)}\"");
sb.AppendLine($"icon = \"{EscapeTomlString(category.Icon)}\"");
sb.AppendLine($"traversable = {category.Traversable.ToString().ToLower()}");
sb.AppendLine($"priority = {category.Priority}");
sb.AppendLine($"weight = {category.Weight:F2}");
sb.AppendLine($"color = \"{ColorToHex(category.DisplayColor)}\"");
sb.AppendLine($"height_limit_meters = {category.Defaults.HeightLimitMeters:F2}");
sb.AppendLine($"speed_limit_meters_per_second = {category.Defaults.SpeedLimitMetersPerSecond:F2}");
sb.AppendLine($"width_limit_meters = {category.Defaults.WidthLimitMeters:F2}");
sb.AppendLine();
}
return sb.ToString();
}
/// <summary>
/// 解析颜色字符串
/// </summary>
private static Color ParseColor(string colorStr)
{
try
{
if (string.IsNullOrEmpty(colorStr))
return Color.Gray;
// 支持 #RRGGBB 或 #AARRGGBB 格式
if (colorStr.StartsWith("#"))
colorStr = colorStr.Substring(1);
if (colorStr.Length == 6)
{
int r = int.Parse(colorStr.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
int g = int.Parse(colorStr.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
int b = int.Parse(colorStr.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
return Color.FromArgb(r, g, b);
}
else if (colorStr.Length == 8)
{
int a = int.Parse(colorStr.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
int r = int.Parse(colorStr.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
int g = int.Parse(colorStr.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
int b = int.Parse(colorStr.Substring(6, 2), System.Globalization.NumberStyles.HexNumber);
return Color.FromArgb(a, r, g, b);
}
}
catch { }
return Color.Gray;
}
/// <summary>
/// 颜色转十六进制字符串
/// </summary>
private static string ColorToHex(Color color)
{
return $"#{color.R:X2}{color.G:X2}{color.B:X2}";
}
/// <summary>
/// 转义 TOML 字符串
/// </summary>
private static string EscapeTomlString(string str)
{
if (string.IsNullOrEmpty(str))
return "";
return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
}
}
/// <summary>
/// 类别定义基类(内置和自定义共用接口)
/// </summary>
public abstract class CategoryDefinition
{
public abstract string Id { get; set; }
public abstract string DisplayName { get; set; }
public abstract string Icon { get; set; }
public abstract bool Traversable { get; set; }
public abstract int Priority { get; set; }
public abstract double Weight { get; set; }
public abstract Color DisplayColor { get; set; }
public abstract CategoryDefaultProperties Defaults { get; set; }
}
/// <summary>
/// 自定义类别定义
/// </summary>
public class CustomCategoryDefinition : CategoryDefinition
{
public override string Id { get; set; }
public override string DisplayName { get; set; }
public override string Icon { get; set; } = "Box";
public override bool Traversable { get; set; } = true;
public override int Priority { get; set; } = 5;
public override double Weight { get; set; } = 1.0;
public override Color DisplayColor { get; set; } = Color.Gray;
public override CategoryDefaultProperties Defaults { get; set; } = new CategoryDefaultProperties();
}
/// <summary>
/// 类别默认属性
/// </summary>
public class CategoryDefaultProperties
{
public double HeightLimitMeters { get; set; } = 3.0;
public double SpeedLimitMetersPerSecond { get; set; } = 1.0;
public double WidthLimitMeters { get; set; } = 3.0;
}
/// <summary>
/// 内置类别定义(包装 LogisticsElementType 枚举)
/// </summary>
public class BuiltInCategoryDefinition : CategoryDefinition
{
private readonly CategoryAttributeManager.LogisticsElementType _elementType;
public BuiltInCategoryDefinition(CategoryAttributeManager.LogisticsElementType elementType)
{
_elementType = elementType;
}
public override string Id { get => _elementType.ToString(); set => throw new NotSupportedException("内置类别不支持修改"); }
public override string DisplayName { get => _elementType.ToString(); set => throw new NotSupportedException("内置类别不支持修改"); }
public override string Icon { get => GetIconForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
public override bool Traversable { get => GetTraversableForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
public override int Priority { get => GetPriorityForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
public override double Weight { get => GetWeightForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
public override Color DisplayColor { get => GetColorForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
public override CategoryDefaultProperties Defaults { get => GetDefaultsForType(_elementType); set => throw new NotSupportedException("内置类别不支持修改"); }
public CategoryAttributeManager.LogisticsElementType ElementType => _elementType;
private static string GetIconForType(CategoryAttributeManager.LogisticsElementType type)
{
switch (type)
{
case CategoryAttributeManager.LogisticsElementType.: return "BlockHelper";
case CategoryAttributeManager.LogisticsElementType.: return "Rectangle";
case CategoryAttributeManager.LogisticsElementType.: return "Door";
case CategoryAttributeManager.LogisticsElementType.: return "Elevator";
case CategoryAttributeManager.LogisticsElementType.: return "Stairs";
case CategoryAttributeManager.LogisticsElementType.: return "Road";
case CategoryAttributeManager.LogisticsElementType.: return "Corridor";
case CategoryAttributeManager.LogisticsElementType.: return "Truck";
case CategoryAttributeManager.LogisticsElementType.: return "Parking";
case CategoryAttributeManager.LogisticsElementType.: return "Air";
case CategoryAttributeManager.LogisticsElementType.: return "EyeOff";
default: return "Box";
}
}
private static bool GetTraversableForType(CategoryAttributeManager.LogisticsElementType type)
{
switch (type)
{
case CategoryAttributeManager.LogisticsElementType.:
case CategoryAttributeManager.LogisticsElementType.Unknown:
case CategoryAttributeManager.LogisticsElementType.:
return false;
default:
return true;
}
}
private static int GetPriorityForType(CategoryAttributeManager.LogisticsElementType type)
{
switch (type)
{
case CategoryAttributeManager.LogisticsElementType.: return 1;
case CategoryAttributeManager.LogisticsElementType.: return 2;
case CategoryAttributeManager.LogisticsElementType.: return 3;
case CategoryAttributeManager.LogisticsElementType.: return 4;
case CategoryAttributeManager.LogisticsElementType.: return 5;
case CategoryAttributeManager.LogisticsElementType.: return 6;
case CategoryAttributeManager.LogisticsElementType.: return 7;
case CategoryAttributeManager.LogisticsElementType.: return 8;
case CategoryAttributeManager.LogisticsElementType.: return 9;
case CategoryAttributeManager.LogisticsElementType.: return 10;
default: return 5;
}
}
private static double GetWeightForType(CategoryAttributeManager.LogisticsElementType type)
{
switch (type)
{
case CategoryAttributeManager.LogisticsElementType.: return 0.5;
case CategoryAttributeManager.LogisticsElementType.: return 0.6;
case CategoryAttributeManager.LogisticsElementType.: return 0.7;
case CategoryAttributeManager.LogisticsElementType.: return 0.8;
case CategoryAttributeManager.LogisticsElementType.: return 0.9;
case CategoryAttributeManager.LogisticsElementType.: return 1.0;
case CategoryAttributeManager.LogisticsElementType.: return 1.2;
case CategoryAttributeManager.LogisticsElementType.: return 2.0;
case CategoryAttributeManager.LogisticsElementType.: return 3.0;
case CategoryAttributeManager.LogisticsElementType.:
case CategoryAttributeManager.LogisticsElementType.Unknown:
return double.MaxValue;
default: return 1.0;
}
}
private static Color GetColorForType(CategoryAttributeManager.LogisticsElementType type)
{
switch (type)
{
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(76, 175, 80); // Green
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(129, 199, 132); // Light Green
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(33, 150, 243); // Blue
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(156, 39, 176); // Purple
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(255, 87, 34); // Deep Orange
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(255, 152, 0); // Orange
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(0, 150, 136); // Teal
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(63, 81, 181); // Indigo
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(244, 67, 54); // Red
case CategoryAttributeManager.LogisticsElementType.: return Color.FromArgb(158, 158, 158); // Gray
default: return Color.FromArgb(96, 125, 139); // Blue Gray
}
}
private static CategoryDefaultProperties GetDefaultsForType(CategoryAttributeManager.LogisticsElementType type)
{
var defaults = new CategoryDefaultProperties();
switch (type)
{
case CategoryAttributeManager.LogisticsElementType.:
defaults.HeightLimitMeters = 2.5;
defaults.SpeedLimitMetersPerSecond = 0.5;
defaults.WidthLimitMeters = 2.0;
break;
case CategoryAttributeManager.LogisticsElementType.:
defaults.HeightLimitMeters = 3.0;
defaults.SpeedLimitMetersPerSecond = 0.3;
defaults.WidthLimitMeters = 2.5;
break;
case CategoryAttributeManager.LogisticsElementType.:
defaults.HeightLimitMeters = 2.8;
defaults.SpeedLimitMetersPerSecond = 0.2;
defaults.WidthLimitMeters = 1.5;
break;
case CategoryAttributeManager.LogisticsElementType.:
defaults.HeightLimitMeters = 4.0;
defaults.SpeedLimitMetersPerSecond = 1.5;
defaults.WidthLimitMeters = 4.0;
break;
// 其他使用默认值
}
return defaults;
}
}
/// <summary>
/// 内置类别管理器
/// </summary>
public static class BuiltInCategories
{
private static readonly Dictionary<string, BuiltInCategoryDefinition> _categories;
static BuiltInCategories()
{
_categories = new Dictionary<string, BuiltInCategoryDefinition>(StringComparer.OrdinalIgnoreCase);
foreach (CategoryAttributeManager.LogisticsElementType type in
Enum.GetValues(typeof(CategoryAttributeManager.LogisticsElementType)))
{
var definition = new BuiltInCategoryDefinition(type);
_categories[type.ToString()] = definition;
}
}
/// <summary>
/// 获取所有内置类别
/// </summary>
public static IEnumerable<BuiltInCategoryDefinition> All => _categories.Values;
/// <summary>
/// 根据ID获取内置类别
/// </summary>
public static BuiltInCategoryDefinition GetById(string id)
{
_categories.TryGetValue(id ?? "", out var category);
return category;
}
/// <summary>
/// 根据枚举类型获取定义
/// </summary>
public static BuiltInCategoryDefinition GetByType(CategoryAttributeManager.LogisticsElementType type)
{
return GetById(type.ToString());
}
}
/// <summary>
/// TOML 扩展方法
/// </summary>
internal static class TomlExtensions
{
public static string GetString(this TomlTable table, string key, string defaultValue = "")
{
if (table.TryGetValue(key, out var value) && value is string str)
return str;
return defaultValue;
}
public static bool GetBool(this TomlTable table, string key, bool defaultValue = false)
{
if (table.TryGetValue(key, out var value) && value is bool b)
return b;
return defaultValue;
}
public static int GetInt(this TomlTable table, string key, int defaultValue = 0)
{
if (table.TryGetValue(key, out var value))
{
if (value is long l) return (int)l;
if (value is int i) return i;
}
return defaultValue;
}
public static double GetDouble(this TomlTable table, string key, double defaultValue = 0.0)
{
if (table.TryGetValue(key, out var value))
{
if (value is double d) return d;
if (value is long l) return l;
if (value is int i) return i;
}
return defaultValue;
}
}
}

View File

@ -1369,7 +1369,7 @@ namespace NavisworksTransport
if (document != null)
{
var allLogisticsItems = CategoryAttributeManager.GetAllLogisticsItems();
var channelItems = CategoryAttributeManager.FilterByLogisticsType(allLogisticsItems, CategoryAttributeManager.LogisticsElementType.);
var channelItems = CategoryAttributeManager.FilterByLogisticsType(allLogisticsItems, "通道");
_walkableAreas.AddRange(channelItems);
LogManager.Info($"[SelectChannels] 通过CategoryAttributeManager直接筛选到 {channelItems.Count} 个通道");
}
@ -4639,7 +4639,7 @@ namespace NavisworksTransport
if (layer.IsWalkable)
{
// 可通行层 - 门特殊处理,其他统一样式
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.)
if (cell.CellType == "门")
{
if (_showDoorGrid)
{
@ -4693,7 +4693,7 @@ namespace NavisworksTransport
}
}
}
else if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown)
else if (cell.CellType == "Unknown")
{
// Unknown网格没有高度层用于调试
if (_showUnknownGrid)

View File

@ -1,9 +1,10 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
using NavisworksTransport.PathPlanning;
using NavisworksTransport.Core.Config;
namespace NavisworksTransport
{
@ -186,7 +187,6 @@ namespace NavisworksTransport
return successCount;
}
/// <summary>
/// 检查模型项是否已有物流属性
/// </summary>
@ -284,6 +284,44 @@ namespace NavisworksTransport
}
}
/// <summary>
/// 获取指定物流元素类型的所有模型项(字符串版本,支持自定义类别)
/// </summary>
/// <param name="typeId">类型ID内置类别名称或自定义类别ID</param>
/// <returns>符合条件的模型项列表</returns>
public static List<ModelItem> GetLogisticsItemsByType(string typeId)
{
try
{
// 使用 Search API 搜索当前活动文档
using (var search = new Search())
{
// 搜索整个文档的根项及其所有后代
search.Selection.SelectAll();
search.Locations = SearchLocations.DescendantsAndSelf;
var searchConditions = search.SearchConditions;
searchConditions.Clear();
// 搜索条件:必须有物流分类
searchConditions.Add(SearchCondition.HasCategoryByDisplayName(LogisticsCategories.LOGISTICS));
// 搜索条件:类型属性等于指定值
searchConditions.Add(
SearchCondition.HasPropertyByDisplayName(LogisticsCategories.LOGISTICS, LogisticsProperties.TYPE)
.EqualValue(VariantData.FromDisplayString(typeId)));
// 执行搜索并返回列表
return search.FindAll(Application.ActiveDocument, false).ToList();
}
}
catch (Exception ex)
{
LogManager.Error($"[CategoryAttributeManager] 获取物流项目失败: {ex.Message}");
return new List<ModelItem>();
}
}
/// <summary>
/// 根据物流属性筛选模型项
/// </summary>
@ -333,6 +371,55 @@ namespace NavisworksTransport
}
}
/// <summary>
/// 按物流元素类型筛选模型项(字符串版本,支持自定义类别)
/// </summary>
/// <param name="items">要筛选的模型项集合</param>
/// <param name="typeId">类型ID内置类别名称或自定义类别ID</param>
/// <returns>符合条件的模型项集合</returns>
public static ModelItemCollection FilterByLogisticsType(ModelItemCollection items, string typeId)
{
try
{
// 使用SearchAPI优化直接搜索具有指定类型值的物流项目
using (var search = new Search())
{
// 将输入的ModelItemCollection转换为搜索选择
search.Selection.CopyFrom(items);
search.Locations = SearchLocations.DescendantsAndSelf;
var searchConditions = search.SearchConditions;
searchConditions.Clear();
// 搜索条件:必须有物流分类
searchConditions.Add(SearchCondition.HasCategoryByDisplayName(LogisticsCategories.LOGISTICS));
// 搜索条件:类型属性等于指定值
searchConditions.Add(
SearchCondition.HasPropertyByDisplayName(LogisticsCategories.LOGISTICS, LogisticsProperties.TYPE)
.EqualValue(VariantData.FromDisplayString(typeId)));
return search.FindAll(Application.ActiveDocument, false);
}
}
catch (Exception ex)
{
LogManager.Error($"按物流类型过滤失败: {ex.Message}");
// 如果SearchAPI失败回退到原始实现
ModelItemCollection filteredItems = new ModelItemCollection();
foreach (ModelItem item in items)
{
string typeValue = GetLogisticsPropertyValue(item, LogisticsProperties.TYPE);
if (typeValue == typeId)
{
filteredItems.Add(item);
}
}
return filteredItems;
}
}
/// <summary>
/// 筛选可通行的模型项
/// </summary>
@ -985,6 +1072,199 @@ namespace NavisworksTransport
return -1;
}
}
#region
private static Core.Config.CustomCategoryConfig _customCategoryConfig;
private static readonly object _customCategoryLock = new object();
/// <summary>
/// 自定义类别配置(懒加载)
/// </summary>
public static Core.Config.CustomCategoryConfig CustomCategories
{
get
{
if (_customCategoryConfig == null)
{
lock (_customCategoryLock)
{
if (_customCategoryConfig == null)
{
_customCategoryConfig = Core.Config.CustomCategoryConfig.LoadFromFile();
}
}
}
return _customCategoryConfig;
}
}
/// <summary>
/// 重新加载自定义类别配置
/// </summary>
public static void ReloadCustomCategories()
{
lock (_customCategoryLock)
{
_customCategoryConfig = Core.Config.CustomCategoryConfig.LoadFromFile();
LogManager.Info("[CategoryAttributeManager] 自定义类别配置已重新加载");
}
}
/// <summary>
/// 获取模型项的物流元素类型(字符串形式,支持自定义类别)
/// </summary>
/// <param name="item">模型项</param>
/// <returns>类别ID字符串如果没有设置则返回"障碍物"</returns>
public static string GetCategoryId(ModelItem item)
{
try
{
string typeValue = GetLogisticsPropertyValue(item, LogisticsProperties.TYPE);
if (!string.IsNullOrEmpty(typeValue))
{
// 先检查是否为内置类别
if (Enum.TryParse<LogisticsElementType>(typeValue, out _))
{
return typeValue;
}
// 再检查是否为自定义类别
if (CustomCategories.CategoryExists(typeValue))
{
return typeValue;
}
}
}
catch (Exception ex)
{
LogManager.Error($"[CategoryAttributeManager] 获取类别ID失败: {ex.Message}");
}
// 默认返回障碍物
return LogisticsElementType..ToString();
}
/// <summary>
/// 获取类别定义(内置或自定义)
/// </summary>
/// <param name="categoryId">类别ID</param>
/// <returns>类别定义如果不存在返回null</returns>
public static Core.Config.CategoryDefinition GetCategoryDefinition(string categoryId)
{
if (string.IsNullOrEmpty(categoryId))
return null;
// 先查内置
if (Enum.TryParse<LogisticsElementType>(categoryId, out var builtInType))
{
return Core.Config.BuiltInCategories.GetByType(builtInType);
}
// 再查自定义
return CustomCategories.GetCategory(categoryId);
}
/// <summary>
/// 获取类别的寻路权重
/// </summary>
/// <param name="categoryId">类别ID</param>
/// <returns>权重值未知类别返回1.0</returns>
public static double GetCategoryWeight(string categoryId)
{
var definition = GetCategoryDefinition(categoryId);
return definition?.Weight ?? 1.0;
}
/// <summary>
/// 检查类别是否可通行
/// </summary>
/// <param name="categoryId">类别ID</param>
/// <returns>是否可通行</returns>
public static bool IsCategoryTraversable(string categoryId)
{
var definition = GetCategoryDefinition(categoryId);
return definition?.Traversable ?? false;
}
/// <summary>
/// 获取所有可用类别(内置 + 自定义)
/// </summary>
public static IEnumerable<Core.Config.CategoryDefinition> GetAllAvailableCategories()
{
return CustomCategories.GetAllCategories();
}
/// <summary>
/// 检查是否为自定义类别
/// </summary>
public static bool IsCustomCategory(string categoryId)
{
return !string.IsNullOrEmpty(categoryId) &&
!Enum.TryParse<LogisticsElementType>(categoryId, out _);
}
/// <summary>
/// 为选中的模型项添加物流属性(支持自定义类别)
/// </summary>
public static int AddLogisticsAttributes(
ModelItemCollection items,
string categoryId,
bool isTraversable,
int priority,
double heightLimit,
double speedLimit,
double widthLimit)
{
if (items == null || items.Count == 0)
return 0;
if (string.IsNullOrEmpty(categoryId))
{
LogManager.Warning("[CategoryAttributeManager] 类别ID为空无法设置属性");
return 0;
}
// 准备属性字典
var properties = new Dictionary<string, string>
{
{ LogisticsProperties.TYPE, categoryId },
{ LogisticsProperties.TRAVERSABLE, isTraversable ? "是" : "否" },
{ LogisticsProperties.PRIORITY, priority.ToString() },
{ LogisticsProperties.HEIGHT_LIMIT, heightLimit.ToString("F2") + " m" },
{ LogisticsProperties.SPEED_LIMIT, speedLimit.ToString("F2") + " m/s" },
{ LogisticsProperties.WIDTH_LIMIT, widthLimit.ToString("F2") + " m" }
};
// 准备内部名称字典
var propertyInternalNames = new Dictionary<string, string>
{
{ LogisticsProperties.TYPE, categoryId + "_Internal" },
{ LogisticsProperties.TRAVERSABLE, "Traversable_Internal" },
{ LogisticsProperties.PRIORITY, "Priority_Internal" },
{ LogisticsProperties.HEIGHT_LIMIT, "HeightLimit_Internal" },
{ LogisticsProperties.SPEED_LIMIT, "SpeedLimit_Internal" },
{ LogisticsProperties.WIDTH_LIMIT, "WidthLimit_Internal" }
};
int successCount = NavisworksComPropertyManager.SetUserDefinedProperties(
items,
LogisticsCategories.LOGISTICS,
LogisticsCategories.CATEGORY_INTERNAL_NAME,
properties,
propertyInternalNames);
LogManager.Info($"[属性添加] 添加操作完成,类别: {categoryId}, 成功添加 {successCount} 个模型的属性");
// 如果是空轨类型,预计算基准路径
if (categoryId == "空轨" && successCount > 0)
{
PreCalculateRailBaselinePaths(items);
}
return successCount;
}
#endregion
}
/// <summary>
@ -1286,5 +1566,6 @@ namespace NavisworksTransport
return passableAreas;
}
}
}
}

View File

@ -116,7 +116,7 @@ namespace NavisworksTransport.PathPlanning
public int Y { get; set; }
public int LayerIndex { get; set; }
public double Z { get; set; }
public CategoryAttributeManager.LogisticsElementType CellType { get; set; }
public string CellType { get; set; }
}
/// <summary>
@ -219,8 +219,8 @@ namespace NavisworksTransport.PathPlanning
continue;
// 楼梯/电梯区域跳过层0下方楼板不是可行走表面
if (li == 0 && (layer.Type == CategoryAttributeManager.LogisticsElementType. ||
layer.Type == CategoryAttributeManager.LogisticsElementType.))
if (li == 0 && (layer.Type == "楼梯" ||
layer.Type == "电梯"))
{
continue;
}
@ -334,8 +334,8 @@ namespace NavisworksTransport.PathPlanning
// 检查该网格是否有任何楼梯或电梯层(允许垂直移动)
bool hasStairsOrElevator = cell.HeightLayers.Any(layer =>
layer.Type == CategoryAttributeManager.LogisticsElementType. ||
layer.Type == CategoryAttributeManager.LogisticsElementType.);
layer.Type == "楼梯" ||
layer.Type == "电梯");
if (!hasStairsOrElevator || cell.HeightLayers.Count < 2)
continue;
@ -352,8 +352,8 @@ namespace NavisworksTransport.PathPlanning
continue;
// 垂直边只在楼梯/电梯类型之间创建
if (layer1.Type != CategoryAttributeManager.LogisticsElementType. &&
layer1.Type != CategoryAttributeManager.LogisticsElementType.)
if (layer1.Type != "楼梯" &&
layer1.Type != "电梯")
continue;
if (layer1.PassableHeight.GetSpan() < objectHeight ||
@ -369,7 +369,7 @@ namespace NavisworksTransport.PathPlanning
continue;
// 垂直移动速度:电梯较快,楼梯较慢
float verticalSpeed = layer1.Type == CategoryAttributeManager.LogisticsElementType.
float verticalSpeed = layer1.Type == "电梯"
? (float)(baseSpeed * 0.5)
: (float)(baseSpeed * 0.3);
maxSpeed = Math.Max(maxSpeed, verticalSpeed);
@ -1568,7 +1568,7 @@ namespace NavisworksTransport.PathPlanning
? endCell.Value.HeightLayers[0].Z
: 0;
if (endCell.Value.CellType == CategoryAttributeManager.LogisticsElementType.)
if (endCell.Value.CellType == "门")
{
LogManager.Info($"[路径转换-门] 门网格({endGridPos.X},{endGridPos.Y}) HeightLayer[0].Z={endZ:F3}, 位置({endWorldPos.X:F2},{endWorldPos.Y:F2})");
}
@ -2483,7 +2483,7 @@ namespace NavisworksTransport.PathPlanning
checkedPoints++;
// 特别标记门网格点
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.)
if (cell.CellType == "门")
{
doorPoints++;
LogManager.Info($"[路径高度验证] 检查路径点{i}(门网格): ({point.X:F2}, {point.Y:F2}, {point.Z:F2}) -> 网格({gridPos.X},{gridPos.Y})");
@ -2914,9 +2914,9 @@ namespace NavisworksTransport.PathPlanning
distanceMap[x, y] = isUnsafeArea ? 0 : int.MaxValue;
// 统计网格类型
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.)
if (cell.CellType == "障碍物")
obstacleCount++;
else if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown)
else if (cell.CellType == "Unknown")
unknownCount++;
else if (cell.HasAnyWalkableLayer())
walkableCount++;

View File

@ -306,7 +306,7 @@ namespace NavisworksTransport.PathPlanning
double gridCenterElevation = GetElevationAtPoint(triangle, worldPos);
// 创建高度层(追加模式,不覆盖)
var channelType = CategoryAttributeManager.GetLogisticsElementType(channel);
var channelType = CategoryAttributeManager.GetCategoryId(channel);
var layer = new HeightLayer(
z: gridCenterElevation,
passableHeight: new HeightInterval(0, 0), // 初始化后续由SetChannelPassableHeights设置

View File

@ -20,7 +20,7 @@ namespace NavisworksTransport.PathPlanning
{
return new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = "通道",
IsInChannel = true,
ChannelType = ChannelType.Corridor,
RelatedModelItem = channel,
@ -42,7 +42,7 @@ namespace NavisworksTransport.PathPlanning
{
return new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = "门",
IsInChannel = false,
ChannelType = ChannelType.Other,
RelatedModelItem = door,
@ -62,7 +62,7 @@ namespace NavisworksTransport.PathPlanning
{
return new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = "障碍物",
IsInChannel = false,
ChannelType = ChannelType.Other,
RelatedModelItem = obstacle,
@ -84,7 +84,7 @@ namespace NavisworksTransport.PathPlanning
{
return new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = "电梯",
IsInChannel = false,
ChannelType = ChannelType.Other,
RelatedModelItem = elevator,
@ -106,7 +106,7 @@ namespace NavisworksTransport.PathPlanning
{
return new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = "楼梯",
IsInChannel = false,
ChannelType = ChannelType.Other,
RelatedModelItem = stairs,
@ -125,7 +125,7 @@ namespace NavisworksTransport.PathPlanning
{
return new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.Unknown,
CellType = "Unknown",
IsInChannel = false,
ChannelType = ChannelType.Other,
RelatedModelItem = null,

View File

@ -166,7 +166,7 @@ namespace NavisworksTransport.PathPlanning
{
var cell = new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.Unknown, // 修改:默认为未知/空洞类型
CellType = CategoryAttributeManager.LogisticsElementType.Unknown.ToString(), // 修改:默认为未知/空洞类型
IsInChannel = false,
HeightLayers = new List<HeightLayer>(),
ChannelType = ChannelType.Other
@ -264,7 +264,7 @@ namespace NavisworksTransport.PathPlanning
/// <param name="cost">遍历成本</param>
/// <param name="speedLimit">限速</param>
/// <param name="cellType">单元格类型</param>
public void SetCell(GridPoint2D gridPosition, bool isWalkable, double cost, double speedLimit, CategoryAttributeManager.LogisticsElementType cellType)
public void SetCell(GridPoint2D gridPosition, bool isWalkable, double cost, double speedLimit, string cellType)
{
if (!IsValidGridPosition(gridPosition))
return;
@ -273,9 +273,9 @@ namespace NavisworksTransport.PathPlanning
var cell = new GridCell
{
CellType = cellType,
IsInChannel = cellType == CategoryAttributeManager.LogisticsElementType.,
IsInChannel = cellType == "通道",
HeightLayers = new List<HeightLayer>(),
ChannelType = cellType == CategoryAttributeManager.LogisticsElementType. ? ChannelType.Corridor : ChannelType.Other,
ChannelType = cellType == "通道" ? ChannelType.Corridor : ChannelType.Other,
SpeedLimit = speedLimit
};
@ -393,8 +393,8 @@ namespace NavisworksTransport.PathPlanning
private void ApplyCellValidationRules(ref GridCell cell, double originalCost)
{
// 🔥 关键验证确保Unknown和障碍物类型永远不可通行
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown ||
cell.CellType == CategoryAttributeManager.LogisticsElementType.)
if (cell.CellType == "Unknown" ||
cell.CellType == "障碍物")
{
// Unknown和障碍物没有可通行层HasAnyWalkableLayer()自动返回false
cell.Cost = double.MaxValue;
@ -486,7 +486,7 @@ namespace NavisworksTransport.PathPlanning
for (int y = 0; y < Height; y++)
{
var cell = Cells[x, y];
if (cell.IsInChannel || cell.CellType == CategoryAttributeManager.LogisticsElementType.)
if (cell.IsInChannel || cell.CellType == "通道")
{
channelCells.Add((new GridPoint2D(x, y), cell));
}
@ -629,61 +629,27 @@ namespace NavisworksTransport.PathPlanning
if (cell.HasAnyWalkableLayer())
{
totalWalkable++;
switch (cell.CellType)
{
case CategoryAttributeManager.LogisticsElementType.:
walkable_楼板++;
break;
case CategoryAttributeManager.LogisticsElementType.:
walkable_门++;
break;
case CategoryAttributeManager.LogisticsElementType.:
walkable_通道++;
break;
case CategoryAttributeManager.LogisticsElementType.:
walkable_装卸区++;
break;
case CategoryAttributeManager.LogisticsElementType.:
walkable_停车位++;
break;
case CategoryAttributeManager.LogisticsElementType.:
walkable_楼梯++;
break;
case CategoryAttributeManager.LogisticsElementType.:
walkable_电梯++;
break;
case CategoryAttributeManager.LogisticsElementType.:
walkable_走廊++;
break;
default:
walkable_Other++;
break;
}
// 使用字符串比较(支持自定义类别)
if (cell.CellType == "楼板") walkable_楼板++;
else if (cell.CellType == "门") walkable_门++;
else if (cell.CellType == "通道") walkable_通道++;
else if (cell.CellType == "装卸区") walkable_装卸区++;
else if (cell.CellType == "停车位") walkable_停车位++;
else if (cell.CellType == "楼梯") walkable_楼梯++;
else if (cell.CellType == "电梯") walkable_电梯++;
else if (cell.CellType == "走廊") walkable_走廊++;
else walkable_Other++;
}
else
{
totalNonWalkable++;
switch (cell.CellType)
{
case CategoryAttributeManager.LogisticsElementType.Unknown:
nonWalkable_Unknown++;
break;
case CategoryAttributeManager.LogisticsElementType.:
nonWalkable_障碍物++;
break;
case CategoryAttributeManager.LogisticsElementType.:
nonWalkable_楼板++;
break;
case CategoryAttributeManager.LogisticsElementType.:
nonWalkable_门++;
break;
case CategoryAttributeManager.LogisticsElementType.:
nonWalkable_通道++;
break;
default:
nonWalkable_Other++;
break;
}
// 使用字符串比较(支持自定义类别)
if (cell.CellType == "Unknown") nonWalkable_Unknown++;
else if (cell.CellType == "障碍物") nonWalkable_障碍物++;
else if (cell.CellType == "楼板") nonWalkable_楼板++;
else if (cell.CellType == "门") nonWalkable_门++;
else if (cell.CellType == "通道") nonWalkable_通道++;
else nonWalkable_Other++;
}
}
}
@ -720,9 +686,9 @@ namespace NavisworksTransport.PathPlanning
public double Cost { get; set; }
/// <summary>
/// 单元格类型
/// 单元格类型类别ID字符串支持内置和自定义类别
/// </summary>
public CategoryAttributeManager.LogisticsElementType CellType { get; set; }
public string CellType { get; set; }
/// <summary>
/// 相关的模型项引用(可选)
@ -767,33 +733,8 @@ namespace NavisworksTransport.PathPlanning
if (!HasAnyWalkableLayer())
return double.MaxValue;
switch (CellType)
{
case CategoryAttributeManager.LogisticsElementType.:
return 0.5;
case CategoryAttributeManager.LogisticsElementType.:
return 0.8;
case CategoryAttributeManager.LogisticsElementType.:
return 0.9;
case CategoryAttributeManager.LogisticsElementType.:
return 1.0;
case CategoryAttributeManager.LogisticsElementType.:
return 1.2;
case CategoryAttributeManager.LogisticsElementType.:
return 3.0;
case CategoryAttributeManager.LogisticsElementType.:
return 2.0;
case CategoryAttributeManager.LogisticsElementType.:
return 0.6;
case CategoryAttributeManager.LogisticsElementType.Unknown:
// Unknown类型空洞永远不可通行
return double.MaxValue;
case CategoryAttributeManager.LogisticsElementType.:
// 障碍物永远不可通行
return double.MaxValue;
default:
return 1.0;
}
// 使用类别定义获取权重(支持内置和自定义类别)
return CategoryAttributeManager.GetCategoryWeight(CellType);
}
/// <summary>
@ -804,7 +745,7 @@ namespace NavisworksTransport.PathPlanning
{
var cell = new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = CategoryAttributeManager.LogisticsElementType..ToString(),
IsInChannel = false,
HeightLayers = new List<HeightLayer>(), // 空列表HasAnyWalkableLayer()返回false
ChannelType = ChannelType.Other,
@ -823,7 +764,7 @@ namespace NavisworksTransport.PathPlanning
{
var cell = new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = CategoryAttributeManager.LogisticsElementType..ToString(),
IsInChannel = isOpen,
HeightLayers = new List<HeightLayer>(),
ChannelType = ChannelType.Other,
@ -842,7 +783,7 @@ namespace NavisworksTransport.PathPlanning
{
var cell = new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = CategoryAttributeManager.LogisticsElementType..ToString(),
IsInChannel = true,
HeightLayers = new List<HeightLayer>(),
ChannelType = channelType,
@ -880,9 +821,9 @@ namespace NavisworksTransport.PathPlanning
public double SpeedLimit { get; set; }
/// <summary>
/// 层类型
/// 层类型类别ID字符串支持自定义类别
/// </summary>
public CategoryAttributeManager.LogisticsElementType Type { get; set; }
public string Type { get; set; }
/// <summary>
/// 是否是边界层(用于膨胀计算)
@ -900,7 +841,7 @@ namespace NavisworksTransport.PathPlanning
/// <summary>
/// 构造函数
/// </summary>
public HeightLayer(double z, HeightInterval passableHeight, ModelItem sourceItem, double speedLimit, CategoryAttributeManager.LogisticsElementType type)
public HeightLayer(double z, HeightInterval passableHeight, ModelItem sourceItem, double speedLimit, string type)
{
Z = z;
PassableHeight = passableHeight;
@ -1037,7 +978,7 @@ namespace NavisworksTransport.PathPlanning
{
for (int y = 0; y < GridMap.Height; y++)
{
if (GridMap.Cells[x, y].CellType == CategoryAttributeManager.LogisticsElementType.)
if (GridMap.Cells[x, y].CellType == "通道")
{
channelCellCount++;
}

View File

@ -170,7 +170,7 @@ namespace NavisworksTransport.PathPlanning
}
// 物流类型属性
var logisticsType = CategoryAttributeManager.GetLogisticsElementType(item);
var logisticsType = CategoryAttributeManager.GetCategoryId(item);
hashBuilder.Append($"LogType:{logisticsType}|");
// Transform信息如果有变换

View File

@ -132,7 +132,7 @@ namespace NavisworksTransport.PathPlanning
// 1.6. 批量获取所有"无关项"(不参与网格生成的大型构件)
LogManager.Info("【生成网格地图】步骤1.6: 批量获取无关项并收集相关节点");
var irrelevantItems = CategoryAttributeManager.GetLogisticsItemsByType(
CategoryAttributeManager.LogisticsElementType.);
"无关项");
LogManager.Info($"【生成网格地图】直接获取到 {irrelevantItems.Count} 个无关项");
// 智能收集无关项相关节点(包括子节点等)
@ -330,7 +330,7 @@ namespace NavisworksTransport.PathPlanning
// 为门添加可通行的高度层
var doorLayer = new HeightLayer
{
Type = CategoryAttributeManager.LogisticsElementType.,
Type = "门",
Z = doorBottomZ,
PassableHeight = new HeightInterval(doorBottomZ, doorBottomZ + doorHeight),
IsWalkable = true, // 关键:门是可通行的
@ -382,8 +382,8 @@ namespace NavisworksTransport.PathPlanning
{
// 检查是否是楼梯或电梯网格(有多层且包含楼梯/电梯层)
bool hasStairsOrElevator = cell.HeightLayers.Any(layer =>
layer.Type == CategoryAttributeManager.LogisticsElementType. ||
layer.Type == CategoryAttributeManager.LogisticsElementType.);
layer.Type == "楼梯" ||
layer.Type == "电梯");
// 为每个高度层设置PassableHeight
for (int i = 0; i < cell.HeightLayers.Count; i++)
@ -492,7 +492,7 @@ namespace NavisworksTransport.PathPlanning
int neighborLayerCount = (neighbor.HeightLayers != null) ? neighbor.HeightLayers.Count : 0;
// 情况2邻居是Unknown - 标记当前网格所有层为边界
if (neighbor.CellType == CategoryAttributeManager.LogisticsElementType.Unknown)
if (neighbor.CellType == "Unknown")
{
for (int li = 0; li < currentLayerCount; li++)
{
@ -585,8 +585,8 @@ namespace NavisworksTransport.PathPlanning
{
try
{
var logisticsType = CategoryAttributeManager.GetLogisticsElementType(item);
if (logisticsType == CategoryAttributeManager.LogisticsElementType.)
var logisticsType = CategoryAttributeManager.GetCategoryId(item);
if (logisticsType == "门")
{
doorItems.Add(item);
}
@ -617,7 +617,7 @@ namespace NavisworksTransport.PathPlanning
var cell = gridMap.Cells[x, y];
// 只为通道类型的网格生成扫描点
if (cell.CellType == CategoryAttributeManager.LogisticsElementType. && cell.IsInChannel)
if (cell.CellType == "通道" && cell.IsInChannel)
{
// 使用网格的实际Z坐标
var worldPos = gridMap.GridToWorld3D(new GridPoint2D(x, y));
@ -701,7 +701,7 @@ namespace NavisworksTransport.PathPlanning
// 关键修复:只处理通道单元格的高度信息集成
// 非通道单元格不应该通过高度扫描而改变其类型或可通行性
if (cell.CellType == CategoryAttributeManager.LogisticsElementType. && cell.IsInChannel)
if (cell.CellType == "通道" && cell.IsInChannel)
{
channelCellsWithHeightData++;
@ -713,7 +713,7 @@ namespace NavisworksTransport.PathPlanning
// 创建更新后的通道GridCell保持原有属性但更新位置和高度
var updatedCell = new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = "通道",
IsInChannel = true,
ChannelType = ChannelType.Corridor,
RelatedModelItem = cell.RelatedModelItem, // 保持原有关联
@ -733,7 +733,7 @@ namespace NavisworksTransport.PathPlanning
// 创建高度不足的通道GridCell保持原有属性但设为不可通行
var updatedCell = new GridCell
{
CellType = CategoryAttributeManager.LogisticsElementType.,
CellType = "通道",
IsInChannel = true,
ChannelType = ChannelType.Corridor,
RelatedModelItem = cell.RelatedModelItem, // 保持原有关联
@ -791,20 +791,20 @@ namespace NavisworksTransport.PathPlanning
walkableCount++;
switch (cell.CellType)
{
case CategoryAttributeManager.LogisticsElementType.:
case "通道":
channelCount++;
break;
case CategoryAttributeManager.LogisticsElementType.:
case "楼板":
openSpaceCount++;
break;
case CategoryAttributeManager.LogisticsElementType.:
case "门":
doorCount++;
break;
}
}
else
{
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.)
if (cell.CellType == "障碍物")
{
obstacleCount++;
}
@ -986,7 +986,7 @@ namespace NavisworksTransport.PathPlanning
distanceMap[x, y] = double.MaxValue;
// Unknown网格 → distance=0整个网格无法通行
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown)
if (cell.CellType == "Unknown")
{
distanceMap[x, y] = 0.0;
continue;
@ -1026,7 +1026,7 @@ namespace NavisworksTransport.PathPlanning
var layer = cell.HeightLayers[layerIndex];
// 门保护:不膨胀门类型的层
if (layer.Type == CategoryAttributeManager.LogisticsElementType.)
if (layer.Type == "门")
continue;
// 障碍物膨胀条件不包括障碍物本身distance=0
@ -1103,7 +1103,7 @@ namespace NavisworksTransport.PathPlanning
var layer = cell.HeightLayers[layerIndex];
// 门保护:不膨胀门类型的层
if (layer.Type == CategoryAttributeManager.LogisticsElementType.)
if (layer.Type == "门")
continue;
// 楼梯口保护Layer[0]且存在Layer[1]且高度差小于阈值
@ -1386,7 +1386,7 @@ namespace NavisworksTransport.PathPlanning
var cell = gridMap.Cells[update.X, update.Y];
// 只更新通道网格,跳过已是障碍物的网格
if (cell.CellType == CategoryAttributeManager.LogisticsElementType. && cell.IsInChannel)
if (cell.CellType == "通道" && cell.IsInChannel)
{
var bbox = update.BoundingBox;
@ -1428,7 +1428,7 @@ namespace NavisworksTransport.PathPlanning
// 如果所有层都被标记为不可通行,标记整个网格为障碍物
if (anyLayerModified && !cell.HeightLayers.Any(l => l.IsWalkable))
{
cell.CellType = CategoryAttributeManager.LogisticsElementType.;
cell.CellType = "障碍物";
cell.Cost = double.MaxValue;
cell.IsInChannel = false;
cell.RelatedModelItem = update.Item;

View File

@ -264,16 +264,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
/// <summary>
/// 可用类别筛选选项 - 从枚举动态获取所有类别,并添加"全部"选项
/// 可用类别筛选选项 - 从所有可用类别动态获取,并添加"全部"选项
/// </summary>
public ThreadSafeObservableCollection<string> AvailableCategoryFilters { get; } =
new ThreadSafeObservableCollection<string>(
new[] { "全部" }.Concat(
Enum.GetValues(typeof(CategoryAttributeManager.LogisticsElementType))
.Cast<CategoryAttributeManager.LogisticsElementType>()
.Select(e => e.ToString())
)
);
new ThreadSafeObservableCollection<string>(new[] { "全部" });
/// <summary>
/// 当前选中的类别筛选条件
@ -339,9 +333,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return;
}
if (!AvailableCategories.Contains(info.ElementType))
// 将类别ID转换为显示名称
string displayName = GetDisplayNameByCategoryId(info.ElementType);
if (string.IsNullOrEmpty(displayName))
{
LogManager.Error($"[ModelSettingsViewModel] 异常:模型 {logisticsModel.Name} 的ElementType '{info.ElementType}' 不在可用类别中!");
LogManager.Error($"[ModelSettingsViewModel] 异常:模型 {logisticsModel.Name} 的ElementType '{info.ElementType}' 无法找到对应的类别!");
return;
}
if (!AvailableCategories.Contains(displayName))
{
LogManager.Error($"[ModelSettingsViewModel] 异常:模型 {logisticsModel.Name} 的类别显示名称 '{displayName}' 不在可用类别中!");
return;
}
@ -373,8 +375,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return;
}
// 所有验证通过,回填属性
SelectedCategory = info.ElementType;
// 所有验证通过,回填属性(使用显示名称)
SelectedCategory = displayName;
IsTraversable = info.IsTraversable;
Priority = info.Priority;
HeightLimit = info.HeightLimit;
@ -675,28 +677,27 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return new { Success = false, Count = 0, Message = "请先选择模型元素" };
}
// 解析选中的类别为枚举
if (Enum.TryParse<CategoryAttributeManager.LogisticsElementType>(SelectedCategory, out var elementType))
{
int successCount = CategoryAttributeManager.AddLogisticsAttributes(
selectedItems,
elementType,
isTraversable: IsTraversable,
priority: Priority,
heightLimit: HeightLimit,
speedLimit: SpeedLimit,
widthLimit: WidthLimit);
return new {
Success = true,
Count = successCount,
Message = $"已为 {successCount} 个元素设置物流属性: {SelectedCategory}"
};
}
else
// 根据显示名称查找类别ID
string categoryId = GetCategoryIdByDisplayName(SelectedCategory);
if (string.IsNullOrEmpty(categoryId))
{
return new { Success = false, Count = 0, Message = $"无效的物流类别: {SelectedCategory}" };
}
int successCount = CategoryAttributeManager.AddLogisticsAttributes(
selectedItems,
categoryId,
isTraversable: IsTraversable,
priority: Priority,
heightLimit: HeightLimit,
speedLimit: SpeedLimit,
widthLimit: WidthLimit);
return new {
Success = true,
Count = successCount,
Message = $"已为 {successCount} 个元素设置物流属性: {SelectedCategory}"
};
}
catch (Exception ex)
{
@ -1286,11 +1287,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
// 初始化物流类别
// 初始化物流类别(包括内置和自定义类别)
AvailableCategories.Clear();
foreach (var elementType in Enum.GetValues(typeof(CategoryAttributeManager.LogisticsElementType)))
AvailableCategoryFilters.Clear();
AvailableCategoryFilters.Add("全部");
// 从 CategoryAttributeManager 获取所有可用类别(内置 + 自定义)
var allCategories = CategoryAttributeManager.GetAllAvailableCategories();
foreach (var category in allCategories)
{
AvailableCategories.Add(elementType.ToString());
AvailableCategories.Add(category.DisplayName);
AvailableCategoryFilters.Add(category.DisplayName);
}
// 设置默认选择
@ -1423,6 +1430,54 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
/// <summary>
/// 根据显示名称获取类别ID
/// </summary>
/// <param name="displayName">类别显示名称</param>
/// <returns>类别ID如果未找到返回null</returns>
private string GetCategoryIdByDisplayName(string displayName)
{
if (string.IsNullOrEmpty(displayName))
return null;
// 从所有可用类别中查找
var allCategories = CategoryAttributeManager.GetAllAvailableCategories();
foreach (var category in allCategories)
{
if (category.DisplayName == displayName)
{
return category.Id;
}
}
// 如果没找到匹配,直接返回显示名称(可能是旧数据或其他情况)
return displayName;
}
/// <summary>
/// 根据类别ID获取显示名称
/// </summary>
/// <param name="categoryId">类别ID</param>
/// <returns>类别显示名称如果未找到返回null</returns>
private string GetDisplayNameByCategoryId(string categoryId)
{
if (string.IsNullOrEmpty(categoryId))
return null;
// 从所有可用类别中查找
var allCategories = CategoryAttributeManager.GetAllAvailableCategories();
foreach (var category in allCategories)
{
if (category.Id.Equals(categoryId, StringComparison.OrdinalIgnoreCase))
{
return category.DisplayName;
}
}
// 如果没找到匹配直接返回类别ID可能是旧数据或其他情况
return categoryId;
}
/// <summary>
/// 格式化物流属性为显示字符串
/// </summary>