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 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-10-15 16:16:22 +02:00
co-authored by Claude Opus 5.5
parent 77c5aac343
commit 7ec2b88b79
5 changed files with 107 additions and 18 deletions
+5
View File
@@ -68,3 +68,8 @@ ipython_config.py
/work /work
/log /log
/results /results
# Claude Code: persoenliche Einstellungen und TRO-Kriterien-Waechter
.claude/settings.local.json
.claude/hooks/
.claude/tro_criteria_baseline
+30
View File
@@ -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
+32 -14
View File
@@ -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. """PinStore_Auto: eine Linie je Speicherbahn (Host) des Layouts.
tro_flow legt die Separatoren als [steuernde ..., Linien ...] ab; die tro_flow legt die Separatoren als [steuernde ..., Linien ...] ab; die
Linien sind die letzten len(hosts). Fehlen Hosts (fremde JSON), zaehlt 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 []) hosts = list(getattr(tro, "hosts", []) or [])
seps = list(tro.separators) seps = list(tro.separators)
anzahl = len(hosts) if 0 < len(hosts) <= len(seps) else len(seps) anzahl = len(hosts) if 0 < len(hosts) <= len(seps) else len(seps)
steuer, linien = seps[:len(seps) - anzahl], seps[len(seps) - anzahl:] 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( return SclStore(
encoder=_offen("#fbEncoder____"), encoder=_offen("#fbEncoder____"),
dest=_offen("cDest____"), dest=_offen("cDest____"),
@@ -460,27 +471,34 @@ def _store(tro: TroBase) -> SclStore:
'arrudt_StorageRemovalHandshakeTROsLvl1[____]'), 'arrudt_StorageRemovalHandshakeTROsLvl1[____]'),
conv_db=_offen('"DB_ConveyorStations".stConveyorStations____'), conv_db=_offen('"DB_ConveyorStations".stConveyorStations____'),
pin_layout=", ".join(steuer) or "?", pin_layout=", ".join(steuer) or "?",
pin_sensor=_offen("cInBG____"), pin_sensor=pin_sensor,
pin_stopper=_offen("MB____"), pin_stopper=pin_stopper,
lines=[ lines=zeilen,
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)
],
) )
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<n>) 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 {} rollen = eplan.for_layout(layout) if eplan is not None and layout != "?" else {}
sensor = rollen.get("sensor") sensor = rollen.get("sensor")
stopper = rollen.get("stopper") 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( return SclSeparator(
n=i, n=i,
layout=layout, layout=layout,
no=_offen("cSep_____"), no=_offen("cSep_____"),
sensor=_bekannt(_SENSOR.format(bmk=sensor.bmk)) if sensor else _offen("cInBG____"), sensor=sensor,
stopper=_bekannt(_VENTIL.format(bmk=stopper.bmk)) if stopper else _offen("MB____"), stopper=stopper,
# Teilesensor kennt die E-Planung-Bruecke noch nicht - offen # Teilesensor kennt die E-Planung-Bruecke noch nicht - offen
part_sensor=_offen("cInBG____"), part_sensor=_offen("cInBG____"),
) )
@@ -604,7 +622,7 @@ def _tro_context(tro: TroBase, alle: dict[str, TroBase],
station_release=_offen('#"rTrig_Workstation ____".CLK'), station_release=_offen('#"rTrig_Workstation ____".CLK'),
loaded_error_sensor=_offen("cInBG____")) loaded_error_sensor=_offen("cInBG____"))
if items.get(TroItem.WORK_STATION) else None), 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,
) )
+11 -4
View File
@@ -485,10 +485,17 @@ def _in_results(name: str, results: Path) -> Path:
def _eingesetzt(context: SclFile) -> int: def _eingesetzt(context: SclFile) -> int:
"""Wie viele Separator-Namen aus der E-Planung kamen.""" """Wie viele Separator-Namen aus der E-Planung kamen (auch Speicherlinien)."""
return sum(not name.todo namen = []
for tro in context.tros for s in tro.separators for tro in context.tros:
for name in (s.sensor, s.stopper)) 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: def _schema_of(path: Path) -> str:
+29
View File
@@ -208,6 +208,35 @@ def test_kontext_setzt_namen_ein_und_loescht_den_merker():
assert kontext.eplan_source.startswith("Mubea_positionsdraw.json") 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, def _lauf(tmp_path, monkeypatch, eplan: EplanDocument,
*weitere: str) -> tuple[int, Path]: *weitere: str) -> tuple[int, Path]:
shutil.copy(FIXTURES / "mubea_tro.json", tmp_path / "mubea_tro.json") shutil.copy(FIXTURES / "mubea_tro.json", tmp_path / "mubea_tro.json")