CATIA CAA 跨文档选择代理使用指南
前面几篇文章里已经讨论过 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。问题在于,很多实际业务不是这么干净:
|
|
|
CATOtherDocumentAgent
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
BuildGraph 中创建,并添加到某个 CATDialogState |
CATMultiDocumentCommand |
|
|
CATBasicMultiDocumentCommand |
|
|
可以把它们理解成“主控 agent + 外部文档选择命令”。CATOtherDocumentAgent 本身不直接在其它窗口里接管鼠标事件,而是在状态激活时,按构造函数中给出的命令类名和库名,在其它打开文档的 editor 中启动一个命令实例。这个命令必须派生自 CATMultiDocumentCommand。
如果你的需求只是“到其它文档中选一个符合类型的对象”,通常可以先用 CATBasicMultiDocumentCommand。如果你需要更复杂的过滤、状态、提示、多选处理或业务判断,再写自己的 CATMultiDocumentCommand 派生类。
3. 构造函数参数
CATOtherDocumentAgent 的构造函数原型如下:
CATOtherDocumentAgent(const CATString & iId, CATClassId iCommandToLaunchInDocuments,constchar * iLibrary, CATClassId iType = NULL, CATDlgEngBehavior iBehavior = NULL);
参数含义可以这样记:
|
|
|
|---|---|
iId |
|
iCommandToLaunchInDocuments |
|
iLibrary |
lib 前缀 |
iType |
NULL 时不在构造阶段限制 |
iBehavior |
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) |
|
ReturnListOfValues(CATSO *iList) |
|
调用之后,代理命令实例会被自动删除。主命令不需要也不应该手动管理这些在其它 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. 主命令继承 CATStateCommand2. 在 BuildGraph 中创建 CATOtherDocumentAgent3. 构造 agent 时指定要在其它文档中启动的 CATMultiDocumentCommand 类名和库名4. 设置 AddElementType / SetBehavior5. 把 agent 添加到 CATDialogState6. 用 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 状态机。
夜雨聆风