diff --git a/modules/Dateipruefer.py b/modules/Dateipruefer.py
index 552c558..84cfb62 100644
--- a/modules/Dateipruefer.py
+++ b/modules/Dateipruefer.py
@@ -46,7 +46,7 @@ class Dateipruefer:
standarddatei: Optional[str] = None,
temporaer_erlaubt: bool = False,
*,
- verfahrens_db_modus: bool = True, # 🆕 Verfahrens-DB-spezifische Logik
+ verfahrens_db_modus: bool = False,
) -> None:
"""
Parameters
@@ -90,6 +90,8 @@ class Dateipruefer:
def _pfad(self, relativer_pfad: str) -> Path:
"""Erzeugt OS-unabhängigen Pfad relativ zum Basisverzeichnis."""
+ if relativer_pfad.startswith(("/", "\\")):
+ return Path(relativer_pfad)
return join_path(self.basis_pfad, relativer_pfad)
@@ -152,6 +154,22 @@ class Dateipruefer:
#Excel-dateien erkennen
dateityp = self.erkenne_dateityp(pfad)
+ if not self.verfahrens_db_modus:
+ if not file_exists(pfad):
+ return pruef_ergebnis(
+ ok=False,
+ meldung=f"Datei '{self.pfad}' wurde nicht gefunden.",
+ aktion="datei_nicht_gefunden",
+ kontext=pfad,
+ )
+
+ return pruef_ergebnis(
+ ok=True,
+ meldung="Datei ist gĂĽltig.",
+ aktion="ok",
+ kontext=pfad,
+ )
+
if dateityp == "excel":
if not file_exists(pfad):
return pruef_ergebnis(
@@ -176,7 +194,7 @@ class Dateipruefer:
)
# 🆕 2.c: Ungültiger GPKG-Pfad?
- if not self.verfahrens_db_modus or not self._ist_gueltiger_gpkg_pfad(pfad):
+ if not self._ist_gueltiger_gpkg_pfad(pfad):
return pruef_ergebnis(
ok=False,
meldung=f"Der Pfad '{self.pfad}' ist kein gĂĽltiger GPKG-Pfad.",
diff --git a/modules/Pruefmanager.py b/modules/Pruefmanager.py
index 1bb131f..1b1fdb1 100644
--- a/modules/Pruefmanager.py
+++ b/modules/Pruefmanager.py
@@ -6,8 +6,8 @@ from __future__ import annotations
from typing import Optional, Any
from sn_basis.functions import ask_yes_no, info, warning, error, ask_overwrite_append_cancel_custom
+from sn_basis.functions.ly_visibility_wrapper import set_layer_visible
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis, PruefAktion
-print("DEBUG: Pruefmanager DATEI GELADEN:", __file__)
class Pruefmanager:
def __init__(self, ui_modus: str = "qgis", parent: Optional[Any] = None) -> None:
@@ -146,10 +146,7 @@ class Pruefmanager:
# ------------------------------------------------------------------
def _handle_basic_decision(self, ergebnis: pruef_ergebnis) -> pruef_ergebnis:
"""Basis-Entscheidung fĂĽr einfache Ja/Nein-Fragen."""
- print(f"DEBUG _handle_basic_decision: aktion='{ergebnis.aktion}', ui_modus='{self.ui_modus}'")
-
if self.ui_modus != "qgis":
- print("DEBUG: Nicht QGIS → ergebnis unverändert")
return ergebnis
title_map = {
@@ -163,27 +160,32 @@ class Pruefmanager:
meldung = ergebnis.meldung or ""
try:
- print(f"DEBUG ask_yes_no: title='{title}', meldung='{meldung[:50]}...'")
yes = ask_yes_no(title, meldung, default=False, parent=self.parent)
- print(f"DEBUG ask_yes_no: yes={yes}")
- except Exception as e:
- print(f"DEBUG ask_yes_no Exception: {e}")
+ except Exception:
return ergebnis
if not yes:
- print("DEBUG: Nutzer sagte Nein → ok=False")
return ergebnis
# Nutzer sagte Ja
if ergebnis.aktion == "temporaer_erlaubt":
- print("DEBUG: temporaer_erlaubt bestätigt → ok=True")
return pruef_ergebnis(
ok=True,
- aktion="temporaer_erlaubt",
+ aktion="temporaer_erzeugen",
kontext=ergebnis.kontext
)
- print("DEBUG: Andere Aktion bestätigt → ok=True, aktion='ok'")
+ if ergebnis.aktion == "layer_unsichtbar":
+ try:
+ set_layer_visible(ergebnis.kontext, True)
+ except Exception:
+ return ergebnis
+ return pruef_ergebnis(
+ ok=True,
+ aktion="ok",
+ kontext=ergebnis.kontext,
+ )
+
return pruef_ergebnis(
ok=True,
aktion="ok",
@@ -194,48 +196,31 @@ class Pruefmanager:
# Hauptlogik: verarbeite() (KORRIGIERT!)
# ------------------------------------------------------------------
def verarbeite(self, ergebnis: pruef_ergebnis) -> pruef_ergebnis:
- print("🔥 verarbeite() START")
- print("DEBUG Pruefmanager:", ergebnis.ok, ergebnis.aktion)
- print("DEBUG ergebnis.aktion TYPE:", type(ergebnis.aktion), repr(ergebnis.aktion))
-
# 1. Erfolg → direkt weiter
- print("🔍 Schritt 1: Prüfe ergebnis.ok =", ergebnis.ok)
if ergebnis.ok:
- print("✅ Schritt 1: ok=True → return")
return ergebnis
# 2. VERFAHRENS-DB: Bestehende Datei
- print("🔍 Schritt 2: Prüfe datei_existiert =", ergebnis.aktion == "datei_existiert")
if ergebnis.aktion == "datei_existiert":
- print("âś… Schritt 2: _handle_datei_existiert")
return self._handle_datei_existiert(ergebnis)
# 3. Basis interaktive Aktionen
- print("🔍 Schritt 3: Definiere interactive_actions")
interactive_actions = {
"leereingabe_erlaubt",
"standarddatei_vorschlagen",
"temporaer_erlaubt",
"layer_unsichtbar",
}
- print("DEBUG interactive_actions:", repr(interactive_actions))
- print("DEBUG ergebnis.aktion in interactive_actions?", ergebnis.aktion in interactive_actions)
-
if ergebnis.aktion in interactive_actions:
- print("✅ Schritt 3: Interaktive Aktion → _handle_basic_decision")
- decision = self._handle_basic_decision(ergebnis)
- print(f"DEBUG: _handle_basic_decision Ergebnis: ok={decision.ok}, aktion='{decision.aktion}'")
- return decision
+ return self._handle_basic_decision(ergebnis)
# 4. Fehler behandeln
- print("❌ Schritt 4: FEHLER BEHANDELN")
self.report_error(
thema=ergebnis.aktion or "pruefung",
meldung=ergebnis.meldung or "",
aktion=ergebnis.aktion,
kontext=ergebnis.kontext,
)
- print("🔥 verarbeite() ENDE mit ok=False")
return ergebnis
def _ask_use_or_replace_pufferlayer(self) -> str:
diff --git a/modules/print_layout_baufreigabe.py b/modules/print_layout_baufreigabe.py
new file mode 100644
index 0000000..ecdce44
--- /dev/null
+++ b/modules/print_layout_baufreigabe.py
@@ -0,0 +1,365 @@
+"""
+sn_basis/modules/print_layout_baufreigabe.py - Layout-Erstellung fuer Baufreigabe-Vorlagen.
+
+Baufreigabe-Layouts verwenden immer feste DIN A3/A4 Seitengroessen im Querformat,
+ein 120 mm breites Kartenschild (statt 180 mm) und erbenerben die Standard-Logik
+fuer Hauptkarte, Quellenangabe und Legende vom PrintLayout.
+
+Atlas-Integration erfolgt nach Layouterstellung durch externe Konfiguration
+in der PrintLogic.
+"""
+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,
+ QgsLayoutItemLegend,
+ QgsLayoutItemMap,
+ QgsLayoutPoint,
+ QgsLayoutSize,
+ QgsPrintLayout,
+ QgsProject,
+ QgsUnitTypes,
+)
+from sn_basis.functions.qgisui_wrapper import open_layout_designer
+from sn_basis.modules.print_layout import PrintLayout
+
+
+MM = QgsUnitTypes.LayoutMillimeters
+
+
+class PrintLayoutBaufreigabe(PrintLayout):
+ """Erzeugt QGIS-Einzelblatt-Layouts mit fester DIN A3/A4 Querformat-Groesse und 120mm Kartenschild."""
+
+ _FRAME_STROKE_WIDTH_MM = 0.5
+ _KARTENSCHILD_WIDTH_MM = 120.0 # Unterschied zu StandardLayout: 120mm statt 180mm
+ _KARTENSCHILD_HEIGHT_MM = 92.0
+ _GRID_INTERVAL_MAP_UNITS = 500.0
+
+ # DIN-Formate: nur A3 und A4 im Querformat
+ _DIN_A3_LANDSCAPE = (420, 297) # Breite x Hoehe (Querformat)
+ _DIN_A4_LANDSCAPE = (297, 210) # Querformat
+
+ _TEXT_FORMAT_TEMPLATES = {
+ "Format_1": {"font_family": "Arial", "font_size": 11, "font_style": "normal"},
+ "Format_2": {"font_family": "Arial", "font_size": 13, "font_style": "bold"},
+ "Format_3": {"font_family": "Arial", "font_size": 16, "font_style": "bold"},
+ }
+
+ _OBJECT_TEMPLATES = {
+ "hauptkarte": {"id": "Hauptkarte", "object_name": "Hauptkarte"},
+ "quellenangabe": {
+ "id": "Quellenangabe",
+ "object_name": "Quellenangabe",
+ "size_mm": (120.0, 100.0), # Breite angepasst auf 120mm Kartenschild
+ "format": "Format_1",
+ },
+ "textfeld": {
+ "id": "Textfeld",
+ "object_name": "Textfeld",
+ "size_mm": (60.0, 8.0),
+ "format": "Format_1",
+ "html_mode": True,
+ "text": "",
+ },
+ "legende": {
+ "id": "Legende",
+ "object_name": "Legende",
+ "size_mm": (120.0, 60.0), # Breite angepasst auf 120mm Kartenschild
+ },
+ }
+
+ _QUELLENANGABE_TEXT = (
+ "Quelle Geobasisdaten: GeoSN, "
+ "dl-de/by-2-0
"
+ "Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des "
+ "Saechsischen Landesamtes fuer Umwelt, Landwirtschaft und Geologie
"
+ "Basemap:"
+ "© GeoBasis-DE / BKG ([%year($now)%]) "
+ "CC BY 4.0 "
+ "mit teilweise angepasster Signatur
"
+ )
+
+ def __init__(self, project: Any | None = None) -> None:
+ super().__init__(project)
+
+ def create_baufreigabe_layout(
+ self,
+ name: str,
+ din_format: str,
+ map_width_mm: float,
+ map_height_mm: float,
+ extent: Any,
+ plotmassstab: float,
+ thema: str = "",
+ fit_extent_to_map: bool = True,
+ ) -> Any:
+ """Erstellt ein Baufreigabe-Layout mit fester DIN A3/A4 Querformat-Groesse.
+
+ Args:
+ name: Name des Layouts
+ din_format: "DIN A3" oder "DIN A4"
+ map_width_mm: Breite der Hauptkarte
+ map_height_mm: Hoehe der Hauptkarte
+ extent: QgsRectangle der Kartenausdehnung
+ plotmassstab: Plotmassstab (z.B. 5000 fuer 1:5000)
+ thema: Name des Visibility-Presets (optional)
+ fit_extent_to_map: Ob das Extent an die Kartengroe angepasst wird
+
+ Returns:
+ QgsPrintLayout objektInstance (wird im Layout-Dialog geoeffnet)
+
+ Raises:
+ ValueError: Falls Name bereits existiert oder Format ungueltig ist
+ """
+ # Seitenformat auswaehlen (nur A3/A4 im Querformat)
+ if din_format == "DIN A3":
+ page_width_mm, page_height_mm = self._DIN_A3_LANDSCAPE
+ elif din_format == "DIN A4":
+ page_width_mm, page_height_mm = self._DIN_A4_LANDSCAPE
+ else:
+ raise ValueError(f"Baufreigabe: Format {din_format} ist nicht unterstuetzt. Nur DIN A3/A4 Querformat.")
+
+ layout_manager = self.project.layoutManager()
+ 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)
+ self._current_layout = layout
+
+ page = layout.pageCollection().page(0)
+ page.setPageSize(QgsLayoutSize(page_width_mm, page_height_mm, MM))
+
+ map_left_mm = 10.0
+ map_top_mm = 10.0
+ map_right_mm = map_left_mm + map_width_mm
+ map_bottom_mm = map_top_mm + map_height_mm
+ kartenfenster_untere_rechte_ecke_mm = (map_right_mm, map_bottom_mm)
+
+ hauptkarte = self._create_map_item(
+ layout,
+ "hauptkarte",
+ map_left_mm,
+ map_top_mm,
+ map_width_mm,
+ map_height_mm,
+ )
+
+ 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.setExtent(fitted_extent)
+ except Exception:
+ pass
+
+ if isinstance(plotmassstab, (int, float)) and math.isfinite(plotmassstab) and plotmassstab > 0:
+ try:
+ hauptkarte.setScale(plotmassstab)
+ except Exception:
+ pass
+
+ set_frame_enabled = getattr(hauptkarte, "setFrameEnabled", None)
+ if callable(set_frame_enabled):
+ try:
+ set_frame_enabled(True)
+ except Exception:
+ pass
+
+ set_frame_stroke_width = getattr(hauptkarte, "setFrameStrokeWidth", None)
+ if callable(set_frame_stroke_width):
+ try:
+ self._set_item_frame_stroke_width(hauptkarte, self._FRAME_STROKE_WIDTH_MM)
+ except Exception:
+ pass
+
+ if thema and thema != "aktuell":
+ follow_theme = getattr(hauptkarte, "setFollowVisibilityPreset", None)
+ set_theme_name = getattr(hauptkarte, "setFollowVisibilityPresetName", None)
+ if callable(follow_theme):
+ try:
+ follow_theme(True)
+ except Exception:
+ pass
+ if callable(set_theme_name):
+ try:
+ set_theme_name(thema)
+ except Exception:
+ pass
+
+ set_keep_layer_set = getattr(hauptkarte, "setKeepLayerSet", None)
+ if callable(set_keep_layer_set):
+ try:
+ set_keep_layer_set(True)
+ except Exception:
+ pass
+
+ set_refresh_strategy = getattr(hauptkarte, "setRefreshStrategy", None)
+ if callable(set_refresh_strategy):
+ refresh_cache = (
+ getattr(hauptkarte, "RefreshLaterOnly", None)
+ or getattr(hauptkarte, "RefreshWhenRequested", None)
+ or 0
+ )
+ try:
+ set_refresh_strategy(refresh_cache)
+ except Exception:
+ pass
+
+ self._configure_hauptkarte_grids(hauptkarte)
+
+ self._setup_layout_objects_baufreigabe(
+ kartenfenster_untere_rechte_ecke_mm,
+ hauptkarte,
+ map_top_mm,
+ map_height_mm,
+ )
+
+ layout_manager.addLayout(layout)
+ self._current_layout = None
+ open_layout_designer(layout)
+ return layout
+
+ def _setup_layout_objects_baufreigabe(
+ self,
+ kartenfenster_untere_rechte_ecke_mm: tuple,
+ hauptkarte: Any,
+ map_top_mm: float,
+ map_height_mm: float,
+ ) -> None:
+ """Erstellt Legende, Quellenangabe und Kartenschild fuer Baufreigabe-Layout.
+
+ Angepasst fuer 120mm Kartenschild statt 180mm, mit proportional skalierten Textfeldern.
+ """
+ if self._current_layout is None:
+ raise RuntimeError("Kein Layout aktiv.")
+
+ def _coords_rel_untere_rechte_ecke(rel_x_mm: float, rel_y_mm: float) -> tuple[float, float]:
+ return (
+ kartenfenster_untere_rechte_ecke_mm[0] + rel_x_mm,
+ kartenfenster_untere_rechte_ecke_mm[1] + rel_y_mm,
+ )
+
+ # Legende: 120mm Breite (angepasst), hoehe adaptive wie Standard
+ legende_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0
+ legende_height_mm = max(map_height_mm - 122.0, 20.0)
+ legende_y = map_top_mm
+ legende = self._create_legend_item(
+ self._current_layout,
+ "legende",
+ hauptkarte,
+ legende_x,
+ legende_y,
+ width_mm=self._KARTENSCHILD_WIDTH_MM,
+ height_mm=legende_height_mm,
+ object_name="Legende",
+ )
+
+ legende_hoehe_mm = self._configure_legend_size(
+ legende,
+ width_limit_mm=self._KARTENSCHILD_WIDTH_MM,
+ column_count=1,
+ equal_column_width=False,
+ )
+
+ # Quellenangabe: 120mm Breite
+ quellenangabe_x, quellenangabe_y = _coords_rel_untere_rechte_ecke(10.0, -102.0)
+ quellenangabe_hoehe_mm = 20.0
+ quellenangabe_obere_y = quellenangabe_y - quellenangabe_hoehe_mm
+ legende_untere_y = legende_y + legende_hoehe_mm
+
+ if legende_untere_y > quellenangabe_obere_y:
+ self._configure_legend_size(
+ legende,
+ width_limit_mm=self._KARTENSCHILD_WIDTH_MM,
+ column_count=2,
+ equal_column_width=True,
+ )
+
+ self.add_text_label(
+ self._QUELLENANGABE_TEXT,
+ quellenangabe_x,
+ quellenangabe_y,
+ width_mm=self._KARTENSCHILD_WIDTH_MM,
+ height_mm=20.0,
+ text_format="Format_1",
+ html_mode=True,
+ expression_enabled=True,
+ object_name="Quellenangabe",
+ )
+
+ # Kartenschild: 120mm Breite (angepasst)
+ kartenschild_x, kartenschild_y = _coords_rel_untere_rechte_ecke(10.0, 0.0)
+ self._create_rectangle_box_item(
+ self._current_layout,
+ "Kartenschild",
+ kartenschild_x,
+ kartenschild_y,
+ self._KARTENSCHILD_WIDTH_MM,
+ self._KARTENSCHILD_HEIGHT_MM,
+ stroke_width_mm=self._FRAME_STROKE_WIDTH_MM,
+ )
+
+ # Kartenschild-Linien: 6 Trennlinien (Positionen gleich)
+ kartenschild_line_x, _ = _coords_rel_untere_rechte_ecke(10.0, 0.0)
+ kartenschild_line_offsets_y = (-10.0, -20.0, -30.0, -46.0, -62.0, -82.0)
+ for index, offset_y in enumerate(kartenschild_line_offsets_y, start=1):
+ _, line_y = _coords_rel_untere_rechte_ecke(0.0, offset_y)
+ self._create_rectangle_box_item(
+ self._current_layout,
+ f"Kartenschild_Linie_{index}",
+ kartenschild_line_x,
+ line_y,
+ self._KARTENSCHILD_WIDTH_MM,
+ 0.0,
+ stroke_width_mm=0.1,
+ )
+
+ # Textfelder im Kartenschild: proportional skaliert auf 120mm
+ # Skalierungsfaktor: 120/180 = 0.667 fuer horizontale Abstaende/Breiten
+ scale_x = self._KARTENSCHILD_WIDTH_MM / 180.0
+ base_x = 16.0 * scale_x # ~10.7mm statt 16mm
+ label_width_scaled = 101.0 * scale_x # ~67.3mm statt 101mm
+
+ labels = [
+ ("[% @sn_amt %]", base_x, -84.0, label_width_scaled, 4.0, "Format_1", "Herausgeber"),
+ ("[% @sn_bezeichnung %]", base_x, -75.0, label_width_scaled, 4.0, "Format_1", "Bezeichnung_Flurbereinigung"),
+ ("[% @sn_name %]", base_x, -64.0, label_width_scaled, 5.0, "Format_2", "Name_Flurbereinigung"),
+ ("[% @sn_gemeinden %]", base_x, -56.0, label_width_scaled, 4.0, "Format_1", "Stadt_Gemeinde"),
+ ("[% @sn_landkreise_proj %]", base_x, -48.0, label_width_scaled, 4.0, "Format_1", "Landkreis"),
+ ("[% @sn_kartenname %]", base_x, -32.0, label_width_scaled - 30, 12.0, "Format_3", "Kartenname"),
+ ("Gefertigt: ", base_x, -23.0, 40.0 * scale_x, 4.0, "Format_1", "Gefertigt"),
+ ("Geprueft: ", base_x + 40.0 * scale_x + 5.0, -23.0, 40.0 * scale_x, 4.0, "Format_1", "Geprueft"),
+ (
+ "Massstab: 1 : [%round(map_get(item_variables('Hauptkarte'),'map_scale'),0)%]",
+ base_x,
+ -13.0,
+ 40.0 * scale_x,
+ 4.0,
+ "Format_1",
+ "Massstab",
+ ),
+ ("Ausgabedatum: [%format_date(now(), 'dd.MM.yyyy')%]", base_x + 40.0 * scale_x + 5.0, -13.0, 40.0 * scale_x, 4.0, "Format_1", "Ausgabedatum"),
+ ("Aktenzeichen: ", base_x, -3.0, label_width_scaled, 4.0, "Format_1", "Aktenzeichen"),
+ ]
+
+ for text, rx, ry, w, h, fmt, obj_name in labels:
+ x, y = _coords_rel_untere_rechte_ecke(rx, ry)
+ self.add_text_label(
+ text,
+ x,
+ y,
+ width_mm=w,
+ height_mm=h,
+ text_format=fmt,
+ expression_enabled=True,
+ object_name=obj_name,
+ )
diff --git a/modules/print_logic.py b/modules/print_logic.py
index c203158..4624f7b 100644
--- a/modules/print_logic.py
+++ b/modules/print_logic.py
@@ -4,7 +4,7 @@ sn_basis/modules/print_logic.py - Fachlogik fuer den Druck-Tab (nur Einzelblatt)
from __future__ import annotations
import importlib
-from typing import Any, cast
+from typing import Any, cast, TypedDict
from sn_basis.functions.qgiscore_wrapper import QgsProject, get_layer_extent
from sn_basis.functions.qgisui_wrapper import iface
@@ -12,6 +12,48 @@ from sn_basis.functions.variable_wrapper import get_variable, set_variable
from sn_basis.modules.layerpruefer import Layerpruefer
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.print_layout import PrintLayout
+from sn_basis.modules.print_layout_baufreigabe import PrintLayoutBaufreigabe
+
+
+class AtlasConfig(TypedDict, total=False):
+ """Strukturierte Konfiguration fuer Atlas-Optionen.
+
+ Alle Felder sind optional (total=False) und haben vordefinierte Standartwerte.
+ """
+ # Basis-Konfiguration
+ enabled: bool # Atlas aktivieren (Standard: True nach Coverage-Layer-Setzung)
+ page_name_expression: str # QGIS Expression fĂĽr Seitennamen (z.B. "@atlas_featurenumber")
+
+ # Filterung
+ filter_features: bool # Features filtern? (Standard: False)
+ filter_expression: str # QGIS Expression fĂĽr Feature-Filter
+
+ # Sortierung
+ sort_features: bool # Features sortieren? (Standard: False)
+ sort_expression: str # QGIS Expression/Feldname zum Sortieren
+ sort_ascending: bool # Aufsteigend sortieren? (Standard: True)
+
+ # Rendering
+ hide_coverage: bool # Coverage-Layer in Layout verstecken? (Standard: False)
+ limit_render_to_current_feature: bool # Rendering auf aktuelles Feature limitieren (QGIS 4.0+, Standard: False)
+
+ # Metadaten
+ description: str # Beschreibung der Atlas-Konfiguration
+
+
+# Vordefinierte Standardkonfigurationen fĂĽr Baufreigabe
+ATLAS_CONFIG_BAUFREIGABE_DEFAULT: AtlasConfig = {
+ "enabled": True,
+ "page_name_expression": "@atlas_featurenumber",
+ "filter_features": False,
+ "filter_expression": "",
+ "sort_features": False,
+ "sort_expression": "",
+ "sort_ascending": True,
+ "hide_coverage": False,
+ "limit_render_to_current_feature": False,
+ "description": "Baufreigabe Atlas mit Standardkonfiguration",
+}
KARTENNAME_VAR = "kartenname"
@@ -25,6 +67,7 @@ DRUCKRECHTECK_LAYER_NAME = "Druckrechteck"
KARTENNAME_38 = "§38"
KARTENNAME_41 = "§41"
+KARTENNAME_BAUFREIGABE = "Anlage zur Baufreigabe"
MASSSTAB_WIE_KARTENFENSTER = "Wie Kartenfenster"
THEMA_WIE_KARTENFENSTER = "wie kartenfenster"
@@ -35,6 +78,7 @@ DRUCKBEREICH_RECHTECK = "Rechteck"
KARTENNAME_BY_AUSWAHL = {
KARTENNAME_38: "Planungsübersicht §38 FlurbG",
KARTENNAME_41: "Karte zum Plan über die gemeinschaftlichen und öffentlichen Anlagen (§ 41 FlurbG)",
+ KARTENNAME_BAUFREIGABE: "Anlage zur Baufreigabe",
}
PLOTMASSSTAB_BY_AUSWAHL = {
@@ -253,6 +297,269 @@ class PrintLogic:
except Exception:
pass
+ def create_atlas_layer_from_selection(self, source_layer: object | None = None) -> tuple[object | None, str]:
+ """Erstellt einen neuen Layer 'Atlaslayer' aus der Auswahl des Quell-Layers.
+
+ Args:
+ source_layer: Der Layer mit der Auswahl (wenn None, wird aktiver Layer verwendet)
+
+ Returns:
+ (created_layer, layer_id) oder (None, "") bei Fehler
+ """
+ # Source-Layer bestimmen
+ if source_layer is None:
+ source_layer = self._resolve_active_layer()
+ if source_layer is None:
+ self.pruefmanager.zeige_hinweis(
+ "Atlas Layer erstellen",
+ "Kein Layer mit Auswahl gefunden.",
+ )
+ return None, ""
+
+ # Pruefen ob Auswahl vorhanden ist
+ selected_count = getattr(source_layer, "selectedFeatureCount", None)
+ if callable(selected_count):
+ try:
+ if int(cast(Any, selected_count())) == 0:
+ self.pruefmanager.zeige_hinweis(
+ "Atlas Layer erstellen",
+ "Auf dem Layer sind keine Features ausgewaehlt.",
+ )
+ return None, ""
+ except Exception:
+ pass
+
+ # Bestehenden Atlaslayer loeschen (optional Wiederverwedung)
+ existing_atlas = self._get_existing_layer_by_name("Atlaslayer")
+ if existing_atlas is not None:
+ try:
+ QgsProject.instance().removeMapLayer(existing_atlas)
+ except Exception:
+ pass
+
+ # Neuen Memory-Layer mit Geometrie-Type des Quell-Layers erstellen
+ qgs_vector_layer = self._import_qgis_symbol("qgis.core", "QgsVectorLayer")
+ if qgs_vector_layer is None:
+ self.pruefmanager.zeige_hinweis(
+ "Atlas Layer erstellen",
+ "QgsVectorLayer ist nicht verfuegbar.",
+ )
+ return None, ""
+
+ # CRS vom Quell-Layer ĂĽbernehmen
+ crs_authid = "EPSG:25832"
+ try:
+ source_crs = getattr(source_layer, "crs", lambda: None)()
+ if source_crs is not None:
+ crs_authid_method = getattr(source_crs, "authid", lambda: None)
+ if callable(crs_authid_method):
+ crs_authid = crs_authid_method() or crs_authid
+ except Exception:
+ pass
+
+ # Geometrie-Type vom Quell-Layer bestimmen (vereinfacht: Polygon fĂĽr Atlas)
+ # TODO: Intelligentere Bestimmung des Geometrie-Types
+ atlas_layer = qgs_vector_layer(f"Polygon?crs={crs_authid}", "Atlaslayer", "memory")
+ if not atlas_layer or not getattr(atlas_layer, "isValid", lambda: False)():
+ self.pruefmanager.zeige_hinweis(
+ "Atlas Layer erstellen",
+ "Der Atlaslayer konnte nicht erstellt werden.",
+ )
+ return None, ""
+
+ # Ausgewaehlte Features kopieren
+ try:
+ get_selected = getattr(source_layer, "selectedFeatures", None)
+ if not callable(get_selected):
+ raise Exception("selectedFeatures ist nicht aufrufbar")
+
+ selected_features = list(cast(Any, get_selected()))
+ if not selected_features:
+ raise Exception("Keine Features ausgewaehlt")
+
+ qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
+ if qgs_feature is None:
+ raise Exception("QgsFeature Symbol nicht verfuegbar")
+
+ data_provider = getattr(atlas_layer, "dataProvider", lambda: None)()
+ if data_provider is None:
+ raise Exception("dataProvider nicht verfuegbar")
+
+ new_features = []
+ for source_feature in selected_features:
+ geometry = getattr(source_feature, "geometry", lambda: None)()
+ if geometry is None:
+ continue
+ new_feature = qgs_feature()
+ set_geometry = getattr(new_feature, "setGeometry", None)
+ if callable(set_geometry):
+ try:
+ set_geometry(geometry)
+ new_features.append(new_feature)
+ except Exception:
+ pass
+
+ if new_features:
+ add_features = getattr(data_provider, "addFeatures", None)
+ if callable(add_features):
+ add_features(new_features)
+
+ update_fields = getattr(atlas_layer, "updateFields", None)
+ if callable(update_fields):
+ update_fields()
+
+ update_extents = getattr(atlas_layer, "updateExtents", None)
+ if callable(update_extents):
+ update_extents()
+
+ except Exception as exc:
+ self.pruefmanager.zeige_hinweis(
+ "Atlas Layer erstellen",
+ f"Fehler beim Kopieren der Features: {exc}",
+ )
+ return None, ""
+
+ # Layer ins Projekt aufnehmen
+ try:
+ QgsProject.instance().addMapLayer(atlas_layer)
+ except Exception:
+ self.pruefmanager.zeige_hinweis(
+ "Atlas Layer erstellen",
+ "Der Layer konnte nicht ins Projekt aufgenommen werden.",
+ )
+ return None, ""
+
+ # Layer-ID speichern
+ layer_id = getattr(atlas_layer, "id", lambda: "")() or ""
+ set_variable("sn_abdeckungslayer", layer_id, scope="project")
+
+ self.pruefmanager.zeige_hinweis(
+ "Atlas Layer erstellen",
+ f"Atlas Layer 'Atlaslayer' erfolgreich erstellt.",
+ )
+
+ return atlas_layer, layer_id
+
+ def configure_atlas_for_baufreigabe_layout(
+ self,
+ layout_name: str = "",
+ atlas_config: AtlasConfig | None = None,
+ ) -> bool:
+ """Konfiguriert den Atlas fuer ein Baufreigabe-Layout mit erweiterten Optionen.
+
+ Wird nach erfolgreicher Baufreigabe-Layout-Erstellung aufgerufen.
+ Setzt den Coverage-Layer (aus sn_abdeckungslayer Variable) und konfiguriert den Atlas
+ mit allen Optionen aus der AtlasConfig.
+ """
+ try:
+ if atlas_config is None:
+ atlas_config = ATLAS_CONFIG_BAUFREIGABE_DEFAULT
+
+ project = QgsProject.instance()
+ layout_manager = getattr(project, "layoutManager", lambda: None)()
+ if layout_manager is None:
+ self.pruefmanager.zeige_hinweis("Atlas konfigurieren", "Layout Manager nicht verfuegbar.")
+ return False
+
+ if not layout_name:
+ layouts_fn = getattr(layout_manager, "layouts", None)
+ if not callable(layouts_fn):
+ return False
+ try:
+ layout_list = layouts_fn()
+ if not layout_list:
+ return False
+ layout = layout_list[-1]
+ except Exception:
+ return False
+ else:
+ layout_by_name = getattr(layout_manager, "layoutByName", None)
+ if not callable(layout_by_name):
+ return False
+ layout = layout_by_name(layout_name)
+ if layout is None:
+ return False
+
+ atlas = getattr(layout, "atlas", lambda: None)()
+ if atlas is None:
+ return False
+
+ coverage_layer_id = get_variable("sn_abdeckungslayer", scope="project") or ""
+ if not coverage_layer_id:
+ return True
+
+ coverage_layer = project.mapLayer(coverage_layer_id)
+ if coverage_layer is None:
+ self.pruefmanager.zeige_hinweis(
+ "Atlas konfigurieren",
+ f"Atlas-Layer mit ID '{coverage_layer_id}' nicht gefunden.",
+ )
+ return False
+
+ set_coverage = getattr(atlas, "setCoverageLayer", None)
+ if callable(set_coverage):
+ set_coverage(coverage_layer)
+
+ page_name_expr = atlas_config.get("page_name_expression", "")
+ if page_name_expr:
+ set_page_name = getattr(atlas, "setPageNameExpression", None)
+ if callable(set_page_name):
+ set_page_name(page_name_expr)
+
+ if atlas_config.get("filter_features", False):
+ set_filter_features = getattr(atlas, "setFilterFeatures", None)
+ if callable(set_filter_features):
+ set_filter_features(True)
+ filter_expr = atlas_config.get("filter_expression", "")
+ if filter_expr:
+ set_filter_expr = getattr(atlas, "setFilterExpression", None)
+ if callable(set_filter_expr):
+ set_filter_expr(filter_expr)
+
+ if atlas_config.get("sort_features", False):
+ set_sort_features = getattr(atlas, "setSortFeatures", None)
+ if callable(set_sort_features):
+ set_sort_features(True)
+ sort_expr = atlas_config.get("sort_expression", "")
+ if sort_expr:
+ set_sort_expr = getattr(atlas, "setSortExpression", None)
+ if callable(set_sort_expr):
+ set_sort_expr(sort_expr)
+ set_sort_asc = getattr(atlas, "setSortAscending", None)
+ if callable(set_sort_asc):
+ set_sort_asc(atlas_config.get("sort_ascending", True))
+
+ set_hide_coverage = getattr(atlas, "setHideCoverage", None)
+ if callable(set_hide_coverage):
+ set_hide_coverage(atlas_config.get("hide_coverage", False))
+
+ set_limit_render = getattr(atlas, "setLimitCoverageLayerRenderToCurrentFeature", None)
+ if callable(set_limit_render):
+ try:
+ set_limit_render(atlas_config.get("limit_render_to_current_feature", False))
+ except Exception:
+ pass
+
+ num_features = 0
+ update_features = getattr(atlas, "updateFeatures", None)
+ if callable(update_features):
+ try:
+ num_features = update_features() or 0
+ except Exception:
+ num_features = 0
+
+ set_enabled = getattr(atlas, "setEnabled", None)
+ if callable(set_enabled):
+ set_enabled(atlas_config.get("enabled", True))
+ return True
+
+ except Exception as exc:
+ self.pruefmanager.zeige_hinweis(
+ "Atlas konfigurieren",
+ f"Fehler beim Konfigurieren des Atlas: {exc}",
+ )
+ return False
+
def _store_druckrechteck(self, layer: object, rectangle: Any) -> bool:
qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
qgs_geometry = self._import_qgis_symbol("qgis.core", "QgsGeometry")
@@ -579,6 +886,43 @@ class PrintLogic:
auswahl = DRUCKBEREICH_VERFAHRENSGEBIET
set_variable(DRUCKBEREICH_VAR, auswahl, scope="project")
+ def _get_baufreigabe_page_dimensions(
+ self,
+ kartenbild_w: float,
+ kartenbild_h: float,
+ zielgroesse: str,
+ ) -> tuple[str, float, float, float, float]:
+ """Berechnet Seitenformat und verfuegbare Kartenfenster fuer Baufreigabe.
+
+ Args:
+ kartenbild_w: Berechnete Kartenbild-Breite aus Extent/Massstab
+ kartenbild_h: Berechnete Kartenbild-Hoehe aus Extent/Massstab
+ zielgroesse: "DIN A3" oder "DIN A4" (wird zu Querformat konvertiert)
+
+ Returns:
+ (din_format, page_width_mm, page_height_mm, adjusted_map_w, adjusted_map_h)
+ """
+ # 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
+
+ # Verfuegbare Flaeche fuer Kartenbild: Seite - Kartenschild und Maerchen
+ kartenschild_w = 120.0
+ kartenschild_h = 92.0
+ margin = 10.0
+
+ avail_w = page_w - 2 * margin - kartenschild_w - margin
+ avail_h = page_h - 2 * margin - kartenschild_h - margin
+
+ adjusted_map_w = min(kartenbild_w, avail_w)
+ adjusted_map_h = min(kartenbild_h, avail_h)
+
+ return din_format, page_w, page_h, adjusted_map_w, adjusted_map_h
+
def druckvorlage_anlegen(
self,
layer: object | None,
@@ -590,7 +934,7 @@ class PrintLogic:
) -> dict:
resolved_layer = layer or self._resolve_layer_from_project()
- if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41):
+ if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41, KARTENNAME_BAUFREIGABE):
self.pruefmanager.zeige_hinweis("Kartennamen waehlen", "Kartennamen waehlen")
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
@@ -671,18 +1015,36 @@ class PrintLogic:
vorlage_name = (vorlage_name or "").strip() or kartenname
+ # Verzweigung: Baufreigabe vs. Standard-Layout
try:
- 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,
- extent=extent,
- plotmassstab=massstab_zahl,
- thema=thema,
- fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET),
- )
+ if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
+ # Baufreigabe: feste DIN A3/A4 Querformat-Logik mit 120mm Kartenschild
+ din_format, page_w, page_h, map_w, map_h = self._get_baufreigabe_page_dimensions(
+ kartenbild_w, kartenbild_h, zielgroesse
+ )
+ 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,
+ extent=extent,
+ plotmassstab=massstab_zahl,
+ thema=thema,
+ fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET),
+ )
+ 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,
+ extent=extent,
+ plotmassstab=massstab_zahl,
+ thema=thema,
+ fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET),
+ )
except ValueError as exc:
self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc))
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
@@ -693,4 +1055,8 @@ class PrintLogic:
)
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
+ # Nach erfolgreicher Layout-Erstellung: Atlas konfigurieren (falls Baufreigabe)
+ if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
+ self.configure_atlas_for_baufreigabe_layout()
+
return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": 1}
diff --git a/modules/stilpruefer.py b/modules/stilpruefer.py
index f8b743d..cdb601f 100644
--- a/modules/stilpruefer.py
+++ b/modules/stilpruefer.py
@@ -5,6 +5,7 @@ Die Anwendung erfolgt später über eine Aktion.
"""
import os
+from pathlib import Path
from sn_basis.functions.os_wrapper import is_absolute_path
from sn_basis.functions.sys_wrapper import get_plugin_root, file_exists, join_path
@@ -43,7 +44,7 @@ class Stilpruefer:
pfad = str(stil_pfad)
- if not is_absolute_path(pfad):
+ if not is_absolute_path(pfad) and not pfad.startswith(("/", "\\")):
plugin_root = get_plugin_root()
pfad = str(join_path(plugin_root, "sn_plan41", "assets", pfad))
@@ -55,7 +56,7 @@ class Stilpruefer:
ok=False,
meldung=f"Die Stil-Datei '{stil_pfad}' wurde nicht gefunden.",
aktion="datei_nicht_gefunden",
- kontext=pfad,
+ kontext=Path(pfad),
)
# -----------------------------------------------------
@@ -66,7 +67,7 @@ class Stilpruefer:
ok=False,
meldung="Die Stil-Datei muss die Endung '.qml' haben.",
aktion="falsche_endung",
- kontext=pfad,
+ kontext=Path(pfad),
)
# -----------------------------------------------------
@@ -76,5 +77,5 @@ class Stilpruefer:
ok=True,
meldung="Stil-Datei ist gĂĽltig.",
aktion="stil_anwendbar",
- kontext=pfad,
+ kontext=Path(pfad),
)
diff --git a/ui/tabs/print_tab.py b/ui/tabs/print_tab.py
index 8884cd6..8d19eb4 100644
--- a/ui/tabs/print_tab.py
+++ b/ui/tabs/print_tab.py
@@ -13,9 +13,13 @@ from sn_basis.functions.qt_wrapper import (
QCheckBox,
QHBoxLayout,
QPushButton,
+ QToolButton,
+ ToolButtonTextBesideIcon,
+ ArrowDown,
+ ArrowRight,
)
-from sn_basis.functions.qgiscore_wrapper import QgsProject
-from sn_basis.functions.qgisui_wrapper import iface
+from sn_basis.functions.qgiscore_wrapper import QgsProject, QgsMapLayerProxyModel
+from sn_basis.functions.qgisui_wrapper import iface, QgsMapLayerComboBox
from sn_basis.functions.variable_wrapper import get_variable, set_variable
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.DataGrabber import DataGrabber
@@ -39,6 +43,7 @@ KARTENNAME_VAR = "tab_b_kartenname"
KARTENNAME_PLACEHOLDER = "Kartenname waehlen"
KARTENNAME_38 = "§38"
KARTENNAME_41 = "§41"
+KARTENNAME_BAUFREIGABE = "Anlage zur Baufreigabe"
MASSSTAB_VAR = "tab_b_massstab"
THEMA_VAR = "tab_b_thema"
ZIELGROESSE_UI_VAR = "tab_b_zielgroesse"
@@ -70,6 +75,10 @@ class PrintTab(QWidget):
self._endlosrolle_cb: Optional[Any] = None
self._druckpuffer_button: Optional[Any] = None
self._btn_vorlage_erstellen: Optional[Any] = None
+ self._atlas_layer_combo: Optional[Any] = None
+ self._btn_auswahl_as_atlas: Optional[Any] = None
+ self._atlas_button: Optional[Any] = None
+ self._atlas_content: Optional[Any] = None
self._build_ui()
self._restore_state()
@@ -110,6 +119,7 @@ class PrintTab(QWidget):
kartenname_combo.addItem(KARTENNAME_PLACEHOLDER)
kartenname_combo.addItem(KARTENNAME_38)
kartenname_combo.addItem(KARTENNAME_41)
+ kartenname_combo.addItem(KARTENNAME_BAUFREIGABE)
kartenname_combo.currentTextChanged.connect(self._on_kartenname_changed)
self._kartenname_combo = kartenname_combo
main_layout.addWidget(kartenname_combo)
@@ -191,6 +201,49 @@ class PrintTab(QWidget):
main_layout.addLayout(druckbereich_row)
+ # Atlas-Bereich (einklappbar, analog Tab A)
+ atlas_button = QToolButton()
+ atlas_button.setText("Atlas")
+ atlas_button.setCheckable(True)
+ atlas_button.setChecked(False)
+ atlas_button.setToolButtonStyle(ToolButtonTextBesideIcon)
+ atlas_button.setArrowType(ArrowRight)
+ atlas_button.setStyleSheet("font-weight: bold; margin-top: 6px;")
+ atlas_button.toggled.connect(self._toggle_atlas)
+ self._atlas_button = atlas_button
+ main_layout.addWidget(atlas_button)
+
+ atlas_content = QWidget()
+ atlas_layout = QVBoxLayout()
+ atlas_layout.setSpacing(4)
+ atlas_layout.setContentsMargins(10, 4, 4, 4)
+
+ atlas_layer_label = QLabel("Abdeckungslayer", self)
+ atlas_layout.addWidget(atlas_layer_label)
+
+ atlas_layer_combo = QgsMapLayerComboBox(self)
+ set_filters = getattr(atlas_layer_combo, "setFilters", None)
+ if callable(set_filters):
+ set_filters(QgsMapLayerProxyModel.VectorLayer)
+ layer_changed = getattr(atlas_layer_combo, "layerChanged", None)
+ if layer_changed is not None:
+ try:
+ layer_changed.connect(self._on_atlas_layer_changed)
+ except Exception:
+ pass
+ self._atlas_layer_combo = atlas_layer_combo
+ atlas_layout.addWidget(atlas_layer_combo)
+
+ btn_auswahl_as_atlas = QPushButton("Auswahl als Atlas anlegen", self)
+ btn_auswahl_as_atlas.clicked.connect(self._on_auswahl_as_atlas_clicked)
+ self._btn_auswahl_as_atlas = btn_auswahl_as_atlas
+ atlas_layout.addWidget(btn_auswahl_as_atlas)
+
+ atlas_content.setLayout(atlas_layout)
+ atlas_content.setVisible(False)
+ self._atlas_content = atlas_content
+ main_layout.addWidget(atlas_content)
+
btn_vorlage_erstellen = QPushButton("Vorlage erstellen", self)
btn_vorlage_erstellen.clicked.connect(self._on_vorlage_erstellen)
self._btn_vorlage_erstellen = btn_vorlage_erstellen
@@ -208,7 +261,7 @@ class PrintTab(QWidget):
return
saved_kartenname = get_variable(KARTENNAME_VAR, scope="project")
- if saved_kartenname in (KARTENNAME_38, KARTENNAME_41):
+ if saved_kartenname in (KARTENNAME_38, KARTENNAME_41, KARTENNAME_BAUFREIGABE):
self._kartenname_combo.setCurrentText(saved_kartenname)
else:
self._kartenname_combo.setCurrentText(KARTENNAME_PLACEHOLDER)
@@ -252,12 +305,18 @@ class PrintTab(QWidget):
druckpuffer_cm = DRUCKPUFFER_STANDARD_CM
self._druckpuffer_button.setText(self._format_druckpuffer_label(max(0, druckpuffer_cm)))
+ self._restore_atlas_layer_selection()
+
def _on_kartenname_changed(self, value: str) -> None:
- if value in (KARTENNAME_38, KARTENNAME_41):
+ if value in (KARTENNAME_38, KARTENNAME_41, KARTENNAME_BAUFREIGABE):
set_variable(KARTENNAME_VAR, value, scope="project")
else:
set_variable(KARTENNAME_VAR, "", scope="project")
+ # Atlas-Bereich automatisch ein-/ausklappen je nach Kartenname.
+ if self._atlas_button:
+ self._atlas_button.setChecked(value == KARTENNAME_BAUFREIGABE)
+
if self.logic:
self.logic.set_kartenname_for_auswahl(value)
@@ -358,6 +417,36 @@ class PrintTab(QWidget):
self._connect_theme_collection_signals()
self._restore_state()
+ def _toggle_atlas(self, checked: bool) -> None:
+ if self._atlas_button:
+ self._atlas_button.setArrowType(ArrowDown if checked else ArrowRight)
+ if self._atlas_content:
+ self._atlas_content.setVisible(checked)
+
+ def _restore_atlas_layer_selection(self) -> None:
+ if not self._atlas_layer_combo:
+ return
+
+ layer_id = (get_variable("sn_abdeckungslayer", scope="project") or "").strip()
+ if not layer_id:
+ return
+
+ try:
+ layer = QgsProject.instance().mapLayer(layer_id)
+ if layer is None:
+ return
+
+ set_current_layer = getattr(self._atlas_layer_combo, "setLayer", None)
+ if callable(set_current_layer):
+ set_current_layer(layer)
+ return
+
+ set_current_layer = getattr(self._atlas_layer_combo, "setCurrentLayer", None)
+ if callable(set_current_layer):
+ set_current_layer(layer)
+ except Exception:
+ pass
+
def _disconnect_theme_collection_signals(self) -> None:
collection = self._connected_theme_collection
if collection is not None:
@@ -450,3 +539,101 @@ class PrintTab(QWidget):
return float(scale) if scale else None
except Exception:
return None
+
+ def _on_atlas_layer_changed(self, layer_or_name: Any) -> None:
+ """Speichert den ausgewaehlten Atlas-Layer als Layer-ID in Projektvariablen."""
+ if layer_or_name is None or layer_or_name == "":
+ set_variable("sn_abdeckungslayer", "", scope="project")
+ return
+
+ # Standardfall QgsMapLayerComboBox: echtes Layer-Objekt
+ layer_id_getter = getattr(layer_or_name, "id", None)
+ if callable(layer_id_getter):
+ layer_id = layer_id_getter() or ""
+ set_variable("sn_abdeckungslayer", layer_id, scope="project")
+ return
+
+ # Fallback: ggf. Name aus Mock/anderen Widgets
+ value = str(layer_or_name).strip()
+ if not value:
+ set_variable("sn_abdeckungslayer", "", scope="project")
+ return
+
+ try:
+ project = QgsProject.instance()
+ map_layers = project.mapLayers()
+ for layer_id, layer in map_layers.items():
+ if getattr(layer, "name", lambda: "")() == value:
+ set_variable("sn_abdeckungslayer", layer_id, scope="project")
+ return
+ except Exception:
+ pass
+
+ def _on_auswahl_as_atlas_clicked(self) -> None:
+ """Handler fuer Button 'Auswahl als Atlas anlegen'.
+
+ Erstellt einen neuen Layer 'Atlaslayer' aus der aktuellen Feature-Auswahl
+ und traegt ihn ins Atlas-Dropdown ein.
+ """
+ if not self.logic or not self.pruefmanager:
+ return
+
+ atlas_layer, layer_id = self.logic.create_atlas_layer_from_selection()
+ if atlas_layer is None or not layer_id:
+ return
+
+ if self._atlas_button and self._atlas_content:
+ self._atlas_button.setChecked(True)
+ self._toggle_atlas(True)
+
+ # Neu erstellten Layer im Layer-Combo setzen
+ if self._atlas_layer_combo:
+ set_current_layer = getattr(self._atlas_layer_combo, "setLayer", None)
+ if callable(set_current_layer):
+ try:
+ set_current_layer(atlas_layer)
+ return
+ except Exception:
+ pass
+
+ set_current_layer = getattr(self._atlas_layer_combo, "setCurrentLayer", None)
+ if callable(set_current_layer):
+ try:
+ set_current_layer(atlas_layer)
+ return
+ except Exception:
+ pass
+
+ # Fallback nur fuer Mock/klassische ComboBox
+ self._populate_atlas_layer_combo()
+
+ def _populate_atlas_layer_combo(self) -> None:
+ """Fallback-Befuellung, falls kein echtes QgsMapLayerComboBox Verhalten verfuegbar ist."""
+ if not self._atlas_layer_combo:
+ return
+
+ clear_fn = getattr(self._atlas_layer_combo, "clear", None)
+ add_item_fn = getattr(self._atlas_layer_combo, "addItem", None)
+ if not callable(clear_fn) or not callable(add_item_fn):
+ return
+
+ self._atlas_layer_combo.blockSignals(True)
+ clear_fn()
+ add_item_fn("")
+
+ try:
+ project = QgsProject.instance()
+ map_layers = getattr(project, "mapLayers", None)
+ if not callable(map_layers):
+ self._atlas_layer_combo.blockSignals(False)
+ return
+
+ layers = map_layers().values() if hasattr(map_layers(), "values") else map_layers()
+ for layer in layers:
+ layer_name = getattr(layer, "name", lambda: None)()
+ if layer_name and layer_name not in ("", "Druckrechteck"):
+ add_item_fn(layer_name)
+ except Exception:
+ pass
+
+ self._atlas_layer_combo.blockSignals(False)