libdxfrw 实战手册:用 C++ 免费读写 DXF、解析 DWG 的完整路线图
【免费下载链接】libdxfrwC++ library to read and write DXF/DWG files项目地址: https://gitcode.com/gh_mirrors/li/libdxfrw
"客户发来的图纸是.dwg,我们软件只认.dxf,怎么办?"——这几乎是每个做 CAD 二次开发的工程师都撞过的墙。买商业 SDK?年费劝退;自己逆向二进制格式?DWG 的私有编码足以耗掉你几个月。其实还有第三条路:libdxfrw,一个开源的 C++ 库,能读写 ASCII 与二进制两种 DXF,并解析 R14 到 2015 的 DWG 文件。本文就是一份带你把这条路走通的完整教程。
一、它从哪来,凭什么能省下六位数预算
故事要从 2011 年说起。作者 Rallaz 在做 LibreCAD 的 DXF/DWG 支持时,发现商业方案要么贵、要么闭源,索性自己写了一个轻量库,后来由社区持续接手维护至今。它的定位很朴素:不追求替代 AutoCAD,只负责把图纸文件"读进内存、写回磁盘"这一件事做扎实。
和商业 SDK 比,它的优势一眼可见:
- 💰 免费开源,协议为 GPL v2(或更高版本),没有授权费,只有"你的代码要兼容 GPL"这一条契约需要留意;
- 📦 依赖极简,核心只有 iconv 一个外部依赖(字符编码转换),不会拖入一整套重型框架;
- 🧩 自带
dwg2dxf与dwg2text两个可执行工具,既是生产力工具,也是现成的 API 教学案例。
一句话总结:商业 SDK 是"全套装修交付",libdxfrw 是"毛坯房 + 设计图",后者需要你亲手砌墙,但便宜得几乎等于免费。
二、五分钟跑通第一个程序:安装、读、写
第一步:三行命令完成编译安装
推荐 CMake 路线,依赖只需build-essential、cmake和libiconv:
git clone https://gitcode.com/gh_mirrors/li/libdxfrw cd libdxfrw && mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release cmake --build . --config Release sudo cmake --build . --config Release --target install装完后头文件在/usr/local/include/,静态库是/usr/local/lib/libdxfrw.a,链接时加上-ldxfrw -liconv即可。
第二步:写一个最小读取器
整个库的心脏是回调接口DRW_Interface:你只负责"收到实体后做什么",文件怎么切分、版本怎么识别,全由dxfRW::read()驱动。注意一个坑:DRW_Interface里几十个纯虚函数必须全部实现,否则编译直接失败,偷懒不行的。
#include <cstdio> #include "libdxfrw.h" class GeoDumper : public DRW_Interface { public: void addHeader(const DRW_Header*) override {} void addLType(const DRW_LType&) override {} void addLayer(const DRW_Layer&) override {} void addDimStyle(const DRW_Dimstyle&) override {} void addVport(const DRW_Vport&) override {} void addTextStyle(const DRW_Textstyle&) override {} void addAppId(const DRW_AppId&) override {} void addBlock(const DRW_Block&) override {} void setBlock(const int) override {} void endBlock() override {} void addLine(const DRW_Line& e) override { printf("LINE (%g, %g) -> (%g, %g)\n", e.basePoint.x, e.basePoint.y, e.secPoint.x, e.secPoint.y); } void addCircle(const DRW_Circle& e) override { printf("CIRCLE center(%g, %g) r=%g\n", e.basePoint.x, e.basePoint.y, e.radious); } // 其余 add*/linkImage/addComment 均留空实现 void addPoint(const DRW_Point&) override {} void addRay(const DRW_Ray&) override {} void addXline(const DRW_Xline&) override {} void addArc(const DRW_Arc&) override {} void addEllipse(const DRW_Ellipse&) override {} void addLWPolyline(const DRW_LWPolyline&) override {} void addPolyline(const DRW_Polyline&) override {} void addSpline(const DRW_Spline*) override {} void addKnot(const DRW_Entity&) override {} void addInsert(const DRW_Insert&) override {} void addTrace(const DRW_Trace&) override {} void add3dFace(const DRW_3Dface&) override {} void addSolid(const DRW_Solid&) override {} void addMText(const DRW_MText&) override {} void addText(const DRW_Text&) override {} void addDimAlign(const DRW_DimAligned*) override {} void addDimLinear(const DRW_DimLinear*) override {} void addDimRadial(const DRW_DimRadial*) override {} void addDimDiametric(const DRW_DimDiametric*) override {} void addDimAngular(const DRW_DimAngular*) override {} void addDimAngular3P(const DRW_DimAngular3p*) override {} void addDimOrdinate(const DRW_DimOrdinate*) override {} void addLeader(const DRW_Leader*) override {} void addHatch(const DRW_Hatch*) override {} void addViewport(const DRW_Viewport&) override {} void addImage(const DRW_Image*) override {} void linkImage(const DRW_ImageDef*) override {} void addComment(const char*) override {} void writeHeader(DRW_Header&) override {} void writeBlocks() override {} void writeBlockRecords() override {} void writeEntities() override {} void writeLTypes() override {} void writeLayers() override {} void writeTextstyles() override {} void writeVports() override {} void writeDimstyles() override {} void writeAppId() override {} }; int main(int argc, char** argv) { if (argc < 2) return 1; GeoDumper dumper; dxfRW file(argv[1]); // 第二个参数传 false:保留原始 3D 坐标,不做 2D 投影 return file.read(&dumper, false) ? 0 : 1; }编译命令:
g++ dump.cpp -o dump -ldxfrw -liconv ./dump drawing.dxf屏幕上滚动出每条直线和圆的坐标,你的第一个 DXF 读取程序就此诞生。
第三步:反向操作,把实体写回文件
生成 DXF 同样是回调驱动,只是方向相反——库会依次调用你的writeHeader、writeLayers、writeBlocks、writeEntities等函数,你只需在writeEntities里用dxfWriter指针把实体"塞"出去:
class DemoWriter : public GeoDumper { public: void save(const char* name) { dxfRW out(name); out.write(this, DRW::AC1015, false); // AC1015 = AutoCAD 2000 ASCII } void writeEntities() override { DRW_Line line; line.basePoint = DRW_Coord(0, 0, 0); line.secPoint = DRW_Coord(100, 100, 0); dxfWriter->writeLine(&line); DRW_Circle circle; circle.basePoint = DRW_Coord(50, 50, 0); circle.radious = 25; dxfWriter->writeCircle(&circle); } dxfRW* dxfWriter; };生成的 DXF 可以直接丢进任意 CAD 软件打开。注意版本号要用DRW::AC1015这种 AC 开头的编码(AC1009=R12、AC1015=2000、AC1018=2004、AC1021=2007、AC1024=2010、AC1027=2013),记不住没关系,src/drw_base.h里注释写得清清楚楚。
三、一张表看清它能做什么、适合谁
| 能力维度 | 支持情况 | 说明 |
|---|---|---|
| DXF 读取 | ✅ ASCII / 二进制 | 版本覆盖 R12、2000、2004、2007、2010 |
| DXF 写入 | ✅ ASCII / 二进制 | write()第三个参数传true输出二进制 |
| DWG 读取 | ✅ 只读 | 内置 15/18/21/24/27 五套版本化阅读器,对应 R14 至 2013/2015 |
| DWG 写入 | ❌ 不支持 | 边界清晰:想生成 DWG,先写 DXF 再转换 |
| 字符编码 | ✅ 自动转换 | 内置日/中/韩/繁 4 张码表,回调里通常是 UTF-8 |
适用人群很集中:做 CAD 二次开发的工程师、需要批量提取图纸数据的制造业/BIM 软件开发者、想给产品加"图纸导入"能力的嵌入式团队。如果你只是偶尔手动转一个文件,直接跳去下一节用现成工具即可。
四、三个实战场景,从命令到原理逐层拆解
场景一:批量 DWG 转 DXF,一行命令搞定
要解决的问题:几十张老图纸要从 DWG 统一转成 DXF,逐张手工转换太痛苦。
关键命令:仓库里现成的dwg2dxf就是干这个的:
./dwg2dxf input.dwg -v2000 output.dxf # 输出 2000 版 ASCII DXF ./dwg2dxf input.dwg -v2007 -b output.dxf # -b 输出二进制 DXF ./dwg2dxf list.txt -B -y outdir/ # -B 批量;-y 静默覆盖已存在文件版本参数可选-R12 / -v2000 / -v2004 / -v2007 / -v2010。
背后原理:dwg2dxf复用了同一套DRW_Interface回调——先用dwgR把 DWG 解析成内存里的实体集合,再用dxfRW::write()按你指定的版本写出去。读进来、写出去,版本迁移天然完成,这就是为什么它是绝佳的参考实现。想看细节,直接读dwg2dxf/dx_iface.h,里面把"如何把收到的实体暂存、再统一转发给 writer"演了一遍。
场景二:抽取图纸文字,搞定中文乱码
要解决的问题:要从一批图纸里把 TEXT、MTEXT 标注全部提取入库,结果打印出来全是乱码。
关键思路:libdxfrw内部通过src/intern/drw_textcodec.cpp和四张码表(CP932 日文 Shift-JIS、CP936 简体 GB2312、CP949 韩文、CP950 繁体 Big5)做自动转换,你只需在addText/addMText回调里取字段即可。真正导致乱码的,九成是源文件的 code page 声明和你的输出终端编码不一致——先查这两处,再怀疑库本身。
背后原理:图纸里的文本按绘制时的系统代码页存储,库在解析时先识别文件头部的代码页声明,再经 iconv 转成统一编码交给你的回调。所以"换一个输出环境乱码就消失了"这种情况,基本可以断定是终端或数据库的字符集没对齐。
场景三:写一个"格式无关"的图纸导入器
要解决的问题:业务系统要同时接收 DXF 和 DWG,不想写两套解析逻辑。
关键代码:dxfRW管 DXF、dwgR管 DWG,两者read()签名一致,封装一层即可:
#include <string> #include "libdxfrw.h" #include "libdwgr.h" bool openDrawing(const char* path, DRW_Interface* sink) { std::string p(path); if (p.size() >= 4 && p.compare(p.size() - 4, 4, ".dxf") == 0) return dxfRW(path).read(sink, false); if (p.size() >= 4 && p.compare(p.size() - 4, 4, ".dwg") == 0) return dwgR(path).read(sink, false); return false; }上层业务只认一种回调,底层格式差异被完全屏蔽。这是整个库最值钱的设计:接口统一,换文件格式对业务代码零侵入。
五、常见误区集中扫雷
- ⚠️"只实现几个实体回调就行了":
DRW_Interface的纯虚函数一个都不能少,否则编译不过。偷懒的姿势是把不关心的回调全部写成空函数体。 - ⚠️"版本号用
DRW::R2000":没有这个枚举。版本在drw_base.h里是AC1009~AC1032的 AC 系列编码,照抄即可。 - ⚠️"
read()的ext参数默认传true":true会把实体按 extrusion 投影成 2D、坐标值会被改写;要原始 3D 坐标就传false。 - ⚠️"试试用库直接写 DWG":不存在的功能。先写 DXF,再用
dwg2dxf反向转换,是唯一正路。 - ⚠️"乱码就改源码":先确认图纸 code page,再查输出端编码,见场景二,别让库背锅。
六、进阶技巧与继续深入的方向
技巧一:椭圆转多段线的精度控制。下游工具若不支持椭圆(比如老版本 R12),可以先把椭圆离散成多段线再导出:
dxf.setEllipseParts(64); // 分段数越大越接近真实椭圆,代价是文件变大技巧二:调试开关定位解析问题。解析失败时打开调试输出,配合版本与错误码能快速判断是"版本不支持"还是"文件已损坏":
dxf.setDebug(DRW::DEBUG); // 读 DWG 时可用:dwgR::getVersion() / dwgR::getError()继续深入的三份资料:src/drw_interface.h是整个库的"契约书",所有回调都在这里,先读它效率最高;dwg2dxf/与dwg2text/两个目录是官方示例,看完等于学会一半 API;tests/下覆盖实体读写、多段线、文字、表格、版本兼容、错误处理等场景的单元测试,构建后在 build 目录执行ctest一键验证。
最后提醒一句协议:**GNU GPL v2(或更高版本)**意味着如果你的项目要静态链接它,整个程序就得开源——商用前请务必评估这一条。
七、现在就动手
路线图已经铺好:三条命令装好库,一个继承DRW_Interface的类完成读取,一个write()调用完成写出,再加一条dwg2dxf命令解决格式迁移。接下来十分钟,你只需要做一件事——git clone https://gitcode.com/gh_mirrors/li/libdxfrw,然后把手头那张图纸交给dwg2dxf,或者把上面的读取器粘进你的工程编译一次。等屏幕上滚出第一行坐标,你就明白:CAD 文件,从此不再是你程序的禁区。
【免费下载链接】libdxfrwC++ library to read and write DXF/DWG files项目地址: https://gitcode.com/gh_mirrors/li/libdxfrw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考