☰
Teigha4 托管代码读写 DWG 实战:从环境搭建到批量处理
2026/10/2 5:32:32 网站建设 项目流程

简介:这份资源面向AutoCAD二次开发人员与.NET开发者,聚焦于不依赖AutoCAD环境直接读写DWG文件这一常见难题。Teigha(原名OpenDWG、DWGdirect)相关开发资料在网络上较为稀缺,作者花费大量精力收集整理,并给出VB.NET读取DWG的完整实现,适合需要做图纸解析、批量处理或格式转换的工程师参考。压缩包共165个文件,约61.5MB,包含62个dll核心库、6个vb与6个cs源码文件、6个exe示例程序,以及sln、csproj、vbproj等工程文件,另附config、xml、txt等配置与说明文档,并打包了DWGdirect_NET_3_02、Teigha_Net_40010及配套帮助文件。目前已有3825人学习下载。读者可从中获得可编译运行的VB.NET与C#示例源码、Teigha各版本类库与帮助文档,以及工程配置与依赖引用方式,便于快速搭建读写DWG的开发环境并理解接口调用思路。

1. Teigha4 读写 DWG:从托管代码到图纸数据库的落地路径

如果你手上有一批 DWG 图纸,需要在不装 AutoCAD 的服务器上批量读取图层、块表、实体坐标,甚至回写修改,那么 Teigha4(现属 ODA 的 Drawings SDK)几乎是绕不开的选择。它把 DWG 当成一个可编程的数据库来暴露,而不是一个只能靠桌面软件打开的黑匣子。标题里提到的 DWGdirect_NET_3_02 和 Teigha_Net_40010,本质是同一套 .NET 封装在不同版本阶段的产物,前者是早期命名,后者是 4.x 时代的托管程序集。配套的 vb.net、C# 原码,解决的是「怎么在托管环境里把非托管图纸引擎调起来」这件事。这篇笔记面向的是需要把 DWG 读写嵌进自己系统的工程师,尤其是做上位机、做图纸转换、做批量处理的场景。下面从环境搭建讲到实体遍历,再到回写和排错,尽量把每一步落到能复现的命令和参数上。

2. Teigha4 托管环境搭建:程序集、平台与第一个能跑通的读取程序

2.1 为什么是 Teigha4 而不是直接调 AutoCAD

先说选型理由。AutoCAD 的 COM 自动化依赖本机安装完整 CAD,进程外调用慢,批量处理时容易卡死,而且授权成本高。Teigha4 提供的是进程内 SDK,直接解析 DWG 二进制格式,不依赖 CAD 进程,读取一张中等复杂度的图纸通常在几十到几百毫秒级别。它支持 DWG 的多个版本(R14 到较新的格式),并且同时提供 C++、.NET、Java 等绑定。对于 C# 和 vb.net 开发者,Teigha_Net_40010 里的托管程序集就是入口。

需要区分两个概念:DWGdirect_NET_3_02 是较早的 .NET 封装版本,命名空间和 API 与后来的 Teigha_Net 有差异;Teigha_Net_40010 对应 4.x 系列,程序集命名通常带 TD_ 前缀,比如 TD_Mgd.dll、TD_DbMgd.dll。两者不要混用,引用错了会出现类型找不到或方法签名不匹配。

2.2 程序集引用与平台目标设置

Teigha 的 .NET 封装底层是非托管 DLL,所以平台目标必须明确。常见做法是把项目平台设为 x64 或 x86,不要用 Any CPU,否则运行时会报「无法加载 DLL」或「试图加载格式不正确的程序」。

在 C# 项目里,需要引用的核心程序集一般包括:

程序集作用
TD_Mgd.dll托管入口,服务、数据库、事务等
TD_DbMgd.dll数据库对象、实体、符号表
TD_GeMgd.dll几何计算相关
TD_RootMgd.dll基础类型与运行时

引用后,把 Teigha 的非托管 DLL 目录加入 PATH,或者复制到输出目录。下面是一个最小的 C# 读取程序,打开 DWG 并输出模型空间里的实体数量。

using System; using Teigha.Runtime; using Teigha.DatabaseServices; class Program { static void Main(string[] args) { // 初始化 Teigha 运行时,必须在使用任何数据库对象前调用 using (new Services()) { string dwgPath = @"D:\samples\demo.dwg"; using (Database db = new Database(false, true)) { // 第二个参数 true 表示以只读方式打开,避免误写 db.ReadDwgFile(dwgPath, FileOpenMode.OpenForReadAndAllShare, true, null); using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord modelSpace = (BlockTableRecord)tr.GetObject( bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); int count = 0; foreach (ObjectId id in modelSpace) { Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity; if (ent != null) { count++; Console.WriteLine(ent.GetType().Name + " handle=" + ent.Handle); } } Console.WriteLine("实体总数: " + count); tr.Commit(); } } } } }

逻辑说明:Services是 Teigha 运行时的入口,负责加载非托管引擎,必须最先创建、最后释放。Database代表一个 DWG 文件,ReadDwgFile的第三个参数控制是否允许写回,只读场景传 true 更安全。Transaction是 Teigha 的对象访问机制,所有数据库对象都要通过事务获取,直接持有 ObjectId 而不开事务会抛异常。BlockTableRecord.ModelSpace是模型空间,图纸里的可见实体基本都在这里。

参数说明:FileOpenMode.OpenForReadAndAllShare表示只读且允许其他进程同时打开,适合服务器批量读取。如果后续要写回,改成OpenForReadAndWrite,但要注意文件锁。db.ReadDwgFile的最后一个参数是密码,加密图纸才需要。

vb.net 的写法结构一致,只是语法不同:

Imports Teigha.Runtime Imports Teigha.DatabaseServices Module Module1 Sub Main() Using svc As New Services() Dim db As New Database(False, True) db.ReadDwgFile("D:\samples\demo.dwg", FileOpenMode.OpenForReadAndAllShare, True, Nothing) Using tr As Transaction = db.TransactionManager.StartTransaction() Dim bt As BlockTable = DirectCast(tr.GetObject(db.BlockTableId, OpenMode.ForRead), BlockTable) Dim ms As BlockTableRecord = DirectCast(tr.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForRead), BlockTableRecord) For Each id As ObjectId In ms Dim ent As Entity = TryCast(tr.GetObject(id, OpenMode.ForRead), Entity) If ent IsNot Nothing Then Console.WriteLine(ent.GetType().Name) End If Next tr.Commit() End Using End Using End Sub End Module

2.3 第一个程序跑不通时先查这三处

第一,平台目标。Any CPU 在 64 位系统上默认以 64 位运行,但如果你引用的非托管 DLL 是 32 位,就会报 BadImageFormatException。第二,PATH 里是否包含 Teigha 的非托管 DLL 目录,缺 DLL 时异常信息通常是「找不到指定的模块」。第三,Services 是否在 Database 之前创建,顺序反了会直接崩溃。这三点覆盖了新手八成以上的启动失败。

3. 遍历 DWG 实体与符号表:图层、块、文字和坐标怎么取

3.1 实体遍历的通用模式与类型判断

DWG 里的可见对象都继承自 Entity,常见类型包括 Line、Circle、Arc、Polyline、Text、MText、Insert(块引用)。遍历时不要假设只有这几种,用基类接收再判断具体类型更稳。下面这段代码演示如何按类型提取关键数据。

foreach (ObjectId id in modelSpace) { Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity; if (ent == null) continue; switch (ent) { case Line line: Console.WriteLine($"Line: {line.StartPoint} -> {line.EndPoint}"); break; case Circle circle: Console.WriteLine($"Circle: center={circle.Center}, r={circle.Radius}"); break; case Polyline pl: Console.WriteLine($"Polyline: vertices={pl.NumberOfVertices}"); for (int i = 0; i < pl.NumberOfVertices; i++) { var pt = pl.GetPoint2dAt(i); Console.WriteLine($" v{i}: {pt}"); } break; case Text txt: Console.WriteLine($"Text: {txt.TextString} at {txt.Position}"); break; case Insert ins: Console.WriteLine($"BlockRef: {ins.Name}, pos={ins.Position}"); break; } }

逻辑说明:switch模式匹配是 C# 7 以后的写法,vb.net 里用TypeOf ... Is判断。Polyline 的顶点用GetPoint2dAt取二维点,如果是三维多段线要用Polyline3d并遍历Vertex3d。Insert 代表块引用,它的Name是块名,实际几何在块表记录里,需要再开一次事务取 BlockTableRecord。

参数说明:OpenMode.ForRead表示只读打开,写回时改ForWrite。GetPoint2dAt返回的是 OCS 坐标,如果图纸有非默认的挤出方向,需要转换到 WCS,常见做法是用pl.Elevation和pl.Normal做变换。

3.2 图层表与块表的读取

图层信息在 LayerTable 里,块定义在 BlockTable 里。读取图层可以拿到颜色、线型、是否冻结等属性,批量出图或做图层过滤时很有用。

LayerTable lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); foreach (ObjectId layerId in lt) { LayerTableRecord ltr = (LayerTableRecord)tr.GetObject(layerId, OpenMode.ForRead); Console.WriteLine($"Layer: {ltr.Name}, Color={ltr.Color}, Off={ltr.IsOff}, Frozen={ltr.IsFrozen}"); }

逻辑说明:LayerTable 是符号表之一,遍历得到每个 LayerTableRecord。Color是 Teigha 的颜色对象,转成 RGB 需要调ltr.Color.ColorValue或ltr.Color.Red/Green/Blue。IsOff和IsFrozen分别对应关闭和冻结,做可见性判断时两个都要看。

块表遍历类似,但块表里包含模型空间和图纸空间两个特殊记录,过滤时用ltr.Name判断,模型空间的名字通常是*Model_Space。

3.3 坐标转换与单位问题

DWG 本身不强制单位,图纸可能以毫米、米或英寸绘制。Teigha 读出来的坐标是图纸单位,不做换算。如果下游系统需要米,得自己判断。常见做法是读db.Insunits,它对应 INSUNITS 系统变量,值 4 是毫米,6 是米。但很多图纸这个值是 0(无单位),这时候只能靠图框尺寸或人工约定。血泪经验是:不要假设单位,先输出几个已知尺寸的实体坐标,和图纸标注对比一下再决定换算系数。

4. 写回 DWG:新增实体、修改属性与保存的完整流程

4.1 写操作的事务与提交

Teigha 的写操作必须在事务里,并且对象要以OpenMode.ForWrite打开。新增实体时,先创建对象,再AppendEntity到块表记录,最后AddNewlyCreatedDBObject通知事务。顺序错了对象不会出现在文件里。

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 newLine = new Line(new Point3d(0, 0, 0), new Point3d(100, 100, 0)); newLine.Layer = "0"; // 指定图层,图层必须已存在 ms.AppendEntity(newLine); tr.AddNewlyCreatedDBObject(newLine, true); tr.Commit(); } db.SaveAs(@"D:\samples\out.dwg", DwgVersion.Current);

逻辑说明:AppendEntity把实体加入块表记录,AddNewlyCreatedDBObject让事务跟踪这个新对象,缺一不可。Commit提交事务,但此时还没写文件,要调db.SaveAs才落盘。DwgVersion.Current表示用当前 SDK 支持的较新版本,如果要兼容旧 CAD,改成DwgVersion.AC1015(2000 格式)之类。

参数说明:newLine.Layer赋值的图层名必须已经在 LayerTable 里存在,否则保存时会报错或实体丢失。需要新建图层时,先往 LayerTable 里加 LayerTableRecord。

4.2 修改已有实体属性

修改和新增类似,区别是对象从ForRead改成ForWrite,改完属性后事务提交即可。

Entity ent = tr.GetObject(id, OpenMode.ForWrite) as Entity; if (ent is Circle c) { c.Radius = c.Radius * 2; // 半径翻倍 c.ColorIndex = 1; // 红色 }

逻辑说明:ColorIndex是 AutoCAD 颜色索引,1 是红,2 是黄,3 是绿,256 是随层。用真彩色则设c.Color。修改后不需要额外调用,事务提交时自动写回。

4.3 保存格式与版本兼容

SaveAs的第二个参数决定输出 DWG 版本。常见值:AC1015对应 2000,AC1018对应 2004,AC1021对应 2007,AC1024对应 2010,AC1027对应 2013。选太低会丢新特性,选太高旧 CAD 打不开。如果下游是 LibreCAD 或某些国产 CAD,建议存 2004 或 2007 格式,兼容性更稳。注意SaveAs会覆盖目标文件,批量处理时先检查文件是否存在,必要时加时间戳后缀。

5. 避坑与排查:Teigha4 读写 DWG 最常见的五类翻车

5.1 现象:程序启动即崩溃,无托管异常信息

原因:Services 未初始化,或非托管 DLL 位数与进程不匹配。Teigha 的异常有时不抛到托管层,直接进程退出。解决:确认new Services()在最先执行,检查项目平台目标与 DLL 位数一致,用 Dependency Walker 或dumpbin /headers看非托管 DLL 是 32 位还是 64 位。

5.2 现象:读取时抛 eInvalidInput 或 eNotOpenForWrite

原因:对象以只读打开却试图修改,或者 ObjectId 已失效。常见于跨事务持有 ObjectId,前一个事务提交后再用。解决:所有对象访问都在同一个事务内完成,需要跨事务时重新GetObject。写操作确认OpenMode.ForWrite。

5.3 现象:中文图层名或文字乱码

原因:DWG 内部字符串编码与 .NET 默认编码不一致,早期版本尤其明显。解决:Teigha 4.x 的 .NET 封装一般返回 Unicode,但如果图纸是旧版本且用了非标准代码页,可能需要在读取后做编码转换。更稳的做法是升级到较新的 Teigha_Net 版本,旧版 DWGdirect_NET_3_02 对中文支持较弱。

5.4 现象:保存后的文件 AutoCAD 提示「图形文件无效」

原因:保存时事务未正确提交,或实体引用了不存在的图层/线型。解决:确保tr.Commit()在SaveAs之前执行,新增实体引用的符号表记录必须已存在。可以用db.Audit做一次自检,看有没有修复信息。

5.5 现象:批量处理时内存持续增长直至崩溃

原因:Database 和 Services 未释放,或者事务未 Dispose。Teigha 的非托管内存不会自动被 GC 回收。解决:每个文件处理完显式db.Dispose(),Services 在整个批次结束后释放。用using包裹 Database 和 Transaction,避免遗漏。

6. 进阶技巧:用 Teigha4 做 DWG 到结构化数据的批量管道

如果只是单张读取,上面的代码够了。但实际项目往往是几百上千张图纸,需要抽图层、抽块、抽文字,最后落到数据库或 CSV。我一般会搭一个三段式管道:扫描目录、逐文件解析、结果落库。关键技巧是复用 Services,不要每个文件都新建,Services 创建一次即可,Database 每个文件独立。

using (new Services()) { foreach (string file in Directory.EnumerateFiles(@"D:\dwg", "*.dwg")) { try { using (Database db = new Database(false, true)) { db.ReadDwgFile(file, FileOpenMode.OpenForReadAndAllShare, true, null); using (Transaction tr = db.TransactionManager.StartTransaction()) { // 抽取逻辑,结果写入 List<Record> tr.Commit(); } } } catch (Exception ex) { // 记录失败文件,继续下一张,不要整体中断 Console.WriteLine(file + " 失败: " + ex.Message); } } }

逻辑说明:Services只创建一次,避免反复加载非托管引擎的开销。每个文件用独立 Database,异常捕获后继续,保证批量任务不因单张图纸损坏而中断。抽取结果可以攒够一批再写数据库,减少 IO。

参数说明:Directory.EnumerateFiles比GetFiles更省内存,适合大目录。如果图纸有密码,ReadDwgFile最后一个参数传密码字符串,但批量场景建议先过滤加密文件。

验证方法:抽几张图纸,用 AutoCAD 或 ODA Viewer 打开,对比实体数量和关键坐标。我习惯先跑一个只统计实体类型和数量的版本,确认解析没漏,再开属性抽取。这样出问题时容易定位是解析层还是业务层。

最后说个习惯:Teigha 的版本和程序集命名比较乱,DWGdirect_NET_3_02 和 Teigha_Net_40010 的 API 有差异,网上搜到的代码不一定能直接跑。我一般会先写一个最小读取程序,确认 Services、Database、Transaction 这条链路通了,再往上堆业务逻辑。这个习惯帮我省了很多返工。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询