MEAICAD预约演示
技术博客/二次开发
二次开发

🛠️ CATIA 二次开发:CAA 状态机命令开发,交互式画直线从零实现

2026-08-17#CATIA#二次开发

一、痛点引入

上篇搞定 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.mkLINK_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() 投影到3D
  • GetInitialState / 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字_

本文为基础实战分享。如需完整源码包、配套课程或企业内训,欢迎进一步了解付费体系。

咨询深入资源 →