【随笔】一个CAD开发接口的使用和思考

       国外CAD设计软件,比如CATIA、UG/NX,被广泛应用于各行各业,也催生出了大量基于成熟平台进行二次开发的工作。这里记录一个UG/NX平台上的一个Ufun接口的使用问题、解决方案和一些思考。

1. 需求描述

       需求是这样的:给定距离曲面很近的一个参考点,计算曲面上的最近点及其法向量。这个需求非常简单,计算过程是:(1) 根据参考点反求在该曲面上的uv参数,(2) 根据曲面表示和uv参数,正向计算此处的点坐标和法向量。但是,在第(2)步出现问题,主要代码如下:

    //输入
    NXOpen::Face* pNXFace;      //给定曲面
    double        coordRef[3];  //给定参考点坐标

    //获取uv参数过程略
    double        uv_pair[2] = { 0.0 };
    
    //初始化计算器
    UF_EVALSF_p_t evaluator;
    UF_EVALSF_initialize(pNXFace->Tag(), &evaluator);

    //计算uv参数上的信息
    int deriv_flag = UF_MODL_EVAL_ALL;
    UF_MODL_SRF_VALUE_p_t surf_eval = new UF_MODL_SRF_VALUE_s();
    UF_EVALSF_evaluate(evaluator, deriv_flag, uv_pair, surf_eval);

    //检查结果,tol为容许的最大误差
    if (CalDistance(coordRef, surf_eval->srf_pos) > tol)
    {
      return -1;
    }

       问题出在第35行,本来参考点距离曲面非常近,最近点和参考点的距离也应该很小,但实际距离大于设定的误差,导致返回错误码(-1)。

2. 深入探究

       一开始,这样的问题是让我莫名其妙的,这么强大的CAD平台上的这么简单的功能,怎么会出错呢?接下来就是怎么解决这个问题了,有两种思路:(1) 是不是使用方法有误,比如检查输入条件;(2) 寻找其它相似接口。我简单地看了一下UF_EVALSF_evaluate函数的介绍,提示这个函数可能会在某些场景下有问题,可以尝试UF_MODL_evaluate_face和 UF_MODL_ask_face_props函数。后面就分别写了这三种函数的实现,发现后两个函数的结果是一致的、正确的,也就是说,可以采用这两个函数实现以上功能。

       但是,前面一个函数存在的意义是什么?目前猜测可能有两种考虑:(1) 该函数是功能版本,为了兼容以前的应用,所以保留了下来;(2) 该函数为了某个特定场景而开发。有时候确认这样的问题是无从下手的,甚至是无意义的,毕竟无法看到源码,只能从接口介绍里寻找蛛丝马迹。接下来开始系统地看该函数所在的文件,不得不说,NX接口的介绍是很完善的,在所在文件开头写着:

File description:

       Open API interface to functions that evaluate surfaces. These functions are the preferred way for fast evaluation of faces and underlying surfaces.

       Note that UF_EVALSF_evaluate and UF_EVALSF_evaluate_array may not properly handle parameter values returned by UF_EVALSF_find_closest_point or similar functions for surface types Blend or Trimmed B-surface. The functions UF_MODL_evaluate_face or UF_MODL_ask_face_props should be called instead.

       Note - please use the new UF_EVALSF_initialize_2 to avoid above problem.

       到这里,前面的两个问题都有了答案:(1) 该函数是为了效率而开发的;(2) 问题的解决办法不在于UF_EVALSF_evaluate函数,而是将初始化函数UF_EVALSF_initialize替换成UF_EVALSF_initialize_2函数。替换后,果然结果就正确了,与另外两个接口的结果一致。效率是CAD软件开发者非常关心的,那么就比较一下三者的计算效率,代码如下:

int TestNXFaceEvaluateEfficiency(NXOpen::Face* pNXFace)
{
  int numRow = 100;
  int numCol = 100;
  int stepRow = 1.0/numRow;
  int stepCol = 1.0/numCol;
  std::vector<double> vctTestUParm;
  std::vector<double> vctTestVParm;
  for (int i = 0; i <= numRow; i++)
  {
    for (int j = 0; j <= numCol; j++)
    {
      double uParm = stepRow*i;
      double vParm = stepRow*j;
      vctTestUParm.push_back(uParm);
      vctTestVParm.push_back(vParm);
    }
  }

  int numTest = (int)vctTestUParm.size();
  
  clock_t start;
  clock_t end;
  
  start = clock();
  for (int i = 0; i < numTest; i++)
  {
    double uv_pair[2] = { vctTestUParm[i], vctTestVParm[i] };

    UF_EVALSF_p_t evaluator;
    UF_EVALSF_initialize_2(pNXFace->Tag(), &evaluator);

    int deriv_flag = UF_MODL_EVAL_ALL;
    UF_MODL_SRF_VALUE_p_t surf_eval = new UF_MODL_SRF_VALUE_s();
    UF_EVALSF_evaluate(evaluator, deriv_flag, 
       uv_pair, surf_eval);
  }
  end = clock();
  cout<<"Time1:   "<<end-start<<"ms"<<endl;

  start = clock();
  for (int i = 0; i < numTest; i++)
  {
    double uv_pair[2] = { vctTestUParm[i], vctTestVParm[i] };

    int deriv_flag = UF_MODL_EVAL_ALL;
    UF_MODL_SRF_VALUE_p_t surf_eval = new UF_MODL_SRF_VALUE_s();
    UF_MODL_evaluate_face(pNXFace->Tag(), deriv_flag,
      uv_pair, surf_eval);
  }
  end = clock();
  cout<<"Time2:   "<<end-start<<"ms"<<endl;

  start = clock();
  for (int i = 0; i < numTest; i++)
  {
    double uv_pair[2] = { vctTestUParm[i], vctTestVParm[i] };

    double point_F[3] = { 0.0 };
    double u1[3] = { 0.0 };
    double v1[3] = { 0.0 };
    double u2[3] = { 0.0 };
    double v2[3] = { 0.0 };
    double unit_norm[3] = { 0.0 };
    double radii[2] = { 0.0 };
    UF_MODL_ask_face_props(pNXFace->Tag(), uv_pair,
      point_F, u1, v1, u2, v2, unit_norm, radii);
  }
  end = clock();
  cout<<"Time3:   "<<end-start<<"ms"<<endl;

  return 0;
}

运行代码,输出结果如下:

Time1:    127 ms
Time2:    103 ms
Time3:    75  ms

       UF_EVALSF_evaluate函数反而用的时间最长。结果也算是在意料之中,在写上面代码时,故意将计算器初始化放在了for循环之内。因为我猜测计算器提效的原理是采用了缓存技术,比如存储初始化时或者当前计算过程中的通用量,以备后续计算重复使用。关键在复用,如果每次计算都需要初始化新的计算器,那就不会起作用,甚至会因为额外计算而导致效率降低。进一步验证,将计算器初始化放在for循环外面。运行代码,输出结果:UF_EVALSF_evaluate函数的运行时间是3ms,与UF_MODL_evaluate_face函数对比,提升了34倍。

总结

       整个问题解决的过程就告一段落了,以下是一些个人思考:

  • 问题的出现并不一定是坏事,解决的过程可能会带来意外收获。通过不断地挖掘,可以获得对接口甚至是底层技术的深层理解。当然,前提是开发时间允许。
  • 接口使用的正确与否很大程度上受限于说明文档的质量,但反过来,说明文档再好,也不一定能正确使用接口,比如NX的注释和说明文档已经很好了,但是关键点不一定能看到,看到了也不一定能联想到解决办法。
  • 二次开发总会受制于人。上面的问题其实是个非常简单的功能,定义良好,需求明确,尚且如此。但对于复杂功能,比如曲线投影,场景复杂,求解困难,应用起来会更加困难,一旦出现问题就只能想办法补救,比如总结失败规律,提前处理好输入条件,但是,费了很大劲实现各种补救方案,可能还是会出现问题:(1) 额外处理导致效率变低;(2) 仍然存在失败情况。然后发现问题无法解决,不得不改变整体设计流程。这就导致开发周期难以控制,甚至项目烂尾。

参考文献

[1] UGOpen Function Reference Manual, UGS Corp.

[2] The NURBS Book (2nd Edition), Les Piegl, et al., Springer.

 

文章首发于微信公众号:CAD智造干将,欢迎关注
 
请添加图片描述

  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值