CATIA CAA 跨文档选择代理使用指南
作者:CATIA开发者指南
发布时间:2026年6月11日 18:36
原文链接:https://mp.weixin.qq.com/s?__biz=MzkxNTQ0MTY0NQ==&mid=2247486521&idx=1&sn=e020373e43e02f885e42876fc08b5bd2&chksm=c15e5ebbf629d7adfc915657b9b53830c7b596af280c55fc07be909a2b31708bcc0a99168242&cur_album_id=2800898825187262468&scene=189#wechat_redirect
前面几篇文章里已经讨论过 CATStateCommand、CATPathElementAgent 和命令/工作台接入。本文继续往交互命令里推进一小步:如果命令运行在当前活动文档里,但用户需要到另一个已经打开的文档中选择对象,应该怎么做?
这就是 CATOtherDocumentAgent 的典型场景。
普通 CATPathElementAgent 负责在当前命令所在的活动编辑器中选择对象。CATOtherDocumentAgent 也是一个选择 agent,但它把“选择动作”代理到其它非活动文档的 editor 中完成,然后把选中的 CATPathElement 回传给活动文档里的 state command。换句话说,主命令仍然在活动文档中推进状态机,真正的鼠标选择可以发生在其它已经打开的 CATIA 文档窗口里。
1. 先理解它解决的问题
在 CAA 交互命令中,最常见的写法是:
CATPathElementAgent *_selectionAgent = newCATPathElementAgent("SelectionId");
_selectionAgent->AddElementType(IID_CATIxxx);
state->AddDialogAgent(_selectionAgent);
这种方式适合选择当前活动文档里的对象。例如在一个 Part 中选择一条线、一张面或一个自定义 feature。问题在于,很多实际业务不是这么干净:
| 场景 | 普通选择 agent 的局限 | CATOtherDocumentAgent 的价值 |
|---|---|---|
| 在当前 Part 中创建对象,但半径/方向参考另一个 Part | 当前命令只在活动 editor 内接收选择 | 可以让用户在其它打开文档中点选参考对象 |
| 在装配或多文档环境下引用外部几何 | 切换活动文档会打断当前命令语义 | 主命令保持活动,其它文档只承担选择代理 |
| 一个命令需要同时收集当前文档和其它文档输入 | 状态机难以统一管理跨窗口输入 | 回传后仍然用 IsOutputSetCondition、GetValue 等常规方式处理 |
它的心智模型可以写成一条链:
活动文档中的 CATStateCommand
-> 状态激活 CATOtherDocumentAgent
-> agent 在其它已打开文档的 editor 中启动选择命令
-> 用户在某个非活动文档中选择对象
-> 选择命令调用 ReturnValue / ReturnListOfValues
-> CATOtherDocumentAgent 被赋值
-> 主命令继续 transition 和 action
注意这里的“其它文档”指的是已经打开、并且有 editor 的非活动文档。CATOtherDocumentAgent 不是文件打开器,也不是后台扫描所有 CATIA 文档的 API。它解决的是交互选择问题。
2. 相关类之间的关系
CATOtherDocumentAgent 属于 DialogEngine framework,头文件是:
#include"CATOtherDocumentAgent.h"
它继承自 CATPathElementAgent。因此主命令拿结果时,仍然按路径元素选择 agent 的方式理解:
CATPathElement *path = _otherDocAgent->GetValue();
真正需要一起理解的是下面三个类:
| 类 | 作用 | 使用方式 |
|---|---|---|
| CATOtherDocumentAgent | 放在活动文档主命令中的 dialog agent | 在 BuildGraph 中创建,并添加到某个 CATDialogState |
| CATMultiDocumentCommand | 在非活动文档 editor 中运行的选择命令基类 | 自定义跨文档选择逻辑时继承它 |
| CATBasicMultiDocumentCommand | Dassault 提供的简单跨文档选择命令 | 只需要一个普通选择 agent 时可以直接使用 |
可以把它们理解成“主控 agent + 外部文档选择命令”。CATOtherDocumentAgent 本身不直接在其它窗口里接管鼠标事件,而是在状态激活时,按构造函数中给出的命令类名和库名,在其它打开文档的 editor 中启动一个命令实例。这个命令必须派生自 CATMultiDocumentCommand。
如果你的需求只是“到其它文档中选一个符合类型的对象”,通常可以先用 CATBasicMultiDocumentCommand。如果你需要更复杂的过滤、状态、提示、多选处理或业务判断,再写自己的 CATMultiDocumentCommand 派生类。
3. 构造函数参数
CATOtherDocumentAgent 的构造函数原型如下:
CATOtherDocumentAgent(
const CATString & iId,
CATClassId iCommandToLaunchInDocuments,
constchar * iLibrary,
CATClassId iType = NULL,
CATDlgEngBehavior iBehavior = NULL);
参数含义可以这样记:
| 参数 | 含义 |
|---|---|
| iId | agent 标识,和普通 dialog agent 一样,也用于消息资源等场景 |
| iCommandToLaunchInDocuments | 要在其它文档 editor 中启动的命令类名 |
| iLibrary | 命令所在共享库名称,不带扩展名,也不带 lib 前缀 |
| iType | 期望选择对象支持的接口类型;为 NULL 时不在构造阶段限制 |
| iBehavior | agent 行为位,例如 CATDlgEngRepeat、CATDlgEngWithPSOHSO 等 |
本地 CAA 示例中使用了最常见的前三个参数:
_daSelectionCircle = newCATOtherDocumentAgent(
"SelectionCircleId",
"CATBasicMultiDocumentCommand",
"CATDialogEngine");
这表示:当这个 agent 所在状态激活时,在其它非活动文档中启动 CATDialogEngine 模块里的 CATBasicMultiDocumentCommand。选择完成后,CATBasicMultiDocumentCommand 会把路径元素回传给 _daSelectionCircle。
因为 CATOtherDocumentAgent 继承自 CATPathElementAgent,所以后续也可以使用选择类型和行为设置:
_daSelectionCircle->AddElementType(IID_CAAISysCircle);
_daSelectionCircle->SetBehavior(
CATDlgEngWithPSOHSO |
CATDlgEngWithPrevaluation |
CATDlgEngRepeat |
CATDlgEngNewHSOManager);
4. 在主 State Command 中使用
下面用一个简化例子说明:主命令在当前文档中选择一条线,同时允许用户在其它文档中选择一个圆,用线作为圆柱轴线,用圆半径作为圆柱半径。
头文件里保存两个 agent:
#include"CATStateCommand.h"
classCATPathElementAgent;
classCATOtherDocumentAgent;
classMyCreateCylinderCmd : public CATStateCommand
{
CmdDeclareResource(MyCreateCylinderCmd, CATStateCommand);
public:
MyCreateCylinderCmd();
virtual ~MyCreateCylinderCmd();
virtualvoidBuildGraph();
private:
CATBoolean CreateCylinder(void *iData);
private:
CATPathElementAgent *_lineAgent;
CATOtherDocumentAgent *_circleInOtherDocAgent;
};
构造和析构保持 CAA 常规写法。agent 在命令析构时延迟销毁:
MyCreateCylinderCmd::MyCreateCylinderCmd()
: CATStateCommand("MyCreateCylinderCmd"),
_lineAgent(NULL),
_circleInOtherDocAgent(NULL)
{
}
MyCreateCylinderCmd::~MyCreateCylinderCmd()
{
if (NULL != _lineAgent)
{
_lineAgent->RequestDelayedDestruction();
_lineAgent = NULL;
}
if (NULL != _circleInOtherDocAgent)
{
_circleInOtherDocAgent->RequestDelayedDestruction();
_circleInOtherDocAgent = NULL;
}
}
BuildGraph 中,一个普通 agent 负责当前文档选择线,一个 CATOtherDocumentAgent 负责其它文档选择圆:
#include"CATPathElementAgent.h"
#include"CATOtherDocumentAgent.h"
#include"CATDialogState.h"
#include"CATDialogTransition.h"
voidMyCreateCylinderCmd::BuildGraph()
{
_lineAgent = newCATPathElementAgent("SelectionLineId");
_lineAgent->AddElementType(IID_CAAISysLine);
_lineAgent->SetBehavior(
CATDlgEngWithPSOHSO |
CATDlgEngWithPrevaluation |
CATDlgEngRepeat |
CATDlgEngNewHSOManager);
_circleInOtherDocAgent = newCATOtherDocumentAgent(
"SelectionCircleId",
"CATBasicMultiDocumentCommand",
"CATDialogEngine");
_circleInOtherDocAgent->AddElementType(IID_CAAISysCircle);
_circleInOtherDocAgent->SetBehavior(
CATDlgEngWithPSOHSO |
CATDlgEngWithPrevaluation |
CATDlgEngRepeat |
CATDlgEngNewHSOManager);
CATDialogState *stCylinder = GetInitialState("stCylinderId");
stCylinder->AddDialogAgent(_lineAgent);
stCylinder->AddDialogAgent(_circleInOtherDocAgent);
AddTransition(
stCylinder,
NULL,
AndCondition(
IsOutputSetCondition(_lineAgent),
IsOutputSetCondition(_circleInOtherDocAgent)),
Action((ActionMethod)&MyCreateCylinderCmd::CreateCylinder));
}
这段代码里最重要的是状态条件:
AndCondition(
IsOutputSetCondition(_lineAgent),
IsOutputSetCondition(_circleInOtherDocAgent))
从主命令角度看,跨文档选择和当前文档选择最终都表现为“agent 有输出”。这就是 CATOtherDocumentAgent 好用的地方:它把跨 editor 的复杂性包起来,留给状态机的仍然是熟悉的 dialog agent 语义。
5. 在 action 中读取选择结果
CATOtherDocumentAgent 被赋值后,读取方式和 CATPathElementAgent 很接近:
CATBoolean MyCreateCylinderCmd::CreateCylinder(void *iData)
{
CATPathElement *circlePath = _circleInOtherDocAgent->GetValue();
if (NULL == circlePath || 0 == circlePath->GetSize())
{
return FALSE;
}
CATBaseUnknown *leaf = (*circlePath)[circlePath->GetSize() - 1];
if (NULL == leaf)
{
return FALSE;
}
CAAISysCircle *circle = NULL;
HRESULT hr = leaf->QueryInterface(IID_CAAISysCircle, (void **)&circle);
if (FAILED(hr) || NULL == circle)
{
return FALSE;
}
float radius = 0.f;
circle->GetRadius(radius);
circle->Release();
circle = NULL;
// 使用 radius 和其它输入创建当前文档中的业务对象。
return TRUE;
}
几点习惯建议:
- 总是检查
GetValue()是否为空,以及 path size 是否大于 0。 - 通常取 path 的最后一个元素作为被选中的叶子对象。
- 通过接口查询拿业务能力,不要假设 leaf 的具体 C++ 实现类。
- 如果跨文档引用会被保存到模型中,要额外考虑链接、生命周期、更新和文档关闭后的失效问题。
最后一点很关键。CATOtherDocumentAgent 只负责交互阶段“选到一个对象”。如果你把这个对象作为跨文档依赖保存下来,那已经进入 document link、external reference、更新机制和数据持久化的范畴,不能只靠 agent 本身兜底。
6. 使用 CATBasicMultiDocumentCommand
如果只是简单选择,可以直接使用 CATBasicMultiDocumentCommand:
_agent = newCATOtherDocumentAgent(
"OtherDocSelectionId",
"CATBasicMultiDocumentCommand",
"CATDialogEngine");
它内部已经是一个 CATMultiDocumentCommand,包含一个状态和一个 CATPathElementAgent。当主命令中的 CATOtherDocumentAgent 激活时,它会在其它非活动文档中作为代理命令运行。选中对象后,它会把结果返回给主 agent。
这种写法适合:
- 单选或普通多选;
- 过滤条件可以通过
CATOtherDocumentAgent的 element type 和 behavior 表达; - 不需要在其它文档中做额外状态机交互;
- 不需要自定义提示、检查逻辑或选择后的预处理。
如果项目初期只是验证跨文档选择流程,建议先用它跑通。等确认需求确实复杂,再写自定义 CATMultiDocumentCommand。这个顺序会少绕不少弯。
7. 自定义跨文档选择命令
如果 CATBasicMultiDocumentCommand 不够用,就写一个派生自 CATMultiDocumentCommand 的命令。这个命令运行在其它文档 editor 中,职责很克制:接收用户选择,然后调用 ReturnValue 或 ReturnListOfValues 把结果交回主命令中的 CATOtherDocumentAgent。
头文件示意:
#include"CATMultiDocumentCommand.h"
#include"CATPathElementAgent.h"
classMyOtherDocSelectionCmd : public CATMultiDocumentCommand
{
CmdDeclareResource(MyOtherDocSelectionCmd, CATMultiDocumentCommand);
public:
MyOtherDocSelectionCmd();
virtual ~MyOtherDocSelectionCmd();
virtualvoidBuildGraph();
private:
CATBoolean SelectionDone(void *iData);
private:
CATPathElementAgent _acquisitionAgent;
};
实现中要注意两件事。第一,命令要能通过类名被创建:
#include"CATCreateExternalObject.h"
CATCreateClass(MyOtherDocSelectionCmd);
第二,选择完成后必须调用回传函数:
MyOtherDocSelectionCmd::MyOtherDocSelectionCmd()
: CATMultiDocumentCommand(),
_acquisitionAgent("MyOtherDocSelectionCmdId")
{
}
voidMyOtherDocSelectionCmd::BuildGraph()
{
if (NULL != _fromCommand)
{
_acquisitionAgent.SetBehavior(_fromCommand->GetBehavior());
}
else
{
_acquisitionAgent.SetBehavior(CATDlgEngWithPSOHSO);
}
_acquisitionAgent.SetElementType("CAAISysCircle");
CATDialogState *state = GetInitialState("SelectInOtherDocId");
state->AddDialogAgent(&_acquisitionAgent);
AddTransition(
state,
NULL,
NULL,
Action((ActionMethod)&MyOtherDocSelectionCmd::SelectionDone));
}
CATBoolean MyOtherDocSelectionCmd::SelectionDone(void *iData)
{
if (_acquisitionAgent.GetBehavior() & CATDlgEngMultiAcquisition)
{
ReturnListOfValues(_acquisitionAgent.GetListOfValues());
}
else
{
ReturnValue(_acquisitionAgent.GetValue());
}
return TRUE;
}
_fromCommand 是 CATMultiDocumentCommand 中保存的指针,指向创建它的 CATOtherDocumentAgent。示例里用 _fromCommand->GetBehavior() 让其它文档中的选择 agent 继承主 agent 的行为设置,这样高亮、多选、重复选择等表现能保持一致。
ReturnValue 和 ReturnListOfValues 是整套机制的收口点:
| 方法 | 用途 |
|---|---|
| ReturnValue(CATPathElement *iValue) | 单选时把路径元素返回给主 agent |
| ReturnListOfValues(CATSO *iList) | 多选时把选择集返回给主 agent |
调用之后,代理命令实例会被自动删除。主命令不需要也不应该手动管理这些在其它 editor 中创建出来的命令实例。
8. 主命令如何引用自定义代理命令
假设上面的 MyOtherDocSelectionCmd 编译在 MyCompanyDialogCmds 模块中,主命令的 agent 可以这样创建:
_otherDocAgent = newCATOtherDocumentAgent(
"OtherDocCircleSelectionId",
"MyOtherDocSelectionCmd",
"MyCompanyDialogCmds",
"CAAISysCircle",
CATDlgEngWithPSOHSO | CATDlgEngWithPrevaluation);
这里有几个容易写错的点:
- 第二个参数是命令类名,不是头文件名,也不是 command header 名。
- 第三个参数是库名,不带
.dll、.so,也不带lib前缀。 - 这个命令类必须能被运行时按类名创建,通常要有
CATCreateClass(MyOtherDocSelectionCmd)。 - 代理命令必须派生自
CATMultiDocumentCommand,不能随便写一个普通CATCommand。 CATOtherDocumentAgent本身文档明确要求“as is”使用,不要从它派生。
9. Imakefile 和依赖
使用 CATOtherDocumentAgent 至少要依赖 CATDialogEngine 模块:
LINK_WITH = CATDialogEngine
实际项目中通常还会依赖选择对象所在的接口模块、Visualization、ApplicationFrame 或自己的业务 framework。例如:
LINK_WITH = CATDialogEngine \
CATVisualization \
CATApplicationFrame \
MyCompanyInterfaces
如果你使用 CATBasicMultiDocumentCommand,它在 CATDialogEngine 中。如果你写自定义 CATMultiDocumentCommand,主命令创建 CATOtherDocumentAgent 时传入的库名必须和该命令实际导出的模块一致。
10. 常见坑
1. 以为它能选择未打开文件中的对象
不能。它面向的是其它已经打开文档的 editor。未打开文件需要先走文档打开、读取或链接相关 API。
2. 只写了主 agent,没有可启动的跨文档命令
CATOtherDocumentAgent 构造函数里必须告诉它要启动哪个命令。最简单可以用 CATBasicMultiDocumentCommand,复杂场景才自定义。
3. 自定义命令继承错基类
其它文档中的代理命令应派生自 CATMultiDocumentCommand。普通 CATStateCommand 没有 ReturnValue、ReturnListOfValues 和 _fromCommand 这套回传机制。
4. 忘记导出命令类
如果代理命令没有 CATCreateClass 或模块名传错,agent 激活时就无法按类名创建代理命令。表现通常是其它文档里没有可用选择流程,主 agent 也不会被赋值。
5. 主 agent 的类型过滤和代理命令里的选择 agent 不一致
例如主 agent 期待 CAAISysCircle,自定义代理命令内部却允许选择 line。最终回传到主命令时,业务代码再 QueryInterface 就会失败。建议主 agent 和代理命令内部 agent 的选择类型保持一致。
6. 没有处理多选
如果 behavior 中启用了 CATDlgEngMultiAcquisition,代理命令要调用 ReturnListOfValues,主命令也要按 list 处理。不要仍然只写 GetValue()。
7. 把交互选择等同于持久化引用
选中另一个文档里的对象,只说明交互阶段拿到了对象路径。如果要把它作为外部引用保存到当前模型,要另行设计链接创建、更新、文档关闭、断链和错误恢复。
11. 推荐使用模板
可以把跨文档选择按下面这套步骤落地:
1. 主命令继承 CATStateCommand
2. 在 BuildGraph 中创建 CATOtherDocumentAgent
3. 构造 agent 时指定要在其它文档中启动的 CATMultiDocumentCommand 类名和库名
4. 设置 AddElementType / SetBehavior
5. 把 agent 添加到 CATDialogState
6. 用 IsOutputSetCondition 判断选择是否完成
7. action 中通过 GetValue 或 GetListOfValues 读取结果
8. 如果需要复杂选择,编写自定义 CATMultiDocumentCommand 并在其中调用 ReturnValue / ReturnListOfValues
最小主命令片段如下:
_otherDocAgent = newCATOtherDocumentAgent(
"OtherDocSelectionId",
"CATBasicMultiDocumentCommand",
"CATDialogEngine");
_otherDocAgent->AddElementType(IID_CATIExpectedInterface);
_otherDocAgent->SetBehavior(CATDlgEngWithPSOHSO | CATDlgEngWithPrevaluation);
CATDialogState *state = GetInitialState("InputStateId");
state->AddDialogAgent(_otherDocAgent);
AddTransition(
state,
NULL,
IsOutputSetCondition(_otherDocAgent),
Action((ActionMethod)&MyCommand::DoSomething));
最小自定义代理命令片段如下:
CATBoolean MyOtherDocSelectionCmd::SelectionDone(void *iData)
{
ReturnValue(_acquisitionAgent.GetValue());
return TRUE;
}
12. 总结
CATOtherDocumentAgent 可以理解为 CATPathElementAgent 的跨文档版本,但这个说法只对了一半。它真正的机制是:主命令中的 agent 在其它非活动文档 editor 中启动一个 CATMultiDocumentCommand 派生命令,由该命令完成选择,再通过 ReturnValue 或 ReturnListOfValues 把选择结果回传。
所以使用它时要抓住三个核心:
- 主命令里放
CATOtherDocumentAgent,它仍然参与普通 state command 的状态条件和 action。 - 其它文档里运行
CATMultiDocumentCommand派生命令,简单情况直接用CATBasicMultiDocumentCommand。 - 回传结果后,主命令按
CATPathElement或CATSO处理选择输出,真正的跨文档引用和持久化要另行设计。
这套机制适合多文档交互式建模命令:当前文档负责创建或修改目标对象,其它已打开文档提供参考输入。它不会替你设计外部引用体系,但能把“用户怎么在另一个窗口点到对象”这件事优雅地接入 CATStateCommand 状态机。
评论