本文讨论一个 CAA 开发中很常用的部分:如何把自己的功能接入 CATIA V5 的交互界面,让用户可以在工作台中点到命令、选择对象、输入参数、确认创建,并且在菜单、工具栏、资源文件和运行时加载机制之间保持一致。
如果说自定义特征回答的是“我要在模型里创造什么对象”,那么命令和工作台接入回答的是“用户如何在 CATIA 里自然地使用它”。在实际项目中,这两部分经常绑定出现:一个新几何特征需要创建命令,一个分析工具需要选择命令,一个批处理能力也可能需要放到工具栏或菜单中。
1. 先建立三层心智模型
CAA 里的一个可点击功能通常由三层组成:
| 层次 | 典型类/对象 | 作用 |
|---|---|---|
| 命令实现 | CATCommand、 CATStateCommand、CATDlgDialog 派生类 |
真正执行业务逻辑:选择对象、弹出面板、创建特征、修改文档。 |
| 命令头 | CATCommandHeader派生类或 MacDeclareHeader 生成类 |
把一个菜单项/按钮和某个 DLL 中的某个命令类绑定起来,同时关联标题、图标和帮助资源。 |
| 入口布局 | CATCmdWorkbench、 CATCmdContainer、CATCmdStarter |
描述命令出现在什么工作台、工具栏、菜单、子菜单或分隔符之后。 |
很多初学者会把“命令类”和“工具栏按钮”混在一起看。更稳妥的理解是:按钮本身不是命令,按钮是一个 CATCmdStarter;CATCmdStarter 指向命令头;命令头知道要加载哪个库、实例化哪个命令类;命令类才真正运行。
典型调用链如下:
用户点击工具栏按钮
-> CATCmdStarter
-> CATCommandHeader
-> 加载命令所在模块
-> 实例化 CATCommand/CATStateCommand 派生类
-> 命令开始接收交互事件并修改文档
2. 选择命令类型
CAA 文档把用户可见的命令大致分为三类。选择哪一类,取决于用户是否需要继续交互。
| 场景 | 推荐命令类型 | 说明 |
|---|---|---|
| 点击后立即执行 | CATCommand派生类 |
例如 Update All、切换显示状态、执行一次检查。 |
| 需要固定面板输入参数 | CATDlgDialog或对话框命令 |
例如设置选项、输入若干参数后 OK/Apply。 |
| 需要选择对象、按步骤输入、反复预览 | CATStateCommand派生类 |
例如选择曲线、选择方向、输入半径、确认创建特征。 |
在机械/几何 CAA 开发中,最常见也最值得掌握的是 CATStateCommand。它把交互过程建模为状态机:每个状态收集一类输入,输入满足条件后通过 transition 进入下一个状态,并执行 action。
例如一个“创建测量特征”的命令可以设计为:
初始状态:选择被测几何
-> 选择有效后保存输入
第二状态:选择测量模式或输入参数
-> 参数有效后预览或创建
结束状态:调用 factory 创建特征并插入模型
3. 写一个 State Command 骨架
一个 state command 需要继承 CATStateCommand,声明资源文件,覆盖 BuildGraph,并根据需要实现生命周期函数。
头文件骨架可以类似这样:
#include"CATStateCommand.h"
#include"CATBoolean.h"
classCATPathElementAgent;
classCATDialogAgent;
classCATNotification;
class MyCompanyCreateMeasureCmd : public CATStateCommand
{
CmdDeclareResource(MyCompanyCreateMeasureCmd, CATStateCommand);
public:
MyCompanyCreateMeasureCmd();
virtual ~MyCompanyCreateMeasureCmd();
virtualvoidBuildGraph();
CATStatusChangeRC Activate(CATCommand *cmd, CATNotification *notif);
CATStatusChangeRC Desactivate(CATCommand *cmd, CATNotification *notif);
CATStatusChangeRC Cancel(CATCommand *cmd, CATNotification *notif);
private:
CATBoolean CheckSelectedObject(void *data);
CATBoolean StoreSelectedObject(void *data);
CATBoolean CreateMeasureFeature(void *data);
private:
CATPathElementAgent *_selectionAgent;
CATDialogAgent *_okAgent;
};
实现类通常是一个普通 CAA implementation class:
#include"MyCompanyCreateMeasureCmd.h"
#include"CATPathElementAgent.h"
#include"CATDialogState.h"
CATImplementClass(
MyCompanyCreateMeasureCmd,
Implementation,
CATStateCommand,
CATNull);
MyCompanyCreateMeasureCmd::MyCompanyCreateMeasureCmd()
: CATStateCommand("MyCompanyCreateMeasureCmd"),
_selectionAgent(NULL),
_okAgent(NULL)
{
}
MyCompanyCreateMeasureCmd::~MyCompanyCreateMeasureCmd()
{
_selectionAgent = NULL;
_okAgent = NULL;
}
BuildGraph 是 state command 的核心。它创建状态、创建 dialog agent,并把状态之间的条件和动作串起来。
void MyCompanyCreateMeasureCmd::BuildGraph()
{
CATDialogState *selectState = GetInitialState("SelectInputState");
_selectionAgent = newCATPathElementAgent("SelectInputAgent");
selectState->AddDialogAgent(_selectionAgent);
AddTransition(
selectState,
NULL,
IsOutputSetCondition(_selectionAgent),
Action((ActionMethod)&MyCompanyCreateMeasureCmd::StoreSelectedObject));
}
实际项目会比这个例子多几件事:
-
给
CATPathElementAgent设置过滤条件,只接受目标接口、目标 StartUp 或目标几何类型。 -
从 agent 的输出中取得
CATPathElement,再沿路径找到模型对象或CATBaseUnknown。 -
在 action 中只保存选择结果,不要把所有业务逻辑塞进 guard condition。
-
在命令结束前统一调用 factory 或服务类修改文档。
-
对创建、替换、删除这类文档修改考虑 undo/redo 和 warm start。
4. 命令生命周期和运行模式
CAA 命令运行在 command tree 中,同一时刻只有一个面向用户的 dialog command 拿到 focus。命令启动模式会影响它和已有命令的关系:
| 模式 | 常量 | 适用场景 |
|---|---|---|
| Exclusive | CATCommandModeExclusive |
创建、编辑、选择这类主命令。启动时会取消当前命令栈中的其他命令。 |
| Shared | CATCommandModeShared |
可以和已有命令短暂共存的辅助命令。 |
| Undefined | CATCommandModeUndefined |
不由 command selector 管理的内部命令;state command 不适合设为 undefined。 |
常见生命周期函数含义如下:
| 函数 | 调用时机 | 建议处理 |
|---|---|---|
| 构造函数 | 命令实例化时 | 初始化成员、设置命令模式、准备轻量状态。 |
BuildGraph |
state command 构建状态图时 | 创建 state、agent、transition、condition、action。 |
Activate |
命令取得 focus 时 | 刷新提示、恢复临时显示、激活面板。 |
Desactivate |
shared 命令抢占 focus 时 | 暂停高亮、临时预览或面板响应。 |
Cancel |
命令结束或被 exclusive 命令取消时 | 清理临时几何、释放选择状态、关闭面板。 |
如果命令会修改 CATPart 或 CATProduct,不要只关心“点 OK 后成功”。还要验证这些情况:取消命令是否清理预览,切换工作台是否不会残留 agent,选择另一个 exclusive 命令时是否能安全退出,撤销/重做后模型是否一致。
5. 用 Command Header 让命令可被启动
命令类写好后,还不能被工具栏直接调用。你需要在工作台或 add-in 的 CreateCommands 中创建 command header。
官方示例常用 MacDeclareHeader 快速生成命令头类:
#include"CATCommandHeader.h"
MacDeclareHeader(MyCompanyWorkbenchHeader);
然后在 CreateCommands 中为每个命令实例化一个 header:
void MyCompanyWorkbench::CreateCommands()
{
newMyCompanyWorkbenchHeader(
"MyCompanyCreateMeasureHdr",
"MyCompanyCommands",
"MyCompanyCreateMeasureCmd",
(void *)NULL);
}
四个参数分别是:
| 参数 | 含义 |
|---|---|
MyCompanyCreateMeasureHdr |
command header 实例 ID。后续 SetAccessCommand 和资源 key 都要用它。 |
MyCompanyCommands |
命令实现所在共享库/DLL 名,不写 lib 前缀,也不写平台后缀。 |
MyCompanyCreateMeasureCmd |
要实例化的命令类名。 |
(void *)NULL |
可选启动参数;同一个命令类服务多个动作时可传入动作 ID。 |
如果需要传整数参数,在 64 位环境中不要直接强转,应使用文档示例中的 CATINT32ToPtr 这类宏,避免指针宽度问题。
6. 把命令放进 Workbench
如果你拥有目标 workbench 的源代码,或者要创建一个完全独立的工作台,可以在 workbench 描述类中实现两件事:
-
CreateCommands:创建 command headers。
-
CreateWorkbench:创建工作台、工具栏、菜单和命令入口。
一个简化的工具栏布局如下:
CATCmdWorkbench *MyCompanyWorkbench::CreateWorkbench()
{
NewAccess(CATCmdWorkbench, workbench, MyCompanyWorkbench);
NewAccess(CATCmdContainer, measureToolbar, MyCompanyMeasureTlb);
SetAccessChild(workbench, measureToolbar);
AddToolbarView(measureToolbar, 1, Right);
NewAccess(CATCmdStarter, createMeasureStarter, MyCompanyCreateMeasureStr);
SetAccessCommand(createMeasureStarter, "MyCompanyCreateMeasureHdr");
SetAccessChild(measureToolbar, createMeasureStarter);
return workbench;
}
几个标识符要分清:
| 标识符 | 示例 | 用在哪里 |
|---|---|---|
| Workbench ID | MyCompanyWorkbench |
NewAccess(CATCmdWorkbench, ...),也是 workbench 资源文件名基础。 |
| Toolbar ID | MyCompanyMeasureTlb |
工具栏资源、默认显示位置、用户自定义布局。 |
| Starter ID | MyCompanyCreateMeasureStr |
工具栏/菜单中的具体入口资源。 |
| Header ID | MyCompanyCreateMeasureHdr |
SetAccessCommand和 command header 资源。 |
菜单和子菜单也使用 CATCmdContainer,菜单项仍然使用 CATCmdStarter。工具栏和菜单可以指向同一个 command header,因此同一个命令可以同时出现在 toolbar 和 menu 中,而不需要写两份命令类。
7. 用 Add-in 接入已有 Workbench
如果你不想修改已有 workbench,通常应使用 add-in。Add-in 的好处是解耦:目标 workbench 暴露一个 add-in interface,你实现这个接口,把自己的工具栏和命令注册进去。
一个 add-in 描述类通常像这样:
#include"CATBaseUnknown.h"
classCATCmdContainer;
classMyCompanyMeasureAddin : public CATBaseUnknown
{
CATDeclareClass;
public:
MyCompanyMeasureAddin();
virtual ~MyCompanyMeasureAddin();
voidCreateCommands();
CATCmdContainer *CreateToolbars();
};
实现时它是某个 late type 的 DataExtension,并通过 TIE 声明实现目标 workbench 暴露的 add-in interface:
#include"MyCompanyMeasureAddin.h"
#include"CATCommandHeader.h"
#include"CATCreateWorkshop.h"
MacDeclareHeader(MyCompanyMeasureAddinHeader);
CATImplementClass(
MyCompanyMeasureAddin,
DataExtension,
CATBaseUnknown,
MyCompanyMeasureAddinComponent);
#include"TIE_MyTargetWorkbenchAddin.h"
TIE_MyTargetWorkbenchAddin(MyCompanyMeasureAddin);
dictionary 中要声明 component late type 实现了 add-in interface:
MyCompanyMeasureAddinComponent MyTargetWorkbenchAddin libMyCompanyMeasureAddin
CreateToolbars 中创建 toolbar、starter,并用 SetAccessCommand 指向 command header:
CATCmdContainer *MyCompanyMeasureAddin::CreateToolbars()
{
NewAccess(CATCmdContainer, measureToolbar, MyCompanyMeasureAddinTlb);
NewAccess(CATCmdStarter, createMeasureStarter, MyCompanyCreateMeasureStr);
SetAccessCommand(createMeasureStarter, "MyCompanyCreateMeasureHdr");
SetAccessChild(measureToolbar, createMeasureStarter);
AddToolbarView(measureToolbar, -1, Right);
return measureToolbar;
}
注意 add-in 的能力边界:它适合增加工具栏和有限的菜单入口,但不应该假设能任意重排目标 workbench 的菜单结构。对于已有菜单,通常只能按目标框架允许的方式追加入口。
8. 准备 CATNls、CATRsc 和图标
命令能启动只是第一步。要让用户看到正常标题、提示和图标,还要提供资源文件。
8.1 Command Header 资源
如果 header 类名为 MyCompanyWorkbenchHeader,则资源文件通常是:
MyCompanyWorkbenchHeader.CATNls
MyCompanyWorkbenchHeader.CATRsc
放置在:
YourFramework/CNext/resources/msgcatalog
CATNls 中常见 key 由“header 类名 + header 实例 ID + 资源项”拼成:
MyCompanyWorkbenchHeader.MyCompanyCreateMeasureHdr.Title = "Measure Feature";
MyCompanyWorkbenchHeader.MyCompanyCreateMeasureHdr.ShortHelp = "Measure Feature";
MyCompanyWorkbenchHeader.MyCompanyCreateMeasureHdr.Help = "Create a persistent measure feature";
MyCompanyWorkbenchHeader.MyCompanyCreateMeasureHdr.Category = "MyCompany";
CATRsc 中常用于指定图标、快捷键、助记键等非翻译资源。图标文件通常进入 CNext/resources/graphic,并随运行时视图复制到对应 OS 目录。
8.2 State Command 资源
CATStateCommand 自己也可以有资源文件,例如:
MyCompanyCreateMeasureCmd.CATNls
它用于命令状态提示、undo prompt 或面板文案。因为命令类中写了:
CmdDeclareResource(MyCompanyCreateMeasureCmd, CATStateCommand);
所以运行时会按命令类名查找资源。状态 ID 也应保持稳定,方便 NLS 资源和代码对应。
8.3 Workbench/Add-in 资源
Workbench、toolbar、menu container 也需要自己的资源。比如 MyCompanyWorkbench 对应:
MyCompanyWorkbench.CATNls
MyCompanyWorkbench.CATRsc
Add-in 通常也会有自己的 .CATNls,用于工具栏标题等。
资源相关路径常见如下:
| 类型 | 开发态路径 | 运行时环境变量 |
|---|---|---|
.CATNls、 .CATRsc |
CNext/resources/msgcatalog |
CATMsgCatalogPath |
图标、.CATfct 等 graphic 资源 |
CNext/resources/graphic |
CATGraphicPath |
| interface dictionary | CNext/code/dictionary |
CATDictionaryPath |
9. 把命令和自定义特征串起来
如果这篇和“自定义特征”一起看,一个完整功能通常会这样拆分:
| 模块 | 职责 |
|---|---|
StartUp .CATfct |
定义持久化对象、输入、输出和属性。 |
| 类型接口 DataExtension | 封装 StartUp 属性访问。 |
| Factory DataExtension | 在 CATPrtCont 或目标容器中实例化特征。 |
| Build/Replace/Edit 行为 | 让特征参与 update、替换、编辑和规格树行为。 |
| State Command | 负责用户选择、参数面板、预览、确认/取消。 |
| Workbench/Add-in | 负责把命令放到 CATIA UI 中。 |
| 资源文件 | 负责标题、帮助、图标、提示和本地化。 |
创建命令中不要直接手写 StartUp 属性字符串,也不要在 UI 代码中散落模型细节。更清晰的做法是:
-
command 只处理交互和校验。
-
factory 负责创建实例。
-
类型接口负责写入输入和参数。
-
插入服务或命令收尾逻辑负责放入当前 Body、OGS、Geometrical Set 或 MechanicalSet。
-
update 交给 CATIA 更新机制或明确调用对应服务。
这样做的好处是:同一个 factory 可被命令、脚本、批处理或测试复用;同一个命令也可以在创建模式和编辑模式之间共享大部分逻辑。
10. 构建和运行验证闭环
一个最小验证流程如下:
-
在 CAA workspace 中创建 command module 和 add-in/workbench module。
-
编写命令类,确认
CATImplementClass、头文件导出和 Imakefile/mj 依赖正确。 -
在 workbench 或 add-in 的
CreateCommands中实例化 command header。 -
在
CreateWorkbench或CreateToolbars中创建CATCmdStarter,并用SetAccessCommand指向 header ID。 -
更新 interface dictionary,确认 late type、interface、library 三列都正确。
-
放置
.CATNls、.CATRsc、图标,并刷新运行时视图。 -
启动
CNEXT,进入目标文档和工作台,确认工具栏/菜单出现。 -
点击命令,验证选择过滤、状态提示、OK/Cancel、预览清理、撤销/重做。
-
保存文档、关闭、重开,确认命令创建的对象和更新行为正常。
调试时可以先把命令做成最小 one-shot:点击后弹出消息或打印 trace,确认 header、library、class name 和资源链路没问题;再逐步加入 state graph 和业务逻辑。
11. 常见坑
| 问题 | 典型原因 | 处理建议 |
|---|---|---|
| 工具栏按钮不显示 | CATCmdStarter没有 SetAccessCommand,或 header ID 不匹配 |
检查 CreateToolbars/CreateWorkbench 中 starter 和 header 的字符串。 |
| 点击按钮没反应 | header 指向的 library 名或 command class 名错误 | library 名不要写 lib 前缀和平台后缀,class 名要和 CATImplementClass 的实现类一致。 |
| 进入工作台后 add-in 不出现 | dictionary 未声明 add-in interface,或 late type 名冲突 | 检查 .dico、CATImplementClass 第四参、TIE 宏和目标 workbench 暴露的 add-in interface。 |
| 标题显示内部 ID | 缺少 command header .CATNls 或 key 拼错 |
按“HeaderClass.HeaderId.Title”格式检查资源 key。 |
| 图标不显示 | .CATRsc、图标名或 resources/graphic 路径错误 |
确认图标进入运行时视图,并检查 CATGraphicPath。 |
| 选择不受限制 | CATPathElementAgent未设置过滤 |
给 agent 增加接口过滤、类型过滤或自定义 guard condition。 |
| 取消命令后残留高亮/临时几何 | Cancel/ Desactivate 没有清理临时对象 |
所有 preview、临时 body、agent 输出都应有集中清理路径。 |
| 命令创建对象但 undo 异常 | 直接修改模型,未纳入命令/文档修改机制 | 对文档修改命令检查 undo/redo、warm start 和事务边界。 |
| 64 位下启动参数异常 | 把整数直接强转成 void * |
使用 CATINT32ToPtr 等兼容宏,或传稳定对象/字符串。 |
| 修改资源后无变化 | 运行时视图未刷新,或 CATIA 使用缓存 | 刷新 RTV,重启 CNEXT,确认资源文件在 OS 目录下。 |
12. 推荐阅读的本地 CAADoc 页面
以下路径均来自当前安装库:
-
CAADoc/Doc/online/CAADegTechArticles/CAADegCommandModel.htm:CAA command model、命令类型、command tree、focus 和运行模式。
-
CAADoc/Doc/online/CAADegTechArticles/CAADegCreatingCommand.htm:创建
CATStateCommand的基础步骤、BuildGraph、agent、condition 和 action。 -
CAADoc/Doc/online/CAAAfrTechArticles/CAAAfrIntegratingCommand.htm:命令如何接入 workbench、add-in 和 warm start。
-
CAADoc/Doc/online/CAAAfrTechArticles/CAAAfrCommandHeaders.htm:command header 的职责、资源 key 和命令加载关系。
-
CAADoc/Doc/online/CAAAfrUseCases/CAAAfrSampleStdCommandHeader.htm:使用
MacDeclareHeader创建标准 command header 的示例。 -
CAADoc/Doc/online/CAAAfrUseCases/CAAAfrSampleWorkbench.htm:创建 workbench、toolbar、menu、command starter 的完整示例。
-
CAADoc/Doc/online/CAAAfrUseCases/CAAAfrSampleAddin.htm:创建 workbench add-in,并通过
CreateCommands/CreateToolbars接入命令。 -
CAADoc/Doc/online/CAAAfrUseCases/CAAAfrSampleGeneralWksAddin.htm:接入 general workshop 的 add-in 示例。
-
CAADoc/Doc/online/CAAAfrTechArticles/CAAAfrI18NHeader.htm:command header 的
.CATNls/.CATRsc资源写法。 -
CAADoc/Doc/online/CAAAfrTechArticles/CAAAfrI18NWorkshop.htm:workshop/workbench、toolbar 和 menu container 的资源写法。
评论