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;
+ }
+
///
/// 格式化物流属性为显示字符串
///