From 1d082ccde75b8b7d4c7f1f069a28d846418a47d4 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Wed, 16 Sep 2026 11:55:48 +0200 Subject: [PATCH] feat: Eingang/Ausgang-Symbole als eigene CSV-Zeile + in TrackIds Die Materialfluss-Ein-/Ausgangssymbole (Bloecke Eingang_/Ausgang_, eingefuegt ueber ils-insert-station) erscheinen jetzt NUR im CSV-Export als eigene TeileArt-Zeile ("Eingang"/"Ausgang", Merkmale wie BTMT). Wie Separator/AS/ES nehmen sie als freie Umlauf-Punkte (KREISEL_UMLAUF_TEILEARTEN) an der TrackIds-Spalte teil und tauchen so auch auf Kreiseln auf, auf deren Bahn sie liegen. Der Sivas-Export schliesst sie bewusst aus. - cfg/export.cfg: pattern_eingang/pattern_ausgang - Lisp/export.lsp: csv:collect-export-blocks sammelt beide Muster - Lisp/ssg_id.lsp: ssg-id-collect-blocks erfasst sie (ssg-id-check-all fuellt das nachgetragene ID-ATTDEF, dokumentiert in data/ils/allocations.json) - lib/export_csv.py: Klassifikations-Zweige Eingang/Ausgang - lib/export_neighbors.py: KREISEL_UMLAUF_TEILEARTEN um Eingang/Ausgang erweitert - lib/export_sivas.py: Eingang/Ausgang uebersprungen (kein Sivas-Eintrag) - Tests: test_export_eingang_ausgang.py + Kreisel-Umlauf-Test Co-Authored-By: Claude Opus 4.8 (1M context) --- Lisp/export.lsp | 9 +++ Lisp/ssg_id.lsp | 6 ++ cfg/export.cfg | 13 ++++ data/ils/allocations.json | 14 +++++ doc/csv-export.md | 30 ++++++++-- lib/export_csv.py | 40 +++++++++++++ lib/export_neighbors.py | 7 +++ lib/export_sivas.py | 10 ++++ tests/test_export_eingang_ausgang.py | 90 ++++++++++++++++++++++++++++ tests/test_export_kreisel_umlauf.py | 33 ++++++++++ 10 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 data/ils/allocations.json create mode 100644 tests/test_export_eingang_ausgang.py diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 6ecbfc1..fa2e7d6 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -1088,6 +1088,13 @@ ;; ils-insert-station in SSG_LIB_Commands.lsp). Anders als Separator/ ;; Scanner werden sie von KEINEM anderen Element mitgezaehlt - export_csv.py ;; UND export_sivas.py fuehren sie beide als eigene TeileArt-Zeilen. +;; Eingang*/Ausgang* = Materialfluss-Ein-/Ausgangssymbole (siehe +;; pattern_eingang/pattern_ausgang in cfg/export.cfg, ils-insert-station in +;; SSG_LIB_Commands.lsp). Wie BTMT eigenstaendig, aber NUR fuer den +;; CSV-Export: export_csv.py gibt ihnen eigene TeileArt-Zeilen und traegt +;; sie - wie Separator/AS/ES - in die "TrackIds"-Spalte ein (auch auf +;; Kreiseln, siehe KREISEL_UMLAUF_TEILEARTEN in lib/export_neighbors.py); +;; export_sivas.py schliesst sie bewusst aus. ;; GR_* = Geruest-Baugruppen (siehe pattern_geruest in cfg/export.cfg, ;; GeruestInsert.lsp). Wie BTMT eigenstaendig und von keinem anderen ;; Element mitgezaehlt - export_csv.py UND export_sivas.py fuehren sie @@ -1111,6 +1118,8 @@ (wcmatch bname (export:pattern "pattern_scanner" "Scanner*")) (wcmatch bname (export:pattern "pattern_btmt_beladung" "BTMT-Beladung*")) (wcmatch bname (export:pattern "pattern_btmt_entladung" "SC_Entladung*")) + (wcmatch bname (export:pattern "pattern_eingang" "Eingang*")) + (wcmatch bname (export:pattern "pattern_ausgang" "Ausgang*")) (wcmatch bname (export:pattern "pattern_geruest" "GR_*")) ;; Rein numerische Namen = Omniflo SivasNr (Boegen/Weichen) ;; Rein LISP-intern (Python klassifiziert Omniflo stattdessen diff --git a/Lisp/ssg_id.lsp b/Lisp/ssg_id.lsp index 4a83c5e..65a3e76 100644 --- a/Lisp/ssg_id.lsp +++ b/Lisp/ssg_id.lsp @@ -38,6 +38,11 @@ ;; pattern_btmt_entladung in cfg/export.cfg) aus demselben Grund: sonst bleibt ;; ihre ID trotz ATTDEF leer und sie fehlen bei der zeichnungsweiten Duplikat- ;; pruefung/Maximum-Ermittlung (ssg-id-check-all). +;; Eingang*/Ausgang* (Materialfluss-Ein-/Ausgangssymbole, siehe +;; pattern_eingang/pattern_ausgang in cfg/export.cfg) ebenso: die DWGs haben +;; seit dem ID-Nachtrag (allocations.json) ein leeres ID-ATTDEF, das hier +;; gefuellt werden muss, damit sie eine stabile TeileId fuer die "TrackIds"- +;; Spalte (auch auf Kreiseln) bekommen. ;; GR_* (Geruest-Baugruppen, siehe pattern_geruest in cfg/export.cfg) ebenso - ;; das ID-ATTDEF steht seit *geruest-attrib-defs* zur Verfuegung. (defun ssg-id-collect-blocks ( / ss-all ss-out i ename ed bname) @@ -57,6 +62,7 @@ (wcmatch bname "Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*") (wcmatch bname "Separator_SP*,S-LP*,Scanner*") (wcmatch bname "BTMT-Beladung*,SC_Entladung*") + (wcmatch bname "Eingang*,Ausgang*") (wcmatch bname "GR_*") (wcmatch bname "#*") ) diff --git a/cfg/export.cfg b/cfg/export.cfg index f2bbb6d..ee7b345 100644 --- a/cfg/export.cfg +++ b/cfg/export.cfg @@ -37,6 +37,17 @@ # in den Summierungszaehlern "Anzahl aller BTBeladung"/"Anzahl aller # Abwurfstationen" der ILS-Automation-Zeile). # +# pattern_eingang/pattern_ausgang: Eingangs-/Ausgangssymbole des Material- +# flusses (siehe ils-insert-station in SSG_LIB_Commands.lsp, Bloecke +# Eingang_/Ausgang_). NUR fuer den CSV-Export: export_csv.py fuehrt +# sie als eigene TeileArt-Zeilen ("Eingang"/"Ausgang", Merkmale wie BTMT) UND +# als frei platzierte Umlauf-Punkte auf einem Kreisel (KREISEL_UMLAUF_TEILEARTEN +# in lib/export_neighbors.py), damit sie - wie Separator/AS/ES - in die +# "TrackIds"-Spalte gelangen. export_sivas.py schliesst sie bewusst aus (kein +# Sivas-Eintrag). Voraussetzung fuer eine stabile TeileId ist ein ID-ATTDEF in +# den DWGs (ssg-id-check-all fuellt es vor jedem Export, siehe +# ssg-id-collect-blocks in ssg_id.lsp). +# # pattern_geruest: Geruest-Baugruppen (GeruestInsert.lsp, GR__). # Eigenstaendig wie BTMT, kein ANZAHL_*-Attribut an einem Carrier - export_csv.py # UND export_sivas.py fuehren sie als eigene TeileArt-Zeilen. @@ -54,6 +65,8 @@ pattern_separator= Separator_SP*, S-LP* pattern_scanner= Scanner* pattern_btmt_beladung= BTMT-Beladung* pattern_btmt_entladung= SC_Entladung* +pattern_eingang= Eingang* +pattern_ausgang= Ausgang* pattern_geruest= GR_* pattern_ks_subblocks= KS_EIN, KS_AUS, K1, K2, K3, K4 diff --git a/data/ils/allocations.json b/data/ils/allocations.json new file mode 100644 index 0000000..b78ef86 --- /dev/null +++ b/data/ils/allocations.json @@ -0,0 +1,14 @@ +{ + "Eingang_2D": { + "ID": "" + }, + "Eingang_3D": { + "ID": "" + }, + "Ausgang_2D": { + "ID": "" + }, + "Ausgang_3D": { + "ID": "" + } +} diff --git a/doc/csv-export.md b/doc/csv-export.md index 35cb3c7..c3538f4 100644 --- a/doc/csv-export.md +++ b/doc/csv-export.md @@ -70,8 +70,8 @@ Bounding-Boxen braucht). `vla-update` auf, das allein reichte in diesem Fall nicht aus. 7. **`csv:collect-export-blocks`** — sammelt alle relevanten top-level INSERTs (Kreisel, Omniflo, VF/GF-Wrapper, Separator/Scanner, - BTMT-Stationen) ueber die Blockname-Muster aus `cfg/export.cfg` - `[blockpattern]`. + BTMT-Stationen, Eingang/Ausgang) ueber die Blockname-Muster aus + `cfg/export.cfg` `[blockpattern]`. 8. **`csv:block-to-json`** je Fund — siehe naechster Abschnitt. ### `csv:block-to-json` — Inhalt eines JSON-Eintrags @@ -277,7 +277,8 @@ Zu beachten: Eine grosse Schleife über alle rohen JSON-Blöcke ordnet jeden Block anhand der `cfg/export.cfg [blockpattern]`-Muster einer TeileArt zu (Kreisel, Eckrad, Omniflo Bogen/Weiche/Gerade, VF-Strecke, Gefaellestrecke, -Strecke-Modul, Separator, Scanner, BTMT Be-/Entladung) und baut daraus ein +Strecke-Modul, Separator, Scanner, BTMT Be-/Entladung, Eingang/Ausgang) und +baut daraus ein Item-dict mit `teileart`, `teileid`, `bezeichnung`, `planquadrat`, `merkmale` und den bbox-/KOS-Spalten (`bbox_columns`). VF_n/GF_n-Items bekommen dabei zusaetzlich `x`/`y` (Kettenanfang, fuer die Schleuselement- @@ -299,6 +300,24 @@ Default 210x150x14mm) und der bekannten Position/Rotation eine synthetische `_bbox`, damit der Separator trotzdem an der Nachbarschaftserkennung teilnimmt. +#### Eingang/Ausgang — nur im CSV-Export + +Die Materialfluss-Ein-/Ausgangssymbole (Bloecke `Eingang_`/`Ausgang_`, +eingefuegt ueber `ils-insert-station`, Befehle `ILS_EINGANG_INSERT`/ +`ILS_AUSGANG_INSERT`) werden **nur im CSV-Export** als eigene Zeile gefuehrt +(TeileArt `"Eingang"`/`"Ausgang"`, Merkmale wie BTMT: ARTINR/Hoehe/Drehung). +`export_sivas.py` ueberspringt sie bewusst — ein Fluss-Symbol hat keinen +eigenen Sivas-Artikel. + +Wie Separator/AS/ES nehmen sie als **freie Umlauf-Punkte** +(`KREISEL_UMLAUF_TEILEARTEN`) an der `"TrackIds"`-Spalte teil und tauchen so +auch auf einem Kreisel auf, auf dessen Bahn sie liegen. Voraussetzung fuer +eine ueber Exportlaeufe stabile TeileId ist ein `ID`-ATTDEF in den DWGs +(nachgetragen, dokumentiert in `data/ils/allocations.json`); `ssg-id-check-all` +fuellt es vor jedem Export, und `ssg-id-collect-blocks` (`Lisp/ssg_id.lsp`) +erfasst die Bloecke dafuer. Fehlt die ID (noch), greift wie bei BTMT eine +lauf-lokale Fallback-Nummer (`EIN-`/`AUS-`-Praefix). + ### 2. `compute_neighbor_ids` — die generische Bounding-Box-Nachbarschaft Datei `lib/export_neighbors.py`. Testet drei Gruppen gegeneinander (NICHT @@ -347,8 +366,9 @@ Weichen zu einer widerspruchsfreien Nachbarschaft zusammenfuehrt. Fuer jeden echten Kreisel werden alle Punkte gesammelt, die dort geometrisch liegen: - freie Punkte (`KREISEL_UMLAUF_TEILEARTEN` in `lib/export_neighbors.py`: - Separator, BTMT Beladung, BTMT Entladung — alles Stationen direkt auf der - Bahn/dem Track), deren Bounding-Box die Kreisel-Box ueberschneidet, + Separator, BTMT Beladung, BTMT Entladung, Eingang, Ausgang — alles + Stationen/Markierungen direkt auf der Bahn/dem Track), deren Bounding-Box + die Kreisel-Box ueberschneidet, - die Kreisel-Kreisel-Weichen aus Schritt 3 (in BEIDEN beteiligten Kreisel-Umlaeufen, da eine Weiche der gemeinsame Verzweigungspunkt ist — man kann von Kreisel A nach B und zurueck), diff --git a/lib/export_csv.py b/lib/export_csv.py index 41fe681..81cea7a 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -886,6 +886,8 @@ def process_blocks(blocks, lookup, dbg=None): scanner_count = 0 btmt_beladung_count = 0 btmt_entladung_count = 0 + eingang_count = 0 + ausgang_count = 0 geruest_count = 0 # Zaehlung fuer die Omniflo-Sum-Zeile (nur Bogen/Weiche/Gerade) @@ -1159,6 +1161,44 @@ def process_blocks(blocks, lookup, dbg=None): }) continue + # Eingangssymbol (Materialfluss-Eingang der Anlage) - eigene + # TeileArt-Zeile, analog zu BTMT. Wie BTMT von keinem Carrier-Attribut + # mitgezaehlt; anders als BTMT NUR im CSV-Export (export_sivas.py + # ueberspringt Eingang/Ausgang). Die TeileId kommt aus dem ID-Attribut + # (ssg-id-check-all fuellt es vor jedem Export, ID-ATTDEF seit dem + # allocations.json-Nachtrag) - dieselbe TeileId, die die + # "TrackIds"-Spalte an Kreisel/Strecke referenziert (KREISEL_UMLAUF_ + # TEILEARTEN in export_neighbors.py). Fallback-Nummer wie bei BTMT. + if matches_any(bname, BLOCKPATTERNS.get("pattern_eingang", [])): + elem_nr += 1 + eingang_count += 1 + items.append({ + "nr": elem_nr, + "teileart": "Eingang", + "teileid": block.get("attribs", {}).get("ID") or format_sensor_id("EIN", eingang_count), + "bezeichnung": f"Eingang :{eingang_count}", + "planquadrat": planquadrat_column(block, planquadrat_cfg), + "merkmale": build_btmt_merkmale(block), + **bbox_columns(block), + }) + continue + + # Ausgangssymbol (Materialfluss-Ausgang der Anlage) - analog zum + # Eingangssymbol oben. + if matches_any(bname, BLOCKPATTERNS.get("pattern_ausgang", [])): + elem_nr += 1 + ausgang_count += 1 + items.append({ + "nr": elem_nr, + "teileart": "Ausgang", + "teileid": block.get("attribs", {}).get("ID") or format_sensor_id("AUS", ausgang_count), + "bezeichnung": f"Ausgang :{ausgang_count}", + "planquadrat": planquadrat_column(block, planquadrat_cfg), + "merkmale": build_btmt_merkmale(block), + **bbox_columns(block), + }) + continue + # Geruest-Baugruppe (GR__) - eigene TeileArt-Zeile, # analog zu BTMT: kein ANZAHL_*-Attribut an einem Carrier zaehlt sie # mit. TeileId ist das echte ID-Attribut (ssg-id-check-all fuellt es diff --git a/lib/export_neighbors.py b/lib/export_neighbors.py index 7d0af42..ec5412f 100644 --- a/lib/export_neighbors.py +++ b/lib/export_neighbors.py @@ -84,8 +84,15 @@ SENSOR_TEILEARTEN = {"ILS 2.0 Separator", "ILS 2.0 Scanner"} # Sensoren - eigene Konstante statt Erweiterung von SENSOR_TEILEARTEN, damit # deren Sensor-spezifische Weiterverarbeitung (z.B. compute_scanner_nearest_ # separator) unberuehrt bleibt. +# Eingang/Ausgang (Materialfluss-Ein-/Ausgangssymbole, siehe pattern_eingang/ +# pattern_ausgang in cfg/export.cfg) werden hier wie BTMT als frei platzierte +# Umlauf-Punkte gefuehrt - so tauchen sie, wenn sie auf einer Kreiselbahn +# liegen, in dessen "TrackIds"-Spalte auf (Nutzeranforderung: wie +# Separator/AS/ES behandeln). Die TeileArt-Strings sind bewusst OHNE "ILS 2.0"- +# Praefix (siehe export_csv.py) - dieselben Strings muessen hier stehen. KREISEL_UMLAUF_TEILEARTEN = { "ILS 2.0 Separator", "ILS 2.0 BTMT Beladung", "ILS 2.0 BTMT Entladung", "ILS Weiche", + "Eingang", "Ausgang", } # Nur "ILS 2.0 Kreisel" (echte Kreisel-Bloecke mit AN8/Antrieb + SP8/Spann- diff --git a/lib/export_sivas.py b/lib/export_sivas.py index ca07c3d..1799c03 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -504,6 +504,16 @@ def process_blocks(blocks, lookup): counters["anzahl_abwurfstationen"] += 1 continue + # --- Eingangs-/Ausgangssymbol (Materialfluss-Ein-/Ausgang) --- + # Bewusst NUR im CSV-Export als eigene Zeile (siehe export_csv.py) - + # hier ohne Sivas-Zeile und ohne eigenen Zaehler ueberspringen. Ein + # Eingangs-/Ausgangssymbol ist eine reine Fluss-Markierung ohne Sivas- + # Artikel; ein eigener Sivas-Eintrag wuerde die Stueckliste verfaelschen + # (Nutzeranforderung: Eingang/Ausgang nur im CSV-Export). + if matches_any(bname, BLOCKPATTERNS.get("pattern_eingang", [])) or \ + matches_any(bname, BLOCKPATTERNS.get("pattern_ausgang", [])): + continue + # --- Geruest-Baugruppe --- # Anders als BTMT/Separator je Block eine EIGENE Zeile statt gruppiert: # ARTINR (Sivas-IDs) und Laenge/Farbe koennen von Block zu Block diff --git a/tests/test_export_eingang_ausgang.py b/tests/test_export_eingang_ausgang.py new file mode 100644 index 0000000..816daf3 --- /dev/null +++ b/tests/test_export_eingang_ausgang.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_export_eingang_ausgang.py - Eingangs-/Ausgangssymbole im Export. +Laeuft komplett ohne BricsCAD (reine Python-Logik). + +Kundenwunsch: die Materialfluss-Ein-/Ausgangssymbole (Bloecke +Eingang_/Ausgang_, eingefuegt ueber ils-insert-station) sollen +- NUR im CSV-Export - als eigene Zeile erscheinen, inhaltlich aehnlich den + BTMT Be-/Entladestationen, und +- wie Separator/AS/ES in die "TrackIds" eingebaut werden (auch auf Kreiseln, + siehe test_export_kreisel_umlauf.py). +Im Sivas-Export duerfen sie NICHT auftauchen. +""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) + +import export_csv # noqa: E402 +import export_sivas # noqa: E402 + + +def _block(block_name, teileid, artinr="12345", x=0.0, y=0.0): + return { + "block_name": block_name, + "x": x, "y": y, "z": 0.0, + "rotation": 0.0, + "attribs": {"ID": teileid, "ARTINR": artinr, "LAYER": "ILS_Flow", + "ZUORDNUNG": ""}, + "_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 200.0, "dy": 150.0}, + } + + +class TestCsvKlassifikation: + """export_csv.process_blocks gibt Eingang/Ausgang je eine eigene + TeileArt-Zeile ("Eingang"/"Ausgang") mit der Block-ID als TeileId.""" + + def test_eingang_und_ausgang_bekommen_eigene_zeile(self): + blocks = [ + _block("Eingang_2D", "0050", x=0.0), + _block("Ausgang_3D", "0051", x=1000.0), + ] + items = export_csv.process_blocks(blocks, lookup={}) + nach_art = {it["teileart"]: it for it in items} + + assert "Eingang" in nach_art, "Eingang-Zeile fehlt im CSV-Export" + assert "Ausgang" in nach_art, "Ausgang-Zeile fehlt im CSV-Export" + + ein = nach_art["Eingang"] + aus = nach_art["Ausgang"] + # TeileId kommt aus dem ID-Attribut (stabil ueber Exportlaeufe). + assert ein["teileid"] == "0050" + assert aus["teileid"] == "0051" + # Merkmale wie BTMT: ARTINR als Merkmal gefuehrt. + assert ein["merkmale"].get("ARTINR") == "12345" + + def test_teileid_fallback_ohne_id_attribut(self): + """Ohne (noch) gefuelltes ID-Attribut greift der Fallback (EIN-/AUS- + Praefix), damit die Zeile trotzdem eine TeileId traegt - wie bei BTMT.""" + blocks = [ + {"block_name": "Eingang_2D", "x": 0.0, "y": 0.0, "z": 0.0, + "rotation": 0.0, "attribs": {}, + "_bbox": {"cx": 0.0, "cy": 0.0, "cz": 0.0, "dx": 200.0, "dy": 150.0}}, + ] + items = export_csv.process_blocks(blocks, lookup={}) + ein = next(it for it in items if it["teileart"] == "Eingang") + assert ein["teileid"].startswith("EIN-") + + +class TestSivasSchliesstAus: + """export_sivas.process_blocks fuehrt Eingang/Ausgang bewusst NICHT als + eigene Zeile und zaehlt sie in keinem Summenzaehler mit.""" + + def test_keine_sivas_zeile_und_kein_zaehler(self): + blocks = [ + _block("Eingang_2D", "0050"), + _block("Ausgang_3D", "0051"), + ] + items, counters = export_sivas.process_blocks(blocks, lookup={}) + + arten = {it.get("teileart", "") for it in items} + assert "Eingang" not in arten + assert "Ausgang" not in arten + # Kein bestehender Zaehler darf durch die zwei Symbole hochgezaehlt + # worden sein (alle Zaehler bleiben 0, da nur Eingang/Ausgang im Satz). + assert all(v == 0 for v in counters.values()), ( + f"Eingang/Ausgang haben einen Sivas-Zaehler beeinflusst: " + f"{{k: v for k, v in counters.items() if v}}") diff --git a/tests/test_export_kreisel_umlauf.py b/tests/test_export_kreisel_umlauf.py index fa24352..f3e2b39 100644 --- a/tests/test_export_kreisel_umlauf.py +++ b/tests/test_export_kreisel_umlauf.py @@ -64,6 +64,17 @@ def _btmt(teileart, teileid, x, y): } +def _flusssymbol(teileart, teileid, x, y): + """Eingangs-/Ausgangssymbol ("Eingang"/"Ausgang") - frei auf der Bahn + platziert wie ein Separator/BTMT, siehe KREISEL_UMLAUF_TEILEARTEN. Die + TeileArt-Strings sind bewusst OHNE "ILS 2.0"-Praefix (siehe export_csv.py).""" + return { + "teileart": teileart, + "teileid": teileid, + "_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 200.0, "dy": 150.0}, + } + + def _zwei_beruehrende_kreisel(): """Zwei Kreisel, deren AN8-Enden sich exakt beruehren (End-an-End, wie im Mubea-Fall: 'Kreis/Kreis'-Beruehrung an der jeweiligen AN8-Kappe). @@ -422,3 +433,25 @@ class TestKreiselTrackidsSpalte: write_kreisel_separatorliste_merkmale( items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={}) assert k["trackids"] == "0031, 0020, 0030" + + def test_eingang_und_ausgang_zaehlen_als_freie_punkte(self): + """Eingangs-/Ausgangssymbole ("Eingang"/"Ausgang") sind Fluss- + Markierungen AUF der Bahn wie ein Separator/BTMT - sie muessen (auf + Kundenwunsch) im Kreisel-Umlauf UND in der TrackIds-Spalte des + Kreisels auftauchen, genau wie Separator/AS/ES.""" + k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0) + k["merkmale"] = {} + sep = _separator("0020", 300.0, 300.0) + ein = _flusssymbol("Eingang", "0040", 900.0, 400.0) + aus = _flusssymbol("Ausgang", "0041", 1400.0, -50.0) + items = [k, sep, ein, aus] + + kreisel_umlauf = compute_kreisel_umlauf( + items, sep_kette={}, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM) + keys = [p[0] for p in kreisel_umlauf["0001"]] + assert set(keys) == {"0020", "0040", "0041"}, ( + f"Eingang/Ausgang fehlen im Kreisel-Umlauf: {keys}") + + write_kreisel_separatorliste_merkmale( + items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={}) + assert k["trackids"] == "0041, 0020, 0040"