NavisworksTransport/doc/design/2026/NavisworksAPI使用方法.md

1024 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# Navisworks API 使用方法指南
基于真实官方示例的正确API用法总结
## 参考示例来源
基于以下官方示例文件的真实API用法
- `C:\Users\Tellme\apps\NavisworksTransport\doc\navisworks_api\NET\examples\PlugIns\SearchComparisonPlugIn\SearchComparisonPlugIn.cs`
- `C:\Users\Tellme\apps\NavisworksTransport\doc\navisworks_api\NET\examples\PlugIns\Examiner\Examiner.cs`
## 1. 模型遍历和节点访问
### 1.1 正确的遍历方式
```csharp
// ✅ 正确:获取所有模型项
IEnumerable<ModelItem> allItems =
Application.ActiveDocument.Models.RootItemDescendantsAndSelf;
// ✅ 正确:遍历特定模型的所有项
foreach (Model model in document.Models)
{
foreach (ModelItem item in model.RootItem.DescendantsAndSelf)
{
// 处理每个模型项
}
}
// ✅ 正确:只获取顶级节点
foreach (Model model in document.Models)
{
foreach (ModelItem topLevelItem in model.RootItem.Children)
{
// 处理顶级节点
}
}
```
### 1.2 获取子节点
```csharp
// ✅ 正确:获取某个节点的所有后代
var childItems = selectedItem.DescendantsAndSelf.Where(x => x != selectedItem);
// ✅ 正确:只获取直接子节点
foreach (ModelItem child in parentItem.Children)
{
// 处理直接子节点
}
```
### 1.3 遍历祖先节点
```csharp
// ✅ 正确:向上遍历父节点链
var current = selectedItem.Parent;
while (current != null)
{
// 处理祖先节点
current = current.Parent;
}
```
## 2. 搜索和查询
### 2.1 使用LINQ查询
```csharp
// ✅ 正确使用LINQ查询模型项
IEnumerable<ModelItem> results =
Application.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(x =>
x.HasGeometry &&
!x.IsHidden &&
x.ClassDisplayName.ToLower().Contains("wall"));
```
### 2.2 使用Search类
```csharp
// ✅ 正确使用Search API
Search search = new Search();
// 添加搜索条件
search.SearchConditions.Add(
SearchCondition.HasCategoryByName(PropertyCategoryNames.Geometry));
search.SearchConditions.Add(
SearchCondition.HasPropertyByName(PropertyCategoryNames.Item, DataPropertyNames.ItemHidden)
.EqualValue(VariantData.FromBoolean(false)));
// 设置搜索范围
search.Selection.SelectAll();
search.Locations = SearchLocations.DescendantsAndSelf;
// 执行搜索
ModelItemCollection results = search.FindAll(document, false);
```
### 2.3 迭代遍历(性能对比)
```csharp
// ✅ 可用但性能较低:迭代方法
ModelItemCollection searchResults = new ModelItemCollection();
foreach (ModelItem modelItem in Application.ActiveDocument.Models.CreateCollectionFromRootItems().DescendantsAndSelf)
{
if (modelItem.HasGeometry && !modelItem.IsHidden)
searchResults.Add(modelItem);
}
```
## 3. 选择操作
### 3.1 操作当前选择
```csharp
// ✅ 正确:获取当前选择
var currentSelection = document.CurrentSelection.SelectedItems;
// ✅ 正确:清空选择
document.CurrentSelection.Clear();
// ✅ 正确:添加到选择
document.CurrentSelection.Add(modelItem);
// ✅ 正确:复制集合到选择
document.CurrentSelection.CopyFrom(modelItems);
```
## 4. 可见性控制
### 4.1 隐藏和显示
```csharp
// ✅ 正确:隐藏项目
ModelItemCollection itemsToHide = new ModelItemCollection();
itemsToHide.Add(modelItem);
document.Models.SetHidden(itemsToHide, true);
// ✅ 正确:显示项目
document.Models.SetHidden(itemsToHide, false);
// ✅ 正确:检查是否隐藏
if (modelItem.IsHidden)
{
// 项目被隐藏
}
```
## 5. 文件导出
### 5.1 基本文件保存
```csharp
// ✅ 正确保存NWD文件
document.SaveFile(filePath);
// ✅ 正确:指定版本保存
document.SaveFile(filePath, DocumentFileVersion.Current);
```
### 5.2 ExportToNwd API
```csharp
// ✅ 正确使用ExportToNwd导出
var exportOptions = new NwdExportOptions();
exportOptions.ExcludeHiddenItems = true; // 只导出可见项目
exportOptions.EmbedXrefs = false;
exportOptions.PreventObjectPropertyExport = false;
document.ExportToNwd(saveFilePath, exportOptions);
```
## 6. 性能最佳实践
### 6.1 避免的做法
```csharp
// ❌ 错误使用不存在的API
// SearchCondition.HasAncestor(items) // 这个API不存在
// ❌ 错误:深度递归遍历
// void RecursiveTraversal(ModelItem item) // 大模型中可能导致堆栈溢出
```
### 6.2 推荐的做法
```csharp
// ✅ 推荐使用内置的DescendantsAndSelf
var allDescendants = rootItem.DescendantsAndSelf;
// ✅ 推荐使用LINQ进行高效查询
var filteredItems = allItems.Where(x => x.HasGeometry);
// ✅ 推荐:批量操作而不是逐个操作
ModelItemCollection batchItems = new ModelItemCollection();
// 添加所有需要处理的项目
document.Models.SetHidden(batchItems, true); // 一次性操作
```
## 7. 完整示例:多选节点导出
```csharp
public void ExportSelectedNodes(List<ModelItem> selectedItems, string filePath)
{
var document = Application.ActiveDocument;
var nodesToKeepVisible = new HashSet<ModelItem>();
// 1. 收集需要保持可见的节点
foreach (var selectedItem in selectedItems)
{
// 添加选中节点本身
nodesToKeepVisible.Add(selectedItem);
// 添加所有祖先节点
var current = selectedItem.Parent;
while (current != null)
{
nodesToKeepVisible.Add(current);
current = current.Parent;
}
// 添加所有子节点(可选)
var childItems = selectedItem.DescendantsAndSelf.Where(x => x != selectedItem);
foreach (ModelItem child in childItems)
{
nodesToKeepVisible.Add(child);
}
}
// 2. 收集顶级节点并决定隐藏哪些
var itemsToHide = new ModelItemCollection();
foreach (Model model in document.Models)
{
foreach (ModelItem topLevelItem in model.RootItem.Children)
{
bool shouldKeep = false;
// 检查是否包含选中节点
foreach (var selectedItem in selectedItems)
{
var current = selectedItem;
while (current != null)
{
if (current == topLevelItem)
{
shouldKeep = true;
break;
}
current = current.Parent;
}
if (shouldKeep) break;
}
if (!shouldKeep)
{
itemsToHide.Add(topLevelItem);
}
}
}
// 3. 执行隐藏和导出
try
{
document.Models.SetHidden(itemsToHide, true);
var exportOptions = new NwdExportOptions();
exportOptions.ExcludeHiddenItems = true;
document.ExportToNwd(filePath, exportOptions);
}
finally
{
// 4. 恢复可见性
document.Models.SetHidden(itemsToHide, false);
}
}
```
## 8. 线程安全 - 关键重要
### 8.1 Navisworks API 线程安全要求
**核心原则:所有 Navisworks API 调用必须在主 UI 线程STA 线程)中执行**
```csharp
// ❌ 错误:在后台线程中调用 Navisworks API
await Task.Run(() =>
{
var document = Application.ActiveDocument; // 可能崩溃
document.ExportToNwd(path, options); // 会崩溃
});
// ✅ 正确:使用 Dispatcher.Invoke 确保主线程执行
await Task.Run(() =>
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
var document = Application.ActiveDocument;
document.ExportToNwd(path, options); // 安全执行
});
});
```
### 8.2 实际案例:分层导出修复
**问题场景**`SimplifiedModelSplitterManager.ExportLayerToNwd` 方法通过后台线程调用时崩溃
```csharp
// ❌ 问题代码:导致崩溃
public bool ExportLayerToNwd(...)
{
var document = NavisApplication.ActiveDocument;
document.ExportToNwd(outputPath, exportOptions); // 后台线程崩溃
}
```
**修复方案**:使用 Dispatcher.Invoke 包装所有 API 调用
```csharp
// ✅ 修复代码:线程安全
public bool ExportLayerToNwd(...)
{
bool exportResult = false;
Exception exportException = null;
// 确保在主线程中执行所有 Navisworks API 调用
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
try
{
var document = NavisApplication.ActiveDocument;
// 保存可见性状态
var originalVisibilityState = SaveCurrentVisibilityState(document);
try
{
// 隐藏不需要的项目
var itemsToHide = GetItemsToHide(...);
document.Models.SetHidden(itemsToHide, true);
// 创建导出选项
var exportOptions = new NwdExportOptions
{
ExcludeHiddenItems = true,
EmbedXrefs = false,
PreventObjectPropertyExport = false
};
// 在主线程中安全执行导出
document.ExportToNwd(outputPath, exportOptions);
exportResult = true;
}
finally
{
// 恢复可见性状态
RestoreVisibilityState(document, originalVisibilityState);
}
}
catch (Exception ex)
{
exportException = ex;
}
});
if (exportException != null)
throw exportException;
return exportResult;
}
```
### 8.3 线程安全检查和诊断
```csharp
// ✅ 检查当前线程状态
var apartmentState = System.Threading.Thread.CurrentThread.GetApartmentState();
LogManager.Info($"当前线程状态: {apartmentState}"); // 应该是 STA
if (apartmentState != System.Threading.ApartmentState.STA)
{
LogManager.Warning("警告不在STA线程中API调用可能失败");
}
// ✅ 验证是否在主线程中
bool isMainThread = System.Windows.Application.Current.Dispatcher.CheckAccess();
if (!isMainThread)
{
LogManager.Warning("警告不在主线程中需要使用Dispatcher.Invoke");
}
```
### 8.4 常见线程安全问题和解决方案
| 问题场景 | 症状 | 解决方案 |
|---------|------|---------|
| 后台线程调用 API | 程序崩溃,无错误信息 | 使用 `Dispatcher.Invoke()` |
| Command.ExecuteAsync() | Task 中的 API 调用崩溃 | 在 Task 内部使用 Dispatcher |
| 异步方法调用 API | 间歇性崩溃 | 检查执行线程,确保主线程 |
| Timer 中调用 API | 定时器触发时崩溃 | Timer 回调使用 Dispatcher |
### 8.5 最佳实践模式
```csharp
// ✅ 推荐模式:安全的异步 Navisworks API 调用
public async Task<bool> SafeNavisworksOperationAsync()
{
// 1. 后台准备数据
var preparedData = await Task.Run(() =>
{
// 在后台线程中进行数据准备(不涉及 Navisworks API
return PrepareDataSafely();
});
// 2. 主线程执行 API 调用
bool result = false;
await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
{
// 所有 Navisworks API 调用都在主线程中
var document = Application.ActiveDocument;
result = document.SomeNavisworksOperation(preparedData);
});
return result;
}
// ✅ 推荐模式:批量 API 操作
public void BatchNavisworksOperations(List<ModelItem> items)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
var document = Application.ActiveDocument;
// 批量操作,避免多次线程切换
var itemCollection = new ModelItemCollection();
foreach (var item in items)
{
itemCollection.Add(item);
}
// 一次性完成所有操作
document.Models.SetHidden(itemCollection, true);
document.CurrentSelection.CopyFrom(itemCollection);
});
}
```
## 9. 常用属性和方法速查
### ModelItem 常用属性
- `HasGeometry` - 是否有几何体
- `IsHidden` - 是否隐藏
- `IsRequired` - 是否必需
- `IsInsert` - 是否为插入对象
- `IsLayer` - 是否为图层
- `DisplayName` - 显示名称
- `ClassName` - 类名
- `ClassDisplayName` - 类显示名称
- `Parent` - 父节点
- `Children` - 子节点集合
- `DescendantsAndSelf` - 所有后代节点(包括自己)
### Document 常用方法
- `SaveFile(string path)` - 保存文件
- `ExportToNwd(string path, NwdExportOptions options)` - 导出NWD
- `CurrentSelection` - 当前选择
- `Models` - 模型集合
### Models 常用方法
- `SetHidden(ModelItemCollection items, bool hidden)` - 设置隐藏状态
- `SetRequired(ModelItemCollection items, bool required)` - 设置必需状态
- `RootItemDescendantsAndSelf` - 所有根项目的后代
## 10. 错误避免指南
1. **线程安全是第一要务**:所有 Navisworks API 调用必须在主 UI 线程中执行
2. **不要使用不存在的API**:如 `SearchCondition.HasAncestor`
3. **避免深度递归**:使用内置的 `DescendantsAndSelf` 代替手写递归
4. **批量操作**:使用 `ModelItemCollection` 进行批量设置,而不是逐个操作
5. **正确的命名空间**:确保引用 `using Autodesk.Navisworks.Api;`
6. **异常处理**文件操作和API调用要适当处理异常
7. **资源清理**:隐藏操作后要恢复原始状态
8. **线程状态检查**在关键操作前验证线程状态STA
9. **Dispatcher 模式**:后台线程中需要调用 API 时,始终使用 Dispatcher.Invoke
## 11. Transform 变换操作
### 11.1 Transform 相关 API 概念
**核心概念**
- `ModelItem.Transform` - 返回设计文件中的原始变换,只读属性
- `OverridePermanentTransform()` - 应用增量变换(与现有变换叠加)
- `ResetPermanentTransform()` - 清除所有增量变换,恢复到设计文件原始位置
### 11.2 Transform 操作的正确用法
```csharp
// ✅ 获取物体的原始Transform设计文件中的位置
Transform3D originalTransform = modelItem.Transform;
// ✅ 应用增量变换(累积变换)
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { modelItem };
doc.Models.OverridePermanentTransform(modelItems, newTransform, false);
// ✅ 重置到原始位置(清除所有增量变换)
doc.Models.ResetPermanentTransform(modelItems);
```
### 11.3 Transform 操作的关键区别
| API方法 | 作用 | 使用场景 | 注意事项 |
|---------|------|---------|---------|
| `ModelItem.Transform` | 获取原始变换 | 记录物体初始位置 | 只读属性,返回设计文件位置 |
| `OverridePermanentTransform()` | 应用增量变换 | 动画中移动物体 | 与现有变换累积,不是绝对位置 |
| `ResetPermanentTransform()` | 重置到原始位置 | 清除所有移动,恢复初始状态 | 忽略所有之前的变换 |
### 11.4 实际应用案例
**案例1动画系统中的Transform管理**
```csharp
// 动画开始时记录原始位置
private Transform3D _originalTransform;
public void StartAnimation(ModelItem animatedObject)
{
// 记录原始Transform
_originalTransform = animatedObject.Transform;
// 移动到路径起点(增量变换)
var startTransform = Transform3D.CreateTranslation(startPosition);
var modelItems = new ModelItemCollection { animatedObject };
doc.Models.OverridePermanentTransform(modelItems, startTransform, false);
}
public void ResetAnimation()
{
// 动画结束后使用原始Transform恢复位置
var modelItems = new ModelItemCollection { _animatedObject };
doc.Models.OverridePermanentTransform(modelItems, _originalTransform, false);
}
```
**案例2用户手动位置恢复**
```csharp
public void RestoreToOriginalPosition(ModelItem selectedObject)
{
// 不需要记录Transform直接重置到设计文件原始位置
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { selectedObject };
// 清除所有增量变换,恢复到设计文件原始位置
doc.Models.ResetPermanentTransform(modelItems);
}
```
### 11.5 常见Transform问题和解决方案
**问题1位置恢复有偏移**
```csharp
// ❌ 错误:使用增量变换恢复位置
doc.Models.OverridePermanentTransform(modelItems, originalTransform, false);
// 问题:如果物体已经被移动过,这会导致累积偏移
// ✅ 正确:重置到原始位置
doc.Models.ResetPermanentTransform(modelItems);
// 结果:直接恢复到设计文件中的原始位置,无偏移
```
**问题2动画结束后位置不准确**
```csharp
// ✅ 动画系统应该记录原始Transform并使用增量恢复
private Transform3D _originalTransform;
// 动画开始时
_originalTransform = animatedObject.Transform;
// 动画结束时恢复
doc.Models.OverridePermanentTransform(modelItems, _originalTransform, false);
```
**问题3记录Transform但不使用**
```csharp
// ❌ 不必要记录Transform但使用Reset
private Transform3D _originalTransform;
_originalTransform = selectedItem.Transform; // 记录了但不使用
doc.Models.ResetPermanentTransform(modelItems); // 直接重置
// ✅ 简化:直接重置,无需记录
doc.Models.ResetPermanentTransform(modelItems);
```
### 11.6 Transform 最佳实践
1. **选择合适的恢复方式**
- 动画系统:使用 `OverridePermanentTransform` + 原始Transform
- 用户操作:使用 `ResetPermanentTransform` 直接重置
2. **避免不必要的Transform记录**
- 如果只需要恢复到设计文件原始位置,使用 `ResetPermanentTransform`
- 只有需要恢复到特定中间状态时才记录Transform
3. **理解增量vs绝对变换**
- `OverridePermanentTransform` 是增量的,会与现有变换叠加
- `ResetPermanentTransform` 是绝对的,清除所有变换
4. **线程安全**
- 所有Transform操作都必须在主UI线程中执行
- 使用 `Dispatcher.Invoke` 确保线程安全
## 12. Item属性和自定义属性访问
基于官方示例的正确属性访问方法总结。
### 12.1 NET API 属性访问方法
#### 基础属性访问(基于 Examiner.cs
```csharp
// ✅ 通过PropertyCategories查找特定分类
var category = item.PropertyCategories.FindCategoryByDisplayName("Material");
// ✅ 访问基础属性
string displayName = item.DisplayName;
string className = item.ClassName;
string classDisplayName = item.ClassDisplayName;
bool hasGeometry = item.HasGeometry;
bool isHidden = item.IsHidden;
bool isRequired = item.IsRequired;
// ✅ 通过LINQ查询特定属性的项目
IEnumerable<ModelItem> itemsWithMaterial =
Application.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(x =>
x.PropertyCategories.FindCategoryByDisplayName("Material") != null);
```
#### 高级属性搜索(基于 SearchComparisonPlugIn.cs
```csharp
// ✅ 使用预定义属性分类和属性名进行搜索
Search search = new Search();
// 搜索有几何体的项目
search.SearchConditions.Add(
SearchCondition.HasCategoryByName(PropertyCategoryNames.Geometry));
// 搜索非隐藏的项目
search.SearchConditions.Add(
SearchCondition.HasPropertyByName(PropertyCategoryNames.Item, DataPropertyNames.ItemHidden)
.EqualValue(VariantData.FromBoolean(false)));
// 设置搜索范围并执行
search.Selection.SelectAll();
search.Locations = SearchLocations.DescendantsAndSelf;
ModelItemCollection results = search.FindAll(document, false);
```
#### 属性分类和属性名常量
```csharp
// ✅ 使用预定义常量访问标准属性
// PropertyCategoryNames 包含:
// - PropertyCategoryNames.Item (项目属性)
// - PropertyCategoryNames.Geometry (几何属性)
// - PropertyCategoryNames.Material (材质属性)
// DataPropertyNames 包含:
// - DataPropertyNames.ItemHidden (隐藏状态)
// - DataPropertyNames.ItemRequired (必需状态)
// 等等...
```
### 12.2 COM API 属性访问方法(基于 AutoUserPropsExample.cs
#### 获取和遍历属性
```csharp
// ✅ 获取选中对象的属性节点
InwOpSelection2 selection = m_state.CurrentSelection as InwOpSelection2;
if (selection.Paths().Count > 0)
{
InwGUIPropertyNode2 propertyNode =
m_state.GetGUIPropertyNode(selection.Paths()[1], true) as InwGUIPropertyNode2;
// 遍历所有属性分类
foreach (InwGUIAttribute2 guiAttribute in propertyNode.GUIAttributes())
{
Console.WriteLine($"分类: {guiAttribute.ClassName}");
Console.WriteLine($"显示名: {guiAttribute.ClassUserName}");
Console.WriteLine($"用户自定义: {guiAttribute.UserDefined}");
// 遍历分类中的所有属性
foreach (InwOaProperty property in guiAttribute.Properties())
{
string propertyName = property.name; // 内部名称
string displayName = property.UserName; // 显示名称
string value = property.value; // 属性值
Console.WriteLine($" {displayName}({propertyName}) = {value}");
}
}
}
```
#### 添加自定义属性
```csharp
// ✅ 创建新的自定义属性
private void AddCustomProperty(string categoryName, string internalName, string value)
{
InwOpSelection2 selection = m_state.CurrentSelection as InwOpSelection2;
if (selection.Paths().Count > 0)
{
InwGUIPropertyNode2 propertyNode =
m_state.GetGUIPropertyNode(selection.Paths()[1], true) as InwGUIPropertyNode2;
// 创建属性容器
InwOaPropertyVec propertyVector =
m_state.ObjectFactory(nwEObjectType.eObjectType_nwOaPropertyVec);
// 创建单个属性
InwOaProperty property =
m_state.ObjectFactory(nwEObjectType.eObjectType_nwOaProperty);
property.name = "CustomProperty1"; // 内部名称
property.UserName = "自定义属性1"; // 显示名称
property.value = value; // 属性值
// 添加到容器
propertyVector.Properties().Add(property);
// 设置到对象上
propertyNode.SetUserDefined(0, categoryName, internalName, propertyVector);
}
}
// ✅ 使用示例
AddCustomProperty("自定义分类", "Custom_Category", "自定义值");
```
#### 修改现有自定义属性
```csharp
// ✅ 修改已存在的自定义属性值
private void UpdateCustomProperty(string categoryName, string internalName, string newValue)
{
// 重新调用SetUserDefined即可覆盖现有值
AddCustomProperty(categoryName, internalName, newValue);
}
```
#### 删除自定义属性
```csharp
// ✅ 删除指定的自定义属性分类
private void RemoveCustomProperty()
{
InwOpSelection2 selection = m_state.CurrentSelection as InwOpSelection2;
if (selection.Paths().Count > 0)
{
InwGUIPropertyNode2 propertyNode =
m_state.GetGUIPropertyNode(selection.Paths()[1], true) as InwGUIPropertyNode2;
// 删除索引为0的用户自定义属性分类
propertyNode.RemoveUserDefined(0);
}
}
```
### 12.3 两种API的选择建议
| 操作类型 | 推荐API | 理由 |
|---------|---------|------|
| 搜索和过滤 | NET API | LINQ查询更灵活性能更好 |
| 读取标准属性 | NET API | 类型安全,代码简洁 |
| 添加/修改自定义属性 | COM API | 提供完整的属性操作能力 |
| 删除自定义属性 | COM API | NET API不支持属性删除 |
| 批量属性操作 | NET API | 支持ModelItemCollection批量操作 |
### 12.4 属性操作最佳实践
```csharp
// ✅ 推荐模式结合两种API的优势
public void ProcessItemsWithCustomProperties()
{
// 1. 使用NET API进行搜索和过滤
var itemsWithGeometry = Application.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(item => item.HasGeometry && !item.IsHidden)
.ToList();
// 2. 对每个项目使用COM API添加自定义属性
foreach (ModelItem item in itemsWithGeometry)
{
// 选中当前项目
Application.ActiveDocument.CurrentSelection.Clear();
Application.ActiveDocument.CurrentSelection.Add(item);
// 使用COM API添加自定义属性
AddCustomProperty("物流信息", "Logistics_Info", $"处理时间: {DateTime.Now}");
}
}
```
### 12.5 常见属性操作示例
```csharp
// ✅ 检查项目是否有特定属性分类
public bool HasPropertyCategory(ModelItem item, string categoryName)
{
return item.PropertyCategories.FindCategoryByDisplayName(categoryName) != null;
}
// ✅ 获取项目的所有属性信息(用于调试)
public string GetItemPropertyInfo(ModelItem item)
{
var sb = new StringBuilder();
sb.AppendLine($"项目: {item.DisplayName}");
foreach (PropertyCategory category in item.PropertyCategories)
{
sb.AppendLine($" 分类: {category.DisplayName}");
foreach (DataProperty property in category.Properties)
{
sb.AppendLine($" {property.DisplayName}: {property.Value}");
}
}
return sb.ToString();
}
// ✅ 基于属性值进行复杂搜索
public List<ModelItem> FindItemsByPropertyValue(string categoryName, string propertyName, string searchValue)
{
return Application.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(item =>
{
var category = item.PropertyCategories.FindCategoryByDisplayName(categoryName);
if (category == null) return false;
var property = category.Properties.FirstOrDefault(p => p.DisplayName == propertyName);
return property != null && property.Value.ToString().Contains(searchValue);
})
.ToList();
}
```
### 12.6 Item属性的可编辑性分析
根据官方示例和Navisworks API设计**大部分标准Item属性是只读的**,但有**少数属性可以编辑**。
#### 可编辑的Item属性
**1. 可见性和状态属性通过Models API编辑**
```csharp
// ✅ 可以修改:隐藏状态
document.Models.SetHidden(modelItemCollection, true);
// ✅ 可以修改:必需状态
document.Models.SetRequired(modelItemCollection, true);
// ✅ 可以修改:颜色覆盖
document.Models.OverridePermanentColor(modelItemCollection, Color.Red);
// ✅ 可以修改:透明度覆盖
document.Models.OverridePermanentTransparency(modelItemCollection, 0.5);
// ✅ 可以修改Transform变换
document.Models.OverridePermanentTransform(modelItemCollection, transform, false);
```
**2. 基于官方Examiner.cs示例的确认**
```csharp
// 官方示例中的这些操作证实了这些属性是可修改的
document.Models.SetRequired(items, (required == SearchForm.ChangeDecision.Yes));
document.Models.SetHidden(items, (hidden == SearchForm.ChangeDecision.Yes));
document.Models.OverridePermanentColor(items, overrideColor);
document.Models.OverridePermanentTransparency(items, overrideTransparencyValue);
```
#### 只读的Item属性
**1. 基础标识属性(只读)**
```csharp
// ❌ 只读无法修改由原始CAD文件决定
item.DisplayName // 显示名称
item.ClassName // 类名
item.ClassDisplayName // 类显示名称
item.HasGeometry // 几何体标志
item.IsInsert // 插入对象标志
item.IsLayer // 图层标志
```
**2. 结构关系属性(只读)**
```csharp
// ❌ 只读结构关系由模型文件决定无法通过API修改
item.Parent // 父节点
item.Children // 子节点
item.Ancestors // 祖先节点
item.Descendants // 后代节点
```
**3. 几何和材质属性(只读)**
```csharp
// ❌ 只读由原始CAD文件决定
item.Geometry // 几何信息
item.BoundingBox() // 包围盒
item.PropertyCategories // 标准属性分类但可通过COM API添加自定义分类
```
#### 特殊情况:自定义属性完全可编辑
通过COM API可以**完全控制自定义属性**
```csharp
// ✅ 完全可编辑:自定义属性
// 添加新的自定义属性
propertyNode.SetUserDefined(index, categoryName, internalName, propertyVector);
// 修改现有自定义属性(重新设置)
propertyNode.SetUserDefined(index, categoryName, internalName, newPropertyVector);
// 删除自定义属性
propertyNode.RemoveUserDefined(index);
```
#### 物流插件的实际应用策略
**1. 使用可编辑的标准属性进行状态管理**
```csharp
// 物流分类可见性管理
document.Models.SetHidden(obstacleItems, true); // 隐藏障碍物
document.Models.SetHidden(loadingZoneItems, false); // 显示装卸区
// 路径可视化
document.Models.OverridePermanentColor(pathItems, pathColor); // 路径颜色标记
document.Models.OverridePermanentColor(selectedItems, highlightColor); // 选中高亮
// 动画和移动
document.Models.OverridePermanentTransform(movableItems, newTransform, false); // 物体移动动画
```
**2. 使用自定义属性存储业务数据**
```csharp
// 存储物流分类信息
AddCustomProperty("物流信息", "Logistics_Category", "装卸区");
AddCustomProperty("物流信息", "Access_Level", "高优先级");
AddCustomProperty("物流信息", "Capacity", "1000kg");
// 存储路径规划数据
AddCustomProperty("路径信息", "Path_ID", "Path_001");
AddCustomProperty("路径信息", "Path_Length", "15.5m");
AddCustomProperty("路径信息", "Travel_Time", "120s");
// 存储碰撞检测结果
AddCustomProperty("碰撞信息", "Collision_Status", "Safe");
AddCustomProperty("碰撞信息", "Last_Check", DateTime.Now.ToString());
```
**3. 只读属性用于查询和智能分析**
```csharp
// 基于只读属性进行智能空间分析
var suitableForStorage = items.Where(x =>
x.HasGeometry &&
x.ClassDisplayName.Contains("Room") &&
!x.IsHidden &&
x.BoundingBox().Max.Z > 3.0); // 高度足够的房间
// 基于结构关系进行逻辑分组
var floorItems = selectedFloor.DescendantsAndSelf
.Where(x => x.HasGeometry);
```
**4. 综合应用示例:物流节点标记**
```csharp
public void MarkAsLogisticsNode(ModelItem item, string category, Dictionary<string, string> properties)
{
var itemCollection = new ModelItemCollection { item };
// 1. 使用标准可编辑属性设置可视化
Color categoryColor = GetCategoryColor(category);
document.Models.OverridePermanentColor(itemCollection, categoryColor);
document.Models.SetRequired(itemCollection, true); // 标记为重要
// 2. 使用自定义属性存储详细信息
document.CurrentSelection.Clear();
document.CurrentSelection.Add(item);
AddCustomProperty("物流分类", "Category", category);
foreach (var kvp in properties)
{
AddCustomProperty("物流属性", kvp.Key, kvp.Value);
}
// 3. 基于只读属性进行验证
if (!item.HasGeometry)
{
LogManager.Warning($"警告:{item.DisplayName} 没有几何体,可能不适合作为物流节点");
}
}
```
#### 属性编辑最佳实践
| 用途 | 推荐方法 | API类型 | 特点 |
|------|----------|---------|------|
| 状态标记 | SetHidden, SetRequired | NET API | 影响显示和选择 |
| 可视化 | OverridePermanentColor | NET API | 临时视觉效果 |
| 空间变换 | OverridePermanentTransform | NET API | 支持动画 |
| 业务数据 | SetUserDefined | COM API | 持久化存储 |
| 查询分析 | 只读属性 + LINQ | NET API | 高性能筛选 |
### 12.7 属性操作注意事项
1. **线程安全**所有属性操作都必须在主UI线程中执行
2. **选择状态**COM API属性操作需要先选中目标对象
3. **性能考虑**大批量属性读取时NET API性能更佳
4. **属性持久化**自定义属性会保存在NWD文件中标准属性覆盖是临时的
5. **属性索引**COM API中的用户自定义属性使用索引管理
6. **错误处理**属性不存在时API会返回null需要检查
7. **属性类型限制**只读属性无法通过API修改只能通过可编辑API间接影响
8. **覆盖vs原始**OverridePermanent系列方法是覆盖原始属性可以恢复
## 13. 参考官方示例
强烈建议查看以下官方示例了解更多用法:
- `SearchComparisonPlugIn.cs` - 搜索性能对比和属性搜索
- `Examiner.cs` - LINQ查询和属性过滤示例
- `AutoUserPropsExample.cs` - COM API自定义属性操作
- `BasicDockPanePlugin.cs` - 基础插件结构
- `DatabaseDockPane/Models.cs` - 数据库操作示例
- `ClashDetective` 相关示例 - 高级功能示例