NX 二次开发编程方式:
1、GRIP(Graphics Interactive Programming, 图形交互编程);
2、KF(Knowledge Fusion, 知识熔接技术);
3、SNAP(Simple NX Application Programming, 简易 NX 应用编程);
4、NXOpen C++,也称为 User Function,简称 UFUN;
5、NXOpen.NET;
6、NXOpen Java;
7、NXOpen Python。
NX 二次开发架构关系:
NX 二次开发内部机制:
无论采用哪种方式编程,最终都是调用共享的 C/C++ API。
官方在开发 NX 时使用的是 C/C++,再通过 *.ja 文件控制是否要自动生成其他开发方式对应的 API。
NX 二次开发流程:
设计菜单与功能区 -> 设计对话框 -> 设计代码
NX 系统中的原生工具通常由三部分组成:菜单与功能区、对话框、代码。
设计菜单与功能区:
菜单与功能区使用 MenuScript 编写。
设计对话框:
对话框使用 NX 中 Block UI Style 模块的相关 UI Block 设计。
设计代码:
代码使用 Visual Studio 调用 NXOpen C/C++ 相关 API 完成。
NX 版本与编译器的对应关系(C/C++):
NX 12(NX 版本) - Visual Studio 2015 Build 19.00.23026(Windows 下编译器) - gcc 4.8.2(Linux 下编译器)
NX 1926/NX 1953(NX 版本) - Visual Studio 2019 Professional 16.0.5 Build 19.20.27508.1(Windows 下编译器) - gcc 7.3.1(Linux 下编译器)
使用与 NX 版本不匹配的编译器设计代码时:
编译链接能通过,但在 NX 中运行应用程序时系统报错。
解决方法:
使用 NX 对应版本的 Visual Studio 重新编译链接。
在低版本 NX 中开发的应用程序不能在高版本 NX 中运行:
高版本 NX 中的 *.lib 文件发生了变化,需要在高版本 NX 对应的 Visual Studio 中使用最新的库,修改代码再编译链接它。
高低版本通用情况:
通常情况下,在低版本 NX 中开发应用程序时,如果只使用 libufun.lib、libnxopencpp.lib、libugopenint.lib、libnxopenuicpp.lib 这四个库,是可以在高版本 NX 中运行应用程序的。
创建项目:
启动 VS,单击 Create a new project(新建项目) 选项,在弹出的对话框中单击 Windows Desktop Wizard() 选项,再单击 Next 按钮。
将 %UGII_BASE_DIR%\UGOPEN\vs_files\VC 文件夹合并到 Visual Studio 中的 VC 文件夹中。
点击 Finish 即可。
NXOpen C++ Wizard(开发向导) 方式会自动完成手工搭建环境的一系列设置,并创建一个 *.cpp 文件。在该文件中,系统默认创建了 MyClass 类,还添加了 ufusr() 与 ufusr_ask_unload() 函数。
VS:
在 do_it() 函数中添加代码:
void MyClass::do_it()
{
UF_initialize();
lw->Open();
lw->WriteLine("Hello World!");
UF_terminate;
// TODO: add your code here
}
点击 Build(生成(B)) -> Build Solution(生成解决方案(B)),对项目进行编译链接,生成 *.dll 动态链接库文件。
NX:
在 NX 中单击 FIle(文件(F)) -> Execute(执行(T)) -> NX Open,选择该动态链接库文件 *.dll,运行结果如下:
库文件(DLL 文件):
库文件是计算机上的一类文件,提供给使用者一些开箱即用的变量、函数或类。
可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。
库文件分为静态库和动态库:
在库文件的发展史上经历了 无库 - 静态链接库 - 动态链接库 的时代。
静态链接库与动态链接库都是共享代码的方式。
静态库和动态库的区别体现在程序的链接阶段:
1、静态库在程序的链接阶段被复制到了程序中;
也就是说,如果使用静态链接库,库中的指令都被直接包含在最终生成的可执行文件中了。
2、动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。使用动态库系统只需载入一次,不同的程序可以得到内存中相同的动态库的副本,因此节省了很多内存,而且使用动态库也便于模块化更新程序。
也就是说,如果使用动态链接库,该库文件则不必被包含在最终可执行文件中,可执行文件执行时可以“动态”地引用和卸载这个与可执行文件独立的库文件。
文件扩展名:
Windows 的静态库文件扩展名是 .lib,动态库文件扩展名是 .dll (Dynamic-Link Libraries),Linux 的静态库扩展名是 .a,动态库扩展名是 .so (Shared Object)。
应用程序与动态库的关系:
在操作系统中,许多应用程序并不是只有一个完整的可执行文件,大多数程序模块被分割成一些相对独立的动态库。当执行某一个程序时,相应的动态库文件就会被调用。一个应用程序可使用多个动态库文件,一个动态库文件也可能被不同的应用程序使用,这样的动态库文件被称为共享库文件。
NXOpen C 命名约定:
NXOpen C API 有两种命名约定,即 标准命名约定 和 遗留命名约定。
标准命名约定(Standard Naming Convention):
根据 API 所在的模块与实现的功能赋予有意义的名称。这一类 API 格式是 UF_<area>_<name>。
1、UF 是 User Function(用户函数) 的简写;
2、<area> 通常是对应用模块或功能领域的说明;
例如,UF_MODL_create_sphere 中的 MODL 表示 Modeling(/ˈmɒdlɪŋ/, 建模) 模块。
3、<name> 通常是对实现的具体功能的描述,一般由动词或名词组成。
常见的词语有 ask(查询)、get(获取)、create(创建)、new(新建)、edit(编辑)、delete(删除)、set(设置)、init(初始化)、remove(移除)等。
遗留命名约定(Legacy Naming Convention):
这一类 API 格式是 uc---- 或者 uf----,其中 ---- 通常是4位数字或者3位数字加1字母,NX 早期采用 FORTRAN 语言开发,FORTRAN77 中变量名长度限制为6。
这一类型的 API 大部分已经被其他标准命名约定的 API 替代,但还有一部分被开发者青睐,如 uc1601、uf5947,它们的名字没有规律可循,只需记忆。
以 Sketch(/sketʃ/, 草图)为例:
希望利用 NXOpen C 中的 API 创建一个 Sketch,根据模块分类这个 API 应该是以 UF_SKET 开头的,例如,UF_SKET_create_sketch。
NXOpen C 数据类型:
除 C/C++ 标准的数据类型外,NXOpen C 还大量使用结构体、联合结构体、枚举、指针等数据类型。
NXOpen C 数据类型约定:
1、后缀 _t:数据类型(Data type);
2、后缀 _p_t:数据类型指针(Pointer to that type);
3、后缀 _s:结构体类型(Structure tag);
4、后缀 _e:枚举类型(Enumeration type);
5、后缀 _u_t:联合结构体类型(Union type);
6、后缀 _u_p_t:联合结构体类型指针(Pointer to a union type);
7、后缀 _f_t:函数指针(Pointer to a function)。
除了后缀满足上述约定,数据类型的命名约定还与 API 命名约定一致。
结构体 UF_MODL_bsurface_s 来自与建模相关的头文件(uf_modl_types.h):
struct UF_MODL_bsurface_s
{
int num_poles_u;
int num_poles_v;
int order_u;
int order_v;
int is_rational;
double *knots_u;
double *knots_v;
double (*poles)[4]; /* <len:num_poles_u*num_poles_v> */
} ;
typedef struct UF_MODL_bsurface_s UF_MODL_bsurface_t, *UF_MODL_bsurface_p_t;
tag_t:
在 NXOpen C 中,使用最多的数据类型是 tag_t(/tæɡ/, 标签,标识符),在文件 %UGII_BASE_DIR%\UGOPEN\uf_defs.h 中的定义如下:
typedef unsigned int tag_t;
数据类型 tag_t 早期被称为 Eid(Entity Identifier, 实体标识符),现在通常被称为 对象标识符(Object Identifier)。
在 NX 系统中,每一个对象都有唯一的标识符,可以理解为每一个对象都是由大于0的不重复整数来标识。
Handle(句柄):
对象标识符是由 NX 系统根据一定的算法规则临时生成的,它不会随着部件的保存而保存。例如,开发者希望当前工作部件(Part)中指定的面(Face) 在下次打开部件时,自动高亮显示,而一般情况下,同一部件在不同的计算机上打开,或者被再次打开后,对象标识符的值是不同的。此时可考虑使用 handle(句柄)来解决这个问题。handle 会随着部件的保存而被保存。
相关 API 如下:
UF_TAG_ask_handle_of_tag(查询标识符的句柄)
UF_TAG_ask_tag_of_handle(查询句柄的标识符)
UF_TAG_compose_handle(构造句柄)
UF_TAG_decompose_handle(分解句柄)
NX 中常见的对象:
Feature(/ˈfiːtʃə(r)/, 特征)
Point(/pɔɪnt/, 点)
Edge(/edʒ/, 边)
Face(/feɪs/, 面)
Body(/ˈbɒdi/, 体)
对象拥有 Type(/taɪp/, 类型)和 Subtype(/ˈsʌbˌtaɪp/, 子类型) 两部分定义,其中 Subtype 可以更详细地描述对象。
对于 NX 中已经存在的对象,可以通过以下 API 获取:
API | 说明 |
---|---|
UF_OBJ_ask_type_and_subtype | 获取对象的 Type 和 Subtype |
UF_MODL_ask_object | 查询对象 |
UF_OBJ_cycle_all | 遍历 |
UF_OBJ_cycle_by_name | |
UF_OBJ_cycle_by_name_and_type_extended | |
UF_OBJ_cycle_objs_in_part | |
UF_OBJ_cycle_typed_objs_in_part |
遍历当前工作部件中所有的 Body 示例代码:
static void do_it(void)
{
int type = UF_solid_type, stype = UF_solid_body_subtype;
tag_t obj = NULL_TAG;
while(UF_MODL_ask_object(type, stype, &obj) == 0 && obj != NULL_TAG)
{
}
}
solid /ˈsɒlɪd/ 实体
NX 对象存在四种状态:
值 | 说明 |
---|---|
UF_OBJ_ALIVE | 对象是活跃的。 |
UF_OBJ_DELETED | 对象已删除。 |
UF_OBJ_TEMPORARY | 对象是临时的,不保存在部件中。例如,利用 UF_CSYS_create_temp_csys 创建的 CSYS。 |
UF_OBJ_CONDEMNED | 这种状态的对象通常在 NX 中不显示,例如,矩阵对象、智能对象等。 |
在开发应用程序时,为了保证程序代码的稳定性,一般情况下会先获取对象的状态,如果对象的状态是 UF_OBJ_ALIVE,再执行其他操作。
可以利用 UF_OBJ_ask_status 获取对象的状态。
考虑这样一种情况,NXOpen C 中的 UF_MODL_create_block 创建了一个 Block,输出的是 Block Feature 的标识符,此时如果要对所有的 Edge(边)进行 Edge Blend 操作,那么传入的对象应当是 Edge 的标识符,而不是 Feature 的标识符。这就涉及到对象之间的相互转换问题。
常见的对象转换 API:
API | 说明 |
---|---|
UF_MODL_ask_feat_body | 通过 Feature 查询 Body |
UF_MODL_ask_feat_edges | 通过 Feature 查询 Edges |
UF_MODL_ask_feat_faces | 通过 Feature 查询 Faces |
UF_SKET_ask_feature_sketches | 通过 Feature 查询 Seketches |
UF_SKET_ask_sketch_features | 通过 Sketch 查询关联 Features |
UF_MODL_ask_body_features | 通过 Body 查询 Features |
UF_MODL_ask_body_faces | 通过 Body 查询 Faces |
UF_MODL_ask_body_edges | 通过 Body 查询 Edges |
UF_MODL_ask_face_body | 通过 Face 查询 Body |
UF_MODL_ask_face_feats | 通过 Face 查询 Features |
UF_MODL_ask_edge_body | 通过 Edge 查询 Body |
UF_MODL_ask_edge_faces | 通过 Edge 查询 Faces |
UF_MODL_ask_edge_feats | 通过 Edge 查询 Features |
头文件与 API 分类:
在使用 NXOpen C API 时,应该添加对应的头文件,头文件与 API 分类一一对应。
例如,新建一个部件(part)使用的 API 是 UF_PART_new,应当添加 uf_part.h 头文件。
NXOpen C 提供的 API 符合 ANSI/ISO C 标准,在相应的头文件中定义的原型格式为:
<return data type> <function name> (argument list)
<return data type>:
大部分的 NXOpen C API 返回都是一个整数(int),
1、为0时,表明该 API 执行正常;
2、非0时,表明该 API 执行异常。
如果开发者希望获得非0整数所代表的意义,需要使用 API UF_get_fail_message。
需要注意的是,有部分 API 返回非0整数时,不表示执行有异常,而是有其他某种具体的含义。例如,UF_PART_ask_num_parts API 返回的是当前 NX 会话中加载部件的数量。
<function name>:
遵循 NXOpen C 命名约定。
(argument list):
在 API 帮助文档中,API 的描述一般由以下部分组成:
1、Defined in:描述 API 被定义在哪个头文件中;
2、Overview:描述 API 实现的功能等;
3、Environment:描述 API 允许的运行模式;
如果描述中不包含关键词 External(/[ɪkˈstɜːnl/, 外部的),说明该 API 不允许以批处理模式(Batch(/bætʃ/, 批), 又称外部模式 External)运行。
例如,uc1601 API 不允许以批处理模式运行。
4、See Also:描述与该 API 相关联或者类似的 API;
若描述中含有 Refer to example 这些字段,表明该 API 有样例,点击它即可查看。
5、History:描述 API 发布的 NX 版本;
6、Required License(s):使用该 API 需要的 License(/ˈlaɪsns/, 许可);
7、API 详细声明:一般格式如下表示
以 UF_MODL_create_plane 为例:
类型 | 参数 | 输入/输出 | 描述 |
---|---|---|---|
double | origin_point[3] | Input | Origin point of the plane(平面原点) |
double | plane_normal[3] | Input | Plane normal(平面法向) |
tag_t* | plane_tag | Output | New plane(创建的平面) |
在创建平面的 API 声明中,创建一个平面需要一个起点和法向量,所以在输入参数时,会要求指定 Plane(/pleɪn/, 平面) 的原点和法向量,如果成功创建就输出创建的 Plane 的标识符。
3D 模型通常是基于草图(Sketch)创建的,有了草图,就可以利用 Extrude(/ɪkˈstruːd/, 拉伸)、Revolve(/rɪˈvɒlv/, 旋转)等工具创建实体形状。
NXOpen C 中草图特征操作常用 API:
API | 说明 |
---|---|
UF_SKET_initialize_sketch | 初始化草图环境(进入草图环境)。 |
UF_SKET_terminate_sketch | 终止(退出)草图环境。 |
UF_SKET_create_sketch | 创建一个空的草图。 |
UF_SKET_update_objects | 更新指定草图。 |
UF_SKET_ask_sketch_status | 查询草图的状态(自由度)。 |
UF_SKET_ask_active_sketch | 获取活动草图的标识符。 |
UF_SKET_ask_feature_sketches | 获取给定特征(Feature)关联的所有草图。 |
UF_SKET_ask_sketch_info | 获取给定草图的信息,例如,矩阵、草图名称、视图等。 |
UF_SKET_ask_exps_of_sketch | 获取给定草图的所有表达式。 |
UF_SKET_ask_sketch_features | 获取给定草图关联的特征。 |
UF_SKET_add_objects | 添加几何对象到指定的草图中。 |
UF_SKET_ask_geoms_of_sketch | 获取给定草图的所有几何对象。 |
UF_SKET_ask_sketch_of_geom | 通过给定几何对象标识符查询草图标识符。 |
UF_SKET_mirror_objects | 根据中心线镜像草图中的对象。 |
UF_SKET_create_dimension | 创建尺寸,输出尺寸标识符。 |
UF_SKET_delete_dimensions | 删除指定草图的尺寸。 |
UF_SKET_create_dimensional_constraint | 创建尺寸约束,输出尺寸约束标识符。 |
UF_SKET_ask_dimensions_of_sketch | 获取给定草图的所有尺寸约束。 |
UF_SKET_create_geometric_constraint | 创建几何约束。 |
UF_SKET_ask_geo_cons_of_sketch | 获取给定草图的所有几何约束。 |
UF_SKET_ask_geo_cons_of_geometry | 获取给定草图几何对象的所有几何约束。 |
UF_SKET_delete_constraints | 删除指定草图的几何约束。 |
extern UFUNEXPORT int UF_SKET_create_sketch
(
char name[ UF_OBJ_NAME_BUFSIZE ],
/* <I> Sketch name. It can be at most UF_OBJ_NAME_LEN bytes long. */
int option,
/* <I> Option. 1: Sketch on face/datum plane; 2: Specify sketch CSYS. */
double matrix[9],
/* <I> Sketch CSYS (for option = 2): [0-5]: X-AXIS and Y-AXIS of matrix; [6-8]: Origin of CSYS. */
tag_t object[2],
/* <I> Objects (for option = 1): [0]: Solid face/Datum plane object; [1]: Reference object; (edge, datum axis, solid face/datum). */
int reference[2],
/* <I> Reference and direction (for option = 1): [0]: Reference edge 1: Horizontal, 2: Vertical; [1]: Direction 1: Start to end (from vertex1 to vertex2), -1: End to start (from vertex2 to vertex1). */
int plane_dir,
/* <I> Datum plane direction: 1: Outwards from parent body; 2: Inward. */
tag_t *sketch_id
/* <O> Tag of teh sketch created */
);
extern /ˈekstɜːn/ 外部变量声明
bufsize(buffer size) 缓冲区大小
option /ˈɒpʃn/ 选项
datum /ˈdeɪtəm/ 数据
plane /pleɪn/ 平面
Specify /ˈspesɪfaɪ/ 具体说明
matrix /ˈmeɪtrɪks/ 矩阵
axis /ˈæksɪs/ 轴
reference /ˈrefrəns/ 参考,引用
direction /dəˈrekʃn/ 方向
horizontal /ˌhɒrɪˈzɒntl/ 水平的
vertical /ˈvɜːtɪkl/ 垂直的
vertex /ˈvɜːteks/ 顶点
outwards /ˈaʊtwədz/ 向外
inward /ˈɪnwəd/ 向内
NXOpen C 创建草图特征的一般流程:
1、UF_SKET_initialize_sketch 初始化草图环境;
2、UF_SKET_create_sketch 创建空草图;
3、其他操作,例如,UF_SKET_add_objects 添加对象到草图;
4、UF_SKET_terminate_sketch 退出草图环境。
需求是在 NX 中执行某一操作时,也执行 NXOpen 应用程序,例如,在保存一个部件时检查文件名是否合法。
为了解决这种需求,NX 在某些位置规定了出口(Exit),可以在指定的出口自动运行编写好的 NXOpen 应用程序。
在使用 NXOpen API 时需要正确地初始化与终止。
1、当使用 NXOpen API 前需要调用 UF_initialize 来初始化 API 环境和获取许可;
2、当不再使用 NXOpen API 时需要调用 UF_terminate 来终止许可。
一般情况下,可以考虑在程序入口点采用下列格式编写代码来实现初始化与终止:
extern "C" DllExport void ufusr(char* param, int* retcod, int param_len)
{
if(UF_initialize() == 0)
{
// 添加用户代码
UF_terminate();
}
}
大部分的 NXOpen C API 运行完毕时返回一个整数,如果非0表示有异常。当有上万行代码,其中一行代码出现 BUG,运行完毕时才得到非0,那么有没有一种手段,当代码运行异常时,能立马返回,并且自动输出代码行号,错误原因等。
UF_CALL 为解决这一问题提供了可行性。UF_CALL 是一个自定义的宏,开发者可以参考官方思路进行修改。
UF_CALL(UF_PART_new(part_name, UF_PART_ENGLISH, &part));
#include <stdarg.h>
#include <stdio.h>
#include <uf.h>
#include <uf_curve.h>
#include <uf_obj.h>
#include <uf_ui.h>
#define UF_CALL(X) (report(__FILE__, __LINE__, #X, (X)))
static void ECHO(char* format, ...)
{
char msg[UF_UI_MAX_STRING_LEN] = {0};
va_list args;
va_start(args, format);
vsnprintf_s(msg, sizeof(msg), __TRUNCATE, format, args);
va_end(args);
UF_UI_open_listing_window(); // 打开信息窗口
UF_UI_write_listing_window(msg); // 向信息窗口写入内容
}
static int report(char* file, int line, char* call, int irc)
{
if(irc)
{
char err[133] = {0};
UF_get_fail_message(irc, err); // 获取错误信息
ECHO("*** ERROR code %d at line %d in %s:\n", irc, line, file);
ECHO("+++ %s\n", err);
ECHO("%s;\n", call);
}
return(irc);
}
static void do_it(void)
{
// 创建点
tag_t pointA = NULL_TAG;
double pointACoord[3] = {0.0, 0.0, 0.0};
UF_CALL(UF_CURVE_create_point(pointACoord, &pointA));
// 删除点
UF_CALL(UF_OBJ_delete_object(pointA));
// 设置删除点的颜色
UF_CALL(UF_OBJ_set_color(pointA, 186));
}
void ufusr(char* param, int* retcode, int paramLen)
{
if(UF_CALL(UF_initialize()) == 0)
{
do_it();
UF_CALL(UF_terminate());
}
}
int ufusr_ask_unload(void)
{
return (UF_UNLOAD_IMMEDIATELY);
}
curve /kɜːv/ 曲线
report /rɪˈpɔːt/ 输出
terminate /ˈtɜːmɪneɪt/ 终止
immediately /ɪˈmiːdiətli/ 立即