在CAD/CAM软件开发领域,几何对象之间的距离计算是最基础也是最重要的功能之一。作为Siemens PLM Software旗下的旗舰产品,NX(原Unigraphics)提供了强大的二次开发接口,允许开发者通过API扩展软件功能。UFUN(User Function)作为NX Open API的核心组成部分,封装了大量底层几何操作算法,其中就包括我们今天要重点讨论的对象间最小距离计算功能。
实际工程应用中,最短距离计算有着广泛的使用场景:
与直接在NX界面中使用测量工具不同,通过UFUN编程实现距离计算的优势在于:
要进行NX二次开发,首先需要配置正确的开发环境。以下是基于Visual Studio的推荐配置步骤:
C:\Program Files\Siemens\NX 12.0\NXOPEN\CPP\include)C:\Program Files\Siemens\NX 12.0\NXOPEN\CPP\libs)libufun.lib、libnxopen.lib)注意:不同NX版本的头文件和库路径可能有所不同,建议通过NX安装目录下的docs查看具体路径。
每个NX二次开发程序都需要包含基本的初始化和清理代码。以下是典型的结构:
cpp复制#include <uf.h>
#include <uf_object_types.h>
int main(int argc, char* argv[])
{
// 初始化NX Open API
UF_initialize();
try {
// 主逻辑代码将放在这里
} catch(const NXOpen::NXException& ex) {
// 异常处理
std::cerr << "NX Exception: " << ex.Message() << std::endl;
} catch(...) {
// 通用异常处理
std::cerr << "Unknown exception occurred" << std::endl;
}
// 清理NX Open API
UF_terminate();
return 0;
}
在计算距离前,我们需要创建或获取几何对象。NX Open提供了多种创建几何体的方式:
cpp复制#include <NXOpen/Body.hxx>
#include <NXOpen/BodyCollection.hxx>
#include <NXOpen/Line.hxx>
// 获取当前工作部件
NXOpen::Part* workPart = theSession->Parts()->Work();
// 创建点对象
NXOpen::Point3d point1(0.0, 0.0, 0.0);
NXOpen::Point3d point2(10.0, 10.0, 10.0);
NXOpen::Point* pt1 = workPart->Points()->CreatePoint(point1);
NXOpen::Point* pt2 = workPart->Points()->CreatePoint(point2);
// 创建线对象
NXOpen::Line* line = workPart->Curves()->CreateLine(point1, point2);
在实际应用中,我们通常需要让用户选择已有对象而非创建新对象。NX Open提供了交互选择机制:
cpp复制#include <NXOpen/Selection.hxx>
// 创建选择管理器
NXOpen::Selection* selection = theSession->SelectionManager();
// 设置选择过滤器为仅选择体
NXOpen::Selection::SelectionAction action = NXOpen::Selection::SelectionActionClearAndEnableSpecific;
NXOpen::Array<NXOpen::Selection::MaskTriple> mask(1);
mask[0].Type = UF_solid_type;
mask[0].Subtype = 0;
mask[0].SolidType = UF_UI_SEL_FEATURE_ANY_SOLID;
// 提示用户选择第一个对象
NXOpen::NXObject* obj1 = selection->SelectObject("选择第一个对象", "请选择第一个几何体", action, mask);
// 提示用户选择第二个对象
NXOpen::NXObject* obj2 = selection->SelectObject("选择第二个对象", "请选择第二个几何体", action, mask);
UFUN提供了UF_MODL_ask_minimum_dist函数来计算两个对象间的最小距离,其原型如下:
cpp复制extern "C" int UF_MODL_ask_minimum_dist(
tag_t object1, // 第一个对象的tag
tag_t object2, // 第二个对象的tag
double* min_dist, // 返回的最小距离值
double pt1[3], // 第一个对象上的最近点坐标
double pt2[3] // 第二个对象上的最近点坐标
);
函数返回值为错误代码,0表示成功。参数说明:
结合前面的对象创建和选择代码,完整的距离计算实现如下:
cpp复制#include <uf_modl.h>
// 获取对象的tag标识
tag_t tag1 = obj1->Tag();
tag_t tag2 = obj2->Tag();
// 准备输出变量
double minDist = 0.0;
double point1[3] = {0};
double point2[3] = {0};
// 调用距离计算函数
int status = UF_MODL_ask_minimum_dist(tag1, tag2, &minDist, point1, point2);
if (status == 0) {
// 输出结果到NX信息窗口
NXOpen::ListingWindow* lw = theSession->ListingWindow();
lw->Open();
lw->WriteLine("最小距离计算结果:");
lw->WriteLine("距离值: " + std::to_string(minDist));
lw->WriteLine("对象1上的点: (" +
std::to_string(point1[0]) + ", " +
std::to_string(point1[1]) + ", " +
std::to_string(point1[2]) + ")");
lw->WriteLine("对象2上的点: (" +
std::to_string(point2[0]) + ", " +
std::to_string(point2[1]) + ", " +
std::to_string(point2[2]) + ")");
lw->Close();
} else {
// 错误处理
NXOpen::UI::GetUI()->NXMessageBox()->Show("错误",
NXOpen::NXMessageBox::DialogTypeError,
"距离计算失败,错误代码: " + std::to_string(status));
}
当处理复杂几何体时,距离计算可能会消耗较多资源。以下技巧可以提高效率:
cpp复制// 边界框检查示例
UF_MODL_ask_bounding_box(tag1, box1_corner1, box1_corner2);
UF_MODL_ask_bounding_box(tag2, box2_corner1, box2_corner2);
if (!boxesIntersect(box1_corner1, box1_corner2, box2_corner1, box2_corner2)) {
double bboxDist = distanceBetweenBoxes(box1_corner1, box1_corner2, box2_corner1, box2_corner2);
if (bboxDist > threshold) {
return bboxDist; // 直接返回边界框距离
}
}
计算得到的最小距离和最近点可以用于多种后续处理:
cpp复制// 创建距离标注示例
NXOpen::Annotations::Dimension* dimension =
workPart->Dimensions()->CreateLengthDimension(
NXOpen::Point3d(point1),
NXOpen::Point3d(point2),
NXOpen::SmartObject::Null,
NXOpen::Annotations::Dimension::TextPositionAbove,
NXOpen::Annotations::Dimension::TextAlignmentHorizontal,
NXOpen::Annotations::DimensionStyleBuilder::OffsetFromLineAutomatic);
在使用UF_MODL_ask_minimum_dist时可能会遇到以下错误:
错误代码1:无效的对象tag
错误代码3:对象类型不支持
错误代码5:内存不足
cpp复制// 调试输出示例
try {
// 关键操作代码
} catch(const NXOpen::NXException& ex) {
NXOpen::ListingWindow* lw = theSession->ListingWindow();
lw->Open();
lw->WriteLine("调试信息:");
lw->WriteLine("错误位置: " + std::string(__FILE__) + " line " + std::to_string(__LINE__));
lw->WriteLine("错误信息: " + ex.Message());
lw->Close();
}
基于最小距离计算,我们可以开发一个实用的装配体间隙检查工具:
cpp复制// 伪代码:装配体间隙检查
for (each component1 in assembly) {
for (each component2 in assembly) {
if (component1 != component2 && shouldCheckPair(component1, component2)) {
double dist = calculateMinimumDistance(component1, component2);
if (dist < safetyThreshold) {
addToReport(component1, component2, dist);
createVisualMarker(component1, component2);
}
}
}
}
对于需要处理大量对象对的情况,建议:
cpp复制// 进度条使用示例
NXOpen::UI::UI* ui = NXOpen::UI::GetUI();
NXOpen::UI::BlockUI* block = ui->BlockUI();
block->SetMessage("正在计算距离...");
block->SetProgress(0);
block->Show();
for (int i = 0; i < totalPairs; i++) {
if (block->WasCancelled()) break;
// 计算一对对象的距离
processOnePair(objectPairs[i]);
// 更新进度
block->SetProgress(100 * i / totalPairs);
}
block->Hide();
对于包含大量对象的场景,可以使用空间分区数据结构来加速距离计算:
cpp复制// 伪代码:基于空间分区的距离查询
Octree octree;
octree.build(allObjects);
for (each queryObject in queryObjects) {
// 只检查附近单元格中的对象
candidates = octree.queryNeighbors(queryObject);
for (each candidate in candidates) {
dist = calculateDistance(queryObject, candidate);
if (dist < minDist) {
updateMinDistance(dist);
}
}
}
利用现代多核CPU的并行计算能力:
注意:NX Open API不是线程安全的,多线程环境下需要特别注意:
- 每个线程需要独立的UF_initialize/UF_terminate调用
- 避免同时修改同一模型
- 使用互斥锁保护共享资源
cpp复制// 伪代码:多线程距离计算
std::vector<std::thread> threads;
std::mutex resultMutex;
std::vector<Result> results;
for (int i = 0; i < threadCount; i++) {
threads.emplace_back([&](int threadId) {
UF_initialize(); // 每个线程需要独立初始化
// 处理分配到的任务块
for (int j = threadId; j < totalPairs; j += threadCount) {
Result r = processPair(j);
std::lock_guard<std::mutex> lock(resultMutex);
results.push_back(r);
}
UF_terminate();
}, i);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
除了距离值本身,有时还需要获取距离向量(从一个对象指向另一个对象的最短方向):
cpp复制UF_MODL_ask_minimum_dist(tag1, tag2, &minDist, point1, point2);
// 计算距离向量
NXOpen::Vector3d distanceVector(
point2[0] - point1[0],
point2[1] - point1[1],
point2[2] - point1[2]);
// 单位化向量
distanceVector.Normalize();
基于最小距离计算,可以生成安全距离区域(如加工安全区、设备操作空间等):
cpp复制// 伪代码:生成安全距离区域
for (each face on tool) {
for (each face on workpiece) {
UF_MODL_ask_minimum_dist(toolFaceTag, workpieceFaceTag, &dist, pt1, pt2);
if (dist < safetyMargin) {
createOffsetSurface(workpieceFace, safetyMargin - dist);
}
}
}
在实际项目中应用最小距离计算功能时,我总结了以下几点经验:
对象预处理很重要:计算前确保几何体是干净的(无自相交、无退化面),否则可能得到错误结果。可以使用UF_MODL_check_small和UF_MODL_fix_body等函数进行几何修复。
性能监控不可少:在批量处理时添加计时逻辑,识别性能瓶颈。对于复杂装配体,单次距离计算可能耗时数秒,需要优化算法。
结果验证很关键:对于重要计算,建议用NX内置测量工具手动验证几个典型案例,确保程序结果正确。我曾经遇到过由于单位制不一致导致的数值误差问题。
异常处理要全面:除了NX异常,还要处理内存不足、用户取消等特殊情况。健壮的程序应该在任何情况下都能安全退出。
用户交互需友好:长时间计算时提供进度反馈,允许取消操作。结果展示要直观,可以使用高亮、标注等多种可视化手段。
文档记录不能忘:完善的API文档和用户指南能大大减少后期维护成本。特别要记录已知限制和特殊处理情况。
下面是一个综合了这些经验教训的改进版距离计算函数:
cpp复制DistanceResult calculateRobustDistance(tag_t obj1, tag_t obj2)
{
DistanceResult result;
try {
// 记录开始时间
auto start = std::chrono::high_resolution_clock::now();
// 检查对象有效性
if (!UF_is_object_valid(obj1) || !UF_is_object_valid(obj2)) {
throw std::runtime_error("无效的对象tag");
}
// 执行距离计算
double minDist = 0;
double pt1[3] = {0}, pt2[3] = {0};
int status = UF_MODL_ask_minimum_dist(obj1, obj2, &minDist, pt1, pt2);
if (status != 0) {
throw NXOpen::NXException(status);
}
// 记录结束时间
auto end = std::chrono::high_resolution_clock::now();
// 填充结果
result.distance = minDist;
result.point1 = NXOpen::Point3d(pt1[0], pt1[1], pt1[2]);
result.point2 = NXOpen::Point3d(pt2[0], pt2[1], pt2[2]);
result.elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
result.status = DistanceStatus::Success;
} catch (const NXOpen::NXException& ex) {
result.errorMessage = "NX错误: " + std::string(ex.Message());
result.status = DistanceStatus::NxError;
} catch (const std::exception& ex) {
result.errorMessage = "系统错误: " + std::string(ex.what());
result.status = DistanceStatus::SystemError;
} catch (...) {
result.errorMessage = "未知错误";
result.status = DistanceStatus::UnknownError;
}
return result;
}