From e2f1a558a27be89f00dee57888082fd0798ee6cf Mon Sep 17 00:00:00 2001 From: Samer Ayadi Date: Mon, 5 Oct 2026 15:34:18 +0200 Subject: [PATCH] SCL-Geruest: Stationen (SYSn) als Materialeingang; kurze TRO-Verbindungen nicht mehr verwerfen scl_skeleton.py brach mit KeyError ab, sobald der TRO-Fluss Stationen (SYSn) enthielt. Stationen bekommen jetzt keine REGION/Instanz, der von einer Station gespeiste TRO ist der Anfang der Reihenfolge, in den Staubereich-Kommentaren sind sie als Station markiert. tro_annotate.py liess Verbindungen unter 2,2 Markergroessen still weg (HundM05 TRO03->TRO04, 500 mm) - sie fehlten dann in tro_extract.py und in der SCL. Kurze Verbindungen werden jetzt Mitte-zu-Mitte gezeichnet. Co-Authored-By: Claude Opus 5.5 --- doc/Python_Scripts.md | 7 +++++++ lib/scl_skeleton.py | 30 +++++++++++++++++++++++++----- lib/tro_annotate.py | 18 ++++++++++++++---- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/doc/Python_Scripts.md b/doc/Python_Scripts.md index 5948eb9..c011626 100644 --- a/doc/Python_Scripts.md +++ b/doc/Python_Scripts.md @@ -432,6 +432,13 @@ no loading/unloading station: the flow is then a closed loop with no entry, so the order has to be broken somewhere arbitrary. The chosen start and the reason are recorded in the file header. +**Stations (`SYSn`).** Loading/unloading stations from `tro_flow.py` appear in +the JSON as `SYSn` neighbours, not as TROs. They get no REGION and no instance. +A TRO fed by a `SYSn` is the start of the order ("Materialeingang ueber SYSn"), +so `--start` is no longer needed once the export contains a station. In the +jam-area comments they are marked "(Station, kein TRO)", and an exit station +shows up as a destination in the per-switch lists. + ## 6. `tro_catalog.py` — TRO type catalog (library, no CLI) The single source of truth for everything needed to generate or draw a TRO diff --git a/lib/scl_skeleton.py b/lib/scl_skeleton.py index 75c6afe..1228846 100644 --- a/lib/scl_skeleton.py +++ b/lib/scl_skeleton.py @@ -72,11 +72,25 @@ def flow_order(tros: list[dict], start_id: str = "") -> tuple[list[dict], str]: """ nach_id = {t["id"]: t for t in tros} ohne_vorgaenger = sorted(t["id"] for t in tros if not t.get("predecessors")) + # Materialeingang ueber eine Station (SYSn von tro_flow.py): der TRO, den + # sie speist, ist der natuerliche Anfang - auch wenn er zusaetzlich einen + # TRO-Vorgaenger hat (Kreisel-Umlauf). + eingaenge = sorted({ + t["id"] for t in tros + for vor in t.get("predecessors", []) if vor not in nach_id + }) if start_id and start_id in nach_id: start, grund = [start_id], "per --start gesetzt" elif ohne_vorgaenger: start, grund = ohne_vorgaenger, "TRO ohne Vorgaenger (Materialeingang)" + elif eingaenge: + stationen = sorted({ + vor for i in eingaenge for vor in nach_id[i]["predecessors"] + if vor not in nach_id + }) + start = eingaenge + grund = f"Materialeingang ueber {', '.join(stationen)}" else: start = [min(nach_id)] grund = ("Kreislauf ohne Eingang - willkuerlich aufgetrennt; sobald " @@ -90,7 +104,8 @@ def flow_order(tros: list[dict], start_id: str = "") -> tuple[list[dict], str]: continue gesehen.append(tro_id) for nxt in nach_id.get(tro_id, {}).get("successors", []): - if nxt not in gesehen: + # SYSn (Materialausgang) ist kein TRO - keine REGION, keine Instanz. + if nxt in nach_id and nxt not in gesehen: queue.append(nxt) for tro_id in sorted(nach_id): if tro_id not in gesehen: @@ -116,6 +131,11 @@ def reachable_via(start: str, first: str, tros: dict[str, dict]) -> list[str]: return sorted(gesehen) +def nachbar(node: str, alle: dict[str, dict]) -> str: + """Nachbar fuer Kommentare; SYSn als Station kenntlich machen.""" + return node if node in alle else f"{node} (Station, kein TRO)" + + def region_title(tro: dict) -> str: seps = ", ".join(tro.get("separators", [])) teile = f" Sep {seps}" if seps else "" @@ -199,7 +219,7 @@ def emit_barcode(add, tro: dict, anzahl: int) -> None: add("") -def emit_call(add, tro: dict, items: dict[str, int]) -> None: +def emit_call(add, tro: dict, items: dict[str, int], alle: dict[str, dict]) -> None: """Aufrufstelle schreiben. Die Zeilen werden als (Code, Kommentar) gefuehrt und das Komma erst beim @@ -224,11 +244,11 @@ def emit_call(add, tro: dict, items: dict[str, int]) -> None: # nur, WIE VIELE es sein muessen und zu welchem Nachbarn sie gehoeren. for n, vor in enumerate(tro.get("predecessors", []), start=1): zeilen.append((f'arInOutJamEntr{n} := "DB_JamArea____".stJam____.arCarrier', - f"{TODO} - Staubereich von {vor}")) + f"{TODO} - Staubereich von {nachbar(vor, alle)}")) zeilen.append((f'stInOutJamEntr{n} := "DB_JamArea____".stJam____.stData', "")) for n, nach in enumerate(tro.get("successors", []), start=2): zeilen.append((f'arInOutJamExit{n} := "DB_JamArea____".stJam____.arCarrier', - f"{TODO} - Staubereich nach {nach}")) + f"{TODO} - Staubereich nach {nachbar(nach, alle)}")) zeilen.append((f'stInOutJamExit{n} := "DB_JamArea____".stJam____.stData', "")) for i in range(1, items.get("Separator", 1) + 1): @@ -338,7 +358,7 @@ def render(daten: dict, quelle: str, start_id: str = "") -> str: add(" #%s.stInPriorityManager.xReleaseOk := TRUE;" % tro["id"]) add("") - emit_call(add, tro, items) + emit_call(add, tro, items, alle) if n_swi: emit_direction(add, tro, n_swi) add("END_REGION") diff --git a/lib/tro_annotate.py b/lib/tro_annotate.py index dde471b..fe62a04 100644 --- a/lib/tro_annotate.py +++ b/lib/tro_annotate.py @@ -680,23 +680,33 @@ def draw_connections( x1, y1 = placed[src] x2, y2 = placed[dst] length = math.dist((x1, y1), (x2, y2)) - if length <= 2 * marker * 1.1: + if length < 1e-6: continue ux, uy = (x2 - x1) / length, (y2 - y1) / length # An beiden Enden um den Marker kuerzen, damit der Pfeil ihn nicht # deckt; das Stationsende ist nur eine Beschriftung, dort weniger. kurz_a = marker * (0.3 if src in stationen else 1.1) kurz_b = marker * (0.3 if dst in stationen else 1.1) + # Zu kurz fuer einen gekuerzten Pfeil (Marker liegen dicht beieinander, + # z.B. HundM05 TRO03->TRO04 mit 500 mm): NICHT weglassen - sonst fehlt + # die Verbindung in tro_extract.py und damit in der SCL. Stattdessen + # Mitte-zu-Mitte ohne Kuerzung zeichnen. + kurz = length <= kurz_a + kurz_b + head + if kurz: + kurz_a = kurz_b = 0.0 sx, sy = x1 + ux * kurz_a, y1 + uy * kurz_a ex, ey = x2 - ux * kurz_b, y2 - uy * kurz_b shaft = msp.add_lwpolyline([(sx, sy), (ex, ey)], dxfattribs={"layer": layer}) tag(shaft, f"{src}->{dst}") - # Pfeilspitze als gefuellte Flaeche - bx, by = ex - ux * head, ey - uy * head + # Pfeilspitze als gefuellte Flaeche (bei kurzer Verbindung in der Mitte, + # damit sie nicht im Zielmarker verschwindet) + hx, hy = ((x1 + x2) / 2 + ux * head / 2, (y1 + y2) / 2 + uy * head / 2) \ + if kurz else (ex, ey) + bx, by = hx - ux * head, hy - uy * head px, py = -uy * head * 0.38, ux * head * 0.38 point = msp.add_solid( - [(ex, ey), (bx + px, by + py), (bx - px, by - py)], + [(hx, hy), (bx + px, by + py), (bx - px, by - py)], dxfattribs={"layer": layer}, ) tag(point, f"{src}->{dst}")