简介:这份资源面向从事CATIA二次开发的工程师与研究人员,聚焦基于CAA组件应用架构的界面开发技术。内容以Windows XP平台下VC++ 6.0为工具,讲解如何新建独立workbench、添加自定义菜单与工具条按钮、插入CATIA风格对话框,并通过command响应调用对话框、由输入参数直接生成三维模型,同时以五坐标数控机床运动仿真系统为实例展示开发成果。资源包内含1个doc文档,约205KB,属于图文并茂的技术说明类资料,便于按章节查阅与对照实践。目前已有6409人学习下载,说明其在CATIA开发圈内具有较高关注度。对于希望掌握CAA C++开发流程、理解进程内与进程外两种二次开发方式差异、并需要界面定制与运动仿真参考案例的读者,这份文档提供了较为完整的思路与实现路径,可作为入门与进阶的实用参考。
1. 从一份老资料说起:CATIA 二次开发到底能干什么
如果你手头有一份基于 CAA 的 CATIA 界面二次开发资料,却卡在“知道它能定制界面,但不知道从哪下手”,这篇笔记就是拆给你看的。CATIA 作为高端 CAD/CAM 系统,自带 20 多个模块,但真正做产品设计的人往往需要把重复操作固化成一个按钮、把一套参数化建模流程塞进一个对话框。这份资料的核心价值,就是用 VC++ 6.0 配合 CAA 组件应用架构,在 CATIA V5 里新建独立 workbench、挂自定义工具条和菜单、插入 CATIA 风格对话框,最后通过输入参数直接生成三维模型。它适合两类人:一是做数控机床运动仿真、工装夹具参数化的工程师,二是想从宏录制过渡到 CAA 深度定制的开发者。注意,CAA 的开发门槛比 VBA 宏高不少,但能碰到的底层接口也深得多。
2. CAA 与宏的分水岭:进程内和进程外到底怎么选
2.1 两种通信方式的实际差异
CATIA 二次开发接口通过两种方式与外部程序通信:进程内应用程序和进程外应用程序。进程内方式下,CATIA 与脚本运行在同一进程地址空间,典型代表就是宏。你在 CATIA 环境里通过菜单记录宏,生成 VB 脚本序列,运行期间 CATIA 处于非激活状态,宏调用之间不能存储变量值。这种方式简单,在 CATIA 环境里就能完成,适合录一段重复操作然后回放。
进程外方式下,CATIA 与外部应用程序在不同进程地址空间运行。CATIA 运行的情况下,外部进程可以通过接口驾驭 CATIA,创建、修改环境和几何形体的数据、尺寸,同时支持对象连接与嵌入。这意味着你可以用 VC++ 写一个独立程序,在后台操控 CATIA 完成建模、装配、仿真,而不需要 CATIA 界面处于激活状态。
选型逻辑很直接:如果只是把每天重复的十步操作变成一键执行,宏足够;如果要做一个带对话框、带参数输入、带模型自动生成的专用工具,必须走 CAA。资料里那个“输入截面圆半径、拉伸长度、拉伸方向与截面法向角度值生成拉伸体”的例子,就是典型的 CAA 场景——宏做不到这种程度的参数化交互。
2.2 CAA RADE 的开发环境约束
CAA 的快速应用研发环境 RADE 以 Microsoft Visual C++ 6.0 为载体,开发工具完全集成在 VC++ 环境中,并且提供了一个 CAA 框架程序编译器,但同时也限制了 VC++ 的部分功能。这是很多人第一次配环境时容易翻车的地方:你装了一个高版本 Visual Studio,发现 CAA 的工程模板根本加载不了。资料明确写了基于 VC++ 6.0,这不是随便选的,是 RADE 的硬约束。
CAA 采用面向对象的程序语言,开发过程可以看作是其组件对象的组合和扩展。CAA 采用组件对象模型和对象的连接和嵌入技术。所有接口都是从 IUnknown/CATBaseUnknown 继承的,这意味着你写的每一个类都要挂在这棵继承树上。常见做法是先用 CAA Rade 菜单里的“Insert → CATIA Resource → Command”生成框架代码,再往里填业务逻辑,而不是从零手写。
提示:如果你现在用的是 Windows 10/11,VC++ 6.0 的安装本身就可能遇到兼容性问题。我一般会建议在虚拟机里跑 Windows XP 或 Windows 7 32 位环境,专门用来做 CAA 编译,避免和日常开发环境打架。
3. 从零建一个 workbench:工具条、菜单和按钮的代码拆解
3.1 创建工作台的六个步骤
CATIA V5 将某类包含一系列交互命令的工具条分组显示在不同的 workbench 中。新建独立 workbench 的目的,是把二次开发形成的一系列命令集中显示在一个工作台里,便于查找和使用。资料给出了六个步骤:创建工作台厂的接口、创建工作台厂、创建工作台描述类、创建响应命令的标题、创建工作台并排列图标按钮响应、提供图片及提示等资源并将新建的工作台插入开始菜单。
这六步里,真正需要写代码的是描述类和资源文件。工作台描述类 CAAAfrGeoCreationWkb 派生于 CATBaseUnknown 类,类中建立两个函数:CATCmdWorkbench* 的 CreateWorkbench() 函数和无返回值类型的 CreateCommands()。前者负责顺序插入工具条、按钮图标以及菜单,后者负责对插入按钮以及菜单和响应函数的关联。
3.2 工具条和按钮的 NewAccess 宏
在 CreateWorkbench() 函数中,核心是 NewAccess 宏。CATIA 的 workshop 或 workbench 可以被看作是一个入口的集合包,使用 NewAccess 宏可以创建一个这样的入口。使用 SetAccessChild 和 SetAccessNext 这两个宏则可以连接入口。className 表示被创建类的类型,包括 CATCmdContainer、CATCmdWorkshop、CATCmdSeparator、CATCmdStarter。
// 创建工具条容器 NewAccess(CATCmdContainer, pCAAAfrTB1EltTlb, CAAAfrTB1EltTlb); // 将工具条加入工作台 SetAccessChild(pCAAAfrGeoCreationWkb, pCAAAfrTB1EltTlb); // 创建按钮 cmd1,设置其响应宏为 CAAAfrCmd1Hdr NewAccess(CATCmdStarter, pCAAAfrTTB1EltCmd1Str, CAAAfrTTB1EltCmd1Str); SetAccessCommand(pCAAAfrTTB1EltCmd1Str, "CAAAfrCmd1Hdr"); // 将按钮加入工具条 SetAccessChild(pCAAAfrTB1EltTlb, pCAAAfrTTB1EltCmd1Str);这段代码的逻辑是:先创建一个工具条容器,把它挂到工作台下面;再创建一个按钮启动器,把按钮的响应命令指向 CAAAfrCmd1Hdr,最后把按钮挂到工具条下面。SetAccessChild 建立父子关系,SetAccessNext 建立兄弟关系。参数命名有约定:p 开头表示指针,CAAAfr 是项目前缀,TB1 表示 Toolbar 1,Elt 表示 Element,Cmd1 表示 Command 1。这套命名不是强制的,但保持一致性会让你在几十个按钮里找代码时不至于崩溃。
3.3 图标和提示资源的关联
按钮建好后,需要绘制图标并在 CAAAfrGeoCreationWkbHeader.CATRsc 中关联。资料里的写法是:
CAAAfrGeoCreationWkbHeader.CAAAfrCmd1Hdr.Icon.Normal = "I_CAACmd1";然后在 CAAAfrGeoCreationWkbHeader.CATNls 文件中设置按钮的标题和提示内容:
CAAAfrGeoCreationWkbHeader.CAAAfrCmd1Hdr.Category = "Element"; CAAAfrGeoCreationWkbHeader.CAAAfrCmd1Hdr.Title = "command1"; CAAAfrGeoCreationWkbHeader.CAAAfrCmd1Hdr.ShortHelp = "new cmd1";CATRsc 管图标资源,CATNls 管文本资源。这两个文件是 CAA 的资源描述文件,编译时会被打包进 CATIA 的资源体系。常见坑是图标文件名大小写不匹配——Windows 文件系统不区分大小写,但 CATIA 资源加载器区分,写 I_CAACmd1 就必须是 I_CAACmd1.bmp,不能是 i_caacmd1.bmp。
3.4 菜单的添加与按钮的差异
菜单的添加与添加按钮类似,也是在 CreateWorkbench() 函数中,但宏的参数不一样。菜单需要先创建一个 CATCmdContainer 作为菜单容器,再创建 CATCmdSeparator 作为分隔符,然后创建子菜单容器和菜单项启动器。
// 创建菜单容器 NewAccess(CATCmdContainer, pCAAAfrGeoCreationMbr, CAAAfrGeoCreationMbr); NewAccess(CATCmdContainer, pCATAfrInsertMnu, CATAfrInsertMnu); SetAccessChild(pCAAAfrGeoCreationMbr, pCATAfrInsertMnu); // 创建分隔符 NewAccess(CATCmdSeparator, pCAAAfrGeoCreationInsertSep, CAAAfrGeoCreationInsertSep); SetAccessChild(pCATAfrInsertMnu, pCAAAfrGeoCreationInsertSep); // 创建子菜单 NewAccess(CATCmdContainer, pCAAAfrMeu1EltSnu, CAAAfrMeu1EltSnu); SetAccessNext(pCAAAfrGeoCreationInsertSep, pCAAAfrMeu1EltSnu); // 创建菜单项并绑定响应 NewAccess(CATCmdStarter, pCAAAfrMMeu1SubMn1Str, CAAAfrMMeu1SubMn1Str); SetAccessChild(pCAAAfrMeu1EltSnu, pCAAAfrMMeu1SubMn1Str); SetAccessCommand(pCAAAfrMMeu1SubMn1Str, "CAAAfrCmd1Hdr");菜单和按钮的关键差异在于:按钮用 SetAccessChild 直接挂到工具条,菜单需要先建分隔符再用 SetAccessNext 串联。菜单的标题在 CATNls 文件中设置,比如 CAAAfrMeu1EltSnu.Title = "新建菜单 1"。注意菜单项的响应命令可以和按钮共用同一个 CAAAfrCmd1Hdr,这样点击按钮和点击菜单项触发的是同一个对话框。
4. 对话框与响应类:从按钮点击到模型生成
4.1 三种 command 类型的选择
建好的 workbench 空框架中可以创建三种类型的响应类,都是 CATCommand 派生出来的。添加过程是打开 CAA Rade 菜单“Insert”,选择“CATIA Resource”下的“Command...”。三种类型分别是 Statechart command、dialog-box based command 和 Basic command。
Basic command 创建一个空的响应类,在 Activate(CATCommand* iFromClient, CATNotification* iEvtDat) 中添加响应。dialog-box based command 在二次开发中会被更多用到,选择该类型响应类,运行时会直接弹出一个对话框,可以对这个对话框进行编辑。Statechart command 适合有状态流转的复杂交互,比如需要根据上一步输入决定下一步显示什么控件。
我一般会这样选:如果只是弹个框让用户输几个参数然后点确定,用 dialog-box based command;如果按钮点击后需要连续多步交互,用 Statechart command;如果只是执行一段无界面的后台逻辑,用 Basic command。
4.2 对话框控件的布局与回调
对话框编辑界面右侧自带一些控件,包括 CATDlgFrame、CATDlgLabel、CATDlgEditor、CATDlgPushButton、CATDlgRadioButton 等。对话框内所有控件在 Build() 函数中列出,应用 SetGridConstraints(short int iTopRow, short int iLeftColumn, short int iRowSpan, unsigned int iJustification) 通过设置控件在对话框中矩阵位置的方式来调整位置。
// 在 Build() 函数中创建控件 _pLabelRadius = new CATDlgLabel(this, "RadiusLabel"); _pLabelRadius->SetTitle("截面圆半径"); _pEditorRadius = new CATDlgEditor(this, "RadiusEditor"); _pEditorRadius->SetVisibleTextWidth(20); _pButtonOK = new CATDlgPushButton(this, "OKButton"); _pButtonOK->SetTitle("确定"); // 设置控件在网格中的位置 SetGridConstraints(_pLabelRadius, 1, 1, 1, CATDlgGridConstraints::CATDlgGridLeft); SetGridConstraints(_pEditorRadius, 1, 2, 1, CATDlgGridConstraints::CATDlgGridLeft); SetGridConstraints(_pButtonOK, 3, 2, 1, CATDlgGridConstraints::CATDlgGridRight);SetGridConstraints 的四个参数分别是:起始行、起始列、跨行数、对齐方式。行和列从 1 开始计数。跨行数设为 1 表示占一行,如果某个控件需要占两行就设为 2。对齐方式控制控件在单元格内的水平对齐。这套布局系统是 CATIA 自有的,和 MFC 的对话框布局不通用,需要重新适应。
为控件添加回调函数来响应操作。CATDlgPushButton 按钮控件包括 Creation、Visibility swap、Resizement、Button activation 以及 Repetitive button activation 五种回调函数。选择 Button activation 产生点击响应,生成回调函数:
void CAAAfrDlg::OnPushButton004PushBActivateNotification( CATCommand* cmd, CATNotification* evt, CATCommandClientData data) { // 从输入框读取参数 CATUnicodeString radiusStr; _pEditorRadius->GetVisibleText(radiusStr); double radius = atof(radiusStr.ConvertToChar()); // 调用建模逻辑 CreateExtrusion(radius, length, angle); }回调函数的三个参数:cmd 是触发命令的指针,evt 是事件通知,data 是客户端数据。在函数内部通过 GetVisibleText 读取输入框内容,转换成数值后调用建模函数。注意 CATUnicodeString 和 char* 之间的转换需要显式调用 ConvertToChar(),直接赋值会编译报错。
4.3 参数化拉伸体的生成逻辑
资料里的实例是通过输入截面圆的半径、拉伸长度以及拉伸方向与截面法向角度值生成拉伸体,并可以改变其位置。这个流程涉及 CAA 的 PartDesign 接口。常见做法是:先用 CATIPrtContainer 获取当前 Part 容器,再用 CATIPrtFactory 创建草图,在草图上画圆,然后创建拉伸特征。
// 获取当前 Part 文档的容器 CATIPrtContainer* pPartContainer = NULL; pPartContainer = (CATIPrtContainer*)pDoc->GetRootContainer("CATIPrtContainer"); // 创建草图 CATISketch* pSketch = pPartContainer->CreateSketch(); // 在草图上创建圆 CATI2DCurve* pCircle = pSketch->CreateCircle(centerX, centerY, radius); // 创建拉伸特征 CATIPad* pPad = pPartContainer->CreatePad(pSketch, length, direction);这段代码是简化后的逻辑,实际 CAA 接口调用需要处理更多细节,比如草图平面的选择、约束的添加、特征的更新。参数说明:centerX 和 centerY 是圆心坐标,radius 是截面圆半径,length 是拉伸长度,direction 是拉伸方向向量。角度参数用于控制拉伸方向与截面法向的夹角,实现斜拉伸效果。
注意:CAA 的接口调用有严格的顺序要求。必须先创建草图并闭合,才能创建拉伸特征;必须先更新 Part 文档,才能在 CATIA 窗口中看到模型。如果顺序错了,接口会返回 NULL 或者抛出异常,但异常信息往往不明确,这是 CAA 调试最头疼的地方。
5. 五坐标机床运动仿真:从模型装配到刀轨显示
5.1 机床数字化模型的建立方式
资料里的五坐标数控机床运动仿真,新建了一个独立工作台,下面建立两个模块:机床模型建立模块和运动仿真模块。为每个模块建立各自所需的 Toolbar 和对话框以及响应函数。
机床数字化模型的建立有两种方式:利用 CATIA 本身的建模功能,或者利用二次开发自己应用代码生成。资料采用了后者,这样有利于后面对于模型的操作和添加机床行程等约束参数。建模应用了 CATIA 的产品结构模块,机床文件为 *.CATProduct。首先应用 CAA C++ 的 PartDesign 部分建立机床各部分的零件,每个部分都为独立的 *.CATPart 文件。然后应用 CAA C++ 的 3D PLM PPR 组件实现整个机床的装配。
这里的关键决策是:为什么不用 CATIA 交互式建模然后手动装配?因为运动仿真需要遍历产品结构树,找到需要移动的部件并施加变换矩阵。如果模型是手动装配的,结构树里的节点命名和层级关系可能不规范,遍历时容易找错节点。用代码生成模型,可以在创建时就给每个部件赋予明确的名称和属性,后续遍历时按名称查找即可。
5.2 读取 NC 文件与运动矩阵计算
运动仿真的实现流程是:读取 NC 文件,经过处理后提取到移动的方向和坐标值。然后应用 CATIDocRoots 接口找到机床模型的根产品,遍历得到要移动的相应机床部件,得到 CATIMovable 接口实现相应部分的移动,移动矩阵为 CATMathTransformation 类型。
// 获取根产品 CATIDocRoots* pDocRoots = NULL; pDoc->QueryInterface(IID_CATIDocRoots, (void**)&pDocRoots); CATIProduct* pRootProduct = pDocRoots->GetRootProduct(); // 按名称查找部件 CATIProduct* pAxisX = FindProductByName(pRootProduct, "AxisX"); // 获取可移动接口 CATIMovable* pMovable = NULL; pAxisX->QueryInterface(IID_CATIMovable, (void**)&pMovable); // 构建移动矩阵 CATMathTransformation moveMatrix; moveMatrix.SetTranslation(CATMathVector(dx, dy, dz)); // 应用移动 pMovable->SetPosition(moveMatrix);CATMathTransformation 是 CATIA 的数学变换类,支持平移、旋转和复合变换。SetTranslation 设置平移分量,SetRotation 设置旋转分量。五坐标机床的运动通常涉及三个直线轴和两个旋转轴,每个轴的运动都需要单独计算变换矩阵,然后按机床运动链的顺序依次应用。
5.3 刀具轨迹的生成与显示
刀具轨迹的生成和显示用到了 CAA 的 GSM 和 Shape Design & Styling,可以显示直线、圆弧和样条曲线。GSM 是几何造型模块,提供曲线和曲面的创建接口;Shape Design & Styling 提供更高级的造型功能。
// 创建直线段 CATIGSMFactory* pGSMFactory = NULL; pDoc->QueryInterface(IID_CATIGSMFactory, (void**)&pGSMFactory); CATIGSMLine* pLine = pGSMFactory->CreateLine(startPoint, endPoint); // 创建样条曲线 CATIGSMSpline* pSpline = pGSMFactory->CreateSpline(points, pointCount);刀轨显示的关键是坐标系转换:NC 文件里的坐标是机床坐标系下的,需要转换到工件坐标系才能正确显示。资料里提到“独立显示的导轨”,说明刀轨可以脱离机床模型单独显示,这在实际调试时很有用——先确认刀轨本身正确,再叠加到机床上看运动关系。
6. 避坑与排查:CAA 开发中那些让人摔跟头的地方
6.1 编译通过但 CATIA 启动时找不到 workbench
现象:VC++ 编译链接都通过了,生成的 DLL 也放到了指定目录,但启动 CATIA 后开始菜单里找不到新建的 workbench。
原因:CAA 的 workbench 注册依赖 CATRsc 和 CATNls 资源文件的正确编译和部署。常见情况是资源文件没有加入工程,或者资源文件中的标识符和代码中的不一致。
解决:检查 CAA Rade 工程的资源文件列表,确认 CATRsc 和 CATNls 都在编译目标中。然后核对 CATNls 文件里的 workbench 标题和代码中 CreateWorkbench() 返回的容器名称是否一致。最后确认 DLL 输出路径是 CATIA 的 win_b64 或 intel_a 目录下的 code/bin 子目录。
6.2 对话框弹出但控件位置错乱
现象:对话框能弹出,但控件重叠在一起,或者跑到对话框外面去了。
原因:SetGridConstraints 的行列参数设置错误,或者没有为对话框设置网格布局模式。
解决:在对话框的 Build() 函数开头调用 SetGridConstraints 之前,先确认对话框本身启用了网格布局。然后逐个检查每个控件的 SetGridConstraints 调用,确保行号和列号没有冲突。我一般会画一个表格,把每个控件的行列位置标出来,再对照代码检查。
6.3 模型生成后 CATIA 界面不刷新
现象:代码执行完毕,接口返回成功,但 CATIA 窗口里看不到新建的模型。
原因:CAA 的几何特征创建后需要显式调用更新操作,否则只在内存中生成,没有反映到显示层。
解决:在创建完特征后调用 CATIPrtContainer 的 Update() 方法,或者调用 CATIA 的全局刷新接口。常见做法是在每个特征创建后都调用一次 Update,虽然会稍微影响性能,但能保证显示同步。
6.4 遍历产品结构树时找错节点
现象:运动仿真时,该动的部件没动,不该动的部件反而移动了。
原因:按名称查找部件时,名称匹配不精确或者结构树层级理解错误。CATIA 的产品结构树中,同名节点可能出现在不同层级。
解决:不要只用名称匹配,结合节点类型和父节点名称一起判断。我一般会写一个递归遍历函数,打印出每个节点的完整路径和类型,先确认结构树的实际形态,再写查找逻辑。
6.5 VC++ 6.0 在新型操作系统上编译报错
现象:在 Windows 10/11 上安装 VC++ 6.0 后,编译 CAA 工程时报错,提示找不到某些系统头文件或库文件。
原因:VC++ 6.0 是上世纪的产品,对新型操作系统的 SDK 和运行库支持不完整。
解决:在虚拟机中安装 Windows XP 或 Windows 7 32 位系统,专门用于 CAA 开发。如果必须在新型系统上开发,可以尝试安装 VC++ 6.0 的兼容性补丁,但稳定性无法保证。这是环境问题,不是代码问题,不要试图通过改代码解决。
7. 进阶技巧:用 CATNls 做多语言和动态提示
7.1 CATNls 文件的结构与加载机制
CATNls 是 CATIA 的本地化字符串资源文件,格式是“标识符 = 值”的键值对。CAA 在运行时根据当前 CATIA 的语言设置加载对应的 CATNls 文件。如果你的 CATIA 是中文环境,它会优先加载 CATNls 中中文的条目;如果是英文环境,加载英文条目。
这意味着你可以通过维护多份 CATNls 文件来实现多语言支持。常见做法是:CATNls 文件用英文写一份作为默认,然后创建 CATNls.CHN 和 CATNls.JPN 等后缀文件存放中文和日文翻译。CATIA 会根据语言设置自动选择。
7.2 动态修改按钮提示的技巧
按钮的 ShortHelp 和 Title 可以在运行时动态修改。比如你有一个按钮,功能是根据当前选择的对象类型执行不同操作,那么提示文本也应该随之变化。
// 获取按钮的命令头 CATCommandHeader* pHeader = CATCommandHeader::FindHeader("CAAAfrCmd1Hdr"); if (pHeader != NULL) { // 动态修改 ShortHelp CATUnicodeString newHelp = "当前选择:"; newHelp += GetSelectedObjectType(); pHeader->SetShortHelp(newHelp); }这段代码先通过 FindHeader 找到命令头,然后调用 SetShortHelp 修改提示文本。GetSelectedObjectType() 是一个自定义函数,返回当前选中对象的类型名称。这样当用户选中一个圆柱面时,按钮提示会变成“当前选择:圆柱面”,选中一个平面时变成“当前选择:平面”。
7.3 用 CATIA 日志定位接口调用失败
CAA 接口调用失败时,返回值往往是 NULL,但不告诉你为什么。这时候需要打开 CATIA 的日志功能。在 CATIA 启动快捷方式的目标路径后面加上 -log 参数,CATIA 会在临时目录生成日志文件。日志里会记录接口调用的详细过程,包括失败的原因码。
我一般会在代码的关键节点插入日志输出:
#include <CATLib.h> CATLib::Trace("开始创建草图"); CATISketch* pSketch = pPartContainer->CreateSketch(); if (pSketch == NULL) { CATLib::Trace("创建草图失败,错误码:%d", CATLib::GetLastError()); return; } CATLib::Trace("草图创建成功");CATLib::Trace 的输出会写入 CATIA 日志文件。GetLastError() 返回上一个接口调用的错误码。这样当模型生成失败时,翻日志就能定位到是哪一步出的问题,而不是盲目地在代码里加断点。
7.4 一个我踩过的坑
早期做 CAA 开发时,我习惯用 printf 输出调试信息,结果发现 CATIA 运行时根本看不到控制台输出。后来才知道 CAA 的 DLL 是在 CATIA 进程内运行的,标准输出被重定向了。从那以后我每次新建 CAA 工程,第一件事就是配好 CATLib::Trace 的日志输出,并且在 CATIA 快捷方式里加上 -log 参数。这个习惯帮我省了无数个抓瞎的下午。
希望帮到你。
本文还有配套的精品资源,点击获取