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:
co-authored by
Claude Opus 5.5
parent
d0a3ac7e5a
commit
7b98c8b8d4
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"mubea.csv|500573_60_1.dxf": {
|
||||
"dx": 50905.678327433096,
|
||||
"dx": 50833.7283274331,
|
||||
"dy": 8414.490373798908,
|
||||
"rotation": 0.0,
|
||||
"source": "auto",
|
||||
"anchor": "20 exakte Treffer (<= 50 mm) ueber die Rollen Scanner:4, Separator:31; 20 Stimmen im 100-mm-Raster, 6 Kandidaten geprueft",
|
||||
"matched": 34,
|
||||
"candidates": 35,
|
||||
"residual_max": 452.04557170439375,
|
||||
"residual_mean": 102.6006793267277,
|
||||
"anchor": "20 exakte Treffer (<= 50 mm) ueber die Rollen Scanner:8, Separator:38; 22 Stimmen im 100-mm-Raster, 6 Kandidaten geprueft",
|
||||
"matched": 36,
|
||||
"candidates": 46,
|
||||
"residual_max": 487.77757110283585,
|
||||
"residual_mean": 93.12692369578822,
|
||||
"csv_file": "mubea.csv",
|
||||
"dxf_file": "500573_60_1.dxf",
|
||||
"created": "2026-07-30 15:32"
|
||||
"created": "2026-10-02 12:45"
|
||||
},
|
||||
"export.csv|Mubea.dxf": {
|
||||
"dx": 59.950000000000045,
|
||||
@@ -26,5 +26,33 @@
|
||||
"csv_file": "export.csv",
|
||||
"dxf_file": "Mubea.dxf",
|
||||
"created": "2026-08-12 10:09"
|
||||
},
|
||||
"HundM05.csv|HundM05.dxf": {
|
||||
"dx": 0.0,
|
||||
"dy": 0.0,
|
||||
"rotation": 0.0,
|
||||
"source": "cli",
|
||||
"anchor": "von Hand: CSV aus derselben dxfmakros-Zeichnung exportiert, Koordinaten identisch (Auto-Ermittlung sieht die in VF_/KREISEL_-Bloecken verschachtelten Separatoren nicht und war mehrdeutig)",
|
||||
"matched": 14,
|
||||
"candidates": 26,
|
||||
"residual_max": 360.0,
|
||||
"residual_mean": 164.97465794329284,
|
||||
"csv_file": "HundM05.csv",
|
||||
"dxf_file": "HundM05.dxf",
|
||||
"created": "2026-10-02 13:40"
|
||||
},
|
||||
"mubea.csv|mubea.dxf": {
|
||||
"dx": 0.0,
|
||||
"dy": 0.0,
|
||||
"rotation": 0.0,
|
||||
"source": "cli",
|
||||
"anchor": "von Hand: CSV aus derselben dxfmakros-Zeichnung exportiert, Koordinaten identisch (Auto-Ermittlung sieht die in VF_/KREISEL_-Bloecken verschachtelten Separatoren nicht und war mehrdeutig)",
|
||||
"matched": 20,
|
||||
"candidates": 46,
|
||||
"residual_max": 360.0,
|
||||
"residual_mean": 256.77327549812964,
|
||||
"csv_file": "mubea.csv",
|
||||
"dxf_file": "mubea.dxf",
|
||||
"created": "2026-10-02 13:40"
|
||||
}
|
||||
}
|
||||
|
||||
+144
-31
@@ -101,7 +101,14 @@ from dxf_registration import (
|
||||
)
|
||||
from material_flow import build_graph, env_dir, read_elements, resolve_input
|
||||
from tro_catalog import STYLES, TRO_CATALOG, TroSymbol, get_tro
|
||||
from tro_flow import Analysis, analyse_tros
|
||||
from tro_flow import (
|
||||
BOUNDARY_ENTRY,
|
||||
BOUNDARY_EXIT,
|
||||
STATION_FARBE,
|
||||
Analysis,
|
||||
_station_positionen,
|
||||
analyse_tros,
|
||||
)
|
||||
from tro_overrides import Overrides, load_overrides
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -314,7 +321,12 @@ def build_layers(doc, prefix: str) -> dict[str, str]:
|
||||
})
|
||||
# openpoint: im Review gemeldete Stellen, die noch KEIN TRO sind. Kraeftiges
|
||||
# Rot, damit sie sich von den abgeleiteten Markern klar abheben.
|
||||
# Kantenfarben wie im TRO-Fluss-SVG (tro_flow.render_dot): Uebergabe blau,
|
||||
# Weiche orange, Materialeingang gruen, Materialausgang rot.
|
||||
for key, colour in (("label", "#404040"), ("flow", "#2f5597"),
|
||||
("weiche", "#cc8800"),
|
||||
("eingang", STATION_FARBE[BOUNDARY_ENTRY]),
|
||||
("ausgang", STATION_FARBE[BOUNDARY_EXIT]),
|
||||
("legend", "#808080"), ("openpoint", "#e00000")):
|
||||
name = f"{prefix}{key.upper()}"
|
||||
names[key] = name
|
||||
@@ -334,6 +346,40 @@ def _polygon(sides: int, radius: float, start_deg: float) -> list[tuple[float, f
|
||||
]
|
||||
|
||||
|
||||
def shape_outline(symbol: str, r: float) -> list[tuple[float, float]]:
|
||||
"""Aeussere Kontur eines Symbols als Polygon - Rand der Fuellflaeche
|
||||
(Kreise als 48-Eck angenaehert)."""
|
||||
if symbol in (TroSymbol.CIRCLE, TroSymbol.DOUBLE_CIRCLE):
|
||||
return _polygon(48, r, 0)
|
||||
if symbol == TroSymbol.TRIANGLE:
|
||||
return _polygon(3, r, 90)
|
||||
if symbol in (TroSymbol.TRIANGLE_DOWN, TroSymbol.DOUBLE_TRIANGLE_DOWN):
|
||||
return _polygon(3, r, 270)
|
||||
if symbol == TroSymbol.SQUARE:
|
||||
return _polygon(4, r, 45)
|
||||
if symbol == TroSymbol.DIAMOND:
|
||||
return _polygon(4, r, 90)
|
||||
if symbol == TroSymbol.PENTAGON:
|
||||
return _polygon(5, r, 90)
|
||||
if symbol == TroSymbol.HEXAGON:
|
||||
return _polygon(6, r, 0)
|
||||
if symbol == TroSymbol.RECT:
|
||||
w, h = r * 1.45, r * 0.72
|
||||
return [(-w, -h), (w, -h), (w, h), (-w, h)]
|
||||
if symbol == TroSymbol.ARROW:
|
||||
return [(-r * 0.55, -r), (r, 0), (-r * 0.55, r), (-r * 0.55, r * 0.35),
|
||||
(-r, r * 0.35), (-r, -r * 0.35), (-r * 0.55, -r * 0.35)]
|
||||
return _polygon(48, r, 0)
|
||||
|
||||
|
||||
def draw_fill(blk, symbol: str, r: float, fill_hex: str) -> None:
|
||||
"""Typ-eigene Fuellfarbe (wie im TRO-Fluss-SVG) als Solid-Hatch unter der
|
||||
Kontur. Feste Farbe statt BYLAYER: der Layer traegt die Randfarbe der
|
||||
Gruppe, die Fuellung unterscheidet den einzelnen Typ."""
|
||||
hatch = blk.add_hatch(dxfattribs={"true_color": ezdxf.rgb2int(hex_to_rgb(fill_hex))})
|
||||
hatch.paths.add_polyline_path(shape_outline(symbol, r), is_closed=True)
|
||||
|
||||
|
||||
def draw_shape(blk, symbol: str, r: float) -> None:
|
||||
"""
|
||||
Symbolgeometrie in eine Blockdefinition zeichnen.
|
||||
@@ -399,7 +445,10 @@ def build_symbol(doc, type_name: str, marker: float, text: float, show_fb: bool)
|
||||
blk = doc.blocks.new(name=name)
|
||||
|
||||
definition = get_tro(type_name)
|
||||
draw_shape(blk, definition.get_symbol() if definition else "", marker)
|
||||
symbol = definition.get_symbol() if definition else ""
|
||||
if definition:
|
||||
draw_fill(blk, symbol, marker, definition.get_fill())
|
||||
draw_shape(blk, symbol, marker)
|
||||
|
||||
gap = marker * 1.5
|
||||
rows = [("ID", "TRO??", text * 1.35), ("TYPE", type_name, text)]
|
||||
@@ -522,6 +571,27 @@ def draw_open_points(
|
||||
return drawn
|
||||
|
||||
|
||||
def place_stations(
|
||||
msp, analysis: Analysis, placed: dict[str, tuple[float, float]],
|
||||
layers: dict[str, str], text: float,
|
||||
) -> dict[str, tuple[float, float]]:
|
||||
"""Be-/Entladestationen wie im TRO-Fluss-SVG: kein Symbol, nur der Name
|
||||
in Gruen (Eingang) bzw. Rot (Ausgang) am Ende des Zu-/Ablaufpfeils.
|
||||
Lage wie dort (tro_flow._station_positionen): feste Pfeillaenge ab dem
|
||||
angehaengten TRO, Richtung von dessen Kanten weg."""
|
||||
positionen = _station_positionen(analysis, placed)
|
||||
for station in analysis.boundaries:
|
||||
if station.tro_id not in positionen:
|
||||
continue
|
||||
x, y = positionen[station.tro_id]
|
||||
layer = layers["eingang" if station.type_name == BOUNDARY_ENTRY else "ausgang"]
|
||||
label = msp.add_text(station.label, height=text,
|
||||
dxfattribs={"layer": layer, "style": "Standard"})
|
||||
label.set_placement((x, y), align=TextEntityAlignment.MIDDLE_CENTER)
|
||||
tag(label, station.tro_id)
|
||||
return positionen
|
||||
|
||||
|
||||
def build_connection_symbol(doc, marker: float, text: float) -> str:
|
||||
"""
|
||||
Blockdefinition fuer eine Verbindung anlegen.
|
||||
@@ -537,9 +607,9 @@ def build_connection_symbol(doc, marker: float, text: float) -> str:
|
||||
UID unsichtbar - eindeutige interne Nummer, dauerhaft
|
||||
FROM_UID unsichtbar - interne UID des Quell-TRO
|
||||
TO_UID unsichtbar - interne UID des Ziel-TRO
|
||||
FROM_TRO sichtbar - sprechender Name der Quelle (TRO01)
|
||||
TO_TRO sichtbar - sprechender Name des Ziels (TRO09)
|
||||
KIND unsichtbar - flow | colocated
|
||||
FROM_TRO unsichtbar - sprechender Name der Quelle (TRO01)
|
||||
TO_TRO unsichtbar - sprechender Name des Ziels (TRO09)
|
||||
KIND unsichtbar - flow | weiche | colocated
|
||||
"""
|
||||
name = CONNECTION_BLOCK
|
||||
if name in doc.blocks:
|
||||
@@ -552,13 +622,17 @@ def build_connection_symbol(doc, marker: float, text: float) -> str:
|
||||
dxfattribs={"color": 256})
|
||||
blk.add_circle((0, 0), size * 0.12, dxfattribs={"color": 256})
|
||||
|
||||
# FROM_TRO/TO_TRO unsichtbar: an jeder Kante sichtbar machten sie die
|
||||
# Zeichnung unruhig, im TRO-Fluss-SVG stehen sie auch nicht (Review
|
||||
# 2026-10-02). Die Daten bleiben am Block, CONNECTION_EDIT zeigt sie.
|
||||
offset = size * 1.4
|
||||
for tag_name in ("FROM_TRO", "TO_TRO"):
|
||||
blk.add_attdef(
|
||||
tag_name,
|
||||
insert=(size * 1.3, offset),
|
||||
height=text * 0.8,
|
||||
dxfattribs={"style": "Standard", "color": 256, "text": ""},
|
||||
dxfattribs={"style": "Standard", "color": 256, "text": "",
|
||||
"invisible": 1},
|
||||
)
|
||||
offset -= text * 1.2
|
||||
|
||||
@@ -589,30 +663,47 @@ def draw_connections(
|
||||
"""
|
||||
head = marker * 0.9
|
||||
block = build_connection_symbol(doc, marker, text)
|
||||
stationen = {b.tro_id for b in analysis.boundaries}
|
||||
drawn = 0
|
||||
for src, dst in analysis.edges:
|
||||
if src not in placed or dst not in placed:
|
||||
continue
|
||||
# Farbe/Layer wie im TRO-Fluss-SVG
|
||||
if src in stationen:
|
||||
layer, kind = layers["eingang"], "eingang"
|
||||
elif dst in stationen:
|
||||
layer, kind = layers["ausgang"], "ausgang"
|
||||
elif analysis.edge_kinds.get((src, dst)) == "weiche":
|
||||
layer, kind = layers["weiche"], "weiche"
|
||||
else:
|
||||
layer, kind = layers["flow"], "flow"
|
||||
x1, y1 = placed[src]
|
||||
x2, y2 = placed[dst]
|
||||
length = math.dist((x1, y1), (x2, y2))
|
||||
if length <= 2 * marker * 1.1:
|
||||
continue
|
||||
ux, uy = (x2 - x1) / length, (y2 - y1) / length
|
||||
# An beiden Enden um den Marker kuerzen, damit der Pfeil ihn nicht deckt
|
||||
sx, sy = x1 + ux * marker * 1.1, y1 + uy * marker * 1.1
|
||||
ex, ey = x2 - ux * marker * 1.1, y2 - uy * marker * 1.1
|
||||
# An beiden Enden um den Marker kuerzen, damit der Pfeil ihn nicht
|
||||
# deckt; das Stationsende ist nur eine Beschriftung, dort weniger.
|
||||
kurz_a = marker * (0.3 if src in stationen else 1.1)
|
||||
kurz_b = marker * (0.3 if dst in stationen else 1.1)
|
||||
sx, sy = x1 + ux * kurz_a, y1 + uy * kurz_a
|
||||
ex, ey = x2 - ux * kurz_b, y2 - uy * kurz_b
|
||||
shaft = msp.add_lwpolyline([(sx, sy), (ex, ey)],
|
||||
dxfattribs={"layer": layers["flow"]})
|
||||
dxfattribs={"layer": layer})
|
||||
tag(shaft, f"{src}->{dst}")
|
||||
# Pfeilspitze als gefuellte Flaeche
|
||||
bx, by = ex - ux * head, ey - uy * head
|
||||
px, py = -uy * head * 0.38, ux * head * 0.38
|
||||
point = msp.add_solid(
|
||||
[(ex, ey), (bx + px, by + py), (bx - px, by - py)],
|
||||
dxfattribs={"layer": layers["flow"]},
|
||||
dxfattribs={"layer": layer},
|
||||
)
|
||||
tag(point, f"{src}->{dst}")
|
||||
if kind in ("eingang", "ausgang"):
|
||||
# Stationspfeile ohne Verbindungsblock: keine TRO-TRO-Beziehung
|
||||
drawn += 1
|
||||
continue
|
||||
|
||||
# Traeger der Beziehung: Block in der Pfeilmitte, entlang des Pfeils
|
||||
ref = msp.add_blockref(
|
||||
@@ -626,7 +717,7 @@ def draw_connections(
|
||||
"TO_UID": internal.get(dst, ""),
|
||||
"FROM_TRO": src,
|
||||
"TO_TRO": dst,
|
||||
"KIND": "flow",
|
||||
"KIND": kind,
|
||||
})
|
||||
tag(ref, f"{src}->{dst}")
|
||||
next_id += 1
|
||||
@@ -653,28 +744,48 @@ def draw_legend(
|
||||
title.set_placement((x0, y0 + row))
|
||||
tag(title)
|
||||
|
||||
# Wie die Legende im TRO-Fluss-SVG: je vorkommendem Typ Fuellfarbe, Rand
|
||||
# der Gruppe und Form, danach die Kantenfarben.
|
||||
used = {t.type_name for t in analysis.tros}
|
||||
groups = []
|
||||
for style in STYLES:
|
||||
types = sorted(n for n in used
|
||||
if (get_tro(n).get_group() if get_tro(n) else "ext") == style.group)
|
||||
if types:
|
||||
groups.append((style, types))
|
||||
zeilen: list[tuple[str, str | None, str | None]] = []
|
||||
for definition in TRO_CATALOG:
|
||||
if definition.get_name() in used:
|
||||
zeilen.append((f"{definition.get_name()} - {definition.describe_symbol()}",
|
||||
definition.get_fill(),
|
||||
layers.get(definition.get_group(), layers["ext"])))
|
||||
pfeile = [("eingang", "Materialeingang (Beladung / Eingang)"),
|
||||
("ausgang", "Materialausgang (Entladung / Ausgang)"),
|
||||
("flow", "Uebergabe / Ein-Ausschleusung"),
|
||||
("weiche", "Weiche (Kreisel-Uebergang)")]
|
||||
|
||||
for index, (style, types) in enumerate(groups):
|
||||
y = y0 - index * row
|
||||
box = msp.add_solid(
|
||||
[(x0, y), (x0 + text, y), (x0 + text, y - text), (x0, y - text)],
|
||||
dxfattribs={"layer": layers[style.group]},
|
||||
)
|
||||
y = y0
|
||||
for beschriftung, fill, rand_layer in zeilen:
|
||||
quadrat = [(x0, y), (x0 + text, y), (x0 + text, y - text), (x0, y - text)]
|
||||
box = msp.add_hatch(dxfattribs={"layer": layers["legend"],
|
||||
"true_color": ezdxf.rgb2int(hex_to_rgb(fill))})
|
||||
box.paths.add_polyline_path(quadrat, is_closed=True)
|
||||
tag(box)
|
||||
label = msp.add_text(
|
||||
f"{style.label}: {', '.join(types)}",
|
||||
height=text,
|
||||
dxfattribs={"layer": layers["legend"], "style": "Standard"},
|
||||
)
|
||||
rand = msp.add_lwpolyline(quadrat, close=True, dxfattribs={"layer": rand_layer})
|
||||
tag(rand)
|
||||
label = msp.add_text(beschriftung, height=text,
|
||||
dxfattribs={"layer": layers["legend"], "style": "Standard"})
|
||||
label.set_placement((x0 + text * 1.8, y - text))
|
||||
tag(label)
|
||||
y -= row
|
||||
for key, beschriftung in pfeile:
|
||||
mitte = y - text / 2
|
||||
linie = msp.add_lwpolyline([(x0 - text * 0.5, mitte), (x0 + text * 0.9, mitte)],
|
||||
dxfattribs={"layer": layers[key]})
|
||||
tag(linie)
|
||||
spitze = msp.add_solid([(x0 + text * 1.4, mitte), (x0 + text * 0.9, mitte + text * 0.3),
|
||||
(x0 + text * 0.9, mitte - text * 0.3)],
|
||||
dxfattribs={"layer": layers[key]})
|
||||
tag(spitze)
|
||||
label = msp.add_text(beschriftung, height=text,
|
||||
dxfattribs={"layer": layers[key], "style": "Standard"})
|
||||
label.set_placement((x0 + text * 1.8, y - text))
|
||||
tag(label)
|
||||
y -= row
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -896,9 +1007,11 @@ def main(argv: list[str] | None = None) -> int:
|
||||
args.marker_size, args.text_height)
|
||||
|
||||
if args.flow:
|
||||
# Stationen nur mit --flow: sie bestehen aus ihrem Zu-/Ablaufpfeil
|
||||
stationen = place_stations(msp, analysis, placed, layers, args.text_height)
|
||||
counts["flow"] = draw_connections(
|
||||
msp, doc, analysis, placed, internal, layers, args.marker_size,
|
||||
args.text_height, next_id=next_id + len(placed),
|
||||
msp, doc, analysis, {**placed, **stationen}, internal, layers,
|
||||
args.marker_size, args.text_height, next_id=next_id + len(placed),
|
||||
)
|
||||
if args.legend:
|
||||
draw_legend(msp, analysis, placed, layers, args.marker_size,
|
||||
|
||||
+34
-14
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user