一、痛点引入
上篇搞定 CAA 环境配置后,你成功跑通了第一个 Hello World 命令。但真正的需求来了——项目经理说:"给我做个按钮,点两下鼠标在模型里画一条直线。"
你翻遍 CAA 文档,发现没有 OnMouseClick 事件,没有 Button_Click 回调。CATIA 的交互式命令不靠事件驱动,而是靠一套状态机机制:用户每点一次鼠标,就是一次"状态转移"。
这套机制和普通 GUI 开发完全不同,是 CAA 劝退率最高的概念之一。今天用一个经典的"两点画直线"命令,把状态机、对话代理、状态转移三个核心概念一次讲透。
二、核心思路
CATIA 交互式命令的核心是有限状态机:命令运行期间,用户处于某个"状态"等待输入,输入满足条件后触发"转移",执行动作并进入下一状态,直到命令结束。
用户点按钮 命令启动 [状态1: 选起点] 转移(条件+动作) [状态2: 选终点] 转移(条件+动作) 命令结束三个关键概念:
- 状态(State):命令的一个交互步骤,如"等待用户点击起点"
- 对话代理(Dialog Agent):封装用户交互,把鼠标点击翻译成程序能用的数据(2D坐标、选中对象路径等)
- 转移(Transition):连接两个状态,包含触发条件(Guard Condition)和执行动作(Action)
你需要准备:
- CAA V5 RADE 环境(参考上篇环境配置)
- 一个可编译的 CAA 模块(含 IdentityCard.h + Imakefile.mk)
- CATIA V5 已启动并打开一个 Part 文档
三、环境搭建(2分钟)
上篇已完成 RADE 环境配置,这里只补充模块依赖。在模块的 IdentityCard.h 中添加:
#include "JS0GROUP.h"
#include "CATStateCommand.h"
#include "CATDialogEngine.h"
#include "CATIndicationAgent.h"
#include "CATMathPlane.h"
#include "CATMathPoint.h"Imakefile.mk 的 LINK_WITH 确保包含:
LINK_WITH = JS0GROUP CATDialogEngine CATMathematics验证方式:在 RADE 中 mkmk 编译,无报错即通过。
四、核心代码
以下实现一个完整的"两点画直线"状态命令。包含头文件、源文件和命令头注册三部分。
4.1 头文件 CreateLineCmd.h
#include "CATStateCommand.h"
#include "CATMathPoint.h"
class CATIndicationAgent;
class CreateLineCmd : public CATStateCommand
{
// 声明资源文件(NLS多语言提示信息)
DeclareResource(CreateLineCmd, CATStateCommand)
public:
CreateLineCmd();
virtual ~CreateLineCmd();
// 核心方法:在此构建状态图
virtual void BuildGraph();
private:
CATIndicationAgent* _daIndication; // 鼠标点击代理
CATMathPoint _startPoint; // 缓存起点坐标
// 监控条件:检查终点是否与起点重合
CATBoolean CheckEndPoint(void* iUsefulData);
// 动作方法:获取起点坐标
CATBoolean ActionGetStartPoint(void* iUsefulData);
// 动作方法:创建直线
CATBoolean ActionCreateLine(void* iUsefulData);
};4.2 源文件 CreateLineCmd.cpp
#include "CreateLineCmd.h"
#include "CATIndicationAgent.h"
#include "CATMathPlane.h"
#include "CATMathPoint2D.h"
// ========== 类注册宏(三件套) ==========
CATImplementClass(CreateLineCmd, Implementation, CATStateCommand, CATNull);
#include "CATCreateExternalObject.h"
CATCreateClass(CreateLineCmd);
// ========== 构造/析构 ==========
CreateLineCmd::CreateLineCmd() : CATStateCommand("CreateLineCmd") {}
CreateLineCmd::~CreateLineCmd()
{
// 代理由对话框引擎管理,这里只请求延迟销毁
if (_daIndication != NULL)
_daIndication->RequestDelayedDestruction();
}
// ========== 核心方法:构建状态图 ==========
void CreateLineCmd::BuildGraph()
{
// ---- 第1步:创建对话代理 ----
_daIndication = new CATIndicationAgent("PointIndication");
// 设置代理行为:支持预选高亮 + 接受预估值
_daIndication->SetBehavior(CATDlgEngWithPrevaluation | CATDlgEngAcceptOnPrevaluate);
// ---- 第2步:创建状态 ----
// 初始状态(命令启动后自动进入)
CATDialogState* stStart = GetInitialState("stStartPointId");
// 第二状态
CATDialogState* stEnd = AddDialogState("stEndPointId");
// ---- 第3步:将代理加入状态 ----
// 同一个代理可以关联多个状态,通过 InitializeAcquisition() 重置后复用
stStart->AddDialogAgent(_daIndication);
stEnd->AddDialogAgent(_daIndication);
// ---- 第4步:添加状态转移 ----
// 转移1:起点状态 终点状态(用户点击后触发)
AddTransition(
stStart, stEnd,
IsOutputSetCondition(_daIndication),
Action((ActionMethod)&CreateLineCmd::ActionGetStartPoint));
// 转移2:终点状态 NULL(命令结束)
// 附加自定义条件:终点不能与起点重合
AddTransition(
stEnd, NULL,
AndCondition(
IsOutputSetCondition(_daIndication),
Condition((ConditionMethod)&CreateLineCmd::CheckEndPoint)),
Action((ActionMethod)&CreateLineCmd::ActionCreateLine));
}
// ========== 监控条件:终点不能与起点重合 ==========
CATBoolean CreateLineCmd::CheckEndPoint(void* iUsefulData)
{
CATMathPoint2D point2D = _daIndication->GetValue();
CATMathPlane plane = _daIndication->GetMathPlane();
CATMathPoint endPoint;
plane.EvalPoint(point2D.GetX(), point2D.GetY(), endPoint);
// 距离小于0.001mm视为重合,拒绝转移
if (endPoint.DistanceTo(_startPoint) < 0.001)
return FALSE;
return TRUE;
}
// ========== 动作1:获取并缓存起点坐标 ==========
CATBoolean CreateLineCmd::ActionGetStartPoint(void* iUsefulData)
{
CATMathPoint2D point2D = _daIndication->GetValue();
CATMathPlane plane = _daIndication->GetMathPlane();
plane.EvalPoint(point2D.GetX(), point2D.GetY(), _startPoint);
// 关键:代理复用前必须重置,否则下一次点击不会触发状态转移
_daIndication->InitializeAcquisition();
return TRUE;
}
// ========== 动作2:创建直线 ==========
CATBoolean CreateLineCmd::ActionCreateLine(void* iUsefulData)
{
CATMathPoint2D point2D = _daIndication->GetValue();
CATMathPlane plane = _daIndication->GetMathPlane();
CATMathPoint endPoint;
plane.EvalPoint(point2D.GetX(), point2D.GetY(), endPoint);
// 此处调用 GSM 工厂创建实际几何线
// 完整实现需 CATIGSMUseFactory->CreateLinePtPt(pt1, pt2)
// 详见 CAA 官方案例 CAADegCreateLineCmd
printf("直线创建完成: (%.1f,%.1f,%.1f) -> (%.1f,%.1f,%.1f)\n",
_startPoint.GetX(), _startPoint.GetY(), _startPoint.GetZ(),
endPoint.GetX(), endPoint.GetY(), endPoint.GetZ());
return TRUE;
}4.3 命令头注册
在 Addin 的 CreateCommands() 中注册命令头:
CATAfrCommandHeader::CATCreateCommandHeader(
"CreateLineCmdHeader", // 命令头名称
"MyModule", // 所属模块
"CreateLineCmd", // 命令实现类名
(void*)NULL, // 构造参数
"CreateLineCmdHeader", // 资源头名称
CATFrmAvailable); // 可用性代码解析:
CATIndicationAgent:鼠标点击代理,返回2D屏幕坐标,需配合GetMathPlane()投影到3DGetInitialState / AddDialogState:创建状态,初始状态命令启动后自动进入AddTransition:四参数——源状态、目标状态(NULL=结束)、监控条件、动作IsOutputSetCondition:内置条件,检查代理是否已产生输出(用户是否已点击)InitializeAcquisition():代理复用前必须调用,否则状态机卡死
五、效果对比
| 对比项 | CATScript 宏录制 | CAA 状态机命令 |
|---|---|---|
| 交互能力 | 仅模拟键盘输入 | 原生鼠标拾取+预选高亮 |
| 条件判断 | 无法在交互中校验 | 监控条件实时拦截非法输入 |
| 可扩展性 | 固定流程 | 多状态+多代理自由组合 |
| 用户体验 | 无提示 | 状态栏自动显示操作引导 |
| 开发门槛 | 低 | 中高(需理解状态机) |
六、进阶用法
技巧1:用 CATPathElementAgent 选择已有对象
如果需要用户选择模型树中的点而非屏幕点击,把代理换成 CATPathElementAgent:
_daPathElement = new CATPathElementAgent("SelectPoint");
// 限制只能选择点对象,其他类型自动过滤
_daPathElement->AddElementType(IID_CATIGSMPoint);
_daPathElement->SetBehavior(CATDlgEngWithPrevaluation | CATDlgEngAcceptOnPrevaluate);技巧2:自转移实现连续画线
把转移2的目标状态从 NULL 改为 stEnd,即可实现连续画多条线:
// 终点状态自转移(不停循环画线),直到用户按ESC
AddTransition(
stEnd, stEnd, // 目标改为自身,形成循环
AndCondition(
IsOutputSetCondition(_daIndication),
Condition((ConditionMethod)&CreateLineCmd::CheckEndPoint)),
Action((ActionMethod)&CreateLineCmd::ActionCreateLine));注意:自转移模式中 ActionCreateLine 结尾也要调用 InitializeAcquisition(),否则只能画一条。
关注回复「CAA状态机」获取完整源码
下一篇:CAA C++ 第3篇——对话框代理进阶,CATPathElementAgent 多类型过滤与对象路径解析
_栏目:开发实战 | 发布日期:2026-08-17 | 字数:约1150字_