简介:面向NX二次开发初学者的工程源码包,围绕UFUN库中的UF_VEC3_distance接口,演示如何在UG/NX环境里用C++计算三维空间中两点距离。包内提供完整工程源代码、VS工程配置与编译产物,从源文件编写、库依赖配置到动态库生成的完整脉络一目了然,适合初步接触NX二次开发、希望了解UFUN调用流程的工程师对照学习。压缩包共25个文件,以C++源码、工程文件、动态库、调试信息、说明文档等类型为主,整体大小约3.4MB,结构简洁,便于本地编译验证。已有894人浏览学习。借助源码可掌握该接口的函数签名与欧几里得距离运算实现,同时理解NX二次开发项目从创建、编译到注册调用的基础过程,对于后续处理更复杂的几何对象、装配关系或工程图功能也是一条清晰起点。
1. NX二次开发:绕不开的UF_VEC3_distance两点距离计算
做NX二次开发的人,几乎都会遇到同一个需求:拿到两个三维点坐标,算出它们之间的距离。有人直接写sqrt((x1-x2)^2+(y1-y2)^2+(z1-z2)^2),但UFUN函数库里其实提供一个现成函数UF_VEC3_distance,很多老工程师都在用。这个函数的作用就是计算两个double[3]类型点坐标之间的欧氏距离,返回值是double,代码量只有一行,但背后涉及的传参方式、头文件引用、错误码处理、单位换算,新手第一次用大概率会踩坑。这篇文章把这套流程拆开来讲——从为什么应该用UF_VEC3_distance而不是手写公式,到完整的VS工程代码、UFUN和NXOpen的混用方式,再到实际开发中遇到的四个高频坑,最后给出批量距离计算的进阶封装。不管你是刚接触NX二次开发的初学者,还是已经在写DLL的老手,这篇都值得花几分钟过一遍。
2. 为什么选UF_VEC3_distance:从函数签名到坐标系选型
2.1 UF_VEC3_distance函数签名与返回值语义
UF_VEC3_distance在UFUN库里的声明长这样:
double UF_VEC3_distance( const double vec1[3], const double vec2[3] );两个参数都是const double[3]数组,分别表示第一个点和第二个点的X、Y、Z坐标。返回值就是两点之间的直线距离。注意这个函数不返回错误码——这点和UFUN里大多数函数不一样。UFUN大部分函数返回int类型的错误码(0表示成功,非0表示失败),但UF_VEC3_distance直接返回一个double,距离算不出来或者参数有问题的场景它并不会告诉你。传进去的数组如果不是有效数值,比如NaN或无穷大,函数也不会报错,只会返回一个NaN或无穷大的结果。所以调用前确保点坐标是真实有效的数值,这一点在后文的避坑章节会重点展开。
2.2 手写公式和UF_VEC3_distance的差异
手写三维距离公式当然不难:
double dist = sqrt( pow(p1[0] - p2[0], 2) + pow(p1[1] - p2[1], 2) + pow(p1[2] - p2[2], 2) );这段代码逻辑上完全等价,但实际开发中我更推荐直接用UF_VEC3_distance,原因有三个。
第一,UF_VEC3_distance是NX官方内部使用的同一个实现,精度和NX自身测量功能保持完全一致。曾经有开发者手写公式算出结果和NX的测量命令相差0.000001mm,排查半天发现是浮点运算舍入方式不同。
第二,从代码可读性角度,UF_VEC3_distance一行就表达意图,后续维护的人不需要去读sqrt和pow那一串。
第三,UFUN函数库在NX各版本之间保持二进制兼容,你的DLL用UF_VEC3_distance写的代码,从NX 8.5到NX 2212都能正常编译运行,不会因为接口变动而翻车。
/* 推荐写法:直接调用UFUN距离函数 */ double dist = UF_VEC3_distance(point1, point2);2.3 坐标系的潜在影响:工作坐标系还是绝对坐标系
这是新手最容易忽略的一个点。UF_VEC3_distance本身不关心坐标系,它只对传进来的数值做数学计算。问题在于你的点在哪个坐标系下获取的。从UF_CSYS_ask_wcs拿到的点,坐标值是相对于工作坐标系的,从UF_CSYS_ask_origin_of_object拿到的点,坐标值是相对于绝对坐标系的。同一个物体在这两个坐标系下的坐标完全不同,算出来的距离自然也不同。
如果两个点来自不同坐标系,直接传给UF_VEC3_distance,算出来的结果就是错误的。常见做法是先把点统一转换到绝对坐标系再计算。
#include <uf.h> #include <uf_csys.h> #include <uf_vec.h> /* 把工作坐标系下的点转换到绝对坐标系 */ double wcs_point[3] = {x_wcs, y_wcs, z_wcs}; double abs_point[3]; UF_CSYS_map_point(UF_CSYS_ROOT_COORDS, wcs_point, UF_CSYS_WORK_COORDS, abs_point);UF_CSYS_map_point这个函数负责坐标系转换,第一个参数是源坐标系,第三个参数是目标坐标系,这里把根坐标系(绝对坐标系)下的点映射到工作坐标系下——等等,这个用法需要澄清一下。UF_CSYS_map_point的参数含义是从源坐标系映射到目标坐标系,源点坐标和结果点坐标分别用第二和第四个参数传入。如果要把WCS下的点转到绝对坐标系,第一个参数应该传UF_CSYS_WORK_COORDS,第三个传UF_CSYS_ROOT_COORDS。我之前在这里就写反过一次,算出来的距离差得离谱,后来翻帮助文档才发现参数搞反了。
3. 完整工程实现:从VS项目配置到DLL生成
3.1 开发环境准备和头文件依赖
开发NX二次开发DLL需要准备三样东西:NX软件本身、Visual Studio(推荐2017或2019版本)、NX自带的NXOpen C++开发包。安装NX时勾选开发环境模块后,安装目录下会出现UGOPEN文件夹,里面就是开发需要的头文件和库文件。
一个最简的UFUN程序需要包含以下头文件:
#include <uf.h> /* UFUN初始化相关函数 */ #include <uf_ui.h> /* 用户界面接口,用于打印信息到NX信息窗口 */ #include <uf_curve.h> /* 曲线相关函数 */ #include <uf_vec.h> /* 向量和距离计算函数,UF_VEC3_distance就在这里面 */ #include <uf_csys.h> /* 坐标系相关函数 */编译链接阶段需要添加的库文件包括libufun.lib、libnxopenuicpp.lib、libnxopencpp.lib等。在VS的工程属性里,附加依赖项里添加这些lib文件。如果用的是NX 12或更高版本,还需要把$(UGII_BASE_DIR)\UGOPEN添加到附加包含目录,把$(UGII_BASE_DIR)\UGOPEN添加到附加库目录。
3.2 最小可运行的UF_VEC3_distance计算代码
下面是一个可以在NX里直接运行的完整代码片段。这段代码的作用是:在NX信息窗口输出两个已知点之间的距离。
#include <uf.h> #include <uf_ui.h> #include <uf_vec.h> /* NX入口函数,在NX里执行菜单或命令时被调用 */ extern "C" DllExport void ufusr(char* param, int* returnCode, int rlen) { /* 初始化UFUN环境,很多新手忘了这步直接调函数会报错 */ UF_initialize(); /* 定义两个测试点的坐标 */ double point1[3] = {0.0, 0.0, 0.0}; double point2[3] = {30.0, 40.0, 0.0}; /* 调用UF_VEC3_distance计算两点距离 */ double distance_val = UF_VEC3_distance(point1, point2); /* 在NX信息窗口输出结果,方便调试 */ char message[256]; sprintf(message, "两点距离 = %f mm", distance_val); UF_UI_open_listing_window(); UF_UI_write_listing_window(message); /* 释放UFUN环境 */ UF_terminate(); }这段代码的逻辑是:入口函数ufusr是NX调用DLL的约定入口,整个流程分为三块——初始化UFUN环境、执行距离计算、输出结果到信息窗口。计算本身只有一行UF_VEC3_distance(point1, point2),输入是double[3]数组,输出是距离数值。
参数说明:point1和point2分别是起点和终点的X、Y、Z坐标。在这个例子里,(0,0,0)到(30,40,0)的距离是sqrt(30^2 + 40^2) = 50,运行后信息窗口会显示50.000000。UF_UI_open_listing_window和UF_UI_write_listing_window是UFUN输出调试信息的两个必要步骤,前者负责打开信息窗口,后者负责写入内容。
3.3 从任意UF对象获取点坐标并计算
实际开发中很少直接手工定义一个点坐标,更多是从NX模型里选中物体、再获取它的关键点坐标。这里演示一个从已有直线端点计算长度的例子:
#include <uf.h> #include <uf_obj.h> #include <uf_curve.h> #include <uf_vec.h> extern "C" DllExport void ufusr(char* param, int* returnCode, int rlen) { UF_initialize(); /* 获取当前工作部件中第一条直线的标签 */ tag_t line_tag = NULL_TAG; UF_OBJ_cycle_objs_in_part(UF_PART_ask_display_part(), UF_line_type, &line_tag); if (line_tag != NULL_TAG) { /* 把直线对象转换成曲线数据结构 */ UF_CURVE_line_t line_data; UF_CURVE_ask_line_data(line_tag, &line_data); /* 直线的起点和终点坐标 */ double start_point[3] = {line_data.start_point[0], line_data.start_point[1], line_data.start_point[2]}; double end_point[3] = {line_data.end_point[0], line_data.end_point[1], line_data.end_point[2]}; /* 计算直线长度,这个就是UF_VEC3_distance的典型场景 */ double length = UF_VEC3_distance(start_point, end_point); char message[256]; sprintf(message, "直线长度 = %f mm", length); UF_UI_open_listing_window(); UF_UI_write_listing_window(message); } else { UF_UI_open_listing_window(); UF_UI_write_listing_window("当前部件中没有直线对象"); } UF_terminate(); }这段代码展示了从NX模型对象中提取坐标的完整链路。UF_OBJ_cycle_objs_in_part遍历部件中所有直线类型的对象,返回第一个找到的直线tag;UF_CURVE_ask_line_data把tag转换为包含起终点坐标的数据结构。最终交给UF_VEC3_distance计算的就是这个真实模型中的几何数据。
这个写法在批量处理场景里很有用。比如要获取一个部件里所有直线段的长度,只需要用循环遍历所有tag,对每条线段重复执行UF_CURVE_ask_line_data和UF_VEC3_distance就可以了。
3.4 工程配置避坑:debug和release模式要区分
VS工程配置NX二次开发DLL时,Debug和Release两个模式下的配置建议分开做。Debug模式下使用MDd运行库,Release模式下使用MD运行库,混用会在运行时出现内存分配错误。另外,NX加载DLL要求64位编译,在VS的解决方案平台里选择x64,否则NX会直接拒绝加载并报错。
一个更隐蔽的问题是预处理器定义。NXOpen工程需要在预处理器里加上_USRDLL和NXOPENCPP这两个宏定义。有些开发者只加了_USRDLL,编译时头文件包含逻辑就不对,报出一堆莫名其妙的语法错误。
4. 进阶封装:把UF_VEC3_distance变成批量距离计算工具
4.1 封装成通用工具函数
一个项目里可能需要在几十个地方调用距离计算,每次写double[3]数组比较麻烦。更好的做法是封装一个工具函数,统一入口和错误处理逻辑。
#include <uf.h> #include <math.h> /* 工具函数:计算两个三维点的距离,带坐标合法性检查 */ double CalculateDistance(double* point1, double* point2) { /* 非法坐标检查:如果包含NaN或无穷大就直接返回0,避免污染计算结果 */ if (!isfinite(point1[0]) || !isfinite(point1[1]) || !isfinite(point1[2]) || !isfinite(point2[0]) || !isfinite(point2[1]) || !isfinite(point2[2])) { return 0.0; } /* 直接调用UFUN距离函数 */ return UF_VEC3_distance(point1, point2); }这个封装函数在两个场景下特别有用:一是从建模API拿到的坐标可能包含未初始化的值;二是用户在交互选择过程中可能存在点选不到的情况,导致坐标数组未赋值。isfinite是C标准库math.h里的函数,能同时检查NaN和无穷大。
参数说明:point1和point2是double数组指针,调用时传入double[3]即可。如果坐标合法就正常返回距离值,如果非法返回0.0并让调用方知道数据有问题。
4.2 批量计算点到点的距离
在实际项目中,比如装配干涉检查、刀路模拟、测量分析,经常需要计算一个点到一组点之间的距离,然后找出最近或最远的那一个。下面是批量距离计算的实现思路:
#include <uf.h> /* 批量计算参考点到所有目标点的距离,返回最近距离和对应索引 */ double FindNearestDistance(double* ref_point, double** target_points, int point_count, int* nearest_index) { double min_dist = DBL_MAX; int min_idx = -1; for (int i = 0; i < point_count; i++) { double dist = UF_VEC3_distance(ref_point, target_points[i]); /* 记录距离最小的点 */ if (dist < min_dist) { min_dist = dist; min_idx = i; } } *nearest_index = min_idx; return min_dist; }这个函数解决了「一个参考点对多个目标点求最近距离」的常见需求。target_points是二维数组,每一行是一个点坐标;point_count是目标点的数量;nearest_index通过指针参数把距离最近的点的索引回传给调用方。返回值是最小距离值。
实际使用中要注意,DBL_MAX在float.h头文件里定义,如果你的工程没有包含这个头文件,编译会报错。另外,当所有点都不合法时这个函数会返回DBL_MAX,调用方需要感知到这个边界情况。
4.3 把UF_VEC3_distance和NXOpen的Point对象打通
现在很多NX二次开发项目是NXOpen和UFUN混用的。从NXOpen的Point对象获取坐标再交给UF_VEC3_distance计算,这个流程很多人写错过。NXOpen的Point.Coordinates返回的是Point3d结构体,不能直接传给UF_VEC3_distance。
/* C#版本的NXOpen混用示例 */ using NXOpen; using NXOpen.UF; public class DistanceTool { public double DistanceBetweenPoints(Point point1, Point point2) { /* 从NXOpen Point对象提取坐标 */ Point3d p1 = point1.Coordinates; Point3d p2 = point2.Coordinates; /* 转换成UFUN的double[3]格式 */ double[] ufPoint1 = new double[3] { p1.X, p1.Y, p1.Z }; double[] ufPoint2 = new double[3] { p2.X, p2.Y, p2.Z }; /* 调用UF_VEC3_distance */ double distance = UFUFSession.GetUFSession().Vec3.Distance(ufPoint1, ufPoint2); return distance; } }这里的核心转换逻辑是:NXOpen用Point3d结构体(成员是X、Y、Z),UFUN用double[3]数组。两者看起来很像,但类型不同,不能直接强转。先用point.Coordinates取出坐标,再new一个double数组把值填进去,最后通过UFUFSession.GetUFSession().Vec3.Distance调用UF_VEC3_distance函数。
4.4 距离计算性能:批量场景下的优化空间
UF_VEC3_distance本身的开销极小,每次调用只有几个乘法和一个开方。真正的性能瓶颈在遍历点的循环本身,以及从tag获取坐标数据的IO操作。数据规模是10万个点以下,UF_VEC3_distance直接调用完全没问题。数据规模到了百万级别,就要考虑减少UF_CURVE_ask_line_data这种获取坐标信息的高开销调用,最好一次性把所有坐标都读出来存入内存数组,再循环计算距离。
/* 批量坐标读取后统一计算距离,避免反复调用UF API */ double* all_points = (double*)malloc(point_count * 3 * sizeof(double)); /* 循环中只做UF_VEC3_distance运算 */ for (int i = 0; i < point_count; i++) { double dist = UF_VEC3_distance(ref_point, &all_points[i * 3]); /* 处理距离结果 */ } free(all_points);&all_points[i * 3]这种写法相当于把一维数组当成二维数组用,第i个点的坐标存储在all_points[3*i]、all_points[3*i+1]、all_points[3*i+2]这三个连续内存槽位里。这样做的优势是内存连续,CPU缓存命中率高,比二维数组double[point_count][3]的形式在遍历时更友好。
5. 避坑与大坑排查:UF_VEC3_distance常见问题记录
5. 避坑与大坑排查:UF_VEC3_distance常见问题记录
5.1 忘记了UF_initialize直接调用函数导致死机
现象:在ufusr入口函数里直接调用UF_VEC3_distance,没有先调用UF_initialize()。程序运行到计算那行时NX直接崩溃,或者函数返回异常值。
原因:UFUN库的大部分函数都要求先调用UF_initialize()来初始化运行环境。这个初始化会建立UFUN和NX核心的数据交互通道,没有初始化的情况下直接调用,内部指针是空指针或无效状态,函数行为未定义。
解决:在ufusr函数的第一行加UF_initialize(),在函数返回前加UF_terminate()。这个习惯必须养成,不要只加初始化忘记了终止,否则NX会话里会积累未释放的内存资源,长时间运行会导致NX越来越卡。
5.2 坐标获取方式不对导致距离结果和NX测量不一致
现象:计算出来的距离和NX的测量命令(Analysis -> Measurement)结果不一样,差值可能是几百毫米甚至上千毫米。
原因:NX测量命令默认是在工作坐标系下显示的,但你从UF_CSYS_ask_origin_of_object获取的对象坐标是绝对坐标系下的值。两套坐标系不一致时,直接计算当然结果对不上。
解决:确认两个点来自同一个坐标系。如果来自不同坐标系,先通过UF_CSYS_map_point统一坐标系再计算。还有一个最隐蔽的情况——NX的测量命令可能启用了「投影距离」,而UF_VEC3_distance计算的是真实三维直线距离。投影距离是把点投影到某个平面再量距离,那结果当然更小。遇到结果不一致时先确认NX测量面板里选的是「3D距离」而不是「投影距离」。
5.3 传了非法值给UF_VEC3_distance函数
现象:计算结果是一个极大值、极小值或者NaN,完全不符合实际情况。
原因:两个点坐标数组里含有未初始化数据或者NaN。这在交互选择点失败、建模函数返回错误点、或者数组越界写入时经常发生。UF_VEC3_distance本身不校验输入数据,拿到什么算什么。
解决:调用UF_VEC3_distance之前用isfinite检查点坐标的每个分量,发现非法值就返回错误提示而不是继续计算。写一个工具函数统一处理这个问题,不建议在业务代码里重复写检查逻辑。
/* 安全计算:带数据合法性检查的距离计算 */ double SafeDistance(double* p1, double* p2) { for (int i = 0; i < 3; i++) { if (!isfinite(p1[i]) || !isfinite(p2[i])) { return -1.0; /* 返回负值表示计算失败 */ } } return UF_VEC3_distance(p1, p2); }5.4 Debug和Release混用导致DLL加载崩溃
现象:DLL编译成功,但在NX里执行菜单命令时NX直接闪退,没有任何错误提示。用Debug模式编译的DLL在Release版NX环境里运行尤其容易出现这种问题。
原因:Debug模式下C运行时库是MDd,Release模式是MD。两种模式的内存管理机制不同,Debug模式下分配的内存块带有调试信息头,Release模式的NX在释放时不知道如何处理这些额外的调试数据,导致内存损坏。
解决:发布给用户使用时必须用Release模式编译,勾选x64平台。开发调试阶段可以用Debug模式,但发布前要养成检查配置的习惯。另外注意NX 12.0及以上版本只支持64位DLL,32位编译的DLL就算Release模式也加载不了。
5.5 数组下标越界导致计算结果随机波动
现象:距离计算结果的数值每次都变,有时候对有时候错,完全琢磨不定。
原因:传入UF_VEC3_distance的数组长度不足3,或者调用前越界写入了别的值覆盖了数组内容。C语言不检查数组越界,程序不会直接报错,但数据已经被破坏,后续计算自然不稳定。
解决:检查所有创建坐标数组的代码,确保申请了3个double的内存空间。如果用了struct类型传参,要确保结构体里就是三个连续double成员,中间不能夹杂其他变量。从NXOpen转换坐标时,务必用new double[3]显式创建,不要用double[]的猜法。
6. 活用UF_VEC3_distance:测量模块开发的一种轻量方案
NX自带的测量命令功能完整,但有些场景下我们需要的不是复杂测量而是快速拿到特定几何的距离做后续判断。比如在自动检查装配间隙的代码里,只需要知道两个面的最近距离是否大于某个阈值;或者在加工路径规划时,需要判断刀尖点到工件表面的距离。UF_VEC3_distance在这些场景里可以作为测量模块的轻量替代方案。
做法是这样的:拿到两个几何对象后,先用UF_MODL_ask_minimum_dist_3计算它们之间的最小距离。这个函数输出的结果里就包含距离值,同时还能给出两个面上距离最近的一对点坐标。把这两个点坐标再用UF_VEC3_distance算一遍,交叉验证结果的正确性:
#include <uf_modl.h> #include <uf_vec.h> /* 计算两个对象之间的最小距离,并用UF_VEC3_distance做交叉验证 */ double MinDistanceBetweenObjects(tag_t object1, tag_t object2) { double min_dist = 0.0; double point_on_obj1[3], point_on_obj2[3]; double closest_points[2][3]; /* 调用UFUN求最小距离函数 */ UF_MODL_ask_minimum_dist_3(object1, object2, 0.0, &min_dist, point_on_obj1, point_on_obj2, closest_points); /* 用UF_VEC3_distance验证最近点对之间的距离 */ double verify_dist = UF_VEC3_distance(point_on_obj1, point_on_obj2); /* 如果两者差异超过0.001mm说明计算有问题 */ if (fabs(min_dist - verify_dist) > 0.001) { return -1.0; /* 返回-1表示计算不一致 */ } return min_dist; }UF_MODL_ask_minimum_dist_3的第一个参数是对象1的tag,第二个是对象2的tag,第三个是精度控制参数(传0.0表示使用默认精度),第四个是输出距离值,第五和第六个参数是最近点的坐标输出。这个函数本身已经能给到距离值,那为什么还要用UF_VEC3_distance做验证?因为UF_MODL_ask_minimum_dist_3在某些复杂曲面场景下可能出现收敛不到全局最优解的情况,返回的最小距离局部最优——如果和最近点坐标算出来的直线距离差异超过0.001mm,就说明结果不可靠。这个验证逻辑在模具分型面间隙检查里帮过大忙。
批量场景的处理方式是把所有对象存放在数组里,双重循环遍历每对组合,对每组调用MinDistanceBetweenObjects。注意UF_MODL_ask_minimum_dist_3是计算密集型函数,N个对象就是N*(N-1)/2次调用,对象数量超过500个时运行时间会比较明显,建议放在后台线程执行,同时加上进度条反馈给用户。
从那以后,我每次接到NX几何计算的需求,都会先想清楚一个问题——我要的是真实三维距离、投影距离、还是最小距离?确定之后再选UF_VEC3_distance还是UF_MODL_ask_minimum_dist,而不是拿到需求就直接复制代码。这个区分也建议你保持,能省掉大量排查和返工的时间。希望帮到你。
本文还有配套的精品资源,点击获取