@@ -0,0 +1,180 @@
"""
placeblocks.py
Erzeugt DXF‑ Elemente aus einer Rule‑ Designer‑ CSV.
Einfache Formen (z. B. " ILS 2.0 Kreisel " ) werden direkt konstruiert,
komplexe per Blockreferenz aus einer DXF‑ Bibliothek eingefügt.
"""
import os
import sys
import csv
import json
import re
import argparse
import configparser
import ezdxf
from pathlib import Path
# --------------------------------------------------------- Konstante Parameter
ATTR_TAG = " TeileId " # Attributtag im Block
RADIUS = 1000 # Radius der Kreiselkreise (mm)
# --------------------------------------------------------- Hilfsfunktionen
def check_environment_var ( env_str : str ) - > Path :
""" Liefert Path aus Umgebungs variable oder beendet mit Fehlermeldung. """
out_path = os . environ . get ( env_str )
if out_path :
return Path ( out_path )
print ( f " Umgebungsvariable { env_str } ist nicht gesetzt oder leer. " )
sys . exit ( 1 )
def extract_coords ( planquadrat : str ) - > tuple [ float , float ] :
""" Extrahiert X/Y‑ Koordinaten aus Planquadrat‑ String. """
m = re . search ( r " X:( \ d+[ \ .,]? \ d*) \ s+Y:( \ d+[ \ .,]? \ d*) " , planquadrat )
if not m :
raise ValueError ( f " Koordinaten nicht gefunden in: ' { planquadrat } ' " )
x , y = m . groups ( )
return float ( x . replace ( " , " , " . " ) ) , float ( y . replace ( " , " , " . " ) )
def parse_merkmale ( merkmale_str : str ) - > dict :
""" Parst Merkmale‑ JSON‑ String in dict; bei Fehler → leeres Dict. """
try :
return json . loads ( merkmale_str )
except json . JSONDecodeError :
return { }
def import_block ( block_name : str , from_doc , to_doc ) - > None :
""" Importiert Blockdefinition block_name von from_doc nach to_doc. """
if block_name in to_doc . blocks :
return
if block_name not in from_doc . blocks :
raise ValueError ( f " Block ' { block_name } ' nicht in Bibliothek gefunden. " )
src = from_doc . blocks [ block_name ]
tgt = to_doc . blocks . new ( name = block_name , dxfattribs = src . dxf . attribs ( ) )
for ent in src :
tgt . add_entity ( ent . copy ( ) )
def build_simple_shape ( msp , teileart : str , x : float , y : float ,
teile_id : str , merkmale : dict ) :
""" Erzeugt einfache Geometrien direkt im Modelspace. """
if teileart == " ILS 2.0 Kreisel " :
abstand_m = merkmale . get (
" Abstand (Kreiselachse A - Kreiselachse) in Meter " , " 20 "
) . replace ( " , " , " . " )
try :
abstand = float ( abstand_m ) * 1000 # Meter → mm
except ValueError :
abstand = 20000 # Fallback 20 m
cx1 = x - abstand / 2
cx2 = x + abstand / 2
# Zwei Kreise + tangentiale Verbindungs linien
msp . add_circle ( ( cx1 , y ) , RADIUS )
msp . add_circle ( ( cx2 , y ) , RADIUS )
msp . add_line ( ( cx1 , y - RADIUS ) , ( cx2 , y - RADIUS ) )
msp . add_line ( ( cx1 , y + RADIUS ) , ( cx2 , y + RADIUS ) )
else :
raise NotImplementedError ( f " Einfache Form ' { teileart } ' nicht implementiert. " )
def load_simple_types ( cfg_path : Path ) - > set [ str ] :
""" Lädt die Liste der einfachen TeileArt‑ Namen aus .cfg. """
cfg = configparser . ConfigParser ( )
cfg . read ( cfg_path )
names = cfg . get ( " simple_types " , " shape_names " , fallback = " " )
return { n . strip ( ) for n in names . split ( " , " ) if n . strip ( ) }
# --------------------------------------------------------- Hauptfunktion
def main ( csv_path : Path , lib_path : Path , cfg_path : Path ,
output_path : Path , verbose = False ) :
simple_types = load_simple_types ( cfg_path )
# Bibliothek nur laden, wenn Datei existiert
lib_doc = None
if lib_path . exists ( ) :
try :
lib_doc = ezdxf . readfile ( lib_path )
if verbose :
print ( f " [INFO] Bibliothek geladen: { lib_path } " )
except Exception as e :
sys . exit ( f " Fehler beim Lesen der Bibliothek ' { lib_path } ' : { e } " )
else :
print ( f " [INFO] Keine Bibliothek gefunden unter { lib_path } . "
" Komplexe Formen werden übersprungen. " )
# Neue Ziel zeichnung (DXF R2018)
doc = ezdxf . new ( dxfversion = " R2018 " )
msp = doc . modelspace ( )
# CSV einlesen
with csv_path . open ( newline = " " , encoding = " utf-8 " ) as fh :
reader = csv . DictReader ( fh , delimiter = ' ; ' )
for row in reader :
teileart = row [ " TeileArt " ] . strip ( )
teileid = row [ " TeileId " ] . strip ( )
planquadrat = row [ " Planquadrat " ]
merkmale = parse_merkmale ( row . get ( " Merkmale " , " " ) )
try :
x , y = extract_coords ( planquadrat )
except Exception as e :
print ( f " [WARN] { teileid } : { e } " )
continue
# Einfache Form
if teileart in simple_types :
try :
build_simple_shape ( msp , teileart , x , y , teileid , merkmale )
if verbose :
print ( f " [INFO] Simple ' { teileart } ' → { teileid } "
f " ( { x : .1f } , { y : .1f } ) " )
except Exception as e :
print ( f " [ERROR] Simple ' { teileart } ' ( { teileid } ): { e } " )
continue
# Komplexe Form (Block)
if not lib_doc :
print ( f " [WARN] ' { teileart } ' benötigt Bibliothek, wird übersprungen. " )
continue
try :
import_block ( teileart , lib_doc , doc )
bref = msp . add_blockref ( teileart , ( x , y ) )
bref . add_auto_attribs ( { ATTR_TAG : teileid } )
if verbose :
print ( f " [INFO] Block ' { teileart } ' → { teileid } "
f " ( { x : .1f } , { y : .1f } ) " )
except Exception as e :
print ( f " [ERROR] Block ' { teileart } ' ( { teileid } ): { e } " )
# DXF speichern
doc . saveas ( output_path )
print ( f " [DONE] DXF gespeichert unter: { output_path } " )
if __name__ == " __main__ " :
parser = argparse . ArgumentParser (
description = " Plaziert Anlagenkomponenten aus Rule‑ Designer CSV. " )
parser . add_argument ( " -f " , " --file " , required = True , help = " CSV‑ Datei (Name oder Pfad) " , metavar = " input.csv " )
parser . add_argument ( " -c " , " --config " , help = " CFG mit einfachen Formen " , metavar = " shapes.cfg " )
parser . add_argument ( " -l " , " --lib " , help = " DXF‑ Bibliothek mit Blöcken " , metavar = " bibliothek.dxf " )
parser . add_argument ( " -o " , " --output " , help = " Ziel‑ DXF (Standard: PROJECT_WORK/anlage.dxf) " , metavar = " anlage.dxf " )
parser . add_argument ( " -v " , " --verbose " , action = " store_true " , help = " mehr Ausgaben anzeigen " )
args = parser . parse_args ( )
# Verzeichnisse aus Umgebungs variablen
data_dir = check_environment_var ( " PROJECT_DATA " )
work_dir = check_environment_var ( " PROJECT_WORK " )
config_dir = check_environment_var ( " PROJECT_CFG " )
# CSV‑ Pfad: nur Dateiname → im WORK‑ Ordner suchen
if os . sep not in args . file and " / " not in args . file :
csv_path = work_dir / args . file
else :
csv_path = Path ( args . file )
cfg_path = Path ( args . config ) if args . config else config_dir / " shapes.cfg "
lib_path = Path ( args . lib ) if args . lib else data_dir / " bibliothek.dxf "
output_path = Path ( args . output ) if args . output else work_dir / " anlage.dxf "
main ( csv_path , lib_path , cfg_path , output_path , verbose = args . verbose )