Routine um Attribute in jedem Omniflo .dxf File zu setzen.
This commit is contained in:
@@ -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
|
||||
+259
@@ -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()
|
||||
Reference in New Issue
Block a user