1054 lines
40 KiB
C#
1054 lines
40 KiB
C#
using Autodesk.Navisworks.Api;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using System.ComponentModel;
|
||
using System.Text;
|
||
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
|
||
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
|
||
using NavisApplication = Autodesk.Navisworks.Api.Application;
|
||
|
||
namespace NavisworksTransport
|
||
{
|
||
/// <summary>
|
||
/// 模型分层拆分管理器 - 负责将大型建筑模型按楼层或自定义属性拆分为多个独立文件
|
||
/// </summary>
|
||
public class ModelSplitterManager
|
||
{
|
||
#region 枚举和数据结构
|
||
|
||
/// <summary>
|
||
/// 分层策略枚举
|
||
/// </summary>
|
||
public enum SplitStrategy
|
||
{
|
||
ByFloor, // 按楼层
|
||
ByCustomAttribute, // 按自定义属性
|
||
ByCategory, // 按类别
|
||
BySelection, // 按选择集
|
||
ByElevation // 按高程范围
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分层配置类
|
||
/// </summary>
|
||
public class SplitConfiguration
|
||
{
|
||
public SplitStrategy Strategy { get; set; } = SplitStrategy.ByFloor;
|
||
public string AttributeName { get; set; } = "Level";
|
||
public string OutputDirectory { get; set; } = "";
|
||
public string FileNamePattern { get; set; } = "{ProjectName}_{LayerName}";
|
||
public bool IncludeEmptyLayers { get; set; } = false;
|
||
public bool PreserveOriginalStructure { get; set; } = true;
|
||
public double ElevationTolerance { get; set; } = 0.5;
|
||
public double MinFloorHeight { get; set; } = 2.5;
|
||
public int MaxLayersPerFile { get; set; } = 1;
|
||
public bool GenerateReport { get; set; } = true;
|
||
public bool CreateSubDirectories { get; set; } = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分层结果类
|
||
/// </summary>
|
||
public class SplitResult
|
||
{
|
||
public string LayerName { get; set; }
|
||
public ModelItemCollection Items { get; set; }
|
||
public string OutputFilePath { 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; }
|
||
public Dictionary<string, object> Metadata { get; set; }
|
||
|
||
public SplitResult()
|
||
{
|
||
Items = new ModelItemCollection();
|
||
Metadata = new Dictionary<string, object>();
|
||
ProcessTime = DateTime.Now;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 楼层信息类
|
||
/// </summary>
|
||
public class FloorInfo
|
||
{
|
||
public string FloorName { get; set; }
|
||
public double Elevation { get; set; }
|
||
public BoundingBox3D Bounds { get; set; }
|
||
public ModelItemCollection Items { get; set; }
|
||
public int ItemCount => Items?.Count ?? 0;
|
||
public Dictionary<string, object> Properties { get; set; }
|
||
|
||
public FloorInfo()
|
||
{
|
||
Items = new ModelItemCollection();
|
||
Properties = new Dictionary<string, object>();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 事件定义
|
||
|
||
public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
|
||
public event EventHandler<SplitResult> LayerProcessed;
|
||
public event EventHandler<List<SplitResult>> SplitCompleted;
|
||
public event EventHandler<string> StatusChanged;
|
||
public event EventHandler<Exception> ErrorOccurred;
|
||
|
||
#endregion
|
||
|
||
#region 私有字段
|
||
|
||
private readonly FloorDetector _floorDetector;
|
||
private readonly AttributeGrouper _attributeGrouper;
|
||
private readonly NavisworksFileExporter _fileExporter;
|
||
private bool _isSplitting = false;
|
||
|
||
#endregion
|
||
|
||
#region 构造函数
|
||
|
||
public ModelSplitterManager()
|
||
{
|
||
_floorDetector = new FloorDetector();
|
||
_attributeGrouper = new AttributeGrouper();
|
||
_fileExporter = new NavisworksFileExporter();
|
||
|
||
LogManager.Info("[ModelSplitter] 模型分层管理器已初始化");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 公共方法
|
||
|
||
/// <summary>
|
||
/// 预览分层结果
|
||
/// </summary>
|
||
public List<SplitResult> PreviewSplit(SplitConfiguration config)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[ModelSplitter] 开始预览分层,策略: {config.Strategy}");
|
||
OnStatusChanged("正在分析模型结构...");
|
||
|
||
var allItems = GetAllModelItems();
|
||
if (allItems.Count == 0)
|
||
{
|
||
throw new InvalidOperationException("当前文档中没有模型元素");
|
||
}
|
||
|
||
var previewResults = new List<SplitResult>();
|
||
|
||
switch (config.Strategy)
|
||
{
|
||
case SplitStrategy.ByFloor:
|
||
previewResults = PreviewSplitByFloor(allItems, config);
|
||
break;
|
||
case SplitStrategy.ByCustomAttribute:
|
||
previewResults = PreviewSplitByAttribute(allItems, config);
|
||
break;
|
||
case SplitStrategy.ByCategory:
|
||
previewResults = PreviewSplitByCategory(allItems, config);
|
||
break;
|
||
case SplitStrategy.ByElevation:
|
||
previewResults = PreviewSplitByElevation(allItems, config);
|
||
break;
|
||
default:
|
||
throw new NotSupportedException($"不支持的分层策略: {config.Strategy}");
|
||
}
|
||
|
||
LogManager.Info($"[ModelSplitter] 预览完成,共识别 {previewResults.Count} 个分层");
|
||
OnStatusChanged($"预览完成,识别到 {previewResults.Count} 个分层");
|
||
|
||
return previewResults;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[ModelSplitter] 预览分层失败: {ex.Message}");
|
||
OnErrorOccurred(ex);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行分层拆分
|
||
/// </summary>
|
||
public async Task<List<SplitResult>> ExecuteSplitAsync(SplitConfiguration config)
|
||
{
|
||
if (_isSplitting)
|
||
{
|
||
throw new InvalidOperationException("分层操作正在进行中");
|
||
}
|
||
|
||
try
|
||
{
|
||
_isSplitting = true;
|
||
|
||
// 写入会话分隔符,标记新的分层操作开始
|
||
LogManager.WriteSessionSeparator();
|
||
LogManager.Info($"[ModelSplitter] ========== 开始执行分层拆分 ==========");
|
||
LogManager.Info($"[ModelSplitter] 分层策略: {config.Strategy}");
|
||
LogManager.Info($"[ModelSplitter] 输出目录: {config.OutputDirectory}");
|
||
LogManager.Info($"[ModelSplitter] 属性名称: {config.AttributeName}");
|
||
LogManager.Info($"[ModelSplitter] 文件命名模式: {config.FileNamePattern}");
|
||
LogManager.Info($"[ModelSplitter] 包含空分层: {config.IncludeEmptyLayers}");
|
||
LogManager.Info($"[ModelSplitter] 创建子目录: {config.CreateSubDirectories}");
|
||
LogManager.Info($"[ModelSplitter] 生成报告: {config.GenerateReport}");
|
||
|
||
OnStatusChanged("正在执行分层拆分...");
|
||
|
||
// 验证配置
|
||
LogManager.Info("[ModelSplitter] 开始验证配置...");
|
||
ValidateConfiguration(config);
|
||
LogManager.Info("[ModelSplitter] 配置验证完成");
|
||
|
||
// 创建输出目录
|
||
LogManager.Info("[ModelSplitter] 开始创建输出目录...");
|
||
EnsureOutputDirectory(config.OutputDirectory);
|
||
LogManager.Info("[ModelSplitter] 输出目录创建完成");
|
||
|
||
// 获取预览结果
|
||
LogManager.Info("[ModelSplitter] 开始获取预览结果...");
|
||
var previewResults = PreviewSplit(config);
|
||
LogManager.Info($"[ModelSplitter] 预览结果获取完成,共 {previewResults.Count} 个分层");
|
||
|
||
var results = new List<SplitResult>();
|
||
int processedCount = 0;
|
||
int totalCount = previewResults.Count;
|
||
|
||
// 记录系统状态
|
||
LogManager.Info($"[ModelSplitter] 系统内存使用: {GC.GetTotalMemory(false) / 1024 / 1024} MB");
|
||
LogManager.Info($"[ModelSplitter] 当前进程内存: {System.Diagnostics.Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024} MB");
|
||
|
||
// 关键修复:添加内存管理和错误恢复机制
|
||
foreach (var preview in previewResults)
|
||
{
|
||
try
|
||
{
|
||
OnStatusChanged($"正在处理分层: {preview.LayerName} ({processedCount + 1}/{totalCount})");
|
||
OnProgressChanged(new ProgressChangedEventArgs(
|
||
(int)((double)processedCount / totalCount * 100),
|
||
$"处理分层 {processedCount + 1}/{totalCount}"));
|
||
|
||
// 在每个分层处理前,确保Navisworks处于稳定状态
|
||
EnsureStableState();
|
||
|
||
var result = await ProcessSingleLayerAsync(preview, config);
|
||
results.Add(result);
|
||
|
||
OnLayerProcessed(result);
|
||
processedCount++;
|
||
|
||
LogManager.Info($"[ModelSplitter] 已处理分层: {result.LayerName}, 成功: {result.Success}");
|
||
|
||
// 在每个分层处理后,强制垃圾回收以释放内存
|
||
if (processedCount % 3 == 0) // 每处理3个分层进行一次垃圾回收
|
||
{
|
||
GC.Collect();
|
||
GC.WaitForPendingFinalizers();
|
||
GC.Collect();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[ModelSplitter] 处理分层失败: {preview.LayerName}, 错误: {ex.Message}");
|
||
|
||
// 尝试恢复Navisworks状态
|
||
try
|
||
{
|
||
RecoverFromError();
|
||
}
|
||
catch (Exception recoverEx)
|
||
{
|
||
LogManager.Error($"[ModelSplitter] 状态恢复失败: {recoverEx.Message}");
|
||
}
|
||
|
||
var errorResult = new SplitResult
|
||
{
|
||
LayerName = preview.LayerName,
|
||
Items = preview.Items,
|
||
Success = false,
|
||
ErrorMessage = ex.Message
|
||
};
|
||
results.Add(errorResult);
|
||
|
||
// 如果连续失败超过一定次数,停止处理
|
||
int consecutiveFailures = results.Skip(Math.Max(0, results.Count - 3)).Count(r => !r.Success);
|
||
if (consecutiveFailures >= 3)
|
||
{
|
||
LogManager.Warning("[ModelSplitter] 连续失败次数过多,停止处理剩余分层");
|
||
OnStatusChanged("检测到连续失败,停止处理以保护系统稳定性");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 生成汇总报告
|
||
if (config.GenerateReport)
|
||
{
|
||
try
|
||
{
|
||
await GenerateSummaryReportAsync(results, config);
|
||
}
|
||
catch (Exception reportEx)
|
||
{
|
||
LogManager.Warning($"[ModelSplitter] 生成报告失败: {reportEx.Message}");
|
||
}
|
||
}
|
||
|
||
OnProgressChanged(new ProgressChangedEventArgs(100, "分层拆分完成"));
|
||
OnStatusChanged($"分层拆分完成,成功: {results.Count(r => r.Success)}, 失败: {results.Count(r => !r.Success)}");
|
||
OnSplitCompleted(results);
|
||
|
||
LogManager.Info($"[ModelSplitter] 分层拆分完成,总计: {results.Count}, 成功: {results.Count(r => r.Success)}");
|
||
|
||
return results;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[ModelSplitter] 执行分层拆分失败: {ex.Message}");
|
||
|
||
// 尝试最终恢复
|
||
try
|
||
{
|
||
RecoverFromError();
|
||
}
|
||
catch (Exception recoverEx)
|
||
{
|
||
LogManager.Error($"[ModelSplitter] 最终状态恢复失败: {recoverEx.Message}");
|
||
}
|
||
|
||
OnErrorOccurred(ex);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
_isSplitting = false;
|
||
|
||
// 最终清理
|
||
try
|
||
{
|
||
FinalCleanup();
|
||
}
|
||
catch (Exception cleanupEx)
|
||
{
|
||
LogManager.Warning($"[ModelSplitter] 最终清理失败: {cleanupEx.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取可用的楼层属性列表
|
||
/// </summary>
|
||
public List<string> GetAvailableFloorAttributes()
|
||
{
|
||
try
|
||
{
|
||
var allItems = GetAllModelItems();
|
||
return _floorDetector.GetAvailableFloorAttributes(allItems);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[ModelSplitter] 获取楼层属性失败: {ex.Message}");
|
||
return new List<string>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取可用的自定义属性列表
|
||
/// </summary>
|
||
public List<string> GetAvailableCustomAttributes()
|
||
{
|
||
try
|
||
{
|
||
var allItems = GetAllModelItems();
|
||
return _attributeGrouper.GetAvailableAttributes(allItems);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[ModelSplitter] 获取自定义属性失败: {ex.Message}");
|
||
return new List<string>();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法 - 预览功能
|
||
|
||
private List<SplitResult> PreviewSplitByFloor(ModelItemCollection allItems, SplitConfiguration config)
|
||
{
|
||
var floors = _floorDetector.DetectFloors(allItems, config.AttributeName);
|
||
var results = new List<SplitResult>();
|
||
|
||
foreach (var floor in floors)
|
||
{
|
||
if (!config.IncludeEmptyLayers && floor.ItemCount == 0)
|
||
continue;
|
||
|
||
var result = new SplitResult
|
||
{
|
||
LayerName = floor.FloorName,
|
||
Items = floor.Items,
|
||
ItemCount = floor.ItemCount,
|
||
OutputFilePath = GenerateOutputFilePath(floor.FloorName, config),
|
||
Metadata = new Dictionary<string, object>
|
||
{
|
||
["Elevation"] = floor.Elevation,
|
||
["Bounds"] = floor.Bounds,
|
||
["Properties"] = floor.Properties
|
||
}
|
||
};
|
||
|
||
results.Add(result);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
private List<SplitResult> PreviewSplitByAttribute(ModelItemCollection allItems, SplitConfiguration config)
|
||
{
|
||
var groups = _attributeGrouper.GroupByAttribute(allItems, config.AttributeName);
|
||
var results = new List<SplitResult>();
|
||
|
||
foreach (var group in groups)
|
||
{
|
||
if (!config.IncludeEmptyLayers && group.Items.Count == 0)
|
||
continue;
|
||
|
||
var result = new SplitResult
|
||
{
|
||
LayerName = SanitizeLayerName(group.AttributeValue),
|
||
Items = group.Items,
|
||
ItemCount = group.Items.Count,
|
||
OutputFilePath = GenerateOutputFilePath(group.AttributeValue, config),
|
||
Metadata = group.Metadata
|
||
};
|
||
|
||
results.Add(result);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
private List<SplitResult> PreviewSplitByCategory(ModelItemCollection allItems, SplitConfiguration config)
|
||
{
|
||
var categoryGroups = new Dictionary<string, ModelItemCollection>();
|
||
|
||
foreach (ModelItem item in allItems)
|
||
{
|
||
string category = GetItemCategory(item);
|
||
if (string.IsNullOrEmpty(category))
|
||
category = "未分类";
|
||
|
||
if (!categoryGroups.ContainsKey(category))
|
||
{
|
||
categoryGroups[category] = new ModelItemCollection();
|
||
}
|
||
categoryGroups[category].Add(item);
|
||
}
|
||
|
||
var results = new List<SplitResult>();
|
||
foreach (var kvp in categoryGroups)
|
||
{
|
||
if (!config.IncludeEmptyLayers && kvp.Value.Count == 0)
|
||
continue;
|
||
|
||
var result = new SplitResult
|
||
{
|
||
LayerName = kvp.Key,
|
||
Items = kvp.Value,
|
||
ItemCount = kvp.Value.Count,
|
||
OutputFilePath = GenerateOutputFilePath(kvp.Key, config)
|
||
};
|
||
|
||
results.Add(result);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
private List<SplitResult> PreviewSplitByElevation(ModelItemCollection allItems, SplitConfiguration config)
|
||
{
|
||
var elevationGroups = new Dictionary<string, ModelItemCollection>();
|
||
|
||
foreach (ModelItem item in allItems)
|
||
{
|
||
var bounds = item.BoundingBox();
|
||
if (bounds.HasVolume)
|
||
{
|
||
double elevation = bounds.Min.Z;
|
||
string elevationRange = GetElevationRange(elevation, config.MinFloorHeight);
|
||
|
||
if (!elevationGroups.ContainsKey(elevationRange))
|
||
{
|
||
elevationGroups[elevationRange] = new ModelItemCollection();
|
||
}
|
||
elevationGroups[elevationRange].Add(item);
|
||
}
|
||
}
|
||
|
||
var results = new List<SplitResult>();
|
||
foreach (var kvp in elevationGroups.OrderBy(x => x.Key))
|
||
{
|
||
if (!config.IncludeEmptyLayers && kvp.Value.Count == 0)
|
||
continue;
|
||
|
||
var result = new SplitResult
|
||
{
|
||
LayerName = $"Elevation_{kvp.Key}",
|
||
Items = kvp.Value,
|
||
ItemCount = kvp.Value.Count,
|
||
OutputFilePath = GenerateOutputFilePath($"Elevation_{kvp.Key}", config)
|
||
};
|
||
|
||
results.Add(result);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法 - 处理功能
|
||
|
||
private async Task<SplitResult> ProcessSingleLayerAsync(SplitResult preview, SplitConfiguration config)
|
||
{
|
||
LogManager.Info($"[ModelSplitter] ========== 开始处理分层: {preview.LayerName} ==========");
|
||
LogManager.Info($"[ModelSplitter] 分层元素数量: {preview.ItemCount}");
|
||
LogManager.Info($"[ModelSplitter] 输出文件路径: {preview.OutputFilePath}");
|
||
|
||
var result = new SplitResult
|
||
{
|
||
LayerName = preview.LayerName,
|
||
Items = preview.Items,
|
||
ItemCount = preview.ItemCount,
|
||
OutputFilePath = preview.OutputFilePath,
|
||
Metadata = preview.Metadata
|
||
};
|
||
|
||
try
|
||
{
|
||
// 记录处理前的内存状态
|
||
LogManager.Info($"[ModelSplitter] 处理前内存使用: {GC.GetTotalMemory(false) / 1024 / 1024} MB");
|
||
|
||
// 创建子目录
|
||
if (config.CreateSubDirectories)
|
||
{
|
||
LogManager.Info("[ModelSplitter] 开始创建子目录...");
|
||
string layerDir = Path.Combine(config.OutputDirectory, SanitizeFileName(preview.LayerName));
|
||
Directory.CreateDirectory(layerDir);
|
||
result.OutputFilePath = Path.Combine(layerDir, Path.GetFileName(result.OutputFilePath));
|
||
LogManager.Info($"[ModelSplitter] 子目录创建完成: {layerDir}");
|
||
}
|
||
|
||
// 导出文件
|
||
LogManager.Info("[ModelSplitter] 开始导出文件...");
|
||
LogManager.Info($"[ModelSplitter] 目标文件: {result.OutputFilePath}");
|
||
|
||
bool exportSuccess = await _fileExporter.ExportModelItemsAsync(preview.Items, result.OutputFilePath);
|
||
|
||
LogManager.Info($"[ModelSplitter] 文件导出结果: {exportSuccess}");
|
||
|
||
if (exportSuccess && File.Exists(result.OutputFilePath))
|
||
{
|
||
result.Success = true;
|
||
result.FileSizeBytes = new FileInfo(result.OutputFilePath).Length;
|
||
LogManager.Info($"[ModelSplitter] 导出成功,文件大小: {result.FileSizeBytes / 1024.0 / 1024.0:F2} MB");
|
||
|
||
// 生成元数据文件
|
||
if (config.GenerateReport)
|
||
{
|
||
LogManager.Info("[ModelSplitter] 开始生成元数据文件...");
|
||
await GenerateLayerMetadataAsync(result, config);
|
||
LogManager.Info("[ModelSplitter] 元数据文件生成完成");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
result.Success = false;
|
||
result.ErrorMessage = "文件导出失败";
|
||
LogManager.Error($"[ModelSplitter] 文件导出失败: exportSuccess={exportSuccess}, 文件存在={File.Exists(result.OutputFilePath)}");
|
||
}
|
||
|
||
// 记录处理后的内存状态
|
||
LogManager.Info($"[ModelSplitter] 处理后内存使用: {GC.GetTotalMemory(false) / 1024 / 1024} MB");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
result.Success = false;
|
||
result.ErrorMessage = ex.Message;
|
||
LogManager.Error($"[ModelSplitter] 处理分层异常: {ex.Message}");
|
||
LogManager.Error($"[ModelSplitter] 异常堆栈: {ex.StackTrace}");
|
||
}
|
||
|
||
LogManager.Info($"[ModelSplitter] ========== 分层处理完成: {preview.LayerName}, 成功: {result.Success} ==========");
|
||
return result;
|
||
}
|
||
|
||
private async Task GenerateLayerMetadataAsync(SplitResult result, SplitConfiguration config)
|
||
{
|
||
try
|
||
{
|
||
string metadataPath = Path.ChangeExtension(result.OutputFilePath, ".json");
|
||
|
||
var metadata = new
|
||
{
|
||
LayerName = result.LayerName,
|
||
ItemCount = result.ItemCount,
|
||
FileSizeBytes = result.FileSizeBytes,
|
||
ProcessTime = result.ProcessTime,
|
||
Configuration = new
|
||
{
|
||
Strategy = config.Strategy.ToString(),
|
||
AttributeName = config.AttributeName,
|
||
ElevationTolerance = config.ElevationTolerance
|
||
},
|
||
Properties = result.Metadata
|
||
};
|
||
|
||
// 使用简单的JSON格式化,避免依赖外部库
|
||
var jsonBuilder = new StringBuilder();
|
||
jsonBuilder.AppendLine("{");
|
||
jsonBuilder.AppendLine($" \"LayerName\": \"{EscapeJsonString(result.LayerName)}\",");
|
||
jsonBuilder.AppendLine($" \"ItemCount\": {result.ItemCount},");
|
||
jsonBuilder.AppendLine($" \"FileSizeBytes\": {result.FileSizeBytes},");
|
||
jsonBuilder.AppendLine($" \"ProcessTime\": \"{result.ProcessTime:yyyy-MM-dd HH:mm:ss}\",");
|
||
jsonBuilder.AppendLine(" \"Configuration\": {");
|
||
jsonBuilder.AppendLine($" \"Strategy\": \"{config.Strategy}\",");
|
||
jsonBuilder.AppendLine($" \"AttributeName\": \"{EscapeJsonString(config.AttributeName)}\",");
|
||
jsonBuilder.AppendLine($" \"ElevationTolerance\": {config.ElevationTolerance}");
|
||
jsonBuilder.AppendLine(" }");
|
||
jsonBuilder.AppendLine("}");
|
||
|
||
await Task.Run(() => File.WriteAllText(metadataPath, jsonBuilder.ToString()));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[ModelSplitter] 生成元数据文件失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private async Task GenerateSummaryReportAsync(List<SplitResult> results, SplitConfiguration config)
|
||
{
|
||
try
|
||
{
|
||
string reportPath = Path.Combine(config.OutputDirectory, "split_summary_report.html");
|
||
|
||
var html = await Task.Run(() => GenerateHtmlReport(results, config));
|
||
await Task.Run(() => File.WriteAllText(reportPath, html));
|
||
|
||
LogManager.Info($"[ModelSplitter] 汇总报告已生成: {reportPath}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[ModelSplitter] 生成汇总报告失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法 - 辅助功能
|
||
|
||
private ModelItemCollection GetAllModelItems()
|
||
{
|
||
var document = NavisApplication.ActiveDocument;
|
||
if (document?.Models == null || document.Models.Count == 0)
|
||
{
|
||
return new ModelItemCollection();
|
||
}
|
||
|
||
var allItems = new ModelItemCollection();
|
||
allItems.AddRange(document.Models.RootItemDescendantsAndSelf);
|
||
return allItems;
|
||
}
|
||
|
||
private void ValidateConfiguration(SplitConfiguration config)
|
||
{
|
||
if (string.IsNullOrEmpty(config.OutputDirectory))
|
||
{
|
||
throw new ArgumentException("输出目录不能为空");
|
||
}
|
||
|
||
if (config.Strategy == SplitStrategy.ByCustomAttribute && string.IsNullOrEmpty(config.AttributeName))
|
||
{
|
||
throw new ArgumentException("按自定义属性分层时,属性名称不能为空");
|
||
}
|
||
|
||
if (!Directory.Exists(Path.GetDirectoryName(config.OutputDirectory)))
|
||
{
|
||
throw new DirectoryNotFoundException($"输出目录的父目录不存在: {Path.GetDirectoryName(config.OutputDirectory)}");
|
||
}
|
||
}
|
||
|
||
private void EnsureOutputDirectory(string outputDirectory)
|
||
{
|
||
if (!Directory.Exists(outputDirectory))
|
||
{
|
||
Directory.CreateDirectory(outputDirectory);
|
||
LogManager.Info($"[ModelSplitter] 已创建输出目录: {outputDirectory}");
|
||
}
|
||
}
|
||
|
||
private string GenerateOutputFilePath(string layerName, SplitConfiguration config)
|
||
{
|
||
string sanitizedLayerName = SanitizeFileName(layerName);
|
||
string projectName = GetProjectName();
|
||
|
||
string fileName = config.FileNamePattern
|
||
.Replace("{ProjectName}", projectName)
|
||
.Replace("{LayerName}", sanitizedLayerName)
|
||
.Replace("{DateTime}", DateTime.Now.ToString("yyyyMMdd_HHmmss"));
|
||
|
||
return Path.Combine(config.OutputDirectory, fileName + ".nwd");
|
||
}
|
||
|
||
private string GetProjectName()
|
||
{
|
||
var document = Application.ActiveDocument;
|
||
if (document?.FileName != null)
|
||
{
|
||
return Path.GetFileNameWithoutExtension(document.FileName);
|
||
}
|
||
return "NavisworksModel";
|
||
}
|
||
|
||
private string SanitizeFileName(string fileName)
|
||
{
|
||
if (string.IsNullOrEmpty(fileName))
|
||
return "Unknown";
|
||
|
||
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||
foreach (char c in invalidChars)
|
||
{
|
||
fileName = fileName.Replace(c, '_');
|
||
}
|
||
|
||
return fileName.Trim();
|
||
}
|
||
|
||
private string SanitizeLayerName(string layerName)
|
||
{
|
||
if (string.IsNullOrEmpty(layerName))
|
||
return "Unknown";
|
||
|
||
return layerName.Trim().Replace(" ", "_");
|
||
}
|
||
|
||
private string GetItemCategory(ModelItem item)
|
||
{
|
||
try
|
||
{
|
||
// 尝试从属性中获取类别信息
|
||
foreach (PropertyCategory category in item.PropertyCategories)
|
||
{
|
||
foreach (DataProperty property in category.Properties)
|
||
{
|
||
if (property.DisplayName.ToLower().Contains("category") ||
|
||
property.DisplayName.ToLower().Contains("type") ||
|
||
property.DisplayName.ToLower().Contains("family"))
|
||
{
|
||
return property.Value.ToString();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没有找到类别属性,使用显示名称的第一部分
|
||
string displayName = item.DisplayName;
|
||
if (!string.IsNullOrEmpty(displayName))
|
||
{
|
||
var parts = displayName.Split(':', '_', '-');
|
||
if (parts.Length > 0)
|
||
{
|
||
return parts[0].Trim();
|
||
}
|
||
}
|
||
|
||
return "未分类";
|
||
}
|
||
catch
|
||
{
|
||
return "未分类";
|
||
}
|
||
}
|
||
|
||
private string GetElevationRange(double elevation, double rangeSize)
|
||
{
|
||
double rangeStart = Math.Floor(elevation / rangeSize) * rangeSize;
|
||
double rangeEnd = rangeStart + rangeSize;
|
||
return $"{rangeStart:F1}_{rangeEnd:F1}";
|
||
}
|
||
|
||
private string EscapeJsonString(string input)
|
||
{
|
||
if (string.IsNullOrEmpty(input))
|
||
return "";
|
||
|
||
return input.Replace("\\", "\\\\")
|
||
.Replace("\"", "\\\"")
|
||
.Replace("\r", "\\r")
|
||
.Replace("\n", "\\n")
|
||
.Replace("\t", "\\t");
|
||
}
|
||
|
||
private string GenerateHtmlReport(List<SplitResult> results, SplitConfiguration config)
|
||
{
|
||
var html = $@"
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>模型分层拆分报告</title>
|
||
<meta charset='utf-8'>
|
||
<style>
|
||
body {{ font-family: Arial, sans-serif; margin: 20px; }}
|
||
.header {{ background-color: #f0f0f0; padding: 15px; border-radius: 5px; }}
|
||
.summary {{ margin: 20px 0; }}
|
||
.results {{ margin-top: 20px; }}
|
||
table {{ border-collapse: collapse; width: 100%; }}
|
||
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
|
||
th {{ background-color: #f2f2f2; }}
|
||
.success {{ color: green; }}
|
||
.error {{ color: red; }}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class='header'>
|
||
<h1>模型分层拆分报告</h1>
|
||
<p>生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}</p>
|
||
<p>分层策略: {config.Strategy}</p>
|
||
<p>输出目录: {config.OutputDirectory}</p>
|
||
</div>
|
||
|
||
<div class='summary'>
|
||
<h2>汇总信息</h2>
|
||
<p>总分层数: {results.Count}</p>
|
||
<p>成功: <span class='success'>{results.Count(r => r.Success)}</span></p>
|
||
<p>失败: <span class='error'>{results.Count(r => !r.Success)}</span></p>
|
||
<p>总文件大小: {results.Where(r => r.Success).Sum(r => r.FileSizeBytes) / 1024.0 / 1024.0:F2} MB</p>
|
||
</div>
|
||
|
||
<div class='results'>
|
||
<h2>详细结果</h2>
|
||
<table>
|
||
<tr>
|
||
<th>分层名称</th>
|
||
<th>元素数量</th>
|
||
<th>文件大小(MB)</th>
|
||
<th>状态</th>
|
||
<th>输出文件</th>
|
||
</tr>";
|
||
|
||
foreach (var result in results)
|
||
{
|
||
string statusClass = result.Success ? "success" : "error";
|
||
string status = result.Success ? "成功" : $"失败: {result.ErrorMessage}";
|
||
double fileSizeMB = result.FileSizeBytes / 1024.0 / 1024.0;
|
||
|
||
html += $@"
|
||
<tr>
|
||
<td>{result.LayerName}</td>
|
||
<td>{result.ItemCount}</td>
|
||
<td>{fileSizeMB:F2}</td>
|
||
<td class='{statusClass}'>{status}</td>
|
||
<td>{Path.GetFileName(result.OutputFilePath)}</td>
|
||
</tr>";
|
||
}
|
||
|
||
html += @"
|
||
</table>
|
||
</div>
|
||
</body>
|
||
</html>";
|
||
|
||
return html;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保Navisworks处于稳定状态 - 使用最安全的方法
|
||
/// </summary>
|
||
private void EnsureStableState()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[ModelSplitter] 开始确保Navisworks稳定状态...");
|
||
|
||
var document = NavisApplication.ActiveDocument;
|
||
if (document != null)
|
||
{
|
||
LogManager.Info($"[ModelSplitter] 当前文档: {document.FileName ?? "未命名"}");
|
||
LogManager.Info($"[ModelSplitter] 模型数量: {document.Models?.Count ?? 0}");
|
||
|
||
// 记录当前状态
|
||
if (document.CurrentSelection != null)
|
||
{
|
||
LogManager.Info($"[ModelSplitter] 当前选择项数量: {document.CurrentSelection.SelectedItems.Count}");
|
||
}
|
||
|
||
// 关键修复:完全跳过可见性重置,避免崩溃
|
||
LogManager.Info("[ModelSplitter] 跳过可见性重置以避免崩溃风险");
|
||
LogManager.Info("[ModelSplitter] 使用选择集策略替代可见性控制");
|
||
|
||
// 只进行安全的操作
|
||
try
|
||
{
|
||
// 清除当前选择,这是安全的操作
|
||
LogManager.Info("[ModelSplitter] 清除当前选择...");
|
||
document.CurrentSelection.Clear();
|
||
LogManager.Info("[ModelSplitter] 选择清除完成");
|
||
|
||
// 短暂等待,让Navisworks稳定
|
||
LogManager.Info("[ModelSplitter] 等待系统稳定...");
|
||
System.Threading.Thread.Sleep(100);
|
||
|
||
LogManager.Info("[ModelSplitter] Navisworks稳定状态确保完成");
|
||
}
|
||
catch (Exception safeEx)
|
||
{
|
||
LogManager.Warning($"[ModelSplitter] 安全操作失败: {safeEx.Message}");
|
||
LogManager.Info("[ModelSplitter] 继续处理,忽略稳定状态错误");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[ModelSplitter] 当前没有活动的Navisworks文档");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[ModelSplitter] 确保稳定状态失败: {ex.Message}");
|
||
LogManager.Error($"[ModelSplitter] 稳定状态异常堆栈: {ex.StackTrace}");
|
||
LogManager.Info("[ModelSplitter] 继续处理,忽略稳定状态错误");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从错误中恢复
|
||
/// </summary>
|
||
private void RecoverFromError()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[ModelSplitter] 开始错误恢复...");
|
||
|
||
var document = NavisApplication.ActiveDocument;
|
||
if (document != null)
|
||
{
|
||
// 使用安全的可见性重置方法
|
||
LogManager.Info("[ModelSplitter] 错误恢复:重置可见性状态...");
|
||
try
|
||
{
|
||
document.Models.ResetAllHidden();
|
||
LogManager.Info("[ModelSplitter] 错误恢复:ResetAllHidden成功");
|
||
}
|
||
catch (Exception resetEx)
|
||
{
|
||
LogManager.Warning($"[ModelSplitter] 错误恢复:ResetAllHidden失败: {resetEx.Message}");
|
||
// 在错误恢复中,如果重置失败就跳过,不再尝试替代方法
|
||
LogManager.Info("[ModelSplitter] 错误恢复:跳过可见性重置");
|
||
}
|
||
|
||
// 清除选择
|
||
document.CurrentSelection.Clear();
|
||
|
||
// 强制刷新视图
|
||
document.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
|
||
// 等待恢复完成
|
||
System.Threading.Thread.Sleep(500);
|
||
}
|
||
|
||
// 强制垃圾回收
|
||
GC.Collect();
|
||
GC.WaitForPendingFinalizers();
|
||
GC.Collect();
|
||
|
||
LogManager.Info("[ModelSplitter] 错误恢复完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[ModelSplitter] 错误恢复失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 最终清理
|
||
/// </summary>
|
||
private void FinalCleanup()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[ModelSplitter] 开始最终清理...");
|
||
|
||
var document = NavisApplication.ActiveDocument;
|
||
if (document != null)
|
||
{
|
||
// 使用安全的可见性重置方法
|
||
LogManager.Info("[ModelSplitter] 最终清理:重置可见性状态...");
|
||
try
|
||
{
|
||
document.Models.ResetAllHidden();
|
||
LogManager.Info("[ModelSplitter] 最终清理:ResetAllHidden成功");
|
||
}
|
||
catch (Exception resetEx)
|
||
{
|
||
LogManager.Warning($"[ModelSplitter] 最终清理:ResetAllHidden失败: {resetEx.Message}");
|
||
LogManager.Info("[ModelSplitter] 最终清理:跳过可见性重置");
|
||
}
|
||
|
||
// 清除选择
|
||
document.CurrentSelection.Clear();
|
||
|
||
// 刷新视图
|
||
document.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
}
|
||
|
||
// 最终垃圾回收
|
||
GC.Collect();
|
||
GC.WaitForPendingFinalizers();
|
||
GC.Collect();
|
||
|
||
LogManager.Info("[ModelSplitter] 最终清理完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[ModelSplitter] 最终清理失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 事件触发方法
|
||
|
||
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
|
||
{
|
||
ProgressChanged?.Invoke(this, e);
|
||
}
|
||
|
||
protected virtual void OnLayerProcessed(SplitResult result)
|
||
{
|
||
LayerProcessed?.Invoke(this, result);
|
||
}
|
||
|
||
protected virtual void OnSplitCompleted(List<SplitResult> results)
|
||
{
|
||
SplitCompleted?.Invoke(this, results);
|
||
}
|
||
|
||
protected virtual void OnStatusChanged(string status)
|
||
{
|
||
StatusChanged?.Invoke(this, status);
|
||
}
|
||
|
||
protected virtual void OnErrorOccurred(Exception ex)
|
||
{
|
||
ErrorOccurred?.Invoke(this, ex);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |