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 } }