431 lines
12 KiB
Markdown
431 lines
12 KiB
Markdown
# Navisworks 2017 API 使用指南
|
||
|
||
## 概述
|
||
|
||
本指南基于实际项目开发经验,汇总了Navisworks 2017 API的核心用法和最佳实践。重点覆盖COM API的属性操作功能,为插件开发提供实用的参考。
|
||
|
||
## 目录
|
||
|
||
1. [环境配置](#环境配置)
|
||
2. [COM API 基础](#com-api-基础)
|
||
3. [属性操作 API](#属性操作-api)
|
||
4. [常见问题与解决方案](#常见问题与解决方案)
|
||
5. [最佳实践](#最佳实践)
|
||
6. [参考资源](#参考资源)
|
||
|
||
## 环境配置
|
||
|
||
### 必需引用
|
||
```xml
|
||
<!-- 项目文件中的引用 -->
|
||
<Reference Include="Autodesk.Navisworks.Api" />
|
||
<Reference Include="Autodesk.Navisworks.ComApi" />
|
||
<Reference Include="Autodesk.Navisworks.Interop.ComApi" />
|
||
<Reference Include="Autodesk.Navisworks.Interop.ComApiAutomation" />
|
||
```
|
||
|
||
### 命名空间导入
|
||
```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<string, string> 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<string, string> 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<string, string> 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用法和最佳实践。欢迎贡献新的内容和改进建议。 |