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

32 KiB
Raw Blame History

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 正确的遍历方式

// ✅ 正确:获取所有模型项
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 获取子节点

// ✅ 正确:获取某个节点的所有后代
var childItems = selectedItem.DescendantsAndSelf.Where(x => x != selectedItem);

// ✅ 正确:只获取直接子节点
foreach (ModelItem child in parentItem.Children)
{
    // 处理直接子节点
}

1.3 遍历祖先节点

// ✅ 正确:向上遍历父节点链
var current = selectedItem.Parent;
while (current != null)
{
    // 处理祖先节点
    current = current.Parent;
}

2. 搜索和查询

2.1 使用LINQ查询

// ✅ 正确使用LINQ查询模型项
IEnumerable<ModelItem> results = 
    Application.ActiveDocument.Models.RootItemDescendantsAndSelf
    .Where(x => 
        x.HasGeometry && 
        !x.IsHidden &&
        x.ClassDisplayName.ToLower().Contains("wall"));

2.2 使用Search类

// ✅ 正确使用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 迭代遍历(性能对比)

// ✅ 可用但性能较低:迭代方法
ModelItemCollection searchResults = new ModelItemCollection();
foreach (ModelItem modelItem in Application.ActiveDocument.Models.CreateCollectionFromRootItems().DescendantsAndSelf)
{
    if (modelItem.HasGeometry && !modelItem.IsHidden)
        searchResults.Add(modelItem);
}

3. 选择操作

3.1 操作当前选择

// ✅ 正确:获取当前选择
var currentSelection = document.CurrentSelection.SelectedItems;

// ✅ 正确:清空选择
document.CurrentSelection.Clear();

// ✅ 正确:添加到选择
document.CurrentSelection.Add(modelItem);

// ✅ 正确:复制集合到选择
document.CurrentSelection.CopyFrom(modelItems);

4. 可见性控制

4.1 隐藏和显示

// ✅ 正确:隐藏项目
ModelItemCollection itemsToHide = new ModelItemCollection();
itemsToHide.Add(modelItem);
document.Models.SetHidden(itemsToHide, true);

// ✅ 正确:显示项目
document.Models.SetHidden(itemsToHide, false);

// ✅ 正确:检查是否隐藏
if (modelItem.IsHidden)
{
    // 项目被隐藏
}

5. 文件导出

5.1 基本文件保存

// ✅ 正确保存NWD文件
document.SaveFile(filePath);

// ✅ 正确:指定版本保存
document.SaveFile(filePath, DocumentFileVersion.Current);

5.2 ExportToNwd API

// ✅ 正确使用ExportToNwd导出
var exportOptions = new NwdExportOptions();
exportOptions.ExcludeHiddenItems = true;  // 只导出可见项目
exportOptions.EmbedXrefs = false;
exportOptions.PreventObjectPropertyExport = false;

document.ExportToNwd(saveFilePath, exportOptions);

6. 性能最佳实践

6.1 避免的做法

// ❌ 错误使用不存在的API
// SearchCondition.HasAncestor(items) // 这个API不存在

// ❌ 错误:深度递归遍历
// void RecursiveTraversal(ModelItem item) // 大模型中可能导致堆栈溢出

6.2 推荐的做法

// ✅ 推荐使用内置的DescendantsAndSelf
var allDescendants = rootItem.DescendantsAndSelf;

// ✅ 推荐使用LINQ进行高效查询
var filteredItems = allItems.Where(x => x.HasGeometry);

// ✅ 推荐:批量操作而不是逐个操作
ModelItemCollection batchItems = new ModelItemCollection();
// 添加所有需要处理的项目
document.Models.SetHidden(batchItems, true); // 一次性操作

7. 完整示例:多选节点导出

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 线程)中执行

// ❌ 错误:在后台线程中调用 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 方法通过后台线程调用时崩溃

// ❌ 问题代码:导致崩溃
public bool ExportLayerToNwd(...)
{
    var document = NavisApplication.ActiveDocument;
    document.ExportToNwd(outputPath, exportOptions);  // 后台线程崩溃
}

修复方案:使用 Dispatcher.Invoke 包装所有 API 调用

// ✅ 修复代码:线程安全
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 线程安全检查和诊断

// ✅ 检查当前线程状态
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 最佳实践模式

// ✅ 推荐模式:安全的异步 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 操作的正确用法

// ✅ 获取物体的原始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管理

// 动画开始时记录原始位置
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用户手动位置恢复

public void RestoreToOriginalPosition(ModelItem selectedObject)
{
    // 不需要记录Transform直接重置到设计文件原始位置
    var doc = Application.ActiveDocument;
    var modelItems = new ModelItemCollection { selectedObject };
    
    // 清除所有增量变换,恢复到设计文件原始位置
    doc.Models.ResetPermanentTransform(modelItems);
}

11.5 常见Transform问题和解决方案

问题1位置恢复有偏移

// ❌ 错误:使用增量变换恢复位置
doc.Models.OverridePermanentTransform(modelItems, originalTransform, false);
// 问题:如果物体已经被移动过,这会导致累积偏移

// ✅ 正确:重置到原始位置
doc.Models.ResetPermanentTransform(modelItems);
// 结果:直接恢复到设计文件中的原始位置,无偏移

问题2动画结束后位置不准确

// ✅ 动画系统应该记录原始Transform并使用增量恢复
private Transform3D _originalTransform;

// 动画开始时
_originalTransform = animatedObject.Transform;

// 动画结束时恢复
doc.Models.OverridePermanentTransform(modelItems, _originalTransform, false);

问题3记录Transform但不使用

// ❌ 不必要记录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

// ✅ 通过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

// ✅ 使用预定义属性分类和属性名进行搜索
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);

属性分类和属性名常量

// ✅ 使用预定义常量访问标准属性
// PropertyCategoryNames 包含:
// - PropertyCategoryNames.Item (项目属性)
// - PropertyCategoryNames.Geometry (几何属性)
// - PropertyCategoryNames.Material (材质属性)

// DataPropertyNames 包含:
// - DataPropertyNames.ItemHidden (隐藏状态)
// - DataPropertyNames.ItemRequired (必需状态)
// 等等...

12.2 COM API 属性访问方法(基于 AutoUserPropsExample.cs

获取和遍历属性

// ✅ 获取选中对象的属性节点
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}");
        }
    }
}

添加自定义属性

// ✅ 创建新的自定义属性
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", "自定义值");

修改现有自定义属性

// ✅ 修改已存在的自定义属性值
private void UpdateCustomProperty(string categoryName, string internalName, string newValue)
{
    // 重新调用SetUserDefined即可覆盖现有值
    AddCustomProperty(categoryName, internalName, newValue);
}

删除自定义属性

// ✅ 删除指定的自定义属性分类
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 属性操作最佳实践

// ✅ 推荐模式结合两种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 常见属性操作示例

// ✅ 检查项目是否有特定属性分类
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编辑

// ✅ 可以修改:隐藏状态
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示例的确认

// 官方示例中的这些操作证实了这些属性是可修改的
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. 基础标识属性(只读)

// ❌ 只读无法修改由原始CAD文件决定
item.DisplayName        // 显示名称
item.ClassName          // 类名
item.ClassDisplayName   // 类显示名称
item.HasGeometry        // 几何体标志
item.IsInsert          // 插入对象标志
item.IsLayer           // 图层标志

2. 结构关系属性(只读)

// ❌ 只读结构关系由模型文件决定无法通过API修改
item.Parent            // 父节点
item.Children          // 子节点
item.Ancestors         // 祖先节点
item.Descendants       // 后代节点

3. 几何和材质属性(只读)

// ❌ 只读由原始CAD文件决定
item.Geometry          // 几何信息
item.BoundingBox()     // 包围盒
item.PropertyCategories // 标准属性分类但可通过COM API添加自定义分类

特殊情况:自定义属性完全可编辑

通过COM API可以完全控制自定义属性

// ✅ 完全可编辑:自定义属性
// 添加新的自定义属性
propertyNode.SetUserDefined(index, categoryName, internalName, propertyVector);

// 修改现有自定义属性(重新设置)
propertyNode.SetUserDefined(index, categoryName, internalName, newPropertyVector);

// 删除自定义属性
propertyNode.RemoveUserDefined(index);

物流插件的实际应用策略

1. 使用可编辑的标准属性进行状态管理

// 物流分类可见性管理
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. 使用自定义属性存储业务数据

// 存储物流分类信息
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. 只读属性用于查询和智能分析

// 基于只读属性进行智能空间分析
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. 综合应用示例:物流节点标记

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 相关示例 - 高级功能示例