Compare commits
12 Commits
6436b06a43
...
unstable
| Author | SHA1 | Date | |
|---|---|---|---|
| 800488fb15 | |||
| 4d0dcc0310 | |||
| a54d4fbe3c | |||
| 284f2a2a03 | |||
| d21483ce53 | |||
| bfc9fae324 | |||
| ff5fd990bc | |||
| fcf5b11373 | |||
| 6a4c7b4609 | |||
| a3b22d0444 | |||
| 5c00b4abee | |||
| 6261b88fee |
43
assets/atlasobjekte.qml
Normal file
43
assets/atlasobjekte.qml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
|
||||||
|
<qgis version="3.34.0" styleCategories="AllStyleCategories">
|
||||||
|
<renderer-v2 type="singleSymbol" symbollevels="0" enableorderby="0" referencescale="-1" forceraster="0">
|
||||||
|
<symbols>
|
||||||
|
<symbol alpha="1" type="fill" name="0" force_rhr="0" clip_to_extent="1">
|
||||||
|
<layer class="SimpleFill" enabled="1" locked="0" pass="0">
|
||||||
|
<Option type="Map">
|
||||||
|
<Option name="border_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
|
||||||
|
<Option name="color" type="QString" value="255,0,0,40"/>
|
||||||
|
<Option name="joinstyle" type="QString" value="bevel"/>
|
||||||
|
<Option name="offset" type="QString" value="0,0"/>
|
||||||
|
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
|
||||||
|
<Option name="offset_unit" type="QString" value="MM"/>
|
||||||
|
<Option name="outline_color" type="QString" value="255,0,0,255"/>
|
||||||
|
<Option name="outline_style" type="QString" value="solid"/>
|
||||||
|
<Option name="outline_width" type="QString" value="0.8"/>
|
||||||
|
<Option name="outline_width_unit" type="QString" value="MM"/>
|
||||||
|
<Option name="style" type="QString" value="solid"/>
|
||||||
|
</Option>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
<fieldConfiguration>
|
||||||
|
<field name="Seitenzahl" configurationFlags="None">
|
||||||
|
<editWidget type="Range">
|
||||||
|
<config>
|
||||||
|
<Option type="Map">
|
||||||
|
<Option name="AllowNull" type="bool" value="false"/>
|
||||||
|
<Option name="Max" type="double" value="9999"/>
|
||||||
|
<Option name="Min" type="double" value="1"/>
|
||||||
|
<Option name="Precision" type="int" value="0"/>
|
||||||
|
<Option name="Step" type="double" value="1"/>
|
||||||
|
<Option name="Style" type="QString" value="SpinBox"/>
|
||||||
|
</Option>
|
||||||
|
</config>
|
||||||
|
</editWidget>
|
||||||
|
</field>
|
||||||
|
</fieldConfiguration>
|
||||||
|
<defaults>
|
||||||
|
<default field="Seitenzahl" expression="" applyOnUpdate="0"/>
|
||||||
|
</defaults>
|
||||||
|
</qgis>
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#sn_plan41/ui/dockwidget.py
|
#sn_plan41/ui/dockwidget.py
|
||||||
from sn_basis.ui.tabs.settings_tab import SettingsTab
|
from sn_basis.ui.tabs.settings_tab import SettingsTab
|
||||||
from sn_plan41.ui.tab_a_ui import TabA
|
from sn_plan41.ui.tab_a_ui import TabA
|
||||||
#from sn_plan41.ui.tabs.tab_b import TabB
|
from sn_plan41.ui.tab_b_ui import TabB
|
||||||
from sn_basis.ui.base_dockwidget import BaseDockWidget
|
from sn_basis.ui.base_dockwidget import BaseDockWidget
|
||||||
from sn_basis.functions.qt_wrapper import QTabWidget
|
from sn_basis.functions.qt_wrapper import QTabWidget
|
||||||
from sn_basis.functions.message_wrapper import error
|
from sn_basis.functions.message_wrapper import error
|
||||||
|
|
||||||
|
|
||||||
class DockWidget(BaseDockWidget):
|
class DockWidget(BaseDockWidget):
|
||||||
tabs = [TabA, SettingsTab]
|
tabs = [TabA, TabB,SettingsTab]
|
||||||
|
|
||||||
def __init__(self, parent=None, subtitle="", pruefmanager=None, data_grabber=None):
|
def __init__(self, parent=None, subtitle="", pruefmanager=None, data_grabber=None):
|
||||||
super().__init__(parent, subtitle)
|
super().__init__(parent, subtitle)
|
||||||
|
|||||||
431
ui/layout.py
Normal file
431
ui/layout.py
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
"""
|
||||||
|
sn_plan41/ui/layout.py – Aufbau von Drucklayouts für Plan41.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from sn_basis.functions.qt_wrapper import QFont
|
||||||
|
from sn_basis.functions.qgiscore_wrapper import (
|
||||||
|
QgsLayoutItem,
|
||||||
|
QgsLayoutItemLabel,
|
||||||
|
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."""
|
||||||
|
|
||||||
|
def __init__(self, project: Any | None = None) -> None:
|
||||||
|
self.project = project or QgsProject.instance()
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
print(
|
||||||
|
f"[Layout] Kartenbild-Kanten: rechts={map_right_mm:.1f} mm, unten={map_bottom_mm:.1f} mm"
|
||||||
|
)
|
||||||
|
|
||||||
|
hauptkarte = QgsLayoutItemMap(layout)
|
||||||
|
hauptkarte.setId("Hauptkarte")
|
||||||
|
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
|
||||||
|
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:
|
||||||
|
set_frame_stroke_width(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")
|
||||||
|
|
||||||
|
quellenangabe = QgsLayoutItemLabel(layout)
|
||||||
|
quellenangabe.setId("Quellenangabe")
|
||||||
|
quellenangabe.setText(
|
||||||
|
"Quelle Geobasisdaten: GeoSN, "
|
||||||
|
"<a href=\"https://www.govdata.de/dl-de/by-2-0\">dl-de/by-2-0</a><br><br>"
|
||||||
|
"Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des "
|
||||||
|
"Sächsischen Landesamtes für Umwelt, Landwirtschaft und Geologie<br><br>"
|
||||||
|
"Basemap:<br><br>"
|
||||||
|
"© 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>"
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
print("[Layout] Quellenangabe zum Layout hinzugefügt")
|
||||||
|
|
||||||
|
layout_manager.addLayout(layout)
|
||||||
|
print("[Layout] Layout zum LayoutManager hinzugefügt")
|
||||||
|
open_layout_designer(layout)
|
||||||
|
print("[Layout] Layout Designer geöffnet")
|
||||||
|
return layout
|
||||||
|
|
||||||
|
def create_atlas_layout(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
page_width_mm: float,
|
||||||
|
page_height_mm: float,
|
||||||
|
map_width_mm: float,
|
||||||
|
map_height_mm: float,
|
||||||
|
extent: Any,
|
||||||
|
plotmassstab: float,
|
||||||
|
atlas_layer: Any,
|
||||||
|
thema: str = "",
|
||||||
|
) -> Any:
|
||||||
|
"""Erzeugt ein Atlas-Layout mit Coverage-Layer ``Atlasobjekte``."""
|
||||||
|
layout_manager = self.project.layoutManager()
|
||||||
|
existing_layout = layout_manager.layoutByName(name)
|
||||||
|
if existing_layout is not None:
|
||||||
|
raise ValueError(f"Eine Vorlage mit der Bezeichnung '{name}' existiert bereits.")
|
||||||
|
|
||||||
|
layout = QgsPrintLayout(self.project)
|
||||||
|
layout.initializeDefaults()
|
||||||
|
layout.setName(name)
|
||||||
|
|
||||||
|
page = layout.pageCollection().page(0)
|
||||||
|
page.setPageSize(QgsLayoutSize(page_width_mm, page_height_mm, MM))
|
||||||
|
# Verifiziere, dass QGIS die Größe akzeptiert hat
|
||||||
|
page_size = page.pageSize() if hasattr(page, 'pageSize') else None
|
||||||
|
if page_size is not None and hasattr(page_size, 'width'):
|
||||||
|
actual_w = float(page_size.width())
|
||||||
|
actual_h = float(page_size.height())
|
||||||
|
print(f"[Layout] Atlas Page gesetzt: x=0, y=0, width={page_width_mm:.1f}mm→{actual_w:.1f}mm, height={page_height_mm:.1f}mm→{actual_h:.1f}mm")
|
||||||
|
else:
|
||||||
|
print(f"[Layout] Atlas Page: x=0, y=0, width={page_width_mm:.1f}mm, height={page_height_mm:.1f}mm")
|
||||||
|
|
||||||
|
map_left_mm = 10.0
|
||||||
|
map_top_mm = 10.0
|
||||||
|
map_right_mm = map_left_mm + map_width_mm
|
||||||
|
map_bottom_mm = map_top_mm + map_height_mm
|
||||||
|
|
||||||
|
hauptkarte = QgsLayoutItemMap(layout)
|
||||||
|
hauptkarte.setId("Hauptkarte")
|
||||||
|
layout.addLayoutItem(hauptkarte)
|
||||||
|
hauptkarte.attemptMove(QgsLayoutPoint(map_left_mm, map_top_mm, MM))
|
||||||
|
hauptkarte.attemptResize(QgsLayoutSize(map_width_mm, map_height_mm, MM))
|
||||||
|
|
||||||
|
# Verifiziere mit Units-bewussten Methoden (rect() kann andere Units verwenden).
|
||||||
|
actual_w = None
|
||||||
|
actual_h = None
|
||||||
|
actual_x = None
|
||||||
|
actual_y = None
|
||||||
|
|
||||||
|
# Versuche zuerst, die Größe mit Unit-Methoden zu lesen
|
||||||
|
try:
|
||||||
|
if hasattr(hauptkarte, 'sizeWithUnits'):
|
||||||
|
size_item = hauptkarte.sizeWithUnits()
|
||||||
|
if hasattr(size_item, 'width') and hasattr(size_item, 'height'):
|
||||||
|
actual_w = float(size_item.width())
|
||||||
|
actual_h = float(size_item.height())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
if hasattr(hauptkarte, 'positionWithUnits'):
|
||||||
|
pos_item = hauptkarte.positionWithUnits()
|
||||||
|
if hasattr(pos_item, 'x') and hasattr(pos_item, 'y'):
|
||||||
|
actual_x = float(pos_item.x())
|
||||||
|
actual_y = float(pos_item.y())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Fallback: nutze rect() und teile durch UnitFaktor, falls nötig
|
||||||
|
if actual_w is None or actual_h is None:
|
||||||
|
try:
|
||||||
|
actual_rect = hauptkarte.rect()
|
||||||
|
if actual_rect is not None:
|
||||||
|
actual_w = float(actual_rect.width())
|
||||||
|
actual_h = float(actual_rect.height())
|
||||||
|
actual_x = float(actual_rect.x())
|
||||||
|
actual_y = float(actual_rect.y())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if actual_w is not None and actual_h is not None:
|
||||||
|
print(f"[Layout] Atlas Hauptkarte gesetzt: x={map_left_mm:.1f}mm→{actual_x:.1f}mm, y={map_top_mm:.1f}mm→{actual_y:.1f}mm, width={map_width_mm:.1f}mm→{actual_w:.1f}mm, height={map_height_mm:.1f}mm→{actual_h:.1f}mm")
|
||||||
|
else:
|
||||||
|
print(f"[Layout] Atlas Hauptkarte: x={map_left_mm:.1f}mm, y={map_top_mm:.1f}mm, width={map_width_mm:.1f}mm, height={map_height_mm:.1f}mm")
|
||||||
|
|
||||||
|
if extent is not None and hasattr(extent, "isNull") and callable(extent.isNull) and not extent.isNull():
|
||||||
|
try:
|
||||||
|
hauptkarte.setExtent(extent)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if isinstance(plotmassstab, (int, float)) and math.isfinite(plotmassstab) and plotmassstab > 0:
|
||||||
|
try:
|
||||||
|
hauptkarte.setScale(plotmassstab)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
set_frame_enabled = getattr(hauptkarte, "setFrameEnabled", None)
|
||||||
|
if callable(set_frame_enabled):
|
||||||
|
try:
|
||||||
|
set_frame_enabled(True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
set_frame_stroke_width = getattr(hauptkarte, "setFrameStrokeWidth", None)
|
||||||
|
if callable(set_frame_stroke_width):
|
||||||
|
try:
|
||||||
|
set_frame_stroke_width(0.5)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if thema and thema != "aktuell":
|
||||||
|
follow_theme = getattr(hauptkarte, "setFollowVisibilityPreset", None)
|
||||||
|
set_theme_name = getattr(hauptkarte, "setFollowVisibilityPresetName", None)
|
||||||
|
if callable(follow_theme):
|
||||||
|
try:
|
||||||
|
follow_theme(True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if callable(set_theme_name):
|
||||||
|
try:
|
||||||
|
set_theme_name(thema)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
set_atlas_driven = getattr(hauptkarte, "setAtlasDriven", None)
|
||||||
|
if callable(set_atlas_driven):
|
||||||
|
try:
|
||||||
|
set_atlas_driven(True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Fester Atlas-Maßstab: plotmassstab bleibt unverändert.
|
||||||
|
set_scaling_mode = getattr(hauptkarte, "setAtlasScalingMode", None)
|
||||||
|
if callable(set_scaling_mode):
|
||||||
|
fixed_mode = getattr(QgsLayoutItemMap, "Fixed", None)
|
||||||
|
if fixed_mode is not None:
|
||||||
|
try:
|
||||||
|
set_scaling_mode(fixed_mode)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
set_atlas_margin = getattr(hauptkarte, "setAtlasMargin", None)
|
||||||
|
if callable(set_atlas_margin):
|
||||||
|
try:
|
||||||
|
set_atlas_margin(0.0)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
set_keep_layer_set = getattr(hauptkarte, "setKeepLayerSet", None)
|
||||||
|
if callable(set_keep_layer_set):
|
||||||
|
try:
|
||||||
|
set_keep_layer_set(True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Sicherheit: Größe nochmal nach Atlas-Konfiguration setzen, um sicherzustellen, dass sie nicht von der Atlas-Einstellung überschrieben wurde.
|
||||||
|
hauptkarte.attemptResize(QgsLayoutSize(map_width_mm, map_height_mm, MM))
|
||||||
|
print(f"[Layout] Atlas Hauptkarte Größe nach Atlas-Konfiguration erneut gesetzt: {map_width_mm:.1f}mm × {map_height_mm:.1f}mm")
|
||||||
|
|
||||||
|
quellenangabe = QgsLayoutItemLabel(layout)
|
||||||
|
quellenangabe.setId("Quellenangabe")
|
||||||
|
quellenangabe.setText(
|
||||||
|
"Quelle Geobasisdaten: GeoSN, "
|
||||||
|
"<a href=\"https://www.govdata.de/dl-de/by-2-0\">dl-de/by-2-0</a><br><br>"
|
||||||
|
"Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des "
|
||||||
|
"Sächsischen Landesamtes für Umwelt, Landwirtschaft und Geologie<br><br>"
|
||||||
|
"Basemap:<br><br>"
|
||||||
|
"© 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>"
|
||||||
|
)
|
||||||
|
set_mode = getattr(quellenangabe, "setMode", None)
|
||||||
|
mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None)
|
||||||
|
if callable(set_mode) and mode_html is not None:
|
||||||
|
set_mode(mode_html)
|
||||||
|
quellenangabe.setFont(QFont("Arial", 12))
|
||||||
|
set_reference_point = getattr(quellenangabe, "setReferencePoint", None)
|
||||||
|
lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None)
|
||||||
|
if callable(set_reference_point) and lower_left is not None:
|
||||||
|
set_reference_point(lower_left)
|
||||||
|
quellenangabe.attemptMove(QgsLayoutPoint(map_right_mm + 5.0, map_bottom_mm, MM))
|
||||||
|
quellenangabe.attemptResize(QgsLayoutSize(180.0, 100.0, MM))
|
||||||
|
layout.addLayoutItem(quellenangabe)
|
||||||
|
|
||||||
|
seitenzahl_label = QgsLayoutItemLabel(layout)
|
||||||
|
seitenzahl_label.setId("Seitenzahl")
|
||||||
|
seitenzahl_label.setFont(QFont("Arial", 12))
|
||||||
|
set_expr_enabled = getattr(seitenzahl_label, "setExpressionEnabled", None)
|
||||||
|
if callable(set_expr_enabled):
|
||||||
|
try:
|
||||||
|
set_expr_enabled(True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Ausdrucksauswertung robust über [% ... %]-Platzhalter.
|
||||||
|
seitenzahl_label.setText(
|
||||||
|
"Seite [% attribute(@atlas_feature, 'Seitenzahl') %] von [% @atlas_totalfeatures %]"
|
||||||
|
)
|
||||||
|
set_reference_point = getattr(seitenzahl_label, "setReferencePoint", None)
|
||||||
|
lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None)
|
||||||
|
if callable(set_reference_point) and lower_left is not None:
|
||||||
|
set_reference_point(lower_left)
|
||||||
|
seitenzahl_label.attemptMove(QgsLayoutPoint(map_right_mm + 5.0, map_bottom_mm - 2.0, MM))
|
||||||
|
seitenzahl_label.attemptResize(QgsLayoutSize(60.0, 8.0, MM))
|
||||||
|
layout.addLayoutItem(seitenzahl_label)
|
||||||
|
|
||||||
|
atlas = layout.atlas()
|
||||||
|
if atlas is not None:
|
||||||
|
set_enabled = getattr(atlas, "setEnabled", None)
|
||||||
|
set_coverage = getattr(atlas, "setCoverageLayer", None)
|
||||||
|
set_hide_coverage = getattr(atlas, "setHideCoverage", None)
|
||||||
|
set_filter_features = getattr(atlas, "setFilterFeatures", None)
|
||||||
|
set_page_name = getattr(atlas, "setPageNameExpression", None)
|
||||||
|
|
||||||
|
if callable(set_enabled):
|
||||||
|
set_enabled(True)
|
||||||
|
if callable(set_coverage):
|
||||||
|
set_coverage(atlas_layer)
|
||||||
|
if callable(set_hide_coverage):
|
||||||
|
set_hide_coverage(True)
|
||||||
|
if callable(set_filter_features):
|
||||||
|
set_filter_features(False)
|
||||||
|
if callable(set_page_name):
|
||||||
|
set_page_name("attribute(@atlas_feature, 'Seitenzahl')")
|
||||||
|
|
||||||
|
layout_manager.addLayout(layout)
|
||||||
|
open_layout_designer(layout)
|
||||||
|
return layout
|
||||||
680
ui/tab_b_logic.py
Normal file
680
ui/tab_b_logic.py
Normal file
@@ -0,0 +1,680 @@
|
|||||||
|
"""
|
||||||
|
sn_plan41/ui/tab_b_logic.py – Fachlogik für Tab B (Druck)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
import math
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from sn_basis.functions.qt_wrapper import QVariant
|
||||||
|
from sn_basis.functions.variable_wrapper import set_variable, get_variable
|
||||||
|
from sn_basis.functions.qgiscore_wrapper import get_layer_extent
|
||||||
|
from sn_basis.functions.qgiscore_wrapper import (
|
||||||
|
QgsProject,
|
||||||
|
QgsVectorLayer,
|
||||||
|
QgsGeometry,
|
||||||
|
QgsFeature,
|
||||||
|
QgsField,
|
||||||
|
QgsVectorFileWriter,
|
||||||
|
)
|
||||||
|
from sn_basis.functions.sys_wrapper import get_plugin_root, join_path, file_exists
|
||||||
|
from sn_basis.modules.Pruefmanager import Pruefmanager
|
||||||
|
from sn_basis.modules.layerpruefer import Layerpruefer
|
||||||
|
from sn_plan41.ui.layout import Layout
|
||||||
|
|
||||||
|
|
||||||
|
KARTENNAME_VAR = "sn_kartenname"
|
||||||
|
PLOTMASSSTAB_VAR = "sn_plotmassstab"
|
||||||
|
VIEW_VAR = "sn_view"
|
||||||
|
ZIELGROESSE_VAR = "sn_zielgroesse"
|
||||||
|
FORMFAKTOR_VAR = "sn_formfaktor"
|
||||||
|
KARTENNAME_38 = "§38"
|
||||||
|
KARTENNAME_41 = "§41"
|
||||||
|
MASSSTAB_WIE_KARTENFENSTER = "Wie Kartenfenster"
|
||||||
|
THEMA_WIE_KARTENFENSTER = "wie kartenfenster"
|
||||||
|
|
||||||
|
KARTENNAME_BY_AUSWAHL = {
|
||||||
|
KARTENNAME_38: "Planungsübersicht §38 FlurbG",
|
||||||
|
KARTENNAME_41: "Karte zum Plan über die gemeinschaftlichen und öffentlichen 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",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Breite x Höhe in mm (Hochformat, DIN-Standard)
|
||||||
|
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 TabBLogic:
|
||||||
|
"""
|
||||||
|
Kapselt die Fachlogik von Tab B.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, pruefmanager: Pruefmanager) -> None:
|
||||||
|
self.pruefmanager = pruefmanager
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _wkt_rect(x_min: float, y_min: float, x_max: float, y_max: float) -> str:
|
||||||
|
return (
|
||||||
|
f"POLYGON(({x_min} {y_min}, {x_max} {y_min}, {x_max} {y_max}, "
|
||||||
|
f"{x_min} {y_max}, {x_min} {y_min}))"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _set_topological_editing_enabled() -> None:
|
||||||
|
project = QgsProject.instance()
|
||||||
|
set_topological = getattr(project, "setTopologicalEditing", None)
|
||||||
|
if callable(set_topological):
|
||||||
|
try:
|
||||||
|
set_topological(True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _apply_atlas_style(layer: Any) -> None:
|
||||||
|
style_path = join_path(get_plugin_root(), "sn_plan41", "assets", "atlasobjekte.qml")
|
||||||
|
if not file_exists(style_path):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
ok, _ = layer.loadNamedStyle(str(style_path))
|
||||||
|
if ok:
|
||||||
|
getattr(layer, "triggerRepaint", lambda: None)()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_tile_grid_for_roll_atlas(
|
||||||
|
kartenbild_w_mm: float,
|
||||||
|
kartenbild_h_mm: float,
|
||||||
|
din_dims: tuple[int, int],
|
||||||
|
respect_max_sheet_size: bool = False,
|
||||||
|
) -> tuple[int, int, float, float]:
|
||||||
|
"""Bestimmt ein Atlas-Raster für Endlosrolle, das Kacheln statt Streifen erzeugt.
|
||||||
|
|
||||||
|
Ziel: Kachel-Seitenverhältnis möglichst nah am Einzelblatt-Kartenfenster
|
||||||
|
(inkl. Rändern) bei zugleich moderater Seitenanzahl.
|
||||||
|
"""
|
||||||
|
if kartenbild_w_mm <= 0 or kartenbild_h_mm <= 0:
|
||||||
|
return 1, 1, max(1.0, kartenbild_w_mm), max(1.0, kartenbild_h_mm)
|
||||||
|
|
||||||
|
# Einzelblatt-Kartenfenster für beide Orientierungen
|
||||||
|
dim_w, dim_h = float(din_dims[0]), float(din_dims[1])
|
||||||
|
orientation_candidates: list[tuple[float, float]] = [
|
||||||
|
(dim_w - 210.0, dim_h - 20.0),
|
||||||
|
(dim_h - 210.0, dim_w - 20.0),
|
||||||
|
]
|
||||||
|
|
||||||
|
best_score = math.inf
|
||||||
|
best_result: tuple[int, int, float, float] | None = None
|
||||||
|
|
||||||
|
for target_w, target_h in orientation_candidates:
|
||||||
|
if target_w <= 0 or target_h <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
target_aspect = target_w / target_h
|
||||||
|
px0 = max(1, int(round(kartenbild_w_mm / target_w)))
|
||||||
|
py0 = max(1, int(round(kartenbild_h_mm / target_h)))
|
||||||
|
|
||||||
|
for pages_x in range(max(1, px0 - 3), px0 + 4):
|
||||||
|
for pages_y in range(max(1, py0 - 3), py0 + 4):
|
||||||
|
tile_w = kartenbild_w_mm / pages_x
|
||||||
|
tile_h = kartenbild_h_mm / pages_y
|
||||||
|
if tile_w <= 0 or tile_h <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Im Blatt-Modus darf die resultierende Atlasseite die
|
||||||
|
# gewählte Zielgröße (inkl. Orientierung) nicht überschreiten.
|
||||||
|
if respect_max_sheet_size and (tile_w > target_w or tile_h > target_h):
|
||||||
|
continue
|
||||||
|
|
||||||
|
tile_aspect = tile_w / tile_h
|
||||||
|
# 0 bei perfekter Übereinstimmung; symmetrisch für >1/<1
|
||||||
|
aspect_error = abs(math.log(tile_aspect / target_aspect))
|
||||||
|
|
||||||
|
# Streifen bestrafen
|
||||||
|
strip_penalty = 0.0
|
||||||
|
if tile_aspect < 0.5:
|
||||||
|
strip_penalty = abs(math.log(tile_aspect / 0.5))
|
||||||
|
elif tile_aspect > 2.0:
|
||||||
|
strip_penalty = abs(math.log(tile_aspect / 2.0))
|
||||||
|
|
||||||
|
page_count = pages_x * pages_y
|
||||||
|
|
||||||
|
# Gewichtung: zuerst Formatnähe, dann Streifenvermeidung,
|
||||||
|
# danach Seitenzahl minimieren.
|
||||||
|
score = (aspect_error * 12.0) + (strip_penalty * 6.0) + (page_count * 0.20)
|
||||||
|
|
||||||
|
if score < best_score:
|
||||||
|
best_score = score
|
||||||
|
best_result = (pages_x, pages_y, tile_w, tile_h)
|
||||||
|
|
||||||
|
if best_result is None:
|
||||||
|
if respect_max_sheet_size:
|
||||||
|
fallback_candidates: list[tuple[int, int, float, float, int]] = []
|
||||||
|
for target_w, target_h in orientation_candidates:
|
||||||
|
if target_w <= 0 or target_h <= 0:
|
||||||
|
continue
|
||||||
|
pages_x = max(1, math.ceil(kartenbild_w_mm / target_w))
|
||||||
|
pages_y = max(1, math.ceil(kartenbild_h_mm / target_h))
|
||||||
|
tile_w = kartenbild_w_mm / pages_x
|
||||||
|
tile_h = kartenbild_h_mm / pages_y
|
||||||
|
fallback_candidates.append((pages_x, pages_y, tile_w, tile_h, pages_x * pages_y))
|
||||||
|
|
||||||
|
if fallback_candidates:
|
||||||
|
fallback_candidates.sort(key=lambda entry: (entry[4], abs(math.log((entry[2] / entry[3]) if entry[3] > 0 else 1.0))))
|
||||||
|
fx, fy, fw, fh, _ = fallback_candidates[0]
|
||||||
|
return fx, fy, fw, fh
|
||||||
|
|
||||||
|
return 1, 1, kartenbild_w_mm, kartenbild_h_mm
|
||||||
|
|
||||||
|
return best_result
|
||||||
|
|
||||||
|
def _create_atlasobjekte_layer(
|
||||||
|
self,
|
||||||
|
layer: Any,
|
||||||
|
extent: Any,
|
||||||
|
pages_x: int,
|
||||||
|
pages_y: int,
|
||||||
|
seite_karte_w: float,
|
||||||
|
seite_karte_h: float,
|
||||||
|
massstab_zahl: float,
|
||||||
|
) -> Any | None:
|
||||||
|
layer_crs = layer.crs() if hasattr(layer, "crs") else None
|
||||||
|
crs_authid = layer_crs.authid() if layer_crs is not None and hasattr(layer_crs, "authid") else "EPSG:25832"
|
||||||
|
atlas_layer = QgsVectorLayer(f"Polygon?crs={crs_authid}", "Atlasobjekte", "memory")
|
||||||
|
if not atlas_layer or not atlas_layer.isValid():
|
||||||
|
return None
|
||||||
|
|
||||||
|
provider = atlas_layer.dataProvider()
|
||||||
|
provider.addAttributes([
|
||||||
|
QgsField("Seitenzahl", QVariant.Int),
|
||||||
|
])
|
||||||
|
atlas_layer.updateFields()
|
||||||
|
|
||||||
|
tile_w_m = seite_karte_w * massstab_zahl / 1000.0
|
||||||
|
tile_h_m = seite_karte_h * massstab_zahl / 1000.0
|
||||||
|
|
||||||
|
x_min = extent.xMinimum()
|
||||||
|
y_min = extent.yMinimum()
|
||||||
|
x_max = extent.xMaximum()
|
||||||
|
y_max = extent.yMaximum()
|
||||||
|
|
||||||
|
seitenzahl = 1
|
||||||
|
features = []
|
||||||
|
for row_idx in range(pages_y):
|
||||||
|
tile_y_max = y_max - row_idx * tile_h_m
|
||||||
|
tile_y_min = tile_y_max - tile_h_m
|
||||||
|
|
||||||
|
for col_idx in range(pages_x):
|
||||||
|
tile_x_min = x_min + col_idx * tile_w_m
|
||||||
|
tile_x_max = tile_x_min + tile_w_m
|
||||||
|
|
||||||
|
tile_geom = QgsGeometry.fromWkt(
|
||||||
|
self._wkt_rect(tile_x_min, tile_y_min, tile_x_max, tile_y_max)
|
||||||
|
)
|
||||||
|
|
||||||
|
feat = QgsFeature(atlas_layer.fields())
|
||||||
|
feat.setGeometry(tile_geom)
|
||||||
|
feat["Seitenzahl"] = seitenzahl
|
||||||
|
features.append(feat)
|
||||||
|
seitenzahl += 1
|
||||||
|
|
||||||
|
if not features:
|
||||||
|
return None
|
||||||
|
|
||||||
|
provider.addFeatures(features)
|
||||||
|
atlas_layer.updateExtents()
|
||||||
|
self._set_topological_editing_enabled()
|
||||||
|
|
||||||
|
verfahrens_db = get_variable("verfahrens_db", scope="project") or ""
|
||||||
|
print(f"[TabBLogic] Atlasobjekte: verfahrens_db='{verfahrens_db}'")
|
||||||
|
if not verfahrens_db:
|
||||||
|
QgsProject.instance().addMapLayer(atlas_layer)
|
||||||
|
self._apply_atlas_style(atlas_layer)
|
||||||
|
print("[TabBLogic] Atlasobjekte temporär ins Projekt geladen")
|
||||||
|
return atlas_layer
|
||||||
|
|
||||||
|
opts = QgsVectorFileWriter.SaveVectorOptions()
|
||||||
|
opts.driverName = "GPKG"
|
||||||
|
opts.fileEncoding = "UTF-8"
|
||||||
|
opts.layerName = "Atlasobjekte"
|
||||||
|
if file_exists(verfahrens_db):
|
||||||
|
opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
|
||||||
|
else:
|
||||||
|
opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile
|
||||||
|
|
||||||
|
err_result = QgsVectorFileWriter.writeAsVectorFormatV3(
|
||||||
|
atlas_layer,
|
||||||
|
verfahrens_db,
|
||||||
|
QgsProject.instance().transformContext(),
|
||||||
|
opts,
|
||||||
|
)
|
||||||
|
|
||||||
|
# QGIS-Versionen liefern hier entweder nur WriterError
|
||||||
|
# oder ein Tupel (WriterError, msg, newPath, layerName).
|
||||||
|
err_code = err_result[0] if isinstance(err_result, tuple) else err_result
|
||||||
|
if err_code != QgsVectorFileWriter.NoError:
|
||||||
|
print(f"[TabBLogic] Atlasobjekte schreiben fehlgeschlagen: err={err_result}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for existing in QgsProject.instance().mapLayersByName("Atlasobjekte"):
|
||||||
|
try:
|
||||||
|
QgsProject.instance().removeMapLayer(existing.id())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
uri = f"{verfahrens_db}|layername=Atlasobjekte"
|
||||||
|
loaded_layer = QgsVectorLayer(uri, "Atlasobjekte", "ogr")
|
||||||
|
if not loaded_layer or not loaded_layer.isValid():
|
||||||
|
print(f"[TabBLogic] Atlasobjekte laden aus GPKG fehlgeschlagen: uri='{uri}'")
|
||||||
|
return None
|
||||||
|
|
||||||
|
QgsProject.instance().addMapLayer(loaded_layer)
|
||||||
|
self._apply_atlas_style(loaded_layer)
|
||||||
|
print("[TabBLogic] Atlasobjekte aus Verfahrens-DB geladen und gestylt")
|
||||||
|
return loaded_layer
|
||||||
|
|
||||||
|
def set_kartenname_for_auswahl(self, auswahl: str) -> None:
|
||||||
|
"""Setzt die Projektvariable ``sn_kartenname`` anhand der Kartennamen-Auswahl."""
|
||||||
|
kartenname = KARTENNAME_BY_AUSWAHL.get(auswahl, "")
|
||||||
|
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."""
|
||||||
|
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:
|
||||||
|
"""Setzt ``sn_view`` auf ``aktuell`` oder den Namen des gewählten Layerthemas."""
|
||||||
|
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:
|
||||||
|
"""Setzt ``sn_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``."""
|
||||||
|
set_variable(FORMFAKTOR_VAR, "Endlosrolle" if endlosrolle else "Blatt", scope="project")
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# Pipeline: Druckvorlage_anlegen
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def druckvorlage_anlegen(
|
||||||
|
self,
|
||||||
|
layer: object,
|
||||||
|
kartenname_auswahl: str,
|
||||||
|
massstab_auswahl: str,
|
||||||
|
zielgroesse: str,
|
||||||
|
formfaktor: bool,
|
||||||
|
) -> dict:
|
||||||
|
"""Pipeline 'Druckvorlage_anlegen'.
|
||||||
|
|
||||||
|
Prüft Parameter, berechnet Plotgröße und stellt bei Bedarf Atlas-Rückfrage.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
layer:
|
||||||
|
Aktuell gewählter Verfahrensgebiet-Layer aus Tab A.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
``ok`` (bool): Ob die Pipeline erfolgreich durchlaufen werden soll.
|
||||||
|
``switch_to_tab_a`` (bool): Ob Tab A aktiviert werden soll.
|
||||||
|
``atlas_seiten`` (int): Anzahl benötigter Seiten (1 = kein Atlas).
|
||||||
|
"""
|
||||||
|
# ─── 1. Verfahrensgebiet-Layer prüfen ─────────────────────────────
|
||||||
|
lp = Layerpruefer(layer=layer)
|
||||||
|
ergebnis = lp.pruefe()
|
||||||
|
if not ergebnis.ok:
|
||||||
|
self.pruefmanager.zeige_hinweis(
|
||||||
|
"Verfahrensgebiets-Layer angeben",
|
||||||
|
"Verfahrensgebiets-Layer angeben",
|
||||||
|
)
|
||||||
|
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")
|
||||||
|
|
||||||
|
# ─── 2. Kartenname prüfen ─────────────────────────────────────────
|
||||||
|
if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41):
|
||||||
|
self.pruefmanager.zeige_hinweis(
|
||||||
|
"Kartennamen wählen",
|
||||||
|
"Kartennamen wählen",
|
||||||
|
)
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||||
|
|
||||||
|
# ─── 3. Maßstab ermitteln ─────────────────────────────────────────
|
||||||
|
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(
|
||||||
|
"Maßstab fehlt",
|
||||||
|
"Kein gültiger Maßstab angegeben.",
|
||||||
|
)
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||||
|
|
||||||
|
# ─── 4. Kartenbild berechnen ──────────────────────────────────────
|
||||||
|
# Der Layer wird als metrisch projiziert (Einheit: m) vorausgesetzt,
|
||||||
|
# wie es für deutsche Planungslagen (z.B. EPSG:25832) üblich ist.
|
||||||
|
extent = get_layer_extent(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}
|
||||||
|
|
||||||
|
# Naturgröße (m) → Papiergröße (mm): mm = m * 1000 / massstab
|
||||||
|
kartenbild_w = extent.width() * 1000.0 / massstab_zahl
|
||||||
|
kartenbild_h = extent.height() * 1000.0 / massstab_zahl
|
||||||
|
|
||||||
|
# ─── 5. Plotgröße = Kartenbild + Randabstand (x+210 mm, y+20 mm) ──
|
||||||
|
plotgroesse_w = kartenbild_w + 210.0
|
||||||
|
plotgroesse_h = kartenbild_h + 20.0
|
||||||
|
|
||||||
|
# ─── 6. Zielgröße bestimmen ───────────────────────────────────────
|
||||||
|
din_dims = DIN_GROESSEN.get(zielgroesse, DIN_GROESSEN[DIN_STANDARD])
|
||||||
|
if formfaktor: # Endlosrolle: X-Richtung entspricht der Plotgröße
|
||||||
|
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(
|
||||||
|
"Blattgröße zu klein",
|
||||||
|
"Die Zielgröße darf nicht kleiner als DIN A4 sein.",
|
||||||
|
)
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||||
|
|
||||||
|
# ─── 7. Passt auf ein Blatt? -> Layout erzeugen ───────────────────
|
||||||
|
print(f"[TabBLogic] plotgroesse=({plotgroesse_w:.1f}x{plotgroesse_h:.1f}), "
|
||||||
|
f"zielgroesse=({ziel_w:.1f}x{ziel_h:.1f}), passt={plotgroesse_w <= ziel_w and plotgroesse_h <= ziel_h}")
|
||||||
|
if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h:
|
||||||
|
if plotgroesse_w <= ziel_h and plotgroesse_h <= ziel_w:
|
||||||
|
ziel_w, ziel_h = ziel_h, ziel_w
|
||||||
|
|
||||||
|
if plotgroesse_w <= ziel_w and plotgroesse_h <= ziel_h:
|
||||||
|
kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get(
|
||||||
|
kartenname_auswahl, "Vorlage"
|
||||||
|
)
|
||||||
|
thema = get_variable(VIEW_VAR, scope="project") or ""
|
||||||
|
print(f"[TabBLogic] frage_text aufrufen, default='{kartenname}', thema='{thema}'")
|
||||||
|
vorlage_name, bestaetigt = self.pruefmanager.frage_text(
|
||||||
|
"Neue Vorlage anlegen",
|
||||||
|
"Bezeichnung der Vorlage:",
|
||||||
|
default_text=kartenname,
|
||||||
|
)
|
||||||
|
print(f"[TabBLogic] frage_text Ergebnis: name='{vorlage_name}', bestaetigt={bestaetigt}")
|
||||||
|
if not bestaetigt:
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||||
|
|
||||||
|
vorlage_name = (vorlage_name or "").strip() or kartenname
|
||||||
|
|
||||||
|
print(f"[TabBLogic] Rufe Layout().create_single_page_layout auf: name='{vorlage_name}', "
|
||||||
|
f"page=({ziel_w}x{ziel_h}), map=({kartenbild_w:.1f}x{kartenbild_h:.1f}), "
|
||||||
|
f"massstab={massstab_zahl}, thema='{thema}'")
|
||||||
|
try:
|
||||||
|
Layout().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,
|
||||||
|
)
|
||||||
|
print("[TabBLogic] create_single_page_layout erfolgreich abgeschlossen")
|
||||||
|
except ValueError as exc:
|
||||||
|
print(f"[TabBLogic] ValueError: {exc}")
|
||||||
|
self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc))
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"[TabBLogic] Exception: {exc!r}")
|
||||||
|
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}
|
||||||
|
|
||||||
|
# ─── 8. Atlas: Anzahl Seiten berechnen ────────────────────────────
|
||||||
|
# Nutzbarer Kartenbereich pro Atlasseite (abzüglich gleichem Randabstand)
|
||||||
|
seite_karte_w = ziel_w - 210.0
|
||||||
|
seite_karte_h = ziel_h - 20.0
|
||||||
|
|
||||||
|
if seite_karte_w <= 0 or seite_karte_h <= 0:
|
||||||
|
self.pruefmanager.zeige_hinweis(
|
||||||
|
"Blattgröße zu klein",
|
||||||
|
"Die gewählte Zielgröße ist kleiner als der Mindest-Randabstand. "
|
||||||
|
"Bitte eine größere Blattgröße wählen.",
|
||||||
|
)
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||||
|
|
||||||
|
pages_x = math.ceil(kartenbild_w / seite_karte_w)
|
||||||
|
pages_y = math.ceil(kartenbild_h / seite_karte_h)
|
||||||
|
|
||||||
|
# Für Atlas in beiden Modi (Endlosrolle + Blatt): Seitenraster so wählen,
|
||||||
|
# dass Atlasobjekte näher am Einzelblattformat liegen und keine Streifen entstehen.
|
||||||
|
opt_pages_x, opt_pages_y, opt_tile_w_mm, opt_tile_h_mm = self._find_tile_grid_for_roll_atlas(
|
||||||
|
kartenbild_w_mm=kartenbild_w,
|
||||||
|
kartenbild_h_mm=kartenbild_h,
|
||||||
|
din_dims=din_dims,
|
||||||
|
respect_max_sheet_size=(not formfaktor),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Endlosrolle: Nur die Breite darf wachsen, die Höhe muss innerhalb
|
||||||
|
# der gewählten Zielhöhe bleiben.
|
||||||
|
if formfaktor:
|
||||||
|
max_tile_h_mm = max(1.0, ziel_h - 20.0)
|
||||||
|
if opt_tile_h_mm > max_tile_h_mm:
|
||||||
|
required_pages_y = max(1, math.ceil(kartenbild_h / max_tile_h_mm))
|
||||||
|
opt_pages_y = max(opt_pages_y, required_pages_y)
|
||||||
|
opt_tile_h_mm = kartenbild_h / opt_pages_y
|
||||||
|
|
||||||
|
pages_x = max(1, opt_pages_x)
|
||||||
|
pages_y = max(1, opt_pages_y)
|
||||||
|
seite_karte_w = max(1.0, opt_tile_w_mm)
|
||||||
|
seite_karte_h = max(1.0, opt_tile_h_mm)
|
||||||
|
modus = "Endlosrolle" if formfaktor else "Blatt"
|
||||||
|
print(
|
||||||
|
f"[TabBLogic] {modus} Rasteroptimierung: pages_x={pages_x}, pages_y={pages_y}, "
|
||||||
|
f"tile_mm=({seite_karte_w:.1f}x{seite_karte_h:.1f}), "
|
||||||
|
f"tile_aspect={seite_karte_w / seite_karte_h:.3f}"
|
||||||
|
)
|
||||||
|
if formfaktor:
|
||||||
|
max_tile_h_mm = max(1.0, ziel_h - 20.0)
|
||||||
|
print(
|
||||||
|
f"[TabBLogic] Endlosrolle Höhenlimit: tile_h={seite_karte_h:.1f}mm, "
|
||||||
|
f"max_tile_h={max_tile_h_mm:.1f}mm, within_limit={seite_karte_h <= max_tile_h_mm}"
|
||||||
|
)
|
||||||
|
|
||||||
|
anzahl_seiten = pages_x * pages_y
|
||||||
|
|
||||||
|
ja = self.pruefmanager.frage_ja_nein(
|
||||||
|
"Ausdruck als Atlas anlegen?",
|
||||||
|
f"Für die ausgewählten Parameter sind {anzahl_seiten} Einzelseiten erforderlich.\n"
|
||||||
|
"Ausdruck als Atlas anlegen?",
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
print(f"[TabBLogic] Atlas-Rückfrage: ja={ja}, geplante_seiten={anzahl_seiten}")
|
||||||
|
if not ja:
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": anzahl_seiten}
|
||||||
|
|
||||||
|
atlas_layer = self._create_atlasobjekte_layer(
|
||||||
|
layer=layer,
|
||||||
|
extent=extent,
|
||||||
|
pages_x=pages_x,
|
||||||
|
pages_y=pages_y,
|
||||||
|
seite_karte_w=seite_karte_w,
|
||||||
|
seite_karte_h=seite_karte_h,
|
||||||
|
massstab_zahl=massstab_zahl,
|
||||||
|
)
|
||||||
|
if atlas_layer is None:
|
||||||
|
self.pruefmanager.zeige_hinweis(
|
||||||
|
"Atlasobjekte",
|
||||||
|
"Atlasobjekte-Layer konnte nicht erzeugt werden.",
|
||||||
|
)
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||||
|
print("[TabBLogic] Atlasobjekte-Layer erfolgreich erzeugt")
|
||||||
|
|
||||||
|
try:
|
||||||
|
anzahl_seiten = int(atlas_layer.featureCount())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Berechne die Kachelgröße aus den Atlasobjekten.
|
||||||
|
# Für die Layout-Kartengröße muss die größte Atlas-Kachel passen,
|
||||||
|
# sonst werden größere Atlasobjekte abgeschnitten.
|
||||||
|
total_w_m = 0.0
|
||||||
|
total_h_m = 0.0
|
||||||
|
min_w_m = math.inf
|
||||||
|
min_h_m = math.inf
|
||||||
|
max_w_m = 0.0
|
||||||
|
max_h_m = 0.0
|
||||||
|
feature_count = 0
|
||||||
|
get_features = getattr(atlas_layer, "getFeatures", None)
|
||||||
|
if callable(get_features):
|
||||||
|
try:
|
||||||
|
for feat in get_features():
|
||||||
|
geom = feat.geometry() if hasattr(feat, "geometry") else None
|
||||||
|
if geom is None or geom.isEmpty():
|
||||||
|
continue
|
||||||
|
bbox = geom.boundingBox() if hasattr(geom, "boundingBox") else None
|
||||||
|
if bbox is None:
|
||||||
|
continue
|
||||||
|
feat_w_m = float(bbox.width())
|
||||||
|
feat_h_m = float(bbox.height())
|
||||||
|
total_w_m += feat_w_m
|
||||||
|
total_h_m += feat_h_m
|
||||||
|
min_w_m = min(min_w_m, feat_w_m)
|
||||||
|
min_h_m = min(min_h_m, feat_h_m)
|
||||||
|
max_w_m = max(max_w_m, feat_w_m)
|
||||||
|
max_h_m = max(max_h_m, feat_h_m)
|
||||||
|
feature_count += 1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if feature_count > 0:
|
||||||
|
avg_tile_w_m = total_w_m / feature_count
|
||||||
|
avg_tile_h_m = total_h_m / feature_count
|
||||||
|
if not math.isfinite(min_w_m):
|
||||||
|
min_w_m = avg_tile_w_m
|
||||||
|
if not math.isfinite(min_h_m):
|
||||||
|
min_h_m = avg_tile_h_m
|
||||||
|
target_tile_w_m = max_w_m
|
||||||
|
target_tile_h_m = max_h_m
|
||||||
|
else:
|
||||||
|
avg_tile_w_m = seite_karte_w * massstab_zahl / 1000.0
|
||||||
|
avg_tile_h_m = seite_karte_h * massstab_zahl / 1000.0
|
||||||
|
min_w_m = avg_tile_w_m
|
||||||
|
min_h_m = avg_tile_h_m
|
||||||
|
max_w_m = avg_tile_w_m
|
||||||
|
max_h_m = avg_tile_h_m
|
||||||
|
target_tile_w_m = avg_tile_w_m
|
||||||
|
target_tile_h_m = avg_tile_h_m
|
||||||
|
|
||||||
|
# Konvertiere Kachelgrößen zu mm
|
||||||
|
avg_tile_w_mm = avg_tile_w_m * 1000.0 / massstab_zahl
|
||||||
|
avg_tile_h_mm = avg_tile_h_m * 1000.0 / massstab_zahl
|
||||||
|
min_tile_w_mm = min_w_m * 1000.0 / massstab_zahl
|
||||||
|
min_tile_h_mm = min_h_m * 1000.0 / massstab_zahl
|
||||||
|
max_tile_w_mm = max_w_m * 1000.0 / massstab_zahl
|
||||||
|
max_tile_h_mm = max_h_m * 1000.0 / massstab_zahl
|
||||||
|
target_tile_w_mm = target_tile_w_m * 1000.0 / massstab_zahl
|
||||||
|
target_tile_h_mm = target_tile_h_m * 1000.0 / massstab_zahl
|
||||||
|
|
||||||
|
# Layout-Kartengröße = größte Kachelgröße
|
||||||
|
atlas_map_w = max(1.0, target_tile_w_mm)
|
||||||
|
atlas_map_h = max(1.0, target_tile_h_mm)
|
||||||
|
atlas_page_w = atlas_map_w + 210.0
|
||||||
|
atlas_page_h = atlas_map_h + 20.0
|
||||||
|
|
||||||
|
# Debug: Atlasobjekte Geometrien
|
||||||
|
print(
|
||||||
|
f"[TabBLogic] Atlasobjekte Geometrien (Meter): "
|
||||||
|
f"total_w={total_w_m:.1f}m, total_h={total_h_m:.1f}m, "
|
||||||
|
f"min_w={min_w_m:.1f}m, min_h={min_h_m:.1f}m, "
|
||||||
|
f"avg_w={avg_tile_w_m:.1f}m, avg_h={avg_tile_h_m:.1f}m, "
|
||||||
|
f"max_w={max_w_m:.1f}m, max_h={max_h_m:.1f}m, features={feature_count}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"[TabBLogic] Atlas layout Größen: "
|
||||||
|
f"page=(x=10, y=10, w={atlas_page_w:.1f}mm, h={atlas_page_h:.1f}mm), "
|
||||||
|
f"map=(x=10, y=10, w={atlas_map_w:.1f}mm, h={atlas_map_h:.1f}mm), "
|
||||||
|
f"kachel_min_mm=({min_tile_w_mm:.1f}x{min_tile_h_mm:.1f}), "
|
||||||
|
f"kachel_avg_mm=({avg_tile_w_mm:.1f}x{avg_tile_h_mm:.1f}), "
|
||||||
|
f"kachel_max_mm=({max_tile_w_mm:.1f}x{max_tile_h_mm:.1f})"
|
||||||
|
)
|
||||||
|
|
||||||
|
kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get(
|
||||||
|
kartenname_auswahl, "Atlas"
|
||||||
|
)
|
||||||
|
thema = get_variable(VIEW_VAR, scope="project") or ""
|
||||||
|
vorlage_name, bestaetigt = self.pruefmanager.frage_text(
|
||||||
|
"Neue Atlasvorlage anlegen",
|
||||||
|
"Bezeichnung der Vorlage:",
|
||||||
|
default_text=f"{kartenname} Atlas",
|
||||||
|
)
|
||||||
|
print(f"[TabBLogic] Atlas frage_text Ergebnis: name='{vorlage_name}', bestaetigt={bestaetigt}")
|
||||||
|
if not bestaetigt:
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": anzahl_seiten}
|
||||||
|
|
||||||
|
vorlage_name = (vorlage_name or "").strip() or f"{kartenname} Atlas"
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(
|
||||||
|
f"[TabBLogic] Rufe create_atlas_layout auf: name='{vorlage_name}', "
|
||||||
|
f"page=({atlas_page_w:.1f}x{atlas_page_h:.1f}), "
|
||||||
|
f"map=({atlas_map_w:.1f}x{atlas_map_h:.1f}), massstab={massstab_zahl}, thema='{thema}'"
|
||||||
|
)
|
||||||
|
Layout().create_atlas_layout(
|
||||||
|
name=vorlage_name,
|
||||||
|
page_width_mm=atlas_page_w,
|
||||||
|
page_height_mm=atlas_page_h,
|
||||||
|
map_width_mm=atlas_map_w,
|
||||||
|
map_height_mm=atlas_map_h,
|
||||||
|
extent=extent,
|
||||||
|
plotmassstab=massstab_zahl,
|
||||||
|
atlas_layer=atlas_layer,
|
||||||
|
thema=thema,
|
||||||
|
)
|
||||||
|
print("[TabBLogic] create_atlas_layout erfolgreich abgeschlossen")
|
||||||
|
except ValueError as exc:
|
||||||
|
self.pruefmanager.zeige_hinweis("Atlasvorlage anlegen", str(exc))
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||||
|
except Exception as exc:
|
||||||
|
self.pruefmanager.zeige_hinweis("Atlasvorlage anlegen", f"Die Atlasvorlage konnte nicht angelegt werden: {exc}")
|
||||||
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||||
|
|
||||||
|
return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": anzahl_seiten}
|
||||||
427
ui/tab_b_ui.py
Normal file
427
ui/tab_b_ui.py
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
"""
|
||||||
|
sn_plan41/ui/tab_b_ui.py – UI für Tab B (Druck)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import 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
|
||||||
|
|
||||||
|
# Services (werden von DockWidget injiziert)
|
||||||
|
from sn_basis.modules.Pruefmanager import Pruefmanager
|
||||||
|
from sn_basis.modules.DataGrabber import DataGrabber
|
||||||
|
from sn_plan41.ui.tab_b_logic import (
|
||||||
|
TabBLogic,
|
||||||
|
MASSSTAB_WIE_KARTENFENSTER,
|
||||||
|
PLOTMASSSTAB_BY_AUSWAHL,
|
||||||
|
THEMA_WIE_KARTENFENSTER,
|
||||||
|
DIN_GROESSEN,
|
||||||
|
DIN_STANDARD,
|
||||||
|
ZIELGROESSE_VAR,
|
||||||
|
FORMFAKTOR_VAR,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
KARTENNAME_VAR = "tab_b_kartenname"
|
||||||
|
KARTENNAME_PLACEHOLDER = "Kartenname wählen"
|
||||||
|
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 TabB(QWidget):
|
||||||
|
"""
|
||||||
|
UI-Klasse für Tab B (Druck) des Plan41-Plugins.
|
||||||
|
|
||||||
|
Zuständig für:
|
||||||
|
- Auswahl des Druckthemas
|
||||||
|
- Auswahl der Druckparameter
|
||||||
|
- Start der Vorlagenanlage (Druck über QGIS-Druckfunktion)
|
||||||
|
|
||||||
|
Services (Pruefmanager, DataGrabber) werden zur Laufzeit vom DockWidget injiziert.
|
||||||
|
Alle fachlichen Prüfungen laufen über den zentralen Pruefmanager.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tab_title = "Druck" #: Tab-Titel für BaseDockWidget
|
||||||
|
|
||||||
|
def __init__(self, parent: Optional[QWidget] = None):
|
||||||
|
"""
|
||||||
|
Initialisiert die UI-Struktur.
|
||||||
|
|
||||||
|
Services werden später über :meth:`set_services` injiziert.
|
||||||
|
|
||||||
|
:param parent: Parent-Widget (typischerweise DockWidget)
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
# Services (werden von DockWidget gesetzt)
|
||||||
|
self.pruefmanager: Optional[Pruefmanager] = None
|
||||||
|
|
||||||
|
self.logic: Optional[TabBLogic] = None
|
||||||
|
self._kartenname_combo: Optional[QComboBox] = None
|
||||||
|
self._massstab_combo: Optional[QComboBox] = None
|
||||||
|
self._thema_combo: Optional[QComboBox] = None
|
||||||
|
self._theme_signal_connected = False
|
||||||
|
self._connected_theme_collection: object = None # Referenz für sauberes Trennen
|
||||||
|
self._zielgroesse_combo: Optional[QComboBox] = None
|
||||||
|
self._endlosrolle_cb: Optional[QCheckBox] = None
|
||||||
|
self._btn_vorlage_erstellen: Optional[QPushButton] = 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:
|
||||||
|
"""Injiziert Services vom übergeordneten DockWidget."""
|
||||||
|
_ = data_grabber
|
||||||
|
self.pruefmanager = pruefmanager
|
||||||
|
self.logic = TabBLogic(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:
|
||||||
|
"""Erstellt die reduzierte UI für die Themenauswahl."""
|
||||||
|
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)
|
||||||
|
|
||||||
|
self._kartenname_combo = QComboBox(self)
|
||||||
|
self._kartenname_combo.addItem(KARTENNAME_PLACEHOLDER)
|
||||||
|
self._kartenname_combo.addItem(KARTENNAME_38)
|
||||||
|
self._kartenname_combo.addItem(KARTENNAME_41)
|
||||||
|
self._kartenname_combo.currentTextChanged.connect(self._on_kartenname_changed)
|
||||||
|
main_layout.addWidget(self._kartenname_combo)
|
||||||
|
|
||||||
|
massstab_label = QLabel("Maßstab")
|
||||||
|
massstab_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||||||
|
main_layout.addWidget(massstab_label)
|
||||||
|
|
||||||
|
self._massstab_combo = QComboBox(self)
|
||||||
|
self._massstab_combo.addItem(MASSSTAB_WIE_KARTENFENSTER)
|
||||||
|
self._massstab_combo.addItems(list(PLOTMASSSTAB_BY_AUSWAHL.keys()))
|
||||||
|
self._massstab_combo.currentTextChanged.connect(self._on_massstab_changed)
|
||||||
|
main_layout.addWidget(self._massstab_combo)
|
||||||
|
|
||||||
|
thema_label = QLabel("Thema")
|
||||||
|
thema_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||||||
|
main_layout.addWidget(thema_label)
|
||||||
|
|
||||||
|
self._thema_combo = QComboBox(self)
|
||||||
|
self._thema_combo.addItem(THEMA_WIE_KARTENFENSTER)
|
||||||
|
self._thema_combo.addItems(self._get_gespeicherte_themen())
|
||||||
|
self._thema_combo.currentTextChanged.connect(self._on_thema_changed)
|
||||||
|
main_layout.addWidget(self._thema_combo)
|
||||||
|
|
||||||
|
zielgroesse_label = QLabel("max. Blattgröße")
|
||||||
|
zielgroesse_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||||||
|
main_layout.addWidget(zielgroesse_label)
|
||||||
|
|
||||||
|
zielgroesse_row = QHBoxLayout()
|
||||||
|
zielgroesse_row.setSpacing(6)
|
||||||
|
self._zielgroesse_combo = QComboBox(self)
|
||||||
|
self._zielgroesse_combo.addItems(list(DIN_GROESSEN.keys()))
|
||||||
|
self._zielgroesse_combo.setCurrentText(DIN_STANDARD)
|
||||||
|
self._zielgroesse_combo.currentTextChanged.connect(self._on_zielgroesse_changed)
|
||||||
|
zielgroesse_row.addWidget(self._zielgroesse_combo)
|
||||||
|
self._endlosrolle_cb = QCheckBox("Endlosrolle", self)
|
||||||
|
self._endlosrolle_cb.setChecked(False)
|
||||||
|
self._endlosrolle_cb.stateChanged.connect(self._on_formfaktor_changed)
|
||||||
|
zielgroesse_row.addWidget(self._endlosrolle_cb)
|
||||||
|
main_layout.addLayout(zielgroesse_row)
|
||||||
|
|
||||||
|
self._btn_vorlage_erstellen = QPushButton("Vorlage erstellen", self)
|
||||||
|
self._btn_vorlage_erstellen.clicked.connect(self._on_vorlage_erstellen)
|
||||||
|
main_layout.addWidget(self._btn_vorlage_erstellen)
|
||||||
|
|
||||||
|
main_layout.addStretch(1)
|
||||||
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
|
def _restore_state(self) -> None:
|
||||||
|
"""Stellt die gespeicherten Combobox-Zustände wieder her."""
|
||||||
|
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:
|
||||||
|
"""Persistiert die Kartennamen-Auswahl und setzt ``sn_kartenname``."""
|
||||||
|
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:
|
||||||
|
"""Persistiert Maßstabsauswahl und setzt ``sn_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``."""
|
||||||
|
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``."""
|
||||||
|
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``."""
|
||||||
|
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:
|
||||||
|
"""Startet die Pipeline Druckvorlage_anlegen."""
|
||||||
|
print("[TabB] _on_vorlage_erstellen aufgerufen")
|
||||||
|
if not self.logic or not self.pruefmanager:
|
||||||
|
print(f"[TabB] Abbruch: logic={self.logic}, pruefmanager={self.pruefmanager}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Layer direkt aus Tab A lesen (unabhängig von Projektvariable)
|
||||||
|
layer = None
|
||||||
|
tab_a = self._get_tab_a_widget()
|
||||||
|
print(f"[TabB] tab_a Widget: {tab_a!r}")
|
||||||
|
if tab_a is not None:
|
||||||
|
layer_combo = getattr(tab_a, "layer_combo", None)
|
||||||
|
print(f"[TabB] layer_combo: {layer_combo!r}")
|
||||||
|
if layer_combo is not None:
|
||||||
|
layer = layer_combo.currentLayer()
|
||||||
|
print(f"[TabB] layer: {layer!r} (Name: {getattr(layer, 'name', lambda: '–')()})")
|
||||||
|
|
||||||
|
print(f"[TabB] Rufe druckvorlage_anlegen auf mit layer={layer!r}, "
|
||||||
|
f"kartenname={self._kartenname_combo.currentText() if self._kartenname_combo else '?'}, "
|
||||||
|
f"massstab={self._massstab_combo.currentText() if self._massstab_combo else '?'}, "
|
||||||
|
f"zielgroesse={self._zielgroesse_combo.currentText() if self._zielgroesse_combo else '?'}, "
|
||||||
|
f"formfaktor={self._endlosrolle_cb.isChecked() if self._endlosrolle_cb else '?'}")
|
||||||
|
|
||||||
|
result = self.logic.druckvorlage_anlegen(
|
||||||
|
layer=layer,
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"[TabB] druckvorlage_anlegen Ergebnis: {result}")
|
||||||
|
if result.get("switch_to_tab_a"):
|
||||||
|
self._aktiviere_tab_a()
|
||||||
|
|
||||||
|
def _get_tab_widget(self):
|
||||||
|
"""Findet das übergeordnete QTabWidget anhand des ``tabBar``-Attributs."""
|
||||||
|
try:
|
||||||
|
widget = self.parent()
|
||||||
|
while widget is not None:
|
||||||
|
if hasattr(widget, "tabBar") and hasattr(widget, "setCurrentIndex"):
|
||||||
|
return widget
|
||||||
|
parent_fn = getattr(widget, "parent", None)
|
||||||
|
widget = parent_fn() if callable(parent_fn) else None
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_tab_a_widget(self):
|
||||||
|
"""Gibt die Tab-A-Widget-Instanz zurück (Index 0 im übergeordneten QTabWidget)."""
|
||||||
|
tab_widget = self._get_tab_widget()
|
||||||
|
if tab_widget is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return tab_widget.widget(0)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _aktiviere_tab_a(self) -> None:
|
||||||
|
"""Wechselt den aktiven Reiter auf Tab A im übergeordneten QTabWidget."""
|
||||||
|
tab_widget = self._get_tab_widget()
|
||||||
|
if tab_widget is not None:
|
||||||
|
try:
|
||||||
|
tab_widget.setCurrentIndex(0)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _connect_project_signals(self) -> None:
|
||||||
|
"""Verbindet QgsProject-Signale für Projektwechsel/-neuladen."""
|
||||||
|
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:
|
||||||
|
"""Reagiert auf Projektwechsel: Signale neu binden, Combobox und State auffrischen."""
|
||||||
|
_ = args
|
||||||
|
# Alte Theme-Collection-Signals zuerst trennen
|
||||||
|
self._disconnect_theme_collection_signals()
|
||||||
|
# Neu verbinden für das jetzt geladene Projekt
|
||||||
|
self._theme_signal_connected = False
|
||||||
|
self._connect_theme_collection_signals()
|
||||||
|
# Ansicht-Liste + gespeicherten State wiederherstellen
|
||||||
|
self._restore_state()
|
||||||
|
|
||||||
|
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
|
||||||
|
self._connected_theme_collection = None
|
||||||
|
|
||||||
|
def _connect_theme_collection_signals(self) -> None:
|
||||||
|
"""Verbindet Signale der Theme-Collection für Live-Aktualisierung der Themenliste."""
|
||||||
|
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:
|
||||||
|
"""Aktualisiert die Thema-Combobox bei Änderungen gespeicherter Layerthemen."""
|
||||||
|
_ = 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]:
|
||||||
|
"""Liefert die Namen der im Projekt gespeicherten Layerthemen (QgsMapThemeCollection)."""
|
||||||
|
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:
|
||||||
|
"""Liest den aktuellen Maßstab aus der Kartensicht."""
|
||||||
|
try:
|
||||||
|
canvas = iface.mapCanvas()
|
||||||
|
if canvas is None:
|
||||||
|
return None
|
||||||
|
scale = canvas.scale()
|
||||||
|
return float(scale) if scale else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
Reference in New Issue
Block a user