# 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用法和最佳实践。欢迎贡献新的内容和改进建议。 