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

🛠️ CATIA 二次开发:CAA 特征树遍历与批量操作,遍历重命名参数读写一键搞定

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

一、痛点引入

做过 CAA 开发的同学都懂:拿到一个陌生零件,第一件事就是在特征树里翻来找去——这个凸台叫什么名字?那个凹槽的参数是多少?孔特征有没有统一命名规范?

场景:结构审查时,项目经理让你把零件里所有直径小于 5mm 的孔特征找出来重命名为"SMALL_HOLE_序号",再把相关参数批量导出。你点开模型树,逐个点开 80 多个特征,手动记录、改名、抄参数,一上午没了。

这不是你慢,是 CAA 的 SpecTree 操作本来就有门槛。今天这篇把遍历、过滤、重命名、参数读写一次性讲透。


二、核心思路

CAA 里所有能出现在模型树里的对象,底层都是 CATISpecObject。想操作特征树,核心就四张牌:

CATISpecObject       特征树根基接口(拿类型、拿名称)
CATIDescendants      遍历子元素(GetDirectChildren / GetAllChildren)
CATIAlias            读写特征显示名称(GetAlias / SetAlias)
CATICkeParmFactory   参数工厂(创建/读写参数)

你需要准备

  • CAA RADE 环境(VS + RADE + CAA 文档,环境配置参考本系列第 1 篇)
  • 当前零件文档已打开
  • Imakefile.mk 里链接 ObjectSpecsModelerObjectModelerBaseMecModInterfacesKnowledgeInterfaces

三、环境搭建(3 分钟)

在模块的 Imakefile.mk 中追加依赖:

LINK_WITH = $(MKMK_WARNING:Mkmk::UndefinedLinkWith) \
    ObjectSpecsModeler ObjectModelerBase \
    MecModInterfaces KnowledgeInterfaces

头文件引用:

#include "CATISpecObject.h"
#include "CATIDescendants.h"
#include "CATIAlias.h"
#include "CATIPrtPart.h"
#include "CATICkeParmFactory.h"
#include "CATICkeParm.h"
#include "CATICkeInst.h"
#include "CATInit.h"

四、核心代码

// ========== 第1步:获取当前 Part 根对象 ==========
CATDocument* pDoc = NULL;
CATBaseUnknown_var spActiveObj;
// 从当前编辑器获取文档(命令初始化时通常已持有)
CATInit_var spInit = spActiveObj;  // 或从命令参数传入
if (NULL_var != spInit) {
    spInit->GetRootContainer(&pDoc);
}
if (!pDoc) return;

// 取 Part 根 SpecObject
CATIPrtPart_var spPart = pDoc->GetRoot();
CATISpecObject_var spRoot = spPart;

// ========== 第2步:递归遍历特征树 ==========
void TraverseSpecTree(CATISpecObject_var& spObj, int depth) {
    if (spObj == NULL_var) return;

    // 获取类型与显示名称
    CATUnicodeString type = spObj->GetType();
    CATUnicodeString name = spObj->GetDisplayName();

    // 打印缩进层级
    CString indent;
    indent.Format("%*s", depth * 2, "");
    printf("%s[%s] %s\n", indent, type.ConvertToChar(),
           name.ConvertToChar());

    // 获取直接子元素(关键接口:CATIDescendants)
    CATIDescendants_var spDesc;
    HRESULT hr = spObj->QueryInterface(IID_CATIDescendants,
                                       (void**)&spDesc);
    if (FAILED(hr) || spDesc == NULL_var) return;

    CATListValCATISpecObject_var children;
    hr = spDesc->GetDirectChildren("CATISpecObject", children);
    if (FAILED(hr)) return;

    //  CAA 列表是 1-based 索引!
    for (int i = 1; i <= children.Size(); i++) {
        CATISpecObject_var spChild = children[i];
        TraverseSpecTree(spChild, depth + 1);
    }
}

// 执行遍历
TraverseSpecTree(spRoot, 0);

// ========== 第3步:按类型过滤 + 批量重命名 ==========
void RenameFeaturesByType(CATISpecObject_var& spObj,
                          const CATUnicodeString& targetType,
                          const CATUnicodeString& prefix) {
    if (spObj == NULL_var) return;

    CATUnicodeString type = spObj->GetType();
    if (type == targetType) {
        static int seq = 1;
        CATUnicodeString newName;
        newName.BuildFromCS(prefix.ConvertToChar());
        newName.Append("_");
        char buf[8];
        itoa(seq++, buf, 10);
        newName.Append(buf);

        // CATIAlias 修改显示名称
        CATIAlias_var spAlias = spObj;
        if (spAlias != NULL_var) {
            spAlias->SetAlias(newName);
        }
    }

    // 递归处理子节点
    CATIDescendants_var spDesc = spObj;
    if (spDesc == NULL_var) return;
    CATListValCATISpecObject_var children;
    if (SUCCEEDED(spDesc->GetDirectChildren("CATISpecObject", children))) {
        for (int i = 1; i <= children.Size(); i++) {
            RenameFeaturesByType(children[i], targetType, prefix);
        }
    }
}

// 把所有 "Pad" 特征重命名为 "BOSS_序号"
RenameFeaturesByType(spRoot, "Pad", "BOSS");

// ========== 第4步:参数批量读写 ==========
void BatchReadParameters(CATISpecObject_var& spObj) {
    if (spObj == NULL_var) return;

    // 尝试查询参数接口
    CATICkeParm_var spParm = spObj;
    if (spParm != NULL_var) {
        CATUnicodeString parmName = spParm->Name();
        double val = spParm->Value()->AsReal();
        printf("参数: %s = %.3f\n", parmName.ConvertToChar(), val);

        // 批量修改:长度参数乘以 1.1(放大 10%)
        if (parmName.Search("Length") >= 0) {
            spParm->Valuate(val * 1.1);
        }
    }

    // 递归子节点
    CATIDescendants_var spDesc = spObj;
    if (spDesc == NULL_var) return;
    CATListValCATISpecObject_var children;
    if (SUCCEEDED(spDesc->GetDirectChildren("CATISpecObject", children))) {
        for (int i = 1; i <= children.Size(); i++) {
            BatchReadParameters(children[i]);
        }
    }
}

BatchReadParameters(spRoot);

代码解析

  • CATIDescendants::GetDirectChildren("CATISpecObject", list):只取一层子元素,避免递归过深时性能爆炸;如需全量展开可用 GetAllChildren
  • CATISpecObject::GetType():返回特征的具体类型字符串(如 "Pad""Pocket""Hole"),是过滤的关键
  • CATIAlias::SetAlias():修改模型树里看到的显示名称,不会改变内部特征名
  • CATICkeParm::Valuate() / AsReal():参数写/读的标准方法,数值单位均为 MKS(米、千克、秒),长度值 0.01 表示 10mm

五、效果对比

对比项传统手动操作CAA 自动化脚本
遍历 200 个特征逐个点开,15 分钟一键输出,< 1 秒
批量重命名 50 个孔逐个 F2 改名,10 分钟递归过滤改名,< 1 秒
参数提取与修改手抄到 Excel,易错内存直读直写,零出错
可重复性每次重做编译一次,随时运行

六、进阶用法

1. 批量删除指定类型特征

// 删除所有 "Groove" 特征(注意:父节点删除后子节点失效,
// 建议从叶子往根删,或收集到列表后统一删)
CATIDescendants_var spDesc = spRoot;
CATListValCATISpecObject_var toDelete;
// ... 收集目标特征到 toDelete ...
for (int i = toDelete.Size(); i >= 1; i--) {
    CATISpecObject_var spTarget = toDelete[i];
    spTarget->Remove();  // 从特征树移除
}

2. 安全释放与 NULL_var 检查

CAA 的 QueryInterface 和智能指针 _var 混用时,一个常见坑是:接口查询失败返回 NULL,但代码继续往下跑导致崩溃。养成习惯:每次 QueryInterface 后判 FAILED(hr),每次 _var 赋值后判 == NULL_var


关注回复「特征树」获取完整源码

下一篇:CAA 中的 Undo/Redo 事务控制——批量操作如何安全回滚


_栏目:开发实战 | 发布日期:2026-08-26 | 字数:约 1180 字_

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

咨询深入资源 →