简介:面向UG NX二次开发入门者,这份资源聚焦外部开发模式下如何通过NX Open C API创建块体。以一段可运行的外部程序为例,完整演示了环境初始化、新建公制零件、指定原点与边长创建块、保存零件、关闭零件以及终止API环境的调用顺序,并给出错误处理宏的写法,帮助读者理解外部程序驱动NX建模的基本骨架。代码中对零件文件路径、单位制、块的原点坐标和边长参数均做了清晰标注,并解释了关闭零件时三个参数的含义:零件标签、是否强制保存、是否清除内存中的零件数据;同时强调操作完成后需终止API环境以释放资源。路径与尺寸可直接修改,便于复制运行验证。压缩包仅含1个docx文档,体积14KB,内容精炼,适合学过C/C++、希望快速上手UG二次开发的初学者查阅。目前已有618人浏览/学习,可作为外部开发入门阶段的对照范本。
1. 外部开发模式与创建Block:为什么这条路径值得上手
做过NX二次开发的人大多有个共同体验:想用代码在装配体里快速摆一个方块,翻遍资料发现教程全在讲交互式建模,真正能直接跑的示例代码却少得可怜。尤其在外部开发模式下创建Block,很多资料要么只给内部模式示例,要么压根没讲清楚“外部模式”和“内部模式”在使用体验上的巨大差异。这直接导致新手把时间耗在环境配置和API选型上,而不是真正的事建模本身。
外部开发模式的价值很明确:不进入NX的UI线程,用独立进程跑.NET程序,适合批处理、参数化生成、多文件循环处理这类场景——设定好参数后丢给程序,跑完批量输出结果,中途不需要人工干预。创建Block是其中最基础也最高频的操作,能用来测试环境、验证模型历史树、甚至做DFM自动检查的底稿。适合的人包括:被装配重复建模折磨的工艺工程师、做参数化模板的CAE前处理脚本党,以及刚毕业还没摸清NXOpen门道的应届开发者。
2. 先搭一个外部模式工程:环境、引用与最小入口
2.1 外部模式与内部模式的关键差别
内部模式(Internal)的运行机制是让代码在NX进程中以托管DLL的方式加载,能直接拿到UI会话、能弹对话框、能访问当前打开的所有Part。代价是运行期必须开着NX,调试时动不动就崩出“内存访问冲突”,而且更新代码要反复重启NX,效率很低。外部模式(External)则是独立进程调用NXOpen API,通过NXOpen.Session来间接控制NX核心。它不需要界面就能跑最小命令,适合没有图形界面的服务器或批处理场景。
我在某公司的自动化项目里吃过亏:当时用内部模式写装配体批量摆正工具,每次跑完模型树里全是回到会话留下的残留区块,程序一退出界面就花屏。改成外部模式后,进程独立、错误隔离、日志可控,整个工具链稳定了很多。所以如果你只是自用脚本,内部模式能快一些;但凡是往生产链路里挂的,推荐直接用外部模式起步。
外部模式的第一个门槛是环境变量。NXOpen的外部程序默认要能找到NX安装目录/UGII下的托管DLL和原生DLL,常见做法是每次运行前设置UGII_ROOT_DIR和UGII_BASE_DIR。另一个门槛是.NET版本与NX版本匹配。我习惯用当前主流NX对应的.NET 4.8,C#写主体,VB.NET也能勉强跑但语法体验一般。
2.2 创建一个最小可运行的外部模式项目
下面这个C#控制台项目就是外部模式的最小骨架,先跑通它再往里面加Block逻辑。打开Visual Studio,新建控制台应用(.NET Framework 4.8或更高),然后添加以下引用:
NXOpen.dllNXOpen.UF.dllNXOpenUI.dll(部分功能需要)NXOpen.Utilities.dll
引用路径一般在[NX安装目录]\NXBIN\managed下,不要直接引NXOpen\NXOpen.dll,那个经常是符号链接或旧版本。
using NXOpen; using NXOpen.UF; class ExternalEntry { static Session theSession; static UFSession theUfSession; static Part workPart; static void Main(string[] args) { // 1. 启动外部会话 theSession = Session.GetSession(); theUfSession = UFSession.GetUFSession(); // 2. 加载目标部件(空Part或已有文件均可) string partPath = @"D:\test\block_demo.prt"; theUfSession.Uf.Part.Open(partPath, out workPart); theUfSession.Uf.SetDisplay(workPart.Tag, 1); // 日志可见,便于调试 // 3. 在这里调用创建Block的逻辑 CreateBlockDemo(); // 4. 保存并退出 workPart.Save(); theUfSession.Uf.Part.Close(workPart.Tag, 0, 1); } static void CreateBlockDemo() { // 本方法内部实现将在第3章详细展开 Console.WriteLine("Block创建逻辑待补充"); } }这个骨架的意图很清晰:用Session.GetSession()拿到外部会话,用UFSession.GetUFSession()拿到古老的UFUN功能入口。这里有个常见误解——很多教程说外部模式必须要Session.GetSession,实际上如果你只想调UFUN接口,直接用UFSession.GetUFSession()就够了。但在同一个程序里混合使用NXOpen和UFUN时,两个入口都要初始化。
Uf.Part.Open用来打开部件,注意外部模式没有“当前显示部件”的概念,每次操作都必须显式指定workPart,否则后续调用直接报“未初始化”。Uf.SetDisplay只是把Part标记为会话内可见,方便你开着NX观察操作过程,不调用它也能跑。
参数说明方面,Part.Open的第一个参数是完整路径,第二个参数是输出参数,拿到打开后的Part对象。Part.Close的三个参数分别是Part的Tag、是否保存、是否丢弃修改,我一般在调试期用(tag, 0, 1)不保存直接关,确认逻辑安全后再改成保存。SetDisplay的第二个参数1代表设为显示部件,部分NX版本对空Part调用会报错,需要先至少有一次MakeDisplayed,这里直接简化处理。
2.3 调试时最值得养成的习惯:日志先行
外部模式最常见的死法是“程序崩了但不知道崩在哪”。因为进程独立,NX的崩溃对话框弹不出来,你只有事件日志或stderr可用。我一般会在代码里提前埋日志,用Console.WriteLine是第一步,再往%APPDATA%\日志文件里落一份文件日志,崩溃时先看日志再改代码。这个习惯在写Block创建脚本时尤为重要——几何操作失败往往不是语法错,而是Part文件状态不对或没有预先初始化。
3. 用NXOpen创建第一个Block:两种API写法与参数细节
3.1 NXOpen的创建Block入口
NXOpen创建Block的正统入口是FeatureCollection的CreateBlock方法,它的全名在命名空间NXOpen.Features下。参数有四个:原点坐标、边长、布尔操作、布尔目标体。为了讲清楚,我把创建空Part再加一个100×200×300方块的完整代码写出来。
static void CreateBlockDemo() { // 获取特征集合 Features.FeatureCollection feats = workPart.Features; // 创建Block特征 Point3d origin = new Point3d(0, 0, 0); Vector3d edgeLengths = new Vector3d(100, 200, 300); Features.BlockFeature block = feats.CreateBlock(origin, edgeLengths); block.Name = "MyFirstBlock"; // 更新模型 workPart.ModelingViews.WorkView.Regenerate(); }这套代码看着简洁,但有两个关键点:第一是ModelingViews.WorkView.Regenerate()不能省,NXOpen创建特征后不会自动更新视图,不调用就无法在界面上看到方块。第二是CreateBlock的返回类型是Features.BlockFeature,它派生自Features.Feature,后续如果要拿这个块做布尔运算或移动,需要先转成Features.BlockFeature再转成Features.Feature,不能直接混用。
参数说明:Point3d是用户坐标系里的原点坐标;Vector3d的X、Y、Z分量分别对应长度、宽度、高度。有工程师习惯把三个值写成(100,100,100)改名正方体,这没问题,但要清楚这是沿WCS方向,不是沿绝对坐标系,模型坐标系和WCS不重合时容易产生“我怎么建的块不在原点”的疑惑。如果想沿绝对坐标系创建,先把WCS设为绝对坐标系,或者在创建后用Move命令调整体的位置。
3.2 UFUN的创建Block入口:另一个思维模式
如果你常写UFUN老代码,会习惯用UF_MODL_create_block1这个函数。它和NXOpen的CreateBlock的区别是:它创建的是原生UFUN体,不是特征对象,后续历史记录里看到的是“体”,不是“Block Feature”,因此无法像特征那样进草图尺寸驱动。适合做中间过渡几何,不适合做参数化模板。
/* C语言风格调用,适用于C++/C#中P/Invoke */ tag_t blockTag = NULL_TAG; char* origin[3] = {"0.0", "0.0", "0.0"}; char* edgeLengths[3] = {"100.0", "200.0", "300.0"}; UF_MODL_create_block1(origin, edgeLengths, &blockTag);用UFUN创建Block时要注意:输入参数是char*数组而非数值数组,NX内部会用解析器去解析字符串,所以字符串格式必须规范,多了空格或少了小数点都可能解析失败。NULL_TAG在C#里对应Tag.Null,UF_MODL_create_block1函数会输出刚创建的体Tag,随后可以用UF_MODL_ask_body_feature把它转成特征对象。
从实用角度,我个人推荐优先使用NXOpen的CreateBlock,如果你只是做一次性摆块、不关心历史树参数化,UFUN也能胜任。两者的核心差别一句话能说清:NXOpen是面向特征建模的,UFUN是面向几何体的。
3.3 创建Block时的坐标系与原点陷阱
坐标系问题是外部模式创建Block最容易翻车的地方,没有之一。早期版本NX中CreateBlock的原点参考WCS,但后期版本部分预设模板会参考绝对坐标系,具体取决于当前Part的建模偏好。
我通常先写一个断言检查:创建后的方块边界盒,看看原点是不是我预期的WCS原点。如果有偏移,多半是原点传给的是绝对坐标值、但实际采用了WCS原点的相对偏移,此时用Uf.UF_CSYS_ask_wcs和UF_CSYS_ask_csys_of_wcs来换算坐标后再创建。这是绕不过去的一步,很多同事直接拿绝对坐标硬填,等模型导入装配体后发现所有Block位置都错位,才回头排查坐标系。
简单说:在创建前,先记录当前WCS的原点,再用它来做相对偏移。不要把“绝对坐标值”和“WCS坐标值”混为一谈。
3.4 布尔操作的传参与Update时机
创建Block本身只有几何,没有布尔操作时,每个Box都是独立实体,干涉也互不影响。而在公差分析或DFM场景里,通常希望Block直接和目标体做布尔运算,省去后面二次求和。NXOpen里CreateBlock函数一个重载支持布尔操作,写法如下:
// 带布尔操作的创建方式 Features.BlockFeature combBlock = feats.CreateBlock( new Point3d(50, 50, 50), new Vector3d(50, 50, 50), Features.Feature.BooleanType.Union, targetBodyFeature);参数这里非常容易混淆。BooleanType取值是Union、Subtract、Intersect,分别对应布尔加、布尔减、布尔交。targetBodyFeature必须是特征对象,不是Body对象——如果你手里只有Body,需要先用NXOpen.Features.FeatureCollection里的方法找“BRICK特征”再转换。我经常见到同事传了一个Body.Tag进来,直接编译报错,原因是把Features.Feature和Body两个概念搞混了。
还有一点眼睛看不到但影响很大的:布尔操作的目标体不能是刚创建还没更新到底层的体。也就是你在上一轮建了一个BlockA,紧接着要拿BlockA当目标做Union,必须先调用Part.Update()或UpdateManager.DoUpdate(),确保BlockA在NX内部已经落库。否则布尔布尔操作会报“体已被删除或未初始化”。这个坑在官方文档里几乎不写,却是最常见的运行时报错来源。
3.5 更新模型与批处理回流
外部模式处理批量任务时,如果循环里建了上百个块,每次都调用一次Regenerate会奇慢无比。正确做法是先快速创建所有Block,等所有特征都添加完毕后再统一调用一次更新。代码逻辑大致是:
for (int i = 0; i < 100; i++) { Vector3d size = new Vector3d(10+i, 20, 30); feats.CreateBlock(new Point3d(i*15, 0, 0), size); } // 统一更新 workPart.ModelingViews.WorkView.Regenerate();这里延后更新能减少ASM内部重建次数,但有一个副作用:如果你在循环里需要拿前一个Block的BoundingBox来计算下一个位置,那么拿到的永远是未更新前的旧坐标,跑出来的结果会全部堆在一起。自己把握尺度,小批量无脑更新没毛病,大批量务必强行合并更新。
4. 外部模式创建Block避坑:五个常见问题与排查思路
4.1 程序跑了但界面上看不到Block
现象:外部程序执行完成无报错,日志也打出来了,打开Part看不到方块,还以为创建失败。
原因:极大概率是Regenerate没调用。NXOpen创建的Feature需要模型视图刷新才会显示,外部模式不像交互式建模那样自动显示,必须手动触发视图重生成。另一个常见原因是Part没有显示为当前视图,你虽然打开了它,但SetDisplay没设,所以NX主进程压根没把它呈现出来。
解决:创建完Block后明确调用WorkView.Regenerate();如果还看不见,用Uf.UF_LAYER_set_status检查目标图层是否可见。外部模式下Block默认建在当前工作层,如果当前工作层是隐藏图层,那它就像“存在但看不见”,日志里不会有任何报错,你就干瞪眼。图层问题建议一上来就查,不然排查周期很长。
4.2 CreateBlock报“部分修饰符无效”或“参数类型不对”
现象:编译过不了,报错指向CreateBlock的布尔参数或Point3d参数类型不正确。
原因:Features.BlockFeature不是Features.Feature,外部模式里直接调用CreateBlock时容易混淆重载。如果传了布尔操作参数,但目标体传成Body而非Feature,编译器就不高兴。
解决:先确认当前NX版本对应的API签名,拿官方例子对照参数类型。通常目标体需要从Body转成Feature,转换方式是查找Body所属的BuildingFeature或使用NXOpen.Features.FeatureCollection的FindFeature。另一个歪招:把布尔操作留空,创建后再用BooleanFeature单独操作,虽然多一步但避免类型混淆。
4.3 Part文件里多了一堆没用的历史特征
现象:创建Block后,模型历史树里出现CSYS、DATUM_CSYS等一堆祖先特征,导出到其他系统后文件变乱。
原因:外部模式打开一个空Part时,NX默认继承模板里的坐标系和基准面对象,这些特征在历史树上会占用位置。你只想建一个块,她却把模板内部的一整套东西都带出来了。
解决:不用管,这些特征是模板属性,不影响Block本身。如果强迫症犯了,可以在创建前删掉这些基准特征,但我不建议,因为删了之后二次编辑时缺少坐标系参考反而麻烦。更合理的做法是直接修改模板文件,把默认基准特征清掉,再外部加载时自然干净。
4.4 UFUN版本和NXOpen版本混用导致解锁问题
现象:程序前半段用UF_MODL_create_block1创建几何,后半段用NXOpen.Features里的方法处理特征,结果报“内部错误:内存访问冲突”。
原因:UFUN和NXOpen虽然都在同一个进程内,但它们在内部走两套不同的API通道,部分对象状态不能隐式互转。UFUN创建的是原生几何体,NXOpen的特征方法可能拿不到对应的特征对象,强行访问就崩。
解决:在一个外部进程里尽量只选一套API作为主导。需要UFUN读几何就全程用UFUN,需要特征历史就全程用NXOpen。我平时只把UF_MODL_create_block1用于快速创建不参与特征操作的中转体,一旦Block要参与布尔、移动、改名,就换用NXOpen创建。别图一时方便混着写。
4.5 路径含中文或空格导致加载失败
现象:Part路径放在C:\某公司\项目B\model v2.prt,外部程序死活打不开,报“文件不存在”或“路径无效”。
原因:NX的UFUN接口早期版本对非ASCII路径支持很差,空格和中文混合时Part.Open直接跪。这与创建Block无关,但外部模式配置环境时最容易碰到。
解决:统一用纯英文路径,目录层级短一点。路径中不要有空格,不要有括号。多个项目之间共享路径时,建议用网络映射盘符或创建符号链接,不要直接写UNC路径。这个问题极其玄学,遇到先怀疑编码,再怀疑权限。
5. 批量与参数化:把Block创建变成可复用的脚本层
外部模式创建Block如果只做一个块,确实有点浪费这条技术路线。常见的进阶用法是把某个特征参数提取成可配置项,比如从Excel或CSV读取Block原点、边长、布尔类型,批量生成模型。这里面有个比较隐蔽的问题:外部模式每次运行都是新的进程,Part里的更新历史是累积的,第二次跑同样的参数可能生成重复块,所以写脚本时一定要先判断“是否已有同名Block特征”。
判断方式很简单,遍历Part.Features找BlockFeature的名字,如果存在就跳过或删除再新建。名字在创建时通过block.Name设好,后续用WorkPart.Features.FindObject按名字查找。但这个在外部模式下有线缆,因为FindObject是全Part范围,批量创建时名字不能重,否则找回第一个就停。
我常用的一种做法是给每个Block名字加时间戳或序号,例如"Block_" + DateTime.Now.ToString("HHmmssfff"),既满足唯一性,又方便日志定位。这时Part的历史树会很乱,但对于自动生成模型来说,干净或乱并不重要,重要的是能被下游识别和引用。
从这边出发,可以做更多有意思的事:把Block边长关联到表格里的某个参数,用NXOpen.Features.BlockFeature.Feature拿到的特征对象去参与更复杂的装配约束;或者配合NXOpen.Positioning做装配距离约束。不要把Block创建当成终点,它只是你进入NXOpen特征操作的一个入口。
最后补充一个调试技巧:外部模式跑比较长的批处理前,先建一个测试Part,用workPart.ModelingViews.WorkView.Regenerate()配合theSession.ListingWindow()把过程信息输出到NX的窗口,而不是只依赖命令行。ListingWindow能直接看到NX内部特征创建的状态和错误码,排查速度能快不少。
我最早写Block创建脚本时就是因为不习惯外部模式的“无界面”特性,把时间全浪费在视图刷新和图层问题上,后来把日志逻辑理顺、把API选型固定下来,一套流程半小时内可以跑完。希望这些经验帮到你,尤其是那条“先更新再取值”的原则——别不信,它真的能让你少写几百行无用代码。
本文还有配套的精品资源,点击获取