NavisworksTransport/src/Commands/ImportPathCommand.cs
tian c0714a5180 fix: regenerate path point IDs and rebuild edges on import rename
- Also regenerate all PathPoint.Id to avoid UNIQUE constraint on PathPoints.Id
- Call RecalculateRoute() to rebuild edges with new point references
- Edge constructor auto-generates new Id, so edges are fresh too
2026-06-03 20:58:49 +08:00

819 lines
31 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NavisworksTransport.Core;
namespace NavisworksTransport.Commands
{
/// <summary>
/// 导入路径参数类
/// </summary>
public class ImportPathParameters
{
/// <summary>
/// 导入文件路径
/// </summary>
public string FilePath { get; set; }
/// <summary>
/// 导入格式
/// </summary>
public ExportFormat ImportFormat { get; set; } = ExportFormat.Xml;
/// <summary>
/// 是否合并到现有路径false则替换所有路径
/// </summary>
public bool MergeWithExisting { get; set; } = true;
/// <summary>
/// 重名路径处理策略
/// </summary>
public DuplicateNameHandling DuplicateHandling { get; set; } = DuplicateNameHandling.Rename;
/// <summary>
/// 导入前是否创建当前路径的备份
/// </summary>
public bool CreateBackup { get; set; } = true;
/// <summary>
/// 是否验证导入的路径数据
/// </summary>
public bool ValidateImportedData { get; set; } = true;
/// <summary>
/// 导入后是否自动选择第一个路径为当前路径
/// </summary>
public bool AutoSelectFirstRoute { get; set; } = true;
/// <summary>
/// 路径过滤条件(可选)
/// </summary>
public Func<PathRoute, bool> RouteFilter { get; set; }
/// <summary>
/// 操作描述
/// </summary>
public string Description { get; set; }
/// <summary>
/// 验证参数有效性
/// </summary>
public PathPlanningResult ValidateParameters()
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(FilePath))
{
errors.Add("导入文件路径不能为空");
}
else if (!File.Exists(FilePath))
{
errors.Add($"导入文件不存在: {FilePath}");
}
else
{
// 检查文件扩展名与格式是否匹配
var extension = Path.GetExtension(FilePath).ToLower();
var expectedExtensions = ImportFormat == ExportFormat.Json ? new[] { ".json" } : new[] { ".xml" };
if (!expectedExtensions.Contains(extension))
{
errors.Add($"文件扩展名 '{extension}' 与导入格式 '{ImportFormat}' 不匹配");
}
// 检查文件大小
try
{
var fileInfo = new FileInfo(FilePath);
if (fileInfo.Length > 100 * 1024 * 1024) // 100MB限制
{
errors.Add("导入文件过大超过100MB限制");
}
else if (fileInfo.Length == 0)
{
errors.Add("导入文件为空");
}
}
catch (Exception ex)
{
errors.Add($"无法读取文件信息: {ex.Message}");
}
}
if (!Enum.IsDefined(typeof(ExportFormat), ImportFormat))
{
errors.Add("无效的导入格式");
}
if (!Enum.IsDefined(typeof(DuplicateNameHandling), DuplicateHandling))
{
errors.Add("无效的重名处理策略");
}
if (errors.Count > 0)
{
return PathPlanningResult.ValidationFailure($"参数验证失败: {string.Join("; ", errors)}");
}
return PathPlanningResult.Success("参数验证通过");
}
}
/// <summary>
/// 重名路径处理策略
/// </summary>
public enum DuplicateNameHandling
{
/// <summary>
/// 重命名新路径
/// </summary>
Rename,
/// <summary>
/// 覆盖现有路径
/// </summary>
Overwrite,
/// <summary>
/// 跳过重名路径
/// </summary>
Skip
}
/// <summary>
/// 导入路径结果类
/// </summary>
public class ImportPathResult
{
/// <summary>
/// 成功导入的路径数量
/// </summary>
public int ImportedCount { get; set; }
/// <summary>
/// 跳过的路径数量
/// </summary>
public int SkippedCount { get; set; }
/// <summary>
/// 因重名被重命名的路径数量
/// </summary>
public int RenamedCount { get; set; }
/// <summary>
/// 失败的路径数量
/// </summary>
public int FailedCount { get; set; }
/// <summary>
/// 文件中的总路径数量
/// </summary>
public int TotalInFile { get; set; }
/// <summary>
/// 已导入的路径信息
/// </summary>
public List<string> ImportedPaths { get; set; }
/// <summary>
/// 跳过的路径信息
/// </summary>
public List<string> SkippedPaths { get; set; }
/// <summary>
/// 失败的路径信息
/// </summary>
public List<string> FailedPaths { get; set; }
/// <summary>
/// 备份文件路径(如果创建了备份)
/// </summary>
public string BackupFilePath { get; set; }
/// <summary>
/// 导入的文件路径
/// </summary>
public string ImportedFilePath { get; set; }
/// <summary>
/// 导入格式
/// </summary>
public ExportFormat ImportFormat { get; set; }
/// <summary>
/// 操作执行时间(毫秒)
/// </summary>
public long ExecutionTimeMs { get; set; }
public ImportPathResult()
{
ImportedPaths = new List<string>();
SkippedPaths = new List<string>();
FailedPaths = new List<string>();
}
/// <summary>
/// 导入成功率
/// </summary>
public double ImportSuccessRate => TotalInFile > 0 ? (double)ImportedCount / TotalInFile : 0.0;
}
/// <summary>
/// 导入路径Command
/// </summary>
public class ImportPathCommand : CommandBase
{
#region
private readonly ImportPathParameters _parameters;
private readonly PathPlanningManager _pathPlanningManager;
private readonly UIStateManager _uiStateManager;
private readonly PathDataManager _pathDataManager;
/// <summary>
/// 命令参数
/// </summary>
public ImportPathParameters Parameters => _parameters;
#endregion
#region
/// <summary>
/// 初始化导入路径命令
/// </summary>
/// <param name="parameters">导入参数</param>
/// <param name="pathPlanningManager">路径规划管理器</param>
public ImportPathCommand(ImportPathParameters parameters, PathPlanningManager pathPlanningManager = null)
: base("ImportPath", "导入路径", "从文件导入路径规划数据")
{
_parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
_pathPlanningManager = pathPlanningManager ?? throw new ArgumentNullException(nameof(pathPlanningManager));
_uiStateManager = UIStateManager.Instance;
_pathDataManager = new PathDataManager();
LogInfo("导入路径命令初始化完成");
}
#endregion
#region CommandBase实现
/// <summary>
/// 验证命令参数
/// </summary>
protected override PathPlanningResult ValidateParameters()
{
LogInfo("开始验证导入路径参数");
try
{
// 验证基础参数
var basicValidation = _parameters.ValidateParameters();
if (!basicValidation.IsSuccess)
{
LogWarning($"基础参数验证失败: {basicValidation.ErrorMessage}");
return basicValidation;
}
// 验证PathPlanningManager状态
if (_pathPlanningManager == null)
{
return PathPlanningResult.ValidationFailure("路径规划管理器未初始化");
}
// 验证文件格式(尝试解析)
try
{
if (_parameters.ImportFormat == ExportFormat.Xml)
{
var testResult = _pathDataManager.ValidateExportFile(_parameters.FilePath, ExportFormat.Xml);
if (!testResult.IsValid)
{
return PathPlanningResult.ValidationFailure($"XML文件格式验证失败: {string.Join("; ", testResult.Errors)}");
}
if (testResult.Warnings.Count > 0)
{
LogWarning($"XML文件格式警告: {string.Join("; ", testResult.Warnings)}");
}
}
}
catch (Exception validateEx)
{
LogWarning($"文件格式预验证失败: {validateEx.Message}");
}
LogInfo("参数验证通过");
return PathPlanningResult.Success("所有参数验证通过");
}
catch (Exception ex)
{
LogError("参数验证过程中发生异常", ex);
return PathPlanningResult.ValidationFailure($"验证过程异常: {ex.Message}");
}
}
/// <summary>
/// 执行导入路径的核心逻辑
/// </summary>
protected override async Task<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
LogInfo("开始执行导入路径");
var startTime = DateTime.UtcNow;
try
{
// 第一阶段初始化10%
UpdateProgress(10, "正在初始化导入操作...");
ThrowIfCancellationRequested(cancellationToken);
// 更新UI状态
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
LogInfo("UI状态更新路径导入进行中");
});
var result = new ImportPathResult
{
ImportedFilePath = _parameters.FilePath,
ImportFormat = _parameters.ImportFormat
};
// 第二阶段创建备份20%
if (_parameters.CreateBackup && _pathPlanningManager.Routes.Count > 0)
{
UpdateProgress(20, "正在创建导入前备份...");
ThrowIfCancellationRequested(cancellationToken);
try
{
result.BackupFilePath = await CreateBackupAsync(cancellationToken);
LogInfo($"备份已创建: {result.BackupFilePath}");
}
catch (Exception backupEx)
{
LogWarning($"创建备份失败: {backupEx.Message}");
// 备份失败不阻止导入操作,但记录警告
}
}
// 第三阶段读取导入文件30%
UpdateProgress(30, "正在读取导入文件...");
ThrowIfCancellationRequested(cancellationToken);
List<PathRoute> importedRoutes = await Task.Run(() => LoadRoutesFromFile(), cancellationToken);
if (importedRoutes == null || importedRoutes.Count == 0)
{
LogWarning("导入文件中没有找到有效的路径数据");
return PathPlanningResult.Success("导入文件中没有找到有效的路径数据");
}
result.TotalInFile = importedRoutes.Count;
LogInfo($"从文件中读取到 {importedRoutes.Count} 个路径");
// 第四阶段应用过滤和验证40%
UpdateProgress(40, "正在验证导入数据...");
ThrowIfCancellationRequested(cancellationToken);
// 应用过滤器
if (_parameters.RouteFilter != null)
{
var originalCount = importedRoutes.Count;
importedRoutes = importedRoutes.Where(_parameters.RouteFilter).ToList();
LogInfo($"过滤后剩余 {importedRoutes.Count} 个路径(原始 {originalCount} 个)");
}
// 验证导入的路径
if (_parameters.ValidateImportedData)
{
await Task.Run(() => ValidateImportedRoutes(importedRoutes, result), cancellationToken);
}
// 第五阶段处理重名和合并50% - 80%
UpdateProgress(50, "正在处理路径合并...");
ThrowIfCancellationRequested(cancellationToken);
await Task.Run(() =>
{
try
{
LogInfo("开始处理路径导入和合并");
// 如果不是合并模式,先清空现有路径
if (!_parameters.MergeWithExisting)
{
var existingRoutesCount = _pathPlanningManager.Routes.Count;
if (existingRoutesCount > 0)
{
LogInfo($"非合并模式:将清空现有的 {existingRoutesCount} 个路径");
// 注意这里应该通过PathPlanningManager的方法来清空但由于架构限制我们需要小心处理
// 实际实现可能需要调用PathPlanningManager的相应方法
}
}
int processedCount = 0;
foreach (var route in importedRoutes)
{
try
{
// 检查取消请求
ThrowIfCancellationRequested(cancellationToken);
bool shouldImport = true;
string finalRouteName = route.Name;
// 处理重名
if (_pathPlanningManager.Routes.Any(r => r.Name == route.Name))
{
switch (_parameters.DuplicateHandling)
{
case DuplicateNameHandling.Skip:
shouldImport = false;
result.SkippedCount++;
result.SkippedPaths.Add($"{route.Name} - 重名跳过");
LogInfo($"跳过重名路径: {route.Name}");
break;
case DuplicateNameHandling.Rename:
{
var originalName = route.Name;
finalRouteName = GenerateUniqueName(originalName, _pathPlanningManager.Routes);
route.Name = finalRouteName;
route.Id = Guid.NewGuid().ToString();
foreach (var pt in route.Points)
{
pt.Id = Guid.NewGuid().ToString();
}
route.RecalculateRoute("导入重命名");
if (finalRouteName != originalName)
{
result.RenamedCount++;
LogInfo($"重命名路径: {originalName} -> {finalRouteName}");
}
break;
}
case DuplicateNameHandling.Overwrite:
// 先删除现有的同名路径
var existingRoute = _pathPlanningManager.Routes.FirstOrDefault(r => r.Name == route.Name);
if (existingRoute != null)
{
_pathPlanningManager.DeleteRoute(existingRoute);
LogInfo($"覆盖现有路径: {route.Name}");
}
break;
}
}
if (shouldImport)
{
// 导入路径
bool addSuccess = _pathPlanningManager.AddRoute(route);
if (addSuccess)
{
result.ImportedCount++;
result.ImportedPaths.Add($"{finalRouteName} ({route.Points.Count} 个点)");
LogInfo($"成功导入路径: {finalRouteName}");
}
else
{
result.FailedCount++;
result.FailedPaths.Add($"{finalRouteName} - 添加失败");
LogWarning($"添加路径失败: {finalRouteName}");
}
}
processedCount++;
// 更新进度50% - 80%
var progressPercent = 50 + (int)((double)processedCount / importedRoutes.Count * 30);
UpdateProgress(progressPercent, $"正在导入路径... ({processedCount}/{importedRoutes.Count})");
}
catch (OperationCanceledException)
{
throw; // 重新抛出取消异常
}
catch (Exception routeEx)
{
result.FailedCount++;
result.FailedPaths.Add($"{route.Name} - 异常: {routeEx.Message}");
LogError($"导入路径时发生异常: {route.Name}", routeEx);
}
}
LogInfo($"路径导入完成 - 导入: {result.ImportedCount}, 失败: {result.FailedCount}, 跳过: {result.SkippedCount}");
}
catch (OperationCanceledException)
{
LogInfo("路径导入被用户取消");
throw; // 重新抛出取消异常
}
catch (Exception ex)
{
LogError("批量导入路径时发生异常", ex);
throw;
}
}, cancellationToken);
UpdateProgress(85, "正在完成导入操作...");
ThrowIfCancellationRequested(cancellationToken);
// 完成100%
UpdateProgress(100, "路径导入完成");
var elapsedMs = (DateTime.UtcNow - startTime).TotalMilliseconds;
result.ExecutionTimeMs = (long)elapsedMs;
LogInfo($"导入路径执行完成,总耗时: {elapsedMs:F0}ms");
LogInfo($"导入结果 - 文件中: {result.TotalInFile}, 导入: {result.ImportedCount}, 失败: {result.FailedCount}, 跳过: {result.SkippedCount}, 重命名: {result.RenamedCount}");
// 创建结果对象
string resultMessage;
if (result.TotalInFile == 0)
{
resultMessage = "导入文件中没有找到路径数据";
}
else
{
resultMessage = $"路径导入完成:导入 {result.ImportedCount}/{result.TotalInFile} 项";
if (result.FailedCount > 0)
{
resultMessage += $",失败 {result.FailedCount} 项";
}
if (result.SkippedCount > 0)
{
resultMessage += $",跳过 {result.SkippedCount} 项";
}
if (result.RenamedCount > 0)
{
resultMessage += $",重命名 {result.RenamedCount} 项";
}
}
var commandResult = PathPlanningResult<ImportPathResult>.Success(result, resultMessage);
commandResult.ElapsedMilliseconds = (long)elapsedMs;
return commandResult;
}
catch (OperationCanceledException)
{
LogInfo("导入路径被用户取消");
throw; // 让基类处理取消异常
}
catch (Exception ex)
{
var elapsedMs = (DateTime.UtcNow - startTime).TotalMilliseconds;
LogError($"导入路径执行失败,已耗时: {elapsedMs:F0}ms", ex);
// 尝试恢复UI状态
try
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
LogInfo("尝试恢复UI状态");
});
}
catch (Exception uiEx)
{
LogError("UI状态恢复失败", uiEx);
}
return PathPlanningResult.Failure($"导入路径失败: {ex.Message}", ex);
}
}
#endregion
#region
/// <summary>
/// 从文件加载路径
/// </summary>
private List<PathRoute> LoadRoutesFromFile()
{
try
{
LogInfo($"开始从文件加载路径: {_parameters.FilePath}");
List<PathRoute> routes;
if (_parameters.ImportFormat == ExportFormat.Json)
{
routes = _pathDataManager.ImportFromJson(_parameters.FilePath);
}
else
{
routes = _pathDataManager.ImportFromXml(_parameters.FilePath);
}
LogInfo($"从文件加载了 {routes?.Count ?? 0} 个路径");
return routes ?? new List<PathRoute>();
}
catch (Exception ex)
{
LogError("从文件加载路径失败", ex);
throw new InvalidOperationException($"读取导入文件失败: {ex.Message}", ex);
}
}
/// <summary>
/// 验证导入的路径
/// </summary>
private void ValidateImportedRoutes(List<PathRoute> routes, ImportPathResult result)
{
LogInfo("开始验证导入的路径数据");
var invalidRoutes = new List<PathRoute>();
foreach (var route in routes)
{
try
{
// 使用PathPlanningManager的验证方法
var validationResult = _pathPlanningManager.ValidateRoute(route);
if (!validationResult.IsSuccess)
{
invalidRoutes.Add(route);
result.FailedPaths.Add($"{route.Name} - 验证失败: {validationResult.ErrorMessage}");
LogWarning($"路径验证失败: {route.Name} - {validationResult.ErrorMessage}");
}
}
catch (Exception ex)
{
invalidRoutes.Add(route);
result.FailedPaths.Add($"{route.Name} - 验证异常: {ex.Message}");
LogError($"验证路径时发生异常: {route.Name}", ex);
}
}
// 从导入列表中移除无效路径
foreach (var invalidRoute in invalidRoutes)
{
routes.Remove(invalidRoute);
result.FailedCount++;
}
LogInfo($"路径验证完成,有效路径: {routes.Count}, 无效路径: {invalidRoutes.Count}");
}
/// <summary>
/// 生成唯一名称
/// </summary>
private string GenerateUniqueName(string baseName, IEnumerable<PathRoute> existingRoutes)
{
var existingNames = existingRoutes.Select(r => r.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
if (!existingNames.Contains(baseName))
{
return baseName;
}
int counter = 1;
string newName;
do
{
newName = $"{baseName}_{counter}";
counter++;
} while (existingNames.Contains(newName));
return newName;
}
/// <summary>
/// 创建导入前备份
/// </summary>
private async Task<string> CreateBackupAsync(CancellationToken cancellationToken)
{
try
{
var backupFileName = $"路径导入前备份_{DateTime.Now:yyyyMMdd_HHmmss}.xml";
var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var backupPath = Path.Combine(documentsPath, "NavisworksTransport", "Backups");
if (!Directory.Exists(backupPath))
{
Directory.CreateDirectory(backupPath);
}
var backupFilePath = Path.Combine(backupPath, backupFileName);
var exportSettings = new ExportSettings
{
ProjectName = "导入前备份",
Description = $"导入 {Path.GetFileName(_parameters.FilePath)} 前的备份",
Units = "meters"
};
await Task.Run(() =>
{
bool success = _pathDataManager.ExportToXml(_pathPlanningManager.Routes.ToList(), backupFilePath, exportSettings);
if (!success)
{
throw new InvalidOperationException("备份文件创建失败");
}
}, cancellationToken);
return backupFilePath;
}
catch (Exception ex)
{
LogError("创建备份文件失败", ex);
throw;
}
}
#endregion
#region
/// <summary>
/// 创建导入XML文件的快捷命令
/// </summary>
/// <param name="filePath">XML文件路径</param>
/// <param name="mergeWithExisting">是否合并到现有路径</param>
/// <param name="createBackup">是否创建备份</param>
/// <returns>导入路径命令实例</returns>
public static ImportPathCommand CreateXmlImport(string filePath, bool mergeWithExisting = true, bool createBackup = true)
{
var parameters = new ImportPathParameters
{
FilePath = filePath,
ImportFormat = ExportFormat.Xml,
MergeWithExisting = mergeWithExisting,
CreateBackup = createBackup,
DuplicateHandling = DuplicateNameHandling.Rename,
ValidateImportedData = true,
AutoSelectFirstRoute = true,
Description = $"导入XML文件: {Path.GetFileName(filePath)}"
};
return new ImportPathCommand(parameters);
}
/// <summary>
/// 创建导入JSON文件的快捷命令
/// </summary>
/// <param name="filePath">JSON文件路径</param>
/// <param name="mergeWithExisting">是否合并到现有路径</param>
/// <param name="createBackup">是否创建备份</param>
/// <returns>导入路径命令实例</returns>
public static ImportPathCommand CreateJsonImport(string filePath, bool mergeWithExisting = true, bool createBackup = true)
{
var parameters = new ImportPathParameters
{
FilePath = filePath,
ImportFormat = ExportFormat.Json,
MergeWithExisting = mergeWithExisting,
CreateBackup = createBackup,
DuplicateHandling = DuplicateNameHandling.Rename,
ValidateImportedData = true,
AutoSelectFirstRoute = true,
Description = $"导入JSON文件: {Path.GetFileName(filePath)}"
};
return new ImportPathCommand(parameters);
}
/// <summary>
/// 创建带过滤器的导入命令
/// </summary>
/// <param name="filePath">文件路径</param>
/// <param name="routeFilter">路径过滤器</param>
/// <param name="duplicateHandling">重名处理策略</param>
/// <returns>导入路径命令实例</returns>
public static ImportPathCommand CreateWithFilter(string filePath, Func<PathRoute, bool> routeFilter, DuplicateNameHandling duplicateHandling = DuplicateNameHandling.Rename)
{
var parameters = new ImportPathParameters
{
FilePath = filePath,
ImportFormat = Path.GetExtension(filePath).ToLower() == ".json" ? ExportFormat.Json : ExportFormat.Xml,
MergeWithExisting = true,
CreateBackup = true,
DuplicateHandling = duplicateHandling,
ValidateImportedData = true,
AutoSelectFirstRoute = true,
RouteFilter = routeFilter,
Description = $"带过滤器导入文件: {Path.GetFileName(filePath)}"
};
return new ImportPathCommand(parameters);
}
/// <summary>
/// 获取命令执行摘要信息
/// </summary>
/// <returns>命令信息摘要</returns>
public string GetExecutionSummary()
{
return $"导入路径命令 - 文件:{Path.GetFileName(_parameters.FilePath ?? "")}, " +
$"格式:{_parameters.ImportFormat}, " +
$"合并:{_parameters.MergeWithExisting}, " +
$"重名处理:{_parameters.DuplicateHandling}, " +
$"状态:{Status}";
}
#endregion
}
}