diff --git a/cfg/translator.cfg b/cfg/translator.cfg index a080fda..669512e 100644 --- a/cfg/translator.cfg +++ b/cfg/translator.cfg @@ -72,3 +72,14 @@ ip_adress = ^.*;(\d{1,3}\.){3}\d{1,3}$ tunnel = ^\\fArial.*TUNNEL\d+-\d{4}$ kw_A = ^\d+\s*KW\s*-\s*\d+\s*A$ + + +[symbolattribute] +# Zuordnung von Sprachcodes zu Block-Attributnamen für Texte +# Format: SPRACHCODE = TEXT-ATTRIBUTNAME +# Diese Zuordnung wird verwendet um die richtigen Attribute aus DXF-Blöcken zu lesen/schreiben +DE = TEXT-D +EN = TEXT-EN +CS = TEXT-CS +FR = TEXT-FR +PL = TEXT-PL diff --git a/lib/translate.py b/lib/translate.py index c762c27..ab519f8 100644 --- a/lib/translate.py +++ b/lib/translate.py @@ -36,10 +36,10 @@ def check_environment_var(env_str: str) -> Path: def load_translation_config(translation_lang: str, translation_dir: Path) -> tuple[dict[str, str], dict[str, str], dict[str, str], dict[str, str]]: """ - Lädt eine Übersetzungs-Config-Datei (z.B. CSK.cfg, EN.cfg, FR.cfg). + Lädt eine Übersetzungs-Config-Datei (z.B. CS.cfg, EN.cfg, FR.cfg). Args: - translation_lang: Sprach-Code (z.B. "CSK", "EN", "FR") + translation_lang: Sprach-Code (z.B. "CS", "EN", "FR") translation_dir: Verzeichnis mit Übersetzungs-Configs (aus PROJECT_TRANSLATION) Returns: @@ -107,6 +107,49 @@ def load_translation_config(translation_lang: str, translation_dir: Path) -> tup return multi, trigramme, bigramme, single +def load_symbol_attribute_mapping(cfg_path: Path) -> dict[str, str]: + """ + Liest die Zuordnung von Sprachcodes zu Symbol-Attributnamen aus der translator.cfg Datei. + + Args: + cfg_path: Pfad zum Konfigurationsverzeichnis (PROJECT_CFG) + + Returns: + Dictionary mit Sprachcode -> Attributname Mapping (z.B. "DE" -> "TEXT-D", "EN" -> "TEXT-EN") + """ + 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) + + # RawConfigParser verhindert dass Backslashes in Regex-Mustern escaped werden + config = RawConfigParser() + # Überschreibe optionxform um Groß-/Kleinschreibung zu erhalten + config.optionxform = str + try: + config.read(config_file, encoding='utf-8') + except Exception as e: + print(f"Fehler beim Lesen der Konfigurationsdatei: {e}") + sys.exit(1) + + symbol_mapping = {} + + # Lade Symbol-Attribut-Zuordnung + if config.has_section('symbolattribute'): + for key in config['symbolattribute']: + attr_name = config['symbolattribute'][key].strip() + if attr_name and not attr_name.startswith('#'): + symbol_mapping[key] = attr_name + + print(f"\nGeladene Symbol-Attribut-Zuordnungen: {len(symbol_mapping)}") + for lang, attr in symbol_mapping.items(): + print(f" {lang} -> {attr}") + + return symbol_mapping + + def load_ignore_patterns(cfg_path: Path) -> tuple[list[str], list[str]]: """ Liest die ignore_pattern aus der translator.cfg Datei. @@ -272,14 +315,15 @@ def should_ignore_text(text: str, wildcard_patterns: list[str], regex_patterns: return False -def extract_text_from_dxf(filename: Path) -> list[str]: +def extract_text_from_dxf(filename: Path, text_attr_name: str = 'TEXT-D') -> list[str]: """ - Extrahiert alle TEXT und MTEXT Objekte sowie TEXT-D Attribute aus Blöcken einer DXF Datei. + Extrahiert alle TEXT und MTEXT Objekte sowie TEXT-{attr} Attribute aus Blöcken einer DXF Datei. Verwendet ein Set, um Duplikate zu vermeiden. Filtert leere Texte heraus. Args: filename: Pfad zur DXF Datei + text_attr_name: Name des Text-Attributs in Blöcken (z.B. "TEXT-D", "TEXT-EN", "TEXT-CS"). Standard: "TEXT-D" Returns: Alphabetisch sortierte Liste aller gefundenen Texte (ohne Duplikate, ohne leere Texte) @@ -290,8 +334,8 @@ def extract_text_from_dxf(filename: Path) -> list[str]: block_count = 0 empty_count = 0 - # Attribute die ein Block haben muss, damit TEXT-D extrahiert wird - required_attributes = {'IO', 'ID', 'VERW', 'BEZEICHNUNG', 'KENNZEICHNUNG', 'TEXT-D'} + # Attribute die ein Block haben muss, damit das Text-Attribut extrahiert wird + required_attributes = {'IO', 'ID', 'VERW', 'BEZEICHNUNG', 'KENNZEICHNUNG', text_attr_name} try: doc = ezdxf.readfile(filename) @@ -332,7 +376,7 @@ def extract_text_from_dxf(filename: Path) -> list[str]: for attrib in entity.attribs: block_attributes.add(attrib.dxf.tag) - if attrib.dxf.tag == 'TEXT-D': + if attrib.dxf.tag == text_attr_name: text_d_value = attrib.dxf.text.strip() if attrib.dxf.text else None # Prüfe ob Block alle erforderlichen Attribute hat @@ -351,7 +395,7 @@ def extract_text_from_dxf(filename: Path) -> list[str]: print(f"Anzahl gefundener TEXT Objekte: {text_count}") print(f"Anzahl gefundener MTEXT Objekte: {mtext_count}") - print(f"Anzahl gefundener TEXT-D in Blöcken: {block_count}") + print(f"Anzahl gefundener {text_attr_name} in Blöcken: {block_count}") print(f"Leere Texte ignoriert: {empty_count}") total_count = text_count + mtext_count + block_count print(f"Duplikate entfernt: {total_count - empty_count - len(texts)}") @@ -543,10 +587,93 @@ def load_translations_from_json(filename: Path) -> dict[str, str]: sys.exit(1) -def translate_dxf_texts(input_dxf: Path, output_dxf: Path, translations: dict[str, str], - wildcard_patterns: list[str], regex_patterns: list[str]): +def translate_block_attribute(entity, translations: dict[str, str], + wildcard_patterns: list[str], regex_patterns: list[str], + required_attributes: set[str], text_source_attr: str, text_target_attr: str) -> tuple[int, int, int]: """ - Übersetzt alle TEXT, MTEXT Objekte und TEXT-D Attribute in Blöcken einer DXF-Datei. + Übersetzt Text-Attribute eines einzelnen Block-Inserts (INSERT entity). + + Args: + entity: INSERT entity aus der DXF-Datei + translations: Dictionary mit source -> target Mappings + wildcard_patterns: Wildcard-Muster zum Filtern + regex_patterns: Regex-Muster zum Filtern + required_attributes: Set mit erforderlichen Attributnamen für den Block + text_source_attr: Quell-Attributname (z.B. "TEXT-D", "TEXT-EN") + text_target_attr: Ziel-Attributname (z.B. "TEXT-EN", "TEXT-CS") + + Returns: + Tuple mit (translated_count, skipped_count, ignored_count) für diesen Block + """ + if not entity.has_attrib: + return 0, 0, 0 + + # Sammle alle vorhandenen Attribute des Blocks + block_attributes = {} + text_source_attrib = None + text_target_attrib = None + + for attrib in entity.attribs: + block_attributes[attrib.dxf.tag] = attrib + if attrib.dxf.tag == text_source_attr: + text_source_attrib = attrib + elif attrib.dxf.tag == text_target_attr: + text_target_attrib = attrib + + # Prüfe ob Block alle erforderlichen Attribute hat + if not (required_attributes.issubset(block_attributes.keys()) and text_source_attrib): + return 0, 0, 0 + + original_text = text_source_attrib.dxf.text.strip() if text_source_attrib.dxf.text else None + + if not original_text: + return 0, 0, 0 + + # Prüfe ob Text ignoriert werden soll + if should_ignore_text(original_text, wildcard_patterns, regex_patterns): + return 0, 0, 1 + + # Suche Übersetzung + if original_text not in translations: + return 0, 1, 0 + + # Schreibe Übersetzung in Ziel-Attribut + if text_target_attrib: + # Ziel-Attribut existiert bereits, aktualisiere es + text_target_attrib.dxf.text = translations[original_text] + else: + # Ziel-Attribut existiert nicht, erstelle es basierend auf Quell-Attribut + # Kopiere alle Eigenschaften vom Quell-Attribut + new_attrib = entity.add_attrib( + tag=text_target_attr, + text=translations[original_text], + insert=text_source_attrib.dxf.insert + ) + # Kopiere weitere Eigenschaften + if text_source_attrib.dxf.hasattr('height'): + new_attrib.dxf.height = text_source_attrib.dxf.height + if text_source_attrib.dxf.hasattr('style'): + new_attrib.dxf.style = text_source_attrib.dxf.style + if text_source_attrib.dxf.hasattr('rotation'): + new_attrib.dxf.rotation = text_source_attrib.dxf.rotation + if text_source_attrib.dxf.hasattr('width'): + new_attrib.dxf.width = text_source_attrib.dxf.width + if text_source_attrib.dxf.hasattr('layer'): + new_attrib.dxf.layer = text_source_attrib.dxf.layer + + return 1, 0, 0 + + +def translate_dxf_texts(input_dxf: Path, output_dxf: Path, translations: dict[str, str], + wildcard_patterns: list[str], regex_patterns: list[str], + text_source_attr: str = 'TEXT-D', text_target_attr: str = 'TEXT-CS'): + """ + Übersetzt alle TEXT, MTEXT Objekte und Text-Attribute in Blöcken einer DXF-Datei. + + Für Block-Attribute: + - Liest die Übersetzung aus dem Quell-Attribut (z.B. TEXT-D) + - Schreibt die Übersetzung in das Ziel-Attribut (z.B. TEXT-EN, TEXT-CS) (erstellt das Attribut falls nicht vorhanden) + - Das Quell-Attribut bleibt unverändert (Original wird bewahrt) Args: input_dxf: Pfad zur Quell-DXF @@ -554,9 +681,11 @@ def translate_dxf_texts(input_dxf: Path, output_dxf: Path, translations: dict[st translations: Dictionary mit source -> target Mappings wildcard_patterns: Wildcard-Muster zum Filtern regex_patterns: Regex-Muster zum Filtern + text_source_attr: Quell-Attributname für Texte in Blöcken (z.B. "TEXT-D", "TEXT-EN"). Standard: "TEXT-D" + text_target_attr: Ziel-Attributname für Übersetzungen in Blöcken (z.B. "TEXT-EN", "TEXT-CS"). Standard: "TEXT-CS" """ - # Attribute die ein Block haben muss, damit TEXT-D übersetzt wird - required_attributes = {'IO', 'ID', 'VERW', 'BEZEICHNUNG', 'KENNZEICHNUNG', 'TEXT-D'} + # Attribute die ein Block haben muss, damit das Text-Attribut übersetzt wird + required_attributes = {'IO', 'ID', 'VERW', 'BEZEICHNUNG', 'KENNZEICHNUNG', text_source_attr} try: doc = ezdxf.readfile(input_dxf) @@ -606,34 +735,15 @@ def translate_dxf_texts(input_dxf: Path, output_dxf: Path, translations: dict[st else: mtext_skipped += 1 - # Übersetze TEXT-D in Blöcken + # Übersetze Text-Attribute in Blöcken for entity in msp.query('INSERT'): - if entity.has_attrib: - # Sammle alle vorhandenen Attribute des Blocks - block_attributes = set() - text_d_attrib = None - - for attrib in entity.attribs: - block_attributes.add(attrib.dxf.tag) - if attrib.dxf.tag == 'TEXT-D': - text_d_attrib = attrib - - # Prüfe ob Block alle erforderlichen Attribute hat - if required_attributes.issubset(block_attributes) and text_d_attrib: - original_text = text_d_attrib.dxf.text.strip() if text_d_attrib.dxf.text else None - - if original_text: - # Prüfe ob Text ignoriert werden soll - if should_ignore_text(original_text, wildcard_patterns, regex_patterns): - block_ignored += 1 - continue - - # Suche Übersetzung - if original_text in translations: - text_d_attrib.dxf.text = translations[original_text] - block_translated += 1 - else: - block_skipped += 1 + translated, skipped, ignored = translate_block_attribute( + entity, translations, wildcard_patterns, regex_patterns, + required_attributes, text_source_attr, text_target_attr + ) + block_translated += translated + block_skipped += skipped + block_ignored += ignored # Speichere neue DXF doc.saveas(output_dxf) @@ -645,8 +755,9 @@ def translate_dxf_texts(input_dxf: Path, output_dxf: Path, translations: dict[st print(f"\nDXF-Übersetzung abgeschlossen:") print(f" TEXT übersetzt: {text_translated}, übersprungen: {text_skipped}, ignoriert: {text_ignored}") print(f" MTEXT übersetzt: {mtext_translated}, übersprungen: {mtext_skipped}, ignoriert: {mtext_ignored}") - print(f" TEXT-D (Blöcke) übersetzt: {block_translated}, übersprungen: {block_skipped}, ignoriert: {block_ignored}") + print(f" {text_target_attr} (Blöcke) erstellt/aktualisiert: {block_translated}, übersprungen: {block_skipped}, ignoriert: {block_ignored}") print(f" Gesamt: {translated_count} übersetzt, {skipped_count} übersprungen, {ignored_count} ignoriert") + print(f" Hinweis: Übersetzungen in Blöcken wurden in {text_target_attr} geschrieben, TEXT-D bleibt unverändert") print(f" Ausgabe-Datei: {output_dxf}") except Exception as e: @@ -904,7 +1015,14 @@ if __name__ == '__main__': '--translate', action='store', metavar='LANG', - help='Übersetzt Texte mit Config aus translation/.cfg (z.B. CSK, EN, FR)' + help='Übersetzt Texte mit Config aus translation/.cfg (z.B. CS, EN, FR)' + ) + parser.add_argument( + '--lang_source', + action='store', + default='DE', + metavar='LANG', + help='Quellsprache für Attribut in Blöcken (z.B. DE->TEXT-D, EN->TEXT-EN, CS->TEXT-CS). Standard: DE' ) parser.add_argument( '--translation-json', @@ -942,6 +1060,18 @@ if __name__ == '__main__': # Lade Ignore-Muster aus Konfigurationsdatei wildcard_patterns, regex_patterns = load_ignore_patterns(cfg_dir) + # Lade Symbol-Attribut-Mapping + symbol_attr_mapping = load_symbol_attribute_mapping(cfg_dir) + + # Bestimme Quell- und Ziel-Attributnamen basierend auf Sprachcodes + source_attr_name = symbol_attr_mapping.get(args.lang_source, 'TEXT-D') + target_attr_name = symbol_attr_mapping.get(args.translate, 'TEXT-CS') if args.translate else 'TEXT-CS' + + print(f"\nVerwendete Attribut-Namen:") + print(f" Quell-Attribut (--lang_source {args.lang_source}): {source_attr_name}") + if args.translate: + print(f" Ziel-Attribut (--translate {args.translate}): {target_attr_name}") + # Optional: Lade Übersetzungs-Config translation_dicts = None if args.translate: @@ -1004,7 +1134,8 @@ if __name__ == '__main__': print(f"Ziel-DXF: {output_dxf}") # Übersetze DXF - translate_dxf_texts(source_dxf, output_dxf, translations, wildcard_patterns, regex_patterns) + translate_dxf_texts(source_dxf, output_dxf, translations, wildcard_patterns, regex_patterns, + source_attr_name, target_attr_name) print("\nVerarbeitung erfolgreich abgeschlossen.") sys.exit(0) @@ -1031,7 +1162,7 @@ if __name__ == '__main__': sys.exit(1) print(f"Lese DXF Datei: {filename}") - all_texts = extract_text_from_dxf(filename) + all_texts = extract_text_from_dxf(filename, source_attr_name) elif args.fromtxt: # TXT-Modus