简介一份基于.NET C#的CAD二次开发源码包面向ZWCAD与AutoCAD可实现按需求自动绘制图块、批量生成图形对象有效减轻人工绘图的重复操作与出错率。该程序系作者在实际工作场景中实践开发已完成基本功能测试具备较好的稳定性。压缩包共68个文件总大小2.83MB核心内容以C#窗体源码.cs文件为主配合编译生成的exe/dll程序集、界面资源.resx及Visual Studio解决方案文件.sln等项目结构完整方便直接打开调试。源码完全开放读者可基于自身业务进行定制化二次开发例如修改图块尺寸、增加新的绘图命令或对接其他数据源。已有6793人学习下载对有CAD绘图提效需求或正在学习C#二次开发的工程师而言是一份贴近实战的参考资料。 做工业自动化或者机械设计的朋友应该都有过这种经历CAD里一堆重复性的绘图工作框选、偏移、修剪、标注一天下来眼都快花了。我之前做设备和产线图纸的时候也被这种重复劳动折磨得不轻后来干脆用C#对CAD做了一轮二次开发把批量出图这种活儿直接扔给程序去跑。这篇文章就把我在.NET C#环境下做CAD二次开发、实现自动绘图功能的过程整理一下从环境搭建、基础框架到自动绘图的核心代码再到我踩过的那些坑一次性说清楚。这篇文章适合两类人看一类是刚接触CAD二次开发、想用C#做自动绘图但不知道怎么入门的同学另一类是已经写过一些LISP或VBA脚本想往更正规的.NET方向迁移的工程师。我会尽量少讲理论废话多给能直接复制的代码和思路。整个方案的核心是用Visual Studio AutoCAD .NET API通过编写托管DLL的方式让CAD在启动后加载我们的程序然后通过自定义命令自动完成绘图任务。下面进入正题。1. 为什么选.NET C#做CAD二次开发1.1 主流的CAD二次开发方式对比先说结论AutoCAD二次开发目前主流有四种方式AutoLISP/Visual LISP、VBA、ObjectARXC和.NET API。很多老工程师习惯用AutoLISP写小工具因为语法简单、加载快几行代码就能改个图元属性。VBA在早期版本里也很流行但Autodesk后来对VBA的支持越来越弱现在的版本默认都不带VBA模块了。ObjectARX性能最强能直接操作CAD内核但开发门槛高一个简单的功能可能就要写一堆C代码调试起来也很痛苦。.NET API是这几个方案里综合性价比最高的它本质上是Autodesk对ObjectARX封装出来的一套托管接口C#写起来比C顺手得多语法上又比LISP严谨适合做稍微复杂一点的工程化功能。更关键的是你写的DLL可以直接被CAD用NETLOAD命令加载不需要安装额外运行时分发部署非常干净。我选C#还有一层原因同一个团队还要做上位机、数据处理这些工具C#一套技能能通吃维护成本低。1.2 .NET API的版本兼容逻辑这里有一个必须提前搞清楚的点AutoCAD .NET API和调用它的程序集版本、.NET Framework版本是强绑定的。AutoCAD 2014开始引入了AcCoreMgd.dll2015版之后基本稳定到了AutoCAD 2020官方要求的.NET Framework版本是4.8。也就是说如果你用VS 2022创建项目选择.NET Framework 4.8目标平台为AnyCPU或x64然后引用对应CAD安装目录下的AcMgd.dll、AcDbMgd.dll、AcCoreMgd.dll基本就能覆盖AutoCAD 2020到2025这批主流版本。如果你的现场还在用AutoCAD 2016到2019那.NET Framework用4.7同样能跑。最新版CAD对.NET Core的支持还处于过渡期不建议在正式项目里赌未来稳定优先老老实实用.NET Framework。我自己习惯的策略是开发机上装一个高版本CAD比如2024但代码里只调用低版本就有的API这样生成的DLL在2016-2025之间都能通用部署的时候省很多事。1.3 我能用什么方式加载和运行加载方式有两种一种是CAD启动时通过注册表或启动组自动加载另一种是手动在CAD命令行输入NETLOAD加载dll文件。调试阶段我建议手动NETLOAD改完代码重新编译再加载不用每次重启CAD。到了交付阶段可以把dll路径写进启动组或者做一个安装包在CAD启动时自动引用用户打开CAD就能直接用自定义命令完全感觉不到背后有个程序在运行。后面这些实操细节我会在第3部分详细展开。2. 环境准备一步步把开发环境搭起来2.1 安装和引用的版本匹配开发环境的核心就三步装Visual Studio、装AutoCAD、建项目引用dll。VS推荐2019或2022社区版免费的够用。AutoCAD用你目标现场的主版本比如2020以上。建项目的时候注意选择“类库(.NET Framework)”不要选“类库(.NET Core)”否则加载进CAD会报找不到程序集。引用路径一般在CAD安装目录下比如C:\Program Files\Autodesk\AutoCAD 2024\AcMgd.dll、AcDbMgd.dll、AcCoreMgd.dll。这三个dll是.NET API的核心库AcMgd管应用程序和文档AcDbMgd管数据库和实体AcCoreMgd里面是命令定义、编辑器交互这些基础服务。三个都要引用缺一个编译能过运行时报错。引用添加完之后记得在项目里把这三个dll的“复制本地”属性设为False。如果不设编译输出目录会多出几十MB的dll而且加载时可能因为版本冲突导致CAD崩溃。2.2 第一个能跑起来的命令框架在项目里新建一个类加一个静态方法方法上面用[CommandMethod(HELLO)]标注这就是注册给CAD的命令。代码如下using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Runtime; namespace CadAutoDraw { public class Commands { [CommandMethod(HELLO)] public static void Hello() { Document doc Application.DocumentManager.MdiActiveDocument; Editor ed doc.Editor; ed.WriteMessage(\nHello from C# CAD 二次开发!); } } }编译成功后在CAD命令行输入NETLOAD定位到bin目录下的dll文件然后输入HELLO命令如果命令行打印出了那行字恭喜你环境已经通了。这就是整个二次开发的最小闭环编译dll - 加载dll - 命令触发。2.3 设置调试时自动启动CAD上面的流程手动操作没问题但开发阶段每次改代码都手动NETLOAD很烦。这里教你一个偷懒的办法在VS里右键项目 - 属性 - 调试 - 启动外部程序选到acad.exe路径。这样你按F5VS会自动拉起CAD并附加上调试器你可以在C#代码里打断点直接在CAD里看变量值、堆栈信息排查问题效率提升一倍。有一点要提醒调试模式下CAD启动后要先用NETLOAD加载你编译出来的dll路径一般在项目bin\Debug下再执行自定义命令断点才会命中。VS的调试器附加的是整个CAD进程dll加载后代码才会进入断点范围。3. 自动绘图的核心原理和示例代码3.1 事务处理CAD数据库写操作的基本单位自动绘图的本质就是往CAD的数据库Database里添加图元Entity比如直线、圆、多段线。这个数据库操作不是直接写入就能完事的必须包在一个“事务”Transaction里。我用一个生活化的类比CAD的数据库就像一本账本你要往里面记一笔账不能直接拿笔在页面中间乱画得先打开账本、找到当前页、用笔写下内容、然后合上账本整个过程要么全部成功要么全部不写。在.NET API里这个流程对应如下using (Transaction tr doc.Database.TransactionManager.StartTransaction()) { BlockTable bt tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; BlockTableRecord btr tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; // 创建实体并添加到 btr tr.Commit(); }这里有两个关键点第一块表记录BlockTableRecord对应的是模型空间或图纸空间的“当前页”第二写完一定要调用Commit()否则事务回滚白画了。很多新手漏掉Commit折腾半天发现图没画出来基本都是这个原因。3.2 坐标系统和单位为什么画出来的东西位置不对做CAD开发离不开坐标系统。CAD里有世界坐标系WCS、用户坐标系UCS、显示坐标系DCS还有图纸空间坐标系PSDCS。我们代码里用Point3d创建实体默认是在WCS下工作。但用户执行命令时如果他切了UCS你程序里返回的点坐标可能是UCS下的值直接用会导致图元位置错乱。解决思路很直观要么在绘制前强制把当前坐标系切回WCS不推荐会影响用户现场视图要么用Editor.CurrentUserCoordinateSystem把用户输入点转换到WCS里。我习惯的做法是写一个转换工具函数private static Point3d ToWcs(Point3d pt) { Editor ed Application.DocumentManager.MdiActiveDocument.Editor; CoordinateSystem3d ucs ed.CurrentUserCoordinateSystem; return pt.TransformBy(Matrix3d.AlignCoordinateSystem( Point3d.Origin, Vector3d.XAxis, Vector3d.YAxis, Vector3d.ZAxis, ucs.Origin, ucs.Xaxis, ucs.Yaxis, ucs.Zaxis)); }至于单位CAD默认图纸单位是毫米还是英寸取决于模板文件DWT。.NET API内部所有坐标都以“图形单位”存储你从用户输入或界面拿到的值不会是带单位的它就是图形单位。所以自动绘图时生成大批坐标点你首先要和图纸约定好单位跟客户对齐否则尺寸会差25.4倍这种低级错误可太常见了。3.3 实际画三个常用图元直线、圆、多段线理解了事务和坐标画图就是往当前块表记录里Add实体的问题。我直接给出三个最常用的示例代码包含直线、圆和带圆弧的多段线[CommandMethod(DRAWSAMPLE)] public static void DrawSample() { Document doc Application.DocumentManager.MdiActiveDocument; Database db doc.Database; Editor ed doc.Editor; using (Transaction tr db.TransactionManager.StartTransaction()) { BlockTableRecord btr tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; // 1. 直线从 (0,0) 到 (100,50) Line line new Line(new Point3d(0, 0, 0), new Point3d(100, 50, 0)); btr.AppendEntity(line); tr.AddNewlyCreatedDBObject(line, true); // 2. 圆圆心 (50,50)半径 30 Circle circle new Circle(new Point3d(50, 50, 0), Vector3d.ZAxis, 30); btr.AppendEntity(circle); tr.AddNewlyCreatedDBObject(circle, true); // 3. 多段线从 (0,0) 到 (200,0)再画圆弧回到起点形成闭合图形 Polyline pline new Polyline(); pline.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); pline.AddVertexAt(1, new Point2d(200, 0), 0, 0, 0); pline.SetBulgeAt(1, -1); // 凸度值 -1 表示半圆圆弧 pline.Closed true; btr.AppendEntity(pline); tr.AddNewlyCreatedDBObject(pline, true); tr.Commit(); } ed.WriteMessage(\n示例图形已生成: 直线、圆、闭合半圆多段线); }AddNewlyCreatedDBObject(entity, true)这行很多人会忽略但其实很关键实体被AppendEntity加入块表记录后还需要通过事务管理器把它标记为“新增对象”事务提交时才会真正写进数据库。漏掉这一行你运行命令虽然不报错但图里什么都没有。3.4 多段线凸度Bulge的数学原理上面代码里pline.SetBulgeAt(1, -1)Bulge是CAD多段线里一个很特别的概念很多新手在这里翻车。凸度表示顶点到下一个顶点之间圆弧的弯曲程度等于圆心角四分之一的正切值。如果凸度是0说明这一段是直线凸度绝对值越大圆弧越陡正负号决定圆弧往哪边弯逆时针为正顺时针为负。比如我想从(0,0)到(200,0)画一个经过点(100,100)的凸圆弧它的凸度值是tan(圆心角/4)圆心角90度所以凸度是tan(22.5°) 0.41421356。这个值不需要你背公式但你要懂得怎么反向推算因为很多从Excel或外部数据接口读取的数据给的可能是半径、圆心、起点终点你要先把这些几何参数换算成凸度才能塞进多段线里不然画出来的圆弧方向完全不对。4. 让自动绘图更“能用”的关键设计4.1 先分层再画图线型、颜色、图层的工程化处理直接往模型空间扔一堆图元满足演示没问题但放到真实项目里会被同事骂死因为图面完全失控。工程上自动绘图的第一步永远是建图层、设颜色、定线型。下面这个函数可以帮你保证“图层存在且属性正确”public static ObjectId CreateLayer(Database db, string layerName, short colorIndex, string linetype Continuous) { using (Transaction tr db.TransactionManager.StartTransaction()) { LayerTable lt tr.GetObject(db.LayerTableId, OpenMode.ForRead) as LayerTable; if (lt.Has(layerName)) { tr.Commit(); return lt[layerName]; } tr.Commit(); } using (Transaction tr db.TransactionManager.StartTransaction()) { LayerTable lt tr.GetObject(db.LayerTableId, OpenMode.ForWrite) as LayerTable; LayerTableRecord ltr new LayerTableRecord(); ltr.Name layerName; ltr.Color Color.FromColorIndex(ColorMethod.ByAci, colorIndex); ltr.LinetypeObjectId db.GetLinetype(linetype); lt.Add(ltr); tr.AddNewlyCreatedDBObject(ltr, true); tr.Commit(); return ltr.Id; } }注意两个细节线型必须先存在于数据库中如果图纸里没有DASHED这个线型赋给图层的LinetypeObjectId会是无效的。所以要么先用db.LoadLineTypeFile(DASHED, acad.lin)加载要么通过db.GetLinetype确认存在性图层名称尽量统一用标准前缀比如“S_设备区”“T_标注区”方便图面管理也方便后续按图层批量选择。4.2 命令交互让用户点选插入点或选对象自动绘图不意味着死板地一把梭更多时候你需要给用户一个选择机会。比如批量插入设备符号时用户希望先在图纸上点一个位置程序再根据这个位置做阵列或偏移。用Editor类的GetPoint就能实现点选交互[CommandMethod(DRAWATCLICK)] public static void DrawAtClick() { Document doc Application.DocumentManager.MdiActiveDocument; Editor ed doc.Editor; PromptPointOptions ppo new PromptPointOptions(\n请指定插入点: ); PromptPointResult ppr ed.GetPoint(ppo); if (ppr.Status ! PromptStatus.OK) { ed.WriteMessage(\n用户取消操作); return; } Point3d insertPt ppr.Value; // 这里将 insertPt 传入绘图函数即可 }如果你是做上位机开发的这里的设计思路和串口/网口通讯里的“事件触发”很像程序不主动猜用户意图而是以用户输入作为触发条件。再进一步你可以把扫码枪扫码的结果转成坐标或参数然后触发自动绘图这个在产线数据采集和图纸可视化场景里非常实用本质上就是一个“外部数据 - CAD绘图”的管道。4.3 批量绘图不要让命令一条条执行自动绘图最大的价值在于批量。比如你有上千个坐标点要一次性全部画成圆或块。最简单的错误做法是在CAD命令行循环调用DRAWCIRCLE命令这样每执行一次命令都要做一次事务、刷新一次屏幕性能惨不忍睹。正确做法是一个事务里循环创建实体最后提交一次。using (Transaction tr db.TransactionManager.StartTransaction()) { BlockTableRecord btr tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; foreach (var pt in pointList) { DBPoint dbPoint new DBPoint(new Point3d(pt.X, pt.Y, 0)); btr.AppendEntity(dbPoint); tr.AddNewlyCreatedDBObject(dbPoint, true); } tr.Commit(); }一次性创建几千上万个对象都在一个事务里完成速度非常快。如果再配合图层关闭、锁定等操作把视觉更新降到最低性能还能再提升一截。实测下来一万个点绘制基本能做到秒级完成。这里有个经验不要每画一个实体就调一次doc.Editor.Update()或doc.Editor.Regen()那会让事务边界被强制刷新画面性能会退化几十倍。4.4 插入块参照从模板文件复制图框和符号如果你要自动往图纸里插入图框、标准件符号用“块”Block是最高效的方式。块的定义可以提前放在模板DWT中程序只需要在目标数据库里找到块定义然后创建BlockReference插入到模型空间。读取DWT里的块定义需要一点技巧要用Database templateDb new Database(false, true); templateDb.ReadDwgFile(templatePath, FileOpenMode.OpenForReadAndAllShare, false, );先打开模板文件然后把块表记录拖拽到目标数据库中用IdMapping做对象克隆。完成之后再通过BlockReference插入块引用BlockReference bref new BlockReference(insertPt, blockTableRecordId); btr.AppendEntity(bref); tr.AddNewlyCreatedDBObject(bref, true);这套做法适用于设备布置图、工艺流程图里那种大量重复的符号插入。你在模板文件里把符号画好程序只管“摆位置”图面质量和绘制效率都远高于代码一行行画几何线条。5. 常见问题与排查技巧实录5.1 问题速查表我在实际项目中遇到过不少问题先列个表格后面挑几个典型的展开说。现象可能原因解决办法NETLOAD后命令找不到dll没有编译成.NET Framework检查项目目标框架重新生成dll运行命令报“拒绝访问/没有权限”程序访问了注册表或系统目录、dll在受保护路径以管理员身份运行CADdll放用户目录或项目目录运行命令没反应图也没变化没有调用Commit或AddNewlyCreatedDBObject缺失检查事务是否提交实体是否被追加到BlockTableRecord图纸上的文字显示成“???”样式字体缺失或编码问题统一字体样式确保代码写入字符串编码正确文件保存为UTF-8 with BOM画出来的圆/弧方向反了凸度Bulge正负号反了用正负号规则判断圆弧方向逆时针为正CAD 2016加载dll提示“api-ms-win-crt-runtime-l1-1-0.dll丢失”系统缺少VC运行库安装Windows对应版Universal CRT补丁、或用sysinternals检查系统dllCAD打开或安装过程报0x80070005系统目录或注册表权限不足以管理员方式安装关闭杀毒软件误拦截修复.NET Framework5.2 权限错误0x80070005这一类的问题排查这个错误在Win10系统上装.NET Framework 3.5或CAD相关组件时特别常见错误本身的意思是“拒绝访问”本质上就是当前进程没有权限去访问注册表或C:\Windows\System32下的系统文件。我的排查顺序是先确认Windows Installer服务是否正常启动再用管理员权限运行命令提示符执行dism /online /enable-feature /featurename:NetFx3 /All或sfc /scannow修复系统文件。如果还不行检查杀毒软件是否把CAD或VS的安装进程拦住了。很多CAD加载dll时报的权限问题其实真正原因是目标dll放在了类似C:\Program Files下而CAD进程没有管理员权限去动态加载它。解决办法很简单把dll放到用户的Application Data目录或项目自身的bin目录下再执行NETLOAD。5.3 CAD画直线显示2.1616e这种数字是什么原因这个我有发言权因为被问过起码三次。CAD里输入直线命令后命令行提示显示“2.1616e...”这不是程序问题也不是二次开发特有的bug纯粹是数字精度显示问题。CAD默认的线性精度LUPREC变量可能比较低导致坐标或长度用科学计数法显示看着像报错。解决方式很简单在CAD命令行输入UNITS把线性精度位数调高或者直接在代码里设置Application.SetSystemVariable(LUPREC, 8)。这样绘图过程中命令行显示的点坐标、长度数据就不再出现e这种鬼样子了。做二次开发时很多用户看到这种显示就以为是程序画错位置其实是显示精度在捣乱。5.4 一个容易忽略的性能陷阱循环里反复启动事务我见过不少初学二次开发的朋友代码逻辑清晰但画一万个点要等半天。原因就是循环体内部每次都StartTransaction加Commit相当于CAD要不断把内存数据同步到显示系统性能自然崩了。正确的思路是尽量让一个事务处理一批同类操作。事务开得越大提交次数越少速度越快。但也有一个边界比如在一个事务里创建几十万个实体事务提交前数据一直占用内存提交时也会有一次明显的卡顿。所以我会按5000-10000个实体一批的方式切分事务既能保证速度又不会因为一次提交太多数据导致内存暴涨或崩溃。5.5 跨CAD版本部署的兼容性避坑最后说一个部署层面的经验。很多人在自己电脑上开发调试没问题拷到现场电脑就报错多半是版本不匹配。有三种常见情况一是现场CAD版本比自己开发版本低代码里调用了高版本API二是现场电脑没有安装对应的.NET Framework运行时三是dll引用了其他第三方库而现场没有一起copy。我的建议是开发时尽可能以低版本CAD作为主目标比如用2020做开发测试但代码逻辑里避免调用2020之后新增的API这样高版本也能跑。部署时把.NET Framework运行时一起打包或者直接做安装程序检测并静默安装。第三方依赖尽量少用如果非用不可把对应dll也放进同一目录并在程序集解析事件中指定本地加载路径。最后说两句做CAD二次开发这件事真正难的不是API本身而是要把现场画图的那套业务规则翻译成代码逻辑。比如图框要套哪个模板、设备符号要插到哪层、标注格式要用多少号字这些规则化得越细自动绘图的效果越惊艳。我自己的习惯是每次开发前先把手动画一遍的流程录成视频再一步步拆成可执行的动作表最后才动手写C#代码这个习惯帮我省掉了大量返工时间。如果你正准备做一个CAD自动绘图的项目我建议别一开始就追求大而全的系统先把“NETLOAD加载dll - 执行命令 - 画出第一个图元”这个闭环打通然后再逐步加图层管理、批量绘制、块插入、与上位机数据联调这些能力。一套能跑起来的最小系统永远比一堆设计文档更能帮你摸清问题在哪。最后分享一个小技巧开发过程中在代码里预留一个“日志开关”把每次自动绘图的坐标点数量、耗时、图层名这些关键信息写到txt文件后期排查现场问题时会比逐个问用户“你刚才做了什么操作”高效得多。这个看起来土办法在几次交付中帮我把沟通成本降了一半以上。本文还有配套的精品资源点击获取
