1192 lines
43 KiB
Python
1192 lines
43 KiB
Python
"""
|
||
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
|
||
from sn_basis.functions.qgiscore_wrapper import (
|
||
QgsLayoutItem,
|
||
QgsLayoutItemLabel,
|
||
QgsLayoutItemLegend,
|
||
QgsLayoutItemMap,
|
||
QgsLayoutPoint,
|
||
QgsLayoutSize,
|
||
QgsPrintLayout,
|
||
QgsProject,
|
||
QgsUnitTypes,
|
||
)
|
||
from sn_basis.functions.qgisui_wrapper import open_layout_designer
|
||
|
||
|
||
MM = QgsUnitTypes.LayoutMillimeters
|
||
|
||
|
||
class Layout:
|
||
"""Erzeugt ein QGIS-Layout für den Druck."""
|
||
|
||
_TEXT_FORMAT_TEMPLATES = {
|
||
"Format_1": {
|
||
"font_family": "Arial",
|
||
"font_size": 11,
|
||
"font_style": "normal",
|
||
},
|
||
"Format_2": {
|
||
"font_family": "Arial",
|
||
"font_size": 13,
|
||
"font_style": "bold",
|
||
},
|
||
"Format_3": {
|
||
"font_family": "Arial",
|
||
"font_size": 16,
|
||
"font_style": "bold",
|
||
},
|
||
}
|
||
|
||
_OBJECT_TEMPLATES = {
|
||
"hauptkarte": {
|
||
"id": "Hauptkarte",
|
||
"object_name": "Hauptkarte",
|
||
},
|
||
"quellenangabe": {
|
||
"id": "Quellenangabe",
|
||
"object_name": "Quellenangabe",
|
||
"size_mm": (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,
|
||
"text": "",
|
||
},
|
||
"legende": {
|
||
"id": "Legende",
|
||
"object_name": "Legende",
|
||
"size_mm": (180.0, 60.0),
|
||
},
|
||
}
|
||
|
||
_QUELLENANGABE_TEXT = (
|
||
"Quelle Geobasisdaten: GeoSN, "
|
||
"<a href=\"https://www.govdata.de/dl-de/by-2-0\">dl-de/by-2-0</a><br>"
|
||
"Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des "
|
||
"Sächsischen Landesamtes für Umwelt, Landwirtschaft und Geologie<br>"
|
||
"Basemap:"
|
||
"© GeoBasis-DE / <a href=\"https://www.bkg.bund.de\">BKG</a> ([%year($now)%]) "
|
||
"<a href=\"https://creativecommons.org/licences/by/4.0/\">CC BY 4.0</a> "
|
||
"mit teilweise angepasster Signatur<br>"
|
||
)
|
||
|
||
_SEITENZAHL_TEXT = "Blatt [% attribute(@atlas_feature, 'Seitenzahl') %] von [% @atlas_totalfeatures %]"
|
||
|
||
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,
|
||
layout: Any,
|
||
template_name: str,
|
||
x_mm: float,
|
||
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,
|
||
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,
|
||
object_name: str | None = None,
|
||
) -> 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,
|
||
object_name=object_name,
|
||
)
|
||
|
||
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.
|
||
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
|
||
"""
|
||
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}")
|
||
|
||
legende_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0
|
||
legende_height_mm = max(map_height_mm - 122.0, 20.0)
|
||
legende_y = map_top_mm
|
||
legende = self._create_legend_item(
|
||
self._current_layout,
|
||
"legende",
|
||
hauptkarte,
|
||
legende_x,
|
||
legende_y,
|
||
width_mm=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, kartenschild_y = _coords_rel_untere_rechte_ecke(10.0, 0.0)
|
||
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, _ = _coords_rel_untere_rechte_ecke(10.0, 0.0)
|
||
kartenschild_line_offsets_y = (-10.0, -20.0, -30.0, -46.0, -62.0, -82.0)
|
||
for index, offset_y in enumerate(kartenschild_line_offsets_y, start=1):
|
||
_, line_y = _coords_rel_untere_rechte_ecke(0.0, offset_y)
|
||
self._create_rectangle_box_item(
|
||
self._current_layout,
|
||
f"Kartenschild_Linie_{index}",
|
||
kartenschild_line_x,
|
||
line_y,
|
||
180.0,
|
||
0.0,
|
||
stroke_width_mm=0.1,
|
||
)
|
||
print("[Layout] 6 Kartenschild-Linien zum Layout hinzugefügt")
|
||
|
||
#Kartenschild-Text
|
||
#Herausgeber
|
||
herausgeber_x, herausgeber_y = _coords_rel_untere_rechte_ecke(16.0, -84.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, -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_2",
|
||
expression_enabled=True,
|
||
object_name="Name_Flurbereinigung",
|
||
)
|
||
|
||
#Stadt_Gemeinde
|
||
stadt_gemeinde_x, stadt_gemeinde_y = _coords_rel_untere_rechte_ecke(16.0, -56.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, -48.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, -34.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, seitenzahl_y = _coords_rel_untere_rechte_ecke(166.0, -33.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,
|
||
template_name: str,
|
||
text: str,
|
||
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,
|
||
expression_enabled: bool = False,
|
||
object_name: str | None = None,
|
||
) -> Any:
|
||
template = self._OBJECT_TEMPLATES[template_name]
|
||
label = QgsLayoutItemLabel(layout)
|
||
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(item_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
|
||
|
||
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 == "bold")
|
||
if hasattr(font, "setItalic"):
|
||
font.setItalic(resolved_font_style == "italic")
|
||
label.setFont(font)
|
||
|
||
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,
|
||
)
|
||
)
|
||
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,
|
||
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
|
||
|
||
set_locked = getattr(box, "setLocked", None)
|
||
if callable(set_locked):
|
||
try:
|
||
set_locked(True)
|
||
except Exception:
|
||
pass
|
||
|
||
layout.addLayoutItem(box)
|
||
return box
|
||
|
||
def create_single_page_layout(
|
||
self,
|
||
name: str,
|
||
page_width_mm: float,
|
||
page_height_mm: float,
|
||
map_width_mm: float,
|
||
map_height_mm: float,
|
||
extent: Any,
|
||
plotmassstab: float,
|
||
thema: str = "",
|
||
) -> Any:
|
||
"""Erzeugt ein einseitiges Layout und öffnet es im Designer."""
|
||
print(f"[Layout] create_single_page_layout: name='{name}', "
|
||
f"page=({page_width_mm}x{page_height_mm}), map=({map_width_mm:.1f}x{map_height_mm:.1f}), "
|
||
f"massstab={plotmassstab}, thema='{thema}'")
|
||
layout_manager = self.project.layoutManager()
|
||
print(f"[Layout] layoutManager: {layout_manager!r}")
|
||
existing_layout = layout_manager.layoutByName(name)
|
||
if existing_layout is not None:
|
||
raise ValueError(f"Eine Vorlage mit der Bezeichnung '{name}' existiert bereits.")
|
||
|
||
layout = QgsPrintLayout(self.project)
|
||
layout.initializeDefaults()
|
||
layout.setName(name)
|
||
self._current_layout = layout
|
||
print(f"[Layout] QgsPrintLayout erstellt: {layout!r}")
|
||
|
||
page = layout.pageCollection().page(0)
|
||
page.setPageSize(QgsLayoutSize(page_width_mm, page_height_mm, MM))
|
||
print(f"[Layout] Seitengröße gesetzt: {page_width_mm}x{page_height_mm} mm")
|
||
|
||
map_left_mm = 10.0
|
||
map_top_mm = 10.0
|
||
map_right_mm = map_left_mm + map_width_mm
|
||
map_bottom_mm = map_top_mm + map_height_mm
|
||
kartenfenster_untere_rechte_ecke_mm = (map_right_mm, map_bottom_mm)
|
||
print(
|
||
f"[Layout] Kartenbild-Kanten: rechts={map_right_mm:.1f} mm, unten={map_bottom_mm:.1f} mm"
|
||
)
|
||
|
||
hauptkarte = self._create_map_item(
|
||
layout,
|
||
"hauptkarte",
|
||
map_left_mm,
|
||
map_top_mm,
|
||
map_width_mm,
|
||
map_height_mm,
|
||
)
|
||
print(f"[Layout] QgsLayoutItemMap erstellt")
|
||
print("[Layout] Hauptkarte zum Layout hinzugefügt")
|
||
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
|
||
x_min = getattr(extent, "xMinimum", lambda: float("nan"))()
|
||
y_min = getattr(extent, "yMinimum", lambda: float("nan"))()
|
||
x_max = getattr(extent, "xMaximum", lambda: float("nan"))()
|
||
y_max = getattr(extent, "yMaximum", lambda: float("nan"))()
|
||
print(f"[Layout] Extent input: xmin={x_min}, ymin={y_min}, xmax={x_max}, ymax={y_max}")
|
||
|
||
if extent is not None and hasattr(extent, "isNull") and callable(extent.isNull) and not extent.isNull():
|
||
try:
|
||
hauptkarte.setExtent(extent)
|
||
print(f"[Layout] setExtent() erfolgreich")
|
||
except Exception as exc:
|
||
print(f"[Layout] WARN: setExtent() fehlgeschlagen: {exc}")
|
||
else:
|
||
print(f"[Layout] WARN: Extent nicht gültig/callable, setExtent übersprungen")
|
||
|
||
# Maßstab setzen (NACH Extent)
|
||
if isinstance(plotmassstab, (int, float)) and math.isfinite(plotmassstab) and plotmassstab > 0:
|
||
try:
|
||
hauptkarte.setScale(plotmassstab)
|
||
print(f"[Layout] setScale({plotmassstab}) erfolgreich")
|
||
except Exception as exc:
|
||
print(f"[Layout] WARN: setScale({plotmassstab}) fehlgeschlagen: {exc}")
|
||
else:
|
||
print(f"[Layout] WARN: ungültiger plotmassstab={plotmassstab!r}, setScale übersprungen")
|
||
|
||
# Rahmen aktivieren
|
||
set_frame_enabled = getattr(hauptkarte, "setFrameEnabled", None)
|
||
if callable(set_frame_enabled):
|
||
try:
|
||
set_frame_enabled(True)
|
||
print(f"[Layout] Rahmen aktiviert")
|
||
except Exception as exc:
|
||
print(f"[Layout] WARN: setFrameEnabled fehlgeschlagen: {exc}")
|
||
|
||
set_frame_stroke_width = getattr(hauptkarte, "setFrameStrokeWidth", None)
|
||
if callable(set_frame_stroke_width):
|
||
try:
|
||
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}")
|
||
|
||
# Kartenthema setzen
|
||
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)
|
||
print(f"[Layout] setFollowVisibilityPreset(True)")
|
||
except Exception as exc:
|
||
print(f"[Layout] WARN: setFollowVisibilityPreset fehlgeschlagen: {exc}")
|
||
if callable(set_theme_name):
|
||
try:
|
||
set_theme_name(thema)
|
||
print(f"[Layout] Kartenthema auf '{thema}' gesetzt")
|
||
except Exception as exc:
|
||
print(f"[Layout] WARN: setFollowVisibilityPresetName('{thema}') fehlgeschlagen: {exc}")
|
||
else:
|
||
print(f"[Layout] Kartenthema nicht gesetzt (thema='{thema}')")
|
||
|
||
# Erst nach allen Properties: LayerSet einfrieren für Export
|
||
set_keep_layer_set = getattr(hauptkarte, "setKeepLayerSet", None)
|
||
if callable(set_keep_layer_set):
|
||
try:
|
||
set_keep_layer_set(True)
|
||
print("[Layout] setKeepLayerSet(True) – Layerset für Export erhalten")
|
||
except Exception as exc:
|
||
print(f"[Layout] WARN: setKeepLayerSet fehlgeschlagen: {exc}")
|
||
|
||
# Refresh-Strategie (optional, nur wenn vorhanden)
|
||
set_refresh_strategy = getattr(hauptkarte, "setRefreshStrategy", None)
|
||
if callable(set_refresh_strategy):
|
||
refresh_cache = getattr(hauptkarte, "RefreshLaterOnly", None) or getattr(hauptkarte, "RefreshWhenRequested", None) or 0
|
||
if refresh_cache is not None:
|
||
try:
|
||
set_refresh_strategy(refresh_cache)
|
||
print(f"[Layout] setRefreshStrategy({refresh_cache}) gesetzt")
|
||
except Exception as exc:
|
||
print(f"[Layout] WARN: setRefreshStrategy fehlgeschlagen: {exc}")
|
||
|
||
print(f"[Layout] Hauptkarte vollständig konfiguriert")
|
||
|
||
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")
|
||
self._current_layout = None
|
||
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)
|
||
self._current_layout = layout
|
||
|
||
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
|
||
kartenfenster_untere_rechte_ecke_mm = (map_right_mm, map_bottom_mm)
|
||
|
||
hauptkarte = self._create_map_item(
|
||
layout,
|
||
"hauptkarte",
|
||
map_left_mm,
|
||
map_top_mm,
|
||
map_width_mm,
|
||
map_height_mm,
|
||
)
|
||
|
||
# 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:
|
||
self._set_item_frame_stroke_width(hauptkarte, 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")
|
||
|
||
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:
|
||
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)
|
||
print("[Layout] Layout zum LayoutManager hinzugefügt")
|
||
self._current_layout = None
|
||
open_layout_designer(layout)
|
||
print("[Layout] Layout Designer geöffnet")
|
||
return layout
|