#!/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()