简介:基于NX2406版本、面向Visual Studio 2022环境的UG NX二次开发编程模板,专为需要在C++环境下快速搭建NX插件工程的开发者设计。模板配套作者NX2406系列配置教程使用,适用于UG NX高级定制、用户界面扩展及自动化功能开发,能有效解决从零配置项目时版本不匹配、工程结构混乱等痛点。压缩包共7个文件,体积仅15KB,包含C++源文件、数字签名资源、项目配置文件、Visual Studio项目模板、图标和说明文档,结构紧凑,可快速导入VS2022使用。这套模板已有1737人学习/下载,对NX二次开发初学者及进阶者均有参考价值。借助这套模板,使用者可一步获得标准化项目骨架、签名与图标配置,再配合作者博客中的详细配置讲解,能大幅缩短环境搭建时间;若仍遇配置问题,还可联系作者远程协助或通过闲鱼下单解决,把更多精力留给实际功能开发。 很多朋友在UG NX二次开发上卡住,往往不是卡在某个API不会调,而是连项目从哪建、DLL怎么挂载、菜单怎么注册都理不顺。我之前在NX2406这个版本上集中做了一批自动化工具,从建模辅助到PMI批量处理都涉及,反复折腾之下沉淀出一套比较顺手的编程模板。这篇就把这套基于NX2406的二次开发模板拆开讲清楚,包括项目结构、核心模块、高频功能的实现方式,以及版本升级过程中踩过的坑。不管你是刚入门准备搭环境,还是已经在老版本上写了几年想迁移到NX2406,都可以拿这份经验当个参考。
1. NX2406开发环境搭建:版本选择与VS匹配的坑
1.1 为什么我最终锁定了NX2406
很多老工程师还在用NX12甚至NX10做二次开发,原因不外乎是老代码稳定、资料多。但NX12毕竟是2017年的版本了,这几年NX在Open API层面做了不少调整,尤其是Block UI Styler生成的代码结构、NXOpen命名空间里一些方法的废弃和替代,在NX2206之后变化明显。NX2406这个版本属于当前较新的迭代,它修复了大量老版本中对话框刷新和撤销栈的问题,而且对C++标准库的支持更完整。
我建议你现在做新项目时直接以NX2406为基准确认功能边界。如果你最终要给客户部署,也尽量保证对方主版本不低于NX2406,否则很多新接口用不了。另外,NX官方从NX2007开始引入了语义化版本号和季度发布节奏,NX2406对应的是2024年6月的版本,这个版本延续了NX2206以来的Java和.NET运行时要求,对环境干净程度要求更高。
1.2 Visual Studio版本匹配是第一个大坑
NX2406官方推荐的是VS2019或更高版本,我自己测试下来,VS2019 16.11.1以上版本最稳。用VS2022也可以,但需要额外注意平台工具集的选择。如果你继续用VS2015或VS2017,编译出来的DLL在NX2406启动时经常报“无法加载模块”或“找不到指定的过程”,这类错往往不是代码问题,而是编译器生成的C++运行时与NX自身依赖的MSVCP版本冲突。
项目属性里需要确认这几项:
- 平台选择x64,NX2406已经基本放弃32位
- 字符集使用多字节字符集,NXOpen C++头文件对Unicode的处理比较别扭
- C++语言标准建议ISO C++17
- 附加包含目录指向NX安装目录下的UGII和UGOPEN
提示:如果你装了多个NX版本,附加包含目录一定要精确到具体路径,否则编译器会抓到老版本的头文件,导致类型定义冲突。
1.3 内部开发与外部开发的选型逻辑
UG NX二次开发分两种模式:内部开发(Internal)和外部开发(External)。内部开发编译产出DLL,由NX进程加载,能直接操作当前会话中的模型和UI;外部开发则是独立EXE进程,通过NXOpen.Remote或命名管道和NX通信。
对于模板来说,我基本只保留内部开发模式。因为外部模式在NX2406里要做大量数据交换协议处理,实时性差,而且许可证处理更麻烦。内部模式的入口是标准的ufusr函数,也就是NX启动时扫描并加载的入口函数。我用C++写模板时,导出函数一般是这样的:
extern "C" DllExport void ufusr(char* param, int* returnCode, int rlen) { // 1. 初始化UF环境 int errorCode = UF_initialize(); if (errorCode != 0) return; // 2. 调用模板主逻辑 if (runTemplate()) { *returnCode = 0; } // 3. 收尾、卸载UF环境 UF_terminate(); } extern "C" int ufusr_ask_unload() { return UF_UNLOAD_IMMEDIATELY; }这个模板保留了两个导出函数:ufusr是真正的主入口,ufusr_ask_unload决定DLL是否允许立即卸载。我在开发阶段一律用UF_UNLOAD_IMMEDIATELY,这样每次修改代码重新编译后,在NX里不用重启软件,直接卸载菜单按钮再加载就能生效,迭代效率提升非常多。到了发布阶段,再改成UF_UNLOAD_UG_TERMINATE,避免用户误操作导致DLL被卸载。
2. 模板的整体结构与入口设计
2.1 项目目录怎么组织才不混乱
NX二次开发项目看起来不大,但不规划目录结构很快就乱了。我按功能把模板拆成几个约定目录,不只放源码,还把编译产物和NX配置都收进来:
Template2406/ |-- src/ | |-- main/ # ufusr入口、应用主逻辑 | |-- ui/ # Block UI Styler的dlx文件和对应回调类 | |-- commands/ # 菜单命令的具体实现 | |-- utils/ # 日志、几何计算、NXSession封装 | `-- resources/ # 位图、菜单脚本、工具条定义 |-- startup/ # 编译输出的DLL和菜单脚本最终放置目录 `-- README.md这个结构最大的价值是把NX约定的startup目录单独提出来。NX在启动时会自动扫描客户配置目录下的startup文件夹,加载里面的DLL和菜单脚本。我把编译输出直接配置到startup,这样点击编译后,在开发者模式下重启NX就能自动加载新版本,避免手动拷贝DLL造成的版本错乱。
2.2 封装NXOpen和UFun的关键会话访问
NX二次开发有两条技术路线:NXOpen(面向对象)和UFun(旧式C函数)。NX2406里两者并不冲突,UFun的底层函数在部分场景甚至比NXOpen更直接。模板里我封装了一个核心工具类,把两条路线统一起来,业务代码不直接依赖NXOpen::Session或UF_系列全局函数。
class NXSessionUtil { public: static NXOpen::Session* GetSession() { return NXOpen::Session::GetSession(); } static NXOpen::Part* GetWorkPart() { return GetSession()->Parts()->Work(); } static int GetDisplayedObjectCount() { // UFun方式获取绘图区对象数量 int count = 0; UF_OBJ_disp_props(NULL, &count); return count; } };这个封装的价值在于,当NX2406更新某个API时,我只需要改NXSessionUtil一个文件,不用全局搜索替换。比如NX早期版本用UF_OBJ_cycle_objs_in_part遍历对象,新版本在某些环境下推荐用NXOpen::BasePart::IterateObjects。对这种变化,模板里统一做了适配层,业务调用方完全感知不到差异。
2.3 日志模块:二次开发者的“眼睛”
做NX插件最痛苦的是崩溃后不知道哪里挂了。NX2406里JAVA控制台能输出一些底层信息,但业务日志还得靠自己。模板里我用一个简单的单例日志类,同时输出到文件和控制台:
class NXLog { public: static NXLog& Instance() { static NXLog log; return log; } void Info(const std::string& msg) { // 输出到日志文件,同时可选用UF_UGMGR_invoke_function等接口输出到NX窗口 WriteFile("[INFO] " + msg); } void Error(const std::string& msg) { WriteFile("[ERROR] " + msg); UF_UI_open_listing_window(); UF_UI_write_listing_window(msg.c_str()); } private: std::ofstream logFile; };日志等级我分成Info、Warn、Error三级,发布阶段也不会去掉,只是把控制台输出关掉。这样客户现场出问题,拿一份日志回来就能定位到具体命令和函数。很多前辈做开发不重视日志,出了问题只能靠猜测,我在这上面吃的亏太多了,所以模板里日志模块必须优先落地。
3. 高频功能模块:鼠标交互、坐标获取、PMI遍历实现思路
3.1 绘图区窗口句柄获取与鼠标事件处理
网络热词里有人搜“UG二次开发获取绘图区窗口”,这确实是个容易卡住的点。在NX2406中,绘图区是一个OpenGL窗口,但NXOpen没有直接暴露获取原生窗口句柄的公开接口。常用的做法是通过UFun中的UF_UI_get_viewer_window来获取:
void* hWindow = NULL; UF_UI_get_viewer_window(&hWindow); // hWindow就是窗口句柄,可用于后续SetWindowsHookEx等操作拿到窗口句柄后,你可以挂Windows消息钩子捕获鼠标移动、中键按下等事件。模板里提供了一个事件转发类的草案,把所有鼠标事件统一处理,并回调到业务模块。不过需要提醒的是,NX2406的绘图区在不同显示模式下,底层渲染器可能不同(OpenGL和DX12切换),钩子方式在某些显卡环境下会失效。更稳妥的交互方案是用NXOpen的Selection对象加自定义过滤器,而不是直接截取Windows消息。
3.2 点坐标提取与UF_FLTR_CREATE_BOX_ZONE
关于“获取点的坐标”和“创建BOX选择区域”,这两件事经常是连在一起的。模板里写了一个通用点选择器,先启动NX自带的点构造器,获取用户输入点,然后转换到工作坐标系坐标:
double point[3]; UF_UI_select_point("请选择目标点", &point);如果是框选区域再批量取坐标,就要用UF_FLTR_create_box_zone这类UFun过滤器函数。它的本质是在选择阶段构造一个矩形或长方体区域,把落在区域内的对象过滤出来。NX2406里这个函数还在,但官方更推荐用NXOpen::Selection::SetSelectionScope来限定范围。
注意:
UF_FLTR_create_box_zone创建的Zone需要在选择结束后用UF_FLTR_delete_zone释放,否则内存会一直挂着。这个API在老代码里经常被遗忘,实测多个会话后会导致NX体感变卡。
3.3 PMI遍历与批量标注提取
做工艺和检测自动化时,往往要批量读取PMI标注。NX2406中的PMI对象模型比较绕,它不直接挂在特征树上,而是挂在Annotations管理器下。模板里我封装了一个遍历方法:
NXOpen::Annotation* nAnnotation = annotationMgr->CreateAnnotation(...); // 遍历PMI 3D标注 std::vector<NXOpen::Annotations::PMI3D*> pmiList; part->Annotations()->GetPMIs(pmiList);早期版本里用UF_PMI_ask_...系列函数操作PMI,NX2406虽然还兼容Ufun,但新建PMI或修改属性时,NXOpen方案更加完整。如果你的项目只需要读PMI文本和坐标,用UFun更快;如果需要创建新PMI并关联到几何体,建议走NXOpen的Annotations::PMI3DBuilder。
3.4 刻字与特殊建模的模板化封装
“NX二次开发刻字”这个需求在工装夹具和模具标记领域很常见。NX2406里有两种做法:一种是通过CurveTextBuilder创建曲线文字,再拉伸成实体;另一种是使用PMI里的文本标签。模板里封装了第一种路径,核心是设置文字内容、字体、位置和高度:
NXOpen::Features::CurveTextBuilder* textBuilder = workPart->Features()->CreateCurveTextBuilder(NULL); textBuilder->TextString()->SetValue(userText); textBuilder->FontType()->SetValue("NX"); textBuilder->Height()->SetValue(10.0);这里最容易踩坑的是字体,NX默认字体列表和Windows字体不一致,直接填“仿宋”可能无效。你必须在NX的字体配置目录下确认可用的字体名,否则生成的文字会变成默认空心字体变形。
4. 菜单定制与命令注册:从“裸DLL”到可操作按钮
4.1 使用MenuScript注册下拉菜单项
很多开发者的DLL编译成功后,靠输入“File->Execute->User Function”来手动运行,这样效率低,也没法交付给普通用户。NX提供了MenuScript机制,用纯文本脚本定义菜单项和工具条按钮。模板的startup目录里放了一个经典的.men文件:
VERSION 240 EDIT UG_GATEWAY_MAIN_MENUBAR MENU UG_TEMPLATE_MENU BUTTON TEMPLATE_MAIN_ACTION LABEL 我的模板工具(2024) ACTIONS TemplateMainAction END_OF_MENU这里面的关键行是ACTIONS TemplateMainAction,它表示点击按钮后执行DLL中导出的TemplateMainAction函数。所以你的DLL里必须同时导出这个名称:
extern "C" DllExport void TemplateMainAction() { // 调用主业务逻辑 }注意一个细节:菜单脚本里的按钮名称和DLL导出函数名称必须严格一致,包括大小写。NX的解析过程是先加载DLL,然后查找对应符号,符号找不到直接报错并且按钮灰色不可用。很多朋友第一次配置菜单失败就是栽在这个名称匹配上。
4.2 工具条图标和界面资源
NX2406支持更现代化的Ribbon界面,菜单脚本同样可以定义工具条选项卡。模板里的资源目录放两种位图:小图标16x16和大图标32x32。图标文件命名要和按钮名匹配,否则显示成空白占位符。
如果你使用.NET环境做二次开发,还可以用NX提供的Ribbon XML定义完全自定义的选项卡。模板里保留了一份.rxml文件的样例,但实际上我更推荐先用MenuScript把功能跑通,再去优化界面样式。工具条做得再花哨,命令本身不稳定也是白搭。
4.3 启动脚本与DLL部署路径
NX2406启动时会读取环境变量UGII_CUSTOMER_DIRECTORY指向的目录,然后扫描该目录下startup文件夹。我把整个模板的输出路径配置成:
D:\NXDev\Template2406\startup同时设置UGII_CUSTOMER_DIRECTORY指向D:\NXDev\Template2406。这样开发时所有DLL、菜单、图标都在一个目录下管理,发布时把整个目录打包给客户,再让客户配置环境变量即可,操作极其简单。
5. 编译部署与跨版本兼容:模板在真实项目中的落地
5.1 从NX12迁移到NX2406的破坏性变化
如果你的老项目还在NX12下运行,迁移到NX2406前必须有心理准备。我在迁移过程中遇到几个高概率报错:
NXOpen::Session::GetSession()的返回类型没变,但部分属性方法被标记为废弃,编译器只给警告,运行时行为却发生变化,比如部分Ask方法返回空指针。- UFun函数中,
UF_OBJ_cycle_objs_in_part在循环过程中如果回调里修改了对象状态,NX2406会直接抛出异常,NX12则允许这种操作。 - Block UI Styler生成的
.dlx文件格式在不同版本间不兼容。NX2406的Block UI编辑器打开NX12的dlx文件,有时会提示“版本过低,是否升级”。升级后旧版NX将无法打开,因此发布前要确认用户环境。
模板里我在每个工具类顶部加了编译期版本判断:
#if NX_MAJOR_VERSION >= 2406 // 新版本实现 #else // 兼容老版本 #endif这种做法让同一套代码可以同时编译出面向NX12和NX2406的DLL,代价是宏分支增加了阅读成本,但对商业项目来说很值得。
5.2 调试技巧与内存管理
NX二次开发调试比普通C++调试复杂,因为DLL在NX进程内运行,崩溃会连带NX一起挂掉。模板里我强制要求几个习惯:
- 所有UFun调用都要检查返回值,尤其是
UF_initialize和UF_CALL宏包裹的调用,不能假设函数一定成功。 - 涉及NXOpen对象时,不要直接delete,要用
Dispose()或让会话自动管理。NX2406的NXOpen对象生命周期与Part绑定,强删容易出现二次释放。 - 遇到“Access Violation”,先把错误定位到最近一个日志输出点。模板的日志模块在每条关键操作前后都埋了记录,重新编译开发版DLL后,日志文件基本能精确到崩溃前最后一行。
调试时还有一个高效技巧:使用debug版本的DLL,并配置VS的“附加到进程”,把调试器挂到UGII_dynamic.dll所在进程(即NX主进程)。挂上去后可以正常下断点、看调用栈,比靠MessageBox猜位置高效太多。
5.3 我在这套模板上继续迭代的方向
模板里目前已经集成了很多小工具:批量改名、PMI导出、点坐标批量采集、刻字建模等。但为了保持模板的通用性,我没有把业务逻辑写死在入口里,而是拆成commands目录下独立的命令类。这样做的好处是每接一个项目,只新增一个命令类并注册到菜单脚本,主框架完全不用动。
下一步我计划在模板里加入更多UI回调的封装,也就是把Block UI Styler生成的对话框回调代码自动化,减少重复劳动。另外,NX2406对.NET 6的支持更加完善,我也在考虑做一套C#版本的模板,毕竟C#写界面比C++顺手,适合快速交付小工具。
6. 最后想对做二次开发的朋友说几句实在话
如果你刚开始接触UG NX二次开发,建议不要一上来就啃NXOpen完整的API文档,太厚了,根本看不完。最有效的路径是通过一个小需求切入,用本文的模板骨架建个项目,把菜单注册跑通,把DLL加载跑通,你就已经超过了80%的初学者。接下来每个功能模块查文档、写测试代码,哪怕一次只实现一个功能点,一段时间后自然能形成自己的知识体系。
千万别迷信“某个函数能一次搞定所有问题”,NX的API设计很大程度上是历史演进的产物,老接口和新接口并存,同一个功能往往有UFun、NXOpen C++、NXOpen .NET三种实现。优先选NXOpen C++,因为它在功能和性能之间最平衡,而且NX2406对C++编译器兼容性做得很到位。
如果你在配置模板的过程中遇到报错,比如菜单按钮一直是灰色、DLL加载后命令不响应,优先检查三件事:DLL导出函数名称和菜单脚本是否一致、UF_initialize是否已调用、UGII_CUSTOMER_DIRECTORY环境变量是否指向正确目录。这三个问题占了我接触过的80%的加载失败场景。剩下20%,麻烦把日志发给我,我们一起来看看到底是哪一步出了问题。
本文还有配套的精品资源,点击获取