Anpassung an den Wrappern für sn_plan41

This commit is contained in:
2026-01-08 17:13:51 +01:00
parent f88b5da51f
commit b805f78f02
26 changed files with 401 additions and 125 deletions

View File

@@ -1,20 +1,31 @@
# sn_basis/functions/ly_existence_wrapper.py
def layer_exists(layer) -> bool:
"""
Prüft, ob ein Layer-Objekt existiert (nicht None).
"""
return layer is not None
def layer_is_valid(layer) -> bool:
"""
Prüft, ob ein Layer gültig ist (QGIS-konform).
"""
if layer is None:
return False
is_valid_flag = getattr(layer, "is_valid", None)
if is_valid_flag is not None:
is_valid = getattr(layer, "isValid", None)
if callable(is_valid):
try:
return bool(is_valid_flag)
return bool(is_valid())
except Exception:
return False
try:
is_valid = getattr(layer, "isValid", None)
if callable(is_valid):
return bool(is_valid())
return True
except Exception:
return False
def layer_is_usable(layer) -> bool:
"""
Prüft, ob ein Layer existiert und gültig ist.
"""
return layer_exists(layer) and layer_is_valid(layer)

View File

@@ -1,48 +1,57 @@
# sn_basis/functions/ly_geometry_wrapper.py
def get_layer_geometry_type(layer) -> str:
if layer is None:
return "None"
from typing import Optional
geometry_type = getattr(layer, "geometry_type", None)
if geometry_type is not None:
return str(geometry_type)
GEOM_NONE = None
GEOM_POINT = "Point"
GEOM_LINE = "LineString"
GEOM_POLYGON = "Polygon"
def get_layer_geometry_type(layer) -> Optional[str]:
"""
Gibt den Geometrietyp eines Layers zurück.
Rückgabewerte:
- "Point"
- "LineString"
- "Polygon"
- None (nicht räumlich / ungültig / unbekannt)
"""
if layer is None:
return None
try:
if callable(getattr(layer, "isSpatial", None)) and not layer.isSpatial():
return "None"
is_spatial = getattr(layer, "isSpatial", None)
if callable(is_spatial) and not is_spatial():
return None
gtype = getattr(layer, "geometryType", None)
if callable(gtype):
value = gtype()
if not isinstance(value, int):
return "None"
return {
0: "Point",
1: "LineString",
2: "Polygon",
}.get(value, "None")
if value == 0:
return GEOM_POINT
if value == 1:
return GEOM_LINE
if value == 2:
return GEOM_POLYGON
except Exception:
pass
return "None"
return None
def get_layer_feature_count(layer) -> int:
"""
Gibt die Anzahl der Features eines Layers zurück.
"""
if layer is None:
return 0
count = getattr(layer, "feature_count", None)
if count is not None:
if isinstance(count, int):
return count
return 0
try:
if callable(getattr(layer, "isSpatial", None)) and not layer.isSpatial():
is_spatial = getattr(layer, "isSpatial", None)
if callable(is_spatial) and not is_spatial():
return 0
fc = getattr(layer, "featureCount", None)
@@ -54,4 +63,3 @@ def get_layer_feature_count(layer) -> int:
pass
return 0

View File

@@ -1,35 +1,44 @@
# layer/metadata.py
# sn_basis/functions/ly_metadata_wrapper.py
def get_layer_type(layer) -> str:
from typing import Optional, List
LAYER_TYPE_VECTOR = "vector"
LAYER_TYPE_TABLE = "table"
def get_layer_type(layer) -> Optional[str]:
"""
Gibt den Layer-Typ zurück.
Rückgabewerte:
- "vector"
- "table"
- None (unbekannt / nicht bestimmbar)
"""
if layer is None:
return "unknown"
layer_type = getattr(layer, "layer_type", None)
if layer_type is not None:
return str(layer_type)
return None
try:
if callable(getattr(layer, "isSpatial", None)):
return "vector" if layer.isSpatial() else "table"
is_spatial = getattr(layer, "isSpatial", None)
if callable(is_spatial):
return LAYER_TYPE_VECTOR if is_spatial() else LAYER_TYPE_TABLE
except Exception:
pass
return "unknown"
return None
def get_layer_crs(layer) -> str:
def get_layer_crs(layer) -> Optional[str]:
"""
Gibt das CRS als AuthID zurück (z.B. 'EPSG:25833').
"""
if layer is None:
return "None"
crs = getattr(layer, "crs", None)
if crs is not None and not callable(crs):
if isinstance(crs, str):
return crs
return "None"
return None
try:
crs_obj = layer.crs()
authid = getattr(crs_obj, "authid", None)
crs = layer.crs()
authid = getattr(crs, "authid", None)
if callable(authid):
value = authid()
if isinstance(value, str):
@@ -37,49 +46,47 @@ def get_layer_crs(layer) -> str:
except Exception:
pass
return "None"
return None
def get_layer_fields(layer) -> list[str]:
def get_layer_fields(layer) -> List[str]:
"""
Gibt die Feldnamen eines Layers zurück.
"""
if layer is None:
return []
fields = getattr(layer, "fields", None)
if fields is not None and not callable(fields):
return list(fields)
try:
f = layer.fields()
if callable(getattr(f, "names", None)):
return list(f.names())
return list(f)
return list(layer.fields().names())
except Exception:
return []
def get_layer_source(layer) -> str:
if layer is None:
return "None"
source = getattr(layer, "source", None)
if source is not None and not callable(source):
return str(source)
def get_layer_source(layer) -> Optional[str]:
"""
Gibt die Datenquelle eines Layers zurück.
"""
if layer is None:
return None
try:
return layer.source() or "None"
value = layer.source()
if isinstance(value, str) and value:
return value
except Exception:
return "None"
pass
return None
def is_layer_editable(layer) -> bool:
"""
Prüft, ob ein Layer editierbar ist.
"""
if layer is None:
return False
editable = getattr(layer, "editable", None)
if editable is not None:
return bool(editable)
try:
is_editable = getattr(layer, "isEditable", None)
if callable(is_editable):

View File

@@ -1,4 +1,4 @@
# layer/style.py
# sn_basis/functions/ly_style_wrapper.py
from sn_basis.functions.ly_existence_wrapper import layer_exists
from sn_basis.functions.sys_wrapper import (

View File

@@ -1,15 +1,17 @@
# sn_basis/functions/ly_visibility_wrapper.py
def is_layer_visible(layer) -> bool:
"""
Prüft, ob ein Layer im Layer-Tree sichtbar ist.
"""
if layer is None:
return False
visible = getattr(layer, "visible", None)
if visible is not None:
return bool(visible)
try:
is_visible = getattr(layer, "isVisible", None)
node = getattr(layer, "treeLayer", None)
if callable(node):
tree_node = node()
is_visible = getattr(tree_node, "isVisible", None)
if callable(is_visible):
return bool(is_visible())
except Exception:
@@ -19,20 +21,19 @@ def is_layer_visible(layer) -> bool:
def set_layer_visible(layer, visible: bool) -> bool:
"""
Setzt die Sichtbarkeit eines Layers im Layer-Tree.
"""
if layer is None:
return False
try:
if hasattr(layer, "visible"):
layer.visible = bool(visible)
return True
except Exception:
pass
try:
node = getattr(layer, "treeLayer", lambda: None)()
if node and callable(getattr(node, "setItemVisibilityChecked", None)):
node.setItemVisibilityChecked(bool(visible))
node = getattr(layer, "treeLayer", None)
if callable(node):
tree_node = node()
setter = getattr(tree_node, "setItemVisibilityChecked", None)
if callable(setter):
setter(bool(visible))
return True
except Exception:
pass

View File

@@ -18,6 +18,7 @@ QgsProject: Type[Any]
QgsVectorLayer: Type[Any]
QgsNetworkAccessManager: Type[Any]
Qgis: Type[Any]
QgsMapLayerProxyModel: Type[Any]
QGIS_AVAILABLE = False
@@ -31,12 +32,14 @@ try:
QgsVectorLayer as _QgsVectorLayer,
QgsNetworkAccessManager as _QgsNetworkAccessManager,
Qgis as _Qgis,
QgsMapLayerProxyModel as _QgsMaplLayerProxyModel
)
QgsProject = _QgsProject
QgsVectorLayer = _QgsVectorLayer
QgsNetworkAccessManager = _QgsNetworkAccessManager
Qgis = _Qgis
QgsMapLayerProxyModel=_QgsMaplLayerProxyModel
QGIS_AVAILABLE = True
@@ -94,6 +97,20 @@ except Exception:
Qgis = _MockQgis
class _MockQgsMapLayerProxyModel:
# Layer-Typen (entsprechen QGIS-Konstanten)
NoLayer = 0
VectorLayer = 1
RasterLayer = 2
PluginLayer = 3
MeshLayer = 4
VectorTileLayer = 5
PointCloudLayer = 6
def __init__(self, *args, **kwargs):
pass
QgsMapLayerProxyModel = _MockQgsMapLayerProxyModel
# ---------------------------------------------------------
# Netzwerk

View File

@@ -2,7 +2,10 @@
sn_basis/functions/qgisui_wrapper.py zentrale QGIS-UI-Abstraktion
"""
from typing import Any, List
from __future__ import annotations
from typing import Any, List, Type
from sn_basis.functions.qt_wrapper import QDockWidget
@@ -10,18 +13,39 @@ from sn_basis.functions.qt_wrapper import QDockWidget
iface: Any
QGIS_UI_AVAILABLE = False
QgsFileWidget: Type[Any]
QgsMapLayerComboBox: Type[Any]
# ---------------------------------------------------------
# iface initialisieren (QGIS oder Mock)
# iface + QGIS-Widgets initialisieren (QGIS oder Mock)
# ---------------------------------------------------------
try:
from qgis.utils import iface as _iface
from qgis.gui import (
QgsFileWidget as _QgsFileWidget,
QgsMapLayerComboBox as _QgsMapLayerComboBox,
)
iface = _iface
QgsFileWidget = _QgsFileWidget
QgsMapLayerComboBox = _QgsMapLayerComboBox
QGIS_UI_AVAILABLE = True
except Exception:
QGIS_UI_AVAILABLE = False
class _MockSignal:
def __init__(self):
self._callbacks: list[Any] = []
def connect(self, callback):
self._callbacks.append(callback)
def emit(self, *args, **kwargs):
for cb in list(self._callbacks):
cb(*args, **kwargs)
class _MockMessageBar:
def pushMessage(self, title, text, level=0, duration=5):
@@ -53,6 +77,48 @@ except Exception:
iface = _MockIface()
class _MockQgsFileWidget:
GetFile = 0
def __init__(self, *args, **kwargs):
self._path = ""
self.fileChanged = _MockSignal()
def setStorageMode(self, *args, **kwargs):
pass
def setFilter(self, *args, **kwargs):
pass
def setFilePath(self, path: str):
self._path = path
self.fileChanged.emit(path)
def filePath(self) -> str:
return self._path
class _MockQgsMapLayerComboBox:
def __init__(self, *args, **kwargs):
self.layerChanged = _MockSignal()
self._layer = None
self._count = 0
def setFilters(self, *args, **kwargs):
pass
def setLayer(self, layer):
self._layer = layer
self.layerChanged.emit(layer)
def count(self) -> int:
return self._count
def setCurrentIndex(self, idx: int):
pass
QgsFileWidget = _MockQgsFileWidget
QgsMapLayerComboBox = _MockQgsMapLayerComboBox
# ---------------------------------------------------------
# Main Window
@@ -108,8 +174,6 @@ def add_menu(menu):
main_window.menuBar().addMenu(menu)
def remove_menu(menu):
main_window = iface.mainWindow()
if not main_window:
@@ -119,9 +183,6 @@ def remove_menu(menu):
main_window.menuBar().removeAction(menu.menuAction())
# ---------------------------------------------------------
# Toolbar-Handling
# ---------------------------------------------------------

View File

@@ -29,8 +29,9 @@ QMenu: Type[Any]
QToolBar: Type[Any]
QActionGroup: Type[Any]
QTabWidget: type
QToolButton: Type[Any]
QSizePolicy: Type[Any]
Qt: Type[Any]
YES: Optional[Any] = None
NO: Optional[Any] = None
@@ -65,6 +66,9 @@ try:
QActionGroup as _QActionGroup,# type: ignore
QDockWidget as _QDockWidget,# type: ignore
QTabWidget as _QTabWidget,# type: ignore
QToolButton as _QToolButton,#type:ignore
QSizePolicy as _QSizePolicy,#type:ignore
)
@@ -73,6 +77,7 @@ try:
QEventLoop as _QEventLoop,# type: ignore
QUrl as _QUrl,# type: ignore
QCoreApplication as _QCoreApplication,# type: ignore
Qt as _Qt#type:ignore
)
from qgis.PyQt.QtNetwork import ( # type: ignore
QNetworkRequest as _QNetworkRequest,# type: ignore
@@ -86,6 +91,7 @@ try:
QNetworkRequest = _QNetworkRequest
QNetworkReply = _QNetworkReply
QCoreApplication = _QCoreApplication
Qt=_Qt
QDockWidget = _QDockWidget
QWidget = _QWidget
QGridLayout = _QGridLayout
@@ -99,13 +105,37 @@ try:
QToolBar = _QToolBar
QActionGroup = _QActionGroup
QTabWidget = _QTabWidget
QToolButton=_QToolButton
QSizePolicy=_QSizePolicy
YES = QMessageBox.StandardButton.Yes
NO = QMessageBox.StandardButton.No
CANCEL = QMessageBox.StandardButton.Cancel
ICON_QUESTION = QMessageBox.Icon.Question
# ---------------------------------------------------------
# Qt6 Enum-Aliase (vereinheitlicht)
# ---------------------------------------------------------
ToolButtonTextBesideIcon = Qt.ToolButtonStyle.ToolButtonTextBesideIcon
ArrowDown = Qt.ArrowType.DownArrow
ArrowRight = Qt.ArrowType.RightArrow
# QSizePolicy Enum-Aliase (Qt6)
SizePolicyPreferred = QSizePolicy.Policy.Preferred
SizePolicyMaximum = QSizePolicy.Policy.Maximum
# ---------------------------------------------------------
# QDockWidget Feature-Aliase (Qt6)
# ---------------------------------------------------------
DockWidgetMovable = QDockWidget.DockWidgetFeature.DockWidgetMovable
DockWidgetFloatable = QDockWidget.DockWidgetFeature.DockWidgetFloatable
DockWidgetClosable = QDockWidget.DockWidgetFeature.DockWidgetClosable
# ---------------------------------------------------------
# Dock-Area-Aliase (Qt6)
# ---------------------------------------------------------
DockAreaLeft = Qt.DockWidgetArea.LeftDockWidgetArea
DockAreaRight = Qt.DockWidgetArea.RightDockWidgetArea
@@ -134,12 +164,14 @@ except Exception:
QActionGroup as _QActionGroup,
QDockWidget as _QDockWidget,
QTabWidget as _QTabWidget,
QToolButton as _QToolButton,
QSizePolicy as _QSizePolicy,
)
from PyQt5.QtCore import (
QEventLoop as _QEventLoop,
QUrl as _QUrl,
QCoreApplication as _QCoreApplication,
Qt as _Qt,
)
from PyQt5.QtNetwork import (
QNetworkRequest as _QNetworkRequest,
@@ -153,6 +185,7 @@ except Exception:
QNetworkRequest = _QNetworkRequest
QNetworkReply = _QNetworkReply
QCoreApplication = _QCoreApplication
Qt=_Qt
QDockWidget = _QDockWidget
@@ -168,8 +201,8 @@ except Exception:
QToolBar = _QToolBar
QActionGroup = _QActionGroup
QTabWidget = _QTabWidget
QToolButton=_QToolButton
QSizePolicy=_QSizePolicy
YES = QMessageBox.Yes
NO = QMessageBox.No
@@ -177,6 +210,30 @@ except Exception:
ICON_QUESTION = QMessageBox.Question
QT_VERSION = 5
# ---------------------------------------------------------
# Qt5 Enum-Aliase (vereinheitlicht)
# ---------------------------------------------------------
ToolButtonTextBesideIcon = Qt.ToolButtonTextBesideIcon
ArrowDown = Qt.DownArrow
ArrowRight = Qt.RightArrow
# QSizePolicy Enum-Aliase (Qt5)
SizePolicyPreferred = QSizePolicy.Preferred
SizePolicyMaximum = QSizePolicy.Maximum
# ---------------------------------------------------------
# QDockWidget Feature-Aliase (Qt5)
# ---------------------------------------------------------
DockWidgetMovable = QDockWidget.DockWidgetMovable
DockWidgetFloatable = QDockWidget.DockWidgetFloatable
DockWidgetClosable = QDockWidget.DockWidgetClosable
# ---------------------------------------------------------
# Dock-Area-Aliase (Qt5)
# ---------------------------------------------------------
DockAreaLeft = Qt.LeftDockWidgetArea
DockAreaRight = Qt.RightDockWidgetArea
def exec_dialog(dialog: Any) -> Any:
return dialog.exec_()
@@ -257,15 +314,26 @@ except Exception:
pass
class _MockLayout:
def addWidget(self, *args, **kwargs):
pass
def __init__(self, *args, **kwargs):
self._widgets = []
def addLayout(self, *args, **kwargs):
def addWidget(self, widget):
self._widgets.append(widget)
def addLayout(self, layout):
pass
def addStretch(self, *args, **kwargs):
pass
def setSpacing(self, *args, **kwargs):
pass
def setContentsMargins(self, *args, **kwargs):
pass
class _MockLabel:
def __init__(self, text: str = ""):
self._text = text
@@ -296,7 +364,18 @@ except Exception:
pass
QCoreApplication = _MockQCoreApplication
class _MockQt:
# ToolButtonStyle
ToolButtonTextBesideIcon = 0
# ArrowType
ArrowDown = 1
ArrowRight = 2
Qt=_MockQt
ToolButtonTextBesideIcon = Qt.ToolButtonTextBesideIcon
ArrowDown = Qt.ArrowDown
ArrowRight = Qt.ArrowRight
class _MockQDockWidget(_MockWidget):
def __init__(self, *args, **kwargs):
@@ -380,6 +459,54 @@ except Exception:
QToolBar = _MockToolBar
QActionGroup = _MockActionGroup
class _MockToolButton(_MockWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._checked = False
self.toggled = lambda *a, **k: None
def setText(self, text: str) -> None:
pass
def setCheckable(self, value: bool) -> None:
pass
def setChecked(self, value: bool) -> None:
self._checked = value
def setToolButtonStyle(self, *args, **kwargs):
pass
def setArrowType(self, *args, **kwargs):
pass
def setStyleSheet(self, *args, **kwargs):
pass
QToolButton=_MockToolButton
class _MockQSizePolicy:
# horizontale Policies
Fixed = 0
Minimum = 1
Maximum = 2
Preferred = 3
Expanding = 4
MinimumExpanding = 5
Ignored = 6
# vertikale Policies (Qt nutzt dieselben Werte)
def __init__(self, horizontal=None, vertical=None):
self.horizontal = horizontal
self.vertical = vertical
QSizePolicy=_MockQSizePolicy
SizePolicyPreferred = QSizePolicy.Preferred
SizePolicyMaximum = QSizePolicy.Maximum
DockWidgetMovable = 1
DockWidgetFloatable = 2
DockWidgetClosable = 4
DockAreaLeft = 1
DockAreaRight = 2
def exec_dialog(dialog: Any) -> Any:
return YES

View File

@@ -1,5 +1,5 @@
"""
variable_wrapper.py QGIS-Variablen-Abstraktion
sn_basis/functions/variable_wrapper.py QGIS-Variablen-Abstraktion
"""
from typing import Any

View File

@@ -5,7 +5,7 @@ Verwendet sys_wrapper und gibt pruef_ergebnis an den Pruefmanager zurück.
from pathlib import Path
from sn_basis.functions import (
from sn_basis.functions.sys_wrapper import (
join_path,
file_exists,
)

3
pyrightconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extraPaths": ["."]
}

View File

@@ -6,6 +6,17 @@ Basis-Dockwidget für alle LNO-Module.
from sn_basis.functions.qt_wrapper import QDockWidget, QTabWidget
from sn_basis.functions.message_wrapper import warning, error
from sn_basis.functions.qt_wrapper import (
QDockWidget,
QTabWidget,
Qt,
DockWidgetMovable,
DockWidgetFloatable,
DockWidgetClosable,
DockAreaLeft,
DockAreaRight,
)
class BaseDockWidget(QDockWidget):
@@ -23,6 +34,19 @@ class BaseDockWidget(QDockWidget):
def __init__(self, parent=None, subtitle=""):
super().__init__(parent)
# -----------------------------------------------------
# Dock-Konfiguration (WICHTIG)
# -----------------------------------------------------
self.setFeatures(
DockWidgetMovable
| DockWidgetFloatable
| DockWidgetClosable
)
self.setAllowedAreas(
DockAreaLeft
| DockAreaRight
)
# -----------------------------------------------------
# Titel setzen

View File

@@ -5,7 +5,7 @@ Verwaltet das Anzeigen und Ersetzen von DockWidgets.
Stellt sicher, dass immer nur ein sn_basis-Dock gleichzeitig sichtbar ist.
"""
from typing import Any
from typing import Any, Optional
from sn_basis.functions import (
add_dock_widget,
@@ -14,6 +14,9 @@ from sn_basis.functions import (
warning,
error,
)
from sn_basis.functions.qt_wrapper import (
DockAreaRight,
)
class DockManager:
@@ -24,22 +27,34 @@ class DockManager:
dock_prefix = "sn_dock_"
@classmethod
def show(cls, dock_widget: Any, area=None) -> None:
def show(cls, dock_widget: Any, area: Optional[Any] = None) -> None:
"""
Zeigt ein DockWidget an und entfernt vorher alle anderen
sn_basis-Docks (erkennbar am Prefix 'sn_dock_').
"""
# -----------------------------------------------------
# Default-Dock-Area (wrapper-konform)
# -----------------------------------------------------
if area is None:
area = DockAreaRight
if dock_widget is None:
error("Dock konnte nicht angezeigt werden", "Dock-Widget ist None.")
return
try:
# -------------------------------------------------
# Sicherstellen, dass das Dock einen Namen hat
# -------------------------------------------------
if not dock_widget.objectName():
dock_widget.setObjectName(f"{cls.dock_prefix}{id(dock_widget)}")
dock_widget.setObjectName(
f"{cls.dock_prefix}{id(dock_widget)}"
)
# -------------------------------------------------
# Vorhandene Plugin-Docks entfernen
# -------------------------------------------------
try:
for widget in find_dock_widgets():
if (
@@ -54,7 +69,9 @@ class DockManager:
str(e),
)
# -------------------------------------------------
# Neues Dock anzeigen
# -------------------------------------------------
try:
add_dock_widget(area, dock_widget)
dock_widget.show()
@@ -66,4 +83,3 @@ class DockManager:
except Exception as e:
error("DockManager-Fehler", str(e))

View File

@@ -47,7 +47,8 @@ class Navigation:
test_action = QAction("TEST ACTION", main_window)
self.menu.addAction(test_action)
self.toolbar.addAction(test_action)
self.plugin_group = QActionGroup(main_window)
self.plugin_group.setExclusive(True)
# -----------------------------------------------------

View File

@@ -8,7 +8,7 @@ from sn_basis.functions.qt_wrapper import QDockWidget, QTabWidget
from sn_basis.functions.message_wrapper import warning, error
class BaseDockWidget(QDockWidget):
class SettingsTab(QDockWidget):
"""
Basis-Dockwidget für alle LNO-Module.