E-Planung -> Layout: e_planning.py, scl_skeleton --eplan, Kapitel Daten

- doc/Klassenhierarchie.md: Kapitel "Daten" - alle Daten der SCL-Referenzen
  nach Quelle (Layout, E-Planung, Namenskonvention, Parameter)
- doc/E-Planung_Bruecke.md: Bruecke ueber *_positionsdraw.json des
  Kabellaengen-Tools, Befunde an Mubea
- lib/e_planning.py + cfg/e_planning.ini + bin/e_planning.bat/.sh:
  Lagebezug E-Zeichnung -> CSV, Zuordnung BMK -> Layout-Bauteil nach Regeln
  (Abstand, Sollabstand, Nummernregel MB = BG + 1), <csv>_eplan.json + Bericht
- scl_skeleton --eplan: Separator-Sensor und Stopperventil eingesetzt,
  ohne Schalter unveraenderte Ausgabe (Golden-Files gleich)
- tests/test_e_planning.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Michael Stangl
2026-10-11 09:30:56 +02:00
co-authored by Claude Opus 5.5
parent 5764df1ac8
commit c736288476
11 changed files with 1683 additions and 26 deletions
+56 -4
View File
@@ -51,6 +51,7 @@ from jinja2 import (
)
from pydantic import ValidationError
from e_planning import EPLAN_SCHEMA, EplanDocument
from material_flow import env_dir
from model import SCHEMA, TroDocument
from scl_context import TODO, SclFile, SclName, SclTro, build_context
@@ -329,6 +330,12 @@ def render_context(context: SclFile, templates: SclTemplates | None = None) -> s
add("//")
add("// Reihenfolge der REGIONs = Materialfluss.")
add(f"// Anfang bei {context.tros[0].id}: {context.start_reason}.")
if context.eplan_source:
add("//")
add(f"// E-Planung: {context.eplan_source}")
add("// Separator-Sensoren und Stopperventile aus der Zuordnung sind")
add("// eingesetzt (ohne Marker); was die Zuordnung nicht sicher fand,")
add(f"// bleibt '{TODO}'. Details: <csv>_eplan.md (lib/e_planning.py).")
if templates is not None:
mit, ohne = template_usage(context, templates)
add("//")
@@ -372,9 +379,7 @@ def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
results = env_dir("SKEL_RESULTS", "results")
json_file = Path(args.json)
if not json_file.is_absolute() and json_file.parent == Path("."):
json_file = results / args.json
json_file = _in_results(args.json, results)
if not json_file.is_file():
print(f"FEHLER: JSON nicht gefunden: {json_file}", file=sys.stderr)
print(" Zuerst bin/tro_flow.bat --file <name>.csv --json laufen lassen.",
@@ -412,7 +417,28 @@ def main(argv: list[str] | None = None) -> int:
return 1
templates = SclTemplates(ordner)
context = build_context(document, json_file.name, args.start)
eplan = None
if args.eplan:
eplan_file = _in_results(args.eplan, results)
try:
eplan = EplanDocument.from_json(eplan_file.read_text(encoding="utf-8"))
except OSError as exc:
print(f"FEHLER: E-Planungs-Zuordnung nicht lesbar: {exc}", file=sys.stderr)
print(" Zuerst bin/e_planning.bat --file <name>.csv --eplan "
"<zeichnung>_positionsdraw.json laufen lassen.", file=sys.stderr)
return 1
except ValidationError as exc:
print(f"FEHLER: {eplan_file} passt nicht zum Schema '{EPLAN_SCHEMA}' "
f"({exc.error_count()} Abweichungen).", file=sys.stderr)
return 1
# Eine Zuordnung zu einem anderen Layout setzt falsche Namen ein -
# Separatornummern sind nur innerhalb eines CSV-Exports eindeutig.
if Path(eplan.csv_file).name != Path(document.source).name:
print(f"FEHLER: {eplan_file.name} gehoert zu '{eplan.csv_file}', die "
f"TRO-JSON zu '{document.source}'.", file=sys.stderr)
return 1
context = build_context(document, json_file.name, args.start, eplan=eplan)
mit, ohne = template_usage(context, templates)
if args.strict and ohne:
print(f"FEHLER: kein Template fuer {', '.join(ohne)} (--strict) - "
@@ -440,6 +466,9 @@ def main(argv: list[str] | None = None) -> int:
if templates is not None:
print(f"Templates = {len(mit)} von {len(mit) + len(ohne)} Typen "
f"(generisch: {', '.join(ohne) or '-'})")
if eplan is not None:
print(f"E-Planung = {eplan_file}")
print(f"Eingesetzt = {_eingesetzt(context)} Namen aus der E-Planung")
print(f"Offene Stellen = {offen} (Marker '{TODO}')")
print(f"SCL = {out_file}")
print("================================================================")
@@ -447,6 +476,21 @@ def main(argv: list[str] | None = None) -> int:
return 0
def _in_results(name: str, results: Path) -> Path:
"""Blosser Dateiname -> in SKEL_RESULTS; Pfadangaben bleiben unveraendert."""
path = Path(name)
if not path.is_absolute() and path.parent == Path("."):
return results / name
return path
def _eingesetzt(context: SclFile) -> int:
"""Wie viele Separator-Namen aus der E-Planung kamen."""
return sum(not name.todo
for tro in context.tros for s in tro.separators
for name in (s.sensor, s.stopper))
def _schema_of(path: Path) -> str:
"""Schema-Kennung einer JSON-Datei, nur fuer die Fehlermeldung ("" wenn unlesbar)."""
try:
@@ -465,6 +509,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
" scl_skeleton.bat\n"
" scl_skeleton.bat --json export_tro.json --out FB_Main.scl\n"
" scl_skeleton.bat --json mubea_tro.json --templates\n"
" scl_skeleton.bat --json mubea_tro.json --eplan mubea_eplan.json\n"
"\n"
"Exit-Codes:\n"
" 0 ok (auch wenn TROs ohne Template generisch erzeugt wurden)\n"
@@ -500,6 +545,13 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"Ohne DIR: lib/scl_templates. FB-Typen ohne Template bleiben "
"generisch. Ohne diesen Schalter: bisherige Ausgabe.",
)
parser.add_argument(
"--eplan",
metavar="NAME",
help="Zuordnung zur E-Planung aus e_planning.py (<csv>_eplan.json in "
"%%SKEL_RESULTS%% oder ein Pfad): Separator-Sensoren und "
"Stopperventile werden eingesetzt statt als TODO markiert.",
)
parser.add_argument(
"--strict",
action="store_true",