# Navisworks 2017 API 使用指南
## 概述
本指南基于实际项目开发经验,汇总了Navisworks 2017 API的核心用法和最佳实践。重点覆盖COM API的属性操作功能,为插件开发提供实用的参考。
## 目录
1. [环境配置](#环境配置)
2. [COM API 基础](#com-api-基础)
3. [属性操作 API](#属性操作-api)
4. [常见问题与解决方案](#常见问题与解决方案)
5. [最佳实践](#最佳实践)
6. [参考资源](#参考资源)
## 环境配置
### 必需引用
```xml
```
### 命名空间导入
```csharp
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Plugins;
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
```
## COM API 基础
### COM API 状态对象获取
```csharp
// 获取COM API状态对象 - 这是所有COM操作的入口点
ComApi.InwOpState10 state = ComApiBridge.State;
```
### .NET 对象与 COM 对象转换
```csharp
// ModelItem 转换为 COM Path
ComApi.InwOaPath3 comPath = (ComApi.InwOaPath3)ComApiBridge.ToInwOaPath(modelItem);
// ModelItemCollection 转换为 COM Selection
ComApi.InwOpSelection comSelection = ComApiBridge.ToInwOpSelection(modelItems);
```
### 属性节点获取
```csharp
// 获取模型项的属性节点
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
```
## 属性操作 API
### 1. 创建用户定义属性
#### 基本创建流程
```csharp
public static void CreateUserDefinedProperty(ModelItem item, string categoryName,
Dictionary properties)
{
// 1. 获取COM API状态对象
ComApi.InwOpState10 state = ComApiBridge.State;
// 2. 转换ModelItem为COM路径
ComApi.InwOaPath3 path = (ComApi.InwOaPath3)ComApiBridge.ToInwOaPath(item);
// 3. 获取属性节点
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
// 4. 创建属性类别容器
ComApi.InwOaPropertyVec propertyCategory = (ComApi.InwOaPropertyVec)state.ObjectFactory(
ComApi.nwEObjectType.eObjectType_nwOaPropertyVec, null, null);
// 5. 添加具体属性
foreach (var kvp in properties)
{
AddProperty(state, propertyCategory, kvp.Key, kvp.Value, kvp.Key + "_Internal");
}
// 6. 设置用户定义属性 (index=0表示创建新类别)
propertyNode.SetUserDefined(0, categoryName, categoryName + "_Internal", propertyCategory);
}
// 辅助方法:添加单个属性到类别
private static void AddProperty(ComApi.InwOpState10 state,
ComApi.InwOaPropertyVec propertyCategory,
string displayName, string value, string internalName)
{
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);
}
```
#### 关键参数说明
- **index=0**: 创建新的用户定义属性类别
- **categoryName**: 属性类别的显示名称
- **internalName**: 属性类别的内部名称(建议添加"_Internal"后缀)
### 2. 修改用户定义属性
#### 查找现有属性索引
```csharp
private static int FindUserDefinedPropertyIndex(ComApi.InwGUIPropertyNode2 propertyNode,
string categoryName)
{
int index = 0;
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.UserDefined && attribute.ClassUserName == categoryName)
{
return index;
}
index++;
}
return -1; // 未找到
}
```
#### 修改现有属性
```csharp
public static void UpdateUserDefinedProperty(ModelItem item, string categoryName,
Dictionary newProperties)
{
ComApi.InwOpState10 state = ComApiBridge.State;
ComApi.InwOaPath3 path = (ComApi.InwOaPath3)ComApiBridge.ToInwOaPath(item);
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
// 查找现有属性类别的索引
int existingIndex = FindUserDefinedPropertyIndex(propertyNode, categoryName);
if (existingIndex >= 0)
{
// 创建新的属性集合
ComApi.InwOaPropertyVec newPropertyCategory = (ComApi.InwOaPropertyVec)state.ObjectFactory(
ComApi.nwEObjectType.eObjectType_nwOaPropertyVec, null, null);
// 添加新的属性值
foreach (var kvp in newProperties)
{
AddProperty(state, newPropertyCategory, kvp.Key, kvp.Value, kvp.Key + "_Internal");
}
// 使用现有索引覆盖属性类别
propertyNode.SetUserDefined(existingIndex, categoryName,
categoryName + "_Internal", newPropertyCategory);
}
else
{
// 如果不存在,则创建新的
CreateUserDefinedProperty(item, categoryName, newProperties);
}
}
```
#### 关键要点
- **使用现有索引**: 修改时必须使用找到的现有索引,而不是0
- **完全覆盖**: `SetUserDefined`会完全替换现有属性,而不是增量更新
### 3. 删除用户定义属性
#### 计算相对索引
```csharp
private static int GetUserDefinedPropertyRelativeIndex(ComApi.InwGUIPropertyNode2 propertyNode,
string categoryName)
{
int relativeIndex = 0;
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.UserDefined)
{
if (attribute.ClassUserName == categoryName)
{
return relativeIndex;
}
relativeIndex++;
}
}
return -1; // 未找到
}
```
#### 删除属性类别
```csharp
public static void RemoveUserDefinedProperty(ModelItem item, string categoryName)
{
ComApi.InwOpState10 state = ComApiBridge.State;
ComApi.InwOaPath3 path = (ComApi.InwOaPath3)ComApiBridge.ToInwOaPath(item);
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
// 计算用户定义属性的相对索引
int relativeIndex = GetUserDefinedPropertyRelativeIndex(propertyNode, categoryName);
if (relativeIndex >= 0)
{
// 用户定义属性索引从1开始,所以需要+1
int userDefinedIndex = relativeIndex + 1;
// 使用RemoveUserDefined方法完全删除属性类别
propertyNode.RemoveUserDefined(userDefinedIndex);
}
}
```
#### 关键要点
- **索引从1开始**: 用户定义属性的索引从1开始,不是0
- **相对索引**: 需要计算在用户定义属性中的相对位置
- **完全删除**: `RemoveUserDefined`会完全移除属性类别,不留痕迹
### 4. 读取属性值
#### 使用COM API读取
```csharp
public static string GetPropertyValueViaCom(ModelItem item, string categoryName,
string propertyName)
{
try
{
ComApi.InwOpState10 state = ComApiBridge.State;
ComApi.InwOaPath3 path = (ComApi.InwOaPath3)ComApiBridge.ToInwOaPath(item);
ComApi.InwGUIPropertyNode2 propertyNode =
(ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(path, false);
// 遍历属性类别
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.UserDefined && attribute.ClassUserName == categoryName)
{
// 遍历类别中的属性
foreach (ComApi.InwOaProperty property in attribute.Properties())
{
if (property.UserName == propertyName)
{
return property.value?.ToString() ?? "";
}
}
}
}
return ""; // 未找到
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取属性失败: {ex.Message}");
return "";
}
}
```
## 常见问题与解决方案
### 1. 属性缓存同步问题
**问题**: COM API写入属性后,.NET API读取到的仍是旧值。
**解决方案**:
```csharp
// 强制刷新文档状态
private static void ForceDocumentRefresh()
{
try
{
var doc = Application.ActiveDocument;
if (doc != null)
{
// 强制刷新文档状态
doc.CurrentSelection.Clear();
Application.DoEvents();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"文档刷新失败: {ex.Message}");
}
}
```
### 2. 索引计算错误
**问题**: 使用错误的索引导致操作失败。
**解决方案**:
- 创建新属性:使用 `index = 0`
- 修改现有属性:使用绝对索引(从0开始计数所有属性)
- 删除属性:使用相对索引+1(在用户定义属性中的位置+1)
### 3. 重复属性类别
**问题**: 每次操作都创建新的属性类别。
**解决方案**:
```csharp
// 始终先检查是否存在现有属性
int existingIndex = FindUserDefinedPropertyIndex(propertyNode, categoryName);
if (existingIndex >= 0)
{
// 修改现有属性
propertyNode.SetUserDefined(existingIndex, categoryName, internalName, newProperties);
}
else
{
// 创建新属性
propertyNode.SetUserDefined(0, categoryName, internalName, newProperties);
}
```
## 最佳实践
### 1. 错误处理
```csharp
try
{
// COM API 操作
}
catch (System.Runtime.InteropServices.COMException comEx)
{
// 处理COM特定错误
LogManager.WriteLog($"COM API错误: {comEx.Message}");
}
catch (Exception ex)
{
// 处理一般错误
LogManager.WriteLog($"操作失败: {ex.Message}");
}
```
### 2. 日志记录
```csharp
// 详细的操作日志
LogManager.WriteLog($"[属性操作] 开始为 {items.Count} 个模型设置属性");
LogManager.WriteLog($"[属性操作] 类别名称: {categoryName}");
LogManager.WriteLog($"[属性操作] 操作完成,成功: {successCount}, 失败: {failCount}");
```
### 3. 批量操作
```csharp
public static int BatchUpdateProperties(ModelItemCollection items,
string categoryName, Dictionary properties)
{
int successCount = 0;
foreach (ModelItem item in items)
{
try
{
UpdateUserDefinedProperty(item, categoryName, properties);
successCount++;
}
catch (Exception ex)
{
LogManager.WriteLog($"处理模型 {item.DisplayName} 时失败: {ex.Message}");
}
}
return successCount;
}
```
### 4. 内存管理
```csharp
// COM对象使用完毕后释放引用
ComApi.InwOaPropertyVec propertyCategory = null;
ComApi.InwOaProperty property = null;
try
{
// 使用COM对象
}
finally
{
// 释放COM对象引用
if (propertyCategory != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(propertyCategory);
}
if (property != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(property);
}
}
```
## API 索引机制总结
| 操作类型 | 索引类型 | 索引值 | 说明 |
|---------|---------|--------|------|
| 创建新属性 | 固定值 | 0 | 始终使用0创建新的用户定义属性 |
| 修改现有属性 | 绝对索引 | 0-n | 在所有属性中的位置(包括系统属性) |
| 删除属性 | 相对索引+1 | 1-n | 在用户定义属性中的位置+1 |
## 参考资源
### 官方文档
- Navisworks 2017 SDK 文档(安装目录下的api/documentation文件夹)
- Autodesk Developer Network (ADN)
### 社区资源
- [Autodesk论坛 - Navisworks API](https://forums.autodesk.com/t5/navisworks-api/bd-p/95)
- [TwentyTwo博客 - Navisworks API教程](https://twentytwo.space/)
### 实用工具
- Navisworks Object Model Browser(查看对象结构)
- Visual Studio 调试工具(COM对象检查)
---
## 更新日志
### v1.0 (2025-06-22)
- 初始版本,基于物流属性管理功能开发经验
- 涵盖COM API属性操作的核心方法
- 包含实际项目中遇到的问题和解决方案
---
**注意**: 本指南将持续更新,补充更多API用法和最佳实践。欢迎贡献新的内容和改进建议。