1. 项目概述:中望CAD二次开发中的Overrule机制
在CAD二次开发领域,中望CAD作为国产CAD软件的领军者,其ZRX(ZWCAD Runtime eXtension)开发框架提供了丰富的API接口。其中Overrule机制是ZRX C++开发中最具威力的功能之一,它允许开发者在不修改原始代码的情况下,重定义CAD实体的默认行为。这就像给CAD系统安装了一个"行为拦截器",当系统执行特定操作时,开发者的自定义逻辑能够优先执行。
我在实际项目中曾遇到这样的需求:需要让所有新建的圆对象自动附加特定扩展数据,同时禁止用户删除某些关键图层上的实体。传统做法需要修改大量现有代码,而通过Overrule机制,仅用200行左右的核心代码就实现了这些功能,且完全不影响系统原有逻辑。这种非侵入式的开发方式,正是Overrule最吸引人的特性。
2. 核心原理与工作机制
2.1 Overrule的底层架构
中望CAD的Overrule机制基于观察者模式实现,其核心是三个关键组件:
- ZwcOverrule类:所有自定义Overrule的基类,提供虚函数接口供子类重写
- 实体代理系统:维护Overrule注册表,在实体操作时检查是否存在对应的Overrule
- 操作拦截点:系统预定义的可以被拦截的操作节点(如绘图、修改、查询等)
当CAD执行某个操作时(比如绘制一个圆),系统会依次:
- 检查该实体类型是否注册了Overrule
- 调用Overrule的对应虚函数(如worldDraw())
- 根据返回值决定是否继续执行默认操作
2.2 关键虚函数解析
在自定义Overrule类中,这些是最常用的可重写函数:
cpp复制virtual bool isApplicable(const ZwcRxObject* pOverruledSubject) const;
virtual bool worldDraw(ZwcGiWorldDraw* pWd, ZwcRxObject* pOverruledSubject);
virtual bool getGripPoints(ZwcGePoint3dArray& gripPoints, ZwcRxObject* pOverruledSubject);
virtual bool moveGripPointsAt(const ZwcIntArray& indices, const ZwcGeVector3d& offset);
以worldDraw为例,当需要自定义实体显示时,可以这样实现:
cpp复制bool MyOverrule::worldDraw(ZwcGiWorldDraw* pWd, ZwcRxObject* pSubject)
{
// 先执行自定义绘制逻辑
pWd->geometry().circle(center, radius, normal);
// 返回false表示不执行默认绘制
// 返回true则会继续执行原有绘制逻辑
return false;
}
3. 开发实战:创建自定义Overrule
3.1 环境准备与项目配置
首先确保安装中望CAD 2023及对应版本的ZRX SDK。在Visual Studio中需要配置:
- 包含路径:添加$(ZWCAD_DIR)\inc
- 库路径:添加$(ZWCAD_DIR)\lib
- 链接库:zwcad.lib、zrxapi.lib
创建新项目时选择Win32 DLL项目,在dllmain.cpp中实现必要的入口函数:
cpp复制ZWCAD_DEFINE_OVERULE(MyOverrule); // 声明Overrule类
extern "C" ZWCAD_PORT ZwcRx::AppRetCode
zcrxEntryPoint(ZwcRx::AppMsg msg, void* pkt)
{
switch (msg) {
case ZwcRx::kInitAppMsg:
MyOverrule::rxInit(); // 初始化Overrule
break;
case ZwcRx::kUnloadAppMsg:
MyOverrule::rxUnload(); // 清理Overrule
break;
}
return ZwcRx::kRetOK;
}
3.2 实现实体保护Overrule
下面是一个完整的实体保护示例,防止特定图层上的实体被删除:
cpp复制class ProtectOverrule : public ZwcOverrule {
public:
static ProtectOverrule* instance() {
static ProtectOverrule theInstance;
return &theInstance;
}
virtual bool isEraseAllowed(const ZwcRxObject* pSubject) override {
if (auto pEntity = ZwcDbEntity::cast(pSubject)) {
ZwcDbLayerTableRecord* pLayer;
if (pEntity->getLayer(pLayer) == eOk) {
if (pLayer->getName() == "PROTECTED_LAYER") {
zwcPrintf("禁止删除保护图层上的实体!\n");
return false; // 拦截删除操作
}
}
}
return true; // 其他情况允许删除
}
};
// 注册Overrule
void registerOverrules()
{
// 指定要拦截的操作类型
ZwcOverrule::setOverruling(ZwcOverrule::kEraseAllowed, true);
// 注册Overrule实例
ProtectOverrule::instance()->addOverrule(
ZwcDbEntity::desc(), // 应用于所有实体
ProtectOverrule::instance()
);
}
3.3 动态属性Overrule案例
这个案例演示如何为所有圆对象动态添加直径属性:
cpp复制class CircleDiameterOverrule : public ZwcOverrule {
public:
virtual bool isApplicable(const ZwcRxObject* pSubject) const override {
return pSubject->isKindOf(ZwcDbCircle::desc());
}
virtual bool getGripPoints(ZwcGePoint3dArray& grips,
ZwcRxObject* pSubject) override {
if (auto pCircle = ZwcDbCircle::cast(pSubject)) {
// 添加直径端点作为夹点
ZwcGePoint3d center = pCircle->center();
double radius = pCircle->radius();
grips.append(center + ZwcGeVector3d(radius, 0, 0));
grips.append(center - ZwcGeVector3d(radius, 0, 0));
return true;
}
return false;
}
virtual bool getCustomData(const ZwcRxObject* pSubject,
ZwcResBuf*& pData) override {
if (auto pCircle = ZwcDbCircle::cast(pSubject)) {
pData = new ZwcResBuf(ZwcResBuf::kRtReal, pCircle->radius() * 2);
pData->setNext(new ZwcResBuf(1001, "动态直径"));
return true;
}
return false;
}
};
4. 高级应用与性能优化
4.1 复合Overrule策略
当需要多个Overrule协同工作时,可以采用以下模式:
cpp复制class CompositeOverrule : public ZwcOverrule {
std::vector<ZwcOverrule*> m_overrules;
public:
void addOverrule(ZwcOverrule* pOverrule) {
m_overrules.push_back(pOverrule);
}
virtual bool worldDraw(/*...*/) override {
bool result = false;
for (auto pOverrule : m_overrules) {
if (pOverrule->isApplicable(pSubject)) {
result |= pOverrule->worldDraw(pWd, pSubject);
}
}
return result;
}
// 其他函数类似实现...
};
4.2 性能关键点
-
isApplicable优化:这个函数会被频繁调用,应该尽可能高效
cpp复制// 不好的实现 - 使用了字符串比较 bool isApplicable(const ZwcRxObject* pSubject) const { if (auto pEntity = ZwcDbEntity::cast(pSubject)) { ZwcString layer; pEntity->getLayerName(layer); return layer == "SPECIAL_LAYER"; // 字符串比较耗时 } return false; } // 优化实现 - 使用图层ID缓存 static ZwcDbObjectId m_specialLayerId; bool isApplicable(const ZwcRxObject* pSubject) const { if (auto pEntity = ZwcDbEntity::cast(pSubject)) { return pEntity->layerId() == m_specialLayerId; } return false; } -
避免过度拦截:只注册必要的操作类型
cpp复制// 只拦截需要的操作 ZwcOverrule::setOverruling(ZwcOverrule::kWorldDraw, true); ZwcOverrule::setOverruling(ZwcOverrule::kGetGripPoints, true); // 而不是简单地setAllOverruling(true);
5. 常见问题与调试技巧
5.1 典型问题排查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| Overrule未生效 | 未正确注册Overrule | 检查zcrxEntryPoint是否处理kInitAppMsg |
| CAD运行变慢 | isApplicable实现效率低 | 使用对象ID代替名称比较 |
| 某些操作被意外拦截 | 操作类型设置过宽 | 明确指定setOverruling的具体��作类型 |
| 内存泄漏 | 未正确释放ResBuf等资源 | 使用ZwcResBuf::release()释放资源 |
5.2 调试技巧实录
-
日志输出:在关键函数添加调试输出
cpp复制virtual bool worldDraw(ZwcGiWorldDraw* pWd, ZwcRxObject* pSubject) { zwcPrintf("Drawing entity %lx\n", pSubject->objectId().asOldId()); // ...其余代码 } -
对象检查器:使用ZwcRxObject::dump()输出对象信息
cpp复制void dumpEntityInfo(const ZwcDbEntity* pEntity) { ZwcRxObject* pRx = const_cast<ZwcRxObject*>(pEntity->queryX(ZwcRxObject::desc())); if (pRx) { pRx->dump(); pRx->release(); } } -
性能分析:使用ZwcProfiler测量关键函数耗时
cpp复制ZwcProfiler profiler("MyOverrule::worldDraw"); // ...被测量的代码
6. 实际项目经验分享
在最近的一个工厂布局项目中,我们使用Overrule实现了以下高级功能:
-
智能设备联动:当移动生产线设备时,自动调整相连的管道长度
cpp复制bool moveGripPointsAt(const ZwcIntArray& indices, const ZwcGeVector3d& offset) override { // 移动设备本体 bool res = baseMoveGripPointsAt(indices, offset); // 查找并调整相连管道 adjustConnectedPipes(pSubject, offset); return res; } -
实时面积计算:选择多个房间时动态显示总面积
cpp复制bool worldDraw(ZwcGiWorldDraw* pWd, ZwcRxObject* pSubject) { // 正常绘制 bool res = baseWorldDraw(pWd, pSubject); // 如果是选中状态 if (pWd->subEntityTraits().selectionFlags() & ZwcGiSubEntityTraits::kSelectionSelected) { // 计算并显示面积 displayTotalArea(pWd, pSubject); } return res; } -
一个关键教训:在Overrule中修改数据库要特别小心死锁。我们曾遇到这样的问题:
cpp复制bool getCustomData(const ZwcRxObject* pSubject, ZwcResBuf*& pData) override { // 错误做法:在只读操作中尝试修改数据库 ZwcDbEntity* pEditable = const_cast<ZwcDbEntity*>(ZwcDbEntity::cast(pSubject)); pEditable->setColorIndex(1); // 可能导致死锁 // 正确做法:通过队列延迟处理 PostModifyMessage(pSubject->objectId(), COLOR_CHANGE); return false; }
Overrule机制虽然强大,但需要遵循"最小干预"原则。我们的经验法则是:能用普通实体替代就不用Overrule,能用单个Overrule解决的问题就不要用多个。当确实需要复杂交互时,建议采用"轻量Overrule+事件处理"的组合方案,这样既保持了灵活性,又避免了性能问题。
