""" x8_manual_gui.py - Interfaz Manual con GUI para XML→SCL Este script proporciona una interfaz gráfica simple para convertir archivos XML de Siemens TIA Portal a código SCL usando cuadros de diálogo de Windows. Funcionalidad: - Cuadro de diálogo para seleccionar archivo XML de origen - Cuadro de diálogo para seleccionar directorio de destino - Ejecuta automáticamente x0_main.py en modo simple - Interfaz amigable con mensajes de progreso """ import tkinter as tk from tkinter import filedialog, messagebox, ttk import os import sys import subprocess import threading import time class XMLtoSCLConverter: def __init__(self): self.root = tk.Tk() self.root.title("XML→SCL Converter - Modo Manual") self.root.geometry("600x500") self.root.resizable(False, False) # Variables self.source_xml_path = tk.StringVar() self.dest_directory = tk.StringVar() self.dest_filename = tk.StringVar() # Configurar el directorio de script self.script_dir = os.path.dirname(os.path.abspath(__file__)) self.x0_main_path = os.path.join(self.script_dir, "x0_main.py") self.setup_ui() def setup_ui(self): """Configurar la interfaz de usuario""" # Título principal title_frame = tk.Frame(self.root, bg="#2c3e50") title_frame.pack(fill="x", pady=(0, 20)) title_label = tk.Label( title_frame, text="🔄 XML → SCL Converter", font=("Arial", 16, "bold"), fg="white", bg="#2c3e50", pady=15, ) title_label.pack() subtitle_label = tk.Label( title_frame, text="Convierte archivos XML de Siemens TIA Portal a código SCL", font=("Arial", 10), fg="#ecf0f1", bg="#2c3e50", pady=(0, 10), ) subtitle_label.pack() # Frame principal main_frame = tk.Frame(self.root) main_frame.pack(fill="both", expand=True, padx=20) # Sección 1: Selección de archivo XML xml_frame = tk.LabelFrame( main_frame, text=" 📄 Archivo XML de Origen ", font=("Arial", 11, "bold"), pady=10, ) xml_frame.pack(fill="x", pady=(0, 15)) xml_info_label = tk.Label( xml_frame, text="Selecciona el archivo XML de Siemens TIA Portal que deseas convertir:", font=("Arial", 9), fg="#34495e", ) xml_info_label.pack(anchor="w", padx=10, pady=(0, 5)) xml_entry_frame = tk.Frame(xml_frame) xml_entry_frame.pack(fill="x", padx=10, pady=(0, 10)) self.xml_entry = tk.Entry( xml_entry_frame, textvariable=self.source_xml_path, font=("Arial", 9), state="readonly", bg="#ecf0f1", ) self.xml_entry.pack(side="left", fill="x", expand=True, padx=(0, 10)) xml_browse_btn = tk.Button( xml_entry_frame, text="📁 Examinar...", command=self.browse_xml_file, bg="#3498db", fg="white", font=("Arial", 9, "bold"), relief="flat", padx=15, ) xml_browse_btn.pack(side="right") # Sección 2: Selección de directorio de salida dest_frame = tk.LabelFrame( main_frame, text=" 📂 Directorio de Destino ", font=("Arial", 11, "bold"), pady=10, ) dest_frame.pack(fill="x", pady=(0, 15)) dest_info_label = tk.Label( dest_frame, text="Selecciona el directorio donde se guardará el archivo SCL generado:", font=("Arial", 9), fg="#34495e", ) dest_info_label.pack(anchor="w", padx=10, pady=(0, 5)) dest_entry_frame = tk.Frame(dest_frame) dest_entry_frame.pack(fill="x", padx=10, pady=(0, 10)) self.dest_entry = tk.Entry( dest_entry_frame, textvariable=self.dest_directory, font=("Arial", 9), state="readonly", bg="#ecf0f1", ) self.dest_entry.pack(side="left", fill="x", expand=True, padx=(0, 10)) dest_browse_btn = tk.Button( dest_entry_frame, text="📁 Examinar...", command=self.browse_dest_directory, bg="#e67e22", fg="white", font=("Arial", 9, "bold"), relief="flat", padx=15, ) dest_browse_btn.pack(side="right") # Sección 3: Nombre del archivo de salida filename_frame = tk.LabelFrame( main_frame, text=" 📝 Nombre del Archivo SCL ", font=("Arial", 11, "bold"), pady=10, ) filename_frame.pack(fill="x", pady=(0, 15)) filename_info_label = tk.Label( filename_frame, text="Nombre del archivo SCL de salida (se generará automáticamente):", font=("Arial", 9), fg="#34495e", ) filename_info_label.pack(anchor="w", padx=10, pady=(0, 5)) self.filename_entry = tk.Entry( filename_frame, textvariable=self.dest_filename, font=("Arial", 9), state="readonly", bg="#f8f9fa", ) self.filename_entry.pack(fill="x", padx=10, pady=(0, 10)) # Botón de conversión convert_btn = tk.Button( main_frame, text="🚀 Convertir XML → SCL", command=self.start_conversion, bg="#27ae60", fg="white", font=("Arial", 12, "bold"), relief="flat", pady=10, state="disabled", ) convert_btn.pack(fill="x", pady=20) self.convert_btn = convert_btn # Área de progreso y resultados progress_frame = tk.LabelFrame( main_frame, text=" 📊 Progreso ", font=("Arial", 11, "bold") ) progress_frame.pack(fill="both", expand=True, pady=(0, 20)) # Barra de progreso self.progress_bar = ttk.Progressbar( progress_frame, mode="indeterminate", length=300 ) self.progress_bar.pack(pady=10) # Área de mensajes self.log_text = tk.Text( progress_frame, height=8, font=("Consolas", 9), bg="#2c3e50", fg="#ecf0f1", state="disabled", wrap="word", ) self.log_text.pack(fill="both", expand=True, padx=10, pady=(0, 10)) # Scrollbar para el área de mensajes scrollbar = tk.Scrollbar(self.log_text) scrollbar.pack(side="right", fill="y") self.log_text.config(yscrollcommand=scrollbar.set) scrollbar.config(command=self.log_text.yview) # Vincular eventos para actualizar botón de conversión self.source_xml_path.trace("w", self.update_convert_button) self.dest_directory.trace("w", self.update_convert_button) def log_message(self, message, color="#ecf0f1"): """Agregar mensaje al área de log""" self.log_text.config(state="normal") timestamp = time.strftime("%H:%M:%S") self.log_text.insert("end", f"[{timestamp}] {message}\n") self.log_text.config(state="disabled") self.log_text.see("end") # Scroll automático self.root.update() def browse_xml_file(self): """Abrir cuadro de diálogo para seleccionar archivo XML""" filename = filedialog.askopenfilename( title="Seleccionar archivo XML de Siemens TIA Portal", filetypes=[("Archivos XML", "*.xml"), ("Todos los archivos", "*.*")], initialdir=self.script_dir, ) if filename: self.source_xml_path.set(filename) # Generar nombre de archivo de salida automáticamente base_name = os.path.splitext(os.path.basename(filename))[0] self.dest_filename.set(f"{base_name}.scl") self.log_message( f"✓ Archivo XML seleccionado: {os.path.basename(filename)}" ) def browse_dest_directory(self): """Abrir cuadro de diálogo para seleccionar directorio de destino""" directory = filedialog.askdirectory( title="Seleccionar directorio de destino", initialdir=self.script_dir ) if directory: self.dest_directory.set(directory) self.log_message(f"✓ Directorio de destino seleccionado: {directory}") def update_convert_button(self, *args): """Actualizar estado del botón de conversión""" if self.source_xml_path.get() and self.dest_directory.get(): self.convert_btn.config(state="normal") else: self.convert_btn.config(state="disabled") def start_conversion(self): """Iniciar proceso de conversión en un hilo separado""" # Deshabilitar botón durante conversión self.convert_btn.config(state="disabled", text="🔄 Convirtiendo...") self.progress_bar.start() # Ejecutar conversión en hilo separado para no bloquear UI conversion_thread = threading.Thread(target=self.run_conversion) conversion_thread.daemon = True conversion_thread.start() def run_conversion(self): """Ejecutar el proceso de conversión""" try: # Preparar rutas source_xml = self.source_xml_path.get() dest_dir = self.dest_directory.get() dest_filename = self.dest_filename.get() dest_scl = os.path.join(dest_dir, dest_filename) self.log_message("=" * 50) self.log_message("🚀 INICIANDO CONVERSIÓN XML → SCL") self.log_message("=" * 50) self.log_message(f"📄 Archivo origen: {os.path.basename(source_xml)}") self.log_message(f"📂 Destino: {dest_scl}") self.log_message("") # Verificar que x0_main.py existe if not os.path.exists(self.x0_main_path): raise FileNotFoundError( f"No se encontró x0_main.py en: {self.x0_main_path}" ) # Ejecutar x0_main.py en modo simple self.log_message("🔧 Ejecutando convertidor...") cmd = [ sys.executable, self.x0_main_path, "--source-xml", source_xml, "--dest-scl", dest_scl, ] # Ejecutar proceso process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8", cwd=self.script_dir, ) # Leer salida línea por línea while True: output = process.stdout.readline() if output == "" and process.poll() is not None: break if output: self.log_message(output.strip()) # Obtener código de salida return_code = process.poll() # Leer errores si los hay stderr_output = process.stderr.read() if stderr_output: self.log_message("⚠️ ADVERTENCIAS/ERRORES:") for line in stderr_output.strip().split("\n"): if line.strip(): self.log_message(f" {line.strip()}") # Verificar resultado if return_code == 0 and os.path.exists(dest_scl): self.log_message("") self.log_message("=" * 50) self.log_message("✅ CONVERSIÓN COMPLETADA EXITOSAMENTE") self.log_message("=" * 50) self.log_message(f"📁 Archivo generado: {dest_scl}") # Preguntar si abrir el directorio self.root.after(0, self.show_success_dialog, dest_scl) else: self.log_message("") self.log_message("=" * 50) self.log_message("❌ ERROR EN LA CONVERSIÓN") self.log_message("=" * 50) self.root.after( 0, self.show_error_dialog, "La conversión falló. Revisa los mensajes de error arriba.", ) except Exception as e: self.log_message(f"❌ ERROR INESPERADO: {str(e)}") self.root.after(0, self.show_error_dialog, f"Error inesperado: {str(e)}") finally: # Restaurar UI self.root.after(0, self.restore_ui) def restore_ui(self): """Restaurar estado de la UI después de conversión""" self.progress_bar.stop() self.convert_btn.config(state="normal", text="🚀 Convertir XML → SCL") def show_success_dialog(self, dest_scl): """Mostrar diálogo de éxito""" result = messagebox.askyesno( "Conversión Exitosa", f"✅ La conversión se completó exitosamente.\n\n" f"Archivo generado:\n{dest_scl}\n\n" f"¿Deseas abrir el directorio de destino?", icon="question", ) if result: # Abrir directorio en el explorador de Windows dest_dir = os.path.dirname(dest_scl) os.startfile(dest_dir) def show_error_dialog(self, error_message): """Mostrar diálogo de error""" messagebox.showerror( "Error en Conversión", f"❌ Error durante la conversión:\n\n{error_message}\n\n" f"Revisa los mensajes de log para más detalles.", ) def run(self): """Ejecutar la aplicación""" # Mensaje inicial self.log_message("🎯 XML→SCL Converter iniciado") self.log_message( "👆 Selecciona un archivo XML y directorio de destino para comenzar" ) # Centrar ventana self.root.eval("tk::PlaceWindow . center") # Iniciar loop principal self.root.mainloop() def main(): """Función principal""" try: # Verificar que tkinter esté disponible app = XMLtoSCLConverter() app.run() except ImportError: print( "Error: tkinter no está disponible. Este script requiere una instalación de Python con tkinter.", file=sys.stderr, ) sys.exit(1) except Exception as e: print(f"Error inesperado: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()