fix(task-17): rotated sheets (plot_rotation 1/3) render the whole page instead of a 0.707-scaled, clipped window
AutoCAD stores ISO A4 landscape as 210 x 297 media with plot_rotation 1;
paper-space X then runs along the 297 mm side. _paper_frame turned the
figure but not the axes window, so a 297 x 210 page carried a 210-wide data
window; set_aspect("equal") shrank the axes box to 0.707 and everything past
x = 210 minus the margin was cut, with ok: True and a MediaBox that read as
correct. The window now takes the turned size, and the margins turn with it
the way ezdxf's own Page.from_dxf_layout maps them (rotation 2 included).
Test: a rotated layout with a 277 mm frame exported to PNG; the ink must
reach the right of the sheet (it stopped at column 1314/1753, now 1752),
and the MediaBox still reads 297 x 210.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5
parent
f46fda10cb
commit
70f18efb4b
@@ -2379,21 +2379,40 @@ class EzdxfBackend(AutoCADBackend):
|
||||
"""The sheet as a matplotlib figure: size in inches plus the axes window.
|
||||
|
||||
Used by ``drawing_export_pdf`` so the PDF's /MediaBox *is* the paper.
|
||||
``plot_rotation`` 1/3 swaps the figure; the content is not rotated by
|
||||
the headless renderer (reported, not hidden).
|
||||
|
||||
``plot_rotation`` 1/3 is AutoCAD's landscape-on-portrait-media
|
||||
convention: the media stays 210 x 297 and the sheet is turned, so
|
||||
paper-space X runs along the 297 mm side. The figure *and* the axes
|
||||
window both take the turned size — sizing only the figure left the
|
||||
window 210 wide inside a 297 mm page, and ``set_aspect("equal")`` then
|
||||
shrank the axes box to 0.707 and cut everything past x = 210 minus the
|
||||
margin (measured: a 277 mm frame lost its right 27%, with ``ok: True``
|
||||
and a MediaBox that read as correct). The margins turn with the sheet,
|
||||
mapped the way ezdxf's own ``Page.from_dxf_layout`` maps them. What
|
||||
the headless renderer still does not do is rotate the *content* on the
|
||||
media (reported as ``rotation_applied``, not hidden).
|
||||
"""
|
||||
dxf = layout.dxf_layout.dxf
|
||||
width, height = float(dxf.paper_width), float(dxf.paper_height)
|
||||
rotation = int(dxf.plot_rotation)
|
||||
unit = 25.4 if int(dxf.plot_paper_units) == 0 else 1.0
|
||||
x0 = -(float(dxf.left_margin) + float(dxf.plot_origin_x_offset))
|
||||
y0 = -(float(dxf.bottom_margin) + float(dxf.plot_origin_y_offset))
|
||||
fig_w, fig_h = (height, width) if rotation in (1, 3) else (width, height)
|
||||
top, right = float(dxf.top_margin), float(dxf.right_margin)
|
||||
bottom, left = float(dxf.bottom_margin), float(dxf.left_margin)
|
||||
if rotation == 1:
|
||||
sheet_w, sheet_h, left, bottom = height, width, top, left
|
||||
elif rotation == 2:
|
||||
sheet_w, sheet_h, left, bottom = width, height, right, top
|
||||
elif rotation == 3:
|
||||
sheet_w, sheet_h, left, bottom = height, width, bottom, right
|
||||
else:
|
||||
sheet_w, sheet_h = width, height
|
||||
x0 = -(left + float(dxf.plot_origin_x_offset))
|
||||
y0 = -(bottom + float(dxf.plot_origin_y_offset))
|
||||
return {
|
||||
"size_mm": [round(fig_w, 2), round(fig_h, 2)],
|
||||
"figsize": (fig_w / 25.4, fig_h / 25.4),
|
||||
"xlim": (x0 / unit, (x0 + width) / unit),
|
||||
"ylim": (y0 / unit, (y0 + height) / unit),
|
||||
"size_mm": [round(sheet_w, 2), round(sheet_h, 2)],
|
||||
"figsize": (sheet_w / 25.4, sheet_h / 25.4),
|
||||
"xlim": (x0 / unit, (x0 + sheet_w) / unit),
|
||||
"ylim": (y0 / unit, (y0 + sheet_h) / unit),
|
||||
"rotation_applied": rotation == 0,
|
||||
}
|
||||
|
||||
|
||||
@@ -222,6 +222,62 @@ async def test_apply_portrait_a4(backend, tmp_path):
|
||||
assert abs(width - 210.0) <= 0.5 and abs(height - 297.0) <= 0.5
|
||||
|
||||
|
||||
def _ink_columns(png_path: str) -> tuple[int, int, int]:
|
||||
"""(first inked column, last inked column, image width) of a rendered PNG."""
|
||||
from PIL import Image
|
||||
|
||||
image = Image.open(png_path).convert("L")
|
||||
width, height = image.size
|
||||
pixels = image.load()
|
||||
inked = [x for x in range(width) if any(pixels[x, y] < 128 for y in range(height))]
|
||||
assert inked, "the render is blank"
|
||||
return inked[0], inked[-1], width
|
||||
|
||||
|
||||
async def test_rotated_layout_renders_the_whole_sheet(backend, tmp_path):
|
||||
"""plot_rotation 1 is AutoCAD's landscape-on-portrait-media convention.
|
||||
|
||||
A drawing authored in AutoCAD stores ISO A4 landscape as paper 210 x 297
|
||||
with ``plot_rotation`` 1; paper-space X then runs along the 297 mm side.
|
||||
The MediaBox alone cannot see a frame that has been clipped inside a
|
||||
correctly sized page, so this test looks at the ink: the frame's right
|
||||
edge (x = 277, the printable width) must reach the right of the sheet.
|
||||
"""
|
||||
pytest.importorskip("matplotlib", reason="rendering needs the [pdf] extra")
|
||||
pytest.importorskip("PIL", reason="pixel check needs Pillow")
|
||||
await _sheet(backend, "Rot")
|
||||
lay = backend._doc.layouts.get("Rot")
|
||||
lay.page_setup(
|
||||
size=(210, 297),
|
||||
margins=(20, 7.5, 20, 7.5),
|
||||
units="mm",
|
||||
rotation=1,
|
||||
scale=16,
|
||||
name="ISO_A4",
|
||||
device="DWG To PDF.pc3",
|
||||
)
|
||||
lay.add_lwpolyline([(0, 0), (277, 0), (277, 190), (0, 190)], close=True)
|
||||
|
||||
row = (await backend.page_setup_list("Rot"))[0]
|
||||
assert row["size_mm"] == [297.0, 210.0] and row["orientation"] == "landscape"
|
||||
|
||||
png = tmp_path / "rot.png"
|
||||
result = await backend.drawing_export_pdf(str(png), layout="Rot")
|
||||
assert result["ok"] is True, result
|
||||
assert result["paper_mm"] == [297.0, 210.0]
|
||||
assert result["rotation_applied"] is False
|
||||
|
||||
first, last, width = _ink_columns(str(png))
|
||||
# x = 277 sits at 277 / 297 of the sheet from the left margin, i.e. well
|
||||
# past the 0.707-scaled box the unrotated window produced (col 1314/1753).
|
||||
assert last >= int(width * 0.9), (first, last, width)
|
||||
|
||||
pdf = tmp_path / "rot.pdf"
|
||||
await backend.drawing_export_pdf(str(pdf), layout="Rot")
|
||||
mm_w, mm_h = read_mediabox(str(pdf))
|
||||
assert abs(mm_w - 297.0) <= 0.5 and abs(mm_h - 210.0) <= 0.5
|
||||
|
||||
|
||||
async def test_apply_materialises_the_default_flags_before_setting_centre(backend):
|
||||
"""Measured: set_flag_state starts from 0 on a fresh layout, so a naive
|
||||
plot_centered(True) writes 4 and drops lineweights/plot-styles/viewports-first."""
|
||||
|
||||
Reference in New Issue
Block a user