docs: CSV-Export-Doku in doc/csv-export.md ausgelagert und mit Beispiel ergaenzt
Der lange Docstring in lib/export_csv.py wandert nach doc/csv-export.md (inkl. durchgerechnetem Beispiel Kreisel+VarioFoerderer), der Docstring verweist nur noch darauf. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
23d79ec354
commit
c171bc44d6
@@ -145,6 +145,133 @@ Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingb
|
||||
Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale
|
||||
```
|
||||
|
||||
- **Position** = Mittenkoordinate x, y, z; **Boundingbox** = Ausdehnung
|
||||
(Laenge, Breite, Hoehe) in x, y, z — beide aus `csv:get-bbox`
|
||||
(`vla-getboundingbox`), siehe Phase 1.
|
||||
- **Insertpoint** = KOS-String (24-Zeichen Base64, Position+Rotation als
|
||||
Quaternion, `csv:kos-encode`) des Block-Einfuegepunkts selbst.
|
||||
- **K1-K4** = Positions-Strings der im Block enthaltenen Koordinatensysteme
|
||||
K1..K4 (12-Zeichen Base64, nur Position, `csv:trans-encode`) — nur bei
|
||||
Omniflo Bogen/Weiche/Gerade und VF_n/GF_n (K1/K2 aus AS/ES) vorhanden,
|
||||
sonst leer.
|
||||
- **Fehler** = `"unverbunden"` (keine Partner, aber mindestens einer noetig),
|
||||
`"nur ein Partner"` (ein Partner, aber mindestens zwei noetig) oder leer.
|
||||
Kreisel/Eckrad/Omniflo brauchen mindestens einen Partner,
|
||||
Gefaellestrecke/Foerderer/Strecke-Modul mindestens zwei
|
||||
(`export_neighbors.MIN_PARTNER`).
|
||||
- **Elementnummer** wird NICHT aus der TeileId uebernommen, sondern am Ende
|
||||
neu von 1 an vergeben, nachdem alle Zeilen aufsteigend nach TeileId
|
||||
sortiert wurden (`teileid_sort_key` in `process_blocks`). Ohne geloeschte
|
||||
Elemente stimmen Elementnummer und TeileId dadurch ueberein; geloeschte
|
||||
Elemente hinterlassen Luecken nur in der TeileId-Folge. Zeilen ohne
|
||||
numerische TeileId (Omniflo-Sum-Zeile, SEP-/SCN-Fallback-IDs ohne
|
||||
ID-Attribut) behalten stabil ihre bisherige Reihenfolge und stehen danach.
|
||||
|
||||
### Beispiel: Kreisel mit 2 Separatoren + VarioFoerderer mit 2 Separatoren
|
||||
|
||||
Ausgangslage in der Zeichnung: ein kleiner Kreisel (`KREISEL_1`, ID `0010`)
|
||||
mit zwei frei aufgesetzten Separatoren auf seiner Bahn, daran andockend ein
|
||||
`VF_1` mit einer Kette AS -> Separator -> Separator -> ES (IDs `0021`-`0024`).
|
||||
|
||||
**Ausschnitt `export_raw.json`** (von `csv:collect-export-blocks` /
|
||||
`csv:block-to-json` geschrieben, hier gekuerzt auf die fuer dieses Beispiel
|
||||
relevanten Felder):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"block_name": "KREISEL_1",
|
||||
"layer": "ILS_Kreisel",
|
||||
"handle": "2A1",
|
||||
"x": 10000.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
||||
"attribs": {"ID": "0010", "DREHRICHTUNG": "UZS", "ABSTAND": "800"},
|
||||
"insertpoint": "AAB3QAAATkAAAAAA...",
|
||||
"bbox": {"min": [9200.0, 4200.0, 0.0], "max": [10800.0, 5800.0, 0.0]}
|
||||
},
|
||||
{
|
||||
"block_name": "Separator_SP_300mm",
|
||||
"layer": "ILS_Sensor",
|
||||
"handle": "2A5",
|
||||
"x": 10800.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
||||
"attribs": {"ID": "0011"},
|
||||
"insertpoint": "AAD4QAAATkAAAAAA..."
|
||||
},
|
||||
{
|
||||
"block_name": "Separator_SP_300mm",
|
||||
"layer": "ILS_Sensor",
|
||||
"handle": "2A9",
|
||||
"x": 10000.0, "y": 5800.0, "z": 0.0, "rotation": 90.0,
|
||||
"attribs": {"ID": "0012"},
|
||||
"insertpoint": "AAB3QAAAxEAAAAAA..."
|
||||
},
|
||||
{
|
||||
"block_name": "VF_1",
|
||||
"layer": "ILS_Foerderer",
|
||||
"handle": "2B0",
|
||||
"x": 10800.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
||||
"attribs": {"ID": "0021", "Bezeichnung": ""},
|
||||
"insertpoint": "AAD4QAAATkAAAAAA...",
|
||||
"k1": "AAD4QAAATkAAAAAA...",
|
||||
"k2": "AAAAQwAATkAAAAAA...",
|
||||
"bbox": {"min": [10800.0, 4850.0, 0.0], "max": [13000.0, 5150.0, 300.0]},
|
||||
"sepliste": [
|
||||
["AS", 1, 10800.0, 5000.0],
|
||||
["SEP", 2, 11600.0, 5000.0],
|
||||
["SEP", 3, 12300.0, 5000.0],
|
||||
["ES", 4, 13000.0, 5000.0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"block_name": "Separator_SP_300mm",
|
||||
"layer": "ILS_Sensor",
|
||||
"handle": "2B4",
|
||||
"x": 11600.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
||||
"attribs": {"ID": "0022"}
|
||||
},
|
||||
{
|
||||
"block_name": "Separator_SP_300mm",
|
||||
"layer": "ILS_Sensor",
|
||||
"handle": "2B8",
|
||||
"x": 12300.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
||||
"attribs": {"ID": "0023"}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Die beiden Separatoren `0022`/`0023` sind hier die ECHTEN, top-level
|
||||
exportierten Separator-Bloecke (identisch mit den `SEP`-Eintraegen der
|
||||
`sepliste` von `VF_1` — siehe "Die `sepliste`" oben: kein doppeltes Konzept,
|
||||
zwei Blickwinkel auf dieselben Symbole).
|
||||
|
||||
**Resultierende CSV-Zeilen** (Header + betroffene Zeilen, Spalten
|
||||
`Insertpoint`/`K3`/`K4`/`Merkmale` fuer die Lesbarkeit gekuerzt):
|
||||
|
||||
```text
|
||||
Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale
|
||||
1;"ILS 2.0 Kreisel";"0010";"Kreisel :1";"B/3";1;"10000.0, 5000.0, 0.0";"1600.0, 1600.0, 0.0";"...";"";"";"";"";"0011, 0012, 0021";"0011, 0012, 0021, 0022, 0023, 0024";"";"";{"Abstand":"800"}
|
||||
2;"ILS 2.0 Separator";"0011";"Separator :1";"B/3";1;"10800.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";"0010, 0012";"";"";"";{"Zuordnung":"0010"}
|
||||
3;"ILS 2.0 Separator";"0012";"Separator :2";"B/3";1;"10000.0, 5800.0, 0.0";"150.0, 210.0, 14.0";"...";"";"";"";"";"0011, 0021";"";"";"";{"Zuordnung":"0010"}
|
||||
4;"ILS 2.0 VarioFoerderer";"0021";"VarioFoerderer :1";"B/3";1;"11900.0, 5000.0, 0.0";"2200.0, 300.0, 300.0";"...";"...";"...";"";"";"0010, 0024";"0021, 0022, 0023, 0024";"";"";{}
|
||||
5;"ILS 2.0 Separator";"0022";"Separator :3";"C/4";1;"11600.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";"";"";"";"";{"Zuordnung":"0021"}
|
||||
6;"ILS 2.0 Separator";"0023";"Separator :4";"C/4";1;"12300.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";"";"";"";"";{"Zuordnung":"0021"}
|
||||
```
|
||||
|
||||
Zu beachten:
|
||||
|
||||
- Der Kreisel (`0010`) traegt in `TrackIds` den KOMPLETTEN Umlauf inkl. der
|
||||
VF-Kettenenden (`0021`=AS, `0024`=ES, aus `compute_kreisel_umlauf`), waehrend
|
||||
seine `Nachbarn`-Spalte nur die unmittelbaren Umlauf-Nachbarn zeigt.
|
||||
- `VF_1` (`0021`) bekommt seine eigene `TrackIds`-Spalte
|
||||
(`write_separatorliste_merkmale`) aus der `sepliste`: AS -> SEP -> SEP -> ES
|
||||
in Baureihenfolge, unabhaengig vom Kreisel-Umlauf.
|
||||
- Die AS/ES-Enden (`0021`=AS, `0024`=ES) sind hier synthetische Zeilen aus
|
||||
`build_strecke_schleus_items` (Zeilentyp "ILS Ausschleuselement"/
|
||||
"ILS Einschleuselement") — in diesem gekuerzten Beispiel nicht einzeln
|
||||
aufgefuehrt, aber ihre TeileIds tauchen in `TrackIds` beider Seiten auf.
|
||||
- Die Separatoren 3 und 5 (`0012`/`0022`) unterscheiden sich nur durch ihre
|
||||
`Zuordnung`: `0012` haengt am Kreisel, `0022`/`0023` an der VF-Kette — beide
|
||||
Faelle laufen ueber denselben `pattern_separator`-Blocktyp.
|
||||
|
||||
### 1. Blöcke klassifizieren
|
||||
|
||||
Eine grosse Schleife über alle rohen JSON-Blöcke ordnet jeden Block anhand
|
||||
|
||||
+8
-98
@@ -3,111 +3,21 @@
|
||||
"""
|
||||
export_csv.py - Erzeugt eine einfache Item-Liste als CSV.
|
||||
|
||||
Fuer Omniflo-Elemente (Bogen/Weiche/Gerade) wird am Ende zusaetzlich eine
|
||||
"Omniflo Sum"-Zeile mit Anzahl Boegen/Weichentypen und Gesamtlaenge AP110/AP60
|
||||
angehaengt (siehe build_omni_sum_merkmale).
|
||||
|
||||
Nach der Kollisions-/Nachbarschaftserkennung wird ausserdem an jedem
|
||||
Beruehrpunkt zweier Kreisel eine "ILS Weiche"-Zeile angehaengt (siehe
|
||||
build_kreisel_weiche_items / export_neighbors.compute_kreisel_touch_switches):
|
||||
Anzahl 1, Position = Beruehrkoordinaten, Nachbarn = die beiden Kreisel-IDs,
|
||||
K1-K4/Insertpoint leer, Boundingbox = feste Element-Box (element_box_mm,
|
||||
cfg/export.cfg [Boundingbox]),
|
||||
mit der Laengsseite senkrecht zur Achse des beruehrenden Kreisels; die TeileId
|
||||
zaehlt aus der hoechsten bereits vergebenen numerischen ID hoch.
|
||||
|
||||
Anschliessend (TeileId-Zaehlung laeuft ueber die Weichen hinweg weiter) wird
|
||||
an jedem Ende einer Gefaellestrecke/eines Foerderers, an dem ein Kreisel
|
||||
anschliesst, ein Aus- (Anfang) bzw. Einschleuselement (Ende) an der Kollisions-
|
||||
position Kreisel<->Strecke angehaengt (siehe build_strecke_schleus_items /
|
||||
export_neighbors.compute_strecke_kreisel_schleus) - Format wie die Weiche,
|
||||
Nachbarn = Kreisel-ID + Strecken-ID. Zugeordnet wird je Streckenende der
|
||||
naechstgelegene ihrer BBox-Kreisel-Nachbarn.
|
||||
|
||||
Warnungen = Spalte fuer Auffaelligkeiten. Aktuell: eine Strecke sollte genau
|
||||
zwei Kreisel (Anfang + Ende) beruehren - beruehrt ihre (in x/y gepruefte)
|
||||
Bounding-Box mehr, steht hier ein Hinweis und es wird zusaetzlich eine
|
||||
Konsolen-WARNUNG ausgegeben (mit exakten 3D-BBoxen sollte das nicht auftreten).
|
||||
Scanner-Zeilen bekommen dieselbe Spalte fuer strittige Distanz-Zuordnungen:
|
||||
liegt ein Scanner ausserhalb jeder Kreisel-/Streckengruppen-/Gefaellestrecke-
|
||||
Boundingbox und ist der Abstand zum naechsten und zweitnaechsten Carrier fast
|
||||
gleich gross, schreibt Lisp/count_sep_scan.lsp (cs-zuordnung-lauf, vor dem
|
||||
Export automatisch aufgerufen) einen Warnungstext mit den IDs beider
|
||||
Kandidaten in das "warnung"-Feld des Scanner-Blocks im Roh-JSON (siehe
|
||||
csv:block-to-json in export.lsp) - hier unveraendert uebernommen.
|
||||
|
||||
Aufruf:
|
||||
python export_csv.py <export_raw.json> <data_dir> <output.csv>
|
||||
|
||||
CSV-Format:
|
||||
CSV-Format (Header):
|
||||
Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;
|
||||
Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale
|
||||
|
||||
Position und Boundingbox stammen aus der von export.lsp (csv:get-bbox, per
|
||||
vla-getboundingbox) ermittelten Bounding-Box je Block:
|
||||
Position = Mittenkoordinate x, y, z
|
||||
Boundingbox = Ausdehnung (Laenge, Breite, Hoehe) in x, y, z
|
||||
Insertpoint = KOS-String (24-Zeichen Base64, Position+Rotation als Quaternion,
|
||||
siehe csv:kos-encode in export.lsp) des Block-Einfuegepunkts selbst.
|
||||
K1-K4 = Positions-Strings der im Block enthaltenen Koordinatensysteme K1..K4
|
||||
(12-Zeichen Base64, nur Position ohne Rotation, siehe csv:trans-encode;
|
||||
nur bei Omniflo Bogen/Weiche/Gerade vorhanden, sonst leer).
|
||||
Planquadrat wird aus der x/y-Koordinate des Blocks berechnet, siehe
|
||||
export_planquadrat.py ([Planquadrate] in cfg/export.cfg).
|
||||
Nachbarn = kommaseparierte TeileId-Liste ueberschneidender Elemente (Bounding-
|
||||
Box-Ueberschneidung in der x/y-Ebene, reines Python ohne externe Abhaengigkeit,
|
||||
siehe export_neighbors.py). Geprueft wird NICHT global ueber alle Elemente,
|
||||
sondern in drei getrennten Gruppen: 1) Kreisel/Eckrad gegen Kreisel/Eckrad,
|
||||
2) Kreisel/Eckrad gegen Gefaellestrecke/Foerderer/Strecke-Modul (diese drei
|
||||
nicht gegeneinander), 3) Omniflo-Elemente gegen Omniflo-Elemente. Fuer die
|
||||
Omniflo-Gruppe liefert eine shapely-STRtree-Broad-Phase zunaechst alle Paare
|
||||
mit ueberschneidenden Bounding-Boxen; anschliessend gelten sie nur dann als
|
||||
benachbart, wenn sich ihre Anschluss-Koordinatensysteme K1-K4 tatsaechlich
|
||||
beruehren (ein K-Punkt naeher als omniflo_ks_toleranz_mm, Default 10mm) -
|
||||
das trennt bloss ueberlappende BBoxen von echten Anschluessen (Geraden fuehren
|
||||
dafuer beim Export synthetisierte K1/K2, siehe csv:gerade-k-kos-strings).
|
||||
Toleranzen und (Fallback-)Rastergroesse konfigurierbar ueber [Nachbarschaft]
|
||||
in cfg/export.cfg.
|
||||
TrackIds = kommaseparierte TeileId-Liste der Separatoren/BTMT-Beladung/
|
||||
BTMT-Entladung (und bei einem Kreisel zusaetzlich der AS/ES-Streckenenden
|
||||
und Kreisel-Kreisel-Weichen) entlang einer VF_n/GF_n-Strecke bzw. rund um
|
||||
einen Kreisel, in Baureihenfolge (Strecke) bzw. Umlaufreihenfolge ab Winkel
|
||||
0 Grad (Kreisel, siehe export_neighbors.compute_kreisel_umlauf) - nur bei
|
||||
VF_n/GF_n/Kreisel vorhanden, sonst leer. Siehe write_separatorliste_
|
||||
merkmale/write_kreisel_separatorliste_merkmale.
|
||||
Fehler = "unverbunden" (keine Partner, aber mindestens einer noetig),
|
||||
"nur ein Partner" (ein Partner, aber mindestens zwei noetig) oder leer
|
||||
(Mindestanzahl erreicht bzw. TeileArt wird nicht geprueft). Kreisel/Eckrad/
|
||||
Omniflo-Elemente brauchen mindestens einen Partner, Gefaellestrecke/
|
||||
Foerderer/Strecke-Modul mindestens zwei (siehe export_neighbors.py
|
||||
MIN_PARTNER). Ein Kreisel wird dabei NICHT als eine, sondern als zwei
|
||||
BBoxen (links/rechts, geteilt entlang der Achse durch Antriebs-/
|
||||
Spannstation, siehe export_neighbors.kreisel_half_bboxes) gegen
|
||||
Gefaellestrecke/Foerderer getestet - deren "Nachbarn"-Spalte zeigt darum
|
||||
"<KreiselTeileId>-L" bzw. "-R" statt nur der Kreisel-TeileId.
|
||||
Separator/Scanner-Zeilen bekommen zusaetzlich ein berechnetes "Zuordnung"-
|
||||
Merkmal (Gefaellestrecke > Foerderer > Kreiselhaelfte, per BBox-Ueber-
|
||||
schneidung - siehe export_neighbors.compute_sensor_zuordnung), das die
|
||||
ATTRIB-Zuordnung (falls das Attribut ZUORDNUNG in der Zeichnung von Hand
|
||||
gesetzt wurde) ersetzt, sofern ein Treffer gefunden wird. Scanner-Zeilen
|
||||
bekommen zusaetzlich ein "Naechster Separator"-Merkmal (raeumlich naechst-
|
||||
gelegener Separator, euklidischer Abstand der BBox-Mittelpunkte - eigene,
|
||||
von der Zuordnung unabhaengige Suche, siehe export_neighbors.compute_
|
||||
scanner_nearest_separator).
|
||||
Position, Boundingbox, Planquadrat, Nachbarn und Fehler sind nur fuer
|
||||
EXPORTCSV vorhanden (nicht EXPORTSIVAS) - siehe csv:run-export. Insertpoint/
|
||||
K1-K4 stehen zwar in export_raw.json fuer jeden Export zur Verfuegung (siehe
|
||||
csv:block-to-json), werden aber ebenfalls nur von diesem Skript (EXPORTCSV)
|
||||
als CSV-Spalten ausgegeben.
|
||||
Beispielzeile (Separator, TeileId 0011 an Kreisel 0010):
|
||||
2;"ILS 2.0 Separator";"0011";"Separator :1";"B/3";1;
|
||||
"10800.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";
|
||||
"0010, 0012";"";"";"";{"Zuordnung":"0010"}
|
||||
|
||||
Reihenfolge/Elementnummer: die Zeilen werden am Ende aufsteigend nach
|
||||
TeileId sortiert (numerische IDs) und die Elementnummer (erste Spalte)
|
||||
danach neu von 1 an vergeben (siehe teileid_sort_key in process_blocks) -
|
||||
wurde kein Element geloescht, stimmen Elementnummer und TeileId dadurch
|
||||
ueberein, geloeschte Elemente hinterlassen Luecken in der TeileId-Folge.
|
||||
Zeilen ohne numerische TeileId (Omniflo-Sum-Zeile, SEP-/SCN-Fallback-IDs
|
||||
ohne ID-Attribut) behalten stabil ihre bisherige Reihenfolge und stehen
|
||||
danach.
|
||||
Fuer die vollstaendige Herleitung jeder Spalte (Nachbarn/TrackIds/sepliste/
|
||||
Fehler/Warnungen, inkl. eines durchgerechneten Beispiels Kreisel+VarioFoerderer
|
||||
von export_raw.json bis zur fertigen CSV-Zeile) siehe doc/csv-export.md.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
Reference in New Issue
Block a user