fix(task-17): the headless page setup writes the scale AutoCAD plots by, and reads the code 75 AutoCAD writes

Two measured root causes on the headless engine, both silent:

1. page_setup_apply wrote PLOTSETTINGS group 147 (unit_factor) as 1.0 for
   every scale. Under USE_STANDARD_SCALE AutoCAD's plot engine scales by
   group 147 alone (a 1:10 sheet with code 21 and 147 = 1.0 plotted a
   100-unit line at 99.99 mm while the row, `applied` and 142/143 said
   1:10; patching 147 to 0.1 alone gave 9.99 mm). AutoCAD writes 147 as
   numerator / denominator (measured live on AutoCAD 2026 for fit, 1:1,
   1:2, 1:10, 1:50, 2:1, 10:1: 1.0, 1.0, 0.5, 0.1, 0.02, 2.0, 10.0). The
   writer now stores 75, 142/143 and 147 from the one resolved ratio on
   every route, so no reader can disagree with the plot.

2. DXF_STANDARD_SCALE_TYPE and _plot_scale followed ezdxf's STD_SCALES,
   which has no 19 = 1:5 and numbers everything above 18 one lower than
   the ObjectARX StdScaleType enum AutoCAD actually writes (1:10 = 21,
   1:20 = 23, 1:50 = 26, 1:100 = 27, 2:1 = 28, 10:1 = 31 — measured via COM
   SaveAs DXF). An AutoCAD-authored 1:50 sheet listed as "1:100" and a
   1:10 sheet rendered at 1:16. papers.py now authors the full file enum
   (DXF_STD_SCALES, codes 1-33 with AutoCAD's imperial names), the writer
   uses the same codes ActiveX uses, and nothing goes through ezdxf's
   table. 1:5 stays on the custom route on both engines (the live setter
   refuses 19) while the reader knows code 19 for files AutoCAD wrote.

Tests: the plan's `1:50 -> 25` assertion was the stale numbering and now
pins 26; a pure-data test pins the enum against the measured codes and the
ActiveX numbering; a parametrised writer test checks 75/142/143/147 for
every catalogue scale; a reader test stores the scale exactly as AutoCAD
saves it (including codes 19 and 22 the writer never emits) and checks
page_setup_list and _paper_frame report and render it at its own scale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
Umut CAN
2026-09-17 15:29:47 +03:00
co-authored by Claude Opus 5
parent f2386a9fdd
commit aeb27c252b
3 changed files with 281 additions and 39 deletions
+18 -13
View File
@@ -2405,12 +2405,12 @@ class EzdxfBackend(AutoCADBackend):
Reads the same bits ``_page_setup_row`` reports, so the label here is
the label ``page_setup_list`` shows: a standard code under
``USE_STANDARD_SCALE`` resolves through ezdxf's ``STD_SCALES`` table,
``USE_STANDARD_SCALE`` resolves through the authored ``DXF_STD_SCALES``
(the ObjectARX enum AutoCAD writes — ezdxf's ``STD_SCALES`` is one off
above 18 and would render an AutoCAD-authored 1:10 sheet at 1:16),
anything else through the stored numerator / denominator.
"""
from ezdxf.lldxf.const import STD_SCALES
from engineering.standards.papers import dxf_scale_label, scale_label
from engineering.standards.papers import DXF_STD_SCALES, dxf_scale_label, scale_label
dxf = layout.dxf_layout.dxf
flags = int(dxf.get("plot_layout_flags", dxf.get_default("plot_layout_flags")))
@@ -2419,8 +2419,8 @@ class EzdxfBackend(AutoCADBackend):
code = int(dxf.standard_scale_type)
if code == 0:
return "fit", None, None
if code in STD_SCALES:
numerator, denominator = (float(v) for v in STD_SCALES[code])
if code in DXF_STD_SCALES:
numerator, denominator = (float(v) for v in DXF_STD_SCALES[code])
return dxf_scale_label(code, numerator, denominator), numerator, denominator
return scale_label(numerator, denominator), numerator, denominator
@@ -2586,8 +2586,6 @@ class EzdxfBackend(AutoCADBackend):
resolved = require_page_setup(setup)
def _sync():
from ezdxf.lldxf.const import STD_SCALES
target = self._paper_layout(layout, "page_setup_apply")
before = self._page_setup_row(target)
dxf = target.dxf_layout.dxf
@@ -2619,21 +2617,28 @@ class EzdxfBackend(AutoCADBackend):
dxf.left_margin, dxf.right_margin = left, right
dxf.plot_origin_x_offset = 0.0
dxf.plot_origin_y_offset = 0.0
dxf.unit_factor = 1.0
dxf.current_style_sheet = resolved["plot_style"]
target.use_plot_styles(True)
target.set_plot_type(int(resolved["plot_type"]))
# The scale is stored three ways and AutoCAD reads a different
# one per route (measured, AutoCAD 2026 — papers.py docstring):
# under USE_STANDARD_SCALE its plot engine scales by group 147
# alone (a sheet with code 21 and 147 = 1.0 plots 1:10 geometry
# at 1:1 while page_setup_list says 1:10); under the custom
# route it scales by 142/143. All three are written, on every
# route, from the one resolved ratio, so no reader can disagree
# with the plot. ``fit`` has ratio 1/1 and 147 = 1.0, as AutoCAD
# writes it.
code = resolved["dxf_standard_scale_type"]
numerator, denominator = (float(v) for v in resolved["scale_ratio"])
if code is not None:
dxf.standard_scale_type = int(code)
numerator, denominator = STD_SCALES.get(int(code), (1.0, 1.0))
dxf.scale_numerator, dxf.scale_denominator = float(numerator), float(denominator)
target.use_standard_scale(True)
else:
numerator, denominator = (float(v) for v in resolved["scale_ratio"])
dxf.standard_scale_type = 16
dxf.scale_numerator, dxf.scale_denominator = numerator, denominator
target.use_standard_scale(False)
dxf.scale_numerator, dxf.scale_denominator = numerator, denominator
dxf.unit_factor = numerator / denominator
# resolve_page_setup hands over None for a layout plot: the bit is
# cleared, never set — the state AutoCAD itself stores for acLayout,
# and the mirror of the live engine never writing CenterPlot there.