From 91773ac6156e356992d795b90864f888b3688b03 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Wed, 13 May 2026 23:25:14 +0200 Subject: [PATCH] Routine um Attribute in jedem Omniflo .dxf File zu setzen. --- cfg/attradd.cfg | 37 +++++++ lib/attradd.py | 259 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 cfg/attradd.cfg create mode 100644 lib/attradd.py diff --git a/cfg/attradd.cfg b/cfg/attradd.cfg new file mode 100644 index 0000000..ca7c710 --- /dev/null +++ b/cfg/attradd.cfg @@ -0,0 +1,37 @@ +# ============================================================ +# attradd.cfg - Attribut-Konfiguration fuer Omniflo DXF-Elemente +# +# Format: +# [ABSCHNITT] Elementtyp (bogen / weiche) +# TAG = QUELLE QUELLE ist entweder: +# - ein JSON-Feldname (wird aus omniflo_*.json gelesen) +# - ein fester Wert in Anfuehrungszeichen: "fester Text" +# - ein Platzhalter: {layer}, {sivasnr} +# +# Verfuegbare Platzhalter: +# {sivasnr} SivasNr des Elements (= DXF-Dateiname) +# {layer} Ziel-Layer (aus dieser Config) +# {profiltyp} ProfilTyp aus JSON +# +# Hinweis: +# Zeilen mit # sind Kommentare. Leerzeilen werden ignoriert. +# ============================================================ + +[bogen] +BESCHR = ProfilTyp +ARTINR = Sivasnr +TEILEART = "Omniflo Kurve" +LAYER = "A-OF-Bogen" +RADIUS = Radius +WINKEL = KurvenWinkel +SIVASNR_TEF = SivasnrTEF + +[weiche] +BESCHR = ProfilTyp +ARTINR = Sivasnr +TEILEART = "Omniflo Weiche" +LAYER = "A-OF-Weiche" +WEICHENTYP = WeichenTyp +WINKEL = KurvenWinkel +RADIUS = Breite +SIVASNR_TEF = SivasnrTEF diff --git a/lib/attradd.py b/lib/attradd.py new file mode 100644 index 0000000..640ccb2 --- /dev/null +++ b/lib/attradd.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +attradd.py - Fuegt Attribute (ATTDEF) zu allen Omniflo DXF-Dateien hinzu. + +Liest die Attribut-Konfiguration aus cfg/attradd.cfg und die Element-Daten +aus data/json/omniflo_boegen.json und omniflo_weichen.json. +Die ergaenzten DXF-Dateien werden in results/omniflo/ gespeichert. + +Umgebungsvariablen: + DXFM_DATA - Pfad zum data/-Verzeichnis + DXFM_CFG - Pfad zum cfg/-Verzeichnis + DXFM_RESULTS - Pfad zum results/-Verzeichnis + +Aufruf: + python lib/attradd.py [--number SIVASNR] [--dry-run] +""" + +import argparse +import configparser +import json +import os +import sys + +import ezdxf +from ezdxf.enums import TextEntityAlignment + + +# --------------------------------------------------------------------------- +# Konfiguration lesen +# --------------------------------------------------------------------------- + +def load_config(cfg_path): + """ + Liest attradd.cfg und gibt ein Dict zurueck: + { + "bogen": [("BESCHR", "ProfilTyp"), ("ARTINR", "Sivasnr"), ...], + "weiche": [("BESCHR", "ProfilTyp"), ...], + } + """ + config = configparser.ConfigParser() + config.optionxform = str # Case-sensitive Keys + config.read(cfg_path, encoding="utf-8") + + result = {} + for section in config.sections(): + attrs = [] + for tag, source in config.items(section): + attrs.append((tag, source.strip())) + result[section] = attrs + + return result + + +def resolve_value(source, json_entry, sivasnr): + """ + Loest einen Quell-Ausdruck aus der Config auf: + - "fester Text" -> fester Text + - {sivasnr} -> SivasNr-Wert + - Feldname -> Wert aus json_entry + """ + source = source.strip() + + # Fester Wert in Anfuehrungszeichen + if source.startswith('"') and source.endswith('"'): + return source[1:-1] + + # Platzhalter + if source == "{sivasnr}": + return str(sivasnr) + + # JSON-Feld + val = json_entry.get(source) + if val is None: + return "" + return str(val) + + +# --------------------------------------------------------------------------- +# JSON-Daten laden +# --------------------------------------------------------------------------- + +def load_json(path): + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + + +def build_lookup(boegen, weichen): + """Dict: SivasNr (String) -> (typ, eintrag)""" + lookup = {} + for b in boegen: + lookup[str(b["Sivasnr"])] = ("bogen", b) + for w in weichen: + lookup[str(w["Sivasnr"])] = ("weiche", w) + return lookup + + +# --------------------------------------------------------------------------- +# ATTDEF zu DXF hinzufuegen +# --------------------------------------------------------------------------- + +ATTDEF_START_Y = -50.0 +ATTDEF_SPACING = -30.0 +ATTDEF_HEIGHT = 5.0 +ATTDEF_LAYER = "ATTRIB" + + +def add_attdefs_to_dxf(dxf_path, output_path, attr_defs, json_entry, sivasnr): + """ + Oeffnet eine DXF-Datei, fuegt ATTDEF-Entities in den Modelspace ein + und speichert das Ergebnis. + + attr_defs: Liste von (TAG, source_expression) aus der Config + json_entry: Dict mit den JSON-Daten fuer dieses Element + """ + doc = ezdxf.readfile(dxf_path) + msp = doc.modelspace() + + # ATTRIB-Layer anlegen falls nicht vorhanden + if ATTDEF_LAYER not in doc.layers: + doc.layers.add(ATTDEF_LAYER, color=7) + + # Bestehende ATTDEFs entfernen (fuer Re-Runs) + existing = [e for e in msp if e.dxftype() == "ATTDEF"] + for e in existing: + msp.delete_entity(e) + + # ATTDEFs hinzufuegen + y_pos = ATTDEF_START_Y + for tag, source in attr_defs: + default_value = resolve_value(source, json_entry, sivasnr) + prompt = tag + + msp.add_attdef( + tag=tag, + text=default_value, + dxfattribs={ + "layer": ATTDEF_LAYER, + "height": ATTDEF_HEIGHT, + "insert": (0, y_pos), + "prompt": prompt, + "flags": 0, # sichtbar + }, + ) + y_pos += ATTDEF_SPACING + + doc.saveas(output_path) + return len(attr_defs) + + +# --------------------------------------------------------------------------- +# Hauptprogramm +# --------------------------------------------------------------------------- + +def process_all(data_dir, cfg_path, results_dir, single_nr=None, dry_run=False): + """Verarbeitet alle Omniflo DXF-Dateien.""" + + # Config laden + attr_config = load_config(cfg_path) + print(f"[attradd] Config geladen: {list(attr_config.keys())}") + for section, attrs in attr_config.items(): + tags = [a[0] for a in attrs] + print(f" [{section}] Attribute: {', '.join(tags)}") + + # JSON-Daten laden + boegen = load_json(os.path.join(data_dir, "json", "omniflo_boegen.json")) + weichen = load_json(os.path.join(data_dir, "json", "omniflo_weichen.json")) + lookup = build_lookup(boegen, weichen) + print(f"[attradd] {len(boegen)} Boegen, {len(weichen)} Weichen geladen.") + + # Ausgabeverzeichnis + out_dir = os.path.join(results_dir, "omniflo") + os.makedirs(out_dir, exist_ok=True) + + omniflo_dir = os.path.join(data_dir, "omniflo") + processed = 0 + skipped = 0 + errors = 0 + + # Alle bekannten SivasNr verarbeiten + for sivasnr, (typ, entry) in sorted(lookup.items()): + if single_nr and sivasnr != single_nr: + continue + + # Quelldatei finden + dxf_name = f"{sivasnr}.dxf" + dxf_path = os.path.join(omniflo_dir, dxf_name) + if not os.path.exists(dxf_path): + skipped += 1 + continue + + # Attribute fuer diesen Typ + if typ not in attr_config: + print(f" WARNUNG: Kein Config-Abschnitt fuer Typ '{typ}', " + f"ueberspringe {sivasnr}") + skipped += 1 + continue + + attr_defs = attr_config[typ] + output_path = os.path.join(out_dir, dxf_name) + + if dry_run: + values = {tag: resolve_value(src, entry, sivasnr) + for tag, src in attr_defs} + print(f" [DRY] {sivasnr} ({typ}): {values}") + processed += 1 + continue + + try: + count = add_attdefs_to_dxf(dxf_path, output_path, attr_defs, + entry, sivasnr) + processed += 1 + print(f" OK: {sivasnr} ({typ}) -> {count} Attribute -> {output_path}") + except Exception as e: + errors += 1 + print(f" FEHLER: {sivasnr}: {e}") + + print(f"\n[attradd] Fertig: {processed} verarbeitet, " + f"{skipped} uebersprungen, {errors} Fehler.") + if not dry_run: + print(f"[attradd] Ausgabe in: {out_dir}") + + +def main(): + parser = argparse.ArgumentParser( + description="Fuegt Attribute (ATTDEF) zu Omniflo DXF-Dateien hinzu.") + parser.add_argument("--number", "-n", type=str, default=None, + help="Nur diese SivasNr verarbeiten") + parser.add_argument("--dry-run", "-d", action="store_true", + help="Nur anzeigen, nicht schreiben") + args = parser.parse_args() + + # Umgebungsvariablen + data_dir = os.environ.get("DXFM_DATA") + cfg_dir = os.environ.get("DXFM_CFG") + results_dir = os.environ.get("DXFM_RESULTS") + + # Fallback: relative Pfade vom Projektverzeichnis + if not data_dir: + base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + data_dir = os.path.join(base, "data") + if not cfg_dir: + base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + cfg_dir = os.path.join(base, "cfg") + if not results_dir: + base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + results_dir = os.path.join(base, "results") + + cfg_path = os.path.join(cfg_dir, "attradd.cfg") + if not os.path.exists(cfg_path): + print(f"[attradd] FEHLER: Config nicht gefunden: {cfg_path}") + sys.exit(1) + + process_all(data_dir, cfg_path, results_dir, + single_nr=args.number, dry_run=args.dry_run) + + +if __name__ == "__main__": + main()