From 7ec2b88b7998f3884dbda941f9202e222cdd2cfe Mon Sep 17 00:00:00 2001 From: Samer Ayadi Date: Thu, 15 Oct 2026 16:16:22 +0200 Subject: [PATCH] scl_skeleton --eplan: E-Planung-Namen auch in den PinStore-Lagerlinien Sensor und Stopper jeder Lagerlinie (und des Pin-Separators) kommen aus der E-Planung wie bei den Separatorbloecken (scl_context._sep_namen); die Zaehlung der eingesetzten Namen beruecksichtigt die Linien. Dazu die ID-Datei cfg/mubea_tro_ids.ini und .gitignore fuer lokale Claude-Code- Einstellungen. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 5 +++++ cfg/mubea_tro_ids.ini | 30 ++++++++++++++++++++++++++ lib/scl_context.py | 46 ++++++++++++++++++++++++++++------------ lib/scl_skeleton.py | 15 +++++++++---- tests/test_e_planning.py | 29 +++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 cfg/mubea_tro_ids.ini diff --git a/.gitignore b/.gitignore index 0fb3124..5d1f8a4 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,8 @@ ipython_config.py /work /log /results + +# Claude Code: persoenliche Einstellungen und TRO-Kriterien-Waechter +.claude/settings.local.json +.claude/hooks/ +.claude/tro_criteria_baseline diff --git a/cfg/mubea_tro_ids.ini b/cfg/mubea_tro_ids.ini new file mode 100644 index 0000000..cd18ef7 --- /dev/null +++ b/cfg/mubea_tro_ids.ini @@ -0,0 +1,30 @@ +; ============================================================ +; TRO-IDs zu mubea.csv - erzeugt von lib/tro_flow.py am 2026-10-15 +; +; Je TRO: ID = x, y (mm) seines Anker-Separators im CSV-Koordinatensystem. +; Die IDs duerfen umbenannt werden (eindeutig, gueltiger SCL-Bezeichner); +; tro_flow findet jeden TRO ueber die Lage wieder. Neue TROs werden +; unten angehaengt, weggefallene bleiben stehen und werden gemeldet. +; Alles neu nummerieren: tro_flow --renumber (ueberschreibt diese Datei). +; Siehe doc/Konzept_Layout_Hardware_SCL.md, Abschnitt TRO-IDs. +; ============================================================ + +[tro] +TRO01 = 10310.0, -440.1 ; 1Sep | Separator 0042 auf Kreisel3 Bahn R | Sep 0042 +TRO02 = 10310.0, 159.9 ; 1Sep | Separator 0043 auf Kreisel3 Bahn R | Sep 0043 +TRO03 = 10310.0, -5013.1 ; 1Sep | Separator 0044 auf Kreisel3 Bahn R | Sep 0044 +TRO04 = 958.0, 12300.0 ; PinStore_Auto | Speicher (20 Schwerkraftlinien) | Sep 0066, 0069, 0072, 0075, 0078, 0081, 0084, 0087, 0090, 0093, 0096, 0099, 0102, 0105, 0108, 0111, 0114, 0117, 0120, 0123 +TRO05 = -400.0, -3554.9 ; 1Sep | Separator 0012 auf Kreisel2 Bahn L | Sep 0012 +TRO06 = 5355.1, -5878.0 ; 1Sep | Separator 0040 auf Kreisel1 Bahn R | Sep 0040 +TRO07 = -898.9, 1134.6 ; Vario | VF_3 | Sep 0062 +TRO08 = -703.0, 12296.5 ; 1Sep | VF_3 | Sep 0063 +TRO09 = -400.0, -562.0 ; 1Sep1Swi | Separator 0011 an Ausschleuselement 0061 (Strecke 0015, Anfang) | Sep 0011 +TRO10 = -1026.0, 10723.0 ; 1Sep | VF_3 | Sep 0037 +TRO11 = -1026.0, 5250.1 ; 1Sep | VF_3 | Sep 0038 +TRO12 = 3299.6, 13509.6 ; Vario | VF_1 | Sep 0054 +TRO13 = 8953.1, 13509.6 ; 1Sep | VF_1 | Sep 0055 +TRO14 = -400.0, 13920.0 ; 1Sep1Swi | Separator 0003 an Ausschleuselement 0053 (Strecke 0013, Anfang) | Sep 0003 +TRO15 = 9510.0, 14217.0 ; 1Sep | VF_1 | Sep 0045 +TRO16 = 3299.6, -4743.4 ; Vario | VF_2 | Sep 0058 +TRO17 = 8953.1, -4743.4 ; 1Sep | VF_2 | Sep 0059 +TRO18 = 400.0, -3978.1 ; 2Sep2Swi | Uebergabestelle Kreisel2: Ausschleuselement 0057 + ILS Weiche 0125 | Sep 0005, 0036 diff --git a/lib/scl_context.py b/lib/scl_context.py index debf2d2..eec37cb 100644 --- a/lib/scl_context.py +++ b/lib/scl_context.py @@ -432,17 +432,28 @@ def _jam(n: int, neighbour: str, jam_db: str, sensor: SclName | None = None, ) -def _store(tro: TroBase) -> SclStore: +def _store(tro: TroBase, eplan: EplanDocument | None = None) -> SclStore: """PinStore_Auto: eine Linie je Speicherbahn (Host) des Layouts. tro_flow legt die Separatoren als [steuernde ..., Linien ...] ab; die Linien sind die letzten len(hosts). Fehlen Hosts (fremde JSON), zaehlt - jeder Separator als Linie. + jeder Separator als Linie. Sensor und Stopper jeder Linie (und des + Pin-Separators, falls tro_flow einen gefunden hat) kommen aus der + E-Planung wie bei den Separatorbloecken. """ hosts = list(getattr(tro, "hosts", []) or []) seps = list(tro.separators) anzahl = len(hosts) if 0 < len(hosts) <= len(seps) else len(seps) steuer, linien = seps[:len(seps) - anzahl], seps[len(seps) - anzahl:] + pin_sensor, pin_stopper = _sep_namen(steuer[0], eplan) if steuer else ( + _offen("cInBG____"), _offen("MB____")) + zeilen = [] + for n, sep in enumerate(linien, start=1): + sensor, stopper = _sep_namen(sep, eplan) + zeilen.append(SclStoreLine(n=n, layout=sep, + jam_length=_offen('"cStorage____ExitJam"'), + sensor=sensor, jam_sensor=_offen("cInBG____"), + stopper=stopper)) return SclStore( encoder=_offen("#fbEncoder____"), dest=_offen("cDest____"), @@ -460,27 +471,34 @@ def _store(tro: TroBase) -> SclStore: 'arrudt_StorageRemovalHandshakeTROsLvl1[____]'), conv_db=_offen('"DB_ConveyorStations".stConveyorStations____'), pin_layout=", ".join(steuer) or "?", - pin_sensor=_offen("cInBG____"), - pin_stopper=_offen("MB____"), - lines=[ - SclStoreLine(n=n, layout=sep, jam_length=_offen('"cStorage____ExitJam"'), - sensor=_offen("cInBG____"), jam_sensor=_offen("cInBG____"), - stopper=_offen("MB____")) - for n, sep in enumerate(linien, start=1) - ], + pin_sensor=pin_sensor, + pin_stopper=pin_stopper, + lines=zeilen, ) -def _separator(i: int, layout: str, eplan: EplanDocument | None) -> SclSeparator: +def _sep_namen(layout: str, eplan: EplanDocument | None) -> tuple[SclName, SclName]: + """Sensor und Stopperventil eines Layout-Separators aus der E-Planung. + + Gilt fuer die Separatorbloecke (stInSeparator) und genauso fuer die + Linien eines PinStore_Auto - beide sind Separatoren im Layout. Ohne + Zuordnung bleiben beide offen. + """ rollen = eplan.for_layout(layout) if eplan is not None and layout != "?" else {} sensor = rollen.get("sensor") stopper = rollen.get("stopper") + return (_bekannt(_SENSOR.format(bmk=sensor.bmk)) if sensor else _offen("cInBG____"), + _bekannt(_VENTIL.format(bmk=stopper.bmk)) if stopper else _offen("MB____")) + + +def _separator(i: int, layout: str, eplan: EplanDocument | None) -> SclSeparator: + sensor, stopper = _sep_namen(layout, eplan) return SclSeparator( n=i, layout=layout, no=_offen("cSep_____"), - sensor=_bekannt(_SENSOR.format(bmk=sensor.bmk)) if sensor else _offen("cInBG____"), - stopper=_bekannt(_VENTIL.format(bmk=stopper.bmk)) if stopper else _offen("MB____"), + sensor=sensor, + stopper=stopper, # Teilesensor kennt die E-Planung-Bruecke noch nicht - offen part_sensor=_offen("cInBG____"), ) @@ -604,7 +622,7 @@ def _tro_context(tro: TroBase, alle: dict[str, TroBase], station_release=_offen('#"rTrig_Workstation ____".CLK'), loaded_error_sensor=_offen("cInBG____")) if items.get(TroItem.WORK_STATION) else None), - store=_store(tro) if items.get(TroItem.STORAGE_LINE) else None, + store=_store(tro, eplan) if items.get(TroItem.STORAGE_LINE) else None, ) diff --git a/lib/scl_skeleton.py b/lib/scl_skeleton.py index 3831335..93d7629 100644 --- a/lib/scl_skeleton.py +++ b/lib/scl_skeleton.py @@ -485,10 +485,17 @@ def _in_results(name: str, results: Path) -> Path: def _eingesetzt(context: SclFile) -> int: - """Wie viele Separator-Namen aus der E-Planung kamen.""" - return sum(not name.todo - for tro in context.tros for s in tro.separators - for name in (s.sensor, s.stopper)) + """Wie viele Separator-Namen aus der E-Planung kamen (auch Speicherlinien).""" + namen = [] + for tro in context.tros: + if tro.store is not None: + # Speicher: Pin-Separator und Linien; der Separatorblock wiederholt + # nur die erste Linie und wird im PinStore-Template nicht geschrieben + namen += [tro.store.pin_sensor, tro.store.pin_stopper] + namen += [n for linie in tro.store.lines for n in (linie.sensor, linie.stopper)] + else: + namen += [n for s in tro.separators for n in (s.sensor, s.stopper)] + return sum(not name.todo for name in namen) def _schema_of(path: Path) -> str: diff --git a/tests/test_e_planning.py b/tests/test_e_planning.py index f3d495e..edd722e 100644 --- a/tests/test_e_planning.py +++ b/tests/test_e_planning.py @@ -208,6 +208,35 @@ def test_kontext_setzt_namen_ein_und_loescht_den_merker(): assert kontext.eplan_source.startswith("Mubea_positionsdraw.json") +def test_kontext_setzt_namen_auch_in_speicherlinien_ein(): + """PinStore_Auto: die Linien sind Separatoren im Layout - ihre E-Planung-Namen + kommen in store.lines an (und damit ins PinStore-Template), nicht nur in den + Separatorblock.""" + from model import Analysis, DerivedTro, TroDocument + from scl_skeleton import TEMPLATE_DIR, SclTemplates + + linien = ["0066", "0069"] + store = DerivedTro(tro_id="TRO04", type_name="PinStore_Auto", label="Speicher", + hosts=["0016", "0017"], separators=linien, scanners=[], + reason="Test", predecessors=["TRO03"], successors=["TRO05"]) + punkte = [LayoutPoint("0066", "Separator", 0, 0), LayoutPoint("0069", "Separator", 1000, 0)] + geraete = [_dev("BG0766", "IN SEP", 0, 0), _dev("MB0767", "SEP", 0, 300), + _dev("BG0769", "IN SEP", 1000, 0), _dev("MB0770", "SEP", 1000, 300)] + eplan = EplanDocument(csv_file="mubea.csv", eplan_file="Mubea_positionsdraw.json", + assignments=assign(punkte, geraete, [SENSOR, STOPPER], IDENT)) + document = TroDocument.from_analysis(Analysis(tros=[store]), source="mubea.csv") + kontext = build_context(document, "mubea_tro.json", eplan=eplan) + + zeilen = kontext.tros[0].store.lines + assert [(z.layout, z.sensor.value, z.stopper.value) for z in zeilen] == [ + ("0066", "cInBG0766", "MB0767"), ("0069", "cInBG0769", "MB0770")] + assert not any(n.todo for z in zeilen for n in (z.sensor, z.stopper)) + + text = SclTemplates(TEMPLATE_DIR).render(kontext.tros[0]) + assert '#TRO04.arInSep[#nActNo].stSenTrolley := "DB_Inputs".Sensors["cInBG0766"];\n' in text + assert '"MB0770" := #TRO04.arOutSeparatorStore[2];\n' in text + + def _lauf(tmp_path, monkeypatch, eplan: EplanDocument, *weitere: str) -> tuple[int, Path]: shutil.copy(FIXTURES / "mubea_tro.json", tmp_path / "mubea_tro.json")