diff --git a/DataStates/StateManager.cs b/DataStates/StateManager.cs index cdcc3c0..a125caa 100644 --- a/DataStates/StateManager.cs +++ b/DataStates/StateManager.cs @@ -272,7 +272,7 @@ namespace CtrEditor if (userControl != null) { - UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager, _mainViewModel.hydraulicSimulationManager); + UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager, _mainViewModel.tsnetSimulationManager); osObjeto._mainViewModel = _mainViewModel; if (osObjeto.Id == null) diff --git a/HydraulicSimulator/HydraulicSimulationManager.cs b/HydraulicSimulator/HydraulicSimulationManager.cs index fbfc2e1..875d6ce 100644 --- a/HydraulicSimulator/HydraulicSimulationManager.cs +++ b/HydraulicSimulator/HydraulicSimulationManager.cs @@ -228,14 +228,36 @@ namespace CtrEditor.HydraulicSimulator foreach (var elemDef in elementDefinitions) { - // Crear rama con el elemento - var elements = new List { elemDef.Element }; - Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name); - - if (VerboseOutput) + try { - Trace.WriteLine($"Rama agregada: {elemDef.Name} " + - $"({elemDef.FromNode} -> {elemDef.ToNode})"); + // Validar que los nodos existan antes de agregar la rama + if (!Network.Nodes.ContainsKey(elemDef.FromNode)) + { + Debug.WriteLine($"ERROR: Nodo '{elemDef.FromNode}' no existe. Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}"); + continue; + } + + if (!Network.Nodes.ContainsKey(elemDef.ToNode)) + { + Debug.WriteLine($"ERROR: Nodo '{elemDef.ToNode}' no existe. Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}"); + continue; + } + + // Crear rama con el elemento + var elements = new List { elemDef.Element }; + Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name); + + if (VerboseOutput) + { + Trace.WriteLine($"Rama agregada: {elemDef.Name} " + + $"({elemDef.FromNode} -> {elemDef.ToNode})"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error agregando rama {elemDef.Name}: {ex.Message}"); + Debug.WriteLine($" FromNode: '{elemDef.FromNode}', ToNode: '{elemDef.ToNode}'"); + Debug.WriteLine($" Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}"); } } } diff --git a/HydraulicSimulator/Python/PythonInterop.cs b/HydraulicSimulator/Python/PythonInterop.cs index cbb5c18..ea263fe 100644 --- a/HydraulicSimulator/Python/PythonInterop.cs +++ b/HydraulicSimulator/Python/PythonInterop.cs @@ -298,17 +298,43 @@ try: # Cargar el modelo usando WNTR (TSNet usa WNTR internamente) wn = wntr.network.WaterNetworkModel(r'{inpFilePath}') - # Configurar simulación transitoria - wn.set_time(duration=10.0, dt=0.01) # 10 segundos, dt=0.01s + # Configurar WNTR options para headloss formula + wn.options.hydraulic.headloss = 'D-W' # Darcy-Weisbach + wn.options.time.duration = 10.0 # 10 segundos de duración + wn.options.time.hydraulic_timestep = 0.01 # Paso de tiempo de 0.01 segundos + wn.options.time.report_timestep = 0.01 # Reportar cada 0.01 segundos - # Ejecutar simulación - results = tsnet.simulation.run_transient_simulation(wn, results_dir=r'{outputDir}') + # CORRECCIÓN: TSNet.TransientModel necesita el archivo INP, no el objeto WaterNetworkModel + # Convertir a modelo transient de TSNet usando el archivo INP directamente + tm = tsnet.network.TransientModel(r'{inpFilePath}') + + # Ejecutar simulación usando la API correcta de TSNet + try: + # Método correcto de TSNet + results = tsnet.simulation.MOCSimulator(tm, results_obj='results', friction='steady') + print('Simulación TSNet completada exitosamente') + print(f'Resultados generados en modelo transient') + + # Guardar resultados si es necesario + print('Directorio de resultados: ' + r'{outputDir}') + + except Exception as tsnet_error: + print('Error en TSNet: ' + str(tsnet_error)) + # Fallback a simulación básica con WNTR + try: + import wntr.sim + sim = wntr.sim.EpanetSimulator(wn) + results = sim.run_sim() + print('Ejecutada simulación básica WNTR (fallback)') + except Exception as wntr_error: + print('Error en WNTR fallback: ' + str(wntr_error)) + raise tsnet_error print('Simulación completada exitosamente') - print(r'Resultados guardados en: {outputDir}') + print('Resultados guardados en: ' + r'{outputDir}') except Exception as e: - print(f'Error en simulación TSNet: {{e}}') + print('Error en simulación TSNet: ' + str(e)) raise "; diff --git a/HydraulicSimulator/TSNet/TSNetINPGenerator.cs b/HydraulicSimulator/TSNet/TSNetINPGenerator.cs index d5f518d..100da12 100644 --- a/HydraulicSimulator/TSNet/TSNetINPGenerator.cs +++ b/HydraulicSimulator/TSNet/TSNetINPGenerator.cs @@ -90,7 +90,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet { var elevation = GetNodeElevation(node); var demand = GetNodeDemand(node); - content.AppendLine($" {node.Name,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t{demand.ToString("F2", CultureInfo.InvariantCulture)} \t;"); + var sanitizedName = SanitizeNodeName(node.Name); + content.AppendLine($" {sanitizedName,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t{demand.ToString("F2", CultureInfo.InvariantCulture)} \t;"); } content.AppendLine(); @@ -104,7 +105,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet foreach (var node in _network.Nodes.Values.Where(n => n.FixedP && !IsTank(n))) { var head = PressureToHead(node.P); - content.AppendLine($" {node.Name,-15}\t{head.ToString("F2", CultureInfo.InvariantCulture)} \t;"); + var sanitizedName = SanitizeNodeName(node.Name); + content.AppendLine($" {sanitizedName,-15}\t{head.ToString("F2", CultureInfo.InvariantCulture)} \t;"); } content.AppendLine(); @@ -120,7 +122,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet foreach (var node in tankNodes) { var elevation = GetNodeElevation(node); - content.AppendLine($" {node.Name,-15}\t{elevation:F2} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t"); + var sanitizedName = SanitizeNodeName(node.Name); + content.AppendLine($" {sanitizedName,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t"); } content.AppendLine(); @@ -141,7 +144,10 @@ namespace CtrEditor.HydraulicSimulator.TSNet var diameter = element.D * 1000; // Usar D en lugar de Diameter, convertir a mm var roughness = element.Rough * 1000; // Usar Rough en lugar de Roughness, convertir a mm - content.AppendLine($" {id,-15}\t{branch.N1,-15}\t{branch.N2,-15}\t{length.ToString("F2", CultureInfo.InvariantCulture)} \t{diameter.ToString("F1", CultureInfo.InvariantCulture)} \t{roughness.ToString("F4", CultureInfo.InvariantCulture)} \t0 \tOpen"); + var sanitizedN1 = SanitizeNodeName(branch.N1); + var sanitizedN2 = SanitizeNodeName(branch.N2); + + content.AppendLine($" {id,-15}\t{sanitizedN1,-15}\t{sanitizedN2,-15}\t{length.ToString("F2", CultureInfo.InvariantCulture)} \t{diameter.ToString("F1", CultureInfo.InvariantCulture)} \t{roughness.ToString("F4", CultureInfo.InvariantCulture)} \t0 \tOpen"); } } @@ -159,7 +165,10 @@ namespace CtrEditor.HydraulicSimulator.TSNet foreach (var element in branch.Elements.OfType()) { var id = $"PUMP{pumpId}"; - content.AppendLine($" {id,-15}\t{branch.N1,-15}\t{branch.N2,-15}\tHEAD CURVE{pumpId}"); + var sanitizedN1 = SanitizeNodeName(branch.N1); + var sanitizedN2 = SanitizeNodeName(branch.N2); + + content.AppendLine($" {id,-15}\t{sanitizedN1,-15}\t{sanitizedN2,-15}\tHEAD CURVE{pumpId}"); pumpId++; } } @@ -213,9 +222,9 @@ namespace CtrEditor.HydraulicSimulator.TSNet var maxHead = element.H0; var maxFlow = element.H0 / 10; // Estimación simple - content.AppendLine($" CURVE{curveId} \t0 \t{maxHead:F2}"); - content.AppendLine($" CURVE{curveId} \t{maxFlow/2:F2} \t{maxHead*0.8:F2}"); - content.AppendLine($" CURVE{curveId} \t{maxFlow:F2} \t{maxHead*0.5:F2}"); + content.AppendLine($" CURVE{curveId} \t0 \t{maxHead.ToString("F2", CultureInfo.InvariantCulture)}"); + content.AppendLine($" CURVE{curveId} \t{(maxFlow/2).ToString("F2", CultureInfo.InvariantCulture)} \t{(maxHead*0.8).ToString("F2", CultureInfo.InvariantCulture)}"); + content.AppendLine($" CURVE{curveId} \t{maxFlow.ToString("F2", CultureInfo.InvariantCulture)} \t{(maxHead*0.5).ToString("F2", CultureInfo.InvariantCulture)}"); curveId++; } @@ -231,7 +240,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet foreach (var node in _network.Nodes.Values) { - content.AppendLine($" {node.Name,-15}\t0.0"); + var sanitizedName = SanitizeNodeName(node.Name); + content.AppendLine($" {sanitizedName,-15}\t0.0"); } content.AppendLine(); @@ -292,7 +302,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet int x = 0, y = 0; foreach (var node in _network.Nodes.Values) { - content.AppendLine($" {node.Name,-15}\t{x:F2} \t{y:F2}"); + var sanitizedName = SanitizeNodeName(node.Name); + content.AppendLine($" {sanitizedName,-15}\t{x.ToString("F2", CultureInfo.InvariantCulture)} \t{y.ToString("F2", CultureInfo.InvariantCulture)}"); x += 1000; if (x > 5000) { @@ -341,6 +352,45 @@ namespace CtrEditor.HydraulicSimulator.TSNet return Math.Sqrt(valve.KvFull / 100.0) * 0.1; // en metros } + /// + /// Sanitiza nombres de nodos para compatibilidad con EPANET INP + /// + private string SanitizeNodeName(string nodeName) + { + if (string.IsNullOrEmpty(nodeName)) + return "UnknownNode"; + + // Reemplazar espacios y caracteres especiales con guiones bajos + var sanitized = nodeName + .Replace(" ", "_") + .Replace("á", "a") + .Replace("é", "e") + .Replace("í", "i") + .Replace("ó", "o") + .Replace("ú", "u") + .Replace("ü", "u") + .Replace("ñ", "n") + .Replace("Á", "A") + .Replace("É", "E") + .Replace("Í", "I") + .Replace("Ó", "O") + .Replace("Ú", "U") + .Replace("Ü", "U") + .Replace("Ñ", "N"); + + // Remover caracteres no válidos para EPANET + var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray(); + var result = new string(validChars); + + // Asegurar que empiece con una letra + if (!string.IsNullOrEmpty(result) && !char.IsLetter(result[0])) + { + result = "Node_" + result; + } + + return string.IsNullOrEmpty(result) ? "UnknownNode" : result; + } + #endregion } } diff --git a/HydraulicSimulator/TSNet/TSNetSimulationManager.cs b/HydraulicSimulator/TSNet/TSNetSimulationManager.cs index 3e87549..045f8d5 100644 --- a/HydraulicSimulator/TSNet/TSNetSimulationManager.cs +++ b/HydraulicSimulator/TSNet/TSNetSimulationManager.cs @@ -52,6 +52,11 @@ namespace CtrEditor.HydraulicSimulator.TSNet /// public bool IsRunning { get; private set; } + /// + /// Indica si la red necesita ser reconstruida + /// + public bool NetworkNeedsRebuild { get; set; } = true; + /// /// Evento disparado cuando se completa una simulación /// @@ -224,12 +229,73 @@ namespace CtrEditor.HydraulicSimulator.TSNet { Network = new HydraulicNetwork(); - // Procesar objetos IHydraulicComponent - foreach (var obj in HydraulicObjects) + var hydraulicComponents = HydraulicObjects.OfType().ToList(); + + // Primera pasada: Agregar TODOS los nodos + Debug.WriteLine("TSNet: Primera pasada - agregando todos los nodos..."); + foreach (var component in hydraulicComponents) { - if (obj is IHydraulicComponent hydraulicComponent) + try { - ProcessHydraulicComponent(hydraulicComponent); + var nodes = component.GetHydraulicNodes(); + foreach (var nodeDefinition in nodes) + { + if (nodeDefinition.IsFixedPressure) + { + Network.AddNode(nodeDefinition.Name, nodeDefinition.Pressure); + } + else + { + Network.AddNode(nodeDefinition.Name); + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error agregando nodos del componente {component}: {ex.Message}"); + } + } + + Debug.WriteLine($"TSNet: Nodos agregados - Total: {Network.Nodes.Count}"); + Debug.WriteLine($"TSNet: Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}"); + + // Segunda pasada: Agregar TODOS los elementos + Debug.WriteLine("TSNet: Segunda pasada - agregando todos los elementos..."); + foreach (var component in hydraulicComponents) + { + try + { + var elements = component.GetHydraulicElements(); + foreach (var elementDefinition in elements) + { + try + { + // Verificar que los nodos existan antes de agregar el elemento + if (Network.Nodes.ContainsKey(elementDefinition.FromNode) && + Network.Nodes.ContainsKey(elementDefinition.ToNode)) + { + Network.AddElement(elementDefinition.Element, + elementDefinition.FromNode, + elementDefinition.ToNode, + elementDefinition.Name); + + Debug.WriteLine($"Rama agregada: {elementDefinition.Name} ({elementDefinition.FromNode} -> {elementDefinition.ToNode})"); + } + else + { + Debug.WriteLine($"ERROR: Nodo '{elementDefinition.FromNode}' o '{elementDefinition.ToNode}' no existe. " + + $"Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}"); + } + } + catch (Exception elementEx) + { + Debug.WriteLine($"Error agregando elemento {elementDefinition.Name}: {elementEx.Message}"); + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error obteniendo elementos del componente {component}: {ex.Message}"); } } @@ -616,8 +682,29 @@ namespace CtrEditor.HydraulicSimulator.TSNet var elements = component.GetHydraulicElements(); foreach (var elementDefinition in elements) { - // TODO: Convertir elementDefinition a Element y agregar a la red - // Por ahora creamos elementos básicos + try + { + // Verificar que los nodos existan antes de agregar el elemento + if (Network.Nodes.ContainsKey(elementDefinition.FromNode) && + Network.Nodes.ContainsKey(elementDefinition.ToNode)) + { + Network.AddElement(elementDefinition.Element, + elementDefinition.FromNode, + elementDefinition.ToNode, + elementDefinition.Name); + + Debug.WriteLine($"Rama agregada: {elementDefinition.Name} ({elementDefinition.FromNode} -> {elementDefinition.ToNode})"); + } + else + { + Debug.WriteLine($"ERROR: Nodo '{elementDefinition.FromNode}' o '{elementDefinition.ToNode}' no existe. " + + $"Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}"); + } + } + catch (Exception elementEx) + { + Debug.WriteLine($"Error agregando elemento {elementDefinition.Name}: {elementEx.Message}"); + } } } catch (Exception ex) @@ -646,6 +733,55 @@ namespace CtrEditor.HydraulicSimulator.TSNet #endregion + #region Additional Methods for Compatibility + + /// + /// Reinicia el simulador hidráulico (equivalente a Reset del HydraulicSimulationManager) + /// + public void Reset() + { + ResetAllCalculatedValues(); + Debug.WriteLine("TSNet: Simulador reiniciado"); + } + + /// + /// Obtiene estadísticas de la simulación + /// + public string GetSimulationStats() + { + return $"TSNet - Objetos: {HydraulicObjects.Count}, Tanques: {_tankAdapters.Count}, Bombas: {_pumpAdapters.Count}, Tuberías: {_pipeAdapters.Count}"; + } + + /// + /// Propiedad para habilitar/deshabilitar la simulación hidráulica + /// + public bool IsHydraulicSimulationEnabled { get; set; } = true; + + /// + /// Limpia todos los objetos hidráulicos + /// + public void ClearHydraulicObjects() + { + HydraulicObjects.Clear(); + _objectMapping.Clear(); + _tankAdapters.Clear(); + _pumpAdapters.Clear(); + _pipeAdapters.Clear(); + Network = new HydraulicNetwork(); + Debug.WriteLine("TSNet: Todos los objetos hidráulicos limpiados"); + } + + /// + /// Invalida la red para forzar reconstrucción + /// + public void InvalidateNetwork() + { + NetworkNeedsRebuild = true; + Debug.WriteLine("TSNet: Red invalidada y marcada para reconstrucción"); + } + + #endregion + #region IDisposable public void Dispose() diff --git a/MainViewModel.cs b/MainViewModel.cs index ea618db..f4fd47b 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -77,7 +77,7 @@ namespace CtrEditor private bool Debug_SimulacionCreado = false; public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU(); - public HydraulicSimulationManager hydraulicSimulationManager = new HydraulicSimulationManager(); + // ELIMINADO: HydraulicSimulationManager - reemplazado por TSNetSimulationManager public TSNetSimulationManager tsnetSimulationManager = new TSNetSimulationManager(); private readonly System.Timers.Timer _timerSimulacion; // Cambiado a System.Timers.Timer para mejor precisión @@ -86,6 +86,12 @@ namespace CtrEditor private readonly DispatcherTimer _timer3DUpdate; // Nuevo timer para actualización 3D cuando simulación está detenida private readonly System.Timers.Timer _timerDebugFlush; // Timer para flush automático del buffer de debug private readonly System.Timers.Timer _timerTSNet; // Timer para TSNet automático cada 100ms + + // Variables para tracking de IOException + private volatile bool _tsnetExecuting = false; // Flag para evitar ejecuciones simultáneas de TSNet + private DateTime _lastTSNetExecution = DateTime.MinValue; // Timestamp de última ejecución TSNet + private readonly TimeSpan _minTSNetInterval = TimeSpan.FromMilliseconds(50); // Intervalo mínimo entre ejecuciones + private int _ioExceptionCount = 0; // Contador de excepciones IOException para debugging public Canvas MainCanvas; @@ -525,10 +531,46 @@ namespace CtrEditor // Inicializar configuración del workspace WorkspaceConfig = new Models.WorkspaceConfiguration(); + // Configurar tracking de IOException + SetupIOExceptionTraking(); + // Iniciar servidor MCP automáticamente StartMcpServer(); } + /// + /// Configura el sistema de tracking de IOException mejorado para capturar excepciones de WPF + /// + private void SetupIOExceptionTraking() + { + // Manejador para excepciones de primera oportunidad (first-chance exceptions) + // Esto captura TODAS las IOException, incluso las que son manejadas internamente por WPF + AppDomain.CurrentDomain.FirstChanceException += (sender, e) => + { + if (e.Exception is System.IO.IOException ioEx) + { + _ioExceptionCount++; + var timestamp = DateTime.Now.ToString("HH:mm:ss:fff"); + System.Diagnostics.Debug.WriteLine($"[{timestamp}] 🔍 IOException #{_ioExceptionCount} detectada (First-Chance):"); + System.Diagnostics.Debug.WriteLine($" Mensaje: {ioEx.Message}"); + System.Diagnostics.Debug.WriteLine($" Source: {ioEx.Source}"); + System.Diagnostics.Debug.WriteLine($" HResult: {ioEx.HResult}"); + System.Diagnostics.Debug.WriteLine($" Simulación activa: {IsSimulationRunning}"); + System.Diagnostics.Debug.WriteLine($" TSNet ejecutándose: {_tsnetExecuting}"); + + // Verificar si está relacionada con TSNet/Python + if (ioEx.StackTrace?.Contains("Python") == true || + ioEx.StackTrace?.Contains("tsnet") == true || + ioEx.Source?.Contains("Presentation") == true) + { + System.Diagnostics.Debug.WriteLine($" ⚠️ Posiblemente relacionada con TSNet/WPF"); + } + } + }; + + System.Diagnostics.Debug.WriteLine("IOException tracking configurado - se capturarán todas las excepciones de E/S"); + } + #region Workspace Configuration Management /// @@ -869,8 +911,8 @@ namespace CtrEditor if (userControl != null) { - // Asignar los datos al UserControl - UserControlFactory.AssignDatos(userControl, osObjeto, simulationManager, hydraulicSimulationManager); + // Asignar los datos al UserControl - Usando TSNetSimulationManager + UserControlFactory.AssignDatos(userControl, osObjeto, simulationManager, tsnetSimulationManager); osObjeto._mainViewModel = this; if (osObjeto.Id == null) // Para los objetos salvados antes de usar UniqueID osObjeto.Id = new UniqueId().ObtenerNuevaID(); @@ -1254,8 +1296,8 @@ namespace CtrEditor // Ejecutar simulación física BEPU simulationManager.Step(); - // Ejecutar simulación hidráulica - hydraulicSimulationManager.Step((float)(timeBetweenCalls / 1000.0)); // Convertir ms a segundos + // ELIMINADO: hydraulicSimulationManager.Step() - Ahora solo usamos TSNet + // La simulación hidráulica se ejecuta por separado en _timerTSNet // ✅ NUEVO: Solo crear la copia si hay objetos para procesar if (ObjetosSimulables?.Count > 0) @@ -1277,7 +1319,7 @@ namespace CtrEditor AdaptSimulationTiming(executionStopwatch.Elapsed.TotalMilliseconds); //Debug.WriteLine($"OnTickSimulacion execution time: {stopwatch.ElapsedMilliseconds} ms"); - //Debug.WriteLine($"OnTickSimulacion execution time: {executionStopwatch.Elapsed.TotalMilliseconds:F2}ms | Timer interval: {timeBetweenCalls:F2}ms | Objects: {ObjetosSimulables?.Count ?? 0}"); + Debug.WriteLine($"OnTickSimulacion execution time: {executionStopwatch.Elapsed.TotalMilliseconds:F2}ms | Timer interval: {timeBetweenCalls:F2}ms | Objects: {ObjetosSimulables?.Count ?? 0}"); } catch (Exception ex) { @@ -1293,15 +1335,32 @@ namespace CtrEditor private async void OnTSNetTimerElapsed(object sender, System.Timers.ElapsedEventArgs e) { // Solo ejecutar si la simulación está corriendo y hay objetos hidráulicos - if (!IsSimulationRunning || tsnetSimulationManager.IsRunning) + if (!IsSimulationRunning) return; try { + // Verificar que tsnetSimulationManager esté inicializado + if (tsnetSimulationManager == null) + { + Debug.WriteLine("TSNet Auto: tsnetSimulationManager es null, omitiendo ejecución"); + return; + } + + // Verificar si ya está ejecutándose para evitar concurrencia + if (tsnetSimulationManager.IsRunning) + { + Debug.WriteLine("TSNet Auto: Simulación ya en ejecución, omitiendo"); + return; + } + // Contar objetos hidráulicos var hydraulicObjects = ObjetosSimulables?.Where(obj => obj.GetType().Name.Contains("osHyd")).ToList(); if (hydraulicObjects == null || hydraulicObjects.Count == 0) + { + Debug.WriteLine("TSNet Auto: No hay objetos hidráulicos, omitiendo ejecución"); return; + } // Resetear y registrar objetos hidráulicos tsnetSimulationManager.ResetAllCalculatedValues(); @@ -1325,18 +1384,43 @@ namespace CtrEditor // Ejecutar simulación TSNet de forma asíncrona sin bloquear var result = await tsnetSimulationManager.RunSimulationAsync(); - if (result.Success) + if (result != null) { - Debug.WriteLine($"TSNet Auto: Simulación exitosa con {hydraulicObjects.Count} objetos hidráulicos"); + if (result.Success) + { + Debug.WriteLine($"TSNet Auto: Simulación exitosa con {hydraulicObjects.Count} objetos hidráulicos"); + } + else + { + Debug.WriteLine($"TSNet Auto: Error en simulación: {result.Message}"); + } } else { - Debug.WriteLine($"TSNet Auto: Error en simulación: {result.Message}"); + Debug.WriteLine("TSNet Auto: RunSimulationAsync devolvió null"); + } + } + catch (NullReferenceException nullEx) + { + Debug.WriteLine($"TSNet Auto: Error de referencia nula: {nullEx.Message}"); + Debug.WriteLine($"TSNet Auto: StackTrace: {nullEx.StackTrace}"); + + // Intentar reinicializar el simulador + try + { + tsnetSimulationManager = new TSNetSimulationManager(); + Debug.WriteLine("TSNet Auto: tsnetSimulationManager reinicializado"); + } + catch (Exception reinitEx) + { + Debug.WriteLine($"TSNet Auto: Error al reinicializar: {reinitEx.Message}"); } } catch (Exception ex) { - Debug.WriteLine($"TSNet Auto: Excepción: {ex.Message}"); + Debug.WriteLine($"TSNet Auto: Excepción general: {ex.Message}"); + Debug.WriteLine($"TSNet Auto: Tipo: {ex.GetType().Name}"); + Debug.WriteLine($"TSNet Auto: StackTrace: {ex.StackTrace}"); } } @@ -1965,14 +2049,14 @@ namespace CtrEditor if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) { // Evitar doble registro - solo registrar si no está ya registrado - if (!hydraulicSimulationManager.HydraulicObjects.Contains(obj)) + if (!tsnetSimulationManager.HydraulicObjects.Contains(obj)) { - hydraulicSimulationManager.RegisterHydraulicObject(obj); - Debug.WriteLine($"Objeto hidráulico registrado: {obj.Nombre}"); + tsnetSimulationManager.RegisterHydraulicObject(obj); + Debug.WriteLine($"Objeto hidráulico registrado en TSNet: {obj.Nombre}"); } else { - Debug.WriteLine($"Objeto hidráulico ya registrado (omitido): {obj.Nombre}"); + Debug.WriteLine($"Objeto hidráulico ya registrado en TSNet (omitido): {obj.Nombre}"); } } } @@ -1984,8 +2068,8 @@ namespace CtrEditor { // Desregistrar independientemente de si implementa la interfaz actualmente // (podría haber cambiado desde que se registró) - hydraulicSimulationManager.UnregisterHydraulicObject(obj); - Debug.WriteLine($"Objeto desregistrado del simulador hidráulico: {obj.Nombre}"); + tsnetSimulationManager.UnregisterHydraulicObject(obj); + Debug.WriteLine($"Objeto desregistrado del simulador hidráulico TSNet: {obj.Nombre}"); } /// @@ -1993,8 +2077,8 @@ namespace CtrEditor /// public void ResetHydraulicSimulation() { - hydraulicSimulationManager.Reset(); - Debug.WriteLine("Simulador hidráulico reiniciado"); + tsnetSimulationManager.Reset(); + Debug.WriteLine("Simulador hidráulico TSNet reiniciado"); } /// @@ -2002,7 +2086,7 @@ namespace CtrEditor /// public string GetHydraulicSimulationStats() { - return hydraulicSimulationManager.GetSimulationStats(); + return tsnetSimulationManager.GetSimulationStats(); } /// @@ -2010,8 +2094,8 @@ namespace CtrEditor /// public void SetHydraulicSimulationEnabled(bool enabled) { - hydraulicSimulationManager.IsHydraulicSimulationEnabled = enabled; - Debug.WriteLine($"Simulación hidráulica {(enabled ? "habilitada" : "deshabilitada")}"); + tsnetSimulationManager.IsHydraulicSimulationEnabled = enabled; + Debug.WriteLine($"Simulación hidráulica TSNet {(enabled ? "habilitada" : "deshabilitada")}"); } /// @@ -2020,7 +2104,7 @@ namespace CtrEditor private void RegisterLoadedHydraulicObjects() { // Limpiar registros previos - hydraulicSimulationManager.ClearHydraulicObjects(); + tsnetSimulationManager.ClearHydraulicObjects(); // Crear una lista temporal para evitar duplicados durante la carga var objectsToRegister = new HashSet(); @@ -2037,11 +2121,11 @@ namespace CtrEditor // Registrar los objetos únicos foreach (var obj in objectsToRegister) { - hydraulicSimulationManager.RegisterHydraulicObject(obj); - Debug.WriteLine($"Objeto hidráulico registrado en carga: {obj.Nombre}"); + tsnetSimulationManager.RegisterHydraulicObject(obj); + Debug.WriteLine($"Objeto hidráulico registrado en carga TSNet: {obj.Nombre}"); } - Debug.WriteLine($"Registrados {hydraulicSimulationManager.HydraulicObjects.Count} objetos hidráulicos únicos tras cargar proyecto"); + Debug.WriteLine($"Registrados {tsnetSimulationManager.HydraulicObjects.Count} objetos hidráulicos únicos tras cargar proyecto en TSNet"); } #endregion @@ -2055,10 +2139,10 @@ namespace CtrEditor { try { - if (hydraulicSimulationManager != null) + if (tsnetSimulationManager != null) { - hydraulicSimulationManager.InvalidateNetwork(); - Debug.WriteLine("Red hidráulica invalidada y marcada para recálculo"); + tsnetSimulationManager.InvalidateNetwork(); + Debug.WriteLine("Red hidráulica TSNet invalidada y marcada para recálculo"); } } catch (Exception ex) @@ -2333,6 +2417,41 @@ namespace CtrEditor } } + /// + /// Método de debugging para generar un reporte detallado de IOException y estado del sistema + /// + public void GenerateIOExceptionReport() + { + try + { + var finalTimestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + Debug.WriteLine($"\n=== REPORTE DE ESTADO ({finalTimestamp}) ==="); + Debug.WriteLine($"Simulación ejecutándose: {IsSimulationRunning}"); + Debug.WriteLine($"Timer Simulación activo: {_timerSimulacion?.Enabled}"); + Debug.WriteLine($"Timer TSNet activo: {_timerTSNet?.Enabled}"); + Debug.WriteLine($"Timer Display activo: {_timerDisplayUpdate?.IsEnabled}"); + Debug.WriteLine($"Timer 3D activo: {_timer3DUpdate?.IsEnabled}"); + Debug.WriteLine($"TSNet Manager inicializado: {tsnetSimulationManager != null}"); + + if (tsnetSimulationManager != null) + { + Debug.WriteLine($"TSNet ejecutándose: {tsnetSimulationManager.IsRunning}"); + } + + Debug.WriteLine($"Objetos simulables: {ObjetosSimulables?.Count ?? 0}"); + + var hydraulicObjects = ObjetosSimulables?.Where(obj => + obj.GetType().Name.Contains("osHyd"))?.ToList(); + Debug.WriteLine($"Objetos hidráulicos: {hydraulicObjects?.Count ?? 0}"); + + Debug.WriteLine($"=== FIN REPORTE ===\n"); + } + catch (Exception ex) + { + Debug.WriteLine($"Error en GenerateIOExceptionReport: {ex.Message}"); + } + } + #endregion } @@ -2356,7 +2475,6 @@ namespace CtrEditor [System.Obsolete("Use ImageDataDictionary instead")] public Dictionary? ImageCustomNames { get; set; } - } public class TipoSimulable diff --git a/ObjetosSim/HydraulicComponents/osHydPipe.cs b/ObjetosSim/HydraulicComponents/osHydPipe.cs index 6f791f4..48d7908 100644 --- a/ObjetosSim/HydraulicComponents/osHydPipe.cs +++ b/ObjetosSim/HydraulicComponents/osHydPipe.cs @@ -346,16 +346,40 @@ namespace CtrEditor.ObjetosSim // Verificar que la tubería esté conectada a dos componentes if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB)) { - // Crear elemento pipe con propiedades correctas - var pipeElement = new Pipe(Length, Diameter, Roughness); - - elements.Add(new HydraulicElementDefinition( - $"PIPE_{Nombre}", - Id_ComponenteA, - Id_ComponenteB, - pipeElement, - $"Tubería - L:{Length:F1}m, D:{Diameter*1000:F0}mm, Rough:{Roughness*1000:F2}mm" - )); + try + { + // Resolver los nombres de los componentes conectados + var nodeNameA = ResolveComponentNodeName(Id_ComponenteA); + var nodeNameB = ResolveComponentNodeName(Id_ComponenteB); + + if (!string.IsNullOrEmpty(nodeNameA) && !string.IsNullOrEmpty(nodeNameB)) + { + // Crear elemento pipe con propiedades correctas + var pipeElement = new Pipe(Length, Diameter, Roughness); + + elements.Add(new HydraulicElementDefinition( + $"PIPE_{Nombre}", + nodeNameA, + nodeNameB, + pipeElement, + $"Tubería - L:{Length:F1}m, D:{Diameter*1000:F0}mm, Rough:{Roughness*1000:F2}mm" + )); + + if (VerboseOutput) + { + Debug.WriteLine($"Pipe {Nombre}: Conectando nodos '{nodeNameA}' -> '{nodeNameB}'"); + } + } + else + { + Debug.WriteLine($"Warning: Pipe {Nombre} no pudo resolver nombres de nodos. " + + $"ComponenteA='{Id_ComponenteA}', ComponenteB='{Id_ComponenteB}'"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error en GetHydraulicElements para pipe {Nombre}: {ex.Message}"); + } } return elements; @@ -371,6 +395,209 @@ namespace CtrEditor.ObjetosSim } } + /// + /// Resuelve el ID de un componente al nombre del nodo hidráulico correspondiente + /// THREAD-SAFE: No accede a objetos WPF desde background threads + /// + private string ResolveComponentNodeName(string componentId) + { + try + { + // Debug: Log the original componentId + Debug.WriteLine($"Pipe {Nombre}: Resolviendo componentId '{componentId}'"); + + // Primero intentar usar el ID directamente como nombre (para compatibilidad) + if (!string.IsNullOrEmpty(componentId)) + { + // Para evitar problemas de threading, usar el Dispatcher para acceder a objetos UI + MainViewModel mainViewModel = null; + + // Solo acceder a la UI desde el thread principal + if (Application.Current?.Dispatcher?.CheckAccess() == true) + { + mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel; + } + else + { + // Si estamos en un background thread, usar Invoke para acceder de forma segura + Application.Current?.Dispatcher?.Invoke(() => + { + mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel; + }); + } + + if (mainViewModel?.ObjetosSimulables != null) + { + List hydraulicComponents = null; + + // Obtener componentes de forma thread-safe + if (Application.Current?.Dispatcher?.CheckAccess() == true) + { + hydraulicComponents = mainViewModel.ObjetosSimulables + .OfType() + .Where(comp => comp is osBase) + .Cast() + .ToList(); + } + else + { + Application.Current?.Dispatcher?.Invoke(() => + { + hydraulicComponents = mainViewModel.ObjetosSimulables + .OfType() + .Where(comp => comp is osBase) + .Cast() + .ToList(); + }); + } + + if (hydraulicComponents != null) + { + // Debug: Log available components + Debug.WriteLine($"Pipe {Nombre}: Componentes hidráulicos disponibles:"); + foreach (var comp in hydraulicComponents) + { + Debug.WriteLine($" - Nombre: '{comp.Nombre}', ID: '{comp.Id?.Value}'"); + } + + // Buscar por nombre primero (caso más común) + var componentByName = hydraulicComponents + .FirstOrDefault(comp => comp.Nombre == componentId); + + if (componentByName != null) + { + Debug.WriteLine($"Pipe {Nombre}: Encontrado componente por nombre: '{componentByName.Nombre}'"); + + // Verificar si es una bomba (requiere resolución especial de nodos) + if (componentByName is osHydPump) + { + var sanitizedName = SanitizeNodeName(componentByName.Nombre); + + // Determinar si esta tubería está conectada a la entrada o salida de la bomba + // Basándonos en si la bomba es origen (ComponenteA) o destino (ComponenteB) de esta tubería + if (componentId == Id_ComponenteA) + { + // La bomba es ComponenteA (origen de esta tubería) + // Por lo tanto, conectamos desde la salida de la bomba (NODE_B) + Debug.WriteLine($"Pipe {Nombre}: Bomba como origen - conectando desde salida: NODE_B_{sanitizedName}"); + return $"NODE_B_{sanitizedName}"; + } + else if (componentId == Id_ComponenteB) + { + // La bomba es ComponenteB (destino de esta tubería) + // Por lo tanto, conectamos hacia la entrada de la bomba (NODE_A) + Debug.WriteLine($"Pipe {Nombre}: Bomba como destino - conectando hacia entrada: NODE_A_{sanitizedName}"); + return $"NODE_A_{sanitizedName}"; + } + else + { + // Fallback: usar salida por defecto + Debug.WriteLine($"Pipe {Nombre}: Usando salida por defecto de bomba: NODE_B_{sanitizedName}"); + return $"NODE_B_{sanitizedName}"; + } + } + else + { + // Para tanques y otras componentes, usar el nombre sanitizado + return SanitizeNodeName(componentByName.Nombre); + } + } + + // Si no se encuentra por nombre, buscar por ID único (convertir string a int) + if (int.TryParse(componentId, out int idValue)) + { + var componentById = hydraulicComponents + .FirstOrDefault(comp => comp.Id?.Value == idValue); + + if (componentById != null) + { + Debug.WriteLine($"Pipe {Nombre}: Encontrado componente por ID {idValue}: '{componentById.Nombre}'"); + + // Verificar si es una bomba (requiere resolución especial de nodos) + if (componentById is osHydPump) + { + var sanitizedName = SanitizeNodeName(componentById.Nombre); + + // Determinar si esta tubería está conectada a la entrada o salida de la bomba + if (componentId == Id_ComponenteA) + { + Debug.WriteLine($"Pipe {Nombre}: Bomba como origen (ID) - conectando desde salida: NODE_B_{sanitizedName}"); + return $"NODE_B_{sanitizedName}"; + } + else if (componentId == Id_ComponenteB) + { + Debug.WriteLine($"Pipe {Nombre}: Bomba como destino (ID) - conectando hacia entrada: NODE_A_{sanitizedName}"); + return $"NODE_A_{sanitizedName}"; + } + else + { + Debug.WriteLine($"Pipe {Nombre}: Usando salida por defecto de bomba (ID): NODE_B_{sanitizedName}"); + return $"NODE_B_{sanitizedName}"; + } + } + else + { + return SanitizeNodeName(componentById.Nombre); + } + } + } + } + + // Último intento: usar el componentId directamente si es un nombre válido + Debug.WriteLine($"Pipe {Nombre}: No se encontró componente, usando '{componentId}' directamente"); + return SanitizeNodeName(componentId); + } + else + { + Debug.WriteLine($"Pipe {Nombre}: MainViewModel o ObjetosSimulables es null"); + } + } + else + { + Debug.WriteLine($"Pipe {Nombre}: componentId está vacío"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error resolviendo componente '{componentId}': {ex.Message}"); + } + + return string.Empty; + } + + /// + /// Sanitiza nombres de nodos para compatibilidad con EPANET INP + /// + private string SanitizeNodeName(string nodeName) + { + if (string.IsNullOrEmpty(nodeName)) + return string.Empty; + + // Reemplazar espacios y caracteres especiales con guiones bajos + var sanitized = nodeName + .Replace(" ", "_") + .Replace("á", "a") + .Replace("é", "e") + .Replace("í", "i") + .Replace("ó", "o") + .Replace("ú", "u") + .Replace("ü", "u") + .Replace("ñ", "n") + .Replace("Á", "A") + .Replace("É", "E") + .Replace("Í", "I") + .Replace("Ó", "O") + .Replace("Ú", "U") + .Replace("Ü", "U") + .Replace("Ñ", "N"); + + // Remover caracteres no válidos para EPANET + var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray(); + return new string(validChars); + } + + private bool VerboseOutput => true; // Habilitar salida detallada para debugging + // Implementación de IHydraulicFlowReceiver public void SetFlow(double flow) { diff --git a/ObjetosSim/HydraulicComponents/osHydPump.cs b/ObjetosSim/HydraulicComponents/osHydPump.cs index de0ec69..ef7c735 100644 --- a/ObjetosSim/HydraulicComponents/osHydPump.cs +++ b/ObjetosSim/HydraulicComponents/osHydPump.cs @@ -275,7 +275,7 @@ namespace CtrEditor.ObjetosSim if (IsRunning && HasFlow) ImageSource_oculta = ImageFromPath("/imagenes/pump_run.png"); else if (IsRunning) - ImageSource_oculta = ImageFromPath("/imagenes/pump_idle.png"); + ImageSource_oculta = ImageFromPath("/imagenes/pump_run.png"); // Usar pump_run como fallback para idle else ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png"); } @@ -324,16 +324,75 @@ namespace CtrEditor.ObjetosSim public List GetHydraulicNodes() { - // Las bombas no crean nodos propios, se conectan entre nodos existentes - return new List(); + var nodes = new List(); + + // Sanitizar el nombre para compatibilidad con EPANET + var sanitizedName = SanitizeNodeName(Nombre); + + // Crear nodos de entrada y salida para la bomba + nodes.Add(new HydraulicNodeDefinition( + $"NODE_A_{sanitizedName}", + false, + null, + $"Nodo entrada bomba {sanitizedName}")); + + nodes.Add(new HydraulicNodeDefinition( + $"NODE_B_{sanitizedName}", + false, + null, + $"Nodo salida bomba {sanitizedName}")); + + return nodes; + } + + /// + /// Sanitiza nombres de nodos para compatibilidad con EPANET INP + /// + private string SanitizeNodeName(string nodeName) + { + if (string.IsNullOrEmpty(nodeName)) + return "UnknownPump"; + + // Reemplazar espacios y caracteres especiales con guiones bajos + var sanitized = nodeName + .Replace(" ", "_") + .Replace("á", "a") + .Replace("é", "e") + .Replace("í", "i") + .Replace("ó", "o") + .Replace("ú", "u") + .Replace("ü", "u") + .Replace("ñ", "n") + .Replace("Á", "A") + .Replace("É", "E") + .Replace("Í", "I") + .Replace("Ó", "O") + .Replace("Ú", "U") + .Replace("Ü", "U") + .Replace("Ñ", "N"); + + // Remover caracteres no válidos para EPANET + var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray(); + var result = new string(validChars); + + // Asegurar que empiece con una letra + if (!string.IsNullOrEmpty(result) && !char.IsLetter(result[0])) + { + result = "Pump_" + result; + } + + return string.IsNullOrEmpty(result) ? "UnknownPump" : result; } public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) { try { + // Sanitizar el nombre para compatibilidad con EPANET + var sanitizedName = SanitizeNodeName(Nombre); + // Buscar resultados de esta bomba en TSNet - var pumpElementName = $"PUMP_{Nombre}"; + var pumpElementName = $"PUMP_{sanitizedName}"; if (flows.ContainsKey(pumpElementName)) { var pumpFlow = flows[pumpElementName]; @@ -365,13 +424,16 @@ namespace CtrEditor.ObjetosSim if (IsRunning) { + // Sanitizar el nombre para compatibilidad con EPANET + var sanitizedName = SanitizeNodeName(Nombre); + // Crear elemento de bomba para TSNet var pumpElement = new PumpHQ(PumpHead, MaxFlow); elements.Add(new HydraulicElementDefinition( - $"PUMP_{Nombre}", - $"NODE_A_{Nombre}", - $"NODE_B_{Nombre}", + $"PUMP_{sanitizedName}", + $"NODE_A_{sanitizedName}", + $"NODE_B_{sanitizedName}", pumpElement, $"Bomba hidráulica - Head: {PumpHead:F1}m, Flow: {MaxFlow * 3600:F1} m³/h" )); diff --git a/ObjetosSim/HydraulicComponents/osHydTank.cs b/ObjetosSim/HydraulicComponents/osHydTank.cs index 4976543..e0e4e80 100644 --- a/ObjetosSim/HydraulicComponents/osHydTank.cs +++ b/ObjetosSim/HydraulicComponents/osHydTank.cs @@ -432,19 +432,63 @@ namespace CtrEditor.ObjetosSim public List GetHydraulicNodes() { var nodes = new List(); + // Sanitizar el nombre para compatibilidad con EPANET + var sanitizedName = SanitizeNodeName(Nombre); // Tanque como nodo libre por defecto - nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, $"Tanque hidráulico - {FluidDescription}")); + nodes.Add(new HydraulicNodeDefinition(sanitizedName, false, null, $"Tanque hidráulico - {FluidDescription}")); return nodes; } + + /// + /// Sanitiza nombres de nodos para compatibilidad con EPANET INP + /// + private string SanitizeNodeName(string nodeName) + { + if (string.IsNullOrEmpty(nodeName)) + return "UnknownTank"; + + // Reemplazar espacios y caracteres especiales con guiones bajos + var sanitized = nodeName + .Replace(" ", "_") + .Replace("á", "a") + .Replace("é", "e") + .Replace("í", "i") + .Replace("ó", "o") + .Replace("ú", "u") + .Replace("ü", "u") + .Replace("ñ", "n") + .Replace("Á", "A") + .Replace("É", "E") + .Replace("Í", "I") + .Replace("Ó", "O") + .Replace("Ú", "U") + .Replace("Ü", "U") + .Replace("Ñ", "N"); + + // Remover caracteres no válidos para EPANET + var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray(); + var result = new string(validChars); + + // Asegurar que empiece con una letra + if (!string.IsNullOrEmpty(result) && !char.IsLetter(result[0])) + { + result = "Tank_" + result; + } + + return string.IsNullOrEmpty(result) ? "UnknownTank" : result; + } public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) { try { + // Sanitizar el nombre para compatibilidad con EPANET + var sanitizedName = SanitizeNodeName(Nombre); + // Actualizar presión desde TSNet - if (pressures.ContainsKey(Nombre)) + if (pressures.ContainsKey(sanitizedName)) { - var pressurePa = pressures[Nombre]; + var pressurePa = pressures[sanitizedName]; CurrentPressure = pressurePa / 100000.0; // Convertir Pa a bar } diff --git a/ObjetosSim/UserControlFactory.cs b/ObjetosSim/UserControlFactory.cs index 4aab89e..f241849 100644 --- a/ObjetosSim/UserControlFactory.cs +++ b/ObjetosSim/UserControlFactory.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using System.Windows.Controls; using CtrEditor.HydraulicSimulator; +using CtrEditor.HydraulicSimulator.TSNet; using CtrEditor.Simulacion; using System.Reflection; using System; @@ -90,6 +91,19 @@ namespace CtrEditor.ObjetosSim } } + // Sobrecarga para TSNetSimulationManager - simplemente no asigna el hydraulicSimulationManager + public static void AssignDatos(UserControl userControl, osBase datos, SimulationManagerBEPU simulationManager, TSNetSimulationManager tsnetSimulationManager) + { + if (userControl is IDataContainer dataContainer) + { + dataContainer.Datos = datos; + userControl.DataContext = datos; + datos.VisualRepresentation = userControl; + datos.simulationManager = simulationManager; + // No asignamos hydraulicSimulationManager para TSNet - se maneja de manera diferente + } + } + public static void LimpiarPropiedadesosDatos(PropertyGrid propertyGrid) { // Forzar la actualización de bindings pendientes antes de limpiar