latest changes

This commit is contained in:
2026-08-20 11:38:51 +02:00
parent 8bd0739653
commit d7edc55116
23 changed files with 4769 additions and 44 deletions
+33
View File
@@ -0,0 +1,33 @@
@echo off
REM ================================================================
REM SPS_SKEL - FB_Main-Geruest als SCL erzeugen
REM ================================================================
REM Aufruf:
REM bin\tro_flow.bat --file mubea.csv
REM bin\tro_flow.bat --file mubea.csv --tosvg
REM bin\tro_flow.bat --file mubea.csv --tosvg --doc
REM
REM Die JSON-Datei wird in %SKEL_RESULTS% gesucht, die SCL-Datei wird
REM ebenfalls dort abgelegt.
REM
REM Schwesterprogramm: bin\material_flow.bat (Materialfluss der Objekte)
REM ================================================================
setlocal
REM Umgebung setzen (SKEL_DATA, SKEL_RESULTS, SKEL_LIB, PYTHONPATH ...)
call "%~dp0setenv.bat" >nul
REM Virtuelle Umgebung nutzen, falls vorhanden (pydantic wird benoetigt)
if exist "%SPS_SKEL%\.venv\Scripts\activate.bat" (
call "%SPS_SKEL%\.venv\Scripts\activate.bat"
)
REM Python-Interpreter bestimmen (py-Launcher bevorzugt, sonst python)
set "PY=py"
py --version >nul 2>&1 || set "PY=python"
"%PY%" "%SKEL_LIB%\scl_skeleton.py" %*
set "RC=%ERRORLEVEL%"
endlocal & exit /b %RC%
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# ================================================================
# SPS_SKEL - FB_Main-Geruest als SCL erzeugen
# ================================================================
# Aufruf:
# bash bin/scl_skeleton.sh
# bash bin/scl_skeleton.sh --json export_tro.json
# bash bin/scl_skeleton.sh --json export_tro.json --out FB_Main.scl
#
# Die JSON-Datei wird in $SKEL_RESULTS gesucht, die SCL-Datei wird
# ebenfalls dort abgelegt.
#
# Vorstufe: bin/tro_extract.sh (Zeichnung -> JSON)
# ================================================================
set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Umgebung setzen (SKEL_DATA, SKEL_RESULTS, SKEL_LIB, PYTHONPATH ...)
# shellcheck source=/dev/null
. "$SCRIPT_DIR/setenv.sh" >/dev/null
# Virtuelle Umgebung nutzen, falls vorhanden (pydantic wird benoetigt)
if [ -f "$SPS_SKEL/.venv/bin/activate" ]; then
# shellcheck source=/dev/null
. "$SPS_SKEL/.venv/bin/activate"
fi
# Python-Interpreter bestimmen
PY="python3"
command -v python3 >/dev/null 2>&1 || PY="python"
exec "$PY" "$SKEL_LIB/scl_skeleton.py" "$@"
+33
View File
@@ -0,0 +1,33 @@
@echo off
REM ================================================================
REM SPS_SKEL - TROs aus der annotierten Zeichnung nach JSON lesen
REM ================================================================
REM Aufruf:
REM bin\tro_flow.bat --file mubea.csv
REM bin\tro_flow.bat --file mubea.csv --tosvg
REM bin\tro_flow.bat --file mubea.csv --tosvg --doc
REM
REM Die Zeichnung wird in %SKEL_RESULTS% gesucht, die JSON-Datei wird
REM ebenfalls dort abgelegt.
REM
REM Schwesterprogramm: bin\material_flow.bat (Materialfluss der Objekte)
REM ================================================================
setlocal
REM Umgebung setzen (SKEL_DATA, SKEL_RESULTS, SKEL_LIB, PYTHONPATH ...)
call "%~dp0setenv.bat" >nul
REM Virtuelle Umgebung nutzen, falls vorhanden (pydantic wird benoetigt)
if exist "%SPS_SKEL%\.venv\Scripts\activate.bat" (
call "%SPS_SKEL%\.venv\Scripts\activate.bat"
)
REM Python-Interpreter bestimmen (py-Launcher bevorzugt, sonst python)
set "PY=py"
py --version >nul 2>&1 || set "PY=python"
"%PY%" "%SKEL_LIB%\tro_extract.py" %*
set "RC=%ERRORLEVEL%"
endlocal & exit /b %RC%
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# ================================================================
# SPS_SKEL - TROs aus der annotierten Zeichnung nach JSON lesen
# ================================================================
# Aufruf:
# bash bin/tro_extract.sh
# bash bin/tro_extract.sh --dxf export_annotated.dxf
# bash bin/tro_extract.sh --dxf export_annotated.dxf --out tros.json
#
# Die Zeichnung wird in $SKEL_RESULTS gesucht, die JSON-Datei wird
# ebenfalls dort abgelegt.
#
# Gegenrichtung zu: bin/tro_annotate.sh (JSON <- Zeichnung)
# ================================================================
set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Umgebung setzen (SKEL_DATA, SKEL_RESULTS, SKEL_LIB, PYTHONPATH ...)
# shellcheck source=/dev/null
. "$SCRIPT_DIR/setenv.sh" >/dev/null
# Virtuelle Umgebung nutzen, falls vorhanden (pydantic wird benoetigt)
if [ -f "$SPS_SKEL/.venv/bin/activate" ]; then
# shellcheck source=/dev/null
. "$SPS_SKEL/.venv/bin/activate"
fi
# Python-Interpreter bestimmen
PY="python3"
command -v python3 >/dev/null 2>&1 || PY="python"
exec "$PY" "$SKEL_LIB/tro_extract.py" "$@"
+1 -1
View File
@@ -1,4 +1,4 @@
;; Automatisch erzeugt von lib/tro_annotate.py --emit-lisp ;; Automatisch erzeugt von lib/tro_annotate.py --emit-lisp
;; Quelle: TRO_CATALOG in lib/tro_catalog.py - nicht manuell aendern. ;; Quelle: TRO_CATALOG in lib/tro_catalog.py - nicht manuell aendern.
(setq *TRO-TYPES* (list "1Sep" "1Sep1Swi" "1Sep2Swi" "1Sep_SSCC" "Vario" "PinStore_Auto" "Vario_workStation" "EmptyCarrBuffer" "LoadingBoom" "2Sep1Swi")) (setq *TRO-TYPES* (list "1Sep" "1Sep1Swi" "1Sep2Swi" "1Sep_SSCC" "Vario" "PinStore_Auto" "Vario_workStation" "EmptyCarrBuffer" "LoadingBoom" "2Sep1Swi" "2Sep2Swi"))
(princ) (princ)
+14
View File
@@ -12,5 +12,19 @@
"csv_file": "mubea.csv", "csv_file": "mubea.csv",
"dxf_file": "500573_60_1.dxf", "dxf_file": "500573_60_1.dxf",
"created": "2026-07-30 15:32" "created": "2026-07-30 15:32"
},
"export.csv|Mubea.dxf": {
"dx": 59.950000000000045,
"dy": 0.0,
"rotation": 0.0,
"source": "auto",
"anchor": "21 exakte Treffer (<= 50 mm) ueber die Rollen Scanner:8, Separator:35; 21 Stimmen im 100-mm-Raster, 6 Kandidaten geprueft",
"matched": 43,
"candidates": 43,
"residual_max": 419.95000000000005,
"residual_mean": 90.1746667187897,
"csv_file": "export.csv",
"dxf_file": "Mubea.dxf",
"created": "2026-08-12 10:09"
} }
} }
+180
View File
@@ -0,0 +1,180 @@
; ============================================================
; tro_overrides.ini - manuelle Korrekturen an der TRO-Ableitung
; Gelesen von lib/tro_overrides.py, angewandt in lib/tro_flow.py und
; lib/tro_annotate.py. Von Hand gepflegt - wird NICHT generiert.
;
; Warum es diese Datei gibt: tro_flow.py leitet die TROs allein aus dem
; mechanischen Layout (CSV-Export) ab. Weichen, die im Export fehlen, oder
; ein SSCC-Scanner sind dort nicht erkennbar. Solche Erkenntnisse aus der
; Begehung stehen hier, damit sie den naechsten Lauf ueberleben.
;
; [<csv>.tro.<TRO-ID>] Typkorrektur an einem abgeleiteten TRO
; type = <Katalogtyp aus lib/tro_catalog.py>
; reason = <Begruendung, erscheint in der Doku>
; confidence = <Vertrauensangabe, Default 'bestaetigt'>
;
; [<csv>.openpoint.<Name>] offener Punkt - ausdruecklich noch KEIN TRO
; x / y / z = Anlagenkoordinate in mm (CSV-Koordinatensystem)
; expect = hier erwarteter TRO-Typ
; belongs_to = TRO, der das Bauteil heute enthaelt
; note = Erlaeuterung
;
; Offene Punkte aendern die TRO-Struktur nicht. Sie werden nur in der Doku
; und in der annotierten Zeichnung markiert - erst nach Pruefung in BricsCAD
; werden daraus echte TROs.
;
; ACHTUNG: die TRO-IDs sind laufende Nummern eines Laufs. Sie gelten nur zur
; angegebenen CSV-Datei. Wechselt die Datenbasis (z.B. auf export.csv),
; verschieben sich die Nummern und die Abschnitte muessen nachgezogen werden.
; ============================================================
; ------------------------------------------------------------
; Mubea 500573 - Review vom 2026-08-10 (Begehung/Layoutpruefung)
; Basis: data/mubea.csv
; ------------------------------------------------------------
[mubea.csv.tro.TRO03]
type = 1Sep1Swi
reason = Weiche am Separator 0010 vor Ort bestaetigt (Review 2026-08-10) - an dieser Stelle wird die Fahrtrichtung umgestellt; im CSV-Export fehlt das Weichenelement
confidence = bestaetigt
[mubea.csv.tro.TRO04]
type = 1Sep_SSCC
reason = Separator 0011 mit Scanner 0006 - im Review als Scanner-Stelle bestaetigt (Review 2026-08-10)
confidence = bestaetigt
[mubea.csv.tro.TRO05]
type = 1Sep1Swi
reason = Weiche am Separator 0059 vor Ort bestaetigt (Review 2026-08-10) - im CSV-Export fehlt das Weichenelement
confidence = bestaetigt
[mubea.csv.openpoint.Qualitaetskontrolle]
x = -1026.00
y = 5250.05
z = 1478.50
expect = 1Sep1Swi
belongs_to = TRO09
note = Im Review als eigene Uebergabestelle (1 Separator + 1 Weiche) gemeldet. In mubea.csv gibt es an dieser Stelle ueberhaupt kein Bauteil - Separator 0059 und Scanner 0060 tauchen erst in export.csv auf. Koordinate daher aus export.csv uebernommen. Vor der Umsetzung in BricsCAD pruefen.
[mubea.csv.openpoint.Foerderer_unten_links]
x = 529.95
y = -3895.27
z = 1468.50
expect = 1Sep1Swi
belongs_to = TRO10
note = Im Review als eigene Uebergabestelle (1 Separator + 1 Weiche) gemeldet, Position aus der Zeichnung (DXF -50375.7283 / -12309.7617, ueber cfg/dxf_registration.json auf Anlagenkoordinaten umgerechnet). Naechstes Bauteil ist Separator 0004 'Separator :30' bei 400 / -3755 auf Gefaellestrecke 0035, das derzeit zum Linienspeicher TRO10 gehoert. Vor der Umsetzung in BricsCAD pruefen.
; ------------------------------------------------------------
; export.csv - Stand Review 2026-08-10 (Begehung + Ablaufbeschreibung)
;
; Adressiert wird ueber SEP<Separatornummer>, nicht ueber die TRO-ID: die
; TRO-IDs sind laufende Nummern eines Laufs und verschieben sich, sobald - wie
; hier - TROs dazukommen. Die Separatornummer aus dem CSV-Export bleibt gleich.
; ------------------------------------------------------------
[export.csv.tro.SEP0011]
type = 1Sep1Swi
reason = Weiche am Separator 0011 vor Ort bestaetigt (Review 2026-08-10) - hier wird die Fahrtrichtung umgestellt; im CSV-Export fehlt das Weichenelement
confidence = bestaetigt
; Separator bei 10310 / -440 auf Kreisel3 Bahn R (Scanner 0008).
; Export vom 2026-08-12: aus 0063 wurde 0064 - die neuen Bauteile haben die
; Nummerierung verschoben, die Stelle selbst ist dieselbe.
[export.csv.tro.SEP0064]
type = 1Sep_SSCC
reason = Review 2026-08-12: Scanner-Stelle, keine Weiche - Einstufung gegenueber dem ersten Review (1Sep1Swi) korrigiert. Separator mit Scanner 0008.
confidence = bestaetigt
; Separator 0003 (-400 / 13920) mit Scanner 0004 auf Kreisel2 Bahn L.
; Die Ableitung stuft das als 1Sep ein (ein Scanner aendert den Typ nicht) und
; gibt nur einen Hinweis - Review 2026-08-12 bestaetigt hier die Scanner-Stelle.
[export.csv.tro.SEP0003]
type = 1Sep_SSCC
reason = Review 2026-08-12: Scanner-Stelle bestaetigt (Separator 0003 mit Scanner 0004)
confidence = bestaetigt
; Separator 0012 + Scanner 0007 bleiben als Scanner-Stelle eingestuft
; (Review 2026-08-10: "keep 1sep1scanner"). 1Sep_SSCC ist der einzige
; Katalogtyp, der Separator und Scanner zusammen fuehrt.
[export.csv.tro.SEP0012]
type = 1Sep_SSCC
reason = Separator 0012 mit Scanner 0007 - im Review als Scanner-Stelle bestaetigt (Review 2026-08-10)
confidence = bestaetigt
; --- manuell ergaenzte Uebergabestellen -----------------------------------
; Beide Separatoren sind im Export vorhanden, ihre "Zuordnung" zeigt aber auf
; ein Transportobjekt statt auf die Kreisel-Bahn, an der sie schalten. Die
; Ableitung hat sie deshalb dem Foerderer bzw. dem Linienspeicher zugeschlagen.
[export.csv.split.Kreisel2_R_vor_Foerderer]
type = 1Sep1Swi
separators = 0005
scanners = 0006
host = 0002-R
label = Separator 0005 auf Kreisel2 Bahn R (vor dem Foerderer)
reason = Review 2026-08-10: liegt auf Kreisel2 Bahn R zwischen Linienspeicher und Foerderer. Vor Ort 1 Separator + 1 Scanner, keine mechanische Weiche - wird auf Wunsch als Schaltstelle (1Sep1Swi) gefuehrt, weil hier die Wegeentscheidung faellt. Export vom 2026-08-12: Position 400/-3978 (223 mm verschoben), Zuordnung jetzt Strecke 0014 statt Gefaellestrecke 0036 - der Separator wuerde also dem Vario-TRO zugeschlagen statt dem Linienspeicher.
confidence = bestaetigt
; --- Zusammenfassung ------------------------------------------------------
; Die beiden Stellen beiderseits des Beruehrpunkts Kreisel2/Kreisel1 sind
; mechanisch getrennt (1,54 m Abstand, zwei verschiedene Kreisel), steuerungs-
; technisch aber eine Uebergabestelle. Laeuft NACH den beiden Splits oben -
; erst dadurch existieren die zwei TROs, die hier zusammengefasst werden.
;
; ACHTUNG: Typ 2Sep2Swi wurde am 2026-08-12 neu in lib/tro_catalog.py
; aufgenommen. Der Baustein FB_ILS_MTRO_2Sep2Swi existiert in der ILSLib noch
; nicht und muss dort angelegt werden.
[export.csv.merge.Beruehrpunkt_K2_K1_gesamt]
type = 2Sep2Swi
separators = 0005, 0057
host = 0002-R
label = Uebergabestelle Kreisel2/Kreisel1 (beide Sperren)
reason = Review 2026-08-12: die Sperre auf Kreisel2 Bahn R und die am Beruehrpunkt bilden zusammen eine Uebergabestelle - als ein Steuerobjekt mit 2 Separatoren, 2 Scannern und 2 Weichen gefuehrt
confidence = bestaetigt
[export.csv.split.Beruehrpunkt_K2_K1]
type = 1Sep1Swi
separators = 0057
; Scanner 0062 (1415 / -5460) ist im Export vom 2026-08-12 neu dazugekommen und
; liest an Separator 0057 - er muss mit herausgeloest werden, sonst bliebe er
; beim Vario-TRO haengen.
scanners = 0062
host = 0001-L
label = Separator 0057 am Beruehrpunkt Kreisel2/Kreisel1
reason = Review 2026-08-10: Uebergabestelle am Beruehrpunkt der beiden Kreisel (ILS Weiche 0067 bei 400/-5478, Kreisel A 0002 / Kreisel B 0001). Hier faellt die Entscheidung Kreisel1 weiter oder auf den unteren Foerderer. Zuordnung im Export zeigt auf Strecke 0014, daher bisher Teil des Vario-TRO.
confidence = bestaetigt
; ------------------------------------------------------------
; Gesetzte TRO-Topologie - Aufnahme vom 2026-08-14
;
; Diese Verbindungen ERSETZEN die abgeleiteten vollstaendig. Grund: die
; Ableitung kontrahiert ueber Kreisel-Bahnen, die als EIN Knoten modelliert
; sind, und verbindet dadurch jeden ankommenden TRO mit ALLEN TROs der Bahn
; statt nur mit dem ersten. Ausserdem ist die Fahrtrichtung der Foerderer
; zwischen zwei Kreiseln aus dem Layout nicht bestimmbar.
;
; Adressiert ueber TRO-IDs. Die sind laufende Nummern eines Laufs - kommt ein
; TRO dazu oder faellt weg, muss dieser Abschnitt nachgezogen werden. Zur
; Wiedererkennung der Stellen der Stand vom 2026-08-14:
; TRO01 Sep 0061 TRO02 Sep 0003 TRO03 Sep 0011 TRO04 Sep 0012
; TRO05 Sep 0005+0057 (2Sep2Swi) TRO06 Sep 0064 TRO07 Sep 0065
; TRO08 Sep 0066 TRO09 Vario 0013 TRO10 Vario 0014 TRO11 Vario 0016
; TRO12 PinStore (20 Linien)
; ------------------------------------------------------------
[export.csv.connections]
c001 = TRO09 -> TRO07 | transfer
c002 = TRO07 -> TRO06 | bahn
c003 = TRO06 -> TRO08 | bahn
c004 = TRO08 -> TRO12 | transfer
c005 = TRO12 -> TRO05 | transfer
c006 = TRO05 -> TRO01 | weiche
c007 = TRO05 -> TRO04 | umlauf
c008 = TRO05 -> TRO10 | transfer
c009 = TRO01 -> TRO05 | weiche
c010 = TRO10 -> TRO12 | transfer
c011 = TRO04 -> TRO03 | bahn
c012 = TRO03 -> TRO11 | transfer
c013 = TRO03 -> TRO02 | bahn
c014 = TRO11 -> TRO02 | transfer
c015 = TRO02 -> TRO09 | transfer
@@ -0,0 +1,193 @@
# Mubea — Material Flow & TROs (from the dxfmakros layout)
**As of:** 2026-08-07 · **Source:** `data/export.csv` (113 rows, fresh `EXPORTCSV`) · **Plant:** Mubea 500573 "Sitzbezügelager"
> **Update (2026-08-07 — material flow rebuilt from the richer `export.csv`).**
> The new export adds the **transfer/switch elements** — 23 Einschleuselement, 23
> Ausschleuselement, 1 Weiche — and fully populated `Nachbarn`. `material_flow.py` now
> **uses the Ein-/Ausschleuselement to direct every Kreisel↔Strecke edge**
> (`Ausschleusung` = Kreisel→Strecke at the strecke's *Anfang*, `Einschleusung` =
> Strecke→Kreisel at its *Ende*) instead of guessing from heights, and **marks the Weiche**
> on the Kreisel↔Kreisel link. Result: directions are now deterministic (no more "unknown
> type" warnings, ambiguities resolved). Two return-lane ends remain unreachable **on
> purpose** — they are the open ends the **BTMT entry/exit (Beladung/Entladung)** will close
> once those stations are placed. *The TRO section further below still reflects the earlier
> `mubea.csv` run and is regenerated in the **next** step.*
> **How to read this.** The Mubea layout was drawn in BricsCAD with our **dxfmakros**
> library, so every conveyor object is a real, attributed item. dxfmakros exported those
> items — with their **coordinates, properties and neighbours** — to `mubea.csv`. From that
> CSV the `lib/` tooling derives two graphs, both **laid out at the real plant coordinates**
> so they match the drawing:
>
> 1. the **material-flow graph** — the mechanical objects and how material moves between them;
> 2. the **TRO graph** — the control units (Transfer Route Objects) derived from that flow.
>
> This is generated output, not hand-authored — see [§5](#5-how-this-was-produced) to regenerate.
> (The earlier [TRO_Identifikation_500573.md](TRO_Identifikation_500573.md) was a manual first
> pass on a *different*, purely mechanical drawing; **this** file supersedes it for Mubea.)
---
## 1. The plant in one paragraph
Empty hanger carriers (**Bügelträger**) circulate on three rotary tables (**Kreisel**) linked
by driven conveyor segments (**VarioFörderer / Strecke**). The core is a **line store** of
**20 gravity lanes** (**Gefällestrecke**), each with its own separator, sharing a common
in-/outfeed — i.e. one **PinStore**. Barcode **scanners** read carriers at three points.
**Item inventory (from `export.csv`):**
| Item type (German) | Meaning | Count | Role in the graph |
|---|---|---:|---|
| `ILS 2.0 Gefaellestrecke` | gravity lane (rolls down by slope) | 20 | node |
| `ILS 2.0 Separator` | stopper / separator (releases one carrier) | 33 | attached (`Zuordnung`) |
| `ILS 2.0 Scanner` | barcode scanner | 7 | attached (`Zuordnung`) |
| `ILS 2.0 Kreisel` | rotary table / turntable (2 lanes each) | 3 | node (2 lanes each) |
| `ILS 2.0 Strecke` | driven conveyor segment (VarioFörderer) | 3 | node |
| `ILS Ausschleuselement` | Kreisel→Strecke transfer (at strecke *Anfang*) | 23 | **directs an edge** |
| `ILS Einschleuselement` | Strecke→Kreisel transfer (at strecke *Ende*) | 23 | **directs an edge** |
| `ILS Weiche` | switch between two Kreisel | 1 | **marks an edge** |
| **Total** | | **113** | |
The transfer/switch elements don't become their own nodes — they supply the **direction** of
the Kreisel↔Strecke edges and mark the Weiche, so the graph stays at the conveyor-object level.
---
## 2. Material-flow graph (real-layout placement)
Nodes are placed at their true plant coordinates (`neato -n`, 0.25 pt/mm), so the picture
matches the drawing: the 20 gravity lanes form the central vertical stack, fed by **Kreisel 3**
(left) and discharging into **Kreisel 2** (right).
![Material-flow graph](graphs/mubea_material_flow.svg)
> Open [`graphs/mubea_material_flow.svg`](graphs/mubea_material_flow.svg) for the full-resolution,
> zoomable version. Full node/connection listing: [`graphs/mubea_material_flow_report.md`](graphs/mubea_material_flow_report.md).
### 2.1 Items with their properties (flow objects)
Separators and scanners are *attachments* — they appear in the last two columns of the object
they sit on. A **Kreisel** is a loop, modelled as two lanes (`-L` / `-R`).
| Node | Type | Name | Grid | Height [m] | Separators | Scanners |
|---|---|---|---|---:|---|---|
| `0001-L` | Kreisel | Kreisel 3 (lane L) | A/1 | 1.470 | | |
| `0001-R` | Kreisel | Kreisel 3 (lane R) | A/1 | 1.470 | 0057 | |
| `0002-L` | Kreisel | Kreisel 2 (lane L) | A/1 | 1.470 | 0003, 0010, 0011 | 0006 |
| `0002-R` | Kreisel | Kreisel 2 (lane R) | A/1 | 1.470 | | |
| `0009-L` | Kreisel | Kreisel 1 (lane L) | F/1 | 1.940 | | |
| `0009-R` | Kreisel | Kreisel 1 (lane R) | F/1 | 1.940 | 0059, 0060 | |
| `0012` | Strecke | VarioFörderer 3 | A/10 | 0.237 | 0014, 0058 | |
| `0013` | Strecke | VarioFörderer 2 | A/1 | 0.237 | 0056, 0061 | 0005 |
| `0015` | Strecke | VarioFörderer 1 | A/3 | 0.000 | | 0008 |
| `0016``0035` | Gefällestrecke ×20 | Gravity lanes 120 | E/2E/10 | 1.941 | one each (00360055, +0004) | |
*Coordinates, heights and the `Merkmale` (feature) properties per item are carried through from
the CSV; the full per-lane table is in the [material report](graphs/mubea_material_flow_report.md).*
---
## 3. TRO graph (control units)
The TROs are **derived** from the material flow using the rules in `lib/tro_flow.py`
(catalogue: `lib/tro_catalog.py`). The graph is placed at the same real coordinates.
![TRO graph](graphs/mubea_tro_flow.svg)
> Full-resolution: [`graphs/mubea_tro_flow.svg`](graphs/mubea_tro_flow.svg) ·
> full report incl. connections & rules: [`graphs/mubea_tro_report.md`](graphs/mubea_tro_report.md).
### 3.1 Result: 10 TROs
| Type | FB block | Count | Components per TRO |
|---|---|---:|---|
| `1Sep` | `FB_ILS_MTRO_1Sep` | 6 | 1× separator |
| `Vario` | `FB_ILS_MTRO_Vario` | 3 | 1× separator, 1× drive |
| `PinStore_Auto` | `FB_ILS_MTRO_PinStore_Auto` | 1 | 1× encoder, 1× scanner, 1× separator, 19× storage line |
### 3.2 TROs with their properties
| TRO | Type | X [mm] | Y [mm] | Host node(s) | Separators | Scanner | Why this type | Confidence |
|---|---|---:|---:|---|---|---|---|---|
| `TRO01` | `1Sep` | 5355 | 5878 | `0001-R` | 0057 | | single separator on a driven Kreisel lane | medium |
| `TRO02` | `1Sep` | 400 | 13920 | `0002-L` | 0003 | | single separator on a driven Kreisel lane | medium |
| `TRO03` | `1Sep` | 220 | 481 | `0002-L` | 0010 | 0008 | separator + scanner on a driven Kreisel lane | medium |
| `TRO04` | `1Sep` | 220 | 3472 | `0002-L` | 0011 | 0006 | separator + scanner on a driven Kreisel lane | medium |
| `TRO05` | `1Sep` | 10490 | 525 | `0009-R` | 0059 | 0007 | separator + scanner on a driven Kreisel lane | medium |
| `TRO06` | `1Sep` | 10310 | 160 | `0009-R` | 0060 | | single separator on a driven Kreisel lane | medium |
| `TRO07` | `Vario` | 9186 | 13893 | `0012` | 0014, 0058 | | driven conveyor segment (drive dir: up) | high |
| `TRO08` | `Vario` | 5492 | 4980 | `0013` | 0056, 0061 | | driven conveyor segment (drive dir: up) | high |
| `TRO09` | `Vario` | 641 | 5859 | `0015` | | | driven conveyor segment (drive dir: up) | high |
| `TRO10` | **`PinStore_Auto`** | 989 | 3924 | `0016 … 0035` (20 nodes) | 0004 … 0055 (21) | 0005 | **20 gravity lanes, each with its own separator, common in-/outfeed — the line-store pattern** | high |
*X/Y is the centroid of the components a TRO was built from (in the CSV plant coordinate system).*
---
## 4. Findings to confirm with the E-planning
The TRO **types** are solid; a few points can't be decided from the mechanical layout alone:
- **`1Sep` vs `1Sep_SSCC`.** TRO03, TRO04, TRO05 each have a scanner at the separator. They are
typed `1Sep`; whether any is really `1Sep_SSCC` (SSCC scanner + end-measurement + WCS telegram)
cannot be seen mechanically — confirm against the I/O list.
- **Scanner mounting vs. reading point.** Scanner 0005 is mounted on `0013` but reads at
separator 0004 on lane `0035`; scanner 0008 is mounted on `0015` but reads at separator 0010.
(Noise from the export; noted, not fatal.)
- **Two flow directions undetermined** around the Kreisel↔Strecke hand-overs (neighbours at equal
height) — drawn bidirectional in the material graph until the drive directions are confirmed.
- **CSV plausibility deltas** (stated `Anzahl_Separator`/`Anzahl_Scanner` vs. actually attached)
are listed in the material report — worth a quick check in the drawing.
What is **not** derivable here and must come from the electrical planning / `FB_Main`: the binding
TRO numbering, sensor/actuator tags, JamAreas, destination logic and timing.
---
## 5. How this was produced
```bat
:: 1) items + material flow, placed at real coordinates
bin\material_flow.bat --file mubea.csv --tosvg --use-cords --doc
:: 2) derive the TROs, placed at real coordinates
bin\tro_flow.bat --file mubea.csv --tosvg --use-cords --doc
```
Outputs land in `%SKEL_RESULTS%` (`results/`); the SVGs and reports here are copies. The
`--use-cords` flag (new for `material_flow.py`, matching `tro_flow.py`) places every node at its
plant coordinate and renders with Graphviz `neato -n`, so both graphs mirror the real layout.
**Next step (later, not now):** feed these TROs back into the BricsCAD drawing as TRO symbols
with their connections (`lib/tro_annotate.py``results/mubea_annotated.dxf`, then the
`dxfmakros` `TRO_INSERT` / `TRO_EDIT` menu), and record neighbours in `connect.ini`.
---
## 6. Entry / exit points (BTMT Be-/Entladestation) & data freshness
The **entry and exit points** of the material flow are the **BTMT Be-/Entladestation**
(`BTMT-Beladung` = loading = **entry/source**, `SC_Entladung` = unloading = **exit/sink**).
`material_flow.py` now **recognises them** (matched on `TeileArt`/`Bezeichnung`): an entry
becomes a green source node feeding the flow, an exit a red sink node — connected via the
export's `Nachbarn`, or to the nearest object if none is given.
Two current limitations to be aware of:
1. **The stations are not in the drawing / CSV yet.** They are not placed in `Mubea.dxf`,
and — importantly — dxfmakros' `EXPORTCSV` does **not** export them at all (their block
names match no export pattern; dxfmakros docs mark this *"Export noch offen"*). So a
freshly exported CSV still won't contain them until either the export is extended in
dxfmakros or the rows are added by hand. The sps_skel recognition is ready for that moment.
2. **The TRO graph does not yet show entry/exit.** `tro_flow.py` ignores the stations for
now (they carry no separator, so they are not TROs). Adding them as boundary markers in
the TRO graph is the planned follow-up, best done against a real BTMT row.
**On reading straight from the DXF instead of the CSV:** ezdxf reads `Mubea.dxf` fine (2.6 s)
and gives correct item positions + attributes, but it **cannot reproduce the 3-D bounding
boxes** that dxfmakros' neighbour detection relies on (`export_neighbors.py`, from BricsCAD's
`vla-getboundingbox`) — so a BricsCAD-free reader can place items but not rebuild the
connections reliably. The reliable path to *current* data is therefore a fresh
`EXPORTCSV` run in BricsCAD; the graphs above are then regenerated with the two commands in §5.
+102
View File
@@ -0,0 +1,102 @@
; ============================================================
; connect.ini - Materialfluss-Topologie (Item-/Flussebene)
; Automatisch erzeugt von lib/material_flow.py aus 'export.csv'.
; Zwischenstand fuer die TRO-Ableitung; von Hand editierbar.
;
; [<Anlage>.nodes] <Knoten> = <Art> | <Bezeichnung>
; [<Anlage>.connections] cNNN = <von> -> <nach> | <JamArea> | <kind>
; kind: normal | umlauf | weiche | einschleusung | ausschleusung | unbestimmt
; [<Anlage>.externals] eNNN = EXTERN -> <Knoten> | <Info> (Zulauf/Beladung)
; eNNN = <Knoten> -> EXTERN | <Info> (Ablauf/Entladung)
; ============================================================
[Mubea.nodes]
0001-L = Kreisel | Kreisel1 (Bahn L)
0001-R = Kreisel | Kreisel1 (Bahn R)
0002-L = Kreisel | Kreisel2 (Bahn L)
0002-R = Kreisel | Kreisel2 (Bahn R)
0010-L = Kreisel | Kreisel3 (Bahn L)
0010-R = Kreisel | Kreisel3 (Bahn R)
0013 = Strecke | VarioFoerderer :3
0014 = Strecke | VarioFoerderer :2
0016 = Strecke | VarioFoerderer :1
0017 = Gefaellestrecke | Gefaellestrecke :20
0018 = Gefaellestrecke | Gefaellestrecke :19
0019 = Gefaellestrecke | Gefaellestrecke :18
0020 = Gefaellestrecke | Gefaellestrecke :17
0021 = Gefaellestrecke | Gefaellestrecke :16
0022 = Gefaellestrecke | Gefaellestrecke :15
0023 = Gefaellestrecke | Gefaellestrecke :14
0024 = Gefaellestrecke | Gefaellestrecke :13
0025 = Gefaellestrecke | Gefaellestrecke :12
0026 = Gefaellestrecke | Gefaellestrecke :11
0027 = Gefaellestrecke | Gefaellestrecke :10
0028 = Gefaellestrecke | Gefaellestrecke :9
0029 = Gefaellestrecke | Gefaellestrecke :8
0030 = Gefaellestrecke | Gefaellestrecke :7
0031 = Gefaellestrecke | Gefaellestrecke :6
0032 = Gefaellestrecke | Gefaellestrecke :5
0033 = Gefaellestrecke | Gefaellestrecke :4
0034 = Gefaellestrecke | Gefaellestrecke :3
0035 = Gefaellestrecke | Gefaellestrecke :2
0036 = Gefaellestrecke | Gefaellestrecke :1
[Mubea.connections]
c001 = 0001-L -> 0002-R | | weiche
c002 = 0001-R -> 0001-L | | umlauf
c003 = 0001-R -> 0002-R | | weiche
c004 = 0002-L -> 0016 | | ausschleusung
c005 = 0002-R -> 0002-L | | umlauf
c006 = 0002-R -> 0013 | | ausschleusung
c007 = 0002-R -> 0014 | | ausschleusung
c008 = 0010-L -> 0017 | | ausschleusung
c009 = 0010-L -> 0018 | | ausschleusung
c010 = 0010-L -> 0019 | | ausschleusung
c011 = 0010-L -> 0020 | | ausschleusung
c012 = 0010-L -> 0021 | | ausschleusung
c013 = 0010-L -> 0022 | | ausschleusung
c014 = 0010-L -> 0023 | | ausschleusung
c015 = 0010-L -> 0024 | | ausschleusung
c016 = 0010-L -> 0025 | | ausschleusung
c017 = 0010-L -> 0026 | | ausschleusung
c018 = 0010-L -> 0027 | | ausschleusung
c019 = 0010-L -> 0028 | | ausschleusung
c020 = 0010-L -> 0029 | | ausschleusung
c021 = 0010-L -> 0030 | | ausschleusung
c022 = 0010-L -> 0031 | | ausschleusung
c023 = 0010-L -> 0032 | | ausschleusung
c024 = 0010-L -> 0033 | | ausschleusung
c025 = 0010-L -> 0034 | | ausschleusung
c026 = 0010-L -> 0035 | | ausschleusung
c027 = 0010-L -> 0036 | | ausschleusung
c028 = 0010-R -> 0010-L | | umlauf
c029 = 0013 -> 0010-L | | einschleusung
c030 = 0014 -> 0010-L | | einschleusung
c031 = 0016 -> 0002-R | | einschleusung
c032 = 0017 -> 0002-R | | einschleusung
c033 = 0018 -> 0002-R | | einschleusung
c034 = 0019 -> 0002-R | | einschleusung
c035 = 0020 -> 0002-R | | einschleusung
c036 = 0021 -> 0002-R | | einschleusung
c037 = 0022 -> 0002-R | | einschleusung
c038 = 0023 -> 0002-R | | einschleusung
c039 = 0024 -> 0002-R | | einschleusung
c040 = 0025 -> 0002-R | | einschleusung
c041 = 0026 -> 0002-R | | einschleusung
c042 = 0027 -> 0002-R | | einschleusung
c043 = 0028 -> 0002-R | | einschleusung
c044 = 0029 -> 0002-R | | einschleusung
c045 = 0030 -> 0002-R | | einschleusung
c046 = 0031 -> 0002-R | | einschleusung
c047 = 0032 -> 0002-R | | einschleusung
c048 = 0033 -> 0002-R | | einschleusung
c049 = 0034 -> 0002-R | | einschleusung
c050 = 0035 -> 0002-R | | einschleusung
c051 = 0036 -> 0002-R | | einschleusung
[Mubea.externals]
; Noch keine BTMT Be-/Entladestation im Layout - Ein-/Ausgang offen.
; Kandidaten (offene Enden aus dem Materialfluss):
; Beladung? EXTERN -> 0001-R (Kreisel, keine Zufuhr)
; Beladung? EXTERN -> 0010-R (Kreisel, keine Zufuhr)
@@ -0,0 +1,637 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 15.1.0 (20260618.0150)
-->
<!-- Title: Materialfluss Pages: 1 -->
<svg width="2928pt" height="5734pt"
viewBox="0.00 0.00 2928.00 5734.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 5730.07)">
<title>Materialfluss</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-5730.07 2924.07,-5730.07 2924.07,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="1460.04" y="-5711.62" font-family="Segoe UI" font-size="11.00">Materialfluss &#45; export.csv</text>
<!-- 0001&#45;L -->
<g id="node1" class="node">
<title>0001&#45;L</title>
<path fill="#2f5597" stroke="#1f3864" d="M1241.32,-981.25C1241.32,-981.25 1182.57,-981.25 1182.57,-981.25 1176.57,-981.25 1170.57,-975.25 1170.57,-969.25 1170.57,-969.25 1170.57,-947 1170.57,-947 1170.57,-941 1176.57,-935 1182.57,-935 1182.57,-935 1241.32,-935 1241.32,-935 1247.32,-935 1253.32,-941 1253.32,-947 1253.32,-947 1253.32,-969.25 1253.32,-969.25 1253.32,-975.25 1247.32,-981.25 1241.32,-981.25"/>
<text xml:space="preserve" text-anchor="middle" x="1211.95" y="-968.7" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Kreisel1 &#160;[0001&#45;L]</text>
<text xml:space="preserve" text-anchor="middle" x="1211.95" y="-955.95" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Bahn L (links)</text>
<text xml:space="preserve" text-anchor="middle" x="1211.95" y="-943.2" font-family="Segoe UI" font-size="9.00" fill="#ffffff">h = 1.470 m</text>
</g>
<!-- 0002&#45;R -->
<g id="node4" class="node">
<title>0002&#45;R</title>
<path fill="#2f5597" stroke="#1f3864" d="M416.69,-3308.13C416.69,-3308.13 357.19,-3308.13 357.19,-3308.13 351.19,-3308.13 345.19,-3302.13 345.19,-3296.13 345.19,-3296.13 345.19,-3273.88 345.19,-3273.88 345.19,-3267.88 351.19,-3261.88 357.19,-3261.88 357.19,-3261.88 416.69,-3261.88 416.69,-3261.88 422.69,-3261.88 428.69,-3267.88 428.69,-3273.88 428.69,-3273.88 428.69,-3296.13 428.69,-3296.13 428.69,-3302.13 422.69,-3308.13 416.69,-3308.13"/>
<text xml:space="preserve" text-anchor="middle" x="386.94" y="-3295.58" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Kreisel2 &#160;[0002&#45;R]</text>
<text xml:space="preserve" text-anchor="middle" x="386.94" y="-3282.83" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Bahn R (rechts)</text>
<text xml:space="preserve" text-anchor="middle" x="386.94" y="-3270.08" font-family="Segoe UI" font-size="9.00" fill="#ffffff">h = 1.470 m</text>
</g>
<!-- 0001&#45;L&#45;&gt;0002&#45;R -->
<g id="edge1" class="edge">
<title>0001&#45;L&#45;&gt;0002&#45;R</title>
<g id="a_edge1"><a xlink:title="Richtung Weiche">
<path fill="none" stroke="#bf8f00" stroke-width="1.2" stroke-dasharray="5,2" d="M1200.43,-990.59C1108.22,-1250.68 490.75,-2992.22 398.47,-3252.49"/>
<polygon fill="#bf8f00" stroke="#bf8f00" stroke-width="1.2" points="1203,-991.72 1203.04,-983.24 1197.73,-989.85 1203,-991.72"/>
<polygon fill="#bf8f00" stroke="#bf8f00" stroke-width="1.2" points="395.94,-3251.27 395.9,-3259.74 401.21,-3253.14 395.94,-3251.27"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="786.11" y="-2124.76" font-family="Segoe UI" font-size="8.00" fill="#cc8800">Weiche</text>
</g>
<!-- 0001&#45;R -->
<g id="node2" class="node">
<title>0001&#45;R</title>
<path fill="#2f5597" stroke="#1f3864" d="M1244.32,-812.62C1244.32,-812.62 1179.57,-812.62 1179.57,-812.62 1173.57,-812.62 1167.57,-806.62 1167.57,-800.62 1167.57,-800.62 1167.57,-765.62 1167.57,-765.62 1167.57,-759.62 1173.57,-753.62 1179.57,-753.62 1179.57,-753.62 1244.32,-753.62 1244.32,-753.62 1250.32,-753.62 1256.32,-759.62 1256.32,-765.62 1256.32,-765.62 1256.32,-800.62 1256.32,-800.62 1256.32,-806.62 1250.32,-812.62 1244.32,-812.62"/>
<text xml:space="preserve" text-anchor="middle" x="1211.95" y="-800.08" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Kreisel1 &#160;[0001&#45;R]</text>
<text xml:space="preserve" text-anchor="middle" x="1211.95" y="-787.33" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Bahn R (rechts)</text>
<text xml:space="preserve" text-anchor="middle" x="1211.95" y="-774.58" font-family="Segoe UI" font-size="9.00" fill="#ffffff">h = 1.470 m</text>
<text xml:space="preserve" text-anchor="middle" x="1211.95" y="-761.83" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Separator (1): 0061</text>
</g>
<!-- 0001&#45;R&#45;&gt;0001&#45;L -->
<g id="edge5" class="edge">
<title>0001&#45;R&#45;&gt;0001&#45;L</title>
<g id="a_edge5"><a xlink:title="Kreisel&#45;Umlauf UZS">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M1211.95,-812.81C1211.95,-843.6 1211.95,-891.69 1211.95,-924.02"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1208.8,-923.93 1211.95,-932.93 1215.1,-923.93 1208.8,-923.93"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="1204.82" y="-877.56" font-family="Segoe UI" font-size="8.00" fill="#5b7fc7">UZS</text>
</g>
<!-- 0001&#45;R&#45;&gt;0002&#45;R -->
<g id="edge2" class="edge">
<title>0001&#45;R&#45;&gt;0002&#45;R</title>
<g id="a_edge2"><a xlink:title="Richtung Weiche">
<path fill="none" stroke="#bf8f00" stroke-width="1.2" stroke-dasharray="5,2" d="M1199.1,-822.08C1102.18,-1116.01 487.19,-2981.01 397.78,-3252.15"/>
<polygon fill="#bf8f00" stroke="#bf8f00" stroke-width="1.2" points="1201.69,-823.16 1201.54,-814.69 1196.37,-821.41 1201.69,-823.16"/>
<polygon fill="#bf8f00" stroke="#bf8f00" stroke-width="1.2" points="395.12,-3251.26 395.28,-3259.74 400.44,-3253.02 395.12,-3251.26"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="782.11" y="-2049.34" font-family="Segoe UI" font-size="8.00" fill="#cc8800">Weiche</text>
</g>
<!-- 0002&#45;L -->
<g id="node3" class="node">
<title>0002&#45;L</title>
<path fill="#2f5597" stroke="#1f3864" d="M441.82,-3495.88C441.82,-3495.88 332.07,-3495.88 332.07,-3495.88 326.07,-3495.88 320.07,-3489.88 320.07,-3483.88 320.07,-3483.88 320.07,-3436.13 320.07,-3436.13 320.07,-3430.13 326.07,-3424.13 332.07,-3424.13 332.07,-3424.13 441.82,-3424.13 441.82,-3424.13 447.82,-3424.13 453.82,-3430.13 453.82,-3436.13 453.82,-3436.13 453.82,-3483.88 453.82,-3483.88 453.82,-3489.88 447.82,-3495.88 441.82,-3495.88"/>
<text xml:space="preserve" text-anchor="middle" x="386.94" y="-3483.33" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Kreisel2 &#160;[0002&#45;L]</text>
<text xml:space="preserve" text-anchor="middle" x="386.94" y="-3470.58" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Bahn L (links)</text>
<text xml:space="preserve" text-anchor="middle" x="386.94" y="-3457.83" font-family="Segoe UI" font-size="9.00" fill="#ffffff">h = 1.470 m</text>
<text xml:space="preserve" text-anchor="middle" x="386.94" y="-3445.08" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Separator (3): 0003, 0011, 0012</text>
<text xml:space="preserve" text-anchor="middle" x="386.94" y="-3432.33" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Scanner (3): 0004, 0007, 0009</text>
</g>
<!-- 0016 -->
<g id="node9" class="node">
<title>0016</title>
<path fill="#e2f0d9" stroke="#548235" d="M279.12,-3816.49C279.12,-3816.49 179.13,-3816.49 179.13,-3816.49 173.13,-3816.49 167.13,-3810.49 167.13,-3804.49 167.13,-3804.49 167.13,-3743.99 167.13,-3743.99 167.13,-3737.99 173.13,-3731.99 179.13,-3731.99 179.13,-3731.99 279.12,-3731.99 279.12,-3731.99 285.12,-3731.99 291.12,-3737.99 291.12,-3743.99 291.12,-3743.99 291.12,-3804.49 291.12,-3804.49 291.12,-3810.49 285.12,-3816.49 279.12,-3816.49"/>
<text xml:space="preserve" text-anchor="middle" x="229.13" y="-3803.94" font-family="Segoe UI" font-size="9.00" fill="#375623">VarioFoerderer :1 &#160;[0016]</text>
<text xml:space="preserve" text-anchor="middle" x="229.13" y="-3791.19" font-family="Segoe UI" font-size="9.00" fill="#375623">Strecke</text>
<text xml:space="preserve" text-anchor="middle" x="229.13" y="-3778.44" font-family="Segoe UI" font-size="9.00" fill="#375623">h = 0.000 m</text>
<text xml:space="preserve" text-anchor="middle" x="229.13" y="-3765.69" font-family="Segoe UI" font-size="9.00" fill="#375623">0 &#45;&gt; 0 mm (+0) = aufwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="229.13" y="-3752.94" font-family="Segoe UI" font-size="9.00" fill="#375623">Separator (2): 0058, 0059</text>
<text xml:space="preserve" text-anchor="middle" x="229.13" y="-3740.19" font-family="Segoe UI" font-size="9.00" fill="#375623">Scanner (1): 0060</text>
</g>
<!-- 0002&#45;L&#45;&gt;0016 -->
<g id="edge46" class="edge">
<title>0002&#45;L&#45;&gt;0016</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M368.69,-3496.36C340.63,-3552.22 286.83,-3659.35 254.84,-3723.04"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="252.38,-3721.7 251.29,-3730.1 257.39,-3724.21 252.38,-3721.7"/>
</g>
<!-- 0002&#45;R&#45;&gt;0002&#45;L -->
<g id="edge4" class="edge">
<title>0002&#45;R&#45;&gt;0002&#45;L</title>
<g id="a_edge4"><a xlink:title="Kreisel&#45;Umlauf UZS">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M386.94,-3308.61C386.94,-3335.29 386.94,-3379.42 386.94,-3413.16"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="383.8,-3412.9 386.95,-3421.9 390.1,-3412.9 383.8,-3412.9"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="379.82" y="-3369.77" font-family="Segoe UI" font-size="8.00" fill="#5b7fc7">UZS</text>
</g>
<!-- 0013 -->
<g id="node7" class="node">
<title>0013</title>
<path fill="#e2f0d9" stroke="#548235" d="M1681.56,-5703.07C1681.56,-5703.07 1563.56,-5703.07 1563.56,-5703.07 1557.56,-5703.07 1551.56,-5697.07 1551.56,-5691.07 1551.56,-5691.07 1551.56,-5643.32 1551.56,-5643.32 1551.56,-5637.32 1557.56,-5631.32 1563.56,-5631.32 1563.56,-5631.32 1681.56,-5631.32 1681.56,-5631.32 1687.56,-5631.32 1693.56,-5637.32 1693.56,-5643.32 1693.56,-5643.32 1693.56,-5691.07 1693.56,-5691.07 1693.56,-5697.07 1687.56,-5703.07 1681.56,-5703.07"/>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-5690.52" font-family="Segoe UI" font-size="9.00" fill="#375623">VarioFoerderer :3 &#160;[0013]</text>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-5677.77" font-family="Segoe UI" font-size="9.00" fill="#375623">Strecke</text>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-5665.02" font-family="Segoe UI" font-size="9.00" fill="#375623">h = 0.237 m</text>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-5652.27" font-family="Segoe UI" font-size="9.00" fill="#375623">0 &#45;&gt; 474 mm (+474) = aufwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-5639.52" font-family="Segoe UI" font-size="9.00" fill="#375623">Separator (2): 0015, 0062</text>
</g>
<!-- 0002&#45;R&#45;&gt;0013 -->
<g id="edge50" class="edge">
<title>0002&#45;R&#45;&gt;0013</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M399.2,-3308.48C413.29,-3335.48 437.1,-3381.15 457.52,-3420.43 918.48,-4307.16 1480.01,-5391.79 1599.53,-5622.68"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1596.92,-5623.73 1603.08,-5629.55 1601.89,-5621.16 1596.92,-5623.73"/>
</g>
<!-- 0014 -->
<g id="node8" class="node">
<title>0014</title>
<path fill="#e2f0d9" stroke="#548235" d="M1681.56,-1146.19C1681.56,-1146.19 1563.56,-1146.19 1563.56,-1146.19 1557.56,-1146.19 1551.56,-1140.19 1551.56,-1134.19 1551.56,-1134.19 1551.56,-1073.69 1551.56,-1073.69 1551.56,-1067.69 1557.56,-1061.69 1563.56,-1061.69 1563.56,-1061.69 1681.56,-1061.69 1681.56,-1061.69 1687.56,-1061.69 1693.56,-1067.69 1693.56,-1073.69 1693.56,-1073.69 1693.56,-1134.19 1693.56,-1134.19 1693.56,-1140.19 1687.56,-1146.19 1681.56,-1146.19"/>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-1133.64" font-family="Segoe UI" font-size="9.00" fill="#375623">VarioFoerderer :2 &#160;[0014]</text>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-1120.89" font-family="Segoe UI" font-size="9.00" fill="#375623">Strecke</text>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-1108.14" font-family="Segoe UI" font-size="9.00" fill="#375623">h = 0.237 m</text>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-1095.39" font-family="Segoe UI" font-size="9.00" fill="#375623">0 &#45;&gt; 474 mm (+474) = aufwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-1082.64" font-family="Segoe UI" font-size="9.00" fill="#375623">Separator (2): 0057, 0065</text>
<text xml:space="preserve" text-anchor="middle" x="1622.56" y="-1069.89" font-family="Segoe UI" font-size="9.00" fill="#375623">Scanner (1): 0006</text>
</g>
<!-- 0002&#45;R&#45;&gt;0014 -->
<g id="edge48" class="edge">
<title>0002&#45;R&#45;&gt;0014</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M400.19,-3261.62C520.3,-3049.62 1420.55,-1460.54 1593.78,-1154.76"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1596.05,-1156.43 1597.55,-1148.09 1591.18,-1153.67 1596.05,-1156.43"/>
</g>
<!-- 0010&#45;L -->
<g id="node5" class="node">
<title>0010&#45;L</title>
<path fill="#2f5597" stroke="#1f3864" d="M2893.82,-3483.13C2893.82,-3483.13 2835.07,-3483.13 2835.07,-3483.13 2829.07,-3483.13 2823.07,-3477.13 2823.07,-3471.13 2823.07,-3471.13 2823.07,-3448.88 2823.07,-3448.88 2823.07,-3442.88 2829.07,-3436.88 2835.07,-3436.88 2835.07,-3436.88 2893.82,-3436.88 2893.82,-3436.88 2899.82,-3436.88 2905.82,-3442.88 2905.82,-3448.88 2905.82,-3448.88 2905.82,-3471.13 2905.82,-3471.13 2905.82,-3477.13 2899.82,-3483.13 2893.82,-3483.13"/>
<text xml:space="preserve" text-anchor="middle" x="2864.44" y="-3470.58" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Kreisel3 &#160;[0010&#45;L]</text>
<text xml:space="preserve" text-anchor="middle" x="2864.44" y="-3457.83" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Bahn L (links)</text>
<text xml:space="preserve" text-anchor="middle" x="2864.44" y="-3445.08" font-family="Segoe UI" font-size="9.00" fill="#ffffff">h = 1.940 m</text>
</g>
<!-- 0017 -->
<g id="node10" class="node">
<title>0017</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-5327.64C1693.98,-5327.64 1563.23,-5327.64 1563.23,-5327.64 1557.23,-5327.64 1551.23,-5321.64 1551.23,-5315.64 1551.23,-5315.64 1551.23,-5267.89 1551.23,-5267.89 1551.23,-5261.89 1557.23,-5255.89 1563.23,-5255.89 1563.23,-5255.89 1693.98,-5255.89 1693.98,-5255.89 1699.98,-5255.89 1705.98,-5261.89 1705.98,-5267.89 1705.98,-5267.89 1705.98,-5315.64 1705.98,-5315.64 1705.98,-5321.64 1699.98,-5327.64 1693.98,-5327.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5315.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :20 &#160;[0017]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5302.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5289.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5276.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5264.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0037</text>
</g>
<!-- 0010&#45;L&#45;&gt;0017 -->
<g id="edge44" class="edge">
<title>0010&#45;L&#45;&gt;0017</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2848.59,-3483.5C2718.91,-3675.72 1832.79,-4989.12 1658.37,-5247.64"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1656.12,-5245.98 1653.96,-5254.18 1660.76,-5249.11 1656.12,-5245.98"/>
</g>
<!-- 0018 -->
<g id="node11" class="node">
<title>0018</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-5127.64C1693.98,-5127.64 1563.23,-5127.64 1563.23,-5127.64 1557.23,-5127.64 1551.23,-5121.64 1551.23,-5115.64 1551.23,-5115.64 1551.23,-5067.89 1551.23,-5067.89 1551.23,-5061.89 1557.23,-5055.89 1563.23,-5055.89 1563.23,-5055.89 1693.98,-5055.89 1693.98,-5055.89 1699.98,-5055.89 1705.98,-5061.89 1705.98,-5067.89 1705.98,-5067.89 1705.98,-5115.64 1705.98,-5115.64 1705.98,-5121.64 1699.98,-5127.64 1693.98,-5127.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5115.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :19 &#160;[0018]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5102.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5089.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5076.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-5064.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0038</text>
</g>
<!-- 0010&#45;L&#45;&gt;0018 -->
<g id="edge42" class="edge">
<title>0010&#45;L&#45;&gt;0018</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2846.74,-3483.39C2711.56,-3661.86 1843.31,-4808.27 1661.83,-5047.9"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1659.61,-5046.19 1657.01,-5054.26 1664.07,-5049.57 1659.61,-5046.19"/>
</g>
<!-- 0019 -->
<g id="node12" class="node">
<title>0019</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-4927.64C1693.98,-4927.64 1563.23,-4927.64 1563.23,-4927.64 1557.23,-4927.64 1551.23,-4921.64 1551.23,-4915.64 1551.23,-4915.64 1551.23,-4867.89 1551.23,-4867.89 1551.23,-4861.89 1557.23,-4855.89 1563.23,-4855.89 1563.23,-4855.89 1693.98,-4855.89 1693.98,-4855.89 1699.98,-4855.89 1705.98,-4861.89 1705.98,-4867.89 1705.98,-4867.89 1705.98,-4915.64 1705.98,-4915.64 1705.98,-4921.64 1699.98,-4927.64 1693.98,-4927.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4915.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :18 &#160;[0019]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4902.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4889.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4876.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4864.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0039</text>
</g>
<!-- 0010&#45;L&#45;&gt;0019 -->
<g id="edge40" class="edge">
<title>0010&#45;L&#45;&gt;0019</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2844.28,-3483.37C2702.46,-3647.67 1855.06,-4629.41 1665.99,-4848.45"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1664.1,-4846.36 1660.99,-4854.24 1668.34,-4850.02 1664.1,-4846.36"/>
</g>
<!-- 0020 -->
<g id="node13" class="node">
<title>0020</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-4727.64C1693.98,-4727.64 1563.23,-4727.64 1563.23,-4727.64 1557.23,-4727.64 1551.23,-4721.64 1551.23,-4715.64 1551.23,-4715.64 1551.23,-4667.89 1551.23,-4667.89 1551.23,-4661.89 1557.23,-4655.89 1563.23,-4655.89 1563.23,-4655.89 1693.98,-4655.89 1693.98,-4655.89 1699.98,-4655.89 1705.98,-4661.89 1705.98,-4667.89 1705.98,-4667.89 1705.98,-4715.64 1705.98,-4715.64 1705.98,-4721.64 1699.98,-4727.64 1693.98,-4727.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4715.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :17 &#160;[0020]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4702.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4689.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4676.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4664.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0040</text>
</g>
<!-- 0010&#45;L&#45;&gt;0020 -->
<g id="edge38" class="edge">
<title>0010&#45;L&#45;&gt;0020</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2840.86,-3483.51C2690.89,-3632.99 1870.17,-4451 1671.79,-4648.73"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1669.97,-4646.58 1666.28,-4654.21 1673.92,-4650.55 1669.97,-4646.58"/>
</g>
<!-- 0021 -->
<g id="node14" class="node">
<title>0021</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-4527.64C1693.98,-4527.64 1563.23,-4527.64 1563.23,-4527.64 1557.23,-4527.64 1551.23,-4521.64 1551.23,-4515.64 1551.23,-4515.64 1551.23,-4467.89 1551.23,-4467.89 1551.23,-4461.89 1557.23,-4455.89 1563.23,-4455.89 1563.23,-4455.89 1693.98,-4455.89 1693.98,-4455.89 1699.98,-4455.89 1705.98,-4461.89 1705.98,-4467.89 1705.98,-4467.89 1705.98,-4515.64 1705.98,-4515.64 1705.98,-4521.64 1699.98,-4527.64 1693.98,-4527.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4515.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :16 &#160;[0021]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4502.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4489.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4476.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4464.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0041</text>
</g>
<!-- 0010&#45;L&#45;&gt;0021 -->
<g id="edge36" class="edge">
<title>0010&#45;L&#45;&gt;0021</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2836.31,-3483.49C2677.05,-3616.46 1888.32,-4274.94 1679.44,-4449.33"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1677.7,-4447.13 1673.36,-4454.4 1681.29,-4451.43 1677.7,-4447.13"/>
</g>
<!-- 0022 -->
<g id="node15" class="node">
<title>0022</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-4327.64C1693.98,-4327.64 1563.23,-4327.64 1563.23,-4327.64 1557.23,-4327.64 1551.23,-4321.64 1551.23,-4315.64 1551.23,-4315.64 1551.23,-4267.89 1551.23,-4267.89 1551.23,-4261.89 1557.23,-4255.89 1563.23,-4255.89 1563.23,-4255.89 1693.98,-4255.89 1693.98,-4255.89 1699.98,-4255.89 1705.98,-4261.89 1705.98,-4267.89 1705.98,-4267.89 1705.98,-4315.64 1705.98,-4315.64 1705.98,-4321.64 1699.98,-4327.64 1693.98,-4327.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4315.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :15 &#160;[0022]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4302.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4289.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4276.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4264.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0042</text>
</g>
<!-- 0010&#45;L&#45;&gt;0022 -->
<g id="edge34" class="edge">
<title>0010&#45;L&#45;&gt;0022</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2829.45,-3483.56C2658.63,-3598.53 1911.67,-4101.25 1690.51,-4250.1"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1689.13,-4247.65 1684.06,-4254.44 1692.26,-4252.3 1689.13,-4247.65"/>
</g>
<!-- 0023 -->
<g id="node16" class="node">
<title>0023</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-4127.64C1693.98,-4127.64 1563.23,-4127.64 1563.23,-4127.64 1557.23,-4127.64 1551.23,-4121.64 1551.23,-4115.64 1551.23,-4115.64 1551.23,-4067.89 1551.23,-4067.89 1551.23,-4061.89 1557.23,-4055.89 1563.23,-4055.89 1563.23,-4055.89 1693.98,-4055.89 1693.98,-4055.89 1699.98,-4055.89 1705.98,-4061.89 1705.98,-4067.89 1705.98,-4067.89 1705.98,-4115.64 1705.98,-4115.64 1705.98,-4121.64 1699.98,-4127.64 1693.98,-4127.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4115.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :14 &#160;[0023]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4102.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4089.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4076.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-4064.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0043</text>
</g>
<!-- 0010&#45;L&#45;&gt;0023 -->
<g id="edge32" class="edge">
<title>0010&#45;L&#45;&gt;0023</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2822.62,-3481.39C2644.26,-3572.56 1945.55,-3929.74 1708.09,-4051.13"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1706.97,-4048.56 1701.12,-4054.7 1709.52,-4053.55 1706.97,-4048.56"/>
</g>
<!-- 0024 -->
<g id="node17" class="node">
<title>0024</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-3927.64C1693.98,-3927.64 1563.23,-3927.64 1563.23,-3927.64 1557.23,-3927.64 1551.23,-3921.64 1551.23,-3915.64 1551.23,-3915.64 1551.23,-3867.89 1551.23,-3867.89 1551.23,-3861.89 1557.23,-3855.89 1563.23,-3855.89 1563.23,-3855.89 1693.98,-3855.89 1693.98,-3855.89 1699.98,-3855.89 1705.98,-3861.89 1705.98,-3867.89 1705.98,-3867.89 1705.98,-3915.64 1705.98,-3915.64 1705.98,-3921.64 1699.98,-3927.64 1693.98,-3927.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3915.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :13 &#160;[0024]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3902.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3889.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3876.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3864.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0044</text>
</g>
<!-- 0010&#45;L&#45;&gt;0024 -->
<g id="edge30" class="edge">
<title>0010&#45;L&#45;&gt;0024</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2822.62,-3474.62C2646.11,-3536.28 1960.05,-3775.97 1715.66,-3861.35"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1714.8,-3858.69 1708.17,-3863.97 1716.64,-3863.97 1714.8,-3858.69"/>
</g>
<!-- 0025 -->
<g id="node18" class="node">
<title>0025</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-3727.64C1693.98,-3727.64 1563.23,-3727.64 1563.23,-3727.64 1557.23,-3727.64 1551.23,-3721.64 1551.23,-3715.64 1551.23,-3715.64 1551.23,-3667.89 1551.23,-3667.89 1551.23,-3661.89 1557.23,-3655.89 1563.23,-3655.89 1563.23,-3655.89 1693.98,-3655.89 1693.98,-3655.89 1699.98,-3655.89 1705.98,-3661.89 1705.98,-3667.89 1705.98,-3667.89 1705.98,-3715.64 1705.98,-3715.64 1705.98,-3721.64 1699.98,-3727.64 1693.98,-3727.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3715.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :12 &#160;[0025]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3702.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3689.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3676.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3664.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0045</text>
</g>
<!-- 0010&#45;L&#45;&gt;0025 -->
<g id="edge28" class="edge">
<title>0010&#45;L&#45;&gt;0025</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2822.62,-3467.85C2646.2,-3500.93 1960.73,-3629.48 1716.02,-3675.37"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1715.59,-3672.6 1708.24,-3676.83 1716.62,-3678.11 1715.59,-3672.6"/>
</g>
<!-- 0026 -->
<g id="node19" class="node">
<title>0026</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-3527.64C1693.98,-3527.64 1563.23,-3527.64 1563.23,-3527.64 1557.23,-3527.64 1551.23,-3521.64 1551.23,-3515.64 1551.23,-3515.64 1551.23,-3467.89 1551.23,-3467.89 1551.23,-3461.89 1557.23,-3455.89 1563.23,-3455.89 1563.23,-3455.89 1693.98,-3455.89 1693.98,-3455.89 1699.98,-3455.89 1705.98,-3461.89 1705.98,-3467.89 1705.98,-3467.89 1705.98,-3515.64 1705.98,-3515.64 1705.98,-3521.64 1699.98,-3527.64 1693.98,-3527.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3515.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :11 &#160;[0026]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3502.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3489.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3476.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3464.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0046</text>
</g>
<!-- 0010&#45;L&#45;&gt;0026 -->
<g id="edge26" class="edge">
<title>0010&#45;L&#45;&gt;0026</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2822.62,-3461.08C2646.2,-3465.61 1960.73,-3483.23 1716.02,-3489.52"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1716.19,-3486.71 1708.27,-3489.72 1716.34,-3492.31 1716.19,-3486.71"/>
</g>
<!-- 0027 -->
<g id="node20" class="node">
<title>0027</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-3327.64C1693.98,-3327.64 1563.23,-3327.64 1563.23,-3327.64 1557.23,-3327.64 1551.23,-3321.64 1551.23,-3315.64 1551.23,-3315.64 1551.23,-3267.89 1551.23,-3267.89 1551.23,-3261.89 1557.23,-3255.89 1563.23,-3255.89 1563.23,-3255.89 1693.98,-3255.89 1693.98,-3255.89 1699.98,-3255.89 1705.98,-3261.89 1705.98,-3267.89 1705.98,-3267.89 1705.98,-3315.64 1705.98,-3315.64 1705.98,-3321.64 1699.98,-3327.64 1693.98,-3327.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3315.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :10 &#160;[0027]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3302.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3289.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3276.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3264.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0047</text>
</g>
<!-- 0010&#45;L&#45;&gt;0027 -->
<g id="edge24" class="edge">
<title>0010&#45;L&#45;&gt;0027</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2822.62,-3454.31C2646.2,-3430.29 1960.73,-3336.98 1716.02,-3303.67"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1716.56,-3300.91 1708.25,-3302.61 1715.8,-3306.46 1716.56,-3300.91"/>
</g>
<!-- 0028 -->
<g id="node21" class="node">
<title>0028</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-3127.64C1693.98,-3127.64 1563.23,-3127.64 1563.23,-3127.64 1557.23,-3127.64 1551.23,-3121.64 1551.23,-3115.64 1551.23,-3115.64 1551.23,-3067.89 1551.23,-3067.89 1551.23,-3061.89 1557.23,-3055.89 1563.23,-3055.89 1563.23,-3055.89 1693.98,-3055.89 1693.98,-3055.89 1699.98,-3055.89 1705.98,-3061.89 1705.98,-3067.89 1705.98,-3067.89 1705.98,-3115.64 1705.98,-3115.64 1705.98,-3121.64 1699.98,-3127.64 1693.98,-3127.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3115.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :9 &#160;[0028]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3102.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3089.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3076.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-3064.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0048</text>
</g>
<!-- 0010&#45;L&#45;&gt;0028 -->
<g id="edge22" class="edge">
<title>0010&#45;L&#45;&gt;0028</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2822.62,-3447.54C2646.11,-3394.95 1960.05,-3190.52 1715.66,-3117.7"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1716.66,-3115.08 1708.19,-3115.48 1715.06,-3120.45 1716.66,-3115.08"/>
</g>
<!-- 0029 -->
<g id="node22" class="node">
<title>0029</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-2927.64C1693.98,-2927.64 1563.23,-2927.64 1563.23,-2927.64 1557.23,-2927.64 1551.23,-2921.64 1551.23,-2915.64 1551.23,-2915.64 1551.23,-2867.89 1551.23,-2867.89 1551.23,-2861.89 1557.23,-2855.89 1563.23,-2855.89 1563.23,-2855.89 1693.98,-2855.89 1693.98,-2855.89 1699.98,-2855.89 1705.98,-2861.89 1705.98,-2867.89 1705.98,-2867.89 1705.98,-2915.64 1705.98,-2915.64 1705.98,-2921.64 1699.98,-2927.64 1693.98,-2927.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2915.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :8 &#160;[0029]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2902.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2889.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2876.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2864.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0049</text>
</g>
<!-- 0010&#45;L&#45;&gt;0029 -->
<g id="edge20" class="edge">
<title>0010&#45;L&#45;&gt;0029</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2822.62,-3440.77C2646.03,-3359.58 1959.37,-3043.85 1715.3,-2931.63"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1716.54,-2929.12 1708.1,-2928.32 1714.2,-2934.2 1716.54,-2929.12"/>
</g>
<!-- 0030 -->
<g id="node23" class="node">
<title>0030</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-2727.64C1693.98,-2727.64 1563.23,-2727.64 1563.23,-2727.64 1557.23,-2727.64 1551.23,-2721.64 1551.23,-2715.64 1551.23,-2715.64 1551.23,-2667.89 1551.23,-2667.89 1551.23,-2661.89 1557.23,-2655.89 1563.23,-2655.89 1563.23,-2655.89 1693.98,-2655.89 1693.98,-2655.89 1699.98,-2655.89 1705.98,-2661.89 1705.98,-2667.89 1705.98,-2667.89 1705.98,-2715.64 1705.98,-2715.64 1705.98,-2721.64 1699.98,-2727.64 1693.98,-2727.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2715.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :7 &#160;[0030]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2702.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2689.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2676.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2664.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0050</text>
</g>
<!-- 0010&#45;L&#45;&gt;0030 -->
<g id="edge18" class="edge">
<title>0010&#45;L&#45;&gt;0030</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2826.44,-3436.38C2651.2,-3327.44 1920.01,-2872.91 1694.88,-2732.96"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1696.58,-2730.72 1688.31,-2728.88 1693.62,-2735.48 1696.58,-2730.72"/>
</g>
<!-- 0031 -->
<g id="node24" class="node">
<title>0031</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-2527.64C1693.98,-2527.64 1563.23,-2527.64 1563.23,-2527.64 1557.23,-2527.64 1551.23,-2521.64 1551.23,-2515.64 1551.23,-2515.64 1551.23,-2467.89 1551.23,-2467.89 1551.23,-2461.89 1557.23,-2455.89 1563.23,-2455.89 1563.23,-2455.89 1693.98,-2455.89 1693.98,-2455.89 1699.98,-2455.89 1705.98,-2461.89 1705.98,-2467.89 1705.98,-2467.89 1705.98,-2515.64 1705.98,-2515.64 1705.98,-2521.64 1699.98,-2527.64 1693.98,-2527.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2515.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :6 &#160;[0031]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2502.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2489.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2476.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2464.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0051</text>
</g>
<!-- 0010&#45;L&#45;&gt;0031 -->
<g id="edge16" class="edge">
<title>0010&#45;L&#45;&gt;0031</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2834.51,-3436.55C2672.02,-3309.25 1895.48,-2700.85 1682.65,-2534.11"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1684.47,-2531.98 1676.45,-2529.25 1681.02,-2536.39 1684.47,-2531.98"/>
</g>
<!-- 0032 -->
<g id="node25" class="node">
<title>0032</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-2327.64C1693.98,-2327.64 1563.23,-2327.64 1563.23,-2327.64 1557.23,-2327.64 1551.23,-2321.64 1551.23,-2315.64 1551.23,-2315.64 1551.23,-2267.89 1551.23,-2267.89 1551.23,-2261.89 1557.23,-2255.89 1563.23,-2255.89 1563.23,-2255.89 1693.98,-2255.89 1693.98,-2255.89 1699.98,-2255.89 1705.98,-2261.89 1705.98,-2267.89 1705.98,-2267.89 1705.98,-2315.64 1705.98,-2315.64 1705.98,-2321.64 1699.98,-2327.64 1693.98,-2327.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2315.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :5 &#160;[0032]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2302.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2289.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2276.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2264.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0052</text>
</g>
<!-- 0010&#45;L&#45;&gt;0032 -->
<g id="edge14" class="edge">
<title>0010&#45;L&#45;&gt;0032</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2839.48,-3436.4C2686.46,-3291.75 1875.24,-2524.91 1673.87,-2334.56"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1676.03,-2332.74 1668.29,-2329.28 1672.18,-2336.81 1676.03,-2332.74"/>
</g>
<!-- 0033 -->
<g id="node26" class="node">
<title>0033</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-2127.64C1693.98,-2127.64 1563.23,-2127.64 1563.23,-2127.64 1557.23,-2127.64 1551.23,-2121.64 1551.23,-2115.64 1551.23,-2115.64 1551.23,-2067.89 1551.23,-2067.89 1551.23,-2061.89 1557.23,-2055.89 1563.23,-2055.89 1563.23,-2055.89 1693.98,-2055.89 1693.98,-2055.89 1699.98,-2055.89 1705.98,-2061.89 1705.98,-2067.89 1705.98,-2067.89 1705.98,-2115.64 1705.98,-2115.64 1705.98,-2121.64 1699.98,-2127.64 1693.98,-2127.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2115.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :4 &#160;[0033]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2102.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2089.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2076.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-2064.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0053</text>
</g>
<!-- 0010&#45;L&#45;&gt;0033 -->
<g id="edge12" class="edge">
<title>0010&#45;L&#45;&gt;0033</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2843.25,-3436.54C2698.93,-3276.76 1860.49,-2348.49 1667.99,-2135.37"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1670.07,-2133.5 1662.63,-2129.44 1665.92,-2137.25 1670.07,-2133.5"/>
</g>
<!-- 0034 -->
<g id="node27" class="node">
<title>0034</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-1927.64C1693.98,-1927.64 1563.23,-1927.64 1563.23,-1927.64 1557.23,-1927.64 1551.23,-1921.64 1551.23,-1915.64 1551.23,-1915.64 1551.23,-1867.89 1551.23,-1867.89 1551.23,-1861.89 1557.23,-1855.89 1563.23,-1855.89 1563.23,-1855.89 1693.98,-1855.89 1693.98,-1855.89 1699.98,-1855.89 1705.98,-1861.89 1705.98,-1867.89 1705.98,-1867.89 1705.98,-1915.64 1705.98,-1915.64 1705.98,-1921.64 1699.98,-1927.64 1693.98,-1927.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1915.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :3 &#160;[0034]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1902.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1889.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1876.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1864.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0054</text>
</g>
<!-- 0010&#45;L&#45;&gt;0034 -->
<g id="edge10" class="edge">
<title>0010&#45;L&#45;&gt;0034</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2846.02,-3436.62C2708.85,-3262.56 1847.34,-2169.33 1663.21,-1935.68"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1665.47,-1934.03 1658.32,-1929.48 1661.08,-1937.49 1665.47,-1934.03"/>
</g>
<!-- 0035 -->
<g id="node28" class="node">
<title>0035</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-1727.64C1693.98,-1727.64 1563.23,-1727.64 1563.23,-1727.64 1557.23,-1727.64 1551.23,-1721.64 1551.23,-1715.64 1551.23,-1715.64 1551.23,-1667.89 1551.23,-1667.89 1551.23,-1661.89 1557.23,-1655.89 1563.23,-1655.89 1563.23,-1655.89 1693.98,-1655.89 1693.98,-1655.89 1699.98,-1655.89 1705.98,-1661.89 1705.98,-1667.89 1705.98,-1667.89 1705.98,-1715.64 1705.98,-1715.64 1705.98,-1721.64 1699.98,-1727.64 1693.98,-1727.64"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1715.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :2 &#160;[0035]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1702.34" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1689.59" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1676.84" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1664.09" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (1): 0055</text>
</g>
<!-- 0010&#45;L&#45;&gt;0035 -->
<g id="edge8" class="edge">
<title>0010&#45;L&#45;&gt;0035</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2848.14,-3436.67C2717.11,-3249.2 1836.18,-1988.76 1659.44,-1735.89"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1661.86,-1734.45 1654.98,-1729.5 1657.27,-1737.66 1661.86,-1734.45"/>
</g>
<!-- 0036 -->
<g id="node29" class="node">
<title>0036</title>
<path fill="#fbe5d6" stroke="#c55a11" d="M1693.98,-1534.01C1693.98,-1534.01 1563.23,-1534.01 1563.23,-1534.01 1557.23,-1534.01 1551.23,-1528.01 1551.23,-1522.01 1551.23,-1522.01 1551.23,-1461.51 1551.23,-1461.51 1551.23,-1455.51 1557.23,-1449.51 1563.23,-1449.51 1563.23,-1449.51 1693.98,-1449.51 1693.98,-1449.51 1699.98,-1449.51 1705.98,-1455.51 1705.98,-1461.51 1705.98,-1461.51 1705.98,-1522.01 1705.98,-1522.01 1705.98,-1528.01 1699.98,-1534.01 1693.98,-1534.01"/>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1521.46" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke :1 &#160;[0036]</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1508.71" font-family="Segoe UI" font-size="9.00" fill="#833c00">Gefaellestrecke</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1495.96" font-family="Segoe UI" font-size="9.00" fill="#833c00">h = 1.941 m</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1483.21" font-family="Segoe UI" font-size="9.00" fill="#833c00">1941 &#45;&gt; 1466 mm (&#45;475) = abwaerts</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1470.46" font-family="Segoe UI" font-size="9.00" fill="#833c00">Separator (2): 0005, 0056</text>
<text xml:space="preserve" text-anchor="middle" x="1628.61" y="-1457.71" font-family="Segoe UI" font-size="9.00" fill="#833c00">Scanner (1): 0066</text>
</g>
<!-- 0010&#45;L&#45;&gt;0036 -->
<g id="edge6" class="edge">
<title>0010&#45;L&#45;&gt;0036</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2849.7,-3436.53C2724.51,-3237.14 1839.69,-1827.95 1660.29,-1542.22"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1662.78,-1540.93 1656.15,-1535.64 1658.04,-1543.9 1662.78,-1540.93"/>
</g>
<!-- 0010&#45;R -->
<g id="node6" class="node">
<title>0010&#45;R</title>
<path fill="#2f5597" stroke="#1f3864" d="M2908.07,-3314.51C2908.07,-3314.51 2820.82,-3314.51 2820.82,-3314.51 2814.82,-3314.51 2808.82,-3308.51 2808.82,-3302.51 2808.82,-3302.51 2808.82,-3267.51 2808.82,-3267.51 2808.82,-3261.51 2814.82,-3255.51 2820.82,-3255.51 2820.82,-3255.51 2908.07,-3255.51 2908.07,-3255.51 2914.07,-3255.51 2920.07,-3261.51 2920.07,-3267.51 2920.07,-3267.51 2920.07,-3302.51 2920.07,-3302.51 2920.07,-3308.51 2914.07,-3314.51 2908.07,-3314.51"/>
<text xml:space="preserve" text-anchor="middle" x="2864.44" y="-3301.95" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Kreisel3 &#160;[0010&#45;R]</text>
<text xml:space="preserve" text-anchor="middle" x="2864.44" y="-3289.2" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Bahn R (rechts)</text>
<text xml:space="preserve" text-anchor="middle" x="2864.44" y="-3276.45" font-family="Segoe UI" font-size="9.00" fill="#ffffff">h = 1.940 m</text>
<text xml:space="preserve" text-anchor="middle" x="2864.44" y="-3263.7" font-family="Segoe UI" font-size="9.00" fill="#ffffff">Separator (2): 0063, 0064</text>
</g>
<!-- 0010&#45;R&#45;&gt;0010&#45;L -->
<g id="edge3" class="edge">
<title>0010&#45;R&#45;&gt;0010&#45;L</title>
<g id="a_edge3"><a xlink:title="Kreisel&#45;Umlauf UZS">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M2864.44,-3314.69C2864.44,-3345.48 2864.44,-3393.57 2864.44,-3425.9"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="2861.3,-3425.81 2864.45,-3434.81 2867.6,-3425.81 2861.3,-3425.81"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2857.32" y="-3379.44" font-family="Segoe UI" font-size="8.00" fill="#5b7fc7">UZS</text>
</g>
<!-- 0013&#45;&gt;0010&#45;L -->
<g id="edge51" class="edge">
<title>0013&#45;&gt;0010&#45;L</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1642.83,-5631.18C1791.09,-5367.68 2706.66,-3740.44 2846.53,-3491.85"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="2848.8,-3493.52 2850.29,-3485.17 2843.92,-3490.77 2848.8,-3493.52"/>
</g>
<!-- 0014&#45;&gt;0010&#45;L -->
<g id="edge49" class="edge">
<title>0014&#45;&gt;0010&#45;L</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1645.05,-1146.6C1800.36,-1441.25 2711.82,-3170.45 2847.55,-3427.95"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="2844.99,-3429.1 2851.2,-3434.87 2849.94,-3426.49 2844.99,-3429.1"/>
</g>
<!-- 0016&#45;&gt;0002&#45;R -->
<g id="edge47" class="edge">
<title>0016&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M236.24,-3731.59C248.24,-3664.1 275.3,-3529.18 316.37,-3420.43 330.4,-3383.28 352.17,-3343.24 367.83,-3316.41"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="370.1,-3318.06 371.75,-3309.75 365.28,-3315.22 370.1,-3318.06"/>
</g>
<!-- 0017&#45;&gt;0002&#45;R -->
<g id="edge45" class="edge">
<title>0017&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1606.26,-5255.65C1452.33,-5006.88 550.95,-3550.07 406.48,-3316.57"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="409.02,-3315.35 402.43,-3310.02 404.25,-3318.3 409.02,-3315.35"/>
</g>
<!-- 0018&#45;&gt;0002&#45;R -->
<g id="edge43" class="edge">
<title>0018&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1603.8,-5055.67C1443.76,-4822.8 558.75,-3535 408.6,-3316.51"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="411.04,-3315.12 404.2,-3310.11 406.42,-3318.29 411.04,-3315.12"/>
</g>
<!-- 0019&#45;&gt;0002&#45;R -->
<g id="edge41" class="edge">
<title>0019&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1600.63,-4855.57C1433.32,-4639.06 566.89,-3517.86 410.99,-3316.12"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="413.34,-3314.58 406.23,-3309.97 408.91,-3318.01 413.34,-3314.58"/>
</g>
<!-- 0020&#45;&gt;0002&#45;R -->
<g id="edge39" class="edge">
<title>0020&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1596.66,-4655.58C1421.17,-4456.74 576.18,-3499.4 413.92,-3315.57"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="416.17,-3313.88 408.78,-3309.74 411.97,-3317.59 416.17,-3313.88"/>
</g>
<!-- 0021&#45;&gt;0002&#45;R -->
<g id="edge37" class="edge">
<title>0021&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1591.44,-4455.65C1406.47,-4275.87 587.15,-3479.59 417.68,-3314.88"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="419.78,-3313.01 412.09,-3309.44 415.88,-3317.03 419.78,-3313.01"/>
</g>
<!-- 0022&#45;&gt;0002&#45;R -->
<g id="edge35" class="edge">
<title>0022&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1584.05,-4255.64C1387.82,-4096.53 602.5,-3459.78 423.42,-3314.58"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="425.23,-3312.44 417.25,-3309.58 421.7,-3316.79 425.23,-3312.44"/>
</g>
<!-- 0023&#45;&gt;0002&#45;R -->
<g id="edge33" class="edge">
<title>0023&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1572.84,-4055.53C1362.67,-3918.98 620.79,-3436.94 431.28,-3313.81"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="432.95,-3311.56 424.72,-3309.55 429.9,-3316.26 432.95,-3311.56"/>
</g>
<!-- 0024&#45;&gt;0002&#45;R -->
<g id="edge31" class="edge">
<title>0024&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1554.19,-3855.4C1324.63,-3743.22 631.34,-3404.43 437.68,-3309.8"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="439.16,-3307.4 430.74,-3306.41 436.7,-3312.43 439.16,-3307.4"/>
</g>
<!-- 0025&#45;&gt;0002&#45;R -->
<g id="edge29" class="edge">
<title>0025&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1550.93,-3666.32C1318.35,-3590.13 631.64,-3365.17 438.19,-3301.79"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="439.28,-3299.2 430.81,-3299.38 437.54,-3304.53 439.28,-3299.2"/>
</g>
<!-- 0026&#45;&gt;0002&#45;R -->
<g id="edge27" class="edge">
<title>0026&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1550.93,-3478.83C1318.58,-3440.14 633,-3325.98 438.77,-3293.63"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="439.23,-3290.87 430.88,-3292.32 438.31,-3296.4 439.23,-3290.87"/>
</g>
<!-- 0027&#45;&gt;0002&#45;R -->
<g id="edge25" class="edge">
<title>0027&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1550.93,-3291.34C1318.58,-3290.08 633,-3286.34 438.77,-3285.29"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="438.92,-3282.49 430.9,-3285.24 438.89,-3288.09 438.92,-3282.49"/>
</g>
<!-- 0028&#45;&gt;0002&#45;R -->
<g id="edge23" class="edge">
<title>0028&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1550.93,-3103.85C1318.58,-3140.01 633,-3246.71 438.77,-3276.94"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="438.35,-3274.17 430.88,-3278.17 439.21,-3279.7 438.35,-3274.17"/>
</g>
<!-- 0029&#45;&gt;0002&#45;R -->
<g id="edge21" class="edge">
<title>0029&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1550.93,-2916.36C1318.35,-2990.02 631.64,-3207.51 438.19,-3268.78"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="437.6,-3266.03 430.82,-3271.11 439.29,-3271.36 437.6,-3266.03"/>
</g>
<!-- 0030&#45;&gt;0002&#45;R -->
<g id="edge19" class="edge">
<title>0030&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1552.8,-2727.98C1321.9,-2838.3 631.24,-3168.29 437.8,-3260.71"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="436.62,-3258.17 430.61,-3264.14 439.03,-3263.22 436.62,-3258.17"/>
</g>
<!-- 0031&#45;&gt;0002&#45;R -->
<g id="edge17" class="edge">
<title>0031&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1572.03,-2527.91C1360.96,-2662.75 621.93,-3134.88 431.81,-3256.34"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="430.47,-3253.88 425.23,-3260.54 433.48,-3258.6 430.47,-3253.88"/>
</g>
<!-- 0032&#45;&gt;0002&#45;R -->
<g id="edge15" class="edge">
<title>0032&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1583.31,-2328C1386.05,-2485.79 603.75,-3111.57 423.93,-3255.42"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="422.23,-3253.19 417.73,-3260.38 425.73,-3257.57 422.23,-3253.19"/>
</g>
<!-- 0033&#45;&gt;0002&#45;R -->
<g id="edge13" class="edge">
<title>0033&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1590.77,-2128.13C1404.65,-2306.98 588.5,-3091.31 418.17,-3255"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="416.37,-3252.84 412.54,-3260.41 420.25,-3256.88 416.37,-3252.84"/>
</g>
<!-- 0034&#45;&gt;0002&#45;R -->
<g id="edge11" class="edge">
<title>0034&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1596.35,-1927.96C1420.24,-2125.57 576.89,-3071.87 414.16,-3254.47"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="412.23,-3252.43 408.99,-3260.27 416.41,-3256.16 412.23,-3252.43"/>
</g>
<!-- 0035&#45;&gt;0002&#45;R -->
<g id="edge9" class="edge">
<title>0035&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1600.34,-1728.04C1432.38,-1943.55 567.62,-3053.17 411.21,-3253.87"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="409.15,-3251.96 406.44,-3259.99 413.57,-3255.4 409.15,-3251.96"/>
</g>
<!-- 0036&#45;&gt;0002&#45;R -->
<g id="edge7" class="edge">
<title>0036&#45;&gt;0002&#45;R</title>
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1599.14,-1534.32C1427.36,-1782.41 557.72,-3038.36 408.69,-3253.6"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="406.4,-3252 404.14,-3260.17 411,-3255.18 406.4,-3252"/>
</g>
<!-- legende -->
<g id="node30" class="node">
<title>legende</title>
<text xml:space="preserve" text-anchor="start" x="12" y="-48.5" font-family="Segoe UI" font-size="9.00">durchgezogen</text>
<text xml:space="preserve" text-anchor="start" x="78.75" y="-48.5" font-family="Segoe UI" font-size="9.00">Materialfluss</text>
<text xml:space="preserve" text-anchor="start" x="12" y="-29.75" font-family="Segoe UI" font-size="9.00" fill="#8ea9db">gestrichelt blau</text>
<text xml:space="preserve" text-anchor="start" x="78.75" y="-29.75" font-family="Segoe UI" font-size="9.00">Kreisel&#45;Umlauf, Richtung aus Drehrichtung (UZS = rechts nach links, GUZ = links nach rechts)</text>
<text xml:space="preserve" text-anchor="start" x="12" y="-11" font-family="Segoe UI" font-size="9.00" fill="#bf8f00">gestrichelt gelb</text>
<text xml:space="preserve" text-anchor="start" x="78.75" y="-11" font-family="Segoe UI" font-size="9.00">Richtung nicht bestimmbar</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 58 KiB

@@ -0,0 +1,145 @@
<!-- erzeugt von lib/material_flow.py -->
# Materialfluss - export.csv
**Erzeugt:** 2026-08-07 13:20 von `lib/material_flow.py`
**Quelle:** `C:\develop\sps_skel\data\export.csv`
> Materialfluss der mechanischen Objekte. Die daraus abgeleiteten Steuerungsobjekte (TROs) dokumentiert `lib/tro_flow.py --doc`.
## 1. Kurzfassung
| | |
|---|---|
| Flussknoten | 29 (Gefaellestrecke: 20, Kreisel: 6, Strecke: 3) |
| Flusskanten | 46 gerichtet, 3 Kreisel-Umlauf, 2 Richtung unbestimmt |
| Anbauteile | Scanner: 6, Separator: 33 |
| Plausibilitaetshinweise | 7 |
Graph: [`export_material_flow.dot`](export_material_flow.dot) - [`export_material_flow.svg`](export_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.
## 2. Anlagenobjekte
| Knoten | Art | Bezeichnung | Planquadrat | Hoehe [m] | Separatoren | Scanner |
|---|---|---|---|---|---|---|
| `0001-L` | Kreisel | Kreisel :3 / Kreisel1 (Bahn L) | A/1 | 1.470 | - | - |
| `0001-R` | Kreisel | Kreisel :3 / Kreisel1 (Bahn R) | A/1 | 1.470 | 0061 | - |
| `0002-L` | Kreisel | Kreisel :2 / Kreisel2 (Bahn L) | A/1 | 1.470 | 0003, 0011, 0012 | 0004, 0007, 0009 |
| `0002-R` | Kreisel | Kreisel :2 / Kreisel2 (Bahn R) | A/1 | 1.470 | - | - |
| `0010-L` | Kreisel | Kreisel :1 / Kreisel3 (Bahn L) | F/1 | 1.940 | - | - |
| `0010-R` | Kreisel | Kreisel :1 / Kreisel3 (Bahn R) | F/1 | 1.940 | 0063, 0064 | - |
| `0013` | Strecke | VarioFoerderer :3 | A/10 | 0.237 | 0015, 0062 | - |
| `0014` | Strecke | VarioFoerderer :2 | A/1 | 0.237 | 0057, 0065 | 0006 |
| `0016` | Strecke | VarioFoerderer :1 | A/4 | 0.000 | 0058, 0059 | 0060 |
| `0017` | Gefaellestrecke | Gefaellestrecke :20 | F/10 | 1.941 | 0037 | - |
| `0018` | Gefaellestrecke | Gefaellestrecke :19 | F/9 | 1.941 | 0038 | - |
| `0019` | Gefaellestrecke | Gefaellestrecke :18 | F/9 | 1.941 | 0039 | - |
| `0020` | Gefaellestrecke | Gefaellestrecke :17 | F/8 | 1.941 | 0040 | - |
| `0021` | Gefaellestrecke | Gefaellestrecke :16 | F/8 | 1.941 | 0041 | - |
| `0022` | Gefaellestrecke | Gefaellestrecke :15 | F/8 | 1.941 | 0042 | - |
| `0023` | Gefaellestrecke | Gefaellestrecke :14 | F/7 | 1.941 | 0043 | - |
| `0024` | Gefaellestrecke | Gefaellestrecke :13 | F/7 | 1.941 | 0044 | - |
| `0025` | Gefaellestrecke | Gefaellestrecke :12 | F/6 | 1.941 | 0045 | - |
| `0026` | Gefaellestrecke | Gefaellestrecke :11 | F/6 | 1.941 | 0046 | - |
| `0027` | Gefaellestrecke | Gefaellestrecke :10 | F/6 | 1.941 | 0047 | - |
| `0028` | Gefaellestrecke | Gefaellestrecke :9 | F/5 | 1.941 | 0048 | - |
| `0029` | Gefaellestrecke | Gefaellestrecke :8 | F/5 | 1.941 | 0049 | - |
| `0030` | Gefaellestrecke | Gefaellestrecke :7 | F/4 | 1.941 | 0050 | - |
| `0031` | Gefaellestrecke | Gefaellestrecke :6 | F/4 | 1.941 | 0051 | - |
| `0032` | Gefaellestrecke | Gefaellestrecke :5 | F/4 | 1.941 | 0052 | - |
| `0033` | Gefaellestrecke | Gefaellestrecke :4 | F/3 | 1.941 | 0053 | - |
| `0034` | Gefaellestrecke | Gefaellestrecke :3 | F/3 | 1.941 | 0054 | - |
| `0035` | Gefaellestrecke | Gefaellestrecke :2 | F/2 | 1.941 | 0055 | - |
| `0036` | Gefaellestrecke | Gefaellestrecke :1 | F/2 | 1.941 | 0005, 0056 | 0066 |
Bezeichnung: `Bezeichnung` aus dem CSV; wo das Merkmal `Name` abweicht, steht es dahinter (die beiden Nummerierungen laufen nicht zwangslaeufig gleich).
Hoehe: bei Kreiseln aus dem Merkmal `Höhe in m`, bei Gefaellestrecke und Strecke aus `Montagehoehe_m` - unveraendert aus dem CSV uebernommen. Fuer die Richtungsableitung werden nur die Hoehen der **Nachbarn** verglichen, nie die des Transportobjekts selbst.
**Anbauteile ohne Zuordnung**
- Scanner 0008 'Scanner :4' (Zuordnung: `nicht zugeordnet`)
## 3. Verbindungen
| von | nach | Art |
|---|---|---|
| `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 |
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.
## 4. Plausibilitaetshinweise
Abweichungen zwischen den Merkmalen des CSV-Exports und den tatsaechlich vorhandenen Objekten.
- Gefaellestrecke 0036 'Gefaellestrecke :1': Merkmal 'Anzahl_Scanner' = 0, tatsaechlich 1 Scanner
- Gefaellestrecke 0036 'Gefaellestrecke :1': Merkmal 'Anzahl_Separator' = 1, tatsaechlich 2 Separator
- Kreisel 0001 'Kreisel :3': Merkmal 'Anzahl der Separatoren' = 2, tatsaechlich 1 Separator
- Kreisel 0002 'Kreisel :2': Merkmal 'Anzahl der Scanner' = 0, tatsaechlich 3 Scanner
- Kreisel 0002 'Kreisel :2': Merkmal 'Anzahl der Separatoren' = 2, tatsaechlich 3 Separator
- Strecke 0014 'VarioFoerderer :2': Merkmal 'Anzahl_Scanner' = 0, tatsaechlich 1 Scanner
- Strecke 0016 'VarioFoerderer :1': Merkmal 'Anzahl_Scanner' = 0, tatsaechlich 1 Scanner
## 5. Warnungen des Laufs
- Zeile 52 (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
- Verbindung 0002-R <-> 0016: Richtung nicht bestimmbar (kein Transportobjekt beteiligt) - beidseitig gezeichnet
- Strecke 0016: Ein- und Ausschleusung am selben Kreisel 0002 (Ruecklauf) - Bahnen 0002-L/0002-R angenommen
- Kante 0001-L <-> 0014: Kreisel<->Strecke ohne Transferelement - als Bounding-Box-Falschtreffer verworfen
- Bahn 0001-R (Separatoren 0061): keine ankommende Verbindung - fuer das Material nicht erreichbar
- Bahn 0010-R (Separatoren 0063, 0064): keine ankommende Verbindung - fuer das Material nicht erreichbar
+431
View File
@@ -0,0 +1,431 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 15.1.0 (20260618.0150)
-->
<!-- Title: TROFluss Pages: 1 -->
<svg width="3852pt" height="5897pt"
viewBox="0.00 0.00 3852.00 5897.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 5892.89)">
<title>TROFluss</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-5892.89 3848.25,-5892.89 3848.25,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="1922.12" y="-5874.44" font-family="Segoe UI" font-size="11.00">TRO&#45;Fluss lagerichtig (0.25 pt/mm) &#45; export.csv</text>
<!-- TRO01&#45;&gt;TRO02 -->
<g id="edge1" class="edge">
<title>TRO01&#45;&gt;TRO02</title>
<g id="a_edge1"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M2473.07,-900.25C2346.54,-1350.49 1254.4,-5236.7 1107.56,-5759.24"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1104.92,-5758.28 1105.45,-5766.74 1110.31,-5759.79 1104.92,-5758.28"/>
</a>
</g>
</g>
<!-- TRO01&#45;&gt;TRO03 -->
<g id="edge2" class="edge">
<title>TRO01&#45;&gt;TRO03</title>
<g id="a_edge2"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M2441,-900.36C2236.51,-1098.32 1379.47,-1927.99 1148.69,-2151.4"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1146.83,-2149.31 1143.03,-2156.88 1150.72,-2153.33 1146.83,-2149.31"/>
</a>
</g>
</g>
<!-- TRO01&#45;&gt;TRO04 -->
<g id="edge3" class="edge">
<title>TRO01&#45;&gt;TRO04</title>
<g id="a_edge3"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M2409.09,-890.51C2168.95,-994.13 1426.97,-1314.27 1175.55,-1422.76"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1174.72,-1420.06 1168.49,-1425.8 1176.94,-1425.21 1174.72,-1420.06"/>
</a>
</g>
</g>
<!-- TRO01&#45;&gt;TRO07 -->
<g id="edge4" class="edge">
<title>TRO01&#45;&gt;TRO07</title>
<g id="a_edge4"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2493.44,-900.48C2581.69,-1348.03 3335.54,-5170.8 3432.51,-5662.51"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="3429.7,-5662.76 3434,-5670.07 3435.2,-5661.68 3429.7,-5662.76"/>
</a>
</g>
</g>
<!-- TRO01&#45;&gt;TRO08 -->
<g id="edge5" class="edge">
<title>TRO01&#45;&gt;TRO08</title>
<g id="a_edge5"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2527.35,-900.37C2599.94,-973.46 2748.13,-1122.65 2828.82,-1203.87"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="2826.55,-1205.57 2834.18,-1209.27 2830.53,-1201.62 2826.55,-1205.57"/>
</a>
</g>
</g>
<!-- TRO02&#45;&gt;TRO03 -->
<g id="edge25" class="edge">
<title>TRO02&#45;&gt;TRO03</title>
<g id="a_edge25"><a xlink:title="gleiche Bahn">
<path fill="none" stroke="#7f7f7f" stroke-width="1.2" stroke-dasharray="1,5" d="M1091.25,-5768.28C1091.25,-5369.14 1091.25,-2655.36 1091.25,-2256.04"/>
</a>
</g>
</g>
<!-- TRO02&#45;&gt;TRO04 -->
<g id="edge26" class="edge">
<title>TRO02&#45;&gt;TRO04</title>
<g id="a_edge26"><a xlink:title="gleiche Bahn">
<path fill="none" stroke="#7f7f7f" stroke-width="1.2" stroke-dasharray="1,5" d="M1092.74,-5768.38C1102.22,-5454.9 1154.18,-3694.58 1170.08,-2259.33 1170.59,-2212.83 1172.7,-2201.12 1170.08,-2154.68 1156.3,-1910.8 1115.56,-1621.1 1098.69,-1507.88"/>
</a>
</g>
</g>
<!-- TRO02&#45;&gt;TRO09 -->
<g id="edge6" class="edge">
<title>TRO02&#45;&gt;TRO09</title>
<g id="a_edge6"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1087.12,-5768.56C1059.71,-5524.2 911.88,-4432.84 870.34,-4167.47"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="873.15,-4167.27 869.14,-4159.8 867.62,-4168.14 873.15,-4167.27"/>
</a>
</g>
</g>
<!-- TRO03&#45;&gt;TRO04 -->
<g id="edge27" class="edge">
<title>TRO03&#45;&gt;TRO04</title>
<g id="a_edge27"><a xlink:title="gleiche Bahn">
<path fill="none" stroke="#7f7f7f" stroke-width="1.2" stroke-dasharray="1,5" d="M1091.25,-2157.94C1091.25,-2022.27 1091.25,-1643.2 1091.25,-1507.89"/>
</a>
</g>
</g>
<!-- TRO03&#45;&gt;TRO09 -->
<g id="edge7" class="edge">
<title>TRO03&#45;&gt;TRO09</title>
<g id="a_edge7"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1082.98,-2255.66C1045.15,-2518.3 893.28,-3765.59 864.29,-4050.93"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="861.55,-4050.23 863.54,-4058.47 867.12,-4050.79 861.55,-4050.23"/>
</a>
</g>
</g>
<!-- TRO04&#45;&gt;TRO09 -->
<g id="edge8" class="edge">
<title>TRO04&#45;&gt;TRO09</title>
<g id="a_edge8"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1015.98,-1493.2C1007.3,-1497.76 1000.96,-1501.65 999.42,-1503.98 990.31,-1517.73 881.98,-3666.6 862.69,-4050.67"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="859.9,-4050.26 862.3,-4058.39 865.5,-4050.54 859.9,-4050.26"/>
</a>
</g>
</g>
<!-- TRO05&#45;&gt;TRO06 -->
<g id="edge28" class="edge">
<title>TRO05&#45;&gt;TRO06</title>
<g id="a_edge28"><a xlink:title="gleiche Bahn">
<path fill="none" stroke="#7f7f7f" stroke-width="1.2" stroke-dasharray="1,5" d="M3755.85,-2245.06C3749.26,-2270.17 3741.31,-2300.43 3734.97,-2324.56"/>
</a>
</g>
</g>
<!-- TRO05&#45;&gt;TRO10 -->
<g id="edge9" class="edge">
<title>TRO05&#45;&gt;TRO10</title>
<g id="a_edge9"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M3693.08,-2231.42C3347.86,-2393.07 1921.37,-3061 1506.91,-3255.06"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1505.9,-3252.44 1499.85,-3258.37 1508.28,-3257.51 1505.9,-3252.44"/>
</a>
</g>
</g>
<!-- TRO06&#45;&gt;TRO10 -->
<g id="edge10" class="edge">
<title>TRO06&#45;&gt;TRO10</title>
<g id="a_edge10"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M3647.87,-2397.88C3309.47,-2534.52 1936.18,-3089.04 1516.16,-3258.64"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1515.37,-3255.93 1509,-3261.53 1517.47,-3261.13 1515.37,-3255.93"/>
</a>
</g>
</g>
<!-- TRO07&#45;&gt;TRO10 -->
<g id="edge11" class="edge">
<title>TRO07&#45;&gt;TRO10</title>
<g id="a_edge11"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M3409.09,-5672C3170.24,-5381.78 1733.51,-3694.53 1444.22,-3364.23"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1446.59,-3362.67 1439.21,-3358.5 1442.37,-3366.37 1446.59,-3362.67"/>
</a>
</g>
</g>
<!-- TRO08&#45;&gt;TRO10 -->
<g id="edge12" class="edge">
<title>TRO08&#45;&gt;TRO10</title>
<g id="a_edge12"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M2845.65,-1308.63C2636.53,-1585.96 1639.83,-2955.65 1431.65,-3251.69"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1429.65,-3249.67 1427.34,-3257.82 1434.23,-3252.89 1429.65,-3249.67"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO01 -->
<g id="edge13" class="edge">
<title>TRO09&#45;&gt;TRO01</title>
<g id="a_edge13"><a xlink:title="Weiche (Kreisel&#45;Uebergang)">
<path fill="none" stroke="#cc8800" stroke-width="1.8" d="M883.26,-4060.48C946.37,-3929.91 1124.95,-3561.19 1276.43,-3255.84 1744.9,-2311.57 2328.58,-1164.71 2458.56,-909.63"/>
<polygon fill="#cc8800" stroke="#cc8800" stroke-width="1.8" points="2460.97,-911.06 2462.11,-902.66 2455.98,-908.52 2460.97,-911.06"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="1652.3" y="-2480.01" font-family="Segoe UI" font-size="8.00" fill="#cc8800">Weiche</text>
</g>
<!-- TRO09&#45;&gt;TRO02 -->
<g id="edge14" class="edge">
<title>TRO09&#45;&gt;TRO02</title>
<g id="a_edge14"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M863.88,-4157.87C891.29,-4402.23 1039.12,-5493.59 1080.66,-5758.96"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1077.85,-5759.16 1081.86,-5766.63 1083.38,-5758.29 1077.85,-5759.16"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO03 -->
<g id="edge15" class="edge">
<title>TRO09&#45;&gt;TRO03</title>
<g id="a_edge15"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M868.02,-4060.52C905.85,-3797.88 1057.72,-2550.59 1086.71,-2265.25"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1089.45,-2265.95 1087.46,-2257.71 1083.88,-2265.39 1089.45,-2265.95"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO04 -->
<g id="edge16" class="edge">
<title>TRO09&#45;&gt;TRO04</title>
<g id="a_edge16"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M862.49,-4060.07C881.68,-3716.07 995.95,-1672.6 1014.3,-1516.27"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1017,-1517.07 1015.57,-1508.71 1011.48,-1516.13 1017,-1517.07"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO07 -->
<g id="edge17" class="edge">
<title>TRO09&#45;&gt;TRO07</title>
<g id="a_edge17"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M920.74,-4147.07C1269.14,-4363.61 3004.98,-5442.43 3373.8,-5671.65"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="3372.18,-5673.94 3380.46,-5675.79 3375.14,-5669.19 3372.18,-5673.94"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO08 -->
<g id="edge18" class="edge">
<title>TRO09&#45;&gt;TRO08</title>
<g id="a_edge18"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M906.4,-4060.33C1016.85,-3943.71 1300.78,-3638 1510.59,-3360.48 2096.53,-2585.4 2703.77,-1566.76 2850.72,-1316.79"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="2852.99,-1318.45 2854.63,-1310.13 2848.16,-1315.61 2852.99,-1318.45"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO01 -->
<g id="edge19" class="edge">
<title>TRO10&#45;&gt;TRO01</title>
<g id="a_edge19"><a xlink:title="Weiche (Kreisel&#45;Uebergang)">
<path fill="none" stroke="#cc8800" stroke-width="1.8" d="M1415.31,-3259.21C1554.56,-2946.6 2318.17,-1232.31 2461.73,-910"/>
<polygon fill="#cc8800" stroke="#cc8800" stroke-width="1.8" points="2464.19,-911.37 2464.89,-902.93 2459.07,-909.1 2464.19,-911.37"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="1927.05" y="-2083.71" font-family="Segoe UI" font-size="8.00" fill="#cc8800">Weiche</text>
</g>
<!-- TRO10&#45;&gt;TRO02 -->
<g id="edge20" class="edge">
<title>TRO10&#45;&gt;TRO02</title>
<g id="a_edge20"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M1387.61,-3357.16C1349.6,-3672.66 1139.61,-5415.85 1098.3,-5758.71"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1095.55,-5758.13 1097.38,-5766.4 1101.11,-5758.8 1095.55,-5758.13"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO03 -->
<g id="edge21" class="edge">
<title>TRO10&#45;&gt;TRO03</title>
<g id="a_edge21"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M1380.03,-3259.07C1330.77,-3079.61 1160.57,-2459.55 1107.31,-2265.52"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1110.01,-2264.79 1105.2,-2257.82 1104.61,-2266.27 1110.01,-2264.79"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO04 -->
<g id="edge22" class="edge">
<title>TRO10&#45;&gt;TRO04</title>
<g id="a_edge22"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M1385.5,-3259.18C1343.27,-3000.81 1146.58,-1797.59 1100.79,-1517.5"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1103.57,-1517.17 1099.52,-1509.72 1098.05,-1518.07 1103.57,-1517.17"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO07 -->
<g id="edge23" class="edge">
<title>TRO10&#45;&gt;TRO07</title>
<g id="a_edge23"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1432.41,-3357.03C1687.95,-3666.69 3123.8,-5352.79 3397.65,-5664.84"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="3395.32,-5666.43 3402.7,-5670.59 3399.53,-5662.73 3395.32,-5666.43"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO08 -->
<g id="edge24" class="edge">
<title>TRO10&#45;&gt;TRO08</title>
<g id="a_edge24"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1432.02,-3259.12C1641.14,-2981.79 2637.84,-1612.1 2846.02,-1316.06"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="2848.02,-1318.08 2850.33,-1309.93 2843.44,-1314.86 2848.02,-1318.08"/>
</a>
</g>
</g>
<!-- TRO01 -->
<g id="node1" class="node">
<title>TRO01</title>
<path fill="#d4e8ff" stroke="#3388cc" d="M2548.51,-900C2548.51,-900 2421.51,-900 2421.51,-900 2415.51,-900 2409.51,-894 2409.51,-888 2409.51,-888 2409.51,-827.5 2409.51,-827.5 2409.51,-821.5 2415.51,-815.5 2421.51,-815.5 2421.51,-815.5 2548.51,-815.5 2548.51,-815.5 2554.51,-815.5 2560.51,-821.5 2560.51,-827.5 2560.51,-827.5 2560.51,-888 2560.51,-888 2560.51,-894 2554.51,-900 2548.51,-900"/>
<text xml:space="preserve" text-anchor="middle" x="2485.01" y="-887.45" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO01 &#160;(1Sep)</text>
<text xml:space="preserve" text-anchor="middle" x="2485.01" y="-874.7" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0061 auf Kreisel1 Bahn R</text>
<text xml:space="preserve" text-anchor="middle" x="2485.01" y="-861.95" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep</text>
<text xml:space="preserve" text-anchor="middle" x="2485.01" y="-849.2" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator</text>
<text xml:space="preserve" text-anchor="middle" x="2485.01" y="-836.45" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0061</text>
<text xml:space="preserve" text-anchor="middle" x="2485.01" y="-823.7" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 5355, &#45;5878</text>
</g>
<!-- TRO02 -->
<g id="node2" class="node">
<title>TRO02</title>
<path fill="#d4e8ff" stroke="#3388cc" d="M1154.38,-5865.89C1154.38,-5865.89 1028.12,-5865.89 1028.12,-5865.89 1022.12,-5865.89 1016.12,-5859.89 1016.12,-5853.89 1016.12,-5853.89 1016.12,-5780.64 1016.12,-5780.64 1016.12,-5774.64 1022.12,-5768.64 1028.12,-5768.64 1028.12,-5768.64 1154.38,-5768.64 1154.38,-5768.64 1160.38,-5768.64 1166.38,-5774.64 1166.38,-5780.64 1166.38,-5780.64 1166.38,-5853.89 1166.38,-5853.89 1166.38,-5859.89 1160.38,-5865.89 1154.38,-5865.89"/>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-5853.34" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO02 &#160;(1Sep)</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-5840.59" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0003 auf Kreisel2 Bahn L</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-5827.84" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-5815.09" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-5802.34" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0003</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-5789.59" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0004</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-5776.84" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: &#45;220, 13960</text>
</g>
<!-- TRO03 -->
<g id="node3" class="node">
<title>TRO03</title>
<path fill="#ffe4a0" stroke="#cc8800" d="M1154.38,-2255.63C1154.38,-2255.63 1028.12,-2255.63 1028.12,-2255.63 1022.12,-2255.63 1016.12,-2249.63 1016.12,-2243.63 1016.12,-2243.63 1016.12,-2170.38 1016.12,-2170.38 1016.12,-2164.38 1022.12,-2158.38 1028.12,-2158.38 1028.12,-2158.38 1154.38,-2158.38 1154.38,-2158.38 1160.38,-2158.38 1166.38,-2164.38 1166.38,-2170.38 1166.38,-2170.38 1166.38,-2243.63 1166.38,-2243.63 1166.38,-2249.63 1160.38,-2255.63 1154.38,-2255.63"/>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-2243.08" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO03 &#160;(1Sep1Swi)</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-2230.33" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0011 auf Kreisel2 Bahn L</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-2217.58" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep1Swi</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-2204.83" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x Switch</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-2192.08" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0011</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-2179.33" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0009</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-2166.58" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: &#45;220, &#45;481</text>
</g>
<!-- TRO04 -->
<g id="node4" class="node">
<title>TRO04</title>
<path fill="#ffd4d4" stroke="#cc3333" d="M1154.38,-1507.75C1154.38,-1507.75 1028.12,-1507.75 1028.12,-1507.75 1022.12,-1507.75 1016.12,-1501.75 1016.12,-1495.75 1016.12,-1495.75 1016.12,-1422.5 1016.12,-1422.5 1016.12,-1416.5 1022.12,-1410.5 1028.12,-1410.5 1028.12,-1410.5 1154.38,-1410.5 1154.38,-1410.5 1160.38,-1410.5 1166.38,-1416.5 1166.38,-1422.5 1166.38,-1422.5 1166.38,-1495.75 1166.38,-1495.75 1166.38,-1501.75 1160.38,-1507.75 1154.38,-1507.75"/>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-1495.2" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO04 &#160;(1Sep_SSCC)</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-1482.45" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0012 auf Kreisel2 Bahn L</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-1469.7" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep_SSCC</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-1456.95" font-family="Segoe UI" font-size="9.00" fill="#111111">1x SSCC, 1x Scanner, 1x Separator</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-1444.2" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0012</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-1431.45" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0007</text>
<text xml:space="preserve" text-anchor="middle" x="1091.25" y="-1418.7" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: &#45;220, &#45;3472</text>
</g>
<!-- TRO05 -->
<g id="node5" class="node">
<title>TRO05</title>
<path fill="#ffe4a0" stroke="#cc8800" d="M3832.25,-2244.61C3832.25,-2244.61 3705.25,-2244.61 3705.25,-2244.61 3699.25,-2244.61 3693.25,-2238.61 3693.25,-2232.61 3693.25,-2232.61 3693.25,-2159.36 3693.25,-2159.36 3693.25,-2153.36 3699.25,-2147.36 3705.25,-2147.36 3705.25,-2147.36 3832.25,-2147.36 3832.25,-2147.36 3838.25,-2147.36 3844.25,-2153.36 3844.25,-2159.36 3844.25,-2159.36 3844.25,-2232.61 3844.25,-2232.61 3844.25,-2238.61 3838.25,-2244.61 3832.25,-2244.61"/>
<text xml:space="preserve" text-anchor="middle" x="3768.75" y="-2232.06" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO05 &#160;(1Sep1Swi)</text>
<text xml:space="preserve" text-anchor="middle" x="3768.75" y="-2219.31" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0063 auf Kreisel3 Bahn R</text>
<text xml:space="preserve" text-anchor="middle" x="3768.75" y="-2206.56" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep1Swi</text>
<text xml:space="preserve" text-anchor="middle" x="3768.75" y="-2193.81" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x Switch</text>
<text xml:space="preserve" text-anchor="middle" x="3768.75" y="-2181.06" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0063</text>
<text xml:space="preserve" text-anchor="middle" x="3768.75" y="-2168.31" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0008</text>
<text xml:space="preserve" text-anchor="middle" x="3768.75" y="-2155.56" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 10490, &#45;525</text>
</g>
<!-- TRO06 -->
<g id="node6" class="node">
<title>TRO06</title>
<path fill="#d4e8ff" stroke="#3388cc" d="M3787.25,-2409.49C3787.25,-2409.49 3660.25,-2409.49 3660.25,-2409.49 3654.25,-2409.49 3648.25,-2403.49 3648.25,-2397.49 3648.25,-2397.49 3648.25,-2336.99 3648.25,-2336.99 3648.25,-2330.99 3654.25,-2324.99 3660.25,-2324.99 3660.25,-2324.99 3787.25,-2324.99 3787.25,-2324.99 3793.25,-2324.99 3799.25,-2330.99 3799.25,-2336.99 3799.25,-2336.99 3799.25,-2397.49 3799.25,-2397.49 3799.25,-2403.49 3793.25,-2409.49 3787.25,-2409.49"/>
<text xml:space="preserve" text-anchor="middle" x="3723.75" y="-2396.94" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO06 &#160;(1Sep)</text>
<text xml:space="preserve" text-anchor="middle" x="3723.75" y="-2384.19" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0064 auf Kreisel3 Bahn R</text>
<text xml:space="preserve" text-anchor="middle" x="3723.75" y="-2371.44" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep</text>
<text xml:space="preserve" text-anchor="middle" x="3723.75" y="-2358.69" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator</text>
<text xml:space="preserve" text-anchor="middle" x="3723.75" y="-2345.94" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0064</text>
<text xml:space="preserve" text-anchor="middle" x="3723.75" y="-2333.19" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 10310, 160</text>
</g>
<!-- TRO07 -->
<g id="node7" class="node">
<title>TRO07</title>
<path fill="#e8d4f0" stroke="#883388" d="M3491.26,-5756.76C3491.26,-5756.76 3394.26,-5756.76 3394.26,-5756.76 3388.26,-5756.76 3382.26,-5750.76 3382.26,-5744.76 3382.26,-5744.76 3382.26,-5684.26 3382.26,-5684.26 3382.26,-5678.26 3388.26,-5672.26 3394.26,-5672.26 3394.26,-5672.26 3491.26,-5672.26 3491.26,-5672.26 3497.26,-5672.26 3503.26,-5678.26 3503.26,-5684.26 3503.26,-5684.26 3503.26,-5744.76 3503.26,-5744.76 3503.26,-5750.76 3497.26,-5756.76 3491.26,-5756.76"/>
<text xml:space="preserve" text-anchor="middle" x="3442.76" y="-5744.21" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO07 &#160;(Vario)</text>
<text xml:space="preserve" text-anchor="middle" x="3442.76" y="-5731.46" font-family="Segoe UI" font-size="9.00" fill="#111111">VarioFoerderer :3</text>
<text xml:space="preserve" text-anchor="middle" x="3442.76" y="-5718.71" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_Vario</text>
<text xml:space="preserve" text-anchor="middle" x="3442.76" y="-5705.96" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x VarioDrive</text>
<text xml:space="preserve" text-anchor="middle" x="3442.76" y="-5693.21" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0015, 0062</text>
<text xml:space="preserve" text-anchor="middle" x="3442.76" y="-5680.46" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 9186, 13549</text>
</g>
<!-- TRO08 -->
<g id="node8" class="node">
<title>TRO08</title>
<path fill="#e8d4f0" stroke="#883388" d="M2932.66,-1308.21C2932.66,-1308.21 2835.66,-1308.21 2835.66,-1308.21 2829.66,-1308.21 2823.66,-1302.21 2823.66,-1296.21 2823.66,-1296.21 2823.66,-1222.96 2823.66,-1222.96 2823.66,-1216.96 2829.66,-1210.96 2835.66,-1210.96 2835.66,-1210.96 2932.66,-1210.96 2932.66,-1210.96 2938.66,-1210.96 2944.66,-1216.96 2944.66,-1222.96 2944.66,-1222.96 2944.66,-1296.21 2944.66,-1296.21 2944.66,-1302.21 2938.66,-1308.21 2932.66,-1308.21"/>
<text xml:space="preserve" text-anchor="middle" x="2884.16" y="-1295.66" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO08 &#160;(Vario)</text>
<text xml:space="preserve" text-anchor="middle" x="2884.16" y="-1282.91" font-family="Segoe UI" font-size="9.00" fill="#111111">VarioFoerderer :2</text>
<text xml:space="preserve" text-anchor="middle" x="2884.16" y="-1270.16" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_Vario</text>
<text xml:space="preserve" text-anchor="middle" x="2884.16" y="-1257.41" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x VarioDrive</text>
<text xml:space="preserve" text-anchor="middle" x="2884.16" y="-1244.66" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0057, 0065</text>
<text xml:space="preserve" text-anchor="middle" x="2884.16" y="-1231.91" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0066</text>
<text xml:space="preserve" text-anchor="middle" x="2884.16" y="-1219.16" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 6952, &#45;4271</text>
</g>
<!-- TRO09 -->
<g id="node9" class="node">
<title>TRO09</title>
<path fill="#e8d4f0" stroke="#883388" d="M908.25,-4157.8C908.25,-4157.8 811.25,-4157.8 811.25,-4157.8 805.25,-4157.8 799.25,-4151.8 799.25,-4145.8 799.25,-4145.8 799.25,-4072.55 799.25,-4072.55 799.25,-4066.55 805.25,-4060.55 811.25,-4060.55 811.25,-4060.55 908.25,-4060.55 908.25,-4060.55 914.25,-4060.55 920.25,-4066.55 920.25,-4072.55 920.25,-4072.55 920.25,-4145.8 920.25,-4145.8 920.25,-4151.8 914.25,-4157.8 908.25,-4157.8"/>
<text xml:space="preserve" text-anchor="middle" x="859.75" y="-4145.24" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO09 &#160;(Vario)</text>
<text xml:space="preserve" text-anchor="middle" x="859.75" y="-4132.49" font-family="Segoe UI" font-size="9.00" fill="#111111">VarioFoerderer :1</text>
<text xml:space="preserve" text-anchor="middle" x="859.75" y="-4119.74" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_Vario</text>
<text xml:space="preserve" text-anchor="middle" x="859.75" y="-4106.99" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x VarioDrive</text>
<text xml:space="preserve" text-anchor="middle" x="859.75" y="-4094.24" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0058, 0059</text>
<text xml:space="preserve" text-anchor="middle" x="859.75" y="-4081.49" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0060</text>
<text xml:space="preserve" text-anchor="middle" x="859.75" y="-4068.74" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: &#45;1146, 7128</text>
</g>
<!-- TRO10 -->
<g id="node10" class="node">
<title>TRO10</title>
<path fill="#d4f0d4" stroke="#338833" d="M1494.88,-3356.78C1494.88,-3356.78 1292.13,-3356.78 1292.13,-3356.78 1286.13,-3356.78 1280.13,-3350.78 1280.13,-3344.78 1280.13,-3344.78 1280.13,-3271.53 1280.13,-3271.53 1280.13,-3265.53 1286.13,-3259.53 1292.13,-3259.53 1292.13,-3259.53 1494.88,-3259.53 1494.88,-3259.53 1500.88,-3259.53 1506.88,-3265.53 1506.88,-3271.53 1506.88,-3271.53 1506.88,-3344.78 1506.88,-3344.78 1506.88,-3350.78 1500.88,-3356.78 1494.88,-3356.78"/>
<text xml:space="preserve" text-anchor="middle" x="1393.51" y="-3344.23" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO10 &#160;(PinStore_Auto)</text>
<text xml:space="preserve" text-anchor="middle" x="1393.51" y="-3331.48" font-family="Segoe UI" font-size="9.00" fill="#111111">Speicher (20 Schwerkraftlinien)</text>
<text xml:space="preserve" text-anchor="middle" x="1393.51" y="-3318.73" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_PinStore_Auto</text>
<text xml:space="preserve" text-anchor="middle" x="1393.51" y="-3305.98" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Encoder, 1x Scanner, 1x Separator, 19x StorageLine</text>
<text xml:space="preserve" text-anchor="middle" x="1393.51" y="-3293.23" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0005 … 0056 (21)</text>
<text xml:space="preserve" text-anchor="middle" x="1393.51" y="-3280.48" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0006</text>
<text xml:space="preserve" text-anchor="middle" x="1393.51" y="-3267.73" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 989, 3924</text>
</g>
<!-- legende -->
<g id="node11" class="node">
<title>legende</title>
<polygon fill="#d4e8ff" stroke="none" points="8,-190.75 8,-211.5 76.75,-211.5 76.75,-190.75 8,-190.75"/>
<polygon fill="none" stroke="black" points="8,-190.75 8,-211.5 76.75,-211.5 76.75,-190.75 8,-190.75"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-197.75" font-family="Segoe UI" font-size="9.00"> &#160;sep &#160;</text>
<polygon fill="none" stroke="black" points="76.75,-190.75 76.75,-211.5 211.5,-211.5 211.5,-190.75 76.75,-190.75"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-197.75" font-family="Segoe UI" font-size="9.00">Separator</text>
<polygon fill="#ffe4a0" stroke="none" points="8,-170 8,-190.75 76.75,-190.75 76.75,-170 8,-170"/>
<polygon fill="none" stroke="black" points="8,-170 8,-190.75 76.75,-190.75 76.75,-170 8,-170"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-177" font-family="Segoe UI" font-size="9.00"> &#160;swi &#160;</text>
<polygon fill="none" stroke="black" points="76.75,-170 76.75,-190.75 211.5,-190.75 211.5,-170 76.75,-170"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-177" font-family="Segoe UI" font-size="9.00">Weiche</text>
<polygon fill="#ffd4d4" stroke="none" points="8,-149.25 8,-170 76.75,-170 76.75,-149.25 8,-149.25"/>
<polygon fill="none" stroke="black" points="8,-149.25 8,-170 76.75,-170 76.75,-149.25 8,-149.25"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-156.25" font-family="Segoe UI" font-size="9.00"> &#160;sscc &#160;</text>
<polygon fill="none" stroke="black" points="76.75,-149.25 76.75,-170 211.5,-170 211.5,-149.25 76.75,-149.25"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-156.25" font-family="Segoe UI" font-size="9.00">Scanner (SSCC)</text>
<polygon fill="#e8d4f0" stroke="none" points="8,-128.5 8,-149.25 76.75,-149.25 76.75,-128.5 8,-128.5"/>
<polygon fill="none" stroke="black" points="8,-128.5 8,-149.25 76.75,-149.25 76.75,-128.5 8,-128.5"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-135.5" font-family="Segoe UI" font-size="9.00"> &#160;vario &#160;</text>
<polygon fill="none" stroke="black" points="76.75,-128.5 76.75,-149.25 211.5,-149.25 211.5,-128.5 76.75,-128.5"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-135.5" font-family="Segoe UI" font-size="9.00">Kettenfoerderer</text>
<polygon fill="#d4f0d4" stroke="none" points="8,-107.75 8,-128.5 76.75,-128.5 76.75,-107.75 8,-107.75"/>
<polygon fill="none" stroke="black" points="8,-107.75 8,-128.5 76.75,-128.5 76.75,-107.75 8,-107.75"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-114.75" font-family="Segoe UI" font-size="9.00"> &#160;store &#160;</text>
<polygon fill="none" stroke="black" points="76.75,-107.75 76.75,-128.5 211.5,-128.5 211.5,-107.75 76.75,-107.75"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-114.75" font-family="Segoe UI" font-size="9.00">Speicher / Puffer</text>
<polygon fill="#f0f0f0" stroke="none" points="8,-87 8,-107.75 76.75,-107.75 76.75,-87 8,-87"/>
<polygon fill="none" stroke="black" points="8,-87 8,-107.75 76.75,-107.75 76.75,-87 8,-87"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-94" font-family="Segoe UI" font-size="9.00"> &#160;ext &#160;</text>
<polygon fill="none" stroke="black" points="76.75,-87 76.75,-107.75 211.5,-107.75 211.5,-87 76.75,-87"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-94" font-family="Segoe UI" font-size="9.00">Systemgrenze / extern</text>
<polygon fill="none" stroke="black" points="8,-66.25 8,-87 76.75,-87 76.75,-66.25 8,-66.25"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-73.25" font-family="Segoe UI" font-size="9.00" fill="#2f5597">durchgezogen</text>
<polygon fill="none" stroke="black" points="76.75,-66.25 76.75,-87 211.5,-87 211.5,-66.25 76.75,-66.25"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-73.25" font-family="Segoe UI" font-size="9.00">Uebergabe / Ein&#45;Ausschleusung</text>
<polygon fill="none" stroke="black" points="8,-45.5 8,-66.25 76.75,-66.25 76.75,-45.5 8,-45.5"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-52.5" font-family="Segoe UI" font-size="9.00" fill="#8ea9db">blau gestrichelt</text>
<polygon fill="none" stroke="black" points="76.75,-45.5 76.75,-66.25 211.5,-66.25 211.5,-45.5 76.75,-45.5"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-52.5" font-family="Segoe UI" font-size="9.00">Kreisel&#45;Umlauf</text>
<polygon fill="none" stroke="black" points="8,-24.75 8,-45.5 76.75,-45.5 76.75,-24.75 8,-24.75"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-31.75" font-family="Segoe UI" font-size="9.00" fill="#cc8800">orange</text>
<polygon fill="none" stroke="black" points="76.75,-24.75 76.75,-45.5 211.5,-45.5 211.5,-24.75 76.75,-24.75"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-31.75" font-family="Segoe UI" font-size="9.00">Weiche (Kreisel&#45;Uebergang)</text>
<polygon fill="none" stroke="black" points="8,-4 8,-24.75 76.75,-24.75 76.75,-4 8,-4"/>
<text xml:space="preserve" text-anchor="start" x="12" y="-11" font-family="Segoe UI" font-size="9.00">grau gepunktet</text>
<polygon fill="none" stroke="black" points="76.75,-4 76.75,-24.75 211.5,-24.75 211.5,-4 76.75,-4"/>
<text xml:space="preserve" text-anchor="start" x="80.75" y="-11" font-family="Segoe UI" font-size="9.00">gleiche Bahn / Strecke</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 34 KiB

@@ -0,0 +1,436 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 15.1.0 (20260618.0150)
-->
<!-- Title: TROFluss Pages: 1 -->
<svg width="1513pt" height="948pt"
viewBox="0.00 0.00 1513.00 948.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 944.32)">
<title>TROFluss</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-944.32 1508.75,-944.32 1508.75,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="752.38" y="-925.87" font-family="Segoe UI" font-size="11.00">TRO&#45;Fluss &#45; export.csv</text>
<g id="clust1" class="cluster">
<title>cluster_legende</title>
<path fill="none" stroke="#a6a6a6" d="M20,-607.32C20,-607.32 231.5,-607.32 231.5,-607.32 237.5,-607.32 243.5,-613.32 243.5,-619.32 243.5,-619.32 243.5,-849.32 243.5,-849.32 243.5,-855.32 237.5,-861.32 231.5,-861.32 231.5,-861.32 20,-861.32 20,-861.32 14,-861.32 8,-855.32 8,-849.32 8,-849.32 8,-619.32 8,-619.32 8,-613.32 14,-607.32 20,-607.32"/>
<text xml:space="preserve" text-anchor="middle" x="125.75" y="-847.82" font-family="Segoe UI" font-size="10.00" fill="#404040">Legende</text>
</g>
<!-- TRO01 -->
<g id="node1" class="node">
<title>TRO01</title>
<path fill="#d4e8ff" stroke="#3388cc" d="M189.25,-590.57C189.25,-590.57 62.25,-590.57 62.25,-590.57 56.25,-590.57 50.25,-584.57 50.25,-578.57 50.25,-578.57 50.25,-518.07 50.25,-518.07 50.25,-512.07 56.25,-506.07 62.25,-506.07 62.25,-506.07 189.25,-506.07 189.25,-506.07 195.25,-506.07 201.25,-512.07 201.25,-518.07 201.25,-518.07 201.25,-578.57 201.25,-578.57 201.25,-584.57 195.25,-590.57 189.25,-590.57"/>
<text xml:space="preserve" text-anchor="middle" x="125.75" y="-578.02" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO01 &#160;(1Sep)</text>
<text xml:space="preserve" text-anchor="middle" x="125.75" y="-565.27" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0061 auf Kreisel1 Bahn R</text>
<text xml:space="preserve" text-anchor="middle" x="125.75" y="-552.52" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep</text>
<text xml:space="preserve" text-anchor="middle" x="125.75" y="-539.77" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator</text>
<text xml:space="preserve" text-anchor="middle" x="125.75" y="-527.02" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0061</text>
<text xml:space="preserve" text-anchor="middle" x="125.75" y="-514.27" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 5355, &#45;5878</text>
</g>
<!-- TRO02 -->
<g id="node2" class="node">
<title>TRO02</title>
<path fill="#d4e8ff" stroke="#3388cc" d="M467.75,-683.94C467.75,-683.94 341.5,-683.94 341.5,-683.94 335.5,-683.94 329.5,-677.94 329.5,-671.94 329.5,-671.94 329.5,-598.69 329.5,-598.69 329.5,-592.69 335.5,-586.69 341.5,-586.69 341.5,-586.69 467.75,-586.69 467.75,-586.69 473.75,-586.69 479.75,-592.69 479.75,-598.69 479.75,-598.69 479.75,-671.94 479.75,-671.94 479.75,-677.94 473.75,-683.94 467.75,-683.94"/>
<text xml:space="preserve" text-anchor="middle" x="404.62" y="-671.39" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO02 &#160;(1Sep)</text>
<text xml:space="preserve" text-anchor="middle" x="404.62" y="-658.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0003 auf Kreisel2 Bahn L</text>
<text xml:space="preserve" text-anchor="middle" x="404.62" y="-645.89" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep</text>
<text xml:space="preserve" text-anchor="middle" x="404.62" y="-633.14" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator</text>
<text xml:space="preserve" text-anchor="middle" x="404.62" y="-620.39" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0003</text>
<text xml:space="preserve" text-anchor="middle" x="404.62" y="-607.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0004</text>
<text xml:space="preserve" text-anchor="middle" x="404.62" y="-594.89" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: &#45;220, 13960</text>
</g>
<!-- TRO01&#45;&gt;TRO02 -->
<g id="edge1" class="edge">
<title>TRO01&#45;&gt;TRO02</title>
<g id="a_edge1"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M201.68,-571.86C238.3,-583.37 282.49,-597.25 320.19,-609.1"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="318.91,-611.63 327.39,-611.36 320.59,-606.29 318.91,-611.63"/>
</a>
</g>
</g>
<!-- TRO03 -->
<g id="node3" class="node">
<title>TRO03</title>
<path fill="#ffe4a0" stroke="#cc8800" d="M1492.75,-851.94C1492.75,-851.94 1366.5,-851.94 1366.5,-851.94 1360.5,-851.94 1354.5,-845.94 1354.5,-839.94 1354.5,-839.94 1354.5,-766.69 1354.5,-766.69 1354.5,-760.69 1360.5,-754.69 1366.5,-754.69 1366.5,-754.69 1492.75,-754.69 1492.75,-754.69 1498.75,-754.69 1504.75,-760.69 1504.75,-766.69 1504.75,-766.69 1504.75,-839.94 1504.75,-839.94 1504.75,-845.94 1498.75,-851.94 1492.75,-851.94"/>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-839.39" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO03 &#160;(1Sep1Swi)</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-826.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0011 auf Kreisel2 Bahn L</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-813.89" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep1Swi</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-801.14" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x Switch</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-788.39" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0011</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-775.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0009</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-762.89" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: &#45;220, &#45;481</text>
</g>
<!-- TRO01&#45;&gt;TRO03 -->
<g id="edge2" class="edge">
<title>TRO01&#45;&gt;TRO03</title>
<g id="a_edge2"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M201.51,-574.64C216.36,-581.9 231.16,-590.75 243.5,-601.32 296.41,-646.61 279.42,-683.91 329.5,-732.32 443.15,-842.18 475.18,-917.32 633.25,-917.32 633.25,-917.32 633.25,-917.32 865.25,-917.32 1042.55,-917.32 1088.25,-905.34 1260.5,-863.32 1288.63,-856.45 1318.63,-846.61 1345.37,-836.87"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1346.07,-839.6 1352.61,-834.2 1344.13,-834.34 1346.07,-839.6"/>
</a>
</g>
</g>
<!-- TRO04 -->
<g id="node4" class="node">
<title>TRO04</title>
<path fill="#ffd4d4" stroke="#cc3333" d="M1492.75,-693.94C1492.75,-693.94 1366.5,-693.94 1366.5,-693.94 1360.5,-693.94 1354.5,-687.94 1354.5,-681.94 1354.5,-681.94 1354.5,-608.69 1354.5,-608.69 1354.5,-602.69 1360.5,-596.69 1366.5,-596.69 1366.5,-596.69 1492.75,-596.69 1492.75,-596.69 1498.75,-596.69 1504.75,-602.69 1504.75,-608.69 1504.75,-608.69 1504.75,-681.94 1504.75,-681.94 1504.75,-687.94 1498.75,-693.94 1492.75,-693.94"/>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-681.39" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO04 &#160;(1Sep_SSCC)</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-668.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0012 auf Kreisel2 Bahn L</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-655.89" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep_SSCC</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-643.14" font-family="Segoe UI" font-size="9.00" fill="#111111">1x SSCC, 1x Scanner, 1x Separator</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-630.39" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0012</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-617.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0007</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-604.89" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: &#45;220, &#45;3472</text>
</g>
<!-- TRO01&#45;&gt;TRO04 -->
<g id="edge3" class="edge">
<title>TRO01&#45;&gt;TRO04</title>
<g id="a_edge3"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M148.76,-505.93C209.61,-395.31 391.66,-106.32 633.25,-106.32 633.25,-106.32 633.25,-106.32 865.25,-106.32 1097.26,-106.32 1098.99,-280.75 1260.5,-447.32 1306.69,-494.96 1311.35,-512.91 1354.5,-563.32 1361.72,-571.74 1369.45,-580.55 1377.11,-589.13"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1374.87,-590.82 1382.29,-594.91 1379.04,-587.09 1374.87,-590.82"/>
</a>
</g>
</g>
<!-- TRO07 -->
<g id="node7" class="node">
<title>TRO07</title>
<path fill="#e8d4f0" stroke="#883388" d="M1478.12,-550.57C1478.12,-550.57 1381.12,-550.57 1381.12,-550.57 1375.12,-550.57 1369.12,-544.57 1369.12,-538.57 1369.12,-538.57 1369.12,-478.07 1369.12,-478.07 1369.12,-472.07 1375.12,-466.07 1381.12,-466.07 1381.12,-466.07 1478.12,-466.07 1478.12,-466.07 1484.12,-466.07 1490.12,-472.07 1490.12,-478.07 1490.12,-478.07 1490.12,-538.57 1490.12,-538.57 1490.12,-544.57 1484.12,-550.57 1478.12,-550.57"/>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-538.02" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO07 &#160;(Vario)</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-525.27" font-family="Segoe UI" font-size="9.00" fill="#111111">VarioFoerderer :3</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-512.52" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_Vario</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-499.77" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x VarioDrive</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-487.02" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0015, 0062</text>
<text xml:space="preserve" text-anchor="middle" x="1429.62" y="-474.27" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 9186, 13549</text>
</g>
<!-- TRO01&#45;&gt;TRO07 -->
<g id="edge4" class="edge">
<title>TRO01&#45;&gt;TRO07</title>
<g id="a_edge4"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M133.01,-505.8C148.68,-414.86 198.23,-200.51 329.5,-87.32 435.49,4.08 493.3,-4.32 633.25,-4.32 633.25,-4.32 633.25,-4.32 865.25,-4.32 1044.75,-4.32 1123.09,28.17 1260.5,-87.32 1374.18,-182.86 1411.62,-366.25 1423.44,-456.1"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1420.66,-456.42 1424.44,-464 1426.22,-455.72 1420.66,-456.42"/>
</a>
</g>
</g>
<!-- TRO08 -->
<g id="node8" class="node">
<title>TRO08</title>
<path fill="#e8d4f0" stroke="#883388" d="M912.75,-626.94C912.75,-626.94 815.75,-626.94 815.75,-626.94 809.75,-626.94 803.75,-620.94 803.75,-614.94 803.75,-614.94 803.75,-541.69 803.75,-541.69 803.75,-535.69 809.75,-529.69 815.75,-529.69 815.75,-529.69 912.75,-529.69 912.75,-529.69 918.75,-529.69 924.75,-535.69 924.75,-541.69 924.75,-541.69 924.75,-614.94 924.75,-614.94 924.75,-620.94 918.75,-626.94 912.75,-626.94"/>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-614.39" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO08 &#160;(Vario)</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-601.64" font-family="Segoe UI" font-size="9.00" fill="#111111">VarioFoerderer :2</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-588.89" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_Vario</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-576.14" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x VarioDrive</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-563.39" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0057, 0065</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-550.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0066</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-537.89" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 6952, &#45;4271</text>
</g>
<!-- TRO01&#45;&gt;TRO08 -->
<g id="edge5" class="edge">
<title>TRO01&#45;&gt;TRO08</title>
<g id="a_edge5"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M201.42,-551.36C344.45,-557.18 655.16,-569.84 793.91,-575.49"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="793.67,-578.28 801.78,-575.81 793.9,-572.69 793.67,-578.28"/>
</a>
</g>
</g>
<!-- TRO02&#45;&gt;TRO03 -->
<g id="edge25" class="edge">
<title>TRO02&#45;&gt;TRO03</title>
<g id="a_edge25"><a xlink:title="gleiche Bahn">
<path fill="none" stroke="#7f7f7f" stroke-width="1.2" stroke-dasharray="1,5" d="M445.3,-684.23C476.3,-718.57 522.59,-762.01 573.75,-783.32 842.37,-895.17 1200.78,-847.47 1354.03,-818.97"/>
</a>
</g>
</g>
<!-- TRO02&#45;&gt;TRO04 -->
<g id="edge26" class="edge">
<title>TRO02&#45;&gt;TRO04</title>
<g id="a_edge26"><a xlink:title="gleiche Bahn">
<path fill="none" stroke="#7f7f7f" stroke-width="1.2" stroke-dasharray="1,5" d="M432.64,-586.29C511.11,-450.02 745.81,-86.19 939.75,-233.32 1063.29,-327.04 919.66,-464.29 1033.75,-569.32 1120.94,-649.58 1266.12,-656.82 1354.05,-652.58"/>
</a>
</g>
</g>
<!-- TRO09 -->
<g id="node9" class="node">
<title>TRO09</title>
<path fill="#e8d4f0" stroke="#883388" d="M682.75,-770.94C682.75,-770.94 585.75,-770.94 585.75,-770.94 579.75,-770.94 573.75,-764.94 573.75,-758.94 573.75,-758.94 573.75,-685.69 573.75,-685.69 573.75,-679.69 579.75,-673.69 585.75,-673.69 585.75,-673.69 682.75,-673.69 682.75,-673.69 688.75,-673.69 694.75,-679.69 694.75,-685.69 694.75,-685.69 694.75,-758.94 694.75,-758.94 694.75,-764.94 688.75,-770.94 682.75,-770.94"/>
<text xml:space="preserve" text-anchor="middle" x="634.25" y="-758.39" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO09 &#160;(Vario)</text>
<text xml:space="preserve" text-anchor="middle" x="634.25" y="-745.64" font-family="Segoe UI" font-size="9.00" fill="#111111">VarioFoerderer :1</text>
<text xml:space="preserve" text-anchor="middle" x="634.25" y="-732.89" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_Vario</text>
<text xml:space="preserve" text-anchor="middle" x="634.25" y="-720.14" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x VarioDrive</text>
<text xml:space="preserve" text-anchor="middle" x="634.25" y="-707.39" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0058, 0059</text>
<text xml:space="preserve" text-anchor="middle" x="634.25" y="-694.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0060</text>
<text xml:space="preserve" text-anchor="middle" x="634.25" y="-681.89" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: &#45;1146, 7128</text>
</g>
<!-- TRO02&#45;&gt;TRO09 -->
<g id="edge6" class="edge">
<title>TRO02&#45;&gt;TRO09</title>
<g id="a_edge6"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M480.16,-654.94C507.3,-664.58 537.84,-676.31 564.46,-687.33"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="563.19,-689.84 571.65,-690.34 565.35,-684.67 563.19,-689.84"/>
</a>
</g>
</g>
<!-- TRO03&#45;&gt;TRO04 -->
<g id="edge27" class="edge">
<title>TRO03&#45;&gt;TRO04</title>
<g id="a_edge27"><a xlink:title="gleiche Bahn">
<path fill="none" stroke="#7f7f7f" stroke-width="1.2" stroke-dasharray="1,5" d="M1429.62,-754.38C1429.62,-734.38 1429.62,-714.38 1429.62,-694.38"/>
</a>
</g>
</g>
<!-- TRO03&#45;&gt;TRO09 -->
<g id="edge7" class="edge">
<title>TRO03&#45;&gt;TRO09</title>
<g id="a_edge7"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1354.03,-806.74C1234.44,-803.86 992.54,-786.79 788.75,-760.82 761.17,-757.3 730.97,-752.41 704.71,-747.01"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="705.41,-744.3 697,-745.38 704.24,-749.78 705.41,-744.3"/>
</a>
</g>
</g>
<!-- TRO04&#45;&gt;TRO09 -->
<g id="edge8" class="edge">
<title>TRO04&#45;&gt;TRO09</title>
<g id="a_edge8"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1354.23,-677.04C1325.19,-684.99 1291.49,-692.31 1260.5,-696.82 1060.04,-725.95 820.29,-732.8 704.54,-729.56"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="704.97,-726.78 696.89,-729.32 704.8,-732.37 704.97,-726.78"/>
</a>
</g>
</g>
<!-- TRO05 -->
<g id="node5" class="node">
<title>TRO05</title>
<path fill="#ffe4a0" stroke="#cc8800" d="M927.75,-452.94C927.75,-452.94 800.75,-452.94 800.75,-452.94 794.75,-452.94 788.75,-446.94 788.75,-440.94 788.75,-440.94 788.75,-367.69 788.75,-367.69 788.75,-361.69 794.75,-355.69 800.75,-355.69 800.75,-355.69 927.75,-355.69 927.75,-355.69 933.75,-355.69 939.75,-361.69 939.75,-367.69 939.75,-367.69 939.75,-440.94 939.75,-440.94 939.75,-446.94 933.75,-452.94 927.75,-452.94"/>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-440.39" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO05 &#160;(1Sep1Swi)</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-427.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0063 auf Kreisel3 Bahn R</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-414.89" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep1Swi</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-402.14" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator, 1x Switch</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-389.39" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0063</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-376.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0008</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-363.89" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 10490, &#45;525</text>
</g>
<!-- TRO06 -->
<g id="node6" class="node">
<title>TRO06</title>
<path fill="#d4e8ff" stroke="#3388cc" d="M927.75,-330.57C927.75,-330.57 800.75,-330.57 800.75,-330.57 794.75,-330.57 788.75,-324.57 788.75,-318.57 788.75,-318.57 788.75,-258.07 788.75,-258.07 788.75,-252.07 794.75,-246.07 800.75,-246.07 800.75,-246.07 927.75,-246.07 927.75,-246.07 933.75,-246.07 939.75,-252.07 939.75,-258.07 939.75,-258.07 939.75,-318.57 939.75,-318.57 939.75,-324.57 933.75,-330.57 927.75,-330.57"/>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-318.02" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO06 &#160;(1Sep)</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-305.27" font-family="Segoe UI" font-size="9.00" fill="#111111">Separator 0064 auf Kreisel3 Bahn R</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-292.52" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_1Sep</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-279.77" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Separator</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-267.02" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0064</text>
<text xml:space="preserve" text-anchor="middle" x="864.25" y="-254.27" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 10310, 160</text>
</g>
<!-- TRO05&#45;&gt;TRO06 -->
<g id="edge28" class="edge">
<title>TRO05&#45;&gt;TRO06</title>
<g id="a_edge28"><a xlink:title="gleiche Bahn">
<path fill="none" stroke="#7f7f7f" stroke-width="1.2" stroke-dasharray="1,5" d="M864.25,-355.28C864.25,-347.16 864.25,-339.03 864.25,-330.91"/>
</a>
</g>
</g>
<!-- TRO10 -->
<g id="node10" class="node">
<title>TRO10</title>
<path fill="#d4f0d4" stroke="#338833" d="M1248.5,-556.94C1248.5,-556.94 1045.75,-556.94 1045.75,-556.94 1039.75,-556.94 1033.75,-550.94 1033.75,-544.94 1033.75,-544.94 1033.75,-471.69 1033.75,-471.69 1033.75,-465.69 1039.75,-459.69 1045.75,-459.69 1045.75,-459.69 1248.5,-459.69 1248.5,-459.69 1254.5,-459.69 1260.5,-465.69 1260.5,-471.69 1260.5,-471.69 1260.5,-544.94 1260.5,-544.94 1260.5,-550.94 1254.5,-556.94 1248.5,-556.94"/>
<text xml:space="preserve" text-anchor="middle" x="1147.12" y="-544.39" font-family="Segoe UI" font-size="9.00" fill="#111111">TRO10 &#160;(PinStore_Auto)</text>
<text xml:space="preserve" text-anchor="middle" x="1147.12" y="-531.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Speicher (20 Schwerkraftlinien)</text>
<text xml:space="preserve" text-anchor="middle" x="1147.12" y="-518.89" font-family="Segoe UI" font-size="9.00" fill="#111111">FB_ILS_MTRO_PinStore_Auto</text>
<text xml:space="preserve" text-anchor="middle" x="1147.12" y="-506.14" font-family="Segoe UI" font-size="9.00" fill="#111111">1x Encoder, 1x Scanner, 1x Separator, 19x StorageLine</text>
<text xml:space="preserve" text-anchor="middle" x="1147.12" y="-493.39" font-family="Segoe UI" font-size="9.00" fill="#111111">Sep: 0005 … 0056 (21)</text>
<text xml:space="preserve" text-anchor="middle" x="1147.12" y="-480.64" font-family="Segoe UI" font-size="9.00" fill="#111111">Scan: 0006</text>
<text xml:space="preserve" text-anchor="middle" x="1147.12" y="-467.89" font-family="Segoe UI" font-size="9.00" fill="#111111">x/y: 989, 3924</text>
</g>
<!-- TRO05&#45;&gt;TRO10 -->
<g id="edge9" class="edge">
<title>TRO05&#45;&gt;TRO10</title>
<g id="a_edge9"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M940.15,-432.05C966.07,-441.65 995.83,-452.67 1024.52,-463.29"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1023.21,-465.79 1031.68,-465.94 1025.15,-460.54 1023.21,-465.79"/>
</a>
</g>
</g>
<!-- TRO06&#45;&gt;TRO10 -->
<g id="edge10" class="edge">
<title>TRO06&#45;&gt;TRO10</title>
<g id="a_edge10"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M923.57,-331.04C929.06,-335.16 934.52,-339.3 939.75,-343.32 986.66,-379.36 1038.69,-420.97 1078.77,-453.38"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1076.71,-455.33 1084.69,-458.18 1080.24,-450.97 1076.71,-455.33"/>
</a>
</g>
</g>
<!-- TRO07&#45;&gt;TRO10 -->
<g id="edge11" class="edge">
<title>TRO07&#45;&gt;TRO10</title>
<g id="a_edge11"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1368.89,-516.15C1340.07,-517.4 1304.4,-517.89 1270.2,-517.62"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1270.44,-514.82 1262.42,-517.54 1270.39,-520.42 1270.44,-514.82"/>
</a>
</g>
</g>
<!-- TRO08&#45;&gt;TRO10 -->
<g id="edge12" class="edge">
<title>TRO08&#45;&gt;TRO10</title>
<g id="a_edge12"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M925.16,-555.54C954.07,-547.09 989.87,-537.69 1024.16,-529.41"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1024.42,-532.23 1031.55,-527.64 1023.12,-526.78 1024.42,-532.23"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO01 -->
<g id="edge13" class="edge">
<title>TRO09&#45;&gt;TRO01</title>
<g id="a_edge13"><a xlink:title="Weiche (Kreisel&#45;Uebergang)">
<path fill="none" stroke="#cc8800" stroke-width="1.8" d="M573.3,-729.64C509.43,-734.7 407.03,-734.75 329.5,-696.32 278.47,-671.02 289.5,-634.9 243.5,-601.32 233.53,-594.04 222.4,-587.42 211.07,-581.53"/>
<polygon fill="#cc8800" stroke="#cc8800" stroke-width="1.8" points="212.38,-579.05 203.98,-577.97 209.87,-584.06 212.38,-579.05"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="316" y="-688.71" font-family="Segoe UI" font-size="8.00" fill="#cc8800">Weiche</text>
</g>
<!-- TRO09&#45;&gt;TRO02 -->
<g id="edge14" class="edge">
<title>TRO09&#45;&gt;TRO02</title>
<g id="a_edge14"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M573.33,-707.78C547.57,-698.98 517.03,-687.54 489.12,-676.3"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="490.31,-673.76 481.84,-673.35 488.2,-678.95 490.31,-673.76"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO03 -->
<g id="edge15" class="edge">
<title>TRO09&#45;&gt;TRO03</title>
<g id="a_edge15"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M695.22,-722.3C723.51,-726.19 757.78,-731.87 788.75,-735.82 987.17,-761.1 1221.71,-777.96 1344.32,-790.08"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1343.99,-792.86 1352.23,-790.88 1344.54,-787.29 1343.99,-792.86"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO04 -->
<g id="edge16" class="edge">
<title>TRO09&#45;&gt;TRO04</title>
<g id="a_edge16"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M695.07,-714.73C806.78,-708.17 1054.41,-701.77 1260.5,-671.82 1288.22,-667.79 1318.11,-661.5 1344.89,-655.75"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1345.22,-658.54 1352.45,-654.13 1344.04,-653.07 1345.22,-658.54"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO07 -->
<g id="edge17" class="edge">
<title>TRO09&#45;&gt;TRO07</title>
<g id="a_edge17"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M694.88,-709.98C806.77,-686.33 1055.41,-631.42 1260.5,-569.32 1293.49,-559.33 1329.49,-546.53 1359.67,-535.24"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1360.65,-537.86 1367.15,-532.43 1358.67,-532.62 1360.65,-537.86"/>
</a>
</g>
</g>
<!-- TRO09&#45;&gt;TRO08 -->
<g id="edge18" class="edge">
<title>TRO09&#45;&gt;TRO08</title>
<g id="a_edge18"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M695.15,-684.48C725.93,-665.05 763.55,-641.29 795.39,-621.18"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="796.55,-623.76 801.81,-617.12 793.56,-619.02 796.55,-623.76"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO01 -->
<g id="edge19" class="edge">
<title>TRO10&#45;&gt;TRO01</title>
<g id="a_edge19"><a xlink:title="Weiche (Kreisel&#45;Uebergang)">
<path fill="none" stroke="#cc8800" stroke-width="1.8" d="M1126.3,-459.41C1096.73,-393.93 1034.26,-280.55 939.75,-233.32 879.72,-203.31 854.21,-218.53 788.75,-233.32 560.06,-284.95 317.73,-425.36 199.79,-500.07"/>
<polygon fill="#cc8800" stroke="#cc8800" stroke-width="1.8" points="198.38,-497.65 193.13,-504.3 201.38,-502.37 198.38,-497.65"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="570.92" y="-291.4" font-family="Segoe UI" font-size="8.00" fill="#cc8800">Weiche</text>
</g>
<!-- TRO10&#45;&gt;TRO02 -->
<g id="edge20" class="edge">
<title>TRO10&#45;&gt;TRO02</title>
<g id="a_edge20"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M1033.52,-503.32C962.79,-502.11 869.91,-504.05 788.75,-517.32 682.98,-534.6 565.83,-573.79 489.26,-602.27"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="488.34,-599.63 481.83,-605.05 490.3,-604.87 488.34,-599.63"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO03 -->
<g id="edge21" class="edge">
<title>TRO10&#45;&gt;TRO03</title>
<g id="a_edge21"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M1194.76,-557.36C1244.66,-609.84 1323.65,-692.91 1375.81,-747.77"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1373.55,-749.46 1381.1,-753.33 1377.61,-745.6 1373.55,-749.46"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO04 -->
<g id="edge22" class="edge">
<title>TRO10&#45;&gt;TRO04</title>
<g id="a_edge22"><a xlink:title="Kreisel&#45;Umlauf">
<path fill="none" stroke="#8ea9db" stroke-width="1.2" stroke-dasharray="5,2" d="M1248.6,-557.39C1280.5,-572.97 1315.39,-590.01 1345.89,-604.91"/>
<polygon fill="#8ea9db" stroke="#8ea9db" stroke-width="1.2" points="1344.32,-607.26 1352.74,-608.25 1346.78,-602.23 1344.32,-607.26"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO07 -->
<g id="edge23" class="edge">
<title>TRO10&#45;&gt;TRO07</title>
<g id="a_edge23"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1260.6,-499.11C1294.02,-498.7 1329.63,-499.04 1359.43,-500.1"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="1358.97,-502.89 1367.07,-500.41 1359.19,-497.29 1358.97,-502.89"/>
</a>
</g>
</g>
<!-- TRO10&#45;&gt;TRO08 -->
<g id="edge24" class="edge">
<title>TRO10&#45;&gt;TRO08</title>
<g id="a_edge24"><a xlink:title="Uebergabe (Ein&#45;/Ausschleusung)">
<path fill="none" stroke="#2f5597" stroke-width="1.2" d="M1033.31,-545.64C999.93,-554.36 964.38,-562.89 934.61,-569.24"/>
<polygon fill="#2f5597" stroke="#2f5597" stroke-width="1.2" points="934.19,-566.47 926.94,-570.85 935.34,-571.95 934.19,-566.47"/>
</a>
</g>
</g>
<!-- legende -->
<g id="node11" class="node">
<title>legende</title>
<polygon fill="#d4e8ff" stroke="none" points="24,-806.32 24,-827.07 92.75,-827.07 92.75,-806.32 24,-806.32"/>
<polygon fill="none" stroke="black" points="24,-806.32 24,-827.07 92.75,-827.07 92.75,-806.32 24,-806.32"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-813.32" font-family="Segoe UI" font-size="9.00"> &#160;sep &#160;</text>
<polygon fill="none" stroke="black" points="92.75,-806.32 92.75,-827.07 227.5,-827.07 227.5,-806.32 92.75,-806.32"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-813.32" font-family="Segoe UI" font-size="9.00">Separator</text>
<polygon fill="#ffe4a0" stroke="none" points="24,-785.57 24,-806.32 92.75,-806.32 92.75,-785.57 24,-785.57"/>
<polygon fill="none" stroke="black" points="24,-785.57 24,-806.32 92.75,-806.32 92.75,-785.57 24,-785.57"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-792.57" font-family="Segoe UI" font-size="9.00"> &#160;swi &#160;</text>
<polygon fill="none" stroke="black" points="92.75,-785.57 92.75,-806.32 227.5,-806.32 227.5,-785.57 92.75,-785.57"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-792.57" font-family="Segoe UI" font-size="9.00">Weiche</text>
<polygon fill="#ffd4d4" stroke="none" points="24,-764.82 24,-785.57 92.75,-785.57 92.75,-764.82 24,-764.82"/>
<polygon fill="none" stroke="black" points="24,-764.82 24,-785.57 92.75,-785.57 92.75,-764.82 24,-764.82"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-771.82" font-family="Segoe UI" font-size="9.00"> &#160;sscc &#160;</text>
<polygon fill="none" stroke="black" points="92.75,-764.82 92.75,-785.57 227.5,-785.57 227.5,-764.82 92.75,-764.82"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-771.82" font-family="Segoe UI" font-size="9.00">Scanner (SSCC)</text>
<polygon fill="#e8d4f0" stroke="none" points="24,-744.07 24,-764.82 92.75,-764.82 92.75,-744.07 24,-744.07"/>
<polygon fill="none" stroke="black" points="24,-744.07 24,-764.82 92.75,-764.82 92.75,-744.07 24,-744.07"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-751.07" font-family="Segoe UI" font-size="9.00"> &#160;vario &#160;</text>
<polygon fill="none" stroke="black" points="92.75,-744.07 92.75,-764.82 227.5,-764.82 227.5,-744.07 92.75,-744.07"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-751.07" font-family="Segoe UI" font-size="9.00">Kettenfoerderer</text>
<polygon fill="#d4f0d4" stroke="none" points="24,-723.32 24,-744.07 92.75,-744.07 92.75,-723.32 24,-723.32"/>
<polygon fill="none" stroke="black" points="24,-723.32 24,-744.07 92.75,-744.07 92.75,-723.32 24,-723.32"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-730.32" font-family="Segoe UI" font-size="9.00"> &#160;store &#160;</text>
<polygon fill="none" stroke="black" points="92.75,-723.32 92.75,-744.07 227.5,-744.07 227.5,-723.32 92.75,-723.32"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-730.32" font-family="Segoe UI" font-size="9.00">Speicher / Puffer</text>
<polygon fill="#f0f0f0" stroke="none" points="24,-702.57 24,-723.32 92.75,-723.32 92.75,-702.57 24,-702.57"/>
<polygon fill="none" stroke="black" points="24,-702.57 24,-723.32 92.75,-723.32 92.75,-702.57 24,-702.57"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-709.57" font-family="Segoe UI" font-size="9.00"> &#160;ext &#160;</text>
<polygon fill="none" stroke="black" points="92.75,-702.57 92.75,-723.32 227.5,-723.32 227.5,-702.57 92.75,-702.57"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-709.57" font-family="Segoe UI" font-size="9.00">Systemgrenze / extern</text>
<polygon fill="none" stroke="black" points="24,-681.82 24,-702.57 92.75,-702.57 92.75,-681.82 24,-681.82"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-688.82" font-family="Segoe UI" font-size="9.00" fill="#2f5597">durchgezogen</text>
<polygon fill="none" stroke="black" points="92.75,-681.82 92.75,-702.57 227.5,-702.57 227.5,-681.82 92.75,-681.82"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-688.82" font-family="Segoe UI" font-size="9.00">Uebergabe / Ein&#45;Ausschleusung</text>
<polygon fill="none" stroke="black" points="24,-661.07 24,-681.82 92.75,-681.82 92.75,-661.07 24,-661.07"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-668.07" font-family="Segoe UI" font-size="9.00" fill="#8ea9db">blau gestrichelt</text>
<polygon fill="none" stroke="black" points="92.75,-661.07 92.75,-681.82 227.5,-681.82 227.5,-661.07 92.75,-661.07"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-668.07" font-family="Segoe UI" font-size="9.00">Kreisel&#45;Umlauf</text>
<polygon fill="none" stroke="black" points="24,-640.32 24,-661.07 92.75,-661.07 92.75,-640.32 24,-640.32"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-647.32" font-family="Segoe UI" font-size="9.00" fill="#cc8800">orange</text>
<polygon fill="none" stroke="black" points="92.75,-640.32 92.75,-661.07 227.5,-661.07 227.5,-640.32 92.75,-640.32"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-647.32" font-family="Segoe UI" font-size="9.00">Weiche (Kreisel&#45;Uebergang)</text>
<polygon fill="none" stroke="black" points="24,-619.57 24,-640.32 92.75,-640.32 92.75,-619.57 24,-619.57"/>
<text xml:space="preserve" text-anchor="start" x="28" y="-626.57" font-family="Segoe UI" font-size="9.00">grau gepunktet</text>
<polygon fill="none" stroke="black" points="92.75,-619.57 92.75,-640.32 227.5,-640.32 227.5,-619.57 92.75,-619.57"/>
<text xml:space="preserve" text-anchor="start" x="96.75" y="-626.57" font-family="Segoe UI" font-size="9.00">gleiche Bahn / Strecke</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 34 KiB

+225
View File
@@ -0,0 +1,225 @@
<!-- erzeugt von lib/tro_flow.py -->
# TRO-Liste und TRO-Fluss - export.csv
**Erzeugt:** 2026-08-10 10:53 von `lib/tro_flow.py`
**Quelle:** `C:\develop\sps_skel\data\export.csv`
**Typdefinition:** `lib/tro_catalog.py` (Stand 2026-07-20)
> Automatisch aus dem CSV-Export erzeugt. Die TRO-Typen sind aus dem mechanischen Layout **abgeleitet** - ohne E-Planung (I/O-Liste) und ohne `FB_Main`-Aufrufstellen gibt es noch keine verbindliche TRO-Nummerierung. Die IDs unten sind laufende Nummern dieses Laufs.
## 1. Kurzfassung
| | |
|---|---|
| Ermittelte TROs | 10 |
| TRO-Verbindungen | 24 |
| Nicht verbundene TROs | 0 |
| Hinweise | 3 |
| Manuelle Typkorrekturen | 3 |
| Offene Punkte (noch kein TRO) | 2 |
| Typ | FB-Baustein | Anzahl hier | Bauteile je TRO | Farbgruppe |
|---|---|---:|---|---|
| `1Sep` | `FB_ILS_MTRO_1Sep` | 3 | 1x Separator | sep |
| `1Sep1Swi` | `FB_ILS_MTRO_1Sep1Swi` | 2 | 1x Separator, 1x Switch | swi |
| `1Sep_SSCC` | `FB_ILS_MTRO_1Sep_SSCC` | 1 | 1x SSCC, 1x Scanner, 1x Separator | sscc |
| `PinStore_Auto` | `FB_ILS_MTRO_PinStore_Auto` | 1 | 1x Encoder, 1x Scanner, 1x Separator, 19x StorageLine | store |
| `Vario` | `FB_ILS_MTRO_Vario` | 3 | 1x Separator, 1x VarioDrive | vario |
## 2. TRO-Flussdiagramm
```mermaid
flowchart LR
classDef sep fill:#d4e8ff,stroke:#3388cc,color:#111111
classDef swi fill:#ffe4a0,stroke:#cc8800,color:#111111
classDef sscc fill:#ffd4d4,stroke:#cc3333,color:#111111
classDef vario fill:#e8d4f0,stroke:#883388,color:#111111
classDef store fill:#d4f0d4,stroke:#338833,color:#111111
classDef ext fill:#f0f0f0,stroke:#888888,color:#404040
TRO01["TRO01<br/>1Sep<br/>Separator 0061 auf Kreisel1 Bahn R"]:::sep
TRO02["TRO02<br/>1Sep<br/>Separator 0003 auf Kreisel2 Bahn L"]:::sep
TRO03["TRO03<br/>1Sep1Swi<br/>Separator 0011 auf Kreisel2 Bahn L"]:::swi
TRO04["TRO04<br/>1Sep_SSCC<br/>Separator 0012 auf Kreisel2 Bahn L"]:::sscc
TRO05["TRO05<br/>1Sep1Swi<br/>Separator 0063 auf Kreisel3 Bahn R"]:::swi
TRO06["TRO06<br/>1Sep<br/>Separator 0064 auf Kreisel3 Bahn R"]:::sep
TRO07["TRO07<br/>Vario<br/>VarioFoerderer :3"]:::vario
TRO08["TRO08<br/>Vario<br/>VarioFoerderer :2"]:::vario
TRO09["TRO09<br/>Vario<br/>VarioFoerderer :1"]:::vario
TRO10["TRO10<br/>PinStore_Auto<br/>Speicher (20 Schwerkraftlinien)"]:::store
TRO01 --> TRO02
TRO01 --> TRO03
TRO01 --> TRO04
TRO01 --> TRO07
TRO01 --> TRO08
TRO02 --> TRO09
TRO03 --> TRO09
TRO04 --> TRO09
TRO05 --> TRO10
TRO06 --> TRO10
TRO07 --> TRO10
TRO08 --> TRO10
TRO09 --> TRO01
TRO09 --> TRO02
TRO09 --> TRO03
TRO09 --> TRO04
TRO09 --> TRO07
TRO09 --> TRO08
TRO10 --> TRO01
TRO10 --> TRO02
TRO10 --> TRO03
TRO10 --> TRO04
TRO10 --> TRO07
TRO10 --> TRO08
TRO02 -. gleiche Bahn .- TRO03
TRO02 -. gleiche Bahn .- TRO04
TRO03 -. gleiche Bahn .- TRO04
TRO05 -. gleiche Bahn .- TRO06
```
Gerendert: [`export_tro_flow.dot`](export_tro_flow.dot) - [`export_tro_flow.svg`](export_tro_flow.svg)
## 3. TRO-Liste
| TRO | Typ | FB-Baustein | Bauteile laut Typ | X [mm] | Y [mm] | Z [mm] | Host-Knoten | Separatoren | Scanner | Begruendung | Vertrauen |
|---|---|---|---|---:|---:|---:|---|---|---|---|---|
| `TRO01` | `1Sep` | `FB_ILS_MTRO_1Sep` | 1x Separator | 5355 | -5878 | 1468 | `0001-R` | 0061 | - | einzelner Separator auf angetriebener Kreisel-Bahn | mittel |
| `TRO02` | `1Sep` | `FB_ILS_MTRO_1Sep` | 1x Separator | -220 | 13960 | 1468 | `0002-L` | 0003 | 0004 | Separator mit Scanner auf angetriebener Kreisel-Bahn | mittel |
| `TRO03` | `1Sep1Swi` | `FB_ILS_MTRO_1Sep1Swi` | 1x Separator, 1x Switch | -220 | -481 | 1468 | `0002-L` | 0011 | 0009 | Separator mit Scanner auf angetriebener Kreisel-Bahn; manuell auf 1Sep1Swi gesetzt (Weiche am Separator 0011 vor Ort bestaetigt (Review 2026-08-10) - an dieser Stelle wird die Fahrtrichtung umgestellt; im CSV-Export fehlt das Weichenelement) | bestaetigt |
| `TRO04` | `1Sep_SSCC` | `FB_ILS_MTRO_1Sep_SSCC` | 1x SSCC, 1x Scanner, 1x Separator | -220 | -3472 | 1468 | `0002-L` | 0012 | 0007 | Separator mit Scanner auf angetriebener Kreisel-Bahn; manuell auf 1Sep_SSCC gesetzt (Separator 0012 mit Scanner 0007 - im Review als Scanner-Stelle bestaetigt (Review 2026-08-10)) | bestaetigt |
| `TRO05` | `1Sep1Swi` | `FB_ILS_MTRO_1Sep1Swi` | 1x Separator, 1x Switch | 10490 | -525 | 1705 | `0010-R` | 0063 | 0008 | Separator mit Scanner auf angetriebener Kreisel-Bahn; manuell auf 1Sep1Swi gesetzt (Weiche am Separator 0063 vor Ort bestaetigt (Review 2026-08-10) - im CSV-Export fehlt das Weichenelement) | bestaetigt |
| `TRO06` | `1Sep` | `FB_ILS_MTRO_1Sep` | 1x Separator | 10310 | 160 | 1942 | `0010-R` | 0064 | - | einzelner Separator auf angetriebener Kreisel-Bahn | mittel |
| `TRO07` | `Vario` | `FB_ILS_MTRO_Vario` | 1x Separator, 1x VarioDrive | 9186 | 13549 | 1706 | `0013` | 0015, 0062 | - | angetriebenes Streckensegment (Antriebfahrtrichtung: Auf) | hoch |
| `TRO08` | `Vario` | `FB_ILS_MTRO_Vario` | 1x Separator, 1x VarioDrive | 6952 | -4271 | 1784 | `0014` | 0057, 0065 | 0066 | angetriebenes Streckensegment (Antriebfahrtrichtung: Auf) | hoch |
| `TRO09` | `Vario` | `FB_ILS_MTRO_Vario` | 1x Separator, 1x VarioDrive | -1146 | 7128 | 1530 | `0016` | 0058, 0059 | 0060 | angetriebenes Streckensegment (Antriebfahrtrichtung: Auf) | hoch |
| `TRO10` | `PinStore_Auto` | `FB_ILS_MTRO_PinStore_Auto` | 1x Encoder, 1x Scanner, 1x Separator, 19x StorageLine | 989 | 3924 | 1553 | `0017``0036` (20 Knoten) | 0005 … 0056 (21) | 0006 | 20 Gefaellestrecken mit je eigenem Separator, gemeinsamer Zu-/Ablauf - Muster eines Linienspeichers | hoch |
**X/Y/Z** ist der Mittelpunkt der Bauteile, aus denen der TRO gebildet wurde (Separatoren und Scanner) - beim Linienspeicher also der Mittelwert aller Linien-Separatoren. Koordinaten in mm im Anlagen-Koordinatensystem des CSV-Exports.
| TRO | Koordinate aus | Anzahl Punkte |
|---|---|---:|
| `TRO01` | Bauteile | 1 |
| `TRO02` | Bauteile | 2 |
| `TRO03` | Bauteile | 2 |
| `TRO04` | Bauteile | 2 |
| `TRO05` | Bauteile | 2 |
| `TRO06` | Bauteile | 1 |
| `TRO07` | Bauteile | 2 |
| `TRO08` | Bauteile | 3 |
| `TRO09` | Bauteile | 3 |
| `TRO10` | Bauteile | 22 |
## 4. TRO-Verbindungen
Richtung des Materialflusses zwischen den Uebergabestellen. Passive Zwischenknoten ohne eigenen TRO sind zusammengefasst.
| von | nach | Typ von -> Typ nach |
|---|---|---|
| `TRO01` | `TRO02` | `1Sep` -> `1Sep` |
| `TRO01` | `TRO03` | `1Sep` -> `1Sep1Swi` |
| `TRO01` | `TRO04` | `1Sep` -> `1Sep_SSCC` |
| `TRO01` | `TRO07` | `1Sep` -> `Vario` |
| `TRO01` | `TRO08` | `1Sep` -> `Vario` |
| `TRO02` | `TRO09` | `1Sep` -> `Vario` |
| `TRO03` | `TRO09` | `1Sep1Swi` -> `Vario` |
| `TRO04` | `TRO09` | `1Sep_SSCC` -> `Vario` |
| `TRO05` | `TRO10` | `1Sep1Swi` -> `PinStore_Auto` |
| `TRO06` | `TRO10` | `1Sep` -> `PinStore_Auto` |
| `TRO07` | `TRO10` | `Vario` -> `PinStore_Auto` |
| `TRO08` | `TRO10` | `Vario` -> `PinStore_Auto` |
| `TRO09` | `TRO01` | `Vario` -> `1Sep` |
| `TRO09` | `TRO02` | `Vario` -> `1Sep` |
| `TRO09` | `TRO03` | `Vario` -> `1Sep1Swi` |
| `TRO09` | `TRO04` | `Vario` -> `1Sep_SSCC` |
| `TRO09` | `TRO07` | `Vario` -> `Vario` |
| `TRO09` | `TRO08` | `Vario` -> `Vario` |
| `TRO10` | `TRO01` | `PinStore_Auto` -> `1Sep` |
| `TRO10` | `TRO02` | `PinStore_Auto` -> `1Sep` |
| `TRO10` | `TRO03` | `PinStore_Auto` -> `1Sep1Swi` |
| `TRO10` | `TRO04` | `PinStore_Auto` -> `1Sep_SSCC` |
| `TRO10` | `TRO07` | `PinStore_Auto` -> `Vario` |
| `TRO10` | `TRO08` | `PinStore_Auto` -> `Vario` |
## 5. Hinweise
- Scanner 0006 'Scanner :6': montiert an 0014, liest aber an Separator 0005 auf 0036
- Scanner 0066 'Scanner :1': montiert an 0036, liest aber an Separator 0065 auf 0014
- TRO02 (Separator 0003 auf Kreisel2 Bahn L): Scanner 0004 am Separator - als 1Sep eingestuft. Pruefen, ob 1Sep_SSCC (SSCC-Scanner, Endmessung, WCS-Telegramm) erforderlich ist; das ist aus dem mechanischen Layout nicht erkennbar.
## 6. Manuelle Korrekturen und offene Punkte
Quelle: `C:\develop\sps_skel\cfg\tro_overrides.ini`
### 6.1 Angewandte Typkorrekturen
Diese Typen stammen **nicht** aus dem Layout, sondern aus der Begehung bzw. dem Review - im Layout sind sie nicht erkennbar.
| Korrektur | Begruendung |
|---|---|
| `TRO03: 1Sep -> 1Sep1Swi` | Weiche am Separator 0011 vor Ort bestaetigt (Review 2026-08-10) - an dieser Stelle wird die Fahrtrichtung umgestellt; im CSV-Export fehlt das Weichenelement |
| `TRO04: 1Sep -> 1Sep_SSCC` | Separator 0012 mit Scanner 0007 - im Review als Scanner-Stelle bestaetigt (Review 2026-08-10) |
| `TRO05: 1Sep -> 1Sep1Swi` | Weiche am Separator 0063 vor Ort bestaetigt (Review 2026-08-10) - im CSV-Export fehlt das Weichenelement |
### 6.2 Offene Punkte - noch kein TRO
Im Review gemeldete Stellen. Die TRO-Struktur ist hier **bewusst unveraendert**; die Punkte sind nur markiert (auch in der annotierten Zeichnung, Layer `TRO_OPENPOINT`), damit sie in BricsCAD geprueft werden koennen.
| Punkt | X [mm] | Y [mm] | Z [mm] | erwartet | heute Teil von | Anmerkung |
|---|---:|---:|---:|---|---|---|
| Foerderer_unten_links | 530 | -3895 | 1468 | `1Sep1Swi` | `TRO10` | Im Review als eigene Uebergabestelle (1 Separator + 1 Weiche) gemeldet, Position aus der Zeichnung (DXF -50375.7283 / -12309.7617, ueber cfg/dxf_registration.json auf Anlagenkoordinaten umgerechnet). Naechstes Bauteil ist Separator 0005 'Separator :32' bei 400 / -3755 auf Gefaellestrecke 0036, das derzeit zum Linienspeicher TRO10 gehoert. Vor der Umsetzung in BricsCAD pruefen. |
| Qualitaetskontrolle | -1026 | 5250 | 1478 | `1Sep1Swi` | `TRO09` | Im Review als eigene Uebergabestelle (1 Separator + 1 Weiche) gemeldet. Separator 0059 und Scanner 0060 gehoeren derzeit zum Vario-TRO09 auf Strecke 0016. Vor der Umsetzung in BricsCAD pruefen. |
## 7. Ableitungsregeln und Typdefinition
Ein TRO ist die Steuerungslogik **einer Uebergabestelle**. Ausgangspunkt ist der Separator und das, was an ihm haengt (Host-Objekt, Scanner).
| Host des Separators | TRO-Typ |
|---|---|
| Gefaellestrecke, >= 2 Linien mit gemeinsamem Zu-/Ablauf | `PinStore_Auto` (ein Block je Liniengruppe) |
| Gefaellestrecke, einzelne Linie | `1Sep` |
| Strecke (angetrieben) | `Vario` (je Streckensegment) |
| Kreisel-Bahn | `1Sep` |
| zusaetzlich: 2 bzw. 3 abgehende Wege | Aufwertung zu `1Sep1Swi` / `1Sep2Swi` |
Ein **Scanner** am Separator aendert den Typ nicht. In der Referenzanlage sind Separatoren mit Scanner ueberwiegend `1Sep`. `1Sep_SSCC` ist dem Sonderfall mit SSCC-Scanner, Endmessung und WCS-Telegramm vorbehalten und aus einem mechanischen Layout nicht erkennbar - betroffene Stellen stehen als Hinweis in Abschnitt 5.
Was aus dem CSV **nicht** ableitbar ist: TRO-Nummerierung, Sensor- und Aktor-Tags, JamAreas, Zielsteuerung und Timing. Das kommt aus der E-Planung (I/O-Liste) bzw. aus `FB_Main`.
Typen, FB-Bausteine, Bauteile und Farben stammen vollstaendig aus `TRO_CATALOG` in `lib/tro_catalog.py` (Stand 2026-07-20):
| # | Typ | FB-Baustein | Bauteile je TRO | Farbgruppe |
|---:|---|---|---|---|
| 1 | `1Sep` | `FB_ILS_MTRO_1Sep` | 1x Separator | sep |
| 2 | `1Sep1Swi` | `FB_ILS_MTRO_1Sep1Swi` | 1x Separator, 1x Switch | swi |
| 3 | `1Sep2Swi` | `FB_ILS_MTRO_1Sep2Swi` | 1x Scanner, 1x Separator, 2x Switch | swi |
| 4 | `1Sep_SSCC` | `FB_ILS_MTRO_1Sep_SSCC` | 1x SSCC, 1x Scanner, 1x Separator | sscc |
| 5 | `Vario` | `FB_ILS_MTRO_Vario` | 1x Separator, 1x VarioDrive | vario |
| 6 | `PinStore_Auto` | `FB_ILS_MTRO_PinStore_Auto` | 1x Encoder, 1x Scanner, 1x Separator, 19x StorageLine | store |
| 7 | `Vario_workStation` | `FB_ILS_MTRO_Vario_workStation` | 1x Accumulate, 1x Scanner, 1x Separator, 1x VarioDrive, 1x WorkStation | vario |
| 8 | `EmptyCarrBuffer` | `FB_EmptyCarrBuffer` | 5x Accumulate, 5x StorageLine | store |
| 9 | `LoadingBoom` | `FB_LoadingBoom_INBOUND` | 1x Boom, 1x Foot, 1x TiltSensor | ext |
| 10 | `2Sep1Swi` | `FB_ILS_MTRO_2Sep1Swi` | 2x Scanner, 2x Separator, 1x Switch | swi |
| Bauteil | Sub-FB |
|---|---|
| Accumulate | `FB_CarrAccumulate1Sep` |
| Boom | `FB_BoomWorkStation` |
| Encoder | _kein eigener FB_ |
| Foot | `FB_DistanceFoot` |
| SSCC | _kein eigener FB_ |
| Scanner | `FB_BarcodeReaderCognex` |
| Separator | `FB_ILS_STRO_Sep` |
| StorageLine | `FB_ILS_STRO_Sep` |
| Switch | `FB_ILS_STRO_Switch` |
| TiltSensor | `FB_Tilt_TEST` |
| VarioDrive | `FB_ILS_STRO_Vario` |
| WorkStation | `FB_ILS_STRO_Vario_WorkStation` |
| Farbgruppe | Bedeutung | Fuellfarbe |
|---|---|---|
| sep | Separator | `#d4e8ff` |
| swi | Weiche | `#ffe4a0` |
| sscc | Scanner (SSCC) | `#ffd4d4` |
| vario | Kettenfoerderer | `#e8d4f0` |
| store | Speicher / Puffer | `#d4f0d4` |
| ext | Systemgrenze / extern | `#f0f0f0` |
+230 -9
View File
@@ -66,11 +66,14 @@ Each CLI tool is invoked through its `bin/<name>.bat` (Windows) / `bin/<name>.sh
3. runs the module with `py` (falling back to `python`). 3. runs the module with `py` (falling back to `python`).
```bat ```bat
bin\material_flow.bat --file mubea.csv --tosvg bin\material_flow.bat --file mubea.csv --tosvg --use-cords --doc
bin\tro_flow.bat --file mubea.csv --tosvg --doc bin\tro_flow.bat --file mubea.csv --tosvg --use-cords --doc
bin\tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --flow --fb --legend bin\tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --flow --fb --legend
``` ```
(`--use-cords` lays both graphs out at the real plant coordinates; drop it for
Graphviz's computed left-to-right arrangement.)
Conventions shared by all three tools: Conventions shared by all three tools:
- `--file` / `--dxf` accept either a bare filename — resolved against - `--file` / `--dxf` accept either a bare filename — resolved against
@@ -97,6 +100,27 @@ Reads the CSV export and builds a directed graph of the mechanical objects:
counter-clockwise = left→right). counter-clockwise = left→right).
- **Separator** / **Scanner** → no node of their own; shown via `Zuordnung` - **Separator** / **Scanner** → no node of their own; shown via `Zuordnung`
(assignment) on the label of their owning node. (assignment) on the label of their owning node.
- **Einschleuselement** / **Ausschleuselement** / **Weiche** (from the richer export)
→ no node of their own; they supply the **direction** of the Kreisel↔Strecke edges
(`Ausschleusung` = Kreisel→Strecke at the strecke's *Anfang*, `Einschleusung` =
Strecke→Kreisel at its *Ende*, read from their `Kreisel`/`Strecke`/`Streckenende`
`Merkmale`) and **mark the Weiche** on a Kreisel↔Kreisel link. A strecke whose two
ends touch the *same* Kreisel (return loop) is split across that Kreisel's two lanes.
This makes Kreisel↔Strecke direction deterministic instead of height-inferred.
- **BTMT Be-/Entladestation** (entry/exit points) → one node each, recognised by
`TeileArt`/`Bezeichnung` containing `Beladung`/`BTMT-Beladung` (→ **entry**, a
material *source*) or `Entladung`/`SC_Entladung`/`Abwurf` (→ **exit**, a *sink*).
An entry gets an outgoing edge, an exit an incoming edge, to the object named in
its `Nachbarn`; if the export lists no neighbour (dxfmakros does not export BTMT
yet — see below), the nearest object by 2-D distance is used and a note is
printed. Recognition is **sps_skel-side only** — nothing in dxfmakros changes;
it simply activates when such a row appears in the CSV.
> **Note on BTMT / entry-exit data.** dxfmakros' `EXPORTCSV` currently does **not**
> emit the BTMT stations (their block names match no export pattern; documented as
> *"Export noch offen"*). Until that is addressed, a freshly exported `mubea.csv`
> 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.
Flow direction between conveyor segments is derived from height change Flow direction between conveyor segments is derived from height change
(`Hoehe_Von_mm`/`Hoehe_Bis_mm`) or, failing that, from `Antriebfahrtrichtung` (`Hoehe_Von_mm`/`Hoehe_Bis_mm`) or, failing that, from `Antriebfahrtrichtung`
@@ -109,11 +133,13 @@ attached.
|---|---|---|---| |---|---|---|---|
| `--file` | `NAME` | `export.csv` | CSV input file, resolved against `%SKEL_DATA%`, or a full path. | | `--file` | `NAME` | `export.csv` | CSV input file, resolved against `%SKEL_DATA%`, or a full path. |
| `--tosvg` | — | off | Also render the `.dot` file to `.svg` via Graphviz `dot`. | | `--tosvg` | — | off | Also render the `.dot` file to `.svg` via Graphviz `dot`. |
| `--use-cords` (alias `--use-coords`) | — | off | Place every node at its plant coordinate and render with `neato -n` (fixed positions) instead of `dot`, so the material graph matches the plant layout — the same option `tro_flow.py` has. The two Kreisel lanes (`-L`/`-R`) are nudged apart by `LANE_NUDGE_MM`; scale is `COORD_SCALE` (0.25 pt/mm, identical to `tro_flow.py`). Only has an effect together with `--tosvg`; fails (exit 1) if any flow node has no coordinate. |
| `--doc` | — | off | Also write a Markdown report (`<name>_material_flow.md`) with object list, connections and plausibility findings. | | `--doc` | — | off | Also write a Markdown report (`<name>_material_flow.md`) with object list, connections and plausibility findings. |
| `--connect` | — | off | Also write the topology as `<name>_connect.ini` (item/flow level): `[…nodes]` (Kreisel lanes, Strecke, Gefällestrecke), `[…connections]` with a `kind` per edge (`normal`/`umlauf`/`weiche`/`einschleusung`/`ausschleusung`), and `[…externals]` — the BTMT entry/exit if present, otherwise the open lane-ends listed as entry candidates. A hand-editable bridge into the TRO step, styled after `doc/TRO_Katalog/connect.ini`. |
**Output** (in `%SKEL_RESULTS%`): `<name>_material_flow.dot` (always), **Output** (in `%SKEL_RESULTS%`): `<name>_material_flow.dot` (always, with `pos`
`<name>_material_flow.svg` (with `--tosvg`), `<name>_material_flow.md` attributes when `--use-cords` was used), `<name>_material_flow.svg` (with `--tosvg`),
(with `--doc`). `<name>_material_flow.md` (with `--doc`), `<name>_connect.ini` (with `--connect`).
**Exit codes:** `0` ok · `1` input/CLI error · `2` SVG rendering failed **Exit codes:** `0` ok · `1` input/CLI error · `2` SVG rendering failed
(Graphviz). (Graphviz).
@@ -210,19 +236,195 @@ overridden.
could not be proven (or exceeds `--max-residual`) — resolve with `--offset` or could not be proven (or exceeds `--max-residual`) — resolve with `--offset` or
by raising `--max-residual`. by raising `--max-residual`.
## 5a. `tro_extract.py` — read the TROs back out of the drawing into JSON
The counter-direction to `tro_annotate.py`:
```
tro_annotate.py CSV + derivation -> markers in the drawing
tro_extract.py drawing -> JSON
```
It reads **only the drawing**, never the CSV. So what comes back is what is
actually in the DXF — including anything adjusted by hand in BricsCAD (markers
moved, type changed via `TRO_EDIT`). That is the point: after a review, the
drawing is the current state, not the export.
It finds the objects `tro_annotate.py` tagged with XDATA (AppID
`SPS_SKEL_TRO`): the `TRO_SYM_<type>` block references (attributes
`ID`/`TYPE`/`ITEMS`/`CONFIDENCE`/`SEPARATORS`, plus `FB_BLOCK` when annotated
with `--fb`), the flow arrows (`"<from>-><to>"`), and the open-point markers
(`"OFFEN:<name>"`). Each TRO also gets `predecessors`/`successors` computed
from the arrows.
Coordinates appear twice: `positionDxf` is the drawing coordinate,
`position` the plant coordinate. The conversion uses the registration written
by `tro_annotate.py` (`<stem>_registration.json`; the `_annotated` suffix and
the file's own `dxf_file` field are both taken into account when locating it).
If no registration is found, `position` is **left out rather than guessed**,
and a warning says so.
Switches: `--dxf NAME` (default `export_annotated.dxf`, looked up in
`%SKEL_RESULTS%`), `--out NAME` (default `<drawing>_tro.json`). Exit codes:
`0` ok, `1` input error, `2` no TRO markers in the drawing.
`fbType` is worth one note: unless the drawing was annotated with `--fb`, the
FB block is not an attribute in the DXF. It is then looked up from
`tro_catalog.py` and marked `"fbTypeFrom": "tro_catalog"` — so a consumer can
tell a value that came out of the drawing from one that was resolved.
## 5b. `scl_skeleton.py` — generate the FB_Main skeleton as SCL
Last step of the chain:
```
material_flow.py objects -> material flow
tro_flow.py flow -> TROs
tro_annotate.py TROs -> markers in the drawing
tro_extract.py drawing -> JSON
scl_skeleton.py JSON -> FB_Main.scl
```
Produces a **skeleton, not a running program**: one `REGION` per TRO with the
instance call and every parameter line in the right order, following the real
call sites in `doc/TRO_Katalog/scl_templates/*.scl`.
**It will not compile as delivered, and that is deliberate.** Every value that
has to come from the electrical planning is a visible gap marked
`TODO(E-Planung)` — never an invented value, because a plausible-looking wrong
sensor address is far worse than an empty one. Grep for that marker and nothing
is missed.
| filled from the layout | left as `TODO(E-Planung)` |
|---|---|
| REGION order (material flow), instance list, FB type per TRO | sensor/actuator addresses (`cInBG`, `MB`, `MA`) |
| number of separator / switch / scanner parameter blocks | separator and switch numbers, `cMainTro` |
| `nTo1Destinations` when the TRO has exactly one switch | jam areas (`DB_JamArea…`) |
| destination list per switch exit (graph reachability) | times marked "pruefen" |
| cross-reference to the layout parts (separator numbers, coordinate) | |
Two derivations worth knowing about. **The destination list per switch exit** is
computed by walking the TRO graph from each exit without going back through the
switch — that is exactly what `FC_Direction` needs to map destination to
direction, and it does *not* require the I/O list. And **`nTo1Destinations`** is
only auto-filled for a single-switch TRO; where several switches share the
exits, how they divide them is a planning decision, so it stays a gap rather
than a guess.
Switches: `--json NAME` (default `export_tro.json`), `--out NAME` (default
`<source>_FB_Main.scl`), `--start TRO`. The last one matters while the plant has
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.
## 6. `tro_catalog.py` — TRO type catalog (library, no CLI) ## 6. `tro_catalog.py` — TRO type catalog (library, no CLI)
The single source of truth for everything needed to generate or draw a TRO The single source of truth for everything needed to generate or draw a TRO
type: short name (as used in `FB_Main`/`connect.ini`), Siemens FB block, type: short name (as used in `FB_Main`/`connect.ini`), Siemens FB block,
sub-components and their counts, color group (for Graphviz/Mermaid/CAD) and sub-components and their counts, color group (for Graphviz/Mermaid/CAD) and
CAD marker shape. Ten types are defined in `TRO_CATALOG`, verified against CAD marker shape. **Eleven** types are defined in `TRO_CATALOG`. Ten of them are
`data/*.scl` where the source is vendored locally and against `TRO_Typen.md`'s verified against `data/*.scl` where the source is vendored locally, and against
mapping table otherwise (see the module docstring, `CATALOG_AS_OF`, for `TRO_Typen.md`'s mapping table otherwise (see the module docstring,
provenance per type). Everything is a Pydantic v2 model `CATALOG_AS_OF`, for provenance per type).
> **`2Sep2Swi` is the exception — it is not backed by any SCL.** It was added on
> 2026-08-12 for project 500573 (Mubea), to model the transfer point at the
> Kreisel2/Kreisel1 touch as a single control object with 2 separators,
> 2 scanners and 2 switches. **`FB_ILS_MTRO_2Sep2Swi` does not exist** in this
> repository or in `doc/TRO_Katalog/scl_templates/`, and has to be created in
> the ILSLib — otherwise a future SCL generator will emit a call to a missing
> block. The closest verified type is `2Sep1Swi` (2 separators, 2 scanners,
> **1** switch), which matches the hardware actually present in that export.
Everything is a Pydantic v2 model
(`TroDefinition`/`TroStyle`), so it validates on every assignment and comes (`TroDefinition`/`TroStyle`), so it validates on every assignment and comes
with `model_dump()`/`model_validate()` for the planned JSON layout model for with `model_dump()`/`model_validate()` for the planned JSON layout model for
free. Consumed by `tro_flow.py` and `tro_annotate.py`; not run directly. free. Consumed by `tro_flow.py` and `tro_annotate.py`; not run directly.
## 6a. `tro_overrides.py` — manual corrections to the derived TROs (library, no CLI)
`tro_flow.py` derives TRO types from the mechanical layout alone. Some things
are simply not in the CSV export: a switch that the export does not carry, a
scanner that lifts the type to `1Sep_SSCC`, or a spot that a walkthrough
identified as its own transfer point. Those findings come from a site review,
and they must survive the next run — the `.dot`/`.svg`/`.md`/`.ini` outputs are
all regenerated from scratch every time, so editing them is not an option.
They therefore live in **`%SKEL_CFG%\tro_overrides.ini`** (hand-maintained,
never generated). Section names are prefixed with the CSV file they apply to,
so several plants — and several exports of the same plant — can coexist:
```ini
[export.csv.tro.SEP0011] ; type correction, keyed on separator 0011
type = 1Sep1Swi ; a type from lib/tro_catalog.py
reason = Weiche vor Ort bestaetigt (Review 2026-08-10)
confidence = bestaetigt ; default when omitted
[mubea.csv.openpoint.Qualitaetskontrolle] ; explicitly NOT a TRO yet
x = -1026.00 ; plant coordinates in mm (CSV system)
y = 5250.05
z = 1478.50
expect = 1Sep1Swi ; type expected here
belongs_to = TRO09 ; TRO that owns the part today
note = ...
```
A fourth kind folds several derived TROs into one control object:
```ini
[export.csv.merge.Beruehrpunkt_K2_K1_gesamt]
type = 2Sep2Swi
separators = 0005, 0057 ; at least two, addressed by separator
host = 0002-R ; primary flow node; the others are kept
label = ...
reason = ...
```
A third kind creates a TRO the derivation missed entirely:
```ini
[export.csv.split.Beruehrpunkt_K2_K1] ; pull a separator out of its TRO
type = 1Sep1Swi
separators = 0057 ; comma-separated
scanners = ; optional, follow the separators
host = 0001-L ; flow node the new TRO sits on
label = ...
reason = ...
```
Three kinds of entry, deliberately different in weight:
- **`tro`** — changes the type of an already-derived TRO. Applied after the
TRO numbers are assigned (the sections address them by ID) and before the FB
block and component list are pulled from the catalog, so the type change
drags both along. A manually set type also suppresses the automatic
"check whether `1Sep_SSCC` is needed" hint for that TRO.
- **`split`** — creates a new TRO from separators taken out of an existing one.
For transfer points the derivation misses because the separator's
`Zuordnung` points at a conveyor (Strecke/Gefaellestrecke) instead of the
Kreisel lane it actually switches on. Applied *before* the numbers are
assigned, so the new TROs slot into the normal ordering — which means
**adding a split renumbers everything after it**. That is exactly why type
corrections should be keyed `SEP<number>`, not `TRO<number>`.
- **`merge`** — folds several TROs into one. Runs *after* the splits, so a
freshly split-out TRO can be part of a merge. The first affected TRO survives
and absorbs the others' separators, scanners and hosts; `host` picks which
flow node is primary, the rest stay attached so the linking still sees both
sides of a transition. Like splits, this renumbers.
- **`openpoint`** — a spot reported in a review that is **not** turned into a
TRO. The structure stays untouched; the point is only marked, in the doc
(section 6) and in the annotated drawing on layer `TRO_OPENPOINT` as a red
crossed circle with plain-text label. This keeps "derived" and "asserted"
distinguishable until the spot has been checked in BricsCAD.
Consumers: `tro_flow.py` (type corrections + doc section 6), `tro_annotate.py`
(corrected marker types + open-point markers), `material_flow.py` (writes the
`[<plant>.manual]` section into `connect.ini`). Unknown TRO IDs and types are
not applied silently — they are reported as warnings in the console output and
in doc section 6.3, which catches the common trap: **TRO IDs are running
numbers of one run and are only valid for the CSV named in the section.** Swap
the data basis and the numbers shift.
## 7. `dxf_registration.py` — CSV↔DXF coordinate registration (library, no CLI) ## 7. `dxf_registration.py` — CSV↔DXF coordinate registration (library, no CLI)
Derives the rigid transform (rotation + offset, no scaling) between the CSV Derives the rigid transform (rotation + offset, no scaling) between the CSV
@@ -235,6 +437,25 @@ Persists/loads the verified transform per CSV/DXF filename pair under
`%SKEL_CFG%\dxf_registration.json`. Used exclusively by `tro_annotate.py`; not `%SKEL_CFG%\dxf_registration.json`. Used exclusively by `tro_annotate.py`; not
run directly. run directly.
`ANCHOR_BLOCKS` holds **name patterns** (fnmatch, case-insensitive), not fixed
names, because two naming worlds occur in this project:
| Drawing | Separator blocks | Scanner blocks |
|---|---|---|
| original customer drawing (`500573_60_1.dxf`) | `S-SP`, `S-LP` | `SCAN` |
| macro-generated (`Mubea.dxf`, from dxfmakros) | `Separator_SP_2D`/`_3D` | `Scanner_2D`/`_3D` |
`Staustrecke_Separator_SP_*` is deliberately **not** an anchor: it is a
conveyor module with a built-in separator, so its insertion point is the module
origin rather than the separator, which would feed a second cluster — offset by
exactly that distance — into the vote.
Note that the two roles can carry slightly different systematic offsets (in
`Mubea.dxf`: separators land on +100/0, scanners on +400/0), so a mixed fit
shows a larger `residual_max` than either role alone. That is expected; the
mean residual is the number to watch, and only pairs within `FIT_TOL` (50 mm)
enter the Procrustes fit.
## 8. `gen_tro_graphs.py` — documentation image generator (doc tooling, not part of the generator) ## 8. `gen_tro_graphs.py` — documentation image generator (doc tooling, not part of the generator)
Lives under `doc/TRO_Katalog/tro_graphs/`, not `lib/` — it produces the Lives under `doc/TRO_Katalog/tro_graphs/`, not `lib/` — it produces the
+20
View File
@@ -0,0 +1,20 @@
# gereral TODOS
## 1. target: Workflow chain
bricscad layout
-> extract TROs to .svg/.dxf (sps_skel)
-> modify symbols in Brciscad (dxfmakros)
-> extract the new modified symbols from the .dxf into a .json (sps_skel)
-> create a .scl file from the .json (sps_skel)
## 2. use Mubea as basis test
- Create the material Flow (ask Simon about the entry point / exit makros ): done
- create a tro of the Mubea Layout in the given state :done
- discuss the tro result with Embiya and Thomas (results as .svg or in .dxf layer):done
- correct your program if necessary. Recreate Mubea until they think it is fine : done
- create scl files and try to import in TIA Portal : done
## 3. create another test case H & M
- extract the basic items and elements from the H&M Part 5 layout
- create a new test case with our makros
- use for testing the workflow chain and compare result with existing TRO code/concept
+12 -4
View File
@@ -69,11 +69,19 @@ __all__ = [
"registration_path", "registration_path",
] ]
# Rollen fuer den Lagebezug: CSV-Objektart -> Blocknamen im DXF. # Rollen fuer den Lagebezug: CSV-Objektart -> Blocknamen im DXF, als Muster
# Erweiterbar, wenn eine Zeichnung andere Bloecke verwendet. # (fnmatch, Gross-/Kleinschreibung egal). Es gibt zwei Namenswelten, und beide
# kommen im Projekt vor:
# - Original-Kundenzeichnung (z.B. 500573_60_1.dxf): S-SP / S-LP / SCAN
# - von den dxfmakros-Makros erzeugte Zeichnung (z.B. Mubea.dxf): die flachen
# Blocknamen mit Dimensionssuffix, also Separator_SP_2D/_3D, Scanner_2D/_3D
# Bewusst NICHT aufgenommen: Staustrecke_Separator_SP_*. Das ist ein Streckenmodul
# mit eingebautem Separator - sein Einfuegepunkt ist der Modulursprung, nicht der
# Separator, was einen zweiten, um genau diesen Versatz verschobenen Cluster in
# die Abstimmung braechte.
ANCHOR_BLOCKS: dict[str, tuple[str, ...]] = { ANCHOR_BLOCKS: dict[str, tuple[str, ...]] = {
"Separator": ("S-SP", "S-LP"), "Separator": ("S-SP", "S-LP", "Separator_SP*"),
"Scanner": ("SCAN",), "Scanner": ("SCAN", "Scanner_*"),
} }
# Rastergroesse der Abstimmung in mm # Rastergroesse der Abstimmung in mm
+472 -10
View File
@@ -77,6 +77,35 @@ CIRCLE_KINDS = ("Kreisel",)
# Objektarten, die per "Zuordnung" an einem Knoten haengen (kein eigener Knoten) # Objektarten, die per "Zuordnung" an einem Knoten haengen (kein eigener Knoten)
DEVICE_KINDS = ("Separator", "Scanner") DEVICE_KINDS = ("Separator", "Scanner")
# Ein-/Ausgabestationen (BTMT Be-/Entladung) = Materialquelle/-senke, d.h. die
# Ein-/Ausgangspunkte des Materialflusses. Sie werden in sps_skel erkannt;
# dxfmakros exportiert sie derzeit NICHT (Blockmuster fehlt), die Erkennung
# greift also, sobald eine passende Zeile im CSV auftaucht - erkannt an
# TeileArt/Bezeichnung: "Beladung"/"BTMT-Beladung" -> Eingang, "Entladung"/
# "SC_Entladung"/"Abwurf" -> Ausgang.
ENTRY_KINDS = ("Beladung",) # BTMT-Beladung -> Materialeingang (Quelle)
EXIT_KINDS = ("Entladung",) # SC_Entladung -> Materialausgang (Senke)
STATION_KINDS = ENTRY_KINDS + EXIT_KINDS
# Schluesselwoerter zur Erkennung der Stationen (Kleinschreibung, Teilstring)
STATION_PATTERNS = (
("Beladung", ("beladung", "beladestation", "btmt-beladung", "btmt beladung")),
("Entladung", ("entladung", "entladestation", "sc_entladung", "abwurf")),
)
# Transfer-/Weichenelemente aus dem erweiterten Export. Sie bilden KEINE eigenen
# Knoten, liefern aber die *gerichtete* Verbindung zwischen den Transportobjekten:
# Ausschleuselement = Kreisel -> Strecke (am Streckenanfang, Material verlaesst Kreisel)
# Einschleuselement = Strecke -> Kreisel (am Streckenende, Material laeuft in Kreisel)
# Weiche = Uebergang zwischen zwei Kreiseln
# Merkmale: {"Kreisel","Strecke","Streckenende"} bzw. {"Kreisel A","Kreisel B"}.
TRANSFER_KINDS = ("Einschleus", "Ausschleus", "Weiche")
TRANSFER_PATTERNS = (
("Ausschleus", ("ausschleus",)),
("Einschleus", ("einschleus",)),
("Weiche", ("weiche",)),
)
LANE_LEFT = "L" LANE_LEFT = "L"
LANE_RIGHT = "R" LANE_RIGHT = "R"
LANE_NAMES = {LANE_LEFT: "links", LANE_RIGHT: "rechts"} LANE_NAMES = {LANE_LEFT: "links", LANE_RIGHT: "rechts"}
@@ -104,6 +133,8 @@ NODE_STYLES = {
"Kreisel": dict(fillcolor="#2f5597", fontcolor="#ffffff", color="#1f3864"), "Kreisel": dict(fillcolor="#2f5597", fontcolor="#ffffff", color="#1f3864"),
"Gefaellestrecke": dict(fillcolor="#fbe5d6", fontcolor="#833c00", color="#c55a11"), "Gefaellestrecke": dict(fillcolor="#fbe5d6", fontcolor="#833c00", color="#c55a11"),
"Strecke": dict(fillcolor="#e2f0d9", fontcolor="#375623", color="#548235"), "Strecke": dict(fillcolor="#e2f0d9", fontcolor="#375623", color="#548235"),
"Beladung": dict(fillcolor="#d4f0d4", fontcolor="#1e5b1e", color="#338833"),
"Entladung": dict(fillcolor="#f4d4d4", fontcolor="#7a1f1f", color="#cc3333"),
} }
DEFAULT_NODE_STYLE = dict(fillcolor="#f2f2f2", fontcolor="#404040", color="#808080") DEFAULT_NODE_STYLE = dict(fillcolor="#f2f2f2", fontcolor="#404040", color="#808080")
@@ -152,6 +183,25 @@ class Element:
def is_device(self) -> bool: def is_device(self) -> bool:
return self.kind in DEVICE_KINDS return self.kind in DEVICE_KINDS
@property
def is_entry(self) -> bool:
"""BTMT-Beladung: Materialeingang (Quelle)."""
return self.kind in ENTRY_KINDS
@property
def is_exit(self) -> bool:
"""SC_Entladung: Materialausgang (Senke)."""
return self.kind in EXIT_KINDS
@property
def is_station(self) -> bool:
return self.kind in STATION_KINDS
@property
def is_transfer(self) -> bool:
"""Ein-/Ausschleuselement oder Weiche (liefert Richtung, kein Knoten)."""
return self.kind in TRANSFER_KINDS
@property @property
def height(self) -> float | None: def height(self) -> float | None:
"""Montage-/Einbauhoehe in Meter, falls ableitbar.""" """Montage-/Einbauhoehe in Meter, falls ableitbar."""
@@ -323,6 +373,25 @@ def parse_neighbours(raw: str) -> list[tuple[str, str | None]]:
return result return result
def _match_kind(patterns, *texts: str) -> str | None:
"""Erste Musterklasse, deren Schluesselwort in den Texten vorkommt."""
haystack = " ".join(t.lower() for t in texts if t)
for kind, keys in patterns:
if any(key in haystack for key in keys):
return kind
return None
def classify_station(*texts: str) -> str | None:
"""BTMT Be-/Entladestation an TeileArt/Bezeichnung erkennen (sonst None)."""
return _match_kind(STATION_PATTERNS, *texts)
def classify_transfer(*texts: str) -> str | None:
"""Ein-/Ausschleuselement bzw. Weiche erkennen (sonst None)."""
return _match_kind(TRANSFER_PATTERNS, *texts)
def read_elements(path: Path, warnings: list[str]) -> list[Element]: def read_elements(path: Path, warnings: list[str]) -> list[Element]:
"""CSV-Export einlesen.""" """CSV-Export einlesen."""
with path.open("r", encoding=CSV_ENCODING, newline="") as handle: with path.open("r", encoding=CSV_ENCODING, newline="") as handle:
@@ -343,6 +412,14 @@ def read_elements(path: Path, warnings: list[str]) -> list[Element]:
teile_art = (row.get("TeileArt") or "").strip() teile_art = (row.get("TeileArt") or "").strip()
kind = teile_art[len(TEILEART_PREFIX):] if teile_art.startswith(TEILEART_PREFIX) else teile_art kind = teile_art[len(TEILEART_PREFIX):] if teile_art.startswith(TEILEART_PREFIX) else teile_art
# BTMT Be-/Entladestation als Ein-/Ausgang erkennen (sps_skel-seitig)
station_kind = classify_station(teile_art, row.get("Bezeichnung") or "")
if station_kind:
kind = station_kind
# Ein-/Ausschleuselement bzw. Weiche erkennen (liefern Richtung)
transfer_kind = classify_transfer(teile_art, row.get("Bezeichnung") or "")
if transfer_kind:
kind = transfer_kind
raw_merkmale = (row.get("Merkmale") or "").strip() raw_merkmale = (row.get("Merkmale") or "").strip()
merkmale: dict = {} merkmale: dict = {}
@@ -395,6 +472,12 @@ def _build_nodes(elements: list[Element], warnings: list[str]) -> dict[str, Node
for lane in (LANE_LEFT, LANE_RIGHT): for lane in (LANE_LEFT, LANE_RIGHT):
node_id = f"{element.teile_id}-{lane}" node_id = f"{element.teile_id}-{lane}"
nodes[node_id] = Node(node_id, element.kind, element, lane=lane) nodes[node_id] = Node(node_id, element.kind, element, lane=lane)
elif element.is_station:
nodes[element.teile_id] = Node(element.teile_id, element.kind, element)
elif element.is_transfer:
# Ein-/Ausschleuselement / Weiche liefern nur die (gerichtete)
# Verbindung, siehe _apply_transfers - kein eigener Knoten.
continue
elif not element.is_device: elif not element.is_device:
warnings.append( warnings.append(
f"{element.describe()}: unbekannte TeileArt '{element.teile_art}' - kein Knoten erzeugt" f"{element.describe()}: unbekannte TeileArt '{element.teile_art}' - kein Knoten erzeugt"
@@ -611,6 +694,160 @@ def _orient(
return edges return edges
def _nearest_node(station: Element, nodes: dict[str, Node]) -> str | None:
"""Naechster Nicht-Stations-Knoten zur Station (2D-Abstand)."""
if station.x is None or station.y is None:
return None
best, best_d = None, None
for node_id, node in nodes.items():
if node.element.is_station or node.element.x is None or node.element.y is None:
continue
d = (node.element.x - station.x) ** 2 + (node.element.y - station.y) ** 2
if best_d is None or d < best_d:
best, best_d = node_id, d
return best
def _attach_stations(
elements: list[Element], nodes: dict[str, Node], warnings: list[str]
) -> list[Edge]:
"""
Ein-/Ausgabestationen (BTMT Be-/Entladung) mit dem Fluss verbinden.
Eingang (Beladung) ist Quelle: Kante Station -> Ziel.
Ausgang (Entladung) ist Senke: Kante Ziel -> Station.
Ziel kommt aus den 'Nachbarn' des Exports; fehlen sie (dxfmakros exportiert
fuer BTMT derzeit keine), wird das naechstgelegene Objekt per Abstand genommen.
"""
edges: list[Edge] = []
for element in elements:
if not element.is_station:
continue
sid = element.teile_id
if sid not in nodes:
continue
partners: list[str] = []
for base, lane in element.neighbours:
if lane and f"{base}-{lane}" in nodes:
partners.append(f"{base}-{lane}")
elif base in nodes:
partners.append(base)
else:
partners += [f"{base}-{L}" for L in (LANE_LEFT, LANE_RIGHT)
if f"{base}-{L}" in nodes]
if not partners:
nearest = _nearest_node(element, nodes)
if nearest:
partners = [nearest]
warnings.append(
f"{element.describe()}: keine Nachbarn im Export - "
f"naechstes Objekt '{nearest}' per Abstand verbunden"
)
else:
warnings.append(f"{element.describe()}: kein Ziel gefunden - unverbunden")
continue
for partner in partners:
if element.is_entry:
edges.append(Edge(sid, partner, EDGE_FLOW))
else:
edges.append(Edge(partner, sid, EDGE_FLOW))
return edges
def _kreisel_lane_node(kreisel_base: str, strecke: Element, nodes: dict[str, Node]) -> str | None:
"""Die konkrete Kreisel-Bahn (…-L/…-R), an der die Strecke haengt."""
for base, lane in strecke.neighbours:
if base == kreisel_base:
node_id = f"{base}-{lane}" if lane else base
return node_id if node_id in nodes else None
for lane in (LANE_LEFT, LANE_RIGHT): # Fallback: irgendeine Bahn
if f"{kreisel_base}-{lane}" in nodes:
return f"{kreisel_base}-{lane}"
return kreisel_base if kreisel_base in nodes else None
def _apply_transfers(
elements: list[Element], by_id: dict[str, Element],
nodes: dict[str, Node], edges: list[Edge], warnings: list[str]
) -> list[Edge]:
"""
Kreisel<->Strecke-Kanten anhand der Transferelemente gerichtet setzen.
Ausschleuselement: Kreisel -> Strecke (Streckenanfang).
Einschleuselement: Strecke -> Kreisel (Streckenende).
Weiche: Kreisel<->Kreisel als Weiche markieren.
Die so definierten Kanten sind massgeblich und ersetzen die aus Hoehe/
Nachbarn abgeleitete Richtung fuer genau diese Knotenpaare.
"""
# Transfer je Strecke sammeln: aus = Einlauf (Kreisel->Strecke, Anfang),
# ein = Auslauf (Strecke->Kreisel, Ende)
per_strecke: dict[str, dict[str, str]] = {}
weiche_pairs: list[set[str]] = []
for el in elements:
if el.kind in ("Ausschleus", "Einschleus"):
k = str(_merkmal(el.merkmale, "Kreisel") or "").strip()
s = str(_merkmal(el.merkmale, "Strecke") or "").strip()
if k and s:
role = "aus" if el.kind == "Ausschleus" else "ein"
per_strecke.setdefault(s, {})[role] = k
elif el.kind == "Weiche":
a = str(_merkmal(el.merkmale, "Kreisel A") or "").strip()
b = str(_merkmal(el.merkmale, "Kreisel B") or "").strip()
if a and b:
weiche_pairs.append({a, b})
directed: dict[frozenset, tuple[str, str]] = {}
for s, roles in per_strecke.items():
if s not in nodes or nodes[s].element.is_circle:
continue
strecke_el = by_id.get(s)
aus_lane = _kreisel_lane_node(roles["aus"], strecke_el, nodes) if roles.get("aus") else None
ein_lane = _kreisel_lane_node(roles["ein"], strecke_el, nodes) if roles.get("ein") else None
# Ruecklauf-Strecke (beide Enden am selben Kreisel): auf die zwei Bahnen verteilen
if aus_lane and aus_lane == ein_lane:
base = aus_lane.split("-")[0]
lanes = [f"{base}-{L}" for L in (LANE_LEFT, LANE_RIGHT) if f"{base}-{L}" in nodes]
if len(lanes) == 2:
aus_lane, ein_lane = lanes
warnings.append(
f"Strecke {s}: Ein- und Ausschleusung am selben Kreisel {base} "
f"(Ruecklauf) - Bahnen {lanes[0]}/{lanes[1]} angenommen"
)
if aus_lane: # Kreisel -> Strecke
directed[frozenset({aus_lane, s})] = (aus_lane, s, "Ausschleusung")
if ein_lane: # Strecke -> Kreisel
directed[frozenset({ein_lane, s})] = (s, ein_lane, "Einschleusung")
# Fuer Strecken mit Transferelementen sind ALLEIN diese massgeblich fuer die
# Kreisel<->Strecke-Verbindung. Jede weitere Kreisel<->Strecke-Kante aus den
# rohen Nachbarn (Bounding-Box-Ueberlappung, z. B. ein VF, der zufaellig neben
# einem dritten Kreisel liegt) ist ein Falschtreffer und wird verworfen.
covered = set(per_strecke)
result: list[Edge] = []
for e in edges:
if frozenset({e.src, e.dst}) in directed:
continue # wird gerichtet neu gesetzt
ends = {e.src, e.dst}
strecke_end = ends & covered
others = ends - strecke_end
if strecke_end and len(others) == 1:
other = next(iter(others))
if other in nodes and nodes[other].kind in CIRCLE_KINDS:
warnings.append(
f"Kante {other} <-> {next(iter(strecke_end))}: Kreisel<->Strecke "
f"ohne Transferelement - als Bounding-Box-Falschtreffer verworfen"
)
continue
result.append(e)
for (src, dst, note) in directed.values():
result.append(Edge(src, dst, EDGE_FLOW, note))
# Kreisel<->Kreisel-Kanten, die eine Weiche verbinden, markieren
for e in result:
if {e.src.split("-")[0], e.dst.split("-")[0]} in weiche_pairs:
e.note = "Weiche"
return result
def build_graph(elements: list[Element], source: str, warnings: list[str]) -> Graph: def build_graph(elements: list[Element], source: str, warnings: list[str]) -> Graph:
by_id = {element.teile_id: element for element in elements} by_id = {element.teile_id: element for element in elements}
nodes = _build_nodes(elements, warnings) nodes = _build_nodes(elements, warnings)
@@ -650,6 +887,12 @@ def build_graph(elements: list[Element], source: str, warnings: list[str]) -> Gr
edges.append(Edge(left, right, EDGE_CIRCLE, "unbestimmt")) edges.append(Edge(left, right, EDGE_CIRCLE, "unbestimmt"))
edges.append(Edge(right, left, EDGE_CIRCLE, "unbestimmt")) edges.append(Edge(right, left, EDGE_CIRCLE, "unbestimmt"))
# Ein-/Ausgabestationen (BTMT Be-/Entladung) als Quelle/Senke anbinden
edges.extend(_attach_stations(elements, nodes, warnings))
# Kreisel<->Strecke gerichtet aus den Transferelementen (massgeblich)
edges = _apply_transfers(elements, by_id, nodes, edges, warnings)
# Sackgassen und unerreichbare Bahnen melden. Sie entstehen, wenn ein # Sackgassen und unerreichbare Bahnen melden. Sie entstehen, wenn ein
# Kreisel nur in einer Richtung durchlaufen wird (Drehrichtung) und die # Kreisel nur in einer Richtung durchlaufen wird (Drehrichtung) und die
# Gegenrichtung nicht ueber aeussere Verbindungen geschlossen ist. # Gegenrichtung nicht ueber aeussere Verbindungen geschlossen ist.
@@ -685,6 +928,14 @@ def _node_label(node: Node) -> str:
element = node.element element = node.element
lines: list[str] = [] lines: list[str] = []
if element.is_station:
role = "Materialeingang" if element.is_entry else "Materialausgang"
lines.append(f"{element.name or element.kind} [{element.teile_id}]")
lines.append(f"{element.kind} - {role}")
if element.height is not None:
lines.append(f"h = {element.height:.3f} m")
return _dot_label(lines)
if node.lane: if node.lane:
name = _merkmal(element.merkmale, "Name") or element.name name = _merkmal(element.merkmale, "Name") or element.name
lines.append(f"{name} [{node.node_id}]") lines.append(f"{name} [{node.node_id}]")
@@ -716,12 +967,47 @@ def _node_label(node: Node) -> str:
return _dot_label(lines) return _dot_label(lines)
def render_dot(graph: Graph) -> str: # Zeichnungsmassstab fuer --use-cords: Graphviz rechnet in Punkt, die Anlage in
# Millimeter. 0.25 pt/mm (1:4) - wie in tro_flow.py, damit Material- und TRO-Graph
# im selben Massstab lagerichtig sind.
COORD_SCALE = 0.25
# Die beiden Bahnen (L/R) eines Kreisels teilen sich eine Position; im
# lagerichtigen Layout leicht versetzen, damit sie nicht uebereinander liegen.
LANE_NUDGE_MM = 350.0
def _node_xy(node: "Node") -> tuple[float, float] | None:
"""Anlagenkoordinate eines Knotens (Kreisel-Bahnen L/R leicht versetzt)."""
el = node.element
if el.x is None or el.y is None:
return None
x, y = el.x, el.y
if node.lane == "L":
y += LANE_NUDGE_MM
elif node.lane == "R":
y -= LANE_NUDGE_MM
return (x, y)
def _pos_attr(node: "Node", use_coords: bool) -> str:
"""pos-Attribut fuer 'neato -n', oder leer ohne --use-cords."""
if not use_coords:
return ""
xy = _node_xy(node)
if xy is None:
return ""
return f', pos="{xy[0] * COORD_SCALE:.2f},{xy[1] * COORD_SCALE:.2f}"'
def render_dot(graph: Graph, use_coords: bool = False) -> str:
out: list[str] = [] out: list[str] = []
add = out.append add = out.append
add("// Automatisch erzeugt von lib/material_flow.py - nicht manuell aendern.") add("// Automatisch erzeugt von lib/material_flow.py - nicht manuell aendern.")
add(f"// Quelle: {graph.source}") add(f"// Quelle: {graph.source}")
if use_coords:
add(f"// Lagerichtig: Knoten auf Anlagenkoordinate, {COORD_SCALE} pt/mm.")
add("// Rendern mit: neato -n -Tsvg <datei>.dot -o <datei>.svg")
add("digraph Materialfluss {") add("digraph Materialfluss {")
add(' graph [rankdir=LR, splines=spline, nodesep=0.25, ranksep=1.6,') add(' graph [rankdir=LR, splines=spline, nodesep=0.25, ranksep=1.6,')
add(' fontname="Segoe UI", fontsize=11, labelloc="t",') add(' fontname="Segoe UI", fontsize=11, labelloc="t",')
@@ -755,7 +1041,8 @@ def render_dot(graph: Graph) -> str:
style = NODE_STYLES.get(element.kind, DEFAULT_NODE_STYLE) style = NODE_STYLES.get(element.kind, DEFAULT_NODE_STYLE)
for node in sorted(lanes, key=lambda n: n.lane or ""): for node in sorted(lanes, key=lambda n: n.lane or ""):
attrs = ", ".join(f'{key}="{value}"' for key, value in style.items()) attrs = ", ".join(f'{key}="{value}"' for key, value in style.items())
add(f' "{node.node_id}" [label="{_node_label(node)}", {attrs}];') add(f' "{node.node_id}" [label="{_node_label(node)}", {attrs}'
f'{_pos_attr(node, use_coords)}];')
add(" { rank=same; " + " ".join(f'"{n.node_id}";' for n in lanes) + " }") add(" { rank=same; " + " ".join(f'"{n.node_id}";' for n in lanes) + " }")
add(" }") add(" }")
add("") add("")
@@ -763,7 +1050,8 @@ def render_dot(graph: Graph) -> str:
for node in plain: for node in plain:
style = NODE_STYLES.get(node.kind, DEFAULT_NODE_STYLE) style = NODE_STYLES.get(node.kind, DEFAULT_NODE_STYLE)
attrs = ", ".join(f'{key}="{value}"' for key, value in style.items()) attrs = ", ".join(f'{key}="{value}"' for key, value in style.items())
add(f' "{node.node_id}" [label="{_node_label(node)}", {attrs}];') add(f' "{node.node_id}" [label="{_node_label(node)}", {attrs}'
f'{_pos_attr(node, use_coords)}];')
add("") add("")
for edge in graph.edges: for edge in graph.edges:
@@ -774,17 +1062,30 @@ def render_dot(graph: Graph) -> str:
'arrowsize=0.9, fontcolor="#5b7fc7", ' 'arrowsize=0.9, fontcolor="#5b7fc7", '
f'tooltip="Kreisel-Umlauf {edge.note}"{label}]') f'tooltip="Kreisel-Umlauf {edge.note}"{label}]')
elif edge.kind == EDGE_UNRESOLVED: elif edge.kind == EDGE_UNRESOLVED:
weiche = ', xlabel="Weiche", fontcolor="#cc8800"' if edge.note == "Weiche" else ""
attrs = ('[color="#bf8f00", style=dashed, dir=both, ' attrs = ('[color="#bf8f00", style=dashed, dir=both, '
f'tooltip="Richtung {edge.note}"]') f'tooltip="Richtung {edge.note}"{weiche}]')
elif edge.note == "Weiche":
attrs = ('[color="#cc8800", penwidth=1.6, xlabel="Weiche", '
'fontcolor="#cc8800", tooltip="Weiche (Kreisel-Uebergang)"]')
else: else:
attrs = "[]" attrs = "[]"
add(f' "{edge.src}" -> "{edge.dst}" {attrs};') add(f' "{edge.src}" -> "{edge.dst}" {attrs};')
add("") add("")
legend_pos = ""
if use_coords:
xs = [xy[0] for n in graph.nodes.values() if (xy := _node_xy(n))]
ys = [xy[1] for n in graph.nodes.values() if (xy := _node_xy(n))]
if xs and ys:
lx, ly = min(xs), min(ys) - 3000.0 # etwas unterhalb der Anlage
legend_pos = (f' pos="{lx * COORD_SCALE:.2f},'
f'{ly * COORD_SCALE:.2f}",')
add(" subgraph cluster_legende {") add(" subgraph cluster_legende {")
add(' label="Legende"; style="rounded"; color="#a6a6a6";') add(' label="Legende"; style="rounded"; color="#a6a6a6";')
add(' fontname="Segoe UI"; fontsize=10; fontcolor="#404040";') add(' fontname="Segoe UI"; fontsize=10; fontcolor="#404040";')
add(' legende [shape=plaintext, style="", fillcolor="none", label=<' add(f' legende [shape=plaintext, style="", fillcolor="none",{legend_pos} label=<'
'<table border="0" cellborder="0" cellspacing="2" cellpadding="2">' '<table border="0" cellborder="0" cellspacing="2" cellpadding="2">'
'<tr><td align="left">durchgezogen</td><td align="left">Materialfluss</td></tr>' '<tr><td align="left">durchgezogen</td><td align="left">Materialfluss</td></tr>'
'<tr><td align="left"><font color="#8ea9db">gestrichelt blau</font></td>' '<tr><td align="left"><font color="#8ea9db">gestrichelt blau</font></td>'
@@ -800,6 +1101,120 @@ def render_dot(graph: Graph) -> str:
return "\n".join(out) + "\n" return "\n".join(out) + "\n"
# ---------------------------------------------------------------------------
# connect.ini (Item-/Flussebene) erzeugen
# ---------------------------------------------------------------------------
def _connect_kind(edge: Edge) -> str:
"""Kantenart fuer connect.ini."""
if edge.kind == EDGE_CIRCLE:
return "umlauf"
if edge.note == "Weiche":
return "weiche"
if edge.kind == EDGE_UNRESOLVED:
return "unbestimmt"
if edge.note in ("Einschleusung", "Ausschleusung"):
return edge.note.lower()
return "normal"
def render_connect_ini(graph: Graph, section: str = "Anlage", overrides=None) -> str:
"""
Materialfluss-Topologie als connect.ini (Item-/Flussebene).
Zwischenstand fuer die TRO-Ableitung: Knoten = Flussobjekte (Kreisel-Bahnen,
Strecke, Gefaellestrecke), Kanten = gerichteter Materialfluss. Von Hand
editierbar. Aufbau angelehnt an doc/TRO_Katalog/connect.ini.
`overrides` (aus cfg/tro_overrides.ini) wird als eigener Abschnitt
`[<Anlage>.manual]` angehaengt. Die Datei hier wird bei jedem Lauf neu
geschrieben - von Hand eingetragene Weichen waeren sonst weg. Der Abschnitt
beschreibt bewusst nur, WAS vor Ort bestaetigt wurde; er erfindet keine
Flusskanten, denn welche Kante zur Weiche gehoert, ist aus dem Layout nicht
ableitbar.
"""
out: list[str] = []
add = out.append
add("; ============================================================")
add("; connect.ini - Materialfluss-Topologie (Item-/Flussebene)")
add(f"; Automatisch erzeugt von lib/material_flow.py aus '{graph.source}'.")
add("; Zwischenstand fuer die TRO-Ableitung; von Hand editierbar.")
add(";")
add("; [<Anlage>.nodes] <Knoten> = <Art> | <Bezeichnung>")
add("; [<Anlage>.connections] cNNN = <von> -> <nach> | <JamArea> | <kind>")
add("; kind: normal | umlauf | weiche | einschleusung | ausschleusung | unbestimmt")
add("; [<Anlage>.externals] eNNN = EXTERN -> <Knoten> | <Info> (Zulauf/Beladung)")
add("; eNNN = <Knoten> -> EXTERN | <Info> (Ablauf/Entladung)")
add("; [<Anlage>.manual] vor Ort bestaetigte Angaben aus cfg/tro_overrides.ini")
add("; ============================================================")
add("")
add(f"[{section}.nodes]")
for node_id in sorted(graph.nodes):
node = graph.nodes[node_id]
el = node.element
if node.lane:
name = _merkmal(el.merkmale, "Name") or el.name
comment = f"{name} (Bahn {node.lane})"
else:
comment = el.name or el.kind
add(f"{node_id} = {node.kind} | {comment}")
add("")
add(f"[{section}.connections]")
for i, edge in enumerate(sorted(graph.edges, key=lambda e: (e.src, e.dst)), start=1):
add(f"c{i:03d} = {edge.src} -> {edge.dst} | | {_connect_kind(edge)}")
add("")
add(f"[{section}.externals]")
stations = [n for n in graph.nodes.values() if n.element.is_station]
if stations:
for j, node in enumerate(sorted(stations, key=lambda n: n.node_id), start=1):
if node.element.is_entry:
add(f"e{j:03d} = EXTERN -> {node.node_id} | {node.element.name}")
else:
add(f"e{j:03d} = {node.node_id} -> EXTERN | {node.element.name}")
else:
add("; Noch keine BTMT Be-/Entladestation im Layout - Ein-/Ausgang offen.")
add("; Kandidaten (offene Enden aus dem Materialfluss):")
incoming = {e.dst for e in graph.edges}
outgoing = {e.src for e in graph.edges}
for node_id in sorted(graph.nodes):
node = graph.nodes[node_id]
if node.element.is_station:
continue
if node_id not in incoming:
add(f"; Beladung? EXTERN -> {node_id} ({node.kind}, keine Zufuhr)")
if node_id not in outgoing:
add(f"; Entladung? {node_id} -> EXTERN ({node.kind}, kein Abfluss)")
add("")
if overrides is not None and (overrides.tros or overrides.splits
or overrides.open_points):
add(f"[{section}.manual]")
add("; Vor Ort bestaetigt, im CSV-Export nicht enthalten.")
add(f"; Quelle: {overrides.source}")
add("; Diese Zeilen werden aus der Override-Datei erzeugt - nicht hier")
add("; editieren, sonst sind sie beim naechsten Lauf wieder weg.")
for section_key, override in sorted(overrides.tros.items()):
if not override.type_name:
continue
wie = f"Separator {override.key}" if override.by_separator else override.key
add(f"{section_key} = {override.type_name} | {wie} | {override.reason}")
for spec in overrides.splits:
add(f"{spec.name} = NEU {spec.type_name} "
f"| Separator {', '.join(spec.separators)}"
+ (f" + Scanner {', '.join(spec.scanners)}" if spec.scanners else "")
+ (f" | Bahn {spec.host}" if spec.host else "")
+ f" | {spec.reason}")
for point in overrides.open_points:
add(f"{point.name} = OFFEN {point.expect or '?'} "
f"| x={point.x:.2f} y={point.y:.2f} "
f"| heute Teil von {point.belongs_to or '-'} "
f"| {point.note}")
add("")
return "\n".join(out) + "\n"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# SVG erzeugen # SVG erzeugen
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -851,9 +1266,13 @@ def graph_to_svg(
f"'{engine}' ist mit Code {result.returncode} fehlgeschlagen:\n" f"'{engine}' ist mit Code {result.returncode} fehlgeschlagen:\n"
f"{(result.stderr or result.stdout).strip()}" f"{(result.stderr or result.stdout).strip()}"
) )
# neato meldet fehlende Positionen nur als Warnung auf stderr # 'neato -n' gibt harmlose Warnungen auf stderr aus (z. B. sich beruehrende
if result.stderr.strip(): # Knoten -> gerade Kanten). Nur dann scheitern, wenn wirklich keine SVG
raise RuntimeError(f"'{engine}' meldet: {result.stderr.strip()}") # entstanden ist; sonst ist die Datei gueltig und die Warnung unkritisch.
if not svg_file.is_file() or svg_file.stat().st_size == 0:
raise RuntimeError(
f"'{engine}' hat keine SVG erzeugt: {result.stderr.strip() or '(keine Meldung)'}"
)
def dot_to_svg(dot_file: Path, svg_file: Path) -> None: def dot_to_svg(dot_file: Path, svg_file: Path) -> None:
@@ -1164,12 +1583,28 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
help="Aus der erzeugten DOT-Datei zusaetzlich eine SVG-Datei erzeugen " help="Aus der erzeugten DOT-Datei zusaetzlich eine SVG-Datei erzeugen "
"(benoetigt Graphviz 'dot').", "(benoetigt Graphviz 'dot').",
) )
parser.add_argument(
"--use-cords",
"--use-coords",
dest="use_cords",
action="store_true",
help="Jeden Knoten auf seine Anlagenkoordinate setzen und das SVG von "
f"Graphviz 'neato -n' erzeugen statt von 'dot' (lagerichtig, "
f"{COORD_SCALE} pt/mm). Nur mit --tosvg wirksam.",
)
parser.add_argument( parser.add_argument(
"--doc", "--doc",
action="store_true", action="store_true",
help="Dokumentation des Materialflusses als Markdown in %%SKEL_RESULTS%% " help="Dokumentation des Materialflusses als Markdown in %%SKEL_RESULTS%% "
"erzeugen (Anlagenobjekte, Verbindungen, Pruefungen).", "erzeugen (Anlagenobjekte, Verbindungen, Pruefungen).",
) )
parser.add_argument(
"--connect",
action="store_true",
help="Materialfluss-Topologie zusaetzlich als connect.ini (Item-/Flussebene) "
"nach %%SKEL_RESULTS%% schreiben (Knoten, gerichtete Verbindungen, "
"Ein-/Ausgang-Kandidaten) - Zwischenstand fuer die TRO-Ableitung.",
)
return parser.parse_args(argv) return parser.parse_args(argv)
@@ -1210,13 +1645,26 @@ def main(argv: list[str] | None = None) -> int:
svg_file = results / f"{stem}_material_flow.svg" svg_file = results / f"{stem}_material_flow.svg"
doc_target = results / f"{stem}_material_flow.md" doc_target = results / f"{stem}_material_flow.md"
# --use-cords braucht an jedem Knoten eine Koordinate
if args.use_cords:
without = [n.node_id for n in graph.nodes.values() if _node_xy(n) is None]
if without:
print(f"FEHLER: --use-cords nicht moeglich, {len(without)} Knoten ohne "
f"Koordinate: {', '.join(without)}", file=sys.stderr)
print(" Ohne pos an jedem Knoten kann 'neato -n' nicht zeichnen.",
file=sys.stderr)
return 1
existing_svg = svg_file if svg_file.is_file() else None existing_svg = svg_file if svg_file.is_file() else None
dot_file.write_text(render_dot(graph), encoding="utf-8") dot_file.write_text(render_dot(graph, use_coords=args.use_cords), encoding="utf-8")
svg_error: str | None = None svg_error: str | None = None
if args.tosvg: if args.tosvg:
try: try:
dot_to_svg(dot_file, svg_file) if args.use_cords:
graph_to_svg(dot_file, svg_file, engine="neato", no_op=1)
else:
dot_to_svg(dot_file, svg_file)
except RuntimeError as exc: except RuntimeError as exc:
svg_error = str(exc) svg_error = str(exc)
created_svg = svg_file if args.tosvg and svg_error is None else None created_svg = svg_file if args.tosvg and svg_error is None else None
@@ -1229,6 +1677,20 @@ def main(argv: list[str] | None = None) -> int:
) )
doc_file = doc_target doc_file = doc_target
connect_file = None
if args.connect:
# Spaet importiert: tro_overrides zieht tro_catalog nach, das
# material_flow sonst nicht braucht.
from tro_overrides import load_overrides
overrides = load_overrides(csv_file.name, env_dir("SKEL_CFG", "cfg"))
connect_target = results / f"{stem}_connect.ini"
connect_target.write_text(
render_connect_ini(graph, section=stem, overrides=overrides),
encoding="utf-8",
)
connect_file = connect_target
report( report(
graph, graph,
findings, findings,
+431
View File
@@ -0,0 +1,431 @@
# -*- coding: utf-8 -*-
# Absichtlich ohne Shebang: der Windows-py-Launcher wuerde daraus "python3"
# ableiten und scheitert, wenn keine PythonCore-Installation registriert ist.
"""
scl_skeleton.py - FB_Main-Geruest als SCL aus der TRO-Liste erzeugen.
Letzter Schritt der Kette:
material_flow.py Objekte -> Materialfluss
tro_flow.py Fluss -> TROs
tro_annotate.py TROs -> Marker in der Zeichnung
tro_extract.py Zeichnung -> JSON
scl_skeleton.py JSON -> FB_Main.scl <- hier
Erzeugt wird ein **Geruest**, kein fertiges Programm: je TRO eine REGION mit
Instanzaufruf und allen Parameterzeilen in der richtigen Reihenfolge, so wie es
die realen Aufrufstellen in doc/TRO_Katalog/scl_templates/*.scl zeigen.
Was aus dem Layout kommt, steht drin. Was aus der E-Planung kommen muss, steht
als Platzhalter mit `TODO(E-Planung)` da - bewusst als offensichtliche Luecke
und nicht als erfundener Wert, denn eine plausibel aussehende falsche
Sensoradresse ist schlimmer als eine leere. Alle Platzhalter sind ueber genau
diesen Marker auffindbar.
Ableitbar und daher gefuellt:
- Reihenfolge der REGIONs (Materialfluss), Instanzliste, FB-Typ je TRO
- Anzahl der Separator-/Weichen-/Scanner-Parameterbloecke je TRO
- nTo1Destinations einer Weiche = Anzahl ihrer Abgaenge
- die Zielliste je Weichenabgang (Erreichbarkeit im TRO-Graphen)
- Querverweis auf die Bauteile im Layout (Separatornummern, Koordinate)
Aufruf ueber bin/scl_skeleton.bat bzw. bin/scl_skeleton.sh.
"""
from __future__ import annotations
import argparse
import json
import sys
from collections import deque
from datetime import datetime
from pathlib import Path
from material_flow import env_dir
TODO = "TODO(E-Planung)"
# Zeitwerte aus den Referenz-Aufrufstellen. Sie sind plausible Startwerte, KEINE
# fuer diese Anlage ermittelten - deshalb ueberall mit Pruefhinweis.
DEFAULT_SEP_TIMES = (
("tDelayToNextItem", '"TimeForSepWaiting_Short"', None),
("tTrailingTime", "t#600ms", "pruefen"),
("tHandlingTime", "t#200ms", "pruefen"),
("tJam", '"TimeForSepJam"', None),
)
def load_tros(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
def flow_order(tros: list[dict], start_id: str = "") -> tuple[list[dict], str]:
"""TROs in Fliessrichtung sortieren.
Startpunkt ist ein TRO ohne Vorgaenger - also die Stelle, an der Material
in die Anlage kommt. Solange Be-/Entladestation fehlen, ist der Fluss ein
geschlossener Kreis und es gibt keinen solchen TRO; dann muss der Kreis
irgendwo aufgetrennt werden. Das ist eine willkuerliche Wahl, deshalb wird
sie zurueckgegeben und im Kopf der SCL vermerkt (und laesst sich mit
--start ueberschreiben). Nicht erreichte TROs haengt die Funktion hinten
an, damit nie einer verlorengeht.
"""
nach_id = {t["id"]: t for t in tros}
ohne_vorgaenger = sorted(t["id"] for t in tros if not t.get("predecessors"))
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)"
else:
start = [min(nach_id)]
grund = ("Kreislauf ohne Eingang - willkuerlich aufgetrennt; sobald "
"Be-/Entladestation im Export stehen, ergibt sich der Anfang")
gesehen: list[str] = []
queue = deque(start)
while queue:
tro_id = queue.popleft()
if tro_id in gesehen:
continue
gesehen.append(tro_id)
for nxt in nach_id.get(tro_id, {}).get("successors", []):
if nxt not in gesehen:
queue.append(nxt)
for tro_id in sorted(nach_id):
if tro_id not in gesehen:
gesehen.append(tro_id)
return [nach_id[i] for i in gesehen], grund
def reachable_via(start: str, first: str, tros: dict[str, dict]) -> list[str]:
"""Welche TROs sind ueber diesen Abgang erreichbar?
Das ist die Zielliste eines Weichenabgangs: von `first` aus weiterlaufen,
ohne ueber die Weiche selbst zurueckzugehen. Genau die Information, die
FC_Direction braucht, um zu entscheiden - und sie steht im Graphen.
"""
gesehen: set[str] = set()
queue = deque([first])
while queue:
node = queue.popleft()
if node in gesehen or node == start:
continue
gesehen.add(node)
queue.extend(tros.get(node, {}).get("successors", []))
return sorted(gesehen)
def region_title(tro: dict) -> str:
seps = ", ".join(tro.get("separators", []))
teile = f" Sep {seps}" if seps else ""
return f"{tro['id']} ({tro['type']}){teile}"
def emit_separators(add, tro: dict, anzahl: int, hat_scanner: bool) -> None:
seps = tro.get("separators", [])
for i in range(1, anzahl + 1):
pfad = f"#{tro['id']}.stInSeparator{i}"
layout = seps[i - 1] if i <= len(seps) else "?"
add(f" // Separator {i} = Bauteil {layout} aus dem Layout")
add(f' {pfad}.Settings.nSeparatorNo := "cSep_____"; // {TODO}')
for name, wert, hinweis in DEFAULT_SEP_TIMES:
kommentar = f" // {TODO}: {hinweis}" if hinweis else ""
add(f" {pfad}.Settings.{name} := {wert};{kommentar}")
add(f" {pfad}.Settings.xPluggedJam := TRUE;")
add(f" {pfad}.Settings.xPluggedPart := FALSE;")
add(f" {pfad}.Settings.xPluggedScanner := "
f"{'TRUE' if hat_scanner else 'FALSE'};")
add(f' {pfad}.stSenInSep := "DB_Inputs".Sensors["cInBG____"]; // {TODO}')
add(f" {pfad}.Settings.tSenFree := T#1500ms; // {TODO}: pruefen")
add(f" {pfad}.Settings.tSenWait := T#4000ms; // {TODO}: pruefen")
add("")
def emit_switches(add, tro: dict, anzahl: int, alle: dict[str, dict]) -> None:
abgaenge = tro.get("successors", [])
for j in range(1, anzahl + 1):
pfad = f"#{tro['id']}.stInSwitch{j}"
add(f' {pfad}.Settings.nSwitchNo := "cSwi_____"; // {TODO}')
add(f" {pfad}.Settings.xPluggedExSen3 := FALSE;")
add(f" {pfad}.Settings.xPluggedExSen4 := FALSE;")
if anzahl == 1:
# Eine Weiche bedient alle Abgaenge - direkt ableitbar.
add(f" {pfad}.Settings.nTo1Destinations := {len(abgaenge)};"
f" // Abgaenge laut Topologie")
else:
# Bei mehreren Weichen ist es eine Planungsentscheidung, welche
# Weiche welche Abgaenge bedient - die Summe ist bekannt, die
# Aufteilung nicht. Deshalb hier kein geratener Wert.
add(f" {pfad}.Settings.nTo1Destinations := _;"
f" // {TODO}: dieser TRO hat {len(abgaenge)} "
f"Abgaenge auf {anzahl} Weichen - Aufteilung festlegen")
add("")
if abgaenge:
add(" // Zielliste je Abgang (aus dem TRO-Graphen berechnet) -")
add(" // Grundlage fuer die Zuordnung Ziel -> Richtung in FC_Direction:")
for abgang in abgaenge:
ziele = reachable_via(tro["id"], abgang, alle)
add(f" // ueber {abgang}: {', '.join(ziele) if ziele else '-'}")
add("")
def emit_vario(add, tro: dict) -> None:
pfad = f"#{tro['id']}.stInVario"
add(f" {pfad}.Settings.xContinousEmptying := FALSE;")
add(f" {pfad}.Settings.tWaitForJog := T#300ms; // {TODO}: pruefen")
add(f" {pfad}.Settings.tConvEmpty := T#45000ms; // {TODO}: pruefen")
add(f" {pfad}.Settings.nCountCarrier := 1; // {TODO}: pruefen")
add(f" {pfad}.Settings.tWaitForStop := t#3s;")
add(f" {pfad}.Settings.tCarrierInPosDelay := t#1200ms;")
add(f" {pfad}.Settings.tCarrierInPosEdgeDelay := t#100ms;")
add(f" {pfad}.Settings.xJamSenDelayActive := TRUE;")
add(f" {pfad}.Settings.tJamSenDelay := T#2s;")
add(f' {pfad}.stSenCarrInPos := "DB_Inputs".Sensors["cInBG____"]; // {TODO}')
add(f' {pfad}.stSenFinger := "DB_Inputs".Sensors["cInBG____"]; // {TODO}')
add(f' {pfad}.stSenLastPos := "DB_Inputs".Sensors["cInBG____"]; // {TODO}')
add(f' {pfad}.stSenJam := "DB_Inputs".Sensors["cInBG____"]; // {TODO}')
add("")
def emit_barcode(add, tro: dict, anzahl: int) -> None:
for i in range(1, anzahl + 1):
suffix = "" if anzahl == 1 else str(i)
pfad = f"#{tro['id']}.stInBarcodeReader{suffix}"
add(f" {pfad}.Settings.xActivateCheckASCI := TRUE;")
add(f" {pfad}.Settings.tTimeout := T#3000ms;")
add(f' // {TODO}: Cognex-Interface verdrahten '
f'(#arCognexInterface["cScanner____"])')
add("")
def emit_call(add, tro: dict, items: dict[str, int]) -> None:
"""Aufrufstelle schreiben.
Die Zeilen werden als (Code, Kommentar) gefuehrt und das Komma erst beim
Zusammensetzen gesetzt - sonst landet es hinter dem "//" und ist Teil des
Kommentars, womit die Parameterliste nicht mehr uebersetzt.
"""
tro_id = tro["id"]
zeilen: list[tuple[str, str]] = [
('nInMainTroNo := "cMainTro____"', TODO),
('xInSftyOk := "DB_InterfaceSafety".stFromSafety.EStopMain.Q', TODO),
("xInAllRdyToStart := TRUE", ""),
(f'xInRelease := "DB_TRO_TEST".{tro_id}', ""),
]
if items.get("VarioDrive"):
zeilen.append(('xInMotorProtection := "FC____"', TODO))
zeilen.append(("xInAutomatic := #t_automatik", ""))
zeilen.append(("stInOutMachineState := #stInOutControlUnitCabinet", ""))
zeilen.append(("stInOutCognexInterface := #stCognexInterfaceDummy", ""))
# Staubereiche: je ankommender/abgehender Verbindung einer. Welche
# DB-Struktur dahintersteht, ist eine Planungsentscheidung - hier steht
# 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}"))
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}"))
zeilen.append((f'stInOutJamExit{n} := "DB_JamArea____".stJam____.stData', ""))
for i in range(1, items.get("Separator", 1) + 1):
zeilen.append((f'xOutStopper{i} => "MB____"', TODO))
if items.get("VarioDrive"):
zeilen.append(('xOutVarioMotor => "MA____"', TODO))
for j in range(1, items.get("Switch", 0) + 1):
zeilen.append((f'xOutSw{j}ExTo3 => "MB____"', TODO))
zeilen.append((f'stInOutHMI := "DB_Interface_HMI".stTRO.{tro_id}', ""))
zeilen.append(('nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"]',
TODO))
kopf = f" #{tro_id}("
einzug = " " * len(kopf)
for index, (code, kommentar) in enumerate(zeilen):
trenner = ");" if index == len(zeilen) - 1 else ","
prefix = kopf if index == 0 else einzug
text = f"{prefix}{code}{trenner}"
add(f"{text} // {kommentar}" if kommentar else text)
def emit_direction(add, tro: dict, anzahl: int) -> None:
"""Weichenlogik - je Weiche ein FC_Direction-Aufruf."""
add("")
add(" REGION Direction")
for j in range(1, anzahl + 1):
ziel = (f"#{tro['id']}.nInDirection" if anzahl == 1
else f"#{tro['id']}.nInDirection{j}")
add(f' "FC_Direction"(nInSwitchNo := "cSwi_____", // {TODO}')
add(f" nOutDirection => {ziel},")
add(f' InOutCarrier := "DB_JamArea____".stJam____'
f".arCarrier[1]);")
if j < anzahl:
add("")
add(" END_REGION")
def render(daten: dict, quelle: str, start_id: str = "") -> str:
tros = daten["tros"]
alle = {t["id"]: t for t in tros}
geordnet, startgrund = flow_order(tros, start_id)
out: list[str] = []
add = out.append
add("// ============================================================")
add("// FB_Main - GERUEST, erzeugt von lib/scl_skeleton.py")
add(f"// Quelle : {quelle}")
add(f"// Erzeugt: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
add("//")
add("// Das ist KEIN lauffaehiges Programm, sondern ein Geruest zum")
add("// Ausfuellen. Struktur, Reihenfolge und Bauteilzahl stammen aus dem")
add("// Anlagenlayout und der vor Ort aufgenommenen Topologie.")
add("//")
add(f"// Jede Stelle, die aus der E-Planung kommen muss, ist mit")
add(f"// '{TODO}' markiert - danach suchen, dann ist nichts vergessen.")
add("// Offen sind durchgaengig: Sensor-/Aktoradressen (cInBG/MB/MA),")
add("// Separator- und Weichennummern, MainTro-Nummern, die Staubereiche")
add("// (DB_JamArea...) und die Zeiten, soweit als 'pruefen' vermerkt.")
add("//")
add("// Reihenfolge der REGIONs = Materialfluss.")
add(f"// Anfang bei {geordnet[0]['id']}: {startgrund}.")
add("// ============================================================")
add("")
add('FUNCTION_BLOCK "FB_Main"')
add("{ S7_Optimized_Access := 'TRUE' }")
add("VERSION : 0.1")
add(" VAR ")
for tro in geordnet:
fb = tro.get("fbType") or "FB_UNBEKANNT"
kommentar = f" // {tro['type']}"
if tro.get("separators"):
kommentar += f", Sep {', '.join(tro['separators'])}"
add(f' {tro["id"]} : "{fb}";{kommentar}')
add(" END_VAR")
add("")
add("BEGIN")
for tro in geordnet:
items = tro.get("items", {})
n_sep = items.get("Separator", 1)
n_swi = items.get("Switch", 0)
n_scan = items.get("Scanner", 0)
add("")
add(f"REGION {region_title(tro)}")
pos = tro.get("position")
if pos:
add(f" // Anlagenkoordinate x/y: {pos['x']:.0f} / {pos['y']:.0f} mm")
add(f" // Vorgaenger: {', '.join(tro.get('predecessors', [])) or '-'}")
add(f" // Nachfolger: {', '.join(tro.get('successors', [])) or '-'}")
if tro.get("confidence"):
add(f" // Einstufung: {tro['confidence']}")
add("")
emit_separators(add, tro, n_sep, hat_scanner=bool(n_scan))
if n_swi:
emit_switches(add, tro, n_swi, alle)
if items.get("VarioDrive"):
emit_vario(add, tro)
if n_scan:
emit_barcode(add, tro, n_scan)
if items.get("StorageLine"):
add(f" // {TODO}: Linienspeicher mit {items['StorageLine']} Linien - "
f"Parametrierung je Linie")
add("")
add(" #%s.stInPriorityManager.xReleaseOk := TRUE;" % tro["id"])
add("")
emit_call(add, tro, items)
if n_swi:
emit_direction(add, tro, n_swi)
add("END_REGION")
add("")
add("END_FUNCTION_BLOCK")
add("")
return "\n".join(out)
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
results = env_dir("SKEL_RESULTS", "results")
json_file = Path(args.json)
if not json_file.is_absolute() and json_file.parent == Path("."):
json_file = results / args.json
if not json_file.is_file():
print(f"FEHLER: JSON nicht gefunden: {json_file}", file=sys.stderr)
print(" Zuerst bin/tro_extract.bat laufen lassen.", file=sys.stderr)
return 1
try:
daten = load_tros(json_file)
except (OSError, ValueError) as exc:
print(f"FEHLER: {json_file} nicht lesbar: {exc}", file=sys.stderr)
return 1
if not daten.get("tros"):
print(f"FEHLER: {json_file} enthaelt keine TROs.", file=sys.stderr)
return 1
text = render(daten, json_file.name, args.start)
out_file = results / (args.out or f"{json_file.stem.replace('_tro', '')}_FB_Main.scl")
out_file.write_text(text, encoding="utf-8")
offen = text.count(TODO)
geordnet, startgrund = flow_order(daten["tros"], args.start)
print("")
print("================================================================")
print("FB_MAIN-GERUEST")
print("================================================================")
print(f"Quelle = {json_file}")
print(f"TROs = {len(daten['tros'])}")
print(f"Reihenfolge = {' -> '.join(t['id'] for t in geordnet)}")
print(f"Anfang = {geordnet[0]['id']} ({startgrund})")
print(f"Offene Stellen = {offen} (Marker '{TODO}')")
print(f"SCL = {out_file}")
print("================================================================")
print("")
return 0
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="scl_skeleton",
description="Erzeugt aus der extrahierten TRO-Liste ein FB_Main-Geruest "
"als SCL zum Import in TIA Portal.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="Beispiele:\n"
" scl_skeleton.bat\n"
" scl_skeleton.bat --json export_tro.json --out FB_Main.scl\n"
"\n"
"Exit-Codes:\n"
" 0 ok\n"
" 1 Eingabe- oder Aufruffehler\n",
)
parser.add_argument(
"--json",
default="export_tro.json",
metavar="NAME",
help="TRO-JSON aus tro_extract.py in %%SKEL_RESULTS%% "
"(oder ein vollstaendiger Pfad). Standard: %(default)s",
)
parser.add_argument(
"--out",
metavar="NAME",
help="Name der SCL-Datei in %%SKEL_RESULTS%%. Standard: <Quelle>_FB_Main.scl",
)
parser.add_argument(
"--start",
default="",
metavar="TRO",
help="TRO, bei dem die Reihenfolge beginnt. Nur noetig, solange der "
"Fluss ein geschlossener Kreis ohne Be-/Entladestation ist.",
)
return parser.parse_args(argv)
if __name__ == "__main__":
sys.exit(main())
+17
View File
@@ -152,6 +152,9 @@ class TroSymbol:
HEXAGON = "hexagon" # Sechseck HEXAGON = "hexagon" # Sechseck
RECT = "rect" # liegendes Rechteck RECT = "rect" # liegendes Rechteck
ARROW = "arrow" # Pfeil/Fahne ARROW = "arrow" # Pfeil/Fahne
# Dreieck mit zweitem Dreieck innen - fuer 2Sep2Swi, sichtbar verwandt mit
# TRIANGLE_DOWN (2Sep1Swi) und trotzdem davon unterscheidbar.
DOUBLE_TRIANGLE_DOWN = "double_triangle_down"
@classmethod @classmethod
def all(cls) -> tuple[str, ...]: def all(cls) -> tuple[str, ...]:
@@ -508,6 +511,20 @@ TRO_CATALOG: tuple[TroDefinition, ...] = (
TroItem.SWITCH: 1, TroItem.SWITCH: 1,
TroItem.SCANNER: 2, TroItem.SCANNER: 2,
}, STYLE_SWI, TroSymbol.TRIANGLE_DOWN), }, STYLE_SWI, TroSymbol.TRIANGLE_DOWN),
# ACHTUNG - NICHT aus SCL belegt. Alle Typen oberhalb stammen aus
# vorhandenem Code bzw. der Mapping-Tabelle in TRO_Typen.md; dieser hier
# wurde am 2026-08-12 auf Anforderung aus dem Projekt 500573 (Mubea)
# aufgenommen, um zwei Uebergabestellen am Beruehrpunkt Kreisel2/Kreisel1
# zu einem Steuerobjekt zusammenzufassen.
# Der Baustein FB_ILS_MTRO_2Sep2Swi existiert in diesem Repository nicht
# und ist auch in doc/TRO_Katalog/scl_templates/ nicht hinterlegt - er muss
# in der ILSLib angelegt werden, sonst erzeugt ein spaeterer
# SCL-Generator einen Aufruf auf einen fehlenden Baustein.
TroDefinition("2Sep2Swi", "FB_ILS_MTRO_2Sep2Swi", {
TroItem.SEPARATOR: 2,
TroItem.SWITCH: 2,
TroItem.SCANNER: 2,
}, STYLE_SWI, TroSymbol.DOUBLE_TRIANGLE_DOWN),
) )
_BY_NAME: dict[str, TroDefinition] = {t.get_name(): t for t in TRO_CATALOG} _BY_NAME: dict[str, TroDefinition] = {t.get_name(): t for t in TRO_CATALOG}
+331
View File
@@ -0,0 +1,331 @@
# -*- coding: utf-8 -*-
# Absichtlich ohne Shebang: der Windows-py-Launcher wuerde daraus "python3"
# ableiten und scheitert, wenn keine PythonCore-Installation registriert ist.
"""
tro_extract.py - TROs aus der annotierten Zeichnung zurueck nach JSON lesen.
Gegenrichtung zu tro_annotate.py:
tro_annotate.py CSV + Ableitung -> Marker in der Zeichnung
tro_extract.py Zeichnung -> JSON
Gelesen wird ausschliesslich die Zeichnung, nicht der CSV-Export. Damit
kommt zurueck, was tatsaechlich in der Zeichnung steht - also auch das, was
in BricsCAD von Hand nachgezogen wurde (Marker verschoben, Typ ueber TRO_EDIT
geaendert). Genau dafuer ist der Weg gedacht: die Zeichnung ist nach dem
Review der Stand der Dinge, nicht der Export.
Erkannt werden die Objekte, die tro_annotate.py mit seinen XDATA (AppID
SPS_SKEL_TRO) gekennzeichnet hat:
Blockreferenz TRO_SYM_<Typ> ein TRO, Attribute ID/TYPE/ITEMS/
CONFIDENCE/SEPARATORS (+ FB_BLOCK mit --fb)
LWPOLYLINE "<von>-><nach>" ein Flusspfeil = eine Verbindung
Kreis/Linien "OFFEN:<Name>" ein offener Punkt (noch kein TRO)
Die Koordinaten stehen doppelt in der Datei: `positionDxf` ist die
Zeichnungskoordinate, `position` die Anlagenkoordinate. Umgerechnet wird mit
dem Lagebezug aus <stem>_registration.json (von tro_annotate.py geschrieben);
fehlt der, bleibt `position` leer statt geraten zu werden.
Aufruf ueber bin/tro_extract.bat bzw. bin/tro_extract.sh.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from collections import Counter
from datetime import datetime
from pathlib import Path
import ezdxf
from dxf_registration import Transform
from material_flow import env_dir
from tro_catalog import get_tro
APPID = "SPS_SKEL_TRO"
BLOCK_PREFIX = "TRO_SYM_"
OPEN_PREFIX = "OFFEN:"
# XDATA-Nutzlast eines Flusspfeils: "<von>-><nach>"
EDGE_RE = re.compile(r"^(?P<src>[^>]+)->(?P<dst>.+)$")
# "1x SSCC, 1x Scanner, 1x Separator" -> {"SSCC": 1, "Scanner": 1, ...}
ITEM_RE = re.compile(r"(?P<count>\d+)\s*x\s*(?P<item>.+)")
def xdata_payload(entity) -> str | None:
"""Zweiter String der XDATA von tro_annotate, sonst None.
Aufbau: (1000, "tro_annotate"), (1000, <Nutzlast>). Die Nutzlast ist je
nach Objekt die TRO-ID, "<von>-><nach>" oder "OFFEN:<Name>".
"""
try:
tags = entity.get_xdata(APPID)
except (ezdxf.DXFValueError, ezdxf.DXFTypeError, ezdxf.DXFError):
return None
strings = [str(value) for code, value in tags if code == 1000]
if len(strings) < 2 or strings[0] != "tro_annotate":
return None
return strings[1]
def parse_items(text: str) -> dict[str, int]:
"""Bauteilliste aus dem ITEMS-Attribut zurueckgewinnen."""
items: dict[str, int] = {}
for part in (text or "").split(","):
match = ITEM_RE.match(part.strip())
if match:
items[match.group("item").strip()] = int(match.group("count"))
return items
def attrib(ref, tag: str) -> str:
"""Attributwert einer Blockreferenz (leerer String, wenn nicht da)."""
try:
return (ref.get_attrib_text(tag) or "").strip()
except Exception:
return ""
def load_transform(dxf_file: Path, results: Path, warnings: list[str]) -> Transform | None:
"""Lagebezug zu dieser Zeichnung suchen.
tro_annotate.py benennt die Datei nach dem CSV-Stamm (export_registration
.json), die Zeichnung heisst aber <stamm>_annotated.dxf - daher wird das
Suffix "_annotated" zusaetzlich ohne Endung probiert. Zuletzt entscheidet
der Inhalt: eine Registrierung, deren `dxf_file` auf diese Zeichnung passt,
gilt auch dann, wenn der Dateiname anders lautet.
"""
stem = dxf_file.stem
namen = [stem]
if stem.endswith("_annotated"):
namen.append(stem[: -len("_annotated")])
kandidaten = [results / f"{n}_registration.json" for n in namen]
kandidaten += [dxf_file.with_name(f"{n}_registration.json") for n in namen]
kandidaten += sorted(results.glob("*_registration.json"))
for candidate in kandidaten:
if not candidate.is_file():
continue
try:
roh = json.loads(candidate.read_text(encoding="utf-8"))
transform = Transform(**roh)
except (OSError, ValueError, TypeError) as exc:
warnings.append(f"{candidate.name} nicht lesbar ({exc})")
continue
# Bei den aus dem Glob gefundenen Dateien pruefen, ob sie ueberhaupt
# zu dieser Zeichnung gehoeren - sonst waere der Bezug frei erfunden.
passt = candidate.stem.split("_registration")[0] in namen
if not passt and Path(roh.get("dxf_file", "")).stem not in (stem, *namen):
continue
return transform
warnings.append(
f"kein Lagebezug gefunden (gesucht: "
f"{', '.join(n + '_registration.json' for n in namen)}) - "
f"'position' bleibt leer, 'positionDxf' ist trotzdem gefuellt"
)
return None
def extract(doc, transform: Transform | None, warnings: list[str]) -> dict:
"""TROs, Verbindungen und offene Punkte aus dem Modelbereich lesen."""
msp = doc.modelspace()
tros: list[dict] = []
edges: list[dict] = []
open_points: list[dict] = []
seen_edges: set[tuple[str, str]] = set()
for entity in msp:
payload = xdata_payload(entity)
if payload is None:
continue
if entity.dxftype() == "INSERT" and entity.dxf.name.startswith(BLOCK_PREFIX):
tro_id = attrib(entity, "ID") or payload
type_name = attrib(entity, "TYPE") or entity.dxf.name[len(BLOCK_PREFIX):]
insert = entity.dxf.insert
eintrag = {
"id": tro_id,
"type": type_name,
"items": parse_items(attrib(entity, "ITEMS")),
"confidence": attrib(entity, "CONFIDENCE"),
"separators": [s.strip() for s in attrib(entity, "SEPARATORS").split(",")
if s.strip()],
"positionDxf": {"x": round(insert.x, 2), "y": round(insert.y, 2),
"z": round(insert.z, 2)},
"layer": entity.dxf.layer,
"block": entity.dxf.name,
}
# FB-Baustein: als Attribut nur vorhanden, wenn mit --fb annotiert
# wurde. Sonst aus dem Katalog nachgeschlagen und als solches
# gekennzeichnet - er steht dann nicht in der Zeichnung.
fb = attrib(entity, "FB_BLOCK")
if fb and fb != "-":
eintrag["fbType"] = fb
eintrag["fbTypeFrom"] = "dxf"
else:
definition = get_tro(type_name)
if definition:
eintrag["fbType"] = definition.get_fb_block()
eintrag["fbTypeFrom"] = "tro_catalog"
else:
eintrag["fbType"] = None
eintrag["fbTypeFrom"] = "unbekannter Typ"
warnings.append(
f"{tro_id}: Typ '{type_name}' steht nicht im TRO-Katalog"
)
if transform is not None:
x, y = transform.to_csv(insert.x, insert.y)
eintrag["position"] = {"x": round(x, 2), "y": round(y, 2)}
tros.append(eintrag)
continue
if payload.startswith(OPEN_PREFIX):
name = payload[len(OPEN_PREFIX):]
if entity.dxftype() != "CIRCLE":
continue # Kreis, Kreuz und Text tragen dieselbe Nutzlast
center = entity.dxf.center
punkt = {"name": name,
"positionDxf": {"x": round(center.x, 2), "y": round(center.y, 2)}}
if transform is not None:
x, y = transform.to_csv(center.x, center.y)
punkt["position"] = {"x": round(x, 2), "y": round(y, 2)}
open_points.append(punkt)
continue
match = EDGE_RE.match(payload)
if match:
# Schaft und Spitze eines Pfeils tragen dieselbe Nutzlast
pair = (match.group("src"), match.group("dst"))
if pair not in seen_edges:
seen_edges.add(pair)
edges.append({"from": pair[0], "to": pair[1]})
tros.sort(key=lambda t: t["id"])
edges.sort(key=lambda e: (e["from"], e["to"]))
open_points.sort(key=lambda p: p["name"])
# Vorgaenger/Nachfolger je TRO - das ist die Frage, die man an so eine
# Datei stellt ("was kommt vor diesem TRO, was danach").
bekannt = {t["id"] for t in tros}
for tro in tros:
tro["predecessors"] = sorted(e["from"] for e in edges if e["to"] == tro["id"])
tro["successors"] = sorted(e["to"] for e in edges if e["from"] == tro["id"])
for edge in edges:
for seite in ("from", "to"):
if edge[seite] not in bekannt:
warnings.append(
f"Verbindung {edge['from']} -> {edge['to']}: '{edge[seite]}' "
f"ist kein TRO in dieser Zeichnung"
)
return {"tros": tros, "connections": edges, "openPoints": open_points}
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
warnings: list[str] = []
results = env_dir("SKEL_RESULTS", "results")
dxf_file = Path(args.dxf)
if not dxf_file.is_absolute() and dxf_file.parent == Path("."):
dxf_file = results / args.dxf
if not dxf_file.is_file():
print(f"FEHLER: Zeichnung nicht gefunden: {dxf_file}", file=sys.stderr)
return 1
try:
doc = ezdxf.readfile(str(dxf_file))
except (OSError, ezdxf.DXFError) as exc:
print(f"FEHLER: {dxf_file} nicht lesbar: {exc}", file=sys.stderr)
return 1
transform = load_transform(dxf_file, results, warnings)
daten = extract(doc, transform, warnings)
if not daten["tros"]:
print(f"FEHLER: keine TRO-Marker in {dxf_file} gefunden. Wurde sie mit "
f"tro_annotate.py erzeugt?", file=sys.stderr)
return 2
kopf = {
"schema": "sps_skel-tro-extract-v1",
"source": dxf_file.name,
"extracted": datetime.now().strftime("%Y-%m-%d %H:%M"),
"note": "Aus der Zeichnung gelesen, nicht aus dem CSV-Export - "
"Aenderungen aus BricsCAD sind enthalten.",
}
if transform is not None:
kopf["transform"] = {"dx": round(transform.dx, 3), "dy": round(transform.dy, 3),
"rotation": transform.rotation,
"note": "Anlagenkoordinate = Zeichnung + (dx, dy)"}
out_file = results / (args.out or f"{dxf_file.stem}_tro.json")
out_file.write_text(
json.dumps({**kopf, **daten}, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
typen = Counter(t["type"] for t in daten["tros"])
print("")
print("================================================================")
print("TRO-EXTRAKT")
print("================================================================")
print(f"Zeichnung = {dxf_file}")
print(f"Lagebezug = "
+ (transform.describe() if transform else "- (nur DXF-Koordinaten)"))
print(f"TROs = {len(daten['tros'])} "
f"({', '.join(f'{k}: {v}' for k, v in sorted(typen.items()))})")
print(f"Verbindungen = {len(daten['connections'])}")
print(f"Offene Punkte = {len(daten['openPoints'])}")
print(f"JSON = {out_file}")
print("================================================================")
if warnings:
print("")
print(f"Hinweise ({len(warnings)}):")
for warning in warnings:
print(f" ! {warning}")
print("")
return 0
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="tro_extract",
description="Liest die von tro_annotate.py gesetzten TRO-Marker aus "
"einer Zeichnung zurueck und schreibt sie als JSON.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="Beispiele:\n"
" tro_extract.bat\n"
" tro_extract.bat --dxf export_annotated.dxf\n"
" tro_extract.bat --dxf export_annotated.dxf --out tros.json\n"
"\n"
"Exit-Codes:\n"
" 0 ok\n"
" 1 Eingabe- oder Aufruffehler\n"
" 2 keine TRO-Marker in der Zeichnung\n",
)
parser.add_argument(
"--dxf",
default="export_annotated.dxf",
metavar="NAME",
help="Annotierte Zeichnung in %%SKEL_RESULTS%% (oder ein vollstaendiger "
"Pfad). Standard: %(default)s",
)
parser.add_argument(
"--out",
metavar="NAME",
help="Name der JSON-Datei in %%SKEL_RESULTS%%. "
"Standard: <Zeichnung>_tro.json",
)
return parser.parse_args(argv)
if __name__ == "__main__":
sys.exit(main())
+266 -20
View File
@@ -58,6 +58,7 @@ from __future__ import annotations
import argparse import argparse
import csv import csv
import math
import sys import sys
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime
@@ -69,6 +70,8 @@ from material_flow import (
EDGE_UNRESOLVED, EDGE_UNRESOLVED,
LANE_LEFT, LANE_LEFT,
LANE_RIGHT, LANE_RIGHT,
ROTATION_CCW,
ROTATION_CW,
UNASSIGNED, UNASSIGNED,
Element, Element,
Graph, Graph,
@@ -91,6 +94,14 @@ from tro_catalog import (
get_tro, get_tro,
mermaid_classdefs, mermaid_classdefs,
) )
from tro_overrides import (
Overrides,
apply_connections,
apply_merges,
apply_overrides,
apply_splits,
load_overrides,
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Konstanten # Konstanten
@@ -169,9 +180,13 @@ class Tro:
class Analysis: class Analysis:
tros: list[Tro] = field(default_factory=list) tros: list[Tro] = field(default_factory=list)
edges: list[tuple[str, str]] = field(default_factory=list) edges: list[tuple[str, str]] = field(default_factory=list)
# (von, nach) -> Kantenart (transfer / umlauf / weiche / unbestimmt / normal)
edge_kinds: dict[tuple[str, str], str] = field(default_factory=dict)
isolated: list[Tro] = field(default_factory=list) isolated: list[Tro] = field(default_factory=list)
unknown_types: list[str] = field(default_factory=list) unknown_types: list[str] = field(default_factory=list)
findings: list[str] = field(default_factory=list) findings: list[str] = field(default_factory=list)
# Manuelle Korrekturen aus cfg/tro_overrides.ini (Typkorrekturen, offene Punkte)
overrides: Overrides = field(default_factory=Overrides)
def by_id(self, tro_id: str) -> Tro | None: def by_id(self, tro_id: str) -> Tro | None:
for tro in self.tros: for tro in self.tros:
@@ -228,7 +243,12 @@ def _switch_exits(hosts: list[str], graph: Graph) -> list[str]:
) )
def derive_tros(graph: Graph, elements: list[Element], findings: list[str]) -> list[Tro]: def derive_tros(
graph: Graph,
elements: list[Element],
findings: list[str],
overrides: Overrides | None = None,
) -> list[Tro]:
"""TRO-Liste aus den Separatoren und ihren angeschlossenen Objekten bilden.""" """TRO-Liste aus den Separatoren und ihren angeschlossenen Objekten bilden."""
separators = sorted( separators = sorted(
(e for e in elements if e.kind == "Separator"), key=lambda e: e.teile_id (e for e in elements if e.kind == "Separator"), key=lambda e: e.teile_id
@@ -376,12 +396,41 @@ def derive_tros(graph: Graph, elements: list[Element], findings: list[str]) -> l
tro.reason = f"{tro.reason}; {len(exits)} abgehende Wege -> Weiche" tro.reason = f"{tro.reason}; {len(exits)} abgehende Wege -> Weiche"
tro.confidence = "niedrig" tro.confidence = "niedrig"
# 4b) Manuell ergaenzte Uebergabestellen (cfg/tro_overrides.ini, [.split.]).
# Vor der Nummernvergabe, damit die neuen TROs in derselben Reihenfolge
# eine Nummer bekommen wie die abgeleiteten.
if overrides is not None and overrides.splits:
def _neuer_tro(spec, hosts, label):
return Tro(
tro_id="",
type_name=spec.type_name,
label=label,
hosts=hosts,
separators=list(spec.separators),
scanners=list(spec.scanners),
reason=spec.reason or "manuell ergaenzte Uebergabestelle",
confidence=spec.confidence,
)
apply_splits(tros, overrides, _neuer_tro)
# 4c) Zusammenfassungen - nach den Splits, damit auch ein frisch
# herausgeloester TRO Teil einer Zusammenfassung sein kann.
if overrides is not None and overrides.merges:
apply_merges(tros, overrides)
# 5) Stabile, laufende Nummern in Reihenfolge der Host-Knoten # 5) Stabile, laufende Nummern in Reihenfolge der Host-Knoten
tros.sort(key=lambda t: (t.hosts[0], t.separators[0] if t.separators else "")) tros.sort(key=lambda t: (t.hosts[0], t.separators[0] if t.separators else ""))
width = max(2, len(str(len(tros)))) width = max(2, len(str(len(tros))))
for index, tro in enumerate(tros, start=1): for index, tro in enumerate(tros, start=1):
tro.tro_id = f"TRO{index:0{width}d}" tro.tro_id = f"TRO{index:0{width}d}"
# 5b) Manuelle Korrekturen aus cfg/tro_overrides.ini. Sie greifen erst hier,
# weil sie ueber die TRO-ID adressiert sind, und noch vor Schritt 6, damit
# ein manuell gesetzter Typ den SSCC-Hinweis nicht mehr ausloest.
if overrides is not None:
apply_overrides(tros, overrides)
# 6) Scanner am Separator: 1Sep_SSCC laesst sich mechanisch nicht erkennen # 6) Scanner am Separator: 1Sep_SSCC laesst sich mechanisch nicht erkennen
for tro in tros: for tro in tros:
if tro.type_name == "1Sep" and tro.scanners: if tro.type_name == "1Sep" and tro.scanners:
@@ -394,53 +443,129 @@ def derive_tros(graph: Graph, elements: list[Element], findings: list[str]) -> l
return tros return tros
def link_tros(graph: Graph, tros: list[Tro]) -> list[tuple[str, str]]: def _tro_edge_kind(edge: Edge) -> str:
"""Materialkante -> Kantenart des TRO-Flusses (fuer Einfaerbung)."""
if edge.note == "Weiche":
return "weiche"
if edge.kind == EDGE_CIRCLE:
return "umlauf"
if edge.kind == EDGE_UNRESOLVED:
return "unbestimmt"
if edge.note in ("Einschleusung", "Ausschleusung"):
return "transfer"
return "normal"
def _bahn_richtung(node) -> tuple[float, float] | None:
"""Foerderrichtung einer Kreisel-Bahn als Einheitsvektor.
Der Kreisel ist eine geschlossene Schleife: auf der einen Bahn laeuft das
Material hin, auf der anderen zurueck. Welche Richtung das ist, ergibt sich
aus der Laengsachse ("Drehung") und dem Umlaufsinn ("Drehrichtung"):
UZS (im Uhrzeigersinn) Bahn R entgegen der Achse, Bahn L mit ihr
GUZ (gegen den UZS) umgekehrt
Rueckgabe None, wenn eines der beiden Merkmale fehlt - dann bleibt es bei
der ungerichteten "gleiche Bahn"-Notiz.
"""
element = node.element
drehung = _as_float(_merkmal(element.merkmale, "Drehung"))
umlauf = element.rotation
if drehung is None or umlauf not in (ROTATION_CW, ROTATION_CCW):
return None
rad = math.radians(drehung)
achse = (math.cos(rad), math.sin(rad))
mit_achse = (node.lane == LANE_LEFT) == (umlauf == ROTATION_CW)
return achse if mit_achse else (-achse[0], -achse[1])
def link_tros(
graph: Graph, tros: list[Tro]
) -> tuple[list[tuple[str, str]], dict[tuple[str, str], str]]:
""" """
TROs verknuepfen. TROs verknuepfen.
Der Materialfluss laeuft von Uebergabestelle zu Uebergabestelle, ggf. durch Der Materialfluss laeuft von Uebergabestelle zu Uebergabestelle, ggf. durch
passive Zwischenknoten (Strecken/Bahnen ohne eigenen TRO). Solche Knoten passive Zwischenknoten (Strecken/Bahnen ohne eigenen TRO). Solche Knoten
werden daher durchlaufen ("kontrahiert"), belegte Knoten beenden den Pfad. werden daher durchlaufen ("kontrahiert"), belegte Knoten beenden den Pfad.
Jede TRO-Kante bekommt zusaetzlich die Art der *ankommenden* Materialkante
(transfer / umlauf / weiche / unbestimmt / normal).
""" """
owners: dict[str, list[str]] = {} owners: dict[str, list[str]] = {}
for tro in tros: for tro in tros:
for host in tro.hosts: for host in tro.hosts:
owners.setdefault(host, []).append(tro.tro_id) owners.setdefault(host, []).append(tro.tro_id)
forward: dict[str, list[str]] = {} forward: dict[str, list[tuple[str, str]]] = {}
for edge in graph.edges: for edge in graph.edges:
forward.setdefault(edge.src, []).append(edge.dst) kind = _tro_edge_kind(edge)
forward.setdefault(edge.src, []).append((edge.dst, kind))
if edge.kind == EDGE_UNRESOLVED: if edge.kind == EDGE_UNRESOLVED:
# unbestimmte Richtung: in beide Richtungen begehbar # unbestimmte Richtung: in beide Richtungen begehbar
forward.setdefault(edge.dst, []).append(edge.src) forward.setdefault(edge.dst, []).append((edge.src, kind))
edges: set[tuple[str, str]] = set() edges: set[tuple[str, str]] = set()
edge_kinds: dict[tuple[str, str], str] = {}
for tro in tros: for tro in tros:
seen = set(tro.hosts) seen = set(tro.hosts)
queue = [n for host in tro.hosts for n in forward.get(host, [])] queue = [(nxt, k) for host in tro.hosts for (nxt, k) in forward.get(host, [])]
while queue: while queue:
node = queue.pop() node, arriving = queue.pop()
if node in seen: if node in seen:
continue continue
seen.add(node) seen.add(node)
if node in owners: if node in owners:
for other in owners[node]: for other in owners[node]:
if other != tro.tro_id: if other != tro.tro_id:
edges.add((tro.tro_id, other)) pair = (tro.tro_id, other)
edges.add(pair)
edge_kinds.setdefault(pair, arriving)
continue continue
queue.extend(forward.get(node, [])) queue.extend(forward.get(node, []))
# gleicher Host = physisch dieselbe Strecke/Bahn # Mehrere TROs auf derselben Kreisel-Bahn liegen HINTEREINANDER, nicht
# nebeneinander: das Material laeuft die Bahn in Foerderrichtung ab. Sie
# werden daher der Reihe nach verkettet statt nur als "gleiche Bahn"
# markiert - sonst fehlt z. B. die Kante von der einen Sperre zur naechsten.
verkettet: set[str] = set()
for host, ids in owners.items():
if len(ids) < 2:
continue
node = graph.nodes.get(host)
if node is None or node.kind != "Kreisel" or not node.lane:
continue
richtung = _bahn_richtung(node)
if richtung is None:
continue
auf_bahn = [t for t in tros if t.tro_id in ids and t.position]
if len(auf_bahn) < 2:
continue
# Projektion auf die Foerderrichtung: kleinster Wert kommt zuerst
auf_bahn.sort(key=lambda t: t.position[0] * richtung[0]
+ t.position[1] * richtung[1])
for vorher, nachher in zip(auf_bahn, auf_bahn[1:]):
pair = (vorher.tro_id, nachher.tro_id)
edges.add(pair)
edge_kinds.setdefault(pair, "bahn")
verkettet.update(pair)
# gleicher Host = physisch dieselbe Strecke/Bahn. Verkettete Bahnen sind
# oben schon gerichtet verbunden und brauchen die ungerichtete Notiz nicht.
for host, ids in owners.items(): for host, ids in owners.items():
for tro_id in ids: for tro_id in ids:
tro = next(t for t in tros if t.tro_id == tro_id) tro = next(t for t in tros if t.tro_id == tro_id)
tro.colocated = sorted(set(tro.colocated) | {o for o in ids if o != tro_id}) if tro_id in verkettet:
continue
tro.colocated = sorted(
set(tro.colocated) | {o for o in ids if o != tro_id and o not in verkettet}
)
for tro in tros: for tro in tros:
tro.successors = sorted(dst for src, dst in edges if src == tro.tro_id) tro.successors = sorted(dst for src, dst in edges if src == tro.tro_id)
tro.predecessors = sorted(src for src, dst in edges if dst == tro.tro_id) tro.predecessors = sorted(src for src, dst in edges if dst == tro.tro_id)
return sorted(edges) return sorted(edges), edge_kinds
def _centroid( def _centroid(
@@ -494,12 +619,36 @@ def locate_tros(tros: list[Tro], elements: list[Element], graph: Graph) -> None:
tro.position_from = source if tro.position else "keine Position" tro.position_from = source if tro.position else "keine Position"
def analyse_tros(graph: Graph, elements: list[Element]) -> Analysis: def analyse_tros(
graph: Graph, elements: list[Element], overrides: Overrides | None = None
) -> Analysis:
"""TROs ermitteln, verknuepfen, verorten und gegen die Typdefinition pruefen.""" """TROs ermitteln, verknuepfen, verorten und gegen die Typdefinition pruefen."""
analysis = Analysis() analysis = Analysis()
analysis.tros = derive_tros(graph, elements, analysis.findings) analysis.overrides = overrides if overrides is not None else Overrides()
analysis.edges = link_tros(graph, analysis.tros) analysis.tros = derive_tros(graph, elements, analysis.findings, analysis.overrides)
# Verorten vor dem Verknuepfen: die Reihenfolge mehrerer TROs auf einer
# Kreisel-Bahn wird ueber ihre Koordinate bestimmt.
locate_tros(analysis.tros, elements, graph) locate_tros(analysis.tros, elements, graph)
analysis.edges, analysis.edge_kinds = link_tros(graph, analysis.tros)
# Vor Ort aufgenommene Topologie schlaegt die abgeleitete. Die Differenz
# wird protokolliert, damit sichtbar bleibt, was die Ableitung falsch hatte.
if analysis.overrides.connections:
abgeleitet = set(analysis.edges)
analysis.edges, analysis.edge_kinds = apply_connections(
analysis.tros, analysis.overrides
)
gesetzt = set(analysis.edges)
for src, dst in sorted(abgeleitet - gesetzt):
analysis.findings.append(
f"abgeleitete Verbindung {src} -> {dst} entfaellt - steht nicht "
f"in der gesetzten Topologie"
)
for src, dst in sorted(gesetzt - abgeleitet):
analysis.findings.append(
f"Verbindung {src} -> {dst} nur gesetzt, aus dem Layout nicht "
f"ableitbar"
)
for tro in analysis.tros: for tro in analysis.tros:
definition = tro.definition() definition = tro.definition()
@@ -552,6 +701,21 @@ def _legend_pos(analysis: Analysis) -> tuple[float, float]:
return (min(xs) - LEGEND_MARGIN, min(ys) - LEGEND_MARGIN) return (min(xs) - LEGEND_MARGIN, min(ys) - LEGEND_MARGIN)
# Kantenstil je Art (an material_flow.py angelehnt: Umlauf hellblau gestrichelt,
# Weiche orange). "transfer"/"normal" bleiben der durchgezogene Standardfluss.
TRO_EDGE_STYLE = {
"umlauf": 'color="#8ea9db", style=dashed, tooltip="Kreisel-Umlauf"',
"weiche": 'color="#cc8800", penwidth=1.8, xlabel="Weiche", fontcolor="#cc8800", '
'tooltip="Weiche (Kreisel-Uebergang)"',
"unbestimmt": 'color="#bf8f00", style=dashed, dir=both, tooltip="Richtung unbestimmt"',
"transfer": 'tooltip="Uebergabe (Ein-/Ausschleusung)"',
}
def _tro_edge_attrs(kind: str | None) -> str:
return TRO_EDGE_STYLE.get(kind or "normal", "")
def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str: def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str:
""" """
Gerichtetes Flussdiagramm der TROs als Graphviz-Quelle. Gerichtetes Flussdiagramm der TROs als Graphviz-Quelle.
@@ -601,7 +765,7 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
add("") add("")
for src, dst in analysis.edges: for src, dst in analysis.edges:
add(f' "{src}" -> "{dst}" [];') add(f' "{src}" -> "{dst}" [{_tro_edge_attrs(analysis.edge_kinds.get((src, dst)))}];')
seen: set[frozenset] = set() seen: set[frozenset] = set()
for tro in analysis.tros: for tro in analysis.tros:
@@ -624,9 +788,13 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
) )
legend_label = ('<<table border="0" cellborder="1" cellspacing="0" cellpadding="3">' legend_label = ('<<table border="0" cellborder="1" cellspacing="0" cellpadding="3">'
+ rows + rows
+ '<tr><td align="left">durchgezogen</td>' + '<tr><td align="left"><font color="#2f5597">durchgezogen</font></td>'
'<td align="left">Materialfluss</td></tr>' '<td align="left">Uebergabe / Ein-Ausschleusung</td></tr>'
'<tr><td align="left">gepunktet</td>' '<tr><td align="left"><font color="#8ea9db">blau gestrichelt</font></td>'
'<td align="left">Kreisel-Umlauf</td></tr>'
'<tr><td align="left"><font color="#cc8800">orange</font></td>'
'<td align="left">Weiche (Kreisel-Uebergang)</td></tr>'
'<tr><td align="left">grau gepunktet</td>'
'<td align="left">gleiche Bahn / Strecke</td></tr>' '<td align="left">gleiche Bahn / Strecke</td></tr>'
"</table>>") "</table>>")
if use_coords: if use_coords:
@@ -787,6 +955,11 @@ def render_doc(
add(f"| TRO-Verbindungen | {len(analysis.edges)} |") add(f"| TRO-Verbindungen | {len(analysis.edges)} |")
add(f"| Nicht verbundene TROs | {len(analysis.isolated)} |") add(f"| Nicht verbundene TROs | {len(analysis.isolated)} |")
add(f"| Hinweise | {len(analysis.findings)} |") add(f"| Hinweise | {len(analysis.findings)} |")
if analysis.overrides.applied:
add(f"| Manuelle Typkorrekturen | {len(analysis.overrides.applied)} |")
if analysis.overrides.open_points:
add(f"| Offene Punkte (noch kein TRO) | "
f"{len(analysis.overrides.open_points)} |")
add("") add("")
add("| Typ | FB-Baustein | Anzahl hier | Bauteile je TRO | Farbgruppe |") add("| Typ | FB-Baustein | Anzahl hier | Bauteile je TRO | Farbgruppe |")
add("|---|---|---:|---|---|") add("|---|---|---:|---|---|")
@@ -873,7 +1046,56 @@ def render_doc(
add("_Keine._") add("_Keine._")
add("") add("")
add("## 6. Ableitungsregeln und Typdefinition") add("## 6. Manuelle Korrekturen und offene Punkte")
add("")
overrides = analysis.overrides
if not overrides and not overrides.warnings:
add("_Keine._ Alles unten Stehende ist rein aus dem Layout abgeleitet.")
add("")
else:
add(f"Quelle: `{overrides.source}`" if overrides.source
else "_Keine Override-Datei gefunden._")
add("")
if overrides.applied:
add("### 6.1 Angewandte Typkorrekturen")
add("")
add("Diese Typen stammen **nicht** aus dem Layout, sondern aus der "
"Begehung bzw. dem Review - im Layout sind sie nicht erkennbar.")
add("")
add("| Korrektur | Begruendung |")
add("|---|---|")
for entry in overrides.applied:
head, _, tail = entry.partition(" (")
add(f"| `{head}` | {tail[:-1] if tail.endswith(')') else tail} |")
add("")
if overrides.open_points:
add("### 6.2 Offene Punkte - noch kein TRO")
add("")
add("Im Review gemeldete Stellen. Die TRO-Struktur ist hier "
"**bewusst unveraendert**; die Punkte sind nur markiert (auch in der "
"annotierten Zeichnung, Layer `TRO_OPENPOINT`), damit sie in BricsCAD "
"geprueft werden koennen.")
add("")
add("| Punkt | X [mm] | Y [mm] | Z [mm] | erwartet | heute Teil von | Anmerkung |")
add("|---|---:|---:|---:|---|---|---|")
for point in overrides.open_points:
z_text = f"{point.z:.0f}" if point.z is not None else "-"
expect = f"`{point.expect}`" if point.expect else "-"
owner = f"`{point.belongs_to}`" if point.belongs_to else "-"
add(f"| {point.name} | {point.x:.0f} | {point.y:.0f} | {z_text} "
f"| {expect} | {owner} | {point.note or '-'} |")
add("")
if overrides.warnings:
add("### 6.3 Probleme mit der Override-Datei")
add("")
for warning in overrides.warnings:
add(f"- {warning}")
add("")
add("## 7. Ableitungsregeln und Typdefinition")
add("") add("")
add("Ein TRO ist die Steuerungslogik **einer Uebergabestelle**. Ausgangspunkt " add("Ein TRO ist die Steuerungslogik **einer Uebergabestelle**. Ausgangspunkt "
"ist der Separator und das, was an ihm haengt (Host-Objekt, Scanner).") "ist der Separator und das, was an ihm haengt (Host-Objekt, Scanner).")
@@ -974,6 +1196,29 @@ def report(
print("FEHLER: TRO-Typen ohne Eintrag in TRO_CATALOG: " print("FEHLER: TRO-Typen ohne Eintrag in TRO_CATALOG: "
+ ", ".join(analysis.unknown_types)) + ", ".join(analysis.unknown_types))
overrides = analysis.overrides
if overrides.applied:
print("")
print(f"Manuelle Typkorrekturen ({len(overrides.applied)}) aus "
f"{overrides.source}:")
for entry in overrides.applied:
print(f" * {entry}")
if overrides.open_points:
print("")
print(f"Offene Punkte ({len(overrides.open_points)}) - nur markiert, "
f"TRO-Struktur unveraendert:")
for point in overrides.open_points:
expect = f" erwartet {point.expect}" if point.expect else ""
owner = f", heute Teil von {point.belongs_to}" if point.belongs_to else ""
print(f" o {point.name} bei {point.describe_position()}{expect}{owner}")
if overrides.warnings:
print("")
print(f"Probleme mit {overrides.source} ({len(overrides.warnings)}):")
for warning in overrides.warnings:
print(f" ! {warning}")
if analysis.findings: if analysis.findings:
print("") print("")
print(f"Hinweise ({len(analysis.findings)}):") print(f"Hinweise ({len(analysis.findings)}):")
@@ -1071,7 +1316,8 @@ def main(argv: list[str] | None = None) -> int:
print(f"FEHLER: {csv_file} enthaelt keine Flussobjekte.", file=sys.stderr) print(f"FEHLER: {csv_file} enthaelt keine Flussobjekte.", file=sys.stderr)
return 1 return 1
analysis = analyse_tros(graph, elements) overrides = load_overrides(csv_file.name, env_dir("SKEL_CFG", "cfg"))
analysis = analyse_tros(graph, elements, overrides)
if not analysis.tros: if not analysis.tros:
print(f"FEHLER: {csv_file} enthaelt keine Separatoren - keine TROs " print(f"FEHLER: {csv_file} enthaelt keine Separatoren - keine TROs "
f"ableitbar.", file=sys.stderr) f"ableitbar.", file=sys.stderr)
+492
View File
@@ -0,0 +1,492 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
tro_overrides.py - manuelle Korrekturen an der abgeleiteten TRO-Liste.
`tro_flow.py` leitet die TROs allein aus dem mechanischen Layout (CSV-Export)
ab. Manches steht dort aber nicht drin: eine Weiche, die im Export fehlt, ein
Scanner, der den Typ auf `1Sep_SSCC` hebt, oder eine Stelle, die vor Ort als
eigener TRO erkannt wurde. Solche Erkenntnisse kommen aus der Begehung bzw.
aus dem Review der erzeugten Diagramme.
Damit sie den naechsten Lauf ueberleben, stehen sie in einer INI-Datei
(`%SKEL_CFG%/tro_overrides.ini`) statt in den erzeugten Dateien. `tro_flow.py`
und `tro_annotate.py` lesen sie und wenden sie nach der Ableitung an; die Doku
weist jede angewandte Korrektur getrennt aus, damit abgeleitet und behauptet
unterscheidbar bleiben.
Aufbau der INI (Sektionsnamen mit dem CSV-Dateinamen als Praefix, damit
mehrere Anlagen in einer Datei stehen koennen):
[<csv>.tro.<TRO-ID>] Korrektur an einem abgeleiteten TRO
type = 1Sep1Swi neuer Katalogtyp
reason = ... Begruendung (kommt in die Doku)
confidence = bestaetigt Vertrauensangabe (Default: bestaetigt)
[<csv>.openpoint.<Name>] offener Punkt - noch KEIN TRO
x = 529.95 Anlagenkoordinate in mm
y = -3895.27
z = 1468
expect = 1Sep1Swi hier erwarteter TRO-Typ
belongs_to = TRO10 TRO, der das Bauteil heute enthaelt
note = ... Erlaeuterung
Offene Punkte aendern die TRO-Struktur ausdruecklich **nicht**. Sie werden nur
in der Doku und in der annotierten Zeichnung markiert, damit sie in BricsCAD
geprueft werden koennen, bevor daraus ein TRO wird.
"""
from __future__ import annotations
import configparser
import os
from dataclasses import dataclass, field
from pathlib import Path
from tro_catalog import get_tro
OVERRIDE_FILE = "tro_overrides.ini"
# Vertrauensangabe fuer manuell gesetzte Typen. Bewusst ein eigener Wert - so
# ist in der Doku sichtbar, dass der Typ nicht aus dem Layout stammt.
DEFAULT_CONFIDENCE = "bestaetigt"
@dataclass
class TroOverride:
"""Korrektur an einem abgeleiteten TRO.
Adressiert entweder ueber die TRO-ID (`TRO03`) oder - stabiler - ueber die
Nummer eines seiner Separatoren (`SEP0011`). Die TRO-IDs sind laufende
Nummern eines Laufs und verschieben sich, sobald TROs dazukommen oder
wegfallen; die Separator-Nummer aus dem CSV-Export tut das nicht.
"""
key: str
by_separator: bool = False
type_name: str = ""
reason: str = ""
confidence: str = ""
def matches(self, tro) -> bool:
if self.by_separator:
return self.key in tro.separators
return self.key == tro.tro_id
@dataclass
class SplitSpec:
"""Ein neuer TRO, herausgeloest aus einem bereits abgeleiteten.
Fuer Uebergabestellen, die die Ableitung nicht als eigenstaendig erkennt,
weil die "Zuordnung" des Separators auf ein Transportobjekt zeigt (Strecke,
Gefaellestrecke) statt auf die Kreisel-Bahn, an der er tatsaechlich
schaltet. Die genannten Separatoren/Scanner werden ihrem bisherigen TRO
entnommen und bilden einen neuen.
"""
name: str
type_name: str
separators: list[str] = field(default_factory=list)
scanners: list[str] = field(default_factory=list)
host: str = ""
label: str = ""
reason: str = ""
confidence: str = DEFAULT_CONFIDENCE
@dataclass
class ConnectionSpec:
"""Eine vor Ort bestaetigte Verbindung zwischen zwei TROs."""
src: str
dst: str
kind: str = ""
@dataclass
class MergeSpec:
"""Mehrere abgeleitete TROs zu einem Steuerobjekt zusammenfassen.
Fuer Stellen, die mechanisch aus mehreren Bauteilgruppen bestehen,
steuerungstechnisch aber eine einzige Uebergabestelle sind - etwa zwei
Sperren beiderseits eines Kreisel-Beruehrpunkts. Adressiert wird ueber die
Separatoren, weil deren Nummern ueber Exporte hinweg stabiler sind als die
laufenden TRO-Nummern.
"""
name: str
type_name: str
separators: list[str] = field(default_factory=list)
host: str = ""
label: str = ""
reason: str = ""
confidence: str = DEFAULT_CONFIDENCE
@dataclass
class OpenPoint:
"""Eine im Review gemeldete Stelle, die noch kein eigener TRO ist."""
name: str
x: float
y: float
z: float | None = None
expect: str = ""
belongs_to: str = ""
note: str = ""
def describe_position(self) -> str:
return f"{self.x:.0f}, {self.y:.0f}"
@dataclass
class Overrides:
"""Alle Korrekturen zu einer CSV-Datei."""
source: Path | None = None
tros: dict[str, TroOverride] = field(default_factory=dict)
splits: list[SplitSpec] = field(default_factory=list)
merges: list[MergeSpec] = field(default_factory=list)
# Gesetzte Topologie: ersetzt die abgeleiteten Kanten vollstaendig
connections: list[ConnectionSpec] = field(default_factory=list)
open_points: list[OpenPoint] = field(default_factory=list)
# Protokoll der tatsaechlich angewandten Korrekturen (fuer die Doku)
applied: list[str] = field(default_factory=list)
warnings: list[str] = field(default_factory=list)
def __bool__(self) -> bool:
return bool(self.tros or self.splits or self.merges or self.connections
or self.open_points)
def override_path(cfg_dir: Path | None = None) -> Path:
"""Pfad zur Override-Datei: %SKEL_CFG%, sonst <projekt>/cfg."""
if cfg_dir is not None:
return cfg_dir / OVERRIDE_FILE
env = os.environ.get("SKEL_CFG")
if env:
return Path(env) / OVERRIDE_FILE
return Path(__file__).resolve().parent.parent / "cfg" / OVERRIDE_FILE
def _as_float(raw: str, where: str, warnings: list[str]) -> float | None:
try:
return float(str(raw).strip())
except (TypeError, ValueError):
warnings.append(f"{where}: '{raw}' ist keine Zahl - Eintrag ignoriert")
return None
def load_overrides(csv_name: str, cfg_dir: Path | None = None) -> Overrides:
"""Korrekturen zu `csv_name` laden (leeres Ergebnis, wenn es keine gibt)."""
path = override_path(cfg_dir)
result = Overrides()
if not path.is_file():
return result
result.source = path
parser = configparser.ConfigParser(inline_comment_prefixes=(";",))
# Schluessel sind Kleinschreibung, Sektionsnamen bleiben wie geschrieben.
parser.read(path, encoding="utf-8")
prefix = csv_name.lower()
for section in parser.sections():
# [<csv>.connections] - gesetzte Topologie, eigene Form ohne <name>
if section.lower() == f"{prefix}.connections":
for key, raw in parser[section].items():
pfeil, _, rest = str(raw).partition("|")
src, sep, dst = (t.strip() for t in pfeil.partition("->"))
if not sep or not src or not dst:
result.warnings.append(
f"[{section}] {key}: '{raw}' ist keine Verbindung "
f"'<von> -> <nach>'"
)
continue
result.connections.append(
ConnectionSpec(src=src, dst=dst, kind=rest.strip())
)
continue
parts = section.split(".")
# <csv>.<art>.<name> - der CSV-Name kann selbst einen Punkt enthalten
if len(parts) < 3:
continue
kind, name = parts[-2].lower(), parts[-1]
owner = ".".join(parts[:-2]).lower()
if owner != prefix:
continue
values = parser[section]
if kind == "tro":
type_name = values.get("type", "").strip()
if type_name and get_tro(type_name) is None:
result.warnings.append(
f"[{section}]: Typ '{type_name}' steht nicht im TRO-Katalog "
f"- Korrektur ignoriert"
)
continue
key = name.upper()
by_sep = key.startswith("SEP")
result.tros[key] = TroOverride(
key=key[3:] if by_sep else key,
by_separator=by_sep,
type_name=type_name,
reason=values.get("reason", "").strip(),
confidence=values.get("confidence", "").strip() or DEFAULT_CONFIDENCE,
)
elif kind == "split":
type_name = values.get("type", "").strip()
if get_tro(type_name) is None:
result.warnings.append(
f"[{section}]: Typ '{type_name}' steht nicht im TRO-Katalog "
f"- neuer TRO nicht angelegt"
)
continue
seps = [s.strip() for s in values.get("separators", "").split(",") if s.strip()]
if not seps:
result.warnings.append(
f"[{section}]: 'separators' fehlt - ohne Separator kein TRO"
)
continue
result.splits.append(
SplitSpec(
name=name,
type_name=type_name,
separators=seps,
scanners=[s.strip() for s in values.get("scanners", "").split(",")
if s.strip()],
host=values.get("host", "").strip(),
label=values.get("label", "").strip(),
reason=values.get("reason", "").strip(),
confidence=values.get("confidence", "").strip() or DEFAULT_CONFIDENCE,
)
)
elif kind == "merge":
type_name = values.get("type", "").strip()
if get_tro(type_name) is None:
result.warnings.append(
f"[{section}]: Typ '{type_name}' steht nicht im TRO-Katalog "
f"- Zusammenfassung nicht durchgefuehrt"
)
continue
seps = [s.strip() for s in values.get("separators", "").split(",") if s.strip()]
if len(seps) < 2:
result.warnings.append(
f"[{section}]: 'separators' braucht mindestens zwei Nummern"
)
continue
result.merges.append(
MergeSpec(
name=name,
type_name=type_name,
separators=seps,
host=values.get("host", "").strip(),
label=values.get("label", "").strip(),
reason=values.get("reason", "").strip(),
confidence=values.get("confidence", "").strip() or DEFAULT_CONFIDENCE,
)
)
elif kind == "openpoint":
x = _as_float(values.get("x", ""), f"[{section}] x", result.warnings)
y = _as_float(values.get("y", ""), f"[{section}] y", result.warnings)
if x is None or y is None:
continue
z_raw = values.get("z", "").strip()
expect = values.get("expect", "").strip()
if expect and get_tro(expect) is None:
result.warnings.append(
f"[{section}]: erwarteter Typ '{expect}' steht nicht im "
f"TRO-Katalog"
)
result.open_points.append(
OpenPoint(
name=name,
x=x,
y=y,
z=_as_float(z_raw, f"[{section}] z", result.warnings)
if z_raw
else None,
expect=expect,
belongs_to=values.get("belongs_to", "").strip(),
note=values.get("note", "").strip(),
)
)
else:
result.warnings.append(
f"[{section}]: unbekannte Art '{kind}' - erwartet 'tro' oder "
f"'openpoint'"
)
result.open_points.sort(key=lambda p: p.name)
return result
def apply_splits(tros, overrides: Overrides, make_tro) -> list:
"""Neue TROs aus bestehenden herausloesen (vor der Nummernvergabe).
`make_tro(spec, hosts, label)` erzeugt das projektspezifische Tro-Objekt -
so bleibt dieses Modul frei von einem Import aus tro_flow (das umgekehrt
schon von hier importiert).
"""
for spec in overrides.splits:
quellen = [t for t in tros if set(spec.separators) & set(t.separators)]
if not quellen:
overrides.warnings.append(
f"Split '{spec.name}': keiner der Separatoren "
f"{', '.join(spec.separators)} kommt in diesem Lauf vor - "
f"nicht angelegt"
)
continue
hosts: list[str] = []
for quelle in quellen:
quelle.separators = [s for s in quelle.separators
if s not in spec.separators]
quelle.scanners = [s for s in quelle.scanners if s not in spec.scanners]
hosts.extend(quelle.hosts)
if not quelle.separators and quelle.type_name not in ("Vario",):
overrides.warnings.append(
f"Split '{spec.name}': {quelle.label} hat danach keinen "
f"Separator mehr - bitte pruefen"
)
ziel_hosts = [spec.host] if spec.host else sorted(set(hosts))[:1]
label = spec.label or (
f"Separator {', '.join(spec.separators)} "
f"(manuell aus {quellen[0].label} herausgeloest)"
)
tros.append(make_tro(spec, ziel_hosts, label))
overrides.applied.append(
f"neu {spec.name}: {spec.type_name} auf {', '.join(ziel_hosts)} "
f"mit Separator {', '.join(spec.separators)}, herausgeloest aus "
f"{', '.join(sorted({q.label for q in quellen}))} "
f"({spec.reason or 'manuelle Ergaenzung'})"
)
return tros
def apply_merges(tros, overrides: Overrides) -> list:
"""Mehrere TROs zu einem zusammenfassen (nach den Splits, vor der Nummer).
Der erste betroffene TRO bleibt bestehen und uebernimmt Bauteile, Hosts und
Typ; die uebrigen werden aus der Liste entfernt.
"""
for spec in overrides.merges:
betroffen = [t for t in tros if set(spec.separators) & set(t.separators)]
if len(betroffen) < 2:
gefunden = ", ".join(t.tro_id or t.label for t in betroffen) or "keiner"
overrides.warnings.append(
f"Merge '{spec.name}': nur {len(betroffen)} der Separatoren "
f"{', '.join(spec.separators)} gefunden ({gefunden}) - "
f"nicht zusammengefasst"
)
continue
ziel = betroffen[0]
quellen = betroffen[1:]
for quelle in quellen:
ziel.separators = sorted(set(ziel.separators) | set(quelle.separators))
ziel.scanners = sorted(set(ziel.scanners) | set(quelle.scanners))
ziel.hosts = sorted(set(ziel.hosts) | set(quelle.hosts))
tros.remove(quelle)
if spec.host:
# Ein Host bestimmt, wo der zusammengefasste TRO im Fluss haengt;
# die uebrigen Bahnen bleiben als weitere Hosts erhalten, damit die
# Verknuepfung beide Seiten des Uebergangs sieht.
ziel.hosts = [spec.host] + [h for h in ziel.hosts if h != spec.host]
ziel.type_name = spec.type_name
ziel.confidence = spec.confidence
ziel.label = spec.label or ziel.label
grund = spec.reason or "manuell zusammengefasst"
ziel.reason = f"{ziel.reason}; manuell zusammengefasst ({grund})"
overrides.applied.append(
f"merge {spec.name}: {len(betroffen)} TROs -> ein {spec.type_name} "
f"mit Separatoren {', '.join(ziel.separators)} ({grund})"
)
return tros
def apply_connections(tros, overrides: Overrides):
"""Gesetzte Topologie an die Stelle der abgeleiteten Kanten setzen.
Ersetzt vollstaendig, nicht ergaenzend: wenn die Verbindungen vor Ort
aufgenommen wurden, sind die abgeleiteten damit erledigt - sonst blieben
genau die Kanten stehen, die korrigiert werden sollten. Was dabei wegfaellt
bzw. hinzukommt, steht anschliessend in overrides.applied.
Rueckgabe: (edges, edge_kinds) wie von link_tros.
"""
bekannt = {t.tro_id for t in tros}
edges: list[tuple[str, str]] = []
kinds: dict[tuple[str, str], str] = {}
for spec in overrides.connections:
for seite, wert in (("von", spec.src), ("nach", spec.dst)):
if wert not in bekannt:
overrides.warnings.append(
f"Verbindung {spec.src} -> {spec.dst}: {seite} '{wert}' ist "
f"kein TRO dieses Laufs - Verbindung ignoriert"
)
break
else:
paar = (spec.src, spec.dst)
if paar in kinds:
continue
edges.append(paar)
kinds[paar] = spec.kind or "gesetzt"
for tro in tros:
tro.successors = sorted(d for s, d in edges if s == tro.tro_id)
tro.predecessors = sorted(s for s, d in edges if d == tro.tro_id)
tro.colocated = []
ohne = [t.tro_id for t in tros if not t.successors and not t.predecessors]
if ohne:
overrides.warnings.append(
"ohne Verbindung in der gesetzten Topologie: " + ", ".join(sorted(ohne))
)
overrides.applied.append(
f"Topologie gesetzt: {len(edges)} Verbindungen aus der Override-Datei "
f"ersetzen die abgeleiteten"
)
return sorted(edges), kinds
def apply_overrides(tros, overrides: Overrides) -> None:
"""Typkorrekturen auf die abgeleitete TRO-Liste anwenden.
Wird aufgerufen, nachdem die TRO-IDs vergeben sind (die Korrekturen sind
ueber die ID adressiert) und bevor FB-Baustein und Bauteilliste aus dem
Katalog gezogen werden - der Typwechsel zieht beides mit.
"""
if not overrides.tros:
return
for section_key, override in sorted(overrides.tros.items()):
treffer = [t for t in tros if override.matches(t)]
if not treffer:
wie = "Separator" if override.by_separator else "TRO-ID"
overrides.warnings.append(
f"[{section_key}]: kein TRO mit {wie} '{override.key}' in diesem "
f"Lauf - Korrektur nicht angewandt"
+ ("" if override.by_separator else
" (Nummerierung geaendert? Besser ueber SEP<Nummer> adressieren)")
)
continue
if len(treffer) > 1:
overrides.warnings.append(
f"[{section_key}]: trifft {len(treffer)} TROs "
f"({', '.join(t.tro_id for t in treffer)}) - auf alle angewandt"
)
for tro in treffer:
if not override.type_name or override.type_name == tro.type_name:
continue
vorher = tro.type_name
tro.type_name = override.type_name
tro.confidence = override.confidence
grund = override.reason or "manuelle Korrektur"
tro.reason = (f"{tro.reason}; manuell auf {override.type_name} "
f"gesetzt ({grund})")
overrides.applied.append(
f"{tro.tro_id}: {vorher} -> {override.type_name} ({grund})"
)