diff --git a/TransportPlugin.csproj b/TransportPlugin.csproj
index 11427cc..fd5b58d 100644
--- a/TransportPlugin.csproj
+++ b/TransportPlugin.csproj
@@ -483,11 +483,6 @@
PreserveNewest
resources\default_config.toml
-
-
- PreserveNewest
- resources\default_custom_categories.toml
-
PreserveNewest
diff --git a/resources/default_config.toml b/resources/default_config.toml
index 39df604..96529e8 100644
--- a/resources/default_config.toml
+++ b/resources/default_config.toml
@@ -71,3 +71,13 @@ width_limit_meters = 3.0
# ZUp: 强制使用 Z-Up 坐标系(Navisworks 默认)
# YUp: 强制使用 Y-Up 坐标系(常见于 Revit 导出)
type = "AutoDetect"
+
+# 自定义物流类别
+# 用于扩展内置类别,只需填写类别名称
+#
+# 示例:
+# [[custom_category]]
+# name = "狭窄通道"
+#
+# [[custom_category]]
+# name = "临时存储区"
diff --git a/resources/default_custom_categories.toml b/resources/default_custom_categories.toml
deleted file mode 100644
index 6fd9379..0000000
--- a/resources/default_custom_categories.toml
+++ /dev/null
@@ -1,86 +0,0 @@
-# 自定义物流类别配置文件
-# 位置: %ProgramData%/Autodesk/Navisworks Manage 2026/plugins/TransportPlugin/custom_categories.toml
-# 说明: 本文件定义用户自定义的物流类别,与模型文件无关,可跨项目共享
-
-# ============================================================
-# 类别属性说明
-# ============================================================
-#
-# 【必填属性】
-# id: 唯一标识(英文,用于存储和内部识别)
-# - 不能与内置类别名称重复(如 door, elevator 等)
-# - 建议使用小写和下划线,如: "ramp", "clean_room"
-#
-# display_name: 显示名称(中文)
-# - 在UI类别下拉框和物流模型列表中显示
-#
-# 【可选属性 - 有特殊需求时才设置】
-# traversable: 是否默认可通行
-# - 默认: true(可通行)
-# - 仅当类别不可通行时设置为 false(如限制区域)
-#
-# weight: 寻路权重
-# - 默认: 1.0
-# - 值越小优先级越高,越大越难走
-# - 不可通行区域建议设为极大值如 999999.0
-#
-# 【预留属性 - 当前未使用,可不设置】
-# icon: 图标标识(默认 Box)
-# priority: 优先级 1-10(默认 5)
-# color: UI显示颜色(默认 #9E9E9E)
-# height_limit_meters: 默认高度限制米数(默认 3.0)
-# speed_limit_meters_per_second: 默认速度限制米/秒(默认 1.0)
-# width_limit_meters: 默认宽度限制米数(默认 3.0)
-#
-# ============================================================
-# 配置示例
-# ============================================================
-
-# --------------------------------------------------
-# 示例1: 最简配置(只有必填,其他用默认)
-# --------------------------------------------------
-# 坡道:默认可通行(weight=1.0),无需设置其他属性
-[[category]]
-id = "ramp"
-display_name = "坡道"
-
-# --------------------------------------------------
-# 示例2: 不可通行区域(必须设置特殊属性)
-# --------------------------------------------------
-# 限制区域:不可通行,寻路时避开
-[[category]]
-id = "restricted_area"
-display_name = "限制区域"
-traversable = false
-weight = 999999.0
-
-# --------------------------------------------------
-# 示例3: 优先选择的通道(设置更小weight)
-# --------------------------------------------------
-# 快速通道:比默认值(1.0)优先,寻路时优先走这里
-[[category]]
-id = "express_lane"
-display_name = "快速通道"
-weight = 0.6
-
-# --------------------------------------------------
-# 示例4: 较难的通行区域(设置更大weight)
-# --------------------------------------------------
-# 狭窄通道:比默认值难走,只有必要时才走
-[[category]]
-id = "narrow_passage"
-display_name = "狭窄通道"
-weight = 2.5
-
-# --------------------------------------------------
-# 示例5: 完整配置(展示所有可用属性)
-# --------------------------------------------------
-# 一般不需要这么多,按需设置即可
-[[category]]
-id = "temporary_storage"
-display_name = "临时堆放区"
-# 可通行但较慢(weight > 1.0)
-weight = 1.5
-# 预留属性(当前未使用)
-icon = "Package"
-color = "#FFC107"
diff --git a/src/Core/Config/ConfigManager.cs b/src/Core/Config/ConfigManager.cs
index 02b8e6f..a89a216 100644
--- a/src/Core/Config/ConfigManager.cs
+++ b/src/Core/Config/ConfigManager.cs
@@ -488,6 +488,28 @@ namespace NavisworksTransport.Core.Config
}
}
+ // 加载自定义类别配置(可选)
+ if (model.ContainsKey("custom_category"))
+ {
+ var categoriesArray = model["custom_category"] as TomlTableArray;
+ if (categoriesArray != null)
+ {
+ foreach (var categoryTable in categoriesArray)
+ {
+ var name = GetStringValueWithDefault(categoryTable, "name", null, missingItems);
+
+ if (!string.IsNullOrEmpty(name))
+ {
+ config.CustomCategories.Add(new CustomCategoryConfigItem
+ {
+ Name = name
+ });
+ }
+ }
+ LogManager.Info($"[ConfigManager] 已加载 {config.CustomCategories.Count} 个自定义类别");
+ }
+ }
+
// 如果有缺失项,记录警告并设置标志
if (missingItems.Count > 0)
{
@@ -513,7 +535,8 @@ namespace NavisworksTransport.Core.Config
Visualization = new VisualizationConfig(),
Animation = new AnimationConfig(),
Logistics = new LogisticsConfig(),
- CoordinateSystem = new CoordinateSystemConfig()
+ CoordinateSystem = new CoordinateSystemConfig(),
+ CustomCategories = new List()
};
}
@@ -597,7 +620,24 @@ namespace NavisworksTransport.Core.Config
ApplyConfigValuesToModel(model, config);
// 转换回 TOML 字符串
- return Toml.FromModel(model);
+ var tomlContent = Toml.FromModel(model);
+
+ // 追加自定义类别配置
+ if (config.CustomCategories != null && config.CustomCategories.Count > 0)
+ {
+ var sb = new System.Text.StringBuilder(tomlContent);
+ sb.AppendLine();
+ sb.AppendLine("# 自定义物流类别(只需填写名称)");
+ foreach (var category in config.CustomCategories)
+ {
+ sb.AppendLine("[[custom_category]]");
+ sb.AppendLine($"name = \"{EscapeTomlString(category.Name)}\"");
+ sb.AppendLine();
+ }
+ tomlContent = sb.ToString();
+ }
+
+ return tomlContent;
}
///
@@ -669,6 +709,10 @@ namespace NavisworksTransport.Core.Config
coordSys["type"] = config.CoordinateSystem.Type;
}
}
+
+ // 更新自定义类别配置
+ // 注意:由于Tomlyn对数组的处理限制,自定义类别通过专门的API管理
+ // 这里不直接操作TOML模型,而是在保存前通过模板合并
}
///
@@ -679,6 +723,52 @@ namespace NavisworksTransport.Core.Config
ReloadInternal(isExternal: false);
}
+ ///
+ /// 从内容重新加载配置(用于编辑器保存后不触发文件监控)
+ ///
+ public void ReloadFromContent(string tomlContent, bool notifyChange = true)
+ {
+ try
+ {
+ // 先临时禁用文件监控,避免写入时触发外部修改事件
+ var watcherWasEnabled = _fileWatcher?.EnableRaisingEvents ?? false;
+ if (_fileWatcher != null)
+ {
+ _fileWatcher.EnableRaisingEvents = false;
+ }
+
+ try
+ {
+ // 写入文件(此时 FileSystemWatcher 已禁用,不会触发外部修改事件)
+ File.WriteAllText(ConfigFilePath, tomlContent);
+
+ // 解析并更新配置
+ _currentConfig = ParseTomlToConfig(tomlContent);
+
+ LogManager.Info("配置已从内容重新加载");
+
+ // 触发配置变更事件
+ if (notifyChange)
+ {
+ NotifyConfigurationChanged(ConfigCategory.All, isExternal: false);
+ }
+ }
+ finally
+ {
+ // 恢复文件监控
+ if (_fileWatcher != null && watcherWasEnabled)
+ {
+ _fileWatcher.EnableRaisingEvents = true;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"从内容重新加载配置失败: {ex.Message}");
+ throw;
+ }
+ }
+
///
/// 内部重新加载方法
///
@@ -994,6 +1084,16 @@ namespace NavisworksTransport.Core.Config
return strValue;
}
+ ///
+ /// 转义 TOML 字符串
+ ///
+ private string EscapeTomlString(string str)
+ {
+ if (string.IsNullOrEmpty(str))
+ return "";
+ return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
+ }
+
#endregion
}
}
diff --git a/src/Core/Config/CustomCategoryConfig.cs b/src/Core/Config/CustomCategoryConfig.cs
index 9919928..b2495dd 100644
--- a/src/Core/Config/CustomCategoryConfig.cs
+++ b/src/Core/Config/CustomCategoryConfig.cs
@@ -1,47 +1,16 @@
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 配置文件加载,独立于模型数据库
+ /// 自定义物流类别配置管理器
+ /// 从 ConfigManager 获取配置,不再使用独立的配置文件
///
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();
-
///
/// 获取所有类别(包括内置和自定义)
///
@@ -51,27 +20,27 @@ namespace NavisworksTransport.Core.Config
foreach (var builtIn in BuiltInCategories.All)
yield return builtIn;
- // 自定义类别
- foreach (var custom in Categories)
+ // 自定义类别(从 ConfigManager 获取)
+ foreach (var custom in GetCustomCategories())
yield return custom;
}
///
- /// 根据ID获取类别定义
+ /// 根据名称获取类别定义
///
- public CategoryDefinition GetCategory(string id)
+ public CategoryDefinition GetCategory(string name)
{
- if (string.IsNullOrEmpty(id))
+ if (string.IsNullOrEmpty(name))
return null;
// 先查内置
- var builtIn = BuiltInCategories.GetById(id);
+ var builtIn = BuiltInCategories.GetById(name);
if (builtIn != null)
return builtIn;
- // 再查自定义
- return Categories.FirstOrDefault(c =>
- c.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
+ // 再查自定义(按名称匹配)
+ return GetCustomCategories().FirstOrDefault(c =>
+ c.DisplayName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
///
@@ -91,310 +60,88 @@ namespace NavisworksTransport.Core.Config
}
///
- /// 从 TOML 文件加载配置
+ /// 添加自定义类别
///
- public static CustomCategoryConfig LoadFromFile()
+ public void AddCategory(string name)
{
- var config = new CustomCategoryConfig();
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException("类别名称不能为空");
- try
+ if (CategoryExists(name))
+ throw new InvalidOperationException($"类别 '{name}' 已存在");
+
+ var config = ConfigManager.Instance.Current;
+ config.CustomCategories.Add(new CustomCategoryConfigItem
{
- 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;
+ Name = name
+ });
+
+ ConfigManager.Instance.SaveConfig(config);
+ LogManager.Info($"[CustomCategoryConfig] 已添加自定义类别: {name}");
}
///
- /// 保存配置到 TOML 文件
+ /// 删除自定义类别
///
- public void SaveToFile()
+ public void RemoveCategory(string name)
{
- try
+ if (IsBuiltIn(name))
+ throw new InvalidOperationException("不能删除内置类别");
+
+ var config = ConfigManager.Instance.Current;
+ var category = config.CustomCategories.FirstOrDefault(c =>
+ c.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+
+ if (category != null)
{
- Directory.CreateDirectory(ConfigDirectory);
- var tomlContent = GenerateTomlContent();
- File.WriteAllText(ConfigFilePath, tomlContent);
- LogManager.Info($"[CustomCategoryConfig] 配置已保存: {ConfigFilePath}");
- }
- catch (Exception ex)
- {
- LogManager.Error($"[CustomCategoryConfig] 保存配置失败: {ex.Message}");
+ config.CustomCategories.Remove(category);
+ ConfigManager.Instance.SaveConfig(config);
+ LogManager.Info($"[CustomCategoryConfig] 已删除自定义类别: {name}");
}
}
///
- /// 默认配置文件路径(插件资源目录)
+ /// 从 ConfigManager 获取自定义类别列表
///
- public static string DefaultConfigFilePath
+ private IEnumerable GetCustomCategories()
{
- get
- {
- return Path.Combine(
- Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
- "resources",
- "default_custom_categories.toml"
- );
- }
- }
+ var config = ConfigManager.Instance.Current;
+ if (config.CustomCategories == null)
+ yield break;
- ///
- /// 创建默认配置
- /// 尝试从默认配置文件加载示例,如果失败则返回空配置
- ///
- private static CustomCategoryConfig CreateDefaultConfig()
- {
- var config = new CustomCategoryConfig
+ foreach (var item in config.CustomCategories)
{
- Categories = new List()
- };
-
- try
- {
- // 尝试从默认配置文件加载示例
- if (File.Exists(DefaultConfigFilePath))
+ yield return new CustomCategoryDefinition
{
- 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)
- }
+ Id = item.Name, // 名称同时作为ID
+ DisplayName = item.Name
};
-
- // 验证必填字段
- 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("# 类别属性说明");
- sb.AppendLine("# ============================================================");
- sb.AppendLine("#");
- sb.AppendLine("# 【必填】");
- sb.AppendLine("# id: 唯一标识(英文)");
- sb.AppendLine("# display_name: 显示名称(中文)");
- sb.AppendLine("#");
- sb.AppendLine("# 【可选 - 有特殊需求才设置】");
- sb.AppendLine("# traversable: 是否可通行(默认 true)");
- sb.AppendLine("# weight: 寻路权重(默认 1.0)");
- sb.AppendLine("#");
- sb.AppendLine("# 【预留 - 当前未使用】");
- sb.AppendLine("# icon, priority, color, height_limit_meters 等");
- sb.AppendLine("#");
- sb.AppendLine();
-
- foreach (var category in Categories)
- {
- sb.AppendLine("[[category]]");
- sb.AppendLine($"id = \"{EscapeTomlString(category.Id)}\"");
- sb.AppendLine($"display_name = \"{EscapeTomlString(category.DisplayName)}\"");
-
- // 只输出非默认值的属性
- if (category.Traversable != true)
- sb.AppendLine($"traversable = {category.Traversable.ToString().ToLower()}");
- if (category.Weight != 1.0)
- sb.AppendLine($"weight = {category.Weight: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
{
@@ -407,134 +154,8 @@ namespace NavisworksTransport.Core.Config
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.空洞:
- 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.空洞:
- 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;
- }
}
///
@@ -579,44 +200,4 @@ namespace NavisworksTransport.Core.Config
}
}
- ///
- /// 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/Config/SystemConfig.cs b/src/Core/Config/SystemConfig.cs
index 6121f79..c45e4e2 100644
--- a/src/Core/Config/SystemConfig.cs
+++ b/src/Core/Config/SystemConfig.cs
@@ -1,4 +1,6 @@
+using System.Collections.Generic;
+
namespace NavisworksTransport.Core.Config
{
///
@@ -40,6 +42,22 @@ namespace NavisworksTransport.Core.Config
/// 坐标系配置
///
public CoordinateSystemConfig CoordinateSystem { get; set; }
+
+ ///
+ /// 自定义类别列表
+ ///
+ public List CustomCategories { get; set; } = new List();
+ }
+
+ ///
+ /// 自定义类别配置项(极简版,只用名称作为标识)
+ ///
+ public class CustomCategoryConfigItem
+ {
+ ///
+ /// 类别名称(同时作为显示名称和唯一标识)
+ ///
+ public string Name { get; set; }
}
///
diff --git a/src/Core/Properties/CategoryAttributeManager.cs b/src/Core/Properties/CategoryAttributeManager.cs
index 4dd08fe..c8b88ec 100644
--- a/src/Core/Properties/CategoryAttributeManager.cs
+++ b/src/Core/Properties/CategoryAttributeManager.cs
@@ -1090,7 +1090,7 @@ namespace NavisworksTransport
{
if (_customCategoryConfig == null)
{
- _customCategoryConfig = Core.Config.CustomCategoryConfig.LoadFromFile();
+ _customCategoryConfig = new Core.Config.CustomCategoryConfig();
}
}
}
@@ -1105,7 +1105,8 @@ namespace NavisworksTransport
{
lock (_customCategoryLock)
{
- _customCategoryConfig = Core.Config.CustomCategoryConfig.LoadFromFile();
+ // 重新创建实例,从 ConfigManager 获取最新数据
+ _customCategoryConfig = new Core.Config.CustomCategoryConfig();
LogManager.Info("[CategoryAttributeManager] 自定义类别配置已重新加载");
}
}
@@ -1171,8 +1172,14 @@ namespace NavisworksTransport
/// 权重值,未知类别返回1.0
public static double GetCategoryWeight(string categoryId)
{
- var definition = GetCategoryDefinition(categoryId);
- return definition?.Weight ?? 1.0;
+ // 简化为返回默认值,后续可根据需要扩展
+ // 障碍物类别返回最大权重
+ if (categoryId?.Equals("障碍物", StringComparison.OrdinalIgnoreCase) == true ||
+ categoryId?.Equals("空洞", StringComparison.OrdinalIgnoreCase) == true)
+ {
+ return double.MaxValue;
+ }
+ return 1.0;
}
///
@@ -1182,8 +1189,14 @@ namespace NavisworksTransport
/// 是否可通行
public static bool IsCategoryTraversable(string categoryId)
{
- var definition = GetCategoryDefinition(categoryId);
- return definition?.Traversable ?? false;
+ // 简化为根据类别名称判断
+ if (categoryId?.Equals("障碍物", StringComparison.OrdinalIgnoreCase) == true ||
+ categoryId?.Equals("空洞", StringComparison.OrdinalIgnoreCase) == true ||
+ categoryId?.Equals("无关项", StringComparison.OrdinalIgnoreCase) == true)
+ {
+ return false;
+ }
+ return true;
}
///
diff --git a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs
index a21cb70..f9a3f35 100644
--- a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs
+++ b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs
@@ -264,10 +264,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
///
- /// 可用类别筛选选项 - 从所有可用类别动态获取,并添加"全部"选项
+ /// 可用类别筛选选项 - 从所有可用类别动态获取
+ /// 注意:不包含"全部","全部"是UI层面的特殊选项
///
public ThreadSafeObservableCollection AvailableCategoryFilters { get; } =
- new ThreadSafeObservableCollection(new[] { "全部" });
+ new ThreadSafeObservableCollection();
///
/// 当前选中的类别筛选条件
@@ -542,6 +543,25 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
});
}
+
+ // 处理自定义类别变更 - 刷新可用类别列表
+ if (category == ConfigCategory.All)
+ {
+ LogManager.Info("收到配置变更通知,正在更新可用类别列表...");
+
+ SafeExecute(() =>
+ {
+ // 重新加载自定义类别
+ CategoryAttributeManager.ReloadCustomCategories();
+
+ // 刷新可用类别列表
+ RefreshAvailableCategories();
+
+ string source = eventArgs.IsExternalChange ? "外部修改" : "内部更新";
+ UpdateMainStatus($"类别列表已更新 ({source})");
+ LogManager.Info($"可用类别列表已更新 - 来源: {source}, 当前类别数: {AvailableCategories.Count}");
+ });
+ }
}
catch (Exception ex)
{
@@ -549,6 +569,58 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
+ ///
+ /// 刷新可用类别列表
+ ///
+ private void RefreshAvailableCategories()
+ {
+ _uiStateManager.ExecuteUIUpdateAsync(() =>
+ {
+ // 保存当前选中的类别
+ string previousSelectedCategory = SelectedCategory;
+ string previousSelectedFilter = SelectedCategoryFilter;
+
+ // 清空集合
+ AvailableCategories.Clear();
+ AvailableCategoryFilters.Clear();
+
+ // 先添加"全部"选项到筛选器
+ AvailableCategoryFilters.Add("全部");
+
+ // 从 CategoryAttributeManager 获取所有可用类别(内置 + 自定义)
+ var allCategories = CategoryAttributeManager.GetAllAvailableCategories().ToList();
+
+ foreach (var cat in allCategories)
+ {
+ AvailableCategories.Add(cat.DisplayName);
+ AvailableCategoryFilters.Add(cat.DisplayName);
+ }
+
+ // 恢复之前的选择(如果仍然存在)
+ if (!string.IsNullOrEmpty(previousSelectedCategory) &&
+ AvailableCategories.Contains(previousSelectedCategory))
+ {
+ SelectedCategory = previousSelectedCategory;
+ }
+ else if (AvailableCategories.Count > 0)
+ {
+ // 默认选择"通道"或第一个类别
+ SelectedCategory = AvailableCategories.Contains("通道") ? "通道" : AvailableCategories[0];
+ }
+
+ // 恢复筛选器选择
+ if (!string.IsNullOrEmpty(previousSelectedFilter) &&
+ AvailableCategoryFilters.Contains(previousSelectedFilter))
+ {
+ SelectedCategoryFilter = previousSelectedFilter;
+ }
+ else
+ {
+ SelectedCategoryFilter = "全部";
+ }
+ }).ConfigureAwait(false);
+ }
+
#endregion
#region 命令初始化 - 统一的Command Pattern
@@ -1302,6 +1374,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 初始化物流类别(包括内置和自定义类别)
AvailableCategories.Clear();
AvailableCategoryFilters.Clear();
+
+ // 先添加"全部"选项到筛选器
AvailableCategoryFilters.Add("全部");
// 从 CategoryAttributeManager 获取所有可用类别(内置 + 自定义)
@@ -1321,6 +1395,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
SelectedCategory = AvailableCategories[0];
}
+
+ // 设置默认筛选器为"全部"
+ SelectedCategoryFilter = "全部";
});
// 注意:不在初始化时刷新选择状态和物流模型列表
diff --git a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs
index 06b6711..43c3a49 100644
--- a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs
+++ b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs
@@ -658,22 +658,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
try
{
- // 创建并显示配置编辑器对话框
- var configEditorDialog = new NavisworksTransport.UI.WPF.Views.ConfigEditorDialog();
-
- // 设置窗口所有者并显示对话框
- DialogHelper.SetOwnerSafely(configEditorDialog);
- bool? result = configEditorDialog.ShowDialog();
-
- if (result == true)
+ // 使用单例模式显示配置编辑器(非模态,避免独占焦点)
+ // 不传递 owner,让 ShowEditor 自己通过 DialogHelper 处理
+ var dialog = NavisworksTransport.UI.WPF.Views.ConfigEditorDialog.ShowEditor();
+
+ if (dialog != null)
{
- UpdateMainStatus("配置已更新");
- LogManager.Info("配置编辑器:用户保存了配置");
+ UpdateMainStatus("配置编辑器已打开");
+ LogManager.Info("配置编辑器:已打开(非模态)");
}
else
{
- UpdateMainStatus("配置编辑已取消");
- LogManager.Info("配置编辑器:用户取消了修改");
+ UpdateMainStatus("配置编辑器已在运行");
+ LogManager.Info("配置编辑器:窗口已存在并已激活");
}
}
catch (Exception ex)
diff --git a/src/UI/WPF/Views/ConfigEditorDialog.xaml b/src/UI/WPF/Views/ConfigEditorDialog.xaml
index 5c582f7..135a79a 100644
--- a/src/UI/WPF/Views/ConfigEditorDialog.xaml
+++ b/src/UI/WPF/Views/ConfigEditorDialog.xaml
@@ -20,8 +20,7 @@ NavisworksTransport 配置编辑器对话框 - 采用与主界面一致的Navisw
Height="700"
Width="900"
ResizeMode="CanResize"
- WindowStartupLocation="CenterOwner"
- Topmost="True"
+ WindowStartupLocation="CenterScreen"
MinHeight="500"
MinWidth="700">
@@ -82,11 +81,6 @@ NavisworksTransport 配置编辑器对话框 - 采用与主界面一致的Navisw
-
public partial class ConfigEditorDialog : Window
{
private string _originalContent;
private bool _isModified;
- public ConfigEditorDialog()
+ ///
+ /// 当前显示的编辑器实例(单例模式)
+ ///
+ private static ConfigEditorDialog _currentInstance = null;
+
+ ///
+ /// 用于线程同步的锁对象
+ ///
+ private static readonly object _lock = new object();
+
+ ///
+ /// 私有构造函数,防止外部直接创建
+ ///
+ private ConfigEditorDialog()
{
InitializeComponent();
LoadConfigContent();
@@ -94,7 +107,6 @@ namespace NavisworksTransport.UI.WPF.Views
{
try
{
- string configPath = ConfigManager.ConfigFilePath;
string content = ConfigContentTextBox.Text;
// 验证 TOML 格式
@@ -109,31 +121,26 @@ namespace NavisworksTransport.UI.WPF.Views
return false;
}
- // 确保目录存在
- Directory.CreateDirectory(ConfigManager.ConfigDirectory);
-
- // 保存文件
- File.WriteAllText(configPath, content, System.Text.Encoding.UTF8);
-
- // 重新加载配置
- ConfigManager.Instance.Reload();
+ // 使用 ConfigManager 的 ReloadFromContent 方法保存并加载
+ // 这个方法会临时禁用 FileSystemWatcher,避免重复触发
+ ConfigManager.Instance.ReloadFromContent(content);
_originalContent = content;
_isModified = false;
ModifiedLabel.Content = "";
- StatusLabel.Content = "配置已保存";
+ StatusLabel.Content = "配置已应用";
StatusLabel.Foreground = System.Windows.Media.Brushes.Green;
UpdateStatus();
- LogManager.Info("配置文件已保存");
+ LogManager.Info("配置已应用");
return true;
}
catch (Exception ex)
{
- LogManager.Error($"保存配置失败: {ex.Message}");
- MessageBox.Show($"保存配置失败:\n{ex.Message}",
+ LogManager.Error($"应用配置失败: {ex.Message}");
+ MessageBox.Show($"应用配置失败:\n{ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
@@ -141,11 +148,6 @@ namespace NavisworksTransport.UI.WPF.Views
// 事件处理器
- private void SaveButton_Click(object sender, RoutedEventArgs e)
- {
- SaveConfig();
- }
-
private void ReloadButton_Click(object sender, RoutedEventArgs e)
{
if (_isModified)
@@ -210,6 +212,9 @@ namespace NavisworksTransport.UI.WPF.Views
}
}
+ ///
+ /// 应用配置(保存但不关闭窗口)
+ ///
private void ApplyButton_Click(object sender, RoutedEventArgs e)
{
SaveConfig();
@@ -221,13 +226,11 @@ namespace NavisworksTransport.UI.WPF.Views
{
if (SaveConfig())
{
- DialogResult = true;
Close();
}
}
else
{
- DialogResult = true;
Close();
}
}
@@ -244,7 +247,6 @@ namespace NavisworksTransport.UI.WPF.Views
return;
}
- DialogResult = false;
Close();
}
@@ -266,5 +268,145 @@ namespace NavisworksTransport.UI.WPF.Views
UpdateStatus();
}
}
+
+ ///
+ /// 窗口关闭事件
+ ///
+ protected override void OnClosed(EventArgs e)
+ {
+ try
+ {
+ // 清除静态实例引用
+ lock (_lock)
+ {
+ if (_currentInstance == this)
+ {
+ _currentInstance = null;
+ LogManager.Info("配置编辑器对话框实例已从单例缓存中移除");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"清理配置编辑器对话框资源失败: {ex.Message}");
+ }
+ finally
+ {
+ base.OnClosed(e);
+ }
+ }
+
+ ///
+ /// 窗口加载事件 - 处理激活逻辑避免独占焦点
+ ///
+ protected override void OnSourceInitialized(EventArgs e)
+ {
+ base.OnSourceInitialized(e);
+
+ try
+ {
+ // 激活并前置窗口,但不独占焦点
+ // 先置顶再取消置顶,让窗口前置但不系统级置顶
+ this.Activate();
+ this.Topmost = true;
+ this.Topmost = false;
+
+ LogManager.Debug("配置编辑器对话框已激活并前置显示");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"配置编辑器对话框初始化失败: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 激活并显示已存在的窗口
+ ///
+ private void ActivateWindow()
+ {
+ try
+ {
+ // 如果窗口被最小化,则恢复
+ if (this.WindowState == WindowState.Minimized)
+ {
+ this.WindowState = WindowState.Normal;
+ }
+
+ // 激活并前置窗口(先置顶再取消置顶,避免独占焦点)
+ this.Activate();
+ this.Topmost = true;
+ this.Topmost = false;
+
+ LogManager.Info("配置编辑器对话框已激活并前置显示");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"激活配置编辑器对话框失败: {ex.Message}");
+ }
+ }
+
+ #region 静态工厂方法
+
+ ///
+ /// 创建并显示配置编辑器对话框(单例模式,非模态)
+ ///
+ /// 对话框实例
+ public static ConfigEditorDialog ShowEditor()
+ {
+ try
+ {
+ lock (_lock)
+ {
+ // 如果当前已有实例且仍然打开,则激活窗口
+ if (_currentInstance != null)
+ {
+ try
+ {
+ // 检查窗口是否仍然有效
+ if (_currentInstance.IsLoaded && _currentInstance.IsVisible)
+ {
+ LogManager.Info("检测到已存在的配置编辑器窗口,激活窗口");
+ _currentInstance.ActivateWindow();
+ return _currentInstance;
+ }
+ else
+ {
+ // 窗口已关闭,清除引用
+ _currentInstance = null;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Warning($"检查现有窗口状态时出现异常: {ex.Message},将创建新窗口");
+ _currentInstance = null;
+ }
+ }
+
+ // 创建新的对话框实例
+ var dialog = new ConfigEditorDialog();
+
+ // 使用 DialogHelper 安全地设置 owner
+ NavisworksTransport.Utils.DialogHelper.SetOwnerSafely(dialog);
+
+ // 设置为当前实例
+ _currentInstance = dialog;
+
+ // 使用 Show() 非模态显示,避免独占焦点
+ dialog.Show();
+
+ LogManager.Info("新的配置编辑器对话框已显示(非模态)");
+ return dialog;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"显示配置编辑器对话框失败: {ex.Message}");
+ MessageBox.Show($"显示配置编辑器对话框失败:\n{ex.Message}", "错误",
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ return null;
+ }
+ }
+
+ #endregion
}
}