自定义实体的创建和使用

自定义实体被视为高阶CAD二次开发的象征,很多招聘信息上都明确标注“熟悉自定义实体开发者优先”的字样,其重要性可见一斑!

一直以来,CAD二次开发的主要工作就是通过程序在模型空间绘制各种基本图元,从而实现辅助成图。在这种情况下,即使在业务上密切相关的图元,其在图纸上也是离散的,开发者和用户无法将其视为一个整体进行绘制或调整,封装性仅仅体现在绘图函数这一等级。

如果将这些密切相关的图元形成一个逻辑整体(即自定义实体),有什么好处呢?①可以实现更有意义的封装:无论绘图逻辑多么复杂,它永远被封装在自定义实体内部,调用者只需要向其传递关键数据就能驱动图形绘制和变化,这就是典型的”高内聚低耦合“;②增加用户交互性:自定义实体可以暴露操作接口给用户,用户可以更加灵活地调整图形;③规范业务逻辑:自定义实体的制定过程就是业务分解的过程。在我看来,自定义实体不只是一种技术,更是一种思维。

本文先介绍如何创建并使用自定义实体,在学习自定义实体的过程中,你将领略到AutoCAD系统的博大精深!

下一篇文章将讲解如何使用CLI技术封装自定义实体供.NET项目使用,这在大型CAD插件系统中非常实用,敬请期待!

自定义实体创建

使用ObjectARX向导创建自定义实体的项目模板比较方便,主要步骤如下:

  • 创建ObjectDBX项目

  • 添加COM支持,为后续COM封装做准备

  • 添加自定义实体

接下来的主要工作就是实现自定义实体CustomEntity1的代码逻辑。

自定义实体的实现起始非常简单,就是创建一个继承自AcDbEntity的子类,然后实现父类的各种虚函数,其中每个虚函数都有其特定含义,也对应着在CAD软件中的某项操作。通俗来说,这些虚函数实现得越多,自定义实体的功能就越完善、越强大。

  • 头文件
    • 只包含一个无参构造函数,否则出错,可以在无参构造函数中定义各参数的默认值,另外定义一个set方法用于显示设置各属性值。
    • 在设计自定义实体时,如何确定基本绘图数据将是一项具有挑战性和富有意义的工作!这里定义了四个属性:R1、R2、N、center。
    • 在自定义实体中定义获取/修改属性的成员方法,这样可以保证图形随着属性的变化而变化。这里定义了八个成员方法:getCenter/SetCenter、getR1/setR1、getR2/setR2、getN/setN。
    • 重写父类的八个虚函数,这里是自定义实体的逻辑核心,每个虚函数对应一个功能,详见源文件解释。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#pragma once
#ifdef MYCUSTOMENTITY_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP
#endif
#include "dbmain.h"

class DLLIMPEXP CustomEntity1 : public AcDbEntity {
public:
ACRX_DECLARE_MEMBERS(CustomEntity1) ;

protected:
static Adesk::UInt32 kCurrentVersionNumber ;

private:
// 大圆半径
double R1;
// 小圆半径
double R2;
// 环向等分数
int N;
// 大圆圆心坐标
AcGePoint3d center;

public:
CustomEntity1 () ;
virtual ~CustomEntity1 () ;
void set(AcGePoint3d center, double R1 = 1000, double R2 = 50, int N = 8);
const AcGePoint3d& getCenter() const {
return center;
}

Acad::ErrorStatus getCenter(AcGePoint3d& point) const {
assertReadEnabled();
point = center;
return Acad::eOk;
}

Acad::ErrorStatus setCenter(const AcGePoint3d& point) {
assertWriteEnabled();
center = point;
return Acad::eOk;
}

Acad::ErrorStatus getR1(double &d) const {
assertReadEnabled();
d = R1;
return Acad::eOk;
}
Acad::ErrorStatus setR1(double d) {
assertWriteEnabled();
R1 = d;
return Acad::eOk;
}

Acad::ErrorStatus getR2(double &d) const {
assertReadEnabled();
d = R2;
return Acad::eOk;
}
Acad::ErrorStatus setR2(double d) {
assertWriteEnabled();
R2 = d;
return Acad::eOk;
}

Acad::ErrorStatus getN(int &i) const {
assertReadEnabled();
i = N;
return Acad::eOk;
}
Acad::ErrorStatus setN(int i) {
assertWriteEnabled();
N = i;
return Acad::eOk;
}

public:
virtual Acad::ErrorStatus dwgOutFields (AcDbDwgFiler *pFiler) const ;
virtual Acad::ErrorStatus dwgInFields (AcDbDwgFiler *pFiler) ;
protected:
virtual Adesk::Boolean subWorldDraw (AcGiWorldDraw *mode) ;
virtual Adesk::Boolean subWorldDraw(AcGiWorldDraw* mode);
virtual Acad::ErrorStatus subGetGeomExtents(AcDbExtents& extents) const;
virtual Acad::ErrorStatus subTransformBy(const AcGeMatrix3d& xform);
virtual Acad::ErrorStatus subGetGripPoints(AcGePoint3dArray& gripPoints, AcDbIntArray& osnapModes, AcDbIntArray& geomIds) const;
virtual Acad::ErrorStatus subMoveGripPointsAt(const AcDbIntArray& indices, const AcGeVector3d& offset);
virtual Acad::ErrorStatus subExplode(AcDbVoidPtrArray& entitySet) const;
} ;

#ifdef MYCUSTOMENTITY_MODULE
ACDB_REGISTER_OBJECT_ENTRY_AUTO(CustomEntity1)
#endif
  • 源文件
    • dwgOutFields:自定义实体各属性的序列化逻辑。
    • dwgInFields:自定义实体各属性的反序列化逻辑。
    • subWorldDraw:自定义实体的绘制逻辑,根据属性绘图,在这里可以用mode->geometry()绘制基本图形,也可以定义其他已有实体,调用该实体的worldDraw()方法。
    • subGetGeomExtents:自定义实体的包围盒计算逻辑。
    • subTransformBy:自定义实体的图形变换逻辑。
    • subGetGripPoints:定义自定义实体的夹点。
    • subMoveGripPointsAt:自定义实体夹点拖动的逻辑。
    • subExplode:自定义实体炸开逻辑。
    • 还有很多其他虚函数,逐步探究!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#include "StdAfx.h"
#include "CustomEntity1.h"

const double PI = 3.1415926;
Adesk::UInt32 CustomEntity1::kCurrentVersionNumber =1 ;

ACRX_DXF_DEFINE_MEMBERS (
CustomEntity1, AcDbEntity,
AcDb::kDHL_CURRENT, AcDb::kMReleaseCurrent,
AcDbProxyEntity::kNoOperation, USTOMENTITY1,
LHMYCUSTOMENTITYAPP
|Product Desc: A description for your object
|Company: Your company name
|WEB Address: Your company WEB site address
)

CustomEntity1::CustomEntity1 () : AcDbEntity () {
this->center = AcGePoint3d(0, 0, 0);
this->R1 = 1000;
this->R2 = 50;
this->N = 8;
}

CustomEntity1::~CustomEntity1 () {}

void CustomEntity1::set(AcGePoint3d center, double R1, double R2, int N){
this->center = center;
this->R1 = R1;
this->R2 = R2;
this->N = N;
}

Acad::ErrorStatus CustomEntity1::dwgOutFields (AcDbDwgFiler *pFiler) const {
assertReadEnabled();
//----- Save parent class information first.
Acad::ErrorStatus es = AcDbEntity::dwgOutFields(pFiler);
if (es != Acad::eOk)
return (es);
//----- Object version number needs to be saved first
if ((es = pFiler->writeUInt32(CustomEntity1::kCurrentVersionNumber)) != Acad::eOk)
return (es);
//----- Output params
es = pFiler->writeDouble(R1);
if (Acad::eOk != es) return es;
es = pFiler->writeDouble(R2);
if (Acad::eOk != es) return es;
es = pFiler->writeInt32(N);
if (Acad::eOk != es) return es;
es = pFiler->writePoint3d(center);
if (Acad::eOk != es) return es;
return (pFiler->filerStatus());
}

Acad::ErrorStatus CustomEntity1::dwgInFields (AcDbDwgFiler *pFiler) {
assertWriteEnabled();
//----- Read parent class information first.
Acad::ErrorStatus es = AcDbEntity::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 > CustomEntity1::kCurrentVersionNumber)
return (Acad::eMakeMeProxy);
//----- Read params
es = pFiler->readDouble(&R1);
if (Acad::eOk != es) return es;
es = pFiler->readDouble(&R2);
if (Acad::eOk != es) return es;
es = pFiler->readInt32(&N);
if (Acad::eOk != es) return es;
es = pFiler->readPoint3d(&center);
if (Acad::eOk != es) return es;
return (pFiler->filerStatus());
}

Adesk::Boolean CustomEntity1::subWorldDraw (AcGiWorldDraw *mode) {
assertReadEnabled();
mode->geometry().circle(center, R1, AcGeVector3d(0, 0, 1));
for (int i = 0; i < N; i++)
{
double x1 = center.x + R1 * cos(2 * PI / N * i);
double y1 = center.y + R1 * sin(2 * PI / N * i);
mode->geometry().circle(AcGePoint3d(x1, y1, 0), R2, AcGeVector3d(0, 0, 1));
}
AcDbText* text = new AcDbText();
text->setPosition(center);
text->setTextString(L"测试");
text->setHeight(R1 / 10);
text->setColorIndex(1);
text->worldDraw(mode);
return Adesk::kTrue;
}

Acad::ErrorStatus CustomEntity1::subGetGeomExtents(AcDbExtents& extents) const
{
assertReadEnabled();
for (int i = 0; i < N; i++)
{
double x1 = center.x + R1 * cos(2 * PI / N * i);
double y1 = center.y + R1 * sin(2 * PI / N * i);
extents.addPoint(AcGePoint3d(x1 + R2, y1 + R2, 0));
extents.addPoint(AcGePoint3d(x1 + R2, y1 - R2, 0));
extents.addPoint(AcGePoint3d(x1 - R2, y1 + R2, 0));
extents.addPoint(AcGePoint3d(x1 - R2, y1 - R2, 0));
}
AcDbText* text = new AcDbText();
text->setPosition(center);
text->setTextString(L"测试");
text->setHeight(R1 / 10);
text->setColorIndex(1);
AcDbExtents ext;
text->getGeomExtents(ext);
extents.addExt(ext);
return Acad::eOk;
}

Acad::ErrorStatus CustomEntity1::subTransformBy(const AcGeMatrix3d& xform)
{
assertWriteEnabled();
center.transformBy(xform);
double scale = xform.scale();
R1 *= scale;
R2 *= scale;
return Acad::eOk;
}

Acad::ErrorStatus CustomEntity1::subGetGripPoints(AcGePoint3dArray& gripPoints, AcDbIntArray& osnapModes, AcDbIntArray& geomIds) const
{
assertReadEnabled();
gripPoints.append(center);
return Acad::eOk;
}

Acad::ErrorStatus CustomEntity1::subMoveGripPointsAt(const AcDbIntArray& indices, const AcGeVector3d& offset)
{
assertWriteEnabled();
for (int i = 0; i < indices.length(); i++)
{
switch (indices[i])
{
case 0:
center += offset;
break;
}
}
return Acad::eOk;
}

Acad::ErrorStatus CustomEntity1::subExplode(AcDbVoidPtrArray& entitySet) const
{
assertReadEnabled();
AcDbCircle* circle = new AcDbCircle(center, AcGeVector3d(0, 0, 1), R1);
entitySet.append(circle);
for (int i = 0; i < N; i++)
{
double x1 = center.x + R1 * cos(2 * PI / N * i);
double y1 = center.y + R1 * sin(2 * PI / N * i);
AcDbCircle* subCircle = new AcDbCircle(AcGePoint3d(x1, y1, 0), AcGeVector3d(0, 0, 1), R2);
entitySet.append(subCircle);
}
AcDbText* text = new AcDbText();
text->setPosition(center);
text->setTextString(L"测试");
text->setHeight(R1 / 10);
text->setColorIndex(1);
entitySet.append(text);
return Acad::eOk;
}
  • 导出文件

    • On_kInitAppMsg()中添加以下代码:
    1
    2
    CustomEntity1::rxInit();
    acrxBuildClassHierarchy();
    • On_kUnloadAppMsg()中添加以下代码:
    1
    deleteAcRxClass(CustomEntity1::desc());

将自定义实体添加至模型空间

  • 定义一个命令向模型空间添加自定义实体(该过程与添加内置图元别无二致):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void LHMyGroupMyCommand() {
AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();

AcDbBlockTable* pBlockTable;
pDB->getSymbolTable(pBlockTable, AcDb::kForWrite);
AcDbBlockTableRecord* pBlockTableRecord;
pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite);

CustomEntity1* ent = new CustomEntity1();
pBlockTableRecord->appendAcDbEntity(ent);

CustomEntity1* ent2 = new CustomEntity1();
ent2->set(AcGePoint3d(500, 500, 0), 500, 30, 14);
pBlockTableRecord->appendAcDbEntity(ent2);

ent->close();
ent2->close();
pBlockTableRecord->close();
pBlockTable->close();
}

  • 定义一个命令获取选中自定义实体的包围盒:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void LHMyGroupMyCommand2() {
AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();
ads_name ss;
acedSSGet(nullptr, nullptr, nullptr, nullptr, ss);
Adesk::Int32 len;
acedSSLength(ss, &len);
AcDbObjectId id;
for (Adesk::Int32 i = 0; i < len; i++)
{
ads_name ent;
acedSSName(ss, i, ent);
acdbGetObjectId(id, ent);
}
AcDbEntity* pEnt;
acdbOpenObject(pEnt, id);
if (pEnt != nullptr) {
AcDbExtents ext;
pEnt->getGeomExtents(ext);
// 使用完成之后一定要关闭实体
pEnt->close();
acutPrintf(L"x1=%.3f,y1=%.3f,x2=%.3f,y2=%.3f", ext.minPoint().x, ext.minPoint().y, ext.maxPoint().x, ext.maxPoint().y);
}
}

  • 定义一个命令用于修改圆心坐标:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void LHMyGroupMyCommand3() {
AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();
ads_name ss;
acedSSGet(nullptr, nullptr, nullptr, nullptr, ss);
Adesk::Int32 len;
acedSSLength(ss, &len);
AcDbObjectId id;
for (Adesk::Int32 i = 0; i < len; i++)
{
ads_name ent;
acedSSName(ss, i, ent);
acdbGetObjectId(id, ent);
}
CustomEntity1* pEnt;
acdbOpenObject(pEnt, id, AcDb::kForWrite);
if (pEnt != nullptr) {
pEnt->set(AcGePoint3d(3000, 2000, 0), 800, 25, 15);
// 使用完成之后一定要关闭实体
pEnt->close();
}
}

注意事项

  • 必须加载相应的dbx文件,才能对自定义实体进行相应操作,不然只能对自定义实体进行非常有限的操作!
  • 如果dwg图纸中包含自定义实体,但是没有加载相应的dbx文件,这时会以代理图形显示在CAD中,并给出以下提示;当自定义实体文件加载后,会自动还原为自定义实体。

评论