做NX二次开发这几年,我主要是用Python写一些CAM自动化的小工具。这个领域其实挺垂直的,但需求一直很实在:比如“坯料尺寸”这个东西,在加工编程里几乎是天天要碰的。手动测量、手动填、模型改版再测一遍,这些重复劳动完全可以用脚本一次性吃掉。这篇就是这个系列的第一篇,先把技术路线和核心代码骨架讲透,后面再慢慢展开通孔、盲孔这些特征识别的问题。
开始之前先交代一下背景:NX二次开发(python)指的就是用NX自带的NXOpen Python API去操作模型、做自动化处理,早期大家习惯叫UG二次开发,现在NX版本更新到1953、2206这些大版本,Python的API覆盖已经相当完整了。这篇文章适合数控工艺、CAM工程师,还有想入门NX二次开发的同学,思路和代码都能直接拿去跑。
1. 坯料尺寸在CAM流程里的位置,以及为什么值得写成脚本
1.1 毛坯是CAM编程的“地基”
做过CAM编程的人都知道,一个完整的加工工序通常要定义三样东西:加工坐标系MCS、零件几何体、毛坯几何体。毛坯(blank)的尺寸看起来就是个简单框,但它直接影响后面几乎所有操作。
举个例子,型腔铣的切削层计算要基于毛坯来定;刀轨的进刀、退刀安全距离要参考毛坯轮廓;碰撞检查和刀轨显示仿真也要有一个真实的毛坯体才能看出来过切和撞刀;甚至侧刃切削深度、拐角减速范围这些参数也会根据毛坯余量自动调整。所以说,坯料尺寸不是“填个数”那么简单,它本质上是一个加工策略的输入边界。
1.2 手动设置毛坯的痛点,做过批量编程的人都会懂
我之前在一个零部件加工厂待过,每天要排产十几套、几十套零件。编程员拿到模型以后,第一步往往就是测量模型最大外形尺寸,然后在CAM里把毛坯的X、Y、Z三个方向尺寸敲进去。
这个流程单独看一次不费劲,但实际生产中经常是这几个状态来回切换:
- 零件模型改版,外形尺寸变了,毛坯没更新,刀路计算出来全是错的;
- 一批外形相似但尺寸不同的零件,同样的工序要重复建立毛坯参数,人工比对很容易串数据;
- 想把毛坯尺寸输出到工艺卡片或者下游系统,只能靠手工抄录,一个数字抄错就是一道报废工序。
这些问题靠人盯着是盯不住的。用NX二次开发(python)写一个自动化工具,把“读取零件模型最大外形尺寸、换算成坯料的长宽高、输出或写入CAM设置”这几步串成一条流水线,才是效率层面的正解。
1.3 这篇工具脚本到底能干什么
如果只提一个最核心的目标,那就是:在NX里用Python自动获取当前工作部件所有实体体的包围盒尺寸,并把结果按方向整理成形如“X=120.0000, Y=80.0000, Z=45.0000”的毛坯参照尺寸,输出到信息窗口,同时为后续直接写回CAM的Blank几何体做准备。
这个工具做好以后,点一下运行,毛坯尺寸就出来了,不用再去测量工具里手动点。而且后续完全可以扩展成批量处理整个文件夹的prt文件,导出一个工艺尺寸清单。这一篇先把最基础、最重要的数据获取环节讲明白。
2. 技术选型:为什么用Python,以及开发环境怎么搭
2.1 Python、C++、C#三条路线怎么选
NX二次开发历史上主要有三种语言路线:C++、C#(或者说.NET)、Python(Journal)。很多老工程师习惯C++,因为它最底层、性能最好,适合写复杂的算法和自定义UI;C#在某些企业内部管理系统集成上用得也多。但我个人现在偏向Python,原因很直接:
- Python代码写起来快,尤其在处理批量任务、文本输出、数据整理这些场景下,开发效率高得多;
- NX从1847系列开始,Python的API覆盖程度大幅提升,很多以前必须用C++或者GRIP的事情,现在Python都能做;
- 对于不做重型交互界面、不搞超大计算量的自动化小工具来说,Python完全够用。
当然Python也有它的短板,比如复杂曲面算法、大装配体遍历时会比较慢,但做坯料尺寸这种级别的操作,根本感觉不到性能差异。
2.2 NX版本和Python环境的搭配
NXOpen Python的API在NX 1953系列之后变得比较稳定,我建议如果条件允许,至少用NX 1953以上版本做二次开发,Python解释器和API文档都更齐全。老版本的NX虽然也能跑Python,但一些新方法比如包围盒获取、CAM对象操作的行为会有差异,后面调试起来会多花时间。
环境配置上,其实不需要单独安装Python解释器,NX安装目录下自带了一个Python环境。比如我本机的路径大概是:
C:\Program Files\Siemens\NX2206\NXBIN\python你们可以根据自己的安装盘符和版本找到。如果你想用VSCode做开发,只要在VSCode里配置Python解释器路径指向这个目录,就能获得语法提示和代码补全能力。具体配置不展开,核心就是在.vscode/settings.json里指定python.pythonPath,然后装上Python插件就行。
提示:NX自带的Python环境是精简过的,不建议在里面用pip装第三方库。如果你需要在二次开发里用numpy、openpyxl这些库,要么手动把库文件放到NX搜索路径下,要么在外部解释器里先用Anaconda做好测试再搬进NX环境,这块单独规划更稳妥。
2.3 开发调试时的起点:从录制Journal开始
有一个新手阶段特别推荐的做法:在NX里手动操作一遍目标功能,同时用“工具”菜单里的Journal录制功能把过程录成Python脚本,然后再对这个脚本做二次修改。这个方法能帮你快速确认当前NX版本到底调用了哪些API,比我凭文档瞎猜准确得多。
比如你想知道“获取当前部件”在NXOpen Python里怎么写,最稳的方法就是录一个空操作,生成的开头几行就是标准模板。我的经验是:每个大版本之间API有细微变化,录制出来的代码永远是当前版本最有说服力的参考资料。
3. 核心实现:自动读取实体包围盒并计算坯料尺寸
3.1 拿到当前工作部件和所有实体体
NXOpen Python的命名空间里,一切操作的起点是Session对象。Session是当前NX进程的总入口,通过它可以拿到所有打开的Part、当前工作Part、UI对象、信息窗口等等。这部分代码几乎是所有NXOpen Python脚本的公共前缀:
import NXOpen def main(): the_session = NXOpen.Session.GetSession() the_ui = NXOpen.UI.GetUI() work_part = the_session.Parts.Work if work_part is None: the_ui.NXMessageBox.Show("提示", NXOpen.NXMessageBox.DialogType.Error, "请先打开一个工作部件") return拿到work_part以后,就可以遍历它下面的所有Body对象。在NXOpen对象模型里,Body是几何体的容器,但Body可能不是实体,也可能是片体。为了识别“坯料尺寸”,我们应该只统计实体体,也就是IsSolidBody为True的那些。
solid_bodies = [body for body in work_part.Bodies if body.IsSolidBody] if not solid_bodies: the_ui.NXMessageBox.Show("提示", NXOpen.NXMessageBox.DialogType.Error, "当前部件里没有实体体") return这里用列表推导式把筛选和收集一步做完,代码看起来清爽多了。
3.2 用Body.GetBoundingBox获取包围盒
拿到实体体以后,接下来就是核心:获取它的包围盒。NXOpen Python里面有两种常用的获取方式:
第一种是用NXOpen对象模型直接调用Body上的GetBoundingBox方法,返回一个NXOpen.Box对象。这个Box对象里有X、Y、Z三个方向向量,和一个Point点。X、Y、Z向量的长度可以理解为该方向上的包围盒尺寸,Point是包围盒的一个角点。
第二种是走UF函数,比如UF_MODL_ask_bounding_box,需要先从NXOpen对象拿到UF对象的tag,再调用UF函数。这种方式在老的C++项目里很常见,Python也可以调用,但需要额外处理tag转换,代码会稍微啰嗦一点。
我建议在新版本NX里直接用GetBoundingBox,原因有两个:一是代码可读性好,二是它返回的就是NXOpen对象,后面如果要接其他NXOpen API操作,不需要再转换。
import math listing_window = the_session.ListingWindow listing_window.Open() for idx, body in enumerate(solid_bodies, start=1): bbox = body.GetBoundingBox() x_len = bbox.X.Length() y_len = bbox.Y.Length() z_len = bbox.Z.Length() listing_window.WriteFullline("实体 %d: X=%.4f, Y=%.4f, Z=%.4f" % (idx, x_len, y_len, z_len))有一点要注意,包围盒的X、Y、Z方向未必和WCS完全一致,它返回的是模型空间里的绝对轴对齐包围盒。如果零件被旋转过,X、Y、Z长度是零件在绝对坐标系各方向上的跨度,而不是工件长宽高。实际做坯料尺寸时,大家通常希望按MCS加工坐标系来取尺寸,这个问题在后面的注意事项里详细讲。
3.3 单位问题:毫米和英寸别搞混
NX模型有公制、英制之分,包围盒返回的长度数值在NX内部是按当前部件的显示单位还是内部毫米单位计算,不同版本有细微差别。我的经验是:在信息窗口打印之前,先判断一下当前部件的单位,然后决定要不要换算。
unit_type = work_part.Preferences.Units if unit_type == NXOpen.Part.Units.Inches: scale_factor = 1.0 / 25.4 # 内部毫米转英寸 else: scale_factor = 1.0严格来说,NX内部几何数据是用毫米保存的,但GetBoundingBox返回的单位可能已经按部件单位做了转换,所以要结合实测判断。稳妥的做法是在开发机上用同一个测试模型分别设置公制、英制跑一遍,打印出来对比一下,这样你就知道自己所用NX版本的真实行为。
3.4 坐标变换:把绝对包围盒换算到MCS方向
刚才提到,直接取到的包围盒是绝对坐标系的轴对齐盒子。在CAM编程里,“坯料长宽高”通常是相对于加工坐标系MCS的定义,特别是四轴、五轴编程时,工件装夹方向未必和绝对坐标一致。
如果你只是做三轴加工,零件放平了,那绝对包围盒的X、Y、Z和MCS的X、Y、Z基本对得上。但严谨的做法还是把模型变换到MCS坐标下来计算。思路是获取MCS的Matrix,用拍摄变换把实体体从模型空间映射到MCS坐标系下,再计算变换后体的包围盒。
这部分涉及矩阵运算,代码会多一些。我提供一个简化方案:直接用NXOpen的Matrix3x3和Point3d写一个坐标变换函数,把包围盒的八个角点都换算到MCS矩阵下,再重新求X、Y、Z最大值最小值。
mcs = None cam_setup = work_part.CAMSetup if cam_setup is not None and cam_setup.MCSs.Count > 0: mcs = cam_setup.MCSs.Element(0)如果当前部件还没有创建任何CAM设置,CAMSetup会是空引用,这一步就要加判断,让用户先创建一个基本的MILL_ORIENT或者直接用WCS兜底。这也是为什么我说这个工具的第一版最好保守一点:优先支持三轴平放零件的场景,等骨架跑通后再加MCS矩阵变换。
3.5 完整代码示例
把上面的内容组合起来,一个可以独立运行的版本就是这样:
import NXOpen def main(): the_session = NXOpen.Session.GetSession() the_ui = NXOpen.UI.GetUI() work_part = the_session.Parts.Work if work_part is None: the_ui.NXMessageBox.Show("提示", NXOpen.NXMessageBox.DialogType.Error, "请先打开一个工作部件") return listing_window = the_session.ListingWindow listing_window.Open() listing_window.WriteFullline("========== 坯料尺寸自动提取 ==========") solid_bodies = [body for body in work_part.Bodies if body.IsSolidBody] if not solid_bodies: the_ui.NXMessageBox.Show("提示", NXOpen.NXMessageBox.DialogType.Error, "当前部件里没有实体体") return for idx, body in enumerate(solid_bodies, start=1): bbox = body.GetBoundingBox() x_len = bbox.X.Length() y_len = bbox.Y.Length() z_len = bbox.Z.Length() listing_window.WriteFullline("实体 %d: 长(X)=%.4f, 宽(Y)=%.4f, 高(Z)=%.4f" % (idx, x_len, y_len, z_len)) listing_window.WriteFullline("========== 完成 ==========") if __name__ == "__main__": main()在NX里用“文件-执行-NX Open Python”选择这个脚本,信息窗口就会列出当前部件所有实体体的外形尺寸。这就是坯料尺寸的数据基础。
3.6 为什么先输出到信息窗口,而不是直接写回CAM
有的同学可能会问,既然标题是“坯料尺寸”,为什么不一步到位把这些尺寸写入CAM的Blank设置?原因是这样:
第一,NX CAM的Blank几何体有三种来源:自动块、几何体选择、小平面体。自动块模式下系统已经能自动读取部件包围盒并加上偏置,我们要做的是控制偏置量和方向,而不是重复制造一个包围盒写进去;几何体模式下则需要用户选择毛坯体,那是另一套选择逻辑。
第二,信息窗口输出是零风险操作,不会改坏模型。对刚接触NXOpen Python的人来说,先跑通数据获取,再去动CAM设置,可以避免一层套一层的调试地狱。
第三,从工程使用场景看,很多人拿到坯料尺寸其实只是为了记录工艺卡片或者做离线核对,并不一定都要写回NX界面。先有输出,再谈写入,思路更清晰。
4. 从坯料尺寸往下走:通孔和盲孔的拓扑识别
4.1 为什么一个“读尺寸”的脚本会关联到通孔盲孔
这个话题本来应该放在后面几篇,但热搜词里“nx open 如何区分通孔和盲孔 拓扑”被多次提到,我在实际开发中也确实会遇到,这里先做一个思路预告。
当你拿到毛坯尺寸和成品尺寸以后,两者相减得到的“去料体积”里,孔特征占了很大比例。做钻孔循环、镗孔、攻丝这些工序时,CAM系统需要知道这个孔是通孔还是盲孔,从而计算刀具伸出长度、安全间隙和底孔余量。这时候如果能让程序自动识别孔的类型,整个自动编程流水线就更完整了。
4.2 一个朴素的拓扑判定思路
区分通孔和盲孔,最原始也是最直观的方法是看几何拓扑:一个通孔,在圆柱面上有两个开放的圆边,圆柱面两端都不会被一个封闭平面堵住;而盲孔,圆柱面一端连着底面,底面和圆柱面共享一条封闭圆边。
在NXOpen Python里,可以遍历实体体的所有面,筛出圆柱面(CylindricalFace),然后检查它和相邻面之间的边关系:
- 如果圆柱面的两条边都是自由边,或者邻接面不是平面端盖,很可能就是通孔;
- 如果圆柱面有一条边和一个平面共享,并且这个平面刚好垂直于圆柱轴线,那大概率是盲孔底面。
这个判定方式不够严谨,容易受到圆角、倒角、阶梯孔这些因素的影响,但作为第一版自动识别逻辑,它已经能覆盖掉大部分规则孔。更精确的做法是走到边的层面,统计圆柱面边界的拓扑结构,或者用像uf_modl_ask_feat_object这类函数从特征对象反查几何体,拿到面集合以后再分析邻接矩阵。
我在做这个功能的时候,建议先不要一上来就搞高深的算法,而是先用最朴素的面邻接规则做一个分类,然后拿真实零件去跑一遍,统计哪些孔判错了,再针对错例补规则。这个“朴素规则+案例迭代”的开发模式,比一开始就试图构建完美算法靠谱得多。
这块内容我准备在系列的第二篇专门展开,到时候会把圆柱面识别、边拓扑遍历、通孔盲孔分类的完整代码都放出来,这里先留个引子。
5. 调试实录与常见坑位速查
5.1 最容易踩的坑:GetBoundingBox的坐标参考
我第一次在实际零件上跑这个脚本,发现输出的X、Y、Z数值和我在测量工具里看到的不一样,找了大半天,最后发现问题是:模型是用其它三维软件转过来的,导入NX以后整个体有一个旋转偏移,而GetBoundingBox返回的是绝对坐标包围盒,不是我视觉上看到的“工件的长宽高”。
这个问题在转格式模型里特别常见。解决办法是先把体的WCS方向或者MCS矩阵拿到,做一次坐标变换再求包围盒。如果你只是做快速粗估,也可以先手工把WCS重新定向到工件基准边再跑脚本,效果一样。
5.2 多个实体体的情况
有些prt文件里不仅有零件实体,还有辅助体、夹具体、基准块。如果脚本不筛选,就会把辅助体也算进毛坯尺寸里,导致结果偏大。
我的处理习惯是:先按特征名称或者颜色过滤掉“非加工体”;如果项目规范统一,也可以约定夹具体放在单独的引用集里,脚本只遍历工作引用集内的实体。没有统一规范的情况下,最稳妥的方案是脚本弹出可勾选的实体列表,让使用者手动确认,但我个人不太喜欢这种打断流程的交互,更倾向于在模型规范阶段就约定好。
5.3 信息窗口中文乱码
NXOpen Python的ListingWindow对中文支持在不同版本上表现不一致,有时候中文标签会变成乱码。如果你遇到这种情况,最简单的绕过方式是脚本里所有输出文本用英文或者拼音,数值本身不受影响。
如果一定要输出中文,可以尝试在脚本开头设置代码文件的编码格式,确保保存为UTF-8 with BOM,并且在NX环境变量里把系统区域设置调整好。不过说实话,对这种内部工具来说,英文输出反而更省心,反正关键内容是数字。
5.4 一个调试技巧:用录制功能生成基准脚本
我在开头提到过Journal录制,这里再强调一个具体用法:你先手动对一个实体体执行“测量-边界盒”,同时录制Journal,录完以后打开生成的Python文件,搜索BoundingBox相关的调用,你会看到当前NX版本精确的方法名和参数签名。这是所有API困惑的最终解答方式,比翻文档快得多。
5.5 常见问题速查表
| 现象 | 最可能原因 | 处理办法 |
|---|---|---|
| 脚本报错找不到Session | NX版本太老,或者代码被当成普通Python运行 | 确认脚本是从NX的“执行NX Open Python”入口运行 |
| 输出尺寸和实际测量不一致 | 模型有旋转,绝对包围盒不等于工件长宽高 | 先定向WCS,或做MCS矩阵变换后再计算 |
| 没有任何实体打印 | 部件里全是片体或小平面体 | 检查IsSolidBody筛选条件,确认实体存在 |
| 中文输出乱码 | ListingWindow编码兼容问题 | 改用英文输出,或调整环境区域设置 |
| GetBoundingBox方法不存在 | NX版本较老,Python API不完整 | 升级NX,或改用UF_MODL_ask_bounding_box函数 |
6. 后续扩展方向
这篇文章作为“坯料尺寸”系列的第一篇,代码重点放在“读取实体体包围盒”这个核心动作上。下一步可以做的事情其实很多:
- 实现MCS矩阵变换,让包围盒尺寸真正对应加工坐标方向;
- 加入偏置参数,直接把“零件最大尺寸+偏置量”输出为自动块的毛坯尺寸;
- 研究NX CAM Blank几何体的Python API,把尺寸数据直接写回加工导航器;
- 写一个批量模式,遍历文件夹下所有prt文件,生成一张毛坯尺寸清单表格。
我个人在实际操作中的体会是,这类二次开发小工具,代码本身不复杂,真正花时间的永远是对NX对象模型的理解和边界情况的处理。哪怕你的第一版只做三轴、只处理单实体、只输出数字,也完全够用。先把这条基础链路跑通,后续再往复杂场景延伸就只是加法问题。
下一篇我会重点讲通孔和盲孔的拓扑识别,这中间要用到体面遍历、边分类这些更细的对象关系,正好可以接着今天的包围盒基础继续往下挖。