NavisworksTransport/NavisworksFileExporter.cs

903 lines
35 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 Autodesk.Navisworks.Api;
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
namespace NavisworksTransport
{
/// <summary>
/// Navisworks文件导出器 - 负责将模型元素导出为独立的Navisworks文件
/// </summary>
public class NavisworksFileExporter
{
#region
/// <summary>
/// 导出策略枚举
/// </summary>
public enum ExportStrategy
{
VisibilityControl, // 通过可见性控制
SelectionBased, // 基于选择集
CopyToNewDocument // 复制到新文档
}
/// <summary>
/// 导出配置
/// </summary>
public class ExportConfiguration
{
public ExportStrategy Strategy { get; set; } = ExportStrategy.VisibilityControl;
public bool PreserveViewpoints { get; set; } = false;
public bool PreserveAnimations { get; set; } = false;
public bool PreserveClashTests { get; set; } = false;
public bool CompressOutput { get; set; } = true;
public string FileFormat { get; set; } = "nwd"; // nwd, nwf, nwc
public int TimeoutSeconds { get; set; } = 300;
public bool CreateBackup { get; set; } = true;
}
#endregion
#region
public event EventHandler<string> StatusChanged;
public event EventHandler<int> ProgressChanged;
public event EventHandler<Exception> ErrorOccurred;
#endregion
#region
private readonly VisibilityManager _visibilityManager;
private bool _isExporting = false;
#endregion
#region
public NavisworksFileExporter()
{
_visibilityManager = new VisibilityManager();
LogManager.Info("[FileExporter] Navisworks文件导出器已初始化");
}
#endregion
#region
/// <summary>
/// 异步导出模型元素到文件
/// </summary>
/// <param name="items">要导出的模型元素</param>
/// <param name="outputPath">输出文件路径</param>
/// <param name="config">导出配置</param>
/// <returns>导出是否成功</returns>
public async Task<bool> ExportModelItemsAsync(ModelItemCollection items, string outputPath, ExportConfiguration config = null)
{
if (_isExporting)
{
throw new InvalidOperationException("导出操作正在进行中");
}
try
{
_isExporting = true;
config = config ?? new ExportConfiguration();
LogManager.WriteSessionSeparator();
LogManager.Info($"[FileExporter] ========== 开始文件导出 ==========");
LogManager.Info($"[FileExporter] 元素数量: {items.Count}");
LogManager.Info($"[FileExporter] 输出路径: {outputPath}");
LogManager.Info($"[FileExporter] 导出策略: {config.Strategy}");
LogManager.Info($"[FileExporter] 文件格式: {config.FileFormat}");
LogManager.Info($"[FileExporter] 创建备份: {config.CreateBackup}");
LogManager.Info($"[FileExporter] 超时时间: {config.TimeoutSeconds}秒");
OnStatusChanged($"开始导出 {items.Count} 个元素到 {Path.GetFileName(outputPath)}");
// 记录系统状态
LogManager.Info($"[FileExporter] 导出前内存使用: {GC.GetTotalMemory(false) / 1024 / 1024} MB");
LogManager.Info($"[FileExporter] 导出前进程内存: {System.Diagnostics.Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024} MB");
// 验证输入参数
LogManager.Info("[FileExporter] 开始验证输入参数...");
ValidateExportParameters(items, outputPath);
LogManager.Info("[FileExporter] 输入参数验证完成");
// 创建输出目录
LogManager.Info("[FileExporter] 开始创建输出目录...");
EnsureOutputDirectory(outputPath);
LogManager.Info("[FileExporter] 输出目录创建完成");
// 根据策略执行导出
LogManager.Info($"[FileExporter] 开始执行导出,策略: {config.Strategy}");
bool success = await ExecuteExportAsync(items, outputPath, config);
LogManager.Info($"[FileExporter] 导出执行完成,结果: {success}");
if (success)
{
LogManager.Info($"[FileExporter] 导出成功: {outputPath}");
OnStatusChanged("导出完成");
}
else
{
LogManager.Error($"[FileExporter] 导出失败: {outputPath}");
OnStatusChanged("导出失败");
}
return success;
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 导出异常: {ex.Message}");
OnErrorOccurred(ex);
return false;
}
finally
{
_isExporting = false;
}
}
/// <summary>
/// 同步导出模型元素到文件
/// </summary>
/// <param name="items">要导出的模型元素</param>
/// <param name="outputPath">输出文件路径</param>
/// <param name="config">导出配置</param>
/// <returns>导出是否成功</returns>
public bool ExportModelItems(ModelItemCollection items, string outputPath, ExportConfiguration config = null)
{
try
{
var task = ExportModelItemsAsync(items, outputPath, config);
task.Wait();
return task.Result;
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 同步导出失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 批量导出多个分组
/// </summary>
/// <param name="exportTasks">导出任务列表</param>
/// <param name="config">导出配置</param>
/// <returns>导出结果列表</returns>
public async Task<List<ExportResult>> BatchExportAsync(List<ExportTask> exportTasks, ExportConfiguration config = null)
{
try
{
LogManager.Info($"[FileExporter] 开始批量导出,任务数量: {exportTasks.Count}");
var results = new List<ExportResult>();
int completedCount = 0;
foreach (var task in exportTasks)
{
try
{
OnStatusChanged($"正在导出: {task.LayerName} ({completedCount + 1}/{exportTasks.Count})");
OnProgressChanged((int)((double)completedCount / exportTasks.Count * 100));
bool success = await ExportModelItemsAsync(task.Items, task.OutputPath, config);
var result = new ExportResult
{
LayerName = task.LayerName,
OutputPath = task.OutputPath,
Success = success,
ItemCount = task.Items.Count,
ProcessTime = DateTime.Now
};
if (success && File.Exists(task.OutputPath))
{
result.FileSizeBytes = new FileInfo(task.OutputPath).Length;
}
else if (!success)
{
result.ErrorMessage = "导出失败";
}
results.Add(result);
completedCount++;
LogManager.Info($"[FileExporter] 批量导出进度: {completedCount}/{exportTasks.Count}");
}
catch (Exception ex)
{
var errorResult = new ExportResult
{
LayerName = task.LayerName,
OutputPath = task.OutputPath,
Success = false,
ErrorMessage = ex.Message,
ItemCount = task.Items.Count,
ProcessTime = DateTime.Now
};
results.Add(errorResult);
LogManager.Error($"[FileExporter] 批量导出任务失败: {task.LayerName}, 错误: {ex.Message}");
}
}
OnProgressChanged(100);
OnStatusChanged($"批量导出完成,成功: {results.Count(r => r.Success)}, 失败: {results.Count(r => !r.Success)}");
LogManager.Info($"[FileExporter] 批量导出完成,总计: {results.Count}, 成功: {results.Count(r => r.Success)}");
return results;
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 批量导出异常: {ex.Message}");
throw;
}
}
#endregion
#region
/// <summary>
/// 导出任务
/// </summary>
public class ExportTask
{
public string LayerName { get; set; }
public ModelItemCollection Items { get; set; }
public string OutputPath { get; set; }
}
/// <summary>
/// 导出结果
/// </summary>
public class ExportResult
{
public string LayerName { get; set; }
public string OutputPath { get; set; }
public bool Success { get; set; }
public string ErrorMessage { get; set; }
public int ItemCount { get; set; }
public long FileSizeBytes { get; set; }
public DateTime ProcessTime { get; set; }
}
#endregion
#region -
private async Task<bool> ExecuteExportAsync(ModelItemCollection items, string outputPath, ExportConfiguration config)
{
switch (config.Strategy)
{
case ExportStrategy.VisibilityControl:
return await ExportByVisibilityControlAsync(items, outputPath, config);
case ExportStrategy.SelectionBased:
return await ExportBySelectionAsync(items, outputPath, config);
case ExportStrategy.CopyToNewDocument:
return await ExportByCopyToNewDocumentAsync(items, outputPath, config);
default:
throw new NotSupportedException($"不支持的导出策略: {config.Strategy}");
}
}
private Task<bool> ExportByVisibilityControlAsync(ModelItemCollection items, string outputPath, ExportConfiguration config)
{
try
{
LogManager.Info("[FileExporter] 使用纯选择集策略导出(完全避免可见性操作)");
var document = Application.ActiveDocument;
if (document == null)
{
throw new InvalidOperationException("没有活动的Navisworks文档");
}
// 关键修复:完全避免任何可见性操作,只使用选择集
try
{
OnStatusChanged("正在准备导出...");
LogManager.Info("[FileExporter] 开始纯选择集导出流程");
// 保存原始选择状态
var originalSelection = new ModelItemCollection();
originalSelection.CopyFrom(document.CurrentSelection.SelectedItems);
LogManager.Info($"[FileExporter] 已保存原始选择状态,包含 {originalSelection.Count} 个元素");
try
{
// 设置新的选择集
OnStatusChanged("正在设置选择集...");
LogManager.Info("[FileExporter] 清除当前选择...");
document.CurrentSelection.Clear();
LogManager.Info($"[FileExporter] 开始逐个添加 {items.Count} 个目标元素到选择集...");
// 关键修复:逐个添加元素,避免批量操作导致的崩溃
int addedCount = 0;
foreach (ModelItem item in items)
{
try
{
document.CurrentSelection.Add(item);
addedCount++;
// 每添加50个元素记录一次进度
if (addedCount % 50 == 0)
{
LogManager.Info($"[FileExporter] 已添加 {addedCount}/{items.Count} 个元素到选择集");
}
}
catch (Exception addEx)
{
LogManager.Warning($"[FileExporter] 添加元素到选择集失败: {addEx.Message}");
// 继续处理其他元素,不因单个元素失败而中断
}
}
LogManager.Info($"[FileExporter] 选择集添加完成,成功添加 {addedCount}/{items.Count} 个元素");
LogManager.Info("[FileExporter] 选择集设置完成");
// 等待状态稳定
LogManager.Info("[FileExporter] 等待系统稳定...");
System.Threading.Thread.Sleep(200);
// 直接导出文件,不进行任何可见性操作
OnStatusChanged("正在保存文件...");
LogManager.Info("[FileExporter] 开始保存文档...");
bool saveSuccess = SaveDocument(document, outputPath, config);
LogManager.Info($"[FileExporter] 文档保存结果: {saveSuccess}");
return Task.FromResult(saveSuccess);
}
finally
{
// 恢复原始选择状态
try
{
LogManager.Info("[FileExporter] 开始恢复原始选择状态...");
document.CurrentSelection.Clear();
if (originalSelection.Count > 0)
{
document.CurrentSelection.AddRange(originalSelection);
LogManager.Info($"[FileExporter] 已恢复 {originalSelection.Count} 个原始选择元素");
}
else
{
LogManager.Info("[FileExporter] 原始选择为空,保持清空状态");
}
}
catch (Exception restoreEx)
{
LogManager.Warning($"[FileExporter] 恢复原始选择失败: {restoreEx.Message}");
}
}
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 选择集导出过程中发生异常: {ex.Message}");
LogManager.Error($"[FileExporter] 异常堆栈: {ex.StackTrace}");
// 只进行最安全的清理操作
try
{
document.CurrentSelection.Clear();
LogManager.Info("[FileExporter] 异常恢复:已清除选择集");
}
catch (Exception safeEx)
{
LogManager.Warning($"[FileExporter] 异常恢复中的安全操作也失败: {safeEx.Message}");
}
throw;
}
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 纯选择集导出失败: {ex.Message}");
throw;
}
}
private Task<bool> ExportBySelectionAsync(ModelItemCollection items, string outputPath, ExportConfiguration config)
{
try
{
LogManager.Info("[FileExporter] 使用选择集策略导出");
var document = Application.ActiveDocument;
if (document == null)
{
throw new InvalidOperationException("没有活动的Navisworks文档");
}
// 保存当前选择
var originalSelection = new ModelItemCollection();
originalSelection.CopyFrom(document.CurrentSelection.SelectedItems);
try
{
// 设置新的选择集
OnStatusChanged("正在设置选择集...");
document.CurrentSelection.Clear();
document.CurrentSelection.AddRange(items);
// 等待选择更新
System.Threading.Thread.Sleep(200);
// 导出选中的元素
OnStatusChanged("正在导出选中元素...");
bool exportSuccess = ExportSelectedItems(document, outputPath, config);
return Task.FromResult(exportSuccess);
}
finally
{
// 恢复原始选择
document.CurrentSelection.Clear();
if (originalSelection.Count > 0)
{
document.CurrentSelection.AddRange(originalSelection);
}
}
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 选择集导出失败: {ex.Message}");
throw;
}
}
private async Task<bool> ExportByCopyToNewDocumentAsync(ModelItemCollection items, string outputPath, ExportConfiguration config)
{
try
{
LogManager.Info("[FileExporter] 使用新文档策略导出");
// 注意Navisworks API不直接支持创建新文档并复制元素
// 这里使用可见性控制作为替代方案
LogManager.Warning("[FileExporter] 新文档策略不被直接支持,使用可见性控制替代");
return await ExportByVisibilityControlAsync(items, outputPath, config);
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 新文档导出失败: {ex.Message}");
throw;
}
}
#endregion
#region -
private bool SaveDocument(Document document, string outputPath, ExportConfiguration config)
{
try
{
LogManager.Info($"[FileExporter] 开始保存文档到: {outputPath}");
// 创建备份
if (config.CreateBackup && File.Exists(outputPath))
{
string backupPath = outputPath + ".backup";
File.Copy(outputPath, backupPath, true);
LogManager.Info($"[FileExporter] 已创建备份文件: {backupPath}");
}
// 关键修复:使用更安全的保存方法
bool success = false;
int retryCount = 0;
const int maxRetries = 3;
while (!success && retryCount < maxRetries)
{
try
{
retryCount++;
LogManager.Info($"[FileExporter] 尝试保存文档,第 {retryCount} 次");
// 确保文档处于稳定状态
if (document.Models.Count == 0)
{
throw new InvalidOperationException("文档中没有模型数据");
}
// 等待一小段时间确保状态稳定
System.Threading.Thread.Sleep(100);
// 根据文件格式选择保存方法
string extension = Path.GetExtension(outputPath).ToLower();
switch (extension)
{
case ".nwd":
// 使用最安全的NWD格式保存
document.SaveFile(outputPath);
break;
case ".nwf":
// NWF格式转换为NWD格式更稳定
LogManager.Warning("[FileExporter] NWF格式可能不稳定转换为NWD格式");
string nwdPath = Path.ChangeExtension(outputPath, ".nwd");
document.SaveFile(nwdPath);
// 如果需要NWF格式可以后续转换
if (File.Exists(nwdPath))
{
File.Move(nwdPath, outputPath);
}
break;
case ".nwc":
// NWC格式转换为NWD格式更稳定
LogManager.Warning("[FileExporter] NWC格式可能不稳定转换为NWD格式");
string nwdPath2 = Path.ChangeExtension(outputPath, ".nwd");
document.SaveFile(nwdPath2);
if (File.Exists(nwdPath2))
{
File.Move(nwdPath2, outputPath);
}
break;
default:
// 默认使用NWD格式最稳定
document.SaveFile(outputPath);
break;
}
// 验证文件是否成功保存
if (File.Exists(outputPath))
{
var fileInfo = new FileInfo(outputPath);
if (fileInfo.Length > 0)
{
success = true;
LogManager.Info($"[FileExporter] 文档保存成功: {outputPath}, 大小: {fileInfo.Length} 字节");
}
else
{
LogManager.Warning($"[FileExporter] 保存的文件大小为0: {outputPath}");
File.Delete(outputPath); // 删除空文件
}
}
else
{
LogManager.Warning($"[FileExporter] 保存后文件不存在: {outputPath}");
}
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 第 {retryCount} 次保存尝试失败: {ex.Message}");
// 如果不是最后一次尝试,等待后重试
if (retryCount < maxRetries)
{
LogManager.Info($"[FileExporter] 等待 {retryCount * 1000}ms 后重试...");
System.Threading.Thread.Sleep(retryCount * 1000);
// 尝试清理可能的临时状态
try
{
if (File.Exists(outputPath))
{
var fileInfo = new FileInfo(outputPath);
if (fileInfo.Length == 0)
{
File.Delete(outputPath);
}
}
}
catch (Exception cleanupEx)
{
LogManager.Warning($"[FileExporter] 清理临时文件失败: {cleanupEx.Message}");
}
}
}
}
if (!success)
{
LogManager.Error($"[FileExporter] 经过 {maxRetries} 次尝试后仍然保存失败: {outputPath}");
}
return success;
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 保存文档异常: {ex.Message}");
return false;
}
}
private bool ExportSelectedItems(Document document, string outputPath, ExportConfiguration config)
{
try
{
// 对于选择集导出,我们仍然需要使用可见性控制
// 因为Navisworks不支持直接导出选中的元素
var selectedItems = document.CurrentSelection.SelectedItems;
var task = ExportByVisibilityControlAsync(selectedItems, outputPath, config);
task.Wait();
return task.Result;
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 导出选中元素失败: {ex.Message}");
return false;
}
}
private void SaveAsNwfFormat(Document document, string outputPath)
{
try
{
// NWF是Navisworks的工作文件格式
// 需要使用COM API进行特殊处理
LogManager.Info("[FileExporter] 保存为NWF格式");
document.SaveFile(outputPath);
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 保存NWF格式失败: {ex.Message}");
throw;
}
}
private void SaveAsNwcFormat(Document document, string outputPath)
{
try
{
// NWC是Navisworks的缓存文件格式
LogManager.Info("[FileExporter] 保存为NWC格式");
document.SaveFile(outputPath);
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 保存NWC格式失败: {ex.Message}");
throw;
}
}
#endregion
#region -
private Dictionary<ModelItem, bool> SaveCurrentVisibilityState()
{
try
{
LogManager.Info("[FileExporter] 保存当前可见性状态");
var visibilityState = new Dictionary<ModelItem, bool>();
var document = Application.ActiveDocument;
if (document?.Models != null)
{
var allItems = document.Models.RootItemDescendantsAndSelf;
// 直接执行,避免异步复杂性(.NET Framework 4.6兼容)
foreach (ModelItem item in allItems)
{
try
{
visibilityState[item] = !item.IsHidden;
}
catch
{
// 忽略无法访问的元素
}
}
}
LogManager.Info($"[FileExporter] 已保存 {visibilityState.Count} 个元素的可见性状态");
return visibilityState;
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 保存可见性状态失败: {ex.Message}");
return new Dictionary<ModelItem, bool>();
}
}
private void RestoreVisibilityState(Dictionary<ModelItem, bool> visibilityState)
{
try
{
LogManager.Info("[FileExporter] 恢复可见性状态");
if (visibilityState == null || visibilityState.Count == 0)
{
LogManager.Warning("[FileExporter] 没有可见性状态需要恢复");
return;
}
// 直接执行,避免异步复杂性(.NET Framework 4.6兼容)
var itemsToShow = new ModelItemCollection();
var itemsToHide = new ModelItemCollection();
foreach (var kvp in visibilityState)
{
try
{
if (kvp.Value) // 原来是可见的
{
itemsToShow.Add(kvp.Key);
}
else // 原来是隐藏的
{
itemsToHide.Add(kvp.Key);
}
}
catch
{
// 忽略无法访问的元素
}
}
// 批量恢复可见性 - 使用Document的SetHidden方法
var document = Application.ActiveDocument;
if (document != null)
{
if (itemsToShow.Count > 0)
{
document.Models.SetHidden(itemsToShow, false);
}
if (itemsToHide.Count > 0)
{
document.Models.SetHidden(itemsToHide, true);
}
}
LogManager.Info("[FileExporter] 可见性状态恢复完成");
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 恢复可见性状态失败: {ex.Message}");
}
}
#endregion
#region -
private void ValidateExportParameters(ModelItemCollection items, string outputPath)
{
if (items == null || items.Count == 0)
{
throw new ArgumentException("要导出的模型元素集合不能为空", nameof(items));
}
if (string.IsNullOrEmpty(outputPath))
{
throw new ArgumentException("输出路径不能为空", nameof(outputPath));
}
string directory = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(Path.GetDirectoryName(directory)))
{
throw new DirectoryNotFoundException($"输出目录的父目录不存在: {Path.GetDirectoryName(directory)}");
}
var document = Application.ActiveDocument;
if (document?.Models == null || document.Models.Count == 0)
{
throw new InvalidOperationException("当前没有打开的Navisworks文档");
}
}
private void EnsureOutputDirectory(string outputPath)
{
string directory = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
LogManager.Info($"[FileExporter] 已创建输出目录: {directory}");
}
}
/// <summary>
/// 检查一个模型项或其子项是否包含目标元素中的任何一个
/// </summary>
/// <param name="rootItem">要检查的根模型项</param>
/// <param name="targetItems">目标元素集合</param>
/// <returns>如果包含则返回true</returns>
private bool ContainsAnyTargetItem(ModelItem rootItem, ModelItemCollection targetItems)
{
try
{
// 首先检查根项目本身
if (targetItems.Contains(rootItem))
{
return true;
}
// 递归检查子项目(使用深度优先搜索,但限制深度避免栈溢出)
return ContainsAnyTargetItemRecursive(rootItem, targetItems, 0, 10);
}
catch (Exception ex)
{
LogManager.Error($"[FileExporter] 检查目标元素时出错: {ex.Message}");
// 出错时保守处理,假设包含目标元素
return true;
}
}
/// <summary>
/// 递归检查子项目,带深度限制
/// </summary>
/// <param name="item">当前检查的项目</param>
/// <param name="targetItems">目标元素集合</param>
/// <param name="currentDepth">当前深度</param>
/// <param name="maxDepth">最大深度</param>
/// <returns>如果包含则返回true</returns>
private bool ContainsAnyTargetItemRecursive(ModelItem item, ModelItemCollection targetItems, int currentDepth, int maxDepth)
{
try
{
// 防止递归过深
if (currentDepth > maxDepth)
{
return false;
}
// 检查当前项目的所有子项
if (item.Children != null)
{
foreach (ModelItem child in item.Children)
{
// 检查子项目本身
if (targetItems.Contains(child))
{
return true;
}
// 递归检查子项目的子项
if (ContainsAnyTargetItemRecursive(child, targetItems, currentDepth + 1, maxDepth))
{
return true;
}
}
}
return false;
}
catch (Exception)
{
// 出错时保守处理
return false;
}
}
#endregion
#region
protected virtual void OnStatusChanged(string status)
{
StatusChanged?.Invoke(this, status);
}
protected virtual void OnProgressChanged(int progress)
{
ProgressChanged?.Invoke(this, progress);
}
protected virtual void OnErrorOccurred(Exception ex)
{
ErrorOccurred?.Invoke(this, ex);
}
#endregion
}
}