增加自定义分层设置,修改物流属性设置的问题。

This commit is contained in:
tian 2025-08-26 18:35:25 +08:00
parent 2845f949e3
commit 944f83bd7e
17 changed files with 2917 additions and 4108 deletions

View File

@ -49,7 +49,8 @@
"Bash(powershell:*)",
"mcp__serena__insert_before_symbol",
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" NavisworksTransportPlugin.csproj /p:Configuration=Debug /p:Platform=AnyCPU /verbosity:normal)",
"Bash(cmd:*)"
"Bash(cmd:*)",
"WebFetch(domain:twentytwo.space)"
],
"deny": [],
"additionalDirectories": [

View File

@ -143,6 +143,7 @@
<!-- Core - Properties Management -->
<Compile Include="src\Core\Properties\AttributeGrouper.cs" />
<Compile Include="src\Core\Properties\CategoryAttributeManager.cs" />
<Compile Include="src\Core\FloorAttributeManager.cs" />
<!-- PathPlanning - Auto Path Planning -->
<Compile Include="src\PathPlanning\GridMap.cs" />
@ -237,6 +238,8 @@
<Compile Include="src\Utils\FloorDetector.cs" />
<Compile Include="src\Utils\GeometryExtractor.cs" />
<Compile Include="src\Utils\LogManager.cs" />
<Compile Include="src\Utils\NavisworksApiHelper.cs" />
<Compile Include="src\Utils\NavisworksSelectionHelper.cs" />
<Compile Include="src\Utils\UnitsConverter.cs" />
<!-- Legacy (2017 compatibility - can be removed in 2026-only branch) -->

View File

@ -0,0 +1,93 @@
# 优化选择提示信息 - 完成报告
## 优化目标
优化选择提示信息,在"已选择X个模型"后面添加节点名称,让用户更清楚当前操作的对象。
## 实现方案
### 1. 创建通用格式化函数
`LayerManagementViewModel.cs``ModelSettingsViewModel.cs` 中新增了 `FormatSelectionText` 方法:
```csharp
/// <summary>
/// 格式化选择状态文本,包含节点名称
/// </summary>
/// <param name="count">选择数量</param>
/// <param name="selectedItems">选择的项目集合</param>
/// <param name="unitName">单位名称(如"个模型"、"个节点"</param>
/// <param name="maxDisplayCount">最大显示名称数量</param>
/// <param name="maxTotalLength">最大总长度</param>
/// <returns>格式化后的选择状态文本</returns>
private string FormatSelectionText(int count, IEnumerable<ModelItem> selectedItems = null,
string unitName = "个模型", int maxDisplayCount = 3, int maxTotalLength = 80)
```
### 2. 优化显示策略
- **单选时**: 显示完整节点名称超过50字符时截断
- **多选时**: 显示前几个名称,超过一定长度或数量时用省略号
- **智能截断**: 避免信息过长影响UI显示
### 3. 更新数据结构
扩展了 `SelectNodesResult` 类,增加了 `SelectedItems` 属性来保存选择的项目信息:
```csharp
public class SelectNodesResult
{
public bool IsSuccess { get; set; }
public int Count { get; set; }
public List<ModelItem> SelectedItems { get; set; } = new List<ModelItem>();
public string ErrorMessage { get; set; }
}
```
### 4. 优化涉及的区域
#### LayerManagementViewModel.cs 优化点:
1. **节点选择区域**第801行选择节点时的状态显示
2. **楼层属性设置区域**第2314行和第2372行楼层属性相关的模型选择状态
3. **选择集保存区域**:选择集保存功能的选择状态显示
#### ModelSettingsViewModel.cs 优化点:
1. **模型选择状态显示**第329行物流属性设置相关的模型选择状态
## 优化效果示例
### 优化前:
- "已选择1个模型"
- "已选择5个模型"
### 优化后:
- "已选择1个模型: 主楼-一层-墙体-W001"
- "已选择2个模型: Wall_001, Door_002"
- "已选择5个模型: Wall_001, Door_002, Window_003..."
### 长名称处理:
- 单选超长: "已选择1个模型: 这是一个非常长的节点名称包含很多详细信息..."
- 多选智能截断: "已选择8个模型: Node1, Node2, Very_Long_Node_Name..."
## 技术细节
### 1. 线程安全处理
- 业务逻辑在后台线程执行避免UI阻塞
- 使用 `UIStateManager.ExecuteUIUpdateAsync()` 确保UI更新在主线程
### 2. 错误处理
- 保持原有的错误处理逻辑
- 当获取节点信息失败时,仍显示基本的数量信息
### 3. 性能优化
- 使用 `Take(maxDisplayCount + 1)` 限制处理的项目数量
- 智能截断避免过长字符串的处理
## 代码兼容性
- 保持向后兼容,不影响现有功能
- 编译测试通过,无破坏性更改
- 遵循现有的错误处理和日志记录模式
## 结论
成功实现了选择提示信息的优化,用户现在可以清楚地看到:
1. 选择了多少个对象
2. 选择的具体对象名称
3. 对于多选情况的智能显示
这大大提升了用户体验,让用户能够更清楚地了解当前的操作对象。

View File

@ -0,0 +1,196 @@
# 楼层属性设置功能选择同步问题修复报告
## 问题描述
在NavisworksTransport插件的分层管理功能中当用户在Navisworks中选择树节点时
- **正常现象**:选择集保存区域显示"已选择1个项目"
- **问题现象**:楼层属性设置区域的状态信息没有变化,设置按钮不能点击
## 问题根因分析
### 1. 事件订阅对比
#### 选择集保存功能(正常工作)
- **位置**`LogisticsControlPanel.xaml.cs`
- **事件订阅**`NavisApplication.ActiveDocument.CurrentSelection.Changed += OnSelectionChanged;`
- **处理逻辑**:在`OnSelectionChanged`中调用`UpdateCurrentSelectionAsync()`更新主ViewModel的选择状态
#### 楼层属性设置功能(有问题)
- **位置**`LayerManagementViewModel.cs`
- **事件订阅****没有订阅Navisworks选择变化事件**
- **初始化**:只在`InitializeAsync()`中调用一次`RefreshSelectionAsync()`
### 2. 事件处理流程差异
**正常流程(选择集保存)**
```
Navisworks选择变化
→ LogisticsControlPanel.OnSelectionChanged
→ ViewModel.UpdateCurrentSelectionAsync()
→ 更新"已选择X个项目"状态
```
**问题流程(楼层属性设置)**
```
Navisworks选择变化
→ (无事件处理)
→ 状态不更新
→ 按钮保持不可用状态
```
## 解决方案
### 1. 添加选择事件订阅
在`LayerManagementViewModel.cs`中添加选择事件处理机制:
```csharp
// 构造函数中订阅事件
SubscribeToSelectionEvents();
// 添加订阅方法
private void SubscribeToSelectionEvents()
{
try
{
if (Autodesk.Navisworks.Api.Application.ActiveDocument?.CurrentSelection != null)
{
Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentSelection.Changed += OnNavisworksSelectionChanged;
LogManager.Info("[LayerManagementViewModel] 已订阅Navisworks选择变化事件");
}
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 订阅选择事件失败: {ex.Message}", ex);
}
}
```
### 2. 实现选择变化处理器
```csharp
private async void OnNavisworksSelectionChanged(object sender, EventArgs e)
{
try
{
// 使用UIStateManager确保在正确的线程上执行UI更新
await _uiStateManager.ExecuteUIUpdateAsync(async () =>
{
await UpdateFloorAttributeSelectionStateAsync();
});
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 处理选择变化事件异常: {ex.Message}", ex);
}
}
```
### 3. 专门的楼层属性状态更新方法
```csharp
private async Task UpdateFloorAttributeSelectionStateAsync()
{
try
{
// 纯业务逻辑执行(后台线程)
var result = await Task.Run(() =>
{
try
{
var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (document?.CurrentSelection?.SelectedItems?.Count > 0)
{
var selectedCount = document.CurrentSelection.SelectedItems.Count;
return new { Success = true, Count = selectedCount, Message = $"已选择 {selectedCount} 个模型" };
}
else
{
return new { Success = true, Count = 0, Message = "请在主界面中选择需要设置的模型" };
}
}
catch (Exception ex)
{
return new { Success = false, Count = 0, Message = $"检查选择状态失败: {ex.Message}" };
}
});
// UI更新
SelectedModelsText = result.Message;
// 刷新命令状态
OnPropertyChanged(nameof(HasSelectedItems));
OnPropertyChanged(nameof(HasSelectedModels));
OnPropertyChanged(nameof(CanSetFloorAttribute));
OnPropertyChanged(nameof(CanClearFloorAttribute));
LogManager.Info($"[LayerManagementViewModel] 楼层属性选择状态已更新: {result.Message}");
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 更新楼层属性选择状态异常: {ex.Message}", ex);
SelectedModelsText = "检查选择状态异常";
}
}
```
### 4. 资源清理
在`Dispose()`方法中添加事件取消订阅:
```csharp
// 取消Navisworks选择变化事件订阅
UnsubscribeFromSelectionEvents();
```
## 修复验证
### 1. UI数据绑定验证
确认XAML中的绑定正确
- `SelectedModelsText` → 显示选择状态
- `CanSetFloorAttribute` → 设置按钮可用性
- `CanClearFloorAttribute` → 清除按钮可用性
### 2. 预期修复结果
修复后,当用户选择树节点时:
- ✅ 楼层属性设置区域的状态信息应该正确更新
- ✅ 设置楼层属性的按钮应该变为可点击状态
- ✅ 状态提示应该显示当前选择的对象信息
- ✅ 与选择集保存功能保持同步,无冲突
## 技术要点
### 1. 线程安全
- 使用`UIStateManager.ExecuteUIUpdateAsync()`确保UI更新在正确线程执行
- 业务逻辑在后台线程中执行避免阻塞UI
### 2. 事件处理模式
- 遵循项目的统一事件处理架构
- 业务逻辑与UI分离符合MVVM模式
### 3. 错误处理
- 完善的异常处理和日志记录
- 优雅的错误状态显示
## 影响范围
### 修改的文件
- `src/UI/WPF/ViewModels/LayerManagementViewModel.cs`
### 涉及的功能
- 楼层属性设置功能的用户体验改进
- 不影响现有选择集保存等其他功能
### 风险评估
- **低风险**修改仅限于LayerManagementViewModel内部
- **向后兼容**不改变现有API或接口
- **独立性**:与其他功能模块解耦
## 总结
此修复通过为LayerManagementViewModel添加Navisworks选择变化事件监听解决了楼层属性设置功能中状态不同步的问题。修复遵循了项目既有的架构模式确保了代码质量和系统稳定性。
修复后的楼层属性设置功能将具备与选择集保存功能相同的响应性和用户体验,提升了整体插件的一致性和可用性。

View File

@ -734,14 +734,81 @@ AddCustomProperty("自定义分类", "Custom_Category", "自定义值");
#### 修改现有自定义属性
```csharp
// ✅ 修改已存在的自定义属性值
// ✅ 正确的修改方法基于实际修复经验和官方AutoUserPropsExample示例
private void UpdateCustomProperty(string categoryName, string internalName, string newValue)
{
// 重新调用SetUserDefined即可覆盖现有值
AddCustomProperty(categoryName, internalName, newValue);
InwOpSelection2 selection = m_state.CurrentSelection as InwOpSelection2;
if (selection.Paths().Count > 0)
{
InwGUIPropertyNode2 propertyNode =
m_state.GetGUIPropertyNode(selection.Paths()[1], true) as InwGUIPropertyNode2;
// 🔍 关键第一步:查找现有属性分类的正确索引
int existingIndex = GetFloorAttributeIndex(propertyNode, categoryName);
// 创建新的属性内容
InwOaPropertyVec propertyVector =
m_state.ObjectFactory(nwEObjectType.eObjectType_nwOaPropertyVec);
InwOaProperty property =
m_state.ObjectFactory(nwEObjectType.eObjectType_nwOaProperty);
property.name = "Floor_Level";
property.UserName = "楼层";
property.value = newValue;
propertyVector.Properties().Add(property);
if (existingIndex >= 0)
{
// 🎯 存在属性时:使用正确的索引进行更新
// 注意SetUserDefined的索引是从1开始的
int updateIndex = existingIndex + 1;
propertyNode.SetUserDefined(updateIndex, categoryName, internalName, propertyVector);
}
else
{
// 🆕 不存在属性时使用索引0创建新属性分类
propertyNode.SetUserDefined(0, categoryName, internalName, propertyVector);
}
}
}
// ✅ 查找现有属性分类索引的正确方法
private int GetFloorAttributeIndex(InwGUIPropertyNode2 propertyNode, string categoryName)
{
int userDefinedIndex = 0; // 用户定义属性的索引计数器
foreach (InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.UserDefined)
{
// ⚠️ 关键修复只需要匹配ClassUserName不需要匹配ClassName
// ClassName是系统生成的如"LcOaPropOverrideCat"),不是我们控制的
if (attribute.ClassUserName == categoryName)
{
return userDefinedIndex; // 返回在用户定义属性中的索引位置
}
userDefinedIndex++;
}
}
return -1; // 未找到返回-1
}
// ✅ 使用示例 - 正确的更新方式
// 第一次设置:创建新属性
UpdateCustomProperty("分层信息", "Floor_Category", "F1");
// 第二次设置:找到并更新现有属性
UpdateCustomProperty("分层信息", "Floor_Category", "F2");
// 第三次设置:继续更新同一个属性
UpdateCustomProperty("分层信息", "Floor_Category", "F3");
```
**关键要点**(基于实际修复经验):
- **动态索引查找**:不能使用硬编码索引,必须动态查找现有属性的位置
- **只匹配ClassUserName**`ClassName`是系统生成的标识符(如`LcOaPropOverrideCat`),我们无法控制
- **索引转换规则**查找返回的是0基索引`SetUserDefined`使用的是1基索引
- **创建vs更新**index=0表示创建新属性index>0表示更新指定位置的属性
#### 删除自定义属性
```csharp
// ✅ 删除指定的自定义属性分类
@ -1013,7 +1080,367 @@ public void MarkAsLogisticsNode(ModelItem item, string category, Dictionary<stri
7. **属性类型限制**只读属性无法通过API修改只能通过可编辑API间接影响
8. **覆盖vs原始**OverridePermanent系列方法是覆盖原始属性可以恢复
## 13. 参考官方示例
### 12.8 COM API 自定义属性重要修复经验
基于实际生产环境中发现的问题和修复经验这里记录COM API操作自定义属性时的关键要点。
#### 🚨 常见陷阱:重复创建属性分类
**问题现象**:对同一对象多次设置自定义属性时,会创建多个同名的属性分类,而不是更新现有属性。
**错误做法**
```csharp
// ❌ 错误:硬编码索引会导致重复创建
propertyNode.SetUserDefined(1, "分层信息", "Floor_Category", propertyVector);
```
**正确做法**
```csharp
// ✅ 正确:动态查找现有属性的索引
int existingIndex = GetFloorAttributeIndex(propertyNode, "分层信息");
if (existingIndex >= 0)
{
// 更新现有属性注意索引转换查找用0基设置用1基
propertyNode.SetUserDefined(existingIndex + 1, "分层信息", "Floor_Category", propertyVector);
}
else
{
// 创建新属性
propertyNode.SetUserDefined(0, "分层信息", "Floor_Category", propertyVector);
}
```
#### 🔍 ClassName vs ClassUserName 的区别
这是导致重复创建问题的根本原因:
**ClassUserName**:用户定义的显示名称,由我们控制
```csharp
// 我们定义的显示名称
attribute.ClassUserName == "分层信息"
```
**ClassName**:系统生成的内部标识符,我们无法控制
```csharp
// 系统生成的内部名称,每次可能不同
attribute.ClassName == "LcOaPropOverrideCat" // 系统生成,不可预测
```
**修复要点**
```csharp
// ❌ 错误:同时匹配两个条件会导致找不到现有属性
if (attribute.UserDefined &&
attribute.ClassUserName == FLOOR_CATEGORY &&
attribute.ClassName == FLOOR_CATEGORY_INTERNAL)
// ✅ 正确:只匹配用户显示名称
if (attribute.UserDefined &&
attribute.ClassUserName == FLOOR_CATEGORY)
```
#### 📝 索引管理的正确方式
**索引计算规则**
1. 遍历所有属性,只计算 `UserDefined == true` 的属性
2. 查找方法返回的是**0基索引**第一个用户属性是0
3. `SetUserDefined`方法使用的是**1基索引**第一个用户属性是1
4. `RemoveUserDefined`方法也使用**1基索引**
```csharp
// ✅ 正确的索引转换
int foundIndex = GetFloorAttributeIndex(propertyNode, categoryName); // 返回0, 1, 2...
if (foundIndex >= 0)
{
// SetUserDefined 需要1基索引
int setIndex = foundIndex + 1; // 转换为 1, 2, 3...
propertyNode.SetUserDefined(setIndex, categoryName, internalName, propertyVector);
// RemoveUserDefined 也需要1基索引
int removeIndex = foundIndex + 1; // 转换为 1, 2, 3...
propertyNode.RemoveUserDefined(removeIndex);
}
```
#### 🛠️ 完整的修复模板
```csharp
public class CustomPropertyManager
{
// ✅ 设置自定义属性的正确方式
public bool SetCustomProperty(ModelItem item, string categoryName,
string internalName, string propertyName,
string displayName, string value)
{
return ExecuteWithUIThread(() =>
{
var state = ComApiBridge.State;
var comPath = ComApiBridge.ToInwOaPath(item);
var propertyNode = (ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
// 🔍 关键:动态查找现有属性索引
int existingIndex = FindCustomPropertyIndex(propertyNode, categoryName);
// 创建属性内容
var propertyCategory = (ComApi.InwOaPropertyVec)state.ObjectFactory(
ComApi.nwEObjectType.eObjectType_nwOaPropertyVec, null, null);
var property = (ComApi.InwOaProperty)state.ObjectFactory(
ComApi.nwEObjectType.eObjectType_nwOaProperty, null, null);
property.name = propertyName;
property.UserName = displayName;
property.value = value;
propertyCategory.Properties().Add(property);
if (existingIndex >= 0)
{
// 🎯 更新现有属性(索引+1
propertyNode.SetUserDefined(existingIndex + 1, categoryName, internalName, propertyCategory);
LogManager.Info($"更新现有属性分类 '{categoryName}' (index={existingIndex + 1})");
}
else
{
// 🆕 创建新属性索引0
propertyNode.SetUserDefined(0, categoryName, internalName, propertyCategory);
LogManager.Info($"创建新属性分类 '{categoryName}' (index=0)");
}
return true;
});
}
// ✅ 查找自定义属性索引的正确方式
private int FindCustomPropertyIndex(ComApi.InwGUIPropertyNode2 propertyNode, string categoryName)
{
int userDefinedIndex = 0;
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.UserDefined)
{
// ⚠️ 关键只匹配ClassUserName不匹配ClassName
if (attribute.ClassUserName == categoryName)
{
return userDefinedIndex;
}
userDefinedIndex++;
}
}
return -1;
}
// ✅ 清除自定义属性的正确方式
public bool ClearCustomProperty(ModelItem item, string categoryName)
{
return ExecuteWithUIThread(() =>
{
var state = ComApiBridge.State;
var comPath = ComApiBridge.ToInwOaPath(item);
var propertyNode = (ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
int existingIndex = FindCustomPropertyIndex(propertyNode, categoryName);
if (existingIndex >= 0)
{
// 🗑️ 删除属性(索引+1
propertyNode.RemoveUserDefined(existingIndex + 1);
LogManager.Info($"删除属性分类 '{categoryName}' (removed index={existingIndex + 1})");
return true;
}
else
{
LogManager.Info($"属性分类 '{categoryName}' 不存在,无需删除");
return false;
}
});
}
}
```
#### 📋 问题诊断检查列表
当遇到属性重复创建问题时,检查以下要点:
- [ ] 是否使用了硬编码索引如固定使用索引1
- [ ] 是否同时匹配了`ClassUserName`和`ClassName`
- [ ] 是否正确进行了索引转换0基→1基
- [ ] 是否在主UI线程中执行COM API调用
- [ ] 日志中是否显示"未找到现有属性分类"但实际存在
#### 🎯 修复验证方法
**测试步骤**
1. 选择一个对象,设置自定义属性(如"F1"
2. 再次选择同一对象,设置不同值(如"F2"
3. 检查属性面板,应该只有一个属性分类
4. 重复步骤2设置第三个值如"F3"
5. 确认仍然只有一个属性分类,值为最新设置的值
**日志验证**
```
[FloorAttributeManager] 找到楼层属性分类,索引为: 0
[FloorAttributeManager] ✅ 成功更新现有楼层属性分类 (index=1)
```
#### 💡 经验总结
1. **COM API的ClassUserName是关键**:这是我们控制的显示名称,用于匹配现有属性
2. **ClassName不可靠**:系统生成的内部标识符,每次可能不同
3. **索引转换至关重要**查找用0基设置/删除用1基
4. **动态索引查找必不可少**:硬编码索引是重复创建问题的根源
5. **详细日志帮助调试**:记录索引查找和转换过程,便于问题定位
## 13. 缓存刷新和状态同步 ⚠️ 重要
### 13.1 缓存刷新的必要性
在某些API操作特别是自定义属性的删除Navisworks内部缓存可能与实际状态不同步导致
- 搜索结果不准确
- 属性面板显示异常
- 后续API调用出现意外行为
### 13.2 安全的缓存刷新方法 ✅
基于实际生产环境的测试和验证,以下是推荐的缓存刷新方法:
```csharp
// ✅ 最安全的缓存刷新方法空集合的SetHidden操作
public static void SafeCacheRefresh()
{
try
{
var document = NavisApplication.ActiveDocument;
if (document?.Models != null)
{
// 使用空集合的SetHidden操作触发缓存更新
// 这是一个几乎零开销的"伪操作"
var emptyCollection = new ModelItemCollection();
document.Models.SetHidden(emptyCollection, false);
LogManager.WriteLog("✅ 已执行轻量级缓存刷新");
}
}
catch (Exception ex)
{
LogManager.WriteLog($"⚠️ 缓存刷新失败,但不影响主要操作: {ex.Message}");
}
}
```
### 13.3 危险的缓存刷新方法 ❌
**⚠️ 避免使用以下方法,已确认会导致程序崩溃:**
```csharp
// ❌ 危险:会导致程序崩溃
document.Models.ResetAllTemporaryMaterials();
// ❌ 危险:重载过重,可能影响性能
document.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
// ❌ 危险:可能干扰用户界面状态
document.Models.ResetOverriddenTransparency(allItems);
```
### 13.4 何时需要缓存刷新
**必须刷新的操作**
- COM API删除自定义属性后
- 大批量修改模型可见性后
- 复杂的Transform操作后
**可选刷新的操作**
- 添加自定义属性(通常自动同步)
- 简单的颜色或透明度覆盖
- 单个对象的操作
### 13.5 实践模式:操作后刷新
```csharp
// ✅ 推荐模式:操作 + 刷新 + 验证
public int RemoveLogisticsAttributes(ModelItemCollection items)
{
int successCount = 0;
try
{
// 1. 执行主要操作
foreach (var item in items)
{
// COM API删除操作...
successCount++;
}
}
catch (Exception ex)
{
LogManager.WriteLog($"操作失败: {ex.Message}");
}
// 2. 如果有成功操作,执行缓存刷新
if (successCount > 0)
{
SafeCacheRefresh(); // 使用安全的刷新方法
}
return successCount;
}
```
### 13.6 缓存问题的诊断
**常见症状**
- 删除属性后,搜索仍能找到已删除的属性
- 属性面板显示的内容与API返回不一致
- 连续相同操作的结果不同
**诊断代码**
```csharp
// ✅ 验证缓存同步状态
public bool VerifyCacheSync(ModelItem item, string categoryName)
{
// 通过NET API检查
bool netApiResult = HasPropertyCategory(item, categoryName);
// 通过COM API检查
bool comApiResult = HasPropertyCategoryViaCom(item, categoryName);
if (netApiResult != comApiResult)
{
LogManager.Warning($"缓存不同步检测: NET={netApiResult}, COM={comApiResult}");
SafeCacheRefresh();
return false;
}
return true;
}
```
### 13.7 多线程环境下的缓存刷新
```csharp
// ✅ 线程安全的缓存刷新
public async Task SafeCacheRefreshAsync()
{
await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
{
SafeCacheRefresh(); // 确保在主线程中执行
});
}
```
### 13.8 缓存刷新最佳实践
| 场景 | 刷新时机 | 刷新方法 | 必要性 |
|------|----------|----------|--------|
| 删除自定义属性 | 操作完成后 | 空集合SetHidden | 必须 |
| 批量隐藏对象 | 操作完成后 | 可选择不刷新 | 可选 |
| Transform动画 | 动画结束后 | 空集合SetHidden | 推荐 |
| 颜色覆盖 | 通常不需要 | 无 | 不需要 |
**关键原则**
1. **安全第一**:只使用验证过的安全方法
2. **按需刷新**:不是所有操作都需要缓存刷新
3. **异常处理**:缓存刷新失败不应该影响主要功能
4. **线程安全**在主UI线程中执行刷新操作
## 14. 参考官方示例
强烈建议查看以下官方示例了解更多用法:
- `SearchComparisonPlugIn.cs` - 搜索性能对比和属性搜索

View File

@ -0,0 +1,491 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Autodesk.Navisworks.Api;
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
using NavisworksTransport.Utils;
using NavisApplication = Autodesk.Navisworks.Api.Application;
namespace NavisworksTransport.Core
{
/// <summary>
/// 楼层属性管理器 - 基于COM API的自定义楼层属性管理
/// 提供楼层属性的增删改查功能,支持属性继承查询
/// </summary>
public class FloorAttributeManager
{
#region
/// <summary>
/// 分层信息属性分类名称
/// </summary>
public const string FLOOR_CATEGORY = "分层信息";
/// <summary>
/// 分层信息属性分类内部名称
/// </summary>
public const string FLOOR_CATEGORY_INTERNAL = "Floor_Info_Category";
/// <summary>
/// 楼层属性名称
/// </summary>
public const string FLOOR_LEVEL_PROPERTY = "Floor_Level";
/// <summary>
/// 楼层属性显示名称
/// </summary>
public const string FLOOR_LEVEL_DISPLAY_NAME = "楼层";
/// <summary>
/// 区域属性名称
/// </summary>
public const string ZONE_PROPERTY = "Zone";
/// <summary>
/// 区域属性显示名称
/// </summary>
public const string ZONE_DISPLAY_NAME = "区域";
/// <summary>
/// 子系统属性名称
/// </summary>
public const string SUBSYSTEM_PROPERTY = "Sub_System";
/// <summary>
/// 子系统属性显示名称
/// </summary>
public const string SUBSYSTEM_DISPLAY_NAME = "子系统";
#endregion
#region
/// <summary>
/// 设置对象的分层属性
/// </summary>
/// <param name="item">目标模型项</param>
/// <param name="floorLevel">楼层标识,如"F1", "F2", "B1"等</param>
/// <param name="zone">区域标识(可选)</param>
/// <param name="subSystem">子系统标识(可选)</param>
/// <returns>设置成功返回true失败返回false</returns>
public bool SetFloorAttribute(ModelItem item, string floorLevel, string zone = null, string subSystem = null)
{
if (item == null || string.IsNullOrEmpty(floorLevel))
{
LogManager.Error("[FloorAttributeManager] 设置楼层属性失败:参数无效");
return false;
}
try
{
LogManager.Info($"[FloorAttributeManager] 开始设置模型 {item.DisplayName} 的楼层属性 {floorLevel}");
return ExecuteWithUIThread(() =>
{
// 获取COM API状态对象
ComApi.InwOpState10 state = ComApiBridge.State;
// 转换ModelItem为COM路径
ComApi.InwOaPath comPath = ComApiBridge.ToInwOaPath(item);
// 获取属性节点
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
// 创建新的属性分类
ComApi.InwOaPropertyVec propertyCategory =
(ComApi.InwOaPropertyVec)state.ObjectFactory(
ComApi.nwEObjectType.eObjectType_nwOaPropertyVec, null, null);
// 添加楼层属性
AddFloorProperty(state, propertyCategory, FLOOR_LEVEL_DISPLAY_NAME,
floorLevel, FLOOR_LEVEL_PROPERTY);
// 添加可选属性
if (!string.IsNullOrEmpty(zone))
{
AddFloorProperty(state, propertyCategory, ZONE_DISPLAY_NAME,
zone, ZONE_PROPERTY);
}
if (!string.IsNullOrEmpty(subSystem))
{
AddFloorProperty(state, propertyCategory, SUBSYSTEM_DISPLAY_NAME,
subSystem, SUBSYSTEM_PROPERTY);
}
// 检查是否已存在楼层属性,获取正确的索引
int existingFloorAttributeIndex = GetFloorAttributeIndex(propertyNode);
if (existingFloorAttributeIndex >= 0)
{
// 存在楼层属性时,使用正确的索引进行修改
int updateIndex = existingFloorAttributeIndex + 1;
propertyNode.SetUserDefined(updateIndex, FLOOR_CATEGORY, FLOOR_CATEGORY_INTERNAL, propertyCategory);
LogManager.Info($"[FloorAttributeManager] ✅ 成功更新楼层属性 (索引={updateIndex})");
}
else
{
// 不存在楼层属性时使用索引0创建新属性
propertyNode.SetUserDefined(0, FLOOR_CATEGORY, FLOOR_CATEGORY_INTERNAL, propertyCategory);
LogManager.Info($"[FloorAttributeManager] ✅ 成功创建楼层属性 (索引=0)");
}
return true;
});
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] ❌ 设置楼层属性失败,错误: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 获取对象的楼层标识,支持继承查询
/// </summary>
/// <param name="item">目标模型项</param>
/// <param name="searchParents">是否向上查找父节点的楼层属性</param>
/// <returns>楼层标识未找到返回null</returns>
public string GetFloorLevel(ModelItem item, bool searchParents = true)
{
if (item == null) return null;
try
{
// 首先检查当前节点的楼层属性
string floorLevel = GetFloorLevelDirect(item);
if (!string.IsNullOrEmpty(floorLevel))
{
return floorLevel;
}
// 如果启用父节点搜索,向上查找
if (searchParents)
{
var current = item.Parent;
while (current != null)
{
floorLevel = GetFloorLevelDirect(current);
if (!string.IsNullOrEmpty(floorLevel))
{
LogManager.Debug($"[FloorAttributeManager] 从父节点 {current.DisplayName} 继承楼层属性 {floorLevel}");
return floorLevel;
}
current = current.Parent;
}
}
return null;
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] 获取楼层属性失败:{ex.Message}", ex);
return null;
}
}
/// <summary>
/// 批量设置楼层属性
/// </summary>
/// <param name="floorMappings">楼层映射字典键为ModelItem值为楼层标识</param>
/// <returns>设置成功的数量</returns>
public int SetFloorAttributeBatch(Dictionary<ModelItem, string> floorMappings)
{
if (floorMappings == null || floorMappings.Count == 0)
{
LogManager.Warning("[FloorAttributeManager] 批量设置楼层属性:输入为空");
return 0;
}
int successCount = 0;
LogManager.Info($"[FloorAttributeManager] 开始批量设置楼层属性,共 {floorMappings.Count} 个对象");
foreach (var mapping in floorMappings)
{
try
{
if (SetFloorAttribute(mapping.Key, mapping.Value))
{
successCount++;
}
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] 批量设置楼层属性时出错:{mapping.Key?.DisplayName} -> {mapping.Value}, 错误: {ex.Message}");
}
}
// 批量操作完成后执行缓存刷新
if (successCount > 0)
{
NavisworksApiHelper.SafeCacheRefresh("FloorAttributeManager");
}
LogManager.Info($"[FloorAttributeManager] 批量设置完成,成功 {successCount}/{floorMappings.Count} 个对象");
return successCount;
}
/// <summary>
/// 获取所有楼层分组
/// </summary>
/// <returns>楼层分组字典键为楼层标识值为该楼层的ModelItem列表</returns>
public Dictionary<string, List<ModelItem>> GetFloorGroups()
{
var floorGroups = new Dictionary<string, List<ModelItem>>();
try
{
LogManager.Info("[FloorAttributeManager] 开始分析楼层分组");
// 获取所有模型项
var allItems = NavisApplication.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(item => item.HasGeometry)
.ToList();
LogManager.Info($"[FloorAttributeManager] 共找到 {allItems.Count} 个几何体对象");
foreach (var item in allItems)
{
try
{
string floorLevel = GetFloorLevel(item, true);
if (!string.IsNullOrEmpty(floorLevel))
{
if (!floorGroups.ContainsKey(floorLevel))
{
floorGroups[floorLevel] = new List<ModelItem>();
}
floorGroups[floorLevel].Add(item);
}
else
{
// 未分层对象归类到"未分层"
const string unclassified = "未分层";
if (!floorGroups.ContainsKey(unclassified))
{
floorGroups[unclassified] = new List<ModelItem>();
}
floorGroups[unclassified].Add(item);
}
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] 处理对象 {item.DisplayName} 时出错:{ex.Message}");
}
}
LogManager.Info($"[FloorAttributeManager] 楼层分组完成,共 {floorGroups.Count} 个分组");
foreach (var group in floorGroups)
{
LogManager.Info($" - {group.Key}: {group.Value.Count} 个对象");
}
return floorGroups;
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] 获取楼层分组失败:{ex.Message}", ex);
return new Dictionary<string, List<ModelItem>>();
}
}
/// <summary>
/// 清除对象的楼层属性
/// </summary>
/// <param name="item">目标模型项</param>
/// <returns>清除成功返回true</returns>
public bool ClearFloorAttribute(ModelItem item)
{
if (item == null) return false;
try
{
LogManager.Info($"[FloorAttributeManager] 开始清除模型 {item.DisplayName} 的楼层属性");
bool result = ExecuteWithUIThread(() =>
{
ComApi.InwOpState10 state = ComApiBridge.State;
ComApi.InwOaPath comPath = ComApiBridge.ToInwOaPath(item);
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
// 查找楼层属性分类的索引
int floorAttributeIndex = GetFloorAttributeIndex(propertyNode);
if (floorAttributeIndex >= 0)
{
// 根据COM API文档RemoveUserDefined使用从1开始的索引
int removeIndex = floorAttributeIndex + 1;
propertyNode.RemoveUserDefined(removeIndex);
LogManager.Info($"[FloorAttributeManager] ✅ 成功删除楼层属性 (索引={removeIndex})");
return true;
}
else
{
LogManager.Info($"[FloorAttributeManager] 模型 {item.DisplayName} 没有楼层属性,无需清除");
return false;
}
});
// 执行安全的缓存刷新
if (result)
{
NavisworksApiHelper.SafeCacheRefresh("FloorAttributeManager");
}
return result;
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] ❌ 清除楼层属性失败:{ex.Message}", ex);
return false;
}
}
#endregion
#region
/// <summary>
/// 直接获取对象的楼层属性(不进行继承查找)
/// </summary>
private string GetFloorLevelDirect(ModelItem item)
{
if (item == null) return null;
try
{
return ExecuteWithUIThread(() =>
{
ComApi.InwOpState10 state = ComApiBridge.State;
ComApi.InwOaPath comPath = ComApiBridge.ToInwOaPath(item);
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
// 遍历所有属性分类
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.ClassUserName == FLOOR_CATEGORY)
{
// 在楼层属性分类中查找Floor_Level属性
foreach (ComApi.InwOaProperty property in attribute.Properties())
{
if (property.name == FLOOR_LEVEL_PROPERTY)
{
return property.value?.ToString();
}
}
}
}
return null;
});
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] 直接获取楼层属性失败:{ex.Message}");
return null;
}
}
/// <summary>
/// 添加楼层属性到属性分类
/// </summary>
private void AddFloorProperty(ComApi.InwOpState10 state, ComApi.InwOaPropertyVec propertyCategory,
string displayName, string value, string internalName)
{
try
{
ComApi.InwOaProperty property =
(ComApi.InwOaProperty)state.ObjectFactory(
ComApi.nwEObjectType.eObjectType_nwOaProperty, null, null);
property.name = internalName;
property.UserName = displayName;
property.value = value;
propertyCategory.Properties().Add(property);
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] 添加楼层属性失败:{displayName} = {value}, 错误: {ex.Message}");
throw;
}
}
/// <summary>
/// 获取楼层属性分类的索引
/// </summary>
private int GetFloorAttributeIndex(ComApi.InwGUIPropertyNode2 propertyNode)
{
try
{
int userDefinedIndex = 0; // 用户定义属性的索引计数器
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.UserDefined)
{
// ⚠️ 关键修复只匹配ClassUserName不匹配ClassName
// ClassName是系统生成的如"LcOaPropOverrideCat"),我们无法控制
if (attribute.ClassUserName == FLOOR_CATEGORY)
{
LogManager.Debug($"[FloorAttributeManager] ✅ 找到楼层属性分类,相对索引为: {userDefinedIndex}");
return userDefinedIndex;
}
userDefinedIndex++;
}
}
LogManager.Debug($"[FloorAttributeManager] 未找到楼层属性分类");
return -1; // 未找到返回-1
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] 获取楼层属性索引失败:{ex.Message}", ex);
return -1;
}
}
/// <summary>
/// 检查属性节点是否已存在楼层属性分类
/// </summary>
/// <param name="propertyNode">属性节点</param>
/// <returns>存在返回true不存在返回false</returns>
private bool HasExistingFloorAttribute(ComApi.InwGUIPropertyNode2 propertyNode)
{
try
{
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.UserDefined &&
attribute.ClassUserName == FLOOR_CATEGORY)
{
LogManager.Info($"[FloorAttributeManager] 找到现有楼层属性分类: ClassUserName='{attribute.ClassUserName}', ClassName='{attribute.ClassName}'");
return true;
}
}
LogManager.Info($"[FloorAttributeManager] 未找到现有楼层属性分类");
return false;
}
catch (Exception ex)
{
LogManager.Error($"[FloorAttributeManager] 检查楼层属性失败:{ex.Message}");
return false; // 发生错误时,默认认为不存在
}
}
/// <summary>
/// 在UI线程中执行操作确保线程安全
/// </summary>
private T ExecuteWithUIThread<T>(Func<T> operation)
{
return NavisworksApiHelper.ExecuteOnUIThread(operation);
}
#endregion
}
}

View File

@ -4,6 +4,8 @@ using System.Linq;
using Autodesk.Navisworks.Api;
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
using NavisApplication = Autodesk.Navisworks.Api.Application;
using NavisworksTransport.Utils;
namespace NavisworksTransport
{
@ -93,8 +95,8 @@ namespace NavisworksTransport
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
// 查找现有属性类别的索引
int existingIndex = GetLogisticsAttributeIndex(propertyNode);
// 🔍 关键修复:动态查找现有属性的索引
int existingIndex = GetLogisticsAttributeRelativeIndex(propertyNode);
// 创建新的属性类别
ComApi.InwOaPropertyVec propertyCategory =
@ -127,16 +129,18 @@ namespace NavisworksTransport
if (existingIndex >= 0)
{
// 如果存在现有属性,使用覆盖模式 (参数1)
LogManager.WriteLog($"[属性添加] 发现现有物流属性,使用覆盖模式更新");
// 🎯 存在属性时:使用正确的索引进行更新
// 注意SetUserDefined的索引是从1开始的
int updateIndex = existingIndex + 1;
LogManager.WriteLog($"[属性添加] 发现现有物流属性在索引 {existingIndex},将使用索引 {updateIndex} 进行更新");
LogPropertyNodeState(propertyNode, "属性添加-覆盖前");
try
{
// 使用参数1表示覆盖现有PropertyCategory
propertyNode.SetUserDefined(1, LogisticsCategories.LOGISTICS,
// 使用正确的索引覆盖现有PropertyCategory
propertyNode.SetUserDefined(updateIndex, LogisticsCategories.LOGISTICS,
LogisticsCategories.CATEGORY_INTERNAL_NAME, propertyCategory);
LogManager.WriteLog($"[属性添加] ✅ 成功覆盖现有属性类别");
LogManager.WriteLog($"[属性添加] ✅ 成功覆盖现有属性类别 (index={updateIndex})");
// 立即刷新并验证结果
propertyNode = RefreshPropertyNode(state, path, "属性添加-覆盖验证");
@ -150,8 +154,8 @@ namespace NavisworksTransport
}
else
{
// 如果不存在,创建新的属性类别,使用创建模式 (参数0)
LogManager.WriteLog("[属性添加] 未发现现有物流属性,使用创建模式");
// 🆕 不存在属性时使用索引0创建新属性
LogManager.WriteLog("[属性添加] 未发现现有物流属性,将创建新的属性类别");
LogPropertyNodeState(propertyNode, "属性添加-创建前");
try
@ -159,7 +163,7 @@ namespace NavisworksTransport
// 使用参数0表示创建新的PropertyCategory
propertyNode.SetUserDefined(0, LogisticsCategories.LOGISTICS,
LogisticsCategories.CATEGORY_INTERNAL_NAME, propertyCategory);
LogManager.WriteLog("[属性添加] ✅ 成功创建新的属性类别");
LogManager.WriteLog("[属性添加] ✅ 成功创建新的属性类别 (index=0)");
// 立即刷新并验证结果
propertyNode = RefreshPropertyNode(state, path, "属性添加-创建验证");
@ -176,15 +180,18 @@ namespace NavisworksTransport
}
catch (Exception ex)
{
LogManager.WriteLog($"[属性添加] 处理单个模型项时发生错误: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"处理单个模型项时发生错误: {ex.Message}");
}
}
}
catch (Exception ex)
{
LogManager.WriteLog($"[属性添加] 添加物流属性时发生错误: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"添加物流属性时发生错误: {ex.Message}");
}
LogManager.WriteLog($"[属性添加] 添加操作完成,成功添加 {successCount} 个模型的属性");
return successCount;
}
@ -374,7 +381,6 @@ namespace NavisworksTransport
{
if (items == null || items.Count == 0)
{
LogManager.WriteLog("[属性删除] 输入参数为空或无模型项");
return 0;
}
@ -385,84 +391,51 @@ namespace NavisworksTransport
{
// 获取COM API状态对象
ComApi.InwOpState10 state = ComApiBridge.State;
LogManager.WriteLog("[属性删除] 已获取COM API状态对象");
// 转换选择集合为COM对象
ComApi.InwOpSelection comSelection = ComApiBridge.ToInwOpSelection(items);
LogManager.WriteLog($"[属性删除] 已转换为COM选择集合路径数: {comSelection.Paths().Count}");
// 遍历每个路径对象
foreach (ComApi.InwOaPath3 path in comSelection.Paths())
{
try
{
LogManager.WriteLog("[属性删除] 开始处理路径对象");
// 获取属性节点
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
LogManager.WriteLog("[属性删除] 已获取属性节点");
// 记录操作前的属性状态
LogPropertyNodeState(propertyNode, "属性删除-操作前");
// 查找现有属性类别的索引
int existingIndex = GetLogisticsAttributeIndex(propertyNode);
// 查找物流属性的相对索引(只查找一次)
int relativeIndex = GetLogisticsAttributeRelativeIndex(propertyNode);
if (existingIndex < 0)
if (relativeIndex < 0)
{
LogManager.WriteLog("[属性删除] 该模型没有物流属性类别,跳过");
continue;
}
// 使用RemoveUserDefined方法完全删除属性类别
LogManager.WriteLog($"[属性删除] 发现现有物流属性使用RemoveUserDefined方法删除");
LogPropertyNodeState(propertyNode, "属性删除-删除前");
// 执行删除操作
// 用户定义属性索引从1开始所以需要+1
int userDefinedIndex = relativeIndex + 1;
propertyNode.RemoveUserDefined(userDefinedIndex);
try
{
// 计算用户定义属性的相对索引从1开始
int relativeIndex = GetLogisticsAttributeRelativeIndex(propertyNode);
if (relativeIndex >= 0)
{
// 用户定义属性索引从1开始所以需要+1
int userDefinedIndex = relativeIndex + 1;
LogManager.WriteLog($"[属性删除] 使用用户定义属性索引: {userDefinedIndex}(相对索引: {relativeIndex}");
// 使用RemoveUserDefined方法完全删除属性类别
propertyNode.RemoveUserDefined(userDefinedIndex);
LogManager.WriteLog($"[属性删除] ✅ 成功使用RemoveUserDefined删除属性类别");
}
else
{
LogManager.WriteLog($"[属性删除] ❌ 无法找到物流属性的相对索引");
throw new InvalidOperationException("无法找到物流属性的相对索引");
}
LogPropertyNodeState(propertyNode, "属性删除-删除后");
}
catch (Exception setEx)
{
LogManager.WriteLog($"[属性删除] ❌ 删除属性失败,错误: {setEx.Message}");
throw;
}
successCount++;
LogManager.WriteLog($"[属性删除] 成功删除一个模型的属性,当前成功数: {successCount}");
LogManager.WriteLog($"[属性删除] ✅ 成功删除物流属性 (索引={userDefinedIndex})");
}
catch (Exception ex)
{
LogManager.WriteLog($"[属性删除] 删除单个模型项属性时发生错误: {ex.Message}");
LogManager.WriteLog($"[属性删除] 异常详细信息: {ex}");
System.Diagnostics.Debug.WriteLine($"删除单个模型项属性时发生错误: {ex.Message}");
LogManager.WriteLog($"[属性删除] ❌ 删除单个模型项属性时发生错误: {ex.Message}");
}
}
}
catch (Exception ex)
{
LogManager.WriteLog($"[属性删除] 删除物流属性时发生错误: {ex.Message}");
LogManager.WriteLog($"[属性删除] 异常详细信息: {ex}");
System.Diagnostics.Debug.WriteLine($"删除物流属性时发生错误: {ex.Message}");
LogManager.WriteLog($"[属性删除] ❌ 删除物流属性时发生错误: {ex.Message}");
}
// 轻量级缓存刷新触发Navisworks内部状态更新
if (successCount > 0)
{
NavisworksApiHelper.SafeCacheRefresh("属性删除");
}
LogManager.WriteLog($"[属性删除] 删除操作完成,成功删除 {successCount} 个模型的属性");
@ -510,13 +483,11 @@ namespace NavisworksTransport
{
LogManager.WriteLog($"[{operation}] 强制刷新属性节点");
// 尝试多种方式获取属性节点以清理缓存
ComApi.InwGUIPropertyNode2 propertyNode1 = (ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
ComApi.InwGUIPropertyNode2 propertyNode2 = (ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, true);
ComApi.InwGUIPropertyNode2 propertyNode3 = (ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
// 修复COM对象资源泄漏只创建一个COM对象
ComApi.InwGUIPropertyNode2 propertyNode = (ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
LogManager.WriteLog($"[{operation}] 属性节点刷新完成");
return propertyNode3;
return propertyNode;
}
catch (Exception ex)
{
@ -643,25 +614,30 @@ namespace NavisworksTransport
{
try
{
int userDefinedIndex = 0;
int userDefinedIndex = 0; // 用户定义属性的索引计数器
LogManager.WriteLog("[相对索引查找] 开始查找物流属性分类的相对索引");
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
string className = attribute.ClassUserName ?? "无名称";
LogManager.WriteLog($"[相对索引查找] 检查属性: UserDefined={attribute.UserDefined}, ClassUserName='{attribute.ClassUserName ?? ""}'");
if (attribute.UserDefined)
{
if (className == LogisticsCategories.LOGISTICS)
// ⚠️ 关键修复只匹配ClassUserName不匹配ClassName
// ClassName是系统生成的如"LcOaPropOverrideCat"),我们无法控制
if (attribute.ClassUserName == LogisticsCategories.LOGISTICS)
{
LogManager.WriteLog($"[相对索引查找] ✅ 找到物流属性类别,相对索引: {userDefinedIndex}");
return userDefinedIndex;
LogManager.WriteLog($"[相对索引查找] ✅ 找到物流属性类,相对索引: {userDefinedIndex}, ClassUserName='{attribute.ClassUserName}'");
return userDefinedIndex; // 返回在用户定义属性中的索引位置
}
// 只有用户定义的属性才计入索引
userDefinedIndex++;
LogManager.WriteLog($"[相对索引查找] 发现其他用户定义属性 '{attribute.ClassUserName}',当前索引计数: {userDefinedIndex}");
}
}
LogManager.WriteLog($"[相对索引查找] ❌ 未找到物流属性类别");
return -1;
LogManager.WriteLog($"[相对索引查找] ❌ 未找到物流属性分类,总共检查了 {userDefinedIndex} 个用户定义属性");
return -1; // 未找到返回-1
}
catch (Exception ex)
{
@ -761,8 +737,8 @@ namespace NavisworksTransport
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
// 查找现有属性类别的索引
int existingIndex = GetLogisticsAttributeIndex(propertyNode);
// 🔍 关键修复:动态查找现有属性的索引
int existingIndex = GetLogisticsAttributeRelativeIndex(propertyNode);
// 创建新的属性类别
ComApi.InwOaPropertyVec propertyCategory =
@ -786,16 +762,18 @@ namespace NavisworksTransport
if (existingIndex >= 0)
{
// 如果存在现有属性,使用覆盖模式 (参数1)
LogManager.WriteLog($"[属性更新] 发现现有物流属性,使用覆盖模式更新");
// 🎯 存在属性时:使用正确的索引进行更新
// 注意SetUserDefined的索引是从1开始的
int updateIndex = existingIndex + 1;
LogManager.WriteLog($"[属性更新] 发现现有物流属性在索引 {existingIndex},将使用索引 {updateIndex} 进行更新");
LogPropertyNodeState(propertyNode, "属性更新-覆盖前");
try
{
// 使用参数1表示覆盖现有PropertyCategory
propertyNode.SetUserDefined(1, LogisticsCategories.LOGISTICS,
// 使用正确的索引覆盖现有PropertyCategory
propertyNode.SetUserDefined(updateIndex, LogisticsCategories.LOGISTICS,
LogisticsCategories.CATEGORY_INTERNAL_NAME, propertyCategory);
LogManager.WriteLog($"[属性更新] ✅ 成功覆盖现有属性类别");
LogManager.WriteLog($"[属性更新] ✅ 成功覆盖现有属性类别 (index={updateIndex})");
// 立即刷新并验证结果
propertyNode = RefreshPropertyNode(state, path, "属性更新-覆盖验证");
@ -809,8 +787,8 @@ namespace NavisworksTransport
}
else
{
// 如果不存在,创建新的属性类别,使用创建模式 (参数0)
LogManager.WriteLog("[属性更新] 未发现现有物流属性,使用创建模式");
// 🆕 不存在属性时使用索引0创建新属性
LogManager.WriteLog("[属性更新] 未发现现有物流属性,将创建新的属性类别");
LogPropertyNodeState(propertyNode, "属性更新-创建前");
try
@ -818,7 +796,7 @@ namespace NavisworksTransport
// 使用参数0表示创建新的PropertyCategory
propertyNode.SetUserDefined(0, LogisticsCategories.LOGISTICS,
LogisticsCategories.CATEGORY_INTERNAL_NAME, propertyCategory);
LogManager.WriteLog("[属性更新] ✅ 成功创建新的属性类别");
LogManager.WriteLog("[属性更新] ✅ 成功创建新的属性类别 (index=0)");
// 立即刷新并验证结果
propertyNode = RefreshPropertyNode(state, path, "属性更新-创建验证");
@ -970,8 +948,8 @@ namespace NavisworksTransport
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
// 查找现有物流属性类别的索引
int existingIndex = GetLogisticsAttributeIndex(propertyNode);
// 🔍 关键修复:使用正确的相对索引查找
int existingIndex = GetLogisticsAttributeRelativeIndex(propertyNode);
if (existingIndex < 0)
{
@ -981,15 +959,18 @@ namespace NavisworksTransport
// 获取现有的物流属性类别
ComApi.InwGUIAttribute2 existingAttribute = null;
int index = 0;
int userDefinedIndex = 0;
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (index == existingIndex)
if (attribute.UserDefined)
{
existingAttribute = attribute;
break;
if (userDefinedIndex == existingIndex)
{
existingAttribute = attribute;
break;
}
userDefinedIndex++;
}
index++;
}
if (existingAttribute == null)
@ -1020,11 +1001,12 @@ namespace NavisworksTransport
}
}
// 使用覆盖模式更新属性
propertyNode.SetUserDefined(1, LogisticsCategories.LOGISTICS,
// 🎯 使用正确的索引进行覆盖更新
int updateIndex = existingIndex + 1;
propertyNode.SetUserDefined(updateIndex, LogisticsCategories.LOGISTICS,
LogisticsCategories.CATEGORY_INTERNAL_NAME, newPropertyCategory);
LogManager.WriteLog($"[设置可通行性] ✅ 成功设置模型 {item.DisplayName} 的可通行性为: {isTraversable}");
LogManager.WriteLog($"[设置可通行性] ✅ 成功设置模型 {item.DisplayName} 的可通行性为: {isTraversable} (index={updateIndex})");
return true;
}
catch (Exception ex)
@ -1058,8 +1040,8 @@ namespace NavisworksTransport
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
// 查找现有物流属性类别的索引
int existingIndex = GetLogisticsAttributeIndex(propertyNode);
// 🔍 关键修复:使用正确的相对索引查找
int existingIndex = GetLogisticsAttributeRelativeIndex(propertyNode);
if (existingIndex < 0)
{
@ -1069,15 +1051,18 @@ namespace NavisworksTransport
// 获取现有的物流属性类别
ComApi.InwGUIAttribute2 existingAttribute = null;
int index = 0;
int userDefinedIndex = 0;
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (index == existingIndex)
if (attribute.UserDefined)
{
existingAttribute = attribute;
break;
if (userDefinedIndex == existingIndex)
{
existingAttribute = attribute;
break;
}
userDefinedIndex++;
}
index++;
}
if (existingAttribute == null)
@ -1127,11 +1112,12 @@ namespace NavisworksTransport
AddProperty(state, newPropertyCategory, propertyName, propertyValue, internalName);
}
// 使用覆盖模式更新属性
propertyNode.SetUserDefined(1, LogisticsCategories.LOGISTICS,
// 🎯 使用正确的索引进行覆盖更新
int updateIndex = existingIndex + 1;
propertyNode.SetUserDefined(updateIndex, LogisticsCategories.LOGISTICS,
LogisticsCategories.CATEGORY_INTERNAL_NAME, newPropertyCategory);
LogManager.WriteLog($"[设置自定义属性] ✅ 成功设置模型 {item.DisplayName} 的属性 {propertyName} = {propertyValue}");
LogManager.WriteLog($"[设置自定义属性] ✅ 成功设置模型 {item.DisplayName} 的属性 {propertyName} = {propertyValue} (index={updateIndex})");
return true;
}
catch (Exception ex)

View File

@ -7,6 +7,7 @@ using System.ComponentModel;
using System.Threading;
using System.Linq;
using NavisApplication = Autodesk.Navisworks.Api.Application;
using NavisworksTransport.Core;
namespace NavisworksTransport
{
@ -23,8 +24,8 @@ namespace NavisworksTransport
/// </summary>
public enum SplitStrategy
{
ByFloor, // 按楼层分
ByAttribute // 按自定义属性分层
ByFloor, // 智能检测楼
ByCustomFloor // 按自定义分层
}
/// <summary>
@ -38,7 +39,7 @@ namespace NavisworksTransport
public SplitStrategy Strategy { get; set; } = SplitStrategy.ByFloor;
/// <summary>
/// 属性名称(当策略为ByAttribute时使用)
/// 属性名称(用于自定义分层时使用)
/// </summary>
public string AttributeName { get; set; } = "Level";
@ -109,11 +110,9 @@ namespace NavisworksTransport
#region
private readonly FloorDetector _floorDetector;
private readonly AttributeGrouper _attributeGrouper;
// 楼层检测结果缓存
private readonly Dictionary<string, List<SplitPreviewResult>> _floorCache;
private readonly Dictionary<string, List<SplitPreviewResult>> _attributeCache;
private readonly object _cacheLock;
#endregion
@ -123,11 +122,9 @@ namespace NavisworksTransport
public SimplifiedModelSplitterManager()
{
_floorDetector = new FloorDetector();
_attributeGrouper = new AttributeGrouper();
// 初始化缓存
_floorCache = new Dictionary<string, List<SplitPreviewResult>>();
_attributeCache = new Dictionary<string, List<SplitPreviewResult>>();
_cacheLock = new object();
// 验证Navisworks运行时环境
@ -210,7 +207,6 @@ namespace NavisworksTransport
lock (_cacheLock)
{
_floorCache.Clear();
_attributeCache.Clear();
LogManager.Info("[SimplifiedModelSplitter] 缓存已清除");
}
}
@ -268,8 +264,8 @@ namespace NavisworksTransport
case SplitStrategy.ByFloor:
previewResults = PreviewSplitByFloor(allItems, config);
break;
case SplitStrategy.ByAttribute:
previewResults = PreviewSplitByAttribute(allItems, config);
case SplitStrategy.ByCustomFloor:
previewResults = PreviewSplitByCustomFloor(allItems, config);
break;
default:
throw new NotSupportedException($"不支持的分层策略: {config.Strategy}");
@ -317,7 +313,8 @@ namespace NavisworksTransport
{
lock (_cacheLock)
{
var cache = strategy == SplitStrategy.ByFloor ? _floorCache : _attributeCache;
// 只支持楼层缓存和自定义楼层缓存
var cache = _floorCache;
return cache.ContainsKey(cacheKey) ? cache[cacheKey] : null;
}
}
@ -329,7 +326,8 @@ namespace NavisworksTransport
{
lock (_cacheLock)
{
var cache = strategy == SplitStrategy.ByFloor ? _floorCache : _attributeCache;
// 只支持楼层缓存和自定义楼层缓存
var cache = _floorCache;
cache[cacheKey] = results;
LogManager.Info($"[SimplifiedModelSplitter] 结果已缓存: {strategy}, 键: {cacheKey}, 数量: {results.Count}");
}
@ -638,84 +636,86 @@ namespace NavisworksTransport
}
/// <summary>
/// 按属性预览分层(带进度报告
/// 按自定义楼层预览分层基于FloorAttributeManager
/// </summary>
private List<SplitPreviewResult> PreviewSplitByAttribute(ModelItemCollection items, SplitConfiguration config)
private List<SplitPreviewResult> PreviewSplitByCustomFloor(ModelItemCollection items, SplitConfiguration config)
{
var results = new List<SplitPreviewResult>();
try
{
// 属性分组阶段 (35-70%)
OnProgressChanged(new ProgressChangedEventArgs(40, $"正在按属性 '{config.AttributeName}' 分组 {items.Count} 个模型元素"));
// 初始化FloorAttributeManager
OnProgressChanged(new ProgressChangedEventArgs(40, $"正在分析 {items.Count} 个模型元素的自定义楼层信息"));
var groups = _attributeGrouper.GroupByAttribute(items, config.AttributeName);
var floorManager = new FloorAttributeManager();
var floorGroups = floorManager.GetFloorGroups();
OnProgressChanged(new ProgressChangedEventArgs(70, $"属性分组完成,发现 {groups?.Count ?? 0} 个分组"));
LogManager.Info($"[SimplifiedModelSplitter] 按属性 '{config.AttributeName}' 分组完成,发现 {groups?.Count ?? 0} 个分组");
LogManager.Info($"[SimplifiedModelSplitter] 自定义楼层检测完成,发现 {floorGroups?.Count ?? 0} 个楼层分组");
if (groups != null && groups.Count > 0)
OnProgressChanged(new ProgressChangedEventArgs(70, $"楼层分组完成,发现 {floorGroups?.Count ?? 0} 个楼层"));
if (floorGroups != null && floorGroups.Count > 0)
{
// 生成预览结果阶段 (70-85%)
OnProgressChanged(new ProgressChangedEventArgs(75, "正在生成属性分组预览结果"));
OnProgressChanged(new ProgressChangedEventArgs(75, "正在生成自定义楼层预览结果"));
int processedGroups = 0;
foreach (var group in groups)
foreach (var floorGroup in floorGroups)
{
var previewResult = new SplitPreviewResult
{
LayerName = SanitizeLayerName(group.GroupName),
ItemCount = group.ItemCount,
EstimatedFileSize = EstimateFileSize(group.ItemCount),
LayerName = SanitizeLayerName(floorGroup.Key),
ItemCount = floorGroup.Value.Count,
EstimatedFileSize = EstimateFileSize(floorGroup.Value.Count),
Status = "就绪",
Items = new ModelItemCollection()
};
// 复制ModelItem集合
if (group.Items != null && group.Items.Count > 0)
// 添加模型项到集合中
if (floorGroup.Value != null && floorGroup.Value.Count > 0)
{
previewResult.Items.AddRange(group.Items);
LogManager.Info($"[SimplifiedModelSplitter] 属性预览结果已缓存模型项: 分组='{previewResult.LayerName}', 原始名称='{group.GroupName}', 模型项数量={previewResult.Items.Count}");
previewResult.Items.AddRange(floorGroup.Value);
LogManager.Info($"[SimplifiedModelSplitter] 自定义楼层预览结果已缓存模型项: 楼层='{previewResult.LayerName}', 模型项数量={previewResult.Items.Count}");
}
else
{
LogManager.Warning($"[SimplifiedModelSplitter] 属性预览结果没有模型项: 分组='{previewResult.LayerName}', 原始名称='{group.GroupName}'");
LogManager.Warning($"[SimplifiedModelSplitter] 自定义楼层预览结果没有模型项: 楼层='{previewResult.LayerName}'");
}
// 添加元数据 - 保存原始名称用于后续匹配
previewResult.Metadata["OriginalGroupName"] = group.GroupName; // 保存原始组名称
previewResult.Metadata["AttributeName"] = config.AttributeName;
previewResult.Metadata["AttributeValue"] = group.AttributeValue;
previewResult.Metadata["GroupName"] = group.GroupName;
previewResult.Metadata["DetectionMethod"] = "Attribute";
// 添加元数据
previewResult.Metadata["OriginalFloorName"] = floorGroup.Key;
previewResult.Metadata["FloorName"] = floorGroup.Key;
previewResult.Metadata["DetectionMethod"] = "CustomFloor";
previewResult.Metadata["MaxDepth"] = config.MaxDepth;
results.Add(previewResult);
// 更新进度
processedGroups++;
int progressPercent = 75 + (processedGroups * 10 / groups.Count);
int progressPercent = 75 + (processedGroups * 10 / floorGroups.Count);
OnProgressChanged(new ProgressChangedEventArgs(progressPercent,
$"已处理 {processedGroups}/{groups.Count} 个属性分组"));
$"已处理 {processedGroups}/{floorGroups.Count} 个楼层分组"));
}
OnProgressChanged(new ProgressChangedEventArgs(85, $"属性预览结果生成完成,共 {results.Count} 个分层"));
OnProgressChanged(new ProgressChangedEventArgs(85, $"自定义楼层预览结果生成完成,共 {results.Count} 个分层"));
}
else
{
OnProgressChanged(new ProgressChangedEventArgs(85, $"未找到属性 '{config.AttributeName}' 的分组信息"));
LogManager.Warning($"[SimplifiedModelSplitter] 未找到属性 '{config.AttributeName}' 的分组信息");
OnProgressChanged(new ProgressChangedEventArgs(85, "未检测到自定义楼层信息"));
LogManager.Warning("[SimplifiedModelSplitter] 未检测到任何自定义楼层信息");
}
}
catch (Exception ex)
{
LogManager.Error($"[SimplifiedModelSplitter] 按属性预览失败: {ex.Message}");
OnProgressChanged(new ProgressChangedEventArgs(35, $"属性分析失败: {ex.Message}"));
LogManager.Error($"[SimplifiedModelSplitter] 按自定义楼层预览失败: {ex.Message}", ex);
OnProgressChanged(new ProgressChangedEventArgs(35, $"自定义楼层分析失败: {ex.Message}"));
throw;
}
return results;
}
/// <summary>
/// 处理单个分层(异步版本)
/// </summary>
@ -1578,37 +1578,6 @@ namespace NavisworksTransport
return floorItems;
}
/// <summary>
/// 获取指定属性组的模型项
/// </summary>
private ModelItemCollection GetAttributeGroupItems(ModelItemCollection allItems, string attributeName, string groupValue)
{
var groupItems = new ModelItemCollection();
try
{
foreach (ModelItem item in allItems)
{
try
{
if (HasAttributeValue(item, attributeName, groupValue))
{
groupItems.Add(item);
}
}
catch (Exception ex)
{
LogManager.Warning($"[SimplifiedModelSplitter] 检查模型项属性值时出错: {ex.Message}");
}
}
}
catch (Exception ex)
{
LogManager.Error($"[SimplifiedModelSplitter] 获取属性组项目失败: {ex.Message}");
}
return groupItems;
}
/// <summary>
/// 检查模型项是否具有指定的楼层属性
@ -1944,9 +1913,9 @@ namespace NavisworksTransport
switch (strategy)
{
case SplitStrategy.ByFloor:
return "楼层分层分析";
case SplitStrategy.ByAttribute:
return "属性分层分析";
return "智能楼层检测分析";
case SplitStrategy.ByCustomFloor:
return "自定义分层分析";
default:
return "分层分析";
}

View File

@ -43,6 +43,7 @@ namespace NavisworksTransport.UI.WPF.Commands
{
public bool IsSuccess { get; set; }
public int Count { get; set; }
public List<ModelItem> SelectedItems { get; set; } = new List<ModelItem>();
public string ErrorMessage { get; set; }
}
@ -313,10 +314,13 @@ namespace NavisworksTransport.UI.WPF.Commands
LogManager.Info($"[SelectNodesCommand] 获取到 {count} 个选中节点");
var selectedItems = selection?.SelectedItems?.Cast<ModelItem>().ToList() ?? new List<ModelItem>();
return new SelectNodesResult
{
IsSuccess = true,
Count = count
Count = count,
SelectedItems = selectedItems
};
}
catch (Exception ex)
@ -326,6 +330,7 @@ namespace NavisworksTransport.UI.WPF.Commands
{
IsSuccess = false,
Count = 0,
SelectedItems = new List<ModelItem>(),
ErrorMessage = $"获取选中节点失败: {ex.Message}"
};
}

View File

@ -43,6 +43,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private readonly AttributeGrouper _attributeGrouper;
private readonly UIStateManager _uiStateManager;
private CancellationTokenSource _cancellationTokenSource;
// 选择事件订阅管理器
private SelectionEventSubscription _selectionEventSubscription;
#endregion
@ -62,7 +65,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private string _currentOperationText = "";
private string _progressDetailText = "";
private double _progressPercentage;
private string _selectedSplitStrategy = "按楼层分层";
private string _selectedSplitStrategy = "智能检测楼层";
private bool _includeChildNodes = true;
private bool _preserveMaterials = true;
@ -75,6 +78,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private bool _embedXrefs = false;
private bool _preventObjectPropertyExport = false;
// 楼层属性相关字段
private string _selectedModelsText = "请在主界面中选择需要设置的模型";
private string _selectedFloorLevel = "F1";
private string _selectedZone = "";
private string _selectedSubSystem = "";
private bool _showFloorAttributeInfo = false;
private string _currentFloorAttributeInfo = "";
private bool _showCancelButton = false;
#endregion
#region - 使线SetProperty方法
@ -224,8 +236,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ThreadSafeObservableCollection<string> SplitStrategies { get; } =
new ThreadSafeObservableCollection<string>
{
"按楼层分层",
"按自定义属性分层"
"智能检测楼层",
"按自定义分层"
};
/// <summary>
@ -395,6 +407,131 @@ namespace NavisworksTransport.UI.WPF.ViewModels
set => SetPropertyThreadSafe(ref _progressPercentage, value);
}
/// <summary>
/// 选中模型文本
/// </summary>
public string SelectedModelsText
{
get => _selectedModelsText;
set => SetPropertyThreadSafe(ref _selectedModelsText, value);
}
/// <summary>
/// 选中的楼层标识
/// </summary>
public string SelectedFloorLevel
{
get => _selectedFloorLevel;
set => SetPropertyThreadSafe(ref _selectedFloorLevel, value);
}
/// <summary>
/// 选中的区域标识
/// </summary>
public string SelectedZone
{
get => _selectedZone;
set => SetPropertyThreadSafe(ref _selectedZone, value);
}
/// <summary>
/// 选中的子系统标识
/// </summary>
public string SelectedSubSystem
{
get => _selectedSubSystem;
set => SetPropertyThreadSafe(ref _selectedSubSystem, value);
}
/// <summary>
/// 是否显示楼层属性信息
/// </summary>
public bool ShowFloorAttributeInfo
{
get => _showFloorAttributeInfo;
set => SetPropertyThreadSafe(ref _showFloorAttributeInfo, value);
}
/// <summary>
/// 当前楼层属性信息
/// </summary>
public string CurrentFloorAttributeInfo
{
get => _currentFloorAttributeInfo;
set => SetPropertyThreadSafe(ref _currentFloorAttributeInfo, value);
}
/// <summary>
/// 是否有选中的模型(用于楼层属性设置)
/// </summary>
public bool HasSelectedModels
{
get
{
try
{
var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
return document?.CurrentSelection?.SelectedItems?.Count > 0;
}
catch
{
return false;
}
}
}
/// <summary>
/// 是否可以设置楼层属性
/// </summary>
public bool CanSetFloorAttribute
{
get => HasSelectedModels && !string.IsNullOrEmpty(SelectedFloorLevel) && IsNotProcessing;
}
/// <summary>
/// 是否可以清除楼层属性
/// </summary>
public bool CanClearFloorAttribute
{
get
{
if (!HasSelectedModels || IsProcessing)
return false;
try
{
var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (document?.CurrentSelection?.SelectedItems == null || document.CurrentSelection.SelectedItems.Count == 0)
return false;
var floorManager = new FloorAttributeManager();
// 检查是否有任何选中模型拥有分层属性
foreach (ModelItem item in document.CurrentSelection.SelectedItems)
{
string floorLevel = floorManager.GetFloorLevel(item, false); // 不搜索父节点
if (!string.IsNullOrEmpty(floorLevel))
{
return true; // 只要找到一个有属性的就可以清除
}
}
return false; // 没有找到任何有属性的模型
}
catch
{
return false; // 出错时默认禁用
}
}
}
/// <summary>
/// 是否显示取消按钮
/// </summary>
public bool ShowCancelButton
{
get => _showCancelButton;
set => SetPropertyThreadSafe(ref _showCancelButton, value);
}
#endregion
#region - 使Command Pattern
@ -410,6 +547,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand CancelOperationCommand { get; private set; }
public ICommand DiagnosticCommand { get; private set; }
public ICommand TestExportToNwdCommand { get; private set; }
// 楼层属性相关命令
public ICommand RefreshSelectionCommand { get; private set; }
public ICommand SetFloorAttributeCommand { get; private set; }
public ICommand ClearFloorAttributeCommand { get; private set; }
public ICommand ViewFloorAttributeCommand { get; private set; }
#endregion
@ -439,6 +582,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 订阅事件
_modelSplitterManager.ProgressChanged += OnProgressChanged;
_modelSplitterManager.StatusChanged += OnStatusChanged;
// 订阅Navisworks选择变化事件 - 使用新的选择管理服务
SubscribeToSelectionEvents();
// 异步初始化
InitializeAsync();
@ -508,6 +654,23 @@ namespace NavisworksTransport.UI.WPF.ViewModels
TestExportToNwdCommand = new RelayCommand(
async () => await TestExportToNwdAsync(),
() => IsNotProcessing);
// 楼层属性相关命令初始化
RefreshSelectionCommand = new RelayCommand(
async () => await RefreshSelectionAsync(),
() => IsNotProcessing);
SetFloorAttributeCommand = new RelayCommand(
async () => await SetFloorAttributeAsync(),
() => CanSetFloorAttribute);
ClearFloorAttributeCommand = new RelayCommand(
async () => await ClearFloorAttributeAsync(),
() => CanClearFloorAttribute);
ViewFloorAttributeCommand = new RelayCommand(
async () => await ViewFloorAttributeAsync(),
() => HasSelectedItems);
LogManager.Info("分层管理命令初始化完成 - 使用统一Command Pattern框架");
}, "初始化命令");
@ -529,6 +692,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = true;
ShowCancelButton = false; // 分析楼层操作不支持取消
CurrentOperationText = "正在分析楼层...";
ProgressDetailText = "检测模型中的楼层属性";
ProgressPercentage = 0;
@ -573,6 +737,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = false;
ShowCancelButton = false;
CurrentOperationText = "";
ProgressPercentage = 0;
});
@ -662,9 +827,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
if (result.IsSuccess)
{
SelectedNodesText = result.Count > 0
? $"已选择 {result.Count} 个节点"
: "未选择节点";
SelectedNodesText = NavisworksSelectionHelper.FormatSelectionText(result.Count, result.SelectedItems, "个节点");
ProgressDetailText = result.Count > 0
? $"获取到 {result.Count} 个选中节点"
: "当前没有选中的节点";
@ -764,6 +927,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = true;
ShowCancelButton = false; // 预览操作不支持取消
CurrentOperationText = "正在生成分层预览...";
ProgressPercentage = 0;
ProgressDetailText = "准备分层配置";
@ -877,6 +1041,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = false;
ShowCancelButton = false;
CurrentOperationText = "";
ProgressPercentage = 0;
});
@ -910,6 +1075,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = true;
ShowCancelButton = true; // 分层保存支持取消
CurrentOperationText = "正在执行分层保存...";
ProgressDetailText = $"准备保存到: {selectedDirectory}";
});
@ -972,6 +1138,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = false;
ShowCancelButton = false;
_cancellationTokenSource = null;
});
}
@ -1220,13 +1387,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
document.CurrentSelection.Add(selectedItem);
}
LogManager.Info($"[LayerManagementViewModel] 已重新选择 {originalSelection.Count} 个目标节点");
// 创建导出选项 - 使用用户配置的参数
var exportOptions = new Autodesk.Navisworks.Api.NwdExportOptions();
exportOptions.ExcludeHiddenItems = true; // 只导出可见项目(选中节点及其子项)
exportOptions.EmbedXrefs = EmbedXrefs;
exportOptions.PreventObjectPropertyExport = PreventObjectPropertyExport;
var exportOptions = new Autodesk.Navisworks.Api.NwdExportOptions
{
ExcludeHiddenItems = true, // 只导出可见项目(选中节点及其子项)
EmbedXrefs = EmbedXrefs,
PreventObjectPropertyExport = PreventObjectPropertyExport
};
LogManager.Info($"[LayerManagementViewModel] SaveSelectedItems导出选项: EmbedXrefs={exportOptions.EmbedXrefs}, PreventObjectPropertyExport={exportOptions.PreventObjectPropertyExport}");
LogManager.Info("[LayerManagementViewModel] 开始调用ExportToNwd API");
@ -1787,32 +1956,31 @@ namespace NavisworksTransport.UI.WPF.ViewModels
#endregion
// 选择状态格式化方法已移至NavisworksSelectionHelper中
#region
/// <summary>
/// 更新当前选择状态 - 实现正确的业务逻辑与UI分离模式
/// 更新当前选择状态 - 使用新的选择管理服务
/// </summary>
public async Task UpdateCurrentSelectionAsync()
{
try
{
// 纯业务逻辑执行后台线程不使用UIStateManager
var command = new GetCurrentSelectionCommand();
var result = await command.ExecuteAsync();
// 使用新的选择管理服务获取选择状态(后台线程)
var selectionResult = await NavisworksSelectionHelper.GetCurrentSelectionStateAsync();
// 结果UI更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
if (result.IsSuccess)
if (selectionResult.Success)
{
CurrentSelectionText = result.Count > 0
? $"已选择 {result.Count} 个项目"
: "未选择项目";
CurrentSelectionText = NavisworksSelectionHelper.FormatSelectionText(selectionResult, "个项目");
}
else
{
CurrentSelectionText = "获取选择状态失败";
LogManager.Warning($"[LayerManagementViewModel] 更新选择状态失败: {result.ErrorMessage}");
CurrentSelectionText = selectionResult.ErrorMessage ?? "获取选择状态失败";
LogManager.Warning($"[LayerManagementViewModel] 更新选择状态失败: {selectionResult.ErrorMessage}");
}
});
}
@ -1848,6 +2016,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 更新选择状态
await UpdateCurrentSelectionAsync();
// 刷新楼层属性模型选择状态
await RefreshSelectionAsync();
}
catch (Exception ex)
{
@ -1883,10 +2054,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
switch (strategy)
{
case "按楼层分层":
case "智能检测楼层":
return SimplifiedModelSplitterManager.SplitStrategy.ByFloor;
case "按自定义属性分层":
return SimplifiedModelSplitterManager.SplitStrategy.ByAttribute;
case "按自定义分层":
return SimplifiedModelSplitterManager.SplitStrategy.ByCustomFloor;
default:
return SimplifiedModelSplitterManager.SplitStrategy.ByFloor;
}
@ -2048,6 +2219,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
_modelSplitterManager.ProgressChanged -= OnProgressChanged;
_modelSplitterManager.StatusChanged -= OnStatusChanged;
}
// 取消Navisworks选择变化事件订阅
UnsubscribeFromSelectionEvents();
LogManager.Info("[LayerManagementViewModel] 资源清理完成");
}
@ -2088,5 +2262,411 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
#endregion
#region
/// <summary>
/// 订阅Navisworks选择变化事件 - 使用新的选择管理服务
/// </summary>
private void SubscribeToSelectionEvents()
{
try
{
// 使用新的选择管理服务订阅选择变化事件
_selectionEventSubscription = NavisworksSelectionHelper.SubscribeToSelectionChanges(
OnSelectionChangedAsync, _uiStateManager);
LogManager.Info("[LayerManagementViewModel] 已通过NavisworksSelectionHelper订阅选择变化事件");
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 订阅选择事件失败: {ex.Message}", ex);
}
}
/// <summary>
/// 取消订阅Navisworks选择变化事件 - 使用新的选择管理服务
/// </summary>
private void UnsubscribeFromSelectionEvents()
{
try
{
// 通过Dispose方法取消订阅
_selectionEventSubscription?.Dispose();
_selectionEventSubscription = null;
LogManager.Info("[LayerManagementViewModel] 已通过NavisworksSelectionHelper取消订阅选择变化事件");
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 取消选择事件订阅失败: {ex.Message}", ex);
}
}
/// <summary>
/// 选择变化事件处理器 - 使用新的选择管理服务
/// </summary>
private async Task OnSelectionChangedAsync(SelectionStateResult selectionResult)
{
try
{
// 更新楼层属性相关的选择状态(使用新的选择结果)
await UpdateFloorAttributeSelectionStateAsync(selectionResult);
LogManager.Info($"[LayerManagementViewModel] 选择状态已更新: {selectionResult.Count}个项目");
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 处理选择变化事件异常: {ex.Message}", ex);
}
}
/// <summary>
/// 更新楼层属性相关的选择状态 - 使用新的选择管理服务
/// </summary>
private async Task UpdateFloorAttributeSelectionStateAsync(SelectionStateResult selectionResult = null)
{
try
{
// 如果没有提供选择结果,则获取当前选择状态
if (selectionResult == null)
{
selectionResult = await NavisworksSelectionHelper.GetCurrentSelectionStateAsync();
}
// UI更新 - 使用新的选择管理服务格式化选择文本
if (selectionResult.Success)
{
SelectedModelsText = NavisworksSelectionHelper.FormatSelectionText(selectionResult, "个模型");
}
else
{
SelectedModelsText = selectionResult.ErrorMessage ?? "检查选择状态异常";
}
// 刷新命令状态
OnPropertyChanged(nameof(HasSelectedItems));
OnPropertyChanged(nameof(HasSelectedModels));
OnPropertyChanged(nameof(CanSetFloorAttribute));
OnPropertyChanged(nameof(CanClearFloorAttribute));
LogManager.Info($"[LayerManagementViewModel] 楼层属性选择状态已更新: 成功={selectionResult.Success}, 数量={selectionResult.Count}");
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 更新楼层属性选择状态异常: {ex.Message}", ex);
SelectedModelsText = "检查选择状态异常";
}
}
#endregion
#region
/// <summary>
/// 刷新选择状态 - 使用新的选择管理服务
/// </summary>
private async Task RefreshSelectionAsync()
{
// 1. 初始UI状态更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = true;
CurrentOperationText = "正在检查模型选择...";
});
try
{
// 2. 使用新的选择管理服务获取选择状态(后台线程)
var selectionResult = await NavisworksSelectionHelper.GetCurrentSelectionStateAsync();
// 3. 结果UI更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
if (selectionResult.Success)
{
SelectedModelsText = NavisworksSelectionHelper.FormatSelectionText(selectionResult, "个模型");
CurrentOperationText = "检查完成";
}
else
{
SelectedModelsText = selectionResult.ErrorMessage ?? "检查选择状态失败";
CurrentOperationText = "检查失败";
}
// 刷新命令状态
OnPropertyChanged(nameof(HasSelectedItems));
OnPropertyChanged(nameof(HasSelectedModels));
OnPropertyChanged(nameof(CanSetFloorAttribute));
OnPropertyChanged(nameof(CanClearFloorAttribute));
});
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 刷新选择异常: {ex.Message}", ex);
// 异常UI更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
SelectedModelsText = "检查选择状态异常";
CurrentOperationText = $"检查失败: {ex.Message}";
});
}
finally
{
// 4. 清理UI状态
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = false;
});
}
}
/// <summary>
/// 设置楼层属性
/// </summary>
private async Task SetFloorAttributeAsync()
{
await SafeExecuteAsync(async () =>
{
IsProcessing = true;
ShowCancelButton = false; // 设置楼层属性不支持取消
CurrentOperationText = "正在设置楼层属性...";
var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (document?.CurrentSelection?.SelectedItems == null || document.CurrentSelection.SelectedItems.Count == 0)
{
CurrentOperationText = "未选择任何模型项";
return;
}
var floorManager = new FloorAttributeManager();
var selectedItems = document.CurrentSelection.SelectedItems.ToList();
var successCount = 0;
foreach (var item in selectedItems)
{
try
{
bool result = floorManager.SetFloorAttribute(
item,
SelectedFloorLevel,
string.IsNullOrWhiteSpace(SelectedZone) ? null : SelectedZone,
string.IsNullOrWhiteSpace(SelectedSubSystem) ? null : SelectedSubSystem);
if (result)
{
successCount++;
}
}
catch (Exception ex)
{
LogManager.Error($"设置模型 {item.DisplayName} 的楼层属性失败:{ex.Message}");
}
}
CurrentOperationText = $"楼层属性设置完成,成功设置 {successCount}/{selectedItems.Count} 个模型项";
LogManager.Info($"[LayerManagementViewModel] 楼层属性设置完成,成功 {successCount}/{selectedItems.Count} 个");
// 刷新选中模型信息
await RefreshSelectionAsync();
}, "设置楼层属性");
}
/// <summary>
/// 清除楼层属性 - 实现正确的业务逻辑与UI分离模式参考ModelSettingsViewModel
/// </summary>
private async Task ClearFloorAttributeAsync()
{
// 1. 初始UI状态更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = true;
ShowCancelButton = false; // 清除楼层属性不支持取消
CurrentOperationText = "正在清除楼层属性...";
ProgressDetailText = "正在清除选中模型的楼层属性";
});
try
{
// 2. 纯业务逻辑执行后台线程不使用UIStateManager
var result = await Task.Run<dynamic>(() =>
{
try
{
var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (document?.CurrentSelection?.SelectedItems == null || document.CurrentSelection.SelectedItems.Count == 0)
{
return new { Success = false, Count = 0, Message = "请先选择模型元素" };
}
var floorManager = new FloorAttributeManager();
var selectedItems = document.CurrentSelection.SelectedItems.ToList();
var successCount = 0;
foreach (var item in selectedItems)
{
try
{
bool itemResult = floorManager.ClearFloorAttribute(item);
if (itemResult)
{
successCount++;
}
}
catch (Exception itemEx)
{
LogManager.Warning($"清除模型项 {item.DisplayName} 的楼层属性失败:{itemEx.Message}");
}
}
return new {
Success = successCount > 0,
Count = successCount,
TotalCount = selectedItems.Count,
Message = successCount > 0 ?
$"已清除 {successCount} 个模型项的楼层属性" :
"没有找到可清除的楼层属性"
};
}
catch (Exception ex)
{
return new { Success = false, Count = 0, TotalCount = 0, Message = $"清除属性失败: {ex.Message}" };
}
});
// 3. 结果UI更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
CurrentOperationText = result.Message;
ProgressDetailText = result.Success ?
$"楼层属性清除完成,成功清除 {result.Count}/{result.TotalCount} 个模型项" :
"楼层属性清除失败";
if (result.Success)
{
// 清除成功,刷新选择状态
LogManager.Info($"[LayerManagementViewModel] 清除楼层属性成功,{result.Count}/{result.TotalCount} 个模型项");
}
else
{
LogManager.Warning($"[LayerManagementViewModel] 清除楼层属性失败:{result.Message}");
}
});
// 4. 刷新选择状态
if (result.Success)
{
await RefreshSelectionAsync();
}
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 清除楼层属性异常: {ex.Message}", ex);
// 异常UI更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
CurrentOperationText = "清除楼层属性异常";
ProgressDetailText = $"清除失败: {ex.Message}";
});
}
finally
{
// 5. 清理UI状态
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = false;
ShowCancelButton = false;
});
}
}
/// <summary>
/// 查看楼専属性 - 不显示进度条的即时操作
/// </summary>
private async Task ViewFloorAttributeAsync()
{
try
{
var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (document?.CurrentSelection?.SelectedItems == null || document.CurrentSelection.SelectedItems.Count == 0)
{
// 使用UI线程更新状态
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
ShowFloorAttributeInfo = true;
CurrentFloorAttributeInfo = "未选择任何模型项,请先选择要查看的模型";
});
return;
}
// 在后台线程执行业务逻辑
var result = await Task.Run<dynamic>(() =>
{
try
{
var floorManager = new FloorAttributeManager();
var selectedItems = document.CurrentSelection.SelectedItems.ToList();
var floorAttributeInfo = new List<string>();
foreach (var item in selectedItems.Take(10)) // 限制显示前10个避免信息过多
{
try
{
string floorLevel = floorManager.GetFloorLevel(item, true);
string itemName = item.DisplayName ?? "未命名";
if (!string.IsNullOrEmpty(floorLevel))
{
floorAttributeInfo.Add($"✅ {itemName}: {floorLevel}");
}
else
{
floorAttributeInfo.Add($"❌ {itemName}: 未设置楼层属性");
}
}
catch (Exception ex)
{
floorAttributeInfo.Add($"⚠️ {item.DisplayName}: 查询失败 - {ex.Message}");
}
}
if (selectedItems.Count > 10)
{
floorAttributeInfo.Add($"... 还有 {selectedItems.Count - 10} 个模型项未显示");
}
return new { Success = true, Info = string.Join("\n", floorAttributeInfo), Count = selectedItems.Count };
}
catch (Exception ex)
{
return new { Success = false, Info = $"查询失败: {ex.Message}", Count = 0 };
}
});
// 在UI线程更新结果
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
CurrentFloorAttributeInfo = result.Info;
ShowFloorAttributeInfo = true;
});
LogManager.Info($"[LayerManagementViewModel] 楼层属性查看完成,共 {result.Count} 个模型项");
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 查看楼层属性异常: {ex.Message}", ex);
// 在UI线程显示错误信息
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
CurrentFloorAttributeInfo = $"查看操作异常: {ex.Message}";
ShowFloorAttributeInfo = true;
});
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
#region
private readonly UIStateManager _uiStateManager;
// 选择事件订阅管理器
private SelectionEventSubscription _selectionEventSubscription;
#endregion
@ -48,6 +51,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private double _speedLimit = 0.8;
private bool _isLogisticsOnlyMode = false;
private LogisticsModel _selectedLogisticsModel;
#endregion
@ -171,6 +175,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels
!string.IsNullOrEmpty(SelectedCategory) &&
!IsProcessing &&
HasSelectedModels;
/// <summary>
/// 是否可以清除物流属性
/// </summary>
public bool CanClearLogisticsAttribute =>
!IsProcessing &&
HasSelectedModels &&
HasSelectedModelsWithLogisticsAttributes;
/// <summary>
/// 是否有选中的模型
@ -190,6 +201,36 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
}
/// <summary>
/// 选中的模型是否包含物流属性
/// </summary>
public bool HasSelectedModelsWithLogisticsAttributes
{
get
{
try
{
var document = NavisApplication.ActiveDocument;
var selectedItems = document?.CurrentSelection?.SelectedItems;
if (selectedItems == null || selectedItems.Count == 0)
return false;
// 检查是否至少有一个选中的模型具有物流属性
foreach (var item in selectedItems)
{
if (CategoryAttributeManager.HasLogisticsAttributes(item))
{
return true;
}
}
return false;
}
catch
{
return false;
}
}
}
/// <summary>
/// 物流模型集合 - 使用线程安全集合
@ -222,12 +263,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
#endregion
#region - 使Command Pattern
public ICommand RefreshSelectionCommand { get; private set; }
public ICommand SetLogisticsAttributeCommand { get; private set; }
public ICommand ClearLogisticsAttributeCommand { get; private set; }
public ICommand RefreshLogisticsModelsCommand { get; private set; }
public ICommand ResetToDefaultsCommand { get; private set; }
@ -252,6 +295,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 初始化命令
InitializeCommands();
// 订阅Navisworks选择变化事件 - 使用新的选择管理服务
SubscribeToSelectionEvents();
// 异步初始化
InitializeAsync();
@ -285,6 +331,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
async () => await SetLogisticsAttributeAsync(),
() => CanSetLogisticsAttribute);
ClearLogisticsAttributeCommand = new RelayCommand(
async () => await ClearLogisticsAttributeAsync(),
() => CanClearLogisticsAttribute);
RefreshLogisticsModelsCommand = new RelayCommand(
async () => await RefreshLogisticsModelsAsync(),
() => IsNotProcessing);
@ -302,7 +352,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
#region - 使UIStateManager
/// <summary>
/// 刷新选择状态 - 实现正确的业务逻辑与UI分离模式
/// 刷新选择状态 - 使用新的选择管理服务
/// </summary>
private async Task RefreshSelectionAsync()
{
@ -315,37 +365,28 @@ namespace NavisworksTransport.UI.WPF.ViewModels
try
{
// 2. 纯业务逻辑执行后台线程不使用UIStateManager
var result = await Task.Run(() =>
{
try
{
var document = NavisApplication.ActiveDocument;
if (document?.CurrentSelection?.SelectedItems?.Count > 0)
{
var selectedCount = document.CurrentSelection.SelectedItems.Count;
return new { Success = true, Count = selectedCount, Message = $"已选择 {selectedCount} 个模型" };
}
else
{
return new { Success = true, Count = 0, Message = "请在主界面中选择需要设置的模型" };
}
}
catch (Exception ex)
{
return new { Success = false, Count = 0, Message = $"检查选择状态失败: {ex.Message}" };
}
});
// 2. 使用新的选择管理服务获取选择状态(后台线程)
var selectionResult = await NavisworksSelectionHelper.GetCurrentSelectionStateAsync();
// 3. 结果UI更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
SelectedModelsText = result.Message;
StatusText = result.Success ? "检查完成" : "检查失败";
if (selectionResult.Success)
{
SelectedModelsText = NavisworksSelectionHelper.FormatSelectionText(selectionResult, "个模型");
StatusText = "检查完成";
}
else
{
SelectedModelsText = selectionResult.ErrorMessage ?? "检查选择状态失败";
StatusText = "检查失败";
}
// 刷新命令状态
// 🔧 修复:刷新所有与选择相关的命令状态(包括清除属性按钮)
OnPropertyChanged(nameof(HasSelectedModels));
OnPropertyChanged(nameof(HasSelectedModelsWithLogisticsAttributes));
OnPropertyChanged(nameof(CanSetLogisticsAttribute));
OnPropertyChanged(nameof(CanClearLogisticsAttribute));
});
}
catch (Exception ex)
@ -476,6 +517,98 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
/// <summary>
/// 清除物流属性 - 实现正确的业务逻辑与UI分离模式
/// </summary>
private async Task ClearLogisticsAttributeAsync()
{
// 1. 初始UI状态更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = true;
StatusText = "正在清除物流属性...";
});
try
{
// 2. 纯业务逻辑执行后台线程不使用UIStateManager
var result = await Task.Run(() =>
{
try
{
var selectedItems = NavisApplication.ActiveDocument?.CurrentSelection?.SelectedItems;
if (selectedItems == null || selectedItems.Count == 0)
{
return new { Success = false, Count = 0, Message = "请先选择模型元素" };
}
// 使用CategoryAttributeManager的RemoveLogisticsAttributes方法
int successCount = CategoryAttributeManager.RemoveLogisticsAttributes(selectedItems);
return new {
Success = successCount > 0,
Count = successCount,
Message = successCount > 0 ?
$"已清除 {successCount} 个元素的物流属性" :
"没有找到可清除的物流属性"
};
}
catch (Exception ex)
{
return new { Success = false, Count = 0, Message = $"清除属性失败: {ex.Message}" };
}
});
// 3. 结果UI更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
StatusText = result.Message;
if (result.Success)
{
LogManager.Info($"清除物流属性成功: {result.Message}");
}
else
{
LogManager.Warning($"清除物流属性失败: {result.Message}");
}
});
// 如果清除成功,异步刷新物流模型列表
if (result.Success)
{
await RefreshLogisticsModelsAsync();
// 如果当前处于仅显示物流模式,重新应用可见性设置
if (IsLogisticsOnlyMode)
{
await Task.Run(() =>
{
ApplyVisibilityMode();
});
}
}
}
catch (Exception ex)
{
LogManager.Error($"[ModelSettingsViewModel] 清除物流属性异常: {ex.Message}", ex);
// 异常UI更新
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
StatusText = $"清除属性出错: {ex.Message}";
});
}
finally
{
// 4. 清理UI状态
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
IsProcessing = false;
});
}
}
/// <summary>
/// 刷新物流模型列表 - 实现正确的业务逻辑与UI分离模式
/// </summary>
@ -586,6 +719,106 @@ namespace NavisworksTransport.UI.WPF.ViewModels
#endregion
// 选择状态格式化方法已移至NavisworksSelectionHelper中
#region
/// <summary>
/// 订阅Navisworks选择变化事件 - 使用新的选择管理服务
/// </summary>
private void SubscribeToSelectionEvents()
{
try
{
// 使用新的选择管理服务订阅选择变化事件
_selectionEventSubscription = NavisworksSelectionHelper.SubscribeToSelectionChanges(
OnSelectionChangedAsync, _uiStateManager);
LogManager.Info("[ModelSettingsViewModel] 已通过NavisworksSelectionHelper订阅选择变化事件");
}
catch (Exception ex)
{
LogManager.Error($"[ModelSettingsViewModel] 订阅选择事件失败: {ex.Message}", ex);
}
}
/// <summary>
/// 取消订阅Navisworks选择变化事件 - 使用新的选择管理服务
/// </summary>
private void UnsubscribeFromSelectionEvents()
{
try
{
// 通过Dispose方法取消订阅
_selectionEventSubscription?.Dispose();
_selectionEventSubscription = null;
LogManager.Info("[ModelSettingsViewModel] 已通过NavisworksSelectionHelper取消订阅选择变化事件");
}
catch (Exception ex)
{
LogManager.Error($"[ModelSettingsViewModel] 取消选择事件订阅失败: {ex.Message}", ex);
}
}
/// <summary>
/// 选择变化事件处理器 - 使用新的选择管理服务
/// </summary>
private async Task OnSelectionChangedAsync(SelectionStateResult selectionResult)
{
try
{
// 更新物流属性相关的选择状态(使用新的选择结果)
await UpdateModelSelectionStateAsync(selectionResult);
LogManager.Info($"[ModelSettingsViewModel] 选择状态已更新: {selectionResult.Count}个项目");
}
catch (Exception ex)
{
LogManager.Error($"[ModelSettingsViewModel] 处理选择变化事件异常: {ex.Message}", ex);
}
}
/// <summary>
/// 更新模型选择状态 - 使用新的选择管理服务
/// </summary>
private async Task UpdateModelSelectionStateAsync(SelectionStateResult selectionResult = null)
{
try
{
// 如果没有提供选择结果,则获取当前选择状态
if (selectionResult == null)
{
selectionResult = await NavisworksSelectionHelper.GetCurrentSelectionStateAsync();
}
// UI更新 - 使用新的选择管理服务格式化选择文本
if (selectionResult.Success)
{
SelectedModelsText = NavisworksSelectionHelper.FormatSelectionText(selectionResult, "个模型");
}
else
{
SelectedModelsText = selectionResult.ErrorMessage ?? "检查选择状态异常";
}
// 🔧 修复:刷新所有与选择相关的命令状态(包括清除属性按钮)
OnPropertyChanged(nameof(HasSelectedModels));
OnPropertyChanged(nameof(HasSelectedModelsWithLogisticsAttributes));
OnPropertyChanged(nameof(CanSetLogisticsAttribute));
OnPropertyChanged(nameof(CanClearLogisticsAttribute));
LogManager.Info($"[ModelSettingsViewModel] 模型选择状态已更新: 成功={selectionResult.Success}, 数量={selectionResult.Count}");
}
catch (Exception ex)
{
LogManager.Error($"[ModelSettingsViewModel] 更新模型选择状态异常: {ex.Message}", ex);
SelectedModelsText = "检查选择状态异常";
}
}
#endregion
#region
/// <summary>
@ -620,10 +853,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 刷新物流模型列表
await RefreshLogisticsModelsAsync();
// 🔧 确保初始化完成后所有Command状态正确
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
RefreshAllCommands();
StatusText = "分层属性设置已就绪";
});
}
catch (Exception ex)
{
LogManager.Error($"[ModelSettingsViewModel] 初始化失败: {ex.Message}");
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
StatusText = "初始化失败,请检查日志";
});
}
}
@ -785,18 +1029,59 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
try
{
// 防御性检查确保item和其属性集合不为null
if (item?.PropertyCategories == null)
{
return null;
}
foreach (var category in item.PropertyCategories)
{
if (category.DisplayName == CategoryAttributeManager.LogisticsCategories.LOGISTICS)
// 防御性检查:跳过无效或损坏的属性类别
if (category == null)
{
foreach (var property in category.Properties)
continue;
}
try
{
// 检查类别显示名称,捕获"幽灵"属性异常
if (category.DisplayName == CategoryAttributeManager.LogisticsCategories.LOGISTICS)
{
if (property.DisplayName == CategoryAttributeManager.LogisticsProperties.TYPE)
// 防御性检查确保Properties集合存在
if (category.Properties == null)
{
return property.Value.ToDisplayString();
continue;
}
foreach (var property in category.Properties)
{
// 防御性检查:跳过无效属性
if (property == null)
{
continue;
}
try
{
if (property.DisplayName == CategoryAttributeManager.LogisticsProperties.TYPE)
{
return property.Value.ToDisplayString();
}
}
catch (Exception propEx)
{
// 捕获单个属性读取异常,继续处理其他属性
LogManager.Warning($"读取物流属性失败,跳过: {propEx.Message}");
}
}
}
}
catch (Exception catEx)
{
// 捕获单个类别处理异常(如"幽灵"属性错误),继续处理其他类别
LogManager.Warning($"处理属性类别时发生异常,跳过: {catEx.Message}");
}
}
}
catch (Exception ex)
@ -814,21 +1099,62 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
try
{
// 防御性检查确保item和其属性集合不为null
if (item?.PropertyCategories == null)
{
return null;
}
var attributes = new System.Text.StringBuilder();
foreach (var category in item.PropertyCategories)
{
if (category.DisplayName == CategoryAttributeManager.LogisticsCategories.LOGISTICS)
// 防御性检查:跳过无效或损坏的属性类别
if (category == null)
{
foreach (var property in category.Properties)
continue;
}
try
{
// 检查类别显示名称,捕获"幽灵"属性异常
if (category.DisplayName == CategoryAttributeManager.LogisticsCategories.LOGISTICS)
{
if (property.DisplayName != CategoryAttributeManager.LogisticsProperties.TYPE)
// 防御性检查确保Properties集合存在
if (category.Properties == null)
{
if (attributes.Length > 0) attributes.Append(", ");
attributes.Append($"{property.DisplayName}: {property.Value.ToDisplayString()}");
break;
}
foreach (var property in category.Properties)
{
// 防御性检查:跳过无效属性
if (property == null)
{
continue;
}
try
{
if (property.DisplayName != CategoryAttributeManager.LogisticsProperties.TYPE)
{
if (attributes.Length > 0) attributes.Append(", ");
attributes.Append($"{property.DisplayName}: {property.Value.ToDisplayString()}");
}
}
catch (Exception propEx)
{
// 捕获单个属性读取异常,继续处理其他属性
LogManager.Warning($"读取物流属性详情失败,跳过: {propEx.Message}");
}
}
break;
}
break;
}
catch (Exception catEx)
{
// 捕获单个类别处理异常(如"幽灵"属性错误),继续处理其他类别
LogManager.Warning($"处理属性类别详情时发生异常,跳过: {catEx.Message}");
}
}
@ -889,7 +1215,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private void RefreshAllCommands()
{
OnPropertyChanged(nameof(CanSetLogisticsAttribute));
OnPropertyChanged(nameof(CanClearLogisticsAttribute));
OnPropertyChanged(nameof(HasSelectedModels));
OnPropertyChanged(nameof(HasSelectedModelsWithLogisticsAttributes));
// 🔧 强制WPF重新查询所有Command的CanExecute状态
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
}
#endregion
@ -922,6 +1253,25 @@ namespace NavisworksTransport.UI.WPF.ViewModels
$"物流模型数量: {LogisticsModels?.Count ?? 0}";
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
try
{
// 取消选择事件订阅
UnsubscribeFromSelectionEvents();
LogManager.Info("[ModelSettingsViewModel] 资源清理完成");
}
catch (Exception ex)
{
LogManager.Error($"[ModelSettingsViewModel] 资源清理失败: {ex.Message}");
}
}
#endregion
}
}

View File

@ -33,61 +33,122 @@ NavisworksTransport 分层管理页签视图 - 重构优化版本
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="10">
<StackPanel>
<!-- 区域1: 楼层属性设置(仅保留手动设置部分) -->
<!-- 区域1: 楼层属性设置 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,0,0,15" Padding="12">
<StackPanel>
<Label Content="楼层属性设置" Style="{StaticResource SectionHeaderStyle}"/>
<!-- 属性设置功能 -->
<Expander Header="手动设置层属性"
IsExpanded="{Binding NeedsManualFloorSetup}"
<!-- 模型选择状态显示 -->
<Grid Margin="0,5,0,15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding SelectedModelsText}"
FontWeight="SemiBold"
Foreground="#FF2B579A"
Margin="0,0,0,5"/>
</Grid>
<!-- 楼层属性设置功能 -->
<Expander Header="楼层信息设置"
IsExpanded="False"
Margin="0,5,0,0">
<StackPanel Margin="10,10,0,5">
<Grid>
<!-- 使用提示信息 -->
<TextBlock Text="为选中的模型设置楼层信息,用于后续的分层导出和路径规划"
Style="{StaticResource StatusTextStyle}"
Foreground="#FF666666"
Margin="0,0,0,10"
TextWrapping="Wrap"/>
<!-- 第一行:楼层标识 -->
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="可用属性:" VerticalAlignment="Center"/>
<ComboBox Grid.Row="0" Grid.Column="1"
ItemsSource="{Binding AvailableAttributes}"
SelectedItem="{Binding SelectedFloorAttribute}"
Margin="5,2" IsEnabled="{Binding IsNotProcessing}"/>
<Button Grid.Row="0" Grid.Column="2"
Content="刷新"
Style="{StaticResource SecondaryButtonStyle}"
Command="{Binding RefreshAttributesCommand}"/>
<Label Grid.Column="0" Content="楼层:" Style="{StaticResource ParameterLabelStyle}"/>
<ComboBox Grid.Column="1"
x:Name="FloorLevelComboBox"
Text="{Binding SelectedFloorLevel}"
IsEditable="True"
Margin="5,2" IsEnabled="{Binding IsNotProcessing}"
ToolTip="选择或输入楼层标识如F1、F2、B1等">
<ComboBoxItem>F1</ComboBoxItem>
<ComboBoxItem>F2</ComboBoxItem>
<ComboBoxItem>F3</ComboBoxItem>
<ComboBoxItem>F4</ComboBoxItem>
<ComboBoxItem>F5</ComboBoxItem>
<ComboBoxItem>B1</ComboBoxItem>
<ComboBoxItem>B2</ComboBoxItem>
<ComboBoxItem>B3</ComboBoxItem>
</ComboBox>
<Label Grid.Row="1" Grid.Column="0" Content="选择范围:" VerticalAlignment="Center"/>
<TextBlock Grid.Row="1" Grid.Column="1"
Text="{Binding SelectedNodesText}"
VerticalAlignment="Center"
Margin="5,2"
Background="#FFF0F0F0"
Padding="4"/>
<Button Grid.Row="1" Grid.Column="2"
Content="选择"
Style="{StaticResource SecondaryButtonStyle}"
Command="{Binding SelectNodesCommand}"/>
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"
Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="应用层属性"
Style="{StaticResource ActionButtonStyle}"
Command="{Binding ApplyFloorAttributesCommand}"
IsEnabled="{Binding CanApplyFloorAttributes}"/>
<TextBlock Text="{Binding FloorAttributeStatus}"
VerticalAlignment="Center"
Style="{StaticResource StatusTextStyle}"/>
</StackPanel>
<Label Grid.Column="2" Content="区域:" VerticalAlignment="Center" Margin="15,0,0,0"/>
<TextBox Grid.Column="3"
Text="{Binding SelectedZone}"
Style="{StaticResource ParameterInputStyle}"
ToolTip="可选:区域标识,如北区、南区等"/>
</Grid>
<!-- 第二行:建筑标识 -->
<Grid Margin="0,0,0,15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="子系统:" Style="{StaticResource ParameterLabelStyle}"/>
<TextBox Grid.Column="1"
Text="{Binding SelectedSubSystem}"
Style="{StaticResource ParameterInputStyle}"
ToolTip="可选:子系统标识"/>
<TextBlock Grid.Column="2"
Text="(选填)"
Style="{StaticResource StatusTextStyle}"
VerticalAlignment="Center"
Margin="10,0,0,0"/>
</Grid>
<!-- 操作按钮区域 -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="0,5,0,5">
<Button Content="设置楼层属性"
Style="{StaticResource ActionButtonStyle}"
Command="{Binding SetFloorAttributeCommand}"
IsEnabled="{Binding CanSetFloorAttribute}"
ToolTip="为选中的模型设置楼层属性"/>
<Button Content="清除楼层属性"
Style="{StaticResource ActionButtonStyle}"
Command="{Binding ClearFloorAttributeCommand}"
IsEnabled="{Binding CanClearFloorAttribute}"
ToolTip="清除选中模型的楼层属性"/>
<Button Content="查看楼层属性"
Style="{StaticResource SecondaryButtonStyle}"
Command="{Binding ViewFloorAttributeCommand}"
IsEnabled="{Binding HasSelectedItems}"
ToolTip="查看选中模型的当前楼层属性"/>
</StackPanel>
<!-- 楼层属性显示区域 -->
<GroupBox Header="当前楼层属性" Margin="0,10,0,0"
Visibility="{Binding ShowFloorAttributeInfo, Converter={StaticResource BoolToVisConverter}}">
<StackPanel Margin="10">
<TextBlock Text="{Binding CurrentFloorAttributeInfo}"
Style="{StaticResource StatusTextStyle}"
TextWrapping="Wrap"
Margin="0,5"/>
</StackPanel>
</GroupBox>
</StackPanel>
</Expander>
</StackPanel>
@ -271,20 +332,19 @@ NavisworksTransport 分层管理页签视图 - 重构优化版本
<Border BorderBrush="#FFE0E0E0" BorderThickness="1" CornerRadius="0" Padding="12"
Visibility="{Binding IsProcessing, Converter={StaticResource BoolToVisConverter}}">
<StackPanel>
<TextBlock Text="{Binding CurrentOperationText}"
FontWeight="SemiBold"
Margin="0,0,0,5"/>
<ProgressBar Value="{Binding ProgressPercentage}"
Height="16"
Margin="0,0,0,5"/>
<TextBlock Text="{Binding ProgressDetailText}"
Style="{StaticResource StatusTextStyle}"/>
<!-- 只在有取消令牌时显示取消按钮 -->
<Button Content="取消操作"
Style="{StaticResource SecondaryButtonStyle}"
Command="{Binding CancelOperationCommand}"
HorizontalAlignment="Right"
Margin="0,10,0,0"/>
Margin="0,10,0,0"
Visibility="{Binding ShowCancelButton, Converter={StaticResource BoolToVisConverter}}"/>
</StackPanel>
</Border>

View File

@ -159,6 +159,11 @@ NavisworksTransport 类别设置页签视图 - 参考分层管理页签的设计
Command="{Binding SetLogisticsAttributeCommand}"
IsEnabled="{Binding CanSetLogisticsAttribute}"
ToolTip="为选中的模型应用上述物流属性"/>
<Button Content="清除属性"
Style="{StaticResource ActionButtonStyle}"
Command="{Binding ClearLogisticsAttributeCommand}"
IsEnabled="{Binding CanClearLogisticsAttribute}"
ToolTip="清除选中模型的物流属性"/>
<Button Content="重置默认值"
Style="{StaticResource SecondaryButtonStyle}"
Command="{Binding ResetToDefaultsCommand}"

View File

@ -0,0 +1,101 @@
using System;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils
{
/// <summary>
/// Navisworks API 通用工具类
/// 提供跨模块使用的通用 API 操作方法
/// </summary>
public static class NavisworksApiHelper
{
/// <summary>
/// 安全的缓存刷新方法
/// 用于解决某些 API 操作后缓存不同步的问题
/// 基于实际生产环境的验证,使用空集合的 SetHidden 操作来触发缓存更新
/// </summary>
/// <param name="logPrefix">日志前缀,用于标识调用方</param>
public static void SafeCacheRefresh(string logPrefix = "NavisworksApiHelper")
{
try
{
var document = Application.ActiveDocument;
if (document?.Models != null)
{
// 使用更安全的空操作来触发缓存更新
// 方法: 空集合的SetHidden操作几乎零开销的伪操作
var emptyCollection = new ModelItemCollection();
document.Models.SetHidden(emptyCollection, false);
LogManager.WriteLog($"[{logPrefix}] ✅ 已执行轻量级缓存刷新");
}
}
catch (Exception refreshEx)
{
LogManager.WriteLog($"[{logPrefix}] ⚠️ 缓存刷新失败,但不影响主要操作: {refreshEx.Message}");
}
}
/// <summary>
/// 线程安全的缓存刷新方法
/// 确保缓存刷新操作在主 UI 线程中执行
/// </summary>
/// <param name="logPrefix">日志前缀,用于标识调用方</param>
public static void SafeCacheRefreshUIThread(string logPrefix = "NavisworksApiHelper")
{
if (System.Windows.Application.Current?.Dispatcher?.CheckAccess() == true)
{
SafeCacheRefresh(logPrefix);
}
else
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
SafeCacheRefresh(logPrefix);
});
}
}
/// <summary>
/// 检查当前是否在主 UI 线程中
/// </summary>
/// <returns>在主 UI 线程中返回 true</returns>
public static bool IsOnUIThread()
{
return System.Windows.Application.Current?.Dispatcher?.CheckAccess() == true;
}
/// <summary>
/// 在主 UI 线程中执行操作,确保线程安全
/// </summary>
/// <typeparam name="T">返回值类型</typeparam>
/// <param name="operation">要执行的操作</param>
/// <returns>操作结果</returns>
public static T ExecuteOnUIThread<T>(Func<T> operation)
{
if (IsOnUIThread())
{
return operation();
}
else
{
return System.Windows.Application.Current.Dispatcher.Invoke(operation);
}
}
/// <summary>
/// 在主 UI 线程中执行操作,确保线程安全(无返回值)
/// </summary>
/// <param name="operation">要执行的操作</param>
public static void ExecuteOnUIThread(Action operation)
{
if (IsOnUIThread())
{
operation();
}
else
{
System.Windows.Application.Current.Dispatcher.Invoke(operation);
}
}
}
}

View File

@ -0,0 +1,359 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils
{
/// <summary>
/// Navisworks选择状态管理帮助类
/// 提供统一的选择状态查询、格式化和事件处理功能
/// </summary>
public static class NavisworksSelectionHelper
{
#region
/// <summary>
/// 获取当前选择状态信息
/// </summary>
/// <returns>选择状态信息结果</returns>
public static SelectionStateResult GetCurrentSelectionState()
{
try
{
var document = Application.ActiveDocument;
if (document?.CurrentSelection?.SelectedItems?.Count > 0)
{
var selectedCount = document.CurrentSelection.SelectedItems.Count;
var selectedItems = document.CurrentSelection.SelectedItems.Cast<ModelItem>().ToList();
return new SelectionStateResult
{
Success = true,
Count = selectedCount,
SelectedItems = selectedItems,
HasSelection = true
};
}
else
{
return new SelectionStateResult
{
Success = true,
Count = 0,
SelectedItems = new List<ModelItem>(),
HasSelection = false
};
}
}
catch (Exception ex)
{
return new SelectionStateResult
{
Success = false,
Count = 0,
SelectedItems = new List<ModelItem>(),
HasSelection = false,
ErrorMessage = $"检查选择状态失败: {ex.Message}"
};
}
}
/// <summary>
/// 异步获取当前选择状态信息
/// </summary>
/// <returns>选择状态信息结果</returns>
public static async Task<SelectionStateResult> GetCurrentSelectionStateAsync()
{
return await Task.Run(() => GetCurrentSelectionState());
}
/// <summary>
/// 检查是否有选中的项目
/// </summary>
/// <returns>是否有选择</returns>
public static bool HasSelectedItems()
{
try
{
var document = Application.ActiveDocument;
return document?.CurrentSelection?.SelectedItems?.Count > 0;
}
catch
{
return false;
}
}
#endregion
#region
/// <summary>
/// 格式化选择状态文本,包含节点名称
/// </summary>
/// <param name="count">选择数量</param>
/// <param name="selectedItems">选择的项目集合</param>
/// <param name="unitName">单位名称(如"个模型"、"个节点"</param>
/// <param name="maxDisplayCount">最大显示名称数量</param>
/// <param name="maxTotalLength">最大总长度</param>
/// <returns>格式化后的选择状态文本</returns>
public static string FormatSelectionText(int count, IEnumerable<ModelItem> selectedItems = null,
string unitName = "个模型", int maxDisplayCount = 3, int maxTotalLength = 80)
{
if (count == 0)
{
return $"请在主界面中选择需要设置的{unitName.Replace("", "")}";
}
var baseText = $"已选择{count}{unitName}";
if (selectedItems == null)
{
return baseText;
}
var itemsList = selectedItems.Take(maxDisplayCount + 1).ToList();
if (itemsList.Count == 0)
{
return baseText;
}
var names = new List<string>();
foreach (var item in itemsList.Take(maxDisplayCount))
{
var name = item?.DisplayName;
if (!string.IsNullOrWhiteSpace(name))
{
names.Add(name);
}
}
if (names.Count == 0)
{
return baseText;
}
string namesText;
if (count == 1)
{
// 单选时显示完整名称,但限制长度
var fullName = names[0];
if (fullName.Length > 50)
{
fullName = fullName.Substring(0, 47) + "...";
}
namesText = fullName;
}
else
{
// 多选时显示前几个名称
var joinedNames = string.Join(", ", names);
if (count > maxDisplayCount)
{
if (joinedNames.Length + 20 > maxTotalLength)
{
// 如果名称太长,截断并添加省略号
var truncatedLength = Math.Max(20, maxTotalLength - 20);
joinedNames = joinedNames.Substring(0, Math.Min(joinedNames.Length, truncatedLength)) + "...";
}
namesText = joinedNames + "...";
}
else
{
if (joinedNames.Length > maxTotalLength)
{
joinedNames = joinedNames.Substring(0, maxTotalLength - 3) + "...";
}
namesText = joinedNames;
}
}
return $"{baseText}: {namesText}";
}
/// <summary>
/// 基于选择状态结果格式化选择文本
/// </summary>
/// <param name="selectionResult">选择状态结果</param>
/// <param name="unitName">单位名称</param>
/// <param name="maxDisplayCount">最大显示名称数量</param>
/// <param name="maxTotalLength">最大总长度</param>
/// <returns>格式化后的选择状态文本</returns>
public static string FormatSelectionText(SelectionStateResult selectionResult,
string unitName = "个模型", int maxDisplayCount = 3, int maxTotalLength = 80)
{
if (!selectionResult.Success)
{
return selectionResult.ErrorMessage ?? "检查选择状态异常";
}
return FormatSelectionText(selectionResult.Count, selectionResult.SelectedItems,
unitName, maxDisplayCount, maxTotalLength);
}
#endregion
#region
/// <summary>
/// 选择变化事件委托
/// </summary>
/// <param name="selectionResult">新的选择状态</param>
public delegate Task SelectionChangedEventHandler(SelectionStateResult selectionResult);
/// <summary>
/// 订阅选择变化事件的包装器
/// </summary>
/// <param name="handler">事件处理器</param>
/// <param name="uiStateManager">UI状态管理器可选用于确保UI更新在正确线程执行</param>
/// <returns>事件订阅管理器</returns>
public static SelectionEventSubscription SubscribeToSelectionChanges(
SelectionChangedEventHandler handler,
Core.UIStateManager uiStateManager = null)
{
return new SelectionEventSubscription(handler, uiStateManager);
}
#endregion
}
/// <summary>
/// 选择状态结果
/// </summary>
public class SelectionStateResult
{
/// <summary>
/// 操作是否成功
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 选择的数量
/// </summary>
public int Count { get; set; }
/// <summary>
/// 选择的项目集合
/// </summary>
public List<ModelItem> SelectedItems { get; set; } = new List<ModelItem>();
/// <summary>
/// 是否有选择
/// </summary>
public bool HasSelection { get; set; }
/// <summary>
/// 错误信息(如果操作失败)
/// </summary>
public string ErrorMessage { get; set; }
}
/// <summary>
/// 选择事件订阅管理器
/// 负责管理Navisworks选择变化事件的订阅和取消订阅
/// </summary>
public class SelectionEventSubscription : IDisposable
{
private readonly NavisworksSelectionHelper.SelectionChangedEventHandler _handler;
private readonly Core.UIStateManager _uiStateManager;
private bool _isSubscribed = false;
private bool _disposed = false;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="handler">事件处理器</param>
/// <param name="uiStateManager">UI状态管理器</param>
public SelectionEventSubscription(
NavisworksSelectionHelper.SelectionChangedEventHandler handler,
Core.UIStateManager uiStateManager = null)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
_uiStateManager = uiStateManager;
Subscribe();
}
/// <summary>
/// 订阅事件
/// </summary>
private void Subscribe()
{
try
{
if (!_isSubscribed && Application.ActiveDocument?.CurrentSelection != null)
{
Application.ActiveDocument.CurrentSelection.Changed += OnNavisworksSelectionChanged;
_isSubscribed = true;
LogManager.Info("[NavisworksSelectionHelper] 已订阅Navisworks选择变化事件");
}
}
catch (Exception ex)
{
LogManager.Error($"[NavisworksSelectionHelper] 订阅选择事件失败: {ex.Message}", ex);
}
}
/// <summary>
/// 取消订阅事件
/// </summary>
private void Unsubscribe()
{
try
{
if (_isSubscribed && Application.ActiveDocument?.CurrentSelection != null)
{
Application.ActiveDocument.CurrentSelection.Changed -= OnNavisworksSelectionChanged;
_isSubscribed = false;
LogManager.Info("[NavisworksSelectionHelper] 已取消订阅Navisworks选择变化事件");
}
}
catch (Exception ex)
{
LogManager.Error($"[NavisworksSelectionHelper] 取消选择事件订阅失败: {ex.Message}", ex);
}
}
/// <summary>
/// Navisworks选择变化事件处理器
/// </summary>
private async void OnNavisworksSelectionChanged(object sender, EventArgs e)
{
try
{
// 获取最新的选择状态
var selectionResult = await NavisworksSelectionHelper.GetCurrentSelectionStateAsync();
// 如果有UI状态管理器确保在正确的线程上执行
if (_uiStateManager != null)
{
await _uiStateManager.ExecuteUIUpdateAsync(async () =>
{
await _handler(selectionResult);
});
}
else
{
await _handler(selectionResult);
}
}
catch (Exception ex)
{
LogManager.Error($"[NavisworksSelectionHelper] 处理选择变化事件异常: {ex.Message}", ex);
}
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
if (!_disposed)
{
Unsubscribe();
_disposed = true;
}
}
}
}