简介ezdxf 是一个与 DXF 版本无关的 Python 程序包面向需要以编程方式创建、读取和修改 DXF 图纸的开发者尤其适合 CAD 二次开发、工程图纸自动化处理等场景。它支持 R12 至 R2018 各版本 DXF 的读写与新建能完整保留未知标签即使图纸包含第三方应用数据也可安全打开与保存且跨 Windows、Linux、macOS 平台兼容 CPython 与 pypy3。资源包共 1250 个文件约 20.12MB以 717 个 py 源码为主体辅以 rst 文档、png 示意图、dxf 示例图纸、ttf 字体及少量 pyx、pxd 等 Cython 扩展文件结构完整便于查阅源码与文档。目前已有 2418 人学习下载。借助该包读者可快速掌握 DXF 文件读写、实体创建与修改、版本兼容处理等核心用法并参考示例图纸与文档搭建自己的 CAD 自动化脚本是学习 Python 操作 DXF 的实用工具库。1. 用 Python 读写 DXF为什么我最后留了 ezdxf 在环境里如果你做过 CAD 相关的自动化大概率遇到过这种局面甲方丢来一堆 DXF 图纸要求批量改图层颜色、提取所有标注文字、或者把几千个图元按规则重新归类。手动开 CAD 点一整天不现实用别的库要么读不全、要么写出来的文件打不开。我最早用过一个只能读不能写的库改完还得回 CAD 里手动另存等于白干。后来换成 ezdxf才算把「读—改—写」这条链路跑通。ezdxf 是一个纯 Python 的 DXF 读写库不需要装 AutoCAD不依赖任何商业组件pip 装完就能用。它覆盖了从 R12 到 R2018 的 DXF 版本能操作图元、图层、块、标注、文字样式这些核心对象。适合谁做批量图纸处理的、搞 CAD 数据提取的、需要程序化生成图纸的以及想把 DXF 接进自己 Python 工具链的人。这篇不是 API 文档翻译是我自己踩过坑之后整理的一条可复现路径。2. 环境搭建与第一个可运行脚本从安装到读出图元2.1 安装与版本选择安装本身没什么好说的但版本选择有个坑值得先讲。ezdxf 的 API 在不同大版本之间有过调整尤其是add_text和add_mtext的参数、以及doc.header的访问方式。我一般会锁一个近期稳定版而不是无脑装最新。# 建议在虚拟环境里装避免和系统里的 CAD 相关包冲突 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 装 ezdxf锁一个稳定版本 pip install ezdxf1.1,2.0 # 验证安装 python -c import ezdxf; print(ezdxf.__version__)逻辑说明用虚拟环境隔离是基本操作重点在版本约束。1.1,2.0这个范围能避开早期 0.x 版本里一些已经废弃的接口又不会跳到还没稳定的 2.x。参数上如果你团队里有人用旧脚本先确认他们用的哪个版本再决定要不要统一。装完之后先别急着写复杂逻辑拿一个最小 DXF 跑通读取流程。如果没有现成文件ezdxf 可以自己创建一个。import ezdxf # 创建一个新的 DXF 文档版本用 R2010兼容性比较好 doc ezdxf.new(dxfversionR2010) # 新建一个图层颜色设为红色颜色号 1 doc.layers.add(nameMY_LAYER, color1) # 拿到模型空间往里加图元 msp doc.modelspace() msp.add_line((0, 0), (100, 0), dxfattribs{layer: MY_LAYER}) msp.add_circle((50, 50), radius25, dxfattribs{layer: MY_LAYER}) msp.add_text(测试文字, dxfattribs{height: 5, layer: MY_LAYER}).set_placement((10, 10)) # 保存 doc.saveas(demo_output.dxf) print(已生成 demo_output.dxf)逻辑说明ezdxf.new()的dxfversion参数决定输出文件的 DXF 版本R2010 是很多下游软件都能读的折中选项。doc.layers.add()里color1是 AutoCAD 颜色索引ACI1 代表红。dxfattribs这个字典是 ezdxf 里非常核心的一个参数几乎所有add_*方法都靠它来指定图层、颜色、线型等属性。set_placement是给文字定位的比直接传insert更直观。2.2 读取已有 DXF 并遍历图元生成只是热身实际工作里更多是读别人的文件。下面这段遍历所有图元、按类型统计并提取文字内容。import ezdxf from collections import Counter doc ezdxf.readfile(input.dxf) msp doc.modelspace() type_counter Counter() texts [] for entity in msp: dxftype entity.dxftype() type_counter[dxftype] 1 # 提取 TEXT 和 MTEXT 的文字内容 if dxftype TEXT: texts.append(entity.dxf.text) elif dxftype MTEXT: texts.append(entity.text) # MTEXT 用 .text 属性 print(图元类型统计, dict(type_counter)) print(提取到的文字条数, len(texts)) for t in texts[:10]: print(repr(t))逻辑说明ezdxf.readfile()会自动识别 DXF 版本。遍历msp得到的每个entity用dxftype()判断类型。注意 TEXT 和 MTEXT 取文字的方式不一样TEXT 用entity.dxf.textMTEXT 用entity.text这个差异我第一次写的时候搞混了结果 MTEXT 全取到空字符串。Counter用来快速统计图元构成排查文件里到底有什么东西时很好用。参数上readfile还有个encoding参数遇到中文乱码时可以显式指定但大多数情况让它自动检测就行。如果文件很大遍历时可以考虑用msp.query(TEXT MTEXT)只取需要的类型比全量遍历快。3. 图元操作与批量修改图层、文字、块的实际处理3.1 图层批量改名与颜色调整批量改图层是高频需求。比如甲方要求把所有以OLD_开头的图层统一改成NEW_前缀同时把颜色改成指定值。import ezdxf doc ezdxf.readfile(input.dxf) # 遍历所有图层按规则改名和改色 rename_map {} for layer in doc.layers: old_name layer.dxf.name if old_name.startswith(OLD_): new_name NEW_ old_name[4:] rename_map[old_name] new_name layer.dxf.name new_name layer.dxf.color 3 # 绿色 # 关键一步图元上引用的图层名也要同步更新 for entity in doc.modelspace(): layer_name entity.dxf.get(layer, None) if layer_name in rename_map: entity.dxf.layer rename_map[layer_name] doc.saveas(output_renamed.dxf) print(改名映射, rename_map)逻辑说明这里有个容易翻车的地方——改了doc.layers里的图层名并不自动更新图元上引用的图层名。图元通过dxf.layer存的是图层名字符串不是对象引用。所以必须再遍历一遍图元把旧名字替换成新名字。我第一次只改了图层表结果打开文件发现图元全跑到默认图层 0 上去了血泪经验。参数上layer.dxf.color用的是 ACI 颜色号常见值1 红、2 黄、3 绿、4 青、5 蓝、6 品红、7 白/黑。如果要真彩色得用layer.rgb属性但兼容性不如 ACI。3.2 文字内容批量替换与块内图元处理文字替换看起来简单但块BLOCK里的文字是另一个坑。块定义里的图元不在模型空间直接遍历到得单独处理。import ezdxf doc ezdxf.readfile(input.dxf) def replace_text_in_entities(entities, old, new): 在给定图元集合里替换文字内容 count 0 for e in entities: if e.dxftype() TEXT: if old in e.dxf.text: e.dxf.text e.dxf.text.replace(old, new) count 1 elif e.dxftype() MTEXT: if old in e.text: e.text e.text.replace(old, new) count 1 return count # 1. 处理模型空间 n1 replace_text_in_entities(doc.modelspace(), 旧项目名, 新项目名) # 2. 处理所有块定义内部的图元 n2 0 for block in doc.blocks: n2 replace_text_in_entities(block, 旧项目名, 新项目名) # 3. 处理布局图纸空间 n3 0 for layout in doc.layouts: n3 replace_text_in_entities(layout, 旧项目名, 新项目名) doc.saveas(output_replaced.dxf) print(f模型空间替换 {n1} 处块内替换 {n2} 处布局替换 {n3} 处)逻辑说明doc.blocks遍历的是所有块定义块定义本身也是可迭代的图元集合。doc.layouts包含模型空间和各个图纸空间布局。三处都处理完才算干净。很多人只处理模型空间结果图纸边框里的标题栏文字没换掉交付时被退回来。参数上替换逻辑用的是 Python 字符串的replace区分大小写。如果要不区分大小写得先lower()比较再处理但那样会丢失原始大小写需要额外记录。块内文字如果属性是ATTRIB块属性处理方式又不一样得用entity.dxf.tag和entity.dxf.text配合这个后面进阶部分再说。4. 避坑与排查那些让我重跑脚本的瞬间4.1 中文乱码现象是文字变成问号或方块现象读进来的中文文字显示为???或者乱码方块。原因DXF 文件的编码声明和实际内容不一致早期 R12 版本尤其常见有些文件头写的是 ANSI 但内容实际是 GBK。解决ezdxf.readfile()加encoding参数显式指定比如encodinggbk。如果还不确定先用二进制方式读文件头几行看$DWGCODEPAGE的值。4.2 保存后文件打不开现象是 CAD 提示文件损坏现象脚本跑完没报错但用 CAD 打开提示文件损坏或版本不兼容。原因多半是dxfversion设得太老或太新或者写入了一些该版本不支持的图元属性。解决统一用R2010或R2013这两个版本兼容性最稳。如果必须输出 R12注意 R12 不支持 MTEXT、LWPOLYLINE 等图元得先转换。4.3 图元丢失现象是遍历数量对不上现象明明文件里有很多图元遍历modelspace()却只拿到一部分。原因图元可能在块定义里、在图纸空间布局里或者被放在了paperspace而不是modelspace。解决分别遍历doc.blocks、doc.layouts用Counter统计各处的图元数量定位到底在哪。另外msp.query()支持按类型过滤比手动判断更可靠。4.4 性能问题现象是处理大文件时内存暴涨现象处理几万个图元的 DXF 时内存占用飙升甚至卡死。原因一次性把所有图元读进列表或者频繁saveas。解决用生成器方式遍历不要list(msp)批量修改完再统一保存一次不要每改一个图元存一次。如果文件特别大可以考虑用ezdxf.addons里的drawing模块做流式处理。4.5 块属性取不到现象是 ATTRIB 文字为空现象块参照里的属性文字用普通 TEXT 方式取不到。原因块属性是ATTRIB类型挂在INSERT图元下面不是独立的 TEXT。解决先找到INSERT图元再遍历它的attribs用attrib.dxf.text取值。如果要改改完还得调attrib.dxf.text new_value并且确保块定义里有对应的ATTDEF。5. 进阶技巧块属性操作与 DXF 版本转换的验证方法块属性这块值得单独拎出来讲因为它是自动化改图纸标题栏的核心。假设你有一个带标题栏的图框块里面有「项目名称」「图号」「日期」几个属性要批量替换。import ezdxf doc ezdxf.readfile(input.dxf) msp doc.modelspace() # 找到所有块参照INSERT按块名筛选 target_block_name TITLE_BLOCK updated 0 for insert in msp.query(fINSERT[name{target_block_name}]): for attrib in insert.attribs: tag attrib.dxf.tag if tag PROJECT_NAME: attrib.dxf.text 新项目名称 updated 1 elif tag DRAWING_NO: attrib.dxf.text DWG-2024-001 updated 1 doc.saveas(output_titleblock.dxf) print(f更新了 {updated} 个属性)逻辑说明msp.query(INSERT[nameTITLE_BLOCK])是 ezdxf 的查询语法按图元类型和属性过滤比手动遍历快也准。insert.attribs拿到的是该块参照的所有属性每个attrib有dxf.tag属性标签和dxf.text属性值。改完直接保存即可不需要额外同步块定义因为属性值是存在块参照上的。参数上查询语法里的name是块名区分大小写。如果块名不确定可以先遍历所有 INSERT打印insert.dxf.name看看。版本转换的验证方法转换完不要只看脚本有没有报错要实际验证。我一般会做三步——第一步用 ezdxf 重新读一遍输出文件确认图元数量和类型统计和预期一致第二步用doc.dxfversion确认版本号第三步如果条件允许用目标 CAD 软件实际打开一次看有没有警告。import ezdxf # 验证输出文件 doc ezdxf.readfile(output_titleblock.dxf) print(DXF 版本, doc.dxfversion) print(图层数, len(doc.layers)) print(块定义数, len(doc.blocks)) msp doc.modelspace() from collections import Counter counter Counter(e.dxftype() for e in msp) print(模型空间图元统计, dict(counter)) # 检查是否有图元落在不存在的图层上 layer_names {layer.dxf.name for layer in doc.layers} orphans [e.dxf.layer for e in msp if e.dxf.get(layer) and e.dxf.layer not in layer_names] print(孤立图层引用, set(orphans) if orphans else 无)逻辑说明这段验证脚本是我每次批量处理完都会跑的。doc.dxfversion确认版本Counter确认图元构成最后那个孤立图层检查能发现「图元引用了不存在的图层」这种隐蔽问题——这种文件在 CAD 里打开可能不报错但图元显示会异常。从那以后我每次批量改完 DXF都强制走一遍「重读—统计—孤立图层检查」这三步宁可多花三十秒也不想交付后被退回来重跑。希望帮到你。本文还有配套的精品资源点击获取