简介:这是一份面向CATIA CAA二次开发工程师的Nurbs插件完整源码包,基于CAA架构为CATIA扩展非均匀有理B样条建模能力,适合需要自定义自由曲面工具或研究达索CAA程序框架的开发者。包内为完整工程,共929个文件,压缩包约11.16MB,以C++头文件(535个h)和实现文件(72个cpp)为主,另包含mk/mkdg编译脚本、CATNls多语言资源、CATDlg对话框资源、CATfct目录描述及位图图标等,能够清晰呈现CAA组件从源码编译到资源装载的完整结构,目录组织便于按模块检索。目前已有325人学习下载。源码覆盖Nurbs曲线创建、控制点编辑、权重调整与点生成等模块,对应交互对话框和图标资源齐全,适合作为CATIA插件二次开发的实战参考,对理解CAA工程构建方式、对话框搭建、多语言配置和编译脚本编写均有直接帮助,也能为复杂曲面工具的设计提供可复用的示例。
1. 一份能跑通 NURBS 编辑全流程的 CAA 源码:拆包之前先弄清它是什么
凡是做 CATIA V5 二次开放的人,应该都体会过这种憋屈:业务算法不难,难的是把命令、对话框、图标、本地化字符串这一整套 CAA 骨架搭起来。命令点了没反应、对话框弹出来全是英文、NLS 文件改名后按钮直接消失——这些才是真正吃掉时间的坑。这套CAAKcNurbsPluginFrm_20200430_CAANurbs恰好把 NURBS 曲线从「新建 Part -> 创建点 -> 创建曲线 -> 编辑控制点 -> 编辑权重」的全链路做成了源码包,解压后能看到 CATDlg 对话框、CATNls 本地化、CATfct 目录资源、bmp 命令图标,四类 CAA 资源文件齐全。它适合正在写 CAA 插件但卡在工程结构上的工程师,也适合想用现成骨架改造成自己 NURBS 工具的人。本篇就把这份源码拆开,讲清楚每一类资源文件干什么、怎么编译跑起来、改哪些地方能变成自己的插件。
2. 先认资源文件:CATDlg、CATNls、CATfct 和 bmp 在 CAA 工程中各管哪一段
CAA 工程拿到手的第一件事,不是急着编译,而是先根据文件后缀判断它是不是一个完整的 Framework。这套资源里的文件清单如下:
| 文件 | 类型 | 在 CAA 工程中的作用 |
|---|---|---|
| NewPartDlg.CATDlg | 对话框实现文件 | 新建 Part 的交互对话框,决定新建文档还是复用当前文档 |
| CreatePointDlg.CATDlg | 对话框实现文件 | 创建点的参数对话框,这里输入笛卡尔坐标 |
| MainCmdHeader.CATNls | 本地化资源 | 命令标题、帮助文本、状态栏提示的字符串表 |
| CreatePointDlg.CATNls / MmrNurbsCreatePointDlg.CATNls | 本地化资源 | 对话框控件文本的资源文件,同一个对话框可能对应多语言版本 |
| CAAMmrNurbsCurveCatalog.CATfct | 目录资源 | 把 NURBS 曲线对象注册进 CATIA 的对象浏览器,供外部按名调用 |
| I_CreatePoint.bmp / I_CreateNurbs.bmp | 命令图标 | 工具条按钮激活时显示的位图 |
| I_EditCtlPoint.bmp / I_EditWeight.bmp | 命令图标 | 编辑控制点和编辑权重两个命令的按钮图标 |
2.1 从文件清单识别 CAA 工程类型:Resource 与 Implement 的划分
一个 CAA Framework 通常拆成两个工程:一个 Implement 工程(放 C++ 实现,编译出 dll),一个 Resource 工程(放对话框、位图、NLS、Catalog 等,编译后进 resources 目录)。这套 zip 里 CATDlg、CATNls、CATfct、bmp 四类文件同时出现,说明它至少包含完整的 Resource 层,而.CATDlg虽然是对话框实现,但在 CAA 的工程划分里它往往独立成一个 Dialog 相关的模块,不跟几何内核的 dll 搅在一起。
识别方法有一个实用套路:看文件前缀。CATDlg后缀的文件是对话框类定义,CATNls是本地化,CATfct是框架目录,bmp是位图。如果解压后还有.m文件、.h文件、.dll或.tie文件,说明它是带 Implement 的完整框架;如果只有资源文件,那需要你自己补一个调用它的 Implement 工程。这套包里虽然没列出 .h 和 .cpp,但 CATDlg 本身是 C++ 源码,编译时会参与生成,所以它更像一个「Resource 为主、对话框源码混排」的混合结构——这恰恰是 CAA 早期工程的典型形态。
我拿到这类包时,第一步会打开 CATDlg 文件看框架声明,确认对话框继承的是 CATDlgDialog 还是 CATDlgIndirectDialog,这决定了它是模态主窗口还是嵌入式的属性页。从文件命名看,NewPartDlg 是模态对话框,CreatePointDlg 是模态的建模参数框,二者在 CAA 里的基类一般是 CATDlgDialog。
2.2 CATNls 与 CATfct:命令头部、目录字典和字符串注册的机制
CATNls 是 CATIA 本地化机制的载体,格式上类似 Java properties 文件,每一行是「资源标识 = 文本内容」。以 MainCmdHeader.CATNls 为例,它对应命令头部的声明:
# 命令头部资源:标题、简短说明、帮助文本 CreateNurbsCmd.Title="Create NURBS Curve" CreateNurbsCmd.Comment="Create a NURBS curve from control points" CreateNurbsCmd.Help="This command creates a NURBS curve, supports editing weights and control points" EditCtlPointCmd.Title="Edit Control Point" EditCtlPointCmd.Comment="Move or tweak control point positions"这段文件想表达的机制是:CAA 命令派生类里用CATDeclareHeader声明命令标识,同时要在对应的 CATNls 里注册该标识下的 Title/Comment/Help 三件套。命令按钮在工具条上显示的文字取自 Title,鼠标悬停的提示取自 Comment,底栏帮助取自 Help。如果 CATNls 缺失或标识拼写不一致,命令在工具条上显示为空白或直接加载失败——这是后面避坑章里最常见的一种翻车。
CATfct 则是框架级的目录文件,CAA 里它负责把对象工厂、数据字典注册进 CATIA 的运行时类库。常见的调用方式是:
// 通过 Catalog 中注册的接口名创建 NURBS 创建器 CATICreateNurbs* pNurbsCreator = NULL; HRESULT rc = ::CATCreateInstance("CAAMmrNurbsCurveCreator", IID_CATICreateNurbs, (void**) &pNurbsCreator); if (FAILED(rc) || NULL == pNurbsCreator) { // Catalog 未注册或框架未加载时,这里会失败 return -1; }CATCreateInstance的第二个接口 ID 需要对上接口定义。这个方法在 CAA 里属于用名字换对象的黑匣子式调用,好处是解耦,坏处是排查难——一旦 CATfct 没编进去,这里是静默失败。所以调试这类源码时,我一般会在调用前后各打一条日志,确认到底是 Catalog 缺失还是几何内核初始化的失败。
2.3 位图与对话框资源:命令图标和交互面板是怎么挂上去的
bmp 图标在 CAA 里的挂载点是命令声明的资源位置。命令类构造函数里一般这样绑定图标:
// 命令的图标资源路径,通常指向 Resource 工程里的 bmp 文件 NewAccess 一命名的代码中可以看到: static const char* const _I_CreateNurbs = "I_CreateNurbs.bmp"; SetAccessCmdBarCommand( _I_CreateNurbs, "CreateNurbsCmd" );图标绑定的原理是:CAA 启动时按「框架名 + 命令名 + 资源名」的索引去 resources 目录找位图,所以 bmp 文件必须跟 CATNls 里的命令标识严格对应。位图分辨率一般做成 24x24 或 32x32,超过这个尺寸 CATIA 工具条会拉伸失真,这是视觉层面的小坑。
CATDlg 对话框资源的组织方式则是把控件声明和事件回调写在一个类里,典型的构造片段:
// CreatePointDlg 的架构:构造函数里创建控件,回调里读取值 CreatePointDlg::CreatePointDlg(CATCommand* pParent, const CATString& name) : CATDlgDialog(pParent, name, CATDlgFraOKCancel) { _pX = new CATDlgSpinEdit(this, "CoordX"); _pY = new CATDlgSpinEdit(this, "CoordY"); _pZ = new CATDlgSpinEdit(this, "CoordZ"); // Attach 到布局管理器,设置取值范围 _pX->SetRange(-10000.0, 10000.0); }这里的要点是:CATDlg 不是 XML 界面描述,而是 C++ 代码构建的控件树,每个控件都要Attach到对话框容器并指定归属,否则控件创建了也不显示。它跟 CATNls 的联动方式是:控件创建时传入的字符串就是 CATNls 里的资源标识,CATDlgSpinEdit(this, "CoordX")的第二个参数在运行时会被 CATIA 拿去查 Nls 表,查到就显示本地化文本,查不到就原样显示。所以 CreatePointDlg.CATNls 跟 CreatePointDlg.CATDlg 同名不完全是巧合,是「对话框绑定同源 Nls」的常见约定。
3. 用 RADE 把工程跑起来:环境变量、mkmk 编译与运行时资源路径
熟悉这套骨架之后,下一步就是在自己的机器上把它编译出来。CAA 的构建体系跟普通 Visual Studio 工程完全不是一回事:它依赖 RADE 提供的一整套环境变量,用mkmk命令驱动,而不是直接按 F7。
3.1 导入前的环境准备:版本匹配与 SetASF 变量
CAA RADE 的版本必须和 CATIA V5 对应,这一点没有回旋余地。V5R28 配 RADE V5R28、V5R21 配 RADE V5R21,跨版本编译时 Math 和 Geometry 输出目录的命名会不同,链接阶段会报一堆莫名其妙的 LNK 错误。
路径设置也有讲究。CAA 的编译输出目录通常分为Intelf_a和CNext两套,Intelf_a是 Windows 下的中间文件和 dll 输出,CNext是跨平台通用目录。需要在系统环境变量里加上CATIA_PATH、PATH、LD_LIBRARY_PATH(Linux 下)指向本机 CATIA 安装目录的 code/bin,这一步漏了后面所有步骤都起不来。
我一般会先打开 CAA RADE 自带的命令行工具(叫做mkmk控制台),在里面确认环境变量是否注入成功:
# 检查 CAA 环境中关键的 CATIA 路径变量 echo %CATIA_PATH% echo %PATH% | findstr CATIA如果输出里找不到 CATIA 的路径,说明 RADE 的环境没有加载,需要先运行 CAA RADE 的环境初始化批处理,或者直接使用 RADE 桌面图标进入的开发控制台。这一步是 CAA 开发里最常见的环境类坑,很多编译报错的根源不是代码,而是mkmk根本没用同一个环境。
3.2 用 RADE 导入 workspace:mkmk 构建与目录产物
把 zip 解压后放到你自己的 CAA workspace 目录,然后用 mkmk 构建。构建命令只有一条:
mkmk -a CAAKcNurbsPluginFrm_20200430_CAANurbs-a参数表示 build all,会递归处理该 Framework 下所有模块,包括对话框模块和资源模块。构建产物分布在两个位置:dll 文件进入CAAKcNurbsPluginFrm_20200430_CAANurbs\Intelf_a\code\bin,资源文件(CATNls 的编译结果、CATfct、bmp)进入同级的resources目录。对于 Resource 工程,mkmk会把.CATNls文本编译成二进制化 NLS 文件,所以不要试图把源 Nls 直接拷贝到 CATIA 安装目录——CATIA 加载的是编译后的 NLS 资源模块。
这里有个容易踩的动作:手动修改了源 NLS 文本后,必须重新对 Resource 模块执行构建,而不是只重新编译 dll。做法是:
# 只重建 Resource 模块,不碰 C++ 编译 mkmk -m CAAKcNurbsPluginFrm_20200430_CAANurbs -r如果构建成功,控制台尾部会输出Build done之类的提示。失败时优先看当前模块对应的 *.mk 文件里的报错行,CAA 的报错常见两类:头文件找不到,和某个接口 ID 未定义。前者查 CATIA 版本匹配,后者查 Framework 依赖列表是否写全。
3.3 编译通过只是第一步:运行时模块搜索路径与部署
CAA 插件在运行时按 Framework 名去固定目录找资源和 dll,所以光编译成功不代表 CATIA 能加载它。部署有两种常见做法:一种是直接把整个 workspace 输出目录加入PATH和CATIA_PATH;另一种是把 code/bin 和 resources 拷贝到 CATIA 安装目录对应的扩展位置。对于个人开发,我推荐前者,因为调试时能立刻生效,不用反复拷贝。
关键环境变量是CATIA_PATH,它定义了运行时 Framework 搜索路径。设置方式:
# 把 CAA 框架的输出根目录设进 CATIA_PATH set CATIA_PATH=<workspace_root>\CAAKcNurbsPluginFrm_20200430_CAANurbs:%CATIA_PATH%注意:这里加的是 Framework 根目录,不是 code/bin 也不是 resources,CATIA 会自己在根目录下找 code/bin 和 resources 子目录。
部署完成后,在 CATIA 里看新增工具条,如果按钮出来了但点击无反应,优先查 RADE 调试输出窗口里模块是否成功加载。CAA 的模块加载是惰性的,只有第一次点击命令时才会触发 dll 加载,所以「按钮亮、点击没反应」多数是 dll 未部署或依赖的几何内核框架没被引用。
4. NURBS 创建与编辑流程:从 NewPartDlg 到控制点和权重改形
资源骨架认完了、能编译能加载,接下来看这份源码的核心业务:NURBS 曲线的完整交互链路。它从 NewPartDlg 开始,到 CreatePointDlg 采集坐标,再到创建曲线和编辑控制点/权重,每一步都对应一个交互模块。
4.1 NewPartDlg 与 CreatePointDlg:交互采集输入的实现路径
NewPartDlg 的作用是先解决「几何画在哪」的问题。CAA 里新建 Part 的标准做法是通过 CATIContainerOfDocument 拿容器,再创建几何容器。这个对话框关闭后,主命令会拿到指向 Part 文档的容器指针,后续创建的点会挂靠在这个容器下。
CreatePointDlg 则纯粹是参数面板,三个轴方向的输入框值确认后转成一个 CATMathPoint:
// CreatePointDlg 获取输入,并转成 CAA 几何运算用的点对象 double x = _pX->GetValue(); double y = _pY->GetValue(); double z = _pZ->GetValue(); CATMathPoint mathPt(x, y, z); // 通过几何工厂创建特征点 CATIGeometricFactory* pGeomFactory = NULL; pContainer->QueryInterface(IID_CATIGeometricFactory, (void**) &pGeomFactory); CATIPoint_var spPoint = pGeomFactory->CreatePoint(mathPt);这段代码有两点值得注意。第一,CreatePoint的参数类型是CATMathPoint而不是三个独立 double,CAA 里把数学对象和特征对象分得很清,Math 对象只做运算,Feature 才进 CATIA 的规范树。第二,QueryInterface的返回值必须检查,如果容器类型不对,IID_CATIGeometricFactory查不到,这里返回 E_NOINTERFACE,后续创建点就是空指针。这不是玄学,是 CAA 接口查询的常态,每个 QueryInterface 后面都应该有 FAILED 判断。
4.2 CAAMmrNurbsCurveCatalog.CATfct:曲线内核接口的 CAA 封装入口
创建点的数据备好后,主命令开始调用 NURBS 创建器。这套资源里的CAAMmrNurbsCurveCatalog.CATfct就是干这个的——它为外部命令提供一条找到 NURBS 创建接口的路径。在 CAA 架构里的调用方式是:
// 通过 Catalog 获取 Nurbs 曲线的创建工厂 CATICreateNurbs* pCreateNurbs = NULL; HRESULT hr = ::CATCreateInstance("CAAMmrNurbsCreateNurbs", IID_CATICreateNurbs, (void**) &pCreateNurbs); if (SUCCEEDED(hr) && pCreateNurbs) { // 传控制点数组和权重数组给内核 CATMathPointArray controlPoints; CATMathNurbsCurve* pCurve = NULL; pCreateNurbs->Create(controlPoints, weights, knots, degree, &pCurve); }CAAMmrNurbsCurveCatalog从命名推测,库里面应该注册着 CAAMmrNurbsCurve 和 CAAMmrNurbsCreateNurbs 两个对象。前者是曲线实体,后者是创建工厂。CAA 的 Catalog 机制本质是 Latent Object 的注册表,业务方拿到的是接口指针,具体实现藏在框架内部,这就是「用名字替对象」的解耦思想。调试时如果CATCreateInstance返回失败,可以用 CATIA 的 Object Browser 查一下这个类名是否真的被注册,而不要直接怀疑代码逻辑。
4.3 权重与会插值:NURBS 不同于 Bezier 的两个关键参数
这套插件把「编辑权重」单独做成一个命令按钮,说明作者很清楚 NURBS 最有价值的参数不是控制点,而是权重。NURBS 曲线的数学形式是有理式,权重 w 出现在分子和分母上:
C(u) = Σ(N_i,p(u) * w_i * P_i) / Σ(N_i,p(u) * w_i)这个形式带来两个直接结果。第一,所有权重同时放大一个常数倍,曲线形状完全不变,因为分子分母同时缩放,消掉了。所以权重的绝对值没有意义,只有相对值有意义。第二,权重越大,曲线被拉向对应控制点,而且这个吸引作用只在局部节点区间生效,不会像 Bezier 那样牵一发动全身。
权重编辑的代码路径大致是这样:
// 编辑权重:修改第 i 个控制点的权重值 CATMathNurbsCurve* pCurve = ...; // 已有曲线对象 double newWeight = 2.5; // 当前权重放大 2.5 倍 pCurve->SetWeight(i, newWeight); // i 是控制点索引,从 0 开始这里踩坑概率很高的是索引语义。NURBS 的权重数组长度跟控制点数组长度一致,但某些内核实现里权重是按「每一段」存的,长度是控制点数减次数。改代码前先打印一下所有权重数组长度和默认值,默认权重通常都是 1.0,排查时从首尾向中间对比更快。
节点向量是另一个必须理解的参数。Bezier 是 NURBS 的特例:次数等于控制点减 1、节点向量只有首尾重复到次数加 1。这个插件既然叫 Nurbs 而不是 Bezier,大概率支持不均匀节点,也就是你可以局部加密一段区间,实现「某一段更贴近控制点」的效果。这是它比纯 Bezier 高级的地方,也是调形时最容易出直觉偏差的地方——权重改了曲线没反应,先看节点向量在该区间是不是均匀的,不均匀区域对权重变化不敏感,这不是 bug。
5. 避坑记录:CAA 插件从编译到弹窗最常翻车的 6 个点
代码能跑通的框架大同小异,跑不通的框架各有各的姿势。下面这 6 条是处理这类 CAA 资源包时最常遇到的坑,每一条都对应一次真实翻车,写成案例供你避雷。
5.1 现象:mkmk 编译报错找不到 CATIA 头文件
fatal error C1083: Cannot open include file: 'CATGeoFactory.h'这类错。原因通常是 CAA 环境变量没加载完整,编译器没拿到 CATIA 安装目录的 include 路径。解决:重开 RADE 控制台,确认%MSDEV_DIR%和%CATIA_PATH%都已正确设置,再看mkmk -v的输出里编译命令行是否带了-I指向 CATIA 的 include。注意 RADE 控制台和普通 CMD 的环境变量集合完全不同,不要用普通 CMD 跑 mkmk。
5.2 现象:命令按钮出现在工具条上,点击无任何反应
现象是按钮能显示,但点击后既不弹对话框也不执行任何逻辑。原因通常有两类:一是命令实现 dll 没有被加载(检查模块名是否在*.CATNls的头部资源里登记);二是命令类没有正确实例化,命令入口的工厂函数没实现。解决:打开 RADE 调试视图,把日志级别调到 Info,点击按钮后看Module not found还是Command not created,前者查部署路径,后者查命令类的CreateCommand实现。
5.3 现象:对话框弹出来全是英文
对话框文本控件显示的是原始资源标识或者英文默认值,而不是本机中文。原因基本锁定为 CATNls 资源文件没有被编译或未被加载。解决:确认 Resource 模块执行了mkmk -r重建,然后检查部署目录中resources下是否有编译后的 Nls 相关文件。这里有个隐蔽点:CATNls 文件编码必须是 UTF-8 或 CATIA 认可的编码,用记事本另存为带 BOM 的 UTF-8 反而可能让 CATIA 读不懂,我一般统一用不带 BOM 的 UTF-8。
5.4 现象:权重编辑对曲线形状没有影响
既有可能是权重数组索引搞错,也可能是数值范围问题。原因分析:如果权重从 1.0 改成 1.01,本身变化就微乎其微,曲线视觉上看不出来;如果索引传错,改的是末尾权重,而那段控制点在曲线外延,自然没效果。解决:改权重前先打印GetWeightLength(),把所有权重打印出来,确认索引范围是 0 到长度减 1;想看得明显,先直接改成 10.0 或 0.1,确认生效后再精细调。
5.5 现象:修改命令名后工具条按钮消失
这是个非常容易掉进去的坑。你以为只改 CATNls 里的 Title 就行,结果按钮整个不见了。原因:CATIA 定位命令按钮时,靠的是命令头部的标识而非 Title 文本。你改了资源标识(等号左边的 key),等于告诉 CATIA 去找一个不存在的新命令。解决:只改 Title 后面的值文本,不要改等号左边的标识;如果确实要修改标识,需要同步改命令类里的CATDeclareHeader,两处必须保持一致。
5.6 现象:调试时断点打不进去,点击一次才加载
CAA 调试的断点打不进去,跟普通 C++ 程序感觉完全不同——你设好断点,但 CATIA 点击命令前,代码根本不在内存里。原因:CAA 框架默认惰性加载,第一次点击命令时才把 dll 拉进进程。解决:在项目属性里设置Debugging > Command Arguments为运行 CATIA 的完整路径,并开启「调试时加载所有模块」选项;或者先手工用CATPUT工具加载一遍模块,再开始调试。
提示:以上每条坑都建议按「先看日志、再查资源、最后查代码」的顺序排查。CAA 报错信息少,很多时候日志里一句话就定位了,别先怀疑自己的 NURBS 算法。
6. 把它改成自己的插件:换本地化字符串、换图标、加一种插值模式
框架摸透之后,最后的实战是把它改造成你自己的插件。改动分三层:最外层是视觉和文案,中间层是命令逻辑,最深层是 NURBS 生成算法。
文案层改动最安全。打开 MainCmdHeader.CATNls,把Title和Comment改成你的业务措辞,保持等号左边的标识不动,重新mkmk -r,按钮文字即刻更新。图标替换需要保持 bmp 尺寸一致,我常用 32x32 的 256 色位图,把 I_CreateNurbs.bmp 换成你的图标,文件名保持原名。这一层的原则是:只改值、不改键、不改文件名。
逻辑层要加功能,最实用的是加一种插值模式。原包大概率是「给定控制点 -> 生成逼近曲线」,但工程里还有一种需求是「给定数据点 -> 生成穿过这些点的插值曲线」。CAA 的 NURBS 内核通常提供插值接口,常见做法是:
// 插值模式:输入数据点,内核反算控制点与节点 CATMathPointArray dataPoints; // 输入:欲穿过的点列 CATMathNurbsCurve* pInterpCurve = NULL; pCreateNurbs->Interpolate(dataPoints, degree, &pInterpCurve);这个接口和Create的区别在于:Create你给控制点,内核只算曲线;Interpolate你给数据点,内核会反算一组控制点,使得曲线刚好穿过这些数据点。反算过程是解线性方程组,对数据和节点向量都很敏感,数据点太密或分布不均时容易产生振荡。我一般把插值次数压在 3 次以内,超过 5 次配合不均匀节点很容易出现过冲。
权重编辑命令也可以顺手加强一下:现在权重是手工输入,可以改成在曲线上拾取控制点后自动显示当前权重,做成一个滑条实时调。这样用户能直观感受到「权重越大、曲线越贴控制点」的 NURBS 特性。
这套源码带给我最大的启发不是 NURBS 算法本身,而是 CAA 工程的资源组织方式——命令、图标、本地化、对话框各居其位,改一层不伤另一层。从那以后我每次在 CATIA 里看到工具条按钮显示异常,都会强制自己先走一遍「资源文件编译没、路径对不对、标识一致不一致」这三连问,而不是一上来就去翻 C++ 业务代码。这个习惯帮我省下了大量排查时间,希望也能帮到你。
本文还有配套的精品资源,点击获取