☰
AutoCAD二次开发从VBA到VB.NET:对象模型与实战避坑指南
2026/10/13 4:11:34 网站建设 项目流程

简介:面向AutoCAD二次开发初学者与需要提升插件开发能力的工程师,代码包围绕VBA与VB.NET展开,系统演示利用AutoCAD .NET API控制工作环境、创建编辑实体、尺寸标注、三维图形处理、打印布局与输出、事件响应,并涵盖Visual Studio中的错误处理和程序发布,基本覆盖了AutoCAD .NET二次开发的主要任务。包内共1827个文件,以cs源文件、dll库、pdb调试信息、工程文件和txt说明为主,压缩包约48.35MB,目录结构便于按章节对照学习。已有578人学习下载,适合希望从代码入手、快速建立开发流程认知的读者。除完整示例代码外,还包含可运行的工程配置与编译产物,便于直接调试、修改和扩展,可作为日常开发时的参考模板。

1. AutoCAD VBA与VB.NET二次开发:入门选VBA,项目落地选VB.NET

AutoCAD VBA和VB.NET二次开发,是两代技术栈,但很多教程把它们混在一起讲,导致新手学会了VBA写宏,再去看VB.NET代码时一头雾水。我自己的经验是:VBA适合改图、批量处理这种小而快的活,写出的宏能在半小时内跑通;VB.NET适合做完整工具,能跨图纸、带界面、处理异常,但工程结构、事务和引用问题一开始就能劝退一半人。如果你正想学AutoCAD二次开发,又以这个教程标题为入口,可以先把它当作一条路线图:先用VBA把对象模型摸熟,再用VB.NET把工程能力补齐。这篇笔记就是按这个顺序拆的,每一步都给能直接跑的代码和参数说明,最后集中讲那些让人翻车的坑。

2. 对象模型是所有AutoCAD开发的总纲:从Application一路摸到图层和实体

2.1 层级关系的三个关键层:为什么入口代码都是固定的

VBA与VB.NET访问AutoCAD,本质上都在操作同一个对象模型。顶层是Application,代表正在运行的程序实例;往下是Document,对应打开的一张图纸;再往下是Database,装着图纸里所有图形数据。很多新手一开始不关心这个层级,写代码时靠抄,结果换一个场景就不知道怎么改了。

VBA里常见的ThisDrawing其实就是当前文档,对应VB.NET里的Application.DocumentManager.MdiActiveDocument。VBA因为封装得好,可以直接写ThisDrawing.ModelSpace;VB.NET则要经过Document、Database、BlockTable逐层取到模型空间块表记录。这个差异不是语法别扭,而是API设计思路不同:COM方式把一切简化成了对象嵌套,托管方式更强调显式声明“你正在操作哪个数据源”。

我一般会建议先背下这张链:Application → Document → Database → BlockTable → BlockTableRecord → Entity。写VBA时,前两层藏在ThisDrawing后面;写VB.NET时,每一层都得自己打开。后面所有开发的基础,都是在这条链上做读和写。

2.2 用VBA跑通第一个宏:遍历当前图纸所有直线

先写一个最实用的VBA宏,用来统计当前模型空间里有多少条直线,并把起点坐标输出到立即窗口。这个代码量小,但覆盖了遍历、类型判断、坐标数组三个基础点。

Sub ListLines() Dim doc As AcadDocument Set doc = ThisDrawing Dim ent As AcadEntity Dim lineObj As AcadLine Dim count As Long count = 0 For Each ent In doc.ModelSpace If TypeOf ent Is AcadLine Then Set lineObj = ent Debug.Print "起点: " & lineObj.StartPoint(0) & ", " & lineObj.StartPoint(1) count = count + 1 End If Next ent MsgBox "直线数量: " & count End Sub

这段代码的逻辑很直白:For Each遍历模型空间中每一个实体,TypeOf判断当前实体是否为AcadLine类型,避免把圆、图块引用当作直线处理。lineObj.StartPoint是一个Variant数组,下标0是X坐标,1是Y坐标,2是Z坐标,所以打印时只取前两个。

这里有两个实战细节。第一,TypeOf判断是这类代码的标配,不加判断直接Set lineObj = ent,遇到非直线对象会报类型不匹配。第二,Debug.Print的内容显示在VBA IDE的“立即窗口”,按Ctrl+G可以调出。如果按了宏却没有反应,先看这个窗口有没有输出,这是最快的排查手段。另外,遍历过程中不要做删除或移动操作,否则会报“集合已修改”错误,这是VBA老用户的共同记忆。

2.3 模型空间、块表和选择集:这几个集合决定了后续代码写在哪

对象模型里最常用的集合有三个:模型空间、图层集合和选择集。模型空间存放图纸上的实际实体,VBA里是doc.ModelSpace,VB.NET里是BlockTableRecord中名称等于ModelSpace的记录。图纸空间则是另一个独立的块表记录,用AddLine这些方法操作模型空间时,要明确自己写在哪个空间里。

图层集合在VBA里是doc.Layers,新建图层用doc.Layers.Add("图层名", 颜色号)。VB.NET里要新建LayerTableRecord,加到LayerTable后还要走一次事务提交,步骤比VBA多出两倍,但逻辑更清晰。选择集是解决“只处理一部分对象”问题的关键,VBA里用SelectionSets集合创建选择集,VB.NET里用Editor.GetSelection加过滤器。后续做批次工具时,选择集配上过滤器,效率比全图遍历高很多,这也是第6章要展开的内容。

3. VB.NET开发环境搭建:从两个引用到命令行里跑通自定义命令

3.1 先引对两个程序集:AcDbMgd与AcMgd的分工要分清

VB.NET开发AutoCAD插件,第一步是建一个.NET Framework类库项目,然后添加两个程序集引用。一个是AcMgd.dll,负责应用层,包含Document、Editor、命令注册等;另一个是AcDbMgd.dll,负责数据库层,包含Database、BlockTable、实体对象等。两个都得引,缺一个都会编译报错。

添加引用后,需要把这两个文件在项目属性里的“复制本地”设为False。如果不设置,编译器会把dll复制到输出目录,运行时AutoCAD会同时加载自己安装目录下的版本和输出目录里的版本,大概率出现类型不匹配或加载失败。我见过不少同事在这个问题上卡了一下午,最后发现是CopyLocal没关。

在代码开头,需要写入必要的Imports语句,分别是Autodesk.AutoCAD.ApplicationServices、Autodesk.AutoCAD.DatabaseServices、Autodesk.AutoCAD.Geometry和Autodesk.AutoCAD.Runtime。这四个命名空间覆盖了命令注册、文档访问、坐标类型和数据库对象,是VB.NET开发的基本配置。

3.2 创建第一个VB.NET命令:画一个带半径的圆

下面这段代码是VB.NET开发里最典型的结构,也是一个完整的入门实例:创建一个半径为50的圆,圆心在原点。

Imports Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.Geometry Imports Autodesk.AutoCAD.Runtime Public Class CircleCommands <CommandMethod("CreateCircle")> Public Shared Sub CreateCircle() Dim doc As Document = Application.DocumentManager.MdiActiveDocument Dim db As Database = doc.Database Using tr As Transaction = db.TransactionManager.StartTransaction() Dim bt As BlockTable = tr.GetObject(db.BlockTableId, OpenMode.ForRead) Dim btr As BlockTableRecord = tr.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForWrite) Dim center As New Point3d(0, 0, 0) Dim circle As New Circle(center, Vector3d.ZAxis, 50) btr.AppendEntity(circle) tr.AddNewlyCreatedDBObject(circle, True) tr.Commit() End Using doc.Editor.WriteMessage(vbLf & "已创建半径为50的圆") End Sub End Class

先看整体结构。CommandMethod特性把CreateCircle方法注册成AutoCAD命令,方法必须是Public Shared才能在命令行被调用。事务是整个操作的骨架:StartTransaction开启,Commit提交,Using块结束时自动释放事务资源。

逐行看核心操作。打开块表用ForRead就够了,因为我们不会修改块表本身。打开模型空间记录时必须用ForWrite,因为要向里面追加实体。创建Circle对象时,三个参数分别是圆心、法向量和半径,法向量通常用Vector3d.ZAxis表示平面法线方向。AppendEntity把圆加入模型空间,AddNewlyCreatedDBObject把对象登记到事务里,这样撤销和保存才能正常工作。

这里有个容易忽略的地方。如果只写AppendEntity而不写AddNewlyCreatedDBObject,图形会显示,但可能出现撤销异常或保存后实体丢失。这是.NET API和COM API一个很重要的差别,VBA里AddCircle一步搞定,VB.NET里必须两步走。

3.3 加载、执行与调试:NETLOAD三条路和附加调试

编译生成dll后,在AutoCAD命令行输入NETLOAD命令,选择生成的dll文件,再输入CreateCircle,就能看到命令行提示“已创建半径为50的圆”。命令名是CommandMethod里写死的字符串,不是方法名。如果改了方法名没改特性里的字符串,命令行里依然用旧名字。

调试时,我一般会先在IDE里把输出类型改成类库,然后通过“调试”菜单附加到正在运行的AutoCAD进程。这样可以在代码里打断点,看到事务执行到哪一步出错。那种直接运行项目启动AutoCAD的方式也可以,但每次重新编译都要关CAD,效率低。

首次加载失败时,优先看IDE的输出窗口和系统事件日志里的.NET运行时错误,上面会写明是哪个程序集找不到、哪个类型不匹配。不要去猜,错误信息里通常已经指明了方向。

4. 从VBA迁到VB.NET:映射表、重写实例与混合使用的边界

4.1 一张表看懂VBA概念在VB.NET里的对应位置

VBA代码迁移到VB.NET时,最怕的就是拿VBA的思维去套VB.NET的API。下面这张映射表可以帮你快速定位新代码应该写在哪里,我在做迁移培训时也常拿这张表做开场。

VBA习惯写法VB.NET对应写法说明
ThisDrawingApplication.DocumentManager.MdiActiveDocument获取当前文档
doc.ModelSpacebt(BlockTableRecord.ModelSpace)进入模型空间块表记录
doc.Layersdb.LayerTableId 打开后遍历图层集合
AddLine(x1,y1,z1,x2,y2,z2)new Line(New Point3d(...), New Point3d(...))创建直线
AddCircle(center, radius)new Circle(center, Vector3d.ZAxis, radius)创建圆
doc.SendCommanddoc.Editor.Command执行命令行指令
AcadBlockReferenceBlockReference图块引用实体
SelectionSetsEditor.GetSelection交互选择集

表格里最重要的一行是ObjectId。VB.NET里几乎所有实体访问都以ObjectId为中介,id本身不代表对象,要通过tr.GetObject(id)把实体打开。这是为了保证事务边界清晰、内存可控。VBA里拿到的是对象引用,VB.NET里拿到的是身份凭证,这个思维转换是迁移过程中的第一道坎。

4.2 同样的功能写两遍:统计图块数量的VBA版与VB.NET版

以“统计当前图纸里每种图块的引用数量”为例,先看VBA版本。这个功能在施工图管理里经常用到,逻辑简单但具备代表性。

Sub CountBlocksVBA() Dim doc As AcadDocument Set doc = ThisDrawing Dim ent As AcadEntity Dim blk As AcadBlockReference Dim dict As Object Set dict = CreateObject("Scripting.Dictionary") For Each ent In doc.ModelSpace If TypeOf ent Is AcadBlockReference Then Set blk = ent If dict.Exists(blk.Name) Then dict(blk.Name) = dict(blk.Name) + 1 Else dict.Add blk.Name, 1 End If End If Next ent Dim key As Variant For Each key In dict.Keys Debug.Print key & ": " & dict(key) Next key End Sub

VBA版的核心思路是遍历模型空间,用TypeOf识别图块引用,然后用字典对象按块名计数。这个写法简洁,但需要注意,Scripting.Dictionary需要引用Microsoft Scripting Runtime库,没有勾选引用时CreateObject会失败,具体现象就是运行到那行报“自动化错误”。

再看VB.NET版本,同样的功能,写法完全换了一套逻辑。

Imports Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.Runtime Imports System.Collections.Generic Public Class BlockTools <CommandMethod("CountBlocks")> Public Shared Sub CountBlocks() Dim doc As Document = Application.DocumentManager.MdiActiveDocument Dim db As Database = doc.Database Dim countDict As New Dictionary(Of String, Integer) Using tr As Transaction = db.TransactionManager.StartTransaction() Dim bt As BlockTable = tr.GetObject(db.BlockTableId, OpenMode.ForRead) Dim btr As BlockTableRecord = tr.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForRead) For Each id As ObjectId In btr Dim ent As Entity = tr.GetObject(id, OpenMode.ForRead) If TypeOf ent Is BlockReference Then Dim br As BlockReference = DirectCast(ent, BlockReference) If countDict.ContainsKey(br.Name) Then countDict(br.Name) += 1 Else countDict(br.Name) = 1 End If End If Next id tr.Commit() End Using For Each item As KeyValuePair(Of String, Integer) In countDict doc.Editor.WriteMessage(vbLf & item.Key & ": " & item.Value) Next End Sub End Class

VB.NET版本的差异可以从两个角度看。第一,访问入口不同,必须先打开BlockTable,再从块表里取出模型空间记录,不能用ThisDrawing直接进模型空间。第二,实体遍历方式不同,btr本身是一组ObjectId,每个实体都要调用tr.GetObject打开,而且这个读操作同样要放在事务里。

比较下来会发现,VBA版本写起来快,但Dictionary依赖外部库;VB.NET版本代码长,但类型安全、不依赖外部COM组件。如果你在公司内网环境部署,Scripting.Runtime不一定会默认安装,这时VB.NET版明显更稳。

4.3 对话框留在VBA,批处理交给VB.NET:混合开发的常见边界

现实中很多团队不会一次性把所有代码都迁到VB.NET,而是让两套代码并存。最常见做法是:VBA负责写交互界面和轻量改图,VB.NET负责批量计算和数据处理。VBA的UserForm开发速度快,设计师用起来也习惯;VB.NET的批量处理性能和稳定性更好,适合没有界面的后台命令。

两者怎么协作?我见过一种可行的方案:VBA宏读取对话框里的参数,把结果写入图纸的扩展数据或自定义字典,然后通过SendCommand触发VB.NET注册的自定义命令,命令再从图纸数据里读取参数完成业务。SendCommand是异步执行的,传参要拼接成完整命令行字符串,命令名后面要加空格或参数。

但我不建议在没有明确需求时强行混合。如果功能简单、图纸量小,纯VBA够用;如果要做批量处理、需要频繁读写大量实体,或者要发布到多台机器,直接走VB.NET是更省心的路线。混合方案的调试成本在于两套环境各自排错,问题往往出在“命令没触发”而不是逻辑本身。

5. AutoCAD开发避坑指南:VBA与VB.NET最常见的五个报错现场

5.1 现象:宏提示“无法加载VBA”或“对象库未注册”

运行宏时提示无法加载VBA工程,或者在代码里Set doc = ThisDrawing时弹出“对象库未注册”,通常出现在重装系统或换了新电脑之后。原因是CAD安装时没有勾选VBA组件,或者VBA IDE里引用列表中缺少AutoCAD类型库。另一种常见情况是宏安全级别太高,代码被直接禁用。

解决方式是依次排查:先在CAD安装程序里确认VBA组件是否已安装,没有就补装;然后打开VBA IDE,检查“工具”菜单下的引用,确保AutoCAD对象库处于勾选状态;最后把宏安全级别设为“中”或“低”。公司环境建议对宏做数字签名,不然每次打开都会安全提示。

5.2 现象:坐标数组越界,图形画在了奇怪的位置

VBA里AddLine时报下标越界,或者画出来的线条起点不对,绝大多数原因是坐标参数用错了。VBA的坐标参数是一个长度为3的Variant数组,分别为X、Y、Z,即使只画二维图形也必须给三个值。很多人习惯只传两个值,就会在下标取到2时越界。

VB.NET里对应的问题是把Point2d传给了需要Point3d的构造方法。解决方法很直接:打印一下坐标数组的维度,用UBound查看最大下标,确认是0到2;画二维图形时Z值填0。另一个排查点是以Point变量作为中间值,改成直接传入坐标表达式,减少类型转换的出错机会。

5.3 现象:大量循环操作后AutoCAD内存只涨不降

处理几千个实体后,CAD内存占用一路升高,关闭图纸也不释放,这是VBA和VB.NET开发里都很常见的现象。VBA里大部分原因是循环中创建的对象没有显式释放,而VB.NET里多是因为事务使用不当,比如读取实体时没有放在Using事务里,或者Commit之后没有让事务Dispose。

解决方法是把整个循环包进事务,读操作也一样。Using tr = db.TransactionManager.StartTransaction() ... End Using能保证事务结束时自动释放资源。另外,不要在一个事务里同时做遍历和写入,先遍历收集ObjectId,再开第二个事务做修改,这个习惯能规避很多内存和状态问题。

5.4 现象:NETLOAD加载成功,但命令行找不到你的命令

NETLOAD提示加载成功,输入命令时却提示未知命令。原因集中在几个地方:类没有声明为Public,方法没有声明为Public Shared,或者漏写了CommandMethod特性。还有一个原因是命令名与AutoCAD内置命令重名,内置命令优先级更高,你的命令被遮蔽了。

解决办法是按顺序检查:类名和方法名是否Public,方法是否为Static,CommandMethod里的命令名是否拼写错误。如果这些都没问题,把命令名换成一个带产品前缀的名字,比如项目缩写加功能名,一般能避开冲突。加载后记得用命令行输入命令全名,不是类名也不是方法名。

5.5 现象:换台电脑加载就崩,多半是框架版本不一致

本机调试正常的dll,拷到同事机器上加载后崩溃,最常见的原因是两边的CAD版本不同,而你编译时引用的是自己机器上的AcDbMgd.dll。不同版本之间,程序集强名称不同,运行时就会冲突。另一个原因是目标机器没有安装你项目需要的.NET框架版本,代码可能在加载时直接抛异常。

解决方式是统一编译环境。项目里引用的这两个程序集,必须从目标CAD版本的安装目录里引用,并在发布时把程序集目标框架设为目标机器支持的版本。不要图省事从自己机器复制dll到别处,也不要把CopyLocal设为True。这个坑我踩过两次,后来养成了习惯,每次新建项目先确认目标CAD版本再配引用。

6. 从实例走向工具:选择集过滤、参数对话框与自测三板斧

6.1 用选择集加过滤器,替代“遍历全部实体”的笨办法

前面的统计图块实例是全图遍历,功能上没毛病,但在几千个实体的图纸里,遍历效率明显下降。工具化的第一步,是把全图遍历改成用户交互选择。下面这段代码只让用户选择圆,再对每个圆做处理,过滤条件写在TypedValue里。

Imports Autodesk.AutoCAD.EditorInput Dim ed As Editor = doc.Editor Dim opts As New PromptSelectionOptions() opts.MessageForAdding = "选择要处理的圆: " Dim filter As New SelectionFilter(New TypedValue() {New TypedValue(0, "CIRCLE")}) Dim res As PromptSelectionResult = ed.GetSelection(opts, filter) If res.Status = PromptStatus.OK Then For Each id As ObjectId In res.Value.GetObjectIds() ' 对每个圆执行具体处理 Next End If

这段代码的价值在于DXF组码0配合实体类型字符串。过滤器直接由AutoCAD内核执行,比在遍历循环里做类型判断快得多。如果未来要支持直线或多段线,把“CIRCLE”改为“LINE”或“LWPOLYLINE”即可。参数说明就一条:PromptSelectionOptions控制提示文本,SelectionFilter控制允许选中的类型,两者配合能满足大部分交互需求。

6.2 把参数交互交给Editor和对话框,让使用的人不碰代码

工具发布后,使用的人往往不关心代码,只关心怎么操作。简单参数用Editor.GetPoint、GetString获取,复杂参数建议用窗体。在VB.NET里用Application.ShowModalDialog可以将窗体作为CAD子窗口弹出,参数填写和图纸操作可以同时进行。

一个值得借鉴的习惯是:命令尽量设计成“无参数默认执行,有参数按参数执行”。也就是说,命令启动后先让用户选择对象,如果没有选择完成就提示默认值。这样即使使用者不看帮助文档,也能自然操作下来。参数校验放在对话框关闭之后,不合法就直接拒绝,不在命令中途报错。

6.3 发布前自测三板斧:空图、批量、极限参数

每次发布工具前,我坚持用三种场景自测。第一是空图场景,新建一张空白图纸直接运行命令,不允许报未处理的异常。第二是批量场景,复制几千个对象后运行,观察内存是否稳定增长、撤销是否正常、命令行提示是否正确。第三是极限参数场景,测试超小半径、超大坐标、炸开的图块、嵌套图块引用等情况,这些边界最容易暴露问题。

这三板斧帮我挡掉了大部分翻车事故。曾经有一次,工具在正常图纸上运行流畅,但遇到一张含有嵌套图块的图纸就崩溃,原因就是遍历时没有对嵌套块引用做递归处理。从那以后,我把“嵌套对象处理”写进了自测清单。希望这些经验帮到你,至少让你在发布前能少走两趟弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询