Weichenknoten zwischen Kreiseln, Track-Mouseover, Parameter in cfg/parameter.ini
TRO-Typen an Kreisel-Beruehrpunkten (lib/kreisel_junctions.py, derive_tros Schritt 3c): jeder Kreisel als Stadion-Form mit Laufrichtung; jeder gleichlaufende Beruehrpunkt ist eine Weiche (exportierte ILS Weiche oder abgeleitet W<a>_<b>), Beruehrpunkte derselben Umlenkung bilden einen Knoten. Ein Separator steuert ihn, wenn er in Laufrichtung unmittelbar davor liegt (keine andere Weiche dazwischen). Typ = Separatoren x Weichen. export.csv: 1Sep1Swi (9/1, 9/8, 8/7), 2Sep1Swi (7/2), 2Sep2Swi (2/3/4), 3Sep2Swi (4/5/6). Neuer Katalogtyp 3Sep2Swi (nicht aus SCL belegt). Gegenlaeufige Beruehrpunkte (Mubea, Dematic) bleiben bei der alten Regel; Mubea/HundM05/Dematic unveraendert, Showroom 0018+0019 jetzt 2Sep1Swi. material_flow --track: Mouseover je Knoten mit Zugehoerigkeit zu Kreisel/VF/GF (Zuordnung, TrackIds-Kette, Lage laut Geometrie), Weiche mit beiden Kreiseln, Be-/Entladung mit ihrem Kreisel; Widersprueche mit "!". Zahlenwerte (Toleranzen, Schwellen, Massstaebe, Groessen) aus allen lib-Modulen nach cfg/parameter.ini (lib/parameter.py, Abschnitt je Modul, Ueberschreiben je Anlage ueber %SKEL_CFG%/parameter.ini). Ausgaben aller Anlagen bytegleich. tro_flow.py: Docstrings mit Parametern/Rueckgabe, fehlende Typangaben. Geprueft: 90 Tests gruen; tro_flow/material_flow/scl_skeleton auf 5 CSVs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0c69faf3db
commit
550bdeb464
@@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
parameter.py - Zahlenwerte der Bibliotheken aus cfg/parameter.ini.
|
||||
|
||||
Toleranzen, Schwellen, Massstaebe und Zeichnungsgroessen stehen nicht im Code,
|
||||
sondern in einer INI-Datei: ein Abschnitt je Modul, der Schluessel ist der
|
||||
Name der Konstanten in Kleinbuchstaben ([kreisel_junctions]
|
||||
contact_tolerance -> kreisel_junctions.CONTACT_TOLERANCE).
|
||||
|
||||
Gelesen wird zuerst die Grunddatei des Repositorys (<Projekt>/cfg/
|
||||
parameter.ini, versioniert, muss vollstaendig sein). Liegt in %SKEL_CFG% eine
|
||||
weitere parameter.ini, ueberschreibt sie einzelne Werte - so kann eine Anlage
|
||||
abweichende Toleranzen bekommen, ohne die Grunddatei zu aendern.
|
||||
|
||||
Fehlt die Grunddatei oder ein Schluessel, bricht der Import mit
|
||||
ParameterError ab: einen stillen Ersatzwert im Code gibt es bewusst nicht,
|
||||
sonst stuende die Zahl doch wieder im Code.
|
||||
|
||||
Unterste Schicht: importiert nichts aus lib/, damit jedes Modul es nutzen kann.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
PARAMETER_FILE = "parameter.ini"
|
||||
|
||||
__all__ = ["PARAMETER_FILE", "ParameterError", "files", "get_float", "get_int"]
|
||||
|
||||
|
||||
class ParameterError(RuntimeError):
|
||||
"""Grunddatei fehlt oder ein Wert fehlt bzw. ist keine Zahl."""
|
||||
|
||||
|
||||
def _base_file() -> Path:
|
||||
"""Grunddatei im Repository: <Projekt>/cfg/parameter.ini."""
|
||||
return Path(__file__).resolve().parent.parent / "cfg" / PARAMETER_FILE
|
||||
|
||||
|
||||
def files() -> list[Path]:
|
||||
"""Gelesene Dateien in Lesereihenfolge (Grunddatei, ggf. %SKEL_CFG%)."""
|
||||
result = [_base_file()]
|
||||
cfg = os.environ.get("SKEL_CFG")
|
||||
if cfg:
|
||||
override = Path(cfg) / PARAMETER_FILE
|
||||
if override.is_file() and override.resolve() != result[0].resolve():
|
||||
result.append(override)
|
||||
return result
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _parser() -> configparser.ConfigParser:
|
||||
"""Alle Parameterdateien einlesen (einmal je Prozess)."""
|
||||
base = _base_file()
|
||||
if not base.is_file():
|
||||
raise ParameterError(f"Parameterdatei fehlt: {base}")
|
||||
parser = configparser.ConfigParser(inline_comment_prefixes=(";", "#"))
|
||||
for path in files():
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
parser.read_file(handle, source=str(path))
|
||||
return parser
|
||||
|
||||
|
||||
def _raw(section: str, key: str) -> str:
|
||||
"""Rohwert; ParameterError, wenn Abschnitt oder Schluessel fehlt."""
|
||||
parser = _parser()
|
||||
if not parser.has_option(section, key):
|
||||
raise ParameterError(
|
||||
f"[{section}] {key} fehlt in {', '.join(str(p) for p in files())}")
|
||||
return parser.get(section, key)
|
||||
|
||||
|
||||
def get_float(section: str, key: str) -> float:
|
||||
"""Zahl aus [section] key.
|
||||
|
||||
Parameter:
|
||||
section: Abschnitt, in der Regel der Modulname (z. B. "tro_flow").
|
||||
key: Schluessel, der Konstantenname in Kleinbuchstaben.
|
||||
|
||||
Rueckgabe:
|
||||
Der Wert als float.
|
||||
"""
|
||||
raw = _raw(section, key)
|
||||
try:
|
||||
return float(raw)
|
||||
except ValueError:
|
||||
raise ParameterError(f"[{section}] {key} = {raw!r} ist keine Zahl") from None
|
||||
|
||||
|
||||
def get_int(section: str, key: str) -> int:
|
||||
"""Ganze Zahl aus [section] key (Anzahlen, Schrittweiten)."""
|
||||
raw = _raw(section, key)
|
||||
try:
|
||||
return int(raw)
|
||||
except ValueError:
|
||||
raise ParameterError(f"[{section}] {key} = {raw!r} ist keine ganze Zahl") from None
|
||||
Reference in New Issue
Block a user