From e165beeaa863e2306a577f7c229b123558512ea7 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Fri, 21 Aug 2026 12:08:21 +0200 Subject: [PATCH] Group parallel Gefaellestrecken in the material-flow diagram A bank of gravity lanes between the same two Kreisel (same slope) was drawn as one box per lane, making large storage banks unreadable. render_dot now collapses such lanes into a single Gefaellestrecke-styled block with a double border, labelled with the id range and combined Separator/Scanner ranges. Purely visual - the graph, --doc report and --connect output still list every lane individually. --- .../graphs/mubea_material_flow.svg | 653 ++++-------------- .../graphs/mubea_material_flow_report.md | 106 +-- doc/Python_Scripts.md | 10 + lib/material_flow.py | 117 +++- 4 files changed, 298 insertions(+), 588 deletions(-) diff --git a/doc/500573_Mubea/graphs/mubea_material_flow.svg b/doc/500573_Mubea/graphs/mubea_material_flow.svg index 6dcfaa6..042109a 100644 --- a/doc/500573_Mubea/graphs/mubea_material_flow.svg +++ b/doc/500573_Mubea/graphs/mubea_material_flow.svg @@ -4,634 +4,219 @@ - - + + Materialfluss - -Materialfluss - export.csv + +Materialfluss - mubea.csv 0001-L - -Kreisel1  [0001-L] -Bahn L (links) -h = 1.470 m + +Kreisel1  [0001-L] +Bahn L (links) +h = 1.470 m 0002-R - -Kreisel2  [0002-R] -Bahn R (rechts) -h = 1.470 m + +Kreisel2  [0002-R] +Bahn R (rechts) +h = 1.470 m 0001-L->0002-R - - - + + + -Weiche +Weiche 0001-R - -Kreisel1  [0001-R] -Bahn R (rechts) -h = 1.470 m -Separator (1): 0061 + +Kreisel1  [0001-R] +Bahn R (rechts) +h = 1.470 m +Separator (1): 0061 - + 0001-R->0001-L - - - + + + -UZS +UZS 0001-R->0002-R - - - + + + -Weiche +Weiche 0002-L - -Kreisel2  [0002-L] -Bahn L (links) -h = 1.470 m -Separator (3): 0003, 0011, 0012 -Scanner (3): 0004, 0007, 0009 + +Kreisel2  [0002-L] +Bahn L (links) +h = 1.470 m +Separator (3): 0003, 0011, 0012 +Scanner (3): 0004, 0007, 0009 0016 - -VarioFoerderer :1  [0016] -Strecke -h = 0.000 m -0 -> 0 mm (+0) = aufwaerts -Separator (2): 0058, 0059 -Scanner (1): 0060 + +VarioFoerderer :1  [0016] +Strecke +h = 0.000 m +0 -> 0 mm (+0) = aufwaerts +Separator (2): 0058, 0059 +Scanner (1): 0060 - + 0002-L->0016 - - + + 0002-R->0002-L - - + + -UZS +UZS 0013 - -VarioFoerderer :3  [0013] -Strecke -h = 0.237 m -0 -> 474 mm (+474) = aufwaerts -Separator (2): 0015, 0062 + +VarioFoerderer :3  [0013] +Strecke +h = 0.237 m +0 -> 474 mm (+474) = aufwaerts +Separator (2): 0015, 0062 - + 0002-R->0013 - - + + 0014 - -VarioFoerderer :2  [0014] -Strecke -h = 0.237 m -0 -> 474 mm (+474) = aufwaerts -Separator (2): 0057, 0065 -Scanner (1): 0006 + +VarioFoerderer :2  [0014] +Strecke +h = 0.237 m +0 -> 474 mm (+474) = aufwaerts +Separator (2): 0057, 0065 +Scanner (1): 0006 - + 0002-R->0014 - - + + 0010-L - -Kreisel3  [0010-L] -Bahn L (links) -h = 1.940 m + +Kreisel3  [0010-L] +Bahn L (links) +h = 1.940 m - + -0017 - -Gefaellestrecke :20  [0017] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0037 +grp_0017 + + +Gefaellestrecke 0017 -> 0036 (20x) +Gruppe paralleler Schwerkraftlinien +h = 1.941 m +1941 -> 1466 mm (-475) = abwaerts +Separator (21): 0005 - 0056 +Scanner (1): 0066 - - -0010-L->0017 - - - - - -0018 - -Gefaellestrecke :19  [0018] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0038 - - - -0010-L->0018 - - - - - -0019 - -Gefaellestrecke :18  [0019] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0039 - - - -0010-L->0019 - - - - - -0020 - -Gefaellestrecke :17  [0020] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0040 - - - -0010-L->0020 - - - - - -0021 - -Gefaellestrecke :16  [0021] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0041 - - - -0010-L->0021 - - - - - -0022 - -Gefaellestrecke :15  [0022] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0042 - - - -0010-L->0022 - - - - - -0023 - -Gefaellestrecke :14  [0023] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0043 - - - -0010-L->0023 - - - - - -0024 - -Gefaellestrecke :13  [0024] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0044 - - - -0010-L->0024 - - - - - -0025 - -Gefaellestrecke :12  [0025] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0045 - - - -0010-L->0025 - - - - - -0026 - -Gefaellestrecke :11  [0026] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0046 - - - -0010-L->0026 - - - - - -0027 - -Gefaellestrecke :10  [0027] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0047 - - - -0010-L->0027 - - - - - -0028 - -Gefaellestrecke :9  [0028] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0048 - - - -0010-L->0028 - - - - - -0029 - -Gefaellestrecke :8  [0029] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0049 - - - -0010-L->0029 - - - - - -0030 - -Gefaellestrecke :7  [0030] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0050 - - - -0010-L->0030 - - - - - -0031 - -Gefaellestrecke :6  [0031] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0051 - - - -0010-L->0031 - - - - - -0032 - -Gefaellestrecke :5  [0032] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0052 - - - -0010-L->0032 - - - - - -0033 - -Gefaellestrecke :4  [0033] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0053 - - - -0010-L->0033 - - - - - -0034 - -Gefaellestrecke :3  [0034] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0054 - - - -0010-L->0034 - - - - - -0035 - -Gefaellestrecke :2  [0035] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (1): 0055 - - - -0010-L->0035 - - - - - -0036 - -Gefaellestrecke :1  [0036] -Gefaellestrecke -h = 1.941 m -1941 -> 1466 mm (-475) = abwaerts -Separator (2): 0005, 0056 -Scanner (1): 0066 - - + -0010-L->0036 - - +0010-L->grp_0017 + + 0010-R - -Kreisel3  [0010-R] -Bahn R (rechts) -h = 1.940 m -Separator (2): 0063, 0064 + +Kreisel3  [0010-R] +Bahn R (rechts) +h = 1.940 m +Separator (2): 0063, 0064 - + 0010-R->0010-L - - - + + + -UZS +UZS - + 0013->0010-L - - + + - + 0014->0010-L - - + + - -0016->0002-R - - - - - -0017->0002-R - - - - - -0018->0002-R - - - - - -0019->0002-R - - - - - -0020->0002-R - - - - - -0021->0002-R - - - - - -0022->0002-R - - - - - -0023->0002-R - - - - - -0024->0002-R - - - - - -0025->0002-R - - - - - -0026->0002-R - - - - - -0027->0002-R - - - - - -0028->0002-R - - - - - -0029->0002-R - - - - - -0030->0002-R - - - - - -0031->0002-R - - - - - -0032->0002-R - - - - - -0033->0002-R - - - - - -0034->0002-R - - - - -0035->0002-R - - +0016->0002-R + + - + -0036->0002-R - - +grp_0017->0002-R + + - + legende -durchgezogen -Materialfluss -gestrichelt blau -Kreisel-Umlauf, Richtung aus Drehrichtung (UZS = rechts nach links, GUZ = links nach rechts) -gestrichelt gelb -Richtung nicht bestimmbar +durchgezogen +Materialfluss +gestrichelt blau +Kreisel-Umlauf, Richtung aus Drehrichtung (UZS = rechts nach links, GUZ = links nach rechts) +gestrichelt gelb +Richtung nicht bestimmbar +doppelte Umrandung +Gruppe paralleler Gefaellestrecken (gleicher Zu-/Ablauf, gleiche Steigung) diff --git a/doc/500573_Mubea/graphs/mubea_material_flow_report.md b/doc/500573_Mubea/graphs/mubea_material_flow_report.md index e062c5a..a32a313 100644 --- a/doc/500573_Mubea/graphs/mubea_material_flow_report.md +++ b/doc/500573_Mubea/graphs/mubea_material_flow_report.md @@ -1,8 +1,8 @@ -# Materialfluss - export.csv +# Materialfluss - mubea.csv -**Erzeugt:** 2026-08-07 13:20 von `lib/material_flow.py` -**Quelle:** `C:\develop\sps_skel\data\export.csv` +**Erzeugt:** 2026-08-21 11:53 von `lib/material_flow.py` +**Quelle:** `C:\10-Develop\gitrepos\sps_skel\data\mubea.csv` > Materialfluss der mechanischen Objekte. Die daraus abgeleiteten Steuerungsobjekte (TROs) dokumentiert `lib/tro_flow.py --doc`. @@ -15,7 +15,7 @@ | Anbauteile | Scanner: 6, Separator: 33 | | Plausibilitaetshinweise | 7 | -Graph: [`export_material_flow.dot`](export_material_flow.dot) - [`export_material_flow.svg`](export_material_flow.svg) +Graph: [`mubea_material_flow.dot`](mubea_material_flow.dot) - [`mubea_material_flow.svg`](mubea_material_flow.svg) Ein **Kreisel** ist ein Umlauf und wird als zwei Knoten modelliert - eine Bahn je Fahrtrichtung (`-L` links, `-R` rechts), die zu einem Kreis verbunden sind. @@ -67,55 +67,55 @@ Hoehe: bei Kreiseln aus dem Merkmal `Höhe in m`, bei Gefaellestrecke und Streck |---|---|---| | `0001-L` | `0002-R` | Richtung unbestimmt (Weiche) | | `0001-R` | `0002-R` | Richtung unbestimmt (Weiche) | -| `0010-R` | `0010-L` | Kreisel-Umlauf (UZS) | -| `0002-R` | `0002-L` | Kreisel-Umlauf (UZS) | | `0001-R` | `0001-L` | Kreisel-Umlauf (UZS) | -| `0010-L` | `0036` | Materialfluss | -| `0036` | `0002-R` | Materialfluss | -| `0010-L` | `0035` | Materialfluss | -| `0035` | `0002-R` | Materialfluss | -| `0010-L` | `0034` | Materialfluss | -| `0034` | `0002-R` | Materialfluss | -| `0010-L` | `0033` | Materialfluss | -| `0033` | `0002-R` | Materialfluss | -| `0010-L` | `0032` | Materialfluss | -| `0032` | `0002-R` | Materialfluss | -| `0010-L` | `0031` | Materialfluss | -| `0031` | `0002-R` | Materialfluss | -| `0010-L` | `0030` | Materialfluss | -| `0030` | `0002-R` | Materialfluss | -| `0010-L` | `0029` | Materialfluss | -| `0029` | `0002-R` | Materialfluss | -| `0010-L` | `0028` | Materialfluss | -| `0028` | `0002-R` | Materialfluss | -| `0010-L` | `0027` | Materialfluss | -| `0027` | `0002-R` | Materialfluss | -| `0010-L` | `0026` | Materialfluss | -| `0026` | `0002-R` | Materialfluss | -| `0010-L` | `0025` | Materialfluss | -| `0025` | `0002-R` | Materialfluss | -| `0010-L` | `0024` | Materialfluss | -| `0024` | `0002-R` | Materialfluss | -| `0010-L` | `0023` | Materialfluss | -| `0023` | `0002-R` | Materialfluss | -| `0010-L` | `0022` | Materialfluss | -| `0022` | `0002-R` | Materialfluss | -| `0010-L` | `0021` | Materialfluss | -| `0021` | `0002-R` | Materialfluss | -| `0010-L` | `0020` | Materialfluss | -| `0020` | `0002-R` | Materialfluss | -| `0010-L` | `0019` | Materialfluss | -| `0019` | `0002-R` | Materialfluss | -| `0010-L` | `0018` | Materialfluss | -| `0018` | `0002-R` | Materialfluss | -| `0010-L` | `0017` | Materialfluss | -| `0017` | `0002-R` | Materialfluss | -| `0002-L` | `0016` | Materialfluss | -| `0016` | `0002-R` | Materialfluss | -| `0002-R` | `0014` | Materialfluss | -| `0014` | `0010-L` | Materialfluss | -| `0002-R` | `0013` | Materialfluss | -| `0013` | `0010-L` | Materialfluss | +| `0002-R` | `0002-L` | Kreisel-Umlauf (UZS) | +| `0010-R` | `0010-L` | Kreisel-Umlauf (UZS) | +| `0010-L` | `0036` | Materialfluss (Ausschleusung) | +| `0036` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0035` | Materialfluss (Ausschleusung) | +| `0035` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0034` | Materialfluss (Ausschleusung) | +| `0034` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0033` | Materialfluss (Ausschleusung) | +| `0033` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0032` | Materialfluss (Ausschleusung) | +| `0032` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0031` | Materialfluss (Ausschleusung) | +| `0031` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0030` | Materialfluss (Ausschleusung) | +| `0030` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0029` | Materialfluss (Ausschleusung) | +| `0029` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0028` | Materialfluss (Ausschleusung) | +| `0028` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0027` | Materialfluss (Ausschleusung) | +| `0027` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0026` | Materialfluss (Ausschleusung) | +| `0026` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0025` | Materialfluss (Ausschleusung) | +| `0025` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0024` | Materialfluss (Ausschleusung) | +| `0024` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0023` | Materialfluss (Ausschleusung) | +| `0023` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0022` | Materialfluss (Ausschleusung) | +| `0022` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0021` | Materialfluss (Ausschleusung) | +| `0021` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0020` | Materialfluss (Ausschleusung) | +| `0020` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0019` | Materialfluss (Ausschleusung) | +| `0019` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0018` | Materialfluss (Ausschleusung) | +| `0018` | `0002-R` | Materialfluss (Einschleusung) | +| `0010-L` | `0017` | Materialfluss (Ausschleusung) | +| `0017` | `0002-R` | Materialfluss (Einschleusung) | +| `0002-L` | `0016` | Materialfluss (Ausschleusung) | +| `0016` | `0002-R` | Materialfluss (Einschleusung) | +| `0002-R` | `0014` | Materialfluss (Ausschleusung) | +| `0014` | `0010-L` | Materialfluss (Einschleusung) | +| `0002-R` | `0013` | Materialfluss (Ausschleusung) | +| `0013` | `0010-L` | Materialfluss (Einschleusung) | Die Richtung innerhalb eines Kreisels kommt aus dem Merkmal `Drehrichtung`: **UZS** (im Uhrzeigersinn) laesst das Material rechts nach links laufen, **GUZ** (gegen den Uhrzeigersinn) links nach rechts. @@ -133,7 +133,7 @@ Abweichungen zwischen den Merkmalen des CSV-Exports und den tatsaechlich vorhand ## 5. Warnungen des Laufs -- Zeile 52 (Strecke 0016 'VarioFoerderer :1'): alle Nachbarn auf gleicher Hoehe (1.470 m) - Richtung nicht bestimmbar +- Zeile 17 (Strecke 0016 'VarioFoerderer :1'): alle Nachbarn auf gleicher Hoehe (1.470 m) - Richtung nicht bestimmbar - Verbindung 0001-L <-> 0002-R: Richtung nicht bestimmbar (kein Transportobjekt beteiligt) - beidseitig gezeichnet - Verbindung 0001-R <-> 0002-R: Richtung nicht bestimmbar (kein Transportobjekt beteiligt) - beidseitig gezeichnet - Verbindung 0002-L <-> 0016: Richtung nicht bestimmbar (kein Transportobjekt beteiligt) - beidseitig gezeichnet diff --git a/doc/Python_Scripts.md b/doc/Python_Scripts.md index dd900f3..09bd553 100644 --- a/doc/Python_Scripts.md +++ b/doc/Python_Scripts.md @@ -132,6 +132,16 @@ Reads the CSV export and builds a directed graph of the mechanical objects: > will contain the 61 conveyor items but **no** BTMT rows, so no entry/exit nodes > appear. The recognition above is ready for when BTMT rows are present. +In the rendered graph, **parallel Gefällestrecken between the same two Kreisel +lanes with the same slope** (`Hoehe_Von_mm`/`Hoehe_Bis_mm`) — a typical +line-storage bank — are drawn as a **single** Gefällestrecke-styled box with a +double border (`peripheries=2`) instead of one box per line. Its label states +the id range and count of the merged lines plus the combined Separator/Scanner +id ranges, e.g. `Gefaellestrecke 0017 - 0036 (20x)` / `Separator (20): 0037 - +0056`. This is purely a rendering simplification (`_group_parallel_gefaelle` +in `lib/material_flow.py`) — the underlying graph, `--doc` report and +`--connect` output are unaffected and still list every line individually. + Flow direction between conveyor segments is derived from height change (`Hoehe_Von_mm`/`Hoehe_Bis_mm`) or, failing that, from `Antriebfahrtrichtung` ("Auf"/"Ab"); connections whose direction can't be determined are drawn diff --git a/lib/material_flow.py b/lib/material_flow.py index e8a7800..dc9d2a0 100644 --- a/lib/material_flow.py +++ b/lib/material_flow.py @@ -138,6 +138,12 @@ NODE_STYLES = { } DEFAULT_NODE_STYLE = dict(fillcolor="#f2f2f2", fontcolor="#404040", color="#808080") +# Ab dieser Anzahl paralleler Gefaellestrecken zwischen denselben zwei +# Kreisel-Bahnen (gleicher Zulauf, gleicher Ablauf, gleiche Steigung) werden +# sie im Materialfluss-Diagramm zu einem Block zusammengefasst - sonst wird +# eine grosse Linienbank (Speicher) unlesbar breit. +MIN_GEFAELLE_GROUP = 2 + # --------------------------------------------------------------------------- # Datenmodell @@ -999,6 +1005,78 @@ def _pos_attr(node: "Node", use_coords: bool) -> str: return f', pos="{xy[0] * COORD_SCALE:.2f},{xy[1] * COORD_SCALE:.2f}"' +def _flow_neighbours(node_id: str, edges: list[Edge]) -> tuple[str, str] | None: + """ + (Vorgaenger, Nachfolger) eines Knotens im Materialfluss. + + None, wenn der Knoten nicht genau einen ankommenden und einen abgehenden + EDGE_FLOW hat - dann ist er kein einfacher Durchlaufknoten zwischen zwei + festen Nachbarn. + """ + incoming = {e.src for e in edges if e.dst == node_id and e.kind == EDGE_FLOW} + outgoing = {e.dst for e in edges if e.src == node_id and e.kind == EDGE_FLOW} + if len(incoming) != 1 or len(outgoing) != 1: + return None + return next(iter(incoming)), next(iter(outgoing)) + + +def _group_parallel_gefaelle(graph: Graph) -> list[list[Node]]: + """ + Parallele Gefaellestrecken zwischen denselben zwei Kreisel-Bahnen zu + Gruppen zusammenfassen (z. B. eine Bank aus Schwerkraftlinien). + + Kriterium: gleicher Zulauf- und Ablauf-Knoten (jeweils eine Kreisel-Bahn) + und gleiche Steigung (Hoehe_Von/Hoehe_Bis) - "gleich lang und gleiche + Steigung" laesst sich aus dem Export nur ueber die Hoehenangabe pruefen, + die Laenge ergibt sich daraus, dass beide Enden an denselben zwei Knoten + haengen. Rein fuer die Darstellung; Graph-Knoten/-Kanten bleiben unveraendert. + """ + groups: dict[tuple, list[Node]] = {} + for node_id in sorted(graph.nodes): + node = graph.nodes[node_id] + if node.kind != "Gefaellestrecke": + continue + neighbours = _flow_neighbours(node_id, graph.edges) + if neighbours is None: + continue + before, after = neighbours + if graph.nodes[before].kind != "Kreisel" or graph.nodes[after].kind != "Kreisel": + continue + element = node.element + key = (before, after, element.height_from, element.height_to) + groups.setdefault(key, []).append(node) + return [members for members in groups.values() if len(members) >= MIN_GEFAELLE_GROUP] + + +def _id_range(ids: list[str]) -> str: + """Sortierte Ids als Bereich, z. B. ['0037', '0056'] -> '0037 - 0056'.""" + ids = sorted(ids) + return ids[0] if len(ids) == 1 else f"{ids[0]} - {ids[-1]}" + + +def _gefaelle_group_label(members: list[Node]) -> str: + """Sammel-Label einer Gefaellestrecken-Gruppe: Bereich der Linien-Ids, + gemeinsame Steigung, zusammengefasste Separatoren/Scanner.""" + element = members[0].element + ids = sorted(m.node_id for m in members) + lines = [ + f"Gefaellestrecke {ids[0]} -> {ids[-1]} ({len(ids)}x)", + "Gruppe paralleler Schwerkraftlinien", + ] + if element.height is not None: + lines.append(f"h = {element.height:.3f} m") + if element.rise is not None: + uphill = drive_uphill(element)[0] + arrow = {True: "aufwaerts", False: "abwaerts", None: "Richtung offen"}[uphill] + lines.append(f"{element.height_from:.0f} -> {element.height_to:.0f} mm " + f"({element.rise:+.0f}) = {arrow}") + for kind in DEVICE_KINDS: + ids_dev = sorted({d for m in members for d in m.devices.get(kind, [])}) + if ids_dev: + lines.append(f"{kind} ({len(ids_dev)}): {_id_range(ids_dev)}") + return _dot_label(lines) + + def render_dot(graph: Graph, use_coords: bool = False) -> str: out: list[str] = [] add = out.append @@ -1047,14 +1125,48 @@ def render_dot(graph: Graph, use_coords: bool = False) -> str: add(" }") add("") + # Parallele Gefaellestrecken (gleicher Zu-/Ablauf, gleiche Steigung) zu + # je einem Block zusammenfassen, damit eine Linienbank nicht als N + # einzelne Kaesten gezeichnet wird. + gefaelle_groups = _group_parallel_gefaelle(graph) + group_of: dict[str, str] = {} + for members in gefaelle_groups: + group_id = f"grp_{members[0].node_id}" + for member in members: + group_of[member.node_id] = group_id + grouped_ids = set(group_of) + for node in plain: + if node.node_id in grouped_ids: + continue style = NODE_STYLES.get(node.kind, DEFAULT_NODE_STYLE) attrs = ", ".join(f'{key}="{value}"' for key, value in style.items()) add(f' "{node.node_id}" [label="{_node_label(node)}", {attrs}' f'{_pos_attr(node, use_coords)}];') + + for members in gefaelle_groups: + group_id = f"grp_{members[0].node_id}" + style = NODE_STYLES.get("Gefaellestrecke", DEFAULT_NODE_STYLE) + attrs = ", ".join(f'{key}="{value}"' for key, value in style.items()) + pos = "" + if use_coords: + xs = [xy[0] for m in members if (xy := _node_xy(m))] + ys = [xy[1] for m in members if (xy := _node_xy(m))] + if xs and ys: + pos = (f', pos="{sum(xs) / len(xs) * COORD_SCALE:.2f},' + f'{sum(ys) / len(ys) * COORD_SCALE:.2f}"') + add(f' "{group_id}" [label="{_gefaelle_group_label(members)}", {attrs}, ' + f'peripheries=2{pos}];') add("") + seen_group_edges: set[tuple[str, str]] = set() for edge in graph.edges: + src = group_of.get(edge.src, edge.src) + dst = group_of.get(edge.dst, edge.dst) + if edge.src in grouped_ids or edge.dst in grouped_ids: + if src == dst or (src, dst) in seen_group_edges: + continue + seen_group_edges.add((src, dst)) if edge.kind == EDGE_CIRCLE: # Richtung kommt aus der Drehrichtung und steht als Label an der Kante label = f', xlabel="{_dot_escape(edge.note)}"' if edge.note else "" @@ -1070,7 +1182,7 @@ def render_dot(graph: Graph, use_coords: bool = False) -> str: 'fontcolor="#cc8800", tooltip="Weiche (Kreisel-Uebergang)"]') else: attrs = "[]" - add(f' "{edge.src}" -> "{edge.dst}" {attrs};') + add(f' "{src}" -> "{dst}" {attrs};') add("") legend_pos = "" @@ -1094,6 +1206,9 @@ def render_dot(graph: Graph, use_coords: bool = False) -> str: f'{ROTATION_CCW} = {ROTATION_FLOW[ROTATION_CCW]})' '
gestrichelt gelb' 'Richtung nicht bestimmbar' + 'doppelte Umrandung' + 'Gruppe paralleler Gefaellestrecken (gleicher Zu-/Ablauf, ' + "gleiche Steigung)" ">];") add(" }") add("}")