ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

AutoCAD .NET API实战:C#插件开发与图形自动化指南

AutoCAD .NET API实战:C#插件开发与图形自动化指南

简介:AutoCAD .NET API开发库是一套面向C#编程人员的插件开发辅助框架,专门解决基于AutoCAD官方接口进行二次开发时门槛高、重复劳动多的问题。压缩包共61个文件,大小约6.06MB,核心内容以C#源码(20个cs)、DLL引用(13个dll)和XAML界面文件(5个xaml)为主,并附带项目配置、技术文档与测试用例,解压后目录结构清晰,便于直接集成与扩展。这套工具包内置高度模块化的类库,覆盖几何元素生成、图层控制、DWG文件操作、交互界面定制等常见场景,配合功能演示案例与质量验证套件,可支撑开发者快速实现参数化绘图系统、CAD与外部数据源的双向交换、图形几何属性自动计算以及多图层状态协同管理。目前已有101人学习下载,对于希望提升AutoCAD图形自动化处理效率的C#工程师,这套经过工程验证的框架能显著提高代码复用率,使团队将主要精力聚焦于业务逻辑与技术创新。

1. AutoCAD .NET API 开发库:C# 插件与图形自动化的正确打开方式

AutoCAD 的 .NET API 是官方托管接口,在 AutoCAD 2026 里已经沿 .NET 8 走得很稳,C# 插件能覆盖从批量改图层、遍历图纸抽属性到多 DWG 合并的几乎所有自动化需求。这套资源是一份 C# 开发库:把事务、块表、实体遍历和 DWG 合并这些高频操作封装成类,配上示例工程和速查文档,目的是让你不用抱着英文 ObjectARX 文档啃,也能在几天内把插件挂进 AutoCAD。它适合三类人:给设计院做批量工具的开发、被图纸统计和归档耗时间的工程师、正在个人学习 .NET 二次开发的自学者。下面按我实际拆这套库的顺序,从环境、事务模型、批量操作到参数与踩坑逐条讲。

2. 环境与加载:第一个 C# 插件从工程配置到 NETLOAD 全流程

2.1 开发机环境与 .NET 版本选型

开发机标配是 Windows 10/11 + Visual Studio 2022 + 一个目标 AutoCAD。这里最先要定的是版本选型,因为它直接决定你引用的程序集和框架。AutoCAD 的托管 API 分三个主程序集:AcMgd.dll 管 ApplicationServices 和 EditorInput,AcDbMgd.dll 管 DatabaseServices 和 Geometry,AcCoreMgd.dll 管 Runtime 等底层服务。三个 DLL 都在 AutoCAD 安装目录下,装完 AutoCAD 就能引用,不一定要去下那个几 GB 的 ObjectARX SDK。SDK 我只在需要官方文档和头文件时才装,纯托管开发直接引用安装目录里的 DLL 最省事。

框架版本按目标 AutoCAD 版本走,这是社区里反复验证过的对应关系:AutoCAD 2022~2024 对应 .NET Framework 4.8,AutoCAD 2025 和 2026 开始支持 .NET 8;更老的 2017~2021 对应 4.5~4.7,其中部分版本还依赖 .NET Framework 3.5 SP1 的 Windows 功能。记不住也没关系,装完 AutoCAD 打开安装目录,看 AcCoreMgd.dll 的依赖清单就能反推。我一般建议新项目按机器上最高的 AutoCAD 版本定框架,个人学习优先跟最新版 2026 走,这套资源的速查文档里针对这个版本的参数写得最全。

目标 AutoCAD推荐框架平台
2017~2021.NET Framework 4.5~4.7x64
2022~2024.NET Framework 4.8x64
2025~2026.NET 8(兼容 4.8 宿主)x64

提示:平台目标一律 x64。AutoCAD 从 2013 起主流就是 64 位进程,AnyCPU 编译部分机器能跑,一旦混进 x86 依赖就翻车,避坑章节专门讲这个。

2.2 工程引用与 csproj 配置

用 Visual Studio 2022 建一个类库工程,关键不是写代码,而是引用配置。照着这套开发库的工程模板,csproj 长这样:

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <OutputType>Library</OutputType> <PlatformTarget>x64</PlatformTarget> <LangVersion>latest</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="AcMgd"> <HintPath>C:\Program Files\Autodesk\AutoCAD 2026\AcMgd.dll</HintPath> <Private>false</Private> </Reference> <Reference Include="AcDbMgd"> <HintPath>C:\Program Files\Autodesk\AutoCAD 2026\AcDbMgd.dll</HintPath> <Private>false</Private> </Reference> <Reference Include="AcCoreMgd"> <HintPath>C:\Program Files\Autodesk\AutoCAD 2026\AcCoreMgd.dll</HintPath> <Private>false</Private> </Reference> </ItemGroup> </Project>

三个点说明。第一,TargetFramework 用 net8.0 对应 2025/2026,目标是 2022~2024 就改成 net48,同时 HintPath 换成那台机器对应版本的目录,老版本 AutoCAD 不认 net8 编出来的程序集。第二,HintPath 里的安装路径每台机器不一定一样,示例里的占位路径别直接抄。第三,Copy Local 必须为 false,也就是 Private 设为 false,运行时用的是 AutoCAD 进程自己加载的程序集,拷到输出目录反而造成版本冲突。

2.3 第一个命令:HELLOAC 与 NETLOAD 加载

引用配好后写第一个命令。AutoCAD .NET API 的约定是:公开类里的公开方法打上CommandMethod特性,就是一条 AutoCAD 命令;同时实现IExtensionApplication接口,让 AutoCAD 加载时回调 Initialize/Terminate。命令名建议全大写,大小写不敏感但混写容易在别名映射时出问题,也别跟内置命令重名。

using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Runtime; namespace AcDevLib.Demos { public class HelloCommands : IExtensionApplication { [CommandMethod("HELLOAC")] public void HelloAc() { Document doc = Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; ed.WriteMessage("\nHELLOAC 执行成功:C# 插件已加载。"); } public void Initialize() { } public void Terminate() { } } }

逻辑很简单:Application.DocumentManager.MdiActiveDocument 拿当前激活文档,Editor 是命令行交互入口,WriteMessage 往命令行写文本。这个骨架是所有插件的起点,后续所有命令都长这样。编译出 DLL 后,打开 AutoCAD,命令行输 NETLOAD 选中它,再敲 HELLOAC 看输出。

注意:NETLOAD 加载过的 DLL 会被 AutoCAD 占用,重编译前要关掉 AutoCAD 再开,否则 VS 报文件被占用。开发期我习惯把编译输出复制一份加载副本,原 DLL 随便改。

调试用 VS 的「调试 - 附加到进程」选 acad.exe,先 NETLOAD 再在 HelloAc 方法里打断点,敲命令即命中。这套开发库的封装类就在这个骨架之上:你引用库的 DLL,命令方法里调用 BatchLayerHelper、DwgMergeHelper 这些类,保持三行以内,事务和参数细节都收在库内部。

3. 图形自动化核心:事务模型、图纸遍历与批量修改的落地写法

3.1 事务模型:所有读写都必须在 Transaction 里

AutoCAD 的图形数据库不是普通的共享集合,它有一套事务机制:任何对图元、表、字典的读写都要先 StartTransaction,拿到对象后按 OpenMode 打开,写完 Commit 才落库。不 Commit 会怎样?using 块结束时事务 Dispose,所有修改自动回滚,图面纹丝不动。这是新手最容易翻车的点,避坑章节专门有一条。

开发库把事务封装成了 Helper,但底层逻辑必须懂。下面是在模型空间画一条线的标准写法:

[CommandMethod("MKLINE")] public void MakeLine() { Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; using (Transaction tr = db.TransactionManager.StartTransaction()) { // 打开块表,再定位模型空间 BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); // 构造直线实体并设置图层、颜色 Line line = new Line(new Point3d(0, 0, 0), new Point3d(100, 50, 0)); line.Layer = "0"; line.ColorIndex = 1; ms.AppendEntity(line); tr.AddNewlyCreatedDBObject(line, true); tr.Commit(); // 不调用这句,上面的全部白做 } }

几个要点。db.BlockTableId 是块表的 ObjectId,这是进入图纸的入口;bt[BlockTableRecord.ModelSpace] 通过块名取模型空间记录,OpenMode.ForWrite 是因为要往里追加实体。AddNewlyCreatedDBObject 第二个参数传 true,表示把新对象纳入事务跟踪,Commit 时才写入数据库。ColorIndex 是索引色,1 为红色;不想用索引色就 new Color 或用 trueColor。实体默认落 0 层,线宽 LineWeight 默认 -1 随层。

3.2 图纸遍历与图元过滤:从所有布局里筛出你要的东西

批量处理第一步是遍历。新手很容易只处理模型空间、漏了布局里的图纸空间。正统做法是遍历块表所有记录,用 IsLayout 过滤出布局,因为模型空间本身也是布局之一:

using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); int lineCount = 0, circleCount = 0; foreach (ObjectId btrId in bt) // 块表里每一行是一个块定义或布局 { BlockTableRecord btr = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead); if (!btr.IsLayout) continue; // 跳过普通块定义,只保留布局 foreach (ObjectId entId in btr) // 布局里所有图元 { Entity ent = (Entity)tr.GetObject(entId, OpenMode.ForRead); if (ent is Line) lineCount++; else if (ent is Circle) circleCount++; } } tr.Commit(); }

两层循环:外层走块表拿布局和块定义,内层走单条记录里的实体集合。is 类型判断是最实用的过滤手段,实体类型和 C# 类名对应关系见下表,写统计、导出、改样式都靠它。

图形对象对应类型常用读取属性
直线LineStartPoint / EndPoint / Length
圆CircleCenter / Radius
多段线PolylineNumberOfVertices / GetPoint2dAt
单行文字DBTextTextString / Position / Height
多行文字MTextContents / Location
块引用BlockReferenceName / Position / ScaleFactors

只读统计用 ForRead 就行,要改属性就先 UpgradeOpen 再写,或者直接 ForWrite 打开。开发库里的 DbHelper 把两层循环封装成了 VisitEach(Action<Entity>) 委托,传一个 lambda 就能统计,但原理还是这段代码,别被封装迷了眼。

3.3 两个高频需求落地:批量改图层与 DWG 合并

批量改图层是使用频率最高的场景:选中一批对象统一换到目标图层,图层不存在就自动创建。关键在于交互参数和事务顺序:

[CommandMethod("BATCH2LAYER")] public void BatchChangeLayer() { Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; Editor ed = doc.Editor; // 第一步:框选对象 PromptSelectionOptions selOpts = new PromptSelectionOptions(); selOpts.MessageForAdding = "\n选择要改图层的对象: "; selOpts.AllowNone = false; PromptSelectionResult selRes = ed.GetSelection(selOpts); if (selRes.Status != PromptStatus.OK) return; // 第二步:读取目标图层名 PromptStringOptions strOpts = new PromptStringOptions("\n输入目标图层名: "); strOpts.AllowSpaces = false; PromptStringResult strRes = ed.GetString(strOpts); if (strRes.Status != PromptStatus.OK) return; string layerName = strRes.StringResult; using (Transaction tr = db.TransactionManager.StartTransaction()) { // 确保图层存在,不存在则创建 LayerTable lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); if (!lt.Has(layerName)) { lt.UpgradeOpen(); LayerTableRecord ltr = new LayerTableRecord { Name = layerName }; lt.Add(ltr); tr.AddNewlyCreatedDBObject(ltr, true); } // 遍历选择集,写图层名 foreach (SelectedObject so in selRes.Value.GetObjectIds()) { Entity ent = tr.GetObject(so.ObjectId, OpenMode.ForWrite) as Entity; if (ent != null) ent.Layer = layerName; } tr.Commit(); } ed.WriteMessage($"\n已处理 {selRes.Value.Count} 个对象,图层: {layerName}"); }

参数是这门命令真正的门槛。PromptSelectionOptions 控制框选:AllowNone=false 禁止空选,RejectObjectsOnLockedLayers 默认 true,锁定层对象选不中,要改锁层对象得先解锁或用 Overrule。LayerTable.Has 判断图层存在性比直接索引安全。最后一条 WriteMessage 放在事务外,避免长事务里刷屏。字符串输入记得 Trim,命令行输入经常带尾随空格。

DWG 合并是另一个高频需求,开发库的 DwgMergeHelper 封装了核心调用:打开源数据库、收集模型空间实体 ObjectId、WblockCloneObjects 克隆到当前图:

using (Database srcDb = new Database(false, true)) { srcDb.ReadDwgFile(dwgPath, System.IO.FileShare.Read, true, ""); using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord destMs = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); // 收集源文件模型空间的实体 ObjectId ObjectIdCollection ids = new ObjectIdCollection(); using (Transaction srcTr = srcDb.TransactionManager.StartTransaction()) { BlockTable srcBt = (BlockTable)srcTr.GetObject(srcDb.BlockTableId, OpenMode.ForRead); BlockTableRecord srcMs = (BlockTableRecord)srcTr.GetObject(srcBt[BlockTableRecord.ModelSpace], OpenMode.ForRead); foreach (ObjectId id in srcMs) ids.Add(id); srcTr.Commit(); } // 克隆到当前图纸,同名对象按 Replace 策略处理 IdMapping mapping = new IdMapping(); srcDb.WblockCloneObjects(ids, destMs.ObjectId, mapping, DuplicateRecordCloning.Replace, false); tr.Commit(); } }

ReadDwgFile 第一个参数是路径,第二个 FileShare.Read 表示允许其他进程读,第三个 true 允许代码页自动解析,最后一个密码参数空串即可。new Database(false, true) 第一个 false 表示不绑定图形界面,第二个 true 表示作为非主数据库管理。DuplicateRecordCloning.Replace 决定同名块定义、图层冲突时谁赢,Replace 用源文件覆盖当前;保留主图定义就换 Ignore。合并前先做文件存在性和格式校验,否则源文件损坏时异常不好追。

4. 参数与性能:交互选项怎么设、大批量操作怎么不卡

4.1 交互 API 参数:把命令做成别人愿意用的工具

插件不是写给自己点的,参数设计决定工具能不能被同事接受。AutoCAD .NET API 的交互集中在 Editor 的 GetXXX 系列,每类都有配套的 PromptXXXOptions。开发库示例把常用参数都拉出来了,实际调整按表抄:

选项类关键属性作用与建议值
PromptSelectionOptionsMessageForAdding提示语必须说清「选什么」,别用默认的 Select objects
PromptSelectionOptionsAllowNone是否允许空选;批量改属性常设 false
PromptSelectionOptionsRejectObjectsOnLockedLayers锁定层对象是否可选中;批量处理时按需改
PromptSelectionOptionsSingleOnlytrue 时只允许单选,适合点选类操作
PromptStringOptionsAllowSpaces图层名等标识不建议允许空格,设 false
PromptStringOptionsDefaultValue给默认值,用户直接回车就用,体验提升明显
PromptPointOptionsUseBasePoint / BasePoint需要参考点时打开,比如以某点为中心阵列
PromptEntityOptionsAllowObjectOnLockedLayer点选实体时是否允许点锁定层对象

参数再多目的只有一个:让用户最少输入、最少出错。图层名这种输入,DefaultValue 给上次用的值,AllowSpaces 关掉,能在源头挡掉一半误操作。

4.2 性能优化:大图纸上别让命令变成「转圈十分钟」

几千个实体上跑批处理没问题,图纸上到几万实体、还带着大量块引用时,无脑遍历就会卡。这套开发库的性能优化集中在几个可复现的点上。

第一个是避免事务里做无关输出。Editor.WriteMessage 在循环里每行输出一次,命令行刷新开销比想象贵得多,正确做法是循环外累计、最后输出一条。第二个是读写分流:遍历用 ForRead,确认要改的实体再 UpgradeOpen;ForWrite 打开的对象越多,事务锁持有的时间越长。第三个是长事务拆分:几千个实体一次 Commit 没问题,十万级批量操作要么内存涨、要么抛 Command will not be allowed 类错误。常见做法是分块,每 1000 个实体一个事务:

const int BatchSize = 1000; int handled = 0; PromptSelectionResult selRes = ed.GetSelection(selOpts); if (selRes.Status != PromptStatus.OK) return; ObjectId[] ids = selRes.Value.GetObjectIds(); while (handled < ids.Length) { using (Transaction tr = db.TransactionManager.StartTransaction()) { int take = Math.Min(BatchSize, ids.Length - handled); for (int i = handled; i < handled + take; i++) { Entity ent = tr.GetObject(ids[i], OpenMode.ForWrite) as Entity; if (ent != null) ent.Layer = layerName; } tr.Commit(); } handled += take; ed.WriteMessage($"\n进度: {handled}/{ids.Length}"); // 每批报一次,不是每实体报 }

两点要说。第一,事务变量每次循环新建,Commit 后立刻释放锁,这是「不卡」的关键;第二,进度输出频率从「每实体一次」降到「每批一次」,命令行刷新成本线性下降。十万实体级别,肉眼能看出的差异就在这两处。

还有一个常被忽略的点:不需要刷新时别调 Regen。很多人改完属性习惯性 ed.Regen(),这是强制全图重生成,处理一万个对象时一次 Regen 比前面所有修改加起来还慢。事务 Commit 后图形队列会自动排刷新,手动 Regen 只在显示顺序、视口等特殊场景才需要。图层判断也提到事务外做一次,避免每个实体都重复判断。

5. 避坑指南:加载失败、事务丢失与版本错配的排查顺序

5.1 加载阶段的两个高频翻车点

翻车 1:能编译,NETLOAD 却报「未能加载文件或程序集」。

现象:VS 里编得干干净净,NETLOAD 选 DLL 后立刻弹 BadImageFormatException,或提示找不到 Autodesk.AutoCAD.Runtime 特定版本。

原因:最常见是平台目标不对。AutoCAD 2013 以后进程是 64 位,如果「任何 CPU」编译,且工程里不小心引了 x86 的第三方 DLL,加载就踩 BadImageFormat。另一个是框架错配,拿 net8 编的 DLL 塞进只支持 .NET Framework 4.8 的 AutoCAD 2024。

解决:csproj 里 PlatformTarget 写死 x64;旧式 .NET Framework 工程还要在项目属性里关掉 Prefer 32-bit。框架按第 2 章表格对号入座,别想当然「机器上是新版,目标机也是新版」。

翻车 2:NETLOAD 后 DLL 被锁,VS 编译报文件占用。

现象:改了代码再编译,VS 弹「文件正被另一进程使用」。

原因:NETLOAD 加载的程序集无法卸载,AutoCAD 进程一直持有 DLL 文件句柄。这是加载机制决定的,不是代码问题。

解决:开发期把编译输出复制一份再加载,原 DLL 随便编译;或者用 VS 调试器附加到 acad.exe 进程,断点打在命令方法里,减少来回重启。

5.2 运行阶段的经典问题:事务和对象生命周期

翻车 3:代码没报错,图面却一个对象都没变。

现象:批处理命令跑完,命令行干干净净,回到图里什么都没改。

原因:Transaction 的 using 块没写 Commit。事务 Dispose 时自动回滚未提交修改,看起来「正常执行」实际全部丢弃。

解决:养成习惯,每个 using 块结束前检查 tr.Commit()。调试时可以故意在 Commit 前抛异常看回滚路径。这套开发库的 Helper 把 Commit 收在内部方法里,但自己写事务时这是第一检查项。

翻车 4:把 Entity 对象存到集合里,下次访问报 ObjectDisposedException。

现象:第一次遍历正常,把实体对象(不是 ObjectId)存进 List,第二次访问抛 ObjectDisposedException,或 eInvalidObjectId。

原因:Entity 对象属于它被创建的那个事务上下文,事务结束后对象资源释放,保留引用再访问必然出错。ObjectId 是长期有效的标识,Entity 不是。

解决:跨事务保存数据只存 ObjectId、不存对象引用,每次需要属性时重新 GetObject。另一个变体是遍历中 Delete 后再访问,删除后的对象也要重新从 ObjectId 打开。

5.3 目标机环境:部署阶段提前验证三件事

翻车 5:目标机上 NETLOAD 报找不到 .NET Framework,或 AutoCAD 安装卡在 net3.5sp1。

现象:开发机一切正常,换一台没装 VS 的机器就加载失败;老版本 AutoCAD 在 Win10/11 上安装时长时间卡在启用 .NET 3.5 SP1 功能。

原因:目标机缺对应运行时。AutoCAD 2022~2024 需要 .NET Framework 4.8,2025/2026 需要 .NET 8;而 .NET 3.5 SP1 是 Windows 可选功能默认关闭,安装程序触发启用时如果系统组件异常或离线,就卡住。

解决:部署前在目标机核对运行时,dotnet --list-runtimes看 .NET 8 是否在列,注册表或「启用或关闭 Windows 功能」看 4.8 是否装了。卡 3.5 时用管理员 PowerShell 执行Enable-WindowsOptionalFeature -Online -FeatureName NetFx3 -All再重装 AutoCAD。插件分发同理,把运行时要求写进说明,别默认对方机器和开发机一样。

6. 进阶技巧:用事件驱动把批处理命令升级成自动流程

插件做到能跑只是第一步,真正省人力的是事件驱动。Application.DocumentManager 暴露了 DocumentCreated、DocumentActivated、DocumentToBeDestroyed 等事件,挂上回调后命令都不用敲,文档一激活就自动执行。开发库的事件封装类典型写法如下:

public class EventAutomation : IExtensionApplication { public void Initialize() { DocumentManager dm = Application.DocumentManager; dm.DocumentActivated += OnDocActivated; } public void Terminate() { DocumentManager dm = Application.DocumentManager; dm.DocumentActivated -= OnDocActivated; // 卸载时务必摘事件,防重复挂载 } private void OnDocActivated(object sender, DocumentCollectionEventArgs e) { Document doc = e.Document; using (doc.LockDocument()) // 事件回调不是命令上下文,改图前必须拿文档锁 { // 在这里调用库里的批处理方法,比如自动修正图层、清理空组 } } }

两个关键点。第一,事件回调运行在非命令上下文里,直接改图会抛 DocumentLocked,所以一律先 LockDocument,不能省。第二,Initialize 挂的事件必须在 Terminate 摘掉,否则 NETLOAD 重载后回调挂两次,同一操作执行两遍,这是事件类插件特有的坑。

就我的习惯,事件回调里只做轻量自动操作,重活仍然暴露成命令——事件触发频繁,每次全量批处理会把性能拖垮。做法是事件里只做标记或单对象修正,全量批处理留给命令或定时触发。这套资源里的封装库、示例工程和速查文档,下载后按第 2 章流程先跑通 HELLOAC,再改成自己的批处理命令,成本最低。早些年我写事件回调图省事,不拿文档锁直接改图层,结果一激活文档就抛异常,从那时起每次发布插件,我都强制在干净机器上走一遍:x64 目标、目标版本框架、最小环境 NETLOAD 验证三步,再也不敢跳过。希望帮到你。

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

RELATED READING

延伸阅读

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