甲方甩来一张 DWG你的程序却只认 DXFlibdxfrw 帮你把图纸数据抠出来【免费下载链接】libdxfrwC library to read and write DXF/DWG files项目地址: https://gitcode.com/gh_mirrors/li/libdxfrw做 CAD 二次开发或者给制造、BIM 系统做数据对接的工程师几乎都撞过同一堵墙甲方发来.dwg图纸你的解析模块只吃.dxf想从图纸里批量提取标注文字入库翻遍商业 SDK 报价单一年授权费够买几台工作站就算咬着牙买了 SDK还得被授权服务器的网络校验折腾到怀疑人生。别急着掏钱。C 开源库libdxfrwDXF/DWG 读写库能免费解决这条链路里的绝大多数问题它既能读写 ASCII 和二进制两种 DXF也能解析从 R14 一路到 V2018 的 DWG 文件还自带两个开箱即用的命令行工具。本文不打算给你铺一段冗长的库简介而是直接带你按真实业务需求走一遍装环境、写最小读写器、做格式转换、抠文字、写格式无关的导入器每一段代码都能复制到工程里跑。先花 30 秒看清它的能力边界动代码之前先明确一件事这个库能做什么、不能做什么。边界清楚了后面才不会走弯路。维度支持情况说明DXF 读取✅ ASCII / 二进制两种编码格式都能读DXF 写入✅ ASCII / 二进制通过dxfRW::write()指定DXF 版本AC1009R12到 AC1032V2018枚举定义在drw_base.h的DRW::VersionDWG 读取✅ 只读内置 6 个版本化阅读器15R14、18R2004、21R2007、24R2010、27R2013/R2015、32R2018DWG 写入❌ 不支持这是明确边界别在写入上浪费时间字符编码✅ 自动转换内置 CP932/936/949/950 四张码表回调解口里拿到的基本是 UTF-8构建方式CMake / Autotools / Visual Studio / Docker仓库里有现成的四套构建配置自带工具dwg2dxf转换、dwg2text提取文本两个可独立编译的命令行程序许可协议GPL v2或更高版本商用集成前务必评估传染性条款一句话总结DXF 双向、DWG 单向。如果你的需求是把 DWG 数据读进来用它完全够如果你指望程序里直接生成 DWG 交给甲方趁早换思路——先写 DXF再用dwg2dxf转换一样能交付。三十分钟跑通读写链路第一步两分钟完成编译安装仓库推荐 CMake 路线四条命令收工git clone https://gitcode.com/gh_mirrors/li/libdxfrw cd libdxfrw mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease cmake --build . --config Release sudo cmake --build . --config Release --target install编译依赖只有一个libiconv字符编码转换用。Debian/Ubuntu 系装libiconv-hook-devRedHat 系装libiconv-devel在 glibc 较新的 Linux 上iconv 通常随系统自带find_package(Iconv)会直接命中。安装完成后头文件在/usr/local/include/静态库是/usr/local/lib/libdxfrw.a链接时记得带上-ldxfrw -liconv。第二步写一个最小 DXF 读取器先搞懂这个库的核心机制一句话大白话它不把数据喂给你而是把数据扔给你挑。你继承DRW_Interface接口实现自己关心的回调函数解析器每遇到一条线、一个圆、一段文字就会调用对应的回调把实体对象丢过来。你不想要的数据把回调写空就行解析器照样正常往下走。下面这个 40 来行的程序能把 DXF 里所有线段、圆和文字全部打印出来#include cstdio #include libdxfrw.h class MyReader : public DRW_Interface { public: // —— 以下回调本次不关心全部空实现 —— virtual void addHeader(const DRW_Header*) {} virtual void addLType(const DRW_LType) {} virtual void addLayer(const DRW_Layer) {} virtual void addDimStyle(const DRW_Dimstyle) {} virtual void addVport(const DRW_Vport) {} virtual void addTextStyle(const DRW_Textstyle) {} virtual void addAppId(const DRW_AppId) {} virtual void addBlock(const DRW_Block) {} virtual void setBlock(int) {} virtual void endBlock() {} virtual void addPoint(const DRW_Point) {} virtual void addRay(const DRW_Ray) {} virtual void addXline(const DRW_Xline) {} virtual void addArc(const DRW_Arc) {} virtual void addEllipse(const DRW_Ellipse) {} virtual void addLWPolyline(const DRW_LWPolyline) {} virtual void addPolyline(const DRW_Polyline) {} virtual void addSpline(const DRW_Spline*) {} virtual void addKnot(const DRW_Entity) {} virtual void addInsert(const DRW_Insert) {} virtual void addTrace(const DRW_Trace) {} virtual void add3dFace(const DRW_3Dface) {} virtual void addSolid(const DRW_Solid) {} virtual void addMText(const DRW_MText) {} virtual void addDimAlign(const DRW_DimAligned*) {} virtual void addDimLinear(const DRW_DimLinear*) {} virtual void addDimRadial(const DRW_DimRadial*) {} virtual void addDimDiametric(const DRW_DimDiametric*) {} virtual void addDimAngular(const DRW_DimAngular*) {} virtual void addDimAngular3P(const DRW_DimAngular3p*) {} virtual void addDimOrdinate(const DRW_DimOrdinate*) {} virtual void addLeader(const DRW_Leader*) {} virtual void addHatch(const DRW_Hatch*) {} virtual void addViewport(const DRW_Viewport) {} virtual void addImage(const DRW_Image*) {} virtual void linkImage(const DRW_ImageDef*) {} virtual void addComment(const char*) {} virtual void writeHeader(DRW_Header) {} virtual void writeBlocks() {} virtual void writeBlockRecords() {} virtual void writeEntities() {} virtual void writeLTypes() {} virtual void writeLayers() {} virtual void writeTextstyles() {} virtual void writeVports() {} virtual void writeDimstyles() {} virtual void writeAppId() {} // —— 以下才是本次真正关心的数据 —— virtual void addLine(const DRW_Line d) { printf(LINE (%.2f, %.2f) - (%.2f, %.2f)\n, d.basePoint.x, d.basePoint.y, d.secPoint.x, d.secPoint.y); } virtual void addCircle(const DRW_Circle d) { printf(CIRCLE 圆心(%.2f, %.2f) 半径 %.2f\n, d.basePoint.x, d.basePoint.y, d.radious); } virtual void addText(const DRW_Text d) { printf(TEXT %s\n, d.text.c_str()); } }; int main(int argc, char* argv[]) { if (argc 2) { printf(用法: %s file.dxf\n, argv[0]); return 1; } MyReader r; dxfRW dxf(argv[1]); if (!dxf.read(r, false)) { printf(读取失败\n); return 1; } return 0; }编译并运行g reader.cpp -o reader -ldxfrw -liconv ./reader drawing.dxf运行预期你会看到文件里每条线段、每个圆、每段单行文字的坐标与内容被依次打印出来。两处值得注意read()的第二个参数ext传false意思是不做 2D 投影保留原始 3D 坐标实体类里basePoint/secPoint/radious这些成员名是库里定义的对应 DXF 的 10/11/40 组码写代码前可以翻一眼src/drw_entities.h避免记错拼写。第三步写一个最小 DXF 写出器反向操作同样走回调只是角色从接收方变成供给方。你要实现一组write*回调在writeEntities()里往库的写入口塞实体#include libdxfrw.h class MyWriter : public DRW_Interface { public: bool exportFile(const char* name) { dxfW new dxfRW(name); bool ok dxfW-write(this, DRW::AC1015, false); // 2000 版 ASCII delete dxfW; return ok; } virtual void writeHeader(DRW_Header) {} virtual void writeBlocks() {} virtual void writeBlockRecords() {} virtual void writeLTypes() {} virtual void writeLayers() {} virtual void writeTextstyles() {} virtual void writeVports() {} virtual void writeDimstyles() {} virtual void writeAppId() {} virtual void writeEntities() { DRW_Line l; l.basePoint DRW_Coord(0, 0, 0); l.secPoint DRW_Coord(100, 100, 0); dxfW-writeLine(l); DRW_Circle c; c.basePoint DRW_Coord(50, 50, 0); c.radious 25; dxfW-writeCircle(c); } private: dxfRW* dxfW; // 写入口先 new 出来write() 驱动再销毁 }; int main() { MyWriter w; return w.exportFile(out.dxf) ? 0 : 1; }编译命令同上加上-ldxfrw -liconv。运行预期生成一个out.dxf直接用 CAD 或./reader out.dxf打开能看到一条从原点 (0,0) 到 (100,100) 的线段和一个圆心 (50,50)、半径 25 的圆。这里解释一下为什么版本号要写成DRW::AC1015这个库的版本枚举不是2000/2004这种直观名字而是 DXF 文件头里的ACAD 版本编码——AC1015就是 ACAD 2000AC1018是 2004AC1021是 2007AC1024是 2010。写的时候别拿R2000这种臆造的枚举去对号入座编译会直接报错。三个业务场景从能用到好用场景一把一摞 DWG 批量转成老版本 DXF需求产线 ERP 系统只认 R12 老格式 DXF甲方却按月批量投喂 R2007 以上的 DWG。方案用仓库自带的dwg2dxf零代码搞定。它内部就是一个DRW_Interface把 DWG 读进来、另一个DRW_Interface把数据写成 DXF——读和写复用同一套回调契约所以天然支持版本迁移R2015 的图纸能给你吐成 R12。./dwg2dxf input.dwg -R12 output.dxf参数释义-R12 / -v2000 / -v2004 / -v2007 / -v2010输出 DXF 的目标版本分别对应AC1009 / AC1015 / AC1018 / AC1021 / AC1024-b输出二进制DXF默认 ASCII-B批量模式——输入参数换成一个文本文件每行一个 DWG 完整路径最后一个参数改为输出目录同名输出-y输出文件已存在时静默覆盖不加则交互询问。边界提醒dwg2dxf的输出版本只到 V2010且转换依赖对源 DWG 版本的解析能力——遇到 R2018 之后的新格式先确认你的库版本有没有对应的 readerdwgreader32版本不支持会明确报错而不是静默产出坏文件。场景二把图纸上的文字批量抠出来需求几千张图纸的标题栏、标注、技术要求要抽出来做检索库人工抄录不现实。方案dwg2text一条命令把 DWG/DXF 里的TEXT、MTEXT全部打到标准输出配合管道就能入库./dwg2text input.dwg text_output.txt关键在编码这是新手最容易误判的点。图纸里的中文字符串在文件内可能以 CP936简体 GB2312、CP932日文 Shift-JIS、CP949韩文、CP950繁体 Big5等 code page 存储libdxfrw内部通过src/intern/drw_textcodec.cpp和四张码表完成自动转换你从addText()/addMText()回调里拿到的std::string通常已经是 UTF-8。注意事项如果抠出来的中文是乱码优先检查源文件的 code page 声明DXF 文件头$DWGCODEPAGE变量再确认你的终端/文件输出端编码——多数情况下是输出端显示问题而不是库没转对。改源码前先做这两步排查能省一大半排查时间。场景三写一个格式无关的统一导入器需求业务系统上游既会来 DXF 也会来 DWG你不希望为两种格式维护两套解析逻辑。方案dxfRW管 DXFdwgR管 DWG两者接口签名几乎一致都接受同一个DRW_Interface*。于是可以写一个入口按扩展名分发#include string #include libdxfrw.h #include libdwgr.h // 上层业务只认 DRW_Interface底层格式差异被完全屏蔽 bool importFile(const char* path, DRW_Interface* cb) { std::string p(path); size_t n p.size(); if (n 4 p.substr(n - 4) .dxf) return dxfRW(path).read(cb, false); if (n 4 p.substr(n - 4) .dwg) return dwgR(path).read(cb, false); return false; }边界提醒DWG 分支失败时用dwgR自带的诊断接口定位问题——getError()返回DRW::error枚举BAD_OPEN打不开、BAD_VERSION版本不支持、BAD_READ_ENTITIES实体段损坏等getVersion()返回识别到的 DWG 版本。这比对着二进制流瞎猜高效得多。这个接口契约统一 双入口分发的设计是整个库最值得借鉴的架构——你的业务层从此只依赖一种回调协议。避坑记录新手最容易翻车的 5 个点错误做法正确做法原因只实现addLine/addCircle就实例化DRW_Interface的纯虚函数一个都不能少C 规则含未实现纯虚函数的类无法实例化编译直接失败用DRW::R2000、DRW::V2004当版本号用AC1009/AC1015/AC1018/AC1021/AC1024版本枚举是 DXF 的 ACAD 编码定义在drw_base.h臆造名字过不了编译read()的第二个参数随手传true需要原始 3D 坐标就传falseexttrue会对实体应用 extrusion 投影到 2D坐标值会被改写用dxfRW直接写 DWG先写 DXF再用dwg2dxf转出目标格式库明确不支持 DWG 写入这是设计边界别耗时间硬试中文乱码就怀疑库、改源码先查源文件 code page再查输出端编码转换逻辑在drw_textcodec.cpp多数乱码是显示环节问题每一条背后都是一个真实的编译报错或数据异常记下来能帮你省掉不少排查时间。两个容易被忽略的效率开关开关一椭圆离散精度调优。导出的 DXF 如果下游工具不支持椭圆比如老版本 R12 的 CAD库会把椭圆离散成多段线。默认分段数偏低大尺寸椭圆会棱角分明。用dxf.setEllipseParts(64)调大分段数即可逼近真实椭圆代价是文件体积略微增大——需要质量还是体积看场景自己权衡。开关二调试输出与错误定位。解析失败时dxf.setDebug(DRW::DEBUG)dwgR同样支持会输出解析进度和关键状态配合dwgR::getError()/dwgR::getVersion()能在几秒钟内判断是文件版本不支持还是文件本身损坏而不是对着日志瞎猜。想继续深入往这几个方向走官方示例当教程读dwg2dxf/和dwg2text/两个工具目录就是完整的参考实现——dx_iface.cpp里你能看到所有实体类型的读写分发逻辑dx_iface.h里那个dxfRW* dxfW指针的用法和上面写代码如出一辙接口契约是学习地图src/drw_interface.h是整套回调协议的定义先通读一遍再去看实现效率最高单元测试当行为文档tests/目录覆盖实体读写、多段线、文字、表格、版本兼容、错误处理等场景cd build ctest一条命令跑完哪个功能应该是什么行为一目了然参与贡献项目由原作者 Rallaz 发起社区持续维护。常规流程clone 仓库、开分支、改代码、补测试、提 PR注意代码风格跟随现有文件新增实体类型记得补对应测试许可证要上心GPL v2或更高版本意味着如果你的项目要闭源商用直接链接静态库会触发传染性条款务必先让法务评估或者考虑进程隔离等规避方式。现在就能开始的三个小动作回顾一下你现在的弹药库四行命令装好环境一个继承DRW_Interface的读取器能把 DXF 里的线、圆、文字全部打出来一个 30 行的写出器能生成 CAD 可直接打开的图纸dwg2dxf/dwg2text覆盖了转换和抽文字两个高频需求还掌握了版本枚举、ext参数、编码排查这几个关键细节。接下来不用再读任何文档直接动手按本文四行命令编译安装libdxfrw找一张手头的 DWG跑./dwg2dxf 你的图纸.dwg -v2010 输出.dxf亲眼看着闭源格式变成你能解析的文本格式把第 2 节的读取器粘进你的工程换成addText只打印标注文字——你离图纸数据进库只差这一次git clone的距离。【免费下载链接】libdxfrwC library to read and write DXF/DWG files项目地址: https://gitcode.com/gh_mirrors/li/libdxfrw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考