找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 10215|回复: 15

[老徐讲堂] 告别了,教学

[复制链接]

已领礼包: 12个

财富等级: 恭喜发财

发表于 2016-7-27 17:36:30 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
从2013年搞这个网上教学开始,到现在,我的工作一直很忙,加上本人EQ不高,没法协调人际关系。现在将一本破书敬上,作为告别。

     
第一章  AutoCAD开发工具概述... 6
1.1 AutoCAD开发系统概述... 6
1.1.1为什么要对AutoCAD进行二次开发... 6
1.1.2 怎样对AutoCAD进行二次开发... 7
1.1.3 AutoCAD二次开发各编程接口的比较及选用... 13
1.2 ObjectARX开发环境概述... 14
1.2.1 ObjectARX应用程序的运行机制... 15
1.2.2 ObjectARX类库简介... 15
1.2.3 ObjectARX应用程序的功能... 16
1.3 ObjectARX全局函数简介... 18
1.4 ObjectARX SDK简介... 29
1.4.1SDK的下载与安装... 29
1.4.2运行ObjectARX应用程序的软硬件环境... 32
第二章  创建ObjectARX应用程序... 33
2.1 第一个简单的ObjectARX应用实例... 33
2.2 ObjectARX应用程序的结构分析... 36
2.2.1 头文件... 36
2.2.2 函数的声明... 37
2.2.3 接口函数部分... 37
2.2.4 用户程序主体函数部分... 40
2.3 在Visual Studio中创建应用程序的方法... 41
2.3.1 手动创建应用程序... 43
2.3.2 利用向导创建应用程序... 52
2.4 应用程序的加载与卸载... 54
2.4.1 应用程序的加载... 54
2.4.2 应用程序的卸载... 56
2.5请求加载... 57
2.5.1  AutoCAD、Windows系统注册表和ObjectARX应用程序... 58
2.5.2 ObjectARX应用程序安装时修改注册表的有关说明... 60
2.5.3 DEMANDLOAD系统变量... 62
2.5.4 检测到自定义对象时的请求加载... 63
2.5.5 在执行命令时请求加载... 64
2.5.6 在AutoCAD启动时请求加载... 65
2.5.7 使用系统注册表来管理应用程序... 65
第三章  AutoCAD数据库基础... 67
3.1 AutoCAD数据库概述... 67
3.1.1 多元数据库... 68
3.1.2 获取对象ID. 68
3.2 基本数据库对象... 69
3.3 在AutoCAD中创建对象... 70
3.4在ObjectARX中创建对象... 72
3.3.1 创建实体... 73
3.3.2 创建新层... 75
3.3.3 打开和关闭ObjectARX对象... 75
3.3.4 在组词典中添加组... 77
3.5 创建和移植数据库... 78
3.6存储数据库... 79
3.6.1 设置缺省文件格式... 79
3.7写块操作... 80
3.7.1 设置缺省文件格式... 81
3.7.2 创建带有实体的新数据库... 81
3.8插入数据库... 82
3.9 设置数据库当前值... 84
3.9.1 数据库的颜色值... 84
3.9.2数据库的线型值... 84
3.9.3数据库的线型比例值... 85
3.9.3数据库的层值... 86
3.10 数据库操作的简单例子... 86
3.11 长事务... 90
3.11.1 类和函数概述... 90
3.12 长事务实例... 93
3.13 外部参照... 99
3.13.1 外部参照的前、后处理... 101
3.13.2 文件锁定和一致性检查... 103
3.14 索引和筛选器... 103
3.15 图形摘要信息... 106
3.15.1 AcDbDatabaseSummaryInfo类... 107
3.15.2 AcDbSummaryInfoReactor类... 107
3.15.3 AcDbSummaryInfoManager类... 108
3.16 图形摘要信息... 108
第四章  数据库对象... 109
4.1 打开和关闭数据库对象... 109
4.2 删除对象... 113
4.3 对象的数据库隶属关系... 113
4.4 扩展数据... 114
4.5 扩展词典... 120
4.6 从数据库中删除对象... 130
4.7 对象编档... 130
第五章  实体对象... 132
5.1 实体概述... 132
5.2 实体的隶属关系... 133
5.3 实体的公共属性... 134
5.3.1 颜色... 134
5.3.2 线型... 135
5.3.3 线型比例... 136
5.3.4 可见性... 137
5.3.4 层... 137
5.4 实体的公用函数... 138
5.4.1 对象捕捉点... 140
5.4.2 几何变换... 140
5.4.3 交点... 140
5.4.4 GS标记和子实体... 143
5.4.5 分解实体... 169
5.5创建AutoCAD实体对象... 171
5.5.1 创建一个简单实体... 171
5.5.2创建一个简单块表记录... 172
5.5.3创建一个带属性定义的块表记录... 174
5.5.4创建一个带属性的块引用... 178
5.5.5 遍历块表记录... 185
5.6 复杂实体... 188
5.6.1 创建复杂实体... 188
5.6.2 遍历多段线的顶点... 190
5.7 坐标系统的访问... 192
5.7.1 实体坐标系... 192
5.7.2 AcDb2dPolylineVertex. 193
5.8 曲线函数... 193
5.9 给实体加入超链接... 195
5.9.1 AcDbHyperlink类... 196
5.9.2 AcDbHyperlinkCollection类... 196
5.9.3 AcDbEntityHyperlinkPE类... 196
5.9.4 超链接的例子... 197
第六章  容器对象... 201
6.1 符号表和词典的比较... 201
6.2 符号表... 203
6.2.1 块表... 206
6.2.2 层表... 207
6.2.3 遍历器... 212
6.3 词典... 214
6.3.1 组和组词典... 215
6.3.2 MLINE线型词典... 219
6.3.3 布局词典... 220
6.3.4 创建词典... 221
6.3.5 遍历词典表项... 223
6.4 布局... 224
6.4.1 ObjectARX布局类... 225
6.5 扩展记录... 227
6.5.1 扩展记录的DXF组码... 228
6.5.2 扩展记录实例... 229
第七章  访问AutoCAD的全局函数... 234
7.1 在AutoCAD中的查询与命令... 234
7.1.1 通用访问函数... 234
7.2 获取用户输入... 252
7.2.1 用户输入类函数... 252
7.2.2 对用户输入函数的控制... 255
7.2.3 动态拖动函数... 260
7.2.4 用户中断函数... 261
7.2.5 向AutoLISP返回值的函数... 263
7.3 类型转换... 264
7.3.1 字符串转换函数... 264
7.3.2 量纲单位转换函数... 268
7.4  字符处理函数... 269
7.5  坐标变换函数... 270
7.6 显示控制函数... 273
7.6.1 交互式输出函数... 273
7.6.2 图形和文本屏幕控制函数... 274
7.6.3 低级图形和用户输入类控制函数... 275
7.7 通配符的匹配... 276
第八章 ObjectARX全局函数... 278
ObjectARX全局函数的普遍特性... 278
1 0bjectARX全局函数和AutoLISP的差异... 278
2 函数返回值与函数的结果... 280
3 外部函数... 281
4 出错处理... 285
5 应用程序之间的通信... 286
第九章 选择集... 288
9.1 选择集和实体名... 288
9.2 选择集的处理... 288
9.2.1 选择集筛选表... 293
9.2.2 选择集的管理... 296
第十章 在ObjectARX中使用MFC. 300
10.1 概述... 300
10.2 使用MFC建立ObjectARX应用程序... 300
10.3 MFC和非模态对话框... 301
10.4 使用动态链接MFC的ObjectARX应用程序... 302
10.4.1动态链接MFC的VC++工程选项设置... 302
10.4.2资源管理... 303
10.4.3显示地设置资源的方法... 303
10.4.4 CAcExtensionModule类... 304
10.4.5 CAcModuleResourceOverride类... 304
10.5 MFC内置用户界面的使用... 306
10.5.1 AdUi提示签窗口... 308
10.5.2 CAduiTipWindow类... 308
10.5.3 CAdUiDrawTipText类... 309
10.5.4 CAdUiBaseDialog类... 309
10.5.5 CAdUiFileDialog类... 309
10.5.6 CAcUiDialog类... 309
10.6  AcUi MRU组合框... 309
10.6.1 CAcUiMRUComboBox类... 310
10.6.2 CAcUiArrowHeadComboBox类... 310
10.6.3 CAcUiColorComboBox类... 311
第十一章 几何类... 312
11.1 AcGe库概述... 312
11.2基本几何计算类型... 314
11.3直线和平面类... 317
11.4参数几何结构... 319
11.4.1曲线... 319
11.4.2曲面... 322

























第一章  AutoCAD开发工具概述

AutoCAD(AutoComputer Aided Design)是美国Autodesk公司研制开发的一款计算机辅助设计软件,它不是一款简单的绘图工具,而是一个复杂的计算机辅助设计系统。我们可以根据他灵活的开放性对其进行二次开发定制,让它更加适用于某一具体的设计领域。

从1986年发布AutoLISP以来到现在的二十余年来,Autodesk公司推出了AutoLISP、ADS、VBA、ARX、ObjectARX、VisualLISP、ObjectARX.NET、JavaScript等开发方式。本章就是对上述开发方式进行讨论分析从而找到高效开发AutoCAD的一种方式。

下面介绍一下本章的主要内容:
AutoCAD开发系统概述
ObjectARX SDK 简介
ObjectARX.Net开发简介
ObjectARX全局函数简介
ObjectARX对软硬件环境的要求
[本章配合视频详见光盘文件]
1.1 AutoCAD开发系统概述1.1.1为什么要对AutoCAD进行二次开发AutoCAD是目前在Windows和MAC系统中应用最为广泛、使用人数最多的CAD软件。但它只给我们提供了基础的CAD功能,如果我们想完成具体项目设计,就必须根据数据一笔笔绘制出图形,这样一旦在设计完成之后,要更改局部图形则需要重复原来的全部内容。造成了大量工作量的浪费。
如果使用AutoCAD的开发系统,我们就可以将以上的过程用程序编制出来,在需要设计时,只需一个命令就可以运行这个程序,自动完成绘图过程。显而易见,这不仅大大提高了设计效率,而且,还可以通过定制来完成某些专业化的模块,甚至大型设计软件,比如测绘行业的南方CASS软件、建筑行业的天正CAD软件等均是用AutoCAD开发系统实现的。
因此,要想让AutoCAD真正使用于某一具体领域,或让其经常完成一些重复性的工作,则必须利用AutoCAD的开发系统对其进行二次开发.
1.1.2 怎样对AutoCAD进行二次开发从AutoCAD 2.18开始推出AutoLISP开始到现在,我们所能使用的开发工具主要有:AutoLISP、Visual LISP、VBA、COM外部接口、ObjectARX、ObjectARX.NET等开发方式供用户选择。下面开始对上述开发方式进行简要介绍:
1.1.2.1 AutoLISP和Visual LISPAutoLISP是进行对AutoCAD二次开发最早的API,它是人工智能语言LISP的一个分支.主要用来自动完成重复性任务,进行客户化开发和编制AutoCAD菜单以及通过简单机制为AutoCAD扩充命令,能够有机的和AutoCAD结合在一起,它语法简单容易上手,到目前仍有很多的活跃开发用户。但是由于它是解释型API而不是面向对象的编程语言,使它的效率低下,由于执行的是源代码文件所以导致保密性能不高很难用它开发大型的应用程序。
在AutoCAD R14.01中,Autodesk公司首次提供了一种新的LISP编程工具:Visual LISP,它是一种面向对象的开发环境,是AutoLISP的扩展和延伸。
                              
图1-1 Visual LISP forAutoCAD R14 首个Visual LISP编程环境
在AutoCAD2000中,Visual LISP被集成到了AutoCAD环境之中。Visual LISP是一种半编译的API。由于可以被编译所以大大提高了运行效率和安全性。同时它又与AutoLISP完全兼容,又提供了AutoLISP的所有功能,同时它又能够访问AutoCAD的多文档环境,以及对COM/ActiveX技术的支持和反应器等。Visual LISP IDE同时提供了完整的编辑环境使得用户可以对代码进行调试跟踪、源码语法检查、括号匹配、函数提示等工具,方便创建和调试LISP程序。由于VLISP集成于AutoCAD内部,而且随AutoCAD升级而升级所以兼容性比较好,这也是LISP深受广大编程爱好者使用的原因。
但在进行大数据的计算处理方面,Visual LISP不能很好的胜任这项任务,这使得开发大型数据运算的程序仍有一定困难。

图1-2AutoCAD 2015上的Visual LISP编辑器
1.1.2.2 ADS、ARX和ObjectARX

ADS(AutoCAD Development System)是Autodesk公司最早在AutoCAD R11中提供的C语言编程环境。ADS除可使用标准C的函数外,又增加了一组专用于对AutoCAD进行操作的函数。由于ADS程序具有C语言的一切优点,因而它曾是开发AutoCADR11、AutoCAD R12应用程序的主要工具。用C写就的ADS程序,可在所有支持AutoCAD平台上进行源代码移植。只需使用普通的C语言编译器就可以编译生成ADS模块,与ADS库和标准C库链接后生成可执行文件,装入AutoCAD后即可运行。但是ADS和AutoLISP一样,内在结构不是面向对象的,用AutoLISP解释器加载和调用,利用IPC与AutoCAD通讯。

ARX(AutoCAD Runtime eXtension)是在ADS基础上发展起来的一种面向对象的C语言编程环境。由ADS到ARX的变迁就像C到C++的转变。ARX与老式的ADS及AutoLISP的最大差异在于ARX应用程序是动态链接库,共享AutoCAD地址空间,可以对AutoCAD进行直接函数调用,避免了IPC的系统开销和由此引起的性能下降。因此那些频繁与AutoCAD通讯的应用程序在ARX环境下的运行效率明显优于老式ADS或AutoLISP环境。
   ARX最早是在AutoCAD R13中提供的,但在AutoCAD R14中,ARX就被ObjectARX所代替。同时原来的ADS库函数全部被包含在ObjectARX中,将ADS函数合为单一的库就形成了ADSRX。ADSRX库已纳入到AutoCAD的总体结构中,因此它与其他ObjectARX库一样,能共享AutoCAD地址空间。
   到了新世纪,传统意义上的ADS开发环境的概念已经不复存在,且ADSRX已成为了ObjectARX的一个子集,因此,ObjectARX是包含了ADS、ARX和ADSRX的一种综合的C/C++开发环境。ObjectARX的最大特点是引入了面向对象的编程机制,用户可以根据规则定制实体也是ObjectARX的一大亮点。在后面的有关章节中我们将详细介绍ObjectARX,这里就不在赘述了。
1.1.2.3 利用微软的ActiveX技术自AutoCAD R14起,提供了ActiveX Automation形式的API。ActiveX技术可将各种二进制应用程序组件集成一体。它可采用多种编程方式例如:VBA构造一种或多种与应用程序独立的宏编程。提供Automation服务的软件组件通过标准接口能够对外开放它的特定功能。
正是由于AutoCAD拥有ActiveX接口,因此,用户可以容易的用各种AciveX编程语言来定制开发AutoCAD。
AutoCAD包含的VBA能在进程内访问AutoCAD的对象模型。亦可以通过独立的VB进行编程,但是VB与ActiveX的接口需要通过IPC驱动AutoCAD,所以作为ActiveX控件的VBA比VB具有明显的性能优势。
特别要强调的是,ActiveX与AutoCAD交互操作很慢,若需要在外部程序中一边运算一边交互操作的重复迭代,将耗费大量时间,如同死机
值得注意的是,从AutoCAD 2010版本开始,VBA不在随AutoCAD一起安装。如果想要在机器中运行VBA程序必须登录下面的网址进行下载方可运行。
http://www.autodesk.com/vba-download

图1-3 Visual BasicApplication for AutoCAD 2015

1.1.2.4利用ObjectARX.NET进行开发在AutoCAD 2005 版本中Autodesk公司推出了用.NET开发AutoCAD的编程接口。它的实质是通过Managed C++/CLR技术对VC++的ObjectARX进行封装。到目前的AutoCAD 2015十年来Autodesk公司已经完成对大部分ObjectARX编程接口的封装。这种编程方式难度适中,能够访问大部分的编程接口(除了自定义实体);但是,由于AutoCAD的.NET接口是在不断的完善过程中导致了在低版本上不能够使用新增的功能。
1.1.3 AutoCAD二次开发各编程接口的比较及选用比较各种开发AutoCAD的编程接口,需要从以下几个方面进行考虑:
速度:直接与AutoCAD通讯的API比利用IPC进行通讯的API在速度方面要快。编译型的API比解释型的API速度要快。因此,ObjectARX的速度最快,Com接口开发的外部程序速度最慢。
稳定性:运行稳定性反映出因程序可能出现的严重错误所导致的危险。采用LISP开发的程序一旦失败或崩溃,并不危害AutoCAD自身进程;而由于ObjectARX应用程序共享AutoCAD的地址空间,所以其一旦运行失败,AutoCAD进程随之崩溃。
性能:ObjectARX的应用程序能在运行期间实时扩展AutoCAD,共享AutoCAD地址空间,甚至为所欲为,因此性能无比强大,以至于AutoCAD自身的许多功能模块都是由ObjectARX进行制作。相反,解释型API如AutoLISP仅被限用于使用静态的AutoCAD命令集提供结构化函数库
技术难度:AutoLISP和VBA均是解释型语言,方便易学,开发周期短。许多程序员或一些技术人员都在使用它们。相比之下ObjectARX依赖于C++语言,它必须经过严格控制的编译、链接才能生成应用程序。这就需要编程人员需要积累经验,这样才能去发现问题从而解决问题。
保密性:编译型程序要比解释型保密性要好,保护了开发人员的著作权,解释型语言往往保密性能不高。导致用户误操作源码造成不应有的错误。另外:虽然.NET开发出来的DLL文件是编译后的程序,但是由于其程序可以反编译回源码造成了代码的保密性差,也是不容忽视的问题。
相比之下,如果以前已熟悉使用LISP语言的朋友,则可选用Visual LISP。
如果用户只想利用开发一些简单的周期性短的应用程序,则可以依个人爱好和经验选择Visual Basic和Visual LISP。若用户有较好的C++基础和AutoLISP函数基础,同时又要开发速度和性能要求都很高的应用程序或大型CAD应用软件,则要使用ObjectARX。
   当然,并不是说没有较好的C++基础就不能使用ObejectARX。本书就是针对没有C++编程经验的读者,帮助大家找到一条适合自己的编程之路。
1.2 ObjectARX开发环境概述         ObjectARX应用程序是一个动态链接库(DLL),它共享AutoCAD的地址空间并直接调用AutoCAD的函数。我们可以利用ObjectARX直接访问AutoCAD的数据库结构、图形系统以及AutoCAD几何构造核心。我们可以向ObjectARX编程环境添加新类,并将其输出以供其他程序调用。我们创建的自定义实体与AutoCAD内部实体没有任何区别。我们也可以在运行时通过向既有的AutoCAD类添加函数来扩充ObjectARX协议。由ObjectARX定义的外部命令与AutoCAD内部命令的执行机制是一样的。
1.2.1 ObjectARX应用程序的运行机制   ObjectARX应用程序的本质是Windows的DLL程序,而AutoCAD本身则是一个典型的Windows程序,ObjectARX应用程序与AutoCAD、Windows之间均采用Windows消息传递机制直接进行通信。
   ObjectARX应用程序与AutoCAD在同一地址空间内运行并能直接利用AutoCAD核心数据库结构和代码。由ObjectARX应用程序定义的外部命令通过acedRegCmds()宏注册。这些外部命令与AutoCAD本身固有命令一样由AutoCAD本身执行。
   ObjectARX应用程序以Visual C++语言为开发基础,具有面向对象编程方式的数据可封装性、可继承性及多态性等特点。由它开发的工程CAD软件具有模块性好、独立性强、连接简单、使用方便、内部功能高效实用以及代码可重用性强等优点,而且支持微软的MFC类库,能简洁高效的实现很多复杂的功能。
1.2.2 ObjectARX类库简介         ObjectARX开发环境主要包含以下几个类库:AcRx、AcEd、AcDb、AcGi、AcGe
库。下面就分别来探讨这些类库。
   AcRx类库:AcRx库提供了一些系统级的类,用于DLL的初始化和链接以及运行时类的注册和标识,它提供了以下的功能:
对象运行时类标识和继承分析
运行时向既有类添加新协议
对象的比较测试
对象复制。
AcEd类库:AcEd库提供定义和注册新的AutoCAD命令的类。我们定义的新命令之所以被当做本地命令来用是因为它们与AutoCAD内部命令具有相同的内部结构。AcEd库还提供编辑器反应器和一套与AutoCAD通讯的全局函数。
AcDb类库:AcDb库提供了组成AutoCAD数据库的类。AutoCAD数据库用于存储所有的图形对象和非图形对象;图形对象被称为实体,组成AutoCAD图形;非图形对象如:层、线型和字体也是图形的一部分,我们可以使用AcDb库查询和管理既有的AutoCAD数据库对象,亦可以创建新的数据库对象。
AcGi类库:AcGi库提供了用于绘制AuotCAD实体的图形接口。该库由AcDbEntity成员函数worldDraw(),viewportDraw()和saveAs()所使用,这些函数式标准实体协议的一部分。
AcGe类库:AcGe库由AcDb库所用,它提供了应用类,如向量和矩阵,用来完成二维和三维几何操作,它还提供基本的几何对象,如点、曲线和表面。
AcGe库包含了二个主要的子集:二维和三维几何类,主要抽象基类分别为AcGeEntity2d和AcGeEntity3d。
1.2.3 ObjectARX应用程序的功能使用ObjectARX开发AutoCAD程序我们可以完成以下功能。
1.2.3.1访问AutoCAD数据库      AutoCAD图形文件是一个存储在数据库中对象的集合,这些对象不仅表示图形实体,而且表示内部结构,如符号表和词典。ObjectARX为我们的应用程序提供了访问这些数据库结构的能力,我们也可以为指定的应用程序创建数据库。
1.2.3.2 与AutoCAD编辑器进行交互   ObjectARX提供了与AutoCAD编辑器通信的类和成员函数。我们可以用AutoCAD注册命令。应用程序可以接收和回应发生在AutoCAD内部的各种事件
1.2.3.3 使用MFC创建用户界面
    ObjectARX应用程序可以使用AutoCAD共享的动态链接MFC来创建应用程序,用户还可以使用这些库来创建标准Windows风格的图形界面。
1.2.3.4 支持多文档界面(MDI)
         使用ObjectARX,我们可以创建支持AutoCAD多文档界面的应用程序,并且用户可确保自己的应用程序将会正确的与Windows环境的其他应用程序正常通信。
1.2.3.5 创建自定义类
    我们可以用ObjectARX层次结构中的类创建自己的自定义类,同时还可以利用ObjectARX的扩展图形库创建自定义类。
1.2.3.6 编制复杂的应用程序
    ObjectARX支持复杂的应用程序的开发,它提供了如下特性:通知、事务管理、深层克隆、引用编辑、协议扩展和代理对象支持。
1.2.3.7 与其他环境通信
   ObjectARX应用程序可以与其他程序(如 Visual LISP、ActiveX和COM)进行通信,也可以与Internet加载和存储图形文件,ObjectARX应用程序也可以与国际互联网通信。
1.3 ObjectARX全局函数简介         在ObjectARX开发环境中,为我们提供了三组全局函数,根据不同的函数功能分为以下几种。
   第一组函数是实现与AutoCAD图形数据库的交互功能,因此被称为数据库函数,命名前缀为“acdb”。第二组函数是必须与用户交互进行编辑的,因此被称为编辑函数或编辑器函数,它们的命名前缀是“aced”。第三组函数中每个函数都会完成一个特定的实用功能,因此被称为使用函数。命名前缀是“acut”。下面对这些函数的功能进行分类简介。
(一)处理外部函数的函数
   下面列举的函数用于定义和管理外部函数。这些函数由ObjectARX应用程序或AuotCAD外部函数来执行。
   acedDefun                 定义一个外部函数
   acedGetArgs           获得调用外部函数时的参数
   acedGetFunCode            获得外部函数的码值
   acedInvoke               在ObjectARX应用程序中调用外部函数
   acedRegFunc           注册一个可直接调用的外部函数
   acedUndef                 取消一个外部函数的定义。
(二)处理外部应用程序的函数
   下面列举的函数用于管理其他ObjectARX应用程序,包括加载、卸载ObjectARX应用程序,以及测试有哪些ObjectARX应用程序被装入。
acedArxLoad           将一个ObjectARX应用程序装入AutoCAD供使用
acedArxLoaded              以链表方式返回当前装入的ObjectARX应用程序
acedArxUnload              将一个ObjectARX应用程序从AutoCAD中卸载出去
(三)错误处理函数
   当程序运行出现错误时,可以使用下面的函数来显示错误信息,以及中断程序的运行。acedAlert()函数也可以在程序正常运行时显示一些用户指定的信息。
   acrx_abort               发送警告消息,并中断程序运行
   acedAlert                显示一个对话框,对话框中有用户指定的信
                           息。
(四)结果缓冲区链表函数
   结果缓冲区链表是ObjectARX应用程序经常使用的一种数据类型,有关结果缓冲区链表我们会在后面做进一步详细阐述,在此先简要介绍一下与其相关函数的功能简介。
   acutBuildList            创建结果缓冲区链表
   acutNewRb                创建一个新的结果缓冲区
   acutRelRb                释放分配给一个结果缓冲区或一个结果缓
                         冲区链表的内存。
(五)有关查询函数及交互函数
  下面的一组函数,用于在AutoCAD中进行查询以及对内部命令进行调用的一些函数。当然对于这些函数还有一些使用限制(详见附录),这里只是简单介绍其功能。
   acedCommand           执行AutoCAD内部命令
   acedCmd               利用结果缓冲区链表来执行AutoCAD内部命                       令
   acedGetAppName           获取当前ObjectARX应用程序的文件名
   acedGetCName             获取一个命令的本地名称或与语言无关的
                         名称
   acedGetVar               获取指定的AutoCAD系统变量值
   acedFindFile             获取指定文件路径
   acedGetFileD             显示老式AutoCAD文件选择对话框
   acedGetSym               获取一个AutoLISP符号的值
   acedHelp              调用帮助功能
   acedPutSym               设置AutoLISP符号的值
   acedOsnap                根据设置的对象捕捉条件来获取一点
   acedSetVar               设置AutoCAD系统变量的值
   acedSetView           为指定视口建立一个3D视图
   acedVports               为当前视口进行配置,返回视口描述表
(六)实用的几何操作函数
   下面的一组函数,是介绍实用几何函数的一些功能,例如求两点间距离、以极坐标方式测算一个点、求两条线的交点等,这也是我们在应用程序中常用的。
   acutDistance             求两点间距离
   acutAngle                求出两点和当前X轴的夹角
   acutPolar                通过极坐标求一点
   acdbInters               求交点
   acedTextBox           找出包含文本实体的矩形框的对角坐标
(七)交互函数
   下面要介绍的一组函数,是我们经常要在应用程序中输入一些信息,如输入点、实数、字符串、角度、方位、关键字等,有时也要拖动选择集,在程序运行时,用户还可能要用ESC键中断程序。所有这些信息,我们可以通过以下的函数来进行输入和获取。
   acedInitGet           决定用户在响应下一次调用输入函数
                         acedGet**()时哪些值是有效值
   acedGetInt               提示用户输入一整数
   acedGetReal           提示用户输入一实数
   acedGetString            提示用户输入一字符串
   acedGetPoint             提示用户输入一点
   acedGetCorner            提示用户输入矩形的一个顶点
   acedGetDist           提示用户输入一个距离
   acedGetAngle             提示用户输入一个角度,要考虑ANGBASE系
                         统变量值
   acedGetOrient            类似于acedGetAngle(),但零度总是向右的
                         方向
   acedGetKword             提示用户输入一个关键字
   acedGetInPut             提取传递给用户输入函数acedGet**()的
                         关键字(可以是任意字符)
   acedDragGen           提示用户动态拖动一个选择集
   acedUsrBrk               检查用户是否按下了ESC键
(八)外部函数返回值函数
   从外部函数(ObjectARX应用程序)中可向AutoLISP返回某些值,如实数、点、字符串、nil、逻辑T、选择集名等。下面的一组函数可以实现向AutoLISP返回特定值的目的。
   acedRetInt               返回一个整数
   acedRetReal           返回一个实数
   acedRetPoint             返回一个点
   acedRetStr               返回一个字符串
   acedRetName           返回一个实体的名称或选择集的名称
   acedRetVal               返回一个包含在结果缓冲区中的值
   acedRetNil               返回一个nil值
   acedRetT              返回一个逻辑真值(T)
   acedRetVoid           返回一个不显示的空值(void)
   acedRetList           返回一个表
(九)转换函数
   下面一组函数是将数值(实数和整数)转为字符串、角度转为实数、科学计数法转为工程计数法等。下面提供的函数可以实现这样的变换。
   acdbRToS              将一个实数(浮点数)转换成字符串
   acdbDisToF               将一个显示为实数的字符串转换为一个实                         数(浮点数)
   acdbAngToS              将一个角度值转换为字符串
   acdbAngToF               将一个显示为角度值的字符串转换为实数                         (浮点数)
   acutCvUnit               在各种单位制内进行转换
(十)字符类型处理函数
   下面一组函数是对字符串、数字、可打印字符、标点符号、图形字符进行检验判断的函数。其中一些函数的功能是可通过C语言的标准函数库函数来实现的,但为了使用应用程序不依赖于具体的C语言编译环境,因此ObjectARX提供了下面的这些函数。现简单介绍下该组函数的主要功能。
   acutIsAlpha           验证一个字符是否为字母
   acutIsUpper           验证是否是大写字母
   acutIsLower           验证是否是小写字母
   acutIsDigit           验证是否是数字
   acutIsXDigit             验证是否是一个十六进制数字
   acutIsSpace           验证是否是一个空白字符
   acutIsPunct           验证是否是一个标点字符
   acutIsAlNum           验证是否是一个字母或数字
   acutIsPrint           验证是否是一个可打印字符
   acutIsGraph           验证是否是一个图形符号
   acutIsCntrl           验证是否是一个控制字符
   acutToUpper           将字符转换为大写
   acutToLower           将字符转换为小写
(十一)坐标系变换函数
   下面这个函数用于坐标系变换。如将一个点从UCS(用户坐标系)转换为WCS(世界坐标系)。
   acedTrans                将一点或位移从一个坐标系变换到另一个
                         坐标系
(十二)显示控制函数
   这组函数显示界面及信息的显示输出,是AutoCAD与用户交互的基本方式。我们可以对显示界面进行定制,可以通过不同的方式输出我们的信息。现简要介绍这些函数的功能。
   acutPrintf               在文本屏幕中输出一条信息,可进行格式化
   acedPrompt               在文本屏幕中输出一条信息
   acedMenuCmd           显示并激活菜单
   acedRedraw               对当前图形屏幕做重画操作
   acedGraphScr             显示当前图形屏幕
   acedTextScr           显示当前文本屏幕
   acedTextPage             显示当前文本屏幕,在显示前先清屏幕
(十三)图形处理函数
   利用下面这组函数可以对一些临时性的并不进入图形数据库的底层图形进行操作。
   acedGrDraw               在当前视口画一矢量
   acedGrVecs               在当前视口画多个矢量
   acedGrRead               读取输入设备
   acedGrText               在Autocad程序框架的状态区、菜单区等显.
                                                         示文本
(十四)通配符操作函数
   ObjectARX提供了一组可用于检测是否匹配满足一定条件的字符串或字符的通配符。
   acutWcMatch           测试一字符串是否与通配符相匹配
   acutWcMatchEx            功能同上,但可设置是否忽略字母大小写
(十五)选择集操作
   下面介绍的是一组对选择集进行操作的函数。
   acedSSGet                获取一个选择集
   acedSSAdd                向选择集中增加实体,或新建一个选择集
   acedSSDel                从选择集中删除一个实体
   acedSSFree               释放一个选择集
   acedSSLength             返回一个选择集中的实体数量
   acedSSName               返回选择集中的一个实体的名称
   acedSSMemb               判断某一实体是否是选择集中的成员
   acedXformSS           将一个变换矩阵作用到一个选择集上,进行
                         选择集变换
   acedSSNameX           描述选择集中的对象是如何选中的
   acedSSGetFirst           决定哪些对象被选中或被夹取
   acedSSSetFirst           选择和夹取对象
(十六)实体处理函数
   实体操作,如建立新的实体、删除实体、选择实体、获取实体的定义数据等是AutoCAD中最普遍的操作方式。下面的一组函数就是对实体对象的处理。
   acdbDictAdd           向指定字典中增加一个非图形对象
   acdbDictRemove           从指定字典中移走一个实体
   acdbDictNext             使程序指针指向字典中的下一个实体,并返                       回指向它的指针
   acdbDictRename           重命名一个字典条目
   acdbDictSearch           根据指定字典名称,搜索一个字典
   acdbEntGet               获取一个实体的定义数据
   acdbEntGetX           获取一个包含扩展数据的实体定义数据
   acdbEntMod               更新一个实体定义数据
   acdbEntMake           创建一个新实体,并将其存入数据库
   acdbEntMakeX             创建一个新实体,存入数据库并返回实体名
   acdbEntDel               删除(或取消删除)图形中的实体
   acdbEntNext           搜索图形中的下一个实体
   acdbEntlast           返回图形中最后一个建立的实体的实体名
   acdbHandEnt           通过实体的句柄查找实体
   acdbNamedObjDict         返回图形字典的实体名
   acedEntSel               提示用户指定一点的方式来选择实体
   acedNEntSelP             基本功能同acedEntSel()函数,但是可以返
                         回嵌套实体的附加数据。
   acedNEntSel           类似于上面的函数,但使用一个4×3矩阵                        而不是4×4矩阵作为变换矩阵,同时不让
                         程序指定选择点
   acdbEntUpd               更新实体的屏幕显示
   acdbSNValid           检验一个符号表名称的合法性
   acdbTblObjName           返回一个符号表实体名。
(十七)操作扩展数据
   acdbRegApp               注册一个应用程序名用于扩展数据
   acdbXdSize               返回扩展数据所占用的空间大小
   acdbXdRoom               返回一个实体还能容纳的扩展数据的内存
                         空间,一个实体最多能容纳16KB扩展数据
1.4 ObjectARX SDK简介1.4.1SDK的下载与安装       ObjectARXSDK不随AutoCAD软件一起发行,而是由Autodesk的官方网站提供下载。目前最新版本的ObjectARX SDK2015版本。下面附赠从AutoCAD R14至今的所有SDK下载地址:
2015 (32位和64位版本)
http://download.autodesk.com/esd/objectarx/2015/Autodesk_ObjectARX_2015_Win_64_and_32_Bit.exe
2014(32位和64位版本)
http://download.autodesk.com/esd ... 4_and_32Bit.sfx.exe
2013(32位和64位版本)
http://download.autodesk.com/esd ... in_64_and_32Bit.exe
2012(32位和64位版本)
http://download.autodesk.com/esd/objectarx/2012/ObjectARX_2012_Win_64_and_32Bit.exe
2011(32位和64位版本)
http://download.autodesk.com/esd/objectarx/2011/ObjectARX_2011_Win_64_and_32Bit.exe
2010(32位和64位版本)
http://download.autodesk.com/akdlm/esd/dlm/objectarx/ObjectARX_2010_Win_64_and_32Bit.exe
2009(32位和64位版本)
http://download.autodesk.com/esd/objectarx/2009/ObjectARX_2009_Win_64_and_32Bit.exe
2008
32位版本:
http://download.autodesk.com/esd/objectarx/2008/ObjectARX_2008_32Bit.exe
64位版本:
http://download.autodesk.com/esd/objectarx/2008/ObjectARX_2008_64Bit.exe
2007
-Core
http://download.autodesk.com/esd/objectarx/2007/Arx_Core.exe
-SDK
http://download.autodesk.com/esd/objectarx/2007/Arx_All.exe
2006
-Core
http://download.autodesk.com/WebPub/autocad/oarx2006/Arx_Core.exe
-SDK
http://download.autodesk.com/WebPub/autocad/oarx2006/Arx_All.exe
2005
-Core
http://download.autodesk.com/WebPub/Developer/autocad/Arx_Core2005.exe
-SDK
http://download.autodesk.com/WebPub/Developer/autocad/Arx_All2005.exe
2004
-Core
http://download.autodesk.com/WebPub/autocad/oarx/arx_core.exe
-SDK
http://download.autodesk.com/WebPub/autocad/oarx/arx_sdk.exe
2002
-Core
http://download.autodesk.com/pub/objectarx/objectarx_2002/K030.arx.plus.core.zip
-SDK
http://download.autodesk.com/pub/objectarx/objectarx_2002/K030.arx.plus.all.zip
2000
-Core
http://download.autodesk.com/pub/objectarx/ObjectArxCore.exe
-SDK
http://download.autodesk.com/pub/objectarx/ObjectArxSDK.exe
R14
-SDK
http://download.autodesk.com/Pub/developer/sdk/obarxsdk.exe
       下载之后,用户将得到一个压缩包或者一个自解压的exe文件。我们可以双击下载后的文件解压到指定的目录(默认为C)
       解压后为以下目录,现分别将各个目录的功能介绍如下:

图1-4ObjectARX 2015 SDK 目录
    classmap 目录包含一个文件,即classmap.dwg 此文件表示了ObjectARX类的层次关系。
    docs目录包含了ObjectARX SDK 的详细帮助文件。
    inc(inc-win32、inc-x64)目录包含了针对不同编译器对应的ObjectARX头文件。
    lib-win32、lib-x64目录包含了针对不同编译器对应的ObjectARX库文件
    redistrib(win32、x64)目录包含了一组动态链接库。其中一些可能是运行ObjectARX应用程序所必须的。开发者应将开发其应用程序所需要的ARX(DBX)文件复制到AutoCAD搜索路径内的一个目录中。
    Samples目录包含了一些ObjectARX应用程序例子的子目录。这些子目录中包含源程序代码和自述文件。最重要的ObjectARX应用程序例子在polysamp子目录之中。
    Utils目录包含了扩展的ObjectARX应用程序的子目录。每个应用程序目录包含的inc、lib和sample子目录。(在ObjectARX 2012前本目录还包括一个向导安装文件,2012之后请到Autodesk官方网站去下载)
1.4.2运行ObjectARX应用程序的软硬件环境       因为开发生成的应用程序要在对应的AutoCAD环境中运行,因此,运行ObjectARX应用程序所需的硬件环境应与对应的AutoCAD版本所需配置相同。

















第二章 创建ObjectARX应用程序在上一章中,对AutoCAD的二次开发方式做了简要介绍,并对各种开发方式的特点做了阐述。在这一章中,我们将要学习如何利用Visal Stuio创建ObjectARX应用程序,以及怎样利用Visual Studio来编译多版本的ObjectARX
应用程序。
本章的主要内容:
第一个简单的ObjectARX应用实例
ObjectARX应用程序结构分析
VisualStudio中创建应程序的方法
应用程序的加载、运行与卸载
应用程序的调试
[本章配合视频详见光盘文件]

2.1 第一个简单的ObjectARX应用实例一个ObjectARX应用程序是一个动态链接库,它共享AutoCAD的地址空间并且直接调用AutoCAD函数。ObjectARX应用程序典型地实现了从AutoCAD内部访问命令,这些命令通常通过我们自定义类来实现。
下面给出的是一个最为基础的一个应用程序实例,在用户定义的函数Test()中实现了在命令行显示“这是我们第一个简单实例!”如下图所示:



         
   实现这一功能的源代码如下,至于该程序中的详细解释我们将在后面进行介绍。
源码2.1一个简单的程序代码
//*****************************************************************************************
//文件名称:Hello.cpp
//功能:在命令行输出这是我们第一个简单实例!
//命令Hello
//*****************************************************************************************
//   头文件[8/17/2014 xuqinlong]
#include"aced.h"
#include"rxregsvc.h"
#include"TCHAR.H"
//----------函数声明部分------------
Void InitApplication();
Void UnloadApplication();
Void Hello();
//-----------------函数定义---------------------
void InitApplication()
{
    acedRegCmds->addCommand(_T("Exam02"),_T("Hello"),_T("你好"),ACRX_CMD_MODAL,Hello);
}
void UnloadApplication()
{
    acedRegCmds->removeGroup(_T("Exam02"));
}
void Hello()
{
    acutPrintf(_T("\n这是我们第一个简单实例!"));
}
//------------------程序的入口点-------------------------
extern "C" AcRx::AppRetCode
acrxEntryPoint(AcRx::AppMsgCodemsg,void *pkt)
{
    switch(msg)
    {
    caseAcRx::kInitAppMsg:
       acrxDynamicLinker->unlockApplication(pkt);
       acrxRegisterAppMDIAware(pkt);
       InitApplication();
       break;
    case AcRx::kUnloadAppMsg:
       UnloadApplication();
       break;
    default:
       break;
    }
    returnAcRx::kRetOK;
}
   其他程序可以在此程序的基础上扩充而成,下一节我们将详细介绍如何创建ObjectARX应用程序。
2.2 ObjectARX应用程序的结构分析    从上一节中我们已经能够看出,ObjectARX应用程序主要包含了四大部分,即:头文件部分、函数声明部分、接口函数部分以及用户程序主函数部分。下面就来逐一的进行讲解。
2.2.1 头文件       在头文件中,对aced.h和rxregsvc.h的引用是任何一个ObjectARX应用程序必须包含的。其中aced.h是用于ObjectARX应用程序定义以及访问AutoCAD特定编辑器服务的头文件。rxregsvc.h是acr**X实用函数的头文件。目前由向导建立的应用程序上述文件已经被arxHeaders.h文件自动引用,而不需要用户手动添加。本节中只是对手动建立的应用程序进行分析。对于向导建立应用程序我们将在后面进行阐述。
2.2.2 函数的声明       函数的声明部分比较简单,对应用程序中用到的函数进行说明即可。这与普通的Visual C++程序没有太大区别。应当注意的是,要对初始化函数和卸载函数进行声明。因为这两个函数要在入口函数中进行调用。
2.2.3 接口函数部分          在上一节中,我们定义了初始化函数和卸载函数以及入口点函数acrxEntryPoint()。现对其进行探讨。
   当我们的应用程序被加载的时候,初始化函数被AutoCAD所调用;当我们的应用程序被卸载时,卸载函数被AutoCAD所调用。这两个函数均是通过acrxEntryPoint()函数来被AutoCAD调用的。(注:有关此机制详细论述请参见下一章)。
   先来看一下我们定义的初始化函数,InitApplication().此函数只做了一件事情,那就是借助AutoCAD命令机制注册一个新的AutoCAD命令。

void InitApplication()
{
   acedRegCmds->addCommand(_T("Exam02"),_T("Hello"),_T("你好"),ACRX_CMD_MODAL,Hello);
}
   其中,addCommand()函数是ObjectARX库中定义的acedRegCmds类成员函数,该函数向AutoCAD命令堆栈中添加新的外部命令。函数中的第一个参数表示AutoCAD的命令组名,组名由用户定义或用系统已有组名。为了避免与其它命令名称发生冲突,最好在注册组名时利用用户注册的开发者前缀作为命令组名。同一命令组中的命令名必须唯一。上述函数中的第二个参数表示全局命令名。该名称应是容易被不同国家和地区接受的名字,一般用英文表示,不能被AutoCAD翻译为其它语言。第三个参数是本地化命令名,可以用本国语言表示,如中文表示。第四个参数表示用户定义的命令类型,ACRX_CMD_MODAL表示此命令为模态命令,不能作为透明命令使用。(有关这个函数的详细使用方法请参阅附录)。第五个参数表示函数指针,指向执行命令时所调用的函数。
   在上面的例子中,组名为“Exam02”,全局命令名为“Hello”,本地命令为“你好”,该命令为模态命令,执行时调用Hello()函数。
   在卸载函数UnloadApplication()中调用了removeGroup()函数释放用addCommand()函数定义的命令组,函数中的唯一的参数即为命令组名,命令组中相应的命令也同时被移走。由于注册的命令成为进入用户应用程序的附加入口点,因此,当应用程序被卸载时,必须要将这些命令卸载。
入口点函数acrxEntryPoint()是所有ObjectARX应用程序都要有的函数,它是AutoCAD与ObjectARX应用程序通讯的入口点,同时ObjectARX内核通过它向应用程序传递消息和向AutoCAD返回应用程序的状态码。需要注意的是,acrxEntryPoint()函数相当于普通C++程序中的main()函数。对于由acedDefun()定义的函数的调用请求,均是由acrxEntryPoint()函数建立的。
在上节之中acrxEntryPoint()函数的格式如下:
//------------------程序的入口点-------------------------
extern "C" AcRx::AppRetCode
acrxEntryPoint(AcRx::AppMsgCodemsg,void *pkt)
{
    switch(msg)
    {
    caseAcRx::kInitAppMsg:
       acrxDynamicLinker->unlockApplication(pkt);
       acrxRegisterAppMDIAware(pkt);
       InitApplication();
       break;
    case AcRx::kUnloadAppMsg:
       UnloadApplication();
       break;
    default:
       break;
    }
    returnAcRx::kRetOK;
}
acrxEntryPoint()函数中的第一个参数是msg,是AcRx类的数据成员,同时表示从ObjectARX内核向应用程序传递的消息,第二个参数pkt是一个指向传递到不同函数(如锁定与解锁函数)的数据句柄。AppRetCode为返回给AutoCAD的状态码。
在acrxEntryPoint()函数定义中,用switch语句处理来自AutoCAD的各种消息,执行与消息有关的相应动作,并返回一个整型状态码。
上面的应用程序比较简单,只有当应用程序被加载和卸载时,才需要向我们通报,从而达到注册和卸载“Hello”命令。在应用程序被加载时,AutoCAD发送kInitAppMsg消息,用来调用初始化函数;当应用程序被卸载时,AutoCAD发送kUnloadAppMsg消息,用来调用卸载函数。
上面的程序调用了acrxDynamicLinker->unlockApplication(pkt);语句用来解锁应用程序,这是因为我们的程序比较简单。但是一些复杂的涉及到程序稳定性最好调用acrxDynamicLinker->lockApplication(pkt);语句对应用程序进行锁定,防止卸载。
多文档环境(MDI)是从AutoCAD2000以来开始应用的,在缺省时ObjectARX应用程序是不支持MDI环境的。应用程序需要使用acrxRegisterAppMDIAware()全局函数将应用程序设置为支持多文档环境。
2.2.4 用户程序主体函数部分         由于用户需要完成的任务不同,用户程序主体函数部分也大不相同。因为本节中的示例比较简单,因此只有一个用户定义函数Hello(),如下所示:
void Hello()
{
   acutPrintf(_T("\n这是我们第一个简单实例!"));
}
   上面的函数调用了全局函数acutPrintf()在命令行窗口输出这是我们第一个简单实例
   用户开发应用程序时,一般只需要修改用户程序的主体函数即可,即在此函数的基础上进行扩充,从而实现任意复杂的功能,而接口函数部分除了要修改InitApplication()函数中注册的新的命令组名、命令名外,其余不需要改变。
头文件部分和函数声明部分要根据程序主体函数调用了哪些自定义函数和ObjectARX提供的函数及宏而定。
2.3 在Visual Studio中创建应用程序的方法建立ObjectARX应用程序,根据版本的不同需要采用不同的编译环境。现将从AutoCAD 2000版本开始到AutoCAD 2015所需的编译环境列表如下:

表2-1 各种版本ObjectARX 所需要的对应的编译环境
  
开发环境
  
  
对应AutoCAD版本
  
  
Visual Studio版本
  
  
ObjectARX 2000
  
ObjectARX 2000i
  
ObjectARX 2002
  
  
AutoCAD R15
  
(2000 2000i 2002)
  
  
  
Visual C++ 6.0 SP6
  
  
ObjectARX 2004
  
ObjectARX 2005
  
ObjectARX 2006
  
  
   AutoCAD R16
  
(2004 2005 2006)
  
  
  
Visual Stuido 7.0
  
  
ObjectARX 2007
  
ObjectARX 2008
  
ObjectARX 2009
  
  
AutoCAD R17
  
(2007 2008 2009)
  
  
  
Visual Studio 2005
  
  
ObjectARX 2010
  
ObjectARX 2011
  
ObjectARX 2012
  
  
AutoCAD R18
  
(2010 2011 2012)
  
  
  
Visual Studio 2008
  
(必须安装SP1补丁)
  
  
ObjectARX 2013
  
ObjectARX 2014
  
  
  
AutoCAD R19
  
(2013 2014)
  
  
  
Visual Studio 2010
  
(需要安装SP1补丁)
  
  
  
ObjectARX 2015
  
ObjectARX 2016
  
  
  
AutoCAD R20
  
(2015 2016)
  
  
  
Visual Studio 2012
  
  
  
ObjectARX 2017
  
  
AutoCAD R21
  
(2017)
  
  
Visual Studio
  
2015
  
由上表可见,不同系列的AutoCAD所对应的编译器版本是不一致的,本节中就详细的介绍一下如何针对不同版本的ObjectARX应用程序来进行配置环境。
2.3.1 手动创建应用程序2.3.1.1 准备工作
创建项目之前我们应当准备好ObjectARX的SDK安装包与对应版本的Visual Studio编辑环境。以及对应版本的Visual AssistX。这里我们约定本书中的系统开发环境已经安装到了C盘根目录下。下面我们就来针对不同的版本来进行讲解。
2.3.1.2 创建项目文件(一) 在VC6.0中创建项目
    首先打开Visual C++6.0,新建项目选择“Win32 Dynamic-Link Library”新建一个动态链接库项目。
   
    用户可以保存项目到自定义的目录下,当确定无误之后点击OK按钮。系统显示下图所示的界面,选择“An empty DLL Project”。
    点击“Finish”按钮后接着点击“OK”按钮完成项目的创建。
(二) VC6.0以上版本的创建
         对于ObjectARX 2002后的版本由于Visual C++已经被集成到了Visual Studio中所以配置是大同小异的,这里由于版面问题仅用Visual Studio 2008为例进行讲解。如果想在同一版本的VS中编译不同版本的ObjectARX应用程序请参见下一节的介绍。
   首先打开Visual Studio 2008,点击文件菜单下的新建项目
    选择Visusl C++ 节点,之后在右侧选择Win32项目,输入你项目的名称及选择路径后点击确定。接着弹出Win32应用程序向导点击下一步,选择DLL。后点击完成按钮。完成项目的创建。
2.3.1.3设置编译器与链接器选项(一)在VC6.0中设置1.从“Tools”下拉菜单中,选择“Options”节点弹出选项对话框。如下图所示。

2.选中“Directorles”选项卡在“Platform”中选择Win32,在选中右侧的“Show directories for”中的Include选项。把对应的ObjectARXSDK中的Inc目录添加进去。

3.继续在 “Show directories for”中选择 Library files 选项。把对应的ObjectARXSDK中的Lib目录添加进去。

上述步骤设置完后我们已经把ObjectARXSDK中的头文件、库文件路径添加到自动搜索位置了,就不用在为每个项目单独设置搜索路径了。对于不同的项目我们只需在项目的Link选项中添加对应的Lib文件就可以了。
(二)VC6.0以上版本配置与上面的相似在高版本的开发环境配置上我们以VC2008平台为例进行配置。
点击工具菜单下的选项节点,弹出选项配置对话框。在左侧选中项目和解决方案节点中的VC++目录。右侧平台对应您编译目标版本,32位的选择Win32,64位选择x64。再把所需要对应版本的ObjectARX SDK 的Inc以及Lib目录添加到对应的选项下。


2.3.1.4添加源代码文件上面的项目中我们建立的是一个空的Win32项目。需要我们手动添加程序代码文件和def文件。
首先我们从“File”菜单下选择New节点,弹出新建窗口,选择“Files”选项卡。在下面选择C++ Source File节点,右面的File文本框中输入文件名后点击OK按钮。

之后我们把第一节中的代码文件就可以添加到新建的文件中去了。

    创建完源码文件后,我们必须要输出acrxEntryPoint()函数,以便AutoCAD能访问它。有多种方式可以输出,其中之一就是创建DEF文件。与Hello.cpp文件相对应的是Hello.def文件,内容如下:
LIBRARY   FirstApp
EXPORTS
         acrxEntryPoint    PRIVATE
        acrxGetApiVersion PRIVATE
    LIBRARY段定义动态链接库的库名,该库名与项目名相同,不区分大小写。
   EXPORTS段定义输出函数。一般情况下,应输出三个函数,即acrxEntryPoint函数、_SetacrxPtp以及acrxGetApiVersion。这里的EXPORTS段应至少包含acrxEntryPoint函数和acrxGetApiVersion函数。在DEF文件中使用PRIVATE,避免了这些符号出现在应用程序输入库中。这样,可以避免当一个应用程序通过链接从另一个应用程序中使用符号时出现错误。上面定义的第一个输出函数名acrxEntryPoint是AutoCAD调用ObjectARX应用程序的入口函数名,定义的第二个输出函数名acrxGetApiVersion是在rxapi.lib中生成的函数。
   VC6.0以上版本与VC6.0类似,也要新建源码文件和模块定义文件。当所有步骤都完成后就可以生成应用程序了。
   如果编译链接过程中出现错误,应根据错误信息修改源程序代码或者环境设置直到没有错误产生。此时生成的test.arx文件被保存到工程文件目录中的的DEBUG文件夹下。(高版本在win32(或x64)\\Debug)目录下。然后可以通过下一节介绍的方法在AutoCAD中加载、运行和调试此应用程序。
2.3.2 利用向导创建应用程序
上面的方法是根据手工的方法来进行创建Win32动态链接库的方式创建ObjectARX应用程序,下面介绍一种更为简洁的方式来进行创建ObjectARX应用程序,利用这种方式创建的应用程序与手动创建的ObjectARX应用程序的效果是一致的。
首先在到网上下载ObjectARX的开发向导文件,ObjectARX2012之前的向导安装文件随SDK发布,一般在SDK的utils目录下。ObjectARX2012之后一般要到官网去下载,下载的地址是:
下面,以ObjectARX 2015为例进行讲解,第一步,设置开发者代号,根据向导装提示选择AutoCAD所在的目录,在选择ObjectARX SDK所在的目录。第二步点击InstallNow进行安装就可以了。安装向导之前,必须安装ObjectARX SDK对应的Visual Studio版本,否则将无法安装此向导。
安装完向导后,会在Visual Studio集成环境中的Visual C++开发模块中找到新建ObjectARX应用程序项目模板。


新建一个ObjectARX项目之后,系统自动创建了一个框架,就好像MFC向导创建了MFC项目框架一样,我们只需要把程序代码添加到这个项目中就可以了。经过编译后生成的应用程序与上一节手动创建的应用程序效果是一样的。
利用向导我们可以节省很多的工作量,也为我们以后开发应用程序打下了良好的基础。(参见本书书后视频)
2.4 应用程序的加载与卸载2.4.1 应用程序的加载
在AutoCAD中加载应用程序,可以通过以下几种方式进行操作。
第一种方法是使用AutoLISP函数arxload。按如下方式进行操作:
命令:(arxload “应用程序名称”)
这里针对上一节生成的应用程序,我们可以在AutoCAD命令窗口中输入:(arxload “E:\\BOOK\\EXAM02\\Hello.arx”)。
文件名后缀arx可以省略。其中两个反斜杠 “\\”将被AutoLISP解释为一个反斜杠“\”。我们可以用一个斜杠“/”代替两个反斜杠“\\”。
另外,如果将 E:\BOOK 目录加入到 AutoCAD 的搜索路径中后,也可以通过下面的操作加载应用程序:
(arxload “Hello.arx”)
加载应用程序的第二种方法是使用ARX 命令。按如下方式进行操作:
命令: ARX

之后系统弹出如下对话框:

我们选中要加载的应用程序加载即可。
第三种方法,是在命令行输入“APPLOAD”命令进行加载,在系统弹出的对话框中选择需要加载的应用程序。

2.4.2 应用程序的卸载
如果应用程序已解锁,我们可以使用下列方法之一卸载ObjectARX应用程序:
·在一个ObjectARX应用程序中,使AcRxDynamicLinker::unloadModule()要求卸载另一个应用程序。
·使用APPLOAD命令
·在AutoLISP中使用arxunload函数。
·在ObjectARX申使用acedArxUnload()函数。
·在AutoCAD命令行使用ARX命令和Unload选项。

  

2.5请求加载
请求加载是AutoCAD的特点之一,它尝试加载未驻留在AutoCAD中ObjectARX应用程序。在下列情况下ObjectARX应用程序可以设计为AutoCAD可加载的。
·当图形文件包含由未被调入的应用程序创建的自定义对象时。
·当一个用户或另一个应用程序处理未被调入的应用程序命令时。
·当AutoCAD启动时。
值得注意的是,在AutoCAD启动时,请求加载的应用程序优先于acad.rx中的应用程序被加载。
Autodeak推荐在开发OhjectARX应用程序时,应用AutoCAD的请求加载特点,因为请求加载提供如下优点:
·限制创建代理对象。
·为加载ObjectARX应用程序提供更大的灵活性。
·只有当需要应用程序的功能时,加裁应用程序,可以节约内存。
为使应用程序可以按请求加载,必须在Windows系统注册表中添加应用程序的专用信息。另外,具有两个或以上Dll的ObjectARX应用程序可能需要一个“控制器”模块,以便加载应用程序的所有其他部件。并且为了进行请求加载,必须给DEMANDLOAD系统变量设置适当的值。
最后应当注意:不但可以从本地计算机的路径上加载ObjectARX应用程序,而且还可以从包括互联网在内的所有网络地址上请求加载。
2.5.1  AutoCAD、Windows系统注册表和ObjectARX应用程序
AutoCAD使用Windows系统注册表保存各种各样的应用程序信息,包括唯一识别不同AutoCAD发行版、语言版本和产品(如AutoCAD Civil3d)信息,这些信息可以被安装在任何计算机上。识别不同版本AutoCAD的注册信息ObjectARX开发者是非常重要的,ObjectARX应用程序的安装程序必须把ObjeccARX应用程序的信息和要运行的AutoCAD的版本信息联系起来。
AutoCAD安装程序在系统注册表中立即建立一个位于发行版号码后面的唯一时间标志主键(并添加相同的自执行安装ID)。该标志主键保证同一发行版的不同AutoCAD语言版本可以填写系统注册表中自己的段。在该标志主键内,存储的值为AuroCAD文件的位置、语言版本和产品名,例如:

时间标志主键也用于识别当前加载的AutoCAD版本(或最近加载的版本)。该标识是必需的,因为当前AutoCAD版本加载时,需要重新设置注册表中AutoCAD自己所有的全局HKEY_CLASSES_ROOT段的信息。
在注册表中发行版主键段的CurVer键值用于识别当前版本,例如:

当AutoCAD准备请求加载一个ObjectARX应用程序时,在注册表中属AutoCAD最新发行版的段内查找有关ObjectARX应用程序的信息。如果没有找ObjectARX的信息,则检查前一AutoCAD发行版的注册表段,依次向上,直到找ObjectARX应用程序的信息或者AutoCAD发行版的信息,直到找到为止。
2.5.2 ObjectARX应用程序安装时修改注册表的有关说明         AutoCAD使用Windows系统注册表定位请求加载的ObjectARX应用程序。注册表中AutoCAD段的一部分是关于ObjectARX应用程序注册信息的位置的信息。
ObjectARX应用程序的安装程序必须在系统注册中创建请求加载需要的专用主键和键值。某些必需的主键和键值必须建立在注册表中AuToCAD段,其他主键和键值必须建立在注册表中ObjectARX应用程序段内。
如果ObjectARX应用程序被设计为可以在多个AutoCAD版本(即不同的语言版本或相关产品,如AutoCAD Map)内运行,安装程序必须在每个AutoCAD版本的注册表段内添加合适的信息。
ObjectARX应用程序的安装过程必须包括:
·确认在系统注册表中存在合适的AutoCAD版本的信息段。如果AutoCAD的注册段不存在,应警告用户兼容的AutoCAD版本尚未安装,并且终止安装。
·在系统注册表中合适的AutoCAD版本段内,为应用程序创建专用的一套主键和键值。
·为应用程序自己创建一个主键,并与另一套的专用主键和键值相组合。
2.5.2.1 创建AutoCAD子键和键值ObjectARX应用程序的安装程序,必须被设计为可管理该应用程序的一套主键和键值,该主键和键值位于要运行该应用程序的AutoCAD每个版本系统注册表段内。下面例子给出了在注册表的应用程序段内,必须创建和保存的主键和键值的形式:
\\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\releaseNum\
    ACAD-ProductID:LocaleID\
        Applications\
            ApplicationName\
                DESCRIPTION:REG_SZ:UserFriendly App Name
                LOADCTRLS:REG_DWORD:acrxAppLoadReason
                LOADER:REG_SZ:DirPathFileName
                Commands\
                    GlobalCommandName1:REG_SZ:LocalCommandName1
                    GlobalCommandName2:REG_SZ:LocalCommandName2
                    GlobalCommandName3:REG_SZ:LocalCommandName3
                    GlobalCommandName4:REG_SZ:LocalCommandName4
                    GlobalCommandName5:REG_SZ:LocalCommandName5
                Groups\
                    GroupName:REG_SZ:GroupName
releaseNum和ACAD –ProductId:LocaleID主键是由AutoCAD安装程序的。
ApplicationName主键必须是应用程序的逻辑名,AutoCAD使用该逻辑名在其内部识别程序。
acrxAppLoadReason键值定义加载应用程序的条件,其键值为下列一个或多个十六进制值的逻辑OR的结果。十六进制值的含义知下:
0x01检测到代理对象后加载应用程序。
0x02AutoCAD启动时加载应用程序。
0x04命令执行时加载应用程序。
0x08用户或另一个应用程序要求时加载应用程序。
0x10不加载应用程序。
0x20透明加载应用程序。

ObjectARXAPI包含acrxRegisterApp()函数,在ObjectARX应用程序中可以使用该函数将有关应用程序的信息输入到系统注册表的AutoCAD段。一般情况下,当第一次加载应用程序时,acrxRegisterApp()将应用程序的信息输入到系统注册表中;而在以后每次加载时,检查并确认这些信息是否存在。

2.5.3 DEMANDLOAD系统变量

AutoCADDEMANDLOAD系统变量控制ObjectARX应用程序的请求加载选项。

DEMANDLOAD系统变量的缺省值(当AutoCAD被安装时)被设置为:不管在应用程序系统注册表输入项指定哪一个选项,在命令要求或在发现代理后,允许应用程序的请求加载。DEMANDLOAD的设置不影响在AutoCAD启动时的请求加载,当在系统注册表中选用如下这些选项之一时,DEMANDLOAD的设置也不影响用户或应用程序要求时的请求加载。系统变量的合法值定义如下(可以组合使用):
0禁止所有ObjectARX应用程序的请求加载。
l允许在发现代理对象时ObjectARX应用程序的请求加载。
2允许在命令执行时ObjectAfIX应用程序的请求加载。
3允许在发现代理对象时或命令执行时ObjectARX应用程序的请求加载(缺省值)。
即使系统注册表被设置为命令执行时和发现代理对象时,允许ObjectABX应用程序的请求加载,DEMANDLOAD系统变量还是允许用户禁止所有ObjectARX应用程序的请求加载。如果不存在合适的系统注册表设定,就不能请求加载应用程序。
2.5.4 检测到自定义对象时的请求加载    当加载包含自定义对象的DWG或DXF文件时,AutoCAD特确定是否相关的应用程序已被加载,如果应用程序未被加载,而且设置了系统变量DEMANDLOAD的第一个位值。AutoCAD在Windows系统注册表中查找有关应用程序信息和它的加载者模块。如果AutoCAD在系统注册表中找到合适的信息,将加载应用程序。
注意:检测到自定义类时,只有当该自定义类是从AcDbObject直接或间接的派生的类,其应用程序才会被请求加载。
例如,假定AutoCAD读一个由OhjectARX应用程序polysamp创建的文件。
(l)在AutoCAD读取文件时,遇到了应用程序polysamp创建的自定义对象,并发现该应用程序未加载。

(2)AutoCAD查找DEMANDLOAD系统变量,发现DEMANDLOAD被设置为在检测到代理对象时,允许应用程序的请求加载,因此在系统注册表的AutoCAD应用程序的段内查找有关polysamp的主键,AutoCAD读人polysamp、Loader主键,确定要加裁的模块的目录、路径和文件名。注册表的目录段为:

\\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\ObjectDBX\<VERSION>\Applications\<Application>
            DESCRIPTION:REG_SZ:PolyCad
            LOADCTRLS:REG_DWORD:0xd
            LOADER:REG_SZ:C:\ProgramFiles\polysampinc\polyui.dbx
AutoCAD加载ObjectARX模块。如果模块加载成功,AutoCAD将该应用程序的句柄添加到由kLoadDwgMsg消息发送的应用程序句柄表中。然后AutoCAD核对该应用程序是否已被正确地加载,并核对自定义类是否被注册。如果应用程序加载成功,AutoCAD将继续加载图形文件。如果ObjectARX模块不能加载,或者如果类仍然不能正常执行,自定义对象将被作为代理对待,并继续加载。
2.5.5 在执行命令时请求加载         如果用户执行AutoCAD未注册的命令,AutoCAD将试图加载合适的ObjectARX应用程序。
为了支持在命令执行时的请求加载,ObjectARX应用程序的安装程序,必须在应用程序命令的系统注册中创建合适的主键和键值。系统注册表的应用程序命令段应包含命令的信息。
例加:
\\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R20.0\
    ACAD-E001:409\
        Applications\polysamp\
            DESCRIPTION:REG_SZ:PolyCad
            LOADCTRLS:REG_DWORD:0xd
            LOADER:REG_SZ:C:\ProgramFiles\polysampinc\polyui.arx
            Commands\
                ASDKPOLY:REG_SZ:ASDKPOLY
                ASDKDRAGPOLY:REG_SZ:ASDKDRAGPOLY
                ASDKPOLYEDIT:REG_SZ:ASDKPOLYEDIT
            Groups\
                ASDK:REG_SZ:ASDK
...
    在这个例子中,将开发者注册的开发者前缀(ASDK)作为所有命令的前缀,以确保该命令不会与在其他应用程序中的同名命令冲突。
为实现在命令执行时请求加载,ObjectARX应用程序也必须包含对acedRegCmds宏的正确调用。
2.5.6 在AutoCAD启动时请求加载在AutoCAD启动时的ObjectAFX应用程序请求加载,可以通过将系统注册表中的LoadCtrls键值设置为Ox02(或者Or02与另一个合法值的逻辑“或”)来实现。例如:
\\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R20.0\
    ACAD-E001:409\
        Applications\PolyCAD\
        LOADCTRLS:REG_DWORD:0x02
2.5.7 使用系统注册表来管理应用程序一旦创建了请求加载的系统注册表信息加载和卸载ObjectARX函数及监视是否存在无请求加载特征的ObjectARX,都使用该信息。加载和卸载函数使用的AppName变量是逻辑应用程序名。
可以使用下列ObjectARX函数注册应用程序名:
    bool acrxLoadApp(“AppName”)
该函数只有一个变量,它表示区分大小写的被加载的应用程序逻辑名。如果函数加载失败,返回0;如果加载成功,返回1。
    bool acrxunloadApp(“AppName”)
该函数只有一个变量,它表示区分大小写的以前加载的应用程序逻辑名。如果函数加载失败,返回0;如果加载成功,返回1。
    void* acrXLoadedApps()
该函数返回一个void*串阵列,它包含当前被加载的每个应用程序的逻辑应用程序名。如果没有应用程序被加载,函数返回NULL,调用者应负责释放分配给返回串的内存空间。























第三章  AutoCAD数据库基础
    AutoCAD数据库用来存储组成AutoCAD图的对象和实体。本章介绍数据库的主要元素有:实体、符号表和命名对象词典;也介绍对象处理、对象ID及打开和关闭数据库对象的协议。
本章给出了创建实体、层和组,并添加对象到数据库的程序例子。
3.1 AutoCAD数据库概述AutoCAD图形是一个储存在数据库中的对象的集合。基本的数据库对象是实体、符号表和词典。实体是在AutoCAD图内部表示图的一种特殊数据库对象,线、圆、弧、文本、实心体、面域、复合线和椭圆都是实体,用户可以在屏幕上看见实体并能对其进行操作。
符号表和词典是用于存储数据库对象的容器,这两个容器对象都映射一个符号名(字符串)到一个数据库对象,一个AutoCAD数据库包含一套固定的符号表,每一个符号表包含一个特定符号表记录类的实例,我们不能向数据库添加新符号表。层表(AcDbLayTable)是符号表之一,它包含层表记录;块表(AcDbBlockTable)也是一个符号表,包含块表记录。所有AutoCAD实体都属于块表记录。
词典为存储对象提供了比符号表更加普通的容器。一个词典可以包含任何类型的AcDbObject及其子类的对象;当AutoCAD创建新图形时,AutoCAD数据库创建一个叫作“命名对象词典”的词典。对所有与数据库有关的词典,命名对象词典可以被视作为主“目录表”。我们可以在命名对象词典内刨建新词典,并在新词典中添加新数据库对象。
在AutoCAD编辑会话中,我们可以通过调用下面的全局函数来获得当前图的数据库。
  acdbHostApplicationServices()->workingDatabase()
3.1.1 多元数据库在一个AutoCAD会话中,可以加载多个数据库。在会话中的每个对象都有一个句柄和对象ID。在特定的数据库范围内句柄唯一地识别对象,而在一次加载的所有数据库内对象ID唯一地识别对象。对象ID只在一个编辑会话内存在,但句柄保存在图中。与对象ID相反,当在一个AutoCAD会话中加载多个数据库时,对象句柄不能保证是唯一的。
3.1.2 获取对象ID通过使用对象ID可以获得一个指向当前数据库对象的指针,所以可以对对象进行操作。
我们可以通过以下方法获得对象ID:
· 创建一个对象并将其添加到数据库中,然后数据库给对象一个ID并将其返回。
· 当创建数据库时,自动创建对象(比如一套固定的符号表和命名对象词典)的对象ID可使用数据库协议获得它们的对象ID。
· 使用类专用协议获得对象ID。某些类,如符号表和词典,可以定义拥有其他对象的对象,这些类提供获得其所有的对象ID的协议。
· 使用遍历器遍历一个对象表或对象系列。AcDb库提供了许多遍历器可以遍历各种容器对象(AcDbDictionarylterator.AcDbObjectIterator).
· 查询一个选择集。用户选择了一个对象后,可以要求选择集给出已选定对象的实体名表,并将实体名转换为对象ID。
3.2 基本数据库对象在AutoCAD中创建的对象被添加到数据库对应的容器对象中,实体被添加到块表的记录中,符号表记录被添加到相应的符号表中,所有其他的对象被添加到命名对象词典中,或添加到其他对象拥有的对象(拥有其他对象的对象最终属于命名对象词典)中,或添加到扩展词典中。
   可用的数据库必须至少应有下列对象:
    ·一套(九个)符号表,包括块表、层表和线型表。块表最初包含三记录,一个记录叫作 * MODEL_SPACE,两个图纸空间记录叫作***_SPACE和*** _SPACEO。这些块表记录表示模型空间和两个预先确定的图纸空间布局。层表最初包含一个0层记录。线型表最初包含CONTINUOUS线型。
·一个命名对象词典,当数据库被创建后,命名对象词典就已经包四个数据库词典:GROUP(组)词典、MLINE类型词典、布局词典和绘图式样名司典,在MIINE类型词典内,总有STANDARD类型。
   在一个新数据库中,若构造函数的buildDefaultDrawing变量值为kTrue,自动创建以上对象,若变量值为kFalse时,创建一个空的数据库,可以加载DWG或DXF文件。构造函数如下:

AcDbDatabase(Adesk::Boolean buildDefaultDrawing=Adesk::kTrue);

3.3 在AutoCAD中创建对象         本节介绍在AutoCAD中创建线、圆、层和组的方法,并演示AutoCAD如何将这些对象添加到数据库中的。首先我们使用如下命令在模型空间创建了一条直线:
   
在数据库中,AutoCAD创建一个AcDbLine类的实例,并将其存储在模型空间块表记录中。

当我们第一次运行AutoCAD时,数据库处于缺省状态,实体被添加到模型空间中,即AutoCAD中的主空间,它用于模型几何体和图形。图纸空间留作支持“文档”几何形式和图形,如图纸边界、属性块和注释文字。在AutoCAD中的创建实体命令(如前面创建的线),将实体添加到当前数据库中和模型空间块中。我们可查询实体属于哪一个数据库和哪一个块。
下面,使用Circle命令创建一个圆:

AutoCAD再次创建一个适当的实体(这里是AcDbCircle)实例,并将其添加到模型空间块表记录中。

接下来,创建一个层:

AutoCAD创建一新层表记录,然后将其添加到层表中。

最后,将所有实体归为一组:
AutoCAD创建一个新组对象并将其祷加到命名对象词典的GROUP词典中,新组包含组成该组的对象的对象ID表。

3.4在ObjectARX中创建对象本节具体不再详细介绍创建实体和符号表,请参见本书第二卷实例篇。
3.3.1 创建实体下列ObjectARX源程序代码创建直线并将其添加到模型空间块表记录中:
AcDbObjectId createLine()
{
    AcGePoint3d startPt(4.0,2.0,0.0);
   AcGePoint3d endPt(10.0,7.0,0.0);
   AcDbLine *pLine  = new AcDbLine(startPt,endPt);
   AcDbBlockTable *pBlockTable;
acdbHostApplicationServices()->workingDatabase()->getSymbolTable(pBlockTable,AcDb::kForRead);
   AcDbBlockTableRecord *pBlockTableRecord;
pBlockTable->getAt(ACDB_MODEL_SPACE,pBlockTableRecord,AcDb::kForWrite);
   pBlockTable->close();
   AcDbObjectId lineId;
   pBlockTableRecord->appendAcDbEntity(lineId,pLine);
   pBlockTableRecord->close();
   pLine->close();
   return lineId;
}
createLine()程序从当前图中获得块表,然后以写模式打开模型空间块表记录。关闭块表后,添加实体到块表记录并关闭块表记录和实体。
注意:当我们用完任何ObjectARX对象后,必须尽早地将其关闭。
下面的createCircle()程序创建一个圆并将其添加到模型空间块表记录中。
AcDbObjectId createCircle()
{
   AcGePoint3dcenter(9.0,3.0,0.0);
   AcGeVector3dnormal(0.0,0.0,1.0);
   AcDbCircle*pCirc  = newAcDbCircle(center,normal,2.0);
   AcDbBlockTable*pBlockTable;
acdbHostApplicationServices()->workingDatabase()->getSymbolTable(pBlockTable,AcDb::kForRead);
   AcDbBlockTableRecord*pBlockTableRecord;
pBlockTable->getAt(ACDB_MODEL_SPACE,pBlockTableRecord,AcDb::kForWrite);
   pBlockTable->close();
   AcDbObjectIdlineId;
   pBlockTableRecord->appendAcDbEntity(lineId,pCirc);
   pBlockTableRecord->close();
   pCirc->close();
   returnlineId;
}
3.3.2 创建新层下面的源程序代码从数据库中获得层符号表,创建一个新的层表记录,并将其命名为ASDK_MYLAYER,然后将层表记录添加到层表中。
void createNewLayer()
{
   AcDbLayerTable *pLayerTable;
   acdbHostApplicationServices()->workingDatabase()
       ->getSymbolTable(pLayerTable, AcDb::kForWrite);
   AcDbLayerTableRecord *pLayerTableRecord =
       new AcDbLayerTableRecord;
   pLayerTableRecord->setName("ASDK_MYLAYER");
   // 使用缺省值
   pLayerTable->add(pLayerTableRecord);
   pLayerTable->close();
   pLayerTableRecord->close();
}
3.3.3 打开和关闭ObjectARX对象本章的所有程序例子都说明了打开和关闭对象的协议,每当我们操作数据库驻留对象时都要遵守该协议。该协议确保当对象被访问时在物理内存中,面当对象未被访问时可以被分页存储到磁盘中。在我们可以修改对象之前,必须打开它,例如:
   acdbOpenObject(pObject, objId, AcDb::kForWrite);
   打开函数有一个模式参数,用来说明要打开的对象是用于读、写或是通知操作。当以写模式打开对象时,我们可以对其进行修改;当我们使用完对象后,必须如下例那样关闭对象,不管用什么模式打开对象,都以同一方式关闭:
pObject->close();
   下面是一个改变实体颜色的程序代码例子:
Acad::ErrorStatus changeColor(AcDbObjectIdentId, Adesk::UInt16 newColor)
{
    AcDbEntity *pEntity;
   acdbOpenObject(pEntity, entId,
       AcDb::kForWrite);
   pEntity->setColorIndex(newColor);
   pEntity->close();
   return Acad::eOk;
}
   一个对象的新实例在打开时,缺省是用于写操作的。某些函数,如AcDbBlockTable::getAt()函数,获得对象ID的同时打开对象。对象被添加到数据库之后才能将其关闭;在对象被添加到数据库之前,我们拥有对象并可随时删除对象。
   然而,一旦对象已经被舔加到数据库后,我们就不能够直接删除它;我们可以通过调用AcDbObject::erase()函数,将对象标记为删除;被标记为删除的对象仍然保留在数据库中,直到数据库被析构为止,但是当存图形时,不保存被标记为删除的对象。
   注意:如果直接删除已经被添加到数据库的对象,将导致AutoCAD终止。
3.3.4 在组词典中添加组
下列源程序代码将前面createLine()函数创建的线和createCircle()函数创建的圆创建为一个组(pGroup),并将其放进一个组词典中。线和圆的对象ID是通过参数传入函数的。请注意下面程序是如何以写模式打开组词典,进行修改,然后将其关闭的。
void createGroup(AcDbObjectIdArray&objIds, char* pGroupName)
{
   AcDbGroup *pGroup = new AcDbGroup(pGroupName);
   AcDbDictionary *pGroupDict;
   acdbHostApplicationServices()->workingDatabase()
       ->getGroupDictionary(pGroupDict, AcDb::kForWrite);
   AcDbObjectId pGroupId;
   pGroupDict->setAt(pGroupName, pGroup, pGroupId);
   pGroupDict->close();
   for (int i = 0; i < objIds.length(); i++) {
       pGroup->append(objIds);
       }
   pGroup->close();
}
3.5 创建和移植数据库
我们使用new来新建数据库、使用delete来删除数据库。AcDbDatabase构造函数有一个变量,其缺省值为Adesk::kTrue。如果该变量值为Adesk::kTrue,数据库将由标准数据库对象移植而来。如果该变量值为Adesk::kFalse,将创建一个空数据库,可以通过读人图形文件移植。
使用如下函数读人图形文件:

Acad::ErrorStatusAcDbDatabase::readDwgFile(ACHAR* fileName);

如果我们收到如下任何错误码,一般要通过用户接口使用标准AutoCAD恢复机制恢复图形。
   kDwgNeedsRecovery   kDwgCRCDoesNotMatch   kDwgSentinelDoesNotMatch   kDwgObjectImproperlyRead
   注意:不能删除由acdbHostApplicationServices()->workingDatabase()函数返回的数据库。
3.6存储数据库         使用AcDhDatabase::saveAs()函数保存数据库:
   Acad::ErrorStatusAcdbDatabase::saveAs(ACHAR* fileName);
   文件名可以是指向本地文件的路径或网址。
3.6.1 设置缺省文件格式ObjectARX为SAVEAS、SAVE和QSAVE命令提供了指定缺省文件格式的能力 。
  AcApDocument类包含了定义图文件存储格式的枚举。枚举值如下表所列。

AcApDocument::formatForSave()函数返回用于SAVEAS、SAVE和QSAVE命令的当前保存格式。
返回值既可以是会话期的缺省设置,也可以是用户为该文档选定的其他设置。如果设置是该文档的一个重载,则它将不会被保留至下一个会话。
AcApDocmanager::setDefaultFormatForsave()函数使用一种SaveFmmat保存格式设置文件格式,当SAVEAS、SAVE和QSAVE命令存储图时,就使用巳设置的文件格式。该函数设置会话期的缺省值,用户可以选择会话期的缺省值暂时重载一个文档。
这些函数只直接报告文件格式或为用户输入的交互命令设置文件格式。如果每次要保存数据库时,想要使我们的应用程序使用当前保存格式,就必须首先调用formatForSave(),然后使用返回的SaveFormat值决定调用哪一个函数。例如,如果formatForSave()返回kR14一dxf,我们应该调acdbDxfOutAsRl4(),将数据库写为R14DXF文件。
必须要考虑下列一些问题:

A我们和用户可以设置永久的会话期缺省格式,所有保存命令都将遵从该保存会话期缺省格式。

B只有用户才能为特殊文档临时(在会话之间是非永久的)重载该设置。

CformatForSave()函数返回用户希望保存单个文档的格式,该格式既可以是会话期的缺省值,也可以是临时重载值。

3.7写块操作本节实例请参见本书第二卷实例篇
AcDbDatabase类包含一个重载的wblock()函数,whlock()函数有三种形式分别对应于AutoCAD WBLOCK命令的三个选项。
3.7.1 设置缺省文件格式
下面的函数与WBLOCK* 命令等价:

Acad::ErrorStatusAcDbDatabase::wblock(AcDbDatabase*& newDb);

该函数从已调用的数据库创建新数据库。输人数据库中的任何非引用的符号在新数据库中都被省略(使得新数据库比原数据库简洁而且小);然而,该函数不会复制应用程序定义的其隶属关系在命名对象词典中的对象;我们需要使用AcEditorReactor通知函数,将应用程序数据从源数据库传输到目标数据库
3.7.2 创建带有实体的新数据库
另外两种形式的AcDbDatabase::wblock()函数创建的新数据库,其模型空间块表记录包含从输人数据库来的指定实体。第一种形式从命名块表记录拷贝实体,第二种形式拷贝实体阵列。
3.7.2.1 复制命名块
下面的函数与带有指定块名的WBLOCK命令等价:
Acad::ErrorStatus AcDbDatabase::wblock(AcDbDatabase*& pOutputDb,AcDbObjectId blockId);
blockId变量表示一个在输人数据库中的块表记录,在该块表记录中的实体被拷人新建数据库的模型空间块表记录中,新数据库的插入点是块表记录的原点。
3.7.2.2 复制实体阵列
下面函数等价于调用WBLOCK命令,然后使用选项选择特定对象和指定插入点:
Acad::ErrorStatusAcDbDatabase::wblock(    AcDbDatabase*& pOutputDb,    const AcDbObjectIdArray& outObjIds,    const AcGePoint3d* basePoint);
此函数创建一个新数据库,新数据库包含在pOutputDb变量中指定的实体。在输人数据库内的实体模型空间或图纸空间块表记录中的实体,被放置在新数据库的模型空间中。在新数据库中也包含由这些实体拥有或引用的对象,以及这些对象的所有者。指定点是新图的通用坐标的原点(也就是在新数据库模型空间中的插入基点)。
3.8插入数据库         AcDbDatabase::insert()函数将一个数据库拷入该函数被调用的数据库,AutoCAD合并其定义的对象(如MLINE线型和CROUP词典)。然而,对于应用程序定义的那些隶属关系在命名对象词典中的对象,该函数不进行复制;我们需要使用AcEditorReactor通知函数,将应用程序数据从源数据库传输到目标数据库。insert()函数执行深层克隆。
   如果当源数据库和目标数据库在被合并时出现冲突(例如,如果两个数据库都有相同的线型名),AutoCAD将使用目标数据库中的版本。
  如下函数与标准的INSERT命令等价:
Acad::ErrorStatusAcDbDatabase::insert(    AcDbObjectId& blockId,    const char* pBlockName,    AcDbDatabase* pDb,    bool preserveSourceDatabase = true);
   该函数从输人数据库的模型空间(pDb)拷贝实体到指定的块表记(pBlockName),并返回新块表记录的ID(blockId)。应用程序必须随后为块表记录创建引用,并将其加到数据库中。
如下函数与AutoCAD的一个INSERT*命令等价:
Acad::ErrorStatusAcDbDatabase::insert(    const AcGeMatrix3d& xform,    AcDbDatabase* pDb,    bool preserveSourceDatabase = true);
该函数从输人数据库的模型空间(pDb)拷贝出实体,对实体进行指定的变换(xform)后,复制到新数据库的当前空间(图纸空间或模型空间)中。
3.9 设置数据库当前值
如果没有给实体指定数据属性,例如颜色或线型,就使用该数据的数据库当前值。下面简单介绍用于指定与数据库有关的当前数据值的函数。
3.9.1 数据库的颜色值
如果未给实体指定颜色,就使用数据库的当前颜色值。当前颜色值储存在CECOLOR系统变量中。如下函数设置和返回在数据库中的当前颜色值:
Acad::ErrorStatus AcDbDatabase::setCecolor(    const AcCmColor& color); AcCmColor AcDbDatabase::cecolor() const;3.9.2数据库的线型值         如下函数设置和返回在数据库中的当前线型值:
Acad::ErrorStatus AcDbDatabase::setCeltype(    AcDbObjectId objId); AcDbObjectId AcDbDatabase::celtype() const;3.9.3数据库的线型比例值
数据库有三个线型比例设置:
A 当前实体的线型比例设置,储存在CELTSCALE系统变量中。
B 当前图的线型比例设置,储存在LTSCALE系统变量中。
C 使用何种比例设置的标记,用于表示是在实体所在空间中使用线型比例,还是在图纸空间内实体显示时使用线型比例,该设置存储在PSLTSCALE系统变量中。
当重新生成图时,使用全局LTSCALE和PSLTSCALE设置。使用下面的函数设置和查询这些设置值:
Acad::ErrorStatusAcDbDatabase::setLtscale(double scale); double AcDbDatabase::ltScale() const; Acad::ErrorStatusAcDbDatabase::setCeltscale(double scale); double AcDbDatabase::celtscale() const; Acad::ErrorStatusAcDbDatabase::setPsltscale(bool scale) bool AcDbDatabase::psltscale() const;3.9.3数据库的层值
下面的函数设置和返回数据库中的当前层值:
Acad::ErrorStatusAcDbDatabase::setClayer(    AcDbObjectId objId); AcDbObjectId AcDbDatabase::clayer() const;3.10 数据库操作的简单例子
下例中的creareDwg()程序创建了一个新数据库,取得模型空间块表记录,创建两个圆并添加到模型空间中;该程序使用AcDbDatabase::saveAs()函数保存图。第二个程序readDwg(),读人既有图,打开模型空间块表记录并遍历它,打印其包含的实体名。
void
createDwg()
{
   AcDbDatabase *pDb = new AcDbDatabase();
   AcDbBlockTable *pBtbl;
   pDb->getSymbolTable(pBtbl, AcDb::kForRead);
   AcDbBlockTableRecord *pBtblRcd;
   pBtbl->getAt(ACDB_MODEL_SPACE, pBtblRcd,
       AcDb::kForWrite);
   pBtbl->close();
   AcDbCircle *pCir1 = new AcDbCircle(AcGePoint3d(1,1,1),
                                      AcGeVector3d(0,0,1),
                                       1.0),
               *pCir2 = newAcDbCircle(AcGePoint3d(4,4,4),
                                       AcGeVector3d(0,0,1),
                                       2.0);
   pBtblRcd->appendAcDbEntity(pCir1);
   pCir1->close();
   pBtblRcd->appendAcDbEntity(pCir2);
   pCir2->close();
   pBtblRcd->close();
   // AcDbDatabase::saveAs() does
not
automatically
   // append a DWG file extension, so it
   // must be specified.
   //
   pDb->saveAs(_T("c:\\test1.dwg"));
   delete pDb;
}
void
readDwg()
{
   // Set constructor parameter to kFalse so that the
   // database will be constructed empty. This way only
   // what is read in will be in the database.
   //
   AcDbDatabase *pDb = new AcDbDatabase(Adesk::kFalse);
   // The AcDbDatabase::readDwgFile() function
   // automatically appends a DWG extension if it is not
   // specified in the filename parameter.
   //
   if(Acad::eOk != pDb->readDwgFile(_T("c:\\test1.dwg")))
   return;
   // Open the model space block table record.
   //
    AcDbBlockTable*pBlkTbl;
   pDb->getSymbolTable(pBlkTbl, AcDb::kForRead);
   AcDbBlockTableRecord *pBlkTblRcd;
   pBlkTbl->getAt(ACDB_MODEL_SPACE, pBlkTblRcd,
       AcDb::kForRead);
   pBlkTbl->close();
   AcDbBlockTableRecordIterator *pBlkTblRcdItr;
   pBlkTblRcd->newIterator(pBlkTblRcdItr);
   AcDbEntity *pEnt;
   for (pBlkTblRcdItr->start(); !pBlkTblRcdItr->done();
       pBlkTblRcdItr->step())
   {
       pBlkTblRcdItr->getEntity(pEnt,
            AcDb::kForRead);
       acutPrintf("classname: %s\n",
           (pEnt->isA())->name());
       pEnt->close();
   }
   pBlkTblRcd->close();
   delete pBlkTblRcdItr;
   delete pDb;
}
3.11 长事务
长事务用于支持AutoCAD引用编辑功能,它对ObjectARX应用程序的用处非常大。这些类和函数提供一个表,为应用程序检出要编辑的实体,编辑后将其放回原来的位置。该操作使用编辑后的对象替换原对象。长事务检出有三类:

A从同一图内的普通块。

B从图的外部引用(xref)。

C从具有无关性的、临时数据库。

3.11.1 类和函数概述
主要类和函数:

1AcDbLongTransaction类。

2AcDbLongTransWorkSetIterator类。

3AcAplongTransactionReactor类。

4AcApLongTransactionManager类。

5AcDbDatabase::wblockCloneObjects()函数。

3.11.1.1 AcDbLongTransaction类AcDbLongTranscation类包含需要跟踪长事务的信息。AcDbLongtransactionManager类创建AcDbLongTransaction对象并将其掭加到数据库中,然后返回AcDbLongTransaction对象的AcDbObjectid。与所有其他数据库驻留对象一样,由数据库执行其析构过程。
注意的是:当AcDbLongTranscation对象被激活时,就被添加到数据库中,一旦事务完成,AcDbLongTranscation对象就被删除。它们不会被存储在DWG或DXF文件中。因此不是永久的。
3.11.1.2 AcDbLongTransWorkSetIterator         AcDbLongTransWorkSetiterator类对工作序列中的对象提供只读访问。在AcDbLongTransaction::newWorkSetItrerator()的构造过程中,它可以被设置为只包含活动工作集,或包含添加到工作集的对象,因为在工作集中的对象(辅助对象)要引用这些对象也可以通过AcDbLongTransaction::removeFromWorkSet()函数或直接删除,从工作序列中删除对象。
3.11.1.3 AcApLongTransactionReactor类AcApLongTransactionReactor类为长事务操作提供通知特性。它被用来与深层克隆通知相关联,该深层克隆通知也将被传送,但将随哪一类型的检出/检入被执行而改变。要连接这些通知和深层克隆通知,通过调用AcDbLongTransaction::activeIdMap()函数,可以返回用于克隆的AcDbIdMapping对象。
3.11.1.4 AcApLongTransactionManager         AcApLongTransactionManager类是启动和控制长事务的管理器。每个AutoCAD会话只有一个AcApLongTransactionManager,并且通过acapLongTransactionManager对象返回的指针访问它。
3.11.1.5 AcDbDatabase::wblockCloneObjects()函数wblockCloneObjects()函数是AcDbDatase的一个成员。它将从一个数据库深层克隆对象到另一个数据库,所有依赖性对象也被克隆。当发现重复时,符号表记录的行为由类型参数决定。下表给出了符号表类型(enum-DuplicateRecmdaoning)和深层克隆类型(enum DeepClone)的关系。
  
  
  
  
  
  
  
命令或API函数
  
  
深层克隆类型
  
  
重复记录克隆说
  
  
COPY
  
  
kDcCopy
  
  
kDrcNotApplicable
  
  
EXPLODE
  
  
kDcExplode
  
  
kDrcNotApplicable
  
  
BLOCK
  
  
kDcBlock
  
  
kDrcNotApplicable
  
  
INSERT/BIND
  
  
kDcXrefInsert
  
  
kDrcIgnore
  
  
XRESOLVE
  
  
kDcSymTableMerge
  
  
kDrcXrefMangleName
  
  
INSERT
  
  
kDcInsert
  
  
kDrcIgnore
  
  
insert()
  
  
kDcInsertCopy
  
  
kDrcIgnore
  
  
WBLOCK
  
  
kDcWblock
  
  
kDrcNotApplicable
  
  
deepCloneObjects()
  
  
kDcObjects
  
  
kDrcNotApplicable
  
  
wblockObjects()
  
  
kDcObjects
  
  
kDrcIgnore
  
  
wblockObjects()
  
  
kDcObjects
  
  
kDrcReplace
  
  
wblockObjects()
  
  
kDcObjects
  
  
kDrcMangleName
  
  
wblockObjects()
  
  
kDcObjects
  
  
kDrcUnmangleName
  
3.12 长事务实例

下面这个简单的例子演示如何从另一个数据库检出实体,在当前数据库中修改它们,然后将它们返回到原来的数据库。

voidrefEditApiExample(){AcDbObjectId transId;AcDbDatabase* pDb;TCHAR *fname;struct resbuf *rb;    // 获取用户选择的DWG文件.    //    rb = acutNewRb(RTSTR);    int stat = acedGetFileD(_T("选择一个图形文件"), NULL, _T("dwg"), 0, rb);if ((stat != RTNORM) || (rb == NULL)) {        acutPrintf(_T("\nYou must pick a drawing file."));        return;    }fname = (TCHAR*)acad_malloc((_tcslen(rb->resval.rstring) + 1) * sizeof(TCHAR));    _tcscpy(fname, rb->resval.rstring);    acutRelRb(rb);// 打开DWG文件    //    pDb = new AcDbDatabase(Adesk::kFalse);    if (pDb->readDwgFile(fname) != Acad::eOk) {        acutPrintf(_T("\n抱歉打开DWG文件失败."));        return;    }    // 获得块表和模型空间记录.    //    AcDbBlockTable *pBlockTable;    pDb->getSymbolTable(pBlockTable, AcDb::kForRead);    AcDbBlockTableRecord *pOtherMsBtr;    pBlockTable->getAt(ACDB_MODEL_SPACE, pOtherMsBtr,      AcDb::kForRead);    pBlockTable->close();    // 建立遍历器    //    AcDbBlockTableRecordIterator *pIter;    pOtherMsBtr->newIterator(pIter);    // 建立对象阵列    //    AcDbObjectIdArray objIdArray;    // 遍历模型空间BTR,查找特定的线,将其ID加入阵列    //    for (pIter->start(); !pIter->done(); pIter->step()) {        AcDbEntity *pEntity;        pIter->getEntity(pEntity, AcDb::kForRead);        // Look for only AcDbLine objects and add them to the         // object ID array.        //        if (pEntity->isKindOf(AcDbLine::desc())) {            objIdArray.append(pEntity->objectId());        }        pEntity->close();    }    delete pIter;    pOtherMsBtr->close();    if (objIdArray.isEmpty()) {        acad_free(fname);        acutPrintf(_T("\nYou must pick a drawing file that contains          lines."));        return;    }        AcDbBlockTable *pThisBlockTable;    acdbHostApplicationServices()->workingDatabase()->      getSymbolTable(pThisBlockTable, AcDb::kForRead);    AcDbBlockTableRecord *pThisMsBtr;    pThisBlockTable->getAt(ACDB_MODEL_SPACE, pThisMsBtr,      AcDb::kForWrite);    pThisBlockTable->close();    AcDbObjectId id = pThisMsBtr->objectId();    pThisMsBtr->close();    // Create the long transaction. This will check all the entities     // out of the external database.    //    AcDbIdMapping errorMap;    acapLongTransactionManagerPtr()->checkOut(transId, objIdArray,      id, errorMap);    // Now modify the color of these entities.    //    int colorIndex;    acedGetInt(_T("\nEnter color number to change entities to: "),      &colorIndex);    AcDbObject* pObj;    if (acdbOpenObject(pObj, transId, AcDb::kForRead) == Acad::eOk) {        // Get a pointer to the transaction.        //        AcDbLongTransaction* pLongTrans =          AcDbLongTransaction::cast(pObj);        if (pLongTrans != NULL) {            // Get a work set iterator.            //            AcDbLongTransWorkSetIterator* pWorkSetIter;            pLongTrans->newWorkSetIterator(pWorkSetIter);            // Iterate over the entities in the work set and change            // the color.            for (pWorkSetIter->start(); !pWorkSetIter->done();               pWorkSetIter->step()) {                AcDbEntity *pEntity;                acdbOpenAcDbEntity(pEntity, pWorkSetIter->objectId(),                  AcDb::kForWrite);                pEntity->setColorIndex(colorIndex);                pEntity->close();            }            delete pWorkSetIter;        }        pObj->close();    }    // Pause to see the change.    //    TCHAR str[132];    acedGetString(0, _T("\nSee the new colors. Press return to check      the object into the original database"), str);    // Check the entities back in to the original database.    //    acapLongTransactionManagerPtr()->checkIn(transId, errorMap);    // Save the original database, since we have made changes.    //    pDb->saveAs(fname);    // Close/Delete the database    //    delete pDb;    pDb = NULL;    acad_free(fname);}3.13 外部参照
    通过几个模仿AutoCAD XREF命令功能的全局函数,我们可以创建和操作外部引用(xrefs)。这几个函数是:
  acedXrefAttach()
acedXrefOverlay()
acedXrefUnload()
acedXreIDetach()
acedXrefReload()
acedXreIBind()
acedXrefXBind()
acedXrefCreateBlockname()
acedXrdReload()
   外部引用编程时主要要考虑的是,对每个附属在图中的外部引用,创建一个单独的数据库代表包含外部引用的图形文件。主图中的块表记录,包含外部图名并指向外部引用图模型空间的实体。外部引用数据库也包含其他块表记录和需要从主块表记录(层、线型等)分解所有引用的符号表输入项。
我们可以创建一个编辑器反应器监视外部引用事件, AcEditorReactor类提供如下反应器回调函数:
beginAttach()
otherAttach()
abortAttach()
endAttach()
redirected()
comandeered()
   当使用这些函数时,必须注意返回了哪一个数据库,也需要知道外部引用图可以将它本身的外部引用包含在附加图中。
可以修改图中外部引用实体,但不能把它们存回到原外部引用图中(原图是只读文件)。
3.13.1 外部参照的前、后处理          外部引用的前、后处理可以恢复附属在外部引用的存储器中的AcDbDatabase,从而可以将其存人文件。当外部引用分解时就会损坏许多符号表记录,并且有一些被删除。事实上,这样做简化了分解过程,也是可以接受的,因为数据库是只读的。该分解过程可以临时返回分解后的结果,从而可以修改外部引用数据库并将其写回到文件中。
   在AcDbDatabase中加入了前、后处理的辅助函数,它们包含一个从外部引用数据库查找关联块表记录的实用函数、恢复分解外部引用的函数和恢复后将其重新设置回合适的分解条件的函数。
这些函数的习惯用法应当是恢复原来的符号,修改数据库,保存数据库,然后恢复以前的符号表。这些步骤必须写进单个代码块,防止再生成主图、执行任何外部引用命令或者当外部引用数据库处于它的恢复条件时提示用户,这些函数是
AcDbDatabase::xrefBlockId()
AcDbDatabase::restoreOriginalXrefSymbols()
AcDbDatabase::restoreForwardingXrefSymbols()
3.13.1.1 AcDbDatabase::xrefBlockId()函数这个函数将获得块表记录的AcDbObjectId,它把该数据库当作一个外部引用,当外部引用无论何时被重新加载(例如,使用XREF Reload或XREF Path命令),前一个数据库被保存在内存以备以后恢复。这就意味着至少有一个以上数据库指向同一外部引用块表记录。但无论如何对该记录来说,只有一个是当前活动的外部引用数据库。在已发现的记录上由AcDbBlockTableRecord::xrefDatabase()函数返回的数据库指针将是该外部引用的活动数据库。
3.13.1.2 AcDbDatabase::restoreOriginalXrefSymbols()函数这个函数将已分解的外部引用数据库恢复到它的原来形式,就好像刚从它的文件调用样。然后可以修改外部引用并存回文件。调用该函数之后,主图就处于
无效状态,不能再被生成,也不能被任何外部引用命今修改或重新加载。数据库的修改、存回和restoreForwardingXrefSymbols()函数的调用,必须在执行任何再生成命令之前进行。
3.13.1.3AcDbDatabase::restoreForwardingXrefSymbols()函数         此函数将外部引用恢复回有效状态,即可添加状态。它不仅恢复原来的分解符号,而且找出新加入的符号并分解它们。该函数只能处理那些新加入的、嵌套
的已经存在外部引用块表记录,并在主图中被分解的符号。
3.13.2 文件锁定和一致性检查         AcDbXrefFileLock基类用于外部引用文件锁定的管理。尽管它可以用于其他目的,但其主要目的是在图中为内部编辑准备外部引用块。它假设这些外部引用方法对当前数据库图进行操作。
   acdbXrefReload()全局函数为外部引用的重新加载而处理外部引用的块表记录对象ID表。它假设每个外部引用块表记录对象ID引用一个可以重新加载人当前图的外部引用图文件。它与用于重新加载的AutoCADXREF子命令的功能相同。
3.14 索引和筛选器索引和筛选器的类及其函数,用来定义应用程序自定义索引和自定义块数据的筛选操作。应用程序可以定义它自己的AcDbFilter、AcDbindex和AcDbFilteredBlocklterator,它将通过AcIndexFilterManager::addFilter()函数使用块引用注册AcDbFilter或者通过AcIndexFilterManager::addlndex()函数便用相应块表记录注册AcDbindex。然后,再生成外部引用和块时,将考虑
AcDbFilter定义的查询,使用AcDbFilterdBlocLdterator决定在再生成过程中处理那个对象ID。索引一直保持到应用程序明确地调用了AcIndexFilterManager::updateIndexes()为止,或到应用程序在AcDbDatabase保存时通过AutoCAD保存操作调用了AcindexFilterManager::updateIndexes()为止。
   在AcIndexFilterManager::updateIndexes()调用过程中,会执行函数:
   ACDbIndex::rebuildFull()
    或AcDbIndex::rebuildModified()
    这些类和函数为下列功能提供了接口:
    A更新索引。
    B向块表记录添加和删除索引。
    C向块引用舔加和删除筛选器。
    D从块表记录查询索引。
    E从块引用查询筛选器。
    F遍历块表记录并只访问实体子集。
    主类和函数包括:
   AcDbIndexFilterManager名空间。
   AcDbIndex类。
   AcDbFilter类。
   AcDbFilteredBlocklterator粪。
AcDbCompositeFilteredBlockIterator类。
3.14.1.1 AcDbIndexFilterManager名空间         这个命名空间是一个函数集,它提供索引和筛选器的访问和维护功能。
3.14.1.2 AcDbIndex类   AcDbIndex类是所有索引对象的基类,AcDbSpatialIndex和AcDbLayerIndex派生于该类。索引的更新可以由应用程序或者AutoCAD通过调用下述函数来完成:
AcDbIndexFilterManager::updateIndexes()
AcDbFilteredBlockIterator用于访问所有的AcDbObjectIds,例如,在空间索引的情况下,传递给newIterator()函数的AcDhSpatiaIFilter对象实例定义了一个查询区域。AcDbSpatialIndex对象,通过其newIterator()函数,将提供一个AcDhSpatialIndexIterator用来返回在查询卷内合适的实体对象Id。
3.14.1.3 AcDbFilter类
AcDbFilter类必须定义一个查询它AcDbCompositeFilteredBlockiterator提供了一个“关键字”,通过indexClass()函数获得一个对应的索引。
3.14.1.4AcDbFilteredBlockIterator类
提供在索引中“查询”的方法,它供AcDbCompositeFiltedBlockIterator
使用。
3.14.1.5AcDbCompositeFilteredBlockIterator类提供对普通块遍历的选择靠init()函数法提供筛选器列表,AcDbCompositeFilteredBlockirerator对象通过AcDbFilter::indexClass()函数寻找对应的AcDbIndex派生对象,并创建AcDbFilteredBlockIterator对象。如果投有匹配的最新indexClass()对象,它通过AcDbFilter::newIterator()函数创建AcDbFilteredBlockiterator。然后基于AcDbFilteredBlockIterator::estimatedHits()AcDbFilteredBlockIterator::buffenForComposnion()函数,确定AcDbFilteredBlockiterator对象的成分。筛造器集是一个条件的组合,这就意味着,仅当每个筛选器的accepts()函数接受对象ID时,该对象的ID才从iterator()输出。
3.15 图形摘要信息    图形属性对话框允许AutoCAD用户在他们的DWG文件中嵌入辅助数据(称为摘要信息),并且借助于这些数据检索DWG文件。它为AutoCAD用户这提供了基本的文件检索和管理能力。
   通过Windows资源管理器,可以在AutodCAD环境外查看图形的属性。使用AutoCAD设计中心的高级查找的特性,通过摘要信息,用户可以查找图形包含的预先定义或自定义的数据。
   DatabaseSummaryInfo、cDbSummaryInfoReactor和cDbSummaryInfoManager类提供了一个API与摘要信息一起工作。
3.15.1 AcDbDatabaseSummaryInfo         AcDbDatahaseSummaryInfo类可以封装一系列字符串,将其作为附加信息加到DWC文件中。这些字符串的最大长度是511个字符,每个信息都以专用的函数存储在摘要信息对象的每个信息域中。
   预先定义的域有:
    A标题。
    B主题。
    C作者。
    D关键字。
    E备注。
    F上次存盘时间。
    G修订号。
H超链接基础。
除了预先定义的域外,我们可以创建自己的自定义域。这些自定义被存储在一个列表中,可以根据它们的名字(或键),也可以根据在列表中的位置(索引)操纵自定义域。自定义域序号从1开始,可以创建的域编号没有限制。
3.15.2 AcDbSummaryInfoReactor   该类提供一个反应器,通过该类获知摘要信息是否已改变。
3.15.3 AcDbSummaryInfoManager该类用于组织管理摘要信息反应器。其函数用来添加和删除反应
器及传送摘要信息已改变的通知。
3.16 图形摘要信息如果确定数据库上次是由Autodesk软件(如AutoCAD或AutoCAD LT*)存盘的,函数dwgFileWasSavedByAutodeskSoftware()返回Adesk::kTrue。


















第四章  数据库对象这一章介绍所有涉及AutoCAD数据库的对象,包括实体、符号表记录和词典。主要概念包括打开和关闭对象、管理内存中的对象、对象隶属关系和使用扩展数据或对象的扩展词典的扩展对象。也介绍其他的一般对象操作,如文件编档和删除的操作。
4.1 打开和关闭数据库对象可以通过三种不同方法找到一个AcDbObject对象:
A 从句柄。
B 从对象ID。
C 从C++实例指针。
当没有运行AutoCAD时,图存储在文件系统中,对象包台在DWG文件内,由其句柄识别。图形打开后,图信息可通过AcDbDatabase对象访问,每个对象在数据库中都有一个对象ID,它从对象一建立就一直存在于当前编辑会话中,直到对象所在的AcDbDatabase被删除为止。打开函数将对象ID作为一个变元并返回一个指向AcDhObject对象的指针,该指针在关闭对象以前一直有效。

我们可以用acdbOpenOhject()函数打开对象。我们可以用下面这个函数getAcDbObjectId()函数将一个句柄映射到对象ID。
我们也可以用下面这个函数打开对象,然后获取其句柄:
   AcDbObject*pObject;
   AcDbHandlehandle;
   pObject->getAcDbHandle(handle);
注意:无论何时打开数据库对象,应尽可能早的关闭。可以使用AcDbObject::close()函数关闭数据库对象。
ads_name与AcDbObjectId是等价的。AcDb库提供两个独立的函数,允许我们在AcDbObjectId和ads_name之间转换:
// 返回一个给定对象的ID的ads_name.
//
acdbGetAdsName(ads_name&objName,
               AcDbObjectId objId);
//返回一个给定ads_name的对象ID
//
acdbGetObjectId(AcDbObjectId&objId,
                ads_name objName);
一般来说,我们通过选择莸得对象,并且以ads—name形式返回。然后,我们需要将ads_name转换为AcDhObjectId并打开它。下列函数示范了这一过程
AcDbEntity*
selectEntity(AcDbObjectId& eId, AcDb::OpenMode openMode)
{
    ads_name en;
    ads_point pt;
    acedEntSel("\nSelect anentity: ", en, pt);
    // Exchange the ads_name for anobject ID.
    //
    acdbGetObjectId(eId, en);
    AcDbEntity * pEnt;
    acdbOpenObject(pEnt, eId,openMode);
    return pEnt;
}
我们可以用如下三种模式之一打开对象:
kForRead
如果没有以写模式或通知模式打开对象,对象最多可以同时供256个阅读者以读模式 打开,
kForWrite
如果对象没有被打开,可以以写模式打开。如果对象已被打开,则打开失效。
kForNotify
如果对象未被打开或者已经以读或写模式打开,但没有以通知模式打开,可以以通知模式打开。应用程序将很少需要以通知模式打开对象并向其发送通知。
若对象已被打开,我们以不同模式打开时返回的信息代码。
  
不同方式打开已被打开的对象返回的信息代码
  
  
  
  
  
对象已打开模式
  
  
kForRead
  
  
kForWrite
  
  
kForNotify
  
  
读模式打开
  
  
eAtMaxReaders
  
(if readCount = 256; otherwise succeeds)
  
  
eWasOpenForRead
  
  
(Succeeds)
  
  
写模式打开
  
  
eWasOpenForWrite
  
  
eWasOpenForWrite
  
  
(Succeeds)
  
  
通知模式打开
  
  
eWasOpenForNotify
  
  
eWasOpenForNotify
  
  
eWasOpenForNotify
  
  
正在发送通知
  
  
(Succeeds)
  
  
eWasNotifying
  
  
eWasNotifying
  
  
撤销
  
  
eWasOpenForUndo
  
  
eWasOpenForUndo
  
  
(Succeeds)
  
当我们以写模式打开对象时收到了eWasOpenForRead错误,如果只有一个阅读者打开了对象,我们可以使用upgradeOpen()打开模式升级为写;写完后我们使用downgradeOpen()将写模式降级为读模式。同样地,当我们的对象是以通知模式打开的,如我们正在接收通知,我们叉想以写模式打开该对象,可以使用upgradeFromNotify()将打开模式升级为写;写完后我们使用downgradeToNotify()将写模式降级为通知模式。
4.2 删除对象使用AcDbObject::new()函数,我们可以创建一个准备添加到数据库的AcDbObject对象实例。当一个对象刚被创建还没有被加入数据库,我们就可以删除它。然而,一旦对象已经被加入数据库,我们就不能删除它;AutoCAD管理所有数据库驻留对象的删除操作。
4.3 对象的数据库隶属关系         如果对象间接属于数据库而不是另一个数据库对象,称为根对象。数据库包含十个基本对象:九个符号表和命名的对象词典。所有编档操作都从数据库的根对象开始。
除了基本对象,数据库中的每个对象都必须有一个所有者,并且一个对象只能有一个所有者。数据库是一个由对象隶属关系结构组成的树。下列调用向数据库添加一个对象并为其分配ID,但对象还没有一个所有者:
db->addAcDbObject(...);
通常,我们使用成员函数将对象加到它的所有者内,同时将它加到数据库中,比如AcDbBlockTableRecord::appendAcDbEntity()函数,一次完成上述两个任务。
AutoCAD的隶属关系结构如下:
块表记录拥有实体。
每个符号表拥有一个具体类型的符号表记录。
AcDbDictionary对象可以拥有任何AcDbObject对象。
任何AcDbObject对象可以有一个扩展词典;对象拥有其扩展词典。
另外,应用程序也可以建立它们自己的隶属关系结构。
4.4 扩展数据         扩展数据(xdata)是由应用程序通过ObjectARX或AutoLISP建立并掭到任何对象上。扩展数据包含一个应甩程序使用的结果缓冲区(resbuf)链接表(AutoCAD保持该信息,但不使用它)。数据与在1000至1071范围内的DXF组码相对应。
   此方法节省空间,在向对象添加少量数据时是很有用的。然而,扩展数据不能超过16KB.其数据只能在既有的DXF组码和类型范围内。
   使用AcDbDatabase::Xdata()函数获取一个包含某扩展数据的结果缓冲区表:
virtual resbuf* AcDbObject::xData(const ACHAR* regappName = NULL) const;
使用AcDbObject::setXData()函数为一对象指定扩展数据:
virtual Acad::ErrorStatus
AcDbObject::setXData(const resbuf* xdata);
下面一个一个代码使用xData()函数从选定的对象获取扩展数据,并将扩展数据显示在屏幕上,然后将目标字符串添加到扩展数据,最后调用setXdata()函数修改对象对象的扩展数据。这个例子也演示了upgradeOpen()函数和downgradeOpen()函数的使用方法。
////////选择对象////////////////
AcDbObject*selectObject(AcDb::OpenMode bMode)
{
   ads_name en;
   ads_point pt;
   if (acedEntSel(NULL,en,pt)!=RTNORM)
   {
     return;
   }
   AcDbObjectId entId;
   AcDbObject* pObj;
   acdbOpenObject(pObj,entId,bMode);
   return pObj;
}
// 这个函数调用selectObject()函数提示用户选择对象,然后访问对象的扩展数据
// 最后调用printList()函数,显示结果类型(restype)和结果(resval)值
void
printXdata()
{
   // 选择并打开对象
   //
   AcDbObject *pObj;
   if ((pObj = selectObject(AcDb::kForRead)) == NULL){
     return;
   }
   // 输入扩展数据的应用程序名
   //
   ACHAR appname[133];
   if (acedGetString(NULL,
     L"\n输入想要获取扩展数据的应用程序名: ",
     appname) != RTNORM)
   {
     return;
   }
   // 获取应用程序名的扩展数据.
   //
   struct resbuf *pRb;
   pRb = pObj->xData(appname);
   if (pRb != NULL) {
     //如果扩展数据为空,注册应用程序并将appName添加到表的第一个结果缓冲区中
     //注意:不能像在AutoLISP中有-3组码,这是唯一的扩展数据,因此不需要以-3
     //作为扩展数据开始标记
   
     printList(pRb);
     acutRelRb(pRb);
   }else {
     acutPrintf(L"\n没有发现扩展数据注册名");
   }
   pObj->close();
}
void
addXdata()
{
   AcDbObject* pObj= selectObject(AcDb::kForRead);
   if (!pObj) {
     acutPrintf("选择对象出错\n");
     return;
   }
   // 获取应用程序名和要添加到扩展数据中的字符串
   //
   ACHAR appName[132],resString[200];
   appName[0] = resString[0]= '\0';
   acedGetString(NULL,L"输入应用程序名: ",
     appName);
   acedGetString(NULL,L"输入要添加的字符串: ",
     resString);
   struct  resbuf  *pRb, *pTemp;
   pRb = pObj->xData(appName);
   if (pRb != NULL) {
     // 如果扩展数据不为空,将指针指向表尾
     // end of the list.
     //
     for (pTemp = pRb; pTemp->rbnext != NULL;
        pTemp = pTemp->rbnext)
     {; }
   }else {
     //如果扩展数据为空,注册应用程序并将appName添加到表的第一个结果缓冲区中
     //注意:不能像在AutoLISP中有-3组码,这是唯一的扩展数据,因此不需要以-3
     //作为扩展数据开始标记
     acdbRegApp(appName);
     pRb = acutNewRb(AcDb::kDxfRegAppName);
     pTemp = pRb;
     pTemp->resval.rstring
        =(char*) malloc(wcslen(appName)+ 1);
     wcscpy(pTemp->resval.rstring,appName);
   }
   // 将用户指定的字符串添加到扩展数据中
   //
   pTemp->rbnext= acutNewRb(AcDb::kDxfXdAsciiString);
   pTemp = pTemp->rbnext;
   pTemp->resval.rstring
     =(ACHAR*) malloc(wcslen(resString)+ 1);
   wcscpy(pTemp->resval.rstring,resString);
   //下面的程序代码显示如何使用upgradeOpen()将实体从读模式改为写模式
   //
   
   pObj->upgradeOpen();
   pObj->setXData(pRb);
   pObj->close();
   acutRelRb(pRb);
}
4.5 扩展词典  每个对象可以有一个扩展词典,它包含一个任意的AcDbObject对象序列。使用该方法,几个应用程序可以把数据附加到同一对象上。扩展词典的要求比扩展数据更高,但它也为添加数据提供了更灵活的方法和更高的容量。
下面这个例子演示了扩展记录的建立,并将其加到命名对象词典的扩展词典中:
void
createXrecord()
{
   AcDbXrecord*pXrec = newAcDbXrecord;
   AcDbObject*pObj;
   AcDbObjectIddictObjId, xrecObjId;
   AcDbDictionary*pDict;
   pObj = selectObject(AcDb::kForWrite);
   if (pObj == NULL) {
      return;
   }
   // 为这个对象创建一个扩展词典,如果扩展词典已存在,该语句将不执行
   //
   pObj->createExtensionDictionary();
   
   // 获取已选择对象扩展词典的对象ID
   dictObjId= pObj->extensionDictionary();
   pObj->close();
   // 打开扩展词典并向其添加新的扩展记录
   
   acdbOpenObject(pDict, dictObjId,AcDb::kForWrite);
   pDict->setAt("ASDK_XREC1",pXrec, xrecObjId);
   pDict->close();
   // 创建结果缓冲区表并添加到扩展记录中
   //
   struct resbuf* head;
   ads_point testpt = {1.0, 2.0, 0.0};
   head = acutBuildList(AcDb::kDxfText,
      "This isa test Xrecord list",
      AcDb::kDxfXCoord, testpt,
      AcDb::kDxfReal, 3.14159,
      AcDb::kDxfAngle, 3.14159,
      AcDb::kDxfColor, 1,
      AcDb::kDxfInt16, 180,
      0);
   // 将数据表添加到扩展记录中
   // 注意:该成员函数得到一个结果缓冲区的引用,而不是结果缓冲区的指针
   // 在这里要强制转为指针
   
   //
   pXrec->setFromRbChain(*head);
   pXrec->close();
   acutRelRb(head);
}
//listXrecord()函数获得"ASDK_XREC1"关键字的扩展记录
// 将结果缓冲区表传递给printList()函数显示其内容
//
void
listXrecord()
{
   AcDbObject*pObj;
   AcDbXrecord*pXrec;
   AcDbObjectIddictObjId;
   AcDbDictionary*pDict;
   pObj = selectObject(AcDb::kForRead);
   if (pObj == NULL) {
      return;
   }
   // 获取对象扩展词典的对象ID
   //
   dictObjId= pObj->extensionDictionary();
   pObj->close();
   // 打开扩展词典并获取"ASDK_XREC1"关键字的扩展记录
   acdbOpenObject(pDict, dictObjId,AcDb::kForRead);
   pDict->getAt("ASDK_XREC1",(AcDbObject*&)pXrec,
      AcDb::kForRead);
   pDict->close();
   // 获取扩展记录数据表,然后关闭扩展记录
   //
   struct resbuf *pRbList;
   pXrec->rbChain(&pRbList);
   pXrec->close();
   printList(pRbList);
   acutRelRb(pRbList);
}
下述例子使用全局ObjectABX函数创建一个扩展记录并将其加到有关键字ASDK_REC的词典中去。
int
createXrecord()
{
   struct resbuf *pXrec,*pEnt, *pDict,*pTemp, *pTemp2;
   ads_point dummy, testpt ={1.0, 2.0, 0.0};
   ads_name xrecname, ename,extDict = {0L, 0L};
   // 用户选择实体,然后获取其数据
   //
   if (acedEntSel("\n选择实体:", ename, dummy)
      != RTNORM)
   {
      acutPrintf("\nNothing selected");
      acedRetVoid();
      return RTNORM;
   }
   pEnt = acdbEntGet(ename);
   // 开始检查实体是否有一个扩展词典
   //
   //
   for (pTemp = pEnt; pTemp->rbnext!= NULL;
      pTemp =pTemp->rbnext)
   {
      if (pTemp->restype== 102) {
        if (!strcmp("{ACAD_XDICTIONARY",
           pTemp->resval.rstring))
        {
           ads_name_set(pTemp->rbnext->resval.rlname, extDict);
           break;
        }
      }
   }
   // 如果不存在数据词典,为其添加一个
   //
   if (extDict[0] == 0L) {
      pDict =acutBuildList(RTDXF0,"DICTIONARY", 100,
        "AcDbDictionary",0);
      acdbEntMakeX(pDict, extDict);
      acutRelRb(pDict);
      pDict =acutBuildList(102, "{ACAD_XDICTIONARY",360,
        extDict,102, "}", 0);
      for (pTemp = pEnt; pTemp->rbnext->restype != 100;
        pTemp= pTemp->rbnext)
      { ; }
      for (pTemp2 = pDict;pTemp2->rbnext!= NULL;
        pTemp2= pTemp2->rbnext)
      { ; }
      pTemp2->rbnext = pTemp->rbnext;
      pTemp->rbnext = pDict;
      acdbEntMod(pEnt);
      acutRelRb(pEnt);
   }
   // 实体在此时有了一个扩展词典
   // 创建一个扩展记录的试题信息和数据结果缓冲区表
   //  
   //
   pXrec = acutBuildList(RTDXF0,"XRECORD",
      100, "AcDbXrecord",
      1, "这是一个扩展记录练习",//AcDb::kDxfText
      10, testpt,                       //AcDb::kDxfXCoord
      40, 3.14159,                      //AcDb::kDxfReal
      50, 3.14159,                      //AcDb::kDxfAngle
      60, 1,                            //AcDb::kDxfColor
      70, 180,                          //AcDb::kDxfInt16
      0);
   //创建没有所有者的扩展记录
   // 扩展记录的新实体名将被赋给xrecname变量
   acdbEntMakeX(pXrec, xrecname);
   acutRelRb(pXrec);
   // 将扩展记录的所有者加入扩展词典
   //
   acdbDictAdd(extDict, "ASDK_XRECADS",xrecname);
   acedRetVoid();
   return RTNORM;
}
// 访问用户选定的实体的扩展词典中关键字为ASDK_XRECADS的扩展记录
// 之后用printList函数显示扩展记录表的内容
int
listXrecord()
{
   struct resbuf *pXrec,*pEnt, *pTemp;
   ads_point dummy;
   ads_name ename, extDict= {0L, 0L};
   // 用户选择实体,然后获取其数据
   //
   if (acedEntSel("\nselectentity: ", ename, dummy) != RTNORM){
      acutPrintf("\nNothing selected");
      acedRetVoid();
      return RTNORM;
   }
   pEnt = acdbEntGet(ename);
   // 获取扩展词典的实体名称
   //
   for (pTemp = pEnt;pTemp->rbnext!= NULL;pTemp= pTemp->rbnext){
      if (pTemp->restype== 102) {
        if (!strcmp("{ACAD_XDICTIONARY",pTemp->resval.rstring)){
           ads_name_set(pTemp->rbnext->resval.rlname, extDict);
           break;
        }
      }
   }
   if (extDict[0] == 0L) {
      acutPrintf("\n不存在扩展词典.");
      return RTNORM;
   }
   pXrec = acdbDictSearch(extDict,"ASDK_XRECADS", 0);
   if(pXrec) {
      printList(pXrec);
      acutRelRb(pXrec);
   }
   acedRetVoid();
   return RTNORM;
}
4.6 从数据库中删除对象我们可以用下面的函数删除数据库中的任何对象:

Acad::ErrorStatus

AcDbObject::erase(Adesk::Boolean erasing   = true  );
对数据库对象和实体,erase()函数有不同的结果,体现在其恢复后的结果不同。
当一个数据库对象被删除后,有关该对象的信息就被从词典中删除。如果又用erase(kfalse)恢复了对象,信息不会被自动恢复,我们必须使用setAt()函数重新将信息加到词典中。
当一个实体被删除后,在块表记录中,它只是被简单地作了个删除的标记。实体可以用erase(kfalse)恢复。
缺省情况下,我们不能用acdbOpenObject()函数打开一个已删除的对象。如果我们企图这样做,函数将返回eWasErased错误代码。
要打开一个已删除的对象,acdbOpenObject()函数的最后一个的参数必须使用kTrue。容器对象(如二维多段线和块表记录)通常提供能在遍历其内容时跳过删除元素的选项。缺省时是跳过已删除的元素。
已删除的对象不能输出到DWG或DXF文件。
4.7 对象编档对象编档是指对象状态和数据序列之间的转换过程,其用于对象的存盘、复制和记录对象的状态以便撤销操作。编出有时被叫着串行化,编入对象是将数据序列转换为对象的过程,有时叫并行化。
编档用于下面一些AutoCAD的内容:
写和读DWG文件(使用DWG格式)。
写和读DXF文件(使用DXF格式)。
AutoCAD、AutoLISP和ObjectARX之间的通信(使用DXF格式)。
撤销记录和恢复(使用DWG格式)。
复制操作,如INSERT、XREF和COPY(使用DWC格式)。
分页(使用DWG格式)。
AcDbObject有两个成员函数用于编出:dwgOut()和dxfOut(),也有两个成员函数用于编入:dwgIn()和dxfIn()。这些成员函数最初由AutoCAD调用;使用数据库的应用程序几乎没有明确地控制过对象编档。然面,如果我们的应用程序执行新的数据库对象类,需要更彻底的理解编档。

Dwg -和Dxf-前缀表示两个根本不同的数据格式,Dwg-格式用于写入和读出DWG文件,

而Dxf-格式主要用于DXF文件和AuLoLISP的entget、entmake和entmod函数。这两种格式的基本区别在于,对于DWG编档者(将数据写人文件的对象),数据没有被明确地标记。对于DXF编档者,则相反,数据组码和在公开的数据格式中每个数据元素相互关联。




第五章  实体对象上一章讨论了数据库对象,这一章里我们特别讨论一下有关实体对象的内容,实体是指带有图形表现的数据库对象。我们将列出所有实体的共同操作属性,并举例说明如何建块、如何生成插入对象、如何生成复合实体,以及如何选择并高亮显示一个子实体。
5.1 实体概述         实体是指带有图形表现的数据库对象,包括直线、圆、弧线、文字、实体、区域、样条曲线和椭圆等。它们所在的类(即AcDbEntity类)是从AcDbObject类派生而来的。
   除了少数复杂实体外,大多数实体都含有自身几何图形的所有信息。少数复杂实体则包含其他对象,这些对象保存着实体的几何图形信息。复杂实体有以下几种:

AcDb2dPolyline, 含有AcDb2dPolylineVertex对象。

AcDb3dPolyline, 含有AcDb3dPolylineVertex对象。

AcDbPolygonMesh 含有 AcDbPolygonMeshVertex对象。

   AcDbPolyFaceMesh 含有AcDbPolyFaceMeshVertex对象和AcDbFaceRecord对象。
   AcDbBlockReference 含有AcDbAttribute对象。
AcDbMInsertBlock   含有AcDbAttribute对象。
5.2 实体的隶属关系在图形数据库中实体通常属于一个AcDbBlockTableRecord类的对象。当创建一个新图形数据库时,数据库中的块表将自动含有三个预定义的记录:* MODEL_SPACE、* **_SPACE和***_SPACEO,分别代表模型空间和两个预定义的图纸空间。不论何时用户增加一个块记录,都会被添加到数据库的块表中。用户常常是用一个BLOCK、HATCH或DIMENSION命令来完成这个过程的。
下面的图表现了实体类的隶属关系图。

5.3 实体的公共属性         AutoCAD的所有实体都含有一些公共属性,并且有一系列函数用来设置和查询这些属性。当然,这些属性也可以通过AutoCAD命令进行设置。这些属性是:
   颜色(Color)
   线型(Linetype)
   线型比例(Linetype scale)
   可见性(Visibility)
   层(layer)
   线宽(Line weight)
   绘图式样名(Plot style name)
   当向数据库中某一块表记录添加实体时,如果没有设置这些属性值,则AutoCAD会自动调用AcDbEntity::setDatabaseDefaults()函数,将它们设置成当前数据库中由系统变量确定的缺省值。
5.3.1 颜色可以用两种方法设置和查询实体的颜色,一种是采用颜色索引号,另一种是利用AcCmColor类的对象。颜色索引号的范围是从0到256。用AcCmColor::getColorIndex()成员函数从AcCmColor类的对象中获取颜色索引号。
   颜色索引号从l到7作为标准颜色,颜色索引号从8到255是由显示设备定义的。但0、256和257三个值具有如下所述的特殊含义:
   0 规定颜色为BYBLOCK(随块)。如果实体在块中,使用实体所在块的块表记
录的当前块引用的颜色。如果实体直接在模型空间或图纸空间中,则实体为黑(白)色。
  256 规定颜色为BYLAYER(随层)。实体的颜色为所在层的颜色。
  257 无颜色。该值仅在实体首次生成时使用,说明实体还没有颜色。实体一旦有了颜色或加入到数据库中,就被赋于O到256之间的值。
如果为实体指定了颜色值,则当前数据库中的缺省颜色值就被忽略。在程序中,我们可用下列成员函数设置和查询实体的颜色:
virtual Acad::ErrorStatus
AcDbEntity::setColorIndex(Adesk::UInt16 color, Adesk::Boolean doSubents /* = true */);

Adesk::UInt16 AcDbEntity::colorIndex()const;

5.3.2 线型实体的线型属性值指向一个称作线型表记录的符号表记录,它是由一系列点或虚线的描述符号组成。当生成一个新的实体对象时,线型值被置为Null。但当该实体被加入到数据库时,如果还没有给它赋与线型值,则其就自动被设置成数据库的当前线型缺省值。该缺省值存储在系统变量CELTYPE中。在实体所在的数据库中,可使用线型名、字符串或AcDbLineTypeTableRecord类的对象ID来指定线型。另外,AutoCAD使用了以下三种特殊的线型:
CONTINUOUS  缺省线型,图形数据库产生时,在线型符号表中自动生成。
BYLAYER     实体的线型为所在层的线型。
BYBLOCK     块中实体的线型将采用块引用时的线型。
如果为实体指定了线型值,则当前数据库中的缺省线型值就被忽略。函数setLinetype(),用来让程序员通过使用线型名或对象标识符为实体设置线型。
可以用linetype()函数返回当前实体的线型,可以用linetypeId()函数返回线型表记录对象的标识符。
5.3.3 线型比例生成实体时,其线型比例先被初始化为一个无效值。当实体加人数据库时,如果没有给实体指定线型比例,则自动使用数据库的当前线型比例缺省值。该缺省值存储在CELTSCALE系统变量中。如果为实体指定了线型比例,则当前的数据库线型比例缺省值就被忽略。函数setLinetypeScale()用来设置实体的线型比例,函数linetypeScale()用来查询实体的线型比例。
当实体在屏幕上被重新生成时,实体实际的线型比例是由实体自身的线型比例和整个数据库的线型比例的乘积计算而来的。对于非图纸空间实体,线型比例计算方法如下:
   effltscale= ent->linetypeScale() * ent->database()->ltscale();
   如果系统变量PSLTSCALE为1,那么图纸空间中AutoCAD将使用实际的线型比例来显示模型空间的实体。如果PSLTSCALE为0,那么系统将根据模型空间的视图计算所有的线型比例。
5.3.4 可见性
    如果用户设定某一实体是不可见的,则不管数据库中的其他设置是什么,它的设置都是不可见的。另外,其他因素也能导致实体不可见。例如,如果实体所在的层关闭或被冻结,则该层上的实体也是不可见的。在程序中可以使用以下成员函数设置或查询实体的可见性,其中AcDb::kInvisible的值可设置为kInvisible或kVisible。
可以利用函数setVisibility()函数设置实体的可见性,利用函数visibility()来查询实体的可见性。
5.3.4 AutoCAD中所有的实体都与层相联系。在数据库中至少含有一个层(0层)。与线型比例类似,用户可以为一个实体指定一个层。当向数据库掭加一个实体时,如果该实体的层属性没有被设置,那么系统将使用数据库的当前层作为该实体的层属性。
数据库中每一层也有一些相关属性,包括:冻结/解冻、打开/关闭、锁定/解锁、颜色、线型和视区。如果实体的颜色或线型为BYLAYER,则实体使用所在层的颜色或线型值来绘制实体。
如果已经为实体指定了层,那么系统将忽略数据库的当前层值。在程序中可以使用两种方式为实体设置层,一种为指定层名,另一种通过指定对象标识符。
利用函数layer()返回当前实体所在的层名,利用函数setLayer()设置层名。利用函数layerId()返回当前实体所在图层的标识符。
5.4 实体的公用函数下面要介绍的是实体的一般通用函数,有关其他函数我们将在后面陆续为大家进行介绍。
intersectWith() 在进行修剪、延伸、倒圆倒角、打断和确定对象捕捉的交点、捕捉模式等操作中,调用该函数求算实体之间的交点。
transformBy() 在将要对实体对象中的点进行移动、缩放和旋转等几何变换操作时调用该函数传递变换矩阵。
getTransformedCopy() 此函数生成一个拷贝对象,并对拷贝对象进行几何变换。
getOsnapPoints() 在进行对象捕捉操作时,调用该函数返回捕捉点及其捕捉的类型。
getGripPoints() 在对对象进行有关控制点编辑操作时,调用这个函数返回对象控制点。对象的控制点是其拉伸点的超集。
getStretchPoints() 在对对象进行拉伸操作时调用该函数返回对象的拉伸点。缺省情况下,这个函数仅仅调用getGripPoints()函数,即拉伸点与控制点相同。

moveStretchPointsAt() 在 AutoCADSTRETCH 命令中调用,用来移动指定的点,缺省情况下,此函数在内部仅调用transformBy() 函数。

moveGripPointsAt() 在进行控制点编辑操作时,用来移动指定的点,缺省情况下,该函数在内部仅调用 transformBy() 函数。
worldDraw() 在屏幕上绘制实体时,用来创建一个实体的与视区无关的几何图形。
viewportDraw()在屏幕上绘制实体时,用来创建一个实体的与视区无关的几何图形。
draw() 该函数进行实体的排列并刷新实体的队列,以便在屏幕上绘制该队列中的实体和其他对象。
list() 该函数用于AutoCAD下的LIST命令,在屏幕上使用acutPrintf()函数列出实体的全部信息。
getGeomExtents() 该函数返回包含实体的长方体的顶点。说明实体所占用的空间大小。
explode()该函数用来将实体分解成一些较为简单的实体。
getSubentPathsAtGsMarker()该函数返回与给定GS标记相对应的子实体路径。
getGsMarkersAtSubentPath()该函数返回与给定子实体路径相对应的GS标记。
subentPtr()该函数返回与给定子实体路径相对应的指针。
highlight()该函数高亮显示指定的实体。
5.4.1 对象捕捉点为了方便进行图形编辑操作,使我们能够准确获取图形中的一些特征点,AutoCAD为每种实体对象定义了一些特定意义的点,诸如中心点、中点或端点。如果需要获取这些点,只需打开AutoCAD提供的对象捕捉工具进入对象捕捉模式,则AutoCAD会调用getOsnapPoints()函效获取与当前指定捕捉模式相关的捕捉点。、
5.4.2 几何变换函数transformBy()用指定的几何变换矩阵对实体进行修改。在AutoCAD中,它是在进行控制点的移动、旋转、缩放和镜像等操作模式下被调用的。在有些情况下,如果需要对一个实体进行几何复制,并对复制的实体进行几何变换,则AutoCAD调用函数getTransformedCopy()完成这一功能。
5.4.3 交点intersectWith()函数用于求解图形中两个实体间的交点。调用该函数时,需要给出求交操作的另一个实体及求交点的方式等参数,其中求交点的方式(intType)可以取下值:

  • kOnBothOperands      
  • kExtendThis
  • kExtendArg
  • kExtendBoth
现举例说明各种求交点的方式。如下图所示,图形中含有三条直线。要求直线1与其他两条直线的交点,即调用直线的intersectWith()函数。在求直线1与直线3的交点时,我们看到两条直线并不相交,但是如果指定了kExtendThis求交方式,intersectWith()函数首先将直线l进行延伸,然后与直线3进行求交并返回交点A。在求直线1与直线2的交点时,无论是指定kExtenThis求交方式还是指定kExtendArgument求交方式均不会返回交点。因为单独延伸其中
一条直线均不会与另一条直线相交。但是如果指定了kExtendBoth求交方式,intersectWith()函数将对两条直线均进行延伸求交返回交点B。当然如果指定了kExtendNone求交方式则不会返回交点。
   intersectWith()函数是一个重载函数,它有两种形式,其中第二种形式在调用时需要一个附加参数——投影平面,这个平面用来确定两个实体在三维空间中的外观交点。
函数返回的交点通常位于当前的实体上,这样,在求外观交点时,该函数先将两个实体投影到给定的平面上,再求出两个投影实体的交点,最后所求的交点实体被投影回实体上后再返回该点。
virtual Acad::ErrorStatusAcDbEntity::intersectWith(    const AcDbEntity* ent,    AcDb::Intersect   intType,    AcGePoint3dArray& points, int               thisGsMarker = 0,    int               otherGsMarker = 0) const; virtual Acad::ErrorStatusAcDbEntity::intersectWith(    const AcDbEntity* ent,    AcDb::Intersect   intType,    const AcGePlane&  projPlane,    AcGePoint3dArray& points,    int               thisGsMarker = 0,    int               otherGsMarker = 0) const;

两个intersectWith()函数均允许函数调用者提供cs标记参数来优化函数的性能。如果在程序中实现实体的intersectWith()函数时能够利用GS标记,那么调用时提供GS标记可以定位实体的求交区域,加快测试速度。例如,在下图中如果用户选择了多边形的一条边线,那么调用该函数时给出所选直线的GS标记会使函数不用去测试多边形的其他边。而直接使用所选边进行操作来加速函数的执行。

5.4.4 GS标记和子实体AutoCAD中每个实体在屏幕上绘制自已时,都要调用诸如多义线、圆和弧等图元,它们都位于AcGi库中。每一个由AcDbEntity类派生的类都可以将一个GS(graphics system)标记与用于绘制该实体的显示向量联系起来。每个实体子类确定其GS标记插入的位置。当用户选择了一个实体之后,AutoCAD就能用GS标记来确定用户选择了实体的哪一部分。
由AcDb3dSolid类派生的实体通常由顶点、边和面等图元组成,每个图元均可以用一个GS标记来标识。实体类的构造函数能依据该实体的自然特征来确定在何处插入一个GS标记。例如,对于一个长方体,构造函数可以为每一个边创建一个GS标记,而对于一个圆柱体则可以为其顶面、底面和柱面各创建一个GS标记。
通常一个实体由点、线和面组成。当前的AutoCAD中,由于实体构成的实体有body、region、solid和mline等。我们可以使用getSubentPathsAtGsMarker()函数获得与指定GS标记相关的子实体路径。AutoCAD中可以将多个实体与一个GS标记相关联。例如,对于一个长方体,可以用一个GS标记一条边。如果需要查询与该标记相关联的边,则返回一个直线实体;如果需要查询与该标记相关联的点,则返回该直线的两个端点;如果需要查询与该标记相关联的面,则返回以该直线为交线的两个面的数据。
子实体路径唯一地标识了一个特定实体中的某一个子实体。于实体路径是AcDbFullSubentPath类的实例。它是由一个对象标识符数组和一个子实体标识符对象组成。即:
{AcDbObjectIdArray  mObjectIds;
AcDbSubentId       mSubentId;
}   
mObjectIds是个数组,包含有指定实体相对于主实体路径的对象标识符。例如,一个块引用中可能包含二个长方体,每个长方体均为AcDb3dSolid类型。这样对象标识符数组就包含二个实体标识符:块引用的标识符和其后面的主实体的标识符(InsertID,SolidID)。
AcDbFullSubentPath对象中的第二个元素是一个AcDbSubentId对象。在该对象中包含有该子实体的类型(顶点、边或面),以及子实体在列表中的索引号信息。我们可以用AcDbSubentId类中的函数type()和index()来访问其成员函数。
对于前面的例子,solid实体的第二条边的子实体路径为:
{(InsertID, solid1ID) (kEdgeSubentType, 2)};如果仅仅有一个solid实体,则该实体的第一个面的子实体路径为
{(solidID)(kFaceSubentType, 1)};我们用下面的例子说明如何高亮显示一个子实体。要高亮显示所选择的一个子实体,程序中应遵循以下基本步骤:
首先要从选择集中获取所选择的实体的GS标记;
然后调用getSubentPathsAtGsMarker()函数,将获得的GS标记转换成子实体路径并指定所需要的子实体类型(点、边或面)。
最后用所得的子实体路径调用highlight()函数高亮显示所选择的子实体。
下面说明具体的实现方法。
(1)选择实体
为了选择实体,必须使用一些全局函数。首先,使用acedSSGet()函数获得一个选择集;然后,使用acedSSNameX()函数获得选定实体的子实体GS标记。这两个函数的原型如下:
int acedSSGet(    const char * str,    const void * pt1,    const void * pt2,    const struct resbuf * filter,    ads_name ss); int acedSSNameX(    struct resbuf ** rbpp,    const ads_name ss,    const long i);(2)将GS标记转换为子实体路径由acedSSNameX()函数获得GS标记之后,就调用getSubentPathsAtCsMarker()函数,将其转换成相应的子实体路径。该函数的原型为:
virtual Acad::ErrorStatus AcDbEntity::getSubentPathsAtGsMarker(    AcDb::SubentType type,    int gsMark,    const AcGePoint3d& pickPoint,    const AcGeMatrix3d& viewXform,    int& numPaths,    AcDbFullSubentPath*& subentPaths    int numInserts = 0,    AcDbObjectId* entAndInsertStack = NULL) const;该函数的第一个参数是所期望的子实体类型(点、边或面)。在下面的例子中,第一次调用该函数时,这个参数之所以取kEdgeSubentType,就是因为程序中想要亮显所对应的边;而在第二次调用该函数时,这个参数取kFaceSubentType,是因为程序中想要亮显所选择实体相关的面。当GS标志不能提供足够的关于返回的子实体路径的信息时,参数pickPoint和viewXform就可以传递一些附加的输入条件。在下面这个例子中没有使用这两个参数。参数numInsertsentAndInsertStack是在嵌套插入块的情况下使用的。
(3)高亮显示子实体
在获得选定的实体的子实体路径之后,则可阱说高亮显示子实体的过程已基本完成。现在只需要用所得到的子实体路径调用highlight()函数即可。如果在调用时没有提供任何参数,那么该函数缺省是将整个实体高亮显示。
下面的例子程序将说明高亮显示子实体的三个步骤:选择实体、获得子实体路径和高亮显示与GS标记有关的不同类型的子实体。该例子中还给出了另一个重要的子实体函数的用法,这个函数为:
virtual AcDbEntity* AcDbEntity::subentPtr(const AcDbFullSubentPath& id) const;这个函数复制一个子实体路径指向的子实体,并返回指向复制后子实体的指针。然后可以把它加到数据库中。
注意:如果在程序中要由AcDhEntity类派生新类,则应重我getSubentPathsAtGaMruker()、getGsMarkersAtSubentPath()和subentPtr(等三个函数。highlight()函数在AcDbEntity类中已经实现了,因此派生类时一般不需要重载该函数。如果程序中一定需要重载,则在重载函数中必须调用AcDbEntity::highlight()函数来执行高亮显示操作。
下面就是一个高亮显示所选区域实体的子宴体和整个实体的示例程序。
// 这个函数调用getObjectAndGsMaker()来取得一个solid对象的ID及其GS标记
// 然后调用highightEdge()和highlightFaces()以及highightAll()函数高亮
// 显示所有选边、所有该边周围的面和整个solid
void
highlightTest()
{
   AcDbObjectIdobjId;
   int marker;
   if (getObjectAndGsMarker(objId,marker) != Acad::eOk)
      return;
   highlightEdge(objId, marker);
   highlightFaces(objId, marker);
   highlightAll(objId);
}
// 这个函数使用acedSSGet()函数让用户选择一个单独实体,然后
// 将这个选择集传递给acedSSNameX()函数获取GS标记,最后用该
// 选择集中的实体名获取所选实体的ID
Acad::ErrorStatus
getObjectAndGsMarker(AcDbObjectId&objId, int&marker)
{
   ads_name sset;
   if (acedSSGet("_:S",NULL, NULL,NULL, sset)!= RTNORM) {
      acutPrintf("\nacedSSGet 调用失败");
      return Acad::eInvalidAdsName;
   }
   // 从选择集中获取实体和它的子实体。这时假定用户只选择了一个实体:solid
   // subentity ID.This code assumes that the user
   // selected only oneitem, a solid.
   //
   struct resbuf *pRb;
   if (acedSSNameX(&pRb,sset, 0) != RTNORM){
      acedSSFree(sset);
      return Acad::eAmbiguousOutput;
   }
   acedSSFree(sset);
   // 跳到第三个实体,它是所选实体的实体名
   //
   //
   struct resbuf *pTemp;
   int i;
   for (i=1, pTemp = pRb;i<3;i++, pTemp = pTemp->rbnext)
   { ; }
   ads_name ename;
   ads_name_set(pTemp->resval.rlname, ename);
   //
   pTemp = pTemp->rbnext;
   marker = pTemp->resval.rint;
   acutRelRb(pRb);
   acdbGetObjectId(objId, ename);
   return Acad::eOk;
}
// 这个函数先接受一个对象的ID和一个GS标记。打开对象,用GS标记获取
//AcDbFullSubentIdPath, 用来高亮显示或低亮显示所选对象的边
// 然后使用对象的subentPtr()函数获取一个边的拷贝并将其加入到图形数据库中。
// 最后关闭对象
void
highlightEdge(const AcDbObjectId& objId,const int marker)
{
   char dummy[133]; // space foracedGetString pauses below
   AcDbEntity*pEnt;
   acdbOpenAcDbEntity(pEnt, objId, AcDb::kForRead);
   // Get thesubentity ID for the edge that is picked
   //
   AcGePoint3dpickpnt;
   AcGeMatrix3dxform;
   int numIds;
   AcDbFullSubentPath*subentIds;
   pEnt->getSubentPathsAtGsMarker(AcDb::kEdgeSubentType,
      marker,pickpnt, xform,numIds, subentIds);
   // 到这里,subentId中保存着一组AcDbFullSubentPath对象的地址
   // 因为只有一组,所以所选边的AcDbFullSubentPath保存在subentIds[0]中。
   // 如果有些对象没有边(例如一个球),则高亮显示边没有意义,应该跳过去
   
   if (numIds > 0) {
      // 高亮显示边
      //
      pEnt->highlight(subentIds[0]);
      // 暂停查看效果
      //
      acedGetString(0,"\n按回车键继续...",
        dummy);
      // 低量显示拾取边.
      //
      pEnt->unhighlight(subentIds[0]);
      // 获取一个边的拷贝并加入到图形数据库中
      //
      AcDbEntity*pEntCpy = pEnt->subentPtr(subentIds[0]);
      AcDbObjectIdobjId;
      addToModelSpace(objId, pEntCpy);
   }
   delete []subentIds;
   pEnt->close();
}
// 这个函数先接受一个对象的ID和一个GS标记,
// 打开对象,用GS标记获取AcDbFullSubentIdPath, 用来高亮显示或
// 低亮显示所选对象的拾取边对应的面。最后关闭对象
void
highlightFaces(const AcDbObjectId& objId,const int marker)
{
   char dummy[133];
   AcDbEntity*pEnt;
   acdbOpenAcDbEntity(pEnt, objId, AcDb::kForRead);
   // 获取面的子实体的ID值
   //
   AcGePoint3dpickpnt;
   AcGeMatrix3dxform;
   int numIds;
   AcDbFullSubentPath*subentIds;
   pEnt->getSubentPathsAtGsMarker(AcDb::kFaceSubentType,
      marker,pickpnt, xform,numIds, subentIds);
   // 依次高亮显示每个子实体面
   //
   for (int i = 0;i < numIds; i++) {
      pEnt->highlight(subentIds); // 高亮显示面
      // 暂停观察效果
      //
      acedGetString(0,"\n请按回车继续...",
        dummy);
      pEnt->unhighlight(subentIds);
   }
   delete []subentIds;
   pEnt->close();
}
// 这个函数先接受一个对象的ID
// 打开对象,然后用不带参数的highlight()和unhighlight()函数来高亮显示或低亮显示
// 选择对象所用的边,最后关闭对象
void
highlightAll(const AcDbObjectId& objId)
{
   char dummy[133];
   AcDbEntity*pEnt;
   acdbOpenAcDbEntity(pEnt, objId, AcDb::kForRead);
   // 高亮显示这个SOLID
   //
   pEnt->highlight();
   // 暂停观看效果
   //
   acedGetString(0,"\n请按回车键继续...",
      dummy);
   pEnt->unhighlight();
   pEnt->close();
}
Acad::ErrorStatus
addToModelSpace(AcDbObjectId&objId, AcDbEntity*pEntity)
{
   AcDbBlockTable*pBlockTable;
   AcDbBlockTableRecord*pSpaceRecord;
   acdbHostApplicationServices()->workingDatabase()
      ->getSymbolTable(pBlockTable, AcDb::kForRead);
   pBlockTable->getAt(ACDB_MODEL_SPACE,pSpaceRecord,
      AcDb::kForWrite);
   pSpaceRecord->appendAcDbEntity(objId,pEntity);
   pBlockTable->close();
   pEntity->close();
   pSpaceRecord->close();
   return Acad::eOk;
}
下面用一个例子程序说明如何高亮显示嵌套块引用。本例创建了六个实体,其中三个poly和三个box。另外还有三个块引用(inters),ins3是一个包含poly3和box3的块,而ins2是一个包含poly2、box2和ins3的块,insl是一个包含polyl、boxl和ins2的块。例子中实现了高亮显示不同部件的功能。
本例子中的AsdkPoly请参阅后面的自定义实体章节。

void
createInsert()
{
   // 先创建这些实体
   //
   // Polys
   //
   AsdkPoly *poly1, *poly2,*poly3;
   AcGeVector3dnorm(0, 0, 1);
   if ((poly1=new AsdkPoly)==NULL){
      acutPrintf("\n内存泄漏.");
      return;
   }
   if (poly1->set(AcGePoint2d(2, 8),AcGePoint2d(4,8), 6, norm,
      "POLY1",0)!=Acad::eOk){
        acutPrintf("\n不能用所给参数创建对象.");
        delete poly1;
        return;
   }
   if ((poly2=new AsdkPoly)==NULL){
      acutPrintf("\n内存泄漏.");
      delete poly1;
      return;
   }
   if (poly2->set(AcGePoint2d(7, 8), AcGePoint2d(9,8), 6, norm,
      "POLY2",0)!=Acad::eOk){
        acutPrintf("\n不能用所给参数创建对象.");
        delete poly1;
        delete poly2;
        return;
   }
   if ((poly3=new AsdkPoly)==NULL){
      acutPrintf("\n内存泄漏.");
      delete poly1;
      delete poly2;
      return;
   }
   if (poly3->set(AcGePoint2d(12, 8),AcGePoint2d(14,8), 6, norm,
      "POLY3",0)!=Acad::eOk){
        acutPrintf("\n不能用所给参数创建对象.");
        delete poly1;
        delete poly2;
        delete poly3;
        return;
   }
   postToDb(poly1);
   postToDb(poly2);
   postToDb(poly3);
   // Boxes
   //
   AcDb3dSolid*box1, *box2,*box3;
   box1 = new AcDb3dSolid();
   box2 = new AcDb3dSolid();
   box3 = new AcDb3dSolid();
   box1->createBox(2, 2, 2);
   box2->createBox(2, 2, 2);
   box3->createBox(2, 2, 2);
   AcGeMatrix3dmat;
   mat(0, 3)= 2; mat(1, 3) = 2;
   box1->transformBy(mat);
   mat(0, 3)= 7; mat(1, 3) = 2;
   box2->transformBy(mat);
   mat(0, 3)= 12; mat(1, 3) = 2;
   box3->transformBy(mat);
   postToDb(box1);
   postToDb(box2);
   postToDb(box3);
   // Inserts
   //
   // BLOCK 的参数为:
   //      块名,
   //      插入点,
   //      所选对象,
   //      完成选择后的空字符串
   // INSERT的参数为:
   //      块名,
   //      插入点,
   //      x方向比例,
   //      y方向比例,
   //      旋转角度
   //
   acedCommand(RTSTR, "_globcheck",RTSHORT, 0, RTNONE);
   acedCommand(RTSTR, "BLOCK",RTSTR, "blk3",RTSTR, "0,0",
      RTSTR, "14,8", RTSTR,"11,1", RTSTR,"",
      RTNONE);
   acedCommand(RTSTR, "INSERT",RTSTR, "blk3",RTSTR,
      "0,0",RTSHORT, 1, RTSHORT,1, RTSHORT,
      0, RTNONE);
   acedCommand(RTSTR, "BLOCK",RTSTR, "blk2",RTSTR, "0,0",
      RTSTR, "9,8", RTSTR,"6,1", RTSTR,"11,1",
      RTSTR, "", RTNONE);
   acedCommand(RTSTR, "INSERT",RTSTR, "blk2",RTSTR,
      "0,0", RTSHORT,1, RTSHORT, 1, RTSHORT,
      0, RTNONE);
   acedCommand(RTSTR, "BLOCK",RTSTR, "blk1",RTSTR, "0,0",
      RTSTR, "4,8", RTSTR,"1,1", RTSTR,"6,1",
      RTSTR, "", RTNONE);
   acedCommand(RTSTR, "INSERT",RTSTR, "blk1",RTSTR,
      "0,0",RTSHORT, 1, RTSHORT,1, RTSHORT,
      0, RTNONE);
   return;
}
void
hilitInsert()
{
   Adesk::Boolean interrupted= Adesk::kFalse;
   acutPrintf("\n请选择一个块引用");
   Acad::ErrorStatus es= Acad::eOk;
   AcDbEntity  *ent = NULL;
   AcDbEntity  *ent2 = NULL;
   AcDbBlockReference*blRef = NULL;
   AcDbObjectIdobjectId, blRefId;
   ads_name     ename, sset;
   for (;;) {
      switch (acedSSGet(NULL,NULL, NULL,NULL, sset)){
      case RTNORM:
        {
           structresbuf *rb;
           if (acedSSNameX(&rb,sset, 0) != RTNORM){
              acutPrintf("\n acedSSNameX 调用失败");
              acedSSFree(sset);
              return;
           }
           int            sel_method;
           ads_name       subname;
           short          marker;
           AcGePoint3d    pickpnt;
           AcGeVector3d   pickvec;
           if (!extractEntityInfo(rb,
              sel_method,
              ename,
              subname,
              marker,
              pickpnt,
              pickvec)){
                 acutPrintf("\nextractEntityInfo 调用失败");
                 acedSSFree(sset);
                 return;
           }
           acedSSFree(sset);
           assert(marker != 0);
           if (marker == 0) {
              acutPrintf("\nmarker == 0");
              return;
           }
           // 首先获取insert.
           //
           AOK(acdbGetObjectId(blRefId,ename));
           AOK(acdbOpenAcDbEntity(ent,blRefId,
              AcDb::kForRead));
           assert(ent != NULL);
           blRef= AcDbBlockReference::cast(ent);
           if (blRef == NULL){
              acutPrintf("\nNot an insert.");
              AOK(ent->close());
              continue;
           }
           structresbuf *insStack;
           ads_pointpickpoint;
           ads_matrixadsmat;
           pickpoint[0]= pickpnt[0];
           pickpoint[1]= pickpnt[1];
           pickpoint[2]= pickpnt[2];
           // 获取所选实体的细节
           //
           //
           if (acedNEntSelP(NULL,ename, pickpoint,TRUE,
              adsmat,&insStack) != RTNORM)
           {
              acutPrintf("\nacedNEntSelP调用失败");
              return;
           }
           assert(insStack != NULL);
           AOK(acdbGetObjectId(objectId,ename));
           AOK(acdbOpenAcDbEntity(ent2,objectId,
              AcDb::kForRead));
           assert(ent2 != NULL);
            
           AcDbObjectId*idArray = newAcDbObjectId[100];
           int count = 0;
           structresbuf *rbIter= insStack;
           AcDbObjectIdobjId;
           acdbGetObjectId(objId, ename);
           idArray[count++] = objId;
           while(rbIter != NULL){
              ename[0]= rbIter->resval.rlname[0];
              ename[1]= rbIter->resval.rlname[1];
              acdbGetObjectId(objId, ename);
              idArray[count++] = objId;
              rbIter= rbIter->rbnext;
           }
           count--;
           acutRelRb(insStack);
           // 首先,高亮显示一个边
           //
           int                  numPaths;
           AcDbFullSubentPath  *subentPaths;
           AcGeMatrix3d         xform;
           es= blRef->getSubentPathsAtGsMarker(
              AcDb::kEdgeSubentType,
              marker,
              pickpnt,
              xform,
              numPaths,
              subentPaths,
              count,
              idArray);
           assert(numPaths == 1);
           // 高亮、低亮显示所选边
           //
           acutPrintf("\n高亮显示第一条边.");
           es= blRef->highlight(subentPaths[0]);
           pressEnterToContinue();
           es= blRef->unhighlight(subentPaths[0]);
           // 如果是solid,则有面,这种情况下高亮显示它们
            
           if(ent2->isKindOf(AcDb3dSolid::desc())){
              es= blRef->getSubentPathsAtGsMarker(
                 AcDb::kFaceSubentType,
                 marker,
                 pickpnt,
                 xform,
                 numPaths,
                 subentPaths,
                 count,
                 idArray);
              assert(numPaths == 2);
              // 高亮、低亮显示所选面
               
              //
              acutPrintf("\n高亮显示第一个面。");
              es= blRef->highlight(subentPaths[0]);
              pressEnterToContinue();
              es= blRef->unhighlight(subentPaths[0]);
              acutPrintf("\n高亮显示下一个面.");
              es= blRef->highlight(subentPaths[1]);
              pressEnterToContinue();
              es= blRef->unhighlight(subentPaths[1]);
           }
           delete[]subentPaths;
           // 高亮显示整个实体
           //
           acutPrintf("\n高亮显示整个实体");
           AcDbFullSubentPathsubPath;
           for (int i = count; i >=0; i--) {
              subPath.objectIds().append(idArray);
           }
           es= blRef->highlight(subPath);
           pressEnterToContinue();
           es= blRef->unhighlight(subPath);
           // 最后,高亮显示每个insert
            
           //
           for (i = count -1; i >= 0; i--) {
              subPath.objectIds().removeAt(
                 subPath.objectIds().length()- 1);
              acutPrintf("\n高亮显示插入层 %d",
                 i+ 1);
              blRef->highlight(subPath);
              pressEnterToContinue();
              es= blRef->unhighlight(subPath);
           }
        }     // case RTNORM
        break;
      case RTNONE:
      case RTCAN:
        return;
      default:
        continue;
      } // switch
      break;
   } //for (;;)
   AOK(ent->close());
   AOK(ent2->close());
   return;
}
5.4.5 分解实体
    AutoCAD中一些实体可以被分解成一系列简单的元素,其具体效果取决于各实体的类。例如,长方体能被先分解成面,再分解为直线,多义线则能被分解为直线段,多行文字能被分解成为单行文字,复合线(mline)能被分解成单独的线段。此外,对块引用进行分解时,则AutoCAD复制块引用中所有的宴体并将它们分解成单独的元素添加到图形数据库中去。函数explode()生成由AcDbEntity类派生的一系列实体。
下面的表列出了每个AutoCAD实体被分解的效果。以及位于块引用中的实体被分解的效果。
  
  
  
  
  AutoCAD实体
  
  分解实体(自身)
  
  分解块(实体在块中)
  
  AcDb3dSolid
  
  Regions, bodies
  
  NA; 不能被分解
  
  AcDb2dPolyline
  
  Lines, arcs
  
  本身/NA
  
  AcDb3dPolyline
  
  Lines
  
  本身
  
  AcDbArc
  
  本身
  
  Ellipse
  
  AcDbAssocArray
  
  曲线类 (splines, lines, arcs, circles), blocks, text, and other  standard or custom objects
  
  NA
  
  AcDbBody
  
  Regions, bodies
  
  NA
  
  AcDbCircle
  
  Self
  
  Ellipse
  
  AcDbDimension
  
  Solids, lines, text strings, points
  
  NA
  
  AcDbEllipse
  
  Self
  
  Self
  
  AcDbLeader
  
  Self
  
  NA
  
  AcDbLine
  
  Self
  
  Self
  
  AcDbRay
  
  Self
  
  Self
  
  AcDbSpline
  
  Self
  
  Self
  
  AcDbXline
  
  Self
  
  Self
  
  AcDbFace
  
  Self
  
  Self
  
  AcDbMline
  
  Lines
  
  Self
  
  AcDbMText
  
  One text entity for each line
  
  Self
  
  AcDbPoint
  
  Self
  
  Self
  
  AcDbPolyFaceMesh
  
  AcDbFace
  
  Self
  
  AcDbPolygonMesh
  
  Self
  
  Self
  
  AcDbRegion
  
  Curves (splines, lines, arcs, circles)
  
  NA
  
  AcDbShape
  
  Self
  
  Self
  
  AcDbSolid
  
  Self
  
  Self
  
  AcDbText
  
  Self
  
  Self
  
  AcDbTrace
  
  Self
  
  Self
  
函数explode()是一个只读操作函数,它不会修改初始实体,它返回一组实体让应用程序根据需要进行处理。在处理复杂实体时,可以用explode()函数将它分解成一组简单的实体,再进行操作。这样做可以简化操作和编程工作量。例如,当对一条多义线执行intersectForPoints()函数时,将其分解成单独的线段或弧线进行处理,要比处理整个实体简单得多。
在AutoCAD中执行EXPLODE命令时,与调用explode函数的情况不同,EXPLODE命令执行结果如下:
分解前后的图形保持不变。
被分解的图形从数据库中删除。
分解后生成一个或多个新实体并添加到数据库中。
5.5创建AutoCAD实体对象这一节用一些示例程序说明如何创建简单的和复杂的实体,并将它添加到数据库中。同时也说明创建简单实体、简单块、带属性的块和块引用的方法。有关实体的详细创建方法请参阅本书附带光盘。
有关实体创建的相关内容在我的录制视频中也有所介绍,所以在这里仅仅是用几个实例来进行简单的介绍,从而保证本书的完整性。
5.5.1 创建一个简单实体下面程序创建一条直线,并将其加入到模型空间块表记录中。
AcDbObjectId
createLine()
{
   AcGePoint3dstartPt(4.0, 2.0, 0.0);
   AcGePoint3dendPt(10.0, 7.0, 0.0);
   AcDbLine *pLine = new AcDbLine(startPt,endPt);
   AcDbBlockTable*pBlockTable;
   acdbHostApplicationServices()->workingDatabase()
      ->getSymbolTable(pBlockTable, AcDb::kForRead);
   AcDbBlockTableRecord*pBlockTableRecord;
   pBlockTable->getAt(ACDB_MODEL_SPACE,pBlockTableRecord,
      AcDb::kForWrite);
   pBlockTable->close();
   AcDbObjectIdlineId;
   pBlockTableRecord->appendAcDbEntity(lineId,pLine);
   pBlockTableRecord->close();
   pLine->close();
   return lineId;
}
5.5.2创建一个简单块表记录下面程序创建一个新的块表记录,并把它加入到块表中。然后创建一条直线,并将其加入到新的块表记录中。
void
makeABlock()
{
   // 创建并命名一个新块表记录
   //
   AcDbBlockTableRecord*pBlockTableRec
      = new AcDbBlockTableRecord();
   pBlockTableRec->setName("ASDK-NO-ATTR");
   // 获取块表
   //
   AcDbBlockTable*pBlockTable = NULL;
   acdbHostApplicationServices()->workingDatabase()
      ->getSymbolTable(pBlockTable, AcDb::kForWrite);
   // 将新创建的块表记录加入到块表中
   //
   AcDbObjectIdblockTableRecordId;
   pBlockTable->add(blockTableRecordId,pBlockTableRec);
   pBlockTable->close();
   // 创建一条直线实体并将其作为组成部分加入到块表记录中
   
   //
   AcDbLine *pLine = new AcDbLine();
   AcDbObjectIdlineId;
   pLine->setStartPoint(AcGePoint3d(3,3, 0));
   pLine->setEndPoint(AcGePoint3d(6,6, 0));
   pLine->setColorIndex(3);
   pBlockTableRec->appendAcDbEntity(lineId,pLine);
   pLine->close();
   pBlockTableRec->close();
}
5.5.3创建一个带属性定义的块表记录AutoCAD块是存储在块表记录中的实体的集合。每个块都有一个AcDbBlockBegin对象,其后跟着一个或多个AcDbEntity对象,最后以一个AcDbBlockEnd对象结束。
块可以包含属性定义,属性定义是创建属性的模板。属性是与块有关的文字信息,用于描述块的某些特征。当向图中插入一个带有属性定义的块时,可以根据用户提供的设置将属性值复制到图形中,也可以不复制到图形中。通常,应用程序在运行时提示AutoCAD用户输入属性值。
创建一个带有属性定义的块表记录的步骤如下:
  (1)创建一个新的块表记录。
   (2)将块表记录曝加到块表中。
(3)创建实体并将其加入到块表记录中。
(4)创建属性定义、设置相应的属性值并舔加到块表记录中。
当关闭块表记录时,块头和块尾对象会自动被加入到块中。
下面的例子创建一个名叫ASDK-BLOCK-WITH-ATTR的新块表记录并添加到块表中。之后,创建一个CIRCLE实体并将其添加到该新块表记录中。程序中另外创建两个属性定义实体(第二个是第一个的克隆),并把它们加入到同一个块表记录中。
void
defineBlockWithAttributes(
                   AcDbObjectId&blockId, // 返回值.
                   const AcGePoint3d& basePoint,
                   double textHeight,
                   double textAngle)
{
   int retCode = 0;
   AcDbBlockTable*pBlockTable = NULL;
   AcDbBlockTableRecord*pBlockRecord = newAcDbBlockTableRecord;
   AcDbObjectIdentityId;
   // 第一步:设置块名和块定义的基点
   
   pBlockRecord->setName("ASDK-BLOCK-WITH-ATTR");
   pBlockRecord->setOrigin(basePoint);
   // 打开块表准备写操作.
   //
   acdbHostApplicationServices()->workingDatabase()
      ->getSymbolTable(pBlockTable, AcDb::kForWrite);
   // 第二步:将块表记录加入到块表中
   //
   pBlockTable->add(blockId, pBlockRecord);
   // 第三步:创建一个circle实体
   //
   AcDbCircle*pCircle = newAcDbCircle;
   pCircle->setCenter(basePoint);
   pCircle->setRadius(textHeight* 4.0);
   pCircle->setColorIndex(3);
   // 将circle实体加入到块表记录中
   //
   pBlockRecord->appendAcDbEntity(entityId,pCircle);
   pCircle->close();
   // 第四步:创建一个属性定义实体
   //
   AcDbAttributeDefinition*pAttdef
      = new AcDbAttributeDefinition;
   // 设置属性定义值
   //
   pAttdef->setPosition(basePoint);
   pAttdef->setHeight(textHeight);
   pAttdef->setRotation(textAngle);
   pAttdef->setHorizontalMode(AcDb::kTextLeft);
   pAttdef->setVerticalMode(AcDb::kTextBase);
   pAttdef->setPrompt("Prompt");
   pAttdef->setTextString("DEFAULT");
   pAttdef->setTag("Tag");
   pAttdef->setInvisible(Adesk::kFalse);
   pAttdef->setVerifiable(Adesk::kFalse);
   pAttdef->setPreset(Adesk::kFalse);
   pAttdef->setConstant(Adesk::kFalse);
   pAttdef->setFieldLength(25);
   // 将属性定义值加入到块中
   //
   pBlockRecord->appendAcDbEntity(entityId,pAttdef);
   // 第二个属性定义较为容易实现,仅克隆第一个便可
   //
   //
   AcDbAttributeDefinition*pAttdef2
      = AcDbAttributeDefinition::cast(pAttdef->clone());
   // 设置第二个属性定义值
   //  n.
   //
   AcGePoint3dtempPt(basePoint);
   tempPt.y -= pAttdef2->height();
   pAttdef2->setPosition(tempPt);
   pAttdef2->setColorIndex(1); // Red
   pAttdef2->setConstant(Adesk::kTrue);
   // 将第二个属性定义值加入到块中
   //
   pBlockRecord->appendAcDbEntity(entityId,pAttdef2);
   pAttdef->close();
   pAttdef2->close();
   pBlockRecord->close();
   pBlockTable->close();
   return;
}
5.5.4创建一个带属性的块引用所谓块引用是指引用一个块表记录的实体。该宴体中包含有块插入点、ECS和X、Y、Z方向比例因子、旋转角度,以及一个法向向量等信息。当用户向图形中插入块时,为了节省内存,AutoCAD通过创建块引用实体而不是将块本身的所有实体复制到图形中。如果插入一个带有属性的块,属性值可以在运行时由AutoCAD用户填写,或在插入块时由应用程序填写。程序中可以按以下步骤创建一个带有属性的块引用:
(1)创建一个块引用实体(AcDbBlockReference)。
(2)调用setBlockTableRecord()函数指定所引用的块表记录的对象标识符(该对象标识也可以在块引用实体的构造函数中指定)。
(3)将块引用添加到块表记录(模型空间、图纸空间或其它的块)中。
(4)使用块表记录遍历器遍历所引用的块表记录,寻找属性定义。对于发现的每个属性定义均创建一个新的AcDbAttribute实体,并用属性定义的数据进行填写。然后调用appendAttribute()函数将其掭加到块引用中。
下面的例子创建一个块引用,并填人属性,然后将该引用添加到数据库中。在程序中使用全局函数来获取用户的输入,用上一节上讲到的defineBlockWithAttributes()函数来创建一个块引用。该例子用一个块表记录遍历器遍历属性定义,并且为每个属性定义刨建一个相应的属性值,这个属性值是用setPropertiesFrom()函数在原始属性定义上进行设定的。
void
addBlockWithAttributes()
{
   // 为块引用、块定义和属性定义获取插入点
   
   //
   AcGePoint3dbasePoint;
   if (acedGetPoint(NULL,"\n请输入插入点: ",
      asDblArray(basePoint)) != RTNORM)
      return;
   // 为属性定义获取旋转角度
   //
   double textAngle;
   if (acedGetAngle(asDblArray(basePoint),
      "\n请输入旋转角度:", &textAngle) != RTNORM)
      return;
   // 为属性定义字体高度
   //
   double textHeight;
   if (acedGetDist(asDblArray(basePoint),
      "\n请输入文字高度:", &textHeight) != RTNORM)
      return;
   // 建立要插入的块定义.
   //
   AcDbObjectIdblockId;
   defineBlockWithAttributes(blockId, basePoint,
      textHeight,textAngle);
   // 第一步分配一个块引用对象.
   //
   AcDbBlockReference*pBlkRef = newAcDbBlockReference;
   // 第二步对新建的块定义建立块引用
   //  
   //
   pBlkRef->setBlockTableRecord(blockId);
   // 赋予当前UCS标准
   //
   struct resbuf to, from;
   from.restype = RTSHORT;
   from.resval.rint =1; // UCS
   to.restype = RTSHORT;
   to.resval.rint =0; // WCS
   AcGeVector3dnormal(0.0, 0.0, 1.0);
   acedTrans(&(normal.x),&from, &to,Adesk::kTrue,
      &(normal.x));
   // 为块定义设置插入点
   //
   pBlkRef->setPosition(basePoint);
   // 指出LCS 0.0 角度,不一定要指出UCS 0.0角度
   //
   pBlkRef->setRotation(0.0);
   pBlkRef->setNormal(normal);
   // 第三步打开当前数据库的模型空间块表记录
   //
   //
   AcDbBlockTable*pBlockTable;
   acdbHostApplicationServices()->workingDatabase()
      ->getSymbolTable(pBlockTable, AcDb::kForRead);
   AcDbBlockTableRecord*pBlockTableRecord;
   pBlockTable->getAt(ACDB_MODEL_SPACE,pBlockTableRecord,
      AcDb::kForWrite);
   pBlockTable->close();
   // 将块引用加入到模型空间的块表记录中
   //
   //
   AcDbObjectIdnewEntId;
   pBlockTableRecord->appendAcDbEntity(newEntId,pBlkRef);
   pBlockTableRecord->close();
   // 第四步打开块定义,准备读
   //
   AcDbBlockTableRecord*pBlockDef;
   acdbOpenObject(pBlockDef, blockId,AcDb::kForRead);
   //  
   //创建一个块表记录遍历器遍历属性定义
   //
   AcDbBlockTableRecordIterator*pIterator;
   pBlockDef->newIterator(pIterator);
   AcDbEntity*pEnt;
   AcDbAttributeDefinition*pAttdef;
   for (pIterator->start();!pIterator->done();
      pIterator->step())
   {
      // 获取下一个实体
      //
      pIterator->getEntity(pEnt,AcDb::kForRead);
      // 确保实体是一个属性定义而不是一个常量
      //
      //
      pAttdef= AcDbAttributeDefinition::cast(pEnt);
      if (pAttdef != NULL&& !pAttdef->isConstant()) {
        // 因为有一个不是常量的属性定义,所以新建一个属性实体
        //
        //
        AcDbAttribute*pAtt = new AcDbAttribute();
        pAtt->setPropertiesFrom(pAttdef);
        pAtt->setInvisible(pAttdef->isInvisible());
        // 通过块引用来转移属性
        // 为了确保正确,整个块引用都在此进行转移
        basePoint= pAttdef->position();
        basePoint+= pBlkRef->position().asVector();
        pAtt->setPosition(basePoint);
        pAtt->setHeight(pAttdef->height());
        pAtt->setRotation(pAttdef->rotation());
        pAtt->setTag("Tag");
        pAtt->setFieldLength(25);
        char *pStr = pAttdef->tag();
        pAtt->setTag(pStr);
        free(pStr);
        pAtt->setFieldLength(pAttdef->fieldLength());
        // 应该显示数据库的列值
        // INSERT 提示
        //
        pAtt->setTextString("分配属性值");
        AcDbObjectIdattId;
        pBlkRef->appendAttribute(attId,pAtt);
        pAtt->close();
      }
      pEnt->close(); // 使用pEnt(因为pAttdef可能为NULL)
   }
   delete pIterator;
   pBlockDef->close();
   pBlkRef->close();
}
5.5.5 遍历块表记录我们用下面的例子说明如何使用块表记录遍历器遍历块表记录,并在屏幕上打印出块中所含对象的信息。程序中printAll()函数打开一个块表进行读操作,然后根据用户提供的块名打开块表记录,使用一个新的块表记录遍历器遍历块表记录。如果记录中含有实体,则遍历器打印出该实体的信息。
void
printAll()
{
   int rc;
   char blkName[50];
   rc = acedGetString(Adesk::kTrue,
      "请输入块名<回车取当前空间块名>: ",
      blkName);
   if (rc != RTNORM)
      return;
   if (blkName[0] == '\0'){
      if (acdbHostApplicationServices()->workingDatabase()
        ->tilemode()== Adesk::kFalse){
           structresbuf rb;
           acedGetVar("cvport", &rb);
           if (rb.resval.rint == 1) {
              strcpy(blkName, ACDB_**_SPACE);
           } else{
              strcpy(blkName, ACDB_MODEL_SPACE);
           }
      } else {
        strcpy(blkName, ACDB_MODEL_SPACE);
      }
   }
   AcDbBlockTable*pBlockTable;
   acdbHostApplicationServices()->workingDatabase()
      ->getSymbolTable(pBlockTable, AcDb::kForRead);
   AcDbBlockTableRecord*pBlockTableRecord;
   pBlockTable->getAt(blkName, pBlockTableRecord,
      AcDb::kForRead);
   pBlockTable->close();
   AcDbBlockTableRecordIterator*pBlockIterator;
   pBlockTableRecord->newIterator(pBlockIterator);
   for (; !pBlockIterator->done();
      pBlockIterator->step())
   {
      AcDbEntity*pEntity;
      pBlockIterator->getEntity(pEntity,AcDb::kForRead);
      AcDbHandleobjHandle;
      pEntity->getAcDbHandle(objHandle);
      char handleStr[20];
      objHandle.getIntoAsciiBuffer(handleStr);
      const char *pCname = pEntity->isA()->name();
      acutPrintf("对象Id %lx, 句柄%s, 类名%s.\n",
        pEntity->objectId(), handleStr,pCname);
      pEntity->close();
   }
   delete pBlockIterator;
   pBlockTableRecord->close();
   acutPrintf("\n");
}
5.6 复杂实体5.6.1 创建复杂实体
   这一小节用实例说明如何创建一个AcDb2dPobline对象,并给它设置一些属性,这些属性包括层和颜色等。之后程序创建了四个顶(AcDb2dPolylineVatex)对象、设置它们的位置并加人到多义线中。最后关闭所有打开的对象(顶点、多义线、块表记录和块表)。当关闭多义线后,AutoCAD自动给多义线对象添加AcDhSequenceEnd对象。
void
createPolyline()
{
   // 为这个多段线创建四个顶点
   //
   AcGePoint3dArrayptArr;
   ptArr.setLogicalLength(4);
   for (int i = 0; i < 4; i++){
      ptArr.set((double)(i/2), (double)(i%2),0.0);
   }
   AcDb2dPolyline*pNewPline = newAcDb2dPolyline(
      AcDb::k2dSimplePoly, ptArr,0.0, Adesk::kTrue);
   pNewPline->setColorIndex(3);
   // 获取指向块表对象的指针.
   //
   AcDbBlockTable*pBlockTable;
   acdbHostApplicationServices()->workingDatabase()
      ->getSymbolTable(pBlockTable, AcDb::kForRead);
   // 获取指向模型空间块表记录的指针.
   //
   AcDbBlockTableRecord*pBlockTableRecord;
   pBlockTable->getAt(ACDB_MODEL_SPACE,pBlockTableRecord,
      AcDb::kForWrite);
   pBlockTable->close();
   // 将PLINE对象加入到数据库,并获取其对象的ID
   //
   AcDbObjectIdplineObjId;
   pBlockTableRecord->appendAcDbEntity(plineObjId,
      pNewPline);
   pBlockTableRecord->close();
   // 将pline对象设置到层.
   //
   pNewPline->setLayer("0");
   pNewPline->close();
}
5.6.2 遍历多段线的顶点在程序中我们可以使用AcDbObjectIterator遍历器遍历多义线的顶点,并打印出每个顶点的坐标。遍历器的创建由AcDb2dPolyline::vertexIterator()函数来完成。遍历器的使用与块表记录遍历器类似。本小节也用示例程序说明如何使用顶点遍历器。
// 接受一个AcDb2dPolyline对象的ID值,打开它。然后获取一个顶点遍历器
//
// 遍历所有顶点并打印输出顶点位置
//
void
iterate(AcDbObjectIdplineId)
{
   AcDb2dPolyline*pPline;
   acdbOpenObject(pPline, plineId,AcDb::kForRead);
   AcDbObjectIterator*pVertIter= pPline->vertexIterator();
   pPline->close();  // 关闭对象
   AcDb2dVertex*pVertex;
   AcGePoint3dlocation;
   AcDbObjectIdvertexObjId;
   for (int vertexNumber =0; !pVertIter->done();
      vertexNumber++,pVertIter->step())
   {
      vertexObjId= pVertIter->objectId();
      acdbOpenObject(pVertex, vertexObjId,
        AcDb::kForRead);
      location= pVertex->position();
      pVertex->close();
      acutPrintf("\n顶点#%d 的位置为:"
        "%0.3f,%0.3f, %0.3f", vertexNumber,
        location[X], location[Y], location[Z]);
   }
   delete pVertIter;
}
5.7 坐标系统的访问通常,在AutoCAD中实体函数使用通用坐标系(WCS)的值来设置和查询坐标值。例如,开发者在使用getCenter()函数查询一个圆的圆心时,AutoCAD将返回圆心在通用坐标系中的坐标值。但对于AcDb2dPolylineVertex是个例外,它不使用通用坐标系,而是使用实体坐标系(ECS)。
5.7.1 实体坐标系如果程序中需要定义自己的实体,那么让自定义实体使用自己的相对坐标系来保存其几何结构(点、角度和向量等)在编程处理上可能会非常方便有效。例如,我们可以对圆弧建立一个实体坐标系,让其Z轴垂直于圆弧所在的平面。圆弧的圆心坐标使用通用坐标系,但圆弧的起始和终止角只能用其自身的实体坐标系来解释。在这种情况下,应编写getEcs()函数来返回一个矩阵.该矩阵是一个几何变换矩阵,用于将实体从其自身的实体坐标系转换到通用坐标系。如果一个实体没有自己的实体坐标系的定义,那么getEcs()函数应返回单位矩阵。换句
话说,如果一个实体的getEcs()函数在任何时候均返回单位矩阵,那么可以认为该实体是以通用坐标系的形式定义的。
在AutoCAD中,只有平面实体具有自己的实体坐标系,而三维实体不具有。调用getEcs()函数返回非单位矩阵的AutoCAD的实体有:尺寸标洼、文字、圆、圆弧、二维多段线、块插入、点、迹线、实心体、形、属性定义和属性等。
5.7.2 AcDb2dPolylineVertex一个AcDb2dPolyline对象具有一个标高值,以及用AcDh2dPolylineVertex表示的一系列的XY坐标点。但是AcDb2dPolylineVertex类的position()和setPosition()函数均能在实体坐标系中确定一个三维点。AutoCAD是在调用setPosition()函数时传进Z坐标,保存于实体中,并在调用position()函数时返回,在其他情况下Z坐标被AutoCAD忽略。该Z坐标值不影响多义线的标高。
AcDb2dPolyline类提供了一个vertexPosition()函数,该函数返回调用顶点在通用坐标系中的坐标值。程序中改变多义线标高的唯一方法是调用AcDb2dPolyline::setElevation()函数。
5.8 曲线函数AcDbCurve类是一个抽象基类,该类提供了对曲线进行操作的函数,包括投影、扩展、偏移曲线和一系列查询曲线参数的函数,曲线能在参数空间定义,也能在笛氏坐标空间定义。三维曲线是单变量的函数(f(t)),同样三维面是双变量的函(f(u,v))。曲线函数允许我们把参数点转换成笛氏坐标点。例如,样条曲线是参数空间曲线的典型代表。我们要将一个样条曲线等分为三部分,首先找出样条曲线的响应点参数,然后在参数空间中对其进行操作。
曲线可以作为萁他实体的剪切边界和延伸边界,并能作为构件来构造复杂的三维实体。像下面程序演示的,我们可以按照某个给定的方向把一个曲线投影到一个平面上。
// 先接受一个ellipse对象的ID,并打开ellipse
// 并使用其getOrthoProjectedCurve成员函数创建一个新ellipse
// 这个新ellipse是原ellipse在法线<1,1,1>的平面上的投影,
// 生成的这个ellipse最后加入到模型空间块表记录中
//  
void
projectEllipse(AcDbObjectIdellipseId)
{
   AcDbEllipse*pEllipse;
   acdbOpenObject(pEllipse, ellipseId,AcDb::kForRead);
   // 现在投影这个ellipse到在法线为<1,1,1>的平面上
   //
   AcDbEllipse*pProjectedCurve;
   pEllipse->getOrthoProjectedCurve(AcGePlane(
      AcGePoint3d::kOrigin, AcGeVector3d(1,1, 1)),
      (AcDbCurve*&)pProjectedCurve);
   pEllipse->close();
   AcDbObjectIdnewCurveId;
   addToModelSpace(newCurveId, pProjectedCurve);
}
// 接受一个ellipse对象的ID,打开ellipse
// 然后使用其getOffsetCurves()成员函数创建一个新ellipse
// 这个新ellipse是对原ellipse进行了.5个绘图单元的偏移操作
//
void
offsetEllipse(AcDbObjectIdellipseId)
{
   AcDbEllipse*pEllipse;
   acdbOpenObject(pEllipse, ellipseId,AcDb::kForRead);
   // 现在通过偏移.5个绘图单元生成一个ellipse
   //
   AcDbVoidPtrArraycurves;
   pEllipse->getOffsetCurves(0.5, curves);
   pEllipse->close();
   AcDbObjectIdnewCurveId;
   addToModelSpace(newCurveId, (AcDbEntity*)curves[0]);
}
   5.9 给实体加入超链接ObjectARX程序允许给实体加入超链接,这是通过使用AcDbHyperlink、AcDbHyperlinkCollection和AcDbEntityHyperlinkPE这三个类来实现的。这里的超链接可以是URL或非网络地址,比如本地磁盘的文件等等。在程序中可以访问、查看、编辑和列出这些超链接。
5.9.1 AcDbHyperlink         一个AcDbHyperlink类的对象包括超链接名(例如,http://www.autodesk.com)、一个子链接位置、超链接描述,或提示名和一个超链接显示字串。对于AutoCAD,子链接位置是一个命名视区单元,就像在电子表格程序中,子链接位置可能是一个单元格或一组单元格一样。超链接显示字串一般与超链接描述是一样的。如果超链接描述为null,则可以使用超链接名和子链接位置来替代它,其格式为“链接名 - 子链接位置”。
实体的超链接是可以嵌套的,只有与超链接有关的实体在块中或插人体中时,程序处理时才使用嵌套超链接。
5.9.2 AcDbHyperlinkCollection         这个类是AcDbHyperlink类的集合,并且有很多添加和删除这些对象的函数。当删除AcDbHyperlinkCollection类的内容或删除超链接集合对象本身时,它会删除其内容。超链接在集合中的编号从零开始。
5.9.3 AcDbEntityHyperlinkPE   使用AcDbEntityHyperlinkPE类的函数可以设置、获取和统计与实体有关的超链接。
5.9.4 超链接的例子    下面的例子演示了打印实体的超链接、并且让新的超链接加入的方法。
   
void AddHyperlink()
{
   ads_name en;
   ads_point pt;
   AcDbEntity* pEnt;
   AcDbObjectIdpEntId;
   acedEntSel("\n选择一个实体: ", en, pt);
   // 获取对象的ID.
   acdbGetObjectId(pEntId, en);
   // 以写入的方式打开.
   acdbOpenObject(pEnt, pEntId, AcDb::kForWrite);
   // 下面在getHyperlinkCollection内部创建超链接对象集合对象
   // 但必须在用后删除它
   AcDbHyperlinkCollection* pcHCL = NULL;
   // 获取与实体相关的超链接集合
   ACRX_X_CALL(pEnt, AcDbEntityHyperlinkPE)->
      getHyperlinkCollection(pEnt, pcHCL, false, true);
   // 如果有超链接就打开它.
   if (pcHCL->count()!= 0)
   {
      AcDbHyperlink* pcHO;
      acutPrintf("\n这个实体中已经存在超链接:");
        // 遍历该集合并打印存在的超链接.
        int i = 0;
      for (i = 0; i < pcHCL->count();i++)
      {
        // 获取当前超链接对象的位置
        pcHO= pcHCL->item(i);
        acutPrintf("\n超链接名称: %s", pcHO->name());
        acutPrintf("\n超链接位置: %s",
            pcHO->subLocation());
        acutPrintf("\n超链接描述: %s",
           pcHO->description());
      }
      acutPrintf("\n** 删除这些.**");
      // 从集合中删除存在的超链接.
      // RemoveAt 也能删除对象.
      for (i = pcHCL->count() - 1; i>= 0; i--)
      {
        pcHCL->removeAt(i);
      }
   }
   // 为这个实体获取新的超链接
   for (;;)
   {
      acutPrintf("\n输入空名、位置和描述来结束输入请求.");
        //提示用户输入名称和描述
        char sName[100], sLocation[100],sDescription[100];
      if (acedGetString(TRUE,"\n输入超链接名: ", sName)
        != RTNORM)
        acutPrintf("输入无效\n");
      if (acedGetString(TRUE,"\n输入超链接位置: ",
        sLocation)!= RTNORM)
        acutPrintf("输入无效\n");
      if (acedGetString(TRUE,"\n输入超链接的描述: ",
        sDescription)!= RTNORM)
        acutPrintf("输入无效\n");
      // 添加超链接或退出提示.
      if (strcmp(sName, "") || strcmp(sLocation, "")||
        strcmp(sDescription, ""))
        pcHCL->addTail(sName, sDescription, sLocation);
      else
        break;  
   }
   // 将这些超链接添加到所选的实体中.
   ACRX_X_CALL(pEnt, AcDbEntityHyperlinkPE)->
      setHyperlinkCollection(pEnt, pcHCL);
   // 删除集合。这个集合将删除所有包含的超链接对象
   
   delete pcHCL;
   // 关闭对象
   pEnt->close();
}







第六章  容器对象   本章介绍AutoCAD数据库使用的容器对象。它们是:符号表、词典、组和扩展记录。作为每个图形的一部分,AutoCAD创建了一套固定的符号表和命名对象词典,命名对象词典包含其他两个词典:MLINE线型和组词典。本章的例子示范了如何向符号表、词典和组添加表项,和如何使用遍历器查询这些容器的内容。也示范了如何创建自定义词典和扩展记录,并使用它们管理应用程序数据和对象。有关介绍AcDbObject对象的扩展词典的内容,请看第四章“数据库对象”。
6.1 符号表和词典的比较    符号表和词典在功能上本质完全相同。它们包含可以使用文本字符串关键字查找的数据库对象表项。我们可以向这些容器对象添加表项,而且我们可以使用遍历器遍历表项,并查询这些目录。
   AutoCAD数据库包含一套固定的(九个)符号表,我们不能创建或删除符号表,但可以添加或修改符号表中的表项,表项也叫作记录。每个符号表只包含一个特定类型的对象。例如AcDbLayerTable只包含AcDbLayerTableRecord类型的对象。以这种方式定义符号表主要是为了保持与AutoCAD R12和AutoCAD以前的发行版相兼容。
   词典为访问对象提供了与符号表相似的机制,这种机制就是使用了相关的命名关键字。每当AutaCAD创建一个新图时,AutoCAD数据库创建一个命名对象词典,命名对象词典可以作为图中非实体对象结构的主“目录表”。缺省情况下命名对象词典包含四个词典:组词典、MLINE线型词典、布局词典和图样式名词典。我们可以创建任意数量的附加对象,并将其添加到命名对象词典中去。然而,最好是在命名对象词典中直接添加一个对象,该对象拥有与我们应用程序有关的其他对象。通常,这个拥有其他对象的对象是一个容器类,比如词典。为了能与Autodesk公司达成协作关系,应使用分配给我们的四个字母的挂册开发者符号作为该类的名字。
    AcDbDictionary对象可以包含任何类型的AcDbObject对象,包括其他词典。一个词典对象不检查表项的类型。然而,MLINE线型词典应只包含AcDbMlineStyle类的实例,组词典也应只包含AcDbGroup实例。应用程序可以在其创建和维护的词典中,要求保存特定类型的表项。
   符号表、符号表记录、词典和遍历器的类层次结构如下图所示。
   符号表和词典的重要区别在于符号表记录不能由ObjectARX应用程序宜接删除,这些记录只能由PURGE命令删除或使用写块操作有选择地过滤掉。但ObjectARX应用程序可以删除词典拥有的对象。删除词典或词典表项有可能导致AutoCAD或应用程序失败。
   符号表和词典的另一个重要区别是:符号表记录在其类定义的一个域中保存
其关联的查找名,而词典则将命名关键字作为词典的一部分保存,不依赖于它所关联的对象。


6.2 符号表符号表记录和词典中使用的表项名必须遵循以下规则:
在ObjectARX中的名字可以是任何长度,但我们在AutoCAD中输入的符号名不能超过255个字符。
AutoCAD保留名字的大小写,但是在比较时不考虑大小写。即在AutoCAD中,“Floor”和“FLOOR”是相同的符号。

名称可使用的字符与WindowsNT文件名允许使用的字符相同,除了逗号(,)、单引号(’)、分号(;)和等号(=)外,其他所有字符都可用于名称。

AutoCAD数据库包含下列符号表(括号内为类名和用来添加表项的AutoCAD命令):
  块表(AcDbBlockTable;BLOCK)。
  层表(AcDbLayerTable; LAYER)。
  字体样式表(AcDbTextStyleTable; STYLE)。
  线型表(AcDbLinetypeTable;LTYPE)。
  视图表(AcDbViewTable;VIEW)。
  UCS表(AcDbUCSTable; UCS)。
  视口表(AcDbViewportTable;VPORT)。
  注册的应用程序表(AcDbRegAppTable)。
  尺寸类型表(AcDbDimStyleTable;DIMSTYIE)。
每个表包含对应的AcDbSymbolTableRecord子类对象。
每个符号表类都有一个getAt()函数,用于查找指定名的记录。getAt()函数的重载原型声明如下(## BASE NAME##代表九符号表类中任何一个类的类型):
Acad::ErrorStatus
AcDb##BASE_NAME##Table::getAt(constchar* pEntryName,
                   AcDb##BASE_NAME##TableRecord*& pRecord,
                    AcDb::OpenMode mode,
                    bool openErasedRecord =false) const;
或者是
Acad::ErrorStatus AcDb##BASE_NAME##Table::getAt(const char* pEntryName,                    AcDbObjectId& recordId,                    bool getErasedRecord = false) const;         对该函数的第一种形式,如果找到了匹配的记录并可以将它(用指定模式)打开,则利用pRecord返回指针指向所打开的记录。如openErasedRecord的值为kTrue,函数返回找到的对象(包括已删除的对象);如果openErasedRecord的值为kFalse,函数返回空指针(对已删除的对象,返回eWasErased错误状态)。
对该函数的第二种形式,如果找到了匹配的记录,通过recordId返回指定名称记录的AcDbObjectId。如果getErasedRecird的值为kTrue,函数返回找到的对象(包括已删除的对象),但不打开对象。
一旦我们已获得了一个记录并将它打开后,我们可以取得和设置不同的成员值。
has()和add()是所有符号表类提供的其他重要的函数记录” has()函数的原型声明为:
bool AcDb##BASE_NAME##Table::has(const char* pName) const;bool AcDb##BASE_NAME##Table::has(AcDbObjectId id) const; 如果表中包含与pName中名字相匹配的记录,has()函数返回kTrue。
add()函数的原型声明为:
Acad::ErrorStatus AcDb##BASE_NAME##Table::add(AcDb##BASE_NAME##TableRecord*                                pRecord); Acad::ErrorStatusAcDb##BASE_NAME##Table::add(AcDbObjectId& recordId,                            AcDb##BASE_NAME##TableRecord* pRecord);   该函数在数据库表及其所包含的表中,添加pRecord指向的记录。如果添加成功而且变量pId非空,则pId指向数据库中记录的AcDbObjectId。
6.2.1 块表在数据库中的实体一般隶属于块表记录。缺省情况下块表包含三个记录:* MODEL_SPACE、* **_SPACE和***_SPACEO.这三个记录分别对应于三个初始绘图空闻,每个绘图空间都可以由AutoCAD用户直接编辑。关于向模型空间块表记录添加实体的例子,参见前面的例子。* **_SPACE和***_SPACEO记录对应于AutoCAD中两个预先定义的图纸空间布局,我们可以添加、修改和删除图纸空间布局。
我们使用BLOCK命令或INSERT命令插入一个外部图时,会创建一个新块表记录。也可以使用acdbEntMake()函数创建新块表记录。使用BLOCK?命令,可以列出除*MODEL_SPACE和***_SPACE记录外的块表内容,请看第五章“实体对象”中创建块表记录和块引用的例子(块引用是一个涉及给定块表记录的实体)。
6.2.2 层表缺省情况下,层表包含一个“0层”,我们可以使用LAYER命令在层表中添加层。
6.2.2.1 层的属性AcDblayerTableRecord类包含的成员函数,有许多是设置层属性的。通过层属性影响在该层上实体的显示。所有实体都必须引用一个有效层表记录。
下面介绍设置和查询层属性的成员函数。
6.2.2.2 冻结/解冻当一个层被冻结后,层上的图形不会重新生成。Acad::ErrorStatusAcDbLayerTableRecord::setIsFrozen(bool frozen); boolAcDbLayerTableRecord::isFrozen() const;6.2.2.3 打开/关闭当一个层被关闭后,其上的图形不显示。
void AcDbLayerTableRecord::setIsOff(bool off); boolAcDbLayerTableRecord::isOff() const;6.2.2.4 视口setVPDFLT()函数用于设置层(在缺省情况下)在新视区中是否可见。
void AcDbLayerTableRecord::setVPDFLT(bool frozen); bool AcDbLayerTableRecord::VPDFLT() const;6.2.2.5 锁定/解锁在锁定层上的实体,AutoCAD用户不能对其修改,也不能在程序内用write()函数打开。
void AcDbLayerTableRecord::setIsLocked(bool locked); boolAcDbLayerTableRecord::isLocked() const;6.2.2.6 颜色当实体的颜色是BYLAYER时,使用setColor()面数设置实体颜色。
void AcDbLayerTableRecord::setColor(const AcCmColor & color);AcCmColor AcDbLayerTableRecord::color() const;6.2.2.7 线型         当实体的线型是BYLAYER时,使用setLinetypeObjectId ()函数设置实体线型。
void AcDbLayerTableRecord::setLinetypeObjectId(AcDbObjectId id); AcDbObjectIdAcDbLayerTableRecord::linetypeObjectId() const;
6.2.2.8 创建和修改层表记录下面例子从当前数据库获取一个层表并以写模式打开,创建一个新层表记录(AcDbLayerTableRecord),设置层的属性(层名、冻结属性、开/关、视口和锁定);然后,创建一个颜色类对象,将层的颜色设置为红色。为设置层的线型,该例子以读模式打开线型表,获得要求线型(这里为,“DASHED”)的线型记录的对象ID。一旦取得线型的对象ID,关闭线型表,为新层表记录设置线型。该例子使用add()函数向层表添加层表记录。最后,关闭层表记录和层表。
void
addLayer()
{
   AcDbLayerTable*pLayerTbl;
   acdbHostApplicationServices()->workingDatabase()
      ->getSymbolTable(pLayerTbl, AcDb::kForWrite);
   if (!pLayerTbl->has("ASDK_TESTLAYER")) {
      AcDbLayerTableRecord*pLayerTblRcd
        = new AcDbLayerTableRecord;
      pLayerTblRcd->setName("ASDK_TESTLAYER");
      pLayerTblRcd->setIsFrozen(0);// 解冻图层
      pLayerTblRcd->setIsOff(0);  // 打开图层
      pLayerTblRcd->setVPDFLT(0); // 视口为可见
      pLayerTblRcd->setIsLocked(0);// 解锁图层
      AcCmColorcolor;
      color.setColorIndex(1); // 设置为红色
      pLayerTblRcd->setColor(color);
      // 对我们想使用的线型,需要提供线型记录的对象ID
      // 所以首先需要获得对象ID
      //
      AcDbLinetypeTable*pLinetypeTbl;
      AcDbObjectIdltId;
      acdbHostApplicationServices()->workingDatabase()
        ->getSymbolTable(pLinetypeTbl, AcDb::kForRead);
      if ((pLinetypeTbl->getAt("DASHED", ltId))
        != Acad::eOk)
      {
        acutPrintf("\n找不到DASHED线型,所以使用CONTINUOUS线型");
        // CONTINUOUS isin every drawing, so use it.
        //
        pLinetypeTbl->getAt("CONTINUOUS",ltId);
      }
      pLinetypeTbl->close();
      pLayerTblRcd->setLinetypeObjectId(ltId);
      pLayerTbl->add(pLayerTblRcd);
      pLayerTblRcd->close();
      pLayerTbl->close();
   } else {
      pLayerTbl->close();
      acutPrintf("\n图层已经存在");
   }
}
6.2.3 遍历器每个符号表都有对应的遍历器,我们可使用AcDb##BASE_NAME##Table::newIterator()函数创建遍历器。
Acad::ErrorStatus AcDb##BASE_NAME##Table::newIterator(    AcDb##BASE_NAME##TableIterator*& pIterator,    bool atBeginning = Adesk::kTrue,    bool skipErased = Adesk::kTrue) const;newIterator()函数创建一个用于遍历符号表内容的对象,将pIterator设置为指向遍历器对象。如果atBeginning的值为kTrue,遍历器从表头开始遍历;如果atBeginning的值为kFalse.遍历器从表尾开始遍历。如果skipErased的值为kTrue,遍历器的最初位置被定在第一个(或最后一个)未删除的记录上;如果skipErased柏值为kFalse,遍历器被定位在第一个(或最后一个)记录,而不管记录是否已被删除。
如果我们已经创建了一个新遍历器,也应负责删除它。只有已构造的所有遍历器被删除后,才能关闭符号表。除了符号表外,块表记录也有遍历器,用来遍历块表所拥有的实体。当我们创建一个新遍历器时.AcDbBlockTableRecord类返回一个AcDbBlockTableRecordIterator类对象。该遍历器允许我们遍历块表记录包含的实体,并查找特定的实体。
  下面例子中的源程序代码创建了一个遍历器,遍历线型表中的符号表记录。该程序获取每个记录,以读模式打开它,获得线型名,关闭记录,然后打印线型名,最后,程序删除遍历器。
void
iterateLinetypes()
{
   AcDbLinetypeTable*pLinetypeTbl;
   acdbHostApplicationServices()->workingDatabase()
      ->getSymbolTable(pLinetypeTbl, AcDb::kForRead);
   // 创建一个新遍历器,从表头开始遍历,跳过已经删除的记录
   
   AcDbLinetypeTableIterator*pLtIterator;
   pLinetypeTbl->newIterator(pLtIterator);
   //遍历表,取得每个块表记录并打印线型名称
   AcDbLinetypeTableRecord*pLtTableRcd;
   char *pLtName;
   for (; !pLtIterator->done();pLtIterator->step()){
      pLtIterator->getRecord(pLtTableRcd,AcDb::kForRead);
      pLtTableRcd->getName(pLtName);
      pLtTableRcd->close();
      acutPrintf("\n线型名称是: %s",pLtName);
      free(pLtName);
   }
   delete pLtIterator;
   pLinetypeTbl->close();
}
6.3 词典要创建一个新词典,我们需要创建一个AcDbDictionary实例,将其添加到数据库中,并用其所有者对象对其注册。使用AcDbDictionary的setAt()函数,将对象和词典添加到数据库中。:
setAt()函数将由newValue指定的新表项添加到词典中,如果表项已经存在,将被新值取代,srchKey指定对象名,retObjId返回表项的对象ID。
当我们向一个词典添加一个表项时,词典自动为表项附加一个反应器。如果对象被删除,词典会得到通知,并会从词典中删除该表项。
6.3.1 组和组词典组是一个容器对象,它是一个有序的数据库实体集合。组可以被当作命名的永久选择集,它们与其所包含实体间不存在所属关系。
当一个实体被删除后,则自动从其组中删除。如果一个删除了的实体被恢复,则自动重新插入到组中。
使用AcDbGroup::newIterator()函数可以获得一个遍历器,并遍历组中的实体。AcDbGroup类也提供了一些函数,可以在组的头部和尾部添加实体、在组的特定索引中插入实体、删除实体和将实体从组的一个位置转移到另一个位置。
使用AcDbGroup类的setColor()、setLayer()、setLinelype()、setVisibility()和setHighlight()函数,我们也可以给组的所有成员指定属性。这些操作与打开组中的每个实体并直接设置其属性有相同的效果。
组应一直保存在组词典中,可以通过如下方法获得:
AcDbDictionary* pGrpDict =     acdbHostApplicationServices()->working Database()->        getGroupDictionary(pGroupDict, AcDb::kForWrite); 获得组词典的另一种方法是在命名对象词典中查找“ACAD_GROUP”。下列函数是应用程序的一部分,首先提示我们选择一些“ASDK_GROUPTEST”组中的实体,然后调用removeAllButLines()函数遍历组,删除除直线以外的全部实体,最后,将组中剩余的所有实体颜色改为红色。
void
groups()
{
   AcDbGroup*pGroup = newAcDbGroup("grouptest");
   AcDbDictionary*pGroupDict;
   acdbHostApplicationServices()->workingDatabase()
      ->getGroupDictionary(pGroupDict, AcDb::kForWrite);
   AcDbObjectIdgroupId;
   pGroupDict->setAt("ASDK_GROUPTEST",pGroup, groupId);
   pGroupDict->close();
   pGroup->close();
   makeGroup(groupId);
   removeAllButLines(groupId);
}
// 提示我们选择要添加到组中的对象,打开groupId(groupId作为参数传入这个函数)
// 指定的组,然后将选定的对象添加到组中
//
void
makeGroup(AcDbObjectIdgroupId)
{
   ads_name sset;
   int err = acedSSGet(NULL, NULL, NULL, NULL, sset);
   if (err != RTNORM){
      return;
   }
   AcDbGroup*pGroup;
   acdbOpenObject(pGroup, groupId,AcDb::kForWrite);
   // 将选择集中每个实体名转换为对象ID,然后将对象添加到组中
   
   long i, length;
   ads_name ename;
   AcDbObjectIdentId;
   acedSSLength(sset, &length);
   for (i = 0; i < length; i++) {
      acedSSName(sset, i, ename);
      acdbGetObjectId(entId, ename);
      pGroup->append(entId);
   }
   pGroup->close();
   acedSSFree(sset);
}
// 接受一个AcDbGroup对象ID,将其打开,遍历整个组
// 删除全部非AcDbLine实体,并将组中剩余的所有实体颜色改为红色
void
removeAllButLines(AcDbObjectIdgroupId)
{
   AcDbGroup*pGroup;
   acdbOpenObject(pGroup, groupId,AcDb::kForWrite);
   AcDbGroupIterator*pIter = pGroup->newIterator();
   AcDbObject*pObj;
   for (; !pIter->done();pIter->next()){
      pIter->getObject(pObj,AcDb::kForRead);
      //如果不是直线,将其关闭并从组中删除
   
      if (!pObj->isKindOf(AcDbLine::desc())){
        //AcDbGroup::remove() 要求删除的对象是关闭的,
        // 所以现在关闭它
        //
        pObj->close();
        pGroup->remove(pIter->objectId());
      } else {
        pObj->close();
      }
   }
   delete pIter;
   // 现在将组中所有实体的颜色改为红色。
   //
   pGroup->setColorIndex(1);
   pGroup->close();
}
6.3.2 MLINE线型词典   MLINE线型词典包含AcDbMlineStyle类的对象,如图所示。每个AcDbMline类的对象都有一个与其相关的MLINE线型。MLINE线型指定多义线的属性,如偏移量、颜色和线型。



6.3.3 布局词典    布局词典是命名对象词典内的一个缺省词典,它包含类AcDbLayout对象。AcDbLaynut对象保存图纸空间布局的特征,包括绘图设置。每个AcDLayout对象还包含一个有关块表记录的对象ID,用来存储与布局有关的实体。布局词典在图形数据库中的隶属关系结构。
   
6.3.4 创建词典    下面的例子创建了一个新词典(ASDK_DICT),并将其掭加到命名对象词典中。然后创建两个新的自定义类AsdkMyClass对象(从AcDbObject派生),使用setAt()函数将其添加到词典中。使用setAt()函数将对象添加到词典中后,需要将对象关闭。
   
// 此函数创建两个类AsdkMyClass对象,并分别使用整数和初始化它们
// 然后将其添加到关键字ASDK_DICT词典中,如果词典ASDK_DICT词典不存在
// 创建一个词典并将其添加到命名对象词典中
void
createDictionary()
{
   AcDbDictionary*pNamedobj;
   acdbHostApplicationServices()->workingDatabase()->
      getNamedObjectsDictionary(pNamedobj, AcDb::kForWrite);
   // 检查是否我们想创建的词典已经存在,如果不存在,则创建一个
   // 添加到命名对象词典中
   //
   AcDbDictionary*pDict;
   if (pNamedobj->getAt("ASDK_DICT", (AcDbObject*&)pDict,
      AcDb::kForWrite) == Acad::eKeyNotFound)
   {
      pDict =new AcDbDictionary;
      AcDbObjectIdDictId;
      pNamedobj->setAt("ASDK_DICT",pDict, DictId);
   }
   pNamedobj->close();
   if (pDict) {
      // 创建要添加到新词典中的新对象,将他们添加到新的词典中,然后关闭对象
      // add them,then close them.
      //
      AsdkMyClass*pObj1 = newAsdkMyClass(1);
      AsdkMyClass*pObj2 = newAsdkMyClass(2);
      AcDbObjectIdrId1, rId2;
      pDict->setAt("OBJ1",pObj1, rId1);
      pDict->setAt("OBJ2",pObj2, rId2);
      pObj1->close();
      pObj2->close();
      pDict->close();
   }
}
6.3.5 遍历词典表项   AcDbDictionaryIterator是词典的遍历器类。下面源程序代码从命名对象词典获取一个词典(ASDK_DICT),然后使用一个词典遍历器遍历词典表项,并打印所储存的整型值,最后,删除遍历器,关闭词典。
void
iterateDictionary()
{
   AcDbDictionary *pNamedobj;
   acdbHostApplicationServices()->workingDatabase()
      ->getNamedObjectsDictionary(pNamedobj,AcDb::kForRead);
   
   //
   AcDbDictionary *pDict;
   pNamedobj->getAt("ASDK_DICT", (AcDbObject*&)pDict,
      AcDb::kForRead);
   pNamedobj->close();
   // 获取指向ASDK_DICT词典的指针
   //
   AcDbDictionaryIterator* pDictIter= pDict->newIterator();
   AsdkMyClass *pMyCl;
   Adesk::Int16 val;
   for (; !pDictIter->done(); pDictIter->next()) {
      // 获取当前记录,以读模式打开,打印其中数据
      //
      pDictIter->getObject((AcDbObject*&)pMyCl,
        AcDb::kForRead);
      pMyCl->getData(val);
      pMyCl->close();
      acutPrintf("\n整型值为: %d", val);
   }
   delete pDictIter;
   pDict->close();
}
6.4 布局AutoCAD最初包含三个布局:一个模型空间布局和两个图纸空间布局。这些布局可以通过在AutoCAD图窗口底部的工具条访问。工具条最初命名为模型、布局1和布局2。缺省情况下的工具条是模型选项卡,表示模型空间,我们一般可以在模型空间中创建自己的图形。布局1和布局2工具条表示图纸空间,通常用于布置我们要绘的图;图纸空间布局显示一个图纸映像,以显示可打印的边界,配置打印设备。
   建议我们最后使用图纸空间布局处理图,为打印图作准备,但是打印可以在任一布局中进行,包括模型空间布局。
6.4.1 ObjectARX布局类创建和操作布局的主类有:
AcDbLayout
AcDbPlotSettings
AcDbPlotSettingsValidator
AcDbLayoutManager
AcApLayoutManager
AcDbLayoutManagerReactor
AcDbLayout、AcDbPlotSettings和AcDbPlotSettigsValidator用于创建和设置布局对象的属性。AcDbLayoutManager、AcApLayoutManager和AcDbLayoutManagerReactor用于操作布局对象和执行其他与布局有关的任务。
6.4.1.1 布局对象有关布局的信息存储在AcDbLayout类的实例中。布局对象包含对图中需要打印部分的打印和绘图设置信息。例如,布局对象包含绘图设备、纸的大小、绘图范围和旋转角,以及其他一些帮助规定绘图范围的属性。AcDbLayout对象存储在数据库内命名对象词典的ACAD_LAYOUT词典中。每个图纸空间布局有一个AcDbLayout对象,模型空间还有一个单独的AcDbLayout。每个AcDbLayout对象包含与它有关昀AcDbBlockTableRecord对象的ID,这样它很容易查找到布局的实际几何图所在的块表记录。如果一AcDbBlockTableRecord表示一个布局,它包含与其有关的AcDbLayout对象的对象ID。
大多数布局对象的绘图信息存储在AcDbPlotSettings中,AcDbPlotSettings是AcDbLayout的基类。我们可以创建命名绘图设置,并用它对其他AcDbhyout对象进行初始化,这使得我们可以从
一个布局向另一个布局输入或输出绘图设置。这些命名绘图设置存储在AcDbPlotSettings类的实例中。每个命名绘图设置有一个AcDbPlotSettings对象,它们被存储在命名对象词典内的ACAD_PLOTSETTINGS词典中。
   值得注意的是,ACAD_LAYOUT词典中的AcDbLayOut对象与ACAD_PLOTSETTINGS词典中的AcDbPlotSettings对象之间没有直接的联系。
6.4.1.2 布局管理器   我们可以使用AcApLayoutManager类管理AcDbLayout对象。AcApLayoutManager类允许我们进行如下一些工作:
   创建布局。
   删除布局。
   布局改名。
拷贝和克隆布局。
设置当前布局。
查找一个特定的布局。
设置一个布局的绘图特性。
每个应用程序有一个布局管理器实例,布局管理器总是对当前布局进行操作。
6.5 扩展记录扩展记录可以使我们添加任意的应用程序专用数据。因为定义自己的对象类时扩展记录是可选择的,所以它们尤其对AutoLISP程序员有用。扩展记录是一个AcDbXrecord类的实例,
AcDbXRecord类是一个AcDbObject类的子类。扩展记录是以结果缓冲区链表定义的,结果缓冲区链表是一组数据的列表,数据组中的每一个表项包含一个DXF组码和相关的数据,组码值定义了与其相关的数据的类型,扩展记录组码的取值范围为l到369。下面介绍可行的DXF组码。保存在扩展记录中数据数量的多少没有任何限制。任何对象都可以拥有扩展记录,包括任何对象的扩展词典、命名对象词典、任何其他词典或其他扩展记录。当修改了一个扩展记录,不发送通知。如果应用程序想知道何时修改了一个对象拥有的一个扩展记录,应用程序就需要发送它自己的通知。
AcDbXRecord类提供了两个成员函数,setfromRbChain()函数和rbChain(),来设置获得结果缓冲区链表:
Acad::ErrorStatus
AcDbXrecord::setFromRbChain(
    const resbuf& pRb,
    AcDbDatabase* auxDb = NULL);
Acad::ErrorStatus
AcDbXrecord::rbChain(
    resbuf** ppRb,
    AcDbDatabase* auxDb = NULL) const;
AcDbXRecord::setFromRbChain()函数用通过参数传人结果缓冲区链表替换既有的结果缓冲区链表。
6.5.1 扩展记录的DXF组码
  用于扩展记录的DXF组码
  
  
  
  
  
  
  
  
  
  数据类型
  
  1
  
  4
  
  文本
  
  6
  
  9
  
  文本
  
  10
  
  17
  
  点或矢量(3 个实型数)
  
  38
  
  59
  
  实数
  
  60
  
  79
  
  16位整数
  
  90
  
  99
  
  32位整数
  
  102
  
  102
  
  控制字符 “{“ “}”
  
  140
  
  149
  
  实数
  
  160
  
  169
  
  64位整数
  
  170
  
  179
  
  16位整数
  
  210
  
  219
  
  实数
  
  270
  
  279
  
  16位整数
  
  280
  
  289
  
  8位整数
  
  300
  
  309
  
  文本
  
  310
  
  319
  
  二进制块
  
  320
  
  329
  
  句柄
  
  330
  
  339
  
  软指针ID
  
  340
  
  349
  
  硬指针 ID
  
  350
  
  359
  
  软所有 ID
  
  360
  
  369
  
  硬所有
  
6.5.2 扩展记录实例下面的ObjectARX程序例子包含两个函数:createXrecord()和listXrecord()。第一个函数createXrecord()向一个词典添加一个新的扩展记录,并将词典傣加到命名对象词典中,然后向扩展记录添加数据。第二个函数listXrecord()打开扩展记录,获取其数据表,并将数据表传送给打印列表函数,打印出数据。
void
createXrecord()
{
   AcDbDictionary *pNamedobj,*pDict;
   acdbHostApplicationServices()->workingDatabase()
      ->getNamedObjectsDictionary(pNamedobj,AcDb::kForWrite);
   // 检查我们想创建的词典是否已经存在,如果不存在,
   // 创建一个新词典并将其添加到命名对象词典中
   if (pNamedobj->getAt("ASDK_DICT",(AcDbObject*&) pDict,
      AcDb::kForWrite)== Acad::eKeyNotFound)
   {
      pDict = new AcDbDictionary;
      AcDbObjectId DictId;
      pNamedobj->setAt("ASDK_DICT", pDict,DictId);
   }
   pNamedobj->close();
   // 向ASDK_DICT词典添加一个新的扩展记录.
   //
   AcDbXrecord *pXrec= new AcDbXrecord;
   AcDbObjectId xrecObjId;
   pDict->setAt("XREC1", pXrec,xrecObjId);
   pDict->close();
   // 创建一个要添加到扩展记录的结果缓冲区链表
   //
   struct resbuf *pHead;
   ads_point testpt= {1.0, 2.0, 0.0};
   pHead = acutBuildList(AcDb::kDxfText,
      "This is a test Xrecord list",
      AcDb::kDxfXCoord,testpt,
      AcDb::kDxfReal,3.14159,
      AcDb::kDxfAngle,3.14159,
      AcDb::kDxfColor,1,
      AcDb::kDxfInt16,180,
      0);
   // 将数据表添加到扩展记录中。注意:这个函数获取了一个结果缓冲区的引用
   // 而不是指向结果缓冲区的指针,所以我们必须在传送前解除引用的指针
   //
   //
   pXrec->setFromRbChain(*pHead);
   acutRelRb(pHead);
   pXrec->close();
}
// 获取关键字为XREC1的扩展记录
// 将结果缓冲区列表传递给参数printlist使其列出扩展记录的内容
//
void
listXrecord()
{
   AcDbDictionary *pNamedobj;
   acdbHostApplicationServices()->workingDatabase()
      ->getNamedObjectsDictionary(pNamedobj,AcDb::kForRead);
   // 获取关键字为ASDK_DICT的词典对象
   //
   AcDbDictionary *pDict;
   pNamedobj->getAt("ASDK_DICT", (AcDbObject*&)pDict,
      AcDb::kForRead);
   pNamedobj->close();
   // 获取关键字为XREC1的扩展记录
   //
   AcDbXrecord *pXrec;
   pDict->getAt("XREC1", (AcDbObject*&)pXrec,
      AcDb::kForRead);
   pDict->close();
   struct resbuf *pRbList;
   pXrec->rbChain(&pRbList);
   pXrec->close();
   printList(pRbList);
   acutRelRb(pRbList);
}













第七章 访问AutoCAD的全局函数这一章开始,我们所讨论的全局函数是与AutoCAD通信的全局函数,包括给AutoCAD注册命令、处理用户输入、处理数据转换等。对于ObjectARX所有全局函数的一般特性,将在下一章做详细探讨。
7.1 在AutoCAD中的查询与命令7.1.1 通用访问函数在访问AutoCAD命令的函数中,最常用的就是acedCmd()和acedCommand()。像AutoLISP中的函数(command)-样,这两个函数可以直接给AutoCAD图形环境中的“命令:”命令提示符发送命令和其他输入。
int
acedCommand(int rtype, ...);
int
acedCmd(const struct resbuf * rbp);
   与其他大多数的AutoCAD交互函数有所不同,acedCommand()函数具有长度可变的参数表。除了RTLE和RTLB需要传递一个拾取点之外,参数表中的参数是成对出现的,我们称之为“参数对”。参数对中的第二个参数为真正要传递的数据.第一个参数用来表示第二个参数的类型。参数表中的最后一个参数为单个参数,其值为O或RTNONE。对于参数对来说,其中的第一个参数的典型类型码为RTSTR,第二个参数表示要调用的AutoCAD命令的名字字符串
和调用命令所需要的选择项或数据项。正确的参数要满足指定的command命令的需求,acedCommand()的参数表类型码就取结果码。
    参数表中所传递的参数必须与“命令:”提示语句所要求的格式一致。数据类型可以是字符串、实数、整数、点、实体名或者选择集名。像角度、距离和点这样的数据,即可以按照字符串的形式(用户输入的形式)传递,也可以按照各自的类型(整数、实数和点值)来传递。另外,空字符串(“”)等价于从键盘上输入的空格。
在ObjectARX中,函数acedCommand()所能调用的命令是有限制的,这些限制与AutoLISP中对函数(Command)限制是类似的。
注意:acedCommand()。函数可以调用AutoCAD的SAVE和SAVE AS命令。当调用这两个命令时,AutoLISP向当前装入的所有其他ObjectARX应用程序发出kSaveMsg消息,但不向调用SAVE的应用程序发送。当这两个函数调用NEW,OPEN,QUIT命令时,也发送类似消息。
下面的例子演示了在ObjectARX应用程序中调用函数acedCommand()的方法:
int docmd()
{
   ads_point p1;
   ads_real rad;
   if (acedCommand(RTSTR,"circle", RTSTR,"0,0", RTSTR,
      "3,3",0) != RTNORM)
      return BAD;
   if (acedCommand(RTSTR,"setvar", RTSTR,"thickness",
      RTSHORT,1, 0) != RTNORM)
      return BAD;
   p1[X] = 1.0; p1[Y] = 1.0; p1[Z] = 3.0;
   rad = 4.5;
   if (acedCommand(RTSTR,"circle", RT3DPOINT,p1, RTREAL,
      rad, 0)!= RTNORM)
      return BAD;
   return GOOD;
}
当调用这个函数时,只要AutoCAD处于“命令:”提示状态.AutoCAD将完成下列动作:
(1)画一个圆,其圆心在(0.0,0 0),且通过点(3.0,3.O)。
(2)把当前厚度改为1.0;注意第一次调用acedCommand()时,把点当作字符串来传递,而第二次调用acedCornmand()时,则把点当作短整型数来传递。这两种方法都是可行的。
(3)画另一个(拉伸)圆,其圆心在(1.O,1.O,3.0),半径为4.5。注意,最后一次调用acedCommand()时,使用了一个三维点和实数(双精浮点)。点是以引用方式传递的,这是因为ads_point是数组类型。
函数acedCmd()和acedCommand()在功能上是等价的,但它所传递的参数是一个结果缓冲区表,当参数具有复杂逻辑结构时,使用acedCmd()要比使用acedCommand()方便。函数acutBuildList()对于构造命令表是很有用的。使用acedCmd()还有这样的好处,就是命令表在运行时可以动态地进行修改,而不像使用acedCommand()那样命令表在编译时就固定,这样可使执行文件字节较小。下面这段代码能使AutoCAD在当前图形屏幕(或视图)上完成重画(调用REDRAW命令)的功能
struct resbuf *cmdlist;
cmdlist = acutBuildList(RTSTR, "redraw",0);
if(cmdlist == NULL){
    acdbFail("不能创建链表\n");
    return BAD;
}
acedCmd(cmdlist);
acutRelRb(cmdlist);
在调用函数acedCommand()或acedCmd()执行AutoCAD的命令时,如果使用了符号PAUSE做为参数,则所执行的命令将暂停并等待用户的输入(包括拖动)。暂停符PAUSE由包含一个反斜杠的字符串组成,这与为菜单提供的暂停机制是相似的。
在下面的例子中,函数acedCommand()调用AutoCAD的ZOOM命令,并以PAUSE做为其参数,暂停以便用户能够选择ZOOM命令中的选择项:
        result = acedCommand(RTSTR, "Zoom", RTSTR, PAUSE, RTNONE);下面的这个例子调用AutoCAD的CIRCLE命令。在将圆心设置为(5,5)之后便暂停,这样用户就可以在屏幕上拖动圆的半径,当用户用鼠标选择了一个点或者使用键盘输入了圆的半之后,函数将继续执行,从(5,5)到(7,5)面一条直线:
result = acedCommand(RTSTR, "circle", RTSTR, "5,5",     RTSTR, PAUSE, RTSTR, "line", RTSTR, "5,5", RTSTR,     "7,5", RTSTR, "", 0); 对于AutoCAD的某些命令,如TRIM、EXTEND和FILIT等,它们需要用户为命令指定一个拾取点和实体本身。为了通过函数acedCommand()传递这样的实体和点的数据,用户就可以将实体和点放在结果类型码RTLB和RTLE之间。
下面这段代码用于在一张空图上生成一个圆及一条线段。圆的圆心位于(5,5),线段的两端点为(1,5)和(8,5),然后调用AutoCAD的TRIM命令,利用一个在圆上的拾取点,将圆以外的线段剪去。其中acdbEntNext函数寻找出图中下一个实体,acdbEntLast函数寻找出图中最后一个实体:
ads_point p1;
ads_name first,last;
acedCommand(RTSTR,"Circle", RTSTR,"5,5", RTSTR,"2",
        0);
acedCommand(RTSTR,"Line", RTSTR,"1,5", RTSTR,"8,5",
        RTSTR,"", 0);
acdbEntNext(NULL,first);    // 获取圆
acdbEntLast(last);     // 获取直线
// 设置拾取点
p1[X] =2.0;
p1[Y] =5.0;
p1[Z] =0.0;
acedCommand(RTSTR,"Trim", RTENAME,first, RTSTR,"",
        RTLB,RTENAME, last,RTPOINT, p1,RTLE,
        RTSTR,"", 0);
在ObjectARX应用程序中,用户可以通过函数acedGetVar()和acedSetVar()来查询和设置AutoCAD系统变量。这两个函数都使用一个字符串(不区分大小写)来确定系统变量的名称,还为系统变量的类型和值设置了一个结果缓冲区。在使用系统变量查询和设置函数时,设置结果缓冲区是必需的,因为函数所读人的Auto.CAD的系统变量是各种各样的,它可能是整数、实数、字符串等简单变量.也可能是二维点或三维点等复杂变量。
下面这段代码用来保证AutoCAD的命令FILLET中使用的圆的半径大于等于1.O:
struct resbuf rb, rb1; acedGetVar("FILLETRAD", &rb); rb1.restype = RTREAL; rb1.resval.rreal = 1.0; if (rb.resval.rreal < 1.0)     if (acedSetVar("FILLETRAD", &rb1) != RTNORM)         return BAD; // 设置失败结果缓冲区是通过在应用程序中说明自动变量的方法来分配的,这是静态分配缓冲区方式,因此,应用程序并不需要像对待动态分配缓冲区方式那样显式地管理结果缓冲区。
如果要访问的AutoCAD的系统变量为字符串类型,则acedGetVar()函数会自动地为这个字符串分配存储空间,用户必须在自己的应用程序中通过调用标准C的free()函数来负责这部分空间的释放工作,例如:
acedGetVar("TEXTSTYLE", &rb); if (rb.resval.rstring != NULL) //记住要释放字符串占用的空间     free(rb.resval.rstring); 在ObjectARX应用程序中,用户可以通过函数acedGetSym()和acedPutSym()来查询和设置AutoLISP的变量值。
首先,用户在AutoCAD命令行输入以下AutoLISP表达式:
Command:(setq testboole t)
T
Command:(setq teststr “HELLO, WORLD”)
“HELLO, WORLD”
Command:(setq sset1 (ssget))
<Selection set: 1>
下面的程序代码说明如何用acedGetSym()来取得新符号的值
struct resbuf*rb;
int rc;
long sslen;
rc = acedGetSym("testboole", &rb);
if (rc== RTNORM && rb->restype == RTT)
acutPrintf("TESTBOOLEis TRUE\n");
acutRelRb(rb);
rc = acedGetSym("teststr", &rb);
if (rc== RTNORM && rb->restype == RTSTR)
acutPrintf("TESTSTRis %s\n", rb->resval.rstring);
acutRelRb(rb);
rc = acedGetSym("sset1", &rb);
if (rc== RTNORM && rb->restype == RTPICKS) {
   rc = acedSSLength(rb->resval.rlname,&sslen);
   acutPrintf("SSET1 含有%lu 个实体\n", sslen);
}
acutRelRb(rb);
与函数acedGetSym()相对应,函数acedPutSym()可以用来建立和改变AutoLISP符号
ads_point pt1;
pt1[X] =pt1[Y] =1.4; pt1[Z]= 10.9923;
rb = acutBuildList(RTSTR, "GREETINGS",0);
rc = acedPutSym("teststr", rb);
acedPrompt("TESTSTRhas been reset\n");
acutRelRb(rb);
rb = acutBuildList(RTLB, RTSHORT,-1,
             RTSTR, "Thecombinations of the world",
             RTSTR, "areunstable by nature.", RTSHORT,100,
             RT3DPOINT, pt1,
             RTLB, RTSTR,"He jests at scars",
             RTSTR, "thatnever felt a wound.", RTLE, RTLE, 0);
rc = acedPutSym("longlist", rb);
acedPrompt("LONGLIST值被创建\n");
acutRelRb(rb);
要设置AutoLISP的变量为nil,可用下面程序:
    rb->restype = RTNIL;

acedPutSym("var1",rb);

上述程序的运行结果为(应用程序应该通知用户变量已经被改变):
  TESTSTR被重置!
  LONGLIST被创建!
命令: !teststr
         (“GREETINGS”)
命令: !longlist

     ((-1 “The combinations ofthe world”“are unstableby nature.” 100 (1.4 1.4 10.9923) (“Hejests at scars” “that never felt a wound.”)))

在ObjectARX应用程序中,可以使用函数acedFindFile()来搜索指定名字的文件。该函数能够按照指定的路径进行搜寻,也能够按照当前AutoCAD库的路径进行搜寻。
下面这段代码演示了函数acedFindFile()按照AutoCAD的库路径来搜索指定文件的方法:
char *refname= "refc.dwg";
char fullpath[100];
.
.
.
if (acedFindFile(refname, fullpath)!= RTNORM) {
   acutPrintf("找不到文件 %s.\n", refname);
   return BAD;
   如果acedFindFile()函数调用成功,参数fullpath将被设置成一个完整的路径名字符串,如:
   /home/work/ref/refc.dwg
   在调用函数acedFindFile()的同时,ObjectAPX程序还可以通过标准的AutoCAD文件对话框提示用户输入文件名。显示文件话框的函数为acedGetFielD()。
  下例就是使用对话框来提示用户输入一个objectARX应用程序的名字:
struct resbuf*result;
int rc, flags;
if (result= acutNewRb(RTSTR)== NULL) {
   acdbFail("不能给缓冲区分配内存\n");
   return BAD;
}
result->resval.rstring=NULL;
flags = 2; // 使"Type it" 按钮无效.
rc = acedGetFileD("Get ObjectARX Application", // 标题
            "/home/work/ref/myapp",   // 默认路径
            NULL, // 缺省文件扩展名相当于"*".
            flags, // 控制标记
            result); //用户选择路径
if (rc== RTNORM)
rc = acedArxLoad(result->resval.rstring);
ObjectARX的函数acedOsnap()可以按照AutoCAD的某种目标捕捉方式找到一个点。捕捉方式是以字符串的形式在函数acedOsnap()的参数中指定的。下面是对acedOsnap()的调用,用来寻找一个接近ptl的某个线段的中点:
    acedOsnap(pt1, "midp" , pt2);
下面是对acedOsnap()的调用,用来寻找一个离m1最近的线段的中点、端点或圆弧(或圆)的圆心:
    acedOsnap(ptl, "midp,endp,center" ,pt2);
如果找到一个点,第三参数(本例中为pt2)就设置为捕捉点,如果没找到点,acedOsnap()函数返回RTNORM。
注意:当使用目标捕捉(Object Snap)方式时,AutoCAD的系统变量APERTURE决定了所选点及实体间所允许的最小距离。
与AutoLISP的(vports)函数相似,ObjectARX的函数acedVports()用来取得描述当前视区的描述符表。
下面这段代码用来取得当前视区的设置,然后将其传递给AutoLISP使其显示:

struct resbuf*rb;

    int rc;
    rc = acedVports(&rb);
    acedRetList(rb);
    acutRelRb(rb);
   在上述例子中,如果用户所给定的为一个单视区配置(TILEMODE的值为ON),则该程序返回的表如下图所示。
  如果给出的是四个大小相等的视区,它们位于屏幕的四个角,且TILEMODE设为on.那么上面的程序返回的链表如下图所示。
   由于当前视区的描述符总是在表的开始部分,因此图上第5号视区为当前视区。
ObjectARX函数中除提供一组用于绘制几何图形的函数之外,还提供了一组函数让应用程序获得几何信息,称之为几何类辅助函数。例如:函数acutDistance()可用来计算两个点间的距离;函数acutAngle()可用来计算某条线段与当前UCS(在XY平面内)的X轴间的夹角;函数acutPolar()可以使用极坐标方法计算出某个点相对另一个点的极坐标值。与ObjectARX库中的大多数函数不同,这些几何类辅助函数都不返回状态值。不过,也有一个例外,如果用来计算两条直线交点的函数acdbInters(),找到了符合要求的交点,则将返回RTNORM。
下面这段代码演示了调用几何类辅助函数的方法:
ads_point pt1,pt2;
ads_point base,endpt;
ads_real rads,length;
  
// 返回在当前UCS上XY平面上的角度
rads = acutAngle(pt1, pt2);
// 返回三维空间距离
length = acutDistance(pt1, pt2);
base[X] =1.0; base[Y]= 7.0; base[Z]= 0.0;
acutPolar(base, rads, length, endpt);
对函数acutPolar()的调用把参数endpt设置为一点,它与(1,7)点的距离等于ptl和pt2之间的距离,endpt与(1,7)点的连线和X轴的夹角等于ptl和pt2之间的连线和X轴的夹角。
函数acedTextBox()用来查找包含某文本实体的框的两个对角点的坐标值。其参数ent必须是一个文字定义或以结果缓冲区结构指定的一个文本字符串组。参数pl表示框的XY最坐标.p2表示框的XY最大坐标。
如果文本框位于水平位置且没有旋转,则pl表示文本框的左下角,p2表示文本框的右上角。文本实体位于实体坐标系( ECS)中,其原点(O,0)为基线的左端点(如果文本字符中有下行字母,如p和g,则原点将不在左下角)。例如下图显示了文本高度为1.0时调用函数acedTextBox()之后的结果,图中同时显示了文本的基线和原点。

下图显示了对于垂直文本和倾斜文本,调用函数acedTextBox()返回的点的情况。字符的高度限定为1.0。

注意:对于垂直文本来说,函教acedTextBox()仍然按照从左到右,从下到上的次序返回ptl和pt2点位;因此,图中的ptl点的值为负。
函数acedTextBox()也可以用来度量实体Attdef和Attrib中的字符串。对于Attdef,函数acedTextBox()度量标记字符串(第2组);对于Attrib,它度量当前值(第1组)。
下面的函数,也使用了一些实体处理函数,提示用户选择某个实体,然后用函数acedTextBox()返回的坐标值画出文本边界框。
注意:下面的函数只是一个简单的例子,只有在通用坐标系(WCS)中才能正确运行。如果用户当前不在通用坐标系中。应该调用acedCommand()将实体中得到的ECS点转为UCS点。
int tbox()
{
   ads_nametname;
   structresbuf *textent,*tent;
   ads_pointorigin, lowleft,upright, p1,p2, p3, p4;
   ads_realrotatn;
   charrotatstr[15];
   if(acedEntSel("\n请选择文本:",tname, p1)!= RTNORM) {
      acdbFail("没有选中文本\n");
      returnBAD;
   }
   textent= acdbEntGet(tname);
   if(textent == NULL){
      acdbFail("不能检索文字实体\n");
      returnBAD;
   }
   tent= entitem(textent,10);
   origin[X] = tent->resval.rpoint[X]; //ECS 坐标
   origin[Y] = tent->resval.rpoint[Y];
   tent= entitem(textent,50);
   rotatn= tent->resval.rreal;
   
   if(acdbAngToS(rotatn,0, 8, rotatstr) != RTNORM){
      acdbFail("不能检索或转换角度\n");
      acutRelRb(textent);
      returnBAD;
   }
   if(acedTextBox(textent,lowleft, upright)!= RTNORM) {
      acdbFail("不能检索到文字边框坐标\n");
        acutRelRb(textent);
      returnBAD;
   }
   acutRelRb(textent);
   //
   //  
   p1[X] = origin[X] + lowleft[X]; // UCS 坐标
   p1[Y] = origin[Y] + lowleft[Y];
   p2[X] = origin[X] + upright[X];
   p2[Y] = origin[Y] + lowleft[Y];
   p3[X] = origin[X] + upright[X];
   p3[Y] = origin[Y] + upright[Y];
   p4[X] = origin[X] + lowleft[X];
   p4[Y] = origin[Y] + upright[Y];
   if(acedCommand(RTSTR,"pline", RTPOINT,p1,
      RTPOINT,p2, RTPOINT,p3,RTPOINT,p4, RTSTR,"c",
      0) != RTNORM){
        acdbFail("创建多段线有问题\n");
        returnBAD;
   }
   if(acedCommand(RTSTR,"rotate", RTSTR,"L", RTSTR,"",
      RTPOINT,origin, RTSTR,rotatstr, 0) != RTNORM){
        acdbFail("旋转多段线有问题\n");
        returnBAD;
   }
   returnGOOD;
}
上面的例子中调用了AutoCAD的ROTATE命令,间接地起到旋转的作用。有一种更直接的办法就是把旋转和窗口点的计算结合在一起,例如:
ads_real srot,crot;
tent = entitem(textent, 50);
rotatn = tent->resval.rreal;
srot = sin(rotatn);
crot = cos(rotatn);
.
.
.
p1[X] =origin[X]+ (lowleft[X]*crot - lowleft[Y]*srot);
p1[Y] =origin[Y]+ (lowleft[X]*srot + lowleft[Y]*crot);
p2[X] =origin[X]+ (upright[X]*crot - lowleft[Y]*srot);
p2[Y] =origin[Y]+ (upright[X]*srot + lowleft[Y]*crot);
p3[X] =origin[X]+ (upright[X]*crot - upright[Y]*srot);
p3[Y] =origin[Y]+ (upright[X]*srot + upright[Y]*crot);
p4[X] =origin[X]+ (lowleft[X]*crot - upright[Y]*srot);
p4[Y] =origin[Y]+ (lowleft[X]*srot + upright[Y]*crot);
7.2 获取用户输入ObjectARX提供了许多全局函数允许应用程序交互地让用户输入数据。下面将分别予以介绍。
7.2.1 用户输入类函数         用户输入类函数或acedGet**()函数的暂停,用来等待用户输入指定类型的数据,并将读入的值用参数result返回。用户可以预先定义一条当函数处于等待状态时显示的提示语句。
注意:ObjectARX库中,函数acedGetFuncode()、acedGetArgs()、acedGetVar()和acedGetInput()与用户输入类函数名的形式相同,但它们并不属于于本节中要介绍的用户输入类函数。与之相反,函数acedEntsel()、acedNEntSelp()、acedNetSel()和acedDragGen()虽然不具有输入类函数名的形式,但它们都属于用户输入类函数。
  
用户输入类函数
  
  函数名
  
  功能
  
  acedGetInt
  
  获取整数值
  
  acedGetReal
  
  获取实数值
  
  acedGetDist
  
  计算距离
  
  acedGetAngle
  
  获取一个角度值
  
  acedGetOrient
  
  获取一个角度值
  
  acedGetPoint
  
  获取一个点
  
  acedGetCorner
  
  获取矩形一个角
  
  acedGetKword
  
  获取一个关键字
  
  acedGetString
  
  获取字符串
  
在用户输入类函数中,有些函数,如acedGetString(),要求用户在AutoCAD提示符之后输入适当类型的数据;其他的函数,如acedGetDist(),允许用户通过键盘输入数据或在屏幕上选择点来指定一个数据。
如果用户希望通过屏幕选择一个输入值,则AutoCAD会在屏幕上显示一条拉伸线。用户还可以在应用程序中对拉伸线进行控制,例如,调用函数acedInitGet()便能在屏幕上高亮显示某条拉伸线(或方框)。
函数acedGetKword()用于读入一个关键字。要读入的关键字为字符串值,不过其中不包含空格符,关键字可以用缩写形式来表示。对于所有的用户输入类函数(函数acedGetString除外),除了能接受正常的返回值之外,也能接受关键字的值,但是,在使用这些函数之前,必须调用函数acedInitGet()来设置该关键字。能接受关键字输入的函数同样也能接受不带空格的任意文本输入。
注意:利用函数acedInitGet().用户也可使用选择实体类函数acedEntSel()、acedNEntSelP()和acedNetSel() 识别关键字输入。另外,函数acedDragGen()也能识别关键字。
AutoCAD用户不能用AutoLISP表达式对用户输入函数进行响应。用户输入类函数都具有AutoCAD那样对输入进行检查的功能,细微的输入错误(像只用一个数值来响应acedGetPoint())由AutoCAD来捕捉处理,而且不用用户输入类函数返回该错误。
用户在编程时仅仅对下表中的一些系统变量进行检查即可。
  
  
  
  
  代码
  
  含义
  
  RTNORM
  
  用户输入的是一个有效的值
  
  RTERROR
  
  函数调用失败
  
  RTCAN
  
  用户按下了ESC
  
  RTNONE
  
  用户只按下了回车键
  
  RTREJ
  
  AutoCAD 拒绝无效请求
  
  RTKWORD
  
  用户输入一个关键字或任意文本
  
使用RTCAN是为让用户能够通过ESC键来取消对应用程序的请求,这样便使ObjectARX应用程序与AutoCAD的内部取得一致,因为AutoCAD的内部是允许取消的。RTNONE和RTKWORD是由函数acedInitGet()控制,仅当调用acedInitGet()函数将这两个返回值显式地置为有效时,用户输入类函数才能返回RTNONE和RTKWORD。
7.2.2 对用户输入函数的控制函数acedInitGet()有两个参数va1和kw1,参数val用于指定一个或多个控制位,它用来确定其后的输入函数acedGet**()的某些输入值是否有效;参数kw1用来指定某些关键字,在其后调用函数acedGet**()、acedEntSel()、acedNEntselP()、acedNEntsel()或acedDragGen()的过程中,这些关键字是可以识别的。
注意:函数acedInitGet(),建立的控制位和关键字仅对其后的一个用户输入类函数的调用起作用,井在调用之后由动消除;因此用户不用对这种拉刺和关键字进行第二次调用去消除它。
下表汇总了参数var能设置的控制位。如果要同时使其满足一个以上的情况,则可同时置位,让var值成为0到127之间的一个数。如果var等于O,则没有任何情况对其后的用户输入类函数的调用起作用。
  
  
  
  
  
  
  代码
  
  位值
  
  控制作用说明
  
  RSG_NONULL
  
  1
  
  防止用户仅用<回车>键和空格键响应请求
  
  RSG_NOZERO
  
  2
  
  防止用户输入零响应请求
  
  RSG_NONEG
  
  4
  
  防止用户输入负值响应请求
  
  RSG_NOLIM
  
  8
  
  在当前图形的限制下,允许用户输入一个点。这个条件适用于一个用户输入函数,而不管AutoCAD系统变量LIMCHECK在当前是否设置为on
  
  RSG_DASH
  
  32
  
  对那些允许用在图形屏幕上通过选择一个位置来设定一个点的函数来说,将导致AutoCAD所显示的橡皮条或线框成为虚线,代替原来的实线。如果系统变量POPUPS为零,那么AutoCAD将忽略这一位的作用。
  
  RSG_2D
  
  64
  
  忽略由acedGetDist()返回的三维点Z坐标,保证一个程序返回二维距离
  
  RSG_OTHER
  
  128
  
  允许任意的键盘输入,也就是说允许用户随意输入
  
下面的程序演示了如何用函数acedInitGet()建立对函数acedGetInt()的调用:
int age;
acedInitGet(RSG_NONULL| RSG_NOZERO | RSG_NONEG,NULL);
acedGetInt("Howold are you? ", &age);
这段程序要求输人用户的年龄。如果用户输入的是一个负数或零,或者只键人回车或一个关键宇(此时AutoCAD自动拒绝任何输入的非正数值),那么AutoCAD会自动显示一个错误信息并重复提示符。
RSG_OTHER控制位可使下一个用户输入类函数的调用接受任意输人值。如果设置了RSG_OTHER,而用户输人了一个不可识别的值,那么函数acedGet**()特返回RIKWORD,且通过调用acedGetInput()检索输入的值。因为在AutoCAD中,空格符与回车符一样能够表示用
户输人结束,因此用户输人的字符串之间不能出现空格符号。RSG_OTHER的优先级最低,也就是说,如果函数acedInitGet()设置不允许负值输人的RSC_NONEG位,则AutoCAD会始终拒绝负值的输人,而不考虑设置了RSG_OTHER位与否。
下面的程序允许任意输入(出错检测最少):
int age,rc;
char userstring[511];
acedInitGet(RSG_NONULL| RSG_NOZERO | RSG_NONEG| RSG_OTHER,
        "MineYours");
if ((rc= acedGetInt("Howold are you? ", &age))
   == RTKWORD)
   // 关键字输入或任意输入
   acedGetInput(userstring);
}
在这个例子中,函数acedGetInt()根据用户的输入将返回下表中的值。
    
  
  
  
  用户输入
  
  结果
  
  41
  
  acedGetInt() 返回RTNORM age 设置为 41
  
  m
  
  acedGetInt() 返回RTKWORD, 然后 acedGetInput() 返回 “Mine”
  
  y
  
  acedGetInt() 返回 RTKWORD, 然后 acedGetInput() 返回 “Yours”
  
  twenty
  
  acedGetInt() 返回 RTKWORD, 然后 acedGetInput() 返回 “twenty”
  
  what???
  
  acedGetInt() 返回 RTKWORD, 然后 acedGetInput() 返回“what???”
  
  -10
  
  由于设置了RGS_NINEG,优先于RSG_OTHER,所以AutoCAD拒绝接受该输入并重新显示提示符。
  
  -34.5
  
  acedGetInt() 返回 RTKWORD, 然后 acedGetInput() 返回
  “-34.5”
  AutoCAD 不会拒绝该输入,因为函数要求的是整数而不是实数
  
注意:如果先调用了函数acedInitGet()允许任意输入,函数acedDragGen()用返回RTSTR(而不是RTKWORD)表明所接受的输入为任意输入。
acedInitGet()函数的参数kw1是可选项。它用来设置能被下一个用户输人类函数acedGet**()调用所要识别的关键字表。完成这种设置之后,用户所输入的关键字值便可通过调用函数acedGetInput()来检索(如果应用程序中所调用的用户输入类函数为acedGetKword(),就不必再调用函数acedGetInput()来检索关键宇了,因为函数acedGetKword()本身就返回关键字的值)。至于每个关键字所对应的下一步操作是什么,就是ObjectARX应用程序所要解决的问题了。
函数acedGetInput()总是返回参数kw1中所设置的关键字,并且保留关键字的大小写(但不包括其后面逗号之后的可选说明字符串)。不管用户输入什么关键字,应用程序仅仅用一个字串去比较识别它,就像下面例子中所示。此例中,在调用acedGetReal()之前,用acedInitGet()定义了两个关键字,程序检查这些关键字并根据它们设置输入值:
int stat;
ads_real x, pi = 3.14159265;
char kw[20];
// Nullinput is not allowed.
acedInitGet(RSG_NONULL,"Pi Two-pi");
if ((stat= acedGetReal("Pi/Two-pi/<number>:", &x)) < 0) {
   if (stat == RTKWORD&& acedGetInput(kw) == RTNORM){
      if (strcmp(kw, "Pi") == 0) {
        x = pi;
        stat= RTNORM;
      } else if (strcmp(kw, "Two-pi")== 0) {
        x = pi * 2;
        stat= RTNORM;
      }
   }
}
if (stat!= RTNORM)
acutPrintf("acedGetReal() 输入失败.\n");
else
acutPrintf("输入的是%f\n", x);
在这一段程序中,用函数acedInitGet()进行了设置,使其不能有空输入,并设定了两个关键字:Pi和Two - pi。当调用acedGetReal()时,用户就对提示符“Pi/Two - pi/< number>:”进行响应,可以输入一个实型数(存入局部变量x中),也可以输入一个关键字。如果是一个关键字,则acedGetReal()会返回RTKWORD.然后程序用acedGetInput()检索该关键字(注意,这里对acedGetInput()的返回状态也进行了检查),并根据用户输入情况对x值进行了设定。该例中,用户可以用p来选pi、用t来选2pi的值。
7.2.3 动态拖动函数函数acedDrgGen()使用户通过拖动一组选中的实体集来对图形进行修改。下面的程序演示了函数acedDragGen()的用法:
int rc;
ads_name ssname;
ads_point return_pt;
// 提示用户生成一个选择集
if (acedSSGet(NULL, NULL, NULL, NULL, ssname) == RTNORM)
// 新选择实体
rc = acedDragGen(ssname,
            "拖动选择对象", //提示
            0, // 显示常规光标(十字丝)
            dragsample,// 转换函数

            return_pt);// 设置指定的位置

7.2.4 用户中断函数如果AutoCAD用户在输人类函数运行时敲ESC来响应,则函数将返回RTCAN。在ObjectARX库中具备上述功能函数有:acedCommand()、acedCmd()、acedEntSel()、acedNEntSelP()、acedNEntsel()、acedDragGen()和acedSSGet()等。对于我们所扩充的函数,也应该响应ESC能立即退出。ObjectARX提供了一个函数acedUsrBrk()来检查用户是否按下了ESC键。该函数可以使应用程序检查用户是否有中断申请。
在应用程序与用户交互的过程中,除非需要进行长时间的计算,否则没必要在应用程序中调用函数acedUsrBrk()。另外,不要把函数acedUsrBlk()作为检查用户输入类函数acedGet**(),或者其他能返回符号RTCAN的函数的替代函数来使用。
在某些特殊的情况下,应用程序需要忽略用户的中断请求,这时,可以利用函数acedUsrBlk ()来消除中断请求;否则,用户所按下的ESC键将会使后面的用户输入acedGebcxx()函数的调用失败。如果应用程序需要不响应ESC中断请求,则它应当显示出一条信息来提示用户,以免给用户带来不必要的麻烦。在ObjectARX应用程序中,ESC的作用状态总是自动消除的。
在下面例子运行时,如果用户按下ESC键,则程序将终止运行:
int test()
{
   int i;
   while (!acedUsrBrk()) {
      acedGetInt("\nInput integer:", &i); // WRONG
      .
        .
        .
   }
}
   我们也可不通过调用函数acedUsrBrk()就正确处理ESC中断请求,修改后的代码如下:
   int test()
{
   int i;
   for (;;) {
      if (acedGetInt("\nInputinteger:", &i) != RTNORM)
        break;
      ...
   }
}
   下面的程序中,对循环条件进行了修改,该程序也能正确运行:
int test()
{
   int i;
   while (acedGetInt("\n请输入整数:", &i)== RTNORM) {
      ...
   }
}
在涉及到长时间操作的程序中,使用函数acedUsrBrk()是比较合适的。某个代码在检查图形数据库中的每一个实体时可能消耗时间,这时就应该使用acedUsrBrk()。
7.2.5 向AutoLISP返回值的函数
   ObjectARX库中提供了一组函数,它们用来向AutoLISP返回数据。与其他的ObjectARX函数不同,AutoLISP中没有与这组函数对应的函数。
   
  函数名称
  
  返回值
  
  acedRetInt
  
  整数值
  
  acedRetReal
  
  实数值
  
  acedRetPoint
  
  三维点
  
  acedRetStr
  
  字符串
  
  acedRetVal
  
  用结果缓冲区传递返回值
  
  acedRetName
  
  实体(RTENAME)或者选择集(RTPICKS)
  
  acedRetT
  
  AutoLISPT
  
  acedRetNil
  
  AutoLISPnil
  
  acedRetVoid
  
  一个空白值:AutoCAD不显示该结果
  
  acedRetList
  
  一个结果缓冲区表
  
   下面的例子演示了当应用程序接到kInvkSubrMsg请求时,如何调用函数acedRetReal()向AutoLISP返回一个实数值:
int dofun()
{
    ads_real x;
       // 这里检查参数和输入条件
       // 计算x值
       acedRetReal(x);
    return GOOD;
}
    注意:对于单个kInSubrMsg请求,外部函数可以多次调用返回值函数,但是AutoLISP函数只返回最后一个返回值函数传递给它的值
7.3 类型转换
    这一节我们讨论对数据类型和单位进行转换的一些函数。
7.3.1 字符串转换函数
   函数函数acdbRToS()和acdbAngToS()用来将AutoCAD中使用的数值转换成字符串值。函数acdbRToS()用来将一个实数值转换成字符串值;函数acdbAngToS()用来将一个角度值转换成字符串值。它们生成的字符串格式由AutoCAD的系统变量来控制,对于实数值,单位和精度由系统变量LUNITS和LUPREC指定;对于角度值,单位和精度由系统变量AUNITS和AUPERC指定。另外,不论是实数值还是角度值,标注变量DIMZIN控制着是否在生成字符串的前后补0.ObjectARX中还提供了两个与上述函数功能相反的函数acdbDisToFO和acdbAngToF(),它们用来将字符串值转换成实数值和角度值。如果传递给它们的字符来自函数acdbRToS()或acdbAngToS(),则函数acdbDisToF()和acdbAngToF()所返回的是有效值。
   下面这段程序演示了调用函数acdbRToS()的方法(该例子省略了必要的错误检查):
ads_real x =17.5;
char fmtval[12];
//精度是第三个参数
acdbRToS(x,1, 4, fmtval); //Mode 1 = scientific
acutPrintf("结果形式为%s\n", fmtval);
acdbRToS(x,2, 2, fmtval); //Mode 2 = decimal
acutPrintf("结果形式为%s\n", fmtval);
acdbRToS(x,3, 2, fmtval); //Mode 3 = engineering
acutPrintf("结果形式为%s\n", fmtval);
acdbRToS(x,4, 2, fmtval); //Mode 4 = architectural
acutPrintf("结果形式为%s\n", fmtval);
acdbRToS(x,5, 2, fmtval); //Mode 5 = fractional
acutPrintf("结果形式为%s\n", fmtval);
上述函数调用(假定DIMZIN为0)将在AutoCAD文本屏幕上显示
结果形式为 1.7500E+01
结果形式为 17.50
结果形式为 1′-5.50″
结果形式为 1′-5 1/2″
结果形式为 17 1/2
   当系统变量UNITMODE等于1时,输出串的单位由输人字符串的单位决定,因此,函数acdbRToS()在下列模式下返回的值在形式上是不同的:工程单位制(模式3),建筑单位制(模式4)和分数形式(模式5)。举例来说,当UNITMODE=1时,在上面的例子中,前两行的输出形式保持不变,而后三行将变成:
结果形式为 1′.50″
结果形式为 1′-1/2″
结果形式为 17-1/2
函数acdbDisToF()和acdbRToS()功能相反。下例就是引用了上面程序转换后生成的字符串值,再进行反变换得来的,因此,参量result的值是17.5。

acdbDisToF("1.7500E+01",1, &result);  

acdbDisToF("17.50",2, &result);   

acdbDisToF("1'-5.50\"",3, &result);  

acdbDisToF("1'-51/2\"", 4, &result);

acdbDisToF("171/2", 5, &result);  

下面的程序代码演示调用函数acdbAngToS()的方法,这与在上面的例子中对函数acdbRtoS()的调用类似:
ads_real ang= 3.14159;
char fmtval[12];  
acdbAngToS(ang,0, 0, fmtval);  
acutPrintf("角度的形式为%s\n", fmtval);
acdbAngToS(ang,1, 4, fmtval);  
acutPrintf("角度的形式为%s\n", fmtval);
acdbAngToS(ang,2, 4, fmtval);  
acutPrintf("角度的形式为%s\n", fmtval);
acdbAngToS(ang,3, 4, fmtval);  
acutPrintf("角度的形式为%s\n", fmtval);
acdbAngToS(ang,4, 2, fmtval);
acutPrintf("角度的形式为%s\n", fmtval);
   上述调用(仍然假设DIMZIN等于0)将在AutoCAD文本屏幕上显示:
   角度的形式为 180
角度的形式为 180d0′″
角度的形式为 200.0000g
角度的形式为 3.1416r
角度的形式为 W

注意:当函数acdbAngToS()以测量系统(surveyor)单位返回字符串(模式4)时,系统变量UNITMODE对字符串也是有影响的,如果UNITMODE等于0,返回的字符串可以含有空格例如(“N45d E”);如果UNITMODE等于1,那么字符串不含有空格(例如“N45dE”)。

函数acdbAngroF()与函数acdbAngToS()功能相反,下例中引用上面程序转换之后生成的字符串值,再进行反变换得到的,所以在下面的调用中,参数result的返回值均为3.14159(事实上,例子中这个值被4舍5入成为3.1416)。

acdbAngToF("180",0, &result);  

acdbAngToF("180d0'0\"",1, &result);  

acdbAngToF("200.0000g",2, &result);  

acdbAngToF("3.1416r",3, &result);  

acdbAngToF("W",4, &result);

注意:当使用度、分、秒的形式表示角度时,必须在单位秒(”)之前使用反斜杠符(\),否则字符串中的秒(”)将作为字符串的结束标志。上例中函数acdbAngToF()的调用,就用了这一点。
7.3.2 量纲单位转换函数AutoCAD软件包的文件acad.unt中,定义了一些常用的量纲单位转换关系,例如英里与公里,华氏与摄氏等等。在ObjectARX中,函数acutCvUnit()用来将一种单位的数值转换成另一种单位的数值。单位用字符串的形式表示,用户所使用的字符串必须与文件acad.unt中定义的字符串一致。
如果当前绘图单位是工程单位或建筑单位(英尺或英寸),下面的程序能把用户指定的距离转换成以米为单位的值:
ads_real eng_len, metric_len;
char *prmpt = "请输入距离值: ";
if(acedGetDist(NULL,prmpt, &eng_len)!= RTNORM)
return BAD;
acutCvUnit(eng_len, "inches","meters", &metric_len);
不能用函数acutCvUnit()转换不相配的单位,例如不能把英寸转换成年。
7.4  字符处理函数ObjectARX提供了一组函数用于处理字符。与标准C中的头文件ctype.h中定义的字符处理函数相比,ObjectARX中的这组函数具有除ASCII之外独立于任意特定字符集的优点,它们适合于当前AutoCAD的语言配置,在其他方面,这组函数与标准C中的类似函数完全一样。
  函数名称
  
  功能
  
  acutIsAlpha
  
  验证字符是否为字母
  
  acutIsUpper
  
  验证字符是否是大写形式
  
  acutIsLower
  
  验证字符是否是小写形式
  
  acutIsDigit
  
  验证字符是否是数字
  
  acutIsXDigit
  
  验证字符是否是十六进制数字
  
  acutIsSpace
  
  验证字符是否是空格
  
  acutIsPunct
  
  验证字符是否是标点
  
  acutIsAlNum
  
  验证字符是否是数字或字母
  
  acutIsPrint
  
  验证字符是否为可打印字母
  
  acutIsGraph
  
  验证字符是否是图形字符
  
  acutIsCntrl
  
  验证字符是否是控制字符
  
  acutToUpper
  
  把字符串转换为大写形式
  
  acutToLower
  
  把字符串转换为小写形式
  
下面的程序代码用于将一个字符转换成其大写形式。如果字符已经是大写。则函数acutIsUpper()不起作用:

int cc =0x24;

cc = acutToUpper(cc);
7.5  坐标变换函数函数acedTrans()用于将一个点值或位移向量从一个坐标系转换到另外一个坐标系中。参数pt可以表示三维空间中的一个点,也可以表示一个三维向量。若参数disp的值为O,则pt表示一个点;否则,pt表示一个向量。参数result用于返回转换点或向量的值,它是以引用方式来调用的,它与pt 一样都是ads_point类型。
两个坐标系的参数from和to都指向某个结果缓冲区。参数from指定pt的参照坐标系,参数to指定result的参照坐标系。参数from和to可以按下面的方式指定坐标系:

一个整形码(restype==RTSHORT)指定WCS、当前UCS或当前DCS(属于当前视区或图纸空间的)。

一个实体名(restype ==RTNAME),由宴体名返回或由选择集函数返回,它指定该命名实体的ECS。对平面实体来说,ECS可以不同于WCS,如果ECS和WCS没有区别,那么它们之间的转换是一个恒等变换。

一个3D拉伸矢量(restype==RT3DPOINT)。这是指定实体的ECS的另一种方法。拉伸矢量总是以通用坐标表示,拉伸矢量(O,0 1)指定了WCS本身。
下面分别介绍能由参数from和to指定的AutoCAD坐标系:

WCS通用坐标系,即“参考”坐标系。其他的坐标系都是相对于WCS来定义的。WCS不会有任何改变。在WCS中计算的各种值在别的坐标系的相互变换中是不会改变的。

UCS用户坐标系,即所谓“工作”坐标系统。所有传递给AutoCAD命令的点,包括那些由AutoLISP程序和外部函数返回的点,都是当前UCS中的点(除非用户在命令:提示符下,在它们之前加一星号(*))。如果要让应用程序发送以WCS、ECS或DCS坐标系表示的坐标给AutoCAD命令,那么首先必须调用acedTrans()将它们转换成UCS坐标系的坐标。

ECS实体坐标系。由函数acedEntGet()返回的点值是用相对于实体自身的实体坐标系表示的。除非按照实体的具体使用将它们转换成WCS坐标系、当前UCS坐标系或当前的DCS坐标系中的值,否则这些值都是没有用的。相反,当调用函数acedEntMod()或函数acedEntMake()耍把点写人数据库时,必须把这些点的坐标转换成ECS坐标系表示形式。

DCS显示坐标系。各种显示对象在它们被显示之前都要转换成用该坐标系表示的坐标值。DCS坐标系统的原点是存储在AutoCAD系统变量TARGET中的点,它的Z轴是描视图方向的。换句话说,一个视区总是DCS坐标系的一个平面视图。这些坐标可以用于确定各种对象放在什么地方。当from和to的整型码是2和3时,无论谁是2,谁是3,2都表示当前模型空间视区的DCS坐标系,3表示图纸空间的DCS坐标系(PSDCS)。当型码2和除3之外的其他整型码联用时,将表示当前空间的DCS坐标系,不管它是图纸空间还是模型宅间,而另外一个参数也假设为表示当前空问的一个坐标系。
PSDCS  图纸空间的DCS坐标系。这种坐标系只能与当前激括模型空间视区的DCS坐标系进行转换。这种转换在本质上是一种二维的转换,这里X和Y坐标总是成比例的,而且如果参数disp为O,那么也可“有偏移量。这里Z坐标也是成比例的,但是它绝不被转换;它可以用来计算两种坐标系统之间的比例因子。PSDCS(整形码为2)只能转换到当前空间视区:如果参数from等于3,那么参数协必须等于2.反之亦然。
下面的程序代码将一个点从WCS的坐标系转换成当前UCS坐标系:
ads_point pt, result;
struct resbuffromrb, torb;
pt[X] =1.0;
pt[Y] =2.0;
pt[Z] =3.0;
fromrb.restype= RTSHORT;
fromrb.resval.rint = 0; // WCS  
torb.restype= RTSHORT;
torb.resval.rint = 1; // UCS  
// disp ==0 表明pt是一个点
acedTrans(pt,&fromrb, &torb,FALSE, result);
如果当前UCS坐标系绕通用坐标系的Z轴逆时针旋转了90度。那么对函数acedTrans()的调用应设置result为点(2.0,-1.0,3.0)。另一方面,如果按如下形式调用函数acedTrans():那么结果是:
(-2.0,-1.0,3.0)
acedTrans(pt, &torb,&fromrb, FALSE,result);
7.6 显示控制函数
   ObjectARX库中提供了一些控制AucoCAD显示的函数,它们即可以控制文本屏幕,又可以控制图形屏幕。
7.6.1 交互式输出函数
         函数acedPrompt()是基本的输出函数,它在AutoCAD的提示行上显示一条信息。还有一个名为acutPrintf()的函数,它在文本屏幕上显示文本信息。该函数的调用过程等价于标准C中的函printf()。之所以单独提供函数acutPrintf(),是因为在有些开发平台上,标准C库函数priruf()在屏幕上显示不出其应该输出的信息acdbFail()函数也在文本屏幕上显示信息。在使用上述两个函数时,用函数acedPrompt()显示的字符串的长度不得超过图形屏幕提示行的最大长度,通常为80个字符;用函数acutPnntf()显示的字符串的长度不得超过132,因为该函数所使用的缓冲区长度为132。
7.6.2 图形和文本屏幕控制函数
   为了在单屏幕的AutoCAD初始化过程中实现文本屏幕与图形屏幕的切换,ObjectARX应用程序可以调用函数acedGraphScr()显示图形屏幕,面通过调用函数acedTextScr()来显示文本屏幕,这两个函数与AutoCAD的命令GRAPHSCR和TEXTSCR的作用相同,也和触动屏幕翻页功能键的作用相同。函数acedTextPage()和函数acedTextScr()功能相同,但是前者在显示文本屏幕前要先清屏幕(如同AutoCAD的STATUS的命令一样)。
函数acedRedraw()类似于AutoCAD的REDRAW命令,但是它提供比控制显示更多的功能:它不仅能重画整个图形屏幕,而且能指定一个实体垂画或者不重画(就是取消它)。如果实体是复杂的实体,比如说是多义线或块,acedRedraw()可以重画(或不画)整个实体,或者是实体中的某一部分,函数acedRedraw()可以用来以高亮度或低亮度显示指定的实体。
7.6.3 低级图形和用户输入类控制函数
低级图形和用户输入控制函数,能够直接存取AutoCAD的图形屏幕和输入设备。利用这些函数.ObjectARX应用程序可以使AutoCAD内部的用于显示和交互的功能。
函数acedGrText()能以高亮度或低亮度方式直接在状态区或菜单区显示文本信息。也能在当前视区内画一个矢量,并可控制它的颜色和亮度状态。函数acedGrVecs()用于画多个矢量,函数acedGrRead()用于返回用户输入的“原始”状态数据,这些数据可以是从键盘敲人或用点设备(鼠标器或数字化仪)输人的。如果在函数acedGrRead()的调用过程中允许跟踪,那么函数返回可用于拖动的数字化坐标。
注意:上述函数依赖于AutoCAD的内部代码,因此,在不同的AutoCAD版本上运行这些函数可能会有所不同;也就是说,这些函数不具备向上兼客性。另外.这些函数还依赖于当前的硬件配置.特别是函数acedGrText().它们并不是在所有的配置情况下都能够正常运行。除非开发人员已经设置使其使用于硬件特性。因为这些函数是低级函数,日此它们大都没有错误提示,而且还有可能破坏图形屏幕的显示。
下面例子用来消除用户在不正确地调用函数acedGrText()、acedGrDraw()或acedGrVecs()后,对图形屏幕造成的破坏:
   acedGrText(-3, NULL,0);

acedRedraw(NULL,0);

函数acedGrText()的参数意义是-3表示恢复标准文本.NULL表示没有新文本,0表示不高亮显。而函数acedRedraw()的参数意义是:NULL表示所有实体,0表示实体视区。
7.7 通配符的匹配
函数acutWcMatch()能使应用程序将一个字符串与一个通配符进行比较。这个特性可用在当建立一个选择集(与acedSSGet联用)时和当要用应用程序检索扩展数据(与acedEntGetX()联用)时。
函数acutWcMatch()将一个单字符串与模式比较。如果串与模式匹配,那么返回RTNORM;如果不匹配,就返回RTERROR。通配符模式类似于许多系统和程序使用的标准的表达式。在模式中,字母字符和数字字符都被认为是字符。括号用于设定可选的字符或者是设定字母或数字的范围。一个问号(?)匹配一个单个的字符;一个星号(*)匹配一个字符串序列。一些特殊字符在模式中有特殊的含义。
在下面这个例子中,假设字符串变量matchme已声明并且初始化。下面的调用检查matchme的前5个字符是否为“allof”:
if (acutWcMatch(matchme,"allof*") == RTNORM) {
   ………………………………..}
  下面的调用说明括号在模式中的使用方法。在这种情况下,如果matchme等于“STR1”、“STR2”、“STR3”或“STR8”,那么acedWcMatch()函数将返回RTNORM:
if (acutWcMatch(matchme, "STR[1-38]")== RTNORM) {}
模式串可指定逗号分开的多种匹配模式。在下面的调用中,如果matchme等于“ABC”,或者以“XYZ”开头,或者以“123”结尾,那么函数都将返回RTNORM:

if (acutWcMatch(matchme, "ABC,XYZ*,*123")== RTNORM) { }

函数acutWcMatchEx()与函数acutWcMatch()类似,但它多了一个参数,允许其可以忽略大小写。
bool
acutWcMatchEx(
          const char * string,
          const char * pattern,
          bool ignoreCase);
第八章ObjectARX全局函数
在上一章我们讨论了用户与AutoCAD进行交互通信时所使用的一些全局函数,在本章讨论ObjectARX全局实用函数的一些普遍特性。对于各个函数的细节,读者可参考ObjectARX相关文档资料。
ObjectARX全局函数的普遍特性         本节我们讨论在ObjectARX库中的全局函数的一些普遍特性。大多数全局函数都是对在当前文档中运行的数据库、系统变量和选择集进行操作。
1 0bjectARX全局函数和AutoLISP的差异         许多ObjectARX全局函数是ObjectARX开发环境所独有的,但是它们中也有许多函数与AutoLISP函数有相同的功能,除了前缀(aced、acut等)不同,它们的函数名基本与相应的AutoLISP函数名相同。这种相似性使得用户能很方便地将程序从AutoLISP“移植”到ObjectARX不过,应该注意,AutoLISP解释型运行环境和c++的编译型环境却有着较大的差异。
   从AutoLISP函数和c++中的参数表方面来看,许多内嵌式AutoLISP函数能接受任意数目的参数,这是LISP环境的特点。如果我们要求ObjectARX库中与之对应的每个c++函数也能接受可变数目参数表,不但大大增加了该函数的复杂度,而且这也是不必要的。为了解决这个问题,ObjectARX库函数采取了一种变通的方法,使用了简单的规则:使用一个ObjectARX函数模拟AutoLISP函数,像AutoLISP函数使用参数那样使用参数。如果某个参数在AutoLISP中
是可选参数时面没有选用,则在对应的ObjectARX库函数中使用一个特殊的值,通常为空指针、0或者1,来指明该参数没有被使用。
    在ObjectARX库中有一些函数则没有遵从这个规则。以acutPrintf()函数来说,它反而与标准的C库printf()函数很相似。就像标准版本一样,它设计成一个不定型的函数来执行,也就是说,它能够接受可变长度的参数表。而函数acedCommand()和acedCmd()则更为复杂一些。AutoLISP中的(command)函数不但可以接受数目可变的不同类型的参数,而且它也接受专门为AutoCAD定义的类型,比如点和选择集。为了使ObjectARX中的对应函数有同样的灵活性,acedCommand()不仅设计为可阻接受可变长度的参数表,还使用了一些参数用来指定所传送的数据类型;函数acedCmd()要求一个类似的值的集合,但是要作为一个连接好的链表来传送。因此,acedCommand()和acedCmd()参数并不完全对应于AutoLISP的(command)函数。最
后,AutoLISP的(entget)函数带有一个可选择的参数用来检索扩展数据,而在ObjectARX中,对应的aedbEntGet()函数却没有这个相应的参数。不过,ObjectARX提供了一个名为acdbEntGetX()
的函数,用来完成检索扩展数据的功能。
    在对内存空间的需求方面,ObjectARX与AutoLISP有很大差异。一方面,c++程序使用的数据结构要比AutoLISP使用的表结构要紧凑的多,另一方面,运行OhjectARX应用程序却需要很大的固定开销,因为除了应用程序编码本身之外,ObjectARX目标函数库要占去较大的一部分空间。在内存管理方面,一些ObjectARX全局函数自动进行内存分配。在大多数情况下,应用程序必须明确地释放这些内存,就好像释放应用程序本身申请的内存一样。AutoLISP会自动收集无用数据所占的内存,但是ObjectARX不会。
   注意:不释放这些内存会降低系统的运行速度,并可能导致AutoCAD运行终止。
2 函数返回值与函数的结果         少数ObjectARX全局函数不返回值(实际上返回void类型的值),还有一些全局函数直接返回它们的结果,但大多数韵全局函数都返回一个int(整型)值,作为一个状态码,用来表明函数调用的成功与否。状态码RTNORM表示函数调用成功,其他的代码则表示调用失败或发生了特殊情况。返回状态码的库函数的实际结果(若有的话)是通过参数引用方式返回调用程序之中的。
   注意:不要把画数的返回值与结果参数和值相混淆。返回值一般为整数形式的状态码,而函数的结果是通过参数引用的方式返回到调用程序之中。
   请看下列典型的ObjectARX函数原型说明:

int acdbEntNext(ads_name ent, ads_name result);

int acedOsnap(ads_point pt, char *mode, ads_point
          result);
int acedGetInt(char *prompt, int *result);
   应用程序中可以通过如下方式对上述函数进行调用:
   stat = acdbEntNext(ent, entres);
stat = acedOsnap(pt, mode, ptres);

stat = acedGetInt(prompt, &intres);

   当这些函数调用完成之后,变量stat的值要么表明调用成功(stat==RTNORM),要么表明失败(stat==RTERROR或其他错误码,比如RTCAN表明取消)。每一个函数参数表中最后一个参数用来返回函数的结果,且必须以引用的方式传递。如果函数调用成功,则函数
acdbEntNext()将通过参数entres返回一个实体名,函数acedOsnap()将通过ptres返回一个点,函数acedGetint()将通过intres返回一个整型数。类型ads_name和ads_point是数组类型,这也正是为什么必须使用指针entres和ptres来隐式地返回函数结果的原因。
注意:在ObjectARX全局函数的声明中,用来返回函数结果参数总是位于输入值参数表的后面,这并不是语法上的要求,而是一种被广泛接受的习惯。
3 外部函数一旦ObjectARX应用程序定义了它的外部函数(通过调用acedDefun()函数来定义),那么这些函数就可以被AutoLISP用户、AutoLISP程序或AutoLISP函数作为它们的内部函数或用户定义的AutoLISP函数来调用。一个外部函数可以传递AutoLISP的值和变量,还能返回一个值给调用它的AutoLISP表达式。本节讲它们的一些限定条件。
当0bjectARX应用程序接收到AutoCAD的kLoadDwgMsg请求时,它必须定义其所有的外部函数,每定义一个函数就要调用一次acedDefun()函数,函数∞edDefun()需要外部函数的名字(作为一个字符串传入)和一个应用程序中唯一的整型码,整型码的范围介于0到32767之间(是一个短整型数)。

下面这个acedDefun()函数调用使AutoLISP识别一个名为(doit)外部函数,且当AutoLISP调用(doit)时,它将函数码(0)传递给ObjectARX应用程序:acedDefun("doit",0);

新的外部函数使用的名字字符串可以是AutoLISP中任意的有效符号名。AutoLISP自动将它转换为大写形式,并以一个Exsubr类型符号保存。

外部函数在MDI中的每个打开文档中是独立定义的。当文档变为活动文档时,函数就获得它的定义内容。

注意:如果两个或多个ObjectARX应用程序在同一文档中用相同的函数名定义函数。 AutoLISP只能识别最近定义的外部函数,而前面装入的相同名字定义的函数将会丢失。这种结果在用户用一个有矛盾的名称调用defun时也可发生
就像在AutoLISP中一样,新的函数可以在其名字前面加前缀“C:”或“c:”来定义一个AutoCAD命令,例如:
     acedDefun("C:DOIT", 0);
这种情况下D0IT函数可以在AutoCAD的命令:提示符下直接调用,面不用在其名字外加括号。调用时,DOIT定义为AutoCAD命令,也可以被AutoLISP的表达式调用,这时前缀“C:”要作为名字的一部分一起输入。例如,前例中定义的DOIT命令的调用:
命令: (c:doit x y)
这里,被调用函数带了两个变量。
注意:如果应用程序定义的C:**命令与内部命令或定义在acad.pgp文件中的命令名冲突,则AutoCAD不将外部函数作为命令对待。但这个函数仍然能作为AutoLISP函数外部函数调用。例如在调用acedDefun("c:cp",0)之后,因为cp在acad.pgp文件中定义为COPY的别名,则用户输入cp就只调用AutoCAD的COPY命令,但用户可以通过输入c:cp来调用外部函数。
说明:由acedDefun()定义的函数名,可以通过调用acedUndef()取消定义。定义被取消之后,就不能再对其进行调用了。
  对于已定义了的外部函数,AutoLISP就可以调kInvkSubrMsg请求码来调用该函数。当ObjectARX应用程序接收到该请求后,就通过调用acedCetFunCode()获得外部函数的整型码。然后用switch语句、if语句或一个函数选择表进行选择来调用相应的函数处理程序。这部分程序就是ObjectARX程序中为实现外部函数而定义的函数,该函数名和由acedDefun()定义的名字不一定相同。
如果函数处理程序带有参数,则要得到它们的值必须调用acedGetArgs()函数,它会返回一个指向一个结果缓冲区链表的指针值,该结果缓冲区链表包含着从AutoLISP传来的值。如果处理程序不带有参数.就不需要调用acedGetArgs()函数,也可以用该函数确认投有传入参数。因为存放参数的结果缓冲区是个链表,所以参数的长度是可变的,并且参数的类型也可不相同。
说明:函数处理程序必须对传入参数的数目与类型进行检查,因为没有现成的方法来告诉AutoLISP需要的是什么。
如果acedGetArgs()返回空的参数表,那么应该提示用户给出值。这一点对定义成AutoCAD命令的外部函数非常有用。
有许多ObjectARX函数是作为返回值式的函数来设计的。比如acetRetInt()、acedRetReal()和acedRetPoint(),它们可以给调用它的AutoLISP表达式返回一个值。
在外部函数和AutoLISP之间传递的参数必须是下列数据类型之一:整数、实数(浮点数)、字符串、点(在AutoLISP中以含有两个或三个实数的表的形式来表示)、实体名、选择集名、AutoLISP符号T和nil或者含有上述元素的表。AutoLISP符号T和nil不能传递给外部函数或从外部函数传出,但是ObjectARX应用程序可以通过调用acedGetSym()和acedPutSym()函数查询和设置AutoLISP符号。
  例如,一个ObjectARX应用程序中有一个外部函数,其参数以一个字符申、一个整数和一个实数来词用,则其AutoLISP函数形式表示为:
(doitagain pstr iarg rarg)
如果此函数已经由acedDefun()定义,那么AutoCAD用户可以用下面表达式来调用它:

   

该调用分别给字符串、整数和实数参数提供了需要的值,这些值可以由doitagain()处理程序调用acedGetArgs()得到。
4 出错处理AutoCAD环境是个的复杂的交互运行环境,这就要求ObjectARX应用程序必须是有较强的处理各种情况的能力。ObjectARX提供了一些错误处理方法,它与AutoLISP进行信息交换后的返回结果代码,能指明出错的原因,这与库函数向应用程序返回结果码的原理一样。另外要求AutoCAD对于用户的内部输入函数的输入值具有检查能力。有三个函数可以让应用程序在出错时通知用户:acdbFail()、acedAlert()和acrxAbort()。
acdbFail()函数简单地在AutoCAD命令提示符命令:下显示一个字符串表达的出错信息。这个函数一般在所产生的错误能让用户重新来改正时被调用,例如,用户输入了不正确的参数,就可以使用该函数进行提示。例如,程序test.arx中调用如下语句:
acdbFail("invaiid osnap point \n");
则acdbFail()函数显示下列内容:
test.arx应用程序错误:invaild osnap point

AppliCation te8t arx  ERROR:  invalid snap  point

我们也可以使用函数acedAlet(),它通过警告框显示关于错误情况信息。该警告框对用户来说就使错误显得更为强调一点,用户不得不点选OK按纽才能退出警告框继续运行。而对于致命的错误,就应该调用acrxAbort()函数。这个函数要求用户保存当前的操作,然后退出。这里不能使用标准c++的exit()函数。
通过检查AutoCAD系统变量ERRNO的值,就可以获得关于ObjectARX函数失败原因的详细信息。当某一个ObjectARX函数调用(或AutoLISP函数调用)引起错误时,ERRNO就被设置为一个值,可以通过acedGetVar()查得此值,ObjectARX在头文件ol_errno.h中定义了错误代码的符号名,ObjectARX应用程序能包含该头文件,对ERRNO进行检查。
5 应用程序之间的通信在一个ObjectARX应用程序中,通过ObjectARX捉供的函数acedInvoke(),我们可以调用其他ObjectARX应用程序实现了的外部函数,这个外部函数必须是当前已经加载的ObjectARX应用程序所定义的。
函数acedInvoke()使用应用程序在acedDefun()中指定给外部函数的名字来调用外部函数,AutoLISP表达式和函数也使用该名字来调用该函数。如果外部函数被定义为AutoLISP命令,
名字前加了前缀“C:”,则用accdInvoke()调用该外部函数时,函数名前必须带前缀,这与AutoIISP表达式中调用命令一样。
注意:因为同时加载的多个应用程序程序不能有相同的函数名,在开发由多个程序文件组成的应用程序时要注意这一点。在命名计划或统一性规定时就要避免这一问题,以确保每个外部函数名称是唯一的。最好的办法是用注册开发者符号(RDS)作前缀。



















第九章选择集       在这一章中,我们讲述对选择集、实体和符号表函数操作的全局函数。上一章中我们介绍了ObjectARX有关的全局函数的通性,从这一章开始我们开始陆续学习这些全局函数的详细使用方式。
9.1 选择集和实体名大多数ObjectARX函数在处理选择集和实体时,都用名字来识别选择集或实体。该名字是用一个长整型对来表示的,并由AutoCAD来维护。在ObjectARX中,该名字的类型为ads_name.
注意:选择集和实体的名字是不稳定的,它们仅仅在AutoCAD当前图形工作时有效。如果从AutoCAD退出或切换到另一个图形时,其值就会丢失。
     在对选择集或实体进行操作之前,ObjectARX应用程序必须通过调用一个能返回其名字的库函数来得到选择集或实体名字。对于选择集来说,它也是与当前图有关联的,所以其名字的不稳定性不会影响选择集。但是对于实体就不是这样了,因为它是被存放在图形数据库中的,名字的不稳定性要影响到对实体的操作。应用程序必须在下一次对同一图文件中的同一实体进行操作时,可以使用实体句柄,重新获取其实体名。这一点,详见后面的“实体句柄及其用法”。
9.2 选择集的处理ObjectARX函数对选择集的处理类似于AutoLISP。acedSSGet()函数提供了大多数创建选择集方法。它一般通过以下三种方法之一创建选择集:
    (l)提示让用户选择对象。
    (2)像交互式应用AutoCAD -样,利用PICKIIRST定义、Crossing,Crossing Polygon、Fence、Last、Previous、Windows、或者Window Polygon等匹配条件的方式来选择实体对象,也可以通过指定一个单独点或Fence点来选择。
    (3)使用一系列属性和选定条件筛选当前图数据库来选择实体对象,我们可以使用前面提到的任何匹配条件。
    该函数原型为:
    int acedSSGet(const ACHAR *str, const void *pt1, const void *pt2, const struct resbuf *filter,ads_name ss);
acedSSGet()的第一个参数str,说明所使用的选择条件,紧跟着的两个参数用于指定与某些选择方式有关的可选择的点。当不使用它们时,应该取NULL值。如果第四个参数entmask不是NULL,则它指向一个结果缓冲区表,用于存放用筛选选择方式的结果。第五参数ss是选择集的识别名字。
  表示码
  
     
  
  NULL
  
  单点选择(如果指定了pt1)或用户选择(如果pt1也为NULL)
  
  #
  
  非几何选择(all last previous)
  
  :$
  
  提供提示文字
  
  .
  
  用户拾取方式
  
  :?
  
  其他回调函数
  
  A
  
  All选择方式
  
  B
  
  Box选择方式
  
  C
  
  Crossing选择方式
  
  CP
  
  Crossing Polygon 选择
  
  :D
  
  可以重复,即可以重复选择一个实体,并都添加到选择集中
  
  :E
  
  aperture中的所有实体
  
  F
  
  栏选
  
  G
  
  编组选择
  
  I
  
  如果存在PICKFIRST集,则用这个选择集
  
  :K
  
  关键字回调函数
  
  L
  
  选择最后一个选择集
  
  M
  
  多重选择方式
  
  P
  
  选择上一个选择集
  
  :S
  
  强制单个实体对象被选择
  
  W
  
  Winodws选择方式
  
  WP
  
  Window Polygon选择方式
  
  X
  
  用于筛选程序搜索整个图形数据库
  
下列是调用aeedSSGet()的一段例子,对于多边形选项“CP”和“WP”(不包括“F”),像函数acutBuildList()说明的哪样,函数aeedSSGet()自动封闭点的列表.所以我们不必建立一个终点与起点相同的表。
ads_point pt1,pt2, pt3,pt4;
struct resbuf*pointlist;
ads_name ssname;
pt1[X] =pt1[Y] = pt1[Z] = 0.0;
pt2[X] =pt2[Y] =5.0; pt2[Z]= 0.0;
// 如果选择集存在,获取当前PICKFIRST选择集,没有提示用户进行选择
acedSSGet(NULL,NULL, NULL,NULL, ssname);
// 如果选择集存在,获取当前PICKFIRST选择集
acedSSGet("I",NULL, NULL,NULL, ssname);
// 选择最近使用过的选择集
acedSSGet("P",NULL, NULL,NULL, ssname);
// 选择最后加入到数据库的对象
acedSSGet("L",NULL, NULL,NULL, ssname);
// 选择通过点(5,5)的实体
acedSSGet(NULL,pt2, NULL,NULL, ssname);
// 选择在从点(0,0)到点(5,5)的窗口中的实体
acedSSGet("W",pt1, pt2,NULL, ssname);
// 选择指定多边形包围的实体
pt3[X] =10.0; pt3[Y]= 5.0; pt3[Z]= 0.0;
pt4[X] =5.0; pt4[Y]= pt4[Z]= 0.0;
pointlist = acutBuildList(RTPOINT, pt1, RTPOINT, pt2,
                   RTPOINT, pt3, RTPOINT, pt4, 0);
acedSSGet("WP",pointlist, NULL,NULL, ssname);
// 选择从点(0,0)到点(5,5)窗口内相交的实体
acedSSGet("C",pt1, pt2,NULL, ssname);
// 选择指定多边形内交的实体
acedSSGet("CP",pointlist, NULL,NULL, ssname);
acutRelRb(pointlist);
// 选择与指定栏选交叉的实体
pt4[Y] =15.0; pt4[Z]= 0.0;
pointlist = acutBuildList(RTPOINT, pt1, RTPOINT, pt2,
                   RTPOINT, pt3, RTPOINT, pt4, 0);
acedSSGet("F",pointlist, NULL,NULL, ssname);
acutRelRb(pointlist);
对acedSSGet()的补充函数是acedSSFree(),它能在应用程序用完选择集后释放选择集。选择集是按名字来被使用的。对上面程序段中定义的ads_name,在这里这样使用:

   acedSSFree(ssname);

注意:AutoCAD不能同时打开多于128个选择集,这包括运行中的ObjectARX和AutoLISP应用程序所打开的选择集的总合。该限制在不同的系统中可能有所不同。如果这一限制被突破,AutoCAD会拒绝创建更多选择集。我们不推荐同时管理的大量选择集。相反应该在任何时刻仅仅打开并保持适当的最小数量的选择集。并且在不用选择集时采时应尽快地用acedSSFree()释放。与AutoLISP不一样,ObjectARX环境不能对使用过的选择集所形成的那些零碎的无用的信息单元进行自动收集,以释放这些选择集。应用程序应该记着在收到kUnloadDwgMsg、kEndMsg或kQuitMsg消息时,释放它所打开的选择集。
9.2.1 选择集筛选表当acedSSGet()函数的entmask参数明确记录了实体的范围值列表时,acedSSGet()扫描被选择的实体,同时建立一个包含主实体名的选择集,这些实体与筛选条件相匹配。比如,使用这种方法,用户可以得到一个给定的选择集,这个选择集包括给定层、给定类型或给定颜色的所有实体。
筛选表可以与任何的选择项联合使用。如果用“X”选择方式,意味着建立一个仅使用筛选表的选择集。在AutoCAD以前的版本中,如果用“X”选项,acedSSGet()将扫描全部图形数据库。
参数entmask必须是一个结果缓冲区表。每一个缓冲区指定一个检查参数和匹配的值;缓冲区的restype段是一个DXF组码,它指示要查询的参数的种类,而缓冲区的reaval域指定要匹配的值。下面是相应的例子:
struct resbufeb1, eb2,eb3;
char sbuf1[10],sbuf2[10];
ads_name ssname1,ssname2;
eb1.restype= 0; // 实体名称
strcpy(sbuf1,"CIRCLE");
eb1.resval.rstring = sbuf1;
eb1.rbnext= NULL; // 没有其他属性内容
//Retrieve all circles.
acedSSGet("X",NULL, NULL,&eb1, ssname1);
eb2.restype= 8; // 层名
strcpy(sbuf2,"FLOOR3");
eb2.resval.rstring = sbuf2;
eb2.rbnext= NULL; // 没有其他属性内容
// 检索在FLOOR3上的所有实体.

acedSSGet("X",NULL, NULL,&eb2, ssname2);

注意:在每个缓冲区中指定的resval必须属于合适的类型,比如:名称类型是字符串(resval.rstring);标高和厚度是双精度浮点型(resval.rreal);颜色、属性和标识值是短整型(resval.rint),拉伸向量是三维的点(resval.rpoint),等等。
如果entmask指定了多个参数,那么只有匹配所有指定条件的实体才能被包含在选择集里。就像下面的例子:
eb3.restype= 62; // 实体颜色
eb3.resval.rint = 1; // 红色
eb3.rbnext= NULL;
eb1.rbnext= &eb2; // 添加个条件
eb2.rbnext= &eb3; // 建立链表
// 检索在FLOOR3层上所有红色圆实体

acedSSGet("X",NULL, NULL,&eb1, ssname1);

除非表中包括关系和条件操作符,否则实体的所有域都要被测试。
如果在数据库中没有实体与指定的筛选条件相匹配,函数acedSSGet()将返回RTERROR。
前面关于acedSSGet()的例子用的是“X”选项,它扫描整个图形数据库;如果筛选表与其他选项(如用户选择、窗口选择,等等)联合使用,筛选条件只能在被选中的实体上起作用。请看
下面一组例子----筛选用户选择的实体:
eb1.restype = 0; // 实体类型组strcpy(sbuf1, "TEXT"); eb1.resval.rstring = sbuf1; // 类型为文本eb1.rbnext = NULL; // 让用户生成选择集,但该集合中只能有TEXT实体acedSSGet(NULL, NULL, NULL, &eb1, ssname1); 筛选前一个选择集:
eb1.restype = 0; // 实体组类型strcpy(sbuf1, "LINE"); eb1.resval.rstring = sbuf1; // 实体类型为直线eb1.rbnext = NULL; //选择在上一个选择集中符合条件的实体acedSSGet("P", NULL, NULL, &eb1, ssname1); 在窗口内筛选实体:
eb1.restype = 8; // 图层strcpy(sbuf1, "FLOOR9"); eb1.resval.rstring = sbuf1; // 层名eb1.rbnext = NULL; // 选择在窗口内并在FLOOR9层上的所有实体acedSSGet("W", pt1, pt2, &eb1, ssname1); 注意: 某些码在不同的实体里有不同的含义,并且不是所有的组码都存在于所有的实律内。如果在筛选程序中指定一个特定的组码,不包含该组码的实体将被排除在acedSSGet()所返回的选择集之外。
9.2.2 选择集的管理一旦建立了选择集,就可以调用函数acedSSAdd()和acedSSDel()来将实体放进选择集或从选择集中删去,这就类似于当AutoCAD提示用户选择对象:或删除对象:时的Add和Rmove选项一样。
注意:如下面的例子所示,函数acedSSAdd()也可以用于建立一个新的选择集。只有其返回RTNORM时acedSSGet()和acedSSAdd()才建立一个新集。
下面的程序建立了一个包括当前图形的第一个和最后一个实体的选择集:
ads_name fname,lname; // 实体名
ads_name ourset;// 选择集名
// 获取图形中第一个实体
if (acdbEntNext(NULL, fname) !=RTNORM) {
   acdbFail("No entities in drawing\n");
   return BAD;
}
// 创建一个包含第一个实体的选择集
if (acedSSAdd(fname, NULL, ourset) != RTNORM){
   acdbFail("Unable to create selection set\n");
   return BAD;
}
// 获取图形中最后一个实体
if (acdbEntLast(lname) != RTNORM){
   acdbFail("No entities in drawing\n");
   return BAD;
}
// 将最后一个实体添加到同一个选择集中
if (acedSSAdd(lname, ourset, ourset) != RTNORM){
   acdbFail("Unable to add entity to selection set\n");
   return BAD;
即使在数据库中只有一个实体,上面的程序也能正确运行(在这种情况下,函数acdbEntNext()和acdbEntLast0都把它们的参数设为该实体名)。如果将一个已在选择集中的实体的名字传递给函数acedSSAdd(),该函数就忽略选择实体请求,而且也不会报告错误信息。
上面的程序还表明,函数acedSSAdd()的第二个和第三个参数可以以同一个选择集的名字来传递:如果调用成功,由两个参数命名的这个选择集在函数acedSSAdd()返回后,就会包含一个附加的成员(除非指定的实体己存在于该选择集中)。
下面调用将删除在前一个实例中用于建立选择集的实体:
acedSSDel(fname,ourset);
如果图中不只有一个实体(即fname和lname不相等),选择集outset在只包含图中的最后一个实体lname。
函数aeedSSLength()返回在选择集中的实体的个数,函数acedSSMemb()检测某个特殊的实体是否是一个选择集中的一员。最后,函数acedSSName()返回一个选择集包含的某个特定实体的名字,即该选择集的索引号(选择集的实体从零开始编号)。
注意:因为选择集可大可小,由acedSSLength()返回的参数len和在对acedSSName()的调用中用做索引号的参数i必须都被说明为长整数(标准C编译程序会正确地转换一个简单整形数)。
下面的程序列举几个对acedSSName()调用:
ads_name sset,ent1, ent4,lastent;
long ilast;
// 通过提示用户创建一个选择集
acedSSGet(NULL,NULL, NULL,NULL, sset);
// 获取选择集中的第一个实体
if (acedSSName(sset, 0L, ent1)!= RTNORM)
return BAD;
// 获取在选择集中的第四个实体
if (acedSSName(sset, 3L, ent4)!= RTNORM) {
   acdbFail("Need to select at least four entities\n");
   return BAD;
}
// 查找在sset中最后一个实体的索引号
if (acedSSLength(sset, &ilast)!= RTNORM)
return BAD;
// 获取最后一个实体的名称
if (acedSSName(sset, ilast-1, lastent) != RTNORM)
return BAD;








第十章在ObjectARX中使用MFC本章是介绍在ObjectARX应用程序中如何使用MFC进行界面开发,只是对在ObjectARX应用程序中涉及到的有关MFC类进行探讨,因为本书篇幅有限没有对实例进行讲解,对实例将在本丛书的第二卷里进行介绍。
Microsoft基础类库(MFC)给软件开发者提供了一种便捷快速建立标准的用户界面的方法。ObjectARX开发环境也提供一系列基于MFC的类,使用这些类开发的用户界面与AutoCAD用户界面是一致的。
10.1 概述利用MFC类库的优势完全能够开发OhjectARX应用程序。在这一章我们来讨论说明如何利用MFC类库创建一个ObjectARX应用程序,以及如何使用AutoCAD内部的MFC系统来生成AutoCAD风格的对话框。
10.2 使用MFC建立ObjectARX应用程序在建立一个ObjectARX应用程序的时候,我们可以选择动态或静态链接MFC库的方式,对于动态方式,我们可以选择常规的DLL方式或扩展的DLL方式。
需要说明的是,推荐开发者使用动态链接MFC ObjectARX应用程序并将其做成扩展DLL,因为只有这样,才能使用基于Autodesk的AdUi和AcUi。
有关MFC其他较为完善的内容,请读者参考相关资料书籍,限于篇幅,本书不作介绍。但是大家有必要学习使用MFC基本类来建立DLL的有关知识,因为这是ObjectARX应用程序的一个重要概念。  对于新入手的朋友,我建议采用向导的方式来建立支持MFC扩展的应用程序。
10.3 MFC和非模态对话框由于在AutoCAD中,输入焦点总是试图离开它的子窗口,这对于非模态的对话框就提出了特殊的要求。非模态的对话框将定期获得WM_ACAD_KEEPFOCUS WINDOWS消息。对话框获此消息后,如果它将保持焦点,则它必须返回TRUE;如果返回FALSE消息(缺省值),则只要用户移动鼠标器指针离开对话框,对话框就会失去输入焦点。
BEGIN_MESSAGE_MAP(HelloDlg, CDialog)    ON_COMMAND(IDCLOSE, OnClose)    ON_COMMAND(IDC_DRAW_CIRCLE, OnDrawCircle)    ON_MESSAGE(WM_ACAD_KEEPFOCUS, onAcadKeepFocus)END_MESSAGE_MAP()该例子中应用程序对话框类为HelloDlg,它是从CDialog类派生出来的。每个消息都与一个函数相对应的,假定我们已写出这样一个函数keepTheFocus(),如果想让对话框保持输入焦点,则返回TRUE,否则返回FALSE,相应的消息函数就可以写为:
afx_msg LONG HelloDlg::onAcadKeepFocus(UINT, LONG){    return keepTheFocus() ? TRUE : FALSE;}10.4 使用动态链接MFC的ObjectARX应用程序前面已经提到,我们首选使用动态链接MFC类库来建立基于MFC的OhjectARX应用程序。下面介绍的是建立一个标准的MFC扩展DLL的方式来建立应用程序,我们当然也可以选用ObjectARX向导来建立这个应用程序。
10.4.1动态链接MFC的VC++工程选项设置以ObjectARX 2015为例,我们选用的VC++版本是VC++2012,下面按以下步骤来建立一个动态链接MFC库的ObjectARX应用程序:
(1)选择MFC AppWizard(DLL)选项建立一个工程。

(2)选择MFC扩展DLL选项。
(3)点击完成,在创建的工程的CPP文件中添加acrxEntryPoint函数。其余步骤请参阅前面的创建工程。
10.4.2资源管理要建立一个ObjectARX应用程序与AutoCAD和其他应用程序共享MFC库,那么资源管理是一个很重要的考虑因素。我们必须在执行诸如定位资源这样的操作时,用CdynaLinkLibary给MFC检查链中插入模块状态。需要特别强调的是,无论如何,我们要显式地管理程序中的资源,以免与AutoCAD或其他ObjectARX应用程序的资源发生冲突。
10.4.3显示地设置资源的方法(l)在任何能引起MFC去操作自定义的资源之前,通过调用函数AfxSetResourceHandle()将该资源设置成系统缺省资源。
(2)在设置系统资源到自定义资源之前,通过调用函数AfxGetResourceHandle()获得当前的系统资源。
(3)在任何一个调用了使用自定义资源的函数之后,应立即把系统资源复位设置为其操作前的资源。
在一个对话命令操作中,调用诸如acedGetFileD()的AutoCAD API函数时,如果要用到AutoCAD的资源,应该在调用函数之前,把资源设置成AutoCAD的,调用之后再恢复应用程序的资源。在这之中要使用acedGetAcadResourceInstance()获得AutoCAD的资源的句柄。
10.4.4 CAcExtensionModuleObjectARX SDK提供了两个简单的c++类来简化资源的管理。其中CAcExtensionModule类提供两个用途:
(1)给AFX_EXTENSION_MODULE结构提供一个占位符,这个结构通常用来初始化或终止一个扩展DLL。
(2)为DLL引出两个资源的提供方式:一个是标准资源,一个是缺省资源。标准资源一般是DLL本身,当然也可以设置成其他一些模块。缺省资源通常是调用该DLL的应用程序,但事实上是在AttachInstance()调用时已经被激活。CAcExtensionModule引出这些以简化在MFC标准资源和缺省资源之间的切换。一个DLL程序应该创建这个类的实例,使该类的这些功能得以实现。
10.4.5 CAcModuleResourceOverride 我们可以使用该类的实例在资源捉供者之间进行切换。当构造对象时,就会引入新的资源提供者。对象析构时,就会恢复原来的资源提供者。
void MyFunc (){    CAcModuleResourceOverride myResources;}进入这个函数后,应用程序就选择标准的资源,从该函数返回,应用程序又恢复缺省资源。
我们能使用以下任何三种方法之一实现资源的重载:
(1)使用缺省的构造函数选择标准资源,而析构函数又恢复缺省资源。这里标准资源和缺省资源就是由DLL中的CAcExtensionModule维护的资源。
(2)传送一个空值(null)给构造函数,则会使DLL中的资源被选定,而实际上,当前工作中的资源在析构重载时恢复。
  (3)传送一个非空句柄给构造函数,则会使相关标准资源被选中,而实际上,当前工作中的资源在析构重载时恢复。
ObjectARX为我们提供了两个宏,用在应用程序中声明并实现这些类。这两个宏为:AC_DECLARE_EXTENSION_MODULE和AC_IMPLEMENT_EXTENSION_MODULE。
下面的例子说明了在应用程序中使用CAcExtensionModule和CAcModuleResourceOverride类:
AC_IMPLEMENT_EXTENSION_MODULE(theArxDLL);HINSTANCE _hdllInstance = NULL;extern "C" int APIENTRYDllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved){    // Remove this if you use lpReserved    UNREFERENCED_PARAMETER(lpReserved);    if (dwReason == DLL_PROCESS_ATTACH)    {        theArxDLL.AttachInstance(hInstance);        hdllInstance = hInstance;    }    else if (dwReason == DLL_PROCESS_DETACH)    {        theArxDLL.DetachInstance();      }    return 1;   // ok}10.5 MFC内置用户界面的使用ObjectARX有一系列MFC用户界面与一些类有关。这些类使得我们很容易地开发一致的用户界面。所谓一致是与AutoCAD风格的界面一致。作者推荐使用这些类,因为这样使得我们所开发的应用程序能与AutoCAD风格界面很好地融合在一起。Autodesk公司的MFC系统
分为两个库,第一个是叫做AdUi.它并不是AutoCAD所特有的。第二个叫做AcUi,它包含AutoCAD特有的外观和行为。
AdUi是一个MFC扩展动态链接库,它用来扩充一些与用户界面有关的MFC类库。该库包含一些Autodesk产品的核心功能。而另一个AcUi是基于AdUi结构框架建立的.用来实现AutoCAD风格的。之所以能允许ARX开发者使用与AutoCAD甩户界面相一致的用户界面,是因为AdUi和AcUi库扩充了MFC的功能。开发者可以直接使用这些类,我们后面将列出这两个类库的主要功能。
要说明的是,一旦要使用AdUi类库,那么在我们的应用程序的c++源文件中必须包含adui.h头文件,并且链接库中应该有adui20.lib。同样,要使用AcUi类库,那么在我们应用程序的c++的源文件中必须同时包含adui.h和acui.h头文件,并且链接库中应该同时有acui20.lib和adui20.lib。AutoCAD会自动请求例程InitAcUiDLL()通过InitAdUiDLL()来实现AdUi的初始化。因此开发者的应用程序不需要对AcUi或AdUi再进行初始化。
AdUi和AcUi所提供的类能实现下列功能:
对话框尺寸的变化。
对话框数据记忆功能。
标签式对话框。
可展开的标签式对话框。
上下文相关的帮助和F1帮助。
对话框与AutoCAD的图形之间的通信。
易使用的位图按钮。
静态位图按钮。
能拖动换位的位图按钮。
工具条式位图按钮。
易使用的自绘按钮。
对话框和控件对标准工具提示的支持。
对话框和控件对标准文本提示的支持。
对话框和控件对标准图案提示的支持。
自定义消息,包括对数据合法性的检查。
组合框中允许对AutoCAD特殊项进行多选并显示。
在AutaCAD中实现自动定位式控翩条。
AutoCAD风格的位图按钮。
能处理AutoCAD风格数据的合法性检查的特殊编辑控件。
10.5.1 AdUi提示签窗口AdUi提供了三种类型的提示签窗口:工具提示,文本提示和图案提示。其中,工具提示是指Windows内部的工具提示签,就像一个通常由安装在我们用户系统上的DLL提供的提示签一样。文本提示是指那些在控件上弹出的基于文本的提示窗口,有时它放不下显示的文字,需要使用者翻滚窗口才能看见。图案提示签是文本提示签的一种扩展,这往往在那些随提示内容的刷新而有所反应的控件上使用(类似一个自绘提示签)。
大多数应用程序很少直接地使用这些类。因为AdUi类通常控制所有的请求,它利用自身内部的消息传送机制,来确定容器与控件之间何时以及如何显示一个提示签。
10.5.2 CAduiTipWindow      CAdUiTipWindow类是基本的AdUi提示窗口类。这些类的对象处理一般的提示显示,并且能在合适的时候自动隐藏掉(比如,检测到光标的移动,暂停时间到或键盘活动等等)。
10.5.3 CAdUiDrawTipTextAdUi消息传送系统内部使用CAdUiDrawTipText类,来操纵一个提示窗口需要刷新的控件。这种控件能够改变提示窗口的属性,并且写出文字。
10.5.4 CAdUiBaseDialog CAdUiBaseDialog提供对提示窗口基本的支持和AdUi信息的处理功能。它在对话框中也支持上下文帮助和F1帮助。它是那些基于公用文件对话框之外的所有对话框的公用基础类。
10.5.5 CAdUiFileDialog就像CAdUiBaseDialo专门化了CDialog -样。CAdUiFileDialog专门化了CFileDialog。该类提供了对提示窗口的基本支持、上下文相关帮助,以及在一个公用文件对话框中对AdUi信息的处理。不像CAdUiBaseDiahg,它没有对位置和尺寸大小记忆的内部支持。
10.5.6 CAcUiDialog   CAcUiDialog类是一个提供了只能在AutoCAD中才能起作用的一系列对话框尺寸变化和数据记忆的成员函数的通用类。
10.6  AcUiMRU组合框   AcUi扩充了组合框的性能,使其在内部提供了对MRU(most recently used最近使用风格)列表框的自动支持。这种基本功能是由CAcUiComboBox类派生出来的CAcUiMRUComboBox类来提供的。与该类相伴随的一个类是CAcUiMRUListBox,它对组合框提供图案提示支持。之所以这样,是由于MRU组台框控件是作为一个自绘控件来处理的。
另外还提供了五个专门的MRU组合框类它们提供标准的用户接口来管理对标注箭头、颜色、线宽的选择,以及对出图式样表和出图式样名的选择。
10.6.1 CAcUiMRUComboBoxCAcUiMRUComboBox类继承CAcUiComboBox而成为实现带MRU表格列表的自绘控件的基础类。每个在列表中的条目项都能包含一些文本跟随在小小的图像之后,并且每个条目项还能与一个唯一值相联系,并把其当作一个标准的Windows ITEMDATA来维护。这种在类内部支持的特性,通常依赖的是两个选项,选项一和选项二,一般分别与“ByLayer”和“ByBlock”对应,并具有特殊的重要意义。在列表框被拉下时,其他两个条目项就会生效并显示出来,选择其中一个会相应地激活某个在控件中的事件。
10.6.2 CAcUiArrowHeadComboBoxCAcUiArrowHeadComboBox类是针对标注线箭头的选择对CAcUiMRUComboBox类进行了特殊化处理。该控件将AutoCAD当前的标注箭头形式图像显示出来,而且能将其记在选择列表中。缺省情况下,没有使用或加进什么选项或附加项。每项所对应的搭载值是与列表中的箭头所对应的AutoCAD索引值相联系的。如果加入了一个MRU项,则该类会自动地给其指定一个措载值(该值将大于AutoCAD自定义箭头形式的索引值)。
10.6.3 CAcUiColorComboBoxCAcUiColorComboBox类是针对颜色的选择,对CAcUiMRUComboBox类进行了特殊化处理。该控件显示从AutoCAD的调色板中选择的颜色的样品。颜色编号1到7的显示项总在控件列表中。在这里,两个选项均被使用,其中选项一显示“ByLayer”,选项=显示“ByBlock”。MRU项显示“Color nnn”,其中一是颜色编号。每一个项目都与AutoCAD颜色编号(1到255)相联系,而“ByBlock”代表颜色编号。“ByLayer”相当于颜色编号256。其它一项被用来启动AutoCAD颜色选择对话框。
因篇幅有限,有关MFC组合框的介绍本卷书就介绍到这里了,有关ObjectARX中使用MFC的实例请参见本书第二卷。






第十一章几何类 本章我们将要学习几何类库(AcGe)的主要用法了,这个类库为二维和三维几何运算提供了很多类。这个库可以被Autodesk公司的所有应用程序使用,并经常在ObjectARX开发环境的AcDb和AcGi库中使用。
下一章介绍了有关几何类库的实例,通过实例,希望能够提高大家的技术水平。
11.1 AcGe库概述AcGe库为我们提供了广泛的用于一般几何表示的类,像点、线、曲线、曲面等。这些对象可以被Autodesk公司所有的应用程序使用。该类库是个纯数学库,它的类不能直接处理数据库和图形。但AcDb和AcGi库能使用它的许多类完成计算工作。

有关AcGe类库的结构请参看ObjectARXSDK的classmap.dwg文件。AcGe库提供了简单几何类又提供了复杂几何类。简单的线性代数类包括点、矢量、矩阵、二维和三维线性实体类及平面实体类。而复杂的类包括曲线类和曲面类。

根据类图我们可以看到此类库把二维和三维几何类分为两类。明确地将二维参数空间几何结构和三维模型空间几何结构分开,简化了程序设计工作。由于这样区分,我们就要注意在进行相同操作时不能混淆二维和三维几何结构。
本类库中包含了许多基本类型,比如AcGePoint3d、AcGeVector3d和AcGeMatrix3d,并含有为快速高效的存取而提供的公用数据成员。这些简单类一般由其他类及其从AcGeEntity2d和AcGeEntity3d类派生的AcGe类来使用。
ObjectARX为所有从AcGeEntity2d和AcGeEntity3d类派生出的类提供了运行时的类型检验函数。即每个类用type()函数返回对象的类、用isKindOf()函数返回对象是否是特定的类的对象(或特定类的派生类的对象)。
如果两个实体类型相同并代表相同的点集,则认为它们是相等的。曲线和曲面仅仅当它们的参数相同时,才认为它们是相等的。
许多函数都有自己的允许误差值。该值是一个AcGeTol类的值,并在AcGeContext::gTol中定义了缺省值。例如,函数isClosed()和isPlaner()就能在返回布尔值之前计算七点和终点之间是否在允许范围值之内。我们可以对某个特定函数定义它的允许误差,也可以改变全局公用的允许误差。
AcGeTol类为设置点或矢量的允许误差提供了两个函数:
void setEqualPoint (double);
void setEqualVector(double);
AcGeTol类也为获取点或矢量的允许误差提供了两个函数:
double equalPoint() const;
double equalVector() const;
equalPoint和equalVector允许误差的使用规则如下:
1 两点相等的条件
(p1 – p2).length () <= equalPoint

2 矢量相等的条件:
(v1 – v2).length () <= equalVector
3 矢量平行的条件:
(v1 / v1.length() – v2/v2.length()).length() < equalVector
或(v1 / v1.length() +v2/v2.length()).length() < equalVector
4 矢量正交的条件:
abs((v1.dotProduct(v2))/(v1.length() *v2.length())) <= equalVector
两条直线或射线平行(正交)的条件是它们的所对应的矢量平行(正交).
两条直线相等的条件是它们的参数0的点相等,丙炔直线的方向一致。

11.2基本几何计算类型下面是一些用于点、矢量和矩阵类的常用函数和操作符的例子。下面的例子中都使用的是三维类函数,大多数同样也适用于二维类。
点和矢量缺省的构造函数把其所有的坐标点都初始化为0,也可以用下面的方式给定它们的初始值:
AcGePoint3d    p1 (0.0,1.0,2.0),p2,p3(1.0,2.0,3.0);
AcGeVector3d   v1 (3.0,4.0,5.0),v2(0,0,1.0,-1.0),v3;
点和矢量类提供了 +、+=、-和-=等操作符。使用这些操作符对点和矢量的操作基本与对双精度和证书这样的内部数据类型的操作方法是一样的。下面的例子对点和矢量进行加减运算:
p2 = p1 + v1;   // 点p2等于点p1加上矢量v1
p1 += v1;         //将矢量v1加到点p1上
p3 -= v1;         //从点p3上减去矢量v1
v3 = v1 + v2;  //矢量v3等于矢量v1 + v2
v1 +=v2;        //将矢量v2加到矢量v1上
v3 = v1 -v2;   //矢量v3等于矢量v1和v2的差
两个点之间不存在+操作符;但是可以先将点转换成矢量再进行加运算:
p1 += p2.asVector();
下例说明如何获得矢量的相反值:
v2 = -v1;
或者:
v1.negate();
下面是几种对矢量进行缩放的例子:
v1 *= 2.0; //使矢量v1的长度加倍
v3 = v1/2.0; //使矢量v3等于矢量v1长度的一半
v1->normalize(); //将矢量v1设为一个单位矢量
在点和矢量类中还包含了一些查询函数,用来计算距离和长度:
double len = v2.length();  //矢量v2的长度
len = p1.distanceTo(p2);  //点p1到p2的距离
下面这个函数在计算两个三维矢量之间的夹角时很有用。下面的这个式子返回矢量v1和v2之间的夹角,角度相对于矢量v3是逆时针方向旋转的(前提是v3是与v1和v2正交的):
angle = v1.angleTo(v2,v3);
下面的函数返回布尔类型的值(TRUE或FALSE),来确定某种状态或说明某种情况。它们一般是在判断语句内使用:
if(v1.isZeroLength())
if(v1.isParallelTo(v2))
if(v1.isPerpendicularTo(v2))
矢量类中还包含了一些进行常规矢量运算的函数:
len = v1.dotProduct(v2);
v3 = v1.crossProduct(v2);
矩阵的缺省构造函数会把矩阵初始化为单位矩阵:
AcGeMatrix3d mat1,mat2,mat3;
下面这两个语句把p3绕由p1和v1所确定的直线旋转度:
mat1.setToRotation(3.1415926 /2.0,v1,p1);
p3 = mat1   * p2;
对一个非奇异矩阵进行求逆:
if(!mat2.isSingular())
mat2.invert();
可以对矩阵进行叉乘:
mat3 = mat1 * mat2;
下面这个语句用来测试矩阵在三个坐标方向的缩放比例是否一致,以保证该矩阵应用之后不使
实体的形状变形:
if(mat.isUniScaledOrtho())...
11.3直线和平面类下面是一些是经常要用到的直线和平面类函数操作的例子,演示了如何用直线的平面类的函数进行一些基本的线性代数操作。虽然也使用了一些三维类,但大多数不涉及平面类的函数也同样适用的于二维类。对于无限长直线和无限大平面类的使用方式与线段、射线和带边界的面的使用方式是一样的。
直线的缺省构造函数所生成的直线在X坐标轴下,平面的缺省构造函数所生成的平面在XY平面上:
AcGePoint3d  p1(0.0,1.0,2.0),p2;
AcGeLine3d   line1(p1,v1),line2;
AcGePlane    plane1(p1,v1),plane2;
其中,直线line1沿v1方向通过点p1,平面plane1通过点p1且发现方向为v1,所以直线line1垂直于平面plane1。
下面这些函数返回直线或平面的一些定义数据:
p1 = line1.pointOnLine();  // 返回在直线上的任意一点
v1 = line1.direction();       //返回直线的方向矢量
p1 = plane1.pointOnPlane(); //返回平面上的任意一点
v1 = plane1.normal();           //返回平面的法线矢量
函数direction()和normal()返回的是单位矢量。下面这两个函数分别返回的是直线上最靠近p1的点:
p2 = line1.closestPointTo(p1);
p2 = plane1.closestPointTo(p1);
下面例子中的这两个函数分别返回的是点到直线和点到平面之间的距离,该距离值实际上就是上面p1点和p2点之间的距离:
double len = len1.distanceTo(p1);
len = plane1.distanceTo(p1);
下面这几个if语句中所使用的函数返回布尔型值(TRUE和FALSE)。第一个测试点p1是否在直线line1上,第二个测试点p1是否在平面plane1上,第三个测试直线line1是否在平面plane1内:
if(line1.isOn(p1))
if(plane1.isOn(p1))
if(line1.isOn(plane1))
下面这几个if语句中的函数测试直线或平面是否平行、正交或重合:
if(line1.isParallelTo(line2))
if(line1.isParallelTo(plane1))
if(line1.isPerpendicularTo(line2))
if(line1.isPerpendicularTo(plane1))
if(line1.isColinearTo(line2))
if(plane1.isParallelTo(plane2))
if(plane1.isPerpendicularTo(plane2))
if(plane1.isCoplanarTo(plane2))
下面三个例子中的函数分别返回直线与直线的交点、直线与平面的交点以及平面与平面的交线:
if(line1.intersectWith(line2,p1))
if(line1.intersectWith(plane1,p1))
if(plane1.intersectWith(plane2,line1))
11.4参数几何结构本节我们来继续讨论一些与参数几何结构有关的实体
11.4.1曲线曲线和曲面在AcGe库中是用参数函数来描述的。实际上所谓曲线就是用带一个自变量的计算函数将实线从相应区间上映射到二维或三维模型空间中,比如f(u)。同样,曲面就是用带两个自变量的计算函数将二维面映射到三维模型空间,如f(u,v)。每个二维和三维曲线类的对象都有一个getInterval()函数来返回区间参数。这个函数有两种形式:一种返回区间值;另一种返回曲线的区间和起终点。如果区间在某个方向是无限的,则该端点所对应的值曲线具有下列一些特性即:方向、周期、闭合性、平面性、长度。
曲线的方向是由其参数增大的趋势来确定的。使用函数AcGeCurve2d::reverseParam()或AcGeCurve3d::reverseParam()来使曲线的方向变为反向。有些曲线具有一定的周期,也就是说在一定的间隔后会重复一次。我们常见的周期性曲线就是圆,它的周期为2π。用下面的两个函数我们可以检测一个曲线是否具有周期性:
Adesk::BooleanAcGeCurve2d::isPeriodic(double& period)const;
Adesk::BooleanAcGeCurve3d::isPeriodic(double& period)const;
使用下面两个函数可以确定曲线是否封闭:
Adesk::Boolean
AcGeCurve2d::isClosed(constAcGeTol&=AcGeContext::gTol)const;
Adesk::Boolean
AcGeCurve3d::isClosed(constAcGeTol&=AcGeContext::gTol)const;
下面这个函数可以确定曲线上的所有点是否位于同一平面上:
Adesk::Boolean
AcGeCurve3d::isPlanar(AcGePlane&,constAcGeTol&=AcGeContext::gTol)const;
下面两个函数可以或大额曲线上两个参数值之间所对应曲线段的长度:
double
AcGeCurve2d::length(doublefromParam,double toParam,
             double =AcGeContext::gTol.equalPoint()) const;

double
AcGeCurve3d::length(doublefromParam,double toParam,
             double =AcGeContext::gTol.equalPoint()) const;
我们要想获得某个参数对应于模型空间上的点,就使用AcGeCurve2d::evalPoint()和AcGeCurve3d::evalPoint()函数来完成。下面我们会发现,如果在应用程序中频繁调用这类函数,也就会频繁的对AcGePointOnCurve2d类和AcGePointOnCurve3d类进行访问,计算曲线上点的函数原型声明是:
AcGePoint2d
AcGeCurve2d::evalPoint(doubleparam)const;
AcGePoint2d
AcGeCurve2d::evalPoint(double param,intnumDeriv,AcGeVector2dArray& derivArray)const;

AcGePoint3d
AcGeCurve3d::evalPoint(doubleparam)const;
AcGePoint3d
AcGeCurve3d::evalPoint(double param,intnumDeriv,AcGeVector3dArray& derivArray)const;

有些操作能引起所创建的实体变异。这里所谓的变异,是指虽然创建产生的对象属于某一特定的类,但它的几何特性却肯呢个不符合该类的要求。例如在创建一个圆弧对象时,如果它的起始角度与终止角度相等,则会产生一个点而不是圆弧。从几何特性上说,所产生的对象时一个点对象,但是在程序运行中的类型仍然是圆弧对象。我们可以通过一个isDegenerate()函数来确定对象是否有所变异。该函数对于二维三维曲线分别有两种方法,第一种方法返回类型,而第二种返回一种不同运行类型的一个非变异对象,在上面的举例中,它将返回点。有关函数的原型请参看gecurv2d.h或gecurv3d.h
11.4.2曲面曲面的方向部分地确定了它的计算法线矢量。参数化曲面有两个参数u和v,每个代表位于该曲面上的参数直线的方向。如果得到某个点上u和v的切线矢量,通过叉乘,就得到一个法线矢量,这就是该点上所对应的曲面法线方向。下面的函数可以使用曲面的方向取反:
AcGeSu**ce&AcGeSu**ce::isNormalReversed()const
曲面求值函数返回曲面的法线或法线的反向,这与reverseNormal()函数调用的奇偶次数有关。如果曲面的方向和它的自然方向相反,则   下面函数返回TRUE:
Adesk::BooleanAcGeSu**ce::isNormalReversed()const
下例创建一个圆,并将它投影到XY平面上,然后检查投影到XY平面上的实体的类型:
AcGePlane plane;
      AcGePoint3dp1(2,3,5);
      AcGeVector3dv1(1,1,1);
      AcGeCircArc3dcirc (p1,v1,2.0);
      AcGeEntity3d*projectedEntity = circ.project(plane,v1);
      if(projectedEntity->type()== AcGe::kEllipArc3d)
        return;
      elseif(projectedEntity->type()==AcGe::kCircArc3d)
        return;
else if(projectedEntity->type()==AcGe::kLineSeg3d)
  return;
下例创建一个非均匀有理B样条曲线,并寻找该曲线上最靠近点p1的点。该点是以AcGePointOnCurve3d对象的形式返回的,用这个对象就可以得到该点的坐标和它的参数:
      AcGeKnotVectorknots;
      AcGePoint3dArraycntrlPnts;
      AcGePointOnCurve3dpntOnCrv;
      AcGePoint3d  p1(1,3,2),p2;
      knots.append(0.0);
      knots.append(0.0);
      knots.append(0.0);
      knots.append(0.0);
      knots.append(1.0);
      knots.append(1.0);
      knots.append(1.0);
      knots.append(1.0);
      cntrlPnts.append(AcGePoint3d(0,0,0));
      cntrlPnts.append(AcGePoint3d(1,1,0));
      cntrlPnts.append(AcGePoint3d(2,1,0));
      cntrlPnts.append(AcGePoint3d(3,0,0));
      AcGeNurbCurve3d    nurb (3,knots,cntrlPnts);
      nurb.getClosestPointTo(p1,pntOnCrv);
      p2= pntOnCrv.point();
      doubleparam = pntOnCrv.parameter();






                          

评分

参与人数 2威望 +1 D豆 +6 贡献 +2 收起 理由
tigcat + 1 很给力!经验;技术要点;资料分享奖!
newer + 1 + 5 + 2

查看全部评分

论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

已领礼包: 12个

财富等级: 恭喜发财

 楼主| 发表于 2016-7-27 17:40:43 | 显示全部楼层
这就是前几天发的那个书的部分。最后谢谢大家 再见了
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 264个

财富等级: 日进斗金

发表于 2016-7-27 21:52:17 | 显示全部楼层
送个目录?!

点评

您看好了 这不是目录  详情 回复 发表于 2016-7-28 06:47
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复

使用道具 举报

已领礼包: 51个

财富等级: 招财进宝

发表于 2016-7-27 22:50:58 | 显示全部楼层
版主可否做成word啊  辛苦了  不过是不是要求太多
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 12个

财富等级: 恭喜发财

 楼主| 发表于 2016-7-28 06:47:52 | 显示全部楼层

您看好了 这不是目录
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 2471个

财富等级: 金玉满堂

发表于 2016-7-30 18:01:58 | 显示全部楼层
很好,正需要。
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 2个

财富等级: 恭喜发财

发表于 2016-12-1 23:04:48 | 显示全部楼层

求书,楼主,买不到
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

发表于 2017-1-9 17:59:32 | 显示全部楼层
请问这书有地方买吗??
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 271个

财富等级: 日进斗金

发表于 2017-1-10 09:41:24 | 显示全部楼层
谢谢楼主无私的奉献!
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 69个

财富等级: 招财进宝

发表于 2017-2-6 00:37:40 | 显示全部楼层
啥也不说了,感谢楼主分享哇!
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

发表于 2018-8-21 15:21:16 | 显示全部楼层
这本书的名字是????有知道的能否说一哈
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 1个

财富等级: 恭喜发财

发表于 2018-8-23 23:43:50 来自手机 | 显示全部楼层
厉害,厉害
来自: 微社区
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复

使用道具 举报

已领礼包: 6个

财富等级: 恭喜发财

发表于 2018-8-27 10:31:02 | 显示全部楼层
有实体书或者PDF修订版的书吗?想买一本
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

发表于 2018-9-2 17:25:48 | 显示全部楼层
谢谢了,好文章
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 12个

财富等级: 恭喜发财

 楼主| 发表于 2018-9-5 07:44:48 来自手机 | 显示全部楼层
统一回复,本人告别了,感谢支持
来自: 微社区
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|辽公网安备|晓东CAD家园 ( 辽ICP备15016793号 )

GMT+8, 2024-11-17 18:27 , Processed in 0.758917 second(s), 60 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表