2023年12月7日发(作者:)

第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 ( e != 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) ;

if ( version > CPipeAttribute::kCurrentVersionNumber )

return (Acad::eMakeMeProxy) ;

while ( es == Acad::eOk && (es =pFiler->readResBuf (&rb)) == Acad::eOk ) {

6

PDF 文件使用 "pdfFactory Pro" 试用版本创建 switch ( e ) {

// 从DXF中读取数据

case AcDb::kDxfReal:

m_dRadius = ;

break ;

case AcDb::kDxfReal+1:

m_dThickness = ;

break ;

case AcDb::kDxfReal+2:

m_dDeep = ;

break ;

case AcDb::kDxfText:

_tcscpy(m_cMaterial,g);

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对象11

PDF 文件使用 "pdfFactory Pro" 试用版本创建 提供了用于查找适口变换矩阵和观察参数的函数。

图 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

12

PDF 文件使用 "pdfFactory Pro" 试用版本创建 && osnapMode != AcDb::kOsModeCen

&& osnapMode != AcDb::kOsModeIns)

{

return Acad::eOk;

}

AcGePoint3d center;

getCenter(center);

if (gsSelectionMark == (mNumSides + 1)) {

if (osnapMode == AcDb::kOsModeIns)

(center);

else if (osnapMode == AcDb::kOsModeCen)

(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:

(vertexArray[startIndex]);

(vertexArray[startIndex + 1]);

break;

case AcDb::kOsModeMid:

(

((vertexArray[startIndex])[X]

+ (vertexArray[startIndex + 1])[X]) * 0.5,

((vertexArray[startIndex])[Y]

13

PDF 文件使用 "pdfFactory Pro" 试用版本创建 + (vertexArray[startIndex + 1])[Y]) * 0.5,

((vertexArray[startIndex])[Z]

+ (vertexArray[startIndex + 1])[Z]) * 0.5);

(pt);

break;

case AcDb::kOsModeNear:

pt = osestPointTo(pickPoint, viewDir);

(pt);

break;

case AcDb::kOsModePerp:

vect = vertexArray[startIndex + 1]

- vertexArray[startIndex];

ize();

(vertexArray[startIndex], vect);

pt = tPointTo(lastPoint);

(pt);

break;

case AcDb::kOsModeCen:

(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

{

14

PDF 文件使用 "pdfFactory Pro" 试用版本创建 assertReadEnabled();

Acad::ErrorStatus es;

if ((es = getVertices3d(gripPoints)) != Acad::eOk) {

return es;

}

At(() - 1);

AcGePoint3d center;

getCenter(center);

(center);

return es;

}

夹点编辑的拉伸功能允许用户通过把选择的夹点移动到新位置来拉伸实体对象,AutoCAD通过的用moveGripPointsAt()函数实现夹点的拉伸功能,但对一些特定的实体移动一些夹点时移动对象而不是拉伸对象,如圆的圆心,在这种情况下moveGripPointsAt()函数调用transformBy()函数来移动对象。

Acad::ErrorStatus

CSPolyline::moveGripPointsAt(

const AcDbIntArray& indices,

const AcGeVector3d& offset)

{

if (()== 0 || Length())

return Acad::eOk; //that's easy :-)

if (mDragDataFlags & kCloneMeForDraggingCalled) {

mDragDataFlags |= kUseDragCache;

} else

assertWriteEnabled();

if (()>1 || indices[0] == mNumSides)

return transformBy(AcGeMatrix3d::translation(offset));

AcGeVector3d off(offset);

double rotateBy = 2.0 * 3.979323846 / mNumSides * indices[0];

AcGePoint3d cent;

getCenter(cent);

ormBy(AcGeMatrix3d::rotation(-rotateBy,normal(),cent));

acdbWcs2Ecs(asDblArray(off),asDblArray(off),asDblArray(normal()),Adesk::kTrue);

if (mDragDataFlags & kUseDragCache){

mDragCenter = mCenter;

mDragPlaneNormal = mPlaneNormal;

mDragStartPoint = mStartPoint + AcGeVector2d(off.x,off.y);

mDragElevation = mElevation + off.z;

15

PDF 文件使用 "pdfFactory Pro" 试用版本创建 }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;

}

At(() - 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(tToLocal(mDragPlaneNormal,mDragElevation));

mDragCenter = mCenter;

ormBy(xform2d);

mDragStartPoint = mStartPoint;

16

PDF 文件使用 "pdfFactory Pro" 试用版本创建 ormBy(xform2d);

ize();

} else {

assertWriteEnabled();

AcGeMatrix2d

xform2d(tToLocal(mPlaneNormal,mElevation));

ormBy(xform2d);

ormBy(xform2d);

ize();

}

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

{

17

PDF 文件使用 "pdfFactory Pro" 试用版本创建 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 = getVertices3d(vertexArray))

!= Acad::eOk)

{

return es;

}

if (intType == AcDb::kExtendArg

|| intType == AcDb::kExtendBoth)

{

18

PDF 文件使用 "pdfFactory Pro" 试用版本创建 intType = AcDb::kExtendThis;

}

AcDbLine *pAcadLine;

int i;

for (i = 0; i < () - 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的BHATCH和EXPLODE命令对自定义实体起作用,则必须重载explode()函数。自定义的explode()函数应该能为实体分解为复杂程度小的实体。如果分解以后的实体不是固有的实体,则函数返回explodeAgain。这将导致BHATCH对所返回的实体递归调用explode,直到他们分解为AutoCAD缺省定义的实体为止。

Acad::ErrorStatus

CSPolyline::explode(AcDbVoidPtrArray& 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 < () - 1; i++) {

line = new AcDbLine();

line->setStartPoint(vertexArray[i]);

line->setEndPoint(vertexArray[i + 1]);

line->setNormal(normal());

(line);

}

AcDbText *text ;

19

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

if ((mpName != NULL) && (mpName[0] != _T('0')))

{

AcGePoint3d center,startPoint;

getCenter(center);

getStartPoint(startPoint);

AcGeVector3d direction = startPoint - center;

if (mTextStyle != AcDbObjectId::kNull)

text =new AcDbText (center, mpName, mTextStyle, 0,

o (AcGeVector3d (1, 0, 0))) ;

else

text =new AcDbText (center, mpName, mTextStyle, ()

/ 20, o (AcGeVector3d (1, 0, 0))) ;

(text) ;

}

return es;

}

下面我们通过实例来说明自定义实体的创建,通常我们把自定义实体创建为一个DBX工程,在通过ObjectARX向导创建工程的时候可以设置工程的类型,如图8-7所示,这样工程编译后生成扩展名为DBX的文件。

图8-7 创建DBX工程

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

20

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

图8-8创建CPipeLine类

CPipeLine类关于文件操作的部分和CPipeAttribute类中的实现过程一样,这里就不再介绍。 CPipeLine类的绘制函数中在顶点位置出绘制圆并第一个节点位置输出其属性数据,实现代码如下:

Adesk::Boolean CPipeLine::worldDraw (AcGiWorldDraw *mode) {

assertReadEnabled () ;

// 准备数据

int nVerts = AcDbPolyline::numVerts();

AcGePoint3d pt;

AcGeVector3d norm = AcDbPolyline::normal ();

// 设定绘制颜色为青色

mode->subEntityTraits().setColor(4);

// 计算方向向量

AcDbPolyline::getPointAt(0,pt);

AcGePoint3d pt1;

AcDbPolyline::getPointAt(1,pt1);

AcGeVector3d vec = pt1-pt;

ize();

// 计算垂直向量

AcGeVector3d vecV;

vecV = roduct (AcGeVector3d::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);

21

PDF 文件使用 "pdfFactory Pro" 试用版本创建 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

{

AcDbPolyline::getPointAt (i,pt);

mode->geometry ().circle (pt,1.0,norm);

}

// 设定颜色为黄色

mode->subEntityTraits().setColor(3);

//

return (AcDbPolyline::worldDraw (mode)) ;

}

为了使用定义自定义实体,通常还要创建一个ObjectARX工程CH08UI,其中通过命令创建的自定义实体,创建过程和创建一个普通的实体的过程是一样的,代码如下:

// - e 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);

22

PDF 文件使用 "pdfFactory Pro" 试用版本创建 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_STATIC_CHECK

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

A. deleteAcRxClass()

B. acrxBuildClassHierarchy()

C. worldDraw()

D. viewportDraw()

(3)判断题(正确的在括号内画“√”,错误的画“×”)

( )代理对象是AutoCAD为自定义ObjectARX对象在内存中建立的一种代用的数据存放器。

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

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

23

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