我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

中望CAD二次开发实战:ObjectARX迁移与QT界面集成

中望CAD二次开发实战:ObjectARX迁移与QT界面集成 简介这是一份非常简单但完整可用的中望CAD二次开发示例工程基于C与QT对话框技术实现核心功能是读取扩展字典适合已经具备C基础、想了解CAD插件开发流程的开发者。工程针对VS2015、QT 5.12.9和ZWCAD 2022做了完整配置代码结构清晰既可以在中望CAD中加载运行也容易迁移到AutoCAD中使用。压缩包共273个文件约为206MB主要文件类型包括dll动态库、lib依赖库、h/cpp源码、ui界面设计文件以及工程配置文档同时附带了pdb调试信息、中间编译产物和构建日志便于对照排查和逐步学习。示例项目虽不复杂却涉及QT界面创建、命令注册、扩展字典读写等关键环节配合目录中明确的模块划分适合初学者拆解真实插件工程读者可从零开始对照目录结构与工程配置进行学习。已有2804人浏览学习适用于快速上手和作为后续开发的参考模板。 中望CAD二次开发这块圈子里聊的人不少但真正把方案落到日常生产里、每天都在用的其实不多。年前我们团队接到一个任务把原来基于ObjectARX的一批自动化工具迁移到中望CAD环境同时要新补一套操作界面。选型一开始就定了C和QT——原因很简单中望CAD底层的ZRX接口就是C的性能和可控性没得挑而QT做参数面板、文件选择、进度提示这类界面工作开发效率明显高于手写MFC。项目本身不大也没有上什么花哨的架构但发布到现在稳定跑了大半年可以总结成一句话简单但有效可用。1. 方案选型为什么落在这条技术栈上1.1 国产CAD插件开发的主流选择中望CAD目前提供好几条二次开发路线LISP脚本、.NET接口、还有底层的ZRXZWCAD Runtime eXtension。每条路都有各自擅长的场景但做工程自动化工具时选择往往不是看哪条路最潮而是看现有团队资产和性能需求。LISP上手最快适合写几十行的批处理脚本但做不了复杂交互界面对实体数据的大规模遍历和计算也偏慢。.NET开发效率高程序集部署也方便不过CAD从启动到加载托管程序集的过程中版本绑定和依赖冲突出现得比较频繁尤其做底层图形数据操作时性能还是隔了一层。最后剩下C配合ZRX这套接口和经典ARX高度兼容很多以前为ARX写的代码几乎可以平移过来底层数据库操作、实体遍历、事务控制都很直接。这个项目之所以选择C路线最重要的一点是团队里有现成的ObjectARX代码遗产。你不必把每条业务逻辑重新构思一遍重点只是适配中望CAD的接口差异这比从零开始省太多时间。另外标题里强调“简单但有效”说明需求收敛得非常好我们不去做一个CAD平台级框架只实现最核心的几个功能点然后把稳定性做到位。对生产工具来说功能全不如跑得稳。1.2 QT界面在整个方案中的定位界面这一层常见的做法是用CAD自己的对话框或者MFC实现。但我们的场景需要频繁选择外部文件、录入参数、展示导入导出进度用QT来做这类表单界面非常顺手信号槽机制让界面和业务逻辑的耦合度很低。更重要的是QT的布局控件和样式表能快速做出一个专业级的参数面板不用花大量时间在控件绘制上。在这个项目里QT界面只做三件事选择文件、填参数、看进度。不追求把所有功能塞进CAD的Ribbon面板或者停靠窗口因为做可停靠面板需要和CAD的窗口系统深度集成维护成本立刻上一个台阶。独立的对话框方案对用户来说操作路径短对我们来说也更容易控制边界。我们最终采用的是一个可重复弹出的非模态对话框CAD命令行保持可交互配合后面的定时轮询逻辑来驱动QT事件循环整体效果相当顺手。有一点必须在方案阶段就明确QT运行在CAD进程中本质上是在别人的消息循环里跑自己的事件系统。这个组合天生有一个“坑”后面我会专门展开讲也是这个项目里最值得记录的一段排查经验。2. 环境搭建与核心开发细节2.1 ZRX SDK和QT的版本匹配开发环境的第一步是拿到匹配的SDK。中望CAD的ZRX开发包通常随安装目录提供也可以在官网开发者专区下载到对应版本的SDK。用的时候注意两点一是SDK版本和要加载的CAD版本必须严格对应用2024的SDK编出来的插件大概率加载不到2022里二是编译平台要和安装的CAD位数一致现在主流是64位那就一律编x64。QT版本建议选用5.15 LTS或者更新的长期支持版本编译器使用MSVC。编译插件时需要把头文件路径分别指向ZRX SDK的inc目录和QT的include目录链接器里的附加依赖项加上ZRX的lib文件rxapi.lib、acdb.lib这些同时确保QT的lib路径在搜索范围内。运行库建议统一使用多线程DLL/MD不要混用静态和动态运行时否则很容易在加载时出现运行时库冲突。这里值得多说一句中望CAD本身可能会带上一些第三方动态库如果QT的DLL命名冲突插件运行时可能加载到CAD自带的版本导致莫名其妙的崩溃。最稳的做法是把QT以静态库方式编进插件但这会显著增大文件体积且编译时间变长。我们实践下来动态链接QT然后把所有依赖DLL放在插件所在目录并在代码里主动指定插件目录为QT库搜索路径这套方案简单可靠至少半年来没出过加载问题。2.2 在CAD线程中驱动QT界面这是整个项目最核心的坑也是让很多人放弃在中望CAD里用QT做界面的原因。一开始我们的代码很直接在命令回调里new一个QDialog然后调用exec()结果发现对话框虽然能弹出来但CAD的整个命令行状态直接卡住任何后续CAD操作都无法响应甚至有时候对话框上按钮点了没反应。原因其实不复杂。CAD的命令回调是同步执行的回调函数不返回CAD就不会继续处理自己的消息和命令队列。而QDialog::exec()内部创建了一个嵌套事件循环它自己在这个循环里不断处理QT事件但CAD的消息没有得到同等照顾两边互相等表现出来就是界面卡死或者操作无响应。我们后来采用了一个比较稳定的模式在CAD主线程中显示非模态对话框然后在一个局部事件循环里用定时器持续调用QCoreApplication::processEvents()同时也处理CAD的Windows消息这样两边的事件都能被及时处理。核心代码大致是这样void zxBatchImportCmd() { MyDialog dlg; QEventLoop loop; QObject::connect(dlg, QDialog::finished, loop, QEventLoop::quit); QTimer timer; QObject::connect(timer, QTimer::timeout, []() { MSG msg; while (PeekMessage(msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(msg); DispatchMessage(msg); } QCoreApplication::processEvents(); }); timer.start(50); dlg.show(); loop.exec(); timer.stop(); }这段代码实际用了很久稳定性很高。需要注意的细节是对话框关闭后一定要把timer停下来否则它还会一直触发消息处理白白消耗CPU。2.3 QString、TCHAR和CAD字符串的转换中望CAD的ZRX接口在Windows下默认使用TCHAR字符类型和QT的QString是两套体系。中文环境下最典型的坑就是命令行输出或文件名路径出现乱码。我们封装了一个专门处理QString和TCHAR*互转的小函数static QString TcharToQString(const TCHAR* str) { #ifdef UNICODE return QString::fromWCharArray(str); #else return QString::fromLocal8Bit(str); #endif } static std::basic_stringTCHAR QStringToTchar(const QString str) { #ifdef UNICODE return str.toStdWString(); #else return str.toLocal8Bit().toStdString(); #endif }这套封装后来被大量复用。凡是涉及acutPrintf输出、读取实体文字内容、或者向CAD命令行传递路径的地方都统一走这两个函数没有再出现乱码问题。3. 实操过程与核心环节实现3.1 最小ZRX插件骨架ZRX插件的基本结构和传统ARX几乎一致核心是继承AcRxArxApp的应用类加上入口宏。一个最小可加载的插件骨架大致如下#include zrx.h #include dbents.h #include aced.h class ZxApp : public AcRxArxApp { public: ZxApp() : AcRxArxApp() {} virtual AcRx::AppRetCode On_kInitAppMsg(void* pkt) { AcRx::AppRetCode ret AcRxArxApp::On_kInitAppMsg(pkt); return ret; } virtual AcRx::AppRetCode On_kUnloadAppMsg(void* pkt) { return AcRxArxApp::On_kUnloadAppMsg(pkt); } }; IMPLEMENT_ARX_ENTRYPOINT(ZxApp) static void zxBatchImportCmd() { // 弹出QT界面并驱动事件循环 QDialog dlg; // ... }命令zxBatchImportCmd编译后会注册成CAD命令ZXBATCHIMPORT用户在命令行输入ZXBATCHIMPORT就能触发。中望CAD里用APPLOAD加载生成的.zrx文件出现“加载成功”提示后即可运行。整个骨架非常轻适合作为后续所有二次开发命令的通用模板。3.2 核心业务批量导入数据并生成实体这个项目里最常用的一个业务是从外部CSV文件里读取坐标和半径批量生成圆同时在圆心处加一个指定图层的小标记。整体流程说白了就三步选文件、解析数据、写入模型空间。选文件直接用QT的QFileDialog没有任何额外的CAD接口依赖QString filePath QFileDialog::getOpenFileName( nullptr, QStringLiteral(选择数据文件), QStringLiteral(D:/data), QStringLiteral(文本文件 (*.txt *.csv)));拿到文件路径后按行解析逐行读出x、y、r三个数值校验合法性后创建AcDbCircle实体。写入模型空间的固定写法如下AcDbCircle* pCircle new AcDbCircle( AcGePoint3d(x, y, 0.0), AcGeVector3d::kZAxis, r); AcDbBlockTable* pTable nullptr; acdbHostApplicationServices()-workingDatabase() -getSymbolTable(pTable, AcDb::kForRead); AcDbBlockTableRecord* pRecord nullptr; pTable-getAt(ACDB_MODEL_SPACE, pRecord, AcDb::kForWrite); pRecord-appendAcDbEntity(pCircle); pCircle-close(); pRecord-close(); pTable-close();有个非常实际的性能经验批量生成上千个实体时不要在循环里每次重新查一次块表记录正确的做法是循环开始前打开一次模型空间块表记录并保持写入状态循环里只做new实体、appendAcDbEntity、close这三件事最后再统一关闭块表记录。实测同样导入5000个圆优化后耗时只有原来的不到三分之一。3.3 从图纸中批量提取数据有导入就有导出。另一条常用业务是把当前图纸里所有指定图层的圆导出成CSV方便后续在外部程序里做数据统计。这个场景用到选择集过滤只选圆实体ads_name ssname; resbuf* rbFilter acutNewRb(RTDXF0); rbFilter-resval.rstring _T(CIRCLE); acedSSGet(_T(X), nullptr, nullptr, rbFilter, ssname); acutRelRb(rbFilter);然后遍历选择集用acdbOpenAcDbObject打开实体通过AcDbCircle::center()和radius()取数据最后用QT的QTextStream逐行写文件。过程中给用户一个进度提示每处理100个实体输出一条进度信息到命令行。这里有个小经验遍历结束后记得用acedSSFree释放选择集很多刚上手的同学会忽略长时间运行后会产生对象句柄泄漏。4. 常见问题与排查技巧实录4.1 对话框能打开但CAD整个卡住这个前面已经提过核心是不要直接QDialog::exec()。我们遇到过用户反馈“点命令后整个CAD像死了一样”其实就是命令回调没返回、事件循环互相嵌套导致的。换成定时器processEvents()的方案后问题彻底消失。如果项目确实需要模态效果可以在命令回调里先用acedCommand把CAD的命令行状态机“挂起”但这样处理起来要小心每个分支都恢复状态复杂度高非必要不推荐。4.2 插件加载失败版本、位数和依赖DLL插件加载失败是排查最多的问题集中在这几类现象可能原因解决办法加载提示版本不匹配SDK和CAD版本不一致换成完全对应的ZRX SDK加载后立刻崩溃编译位数和CAD位数不一致统一x64或x86提示找不到QT DLL运行环境的QT依赖缺失把QT的DLL和plugins目录放一起提示缺少platform插件缺qwindows.dll拷贝Qt的platforms目录并设置路径这条特别提醒QT程序在Windows下需要platforms目录里的qwindows.dll否则连QDialog都创建不了。发布时要把这个目录整体拷到插件旁边并在代码启动早期调用QCoreApplication::addLibraryPath()指向插件所在目录可以避免很多环境问题。4.3 生成实体后图形不刷新有时候数据明明写进去了但屏幕上看不到新实体。这通常不是逻辑错误而是视口没有主动刷新。调用一次acedUpdateDisplay()或者用acedCommand执行一次REGEN就能解决。还有个小技巧批量操作结束后统一刷新一次不要每生成一个实体就刷一次否则大图会卡得非常明显。4.4 交付前一定要做的事情这个项目发布阶段我踩过不少坑最值得说的三点。一是日志不能省在插件目录写一个简单的运行日志记录每次导入导出的文件路径、实体数量和耗时用户报问题的时候能快速定位。二是做一次“干净环境验证”找一台没有安装开发工具的机器把插件和QT依赖一起拷过去跑一遍完整流程。这一步能发现大量隐性问题。三是小步验证先拿100条数据跑通全流程再放全量数据不要一上来就处理几万个实体出了问题很难判断是解析逻辑还是CAD接口调用导致的。这个项目真正让我有体会的是二次开发很多时候不是拼技术难度而是拼怎么在复杂环境里把一条路径彻底打通。C、ZRX、QT这三样东西单拎出来都不新鲜但放到中望CAD的环境里每一个环节都可能踩到意想不到的坑。尤其那个QT事件循环的问题网上资料很少我们也是反复试错才找到了稳定的模式。最后分享一个发布阶段的小技巧所有依赖DLL不要直接放到CAD安装目录而是放在插件自己的子目录里然后用相对路径去加载这样即使用户后续升级CAD版本或者清理安装目录插件也不会因为依赖项被误删而失效。本文还有配套的精品资源点击获取
返回列表