437 lines
14 KiB
Python
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()
|