diff --git a/bin/setenv.bat b/bin/setenv.bat index 0b5920e..c928cf5 100644 --- a/bin/setenv.bat +++ b/bin/setenv.bat @@ -12,8 +12,10 @@ set PROJECT_LIB=%PROJECT%\lib set PROJECT_DATA=%PROJECT%\data set PROJECT_WORK=%PROJECT%\work set PROJECT_TEST=%PROJECT%\testdata +set PROJECT_LOG=%PROJECT%\log if not exist %PROJECT%\data mkdir %PROJECT%\data +if not exist %PROJECT%\log mkdir %PROJECT%\log set INSTALL_DIR="%ONEDRIVE%\Desktop\Kabeltool" diff --git a/cfg/allgemein.cfg b/cfg/allgemein.cfg index 7003541..7ab574e 100644 --- a/cfg/allgemein.cfg +++ b/cfg/allgemein.cfg @@ -1,2 +1,23 @@ # Allgemeine Konfigurationsdatei, welche die einzulesenden Layer, sowie geometrische Abhängigkeiten steuert. # General Config-File, that controls the Layers being read while processing a certain dxf file + +[dxf2lib] +# Text-Positionierung und Formatierung +# Vertikaler Abstand des Texts über dem Block (in DXF-Einheiten) +text_offset_y = 175 +# Horizontaler Abstand des Texts links vom Block (in DXF-Einheiten) +text_offset_x = -150 +# Maximale Zeichenlänge pro Textzeile (bei Überschreitung wird umgebrochen) +max_len = 9 +# Schriftgröße des Texts (in DXF-Einheiten) +text_height = 20 +# Abstand zwischen zwei Textzeilen bei Umbrüchen (in DXF-Einheiten) +line_spacing = 22 + +# Block-Anordnung im Raster +# Horizontaler Abstand zwischen Blöcken in einer Reihe (in DXF-Einheiten) +block_spacing_x = 350 +# Anzahl Blöcke pro Zeile im Raster +blocks_per_row = 20 +# Vertikaler Abstand zwischen Zeilen im Raster (in DXF-Einheiten) +block_spacing_y = 500 diff --git a/lib/dxf2lib.py b/lib/dxf2lib.py index 61e06b6..7ebefdc 100644 --- a/lib/dxf2lib.py +++ b/lib/dxf2lib.py @@ -4,8 +4,25 @@ 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 @@ -13,6 +30,20 @@ def check_environment_var(env_str: str, fallback: str) -> str: 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: @@ -43,6 +74,211 @@ def get_bbox(entities): 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") @@ -72,86 +308,35 @@ if __name__ == "__main__": OUTPUT_FILE = os.path.join(check_environment_var("PROJECT_DATA", "."),"block_libaries", f"{args.name}.dxf") - doc = ezdxf.new() - msp = doc.modelspace() + # 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}") - x_offset = 0 - y_offset = 0 - blocks_in_row = 0 + # 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}") - 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: - print(f"Fehler beim Lesen von {filename}: {e}") - continue - - center = get_bbox(entities) - if center is None: - print(f"Keine gültige Geometrie in {filename}") - 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: - print(f"Fehler in {filename}: {err}") - - # Platzierung in Reihen und Spalten - msp.add_blockref(name, insert=(x_offset, y_offset)) - # Text mit Blocknamen über dem Block - text_y = y_offset + 175 # 175 Einheiten über dem Block - max_len = 9 - text_height = 20 - line_spacing = 22 # Abstand zwischen den zwei Textzeilen - - 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 - 150, text_y)}) - # untere Zeile, etwas niedriger (y kleiner) - msp.add_text(second_line, dxfattribs={'height': text_height, 'insert': (x_offset - 150, text_y - line_spacing)}) - else: - msp.add_text(name, dxfattribs={'height': text_height, 'insert': (x_offset - 150, text_y)}) - blocks_in_row += 1 - x_offset += 350 # Abstand zwischen Blöcken in einer Reihe - if blocks_in_row == 20: - blocks_in_row = 0 - x_offset = 0 - y_offset -= 500 # Neue Zeile, nach unten versetzt - - print(f"Bibliotheks-DXF gespeichert: {OUTPUT_FILE} \n") - - 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) + # 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: - print(f"Keep-Argument nicht gesetzt, lösche Input-Verzeichnis: {INPUT_DIR} \n") + logger.info(f"Keep-Argument nicht gesetzt, lösche Input-Verzeichnis: {INPUT_DIR}") try: if os.path.exists(INPUT_DIR): shutil.rmtree(INPUT_DIR) - print(f"Verzeichnis '{INPUT_DIR}' wurde gelöscht.") + logger.info(f"Verzeichnis '{INPUT_DIR}' wurde gelöscht.") except Exception as err: - print(f"[WARN] Konnte '{INPUT_DIR}' nicht löschen: {err}") + logger.warning(f"Konnte '{INPUT_DIR}' nicht löschen: {err}") + + logger.info("=== DXF2LIB Verarbeitung abgeschlossen ===") diff --git a/lib/plant2dxf.py b/lib/plant2dxf.py index 163dbc0..94958ec 100644 --- a/lib/plant2dxf.py +++ b/lib/plant2dxf.py @@ -288,8 +288,8 @@ def main(csv_path: Path, lib_path: Path, cfg_path: 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.") + print(f"[INFO] Keine Bibliothek gefunden unter {lib_path}.") + exit(1) # Neue Ziel­zeichnung (DXF R2018) doc = ezdxf.new(dxfversion="R2018")