diff --git a/doc/csv-export.md b/doc/csv-export.md index 0cfe90b..35cb3c7 100644 --- a/doc/csv-export.md +++ b/doc/csv-export.md @@ -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 diff --git a/lib/export_csv.py b/lib/export_csv.py index eb68361..1ee048c 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -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 -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 -"-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