资讯详情

Python 读写 DXF 实战:ezdxf 从入门到批量处理

📅 2026/10/13 14:35:08 | 华诺云谱 👁 阅读
Python 读写 DXF 实战:ezdxf 从入门到批量处理
简介ezdxf 是一个与 DXF 版本无关的 Python 程序包面向需要以编程方式创建、读取和修改 DXF 图纸的开发者尤其适合 CAD 二次开发、工程图纸自动化处理等场景。它支持 R12 至 R2018 的读写与新建未知标签会被忽略但保留以便保存因此也能打开含第三方应用数据的图纸且与操作系统无关已在 CPython 和 pypy3 上测试。资源包共 1250 个文件约 20.12MB其中 717 个 py 源码文件构成核心实现178 个 rst 文档与 10 个 md 说明提供使用指引131 个 png 与 8 个 jpeg 为示例配图63 个 dxf 样例便于对照测试另有 ttf 字体、pyx/py 等 Cython 扩展源码及少量 dwg、svg 等辅助文件。目前已有 2418 人学习下载。借助完整源码、文档与示例图纸读者可快速掌握 DXF 文件读写、实体创建与版本兼容处理并参考底层数学类的 Cython 实现优化性能。1. 用 Python 读写 DXF为什么 ezdxf 是绕不开的那一层如果你手头有一堆 CAD 图纸要批量改图层、提坐标、生成料表或者后端服务需要按参数吐出一张标准 DXF 给下游机床用那你迟早会撞上「DXF 的 Python 接口」这件事。DXF 是 AutoCAD 的交换格式本质是一堆组码group code加值的文本流手写解析器不是不行但版本差异、实体嵌套、块引用、扩展数据这些坑足够让人掉一层皮。ezdxf 就是在这个位置上出现的它把 DXF 的读写、实体模型、块、图层、标注、三维网格都封装成 Python 对象让你用msp.add_line()这种直觉写法生成图纸也能反过来遍历一张几万实体的图纸做统计。它适合三类人做 CAD 二次开发的、做工程数据批处理的、以及需要把几何数据接进自己算法流水线的。这篇不吹概念直接讲怎么装、怎么建、怎么读、参数怎么调、哪里会翻车。2. 环境准备与最小可运行闭环先把一张 DXF 写出来再读回来2.1 安装与版本选择别在版本上省事ezdxf 是纯 Python 包没有二进制依赖安装本身不复杂但版本选择有讲究。它对新版 DXF 格式的支持是随版本推进的如果你要处理 R2018 及以上格式或者用到ACIS实体、MLEADER这类较新的对象就得用较新的 ezdxf。反过来如果你的运行环境锁死在某个老 Python 上那只能退到支持该 Python 的最后一个 ezdxf 版本。# 建议在虚拟环境里装避免和系统里的 CAD 相关包冲突 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装最新稳定版 pip install ezdxf # 如果需要画图预览把 DXF 渲染成 PNG 看效果加绘图后端 pip install ezdxf[draw] # 确认版本后面排查问题时第一件事就是看这个 python -c import ezdxf; print(ezdxf.__version__)逻辑说明ezdxf[draw]会拉入 matplotlib 等可选依赖用于ezdxf.addons.drawing模块把图纸渲染成图片调试几何位置时非常有用。参数上如果你只是做数据提取不装 draw 也完全能跑。注意不要用pip install --pre去装预发布版除非你在追某个刚修的问题预发布版的 API 可能和文档不一致。2.2 新建图纸doc、msp、图层三件套最小闭环的第一步是新建一个文档对象拿到模型空间然后往里塞实体。这里的关键认知是ezdxf 里doc是整张图纸msp是模型空间你画的东西默认都在这psp是图纸空间布局。新手最容易搞混的就是往 psp 里画了东西却在模型空间找不到。import ezdxf # 新建一个 R2010 格式的文档版本用字符串常量别写魔法数字 doc ezdxf.new(dxfversionR2010, setupTrue) # setupTrue 会预置一些标准线型和文字样式省得后面自己建 msp doc.modelspace() # 建一个自己的图层颜色用 ACI 索引色1红2黄3绿4青5蓝7白/黑 doc.layers.add(nameWALL, color1) doc.layers.add(nameDIM, color3) # 在 WALL 图层上画一条线坐标是 (x, y) 或 (x, y, z) msp.add_line((0, 0), (1000, 0), dxfattribs{layer: WALL}) # 画一个圆半径 50圆心在 (500, 200) msp.add_circle((500, 200), radius50, dxfattribs{layer: WALL}) # 加一段单行文字 msp.add_text(测试标注, height25, dxfattribs{layer: DIM, insert: (500, 300)}) doc.saveas(demo_min.dxf) print(实体数量:, len(msp))逻辑说明dxfattribs是 ezdxf 里给实体挂 DXF 属性的统一入口layer、color、linetype、lineweight都从这里传。参数上height是文字高度单位跟随图纸单位insert是文字插入点。setupTrue这个参数值得单独说它会在文档里预置标准线型CONTINUOUS、DASHED 等和文字样式Standard如果你不设后面用linetypeDASHED时会报找不到线型。很多人第一次用 ezdxf 画虚线失败就是栽在这。2.3 读回图纸遍历实体与按类型过滤写出去只是热身真正的日常是读别人的图纸。读的时候不要一上来就for e in msp全遍历几万实体的图纸这样跑又慢又难处理正确做法是按类型查询。import ezdxf doc ezdxf.readfile(demo_min.dxf) msp doc.modelspace() # 按类型查询比全遍历快也更清晰 for line in msp.query(LINE): start line.dxf.start end line.dxf.end print(f线: {start} - {end}, 图层{line.dxf.layer}) # 查询多个类型用空格分隔 for e in msp.query(CIRCLE TEXT): print(e.dxftype(), e.dxf.layer) # 按图层过滤注意图层名大小写敏感 for e in msp.query(*[layerWALL]): print(WALL 上的实体:, e.dxftype()) # 统计各类型数量做图纸体检时常用 from collections import Counter counter Counter(e.dxftype() for e in msp) print(counter)逻辑说明msp.query()支持类似 SQL 的字符串语法*[layerWALL]是属性过滤LINE CIRCLE是类型并集。参数上line.dxf.start返回的是Vec3对象可以直接当元组用也能做向量运算。这里有个性能点query内部会走索引比手写if e.dxftype() LINE快尤其是大图纸。如果你要反复按图层查可以先把实体按图层分桶缓存起来。3. 实体操作进阶块、标注、坐标变换怎么用对3.1 块定义与插入别把块当普通实体块BLOCK是 DXF 里复用几何的核心机制但它的模型和普通实体不一样块定义存在doc.blocks里插入到模型空间的是INSERT实体它引用块定义并带自己的插入点和缩放。很多人直接改块定义里的实体结果发现图纸里所有引用都变了这就是块引用共享定义的特性。import ezdxf doc ezdxf.new(R2010, setupTrue) msp doc.modelspace() # 定义一个块基点是 (0,0) blk doc.blocks.new(nameBOLT_M8) # 往块定义里加几何一个圆加两条线表示螺栓俯视 blk.add_circle((0, 0), radius4) blk.add_line((-6, 0), (6, 0)) blk.add_line((0, -6), (0, 6)) # 插入块指定插入点、缩放、旋转角度度 msp.add_blockref(BOLT_M8, insert(100, 100), dxfattribs{xscale: 1.0, yscale: 1.0, rotation: 0}) msp.add_blockref(BOLT_M8, insert(200, 100), dxfattribs{rotation: 45}) doc.saveas(demo_block.dxf)逻辑说明doc.blocks.new()创建块定义add_blockref()创建 INSERT 实体。参数上rotation单位是度不是弧度xscale/yscale控制缩放负值可以镜像。注意块定义里的坐标是相对于块基点的插入点才是它在模型空间的位置。如果你要改某个插入实例而不影响其他实例得用INSERT实体的virtual_entities()拿到变换后的实体或者直接炸开explode。3.2 标注与尺寸样式不建好标注全是错的标注DIMENSION是 DXF 里最容易被低估的部分。它不是一个简单实体而是由标注样式DIMSTYLE驱动的一组几何。你不建样式直接用默认的出来的标注可能文字高度不对、箭头大小不对、单位精度不对。import ezdxf doc ezdxf.new(R2010, setupTrue) msp doc.modelspace() # 新建标注样式基于 Standard 改 dimstyle doc.dimstyles.duplicate_entry(Standard, MY_DIM) dimstyle.dxf.dimtxt 25 # 标注文字高度 dimstyle.dxf.dimasz 15 # 箭头大小 dimstyle.dxf.dimdec 2 # 小数位数 dimstyle.dxf.dimscale 1.0 # 全局比例出图比例不同时要调 # 加一个线性标注测量 (0,0) 到 (1000,0) 的水平距离 dim msp.add_linear_dim( base(500, -100), # 标注线位置 p1(0, 0), # 第一测量点 p2(1000, 0), # 第二测量点 dimstyleMY_DIM, ) dim.render() # 必须调用才会生成标注几何块 doc.saveas(demo_dim.dxf)逻辑说明add_linear_dim()只是创建了标注定义render()才会根据样式生成实际的几何块漏掉render()是标注不显示的常见原因。参数上dimscale是全局比例如果你按 1:100 出图这里要设成 100否则标注文字会小到看不见。dimdec控制小数位数工程图里通常 0 或 2。注意标注样式是文档级的改一次影响所有用该样式的标注。3.3 坐标变换与 UCS别在错误的坐标系里算坐标DXF 里的坐标默认是 WCS世界坐标系但用户图纸经常有 UCS用户坐标系。ezdxf 提供了ezdxf.math里的矩阵和向量工具做旋转、平移、缩放时不要手算三角函数用现成的。from ezdxf.math import Matrix44, Vec3 # 构造一个绕 Z 轴旋转 30 度、再平移 (100, 200) 的变换矩阵 m Matrix44.z_rotate(30 * 3.1415926 / 180) Matrix44.translate(100, 200, 0) # 对一个点做变换 p Vec3(10, 0, 0) p2 m.transform(p) print(p2) # 对实体做变换会原地修改实体 import ezdxf doc ezdxf.new(R2010) msp doc.modelspace() line msp.add_line((0, 0), (100, 0)) line.transform(m)逻辑说明Matrix44支持矩阵乘法组合变换顺序是从右往左应用。参数上旋转角度用弧度z_rotate是绕 Z 轴。transform()会直接改实体的控制点对 LINE、CIRCLE、LWPOLYLINE 都有效但对 INSERT 实体的变换要小心它改的是插入点而不是块内几何。如果你要批量变换先把矩阵算好再循环调用别在循环里反复构造矩阵。4. 避坑与排查那些让图纸打不开或数据对不上的时刻4.1 保存后 CAD 提示「文件无效或损坏」现象ezdxf 保存的 DXF 用 AutoCAD 打开报错或者某些国产 CAD 直接打不开。原因通常有三类一是用了目标版本不支持的实体或属性比如在 R12 文档里用了LWPOLYLINE的某些扩展属性二是手动改了doc.header里的版本变量导致不一致三是往实体上挂了非法的扩展数据XDATA。解决先确认dxfversion和你要用的实体匹配R12 尽量只用 LINE、CIRCLE、ARC、TEXT 这些基础实体不要手动改$ACADVERXDATA 的组码要按规范来。排查时可以用doc.audit()做一次自检。import ezdxf doc ezdxf.readfile(problem.dxf) auditor doc.audit() print(错误数:, len(auditor.errors)) print(修复数:, len(auditor.fixes)) for e in auditor.errors: print(e)4.2 中文文字变成问号或乱码现象写入的中文在 CAD 里显示为???。原因DXF 的文字编码依赖文档版本和$DWGCODEPAGE头变量R2007 以前用 ANSI 编码之后用 UTF-8。如果你在 R12 文档里写中文而系统代码页不匹配就会乱码。解决优先用 R2007 及以上版本它们原生支持 Unicode如果必须用老版本确保doc.header[$DWGCODEPAGE]设置正确并且文字样式里指定的字体支持中文。另一个常见原因是文字样式用了txt.shx这种不含中文字形的字体换成支持中文的字体即可。4.3 遍历大图纸时内存暴涨现象读一张几十万实体的图纸内存直接几个 G。原因msp.query()返回的是实体对象列表ezdxf 会为每个实体建 Python 对象实体多的时候开销很大。解决用迭代器而不是列表msp.query(LINE)本身返回的是可迭代的但如果你写成list(msp.query(LINE))就全展开了。另外只提取你需要的属性不要保留实体引用。如果只是统计可以用doc.entitydb直接遍历底层数据库跳过对象包装。4.4 块引用炸开后坐标不对现象INSERT实体explode()后得到的实体坐标和视觉位置对不上。原因explode()返回的实体是在块定义坐标系里的没有应用插入点的变换。解决用insert.virtual_entities()拿到已经应用变换的实体或者自己用insert.matrix44()拿到变换矩阵再手动应用。注意virtual_entities()返回的实体是临时的要复制到模型空间才能保存。4.5 标注 render 后重复生成现象反复调用dim.render()导致标注几何重复叠加。原因render()每次都会往标注的几何块里追加内容不是幂等的。解决渲染一次后不要再调或者先清空标注的几何块再渲染。如果你在循环里改标注再渲染记得每次重建标注对象而不是复用。5. 把 ezdxf 接进自动化流水线批量处理与几何提取的实战技巧走到这一步单张图纸的读写应该没问题了。真正体现价值的是批量场景一个目录几百张 DXF要统一改图层颜色、提取所有多段线的顶点做面积计算、或者按规则生成一批标准图框。我一般会写一个处理函数把「读入 → 过滤 → 变换 → 写出」四步固定下来中间用日志记录每张图的实体数和异常。import ezdxf from pathlib import Path from ezdxf.math import area def process_dxf(src: Path, dst: Path): try: doc ezdxf.readfile(src) except Exception as e: print(f[跳过] {src.name}: {e}) return msp doc.modelspace() # 统一把所有 LINE 的颜色改成 ByLayer避免硬编码颜色 for line in msp.query(LINE): line.dxf.color 256 # 256 ByLayer # 提取所有闭合 LWPOLYLINE 的面积 total_area 0.0 for pl in msp.query(LWPOLYLINE): if pl.closed: pts [(p[0], p[1]) for p in pl.get_points()] total_area abs(area(pts)) print(f{src.name}: 实体 {len(msp)}, 闭合多段线面积合计 {total_area:.2f}) doc.saveas(dst) # 批量跑一个目录 src_dir Path(./input) dst_dir Path(./output) dst_dir.mkdir(exist_okTrue) for f in src_dir.glob(*.dxf): process_dxf(f, dst_dir / f.name)逻辑说明line.dxf.color 256是 DXF 里 ByLayer 的约定值设成 256 后实体颜色跟随图层这是图纸规范化的常见操作。area()来自ezdxf.math接受顶点列表返回多边形面积注意顶点顺序会影响正负所以取绝对值。参数上pl.get_points()返回的是(x, y, start_width, end_width, bulge)五元组取前两个就是坐标。这个流水线的关键点是异常隔离单张图读失败不能中断整批所以readfile包在 try 里。再补一个我常用的技巧如果你要对比两张图纸的差异不要逐实体比对先把实体转成「类型 图层 关键坐标」的元组集合再做集合差。这样几万实体也能秒级出结果。另外ezdxf 的doc.header里有很多图纸级设置比如单位$INSUNITS、角度方向$ANGDIR批量处理前先读一遍这些能避免很多「为什么这张图比例不对」的玄学问题。最后一句血泪经验任何批量写操作先在小样本上跑通并人工打开确认再全量跑DXF 的坑往往在打开那一刻才暴露。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑