""" export_cross_references_from_tia : Script para exportar las referencias cruzadas de un proyecto TIA Portal a archivos (probablemente XML). """ import tkinter as tk from tkinter import filedialog import os import sys import traceback from pathlib import Path script_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ) sys.path.append(script_root) from backend.script_utils import load_configuration # --- Configuration --- # Supported TIA Portal versions mapping (extension -> version) SUPPORTED_TIA_VERSIONS = { ".ap18": "18.0", ".ap19": "19.0", ".ap20": "20.0" } # Filter for cross-references. Based on documentation: # 1: 'AllObjects', 2: 'ObjectsWithReferences', 3: 'ObjectsWithoutReferences', 4: 'UnusedObjects' # Using 1 to export all. 0 might also work as a default in some API versions. CROSS_REF_FILTER = 1 # --- TIA Scripting Import Handling --- if os.getenv("TIA_SCRIPTING"): sys.path.append(os.getenv("TIA_SCRIPTING")) else: pass try: import siemens_tia_scripting as ts except ImportError: print("ERROR: Error al importar 'siemens_tia_scripting'.") print("Asegúrese de que:") print("1. TIA Portal Openness está instalado.") print( "2. El módulo 'siemens_tia_scripting' de Python está instalado (pip install ...) o" ) print( " la ruta a sus binarios está configurada en la variable de entorno 'TIA_SCRIPTING'." ) print( "3. Está usando una versión compatible de Python (ej. 3.12.X según la documentación)." ) sys.exit(1) except Exception as e: print(f"Ocurrió un error inesperado durante la importación: {e}") traceback.print_exc() sys.exit(1) # --- Functions --- def get_supported_filetypes(): """Returns the supported file types for TIA Portal projects.""" filetypes = [] for ext, version in SUPPORTED_TIA_VERSIONS.items(): version_major = version.split('.')[0] filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}")) # Add option to show all supported files all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()]) filetypes.insert(0, ("All TIA Portal Projects", all_extensions)) return filetypes def detect_tia_version(project_file_path): """Detects TIA Portal version based on file extension.""" file_path = Path(project_file_path) file_extension = file_path.suffix.lower() if file_extension in SUPPORTED_TIA_VERSIONS: detected_version = SUPPORTED_TIA_VERSIONS[file_extension] print(f"Versión de TIA Portal detectada: {detected_version} (de la extensión {file_extension})") return detected_version else: print(f"ADVERTENCIA: Extensión de archivo no reconocida '{file_extension}'. Extensiones soportadas: {list(SUPPORTED_TIA_VERSIONS.keys())}") # Default to version 18.0 for backward compatibility print("Usando por defecto TIA Portal V18.0") return "18.0" def select_project_file(): """Opens a dialog to select a TIA Portal project file.""" root = tk.Tk() root.withdraw() file_path = filedialog.askopenfilename( title="Seleccionar archivo de proyecto TIA Portal", filetypes=get_supported_filetypes() ) root.destroy() if not file_path: print("No se seleccionó ningún archivo de proyecto. Saliendo.") sys.exit(0) return file_path def export_plc_cross_references(plc, export_base_dir): """Exports cross-references for various elements from a given PLC.""" plc_name = plc.get_name() print(f"\n--- Procesando PLC: {plc_name} ---") # Define base export path for this PLC's cross-references plc_export_dir = export_base_dir / plc_name plc_export_dir.mkdir(parents=True, exist_ok=True) # --- Export Program Block Cross-References --- blocks_cr_exported = 0 blocks_cr_skipped = 0 print(f"\n[PLC: {plc_name}] Exportando referencias cruzadas de bloques de programa...") blocks_cr_path = plc_export_dir / "ProgramBlocks_CR" blocks_cr_path.mkdir(exist_ok=True) print(f" Destino: {blocks_cr_path}") try: program_blocks = plc.get_program_blocks() print(f" Se encontraron {len(program_blocks)} bloques de programa.") for block in program_blocks: block_name = block.get_name() print(f" Procesando bloque: {block_name}...") try: print(f" Exportando referencias cruzadas para {block_name}...") block.export_cross_references( target_directorypath=str(blocks_cr_path), filter=CROSS_REF_FILTER, ) blocks_cr_exported += 1 except RuntimeError as block_ex: print( f" ERROR TIA al exportar referencias cruzadas para el bloque {block_name}: {block_ex}" ) blocks_cr_skipped += 1 except Exception as block_ex: print( f" ERROR GENERAL al exportar referencias cruzadas para el bloque {block_name}: {block_ex}" ) traceback.print_exc() blocks_cr_skipped += 1 print( f" Resumen de exportación de referencias cruzadas de bloques: Exportados={blocks_cr_exported}, Omitidos/Errores={blocks_cr_skipped}" ) except AttributeError: print( " Error de atributo: No se pudo encontrar 'get_program_blocks' en el objeto PLC. Omitiendo bloques de programa." ) except Exception as e: print(f" ERROR al acceder a los bloques de programa para exportar referencias cruzadas: {e}") traceback.print_exc() # --- Export PLC Tag Table Cross-References --- tags_cr_exported = 0 tags_cr_skipped = 0 print(f"\n[PLC: {plc_name}] Exportando referencias cruzadas de tablas de variables...") tags_cr_path = plc_export_dir / "PlcTags_CR" tags_cr_path.mkdir(exist_ok=True) print(f" Destino: {tags_cr_path}") try: tag_tables = plc.get_plc_tag_tables() print(f" Se encontraron {len(tag_tables)} tablas de variables.") for table in tag_tables: table_name = table.get_name() print(f" Procesando tabla de variables: {table_name}...") try: print(f" Exportando referencias cruzadas para {table_name}...") table.export_cross_references( target_directorypath=str(tags_cr_path), filter=CROSS_REF_FILTER ) tags_cr_exported += 1 except RuntimeError as table_ex: print( f" ERROR TIA al exportar referencias cruzadas para la tabla {table_name}: {table_ex}" ) tags_cr_skipped += 1 except Exception as table_ex: print( f" ERROR GENERAL al exportar referencias cruzadas para la tabla {table_name}: {table_ex}" ) traceback.print_exc() tags_cr_skipped += 1 print( f" Resumen de exportación de referencias cruzadas de tablas: Exportados={tags_cr_exported}, Omitidos/Errores={tags_cr_skipped}" ) except AttributeError: print( " Error de atributo: No se pudo encontrar 'get_plc_tag_tables' en el objeto PLC. Omitiendo tablas de variables." ) except Exception as e: print(f" ERROR al acceder a las tablas de variables para exportar referencias cruzadas: {e}") traceback.print_exc() # --- Export PLC Data Type (UDT) Cross-References --- udts_cr_exported = 0 udts_cr_skipped = 0 print(f"\n[PLC: {plc_name}] Exportando referencias cruzadas de tipos de datos PLC (UDTs)...") udts_cr_path = plc_export_dir / "PlcDataTypes_CR" udts_cr_path.mkdir(exist_ok=True) print(f" Destino: {udts_cr_path}") try: udts = plc.get_user_data_types() print(f" Se encontraron {len(udts)} UDTs.") for udt in udts: udt_name = udt.get_name() print(f" Procesando UDT: {udt_name}...") try: print(f" Exportando referencias cruzadas para {udt_name}...") udt.export_cross_references( target_directorypath=str(udts_cr_path), filter=CROSS_REF_FILTER ) udts_cr_exported += 1 except RuntimeError as udt_ex: print( f" ERROR TIA al exportar referencias cruzadas para el UDT {udt_name}: {udt_ex}" ) udts_cr_skipped += 1 except Exception as udt_ex: print( f" ERROR GENERAL al exportar referencias cruzadas para el UDT {udt_name}: {udt_ex}" ) traceback.print_exc() udts_cr_skipped += 1 print( f" Resumen de exportación de referencias cruzadas de UDTs: Exportados={udts_cr_exported}, Omitidos/Errores={udts_cr_skipped}" ) except AttributeError: print( " Error de atributo: No se pudo encontrar 'get_user_data_types' en el objeto PLC. Omitiendo UDTs." ) except Exception as e: print(f" ERROR al acceder a los UDTs para exportar referencias cruzadas: {e}") traceback.print_exc() # --- Export System Block Cross-References --- sys_blocks_cr_exported = 0 sys_blocks_cr_skipped = 0 print(f"\n[PLC: {plc_name}] Intentando exportar referencias cruzadas de bloques de sistema...") sys_blocks_cr_path = plc_export_dir / "SystemBlocks_CR" sys_blocks_cr_path.mkdir(exist_ok=True) print(f" Destino: {sys_blocks_cr_path}") try: if hasattr(plc, "get_system_blocks"): system_blocks = plc.get_system_blocks() print( f" Se encontraron {len(system_blocks)} bloques de sistema." ) for sys_block in system_blocks: sys_block_name = sys_block.get_name() print(f" Procesando bloque de sistema: {sys_block_name}...") try: print(f" Exportando referencias cruzadas para {sys_block_name}...") sys_block.export_cross_references( target_directorypath=str(sys_blocks_cr_path), filter=CROSS_REF_FILTER, ) sys_blocks_cr_exported += 1 except RuntimeError as sys_ex: print( f" ERROR TIA al exportar referencias cruzadas para el bloque de sistema {sys_block_name}: {sys_ex}" ) sys_blocks_cr_skipped += 1 except Exception as sys_ex: print( f" ERROR GENERAL al exportar referencias cruzadas para el bloque de sistema {sys_block_name}: {sys_ex}" ) traceback.print_exc() sys_blocks_cr_skipped += 1 else: print( " Método 'get_system_blocks' no encontrado en el objeto PLC. Omitiendo bloques de sistema." ) print( f" Resumen de exportación de referencias cruzadas de bloques de sistema: Exportados={sys_blocks_cr_exported}, Omitidos/Errores={sys_blocks_cr_skipped}" ) except AttributeError: print( " Error de atributo durante el procesamiento de bloques de sistema. Omitiendo bloques de sistema restantes." ) traceback.print_exc() except Exception as e: print( f" ERROR al acceder/procesar bloques de sistema para exportar referencias cruzadas: {e}" ) traceback.print_exc() # --- Export Software Unit Cross-References --- sw_units_cr_exported = 0 sw_units_cr_skipped = 0 print(f"\n[PLC: {plc_name}] Intentando exportar referencias cruzadas de unidades de software...") sw_units_cr_path = plc_export_dir / "SoftwareUnits_CR" sw_units_cr_path.mkdir(exist_ok=True) print(f" Destino: {sw_units_cr_path}") try: if hasattr(plc, "get_software_units"): software_units = plc.get_software_units() print(f" Se encontraron {len(software_units)} unidades de software.") for unit in software_units: unit_name = unit.get_name() print(f" Procesando unidad de software: {unit_name}...") try: print(f" Exportando referencias cruzadas para {unit_name}...") unit.export_cross_references( target_directorypath=str(sw_units_cr_path), filter=CROSS_REF_FILTER, ) sw_units_cr_exported += 1 except RuntimeError as unit_ex: print( f" ERROR TIA al exportar referencias cruzadas para la unidad de software {unit_name}: {unit_ex}" ) sw_units_cr_skipped += 1 except Exception as unit_ex: print( f" ERROR GENERAL al exportar referencias cruzadas para la unidad de software {unit_name}: {unit_ex}" ) traceback.print_exc() sw_units_cr_skipped += 1 print( f" Resumen de exportación de referencias cruzadas de unidades de software: Exportados={sw_units_cr_exported}, Omitidos/Errores={sw_units_cr_skipped}" ) else: print( " Método 'get_software_units' no encontrado en el objeto PLC. Omitiendo unidades de software." ) except AttributeError: print( " Error de atributo durante el procesamiento de unidades de software. Omitiendo unidades restantes." ) traceback.print_exc() except Exception as e: print( f" ERROR al acceder/procesar unidades de software para exportar referencias cruzadas: {e}" ) traceback.print_exc() print(f"\n--- Finalizado el procesamiento del PLC: {plc_name} ---") # --- Main Script --- if __name__ == "__main__": configs = load_configuration() working_directory = configs.get("working_directory") print("--- Exportador de Referencias Cruzadas de TIA Portal ---") # Validate working directory if not working_directory or not os.path.isdir(working_directory): print("ERROR: Directorio de trabajo no configurado o inválido.") print("Por favor configure el directorio de trabajo usando la aplicación principal.") sys.exit(1) # 1. Select Project File project_file = select_project_file() # 2. Detect TIA Portal version from project file tia_version = detect_tia_version(project_file) # 3. Define Export Directory using working_directory and subfolder export_base_dir = Path(working_directory) try: export_base_dir.mkdir(parents=True, exist_ok=True) print(f"\nProyecto seleccionado: {project_file}") print(f"Usando directorio base de exportación: {export_base_dir.resolve()}") except Exception as e: print(f"ERROR: No se pudo crear el directorio de exportación '{export_base_dir}'. Error: {e}") sys.exit(1) portal_instance = None project_object = None try: # 4. Connect to TIA Portal with detected version print(f"\nConectando a TIA Portal V{tia_version}...") portal_instance = ts.open_portal( version=tia_version, portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface, ) print("Conectado a TIA Portal.") print(f"ID del proceso del Portal: {portal_instance.get_process_id()}") # 5. Open Project print(f"Abriendo proyecto: {os.path.basename(project_file)}...") project_path_obj = Path(project_file) project_object = portal_instance.open_project( project_file_path=str(project_path_obj) ) if project_object is None: print("El proyecto podría estar ya abierto, intentando obtener el manejador...") project_object = portal_instance.get_project() if project_object is None: raise Exception("No se pudo abrir u obtener el proyecto especificado.") print("Proyecto abierto exitosamente.") # 6. Get PLCs plcs = project_object.get_plcs() if not plcs: print("No se encontraron dispositivos PLC en el proyecto.") else: print( f"Se encontraron {len(plcs)} PLC(s). Iniciando proceso de exportación de referencias cruzadas..." ) # 7. Iterate and Export Cross-References for each PLC for plc_device in plcs: export_plc_cross_references( plc=plc_device, export_base_dir=export_base_dir, ) print("\nProceso de exportación de referencias cruzadas completado.") except RuntimeError as tia_ex: print(f"\nError de TIA Portal Openness: {tia_ex}") traceback.print_exc() except FileNotFoundError: print(f"\nERROR: Archivo de proyecto no encontrado en {project_file}") except Exception as e: print(f"\nOcurrió un error inesperado: {e}") traceback.print_exc() finally: # 8. Cleanup if portal_instance: try: print("\nCerrando TIA Portal...") portal_instance.close_portal() print("TIA Portal cerrado.") except Exception as close_ex: print(f"Error durante la limpieza de TIA Portal: {close_ex}") print("\nScript finalizado.")