diff --git a/cfg/CSK-lang.cfg b/cfg/CSK-lang.cfg new file mode 100644 index 0000000..27167a5 --- /dev/null +++ b/cfg/CSK-lang.cfg @@ -0,0 +1,176 @@ +[trigramme] +from Storage level=z úložné úrovně +rail bottom edge=spodní hrana kolejnice +Loaded Minitrolleys to=Naložené minivozíky do +to Storage level=do úložné úrovně +to operational area=do provozní oblasti +Empty Minitrolleys to=Prázdné minivozíky do +Minitrolleys to storage=Minivozíky do úložiště +Multi Purpose Station=Víceúčelová stanice +Rail Bottom Edge=Spodní hrana kolejnice +hangsacks max capacity=maximální kapacita závěsných pytlů +Empty Minitrolleys Buffer=Prázdné minivozíky Buffer +Minitrolleys Buffer Loading=Minivozíky Pufr Nakládání +Minitrolleys to Inbound=Minivozíky do příjmu +loaded Minitrolleys to=naložené minivozíky do +Minitrolleys to Storage=Minivozíky do skladu +to Inbound empty=do příjmu prázdné +empty Minitrolleys from=prázdné minivozíky z +Length of Trolley=Délka vozíku + + +[bigramme] +AMR area=AMR area +AMR ILS=AMR ILS +AMR loaded=AMR loaded +Belastung Abhänger=Zatížení závěsu +Belastung Hänger=Zatížení závěsu +Belastung Säule=Zatížení sloupu +Bottom Edge=Bottom Edge +bottom edge=spodní hrana +Buffer Loading=Buffer Loading +capacity x=kapacita x +compressed air=stlačený vzduch +Control cabinet=Rozvaděč +Dimensions L=Rozměry L +Distance to=Vzdálenost k +distributor omniflo=distributoru omniflo +Empty Minitrolleys=Prázdné minivozíky +empty Minitrolleys=prázdné minivozíky +Fire door=Požární dveře +from AMR=z AMR +from Storage=ze skladu +GOH Storage=Sklad GOH +GOH storage=Sklad GOH +hangsack =závěsný vak +hangsacks max=závěsné vaky max. +Inbound empty=Příchozí prázdné +label Printer=tiskárna štítků +Lenght Measurement=Měření délky +Length of=Délka +line x=řady x +lines GOH=řady GOH +lines x=řady x +loaded Minitrolleys=naložené minivozíky +Loaded Minitrolleys=Naložené minivozíky +Loading Boom=Nakládací rameno +Loading Buff=Nakládací nárazník +long x=dlouhý x +max capacity=max. kapacita +Minitrolleys Buffer=Minivozíky Buffer +Minitrolleys from=Minivozíky z +Minitrolleys to=Minitrolleys to +MT line=MT line +Multi Purpose=Multi Purpose +of Trolley=of Trolley +Operation panel=Operation panel +operational area=operational area +Purpose Station=Purpose Station +rail bottom=rail bottom +Rail Bottom=Rail Bottom +Säule=Säule +Schnitt A=Schnitt A +Schnitt Highway=Schnitt Highway +short x=short x +storage level=storage level +Storage level=Storage level +Storage Level=Storage Level +Sub distributor=Sub distributor +to AMR=to AMR +to Inbound=to Inbound +to L=to L +to operational=to operational +to Q=to Q +to storage=to storage +to Storage=to Storage +Trolley MT=Trolley MT +with TEF=with TEF + + +[single] +# deutsche Begriffe und ihre tschechische Übersetzung +# Format: Begriffe in der dxf=tschechisch +# ## deutsch +Abhänger=Závěs +Belastung=Zatížení +Säule=Sloup +Abhänger=Závěs +Schnitt=Řez +Unterverteiler=Rozvaděč +Schaltschrank=Rozvaděč +zur=k +ca=ca +Hänger=Závěs +# ## english +air=prázdná +area=oblast +Boom=Rameno +bottom=Kolejnice +Bottom=Spodní +buffer=paleta +Buffer=Rozměry +cabinet=Ovládací +capacity=kapacitu +compressed=hrana +control=ovládání +Control=Sběrné místo +Dimensions=skříň +Distance=naložené +distributor=distributor +door=dveře +Door=Dveře +edge=kolejnice +Edge=Okraj +empty=krátká +Empty=Prázdná +Exit=Výstup +Fire=Požární +Firewall=Požární +for=pro +from=z +hangsack=Stanice +hangsacks=vzduch +Highway=Dálniční +Inbound=stlačená +label=etiketa +Lenght=Délka +Length=Délka +level=úroveň +Level=Úroveň +line=linka +lines=linky +Loaded=dlouhý +loaded=závěsné vaky +Loading=Vzdálenost +long=Buffer +max=max. +Measurement=Měření +Multi=buffer +of=odchozího +Operation=Provoz +operational=Nakládání +Outbound=Odchozí +pallet=závěsný vak +panel=panel +Pickstation=Příchozí +Plastic=Plastová +Printer=Tiskárna +Purpose=Víceúčelový +Rail=provozní +rail=spodní +short=stěna +Station=Naložený +Storage=Skladování +storage=skladovací +Sub=Pod +System=systém +to=do +Trolley=Vozík +Unterverteiler=Podrozdělovač +Schaltschrank=Rozvaděč +waste=odpad +Weight=Hmotnost +with=s + + + diff --git a/cfg/translator.cfg b/cfg/translator.cfg index db4925c..ee1bd99 100644 --- a/cfg/translator.cfg +++ b/cfg/translator.cfg @@ -6,13 +6,13 @@ # 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=* +KR_code = KR-M* @@ -21,12 +21,30 @@ coord_z = Z=* # Ein Ausdruck pro Zeile (Python regex Syntax) # Format: pattern_name = regex +# Dezimalzahl mit Punkt oder Komma +decimal_number = ^-?\d+([.,]\d+)?$ +pos_decimal_number = ^\+?\d+([.,]\d+)?$ +# Prozentangaben +percent = ^-?\d+([.,]\d+)?\s*%$ + # Angaben wie =A1 +AB12 oder =A2+UZ304 anlagen_nr = ^=A\d\s*\+[A-Z]{2}\d+ -anlagen_nr3 = ^=A\d\+UZ\d{3} +anlagen_nr3 = ^=A\d\+UZ\d{3} + +# Zeichen von A bis F als einzelnes Zeichen ein_zeichen = ^[A-F]$ +# A.1 bold +single_letters = ^\\f.*;[A-Z]\.\d$ +AX = ^[A-F]\d$ +ILSCV = ^ILS-CV\\PM\d{4}$ + +# # Blöcke wie BLOCK 1.1.2 +# BLOCK_3 = ^BLOCK \d\.\d(\.\d)?$ +# BLOCK_2 = ^BLOCK \d\.\d$ +# BLOCK_1 = ^BLOCK \d\$ + # Technische Angaben h1875 = ^\\H1.875x h1875ff = ^\\H1.875x;.*$ @@ -37,12 +55,19 @@ m_numbers = ^M\d{3}$ 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} -uc3 = ^=A\d\+UC\d{3} -uz2 = ^=A\d\+UZ\d{2} -uz3 = ^=A\d\+UZ\d{3} +ax_uc4 = ^=A\d\+UC\d{2,4} +ax_uz4 = ^=A\d\+UZ\d{2,4} +a0x_uz1= ^=A01\+UZ\d+ +a0x_uc4 = ^=A0\d\+UC\d{2,4} +a0x_uz4 = ^=A\d\+UZ\d{2,4} +pxqc_patterns = ^\\pxqc; =A0[1-9]\+UC\d{2,4}$ +pxqc_patterns2 = ^\\pxqc; =A1\+UC\d{2,4}$ +pxqc_patterns3 = ^\\pxqc; =A1\+UH\d{2,4}$ -ip_adresse = ^\\fArial Narrow.*(\d{1,3}\.){3}\d{1,3}$ + +ip_adress = ^.*;(\d{1,3}\.){3}\d{1,3}$ + +# TUNNEL Bezeichnungen wie \fArial TUNNEL12-3456 tunnel = ^\\fArial.*TUNNEL\d+-\d{4}$ -kr = ^KR-\d{3,4}$ +kw_A = ^\d+\s*KW\s*-\s*\d+\s*A$ diff --git a/lib/translate.py b/lib/translate.py index 8f1e30e..eb6075b 100644 --- a/lib/translate.py +++ b/lib/translate.py @@ -13,8 +13,10 @@ 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 + ODER liest Texte aus einer TXT Datei (ein Text pro Zeile) +- filtert die Texte basierend auf Mustern in translator.cfg +- schreibt die Ergebnisse in eine Excel- oder Textdatei +- speichert die Ausgabedatei im PROJECT_WORK Verzeichnis """ @@ -117,23 +119,21 @@ def should_ignore_text(text: str, wildcard_patterns: list[str], regex_patterns: return False -def extract_text_from_dxf(filename: Path, wildcard_patterns: list[str], regex_patterns: list[str], debug: bool = False) -> list[str]: +def extract_text_from_dxf(filename: Path) -> list[str]: """ Extrahiert alle TEXT und MTEXT Objekte aus einer DXF Datei. Verwendet ein Set, um Duplikate zu vermeiden. + Filtert leere Texte heraus. Args: filename: Pfad zur DXF Datei - wildcard_patterns: Liste der Wildcard-Muster zum Filtern - regex_patterns: Liste der Regex-Muster zum Filtern - debug: Wenn True, gibt Debug-Informationen aus Returns: - Alphabetisch sortierte Liste mit allen gefundenen Texten (nach Filterung, ohne Duplikate) + Alphabetisch sortierte Liste aller gefundenen Texte (ohne Duplikate, ohne leere Texte) """ texts_set = set() # Set für eindeutige Texte - ignored_count = 0 total_count = 0 + empty_count = 0 try: doc = ezdxf.readfile(filename) @@ -147,13 +147,10 @@ def extract_text_from_dxf(filename: Path, wildcard_patterns: list[str], regex_pa # Ignoriere leere Texte if not text: - ignored_count += 1 + empty_count += 1 continue - if should_ignore_text(text, wildcard_patterns, regex_patterns, debug): - ignored_count += 1 - else: - texts_set.add(text) + texts_set.add(text) # Sammle alle MTEXT Objekte for entity in msp.query('MTEXT'): @@ -163,21 +160,18 @@ def extract_text_from_dxf(filename: Path, wildcard_patterns: list[str], regex_pa # Ignoriere leere Texte if not text: - ignored_count += 1 + empty_count += 1 continue - if should_ignore_text(text, wildcard_patterns, regex_patterns, debug): - ignored_count += 1 - else: - texts_set.add(text) + 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)}") + print(f"Leere Texte ignoriert: {empty_count}") + print(f"Duplikate entfernt: {total_count - empty_count - len(texts)}") + print(f"Eindeutige Texte extrahiert: {len(texts)}") except Exception as e: print(f"Fehler beim Lesen der DXF Datei: {e}") @@ -186,6 +180,71 @@ def extract_text_from_dxf(filename: Path, wildcard_patterns: list[str], regex_pa return texts +def filter_texts(texts: list[str], wildcard_patterns: list[str], regex_patterns: list[str], debug: bool = False) -> tuple[list[str], list[str]]: + """ + Filtert eine Liste von Texten basierend auf Wildcard- und Regex-Mustern. + Diese Funktion ist unabhängig von der Quelle der Texte (DXF oder TXT). + + Args: + texts: Liste der zu filternden Texte + wildcard_patterns: Liste der Wildcard-Muster zum Filtern + regex_patterns: Liste der Regex-Muster zum Filtern + debug: Wenn True, gibt Debug-Informationen aus + + Returns: + Tuple mit (akzeptierte Texte, ignorierte Texte) - beide alphabetisch sortiert + """ + accepted_texts = [] + ignored_texts = [] + + for text in texts: + if should_ignore_text(text, wildcard_patterns, regex_patterns, debug): + ignored_texts.append(text) + else: + accepted_texts.append(text) + + # Sortiere beide Listen alphabetisch + accepted_texts.sort() + ignored_texts.sort() + + print(f"\nFilterung abgeschlossen:") + print(f" Akzeptierte Texte: {len(accepted_texts)}") + print(f" Ignorierte Texte: {len(ignored_texts)}") + + return accepted_texts, ignored_texts + + +def read_texts_from_file(filename: Path) -> list[str]: + """ + Liest Texte aus einer UTF-8 Textdatei (ein Text pro Zeile). + Entfernt Duplikate und leere Zeilen. + + Args: + filename: Pfad zur Textdatei + + Returns: + Alphabetisch sortierte Liste aller Texte (ohne Duplikate, ohne leere Zeilen) + """ + texts_set = set() + + try: + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + text = line.strip() + if text: # Ignoriere leere Zeilen + # Konvertiere escaped Zeilenumbrüche zurück + text = text.replace('\\n', '\n').replace('\\r', '\r') + texts_set.add(text) + + texts = sorted(texts_set) + print(f"Texte aus Datei gelesen: {len(texts)}") + return texts + + except Exception as e: + print(f"Fehler beim Lesen der Textdatei: {e}") + sys.exit(1) + + def write_texts_to_excel(texts: list[str], output_path: Path): """ Schreibt die Texte in die erste Spalte einer Excel-Datei. @@ -216,28 +275,72 @@ def write_texts_to_excel(texts: list[str], output_path: Path): sys.exit(1) +def write_texts_to_textfile(texts: list[str], ignored_texts: list[str], output_path: Path): + """ + Schreibt die Texte in eine UTF-8 Textdatei. + Jeder Text wird in einer eigenen Zeile geschrieben (auch Multiline-Texte). + Die Datei enthält zwei Teile: akzeptierte und ignorierte Texte. + + Args: + texts: Liste der akzeptierten Texte + ignored_texts: Liste der ignorierten Texte + output_path: Pfad zur Ausgabe-Textdatei + """ + try: + with open(output_path, 'w', encoding='utf-8') as f: + # Schreibe akzeptierte Texte + for text in texts: + # Multiline-Texte werden in einer Zeile geschrieben (Zeilenumbrüche ersetzen) + text_single_line = text.replace('\n', '\\n').replace('\r', '\\r') + f.write(text_single_line + '\n') + + # Trennzeile + f.write('\n-- durch translator.cfg ausgeschlossen --\n\n') + + # Schreibe ignorierte Texte + for text in ignored_texts: + # Multiline-Texte werden in einer Zeile geschrieben (Zeilenumbrüche ersetzen) + text_single_line = text.replace('\n', '\\n').replace('\r', '\\r') + f.write(text_single_line + '\n') + + print(f"Textdatei erfolgreich erstellt: {output_path}") + print(f" Akzeptierte Texte: {len(texts)}") + print(f" Ignorierte Texte: {len(ignored_texts)}") + except Exception as e: + print(f"Fehler beim Schreiben der Textdatei: {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', + description='Extrahiert TEXT und MTEXT Objekte aus einer DXF Datei oder filtert Texte aus einer TXT Datei', prog='translate' ) + + # Mutually exclusive group: entweder DXF-Extraktion oder TXT-Filterung + input_group = parser.add_mutually_exclusive_group(required=True) + input_group.add_argument( + '-e', '--extract', + action='store_true', + help='Extrahiert Texte aus DXF/DWG Datei (benötigt --filename)' + ) + input_group.add_argument( + '--fromtxt', + action='store', + metavar='FILE.txt', + help='Filtert Texte aus einer TXT Datei (ein Text pro Zeile)' + ) + parser.add_argument( '-f', '--filename', action='store', - required=True, - help='DXF Datei die verarbeitet werden soll', + help='DXF/DWG Datei (.dxf, .dwg) die verarbeitet werden soll (nur mit --extract)', 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)', + help='Name der Ausgabedatei (ohne Pfad, mit oder ohne .xlsx/.txt Endung)', metavar='output.xlsx' ) parser.add_argument( @@ -245,9 +348,24 @@ if __name__ == '__main__': action='store_true', help='Aktiviert Debug-Ausgabe für Pattern-Matching' ) + parser.add_argument( + '-t', '--export-type', + action='store', + default='excel', + choices=['excel', 'text'], + help='Exportformat: excel (Standard) oder text (UTF-8 Textdatei)' + ) args = parser.parse_args() + # Validierung: --extract benötigt --filename + if args.extract and not args.filename: + parser.error("--extract benötigt --filename") + + # Validierung: --filename nur mit --extract + if args.filename and not args.extract: + parser.error("--filename kann nur mit --extract verwendet werden") + # Prüfe Umgebungsvariablen work_dir = check_environment_var('PROJECT_WORK') cfg_dir = check_environment_var('PROJECT_CFG') @@ -255,44 +373,82 @@ if __name__ == '__main__': # 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) + if args.debug: + print("DEBUG-Modus aktiviert") - # 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}") + # Bestimme Input-Modus und Datei + if args.extract: + # DXF/DWG-Modus + input_filename = args.filename + file_extension = input_filename.lower() + + if not file_extension.endswith(('.dxf', '.dwg')): + print("Mit --extract nur .dxf oder .dwg Dateien erlaubt") sys.exit(1) - # Extrahiere Texte aus DXF - if args.extract: - print(f"Lese DXF Datei: {filename}") - if args.debug: - print("DEBUG-Modus aktiviert") - texts = extract_text_from_dxf(filename, wildcard_patterns, regex_patterns, args.debug) + # 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) - # Erstelle Output-Dateinamen - if args.outname: - # Verwende benutzerdefinierten Namen - output_filename = args.outname - # Füge .xlsx Endung hinzu falls nicht vorhanden + print(f"Lese DXF Datei: {filename}") + all_texts = extract_text_from_dxf(filename) + + else: # args.fromtxt + # TXT-Modus + input_filename = args.fromtxt + + if not input_filename.lower().endswith('.txt'): + print("Mit --fromtxt nur .txt Dateien erlaubt") + 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) + + print(f"Lese Textdatei: {filename}") + all_texts = read_texts_from_file(filename) + + # Schritt 2: Texte filtern + texts, ignored_texts = filter_texts(all_texts, wildcard_patterns, regex_patterns, args.debug) + + # Erstelle Output-Dateinamen basierend auf Export-Typ + if args.outname: + # Verwende benutzerdefinierten Namen + output_filename = args.outname + # Füge Endung hinzu falls nicht vorhanden + if args.export_type == 'excel': if not output_filename.endswith('.xlsx'): output_filename += '.xlsx' - else: - # Standardname basierend auf Eingabedatei + else: # text + if not output_filename.endswith('.txt'): + output_filename += '.txt' + else: + # Standardname basierend auf Eingabedatei + if args.export_type == 'excel': output_filename = filename.stem + "_texts.xlsx" + else: # text + output_filename = filename.stem + "_texts.txt" - output_path = work_dir / output_filename + output_path = work_dir / output_filename - # Schreibe Texte in Excel + # Schreibe Texte in gewähltem Format + if args.export_type == 'excel': write_texts_to_excel(texts, output_path) + else: # text + write_texts_to_textfile(texts, ignored_texts, output_path) - print("Verarbeitung erfolgreich abgeschlossen.") + print("Verarbeitung erfolgreich abgeschlossen.")