Files
Projektwissen/docs/wissen/fallstricke.md
T

121 lines
15 KiB
Markdown
Raw Normal View History

# Fallstricke und Erkenntnisse
Gemeinsames Gedächtnis: Dinge, die man **nicht aus dem Code ablesen kann** und die schon einmal Zeit gekostet haben.
Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik. Veraltetes löschen, nicht auskommentieren.
## Umgebung und Werkzeuge
- **`qt_wrapper` meldet unter QGIS 3.44 `QT_VERSION == 6`, obwohl Qt5 läuft.** Der „PyQt6“-Zweig importiert
`qgis.PyQt`, das unter QGIS 3 PyQt5 ist; die Qt6-Schreibweise der Enums (`Qt.ItemFlag.ItemIsUserCheckable`)
funktioniert mit PyQt 5.15 trotzdem. `QT_VERSION` also nicht als Qt-Hauptversion verstehen, nur als
„echtes Qt (≠ 0)“. (02.10.2026, QGIS 3.44.4 geprüft, Basis #88)
- **Lücken im Mock-Modus:** `QgsGeometry` hat im Mock kein `fromRect`, `QgsLayoutPoint`/`QgsLayoutSize` haben
`x`/`width` als Attribute statt als Methoden; `QgsRectangle` hatte bis Basis #88 gar keinen Mock. Tests patchen
solche Stellen gezielt, statt die Produktionsfunktion umzubauen. (02.10.2026)
- **QGIS 3.40 ist seit Januar 2026 End of Life.** Aktueller LTR ist 3.44, Stable 4.2 (wird 25.09.2026 LTR). `workspace.toml` und alle `metadata.template` stehen noch auf `3.40` – offene Produktentscheidung, siehe `docs/konzept/07_qgis_tests_plan.md` Abschnitt 2. (04.09.2026, QGIS-Release-Fahrplan)
- **Zwei Qt-Welten.** QGIS 3.40/3.44 laufen mit Qt5/PyQt5, QGIS 4 mit Qt6/PyQt6. `qt_wrapper` lädt PyQt6 zuerst, dann PyQt5, sonst Mock. Enum-Zugriffe unterscheiden sich (`Qt.AlignmentFlag.AlignTop` statt `Qt.AlignTop`), deshalb nur Wrapper-Aliase verwenden. (09/2026, Code)
- **Mock-Modus ist kein QGIS.** `python scripts/run_tests.py` läuft in Sekunden, prüft aber nur Logik gegen Mock-Klassen. Geometrieoperationen, WFS, Layouts und Signale müssen in QGIS geprüft werden (`tests/test_qgis.bat` unter Windows). Manuelle QGIS-Tests im PR dokumentieren. (09/2026)
- **`openpyxl` ist eine harte Abhängigkeit** von `sn_basis` (Excel-Import) und unter Linux-QGIS oft nicht installiert (Issue Verfahrensgebiet #21). `requests` ist nur optionaler Fallback. Es gibt keine `requirements.txt`. (09/2026)
- **QGIS lädt Plugins in nicht garantierter Reihenfolge.** Fachplugins dürfen in `initGui()` nicht davon ausgehen, dass `sn_basis` fertig ist; `sn_basis` ruft `initGui()` aller `sn_*` nach. (Code `main.py`)
- **Exit-Crash unter QGIS 3.40** entstand durch nicht getrennte Projektsignale beim Unload. Regel: jeder Tab/Manager, der Signale verbindet, bietet `cleanup()`/`teardown_...()` und wird beim Schließen aufgerufen. (Basis, `assets/plan-qgis340ExitCrashFix.prompt.md`)
- **`docs/tests/testkatalog.html` (Dashboard „Prüfstand") lädt Mermaid von `cdnjs.cloudflare.com`.** Ohne
Internetverbindung beim Öffnen bleiben die Baumdiagramme leer, Text/Tabellen/Filter funktionieren trotzdem
(kein Server nötig, einfach lokal im Browser öffnen). Erzeugt zusammen mit `testkatalog.md` durch
`python scripts/testkatalog.py`; beide Dateien müssen zueinander passen (`--pruefen` prüft beide). (09/2026)
- **`.vscode/` gehört nicht ins Repository.** Im Projektwissen wurde er am 04.09.2026 entfernt; brauchbare Einstellungen stehen jetzt in `docs/leitfaden/04_werkzeuge_einrichten.md` Abschnitt 4.2 zum Kopieren in die Benutzereinstellungen. In `Plugin_SN_Basis`, `Plugin_SN_Plan41`, `Plugin_SN_Verfahrensgebiet` und `Plugin_SN_Widmung` liegt er noch und enthält Pfade eines einzelnen Arbeitsplatzes (OSGeo-Installationspfad, persönliches Benutzerprofil) – nicht als Vorgabe verstehen. (09/2026)
## QGIS-Tests (geplant, siehe `docs/konzept/07_qgis_tests_plan.md`)
- **Spike/Abnahme ohne QGIS-Oberfläche:** Skript mit `QT_QPA_PLATFORM=offscreen` über `python-qgis.bat` starten,
das Plugin-Repo per Junction (`mklink /J <tmp>\sn_basis <Plugin_SN_Basis>`) unter dem Paketnamen in `sys.path`
legen. Für Widgets und Dialoge `QgsApplication([], True)` (mit GUI) – ohne GUI stürzt z. B.
`QgsSymbolLayerUtils.symbolPreviewIcon` ab. Modale Dialoge: `exec_dialog` patchen und im Ersatz die Widgets
prüfen. Layout-Ergebnis mit `QgsLayoutExporter.renderRegionToImage` als PNG ansehen; ohne installierte Schriften
erscheinen Texte als Kästchen. `exportToImage` liefert bei Erfolg `0`. (02.10.2026, Basis #88)
- **`QT_QPA_PLATFORM=offscreen` muss gesetzt sein, bevor die `QgsApplication` erzeugt wird.** Danach wirkt es nicht mehr. (04.09.2026, Recherche)
- **`qgis.utils.iface` ist nach `start_app()` `None`.** Entweder `pytest-qgis` verwenden (patcht automatisch) oder selbst `qgis.utils.iface = get_iface()` setzen – und keine Module zu früh importieren. (04.09.2026)
- **`QgsProject.instance()` ist global.** Ohne Aufräumen zwischen den Tests beeinflussen sie sich gegenseitig. (04.09.2026)
- **Abstürze beim Beenden** sind ein bekanntes QGIS-Thema, wenn Layer- oder Canvas-Objekte noch referenziert werden. Gegenmittel: `pytest-qgis` oder `--qgis_disable_exit`. (04.09.2026)
- **`QgisTestCase.render_layout_check()` ist für Plugins nicht nutzbar**: es sucht Referenzbilder in einem einkompilierten Pfad des QGIS-Quellbaums. Nur `QgsRenderChecker` bietet `setControlImagePath()`. Referenzbilder immer im CI-Container erzeugen, nie unter Windows (Font- und Antialiasing-Unterschiede). (04.09.2026)
- **Modale Dialoge blockieren den Test.** Reihenfolge der Gegenmittel: modalen Aufruf per `monkeypatch` ersetzen, `open()` statt `exec()`, erst zuletzt `QTimer.singleShot`. (04.09.2026)
- **Unter Windows nur `python-qgis.bat`/`python-qgis-ltr.bat` verwenden** – ein normales `python -m pytest` findet die QGIS-Bindings nicht. (04.09.2026)
- **Gitea ordnet Commits über die E-Mail-Adresse zu.** Stimmt `user.email` nicht mit einer im Gitea-Konto hinterlegten Adresse überein, erscheinen die Commits ohne Zuordnung zum Benutzer. Für dieses Projekt gilt die dienstliche Adresse (`@lfulg.sachsen.de`). Wer den Rechner auch privat nutzt, setzt sie je Repository mit `git config --local` statt global. Nachträglich nur durch Umschreiben der Historie korrigierbar. (04.09.2026)
## Release und Versionen
- **Der Release-Workflow baut den Kopf des Kanal-Branches**, nicht den getaggten Commit (`git checkout "$CHANNEL"` in `Repository:hidden/workflows`). Tag nur setzen, wenn der Kanal-Branch auf dem gewünschten Stand ist. (09/2026)
- **`metadata.txt` nie von Hand pflegen.** Sie wird beim Release aus `.gitea/templates/metadata.template` erzeugt. Die im Repo liegende Datei ist ein Abbild des letzten Builds und kann irreführende Versionen/URLs zeigen (Issue Verfahrensgebiet #26). (09/2026)
- **Stable-Kanal ist leer**, ein Branch dafür existiert nicht; ein Tag ohne Suffix würde derzeit fehlschlagen. Der Branch muss **exakt `stable`** heißen, weil der Release-Workflow in `Repository:hidden/workflows` `git checkout "$CHANNEL"` ausführt und `$CHANNEL` aus dem Tag-Suffix stammt. Anlegen: `docs/konzept/08_repo_einrichten.md` (ADR 0007). (04.09.2026, Workflow gelesen)
- **Repo-Name ≠ Paketname.** `Plugin_SN_Basis` wird als `sn_basis` installiert (Gitea-Variable `ZIP_FOLDER`). Importe im Code verwenden immer den Paketnamen.
- **Bekannte Metadata-Fehler:** Plan41-Template verweist auf `Plugin_Test_Action` als Repository; FLT-Template auf `Plugin_SN_Neu`; Widmung trägt Version `4711-unstable`. (Gesamtüberblick 09/2026)
- **Workspace-Dateien in Release-ZIPs.** Der rsync-Schritt schließt nur `.git`, `.gitea`, `.gitignore`, `__pycache__` aus. `AGENTS.md`, `CLAUDE.md`, `.agents/`, `.claude/`, `docs/`, `tests/` werden mit ausgeliefert, bis die Excludes ergänzt sind (offener Punkt).
## Daten und Dienste
- **Punktlinien aus Mapbox-Styles sind in QGIS unsichtbar.** basemap.de (wie viele MapLibre-Stile) zeichnet Punktlinien
als `line-dasharray` mit Strichlänge 0 und `line-cap: round`. QGIS übernimmt das beim Import 1:1, Qt zeichnet
Striche der Länge 0 aber gar nicht – auch nicht als Punkt, weder in der Karte noch in der Legende. Betroffen
sind bei basemap.de 12 Styles (Baumreihe, Hecke, Durchlass, Seilbahnen …). Abhilfe: Strichlänge 0 durch 0,01
ersetzen (`sn_basis.modules.basemap_stilkorrektur`). Beim Prüfen solcher Effekte die Beschriftung abschalten –
sonst zählen Labels als „sichtbare Pixel“. (02.10.2026, QGIS 3.44 / PyQt 5.15, Basis #90; QGIS 4 ungeprüft)
- **basemap.de-Vektorkacheln (Basis #88):** QGIS zeigt für Vektorkachel-Layer keine Legende. Objekte im Gebiet
liefert `QgsVectorTileLayer.selectByGeometry()` (lädt die Kacheln selbst; `getRawTile` ist in Python gesperrt).
Kacheln enden bei Zoom 15. Der Renderer prüft Styles mit `scaleToZoomLevel(maßstab, False)` und setzt
`@vector_tile_zoom = scaleToZoom(maßstab)` – diese Variable fehlt in der Legende, zoomabhängige Strichmuster
sind dort sonst unsichtbar. Beim Import der Style-JSON werden Beschriftungen zu Labeling (Hausnummern =
Labeling-Style `Hauskoordinate`), `Nummer_*`-Schilder zu Renderer-Symbolen, `fill-extrusion` (3D) entfällt.
Der Legendenkatalog `sn_basis/data/basemap_legende.json` gehört zur Style-Version `bm_web_col` v2; nach einer
neuen Version `python modules/basemap_katalog.py` laufen lassen und Meldungen prüfen. Eine QML-Stilvorlage des
Layers ist rund 3,7 MB groß. (02.10.2026)
- **WFS-Beschränkungen der sächsischen ALKIS-Dienste:** POST/GML-Filter werden blockiert, URL-Länge ist begrenzt, serverseitige Feature-Limits. Deshalb arbeiten Flurstücks- und Grenzpunktabrufe mit BBOX, gemarkungsweisen Batches und Nutzerdialogen. Nicht „vereinfachen“, ohne das gegen den Dienst zu testen. Vollständig mit erprobten Anfragen: `docs/wissen/wfs_dienste.md`, Objektarten/Attribute: `docs/wissen/alkis_objektarten.md`. (Verfahrensgebiet, Code)
- **Zwei Linklisten.** Zentrale `Linkliste/linkliste.csv` (Spalten `id;gruppe;ebene;inhalt;url;provider;stil`) und die vom Plugin genutzte `Plugin_SN_Plan41/assets/Linkliste.xlsx` (`ident, gruppe, kartenebene, inhalt, link, provider, stildatei`) sind nicht verbunden. Änderungen an Diensten in **beiden** nachziehen, bis die Zusammenführung entschieden ist. (09/2026)
- **ArcGIS-REST-Layer nicht nach Nummer deuten.** Gleiche Layer-URLs werden in der Linkliste und im Alternativansatz unterschiedlich benannt; Bedeutung und Geometrie immer gegen die Dienstmetadaten prüfen. (Vergleich Alternativansatz 09/2026)
- **CRS:** Plan41 und VLN-API arbeiten in EPSG:25833. Der Alternativansatz erzeugt EPSG:25832. Für neue Layer 25833 verwenden, bis eine Entscheidung dokumentiert ist.
- **Shape-Import erwartet Feld `FBSchl`** mit `901/10110F` (Neuordnungsgebiet) oder `901/10120F` (Neuvermessungsgebiet). Exporte mit abweichendem Feldnamen (`FBSchl.`) wurden bis 26.8.3 abgewiesen; seit PR Verfahrensgebiet #45 (02.09.2026, 26.8.4-unstable) ist die Feldsuche tolerant. Neue Exportvarianten trotzdem gegen die Erkennung testen.
- **VLN-Upload ersetzt den Serverbestand der VKZ vollständig** (`PUT`). Vor dem Upload sicherstellen, dass alle Teillayer geladen sind.
- **API-Key liegt in `QSettings`** (Registry/Konfigurationsdatei), kein sicherer Schlüsselspeicher. Niemals in Logs oder Tests ausgeben.
## Code-Konventionen, die man kennen muss
- **Legendenelemente im Layout (QGIS 3.44):** `QgsLayoutItemLegend.adjustBoxSize()` wirkt erst, nachdem das
Element einmal gezeichnet wurde – beim Anlegen per Code ändert es nichts. Platzbedarf vorher über
`QgsLegendRenderer(legende.model(), einstellungen).minimumSize(QgsLayoutUtils.createRenderContextForLayout(layout, None))`
berechnen; für mehrere Spalten zusätzlich `setSplitLayer(True)`, sonst bleibt ein Layer in einer Spalte.
Eine Legende mit `setLegendFilterByMapEnabled(True)` blendet Layer aus, die nicht in der Karte liegen, und ihre
Höhe steht erst nach dem Rendern fest. (02.10.2026, Spike Basis #88)
Auch `QgsLegendRenderer.minimumSize` liefert vor dem ersten Zeichnen die **ungefilterte** Höhe (Beispiel: 344 statt
177 mm). Verlässlich vermessen: Legende einmal mit geringer Auflösung zeichnen
(`QgsLayoutExporter(layout).renderRegionToImage(legende.sceneBoundingRect(), QSize(), 25)`), danach wirken
Filter und `adjustBoxSize`. (02.10.2026, Basis #93)
Mehrspaltige Legenden: Mit `setEqualColumnWidth(True)` bestimmt der längste Text die Breite **aller** Spalten.
Schmaler wird es mit ungleichen Spalten und `setAutoWrapLinesAfter(mm)`; der Legendentitel bricht dabei **nicht**
automatisch um und bestimmt die Mindestbreite – Zeilenumbruch (`\n`) im Titel setzen. (02.10.2026, Basis #96)
`setMinimumSize` an Layoutelementen wird **nicht im Projekt gespeichert**: Nach dem Neuladen schrumpft eine
Legende mit „an Inhalt anpassen“ bei der nächsten Größenberechnung wieder (gemessen 180 → 43,7 mm). Mindestgröße
am Layout vermerken und beim Laden (`readProject`) neu setzen. (02.10.2026, Basis #96)
- **Projektvariablen-Schlüssel ohne `sn_` übergeben.** `set_variable("sn_abdeckungslayer", ...)` erzeugt `sn_sn_abdeckungslayer` (Altlast in Druckmodulen; funktioniert nur, weil Lesen und Schreiben denselben Fehler machen).
- **Zwei Schreibweisen für den Verfahrensgebiet-Layer:** `verfahrensgebiet_layer` (Manager, Fachplugins) und `verfahrensgebietslayer` (`print_logic.py`). Bis zur Bereinigung nicht auf Gleichheit vertrauen.
- **Layer werden über Anzeigenamen gefunden** (`BROR_LAYER_NAME` usw.). Umbenennungen in der UI brechen Abläufe.
- **`print()` ist im Code verbreitet**, aber kein Logging. Neue Ausgaben über `message_wrapper` (Nutzer) oder gar nicht; Debug-`print` vor dem PR entfernen.
- **Testrunner pro Repo (`tests/run_tests.py`) und Workspace-Runner unterscheiden sich.** Für PR-Nachweise zählt `scripts/run_tests.py --baseline`.
- **Tests der Verfahrensgebiet-Suite sind teils veraltet** (erwarten Einzeldatei-/Einzellayer-Schnittstellen vor der Mehrfachauswahl). Vor dem Fixen prüfen, ob der Test oder der Code falsch ist; siehe Baseline.
- **Bezeichner (Layernamen, Dateinamen) nie mit eigenem Regex bereinigen.** `sanitize_bezeichner` aus
`sn_basis.functions.text_wrapper` verwenden – Umlaute, `ß`, `_` und `-` wurden in mehreren KI-generierten
Regexen vergessen (Plugin_SN_Basis #42).
- **Deutscher Text ohne Umlaute/ß ist ein eigenständiges Muster, kein Einzelfall.** Bei der Aufarbeitung von
#42 fanden sich über alle Fachplugins verteilt MessageBox-Texte, Labels und Docstrings, die wortwörtlich als
ASCII generiert wurden (`gueltig` statt `gültig`, `ueber` statt `über`, `Massstab`/`Groesse` als Anzeigetext
statt `Maßstab`/`Größe`) – typisch für KI-generierten Text, der Sonderzeichen vermeidet. Neuen deutschen Text
(Meldungen, Labels, Kommentare, Docstrings) immer mit echten Umlauten/ß schreiben, nicht mit `ae`/`oe`/`ue`/`ss`
behelfen. Vor dem Commit `python scripts/pruefe_umlaute.py [<repo>]` laufen lassen – findet genau dieses
Muster (String-Inhalte und Kommentare mit Leerzeichen, die einen bekannten ASCII-Wortstamm enthalten;
technische Bezeichner ohne Leerzeichen werden nicht angetastet). Die Wortstammliste im Skript ist bewusst
konservativ (keine Fehlalarme wichtiger als Vollständigkeit) – Ersatz fürs Lesen des eigenen Textes ist sie
nicht.
## Zusammenarbeit
- **Tool-Gedächtnis ist lokal.** Die Claude-Auto-Memory sieht niemand sonst. Geteiltes Wissen nur in diesem Ordner (siehe Issue Basis #36 „shared-memory-architektur“, Kai).
- **Issues sind teils Diskussionen**, nicht Aufträge (Label `Diskussion`). Vor der Umsetzung Konsens im Issue abwarten oder im PR als Annahme kennzeichnen.