TRO-Fluss: tro_overrides.ini standardmaessig aus, ungelistete Strecken-Separatoren lagerichtig verketten
- tro_flow/tro_annotate: cfg/tro_overrides.ini nur noch mit --use-overrides (die Abschnitte haengen an laufenden TRO-Nummern und am CSV-Namen und veralten mit jedem Export; [export.csv.connections] ersetzte alle abgeleiteten Kanten und liess TRO13-20 unverbunden) - link_tros: Separatoren mit Zuordnung auf eine Strecke, die in keiner TrackIds-Liste stehen, per Projektion AS->ES in die Strecken-Kette einsortieren (Mubea VF_3: 0037/0038 erzeugten je 9 Fehlkanten; mubea 38 -> 22 Verbindungen, HundM05 unveraendert) - tro_catalog: typ-eigene Fuellfarbe je TRO-Typ, Mermaid-classDefs je Typ - tro_overrides/tro_flow: connect.ini zurueck einlesen (--from-connect) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1479519bdc
commit
02db9a95b0
+219
-8
@@ -142,6 +142,7 @@ from tro_overrides import (
|
||||
apply_merges,
|
||||
apply_overrides,
|
||||
apply_splits,
|
||||
load_connect_ini,
|
||||
load_overrides,
|
||||
)
|
||||
|
||||
@@ -1013,6 +1014,62 @@ def _tro_bahn(tro: Tro, graph: Graph, sep_assignment: dict[str, str]) -> str | N
|
||||
return None
|
||||
|
||||
|
||||
def _chain_mit_ungelisteten_separatoren(
|
||||
strecke: Element,
|
||||
chain: list[str],
|
||||
elements: list[Element],
|
||||
elements_by_id: dict[str, Element],
|
||||
) -> list[str]:
|
||||
"""Separatoren der Strecke, die in KEINER TrackIds-Liste stehen, lagerichtig
|
||||
in die (bereits AS -> ES orientierte) Kette einsortieren.
|
||||
|
||||
Hintergrund (Mubea, VF_3): Separatoren 0037/0038 sind per Zuordnung der
|
||||
Strecke 0015 zugeordnet und liegen physisch zwischen 0062 und 0063, fehlen
|
||||
aber in deren TrackIds. Ohne Platz in der Kette bekommen ihre TROs keinen
|
||||
praezisen Nachfolger, behalten die groben Contraction-Walk-Kanten und
|
||||
zeigen auf jeden TRO hinter Kreisel2 Bahn R (je 9 Fehlkanten).
|
||||
|
||||
Einsortiert wird ueber die Projektion auf die Gerade erstes -> letztes
|
||||
Kettenelement. Steht ein Separator in irgendeiner TrackIds-Liste (z. B.
|
||||
eines Kreisels), gehoert er dorthin und bleibt hier draussen.
|
||||
"""
|
||||
if len(chain) < 2:
|
||||
return chain
|
||||
gelistet = {tid for e in elements for tid in e.track_ids}
|
||||
fehlend = [
|
||||
e for e in elements
|
||||
if e.kind == "Separator" and e.assignment == strecke.teile_id
|
||||
and e.teile_id not in gelistet and e.position
|
||||
]
|
||||
if not fehlend:
|
||||
return chain
|
||||
anfang = elements_by_id.get(chain[0])
|
||||
ende = elements_by_id.get(chain[-1])
|
||||
if not (anfang and ende and anfang.position and ende.position):
|
||||
return chain
|
||||
dx = ende.position[0] - anfang.position[0]
|
||||
dy = ende.position[1] - anfang.position[1]
|
||||
if dx == 0 and dy == 0:
|
||||
return chain
|
||||
|
||||
def _t(pos) -> float:
|
||||
return (pos[0] - anfang.position[0]) * dx + (pos[1] - anfang.position[1]) * dy
|
||||
|
||||
# Kettenelemente ohne Position behalten ihren Platz: sie erben den Wert des
|
||||
# Vorgaengers, damit die Sortierung stabil bleibt.
|
||||
werte: list[tuple[float, int, str]] = []
|
||||
letzter = float("-inf")
|
||||
for i, tid in enumerate(chain):
|
||||
el = elements_by_id.get(tid)
|
||||
if el is not None and el.position:
|
||||
letzter = _t(el.position)
|
||||
werte.append((letzter, i, tid))
|
||||
for e in fehlend:
|
||||
werte.append((_t(e.position), len(chain), e.teile_id))
|
||||
werte.sort(key=lambda w: (w[0], w[1]))
|
||||
return [tid for _, _, tid in werte]
|
||||
|
||||
|
||||
def link_tros(
|
||||
graph: Graph, tros: list[Tro], elements: list[Element]
|
||||
) -> tuple[list[tuple[str, str]], dict[tuple[str, str], str]]:
|
||||
@@ -1133,6 +1190,7 @@ def link_tros(
|
||||
# er ist der Kreisel-Bahn zugehoerig und dort schon (Schritt 1 unten)
|
||||
# verkettet. Ohne diesen Ausschluss wuerde er faelschlich als erster
|
||||
# oder letzter "Vario" gelten (Review 2026-09-21).
|
||||
chain = _chain_mit_ungelisteten_separatoren(el, chain, elements, elements_by_id)
|
||||
owned = [
|
||||
elem_owner[tid] for tid in chain if tid in elem_owner
|
||||
and not ((d := by_id[elem_owner[tid]].definition()) and d.has_item(TroItem.SWITCH))
|
||||
@@ -1550,7 +1608,7 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
||||
# Kantenarten. Bei -n braucht auch sie ein pos, sonst landet sie auf 0,0
|
||||
# mitten in der Anlage - daher unterhalb links vom Anlagenrand.
|
||||
rows = "".join(
|
||||
f'<tr><td bgcolor="{d.get_style().fill}" align="left"> {d.get_name()} </td>'
|
||||
f'<tr><td bgcolor="{d.get_fill()}" color="{d.get_style().stroke}" align="left"> {d.get_name()} </td>'
|
||||
f'<td align="left">{d.describe_symbol()}</td></tr>'
|
||||
for d in TRO_CATALOG
|
||||
)
|
||||
@@ -1584,6 +1642,90 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# connect.ini (TRO-Ebene) - --connect schreibt, --from-connect liest
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def render_connect_ini(analysis: Analysis, section: str, source: str) -> str:
|
||||
"""
|
||||
TRO-Topologie als connect.ini, angelehnt an doc/TRO_Katalog/connect.ini
|
||||
(hmf-connect-v1). Zurueck eingelesen von tro_overrides.load_connect_ini
|
||||
(--from-connect): geaenderte Typen und Verbindungen gehen dann in DOT/SVG.
|
||||
|
||||
Abweichung vom Referenzformat: Systemgrenzen stehen nicht als EXTERN,
|
||||
sondern mit ihrer ID (SYSn), damit die Datei verlustfrei zurueckgelesen
|
||||
werden kann. [<Anlage>.tros] ist eine reine Uebersicht (Vorgaenger/
|
||||
Nachfolger je TRO) und wird beim Einlesen ignoriert.
|
||||
"""
|
||||
out: list[str] = []
|
||||
add = out.append
|
||||
add("; ============================================================")
|
||||
add("; connect.ini - Routing / Zusammenhalt der TROs (TRO-Ebene)")
|
||||
add(f"; Automatisch erzeugt von lib/tro_flow.py aus '{source}'.")
|
||||
add("; Wird bei jedem Lauf mit --connect neu geschrieben. Eine von Hand")
|
||||
add("; geaenderte Fassung wird mit --from-connect gezeichnet; dauerhafte")
|
||||
add("; Korrekturen gehoeren nach cfg/tro_overrides.ini.")
|
||||
add(";")
|
||||
add("; [<Anlage>.nodes] TRO = <Typ> | <Bezeichnung> | <Bauteile>")
|
||||
add("; SYSn = Materialeingang/-ausgang (kein TRO)")
|
||||
add("; [<Anlage>.connections] cNNN = <von> -> <nach> | <JamArea> | <kind>")
|
||||
add("; kind: normal | transfer | bahn | weiche | umlauf | unbestimmt | gesetzt")
|
||||
add("; [<Anlage>.externals] eNNN = SYSn -> <TRO> | <JamArea> | <kind> (Zulauf)")
|
||||
add("; eNNN = <TRO> -> SYSn | <JamArea> | <kind> (Ablauf)")
|
||||
add("; [<Anlage>.tros] TRO = <Typ> | prev: ... | next: ...")
|
||||
add("; nur Uebersicht - beim Einlesen ignoriert")
|
||||
add("; ============================================================")
|
||||
add("")
|
||||
|
||||
add(f"[{section}.nodes]")
|
||||
for tro in analysis.tros:
|
||||
teile = []
|
||||
if tro.separators:
|
||||
teile.append(f"Sep {', '.join(tro.separators)}")
|
||||
if tro.scanners:
|
||||
teile.append(f"Scan {', '.join(tro.scanners)}")
|
||||
add(f"{tro.tro_id} = {tro.type_name} | {tro.label}"
|
||||
+ (f" | {'; '.join(teile)}" if teile else ""))
|
||||
for boundary in analysis.boundaries:
|
||||
add(f"{boundary.tro_id} = {boundary.type_name} | {boundary.label}")
|
||||
add("")
|
||||
|
||||
boundary_ids = {b.tro_id for b in analysis.boundaries}
|
||||
intern = [e for e in analysis.edges
|
||||
if e[0] not in boundary_ids and e[1] not in boundary_ids]
|
||||
extern = [e for e in analysis.edges
|
||||
if e[0] in boundary_ids or e[1] in boundary_ids]
|
||||
|
||||
add(f"[{section}.connections]")
|
||||
for i, (src, dst) in enumerate(intern, start=1):
|
||||
kind = analysis.edge_kinds.get((src, dst)) or "normal"
|
||||
add(f"c{i:03d} = {src} -> {dst} | | {kind}")
|
||||
add("")
|
||||
|
||||
add(f"[{section}.externals]")
|
||||
if not extern and boundary_ids:
|
||||
add(f"; Systemgrenzen ohne Verbindung: {', '.join(sorted(boundary_ids))}")
|
||||
elif not extern:
|
||||
add("; keine Be-/Entladestation im Layout - Materialein-/ausgang offen")
|
||||
for i, (src, dst) in enumerate(extern, start=1):
|
||||
kind = analysis.edge_kinds.get((src, dst)) or "normal"
|
||||
add(f"e{i:03d} = {src} -> {dst} | | {kind}")
|
||||
add("")
|
||||
|
||||
add(f"[{section}.tros]")
|
||||
for tro in analysis.tros:
|
||||
prev = ", ".join(tro.predecessors) or "-"
|
||||
nxt = ", ".join(tro.successors) or "-"
|
||||
line = f"{tro.tro_id} = {tro.type_name} | prev: {prev} | next: {nxt}"
|
||||
if tro.colocated:
|
||||
line += f" | gleiche Bahn: {', '.join(tro.colocated)}"
|
||||
add(line)
|
||||
add("")
|
||||
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fehlerdatei
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1669,9 +1811,9 @@ def _tro_mermaid(analysis: Analysis) -> list[str]:
|
||||
|
||||
for tro in analysis.tros:
|
||||
definition = tro.definition()
|
||||
group = definition.get_group() if definition else "ext"
|
||||
css_class = definition.mermaid_class() if definition else "ext"
|
||||
label = f"{tro.tro_id}<br/>{tro.type_name}<br/>{_mermaid_text(tro.label)}"
|
||||
out.append(f' {tro.tro_id}["{label}"]:::{group}')
|
||||
out.append(f' {tro.tro_id}["{label}"]:::{css_class}')
|
||||
|
||||
for boundary in analysis.boundaries:
|
||||
label = f"{boundary.tro_id}<br/>{boundary.type_name}<br/>{_mermaid_text(boundary.label)}"
|
||||
@@ -1921,12 +2063,12 @@ def render_doc(
|
||||
add(f"Typen, FB-Bausteine, Bauteile und Farben stammen vollstaendig aus "
|
||||
f"`TRO_CATALOG` in `lib/tro_catalog.py` (Stand {CATALOG_AS_OF}):")
|
||||
add("")
|
||||
add("| # | Typ | FB-Baustein | Bauteile je TRO | Farbgruppe | Symbol |")
|
||||
add("|---:|---|---|---|---|---|")
|
||||
add("| # | Typ | FB-Baustein | Bauteile je TRO | Farbgruppe | Symbol | Fuellfarbe |")
|
||||
add("|---:|---|---|---|---|---|---|")
|
||||
for index, definition in enumerate(TRO_CATALOG, start=1):
|
||||
add(f"| {index} | `{definition.get_name()}` | `{definition.get_fb_block()}` "
|
||||
f"| {definition.describe_items()} | {definition.get_group()} "
|
||||
f"| {definition.describe_symbol()} |")
|
||||
f"| {definition.describe_symbol()} | `{definition.get_fill()}` |")
|
||||
add("")
|
||||
add("| Bauteil | Sub-FB |")
|
||||
add("|---|---|")
|
||||
@@ -2085,6 +2227,25 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
help="Dokumentation der TROs als Markdown in %%SKEL_RESULTS%% erzeugen "
|
||||
"(TRO-Liste, Verbindungen, Typdefinition).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--connect",
|
||||
action="store_true",
|
||||
help="TRO-Topologie als <name>_tro_connect.ini nach %%SKEL_RESULTS%% "
|
||||
"schreiben: Knoten (TRO = Typ), Verbindungen, Systemgrenzen und je "
|
||||
"TRO Vorgaenger/Nachfolger.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--from-connect",
|
||||
dest="from_connect",
|
||||
nargs="?",
|
||||
const="",
|
||||
default=None,
|
||||
metavar="INI",
|
||||
help="Typen und Verbindungen aus einer (von Hand geaenderten) connect.ini "
|
||||
"uebernehmen und damit DOT/SVG/Doku erzeugen. Ohne Wert: "
|
||||
"<name>_tro_connect.ini in %%SKEL_RESULTS%%. Die connect.ini wird "
|
||||
"dabei nicht ueberschrieben.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--geometry",
|
||||
action="store_true",
|
||||
@@ -2102,6 +2263,21 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
"Anschlusspunkte als derselbe Uebergabepunkt gelten. "
|
||||
"Standard: %(default)s",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use-overrides",
|
||||
dest="use_overrides",
|
||||
action="store_true",
|
||||
help="Manuelle Korrekturen aus cfg/tro_overrides.ini anwenden. Standard "
|
||||
"ist AUS: TROs, Typen und Verbindungen kommen rein aus dem Layout "
|
||||
"(die INI-Abschnitte haengen an laufenden TRO-Nummern und veralten "
|
||||
"mit jedem neuen Export).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-overrides",
|
||||
dest="use_overrides",
|
||||
action="store_false",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
@@ -2165,7 +2341,28 @@ def main(argv: list[str] | None = None) -> int:
|
||||
if raw_json_file is not None:
|
||||
sepliste_counts = load_sepliste_counts(raw_json_file, warnings)
|
||||
|
||||
overrides = load_overrides(csv_file.name, env_dir("SKEL_CFG", "cfg"))
|
||||
if args.use_overrides:
|
||||
overrides = load_overrides(csv_file.name, env_dir("SKEL_CFG", "cfg"))
|
||||
else:
|
||||
overrides = Overrides()
|
||||
|
||||
stem = csv_file.stem + ("_geo" if args.geometry else "")
|
||||
connect_target = results / f"{stem}_tro_connect.ini"
|
||||
connect_source: Path | None = None
|
||||
if args.from_connect is not None:
|
||||
connect_source = Path(args.from_connect) if args.from_connect else connect_target
|
||||
if not connect_source.is_absolute() and connect_source.parent == Path("."):
|
||||
connect_source = results / connect_source
|
||||
if not connect_source.is_file():
|
||||
print(f"FEHLER: connect.ini nicht gefunden: {connect_source}", file=sys.stderr)
|
||||
print(" Erst mit --connect erzeugen.", file=sys.stderr)
|
||||
return 1
|
||||
try:
|
||||
overrides = load_connect_ini(connect_source, overrides)
|
||||
except ValueError as exc:
|
||||
print(f"FEHLER: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
analysis = analyse_tros(graph, elements, overrides,
|
||||
resolve_host=resolve_host, count_exits=count_exits,
|
||||
method=method, sepliste_counts=sepliste_counts)
|
||||
@@ -2174,7 +2371,6 @@ def main(argv: list[str] | None = None) -> int:
|
||||
f"ableitbar.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
stem = csv_file.stem + ("_geo" if args.geometry else "")
|
||||
dot_file = results / f"{stem}_tro_flow.dot"
|
||||
svg_file = results / f"{stem}_tro_flow.svg"
|
||||
doc_target = results / f"{stem}_tro_doc.md"
|
||||
@@ -2194,6 +2390,14 @@ def main(argv: list[str] | None = None) -> int:
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
connect_file = None
|
||||
if args.connect and connect_source is None:
|
||||
connect_target.write_text(
|
||||
render_connect_ini(analysis, section=stem, source=csv_file.name),
|
||||
encoding="utf-8",
|
||||
)
|
||||
connect_file = connect_target
|
||||
|
||||
has_errors = write_error_file(analysis, csv_file, error_target)
|
||||
error_file = error_target if has_errors else None
|
||||
|
||||
@@ -2217,6 +2421,13 @@ def main(argv: list[str] | None = None) -> int:
|
||||
|
||||
report(analysis, csv_file, dot_file, created_svg, error_file, doc_file,
|
||||
warnings, engine=engine)
|
||||
if connect_file:
|
||||
print(f"connect.ini = {connect_file}")
|
||||
if connect_source:
|
||||
print(f"Gezeichnet aus = {connect_source}")
|
||||
if args.connect and connect_source is not None:
|
||||
print("HINWEIS: --connect zusammen mit --from-connect - die connect.ini "
|
||||
"wird nicht ueberschrieben.")
|
||||
|
||||
if svg_error:
|
||||
print(f"FEHLER: SVG nicht erzeugt: {svg_error}", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user