简介本资源是面向NX二次开发初学者与工程技术人员的C实践项目包聚焦UG Open C在家族表Family Table自动化管理中的典型应用解决企业参数化设计与批量变体生成的定制开发需求。压缩包共19个文件含3个核心头文件.h定义接口与回调3个C源文件.c实现主逻辑与对话框功能3个目标文件.obj及1个动态链接库.dll辅以VC6工程配置文件.dsw/.dsp、调试符号.pdb和链接库.lib/.exp完整覆盖编译、调试与部署环节总大小仅61KB轻量易集成。已有230人学习下载资源结构清晰包含可直接编译运行的fam_test工程配套函数封装fam_test_fun.h/c、UI交互模块fam_test_dialog.h/c及NX会话初始化、模型加载、家族表创建与参数写入等关键代码片段为理解NX数据模型与Open API调用提供可实操的入门范例。 看到fam_test.rar_NX Open C_nx_nx open_open nx_ug open c这个标题我第一反应是这大概率是一个 NX 二次开发练手项目。fam_test是工程名后面挂的NX Open、NX、UG Open C基本把技术栈写清楚了——用 C 写 NX也就是以前的 UG插件跑的是 NX Open 接口。这类项目在制造业里太常见了自动化出图、批量建模、参数化设计、自动测量分析全是这个路子。fam_test这种命名方式一般是工程师在验证某个功能时随手起的测试工程名里面很可能放了一个测试部件和几段测试代码用来验证“光标位置能不能拿到”“这个面是孔面还是轴面”“测距能不能跑通”这类问题。这篇文章我就以fam_test这个测试工程为切入点把 NX Open C 二次开发从环境准备、项目配置到高频代码实现的完整链路拆开讲一遍。不管你是刚接触 NX 二开的新手还是从 Journal 录制转到 C 深度开发的工程师这篇文章都能给你一份可以直接抄作业的参考。1. 项目拆解fam_test 到底在测什么1.1 从文件名反推项目边界压缩包名字里带着fam_test结合常见二开场景我推测这个工程至少包含三样东西一个 NX 测试部件文件比如带孔、带轴的实体模型、一个 Visual Studio 工程或者 NX Open Wizard 生成的工程文件、以及编译好的 DLL。命名里fam可能是“Family”的缩写也可能是一个模块名但test后缀基本确定这是个功能验证工程。这种测试工程最大的价值不是“能跑”而是“验证某个 API 在当前 NX 版本下能不能用、用法是不是和文档一致”。我见过太多工程师在正式项目里被一个 API 卡住最后发现是版本行为差异导致的。所以fam_test这类工程本质上是给自己留的技术验证基线——以后再遇到同样问题直接翻这个工程就能确认。1.2 三个决定成败的准备工作很多人在 NX 二开上栽跟头不是代码写不对而是最开始的三个准备没做好。第一个准备是确认 NX 版本和编译器版本的匹配关系。这是老生常谈但永远有人踩坑的点。NX 12.0 官方推荐 Visual Studio 2015 或 2017NX 2206 系列推荐 VS 2017 或 2019NX 2306 之后逐步转向 VS 2019/2022。你用 VS 2022 去编译给 NX 12 用的 DLL运行时经常会出现奇怪的崩溃就是因为 C 运行库对不上。第二个准备是环境变量。每次启动 NX 之前UGII_BASE_DIR、UGII_ROOT_DIR必须指向正确的安装目录PATH里也要包含UGII_ROOT_DIR。如果不确定可以在 NX 安装目录下找到ugii_env.dat文件里面会写明所有关键环境变量的默认值。第三个准备是明确的入口函数约定。NX Open C 插件和普通 C 程序完全不同它不是从main函数开始跑的而是从 NX 进程内加载你编译出来的 DLL然后调用约定的入口函数ufusr。这个入口函数会接收 NX 传进来的参数执行完毕后还要告诉 NX“我这个 DLL 能被卸载吗”。这些约定如果没搞清楚后面写再多代码都白搭。2. 环境基础NX Open C 开发的底层配置2.1 版本选型编译器怎么和 NX 配对选编译器这件事直接决定你后面三个月的开发体验。我个人的经验是优先选择 NX 官方帮助文档里明确列出的 Visual Studio 版本不要追新。比如 NX 12.0 的帮助文档中就明确写了支持 VS 2015 和 VS 2017你用 VS 2019 编译也能弹窗跑起来但一旦用到某些依赖内部 ABI 的接口就可能出现“莫名其妙的内存错误”。这不是代码问题是编译器版本导致的二进制兼容问题。关于 Windows SDK 版本建议使用 VS 安装时自带的对应版本即可不要刻意追求最新版。NX 毕竟是工业软件求稳比求新重要得多。2.2 项目属性配置include、lib、dll 一个都不能少在 Visual Studio 中创建一个空的 DLL 项目后需要手动配置以下几项。附加包含目录要指向 NX 的二次开发头文件目录。NX 12 里通常是$(UGII_BASE_DIR)\UGOPEN里面放着NXOpen、uf*.h等头文件。如果你用的是 NX 2206 及以上版本还可能出现NXOPEN目录要把这两个路径都加进去。附加库目录同样指向$(UGII_BASE_DIR)\UGOPEN目录。链接时通常需要加入这些库库文件名作用libnxopencpp.libNXOpen C 接口基础库含 Session、Part、Point 等核心类libnxopencpp_features.libNXOpen 特征建模相关类如 Feature、Bodylibnxopencpp_annotations.lib注释、尺寸标注相关libnxopencpp_undo.lib撤销栈支持libnxopencpp_fbm.lib基于特征的建模相关常用于同步建模替代方案libugopenint.libUF 风格入口函数库包含 ufusr、UF_initialize 等符号libufun.libUF 函数库UF_MODL、UF_UI 等libnxopenuicpp.lib对话框 UI 相关这里有个容易漏的坑ufusr和UF_initialize这些符号在libugopenint.lib里很多新手只添加了libnxopencpp.lib结果编译通过但链接时报一堆LNK2019就是因为少了这个库。配置完包含目录和库目录后还要在“预处理器定义”里加上_CRT_SECURE_NO_WARNINGS否则 NX 头文件里的一些老式字符串函数会触发一堆警告虽然不影响编译但看着心烦。2.3 在 NX 里跑起来第一个测试程序环境配置完成后先不要写复杂逻辑按下面的最小结构验证整个链路是否打通。#include uf.h #include uf_ui.h extern C DllExport void ufusr(char* param, int* returnCode, int rlen) { UF_initialize(); UF_UI_open_listing_window(); UF_UI_write_listing_window(fam_test: Hello NX Open!\n); UF_terminate(); } extern C DllExport int ufusr_ask_unload(void) { return UF_UNLOAD_IMMEDIATELY; }这段代码做了几件事包含 UF 核心头文件声明ufusr为导出函数在 NX 的 Listing Window 里打印一行文字最后声明 DLL 可以立即卸载。UF_initialize和UF_terminate的配对是关键所有 UF 接口调用都必须在两者之间进行。编译生成 DLL 后在 NX 里可以按CtrlU选择这个 DLL也可以直接用File - Execute - NX Open加载。如果 Listing Window 里出现了那行文字说明环境全部打通了。3. 代码实现四个高频需求的完整示例3.1 获取光标位置并创建点获取光标位置是 NX 二开里一个很常见的交互需求比如用户点击屏幕某个位置程序在那个位置放置特征或创建对象。较新的 NX 版本提供了UF_UI_ask_cursor_position可以直接拿到光标在 WCS 下的坐标值。注意是 WCS不是绝对坐标系如果你的工作坐标系被旋转过这个值就会和绝对坐标不同。#include uf.h #include uf_ui.h #include uf_obj.h #include NXOpen/Session.hxx #include NXOpen/Part.hxx #include NXOpen/Point.hxx #include NXOpen/PointCollection.hxx #include NXOpen/Coordinates.hxx extern C DllExport void ufusr(char* param, int* returnCode, int rlen) { UF_initialize(); double cursor_pos[3] { 0.0, 0.0, 0.0 }; UF_UI_ask_cursor_position(cursor_pos[0], cursor_pos[1], cursor_pos[2]); NXOpen::Session* session NXOpen::Session::GetSession(); NXOpen::Part* workPart session-Parts()-Work(); NXOpen::Point3d pnt(cursor_pos[0], cursor_pos[1], cursor_pos[2]); workPart-Points()-CreatePoint(pnt); UF_terminate(); }这段代码混合了 NXOpen C 接口Session、Part、Points和 UF 接口UF_UI这在 NX 二开里非常常见两者可以共存。注意 C 风格接口不需要UF_initialize但 UF 接口必须要有所以UF_initialize和UF_terminate包住全部代码是最稳妥的写法。如果遇到返回坐标始终是(0,0,0)的情况检查一下是不是在非交互上下文里调用。这个函数必须在 NX 图形窗口有鼠标交互时才有实际意义。3.2 获取任意点的坐标含装配环境处理获取点的坐标听上去很简单但如果点在装配环境里或者点不在工作部件里问题就来了。单机环境下遍历所有点对象读取坐标最简单的方式是这样#include uf.h #include uf_obj.h #include uf_part.h #include uf_point.h #include uf_assem.h #include vector static void CollectPointCoordinates(tag_t partTag, std::vectorUF_POINT_data_t outPoints) { tag_t objectTag NULL_TAG; int type 0; int subtype 0; UF_OBJ_cycle_objs_in_part(partTag, UF_point_type, objectTag); while (objectTag ! NULL_TAG) { UF_POINT_data_t pointData; UF_POINT_ask_data(objectTag, pointData); outPoints.push_back(pointData); UF_OBJ_cycle_objs_in_part(partTag, UF_point_type, objectTag); } }这里用了UF_OBJ_cycle_objs_in_part遍历部件里指定类型UF_point_type的所有对象。有个隐藏问题如果点对象不在工作部件里比如是在引用集里你需要在遍历前确认 partTag 对应的部件已经加载否则返回的 tag 可能无效。如果在装配环境下需要把坐标转换到绝对坐标系可以获取所属实例的变换矩阵然后做一次坐标变换。这个属于进阶操作很多工程师直接忽略导致在装配里拿到的坐标和实际显示位置不一致。用UF_ASSEM_ask_component_data拿到实例的 origin 和 matrix再与原始坐标做矩阵乘法即可。3.3 判断孔面还是轴面判断一个圆柱面是孔面内表面还是轴面外表面是很多自动化建模需求里的基础操作。比如自动化倒角就要区分是孔口倒角还是轴端倒角。核心思路是拿到圆柱面的轴线方向、轴线上的参考点、面上某个参数点的法向然后判断法向和径向向量的点积符号。如果法向指向轴线说明是内表面孔面如果法向背离轴线说明是外表面轴面。#include uf.h #include uf_modl.h #include uf_curve.h // 返回值true 表示孔面false 表示轴面 bool IsHoleFace(tag_t faceTag) { int faceType 0; UF_MODL_ask_face_type(faceTag, faceType); if (faceType ! UF_cylinder_type faceType ! UF_cone_type) return false; double point[3] { 0.0 }; double dir[3] { 0.0 }; double box[6] { 0.0 }; double radius 0.0; double radData[2] { 0.0 }; double pData[4] { 0.0 }; int normDir 0; UF_MODL_ask_face_data(faceTag, faceType, point, dir, box, radius, radData, pData, normDir); double u (pData[0] pData[2]) * 0.5; double v (pData[1] pData[3]) * 0.5; double pt[3] { 0.0 }; double u1[3] { 0.0 }; double v1[3] { 0.0 }; double u2[3] { 0.0 }; double v2[3] { 0.0 }; UF_MODL_ask_face_props(faceTag, u, v, pt, u1, v1, u2, v2); double n[3] { 0.0 }; n[0] u1[1] * v1[2] - u1[2] * v1[1]; n[1] u1[2] * v1[0] - u1[0] * v1[2]; n[2] u1[0] * v1[1] - u1[1] * v1[0]; // normDir 为负时法向需要取反 if (normDir 0) { n[0] -n[0]; n[1] -n[1]; n[2] -n[2]; } // 计算面上点到轴线的投影点 double vx pt[0] - point[0]; double vy pt[1] - point[1]; double vz pt[2] - point[2]; double d dir[0] * dir[0] dir[1] * dir[1] dir[2] * dir[2]; if (d 1e-12) return false; double t (vx * dir[0] vy * dir[1] vz * dir[2]) / d; double projX point[0] t * dir[0]; double projY point[1] t * dir[1]; double projZ point[2] t * dir[2]; double radialX pt[0] - projX; double radialY pt[1] - projY; double radialZ pt[2] - projZ; double dotVal n[0] * radialX n[1] * radialY n[2] * radialZ; // 法向与径向同向说明法向朝外是轴面法向朝内是孔面 return dotVal 0.0; }这段代码里有几个关键点容易出错。第一个是UF_MODL_ask_face_data返回的pData是面的参数范围对应 u、v 的最小值和最大值中间值就是面上接近中心的参数点。第二个是normDir的符号含义它表示 NX 内部存储的法向方向与由曲面参数导数计算的叉积方向是否一致必须根据它调整法向符号。第三个是UF_MODL_ask_face_props中的u1、v1、u2、v2这里u1和v1是 u 向和 v 向的导数叉积可以求出法向。如果筒面轴线方向是任意方向的这段代码都能正确处理因为投影计算是通用的。3.4 测量功能优先用 NXOpen 测量接口热词里出现了“pk 测量”这里我多说一句。PK 是 NX 底层的参数化内核接口直接调 PK 做测量的门槛很高而且版本兼容性比较差。对大多数二次开发需求来说NXOpen.Measurement系列接口或者 UF 的UF_MODL_ask_min_dist已经足够。#include uf.h #include uf_modl.h extern C DllExport void ufusr(char* param, int* returnCode, int rlen) { UF_initialize(); tag_t obj1 UF_UI_ask_sel_object(选择第一个面, UF_face_type); tag_t obj2 UF_UI_ask_sel_object(选择第二个面, UF_face_type); if (obj1 ! NULL_TAG obj2 ! NULL_TAG) { double minDist 0.0; double pt1[3] { 0.0 }; double pt2[3] { 0.0 }; UF_MODL_ask_min_dist(obj1, 0, obj2, 0, minDist, pt1, pt2); UF_UI_open_listing_window(); UF_UI_write_listing_window(最小距离: ); UF_UI_write_listing_window(UF_UI_...); } UF_terminate(); }注意UF_MODL_ask_min_dist的第二个和第四个参数是给“精确解析”用的传 0 表示用默认方式。这个函数拿到的是最小距离和对应的两个最近点坐标在自动化报告、碰撞检查里非常实用。4. 编译、运行、调试踩坑最集中的一段路4.1 编译链接失败的典型错误错误一LNK2019 无法解析的外部符号 ufusr。这个报错十有八九是少了libugopenint.lib或者入口函数前面没有加extern C DllExport。C 编译器会做名字修饰不加extern C符号名就会变成一堆乱码链接器自然找不到。错误二C2065 UF_point_type 未声明的标识符。这说明头文件没包含完整你要检查是否包含了uf_object_types.h因为很多对象类型的宏定义在这个头文件里。建议写代码时直接包含uf.h它会带出大部分基础定义。错误三编译时一堆“无法打开包括文件 NXOpen/Session.hxx”。这是附加包含目录没配置对检查项目属性里的 VC 目录或 C/C 常规附加包含目录确认指向了正确的 NX 头文件目录。错误四链接时库解析冲突。如果你同时链接了多个版本的libnxopencpp*.lib比如 NX 12 的库和 NX 2206 的库混在一起会出现一堆重定义错误。确认项目里没有混用不同版本的库目录。4.2 NX 进程内调试的两种思路调试 NX 插件和调试普通程序不一样插件运行在 NX 进程内你不能直接按 F5 启动它。常用的调试方式有两种。第一种是附加到进程先把 NX 启动起来然后在 VS 里选择“调试 - 附加到进程”选择UGII或nx进程断点就能命中。这种方式最灵活推荐使用。第二种是设置 NX 启动路径在项目属性 - 调试 - 命令里填入 NX 的可执行文件路径比如C:\Program Files\Siemens\NX12.0\NXBIN\ugraf.exe再设置命令参数为空这样按 F5 就会启动 NX比较适合每次从头验证的场合。调试时有个细节断点命中的是 DLL 里的代码如果 DLL 是 Release 版局部变量可能显示不出来。建议调试用 Debug 配置编译但发布给用户时用 Release因为 Release 的性能和启动速度都更好。4.3 稳定性问题内存、句柄和异常NX 二开插件运行在 NX 进程内你的崩溃就是 NX 的崩溃所以代码质量要求比普通程序高很多。常见的稳定性问题有三类。第一类是UF_initialize/UF_terminate不配对。早期版本里UF_initialize是每线程有计数器的多次调用就要多次UF_terminate配对。现在虽然强壮了一些但最好还是保持一次调用一次终止的习惯。第二类是 tag 值过期问题。NX 中对象 tag 在模型修改后可能失效你缓存了一个面 tag执行了布尔运算后再用它轻则返回失败重则崩溃。解决办法是尽量在使用前重新获取 tag不要长期缓存。第三类是异常跨模块边界的问题。在 C 插件里抛出异常如果跨越了 DLL 边界而且没有捕获可能导致未定义行为。我的习惯是在ufusr入口处用try/catch(...)把所有代码包住捕获所有异常后返回一个错误码避免异常泄漏到 NX 内部。extern C DllExport void ufusr(char* param, int* returnCode, int rlen) { int errorCode 0; try { UF_initialize(); // 实际业务逻辑 UF_terminate(); } catch (...) { errorCode 1; } *returnCode errorCode; }5. 常见问题速查表与避坑经验我在 NX 二次开发里踩过不少坑下面把这些年高频出现的问题整理成速查表对照查找会很方便。现象可能原因解决方案编译后 DLL 加载到 NX 里没反应入口函数名字不对或没导出确认ufusr前有extern C DllExport装配里获取点的坐标和显示位置不一致没有考虑实例变换矩阵用UF_ASSEM_ask_component_data获取矩阵做变换中文注释或字符串乱码编码方式不对设置环境变量UGII_UTF8_MODE1源码保存为 UTF-8UF_UI_ask_cursor_position返回 0NX 版本不支持或调用上下文错误改用UF_UI_get_current_cursor试一下运行时找不到 DLL1309 错误PATH 没包含 NX 运行目录把UGII_ROOT_DIR加入系统 PATH链接一堆 LNK2019缺少libugopenint.lib等库对照 2.2 节的库清单逐项检查在 NX 打印中文正常但保存文件乱码文件编码和 NX 编码不一致统一使用 UTF-8 保存源文件面遍历时某些面取不到对象类型判断错误或面在非工作部件先用UF_OBJ_ask_type_and_subtype打印类型确认还有一个容易被忽视的坑ugii_env.dat 里的环境变量设置。有些用户自己改了UGII_UTF8_MODE或者自定义了路径导致程序在不同 NX 环境里表现不一致。如果排查问题最终找到环境变量头上一定要对比ugii_env.dat的差异这是很多“我这正常但用户那不行”的问题根源。另外建议给 VS 工程开启/bigobj编译选项。NX 头文件模板展开量很大默认的目标文件格式可能不够用加上这个选项可以避免一些莫名其妙的编译错误。6. 从 fam_test 到正式项目开发流程的最后一块拼图fam_test验证完功能后如果要把代码整理成正式项目我会建议做三件事。第一把验证用的测试代码从正式逻辑里抽离。单独建一个test目录放那些打印坐标、辅助验证的代码不要和正式功能混在一起。这样后续维护时不会误删正式逻辑。第二封装一个统一的 NX 工具函数库。比如“判断孔面轴面”这个函数实际项目里可能在倒角、测量、工艺识别等多个模块里用到把它放到公共库里统一维护比在三个地方复制粘贴好得多。第三写一份 API 版本兼容性记录。你在fam_test里验证过的每个关键接口记录下 NX 版本、VS 版本、接口行为是否有异常。这个记录在 NX 大版本升级时特别有用能帮你提前发现哪些接口有变化。从我个人的经验看NX 二次开发的核心不是学会调用几个 API而是建立一套稳定的验证、封装和版本管理机制。fam_test这样的测试工程如果能坚持做下去慢慢就会沉淀成你自己的私有技术手册——遇到问题查工程比翻帮助文档快得多。本文还有配套的精品资源点击获取