☰
NXOpen二次开发:装配结构树遍历、高亮、统计与加载状态判断实战
2026/10/22 3:59:17 网站建设 项目流程

做装配类工具的时候,我接过一个需求:用户想在自己的对话框里看到整个装配的结构树,树上的每个节点对应一个组件,能点选、能高亮、能统计出现次数、能直接改颜色,还能看出哪些组件是未加载的。标题里那八个关键词,基本上就是把这种工具拆开之后的功能清单:遍历部件、填充树表创建节点、清理选择、高亮所选组件、改色、统计组件出现次数、节点和部件数据关联、判断组件加载状态。如果你也正在做NXOpen二次开发,尤其是装配结构管理、BOM统计、快速选件这类工具,这一篇可以帮你省掉不少翻API文档的时间。

我按自己实际做过的模拟项目X来拆解,把遍历、建树、关联、高亮、改色、计数、状态判断这些环节逐个讲清楚,顺便把踩过的坑一起倒出来。代码以C#为例,因为NXOpen的C# API写BlockUI对话框比较顺手,但原理和流程用C++也一样适用。

1. 装配结构遍历:先搞清楚数据模型再动手写树

1.1 Part、Component、Occurrence到底是什么关系

几乎所有装配遍历的坑,都源于对NX装配数据模型的理解不够。NX里一个“零件文件”叫Part,比如一个底板、一个电机、一块PCB。把一个Part放进某个装配,NX会生成一条Component记录。Component可以理解为“Part在装配里的一个引用位置”,同一个Part被放进装配10次,就有10条Component记录,它们的Tag各不相同,但Prototype()都指向同一个Part对象。

Occurrence这个概念更容易把人绕晕。Occurrence是组件在装配上下文中的实例表示,很多二次开发资料里把Component和Occurrence混着用,实际上Component对象就已经承载了“在哪一层、哪个位置被引用”的信息。你遍历装配时拿到的就是Component,不是Part。换句话说:Part是文件本体,Component是装配中的一次出现。想统计某个零件出现了几次,本质上就是统计指向同一个Part的Component有多少条。

我见过不少新手直接遍历workPart.Objects去抓组件,结果抓到一堆Part,跟装配树对不上号。正确入口永远是ComponentAssembly.GetComponents(),从顶层装配开始一层层往下走。

1.2 加载状态:不判断清楚,后面全白搭

组件加载状态是装配管理工具里最容易翻车的点。NX支持“轻量级装配”,装配文件里只记录组件引用关系,组件对应的Part文件可能完全没加载,也可能只加载了轻量级几何。Component.LoadStatus一般有三种:

  • FullyLoaded:完全加载,可以正常编辑、高亮、改色
  • PartiallyLoaded:部分加载,有轻量级显示数据,但完整几何没进内存
  • Unloaded:未加载,只有引用信息,连完整几何都没有

为什么要单独判断?因为改色、高亮、遍历面这些操作都依赖几何数据。对Unloaded组件直接调用SetColor,很多时候表面上不报错,但图上毫无变化;对PartiallyLoaded组件做遍历实体操作,可能会触发NX后台加载整个部件,大装配场景直接卡死几秒钟。

判断加载状态也有两条路。一是直接看Component.LoadStatus,这是最贴近用户直觉的;二是看原型部件Proto.IsFullyLoaded、Proto.IsUnloaded。两条路我都试过,结论是:界面上显示给用户看的状态,用Component.LoadStatus最准确;做几何操作前的二次确认,用Part层的IsFullyLoaded更保险。因为Component状态是装配层面的,Part状态是文件层面的,两者在部分加载时口径不完全一致。

1.3 递归遍历装配树的实现

遍历装配树的写法相当固定。从顶层workPart.ComponentAssembly()开始,取出所有子组件,逐个判断原型是否是子装配,如果是就继续深入。

private void WalkAssembly(ComponentAssembly parentAssy, Node parentNode) { Component[] comps = parentAssy.GetComponents(); foreach (Component comp in comps) { Part proto = comp.Prototype(); string stateText = GetLoadStatusText(comp); string nodeText = string.Format("{0} [{1}]", proto.Name, stateText); Node node = treeControl.CreateNode(nodeText); parentNode.Add(node); // 保存Tag,后面反查要用 tagMap[comp.Tag] = node; objMap[comp.Tag] = comp; // 统计出现次数 CountComponent(proto); // 判断是否为子装配:原型部件里是否还有下一级组件 ComponentAssembly subAssy = proto.ComponentAssembly; if (subAssy != null && subAssy.GetComponents().Length > 0) { WalkAssembly(subAssy, node); } } }

这段代码有几个细节值得说。proto.ComponentAssembly在普通零件上也可能有值,但GetComponents()返回的是空数组,所以要用Length > 0做二次判断,不要只判断非空。proto.Name是部件文件名,不是组件显示名;如果用户改了组件属性里的名称,最好用comp.GetDisplayName()之类的接口取显示名,否则树节点文本跟装配导航器对不上。

还有一个性能问题:递归写法简洁,但遇到几百上千层的深嵌套装配,递归调用栈有风险。我在实际项目里用了一个Stack来改成迭代遍历,逻辑完全一样,只是把“递归子装配”换成“压栈”,彻底避免栈溢出。小装配无所谓,大装配还是建议迭代。

2. 树表节点填充与部件数据关联

2.1 BlockUI Tree控件的节点模型

NXOpen的BlockUI里有个Tree控件,做装配树展示正合适。它是标准的树形控件,支持多级节点、节点选择事件、列显示,但API风格有点老头,创建节点、挂数据、取选中节点都跟通用TreeView不太一样。

Tree控件的核心对象有三个:TreeControl本身、Node(节点)、NodeData(节点数据)。Node负责显示层级和文本,NodeData负责挂业务数据。业务数据为什么要单挂一份?因为节点的文本是给人看的,而程序需要知道“这个节点对应的是哪个Component”,文本没法可靠反查,必须把Component的Tag塞进NodeData。

我通常的做法是在Tree控件上先创建一个根节点,根节点显示当前工作部件(顶层装配)的名字,下面的子节点就是遍历出来的各级组件。用户默认看到的是顶层装配的完整结构,跟装配导航器一致。

2.2 把Tag塞进NodeData,让节点“记住”部件

节点和部件数据的关联,本质上就是一句话:节点的NodeData里存Component.Tag。Tag是NX对象在本会话里的唯一整数句柄,有了它就能反查到对象。

NodeData data = treeControl.CreateNodeData(); data.SetTag(comp.Tag); // 版本不同可能是 data.Tag = comp.Tag node.SetNodeData(data);

这里有一个关键选择:存Component的Tag,还是存Part的Tag?我一开始存的是Part.Tag,结果发现树上有两个节点都叫“垫片”,统计数量时它俩会算到一起,但用户点击第一个节点,图形区永远只高亮最后一个垫片——因为Part.Tag只能定位到文件,定位不到装配里的具体位置。后来改成存Component.Tag就对了。

Component.Tag和Part.Tag是两回事,同一个Part在装配里出现3次,就有3个Component.Tag。树节点要对应“具体某一次出现”,必须存Component.Tag。如果要跨会话持久化,比如下次打开对话框还要记住某个节点对应哪个部件,Tag是会变的,那就改用Part.FullPath(文件完整路径)来存,FullPath是持久的。

2.3 填充树表的完整流程

填充流程不复杂,但有几个顺序不能乱。先清空旧节点,再创建根节点,然后递归遍历装配,每遇到一个组件就创建一个子节点挂到父节点下。节点文本里我习惯把加载状态也拼进去,比如“A-001 底板 [已加载]”“B-003 电机盖 [未加载]”,这样用户不用点开看属性就知道哪些件没加载。

treeControl.Clear(); Node rootNode = treeControl.CreateNode(workPart.Name); treeControl.Tree.AddNode(rootNode); // 部分版本是 Add(rootNode) tagMap.Clear(); objMap.Clear(); countMap.Clear(); WalkAssembly(workPart.ComponentAssembly, rootNode); rootNode.Expand();

如果你的树控件支持列,我建议预留两列:一列显示“数量”,一列显示“状态”。不过我实际用下来,列操作在NX不同版本上的API差异比较大,为了兼容省事,我一般直接用文本后缀“×N”来标次数,比如“垫片 SHIM-01 ×4”。效果直观,也不会因为API版本不同出幺蛾子。

3. 清理选择、高亮所选组件与改色

3.1 清理选择的两层含义

“清理选择”这个词在NXOpen里有两层意思,新人很容易只做一半。第一层是清空BlockUI里SelectObjectList控件里已选的条目,避免上一次选的结果残留在列表里干扰判断;第二层是取消图形区对象的HighLight状态,否则上一次高亮的组件还亮着,下一次高亮时图上一片混乱。

我项目里做一个“手动选择组件”按钮时,回调里第一步就是清旧选择、取消旧高亮:

SelectObjectList selectList = (SelectObjectList)blockUI.GetBlockControl("selectComp"); selectList.Clear(); // 清空选择列表中已有的条目 NXObject[] oldSelected = selectList.GetSelectedObjects(); foreach (NXObject obj in oldSelected) { if (obj is DisplayableObject disp) disp.Unhighlight(); // 取消旧高亮 }

这里有个小经验:Clear()只清控件里的列表数据,并不会通知图形窗口取消高亮,所以一定要配合对象级的Unhighlight()。反过来,如果用户是在图形窗口直接点选组件,SelectObjectList控件里的选中项会自动同步,这时控件里可能已经有对象了,必须get出来逐个取消高亮。

3.2 高亮组件:Highlight方法和注意点

高亮本身在NXOpen里是DisplayableObject的方法,直接调就行:

Component comp = objMap[tag]; (comp as DisplayableObject).Highlight();

但实际用下来有几个坑。第一,高亮不等于选中。图形区里组件亮起来,但选择列表里可能并没有这个对象,如果后续代码想读“当前选中对象”,会读不到。第二,高亮状态是“粘”的,不主动取消就一直亮着,所以我在模块里维护了一个lastHighlightObj字段,每次新高亮前先把上一次的对象Unhighlight掉。

还有一个场景特别容易漏:用户在树里多选节点,然后点“高亮全部”,这时候遍历所有选中节点,拿到Tag后逐个Highlight。但树控件的多选模式下,你拿到的Node可能不是按层级顺序返回的,而且同一个Component如果出现在多个节点的NodeData里(理论上不会,但如果你把Tag存成了Part.Tag就会),就会重复高亮。解决办法就是高亮前先检查一个HashSet。

HashSet<Tag> highlighted = new HashSet<Tag>(); foreach (Node node in selectedNodes) { Tag tag = GetTagFromNode(node); if (highlighted.Add(tag)) (objMap[tag] as DisplayableObject).Highlight(); }

如果非要更底层的高亮,可以用UFUN里的UF_DISP_set_highlight,传入对象的Tag,效果一样。需要注意的是,全屏刷新或窗口切换有时会丢失高亮状态,这在NX里是正常现象,可以在必要时主动重新高亮。

3.3 组件改色:组件级SetColor和体级SetColor怎么选

改色需求通常分两种:把整个组件改为某一个颜色,以及只改选中面的颜色。简单做法是直接对Component调用SetColor:

Component comp = objMap[tag]; (comp as DisplayableObject).SetColor(211); // 绿色

但实测下来,这种写法在引用集模式下经常不生效。原因是组件显示颜色会被引用的几何体颜色覆盖,尤其当零件内部面的颜色五花八门时,Component.SetColor只是改了装配树的显示属性,图形窗口里还是零件原来的颜色。

更稳的做法是遍历组件原型里的Body,对每个Body或Face设置颜色:

Part proto = comp.Prototype(); foreach (Body body in proto.Bodies) { body.SetColor(colorIndex); }

注意这样改的是原型部件的颜色,意味着装配里所有使用这个零件的位置都会一起变色。如果只想改当前这一处,就需要对Occurrence下的几何体操作,或者用装配级的显示属性覆盖。我的经验是:面向用户审核标注场景,直接改原型Body颜色最简单;面向“只标这一个安装位置”的场景,用Component.SetColor配合显示模式切到“组件属性”更合适。

颜色索引也是个坑。NX的经典调色板是216色,常见颜色索引接近日常认知:1是橙色默认色,211是绿色,186是黄色,但不同模板的调色板索引可能不一样。新版NX支持RGB自定义色,用SetColor(int)传的是调色板索引,不是你直觉里的RGB值。要精确改色,可以用DisplayModification来应用颜色,但又是另一套API。我建议开发前先在录制Journal里手动改一次颜色,看选色器弹出的颜色索引,再写进代码,比猜索引靠谱得多。

4. 统计组件出现次数与双向联动

4.1 统计“出现次数”的语义和实现

标题里的“统计组件出现次数”,实现起来说难不难,但语义要先定清楚。是要统计“装配树里有多少个Component实例”,还是“同一个Part被引用了多少次”?前者直接把遍历到的Component数量加起来就行;后者是BOM里最常见的“件数”,需要按Prototype归并。

我通常两种都算。总组件数是遍历计数,每遍历到一个Component就加1;零件种类数和每种件数用Dictionary来归并。

private Dictionary<Tag, int> countMap = new Dictionary<Tag, int>(); private void CountComponent(Part proto) { Tag protoTag = proto.Tag; if (countMap.TryGetValue(protoTag, out int count)) countMap[protoTag] = count + 1; else countMap[protoTag] = 1; }

统计完怎么展示?除了在树节点文本里追加“×N”之外,建议在对话框里加一个只读文本框或标签,显示“共N个组件,M种零件”。用户对这个汇总信息很敏感,尤其是做BOM核对时。注意一个细节:遍历子装配时,同一个零件可能在多个子装配里各出现几次,countMap会正确累加,因为不管在哪一层,归并的依据都是prototype的Tag。

有个隐藏问题:如果装配里存在“抑制”(Suppressed)的组件,它还在装配树里,但几何不参与计算。统计次数要不要把抑制组件算进去,取决于业务语义。我做的大多数项目里,用户希望看到的是“实际参与装配的组件数”,所以遍历时会先判断comp.IsSuppressed,被抑制的不计入次数,但树节点仍然保留,用文本标上“[抑制]”。

4.2 树节点到图形区、图形区到树节点的双向联动

双向联动说白了就是两个方向的数据查找,但用起来体验差别很大。树节点到图形区很简单:点选节点,从NodeData或tagMap里拿到Component.Tag,然后高亮、选中即可。

private void OnTreeSelect(Node node) { Tag tag = GetTagFromNode(node); if (!objMap.TryGetValue(tag, out NXObject obj)) { // 组件可能已被卸载或删除 return; } // 取消上一个高亮 lastHighlightObj?.Unhighlight(); Component comp = obj as Component; DisplayableObject disp = comp as DisplayableObject; disp.Highlight(); // 可选:把对象加入选择列表 selectList.Clear(); selectList.AddSelectedObject(comp); // 不同版本API可能叫Add lastHighlightObj = disp; }

图形区到树节点的反查稍微麻烦。用户在图形窗口选中的可能是Face、Edge、Body,而不是Component本身。所以第一步是配置选择过滤器,让SelectObjectList控件只接受Component类型的选择,这样GetSelectedObjects返回的每个NXObject都能直接当作Component处理。

NXObject[] objs = selectList.GetSelectedObjects(); if (objs.Length > 0) { Component comp = objs[0] as Component; if (comp != null && tagMap.TryGetValue(comp.Tag, out Node node)) { // 展开父节点、选中该节点 node.Expand(); // 不同版本可能叫ExpandNode treeControl.SelectNode(node, true); } }

反查的核心资产就是那个tagMap字典。在遍历填表时,我就把Component.Tag -> Node的映射建好了,反查复杂度就是O(1)。比每次从头遍历树节点找要快得多,尤其装配有几千个节点时,这个优化能明显感觉到。

4.3 Tag失效风险与节点数据保护

Tag是会话内部的临时句柄,这在NXOpen二次开发中是需要时刻警惕的隐患。组件被删除、组件被卸载再重载、会话里用Delete删除对象之后,旧的Tag大概率失效或者指向了别的对象。你保存的tagMap、objMap如果不能及时清理,轻则节点点了没反应,重则拿到一个无效对象直接抛异常。

我处理这事的策略有三层。第一层,对话框生命周期内,如果只做浏览、高亮、改色,组件基本不会被删除,tagMap可以放心用。第二层,如果允许用户在工具里删组件,每次删除操作之后必须重建tagMap,简单粗暴但有效。第三层,反查时始终用TryGetValue,拿不到就跳过,同时给用户一个提示“组件状态已变化,请重新载入”。

跨会话保存节点数据时,千万不要存Tag。我一般都存Part.FullPath,下次打开对话框时,通过FullPath找到Part,再遍历装配找对应Component,把树重建出来。这个过程稍微慢一点,但可靠。

5. 实际开发里最容易翻车的几个点

5.1 大装配遍历和树构建的性能陷阱

小装配怎么折腾都行,一旦到了几千个组件的大装配,性能问题就出来了。最常见的是递归遍历加上频繁创建节点,导致对话框卡顿好几秒。优化思路有几个。第一,遍历时不要每次都调proto.ComponentAssembly.GetComponents()去判断子装配,可以先在顶层遍历时把所有Part缓存到一个Dictionary,按Tag存起来。第二,节点一次性创建完再统一Add,不要在创建过程中反复刷新Tree控件。第三,如果树节点实在太多,可以在节点文本里增加“父路径”前缀,减少用户频繁展开大树的次数。

我实测过一个7000多个组件的军工模型(当然数据是脱敏的),优化前从点击按钮到树完整显示差不多8秒,优化后压到2秒内。主要就是靠字典缓存和减少控件刷新。

5.2 组件抑制状态对遍历和统计的影响

组件被Suppressed时,Component对象还在,Prototype也拿得到,但几何信息是空的。如果你遍历到这种组件直接去统计或者改色,数据会错。统计场景我已经说了,要跳过或标记。改色场景要更小心,抑制组件的几何不可见,SetColor即使不报错,用户也看不到效果,容易误以为工具坏了。

在树节点文本上标注抑制状态是我个人比较推荐的做法。用文本后缀标“[抑制]”比单独加一列更直观。用户一眼扫过去就知道哪些组件没有实际参与装配。

5.3 未加载和部分加载组件的高亮改色问题

高亮和改色前,最好检查加载状态。对Unloaded组件做操作,NX自己可能会触发后台加载,这会拖慢流程;如果明确告诉用户“这个组件未加载,无法高亮”,反而体验更清晰。我在代码里加了这样一个判断:

if (comp.LoadStatus == LoadStatus.Unloaded) { // 提示用户先加载再操作 return; }

部分加载的组件可以高亮,但改色可能只改了轻量级几何,完整加载后颜色又变回去。这种情况我会先尝试用proto.FullyLoad()把部件完全加载,再进行后续操作。只是加载大文件会有点慢,建议做成“用户点击确认后再加载”的交互,不要偷偷在后台加载。

5.4 API版本差异带来的兼容问题

NXOpen从NX1847到NX2312,BlockUI里Tree控件的API有小幅变动。比如添加子节点的方法,有的版本用parentNode.Add(childNode),有的版本用parentNode.AppendChild(childNode);NodeData设置Tag,有的版本是data.SetTag(tag),有的版本是直接调data.Tag = tag。这些差异不踩过根本不知道。

我的建议是:把Tree操作封装成一个单独的辅助类,把所有跟版本相关的API调用都集中在这个类里。遇到版本差异,只改这一个类,不要满项目找调用点。另外,拿到一个新版本NX,第一件事是录一段手工操作Tree控件的Journal,看它生成的API名字,那就是这个版本的“标准答案”。

这一整套做下来,我最想强调的还是那张tagMap,它是整棵树的“索引”,也是高亮、统计、反查、改色的总入口。只要你把Component.Tag和树节点之间的关系管好,后面的功能全是顺水推舟;管不好,每一步都卡在“找不到对象”上。如果后续继续扩展这个工具,我会把统计数据导出到Excel、增加批量预加载、把颜色索引做成可视化选色器。装配管理这个场景,功能永远不嫌多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询