简介:《CAD-VBA开发人员手册》PDF面向使用 VBA 进行 AutoCAD 二次开发的初学者与进阶开发者,帮助读者从宏编写、工程组织起步,逐步掌握对象模型、图形环境控制和自动化项目开发。全书十章,内容覆盖嵌入与全局 VBA 工程、ActiveX 对象层次与集合操作、图形打开保存及系统变量、图元创建编辑、标注与公差、菜单工具栏定制、事件处理、三维坐标与实体编辑、布局打印,以及光栅图像、块和属性等高级绘图组织技术,目录体系完整,适合按章节系统学习或作为开发时的查阅手册。资源包共1个 PDF 文件,大小约3.16MB,单文件便于离线阅读和检索。已有2492人学习,说明其在 CAD 二次开发入门与实战参考中具有一定认可度。读者可从中获得从 VBA IDE 使用、对象模型理解到界面定制、事件响应和图纸输出的完整知识线索,也能借助章节结构快速定位常见开发问题,适合需要提升 AutoCAD 自动化脚本与插件开发能力的技术人员。
1. 老图纸里那堆 .dvb:CAD 二次开发最省力的一条路
机房旧目录里翻出一堆.dvb,打开图纸时弹一句“启用宏?”,多数人点禁用就过去了。这些文件背后是一整套 VBA CAD 二次开发的做法:用代码直接操作图形数据库,批量改图层、批量标注、批量出图,不必重写一遍绘图流程。这份手册按十章铺开,从工程装载、对象模型、系统变量、图元增删改,一直讲到标注公差、菜单工具栏、事件、三维坐标和打印布局。它面向的是手上有重复劳动、想用宏解决的人:每天要把几十张图的图框挪一遍,或者要把一堆坐标点自动连成多段线。它也适合已经会写点 VBA、但一碰到ThisDrawing就发懵的人。读完不能立刻变成高手,但能让你从“宏是什么”走到“我的宏能在自己图纸上跑完不报错”。
2. 从 .dvb 到第一个能跑的宏:工程形态、IDE 与部件进出
先把一个认知放在前面:VBA 代码在 CAD 里不是一段孤立脚本,它必须依附在某个“工程”上。工程是代码模块、类模块和窗体的集合,这点和 VB6 时代一脉相承。搞不清工程的归属,后面所有调试都会变成玄学。英文界面里它是 VBA Project,中文界面里叫 VBA 工程,两者是同一回事。
2.1 嵌入工程和全局工程:先选形态再动手
手册开篇就把工程分成两类,这个划分决定了你后面能做什么、不能做什么。
嵌入工程保存在图形文件内部,图形打开时自动装载。它最舒服的地方是零分发成本:把 dwg 发给别人,宏跟着走,对方不需要事先找到某个.dvb再装载。代价也很明确,嵌入工程的函数作用域被限制在自己所在的文档里,它不能去打开或关闭其他图形。一个图形在同一时刻只能挂一个嵌入工程,想换一个必须先分离。
全局工程保存为独立的.dvb文件,能跨图形运行,也能自己去开图关图,适合做成通用工具库。但它不会自动装载,除非你把文件命名为acad.dvb并放到搜索路径里,CAD 启动时会自动把它拉起来。这也是很多老项目里那个神秘acad.dvb的来历。
| 对比项 | 嵌入工程 | 全局工程 |
|---|---|---|
| 存储位置 | 图形文件内部 | 独立 .dvb 文件 |
| 随图打开自动装载 | 是 | 否(除非命名 acad.dvb) |
| 能否开关其他图形 | 否 | 是 |
| 单图数量限制 | 只能一个 | 不限,可同时装载多个 |
| 适合场景 | 单图专用流程、随图交付 | 通用工具库、批处理 |
我的选择习惯是:先写成全局工程调试,功能稳定、确认只服务某一类图纸之后,再决定是否嵌入。反过来做,每次改代码都要开图、分离、重嵌,来回折腾。
2.2 装载、嵌入、分离的命令行操作
不用菜单也能完成全套动作,命令行更可控,也方便写进脚本里。
VBAMAN # 打开 VBA 管理器,装载/卸载/嵌入/分离/新建/另存 全在这里 VBALOAD # 直接弹出“打开 VBA 工程”对话框,只做装载 VBAUNLOAD # 卸载已装载的工程,释放内存 VBAIDE # 打开 VBA 集成开发环境 VBARUN # 打开“宏”对话框,运行/编辑/逐语句/新建/删除几个操作细节值得单独拎出来。装载.dvb时如果选错文件类型,会直接报错,因为对话框只认有效工程文件。装载一个工程时,它所引用的其他工程会自动跟着装载,这一点在排查“为什么突然多出三个工程”时很有用。卸载只对全局工程有效,嵌入工程和被引用的工程卸不掉。
注意:每次装载工程都会弹一次代码启用/禁用提示。选禁用,工程照样装载,但所有代码都不能运行,你会看到宏列表是空的,这不是文件坏了。
嵌入的流程是:VBA 管理器里选中工程,点“嵌入”。分离则是选中后点“分离”,此时会问你是否在删除前输出到外部文件,选“是”走另存为对话框,选“否”就直接从图形数据库里删掉,没有后悔药。
2.3 在 IDE 里把第一个宏写进标准模块
VBAIDE打开后,左侧是工程窗口,默认可见,按Ctrl+R可以把它叫回来。每个工程下面挂着四类部件:对象(对应当前图形文档)、窗体、标准模块、类模块,外加一个引用节点。
- 对象部件:声明了代码可访问的文档对象,
ThisDrawing就指向它。 - 窗体部件:自定义对话框,做参数输入界面时用。
- 标准模块:放普通 Sub 和 Function,绝大多数宏写在这里。
- 类模块:自定义类,配合
WithEvents做事件处理时必用。 - 引用:其他工程和类型库,对象浏览器里的东西都从这里来。
新建一个标准模块,插入下面这段,F5就能跑:
Sub CountEntities() Dim ent As AcadEntity Dim total As Long Dim dict As Object Set dict = CreateObject("Scripting.Dictionary") ' 用字典按类型计数 For Each ent In ThisDrawing.ModelSpace total = total + 1 If dict.Exists(ent.ObjectName) Then dict(ent.ObjectName) = dict(ent.ObjectName) + 1 Else dict.Add ent.ObjectName, 1 End If Next ent Dim k As Variant, msg As String For Each k In dict.Keys msg = msg & k & " = " & dict(k) & vbCrLf Next k MsgBox "图元总数: " & total & vbCrLf & "分类统计:" & vbCrLf & msg End SubThisDrawing.ModelSpace是一个集合对象,For Each遍历时拿到的是AcadEntity,所有图元的公共父类型。ObjectName返回英文类型名,比如AcDbLine、AcDbCircle、AcDbBlockReference。这里没有直接对图元做任何写操作,先做只读统计,是验证工程装载和环境连通性最安全的一步。
想让代码在打开任意工程时都能触发,把它放在全局工程的模块里,再把.dvb命名成acad.dvb放进 CAD 的搜索路径。
2.4 部件的导入导出:跨工程复用代码
工程和 VB6 的二进制格式不兼容,但部件可以走文本文件格式互通。标准模块是.bas,窗体是.frm,类模块是.cls。
' 导出与导入都是 IDE 菜单操作,也可以用命令行走 ' 文件 → 导出文件 → 生成 .bas/.frm/.cls ' 文件 → 导入文件 → 把外部部件加进当前工程导入时的一个关键行为:文件是复制进来的一份副本,原来那个文件不受影响,后续改动互不同步。如果你导入的部件和工程里已有部件同名,旧部件会被覆盖,代码直接没了。我一般在导入前先把当前.dvb另存一份,成本两秒钟,能救命。
窗体导出成.frm时,同名目录下会多一个.frx二进制文件,里面是控件的图片、图标等资源,两个文件必须一起搬,只拷.frm打开会缺控件状态。
工程名称和文件名是两个不同的值。工程名称在 IDE 属性窗口里改,默认ACADProject;文件名称是保存时的.dvb名,默认Project.dvb。不设的话它就是这两个默认值,和别人发给你的工程重名也不会冲突,因为它们是两个维度的事。
3. 对象模型不是概念图:从 ThisDrawing 到图元的增删改
Object 模型那一章容易被跳过去,觉得是理论。真跑起来,报“对象变量未设置”的十有八九就是层次没搞清。这一节全部落到代码。
3.1 层次结构:Application → Document → 集合 → 图元
顶层是Application,它下面挂文档集合Documents,每个文档是一个AcadDocument,文档下面分ModelSpace和PaperSpace两个集合,集合里才是具体图元。图元的Parent属性指回它所在的集合,用它可以往上找。
Sub WalkHierarchy() Dim app As AcadApplication Set app = ThisDrawing.Application Dim doc As AcadDocument Set doc = app.ActiveDocument Debug.Print "文档名: " & doc.Name Debug.Print "模型空间图元数: " & doc.ModelSpace.Count Debug.Print "图纸空间图元数: " & doc.PaperSpace.Count ' 从第一个图元往上爬 If doc.ModelSpace.Count > 0 Then Dim ent As AcadEntity Set ent = doc.ModelSpace.Item(0) Debug.Print "图元类型: " & ent.ObjectName Debug.Print "所属集合: " & ent.Parent.ObjectName End If End SubThisDrawing在嵌入工程里指向工程所在的图形,在全局工程里指向当前活动图形。这个差异是很多“本机好的、同事那边跑傻”的根源,后面排查一节会展开。
ActiveDocument只在有活动文档时可用;如果没有打开任何图形,直接取会崩。稳妥写法是先判断Documents.Count。
集合对象除了Count和Item,还有Add系列方法,命名规则是Add加类型名,比如AddLine、AddCircle、AddText、AddDimAligned。写这类代码时把 IDE 的对象浏览器按F2打开,选中类型库,左侧树里能直接把方法签名和参数类型看全,比翻书快。
3.2 创建图元:坐标数组是 Variant,不是 Double()
所有建图元的方法,坐标参数都要求三元素 Double 数组,但接收类型写的是 Variant。
Sub DrawBaseFrame() Dim p1(0 To 2) As Double Dim p2(0 To 2) As Double p1(0) = 0#: p1(1) = 0#: p1(2) = 0# p2(0) = 420#: p2(1) = 297#: p2(2) = 0# Dim ln As AcadLine Set ln = ThisDrawing.ModelSpace.AddLine(p1, p2) ' 圆心 + 半径 Dim c(0 To 2) As Double c(0) = 210#: c(1) = 148.5#: c(2) = 0# Dim cir As AcadCircle Set cir = ThisDrawing.ModelSpace.AddCircle(c, 50#) ' 单行文字:文本、插入点、字高、旋转角 Dim tp(0 To 2) As Double tp(0) = 10#: tp(1) = 10#: tp(2) = 0# Dim txt As AcadText Set txt = ThisDrawing.ModelSpace.AddText("图框 A3", tp, 5#) End Sub参数说明:坐标数组三个元素依次是 X、Y、Z,二维绘图时 Z 填 0,但不能省,省了直接类型不匹配。字高单位跟随图形单位,机械图常按毫米,建筑图常按米,写死数值前先确认当前图纸的单位设置,否则会出现字高 5 米这种翻车现场。
创建之后必须Set到具体类型变量上,才能拿到类型特有的属性。如果你只写ThisDrawing.ModelSpace.AddLine p1, p2不接返回值,图元照样创建成功,但你后面无法直接引用它。
3.3 编辑图元与图层归位
图层是先建后用的,直接往不存在的图层上赋名会报错。
Function EnsureLayer(ByVal nm As String, ByVal clr As Integer) As AcadLayer On Error Resume Next Set EnsureLayer = ThisDrawing.Layers(nm) If Err.Number <> 0 Then Err.Clear Set EnsureLayer = ThisDrawing.Layers.Add(nm) EnsureLayer.color = clr End If On Error GoTo 0 End Function Sub DrawWithLayer() Dim lay As AcadLayer Set lay = EnsureLayer("S-FRAME", acBlue) ' acBlue = 5 Dim p1(0 To 2) As Double, p2(0 To 2) As Double p1(0) = 0#: p1(1) = 0#: p1(2) = 0# p2(0) = 100#: p2(1) = 0#: p2(2) = 0# Dim ln As AcadLine Set ln = ThisDrawing.ModelSpace.AddLine(p1, p2) ln.Layer = "S-FRAME" ' 按名字赋图层 ln.color = acByLayer ' 颜色跟随图层 ln.Linetype = "DASHED" ' 线型必须是已加载的 ln.LinetypeScale = 0.5 ' 线型比例,出图前要调 ln.Update End SubEnsureLayer用On Error Resume Next探测图层是否存在,这是 VBA 里代价最低的存在性判断。颜色常量acByLayer = 256、acByBlock = 0,其余 1 到 7 是标准色索引。线型要先在ThisDrawing.Linetypes里加载,直接赋一个没加载的名字会抛错。
改图元属性之后调Update,屏幕上才会重绘。批量改几千个图元时不建议每个都Update,先全部改完再统一ThisDrawing.Regen acAllViewports,速度差一个数量级。
3.4 标注:先建对,再谈样式
对齐标注是工程图纸里最常用的一类,AddDimAligned接收两个定义点和一个标注线位置点。
Sub DimSegment() Dim p1(0 To 2) As Double, p2(0 To 2) As Double, loc(0 To 2) As Double p1(0) = 0#: p1(1) = 0#: p1(2) = 0# p2(0) = 100#: p2(1) = 0#: p2(2) = 0# loc(0) = 50#: loc(1) = -18#: loc(2) = 0# ' 标注线放在下方 18 处 Dim d As AcadDimAligned Set d = ThisDrawing.ModelSpace.AddDimAligned(p1, p2, loc) d.StyleName = "ISO-25" ' 指定的样式必须已存在 d.Update End Sub样式名不存在会直接报错,所以自动化流程里要么先ThisDrawing.DimStyles.Add建一个,要么显式判断。另一个常见问题是标注文字的测量值倍数,由DIMLFAC系统变量控制,图纸单位是毫米而标注要显示米时,把它设成 0.001,别手动去改TextOverride,那会把标注变成死字。
形位公差用AddTolerance,传入公差文本、插入点和方向向量,公差符号在文本里用控制码表示,比如{\Fgdt;j}表示垂直度。这块建议先手动在 CAD 里拉一个公差框,用List看它的TextString长什么样,再照抄进代码,比查符号表省事。
4. 环境控制与交互:系统变量、用户输入、命令行和打印
宏跑通之后,下一个台阶是“让它像原生命令一样”。第四章和第九章讲的就是这部分:怎么读写环境、怎么从用户那里拿参数、怎么在三维里不出错、怎么把图打出去。
4.1 系统变量:GetVariable / SetVariable 要成对出现
系统变量是 CAD 的全局配置,改错了会影响整机后续操作,所以纪律比技巧重要。
Sub SafeSetVar(ByVal nm As String, ByVal v As Variant) Dim old As Variant old = ThisDrawing.GetVariable(nm) ' 先读旧值 ThisDrawing.SetVariable nm, v Debug.Print nm & ": " & old & " -> " & v ' 需要回滚时把 old 写回去 End Sub Sub ToggleOsnap() SafeSetVar "OSMODE", 0 ' 关对象捕捉,避免选点被吸走 ' ... 批量取点逻辑 ... SafeSetVar "OSMODE", 4133 ' 恢复常见捕捉组合 End SubGetVariable返回 Variant,数值型变量按 Double 处理,字符串型直接是字符串。踩坑点在于某些变量在模型空间和图纸空间不共用,TILEMODE就是典型,切空间前后读到的值不同。批量操图之前把OSMODE、CMDECHO、FILEDIA三个关掉,是最常见的稳妥做法:OSMODE=0防捕捉干扰,CMDECHO=0关回显,FILEDIA=0让文件对话框走命令行参数,避免宏运行到一半被弹窗卡死。
提示:
FILEDIA=0之后用户手动打开文件也会变成命令行问答,记得在Exit Sub前和错误处理里都恢复成 1,否则用户会以为软件坏了。
4.2 用户输入:Utility 对象取点、取串、取整数
Sub AskUser() Dim pt As Variant, kw As String, n As Integer On Error GoTo CancelHandler pt = ThisDrawing.Utility.GetPoint(, "选择基点: ") ' 第一参数留空表示不参考基点 kw = ThisDrawing.Utility.GetString(False, "输入构件编号: ") n = ThisDrawing.Utility.GetInteger("输入复制次数: ") MsgBox "基点 " & pt(0) & "," & pt(1) & " 编号 " & kw & " 次数 " & n Exit Sub CancelHandler: MsgBox "用户取消或输入无效,已退出" End SubGetPoint返回的是三元素 Variant 数组,直接用下标取。用户按 ESC 会抛运行时错误,必须用On Error GoTo接住,否则整个宏崩在半路,前面改过的图元已经写进数据库,回滚不回去。
GetString的第一个参数是“是否允许空格”,False表示遇空格结束输入,编号类输入用False,备注类用True。GetPoint的第一个参数可以传一个基点数组,此时会显示一条橡皮筋预览线,交互体验好很多。
需要关键词选项时用GetKeyword,配合InitializeUserInput先声明可接受的关键词表,非法输入会被系统自动挡掉,不用自己写校验。
4.3 命令行:SendCommand 是黑匣子,能用 API 就别用它
Sub BatchPlot() Dim old As Variant old = ThisDrawing.GetVariable("FILEDIA") ThisDrawing.SetVariable "FILEDIA", 0 ' 同步执行,字符串里带空格分隔参数 ThisDrawing.SendCommand "-plot" & vbCr & "Y" & vbCr & vbCr & "DWG To PDF.pc3" & vbCr ThisDrawing.SetVariable "FILEDIA", old End SubSendCommand把字符串丢给命令行解析,它不返回错误码,出问题时你只能看到一个没头没尾的提示。参数顺序错了、少一个回车、多一个空格,行为都不同。我的原则是:能在对象模型里做的,绝不用SendCommand;只有插件命令、某些打印流程、以及确实没有 API 覆盖的操作才用它。
调试SendCommand有个土办法:先手动在命令行敲一遍完整序列,把每步的回车数记下来,再一比一拼字符串,CMDECHO保持 1 观察回显,跑通了再关。
4.4 三维坐标转换与布局打印
用 UCS 画图时,代码里拿到的点和模型空间 WCS 点不是一个坐标系,直接混用会把图元画到意料之外的位置。
Function WcsToUcs(ByVal pt As Variant) As Variant WcsToUcs = ThisDrawing.Utility.TranslateCoordinates(pt, acWorld, acUCS, False) End Function Function UcsToWcs(ByVal pt As Variant) As Variant UcsToWcs = ThisDrawing.Utility.TranslateCoordinates(pt, acUCS, acWorld, False) End Function两个函数只是参数顺序不同,第三个参数False表示不做位移补偿,True则把点当作方向向量处理。这个布尔值搞反,图元会整体偏移一段 UCS 原点距离,肉眼很难发现,直到出图对不上。
布局和打印的核心是视口。每个布局的PaperSpace里有一个AcadPViewport需要单独设置比例和视图中心,其余视口是用户手动开的。视口比例用StandardScale或自定义CustomScale,设完之后调ThisDrawing.Regen acActiveViewport才生效。打印前用Plot.PlotToFile输出到 PDF,路径里不要带中文和空格,某些打印驱动在这个位置会静默失败,生成一个 0 字节文件。
5. 避坑与排查:五条在真图上翻车后立下的规矩
下面五条都是跑批量任务时真实撞出来的,按“现象 → 原因 → 解决”记,方便你对照自己的日志。
5.1 装载与工程层面的两个坑
现象一:本机跑得好好的宏,到同事机器上点运行毫无反应,宏列表是空的。
原因:装载工程时弹出的代码启用提示被点了“禁用”。工程本身装载成功了,但所有代码被挂起,宏对话框里自然一个都看不到。
解决:重新用VBALOAD装载一次,提示框出现时选“启用代码”。如果要批量分发给多人,把流程做成“先检查宏列表,为空就先提示用户重装”,比事后挨个解释省事。
现象二:宏明明改了图层颜色,运行完却时有变化、时没变化。
原因:ThisDrawing的指向问题。全局工程里ThisDrawing跟着当前活动图形走,宏运行的瞬间如果焦点在另一张图上,改的就是那张图。嵌入工程则固定在工程所在图形,行为完全不同。
解决:凡是跨图操作的宏,开头显式取Set doc = Application.ActiveDocument并用doc变量贯穿到底,别混用ThisDrawing。同时把Documents.Count的判断补上,没有活动文档时提前退出。
5.2 图元与坐标层面的两个坑
现象三:批量画的图元全部偏移了一个固定距离,看上去像整体被挪了一段。
原因:当前 UCS 不是世界坐标系,代码里的坐标被当成 UCS 坐标解释。手册里“指定三维坐标”和“坐标转换”两节讲的正是这件事。
解决:在宏开头把坐标系拉回世界:ThisDrawing.SetVariable "UCSNAME"之外,更稳的做法是显式调TranslateCoordinates做 WCS→UCS 转换,而不是依赖当前状态。同时把UCSFOLLOW设为 0,避免视图跟着 UCS 乱转。
现象四:改了几千个图元的颜色,屏幕上一点没动,但List一看属性确实变了。
原因:没有触发重绘。属性写进数据库了,视图没刷新。
解决:循环里不要逐个Update,那会让运行时间从几秒变成几分钟。全部改完后统一调ThisDrawing.Regen acAllViewports。如果只关心当前视口,用acActiveViewport更快。
5.3 交互与打印层面的坑
现象五:宏在办公室机器上能打出 PDF,在另一台机器上跑完只留一个 0 字节文件,没有任何报错。
原因:打印设备名写死在代码里,比如DWG To PDF.pc3,目标机器上没配这个绘图仪,PlotToFile静默失败。手册第九章讲打印流程时也是先讲设备再讲参数,顺序不能颠倒。
解决:打印前遍历ThisDrawing.PlotConfigurations或读ThisDrawing.GetVariable("PLOTCONFIG")确认设备存在,不存在就退回到手动选设备。输出路径同样要检查:文件名带空格、路径超过一定长度、目录没有写权限,都会表现成静默失败。我的做法是PlotToFile之后立刻用Dir()检查文件大小,为 0 就抛自定义错误,绝不让它悄悄通过。
6. 进阶收口:事件、菜单与交付前的自检
宏跑得动只是及格线。要让它像原生命令那样活着,得挂事件、挂菜单。
事件分三级:应用程序级、文档级、对象级。写法都一样,先在类模块里声明Public WithEvents,再在标准模块里用全局变量持有引用,否则对象一出作用域,事件立刻断掉,表现就是“昨天还能触发,今天不动了”。
' 类模块 ClsDocEvents Public WithEvents Doc As AcadDocument Private Sub Doc_BeginSave(ByVal FileName As String) Debug.Print "开始保存: " & FileName End Sub ' 标准模块 Public gEvt As ClsDocEvents Sub HookDoc() Set gEvt = New ClsDocEvents Set gEvt.Doc = ThisDrawing ' 必须用模块级变量持有 End Sub菜单这边,MenuGroups集合管理菜单组,MenuBar管当前菜单条。加载外部.mnu用MenuGroups.Load,重复加载同名菜单组会报错,所以循环里要先遍历判断存在性。工具栏项的状态栏提示通过ToolbarItem的Tooltip属性设置,右键菜单条目挂在ShortcutMenu上。这些改动都写进图形或注册表,做测试时务必留一条清理路径,否则用户装一次你的宏,CAD 菜单就多一行,卸载不掉。
交付前的自检我固定走这几步:
| 检查项 | 通过标准 |
|---|---|
| 系统变量回滚 | OSMODE / CMDECHO / FILEDIA 全部恢复原值 |
| 错误处理 | 每个交互入口都有 On Error 分支 |
| 重绘与刷新 | 批量改完有 Regen,无逐个 Update |
| 打印输出 | 文件大小大于 0,路径无中文空格 |
| 事件挂载 | 用模块级变量持有,不随过程结束释放 |
说实话,我最早跑批量标注的时候,就是在FILEDIA上翻的车:宏跑一半弹出保存对话框卡住,用户以为死机,强行结束进程,结果那张图半改不改直接废掉。从那以后我每次写涉及文件或打印的宏,都强制走一遍“改环境变量 → 干活 → 错误分支里也要恢复”的三段式,写完先拿一张备份图做破坏性测试。这套习惯比任何技巧都值钱,希望帮到你。
本文还有配套的精品资源,点击获取