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."""