乐于分享
好东西不私藏

CATIA CAA 跨文档选择代理使用指南

CATIA CAA 跨文档选择代理使用指南

前面几篇文章里已经讨论过 CATStateCommandCATPathElementAgent 和命令/工作台接入。本文继续往交互命令里推进一小步:如果命令运行在当前活动文档里,但用户需要到另一个已经打开的文档中选择对象,应该怎么做?

这就是 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 内接收选择
可以让用户在其它打开文档中点选参考对象
在装配或多文档环境下引用外部几何
切换活动文档会打断当前命令语义
主命令保持活动,其它文档只承担选择代理
一个命令需要同时收集当前文档和其它文档输入
状态机难以统一管理跨窗口输入
回传后仍然用 IsOutputSetConditionGetValue 等常规方式处理

它的心智模型可以写成一条链:

活动文档中的 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 行为位,例如 CATDlgEngRepeatCATDlgEngWithPSOHSO 等

本地 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 没有 ReturnValueReturnListOfValues 和 _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 状态机。