""" sn_basis/modules/print_logic.py - Fachlogik fuer 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 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": 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" MASSSTAB_WIE_KARTENFENSTER = "Wie Kartenfenster" THEMA_WIE_KARTENFENSTER = "wie kartenfenster" DRUCKBEREICH_VERFAHRENSGEBIET = "Verfahrensgebiet" DRUCKBEREICH_AKTUELLE_AUSWAHL = "aktuelle Auswahl" 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 = { "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_STANDARD = "DIN A0" DRUCKPUFFER_STANDARD_CM = 1 class PrintLogic: """Kapselt die Fachlogik fuer 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 _resolve_layer_from_project(self) -> object | None: 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: 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: 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: return (self._get_druckpuffer_cm() * 10.0 * plotmassstab) / 1000.0 def _import_qgis_symbol(self, module_name: str, symbol_name: str) -> Any | None: 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: 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: 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: 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: 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: 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 """ # 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 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: 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: 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 verfuegbar.", ) 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 verfuegbar.", ) 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 ungueltig.", ) else: if logic._store_druckrechteck(layer, rectangle): logic.pruefmanager.zeige_hinweis( "Druckrechteck", "Das Druckrechteck wurde uebernommen.", ) 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: 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: 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 _resolve_extent( self, resolved_layer: object | None, druckbereich_auswahl: str, plotmassstab: float, ) -> tuple[Any | None, object | 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 gueltigen Objekte ausgewaehlt.", ) 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 self._buffer_extent(extent, plotmassstab), None lp = Layerpruefer(layer=resolved_layer) ergebnis = lp.pruefe() if not ergebnis.ok: self.pruefmanager.zeige_hinweis( "Verfahrensgebiets-Layer angeben", "Kein gueltiger 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: 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: 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: 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: set_variable(ZIELGROESSE_VAR, auswahl if auswahl in DIN_GROESSEN else DIN_STANDARD, scope="project") def set_formfaktor(self, endlosrolle: bool) -> None: 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_AKTUELLE_AUSWAHL, DRUCKBEREICH_RECHTECK, }: auswahl = DRUCKBEREICH_VERFAHRENSGEBIET set_variable(DRUCKBEREICH_VAR, auswahl, scope="project") def _get_baufreigabe_page_dimensions( self, zielgroesse: str, ) -> tuple[str, float, float, float, float]: """Berechnet feste Seiten- und Hauptkartengroessen fuer Baufreigabe. Args: zielgroesse: "DIN A3" oder "DIN A4" (wird zu Querformat konvertiert) Returns: (din_format, page_width_mm, page_height_mm, map_w_mm, map_h_mm) """ # 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 # Feste Hauptkartenflaeche aus Querformat mit festen Rändern: # links 10 mm, oben 10 mm, rechts 10 mm, unten 10 mm, # plus 10 mm Abstand zum rechten Seitenbereich. kartenschild_w = 80.0 kartenschild_h = 92.0 margin = 10.0 map_w = page_w - (3 * margin + kartenschild_w) map_h = page_h - (2 * margin) # Untergrenzen zur Sicherheit bei abweichenden Seiteneinstellungen map_w = max(map_w, 50.0) map_h = max(map_h, kartenschild_h + margin) return din_format, page_w, page_h, map_w, map_h def druckvorlage_anlegen( self, layer: object | None, kartenname_auswahl: str, massstab_auswahl: str, zielgroesse: str, formfaktor: bool, druckbereich_auswahl: str, ) -> dict: 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 waehlen", "Kartennamen waehlen") return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} if massstab_auswahl == MASSSTAB_WIE_KARTENFENSTER: massstab_str = get_variable(PLOTMASSSTAB_VAR, scope="project") or "" 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("Massstab fehlt", "Kein gueltiger Massstab angegeben.") return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} extent, layer_for_extent = self._resolve_extent(resolved_layer, druckbereich_auswahl, massstab_zahl) if extent is None: return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} 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 = extent.width() * 1000.0 / massstab_zahl kartenbild_h = extent.height() * 1000.0 / massstab_zahl plotgroesse_w = kartenbild_w + 210.0 plotgroesse_h = kartenbild_h + 20.0 din_dims = DIN_GROESSEN.get(zielgroesse, DIN_GROESSEN[DIN_STANDARD]) din_w = float(din_dims[0]) din_h = float(din_dims[1]) din_max_h = float(max(din_dims)) if formfaktor: ziel_w, ziel_h = plotgroesse_w, min(plotgroesse_h, din_max_h) else: cap_w, cap_h = din_w, din_h if not (plotgroesse_w <= cap_w and plotgroesse_h <= cap_h): if plotgroesse_w <= din_h and plotgroesse_h <= din_w: cap_w, cap_h = din_h, din_w ziel_w = min(plotgroesse_w, cap_w) ziel_h = min(plotgroesse_h, cap_h) if kartenname_auswahl != KARTENNAME_BAUFREIGABE: if ziel_w < DIN_GROESSEN["DIN A4"][0] or ziel_h < DIN_GROESSEN["DIN A4"][1]: self.pruefmanager.zeige_hinweis( "Blattgroesse kleiner als DIN A4", "Die Layouterstellung wird trotzdem fortgesetzt.", ) if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h: if plotgroesse_w <= ziel_h and plotgroesse_h <= ziel_w: ziel_w, ziel_h = ziel_h, ziel_w if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h: self.pruefmanager.zeige_hinweis( "Zielgroesse zu klein", "Die gewaehlte Zielgroesse reicht fuer den Einzelblatt-Druck nicht aus.", ) return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get( kartenname_auswahl, "Vorlage" ) 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 {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} vorlage_name = (vorlage_name or "").strip() or kartenname # Verzweigung: Baufreigabe vs. Standard-Layout try: if kartenname_auswahl == KARTENNAME_BAUFREIGABE: # Baufreigabe: feste DIN A3/A4 Querformat-Logik mit 80mm Kartenschild din_format, page_w, page_h, map_w, map_h = self._get_baufreigabe_page_dimensions(zielgroesse) 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, kartenname_auswahl=kartenname_auswahl, 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} except Exception as exc: 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} # Nach erfolgreicher Layout-Erstellung: Atlas ggf. konfigurieren (nur falls aktiviert) if kartenname_auswahl == KARTENNAME_BAUFREIGABE: atlas_raw = get_variable(ATLAS_ERZEUGEN_UI_VAR, scope="project") atlas_erzeugen = True if atlas_raw in (None, "") else str(atlas_raw).strip().lower() in { "1", "true", "ja", "yes", "on", } if atlas_erzeugen: 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}