← 返回首页

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

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


前面几篇文章里已经讨论过 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 内接收选择 可以让用户在其它打开文档中点选参考对象
在装配或多文档环境下引用外部几何 切换活动文档会打断当前命令语义 主命令保持活动,其它文档只承担选择代理
一个命令需要同时收集当前文档和其它文档输入 状态机难以统一管理跨窗口输入 回传后仍然用 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 中,职责很克制:接收用户选择,然后调用 ReturnValueReturnListOfValues 把结果交回主命令中的 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;
}

_fromCommandCATMultiDocumentCommand 中保存的指针,指向创建它的 CATOtherDocumentAgent。示例里用 _fromCommand->GetBehavior() 让其它文档中的选择 agent 继承主 agent 的行为设置,这样高亮、多选、重复选择等表现能保持一致。

ReturnValueReturnListOfValues 是整套机制的收口点:

方法 用途
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. 主命令继承 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 派生命令,由该命令完成选择,再通过 ReturnValueReturnListOfValues 把选择结果回传。

所以使用它时要抓住三个核心:

  • 主命令里放 CATOtherDocumentAgent,它仍然参与普通 state command 的状态条件和 action。
  • 其它文档里运行 CATMultiDocumentCommand 派生命令,简单情况直接用 CATBasicMultiDocumentCommand
  • 回传结果后,主命令按 CATPathElementCATSO 处理选择输出,真正的跨文档引用和持久化要另行设计。

这套机制适合多文档交互式建模命令:当前文档负责创建或修改目标对象,其它已打开文档提供参考输入。它不会替你设计外部引用体系,但能把“用户怎么在另一个窗口点到对象”这件事优雅地接入 CATStateCommand 状态机。

评论