TRO-Fluss: tro_overrides.ini standardmaessig aus, ungelistete Strecken-Separatoren lagerichtig verketten
- tro_flow/tro_annotate: cfg/tro_overrides.ini nur noch mit --use-overrides (die Abschnitte haengen an laufenden TRO-Nummern und am CSV-Namen und veralten mit jedem Export; [export.csv.connections] ersetzte alle abgeleiteten Kanten und liess TRO13-20 unverbunden) - link_tros: Separatoren mit Zuordnung auf eine Strecke, die in keiner TrackIds-Liste stehen, per Projektion AS->ES in die Strecken-Kette einsortieren (Mubea VF_3: 0037/0038 erzeugten je 9 Fehlkanten; mubea 38 -> 22 Verbindungen, HundM05 unveraendert) - tro_catalog: typ-eigene Fuellfarbe je TRO-Typ, Mermaid-classDefs je Typ - tro_overrides/tro_flow: connect.ini zurueck einlesen (--from-connect) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1479519bdc
commit
02db9a95b0
+7
-2
@@ -102,7 +102,7 @@ from dxf_registration import (
|
|||||||
from material_flow import build_graph, env_dir, read_elements, resolve_input
|
from material_flow import build_graph, env_dir, read_elements, resolve_input
|
||||||
from tro_catalog import STYLES, TRO_CATALOG, TroSymbol, get_tro
|
from tro_catalog import STYLES, TRO_CATALOG, TroSymbol, get_tro
|
||||||
from tro_flow import Analysis, analyse_tros
|
from tro_flow import Analysis, analyse_tros
|
||||||
from tro_overrides import load_overrides
|
from tro_overrides import Overrides, load_overrides
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Konstanten
|
# Konstanten
|
||||||
@@ -782,6 +782,10 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
parser.add_argument("--emit-lisp", action="store_true",
|
parser.add_argument("--emit-lisp", action="store_true",
|
||||||
help="cad/tro_types.lsp aus dem Typkatalog neu schreiben "
|
help="cad/tro_types.lsp aus dem Typkatalog neu schreiben "
|
||||||
"(Auswahlliste des Dialogs TROEDIT) und beenden.")
|
"(Auswahlliste des Dialogs TROEDIT) und beenden.")
|
||||||
|
parser.add_argument("--use-overrides", action="store_true",
|
||||||
|
help="Manuelle Korrekturen aus cfg/tro_overrides.ini "
|
||||||
|
"anwenden. Standard ist AUS - wie bei tro_flow, "
|
||||||
|
"damit Marker und Flussdiagramm zusammenpassen.")
|
||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
@@ -834,7 +838,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
print(f"FEHLER: {csv_file} nicht lesbar: {exc}", file=sys.stderr)
|
print(f"FEHLER: {csv_file} nicht lesbar: {exc}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
graph = build_graph(elements, csv_file.name, read_warnings)
|
graph = build_graph(elements, csv_file.name, read_warnings)
|
||||||
overrides = load_overrides(csv_file.name, cfg_dir)
|
overrides = (load_overrides(csv_file.name, cfg_dir) if args.use_overrides
|
||||||
|
else Overrides())
|
||||||
analysis = analyse_tros(graph, elements, overrides)
|
analysis = analyse_tros(graph, elements, overrides)
|
||||||
if not analysis.tros:
|
if not analysis.tros:
|
||||||
print(f"FEHLER: keine TROs aus {csv_file} ableitbar.", file=sys.stderr)
|
print(f"FEHLER: keine TROs aus {csv_file} ableitbar.", file=sys.stderr)
|
||||||
|
|||||||
+54
-16
@@ -231,6 +231,11 @@ class TroStyle(BaseModel):
|
|||||||
|
|
||||||
Wichtig: gruen bedeutet **Speicher**, nicht Foerderer. Der Vario ist
|
Wichtig: gruen bedeutet **Speicher**, nicht Foerderer. Der Vario ist
|
||||||
violett.
|
violett.
|
||||||
|
|
||||||
|
Innerhalb einer Gruppe hat jeder Typ zusaetzlich eine eigene Fuellfarbe
|
||||||
|
(TroDefinition.fill, ein Ton im Farbton der Gruppe); der Rand bleibt die
|
||||||
|
Gruppenfarbe (stroke). So ist jeder Typ an Farbe UND Form eindeutig, und
|
||||||
|
die Gruppe bleibt am Farbton/Rand lesbar.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model_config = ConfigDict(validate_assignment=True, extra="forbid",
|
model_config = ConfigDict(validate_assignment=True, extra="forbid",
|
||||||
@@ -337,6 +342,12 @@ class TroDefinition(BaseModel):
|
|||||||
min_length=1,
|
min_length=1,
|
||||||
description="Zeichensymbol des Markers im CAD, z. B. 'triangle'",
|
description="Zeichensymbol des Markers im CAD, z. B. 'triangle'",
|
||||||
)
|
)
|
||||||
|
fill: str = Field(
|
||||||
|
default="",
|
||||||
|
description="Typ-eigene Fuellfarbe als Hex; leer = Fuellfarbe der Farbgruppe. "
|
||||||
|
"Der Rand bleibt immer in der Gruppenfarbe, damit die Gruppe "
|
||||||
|
"trotz eigener Typfarbe erkennbar bleibt.",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -345,6 +356,7 @@ class TroDefinition(BaseModel):
|
|||||||
items: dict[str, int] | None = None,
|
items: dict[str, int] | None = None,
|
||||||
style: TroStyle | None = None,
|
style: TroStyle | None = None,
|
||||||
symbol: str | None = None,
|
symbol: str | None = None,
|
||||||
|
fill: str | None = None,
|
||||||
**data,
|
**data,
|
||||||
) -> None:
|
) -> None:
|
||||||
# Positionsargumente auf Feldnamen abbilden, damit die bisherige
|
# Positionsargumente auf Feldnamen abbilden, damit die bisherige
|
||||||
@@ -359,6 +371,8 @@ class TroDefinition(BaseModel):
|
|||||||
data["style"] = style
|
data["style"] = style
|
||||||
if symbol is not None:
|
if symbol is not None:
|
||||||
data["symbol"] = symbol
|
data["symbol"] = symbol
|
||||||
|
if fill is not None:
|
||||||
|
data["fill"] = fill
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
|
|
||||||
@field_validator("items", mode="after")
|
@field_validator("items", mode="after")
|
||||||
@@ -420,14 +434,32 @@ class TroDefinition(BaseModel):
|
|||||||
"""Kurzname der Farbgruppe, z. B. 'vario'."""
|
"""Kurzname der Farbgruppe, z. B. 'vario'."""
|
||||||
return self.style.group
|
return self.style.group
|
||||||
|
|
||||||
|
def get_fill(self) -> str:
|
||||||
|
"""Fuellfarbe dieses Typs: die typ-eigene, sonst die der Farbgruppe."""
|
||||||
|
return self.fill or self.style.fill
|
||||||
|
|
||||||
def get_dot_attrs(self) -> dict[str, str]:
|
def get_dot_attrs(self) -> dict[str, str]:
|
||||||
"""Graphviz-Knotenattribute dieses Typs: Fuell-/Randfarbe der Farbgruppe
|
"""Graphviz-Knotenattribute dieses Typs: typ-eigene Fuellfarbe, Rand in
|
||||||
plus die typ-eigene Form (siehe TroSymbol/dot_shape_attrs) - Farbe
|
der Farbe der Gruppe, plus die typ-eigene Form (siehe TroSymbol/
|
||||||
gruppiert verwandte Typen, Form unterscheidet den einzelnen Typ."""
|
dot_shape_attrs) - Rand und Farbton gruppieren verwandte Typen,
|
||||||
|
Fuellung und Form unterscheiden den einzelnen Typ."""
|
||||||
attrs = self.style.dot_attrs()
|
attrs = self.style.dot_attrs()
|
||||||
|
attrs["fillcolor"] = self.get_fill()
|
||||||
attrs.update(dot_shape_attrs(self.symbol))
|
attrs.update(dot_shape_attrs(self.symbol))
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
def mermaid_class(self) -> str:
|
||||||
|
"""Mermaid-Klassenname dieses Typs (Typnamen beginnen teils mit einer
|
||||||
|
Ziffer, die Mermaid als Klassenname nicht erlaubt)."""
|
||||||
|
return f"t_{self.name}"
|
||||||
|
|
||||||
|
def mermaid_classdef(self) -> str:
|
||||||
|
"""Mermaid-classDef-Zeile fuer diesen Typ (eigene Fuellung, Gruppenrand)."""
|
||||||
|
return (
|
||||||
|
f"classDef {self.mermaid_class()} fill:{self.get_fill()},"
|
||||||
|
f"stroke:{self.style.stroke},color:{self.style.font}"
|
||||||
|
)
|
||||||
|
|
||||||
# -- set ---------------------------------------------------------------
|
# -- set ---------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Jede Zuweisung laeuft durch die Pydantic-Validierung, weil
|
# Jede Zuweisung laeuft durch die Pydantic-Validierung, weil
|
||||||
@@ -453,6 +485,10 @@ class TroDefinition(BaseModel):
|
|||||||
"""Zeichensymbol des Markers setzen."""
|
"""Zeichensymbol des Markers setzen."""
|
||||||
self.symbol = symbol
|
self.symbol = symbol
|
||||||
|
|
||||||
|
def set_fill(self, fill: str) -> None:
|
||||||
|
"""Typ-eigene Fuellfarbe setzen ('' = Farbe der Gruppe)."""
|
||||||
|
self.fill = fill
|
||||||
|
|
||||||
def set_item(self, item: str, count: int) -> None:
|
def set_item(self, item: str, count: int) -> None:
|
||||||
"""
|
"""
|
||||||
Anzahl einer Bauteilart setzen.
|
Anzahl einer Bauteilart setzen.
|
||||||
@@ -503,29 +539,29 @@ TRO_CATALOG: tuple[TroDefinition, ...] = (
|
|||||||
# (PriorityManager, JamArea Entry/Exit, HMI, Safety, MachineState).
|
# (PriorityManager, JamArea Entry/Exit, HMI, Safety, MachineState).
|
||||||
TroDefinition("1Sep", "FB_ILS_MTRO_1Sep", {
|
TroDefinition("1Sep", "FB_ILS_MTRO_1Sep", {
|
||||||
TroItem.SEPARATOR: 1,
|
TroItem.SEPARATOR: 1,
|
||||||
}, STYLE_SEP, TroSymbol.CIRCLE),
|
}, STYLE_SEP, TroSymbol.CIRCLE, "#d4e8ff"),
|
||||||
# laut TRO_Typen.md: Basis + 1x Switch-Modul
|
# laut TRO_Typen.md: Basis + 1x Switch-Modul
|
||||||
TroDefinition("1Sep1Swi", "FB_ILS_MTRO_1Sep1Swi", {
|
TroDefinition("1Sep1Swi", "FB_ILS_MTRO_1Sep1Swi", {
|
||||||
TroItem.SEPARATOR: 1,
|
TroItem.SEPARATOR: 1,
|
||||||
TroItem.SWITCH: 1,
|
TroItem.SWITCH: 1,
|
||||||
}, STYLE_SWI, TroSymbol.TRIANGLE),
|
}, STYLE_SWI, TroSymbol.TRIANGLE, "#ffe4a0"),
|
||||||
# laut TRO_Typen.md: Basis + 2x Switch-Modul + Scanner-Modul
|
# laut TRO_Typen.md: Basis + 2x Switch-Modul + Scanner-Modul
|
||||||
TroDefinition("1Sep2Swi", "FB_ILS_MTRO_1Sep2Swi", {
|
TroDefinition("1Sep2Swi", "FB_ILS_MTRO_1Sep2Swi", {
|
||||||
TroItem.SEPARATOR: 1,
|
TroItem.SEPARATOR: 1,
|
||||||
TroItem.SWITCH: 2,
|
TroItem.SWITCH: 2,
|
||||||
TroItem.SCANNER: 1,
|
TroItem.SCANNER: 1,
|
||||||
}, STYLE_SWI, TroSymbol.PENTAGON),
|
}, STYLE_SWI, TroSymbol.PENTAGON, "#ffc766"),
|
||||||
# laut TRO_Typen.md: Basis + Scanner-Modul + SSCC-Modul (Messsensor + WCS)
|
# laut TRO_Typen.md: Basis + Scanner-Modul + SSCC-Modul (Messsensor + WCS)
|
||||||
TroDefinition("1Sep_SSCC", "FB_ILS_MTRO_1Sep_SSCC", {
|
TroDefinition("1Sep_SSCC", "FB_ILS_MTRO_1Sep_SSCC", {
|
||||||
TroItem.SEPARATOR: 1,
|
TroItem.SEPARATOR: 1,
|
||||||
TroItem.SCANNER: 1,
|
TroItem.SCANNER: 1,
|
||||||
TroItem.SSCC: 1,
|
TroItem.SSCC: 1,
|
||||||
}, STYLE_SSCC, TroSymbol.DOUBLE_CIRCLE),
|
}, STYLE_SSCC, TroSymbol.DOUBLE_CIRCLE, "#ffd4d4"),
|
||||||
# laut TRO_Typen.md: Basis + Vario-Antriebsmodul
|
# laut TRO_Typen.md: Basis + Vario-Antriebsmodul
|
||||||
TroDefinition("Vario", "FB_ILS_MTRO_Vario", {
|
TroDefinition("Vario", "FB_ILS_MTRO_Vario", {
|
||||||
TroItem.SEPARATOR: 1,
|
TroItem.SEPARATOR: 1,
|
||||||
TroItem.VARIO_DRIVE: 1,
|
TroItem.VARIO_DRIVE: 1,
|
||||||
}, STYLE_VARIO, TroSymbol.RECT),
|
}, STYLE_VARIO, TroSymbol.RECT, "#e8d4f0"),
|
||||||
# laut TRO_Typen.md: Basis (Pin-Separator) + 19x Storage-Line + Scanner
|
# laut TRO_Typen.md: Basis (Pin-Separator) + 19x Storage-Line + Scanner
|
||||||
# + Encoder + StorageRemoval-Schnittstelle
|
# + Encoder + StorageRemoval-Schnittstelle
|
||||||
TroDefinition("PinStore_Auto", "FB_ILS_MTRO_PinStore_Auto", {
|
TroDefinition("PinStore_Auto", "FB_ILS_MTRO_PinStore_Auto", {
|
||||||
@@ -533,7 +569,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = (
|
|||||||
TroItem.STORAGE_LINE: 19,
|
TroItem.STORAGE_LINE: 19,
|
||||||
TroItem.SCANNER: 1,
|
TroItem.SCANNER: 1,
|
||||||
TroItem.ENCODER: 1,
|
TroItem.ENCODER: 1,
|
||||||
}, STYLE_STORE, TroSymbol.SQUARE),
|
}, STYLE_STORE, TroSymbol.SQUARE, "#d4f0d4"),
|
||||||
# aus SCL belegt (FB_ILS_MTRO_Vario_workStation.scl, VAR ab Zeile 77):
|
# aus SCL belegt (FB_ILS_MTRO_Vario_workStation.scl, VAR ab Zeile 77):
|
||||||
# fbSeparator1, fbConvVario, fbSepWait, fbBarcodeReader.
|
# fbSeparator1, fbConvVario, fbSepWait, fbBarcodeReader.
|
||||||
# Der Vario-Antrieb ist hier die WorkStation-Variante des STRO-Bausteins.
|
# Der Vario-Antrieb ist hier die WorkStation-Variante des STRO-Bausteins.
|
||||||
@@ -543,7 +579,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = (
|
|||||||
TroItem.WORK_STATION: 1,
|
TroItem.WORK_STATION: 1,
|
||||||
TroItem.ACCUMULATE: 1,
|
TroItem.ACCUMULATE: 1,
|
||||||
TroItem.SCANNER: 1,
|
TroItem.SCANNER: 1,
|
||||||
}, STYLE_VARIO, TroSymbol.HEXAGON),
|
}, STYLE_VARIO, TroSymbol.HEXAGON, "#caa6e0"),
|
||||||
# aus SCL belegt (FB_EmptyCarrBuffer.scl): arfbSeparatorStorage als
|
# aus SCL belegt (FB_EmptyCarrBuffer.scl): arfbSeparatorStorage als
|
||||||
# Array[1..5] of FB_ILS_STRO_Sep + 5x fbCarrierWaitStore.
|
# Array[1..5] of FB_ILS_STRO_Sep + 5x fbCarrierWaitStore.
|
||||||
# Kein eigener Eingangs-Separator - der "inTro" ist ein separater 1Sep-TRO
|
# Kein eigener Eingangs-Separator - der "inTro" ist ein separater 1Sep-TRO
|
||||||
@@ -551,7 +587,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = (
|
|||||||
TroDefinition("EmptyCarrBuffer", "FB_EmptyCarrBuffer", {
|
TroDefinition("EmptyCarrBuffer", "FB_EmptyCarrBuffer", {
|
||||||
TroItem.STORAGE_LINE: 5,
|
TroItem.STORAGE_LINE: 5,
|
||||||
TroItem.ACCUMULATE: 5,
|
TroItem.ACCUMULATE: 5,
|
||||||
}, STYLE_STORE, TroSymbol.DIAMOND),
|
}, STYLE_STORE, TroSymbol.DIAMOND, "#a6dca6"),
|
||||||
# aus SCL belegt (FB_LoadingBoom_INBOUND.scl): fbTiltSensor,
|
# aus SCL belegt (FB_LoadingBoom_INBOUND.scl): fbTiltSensor,
|
||||||
# fbDistanceFoot, fbWorkSation. Bewusst KEIN Separator und KEINE Weiche -
|
# fbDistanceFoot, fbWorkSation. Bewusst KEIN Separator und KEINE Weiche -
|
||||||
# der einzige nicht additive Typ.
|
# der einzige nicht additive Typ.
|
||||||
@@ -559,7 +595,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = (
|
|||||||
TroItem.BOOM: 1,
|
TroItem.BOOM: 1,
|
||||||
TroItem.FOOT: 1,
|
TroItem.FOOT: 1,
|
||||||
TroItem.TILT_SENSOR: 1,
|
TroItem.TILT_SENSOR: 1,
|
||||||
}, STYLE_EXT, TroSymbol.ARROW),
|
}, STYLE_EXT, TroSymbol.ARROW, "#d9d9d9"),
|
||||||
# aus SCL belegt (FB_ILS_MTRO_2Sep1Swi.scl, VAR ab Zeile 115):
|
# aus SCL belegt (FB_ILS_MTRO_2Sep1Swi.scl, VAR ab Zeile 115):
|
||||||
# fbSeparator1, fbSeparator2, fbSwitch1, fbBarcodeReader1, fbBarcodeReader2.
|
# fbSeparator1, fbSeparator2, fbSwitch1, fbBarcodeReader1, fbBarcodeReader2.
|
||||||
# Die Mapping-Tabelle in TRO_Typen.md nennt die beiden Scanner nicht -
|
# Die Mapping-Tabelle in TRO_Typen.md nennt die beiden Scanner nicht -
|
||||||
@@ -568,7 +604,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = (
|
|||||||
TroItem.SEPARATOR: 2,
|
TroItem.SEPARATOR: 2,
|
||||||
TroItem.SWITCH: 1,
|
TroItem.SWITCH: 1,
|
||||||
TroItem.SCANNER: 2,
|
TroItem.SCANNER: 2,
|
||||||
}, STYLE_SWI, TroSymbol.TRIANGLE_DOWN),
|
}, STYLE_SWI, TroSymbol.TRIANGLE_DOWN, "#ffd6ad"),
|
||||||
# ACHTUNG - NICHT aus SCL belegt. Alle Typen oberhalb stammen aus
|
# ACHTUNG - NICHT aus SCL belegt. Alle Typen oberhalb stammen aus
|
||||||
# vorhandenem Code bzw. der Mapping-Tabelle in TRO_Typen.md; dieser hier
|
# vorhandenem Code bzw. der Mapping-Tabelle in TRO_Typen.md; dieser hier
|
||||||
# wurde am 2026-08-12 auf Anforderung aus dem Projekt 500573 (Mubea)
|
# wurde am 2026-08-12 auf Anforderung aus dem Projekt 500573 (Mubea)
|
||||||
@@ -582,7 +618,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = (
|
|||||||
TroItem.SEPARATOR: 2,
|
TroItem.SEPARATOR: 2,
|
||||||
TroItem.SWITCH: 2,
|
TroItem.SWITCH: 2,
|
||||||
TroItem.SCANNER: 2,
|
TroItem.SCANNER: 2,
|
||||||
}, STYLE_SWI, TroSymbol.DOUBLE_TRIANGLE_DOWN),
|
}, STYLE_SWI, TroSymbol.DOUBLE_TRIANGLE_DOWN, "#f2a65a"),
|
||||||
)
|
)
|
||||||
|
|
||||||
_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}
|
||||||
@@ -610,5 +646,7 @@ def catalog() -> dict[str, TroDefinition]:
|
|||||||
|
|
||||||
|
|
||||||
def mermaid_classdefs() -> list[str]:
|
def mermaid_classdefs() -> list[str]:
|
||||||
"""classDef-Zeilen fuer ein Mermaid-Diagramm, eine je Farbgruppe."""
|
"""classDef-Zeilen fuer ein Mermaid-Diagramm: eine je Farbgruppe (u. a.
|
||||||
return [style.mermaid_classdef() for style in STYLES]
|
'ext' fuer die Systemgrenzen) plus eine je Typ (t_<Name>)."""
|
||||||
|
return ([style.mermaid_classdef() for style in STYLES]
|
||||||
|
+ [definition.mermaid_classdef() for definition in TRO_CATALOG])
|
||||||
|
|||||||
+218
-7
@@ -142,6 +142,7 @@ from tro_overrides import (
|
|||||||
apply_merges,
|
apply_merges,
|
||||||
apply_overrides,
|
apply_overrides,
|
||||||
apply_splits,
|
apply_splits,
|
||||||
|
load_connect_ini,
|
||||||
load_overrides,
|
load_overrides,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1013,6 +1014,62 @@ def _tro_bahn(tro: Tro, graph: Graph, sep_assignment: dict[str, str]) -> str | N
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _chain_mit_ungelisteten_separatoren(
|
||||||
|
strecke: Element,
|
||||||
|
chain: list[str],
|
||||||
|
elements: list[Element],
|
||||||
|
elements_by_id: dict[str, Element],
|
||||||
|
) -> list[str]:
|
||||||
|
"""Separatoren der Strecke, die in KEINER TrackIds-Liste stehen, lagerichtig
|
||||||
|
in die (bereits AS -> ES orientierte) Kette einsortieren.
|
||||||
|
|
||||||
|
Hintergrund (Mubea, VF_3): Separatoren 0037/0038 sind per Zuordnung der
|
||||||
|
Strecke 0015 zugeordnet und liegen physisch zwischen 0062 und 0063, fehlen
|
||||||
|
aber in deren TrackIds. Ohne Platz in der Kette bekommen ihre TROs keinen
|
||||||
|
praezisen Nachfolger, behalten die groben Contraction-Walk-Kanten und
|
||||||
|
zeigen auf jeden TRO hinter Kreisel2 Bahn R (je 9 Fehlkanten).
|
||||||
|
|
||||||
|
Einsortiert wird ueber die Projektion auf die Gerade erstes -> letztes
|
||||||
|
Kettenelement. Steht ein Separator in irgendeiner TrackIds-Liste (z. B.
|
||||||
|
eines Kreisels), gehoert er dorthin und bleibt hier draussen.
|
||||||
|
"""
|
||||||
|
if len(chain) < 2:
|
||||||
|
return chain
|
||||||
|
gelistet = {tid for e in elements for tid in e.track_ids}
|
||||||
|
fehlend = [
|
||||||
|
e for e in elements
|
||||||
|
if e.kind == "Separator" and e.assignment == strecke.teile_id
|
||||||
|
and e.teile_id not in gelistet and e.position
|
||||||
|
]
|
||||||
|
if not fehlend:
|
||||||
|
return chain
|
||||||
|
anfang = elements_by_id.get(chain[0])
|
||||||
|
ende = elements_by_id.get(chain[-1])
|
||||||
|
if not (anfang and ende and anfang.position and ende.position):
|
||||||
|
return chain
|
||||||
|
dx = ende.position[0] - anfang.position[0]
|
||||||
|
dy = ende.position[1] - anfang.position[1]
|
||||||
|
if dx == 0 and dy == 0:
|
||||||
|
return chain
|
||||||
|
|
||||||
|
def _t(pos) -> float:
|
||||||
|
return (pos[0] - anfang.position[0]) * dx + (pos[1] - anfang.position[1]) * dy
|
||||||
|
|
||||||
|
# Kettenelemente ohne Position behalten ihren Platz: sie erben den Wert des
|
||||||
|
# Vorgaengers, damit die Sortierung stabil bleibt.
|
||||||
|
werte: list[tuple[float, int, str]] = []
|
||||||
|
letzter = float("-inf")
|
||||||
|
for i, tid in enumerate(chain):
|
||||||
|
el = elements_by_id.get(tid)
|
||||||
|
if el is not None and el.position:
|
||||||
|
letzter = _t(el.position)
|
||||||
|
werte.append((letzter, i, tid))
|
||||||
|
for e in fehlend:
|
||||||
|
werte.append((_t(e.position), len(chain), e.teile_id))
|
||||||
|
werte.sort(key=lambda w: (w[0], w[1]))
|
||||||
|
return [tid for _, _, tid in werte]
|
||||||
|
|
||||||
|
|
||||||
def link_tros(
|
def link_tros(
|
||||||
graph: Graph, tros: list[Tro], elements: list[Element]
|
graph: Graph, tros: list[Tro], elements: list[Element]
|
||||||
) -> tuple[list[tuple[str, str]], dict[tuple[str, str], str]]:
|
) -> tuple[list[tuple[str, str]], dict[tuple[str, str], str]]:
|
||||||
@@ -1133,6 +1190,7 @@ def link_tros(
|
|||||||
# er ist der Kreisel-Bahn zugehoerig und dort schon (Schritt 1 unten)
|
# er ist der Kreisel-Bahn zugehoerig und dort schon (Schritt 1 unten)
|
||||||
# verkettet. Ohne diesen Ausschluss wuerde er faelschlich als erster
|
# verkettet. Ohne diesen Ausschluss wuerde er faelschlich als erster
|
||||||
# oder letzter "Vario" gelten (Review 2026-09-21).
|
# oder letzter "Vario" gelten (Review 2026-09-21).
|
||||||
|
chain = _chain_mit_ungelisteten_separatoren(el, chain, elements, elements_by_id)
|
||||||
owned = [
|
owned = [
|
||||||
elem_owner[tid] for tid in chain if tid in elem_owner
|
elem_owner[tid] for tid in chain if tid in elem_owner
|
||||||
and not ((d := by_id[elem_owner[tid]].definition()) and d.has_item(TroItem.SWITCH))
|
and not ((d := by_id[elem_owner[tid]].definition()) and d.has_item(TroItem.SWITCH))
|
||||||
@@ -1550,7 +1608,7 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
|||||||
# Kantenarten. Bei -n braucht auch sie ein pos, sonst landet sie auf 0,0
|
# Kantenarten. Bei -n braucht auch sie ein pos, sonst landet sie auf 0,0
|
||||||
# mitten in der Anlage - daher unterhalb links vom Anlagenrand.
|
# mitten in der Anlage - daher unterhalb links vom Anlagenrand.
|
||||||
rows = "".join(
|
rows = "".join(
|
||||||
f'<tr><td bgcolor="{d.get_style().fill}" align="left"> {d.get_name()} </td>'
|
f'<tr><td bgcolor="{d.get_fill()}" color="{d.get_style().stroke}" align="left"> {d.get_name()} </td>'
|
||||||
f'<td align="left">{d.describe_symbol()}</td></tr>'
|
f'<td align="left">{d.describe_symbol()}</td></tr>'
|
||||||
for d in TRO_CATALOG
|
for d in TRO_CATALOG
|
||||||
)
|
)
|
||||||
@@ -1584,6 +1642,90 @@ def render_dot(analysis: Analysis, source: str, use_coords: bool = False) -> str
|
|||||||
return "\n".join(out) + "\n"
|
return "\n".join(out) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# connect.ini (TRO-Ebene) - --connect schreibt, --from-connect liest
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def render_connect_ini(analysis: Analysis, section: str, source: str) -> str:
|
||||||
|
"""
|
||||||
|
TRO-Topologie als connect.ini, angelehnt an doc/TRO_Katalog/connect.ini
|
||||||
|
(hmf-connect-v1). Zurueck eingelesen von tro_overrides.load_connect_ini
|
||||||
|
(--from-connect): geaenderte Typen und Verbindungen gehen dann in DOT/SVG.
|
||||||
|
|
||||||
|
Abweichung vom Referenzformat: Systemgrenzen stehen nicht als EXTERN,
|
||||||
|
sondern mit ihrer ID (SYSn), damit die Datei verlustfrei zurueckgelesen
|
||||||
|
werden kann. [<Anlage>.tros] ist eine reine Uebersicht (Vorgaenger/
|
||||||
|
Nachfolger je TRO) und wird beim Einlesen ignoriert.
|
||||||
|
"""
|
||||||
|
out: list[str] = []
|
||||||
|
add = out.append
|
||||||
|
add("; ============================================================")
|
||||||
|
add("; connect.ini - Routing / Zusammenhalt der TROs (TRO-Ebene)")
|
||||||
|
add(f"; Automatisch erzeugt von lib/tro_flow.py aus '{source}'.")
|
||||||
|
add("; Wird bei jedem Lauf mit --connect neu geschrieben. Eine von Hand")
|
||||||
|
add("; geaenderte Fassung wird mit --from-connect gezeichnet; dauerhafte")
|
||||||
|
add("; Korrekturen gehoeren nach cfg/tro_overrides.ini.")
|
||||||
|
add(";")
|
||||||
|
add("; [<Anlage>.nodes] TRO = <Typ> | <Bezeichnung> | <Bauteile>")
|
||||||
|
add("; SYSn = Materialeingang/-ausgang (kein TRO)")
|
||||||
|
add("; [<Anlage>.connections] cNNN = <von> -> <nach> | <JamArea> | <kind>")
|
||||||
|
add("; kind: normal | transfer | bahn | weiche | umlauf | unbestimmt | gesetzt")
|
||||||
|
add("; [<Anlage>.externals] eNNN = SYSn -> <TRO> | <JamArea> | <kind> (Zulauf)")
|
||||||
|
add("; eNNN = <TRO> -> SYSn | <JamArea> | <kind> (Ablauf)")
|
||||||
|
add("; [<Anlage>.tros] TRO = <Typ> | prev: ... | next: ...")
|
||||||
|
add("; nur Uebersicht - beim Einlesen ignoriert")
|
||||||
|
add("; ============================================================")
|
||||||
|
add("")
|
||||||
|
|
||||||
|
add(f"[{section}.nodes]")
|
||||||
|
for tro in analysis.tros:
|
||||||
|
teile = []
|
||||||
|
if tro.separators:
|
||||||
|
teile.append(f"Sep {', '.join(tro.separators)}")
|
||||||
|
if tro.scanners:
|
||||||
|
teile.append(f"Scan {', '.join(tro.scanners)}")
|
||||||
|
add(f"{tro.tro_id} = {tro.type_name} | {tro.label}"
|
||||||
|
+ (f" | {'; '.join(teile)}" if teile else ""))
|
||||||
|
for boundary in analysis.boundaries:
|
||||||
|
add(f"{boundary.tro_id} = {boundary.type_name} | {boundary.label}")
|
||||||
|
add("")
|
||||||
|
|
||||||
|
boundary_ids = {b.tro_id for b in analysis.boundaries}
|
||||||
|
intern = [e for e in analysis.edges
|
||||||
|
if e[0] not in boundary_ids and e[1] not in boundary_ids]
|
||||||
|
extern = [e for e in analysis.edges
|
||||||
|
if e[0] in boundary_ids or e[1] in boundary_ids]
|
||||||
|
|
||||||
|
add(f"[{section}.connections]")
|
||||||
|
for i, (src, dst) in enumerate(intern, start=1):
|
||||||
|
kind = analysis.edge_kinds.get((src, dst)) or "normal"
|
||||||
|
add(f"c{i:03d} = {src} -> {dst} | | {kind}")
|
||||||
|
add("")
|
||||||
|
|
||||||
|
add(f"[{section}.externals]")
|
||||||
|
if not extern and boundary_ids:
|
||||||
|
add(f"; Systemgrenzen ohne Verbindung: {', '.join(sorted(boundary_ids))}")
|
||||||
|
elif not extern:
|
||||||
|
add("; keine Be-/Entladestation im Layout - Materialein-/ausgang offen")
|
||||||
|
for i, (src, dst) in enumerate(extern, start=1):
|
||||||
|
kind = analysis.edge_kinds.get((src, dst)) or "normal"
|
||||||
|
add(f"e{i:03d} = {src} -> {dst} | | {kind}")
|
||||||
|
add("")
|
||||||
|
|
||||||
|
add(f"[{section}.tros]")
|
||||||
|
for tro in analysis.tros:
|
||||||
|
prev = ", ".join(tro.predecessors) or "-"
|
||||||
|
nxt = ", ".join(tro.successors) or "-"
|
||||||
|
line = f"{tro.tro_id} = {tro.type_name} | prev: {prev} | next: {nxt}"
|
||||||
|
if tro.colocated:
|
||||||
|
line += f" | gleiche Bahn: {', '.join(tro.colocated)}"
|
||||||
|
add(line)
|
||||||
|
add("")
|
||||||
|
|
||||||
|
return "\n".join(out) + "\n"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Fehlerdatei
|
# Fehlerdatei
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -1669,9 +1811,9 @@ def _tro_mermaid(analysis: Analysis) -> list[str]:
|
|||||||
|
|
||||||
for tro in analysis.tros:
|
for tro in analysis.tros:
|
||||||
definition = tro.definition()
|
definition = tro.definition()
|
||||||
group = definition.get_group() if definition else "ext"
|
css_class = definition.mermaid_class() if definition else "ext"
|
||||||
label = f"{tro.tro_id}<br/>{tro.type_name}<br/>{_mermaid_text(tro.label)}"
|
label = f"{tro.tro_id}<br/>{tro.type_name}<br/>{_mermaid_text(tro.label)}"
|
||||||
out.append(f' {tro.tro_id}["{label}"]:::{group}')
|
out.append(f' {tro.tro_id}["{label}"]:::{css_class}')
|
||||||
|
|
||||||
for boundary in analysis.boundaries:
|
for boundary in analysis.boundaries:
|
||||||
label = f"{boundary.tro_id}<br/>{boundary.type_name}<br/>{_mermaid_text(boundary.label)}"
|
label = f"{boundary.tro_id}<br/>{boundary.type_name}<br/>{_mermaid_text(boundary.label)}"
|
||||||
@@ -1921,12 +2063,12 @@ def render_doc(
|
|||||||
add(f"Typen, FB-Bausteine, Bauteile und Farben stammen vollstaendig aus "
|
add(f"Typen, FB-Bausteine, Bauteile und Farben stammen vollstaendig aus "
|
||||||
f"`TRO_CATALOG` in `lib/tro_catalog.py` (Stand {CATALOG_AS_OF}):")
|
f"`TRO_CATALOG` in `lib/tro_catalog.py` (Stand {CATALOG_AS_OF}):")
|
||||||
add("")
|
add("")
|
||||||
add("| # | Typ | FB-Baustein | Bauteile je TRO | Farbgruppe | Symbol |")
|
add("| # | Typ | FB-Baustein | Bauteile je TRO | Farbgruppe | Symbol | Fuellfarbe |")
|
||||||
add("|---:|---|---|---|---|---|")
|
add("|---:|---|---|---|---|---|---|")
|
||||||
for index, definition in enumerate(TRO_CATALOG, start=1):
|
for index, definition in enumerate(TRO_CATALOG, start=1):
|
||||||
add(f"| {index} | `{definition.get_name()}` | `{definition.get_fb_block()}` "
|
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()} |")
|
f"| {definition.describe_symbol()} | `{definition.get_fill()}` |")
|
||||||
add("")
|
add("")
|
||||||
add("| Bauteil | Sub-FB |")
|
add("| Bauteil | Sub-FB |")
|
||||||
add("|---|---|")
|
add("|---|---|")
|
||||||
@@ -2085,6 +2227,25 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
help="Dokumentation der TROs als Markdown in %%SKEL_RESULTS%% erzeugen "
|
help="Dokumentation der TROs als Markdown in %%SKEL_RESULTS%% erzeugen "
|
||||||
"(TRO-Liste, Verbindungen, Typdefinition).",
|
"(TRO-Liste, Verbindungen, Typdefinition).",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--connect",
|
||||||
|
action="store_true",
|
||||||
|
help="TRO-Topologie als <name>_tro_connect.ini nach %%SKEL_RESULTS%% "
|
||||||
|
"schreiben: Knoten (TRO = Typ), Verbindungen, Systemgrenzen und je "
|
||||||
|
"TRO Vorgaenger/Nachfolger.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--from-connect",
|
||||||
|
dest="from_connect",
|
||||||
|
nargs="?",
|
||||||
|
const="",
|
||||||
|
default=None,
|
||||||
|
metavar="INI",
|
||||||
|
help="Typen und Verbindungen aus einer (von Hand geaenderten) connect.ini "
|
||||||
|
"uebernehmen und damit DOT/SVG/Doku erzeugen. Ohne Wert: "
|
||||||
|
"<name>_tro_connect.ini in %%SKEL_RESULTS%%. Die connect.ini wird "
|
||||||
|
"dabei nicht ueberschrieben.",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--geometry",
|
"--geometry",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -2102,6 +2263,21 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
"Anschlusspunkte als derselbe Uebergabepunkt gelten. "
|
"Anschlusspunkte als derselbe Uebergabepunkt gelten. "
|
||||||
"Standard: %(default)s",
|
"Standard: %(default)s",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--use-overrides",
|
||||||
|
dest="use_overrides",
|
||||||
|
action="store_true",
|
||||||
|
help="Manuelle Korrekturen aus cfg/tro_overrides.ini anwenden. Standard "
|
||||||
|
"ist AUS: TROs, Typen und Verbindungen kommen rein aus dem Layout "
|
||||||
|
"(die INI-Abschnitte haengen an laufenden TRO-Nummern und veralten "
|
||||||
|
"mit jedem neuen Export).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-overrides",
|
||||||
|
dest="use_overrides",
|
||||||
|
action="store_false",
|
||||||
|
help=argparse.SUPPRESS,
|
||||||
|
)
|
||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
@@ -2165,7 +2341,28 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
if raw_json_file is not None:
|
if raw_json_file is not None:
|
||||||
sepliste_counts = load_sepliste_counts(raw_json_file, warnings)
|
sepliste_counts = load_sepliste_counts(raw_json_file, warnings)
|
||||||
|
|
||||||
|
if args.use_overrides:
|
||||||
overrides = load_overrides(csv_file.name, env_dir("SKEL_CFG", "cfg"))
|
overrides = load_overrides(csv_file.name, env_dir("SKEL_CFG", "cfg"))
|
||||||
|
else:
|
||||||
|
overrides = Overrides()
|
||||||
|
|
||||||
|
stem = csv_file.stem + ("_geo" if args.geometry else "")
|
||||||
|
connect_target = results / f"{stem}_tro_connect.ini"
|
||||||
|
connect_source: Path | None = None
|
||||||
|
if args.from_connect is not None:
|
||||||
|
connect_source = Path(args.from_connect) if args.from_connect else connect_target
|
||||||
|
if not connect_source.is_absolute() and connect_source.parent == Path("."):
|
||||||
|
connect_source = results / connect_source
|
||||||
|
if not connect_source.is_file():
|
||||||
|
print(f"FEHLER: connect.ini nicht gefunden: {connect_source}", file=sys.stderr)
|
||||||
|
print(" Erst mit --connect erzeugen.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
try:
|
||||||
|
overrides = load_connect_ini(connect_source, overrides)
|
||||||
|
except ValueError as exc:
|
||||||
|
print(f"FEHLER: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
analysis = analyse_tros(graph, elements, overrides,
|
analysis = analyse_tros(graph, elements, overrides,
|
||||||
resolve_host=resolve_host, count_exits=count_exits,
|
resolve_host=resolve_host, count_exits=count_exits,
|
||||||
method=method, sepliste_counts=sepliste_counts)
|
method=method, sepliste_counts=sepliste_counts)
|
||||||
@@ -2174,7 +2371,6 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
f"ableitbar.", file=sys.stderr)
|
f"ableitbar.", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
stem = csv_file.stem + ("_geo" if args.geometry else "")
|
|
||||||
dot_file = results / f"{stem}_tro_flow.dot"
|
dot_file = results / f"{stem}_tro_flow.dot"
|
||||||
svg_file = results / f"{stem}_tro_flow.svg"
|
svg_file = results / f"{stem}_tro_flow.svg"
|
||||||
doc_target = results / f"{stem}_tro_doc.md"
|
doc_target = results / f"{stem}_tro_doc.md"
|
||||||
@@ -2194,6 +2390,14 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
connect_file = None
|
||||||
|
if args.connect and connect_source is None:
|
||||||
|
connect_target.write_text(
|
||||||
|
render_connect_ini(analysis, section=stem, source=csv_file.name),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
connect_file = connect_target
|
||||||
|
|
||||||
has_errors = write_error_file(analysis, csv_file, error_target)
|
has_errors = write_error_file(analysis, csv_file, error_target)
|
||||||
error_file = error_target if has_errors else None
|
error_file = error_target if has_errors else None
|
||||||
|
|
||||||
@@ -2217,6 +2421,13 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
|
|
||||||
report(analysis, csv_file, dot_file, created_svg, error_file, doc_file,
|
report(analysis, csv_file, dot_file, created_svg, error_file, doc_file,
|
||||||
warnings, engine=engine)
|
warnings, engine=engine)
|
||||||
|
if connect_file:
|
||||||
|
print(f"connect.ini = {connect_file}")
|
||||||
|
if connect_source:
|
||||||
|
print(f"Gezeichnet aus = {connect_source}")
|
||||||
|
if args.connect and connect_source is not None:
|
||||||
|
print("HINWEIS: --connect zusammen mit --from-connect - die connect.ini "
|
||||||
|
"wird nicht ueberschrieben.")
|
||||||
|
|
||||||
if svg_error:
|
if svg_error:
|
||||||
print(f"FEHLER: SVG nicht erzeugt: {svg_error}", file=sys.stderr)
|
print(f"FEHLER: SVG nicht erzeugt: {svg_error}", file=sys.stderr)
|
||||||
|
|||||||
@@ -490,3 +490,104 @@ def apply_overrides(tros, overrides: Overrides) -> None:
|
|||||||
overrides.applied.append(
|
overrides.applied.append(
|
||||||
f"{tro.tro_id}: {vorher} -> {override.type_name} ({grund})"
|
f"{tro.tro_id}: {vorher} -> {override.type_name} ({grund})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# connect.ini (TRO-Ebene) zurueck einlesen - tro_flow.py --from-connect
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Vertrauensangabe fuer Typen, die aus einer (evtl. von Hand geaenderten)
|
||||||
|
# connect.ini stammen - in der Doku von Layout und Begehung unterscheidbar.
|
||||||
|
CONNECT_CONFIDENCE = "connect.ini"
|
||||||
|
|
||||||
|
|
||||||
|
def _connect_pair(raw: str) -> tuple[str, str, str] | None:
|
||||||
|
"""'<von> -> <nach> | <JamArea> | <kind>' -> (von, nach, kind); None wenn
|
||||||
|
kein Pfeil. Die JamArea wird (noch) nicht ausgewertet."""
|
||||||
|
pfeil, _, rest = str(raw).partition("|")
|
||||||
|
src, sep, dst = (t.strip() for t in pfeil.partition("->"))
|
||||||
|
if not sep or not src or not dst:
|
||||||
|
return None
|
||||||
|
felder = [f.strip() for f in rest.split("|")]
|
||||||
|
kind = felder[1] if len(felder) > 1 else ""
|
||||||
|
return src, dst, kind
|
||||||
|
|
||||||
|
|
||||||
|
def load_connect_ini(path: Path, base: Overrides | None = None) -> Overrides:
|
||||||
|
"""
|
||||||
|
Eine von tro_flow.py --connect geschriebene connect.ini als Korrekturen lesen.
|
||||||
|
|
||||||
|
Die Datei wird dabei nicht als eigenes Modell behandelt, sondern auf die
|
||||||
|
vorhandenen Korrekturarten abgebildet - so laeuft sie durch dieselbe
|
||||||
|
Pipeline wie cfg/tro_overrides.ini:
|
||||||
|
|
||||||
|
[<Anlage>.nodes] TRO-ID = <Typ> | ... -> Typkorrektur je TRO-ID
|
||||||
|
[<Anlage>.connections] cNNN = von -> nach | JamArea | kind
|
||||||
|
[<Anlage>.externals] eNNN = SYSn -> TRO | ... bzw. TRO -> SYSn | ...
|
||||||
|
-> zusammen die gesetzte Topologie, die die
|
||||||
|
abgeleiteten Kanten vollstaendig ersetzt
|
||||||
|
|
||||||
|
[<Anlage>.tros] (Vorgaenger/Nachfolger je TRO) ist nur eine Uebersicht und
|
||||||
|
wird ignoriert - massgeblich sind die Verbindungen.
|
||||||
|
|
||||||
|
`base` (die Korrekturen aus cfg/tro_overrides.ini) bleibt erhalten; Typen
|
||||||
|
aus der connect.ini gewinnen, die Topologie der connect.ini ersetzt eine
|
||||||
|
dort gesetzte.
|
||||||
|
"""
|
||||||
|
result = base if base is not None else Overrides()
|
||||||
|
parser = configparser.ConfigParser(inline_comment_prefixes=(";",),
|
||||||
|
interpolation=None)
|
||||||
|
parser.optionxform = str # TRO-IDs nicht in Kleinschreibung wandeln
|
||||||
|
# utf-8-sig: von Hand gespeicherte Dateien (Windows-Editoren, PowerShell)
|
||||||
|
# tragen oft ein BOM, an dem configparser sonst in Zeile 1 scheitert.
|
||||||
|
try:
|
||||||
|
parser.read(path, encoding="utf-8-sig")
|
||||||
|
except configparser.Error as exc:
|
||||||
|
raise ValueError(f"{path.name} nicht lesbar: {exc}") from exc
|
||||||
|
|
||||||
|
connections: list[ConnectionSpec] = []
|
||||||
|
for section in parser.sections():
|
||||||
|
art = section.rsplit(".", 1)[-1].lower()
|
||||||
|
values = parser[section]
|
||||||
|
if art == "nodes":
|
||||||
|
for tro_id, raw in values.items():
|
||||||
|
type_name = str(raw).partition("|")[0].strip()
|
||||||
|
# Systemgrenzen (SYSn) sind kein TRO, ihr "Typ" ist nur Anzeige
|
||||||
|
if tro_id.upper().startswith("SYS") or not type_name:
|
||||||
|
continue
|
||||||
|
if get_tro(type_name) is None:
|
||||||
|
result.warnings.append(
|
||||||
|
f"{path.name} [{section}] {tro_id}: Typ '{type_name}' steht "
|
||||||
|
f"nicht im TRO-Katalog - Typ bleibt wie abgeleitet"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
result.tros[tro_id.upper()] = TroOverride(
|
||||||
|
key=tro_id.upper(), type_name=type_name,
|
||||||
|
reason=f"aus {path.name}", confidence=CONNECT_CONFIDENCE,
|
||||||
|
)
|
||||||
|
elif art in ("connections", "externals"):
|
||||||
|
for key, raw in values.items():
|
||||||
|
pair = _connect_pair(raw)
|
||||||
|
if pair is None:
|
||||||
|
result.warnings.append(
|
||||||
|
f"{path.name} [{section}] {key}: '{raw}' ist keine "
|
||||||
|
f"Verbindung '<von> -> <nach>'"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
src, dst, kind = pair
|
||||||
|
if "EXTERN" in (src.upper(), dst.upper()):
|
||||||
|
result.warnings.append(
|
||||||
|
f"{path.name} [{section}] {key}: EXTERN wird nicht "
|
||||||
|
f"ausgewertet - Systemgrenze als SYSn angeben"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
connections.append(ConnectionSpec(src=src, dst=dst, kind=kind))
|
||||||
|
|
||||||
|
if connections:
|
||||||
|
result.connections = connections
|
||||||
|
else:
|
||||||
|
result.warnings.append(
|
||||||
|
f"{path.name}: keine Verbindungen gefunden - Topologie bleibt wie abgeleitet"
|
||||||
|
)
|
||||||
|
result.applied.append(f"connect.ini eingelesen: {path}")
|
||||||
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user