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
+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,
|
||||
|
||||
Reference in New Issue
Block a user