From f9df83ba5bfd1b34a1d5a2b554c92d9666232e7f Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Thu, 19 Feb 2026 14:46:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E7=B1=BB=E5=88=AB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TransportPlugin.csproj | 6 + resources/default_custom_categories.toml | 77 +++ src/Core/Config/CustomCategoryConfig.cs | 619 ++++++++++++++++++ src/Core/PathPlanningManager.cs | 6 +- .../Properties/CategoryAttributeManager.cs | 287 +++++++- src/PathPlanning/AutoPathFinder.cs | 24 +- src/PathPlanning/ChannelBasedGridBuilder.cs | 2 +- src/PathPlanning/GridCellBuilder.cs | 12 +- src/PathPlanning/GridMap.cs | 129 +--- src/PathPlanning/GridMapCacheKey.cs | 2 +- src/PathPlanning/GridMapGenerator.cs | 40 +- .../WPF/ViewModels/ModelSettingsViewModel.cs | 123 +++- 12 files changed, 1153 insertions(+), 174 deletions(-) create mode 100644 resources/default_custom_categories.toml create mode 100644 src/Core/Config/CustomCategoryConfig.cs diff --git a/TransportPlugin.csproj b/TransportPlugin.csproj index 1f0a6fd..a901fde 100644 --- a/TransportPlugin.csproj +++ b/TransportPlugin.csproj @@ -140,6 +140,7 @@ + @@ -476,6 +477,11 @@ PreserveNewest resources\default_config.toml + + + PreserveNewest + resources\default_custom_categories.toml + PreserveNewest diff --git a/resources/default_custom_categories.toml b/resources/default_custom_categories.toml new file mode 100644 index 0000000..9cb8ff0 --- /dev/null +++ b/resources/default_custom_categories.toml @@ -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 diff --git a/src/Core/Config/CustomCategoryConfig.cs b/src/Core/Config/CustomCategoryConfig.cs new file mode 100644 index 0000000..b7819e6 --- /dev/null +++ b/src/Core/Config/CustomCategoryConfig.cs @@ -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 +{ + /// + /// 自定义物流类别配置 + /// 从用户级 TOML 配置文件加载,独立于模型数据库 + /// + public class CustomCategoryConfig + { + /// + /// 配置文件路径(用户级,与模型无关) + /// + public static string ConfigFilePath + { + get + { + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "Autodesk", + "Navisworks Manage 2026", + "plugins", + "TransportPlugin", + "custom_categories.toml" + ); + } + } + + /// + /// 配置目录 + /// + public static string ConfigDirectory => Path.GetDirectoryName(ConfigFilePath); + + /// + /// 自定义类别列表 + /// + public List Categories { get; set; } = new List(); + + /// + /// 获取所有类别(包括内置和自定义) + /// + public IEnumerable GetAllCategories() + { + // 内置类别 + foreach (var builtIn in BuiltInCategories.All) + yield return builtIn; + + // 自定义类别 + foreach (var custom in Categories) + yield return custom; + } + + /// + /// 根据ID获取类别定义 + /// + 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)); + } + + /// + /// 检查是否为内置类别 + /// + public bool IsBuiltIn(string id) + { + return BuiltInCategories.GetById(id) != null; + } + + /// + /// 检查类别是否存在 + /// + public bool CategoryExists(string id) + { + return GetCategory(id) != null; + } + + /// + /// 从 TOML 文件加载配置 + /// + 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; + } + + /// + /// 保存配置到 TOML 文件 + /// + 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}"); + } + } + + /// + /// 默认配置文件路径(插件资源目录) + /// + public static string DefaultConfigFilePath + { + get + { + return Path.Combine( + Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), + "resources", + "default_custom_categories.toml" + ); + } + } + + /// + /// 创建默认配置 + /// 尝试从默认配置文件加载示例,如果失败则返回空配置 + /// + private static CustomCategoryConfig CreateDefaultConfig() + { + var config = new CustomCategoryConfig + { + Categories = new List() + }; + + 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; + } + + /// + /// 从 TOML 表解析类别定义 + /// + 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; + } + } + + /// + /// 生成 TOML 内容 + /// + 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(); + } + + /// + /// 解析颜色字符串 + /// + 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; + } + + /// + /// 颜色转十六进制字符串 + /// + private static string ColorToHex(Color color) + { + return $"#{color.R:X2}{color.G:X2}{color.B:X2}"; + } + + /// + /// 转义 TOML 字符串 + /// + private static string EscapeTomlString(string str) + { + if (string.IsNullOrEmpty(str)) + return ""; + return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r"); + } + } + + /// + /// 类别定义基类(内置和自定义共用接口) + /// + 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; } + } + + /// + /// 自定义类别定义 + /// + 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(); + } + + /// + /// 类别默认属性 + /// + public class CategoryDefaultProperties + { + public double HeightLimitMeters { get; set; } = 3.0; + public double SpeedLimitMetersPerSecond { get; set; } = 1.0; + public double WidthLimitMeters { get; set; } = 3.0; + } + + /// + /// 内置类别定义(包装 LogisticsElementType 枚举) + /// + 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; + } + } + + /// + /// 内置类别管理器 + /// + public static class BuiltInCategories + { + private static readonly Dictionary _categories; + + static BuiltInCategories() + { + _categories = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (CategoryAttributeManager.LogisticsElementType type in + Enum.GetValues(typeof(CategoryAttributeManager.LogisticsElementType))) + { + var definition = new BuiltInCategoryDefinition(type); + _categories[type.ToString()] = definition; + } + } + + /// + /// 获取所有内置类别 + /// + public static IEnumerable All => _categories.Values; + + /// + /// 根据ID获取内置类别 + /// + public static BuiltInCategoryDefinition GetById(string id) + { + _categories.TryGetValue(id ?? "", out var category); + return category; + } + + /// + /// 根据枚举类型获取定义 + /// + public static BuiltInCategoryDefinition GetByType(CategoryAttributeManager.LogisticsElementType type) + { + return GetById(type.ToString()); + } + } + + /// + /// TOML 扩展方法 + /// + 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; + } + } +} diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index 2cd1bca..b10c6f0 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -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) diff --git a/src/Core/Properties/CategoryAttributeManager.cs b/src/Core/Properties/CategoryAttributeManager.cs index bda07d7..95be22d 100644 --- a/src/Core/Properties/CategoryAttributeManager.cs +++ b/src/Core/Properties/CategoryAttributeManager.cs @@ -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; } - /// /// 检查模型项是否已有物流属性 /// @@ -284,6 +284,44 @@ namespace NavisworksTransport } } + /// + /// 获取指定物流元素类型的所有模型项(字符串版本,支持自定义类别) + /// + /// 类型ID(内置类别名称或自定义类别ID) + /// 符合条件的模型项列表 + public static List 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(); + } + } + /// /// 根据物流属性筛选模型项 /// @@ -333,6 +371,55 @@ namespace NavisworksTransport } } + /// + /// 按物流元素类型筛选模型项(字符串版本,支持自定义类别) + /// + /// 要筛选的模型项集合 + /// 类型ID(内置类别名称或自定义类别ID) + /// 符合条件的模型项集合 + 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; + } + } + /// /// 筛选可通行的模型项 /// @@ -985,6 +1072,199 @@ namespace NavisworksTransport return -1; } } + #region 自定义类别支持 + + private static Core.Config.CustomCategoryConfig _customCategoryConfig; + private static readonly object _customCategoryLock = new object(); + + /// + /// 自定义类别配置(懒加载) + /// + public static Core.Config.CustomCategoryConfig CustomCategories + { + get + { + if (_customCategoryConfig == null) + { + lock (_customCategoryLock) + { + if (_customCategoryConfig == null) + { + _customCategoryConfig = Core.Config.CustomCategoryConfig.LoadFromFile(); + } + } + } + return _customCategoryConfig; + } + } + + /// + /// 重新加载自定义类别配置 + /// + public static void ReloadCustomCategories() + { + lock (_customCategoryLock) + { + _customCategoryConfig = Core.Config.CustomCategoryConfig.LoadFromFile(); + LogManager.Info("[CategoryAttributeManager] 自定义类别配置已重新加载"); + } + } + + /// + /// 获取模型项的物流元素类型(字符串形式,支持自定义类别) + /// + /// 模型项 + /// 类别ID字符串,如果没有设置则返回"障碍物" + public static string GetCategoryId(ModelItem item) + { + try + { + string typeValue = GetLogisticsPropertyValue(item, LogisticsProperties.TYPE); + if (!string.IsNullOrEmpty(typeValue)) + { + // 先检查是否为内置类别 + if (Enum.TryParse(typeValue, out _)) + { + return typeValue; + } + + // 再检查是否为自定义类别 + if (CustomCategories.CategoryExists(typeValue)) + { + return typeValue; + } + } + } + catch (Exception ex) + { + LogManager.Error($"[CategoryAttributeManager] 获取类别ID失败: {ex.Message}"); + } + + // 默认返回障碍物 + return LogisticsElementType.障碍物.ToString(); + } + + /// + /// 获取类别定义(内置或自定义) + /// + /// 类别ID + /// 类别定义,如果不存在返回null + public static Core.Config.CategoryDefinition GetCategoryDefinition(string categoryId) + { + if (string.IsNullOrEmpty(categoryId)) + return null; + + // 先查内置 + if (Enum.TryParse(categoryId, out var builtInType)) + { + return Core.Config.BuiltInCategories.GetByType(builtInType); + } + + // 再查自定义 + return CustomCategories.GetCategory(categoryId); + } + + /// + /// 获取类别的寻路权重 + /// + /// 类别ID + /// 权重值,未知类别返回1.0 + public static double GetCategoryWeight(string categoryId) + { + var definition = GetCategoryDefinition(categoryId); + return definition?.Weight ?? 1.0; + } + + /// + /// 检查类别是否可通行 + /// + /// 类别ID + /// 是否可通行 + public static bool IsCategoryTraversable(string categoryId) + { + var definition = GetCategoryDefinition(categoryId); + return definition?.Traversable ?? false; + } + + /// + /// 获取所有可用类别(内置 + 自定义) + /// + public static IEnumerable GetAllAvailableCategories() + { + return CustomCategories.GetAllCategories(); + } + + /// + /// 检查是否为自定义类别 + /// + public static bool IsCustomCategory(string categoryId) + { + return !string.IsNullOrEmpty(categoryId) && + !Enum.TryParse(categoryId, out _); + } + + /// + /// 为选中的模型项添加物流属性(支持自定义类别) + /// + 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 + { + { 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 + { + { 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 } /// @@ -1286,5 +1566,6 @@ namespace NavisworksTransport return passableAreas; } + } -} \ No newline at end of file +} diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs index 8dd0e4d..26087b6 100644 --- a/src/PathPlanning/AutoPathFinder.cs +++ b/src/PathPlanning/AutoPathFinder.cs @@ -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; } } /// @@ -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++; diff --git a/src/PathPlanning/ChannelBasedGridBuilder.cs b/src/PathPlanning/ChannelBasedGridBuilder.cs index e161fc4..0bba1b4 100644 --- a/src/PathPlanning/ChannelBasedGridBuilder.cs +++ b/src/PathPlanning/ChannelBasedGridBuilder.cs @@ -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设置 diff --git a/src/PathPlanning/GridCellBuilder.cs b/src/PathPlanning/GridCellBuilder.cs index c9d2231..0b3e5f4 100644 --- a/src/PathPlanning/GridCellBuilder.cs +++ b/src/PathPlanning/GridCellBuilder.cs @@ -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, diff --git a/src/PathPlanning/GridMap.cs b/src/PathPlanning/GridMap.cs index ad2f315..a0ab9c0 100644 --- a/src/PathPlanning/GridMap.cs +++ b/src/PathPlanning/GridMap.cs @@ -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(), ChannelType = ChannelType.Other @@ -264,7 +264,7 @@ namespace NavisworksTransport.PathPlanning /// 遍历成本 /// 限速 /// 单元格类型 - 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(), - 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; } /// - /// 单元格类型 + /// 单元格类型(类别ID字符串,支持内置和自定义类别) /// - public CategoryAttributeManager.LogisticsElementType CellType { get; set; } + public string CellType { get; set; } /// /// 相关的模型项引用(可选) @@ -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); } /// @@ -804,7 +745,7 @@ namespace NavisworksTransport.PathPlanning { var cell = new GridCell { - CellType = CategoryAttributeManager.LogisticsElementType.障碍物, + CellType = CategoryAttributeManager.LogisticsElementType.障碍物.ToString(), IsInChannel = false, HeightLayers = new List(), // 空列表,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(), 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(), ChannelType = channelType, @@ -880,9 +821,9 @@ namespace NavisworksTransport.PathPlanning public double SpeedLimit { get; set; } /// - /// 层类型 + /// 层类型(类别ID字符串,支持自定义类别) /// - public CategoryAttributeManager.LogisticsElementType Type { get; set; } + public string Type { get; set; } /// /// 是否是边界层(用于膨胀计算) @@ -900,7 +841,7 @@ namespace NavisworksTransport.PathPlanning /// /// 构造函数 /// - 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++; } diff --git a/src/PathPlanning/GridMapCacheKey.cs b/src/PathPlanning/GridMapCacheKey.cs index 2d9aa46..417f179 100644 --- a/src/PathPlanning/GridMapCacheKey.cs +++ b/src/PathPlanning/GridMapCacheKey.cs @@ -170,7 +170,7 @@ namespace NavisworksTransport.PathPlanning } // 物流类型属性 - var logisticsType = CategoryAttributeManager.GetLogisticsElementType(item); + var logisticsType = CategoryAttributeManager.GetCategoryId(item); hashBuilder.Append($"LogType:{logisticsType}|"); // Transform信息(如果有变换) diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs index 3c7b9ea..284b2f9 100644 --- a/src/PathPlanning/GridMapGenerator.cs +++ b/src/PathPlanning/GridMapGenerator.cs @@ -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; diff --git a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs index add0a55..7386f59 100644 --- a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs +++ b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs @@ -264,16 +264,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels } /// - /// 可用类别筛选选项 - 从枚举动态获取所有类别,并添加"全部"选项 + /// 可用类别筛选选项 - 从所有可用类别动态获取,并添加"全部"选项 /// public ThreadSafeObservableCollection AvailableCategoryFilters { get; } = - new ThreadSafeObservableCollection( - new[] { "全部" }.Concat( - Enum.GetValues(typeof(CategoryAttributeManager.LogisticsElementType)) - .Cast() - .Select(e => e.ToString()) - ) - ); + new ThreadSafeObservableCollection(new[] { "全部" }); /// /// 当前选中的类别筛选条件 @@ -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(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 } + /// + /// 根据显示名称获取类别ID + /// + /// 类别显示名称 + /// 类别ID,如果未找到返回null + 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; + } + + /// + /// 根据类别ID获取显示名称 + /// + /// 类别ID + /// 类别显示名称,如果未找到返回null + 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; + } + /// /// 格式化物流属性为显示字符串 ///