c38b9861d4
- logging in dxf2lib.py implementiert - Umgebungsvariablen in setenv.bat für log-Verzeichnis und Bibliothek-Pfad hinzugefügt - Bibliothek-Pfad in plant2dxf.py angepasst
343 lines
13 KiB
Python
343 lines
13 KiB
Python
import os
|
|
import ezdxf
|
|
from ezdxf.math import Vec2
|
|
import argparse
|
|
import sys
|
|
import shutil
|
|
import datetime
|
|
import configparser
|
|
import logging
|
|
|
|
|
|
def check_environment_var(env_str: str, fallback: str) -> str:
|
|
"""
|
|
Prüft eine Umgebungsvariable und gibt den Wert oder einen Fallback zurück.
|
|
|
|
Args:
|
|
env_str (str): Name der zu prüfenden Umgebungsvariable
|
|
fallback (str): Fallback-Wert, falls die Variable nicht gesetzt ist
|
|
|
|
Returns:
|
|
str: Wert der Umgebungsvariable oder Fallback-Wert
|
|
|
|
Note:
|
|
Gibt eine Warnung aus, falls die Umgebungsvariable nicht gesetzt ist
|
|
"""
|
|
out_path = os.environ.get(env_str)
|
|
if out_path:
|
|
return out_path
|
|
print(f"[WARN] Umgebungsvariable {env_str} ist nicht gesetzt oder leer. Verwende '{fallback}'.")
|
|
return fallback
|
|
|
|
def get_bbox(entities):
|
|
"""
|
|
Berechnet die Bounding Box für eine Liste von DXF-Entities.
|
|
|
|
Args:
|
|
entities: Liste von DXF-Entities (ezdxf entities)
|
|
|
|
Returns:
|
|
Vec2 or None: Zentrum der Bounding Box als Vec2-Objekt oder None,
|
|
falls keine gültige Geometrie gefunden wurde
|
|
|
|
Note:
|
|
Unterstützt POLYLINE, LWPOLYLINE und andere Entity-Typen.
|
|
Fehlerhafte Entities werden übersprungen und protokolliert.
|
|
"""
|
|
min_x, min_y = float('inf'), float('inf')
|
|
max_x, max_y = float('-inf'), float('-inf')
|
|
for e in entities:
|
|
try:
|
|
if e.dxftype() == "POLYLINE":
|
|
for vertex in e.vertices:
|
|
if hasattr(vertex.dxf, 'location'):
|
|
x, y = vertex.dxf.location.x, vertex.dxf.location.y
|
|
min_x, min_y = min(min_x, x), min(min_y, y)
|
|
max_x, max_y = max(max_x, x), max(max_y, y)
|
|
elif e.dxftype() == "LWPOLYLINE":
|
|
for x, y, *_ in e.get_points("xy"):
|
|
min_x, min_y = min(min_x, x), min(min_y, y)
|
|
max_x, max_y = max(max_x, x), max(max_y, y)
|
|
else:
|
|
# Für andere Entity-Typen: Standard bbox() verwenden
|
|
box = e.bbox()
|
|
if box:
|
|
(x1, y1), (x2, y2) = box.extmin, box.extmax
|
|
min_x, min_y = min(min_x, x1), min(min_y, y1)
|
|
max_x, max_y = max(max_x, x2), max(max_y, y2)
|
|
except Exception as err:
|
|
print(f" BBox Fehler für {e.dxftype()}: {err}")
|
|
continue
|
|
|
|
if min_x == float('inf'):
|
|
return None
|
|
|
|
return Vec2((min_x + max_x) / 2, (min_y + max_y) / 2)
|
|
|
|
|
|
def setup_logger(log_dir):
|
|
"""
|
|
Erstellt und konfiguriert einen Logger für die DXF2LIB Verarbeitung.
|
|
|
|
Args:
|
|
log_dir (str): Verzeichnis für die Log-Dateien
|
|
|
|
Returns:
|
|
logging.Logger: Konfigurierter Logger
|
|
|
|
Note:
|
|
Erstellt eine Log-Datei mit Zeitstempel und formatiert die Ausgaben
|
|
"""
|
|
# Logger erstellen
|
|
logger = logging.getLogger('dxf2lib')
|
|
logger.setLevel(logging.INFO)
|
|
|
|
# Bestehende Handler entfernen (falls Logger bereits existiert)
|
|
for handler in logger.handlers[:]:
|
|
logger.removeHandler(handler)
|
|
|
|
# Zeitstempel für Log-Datei
|
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
log_file = os.path.join(log_dir, f"dxf2lib_{timestamp}.log")
|
|
|
|
# File Handler erstellen
|
|
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
|
file_handler.setLevel(logging.INFO)
|
|
|
|
# Console Handler erstellen
|
|
console_handler = logging.StreamHandler()
|
|
console_handler.setLevel(logging.INFO)
|
|
|
|
# Formatter erstellen
|
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
|
file_handler.setFormatter(formatter)
|
|
console_handler.setFormatter(formatter)
|
|
|
|
# Handler zum Logger hinzufügen
|
|
logger.addHandler(file_handler)
|
|
logger.addHandler(console_handler)
|
|
|
|
return logger, log_file
|
|
|
|
|
|
def create_block_library(input_dir, output_file, config, logger=None):
|
|
"""
|
|
Erstellt eine DXF-Block-Bibliothek aus einzelnen DXF-Dateien.
|
|
|
|
Diese Funktion liest alle DXF-Dateien aus einem Verzeichnis und erstellt
|
|
daraus eine Bibliothek mit Blöcken. Die Blöcke werden in einem Raster
|
|
angeordnet und mit Beschriftungen versehen. Fehlerhafte Dateien werden
|
|
protokolliert.
|
|
|
|
Args:
|
|
input_dir (str): Verzeichnis mit den zu verarbeitenden DXF-Dateien
|
|
output_file (str): Pfad zur zu erstellenden Bibliotheks-DXF-Datei
|
|
config: ConfigParser-Objekt mit den Konfigurationswerten
|
|
logger: Optionaler Logger für Logging-Ausgaben
|
|
|
|
Note:
|
|
- Unterstützte Entity-Typen: LINE, LWPOLYLINE, POLYLINE, SPLINE, CIRCLE, ARC
|
|
- Blöcke werden zentriert und in einem 20x20 Raster angeordnet
|
|
- Erstellt eine Log-Datei mit Zeitstempel für fehlerhafte Dateien
|
|
- Automatische Erstellung des Output-Verzeichnisses falls nötig
|
|
"""
|
|
doc = ezdxf.new()
|
|
msp = doc.modelspace()
|
|
|
|
x_offset = 0
|
|
y_offset = 0
|
|
blocks_in_row = 0
|
|
|
|
error_files = []
|
|
processed_files = []
|
|
|
|
for filename in os.listdir(input_dir):
|
|
if not filename.lower().endswith(".dxf"):
|
|
continue
|
|
|
|
filepath = os.path.join(input_dir, filename)
|
|
name = os.path.splitext(filename)[0]
|
|
|
|
try:
|
|
src_doc = ezdxf.readfile(filepath)
|
|
src_msp = src_doc.modelspace()
|
|
entities = list(src_msp)
|
|
allowed_types = {"LINE", "LWPOLYLINE", "POLYLINE", "SPLINE", "CIRCLE", "ARC"}
|
|
filtered_entities = [e for e in entities if e.dxftype() in allowed_types]
|
|
entities = filtered_entities
|
|
except Exception as e:
|
|
error_msg = f"Fehler beim Lesen von {filename}: {e}"
|
|
if logger:
|
|
logger.error(error_msg)
|
|
else:
|
|
print(error_msg)
|
|
error_files.append((filename, error_msg))
|
|
continue
|
|
|
|
center = get_bbox(entities)
|
|
if center is None:
|
|
error_msg = f"Keine gültige Geometrie in {filename}"
|
|
if logger:
|
|
logger.error(error_msg)
|
|
else:
|
|
print(error_msg)
|
|
error_files.append((filename, error_msg))
|
|
continue
|
|
|
|
if name in doc.blocks:
|
|
doc.blocks.delete_block(name)
|
|
|
|
blk = doc.blocks.new(name=name, base_point=(0,0))
|
|
|
|
for e in entities:
|
|
try:
|
|
cp = e.copy()
|
|
cp.translate(-center.x, -center.y, 0) # Geometrie verschieben!
|
|
blk.add_entity(cp)
|
|
except Exception as err:
|
|
error_msg = f"Fehler beim Verarbeiten von Entity {e.dxftype()} in {filename}: {err}"
|
|
if logger:
|
|
logger.error(error_msg)
|
|
else:
|
|
print(error_msg)
|
|
error_files.append((filename, error_msg))
|
|
|
|
# Platzierung in Reihen und Spalten
|
|
msp.add_blockref(name, insert=(x_offset, y_offset))
|
|
# Text mit Blocknamen über dem Block
|
|
|
|
# Werte aus Config holen (Block: [dxf2lib])
|
|
section = "dxf2lib"
|
|
text_y = y_offset + get_cfg_value(section, "text_offset_y", DEFAULTS["text_offset_y"])
|
|
text_offset_x = get_cfg_value(section, "text_offset_x", DEFAULTS["text_offset_x"])
|
|
max_len = get_cfg_value(section, "max_len", DEFAULTS["max_len"])
|
|
text_height = get_cfg_value(section, "text_height", DEFAULTS["text_height"])
|
|
line_spacing = get_cfg_value(section, "line_spacing", DEFAULTS["line_spacing"])
|
|
block_spacing_x = get_cfg_value(section, "block_spacing_x", DEFAULTS["block_spacing_x"])
|
|
blocks_per_row = get_cfg_value(section, "blocks_per_row", DEFAULTS["blocks_per_row"])
|
|
block_spacing_y = get_cfg_value(section, "block_spacing_y", DEFAULTS["block_spacing_y"])
|
|
|
|
if len(name) > max_len:
|
|
first_line = name[:max_len]
|
|
second_line = name[max_len:]
|
|
# obere Zeile
|
|
msp.add_text(first_line, dxfattribs={'height': text_height, 'insert': (x_offset + text_offset_x, text_y)})
|
|
# untere Zeile, etwas niedriger (y kleiner)
|
|
msp.add_text(second_line, dxfattribs={'height': text_height, 'insert': (x_offset + text_offset_x, text_y - line_spacing)})
|
|
else:
|
|
msp.add_text(name, dxfattribs={'height': text_height, 'insert': (x_offset + text_offset_x, text_y)})
|
|
|
|
processed_files.append(filename)
|
|
blocks_in_row += 1
|
|
x_offset += block_spacing_x # Abstand zwischen Blöcken in einer Reihe
|
|
if blocks_in_row == blocks_per_row:
|
|
blocks_in_row = 0
|
|
x_offset = 0
|
|
y_offset -= block_spacing_y # Neue Zeile, nach unten versetzt
|
|
|
|
if logger:
|
|
logger.info(f"Bibliotheks-DXF gespeichert: {output_file}")
|
|
logger.info(f"Verarbeitete Dateien: {len(processed_files)}")
|
|
logger.info(f"Fehlerhafte Dateien: {len(error_files)}")
|
|
|
|
if error_files:
|
|
logger.warning(f"{len(error_files)} Dateien konnten nicht verarbeitet werden:")
|
|
for filename, error_msg in error_files:
|
|
logger.error(f"{filename}: {error_msg}")
|
|
else:
|
|
logger.info("Keine fehlerhaften Dateien gefunden.")
|
|
else:
|
|
print(f"Bibliotheks-DXF gespeichert: {output_file}")
|
|
print(f"Verarbeitete Dateien: {len(processed_files)}")
|
|
print(f"Fehlerhafte Dateien: {len(error_files)}")
|
|
|
|
if error_files:
|
|
print(f"Warnung: {len(error_files)} Dateien konnten nicht verarbeitet werden.")
|
|
|
|
output_dir = os.path.dirname(output_file)
|
|
if not os.path.exists(output_dir):
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
doc.saveas(output_file)
|
|
|
|
|
|
# Standardwerte (falls nicht in der Config)
|
|
DEFAULTS = {
|
|
"text_offset_y": 175, # Vertikaler Abstand des Texts über dem Block (in DXF-Einheiten)
|
|
"text_offset_x": -150, # Horizontaler Abstand des Texts links vom Block (in DXF-Einheiten)
|
|
"max_len": 9, # Maximale Zeichenlänge pro Textzeile (bei Überschreitung wird umgebrochen)
|
|
"text_height": 20, # Schriftgröße des Texts (in DXF-Einheiten)
|
|
"line_spacing": 22, # Abstand zwischen zwei Textzeilen bei Umbrüchen (in DXF-Einheiten)
|
|
"block_spacing_x": 350, # Horizontaler Abstand zwischen Blöcken in einer Reihe (in DXF-Einheiten)
|
|
"blocks_per_row": 20, # Anzahl Blöcke pro Zeile im Raster
|
|
"block_spacing_y": 500, # Vertikaler Abstand zwischen Zeilen im Raster (in DXF-Einheiten)
|
|
}
|
|
|
|
def get_cfg_value(section, key, fallback):
|
|
try:
|
|
return int(config.get(section, key))
|
|
except Exception:
|
|
return fallback
|
|
|
|
if __name__ == "__main__":
|
|
# Argumentparser für Kommandozeilenoptionen
|
|
parser = argparse.ArgumentParser(description="SVG/XML zu DXF Konverter")
|
|
parser.add_argument('-i', '--input', type=str, help='Input-Verzeichnis mit SVG/XML-Dateien')
|
|
parser.add_argument('-n', '--name', required=False, type=str, help='Name der zu erzeugenden Bibliothek (optional, wird sonst abgefragt)')
|
|
parser.add_argument('-k', '--keep', action="store_true", help='Behalte converted_dxf Ordner. Sonst Löschen nach erfolgreicher Bibliothekserstellung')
|
|
|
|
if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.name:
|
|
args.name = input("Bitte Namen der zu erzeugenden Bibliothek eingeben: ").strip()
|
|
if not args.name:
|
|
print("Fehler: Kein Name angegeben. Beende.")
|
|
sys.exit(1)
|
|
|
|
# Verzeichnisse über Umgebungsvariablen oder Fallback
|
|
if args.input:
|
|
INPUT_DIR = args.input
|
|
print(f"Verwende Input-Verzeichnis: {INPUT_DIR} \n")
|
|
else:
|
|
INPUT_DIR = os.path.join(check_environment_var("PROJECT_WORK", "."), "converted_dxfs")
|
|
print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")
|
|
|
|
OUTPUT_FILE = os.path.join(check_environment_var("PROJECT_DATA", "."),"block_libaries", f"{args.name}.dxf")
|
|
|
|
# Prüfe und erstelle log-Verzeichnis falls nötig
|
|
log_dir = check_environment_var("PROJECT_LOG", ".")
|
|
if not os.path.exists(log_dir):
|
|
os.makedirs(log_dir, exist_ok=True)
|
|
print(f"Log-Verzeichnis erstellt: {log_dir}")
|
|
|
|
# Logger Setup
|
|
logger, log_file = setup_logger(log_dir)
|
|
logger.info("=== DXF2LIB Verarbeitung gestartet ===")
|
|
logger.info(f"Input-Verzeichnis: {INPUT_DIR}")
|
|
logger.info(f"Output-Datei: {OUTPUT_FILE}")
|
|
|
|
# Lade Config-Datei
|
|
config = configparser.ConfigParser()
|
|
config_path = os.path.join(check_environment_var("PROJECT_CFG", "."), "allgemein.cfg")
|
|
config.read(config_path, encoding="utf-8")
|
|
logger.info(f"Config-Datei geladen: {config_path}")
|
|
|
|
# Erstelle die Block-Bibliothek
|
|
logger.info(f"Erstelle Block-Bibliothek aus {INPUT_DIR}...")
|
|
create_block_library(INPUT_DIR, OUTPUT_FILE, config, logger)
|
|
|
|
if not args.keep:
|
|
logger.info(f"Keep-Argument nicht gesetzt, lösche Input-Verzeichnis: {INPUT_DIR}")
|
|
try:
|
|
if os.path.exists(INPUT_DIR):
|
|
shutil.rmtree(INPUT_DIR)
|
|
logger.info(f"Verzeichnis '{INPUT_DIR}' wurde gelöscht.")
|
|
except Exception as err:
|
|
logger.warning(f"Konnte '{INPUT_DIR}' nicht löschen: {err}")
|
|
|
|
logger.info("=== DXF2LIB Verarbeitung abgeschlossen ===")
|