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
/log
/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.
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<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 {}
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,
)
+11 -4
View File
@@ -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:
+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")
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")