Files
Plugin_SN_Basis/modules/print_logic.py
T
DanielandClaude Opus 5.5 b549cb8a5a Basemap-Legende: Auswertung und Legendenelement im Drucklayout (#88)
Beim Erstellen einer §38-/§41-Vorlage wertet BasemapLegende die Kacheln des sichtbaren
basemap.de-Layers im Verfahrensgebiet (wahlweise Druckbereich) kachelweise über
selectByGeometry aus, prüft die Renderer-Styles mit derselben Zoomlogik wie QGIS und
bildet daraus Legendeneinträge. Ein Mehrfachauswahl-Dialog (neu: ask_mehrfachauswahl)
lässt Kategorien abwählen; die Auswahl wird im Projekt gemerkt. Die gewählten Einträge
stehen mit der Signatur des Projektlayers (zoomabhängige Werte festgeschrieben) in
einem eigenen Legendenelement über der Quellenangabe. Ein eigenes Element ist nötig,
weil die nach Karteninhalt filternde Hauptlegende die Hilfslayer ausblenden würde.

Wrapper um die nötigen QGIS-/Qt-Symbole mit Mock ergänzt (eigener Block in
qgiscore_wrapper; QgsRectangle hatte bisher keinen Mock).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:57:09 +02:00

1361 lines
53 KiB
Python

"""
sn_basis/modules/print_logic.py - Fachlogik für den Druck-Tab (nur Einzelblatt).
"""
from __future__ import annotations
import importlib
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
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 für 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": True,
"description": "Baufreigabe Atlas mit Standardkonfiguration",
}
KARTENNAME_VAR = "kartenname"
PLOTMASSSTAB_VAR = "plotmassstab"
VIEW_VAR = "view"
ZIELGROESSE_VAR = "zielgroesse"
FORMFAKTOR_VAR = "formfaktor"
ATLAS_ERZEUGEN_UI_VAR = "tab_b_atlas_erzeugen"
DRUCKBEREICH_VAR = "druckbereich"
DRUCKPUFFER_VAR = "druckpuffer"
DRUCKRECHTECK_LAYER_NAME = "Druckrechteck"
KARTENNAME_38 = "§38"
KARTENNAME_41 = "§41"
KARTENNAME_BAUFREIGABE = "Anlage zur Baufreigabe"
# Layouts, die beim Anlegen eine Basemap-Legende erhalten (Basis #88). Neue Layouts hier aufnehmen.
LAYOUTS_MIT_BASEMAP_LEGENDE = frozenset({KARTENNAME_38, KARTENNAME_41})
MASSSTAB_WIE_KARTENFENSTER = "Wie Kartenfenster"
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",
KARTENNAME_41: "Karte zum Plan über die gemeinschaftlichen und öffentlichen Anlagen (§ 41 FlurbG)",
KARTENNAME_BAUFREIGABE: "Anlage zur Baufreigabe",
}
PLOTMASSSTAB_BY_AUSWAHL = {
"1:5.000": "5000",
"1:10.000": "10000",
"1:15.000": "15000",
"1:20.000": "20000",
"1:25.000": "25000",
"1:50.000": "50000",
"1:100.000": "100000",
}
DIN_GROESSEN: dict[str, tuple[int, int]] = {
"DIN A0": (841, 1189),
"DIN A1": (594, 841),
"DIN A2": (420, 594),
"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:
"""Kapselt die Fachlogik für den Druck-Tab (ohne Atlas)."""
def __init__(self, pruefmanager: Pruefmanager) -> None:
self.pruefmanager = pruefmanager
self._rectangle_tool: Any | None = None
self._previous_map_tool: Any | None = None
def _failed_result(self) -> dict:
"""Test ausgesetzt, zunächst Refactor"""
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
def _show_target_size_too_small(self) -> dict:
"""Test ausgesetzt, zunächst Refactor"""
self.pruefmanager.zeige_hinweis(
"Zielgröße zu klein",
"Die gewählte Zielgröße reicht für den Einzelblatt-Druck nicht aus.",
)
return self._failed_result()
def _is_enabled_flag(self, raw_value: Any) -> bool:
"""Test ausgesetzt, zunächst Refactor"""
if raw_value in (None, ""):
return True
return str(raw_value).strip().lower() in {"1", "true", "ja", "yes", "on"}
def _resolve_layer_from_project(self) -> object | None:
"""Test ausgesetzt, zunächst Refactor"""
project = QgsProject.instance()
# Primär: neue zentrale Variable aus sn_verfahrensgebiet_manager
layer_id = (get_variable("verfahrensgebiet_layer", scope="project") or "").strip()
# Rückwärtskompatibilität zu älteren Projekten
if not layer_id:
layer_id = (get_variable("verfahrensgebietslayer", scope="project") or "").strip()
if not layer_id:
layer_id = (get_variable("tab_a_layer_id", scope="project") or "").strip()
if not layer_id:
return None
map_layer = getattr(project, "mapLayer", None)
if not callable(map_layer):
return None
try:
return map_layer(layer_id)
except Exception:
return None
def _resolve_active_layer(self) -> object | None:
"""Test ausgesetzt, zunächst Refactor"""
active_layer_fn = getattr(iface, "activeLayer", None)
if not callable(active_layer_fn):
return None
try:
return active_layer_fn()
except Exception:
return None
def _get_druckpuffer_cm(self) -> int:
"""Test ausgesetzt, zunächst Refactor"""
raw_value = get_variable(DRUCKPUFFER_VAR, scope="project")
try:
value = int(str(raw_value).strip())
except (TypeError, ValueError):
value = DRUCKPUFFER_STANDARD_CM
return max(0, value)
def _get_druckpuffer_m(self, plotmassstab: float) -> float:
"""Test ausgesetzt, zunächst Refactor"""
return (self._get_druckpuffer_cm() * 10.0 * plotmassstab) / 1000.0
def _import_qgis_symbol(self, module_name: str, symbol_name: str) -> Any | None:
"""Test ausgesetzt, zunächst Refactor"""
try:
module = importlib.import_module(module_name)
except Exception:
return None
return getattr(module, symbol_name, None)
def _build_rectangle(self, x_min: float, y_min: float, x_max: float, y_max: float) -> Any | None:
"""Test ausgesetzt, zunächst Refactor"""
qgs_rectangle = self._import_qgis_symbol("qgis.core", "QgsRectangle")
if qgs_rectangle is None:
return None
try:
return qgs_rectangle(x_min, y_min, x_max, y_max)
except Exception:
return None
def _clone_extent(self, extent: Any) -> Any | None:
"""Test ausgesetzt, zunächst Refactor"""
if extent is None:
return None
buffered = getattr(extent, "buffered", None)
if callable(buffered):
try:
return buffered(0.0)
except Exception:
pass
x_min = getattr(extent, "xMinimum", lambda: None)()
y_min = getattr(extent, "yMinimum", lambda: None)()
x_max = getattr(extent, "xMaximum", lambda: None)()
y_max = getattr(extent, "yMaximum", lambda: None)()
if None in (x_min, y_min, x_max, y_max):
return None
assert x_min is not None
assert y_min is not None
assert x_max is not None
assert y_max is not None
x_min_f = float(x_min)
y_min_f = float(y_min)
x_max_f = float(x_max)
y_max_f = float(y_max)
return self._build_rectangle(x_min_f, y_min_f, x_max_f, y_max_f)
def _buffer_extent(self, extent: Any, plotmassstab: float) -> Any | None:
if extent is None:
return None
buffer_m = self._get_druckpuffer_m(plotmassstab)
if buffer_m <= 0:
return extent
buffered = getattr(extent, "buffered", None)
if callable(buffered):
try:
return buffered(buffer_m)
except Exception:
pass
x_min = getattr(extent, "xMinimum", lambda: None)()
y_min = getattr(extent, "yMinimum", lambda: None)()
x_max = getattr(extent, "xMaximum", lambda: None)()
y_max = getattr(extent, "yMaximum", lambda: None)()
if None in (x_min, y_min, x_max, y_max):
return extent
assert x_min is not None
assert y_min is not None
assert x_max is not None
assert y_max is not None
x_min_f = float(x_min)
y_min_f = float(y_min)
x_max_f = float(x_max)
y_max_f = float(y_max)
expanded = self._build_rectangle(
x_min_f - buffer_m,
y_min_f - buffer_m,
x_max_f + buffer_m,
y_max_f + buffer_m,
)
return expanded or extent
def _get_existing_layer_by_name(self, layer_name: str) -> object | None:
"""Test ausgesetzt, zunächst Refactor"""
project = QgsProject.instance()
map_layers_by_name = getattr(project, "mapLayersByName", None)
if not callable(map_layers_by_name):
return None
try:
matches = map_layers_by_name(layer_name)
except Exception:
return None
if not isinstance(matches, (list, tuple)) or not matches:
return None
return matches[0]
def _create_druckrechteck_layer(self, reference_layer: object | None) -> object | None:
"""Test ausgesetzt, zunächst Refactor"""
existing_layer = self._get_existing_layer_by_name(DRUCKRECHTECK_LAYER_NAME)
if existing_layer is not None:
return existing_layer
qgs_vector_layer = self._import_qgis_symbol("qgis.core", "QgsVectorLayer")
if qgs_vector_layer is None:
return None
crs_authid = "EPSG:25832"
if reference_layer is not None:
try:
layer_crs = getattr(reference_layer, "crs", lambda: None)()
if layer_crs is not None:
crs_authid = layer_crs.authid()
except Exception:
pass
layer = qgs_vector_layer(f"Polygon?crs={crs_authid}", DRUCKRECHTECK_LAYER_NAME, "memory")
if not layer or not getattr(layer, "isValid", lambda: False)():
return None
try:
QgsProject.instance().addMapLayer(layer)
except Exception:
return None
return layer
def _clear_layer_features(self, layer: object) -> None:
"""Test ausgesetzt, zunächst Refactor"""
get_features = getattr(layer, "getFeatures", None)
data_provider = getattr(layer, "dataProvider", lambda: None)()
if not callable(get_features) or data_provider is None:
return
feature_ids: list[int] = []
try:
features = cast(Any, get_features())
for feature in features:
feature_id = getattr(feature, "id", lambda: None)()
if feature_id is not None:
feature_ids.append(int(feature_id))
except Exception:
feature_ids = []
if feature_ids:
delete_features = getattr(data_provider, "deleteFeatures", None)
if callable(delete_features):
try:
delete_features(feature_ids)
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
Test ausgesetzt, zunächst Refactor
"""
# 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 ausgewählt.",
)
return None, ""
except Exception:
pass
# Bestehenden Atlaslayer löschen (optional Wiederverwendung)
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 verfügbar.",
)
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, ""
# Ausgewählte 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 ausgewählt")
qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
if qgs_feature is None:
raise Exception("QgsFeature Symbol nicht verfügbar")
data_provider = getattr(atlas_layer, "dataProvider", lambda: None)()
if data_provider is None:
raise Exception("dataProvider nicht verfügbar")
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 für 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.
Test ausgesetzt, zunächst Refactor
"""
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 verfügbar.")
return False
if not layout_name:
layouts_fn = getattr(layout_manager, "layouts", None)
if not callable(layouts_fn):
return False
try:
layout_iterable = layouts_fn()
if layout_iterable is None:
return False
last_layout: Any | None = None
for candidate_layout in cast(Any, layout_iterable):
last_layout = candidate_layout
if last_layout is None:
return False
layout = last_layout
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
atlas_enabled = bool(atlas_config.get("enabled", True))
if not atlas_enabled:
set_enabled = getattr(atlas, "setEnabled", None)
if callable(set_enabled):
try:
set_enabled(False)
except Exception:
pass
# Hauptkarte ebenfalls aus Atlas-Steuerung lösen
item_by_id = getattr(layout, "itemById", None)
if callable(item_by_id):
try:
hauptkarte = item_by_id("Hauptkarte")
set_atlas_driven = getattr(hauptkarte, "setAtlasDriven", None)
if callable(set_atlas_driven):
set_atlas_driven(False)
except Exception:
pass
return True
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:
"""Test ausgesetzt, zunächst Refactor"""
qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
qgs_geometry = self._import_qgis_symbol("qgis.core", "QgsGeometry")
if qgs_feature is None or qgs_geometry is None:
return False
if layer is None or rectangle is None:
return False
data_provider = getattr(layer, "dataProvider", lambda: None)()
if data_provider is None:
return False
self._clear_layer_features(layer)
feature = qgs_feature()
try:
feature.setGeometry(qgs_geometry.fromRect(rectangle))
add_features = getattr(data_provider, "addFeatures", None)
if callable(add_features):
add_features([feature])
update_extents = getattr(layer, "updateExtents", None)
if callable(update_extents):
update_extents()
trigger_repaint = getattr(layer, "triggerRepaint", None)
if callable(trigger_repaint):
trigger_repaint()
except Exception:
return False
try:
set_variable("druckrechteck_xmin", str(rectangle.xMinimum()), scope="project")
set_variable("druckrechteck_ymin", str(rectangle.yMinimum()), scope="project")
set_variable("druckrechteck_xmax", str(rectangle.xMaximum()), scope="project")
set_variable("druckrechteck_ymax", str(rectangle.yMaximum()), scope="project")
except Exception:
pass
map_canvas = getattr(iface, "mapCanvas", lambda: None)()
if map_canvas is not None:
refresh = getattr(map_canvas, "refresh", None)
if callable(refresh):
try:
refresh()
except Exception:
pass
return True
def activate_druckrechteck_capture(self, reference_layer: object | None = None) -> bool:
"""Test ausgesetzt, zunächst Refactor"""
layer = self._create_druckrechteck_layer(reference_layer)
if layer is None:
self.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Der temporaere Layer 'Druckrechteck' konnte nicht erzeugt werden.",
)
return False
map_canvas = getattr(iface, "mapCanvas", lambda: None)()
if map_canvas is None:
self.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Das Kartenfenster ist nicht verfügbar.",
)
return False
qgs_geometry = self._import_qgis_symbol("qgis.core", "QgsGeometry")
qgs_rectangle = self._import_qgis_symbol("qgis.core", "QgsRectangle")
qgs_wkb_types = self._import_qgis_symbol("qgis.core", "QgsWkbTypes")
qgs_map_tool_emit_point = self._import_qgis_symbol("qgis.gui", "QgsMapToolEmitPoint")
qgs_rubber_band = self._import_qgis_symbol("qgis.gui", "QgsRubberBand")
if None in (qgs_geometry, qgs_rectangle, qgs_wkb_types, qgs_map_tool_emit_point, qgs_rubber_band):
self.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Die Rechteck-Erfassung ist in dieser QGIS-Umgebung nicht verfügbar.",
)
return False
logic = self
self._previous_map_tool = getattr(map_canvas, "mapTool", lambda: None)()
base_tool_class = cast(type, qgs_map_tool_emit_point)
rubber_band_class = cast(Any, qgs_rubber_band)
geometry_class = cast(Any, qgs_geometry)
rectangle_class = cast(Any, qgs_rectangle)
polygon_geometry = cast(Any, getattr(qgs_wkb_types, "PolygonGeometry", None))
class RectangleCaptureTool(base_tool_class):
def __init__(self, canvas_widget: Any) -> None:
super().__init__(canvas_widget)
self.canvas = canvas_widget
self.start_point: Any | None = None
self.rubber_band: Any = rubber_band_class(canvas_widget, polygon_geometry)
def _to_rect(self, start_point: Any, end_point: Any) -> Any:
x_min = min(start_point.x(), end_point.x())
x_max = max(start_point.x(), end_point.x())
y_min = min(start_point.y(), end_point.y())
y_max = max(start_point.y(), end_point.y())
return rectangle_class(x_min, y_min, x_max, y_max)
def _show_rect(self, rect: Any) -> None:
try:
self.rubber_band.setToGeometry(geometry_class.fromRect(rect), None)
except Exception:
pass
def canvasPressEvent(self, event: Any) -> None:
self.start_point = self.toMapCoordinates(event.pos())
def canvasMoveEvent(self, event: Any) -> None:
if self.start_point is None:
return
end_point = self.toMapCoordinates(event.pos())
self._show_rect(self._to_rect(self.start_point, end_point))
def canvasReleaseEvent(self, event: Any) -> None:
if self.start_point is None:
return
end_point = self.toMapCoordinates(event.pos())
rectangle = self._to_rect(self.start_point, end_point)
self._show_rect(rectangle)
width = getattr(rectangle, "width", lambda: 0.0)()
height = getattr(rectangle, "height", lambda: 0.0)()
if width <= 0 or height <= 0:
logic.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Das gezeichnete Rechteck ist ungültig.",
)
else:
if logic._store_druckrechteck(layer, rectangle):
logic.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Das Druckrechteck wurde übernommen.",
)
try:
self.rubber_band.reset(polygon_geometry)
except Exception:
pass
if logic._previous_map_tool is not None:
try:
self.canvas.setMapTool(logic._previous_map_tool)
except Exception:
pass
logic._rectangle_tool = None
rectangle_tool = RectangleCaptureTool(map_canvas)
self._rectangle_tool = rectangle_tool
set_active_layer = getattr(iface, "setActiveLayer", None)
if callable(set_active_layer):
try:
set_active_layer(layer)
except Exception:
pass
start_editing = getattr(layer, "startEditing", None)
if callable(start_editing):
try:
start_editing()
except Exception:
pass
set_map_tool = getattr(map_canvas, "setMapTool", None)
if callable(set_map_tool):
try:
set_map_tool(rectangle_tool)
except Exception:
self.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Das Rechteck-Werkzeug konnte nicht aktiviert werden.",
)
return False
self.pruefmanager.zeige_hinweis(
"Druckrechteck",
"Bitte ziehen Sie ein Rechteck in der Karte auf.",
)
return True
def _resolve_selection_layer(self, fallback_layer: object | None) -> object | None:
"""Test ausgesetzt, zunächst Refactor"""
active_layer = self._resolve_active_layer()
if active_layer is not None:
selected_count = getattr(active_layer, "selectedFeatureCount", None)
if callable(selected_count):
try:
selected_count_value = selected_count()
if int(cast(Any, selected_count_value)) > 0:
return active_layer
except Exception:
pass
return fallback_layer
def _get_selected_extent(self, layer: object | None) -> Any | None:
"""Test ausgesetzt, zunächst Refactor"""
if layer is None:
return None
get_selected_features = getattr(layer, "selectedFeatures", None)
if not callable(get_selected_features):
return None
try:
selected_features_any = get_selected_features()
selected_features = list(cast(Any, selected_features_any))
except Exception:
return None
if not selected_features:
return None
merged_extent: Any | None = None
for feature in selected_features:
geometry = getattr(feature, "geometry", lambda: None)()
if geometry is None:
continue
bounding_box = getattr(geometry, "boundingBox", lambda: None)()
if bounding_box is None:
continue
if merged_extent is None:
merged_extent = self._clone_extent(bounding_box)
continue
combine_extent = getattr(merged_extent, "combineExtentWith", None)
if callable(combine_extent):
try:
combine_extent(bounding_box)
continue
except Exception:
pass
x_min = min(float(merged_extent.xMinimum()), float(bounding_box.xMinimum()))
y_min = min(float(merged_extent.yMinimum()), float(bounding_box.yMinimum()))
x_max = max(float(merged_extent.xMaximum()), float(bounding_box.xMaximum()))
y_max = max(float(merged_extent.yMaximum()), float(bounding_box.yMaximum()))
merged_extent = self._build_rectangle(x_min, y_min, x_max, y_max)
return merged_extent
def _get_druckrechteck_extent(self) -> Any | None:
layer = self._get_existing_layer_by_name(DRUCKRECHTECK_LAYER_NAME)
extent = get_layer_extent(layer)
if extent is not None:
width = getattr(extent, "width", lambda: 0.0)()
height = getattr(extent, "height", lambda: 0.0)()
if width > 0 and height > 0:
return extent
try:
x_min = float(get_variable("druckrechteck_xmin", scope="project"))
y_min = float(get_variable("druckrechteck_ymin", scope="project"))
x_max = float(get_variable("druckrechteck_xmax", scope="project"))
y_max = float(get_variable("druckrechteck_ymax", scope="project"))
except (TypeError, ValueError):
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 gültige 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)
if extent is None:
self.pruefmanager.zeige_hinweis(
"Druckbereich",
"Auf dem aktuellen Layer sind keine gültigen Objekte ausgewählt.",
)
return None, None
return self._buffer_extent(extent, plotmassstab), None
if druckbereich_auswahl == DRUCKBEREICH_RECHTECK:
extent = self._get_druckrechteck_extent()
if extent is None:
self.activate_druckrechteck_capture(resolved_layer)
return None, None
return extent, None
lp = Layerpruefer(layer=resolved_layer)
ergebnis = lp.pruefe()
if not ergebnis.ok:
self.pruefmanager.zeige_hinweis(
"Verfahrensgebiets-Layer angeben",
"Kein gültiger Verfahrensgebiets-Layer gefunden. Bitte Layerauswahl im jeweiligen Plugin setzen.",
)
return None, None
extent = get_layer_extent(resolved_layer)
if extent is None:
self.pruefmanager.zeige_hinweis("Fehler", "Layer-Ausdehnung konnte nicht ermittelt werden.")
return None, None
return self._buffer_extent(extent, plotmassstab), resolved_layer
def set_kartenname_for_auswahl(self, auswahl: str) -> None:
"""Test ausgesetzt, zunächst Refactor"""
kartenname = KARTENNAME_BY_AUSWAHL.get(auswahl, "")
set_variable(KARTENNAME_VAR, kartenname, scope="project")
def set_plotmassstab_for_auswahl(self, auswahl: str, aktueller_massstab: float | None = None) -> None:
"""Test ausgesetzt, zunächst Refactor"""
if auswahl == MASSSTAB_WIE_KARTENFENSTER:
if aktueller_massstab and aktueller_massstab > 0:
set_variable(PLOTMASSSTAB_VAR, str(int(round(aktueller_massstab))), scope="project")
else:
set_variable(PLOTMASSSTAB_VAR, "", scope="project")
return
value = PLOTMASSSTAB_BY_AUSWAHL.get(auswahl, "")
set_variable(PLOTMASSSTAB_VAR, value, scope="project")
def set_view_for_auswahl(self, auswahl: str) -> None:
"""Test ausgesetzt, zunächst Refactor"""
if auswahl == THEMA_WIE_KARTENFENSTER:
set_variable(VIEW_VAR, "aktuell", scope="project")
return
set_variable(VIEW_VAR, auswahl or "", scope="project")
def set_zielgroesse_for_auswahl(self, auswahl: str) -> None:
"""Test ausgesetzt, zunächst Refactor"""
set_variable(ZIELGROESSE_VAR, auswahl if auswahl in DIN_GROESSEN else DIN_STANDARD, scope="project")
def set_formfaktor(self, endlosrolle: bool) -> None:
"""Test ausgesetzt, zunächst Refactor"""
set_variable(FORMFAKTOR_VAR, "Endlosrolle" if endlosrolle else "Blatt", scope="project")
def set_druckbereich_for_auswahl(self, auswahl: str) -> None:
if auswahl not in {
DRUCKBEREICH_VERFAHRENSGEBIET,
DRUCKBEREICH_WIE_KARTENFENSTER,
DRUCKBEREICH_AKTUELLE_AUSWAHL,
DRUCKBEREICH_RECHTECK,
}:
auswahl = DRUCKBEREICH_VERFAHRENSGEBIET
set_variable(DRUCKBEREICH_VAR, auswahl, scope="project")
def _get_baufreigabe_page_dimensions(
self,
required_map_width_mm: float,
required_map_height_mm: float,
max_format: str = "DIN A3",
) -> tuple[str, float, float, float, float, bool]:
"""Wählt für Baufreigabe ein Blatt im Querformat.
Args:
required_map_width_mm: benötigte Breite der Hauptkarte in mm
required_map_height_mm: benötigte Höhe der Hauptkarte in mm
max_format: maximale Blattgröße, standardmäßig DIN A3
Returns:
(din_format, page_width_mm, page_height_mm, map_w_mm, map_h_mm, fallback_applied)
"""
candidates = (
("DIN A4", 297.0, 210.0),
("DIN A3", 420.0, 297.0),
)
try:
max_index = [candidate[0] for candidate in candidates].index(max_format)
except ValueError:
max_index = len(candidates) - 1
limited_candidates = candidates[: max_index + 1]
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
# Wenn der gewünschte Ausschnitt nicht passt, verwende das maximal erlaubte Blatt
# und erweitere die Hauptkarte auf die verfügbare Kartenfläche (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]:
"""Test ausgesetzt, zunächst Refactor"""
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 benötigte Kartenfenstergröße in mm für Extent und Maßstab.
Test ausgesetzt, zunächst Refactor
"""
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:
"""Wählt das kleinste passende DIN-Blatt für 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 _basemap_legende_erzeugen(self, kartenname_auswahl: str) -> bool:
"""Ob die neue Vorlage eine Basemap-Legende bekommt (Layoutart und Schalter im Druck-Tab)."""
from sn_basis.modules.basemap_legende import legende_erzeugen_aktiv
return kartenname_auswahl in LAYOUTS_MIT_BASEMAP_LEGENDE and legende_erzeugen_aktiv()
def druckvorlage_anlegen(
self,
layer: object | None,
kartenname_auswahl: str,
massstab_auswahl: str,
zielgroesse: str,
formfaktor: bool,
druckbereich_auswahl: str,
) -> dict:
"""Test ausgesetzt, zunächst Refactor"""
resolved_layer = layer or self._resolve_layer_from_project()
if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41, KARTENNAME_BAUFREIGABE):
self.pruefmanager.zeige_hinweis("Kartennamen wählen", "Kartennamen wählen")
return self._failed_result()
if massstab_auswahl == MASSSTAB_WIE_KARTENFENSTER:
# Maßstab direkt vom aktuellen Canvas lesen; Projektvariable als Fallback.
try:
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))
if massstab_zahl <= 0:
self.pruefmanager.zeige_hinweis("Maßstab fehlt", "Kein gültiger Maßstab angegeben.")
return self._failed_result()
extent, layer_for_extent = self._resolve_extent(resolved_layer, druckbereich_auswahl, massstab_zahl)
if extent is None:
return self._failed_result()
if layer_for_extent is not None:
layer_id = getattr(layer_for_extent, "id", lambda: "")() or ""
if layer_id:
# Neu: zentrale Variable
set_variable("verfahrensgebiet_layer", layer_id, scope="project")
# Alt: Rückwärtskompatibilität
set_variable("verfahrensgebietslayer", layer_id, scope="project")
kartenbild_w, kartenbild_h = self._get_required_map_dimensions_mm(extent, massstab_zahl)
force_fit_extent_for_min_a4 = False
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
_, 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:
standard_layout = self._get_standard_layout_dimensions(
kartenbild_w,
kartenbild_h,
zielgroesse,
formfaktor,
)
if standard_layout is None:
return self._show_target_size_too_small()
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:
fixed_width_mm = BAUFREIGABE_LAYOUT_FIXBREITE_MM
fixed_height_mm = BAUFREIGABE_LAYOUT_FIXHOEHE_MM
else:
fixed_width_mm = STANDARD_LAYOUT_FIXBREITE_MM
fixed_height_mm = STANDARD_LAYOUT_FIXHOEHE_MM
fits_page = self._does_plot_fit_page(
auto_page_w,
auto_page_h,
auto_map_w,
auto_map_h,
fixed_width_mm,
fixed_height_mm,
)
if not fits_page:
return self._show_target_size_too_small()
kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get(
kartenname_auswahl, "Vorlage"
)
thema = get_variable(VIEW_VAR, scope="project") or ""
vorlage_name, bestaetigt = self.pruefmanager.frage_text(
"Neue Vorlage anlegen",
"Bezeichnung der Vorlage:",
default_text=kartenname,
)
if not bestaetigt:
return self._failed_result()
vorlage_name = (vorlage_name or "").strip() or kartenname
# Atlas-Status vorab bestimmen, damit er sowohl an create_ als auch configure_ übergeben wird.
atlas_raw_bf = get_variable(ATLAS_ERZEUGEN_UI_VAR, scope="project")
atlas_erzeugen_bf = self._is_enabled_flag(atlas_raw_bf)
# Verzweigung: Baufreigabe vs. Standard-Layout
try:
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
# Baufreigabe: feste DIN A3/A4 Querformat-Logik mit 80mm Kartenschild
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=auto_map_w,
map_height_mm=auto_map_h,
extent=extent,
plotmassstab=massstab_zahl,
thema=thema,
fit_extent_to_map=True,
atlas_driven=atlas_erzeugen_bf,
)
else:
# Standard-Layout: dynamische Größenanpassung
PrintLayout().create_single_page_layout(
name=vorlage_name,
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) or force_fit_extent_for_min_a4,
basemap_legende=self._basemap_legende_erzeugen(kartenname_auswahl),
)
except ValueError as exc:
self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc))
return self._failed_result()
except Exception as exc:
self.pruefmanager.zeige_hinweis(
"Vorlage anlegen",
f"Die Vorlage konnte nicht angelegt werden: {exc}",
)
return self._failed_result()
# Nach erfolgreicher Layout-Erstellung: Atlas ggf. konfigurieren (nur falls aktiviert)
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
if atlas_erzeugen_bf:
self.configure_atlas_for_baufreigabe_layout()
else:
self.configure_atlas_for_baufreigabe_layout(
atlas_config={
"enabled": False,
}
)
return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": 1}