From 5d7bababd19bb20488c7a9d78e21d19e314130b9 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 23 Mar 2026 10:43:37 +0100 Subject: [PATCH 1/8] Objekte als Templates angelegt --- ui/layout.py | 216 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 138 insertions(+), 78 deletions(-) diff --git a/ui/layout.py b/ui/layout.py index 4aa52c8..2c145fe 100644 --- a/ui/layout.py +++ b/ui/layout.py @@ -26,9 +26,109 @@ MM = QgsUnitTypes.LayoutMillimeters class Layout: """Erzeugt ein QGIS-Layout für den Druck.""" + _OBJECT_TEMPLATES = { + "hauptkarte": { + "id": "Hauptkarte", + }, + "quellenangabe": { + "id": "Quellenangabe", + "size_mm": (180.0, 100.0), + "font": ("Arial", 12), + }, + "seitenzahl": { + "id": "Seitenzahl", + "size_mm": (60.0, 8.0), + "font": ("Arial", 12), + }, + } + + _QUELLENANGABE_TEXT = ( + "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
" + ) + + _SEITENZAHL_TEXT = "Seite [% attribute(@atlas_feature, 'Seitenzahl') %] von [% @atlas_totalfeatures %]" + def __init__(self, project: Any | None = None) -> None: self.project = project or QgsProject.instance() + def _create_map_item( + self, + layout: Any, + template_name: str, + x_mm: float, + y_mm: float, + width_mm: float, + height_mm: float, + ) -> Any: + template = self._OBJECT_TEMPLATES[template_name] + map_item = QgsLayoutItemMap(layout) + map_item.setId(template["id"]) + layout.addLayoutItem(map_item) + map_item.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM)) + map_item.attemptResize(QgsLayoutSize(width_mm, height_mm, MM)) + return map_item + + def _create_label_item( + self, + layout: Any, + template_name: str, + text: str, + x_mm: float, + y_mm: float, + *, + font_name: str | None = None, + font_size: int | None = None, + width_mm: float | None = None, + height_mm: float | None = None, + html_mode: bool = False, + expression_enabled: bool = False, + ) -> Any: + template = self._OBJECT_TEMPLATES[template_name] + label = QgsLayoutItemLabel(layout) + label.setId(template["id"]) + label.setText(text) + + if html_mode: + set_mode = getattr(label, "setMode", None) + mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None) + if callable(set_mode) and mode_html is not None: + set_mode(mode_html) + + if expression_enabled: + set_expr_enabled = getattr(label, "setExpressionEnabled", None) + if callable(set_expr_enabled): + try: + set_expr_enabled(True) + except Exception: + pass + + template_font_name, template_font_size = template.get("font", ("Arial", 12)) + label.setFont(QFont(font_name or template_font_name, font_size or template_font_size)) + + set_reference_point = getattr(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) + + template_width, template_height = template.get("size_mm", (0.0, 0.0)) + label.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM)) + label.attemptResize( + QgsLayoutSize( + width_mm if width_mm is not None else template_width, + height_mm if height_mm is not None else template_height, + MM, + ) + ) + layout.addLayoutItem(label) + return label + def create_single_page_layout( self, name: str, @@ -67,17 +167,16 @@ class Layout: f"[Layout] Kartenbild-Kanten: rechts={map_right_mm:.1f} mm, unten={map_bottom_mm:.1f} mm" ) - hauptkarte = QgsLayoutItemMap(layout) - hauptkarte.setId("Hauptkarte") + hauptkarte = self._create_map_item( + layout, + "hauptkarte", + map_left_mm, + map_top_mm, + map_width_mm, + map_height_mm, + ) print(f"[Layout] QgsLayoutItemMap erstellt") - - # Zum Layout hinzufügen, BEVOR Eigenschaften gesetzt werden - layout.addLayoutItem(hauptkarte) print("[Layout] Hauptkarte zum Layout hinzugefügt") - - # Position und Größe setzen - hauptkarte.attemptMove(QgsLayoutPoint(map_left_mm, map_top_mm, MM)) - hauptkarte.attemptResize(QgsLayoutSize(map_width_mm, map_height_mm, MM)) print(f"[Layout] Position und Größe gesetzt: pos=({map_left_mm}, {map_top_mm}), size=({map_width_mm:.1f}x{map_height_mm:.1f})") # Extent und Maßstab setzen @@ -164,34 +263,20 @@ class Layout: print(f"[Layout] Hauptkarte vollständig konfiguriert") - quellenangabe = QgsLayoutItemLabel(layout) - quellenangabe.setId("Quellenangabe") - quellenangabe.setText( - "Quelle Geobasisdaten: GeoSN, " - "dl-de/by-2-0

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

" - "Basemap:

" - "© GeoBasis-DE / BKG ([%year($now)%]) " - "CC BY 4.0 " - "mit teilweise angepasster Signatur
" - ) - set_mode = getattr(quellenangabe, "setMode", None) mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None) print(f"[Layout] QgsLayoutItemLabel.ModeHtml={mode_html!r}") - if callable(set_mode) and mode_html is not None: - set_mode(mode_html) - quellenangabe.setFont(QFont("Arial", 12)) - set_reference_point = getattr(quellenangabe, "setReferencePoint", None) lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None) print(f"[Layout] QgsLayoutItem.ReferencePoint.LowerLeft={lower_left!r}") - if callable(set_reference_point) and lower_left is not None: - set_reference_point(lower_left) quellenangabe_x_mm = map_right_mm + 20.0 quellenangabe_y_mm = map_bottom_mm - 120.0 - quellenangabe.attemptMove(QgsLayoutPoint(quellenangabe_x_mm, quellenangabe_y_mm, MM)) - quellenangabe.attemptResize(QgsLayoutSize(180.0, 100.0, MM)) - layout.addLayoutItem(quellenangabe) + self._create_label_item( + layout, + "quellenangabe", + self._QUELLENANGABE_TEXT, + quellenangabe_x_mm, + quellenangabe_y_mm, + html_mode=True, + ) print("[Layout] Quellenangabe zum Layout hinzugefügt") layout_manager.addLayout(layout) @@ -238,11 +323,14 @@ class Layout: 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)) + hauptkarte = self._create_map_item( + layout, + "hauptkarte", + map_left_mm, + map_top_mm, + map_width_mm, + map_height_mm, + ) # Verifiziere mit Units-bewussten Methoden (rect() kann andere Units verwenden). actual_w = None @@ -361,51 +449,23 @@ class Layout: 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
" + self._create_label_item( + layout, + "quellenangabe", + self._QUELLENANGABE_TEXT, + map_right_mm + 5.0, + map_bottom_mm, + html_mode=True, ) - 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 %]" + self._create_label_item( + layout, + "seitenzahl", + self._SEITENZAHL_TEXT, + map_right_mm + 5.0, + map_bottom_mm - 2.0, + expression_enabled=True, ) - 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: -- 2.54.0 From 744c5e482a6dce06c19695f1159c0068b41a2c5d Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 23 Mar 2026 10:48:32 +0100 Subject: [PATCH 2/8] =?UTF-8?q?Formatvorlagen=20f=C3=BCr=20Text=20eingef?= =?UTF-8?q?=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/layout.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/ui/layout.py b/ui/layout.py index 2c145fe..8537be1 100644 --- a/ui/layout.py +++ b/ui/layout.py @@ -26,6 +26,24 @@ MM = QgsUnitTypes.LayoutMillimeters class Layout: """Erzeugt ein QGIS-Layout für den Druck.""" + _TEXT_FORMAT_TEMPLATES = { + "Format_1": { + "font_family": "Arial", + "font_size": 12, + "font_style": "normal", + }, + "Format_2": { + "font_family": "Arial", + "font_size": 12, + "font_style": "fett", + }, + "Format_3": { + "font_family": "Arial", + "font_size": 10, + "font_style": "normal", + }, + } + _OBJECT_TEMPLATES = { "hauptkarte": { "id": "Hauptkarte", @@ -33,12 +51,12 @@ class Layout: "quellenangabe": { "id": "Quellenangabe", "size_mm": (180.0, 100.0), - "font": ("Arial", 12), + "format": "Format_1", }, "seitenzahl": { "id": "Seitenzahl", "size_mm": (60.0, 8.0), - "font": ("Arial", 12), + "format": "Format_1", }, } @@ -83,8 +101,10 @@ class Layout: x_mm: float, y_mm: float, *, + text_format: str | None = None, font_name: str | None = None, font_size: int | None = None, + font_style: str | None = None, width_mm: float | None = None, height_mm: float | None = None, html_mode: bool = False, @@ -109,8 +129,16 @@ class Layout: except Exception: pass - template_font_name, template_font_size = template.get("font", ("Arial", 12)) - label.setFont(QFont(font_name or template_font_name, font_size or template_font_size)) + format_name = text_format or template.get("format", "Format_1") + format_template = self._TEXT_FORMAT_TEMPLATES.get(format_name, self._TEXT_FORMAT_TEMPLATES["Format_1"]) + resolved_font_name = font_name or format_template["font_family"] + resolved_font_size = font_size or format_template["font_size"] + resolved_font_style = font_style or format_template["font_style"] + + font = QFont(resolved_font_name, resolved_font_size) + if hasattr(font, "setBold"): + font.setBold(resolved_font_style == "fett") + label.setFont(font) set_reference_point = getattr(label, "setReferencePoint", None) lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None) -- 2.54.0 From ed1e8cf28b34b903e756a177eb8abdd9f63b79b1 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 23 Mar 2026 11:08:25 +0100 Subject: [PATCH 3/8] crash beim speichern und beenden (themen-Auswahl) behoben --- ui/layout.py | 11 ++++++++--- ui/tab_b_ui.py | 25 +++++++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/ui/layout.py b/ui/layout.py index 8537be1..ad87388 100644 --- a/ui/layout.py +++ b/ui/layout.py @@ -58,14 +58,19 @@ class Layout: "size_mm": (60.0, 8.0), "format": "Format_1", }, + "textfeld": { + "id": "Textfeld", + "size_mm": (60.0, 8.0), + "format": "Format_1", + }, } _QUELLENANGABE_TEXT = ( "Quelle Geobasisdaten: GeoSN, " - "dl-de/by-2-0

" + "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:

" + "Sächsischen Landesamtes für Umwelt, Landwirtschaft und Geologie
" + "Basemap:" "© GeoBasis-DE / BKG ([%year($now)%]) " "CC BY 4.0 " "mit teilweise angepasster Signatur
" diff --git a/ui/tab_b_ui.py b/ui/tab_b_ui.py index 3f1f98b..99cd82c 100644 --- a/ui/tab_b_ui.py +++ b/ui/tab_b_ui.py @@ -335,17 +335,22 @@ class TabB(QWidget): def _disconnect_theme_collection_signals(self) -> None: """Trennt Signale der alten Theme-Collection sauber.""" collection = self._connected_theme_collection - if collection is None: - return - for signal_name in ("mapThemesChanged", "changed", "themeChanged"): - signal = getattr(collection, signal_name, None) - if signal is None: - continue - try: - signal.disconnect(self._refresh_thema_combo_live) - except Exception: - pass + if collection is not None: + for signal_name in ("mapThemesChanged", "changed", "themeChanged"): + try: + signal = getattr(collection, signal_name, None) + except RuntimeError: + break + except Exception: + continue + if signal is None: + continue + try: + signal.disconnect(self._refresh_thema_combo_live) + except Exception: + pass self._connected_theme_collection = None + self._theme_signal_connected = False def _connect_theme_collection_signals(self) -> None: """Verbindet Signale der Theme-Collection für Live-Aktualisierung der Themenliste.""" -- 2.54.0 From 57bf12782b3101507bc27b4194dc42ccc0d086f0 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 23 Mar 2026 12:58:59 +0100 Subject: [PATCH 4/8] =?UTF-8?q?A4=20als=20Option=20entfernt,=20Objekterg?= =?UTF-8?q?=C3=A4nzung=20im=20Layout=20als=20Einzeiler=20angelegt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/layout.py | 165 +++++++++++++++++++++++++++++++++++----------- ui/tab_b_logic.py | 1 - 2 files changed, 127 insertions(+), 39 deletions(-) diff --git a/ui/layout.py b/ui/layout.py index ad87388..f82192c 100644 --- a/ui/layout.py +++ b/ui/layout.py @@ -28,17 +28,17 @@ class Layout: _TEXT_FORMAT_TEMPLATES = { "Format_1": { - "font_family": "Arial", - "font_size": 12, + "font_family": "New Helvetica roman", + "font_size": 10, "font_style": "normal", }, "Format_2": { - "font_family": "Arial", - "font_size": 12, + "font_family": "New Helvetica roman", + "font_size": 14, "font_style": "fett", }, "Format_3": { - "font_family": "Arial", + "font_family": "New Helvetica roman", "font_size": 10, "font_style": "normal", }, @@ -62,6 +62,8 @@ class Layout: "id": "Textfeld", "size_mm": (60.0, 8.0), "format": "Format_1", + "html_mode": True, + "text": "", }, } @@ -80,6 +82,7 @@ class Layout: def __init__(self, project: Any | None = None) -> None: self.project = project or QgsProject.instance() + self._current_layout: Any | None = None def _create_map_item( self, @@ -98,6 +101,86 @@ class Layout: map_item.attemptResize(QgsLayoutSize(width_mm, height_mm, MM)) return map_item + def add_text_label( + self, + text: str, + x_mm: float, + y_mm: float, + template_name: str = "textfeld", + text_format: str | None = None, + width_mm: float | None = None, + height_mm: float | None = None, + html_mode: bool = False, + expression_enabled: bool = False, + ) -> Any: + """Einfache öffentliche Methode zum Hinzufügen eines Text-Labels. + + Beispiel: add_text_label("Ausgabedatum: 2026-03-23", 100, 200, text_format="Format_1") + """ + if self._current_layout is None: + raise RuntimeError("Kein Layout aktiv. Bitte create_single_page_layout() oder create_atlas_layout() aufrufen.") + return self._create_label_item( + self._current_layout, + template_name, + text, + x_mm, + y_mm, + text_format=text_format, + width_mm=width_mm, + height_mm=height_mm, + html_mode=html_mode, + expression_enabled=expression_enabled, + ) + + def _setup_layout_objects(self, kartenfenster_untere_rechte_ecke_mm: tuple, is_atlas: bool = False) -> None: + """Zentrale Methode zum Hinzufügen aller Standard-Objekte ins Layout. + + Diese Methode wird sowohl von create_single_page_layout() als auch von create_atlas_layout() aufgerufen. + + Args: + kartenfenster_untere_rechte_ecke_mm: Tupel (x_mm, y_mm) der unteren rechten Kartenecke + is_atlas: True für Atlas-Layout, False für Einzelblatt-Layout + """ + if self._current_layout is None: + raise RuntimeError("Kein Layout aktiv.") + + mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None) + print(f"[Layout] QgsLayoutItemLabel.ModeHtml={mode_html!r}") + lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None) + print(f"[Layout] QgsLayoutItem.ReferencePoint.LowerLeft={lower_left!r}") + + # Quellenangabe: unterschiedliche Positionen für Einzelblatt vs. Atlas + if is_atlas: + quellenangabe_x = kartenfenster_untere_rechte_ecke_mm[0] + 5.0 + quellenangabe_y = kartenfenster_untere_rechte_ecke_mm[1] + else: + quellenangabe_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0 + quellenangabe_y = kartenfenster_untere_rechte_ecke_mm[1] - 120.0 + + self._create_label_item( + self._current_layout, + "quellenangabe", + self._QUELLENANGABE_TEXT, + quellenangabe_x, + quellenangabe_y, + html_mode=True, + ) + print("[Layout] Quellenangabe zum Layout hinzugefügt") + + # Seitenzahl nur bei Atlas-Layout + if is_atlas: + seitenzahl_x = kartenfenster_untere_rechte_ecke_mm[0] + 5.0 + seitenzahl_y = kartenfenster_untere_rechte_ecke_mm[1] - 2.0 + self._create_label_item( + self._current_layout, + "seitenzahl", + self._SEITENZAHL_TEXT, + seitenzahl_x, + seitenzahl_y, + expression_enabled=True, + ) + print("[Layout] Seitenzahl zum Layout hinzugefügt") + def _create_label_item( self, layout: Any, @@ -186,6 +269,7 @@ class Layout: layout = QgsPrintLayout(self.project) layout.initializeDefaults() layout.setName(name) + self._current_layout = layout print(f"[Layout] QgsPrintLayout erstellt: {layout!r}") page = layout.pageCollection().page(0) @@ -196,6 +280,7 @@ class Layout: map_top_mm = 10.0 map_right_mm = map_left_mm + map_width_mm map_bottom_mm = map_top_mm + map_height_mm + kartenfenster_untere_rechte_ecke_mm = (map_right_mm, map_bottom_mm) print( f"[Layout] Kartenbild-Kanten: rechts={map_right_mm:.1f} mm, unten={map_bottom_mm:.1f} mm" ) @@ -295,25 +380,40 @@ class Layout: print(f"[Layout] WARN: setRefreshStrategy fehlgeschlagen: {exc}") print(f"[Layout] Hauptkarte vollständig konfiguriert") + + self._setup_layout_objects(kartenfenster_untere_rechte_ecke_mm, is_atlas=False) + + #Kartenschild-Box - mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None) - print(f"[Layout] QgsLayoutItemLabel.ModeHtml={mode_html!r}") - lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None) - print(f"[Layout] QgsLayoutItem.ReferencePoint.LowerLeft={lower_left!r}") - quellenangabe_x_mm = map_right_mm + 20.0 - quellenangabe_y_mm = map_bottom_mm - 120.0 - self._create_label_item( - layout, - "quellenangabe", - self._QUELLENANGABE_TEXT, - quellenangabe_x_mm, - quellenangabe_y_mm, - html_mode=True, - ) - print("[Layout] Quellenangabe zum Layout hinzugefügt") + #Kartenschild-Linien + #Kartenschild-Text + #Herausgeber + + #Bezeichnung_Flurbereinigung + + #Name Flurbereinigung + + #Stadt_Gemeinde + + #Landkreis + + #Kartenname + + #Ergänzung(Seitenzahl) + + #gefertigt + + #geprüft + + #Maßstab + + #Ausgabedatum + + #Aktenzeichen layout_manager.addLayout(layout) print("[Layout] Layout zum LayoutManager hinzugefügt") + self._current_layout = None open_layout_designer(layout) print("[Layout] Layout Designer geöffnet") return layout @@ -339,6 +439,7 @@ class Layout: layout = QgsPrintLayout(self.project) layout.initializeDefaults() layout.setName(name) + self._current_layout = layout page = layout.pageCollection().page(0) page.setPageSize(QgsLayoutSize(page_width_mm, page_height_mm, MM)) @@ -355,6 +456,7 @@ class Layout: map_top_mm = 10.0 map_right_mm = map_left_mm + map_width_mm map_bottom_mm = map_top_mm + map_height_mm + kartenfenster_untere_rechte_ecke_mm = (map_right_mm, map_bottom_mm) hauptkarte = self._create_map_item( layout, @@ -481,24 +583,8 @@ class Layout: # 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") - - self._create_label_item( - layout, - "quellenangabe", - self._QUELLENANGABE_TEXT, - map_right_mm + 5.0, - map_bottom_mm, - html_mode=True, - ) - - self._create_label_item( - layout, - "seitenzahl", - self._SEITENZAHL_TEXT, - map_right_mm + 5.0, - map_bottom_mm - 2.0, - expression_enabled=True, - ) + + self._setup_layout_objects(kartenfenster_untere_rechte_ecke_mm, is_atlas=True) atlas = layout.atlas() if atlas is not None: @@ -520,5 +606,8 @@ class Layout: set_page_name("attribute(@atlas_feature, 'Seitenzahl')") layout_manager.addLayout(layout) + print("[Layout] Layout zum LayoutManager hinzugefügt") + self._current_layout = None open_layout_designer(layout) + print("[Layout] Layout Designer geöffnet") return layout diff --git a/ui/tab_b_logic.py b/ui/tab_b_logic.py index a1d1b35..ad16225 100644 --- a/ui/tab_b_logic.py +++ b/ui/tab_b_logic.py @@ -53,7 +53,6 @@ DIN_GROESSEN: dict[str, tuple[int, int]] = { "DIN A1": (594, 841), "DIN A2": (420, 594), "DIN A3": (297, 420), - "DIN A4": (210, 297), } DIN_STANDARD = "DIN A0" -- 2.54.0 From 693824f6020045dfb43257a61beab30215c05052 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 23 Mar 2026 13:01:25 +0100 Subject: [PATCH 5/8] =?UTF-8?q?A4=20doch=20wieder=20eingef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/tab_b_logic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/tab_b_logic.py b/ui/tab_b_logic.py index ad16225..a1d1b35 100644 --- a/ui/tab_b_logic.py +++ b/ui/tab_b_logic.py @@ -53,6 +53,7 @@ DIN_GROESSEN: dict[str, tuple[int, int]] = { "DIN A1": (594, 841), "DIN A2": (420, 594), "DIN A3": (297, 420), + "DIN A4": (210, 297), } DIN_STANDARD = "DIN A0" -- 2.54.0 From 96da2ee56610da61c1f5415466303bb5f0868d1e Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 23 Mar 2026 14:25:14 +0100 Subject: [PATCH 6/8] =?UTF-8?q?Kartenschildbox=20und=20Linien=20hinzugef?= =?UTF-8?q?=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/layout.py | 184 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 153 insertions(+), 31 deletions(-) diff --git a/ui/layout.py b/ui/layout.py index f82192c..f688abf 100644 --- a/ui/layout.py +++ b/ui/layout.py @@ -4,6 +4,7 @@ sn_plan41/ui/layout.py – Aufbau von Drucklayouts für Plan41. from __future__ import annotations import math +import importlib from typing import Any from sn_basis.functions.qt_wrapper import QFont @@ -47,19 +48,23 @@ class Layout: _OBJECT_TEMPLATES = { "hauptkarte": { "id": "Hauptkarte", + "object_name": "Hauptkarte", }, "quellenangabe": { "id": "Quellenangabe", + "object_name": "Quellenangabe", "size_mm": (180.0, 100.0), "format": "Format_1", }, "seitenzahl": { "id": "Seitenzahl", + "object_name": "Seitenzahl", "size_mm": (60.0, 8.0), "format": "Format_1", }, "textfeld": { "id": "Textfeld", + "object_name": "Textfeld", "size_mm": (60.0, 8.0), "format": "Format_1", "html_mode": True, @@ -92,15 +97,54 @@ class Layout: y_mm: float, width_mm: float, height_mm: float, + object_name: str | None = None, ) -> Any: template = self._OBJECT_TEMPLATES[template_name] map_item = QgsLayoutItemMap(layout) map_item.setId(template["id"]) + set_object_name = getattr(map_item, "setObjectName", None) + if callable(set_object_name): + set_object_name(object_name or template.get("object_name", template["id"])) layout.addLayoutItem(map_item) map_item.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM)) map_item.attemptResize(QgsLayoutSize(width_mm, height_mm, MM)) return map_item + def _set_item_frame_stroke_width(self, item: Any, stroke_width_mm: float) -> None: + set_frame_stroke_width = getattr(item, "setFrameStrokeWidth", None) + if not callable(set_frame_stroke_width): + return + + try: + set_frame_stroke_width(stroke_width_mm) + return + except Exception: + pass + + try: + qgis_core = importlib.import_module("qgis.core") + QgsLayoutMeasurement = getattr(qgis_core, "QgsLayoutMeasurement", None) + except Exception: + QgsLayoutMeasurement = None + + if QgsLayoutMeasurement is not None: + try: + set_frame_stroke_width(QgsLayoutMeasurement(stroke_width_mm, MM)) + return + except Exception: + pass + + frame_stroke_width = getattr(item, "frameStrokeWidth", None) + if callable(frame_stroke_width): + try: + measurement = frame_stroke_width() + set_length = getattr(measurement, "setLength", None) + if callable(set_length): + set_length(stroke_width_mm) + set_frame_stroke_width(measurement) + except Exception: + pass + def add_text_label( self, text: str, @@ -112,6 +156,7 @@ class Layout: height_mm: float | None = None, html_mode: bool = False, expression_enabled: bool = False, + object_name: str | None = None, ) -> Any: """Einfache öffentliche Methode zum Hinzufügen eines Text-Labels. @@ -130,13 +175,14 @@ class Layout: height_mm=height_mm, html_mode=html_mode, expression_enabled=expression_enabled, + object_name=object_name, ) def _setup_layout_objects(self, kartenfenster_untere_rechte_ecke_mm: tuple, is_atlas: bool = False) -> None: """Zentrale Methode zum Hinzufügen aller Standard-Objekte ins Layout. Diese Methode wird sowohl von create_single_page_layout() als auch von create_atlas_layout() aufgerufen. - + Relatipunkt des Objekts ist die untere linke Ecke, berechnet von der unteren, rechten Ecke des Kartefensters Args: kartenfenster_untere_rechte_ecke_mm: Tupel (x_mm, y_mm) der unteren rechten Kartenecke is_atlas: True für Atlas-Layout, False für Einzelblatt-Layout @@ -166,7 +212,61 @@ class Layout: html_mode=True, ) print("[Layout] Quellenangabe zum Layout hinzugefügt") + # Kartenschild-Box, -Linien und -Text werden hier ergänzt (TODO) + #Kartenschild-Box + kartenschild_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0 + kartenschild_y = kartenfenster_untere_rechte_ecke_mm[1] + self._create_rectangle_box_item( + self._current_layout, + "Kartenschild", + kartenschild_x, + kartenschild_y, + 180.0, + 92.0, + stroke_width_mm= 1.0, + ) + print("[Layout] Kartenschild-Box zum Layout hinzugefügt") + #Kartenschild-Linien + kartenschild_line_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0 + kartenschild_line_offsets_y = (-10.0, -20.0, -30.0, -46.0, -62.0, -82.0) + for index, offset_y in enumerate(kartenschild_line_offsets_y, start=1): + self._create_rectangle_box_item( + self._current_layout, + f"Kartenschild_Linie_{index}", + kartenschild_line_x, + kartenfenster_untere_rechte_ecke_mm[1] + offset_y, + 180.0, + 0.0, + stroke_width_mm=0.1, + ) + print("[Layout] 6 Kartenschild-Linien zum Layout hinzugefügt") + + #Kartenschild-Text + #Herausgeber + self.add_text_label("@Amt", 10.0, -92.0, text_format="Format_1", object_name="Herausgeber") + #Bezeichnung_Flurbereinigung + + #Name Flurbereinigung + + #Stadt_Gemeinde + + #Landkreis + + #Kartenname + + #Ergänzung(Seitenzahl) + + #gefertigt + + #geprüft + + #Maßstab + + #Ausgabedatum + + #Aktenzeichen + # Seitenzahl nur bei Atlas-Layout if is_atlas: seitenzahl_x = kartenfenster_untere_rechte_ecke_mm[0] + 5.0 @@ -197,10 +297,14 @@ class Layout: height_mm: float | None = None, html_mode: bool = False, expression_enabled: bool = False, + object_name: str | None = None, ) -> Any: template = self._OBJECT_TEMPLATES[template_name] label = QgsLayoutItemLabel(layout) label.setId(template["id"]) + set_object_name = getattr(label, "setObjectName", None) + if callable(set_object_name): + set_object_name(object_name or template.get("object_name", template["id"])) label.setText(text) if html_mode: @@ -245,6 +349,52 @@ class Layout: layout.addLayoutItem(label) return label + def _create_rectangle_box_item( + self, + layout: Any, + object_name: str, + x_mm: float, + y_mm: float, + width_mm: float, + height_mm: float, + stroke_width_mm: float = 0.3, + ) -> Any: + box = QgsLayoutItemLabel(layout) + box.setId(object_name) + + set_object_name = getattr(box, "setObjectName", None) + if callable(set_object_name): + set_object_name(object_name) + + box.setText("") + + set_reference_point = getattr(box, "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) + + box.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM)) + box.attemptResize(QgsLayoutSize(width_mm, height_mm, MM)) + + set_frame_enabled = getattr(box, "setFrameEnabled", None) + if callable(set_frame_enabled): + try: + set_frame_enabled(True) + except Exception: + pass + + self._set_item_frame_stroke_width(box, stroke_width_mm) + + set_background_enabled = getattr(box, "setBackgroundEnabled", None) + if callable(set_background_enabled): + try: + set_background_enabled(False) + except Exception: + pass + + layout.addLayoutItem(box) + return box + def create_single_page_layout( self, name: str, @@ -335,7 +485,7 @@ class Layout: set_frame_stroke_width = getattr(hauptkarte, "setFrameStrokeWidth", None) if callable(set_frame_stroke_width): try: - set_frame_stroke_width(0.5) + self._set_item_frame_stroke_width(hauptkarte, 0.5) print(f"[Layout] Rahmenstrichbreite auf 0.5 mm gesetzt") except Exception as exc: print(f"[Layout] WARN: setFrameStrokeWidth(0.5) fehlgeschlagen: {exc}") @@ -383,34 +533,6 @@ class Layout: self._setup_layout_objects(kartenfenster_untere_rechte_ecke_mm, is_atlas=False) - #Kartenschild-Box - - #Kartenschild-Linien - - #Kartenschild-Text - #Herausgeber - - #Bezeichnung_Flurbereinigung - - #Name Flurbereinigung - - #Stadt_Gemeinde - - #Landkreis - - #Kartenname - - #Ergänzung(Seitenzahl) - - #gefertigt - - #geprüft - - #Maßstab - - #Ausgabedatum - - #Aktenzeichen layout_manager.addLayout(layout) print("[Layout] Layout zum LayoutManager hinzugefügt") self._current_layout = None @@ -531,7 +653,7 @@ class Layout: set_frame_stroke_width = getattr(hauptkarte, "setFrameStrokeWidth", None) if callable(set_frame_stroke_width): try: - set_frame_stroke_width(0.5) + self._set_item_frame_stroke_width(hauptkarte, 0.5) except Exception: pass -- 2.54.0 From abbd1f908f9f471822c39b419284b1625dd979c5 Mon Sep 17 00:00:00 2001 From: daniel Date: Tue, 24 Mar 2026 08:08:42 +0100 Subject: [PATCH 7/8] =?UTF-8?q?Kartenschild=20und=20Legende=20hinzugef?= =?UTF-8?q?=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/layout.py | 528 ++++++++++++++++++++++++++++++++++++++++++---- ui/tab_b_logic.py | 22 +- ui/tab_b_ui.py | 10 +- 3 files changed, 507 insertions(+), 53 deletions(-) diff --git a/ui/layout.py b/ui/layout.py index f688abf..52d1ed9 100644 --- a/ui/layout.py +++ b/ui/layout.py @@ -11,6 +11,7 @@ from sn_basis.functions.qt_wrapper import QFont from sn_basis.functions.qgiscore_wrapper import ( QgsLayoutItem, QgsLayoutItemLabel, + QgsLayoutItemLegend, QgsLayoutItemMap, QgsLayoutPoint, QgsLayoutSize, @@ -29,19 +30,19 @@ class Layout: _TEXT_FORMAT_TEMPLATES = { "Format_1": { - "font_family": "New Helvetica roman", + "font_family": "Arial", "font_size": 10, "font_style": "normal", }, "Format_2": { - "font_family": "New Helvetica roman", - "font_size": 14, - "font_style": "fett", + "font_family": "Arial", + "font_size": 10, + "font_style": "bold", }, "Format_3": { - "font_family": "New Helvetica roman", - "font_size": 10, - "font_style": "normal", + "font_family": "Arial", + "font_size": 14, + "font_style": "bold", }, } @@ -70,6 +71,11 @@ class Layout: "html_mode": True, "text": "", }, + "legende": { + "id": "Legende", + "object_name": "Legende", + "size_mm": (180.0, 60.0), + }, } _QUELLENANGABE_TEXT = ( @@ -83,7 +89,7 @@ class Layout: "mit teilweise angepasster Signatur
" ) - _SEITENZAHL_TEXT = "Seite [% attribute(@atlas_feature, 'Seitenzahl') %] von [% @atlas_totalfeatures %]" + _SEITENZAHL_TEXT = "Blatt [% attribute(@atlas_feature, 'Seitenzahl') %] von [% @atlas_totalfeatures %]" def __init__(self, project: Any | None = None) -> None: self.project = project or QgsProject.instance() @@ -178,7 +184,14 @@ class Layout: object_name=object_name, ) - def _setup_layout_objects(self, kartenfenster_untere_rechte_ecke_mm: tuple, is_atlas: bool = False) -> None: + def _setup_layout_objects( + self, + kartenfenster_untere_rechte_ecke_mm: tuple, + hauptkarte: Any, + map_top_mm: float, + map_height_mm: float, + is_atlas: bool = False, + ) -> None: """Zentrale Methode zum Hinzufügen aller Standard-Objekte ins Layout. Diese Methode wird sowohl von create_single_page_layout() als auch von create_atlas_layout() aufgerufen. @@ -189,33 +202,72 @@ class Layout: """ if self._current_layout is None: raise RuntimeError("Kein Layout aktiv.") + + def _coords_rel_untere_rechte_ecke(rel_x_mm: float, rel_y_mm: float) -> tuple[float, float]: + return ( + kartenfenster_untere_rechte_ecke_mm[0] + rel_x_mm, + kartenfenster_untere_rechte_ecke_mm[1] + rel_y_mm, + ) mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None) print(f"[Layout] QgsLayoutItemLabel.ModeHtml={mode_html!r}") lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None) print(f"[Layout] QgsLayoutItem.ReferencePoint.LowerLeft={lower_left!r}") - - # Quellenangabe: unterschiedliche Positionen für Einzelblatt vs. Atlas - if is_atlas: - quellenangabe_x = kartenfenster_untere_rechte_ecke_mm[0] + 5.0 - quellenangabe_y = kartenfenster_untere_rechte_ecke_mm[1] - else: - quellenangabe_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0 - quellenangabe_y = kartenfenster_untere_rechte_ecke_mm[1] - 120.0 - - self._create_label_item( + + legende_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0 + legende_height_mm = max(map_height_mm - 122.0, 20.0) + legende_y = map_top_mm + legende = self._create_legend_item( self._current_layout, - "quellenangabe", + "legende", + hauptkarte, + legende_x, + legende_y, + width_mm=180.0, + height_mm=legende_height_mm, + object_name="Legende", + ) + + legende_hoehe_mm = self._configure_legend_size( + legende, + width_limit_mm=180.0, + column_count=1, + equal_column_width=False, + ) + + quellenangabe_x, quellenangabe_y = _coords_rel_untere_rechte_ecke(10.0, -102.0) + quellenangabe_hoehe_mm = 20.0 + quellenangabe_obere_y = quellenangabe_y - quellenangabe_hoehe_mm + legende_untere_y = legende_y + legende_hoehe_mm + + if legende_untere_y > quellenangabe_obere_y: + legende_hoehe_mm = self._configure_legend_size( + legende, + width_limit_mm=180.0, + column_count=2, + equal_column_width=True, + ) + print("[Layout] Legende überlappt Quellenangabe – auf 2 Spalten umgestellt") + + print("[Layout] Legende zum Layout hinzugefügt") + + # Quellenangabe: + + self.add_text_label( self._QUELLENANGABE_TEXT, quellenangabe_x, quellenangabe_y, + width_mm=180.0, + height_mm=20.0, + text_format="Format_1", html_mode=True, + expression_enabled=True, + object_name="Quellenangabe", ) print("[Layout] Quellenangabe zum Layout hinzugefügt") # Kartenschild-Box, -Linien und -Text werden hier ergänzt (TODO) #Kartenschild-Box - kartenschild_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0 - kartenschild_y = kartenfenster_untere_rechte_ecke_mm[1] + kartenschild_x, kartenschild_y = _coords_rel_untere_rechte_ecke(10.0, 0.0) self._create_rectangle_box_item( self._current_layout, "Kartenschild", @@ -223,19 +275,20 @@ class Layout: kartenschild_y, 180.0, 92.0, - stroke_width_mm= 1.0, + stroke_width_mm=1.0, ) print("[Layout] Kartenschild-Box zum Layout hinzugefügt") #Kartenschild-Linien - kartenschild_line_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0 + kartenschild_line_x, _ = _coords_rel_untere_rechte_ecke(10.0, 0.0) kartenschild_line_offsets_y = (-10.0, -20.0, -30.0, -46.0, -62.0, -82.0) for index, offset_y in enumerate(kartenschild_line_offsets_y, start=1): + _, line_y = _coords_rel_untere_rechte_ecke(0.0, offset_y) self._create_rectangle_box_item( self._current_layout, f"Kartenschild_Linie_{index}", kartenschild_line_x, - kartenfenster_untere_rechte_ecke_mm[1] + offset_y, + line_y, 180.0, 0.0, stroke_width_mm=0.1, @@ -244,33 +297,147 @@ class Layout: #Kartenschild-Text #Herausgeber - self.add_text_label("@Amt", 10.0, -92.0, text_format="Format_1", object_name="Herausgeber") + herausgeber_x, herausgeber_y = _coords_rel_untere_rechte_ecke(16.0, -85.0) + self.add_text_label( + "[% @sn_amt %]", + herausgeber_x, + herausgeber_y, + width_mm=171.0, + height_mm=4.0, + text_format="Format_1", + expression_enabled=True, + object_name="Herausgeber", + ) #Bezeichnung_Flurbereinigung + bezeichnung_flurbereinigung_x, bezeichnung_flurbereinigung_y = _coords_rel_untere_rechte_ecke(16.0, -75.0) + self.add_text_label( + "[% @sn_bezeichnung %]", + bezeichnung_flurbereinigung_x, + bezeichnung_flurbereinigung_y, + width_mm=171.0, + height_mm=4.0, + text_format="Format_1", + expression_enabled=True, + object_name="Bezeichnung_Flurbereinigung", + ) #Name Flurbereinigung + name_flurbereinigung_x, name_flurbereinigung_y = _coords_rel_untere_rechte_ecke(16.0, -65.0) + self.add_text_label( + "[% @sn_name %]", + name_flurbereinigung_x, + name_flurbereinigung_y, + width_mm=171.0, + height_mm=5.0, + text_format="Format_3", + expression_enabled=True, + object_name="Name_Flurbereinigung", + ) #Stadt_Gemeinde + stadt_gemeinde_x, stadt_gemeinde_y = _coords_rel_untere_rechte_ecke(16.0, -57.0) + self.add_text_label( + "[% @sn_gemeinden %]", + stadt_gemeinde_x, + stadt_gemeinde_y, + width_mm=171.0, + height_mm=4.0, + text_format="Format_1", + expression_enabled=True, + object_name="Stadt_Gemeinde", + ) #Landkreis + landkreis_x, landkreis_y = _coords_rel_untere_rechte_ecke(16.0, -49.0) + self.add_text_label( + "[% @sn_landkreise_proj %]", + landkreis_x, + landkreis_y, + width_mm=171.0, + height_mm=4.0, + text_format="Format_1", + expression_enabled=True, + object_name="Landkreis", + ) #Kartenname + kartenname_x, kartenname_y = _coords_rel_untere_rechte_ecke(16.0, -33.0) + self.add_text_label( + "[% @sn_kartenname %]", + kartenname_x, + kartenname_y, + width_mm=137.0, + height_mm=5.0, + text_format="Format_3", + expression_enabled=True, + object_name="Kartenname", + ) #Ergänzung(Seitenzahl) #gefertigt - + gefertigt_x, gefertigt_y = _coords_rel_untere_rechte_ecke(16.0, -23.0) + self.add_text_label( + "Gefertigt: ", + gefertigt_x, + gefertigt_y, + width_mm=81.0, + height_mm=4.0, + text_format="Format_1", + expression_enabled=True, + object_name="Gefertigt", + ) #geprüft - + geprüft_x, geprüft_y = _coords_rel_untere_rechte_ecke(101.0, -23.0) + self.add_text_label( + "Geprüft: ", + geprüft_x, + geprüft_y, + width_mm=81.0, + height_mm=4.0, + text_format="Format_1", + expression_enabled=True, + object_name="Geprüft", + ) #Maßstab - + maßstab_x, maßstab_y = _coords_rel_untere_rechte_ecke(16.0, -13.0) + self.add_text_label( + "Maßstab: 1 : [%round(map_get(item_variables('Hauptkarte'),'map_scale'),0)%]", + maßstab_x, + maßstab_y, + width_mm=81.0, + height_mm=4.0, + text_format="Format_1", + expression_enabled=True, + object_name="Maßstab", + ) #Ausgabedatum - + ausgabedatum_x, ausgabedatum_y = _coords_rel_untere_rechte_ecke(101.0, -13.0) + self.add_text_label( + "Ausgabedatum: [%format_date(now(), 'dd.MM.yyyy')%]", + ausgabedatum_x, + ausgabedatum_y, + width_mm=81.0, + height_mm=4.0, + text_format="Format_1", + expression_enabled=True, + object_name="Ausgabedatum", + ) #Aktenzeichen - + aktenzeichen_x, aktenzeichen_y = _coords_rel_untere_rechte_ecke(16.0, -3.0) + self.add_text_label( + "Aktenzeichen: ", + aktenzeichen_x, + aktenzeichen_y, + width_mm=171.0, + height_mm=4.0, + text_format="Format_1", + expression_enabled=True, + object_name="Aktenzeichen", + ) # Seitenzahl nur bei Atlas-Layout if is_atlas: - seitenzahl_x = kartenfenster_untere_rechte_ecke_mm[0] + 5.0 - seitenzahl_y = kartenfenster_untere_rechte_ecke_mm[1] - 2.0 + seitenzahl_x, seitenzahl_y = _coords_rel_untere_rechte_ecke(166.0, -33.0) self._create_label_item( self._current_layout, "seitenzahl", @@ -301,10 +468,11 @@ class Layout: ) -> Any: template = self._OBJECT_TEMPLATES[template_name] label = QgsLayoutItemLabel(layout) - label.setId(template["id"]) + item_id = object_name or template["id"] + label.setId(item_id) set_object_name = getattr(label, "setObjectName", None) if callable(set_object_name): - set_object_name(object_name or template.get("object_name", template["id"])) + set_object_name(item_id) label.setText(text) if html_mode: @@ -346,9 +514,276 @@ class Layout: MM, ) ) + set_locked = getattr(label, "setLocked", None) + if callable(set_locked): + try: + set_locked(True) + except Exception: + pass layout.addLayoutItem(label) return label + def _resolve_visible_map_layers(self, map_item: Any) -> list[Any]: + layers_method = getattr(map_item, "layers", None) + if callable(layers_method): + try: + resolved_layers = layers_method() + if isinstance(resolved_layers, (list, tuple)): + layers = [layer for layer in resolved_layers if layer is not None] + if layers: + return layers + except Exception: + pass + + layer_tree_root_method = getattr(self.project, "layerTreeRoot", None) + if not callable(layer_tree_root_method): + return [] + + try: + root = layer_tree_root_method() + except Exception: + return [] + + find_layers = getattr(root, "findLayers", None) + if not callable(find_layers): + return [] + + visible_layers: list[Any] = [] + try: + tree_layers = find_layers() + if not isinstance(tree_layers, (list, tuple)): + return [] + for tree_layer in tree_layers: + is_visible = getattr(tree_layer, "itemVisibilityChecked", None) + if callable(is_visible) and not is_visible(): + continue + layer = getattr(tree_layer, "layer", None) + resolved_layer = layer() if callable(layer) else None + if resolved_layer is not None: + visible_layers.append(resolved_layer) + except Exception: + return [] + + return visible_layers + + def _create_legend_item( + self, + layout: Any, + template_name: str, + linked_map: Any, + x_mm: float, + y_mm: float, + *, + width_mm: float | None = None, + height_mm: float | None = None, + object_name: str | None = None, + ) -> Any: + template = self._OBJECT_TEMPLATES[template_name] + legend = QgsLayoutItemLegend(layout) + item_id = object_name or template["id"] + legend.setId(item_id) + + set_object_name = getattr(legend, "setObjectName", None) + if callable(set_object_name): + set_object_name(item_id) + + set_linked_map = getattr(legend, "setLinkedMap", None) + if callable(set_linked_map): + try: + set_linked_map(linked_map) + except Exception: + pass + + set_title = getattr(legend, "setTitle", None) + if callable(set_title): + try: + set_title("Legende") + except Exception: + pass + + set_auto_update_model = getattr(legend, "setAutoUpdateModel", None) + if callable(set_auto_update_model): + try: + set_auto_update_model(False) + except Exception: + pass + + set_filter_by_map = getattr(legend, "setLegendFilterByMapEnabled", None) + if callable(set_filter_by_map): + try: + set_filter_by_map(False) + except Exception: + pass + + model_method = getattr(legend, "model", None) + if callable(model_method): + try: + model = model_method() + root_group_method = getattr(model, "rootGroup", None) + root_group = root_group_method() if callable(root_group_method) else None + clear = getattr(root_group, "clear", None) + if callable(clear): + clear() + add_layer = getattr(root_group, "addLayer", None) + if callable(add_layer): + for layer in self._resolve_visible_map_layers(linked_map): + add_layer(layer) + except Exception: + pass + + set_reference_point = getattr(legend, "setReferencePoint", None) + upper_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "UpperLeft", None) + if callable(set_reference_point) and upper_left is not None: + set_reference_point(upper_left) + + template_width, template_height = template.get("size_mm", (0.0, 0.0)) + legend.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM)) + legend.attemptResize( + QgsLayoutSize( + width_mm if width_mm is not None else template_width, + height_mm if height_mm is not None else template_height, + MM, + ) + ) + + set_frame_enabled = getattr(legend, "setFrameEnabled", None) + if callable(set_frame_enabled): + try: + set_frame_enabled(True) + except Exception: + pass + + self._set_item_frame_stroke_width(legend, 0.3) + + set_background_enabled = getattr(legend, "setBackgroundEnabled", None) + if callable(set_background_enabled): + try: + set_background_enabled(False) + except Exception: + pass + + set_locked = getattr(legend, "setLocked", None) + if callable(set_locked): + try: + set_locked(True) + except Exception: + pass + + layout.addLayoutItem(legend) + + refresh = getattr(legend, "refresh", None) + if callable(refresh): + try: + refresh() + except Exception: + pass + + return legend + + def _get_item_size_mm(self, item: Any) -> tuple[float | None, float | None]: + def _to_float(value: Any) -> float | None: + if isinstance(value, (int, float)): + return float(value) + if isinstance(value, str): + try: + return float(value) + except Exception: + return None + return None + + size_with_units = getattr(item, "sizeWithUnits", None) + if callable(size_with_units): + try: + size = size_with_units() + get_width = getattr(size, "width", None) + get_height = getattr(size, "height", None) + width = _to_float(get_width()) if callable(get_width) else None + height = _to_float(get_height()) if callable(get_height) else None + if width is not None and height is not None: + return width, height + except Exception: + pass + + rect = getattr(item, "rect", None) + if callable(rect): + try: + item_rect = rect() + get_width = getattr(item_rect, "width", None) + get_height = getattr(item_rect, "height", None) + width = _to_float(get_width()) if callable(get_width) else None + height = _to_float(get_height()) if callable(get_height) else None + return width, height + except Exception: + pass + + return None, None + + def _configure_legend_size( + self, + legend: Any, + *, + width_limit_mm: float, + column_count: int, + equal_column_width: bool, + ) -> float: + set_locked = getattr(legend, "setLocked", None) + if callable(set_locked): + try: + set_locked(False) + except Exception: + pass + + set_column_count = getattr(legend, "setColumnCount", None) + if callable(set_column_count): + try: + set_column_count(column_count) + except Exception: + pass + + set_equal_column_width = getattr(legend, "setEqualColumnWidth", None) + if callable(set_equal_column_width): + try: + set_equal_column_width(equal_column_width) + except Exception: + pass + + refresh = getattr(legend, "refresh", None) + if callable(refresh): + try: + refresh() + except Exception: + pass + + adjust_box_size = getattr(legend, "adjustBoxSize", None) + if callable(adjust_box_size): + try: + adjust_box_size() + except Exception: + pass + + width_mm, height_mm = self._get_item_size_mm(legend) + resolved_width = width_mm if width_mm is not None else width_limit_mm + resolved_height = height_mm if height_mm is not None else 20.0 + final_width = min(resolved_width, width_limit_mm) + + legend.attemptResize(QgsLayoutSize(final_width, resolved_height, MM)) + + if callable(refresh): + try: + refresh() + except Exception: + pass + + _, updated_height_mm = self._get_item_size_mm(legend) + + if callable(set_locked): + try: + set_locked(True) + except Exception: + pass + + return updated_height_mm if updated_height_mm is not None else resolved_height + def _create_rectangle_box_item( self, layout: Any, @@ -392,6 +827,13 @@ class Layout: except Exception: pass + set_locked = getattr(box, "setLocked", None) + if callable(set_locked): + try: + set_locked(True) + except Exception: + pass + layout.addLayoutItem(box) return box @@ -531,7 +973,13 @@ class Layout: print(f"[Layout] Hauptkarte vollständig konfiguriert") - self._setup_layout_objects(kartenfenster_untere_rechte_ecke_mm, is_atlas=False) + self._setup_layout_objects( + kartenfenster_untere_rechte_ecke_mm, + hauptkarte, + map_top_mm, + map_height_mm, + is_atlas=False, + ) layout_manager.addLayout(layout) print("[Layout] Layout zum LayoutManager hinzugefügt") @@ -706,7 +1154,13 @@ class Layout: 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") - self._setup_layout_objects(kartenfenster_untere_rechte_ecke_mm, is_atlas=True) + self._setup_layout_objects( + kartenfenster_untere_rechte_ecke_mm, + hauptkarte, + map_top_mm, + map_height_mm, + is_atlas=True, + ) atlas = layout.atlas() if atlas is not None: diff --git a/ui/tab_b_logic.py b/ui/tab_b_logic.py index a1d1b35..986bae6 100644 --- a/ui/tab_b_logic.py +++ b/ui/tab_b_logic.py @@ -22,11 +22,11 @@ from sn_basis.modules.layerpruefer import Layerpruefer from sn_plan41.ui.layout import Layout -KARTENNAME_VAR = "sn_kartenname" -PLOTMASSSTAB_VAR = "sn_plotmassstab" -VIEW_VAR = "sn_view" -ZIELGROESSE_VAR = "sn_zielgroesse" -FORMFAKTOR_VAR = "sn_formfaktor" +KARTENNAME_VAR = "kartenname" +PLOTMASSSTAB_VAR = "plotmassstab" +VIEW_VAR = "view" +ZIELGROESSE_VAR = "zielgroesse" +FORMFAKTOR_VAR = "formfaktor" KARTENNAME_38 = "§38" KARTENNAME_41 = "§41" MASSSTAB_WIE_KARTENFENSTER = "Wie Kartenfenster" @@ -287,12 +287,12 @@ class TabBLogic: return loaded_layer def set_kartenname_for_auswahl(self, auswahl: str) -> None: - """Setzt die Projektvariable ``sn_kartenname`` anhand der Kartennamen-Auswahl.""" + """Setzt die Projektvariable ``kartenname`` anhand der Kartennamen-Auswahl.""" 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: - """Setzt die Projektvariable ``sn_plotmassstab`` anhand der Maßstabsauswahl.""" + """Setzt die Projektvariable ``plotmassstab`` anhand der Maßstabsauswahl.""" if auswahl == MASSSTAB_WIE_KARTENFENSTER: if aktueller_massstab and aktueller_massstab > 0: set_variable(PLOTMASSSTAB_VAR, str(int(round(aktueller_massstab))), scope="project") @@ -304,7 +304,7 @@ class TabBLogic: set_variable(PLOTMASSSTAB_VAR, value, scope="project") def set_view_for_auswahl(self, auswahl: str) -> None: - """Setzt ``sn_view`` auf ``aktuell`` oder den Namen des gewählten Layerthemas.""" + """Setzt die Projektvariable ``view`` auf ``aktuell`` oder den Namen des gewählten Layerthemas.""" if auswahl == THEMA_WIE_KARTENFENSTER: set_variable(VIEW_VAR, "aktuell", scope="project") return @@ -312,11 +312,11 @@ class TabBLogic: set_variable(VIEW_VAR, auswahl or "", scope="project") def set_zielgroesse_for_auswahl(self, auswahl: str) -> None: - """Setzt ``sn_zielgroesse`` auf den gewählten DIN-Namen.""" + """Setzt ``zielgroesse`` auf den gewählten DIN-Namen.""" set_variable(ZIELGROESSE_VAR, auswahl if auswahl in DIN_GROESSEN else DIN_STANDARD, scope="project") def set_formfaktor(self, endlosrolle: bool) -> None: - """Setzt ``sn_formfaktor`` auf ``Endlosrolle`` oder ``Blatt``.""" + """Setzt die Projektvariable ``formfaktor`` auf ``Endlosrolle`` oder ``Blatt``.""" set_variable(FORMFAKTOR_VAR, "Endlosrolle" if endlosrolle else "Blatt", scope="project") # ───────────────────────────────────────────────────────────────────────── @@ -358,7 +358,7 @@ class TabBLogic: return {"ok": False, "switch_to_tab_a": True, "atlas_seiten": 0} layer_id = getattr(layer, "id", lambda: "")() or "" - set_variable("sn_verfahrensgebietslayer", layer_id, scope="project") + set_variable("verfahrensgebietslayer", layer_id, scope="project") # ─── 2. Kartenname prüfen ───────────────────────────────────────── if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41): diff --git a/ui/tab_b_ui.py b/ui/tab_b_ui.py index 99cd82c..be1206f 100644 --- a/ui/tab_b_ui.py +++ b/ui/tab_b_ui.py @@ -206,7 +206,7 @@ class TabB(QWidget): self._endlosrolle_cb.setChecked(saved_formfaktor == "Endlosrolle") def _on_kartenname_changed(self, value: str) -> None: - """Persistiert die Kartennamen-Auswahl und setzt ``sn_kartenname``.""" + """Persistiert die Kartennamen-Auswahl und setzt ``kartenname``.""" if value in (KARTENNAME_38, KARTENNAME_41): set_variable(KARTENNAME_VAR, value, scope="project") else: @@ -216,26 +216,26 @@ class TabB(QWidget): self.logic.set_kartenname_for_auswahl(value) def _on_massstab_changed(self, value: str) -> None: - """Persistiert Maßstabsauswahl und setzt ``sn_plotmassstab``.""" + """Persistiert Maßstabsauswahl und setzt ``plotmassstab``.""" set_variable(MASSSTAB_VAR, value, scope="project") if self.logic: self.logic.set_plotmassstab_for_auswahl(value, self._get_current_canvas_scale()) def _on_thema_changed(self, value: str) -> None: - """Persistiert die Thema-Auswahl und setzt ``sn_view``.""" + """Persistiert die Thema-Auswahl und setzt ``view``.""" set_variable(THEMA_VAR, value, scope="project") if self.logic: self.logic.set_view_for_auswahl(value) def _on_zielgroesse_changed(self, value: str) -> None: - """Persistiert Blattgröße und setzt ``sn_zielgroesse``.""" + """Persistiert Blattgröße und setzt ``zielgroesse``.""" set_variable(ZIELGROESSE_UI_VAR, value, scope="project") if self.logic: self.logic.set_zielgroesse_for_auswahl(value) def _on_formfaktor_changed(self, state: int) -> None: - """Persistiert Endlosrolle-Zustand und setzt ``sn_formfaktor``.""" + """Persistiert Endlosrolle-Zustand und setzt ``formfaktor``.""" checked = bool(state) set_variable(FORMFAKTOR_UI_VAR, "Endlosrolle" if checked else "Blatt", scope="project") if self.logic: -- 2.54.0 From 9f623e9d247507007a9bb693b036ebf771a53499 Mon Sep 17 00:00:00 2001 From: daniel Date: Tue, 24 Mar 2026 16:30:15 +0100 Subject: [PATCH 8/8] Schriftart auf Arial gestellt, Positionen angepasst --- ui/layout.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ui/layout.py b/ui/layout.py index 52d1ed9..a2ec82f 100644 --- a/ui/layout.py +++ b/ui/layout.py @@ -31,17 +31,17 @@ class Layout: _TEXT_FORMAT_TEMPLATES = { "Format_1": { "font_family": "Arial", - "font_size": 10, + "font_size": 11, "font_style": "normal", }, "Format_2": { "font_family": "Arial", - "font_size": 10, + "font_size": 13, "font_style": "bold", }, "Format_3": { "font_family": "Arial", - "font_size": 14, + "font_size": 16, "font_style": "bold", }, } @@ -297,7 +297,7 @@ class Layout: #Kartenschild-Text #Herausgeber - herausgeber_x, herausgeber_y = _coords_rel_untere_rechte_ecke(16.0, -85.0) + herausgeber_x, herausgeber_y = _coords_rel_untere_rechte_ecke(16.0, -84.0) self.add_text_label( "[% @sn_amt %]", herausgeber_x, @@ -322,20 +322,20 @@ class Layout: ) #Name Flurbereinigung - name_flurbereinigung_x, name_flurbereinigung_y = _coords_rel_untere_rechte_ecke(16.0, -65.0) + name_flurbereinigung_x, name_flurbereinigung_y = _coords_rel_untere_rechte_ecke(16.0, -64.0) self.add_text_label( "[% @sn_name %]", name_flurbereinigung_x, name_flurbereinigung_y, width_mm=171.0, height_mm=5.0, - text_format="Format_3", + text_format="Format_2", expression_enabled=True, object_name="Name_Flurbereinigung", ) #Stadt_Gemeinde - stadt_gemeinde_x, stadt_gemeinde_y = _coords_rel_untere_rechte_ecke(16.0, -57.0) + stadt_gemeinde_x, stadt_gemeinde_y = _coords_rel_untere_rechte_ecke(16.0, -56.0) self.add_text_label( "[% @sn_gemeinden %]", stadt_gemeinde_x, @@ -348,7 +348,7 @@ class Layout: ) #Landkreis - landkreis_x, landkreis_y = _coords_rel_untere_rechte_ecke(16.0, -49.0) + landkreis_x, landkreis_y = _coords_rel_untere_rechte_ecke(16.0, -48.0) self.add_text_label( "[% @sn_landkreise_proj %]", landkreis_x, @@ -361,7 +361,7 @@ class Layout: ) #Kartenname - kartenname_x, kartenname_y = _coords_rel_untere_rechte_ecke(16.0, -33.0) + kartenname_x, kartenname_y = _coords_rel_untere_rechte_ecke(16.0, -34.0) self.add_text_label( "[% @sn_kartenname %]", kartenname_x, @@ -497,7 +497,9 @@ class Layout: font = QFont(resolved_font_name, resolved_font_size) if hasattr(font, "setBold"): - font.setBold(resolved_font_style == "fett") + font.setBold(resolved_font_style == "bold") + if hasattr(font, "setItalic"): + font.setItalic(resolved_font_style == "italic") label.setFont(font) set_reference_point = getattr(label, "setReferencePoint", None) -- 2.54.0