核心改进: 1. ExportLayerToNwd专注导出逻辑,不再管理进度条 2. LayerManagementViewModel统一管理批量导出进度条 3. 状态栏只在开始/结束时显示,避免UI刷新同步问题 修改内容: **ModelSplitterManager.cs** - 移除ExportLayerToNwd内的Progress API相关代码 - 移除进度条声明、初始化、更新和清理代码 - 移除用户取消检查(由调用者管理) - 保留核心导出逻辑:隔离、导出、恢复 **LayerManagementViewModel.cs** - 在批量导出循环开始前创建Progress API进度条 - 循环中为每个文件更新进度条描述和百分比 - 显示:"正在导出第 X/Y 个分层:分层名" - 移除循环中的状态栏进度更新(避免UI同步问题) - 只在结束时更新状态栏最终结果 - finally块确保进度条被正确关闭 最终效果: - ✅ 单文件导出:无进度条,快速完成 - ✅ 批量导出:统一的Progress API显示完整进度信息 - ✅ 状态栏:只显示开始/结束提示,不跟踪过程进度 - ✅ 职责清晰:导出逻辑与进度显示分离 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2251 lines
95 KiB
C#
2251 lines
95 KiB
C#
using Autodesk.Navisworks.Api;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Threading.Tasks;
|
||
using System.ComponentModel;
|
||
using System.Threading;
|
||
using System.Linq;
|
||
using NavisApplication = Autodesk.Navisworks.Api.Application;
|
||
using NavisworksTransport.Core;
|
||
|
||
namespace NavisworksTransport
|
||
{
|
||
/// <summary>
|
||
/// 简化版模型分层管理器 - 为新UI设计的轻量级分层管理
|
||
/// 功能范围:楼层分析、属性设置、分层保存、选中保存
|
||
/// </summary>
|
||
public class ModelSplitterManager
|
||
{
|
||
#region 枚举和数据结构
|
||
|
||
/// <summary>
|
||
/// 简化的分层策略
|
||
/// </summary>
|
||
public enum SplitStrategy
|
||
{
|
||
BySmartFloorDetection, // 智能检测楼层(原ByFloor)
|
||
ByFloorAttribute, // 按楼层属性(原ByCustomFloor)
|
||
ByZoneAttribute, // 按区域属性
|
||
BySubSystemAttribute // 按子系统属性
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分层分组策略接口
|
||
/// </summary>
|
||
private interface IGroupingStrategy
|
||
{
|
||
string GroupTypeName { get; }
|
||
string UnclassifiedLabel { get; }
|
||
Dictionary<string, GroupItem> GroupItems(ModelItemCollection items, SplitConfiguration config);
|
||
|
||
/// <summary>
|
||
/// 提取单个节点的属性值,用于智能遍历
|
||
/// </summary>
|
||
string ExtractAttributeValue(ModelItem node);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分组项信息
|
||
/// </summary>
|
||
private class GroupItem
|
||
{
|
||
public string GroupName { get; set; }
|
||
public string OriginalName { get; set; }
|
||
public List<ModelItem> Items { get; set; } = new List<ModelItem>();
|
||
public Dictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 楼层检测分组策略 - 使用统一的深度遍历结果
|
||
/// </summary>
|
||
private class FloorDetectionStrategy : IGroupingStrategy
|
||
{
|
||
private readonly FloorDetector _floorDetector;
|
||
|
||
public string GroupTypeName => "楼层";
|
||
public string UnclassifiedLabel => "未检测到楼层";
|
||
|
||
public FloorDetectionStrategy(FloorDetector floorDetector)
|
||
{
|
||
_floorDetector = floorDetector;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 实现IGroupingStrategy接口的ExtractAttributeValue方法
|
||
/// 使用FloorDetector进行智能楼层检测
|
||
/// </summary>
|
||
public string ExtractAttributeValue(ModelItem node)
|
||
{
|
||
try
|
||
{
|
||
// 使用FloorDetector的通用属性检测逻辑
|
||
// 检查常见的楼层属性名称
|
||
string[] floorAttributes = { "Level", "Floor", "楼层", "层", "Story", "Storey", "Base Constraint" };
|
||
|
||
foreach (string attrName in floorAttributes)
|
||
{
|
||
string value = GetAttributeValueFromNode(node, attrName);
|
||
if (!string.IsNullOrEmpty(value))
|
||
{
|
||
return value;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"智能楼层检测失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从节点获取指定属性的值
|
||
/// </summary>
|
||
private string GetAttributeValueFromNode(ModelItem item, string attributeName)
|
||
{
|
||
try
|
||
{
|
||
// 使用 CategoryAttributeManager 的通用属性获取方法,简化代码
|
||
return CategoryAttributeManager.GetGeneralPropertyValue(item, attributeName);
|
||
}
|
||
catch
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取检测到的属性类型信息(属性名称和值)
|
||
/// </summary>
|
||
public (string attributeName, string attributeValue) ExtractAttributeInfo(ModelItem node)
|
||
{
|
||
try
|
||
{
|
||
// 使用FloorDetector的通用属性检测逻辑
|
||
// 检查常见的楼层属性名称
|
||
string[] floorAttributes = { "Level", "Floor", "楼层", "层", "Story", "Storey", "Base Constraint" };
|
||
|
||
foreach (string attrName in floorAttributes)
|
||
{
|
||
string value = GetAttributeValueFromNode(node, attrName);
|
||
if (!string.IsNullOrEmpty(value))
|
||
{
|
||
return (attrName, value);
|
||
}
|
||
}
|
||
|
||
return (null, null);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"智能楼层属性检测失败: {ex.Message}");
|
||
return (null, null);
|
||
}
|
||
}
|
||
|
||
public Dictionary<string, GroupItem> GroupItems(ModelItemCollection items, SplitConfiguration config)
|
||
{
|
||
var result = new Dictionary<string, GroupItem>();
|
||
|
||
try
|
||
{
|
||
LogManager.Info($"[FloorDetectionStrategy] 开始智能楼层检测,输入模型项数={items?.Count ?? 0}");
|
||
|
||
if (items == null || items.Count == 0)
|
||
{
|
||
LogManager.Warning("[FloorDetectionStrategy] 输入模型项集合为空");
|
||
return result;
|
||
}
|
||
|
||
// 关键修复:直接使用传入的items,不再让FloorDetector重新获取深度项
|
||
// 调用 FloorDetector,但传入 maxDepth = 0 来强制使用传入的items
|
||
var floors = _floorDetector.DetectFloorsFromGivenItems(items, null);
|
||
|
||
LogManager.Info($"[FloorDetectionStrategy] FloorDetector检测完成,发现 {floors?.Count ?? 0} 个楼层");
|
||
|
||
if (floors != null && floors.Count > 0)
|
||
{
|
||
foreach (var floor in floors)
|
||
{
|
||
result[floor.FloorName] = new GroupItem
|
||
{
|
||
GroupName = floor.FloorName,
|
||
OriginalName = floor.FloorName,
|
||
Items = floor.Items?.ToList() ?? new List<ModelItem>(),
|
||
Metadata = new Dictionary<string, object>
|
||
{
|
||
["OriginalFloorName"] = floor.FloorName,
|
||
["FloorName"] = floor.FloorName,
|
||
["Elevation"] = floor.Elevation,
|
||
["DetectionMethod"] = "Floor",
|
||
["ItemsFromUnifiedDepth"] = true // 标记使用了统一深度遍历
|
||
}
|
||
};
|
||
|
||
LogManager.Info($"[FloorDetectionStrategy] 楼层 '{floor.FloorName}' 包含 {floor.Items?.Count ?? 0} 个模型项");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[FloorDetectionStrategy] 未检测到任何楼层信息");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[FloorDetectionStrategy] 楼层检测异常: {ex.Message}");
|
||
}
|
||
|
||
LogManager.Info($"[FloorDetectionStrategy] 楼层检测完成,返回 {result.Count} 个楼层分组");
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 属性分组策略基类
|
||
/// </summary>
|
||
private abstract class AttributeGroupingStrategy : IGroupingStrategy
|
||
{
|
||
protected readonly FloorAttributeManager _floorManager;
|
||
|
||
public abstract string GroupTypeName { get; }
|
||
public abstract string UnclassifiedLabel { get; }
|
||
|
||
protected AttributeGroupingStrategy()
|
||
{
|
||
_floorManager = new FloorAttributeManager();
|
||
}
|
||
|
||
protected abstract string GetAttributeValue(ModelItem item);
|
||
|
||
/// <summary>
|
||
/// 实现IGroupingStrategy接口的ExtractAttributeValue方法
|
||
/// 直接调用子类已实现的GetAttributeValue方法
|
||
/// </summary>
|
||
public string ExtractAttributeValue(ModelItem node)
|
||
{
|
||
return GetAttributeValue(node);
|
||
}
|
||
|
||
public Dictionary<string, GroupItem> GroupItems(ModelItemCollection items, SplitConfiguration config)
|
||
{
|
||
var result = new Dictionary<string, GroupItem>();
|
||
|
||
try
|
||
{
|
||
LogManager.Info($"[{GroupTypeName}AttributeStrategy] 开始属性分组,输入模型项数={items?.Count ?? 0},深度={config?.MaxDepth ?? -1}");
|
||
|
||
if (items == null || items.Count == 0)
|
||
{
|
||
LogManager.Warning($"[{GroupTypeName}AttributeStrategy] 输入模型项集合为空");
|
||
return result;
|
||
}
|
||
|
||
int processedCount = 0;
|
||
var attributeValueCounts = new Dictionary<string, int>();
|
||
|
||
foreach (var item in items)
|
||
{
|
||
try
|
||
{
|
||
processedCount++;
|
||
string attributeValue = GetAttributeValue(item);
|
||
string groupKey = string.IsNullOrEmpty(attributeValue) ? UnclassifiedLabel : attributeValue;
|
||
|
||
// 统计属性值分布
|
||
if (!attributeValueCounts.ContainsKey(groupKey))
|
||
attributeValueCounts[groupKey] = 0;
|
||
attributeValueCounts[groupKey]++;
|
||
|
||
if (!result.ContainsKey(groupKey))
|
||
{
|
||
result[groupKey] = new GroupItem
|
||
{
|
||
GroupName = groupKey,
|
||
OriginalName = attributeValue ?? UnclassifiedLabel,
|
||
Metadata = new Dictionary<string, object>
|
||
{
|
||
["DetectionMethod"] = GroupTypeName + "Attribute",
|
||
["ItemsFromUnifiedDepth"] = true, // 标记使用了统一深度遍历
|
||
["MaxDepth"] = config?.MaxDepth ?? -1
|
||
}
|
||
};
|
||
}
|
||
|
||
result[groupKey].Items.Add(item);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[{GroupTypeName}AttributeStrategy] 处理单个模型项时出错: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 记录属性值分布
|
||
LogManager.Info($"[{GroupTypeName}AttributeStrategy] 处理完成,共处理{processedCount}个模型项,发现{result.Count}个{GroupTypeName}分组");
|
||
foreach (var kvp in attributeValueCounts.OrderByDescending(x => x.Value))
|
||
{
|
||
LogManager.Info($"[{GroupTypeName}AttributeStrategy] {GroupTypeName}分组 '{kvp.Key}': {kvp.Value} 个模型项");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[{GroupTypeName}AttributeStrategy] 属性分组异常: {ex.Message}");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 楼层属性分组策略
|
||
/// </summary>
|
||
private class FloorAttributeStrategy : AttributeGroupingStrategy
|
||
{
|
||
public override string GroupTypeName => "楼层";
|
||
public override string UnclassifiedLabel => "未设置楼层";
|
||
|
||
protected override string GetAttributeValue(ModelItem item)
|
||
{
|
||
return _floorManager.GetFloorProperty(item, "楼层");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 区域属性分组策略
|
||
/// </summary>
|
||
private class ZoneAttributeStrategy : AttributeGroupingStrategy
|
||
{
|
||
public override string GroupTypeName => "区域";
|
||
public override string UnclassifiedLabel => "未设置区域";
|
||
|
||
protected override string GetAttributeValue(ModelItem item)
|
||
{
|
||
return _floorManager.GetFloorProperty(item, "区域");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 子系统属性分组策略
|
||
/// </summary>
|
||
private class SubSystemAttributeStrategy : AttributeGroupingStrategy
|
||
{
|
||
public override string GroupTypeName => "子系统";
|
||
public override string UnclassifiedLabel => "未设置子系统";
|
||
|
||
protected override string GetAttributeValue(ModelItem item)
|
||
{
|
||
return _floorManager.GetFloorProperty(item, "子系统");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 简化的分层配置
|
||
/// </summary>
|
||
public class SplitConfiguration
|
||
{
|
||
/// <summary>
|
||
/// 分层策略
|
||
/// </summary>
|
||
public SplitStrategy Strategy { get; set; } = SplitStrategy.BySmartFloorDetection;
|
||
|
||
/// <summary>
|
||
/// 属性名称(用于自定义分层时使用)
|
||
/// </summary>
|
||
public string AttributeName { get; set; } = "Level";
|
||
|
||
/// <summary>
|
||
/// 输出目录
|
||
/// </summary>
|
||
public string OutputDirectory { get; set; } = "";
|
||
|
||
/// <summary>
|
||
/// 文件命名模式
|
||
/// </summary>
|
||
public string FileNamePattern { get; set; } = "{ProjectName}_{LayerName}";
|
||
|
||
/// <summary>
|
||
/// 遍历深度限制(1=仅第一级,2=到第二级,3=到第三级,0=所有级别)
|
||
/// </summary>
|
||
public int MaxDepth { get; set; } = 1;
|
||
|
||
/// <summary>
|
||
/// 导出选项配置 - 用户可配置的NWD导出选项
|
||
/// </summary>
|
||
public NwdExportUserOptions ExportOptions { get; set; } = new NwdExportUserOptions();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 用户可配置的NWD导出选项 - 对应截图中的选项
|
||
/// </summary>
|
||
public class NwdExportUserOptions
|
||
{
|
||
/// <summary>
|
||
/// 嵌入 ReCap 和纹理数据 - 对应截图中的复选框
|
||
/// </summary>
|
||
public bool EmbedXrefs { get; set; } = false;
|
||
|
||
/// <summary>
|
||
/// 阻止导出对象特性 - 对应截图中的复选框
|
||
/// </summary>
|
||
public bool PreventObjectPropertyExport { get; set; } = false;
|
||
|
||
/// <summary>
|
||
/// Navisworks 版本 - 对应截图中的下拉框(暂时使用默认值)
|
||
/// </summary>
|
||
public string FileVersion { get; set; } = "2026";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分层预览结果
|
||
/// </summary>
|
||
public class SplitPreviewResult
|
||
{
|
||
public string LayerName { get; set; }
|
||
public string LayerAttribute { get; set; }
|
||
public ModelItemCollection Items { get; set; } = new ModelItemCollection();
|
||
public Dictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
|
||
|
||
/// <summary>
|
||
/// 是否选中用于保存,默认为true
|
||
/// </summary>
|
||
public bool IsSelectedForSave { get; set; } = true;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 事件定义
|
||
|
||
public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
|
||
public event EventHandler<string> StatusChanged;
|
||
|
||
#endregion
|
||
|
||
#region 私有字段
|
||
|
||
/// <summary>
|
||
/// 静态实例,方便其他类调用缓存清除功能
|
||
/// </summary>
|
||
public static ModelSplitterManager Instance { get; private set; }
|
||
|
||
private readonly FloorDetector _floorDetector;
|
||
|
||
// 楼层检测结果缓存
|
||
private readonly Dictionary<string, List<SplitPreviewResult>> _floorCache;
|
||
private readonly object _cacheLock;
|
||
|
||
#endregion
|
||
|
||
#region 构造函数
|
||
|
||
public ModelSplitterManager()
|
||
{
|
||
// 设置静态实例
|
||
Instance = this;
|
||
|
||
_floorDetector = new FloorDetector();
|
||
|
||
// 初始化缓存
|
||
_floorCache = new Dictionary<string, List<SplitPreviewResult>>();
|
||
_cacheLock = new object();
|
||
|
||
// 验证Navisworks运行时环境
|
||
ValidateNavisworksRuntime();
|
||
|
||
LogManager.Info("[SimplifiedModelSplitter] 简化分层管理器已初始化,包含缓存机制和运行时验证");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证Navisworks运行时环境 - 基于示例中的最佳实践
|
||
/// </summary>
|
||
private void ValidateNavisworksRuntime()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[SimplifiedModelSplitter] 开始验证Navisworks运行时环境...");
|
||
|
||
// 1. 检查线程模型 - 必须在STA线程中
|
||
var apartmentState = System.Threading.Thread.CurrentThread.GetApartmentState();
|
||
LogManager.Info($"[分层管理器] 当前线程状态: {apartmentState}");
|
||
if (apartmentState != System.Threading.ApartmentState.STA)
|
||
{
|
||
LogManager.Warning("[SimplifiedModelSplitter] 警告:当前不在STA线程中,这可能导致API调用失败");
|
||
LogManager.Warning("[SimplifiedModelSplitter] 建议:确保主线程标记为[STAThread]");
|
||
}
|
||
|
||
// 2. 检查Navisworks API可用性 - 适用于Navisworks 2026
|
||
try
|
||
{
|
||
// 尝试访问Navisworks Application来验证API是否可用
|
||
var app = NavisApplication.ActiveDocument;
|
||
LogManager.Info("[SimplifiedModelSplitter] Navisworks API可用性检查通过");
|
||
}
|
||
catch (Exception apiEx)
|
||
{
|
||
LogManager.Error($"[分层管理器] Navisworks API不可用: {apiEx.Message}");
|
||
LogManager.Error("[SimplifiedModelSplitter] 解决方案:确保在Navisworks环境中运行此插件");
|
||
}
|
||
|
||
// 3. 检查文档状态
|
||
try
|
||
{
|
||
var document = NavisApplication.ActiveDocument;
|
||
if (document != null)
|
||
{
|
||
LogManager.Info($"[分层管理器] 活动文档: {document.FileName ?? "未命名"}");
|
||
LogManager.Info($"[分层管理器] 文档模型数量: {document.Models?.Count ?? 0}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[SimplifiedModelSplitter] 当前没有活动文档");
|
||
}
|
||
}
|
||
catch (Exception docEx)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 检查文档状态时出错: {docEx.Message}");
|
||
}
|
||
|
||
// 4. 内存状态检查
|
||
long memoryMB = GC.GetTotalMemory(false) / 1024 / 1024;
|
||
LogManager.Info($"[分层管理器] 当前内存使用: {memoryMB} MB");
|
||
|
||
LogManager.Info("[SimplifiedModelSplitter] Navisworks运行时环境验证完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 运行时环境验证失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 公共方法
|
||
|
||
/// <summary>
|
||
/// 清除缓存
|
||
/// </summary>
|
||
public void ClearCache()
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
_floorCache.Clear();
|
||
LogManager.Info("[SimplifiedModelSplitter] 缓存已清除");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 预览分层结果(简化版本,带缓存和进度报告)
|
||
/// </summary>
|
||
public List<SplitPreviewResult> PreviewSplit(SplitConfiguration config)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] 开始智能遍历分层预览,策略: {config.Strategy},深度限制: {config.MaxDepth}");
|
||
|
||
// 步骤1:初始化和缓存检查 (0-10%)
|
||
OnStatusChanged("正在初始化智能分层预览...");
|
||
OnProgressChanged(new ProgressChangedEventArgs(0, "准备智能遍历配置"));
|
||
|
||
// 生成缓存键(注意:智能遍历的缓存键需要区别于原有方式)
|
||
string cacheKey = GenerateSmartTraversalCacheKey(config);
|
||
LogManager.Info($"[分层管理器] 生成智能遍历缓存键: {cacheKey}");
|
||
|
||
OnProgressChanged(new ProgressChangedEventArgs(5, "检查缓存"));
|
||
|
||
// 检查缓存
|
||
List<SplitPreviewResult> cachedResults = GetFromCache(config.Strategy, cacheKey);
|
||
if (cachedResults != null && cachedResults.Count > 0)
|
||
{
|
||
LogManager.Info($"[分层管理器] 使用缓存结果,共 {cachedResults.Count} 个分层");
|
||
OnStatusChanged($"使用缓存结果,识别到 {cachedResults.Count} 个分层");
|
||
OnProgressChanged(new ProgressChangedEventArgs(100, $"从缓存加载 {cachedResults.Count} 个分层"));
|
||
return cachedResults;
|
||
}
|
||
|
||
// 步骤2:验证文档状态 (10-15%)
|
||
OnStatusChanged("正在验证模型文档...");
|
||
OnProgressChanged(new ProgressChangedEventArgs(10, "验证Navisworks文档状态"));
|
||
|
||
var document = NavisApplication.ActiveDocument;
|
||
if (document?.Models?.Count == 0)
|
||
{
|
||
throw new InvalidOperationException("当前文档中没有模型");
|
||
}
|
||
|
||
OnProgressChanged(new ProgressChangedEventArgs(15, $"文档验证完成,包含 {document.Models.Count} 个模型"));
|
||
LogManager.Info($"[分层管理器] 文档验证完成,包含 {document.Models.Count} 个模型");
|
||
|
||
// 步骤3:执行智能遍历分层分析 (15-90%)
|
||
OnStatusChanged($"正在执行{GetStrategyDisplayName(config.Strategy)}...");
|
||
OnProgressChanged(new ProgressChangedEventArgs(20, "开始智能遍历分析"));
|
||
|
||
var previewResults = new List<SplitPreviewResult>();
|
||
var strategy = CreateStrategy(config.Strategy);
|
||
|
||
// 关键改变:直接使用智能遍历,不再预收集所有节点
|
||
LogManager.Info($"[分层管理器] 使用智能遍历算法,策略: {strategy.GroupTypeName}");
|
||
previewResults = PreviewSplitWithSmartTraversal(config, strategy);
|
||
|
||
OnProgressChanged(new ProgressChangedEventArgs(90, $"智能分析完成,识别到 {previewResults.Count} 个分层"));
|
||
|
||
// 步骤4:缓存结果和完成 (90-100%)
|
||
OnStatusChanged("正在保存分析结果...");
|
||
OnProgressChanged(new ProgressChangedEventArgs(95, "保存到缓存"));
|
||
|
||
// 缓存结果
|
||
SaveToCache(config.Strategy, cacheKey, previewResults);
|
||
|
||
// 完成
|
||
OnStatusChanged($"智能预览完成,识别到 {previewResults.Count} 个分层");
|
||
OnProgressChanged(new ProgressChangedEventArgs(100, $"预览完成,共 {previewResults.Count} 个分层"));
|
||
|
||
LogManager.Info($"[分层管理器] 智能遍历预览完成,共识别 {previewResults.Count} 个分层(深度: {config.MaxDepth}级),已缓存");
|
||
|
||
return previewResults;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 智能遍历预览失败: {ex.Message}", ex);
|
||
OnStatusChanged($"预览失败: {ex.Message}");
|
||
OnProgressChanged(new ProgressChangedEventArgs(0, $"预览失败: {ex.Message}"));
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成缓存键 - 使用统一的深度遍历
|
||
/// </summary>
|
||
private string GenerateCacheKey(SplitConfiguration config)
|
||
{
|
||
return $"{config.Strategy}_{config.MaxDepth}_{config.AttributeName ?? "Floor"}_" +
|
||
$"{GetItemsByDepthUnified(config.MaxDepth).Count}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成智能遍历专用的缓存键 - 区别于原有的预收集模式
|
||
/// </summary>
|
||
private string GenerateSmartTraversalCacheKey(SplitConfiguration config)
|
||
{
|
||
// 智能遍历的缓存键包含特殊标识,避免与原有缓存冲突
|
||
return $"SmartTraversal_{config.Strategy}_{config.MaxDepth}_{config.AttributeName ?? "Default"}_{DateTime.Now:yyyyMMdd}";
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 创建分层策略实例
|
||
/// </summary>
|
||
private IGroupingStrategy CreateStrategy(SplitStrategy strategy)
|
||
{
|
||
switch (strategy)
|
||
{
|
||
case SplitStrategy.BySmartFloorDetection:
|
||
return new FloorDetectionStrategy(_floorDetector);
|
||
case SplitStrategy.ByFloorAttribute:
|
||
return new FloorAttributeStrategy();
|
||
case SplitStrategy.ByZoneAttribute:
|
||
return new ZoneAttributeStrategy();
|
||
case SplitStrategy.BySubSystemAttribute:
|
||
return new SubSystemAttributeStrategy();
|
||
default:
|
||
throw new NotSupportedException($"不支持的分层策略: {strategy}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从缓存获取结果
|
||
/// </summary>
|
||
private List<SplitPreviewResult> GetFromCache(SplitStrategy strategy, string cacheKey)
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
// 只支持楼层缓存和自定义楼层缓存
|
||
var cache = _floorCache;
|
||
return cache.ContainsKey(cacheKey) ? cache[cacheKey] : null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存结果到缓存
|
||
/// </summary>
|
||
private void SaveToCache(SplitStrategy strategy, string cacheKey, List<SplitPreviewResult> results)
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
// 只支持楼层缓存和自定义楼层缓存
|
||
var cache = _floorCache;
|
||
cache[cacheKey] = results;
|
||
LogManager.Info($"[分层管理器] 结果已缓存: {strategy}, 键: {cacheKey}, 数量: {results.Count}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行分层保存(异步,简化版本)
|
||
/// </summary>
|
||
public async Task ExecuteSplitAsync(SplitConfiguration config, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] 开始执行分层,策略: {config.Strategy}");
|
||
OnStatusChanged("正在准备分层...");
|
||
|
||
// 优先从缓存获取分层结果,避免重复检测
|
||
string cacheKey = GenerateSmartTraversalCacheKey(config);
|
||
var previewResults = GetFromCache(config.Strategy, cacheKey);
|
||
|
||
if (previewResults == null || previewResults.Count == 0)
|
||
{
|
||
LogManager.Info($"[分层管理器] 缓存中没有结果,重新预览分层");
|
||
previewResults = PreviewSplit(config);
|
||
if (previewResults.Count == 0)
|
||
{
|
||
throw new InvalidOperationException("没有可分层的内容");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.Info($"[分层管理器] 使用缓存的分层结果,共 {previewResults.Count} 个分层");
|
||
}
|
||
|
||
// 过滤出需要保存的分层
|
||
var layersToSave = previewResults.Where(p => p.IsSelectedForSave).ToList();
|
||
if (layersToSave.Count == 0)
|
||
{
|
||
LogManager.Warning("[SimplifiedModelSplitter] 没有选中任何分层进行保存");
|
||
OnStatusChanged("没有选中任何分层进行保存");
|
||
return;
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] 将保存 {layersToSave.Count}/{previewResults.Count} 个选中的分层");
|
||
|
||
// 确保输出目录存在
|
||
EnsureOutputDirectory(config.OutputDirectory);
|
||
|
||
// 逐个处理分层,增强错误处理
|
||
int successCount = 0;
|
||
var failedLayers = new List<string>();
|
||
|
||
for (int i = 0; i < layersToSave.Count; i++)
|
||
{
|
||
if (cancellationToken.IsCancellationRequested)
|
||
{
|
||
LogManager.Info($"[分层管理器] 用户取消分层操作,已处理 {successCount}/{layersToSave.Count} 个分层");
|
||
break;
|
||
}
|
||
|
||
var preview = layersToSave[i];
|
||
OnStatusChanged($"正在处理分层: {preview.LayerName} ({i + 1}/{layersToSave.Count})");
|
||
OnProgressChanged(new ProgressChangedEventArgs((i + 1) * 100 / layersToSave.Count,
|
||
$"处理分层 {i + 1}/{layersToSave.Count}: {preview.LayerName}"));
|
||
|
||
try
|
||
{
|
||
// 实现具体的分层保存逻辑
|
||
await ProcessSingleLayerAsync(preview, config, cancellationToken, i + 1, layersToSave.Count);
|
||
successCount++;
|
||
LogManager.Info($"[分层管理器] 分层 {preview.LayerName} 处理成功 ({successCount}/{layersToSave.Count})");
|
||
}
|
||
catch (Exception layerEx)
|
||
{
|
||
string errorMsg = $"分层 {preview.LayerName} 处理失败: {layerEx.Message}";
|
||
LogManager.Error($"[分层管理器] {errorMsg}");
|
||
failedLayers.Add(preview.LayerName);
|
||
|
||
// 继续处理其他分层,不因单个失败而中断整个进程
|
||
OnStatusChanged(errorMsg);
|
||
}
|
||
}
|
||
|
||
// 生成最终统计信息
|
||
string finalMessage = $"分层处理完成:成功 {successCount} 个,失败 {failedLayers.Count} 个";
|
||
if (layersToSave.Count < previewResults.Count)
|
||
{
|
||
int skippedCount = previewResults.Count - layersToSave.Count;
|
||
finalMessage += $",跳过 {skippedCount} 个未选中的分层";
|
||
}
|
||
if (failedLayers.Count > 0)
|
||
{
|
||
finalMessage += $"\n失败列表: {string.Join(", ", failedLayers)}";
|
||
LogManager.Warning($"[分层管理器] {finalMessage}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Info($"[分层管理器] {finalMessage}");
|
||
}
|
||
|
||
OnStatusChanged(finalMessage);
|
||
|
||
// 如果所有分层都失败,抛出异常
|
||
if (successCount == 0 && layersToSave.Count > 0)
|
||
{
|
||
throw new InvalidOperationException($"所有选中的分层处理都失败了,共 {layersToSave.Count} 个分层");
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] 分层执行完成:成功 {successCount}/{layersToSave.Count}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 分层执行失败: {ex.Message}");
|
||
OnStatusChanged($"分层失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行分层保存(同步版本,用于UI线程调用)
|
||
/// </summary>
|
||
public void ExecuteSplit(SplitConfiguration config)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] 开始执行分层,策略: {config.Strategy}");
|
||
OnStatusChanged("正在准备分层...");
|
||
|
||
// 优先从缓存获取分层结果,避免重复检测
|
||
string cacheKey = GenerateSmartTraversalCacheKey(config);
|
||
var previewResults = GetFromCache(config.Strategy, cacheKey);
|
||
|
||
if (previewResults == null || previewResults.Count == 0)
|
||
{
|
||
LogManager.Info($"[分层管理器] 缓存中没有结果,重新预览分层");
|
||
previewResults = PreviewSplit(config);
|
||
if (previewResults.Count == 0)
|
||
{
|
||
throw new InvalidOperationException("没有可分层的内容");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.Info($"[分层管理器] 使用缓存的分层结果,共 {previewResults.Count} 个分层");
|
||
}
|
||
|
||
// 过滤出需要保存的分层
|
||
var layersToSave = previewResults.Where(p => p.IsSelectedForSave).ToList();
|
||
if (layersToSave.Count == 0)
|
||
{
|
||
LogManager.Warning("[SimplifiedModelSplitter] 没有选中任何分层进行保存");
|
||
OnStatusChanged("没有选中任何分层进行保存");
|
||
return;
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] 将保存 {layersToSave.Count}/{previewResults.Count} 个选中的分层");
|
||
|
||
// 确保输出目录存在
|
||
EnsureOutputDirectory(config.OutputDirectory);
|
||
|
||
// 逐个处理分层,增强错误处理(同步版本)
|
||
int successCount = 0;
|
||
var failedLayers = new List<string>();
|
||
|
||
for (int i = 0; i < layersToSave.Count; i++)
|
||
{
|
||
var preview = layersToSave[i];
|
||
OnStatusChanged($"正在处理分层: {preview.LayerName} ({i + 1}/{layersToSave.Count})");
|
||
OnProgressChanged(new ProgressChangedEventArgs((i + 1) * 100 / layersToSave.Count,
|
||
$"处理分层 {i + 1}/{layersToSave.Count}: {preview.LayerName}"));
|
||
|
||
try
|
||
{
|
||
// 同步处理单个分层
|
||
ProcessSingleLayer(preview, config, i + 1, layersToSave.Count);
|
||
successCount++;
|
||
LogManager.Info($"[分层管理器] 分层 {preview.LayerName} 处理成功 ({successCount}/{layersToSave.Count})");
|
||
}
|
||
catch (Exception layerEx)
|
||
{
|
||
string errorMsg = $"分层 {preview.LayerName} 处理失败: {layerEx.Message}";
|
||
LogManager.Error($"[分层管理器] {errorMsg}");
|
||
failedLayers.Add(preview.LayerName);
|
||
|
||
// 继续处理其他分层,不因单个失败而中断整个进程
|
||
OnStatusChanged(errorMsg);
|
||
}
|
||
}
|
||
|
||
// 生成最终统计信息
|
||
string finalMessage = $"分层处理完成:成功 {successCount} 个,失败 {failedLayers.Count} 个";
|
||
if (layersToSave.Count < previewResults.Count)
|
||
{
|
||
int skippedCount = previewResults.Count - layersToSave.Count;
|
||
finalMessage += $",跳过 {skippedCount} 个未选中的分层";
|
||
}
|
||
if (failedLayers.Count > 0)
|
||
{
|
||
finalMessage += $"\n失败列表: {string.Join(", ", failedLayers)}";
|
||
LogManager.Warning($"[分层管理器] {finalMessage}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Info($"[分层管理器] {finalMessage}");
|
||
}
|
||
|
||
OnStatusChanged(finalMessage);
|
||
|
||
// 如果所有分层都失败,抛出异常
|
||
if (successCount == 0 && layersToSave.Count > 0)
|
||
{
|
||
throw new InvalidOperationException($"所有选中的分层处理都失败了,共 {layersToSave.Count} 个分层");
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] 分层执行完成:成功 {successCount}/{layersToSave.Count}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 分层执行失败: {ex.Message}");
|
||
OnStatusChanged($"分层失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 统一的深度遍历核心函数 - 精确控制深度逻辑
|
||
/// </summary>
|
||
/// <param name="maxDepth">最大深度,1=仅第一级,2=到第二级,3=到第三级,4=到第四级,5=到第五级,0=所有级别</param>
|
||
/// <returns>按深度筛选的模型项集合</returns>
|
||
private ModelItemCollection GetItemsByDepthUnified(int maxDepth)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] ===== GetItemsByDepthUnified开始 ===== maxDepth={maxDepth}");
|
||
|
||
var document = NavisApplication.ActiveDocument;
|
||
if (document?.Models?.Count == 0)
|
||
{
|
||
LogManager.Warning("[SimplifiedModelSplitter] 当前文档没有模型");
|
||
return new ModelItemCollection();
|
||
}
|
||
|
||
var result = new ModelItemCollection();
|
||
|
||
if (maxDepth <= 0)
|
||
{
|
||
// 深度为0表示包含所有级别
|
||
result.AddRange(document.Models.RootItemDescendantsAndSelf);
|
||
LogManager.Info($"[分层管理器] maxDepth=0,返回所有级别的项,共{result.Count}个");
|
||
return result;
|
||
}
|
||
|
||
// 从文档的根模型开始精确的深度遍历
|
||
foreach (Model model in document.Models)
|
||
{
|
||
if (model.RootItem != null)
|
||
{
|
||
// 添加第一级节点(模型根的直接子节点)
|
||
if (maxDepth >= 1)
|
||
{
|
||
foreach (ModelItem firstLevelItem in model.RootItem.Children)
|
||
{
|
||
result.Add(firstLevelItem);
|
||
|
||
// 递归添加更深层次的节点
|
||
AddChildrenToDepth(firstLevelItem, result, 2, maxDepth);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] ===== GetItemsByDepthUnified完成 ===== 返回{result.Count}个模型项(深度:{maxDepth}级)");
|
||
return result;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] GetItemsByDepthUnified异常: {ex.Message}");
|
||
return new ModelItemCollection();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归添加子节点到指定深度
|
||
/// </summary>
|
||
/// <param name="parentItem">父节点</param>
|
||
/// <param name="result">结果集合</param>
|
||
/// <param name="currentDepth">当前深度</param>
|
||
/// <param name="maxDepth">最大深度</param>
|
||
private void AddChildrenToDepth(ModelItem parentItem, ModelItemCollection result, int currentDepth, int maxDepth)
|
||
{
|
||
if (currentDepth > maxDepth || parentItem?.Children == null)
|
||
return;
|
||
|
||
foreach (ModelItem child in parentItem.Children)
|
||
{
|
||
result.Add(child);
|
||
|
||
// 如果还没有达到最大深度,继续遍历子项
|
||
if (currentDepth < maxDepth)
|
||
{
|
||
AddChildrenToDepth(child, result, currentDepth + 1, maxDepth);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 智能遍历分层预览 - 边遍历边检查边停止的新算法
|
||
/// 替代原有的预收集模式,大幅提升预览性能
|
||
/// </summary>
|
||
private List<SplitPreviewResult> PreviewSplitWithSmartTraversal(SplitConfiguration config, IGroupingStrategy strategy)
|
||
{
|
||
// 使用字典按楼层名分组,自动合并同名楼层
|
||
var layerGroups = new Dictionary<string, SplitPreviewResult>();
|
||
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] ========== 开始智能遍历分层预览 ==========");
|
||
LogManager.Info($"[分层管理器] 策略: {strategy.GroupTypeName}, 深度限制: {config.MaxDepth}");
|
||
|
||
var document = NavisApplication.ActiveDocument;
|
||
if (document?.Models?.Count == 0)
|
||
{
|
||
LogManager.Warning("[SimplifiedModelSplitter] 当前文档没有模型");
|
||
return new List<SplitPreviewResult>();
|
||
}
|
||
|
||
// 从每个模型的一级节点开始智能遍历
|
||
int processedTopNodes = 0;
|
||
foreach (Model model in document.Models)
|
||
{
|
||
if (model.RootItem?.Children != null)
|
||
{
|
||
foreach (ModelItem rootChild in model.RootItem.Children)
|
||
{
|
||
processedTopNodes++;
|
||
|
||
// 统一使用 TraverseAndRecord 处理所有节点
|
||
// 它会自动检测节点是否有楼层属性并正确处理
|
||
TraverseAndRecord(rootChild, strategy, config, layerGroups, 1);
|
||
|
||
// 每处理10个顶级节点记录一次进度
|
||
if (processedTopNodes % 10 == 0)
|
||
{
|
||
LogManager.Info($"[分层管理器] 已处理 {processedTopNodes} 个顶级节点,找到 {layerGroups.Count} 个分层");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] ========== 智能遍历完成 ==========");
|
||
LogManager.Info($"[分层管理器] 处理了 {processedTopNodes} 个顶级节点,识别到 {layerGroups.Count} 个{strategy.GroupTypeName}分层");
|
||
|
||
// 将字典转换为列表返回
|
||
var results = new List<SplitPreviewResult>(layerGroups.Values);
|
||
|
||
// 记录每个分层的详细信息
|
||
foreach (var layer in results)
|
||
{
|
||
var rootNodes = layer.Metadata["RootNodes"] as List<ModelItem>;
|
||
LogManager.Info($"[分层管理器] 楼层 '{layer.LayerName}': {rootNodes?.Count ?? 0} 个分支, {layer.Items.Count} 个节点");
|
||
}
|
||
|
||
return results;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 智能遍历失败: {ex.Message}", ex);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归遍历节点并记录分层结果的核心方法(支持同名楼层合并)
|
||
/// </summary>
|
||
private void TraverseAndRecord(ModelItem node, IGroupingStrategy strategy,
|
||
SplitConfiguration config, Dictionary<string, SplitPreviewResult> layerGroups, int currentDepth)
|
||
{
|
||
try
|
||
{
|
||
// 检查当前节点的分层属性
|
||
string layerValue = strategy.ExtractAttributeValue(node);
|
||
|
||
if (!string.IsNullOrEmpty(layerValue))
|
||
{
|
||
// 找到分层属性,获取属性类型信息(仅对智能检测策略)
|
||
string detectedAttributeName = null;
|
||
if (strategy is FloorDetectionStrategy floorStrategy)
|
||
{
|
||
var (attributeName, attributeValue) = floorStrategy.ExtractAttributeInfo(node);
|
||
detectedAttributeName = attributeName;
|
||
LogManager.Info($"[分层管理器] 智能检测到属性类型: '{attributeName}', 值: '{attributeValue}'");
|
||
}
|
||
|
||
// 清理楼层名称
|
||
string sanitizedLayerName = SanitizeLayerName(layerValue);
|
||
|
||
// 检查是否已存在该楼层的结果
|
||
if (!layerGroups.ContainsKey(sanitizedLayerName))
|
||
{
|
||
// 第一次遇到这个楼层名,创建新结果
|
||
layerGroups[sanitizedLayerName] = new SplitPreviewResult
|
||
{
|
||
LayerName = sanitizedLayerName,
|
||
LayerAttribute = GetAttributeDescription(config.Strategy, layerValue, null),
|
||
Items = new ModelItemCollection(),
|
||
Metadata = new Dictionary<string, object>
|
||
{
|
||
["RootNodes"] = new List<ModelItem>(), // 存储多个根节点
|
||
["BranchCount"] = 0,
|
||
["GroupType"] = strategy.GroupTypeName,
|
||
["TraversalMode"] = "SmartTraversal",
|
||
["DetectedAttributeName"] = detectedAttributeName
|
||
}
|
||
};
|
||
LogManager.Info($"[分层管理器] 创建新楼层分组: '{sanitizedLayerName}'");
|
||
}
|
||
|
||
// 获取现有楼层结果
|
||
var layerResult = layerGroups[sanitizedLayerName];
|
||
|
||
// 只添加根节点,不展开子节点
|
||
// IsolateSpecificItems 会自动处理子节点(通过 Descendants)
|
||
layerResult.Items.Add(node);
|
||
LogManager.Info($"[分层管理器] 添加根节点: {node.DisplayName}");
|
||
|
||
// 记录这个分支的根节点
|
||
var rootNodes = layerResult.Metadata["RootNodes"] as List<ModelItem>;
|
||
rootNodes.Add(node);
|
||
|
||
// 更新分支计数
|
||
layerResult.Metadata["BranchCount"] = rootNodes.Count;
|
||
layerResult.Metadata["EstimatedNodeCount"] = rootNodes.Count; // 只是根节点数量
|
||
|
||
LogManager.Info($"[分层管理器] 合并到楼层 '{sanitizedLayerName}',现有 {rootNodes.Count} 个根节点");
|
||
|
||
return; // 关键:停止遍历该分支
|
||
}
|
||
|
||
// 未找到分层属性且未达深度限制,继续遍历子节点
|
||
if (currentDepth < config.MaxDepth && node.Children != null)
|
||
{
|
||
foreach (ModelItem child in node.Children)
|
||
{
|
||
TraverseAndRecord(child, strategy, config, layerGroups, currentDepth + 1);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 遍历节点失败: {node?.DisplayName ?? "null"}, 错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// ExpandNodeTree 方法已删除,不再需要展开节点树
|
||
// IsolateSpecificItems 会自动处理子节点(通过 Descendants)
|
||
|
||
/// <summary>
|
||
/// 创建轻量化分层预览结果 - 只包含必要信息,不做统计计算
|
||
/// </summary>
|
||
/// <summary>
|
||
/// 创建轻量化分层预览结果 - 只包含必要信息,不做统计计算
|
||
/// </summary>
|
||
private SplitPreviewResult CreateLayerResult(string layerValue, ModelItem rootNode, string groupTypeName, SplitStrategy strategy, string detectedAttributeName = null)
|
||
{
|
||
// 性能优化:只保存根节点,不展开子节点
|
||
// IsolateSpecificItems 会自动处理子节点(通过 Descendants)
|
||
var allNodes = new ModelItemCollection();
|
||
allNodes.Add(rootNode);
|
||
|
||
// 快速统计子节点数量(可选,用于显示信息)
|
||
int estimatedNodeCount = 1;
|
||
try
|
||
{
|
||
// 只统计直接子节点数量,不递归遍历
|
||
if (rootNode.Children != null && rootNode.Children.Count() > 0)
|
||
{
|
||
estimatedNodeCount += rootNode.Children.Count();
|
||
LogManager.Info($"[分层管理器] 节点 '{rootNode.DisplayName}' 包含 {rootNode.Children.Count()} 个直接子节点");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 统计子节点时出现异常: {ex.Message}");
|
||
}
|
||
|
||
var metadata = new Dictionary<string, object>
|
||
{
|
||
["LayerValue"] = layerValue,
|
||
["RootNodeReference"] = rootNode,
|
||
["GroupType"] = groupTypeName,
|
||
["TraversalMode"] = "SmartTraversal",
|
||
["EstimatedNodeCount"] = estimatedNodeCount // 只是估算值,用于显示
|
||
};
|
||
|
||
// 如果检测到了具体的属性类型,保存到元数据中
|
||
if (!string.IsNullOrEmpty(detectedAttributeName))
|
||
{
|
||
metadata["DetectedAttributeName"] = detectedAttributeName;
|
||
}
|
||
|
||
return new SplitPreviewResult
|
||
{
|
||
LayerName = SanitizeLayerName(layerValue),
|
||
LayerAttribute = GetAttributeDescription(strategy, layerValue, metadata),
|
||
Items = allNodes, // 只包含根节点,导出时会自动处理子节点
|
||
Metadata = metadata
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理单个分层(异步版本)
|
||
/// </summary>
|
||
private async Task ProcessSingleLayerAsync(SplitPreviewResult preview, SplitConfiguration config, CancellationToken cancellationToken, int currentIndex = 0, int totalCount = 0)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] 开始处理分层: {preview.LayerName}, 类型: {preview.LayerAttribute}");
|
||
|
||
// 检查取消请求
|
||
cancellationToken.ThrowIfCancellationRequested();
|
||
|
||
// 生成输出文件路径(使用新的智能文件名格式)
|
||
string fileName = GenerateFileName(preview.LayerName, config.Strategy, config);
|
||
string outputPath = Path.Combine(config.OutputDirectory, fileName);
|
||
|
||
LogManager.Info($"[分层管理器] 分层文件路径: {outputPath}");
|
||
|
||
// 直接在UI线程调用,不使用Task.Run包装
|
||
bool success = ExportLayerToNwd(preview, outputPath, config);
|
||
|
||
if (success)
|
||
{
|
||
LogManager.Info($"[分层管理器] 分层 {preview.LayerName} 导出成功: {outputPath}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Error($"[分层管理器] 分层 {preview.LayerName} 导出失败");
|
||
throw new InvalidOperationException($"分层 {preview.LayerName} 导出失败");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 处理分层失败 {preview.LayerName}: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理单个分层(同步版本)
|
||
/// </summary>
|
||
private void ProcessSingleLayer(SplitPreviewResult preview, SplitConfiguration config, int currentIndex = 0, int totalCount = 0)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] 开始同步处理分层: {preview.LayerName}, 类型: {preview.LayerAttribute}");
|
||
|
||
if (preview.Items == null || preview.Items.Count == 0)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 分层 {preview.LayerName} 没有包含模型元素,跳过导出");
|
||
return;
|
||
}
|
||
|
||
// 生成输出文件路径(使用新的智能文件名格式)
|
||
string fileName = GenerateFileName(preview.LayerName, config.Strategy, config);
|
||
string outputPath = Path.Combine(config.OutputDirectory, fileName);
|
||
|
||
LogManager.Info($"[分层管理器] 分层文件路径: {outputPath}");
|
||
|
||
// 检查模型项数量,对大量模型项进行特殊处理
|
||
if (preview.Items != null && preview.Items.Count > 100)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 大量模型项检测:{preview.Items.Count}个,可能需要更多内存和时间");
|
||
|
||
// 强制垃圾回收,释放内存
|
||
GC.Collect();
|
||
GC.WaitForPendingFinalizers();
|
||
GC.Collect();
|
||
|
||
LogManager.Info($"[分层管理器] 垃圾回收完成,当前内存: {GC.GetTotalMemory(false) / 1024 / 1024} MB");
|
||
}
|
||
|
||
// 更新状态:正在导出
|
||
string progressInfo = (currentIndex > 0 && totalCount > 0) ? $" ({currentIndex}/{totalCount})" : "";
|
||
OnStatusChanged($"正在导出{progressInfo}: {preview.LayerName}...");
|
||
|
||
// 使用TryExportToNwd API导出分层
|
||
bool success = ExportLayerToNwd(preview, outputPath, config);
|
||
|
||
if (success)
|
||
{
|
||
LogManager.Info($"[分层管理器] 分层 {preview.LayerName} 导出成功: {outputPath}");
|
||
|
||
// 获取文件大小信息
|
||
string fileSizeInfo = "";
|
||
try
|
||
{
|
||
if (File.Exists(outputPath))
|
||
{
|
||
var fileInfo = new FileInfo(outputPath);
|
||
double sizeMB = fileInfo.Length / (1024.0 * 1024.0);
|
||
fileSizeInfo = $" ({sizeMB:F1}MB)";
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
// 更新状态:导出成功
|
||
string successProgressInfo = (currentIndex > 0 && totalCount > 0) ? $" ({currentIndex}/{totalCount})" : "";
|
||
OnStatusChanged($"导出成功{successProgressInfo}: {preview.LayerName}{fileSizeInfo}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Error($"[分层管理器] 分层 {preview.LayerName} 导出失败");
|
||
string failProgressInfo = (currentIndex > 0 && totalCount > 0) ? $" ({currentIndex}/{totalCount})" : "";
|
||
OnStatusChanged($"导出失败{failProgressInfo}: {preview.LayerName}");
|
||
throw new InvalidOperationException($"分层 {preview.LayerName} 导出失败");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 同步处理分层失败 {preview.LayerName}: {ex.Message}");
|
||
OnStatusChanged($"处理失败: {preview.LayerName} - {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保输出目录存在
|
||
/// </summary>
|
||
private void EnsureOutputDirectory(string directory)
|
||
{
|
||
if (string.IsNullOrEmpty(directory))
|
||
throw new ArgumentException("输出目录不能为空");
|
||
|
||
if (!Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
LogManager.Info($"[分层管理器] 创建输出目录: {directory}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理分层名称(支持中文字符)
|
||
/// </summary>
|
||
private string SanitizeLayerName(string layerName)
|
||
{
|
||
if (string.IsNullOrEmpty(layerName))
|
||
return "未命名分层";
|
||
|
||
// 移除文件名中的非法字符,保留中文字符
|
||
var invalidChars = Path.GetInvalidFileNameChars();
|
||
string sanitized = layerName;
|
||
|
||
foreach (char c in invalidChars)
|
||
{
|
||
sanitized = sanitized.Replace(c, '_');
|
||
}
|
||
|
||
// 移除其他特殊字符,保留字母、数字、下划线和中文字符
|
||
sanitized = System.Text.RegularExpressions.Regex.Replace(sanitized, @"[^\w\u4e00-\u9fa5\-\.]", "_");
|
||
|
||
// 移除多余的下划线
|
||
sanitized = System.Text.RegularExpressions.Regex.Replace(sanitized, @"_{2,}", "_");
|
||
|
||
// 移除首尾下划线
|
||
sanitized = sanitized.Trim('_', ' ');
|
||
|
||
// 限制长度
|
||
if (sanitized.Length > 50)
|
||
{
|
||
sanitized = sanitized.Substring(0, 50).TrimEnd('_');
|
||
}
|
||
|
||
return string.IsNullOrEmpty(sanitized) ? "未命名分层" : sanitized;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取分层策略的属性描述
|
||
/// </summary>
|
||
/// <summary>
|
||
/// 获取具体的分层属性描述(实际属性值而非策略描述)
|
||
/// </summary>
|
||
/// <param name="strategy">分层策略</param>
|
||
/// <param name="layerName">分层名称</param>
|
||
/// <param name="metadata">元数据,可能包含实际的属性信息</param>
|
||
private string GetAttributeDescription(SplitStrategy strategy, string layerName, Dictionary<string, object> metadata = null)
|
||
{
|
||
try
|
||
{
|
||
switch (strategy)
|
||
{
|
||
case SplitStrategy.BySmartFloorDetection:
|
||
// 对于智能检测,直接返回元数据中检测到的真实属性名称
|
||
if (metadata != null && metadata.ContainsKey("DetectedAttributeName"))
|
||
{
|
||
return metadata["DetectedAttributeName"].ToString();
|
||
}
|
||
// 如果没有元数据,通过分层名称推断属性类型
|
||
return InferAttributeTypeFromLayerName(layerName);
|
||
|
||
case SplitStrategy.ByFloorAttribute:
|
||
return "楼层";
|
||
|
||
case SplitStrategy.ByZoneAttribute:
|
||
return "区域";
|
||
|
||
case SplitStrategy.BySubSystemAttribute:
|
||
return "子系统";
|
||
|
||
default:
|
||
return layerName; // 返回实际的分层名称
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"获取分层属性描述失败: {ex.Message}");
|
||
return layerName; // 失败时返回分层名称
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 从分层名称推断属性类型(用于智能检测)
|
||
/// </summary>
|
||
private string InferAttributeTypeFromLayerName(string layerName)
|
||
{
|
||
if (string.IsNullOrEmpty(layerName))
|
||
return "未知属性";
|
||
|
||
string lowerName = layerName.ToLower();
|
||
|
||
// 楼层相关关键词
|
||
if (lowerName.Contains("floor") || lowerName.Contains("level") ||
|
||
lowerName.Contains("楼") || lowerName.Contains("层") ||
|
||
lowerName.Contains("f1") || lowerName.Contains("f2") || lowerName.Contains("f3"))
|
||
{
|
||
return "楼层";
|
||
}
|
||
|
||
// 区域相关关键词
|
||
if (lowerName.Contains("zone") || lowerName.Contains("area") ||
|
||
lowerName.Contains("区") || lowerName.Contains("域") ||
|
||
lowerName.Contains("北") || lowerName.Contains("南") ||
|
||
lowerName.Contains("东") || lowerName.Contains("西"))
|
||
{
|
||
return "区域";
|
||
}
|
||
|
||
// 子系统相关关键词
|
||
if (lowerName.Contains("system") || lowerName.Contains("subsystem") ||
|
||
lowerName.Contains("系统") || lowerName.Contains("机电") ||
|
||
lowerName.Contains("hvac") || lowerName.Contains("电气") ||
|
||
lowerName.Contains("给排水") || lowerName.Contains("消防"))
|
||
{
|
||
return "子系统";
|
||
}
|
||
|
||
// 默认返回分层名称本身
|
||
return layerName;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 估算文件大小
|
||
/// </summary>
|
||
private long EstimateFileSize(int itemCount)
|
||
{
|
||
// 简化的文件大小估算:每个元素约 1KB
|
||
return itemCount * 1024L;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成文件名 - 新的智能格式:根节点名称_分层属性_属性值_时间戳.nwd
|
||
/// </summary>
|
||
/// <param name="attributeValue">属性值(分层名称)</param>
|
||
/// <param name="strategy">分层策略</param>
|
||
/// <param name="config">配置信息</param>
|
||
/// <returns>文件名(不包括路径)</returns>
|
||
private string GenerateFileName(string attributeValue, SplitStrategy strategy, SplitConfiguration config)
|
||
{
|
||
try
|
||
{
|
||
// 获取根节点名称
|
||
string rootNodeName = GetRootNodeName();
|
||
|
||
// 获取分层属性类型名称
|
||
string attributeTypeName = GetAttributeTypeName(strategy);
|
||
|
||
// 清理属性值(原分层名称)
|
||
string sanitizedAttributeValue = SanitizeLayerName(attributeValue);
|
||
|
||
// 生成时间戳
|
||
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||
|
||
// 新的统一格式:根节点名称_分层属性_属性值_时间戳.nwd
|
||
string fileName = $"{rootNodeName}_{attributeTypeName}_{sanitizedAttributeValue}_{timestamp}.nwd";
|
||
|
||
// 确保文件名唯一性
|
||
fileName = EnsureUniqueFileName(fileName, config.OutputDirectory);
|
||
|
||
LogManager.Info($"[分层管理器] 生成智能文件名: {fileName}");
|
||
LogManager.Info($"[分层管理器] 文件名组成: 根节点='{rootNodeName}', 属性类型='{attributeTypeName}', 属性值='{sanitizedAttributeValue}', 时间戳='{timestamp}'");
|
||
|
||
return fileName;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 生成智能文件名失败: {ex.Message}");
|
||
// 失败时使用后备命名方式
|
||
string fallbackName = $"NavisworksModel_分层_{SanitizeLayerName(attributeValue)}_{DateTime.Now:yyyyMMdd_HHmmss}.nwd";
|
||
LogManager.Warning($"[分层管理器] 使用后备文件名: {fallbackName}");
|
||
return fallbackName;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取根节点名称 - 新的智能文件名格式使用
|
||
/// 逻辑:文档文件名 → 第一个模型根项DisplayName → "NavisworksModel"
|
||
/// </summary>
|
||
/// <returns>根节点名称</returns>
|
||
private string GetRootNodeName()
|
||
{
|
||
try
|
||
{
|
||
var document = NavisApplication.ActiveDocument;
|
||
|
||
// 优先使用文档文件名(去除扩展名)
|
||
if (document != null && !string.IsNullOrEmpty(document.FileName))
|
||
{
|
||
string fileName = Path.GetFileNameWithoutExtension(document.FileName);
|
||
if (!string.IsNullOrEmpty(fileName))
|
||
{
|
||
return SanitizeLayerName(fileName);
|
||
}
|
||
}
|
||
|
||
// 其次尝试使用第一个模型根项的DisplayName
|
||
if (document?.Models != null && document.Models.Count > 0)
|
||
{
|
||
foreach (Model model in document.Models)
|
||
{
|
||
if (model.RootItem != null && !string.IsNullOrEmpty(model.RootItem.DisplayName))
|
||
{
|
||
return SanitizeLayerName(model.RootItem.DisplayName);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 最后使用默认名称
|
||
return "NavisworksModel";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 获取根节点名称失败: {ex.Message}");
|
||
return "NavisworksModel";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前项目名称(保留兼容性)
|
||
/// </summary>
|
||
/// <returns>项目名称</returns>
|
||
private string GetCurrentProjectName()
|
||
{
|
||
return GetRootNodeName(); // 直接使用新的根节点名称方法
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取分层属性类型名称
|
||
/// </summary>
|
||
/// <param name="strategy">分层策略</param>
|
||
/// <returns>属性类型名称</returns>
|
||
private string GetAttributeTypeName(SplitStrategy strategy)
|
||
{
|
||
switch (strategy)
|
||
{
|
||
case SplitStrategy.BySmartFloorDetection:
|
||
return "楼层";
|
||
case SplitStrategy.ByFloorAttribute:
|
||
return "楼层";
|
||
case SplitStrategy.ByZoneAttribute:
|
||
return "区域";
|
||
case SplitStrategy.BySubSystemAttribute:
|
||
return "子系统";
|
||
default:
|
||
return "分层";
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 检查输出目录中是否存在同名文件并生成唯一文件名
|
||
/// </summary>
|
||
/// <param name="originalFileName">原始文件名</param>
|
||
/// <param name="outputDirectory">输出目录</param>
|
||
/// <returns>唯一的文件名</returns>
|
||
private string EnsureUniqueFileName(string originalFileName, string outputDirectory)
|
||
{
|
||
try
|
||
{
|
||
string filePath = Path.Combine(outputDirectory, originalFileName);
|
||
|
||
if (!File.Exists(filePath))
|
||
{
|
||
return originalFileName;
|
||
}
|
||
|
||
string nameWithoutExtension = Path.GetFileNameWithoutExtension(originalFileName);
|
||
string extension = Path.GetExtension(originalFileName);
|
||
|
||
int counter = 1;
|
||
string newFileName;
|
||
|
||
do
|
||
{
|
||
newFileName = $"{nameWithoutExtension}_{counter:D2}{extension}";
|
||
filePath = Path.Combine(outputDirectory, newFileName);
|
||
counter++;
|
||
} while (File.Exists(filePath) && counter < 100);
|
||
|
||
LogManager.Info($"[分层管理器] 文件名冲突,重命名为: {newFileName}");
|
||
return newFileName;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 检查文件名唯一性失败: {ex.Message}");
|
||
return originalFileName;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用正确的Navisworks API导出单个分层 - 基于示例中的最佳实践
|
||
/// </summary>
|
||
public bool ExportLayerToNwd(SplitPreviewResult preview, string outputPath, SplitConfiguration config)
|
||
{
|
||
if (preview == null)
|
||
{
|
||
LogManager.Error($"[分层管理器] 预览结果为null");
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] ========== ExportLayerToNwd开始 ==========");
|
||
LogManager.Info($"[分层管理器] 导出层: {preview.LayerName}");
|
||
LogManager.Info($"[分层管理器] 预览项数量: {preview.Items?.Count ?? 0}");
|
||
LogManager.Info($"[分层管理器] 输出路径: {outputPath}");
|
||
|
||
// 直接使用预览结果中的项目
|
||
if (preview.Items == null || preview.Items.Count == 0)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 预览结果中没有模型项");
|
||
return false;
|
||
}
|
||
|
||
var itemsToExport = preview.Items;
|
||
LogManager.Info($"[分层管理器] 将导出 {itemsToExport.Count} 个模型项");
|
||
|
||
// 检查线程状态
|
||
var apartmentState = System.Threading.Thread.CurrentThread.GetApartmentState();
|
||
LogManager.Info($"[分层管理器] 当前线程状态: {apartmentState}");
|
||
|
||
// 核心修复:确保在主 UI 线程中执行 Navisworks API 调用
|
||
bool exportResult = false;
|
||
Exception exportException = null;
|
||
|
||
// 使用 Application.Current.Dispatcher.Invoke 确保在主线程中执行
|
||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] 在主线程中执行导出操作");
|
||
var document = NavisApplication.ActiveDocument;
|
||
|
||
// 保存当前可见性状态(在主线程中)
|
||
LogManager.Info($"[分层管理器] 开始保存当前可见性状态");
|
||
var originalVisibilityState = SaveCurrentVisibilityState(document);
|
||
LogManager.Info($"[分层管理器] 已保存 {originalVisibilityState.Count} 个项目的可见性状态");
|
||
|
||
try
|
||
{
|
||
// 直接使用预览结果中的项目,这些已经是展开的完整节点树
|
||
var itemsToIsolate = preview.Items;
|
||
|
||
LogManager.Info($"[分层管理器] 准备隔离显示 {itemsToIsolate.Count} 个节点");
|
||
|
||
// 使用 VisibilityManager.IsolateSpecificItems 隔离显示
|
||
bool isolateSuccess = VisibilityHelper.IsolateSpecificItems(itemsToIsolate);
|
||
|
||
if (!isolateSuccess)
|
||
{
|
||
throw new InvalidOperationException("隔离显示失败");
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] 隔离显示成功");
|
||
|
||
// 创建导出选项
|
||
var exportOptions = new Autodesk.Navisworks.Api.NwdExportOptions
|
||
{
|
||
ExcludeHiddenItems = true, // 只导出可见项目
|
||
EmbedXrefs = config?.ExportOptions?.EmbedXrefs ?? false,
|
||
PreventObjectPropertyExport = config?.ExportOptions?.PreventObjectPropertyExport ?? false
|
||
};
|
||
|
||
LogManager.Info($"[分层管理器] 开始调用ExportToNwd API(主线程)");
|
||
LogManager.Info($"[分层管理器] 导出选项: ExcludeHiddenItems=true, EmbedXrefs={exportOptions.EmbedXrefs}, PreventObjectPropertyExport={exportOptions.PreventObjectPropertyExport}");
|
||
|
||
// 在主线程中执行导出
|
||
document.ExportToNwd(outputPath, exportOptions);
|
||
|
||
LogManager.Info($"[分层管理器] ExportToNwd API调用完成(主线程)");
|
||
|
||
exportResult = true;
|
||
}
|
||
finally
|
||
{
|
||
// 恢复原始可见性状态(在主线程中)
|
||
try
|
||
{
|
||
RestoreVisibilityState(document, originalVisibilityState);
|
||
LogManager.Info($"[分层管理器] 已恢复原始可见性状态");
|
||
}
|
||
catch (Exception restoreEx)
|
||
{
|
||
LogManager.Error($"[分层管理器] 恢复可见性失败: {restoreEx.Message}");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
exportException = ex;
|
||
}
|
||
});
|
||
|
||
// 检查导出结果
|
||
if (exportException != null)
|
||
{
|
||
LogManager.Error($"[分层管理器] 主线程导出异常: {exportException.Message}");
|
||
LogManager.Error($"[分层管理器] 异常堆栈: {exportException.StackTrace}");
|
||
return false;
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] ========== ExportLayerToNwd完成 ========== 成功导出到: {outputPath}");
|
||
return exportResult;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] ExportLayerToNwd异常: {ex.Message}");
|
||
LogManager.Error($"[分层管理器] 异常堆栈: {ex.StackTrace}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归处理节点可见性 - 精细化控制每个节点
|
||
/// </summary>
|
||
private void ProcessNodeVisibility(ModelItem node, HashSet<ModelItem> exportItemsSet,
|
||
ModelItemCollection itemsToHide,
|
||
ref int processedCount, ref int hiddenCount)
|
||
{
|
||
if (node == null) return;
|
||
|
||
processedCount++;
|
||
|
||
// 检查当前节点是否在导出集合中
|
||
bool shouldExport = exportItemsSet.Contains(node);
|
||
|
||
if (shouldExport)
|
||
{
|
||
// 这个节点要导出,不隐藏
|
||
// 继续处理其子节点(可能有些子节点不需要导出)
|
||
if (node.Children != null)
|
||
{
|
||
foreach (ModelItem child in node.Children)
|
||
{
|
||
ProcessNodeVisibility(child, exportItemsSet, itemsToHide,
|
||
ref processedCount, ref hiddenCount);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 检查是否有任何子节点需要导出
|
||
bool hasExportChild = HasExportDescendant(node, exportItemsSet);
|
||
|
||
if (hasExportChild)
|
||
{
|
||
// 有子节点需要导出,继续递归处理子节点
|
||
// 但不隐藏当前节点(因为需要保持结构)
|
||
if (node.Children != null)
|
||
{
|
||
foreach (ModelItem child in node.Children)
|
||
{
|
||
ProcessNodeVisibility(child, exportItemsSet, itemsToHide,
|
||
ref processedCount, ref hiddenCount);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 当前节点及其所有子节点都不需要导出,隐藏整个分支
|
||
try
|
||
{
|
||
itemsToHide.Add(node);
|
||
hiddenCount++;
|
||
|
||
if (hiddenCount <= 10 || hiddenCount % 1000 == 0)
|
||
{
|
||
LogManager.Debug($"[分层管理器] 隐藏节点: {node.DisplayName}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 添加隐藏节点失败: {ex.Message}");
|
||
}
|
||
// 不需要继续递归子节点,因为整个分支都隐藏了
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查节点是否有需要导出的后代
|
||
/// </summary>
|
||
private bool HasExportDescendant(ModelItem node, HashSet<ModelItem> exportItemsSet)
|
||
{
|
||
if (node?.Children == null || node.Children.Count() == 0)
|
||
return false;
|
||
|
||
// 使用队列进行广度优先搜索,避免深度递归
|
||
var queue = new Queue<ModelItem>();
|
||
foreach (ModelItem child in node.Children)
|
||
{
|
||
queue.Enqueue(child);
|
||
}
|
||
|
||
while (queue.Count > 0)
|
||
{
|
||
var current = queue.Dequeue();
|
||
|
||
if (exportItemsSet.Contains(current))
|
||
{
|
||
return true; // 找到需要导出的后代
|
||
}
|
||
|
||
if (current.Children != null)
|
||
{
|
||
foreach (ModelItem child in current.Children)
|
||
{
|
||
queue.Enqueue(child);
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存当前可见性状态(参考"保存当前选择项"的方法)
|
||
/// </summary>
|
||
private Dictionary<ModelItem, bool> SaveCurrentVisibilityState(Document document)
|
||
{
|
||
var visibilityState = new Dictionary<ModelItem, bool>();
|
||
|
||
try
|
||
{
|
||
// 获取所有顶级项目并记录它们的可见性状态
|
||
foreach (Model model in document.Models)
|
||
{
|
||
foreach (ModelItem topLevelItem in model.RootItem.Children)
|
||
{
|
||
try
|
||
{
|
||
// 记录是否隐藏(IsHidden为true表示隐藏,我们存储可见性所以取反)
|
||
visibilityState[topLevelItem] = !topLevelItem.IsHidden;
|
||
}
|
||
catch
|
||
{
|
||
// 如果无法获取状态,默认为可见
|
||
visibilityState[topLevelItem] = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] 保存可见性状态完成,记录了 {visibilityState.Count} 个顶级项目");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 保存可见性状态失败: {ex.Message}");
|
||
}
|
||
|
||
return visibilityState;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取分层对应的顶级层节点(不展开子节点)
|
||
/// 根据API指南,我们应该选择顶级节点,让ExportToNwd自动处理其子树
|
||
/// </summary>
|
||
|
||
/// <summary>
|
||
/// 检查一个节点是否是顶级节点
|
||
/// </summary>
|
||
private bool IsTopLevelNode(ModelItem item)
|
||
{
|
||
try
|
||
{
|
||
// 顶级节点的特征:父节点是RootItem,或者父节点为null,或者层级很浅
|
||
var parent = item.Parent;
|
||
if (parent == null)
|
||
{
|
||
return true; // 没有父节点,可能是根节点
|
||
}
|
||
|
||
// 检查父节点是否是模型的根项
|
||
var document = NavisApplication.ActiveDocument;
|
||
if (document?.Models != null)
|
||
{
|
||
foreach (Model model in document.Models)
|
||
{
|
||
if (parent == model.RootItem)
|
||
{
|
||
return true; // 父节点是模型根项,说明这是顶级节点
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查找一个节点的顶级父节点
|
||
/// </summary>
|
||
private ModelItem FindTopLevelParent(ModelItem item)
|
||
{
|
||
try
|
||
{
|
||
var current = item;
|
||
var document = NavisApplication.ActiveDocument;
|
||
|
||
// 向上遍历直到找到顶级节点
|
||
while (current != null)
|
||
{
|
||
if (IsTopLevelNode(current))
|
||
{
|
||
return current;
|
||
}
|
||
current = current.Parent;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
catch
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归收集楼层顶层节点下的所有子节点
|
||
/// </summary>
|
||
/// <param name="floorTopLevelItems">楼层顶层节点集合</param>
|
||
/// <returns>包含所有子节点的完整模型项集合</returns>
|
||
private ModelItemCollection ExpandFloorItems(ModelItemCollection floorTopLevelItems)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] ========== ExpandFloorItems开始 ==========");
|
||
LogManager.Info($"[分层管理器] 输入顶层节点数量: {floorTopLevelItems.Count}");
|
||
|
||
var expandedItems = new ModelItemCollection();
|
||
int totalCollected = 0;
|
||
|
||
foreach (ModelItem topLevelItem in floorTopLevelItems)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] 开始展开顶层节点: '{topLevelItem.DisplayName}'");
|
||
|
||
// 添加顶层节点本身(如果它是具体的构件)
|
||
if (IsActualComponent(topLevelItem))
|
||
{
|
||
expandedItems.Add(topLevelItem);
|
||
totalCollected++;
|
||
}
|
||
|
||
// 递归收集所有子节点
|
||
var childItems = CollectAllChildItems(topLevelItem, 0, 10); // 限制最大深度为10
|
||
|
||
// 验证收集到的子项是否有效
|
||
var validChildItems = ValidateModelItems(childItems);
|
||
expandedItems.AddRange(validChildItems);
|
||
totalCollected += validChildItems.Count;
|
||
|
||
LogManager.Info($"[分层管理器] 顶层节点 '{topLevelItem.DisplayName}' 展开完成: 收集到{childItems.Count}个子项,有效{validChildItems.Count}个");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 展开顶层节点失败: {topLevelItem?.DisplayName ?? "null"}, 错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] ========== ExpandFloorItems完成 ========== 总计收集: {totalCollected}个模型项");
|
||
return expandedItems;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] ExpandFloorItems异常: {ex.Message}");
|
||
LogManager.Error($"[分层管理器] 异常堆栈: {ex.StackTrace}");
|
||
return new ModelItemCollection();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归收集模型项的所有子项
|
||
/// </summary>
|
||
/// <param name="parentItem">父级模型项</param>
|
||
/// <param name="currentDepth">当前递归深度</param>
|
||
/// <param name="maxDepth">最大递归深度</param>
|
||
/// <returns>所有子项的集合</returns>
|
||
private ModelItemCollection CollectAllChildItems(ModelItem parentItem, int currentDepth, int maxDepth)
|
||
{
|
||
var childItems = new ModelItemCollection();
|
||
|
||
try
|
||
{
|
||
// 防止递归过深
|
||
if (currentDepth >= maxDepth)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 达到最大递归深度 {maxDepth},停止展开: {parentItem?.DisplayName}");
|
||
return childItems;
|
||
}
|
||
|
||
if (parentItem?.Children == null || parentItem.Children.Count() == 0)
|
||
{
|
||
return childItems;
|
||
}
|
||
|
||
foreach (ModelItem child in parentItem.Children)
|
||
{
|
||
try
|
||
{
|
||
// 添加子项本身
|
||
childItems.Add(child);
|
||
|
||
// 递归收集子项的子项
|
||
var grandChildren = CollectAllChildItems(child, currentDepth + 1, maxDepth);
|
||
if (grandChildren.Count > 0)
|
||
{
|
||
childItems.AddRange(grandChildren);
|
||
}
|
||
|
||
// 每收集100个项目记录一次进度(只在顶层记录)
|
||
if (currentDepth == 0 && childItems.Count % 100 == 0)
|
||
{
|
||
LogManager.Info($"[分层管理器] 已收集 {childItems.Count} 个子项...");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 收集子项失败: {child?.DisplayName ?? "null"}, 错误: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] CollectAllChildItems异常: {ex.Message}");
|
||
}
|
||
|
||
return childItems;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证模型项集合的有效性,过滤掉无效的模型项
|
||
/// </summary>
|
||
/// <param name="items">要验证的模型项集合</param>
|
||
/// <returns>有效的模型项集合</returns>
|
||
private ModelItemCollection ValidateModelItems(ModelItemCollection items)
|
||
{
|
||
var validItems = new ModelItemCollection();
|
||
int nullCount = 0;
|
||
int invalidCount = 0;
|
||
|
||
try
|
||
{
|
||
LogManager.Info($"[分层管理器] 开始验证 {items.Count} 个模型项的有效性...");
|
||
|
||
foreach (ModelItem item in items)
|
||
{
|
||
try
|
||
{
|
||
// 检查模型项是否为null
|
||
if (item == null)
|
||
{
|
||
nullCount++;
|
||
continue;
|
||
}
|
||
|
||
// 检查模型项是否可以访问基本属性(验证其有效性)
|
||
string displayName = item.DisplayName; // 这会抛出异常如果项目无效
|
||
bool hasGeometry = item.HasGeometry; // 检查是否有几何体
|
||
|
||
// 如果能正常访问属性,则认为是有效的
|
||
validItems.Add(item);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
invalidCount++;
|
||
LogManager.Warning($"[分层管理器] 发现无效模型项: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"[分层管理器] 模型项验证完成: 总数{items.Count}, 有效{validItems.Count}, 空项{nullCount}, 无效{invalidCount}");
|
||
|
||
return validItems;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 验证模型项时发生异常: {ex.Message}");
|
||
// 异常时返回原始集合,避免完全失败
|
||
return items;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断模型项是否是实际的构件(而不是组织结构节点)
|
||
/// </summary>
|
||
/// <param name="item">模型项</param>
|
||
/// <returns>如果是实际构件返回true</returns>
|
||
private bool IsActualComponent(ModelItem item)
|
||
{
|
||
try
|
||
{
|
||
if (item == null) return false;
|
||
|
||
// 检查是否有几何体 - 有几何体的通常是实际构件
|
||
if (item.HasGeometry)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// 检查是否是叶子节点且有意义的名称
|
||
if (item.Children != null && item.Children.Count() == 0 && !string.IsNullOrEmpty(item.DisplayName))
|
||
{
|
||
string name = item.DisplayName.ToLower();
|
||
// 排除一些明显的组织节点
|
||
if (!name.Contains("layer") && !name.Contains("group") && !name.Contains("floor"))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 判断构件类型失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 恢复可见性状态
|
||
/// </summary>
|
||
private void RestoreVisibilityState(Document document, Dictionary<ModelItem, bool> visibilityState)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[SimplifiedModelSplitter] 恢复可见性状态");
|
||
|
||
if (visibilityState == null || visibilityState.Count == 0)
|
||
{
|
||
LogManager.Warning("[SimplifiedModelSplitter] 没有可见性状态需要恢复,重置为全部可见");
|
||
document.Models.ResetAllHidden();
|
||
return;
|
||
}
|
||
|
||
// 使用成熟的可见性控制模式 - 分别收集要显示和隐藏的项目
|
||
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 (Exception ex)
|
||
{
|
||
LogManager.Warning($"[分层管理器] 处理项目可见性状态时出错: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 首先重置所有项目为可见状态
|
||
document.Models.ResetAllHidden();
|
||
|
||
// 然后隐藏原来应该隐藏的项目
|
||
if (itemsToHide.Count > 0)
|
||
{
|
||
document.Models.SetHidden(itemsToHide, true);
|
||
LogManager.Info($"[分层管理器] 恢复隐藏 {itemsToHide.Count} 个项目");
|
||
}
|
||
|
||
LogManager.Info("[SimplifiedModelSplitter] 可见性状态恢复完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[分层管理器] 恢复可见性状态失败: {ex.Message}");
|
||
// 失败时至少确保模型处于可见状态
|
||
try
|
||
{
|
||
document.Models.ResetAllHidden();
|
||
LogManager.Info("[SimplifiedModelSplitter] 已重置为全部可见状态");
|
||
}
|
||
catch (Exception resetEx)
|
||
{
|
||
LogManager.Error($"[分层管理器] 重置可见性也失败: {resetEx.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 获取策略的显示名称
|
||
/// </summary>
|
||
/// <param name="strategy">分层策略</param>
|
||
/// <returns>显示名称</returns>
|
||
private string GetStrategyDisplayName(SplitStrategy strategy)
|
||
{
|
||
switch (strategy)
|
||
{
|
||
case SplitStrategy.BySmartFloorDetection:
|
||
return "智能楼层检测分析";
|
||
case SplitStrategy.ByFloorAttribute:
|
||
return "按楼层属性分析";
|
||
case SplitStrategy.ByZoneAttribute:
|
||
return "按区域属性分析";
|
||
case SplitStrategy.BySubSystemAttribute:
|
||
return "按子系统属性分析";
|
||
default:
|
||
return "分层分析";
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 事件触发
|
||
|
||
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
|
||
{
|
||
ProgressChanged?.Invoke(this, e);
|
||
}
|
||
|
||
protected virtual void OnStatusChanged(string status)
|
||
{
|
||
StatusChanged?.Invoke(this, status);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |