NX二次开发里用 C# 调用 NXOpen 去测量两个对象之间的最小距离和最大距离是我这几年被问得最多的需求之一。原因也简单无论是装配间隙检查、加工余量校核还是零件最大外形的统计几乎每个做 NX 自动化工具的工程师迟早都要写这个功能而自己用“分析—测量—距离”菜单点一遍偶尔一两次还能忍一旦变成几十对、上百对对象就完全没法接受了。这篇文章我不打算讲那些文档里都有的废话直接说清楚 API 怎么选、代码怎么写、批量场景怎么优化以及我在实际项目里踩过的那些坑。适合刚接触 NX 二次开发的 C# 工程师也适合已经在写工具、想把手动测量逻辑往自动化方向迁移的老手。1. 场景与API选型先把“测距离”这件事想透1.1 为什么“最小/最大距离”是刚需做 NX 二次开发的人应该都有体会客户提得最多的三个自动化需求里一定有一个是“帮我自动量一下距离”。这不是单纯的懒而是距离测量在制造流程里太常用了。我举几个真实场景。第一个是装配间隙验证。汽车焊装夹具、电子设备外壳、医疗器械装配里零件之间的最小间隙是设计硬指标比如某处必须保证 0.3mm 以上否则干涉或者装不进去。人工用“测量距离”命令一个个去点点完还要记录到表格一百对零件就是一百次重复操作手一抖选错面结果就废了。程序来做这件事只需把需要检查的对象对枚举出来循环批量计算最后统一导出效率完全不在一个数量级。第二个是加工余量校核。毛坯件和成品件之间要确认最小加工余量是否满足工艺要求。这个本质上也是测量两个体之间的最小距离只是业务上叫法不同。还有模具设计里分型面闭合量、压料面贴合度的检查也一样是“最小距离”的变种。第三个是最大外形尺寸统计。包装设计、运输方案、设备防护罩设计都需要知道一个零件或一个组件在最极端方向上能占到多大空间。很多人以为这是“包围盒尺寸”其实不是NX 的测量距离功能里的“最大距离”是指两个对象边界点之间最远的空间距离和包围盒对角线不是一个概念。我之前有个产品包装项目客户一定要精确的“最远两点距离”就是因为它们的运输托盘尺寸卡得非常死包围盒算出来偏大按包围盒设计会浪费空间。看完这三个场景你就明白了最小距离解决“会不会碰、还剩多少间隙”的问题最大距离解决“能长多大、能占多少空间”的问题。两者合在一起基本覆盖了产品结构和工艺设计里一半以上的几何约束校核。1.2 测量API的家底三条技术路线怎么选NX 给二次开发提供了不止一种测量距离的入口我把它分成三类你根据项目情况选。第一类是 UFUN 风格函数比如老的UF_MODL_ask_minimum_dist。这类函数的历史很长优点是稳定、直接、不依赖 UI缺点也很明显面向过程、接口风格老在 C# 里混着用感觉像是在穿西装跑步。而且 UFUN 返回的信息量比较少拿不到太多辅助数据。我建议新项目不要优先用只有维护旧代码时才考虑。第二类是 NXOpen 里的旧版 Measure API也就是MeasureManager下的CreateMinimumDistance3D和CreateMaximumDistance3D。这两个方法名字非常直观传入两个对象返回一个测量结果对象读.Distance就是距离值。优点是代码量极小适合快速实现批量计算缺点是没有图形界面、不支持配置测量类型到了新版 NX 里官方推荐度逐渐降低但短期用没问题。第三类是MeasureDistanceBuilder。这是 NX 测量功能面向对象化的产物交互式“测量距离”对话框背后就是它。Builder 可以设置测量类型最小、最大、投影距离、参考方向、关联性也可以调用Measure()直接计算。优点是功能全、和 UI 行为一致、可扩展缺点是代码比老 API 多一点需要自己管理 Builder 生命周期。我整理了一个简单的对照表方便你按项目判断。方案代码量易用性关联测量版本趋势适用场景UFUN 函数中一般不支持维护期老代码改造、对性能要求极高的简单计算旧版 Measure API很小高弱可用但逐渐过时快速脚本、批量计算、临时工具MeasureDistanceBuilder较大中高支持推荐方向工程化工具、与 UI 集成、需要参数配置我做自动化工具时的原则是能用摸得着的 Builder 就用 Builder如果只是写一次性脚本处理数据才用旧 API 图省事。后面正文会两种都给你写上。2. 动手前必须搞懂的几个基础概念2.1 什么对象能测几何对象类型与可测性NXOpen 的测量距离接口接收的“对象”并不是任意 NXObject 都行。你传一个 Feature 进去、传一个 Part 进去大概率是测不了的。实际可测的对象集中在几何类Body、Face、Edge、Curve、Point、DatumAxis、DatumPlane、CoordinateSystem 这些。它们都实现了INXObject接口所以写签名的时候你可以直接用INXObject作为参数类型传 Body、Face、Edge 都没问题。我举个简单例子你想测一个体的底面到一个基准平面的距离那么你就是把那个 Face 和 DatumPlane 传给测量方法。又比如你想测两条边是否平行、距离多少就把两条 Edge 传进去。坐标点也支持但测点和点之间的距离意义不大通常都是和面、体、轴配合使用。这里有个容易忽略的点一个 Body 内部可能有很多 Face但你直接测 Body 和 Body 的距离NX 会按整个体的几何边界来算也就是自动取“这两个体上距离最近/最远的点对”。如果你关心的是某个特定面的间隙那必须把 Face 单独取出来传进去而不能只传 Body。我见过有人传了 Body 然后抱怨“怎么结果和我在面上点的距离不一样”就是没搞清楚这一层。2.2 最小距离与最大距离NX 到底在算什么最小距离很容易理解就是两个对象上各自取一个点让这两个点的空间距离最小时的那个距离值。NX 在底层会先做几何求交判断如果两个对象本来就有重叠或接触最小距离直接就是 0。对于复杂曲面NX 的处理方式是解析计算加离散逼近结合结果默认情况下是精确的除非曲面特别复杂才受容差影响。最大距离稍微绕一点。它不是“两个对象中心点的距离”也不是“两个对象包围盒对角点的距离”而是分别在两个对象的边界上取一个点使得这两个点的空间距离在所有点对中最大。换一个生活化的类比两个人站在一块空地上最小距离是两人离得最近的指尖或衣角之间的距离最大距离是一个人最左边的指尖到另一个人最右边的脚尖之间的距离和你俩各自“身体中心”的间距完全是两回事。实际编程中最大距离最常出现在角点、边端点、曲面极端位置。如果是两个长方体最大距离通常就发生在两个相对远角的顶点连线方向。明白这一点你就知道为什么不能用包围盒去代替最大距离了包围盒是轴对齐的长方体尺寸而最大距离是任意方向的真实最远点距后者才反映零件在空间中真正可能“撑到”的最大跨度。2.3 关联与非关联测量的区别Measure API 返回的结果对象默认是一次性快照。什么东西叫快照就是计算完那一刻的数值。之后如果你改了零件的几何尺寸、移动了位置这个结果不会自动更新。绝大多数批量计算场景比如遍历装配体、输出间隙报表用快照就完全够了因为没有谁会反复改几何然后让一个历史结果跟着刷新。但有的场景需要“关联测量”。比如你要做一个装配间隙监控工具零件 A 移动位置后希望能动态看到它与零件 B 的最小间隙是多少。这时就要用关联测量让测量结果和几何对象保持关联几何变化后结果自动刷新。Builder 方式下可以设置关联输出旧版CreateMinimumDistance3D则不太容易做到。我的建议是第一版工具先全部用非关联测量把流程跑通如果业务真的需要动态更新再改成关联版本。因为关联测量的底层实现要维护对象引用和更新回调在大型装配里会明显增加内存和计算开销不适合一上来就无脑使用。3. 核心代码最小距离与最大距离的完整实现3.1 环境准备项目配置与引用在开始写代码之前先说下工程环境。用 Visual Studio 新建一个 C# 类库项目目标框架建议和你安装的 NX 版本匹配比如 NX 系列一般都支持 .NET Framework 4.8 或更高。项目里需要添加几个 NXOpen 相关的程序集引用NXOpen.dll、NXOpen.Utilities.dll、NXOpen.UF.dll具体文件名在你的 NX 安装目录的NXBIN\managed文件夹下能找到。编译输出的 DLL 放在 NX 安装目录的Startup文件夹里NX 启动时会自动加载这个目录下的程序集。然后在 NX 里通过File—Execute—NX Open选择你的 DLL 来运行或者做成菜单按钮、快捷键触发。如果你不确定某个 API 在当前 NX 版本里到底长什么样最简单可靠的办法是用 NX 的 Journal 录制功能手动执行一次“测量距离”录制后会生成一份 C# 录制文件里面就是当前版本的真实 API 调用方式。我写二次开发这几年几乎每个工具的第一版代码都是先录制再改比自己翻帮助文档快得多。3.2 最省事的旧版 API 写法下面是旧版MeasureManager的完整实现代码量非常小适合快速验证。using NXOpen; using NXOpen.Measure; public class DistanceTool { private Session theSession; public DistanceTool() { theSession Session.GetSession(); } public double GetMinimumDistance(INXObject obj1, INXObject obj2) { Measure.MinimumDistance3D minDist theSession.MeasureManager.CreateMinimumDistance3D(obj1, obj2); return minDist.Distance; } public double GetMaximumDistance(INXObject obj1, INXObject obj2) { Measure.MaximumDistance3D maxDist theSession.MeasureManager.CreateMaximumDistance3D(obj1, obj2); return maxDist.Distance; } }调用起来也非常直接比如在工作部件里取第一个和第二个 Body然后分别测最小、最大距离BodyCollection bodies workPart.Bodies; Body body1 bodies.ElementAt(0); Body body2 bodies.ElementAt(1); DistanceTool tool new DistanceTool(); double minD tool.GetMinimumDistance(body1, body2); double maxD tool.GetMaximumDistance(body1, body2); // 输出到信息窗口 Session.GetSession().ListingWindow.Open(); Session.GetSession().ListingWindow.WriteLine( string.Format(最小距离: {0:F3} 最大距离: {1:F3}, minD, maxD));这个结果对象不只是有Distance一个属性。MinimumDistance3D上还有PointOnObject1、PointOnObject2和Vector分别对应两个对象上的最近点坐标和最小距离方向向量。这个信息在实际工程里非常值钱。比如你要判断装配体在最小间隙处是哪个角落先碰到或者要沿最小距离方向生成一根标注线、一个测量点就是从这几个属性里取的。MaximumDistance3D同样有对应的点属性和方向向量。批量算完最小间隙后把它们按对象对记录下来后面在 NX 里生成红色的干涉标记点、间隙标注都很方便。3.3 面向新版 NX 的 Builder 写法如果你的项目长期维护或者要更精细化地控制测量类型建议用MeasureDistanceBuilder。这个 Builder 的本质就是把“测量距离”对话框背后的所有逻辑封装起来程序里可以完全复现用户在界面上的操作。using NXOpen; using NXOpen.Measure; public class DistanceBuilderTool { private Session theSession; public DistanceBuilderTool() { theSession Session.GetSession(); } public double GetDistance(INXObject obj1, INXObject obj2, MeasureDistanceBuilder.MeasurementTypeEnum type) { MeasureDistanceBuilder builder null; try { builder theSession.MeasureManager.CreateMeasureDistanceBuilder(null); builder.MeasurementType type; builder.Object1.Value obj1; builder.Object2.Value obj2; MeasureDistance measure builder.Measure(); return measure.Distance; } finally { if (builder ! null) builder.Destroy(); } } }调用时测量类型用枚举指定最小距离传MeasurementTypeEnum.Minimum最大距离传MeasurementTypeEnum.Maximum。注意 Builder 用完后必须调用Destroy()否则会在 NX 进程里留下原生对象连续创建多个 Builder 后内存占用会一路飙升这在后面踩坑部分我会重点说。关于builder.MeasurementType这个属性名不同 NX 版本的属性名称可能略有差异比如有些版本用builder.Measurement.Type有些版本用builder.MeasurementType。我建议你在目标 NX 环境下打开一个录制好的 Journal 看一下真实名称或者直接用智能提示确认。整个调用骨架是不变的。3.4 交互选择对象的实现方式批量脚本里对象都是程序内部遍历出来的但工具型开发经常会面临“用户鼠标选择两个对象然后程序去测”的场景。这时就需要调选择管理器。NXOpen 里最常用的方式是SelectionManager.SelectTaggedObject选择完成后返回TaggedObject[]再把选中的对象转成INXObject传入测量方法。简化示例UI theUI UI.GetUI(); TaggedObject[] selections null; Selection.Response resp theUI.SelectionManager.SelectTaggedObject( 选择第一个测量对象, 提示标题, Selection.SelectionScope.WorkPart, Selection.Action.SelectAndEnableHighlight, Selection.FilterType.Any, out selections); if (resp Selection.Response.Ok selections.Length 0) { INXObject obj1 selections[0] as INXObject; INXObject obj2 selections[1] as INXObject; double minD tool.GetMinimumDistance(obj1, obj2); }不同 NX 版本的SelectTaggedObject参数签名确实有差异版本越新过滤条件越丰富。不过总体套路不变先弹出选择拿到TaggedObject[]然后as转成INXObject。这里给你一个避坑提示TaggedObject转INXObject时用as成功后再使用不要直接强转避免对象类型不匹配时直接抛异常把 NX 搞崩。4. 批量测量装配间隙矩阵与最大外形统计4.1 批量遍历装配体的场景设计自动化工具的威力在批量。我给你一个常见需求一个装配体里有几十个零件想算两两之间的最小间隙输出一个间隙矩阵同时算出每个零件相对另一零件的最大距离用于判断装配后的最大外形。思路就是把装配体里的 Body 全部取出来然后两层循环对每一对 Body 调用最小距离和最大距离方法。伪代码如下Body[] bodies workPart.Bodies.ToArray(); for (int i 0; i bodies.Length; i) { for (int j i 1; j bodies.Length; j) { double minD tool.GetMinimumDistance(bodies[i], bodies[j]); double maxD tool.GetMaximumDistance(bodies[i], bodies[j]); // 记录到报表 } }这代码逻辑看起来没问题但真放到几十个零件、每个零件结构还不简单的装配里跑起来可能慢得让你怀疑人生。原因在于CreateMinimumDistance3D或者是 Builder 的Measure()要做的是精确几何求值不是简单的包围盒比较遇到复杂曲面时计算成本很高。两层循环本来就是 O(n²)再叠加精确测量成本几百个对象的数量级就能跑到分钟级。4.2 用包围盒做粗筛避免性能灾难我在第一次做批量间隙工具时就直接两层循环精确测结果 38 个零件的装配体跑了一分多钟客户还以为程序死机了。后来我在精确测量之前加了一步粗筛速度直接快了几十倍。粗筛的原理很简单先计算两个体的轴对齐包围盒根据包围盒的坐标范围估算出它们“可能的最小距离”的下界。如果这个下界已经大于你关心的阈值比如你想找所有间隙小于 1mm 的零件对而包围盒估算出来最近也不可能小于 10mm那就完全没必要做精确测量。粗筛代码大致思路如下double gapX Math.Max(box1.minX, box2.minX) - Math.Min(box1.maxX, box2.maxX); double gapY Math.Max(box1.minY, box2.minY) - Math.Min(box1.maxY, box2.maxY); double gapZ Math.Max(box1.minZ, box2.minZ) - Math.Min(box1.maxZ, box2.maxZ);如果三个轴向上的间隙值都大于 0那么两个包围盒互不重叠它们之间最小距离的下界就是三个间隙值的平方和开根号如果某个间隙小于 0说明这个方向上已经压在一起最小距离很可能很小或为 0才需要精确测量。把包围盒互不重叠且下界大于阈值的对象对直接跳过剩下需要精确测的对象对往往只剩 10% 甚至更少。这个优化几乎零成本是批量测量工具里必加的一步。4.3 结果输出信息窗口与 CSV 导出结果输出方面小数据量直接打到 NX 信息窗口就够。用Session.ListingWindow.Open()加上WriteLine用户点开信息窗口就能看到全部结果。数据量大或者要交给其他部门用最好导出 CSV。用 C# 自带的StreamWriter就能满足格式简单、Excel 直接能打开using (StreamWriter sw new StreamWriter(D:\distance_report.csv)) { sw.WriteLine(Object1,Object2,MinDistance,MaxDistance); for (int i 0; i bodies.Length; i) { for (int j i 1; j bodies.Length; j) { double minD tool.GetMinimumDistance(bodies[i], bodies[j]); double maxD tool.GetMaximumDistance(bodies[i], bodies[j]); sw.WriteLine(string.Format({0},{1},{2:F3},{3:F3}, bodies[i].Name, bodies[j].Name, minD, maxD)); } } }要注意的一点是距离单位。NX 返回的Distance值使用当前工作部件的显示单位。如果你的部件是英制读出来是英寸如果是公制读出来是毫米。做报表前最好先统一单位别到时候 Excel 里混了英制公制那数据就完全失真了。可以在代码里读取workPart.Preferences.Modeling里的单位信息做一次换算判断。5. 实战踩坑与经验记录5.1 Builder 必须销毁忘记销毁的惨痛教训这是 NXOpen 开发里最容易犯、也最坑的问题。用MeasureDistanceBuilder时如果你在循环里创建 Builder 而忘记Destroy()第一次跑可能感知不到问题第二次、第三次跑NX 进程内存占用会明显上涨界面开始卡顿最后直接把 NX 干崩溃而且还没有任何报错提示非常难查。原因在于 Builder 是 NX 内部的原生对象托管环境不会自动帮你释放。正确做法就是我在前面代码里写过的finally块里调用Destroy()。更规范一点可以在整个应用入口加上异常捕获任何异常发生时确保已创建的 Builder 都被释放。这个习惯不只在测量距离上适用NXOpen 里所有*Builder对象比如草图 Builder、特征 Builder全都一样必须手动销毁。5.2 版本兼容为什么明明 Copy 的代码编译不通过NXOpen API 在不同版本之间变动不算小尤其是 NX 1847 之后很多旧接口被调整或者逐步标记为过时。你可能会遇到这种情况网上查到的示例代码在自己的 NX 版本里编译不过去或者运行时提示“找不到方法”。解决这个问题的思路不是硬背接口名而是依赖 NX 自身的录制能力。打开 NX手动做一遍测量距离操作保存 Journal 为 C# 格式然后打开录制文件看当前版本的 API 是什么名字、参数有哪些。录出来的代码可能有些冗余但它的签名一定是当前 NX 版本正确的签名比任何网上资料都可靠。另外还有一个经验工程化项目要锁定 NX 版本。如果公司里同时有 NX 10 和 NX 2212你的 DLL 很可能只能在一侧正常运行跨版本使用前必须重新编译并做完整回归测试。API 的编译时签名和运行时行为都可能变不要指望一个 DLL 通吃所有 NX 版本。5.3 单位、精度与方向属性结果不是拿个数就完事有时候你拿到Distance值发现和界面里显示的不一样第一反应是程序算错了。但实际上可能是单位问题也可能是精度显示问题。测量属性的值本身是 double界面显示是按界面设置四舍五入的程序读出来的是完整小数你打印时没有格式化就会看到长长的小数位数。建议输出统一用F3或F6格式化。更关键的是最大距离的方向。最大距离只是给了一个数值但如果要标注哪两个点是最大距离点必须用MaximumDistance3D的PointOnObject1、PointOnObject2和Vector。我做过一个案例客户要求最大距离点对要在三维模型里高亮显示。结果点对取出来后得再把坐标转成临时点对象放到一个图层里再截图输出报告。这些附加需求全都依赖结果对象里的点属性所以要提前把结果对象保存下来不要只存一个 double否则后面想加方向信息就得重新计算一遍。5.4 被抑制的体、隐藏的对象、异常对象处理批量测量里最烦的就是对象本身状态不正常。比如某个 Body 所在的 Feature 被抑制了、被 blank 隐藏了、或者装配对象还没有完全加载。这时候调用测量接口可能直接抛异常把你整个循环打断。好在解法也不复杂在循环里对每个 Body 提前做状态检查。比如判断IsBlanked、判断体是否有效、判断对象的 Tag 是否为空测量调用包一层try/catch一旦出现异常跳出当前对象对记录日志继续处理后面的数据。批量工具的核心原则是“不能因为一个坏对象就让整个任务失败”否则和手工人肉检查没区别了。5.5 关联测量的安全隐患前面提到关联测量会因为几何改动自动更新听起来很方便但你要小心使用场景。如果一个关联测量对象保留在大型装配里用户每次编辑几何、移动组件、更新模型NX 都要重新计算这个测量值。多个关联测量对象叠加在一起就是一场性能灾难。我遇到过的情况是客户要求做一个“实时间隙检查”工具界面里显示当前最小间隙。我和他们沟通后把方案改成了“用户点按钮获取最新值”而不是持续关联刷新。这样一来性能开销降到了最低使用体验也几乎没有差别。所以最终建议是批量计算全用非关联快照需要动态更新的地方控制关联测量数量并给用户明确的“手动刷新”入口。最后分享一点个人体会我第一次写批量测量工具时也踩过从头到尾的坑从 API 选错、单位搞混、忘记销毁 Builder到被抑制体搞崩整个循环前前后后改了好几轮。后来形成的流程是先用 Journal 录制手动测量确认当前版本的 API 签名再写单对测量跑通然后加包围盒粗筛和异常捕获最后才做批量报表。这套流程后来成了我做 NX 工具的习惯也帮我少走了很多弯路。如果你也正在做类似的 NX 二次开发功能建议先不要一上来就追求代码少、接口酷炫。先把“单对对象能不能测准”“单位是不是一致”“Builder 有没有销毁”这三件事确认好再往批量方向扩展你会少很多麻烦。遇到具体问题也欢迎一起交流。