From d21483ce53e3084da3c70489a832cf62decd5b71 Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 20 Mar 2026 14:02:15 +0100 Subject: [PATCH] =?UTF-8?q?Layouterzeugung=20erg=C3=A4nzt=20(nur=20Einzelb?= =?UTF-8?q?l=C3=A4tter)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/layout.py | 125 ++++++++++++++++++++++++++++++++++++++++++++++ ui/tab_b_logic.py | 66 +++++++++++++++++++----- ui/tab_b_ui.py | 50 +++++++++++++++++-- 3 files changed, 224 insertions(+), 17 deletions(-) create mode 100644 ui/layout.py diff --git a/ui/layout.py b/ui/layout.py new file mode 100644 index 0000000..06cd3b9 --- /dev/null +++ b/ui/layout.py @@ -0,0 +1,125 @@ +""" +sn_plan41/ui/layout.py – Aufbau von Drucklayouts für Plan41. +""" +from __future__ import annotations + +import math +from typing import Any + +from sn_basis.functions.qt_wrapper import QFont +from sn_basis.functions.qgiscore_wrapper import ( + QgsLayoutItem, + QgsLayoutItemLabel, + QgsLayoutItemMap, + QgsLayoutPoint, + QgsLayoutSize, + QgsPrintLayout, + QgsProject, + QgsUnitTypes, +) +from sn_basis.functions.qgisui_wrapper import open_layout_designer + + +MM = QgsUnitTypes.LayoutMillimeters + + +class Layout: + """Erzeugt ein QGIS-Layout für den Druck.""" + + def __init__(self, project: Any | None = None) -> None: + self.project = project or QgsProject.instance() + + def create_single_page_layout( + self, + name: str, + page_width_mm: float, + page_height_mm: float, + map_width_mm: float, + map_height_mm: float, + extent: Any, + plotmassstab: float, + thema: str = "", + ) -> Any: + """Erzeugt ein einseitiges Layout und öffnet es im Designer.""" + print(f"[Layout] create_single_page_layout: name='{name}', " + f"page=({page_width_mm}x{page_height_mm}), map=({map_width_mm:.1f}x{map_height_mm:.1f}), " + f"massstab={plotmassstab}, thema='{thema}'") + layout_manager = self.project.layoutManager() + print(f"[Layout] layoutManager: {layout_manager!r}") + existing_layout = layout_manager.layoutByName(name) + if existing_layout is not None: + raise ValueError(f"Eine Vorlage mit der Bezeichnung '{name}' existiert bereits.") + + layout = QgsPrintLayout(self.project) + layout.initializeDefaults() + layout.setName(name) + print(f"[Layout] QgsPrintLayout erstellt: {layout!r}") + + page = layout.pageCollection().page(0) + page.setPageSize(QgsLayoutSize(page_width_mm, page_height_mm, MM)) + print(f"[Layout] Seitengröße gesetzt: {page_width_mm}x{page_height_mm} mm") + + hauptkarte = QgsLayoutItemMap(layout) + hauptkarte.setId("Hauptkarte") + set_rect = getattr(hauptkarte, "setRect", None) + if callable(set_rect): + set_rect(0.0, 0.0, map_width_mm, map_height_mm) + hauptkarte.attemptMove(QgsLayoutPoint(10.0, 10.0, MM)) + hauptkarte.attemptResize(QgsLayoutSize(map_width_mm, map_height_mm, MM)) + x_min = getattr(extent, "xMinimum", lambda: float("nan"))() + y_min = getattr(extent, "yMinimum", lambda: float("nan"))() + x_max = getattr(extent, "xMaximum", lambda: float("nan"))() + y_max = getattr(extent, "yMaximum", lambda: float("nan"))() + print(f"[Layout] Extent input: xmin={x_min}, ymin={y_min}, xmax={x_max}, ymax={y_max}") + hauptkarte.setExtent(extent) + if isinstance(plotmassstab, (int, float)) and math.isfinite(plotmassstab) and plotmassstab > 0: + hauptkarte.setScale(plotmassstab) + else: + print(f"[Layout] WARN: ungültiger plotmassstab={plotmassstab!r}, setScale übersprungen") + print(f"[Layout] Hauptkarte angelegt: pos=(10,10), size=({map_width_mm:.1f}x{map_height_mm:.1f})") + + if thema and thema != "aktuell": + follow_theme = getattr(hauptkarte, "setFollowVisibilityPreset", None) + set_theme_name = getattr(hauptkarte, "setFollowVisibilityPresetName", None) + if callable(follow_theme): + follow_theme(True) + if callable(set_theme_name): + set_theme_name(thema) + print(f"[Layout] Kartenthema gesetzt: '{thema}'") + + layout.addLayoutItem(hauptkarte) + print("[Layout] Hauptkarte zum Layout hinzugefügt") + + quellenangabe = QgsLayoutItemLabel(layout) + quellenangabe.setId("Quellenangabe") + quellenangabe.setText( + "Quelle Geobasisdaten: GeoSN, " + "dl-de/by-2-0

" + "Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des " + "Sächsischen Landesamtes für Umwelt, Landwirtschaft und Geologie

" + "Basemap:

" + "© GeoBasis-DE / BKG ([%year($now)%]) " + "CC BY 4.0 " + "mit teilweise angepasster Signatur
" + ) + set_mode = getattr(quellenangabe, "setMode", None) + mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None) + print(f"[Layout] QgsLayoutItemLabel.ModeHtml={mode_html!r}") + if callable(set_mode) and mode_html is not None: + set_mode(mode_html) + quellenangabe.setFont(QFont("Arial", 12)) + set_reference_point = getattr(quellenangabe, "setReferencePoint", None) + lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None) + print(f"[Layout] QgsLayoutItem.ReferencePoint.LowerLeft={lower_left!r}") + if callable(set_reference_point) and lower_left is not None: + set_reference_point(lower_left) + quellenangabe.attemptMove(QgsLayoutPoint(map_width_mm + 30.0, page_height_mm - 120.0, MM)) + quellenangabe.attemptResize(QgsLayoutSize(180.0, 100.0, MM)) + layout.addLayoutItem(quellenangabe) + print("[Layout] Quellenangabe zum Layout hinzugefügt") + + layout_manager.addLayout(layout) + print("[Layout] Layout zum LayoutManager hinzugefügt") + open_layout_designer(layout) + print("[Layout] Layout Designer geöffnet") + return layout diff --git a/ui/tab_b_logic.py b/ui/tab_b_logic.py index 2e12e2c..8f0024c 100644 --- a/ui/tab_b_logic.py +++ b/ui/tab_b_logic.py @@ -4,9 +4,10 @@ sn_plan41/ui/tab_b_logic.py – Fachlogik für Tab B (Druck) from __future__ import annotations import math from sn_basis.functions.variable_wrapper import set_variable, get_variable -from sn_basis.functions.qgiscore_wrapper import QgsProject, get_layer_extent +from sn_basis.functions.qgiscore_wrapper import get_layer_extent from sn_basis.modules.Pruefmanager import Pruefmanager from sn_basis.modules.layerpruefer import Layerpruefer +from sn_plan41.ui.layout import Layout KARTENNAME_VAR = "sn_kartenname" @@ -91,6 +92,7 @@ class TabBLogic: def druckvorlage_anlegen( self, + layer: object, kartenname_auswahl: str, massstab_auswahl: str, zielgroesse: str, @@ -100,6 +102,11 @@ class TabBLogic: Prüft Parameter, berechnet Plotgröße und stellt bei Bedarf Atlas-Rückfrage. + Parameters + ---------- + layer: + Aktuell gewählter Verfahrensgebiet-Layer aus Tab A. + Returns ------- dict @@ -108,14 +115,6 @@ class TabBLogic: ``atlas_seiten`` (int): Anzahl benötigter Seiten (1 = kein Atlas). """ # ─── 1. Verfahrensgebiet-Layer prüfen ───────────────────────────── - layer_id = get_variable("tab_a_layer_id", scope="project") or "" - layer = None - if layer_id: - try: - layer = QgsProject.instance().mapLayer(layer_id) - except Exception: - pass - lp = Layerpruefer(layer=layer) ergebnis = lp.pruefe() if not ergebnis.ok: @@ -125,6 +124,7 @@ class TabBLogic: ) return {"ok": False, "switch_to_tab_a": True, "atlas_seiten": 0} + layer_id = getattr(layer, "id", lambda: "")() or "" set_variable("sn_verfahrensgebietslayer", layer_id, scope="project") # ─── 2. Kartenname prüfen ───────────────────────────────────────── @@ -173,13 +173,55 @@ class TabBLogic: # ─── 6. Zielgröße bestimmen ─────────────────────────────────────── din_dims = DIN_GROESSEN.get(zielgroesse, DIN_GROESSEN[DIN_STANDARD]) - if formfaktor: # Endlosrolle: X-Richtung auf 2000 mm begrenzt - ziel_w, ziel_h = 2000.0, float(din_dims[1]) + if formfaktor: # Endlosrolle: X-Richtung entspricht der Plotgröße + ziel_w, ziel_h = plotgroesse_w, float(min(din_dims)) else: ziel_w, ziel_h = float(din_dims[0]), float(din_dims[1]) - # ─── 7. Passt auf ein Blatt? ────────────────────────────────────── + # ─── 7. Passt auf ein Blatt? -> Layout erzeugen ─────────────────── + print(f"[TabBLogic] plotgroesse=({plotgroesse_w:.1f}x{plotgroesse_h:.1f}), " + f"zielgroesse=({ziel_w:.1f}x{ziel_h:.1f}), passt={plotgroesse_w <= ziel_w and plotgroesse_h <= ziel_h}") if plotgroesse_w <= ziel_w and plotgroesse_h <= ziel_h: + kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get( + kartenname_auswahl, "Vorlage" + ) + thema = get_variable(VIEW_VAR, scope="project") or "" + print(f"[TabBLogic] frage_text aufrufen, default='{kartenname}', thema='{thema}'") + vorlage_name, bestaetigt = self.pruefmanager.frage_text( + "Neue Vorlage anlegen", + "Bezeichnung der Vorlage:", + default_text=kartenname, + ) + print(f"[TabBLogic] frage_text Ergebnis: name='{vorlage_name}', bestaetigt={bestaetigt}") + if not bestaetigt: + return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} + + vorlage_name = (vorlage_name or "").strip() or kartenname + + print(f"[TabBLogic] Rufe Layout().create_single_page_layout auf: name='{vorlage_name}', " + f"page=({ziel_w}x{ziel_h}), map=({kartenbild_w:.1f}x{kartenbild_h:.1f}), " + f"massstab={massstab_zahl}, thema='{thema}'") + try: + Layout().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, + extent=extent, + plotmassstab=massstab_zahl, + thema=thema, + ) + print("[TabBLogic] create_single_page_layout erfolgreich abgeschlossen") + except ValueError as exc: + print(f"[TabBLogic] ValueError: {exc}") + self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc)) + return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} + except Exception as exc: + print(f"[TabBLogic] Exception: {exc!r}") + self.pruefmanager.zeige_hinweis("Vorlage anlegen", f"Die Vorlage konnte nicht angelegt werden: {exc}") + return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} + return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": 1} # ─── 8. Atlas: Anzahl Seiten berechnen ──────────────────────────── diff --git a/ui/tab_b_ui.py b/ui/tab_b_ui.py index 99799b8..3f1f98b 100644 --- a/ui/tab_b_ui.py +++ b/ui/tab_b_ui.py @@ -243,31 +243,71 @@ class TabB(QWidget): def _on_vorlage_erstellen(self) -> None: """Startet die Pipeline Druckvorlage_anlegen.""" + print("[TabB] _on_vorlage_erstellen aufgerufen") if not self.logic or not self.pruefmanager: + print(f"[TabB] Abbruch: logic={self.logic}, pruefmanager={self.pruefmanager}") return + # Layer direkt aus Tab A lesen (unabhängig von Projektvariable) + layer = None + tab_a = self._get_tab_a_widget() + print(f"[TabB] tab_a Widget: {tab_a!r}") + if tab_a is not None: + layer_combo = getattr(tab_a, "layer_combo", None) + print(f"[TabB] layer_combo: {layer_combo!r}") + if layer_combo is not None: + layer = layer_combo.currentLayer() + print(f"[TabB] layer: {layer!r} (Name: {getattr(layer, 'name', lambda: '–')()})") + + print(f"[TabB] Rufe druckvorlage_anlegen auf mit layer={layer!r}, " + f"kartenname={self._kartenname_combo.currentText() if self._kartenname_combo else '?'}, " + f"massstab={self._massstab_combo.currentText() if self._massstab_combo else '?'}, " + f"zielgroesse={self._zielgroesse_combo.currentText() if self._zielgroesse_combo else '?'}, " + f"formfaktor={self._endlosrolle_cb.isChecked() if self._endlosrolle_cb else '?'}") + result = self.logic.druckvorlage_anlegen( + layer=layer, kartenname_auswahl=self._kartenname_combo.currentText() if self._kartenname_combo else "", massstab_auswahl=self._massstab_combo.currentText() if self._massstab_combo else "", zielgroesse=self._zielgroesse_combo.currentText() if self._zielgroesse_combo else DIN_STANDARD, formfaktor=self._endlosrolle_cb.isChecked() if self._endlosrolle_cb else False, ) + print(f"[TabB] druckvorlage_anlegen Ergebnis: {result}") if result.get("switch_to_tab_a"): self._aktiviere_tab_a() - def _aktiviere_tab_a(self) -> None: - """Wechselt zum Tab A im übergeordneten TabWidget.""" + def _get_tab_widget(self): + """Findet das übergeordnete QTabWidget anhand des ``tabBar``-Attributs.""" try: widget = self.parent() while widget is not None: - if hasattr(widget, "setCurrentIndex") and hasattr(widget, "count"): - widget.setCurrentIndex(0) - return + if hasattr(widget, "tabBar") and hasattr(widget, "setCurrentIndex"): + return widget parent_fn = getattr(widget, "parent", None) widget = parent_fn() if callable(parent_fn) else None except Exception: pass + return None + + def _get_tab_a_widget(self): + """Gibt die Tab-A-Widget-Instanz zurück (Index 0 im übergeordneten QTabWidget).""" + tab_widget = self._get_tab_widget() + if tab_widget is None: + return None + try: + return tab_widget.widget(0) + except Exception: + return None + + def _aktiviere_tab_a(self) -> None: + """Wechselt den aktiven Reiter auf Tab A im übergeordneten QTabWidget.""" + tab_widget = self._get_tab_widget() + if tab_widget is not None: + try: + tab_widget.setCurrentIndex(0) + except Exception: + pass def _connect_project_signals(self) -> None: """Verbindet QgsProject-Signale für Projektwechsel/-neuladen."""