From f7f8aca5f18db5ec875d463a131cb6a60084041d Mon Sep 17 00:00:00 2001 From: mistangl Date: Tue, 2 Dec 2025 22:20:26 +0100 Subject: [PATCH] erste Fassung eines SKripts zur Extraktion der Texte aus einer .dxf Datei erstellt --- bin/setenv.bat | 3 + bin/setenv.sh | 2 +- bin/translate.bat | 4 + cfg/translator.cfg | 35 ++++++ lib/translate.py | 272 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 bin/translate.bat create mode 100644 cfg/translator.cfg create mode 100644 lib/translate.py diff --git a/bin/setenv.bat b/bin/setenv.bat index 870a593..bb7f15b 100644 --- a/bin/setenv.bat +++ b/bin/setenv.bat @@ -10,12 +10,14 @@ set PROJECT_LIB=%PROJECT%\lib set PROJECT_DATA=%PROJECT%\data set PROJECT_WORK=%PROJECT%\work set PROJECT_TEST=%PROJECT%\testdata +set PROJECT_TRANSLATION=%PROJECT%\translation set SIVAS_TEILESTAMM=\\195.243.223.3\sivas\jit\programme\KSbExcelExportSivasTeilestamm.exe set SIVAS_EXCEL_EXPORT_DIR=%PROJECT_DATA% if not exist %PROJECT%\work mkdir %PROJECT%\work if not exist %PROJECT%\data mkdir %PROJECT%\data +if not exist %PROJECT%\translation mkdir %PROJECT%\translation set PROJECT_IO_RESULTS=Z:\Automatisierung\Konvertierungstools\4_io-Konvertierer set PROJECT_BOM_RESULTS=Z:\Automatisierung\Konvertierungstools\3_Kabellaengen-Konvertierer @@ -26,6 +28,7 @@ if exist "%~dp0_setenv.bat" ( call "%~dp0_setenv.bat" ) +REM Prüfe ob PATH bereits erweitert wurde set PATH=%PROJECT_BIN%;%PATH% diff --git a/bin/setenv.sh b/bin/setenv.sh index be928da..63fea69 100755 --- a/bin/setenv.sh +++ b/bin/setenv.sh @@ -22,5 +22,5 @@ export PROJECT_IO_RESULTS=$PROJECT_WORK export PROJECT_BOM_RESULTS=$PROJECT_WORK export INSTALL_DIR="$HOME/kabeltool" -# Add project bin to PATH +# Add project bin to PATH (only if not already added) export PATH="$PROJECT_BIN:$PATH" diff --git a/bin/translate.bat b/bin/translate.bat new file mode 100644 index 0000000..578be49 --- /dev/null +++ b/bin/translate.bat @@ -0,0 +1,4 @@ +@echo off +CALL manage_interpreter.bat activate_interpreter +python %PROJECT_LIB%\translate.py %* +CALL manage_interpreter.bat deactivate_interpreter \ No newline at end of file diff --git a/cfg/translator.cfg b/cfg/translator.cfg new file mode 100644 index 0000000..4658a06 --- /dev/null +++ b/cfg/translator.cfg @@ -0,0 +1,35 @@ +[ignore_pattern_wildcard] +# Wildcard-Muster für Texte die NICHT in die Excel Datei geschrieben werden sollen +# Ein Muster pro Zeile +# Wildcards: * (mehrere Zeichen), ? (ein Zeichen) +# Format: pattern_name = muster + +# Häufige technische Abkürzungen und Codes +layer = LAYER=* +dim = DIM* +special = %%* + +# Koordinaten und technische Angaben +coord_x = X=* +coord_y = Y=* +coord_z = Z=* + + +[ignore_pattern_regex] +# Reguläre Ausdrücke für Texte die NICHT in die Excel Datei geschrieben werden sollen +# Ein Ausdruck pro Zeile (Python regex Syntax) +# Format: pattern_name = regex + +# Formeln wie =A1 +AB12 +formula = ^=A\d\s*\+[A-Z]{2}\d+ + +# Technische Angaben +height = ^\\H1.875x +millimeter = ^\d+\s*mm$ +m_numbers = ^M\d{3}$ + +# Koordinaten mit Pfeilen +coord_arrows = ^\+\d{1,3}(?:[.,]\d{3})\s*(?:<<<|>>>)\s*\+\d{1,3}(?:[.,]\d{3})$ +# +UC3206\P3 x ET200 SP DI16 402.0 +uc4 = ^\+UC\d{4} +uc2 = ^=A\d\+UC\d{2} \ No newline at end of file diff --git a/lib/translate.py b/lib/translate.py new file mode 100644 index 0000000..59dc953 --- /dev/null +++ b/lib/translate.py @@ -0,0 +1,272 @@ +import argparse +import os +import re +import sys +from pathlib import Path +from configparser import ConfigParser +from fnmatch import fnmatch + +import ezdxf +from openpyxl import Workbook + + +""" +Dieses Programm: +- liest eine DXF Datei und sammelt alle TEXT und MTEXT Objekte +- schreibt diese in die erste Spalte einer Excel-Datei +- speichert die Excel-Datei im PROJECT_WORK Verzeichnis +""" + + +def check_environment_var(env_str: str) -> Path: + """Prüft ob eine Umgebungsvariable gesetzt ist und gibt den Pfad zurück.""" + out_path = os.environ.get(env_str) + if out_path: + return Path(out_path) + else: + print(f"Umgebungsvariable {env_str} ist nicht gesetzt oder leer.") + sys.exit(1) + + +def load_ignore_patterns(cfg_path: Path) -> tuple[list[str], list[str]]: + """ + Liest die ignore_pattern aus der translator.cfg Datei. + Unterstützt sowohl Wildcard-Muster (*,?) als auch reguläre Ausdrücke. + + Args: + cfg_path: Pfad zum Konfigurationsverzeichnis (PROJECT_CFG) + + Returns: + Tuple mit (Wildcard-Mustern, Regex-Mustern) + """ + config_file = cfg_path / "translator.cfg" + + if not config_file.exists(): + print(f"FEHLER: Konfigurationsdatei nicht gefunden: {config_file}") + print(f"Bitte erstellen Sie die Datei 'translator.cfg' im Verzeichnis: {cfg_path}") + sys.exit(1) + + config = ConfigParser() + try: + config.read(config_file, encoding='utf-8') + except Exception as e: + print(f"Fehler beim Lesen der Konfigurationsdatei: {e}") + sys.exit(1) + + wildcard_patterns = [] + regex_patterns = [] + + # Lade Wildcard-Muster + if config.has_section('ignore_pattern_wildcard'): + for key in config['ignore_pattern_wildcard']: + pattern = config['ignore_pattern_wildcard'][key].strip() + if pattern and not pattern.startswith('#'): + wildcard_patterns.append(pattern) + + # Lade Regex-Muster + if config.has_section('ignore_pattern_regex'): + for key in config['ignore_pattern_regex']: + pattern = config['ignore_pattern_regex'][key].strip() + if pattern and not pattern.startswith('#'): + regex_patterns.append(pattern) + + print(f"Geladene Wildcard-Muster: {len(wildcard_patterns)}") + for pattern in wildcard_patterns: + print(f" - {pattern}") + + print(f"Geladene Regex-Muster: {len(regex_patterns)}") + for pattern in regex_patterns: + print(f" - {pattern}") + + return wildcard_patterns, regex_patterns + + +def should_ignore_text(text: str, wildcard_patterns: list[str], regex_patterns: list[str]) -> bool: + """ + Prüft ob ein Text ignoriert werden soll basierend auf den Mustern. + + Args: + text: Zu prüfender Text + wildcard_patterns: Liste der Wildcard-Muster (*,?) + regex_patterns: Liste der regulären Ausdrücke + + Returns: + True wenn Text ignoriert werden soll, False sonst + """ + # Prüfe Wildcard-Muster + for pattern in wildcard_patterns: + if fnmatch(text, pattern): + return True + + # Prüfe Regex-Muster + for pattern in regex_patterns: + try: + if re.search(pattern, text): + return True + except re.error: + # Falls ungültiger Regex, ignoriere dieses Muster + print(f"WARNUNG: Ungültiger regulärer Ausdruck: {pattern}") + continue + + return False + + +def extract_text_from_dxf(filename: Path, wildcard_patterns: list[str], regex_patterns: list[str]) -> list[str]: + """ + Extrahiert alle TEXT und MTEXT Objekte aus einer DXF Datei. + Verwendet ein Set, um Duplikate zu vermeiden. + + Args: + filename: Pfad zur DXF Datei + wildcard_patterns: Liste der Wildcard-Muster zum Filtern + regex_patterns: Liste der Regex-Muster zum Filtern + + Returns: + Alphabetisch sortierte Liste mit allen gefundenen Texten (nach Filterung, ohne Duplikate) + """ + texts_set = set() # Set für eindeutige Texte + ignored_count = 0 + total_count = 0 + + try: + doc = ezdxf.readfile(filename) + msp = doc.modelspace() + + # Sammle alle TEXT Objekte + for entity in msp.query('TEXT'): + if entity.dxf.text: + total_count += 1 + text = entity.dxf.text + if should_ignore_text(text, wildcard_patterns, regex_patterns): + ignored_count += 1 + else: + texts_set.add(text) + + # Sammle alle MTEXT Objekte + for entity in msp.query('MTEXT'): + if entity.text: + total_count += 1 + text = entity.text + if should_ignore_text(text, wildcard_patterns, regex_patterns): + ignored_count += 1 + else: + texts_set.add(text) + + # Konvertiere Set zu sortierter Liste + texts = sorted(texts_set) + + print(f"Anzahl gefundener TEXT/MTEXT Objekte: {total_count}") + print(f"Ignorierte Texte (nach Filter): {ignored_count}") + print(f"Duplikate entfernt: {total_count - ignored_count - len(texts)}") + print(f"Eindeutige Texte für Export: {len(texts)}") + + except Exception as e: + print(f"Fehler beim Lesen der DXF Datei: {e}") + sys.exit(1) + + return texts + + +def write_texts_to_excel(texts: list[str], output_path: Path): + """ + Schreibt die Texte in die erste Spalte einer Excel-Datei. + + Args: + texts: Liste der zu schreibenden Texte + output_path: Pfad zur Ausgabe-Excel-Datei + """ + wb = Workbook() + ws = wb.active + ws.title = "Texte" + + # Kopfzeile + ws['A1'] = "Text" + + # Texte in die erste Spalte schreiben + for idx, text in enumerate(texts, start=2): + ws[f'A{idx}'] = text + + # Spaltenbreite anpassen + ws.column_dimensions['A'].width = 50 + + try: + wb.save(output_path) + print(f"Excel-Datei erfolgreich erstellt: {output_path}") + except Exception as e: + print(f"Fehler beim Schreiben der Excel-Datei: {e}") + sys.exit(1) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Extrahiert TEXT und MTEXT Objekte aus einer DXF Datei und schreibt sie in eine Excel-Datei', + prog='translate' + ) + parser.add_argument( + '-f', '--filename', + action='store', + required=True, + help='DXF Datei die verarbeitet werden soll', + metavar='myfile.dxf' + ) + parser.add_argument( + '-e', '--extract', + action='store_true', + required=True, + help='Hole alle Texte aus der .dxf in eine Excel Datei' + ) + parser.add_argument( + '-o', '--outname', + action='store', + help='Name der Ausgabedatei (ohne Pfad, mit oder ohne .xlsx Endung)', + metavar='output.xlsx' + ) + + args = parser.parse_args() + + # Prüfe Umgebungsvariablen + work_dir = check_environment_var('PROJECT_WORK') + cfg_dir = check_environment_var('PROJECT_CFG') + + # Lade Ignore-Muster aus Konfigurationsdatei + wildcard_patterns, regex_patterns = load_ignore_patterns(cfg_dir) + + # Prüfe ob Datei eine DXF Datei ist + input_filename = args.filename + if not input_filename.lower().endswith(('.dxf', '.dwg')): + print("Nur verfügbar für .dxf oder .dwg Dateien") + sys.exit(1) + + # Versuche zuerst Datei im Work-Ordner zu finden + filename = work_dir / input_filename + if not filename.exists(): + # Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist + filename = Path(input_filename) + if not filename.exists(): + print(f"Datei nicht gefunden: {input_filename}") + print(f" Geprüft in Work-Ordner: {work_dir / input_filename}") + print(f" Geprüft als vollständiger Pfad: {filename}") + sys.exit(1) + + # Extrahiere Texte aus DXF + if args.extract: + print(f"Lese DXF Datei: {filename}") + texts = extract_text_from_dxf(filename, wildcard_patterns, regex_patterns) + + # Erstelle Output-Dateinamen + if args.outname: + # Verwende benutzerdefinierten Namen + output_filename = args.outname + # Füge .xlsx Endung hinzu falls nicht vorhanden + if not output_filename.endswith('.xlsx'): + output_filename += '.xlsx' + else: + # Standardname basierend auf Eingabedatei + output_filename = filename.stem + "_texts.xlsx" + + output_path = work_dir / output_filename + + # Schreibe Texte in Excel + write_texts_to_excel(texts, output_path) + + print("Verarbeitung erfolgreich abgeschlossen.")