1000 lines
36 KiB
C#
1000 lines
36 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using Tomlyn;
|
||
using Tomlyn.Model;
|
||
|
||
namespace NavisworksTransport.Core.Config
|
||
{
|
||
/// <summary>
|
||
/// 配置变更类别
|
||
/// </summary>
|
||
public enum ConfigCategory
|
||
{
|
||
None = 0,
|
||
PathEditing = 1,
|
||
Visualization = 2,
|
||
Animation = 3,
|
||
Logistics = 4,
|
||
CoordinateSystem = 5,
|
||
GridGeneration = 6,
|
||
PathPlanning = 7,
|
||
All = 99
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置变更事件参数
|
||
/// </summary>
|
||
public class ConfigurationChangedEventArgs : EventArgs
|
||
{
|
||
/// <summary>
|
||
/// 变更的配置类别
|
||
/// </summary>
|
||
public ConfigCategory ChangedCategory { get; set; }
|
||
|
||
/// <summary>
|
||
/// 是否是外部文件修改触发的变更
|
||
/// </summary>
|
||
public bool IsExternalChange { get; set; }
|
||
|
||
/// <summary>
|
||
/// 变更时间戳
|
||
/// </summary>
|
||
public DateTime ChangeTime { get; set; }
|
||
|
||
public ConfigurationChangedEventArgs(ConfigCategory category, bool isExternal = false)
|
||
{
|
||
ChangedCategory = category;
|
||
IsExternalChange = isExternal;
|
||
ChangeTime = DateTime.Now;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 系统配置管理器
|
||
/// 负责 TOML 配置文件的读取、保存和管理
|
||
/// 支持配置变更通知机制
|
||
/// </summary>
|
||
public class ConfigManager : IDisposable
|
||
{
|
||
private static ConfigManager _instance;
|
||
private static readonly object _lock = new object();
|
||
|
||
/// <summary>
|
||
/// 单例实例
|
||
/// </summary>
|
||
public static ConfigManager Instance
|
||
{
|
||
get
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
lock (_lock)
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
_instance = new ConfigManager();
|
||
}
|
||
}
|
||
}
|
||
return _instance;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置变更事件
|
||
/// 订阅此事件以在配置发生变化时接收通知
|
||
/// </summary>
|
||
public event EventHandler<ConfigurationChangedEventArgs> ConfigurationChanged;
|
||
|
||
/// <summary>
|
||
/// 特定类别配置变更事件
|
||
/// 参数:发送者,(类别,事件参数)
|
||
/// </summary>
|
||
public event EventHandler<Tuple<ConfigCategory, ConfigurationChangedEventArgs>> CategoryConfigurationChanged;
|
||
|
||
/// <summary>
|
||
/// 配置文件路径
|
||
/// </summary>
|
||
public static string ConfigFilePath
|
||
{
|
||
get
|
||
{
|
||
return Path.Combine(
|
||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||
"Autodesk",
|
||
"Navisworks Manage 2026",
|
||
"plugins",
|
||
"TransportPlugin",
|
||
"config.toml"
|
||
);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置目录
|
||
/// </summary>
|
||
public static string ConfigDirectory
|
||
{
|
||
get { return Path.GetDirectoryName(ConfigFilePath); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置模板文件路径
|
||
/// </summary>
|
||
private static string ConfigTemplatePath
|
||
{
|
||
get
|
||
{
|
||
// 从插件当前目录获取模板文件
|
||
var pluginDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||
return Path.Combine(pluginDirectory, "resources", "default_config.toml");
|
||
}
|
||
}
|
||
|
||
private SystemConfig _currentConfig;
|
||
private string _cachedTemplateContent;
|
||
private FileSystemWatcher _fileWatcher;
|
||
private bool _isDisposed;
|
||
private bool _isReloading;
|
||
private DateTime _lastWriteTime;
|
||
private readonly object _reloadLock = new object();
|
||
|
||
// 配置兼容性检查相关
|
||
private bool _hasMissingConfigItems = false;
|
||
private List<string> _missingConfigItems = new List<string>();
|
||
|
||
/// <summary>
|
||
/// 是否存在缺失的配置项(配置文件需要升级)
|
||
/// </summary>
|
||
public bool HasMissingConfigItems => _hasMissingConfigItems;
|
||
|
||
/// <summary>
|
||
/// 缺失的配置项列表
|
||
/// </summary>
|
||
public IReadOnlyList<string> MissingConfigItems => _missingConfigItems.AsReadOnly();
|
||
|
||
/// <summary>
|
||
/// 获取配置兼容性提示信息
|
||
/// </summary>
|
||
public string GetConfigCompatibilityMessage()
|
||
{
|
||
if (!_hasMissingConfigItems || _missingConfigItems.Count == 0)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var message = $"配置文件需要升级:检测到 {_missingConfigItems.Count} 个缺失项,已使用默认值填充。\n\n";
|
||
message += "缺失项:\n";
|
||
foreach (var item in _missingConfigItems.Take(10))
|
||
{
|
||
message += $" - {item}\n";
|
||
}
|
||
if (_missingConfigItems.Count > 10)
|
||
{
|
||
message += $" ... 还有 {_missingConfigItems.Count - 10} 项\n";
|
||
}
|
||
message += "\n建议:请打开【系统管理】→【参数设置】,载入默认配置以升级配置文件。";
|
||
return message;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除配置兼容性警告标志
|
||
/// </summary>
|
||
public void ClearConfigCompatibilityWarning()
|
||
{
|
||
_hasMissingConfigItems = false;
|
||
_missingConfigItems.Clear();
|
||
LogManager.Info("配置兼容性警告已清除");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否启用文件监控(自动检测外部配置修改)
|
||
/// 默认启用
|
||
/// </summary>
|
||
public bool EnableFileWatcher { get; set; } = true;
|
||
|
||
/// <summary>
|
||
/// 文件监控防抖时间(毫秒)
|
||
/// 防止文件保存时多次触发事件
|
||
/// </summary>
|
||
public int FileWatcherDebounceMs { get; set; } = 500;
|
||
|
||
private ConfigManager()
|
||
{
|
||
_currentConfig = LoadConfigFile();
|
||
InitializeFileWatcher();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化文件监控
|
||
/// </summary>
|
||
private void InitializeFileWatcher()
|
||
{
|
||
try
|
||
{
|
||
if (!EnableFileWatcher)
|
||
{
|
||
LogManager.Debug("配置文件监控已禁用");
|
||
return;
|
||
}
|
||
|
||
string configDir = ConfigDirectory;
|
||
if (!Directory.Exists(configDir))
|
||
{
|
||
Directory.CreateDirectory(configDir);
|
||
}
|
||
|
||
_fileWatcher = new FileSystemWatcher(configDir, "config.toml")
|
||
{
|
||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
|
||
EnableRaisingEvents = true
|
||
};
|
||
|
||
_fileWatcher.Changed += OnConfigFileChanged;
|
||
_fileWatcher.Renamed += OnConfigFileRenamed;
|
||
|
||
LogManager.Info($"配置文件监控已启动: {ConfigFilePath}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"初始化配置文件监控失败: {ex.Message}");
|
||
// 不影响主功能,继续运行
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置文件变化处理
|
||
/// </summary>
|
||
private void OnConfigFileChanged(object sender, FileSystemEventArgs e)
|
||
{
|
||
if (_isReloading || e.ChangeType != WatcherChangeTypes.Changed)
|
||
return;
|
||
|
||
lock (_reloadLock)
|
||
{
|
||
try
|
||
{
|
||
// 防抖处理:检查文件最后写入时间
|
||
var fileInfo = new FileInfo(e.FullPath);
|
||
if ((DateTime.Now - fileInfo.LastWriteTime).TotalMilliseconds > FileWatcherDebounceMs)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 避免重复加载
|
||
if ((DateTime.Now - _lastWriteTime).TotalMilliseconds < FileWatcherDebounceMs)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_isReloading = true;
|
||
_lastWriteTime = DateTime.Now;
|
||
|
||
LogManager.Info("检测到配置文件外部修改,正在重新加载...");
|
||
|
||
// 延迟一点执行,确保文件写入完成
|
||
System.Threading.Thread.Sleep(100);
|
||
|
||
// 重新加载配置
|
||
ReloadInternal(isExternal: true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"处理配置文件变更失败: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
_isReloading = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置文件重命名处理
|
||
/// </summary>
|
||
private void OnConfigFileRenamed(object sender, RenamedEventArgs e)
|
||
{
|
||
if (e.Name == "config.toml")
|
||
{
|
||
LogManager.Info("配置文件被重命名/恢复,正在重新加载...");
|
||
ReloadInternal(isExternal: true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前配置
|
||
/// </summary>
|
||
public SystemConfig Current
|
||
{
|
||
get { return _currentConfig; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取配置模板文件内容
|
||
/// </summary>
|
||
private string ReadConfigTemplate()
|
||
{
|
||
if (!File.Exists(ConfigTemplatePath))
|
||
{
|
||
throw new FileNotFoundException($"配置模板文件不存在: {ConfigTemplatePath}");
|
||
}
|
||
|
||
LogManager.Info($"使用配置模板文件: {ConfigTemplatePath}");
|
||
return File.ReadAllText(ConfigTemplatePath);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载配置文件,如果不存在则从模板创建
|
||
/// </summary>
|
||
private SystemConfig LoadConfigFile()
|
||
{
|
||
try
|
||
{
|
||
if (!File.Exists(ConfigFilePath))
|
||
{
|
||
LogManager.Info($"配置文件不存在,从模板创建: {ConfigFilePath}");
|
||
var templateContent = ReadConfigTemplate();
|
||
WriteConfigFileDirect(templateContent);
|
||
return ParseTomlToConfig(templateContent);
|
||
}
|
||
|
||
LogManager.Info($"加载配置文件: {ConfigFilePath}");
|
||
var tomlContent = File.ReadAllText(ConfigFilePath);
|
||
var config = ParseTomlToConfig(tomlContent);
|
||
|
||
LogManager.Info("配置文件加载成功");
|
||
return config;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"加载配置文件失败: {ex.Message}");
|
||
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
|
||
|
||
// 加载失败时使用模板中的默认配置
|
||
LogManager.Info("使用模板中的默认配置");
|
||
var templateContent = ReadConfigTemplate();
|
||
return ParseTomlToConfig(templateContent);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将 TOML 字符串解析为配置对象(兼容旧版本配置,使用默认值填充缺失项)
|
||
/// </summary>
|
||
private SystemConfig ParseTomlToConfig(string tomlContent)
|
||
{
|
||
var model = Toml.ToModel(tomlContent);
|
||
var config = CreateEmptyConfigObject();
|
||
var missingItems = new List<string>();
|
||
|
||
// 加载路径编辑配置
|
||
if (!model.ContainsKey("path_editing"))
|
||
{
|
||
missingItems.Add("[path_editing]");
|
||
LogManager.Warning("配置文件缺少 [path_editing] 部分,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
var pathEdit = model["path_editing"] as TomlTable;
|
||
if (pathEdit == null)
|
||
{
|
||
missingItems.Add("[path_editing] (格式错误)");
|
||
LogManager.Warning("[path_editing] 部分格式错误,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
// 使用带默认值的读取方法,不抛出异常
|
||
config.PathEditing.CellSizeMeters = GetDoubleValueWithDefault(pathEdit, "cell_size_meters", 0.5, missingItems);
|
||
config.PathEditing.MaxHeightDiffMeters = GetDoubleValueWithDefault(pathEdit, "max_height_diff_meters", 0.35, missingItems);
|
||
config.PathEditing.ObjectLengthMeters = GetDoubleValueWithDefault(pathEdit, "object_length_meters", 1.5, missingItems);
|
||
config.PathEditing.ObjectWidthMeters = GetDoubleValueWithDefault(pathEdit, "object_width_meters", 1.0, missingItems);
|
||
config.PathEditing.ObjectHeightMeters = GetDoubleValueWithDefault(pathEdit, "object_height_meters", 2.0, missingItems);
|
||
config.PathEditing.SafetyMarginMeters = GetDoubleValueWithDefault(pathEdit, "safety_margin_meters", 0.1, missingItems);
|
||
config.PathEditing.DefaultPathTurnRadiusMeters = GetDoubleValueWithDefault(pathEdit, "default_path_turn_radius", 2.5, missingItems);
|
||
config.PathEditing.ArcSamplingStepMeters = GetDoubleValueWithDefault(pathEdit, "arc_sampling_step", 0.05, missingItems);
|
||
}
|
||
}
|
||
|
||
// 加载可视化配置
|
||
if (!model.ContainsKey("visualization"))
|
||
{
|
||
missingItems.Add("[visualization]");
|
||
LogManager.Warning("配置文件缺少 [visualization] 部分,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
var visual = model["visualization"] as TomlTable;
|
||
if (visual == null)
|
||
{
|
||
missingItems.Add("[visualization] (格式错误)");
|
||
LogManager.Warning("[visualization] 部分格式错误,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
config.Visualization.MarginRatio = GetDoubleValueWithDefault(visual, "margin_ratio", 0.1, missingItems);
|
||
config.Visualization.PassageSpaceOpacity = GetDoubleValueWithDefault(visual, "passage_space_opacity", 0.4, missingItems);
|
||
}
|
||
}
|
||
|
||
// 加载动画配置
|
||
if (!model.ContainsKey("animation"))
|
||
{
|
||
missingItems.Add("[animation]");
|
||
LogManager.Warning("配置文件缺少 [animation] 部分,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
var anim = model["animation"] as TomlTable;
|
||
if (anim == null)
|
||
{
|
||
missingItems.Add("[animation] (格式错误)");
|
||
LogManager.Warning("[animation] 部分格式错误,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
config.Animation.FrameRate = GetIntValueWithDefault(anim, "frame_rate", 30, missingItems);
|
||
config.Animation.DurationSeconds = GetDoubleValueWithDefault(anim, "duration_seconds", 10.0, missingItems);
|
||
config.Animation.DetectionToleranceMeters = GetDoubleValueWithDefault(anim, "detection_tolerance_meters", 0.05, missingItems);
|
||
config.Animation.SpatialIndexCellSizeMeters = GetDoubleValueWithDefault(anim, "spatial_index_cell_size", 1.0, missingItems);
|
||
}
|
||
}
|
||
|
||
// 加载物流属性配置
|
||
if (!model.ContainsKey("logistics"))
|
||
{
|
||
missingItems.Add("[logistics]");
|
||
LogManager.Warning("配置文件缺少 [logistics] 部分,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
var logistics = model["logistics"] as TomlTable;
|
||
if (logistics == null)
|
||
{
|
||
missingItems.Add("[logistics] (格式错误)");
|
||
LogManager.Warning("[logistics] 部分格式错误,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
config.Logistics.Traversable = GetBoolValueWithDefault(logistics, "traversable", true, missingItems);
|
||
config.Logistics.Priority = GetIntValueWithDefault(logistics, "priority", 5, missingItems);
|
||
config.Logistics.HeightLimitMeters = GetDoubleValueWithDefault(logistics, "height_limit_meters", 3.0, missingItems);
|
||
config.Logistics.SpeedLimitMetersPerSecond = GetDoubleValueWithDefault(logistics, "speed_limit_meters_per_second", 0.8, missingItems);
|
||
config.Logistics.WidthLimitMeters = GetDoubleValueWithDefault(logistics, "width_limit_meters", 3.0, missingItems);
|
||
}
|
||
}
|
||
|
||
// 加载坐标系配置
|
||
if (!model.ContainsKey("coordinate_system"))
|
||
{
|
||
missingItems.Add("[coordinate_system]");
|
||
LogManager.Warning("配置文件缺少 [coordinate_system] 部分,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
var coordSys = model["coordinate_system"] as TomlTable;
|
||
if (coordSys == null)
|
||
{
|
||
missingItems.Add("[coordinate_system] (格式错误)");
|
||
LogManager.Warning("[coordinate_system] 部分格式错误,将使用默认值");
|
||
}
|
||
else
|
||
{
|
||
config.CoordinateSystem = new CoordinateSystemConfig
|
||
{
|
||
Type = GetStringValueWithDefault(coordSys, "type", "AutoDetect", missingItems)
|
||
};
|
||
}
|
||
}
|
||
|
||
// 如果有缺失项,记录警告并设置标志
|
||
if (missingItems.Count > 0)
|
||
{
|
||
_hasMissingConfigItems = true;
|
||
_missingConfigItems = missingItems;
|
||
LogManager.Warning($"配置文件存在缺失项(共{missingItems.Count}项),已使用默认值填充");
|
||
LogManager.Warning($"缺失项: {string.Join(", ", missingItems)}");
|
||
}
|
||
|
||
return config;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建空的配置对象(初始化所有嵌套对象)
|
||
/// </summary>
|
||
private SystemConfig CreateEmptyConfigObject()
|
||
{
|
||
return new SystemConfig
|
||
{
|
||
GridGeneration = new GridGenerationConfig(),
|
||
PathPlanning = new PathPlanningConfig(),
|
||
PathEditing = new PathEditingConfig(),
|
||
Visualization = new VisualizationConfig(),
|
||
Animation = new AnimationConfig(),
|
||
Logistics = new LogisticsConfig(),
|
||
CoordinateSystem = new CoordinateSystemConfig()
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存配置到文件
|
||
/// </summary>
|
||
public void SaveConfig(SystemConfig config)
|
||
{
|
||
SaveConfig(config, notifyChange: true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存配置到文件
|
||
/// </summary>
|
||
/// <param name="config">配置对象</param>
|
||
/// <param name="notifyChange">是否触发配置变更事件</param>
|
||
public void SaveConfig(SystemConfig config, bool notifyChange)
|
||
{
|
||
try
|
||
{
|
||
// 确保目录存在
|
||
Directory.CreateDirectory(ConfigDirectory);
|
||
|
||
var tomlContent = GenerateTomlFromConfig(config);
|
||
File.WriteAllText(ConfigFilePath, tomlContent);
|
||
|
||
_currentConfig = config;
|
||
LogManager.Info($"配置文件已保存: {ConfigFilePath}");
|
||
|
||
// 触发配置变更事件
|
||
if (notifyChange)
|
||
{
|
||
NotifyConfigurationChanged(ConfigCategory.All, isExternal: false);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"保存配置文件失败: {ex.Message}");
|
||
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 直接写入配置文件内容(不经过模板转换)
|
||
/// </summary>
|
||
private void WriteConfigFileDirect(string tomlContent)
|
||
{
|
||
try
|
||
{
|
||
// 确保目录存在
|
||
Directory.CreateDirectory(ConfigDirectory);
|
||
|
||
File.WriteAllText(ConfigFilePath, tomlContent);
|
||
LogManager.Info($"配置文件已创建: {ConfigFilePath}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"写入配置文件失败: {ex.Message}");
|
||
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从配置对象生成 TOML 字符串(使用模板格式)
|
||
/// </summary>
|
||
private string GenerateTomlFromConfig(SystemConfig config)
|
||
{
|
||
// 加载模板内容(缓存)
|
||
if (_cachedTemplateContent == null)
|
||
{
|
||
_cachedTemplateContent = ReadConfigTemplate();
|
||
}
|
||
|
||
// 解析模板
|
||
var syntax = Toml.Parse(_cachedTemplateContent);
|
||
var model = syntax.ToModel();
|
||
|
||
// 更新配置值到模型
|
||
ApplyConfigValuesToModel(model, config);
|
||
|
||
// 转换回 TOML 字符串
|
||
return Toml.FromModel(model);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将配置对象的值应用到 TOML 模型
|
||
/// </summary>
|
||
private void ApplyConfigValuesToModel(TomlTable model, SystemConfig config)
|
||
{
|
||
// 更新路径编辑配置
|
||
if (model.ContainsKey("path_editing"))
|
||
{
|
||
var pathEdit = model["path_editing"] as TomlTable;
|
||
if (pathEdit != null)
|
||
{
|
||
pathEdit["cell_size_meters"] = config.PathEditing.CellSizeMeters;
|
||
pathEdit["max_height_diff_meters"] = config.PathEditing.MaxHeightDiffMeters;
|
||
pathEdit["object_length_meters"] = config.PathEditing.ObjectLengthMeters;
|
||
pathEdit["object_width_meters"] = config.PathEditing.ObjectWidthMeters;
|
||
pathEdit["object_height_meters"] = config.PathEditing.ObjectHeightMeters;
|
||
pathEdit["safety_margin_meters"] = config.PathEditing.SafetyMarginMeters;
|
||
pathEdit["default_path_turn_radius"] = config.PathEditing.DefaultPathTurnRadiusMeters;
|
||
pathEdit["arc_sampling_step"] = config.PathEditing.ArcSamplingStepMeters;
|
||
}
|
||
}
|
||
|
||
// 更新可视化配置
|
||
if (model.ContainsKey("visualization"))
|
||
{
|
||
var visual = model["visualization"] as TomlTable;
|
||
if (visual != null)
|
||
{
|
||
visual["margin_ratio"] = config.Visualization.MarginRatio;
|
||
visual["passage_space_opacity"] = config.Visualization.PassageSpaceOpacity;
|
||
}
|
||
}
|
||
|
||
// 更新动画配置
|
||
if (model.ContainsKey("animation"))
|
||
{
|
||
var anim = model["animation"] as TomlTable;
|
||
if (anim != null)
|
||
{
|
||
anim["frame_rate"] = config.Animation.FrameRate;
|
||
anim["duration_seconds"] = config.Animation.DurationSeconds;
|
||
anim["detection_tolerance_meters"] = config.Animation.DetectionToleranceMeters;
|
||
anim["spatial_index_cell_size"] = config.Animation.SpatialIndexCellSizeMeters;
|
||
}
|
||
}
|
||
|
||
// 更新物流属性配置
|
||
if (model.ContainsKey("logistics"))
|
||
{
|
||
var logistics = model["logistics"] as TomlTable;
|
||
if (logistics != null)
|
||
{
|
||
logistics["traversable"] = config.Logistics.Traversable;
|
||
logistics["priority"] = config.Logistics.Priority;
|
||
logistics["height_limit_meters"] = config.Logistics.HeightLimitMeters;
|
||
logistics["speed_limit_meters_per_second"] = config.Logistics.SpeedLimitMetersPerSecond;
|
||
logistics["width_limit_meters"] = config.Logistics.WidthLimitMeters;
|
||
}
|
||
}
|
||
|
||
// 更新坐标系配置
|
||
if (model.ContainsKey("coordinate_system"))
|
||
{
|
||
var coordSys = model["coordinate_system"] as TomlTable;
|
||
if (coordSys != null && config.CoordinateSystem != null)
|
||
{
|
||
coordSys["type"] = config.CoordinateSystem.Type;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重新加载配置文件
|
||
/// </summary>
|
||
public void Reload()
|
||
{
|
||
ReloadInternal(isExternal: false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部重新加载方法
|
||
/// </summary>
|
||
/// <param name="isExternal">是否是外部修改触发的重载</param>
|
||
private void ReloadInternal(bool isExternal)
|
||
{
|
||
try
|
||
{
|
||
_currentConfig = LoadConfigFile();
|
||
LogManager.Info($"配置文件已重新加载{(isExternal ? "(外部修改)" : "")}");
|
||
|
||
// 触发配置变更事件
|
||
NotifyConfigurationChanged(ConfigCategory.All, isExternal);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"重新加载配置文件失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 恢复为模板中的默认配置
|
||
/// </summary>
|
||
public void RestoreToDefaults()
|
||
{
|
||
LogManager.Info("开始恢复默认配置...");
|
||
var templateContent = ReadConfigTemplate();
|
||
LogManager.Info($"模板内容长度: {templateContent.Length}");
|
||
LogManager.Info($"模板内容前200字符: {templateContent.Substring(0, Math.Min(200, templateContent.Length))}");
|
||
|
||
WriteConfigFileDirect(templateContent);
|
||
|
||
var newConfig = ParseTomlToConfig(templateContent);
|
||
LogManager.Info($"解析后的配置 - CellSizeMeters: {newConfig.PathEditing.CellSizeMeters}");
|
||
LogManager.Info($"解析后的配置 - ObjectLengthMeters: {newConfig.PathEditing.ObjectLengthMeters}");
|
||
|
||
_currentConfig = newConfig;
|
||
|
||
// 清除配置兼容性警告(已恢复到完整配置)
|
||
ClearConfigCompatibilityWarning();
|
||
|
||
LogManager.Info("默认配置恢复完成");
|
||
|
||
// 触发配置变更事件
|
||
NotifyConfigurationChanged(ConfigCategory.All, isExternal: false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发配置变更事件
|
||
/// </summary>
|
||
/// <param name="category">变更的配置类别</param>
|
||
/// <param name="isExternal">是否是外部修改</param>
|
||
public void NotifyConfigurationChanged(ConfigCategory category, bool isExternal = false)
|
||
{
|
||
var args = new ConfigurationChangedEventArgs(category, isExternal);
|
||
|
||
try
|
||
{
|
||
// 触发总事件
|
||
ConfigurationChanged?.Invoke(this, args);
|
||
|
||
// 触发分类事件
|
||
CategoryConfigurationChanged?.Invoke(this,
|
||
new Tuple<ConfigCategory, ConfigurationChangedEventArgs>(category, args));
|
||
|
||
// 如果是 All,触发各个分类事件
|
||
if (category == ConfigCategory.All)
|
||
{
|
||
for (int i = 1; i <= 7; i++)
|
||
{
|
||
CategoryConfigurationChanged?.Invoke(this,
|
||
new Tuple<ConfigCategory, ConfigurationChangedEventArgs>((ConfigCategory)i, args));
|
||
}
|
||
}
|
||
|
||
LogManager.Debug($"配置变更事件已触发: {category}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"触发配置变更事件失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新特定类别的配置并触发事件
|
||
/// </summary>
|
||
/// <param name="category">配置类别</param>
|
||
/// <param name="updateAction">更新操作</param>
|
||
public void UpdateCategoryConfig(ConfigCategory category, Action<SystemConfig> updateAction)
|
||
{
|
||
if (updateAction == null)
|
||
throw new ArgumentNullException(nameof(updateAction));
|
||
|
||
updateAction(_currentConfig);
|
||
SaveConfig(_currentConfig, notifyChange: false);
|
||
NotifyConfigurationChanged(category, isExternal: false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 资源释放
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
if (!_isDisposed)
|
||
{
|
||
_fileWatcher?.Dispose();
|
||
_isDisposed = true;
|
||
LogManager.Info("ConfigManager 已释放");
|
||
}
|
||
}
|
||
|
||
// 辅助方法:从 TomlTable 获取必需值
|
||
private double GetRequiredDoubleValue(TomlTable table, string key)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
throw new InvalidOperationException($"配置项 '{key}' 不存在");
|
||
}
|
||
|
||
var value = table[key];
|
||
if (value is long longValue)
|
||
return (double)longValue;
|
||
if (value is double doubleValue)
|
||
return doubleValue;
|
||
|
||
throw new InvalidOperationException($"配置项 '{key}' 的类型错误,应为数值类型");
|
||
}
|
||
|
||
private int GetRequiredIntValue(TomlTable table, string key)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
throw new InvalidOperationException($"配置项 '{key}' 不存在");
|
||
}
|
||
|
||
var value = table[key];
|
||
if (value is long longValue)
|
||
return (int)longValue;
|
||
|
||
throw new InvalidOperationException($"配置项 '{key}' 的类型错误,应为整数类型");
|
||
}
|
||
|
||
private bool GetRequiredBoolValue(TomlTable table, string key)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
throw new InvalidOperationException($"配置项 '{key}' 不存在");
|
||
}
|
||
|
||
var value = table[key];
|
||
if (value is bool boolValue)
|
||
return boolValue;
|
||
|
||
throw new InvalidOperationException($"配置项 '{key}' 的类型错误,应为布尔类型");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取字符串配置值(可选,有默认值)
|
||
/// </summary>
|
||
private string GetStringValue(TomlTable table, string key, string defaultValue)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
return defaultValue;
|
||
}
|
||
return table[key]?.ToString() ?? defaultValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取必需的字符串配置值
|
||
/// </summary>
|
||
private string GetRequiredStringValue(TomlTable table, string key)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
throw new InvalidOperationException($"配置项 '{key}' 不存在");
|
||
}
|
||
|
||
var value = table[key];
|
||
if (value == null)
|
||
{
|
||
throw new InvalidOperationException($"配置项 '{key}' 的值为 null");
|
||
}
|
||
|
||
var strValue = value.ToString();
|
||
if (string.IsNullOrWhiteSpace(strValue))
|
||
{
|
||
throw new InvalidOperationException($"配置项 '{key}' 不能为空");
|
||
}
|
||
|
||
return strValue;
|
||
}
|
||
|
||
#region 带默认值的配置读取方法(用于配置兼容性处理)
|
||
|
||
/// <summary>
|
||
/// 获取 double 配置值(带默认值,不抛出异常)
|
||
/// </summary>
|
||
private double GetDoubleValueWithDefault(TomlTable table, string key, double defaultValue, List<string> missingItems)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
missingItems.Add($"{key}");
|
||
LogManager.Debug($"配置项 '{key}' 不存在,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
var value = table[key];
|
||
if (value is long longValue)
|
||
return (double)longValue;
|
||
if (value is double doubleValue)
|
||
return doubleValue;
|
||
|
||
missingItems.Add($"{key} (类型错误)");
|
||
LogManager.Warning($"配置项 '{key}' 类型错误,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 float 配置值(带默认值,不抛出异常)
|
||
/// </summary>
|
||
private float GetFloatValueWithDefault(TomlTable table, string key, float defaultValue, List<string> missingItems)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
missingItems.Add($"{key}");
|
||
LogManager.Debug($"配置项 '{key}' 不存在,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
var value = table[key];
|
||
if (value is long longValue)
|
||
return (float)longValue;
|
||
if (value is double doubleValue)
|
||
return (float)doubleValue;
|
||
|
||
missingItems.Add($"{key} (类型错误)");
|
||
LogManager.Warning($"配置项 '{key}' 类型错误,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 int 配置值(带默认值,不抛出异常)
|
||
/// </summary>
|
||
private int GetIntValueWithDefault(TomlTable table, string key, int defaultValue, List<string> missingItems)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
missingItems.Add($"{key}");
|
||
LogManager.Debug($"配置项 '{key}' 不存在,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
var value = table[key];
|
||
if (value is long longValue)
|
||
return (int)longValue;
|
||
|
||
missingItems.Add($"{key} (类型错误)");
|
||
LogManager.Warning($"配置项 '{key}' 类型错误,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 bool 配置值(带默认值,不抛出异常)
|
||
/// </summary>
|
||
private bool GetBoolValueWithDefault(TomlTable table, string key, bool defaultValue, List<string> missingItems)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
missingItems.Add($"{key}");
|
||
LogManager.Debug($"配置项 '{key}' 不存在,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
var value = table[key];
|
||
if (value is bool boolValue)
|
||
return boolValue;
|
||
|
||
missingItems.Add($"{key} (类型错误)");
|
||
LogManager.Warning($"配置项 '{key}' 类型错误,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 string 配置值(带默认值,不抛出异常)
|
||
/// </summary>
|
||
private string GetStringValueWithDefault(TomlTable table, string key, string defaultValue, List<string> missingItems)
|
||
{
|
||
if (!table.ContainsKey(key))
|
||
{
|
||
missingItems.Add($"{key}");
|
||
LogManager.Debug($"配置项 '{key}' 不存在,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
var value = table[key];
|
||
if (value == null)
|
||
{
|
||
missingItems.Add($"{key} (null值)");
|
||
LogManager.Warning($"配置项 '{key}' 为 null,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
var strValue = value.ToString();
|
||
if (string.IsNullOrWhiteSpace(strValue))
|
||
{
|
||
missingItems.Add($"{key} (空值)");
|
||
LogManager.Warning($"配置项 '{key}' 为空,使用默认值: {defaultValue}");
|
||
return defaultValue;
|
||
}
|
||
|
||
return strValue;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|