- 图形学
【免费下载链接】ezdxf
Python interface to DXF
导读
UNDERLAY 实体是 ezdxf 中用于将外部文档(PDF、DWF/DWFx、DGN)作为"底图"链接进 DXF 绘图的机制:底图文件本身不会被嵌入DXF 文件,而始终以独立文件的形式存在。本文基于 dxfentities/underlay.rst 文档,结合 实体源码、工厂函数、ObjectsSection 实现 与 官方示例、单元测试,系统讲解 UNDERLAY 实体的数据模型、工厂函数、裁剪路径、显示属性(对比度/淡出/单色)及底层实现原理,读完即可在你的 ezdxf 项目中正确创建和管理三种格式的底图引用。
UNDERLAY 是什么
UNDERLAY 实体(DXF 标准中的PDFUNDERLAY、DWFUNDERLAY、DGNUNDERLAY)将一个外部底图文件链接到 DXF 文件。其核心设计约束是:
- 底图文件不嵌入DXF 文件,始终是分离的独立文件,移动/分发 DXF 时必须同时携带底图文件;
(PDF)UNDERLAY实体在行为上类似块引用(INSERT):同一个底图可以在不同位置、以不同缩放比例和旋转角度被多次引用;- 每次引用都必须挂接一个
(PDF)DEFINITION定义实体(见 dxfobjects/underlaydef.rst),定义实体负责记录底图文件名与显示参数。
DXF 标准支持三种底图文件格式:PDF、DWF(含 DWFx)和DGN。底图可以被矩形或多边形路径裁剪,裁剪坐标为 2D OCS 坐标,单位为绘图单位但不做缩放(见 ocs_usage.rst 中 OCS 概念)。
实体总览与类层次
文档给出的类关系如下:
| 项目 | Underlay | PdfUnderlay | DwfUnderlay | DgnUnderlay |
|---|---|---|---|---|
| 父类 | ezdxf.entities.DXFGraphic | Underlay | Underlay | Underlay |
| DXF 类型 | 内部基类(虚拟) | 'PDFUNDERLAY' | 'DWFUNDERLAY' | 'DGNUNDERLAY' |
| 工厂函数 | BaseLayout.add_underlay | 同左 | 同左 | 同左 |
| 继承属性 | 通用图形 DXF 属性 | 同左 | 同左 | 同左 |
| 最低 DXF 版本 | DXF R2000('AC1015') | 同左 | 同左 | 同左 |
在源码中,Underlay是DXFGraphic的派生虚拟基类,PdfUnderlay、DwfUnderlay、DgnUnderlay分别以@register_entity注册(src/ezdxf/entities/underlay.py 第 346-373 行)。MIN_DXF_VERSION_FOR_EXPORT = DXF2000表明该实体仅支持 R2000 及以上版本导出;graphicsfactory.py 的 add_underlay 会在dxfversion < DXF2000时直接抛出DXFVersionError。
一个值得注意的细节:源码中还注册了PdfReference(DXFTYPE = "PDFREFERENCE")作为PDFUNDERLAY的同义词——ezdxf 始终创建PDFUNDERLAY实体,但能读取某些 CAD 软件写出的PDFREFERENCE类型。
UNDERLAY 的 DXF 属性(核心数据模型)
Underlay的所有数据都存放在AcDbUnderlayReference子类标记(subclass marker)中,由 acdb_underlay 定义 描述:
| DXF 属性 | 组码 | 默认值 | 说明 |
|---|---|---|---|
dxf.underlay_def_handle | 340 | 无 | 指向 UNDERLAYDEF 定义实体的硬引用句柄 |
dxf.insert | 10 | (0, 0, 0) | 插入点,即底图左下角,OCS 坐标 |
dxf.scale_x | 41 | 1 | x 方向缩放因子(float),校验非零 |
dxf.scale_y | 42 | 1 | y 方向缩放因子(float),校验非零 |
dxf.scale_z | 43 | 1 | z 方向缩放因子(float),校验非零 |
dxf.rotation | 50 | 0 | 绕拉伸矢量(extrusion)逆时针旋转的角度(度) |
dxf.extrusion | 210 | (0, 0, 1) | 拉伸矢量,可选,校验非零向量 |
dxf.flags | 280 | 10 | 显示属性位标志(见下表) |
dxf.contrast | 281 | 100 | 对比度,取值 20–100 |
dxf.fade | 282 | 0 | 淡出值,取值 0–80 |
三个缩放因子在 属性定义 中均带validator.is_not_zero+fixer=RETURN_DEFAULT,即设置为 0 会被拒绝并回退默认值;contrast使用is_in_integer_range(20, 101)、fade使用is_in_integer_range(0, 81)做范围校验,越界时由fit_into_integer_range钳制回合法区间。
flags 位标志
文档给出了dxf.flags的位定义,常量在 src/ezdxf/lldxf/const.py:
| 常量 | 值 | 含义 |
|---|---|---|
UNDERLAY_CLIPPING | 1 | 裁剪开/关 |
UNDERLAY_ON | 2 | 底图显示开/关 |
UNDERLAY_MONOCHROME | 4 | 单色显示 |
UNDERLAY_ADJUST_FOR_BG | 8 | 随背景调整 |
默认flags = 10,即UNDERLAY_ON | UNDERLAY_ADJUST_FOR_BG(底图默认显示且自动适应背景)。这一点被单元测试明确验证:assert underlay.dxf.flags == 10(test_231_underlay_2.py)。
便捷属性与关键方法
除原始 DXF 属性外,Underlay类提供了面向对象编程的读写属性(src/ezdxf/entities/underlay.py#L238-L304):
clipping:True/False(读/写),读写位 1;on:True/False(读/写),读写位 2;monochrome:True/False(读/写),读写位 4;adjust_for_background:True/False(读/写),读写位 8;scale/scaling:(x, y, z)三元组(读/写)。写入单个 float 时按x = y = z = scale做等比缩放,写入元组则分别赋值三个dxf.scale_*;boundary_path:顶点列表(读/写)。两个顶点表示矩形裁剪(左下角 + 右上角),超过两个顶点表示多边形裁剪路径;get_underlay_def():返回关联的 DEFINITION 实体;set_underlay_def(def):设置关联定义实体,同时更新dxf.underlay_def_handle并向定义实体追加 reactor 句柄(set_underlay_def);reset_boundary_path():清除裁剪路径并将clipping置False。
set_boundary_path(vertices)有一个自动行为:传入非空顶点列表时,除了保存路径还会自动开启裁剪(self.clipping = True),传入空列表则等效于reset_boundary_path()。裁剪坐标通过组码 11 导出为 2D 点(export_boundary_path 使用write_vertex(11, vertex[:2]))。
创建底图引用:add_underlay 工厂函数
创建 UNDERLAY 实体的标准入口是布局层的add_underlay方法,其签名(graphicsfactory.py):
msp.add_underlay( underlay_def, # 必需的 UnderlayDefinition 实体 insert=(0, 0, 0), # 插入点,3D WCS 坐标 scale=(1, 1, 1), # 缩放:三元组或单一数值(等比缩放) rotation=0.0, # 绕拉伸轴(默认 z 轴)旋转角度,单位度 dxfattribs=None, # 其他 DXF 属性 ) -> Underlay其内部流程是:校验 DXF 版本(< R2000 抛DXFVersionError)→ 把insert转为Vec3写入dxfattribs→ 自动填充underlay_def_handle和rotation→ 调用new_entity(underlay_def.entity_name, ...)按定义实体的格式创建PDFUNDERLAY/DWFUNDERLAY/DGNUNDERLAY→ 设置scaling→ 调用set_underlay_def()建立引用关系。
对应地,Drawing对象提供add_underlay_def(filename, fmt="ext", name=None)(src/ezdxf/document.py#L1094-L1115),其中:
fmt:"pdf"、"dwf"、"dgn",或"ext"(默认,从文件扩展名自动推断);name:指明显示底图的哪个部分——PDF 为页码、DGN 恒为"default"、DWF 未明确(源码默认给"Model")。省略时 PDF 默认"1"(第一页)、DGN 默认"default"。
UNDERLAYDEF:底图定义实体
每个 UNDERLAY 引用都依赖于一个定义实体(PDFDEFINITION/DWFDEFINITION/DGNDEFINITION),它记录:
dxf.filename(组码 1):底图文件的相对(相对于 DXF 文件)或绝对路径;dxf.name(组码 2):要显示的底图部分,语义随格式而变(PDF 页码 / DGN 恒为"default"/ DWF 未定)。
定义实体驻留在OBJECTS 段,而非布局空间。ObjectsSection.add_underlay_def(src/ezdxf/sections/objects.py#L471-L513)的实现细节揭示了其存储机制:
- 在
rootdict中获取/创建名为ACAD_PDFDEFINITIONS(或ACAD_DWFDEFINITIONS、ACAD_DGNDEFINITIONS)的字典; - 创建
PDFDEFINITION等实体,owner指向该字典句柄; - 通过
next_underlay_key自动生成字典键(src/ezdxf/tools/handle.py 的UnderlayKeyGenerator),并用underlay_dict[key] = underlay_def登记。
这也是 underlaydef.rst 强调"不要自行实例化对象类,始终使用工厂函数"的原因——手动构造会绕过 OBJECTS 段字典登记与 reactor 维护。UnderlayDefinition还提供file_format、entity_name、acad_dict_name三个只读属性,用于反查所属字典名与对应的 UNDERLAY 类型(src/ezdxf/entities/underlay.py#L412-L422)。
完整可运行示例
综合 官方教程 与 示例脚本,一个典型的端到端流程如下:
import ezdxf from ezdxf.math import basic_transformation # 底图需要 DXF R2000 或更高格式 doc = ezdxf.new("R2000") pdf_def = doc.add_underlay_def(filename="underlay.pdf", name="1") # name = 要显示的页码 dwf_def = doc.add_underlay_def(filename="underlay.dwf", name="Underlay_R2013-Model") dgn_def = doc.add_underlay_def(filename="underlay.dgn", name="default") msp = doc.modelspace() # 同一底图可被多次引用,如同 INSERT 一样 msp.add_underlay(pdf_def, insert=(0, 0, 0), scale=1.0) msp.add_underlay(pdf_def, insert=(10, 0, 0), scale=0.5, rotation=30) # scale 也可传单一数值实现等比缩放 # UNDERLAY 支持 copy() 与 transform() clone = pdf_def and msp.add_underlay(pdf_def, insert=(0, 0, 0), scale=1.0) clone = clone.copy() clone.transform(basic_transformation(move=(20, 0), scale=(0.25, 0.25, 0.25), z_rotation=-30)) msp.add_entity(clone) # 查询现有定义:UNDERLAYDEF 位于 OBJECTS 段 pdf_defs = doc.objects.query("PDFDEFINITION") doc.saveas("dxf_with_underlay.dxf") # 别忘了把底图文件复制到 DXF 同级目录,否则 CAD 应用找不到底图注意:示例脚本开头明确警告——"UNDERLAY 对象在不同 CAD 应用之间不能保证可靠工作,非必要不要使用 UNDERLAY"(examples/entities/underlay.py)。DWF 底图的name取值难以从外部得知,这增加了跨 CAD 兼容的难度。
裁剪示例
# 矩形裁剪:两个顶点 = 左下角 + 右上角 underlay.set_boundary_path([(0, 0), (640, 180)]) # 多边形裁剪:三个及以上顶点 underlay.set_boundary_path([(0, 0), (640, 180), (320, 360)]) assert underlay.clipping is True # 设置路径后自动开启裁剪 underlay.reset_boundary_path() # 移除裁剪路径并关闭裁剪上述行为与测试 test_set_boundary_path 完全一致:三顶点三角形路径写入后clipping == 1,boundary_path原样保留。
底层实现与边界行为
加载与导出
读取时,load_dxf_attribs 定位AcDbUnderlayReference子类标记,先用load_boundary_path把组码 11 的顶点抽取为boundary_path(其余标签照常解析),再快速装载其余属性;若裁剪顶点少于 2 个则自动reset_boundary_path()。缺少该子类标记会抛出DXFStructureError。导出时 export_entity 按固定顺序写出 340/10/41/42/43/50/210/280/281/282 与组码 11 裁剪顶点。测试 test_write_dxf 用TagCollector.dxftags验证了读写往返的字节级一致性。
引用关系维护(reactor)
post_load_hook通过underlay_def_handle从doc.entitydb解析出定义实体存入self._underlay_def;post_bind_hook在实体绑定文档时向定义实体追加 reactor 句柄;destroy()时反向移除(src/ezdxf/entities/underlay.py#L157-L169 与 306-313 行)。测试验证了copy()出的克隆与原件共享同一个定义实体,且原件的句柄与克隆句柄都出现在定义实体的reactors列表中(test_231_underlay_2.py)。
变换(transform)
Underlay.transform(m)复用与 INSERT 相同的InsertCoordinateSystem机制(src/ezdxf/entities/underlay.py#L315-L343):从 insert/scale/rotation/extrusion 构造源坐标系,用矩阵m变换到目标坐标系,再回写四个属性。由于 UNDERLAY 无法表达非正交目标坐标系,此时会抛出InsertTransformationError(提示信息明确指出该限制)。测试 test_transform_underlay 确认其变换逻辑与 INSERT 走同一底层函数,并验证了translate(1, 2, 3)后insert变为(1, 2, 3)。
外部参照(xref)支持
Underlay实现了register_resources/map_resources/map_underlay_def(src/ezdxf/entities/underlay.py#L196-L228),因此底图引用可作为外部参照资源被复制和映射:复制定义实体时会检查目标文档rootdict中是否已有同名字典项,没有则调用doc.objects.next_underlay_key生成新键并take_ownership,保证定义实体在新文档中登记到正确的ACAD_*DEFINITIONS字典。
兼容性注意事项
- 版本:UNDERLAY 要求 DXF R2000(
AC1015)及以上,旧版本会直接抛异常; - 文件分离:DXF 只记录文件名路径,不包含底图内容,分发时必须同步底图文件(示例脚本末尾用
shutil.copy演示了这一步); - 跨 CAD 可靠性:示例与 ezdxf 文档均提示 UNDERLAY 在不同 CAD 应用间的兼容性不稳定,尤其是 DWF 的
name参数语义不明确,在实际项目中应充分验证目标 CAD 的读取行为; - 坐标语义:裁剪路径是 2D OCS 坐标、不带缩放的绘图单位;插入点同样是 OCS 坐标,而
add_underlay的insert参数按 WCS 处理,两者需区分。
- 图形学
【免费下载链接】ezdxf
Python interface to DXF
相关推荐
ezdxf UnderlayDefinition 详解:PDF/DWF/DGN 底图定义实体的创建、属性与底层机制
ezdxf UnderlayDefinition 详解:PDF/DWF/DGN 底图定义实体的创建、属性与底层机制 UnderlayDefinition (DX
图形学ezdxf HPGL/2 转换 Add-on 实战指南:将 PLT 绘图文件转换为 DXF、SVG、PDF 与位图
ezdxf HPGL/2 转换 Add on 实战指南:将 PLT 绘图文件转换为 DXF、SVG、PDF 与位图 HPGL/2(Hewlett Packard
图形学ezdxf 中 SHAPE 实体详解:外部 SHX 形状引用、DXF 属性与底层实现
ezdxf 中 SHAPE 实体详解:外部 SHX 形状引用、DXF 属性与底层实现 本文基于 ezdxf 官方文档中的 SHAPE 实体章节( shape.r
图形学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考