diff --git a/assets/atlasobjekte.qml b/assets/atlasobjekte.qml new file mode 100644 index 0000000..952ddd5 --- /dev/null +++ b/assets/atlasobjekte.qml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/layout.py b/ui/layout.py index a4da86e..4aa52c8 100644 --- a/ui/layout.py +++ b/ui/layout.py @@ -199,3 +199,233 @@ class Layout: open_layout_designer(layout) print("[Layout] Layout Designer geöffnet") return layout + + def create_atlas_layout( + self, + name: str, + page_width_mm: float, + page_height_mm: float, + map_width_mm: float, + map_height_mm: float, + extent: Any, + plotmassstab: float, + atlas_layer: Any, + thema: str = "", + ) -> Any: + """Erzeugt ein Atlas-Layout mit Coverage-Layer ``Atlasobjekte``.""" + 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) + + page = layout.pageCollection().page(0) + page.setPageSize(QgsLayoutSize(page_width_mm, page_height_mm, MM)) + # Verifiziere, dass QGIS die Größe akzeptiert hat + page_size = page.pageSize() if hasattr(page, 'pageSize') else None + if page_size is not None and hasattr(page_size, 'width'): + actual_w = float(page_size.width()) + actual_h = float(page_size.height()) + print(f"[Layout] Atlas Page gesetzt: x=0, y=0, width={page_width_mm:.1f}mm→{actual_w:.1f}mm, height={page_height_mm:.1f}mm→{actual_h:.1f}mm") + else: + print(f"[Layout] Atlas Page: x=0, y=0, width={page_width_mm:.1f}mm, height={page_height_mm:.1f}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 + + hauptkarte = QgsLayoutItemMap(layout) + hauptkarte.setId("Hauptkarte") + layout.addLayoutItem(hauptkarte) + hauptkarte.attemptMove(QgsLayoutPoint(map_left_mm, map_top_mm, MM)) + hauptkarte.attemptResize(QgsLayoutSize(map_width_mm, map_height_mm, MM)) + + # Verifiziere mit Units-bewussten Methoden (rect() kann andere Units verwenden). + actual_w = None + actual_h = None + actual_x = None + actual_y = None + + # Versuche zuerst, die Größe mit Unit-Methoden zu lesen + try: + if hasattr(hauptkarte, 'sizeWithUnits'): + size_item = hauptkarte.sizeWithUnits() + if hasattr(size_item, 'width') and hasattr(size_item, 'height'): + actual_w = float(size_item.width()) + actual_h = float(size_item.height()) + except Exception: + pass + + try: + if hasattr(hauptkarte, 'positionWithUnits'): + pos_item = hauptkarte.positionWithUnits() + if hasattr(pos_item, 'x') and hasattr(pos_item, 'y'): + actual_x = float(pos_item.x()) + actual_y = float(pos_item.y()) + except Exception: + pass + + # Fallback: nutze rect() und teile durch UnitFaktor, falls nötig + if actual_w is None or actual_h is None: + try: + actual_rect = hauptkarte.rect() + if actual_rect is not None: + actual_w = float(actual_rect.width()) + actual_h = float(actual_rect.height()) + actual_x = float(actual_rect.x()) + actual_y = float(actual_rect.y()) + except Exception: + pass + + if actual_w is not None and actual_h is not None: + print(f"[Layout] Atlas Hauptkarte gesetzt: x={map_left_mm:.1f}mm→{actual_x:.1f}mm, y={map_top_mm:.1f}mm→{actual_y:.1f}mm, width={map_width_mm:.1f}mm→{actual_w:.1f}mm, height={map_height_mm:.1f}mm→{actual_h:.1f}mm") + else: + print(f"[Layout] Atlas Hauptkarte: x={map_left_mm:.1f}mm, y={map_top_mm:.1f}mm, width={map_width_mm:.1f}mm, height={map_height_mm:.1f}mm") + + if extent is not None and hasattr(extent, "isNull") and callable(extent.isNull) and not extent.isNull(): + try: + hauptkarte.setExtent(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: + set_frame_stroke_width(0.5) + 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_atlas_driven = getattr(hauptkarte, "setAtlasDriven", None) + if callable(set_atlas_driven): + try: + set_atlas_driven(True) + except Exception: + pass + + # Fester Atlas-Maßstab: plotmassstab bleibt unverändert. + set_scaling_mode = getattr(hauptkarte, "setAtlasScalingMode", None) + if callable(set_scaling_mode): + fixed_mode = getattr(QgsLayoutItemMap, "Fixed", None) + if fixed_mode is not None: + try: + set_scaling_mode(fixed_mode) + except Exception: + pass + + set_atlas_margin = getattr(hauptkarte, "setAtlasMargin", None) + if callable(set_atlas_margin): + try: + set_atlas_margin(0.0) + 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 + + # Sicherheit: Größe nochmal nach Atlas-Konfiguration setzen, um sicherzustellen, dass sie nicht von der Atlas-Einstellung überschrieben wurde. + hauptkarte.attemptResize(QgsLayoutSize(map_width_mm, map_height_mm, MM)) + print(f"[Layout] Atlas Hauptkarte Größe nach Atlas-Konfiguration erneut gesetzt: {map_width_mm:.1f}mm × {map_height_mm:.1f}mm") + + quellenangabe = QgsLayoutItemLabel(layout) + quellenangabe.setId("Quellenangabe") + quellenangabe.setText( + "Quelle Geobasisdaten: GeoSN, " + "dl-de/by-2-0

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

" + "Basemap:

" + "© GeoBasis-DE / BKG ([%year($now)%]) " + "CC BY 4.0 " + "mit teilweise angepasster Signatur
" + ) + set_mode = getattr(quellenangabe, "setMode", None) + mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None) + if callable(set_mode) and mode_html is not None: + set_mode(mode_html) + quellenangabe.setFont(QFont("Arial", 12)) + set_reference_point = getattr(quellenangabe, "setReferencePoint", None) + lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None) + if callable(set_reference_point) and lower_left is not None: + set_reference_point(lower_left) + quellenangabe.attemptMove(QgsLayoutPoint(map_right_mm + 5.0, map_bottom_mm, MM)) + quellenangabe.attemptResize(QgsLayoutSize(180.0, 100.0, MM)) + layout.addLayoutItem(quellenangabe) + + seitenzahl_label = QgsLayoutItemLabel(layout) + seitenzahl_label.setId("Seitenzahl") + seitenzahl_label.setFont(QFont("Arial", 12)) + set_expr_enabled = getattr(seitenzahl_label, "setExpressionEnabled", None) + if callable(set_expr_enabled): + try: + set_expr_enabled(True) + except Exception: + pass + # Ausdrucksauswertung robust über [% ... %]-Platzhalter. + seitenzahl_label.setText( + "Seite [% attribute(@atlas_feature, 'Seitenzahl') %] von [% @atlas_totalfeatures %]" + ) + set_reference_point = getattr(seitenzahl_label, "setReferencePoint", None) + lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None) + if callable(set_reference_point) and lower_left is not None: + set_reference_point(lower_left) + seitenzahl_label.attemptMove(QgsLayoutPoint(map_right_mm + 5.0, map_bottom_mm - 2.0, MM)) + seitenzahl_label.attemptResize(QgsLayoutSize(60.0, 8.0, MM)) + layout.addLayoutItem(seitenzahl_label) + + atlas = layout.atlas() + if atlas is not None: + set_enabled = getattr(atlas, "setEnabled", None) + set_coverage = getattr(atlas, "setCoverageLayer", None) + set_hide_coverage = getattr(atlas, "setHideCoverage", None) + set_filter_features = getattr(atlas, "setFilterFeatures", None) + set_page_name = getattr(atlas, "setPageNameExpression", None) + + if callable(set_enabled): + set_enabled(True) + if callable(set_coverage): + set_coverage(atlas_layer) + if callable(set_hide_coverage): + set_hide_coverage(True) + if callable(set_filter_features): + set_filter_features(False) + if callable(set_page_name): + set_page_name("attribute(@atlas_feature, 'Seitenzahl')") + + layout_manager.addLayout(layout) + open_layout_designer(layout) + return layout diff --git a/ui/tab_b_logic.py b/ui/tab_b_logic.py index 8f0024c..14157f8 100644 --- a/ui/tab_b_logic.py +++ b/ui/tab_b_logic.py @@ -3,8 +3,20 @@ sn_plan41/ui/tab_b_logic.py – Fachlogik für Tab B (Druck) """ from __future__ import annotations import math +from typing import Any + +from sn_basis.functions.qt_wrapper import QVariant from sn_basis.functions.variable_wrapper import set_variable, get_variable from sn_basis.functions.qgiscore_wrapper import get_layer_extent +from sn_basis.functions.qgiscore_wrapper import ( + QgsProject, + QgsVectorLayer, + QgsGeometry, + QgsFeature, + QgsField, + QgsVectorFileWriter, +) +from sn_basis.functions.sys_wrapper import get_plugin_root, join_path, file_exists from sn_basis.modules.Pruefmanager import Pruefmanager from sn_basis.modules.layerpruefer import Layerpruefer from sn_plan41.ui.layout import Layout @@ -53,6 +65,140 @@ class TabBLogic: def __init__(self, pruefmanager: Pruefmanager) -> None: self.pruefmanager = pruefmanager + @staticmethod + def _wkt_rect(x_min: float, y_min: float, x_max: float, y_max: float) -> str: + return ( + f"POLYGON(({x_min} {y_min}, {x_max} {y_min}, {x_max} {y_max}, " + f"{x_min} {y_max}, {x_min} {y_min}))" + ) + + @staticmethod + def _set_topological_editing_enabled() -> None: + project = QgsProject.instance() + set_topological = getattr(project, "setTopologicalEditing", None) + if callable(set_topological): + try: + set_topological(True) + except Exception: + pass + + @staticmethod + def _apply_atlas_style(layer: Any) -> None: + style_path = join_path(get_plugin_root(), "sn_plan41", "assets", "atlasobjekte.qml") + if not file_exists(style_path): + return + try: + ok, _ = layer.loadNamedStyle(str(style_path)) + if ok: + getattr(layer, "triggerRepaint", lambda: None)() + except Exception: + pass + + def _create_atlasobjekte_layer( + self, + layer: Any, + extent: Any, + pages_x: int, + pages_y: int, + seite_karte_w: float, + seite_karte_h: float, + massstab_zahl: float, + ) -> Any | None: + layer_crs = layer.crs() if hasattr(layer, "crs") else None + crs_authid = layer_crs.authid() if layer_crs is not None and hasattr(layer_crs, "authid") else "EPSG:25832" + atlas_layer = QgsVectorLayer(f"Polygon?crs={crs_authid}", "Atlasobjekte", "memory") + if not atlas_layer or not atlas_layer.isValid(): + return None + + provider = atlas_layer.dataProvider() + provider.addAttributes([ + QgsField("Seitenzahl", QVariant.Int), + ]) + atlas_layer.updateFields() + + tile_w_m = seite_karte_w * massstab_zahl / 1000.0 + tile_h_m = seite_karte_h * massstab_zahl / 1000.0 + + x_min = extent.xMinimum() + y_min = extent.yMinimum() + x_max = extent.xMaximum() + y_max = extent.yMaximum() + + seitenzahl = 1 + features = [] + for row_idx in range(pages_y): + tile_y_max = y_max - row_idx * tile_h_m + tile_y_min = tile_y_max - tile_h_m + + for col_idx in range(pages_x): + tile_x_min = x_min + col_idx * tile_w_m + tile_x_max = tile_x_min + tile_w_m + + tile_geom = QgsGeometry.fromWkt( + self._wkt_rect(tile_x_min, tile_y_min, tile_x_max, tile_y_max) + ) + + feat = QgsFeature(atlas_layer.fields()) + feat.setGeometry(tile_geom) + feat["Seitenzahl"] = seitenzahl + features.append(feat) + seitenzahl += 1 + + if not features: + return None + + provider.addFeatures(features) + atlas_layer.updateExtents() + self._set_topological_editing_enabled() + + verfahrens_db = get_variable("verfahrens_db", scope="project") or "" + print(f"[TabBLogic] Atlasobjekte: verfahrens_db='{verfahrens_db}'") + if not verfahrens_db: + QgsProject.instance().addMapLayer(atlas_layer) + self._apply_atlas_style(atlas_layer) + print("[TabBLogic] Atlasobjekte temporär ins Projekt geladen") + return atlas_layer + + opts = QgsVectorFileWriter.SaveVectorOptions() + opts.driverName = "GPKG" + opts.fileEncoding = "UTF-8" + opts.layerName = "Atlasobjekte" + if file_exists(verfahrens_db): + opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer + else: + opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile + + err_result = QgsVectorFileWriter.writeAsVectorFormatV3( + atlas_layer, + verfahrens_db, + QgsProject.instance().transformContext(), + opts, + ) + + # QGIS-Versionen liefern hier entweder nur WriterError + # oder ein Tupel (WriterError, msg, newPath, layerName). + err_code = err_result[0] if isinstance(err_result, tuple) else err_result + if err_code != QgsVectorFileWriter.NoError: + print(f"[TabBLogic] Atlasobjekte schreiben fehlgeschlagen: err={err_result}") + return None + + for existing in QgsProject.instance().mapLayersByName("Atlasobjekte"): + try: + QgsProject.instance().removeMapLayer(existing.id()) + except Exception: + pass + + uri = f"{verfahrens_db}|layername=Atlasobjekte" + loaded_layer = QgsVectorLayer(uri, "Atlasobjekte", "ogr") + if not loaded_layer or not loaded_layer.isValid(): + print(f"[TabBLogic] Atlasobjekte laden aus GPKG fehlgeschlagen: uri='{uri}'") + return None + + QgsProject.instance().addMapLayer(loaded_layer) + self._apply_atlas_style(loaded_layer) + print("[TabBLogic] Atlasobjekte aus Verfahrens-DB geladen und gestylt") + return loaded_layer + def set_kartenname_for_auswahl(self, auswahl: str) -> None: """Setzt die Projektvariable ``sn_kartenname`` anhand der Kartennamen-Auswahl.""" kartenname = KARTENNAME_BY_AUSWAHL.get(auswahl, "") @@ -178,9 +324,20 @@ class TabBLogic: else: ziel_w, ziel_h = float(din_dims[0]), float(din_dims[1]) + if ziel_w < DIN_GROESSEN["DIN A4"][0] or ziel_h < DIN_GROESSEN["DIN A4"][1]: + self.pruefmanager.zeige_hinweis( + "Blattgröße zu klein", + "Die Zielgröße darf nicht kleiner als DIN A4 sein.", + ) + return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} + # ─── 7. Passt auf ein Blatt? -> Layout erzeugen ─────────────────── print(f"[TabBLogic] plotgroesse=({plotgroesse_w:.1f}x{plotgroesse_h:.1f}), " f"zielgroesse=({ziel_w:.1f}x{ziel_h:.1f}), passt={plotgroesse_w <= ziel_w and plotgroesse_h <= ziel_h}") + if plotgroesse_w > ziel_w 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 and plotgroesse_h <= ziel_h: kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get( kartenname_auswahl, "Vorlage" @@ -247,7 +404,154 @@ class TabBLogic: "Ausdruck als Atlas anlegen?", default=True, ) + print(f"[TabBLogic] Atlas-Rückfrage: ja={ja}, geplante_seiten={anzahl_seiten}") if not ja: return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": anzahl_seiten} + atlas_layer = self._create_atlasobjekte_layer( + layer=layer, + extent=extent, + pages_x=pages_x, + pages_y=pages_y, + seite_karte_w=seite_karte_w, + seite_karte_h=seite_karte_h, + massstab_zahl=massstab_zahl, + ) + if atlas_layer is None: + self.pruefmanager.zeige_hinweis( + "Atlasobjekte", + "Atlasobjekte-Layer konnte nicht erzeugt werden.", + ) + return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} + print("[TabBLogic] Atlasobjekte-Layer erfolgreich erzeugt") + + try: + anzahl_seiten = int(atlas_layer.featureCount()) + except Exception: + pass + + # Berechne die Kachelgröße aus den Atlasobjekten. + # Für die Layout-Kartengröße muss die größte Atlas-Kachel passen, + # sonst werden größere Atlasobjekte abgeschnitten. + total_w_m = 0.0 + total_h_m = 0.0 + min_w_m = math.inf + min_h_m = math.inf + max_w_m = 0.0 + max_h_m = 0.0 + feature_count = 0 + get_features = getattr(atlas_layer, "getFeatures", None) + if callable(get_features): + try: + for feat in get_features(): + geom = feat.geometry() if hasattr(feat, "geometry") else None + if geom is None or geom.isEmpty(): + continue + bbox = geom.boundingBox() if hasattr(geom, "boundingBox") else None + if bbox is None: + continue + feat_w_m = float(bbox.width()) + feat_h_m = float(bbox.height()) + total_w_m += feat_w_m + total_h_m += feat_h_m + min_w_m = min(min_w_m, feat_w_m) + min_h_m = min(min_h_m, feat_h_m) + max_w_m = max(max_w_m, feat_w_m) + max_h_m = max(max_h_m, feat_h_m) + feature_count += 1 + except Exception: + pass + + if feature_count > 0: + avg_tile_w_m = total_w_m / feature_count + avg_tile_h_m = total_h_m / feature_count + if not math.isfinite(min_w_m): + min_w_m = avg_tile_w_m + if not math.isfinite(min_h_m): + min_h_m = avg_tile_h_m + target_tile_w_m = max_w_m + target_tile_h_m = max_h_m + else: + avg_tile_w_m = seite_karte_w * massstab_zahl / 1000.0 + avg_tile_h_m = seite_karte_h * massstab_zahl / 1000.0 + min_w_m = avg_tile_w_m + min_h_m = avg_tile_h_m + max_w_m = avg_tile_w_m + max_h_m = avg_tile_h_m + target_tile_w_m = avg_tile_w_m + target_tile_h_m = avg_tile_h_m + + # Konvertiere Kachelgrößen zu mm + avg_tile_w_mm = avg_tile_w_m * 1000.0 / massstab_zahl + avg_tile_h_mm = avg_tile_h_m * 1000.0 / massstab_zahl + min_tile_w_mm = min_w_m * 1000.0 / massstab_zahl + min_tile_h_mm = min_h_m * 1000.0 / massstab_zahl + max_tile_w_mm = max_w_m * 1000.0 / massstab_zahl + max_tile_h_mm = max_h_m * 1000.0 / massstab_zahl + target_tile_w_mm = target_tile_w_m * 1000.0 / massstab_zahl + target_tile_h_mm = target_tile_h_m * 1000.0 / massstab_zahl + + # Layout-Kartengröße = größte Kachelgröße + atlas_map_w = max(1.0, target_tile_w_mm) + atlas_map_h = max(1.0, target_tile_h_mm) + atlas_page_w = atlas_map_w + 210.0 + atlas_page_h = atlas_map_h + 20.0 + + # Debug: Atlasobjekte Geometrien + print( + f"[TabBLogic] Atlasobjekte Geometrien (Meter): " + f"total_w={total_w_m:.1f}m, total_h={total_h_m:.1f}m, " + f"min_w={min_w_m:.1f}m, min_h={min_h_m:.1f}m, " + f"avg_w={avg_tile_w_m:.1f}m, avg_h={avg_tile_h_m:.1f}m, " + f"max_w={max_w_m:.1f}m, max_h={max_h_m:.1f}m, features={feature_count}" + ) + print( + f"[TabBLogic] Atlas layout Größen: " + f"page=(x=10, y=10, w={atlas_page_w:.1f}mm, h={atlas_page_h:.1f}mm), " + f"map=(x=10, y=10, w={atlas_map_w:.1f}mm, h={atlas_map_h:.1f}mm), " + f"kachel_min_mm=({min_tile_w_mm:.1f}x{min_tile_h_mm:.1f}), " + f"kachel_avg_mm=({avg_tile_w_mm:.1f}x{avg_tile_h_mm:.1f}), " + f"kachel_max_mm=({max_tile_w_mm:.1f}x{max_tile_h_mm:.1f})" + ) + + kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get( + kartenname_auswahl, "Atlas" + ) + thema = get_variable(VIEW_VAR, scope="project") or "" + vorlage_name, bestaetigt = self.pruefmanager.frage_text( + "Neue Atlasvorlage anlegen", + "Bezeichnung der Vorlage:", + default_text=f"{kartenname} Atlas", + ) + print(f"[TabBLogic] Atlas frage_text Ergebnis: name='{vorlage_name}', bestaetigt={bestaetigt}") + if not bestaetigt: + return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": anzahl_seiten} + + vorlage_name = (vorlage_name or "").strip() or f"{kartenname} Atlas" + + try: + print( + f"[TabBLogic] Rufe create_atlas_layout auf: name='{vorlage_name}', " + f"page=({atlas_page_w:.1f}x{atlas_page_h:.1f}), " + f"map=({atlas_map_w:.1f}x{atlas_map_h:.1f}), massstab={massstab_zahl}, thema='{thema}'" + ) + Layout().create_atlas_layout( + name=vorlage_name, + page_width_mm=atlas_page_w, + page_height_mm=atlas_page_h, + map_width_mm=atlas_map_w, + map_height_mm=atlas_map_h, + extent=extent, + plotmassstab=massstab_zahl, + atlas_layer=atlas_layer, + thema=thema, + ) + print("[TabBLogic] create_atlas_layout erfolgreich abgeschlossen") + except ValueError as exc: + self.pruefmanager.zeige_hinweis("Atlasvorlage anlegen", str(exc)) + return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} + except Exception as exc: + self.pruefmanager.zeige_hinweis("Atlasvorlage anlegen", f"Die Atlasvorlage konnte nicht angelegt werden: {exc}") + return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0} + return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": anzahl_seiten} \ No newline at end of file