ParamManagerScripts/backend/script_groups/XML Parser to SCL/x8_manual_gui.py

437 lines
14 KiB
Python

"""
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()