drucktab aus sn_plan41 eingefügt, Atlasfunktion entfernt

This commit is contained in:
2026-03-26 20:18:17 +01:00
parent e397ccde71
commit 3d8c0aff5d
4 changed files with 1329 additions and 0 deletions
+781
View File
@@ -0,0 +1,781 @@
"""
sn_basis/modules/print_layout.py - Aufbau von Einzelblatt-Drucklayouts.
"""
from __future__ import annotations
import importlib
import math
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 PrintLayout:
"""Erzeugt ein QGIS-Einzelblatt-Layout fuer 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",
},
"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 "
"Saechsischen Landesamtes fuer 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>"
)
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:
if self._current_layout is None:
raise RuntimeError("Kein Layout aktiv. Bitte create_single_page_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,
) -> None:
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,
)
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:
self._configure_legend_size(
legende,
width_limit_mm=180.0,
column_count=2,
equal_column_width=True,
)
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",
)
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,
)
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,
)
labels = [
("[% @sn_amt %]", 16.0, -84.0, 171.0, 4.0, "Format_1", "Herausgeber"),
("[% @sn_bezeichnung %]", 16.0, -75.0, 171.0, 4.0, "Format_1", "Bezeichnung_Flurbereinigung"),
("[% @sn_name %]", 16.0, -64.0, 171.0, 5.0, "Format_2", "Name_Flurbereinigung"),
("[% @sn_gemeinden %]", 16.0, -56.0, 171.0, 4.0, "Format_1", "Stadt_Gemeinde"),
("[% @sn_landkreise_proj %]", 16.0, -48.0, 171.0, 4.0, "Format_1", "Landkreis"),
("[% @sn_kartenname %]", 16.0, -34.0, 137.0, 5.0, "Format_3", "Kartenname"),
("Gefertigt: ", 16.0, -23.0, 81.0, 4.0, "Format_1", "Gefertigt"),
("Geprueft: ", 101.0, -23.0, 81.0, 4.0, "Format_1", "Geprueft"),
(
"Massstab: 1 : [%round(map_get(item_variables('Hauptkarte'),'map_scale'),0)%]",
16.0,
-13.0,
81.0,
4.0,
"Format_1",
"Massstab",
),
("Ausgabedatum: [%format_date(now(), 'dd.MM.yyyy')%]", 101.0, -13.0, 81.0, 4.0, "Format_1", "Ausgabedatum"),
("Aktenzeichen: ", 16.0, -3.0, 171.0, 4.0, "Format_1", "Aktenzeichen"),
]
for text, rx, ry, w, h, fmt, obj_name in labels:
x, y = _coords_rel_untere_rechte_ecke(rx, ry)
self.add_text_label(
text,
x,
y,
width_mm=w,
height_mm=h,
text_format=fmt,
expression_enabled=True,
object_name=obj_name,
)
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:
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))
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,
)
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_keep_layer_set = getattr(hauptkarte, "setKeepLayerSet", None)
if callable(set_keep_layer_set):
try:
set_keep_layer_set(True)
except Exception:
pass
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
)
try:
set_refresh_strategy(refresh_cache)
except Exception:
pass
self._setup_layout_objects(
kartenfenster_untere_rechte_ecke_mm,
hauptkarte,
map_top_mm,
map_height_mm,
)
layout_manager.addLayout(layout)
self._current_layout = None
open_layout_designer(layout)
return layout
+203
View File
@@ -0,0 +1,203 @@
"""
sn_basis/modules/print_logic.py - Fachlogik fuer den Druck-Tab (nur Einzelblatt).
"""
from __future__ import annotations
from typing import Any
from sn_basis.functions.qgiscore_wrapper import QgsProject, get_layer_extent
from sn_basis.functions.variable_wrapper import get_variable, set_variable
from sn_basis.modules.layerpruefer import Layerpruefer
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.print_layout import PrintLayout
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"
THEMA_WIE_KARTENFENSTER = "wie kartenfenster"
KARTENNAME_BY_AUSWAHL = {
KARTENNAME_38: "Planungsuebersicht §38 FlurbG",
KARTENNAME_41: "Karte zum Plan ueber die gemeinschaftlichen und oeffentlichen Anlagen (§ 41 FlurbG)",
}
PLOTMASSSTAB_BY_AUSWAHL = {
"1:5.000": "5000",
"1:10.000": "10000",
"1:15.000": "15000",
"1:20.000": "20000",
"1:25.000": "25000",
"1:50.000": "50000",
"1:100.000": "100000",
}
DIN_GROESSEN: dict[str, tuple[int, int]] = {
"DIN A0": (841, 1189),
"DIN A1": (594, 841),
"DIN A2": (420, 594),
"DIN A3": (297, 420),
"DIN A4": (210, 297),
}
DIN_STANDARD = "DIN A0"
class PrintLogic:
"""Kapselt die Fachlogik fuer den Druck-Tab (ohne Atlas)."""
def __init__(self, pruefmanager: Pruefmanager) -> None:
self.pruefmanager = pruefmanager
def _resolve_layer_from_project(self) -> object | None:
project = QgsProject.instance()
layer_id = (get_variable("verfahrensgebietslayer", scope="project") or "").strip()
if not layer_id:
layer_id = (get_variable("tab_a_layer_id", scope="project") or "").strip()
if not layer_id:
return None
map_layer = getattr(project, "mapLayer", None)
if not callable(map_layer):
return None
try:
return map_layer(layer_id)
except Exception:
return None
def set_kartenname_for_auswahl(self, auswahl: str) -> None:
kartenname = KARTENNAME_BY_AUSWAHL.get(auswahl, "")
set_variable(KARTENNAME_VAR, kartenname, scope="project")
def set_plotmassstab_for_auswahl(self, auswahl: str, aktueller_massstab: float | None = None) -> None:
if auswahl == MASSSTAB_WIE_KARTENFENSTER:
if aktueller_massstab and aktueller_massstab > 0:
set_variable(PLOTMASSSTAB_VAR, str(int(round(aktueller_massstab))), scope="project")
else:
set_variable(PLOTMASSSTAB_VAR, "", scope="project")
return
value = PLOTMASSSTAB_BY_AUSWAHL.get(auswahl, "")
set_variable(PLOTMASSSTAB_VAR, value, scope="project")
def set_view_for_auswahl(self, auswahl: str) -> None:
if auswahl == THEMA_WIE_KARTENFENSTER:
set_variable(VIEW_VAR, "aktuell", scope="project")
return
set_variable(VIEW_VAR, auswahl or "", scope="project")
def set_zielgroesse_for_auswahl(self, auswahl: str) -> None:
set_variable(ZIELGROESSE_VAR, auswahl if auswahl in DIN_GROESSEN else DIN_STANDARD, scope="project")
def set_formfaktor(self, endlosrolle: bool) -> None:
set_variable(FORMFAKTOR_VAR, "Endlosrolle" if endlosrolle else "Blatt", scope="project")
def druckvorlage_anlegen(
self,
layer: object | None,
kartenname_auswahl: str,
massstab_auswahl: str,
zielgroesse: str,
formfaktor: bool,
) -> dict:
resolved_layer = layer or self._resolve_layer_from_project()
lp = Layerpruefer(layer=resolved_layer)
ergebnis = lp.pruefe()
if not ergebnis.ok:
self.pruefmanager.zeige_hinweis(
"Verfahrensgebiets-Layer angeben",
"Kein gueltiger Verfahrensgebiets-Layer gefunden. Bitte Layerauswahl im jeweiligen Plugin setzen.",
)
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
layer_id = getattr(resolved_layer, "id", lambda: "")() or ""
set_variable("verfahrensgebietslayer", layer_id, scope="project")
if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41):
self.pruefmanager.zeige_hinweis("Kartennamen waehlen", "Kartennamen waehlen")
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
if massstab_auswahl == MASSSTAB_WIE_KARTENFENSTER:
massstab_str = get_variable(PLOTMASSSTAB_VAR, scope="project") or ""
try:
massstab_zahl = float(massstab_str)
except (ValueError, TypeError):
massstab_zahl = 0.0
else:
massstab_zahl = float(PLOTMASSSTAB_BY_AUSWAHL.get(massstab_auswahl, 0))
if massstab_zahl <= 0:
self.pruefmanager.zeige_hinweis("Massstab fehlt", "Kein gueltiger Massstab angegeben.")
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
extent = get_layer_extent(resolved_layer)
if extent is None:
self.pruefmanager.zeige_hinweis("Fehler", "Layer-Ausdehnung konnte nicht ermittelt werden.")
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
kartenbild_w = extent.width() * 1000.0 / massstab_zahl
kartenbild_h = extent.height() * 1000.0 / massstab_zahl
plotgroesse_w = kartenbild_w + 210.0
plotgroesse_h = kartenbild_h + 20.0
din_dims = DIN_GROESSEN.get(zielgroesse, DIN_GROESSEN[DIN_STANDARD])
if formfaktor:
ziel_w, ziel_h = plotgroesse_w, float(min(din_dims))
else:
ziel_w, ziel_h = float(din_dims[0]), float(din_dims[1])
if ziel_w < DIN_GROESSEN["DIN A4"][0] or ziel_h < DIN_GROESSEN["DIN A4"][1]:
self.pruefmanager.zeige_hinweis("Blattgroesse zu klein", "Die Zielgroesse darf nicht kleiner als DIN A4 sein.")
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h:
if plotgroesse_w <= ziel_h and plotgroesse_h <= ziel_w:
ziel_w, ziel_h = ziel_h, ziel_w
if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h:
self.pruefmanager.zeige_hinweis(
"Zielgroesse zu klein",
"Die gewaehlte Zielgroesse reicht fuer den Einzelblatt-Druck nicht aus."
)
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get(
kartenname_auswahl, "Vorlage"
)
thema = get_variable(VIEW_VAR, scope="project") or ""
vorlage_name, bestaetigt = self.pruefmanager.frage_text(
"Neue Vorlage anlegen",
"Bezeichnung der Vorlage:",
default_text=kartenname,
)
if not bestaetigt:
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
vorlage_name = (vorlage_name or "").strip() or kartenname
try:
PrintLayout().create_single_page_layout(
name=vorlage_name,
page_width_mm=ziel_w,
page_height_mm=ziel_h,
map_width_mm=kartenbild_w,
map_height_mm=kartenbild_h,
extent=extent,
plotmassstab=massstab_zahl,
thema=thema,
)
except ValueError as exc:
self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc))
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
except Exception as exc:
self.pruefmanager.zeige_hinweis("Vorlage anlegen", f"Die Vorlage konnte nicht angelegt werden: {exc}")
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": 1}
+4
View File
@@ -0,0 +1,4 @@
from sn_basis.ui.tabs.print_tab import PrintTab
from sn_basis.ui.tabs.settings_tab import SettingsTab
__all__ = ["PrintTab", "SettingsTab"]
+341
View File
@@ -0,0 +1,341 @@
"""
sn_basis/ui/tabs/print_tab.py - Zentraler Druck-Tab fuer sn-Plugins.
"""
from __future__ import annotations
from typing import Any, Optional
from sn_basis.functions.qt_wrapper import (
QWidget,
QVBoxLayout,
QLabel,
QComboBox,
QCheckBox,
QHBoxLayout,
QPushButton,
)
from sn_basis.functions.qgiscore_wrapper import QgsProject
from sn_basis.functions.qgisui_wrapper import iface
from sn_basis.functions.variable_wrapper import get_variable, set_variable
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.DataGrabber import DataGrabber
from sn_basis.modules.print_logic import (
PrintLogic,
MASSSTAB_WIE_KARTENFENSTER,
PLOTMASSSTAB_BY_AUSWAHL,
THEMA_WIE_KARTENFENSTER,
DIN_GROESSEN,
DIN_STANDARD,
)
KARTENNAME_VAR = "tab_b_kartenname"
KARTENNAME_PLACEHOLDER = "Kartenname waehlen"
KARTENNAME_38 = "§38"
KARTENNAME_41 = "§41"
MASSSTAB_VAR = "tab_b_massstab"
THEMA_VAR = "tab_b_thema"
ZIELGROESSE_UI_VAR = "tab_b_zielgroesse"
FORMFAKTOR_UI_VAR = "tab_b_formfaktor"
class PrintTab(QWidget):
"""UI-Klasse fuer den zentralen Druck-Tab."""
tab_title = "Druck"
def __init__(self, parent: Optional[Any] = None):
super().__init__(parent)
self.pruefmanager: Optional[Pruefmanager] = None
self.logic: Optional[PrintLogic] = None
self._kartenname_combo: Optional[Any] = None
self._massstab_combo: Optional[Any] = None
self._thema_combo: Optional[Any] = None
self._theme_signal_connected = False
self._connected_theme_collection: object = None
self._zielgroesse_combo: Optional[Any] = None
self._endlosrolle_cb: Optional[Any] = None
self._btn_vorlage_erstellen: Optional[Any] = None
self._build_ui()
self._restore_state()
self._connect_theme_collection_signals()
self._connect_project_signals()
def set_services(self, pruefmanager: Pruefmanager, data_grabber: DataGrabber) -> None:
_ = data_grabber
self.pruefmanager = pruefmanager
self.logic = PrintLogic(pruefmanager=self.pruefmanager)
if self._kartenname_combo:
self.logic.set_kartenname_for_auswahl(self._kartenname_combo.currentText())
if self._massstab_combo:
self.logic.set_plotmassstab_for_auswahl(
self._massstab_combo.currentText(),
self._get_current_canvas_scale(),
)
if self._thema_combo:
self.logic.set_view_for_auswahl(self._thema_combo.currentText())
if self._zielgroesse_combo:
self.logic.set_zielgroesse_for_auswahl(self._zielgroesse_combo.currentText())
if self._endlosrolle_cb:
self.logic.set_formfaktor(self._endlosrolle_cb.isChecked())
def _build_ui(self) -> None:
main_layout = QVBoxLayout()
main_layout.setSpacing(4)
main_layout.setContentsMargins(4, 4, 4, 4)
kartenname_label = QLabel("Kartenname")
kartenname_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
main_layout.addWidget(kartenname_label)
kartenname_combo = QComboBox(self)
kartenname_combo.addItem(KARTENNAME_PLACEHOLDER)
kartenname_combo.addItem(KARTENNAME_38)
kartenname_combo.addItem(KARTENNAME_41)
kartenname_combo.currentTextChanged.connect(self._on_kartenname_changed)
self._kartenname_combo = kartenname_combo
main_layout.addWidget(kartenname_combo)
massstab_label = QLabel("Massstab")
massstab_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
main_layout.addWidget(massstab_label)
massstab_combo = QComboBox(self)
massstab_combo.addItem(MASSSTAB_WIE_KARTENFENSTER)
massstab_combo.addItems(list(PLOTMASSSTAB_BY_AUSWAHL.keys()))
massstab_combo.currentTextChanged.connect(self._on_massstab_changed)
self._massstab_combo = massstab_combo
main_layout.addWidget(massstab_combo)
thema_label = QLabel("Thema")
thema_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
main_layout.addWidget(thema_label)
thema_combo = QComboBox(self)
thema_combo.addItem(THEMA_WIE_KARTENFENSTER)
thema_combo.addItems(self._get_gespeicherte_themen())
thema_combo.currentTextChanged.connect(self._on_thema_changed)
self._thema_combo = thema_combo
main_layout.addWidget(thema_combo)
zielgroesse_label = QLabel("max. Blattgroesse")
zielgroesse_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
main_layout.addWidget(zielgroesse_label)
zielgroesse_row = QHBoxLayout()
zielgroesse_row.setSpacing(6)
zielgroesse_combo = QComboBox(self)
zielgroesse_combo.addItems(list(DIN_GROESSEN.keys()))
zielgroesse_combo.setCurrentText(DIN_STANDARD)
zielgroesse_combo.currentTextChanged.connect(self._on_zielgroesse_changed)
self._zielgroesse_combo = zielgroesse_combo
zielgroesse_row.addWidget(zielgroesse_combo)
endlosrolle_cb = QCheckBox("Endlosrolle", self)
endlosrolle_cb.setChecked(False)
endlosrolle_cb.stateChanged.connect(self._on_formfaktor_changed)
self._endlosrolle_cb = endlosrolle_cb
zielgroesse_row.addWidget(endlosrolle_cb)
main_layout.addLayout(zielgroesse_row)
btn_vorlage_erstellen = QPushButton("Vorlage erstellen", self)
btn_vorlage_erstellen.clicked.connect(self._on_vorlage_erstellen)
self._btn_vorlage_erstellen = btn_vorlage_erstellen
main_layout.addWidget(btn_vorlage_erstellen)
main_layout.addStretch(1)
self.setLayout(main_layout)
def _restore_state(self) -> None:
if not self._kartenname_combo or not self._massstab_combo or not self._thema_combo:
return
if not self._zielgroesse_combo or not self._endlosrolle_cb:
return
saved_kartenname = get_variable(KARTENNAME_VAR, scope="project")
if saved_kartenname in (KARTENNAME_38, KARTENNAME_41):
self._kartenname_combo.setCurrentText(saved_kartenname)
else:
self._kartenname_combo.setCurrentText(KARTENNAME_PLACEHOLDER)
saved_massstab = get_variable(MASSSTAB_VAR, scope="project")
valid_massstaebe = [MASSSTAB_WIE_KARTENFENSTER, *PLOTMASSSTAB_BY_AUSWAHL.keys()]
if saved_massstab in valid_massstaebe:
self._massstab_combo.setCurrentText(saved_massstab)
else:
self._massstab_combo.setCurrentText(MASSSTAB_WIE_KARTENFENSTER)
aktuelle_themen = [THEMA_WIE_KARTENFENSTER, *self._get_gespeicherte_themen()]
self._thema_combo.clear()
self._thema_combo.addItems(aktuelle_themen)
saved_thema = get_variable(THEMA_VAR, scope="project")
if saved_thema in aktuelle_themen:
self._thema_combo.setCurrentText(saved_thema)
else:
self._thema_combo.setCurrentText(THEMA_WIE_KARTENFENSTER)
saved_zielgroesse = get_variable(ZIELGROESSE_UI_VAR, scope="project")
if saved_zielgroesse in DIN_GROESSEN:
self._zielgroesse_combo.setCurrentText(saved_zielgroesse)
else:
self._zielgroesse_combo.setCurrentText(DIN_STANDARD)
saved_formfaktor = get_variable(FORMFAKTOR_UI_VAR, scope="project")
self._endlosrolle_cb.setChecked(saved_formfaktor == "Endlosrolle")
def _on_kartenname_changed(self, value: str) -> None:
if value in (KARTENNAME_38, KARTENNAME_41):
set_variable(KARTENNAME_VAR, value, scope="project")
else:
set_variable(KARTENNAME_VAR, "", scope="project")
if self.logic:
self.logic.set_kartenname_for_auswahl(value)
def _on_massstab_changed(self, value: str) -> None:
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:
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:
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:
checked = bool(state)
set_variable(FORMFAKTOR_UI_VAR, "Endlosrolle" if checked else "Blatt", scope="project")
if self.logic:
self.logic.set_formfaktor(checked)
def _on_vorlage_erstellen(self) -> None:
if not self.logic or not self.pruefmanager:
return
self.logic.druckvorlage_anlegen(
layer=None,
kartenname_auswahl=self._kartenname_combo.currentText() if self._kartenname_combo else "",
massstab_auswahl=self._massstab_combo.currentText() if self._massstab_combo else "",
zielgroesse=self._zielgroesse_combo.currentText() if self._zielgroesse_combo else DIN_STANDARD,
formfaktor=self._endlosrolle_cb.isChecked() if self._endlosrolle_cb else False,
)
def _connect_project_signals(self) -> None:
project = QgsProject.instance()
for signal_name in ("readProject", "newProjectCreated", "cleared"):
signal = getattr(project, signal_name, None)
if signal is None:
continue
try:
signal.connect(self._on_project_changed)
except Exception:
pass
def _on_project_changed(self, *args) -> None:
_ = args
self._disconnect_theme_collection_signals()
self._theme_signal_connected = False
self._connect_theme_collection_signals()
self._restore_state()
def _disconnect_theme_collection_signals(self) -> None:
collection = self._connected_theme_collection
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:
if self._theme_signal_connected:
return
try:
theme_collection = QgsProject.instance().mapThemeCollection()
except Exception:
return
if theme_collection is None:
return
connected_any = False
for signal_name in ("mapThemesChanged", "changed", "themeChanged"):
signal = getattr(theme_collection, signal_name, None)
if signal is None:
continue
try:
signal.connect(self._refresh_thema_combo_live)
connected_any = True
except Exception:
pass
if connected_any:
self._connected_theme_collection = theme_collection
self._theme_signal_connected = connected_any
def _refresh_thema_combo_live(self, *args) -> None:
_ = args
if not self._thema_combo:
return
vorherige_auswahl = self._thema_combo.currentText() or THEMA_WIE_KARTENFENSTER
eintraege = [THEMA_WIE_KARTENFENSTER, *self._get_gespeicherte_themen()]
self._thema_combo.blockSignals(True)
self._thema_combo.clear()
self._thema_combo.addItems(eintraege)
if vorherige_auswahl in eintraege:
self._thema_combo.setCurrentText(vorherige_auswahl)
else:
self._thema_combo.setCurrentText(THEMA_WIE_KARTENFENSTER)
self._thema_combo.blockSignals(False)
self._on_thema_changed(self._thema_combo.currentText())
def _get_gespeicherte_themen(self) -> list[str]:
try:
theme_collection = QgsProject.instance().mapThemeCollection()
if theme_collection is None:
return []
themes = theme_collection.mapThemes()
except Exception:
return []
namen: list[str] = []
for theme_name in themes:
name = str(theme_name or "").strip()
if name and name not in namen:
namen.append(name)
return namen
def _get_current_canvas_scale(self) -> float | None:
try:
canvas = iface.mapCanvas()
if canvas is None:
return None
scale = canvas.scale()
return float(scale) if scale else None
except Exception:
return None