TRO-Fluss: je Typ eigenes Symbol im Diagramm, Weichenerkennung ueber Scanner
lib/tro_catalog.py: dot_shape_attrs()/SYMBOL_LABELS geben render_dot() eine Graphviz-Form je TroSymbol (bisher nur fuer den CAD-Marker in tro_annotate.py genutzt) - Farbe gruppiert weiterhin verwandte Typen, Form unterscheidet den einzelnen Typ. Legende und Doku-Tabellen zeigen jetzt jeden Katalogtyp mit Farbe+Symbol statt nur die 6 Farbgruppen. lib/tro_flow.py: _find_switch_separators() bekommt einen zweiten Durchlauf, der einen Separator mit eigenem Scanner (CAD-Merkmal "Naechster Separator") als Weiche erkennt, wenn die reine Abstandsregel (SWITCH_PROXIMITY_MM) ihn verfehlt - Rolle (Aus-/Einschleusung) wird aus einem bereits bestaetigten Treffer auf derselben Kreisel-Bahn gelesen, keine neue feste mm-Schwelle. Grund: bei HundM05 (5 Abzweige an einem Kreisel) lagen 3 der 5 echten Weichen-Separatoren 727-1139mm von ihrem Ausschleuselement entfernt - ausserhalb der auf einer aelteren Revision kalibrierten 500mm-Grenze - und wurden bisher faelschlich als einfacher 1Sep statt 1Sep1Swi eingestuft. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
60f855fff5
commit
72d0a21f65
+60
-2
@@ -77,6 +77,9 @@ __all__ = [
|
||||
"set_tro",
|
||||
"catalog",
|
||||
"mermaid_classdefs",
|
||||
"dot_shape_attrs",
|
||||
"SYMBOL_LABELS",
|
||||
"symbol_label",
|
||||
]
|
||||
|
||||
# Stand der Dokumente, aus denen die Typdefinition uebernommen wurde
|
||||
@@ -165,6 +168,53 @@ class TroSymbol:
|
||||
)
|
||||
|
||||
|
||||
# Graphviz-Knotenform je Symbol, fuer render_dot() in tro_flow.py. Peripheries
|
||||
# zeichnet die Doppel-Varianten als zwei konzentrische Formen - dasselbe Bild,
|
||||
# das draw_shape() in tro_annotate.py fuer den CAD-Marker als zwei ineinander
|
||||
# liegende Polygone zeichnet. Namen sind Graphviz-Bordformen (dot -Tsvg).
|
||||
_DOT_SHAPE: dict[str, dict[str, str]] = {
|
||||
TroSymbol.CIRCLE: {"shape": "circle"},
|
||||
TroSymbol.DOUBLE_CIRCLE: {"shape": "circle", "peripheries": "2"},
|
||||
TroSymbol.TRIANGLE: {"shape": "triangle"},
|
||||
TroSymbol.TRIANGLE_DOWN: {"shape": "invtriangle"},
|
||||
TroSymbol.DOUBLE_TRIANGLE_DOWN: {"shape": "invtriangle", "peripheries": "2"},
|
||||
TroSymbol.SQUARE: {"shape": "square"},
|
||||
TroSymbol.DIAMOND: {"shape": "diamond"},
|
||||
TroSymbol.PENTAGON: {"shape": "pentagon"},
|
||||
TroSymbol.HEXAGON: {"shape": "hexagon"},
|
||||
TroSymbol.RECT: {"shape": "box"},
|
||||
TroSymbol.ARROW: {"shape": "rarrow"},
|
||||
}
|
||||
|
||||
|
||||
def dot_shape_attrs(symbol: str) -> dict[str, str]:
|
||||
"""Graphviz-Knotenform (shape[, peripheries]) fuer ein TroSymbol; unbekannte
|
||||
Symbole fallen auf die normale Box zurueck."""
|
||||
return dict(_DOT_SHAPE.get(symbol, {"shape": "box"}))
|
||||
|
||||
|
||||
# Klartext je Symbol - fuer die Legende in render_dot()/render_doc() (tro_flow.py),
|
||||
# damit dort nicht nur die Farbgruppe, sondern auch die Form je Typ lesbar ist.
|
||||
SYMBOL_LABELS: dict[str, str] = {
|
||||
TroSymbol.CIRCLE: "Kreis",
|
||||
TroSymbol.DOUBLE_CIRCLE: "Doppelkreis",
|
||||
TroSymbol.TRIANGLE: "Dreieck (Spitze oben)",
|
||||
TroSymbol.TRIANGLE_DOWN: "Dreieck (Spitze unten)",
|
||||
TroSymbol.DOUBLE_TRIANGLE_DOWN: "Doppel-Dreieck (Spitze unten)",
|
||||
TroSymbol.SQUARE: "Quadrat",
|
||||
TroSymbol.DIAMOND: "Raute",
|
||||
TroSymbol.PENTAGON: "Fuenfeck",
|
||||
TroSymbol.HEXAGON: "Sechseck",
|
||||
TroSymbol.RECT: "liegendes Rechteck",
|
||||
TroSymbol.ARROW: "Pfeil",
|
||||
}
|
||||
|
||||
|
||||
def symbol_label(symbol: str) -> str:
|
||||
"""Klartext eines Symbols; der Symbolname selbst, wenn kein Klartext hinterlegt ist."""
|
||||
return SYMBOL_LABELS.get(symbol, symbol)
|
||||
|
||||
|
||||
class TroStyle(BaseModel):
|
||||
"""
|
||||
Darstellung einer TRO-Typgruppe.
|
||||
@@ -371,8 +421,12 @@ class TroDefinition(BaseModel):
|
||||
return self.style.group
|
||||
|
||||
def get_dot_attrs(self) -> dict[str, str]:
|
||||
"""Graphviz-Knotenattribute dieses Typs."""
|
||||
return self.style.dot_attrs()
|
||||
"""Graphviz-Knotenattribute dieses Typs: Fuell-/Randfarbe der Farbgruppe
|
||||
plus die typ-eigene Form (siehe TroSymbol/dot_shape_attrs) - Farbe
|
||||
gruppiert verwandte Typen, Form unterscheidet den einzelnen Typ."""
|
||||
attrs = self.style.dot_attrs()
|
||||
attrs.update(dot_shape_attrs(self.symbol))
|
||||
return attrs
|
||||
|
||||
# -- set ---------------------------------------------------------------
|
||||
#
|
||||
@@ -432,6 +486,10 @@ class TroDefinition(BaseModel):
|
||||
return "-"
|
||||
return ", ".join(f"{self.items[i]}x {i}" for i in self.get_item_names())
|
||||
|
||||
def describe_symbol(self) -> str:
|
||||
"""Symbol als Klartext, z. B. 'Dreieck (Spitze oben)'."""
|
||||
return symbol_label(self.symbol)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Statische Typdefinition
|
||||
|
||||
+80
-22
@@ -273,20 +273,38 @@ def _default_resolve_host(sep: Element, findings: list[str], graph: Graph) -> st
|
||||
def _find_switch_separators(
|
||||
separators: list[Element], elements: list[Element], tol_mm: float
|
||||
) -> dict[str, tuple[Element, float]]:
|
||||
"""Separator-Id -> (naechstgelegenes Aus-/Einschleuselement, Abstand mm),
|
||||
nur wenn Abstand <= tol_mm.
|
||||
"""Separator-Id -> (Aus-/Einschleuselement der Weiche, Abstand mm).
|
||||
|
||||
HundM05 hat an seinen Kreisel<->Strecke-Uebergaengen kein eigenes "ILS
|
||||
Weiche"-Bauteil (anders als ein Kreisel<->Kreisel-Beruehrpunkt, siehe
|
||||
Mubea) - das von dxfmakros synthetisierte Ausschleuselement (Kreisel ->
|
||||
Strecke, am Streckenanfang) bzw. Einschleuselement (Strecke -> Kreisel,
|
||||
am Streckenende) uebernimmt dort faktisch die Rolle der Weiche. Sitzt ein
|
||||
Separator direkt daneben, gehoert er zu dieser Weiche - unabhaengig davon,
|
||||
ob seine CSV-Spalte "Zuordnung" auf die Kreisel-Bahn oder auf die Strecke
|
||||
zeigt (in den Daten kommt beides vor, siehe derive_tros).
|
||||
Zwei Regeln, angewandt in dieser Reihenfolge:
|
||||
|
||||
1. Geometrische Naehe (Distanz <= tol_mm). HundM05 hat an seinen
|
||||
Kreisel<->Strecke-Uebergaengen kein eigenes "ILS Weiche"-Bauteil
|
||||
(anders als ein Kreisel<->Kreisel-Beruehrpunkt, siehe Mubea) - das von
|
||||
dxfmakros synthetisierte Ausschleuselement (Kreisel -> Strecke, am
|
||||
Streckenanfang) bzw. Einschleuselement (Strecke -> Kreisel, am
|
||||
Streckenende) uebernimmt dort faktisch die Rolle der Weiche. Sitzt ein
|
||||
Separator direkt daneben, gehoert er zu dieser Weiche - unabhaengig
|
||||
davon, ob seine CSV-Spalte "Zuordnung" auf die Kreisel-Bahn oder auf
|
||||
die Strecke zeigt (in den Daten kommt beides vor, siehe derive_tros).
|
||||
2. Separator mit eigenem Scanner (Scanner-Merkmal "Naechster Separator"),
|
||||
auf noch unbelegten Uebergabestellen der SELBEN Rolle (Aus- oder
|
||||
Einschleusung) und dem SELBEN Kreisel-Anschluss wie ein bereits durch
|
||||
Regel 1 bestaetigter Treffer auf dieser Bahn - keine feste mm-Grenze,
|
||||
stattdessen die naechstgelegene freie Uebergabestelle dieser Rolle.
|
||||
Grund (HundM05, Review 2026-09-18): an einem Kreisel mit 5 Abzweigen
|
||||
liegen die tatsaechlichen Schalt-Separatoren pro Abzweig unterschiedlich
|
||||
weit vom Uebergabeelement entfernt (418-1139mm in der aktuellen
|
||||
Registrierung) - eine einzelne mm-Schwelle (Regel 1) trifft nicht alle.
|
||||
Ein Scanner direkt am Separator ist hier aber der CAD-belegte
|
||||
Lesepunkt vor der Weichenentscheidung (Barcode -> Richtung), also ein
|
||||
verlaesslicherer Marker als der reine Abstand. Nach Rolle (Aus- vs.
|
||||
Einschleusung) gefiltert, damit z. B. ein Separator auf der
|
||||
Ausschleus-Bahn nicht versehentlich mit dem naeher liegenden
|
||||
Einschleuselement einer anderen Strecke verkoppelt wird.
|
||||
"""
|
||||
transfers = [e for e in elements if e.kind in ("Ausschleus", "Einschleus")]
|
||||
result: dict[str, tuple[Element, float]] = {}
|
||||
|
||||
for sep in separators:
|
||||
if sep.x is None or sep.y is None:
|
||||
continue
|
||||
@@ -299,6 +317,41 @@ def _find_switch_separators(
|
||||
best = (d, transfer)
|
||||
if best is not None:
|
||||
result[sep.teile_id] = (best[1], best[0])
|
||||
|
||||
# Rolle je Kreisel-Anschluss aus einem bereits bestaetigten Treffer lesen -
|
||||
# keine Annahme, welche Bahn (-L/-R) Aus- bzw. Einschleusung uebernimmt.
|
||||
bahn_role: dict[str, str] = {}
|
||||
for sep in separators:
|
||||
hit = result.get(sep.teile_id)
|
||||
if hit and sep.assignment:
|
||||
bahn_role.setdefault(sep.assignment, hit[0].kind)
|
||||
|
||||
scanner_targets = {
|
||||
str(_merkmal(e.merkmale, "Naechster Separator") or "").strip()
|
||||
for e in elements if e.kind == "Scanner"
|
||||
}
|
||||
used_transfers = {hit[0].teile_id for hit in result.values()}
|
||||
|
||||
for sep in separators:
|
||||
if sep.teile_id in result or sep.teile_id not in scanner_targets:
|
||||
continue
|
||||
if sep.x is None or sep.y is None:
|
||||
continue
|
||||
role = bahn_role.get(sep.assignment or "")
|
||||
if role is None:
|
||||
continue
|
||||
best: tuple[float, Element] | None = None
|
||||
for transfer in transfers:
|
||||
if (transfer.kind != role or transfer.teile_id in used_transfers
|
||||
or transfer.x is None or transfer.y is None):
|
||||
continue
|
||||
d = math.hypot(transfer.x - sep.x, transfer.y - sep.y)
|
||||
if best is None or d < best[0]:
|
||||
best = (d, transfer)
|
||||
if best is not None:
|
||||
result[sep.teile_id] = (best[1], best[0])
|
||||
used_transfers.add(best[1].teile_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -976,14 +1029,17 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
||||
f'color="#7f7f7f", constraint=false, tooltip="gleiche Bahn"];')
|
||||
add("")
|
||||
|
||||
# Legende: eine Zeile je Farbgruppe, plus die Kantenarten.
|
||||
# Bei -n braucht auch sie ein pos, sonst landet sie auf 0,0 mitten in der
|
||||
# Anlage - daher unterhalb links vom Anlagenrand.
|
||||
# Legende: eine Zeile je TRO-Typ (Farbe der Gruppe + eigene Form, siehe
|
||||
# tro_catalog.TRO_CATALOG), plus eine Zeile fuer die Systemgrenzen und die
|
||||
# Kantenarten. Bei -n braucht auch sie ein pos, sonst landet sie auf 0,0
|
||||
# mitten in der Anlage - daher unterhalb links vom Anlagenrand.
|
||||
rows = "".join(
|
||||
f'<tr><td bgcolor="{s.fill}" align="left"> {s.group} </td>'
|
||||
f'<td align="left">{s.label}</td></tr>'
|
||||
for s in STYLES
|
||||
f'<tr><td bgcolor="{d.get_style().fill}" align="left"> {d.get_name()} </td>'
|
||||
f'<td align="left">{d.describe_symbol()}</td></tr>'
|
||||
for d in TRO_CATALOG
|
||||
)
|
||||
rows += (f'<tr><td bgcolor="{STYLE_EXT.fill}" align="left"> Systemgrenze </td>'
|
||||
f'<td align="left">Ellipse (Materialein-/ausgang, kein TRO)</td></tr>')
|
||||
legend_label = ('<<table border="0" cellborder="1" cellspacing="0" cellpadding="3">'
|
||||
+ rows
|
||||
+ '<tr><td align="left"><font color="#2f5597">durchgezogen</font></td>'
|
||||
@@ -1165,14 +1221,15 @@ def render_doc(
|
||||
add(f"| Offene Punkte (noch kein TRO) | "
|
||||
f"{len(analysis.overrides.open_points)} |")
|
||||
add("")
|
||||
add("| Typ | FB-Baustein | Anzahl hier | Bauteile je TRO | Farbgruppe |")
|
||||
add("|---|---|---:|---|---|")
|
||||
add("| Typ | FB-Baustein | Anzahl hier | Bauteile je TRO | Farbgruppe | Symbol |")
|
||||
add("|---|---|---:|---|---|---|")
|
||||
for name, count in sorted(types_used.items()):
|
||||
definition = get_tro(name)
|
||||
add(f"| `{name}` | "
|
||||
f"{f'`{definition.get_fb_block()}`' if definition else '_kein Katalogeintrag_'} "
|
||||
f"| {count} | {definition.describe_items() if definition else '-'} "
|
||||
f"| {definition.get_group() if definition else '-'} |")
|
||||
f"| {definition.get_group() if definition else '-'} "
|
||||
f"| {definition.describe_symbol() if definition else '-'} |")
|
||||
add("")
|
||||
|
||||
add("## 2. TRO-Flussdiagramm")
|
||||
@@ -1346,11 +1403,12 @@ def render_doc(
|
||||
add(f"Typen, FB-Bausteine, Bauteile und Farben stammen vollstaendig aus "
|
||||
f"`TRO_CATALOG` in `lib/tro_catalog.py` (Stand {CATALOG_AS_OF}):")
|
||||
add("")
|
||||
add("| # | Typ | FB-Baustein | Bauteile je TRO | Farbgruppe |")
|
||||
add("|---:|---|---|---|---|")
|
||||
add("| # | Typ | FB-Baustein | Bauteile je TRO | Farbgruppe | Symbol |")
|
||||
add("|---:|---|---|---|---|---|")
|
||||
for index, definition in enumerate(TRO_CATALOG, start=1):
|
||||
add(f"| {index} | `{definition.get_name()}` | `{definition.get_fb_block()}` "
|
||||
f"| {definition.describe_items()} | {definition.get_group()} |")
|
||||
f"| {definition.describe_items()} | {definition.get_group()} "
|
||||
f"| {definition.describe_symbol()} |")
|
||||
add("")
|
||||
add("| Bauteil | Sub-FB |")
|
||||
add("|---|---|")
|
||||
|
||||
Reference in New Issue
Block a user