Druck-refactor

This commit is contained in:
2026-04-17 16:52:53 +02:00
parent 4b842bf635
commit 4049090e92
4 changed files with 518 additions and 104 deletions
+34 -27
View File
@@ -97,6 +97,7 @@ class PrintLayoutBaufreigabe(PrintLayout):
plotmassstab: float,
thema: str = "",
fit_extent_to_map: bool = True,
atlas_driven: bool = True,
) -> Any:
"""Erstellt ein Baufreigabe-Layout mit fester DIN A3/A4 Querformat-Groesse.
@@ -108,7 +109,7 @@ class PrintLayoutBaufreigabe(PrintLayout):
extent: QgsRectangle der Kartenausdehnung
plotmassstab: Plotmassstab (z.B. 5000 fuer 1:5000)
thema: Name des Visibility-Presets (optional)
fit_extent_to_map: Wird fuer Baufreigabe ignoriert (feste Kartenflaeche)
fit_extent_to_map: Extent bei Bedarf auf das Karten-Seitenverhaeltnis erweitern
Returns:
QgsPrintLayout objektInstance (wird im Layout-Dialog geoeffnet)
@@ -152,10 +153,17 @@ class PrintLayoutBaufreigabe(PrintLayout):
map_height_mm,
)
_ = fit_extent_to_map
if extent is not None and hasattr(extent, "isNull") and callable(extent.isNull) and not extent.isNull():
fitted_extent = extent
if fit_extent_to_map:
fitted_extent = self._fit_extent_to_map_item(extent, map_width_mm, map_height_mm)
if fitted_extent is not None and hasattr(fitted_extent, "isNull") and callable(fitted_extent.isNull) and not fitted_extent.isNull():
try:
hauptkarte.zoomToExtent(extent)
set_extent = getattr(hauptkarte, "setExtent", None)
if callable(set_extent):
set_extent(fitted_extent)
else:
hauptkarte.zoomToExtent(fitted_extent)
except Exception:
pass
@@ -165,31 +173,30 @@ class PrintLayoutBaufreigabe(PrintLayout):
except Exception:
pass
# Hauptkarte standardmaessig Atlas-gesteuert:
# - Extent folgt aktuellem Atlas-Objekt
# - Skalierung automatisch
# - Margin entspricht ca. 1 cm Rand innerhalb der Kartenflaeche
set_atlas_driven = getattr(hauptkarte, "setAtlasDriven", None)
if callable(set_atlas_driven):
try:
set_atlas_driven(True)
except Exception:
pass
# Atlas-Steuerung nur aktivieren, wenn Atlas tatsaechlich genutzt wird.
# Andernfalls bleiben Extent/Scale unveraendert (z.B. Canvas-Ausschnitt).
if atlas_driven:
set_atlas_driven_fn = getattr(hauptkarte, "setAtlasDriven", None)
if callable(set_atlas_driven_fn):
try:
set_atlas_driven_fn(True)
except Exception:
pass
set_atlas_scaling_mode = getattr(hauptkarte, "setAtlasScalingMode", None)
atlas_mode_auto = getattr(getattr(QgsLayoutItemMap, "AtlasScalingMode", object), "Auto", None)
if callable(set_atlas_scaling_mode) and atlas_mode_auto is not None:
try:
set_atlas_scaling_mode(atlas_mode_auto)
except Exception:
pass
set_atlas_scaling_mode = getattr(hauptkarte, "setAtlasScalingMode", None)
atlas_mode_auto = getattr(getattr(QgsLayoutItemMap, "AtlasScalingMode", object), "Auto", None)
if callable(set_atlas_scaling_mode) and atlas_mode_auto is not None:
try:
set_atlas_scaling_mode(atlas_mode_auto)
except Exception:
pass
set_atlas_margin = getattr(hauptkarte, "setAtlasMargin", None)
if callable(set_atlas_margin):
try:
set_atlas_margin(self._calc_atlas_margin_percent(map_width_mm, map_height_mm))
except Exception:
pass
set_atlas_margin = getattr(hauptkarte, "setAtlasMargin", None)
if callable(set_atlas_margin):
try:
set_atlas_margin(self._calc_atlas_margin_percent(map_width_mm, map_height_mm))
except Exception:
pass
set_frame_enabled = getattr(hauptkarte, "setFrameEnabled", None)
if callable(set_frame_enabled):
+291 -77
View File
@@ -75,6 +75,7 @@ THEMA_WIE_KARTENFENSTER = "wie kartenfenster"
DRUCKBEREICH_VERFAHRENSGEBIET = "Verfahrensgebiet"
DRUCKBEREICH_AKTUELLE_AUSWAHL = "aktuelle Auswahl"
DRUCKBEREICH_RECHTECK = "Rechteck"
DRUCKBEREICH_WIE_KARTENFENSTER = "Wie Kartenfenster"
KARTENNAME_BY_AUSWAHL = {
KARTENNAME_38: "Planungsübersicht §38 FlurbG",
@@ -99,8 +100,16 @@ DIN_GROESSEN: dict[str, tuple[int, int]] = {
"DIN A3": (297, 420),
"DIN A4": (210, 297),
}
DIN_AUTO_REIHENFOLGE = ("DIN A4", "DIN A3", "DIN A2", "DIN A1", "DIN A0")
DIN_STANDARD = "DIN A0"
DRUCKPUFFER_STANDARD_CM = 1
ENDLOSROLLE_MAX_BREITE_MM = 2000.0
STANDARD_LAYOUT_FIXBREITE_MM = 210.0
STANDARD_LAYOUT_FIXHOEHE_MM = 20.0
BAUFREIGABE_LAYOUT_FIXBREITE_MM = 110.0
BAUFREIGABE_LAYOUT_FIXHOEHE_MM = 20.0
STANDARD_A4_LANDSCAPE_W_MM = 297.0
STANDARD_A4_LANDSCAPE_H_MM = 210.0
class PrintLogic:
@@ -833,12 +842,69 @@ class PrintLogic:
return None
return self._build_rectangle(x_min, y_min, x_max, y_max)
def _get_canvas_extent(self) -> Any | None:
map_canvas_fn = getattr(iface, "mapCanvas", None)
if not callable(map_canvas_fn):
return None
try:
canvas = map_canvas_fn()
except Exception:
return None
if canvas is None:
return None
extent_fn = getattr(canvas, "extent", None)
if not callable(extent_fn):
return None
try:
extent = extent_fn()
except Exception:
return None
if extent is None:
return None
is_null = getattr(extent, "isNull", None)
if callable(is_null):
try:
if is_null():
return None
except Exception:
return None
width_fn = getattr(extent, "width", None)
height_fn = getattr(extent, "height", None)
if not callable(width_fn) or not callable(height_fn):
return None
try:
width_value = float(cast(Any, width_fn()))
height_value = float(cast(Any, height_fn()))
if width_value <= 0 or height_value <= 0:
return None
except Exception:
return None
return extent
def _resolve_extent(
self,
resolved_layer: object | None,
druckbereich_auswahl: str,
plotmassstab: float,
) -> tuple[Any | None, object | None]:
if druckbereich_auswahl == DRUCKBEREICH_WIE_KARTENFENSTER:
extent = self._get_canvas_extent()
if extent is None:
self.pruefmanager.zeige_hinweis(
"Druckbereich",
"Das aktuelle Kartenfenster liefert keine gueltige Ausdehnung.",
)
return None, None
return extent, None
if druckbereich_auswahl == DRUCKBEREICH_AKTUELLE_AUSWAHL:
selection_layer = self._resolve_selection_layer(resolved_layer)
extent = self._get_selected_extent(selection_layer)
@@ -855,7 +921,7 @@ class PrintLogic:
if extent is None:
self.activate_druckrechteck_capture(resolved_layer)
return None, None
return self._buffer_extent(extent, plotmassstab), None
return extent, None
lp = Layerpruefer(layer=resolved_layer)
ergebnis = lp.pruefe()
@@ -902,6 +968,7 @@ class PrintLogic:
def set_druckbereich_for_auswahl(self, auswahl: str) -> None:
if auswahl not in {
DRUCKBEREICH_VERFAHRENSGEBIET,
DRUCKBEREICH_WIE_KARTENFENSTER,
DRUCKBEREICH_AKTUELLE_AUSWAHL,
DRUCKBEREICH_RECHTECK,
}:
@@ -910,39 +977,166 @@ class PrintLogic:
def _get_baufreigabe_page_dimensions(
self,
zielgroesse: str,
) -> tuple[str, float, float, float, float]:
"""Berechnet feste Seiten- und Hauptkartengroessen fuer Baufreigabe.
required_map_width_mm: float,
required_map_height_mm: float,
max_format: str = "DIN A3",
) -> tuple[str, float, float, float, float, bool]:
"""Waehlt fuer Baufreigabe ein Blatt im Querformat.
Args:
zielgroesse: "DIN A3" oder "DIN A4" (wird zu Querformat konvertiert)
required_map_width_mm: benoetigte Breite der Hauptkarte in mm
required_map_height_mm: benoetigte Hoehe der Hauptkarte in mm
max_format: maximale Blattgroesse, standardmaessig DIN A3
Returns:
(din_format, page_width_mm, page_height_mm, map_w_mm, map_h_mm)
(din_format, page_width_mm, page_height_mm, map_w_mm, map_h_mm, fallback_applied)
"""
# Nur A3/A4 im Querformat fuer Baufreigabe
if zielgroesse == "DIN A3":
din_format = "DIN A3"
page_w, page_h = 420.0, 297.0
else:
din_format = "DIN A4"
page_w, page_h = 297.0, 210.0
candidates = (
("DIN A4", 297.0, 210.0),
("DIN A3", 420.0, 297.0),
)
# Feste Hauptkartenflaeche aus Querformat mit festen Rändern:
# links 10 mm, oben 10 mm, rechts 10 mm, unten 10 mm,
# plus 10 mm Abstand zum rechten Seitenbereich.
kartenschild_w = 80.0
kartenschild_h = 92.0
margin = 10.0
try:
max_index = [candidate[0] for candidate in candidates].index(max_format)
except ValueError:
max_index = len(candidates) - 1
map_w = page_w - (3 * margin + kartenschild_w)
map_h = page_h - (2 * margin)
limited_candidates = candidates[: max_index + 1]
# Untergrenzen zur Sicherheit bei abweichenden Seiteneinstellungen
map_w = max(map_w, 50.0)
map_h = max(map_h, kartenschild_h + margin)
for din_format, page_w, page_h in limited_candidates:
map_w = page_w - BAUFREIGABE_LAYOUT_FIXBREITE_MM
map_h = page_h - BAUFREIGABE_LAYOUT_FIXHOEHE_MM
if map_w >= required_map_width_mm and map_h >= required_map_height_mm:
return din_format, page_w, page_h, map_w, map_h, False
return din_format, page_w, page_h, map_w, map_h
# Wenn der gewuenschte Ausschnitt nicht passt, verwende das maximal erlaubte Blatt
# und erweitere die Hauptkarte auf die verfuegbare Kartenflaeche (zentriert per fit_extent_to_map).
din_format, page_w, page_h = limited_candidates[-1]
map_w = max(page_w - BAUFREIGABE_LAYOUT_FIXBREITE_MM, 50.0)
map_h = max(page_h - BAUFREIGABE_LAYOUT_FIXHOEHE_MM, 50.0)
return din_format, page_w, page_h, map_w, map_h, True
def _does_plot_fit_page(
self,
page_width_mm: float,
page_height_mm: float,
map_width_mm: float,
map_height_mm: float,
fixed_width_mm: float,
fixed_height_mm: float,
) -> bool:
required_w = map_width_mm + fixed_width_mm
required_h = map_height_mm + fixed_height_mm
return required_w <= page_width_mm and required_h <= page_height_mm
def _get_plot_dimensions_mm(
self,
map_width_mm: float,
map_height_mm: float,
fixed_width_mm: float,
fixed_height_mm: float,
) -> tuple[float, float]:
return map_width_mm + fixed_width_mm, map_height_mm + fixed_height_mm
def _get_required_map_dimensions_mm(self, extent: Any, plotmassstab: float) -> tuple[float, float]:
"""Berechnet die benoetigte Kartenfenstergroesse in mm fuer Extent und Massstab."""
if extent is None or not isinstance(plotmassstab, (int, float)) or plotmassstab <= 0:
return 0.0, 0.0
try:
width_mm = float(extent.width()) * 1000.0 / float(plotmassstab)
height_mm = float(extent.height()) * 1000.0 / float(plotmassstab)
except Exception:
return 0.0, 0.0
return max(width_mm, 0.0), max(height_mm, 0.0)
def _select_standard_page_layout(
self,
required_map_width_mm: float,
required_map_height_mm: float,
max_format: str,
) -> tuple[float, float, float, float] | None:
"""Waehlt das kleinste passende DIN-Blatt fuer das Standardlayout."""
try:
max_index = DIN_AUTO_REIHENFOLGE.index(max_format)
except ValueError:
max_index = len(DIN_AUTO_REIHENFOLGE) - 1
for fmt in DIN_AUTO_REIHENFOLGE[: max_index + 1]:
base_w, base_h = DIN_GROESSEN[fmt]
fitting_candidates: list[tuple[float, float, float, float]] = []
for page_w, page_h in ((float(base_w), float(base_h)), (float(base_h), float(base_w))):
map_w = page_w - STANDARD_LAYOUT_FIXBREITE_MM
map_h = page_h - STANDARD_LAYOUT_FIXHOEHE_MM
if map_w >= required_map_width_mm and map_h >= required_map_height_mm:
fitting_candidates.append((page_w, page_h, map_w, map_h))
if fitting_candidates:
return min(
fitting_candidates,
key=lambda candidate: (candidate[2] - required_map_width_mm) + (candidate[3] - required_map_height_mm),
)
return None
def _get_standard_layout_dimensions(
self,
required_map_width_mm: float,
required_map_height_mm: float,
max_format: str,
formfaktor: bool,
) -> tuple[float, float, float, float, bool] | None:
if formfaktor:
plotgroesse_w, plotgroesse_h = self._get_plot_dimensions_mm(
required_map_width_mm,
required_map_height_mm,
STANDARD_LAYOUT_FIXBREITE_MM,
STANDARD_LAYOUT_FIXHOEHE_MM,
)
din_dims = DIN_GROESSEN.get(max_format, DIN_GROESSEN[DIN_STANDARD])
max_page_h = float(max(din_dims))
page_w = min(plotgroesse_w, ENDLOSROLLE_MAX_BREITE_MM)
page_h = min(plotgroesse_h, max_page_h)
return self._apply_standard_minimum_a4_fallback(
page_w,
page_h,
required_map_width_mm,
required_map_height_mm,
)
selected_layout = self._select_standard_page_layout(
required_map_width_mm,
required_map_height_mm,
max_format,
)
if selected_layout is None:
return None
page_w, page_h, map_w, map_h = selected_layout
force_fit_extent = map_w > required_map_width_mm or map_h > required_map_height_mm
return page_w, page_h, map_w, map_h, force_fit_extent
def _apply_standard_minimum_a4_fallback(
self,
page_width_mm: float,
page_height_mm: float,
map_width_mm: float,
map_height_mm: float,
) -> tuple[float, float, float, float, bool]:
"""Erzwingt bei zu kleinem Standardlayout mindestens DIN A4 im Querformat.
Liefert zusätzlich ein Flag zurück, damit die Extent-Anpassung im Layout
zur zentrierten Darstellung aktiviert werden kann.
"""
fallback_page_w = max(page_width_mm, STANDARD_A4_LANDSCAPE_W_MM)
fallback_page_h = max(page_height_mm, STANDARD_A4_LANDSCAPE_H_MM)
if fallback_page_w == page_width_mm and fallback_page_h == page_height_mm:
return page_width_mm, page_height_mm, map_width_mm, map_height_mm, False
fallback_map_w = max(map_width_mm, fallback_page_w - STANDARD_LAYOUT_FIXBREITE_MM, 50.0)
fallback_map_h = max(map_height_mm, fallback_page_h - STANDARD_LAYOUT_FIXHOEHE_MM, 50.0)
return fallback_page_w, fallback_page_h, fallback_map_w, fallback_map_h, True
def druckvorlage_anlegen(
self,
@@ -960,11 +1154,19 @@ class PrintLogic:
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
if massstab_auswahl == MASSSTAB_WIE_KARTENFENSTER:
massstab_str = get_variable(PLOTMASSSTAB_VAR, scope="project") or ""
# Maßstab direkt vom aktuellen Canvas lesen; Projektvariable als Fallback.
try:
massstab_zahl = float(massstab_str)
except (ValueError, TypeError):
canvas = iface.mapCanvas()
live_scale = canvas.scale() if canvas is not None else 0.0
massstab_zahl = float(live_scale) if live_scale > 0 else 0.0
except Exception:
massstab_zahl = 0.0
if massstab_zahl <= 0:
massstab_str = get_variable(PLOTMASSSTAB_VAR, scope="project") or ""
try:
massstab_zahl = float(massstab_str)
except (ValueError, TypeError):
massstab_zahl = 0.0
else:
massstab_zahl = float(PLOTMASSSTAB_BY_AUSWAHL.get(massstab_auswahl, 0))
@@ -984,44 +1186,58 @@ class PrintLogic:
# Alt: Rückwärtskompatibilität
set_variable("verfahrensgebietslayer", layer_id, scope="project")
kartenbild_w = extent.width() * 1000.0 / massstab_zahl
kartenbild_h = extent.height() * 1000.0 / massstab_zahl
kartenbild_w, kartenbild_h = self._get_required_map_dimensions_mm(extent, massstab_zahl)
plotgroesse_w = kartenbild_w + 210.0
plotgroesse_h = kartenbild_h + 20.0
din_dims = DIN_GROESSEN.get(zielgroesse, DIN_GROESSEN[DIN_STANDARD])
din_w = float(din_dims[0])
din_h = float(din_dims[1])
din_max_h = float(max(din_dims))
if formfaktor:
ziel_w, ziel_h = plotgroesse_w, min(plotgroesse_h, din_max_h)
force_fit_extent_for_min_a4 = False
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
din_format, auto_page_w, auto_page_h, auto_map_w, auto_map_h, _bf_fallback = self._get_baufreigabe_page_dimensions(
kartenbild_w,
kartenbild_h,
max_format=zielgroesse if zielgroesse in {"DIN A4", "DIN A3"} else "DIN A3",
)
else:
cap_w, cap_h = din_w, din_h
if not (plotgroesse_w <= cap_w and plotgroesse_h <= cap_h):
if plotgroesse_w <= din_h and plotgroesse_h <= din_w:
cap_w, cap_h = din_h, din_w
ziel_w = min(plotgroesse_w, cap_w)
ziel_h = min(plotgroesse_h, cap_h)
if kartenname_auswahl != KARTENNAME_BAUFREIGABE:
if ziel_w < DIN_GROESSEN["DIN A4"][0] or ziel_h < DIN_GROESSEN["DIN A4"][1]:
self.pruefmanager.zeige_hinweis(
"Blattgroesse kleiner als DIN A4",
"Die Layouterstellung wird trotzdem fortgesetzt.",
)
if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h:
if plotgroesse_w <= ziel_h and plotgroesse_h <= ziel_w:
ziel_w, ziel_h = ziel_h, ziel_w
if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h:
standard_layout = self._get_standard_layout_dimensions(
kartenbild_w,
kartenbild_h,
zielgroesse,
formfaktor,
)
if standard_layout is None:
self.pruefmanager.zeige_hinweis(
"Zielgroesse zu klein",
"Die gewaehlte Zielgroesse reicht fuer den Einzelblatt-Druck nicht aus.",
)
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
auto_page_w, auto_page_h, auto_map_w, auto_map_h, force_fit_extent_for_min_a4 = standard_layout
# Finale Sicherheitspruefung: alle Layoutobjekte muessen auf die Seite passen.
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
fits_page = self._does_plot_fit_page(
auto_page_w,
auto_page_h,
auto_map_w,
auto_map_h,
BAUFREIGABE_LAYOUT_FIXBREITE_MM,
BAUFREIGABE_LAYOUT_FIXHOEHE_MM,
)
else:
fits_page = self._does_plot_fit_page(
auto_page_w,
auto_page_h,
auto_map_w,
auto_map_h,
STANDARD_LAYOUT_FIXBREITE_MM,
STANDARD_LAYOUT_FIXHOEHE_MM,
)
if not fits_page:
self.pruefmanager.zeige_hinweis(
"Zielgroesse zu klein",
"Die gewaehlte Zielgroesse reicht fuer den Einzelblatt-Druck nicht aus.",
)
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get(
kartenname_auswahl, "Vorlage"
)
@@ -1037,34 +1253,41 @@ class PrintLogic:
vorlage_name = (vorlage_name or "").strip() or kartenname
# Atlas-Status vorab bestimmen, damit er sowohl an create_ als auch configure_ uebergeben wird.
atlas_raw_bf = get_variable(ATLAS_ERZEUGEN_UI_VAR, scope="project")
atlas_erzeugen_bf = True if atlas_raw_bf in (None, "") else str(atlas_raw_bf).strip().lower() in {
"1", "true", "ja", "yes", "on",
}
# Verzweigung: Baufreigabe vs. Standard-Layout
try:
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
# Baufreigabe: feste DIN A3/A4 Querformat-Logik mit 80mm Kartenschild
din_format, page_w, page_h, map_w, map_h = self._get_baufreigabe_page_dimensions(zielgroesse)
din_format = "DIN A3" if auto_page_w >= 420.0 else "DIN A4"
PrintLayoutBaufreigabe().create_baufreigabe_layout(
name=vorlage_name if vorlage_name != kartenname else "Baufreigabe",
din_format=din_format,
map_width_mm=map_w,
map_height_mm=map_h,
map_width_mm=auto_map_w,
map_height_mm=auto_map_h,
extent=extent,
plotmassstab=massstab_zahl,
thema=thema,
fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET),
fit_extent_to_map=True,
atlas_driven=atlas_erzeugen_bf,
)
else:
# Standard-Layout: dynamische Groessenanpassung
PrintLayout().create_single_page_layout(
name=vorlage_name,
page_width_mm=ziel_w,
page_height_mm=ziel_h,
map_width_mm=kartenbild_w,
map_height_mm=kartenbild_h,
page_width_mm=auto_page_w,
page_height_mm=auto_page_h,
map_width_mm=auto_map_w,
map_height_mm=auto_map_h,
extent=extent,
plotmassstab=massstab_zahl,
kartenname_auswahl=kartenname_auswahl,
thema=thema,
fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET),
fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET) or force_fit_extent_for_min_a4,
)
except ValueError as exc:
self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc))
@@ -1078,16 +1301,7 @@ class PrintLogic:
# Nach erfolgreicher Layout-Erstellung: Atlas ggf. konfigurieren (nur falls aktiviert)
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
atlas_raw = get_variable(ATLAS_ERZEUGEN_UI_VAR, scope="project")
atlas_erzeugen = True if atlas_raw in (None, "") else str(atlas_raw).strip().lower() in {
"1",
"true",
"ja",
"yes",
"on",
}
if atlas_erzeugen:
if atlas_erzeugen_bf:
self.configure_atlas_for_baufreigabe_layout()
else:
self.configure_atlas_for_baufreigabe_layout(