☰
ezdxf 图纸空间(Paperspace)视口(VIEWPORT)完全指南:页面设置、缩放比例、图层冻结与属性覆盖
2026/10/16 5:56:08 网站建设 项目流程
  • 图形学

【免费下载链接】ezdxf

Python interface to DXF

项目地址:https://gitcode.com/gh_mirrors/ez/ezdxf
点击查看免费下载

本文以 ezdxf 官方教程 psp_viewports.rst 为骨架,结合仓库内示例脚本与源码实现,系统讲解在图纸空间(Paperspace)布局中创建和管理 VIEWPORT 视口的完整流程:从页面设置(page_setup)、视口添加与缩放比例计算,到视口方向调整、图层冻结与逐视口图层属性覆盖。读完本文,你将掌握用 ezdxf 为 R2000+ 版本 DXF 文件制作多视口图纸布局的完整实战方案,并能理解底层 DXF 数据的存储原理。

教程背景与核心示例脚本

本教程基于示例脚本 viewports_in_paperspace.py 展开。该脚本会为 DXF R12 与 R2000+ 版本各生成一个图纸文件,其模型空间内容包含:

  • 三个位于 WCS(世界坐标系)XY 平面内的平面几何图形:一个红色矩形、一个绿色圆、一个青色三角形(均由 create_2d_modelspace_content 创建);
  • 一个 20×20 网格、由正弦/余弦函数生成起伏曲面的 3D 网格(由 create_3d_modelspace_content 创建,高度为 3.0,范围 30×30)。

模型空间内容如下图所示(图片路径:gfx/vp-tut-msp-content.png):

重要:为什么建议使用 R2000+ 版本

原教程给出了一个重要警告:如果需要在图纸空间布局中使用页面尺寸,请使用 DXF R2000 或更高版本,因为 DXF R12 的页面尺寸导出无法正常工作!作者在 BricsCAD 中发现 R12 导出的图纸尺寸错误,在 Autodesk DWG Trueview 中发现边距错误,且未找到原因也未修复。这是图纸空间布局在 DXF 版本支持上的一个硬性边界,实战中务必注意。

页面设置(Page Setup):定义纸张本身

DXF 参考手册中关于图纸空间布局特性的文档非常匮乏,关于如何使用几乎没有任何实践说明,因此本教程中大部分信息是作者通过反复试错总结出来的假设。

Paperspace类的page_setup()方法定义了纸张本身的属性。模型空间和图纸空间的单位互不相关,甚至可以使用不同的单位制(英制、米制),但为了简单起见,官方建议两个空间使用相同的单位系统:

layout.page_setup(size=(24, 18), margins=(1, 1, 1, 1), units="inch")

page_setup 完整参数签名

从源码 layout.py 可以看到page_setup()的完整签名与默认值:

def page_setup( self, size: tuple[float, float] = (297, 210), # 纸张尺寸 (宽, 高) margins: tuple[float, float, float, float] = (0, 0, 0, 0), # (上, 右, 下, 左),注意是顺时针顺序 units: str = "mm", # "mm" 或 "inch" offset: tuple[float, float] = (0, 0), # 绘图原点偏移,2D 点 rotation: int = 0, # 旋转,取值 0-3 scale: Union[int, tuple[float, float]] = 16, # 标准比例类型索引或 (分子, 分母) 元组 name: str = "ezdxf", # 纸张名称前缀 device: str = "DWG to PDF.pc3", # 设备 .pc3 配置文件或系统打印机名 ) -> None:

各参数要点:

  • size:定义旋转模式 0 下纸张的整体尺寸。最佳实践是先在横向(landscape)模式下定义纸张范围,之后再通过rotate参数旋转纸张。

  • margins:四元组顺序为(top, right, bottom, left)(顺时针)。内部会按unit_factor转换为毫米存储(英寸换算系数为 25.4)。

  • units:仅支持"mm"和"inch"(大小写不敏感,源码中units.startswith("inch")判定),其他值会抛出DXFValueError。

  • rotation取值表(源码与教程一致):

    取值含义
    0不旋转
    1逆时针旋转 90 度
    2上下颠倒(180 度)
    3顺时针旋转 90 度

    源码 layout.py 中会对rotation做校验,非 0-3 的值抛出DXFValueError("valid rotation values: 0-3")。

  • scale:反映图纸空间(paperspace)中纸张单位与绘图单位之间的关系。可以传整数(0-32 范围内的标准比例类型索引,映射关系在const.STD_SCALES中),也可以传(分子, 分母)元组,例如(1, 50)表示 1:50。教程建议保持默认 1:1,并在图纸空间中用与纸张尺寸相同的单位绘制线条和文字。注意分子或分母为 0 会抛出DXFValueError。

page_setup 写入了哪些 DXF 数据

源码显示page_setup()会设置 PLOTSETTINGS 头部的多项属性,并把纸张名称规范化为"{name}_({width:.2f}_x_{height:.2f}_{units})"格式(如ezdxf_(24.00_x_18.00_Inches)),同时写入plot_configuration_file、四边*_margin、paper_width/paper_height(统一换算为毫米)、scale_numerator/scale_denominator、plot_paper_units(0 表示 Inches,1 表示 MM)、plot_rotation、绘图原点偏移等,最后调用reset_paper_limits()、reset_extents()、reset_viewports()重置纸张极限、范围和视口。

更简单的页面设置:Drawing.page_setup

原教程作者在写作过程中注意到:更换打印机/绘图仪和纸张尺寸会导致布局内容移位,因为所有纸张尺寸都是在没有边距的情况下定义的,也许最好把所有边距设为零。为此,作者在Drawing类上添加了辅助方法page_setup(),并提供了示例 simple_page_setup.py 演示用法。

Drawing.page_setup()位于 document.py,签名如下:

def page_setup( self, name: str = "Layout1", # 图纸空间布局名称 fmt: str = "ISO A3", # 纸张格式 landscape=True, # True 横向,False 纵向 ) -> Paperspace:

该方法要求 DXF R2000 或更新版本。fmt支持的纸张格式(尺寸均为横向度量值):

名称单位宽高
ISO A0mm1189841
ISO A1mm841594
ISO A2mm594420
ISO A3mm420297
ISO A4mm297210
ANSI Ainch118.5
ANSI Binch1711
ANSI Cinch2217
ANSI Dinch3422
ANSI Einch4434
ARCH Cinch2418
ARCH Dinch3624
ARCH Einch4836
ARCH E1inch4230
Letterinch118.5
Legalinch148.5

该布局使用纸张格式对应的单位作为绘图单位,无边距、无偏移、图纸空间比例为 1:1,因此更换输出设备非常容易(至少在 BricsCAD 中如此)。示例脚本中还提醒:无边距布局的绘图空间是整个纸张,但打印机/绘图仪不会打印到纸张边缘,真实文件中应始终在纸张边缘与内容之间保留安全空间,且该安全空间的大小因设备而异、无法预知,建议结合psp.dxf.paper_width和psp.dxf.paper_height自行设计。

在图纸空间中绘图

你可以像在任意布局空间一样向图纸空间添加 DXF 实体。坐标系原点(0, 0)位于画布(canvas)的左下角,画布范围等于纸张尺寸减去边距。你可以绘制超出该范围的内容,但 CAD 应用程序可能不会打印这些内容。

在 viewports_in_paperspace.py 中,作者按此规则做了如下计算并绘制边框:

layout: Paperspace = doc.layout("Layout1") # Arch C = 18 x 24 in width, height = 24, 18 m = 1 layout.page_setup(size=(width, height), margins=(m, m, m, m), units="inch") # 画布由纸张尺寸减去边距定义: canvas_width = width - 2 * m # 22 canvas_height = height - 2 * m # 16 # 画布左下角坐标为 0, 0

关于 R12 的特殊处理

示例脚本同时支持 R12 导出,但 R12 走的是另一条路径page_setup_r12()(见 layout.py)。该方法会先删除所有已存在的视口,然后通过头部变量$PLIMMIN、$PLIMMAX、$PEXTMIN、$PEXTMAX设置打印范围,并缩放边距与纸张尺寸(源码中有 TODO 注释:不知道如何在 R12 中设置 inch/mm 模式)。同时注意DXF R12 只支持一个图纸空间(即一个布局),任何布局名称都返回同一个布局(见 viewports_in_paperspace.py 的注释)。

添加视口(Adding Viewports)

Viewport(DXF VIEWPORT)实体是一个指向模型空间的窗口,用于在图纸空间中按任意缩放比例和旋转角度显示模型空间的内容。VIEWPORT 实体通过工厂方法Paperspace.add_viewport()添加。

从源码 layout.py 可以看到其完整签名:

def add_viewport( self, center: UVec, # 视口在图纸空间中的中心点 size: tuple[float, float], # 视口在图纸空间中的 (宽, 高) view_center_point: UVec, # 要显示的模型空间区域中心点 view_height: float, # 模型空间中要显示的区域高度 status: int = 2, # 视口状态,默认 2 dxfattribs=None, # 附加 DXF 属性 ) -> Viewport:

关键点:

  • center定义视口在图纸空间中的中心位置,size定义 VIEWPORT 在图纸空间中的宽度和高度;
  • 要显示的模型空间内容由view_center_point和view_height定义;
  • 默认图层为 "VIEWPORTS"(源码中attribs字典明确写了"layer": "VIEWPORTS"),用于后续控制视口边框显隐;
  • status语义(源码注释):-1表示开启但在屏幕外,或因为$MAXACTVP计数超出而非激活;0表示关闭;任何大于 0 的值表示开启且激活,其中1是"活动视口",2是下一个,以此类推;
  • dxf.id由get_next_viewport_id()自动分配,从 2 开始递增(源码 layout.py)。

添加视口后的图纸空间布局如下图所示(图片路径:gfx/vp-tut-psp-content.png):

缩放比例(Scaling Factor):由尺寸与视高的比值决定

VIEWPORT 的缩放因子不是一个显式的数值,而是由size参数中的 VIEWPORT 高度与view_height参数的比值关系决定的。

源码 viewport.py 中的get_scale()方法印证了这一计算方式:

def get_scale(self) -> float: """Returns the scaling factor from modelspace to viewport.""" msp_height = self.dxf.view_height if abs(msp_height) < 1e-12: return 0.0 vp_height = self.dxf.height return vp_height / msp_height # 视口高度 / 模型空间视高

当两个值相等时,缩放比例为 1:1:

paperspace.add_viewport( center=(14.5, 2.5), size=(5, 5), view_center_point=(12.5, 7.5), view_height=5, )

当view_height比 VIEWPORT 高度大 5 倍时,缩放比例为 1:5:

paperspace.add_viewport( center=(8.5, 2.5), size=(5, 5), view_center_point=(10, 5), view_height=25, )

原示例 viewports_in_paperspace.py 中还演示了更多比例组合:view_height=10、视口高5得到 1:2(矩形视图);size=(0.3, 0.15)、view_height=7.5得到 1:50(缩放比例 = 7.5 / 0.15 = 50),并在每个视口下方用add_text()标注了比例文字,形成规范的"平面图 + 局部放大图"图纸布局。

另外可参考 viewport.py 的get_aspect_ratio()(返回width / height)与 viewport.py 的get_modelspace_limits()(返回模型空间中可见范围的(min_x, min_y, max_x, max_y)),这两个辅助方法可用于进一步验证视口覆盖的模型空间区域。

视口方向(View Direction):从俯视到任意视角

VIEWPORT 的默认视角是俯视(top down view,即沿 Z 轴向下看),但可以通过dxf命名空间中的view_target_point和view_direction_vector属性更改为任意视角:

vp = paperspace.add_viewport( center=(16, 10), size=(4, 4), view_center_point=(0, 0), view_height=30 ) vp.dxf.view_target_point = (40, 40, 0) vp.dxf.view_direction_vector = (-1, -1, 1)

从源码 viewport.py 的 DXF 属性定义看:

  • view_direction_vector(组码 16,3D 点)默认值为Z_AXIS(即(0, 0, 1)),对应俯视;
  • view_target_point(组码 17,3D 点)默认值为NULLVEC((0, 0, 0))。

get_view_direction()方法会返回归一化的视角向量,is_top_view属性判断是否仍为俯视(与 Z 轴接近即为俯视,见 viewport.py)。示例中设置view_target_point=(40, 40, 0)、view_direction_vector=(-1, -1, 1)后,视口将以斜向 45° 视角展示 3D 网格,即"Viewport to 3D Mesh"视图。注意:get_transformation_matrix()(模型空间到图纸空间变换矩阵)目前仅支持俯视视口(见 viewport.py 注释)。

视口边框(Viewport Frame):用图层控制显隐

VIEWPORT 的边框线(borderlines)默认显示在图纸空间中,但VIEWPORT 实体本身没有控制边框显隐的属性。边框的可见性由分配给 VIEWPORT 实体的图层控制——在 ezdxf 中默认图层是 "VIEWPORTS"(源码见 layout.py 的"layer": "VIEWPORTS"注释:"use separated layer to turn off for plotting")。

关闭该图层即可隐藏该图层上所有 VIEWPORT 实体的边框,但图层 "VIEWPORTS" 需要由库的使用者自行创建:

vp_layer = doc.layers.add("VIEWPORTS") vp_layer.off()

示例 viewports_in_paperspace.py 中的写法更稳健:先检查图层是否存在,不存在才创建,然后统一关闭:

if "VIEWPORTS" not in doc.layers: vp_layer = doc.layers.add("VIEWPORTS") else: vp_layer = doc.layers.get("VIEWPORTS") vp_layer.off()

源码注释还指出VIEWPORT 图层不是固定的:可以通过add_viewport(..., dxfattribs={"layer": "MyViewportLayer"})指定自定义视口图层。

冻结图层(Freeze Layers):每个视口独立控制可见性

每个 VIEWPORT 可以有独立的冻结图层,冻结的图层在该视口内不可见。要冻结视口中的图层,只需把冻结图层名称以列表(list-like 对象)形式赋值给 VIEWPORT 实体的frozen_layers属性:

vp.frozen_layers = ["Layer0", "Layer1"]

从源码 viewport.py 看,frozen_layers是列表属性,并配套提供了更精细的操作方法:

  • freeze(layer_name):冻结指定图层;
  • thaw(layer_name):解冻指定图层;
  • is_frozen(layer_name):查询指定图层是否已冻结。

图层名比较通过validator.make_table_key()统一键化,因此大小写不敏感(与所有表名规则一致)。

重要:关于不存在的图层名

原教程特别提示:AutoCAD 和 BricsCAD 不会因图层名没有对应的图层表条目而崩溃,且图层名称与所有表名一样不区分大小写。源码 viewport.py 的post_load_hook印证了这一点:读取文件时冻结图层以句柄形式存储(组码 331),加载后通过实体数据库把句柄解析回图层名,遇到不存在的图层(KeyError)会被静默忽略(pass),不会报错。

覆盖图层属性(Override Layer Properties):逐视口的图层样式覆盖

每个 VIEWPORT 可以单独覆盖图层属性。这些覆盖存储在Layer实体中,并通过 VIEWPORT 的句柄(handle)引用。该流程略为复杂,示例文件 viewports_override_layer_attributes.py 有完整演示,核心四步:

  1. 获取Layer对象;
  2. 从图层获取LayerOverrides对象;
  3. 覆盖该 VIEWPORT 的属性;
  4. 提交更改。
layer = doc.layers.get("Layer0") override = layer.get_vp_overrides() override.set_linetype(vp.dxf.handle, "DASHED") override.commit()

LayerOverrides 源码级解析

LayerOverrides类定义于 layer.py,通过Layer.get_vp_overrides()(见 layer.py)获取。支持的属性覆盖包括:

  • ACI 颜色(set_color(vp_handle, value),仅接受合法的图层颜色索引)
  • 真彩色(set_rgb(vp_handle, value),RGB 三元组,传None可移除真彩色设置)
  • 透明度(set_transparency(vp_handle, value),0.0 完全不透明,1.0 完全透明)
  • 线型(set_linetype(vp_handle, value),要求该线型存在于 LTYPE 表中,否则抛出ValueError)
  • 线宽(set_lineweight(vp_handle, value),要求是合法的图层线宽值)

对应的方法均有配套的get_*()读取方法。关键点是 layer.py 中commit()的注释:"Without a commit() all changes are lost!"(不调用 commit() 所有更改都会丢失!)——覆盖数据在内存中累积,只有commit()才会真正写回Layer实体的扩展数据。此外还有has_overrides()(查询某视口是否存在覆盖)和discard(vp_handle=None)(丢弃指定视口或全部视口的覆盖)。

示例脚本 viewports_override_layer_attributes.py 分别演示了override_aci(逐视口设置 ACI 色)、override_rgb(逐视口设置 RGB 真彩色)、override_ltype(逐视口设置线型,如 DASHED2/DOT2)和override_lw(逐视口设置线宽,如 13-140 等标准线宽值)四种覆盖方式,并把$LWDISPLAY头部变量置 1 以便在 DXF 查看器中显示线宽。仓库中还有对应的单元测试 test_141_layer_vp_override.py 可作参考。

完整运行示例与配套工具

把上述所有元素组合起来,运行 viewports_in_paperspace.py:

python examples/viewports_in_paperspace.py

脚本的main()会依次为 R12、R2000、R2007 三个版本生成viewports_in_paperspace_{dxfversion}.dxf文件(默认输出到~/Desktop/Outbox,目录不存在时输出到当前目录)。R2000/R2007 文件中包含矩形(1:2)、圆(1:5)、三角形(1:1)、总览视图(1:1)、1:50 微缩视口以及 3D 网格斜视角视口等多个视口。

需要一提的是,示例最后调用了 document.py 的set_modelspace_vport(height, center)方法(如 viewports_override_layer_attributes.py 中的doc.set_modelspace_vport(60, (50, 30))),它用于设置 CAD 应用打开文件时模型空间的初始视图/缩放位置,替换当前的"*Active"视口配置并重置坐标系为 WCS——这是让用户打开文件后直接看到目标内容的实用技巧。

小结

本文完整覆盖了 ezdxf 图纸空间视口布局的六大技术要点:

  1. 页面设置:page_setup()(及辅助的Drawing.page_setup())定义纸张尺寸、边距、单位、旋转与比例,注意margins为(上, 右, 下, 左)顺时针顺序;
  2. 图纸空间绘图:原点在画布左下角,画布 = 纸张 − 边距,R12 请改用page_setup_r12();
  3. 添加视口:add_viewport(center, size, view_center_point, view_height, status),默认图层 "VIEWPORTS";
  4. 缩放比例:视口高度 / view_height(源码get_scale()直接印证),相等即 1:1;
  5. 方向与边框:默认俯视,通过view_target_point/view_direction_vector改变视角,通过关闭 "VIEWPORTS" 图层隐藏边框;
  6. 图层冻结与属性覆盖:frozen_layers逐视口冻结图层,LayerOverrides逐视口覆盖 ACI 颜色、真彩色、透明度、线型与线宽,记得调用commit()。

若需在实战中进一步验证视口实体的序列化与缩放行为,可阅读测试文件 test_211_viewport.py(含 R12 与 R2000+ 的 VIEWPORT 实体读写测试),以及图层概念的官方文档 layers.rst。

  • 图形学

【免费下载链接】ezdxf

Python interface to DXF

项目地址:https://gitcode.com/gh_mirrors/ez/ezdxf
点击查看免费下载
上一篇:umy-ui高性能表格组件库深度使用指南
下一篇:claude-code-best-practice工作流设计:提升团队协作效率的6个关键策略

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询