CAD-VBA二次开发:从工程加载到批量出图

简介:《CAD-VBA开发人员手册》面向已具备一定AutoCAD基础、希望用VBA开展二次开发的工程技术人员与制图开发者,定位于打通从对象模型认知到实际自动化编程的知识链条。全书十章,从嵌入与全局VBA工程、VBA IDE与宏管理讲起,逐层深入ActiveX自动操作、对象层次与集合访问、图形环境控制,再到图元创建编辑、标注与公差、菜单工具栏定制、事件处理、三维坐标与实体编辑、布局视口与打印,最后收于光栅图像和块属性等高级组织技术,目录结构由浅入深,便于按主题查阅。资源包内为1个pdf文件,大小约3.16MB,单册即覆盖全部章节内容,适合随身查阅与集中通读。目前已有2492人学习下载,可作为AutoCAD VBA二次开发的常备参考手册,帮助读者理解对象模型关系、掌握系统变量与命令行调用、写出可响应事件并规范出图的自动化脚本。

1. 一份手册解决的不是语法问题,是"图纸怎么被自动改"

上周有个做工艺的朋友找我,说他们部门要在一批老图上统一替换图框、把明细栏数据导成表格、再给上百张图的标题栏填同一个日期。手工做要三天,问我能不能写个脚本。我问他会不会 VBA,他说会写循环和判断,但一打开 CAD 就不知道从哪儿下手。这就是 CAD-VBA 二次开发最常见的卡点:语法不是门槛,对象模型、工程加载方式、图纸上下文才是门槛。这份《CAD-VBA 开发人员手册》正好压在这个位置上,十章内容从工程怎么组织、宏怎么跑,一路讲到图元、标注、菜单、事件、三维和打印。适合两类人:会用 CAD 画图、想把重复动作变成一键执行的绘图员;以及接了别人留下的 dvb 工程、需要读懂并维护的开发者。

2. 手册骨架拆解:十章目录其实是一条从"能跑"到"能交付"的流水线

2.1 十章内容的四层结构

很多人拿到这种几百页的手册,习惯从第一页顺着读,读到第三章就断了。我一般先看目录的分层逻辑,再决定哪几章精读、哪几章当字典查。这份手册的十章不是并列关系,是四层递进:

层级章节解决的核心问题落地产出
第一层 起步第 1 章 VBA 入门工程存哪儿、宏怎么运行、IDE 怎么用一个能被 VBARUN 调起来的空宏
第二层 地基第 2 章 ActiveX 自动操作基础对象模型、集合、属性方法、父对象、变体能遍历并操作图纸里的图元
第三层 主体第 3~5 章 环境控制 / 图元 / 标注图形开关、系统变量、建模、图层文字、尺寸公差一个能改图的完整业务宏
第四层 交付第 6~10 章 菜单工具栏 / 事件 / 三维 / 布局打印 / 块与光栅界面、事件响应、三维坐标、出图、块属性同事愿意点的按钮和稳定响应的工具

按这个分层,第 1、2 章必须逐字读,因为后面所有代码都建立在这两章的概念上;第 3~5 章要跟着敲,是真正的产能区;第 6~10 章可以先读标题,等真要做工具栏、要做批量出图的时候再回来查。第 2 章里"在数据库中返回第一个图元""在方法和属性中使用变体"这种小节,名字看着枯燥,实际上是后面所有 For Each 循环和 AddLightWeightPolyline 调用的前提,跳过它们,代码就会在类型不匹配上报错。

2.2 嵌入工程与全局工程:两种形态决定分发方式

第 1 章第一节讲的就是这个,但它的重要性被严重低估了。VBA 工程在 CAD 里有两种存在形式,选错了,后面怎么调都不顺。手册给的定义很清楚:工程是代码模块、类模块和窗体的集合,可以保存在图形里,也可以作为独立的 dvb 文件保存。

对比项嵌入工程全局工程
存放位置图形文件内部独立 dvb 文件
加载时机图形打开时自动加载需 VBALOAD 手动加载
能否开关图形不能能
能否跨图运行不能,只作用于自身文档能,作用于当前活动文档
典型用途随图分发的专用宏,如图形打开即记录时间日志通用工具库,如跨多图汇总材料清单

这张表我建议直接抄进项目笔记。选型的判断标准就一句话:宏要不要跟着图走。要给甲方交付、打开就生效的,做嵌入工程;要在自己机器上跨几十张图批量处理的,做全局工程。手册里提到一个细节容易被忽略——同一时刻可以同时加载嵌入工程和全局工程,而且全局工程引用的其它工程会自动连带加载。这意味着你可以把公共函数拆成一个底层 dvb,被多个业务 dvb 引用,改一处全部生效。

还有一个必须记住的约束:一个图形在同一时间只能包含一个嵌入工程。想把别人的工程塞进已经带工程的图里,必须先分离再嵌入,分离时会问你要不要导出成 dvb,选"否"的话工程数据直接消失,没有后悔药。

2.3 命令与工具链:五个入口把工程跑起来

手册第 1 章末尾专门"回顾 CAD VBA 命令",这几个命令看着零碎,实际是日常操作的骨架。命令行的入口比菜单快,尤其是调试阶段反复加载卸载的时候:

' 以下为命令行输入,不是代码,仅作对照说明 ' VBAMAN —— 打开 VBA 管理器:加载、卸载、嵌入、分离、新建、另存 ' VBALOAD —— 直接弹出"打开 VBA 工程"对话框,选 dvb 加载 ' VBAUNLOAD —— 卸载指定工程,释放内存 ' VBARUN —— 打开宏对话框,列出当前有效范围内的所有宏 ' VBAIDE —— 打开 VBA 交互开发环境 ' VBASTMT —— 在命令行直接执行一条 VBA 语句,适合临时验证

参数和用法上要注意几点。VBALOAD 加载后会连带加载该工程所引用的其它工程,所以如果你只是想临时跑一个函数,反而会把一串依赖拉进来,卸载时要按引用顺序倒着卸。VBARUN 的宏对话框有个"宏位置"下拉框,可以筛选"所有图形和工程 / 所有图形 / 所有工程 / 当前图形 / 当前工程",图纸多了以后这个筛选比翻列表快得多。VBAIDE 还有一个隐藏用法:想让它随 CAD 启动自动打开,需要在启动加载的 arx 配置里加入 VBA 运行库那一行,手册第 1 章第 4 节写了具体写法,适合长期做开发、不想每次手动敲 VBAIDE 的人。

至于每回加载工程时弹出的"启用/禁用代码"提示,这不是故障,是宏保护机制在起作用。选禁用工程照样加载,但里面所有代码都不执行。团队分发 dvb 时要提前跟使用者说清楚,否则对方会以为你的工具坏了。

3. ActiveX 对象模型:从 Application 一路摸到 ModelSpace 里的图元

3.1 对象层次与父子关系

第 2 章是整本手册的地基,讲的是 ActiveX 自动操作。它的核心只有一件事:CAD 里的一切都是对象,对象之间有严格的父子关系。顶层是 Application,往下是 Documents 集合,再往下是 Document,Document 里分出 ModelSpace 和 PaperSpace 两个集合,集合里装着 Line、Circle、Text、Dimension 这些图元。每个对象都有 Parent 属性指回上一层,手册第 2 章第 5 节专门讲了这个,调试时非常有用——拿到一个来路不明的对象,打印它的 Parent 和 ObjectName,立刻知道自己在哪一层。

在 VBA 里还有一个内置对象 ThisDrawing,它的指向规则是新手最容易搞混的地方:嵌入工程里,ThisDrawing 指向包含该工程的图形;全局工程里,ThisDrawing 指向当前活动文档。所以同一个宏,做成嵌入工程和做成全局工程,行为可能完全不同。我一般的做法是不管哪种形态,都显式写 Application.ActiveDocument,把依赖关系摆在明面上。

定位类型库这一节也值得单独看。早期绑定(在引用对话框里勾选类型库)能获得代码补全和常量提示,但换一台机器、CAD 版本不同,就会报"找不到工程或库";后期绑定用 CreateObject 拿对象,兼容性好但没提示、常量要自己定义。手册第 2 章第 6 节和第 9 节把这两条路的取舍讲清楚了,还提到可以用其它程序语言通过 ActiveX 调 CAD,思路是一样的。

3.2 集合遍历与图元创建

看懂了层次,第一段能干活的代码就能写出来了。下面这段遍历当前图形模型空间里所有图元,按类型分类统计,是所有批量处理的起点:

Sub CountEntities() Dim ent As AcadEntity Dim lineCnt As Long, circleCnt As Long, textCnt As Long ' ModelSpace 是集合对象,Count 属性给出图元总数 For Each ent In ThisDrawing.ModelSpace ' TypeName 返回对象的实际类型名,比 ObjectName 更稳 Select Case TypeName(ent) Case "AcadLine": lineCnt = lineCnt + 1 Case "AcadCircle": circleCnt = circleCnt + 1 Case "AcadText", "AcadMText": textCnt = textCnt + 1 End Select Next ent MsgBox "直线 " & lineCnt & " 条,圆 " & circleCnt & " 个,文字 " & textCnt & " 处" End Sub

这段代码的参数和逻辑要拆开说。ent 声明为 AcadEntity,是所有图元的基类型,用它可以遍历任何对象;如果确定只处理直线,声明成 AcadLine 能直接用上专有属性。TypeName 返回的是运行时的实际类型,比读 ObjectName 属性可靠,因为 ObjectName 在某些代理对象上返回值不带 Acad 前缀。Select Case 里把 AcadText 和 AcadMText 合并统计,是因为单行文字和多行文字在图纸里经常混用,只统计一种会漏。

创建图元走的是集合的 Add 系方法,返回值就是新建的对象。手册第 4 章第 1 节列了一大批,最常用的是 AddLine、AddCircle、AddLightWeightPolyline。轻量多段线要传变体数组,这是第 2 章"在方法和属性中使用变体"那一节的直接应用:

Sub DrawPolyline() ' 点坐标按 x1,y1,x2,y2,... 顺序平铺成一个双精度数组 Dim pts(0 To 5) As Double pts(0) = 0: pts(1) = 0 pts(2) = 100: pts(3) = 0 pts(4) = 100: pts(5) = 60 Dim pl As AcadLWPolyline Set pl = ThisDrawing.ModelSpace.AddLightWeightPolyline(pts) pl.Closed = False ' 是否闭合 pl.Layer = "0" ' 落到哪个图层 pl.Update ' 刷新显示,批量创建时建议最后统一调 End Sub

pts 数组长度必须是偶数,索引 0、1 是第一个点的 X、Y,没有 Z。这一点和第 8 章的三维坐标是两条路:轻量多段线只在 XY 平面上,要做三维得用 Add3DPoly 或三维实体。pl.Update 在单条创建时可以省,但批量生成几百个图元时不加这句,界面会一直不刷新,看起来像卡死,这是很典型的一个体验坑。

3.3 图层、颜色、线型、文本的组织

第 4 章第 3 节讲图层、颜色和线型,第 4 节讲添加文本。这两节合起来解决的是"图元画出来了但不像一张正经图"的问题。图层通过 Layers 集合操作,Add 方法返回图层对象,之后设置 Color 和 Linetype。颜色用常量(acRed、acYellow 这类)比记数字好,因为索引色号在不同版本里容易对不上。

Sub MakeLayer() Dim lay As AcadLayer ' 同名图层已存在时 Add 会报错,先查再建 On Error Resume Next Set lay = ThisDrawing.Layers.Add("BATCH_DIM") On Error GoTo 0 If Not lay Is Nothing Then lay.color = acRed ' 图层默认颜色 lay.Linetype = "CONTINUOUS" ' 线型必须先加载到当前图形 End If ' 把图层设为当前层,之后新建的图元默认落在这一层 ThisDrawing.ActiveLayer = lay End Sub

这里的坑集中在两处。Layers.Add 遇到重名直接抛错,所以先 On Error Resume Next 拦一下,或者用遍历判断。Linetype 赋值失败通常不是名字写错,而是线型还没从线型库加载进当前图形,得先用 Linetypes 集合的 Load 方法加载。至于文字,AddText 的第一个参数是插入点,第二、三个是文字内容和高度,中文内容要确认文字样式里指定的字体支持中文,否则图纸上出现一串问号,排查半天以为是编码问题。手册第 4 章第 4 节把 Text 和 MText 分别讲了,段落式的长注释用 MText 更合适,行距和对齐都好控制。

4. 交互、标注与界面:把一段脚本做成同事愿意点的按钮

4.1 用户输入与命令行交互

脚本能跑和自己用是两回事。给同事用,第一件事是把写死的参数变成运行时输入。手册第 3 章第 8 节讲提示用户输入,核心是 Utility 对象下的 GetString、GetPoint、GetReal、GetInteger 几个方法,每个都有 Prompt 参数做提示语,返回值直接进变量。

Sub PickTwoPoints() Dim p1, p2 Dim basePt As Variant, offVal As Double ' GetPoint 的第二个参数 BasePoint 会影响橡皮筋的起点 p1 = ThisDrawing.Utility.GetPoint(, "指定基准点:") p2 = ThisDrawing.Utility.GetPoint(p1, "指定方向点:") ' 空参数位用 , 占位,Prompt 参数可以带换行符 \n offVal = ThisDrawing.Utility.GetReal("输入偏移距离(mm):") If offVal <= 0 Then MsgBox "距离必须大于 0" Exit Sub End If End Sub

这段的细节全在参数上。GetPoint 的第一个参数是可选的 BasePoint,空着就写个逗号,不写逗号会编译报错,新手经常在这里卡住。第二个参数是提示字符串,可以用 Chr(10) 或者 vbCrLf 拼接多行提示,把可选参数和默认值写进去。用户在命令行按下 Esc 时,GetPoint 会抛出一个用户取消的错误,如果整个过程写在长流程里,得在入口加 On Error 处理,否则整段宏直接中断,前面已经画好的东西留在图里,成了半成品。

第 3 章第 9 节讲访问命令行,用 SendCommand 可以把原生命令串起来。它适合"我自己的代码搞不定、但 CAD 原生命令能搞定"的场景,比如批量炸开块。但 SendCommand 是异步的,发出去就返回,紧接着读取结果会拿到旧数据,必须等命令结束。这个特性在循环里尤其危险,一条没执行完就发下一条,结果是图纸乱套。

4.2 标注与公差的批量生成

第 5 章是标注专题,从概念、创建、编辑讲到标注样式、模型空间与图纸空间标注、引线和形位公差。标注最麻烦的地方不是画线,是让尺寸落进正确的样式,否则标出来的字高、箭头、精度全都跟公司制图标准对不上。

Sub BatchDim() Dim p1, p2, txtPt Dim dimObj As AcadDimAligned ' 创建前先切到目标标注样式 ThisDrawing.ActiveDimStyle = ThisDrawing.DimStyles.Item("STANDARD") p1 = ThisDrawing.Utility.GetPoint(, "第一个尺寸界线原点:") p2 = ThisDrawing.Utility.GetPoint(, "第二个尺寸界线原点:") txtPt = ThisDrawing.Utility.GetPoint(, "尺寸线位置:") ' 对齐标注:两个定义点 + 文字位置,共三个参数,都是变体点 Set dimObj = ThisDrawing.ModelSpace.AddDimAligned(p1, p2, txtPt) dimObj.Update End Sub

AddDimAligned 是三点式:两条尺寸界线的原点加上尺寸文字的位置。线性标注用 AddDimRotated,多一个旋转角参数,控制尺寸线方向。手册特别提醒了模型空间和图纸空间的差别——在图纸空间标,尺寸值反映的是视口缩放后的实际长度,如果视口比例没设对,标出来的数字是错的,这种错误很难在图纸上肉眼发现。形位公差用 AddTolerance,第一个参数是公差符号串,第二个是插入点,符号串有固定语法,写错了会报错而不是显示错。

4.3 菜单、工具栏与事件驱动

第 6 章和第 7 章是这套手册从"脚本集"升级到"小产品"的转折点。第 6 章讲 MenuBar 和 MenuGroups 集合的区别,前者管的是 CAD 自带菜单条,后者管的是用代码加载进来的自定义菜单组。实际做法是把菜单和工具栏做成一个独立的菜单组文件,启动时用 MenuGroups.Load 加载,卸载用 Unload。第 3 章讲的工具栏、状态栏帮助文字、右键菜单条目都在这一章里。

Sub BuildMenu() Dim grp As AcadMenuGroup Dim pop As AcadPopupMenu Dim item As AcadPopupMenuItem ' Load 返回菜单组对象,路径指向编译好的菜单文件 On Error Resume Next Set grp = ThisDrawing.Application.MenuGroups.Load("C:\tools\batch.mnu") On Error GoTo 0 If grp Is Nothing Then Exit Sub ' 在菜单组里新增一个下拉菜单 Set pop = grp.Menus.Add("批量工具") ' 菜单项绑定宏名,宏名要和模块里的 Public Sub 完全一致 Set item = pop.AddMenuItem(pop.Count, "图框替换", "-VBARUN 图框替换") item.HelpString = "批量替换当前目录所有图纸的图框" End Sub

参数上有两个硬要求:Menus.Add 返回的下拉菜单对象,其 AddMenuItem 第一个参数是位置索引,从 0 开始排;第三个参数是动作字符串,运行时宏要写成-VBARUN 宏名的形式,前面那个减号是让命令在命令行静默执行,去掉就会弹出宏对话框。HelpString 的内容会显示在状态栏,团队里人多的时候,这行提示比任何说明文档都管用。

事件是第 7 章的内容,分应用程序级、文档级、对象级三层。用的时候必须先在工程里插一个类模块,在类模块顶部用 WithEvents 声明 app 或 doc 对象,然后在普通模块里 Set 赋值,事件才会触发。类模块里写下对应的回调过程,比如 Document_Open、BeginSave 这类名字。对象级事件的坑最深,它不像前两层那样写个回调就生效,某些对象还需要额外的连接步骤,手册第 7 章第 5 节专门讲了,第一次接触很容易写完了发现根本不触发,白白怀疑半小时。

5. 排查清单:CAD 里跑 VBA 最容易翻车的五个地方

5.1 工程加载与宏可见性

现象一:双击 dvb 文件打不开,或者 VBA 管理器里明明加载了,宏对话框里却找不到自己的宏。

原因通常是两层。第一层是 dvb 不是双击就能运行的文档,它是工程文件,必须通过 VBALOAD 或者 VBA 管理器加载进 CAD 进程。第二层是宏的签名要求:只有声明为 Public 且不带参数的过程才会出现在宏列表里,写成 Private Sub 或者带输入参数的 Function,VBARUN 根本看不见。

解决方式很直接:确认过程声明是 Public Sub 名称(),然后改走 VBAMAN 或 VBALOAD 加载,加载完再用 VBARUN 的宏位置下拉框筛选"所有工程",把列表收窄。如果希望工具随 CAD 启动就有,就把 dvb 放到搜索路径下用启动方式加载,而不是每次手动点。

现象二:工程加载时弹出启用/禁用代码的选择框,同事选了禁用,回来反馈工具没反应。

原因就是宏保护机制,禁用状态下工程照样加载,但所有代码都不执行,界面上看不出任何报错。这不是 bug,但分发体验很差。

解决要靠沟通而不是代码:给团队发工具时把这一条写进说明第一行,让对方选"启用"。另外尽量不要在启用前那段欢迎提示里塞太多无关内容,用户点得快,容易顺手选错。

5.2 引用、坐标与集合遍历

现象三:自己机器上跑得好好的宏,拷到同事电脑上编译就报"找不到工程或库"。

这是早期绑定最典型的血泪经验。工程属性里勾选的类型库带版本号,两台机器的 CAD 版本差一级,引用就对不上,编译直接失败。

解决有两条路。要长期维护的,把引用改成最低公共版本,并在交付说明里写清依赖;要图省事的,改成后期绑定,用 CreateObject 拿 Application 对象,常量自己定义成 Integer,牺牲代码提示换兼容性。我一般公共库走后期绑定,本机专用工具走早期绑定。

现象四:在自定义用户坐标系里用 GetPoint 取点,画出来的图元位置整体偏了一大截。

原因是 GetPoint 返回的坐标和用户当前看到的坐标系读数并不在同一个体系里,图纸一旦建了自定义 UCS,直接拿返回值去建图元就会错位。

解决是用 Utility.TranslateCoordinates 做显式转换,把点从 UCS 转到 WCS 再传入 Add 系方法。转换时要做两次调用,一次从目标系转到原点系,一次从原点系转到目标系,手册第 8 章第 3 节写了完整用法。涉及三维和自定义坐标系的项目,这一步不能省。

现象五:For Each 遍历模型空间,边遍历边删除,结果删了一半就停了。

原因是 For Each 依赖集合的枚举器,遍历过程中集合被修改,枚举器状态失效,后面的图元被跳过。这个坑在批量清理图元、批量删旧标注的场景里出现频率最高。

解决是换成倒序索引循环:

Sub DeleteByLayer() Dim i As Long, ent As AcadEntity Dim ms As AcadModelSpace Set ms = ThisDrawing.ModelSpace ' 必须倒序,正序删除会因索引前移而漏删 For i = ms.Count - 1 To 0 Step -1 Set ent = ms.Item(i) If StrComp(ent.Layer, "TEMP", vbTextCompare) = 0 Then ent.Delete End If Next i End Sub

倒序的理由很实在:删掉第 i 个以后,后面的元素整体前移一位,正序循环的下一次 i+1 就跳过了原来的第 i+1 个。倒着删,前面没处理的索引始终有效。Count 属性在每次循环都取一次,所以循环变量要从 Count-1 开始,而且集合被修改后长度会变,用固定上限反而安全。StrComp 加 vbTextCompare 是为了避开图层名大小写差异,图纸从别人那儿拷过来,图层名大小写经常不统一。

6. 二次开发改造:把手册示例拼成能交付的插件

照着手册敲完示例,下一步要面对的是怎么把它们拼成一个别人敢用的工具。我的做法是先分层,再谈功能。第 1 层的入口模块只放 Public Sub 和菜单绑定,负责参数收集和错误兜底;第 2 层的业务模块按动作拆,图框替换、明细提取、批量标注各占一个模块,互不调用;第 3 层的工具模块放坐标转换、图层创建、日志写入这些被反复用到的东西。这样拆完,改一个业务不动其它业务,公共逻辑只改一处。

模块职责手册对应章节
modEntry菜单绑定、输入收集、错误捕获第 3、4、6 章
modFrame图框替换、标题栏填充第 4、10 章(块与属性)
modBOM明细表提取、导出第 2、4 章
modDimBatch批量标注、公差第 5 章
modUtil坐标系转换、图层、日志第 1、2、8 章

几个落地上真正管用的技巧。参数不要写死在代码里,图框块名、字体样式名、输出目录这些做成一张配置表或者一个文本文件,读进来再跑,换项目不用改代码。第 10 章的块与属性是图框替换的核心,标题栏做成带属性的块,代码只改属性值不碰几何图形,比炸开重画稳得多。错误处理要加在最外层入口,用 On Error GoTo 统一跳到清理段,把已经打开的文档关掉、把临时图层删掉,避免跑一半留下垃圾图形。日志别用 MsgBox 堆,写到一个文本文件里,跑完一次性报告"处理 37 张,跳过 2 张,原因第 12 行"。

验证方法我是这么走的:先用一张只读的样例图跑单功能,确认图元落在正确位置;再用十张小图连续跑,看有没有工程内存泄漏导致越来越慢;最后用真实图纸整批跑一遍,跑完拿原始文件对照差异。第三步最不能省,我在这一步见过标注样式被意外改掉、图层被全部置灰的情况,原因是某些属性赋值会波及 ActiveDimStyle 这类全局状态。从那以后我每次交付前,都会把所有会改全局状态的地方集中到一个 Sub 里,跑完强制还原一遍。希望帮到你。

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