ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AutoCAD二次开发实战:VBA与VB.NET选型、环境搭建及批量处理实例

AutoCAD二次开发实战:VBA与VB.NET选型、环境搭建及批量处理实例

简介:面向AutoCAD二次开发者的VB.NET与VBA开发基础代码包,系统覆盖从.NET API入门到控制绘图环境、创建编辑实体、尺寸标注、3D图形处理、打印布局与输出、事件响应,以及Visual Studio错误处理和程序发布,几乎囊括二次开发常用任务。压缩包共1827个文件,约48.35MB,以347个C#源码文件、345个动态链接库及调试符号为主,配以136个文本说明、90个工程文件与若干示例图纸、资源文件,便于直接编译运行和对照学习。已有578人学习下载。借助完整的工程样例与分模块组织方式,学习者可边看边练,快速掌握基于AutoCAD .NET API的程序结构、调试技巧和发布流程,适合入门及中级开发者作为手边参考。

1. 为什么还在学 AutoCAD 二次开发:VBA 与 VB.NET 的真实分工

从事设计制图的人几乎都有过类似经历:几百个图块要从旧图挪到新模板,几十张图纸要改图层颜色,坐标数据还要手工抄进表格,一坐就是一下午。会写代码的人多半早就把这类重复操作打包成了命令。《AutoCAD VBA&VB;.NET 开发基础与实例教程》这套资源,讲的就是怎么把这种“手工苦力”变成代码工作:VBA 路线适合在现有图纸环境里快速写小工具、改旧工程,VB.NET 路线适合做批量处理、数据交换和真正能交付的插件。适合每天和图纸打交道的制图人员,也适合想进入 CAD 开发方向的技术人员。学习完成后,最直接的回报是批量改图层、自动插块、导出坐标这些事不再是加班理由。

2. 二次开发选型:VBA、ActiveX 还是 VB.NET 适合哪一类项目

2.1 三条技术路线不只有名字区别,运行机制和调试方式完全不同

很多资料把 VBA、ActiveX、VB.NET 放在一起讲,读者按 VBA 写了一小段代码,又想在 VB.NET 工程里直接复制,结果必然翻车。这三条线的关系可以用一句话梳理:VBA 是寄居在 CAD 进程里的宏脚本环境,ActiveX 是一套对外暴露的 COM 接口,VB.NET 是借助托管 API 独立开发的插件体系。

先看 ActiveX。它在旧版本体系里是脚本访问 CAD 的桥梁,VBA、脚本语言都能通过它操作当前打开的图纸。这套接口的特点是方法名直接,AddLine、AddCircle、InsertBlock 读起来就能猜到含义。VBA 本质上就是直接用这套 ActiveX 接口编写绘图宏,它运行在 CAD 进程内部,编辑器由 CAD 自身提供,优势是改完立即运行,连打包分发都省了。

VB.NET 走的是另一条路:通过 CAD 提供的 .NET 类库,把工程编译成 DLL,再以插件形式加载。它更接近传统桌面开发,有完整的类库、事件机制和事务机制,处理多文档、复杂批处理、自定义界面都更顺手。三类路线的区别,落到开发时看这张表:

对比项VBAActiveX + 脚本VB.NET
运行方式进程内宏脚本外部脚本调用编译 DLL 加载
代码修改速度改完即跑视脚本环境而定需要编译再加载
事务与回滚偏隐式无独立事务概念显式事务控制
界面能力弱,仅有简单窗体几乎无界面WinForm 完整支持
分发复杂度复制 .dvb 文件即可依赖脚本环境DLL 与依赖库一起分发
适合规模几十到几百行的小工具轻量自动化和验证中大型批处理与插件

这张表里最容易误导人的是最后一栏。VBA 写几千行的工程在存量环境里并不少见,只要还能跑,继续维护是合理选择。迁移触发信号通常是三个信号同时出现:64 位环境无法加载旧组件,需要复杂界面交互,或者数据要频繁和外部数据库、表格文件联动。

2.2 我的选型标准:遗留项目优先 VBA,新项目直接 VB.NET

做技术选型时,我一般会先问三个问题,而不是看到“新功能”就默认选新框架。第一问:团队有没有已经稳定运行多年的 VBA 工程?如果有,不急着推翻,先评估旧代码在 64 位环境下是否还正常。能跑就直接在其上扩展,因为重写带来的收益很可能抵不过历史图纸和流程适配的代价。第二问:这个工具要活多久?如果只是内部临时的数据整理,VBA 的轻量就是优势;如果预计会被多个部门长期使用,VB.NET 的工程结构更扛得住需求变更。第三问:数据交互复杂吗?只在图纸里画线、改图层,VBA 足够;一旦涉及 Office 表格回填、批量图纸扫描、动态表单,VB.NET 的类库和异常处理会更稳。

我个人的习惯可以概括成一句话:新开工的项目默认选 VB.NET,存量脚本除非出现硬性兼容问题否则不动。这句话背后有一层现实原因:新版 CAD 的 64 位进程对老 VBA 的兼容性是一个黑匣子,今天能跑,不代表升级一个小版本还能跑。与其把风险留到未来,不如在新代码上从一开始就不依赖那个旧环境。

2.3 资源内容地图与建议学习顺序

这套资源标题里有两个关键词:基础、实例。它大概率的组织方式是先讲 VBA 与 VB.NET 公共的对象模型基础,再进入环境搭建,然后给多个实例,最后是调试与发布。拿到手后不建议从头到尾当连续剧看,我倾向于把它拆成四块依次吸收:

  1. 对象模型基础:搞清 Application、Document、ModelSpace、Layer、Block 这些核心对象怎么通过代码获得。
  2. 环境搭建:VBA 这边是启用编辑器和加载 .dvb;VB.NET 这边是创建类库工程、添加引用、编译 DLL。
  3. 高频实例:优先看图层整理、图块操作、批量导出,这些最贴近日常重复劳动。
  4. 调试与排查:把每种报错和实际场景对应起来,而不是只背错误码。

学习顺序上,最有效的一条路径是先用十行代码画一条直线,建立“代码等于操作”的感觉。再拿一个批量实例,在注释里把整体流程标记出来。等流程清楚了,回头再看对象模型细节和参数说明,记忆会扎实很多。源码一定要手动敲一遍,哪怕只改个变量名再运行,也比直接复制更能暴露隐藏的坑。

3. 环境搭建与第一个可用工程:从宏录制到编译加载全流程

3.1 启用 VBA 环境:从命令行到 VBAIDE

不少人在第一关就卡住:新装完软件,打开命令行输入 VBA 相关命令,提示功能不可用。原因是新版 CAD 不再默认内置 VBA 编辑器,需要单独安装对应的兼容组件,否则编辑器入口根本不存在。安装组件需要匹配当前软件位数,安装完成后重启一次程序,再输入命令就能打开。

入口命令是VBAIDE,在命令行输入后回车,代码编辑器就会弹出。如果你习惯菜单操作,也可以在“管理”或“工具”选项卡里找 VBA 管理器。编辑器左侧的工程树里通常会有 ThisDrawing 模块,它对应当前活动文档。双击打开就能写代码。这里有个工程文件选择问题:VBA 工程可以保存为独立的 .dvb 文件,也可以嵌入当前 .dwg 文档。嵌入文档的隐患是宏会随图纸被分发出去,容易在别的机器上触发冲突或权限提示,我建议统一保存成 .dvb,集中管理更干净。

编辑器里输入下面这段,就是完整的最小可用宏:

Sub DrawLineDemo() Dim startPt(0 To 2) As Double Dim endPt(0 To 2) As Double startPt(0) = 10 startPt(1) = 10 startPt(2) = 0 endPt(0) = 100 endPt(1) = 60 endPt(2) = 0 Dim lineObj As AcadLine Set lineObj = ThisDrawing.ModelSpace.AddLine(startPt, endPt) ThisDrawing.Application.ZoomAll End Sub

这段代码里最关键的是数组结构。startPt 和 endPt 都是长度为 3 的 Double 数组,对应世界坐标系的 X、Y、Z 三个轴。新手最常犯的错是只给两个元素就调用 AddLine,运行时直接报类型不匹配。ThisDrawing.ModelSpace.AddLine(startPt, endPt)是核心调用,ModelSpace 代表模型空间,AddLine 是 ActiveX 接口里最基础的绘图方法。最后的 ZoomAll 等同于手动在命令行执行缩放全部,不加这句,画出来的线可能落在当前视图之外,给人“代码没生效”的错觉。写完按 F5 运行,回到图纸窗口就能看到一条斜线。

如果报错,优先检查数组位数和活动文档状态。VBA 环境跑通之后,你已经掌握了最核心的对象访问路径:文档 → 模型空间 → 实体。

3.2 VB.NET 工程最小骨架:两个关键类库与命令方法

VB.NET 那边的工程结构复杂一点,但稳定性和可维护性明显更高。新建一个类库工程之后,第一件事是把目标框架选成 .NET Framework,不能用 .NET Core 或更高版本的跨平台框架,这是新手最容易翻车的点。第二步是添加两个核心类库引用:一个负责应用级对象,比如当前文档、命令行交互;另一个负责图纸级对象,比如数据库、实体、事务。两个 DLL 在 CAD 安装目录下能找到,用“浏览”方式直接选择,不要从全局程序集缓存里挑旧版本。

第三步,也是很多教程轻轻带过的:写好对外开放的命令方法。方法所在的类不需要额外指定类名,重要的是方法本身要标记命令特性:

Imports System.IO Imports CADApplicationServices Imports CADDatabaseServices <CommandMethod("CreateLine")> Public Sub CreateLine() Dim doc As Document = Application.DocumentManager.MdiActiveDocument Dim db As Database = doc.Database Using trans As Transaction = db.TransactionManager.StartTransaction() Dim blkTbl As BlockTable = trans.GetObject(db.BlockTableId, OpenMode.ForRead) Dim msId As ObjectId = blkTbl(BlockTableRecord.ModelSpace) Dim msRec As BlockTableRecord = trans.GetObject(msId, OpenMode.ForWrite) trans.Commit() End Using End Sub

这里的Application.DocumentManager.MdiActiveDocument相当于 VBA 里的 ThisDrawing,拿到当前文档对象。db.TransactionManager.StartTransaction()开启事务,所有对图纸数据的改动都要在事务里进行。trans.Commit()是提交动作,不调用它,代码执行中做的修改不会真正回写到图纸。代码里只演示了打开块表记录,还没添加实体,已经足够验证命令链路。用到真实绘图时,在Using块内添加直线或图块对象,最后统一 Commit。

很多正式插件会在这里引入 WinForm 对话框或者命令行交互窗口,考虑到加载和编译的复杂度,新团队建议先从无界面命令开始,跑通一条闭环再逐步增加界面。

3.3 编译、NETLOAD 加载与路径问题

编译得到 DLL 后,在命令行输入NETLOAD,选择对应的 DLL,就能把插件加载到当前进程中。加载成功后再输入命令名CreateLine,如果看到命令成功执行,整条开发链路已经打通。初次使用时两个路径问题出现频率最高:

一是 DLL 依赖缺失。VB.NET 工程往往引用了多个类库,但 NETLOAD 只加载你选中的 DLL,依赖库不会自动跟着进来。解决方法是把工程输出目录里的所有 DLL 复制到同一个固定文件夹,再从这个文件夹统一加载。

二是路径变动问题。DLL 加载成功后,如果移动了位置,下次 NETLOAD 可能加载失败,因为运行时缓存了旧的程序集路径。这种情况下重启软件再加载即可。我习惯把每个插件放在独立目录,路径里避免中文和空格,能省掉不少编码相关的怪问题。

4. 实例拆解:图层、图块与批量导出三个高频场景

4.1 批量整理图层:遍历、统计、删除空层

接手别人的图纸,最常见的混乱就是图层泛滥。光靠人工检查图层列表,效率极低,还容易误删正在使用的图层。代码实现的正确思路不是直接调图层的“实体数量”属性,而是反向遍历模型空间里的所有实体,按图层名统计计数,再决定删或不删。这一步虽然多绕了一点路,但统计结果和当前图纸实际状态完全一致。

Sub CountEntitiesPerLayer() Dim doc As AcadDocument Set doc = ThisDrawing Dim ent As AcadEntity Dim counts As Object Set counts = CreateObject("Scripting.Dictionary") For Each ent In doc.ModelSpace If counts.Exists(ent.Layer) Then counts(ent.Layer) = counts(ent.Layer) + 1 Else counts.Add ent.Layer, 1 End If Next Dim key As Variant For Each key In counts.Keys Debug.Print key & ": " & counts(key) Next End Sub

代码先用Scripting.Dictionary创建字典,把图层名作为键,实体数量作为值。每遍历到一个实体,取出它的 Layer 属性,如果字典里已经存在这个层名就加一,否则新增键。最后输出到立即窗口。这个思路适用于模型空间,但图纸如果包含多个布局,还需要把布局里的块表记录也加进来,否则数量偏差会让你误删布局中仍在使用的图层。

这里必须强调一个删除原则:不要在遍历集合的同时直接删除图层。因为遍历过程中集合结构发生了变化,会触发“集合已修改”的错误。正确做法是先在字典里筛出数量为零的图层名,存入数组,遍历结束之后再进入第二步,用数组里的名字执行删除。两步看似绕路,实际是在保护代码不崩溃。

4.2 插入图块并读取属性:BlockReference 的两个阶段

图块操作是 CAD 二次开发里出现频率最高的需求,但它天然包含两个独立阶段:先创建块引用,再读取或修改块引用的属性。很多样板代码把两个阶段混在一段示例里,读者抄的时候就不清楚到底哪一步是必需的。

插入块引用用 InsertBlock 方法,五参数齐全才能正确执行:

Sub InsertBlockDemo() Dim insPt(0 To 2) As Double insPt(0) = 0 insPt(1) = 0 insPt(2) = 0 Dim blkRef As AcadBlockReference Set blkRef = ThisDrawing.ModelSpace.InsertBlock(insPt, "CHAIR_01", 1#, 1#, 1#, 0#) MsgBox "块名: " & blkRef.Name & " 插入点: " & blkRef.InsertionPoint(0) & ", " & blkRef.InsertionPoint(1) End Sub

InsertBlock 的参数从左到右分别是插入点坐标、块名、X 比例、Y 比例、Z 比例、旋转角度。第二个参数是块名而不是磁盘文件名,这在调试时最具迷惑性:图纸里没有定义 CHAIR_01 这个块,代码会直接报错,很多人误以为是路径写错了。带属性的图块插入后,还需要调用 GetAttributes 拿到属性数组,数组顺序与块定义中属性标记的顺序一致,赋值后必须调用 Update 方法,图纸画面才会刷新。

属性读取有一个经典陷阱:属性数量与预期对不上。原因是有些标记被定义成了固定文本或者本身不可见,它占用数组元素,但值内容为空。按下标访问属性时容易拿到空数据。稳妥做法是把属性标记名一并遍历出来,再根据标记名去匹配值,而不是硬编码下标。

4.3 把图元数据导出为 CSV:与外部系统交换数据

批量导出坐标和图块信息,是二次开发里最能直接看到效益的场景。比如把图纸里所有门窗图块信息生成采购清单,手动抄一整天才做得完,代码十几秒就能输出一个 CSV。VBA 调用系统文件组件写文本文件非常直接:

Sub ExportBlockToCsv() Dim fso As Object Set fso = CreateObject("Scripting.FileSystemObject") Dim outPath As String outPath = Environ("TEMP") & "\block_export.csv" Dim f As Object Set f = fso.CreateTextFile(outPath, True) f.WriteLine "块名,X坐标,Y坐标" Dim ent As AcadEntity For Each ent In ThisDrawing.ModelSpace If TypeName(ent) = "AcadBlockReference" Then Dim blk As AcadBlockReference Set blk = ent f.WriteLine blk.Name & "," & blk.InsertionPoint(0) & "," & blk.InsertionPoint(1) End If Next f.Close MsgBox "导出完成: " & outPath End Sub

Environ("TEMP")返回系统临时目录,用它拼路径可以避免硬编码不存在的文件夹。CreateTextFile的第二个参数为 True 表示当目标文件已存在时直接覆盖。循环里TypeName(ent) = "AcadBlockReference"用来过滤出图块引用,非图块实体不会写进文件。CSV 默认用逗号分隔,但块名如果本身包含逗号,下游打开表格软件时就会错列。我在真实项目里通常会用竖线作为分隔符,或者在拼接前做一次转义替换。

这个实例扩展空间很大:加一个图层名判断,就变成“只导出某层图块”;把 GetAttributes 的结果一起拼进文件,就变成了带属性明细的清单。资源实例里常在循环和拼接两个位置做注释,这两处正是后续所有定制需求最集中的地方。

5. 避坑与排查:四个让开发者反复翻车的故障

5.1 运行时错误 91:对象变量未设置

现象:VBA 宏运行到一半,弹窗提示“运行时错误 91:对象变量或 With 块变量未设置”,代码高亮停在某一行,后面全部不执行。

原因:通常是在对象还没有赋值的情况下就访问属性,或者当前文档上下文不对。比如在普通模块里直接引用 ThisDrawing,但当前激活文档并不是预期的那一个;还有一种常见情况是 Set 语句返回失败,后面还接着用这个空对象。

解决:对涉及外部对象的位置都加一份空值判断,在 Set 之后写If obj Is Nothing Then Exit Sub,防止空对象继续传递。多文档场景下,先取文档引用,再赋给一个局部变量,全程序统一用这个变量,排查起来方便得多。调试时按 F8 单行逐句执行,哪一行高亮,问题就在哪一行前后。

5.2 图形没变化:事务没有提交

现象:VB.NET 命令执行完毕,命令行没有报错,但图纸上一个图元也没出现,或者修改图层后切到其他标签页再回来,改动全部消失。

原因:对图纸对象的修改发生在事务里,这是托管 API 强制要求的规则。代码开了事务,但始终没有调用trans.Commit(),事务结束时被系统回滚,改动自然不保留。还有另一种原因是用OpenMode.ForRead打开了对象,想改数据却没有申请写权限。

解决:在事务末尾显式调用 Commit,并且对于所有准备修改的对象,用OpenMode.ForWrite打开。如果看到提交位置永远执行不到,就在 Commit 前加一行命令行输出,先把代码执行的路径确认清楚。开发机上跑批处理时,养成“改完立马提交”的肌肉记忆,能减少一大半操作无效的困惑。

5.3 坐标偏了:图纸坐标系的隐性转换

现象:手动画一条线在某个位置,代码画出来的却偏移了一段固定距离;或者在当前视图里点选坐标传入脚本,生成的图元落点不对。

原因:代码里传入的坐标值默认是世界坐标系 WCS,而当前视图可能处于用户坐标系 UCS 下。新手很容易直接取屏幕显示的坐标塞进 AddLine,这两个坐标系之间的差距就成了偏移量。

解决:在绘图代码里先确认当前 UCS 状态,最直接的方式是执行命令把坐标系切回世界坐标系再运行测试,确认问题是否由坐标系引起。真实项目里不能依赖修改用户环境,正规做法是用接口提供的坐标转换方法,把 UCS 坐标换算成 WCS 后再传给绘图 API。这个坑花五分钟排查,能避免误判成“电脑有问题”的长期困惑。

5.4 VBA 模块在 64 位环境无法加载

现象:双击旧的 .dvb 文件没有反应,VBA 管理器里加载宏提示组件未注册,或者宏能打开但一运行就报找不到库。

原因:新版 CAD 已经是 64 位进程,旧的 VBA 环境和部分第三方 ActiveX 控件还是 32 位的,无法被 64 位进程正常加载。这个问题分两层:第一层是 CAD 不再默认带 VBA 编辑器,必须补装匹配位数的组件;第二层是历史工程里引用了老控件,这些控件没有 64 位版本,导致工程整体加载失败。

解决:先确认补装的组件版本与 CAD 位数一致,再检查 .dvb 工程引用,把所有第三方控件从工程里逐一移除,用数组、集合这些内置结构替代。如果旧工程引用的控件太多,并出现无法绕开的问题,不要在这里死磕,直接把功能迁移到 VB.NET。迁移时保留原有业务逻辑,界面和控件层重写,工作量通常是可控的。

6. 把示例变成自己的工具:调试习惯与效率边界

6.1 给代码留一条调试后门:文件日志

批量命令最怕跑一半报错,又不知道崩在哪一步。我通常会在每个关键操作前追加一行日志,写入系统临时目录下的文本文件:

Open Environ("TEMP") & "\cad_debug.log" For Append As #1 Print #1, "开始插入块: " & blkName Close #1

运行结束后打开日志文件,哪一行是最后一条,问题就在那一行附近。这个方法比弹窗提示框稳定得多,不会因为弹窗阻塞批处理流程,也能留下历史记录对比每次运行差异。

6.2 给命令加参数校验与进度提示

开发机上跑通一次,不代表工具可以交出去。使用者的图纸往往千奇百怪,没有活动文档、选择集为空、图层改名都是常态。所以每条命令开头我都习惯先判断文档对象,为空就直接退出;批量循环里每处理一批就调用一次界面刷新方法,同时把进度写到命令行,避免界面长时间无响应被误判为死机。这些校验看似繁琐,实则是工具从“自用”到“交付”的分水岭。

6.3 批处理性能边界:一次事务做完一件事

VB.NET 批量修改上万个图元时,最常见的性能瓶颈不是循环本身,而是循环里反复开启事务。事务本身要和数据库层交互,一万个图元开一万次事务,运行时间会被放大到不可接受。正规做法是用一个事务包住整个批量修改,把需要读取的数据提前缓存到内存,写操作排队在同一个事务内完成。VBA 虽然没有显式事务,但同理不能在循环里反复调用视图缩放或强制重绘,这类操作会触发界面刷新,把性能拖垮。

资源里最后一个实例通常会示范这种“一批一次”的写法。我当年写批量更新图块属性时,没注意事务边界,一千多个图块跑了十几分钟,去掉循环内的事务重复创建后,十几秒就完成了。从那以后,我每次写批处理代码都会强制检查一遍事务层级和循环内的刷新调用,数量一上来,这步检查省下的时间相当于给交付的稳定性和运行效率都添了一份保障。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进