本文目标把 V6 自定义特征开发真正需要走的链路串起来:先定义 StartUp,再生成 CATfct Catalog,然后用 FeatureModelerExt API 在运行时实例化、扩展、更新、可视化并接入交互。
1. 先把几个核心概念说清楚
在 V6 里,自定义特征开发通常围绕下面 4 个对象展开:
-
StartUp:特征模板,定义“这个特征有哪些属性、支持哪些协议、是否是扩展特征”。
-
CATfct Catalog:承载 StartUp 定义的目录文件。
-
Applicative Container:特征实例真正落地的容器。
-
Feature Instance:运行时创建出来的具体特征对象。
如果你的目标是“从零定义一个新特征”,那就是 base feature 路线。
如果你的目标是“给已有特征追加业务语义或行为”,那就是 feature extension 路线,也就是在 OSM 里用 #extension 定义扩展 StartUp。
2. V6 相比旧方式最重要的变化
V6 的关键变化不是“多了几个接口”,而是整个建模方式变了:
-
StartUp Catalog 不再建议通过旧式 C++ 目录 API 直接创建或升级。
-
Catalog 的定义和升级应当统一走
OSM + CATfctEditorAssistant。 -
运行时访问不再围绕“手工打开 Catalog 并查 StartUp”展开,而是围绕
CATFmCredentials、CATFmStartUpFacade、CATFmFeatureFacade、CATFmContainerFacade这些 Facade API 展开。 -
V6 的对象环境以 PLM Representation / Technological Representation 为中心,因此容器、导航、可视化、交互往往都要放到更完整的产品上下文里考虑。
可以把它理解成一句话:
V6 自定义特征开发的主线是“先声明式定义,再运行时实例化,再按场景挂行为”。
3. 推荐的总体开发顺序
比较稳妥的顺序如下:
-
明确你要做的是 base feature 还是 extension。
-
设计 OSM 中的 StartUp、属性、容器和协议声明。
-
用
CATfctEditorAssistant生成或升级 Catalog。 -
实现 Credentials 获取逻辑。
-
实现容器侧工厂或业务创建入口。
-
实现特征业务接口,封装属性读写。
-
实现
CATIFmFeatureBehaviorCustomization,接入 Update / Delete 等行为。 -
再逐步补可视化、规格树、编辑命令、上下文菜单。
这个顺序的好处是:模型层先稳定,UI 和 Session 集成可以后补,不会一开始就把问题摊得太大。
4. 第一步:用 CATfctEditorAssistant 管理 Catalog
4.1 创建一个空 Catalog
典型命令如下:
CATfctEditorAssistant -create-new-catalog -catalog-name MyCatalog.CATfct -
with-client-id MyClientId -into-directory <output-dir>
这一步会生成一个空的 OSM 文件和一个空的 CATfct 文件。
4.2 从已有 Catalog 导出 OSM
如果你是维护老目录,或者想把现有 Catalog 反向拉成文本定义,常用命令是:
CATfctEditorAssistant -describe-as-osm -catalog-name MyCatalog.CATfct -with-
client-id MyClientId -as MyCatalog.osm -into-directory <output-dir>
4.3 根据 OSM 升级 Catalog
CATfctEditorAssistant -update-catalog -catalog-name MyCatalog.CATfct -with-
client-id MyClientId -with-osm MyCatalog.osm -into-directory <output-dir>
4.4 实战建议
-
不要把 CATfct 当成手工维护对象,真正应该维护的是 OSM。
-
修改完成后,要把更新后的 CATfct 放回运行时可见路径,通常是
CNext/resources/graphic或你的 runtime view。 -
如果是从旧版本迁移上来的 extension catalog,要特别检查扩展元数据是否补齐。
5. 第二步:在 OSM 中定义 StartUp
5.1 定义基础特征
下面是一个“从零定义特征”的简化写法,重点是结构,不是照抄某个用例:
document `MyCatalog.CATfct`
{
container CATFeatCont #root {
feature MyRoot #startup {
component Children #list #in
}
feature MyPoint #startup {
double X #in
double Y #in
double Z #in
#interface::CATIUseEntity = metadata_CommonProtocol
}
}
}
metadata metadata_CommonProtocol {
`implementInterface` = true
`implementCopyPasteAspect` = true
`implementDeleteAspect` = true
`implementUpdateAspect` = true
}
这里最关键的是三件事:
-
用
#startup声明它是可实例化模板。 -
用
#in区分 specification 属性。 -
用
#interface::CATIUseEntity这类元数据把通用协议接进来。
5.2 定义扩展特征
如果你是给已有特征挂扩展,结构通常长这样:
feature `MyLineExtension` #startup #extension {
component Param1 #in
component Param2 #in
specobject BaseLine #in #auto_update
#creation::parameter = metadata_Extension
#interface::CATIUseEntity = metadata_CommonProtocol
}
metadata metadata_Extension {
IsLocal = true
Extends = ["MyLine"]
Container = "MyExtensionCont"
ContType = "MyExtensionCont"
}
metadata metadata_CommonProtocol {
`implementInterface` = true
`implementCopyPasteAspect` = false
`implementDeleteAspect` = true
`implementUpdateAspect` = true
}
这里有 4 个要点:
-
#extension说明它不是独立特征,而是依附在 base feature 上。
-
Extends指定它能扩展哪些基础特征。
-
Container和
ContType指定扩展实例要落到哪个 applicative container。 -
#auto_update非常重要,表示 base feature 变化时,这个扩展特征需要跟着更新。
6. 第三步:准备 NLS 与资源文件
Catalog 能生成,不代表特征在会话里“像个产品能力”。至少还要准备:
-
CNext/resources/graphic下的
CATfct。 -
CNext/resources/msgcatalog下的
CATNls。
一个最小化的 NLS 例子:
MyPoint="Point";
MyLineExtension="Relimited Line";
如果没有这一层,很多时候你虽然能创建对象,但界面里显示的晚绑定类型名会非常生硬。
7. 第四步:用 Credentials 取得合法访问权
V6 下很多 API 是否能工作,不取决于你有没有拿到对象指针,而取决于你的 CATFmCredentials 是否具备相应授权。
典型模式是:
static HRESULT RetrieveCredentials(CATFmCredentials*& credentials)
{
static CATBoolean firstTime = TRUE;
static CATFmCredentials uniqueCredential;
static HRESULT uniqueHr = E_FAIL;
if (TRUE == firstTime)
{
CATUnicodeString clientId("MyClientId");
CATUnicodeString partnerId("MyPartner");
uniqueHr = uniqueCredential.RegisterAsApplicationBasedOn(CATFmFeatureModelerID, partnerId);
if (SUCCEEDED(uniqueHr))
uniqueHr = uniqueCredential.RegisterAsCatalogOwner("MyCatalog", clientId);
firstTime = FALSE;
}
credentials = &uniqueCredential;
return uniqueHr;
}
这里必须注意:
-
clientId要和你用
CATfctEditorAssistant生成 Catalog 时使用的 client id 对得上。 -
如果你的特征涉及多个 Catalog,就要为每个 Catalog 注册 owner 资格。
-
如果特征依赖 applicative container,还要确保应用级注册也做了。
8. 第五步:实例化基础特征
基础特征一般通过 CATFmStartUpFacade 完成实例化。
典型链路如下:
CATFmContainerFacade containerFacade(*credentials, iContainer);
CATFmStartUpFacade startUpFacade(*credentials, "`MyPoint`@`MyCatalog.CATfct`");
CATFmFeatureFacade featureFacade;
HRESULT rc = startUpFacade.InstantiateIn(containerFacade, featureFacade);
if (SUCCEEDED(rc))
{
rc = featureFacade.QueryInterfaceOnFeature(IID_IMyPoint, (void**)&oPoint);
}
核心理解是:
-
ContainerFacade负责“装到哪里去”。
-
StartUpFacade负责“按哪个模板装”。
-
FeatureFacade负责“装出来以后怎么继续操作”。
如果你的模型要求所有业务特征必须聚合在一个 Root 下面,最好把实例化逻辑封到容器自己的工厂接口里,而不是到处散落 InstantiateIn 调用。
9. 第六步:创建扩展特征
扩展特征不是用 InstantiateIn 直接落地,而是通常挂在 base feature 上:
CATFmFeatureFacade baseFacade(*credentials, iBaseFeature);
CATFmFeatureFacade extensionFacade(*credentials);
HRESULT rc = baseFacade.AddExtension("`MyLineExtension`@`MyCatalog.CATfct`", extensionFacade);
创建成功后,通常还要做三类初始化:
-
给扩展参数赋值。
-
把 base feature 写回扩展的关联属性。
-
通过你自己的业务接口把扩展对象暴露给上层命令。
例如:
CATFmAttributeName attrName = "BaseLine";
CATFmAttributeValue attrValue;
attrValue.SetFeature(iBaseFeature);
extensionFacade.SetValue(attrName, attrValue);
一个重要行为是:如果 #creation::parameter 里把 container 信息写对了,那么当目标 applicative container 尚不存在时,AddExtension 可以依赖这些声明式信息去创建所需容器。
10. 第七步:为特征封装业务接口
不要让上层命令直接读写 CATFmAttributeValue。更稳妥的做法是给特征实现一个业务接口,例如:
-
SetParam1/
GetParam1 -
SetParam2/
GetParam2 -
GetBaseFeature
这样做有两个实际收益:
-
上层逻辑不需要知道属性名、值类型和 Facade 细节。
-
你可以把约束、默认值和一致性检查收口到接口实现内部。
这也是很多 CAADoc 用例反复强调的做法:让业务接口负责“可安全访问的数据模型”,而不是把属性表暴露给所有调用点。
11. 第八步:实现 CATIFmFeatureBehaviorCustomization
如果说 OSM 解决了“特征是什么”,那么 CATIFmFeatureBehaviorCustomization 解决的是“特征怎么活”。
这个接口至少要重点看 3 个点。
11.1 Build()
Build() 会在特征 update 时被调用,尤其是在 sp_in 属性变化之后。典型职责是:
-
根据输入重新计算结果。
-
必要时发出模型变更通知。
-
让 3D 可视化与模型状态保持同步。
常见模式是通过 CATIModelEvents 发送 CATModify 通知。
11.2 CanBeRemoved()
这个方法决定“当前特征是否允许被删除服务删除”。
一个很容易踩的坑是:
-
Feature Extension 往往是聚合在 base feature 下面的。
-
聚合特征默认通常不允许通过通用删除服务删除。
-
如果你想让扩展支持公共删除流程,经常需要覆写
CanBeRemoved()并显式返回可删除。
11.3 BeforeRemove()
这个方法适合做删除前清理,例如:
-
解除外部引用。
-
清理扩展带来的附属对象。
-
回收容器内的联动数据。
如果你的扩展除了自身属性,还带外部对象或缓存,不做这一步,后续问题通常会非常隐蔽。
12. 第九步:接入删除、更新和通用协议
在 OSM 里声明:
#interface::CATIUseEntity = metadata_CommonProtocol
只是第一层。真正运行时还要分清下面几种删除路径:
-
DeleteSelf():在扩展自己的
FeatureFacade上调用。 -
RemoveExtension():在 base feature 上调用,移除指定扩展。
-
DataCommonProtocolServices::Delete():公共删除服务。
这里最关键的区别是:
-
DeleteSelf()和
RemoveExtension()不等价于公共删除服务。 -
公共删除服务在执行前会做
CanDelete()之类的合法性判断。 -
对扩展特征来说,要想让公共删除服务真正可用,除了 OSM 中启用
CATIUseEntity,通常还需要在CATIFmFeatureBehaviorCustomization::CanBeRemoved()里放行。
所以删除能力一定要从“谁调用、走哪条协议、是否需要 owner 权限”三个维度一起验证。
13. 第十步:补齐可视化、规格树和交互能力
一个能落地到 V6 会话里的“完整自定义特征”,通常还需要以下接口族:
13.1 可视化层
-
CATI3DGeoVisu:负责生成 3D 表示。
-
CATIModelEvents:负责模型变更到视图变更的通知。
-
CATIVisProperties:负责颜色、线宽等图形属性。
-
CATISelectShow:负责 hide/show。
13.2 规格树 / Session 层
-
CATINavigateObject:让对象能进入规格树导航。
-
CATINavigateProvider:由 applicative container 暴露子对象给规格树。
-
CATI3DVisuProvider:由 applicative container 把内部对象暴露给可视化框架。
13.3 交互层
-
CATIEdit:双击或命令编辑。
-
CATICSOFilter:控制上下文菜单命中。
-
Addin / Toolbar / State Command:把功能真正挂到 Product Editor 或相关工作台上。
一个非常实用的经验是:
特征本身负责“能力”,容器负责“把能力接进会话框架”。
也就是说,规格树和可视化并不总是单靠特征自身接口就够,容器侧的 provider 注册通常同样关键。
14. Applicative Container 在 V6 里的地位
很多人第一次做 V6 自定义特征时,最大误区是把 container 当成“只是个存放位置”。实际上它至少承担三类职责:
-
作为实例化目标,承接
InstantiateIn或 extension 自动落地。 -
作为工厂,封装 Root / Point / Line / Wire 等对象的业务创建过程。
-
作为 Provider Owner,在加载时注册规格树、可视化、上下文等 provider。
如果你的模型稍微复杂一点,建议一开始就把“容器工厂接口”和“容器初始化逻辑”分出来,不要把创建逻辑直接写到命令类里。
15. 部署与工程组织建议
建议至少按下面的物理结构组织:
-
一个 modeler 模块:放业务接口实现、FeatureBehaviorCustomization、可视化实现等。
-
一个 command 模块:放
CATIEdit、状态命令、对话框、CSO 过滤等交互代码。 -
CNext/resources/graphic:放 CATfct。
-
CNext/resources/msgcatalog:放 CATNls。
-
CNext/code/dictionary:放接口实现与库映射的 dico 声明。
典型 dico 条目会长成这样:
MyFeature IMyFeature libMyModeler
MyFeature CATIFmFeatureBehaviorCustomization libMyModeler
MyFeature CATI3DGeoVisu libMyModeler
MyFeature CATIEdit libMyCommand
如果 dico 没配好,接口实现写得再完整,运行时也等于没接上。
16. 最容易踩的坑
16.1 只写了 OSM,没有补运行时行为
结果通常是:能建对象,但看不到、不能编辑、不能进规格树。
16.2 忘了把更新后的 CATfct 放回 runtime view
这会导致你明明改了 OSM,但会话里仍旧加载旧目录。
16.3 Credentials 对不上
尤其是 client-id 和 RegisterAsCatalogOwner() 的 catalog 名不一致时,很多 API 会直接失败。
16.4 扩展特征没写 #creation::parameter
旧目录迁移到新版本时,这是高频问题。没有这块元数据,extension 的容器定位和创建往往会出错。
16.5 误以为 DeleteSelf() 会触发所有删除校验
实际上不同删除路径触发的协议不同,公共删除服务和 owner 直接删除不是一回事。
16.6 把 Copy/Paste 想当然地开给 extension
很多 extension 用例默认就是不支持 Copy/Paste 的,必须根据你的数据语义单独设计。
17. 一个实用的落地模板
如果你要从零做一个 V6 自定义特征,我建议照这个最小闭环推进:
-
先做一个 base feature,确认 OSM、Catalog、Credentials、InstantiateIn 链路打通。
-
给它实现一个业务接口,只做最简单的属性读写。
-
实现
CATIFmFeatureBehaviorCustomization::Build(),确认 update 能跑。 -
补
CATI3DGeoVisu和CATIModelEvents,确认模型改了能驱动显示更新。 -
补
CATINavigateObject和容器 provider,确认能进规格树。 -
最后再补
CATIEdit、对话框和上下文命令。
如果你的需求是“给现有特征叠加业务语义”,则把第 1 步替换成扩展路线:先把 #extension、#creation::parameter 和 AddExtension() 跑通,再补行为层。
18. 当前安装库里最值得先看的文档
如果你要继续深挖,建议按下面顺序读:
CAADoc/win_b64.doc/English/CAACppAdoption/CAACenOsmTaSpecsModelerAdoption.htmCAADoc/win_b64.doc/English/CAACppAdoption/CAACenOsmTaOsmMigration.htmCAADoc/win_b64.doc/English/CAAPstProductModelerExt/CAAPstUcINFBuildCatalog.htmCAADoc/win_b64.doc/English/CAAPstProductModelerExt/CAAPstUcINFExtensionStartUp.htmCAADoc/win_b64.doc/English/CAAPstProductModelerExt/CAAPstUcINFExtensionLifeCycle.htmCAADoc/win_b64.doc/English/CAAPstProductModelerExt/CAAPstUcINFExtensionBehaviors.htmCAADoc/win_b64.doc/English/CAAPstProductModelerExt/CAAPstUcINFIntrinsicFeatBehavior.htmCAADoc/win_b64.doc/English/CAAPstProductModelerExt/CAAPstUcINFIntegrateNewFeatures.htm
19. 结论
V6 自定义特征开发,本质上不是“写一个类并注册一下”这么简单,而是三层工作的组合:
-
声明层
:用 OSM 定义 StartUp、属性、协议和扩展元数据。
-
模型层
:用 FeatureModelerExt 的 Facade 与 Credentials 完成实例化、扩展、更新和删除控制。
-
会话层
:用可视化、导航、编辑和 provider 机制把特征真正接入 V6 交互环境。
只要按这个顺序推进,V6 自定义特征开发并不。真正难的部分通常不是 API 数量,而是把 Catalog、Credentials、Container、Behavior 和 Session Integration 这五块拼成一条完整链路。
评论