首页 / 资讯中心 / 文章详情

NX二次开发CustomFeature实战:从配置到回调的避坑指南

NX二次开发CustomFeature实战:从配置到回调的避坑指南 ★ FEATURED ARTICLE
简介面向NX二次开发者的CustomFeature实战记录文档聚焦C环境下用户自定义特征的创建、配置与问题排查适合已具备NXOpen基础、希望扩展NX几何构造能力的中高级开发者参考。压缩包内仅1个docx文件约18KB以文字记录形式整理了CustomFeatureConfiguration.xml的配置要点、核心库与UI库的分工以及特征类注册、编辑模式区分、属性读取等关键环节的测试过程与异常处理思路。内容涉及FeatureClass与代码类名一致性、IsWithoutBody对无实体特征编辑的影响、对话框构造与Show模式切换等易错点并给出头文件引用清单与常见报错场景的应对方式。已有351人学习可作为CustomFeature开发调试阶段的对照笔记帮助读者少走配置与注册环节的弯路。1. 从一次“特征双击没反应”说起去年帮一个做模具检具的团队排查问题他们在 NX 里做了一套多边形孔的自定义特征创建时一切正常模型树里也能看到节点可用户双击那个特征想改参数时界面纹丝不动历史记录里连个编辑入口都没有。折腾半天才发现问题不在 C 代码而在一个只有几十行的CustomFeatureConfiguration.xml里——IsWithoutBody写成了false而他们的特征只产出曲线没有实体。NX 在尝试进入编辑态时找不到目标体直接抛了异常被上层吞掉了。这件事很典型。NX 二次开发里CustomFeature用户自定义特征是把 C 几何构造能力封装成“像原生特征一样可创建、可编辑、可回放”的机制涉及两个 DLL、一个 XML 配置、五个回调函数和一套属性数据模型。它适合已经能写 NXOpen 基础代码、想把内部算法沉淀成标准特征的中高级开发者。下面按“配置怎么摆 → UI 怎么接 → Core 怎么算 → 坑怎么避 → 怎么改”的顺序拆一遍代码都来自实际测试记录能直接对照着抄。2. 工程骨架与 XML 配置两个 DLL 加一个固定文件名2.1 目录结构与部署位置CustomFeature 的产物不是单个 DLL而是一组文件必须落到 NX 能扫描到的application目录下。测试时我用的结构是这样的my_custom_feature/ ├── application/ │ ├── CustomFeatureConfiguration.xml # 固定文件名不能改 │ ├── CustomFeatureConfiguration_schema.xsd │ ├── BlockAndCylinderCore.dll # 特征几何实现 │ ├── BlockAndCylinderUI.dll # 对话框逻辑 │ ├── PolygonalHoleCore.dll │ └── PolygonalHoleUI.dll └── startup/ └── (可选) 菜单、工具栏注册文件application目录是 NX 启动时自动加载的约定路径放在用户目录或环境变量指向的路径下都行。两个 DLL 的分工必须清楚FeatureLibrary对应的 Core DLL 负责几何创建和更新回调FeatureUILibrary对应的 UI DLL 负责 Block Styler 对话框。把它们混在一个 DLL 里NX 加载时会出现类注册冲突这是血泪经验。2.2 XML 每个字段到底管什么配置文件的名字必须是CustomFeatureConfiguration.xml一个字母都不能差否则 NX 静默忽略。内部结构如下?xml version1.0 encodingutf-8? CustomFeatureLib xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:noNamespaceSchemaLocationCustomFeatureConfiguration_schema.xsd CustomFeature FeatureClassNXOpen::CustomFeature::BlockAndCylinder FeatureNameBlock And Cylinder FeatureIconblock_and_cylinder FeatureLibraryBlockAndCylinderCore FeatureUILibraryBlockAndCylinderUI IsWithoutBodyfalse/ CustomFeature FeatureClassNXOpen::CustomFeature::SamplePolygonalHole FeatureNamePolygonal Hole FeatureIconpolygonal_hole FeatureLibraryPolygonalHoleCore FeatureUILibraryPolygonalHoleUI IsWithoutBodytrue/ /CustomFeatureLib逐项说明这些是排查问题时最先要核对的属性作用常见错误FeatureClass与 C 里注册的类名完全一致含命名空间少写NXOpen::CustomFeature::前缀导致找不到类FeatureName历史模型记录里显示的名字与 UI 标题不一致用户困惑FeatureIcon历史树节点图标资源名图标文件缺失时显示空白但不报错FeatureLibraryCore DLL 文件名不含 .dll与实际输出名大小写不符FeatureUILibraryUI DLL 文件名不含 .dll两个特征写反编辑时打开错误对话框IsWithoutBody特征是否不含实体只出曲线却写 false双击编辑直接异常IsWithoutBody是最容易翻车的一项。规则很硬创建结果只有曲线、没有体必须写true这样双击曲线就能进编辑如果实际没有体却写falseNX 会提示“不能创建体或者没有目标体”。反过来有实体却写true编辑时实体引用会丢失。判断标准就看你的createGeometryCallBack最终往SetBodies里塞了什么。2.3 头文件清单与引入顺序Core 和 UI 两个工程都要引一批 NXOpen 头文件测试记录里整理出的最小集合如下#include NXOpen/Features_Feature.hxx #include NXOpen/Features_FeatureCollection.hxx #include NXOpen/Features_CustomAttribute.hxx #include NXOpen/Features_CustomAttributeCollection.hxx #include NXOpen/Features_CustomExpressionAttribute.hxx #include NXOpen/Features_CustomIntegerAttribute.hxx #include NXOpen/Features_CustomLogicalAttribute.hxx #include NXOpen/Features_CustomTagAttribute.hxx #include NXOpen/Features_CustomFeature.hxx #include NXOpen/Features_CustomFeatureBuilder.hxx #include NXOpen/Features_CustomFeatureClass.hxx #include NXOpen/Features_CustomFeatureClassManager.hxx #include NXOpen/Features_CustomFeaturePreUpdateEvent.hxx #include NXOpen/Features_CustomFeatureData.hxx #include NXOpen/Features_CustomFeatureDataCollection.hxx #include NXOpen/Features_CustomFeatureCreateFeatureGeometryEvent.hxx #include NXOpen/Features_CustomFeatureModifyFeatureGeometryEvent.hxx #include NXOpen/Features_CustomFeaturePostUpdateEvent.hxx #include NXOpen/Features_CustomFeatureInformationEvent.hxx属性相关的头文件CustomExpressionAttribute、CustomTagAttribute等在 UI 和 Core 里都要用因为属性既要在对话框里读写也要在几何回调里取值。工程属性里记得把 NXOpen 的 include 路径和 lib 路径配好链接libnxopen.lib一类的基础库这部分按 NX 版本自带的向导生成最稳。3. UI 层拆解对话框如何区分创建与编辑3.1 构造阶段拿到特征类句柄UI DLL 的对话框类里第一步是把CustomFeatureClassManager和具体的CustomFeatureClass存成成员变量。注意是成员变量添加时去掉前面的类型定义只留变量名和初始化// 对话框类成员变量 NXOpen::Features::CustomFeatureClassManager *m_CusFeatManager theSession-CustomFeatureClassManager(); NXOpen::Features::CustomFeatureClass *m_CusFeatClass m_CusFeatManager-GetClassFromName(NXOpen::CustomFeature::SamplePolygonalHole);GetClassFromName的字符串必须和 XML 里的FeatureClass一字不差。这里拿到的m_CusFeatClass后面创建CustomFeatureData时要用所以别用局部变量。3.2 Show 阶段判断编辑还是创建同一个对话框要服务两种入口新建特征和双击编辑。区分靠GetEditedCustomFeature()返回非空就是编辑态NXOpen::Features::CustomFeature* editedFeature m_CusFeatManager-GetEditedCustomFeature(); BlockDialog::DialogMode mode editedFeature ! NULL ? BlockDialog::DialogModeEdit : BlockDialog::DialogModeCreate; theDialog-Show(mode);编辑态下 Block Styler 会自动隐藏 Apply 按钮只留 OK这是框架行为不用自己控制。如果发现编辑时还能点 Apply多半是Show传错了 mode。3.3 初始化阶段回填属性值初始化回调里要分两条路首次创建给默认值二次编辑从FeatureData里按属性名读回来。读取的关键是属性类型要对上表达式属性用CustomExpressionAttributeByNameTag 属性用CustomTagAttributeByNameNXOpen::Part *workPart(theSession-Parts()-Work()); NXOpen::Features::CustomFeature* editedFeature m_CusFeatManager-GetEditedCustomFeature(); if (editedFeature NULL) { UF_UI_open_listing_window(); UF_UI_write_listing_window(初始化中第一次创建\n); } else { NXOpen::Features::CustomFeatureData* cfData editedFeature-FeatureData(); PopulateUIFromCustomFeatureData(cfData); UF_UI_write_listing_window(初始化中二次编辑\n); }PopulateUIFromCustomFeatureData里按类型取值表达式、方向、点、体各有各的写法// 表达式属性 Features::CustomExpressionAttribute *sidesAttrib cfData-CustomExpressionAttributeByName(m_polygonSides); NXOpen::Expression* sidesExp sidesAttrib-Value(); // 方向 Tag 属性 NXOpen::Features::CustomTagAttribute* dirAttrib cfData-CustomTagAttributeByName(m_holeDirection); NXOpen::Direction* dir dynamic_castNXOpen::Direction*(dirAttrib-Value()); // 点 Tag 属性 NXOpen::Features::CustomTagAttribute* ptAttrib cfData-CustomTagAttributeByName(m_polygonPoint); NXOpen::Point* pt dynamic_castNXOpen::Point*(ptAttrib-Value()); // 体 Tag 属性 NXOpen::Features::CustomTagAttribute* bodyAttrib cfData-CustomTagAttributeByName(m_targetBody); NXOpen::Body* body dynamic_castNXOpen::Body*(bodyAttrib-Value());dynamic_cast不能省Value()返回的是基类指针直接强转在类型不匹配时会崩。可选属性建议先判断存在性再取避免空指针Features::CustomTagAttribute *sectionAttrib cfData-HasCustomAttribute(section, NXOpen::Features::CustomAttribute::TypeTag) ? dynamic_castFeatures::CustomTagAttribute*(cfData-CustomTagAttributeByName(section)) : NULL;3.4 Apply 阶段提交特征Apply 回调是整个 UI 的核心负责创建或更新CustomFeatureData再通过 Builder 提交。结构上先建 Builder再按创建/编辑分支准备数据最后SetFeatureData加CommitFeatureFeatures::CustomFeatureBuilder *builder NULL; try { NXOpen::Part *workPart(theSession-Parts()-Work()); NXOpen::Features::CustomFeatureData *cfData NULL; NXOpen::Features::CustomFeature* editedFeature m_CusFeatManager-GetEditedCustomFeature(); builder workPart-Features()-CreateCustomFeatureBuilder(editedFeature); if (editedFeature NULL) { std::vectorNXOpen::Features::CustomAttribute* attrs(0); CreateFeatureAttributes(attrs); cfData workPart-Features()-CustomFeatureDataCollection()-CreateData(m_CusFeatClass, attrs); UF_UI_write_listing_window(APPLY 中第一次创建\n); } else { cfData editedFeature-FeatureData(); UF_UI_write_listing_window(APPLY 中二次编辑\n); } PopulateAttributeValues(cfData); builder-SetFeatureData(cfData); Features::Feature *feature1 builder-CommitFeature(); builder-Destroy(); builder NULL; } catch (exception ex) { errorCode 1; if (builder ! NULL) { builder-Destroy(); builder NULL; } PolygonalHoleUI::theUI-NXMessageBox()-Show( Block Styler, NXOpen::NXMessageBox::DialogTypeError, ex.what()); }CreateCustomFeatureBuilder(editedFeature)传 NULL 表示新建传已有特征表示编辑这是框架约定。CommitFeature之后一定要DestroyBuilder否则内存泄漏在反复编辑时会累积。异常分支里也要销毁测试时漏掉这一步连续编辑十几次后 NX 直接卡死。3.5 属性创建与赋值属性是特征数据的骨架创建时用CustomAttributeCollection逐个建注意Property标志位std::vectorNXOpen::Features::CustomAttribute* attrs; NXOpen::Part *workPart(theSession-Parts()-Work()); NXOpen::Features::CustomAttributeCollection *attribCollection workPart-Features()-CustomAttributeCollection(); std::vectorNXOpen::Features::CustomAttribute::Property attributeProperties; // 表达式属性边数、直径 auto *sidesAttrib dynamic_castNXOpen::Features::CustomExpressionAttribute*( attribCollection-CreateCustomExpressionAttribute(m_polygonSides, attributeProperties)); attrs.push_back(sidesAttrib); // Tag 属性方向标记为必填输入 attributeProperties.push_back(NXOpen::Features::CustomAttribute::Property::PropertyMandatoryInput); auto *dirAttrib dynamic_castNXOpen::Features::CustomTagAttribute*( attribCollection-CreateCustomTagAttribute(m_holeDirection, attributeProperties)); attrs.push_back(dirAttrib); // Tag 属性目标体标记为引用目标体 attributeProperties.push_back(NXOpen::Features::CustomAttribute::PropertyIsReferencingTargetBody); auto *bodyAttrib dynamic_castNXOpen::Features::CustomTagAttribute*( attribCollection-CreateCustomTagAttribute(m_targetBody, attributeProperties)); attrs.push_back(bodyAttrib);PropertyMandatoryInput让 NX 在特征更新时校验该属性非空PropertyIsReferencingTargetBody告诉系统这个属性引用目标体影响更新顺序和依赖追踪。赋值时表达式属性要判断原表达式是否为空为空就新建系统表达式auto *sidesAttrib cfData-CustomExpressionAttributeByName(m_polygonSides); NXOpen::Expression* sidesExp sidesAttrib-Value(); if (sidesExp NULL) { sidesExp workPart-Expressions()-CreateSystemExpression(3); } sidesExp-SetValue(this-numSides-Value()); sidesAttrib-SetValue(sidesExp);Tag 属性从选择控件取对象取到唯一一个才赋值std::vectorNXOpen::TaggedObject* selected this-holeDirection-GetSelectedObjects(); if (selected.size() 1) { cfData-CustomTagAttributeByName(m_holeDirection)-SetValue(selected[0]); }4. Core 层拆解五个回调与几何追踪4.1 入口注册Core DLL 的入口函数里实例化核心类构造函数中完成回调注册PolygonalHoleCore *thePolygonalHoleCore new PolygonalHoleCore(); delete thePolygonalHoleCore;构造函数里拿到类句柄并挂五个 handlerNXOpen::Features::CustomFeatureClassManager *mgr theSession-CustomFeatureClassManager(); NXOpen::Features::CustomFeatureClass* cfClass mgr-GetClassFromName(NXOpen::CustomFeature::SamplePolygonalHole); cfClass-AddPreUpdateHandler(make_callback(PolygonalHoleCore::preUpdateCallBack)); cfClass-AddCreateFeatureGeometryHandler(make_callback(PolygonalHoleCore::createGeometryCallBack)); cfClass-AddModifyFeatureGeometryHandler(make_callback(PolygonalHoleCore::modifyGeometryCallBack)); cfClass-AddPostUpdateHandler(make_callback(PolygonalHoleCore::postUpdateCallBack)); cfClass-AddInformationHandler(make_callback(PolygonalHoleCore::informationCallBack));五个回调的声明都是静态成员签名固定static int preUpdateCallBack(NXOpen::Features::CustomFeaturePreUpdateEvent* event); static int createGeometryCallBack(NXOpen::Features::CustomFeatureCreateFeatureGeometryEvent* event); static int modifyGeometryCallBack(NXOpen::Features::CustomFeatureModifyFeatureGeometryEvent* event); static int postUpdateCallBack(NXOpen::Features::CustomFeaturePostUpdateEvent* event); static int informationCallBack(NXOpen::Features::CustomFeatureInformationEvent* event);4.2 回调职责与执行顺序测试时在 listing window 打日志能清楚看到顺序preUpdateCallBack→createGeometryCallBack或modifyGeometryCallBack→postUpdateCallBackinformationCallBack只在查看特征信息时触发。职责划分如下preUpdateCallBack更新前准备用 NX 函数建临时构造特征时在这里做。createGeometryCallBack首次创建几何产出体或曲线并登记追踪数据。modifyGeometryCallBack参数变化后重建几何逻辑通常和 create 共用。postUpdateCallBack收尾清理临时对象。informationCallBack设置特征信息面板显示的文字。informationCallBack里用SetInformation写自定义说明NXString string This is custom information \n \t\t Custom feature has not yet implemented custom code to show custom information; event-SetInformation(string); return 0;4.3 追踪数据让几何“活”下来这是 CustomFeature 最容易漏的一步。创建出的几何如果不登记追踪数据NX 不认为它属于这个特征后续编辑、删除、回放都会出问题。追踪对象统一塞进一个 vector最后AppendOutputTrackingDatastd::vectorFeatures::TrackingData* trackingObjectVector; // ... 逐个创建追踪数据 ... event-AppendOutputTrackingData(trackingObjectVector);PK 创建的实体用CreateTrackingDataForParasolidEntityNX 创建的对象用CreateTrackingDataForNXObject// PK 实体 Features::TrackingData *track1 event-CreateTrackingDataForParasolidEntity( (NXOpen::Features::CustomFeatureEvent::ParasolidId)output1); std::vectorNXOpen::NXObject* objVec1; objVec1.push_back(dynamic_castNXOpen::NXObject*(body)); track1-AddTagSourceEntities(objVec1); trackingObjectVector.push_back(track1); // NX 对象 Features::TrackingData *track2 event-CreateTrackingDataForNXObject(line1Tag); std::vectorNXOpen::NXObject* objVec2; objVec2.push_back(dynamic_castNXOpen::NXObject*(line1Tag)); track2-AddTagSourceEntities(objVec2); track2-SetIntApplicationKey(1); trackingObjectVector.push_back(track2);实体的面也要单独登记否则面级别的编辑和选择会失效for (int i 0; i numFaces; i) { Features::TrackingData *faceTrack event-CreateTrackingDataForParasolidEntity( (NXOpen::Features::CustomFeatureEvent::ParasolidId)faces[i]); faceTrack-AddIntSourceEntity(i 1); trackingObjectVector.push_back(faceTrack); }不参与显示的中间体用SetBodies排除std::vectorNXOpen::Features::CustomFeatureEvent::ParasolidId bodiesVector; bodiesVector.push_back((NXOpen::Features::CustomFeatureEvent::ParasolidId)output1); event-SetBodies(bodiesVector);4.4 用 NX 函数建构造特征如果几何是用UF_MODL_create_cyl1这类 NX 函数建的要在preUpdateCallBack里包成ConstructionFeatureData并设置显示std::vectorNXOpen::Features::ConstructionFeatureData* constructionVec; tag_t tCyFeatTag, g_tCylTag; UF_MODL_create_cyl1(..., tCyFeatTag); UF_MODL_ask_feat_body(tCyFeatTag, g_tCylTag); NXOpen::Features::Feature *nxFeat dynamic_castNXOpen::Features::Feature*(NXObjectManager::Get(tCyFeatTag)); NXOpen::Features::ConstructionFeatureData *outputFeatData event-CreateConstructionFeatureData(nxFeat); outputFeatData-SetShowInGraphicView(true); constructionVec.push_back(outputFeatData);SetShowInGraphicView(true)决定这个构造特征在图形窗口是否可见调试阶段建议开着方便确认几何位置对不对。5. 避坑清单五个真实翻车现场5.1 双击特征没反应历史树无编辑入口现象特征创建成功双击节点或曲线毫无反应。原因IsWithoutBody与实际产出不符只出曲线却写falseNX 进编辑态时找不到目标体抛异常。解决核对createGeometryCallBack里SetBodies的内容无实体一律改true重新部署 XML 后重启 NX。5.2 编辑时对话框打开的是另一个特征现象编辑多边形孔弹出的是 Block And Cylinder 的界面。原因XML 里两个CustomFeature的FeatureUILibrary写反了或者FeatureClass字符串和代码注册名不一致。解决逐字比对 XML 与GetClassFromName的参数注意命名空间前缀和大小写。5.3 属性读取返回空指针导致崩溃现象二次编辑时程序在dynamic_cast处崩溃。原因属性名拼写错误CustomTagAttributeByName返回 NULL直接Value()解引用。解决所有按名取属性的地方先判空可选属性用HasCustomAttribute加类型判断兜底。5.4 几何创建了但编辑后消失现象首次创建正常改参数后实体不见了。原因modifyGeometryCallBack里重建了几何但没重新登记追踪数据旧追踪数据指向已删除对象。解决create 和 modify 共用同一套追踪登记逻辑每次重建都重新AppendOutputTrackingData。5.5 反复编辑后 NX 卡顿甚至无响应现象连续编辑十几次NX 越来越慢。原因异常分支或正常分支漏掉builder-Destroy()Builder 对象累积。解决用 RAII 思路包一层确保任何路径退出前都销毁 Buildercatch 块里也要处理。6. 二次开发改造从测试 Demo 到可维护特征库测试记录里的代码是能跑通的最小闭环但要变成团队能长期维护的特征库得做几件事。第一把属性名抽成常量头文件UI 和 Core 共用避免字符串拼写错误——我见过太多因为一个字母导致属性读空的案例。第二createGeometryCallBack和modifyGeometryCallBack抽公共函数只保留入口差异减少两处逻辑漂移。第三追踪数据登记封装成模板函数PK 实体和 NX 对象各一个重载调用处只传对象和 key。验证改造是否成功我一般用三步新建特征看历史树节点和图标是否正确双击编辑改一个参数看几何是否按预期重建删除特征看关联几何是否干净移除。三步都过再考虑加第二个特征进同一个 XML。// 属性名常量UI/Core 共用 namespace PolygonalHoleAttr { constexpr const char* Sides m_polygonSides; constexpr const char* Diameter m_polygonDiameter; constexpr const char* Direction m_holeDirection; constexpr const char* Point m_polygonPoint; constexpr const char* TargetBody m_targetBody; }还有一个具体技巧调试阶段在五个回调里都往 listing window 打一行带时间戳的日志能快速定位是哪个回调没触发、触发顺序对不对。上线前把日志级别降下来只保留异常分支的输出。从那以后我每次新增一个 CustomFeature都强制先只注册回调打日志、不写几何确认五个回调按预期触发后再填几何逻辑这样能把配置问题和代码问题彻底分开。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站