Files
dxfmakros/lib/attradd.py
T

329 lines
10 KiB
Python

#!/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"), ...],
"layer": {"bogen": "OF Boegen A-2", "weiche_90": "Weiche 90 Grad A-2", ...},
}
"""
config = configparser.ConfigParser()
config.optionxform = str # Case-sensitive Keys
config.read(cfg_path, encoding="utf-8")
result = {}
for section in config.sections():
if section == "layer":
# Layer-Sektion als einfaches Dict speichern
layer_map = {}
for key, val in config.items(section):
val = val.strip()
if val.startswith('"') and val.endswith('"'):
val = val[1:-1]
layer_map[key] = val
result["layer"] = layer_map
else:
attrs = []
for tag, source in config.items(section):
attrs.append((tag, source.strip()))
result[section] = attrs
return result
KOMBI_TYPEN = {"Deltaweiche", "Sternweiche", "Dreifachweiche"}
def get_layer_for_entry(typ, json_entry, layer_map):
"""
Bestimmt den Layer-Namen anhand von Elementtyp und JSON-Daten.
Gibt den Layer-String zurueck oder "" wenn kein Mapping vorhanden.
"""
if not layer_map:
return ""
if typ == "bogen":
return layer_map.get("bogen", "")
# Weiche: Kategorie anhand KurvenWinkel und WeichenTyp bestimmen
winkel = json_entry.get("KurvenWinkel", 0)
weichentyp = json_entry.get("WeichenTyp", "")
# Weichenkombinationen (Deltaweiche, Sternweiche, Dreifachweiche)
if weichentyp in KOMBI_TYPEN:
return layer_map.get("weichenkombination", "")
# Weichenkoerper (KurvenWinkel = 22.5)
if winkel == 22.5:
return layer_map.get("weichenkoerper", "")
# Weiche 90 Grad
if winkel == 90:
return layer_map.get("weiche_90", "")
# Weiche 45 Grad
if winkel == 45:
return layer_map.get("weiche_45", "")
# Weichenbatterien / Parallel (KurvenWinkel = 0)
if winkel == 0:
return layer_map.get("weichenbatterie", "")
return ""
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,
layer_value=""):
"""
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
layer_value: Layer-Name aus [layer]-Sektion (wird als LAYER-ATTDEF gesetzt)
"""
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)
# Alle ATTDEFs sammeln: Config-Attribute + LAYER aus [layer]-Sektion
all_defs = list(attr_defs)
if layer_value:
all_defs.append(("LAYER", f'"{layer_value}"'))
# ATTDEFs hinzufuegen
y_pos = ATTDEF_START_Y
count = 0
for tag, source in all_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
count += 1
doc.saveas(output_path)
return count
# ---------------------------------------------------------------------------
# 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)
layer_map = attr_config.pop("layer", {})
print(f"[attradd] Config geladen: {list(attr_config.keys())}")
if layer_map:
print(f" [layer] {len(layer_map)} Zuordnungen: "
f"{', '.join(f'{k}={v}' for k, v in layer_map.items())}")
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]
layer_value = get_layer_for_entry(typ, entry, layer_map)
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}
if layer_value:
values["LAYER"] = layer_value
print(f" [DRY] {sivasnr} ({typ}): {values}")
processed += 1
continue
try:
count = add_attdefs_to_dxf(dxf_path, output_path, attr_defs,
entry, sivasnr,
layer_value=layer_value)
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()