尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

NX Open C++二次开发入门:环境搭建、Builder模式与异常排查

NX Open C++二次开发入门:环境搭建、Builder模式与异常排查 简介这份NX二次开发示例代码集基于UG Open C编写面向需要定制NX功能的C工程师通过fam_test示例工程系统演示了如何利用NX Open API完成会话管理、模型加载、几何操作及家族表Family Table的创建与管理是理解NX数据模型和入门二次开发的高效参照。压缩包内共19个文件体积仅61KB包含3个C源文件与3个头文件构成的核心源码以及编译后生成的DLL、LIB、OBJ和PDB等构建产物同时提供VC6.0的工程文件DSP/DSW和项目辅助文件既可直接阅读代码逻辑也能在对应环境下重新编译调试。已有231人学习使用过该资源特别适合刚接触UG Open C的开发者或需要实现参数化变体设计的工程人员。通过这份资料能够快速掌握NX二次开发的环境配置、基础API调用和家族表操作思路为后续开发自定义工具、自动化装配流程或设计检查插件打下坚实基础。1. 拿到 fam_test.rar 之后NX Open C 到底在解决什么问题接手一个 fam_test.rar解压后是 NXOpen C 工程和一张测试 prt这是 NX 二次开发最常见的学习包形态。但如果你按着“录宏、改代码、回放”的思路去做很快会碰壁NX Open C 其实是两套 API 并存的世界面向对象的 NXOpen 类和老的 UG OpenUFunC 函数接口各管一摊很多资料把两者混着讲新手根本分不清该调哪一个。这篇笔记只服务于一个目的让接到 NX Open C 开发任务的人快速分清两套接口、搭好环境、写出能跑的代码并避开那些让 NX 直接弹异常日志的玄学错误。适合要给 NX 做批量自动化、内部工具、参数化改造的工程师不适合只想看概念的人。2. 先分清三种 NX Open 开发模式内部、外部与 Journal2.1 内部模式是主力外部模式适合批处理NX Open C 写出来的东西有三种跑法。内部模式Internal是一个 DLL被 NX 进程加载入口函数是dlxOpen它能直接操作当前界面、读取用户选择、弹对话框绝大多数交互式二次开发都走这条路。外部模式External编译成独立 exe不依赖 NX 界面适合夜间批量转换、报表导出、无人值守的自动建模。第三种是 Journal也就是你用 NX 的宏录制功能自动生成的一段代码它只能交给 NX“重放”不能自己编译成工具它的价值是帮你抄 API 调用这一点后面会反复用到。选型建议很直接目标是“工程师在 NX 里点个按钮就干活”用内部模式。目标是“服务器定时跑一批任务”用外部模式。两者代码差异其实不大核心对象都是 Session、Part、Builder但入口、生命周期和异常处理的侧重点不一样。2.2 用 Visual Studio 搭工程路径、库和版本匹配NX 装好之后二次开发的头文件和库都在安装目录下的 UGOPEN 文件夹里。常见路径是%UGII_BASE_DIR%\UGOPEN里面 include 放头文件lib 放导入库。新建一个空 DLL 工程后项目属性里把UGOPEN\include加进“附加包含目录”把UGOPEN\lib加进“附加库目录”再在“附加依赖项”里加入 NXOpen C 对应的库文件。这里有一个很多人问过的问题能不能用 VSCode 配置 C/C 环境来做 NX 开发我的回答是VSCode 当编辑器看代码、写代码完全没有问题语法高亮和跳转都能用但 NXOpen 的工程向导、DLL 调试、异常定位都是围绕 Visual Studio 设计的我建议你用 VS 做工程VSCode 只做代码浏览。VSCode 配 C/C 环境那个流程再顺也解决不了 NX 加载 DLL 后的断点附加问题。另外注意版本匹配。NX 12.0 对应的 VS 版本和 NX 1980 系列对应的是不一样的工具集太新或者太旧运行时库不一致轻则编译能过加载报错重则直接 C0000005 内存访问违例。我在新机器上踩过一次NX 12 装 VS2022编译全过一加载就崩最后换回 VS2017 的工具集才正常。2.3 最小可运行的 NXOpen C 程序先让 NX 加载你的 DLL配置完工程先别急着写业务流程。我一般先写一个最小内部模式入口确认 DLL 能被 NX 加载和调用。新建first_entry.cpp#include NXOpen/NXException.hxx #include NXOpen/Session.hxx #include NXOpen/Part.hxx #include NXOpen/ListingWindow.hxx extern C __declspec(dllexport) int dlxOpen(char* param) { try { NXOpen::Session* session NXOpen::Session::GetSession(); NXOpen::Part* part session-Parts()-Work(); NXOpen::ListingWindow* lw session-ListingWindow(); lw-Open(); lw-WriteLine(当前工作部件: part-FullPath()); return 0; } catch (const NXOpen::NXException ex) { // 异常不接住NX 就会弹标准C异常日志这里必须处理 NXOpen::Session::GetSession()-ListingWindow()-WriteLine(ex.Message()); return 1; } catch (...) { return 1; } }代码逻辑说明dlxOpen是内部模式 DLL 的入口NX 通过 CtrlU 执行菜单时调用它。函数里先拿全局 Session再拿当前工作部件 Part最后用 ListingWindow 输出一行文字到 NX 的信息窗口。如果拿不到 Part 或者 NX 没打开任何部件Parts()-Work()会抛异常所以必须包 try-catch。参数说明dlxOpen(char* param)的 param 是 NX 传入的参数字符串目前用不到但入口签名必须保持这样返回 0 表示成功返回 1 表示失败。ListingWindow::Open()在部分 NX 版本里叫Activate()编译报错就换一下这个接口名字每个大版本略有差别属于正常现象。编译通过后在 NX 里按 CtrlU 选择生成的 DLL如果信息窗口打印出了部件路径说明环境通了接下来可以开始写真正的功能。3. NXOpen C 核心对象与 Builder 模式从 Feature 到参数修改3.1 Part、Body、Feature、NXObject对象的归属要分清NXOpen 的对象模型是一个树Session 是全局唯一的会话对象往下是 Part部件Part 里有 Body体、Feature特征、Expression表达式、Sketch草图等集合。Feature 是参数化历史里的一个节点Body 是几何体本身比如拉伸特征拉伸出一个 Body改了 Feature 的参数Body 会重建。与普通 C 类最大的不同是NXOpen 里的对象不能自己new必须通过 Session 和 Part 提供的方法去“获取”或“创建”。获取到的对象生命周期由 NX 管理你不能手动delete它否则就是二次开发里最常见的内存崩溃。C 侧通常用NXOpen::NXObject作为基类指针保存引用用完置空即可。新手最容易搞混的是当前显示部件和工作部件session-Parts()-Work()返回正在编辑的部件session-Parts()-Display()返回当前显示在图形区的部件。批量处理时脚本会打开多个文件这时候必须用 Work 而不是 Display因为用户屏幕上显示的可能是另一个文件。3.2 Builder 模式是对话框到模型的桥修改特征参数的套路NXOpen C 里几乎所有“修改特征”的操作都走 Builder 模式。Builder 是 NX 内部功能的一个临时代理对象你通过某类功能的 Builder 设置参数然后 Commit 让改动真正作用到模型上再 Destroy 释放 Builder。这就好比你在对话框里改完点确定NX 才执行操作。我先说一个可靠的方法再给代码拿不准 API 的时候打开 NX 的 Journal 录制手动改一次特征参数NX 会自动生成一段 NXOpen C 代码那段代码里的函数名必定是对的。录制宏里出现的方法就是你要用的方法。下面是我常用的修改孔特征直径的套路NXOpen::Session* session NXOpen::Session::GetSession(); NXOpen::Part* workPart session-Parts()-Work(); NXOpen::Features::FeatureCollection* featColl workPart-Features(); // 按特征名查找名字可以在NX部件导航器里看到 NXOpen::Features::Feature* holeFeature featColl-Find(SIMPLE_HOLE(12)); // 关键一步从特征创建BuilderNXOpen里所有编辑都要走Builder NXOpen::Features::HoleBuilder* holeBuilder dynamic_castNXOpen::Features::HoleBuilder*(holeFeature-CreateBuilder()); // 设置直径表达式SetRightHandSide是表达式赋值的通用方式 holeBuilder-Diameter()-SetRightHandSide(18); holeBuilder-Commit(); holeBuilder-Destroy();逻辑说明Feature::CreateBuilder()会生成一个可以编辑该特征的 Builder 对象类型可能是 HoleBuilder、BlockBuilder、ExtrudeBuilder 等具体类型要看特征类型。通过dynamic_cast转成对应类型后调用其内部的表达式控件设置参数值。所有参数设置完成后必须 Commit不 Commit 的话 NX 界面里看起来改了模型实际没变。Commit 后必须 Destroy否则 Builder 对象一直占着内存多次执行会内存泄漏甚至把 NX 拖崩。参数说明Find(SIMPLE_HOLE(12))里的字符串是特征在部件导航器中的名称带括号带编号不同编程语言封装下格式微有差异SetRightHandSide(18)是给表达式赋值的标准方法字符串形式是因为表达式支持输入公式例如你写18/2它也会接受并得到 9NX 表达式引擎会在 Commit 时自动计算。3.3 最小包容块和最小包容柱NXOpen 没有现成入口要跑 PK搜“NX 二次开发获取最小包容块和最小包容柱PK”是很多人的真实需求比如做零件自动装箱、干涉检查、夹具定位。这里有一个容易踩的点NXOpen 的“信息”功能里能看到体的轴对齐包围盒但它不等于最小包容块。轴对齐包围盒是固定以 WCS 坐标轴为方向的零件一旋转包围盒体积就变大很多最小包容块要的是“任意朝向”下体积最小的那个长方体这是一个优化问题。常见做法分两条路。只求轴对齐包围盒用老接口UF_MODL_ask_bounding_box就能拿到 8 个角点坐标代码短、速度快。要求真正的任意朝向最小包容块或最小包容柱NXOpen 没有直接的函数常见是用 Parasolid KernelPK接口绕开 NXOpen 去直接访问底层的体几何。具体流程是把 NXOpen 的 Body 转成 PK 的 body 标识遍历体面收集顶点集再做凸包计算最后用旋转法或 PCA 求候选方向逐个方向计算包围体积取最小。// 伪代码最小包容块的求解框架实际PK调用以你的NX版本头文件为准 double minVolume DBL_MAX; double bestTransform[4][4]; // 收集体上的采样点或凸包顶点 std::vectorPoint3d points CollectBodyVertices(body); // 对候选方向循环PCA主轴、凸包相邻面法向、均匀采样方向 for (auto dir : candidateAxes) { // 把点变换到以dir为轴的局部坐标系后求AABB double vol ComputeRotatedAABBVolume(points, dir); if (vol minVolume) { minVolume vol; bestTransform BuildTransform(dir); } }逻辑说明这个框架其实是一个通用求解器先确定候选方向集再对每个方向计算旋转后的轴对齐包围盒体积。方向越多结果越接近最优但耗时也越高。工程上折中方案是先做一次 PCA 求主轴作为初始方向然后局部扰动迭代一般几十次迭代就能收敛到工程可用的结果。参数说明CollectBodyVertices这一步要去看 body 的面和边NX 里获取面的 UV 参数范围再采样采集密度决定精度ComputeRotatedAABBVolume计算的是点集在旋转后的三个主轴方向上的投影范围乘积和几何体表面贴合程度有关。Pk 函数的具体写法和 NX 版本强相关我建议你在首次实现时先用录制宏确认 Body 的 Tag 获取方式再去查对应版本的 PK 头文件不要直接搜网上旧代码版本差异非常大。4. UG Open 与 NX Open 混用函数选型、单位转公制与外部模式4.1 UFun 和 NXOpen 的边界什么时候绕不开 UF_*NXOpen 是面向对象的现代 C 接口而 UFUser Function也叫 UG Open是 NX 早年间从 UG 时代带过来的老 C 接口函数名清一色以UF_开头。老代码、老教程、老工程师写的东西大部分都是 UF 风格。问题来了NXOpen 不是完全覆盖了 UF文件批量操作、装配加载选项、表达式批量读写、工程图某些底层操作UF 接口比 NXOpen 简洁得多也稳定得多。混用两套接口时有一件事必须做调用 UF 函数前先UF_initialize()用完再UF_terminate()。不初始化直接调 UF 函数轻则返回错误码重则崩溃。我见过不止一个人把UF_initialize写在dlxOpen入口里然后每个回调函数里直接调 UF结果第二次进入回调就崩因为UF_terminate在入口已经执行过了。#include uf.h #include uf_modl.h // 在NXOpen对象已经可用的情况下再手动初始化UF环境 int ret UF_initialize(); if (ret ! 0) { // 初始化失败返回上一步 return ret; } // 这里才能调用UF_*系列函数 tag_t bodyTag ...; double corners[8][3]; UF_MODL_ask_bounding_box(bodyTag, corners); UF_terminate();逻辑说明UF_initialize和UF_terminate必须成对出现而且最好是同一个函数内完成不要在入口初始化、在回调里 terminate。UF 函数操作的是 tag_t 类型的标识这是 NX 内部的句柄NXOpen 对象可以通过GetTag()拿到这个值反过来 tag_t 也能通过NXObjectManager::Get转成 NXOpen 对象。我自己写代码时的习惯是所有 UF 调用都封装成一个独立的辅助函数辅助函数内部第一行 initialize最后一行 terminate这样调用点无脑安全不会把 initialize/terminate 散落在各个业务分支里。4.2 英制单位转公制NXOpen 的 SetUnits 与 UF 的单位处理“NX 二次开发 转换零件的英制单位到公制单位”这个需求本质不是缩放模型而是改变部件的单位制。NXOpen 里Part::SetUnits可以直接设置单位制但这里有一个关键认知必须先说清楚把英制部件转成公制NX 会保持几何体实际物理尺寸数值不变还是改变默认情况下单位制转换会让模型特征参数按比例换算例如 1 英寸变成 25.4 毫米物理尺寸不变但表达式里的数值变了。如果你的需求是“显示单位变成公制同时数字也变成 25.4”用 SetUnits 就行。如果你的需求是“把整个模型等比缩放到某个尺寸”那是缩放操作不是单位转换千万不要混用。NXOpen::Part* part session-Parts()-Work(); // 先看当前单位制再决定是否转换 if (part-Units() NXOpen::Part::UnitsInches) { // 转换为毫米转换规则是物理尺寸不变、特征数值按比例换算 part-SetUnits(NXOpen::Part::UnitsMillimeters); }逻辑说明Part::Units()返回当前单位制枚举SetUnits执行单位转换。这个操作会触发整个部件的表达式更新如果部件里有复杂的互相引用的表达式转换后可能会产生表达式计算顺序问题导致个别尺寸不是预期的 25.4 倍。参数说明UnitsInches和UnitsMillimeters是单位制枚举的两个值。注意 NX 12 和 NX 1980 系列里这个枚举的命名空间写法有些调整新版可能是NXOpen::Part::Units::Millimeters编译报错就切换成带作用域的写法或者直接录宏看自动生成的是什么。4.3 外部模式批量跑无界面处理比有界面更省事外部模式最适合的场景是有一批 prt 要统一改属性、导出数据、转格式用户在命令行双击一个 exe 就能跑完。外部模式的好处是它不加载 NX 对话框资源内存占用小出问题时直接进程崩溃不会把用户的 NX 会话带崩。下面是一个最小外部模式程序#include NXOpen/Session.hxx #include NXOpen/Part.hxx #include NXOpen/NXException.hxx int main(int argc, char* argv[]) { try { NXOpen::Session* session NXOpen::Session::GetSession(); // 打开要处理的部件第二个参数表示是否可见 NXOpen::Part* part session-Parts()-Open(D:/work/model.prt); // 这里执行你的批量操作 // ...业务逻辑... // 关闭部件CloseModified表示如果有修改就保存 part-Close(NXOpen::BasePart::CloseModified); } catch (const NXOpen::NXException ex) { return 1; } catch (const std::exception ex) { return 1; } return 0; }逻辑说明外部模式没有dlxOpen入口就是标准 C 的main。Session::GetSession()在外部模式下会自动初始化 NX 运行环境不需要额外调用什么启动函数。部件的操作逻辑和内部模式基本一样差别只是不能弹 NX 的交互对话框。参数说明Parts()-Open的第一个参数是文件路径第二个参数是“是否显示在图形窗口”外部模式建议传false或者直接用默认参数省资源。Close(NXOpen::BasePart::CloseModified)的第一个参数是关闭行为可选CloseNoSave、CloseModified、CloseClean等批量处理慎用CloseNoSave一旦处理到一半程序崩溃你前面所有改动全丢。5. NX Open C 常见问题排查异常日志、Access Violation 与对话框宽度5.1 现象NX 弹“捕获到标准 C 异常请参见系统日志文件”这个弹窗在 NX 二次开发里堪称头号拦路虎几乎每个做 NXOpen C 的人都被它卡过。现象是运行自己的插件NX 界面弹出一个错误框说“捕获到标准C异常。有关详细信息请参见系统日志文件”然后插件直接终止有时候 NX 会话也会跟着不稳定。原因NXOpen 的 C 接口内部几乎所有方法都会抛异常典型的是NXOpen::NXException。如果你在dlxOpen或者回调函数里没有接住异常异常会穿过 NX 自己的调用栈NX 框架只能按未处理异常来处理于是弹这个日志框。更麻烦的是日志文件里指向的路径往往是 NX 安装目录下的一长串源码路径像o:\ugnx120\ip27\src\syss\...它只是告诉你异常发生的位置在 NX 内部不代表你的代码写在那。解决给你的入口函数和所有回调函数统一包一层 try-catch。这不是可选项是必须项。入口函数里 catch 住异常并写到日志或 ListingWindow用户至少知道是哪个环节出的问题而不是面对一个黑匣子。extern C __declspec(dllexport) int dlxOpen(char* param) { try { // 你的业务代码 return 0; } catch (const NXOpen::NXException ex) { // 打开信息窗口并打印异常信息 NXOpen::Session::GetSession()-ListingWindow()-WriteLine( NXOpen::NXString(NXException: , ex.Message())); return 1; } catch (const std::exception ex) { return 1; } }踩坑点在于catch 顺序不能乱必须先接NXOpen::NXException再接std::exception最后...。因为NXException继承自std::exception如果std::exception写在前面NXException 就被吞掉了你永远看不到真正的错误类型。另外 catch 到异常后一定要打印ex.Message()很多排查线索都在这个字符串里。5.2 现象Access Violation C0000005 内存访问违例C0000005 是 Windows 的内存访问违例错误码NX 二次开发里出现频率也很高。一种典型场景是执行完一段 NXOpen 代码后NX 立即崩溃事件查看器里看到0xC0000005。另一种是 C# 调 C DLL 时出现这个错误码后面细说。原因最常见的是手动delete了 NXOpen 对象。初学者会按普通 C 的习惯对 NXOpen 返回的指针调用delete这等于删掉了 NX 内部还在用的对象下一次访问时直接访问非法内存。另一种原因是在 DLL 边界传递了 C 对象比如把std::string跨 DLL 传递而两边运行时库不一致内存布局对不上。还有一种原因是 Builder 没有 Destroy反复创建大量 Builder 把堆打崩。解决所有 NXOpen 对象只获取、不释放用完置空指针即可。Builder 用完之后必须Destroy()但Destroy()是 NX 提供的接口不是你调delete。还有一点尽量通过NXOpen::NXObjectManager去管理对象引用不要自己持有裸指针跨函数传递。如果代码里要暂时保存对象用NXOpen::Session提供的智能指针封装或者用tag_t临时存取。5.3 现象Block UI Styler 枚举下拉框宽度固定选项显示不全这是很多做 NX 二次开发界面的人遇到的细节问题Block UI Styler 里放一个枚举控件选项文本比较长运行时下拉框宽度不够文字被截断。热搜里“nx 修改枚举下拉框的宽度”说的就是这个。注意它和 C 代码关系不大主要是对话框编辑器设置问题。原因Block UI Styler 生成的对话框每个控件都有一个固定初始宽度。枚举控件的宽度默认按最宽的枚举项文本计算但在某些 NX 版本里如果枚举项是在代码里动态添加的对话框初始化时不知道文本长度就会用默认窄宽度显示。解决两层方式。第一层在 Block UI Styler 编辑器里选中枚举控件在属性面板的“宽度”或“大小”里直接给一个足够大的值这是最省事的做法。第二层如果枚举项是代码动态生成的那必须在对话框的 initialize 回调里设置控件宽度并且在设置之后不要再去调用ResetProperties否则宽度会被重置回默认值。常见翻车点就是初始化顺序不对先 SetWidth后面别的代码又触发了一次属性重置于是怎么改都没效果。5.4 现象C# 调用 C 出现 Access Violation C0000005这个热搜词指向的场景和 NX 本身无关但很多做 NX 工具链的人会遇到C# 程序调用自己写的 C DLL一调用就报 C0000005。原因通常是调用约定不一致。C# 的DllImport默认使用 Winapistdcall调用约定而 C 导出函数默认是 cdecl两边对参数栈的清理方式理解不一样参数传递完栈指针错位执行完就访问违例。解决C 侧导出函数时显式声明 cdecl或者 C# 侧在DllImport里指定CallingConvention CallingConvention.Cdecl。我推荐前者声明写在 C 侧所有调用方都不用猜。// C 侧导出函数显式声明调用约定 extern C __declspec(dllexport) int __cdecl ProcessPart(const char* path) { return 0; }// C# 侧强制使用Cdecl调用约定两边的栈清理方式才能对上 [DllImport(nx_tools.dll, CallingConvention CallingConvention.Cdecl, CharSet CharSet.Ansi)] static extern int ProcessPart(string path);另外要注意字符串传递C 侧const char*对应 C# 的string但必须指定CharSet.Ansi否则默认 Unicode 编码会传成 UTF-16 给 C 侧整个内存读取就会越界。如果字符串格式不对你只会看到 C0000005不会有任何编译期提示。6. 提高 NX Open C 调试效率用日志、断点和回放复现问题真正写 NX 二次开发最耗时间的不是写功能是出了问题不知道发生在哪。我自己的调试习惯有三个。第一所有对外入口都写日志。dlxOpen第一行写“进入插件”最后一行写“插件返回”中间每个关键步骤写一行。日志优先用ListingWindow输出到 NX 信息窗口因为它不落盘、刷新快批量任务里再叠加写文件日志方便跑完统一查。异常捕获里不仅要打印 Message还要打印ex.GetId()这个 ID 可以直接对应到 NX 帮助文档里的错误码说明。第二断点调试要确认你是否真的加到了 NX 进程。内部模式的 DLL 是被 NX 加载的不是被你启动的所以 VS 里要先设置“附加到进程”选 NX 的进程名。很多人断点打上却不命中原因就在这。附加之后VS 里设置异常过滤器把NXOpen::NXException设为“抛出时中断”这样异常一发生就能看到调用栈比事后看日志快得多。第三拿不准 API 就录宏。NX 的 Journal 录制生成 NXOpen C 代码这是最可靠的 API 参考。录制时把你要做的操作手动做一遍然后查看生成的代码参数名、函数名、调用顺序都是 NX 自己生成的不会错。唯一要注意的是 Journal 生成的是某个具体版本 NX 的语法换版本后有些枚举名和命名空间会有变动需要对照新版本的头文件微调。外部模式调试相对简单一些exe 是独立进程可以直接在 VS 里 F5 启动断点必然命中。建议在开发初期就把核心算法写成外部模式可调用的函数先脱离 NX 界面把逻辑跑通再集成到内部模式的 DLL 里。这样既省内存也省掉了反复附加进程的时间。做 NX Open C 这几年我最深的感受是这个方向 70% 的收益来自稳定运行而不是功能花哨。把异常处理、对象生命周期、单位转换这些地基打好后面加功能才敢放心。希望这篇笔记能让你少走几步弯路也希望你从 fam_test.rar 这类学习包出发时第一件事不再是改代码而是先搭一个能稳定加载、能看日志、能断点定位的骨架。本文还有配套的精品资源点击获取
返回列表