using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace CtrEditor.HydraulicSimulator.Python { /// /// Interoperabilidad con Python usando CPython embebido /// Permite ejecutar scripts de TSNet desde C# /// public static class PythonInterop { #region Python DLL Imports [DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int Py_Initialize(); [DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int Py_Finalize(); [DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int PyRun_SimpleString(string command); [DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyRun_String(string str, int start, IntPtr globals, IntPtr locals); [DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyImport_ImportModule(string name); [DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int Py_IsInitialized(); #endregion #region Configuration /// /// Ruta base de la instalación de Python /// public static string PythonBasePath { get; set; } = @"D:\Proyectos\VisualStudio\CtrEditor\bin\Debug\net8.0-windows8.0\tsnet"; /// /// Ruta al ejecutable de Python /// public static string PythonExecutable => Path.Combine(PythonBasePath, "python.exe"); /// /// Ruta a la DLL de Python /// public static string PythonDll => Path.Combine(PythonBasePath, "python312.dll"); /// /// Indica si Python está inicializado /// public static bool IsInitialized { get; private set; } = false; #endregion #region Initialization /// /// Inicializa el intérprete de Python embebido /// public static bool Initialize() { try { if (IsInitialized) return true; // Verificar que los archivos existan if (!File.Exists(PythonDll)) { Debug.WriteLine($"Error: No se encuentra la DLL de Python en: {PythonDll}"); return false; } // Configurar el path de Python Environment.SetEnvironmentVariable("PYTHONPATH", $"{PythonBasePath};{Path.Combine(PythonBasePath, "Lib")};{Path.Combine(PythonBasePath, "site-packages")}"); // Inicializar Python var result = Py_Initialize(); IsInitialized = Py_IsInitialized() != 0; if (IsInitialized) { // Configurar paths de Python var pathSetup = $@" import sys sys.path.insert(0, r'{PythonBasePath}') sys.path.insert(0, r'{Path.Combine(PythonBasePath, "Lib")}') sys.path.insert(0, r'{Path.Combine(PythonBasePath, "site-packages")}') "; PyRun_SimpleString(pathSetup); Debug.WriteLine("Python inicializado correctamente"); Debug.WriteLine($"PYTHONPATH: {Environment.GetEnvironmentVariable("PYTHONPATH")}"); } else { Debug.WriteLine("Error al inicializar Python"); } return IsInitialized; } catch (Exception ex) { Debug.WriteLine($"Error al inicializar Python: {ex.Message}"); return false; } } /// /// Finaliza el intérprete de Python /// public static void Finalize() { try { if (IsInitialized) { Py_Finalize(); IsInitialized = false; Debug.WriteLine("Python finalizado"); } } catch (Exception ex) { Debug.WriteLine($"Error al finalizar Python: {ex.Message}"); } } #endregion #region Script Execution /// /// Ejecuta un comando Python simple /// public static bool ExecuteCommand(string command) { try { if (!IsInitialized && !Initialize()) return false; var result = PyRun_SimpleString(command); return result == 0; } catch (Exception ex) { Debug.WriteLine($"Error ejecutando comando Python: {ex.Message}"); return false; } } /// /// Ejecuta un script Python desde archivo /// public static bool ExecuteScript(string scriptPath) { try { if (!File.Exists(scriptPath)) { Debug.WriteLine($"Error: Script no encontrado: {scriptPath}"); return false; } var scriptContent = File.ReadAllText(scriptPath); return ExecuteCommand(scriptContent); } catch (Exception ex) { Debug.WriteLine($"Error ejecutando script: {ex.Message}"); return false; } } /// /// Ejecuta un script Python con argumentos usando subprocess /// Útil para scripts más complejos o cuando necesitamos capturar output /// public static async Task ExecuteScriptAsync(string scriptPath, string arguments = "") { try { // Try using cmd.exe wrapper to force proper stream handling var startInfo = new ProcessStartInfo { FileName = "cmd.exe", Arguments = $"/c \"cd /d \"{PythonBasePath}\" && python.exe \"{scriptPath}\" {arguments}\"", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; // Add debugging Debug.WriteLine($"[PythonInterop] Using cmd.exe wrapper for stream capture"); Debug.WriteLine($"[PythonInterop] Command: {startInfo.Arguments}"); using var process = new Process { StartInfo = startInfo }; process.Start(); Debug.WriteLine($"[PythonInterop] Process started, PID: {process.Id}"); // Read streams synchronously var outputTask = process.StandardOutput.ReadToEndAsync(); var errorTask = process.StandardError.ReadToEndAsync(); await process.WaitForExitAsync(); var output = await outputTask; var error = await errorTask; Debug.WriteLine($"[PythonInterop] Process completed"); Debug.WriteLine($"[PythonInterop] Exit Code: {process.ExitCode}"); Debug.WriteLine($"[PythonInterop] Output Length: {output?.Length ?? 0}"); Debug.WriteLine($"[PythonInterop] Error Length: {error?.Length ?? 0}"); Debug.WriteLine($"[PythonInterop] Raw Output: '{output}'"); Debug.WriteLine($"[PythonInterop] Raw Error: '{error}'"); return new PythonExecutionResult { Success = process.ExitCode == 0, Output = output, Error = error, ExitCode = process.ExitCode }; } catch (Exception ex) { Debug.WriteLine($"[PythonInterop] Exception: {ex.Message}"); Debug.WriteLine($"[PythonInterop] Stack trace: {ex.StackTrace}"); return new PythonExecutionResult { Success = false, Error = ex.Message, ExitCode = -1 }; } } #endregion #region TSNet Specific Methods /// /// Verifica si TSNet está disponible /// public static bool IsTSNetAvailable() { try { if (!IsInitialized && !Initialize()) return false; var command = @" try: import tsnet print('TSNet version:', tsnet.__version__) result = True except ImportError as e: print('TSNet not available:', e) result = False "; return ExecuteCommand(command); } catch (Exception ex) { Debug.WriteLine($"Error verificando TSNet: {ex.Message}"); return false; } } /// /// Ejecuta una simulación TSNet básica /// public static async Task RunTSNetSimulationAsync(string inpFilePath, string outputDir) { var scriptContent = $@" import tsnet import os try: # Crear directorio de salida si no existe os.makedirs(r'{outputDir}', exist_ok=True) # Cargar el modelo wn = tsnet.network.WaterNetworkModel(r'{inpFilePath}') # Configurar simulación transitoria wn.set_time(duration=10.0, dt=0.01) # 10 segundos, dt=0.01s # Ejecutar simulación results = tsnet.simulation.run_transient_simulation(wn, results_dir=r'{outputDir}') print('Simulación completada exitosamente') print(r'Resultados guardados en: {outputDir}') except Exception as e: print(f'Error en simulación TSNet: {{e}}') raise "; var tempScript = Path.Combine(Path.GetTempPath(), "tsnet_simulation.py"); await File.WriteAllTextAsync(tempScript, scriptContent); try { return await ExecuteScriptAsync(tempScript); } finally { if (File.Exists(tempScript)) File.Delete(tempScript); } } #endregion } /// /// Resultado de la ejecución de un script Python /// public class PythonExecutionResult { public bool Success { get; set; } public string Output { get; set; } = ""; public string Error { get; set; } = ""; public int ExitCode { get; set; } public override string ToString() { var sb = new StringBuilder(); sb.AppendLine($"Success: {Success}"); sb.AppendLine($"ExitCode: {ExitCode}"); if (!string.IsNullOrEmpty(Output)) sb.AppendLine($"Output: {Output}"); if (!string.IsNullOrEmpty(Error)) sb.AppendLine($"Error: {Error}"); return sb.ToString(); } } }