第8章 自定义对象

合集下载
相关主题
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

第8章
本章简介 8
自定义对象
†自定义对象的概念。

†从 AcDbObject 派生对象。

†从 AcDbEntity 派生自定义实体。


学习要点
† 了解自定义对象的概念及其的应用。

† 掌握从 AcDbObject 派生对象。

† 掌握自定义实体的创建方法。


我们在前面介绍了通过扩充数据方式来存储扩充数据,虽然能满足一定的工程需求,但 是由于最终的扩展数据通过结果缓冲链表的方式存储,缺少面向对象特性,在处理的时候比 较繁琐, 我们完成可以定义自己的类来封装数据, 此种情况下我们需要通 AcDbObject 派生数 据库对象;另外,AutoCAD 是一个通用的 CAD 平台,提供如点、线等通用的对象类型,我 们可以针对行业特征派生自己的实体,如定义螺栓类、管道类等,这些派生的实体除了具有 自己的几何形体外,还包含自己所有的一些数据,如管道的管径、材质等属性。

本章我们介 绍一下自定义数据库对象的概念和方法,用户可以根据自己的实际需求派生一套面向行业的 对象类型。


8.1
自定义对象
在介绍自定义对象之前, 我们需要对 AutoCAD 中数据库对象的层次关系有所了解了 解,这有助于我们理解后面的实际应操作,AutoCAD 中数据库对象的层次关系如图 8-1 所示。


1
PDF 文件使用 "pdfFactory Pro" 试用版本创建


图 8-1AutoCAD 中数据库对象的层次关系 从图 8-1 我们看出所有的数据库对象类都派生自 AcRxObject,该类是所有数据库对 象的基类,它主要实现对象运行时类型识别机制,提供一些用于类型识别的重要函数,它 提供的函数主要有一下几个: n desc() : 静态成员函数,返回指定类的类描述符对象。

n cast(): 返回指定类型的对象。

n isKindOf(): 用于判断对象是否属于指定类或者派生类。

n isA() :返回未知类对象的类描述符对象。

我们在介绍实体操作的时候讲过如何使用这些函数, 这里我们需要在这里介绍这些函 数的实现机制,从 AcRxObject 派生的类都包含一个相应的类描述符对象,用 AcRxClass 类表示, 它包含了运行使类型的识别信息,AcRxObject 的派生类包含一个指向 AcRxClass 对象的指针(gpDesc),可以通过 AcRxObject::desc()获取这个 AcRxClass 对象指针,而 AcRxClass 对象包含一个指向其父对象 AcRxClass 的指针, 这样构成了类的运行时类层次 表,如图 8-2,我们可以调用 AcRxObject::isKindOf()来判断对象是否是从某个类派生 出来。


图 8-2 行时类层次表 在派生自定义类中要实现运行类的识别信息, 也就是要重载上面提到的 desc()、 isKindOf()
2
PDF 文件使用 "pdfFactory Pro" 试用版本创建


等 函 数 , 这 可 以 通 过 ObjectARX 提 供 的 宏 来 实 现 , 通 过 使 用 类 声 明 宏 ACRX_DECLARE_MEMBERS(CLASS_NAME)可以声明 desc(), cast(), isA()函数,代码如 下: class CMyClass : public AcRxObject { public: ACRX_DECLARE_MEMBERS(CMyClass); …. } 该宏经过编译预处理后被扩展成一下代码: virtual AcRxClass* isA() const; static AcRxClass* gpDesc; static AcRxClass* desc(); static CMyClass * cast(const AcRxObject* inPtr) { return ((inPtr == 0) || !inPtr->isKindOf(CMyClass::desc())) ? 0 : (CMyClass *)inPtr; }; static void rxInit(); 自定义类的静态成员函数 rxInit()用于实现以下初始化操作: n 注册自定义类。

n 创建类的描述对象。

n 将类描述对象添加到类的描述词典中。

在应用程序的初始化函数中必须调用自定义类的静态成员函数 rxInit()来实现自定义类的 初始化,然后调用全局函数 acrxBuildClassHierarchy 把该类添加到 ACRX 运行类层次表中。

另外在应用程序的卸载时需要调用 deleteAcRxClass()把该类从 ACRX 运行类层次表中删 除,应用程序的初始化代码如下: extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt) { switch (msg) { case AcRx::kInitAppMsg: acrxDynamicLinker->unlockApplication(pkt); // 自定义类的初始化 CMyClass::rxInit(); // 把该类添加到 ACRX 运行类层次表中 acrxBuildClassHierarchy(); break; case AcRx::kUnloadAppMsg: // 该类从 ACRX 运行类层次表中删除 deleteAcRxClass(CMyClass::desc()); } return AcRx::kRetOK;
3
PDF 文件使用 "pdfFactory Pro" 试用版本创建


} 所有永久或者临时的图形对象都实现可绘制接口,封装该接口的对象可以通过火绘制 API 完成绘制, 可显示的对象派生自 AcGiDrawable 类, 该类实现图形系统 (GS) 绘制协议。

AcDbObject 类执行文件操作协议, 从该类派生的对象通过重载文件操作函数可以被保存 为 DWG 或 DXF 文件,或者从 DWG 或 DXF 文件读入。

AcDbEntity 类是实体类,派生自 AcDbObject 类,从该类派生的对象除了可以支持文件 操作外,还可以通过重载绘制函数来按照开发者的要求绘制图形。


8.2 从 AcDbObject 派生
从 AcDbObject 类派生的子类可以支持文件操作,即对象可以从 DWG 或者 DXF 文件中 读写, 也就是可以保存到 DWG 或者 DXF 文件中, 要实现文件读写操作派生类必须重载以下 四个函数: Acad::ErrorStatus dwgInFields(AcDbDwgFiler *filer); Acad::ErrorStatus dwgOutFieds(AcDbDwgFiler *filer); Acad::ErrorStatus dxfInFieds(AcDbDxfFiler *filer); Acad::ErrorStatus dxfOutFieds(AcDbDxfFiler *filer); 以上函数的参数是文件操作类 AcDbDwgFiler 或 AcDbDxfFiler 指针,文件操作类是一个 工具类,用于数据库对象的读写读写, ObjectARX 通过枚举类型 AcDb::FilerType 来检查文件 操方式和类型。

例如当调用 AutoCAD 的 SAVE 命令保存文件时,会调用数据库对象的 dwgOutFieds 函 数, 此时使用 kFileFiler 枚举类型; 而当使用 WBLOCK 命令时, 同样调用 dwgOutFieds 函数, 但使用的枚举类型为 kWblockCloneFiler 和 kIdXlateFiler, 如果调用 UNDO 命令取消操作时候, 会调用数据库对象的 dwgInFields 函数,使用的枚举类型是 kUndoFiler。

向文件操作类对象写入数据的过程中,不需要执行错误检查,文件操作类都有一个成员 函数 getFilerStatus()用于返回类的状态,有时候开发者需要检查文件操作类对象的状态。

在 自 定 义 类 中 重 载 文 件 操 作 函 数 时 , 必 须 首 次 调 用 assertReadEnabled() 或 assertWriteEnabled()函数来检查对象处于正确的打开状态, 然后调用自定义类父类的同名函数 来提供对父类数据的重载。

对于 DWG 文件操作函数 dwgInFields 和 dwgOutFieds,必须按照相同的顺序进行数据的 读写操作,否则派生类数据可能发生混乱。

文件操作类对象可以调用成员函数 readItem()和 writeItem()来读写数据,实际上这 两个函数会被所有支持的数据类型重载,另外还可以调用一些指定了数据类型的读写函数, 如 writeInt32 ) 这些函数在被调用时会自动转换参数的数据类型而忽略数据的实际类型, ( 等, 例如自定义类中包含整型数据,则可以调用 readInt32()和 writeInt32()进行相应的读写操 作。

Acad::ErrorStatus CPipeAttribute::dwgOutFields (AcDbDwgFiler *pFiler) const { // assertReadEnabled () ; //----- Save parent class information first. Acad::ErrorStatus es =AcDbObject::dwgOutFields (pFiler) ; if ( es != Acad::eOk ) return (es) ;
4
PDF 文件使用 "pdfFactory Pro" 试用版本创建


//----- Object version number needs to be saved first if ( (es =pFiler->writeUInt32 (CPipeAttribute::kCurrentVersionNumber)) != Acad::eOk ) return (es) ; ///写入数据开始 pFiler->writeItem(m_dRadius); pFiler->writeItem(m_dThickness); pFiler->writeItem(m_dDeep); pFiler->writeString(m_cMaterial); //写入数据结束 return (pFiler->filerStatus ()) ; }
Acad::ErrorStatus CPipeAttribute::dwgInFields (AcDbDwgFiler *pFiler) { assertWriteEnabled () ; //----- Read parent class information first. Acad::ErrorStatus es =AcDbObject::dwgInFields (pFiler) ; if ( es != Acad::eOk ) return (es) ; //----- Object version number needs to be read first Adesk::UInt32 version =0 ; if ( (es =pFiler->readUInt32 (&version)) != Acad::eOk ) return (es) ; if ( version > CPipeAttribute::kCurrentVersionNumber ) return (Acad::eMakeMeProxy) ; //读取数据开始 pFiler->readItem(&m_dRadius); pFiler->readItem(&m_dThickness); pFiler->readItem(&m_dDeep); TCHAR *pString=NULL; pFiler->readString(&pString); _tcscpy(m_cMaterial,pString); // 读取数据结束 return (pFiler->filerStatus ()) ; } 对象可以用 DXF 格式来表示,DXF 格式由成对的 DXF 组码和数据构成,组码对应一种 指定的数据类型, 当定义自定义类对象的 DXF 格式时, 函数读写的的一组数据必须是派生类 的数据标记,这个数据标记的 DXF 组码是 100(AcDb::kDxfSubclass),然后是类名的字符串。

对于 DXF 文件操作函数 dxfInFieds 和 dxfOutFieds 也通过文件操作类对象 AcDbDxfFiler 的成员函数 readItem()和 writeItem()来读写数据,用户可以决定数据组是按照一定的顺 序读写还是无顺序读写。

如果程序中允许无顺序读写,则在重载函数中必须使用 switch 语句 来选择于 DXF 组码相应的操作, 无顺序读写通常用于那些包含不变的数据域的对象, 而另外 包含可变长度的数组和结构的对象往往采用顺序读写。


5
PDF 文件使用 "pdfFactory Pro" 试用版本创建


Acad::ErrorStatus CPipeAttribute::dxfOutFields (AcDbDxfFiler *pFiler) const { // 检查对象处于正确的打开状态 assertReadEnabled () ; //父类数据的重载 Acad::ErrorStatus es =AcDbObject::dxfOutFields (pFiler) ; if ( es != Acad::eOk ) return (es) ; es =pFiler->writeItem (AcDb::kDxfSubclass, _RXST("CPipeAttribute")) ; if ( es != Acad::eOk ) return (es) ; //----- Object version number needs to be saved first if ( (es =pFiler->writeUInt32 (kDxfInt32, CPipeAttribute::kCurrentVersionNumber)) != Acad::eOk ) return (es) ; ////写入数据开始 pFiler->writeItem(AcDb::kDxfReal , m_dRadius); pFiler->writeItem(AcDb::kDxfReal+1 , m_dThickness); pFiler->writeItem(AcDb::kDxfReal+2 , m_dDeep); pFiler->writeItem(AcDb::kDxfText ,m_cMaterial); ////写入数据结束 return (pFiler->filerStatus ()) ; } Acad::ErrorStatus CPipeAttribute::dxfInFields (AcDbDxfFiler *pFiler) { assertWriteEnabled () ; //----- Read parent class information first. Acad::ErrorStatus es =AcDbObject::dxfInFields (pFiler) ; if ( es != Acad::eOk || !pFiler->atSubclassData (_RXST("CPipeAttribute")) ) return (pFiler->filerStatus ()) ; //----- Object version number needs to be read first struct resbuf rb ; pFiler->readItem (&rb) ; if ( rb.restype != AcDb::kDxfInt32 ) { pFiler->pushBackItem () ; pFiler->setError (Acad::eInvalidDxfCode, _RXST("\nError: expected group code %d (version #)"), AcDb::kDxfInt32) ; return (pFiler->filerStatus ()) ; } Adesk::UInt32 version =(Adesk::UInt32)rb.resval.rlong ; if ( version > CPipeAttribute::kCurrentVersionNumber ) return (Acad::eMakeMeProxy) ; while ( es == Acad::eOk && (es =pFiler->readResBuf (&rb)) == Acad::eOk ) {
6
PDF 文件使用 "pdfFactory Pro" 试用版本创建


switch ( rb.restype ) { // 从 DXF 中读取数据 case AcDb::kDxfReal: m_dRadius =rb.resval.rreal ; break ; case AcDb::kDxfReal+1: m_dThickness =rb.resval.rreal ; break ; case AcDb::kDxfReal+2: m_dDeep =rb.resval.rreal ; break ; case AcDb::kDxfText: _tcscpy(m_cMaterial,rb.resval.rstring); break ; default: pFiler->pushBackItem () ; es =Acad::eEndOfFile ; break ; } } if ( es != Acad::eEndOfFile ) return (Acad::eInvalidResBuf) ; return (pFiler->filerStatus ()) ; }
AutoCAD 为撤销操作(UNDO)提供了两中基本的方法,一种是系统默认的自动撤销操 作机制,另外一种是部分撤销操作机制。

其中前者通过系统调用函数 dwgOutFields(采用 kUndoFiler 为参数)来复制对象的全部状态来实现,而部分操作机制需要程序对特殊的修改 来读写指定的信息,自动撤销操作通过 assertWriteEnabled()函数来实现。

自定义类中任何修改函数都必须调用函数 assertWriteEnabled , () 用于检查对象是否是用 写的模式打开,当该函数被调用时,首先检查参数 recordModified,如果 recordModified 的值 为 Adesk::kFalse,则不执行任何撤销操作,如果 recordModified 的值为 Adesk::kTrue,则检查 autoUndo 参数,如果参数 autoUndo 为 Adesk::kTrue,则 AutoCAD 将记录对象的状态以便执 行撤销操作,当对象的修改操作完成并关闭对象,操作对象的全部状态将被保存到一个撤销 操作文件中,如果这时的用 UNDO 命令,AutoCAD 调用对象的 dwgInFields()函数把这个 撤销操作文件的内容读入到数据库中。

如果想记录对象的部分状态,自定义类中修改函数都必须在调用函数 assertWriteEnabled ()时将参数 autoUndo 设为 Adesk::kFalse,然后调用 undoFiler::writeItem()(或其他的 undoFiler::writeXXX()函数)把相关的信息保存到撤销操作文件中,如果这时执行 UNDO 操 作,AutoCAD 会调用 applyPartialUndo()读入撤销操作文件中保存的信息。

执行撤销操作时,系统记录当前的状态为重做(REDO)操作做准备,重做操作使用和
7
PDF 文件使用 "pdfFactory Pro" 试用版本创建


撤销做作相同的文件机制, 即调用 dwgOutFields()函数来记录对象的状态, 不需要编写额外的 代码,如果自定义对象的修改函数实现了部分撤销操作,那么在执行撤销操作的同时为重做 操作做记录,这个过程通常调用相应的 set()函数来事项,它会调用 assertWriteEnabled()来 记录数据。

下面我们结合工程实际说明如何从 AcDbObject 派生自己的类并在程序中使用这个派生 的类。

在该例子中我们将为管道定义一个属性类,用于存储管道的属性信息,所定义的属性 类包含以下数据: 表 8-1 管道的属性信息 属性 管径 壁厚 埋深 材质 数据类型 double double double TCHAR
创建一个新的工程 CH081,通过 ObjectARX 工具条的按钮 (第二个) 启动 Autodesk class , Explorer,选择工程 CH081,右键单击,在弹出的右键菜单中选择 Add an ObjectDBX Custom Object…,如图 8-3 所示。


图 8-3 添加自定义类 在弹出的对话框中,从 AcDbObject 派生管道的属性类 CPipeAttribute,如图 8-4.
图 8-4 管道的属性类 在协议页(Protocols) ,选中 DXF Protocol,如图 8-5,其他采用缺省设置。


8
PDF 文件使用 "pdfFactory Pro" 试用版本创建


图 8-5 重载 DXF 协议 在类定义中添加成员变量: //管径 double m_dRidus; //壁厚 double m_dThickness; //埋深 double m_dDeep; //材质 TCHAR m_cMaterial[128]; 在 CPipeAttribute 类的构造函数中完成数据的初始化,代码如下: CPipeAttribute::CPipeAttribute () : AcDbObject () { m_dRidus = 120.0; m_dThickness = 10.0; m_dDeep = 2.4; _tcscpy(m_cMaterial,_T("水泥管")); } CPipeAttribute 类实现文件读写操作的四个重载函数已经在前面列出,ObjectARX 向导 已经给出了缺省函数的代码,我们只需要添加用户自定义部分的数据。

CPipeAttribute 完成以后, 需要定义一个命令来使用这个属性类, 在工程中添加命令, 将 CPipeAttribute 对象实例作为扩展数据存储到实体的扩展字典中,代码如下: static void CSCH081AddAttribute(void) { AcDbObjectId dictObjId,eId, attId; AcDbDictionary* pDict; //选择管道(多义线) ads_name en; ads_point pt;
9
PDF 文件使用 "pdfFactory Pro" 试用版本创建


if ( {
acedEntSel(_T("\n 选择管道(多义线): "), en, pt)!= RTNORM) acutPrintf(_T("\n 选择失败,退出: ")); return ;
} // 打开对象 acdbGetObjectId(eId, en); AcDbEntity * pEnt; acdbOpenObject(pEnt, eId, AcDb::kForWrite); if(!pEnt->isKindOf (AcDbPolyline::desc ())) { acutPrintf(_T("\n 选择的不是管道(多义线) ,退出: " )); return ; } // 判断实体的扩展词典是否创建,如果没有则创建 dictObjId = pEnt->extensionDictionary(); if( dictObjId == AcDbObjectId::kNull ) { pEnt->createExtensionDictionary(); } // 获取实体的扩展词典 dictObjId = pEnt->extensionDictionary(); pEnt->close(); // 判断词典中的属性是否创建 CPipeAttribute* pAttribute; acdbOpenObject(pDict, dictObjId, AcDb::kForWrite); pDict->getAt (_T("属性"),attId); if(attId!= AcDbObjectId::kNull )//如果已经创建则输出数据 { acdbOpenObject(pAttribute, attId, AcDb::kForRead); acutPrintf(_T("\n 管径:%4.2f " ),pAttribute->m_dRadius); acutPrintf(_T("\n 壁厚:%4.2f " ),pAttribute->m_dThickness ); acutPrintf(_T("\n 埋深:%4.2f " ),pAttribute->m_dDeep ); acutPrintf(_T("\n 材质:%s " ),pAttribute->m_cMaterial ); } else { //没有则创建属性 pAttribute = new CPipeAttribute(); pDict->setAt(_T("属性"), pAttribute, attId); }
10
PDF 文件使用 "pdfFactory Pro" 试用版本创建


//关闭对象
pDict->close();
pAttribute->close();
}
将工程编译、运行可以看到CPipeAttribute能够将属性数据保存在多义线的扩展字典中。

8.3从AcDbEntity派生
AcDbEntity是从AcDbObject派生而来,因此AcDbEntity的派生类必须重载AcDbObject 类所有必须重载的函数,如文件操作函数,然后根据需要重载AcDbObject类的其他虚函数和AcDbEntity类的函数。

AutoCAD利用worldDraw()和viewportDraw()函数来显示实体,对任何由AcDbEntity派生的类必须重载worldDraw()函数,而对于viewportDraw()函数来说,则是可选的,下面是函数原型:
Virtual Adesk::Boolean AcDbEntity::worldDraw(AcGiWorldDraw *pWd);
Virtual void AcDbEntity::viewportDraw(AcGiViewportDraw *pVd);
当AutoCAD需要重新生成图形以显示实体时,会用以下方式调用worldDraw()和viewportDraw()函数:
if(!entity->worldDraw(pWd))
for(每一个相关视口)
entity->viewportDraw(void);
函数worldDraw()用于绘制实体的图形表达部分,与指定的模型空间和图纸空间的视口内容无关,然后调用函数viewportDraw()绘制于视口相关的部分,如果实体的所有图形表示都与视图相关,那么函数worldDraw()必须返回kFalse,而且必须重载viewportDraw()函数。

相反,如果实体没有与视图相关的图形,则worldDraw()函数返回kTrue,而且不需要重载viewportDraw()函数。

在函数AcDbEntity::worldDraw(AcGiWorldDraw *pWd)中,有一个指向AcGiWorldDrawde 指针对象,它是一个AcGi几何对象和特征对象的容器类。

另外,AcGeWorldDraw包含其他两个对象:AcGIiWorldGeometry和AcGiSubEntityTraits。

可以通过AcGiWorldDraw::geometry()获取AcGIiWorldGeometry对象,该对象能够通过制实体图形的基本绘制命令即图形原型将几何对象写到AutoCAD的图形缓存中,世界坐标系中绘制图形原型的函数主要有Circle、Circular arc、Polyline、Polygon、Mesh,Shell、Text、Xline和Ray。

通过AcGiWorldDraw::subEntityTraits()函数可以返回AcGiSubEntityTraits对象,该对象能够通过特性函数设置图形的属性值,如Color,Layer,LineType等。

在函数void AcDbEntity::viewportDraw(AcGiViewportDraw *pVd)中,有一个指向AcGiViewportDraw指针的对象,它也是一个容器对象,它包含AcGeViewportGeometry,AcGiSubEntityTraits和AcGiViewport等对象。

其中,AcGeViewportGeometry对象提供了与AcGeWorldGeometry相同的图形原型列表,同时增加了polylineEye(),polygonEye(), polylineDc(),PolygonDc()等函数原型,新添的图形原型使用视觉坐标和显示空间坐标来绘制多段线。

AcGiSubEntityTraints对象的使用方法与AcGiWorldDraw相同,AcGiViewport对象
提供了用于查找适口变换矩阵和观察参数的函数。

图8-6 图形的绘制过程
从上面可以看出图形的绘制过程如下:
1、AutoCAD创建AcGiWorldDraw对象。

2、AutoCAD创建AcGiViewportDraw对象。

3、AutoCAD将AcGiWorldDraw对象传给图形对象。

4、图形对象绘制图形(和视口无关)。

5、AutoCAD将AcGiViewportDraw传给图形对象。

6、图形对象根据视口绘制图形。

n实现对象捕捉功能
如果自定义实体支持对象捕捉功能,则需要重载getOsnapPoints()函数,打开捕捉方式后,AutoCAD会调用该函数获取当前捕捉方式下的相应的捕捉点,在实际的开发过程中,开发者开发的自定义实体如果不需要支持全部捕捉方式,这个时候在重载的getOsnapPoints()函数中只需要对支持的捕捉方式进行处理,对其它不支持的捕捉方式返回eOk,如果用户激活了多种捕捉方式,AutoCAD就会为每种捕捉方式调用一次getOsnapPoints函数。

Acad::ErrorStatus
CSPolyline::getOsnapPoints(
AcDb::OsnapMode osnapMode,
int gsSelectionMark,
const AcGePoint3d& pickPoint,
const AcGePoint3d& lastPoint,
const AcGeMatrix3d& viewXform,
AcGePoint3dArray& snapPoints,
AcDbIntArray& /*geomIds*/) const
{
assertReadEnabled();
Acad::ErrorStatus es = Acad::eOk;
if (gsSelectionMark == 0)
return Acad::eOk;
if ( osnapMode != AcDb::kOsModeEnd
&& osnapMode != AcDb::kOsModeMid
&& osnapMode != AcDb::kOsModeNear
&& osnapMode != AcDb::kOsModePerp
&& osnapMode != AcDb::kOsModeCen
&& osnapMode != AcDb::kOsModeIns)
{
return Acad::eOk;
}
AcGePoint3d center;
getCenter(center);
if (gsSelectionMark == (mNumSides + 1)) {
if (osnapMode == AcDb::kOsModeIns)
snapPoints.append(center);
else if (osnapMode == AcDb::kOsModeCen)
snapPoints.append(center);
return es;
}
int startIndex = gsSelectionMark - 1;
AcGePoint3dArray vertexArray;
if ((es = getVertices3d(vertexArray)) != Acad::eOk) { return es;
}
AcGeLineSeg3d lnsg(vertexArray[startIndex],
vertexArray[startIndex + 1]);
AcGePoint3d pt;
AcGeLine3d line, perpLine;
AcGeVector3d vect;
AcGeVector3d viewDir(viewXform(Z, 0), viewXform(Z, 1), viewXform(Z, 2));
switch (osnapMode) {
case AcDb::kOsModeEnd:
snapPoints.append(vertexArray[startIndex]);
snapPoints.append(vertexArray[startIndex + 1]);
break;
case AcDb::kOsModeMid:
pt.set(
((vertexArray[startIndex])[X]
+ (vertexArray[startIndex + 1])[X]) * 0.5, ((vertexArray[startIndex])[Y]
+ (vertexArray[startIndex + 1])[Y]) * 0.5,
((vertexArray[startIndex])[Z]
+ (vertexArray[startIndex + 1])[Z]) * 0.5);
snapPoints.append(pt);
break;
case AcDb::kOsModeNear:
pt = lnsg.projClosestPointTo(pickPoint, viewDir);
snapPoints.append(pt);
break;
case AcDb::kOsModePerp:
vect = vertexArray[startIndex + 1]
- vertexArray[startIndex];
vect.normalize();
line.set(vertexArray[startIndex], vect);
pt = line.closestPointTo(lastPoint);
snapPoints.append(pt);
break;
case AcDb::kOsModeCen:
snapPoints.append(center);
break;
default:
return Acad::eOk;
}
return es;
}
需要说明的对象的交点捕捉方式通过调用intersectWith()函数进行处理,而不是getOsnapPoints函数。

n自定义实体的夹点
当使用鼠标选择了一个AutoCAD的实体时,AutoCAD会显示出实体的夹点。

如果要求自定义实体支持夹点编辑功能,则需要重载getGripPoints()和moveGripPointsAt()函数,getGripPoints()函数的实例如下:
Acad::ErrorStatus
CSPolyline::getGripPoints(
AcGePoint3dArray& gripPoints,
AcDbIntArray& osnapModes,
AcDbIntArray& geomIds) const
{
assertReadEnabled();
Acad::ErrorStatus es;
if ((es = getVertices3d(gripPoints)) != Acad::eOk) {
return es;
}
gripPoints.removeAt(gripPoints.length() - 1);
AcGePoint3d center;
getCenter(center);
gripPoints.append(center);
return es;
}
夹点编辑的拉伸功能允许用户通过把选择的夹点移动到新位置来拉伸实体对象,AutoCAD通过的用moveGripPointsAt()函数实现夹点的拉伸功能,但对一些特定的实体移动一些夹点时移动对象而不是拉伸对象,如圆的圆心,在这种情况下moveGripPointsAt()函数调用transformBy()函数来移动对象。

Acad::ErrorStatus
CSPolyline::moveGripPointsAt(
const AcDbIntArray& indices,
const AcGeVector3d& offset)
{
if (indices.length()== 0 || offset.isZeroLength())
return Acad::eOk; //that's easy :-)
if (mDragDataFlags & kCloneMeForDraggingCalled) {
mDragDataFlags |= kUseDragCache;
} else
assertWriteEnabled();
if (indices.length()>1 || indices[0] == mNumSides)
return transformBy(AcGeMatrix3d::translation(offset));
AcGeVector3d off(offset);
double rotateBy = 2.0 * 3.14159265358979323846 / mNumSides * indices[0];
AcGePoint3d cent;
getCenter(cent);
off.transformBy(AcGeMatrix3d::rotation(-rotateBy,normal(),cent));
acdbWcs2Ecs(asDblArray(off),asDblArray(off),asDblArray(normal()),Adesk::kTrue);
if (mDragDataFlags & kUseDragCache){
mDragCenter = mCenter;
mDragPlaneNormal = mPlaneNormal;
mDragStartPoint = mStartPoint + AcGeV ector2d(off.x,off.y);
mDragElevation = mElevation + off.z;
}else{
mStartPoint = mStartPoint + AcGeVector2d(off.x,off.y);
mElevation = mElevation + off.z;
}
return Acad::eOk;
}
当用户调用夹点编辑中的移动、旋转、缩放和镜像操作时,AutoCAD调用transformBy()函数来实现相应的操作。

n自定义实体的拉伸点
实体的拉伸点集合实际是实体的夹点集合的一个子集,当用户调用STRETCH命令时,AutoCAD调用实体的getStretchPoints()函数返回其拉伸点,对于大多数实体来说夹点的编辑模式和拉神点的变价模式是一致的,函数getStretchPoints()和moveStretchPointsAt ()只是调用了重载的getGripPoints()和moveGripPointsAt()函数在程序中可以不重载函数getStretchPoints()和moveStretchPointsAt (),他们默认调用getGripPoints()和transformBy()。

Acad::ErrorStatus
CSPolyline::getStretchPoints(
AcGePoint3dArray& stretchPoints) const
{
assertReadEnabled();
Acad::ErrorStatus es;
if ((es = getVertices3d(stretchPoints)) != Acad::eOk)
{
return es;
}
stretchPoints.removeAt(stretchPoints.length() - 1);
return es;
}
n自定义实体的几何变换
AcDbEntity类提供了两个变换函数,其中,transformBy()函数对实体进行指定的矩阵操作变换,另外一个为getTransformedCopy()函数,它首先复制自身,然后再进行指定的矩阵变换并返回变换后的复制实体。

Acad::ErrorStatus
CSPolyline::transformBy(const AcGeMatrix3d& xform)
{
if (mDragDataFlags & kCloneMeForDraggingCalled) {
mDragDataFlags |= kUseDragCache;
mDragPlaneNormal = mPlaneNormal;
mDragElevation = mElevation;
AcGeMatrix2d
xform2d(xform.convertToLocal(mDragPlaneNormal,mDragElevation));
mDragCenter = mCenter;
mDragCenter.transformBy(xform2d);
mDragStartPoint = mStartPoint;
mDragStartPoint.transformBy(xform2d);
mDragPlaneNormal.normalize();
} else {
assertWriteEnabled();
AcGeMatrix2d
xform2d(xform.convertToLocal(mPlaneNormal,mElevation));
mCenter.transformBy(xform2d);
mStartPoint.transformBy(xform2d);
mPlaneNormal.normalize();
}
return Acad::eOk;
}
n自定义实体的相交函数
实体的相交函数主要有两中形式:
virtual Acad::ErrorStatus intersectWith(
const AcDbEntity* pEnt,
AcDb::Intersect intType,
AcGePoint3dArray& points,
int thisGsMarker = 0,
int otherGsMarker = 0) const;
virtual Acad::ErrorStatus intersectWith(
const AcDbEntity* pEnt,
AcDb::Intersect intType,
const AcGePlane& projPlane,
AcGePoint3dArray& points,
int thisGsMarker = 0,
int otherGsMarker = 0) const;
第一种形式对两个实体的简单点进行测试,第二种形式在一个投影面上计算交点,这两种形式都返回在实体自身上的交点。

重载intersectWith函数应该遵循一下原则:
1.每个自定义实体都应该能够处理与AutoCAD中定义实体如AcDbLine等的求交操作
2.如果自定义实体的intersectWith函数被调用时的实体参数不是AutoCAD中缺省定义的实体,应用程序需要把该自定义实体分解为一系列可以识别的AutoCAD中缺省实体,然后对这些分解所得的实体逐一调用intersectWith函数。

Acad::ErrorStatus
CSPolyline::intersectWith(
const AcDbEntity* ent,
AcDb::Intersect intType,
const AcGePlane& projPlane,
AcGePoint3dArray& points,
int /*thisGsMarker*/,
int /*otherGsMarker*/) const
{
assertReadEnabled();
Acad::ErrorStatus es = Acad::eOk;
if (ent == NULL)
return Acad::eNullEntityPointer;
if (ent->isKindOf(AcDbLine::desc())) {
if ((es = intLine(this, AcDbLine::cast(ent),
intType, &projPlane, points)) != Acad::eOk) {
return es;
}
} else if (ent->isKindOf(AcDbArc::desc())) {
if ((es = intArc(this, AcDbArc::cast(ent), intType, &projPlane, points)) != Acad::eOk)
{
return es;
}
} else if (ent->isKindOf(AcDbCircle::desc())) { if ((es = intCircle(this, AcDbCircle::cast(ent),
intType, &projPlane, points)) != Acad::eOk) {
return es;
}
} else if (ent->isKindOf(AcDb2dPolyline::desc())) { if ((es = intPline(this, AcDb2dPolyline::cast(ent), intType, &projPlane, points)) != Acad::eOk) {
return es;
}
} else if (ent->isKindOf(AcDb3dPolyline::desc())) { if ((es = intPline(this, AcDb3dPolyline::cast(ent), intType, &projPlane, points)) != Acad::eOk) {
return es;
}
} else {
AcGePoint3dArray vertexArray;
if ((es = getV ertices3d(vertexArray))
!= Acad::eOk)
{
return es;
}
if (intType == AcDb::kExtendArg
|| intType == AcDb::kExtendBoth)
{
intType = AcDb::kExtendThis;
}
AcDbLine *pAcadLine;
int i;
for (i = 0; i < vertexArray.length() - 1; i++) {
pAcadLine = new AcDbLine();
pAcadLine->setStartPoint(vertexArray[i]);
pAcadLine->setEndPoint(vertexArray[i + 1]);
pAcadLine->setNormal(normal());
if ((es = ent->intersectWith(pAcadLine, intType,
projPlane, points)) != Acad::eOk)
{
delete pAcadLine;
return es;
}
delete pAcadLine;
}
n自定义实体的分解
要使AutoCAD的BHA TCH和EXPLODE命令对自定义实体起作用,则必须重载explode()函数。

自定义的explode()函数应该能为实体分解为复杂程度小的实体。

如果分解以后的实体不是固有的实体,则函数返回explodeAgain。

这将导致BHA TCH对所返回的实体递归调用explode,直到他们分解为AutoCAD缺省定义的实体为止。

Acad::ErrorStatus
CSPolyline::explode(AcDbV oidPtrArray& entitySet) const
{
assertReadEnabled();
Acad::ErrorStatus es = Acad::eOk;
AcGePoint3dArray vertexArray;
if ((es = getVertices3d(vertexArray)) != Acad::eOk) {
return es;
}
AcDbLine* line;
for (int i = 0; i < vertexArray.length() - 1; i++) {
line = new AcDbLine();
line->setStartPoint(vertexArray[i]);
line->setEndPoint(vertexArray[i + 1]);
line->setNormal(normal());
entitySet.append(line);
}
AcDbText *text ;
if ((mpName != NULL) && (mpName[0] != _T('\0')))
{
AcGePoint3d center,startPoint;
getCenter(center);
getStartPoint(startPoint);
AcGeV ector3d direction = startPoint - center;
if (mTextStyle != AcDbObjectId::kNull)
text =new AcDbText (center, mpName, mTextStyle, 0, direction.angleTo (AcGeV ector3d (1, 0, 0))) ;
else
text =new AcDbText (center, mpName, mTextStyle, direction.length() / 20, direction.angleTo (AcGeV ector3d (1, 0, 0))) ;
entitySet.append (text) ;
}
return es;
}
下面我们通过实例来说明自定义实体的创建,通常我们把自定义实体创建为一个DBX工程,在通过ObjectARX向导创建工程的时候可以设置工程的类型,如图8-7所示,这样工程编译后生成扩展名为DBX的文件。

图8-7 创建DBX工程
前面我们从AcDbObject类派生了一个管道属性类,当使用属性类的时候,需要把属性类实例保存在实体的扩展词典中,现在我们创建自定义实体,将管道属性数据直接保存为自定义实体的自身数据,选择从AcDbPolyline类派生,而不是直接从AcDbEntity类派生,这样可以已有对象的特性,避免重复性的工作。

使用ObjectARX向导创建自定义实体类的过程和AcDbObject类派生了一个管道属性类一样,我们创建自定义实体类CPipeLine,如图8-8。

图8-8创建CPipeLine类
CPipeLine类关于文件操作的部分和CPipeAttribute类中的实现过程一样,这里就不再介绍。

CPipeLine类的绘制函数中在顶点位置出绘制圆并第一个节点位置输出其属性数据,实现代码如下:
Adesk::Boolean CPipeLine::worldDraw (AcGiWorldDraw *mode) {
assertReadEnabled () ;
// 准备数据
int nV erts = AcDbPolyline::numVerts();
AcGePoint3d pt;
AcGeV ector3d norm = AcDbPolyline::normal ();
// 设定绘制颜色为青色
mode->subEntityTraits().setColor(4);
// 计算方向向量
AcDbPolyline::getPointAt(0,pt);
AcGePoint3d pt1;
AcDbPolyline::getPointAt(1,pt1);
AcGeV ector3d vec = pt1-pt;
vec.normalize();
// 计算垂直向量
AcGeV ector3d vecV;
vecV = vec.crossProduct (AcGeV ector3d::kZAxis);
// 绘制管径标签
TCHAR buf[100];
pt1 = pt + vecV*2.0;
_stprintf(buf,_T( " 管径:%4.2f"), m_dRadius);
mode->geometry ().text (pt1,norm,vec,1.0,1.0,0,buf);
// 绘制壁厚标签
pt1 = pt1 + vecV*2.0;
_stprintf(buf,_T( " 壁厚:%4.2f"), m_dThickness);
mode->geometry ().text (pt1,norm,vec,1.0,1.0,0,buf);
// 绘制埋深标签
pt1 = pt1 + vecV*2.0;
_stprintf(buf,_T( " 埋深:%4.2f"), m_dDeep);
mode->geometry ().text (pt1,norm,vec,1.0,1.0,0,buf);
// 绘制材质标签
pt1 = pt1 + vecV*2.0;
_stprintf(buf,_T( " 材质:%s"), m_cMaterial);
mode->geometry ().text (pt1,norm,vec,1.0,1.0,0,buf);
// 设定颜色为红色
mode->subEntityTraits().setColor(1);
for(int i = 0;i<nV erts;i++)
{
AcDbPolyline::getPointAt (i,pt);
mode->geometry ().circle (pt,1.0,norm);
}
// 设定颜色为黄色
mode->subEntityTraits().setColor(3);
//
return (AcDbPolyline::worldDraw (mode)) ;
}
为了使用定义自定义实体,通常还要创建一个ObjectARX工程CH08UI,其中通过命令创建的自定义实体,创建过程和创建一个普通的实体的过程是一样的,代码如下:
// - CSCH08UI.AddPipe command (do not rename)
static void CSCH08UIAddPipe(void)
{
// 创建管道类
CPipeLine *pPipeline = new CPipeLine();
AcGePoint2d pt0(0,0);
AcGePoint2d pt1(10,10);
AcGePoint2d pt2(20,0);
AcGePoint2d pt3(30,10);
AcGePoint2d pt4(40,0);
pPipeline->addVertexAt (0,pt0);
pPipeline->addVertexAt (1,pt1);
pPipeline->addVertexAt (2,pt2);
pPipeline->addVertexAt (3,pt3);
pPipeline->addVertexAt (4,pt4);
pPipeline->setElevation(2.6);
pPipeline->setClosed (true);
AcDbObjectId idPipe;
// 将管道类添加到数据库
AddToDatabase(idPipe,pPipeline);
}
创建的管道的显示效果如图8-9所示。

图8-9管道的显示效果
8.4 练习
(1)开发者可以通过________宏和AutoCAD请求加载特性对创建代理进行操作,并且能够控制调整代理。

A. ACRX_DXF_DEFINE_MEMBERS
B. ACRX_NO_CONS_DEFINE_MEMBERS
C. ACRX_CONS_DEFINE_MEMBERS
D. ACRX_STA TIC_CHECK
(2)在卸载应用程序的时候,自定义对象转换为代理对象,自定义实体转换为代理实体,为了能够保证这些工作的顺利进行,在卸载时,所有的自定义类必须调用_________函数从ObjectARX中删除。

A. deleteAcRxClass()
B. acrxBuildClassHierarchy()
C. worldDraw()
D. viewportDraw()
(3)判断题(正确的在括号内画“√”,错误的画“×”)
()代理对象是AutoCAD为自定义ObjectARX对象在内存中建立的一种代用的数据存放器。

()AutoCAD利用worldDraw()和viewportDraw()函数来显示实体,对任何由AcDbEntity派生的类必须重载worldDraw()函数。

(4)编程实现自定义螺栓类。

相关文档
最新文档