62 KiB
Navisworks API 使用方法指南
基于真实官方示例的正确API用法总结
在线资源链接
- Autodesk Platform Services (APS) - 主要开发者门户:https://aps.autodesk.com/developer/overview/navisworks - 提供Navisworks集成工具和SDK
- AEC DevBlog - 官方开发博客:https://adndevblog.typepad.com/aec/navisworks/ - 包含2026版本新功能和技术文章
- Autodesk Developer Network - 开发者网络:https://www.autodesk.com/developer-network/app-store/navisworks - 提供开发资源和支持
- 非官方在线API文档 - ApiDocs.co:https://apidocs.co/apps/navisworks/ - (注:目前仅覆盖2017-2018版本)
参考示例来源
基于以下官方示例文件的真实API用法:
C:\Users\Tellme\apps\NavisworksTransport\doc\navisworks_api\NET\examples\PlugIns\SearchComparisonPlugIn\SearchComparisonPlugIn.csC:\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)- 导出NWDCurrentSelection- 当前选择Models- 模型集合
Models 常用方法
SetHidden(ModelItemCollection items, bool hidden)- 设置隐藏状态SetRequired(ModelItemCollection items, bool required)- 设置必需状态RootItemDescendantsAndSelf- 所有根项目的后代
10. 错误避免指南
- 线程安全是第一要务:所有 Navisworks API 调用必须在主 UI 线程中执行
- 不要使用不存在的API:如
SearchCondition.HasAncestor - 避免深度递归:使用内置的
DescendantsAndSelf代替手写递归 - 批量操作:使用
ModelItemCollection进行批量设置,而不是逐个操作 - 正确的命名空间:确保引用
using Autodesk.Navisworks.Api; - 异常处理:文件操作和API调用要适当处理异常
- 资源清理:隐藏操作后要恢复原始状态
- 线程状态检查:在关键操作前验证线程状态(STA)
- 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 最佳实践
-
选择合适的恢复方式:
- 动画系统:使用
OverridePermanentTransform+ 原始Transform - 用户操作:使用
ResetPermanentTransform直接重置
- 动画系统:使用
-
避免不必要的Transform记录:
- 如果只需要恢复到设计文件原始位置,使用
ResetPermanentTransform - 只有需要恢复到特定中间状态时才记录Transform
- 如果只需要恢复到设计文件原始位置,使用
-
理解增量vs绝对变换:
OverridePermanentTransform是增量的,会与现有变换叠加ResetPermanentTransform是绝对的,清除所有变换
-
线程安全:
- 所有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", "自定义值");
修改现有自定义属性
// ✅ 正确的修改方法(基于实际修复经验和官方AutoUserPropsExample示例)
private void UpdateCustomProperty(string categoryName, string internalName, string newValue)
{
InwOpSelection2 selection = m_state.CurrentSelection as InwOpSelection2;
if (selection.Paths().Count > 0)
{
InwGUIPropertyNode2 propertyNode =
m_state.GetGUIPropertyNode(selection.Paths()[1], true) as InwGUIPropertyNode2;
// 🔍 关键第一步:查找现有属性分类的正确索引
int existingIndex = GetFloorAttributeIndex(propertyNode, categoryName);
// 创建新的属性内容
InwOaPropertyVec propertyVector =
m_state.ObjectFactory(nwEObjectType.eObjectType_nwOaPropertyVec);
InwOaProperty property =
m_state.ObjectFactory(nwEObjectType.eObjectType_nwOaProperty);
property.name = "Floor_Level";
property.UserName = "楼层";
property.value = newValue;
propertyVector.Properties().Add(property);
if (existingIndex >= 0)
{
// 🎯 存在属性时:使用正确的索引进行更新
// 注意:SetUserDefined的索引是从1开始的
int updateIndex = existingIndex + 1;
propertyNode.SetUserDefined(updateIndex, categoryName, internalName, propertyVector);
}
else
{
// 🆕 不存在属性时:使用索引0创建新属性分类
propertyNode.SetUserDefined(0, categoryName, internalName, propertyVector);
}
}
}
// ✅ 查找现有属性分类索引的正确方法
private int GetFloorAttributeIndex(InwGUIPropertyNode2 propertyNode, string categoryName)
{
int userDefinedIndex = 0; // 用户定义属性的索引计数器
foreach (InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.UserDefined)
{
// ⚠️ 关键修复:只需要匹配ClassUserName,不需要匹配ClassName
// ClassName是系统生成的(如"LcOaPropOverrideCat"),不是我们控制的
if (attribute.ClassUserName == categoryName)
{
return userDefinedIndex; // 返回在用户定义属性中的索引位置
}
userDefinedIndex++;
}
}
return -1; // 未找到返回-1
}
// ✅ 使用示例 - 正确的更新方式
// 第一次设置:创建新属性
UpdateCustomProperty("分层信息", "Floor_Category", "F1");
// 第二次设置:找到并更新现有属性
UpdateCustomProperty("分层信息", "Floor_Category", "F2");
// 第三次设置:继续更新同一个属性
UpdateCustomProperty("分层信息", "Floor_Category", "F3");
关键要点(基于实际修复经验):
- 动态索引查找:不能使用硬编码索引,必须动态查找现有属性的位置
- 只匹配ClassUserName:
ClassName是系统生成的标识符(如LcOaPropOverrideCat),我们无法控制 - 索引转换规则:查找返回的是0基索引,
SetUserDefined使用的是1基索引 - 创建vs更新:index=0表示创建新属性,index>0表示更新指定位置的属性
删除自定义属性
// ✅ 删除指定的自定义属性分类
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 属性操作注意事项
- 线程安全:所有属性操作都必须在主UI线程中执行
- 选择状态:COM API属性操作需要先选中目标对象
- 性能考虑:大批量属性读取时,NET API性能更佳
- 属性持久化:自定义属性会保存在NWD文件中,标准属性覆盖是临时的
- 属性索引:COM API中的用户自定义属性使用索引管理
- 错误处理:属性不存在时API会返回null,需要检查
- 属性类型限制:只读属性无法通过API修改,只能通过可编辑API间接影响
- 覆盖vs原始:OverridePermanent系列方法是覆盖原始属性,可以恢复
12.8 COM API 自定义属性重要修复经验
基于实际生产环境中发现的问题和修复经验,这里记录COM API操作自定义属性时的关键要点。
🚨 常见陷阱:重复创建属性分类
问题现象:对同一对象多次设置自定义属性时,会创建多个同名的属性分类,而不是更新现有属性。
错误做法:
// ❌ 错误:硬编码索引会导致重复创建
propertyNode.SetUserDefined(1, "分层信息", "Floor_Category", propertyVector);
正确做法:
// ✅ 正确:动态查找现有属性的索引
int existingIndex = GetFloorAttributeIndex(propertyNode, "分层信息");
if (existingIndex >= 0)
{
// 更新现有属性(注意索引转换:查找用0基,设置用1基)
propertyNode.SetUserDefined(existingIndex + 1, "分层信息", "Floor_Category", propertyVector);
}
else
{
// 创建新属性
propertyNode.SetUserDefined(0, "分层信息", "Floor_Category", propertyVector);
}
🔍 ClassName vs ClassUserName 的区别
这是导致重复创建问题的根本原因:
ClassUserName:用户定义的显示名称,由我们控制
// 我们定义的显示名称
attribute.ClassUserName == "分层信息"
ClassName:系统生成的内部标识符,我们无法控制
// 系统生成的内部名称,每次可能不同
attribute.ClassName == "LcOaPropOverrideCat" // 系统生成,不可预测
修复要点:
// ❌ 错误:同时匹配两个条件会导致找不到现有属性
if (attribute.UserDefined &&
attribute.ClassUserName == FLOOR_CATEGORY &&
attribute.ClassName == FLOOR_CATEGORY_INTERNAL)
// ✅ 正确:只匹配用户显示名称
if (attribute.UserDefined &&
attribute.ClassUserName == FLOOR_CATEGORY)
📝 索引管理的正确方式
索引计算规则:
- 遍历所有属性,只计算
UserDefined == true的属性 - 查找方法返回的是0基索引(第一个用户属性是0)
SetUserDefined方法使用的是1基索引(第一个用户属性是1)RemoveUserDefined方法也使用1基索引
// ✅ 正确的索引转换
int foundIndex = GetFloorAttributeIndex(propertyNode, categoryName); // 返回0, 1, 2...
if (foundIndex >= 0)
{
// SetUserDefined 需要1基索引
int setIndex = foundIndex + 1; // 转换为 1, 2, 3...
propertyNode.SetUserDefined(setIndex, categoryName, internalName, propertyVector);
// RemoveUserDefined 也需要1基索引
int removeIndex = foundIndex + 1; // 转换为 1, 2, 3...
propertyNode.RemoveUserDefined(removeIndex);
}
🛠️ 完整的修复模板
public class CustomPropertyManager
{
// ✅ 设置自定义属性的正确方式
public bool SetCustomProperty(ModelItem item, string categoryName,
string internalName, string propertyName,
string displayName, string value)
{
return ExecuteWithUIThread(() =>
{
var state = ComApiBridge.State;
var comPath = ComApiBridge.ToInwOaPath(item);
var propertyNode = (ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
// 🔍 关键:动态查找现有属性索引
int existingIndex = FindCustomPropertyIndex(propertyNode, categoryName);
// 创建属性内容
var propertyCategory = (ComApi.InwOaPropertyVec)state.ObjectFactory(
ComApi.nwEObjectType.eObjectType_nwOaPropertyVec, null, null);
var property = (ComApi.InwOaProperty)state.ObjectFactory(
ComApi.nwEObjectType.eObjectType_nwOaProperty, null, null);
property.name = propertyName;
property.UserName = displayName;
property.value = value;
propertyCategory.Properties().Add(property);
if (existingIndex >= 0)
{
// 🎯 更新现有属性(索引+1)
propertyNode.SetUserDefined(existingIndex + 1, categoryName, internalName, propertyCategory);
LogManager.Info($"更新现有属性分类 '{categoryName}' (index={existingIndex + 1})");
}
else
{
// 🆕 创建新属性(索引0)
propertyNode.SetUserDefined(0, categoryName, internalName, propertyCategory);
LogManager.Info($"创建新属性分类 '{categoryName}' (index=0)");
}
return true;
});
}
// ✅ 查找自定义属性索引的正确方式
private int FindCustomPropertyIndex(ComApi.InwGUIPropertyNode2 propertyNode, string categoryName)
{
int userDefinedIndex = 0;
foreach (ComApi.InwGUIAttribute2 attribute in propertyNode.GUIAttributes())
{
if (attribute.UserDefined)
{
// ⚠️ 关键:只匹配ClassUserName,不匹配ClassName
if (attribute.ClassUserName == categoryName)
{
return userDefinedIndex;
}
userDefinedIndex++;
}
}
return -1;
}
// ✅ 清除自定义属性的正确方式
public bool ClearCustomProperty(ModelItem item, string categoryName)
{
return ExecuteWithUIThread(() =>
{
var state = ComApiBridge.State;
var comPath = ComApiBridge.ToInwOaPath(item);
var propertyNode = (ComApi.InwGUIPropertyNode2)state.GetGUIPropertyNode(comPath, false);
int existingIndex = FindCustomPropertyIndex(propertyNode, categoryName);
if (existingIndex >= 0)
{
// 🗑️ 删除属性(索引+1)
propertyNode.RemoveUserDefined(existingIndex + 1);
LogManager.Info($"删除属性分类 '{categoryName}' (removed index={existingIndex + 1})");
return true;
}
else
{
LogManager.Info($"属性分类 '{categoryName}' 不存在,无需删除");
return false;
}
});
}
}
📋 问题诊断检查列表
当遇到属性重复创建问题时,检查以下要点:
- 是否使用了硬编码索引(如固定使用索引1)
- 是否同时匹配了
ClassUserName和ClassName - 是否正确进行了索引转换(0基→1基)
- 是否在主UI线程中执行COM API调用
- 日志中是否显示"未找到现有属性分类"但实际存在
🎯 修复验证方法
测试步骤:
- 选择一个对象,设置自定义属性(如"F1")
- 再次选择同一对象,设置不同值(如"F2")
- 检查属性面板,应该只有一个属性分类
- 重复步骤2,设置第三个值(如"F3")
- 确认仍然只有一个属性分类,值为最新设置的值
日志验证:
[FloorAttributeManager] 找到楼层属性分类,索引为: 0
[FloorAttributeManager] ✅ 成功更新现有楼层属性分类 (index=1)
💡 经验总结
- COM API的ClassUserName是关键:这是我们控制的显示名称,用于匹配现有属性
- ClassName不可靠:系统生成的内部标识符,每次可能不同
- 索引转换至关重要:查找用0基,设置/删除用1基
- 动态索引查找必不可少:硬编码索引是重复创建问题的根源
- 详细日志帮助调试:记录索引查找和转换过程,便于问题定位
13. 缓存刷新和状态同步 ⚠️ 重要
13.1 缓存刷新的必要性
在某些API操作(特别是自定义属性的删除)后,Navisworks内部缓存可能与实际状态不同步,导致:
- 搜索结果不准确
- 属性面板显示异常
- 后续API调用出现意外行为
13.2 安全的缓存刷新方法 ✅
基于实际生产环境的测试和验证,以下是推荐的缓存刷新方法:
// ✅ 最安全的缓存刷新方法:空集合的SetHidden操作
public static void SafeCacheRefresh()
{
try
{
var document = NavisApplication.ActiveDocument;
if (document?.Models != null)
{
// 使用空集合的SetHidden操作触发缓存更新
// 这是一个几乎零开销的"伪操作"
var emptyCollection = new ModelItemCollection();
document.Models.SetHidden(emptyCollection, false);
LogManager.WriteLog("✅ 已执行轻量级缓存刷新");
}
}
catch (Exception ex)
{
LogManager.WriteLog($"⚠️ 缓存刷新失败,但不影响主要操作: {ex.Message}");
}
}
13.3 危险的缓存刷新方法 ❌
⚠️ 避免使用以下方法,已确认会导致程序崩溃:
// ❌ 危险:会导致程序崩溃
document.Models.ResetAllTemporaryMaterials();
// ❌ 危险:重载过重,可能影响性能
document.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
// ❌ 危险:可能干扰用户界面状态
document.Models.ResetOverriddenTransparency(allItems);
13.4 何时需要缓存刷新
必须刷新的操作:
- COM API删除自定义属性后
- 大批量修改模型可见性后
- 复杂的Transform操作后
可选刷新的操作:
- 添加自定义属性(通常自动同步)
- 简单的颜色或透明度覆盖
- 单个对象的操作
13.5 实践模式:操作后刷新
// ✅ 推荐模式:操作 + 刷新 + 验证
public int RemoveLogisticsAttributes(ModelItemCollection items)
{
int successCount = 0;
try
{
// 1. 执行主要操作
foreach (var item in items)
{
// COM API删除操作...
successCount++;
}
}
catch (Exception ex)
{
LogManager.WriteLog($"操作失败: {ex.Message}");
}
// 2. 如果有成功操作,执行缓存刷新
if (successCount > 0)
{
SafeCacheRefresh(); // 使用安全的刷新方法
}
return successCount;
}
13.6 缓存问题的诊断
常见症状:
- 删除属性后,搜索仍能找到已删除的属性
- 属性面板显示的内容与API返回不一致
- 连续相同操作的结果不同
诊断代码:
// ✅ 验证缓存同步状态
public bool VerifyCacheSync(ModelItem item, string categoryName)
{
// 通过NET API检查
bool netApiResult = HasPropertyCategory(item, categoryName);
// 通过COM API检查
bool comApiResult = HasPropertyCategoryViaCom(item, categoryName);
if (netApiResult != comApiResult)
{
LogManager.Warning($"缓存不同步检测: NET={netApiResult}, COM={comApiResult}");
SafeCacheRefresh();
return false;
}
return true;
}
13.7 多线程环境下的缓存刷新
// ✅ 线程安全的缓存刷新
public async Task SafeCacheRefreshAsync()
{
await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
{
SafeCacheRefresh(); // 确保在主线程中执行
});
}
13.8 缓存刷新最佳实践
| 场景 | 刷新时机 | 刷新方法 | 必要性 |
|---|---|---|---|
| 删除自定义属性 | 操作完成后 | 空集合SetHidden | 必须 |
| 批量隐藏对象 | 操作完成后 | 可选择不刷新 | 可选 |
| Transform动画 | 动画结束后 | 空集合SetHidden | 推荐 |
| 颜色覆盖 | 通常不需要 | 无 | 不需要 |
关键原则:
- 安全第一:只使用验证过的安全方法
- 按需刷新:不是所有操作都需要缓存刷新
- 异常处理:缓存刷新失败不应该影响主要功能
- 线程安全:在主UI线程中执行刷新操作
14. 参考官方示例
强烈建议查看以下官方示例了解更多用法:
SearchComparisonPlugIn.cs- 搜索性能对比和属性搜索Examiner.cs- LINQ查询和属性过滤示例AutoUserPropsExample.cs- COM API自定义属性操作BasicDockPanePlugin.cs- 基础插件结构DatabaseDockPane/Models.cs- 数据库操作示例ClashDetective相关示例 - 高级功能示例
碰撞检测模式说明
Hard(硬碰撞)
-
检测模型几何体(多边形、线条和/或点)之间发生碰撞的情况,碰撞距离大于设定的容差值
HardConservative(保守硬碰撞)
-
与Hard模式相同,但额外尝试检测那些多边形、线条和点虽然不相交,但它们所代表的体积可能重叠的情况
-
举例:两个相同大小的立方体,方向相同,沿其中一个面的垂直方向偏移很短的距离。在这种情况下,体积明显重 叠,但没有三角形或边缘彼此穿过
-
所有"碰撞"都属于以下情况之一:
- (i) 面仅仅接触另一个面的边缘
- (ii) 共面的面在2D空间中重叠,但在3D中没有相交
- (iii) 边仅仅接触另一个边的端点
- (iv) 共轴的边在1D中重叠,但同样不是以3D方式检测到的
-
HardConservative碰撞检测增加了检测这些复杂情况的逻辑,但可能会产生误报
Clearance(间隙检测)
-
检测几何体之间的最小分离距离小于指定容差的情况
-
例如,可用于检测是否有足够空间放置CAD文件中未建模的保温材料
Duplicate(重复检测)
-
检测模型中完全相同的几何体出现多次的情况
-
当合并包含相同第三方文件的两个文件时可能发生这种情况
-
超出容差范围的重复几何体被视为不同对象,不会被报告
这些不同的检测模式为不同的工程应用场景提供了精确的碰撞分析能力。
容差值说明
对于Hard(硬碰撞)和HardConservative(保守硬碰撞)测试
- 容差值是在干涉被认定为碰撞之前允许的重叠程度
- 这允许您忽略数值近似问题和可以在现场解决的轻微碰撞
对于Clearance(间隙检测)测试
- 容差值是所需的最小间隙距离,低于此值的分离将被视为碰撞
对于Duplicate(重复检测)测试
- 容差值是相同几何体被视为重复的最大距离
- 超出此距离的物体将被忽略,理论上它们虽然相同但是真正独立的实例
简单来说:
- Hard/HardConservative:容差 = 允许的最大重叠距离
- Clearance:容差 = 要求的最小间隙距离
- Duplicate:容差 = 识别重复的最大距离
这种设计让用户可以根据实际工程需求调整检测的敏感度。
设置几何类型:包含面、线和点以获得最全面的碰撞检测
// 创建临时测试以获取真实碰撞结果
var tempTestName = $"临时测试_{resultCount}_{DateTime.Now:HHmmss_fff}";
var tempTest = new ClashTest
{
DisplayName = tempTestName,
TestType = ClashTestType.HardConservative,
Tolerance = detectionGap,
Guid = Guid.Empty,
MergeComposites = true
};
collisionTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
collisionTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
ModelGeometry 属性
ModelGeometry类公开了以下成员:
属性
| 名称 | 说明 |
|---|---|
| ActiveColor | 此几何体的当前(可见)颜色 |
| ActiveTransform | 返回几何体当前活动的变换矩阵 |
| ActiveTransparency | 此几何体的当前(可见)透明度 |
| BoundingBox | 此几何体在世界坐标系中的包围盒 |
| FragmentCount | 此几何体被分割成的片段数量 |
| IsDisposed | 获取一个值,指示对象是否已被释放且不再可用(继承自NativeHandle) |
| IsReadOnly | 是否只读(重写NativeHandle的IsReadOnly属性) |
| IsSolid | 此几何体是否为实体?(包括所有形成封闭、流形外壳的三角形图元) |
| Item | 模型层次结构中对应此几何体的项目 |
| OriginalColor | 此几何体的原始颜色(设计文件中指定的) |
| OriginalTransform | 返回几何体加载时的原始变换矩阵 |
| OriginalTransparency | 此几何体的原始透明度(设计文件中指定的) |
| PermanentColor | 几何体的永久颜色。可能是原始颜色或用户明确覆盖的颜色 |
| PermanentOverrideTransform | 应用于模型几何体原始变换的覆盖变换 |
| PermanentTransform | 模型几何体的永久变换。由原始变换与覆盖变换组合形成的变换 |
| PermanentTransparency | 几何体的永久透明度。可能是原始透明度或用户明确覆盖的透明度 |
| PrimitiveCount | 定义此几何体的图元(三角形、线、点)数量 |
| PrimitiveTypes | 用于定义此几何体的图元类型 |
这些属性提供了访问和查询Navisworks模型几何体各种状态信息的接口,包括颜色、变换、透明度、包围盒和几何体结构等重要属性。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
using System.Text;
using Autodesk.Navisworks.Api.Controls;
static public void OutputFirstGeometry()
{
try
{
if (Autodesk.Navisworks.Api.Application.ActiveDocument != null &&
!Autodesk.Navisworks.Api.Application.ActiveDocument.IsClear)
{
ModelGeometry first =
Autodesk.Navisworks.Api.Application.ActiveDocument.
Models[0].RootItem.FindFirstGeometry();
if (first != null)
{
string text = string.Empty;
text = string.Format("ActiveColor = {0}" +
"\nActiveTransparency = {1}" +
"\nBoundingBox = {2}" +
"\nFragmentCount {3}" +
"\nIsSolid {4}" +
"\nItem {5}" +
"\nOriginalColor {6}" +
"\nOriginalTransparency {7}" +
"\nPermanentColor {8}" +
"\nPermanentTransparency {9}",
first.ActiveColor.ToString(),
first.ActiveTransparency,
first.BoundingBox.ToString(),
first.FragmentCount,
first.IsSolid.ToString(),
first.Item.ToString(),
first.OriginalColor.ToString(),
first.OriginalTransparency,
first.PermanentColor.ToString(),
first.PermanentTransparency);
MessageBox.Show(text);
}
}
}
catch (Exception e)
{
MessageBox.Show(e.Message + "\n\n" + e.ToString());
}
}
Tool Enumeration
● Tool 枚举值
| 成员名称 | 描述 |
|---|---|
| None | 无活动工具 |
| Select | 选择工具 |
| SelectBox | 框选工具 |
| RedlineFreehand | 红线手绘工具 |
| RedlineLine | 红线直线工具 |
| RedlineEllipse | 红线椭圆工具 |
| RedlineCloud | 红线云形标注工具 |
| RedlineLineString | 红线连续线工具 |
| RedlineTag | 红线标签工具 |
| RedlineText | 红线文本工具 |
| RedlineErase | 红线擦除工具 |
| RedlineArrow | 红线箭头工具 |
| MeasurePointToPoint | 点到点测量工具 |
| MeasurePointToMultiplePoints | 点到多点测量工具 |
| MeasurePointLine | 点线测量工具 |
| MeasureAccumulate | 累积测量工具 |
| MeasureAngle | 角度测量工具 |
| MeasureArea | 面积测量工具 |
| MeasureSingle | 单点测量工具 |
| BasicViewObjectWheel | 基础视图对象导航轮 |
| BasicTourBuildingWheel | 基础建筑巡游导航轮 |
| FullNavigationWheel | 完整导航轮 |
| MiniViewObjectWheel | 迷你视图对象导航轮 |
| MiniTourBuildingWheel | 迷你建筑巡游导航轮 |
| MiniFullNavigationWheel | 迷你完整导航轮 |
| Full2DNavigationWheel | 完整2D导航轮 |
| CommonPan | 通用平移(所有Autodesk产品通用) |
| CommonZoom | 通用缩放(所有Autodesk产品通用) |
| CommonZoomWindow | 通用窗口缩放(所有Autodesk产品通用) |
| CommonOrbit | 通用轨道(所有Autodesk产品通用) |
| CommonFreeOrbit | 通用自由轨道(所有Autodesk产品通用) |
| CommonConstrainedOrbit | 通用约束轨道(所有Autodesk产品通用) |
| CommonLookAt | 通用看向(所有Autodesk产品通用) |
| CommonLookAround | 通用环视(所有Autodesk产品通用) |
| CommonWalk | 通用漫游(所有Autodesk产品通用) |
| CommonCenter | 通用居中(所有Autodesk产品通用) |
| NavigateFixed | 固定相机位置 |
| NavigateFreeLookAround | 经典Navisworks自由环视(旋转) |
| NavigateFreeOrbit | 经典Navisworks自由轨道(检查) |
| NavigateWalk | 经典Navisworks漫游 |
| NavigateFly | 经典Navisworks飞行 |
| NavigateConstrainedOrbit | 经典Navisworks约束轨道(转盘) |
| NavigateZoom | 经典Navisworks缩放 |
| NavigatePan | 经典Navisworks平移 |
| NavigateConstrainedPan | 经典Navisworks约束平移 |
| NavigateLookAround | 经典Navisworks环视(旋转) |
| NavigateOrbit | 经典Navisworks轨道 |
| NavigateZoomWindow | 经典Navisworks窗口缩放(缩放框) |
| CustomToolPlugin | 由ToolPlugin提供的自定义功能 |
这个枚举列出了Navisworks中所有可用的工具类型,包括导航工具、测量工具、标注工具等。其中CustomToolPlugin是用于自定 义工具插件的特殊值。
Cursor枚举成员
| 成员名称 | 描述 |
|---|---|
| Unhandled | 特殊情况。如果已经处理则由GetCursor()返回 |
| Handled | 已处理光标 |
| Walk | 行走光标 |
| Fly | 飞行光标 |
| Orbit | 环绕光标 |
| Swivel | 旋转光标 |
| Examine | 检查光标 |
| Pan | 平移光标 |
| Zoom | 缩放光标 |
| Turntable | 转盘光标 |
| Focus | 聚焦光标 |
| Application | 应用程序光标 |
| ZoomBox | 缩放框光标 |
| Measure | 测量光标(十字线) |
| HyperHand | 超级手势光标 |
| PanWorld | 世界平移光标 |
| Roll | 滚动光标 |
| Stop | 停止光标 |
| MeasureEdge | 测量边缘光标 |
| MeasureVertex | 测量顶点光标 |
| Redline | 红线光标 |
| Erase | 擦除光标 |
| Wheel | 滚轮光标 |
| MarkupSelection | 标记选择光标 |
| MarkupSnapping | 标记捕捉光标 |
| MarkupEraser | 标记擦除光标 |
| MarkupQuickPick | 标记快速选择光标 |
| MarkupBucket | 标记桶光标 |
| MarkupAutoPoly | 标记自动多边形光标 |
使用方式:
public class PathClickToolPlugin : ToolPlugin
{
....
/// <summary>
/// 重写光标样式,使用捕捉光标提供更好的视觉反馈
/// </summary>
/// <param name="view">当前视图</param>
/// <param name="modifier">键盘修饰键</param>
/// <returns>返回捕捉光标</returns>
public override Cursor GetCursor(View view, KeyModifiers modifier)
{
// 使用捕捉光标,提供更好的视觉反馈用于精确路径点选择
return Cursor.MarkupSnapping;
}
}
只显示选中项
Examples
CopyHide the unselected ModelItems
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
using System.Text;
using Autodesk.Navisworks.Api.Controls;
static public void HideUnselected()
{
//Create hidden collection
ModelItemCollection hidden = new ModelItemCollection();
//create a store for the visible items
ModelItemCollection visible = new ModelItemCollection();
//Add all the items that are visible to the visible collection
foreach (ModelItem item in Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentSelection.SelectedItems)
{
if (item.AncestorsAndSelf != null)
visible.AddRange(item.AncestorsAndSelf);
if (item.Descendants != null)
visible.AddRange(item.Descendants);
}
//mark as invisible all the siblings of the visible items as well as the visible items
foreach (ModelItem toShow in visible)
{
if (toShow.Parent != null)
{
hidden.AddRange(toShow.Parent.Children);
}
}
//remove the visible items from the collection
foreach (ModelItem toShow in visible)
{
hidden.Remove(toShow);
}
//hide the remaining items
Autodesk.Navisworks.Api.Application.ActiveDocument.Models.
SetHidden(hidden, true);
}