diff --git a/lib/translate.py b/lib/translate.py index ab519f8..37dda90 100644 --- a/lib/translate.py +++ b/lib/translate.py @@ -111,6 +111,11 @@ def load_symbol_attribute_mapping(cfg_path: Path) -> dict[str, str]: """ Liest die Zuordnung von Sprachcodes zu Symbol-Attributnamen aus der translator.cfg Datei. + Verwendet den Abschnitt [symbol_attribute], z.B.: + DE = TEXT-D + EN = TEXT-E + CS = TEXT-CS + Args: cfg_path: Pfad zum Konfigurationsverzeichnis (PROJECT_CFG) @@ -124,9 +129,7 @@ def load_symbol_attribute_mapping(cfg_path: Path) -> dict[str, str]: 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') @@ -134,12 +137,12 @@ def load_symbol_attribute_mapping(cfg_path: Path) -> dict[str, str]: print(f"Fehler beim Lesen der Konfigurationsdatei: {e}") sys.exit(1) - symbol_mapping = {} + symbol_mapping: dict[str, str] = {} # Lade Symbol-Attribut-Zuordnung - if config.has_section('symbolattribute'): - for key in config['symbolattribute']: - attr_name = config['symbolattribute'][key].strip() + if config.has_section('symbol_attribute'): + for key in config['symbol_attribute']: + attr_name = config['symbol_attribute'][key].strip() if attr_name and not attr_name.startswith('#'): symbol_mapping[key] = attr_name @@ -150,6 +153,51 @@ def load_symbol_attribute_mapping(cfg_path: Path) -> dict[str, str]: return symbol_mapping +def load_symbol_layer_mapping(cfg_path: Path) -> dict[str, str]: + """ + Liest die Zuordnung von Sprachcodes zu Symbol-Layern aus der translator.cfg Datei. + + Verwendet den Abschnitt [symbol_layer], z.B.: + DE = 0-0_TEXT-D + EN = 0-0_TEXT-E + CS = 0-0_TEXT-CS + + Args: + cfg_path: Pfad zum Konfigurationsverzeichnis (PROJECT_CFG) + + Returns: + Dictionary mit Sprachcode -> Layername Mapping + """ + 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 = RawConfigParser() + 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) + + layer_mapping: dict[str, str] = {} + + if config.has_section('symbol_layer'): + for key in config['symbol_layer']: + layer_name = config['symbol_layer'][key].strip() + if layer_name and not layer_name.startswith('#'): + layer_mapping[key] = layer_name + + print(f"\nGeladene Symbol-Layer-Zuordnungen: {len(layer_mapping)}") + for lang, layer in layer_mapping.items(): + print(f" {lang} -> {layer}") + + return layer_mapping + + def load_ignore_patterns(cfg_path: Path) -> tuple[list[str], list[str]]: """ Liest die ignore_pattern aus der translator.cfg Datei. @@ -587,20 +635,98 @@ def load_translations_from_json(filename: Path) -> dict[str, str]: sys.exit(1) -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]: +def ensure_block_attdef_and_layer(doc, block_name: str, + text_source_attr: str, + text_target_attr: str, + target_layer: str | None = None) -> None: + """ + Stellt sicher, dass im Block-Definitionseintrag ein ATTDEF für das Ziel-Attribut + existiert und der passende Layer vorhanden ist. + + - Wenn der Layer aus translator.cfg ([symbol_layer]) noch nicht existiert, wird er angelegt. + - Wenn das ATTDEF für text_target_attr noch nicht existiert, wird es auf Basis des + ATTDEF von text_source_attr erzeugt (Position, Höhe, Stil, Rotation werden übernommen). + """ + if doc is None or not block_name: + return + + try: + block = doc.blocks[block_name] + except KeyError: + # Kein Block mit diesem Namen vorhanden (z.B. anonyme Blöcke) -> nichts zu tun + return + + # Layer sicherstellen + if target_layer: + if target_layer not in doc.layers: + doc.layers.new(name=target_layer) + + source_def = None + target_def = None + + for attdef in block.attdefs(): + tag = attdef.dxf.tag + if tag == text_target_attr: + target_def = attdef + elif tag == text_source_attr: + source_def = attdef + + # Ziel-ATTDEF existiert bereits -> ggf. nur Layer korrigieren + if target_def is not None: + if target_layer: + target_def.dxf.layer = target_layer + return + + # Neues ATTDEF anlegen, möglichst anhand der Quell-Definition + insert = (0.0, 0.0) + height = 2.5 + layer_name = target_layer or "0" + + if source_def is not None: + insert = source_def.dxf.insert + if source_def.dxf.hasattr('height'): + height = source_def.dxf.height + if source_def.dxf.hasattr('layer') and not target_layer: + layer_name = source_def.dxf.layer + + # `add_attdef` unterstützt keinen `layer`-Parameter direkt, daher Layer danach setzen + new_def = block.add_attdef( + tag=text_target_attr, + insert=insert, + height=height, + ) + new_def.dxf.layer = layer_name + + # Weitere Eigenschaften (Rotation, Stil) vom Quell-ATTDEF übernehmen + if source_def is not None: + if source_def.dxf.hasattr('rotation'): + new_def.dxf.rotation = source_def.dxf.rotation + if source_def.dxf.hasattr('style'): + new_def.dxf.style = source_def.dxf.style + + +def translate_block_attribute(entity, + doc, + translations: dict[str, str], + wildcard_patterns: list[str], + regex_patterns: list[str], + required_attributes: set[str], + text_source_attr: str, + text_target_attr: str, + target_layer: str | None = None) -> tuple[int, int, int]: """ Übersetzt Text-Attribute eines einzelnen Block-Inserts (INSERT entity). Args: entity: INSERT entity aus der DXF-Datei + doc: ezdxf-Dokument, um Blockdefinitionen und Layer zu ändern 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") + target_layer: Ziel-Layername für das Ziel-Attribut (aus [symbol_layer]) Returns: Tuple mit (translated_count, skipped_count, ignored_count) für diesen Block @@ -641,13 +767,18 @@ def translate_block_attribute(entity, translations: dict[str, str], if text_target_attrib: # Ziel-Attribut existiert bereits, aktualisiere es text_target_attrib.dxf.text = translations[original_text] + # Layer ggf. anpassen + if target_layer: + text_target_attrib.dxf.layer = target_layer else: + # Vor dem Anlegen sicherstellen, dass im Block ein passender ATTDEF existiert + ensure_block_attdef_and_layer(doc, entity.dxf.name, text_source_attr, text_target_attr, target_layer) + # 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 + insert=text_source_attrib.dxf.insert, ) # Kopiere weitere Eigenschaften if text_source_attrib.dxf.hasattr('height'): @@ -658,15 +789,23 @@ def translate_block_attribute(entity, translations: dict[str, str], 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'): + # Layer explizit auf Ziel-Layer setzen, falls konfiguriert, sonst wie Quell-Attribut + if target_layer: + new_attrib.dxf.layer = target_layer + elif 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'): +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', + target_layer: str | None = None): """ Übersetzt alle TEXT, MTEXT Objekte und Text-Attribute in Blöcken einer DXF-Datei. @@ -738,8 +877,15 @@ def translate_dxf_texts(input_dxf: Path, output_dxf: Path, translations: dict[st # Übersetze Text-Attribute in Blöcken for entity in msp.query('INSERT'): translated, skipped, ignored = translate_block_attribute( - entity, translations, wildcard_patterns, regex_patterns, - required_attributes, text_source_attr, text_target_attr + entity, + doc, + translations, + wildcard_patterns, + regex_patterns, + required_attributes, + text_source_attr, + text_target_attr, + target_layer, ) block_translated += translated block_skipped += skipped @@ -1060,17 +1206,23 @@ if __name__ == '__main__': # Lade Ignore-Muster aus Konfigurationsdatei wildcard_patterns, regex_patterns = load_ignore_patterns(cfg_dir) - # Lade Symbol-Attribut-Mapping + # Lade Symbol-Attribut- und Layer-Mapping symbol_attr_mapping = load_symbol_attribute_mapping(cfg_dir) + symbol_layer_mapping = load_symbol_layer_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' + # Bestimme Ziel-Layer für Blockattribute (nur relevant bei --todxf) + target_layer_name = symbol_layer_mapping.get(args.translate) if args.translate else None + 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}") + if target_layer_name: + print(f" Ziel-Layer (--translate {args.translate}): {target_layer_name}") # Optional: Lade Übersetzungs-Config translation_dicts = None @@ -1133,9 +1285,17 @@ if __name__ == '__main__': print(f"Ziel-DXF: {output_dxf}") - # Übersetze DXF - translate_dxf_texts(source_dxf, output_dxf, translations, wildcard_patterns, regex_patterns, - source_attr_name, target_attr_name) + # Übersetze DXF (inkl. ggf. neuer TEXT-Attribute und Layer) + translate_dxf_texts( + source_dxf, + output_dxf, + translations, + wildcard_patterns, + regex_patterns, + source_attr_name, + target_attr_name, + target_layer_name, + ) print("\nVerarbeitung erfolgreich abgeschlossen.") sys.exit(0)