TRO-Fluss: ILS Weiche als echte Weiche, 2Sep2Swi am Kreisel-Beruehrpunkt, lesbare lagerichtige Darstellung
- _find_weiche_separators: ILS Weiche (TeileArt) ist ein echtes Weichen- Bauteil -> 1Sep1Swi; steuernder Separator = CSV-Nachbar mit Scanner (Mubea 0125 -> 0036), mehrdeutig -> Finding statt Raten - _find_switch_separators: Separator mit Zuordnung auf eine Strecke, der in der TrackIds-Kette eines Kreisels steht, gilt als Kreisel-Separator (Mubea 0005 vor Ausschleuselement 0057) - derive_tros 3d: Ausschleuselement-Weiche und ILS Weiche direkt hintereinander in der TrackIds-Kette -> ein 2Sep2Swi (Mubea TRO10: Sep 0005/0036, Scan 0006/0041); FB_ILS_MTRO_2Sep2Swi fehlt noch in der ILSLib - --use-cords: feste Symbolgroesse je Form, nur TRO-Nummer im Symbol, Typ/Separator als xlabel, Details als Tooltip, Massstab 0.1 statt 0.25 pt/mm, Legende rechts mit nur den vorkommenden Typen, blasse Linien fuer gleiche Bahn, Systemgrenzen dicht an einem TRO werden in der Zeichnung beiseitegeschoben - HundM05 unveraendert (27 Verbindungen), Mubea 18 TROs / 23 Verbindungen Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
02db9a95b0
commit
43543915ca
+248
-28
@@ -166,9 +166,16 @@ GEFAELLE_DROP_MM = 30.0
|
|||||||
GENERATED_MARKER = "<!-- erzeugt von lib/tro_flow.py -->"
|
GENERATED_MARKER = "<!-- erzeugt von lib/tro_flow.py -->"
|
||||||
|
|
||||||
# Zeichnungsmassstab fuer --use-cords: Graphviz rechnet in Punkt, die Anlage in
|
# Zeichnungsmassstab fuer --use-cords: Graphviz rechnet in Punkt, die Anlage in
|
||||||
# Millimeter. 0.25 pt/mm entspricht 1:4 - bei ~11 x 20 m Anlage ergibt das eine
|
# Millimeter. Seit die Symbole eine feste Groesse haben (COORD_SYMBOL_SIZE)
|
||||||
# Zeichnung von etwa 2800 x 5000 pt, in der sich die TRO-Kaesten nicht ueberdecken.
|
# statt mit ihrem Label zu wachsen, reichen 0.1 pt/mm: ~11 x 20 m Anlage ergeben
|
||||||
COORD_SCALE = 0.25
|
# etwa 1100 x 2000 pt - bildschirmfuellend lesbar. Vorher 0.25 (riesige
|
||||||
|
# Zeichnung, in der Symbole und Schrift verschwanden; Review 2026-10-01).
|
||||||
|
COORD_SCALE = 0.1
|
||||||
|
|
||||||
|
# Mindestabstand (mm) einer Systemgrenze zu einem TRO in der lagerichtigen
|
||||||
|
# Zeichnung - eine BTMT-Station sitzt oft fast auf ihrem Separator (Mubea SYS1
|
||||||
|
# 186 mm neben TRO04); sie wird dann nur in der Darstellung beiseitegeschoben.
|
||||||
|
COORD_MIN_ABSTAND = 600
|
||||||
|
|
||||||
# Abstand der Legende zum Anlagenrand in mm
|
# Abstand der Legende zum Anlagenrand in mm
|
||||||
LEGEND_MARGIN = 3000
|
LEGEND_MARGIN = 3000
|
||||||
@@ -374,13 +381,21 @@ def _find_switch_separators(
|
|||||||
for sep in separators:
|
for sep in separators:
|
||||||
sep_kreisel_id = sep.assignment.split("-", 1)[0] if sep.assignment else ""
|
sep_kreisel_id = sep.assignment.split("-", 1)[0] if sep.assignment else ""
|
||||||
kreisel = kreisel_by_id.get(sep_kreisel_id)
|
kreisel = kreisel_by_id.get(sep_kreisel_id)
|
||||||
if kreisel is None:
|
# Zuordnung zeigt auf die Strecke statt auf den Kreisel, der Separator
|
||||||
|
# steht aber in der TrackIds-Kette eines Kreisels (Mubea 0005: Zuordnung
|
||||||
|
# VF_2, liegt auf Kreisel2 Bahn R direkt vor Ausschleuselement 0057) -
|
||||||
|
# dann gilt dieser Kreisel. Die Gegenprobe "Ausschleuselement in
|
||||||
|
# derselben Kette" (Kriterium 2) bleibt unveraendert.
|
||||||
|
bahn_kreisel = [k for k in kreisel_by_id.values() if sep.teile_id in k.track_ids]
|
||||||
|
if kreisel is not None:
|
||||||
|
bahn_kreisel = [kreisel]
|
||||||
|
if not bahn_kreisel:
|
||||||
continue
|
continue
|
||||||
for base, _lane in sep.neighbours:
|
for base, _lane in sep.neighbours:
|
||||||
transfer = transfers.get(base)
|
transfer = transfers.get(base)
|
||||||
if transfer is None:
|
if transfer is None:
|
||||||
continue
|
continue
|
||||||
if transfer.teile_id not in kreisel.track_ids:
|
if not any(transfer.teile_id in k.track_ids for k in bahn_kreisel):
|
||||||
continue
|
continue
|
||||||
d = (
|
d = (
|
||||||
math.hypot(transfer.x - sep.x, transfer.y - sep.y)
|
math.hypot(transfer.x - sep.x, transfer.y - sep.y)
|
||||||
@@ -393,6 +408,46 @@ def _find_switch_separators(
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _find_weiche_separators(
|
||||||
|
separators: list[Element], elements: list[Element], findings: list[str]
|
||||||
|
) -> dict[str, Element]:
|
||||||
|
"""Separator-Id -> ILS Weiche, die er steuert.
|
||||||
|
|
||||||
|
Eine "ILS Weiche" (TeileArt, Element.kind "Weiche") ist ein ECHTES
|
||||||
|
Weichen-Bauteil - anders als das Ausschleuselement in
|
||||||
|
_find_switch_separators, das die Weichenrolle nur uebernimmt. dxfmakros
|
||||||
|
erzeugt sie z. B. am Beruehrpunkt zweier Kreisel (Merkmal "Erzeugt aus":
|
||||||
|
"Kreisel-Beruehrpunkt"; Mubea 0125 zwischen Kreisel2 und Kreisel1).
|
||||||
|
|
||||||
|
Steuernder Separator ist der Separator unter den CSV-Nachbarn der Weiche,
|
||||||
|
der einen Scanner traegt (Scanner-Merkmal "Naechster Separator"). Mubea
|
||||||
|
0125: Nachbarn 0040 und 0036, nur 0036 hat einen Scanner (0041) - vor Ort
|
||||||
|
bestaetigt (Review 2026-08-12 und 2026-10-01). Ist nur ein Separator
|
||||||
|
benachbart, gilt dieser. Bleibt es mehrdeutig, wird nichts geraten,
|
||||||
|
sondern ein Finding geschrieben.
|
||||||
|
"""
|
||||||
|
by_id = {s.teile_id: s for s in separators}
|
||||||
|
mit_scanner = {
|
||||||
|
str(_merkmal(e.merkmale, "Naechster Separator") or "").strip()
|
||||||
|
for e in elements if e.kind == "Scanner"
|
||||||
|
}
|
||||||
|
result: dict[str, Element] = {}
|
||||||
|
for weiche in (e for e in elements if e.kind == "Weiche"):
|
||||||
|
kandidaten = sorted({base for base, _lane in weiche.neighbours if base in by_id})
|
||||||
|
gewaehlt = [s for s in kandidaten if s in mit_scanner]
|
||||||
|
if len(gewaehlt) != 1:
|
||||||
|
gewaehlt = kandidaten if len(kandidaten) == 1 else []
|
||||||
|
if not gewaehlt:
|
||||||
|
findings.append(
|
||||||
|
f"ILS Weiche {weiche.teile_id}: steuernder Separator nicht "
|
||||||
|
f"eindeutig (benachbart: {', '.join(kandidaten) or '-'}) - "
|
||||||
|
f"keinem TRO zugeordnet"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
result[gewaehlt[0]] = weiche
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _incoming_drop(
|
def _incoming_drop(
|
||||||
host: Element, sep_id: str, elements_by_id: dict[str, Element]
|
host: Element, sep_id: str, elements_by_id: dict[str, Element]
|
||||||
) -> tuple[str | None, float | None]:
|
) -> tuple[str | None, float | None]:
|
||||||
@@ -543,11 +598,17 @@ def derive_tros(
|
|||||||
# als gewoehnlicher Separator ihres Zuordnung-Hosts (Kreisel-Bahn oder
|
# als gewoehnlicher Separator ihres Zuordnung-Hosts (Kreisel-Bahn oder
|
||||||
# Strecke - beides kommt vor) mitgezaehlt werden.
|
# Strecke - beides kommt vor) mitgezaehlt werden.
|
||||||
switch_hits = _find_switch_separators(switch_candidates, elements)
|
switch_hits = _find_switch_separators(switch_candidates, elements)
|
||||||
|
# Separatoren an einer echten ILS Weiche (siehe _find_weiche_separators) -
|
||||||
|
# wie switch_hits vor der Host-Aufloesung herausgenommen, Schritt 3c.
|
||||||
|
weiche_hits = _find_weiche_separators(
|
||||||
|
[s for s in switch_candidates if s.teile_id not in switch_hits],
|
||||||
|
elements, findings,
|
||||||
|
)
|
||||||
|
|
||||||
# Separator -> Host-Knoten
|
# Separator -> Host-Knoten
|
||||||
sep_host: dict[str, str] = {}
|
sep_host: dict[str, str] = {}
|
||||||
for sep in separators:
|
for sep in separators:
|
||||||
if sep.teile_id in switch_hits:
|
if sep.teile_id in switch_hits or sep.teile_id in weiche_hits:
|
||||||
continue
|
continue
|
||||||
host = resolve_host(sep, findings)
|
host = resolve_host(sep, findings)
|
||||||
if host is not None:
|
if host is not None:
|
||||||
@@ -558,7 +619,7 @@ def derive_tros(
|
|||||||
sep_scanners: dict[str, list[str]] = {}
|
sep_scanners: dict[str, list[str]] = {}
|
||||||
for scanner in scanners:
|
for scanner in scanners:
|
||||||
target = str(_merkmal(scanner.merkmale, "Naechster Separator") or "").strip()
|
target = str(_merkmal(scanner.merkmale, "Naechster Separator") or "").strip()
|
||||||
if target in sep_host or target in switch_hits:
|
if target in sep_host or target in switch_hits or target in weiche_hits:
|
||||||
sep_scanners.setdefault(target, []).append(scanner.teile_id)
|
sep_scanners.setdefault(target, []).append(scanner.teile_id)
|
||||||
mounted = scanner.assignment
|
mounted = scanner.assignment
|
||||||
target_host = sep_host.get(target)
|
target_host = sep_host.get(target)
|
||||||
@@ -804,12 +865,13 @@ def derive_tros(
|
|||||||
# vor), und die Strecke hat bereits reale, gerichtete Kanten zum Kreisel
|
# vor), und die Strecke hat bereits reale, gerichtete Kanten zum Kreisel
|
||||||
# (_apply_transfers) - darueber haengt sich die Verknuepfung (link_tros)
|
# (_apply_transfers) - darueber haengt sich die Verknuepfung (link_tros)
|
||||||
# von selbst ein, ohne einen weiteren synthetischen Host zu erfinden.
|
# von selbst ein, ohne einen weiteren synthetischen Host zu erfinden.
|
||||||
|
as_switch_tro: dict[str, Tro] = {} # Ausschleuselement-Id -> 1Sep1Swi
|
||||||
for sep_id, (transfer, distance) in sorted(switch_hits.items()):
|
for sep_id, (transfer, distance) in sorted(switch_hits.items()):
|
||||||
strecke_id = str(_merkmal(transfer.merkmale, "Strecke", "Förderer") or "")
|
strecke_id = str(_merkmal(transfer.merkmale, "Strecke", "Förderer") or "")
|
||||||
ende = str(_merkmal(transfer.merkmale, "Streckenende") or "")
|
ende = str(_merkmal(transfer.merkmale, "Streckenende") or "")
|
||||||
own_scanners = sorted(sep_scanners.get(sep_id, []))
|
own_scanners = sorted(sep_scanners.get(sep_id, []))
|
||||||
art = "Ausschleuselement"
|
art = "Ausschleuselement"
|
||||||
tros.append(
|
as_switch_tro[transfer.teile_id] = tro = (
|
||||||
Tro(
|
Tro(
|
||||||
tro_id="",
|
tro_id="",
|
||||||
type_name="1Sep1Swi",
|
type_name="1Sep1Swi",
|
||||||
@@ -826,6 +888,64 @@ def derive_tros(
|
|||||||
confidence="mittel",
|
confidence="mittel",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
tros.append(tro)
|
||||||
|
|
||||||
|
# 3c) Separatoren an einer echten ILS Weiche -> 1Sep1Swi (siehe
|
||||||
|
# _find_weiche_separators). Host wie bei 3b: der aufgeloeste Zuordnung-Host
|
||||||
|
# des Separators, sonst die Weiche selbst.
|
||||||
|
weiche_tro: dict[str, Tro] = {} # Weichen-Id -> 1Sep1Swi
|
||||||
|
for sep_id, weiche in sorted(weiche_hits.items()):
|
||||||
|
host = resolve_host(elements_by_id[sep_id], findings)
|
||||||
|
weiche_tro[weiche.teile_id] = tro = Tro(
|
||||||
|
tro_id="",
|
||||||
|
type_name="1Sep1Swi",
|
||||||
|
label=f"Separator {sep_id} an ILS Weiche {weiche.teile_id}",
|
||||||
|
hosts=[host] if host is not None else [weiche.teile_id],
|
||||||
|
separators=[sep_id],
|
||||||
|
scanners=sorted(sep_scanners.get(sep_id, [])),
|
||||||
|
reason=f"steuert die ILS Weiche {weiche.teile_id} (echtes Weichen-Bauteil)",
|
||||||
|
confidence="hoch",
|
||||||
|
)
|
||||||
|
tros.append(tro)
|
||||||
|
|
||||||
|
# 3d) Ausschleuselement-Weiche und ILS Weiche direkt hintereinander in der
|
||||||
|
# TrackIds-Kette eines Kreisels -> EIN 2Sep2Swi (2 Separatoren, 2 Weichen,
|
||||||
|
# 2 Scanner). Mubea: Kreisel2 ... 0005 -> 0057 (AS VF_2) -> 0125 (ILS
|
||||||
|
# Weiche zu Kreisel1) -> 0012 ...; Separator 0005 steuert das Ausschleusen,
|
||||||
|
# 0036 die Weiche - steuerungstechnisch eine Uebergabestelle (Review
|
||||||
|
# 2026-08-12, im Code seit 2026-10-01). Host ist der des AS-Teils (die
|
||||||
|
# Strecke), damit link_tros die Weichenkante in deren ersten Vario-TRO zieht.
|
||||||
|
for kreisel in (e for e in elements if e.kind == "Kreisel"):
|
||||||
|
chain = list(kreisel.track_ids)
|
||||||
|
if len(chain) < 2:
|
||||||
|
continue
|
||||||
|
for a, b in zip(chain, chain[1:] + chain[:1]):
|
||||||
|
for as_id, w_id in ((a, b), (b, a)):
|
||||||
|
as_tro, w_tro = as_switch_tro.get(as_id), weiche_tro.get(w_id)
|
||||||
|
if not (as_tro and w_tro and as_tro in tros and w_tro in tros):
|
||||||
|
continue
|
||||||
|
tros.remove(as_tro)
|
||||||
|
tros.remove(w_tro)
|
||||||
|
tros.append(Tro(
|
||||||
|
tro_id="",
|
||||||
|
type_name="2Sep2Swi",
|
||||||
|
label=(f"Uebergabestelle {kreisel.name}: Ausschleuselement "
|
||||||
|
f"{as_id} + ILS Weiche {w_id}"),
|
||||||
|
hosts=list(as_tro.hosts),
|
||||||
|
separators=as_tro.separators + w_tro.separators,
|
||||||
|
scanners=sorted(as_tro.scanners + w_tro.scanners),
|
||||||
|
reason=(f"Ausschleuselement {as_id} (Separator "
|
||||||
|
f"{', '.join(as_tro.separators)}) und ILS Weiche {w_id} "
|
||||||
|
f"(Separator {', '.join(w_tro.separators)}) liegen in "
|
||||||
|
f"der TrackIds-Kette von {kreisel.name} direkt "
|
||||||
|
f"hintereinander - eine Uebergabestelle"),
|
||||||
|
confidence="hoch",
|
||||||
|
))
|
||||||
|
findings.append(
|
||||||
|
f"{kreisel.name}: Ausschleuselement {as_id} und ILS Weiche "
|
||||||
|
f"{w_id} benachbart - als 2Sep2Swi zusammengefasst "
|
||||||
|
f"(FB_ILS_MTRO_2Sep2Swi fehlt noch in der ILSLib)"
|
||||||
|
)
|
||||||
|
|
||||||
# 4) Weichen-Aufwertung: 2 bzw. 3 abgehende Flusskanten
|
# 4) Weichen-Aufwertung: 2 bzw. 3 abgehende Flusskanten
|
||||||
for tro in tros:
|
for tro in tros:
|
||||||
@@ -1502,14 +1622,91 @@ def missing_positions(analysis: Analysis) -> list[str]:
|
|||||||
return [t.tro_id for t in analysis.tros + analysis.boundaries if not t.position]
|
return [t.tro_id for t in analysis.tros + analysis.boundaries if not t.position]
|
||||||
|
|
||||||
|
|
||||||
def _legend_pos(analysis: Analysis) -> tuple[float, float]:
|
# Lagerichtige Darstellung (--use-cords): feste Symbolgroesse je Form in Zoll
|
||||||
"""Legende links unterhalb der Anlage platzieren (mm)."""
|
# (Breite, Hoehe). Ohne fixedsize waechst jede Form mit ihrem Label - Dreiecke
|
||||||
|
# und Doppel-Dreiecke wurden dadurch um ein Vielfaches groesser als die Kreise
|
||||||
|
# (Review 2026-10-01: "manche Symbole sind riesig").
|
||||||
|
COORD_SYMBOL_SIZE: dict[str, tuple[float, float]] = {
|
||||||
|
"circle": (0.45, 0.45),
|
||||||
|
"triangle": (0.62, 0.52),
|
||||||
|
"invtriangle": (0.62, 0.52),
|
||||||
|
"box": (0.7, 0.34),
|
||||||
|
"square": (0.55, 0.55),
|
||||||
|
"diamond": (0.62, 0.5),
|
||||||
|
"pentagon": (0.55, 0.52),
|
||||||
|
"hexagon": (0.65, 0.45),
|
||||||
|
"rarrow": (0.7, 0.36),
|
||||||
|
"ellipse": (0.7, 0.34),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _anzeige_position(
|
||||||
|
boundary: Tro, tros: list[Tro]
|
||||||
|
) -> tuple[float, float, float] | None:
|
||||||
|
"""Position einer Systemgrenze fuer die Zeichnung: liegt sie naeher als
|
||||||
|
COORD_MIN_ABSTAND an einem TRO, wird sie radial von ihm weggeschoben."""
|
||||||
|
if not boundary.position:
|
||||||
|
return None
|
||||||
|
x, y, z = boundary.position
|
||||||
|
for tro in tros:
|
||||||
|
if not tro.position:
|
||||||
|
continue
|
||||||
|
dx, dy = x - tro.position[0], y - tro.position[1]
|
||||||
|
d = math.hypot(dx, dy)
|
||||||
|
if d >= COORD_MIN_ABSTAND:
|
||||||
|
continue
|
||||||
|
if d == 0:
|
||||||
|
dx, dy, d = 0.0, -1.0, 1.0
|
||||||
|
x = tro.position[0] + dx / d * COORD_MIN_ABSTAND
|
||||||
|
y = tro.position[1] + dy / d * COORD_MIN_ABSTAND
|
||||||
|
return (x, y, z)
|
||||||
|
|
||||||
|
|
||||||
|
def _tro_kurzinfo(tro: Tro) -> str:
|
||||||
|
"""Zweizeilige Beschriftung neben dem Symbol: Typ und Bauteile."""
|
||||||
|
if tro.type_name in BOUNDARY_TYPES:
|
||||||
|
return _dot_label([tro.type_name, tro.label])
|
||||||
|
if len(tro.separators) > 3:
|
||||||
|
teile = f"{len(tro.separators)} Separatoren"
|
||||||
|
elif tro.separators:
|
||||||
|
teile = "Sep " + ", ".join(tro.separators)
|
||||||
|
else:
|
||||||
|
teile = tro.label
|
||||||
|
return _dot_label([tro.type_name, teile])
|
||||||
|
|
||||||
|
|
||||||
|
def _tro_tooltip(tro: Tro) -> str:
|
||||||
|
"""Vollstaendige Angaben als SVG-Tooltip (Zeilenumbruch per )."""
|
||||||
|
return _tro_label(tro).replace("\\n", " ")
|
||||||
|
|
||||||
|
|
||||||
|
def _coord_node_attrs(tro: Tro, base: dict[str, str]) -> str:
|
||||||
|
"""Knotenattribute fuer --use-cords: feste Groesse, kurzes Label im Symbol,
|
||||||
|
Typ/Bauteile als xlabel daneben, alle Details als Tooltip."""
|
||||||
|
attrs = dict(base)
|
||||||
|
shape = attrs.get("shape", "box")
|
||||||
|
width, height = COORD_SYMBOL_SIZE.get(shape, (0.7, 0.5))
|
||||||
|
attrs.update({
|
||||||
|
"fixedsize": "true",
|
||||||
|
"width": f"{width}",
|
||||||
|
"height": f"{height}",
|
||||||
|
"fontsize": "8",
|
||||||
|
"label": tro.tro_id,
|
||||||
|
"xlabel": _tro_kurzinfo(tro),
|
||||||
|
"tooltip": _tro_tooltip(tro),
|
||||||
|
})
|
||||||
|
return ", ".join(f'{k}="{v}"' for k, v in attrs.items())
|
||||||
|
|
||||||
|
|
||||||
|
def _legend_pos_rechts(analysis: Analysis) -> tuple[float, float]:
|
||||||
|
"""Legende rechts neben der Anlage, obere Kante etwa auf Anlagenoberkante
|
||||||
|
(mm; pos ist bei neato -n der Knotenmittelpunkt)."""
|
||||||
all_nodes = analysis.tros + analysis.boundaries
|
all_nodes = analysis.tros + analysis.boundaries
|
||||||
xs = [t.position[0] for t in all_nodes if t.position]
|
xs = [t.position[0] for t in all_nodes if t.position]
|
||||||
ys = [t.position[1] for t in all_nodes if t.position]
|
ys = [t.position[1] for t in all_nodes if t.position]
|
||||||
if not xs:
|
if not xs:
|
||||||
return (0.0, 0.0)
|
return (0.0, 0.0)
|
||||||
return (min(xs) - LEGEND_MARGIN, min(ys) - LEGEND_MARGIN)
|
return (max(xs) + LEGEND_MARGIN + 1500, max(ys) - 1500)
|
||||||
|
|
||||||
|
|
||||||
# Kantenstil je Art (an material_flow.py angelehnt: Umlauf hellblau gestrichelt,
|
# Kantenstil je Art (an material_flow.py angelehnt: Umlauf hellblau gestrichelt,
|
||||||
@@ -1546,17 +1743,23 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
|||||||
add("digraph TROFluss {")
|
add("digraph TROFluss {")
|
||||||
if use_coords:
|
if use_coords:
|
||||||
# Bei -n bestimmen die pos-Attribute die Lage; rankdir/ranksep entfallen.
|
# Bei -n bestimmen die pos-Attribute die Lage; rankdir/ranksep entfallen.
|
||||||
add(" graph [splines=true, outputorder=edgesfirst,")
|
add(" graph [splines=true, outputorder=edgesfirst, forcelabels=true,")
|
||||||
add(' fontname="Segoe UI", fontsize=11, labelloc="t",')
|
add(' bgcolor="white", pad=0.6,')
|
||||||
add(f' label="TRO-Fluss lagerichtig ({COORD_SCALE} pt/mm) - '
|
add(' fontname="Segoe UI", fontsize=16, labelloc="t",')
|
||||||
f'{_dot_escape(source)}"];')
|
add(f' label="TRO-Fluss - {_dot_escape(source)}"];')
|
||||||
else:
|
else:
|
||||||
add(" graph [rankdir=LR, splines=spline, nodesep=0.35, ranksep=1.3,")
|
add(" graph [rankdir=LR, splines=spline, nodesep=0.35, ranksep=1.3,")
|
||||||
add(' fontname="Segoe UI", fontsize=11, labelloc="t",')
|
add(' fontname="Segoe UI", fontsize=11, labelloc="t",')
|
||||||
add(f' label="TRO-Fluss - {_dot_escape(source)}"];')
|
add(f' label="TRO-Fluss - {_dot_escape(source)}"];')
|
||||||
add(' node [shape=box, style="rounded,filled", fontname="Segoe UI", fontsize=9];')
|
if use_coords:
|
||||||
add(' edge [fontname="Segoe UI", fontsize=8, color="#2f5597", penwidth=1.2,'
|
add(' node [shape=box, style="filled", penwidth=1.6, fontname="Segoe UI Semibold",'
|
||||||
" arrowsize=0.8];")
|
' fontsize=9];')
|
||||||
|
add(' edge [fontname="Segoe UI", fontsize=8, color="#2f5597", penwidth=1.3,'
|
||||||
|
" arrowsize=0.7];")
|
||||||
|
else:
|
||||||
|
add(' node [shape=box, style="rounded,filled", fontname="Segoe UI", fontsize=9];')
|
||||||
|
add(' edge [fontname="Segoe UI", fontsize=8, color="#2f5597", penwidth=1.2,'
|
||||||
|
" arrowsize=0.8];")
|
||||||
add("")
|
add("")
|
||||||
|
|
||||||
for tro in analysis.tros:
|
for tro in analysis.tros:
|
||||||
@@ -1572,7 +1775,10 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
|||||||
scale = COORD_SCALE if use_coords else 0.01
|
scale = COORD_SCALE if use_coords else 0.01
|
||||||
pos = (f', pos="{tro.position[0] * scale:.2f},'
|
pos = (f', pos="{tro.position[0] * scale:.2f},'
|
||||||
f'{tro.position[1] * scale:.2f}"')
|
f'{tro.position[1] * scale:.2f}"')
|
||||||
add(f' "{tro.tro_id}" [label="{_tro_label(tro)}", {rendered}{pos}];')
|
if use_coords:
|
||||||
|
add(f' "{tro.tro_id}" [{_coord_node_attrs(tro, attrs)}{pos}];')
|
||||||
|
else:
|
||||||
|
add(f' "{tro.tro_id}" [label="{_tro_label(tro)}", {rendered}{pos}];')
|
||||||
add("")
|
add("")
|
||||||
|
|
||||||
# Systemgrenzen (BTMT Be-/Entladestationen): kein Katalogtyp, eigene
|
# Systemgrenzen (BTMT Be-/Entladestationen): kein Katalogtyp, eigene
|
||||||
@@ -1581,12 +1787,18 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
|||||||
for boundary in analysis.boundaries:
|
for boundary in analysis.boundaries:
|
||||||
rendered = ", ".join(f'{k}="{v}"' for k, v in STYLE_EXT.dot_attrs().items())
|
rendered = ", ".join(f'{k}="{v}"' for k, v in STYLE_EXT.dot_attrs().items())
|
||||||
pos = ""
|
pos = ""
|
||||||
if boundary.position:
|
position = (_anzeige_position(boundary, analysis.tros) if use_coords
|
||||||
|
else boundary.position)
|
||||||
|
if position:
|
||||||
scale = COORD_SCALE if use_coords else 0.01
|
scale = COORD_SCALE if use_coords else 0.01
|
||||||
pos = (f', pos="{boundary.position[0] * scale:.2f},'
|
pos = (f', pos="{position[0] * scale:.2f},'
|
||||||
f'{boundary.position[1] * scale:.2f}"')
|
f'{position[1] * scale:.2f}"')
|
||||||
add(f' "{boundary.tro_id}" [label="{_tro_label(boundary)}", '
|
if use_coords:
|
||||||
f'shape=ellipse, style=filled, {rendered}{pos}];')
|
attrs = dict(STYLE_EXT.dot_attrs(), shape="ellipse")
|
||||||
|
add(f' "{boundary.tro_id}" [{_coord_node_attrs(boundary, attrs)}{pos}];')
|
||||||
|
else:
|
||||||
|
add(f' "{boundary.tro_id}" [label="{_tro_label(boundary)}", '
|
||||||
|
f'shape=ellipse, style=filled, {rendered}{pos}];')
|
||||||
add("")
|
add("")
|
||||||
|
|
||||||
for src, dst in analysis.edges:
|
for src, dst in analysis.edges:
|
||||||
@@ -1599,18 +1811,26 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
|||||||
if key in seen:
|
if key in seen:
|
||||||
continue
|
continue
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
|
# Lagerichtig liegen gleiche Bahn/Strecke ohnehin sichtbar
|
||||||
|
# nebeneinander - dort nur noch als blasser Hinweis.
|
||||||
|
farbe = '"#c8c8c8", penwidth=0.7' if use_coords else '"#7f7f7f"'
|
||||||
add(f' "{tro.tro_id}" -> "{other}" [dir=none, style=dotted, '
|
add(f' "{tro.tro_id}" -> "{other}" [dir=none, style=dotted, '
|
||||||
f'color="#7f7f7f", constraint=false, tooltip="gleiche Bahn"];')
|
f'color={farbe}, constraint=false, tooltip="gleiche Bahn"];')
|
||||||
add("")
|
add("")
|
||||||
|
|
||||||
# Legende: eine Zeile je TRO-Typ (Farbe der Gruppe + eigene Form, siehe
|
# Legende: eine Zeile je TRO-Typ (Farbe der Gruppe + eigene Form, siehe
|
||||||
# tro_catalog.TRO_CATALOG), plus eine Zeile fuer die Systemgrenzen und die
|
# tro_catalog.TRO_CATALOG), plus eine Zeile fuer die Systemgrenzen und die
|
||||||
# Kantenarten. Bei -n braucht auch sie ein pos, sonst landet sie auf 0,0
|
# Kantenarten. Bei -n braucht auch sie ein pos, sonst landet sie auf 0,0
|
||||||
# mitten in der Anlage - daher unterhalb links vom Anlagenrand.
|
# mitten in der Anlage - daher unterhalb links vom Anlagenrand.
|
||||||
|
# Lagerichtig nur die tatsaechlich vorkommenden Typen - die volle
|
||||||
|
# Katalogliste macht die Legende dort laenger als die Anlage breit ist.
|
||||||
|
used_types = {t.type_name for t in analysis.tros}
|
||||||
|
legend_types = [d for d in TRO_CATALOG
|
||||||
|
if not use_coords or d.get_name() in used_types]
|
||||||
rows = "".join(
|
rows = "".join(
|
||||||
f'<tr><td bgcolor="{d.get_fill()}" color="{d.get_style().stroke}" 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>'
|
f'<td align="left">{d.describe_symbol()}</td></tr>'
|
||||||
for d in TRO_CATALOG
|
for d in legend_types
|
||||||
)
|
)
|
||||||
rows += (f'<tr><td bgcolor="{STYLE_EXT.fill}" align="left"> Systemgrenze </td>'
|
rows += (f'<tr><td bgcolor="{STYLE_EXT.fill}" align="left"> Systemgrenze </td>'
|
||||||
f'<td align="left">Ellipse (Materialein-/ausgang, kein TRO)</td></tr>')
|
f'<td align="left">Ellipse (Materialein-/ausgang, kein TRO)</td></tr>')
|
||||||
@@ -1626,8 +1846,8 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
|||||||
'<td align="left">gleiche Bahn / Strecke</td></tr>'
|
'<td align="left">gleiche Bahn / Strecke</td></tr>'
|
||||||
"</table>>")
|
"</table>>")
|
||||||
if use_coords:
|
if use_coords:
|
||||||
lx, ly = _legend_pos(analysis)
|
lx, ly = _legend_pos_rechts(analysis)
|
||||||
add(f' legende [shape=plaintext, style="", fillcolor="none", '
|
add(f' legende [shape=plaintext, style="", fillcolor="none", fontsize=11, '
|
||||||
f'label={legend_label}, pos="{lx * COORD_SCALE:.2f},'
|
f'label={legend_label}, pos="{lx * COORD_SCALE:.2f},'
|
||||||
f'{ly * COORD_SCALE:.2f}"];')
|
f'{ly * COORD_SCALE:.2f}"];')
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user