TRO-Beschriftung wie das TRO-Fluss-SVG; Stationspfeile zeigen von der Kette weg

tro_flow:
- _station_positionen: Richtung des Zu-/Ablaufpfeils mit groesstem
  Winkelabstand zu allen Kanten des Anker-TROs und zu bereits gesetzten
  Stationspfeilen (15-Grad-Raster), echte Stationsrichtung nur bei
  Gleichstand - vorher lag z. B. Mubea TRO02 -> SYS2 fast auf TRO02 -> TRO13

tro_annotate:
- Symbole mit typ-eigener Fuellfarbe (Solid-Hatch) unter der Gruppenkontur
- Kantenfarben/-layer wie im SVG: Uebergabe blau, Weiche orange,
  Materialeingang gruen, Materialausgang rot (TRO_WEICHE/_EINGANG/_AUSGANG)
- Stationen als farbige Beschriftung am Pfeilende, Lage ueber
  tro_flow._station_positionen (ohne CONNECTION_ARROW-Block)
- Legende wie im SVG: je vorkommendem Typ Fuellung/Rand/Form, dann Kantenfarben
- CONNECTION_ARROW: FROM_TRO/TO_TRO unsichtbar (Daten bleiben), KIND auch weiche

cfg/dxf_registration.json: mubea.csv|mubea.dxf und HundM05.csv|HundM05.dxf
mit Versatz 0 (CSV aus derselben dxfmakros-Zeichnung; Auto-Ermittlung sieht
die verschachtelten Separatoren nicht und war mehrdeutig);
mubea.csv|500573_60_1.dxf neu ermittelt (36/46 Punkte).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-10-02 13:30:47 +02:00
co-authored by Claude Opus 5.5
parent d0a3ac7e5a
commit 7b98c8b8d4
3 changed files with 213 additions and 52 deletions
+34 -14
View File
@@ -173,10 +173,8 @@ GENERATED_MARKER = "<!-- erzeugt von lib/tro_flow.py -->"
COORD_SCALE = 0.1
# Zu-/Ablaufpfeil einer Station in der lagerichtigen Zeichnung: feste Laenge
# (mm) ab dem angehaengten TRO und Faecherwinkel (Grad), wenn mehrere
# Stationen am selben TRO haengen (siehe _station_positionen).
# (mm) ab dem angehaengten TRO (siehe _station_positionen).
COORD_PFEIL_LAENGE = 1300
COORD_PFEIL_FAECHER = 45
# Groesste Luecke (mm) zwischen zwei benachbarten Knotenkoordinaten je Achse in
# der lagerichtigen Zeichnung. Groessere Leerstrecken (lange Foerderer ohne
@@ -1733,11 +1731,12 @@ def _station_positionen(
"""Zeichenposition der Stationsbeschriftung (Ende des Zu-/Ablaufpfeils).
Feste Pfeillaenge COORD_PFEIL_LAENGE ab dem angehaengten TRO (Beladung:
der TRO danach, Entladung: der TRO davor), Richtung zur echten Station -
so bleibt der Pfeil sichtbar, auch wenn die Station praktisch auf dem TRO
sitzt (Mubea SYS1 186 mm neben TRO04). Haengen mehrere Stationen am selben
TRO, werden sie um COORD_PFEIL_FAECHER Grad aufgefaechert. Station ohne
angehaengten TRO: echte Koordinate."""
der TRO danach, Entladung: der TRO davor) - so bleibt der Pfeil sichtbar,
auch wenn die Station praktisch auf dem TRO sitzt (Mubea SYS1 186 mm neben
TRO04). Die Richtung haelt den groessten Winkelabstand zu allen Kanten des
Anker-TROs und zu bereits gesetzten Stationspfeilen; die Richtung zur
echten Station entscheidet nur bei Gleichstand. Station ohne angehaengten
TRO: echte Koordinate."""
tro_by_id = {t.tro_id: t for t in analysis.tros}
je_tro: dict[str, list[Tro]] = {}
ergebnis: dict[str, tuple[float, float]] = {}
@@ -1751,17 +1750,38 @@ def _station_positionen(
continue
je_tro.setdefault(anker, []).append(station)
def _abstand(a: float, b: float) -> float:
d = abs(a - b) % (2 * math.pi)
return min(d, 2 * math.pi - d)
for anker, stationen in je_tro.items():
ax, ay = tro_zeichen[anker]
echt = tro_by_id[anker].position
for k, station in enumerate(sorted(stationen, key=lambda s: s.tro_id)):
dx = dy = 0.0
# Richtungen der Kettenkanten am Anker-TRO - der Pfeil soll von der
# Kette WEG zeigen, nicht auf ihr liegen (Review 2026-10-02: Mubea
# TRO02 -> SYS2 lief fast deckungsgleich mit TRO02 -> TRO13).
belegt: list[float] = []
for src, dst in analysis.edges:
anderer = dst if src == anker else src if dst == anker else None
if anderer is None or anderer not in tro_zeichen:
continue
ox, oy = tro_zeichen[anderer]
if (ox, oy) != (ax, ay):
belegt.append(math.atan2(oy - ay, ox - ax))
for station in sorted(stationen, key=lambda s: s.tro_id):
wunsch = -math.pi / 2
if echt and station.position:
dx, dy = station.position[0] - echt[0], station.position[1] - echt[1]
if math.hypot(dx, dy) < 1.0:
dx, dy = 0.0, -1.0
winkel = math.atan2(dy, dx) + math.radians(
(k - (len(stationen) - 1) / 2) * COORD_PFEIL_FAECHER)
if math.hypot(dx, dy) >= 1.0:
wunsch = math.atan2(dy, dx)
# Kandidaten im 15-Grad-Raster: groesster Mindestabstand zu allen
# belegten Richtungen gewinnt, bei Gleichstand die zur echten
# Station naechste Richtung.
kandidaten = [math.radians(g) for g in range(0, 360, 15)]
winkel = max(kandidaten, key=lambda w: (
round(min((_abstand(w, b) for b in belegt), default=math.pi), 3),
-_abstand(w, wunsch)))
belegt.append(winkel)
ergebnis[station.tro_id] = (ax + math.cos(winkel) * COORD_PFEIL_LAENGE,
ay + math.sin(winkel) * COORD_PFEIL_LAENGE)
return ergebnis