***********************************************
第 1章 Hello World: 工程的创建
***************************************
在这一章中,我们将不使用ObjectARX向导来创建一个新的工程。我们将使用Visual Studio .NET来创建一个新的类库工程。通过这个工程,你可以创建一个能被AutoCAD装载的.NET dll文件。这个dll文件会向AutoCAD加入一个名为“HelloWorld”的新命令。当用户运行这个命令后,在AutoCAD 命令行上将显示“Hello World”文本。
1) 启动Visual Studio.NET,选择”文件>新建>工程”(File> New> Project)。在新建工程对话框中选择工程类型为”Visual Basic 工程”,然后选择”类库”模板,在工程名字框中输入”Lab1”,然后选择工程存放的位置。点击确定按钮来创建工程。
2) 在工程的Class1.vb文件中,一个公有类“Class1”已经被系统自动创建了。接下来向这个类加入命令。要加入命令,你必须使用AutoCAD .NET托管封装类。这些托管封装类包含在两个托管模块中。要加入对这两个托管模块的引用,请用鼠标右键单击”引用”然后选择”添加引用”。在弹出的”添加引用”对话框中选择”浏览”。在”选择组件”对话框中,选择AutoCAD 2006的安装目录(这里假定为C:\Program Files\AutoCAD 2006\),在这个目录下找到“acdbmgd.dll”然后选择并打开它。再一次选择”浏览”,在AutoCAD 2006的安装目录下找到“acmgd.dll”并打开它。当这两个组件被加入后,请单击”添加引用” 对话框中的”确定”按钮。正如它们的名字所表示的,acdbmgd.dll包含ObjectDBX托管类,而acmgd.dll包含AutoCAD托管类。
3) 使用对象浏览器(Visual Studio.NET的”查看>其它窗口>对象浏览器”菜单项)来浏览加入的两个托管模块所提供的类。请展开“AutoCAD .NET Managed Wrapper”对象(在对象浏览器中显示为acmgd),在整个教程中我们将使用这个对象中的类。在本章中,我们将使用 “Autodesk.AutoCAD.EditorInput.Editor”类的一个实例来在AutoCAD命令行中显示文本。请再展开“ObjectDBX .NET Managed Wrapper” 对象(在对象浏览器中显示为acdbmgd),这个对象中的类将被用来访问和编辑AutoCAD图形中的实体(这部分内容将在以后的章节中介绍)。
4) 引用了ObjectARX .NET 封装类后,我们就可以导入它们。在Class1类的声明语句(位于Class1.vb文件的顶部的)之前,导入ApplicationServices, EditorInput 和 Runtime命名空间。
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Runtime
5) 接下来在类Class1中加入命令。要加入能在AutoCAD 中调用的命令,你必须使用“CommandMethod”属性。这个属性由Runtime命名空间提供。在类Class1中加入下列属性和子程序。注意续行符” _”的使用。
Public Class Class1
<CommandMethod("HelloWorld")> _
Public Sub HelloWorld()
End Sub
End Class
6) 当“HelloWorld”命令在AutoCAD中运行的时候,上面定义的HelloWorld子程序就会被调用。在这个子程序中,一个Editor类的实例将被创建。Editor类拥有访问AutoCAD命令行的相关方法,它还包括选择对象和其它一些重要的功能。AutoCAD当前活动文档的Editor对象可以使用Application类来访问。当Editor对象被创建后,你可以使用它的WriteMessage方法在命令行中显示“Hello World”文本。在HelloWorld子程序中加入以下代码:
Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor
ed.WriteMessage("Hello World")
7) 要在AutoCAD中调试这个程序,你可以让Visual Studio.NET启动一个AutoCAD进程。在解决方案管理器中右键单击“Lab1”,然后选择”属性”。在Lab1的属性页对话框中,选择” 配置属性>调试”。在”启动”项中,选择”启动一个外部程序”,接下来单击省略号按钮然后选择AutoCAD 2006安装目录下的acad.exe。设置好以后,按F5来启动一个AutoCAD进程。这样就会编译你的程序然后自动启动AutoCAD,而当编译后有错误的时候就会停止。请修正你可能碰到的任何错误。
8) “NETLOAD”命令被用来装载你刚才编译的托管程序。在AutoCAD命令行中输入NETLOAD,会出现”选择.NET组件”的对话框。选择上面生成的“lab1.dll”然后打开它。
9) 在命令行中输入“HellowWorld”。如果一切顺利的话,命令行中将显示“Hello World”文本。切换到Visual Studio.NET,在ed.WriteMessage(“Hello World”);语句处加入一个断点。在AutoCAD中再次运行HelloWorld命令,你会注意到你可以跟踪代码的运行。Visul Studio.NET的”调试”菜单有好几项可以用来跟踪程序的运行。
如果有时间的话,请浏览一下CommandMethod属性。你会发现它有七种不同的形式。在上面的例子中,我们使用了最简单的形式,它只有一个输入参数(命令的名字)。你可以使用其它的形式来控制命令的工作方式,例如你可以确定命令组的名字、全局和局部名字、命令标识(命令如何来运行)等。
*******************************************************************
第2章 .net autocad 向导及简单用户输入
***********************************************
在第一章中,我们使用的是类库模板,这样就不得不手工加入acdbmdg. dll 和acmgd.dll这两个引用。在这一章中,我们将使用autocad托管c#应用程式向导来创建.net工程,他会自动加入以上两个引用。在开始本章之前,您首先得安装objectarx向导(objectarx2006研发包的\utils\objarxwiz\arxwizards.msi)。
1) 启动visual studio .net,选择”文档>新建>工程”(file> new> project)。在新建工程对话框中选择工程类型为”visual basic工程”,然后选择“autocad managed vb project application”模板。在工程名字框中输入”lab2”,然后选择工程存放的位置。点击确定按钮,“autocad managed vb application wizard”对话框将会出现。因为我们无需使用非托管代码,所以不要选择“enable unmanaged debugging”项。“registered developer symbol”将会使用您在安装objectarx向导时输入的值。单击”finish”按钮来创建工程。
2) 下面来看一下向导生成的工程。在解决方案浏览器中,您会看到acdbmgd 和 acmgd已被引用了。在class.vb文档中,“autodesk.autocad.runtime”命名空间已被导入,工程使用“registered developer symbol”的名字来命名缺省的公有类。向导还为类加入了一个commandmethod属性和一个函数,他们用于autocad命令。
3) 在前一章中,我们使用一个“autodesk.autocad.editorinput.editor”类的实例对象在autocad命令行上输出文本。在这一章中,我们将使用这个类来提示用户在autocad图像中选择一个点,然后将用户选择的点的x,y,z值显示出来。和前一章相同,请导入autodesk.autocad.applicationservices 和 autodesk.autocad.editorinput命名空间。
imports autodesk.autocad.applicationservices
imports autodesk.autocad.editorinput
4) 把向导生成的commandmethod属性的值改为有意义一些的名字如“selectpoint”(函数的名字能够不用修改)。promptpointoptions类用来配置提示字符串和其他的一些控制提示的选项。这个类的一个实例作为参数被传入到editor.getpoint方法。在函数的开始,实例化这个类,配置字符串参数为“select a point”。因为 editor.getpoint方法会返回一个promptpointresult类的实例对象,所以我们也要把他实例化。
dim prpointoptions as promptpointoptions =
new promptpointoptions("select a point")
dim prpointres as promptpointresult
5) 接下来实例化一个editor类的对象并使用参数为promptpointoptions对象的getpoint方法。用getpoint方法的返回值来给上面声明的promptpointresult对象赋值。赋值好以后,我们能够测试promptpointresult对象的状态,假如不是ok就返回。
dim ed as editor = application.documentmanager.mdiactivedocument.editor
prpointres = ed.getpoint(prpointoptions)
if prpointres.status <> promptstatus.ok then
return nothing
end if
6) 假如promptpointresult对象返回了一个有效的点,我们就能够使用writemessage方法把结果输出到命令行。promptpointresult.value的tostring方法使输出很容易:
ed.writemessage("you selected point " & prpointres.value.tostring())
7) 按f5来运行一个调试autocad的进程。(注意:向导已配置好用acad.exe来调试)在autocad命令行中输入netload,选择lab2.dll并打开。在命令行中输入您起的命令名字(selectpoint)。在选择点的提示下,单击图像中的任一点。假如一切正常的话,您能够在命令行中看到您所选的点的坐标值。在class.vb文档的“return nothing”行加入断点,然后再次运行selectpoint命令。这一次,在选择点的提示下按esc键而不是选择一个点。promptpointresult对象的状态就不是ok了,所以上面代码中的if语句就会被执行,“return nothing”语句就会被调用。
8) 接下来我们将加入另外一个命令,他能够获取两个点之间的距离。向导没有添加命令的功能,所以我们必须手工添加。在class.vb文档的选择点的函数(getpoint)下面添加一个名为getdistance的新命令。加入命令的方法请参考上一章的内容或本章的源代码,这里就不列出了。使用commandmethod属性并使字符串参数为“getdistance”或其他类似的名字。在命令的函数中使用promptdistanceoptions代替promptpointoptions。当然getdistance方法的返回值是个promptdoubleresult类的实例对象,所以请用promptdoubleresult来代替promptpointresult:
dim prdistoptions as promptdistanceoptions = new promptdistanceoptions("find distance, select first point:")
dim prdistres as promptdoubleresult
prdistres = ed.getdistance(prdistoptions)
9) 和前面的命令相同,也能够测试promptdoubleresult的状态,然后用writemessage方法在命令行中显示值。
if prdistres.status <> promptstatus.ok then
return nothing
end if
ed.writemessage("the distance is: " & prdistres.value.tostring)
*******************************************************************
第 3 章 数据库基础: 创建我们自己的Employee 对象
************************************************
打开Lab3文件夹下的Lab3工程文件,或或接着Lab2的代码。
在这一章中,我们将创建一个‘Employee 对象’(包括一个圆,一个椭圆和一个多行文本对象),这个对象属于一个自定义的EmployeeBlock’块(这个块驻留在‘EmployeeLayer’层,当在模型空间插入这个块的时候,‘EmployeeLayer’层就会拥有这个块的一个块索引)。本章的每一个步骤中的代码都可以运行,这样做的目的可以使你更清楚地知道每一部分代码完成的功能。第一步将简要说明一下如何在模型空间创建一个圆。
这一章的重点是在AutoCAD中访问数据库的基础。主要内容包括事务处理(Transaction)、对象Id(ObjectId)、符号表(symbol tables,如块表BlockTable和层表LayerTable)以及对象引用。使用的其它一些对象如颜色Color、三维点Point3d和三维向量Vector3d,都和各自的步骤有关,但重点应该放在数据库基础上。
1) 创建一个名为‘CREATE’的命令,它调用函数CreateEmployee()。这个函数用来在模型空间(MODELSPACE)的(10,10,0)点处创建一个半径为2.0的圆:
Public Function CreateEmployee()
'首先声明我们要使用的对象
Dim circle As Circle '这个是我们要加入到模型空间的圆
Dim btr As BlockTableRecord '要加入圆,我们必须打开模型空间
Dim bt As BlockTable '要打开模型空间,我们必须通过块表(BlockTable)来访问它
'我们使用一个名为'Transaction’的对象,把函数中有关数据库的操作封装起来
Dim trans As Transaction
'使用TransactionManager的StartTransaction()成员来开始事务处理
trans = HostApplicationServices.WorkingDatabase().TransactionManager.StartTransaction()
'现在创建圆……请仔细看这些参数——注意创建Point3d对象的'New'和Vector3d的静态成员ZAxis
circle = New Circle(New Point3d(10, 10, 0), Vector3d.ZAxis, 2.0)
'我们需要获得块表和模型空间对象
'注意我们是用事务处理的成员GetObject来获取它们的bt = trans.GetObject(HostApplicationServices.WorkingDatabase.BlockTableId, OpenMode.ForRead)
'现在,我们声明了一个ObjectId对象用来表示模型空间块表记录…
Dim btrId As ObjectId = bt.Item(btr.ModelSpace)
'使用这个ObjectId对象来获取块表记录对象–注意我们是打开它用来写入
btr = trans.GetObject(btrId, OpenMode.ForWrite)
'现在使用btr对象来加入圆
btr.AppendEntity(circle)
trans.AddNewlyCreatedDBObject(circle, True) '并确定事务处理知道要加入圆!
trans.Commit() ' 一旦完成以上操作,我们就提交事务处理,这样以上所做的改变就被保存了……
trans.Dispose() ' …然后销毁事务处理,因为我们已经完成了相关的操作(事务处理不是数据库驻留对象,可以销毁)
End Function
请仔细阅读一下上面的代码块的结构,可以通过注释来了解相关的细节。
注意:要编译代码,你必须导入Autodesk.AutoCAD.DatabaseServices 和Autodesk.AutoCAD.Geometry命名空间
运行这个函数来看看它是否可行。应该会在图形中创建一个在(10,10,0)处的半径为2.0的白色的圆。
2) 我们可以减少代码的输入量,这可以通过声明一个Database变量代替HostApplicationServices.WorkingDatabase来实现:
Dim db as Database = HostApplicationServices.WorkingDatabase()
使用这个变量来代替在代码中出现的HostApplicationServices.WorkingDatabase()。
3) 注意:bt.Item(btr.ModelSpace)用来获取模型空间块表记录的ObjectId。我们也可以使用BlockTable的可数化属性来做同样的事:
bt(btr.ModelSpace)
上面的方法使代码变得容易和精简(像下面一样改变有关的代码):
bt = trans.GetObject(db.BlockTableId, OpenMode.ForRead)
btr = trans.GetObject(bt(btr.ModelSpace), OpenMode.ForWrite)
4) 在上面的代码中,我们没有使用任何异常处理,而异常处理对一个正确的.NET应用程序来说是非常重要的。我们要养成使用异常处理的好习惯,所以让我们在这个函数中加入Try-Catch-Finally。
5) 为了使代码紧凑,我们可以把许多变量的声明和初始化放在同一个语句中。现在,你的代码看起来应该是这样的:
Public Function CreateEmployee()
Dim db As Database = HostApplicationServices.WorkingDatabase()
Dim trans As Transaction = db.TransactionManager.StartTransaction()
Try
Dim Circle As Circle = New Circle(New Point3d(10, 10, 0), Vector3d.ZAxis, 2.0)
Dim bt as BlockTable = trans.GetObject(db.BlockTableId, OpenMode.ForRead)
Dim btr as BlockTableRecord = trans.GetObject(bt(btr.ModelSpace), OpenMode.ForWrite)
btr.AppendEntity(circle)
trans.AddNewlyCreatedDBObject(circle, True)
trans.Commit()
Catch
MsgBox("Error Adding Entities")
Finally
trans.Dispose()
End Try
End Function
运行你的代码来进行测试……
上面的catch块只显示一个错误信息。实际的清理工作是在finally块中进行的。这样做的理由是如果在事务处理被提交(Commit())之前,Dispose()被调用的话,事务处理会被 销毁。我们认为如果在trans.Commit()之前出现任何错误的话,你应该销毁事务处理(因为Commit将永远不会被调用)。如果在Dispose()之前调用了Commit(),也就是说没有任何错误发生,那么事务处理将会被提交给数据库。
所以基于上面的分析,Catch块其实并不是必须的,因为它只用来通知用户程序出现了一个错误。它将在下面的代码中被去掉。
6) 现在让我们在Employee加入剩下的部分:椭圆和多行文本的实例。现在让我们在Employee加入剩下的部分:椭圆和多行文本的实例。
多行文本实体:中心点应该与圆心的创建一样:
(建议:创建一个名为‘center’而值为10,10,0的Point3d变量来表示中心点)多行文本的内容可以是你的名字。椭圆(提示:你可以先看一下Ellipse的构造函数)法向量应该沿着Z轴(请查看Vector3d类型)主轴设为Vector3d(3,0,0)(提示:不要忘了用new)半径比例设为0.5 椭圆还必须闭合(也就是说,开始和结束点必须相同)运行你的代码来进行测试……应该可以生成一个圆、一个椭圆和一个中心点在10,10,0的多行文本。
注意:和事务处理对象有关的.NET API中的Try-Catch-Finally块结构,应该是异常观察者。实际上我们是在try块中实例化对象的,但没有显式地销毁它们。当产生异常的时候可能会产生问题,特别是当观察者注意到我们实际上用的是封装的非托管对象!记住,当资源不再使用的时候,垃圾收集机制就会回收内存。垃圾收集机制会不时的调用封装类的Dispose()方法,删除非托管对象。这里还要注意的是Dispose()作用于封装的非托管类对象的方式取决于对象是否是数据库驻留对象。由非数据库驻留对象调用的Dispose()会删除非托管对象,而由数据库驻留对象调用的Dispose()只是关闭它们。
7) 接下来让我们来创建一个新的函数,它用来新建一个颜色为黄色,名字为“EmployeeLayer” 的AutoCAD层。 这个函数应该检查是否这个层已经存在,但不管这个层是否存在,函数都应该返回“EmployeeLayer”的ObjectId。下面是这个函数的代码:
Public Function CreateLayer() As ObjectId
Dim layerId As ObjectId '它返回函数的值
Dim db As Database = HostApplicationServices.WorkingDatabase
Dim trans As Transaction = db.TransactionManager.StartTransaction()
'首先取得层表……
Dim lt As LayerTable = trans.GetObject(db.LayerTableId, OpenMode.ForWrite)
'检查EmployeeLayer层是否存在……
If lt.Has("EmployeeLayer") Then
layerId = lt.Item("EmployeeLayer")
Else
'如果EmployeeLayer层不存在,就创建它
Dim ltr As LayerTableRecord = New LayerTableRecord()
ltr.Name = "EmployeeLayer"//设置层的名字
layerId = lt.Add(ltr)
trans.AddNewlyCreatedDBObject(ltr, True)
End If
trans.Commit()
trans.Dispose()
Return layerId
End Function
是不是觉得这个函数的基本结构与在模型空间加入实体的代码比较类似?访问数据库的方法都是这样的:使用事务处理来获取数据库对象,在符号表(模型空间所在的块表也是符号表之一)中加入实体,然后让事务处理知道。
8) 在这个函数中加入异常处理,就像在CreateEmployee函数中的一样。
9) 接下来,改变新建层的颜色。下面是实现的代码片断,请把它加入到你的代码中:
ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 2)
注意:ColorMethod.ByAci可以让我们使用AutoCAD ACI颜色索引……这里为2(表示黄色)。
10) 回到CreateEmployee(),加入把上面创建的几个实体设置到EmployeeLayer层的代码。声明一个类型为ObjectId的变量,用CreateLayer函数的返回值给它赋值。使用每个实体(文本、圆和椭圆)的LayerId属性设置它们所在的层。
例如:text.LayerId = empId
运行代码来查看“EmployeeLayer”层是否已被创建,所有已创建的实体是否都在这一层上(应该显示为黄色)
11) 现在为各个实体设置不同的颜色,可以使用ColorIndex属性(ColorIndex属性表示AutoCAD的颜色)
圆为红色-1
椭圆为绿色-3
文本为黄色-2
运行代码,看看实体的颜色是否为设置的值,即使这些实体是在“EmployeeLayer”层上。
12) 接下来,我们要在AutoCAD数据库中创建一个独立的块,然后把它插入到块表而不是模型空间中。
首先把CreateEmployee函数的名字改为CreateEmployeeDefinition()。
加入以下代码来创建一个独立的块:
Dim myBtr As BlockTableRecord = New BlockTableRecord()
myBtr.Name = "EmployeeBlock"
Dim myBtrId As ObjectId = bt.Add(myBtr)
trans.AddNewlyCreatedDBObject(myBtr, True)
13) 现在,请稍微改动一下加入实体到模型空间的代码(改为加入块到块表中,记得加入前要打开块表)。
现在运行代码,然后使用INSERT命令来检查是否可以正确插入这个块。
14) 最后,我们要创建一个位于模型空间的块索引,它表示上面创建的块的一个实例。这一步留给大家练习。
下面是你要遵循的最基本的步骤:
创建一个名为CreateEmployee新的函数
把命令属性“CREATE”移动到CreateEmployee()
修改CreateEmployeeDefintion()来返回新创建的块“EmployeeBlock”的ObjectId,操作的步骤请参考CreateLayer()的作法。
你需要修改CreateEmployeeDefintion()来查看块表中是否已包含“EmployeeBlock”块,如果包含这个块,则返回它的ObjectId(做法与CreateLayer()一样)。提示:把'bt'的声明语句移动到try块的顶部,使用BlockTable.Has()方法,把其它的代码移动到else语句:
Try
'获取BlockTable 对象
Dim bt As BlockTable = trans.GetObject(db.BlockTableId, OpenMode.ForWrite)
If (bt.Has("EmployeeBlock")) Then
newBtrId = bt("EmployeeBlock") '已经存在……没必要创建它!
Else
…
在新创建的CreateEmployee()函数中创建一个新的BlockReference对象,并把它加入到模型空间。提示:我们可以使用CreateEmployeeDefinition()中引用模型空间的代码,这些代码在这里不需要了
在CreateEmployee中调用CreateEmployeeDefinition()函数,使上面生成的BlockReference对象的BlockTableRecord()指向CreateEmployeeDefinition()函数。提示:请参考BlockReference的构造函数。
附加的问题:
让我们来看一下代码的运行情况,执行命令会生成一个EmployeeBlock的块索引,你会看到它被插入到20,20,0而不是10,10,0。为什么?
如果你知道原因,那么怎样才能使块索引插入到正确的点?