← 返回首页

CATIA CAA 对象选择代理使用指南

CATIA CAA 对象选择代理使用指南

作者:CATIA开发者指南
发布时间:2026年6月9日 10:49
原文链接:https://mp.weixin.qq.com/s?__biz=MzkxNTQ0MTY0NQ==&mid=2247486520&idx=1&sn=4c9787418334de6fc687ef300f33825d&chksm=c15e5ebaf629d7acbc18a6a2a98398f11dc489995c1fc104d6cd1e5da8a6441ba99341416071&cur_album_id=2800898825187262468&scene=189#wechat_redirect


前面几篇文章里已经讲过 CATStateCommand 状态机、常用 dialog agent 选型,以及 CATOtherDocumentAgentCATFeatureAgent 这类专用选择代理。本文回到最常用、也最应该先掌握的选择代理:CATPathElementAgent

图片

如果一个 CAA 交互命令要让用户在 3D/2D viewer 或规格树中选择一个已有对象,第一反应通常就是它。

可以先用一句话建立心智模型:

CATPathElementAgent 不是直接返回“鼠标点到的 C++ 对象”,而是返回一条 CATPathElement 选择路径。

这条路径可能是完整路径,也可能因为类型过滤被截断到某个祖先对象。理解“路径”和“截断”,比记住构造函数更重要。

1. 它解决什么问题

CATPathElementAgent 属于 DialogEngine framework,继承自 CATAcquisitionAgent,是 CAA 状态命令中最常见的对象选择 agent。

它负责把下面这些用户交互变成状态机可读取的输出:

  • 在 3D viewer 中点击一个对象;
  • 在 2D viewer 中点击一个对象;
  • 在规格树中选择一个对象;
  • 鼠标悬停时产生预选路径;
  • 框选或多选得到一组路径;
  • 从被点对象的路径中找到第一个符合接口类型的对象。

典型场景:

场景 说明
选一个点、线、面、平面、草图、feature 最常见用法
从规格树选业务对象 不一定必须是几何显示对象
命令需要多个步骤分别选对象 每个状态放一个或多个 agent
需要支持预选高亮 设置 CATDlgEngWithPSOHSO 或 CATDlgEngWithPrevaluation
需要多选、框选、Ctrl/Shift 累加选择 设置 multi acquisition 行为
需要从 Point 找到它所属的 Open Body 利用路径截断和类型过滤

不适合它的场景:

场景 更合适的 agent
点空白位置拿坐标 CATIndicationAgent
只接收对话框按钮通知 CATDialogAgent
机械上下文中处理 BRep featurization CATFeatureAgent
选择外部 Part 并导入 External Reference CATFeatureImportAgent
到其它已打开文档窗口中代理选择 CATOtherDocumentAgent

2. 先理解 CATPathElement

CATPathElementAgent 的输出是 CATPathElement。它不是单个对象指针,而是一条从 root 到 leaf 的路径。

假设一个 Product 中包含 Part,Part 中有 Open Body,Open Body 下有 Point:

Product.1 / Part.1 / Open_body.2 / Point.3

用户在 3D 或规格树里点到 Point.3,鼠标下的 leaf 是 Point.3。但你的命令不一定想要 Point,也可能想要它所属的 Open_body.2,或者更上层的某个 feature。CATPathElementAgent 可以根据类型过滤,把返回路径截断到第一个满足接口的对象:

不设类型过滤:
Product.1 / Part.1 / Open_body.2 / Point.3

要求 Open_body 实现的某个接口:
Product.1 / Part.1 / Open_body.2

这就是 CATPathElementAgent 和“直接拿鼠标对象”的区别。它选择的是路径,真正返回哪一段路径取决于类型过滤和行为设置。

读取 leaf 的常见写法:

CATPathElement *path = _selectionAgent->GetValue();
if (NULL != path && 0 != path->GetSize())
{
  CATBaseUnknown *leaf = (*path)[path->GetSize() - 1];
}

如果已经设置类型过滤,通常更直接用:

CATBaseUnknown *selected = _selectionAgent->GetElementValue();

但要注意:GetElementValue() 只适合单选。多选行为下它会返回 NULL,应该改用 GetListOfValues()

3. 最小使用模板

头文件中保存 agent 指针:

#include"CATStateCommand.h"

classCATPathElementAgent;

classMySelectObjectCmd : public CATStateCommand
{
CmdDeclareResource(MySelectObjectCmd, CATStateCommand);

public:
MySelectObjectCmd();
virtual ~MySelectObjectCmd();
virtualvoidBuildGraph();

private:
CATBoolean OnObjectSelected(void *iData);

private:
  CATPathElementAgent *_selectionAgent;
};

构造、析构和状态图。下面的 IID_CATIExpectedInterface 是占位符,真实项目中替换成你的接口 IID:

#include"CATPathElementAgent.h"
#include"CATDialogState.h"
#include"CATDialogTransition.h"
#include"CATDlgEngUtility.h"

MySelectObjectCmd::MySelectObjectCmd()
  : CATStateCommand("MySelectObjectCmd"),
    _selectionAgent(NULL)
{
}

MySelectObjectCmd::~MySelectObjectCmd()
{
if (NULL != _selectionAgent)
  {
    _selectionAgent->RequestDelayedDestruction();
    _selectionAgent = NULL;
  }
}

voidMySelectObjectCmd::BuildGraph()
{
  _selectionAgent = newCATPathElementAgent("SelectionId");
  _selectionAgent->AddElementType(IID_CATIExpectedInterface);
  _selectionAgent->SetBehavior(
    CATDlgEngWithPSOHSO |
    CATDlgEngRepeat |
    CATDlgEngNewHSOManager);

  CATDialogState *stSelect = GetInitialState("SelectObjectStateId");
  stSelect->AddDialogAgent(_selectionAgent);

AddTransition(
    stSelect,
    stSelect,
IsOutputSetCondition(_selectionAgent),
Action((ActionMethod)&MySelectObjectCmd::OnObjectSelected));
}

读取结果:

CATBoolean MySelectObjectCmd::OnObjectSelected(void *iData)
{
  CATPathElement *path = _selectionAgent->GetValue();
if (NULL == path)
  {
return FALSE;
  }

  CATBaseUnknown *selected = _selectionAgent->GetElementValue();
if (NULL == selected)
  {
return FALSE;
  }

// Query your expected interface and run business logic here.

  _selectionAgent->InitializeAcquisition();
return TRUE;
}

如果只需要选一次,transition 的目标状态可以是 NULL 或后续状态;如果需要连续选多个对象,上面这种回到同一状态并 InitializeAcquisition() 的写法更常见。

4. 构造函数参数

构造函数原型如下:

CATPathElementAgent(
const CATString &iId,
  CATClassId       iType = NULL,
  CATDlgEngBehavior iBehavior = NULL);

参数含义:

参数 含义
iId agent 标识,也用于资源文件中的提示、undo/redo 标题等
iType 期望对象实现的接口类型 class id;为 NULL 时接受所有对象
iBehavior agent 行为位,用 `

构造时直接指定类型:

_selectionAgent = newCATPathElementAgent(
"SelectionId",
"CATIExpectedInterface",
  CATDlgEngWithPSOHSO);

也可以构造后再添加类型:

_selectionAgent = newCATPathElementAgent("SelectionId");
_selectionAgent->AddElementType(IID_CATIExpectedInterface);

实际项目中更常见的是后者,因为类型和行为经常要按命令模式动态调整。

5. 类型过滤:AddElementType

CATPathElementAgent 的过滤基于接口,不是 C++ 类名。对象只要实现指定接口,就可以被接受。

最常见写法:

_selectionAgent->AddElementType(IID_CATIExpectedInterface);

可以多次调用:

_selectionAgent->AddElementType(IID_CATITypeA);
_selectionAgent->AddElementType(IID_CATITypeB);

这表示对象实现其中任意一个接口即可。

非有序类型列表的查找规则是:

先按路径位置,从 leaf 往 root 找;
对路径上的每个对象,检查它是否实现任一指定接口;
找到第一个匹配对象后,返回从 root 到该对象的截断路径。

例如:

Product.1 / Part.1 / Open_body.2 / Point.3

如果 Point.3 实现 CATITypeAOpen_body.2 实现 CATITypeB,并且你这样写:

_selectionAgent->AddElementType(IID_CATITypeB);
_selectionAgent->AddElementType(IID_CATITypeA);

返回路径仍然会优先停在 Point.3,因为非有序列表更看重对象在路径中的位置,而不是你添加接口的顺序。

这一点很容易误会:AddElementType 的调用顺序不是优先级。

6. 类型过滤:SetOrderedTypeList

如果你确实希望接口顺序优先,就用 SetOrderedTypeList

CATListOfCATString orderedTypes;
orderedTypes.Append("CATITypeB");
orderedTypes.Append("CATITypeA");

_selectionAgent->SetOrderedTypeList(orderedTypes);

有序类型列表的查找规则是:

先按接口顺序;
对每个接口,再从 leaf 往 root 检查路径对象;
找到第一个实现当前接口的对象后停止。

沿用前面的例子:

Product.1 / Part.1 / Open_body.2 / Point.3

如果 ordered list 是:

CATITypeB, CATITypeA

并且 Open_body.2 实现 CATITypeBPoint.3 实现 CATITypeA,返回路径会停在:

Product.1 / Part.1 / Open_body.2

两个过滤方式不能混用:

调用 影响
AddElementType 会取消之前的 SetOrderedTypeList
SetOrderedTypeList 会取消之前的 AddElementType

选择建议:

只要“点到什么就尽量用什么”,用 AddElementType。
需要“优先找某类祖先对象”,用 SetOrderedTypeList。

7. 路径截断和 CATDlgEngNoSubPath

默认情况下,如果 leaf 不满足类型要求,但它的祖先对象满足,agent 会截断路径并接受这个祖先对象。

这就是前面讲的:

用户点 Point.3
命令要求 Open_body 接口
agent 可以返回 Product.1 / Part.1 / Open_body.2

如果你不希望这种截断发生,可以设置:

_selectionAgent->SetBehavior(
  CATDlgEngWithPSOHSO |
  CATDlgEngNoSubPath);

CATDlgEngNoSubPath 的含义是:

只有鼠标下 leaf 本身实现期望接口时才接受选择;
不允许从 leaf 往 root 找祖先对象作为结果。

典型使用场景:

  • 用户必须精确选中某类对象本身;
  • 不希望点到子元素时自动选中父对象;
  • 业务对 leaf 类型敏感,例如只允许直接选某个自定义 feature。

常见坑是反过来:你期望点子对象时选到父对象,却设置了 CATDlgEngNoSubPath,结果选择怎么点都不接受。

8. 常用 behavior 怎么理解

CATPathElementAgent 的 behavior 是一组位标志,可以用 | 组合。下面按使用频率整理:

行为位 作用
CATDlgEngWithPSO 高亮鼠标下预选对象,会隐含 prevaluation
CATDlgEngWithHSO 高亮已经正式选中的对象
CATDlgEngWithPSOHSO 同时支持预选和已选高亮
CATDlgEngNewHSOManager agent 重新初始化时从 HSO 移除对象
CATDlgEngRepeat agent valued 后仍可复用,常用于连续选择
CATDlgEngWithPrevaluation 鼠标悬停对象时也产生 prevalue
CATDlgEngMultiAcquisition 支持多选,结果用 GetListOfValues()
CATDlgEngMultiAcquisitionCtrl 支持 Ctrl/Shift 累加或移除选择
CATDlgEngMultiAcquisitionUserCtrl 带 UI 控制的多选模式
CATDlgEngWithDeepSel 穿透选择,得到所有符合类型的元素
CATDlgEngWithDeepFirstSel 深度选择中取第一个符合类型的元素
CATDlgEngNoSubPath 禁止路径截断,leaf 必须满足类型
CATDlgEngValuedFromCSO 命令开始时从当前 CSO 给 agent 赋值
CATDlgEngWithTooltip 显示 tooltip,可配合 SetMessage
CATDlgEngWithUserSelectionFilter 启用用户选择过滤

实践中最常见组合:

CATDlgEngWithPSOHSO |
CATDlgEngRepeat |
CATDlgEngNewHSOManager

如果想显式强调预选逻辑,也常写成:

CATDlgEngWithPSOHSO |
CATDlgEngWithPrevaluation |
CATDlgEngRepeat |
CATDlgEngNewHSOManager

不过文档里说明,CATDlgEngWithPSOCATDlgEngWithPSOHSO 本身会隐含 prevaluation。显式写出来主要是为了让读代码的人一眼知道这个 agent 会有预选值。

9. 单选、多选和深度选择

单选时用:

CATPathElement *path = _selectionAgent->GetValue();
CATBaseUnknown *selected = _selectionAgent->GetElementValue();

多选时设置:

_selectionAgent->SetBehavior(
  CATDlgEngWithPSOHSO |
  CATDlgEngMultiAcquisition |
  CATDlgEngNewHSOManager);

读取:

CATSO *values = _selectionAgent->GetListOfValues();
if (NULL != values)
{
// Iterate the CATSO according to your project conventions.
}

多选时不要再用 GetElementValue()。头文件说明得很明确:非 mono acquisition 行为下,GetElementValue() 返回 NULL

深度选择用于“穿透”当前可见对象,拿到鼠标方向上所有符合类型的元素:

_selectionAgent->SetBehavior(
  CATDlgEngWithPSOHSO |
  CATDlgEngWithDeepSel |
  CATDlgEngMultiAcquisition);

注意 CATDlgEngWithDeepSel 本质上会让结果变成多选语义,因此要用 GetListOfValues()

10. Prevaluation:预选值不是最终选择

启用 prevaluation 后,鼠标移动到对象上但还没点击时,agent 也可能有一个 prevalue。

GetValue() 的返回值取决于 valuation state:

状态 GetValue() 返回
Valuated 正式选择值
PreValuated 预选值,即使 agent 已经有正式值

如果你在 action 中只处理正式选择,通常 transition 应该由正式 valuation 触发。若你的命令还处理鼠标悬停预览,就要先判断:

CATAcquisitionAgent::ValuationState state =
  _selectionAgent->GetValuationState();

if (CATAcquisitionAgent::PreValuated == state)
{
// Update preview only.
}
elseif (CATAcquisitionAgent::Valuated == state)
{
// Commit the selection.
}

常见坑是:预选阶段就创建了模型对象,用户只是把鼠标扫过去,命令已经开始修改文档。

11. 从 CSO 初始化选择

有些命令希望支持“先选对象,再点命令”。这时可以使用:

CATDlgEngValuedFromCSO

示意:

_selectionAgent = newCATPathElementAgent(
"SelectionId",
  IID_CATIExpectedInterface,
  CATDlgEngValuedFromCSO |
  CATDlgEngWithPSOHSO);

它表示:命令开始时,如果当前 CSO 中已有符合类型的对象,agent 可以直接被赋值。

适用场景:

  • 用户先在模型中选中对象,再点击 toolbar 命令;
  • 命令启动后希望立即读取预选择对象;
  • 和 CATIA 原生命令的交互习惯保持一致。

注意:CSO 初始化只解决“启动时已有选择”的问题。命令运行过程中继续选择,仍然按普通 GetValue() / GetListOfValues() 处理。

12. 程序化设置选择值

除了让用户点选,也可以在代码里给 agent 设置路径:

_selectionAgent->SetValue(path);
_selectionAgent->SetValuation();

多选时:

_selectionAgent->SetListOfValues(values);
_selectionAgent->SetValuation();

关键点:

  • SetValue / SetListOfValues 不会登记 undo step;
  • 设置后必须调用 SetValuation(),否则这个值不会被 agent 接受;
  • agent 会保存一份值并 AddRef,析构时释放;
  • GetValue()GetListOfValues() 返回值本身不 AddRef,不要随手 Release()

这个能力适合预输入、从其它 agent 转交结果、或者用已有选择路径驱动状态机继续前进。

13. Tooltip、光标和修饰键

如果 agent 启用了 tooltip:

_selectionAgent->SetBehavior(
  CATDlgEngWithPSOHSO |
  CATDlgEngWithTooltip);

可以给 tooltip 增加额外消息:

_selectionAgent->SetMessage(CATUnicodeString("Select a support curve"));

也可以自定义“不可选择”和“预选”光标:

_selectionAgent->SetNoSelectionCursor(noSelectionCursor);
_selectionAgent->SetPreselectionCursor(preselectionCursor);

读取选择时的 Ctrl/Shift:

#include"CATDeviceEvent.h"

int modifier = _selectionAgent->GetModifier();
if (modifier & ControlModifierOn)
{
// Ctrl was pressed during selection.
}
if (modifier & ShiftModifierOn)
{
// Shift was pressed during selection.
}

这些能力不一定每个命令都需要。普通选择命令先把类型过滤和状态机流程写清楚,再加这些交互增强会更稳。

14. 和专用 agent 的关系

CATPathElementAgent 是很多选择 agent 的基础,但不是所有选择场景都该用它硬扛。

和 CATPathElementAgent 的关系
CATOtherDocumentAgent 继承自它,用于其它已打开文档窗口中的选择代理
CATFeatureAgent 继承自它,增加机械上下文、BRep featurization、装配限制
CATFeatureImportAgent 继承自 CATFeatureAgent,增加外部 Part 导入

选择建议:

普通当前文档对象选择:CATPathElementAgent
跨已打开文档窗口选择:CATOtherDocumentAgent
机械 BRep / 装配上下文选择:CATFeatureAgent
外部 Part 几何导入当前 Part:CATFeatureImportAgent

如果你发现自己在 CATPathElementAgent 的 action 里写了大量机械上下文、BRep、外部引用和 Product component 判断,通常说明该换专用 agent 了。

15. 常见坑

1. 把 AddElementType 的顺序当成优先级

AddElementType 是非有序列表,优先看路径位置,不优先看添加顺序。需要接口优先级时用 SetOrderedTypeList

2. 忘记路径会被截断

你点到的是 leaf,但 GetValue() 返回的可能是被类型过滤截断后的路径。业务代码不要盲目假设 path 的最后一个对象就是鼠标下原始 leaf。

3. 错用 CATDlgEngNoSubPath

设置它以后,leaf 必须直接满足类型要求。想让子对象自动选中父对象时,不要设置它。

4. 多选还读 GetElementValue

多选用 GetListOfValues()GetElementValue() 在非单选模式下返回 NULL

5. 预选值当成正式选择

启用 CATDlgEngWithPrevaluation 后,GetValue() 可能返回 prevalue。预览和真正创建模型对象要分开。

6. 复用 agent 但不 InitializeAcquisition

旧输出没清掉时,状态可能立刻再次满足 IsOutputSetCondition。连续选择时 action 末尾通常要清空。

7. 对 GetValue 返回值随手 Release

文档说明 GetValue()GetListOfValues()GetElementValue() 返回值不 AddRef。除非你自己 AddRef 或用 _var 管理,否则不要释放它。

8. 只选对象却用了 CATIndicationAgent

CATIndicationAgent 是点位置,输出 2D 点。只要需要选择已有对象路径,用 CATPathElementAgent

9. 机械 BRep 场景仍用普通 agent 硬写

如果需求已经涉及 face/edge 稳定引用、BRep featurization 或装配上下文,优先看 CATFeatureAgent

10. 先选后命令没有设置 ValuedFromCSO

如果产品交互希望支持先选对象再点命令,就要考虑 CATDlgEngValuedFromCSO,否则命令启动后可能看不到已有选择。

16. 推荐落地步骤

写一个新的对象选择命令,可以按下面顺序做:

1. 明确用户要选的是已有对象还是空白位置
   已有对象 -> CATPathElementAgent
   空白位置 -> CATIndicationAgent

2. 明确对象类型过滤基于哪个接口
   普通场景 -> AddElementType(IID_xxx)
   需要接口优先级 -> SetOrderedTypeList

3. 明确是否允许路径截断
   允许子对象选中父对象 -> 默认
   必须 leaf 直接匹配 -> CATDlgEngNoSubPath

4. 明确单选还是多选
   单选 -> GetValue / GetElementValue
   多选 -> CATDlgEngMultiAcquisition + GetListOfValues

5. 明确是否支持先选后命令
   是 -> CATDlgEngValuedFromCSO

6. 明确是否需要预览
   是 -> 处理 PreValuated 和 Valuated 两种状态

7. 连续选择时,在 action 末尾 InitializeAcquisition

17. 总结

CATPathElementAgent 是 CAA 交互命令里的主力选择代理。它最核心的三个点是:

  • 输出是 CATPathElement 路径,不是裸对象指针;
  • 类型过滤基于接口,并可能截断路径;
  • 单选和多选的读取 API 不同。

可以这样记:

要点 记法
选对象 CATPathElementAgent
读单选路径 GetValue()
读单选对象 GetElementValue()
读多选 GetListOfValues()
普通过滤 AddElementType(IID_xxx)
接口优先级过滤 SetOrderedTypeList
禁止路径截断 CATDlgEngNoSubPath
先选后命令 CATDlgEngValuedFromCSO

掌握它以后,再看 CATOtherDocumentAgentCATFeatureAgentCATFeatureImportAgent 会轻松很多:这些专用 agent 本质上都是在“路径选择”这件事上增加了跨文档、机械建模或外部引用语义。

评论