
简介面向CAD二次开发工程师的C# WinForms程序用于在Windows桌面应用中直接打开AutoCAD dwg/dxf文件支持浏览图纸、提取点位坐标、导出线型等图形元素信息同时具备图形元素分析和几何计算能力可有效解决CAD数据与业务系统间的对接问题。压缩包内共46个文件主体为C#源码文件配合项目解决方案和配置文件并包含可直接运行的exe以及动态库dll方便查看或运行另有调试符号资源文件及说明文档整体体积仅315KB轻量且目录清晰。核心窗体源码完整从界面布局到文件加载、坐标导出、元素属性读取均有对应实现能够帮助读者理解WinForms中调用CAD接口并完成图形计算的整体流程在此之上还可作为扩展自定义绘图或批量数据处理功能的起点。已有2910人浏览学习适合具备C#基础和CAD概念的开发者快速上手也可作为二次开发的重要参考。 做工控上位机的朋友多半迟早会遇到这样一个需求甲方给了一批AutoCAD的dwg/dxf图纸要你在C#写的WinForms程序里把这些图纸里的点位坐标、线型信息扒出来生成一份点表或者用来做轨迹控制。我最初以为是先打开CAD再手动导出后来发现项目要求的是程序里点个按钮选一个图纸自动把图里的直线端点、圆心、多段线顶点外加线型全部提出来。这篇文章就把我踩过的坑和最终跑通的方案完整写出来。1. 项目背景与方案选型1.1 先搞清楚要做什么先不急着写代码拿到这个需求脑子里第一反应是这活儿本质上不是“看图”而是“读数据”。CAD图纸本身就是个数据库dwg和dxf里存的不是像素而是一堆实体描述Line的起点终点坐标、Circle的圆心半径、LWPolyline的顶点序列以及每个实体用的线型是什么。我们真正要做的是把CAD图形数据库里还有价值的实体信息抽出来转成csv或者txt交给下游程序用。搞清楚这一点后整个项目就变成了解析问题而不是图形渲染问题。至于“打开文件”不是要在自己程序里显示CAD界面而是要用程序把文件内容读进来。这一步想通后面的选型就顺了。1.2 两条技术路线的取舍我在网上搜了一圈主流做法无非两种。第一条路是调用AutoCAD自己的COM/ActiveX接口。只要电脑上装了AutoCAD程序就能通过Interop去启动它或者连接已启动的实例用AcadDocument、AcadModelSpace这些对象去遍历图元。这条路的好处是简单粗暴不用去理解DWG二进制格式CAD怎么解析你就怎么拿数据支持所有实体类型甚至连块、标注都能查。坏处也明显客户机器必须安装AutoCAD还得处理版本兼容。第二条路是纯文件解析不依赖AutoCAD。比如直接解析DXF文本或者用开源类库像dxflib、LibDWG、ODA的Teigha去读DWG然后自己提取实体。好处是不用装CAD部署很干净适合做成后台服务。坏处是DXF格式虽然公开但实体种类太多写不好容易漏东西DWG格式更恶心闭源且版本多。我这个项目的甲方现场肯定装了AutoCAD他们也接受依赖CAD运行环境所以我首选了COM方案。另外把DXF解析做成备选用于那些只发了dxf没装CAD的场合。2. 基于AutoCAD COM的快速实现2.1 环境准备与引用C#里用COM访问AutoCAD最标准的方式是添加AutoCAD类型库引用。我这里用的是AutoCAD 2016安装目录下有个acdbmgd.dll或者直接在VS里添加COM引用找到“AutoCAD 2016 Type Library”。注意AutoCAD是用.NET的Managed API还是COM接口取决于版本。最稳妥的通用做法是using System; using System.Runtime.InteropServices;然后用动态类型来调避开版本冲突Type acadType Type.GetTypeFromProgID(AutoCAD.Application); object acadApp Activator.CreateInstance(acadType); dynamic app acadApp;“AutoCAD.Application”这个ProgID不会变但不同位数CAD要注意进程位数。COM调用AutoCAD时如果你的WinForms程序是32位的就配32位AutoCAD如果CAD装的是64位程序也要改成64位否则类型库加载会失败。这是我第一次就踩过的坑编译成AnyCPU照样崩后来强制改成x64就好了。2.2 界面和打开文件逻辑界面不复杂一个按钮选文件一个按钮开始提取一个进度条一个DataGridView用于预览再加一个导出按钮。WinForms布局我就不多画了重点说打开文件这一步的三种模式。如果不想让CAD界面弹出来可以在COM调用的Visible属性设为false。但AutoCAD启动时不许完全隐藏窗口至少得让它加载。可以用下面这段代码兼容处理dynamic app null; try { var t Type.GetTypeFromProgID(AutoCAD.Application); app Activator.CreateInstance(t); // 连接已打开的实例如果存在 if (app.Documents.Count 0) { // 已有实例已打开 } } catch (Exception ex) { MessageBox.Show(无法启动AutoCAD请检查安装 ex.Message); return; }打开指定dwg文件用Documents.Open方法。第二三个参数是“只读”和“密码”只读模式填true避免意外改动原图dynamic doc app.Documents.Open(filePath, true, );拿到doc之后访问ModelSpacedynamic ms doc.ModelSpace;2.3 核心代码遍历模型空间AutoCAD里所有实体都在ModelSpace而且它是索引集合可以直接用Count和Item(i)遍历int count ms.Count; for (int i 0; i count; i) { dynamic ent ms.Item(i); string entName ent.ObjectName; // 如AcDbLine, AcDbCircle... }这里有个容易忽略的点字符串类型的实体名ObjectName是区分大小写的。AcDbLine就是直线AcDbCircle是圆AcDbLWPolyline是多段线AcDbPolyline是旧版多段线AcDbArc是圆弧。用switch判断即可。获取线型信息直接访问ent.Linetype。比如string linetype ent.Linetype;但这里有个坑——如果实体的线型是随层ByLayerLinetype属性返回的是“ByLayer”而不是实际图层上的线型名。要拿到真正的线型得去查图层string layerName ent.Layer; dynamic layer doc.Layers.Item(layerName); string realLinetype layer.Linetype; if (linetype ByLayer) linetype realLinetype;按这个思路我整理了一个实用的提取函数把直线、圆、圆弧、多段线都抓出来var points new ListPointData(); for (int i 0; i ms.Count; i) { dynamic ent ms.Item(i); string type ent.ObjectName; PointData pd new PointData(); pd.Layer ent.Layer; pd.Linetype ent.Linetype; if (pd.Linetype ByLayer) { dynamic layer doc.Layers.Item(pd.Layer); pd.Linetype layer.Linetype; } switch (type) { case AcDbLine: pd.StartX ent.StartPoint[0]; pd.StartY ent.StartPoint[1]; pd.EndX ent.EndPoint[0]; pd.EndY ent.EndPoint[1]; pd.Type Line; points.Add(pd); break; case AcDbCircle: pd.CenterX ent.Center[0]; pd.CenterY ent.Center[1]; pd.Radius ent.Radius; pd.Type Circle; points.Add(pd); break; case AcDbLWPolyline: double[] xs ent.Coordinate; // 实际上取坐标要用ent.GetPointAt(i)或直接跑Coordinates break; } }多段线稍微复杂一点。AcDbLWPolyline有两个常用属性Coordinates扁平数组按x,y成对排列和NumberOfVertices。示例case AcDbLWPolyline: { int n ent.NumberOfVertices; object coords ent.Coordinates; // 返回数组 double[] data (double[])coords; for (int j 0; j n; j) { pd.StartX data[j * 2]; pd.StartY data[j * 2 1]; pd.Type PolylineVertex; points.Add(pd); } break; }注意坐标系CAD世界坐标系Y轴朝上单位是毫米。你在WinForms的DataGridView里显示没问题但如果要映射到屏幕坐标系统记得翻转Y轴并缩放。2.4 线型与点位的深层含义为什么偏偏要“点位坐标及线型信息”对自动化行业来说点位坐标能直接生成走位路径线型是判断这条线是虚线还是实线比如虚线可能代表扫码位点划线可能代表安全边界。取这些信息时不只要取几何数据还要把图层也拎出来后续对图层做过滤就方便了。比如有的图纸把工艺边线画在“edge”图层你就能只提取这个图层的点。3. 不装AutoCAD的纯解析方案3.1 理解DXF文件结构有些电脑真不能装AutoCAD但图纸是dxf格式。这时我就用纯解析兜底。DXF的文本结构极其“直白”它按段组织核心是ENTITIES段。所有实体都以组码0加实体名开头后面跟着坐标组码。坐标组码规则很有规律10, 20, 30起点坐标或圆心、插入点11, 21, 31终点坐标或偏移40半径或文字高度6线型名8图层名举例0 LINE 8 0 6 DASHED 10 100.0 20 200.0 30 0.0 11 300.0 21 400.0 31 0.0这段的意思是在图层0线型DASHED起点(100,200,0)终点(300,400,0)的直线。要解析DXF拿着StreamReader一行行读遇到0就切换实体遇到对应组码取下一行值。代码示例public ListEntityInfo ParseDxf(string filePath) { var list new ListEntityInfo(); var lines File.ReadAllLines(filePath); for (int i 0; i lines.Length - 1; i) { int code int.Parse(lines[i].Trim()); string value lines[i 1].Trim(); if (code 0 value LINE) { var ent new EntityInfo { Type Line }; // 循环读LINE子段遇到0跳出 i 2; while (i lines.Length int.Parse(lines[i].Trim()) ! 0) { int subCode int.Parse(lines[i].Trim()); string subVal lines[i 1].Trim(); if (subCode 10) ent.StartX double.Parse(subVal); if (subCode 20) ent.StartY double.Parse(subVal); if (subCode 11) ent.EndX double.Parse(subVal); if (subCode 21) ent.EndY double.Parse(subVal); if (subCode 6) ent.Linetype subVal; if (subCode 8) ent.Layer subVal; i 2; } list.Add(ent); } } return list; }这段代码只做到了单一实体识别生产级解析要小心两件事组码和值可能换行字符串值也可能带空格导致Split出错多边形标签如LWPOLYLINE的顶点坐标用的是10/20组码循环出现每次出现算一个顶点。所以我更建议用现成库。3.2 用现成库降低开发量如果只解析dxf我可以推荐用netDXF这个开源库NuGet里直接搜NetDXF就能装。它能把DXF文件读成DxfDocument然后遍历实体类型比如var doc DxfDocument.Load(filePath); foreach (var line in doc.Lines) { pointList.Add(line.StartPoint, line.EndPoint, line.Layer?.Name, line.Linetype?.Name); } foreach (var pline in doc.LwPolyline().ToArray()) { foreach (var vertex in pline.Vertexes) { pointList.Add(vertex.Position); } }netDXF对DXF格式的支持挺全DWG它读不了但对于设计院导出的交付件dxf来说完全够用。3.3 DWG文件的替代策略DWG是AutoCAD私有格式没有官方库支持。我处理dwg时不装AutoCAD的场景实际上用两个工具配合ODA File Converter免费工具可以把DWG转换成DXF。装完之后用命令行或外部进程调用转换完再解析DXF。LibDWG原来的libredwg一个开源C库有.NET封装但我试用时发现实体类型覆盖不全遇到高版本DWG容易报错。实战中我更推荐ODA File Converter稳定得多。在WinForms里Process.Start调它示例var p new Process(); p.StartInfo.FileName C:\Program Files\ODA\ODAFileConverter\ODAFileConverter.exe; p.StartInfo.Arguments $\{inputDir}\ \{outputDir}\ ACAD2018 DXF 0 1; p.Start(); p.WaitForExit();参数含义分别是输入目录、输出目录、目标CAD版本、输出格式、递归选项。这个方案唯一的缺点是用户的机器得多装一个转换工具但比起为了读几个文件买AutoCAD授权已经划算太多了。4. 数据导出与实战优化4.1 导出格式设计导出点位和线型信息不能直接瞎导。我设计了一个csv格式正好能塞进plc或者MES系统序号,实体类型,图层,线型,起点X,起点Y,终点X,终点Y,半径 1,Line,机械层,Continuous,100.5,200.0,300.4,400.2, 2,Circle,机械层,DASHED,120.0,220.0,,,45.5代码用StringBuilder拼每一行记录一条实体。注意数值的CultureInfo国内环境小数点“.”但导出时要统一用InvariantCulture不然Excel里可能显示成“100,5”。4.2 复杂实体的处理思路直线、圆这些最简单真正让人头疼的是块BlockReference和样条曲线Spline。如果图里点位是被包在块里的用COM遍历ModelSpace时只能看到块引用看不到块里面的线。需要递归展开BlockTableRecord。我的做法是void ProcessBlockReference(dynamic blkRef, ListPointData result) { dynamic blockTable doc.Blocks; dynamic blockRec blockTable.Item(blkRef.Name); foreach (dynamic innerEnt in blockRec) { // 递归处理 ExtractFromEntity(innerEnt, result); } }碰到嵌套块就继续递归最多设几层防止死循环。Spline这类曲线的点位提取如果甲方只要求“采样点”可以用COM的GetPointAtParam或GetPointAtDist方法按其长度采样一批坐标导出否则就只导出特征点。4.3 从“能跑”到“好用”第一版程序能跑通了但打包给现场后问题一堆。后来加了三样东西才稳下来后台线程提取解析大图纸时UI不能卡死我用BackgroundWorker或async/await把遍历和导出放后台主线程只显示进度。进度反馈ms.Count可能有几万个实体处理完一千个就更新进度条让使用者知道没死。文件锁定处理CAD的COM方式打开文件后不关闭会锁死文件。异常捕获的finally里必须doc.Close(false, )否则下次打开必失败。核心导出逻辑完整下大概长这样private void btnExport_Click(object sender, EventArgs e) { var sb new StringBuilder(); sb.AppendLine(序号,实体类型,图层,线型,起点X,起点Y,终点X,终点Y,半径); int idx 1; foreach (var p in pointList) { sb.AppendLine(${idx},{p.Type},{p.Layer},{p.Linetype},{p.StartX},{p.StartY},{p.EndX},{p.EndY},{p.Radius}); } File.WriteAllText(saveFileDialog.FileName, sb.ToString(), Encoding.UTF8); MessageBox.Show(导出成功); }5. 常见问题与避坑记录5.1 常见问题速查表现象原因解决办法COM报“没有注册类”未安装AutoCAD或注册表丢失确认安装CAD改用纯解析方案程序只能x86CAD是x64位数不匹配目标平台改为x64重新编译实体遍历卡死图元数量巨大UI线程阻塞用后台线程并加进度显示Linetype取到“ByLayer”实体线型随层属性未返回实际线型到图层对象里再取一次Linetype打开文件被锁定doc没关闭或没设为只读Documents.Open第二参数传truefinally里CloseDWG解析乱码用文本方式强读DWG二进制改用ODA转换器先转DXF再解析5.2 我的几条实操心得第一COM方案最坑的是你没装AutoCAD的开发环境调试时全靠错误提示一点一点试。建议开发机上装一个“AutoCAD 图形修复管理器”组件最差也要装至少一个版本的正版CAD不然这条路别选。第二DXF解析别自己造轮子。我一开始图新鲜自己写了二三十个if else后来遇到带块嵌套的图纸直接崩。果断用库不要跟格式细节较劲。第三数据导出前一定要和工艺确认坐标系。很多图纸用了虚拟坐标原点不在设备零点。我们的做法是让甲方在CAD里执行UCS命令把用户坐标系原点挪到设备零点再另存图纸。这个前置条件写进对接文档里能少一半麻烦。5.3 从BOM级提取到更高层次如果项目后续有更多空间建议把点位坐标和线型信息做成一个通用的“图纸数据服务”。今天只导出点线明天可能还要提取文字标注、图框、尺寸。把“打开文件—遍历实体—行数据”这三步抽象成接口后面扩展就容易了。我已经把COM和DXF解析封装成了两个Provider主程序只认接口切换时连界面都不用改。写过这套程序之后再看dwg/dxf就跟我自己写的ini文件一样透明。CAD图纸在工控领域本质就是另一种数据库只不过这个库有图层、有块、有坐标、有约束。你想拿什么信息找到对应的组码或者对象属性就行。将来不管遇到VBA写的宏还是Python的pyautocad其实底层都是同一套东东西只是换了个皮囊。本文还有配套的精品资源点击获取