ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

NX二次开发Python实战:获取实体重量与质量属性全解析

NX二次开发Python实战:获取实体重量与质量属性全解析 做NX二次开发的朋友十有八九会遇到类似需求把一个装配里每个实体零件的重量算出来或者把某几个关键件的质量属性导出到报表。这个需求看起来简单真上手写代码时却发现一堆细节——显示部件和工作部件哪个才是当前要统计的实体里混着片体怎么办密度从哪来算出来的体积、重量单位又是什么今天这个NX二次开发python样例专门解决“获取当前显示部件中的每个实体的重量质量信息”这个高频问题。我把它从原理到代码再到踩坑记录完整拆给你希望能帮你少走弯路。1. 功能拆解先搞懂“显示部件里的实体重量”到底在算什么很多人拿到这个需求的第一反应是“遍历实体拿质量属性完事”。真写起来就会发现第一步“遍历哪个部件”就已经能栽跟头。所以先把三个核心概念理清楚后面代码才不会写拧巴。1.1 显示部件和工作部件别再傻傻分不清NX在打开多个模型时界面上同时存在两个角色显示部件Display Part和工作部件Work Part。显示部件就是当前在图形窗口里显示的那个部件用户眼睛看到的、鼠标能点到的都是它。工作部件则是当前处于“可编辑状态”的部件建模命令操作的目标是它。大多数情况下两者是同一个但当你打开一个装配、然后在装配导航器里双击某个子零件时工作部件就会变成那个子零件而显示部件可能仍然是整个装配。这时候如果你只取工作部件统计到的实体就和用户“眼睛看到的”对不上如果只取显示部件又要小心装配中的引用集、隐藏组件等干扰。我们这个样例的需求关键词是“当前显示部件”所以代码里取的是Session.Parts.Display这也是最符合用户直觉的做法——屏幕上显示什么就统计什么。如果你确实需要按工作部件统计代码几乎不用改换一行属性即可。1.2 哪些实体需要统计实体对象遍历规则NX里Part.Bodies()返回的是这个部件下的所有Body对象但Body不全是实体。片体Sheet Body、小平面体Faceted Body、多面体等也都在Bodies集合里它们的“质量”概念和实体完全不同。一个封闭的片体虽然也能算出表面积但谈重量就没有意义。所以遍历时不能无脑全收必须用IsSolid属性过滤掉非实体对象。这里有一个容易踩的坑有些Body在创建时是封闭的但中间被操作改成了开放状态IsSolid判断依然可能为True但UF计算质量属性时会报错或返回异常数据。稳妥的做法是在调用质量计算前再用UF函数做一次类型确认或者至少把异常状态捕捉住不要让单个坏体毁掉整个脚本。另外还要注意“抑制Suppressed”状态。装配里被抑制的组件其体对象可能在Bodies集合里仍然存在但物理上已经被移出模型空间计算它的质量没有意义。我一般会在遍历时加一个条件跳过IsSuppressed的对象。1.3 质量信息不止是重量体积、质心、密度一个都不能少标题里写的是“重量质量信息”但实际项目里拿到实体的质量只是开始。你要做配重分析就需要质心位置要做装箱估算需要体积要验证材料设置需要密度数据。UF_MODL_ask_mass_props_3d这个函数返回的数据里质量、体积、表面积、质心坐标、主惯性矩全都有。我在这个样例里把常用几个值都打印出来并且在导出的CSV里留了扩展列。这样无论你是做BOM汇总还是做后续分析都不用再二次开发一个工具。顺便说一个容易混淆的点NX里“质量”和“重量”在数值上经常被当一回事但严格说质量是物体的固有属性重量是质量乘以重力加速度。NX默认的材料密度和质量属性设置中返回的是质量单位一般是克g而不是牛顿。如果你要的是“重量值”要么自己乘9.8要么在单位设置里切换结果单位。2. 环境准备与实现思路代码本身不复杂但运行环境没配好再完美的脚本也跑不起来。这一节把NXOpen Python的开发方式、脚本运行逻辑和API选型讲清楚。2.1 NXOpen Python环境这么配NX从很早的版本开始就支持Python日记功能。你不需要额外安装Python解释器NX安装时已经内置了Python运行环境并绑定好了NXOpen相关模块。常见入口有两种一是NX界面里的“工具→日记→新建→Python”会自动打开内置的日记编辑器在里面写代码然后通过“工具→日记→回放”直接运行。二是把脚本保存成.py文件然后在“工具→日记→回放”里选择该文件执行。这种方式适合你已经用VS Code、PyCharm等外部编辑器写好了代码再丢进NX里验证。如果你非要在外部IDE里调试也不是不行但需要手动把NX安装目录下的Python模块路径加到环境变量里。以我的经验这样折腾的效率并不高调试NX脚本最顺手的方式还是“内联运行print输出”。NXOpen在运行时会自动把当前会话、当前部件这些上下文绑定好你不需要自己初始化COM连接。2.2 一个脚本的完整逻辑链路整体流程可以用一句话概括取到当前显示部件遍历它下面的所有实体体逐个调用质量属性函数把结果打印或导出。具体到代码层面顺序是这样的获取NXOpen.Session实例这是所有NXOpen二次开发的总入口。通过Session.Parts.Display拿到当前显示部件。遍历Part.Bodies()用IsSolid过滤出实体体。对每个实体体构造UF计算请求调用UF_MODL_ask_mass_props_3d。解析返回数组中的体积、面积、质量、质心等值。打印到NX日志窗口同时写入CSV文件。这套逻辑可以复用到很多衍生需求比如把“当前显示部件”换成“选中组件”或者把“实体体”换成“片体集合”后面第6节会讲。2.3 为什么样例里我会优先用UF函数而不是纯NXOpen对象NXOpen的API体系很庞大理论上质量属性也可以用MeasureManager这类更加“面向对象”的接口来做。但我在实际项目里踩过不少版本坑NX 1899、NX 2206、NX 2306这几个大版本之间测量相关的类名、方法签名甚至返回值结构都有调整。你今天写好的代码过两年升级NX版本后可能直接编译不过。而UF函数User Function是NX底层更稳定的一套接口从NX老版本一直延续到现在UF_MODL_ask_mass_props_3d就是其中之一。它的参数风格比较“C语言化”看起来不够优雅但胜在稳定、跨版本兼容性好。对质量属性这种几十年没变过的计算逻辑用UF函数是最省心的选择。对比维度UF函数NXOpen对象式API跨版本稳定性高老接口稳定低版本迭代频繁调整代码可读性一般大量数组下标较好属性语义清晰二次开发扩展性偏底层适合计算类功能面向前端交互适合UI集成推荐场景批量计算、后台处理、版本兼容优先交互式命令、带对话框的工具不是说NXOpen对象式API不好而是“求质量属性”这个动作本身就是纯计算跟UI交互无关用UF函数可以把不确定性降到最低。等你的工具要加对话框、要支持用户交互选择时再结合NXOpen的UI模块不迟。3. 样例代码实现逐段拆解下面从第一行代码开始拆每个关键段都讲讲为什么这么写、改成别的会有什么后果。如果你急着要完整代码可以直接跳到第4节但我建议你还是看一眼拆解因为很多坑藏在这些细节里。3.1 获取当前显示部件Session到底怎么拿import NXOpen import NXOpen.UF theSession NXOpen.Session.GetSession() displayPart theSession.Parts.Display if displayPart is None: raise RuntimeError(当前没有打开任何显示部件请先打开模型文件)Session.GetSession()是所有NXOpen脚本的固定起手式它拿到的是当前NX进程的全局会话对象。Parts.Display属性返回当前显示部件类型是NXOpen.Part。注意它不是工作部件我见过不少同事把这里写成Parts.Work然后在装配环境下对不上数据排查半天才发现取错对象。这里还做了一个空判断。如果NX里一个文件都没打开Display会返回None后面的遍历直接抛异常加上这个判断后脚本会给出明确提示而不是报一堆底层错误。3.2 遍历实体过滤实体体避免把片体也算进去bodies [] for body in displayPart.Bodies(): if body.IsSolid: if not hasattr(body, IsSuppressed) or not body.IsSuppressed: bodies.append(body)displayPart.Bodies()返回一个集合遍历它即可拿到该部件下的所有Body。我在这里做了两层过滤第一层用IsSolid排除片体第二层用IsSuppressed排除被抑制的体。关于IsSuppressed不同版本的NX在Body对象上的属性可能略有差异所以我用hasattr做了个保护万一某个版本没有这个属性也不至于脚本崩溃。如果你确定自己的NX版本一定支持可以直接写if not body.IsSuppressed。另一个可选的过滤条件是“是否可见”。如果显示部件里有某个实体体被隐藏了但它仍属于这个部件严格说它还在模型里质量仍然存在。所以要不要过滤可见性取决于你的业务口径。统计“整套模型重量”时隐藏件也必须算统计“当前屏幕上可视重量”时才需要额外判断可见性。3.3 调用UF_MODL_ask_mass_props_3d计算质量属性这是整个脚本的核心也是网上资料最少、最容易写错的地方。ufSession NXOpen.UF.UFSession.GetUFSession() # UF_MODL_ask_mass_props_3d的输入输出 bodyTag body.Tag density 7.85 # g/cm^3钢的常见密度 accuracy 1 # 精度级别1-3越高越慢但越准 massPropsType 0 # 0表示实体属性计算 massProps [0.0] * 47 errors [0.0] * 6 status ufSession.Modl.AskMassProps3d( [bodyTag], # 实体Tag列表 1, # 对象数量 density, # 密度 accuracy, # 精度 massPropsType, # 类型 massProps, # 返回值数组 errors # 误差数组 )先解释参数AskMassProps3d这名字里的“3d”表示三维实体不是某个固定写法。第一个参数传入实体Tag的列表因为UF函数底层支持一次传入多个体批量计算这里我每次只传一个因为我们要逐个输出每个实体的独立信息。density是密度值单位要和模型单位制匹配这个很重要我单独在3.4节说。accuracy精度值一般取1或2模型复杂度高时再考虑3越高计算越慢。massPropsType填0表示按实体属性计算。这个常量在不同UF头文件里的名称可能不同但数值0对应的就是实体类型。如果你算的是片体这里要换成对应类型。我在代码里直接注释了“0表示实体属性计算”防止过几个月自己都忘了。massProps数组是输出区长度我先给了47个元素。不同NX版本的UF文档对这个数组长度的定义不完全一致有些资料写的是至少20个有些写47个。如果你执行时报数组越界直接调大这个长度比如改成64不影响结果解析。函数执行完后massProps里存的就是计算结果关键下标如下数组下标含义常规单位公制毫米模板0体积mm³1表面积mm²2质量g3质心X坐标mm4质心Y坐标mm5质心Z坐标mm6主惯性矩Ixxg·mm²7主惯性矩Iyyg·mm²8主惯性矩Izzg·mm²我特意把单位和下标对照写出来是因为这里出错的概率极高。很多人取完volume和mass后看到数值大得离谱或小得离谱十有八九就是单位制没对上。3.4 密度参数和单位体系处理密度是质量计算的关键输入。NX里的单位制可以设置成公制或英制公制下还有“毫米-克-秒”和“米-千克-秒”的差异。同一个实体你在不同单位制下调用UF函数算质量传入的密度值必须跟着单位制走。默认的公制模板一般是毫米和克对应密度单位就是g/cm³。钢的密度约7.85铝约2.7塑料约1.0左右。如果你用的是英制模板密度单位可能是lb/in³那7.85这个值就完全不对了。我在脚本里把密度写成了函数参数并给了默认值7.85方便直接跑通。实际项目中更好的做法是从NX的材料属性里读取密度而不是在代码里写死。比如通过NXOpen.Material相关API拿到实体的材料再取材料密度。不过那是另一个话题需要结合你公司的材料库建设情况这里先不展开。还有一个细节UF函数文档里说如果传入的密度为0函数可能返回全0的质量结果不报错。所以当你看到“质量为0”时第一反应不一定是代码bug先查密度参数是否真的传进去了。3.5 结果输出控制台打印和写入CSV拿到数据后输出方式决定了这个工具好不好用。我提供了两种输出一是打印到NX日志窗口适合快速验证二是写入CSV文件适合导出给其他同事或Excel处理。import csv import tempfile import os resultLine Body:%s, Volume:%.3f, Area:%.3f, Mass:%.3f, Centroid:(%.3f, %.3f, %.3f) % ( body.JournalIdentifier, massProps[0], massProps[1], massProps[2], massProps[3], massProps[4], massProps[5]) print(resultLine) csvRow [ body.JournalIdentifier, round(massProps[0], 3), round(massProps[1], 3), round(massProps[2], 3), round(massProps[3], 3), round(massProps[4], 3), round(massProps[5], 3), ] rows.append(csvRow)这里用body.JournalIdentifier做标识它通常是NX内部给这个体生成的唯一标识名类似“BLOCK(1)”这样的字符串。虽然不一定对应建模树里的用户自定义名称但作为唯一键已经足够。CSV文件写入到系统临时目录避免污染NX的工程目录。写完后用os.path.join(tempfile.gettempdir(), mass_report.csv)拼出完整路径打印出来这样用户能直接看到文件在哪。实际生产环境里可能还要支持用户自己选择保存路径这属于UI优化范畴后面扩展章节会提。4. 完整可运行代码直接复制到NX里测试这一节给出完整代码。你把它保存成.py文件在NX里通过“工具→日记→回放”运行即可。运行前建议先保存一下当前模型万一有问题也不至于影响数据。4.1 完整脚本import NXOpen import NXOpen.UF import csv import os import tempfile def main(): theSession NXOpen.Session.GetSession() displayPart theSession.Parts.Display if displayPart is None: raise RuntimeError(当前没有打开任何显示部件请先打开模型文件) ufSession NXOpen.UF.UFSession.GetUFSession() # 输入参数 density 7.85 # g/cm^3默认按钢材密度可根据模型实际材料修改 accuracy 1 # 计算精度 1-3 massPropsType 0 # 0实体属性 rows [] header [BodyName, Volume_mm3, Area_mm2, Mass_g, CentroidX, CentroidY, CentroidZ] bodies [] for body in displayPart.Bodies(): if body.IsSolid: if not hasattr(body, IsSuppressed) or not body.IsSuppressed: bodies.append(body) print(共找到 %d 个实体体 % len(bodies)) for body in bodies: massProps [0.0] * 47 errors [0.0] * 6 try: status ufSession.Modl.AskMassProps3d( [body.Tag], 1, density, accuracy, massPropsType, massProps, errors ) if status ! 0: print(计算失败Body: %s状态码: %s % (body.JournalIdentifier, status)) continue except Exception as e: print(计算异常Body: %s错误: %s % (body.JournalIdentifier, str(e))) continue resultLine Body:%s, Volume:%.3f, Area:%.3f, Mass:%.3f, Centroid:(%.3f, %.3f, %.3f) % ( body.JournalIdentifier, massProps[0], massProps[1], massProps[2], massProps[3], massProps[4], massProps[5]) print(resultLine) csvRow [ body.JournalIdentifier, round(massProps[0], 3), round(massProps[1], 3), round(massProps[2], 3), round(massProps[3], 3), round(massProps[4], 3), round(massProps[5], 3), ] rows.append(csvRow) if rows: outPath os.path.join(tempfile.gettempdir(), mass_report.csv) with open(outPath, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow(header) writer.writerows(rows) print(CSV文件已写入: %s % outPath) else: print(没有可导出的实体数据) if __name__ __main__: main()这段代码我已经去掉了复杂的界面逻辑只保留核心计算和输出方便你理解和移植。如果你要在自己的环境里跑核心要改的就是density这个值以及确认你的NX版本支持Bodies()和IsSolid这些属性。4.2 运行步骤说明打开NX加载你要统计的模型确保它在图形窗口中处于显示状态。点击“工具→日记→回放”在弹出的文件选择框里选中刚才保存的.py文件。脚本执行后NX日志窗口会逐行打印每个实体的体积、面积、质量和质心。打开系统临时目录找到mass_report.csv就是完整的导出表。第一遍跑的时候建议先拿一个只有两三个实体的简单模型试确认结果和手工用“分析→测量体”查到的数值一致后再放到复杂装配上跑。这样能快速发现密度、单位等参数是否设对。5. 踩坑记录我在实际测试中遇到的那些问题这部分内容是常规文档里不会写的东西。我把这几年实际操作中遇到的问题整理成速查表再挑几个典型的展开讲。5.1 获取到的质量全是0是怎么回事最常见的质量全为0的原因是密度参数传入了0或非法值。很多人从默认模板里复制代码忘记改密度结果算出来全是0。但这不算最隐蔽的更隐蔽的是你传入了密度但NX当前模型用的是英制单位制你传的7.85在英制体系下根本不匹配即便函数不报错结果也没有意义。还有一种情况实体本身是“小平面体”或“缝合体”在NX中虽然能被IsSolid识别为实体但UF函数对它计算质量时可能返回空数据。遇到这种体建议先用网格诊断或“优化体”功能处理一下再用脚本计算。5.2 密度数据不准确导致重量偏差很大很多工程师在自己的模型里并不会主动设置材料属性导致这个实体其实没有真正的密度数据。脚本里如果写死一个默认密度算出来的重量自然和实际情况不符。我的建议是在正式使用前先统计一下你的模型库里有多少实体是设置了材料的。如果比例很低那就得考虑在脚本里增加一个“根据自定义属性读密度”的逻辑比如读取实体的“Material”属性或“Density”属性读不到再用默认值兜底。这样至少不会算出一个明显离谱的数。5.3 实体数量很多时脚本执行特别慢一个复杂装配里可能有上千个实体逐个调用UF函数计算质量属性时间会线性增长。我实际测过一个500个实体的小总成跑完整轮大约要几十秒用户体验确实一般。如果想提速可以考虑两个方向一是把多个实体一次性传给AskMassProps3d让它一次计算多个实体但这样返回的是一组汇总值拿不到每个实体的独立质量不符合我们“逐个输出”的需求二是加上进度提示让用户知道脚本还在跑而不是卡死了。NXOpen里可以用NXOpen.UF.UFUi或状态栏更新当前进度代码量不大但体验提升很明显。5.4 不同NX版本的API差异别指望一次写好这是NX二次开发最让人头大的地方。我手上的项目横跨NX 1899和NX 2306两个版本同样的脚本在NX 1899上正常到NX 2306上AskMassProps3d的入参顺序竟然有小变化。最稳妥的方法是在你自己的NX版本里手动录制一次“分析→测量体”操作看看生成的日记代码长什么样然后把其中的API调用格式作为基准。我的习惯是在每个NX版本环境里跑一次“录制日记”把生成的代码留存一份作为该版本的API基准。这样不管是升级NX版本还是换到同事的电脑上都能快速定位差异点。这个小习惯帮我省了很多排查时间。5.5 无法打开部件或采集不到实体有时候脚本会报“没有显示部件”但用户明明打开了模型。这种情况多半是NX正处于某个命令的未完成状态比如正在草图中编辑此时Parts.Display可能返回None或异常。对策是在脚本开头明确提示用户退出当前命令再运行。采集不到实体的情况多数发生在“装配环境下的显示部件是装配体”时。装配体本身没有实体实体都在子组件里。如果你想统计整个装配就需要递归遍历每个组件里的体对象而不是只用displayPart.Bodies()。5.6 常见问题速查表现象可能原因解决方案质量为0密度没传或传成0检查密度参数数值量级不对单位制与密度单位不匹配统一为公制毫米模板找不到实体当前是装配体实体在子组件里递归遍历组件脚本报API不存在NX版本API差异录制自己的日记做基准执行很慢实体数量多、逐体计算加进度提示或批量处理返回结果部分为0个别体损坏或为小平面体诊断修复该实体后重算6. 后续还能怎么玩从“取重量”到“自动出报表”脚本跑通之后你会发现这套逻辑的复用价值非常高稍微改改就能变成不同工具。下面这几个方向我都试过按实施成本从低到高列出来。6.1 一键导出整套模型的BOM重量清单这个是最直接的扩展。把CSV导出逻辑改成写Excel或者在CSV里增加“序号”“零件名”“材料”等列就能直接当BOM清单用。如果公司里已经有BOM模板把输出列和模板对齐导出后就能直接导入ERP或PLM系统。我做过一个版本输出时直接用csv模块写多级表头然后在NX里给工具加了一个菜单项用户点一下按钮就自动生成重量报表再也不用一个个实体截图汇报了。6.2 按实体名称、属性筛选统计范围有些场景不需要统计所有实体比如只想统计“外购件”或者只想统计“需要发运单独包装的零件”。这时可以在遍历时加过滤条件比如读取实体的用户属性或自定义属性根据属性值决定是否纳入统计。NX里读取属性可以用NXOpen.NXObject.GetUserAttribute或UF的UF_UGM_ask_user_attribute。虽然代码比单纯遍历多几行但效果很好相当于给统计工具加了一个“筛选器”。6.3 批量修改材料密度后自动更新重量如果你的模型库材料设置不完善可以写一个预处理脚本批量把某个装配下所有实体的密度属性统一设置成指定材料密度然后再跑重量统计。我通常会把“设置密度”和“计算重量”分成两个独立工具因为业务上这两件事经常发生在不同时间点。一个实际案例公司从钣金件切换到铸件材料密度全变了。我用脚本批量把所有实体的密度换成铸铝密度然后重新统计总重量5分钟搞定而手工修改至少得半天。6.4 在装配体中遍历组件实体回到5.5说的装配体场景。如果你想统计整个装配的总重量和每个子件的重量就需要递归遍历所有组件获取装配的所有组件displayPart.Components。对每个组件获取其引用的部件component.Prototype。在该部件上执行和前文一样的实体遍历与质量计算。把组件路径和实体质量合并输出。递归逻辑不复杂但是要注意重复引用的问题。同一个子零件可能在装配中出现了多次直接遍历会把它的重量重复计算。是否要去重取决于你要的是“所有实例的重量总和”还是“每种零件的单件重量”。这两个业务口径在BOM场景里都很常见脚本逻辑要区分清楚。最后再分享一点个人体会写这个工具时我最大的感悟是NX二次开发里代码从来不是最难的难的是把NX的模型概念、单位体系、业务口径都对齐。你在做“获取实体重量”之前最好先亲手用“分析→测量体”查一下目标模型看看NX默认给的质量、体积和单位是什么。这样脚本跑出来你一眼就能判断结果对不对而不是陷入“数值看起来挺像但不知道到底准不准”的迷局。如果你在实际使用中遇到其他坑比如某个版本的UF函数参数对不上、某个特殊类型的体算不出质量欢迎在评论区把现象和版本发出来。这类小众开发问题一个人踩坑苦恼半天大家一句话可能就点破了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进