From 3c9c0e2479aefeaae895f9d64cc2a49a0ba26dcc Mon Sep 17 00:00:00 2001 From: Miguel Date: Sun, 7 Sep 2025 17:06:15 +0200 Subject: [PATCH] Refactor hydraulic components: Simplify pump and tank logic, remove unused properties, and enhance flow calculations - Updated osHydPump.cs to replace NPSH and cavitation calculations with a simpler flow check. - Modified pump status display to indicate whether the pump is pumping or not. - Streamlined fluid updates in osHydPump.cs to only occur when there is flow. - Refactored osHydTank.cs to consolidate flow handling and remove unnecessary three-section logic. - Adjusted UI elements in ucHydTank.xaml to reflect changes in flow representation and removed deprecated visual indicators. - Enhanced DebugConsoleServer.cs to improve client connection handling with timeout and error management. --- Documentation/MCP/MCP_LLM_Guide.md | 8 + Documentation/MemoriadeEvolucion.md | 156 +++++++ .../HydraulicSimulationManager.cs | 29 +- ObjetosSim/HydraulicComponents/osHydPipe.cs | 29 +- ObjetosSim/HydraulicComponents/osHydPump.cs | 215 +++------ ObjetosSim/HydraulicComponents/osHydTank.cs | 428 ++++-------------- ObjetosSim/HydraulicComponents/ucHydTank.xaml | 81 +--- Services/DebugConsoleServer.cs | 51 ++- 8 files changed, 403 insertions(+), 594 deletions(-) diff --git a/Documentation/MCP/MCP_LLM_Guide.md b/Documentation/MCP/MCP_LLM_Guide.md index c7d2acc..c007bf6 100644 --- a/Documentation/MCP/MCP_LLM_Guide.md +++ b/Documentation/MCP/MCP_LLM_Guide.md @@ -1,5 +1,7 @@ # CtrEditor MCP Server - LLM Guide +*MCP 2025-06-18 compliant | Compatible: Claude Desktop + Cursor* + ## ⚡ Command Efficiency Tiers ### 🚀 **Ultra-Fast** (Use Liberally) @@ -117,6 +119,11 @@ High-value, low-token patterns: 2. Check CtrEditor is running with `get_ctreditor_status` 3. Verify MCP server is active on port 5006 +### MCP Client Issues +- **Red LED in Cursor**: Proxy now handles all standard MCP methods correctly +- **Connection timeout**: CtrEditor must be running for tool calls to work +- **Build errors**: Use `build_project` to see only error output + ## 💡 Best Practices - Always use `get_simulation_status` before expensive operations @@ -124,3 +131,4 @@ High-value, low-token patterns: - Call `list_objects` only when object data is actually needed - Stop simulation before major structural changes - Use appropriate units: meters, Pascal, m³/s +- Proxy works with both Claude Desktop and Cursor (MCP 2025-06-18) diff --git a/Documentation/MemoriadeEvolucion.md b/Documentation/MemoriadeEvolucion.md index 491664f..b18be3e 100644 --- a/Documentation/MemoriadeEvolucion.md +++ b/Documentation/MemoriadeEvolucion.md @@ -577,3 +577,159 @@ using CtrEditor.HydraulicSimulator; // Para IHydraulicComponent - ✅ **Código limpio** - Eliminadas duplicaciones y mantenida una sola fuente de verdad por funcionalidad - ✅ **Compilación exitosa** - El proyecto compila completamente sin errores +## Corrección del Sistema de Debug Logging para .NET Core/.NET 8 (Enero 2025) + +### Problema Identificado + +El MCP proxy no estaba retornando los logs de la consola de debug de CtrEditor. El análisis reveló que el `DebugConsoleServer` solo escuchaba `Trace.WriteLine`, pero todo el código de CtrEditor usaba `Debug.WriteLine`. En .NET Core/.NET 8, `Debug.Listeners` no existe (solo disponible en .NET Framework), causando errores de compilación. + +### Errores de Compilación + +``` +D:\Proyectos\VisualStudio\CtrEditor\Services\DebugConsoleServer.cs(57,23): error CS0117: 'Debug' no contiene una definición para 'Listeners' +D:\Proyectos\VisualStudio\CtrEditor\Services\DebugConsoleServer.cs(92,27): error CS0117: 'Debug' no contiene una definición para 'Listeners' +``` + +### Solución Implementada + +**1. Corrección del DebugConsoleServer** +- **Antes**: Intentaba registrar `DebugTraceListener` en `Debug.Listeners` (no disponible en .NET Core) +- **Después**: Solo usa `Trace.Listeners` que sí está disponible en .NET Core/.NET 8 +- **Comentarios actualizados**: Explicar que `Debug.WriteLine` no está disponible en .NET Core/.NET 8 + +**2. Migración de Logs Críticos a Trace.WriteLine** +Se cambiaron los logs más importantes del `HydraulicSimulationManager` de `Debug.WriteLine` a `Trace.WriteLine`: +```csharp +// Antes +Debug.WriteLine("HydraulicSimulationManager inicializado"); +Debug.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}"); + +// Después +Trace.WriteLine("HydraulicSimulationManager inicializado"); +Trace.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}"); +``` + +### Logs Migrados a Trace.WriteLine + +- Inicialización del HydraulicSimulationManager +- Errores de convergencia de simulación hidráulica +- Errores generales en HydraulicSimulationManager.Step +- Reinicio de simulación hidráulica +- Creación de bombas y tanques hidráulicos + +### Validación del Sistema + +**Test Completo Ejecutado:** +1. ✅ **Compilación exitosa**: Sin errores de `Debug.Listeners` +2. ✅ **CtrEditor iniciado**: Proceso PID 36724 funcionando +3. ✅ **Debug listener operativo**: Puerto 5007 conectado exitosamente +4. ✅ **Logs capturados**: Buffer conteniendo logs de `DebugConsoleServer` + +**Resultado del Test:** +``` +"matches_found": 3, +"matches": [ + "[Debug Console Server] Conectado al servidor de debug de CtrEditor en puerto 5007", + "[Debug Console Server] Cliente debug conectado", + "[Debug Console Server] Esperando conexión de cliente..." +] +``` + +### Arquitectura Corregida + +**Flujo de Logging .NET Core/.NET 8:** +1. `Trace.WriteLine()` en código de CtrEditor +2. → `DebugTraceListener` registrado en `Trace.Listeners` +3. → `DebugConsoleServer` TCP puerto 5007 +4. → MCP Proxy debug listener +5. → Búsqueda y análisis de logs via MCP + +### Beneficios de la Corrección + +- **Compatibilidad .NET Core**: Sistema funciona en .NET 8 en lugar de solo .NET Framework +- **Logs visibles**: Los logs críticos del sistema hidráulico ahora son accesibles via MCP +- **Debugging mejorado**: Posibilidad de monitorear simulación hidráulica en tiempo real +- **Arquitectura limpia**: Solo usa APIs disponibles en la plataforma target + +## Corrección Crítica del Freeze en I/O Completion Port + +### Problema Identificado +La aplicación se congelaba en `System.Private.CoreLib.dll!System.Threading.PortableThreadPool.IOCompletionPoller.Poll()` debido a que `AcceptTcpClientAsync()` sin `CancellationToken` no puede ser cancelado, causando bloqueos indefinidos en el thread del I/O completion port cuando se intentaba detener el `DebugConsoleServer`. + +**Stack trace del problema:** +``` +System.Private.CoreLib.dll!System.Threading.PortableThreadPool.IOCompletionPoller.Poll() +Interop.Kernel32.GetQueuedCompletionStatusEx(..., Timeout.Infinite, ...) +``` + +### Causa Raíz +```csharp +// PROBLEMA: Sin cancelación en .NET Core/.NET 8 +private async Task AcceptConnectionsAsync(CancellationToken cancellationToken) +{ + while (_isRunning && !cancellationToken.IsCancellationRequested) + { + var tcpClient = await _tcpListener.AcceptTcpClientAsync(); // ⚠️ BLOQUEO INDEFINIDO + } +} +``` + +El método `AcceptTcpClientAsync()` sin parámetros no acepta `CancellationToken` en .NET Core, causando que el thread se quede esperando una conexión TCP indefinidamente. + +### Solución Implementada +Implementación de timeout responsive con `Task.WhenAny()`: + +```csharp +private async Task AcceptConnectionsAsync(CancellationToken cancellationToken) +{ + try + { + while (_isRunning && !cancellationToken.IsCancellationRequested) + { + try + { + // Implementar timeout y cancelación manual para evitar freeze + var acceptTask = _tcpListener.AcceptTcpClientAsync(); + var delayTask = Task.Delay(1000, cancellationToken); // Check every 1s + + var completedTask = await Task.WhenAny(acceptTask, delayTask); + + if (completedTask == acceptTask && !cancellationToken.IsCancellationRequested) + { + var tcpClient = await acceptTask; + _connectedClients.Add(tcpClient); + // Manejar cliente... + } + // Si delayTask completa, continúa el loop para verificar estado + } + catch (ObjectDisposedException) { break; } // TcpListener cerrado + catch (Exception ex) + { + if (!_isRunning) break; + await Task.Delay(1000, cancellationToken); // Esperar antes de reintentar + } + } + } + catch (OperationCanceledException) { /* Cancelación normal */ } +} +``` + +### Validación de la Corrección + +**Test Completo Ejecutado:** +1. ✅ **Compilación exitosa**: Sin errores de compatibilidad +2. ✅ **CtrEditor iniciado**: Proceso PID 42876 funcionando correctamente +3. ✅ **Debug listener iniciado**: Puerto 5007 conectado sin problemas +4. ✅ **Sin freezes**: Aplicación responde a comandos inmediatamente +5. ✅ **Shutdown limpio**: Debug listener y CtrEditor se cierran correctamente + +### Resultados + +- ✅ **Eliminado freeze completo** - No más bloqueos en I/O completion port +- ✅ **Cancelación responsive** - Verificación de estado cada 1 segundo +- ✅ **Shutdown limpio** - Debug listener se detiene correctamente +- ✅ **Estabilidad mejorada** - Aplicación responde a comandos de stop inmediatamente +- ✅ **Manejo robusto de errores** - Captura `ObjectDisposedException` y `OperationCanceledException` + +Esta corrección resuelve un problema crítico de estabilidad que impedía el uso seguro del sistema de debug logging. + diff --git a/HydraulicSimulator/HydraulicSimulationManager.cs b/HydraulicSimulator/HydraulicSimulationManager.cs index 4816a85..e5fb65b 100644 --- a/HydraulicSimulator/HydraulicSimulationManager.cs +++ b/HydraulicSimulator/HydraulicSimulationManager.cs @@ -55,7 +55,7 @@ namespace CtrEditor.HydraulicSimulator public int MaxIterations { get; set; } = 300; public double Tolerance { get; set; } = 1e-3; // Tolerancia más relajada para convergencia inicial public double RelaxationFactor { get; set; } = 0.8; - public bool VerboseOutput { get; set; } = false; // Activado para debugging + public bool VerboseOutput { get; set; } = true; // Activado para debugging /// /// Contador de pasos de simulación para optimizaciones @@ -101,7 +101,7 @@ namespace CtrEditor.HydraulicSimulator GlobalTime = 0.0f; - Debug.WriteLine("HydraulicSimulationManager inicializado"); + Trace.WriteLine("HydraulicSimulationManager inicializado"); } #endregion @@ -418,9 +418,9 @@ namespace CtrEditor.HydraulicSimulator } else { - Debug.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}"); - Debug.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}"); - Debug.WriteLine($" Tolerancia requerida: {Tolerance:E6}"); + Trace.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}"); + Trace.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}"); + Trace.WriteLine($" Tolerancia requerida: {Tolerance:E6}"); // Diagnóstico detallado deshabilitado para mejorar rendimiento /* @@ -452,7 +452,7 @@ namespace CtrEditor.HydraulicSimulator ErrorMessage = $"Error en simulación hidráulica: {ex.Message}" }; - Debug.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}"); + Trace.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}"); } finally { @@ -660,9 +660,12 @@ namespace CtrEditor.HydraulicSimulator /// private void ApplyResultsToObject(osBase obj) { + Debug.WriteLine($"[HYDRAULIC] ApplyResultsToObject: {obj.Nombre} ({obj.GetType().Name})"); + // Aplicar resultados usando las interfaces hidráulicas if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) { + Debug.WriteLine($"[HYDRAULIC] {obj.Nombre} is IHydraulicComponent with HasHydraulicComponents=true"); // Llamar al método de aplicación de resultados del objeto hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures); } @@ -670,13 +673,21 @@ namespace CtrEditor.HydraulicSimulator // Aplicación específica por tipo de interfaz if (obj is IHydraulicFlowReceiver flowReceiver) { + Debug.WriteLine($"[HYDRAULIC] {obj.Nombre} is IHydraulicFlowReceiver"); // Buscar caudal asociado al objeto string branchName = FindBranchNameForObject(obj); + Debug.WriteLine($"[HYDRAULIC] Branch name for {obj.Nombre}: '{branchName}'"); if (!string.IsNullOrEmpty(branchName) && LastSolutionResult.Flows.ContainsKey(branchName)) { double flow = LastSolutionResult.Flows[branchName]; + Debug.WriteLine($"[HYDRAULIC] Setting flow {flow:F6} m³/s to {obj.Nombre}"); flowReceiver.SetFlow(flow); } + else + { + Debug.WriteLine($"[HYDRAULIC] No flow found for {obj.Nombre} with branch '{branchName}'"); + Debug.WriteLine($"[HYDRAULIC] Available flows: {string.Join(", ", LastSolutionResult.Flows.Keys)}"); + } } if (obj is IHydraulicPressureReceiver pressureReceiver) @@ -791,7 +802,7 @@ namespace CtrEditor.HydraulicSimulator ErrorMessage = "Simulación reiniciada" }; - Debug.WriteLine("Simulación hidráulica reiniciada"); + Trace.WriteLine("Simulación hidráulica reiniciada"); } /// @@ -874,7 +885,7 @@ namespace CtrEditor.HydraulicSimulator HydraulicSimObjects.Add(pump); _networkNeedsRebuild = true; - Debug.WriteLine($"Bomba hidráulica creada: H={pumpHead}m, Q={maxFlow}m³/s"); + Trace.WriteLine($"Bomba hidráulica creada: H={pumpHead}m, Q={maxFlow}m³/s"); return pump; } @@ -896,7 +907,7 @@ namespace CtrEditor.HydraulicSimulator HydraulicSimObjects.Add(tank); _networkNeedsRebuild = true; - Debug.WriteLine($"Tanque hidráulico creado: P={pressure}Pa, Nivel={currentLevel}m"); + Trace.WriteLine($"Tanque hidráulico creado: P={pressure}Pa, Nivel={currentLevel}m"); return tank; } diff --git a/ObjetosSim/HydraulicComponents/osHydPipe.cs b/ObjetosSim/HydraulicComponents/osHydPipe.cs index 3d0d9d1..248a09f 100644 --- a/ObjetosSim/HydraulicComponents/osHydPipe.cs +++ b/ObjetosSim/HydraulicComponents/osHydPipe.cs @@ -565,22 +565,38 @@ namespace CtrEditor.ObjetosSim public override void UpdateGeometryStart() { // Se llama cuando inicia la simulación - crear objeto hidráulico si no existe + Debug.WriteLine($"[DEBUG] {Nombre}: UpdateGeometryStart() - ComponenteA: '{Id_ComponenteA}', ComponenteB: '{Id_ComponenteB}'"); + if (SimHydraulicPipe == null && !string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB)) { + Debug.WriteLine($"[DEBUG] {Nombre}: Creating SimHydraulicPipe..."); SimHydraulicPipe = hydraulicSimulationManager.AddPipe(Length, Diameter, Roughness, Id_ComponenteA, Id_ComponenteB); - SimHydraulicPipe.SimObjectType = "HydraulicPipe"; - SimHydraulicPipe.WpfObject = this; - SimHydraulicPipe.Nombre = Nombre; + if (SimHydraulicPipe != null) + { + SimHydraulicPipe.SimObjectType = "HydraulicPipe"; + SimHydraulicPipe.WpfObject = this; + SimHydraulicPipe.Nombre = Nombre; + Debug.WriteLine($"[DEBUG] {Nombre}: SimHydraulicPipe created successfully"); + } + else + { + Debug.WriteLine($"[DEBUG] {Nombre}: FAILED to create SimHydraulicPipe!"); + } } else if (SimHydraulicPipe != null) { // Actualizar propiedades si el objeto ya existe + Debug.WriteLine($"[DEBUG] {Nombre}: Updating existing SimHydraulicPipe"); SimHydraulicPipe.Length = Length; SimHydraulicPipe.Diameter = Diameter; SimHydraulicPipe.Roughness = Roughness; SimHydraulicPipe.ComponenteAId = Id_ComponenteA; SimHydraulicPipe.ComponenteBId = Id_ComponenteB; } + else + { + Debug.WriteLine($"[DEBUG] {Nombre}: Cannot create SimHydraulicPipe - missing connections or already exists"); + } ActualizarGeometrias(); } @@ -631,18 +647,19 @@ namespace CtrEditor.ObjetosSim // Actualizar propiedades desde la simulación hidráulica if (SimHydraulicPipe != null) { + double previousFlow = CurrentFlow; CurrentFlow = SimHydraulicPipe.CurrentFlow; PressureDrop = SimHydraulicPipe.PressureDrop; // DEBUG: Log flujo para diagnosis - if (CurrentFlow != 0.0) + if (CurrentFlow != 0.0 || CurrentFlow != previousFlow) { - Debug.WriteLine($"[DEBUG] {Nombre}: Flow={CurrentFlow:F6} m³/s ({CurrentFlowLMin:F2} L/min)"); + Debug.WriteLine($"[DEBUG] {Nombre}: Flow={CurrentFlow:F6} m³/s ({CurrentFlowLMin:F2} L/min) - Changed from {previousFlow:F6}"); } } else { - Debug.WriteLine($"[DEBUG] {Nombre}: SimHydraulicPipe is NULL!"); + Debug.WriteLine($"[DEBUG] {Nombre}: SimHydraulicPipe is NULL! ComponenteA='{Id_ComponenteA}', ComponenteB='{Id_ComponenteB}'"); } // Actualizar propiedades del fluido cada ciclo diff --git a/ObjetosSim/HydraulicComponents/osHydPump.cs b/ObjetosSim/HydraulicComponents/osHydPump.cs index 5595e4e..f21e94b 100644 --- a/ObjetosSim/HydraulicComponents/osHydPump.cs +++ b/ObjetosSim/HydraulicComponents/osHydPump.cs @@ -180,85 +180,22 @@ namespace CtrEditor.ObjetosSim public double CurrentFlowLMin => CurrentFlow * 60000.0; // m³/s a L/min [Category("📊 Estado Actual")] - [DisplayName("NPSH Disponible")] - [Description("NPSH disponible calculado (m)")] + [DisplayName("Tiene Flujo")] + [Description("Indica si hay flujo en la bomba")] [JsonIgnore] - public double NPSHAvailable - { - get - { - var (inletNodeName, _) = GetConnectedNodeNames(); - if (!string.IsNullOrEmpty(inletNodeName) && - hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true) - { - var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName]; - var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo - return pump.CalculateNPSHAvailable(suctionPressure, hydraulicSimulationManager.SimulationFluid); - } - return 0.0; - } - } + public bool HasFlow => Math.Abs(CurrentFlow) > 1e-6; [Category("📊 Estado Actual")] - [DisplayName("Factor Cavitación")] - [Description("Factor de cavitación (1=sin cavitación, 0=cavitación total)")] + [DisplayName("Estado")] + [Description("Estado actual de la bomba")] [JsonIgnore] - public double CavitationFactor - { - get - { - var (inletNodeName, _) = GetConnectedNodeNames(); - if (!string.IsNullOrEmpty(inletNodeName) && - hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true) - { - var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName]; - var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo - return pump.GetCavitationFactor(suctionPressure, hydraulicSimulationManager.SimulationFluid); - } - return 1.0; - } - } - - [Category("📊 Estado Actual")] - [DisplayName("Puede Operar")] - [Description("Indica si la bomba puede operar sin cavitación")] - [JsonIgnore] - public bool CanOperateWithoutCavitation - { - get - { - var (inletNodeName, _) = GetConnectedNodeNames(); - if (!string.IsNullOrEmpty(inletNodeName) && - hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true) - { - var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName]; - var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo - return pump.CanOperateWithoutCavitation(suctionPressure, hydraulicSimulationManager.SimulationFluid); - } - return false; - } - } - - [Category("📊 Estado Actual")] - [DisplayName("Estado Cavitación")] - [Description("Estado actual de cavitación de la bomba")] - [JsonIgnore] - public string CavitationStatus + public string PumpStatus { get { if (!IsRunning) return "Bomba detenida"; - - var factor = CavitationFactor; - var canOperate = CanOperateWithoutCavitation; - var npshAvailable = NPSHAvailable; - - if (canOperate && factor >= 0.9) - return "✅ Operación normal"; - else if (factor >= 0.5) - return $"⚠️ Riesgo cavitación (Factor: {factor:F2})"; - else - return $"❌ NPSH insuficiente ({npshAvailable:F2}m)"; + if (HasFlow) return "✅ Bombeando"; + return "⚠️ Sin flujo"; } } @@ -270,13 +207,9 @@ namespace CtrEditor.ObjetosSim { get { - if (!IsRunning) return "Bomba detenida"; - - var factor = CavitationFactor; - var npshAvailable = NPSHAvailable; var flow = CurrentFlowLMin; - - return $"NPSH: {npshAvailable:F2}m | Factor: {factor:F2} | Flujo: {flow:F1} L/min"; + var pressure = CurrentPressureBar; + return $"Flujo: {flow:F1} L/min | Presión: {pressure:F1} bar | {PumpStatus}"; } } @@ -414,23 +347,38 @@ namespace CtrEditor.ObjetosSim try { - // Buscar el componente conectado en la succión (entrada de la bomba) - var suctionComponent = FindSuctionComponent(); - - if (suctionComponent is osHydTank tank) + // Solo actualizar fluido si hay flujo + if (HasFlow) { - var sourceFluid = tank.CurrentOutputFluid; - if (sourceFluid != null) + // Buscar el componente conectado en la succión + var suctionComponent = FindSuctionComponent(); + + if (suctionComponent is osHydTank tank) { - _currentFluid = sourceFluid.Clone(); + var sourceFluid = tank.CurrentOutputFluid; + if (sourceFluid != null) + { + _currentFluid = sourceFluid.Clone(); + OnPropertyChanged(nameof(CurrentFluidType)); + OnPropertyChanged(nameof(CurrentFluidDescription)); + OnPropertyChanged(nameof(ViscosityEffect)); + OnPropertyChanged(nameof(EffectiveFlowLMin)); + OnPropertyChanged(nameof(FluidTemperature)); + OnPropertyChanged(nameof(FluidDensity)); + + // Actualizar color de la bomba según el fluido + UpdatePumpColorFromFluid(); + } + } + } + else + { + // Si no hay flujo, mantener aire como fluido + if (_currentFluid.Type != FluidType.Air) + { + _currentFluid = new FluidProperties(FluidType.Air); OnPropertyChanged(nameof(CurrentFluidType)); OnPropertyChanged(nameof(CurrentFluidDescription)); - OnPropertyChanged(nameof(ViscosityEffect)); - OnPropertyChanged(nameof(EffectiveFlowLMin)); - OnPropertyChanged(nameof(FluidTemperature)); - OnPropertyChanged(nameof(FluidDensity)); - - // Actualizar color de la bomba según el fluido UpdatePumpColorFromFluid(); } } @@ -450,36 +398,24 @@ namespace CtrEditor.ObjetosSim return; } - // Si no hay flujo, verificar cavitación primero - if (Math.Abs(CurrentFlow) < 1e-6) + // Color basado solo en si hay flujo o no + if (HasFlow) { - if (hydraulicSimulationManager?.EnableNPSHVerification == true) + // Si hay flujo, usar color del fluido o verde por defecto + try { - var cavitationFactor = CavitationFactor; - if (cavitationFactor < 0.1) - ColorButton_oculto = Brushes.Red; // Cavitación severa - else if (cavitationFactor < 0.5) - ColorButton_oculto = Brushes.Orange; // Riesgo de cavitación - else - ColorButton_oculto = Brushes.Yellow; // Standby + var fluidColorHex = _currentFluid.Color; + var fluidColor = (Color)System.Windows.Media.ColorConverter.ConvertFromString(fluidColorHex); + ColorButton_oculto = new SolidColorBrush(fluidColor); } - else + catch { - ColorButton_oculto = Brushes.Yellow; // Standby + ColorButton_oculto = Brushes.Green; // Color por defecto si hay flujo } - return; } - - // Colorear según tipo de fluido bombeado - try + else { - var fluidColorHex = _currentFluid.Color; - var fluidColor = (Color)System.Windows.Media.ColorConverter.ConvertFromString(fluidColorHex); - ColorButton_oculto = new SolidColorBrush(fluidColor); - } - catch - { - ColorButton_oculto = Brushes.Green; // Color por defecto si hay flujo + ColorButton_oculto = Brushes.Yellow; // Sin flujo (standby o sin succión) } } @@ -527,14 +463,17 @@ namespace CtrEditor.ObjetosSim CurrentPressure = SimHydraulicPump.CurrentPressure; } - // Actualizar propiedades del fluido cada ciclo + // Actualizar propiedades del fluido basado en si hay flujo UpdateFluidFromSuction(); // Actualizar propiedades de UI OnPropertyChanged(nameof(CurrentFlowLMin)); OnPropertyChanged(nameof(EffectiveFlowLMin)); + OnPropertyChanged(nameof(HasFlow)); + OnPropertyChanged(nameof(PumpStatus)); + OnPropertyChanged(nameof(DetailedStatus)); - // Actualizar el color según el estado (ya se hace en UpdatePumpColorFromFluid) + // Actualizar el color según el flujo if (!IsRunning) { ColorButton_oculto = Brushes.Gray; // Bomba apagada @@ -611,36 +550,17 @@ namespace CtrEditor.ObjetosSim return elements; } - // Crear bomba con parámetros actuales - Element pump; - if (hydraulicSimulationManager.EnableNPSHVerification) - { - // Usar bomba con verificación de NPSH - pump = new PumpHQWithSuctionCheck( - h0: PumpHead, - q0: MaxFlow, - speedRel: effectiveSpeed, - direction: PumpDirection, - enableNpshCheck: true - ); - } - else - { - // Usar bomba estándar - pump = new PumpHQ( - h0: PumpHead, - q0: MaxFlow, - speedRel: effectiveSpeed, - direction: PumpDirection - ); - } + // Crear bomba estándar - el solver manejará las limitaciones de flujo + var pump = new PumpHQ( + h0: PumpHead, + q0: MaxFlow, + speedRel: effectiveSpeed, + direction: PumpDirection + ); - // Asignar nombres de nodos para verificación de NPSH - if (pump is PumpHQ pumpHQ) - { - pumpHQ.InletNodeName = inletNode; - pumpHQ.OutletNodeName = outletNode; - } + // Asignar nombres de nodos + pump.InletNodeName = inletNode; + pump.OutletNodeName = outletNode; var pumpElement = new HydraulicElementDefinition( $"{Nombre}_Pump", @@ -661,7 +581,7 @@ namespace CtrEditor.ObjetosSim public void UpdateHydraulicProperties() { - // Aquí se pueden hacer validaciones o ajustes antes de la simulación + // Validaciones simples antes de la simulación if (!IsRunning) { SpeedRatio = 0.0; @@ -670,10 +590,7 @@ namespace CtrEditor.ObjetosSim if (SpeedRatio < 0.0) SpeedRatio = 0.0; if (SpeedRatio > 1.0) SpeedRatio = 1.0; - // Las verificaciones de NPSH ahora se muestran a través de propiedades - // Se eliminaron los Debug.WriteLine para evitar saturación del sistema - - //Debug.WriteLine($"Bomba {Nombre}: Velocidad={SpeedRatio:F2}, Funcionando={IsRunning}"); + // El solver se encarga del resto - NPSH, limitaciones de flujo, etc. } public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) diff --git a/ObjetosSim/HydraulicComponents/osHydTank.cs b/ObjetosSim/HydraulicComponents/osHydTank.cs index 0ca2f01..3cf3a7d 100644 --- a/ObjetosSim/HydraulicComponents/osHydTank.cs +++ b/ObjetosSim/HydraulicComponents/osHydTank.cs @@ -30,16 +30,12 @@ namespace CtrEditor.ObjetosSim private double _tankPressure = 1.013; // bar (1 atm por defecto) private double _currentLevelM = 1.0; // m - private double _maxLevelM = 2.0; // m - private double _minLevelM = 0.1; // m private double _crossSectionalArea = 1.0; // m² private double _currentVolumeL = 1000.0; // L private double _maxVolumeL = 2000.0; // L - private double _inletFlow = 0.0; // L/min - private double _outletFlow = 0.0; // L/min + private double _netFlow = 0.0; // L/min (positivo = entrada, negativo = salida) private double _currentPressure = 1.013; // bar private bool _isFixedPressure = true; - private HydraulicTankType _tankType = HydraulicTankType.Intermediate; private double _lastUpdateTime = 0.0; // Propiedades de fluido @@ -51,22 +47,15 @@ namespace CtrEditor.ObjetosSim private MixingState _mixingState = MixingState.PrimaryOnly; private double _currentMixRatio = 0.0; // 0 = solo primario, 1 = solo secundario + // Tank configuration fields + private double _maxLevelM = 2.0; // m - máximo nivel del tanque + private double _minLevelM = 0.0; // m - mínimo nivel del tanque + private string _tankType = "Standard"; // Tipo de tanque + [JsonIgnore] private object _levelLock = new object(); - // Enums - public enum HydraulicTankType - { - [Description("Tanque de succión (inicio del sistema)")] - Suction, - [Description("Tanque intermedio (buffer)")] - Intermediate, - [Description("Tanque de almacenamiento")] - Storage, - [Description("Tanque de proceso")] - Process - } // Properties @@ -84,42 +73,8 @@ namespace CtrEditor.ObjetosSim [property: Name("Color")] private Brush colorButton_oculto = Brushes.LightBlue; - // Three-section tank properties - [ObservableProperty] - [property: Category("🏭 Configuración Tanque")] - [property: Description("Habilitar sistema de tres secciones")] - [property: Name("Tres Secciones")] - private bool enableThreeSection = false; - [ObservableProperty] - [property: Category("🏭 Configuración Tanque")] - [property: Description("Altura de la sección de mezcla (m)")] - [property: Name("Altura Mezcla")] - private double mixingSectionHeight = 0.20; - [ObservableProperty] - [property: Category("📊 Niveles")] - [property: Description("Nivel secundario en metros")] - [property: Name("Nivel Secundario")] - private double secondaryLevelM = 0.0; - - [ObservableProperty] - [property: Category("📊 Niveles")] - [property: Description("Nivel primario en metros")] - [property: Name("Nivel Primario")] - private double primaryLevelM = 0.0; - - [ObservableProperty] - [property: Category("📊 Porcentajes")] - [property: Description("Porcentaje de llenado sección secundaria")] - [property: Name("% Secundario")] - private double secondaryPercentage = 0.0; - - [ObservableProperty] - [property: Category("📊 Porcentajes")] - [property: Description("Porcentaje de llenado sección primaria")] - [property: Name("% Primario")] - private double primaryPercentage = 0.0; @@ -151,11 +106,6 @@ namespace CtrEditor.ObjetosSim // Inicializar estado de mezcla UpdateMixingState(); - // Inicializar sistema de tres secciones - if (EnableThreeSection) - { - ConfigureThreeSectionLevels(); - } // Debug: Confirmar que el constructor se ejecuta Debug.WriteLine($"osHydTank Constructor: Nombre='{Nombre}', Tamaño={Tamano}, ZIndex={zIndex_fromFrames}, IsVisFilter={IsVisFilter}, Lock_movement={Lock_movement}"); @@ -201,7 +151,9 @@ namespace CtrEditor.ObjetosSim { // Convertir bar a Pa para el sistema hidráulico interno var pressurePa = TankPressure * 100000.0; - SimHydraulicTank = hydraulicSimulationManager.AddTank(pressurePa, CurrentLevelM, MaxLevelM, MinLevelM, CrossSectionalArea, IsFixedPressure); + // Usar nivel basado solo en volumen primario para cálculos hidráulicos + var effectiveLevel = PrimaryVolumeL / (CrossSectionalArea * 1000.0); + SimHydraulicTank = hydraulicSimulationManager.AddTank(pressurePa, effectiveLevel, MaxLevelM, MinLevelM, CrossSectionalArea, IsFixedPressure); SimHydraulicTank.SimObjectType = "HydraulicTank"; SimHydraulicTank.WpfObject = this; SimHydraulicTank.Nombre = Nombre; @@ -210,7 +162,9 @@ namespace CtrEditor.ObjetosSim { // Actualizar propiedades si el objeto ya existe (convertir bar a Pa) SimHydraulicTank.TankPressure = TankPressure * 100000.0; - SimHydraulicTank.CurrentLevel = CurrentLevelM; + // Usar nivel basado solo en volumen primario para cálculos hidráulicos + var effectiveLevel = PrimaryVolumeL / (CrossSectionalArea * 1000.0); + SimHydraulicTank.CurrentLevel = effectiveLevel; SimHydraulicTank.MaxLevel = MaxLevelM; SimHydraulicTank.MinLevel = MinLevelM; SimHydraulicTank.CrossSectionalArea = CrossSectionalArea; @@ -224,8 +178,7 @@ namespace CtrEditor.ObjetosSim if (SimHydraulicTank != null) { // Convertir de m³/s a L/min - InletFlow = SimHydraulicTank.InletFlow * 60000.0; - OutletFlow = SimHydraulicTank.OutletFlow * 60000.0; + _netFlow = (SimHydraulicTank.InletFlow - SimHydraulicTank.OutletFlow) * 60000.0; // Convertir de Pa a bar CurrentPressure = SimHydraulicTank.CurrentPressure / 100000.0; @@ -246,21 +199,6 @@ namespace CtrEditor.ObjetosSim // Properties - Configuration - [Category("🏗️ Configuración del Tanque")] - [DisplayName("Tipo de tanque")] - [Description("Función del tanque en el sistema hidráulico")] - public HydraulicTankType TankType - { - get => _tankType; - set - { - if (SetProperty(ref _tankType, value)) - { - UpdateTankConfiguration(); - InvalidateHydraulicNetwork(); - } - } - } [Category("🏗️ Configuración del Tanque")] [DisplayName("Área sección transversal")] @@ -303,11 +241,14 @@ namespace CtrEditor.ObjetosSim get => _maxLevelM; set { - if (SetProperty(ref _maxLevelM, Math.Max(0.2, value))) + if (SetProperty(ref _maxLevelM, Math.Max(0.1, value))) { - if (_currentLevelM > _maxLevelM) - CurrentLevelM = _maxLevelM; + // Asegurar que el nivel mínimo no sea mayor que el máximo + if (_minLevelM >= _maxLevelM) + MinLevelM = _maxLevelM * 0.1; // 10% del máximo como mínimo + SafeUpdateVolumeCalculations(); + InvalidateHydraulicNetwork(); } } } @@ -320,15 +261,29 @@ namespace CtrEditor.ObjetosSim get => _minLevelM; set { - if (SetProperty(ref _minLevelM, Math.Max(0.0, Math.Min(value, _maxLevelM - 0.1)))) + if (SetProperty(ref _minLevelM, Math.Max(0.0, value))) { - if (_currentLevelM < _minLevelM) - CurrentLevelM = _minLevelM; + // Asegurar que el nivel mínimo no sea mayor que el máximo + if (_minLevelM >= _maxLevelM) + MaxLevelM = _minLevelM + 0.1; // Al menos 10cm más que el mínimo + SafeUpdateVolumeCalculations(); + InvalidateHydraulicNetwork(); } } } + [Category("🏗️ Configuración del Tanque")] + [DisplayName("Tipo de tanque")] + [Description("Tipo de tanque (Standard, Storage, Process, etc.)")] + public string TankType + { + get => _tankType; + set => SetProperty(ref _tankType, value ?? "Standard"); + } + + + // Properties - Pressure @@ -380,7 +335,7 @@ namespace CtrEditor.ObjetosSim EnsureLockInitialized(); lock (_levelLock) { - var clampedLevel = Math.Max(_minLevelM, Math.Min(_maxLevelM, value)); + var clampedLevel = Math.Max(0.0, value); if (SetProperty(ref _currentLevelM, clampedLevel)) { SafeUpdateVolumeCalculations(); @@ -406,8 +361,8 @@ namespace CtrEditor.ObjetosSim if (SetProperty(ref _currentVolumeL, clampedVolume)) { // Actualizar nivel basado en volumen - _currentLevelM = _minLevelM + (clampedVolume / 1000.0) / _crossSectionalArea; - _currentLevelM = Math.Max(_minLevelM, Math.Min(_maxLevelM, _currentLevelM)); + _currentLevelM = (clampedVolume / 1000.0) / _crossSectionalArea; + _currentLevelM = Math.Max(0.0, _currentLevelM); OnPropertyChanged(nameof(CurrentLevelM)); OnPropertyChanged(nameof(FillPercentage)); UpdateMixingState(); @@ -420,33 +375,23 @@ namespace CtrEditor.ObjetosSim [DisplayName("Porcentaje llenado")] [Description("Porcentaje de llenado del tanque")] [JsonIgnore] - public double FillPercentage => (_currentLevelM - _minLevelM) / (_maxLevelM - _minLevelM) * 100.0; + public double FillPercentage => MaxLevelM > 0 ? (_currentLevelM / MaxLevelM) * 100.0 : 0.0; [Category("📊 Estado Actual")] - [DisplayName("Flujo entrada")] - [Description("Flujo de entrada actual (L/min)")] + [DisplayName("Flujo neto")] + [Description("Flujo neto del tanque (L/min) - positivo=entrada, negativo=salida")] [JsonIgnore] - public double InletFlow + public double NetFlow { - get => _inletFlow; - private set => SetProperty(ref _inletFlow, value); - } - - [Category("📊 Estado Actual")] - [DisplayName("Flujo salida")] - [Description("Flujo de salida actual (L/min)")] - [JsonIgnore] - public double OutletFlow - { - get => _outletFlow; - private set => SetProperty(ref _outletFlow, value); + get => _netFlow; + private set => SetProperty(ref _netFlow, value); } [Category("📊 Estado Actual")] [DisplayName("Balance flujo")] - [Description("Balance de flujo (entrada - salida) (L/min)")] + [Description("Balance de flujo neto (L/min)")] [JsonIgnore] - public double FlowBalance => InletFlow - OutletFlow; + public double FlowBalance => NetFlow; [Category("📊 Estado Actual")] [DisplayName("Presión actual")] @@ -712,7 +657,7 @@ namespace CtrEditor.ObjetosSim if (VerboseLogging()) { Trace.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevelM:F2}m ({FillPercentage:F1}%), " + - $"Flujo_entrada={InletFlow:F2}L/min, Flujo_salida={OutletFlow:F2}L/min, " + + $"Flujo_neto={NetFlow:F2}L/min, " + $"Balance={FlowBalance:F2}L/min, Presión={CurrentPressure:F3}bar ({CurrentPressure * 100000.0:F0}Pa), Fluido={CurrentFluidDescription}"); } } @@ -801,24 +746,6 @@ namespace CtrEditor.ObjetosSim } } - /// - /// Indicador del tipo de tanque - /// - [JsonIgnore] - public string TankTypeIndicator - { - get - { - return TankType switch - { - HydraulicTankType.Suction => "S", - HydraulicTankType.Intermediate => "I", - HydraulicTankType.Storage => "A", - HydraulicTankType.Process => "P", - _ => "T" - }; - } - } /// /// Color del balance de flujo @@ -834,32 +761,7 @@ namespace CtrEditor.ObjetosSim } } - /// - /// Posición Y de la línea de nivel medio (50%) - /// - [JsonIgnore] - public double MidLevelY - { - get - { - var tankHeight = Tamano * 100; // Convertir a píxeles aproximados - return tankHeight * 0.5; // 50% de altura - } - } - /// - /// Posición Y de la línea de nivel mínimo - /// - [JsonIgnore] - public double MinLevelY - { - get - { - var tankHeight = Tamano * 100; // Convertir a píxeles aproximados - var minPercentage = (MinLevelM - MinLevelM) / (MaxLevelM - MinLevelM) * 100; - return tankHeight - (tankHeight * (minPercentage + 10) / 100); // 10% del nivel mínimo - } - } /// /// Indica si el tanque está vacío (contiene aire) @@ -985,6 +887,16 @@ namespace CtrEditor.ObjetosSim if (Alto <= 0) Alto = 0.40f; + // Inicializar configuraciones del tanque + if (_maxLevelM <= 0) + _maxLevelM = 2.0; + + if (_minLevelM < 0 || _minLevelM >= _maxLevelM) + _minLevelM = 0.0; + + if (string.IsNullOrEmpty(_tankType)) + _tankType = "Standard"; + SafeUpdateVolumeCalculations(); SafeUpdateTankPressure(); @@ -1001,8 +913,8 @@ namespace CtrEditor.ObjetosSim try { // Calcular volumen total actual basado en nivel - _currentVolumeL = (_currentLevelM - _minLevelM) * _crossSectionalArea * 1000.0; // convertir m³ a L - _maxVolumeL = (_maxLevelM - _minLevelM) * _crossSectionalArea * 1000.0; + _currentVolumeL = _currentLevelM * _crossSectionalArea * 1000.0; // convertir m³ a L + _maxVolumeL = 2.0 * _crossSectionalArea * 1000.0; // Assuming 2m as max height // Asegurar que los volúmenes de fluidos no excedan el volumen total var totalFluidVolume = _primaryVolumeL + _secondaryVolumeL; @@ -1030,6 +942,13 @@ namespace CtrEditor.ObjetosSim { CurrentPressure = TankPressure; } + else + { + // Calcular presión basada solo en volumen primario (para cálculos hidráulicos) + var primaryLevel = PrimaryVolumeL / (CrossSectionalArea * 1000.0); // Nivel equivalente del volumen primario + var hydrostaticPressure = primaryLevel * 9.81 * CurrentFluidDensity / 100000.0; // Convertir a bar + CurrentPressure = 1.013 + hydrostaticPressure; // Presión atmosférica + hidráulica + } } catch (Exception ex) { @@ -1039,25 +958,6 @@ namespace CtrEditor.ObjetosSim } } - private void UpdateTankConfiguration() - { - // Configuración automática según el tipo de tanque - switch (TankType) - { - case HydraulicTankType.Suction: - // Tanques de succión típicamente a presión atmosférica - if (TankPressure < 0.5) // Si no se ha configurado manualmente - TankPressure = 1.013; // 1 atm - IsFixedPressure = true; - break; - - case HydraulicTankType.Intermediate: - case HydraulicTankType.Storage: - case HydraulicTankType.Process: - // Estos pueden tener presión variable o fija según el proceso - break; - } - } private void UpdateMixingState() { @@ -1140,11 +1040,6 @@ namespace CtrEditor.ObjetosSim // Actualizar volúmenes de fluidos según el estado de mezcla UpdateFluidVolumesFromFlow(volumeChangeL); - // Actualizar sistema de tres secciones si está habilitado - if (EnableThreeSection) - { - UpdateThreeSectionLevels(); - } // Debug para verificar cambios if (VerboseLogging() && Math.Abs(volumeChangeL) > 0.1) @@ -1200,8 +1095,7 @@ namespace CtrEditor.ObjetosSim private void UpdateFlowsFromConnectedPipes(Dictionary flows) { - InletFlow = 0.0; - OutletFlow = 0.0; + NetFlow = 0.0; // Buscar flujos en las ramas que involucren este tanque foreach (var flow in flows) @@ -1210,65 +1104,22 @@ namespace CtrEditor.ObjetosSim var flowValueM3s = flow.Value; // El flujo viene en m³/s var flowValueLmin = flowValueM3s * 60000.0; // Convertir a L/min - // Buscar si esta rama conecta este tanque - if (branchName.Contains("Pump") && HasPumpConnection(branchName, flowValueLmin)) - { - continue; // Ya manejado en HasPumpConnection - } - - // Buscar otras conexiones directas al tanque por nombre + // Buscar conexiones que involucren este tanque if (branchName.Contains(Nombre)) { - if (flowValueLmin > 0) + // Determinar dirección del flujo basado en el nombre de la rama + if (IsFlowTowardsTank(branchName)) { - // Flujo positivo hacia o desde el tanque - if (IsFlowTowardsTank(branchName)) - { - InletFlow += flowValueLmin; - } - else - { - OutletFlow += flowValueLmin; - } + NetFlow += flowValueLmin; // Flujo hacia el tanque (positivo) + } + else + { + NetFlow -= flowValueLmin; // Flujo desde el tanque (negativo) } } } } - private bool HasPumpConnection(string branchName, double flowValueLmin) - { - if (_mainViewModel == null) return false; - - // Buscar si hay una bomba conectada que esté generando este flujo - var connectedPipes = _mainViewModel.ObjetosSimulables - .OfType() - .Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre) - .ToList(); - - foreach (var pipe in connectedPipes) - { - if (pipe.Id_ComponenteA == Nombre) - { - // Esta conexión sale del tanque - if (branchName.Contains(pipe.Id_ComponenteB) && branchName.Contains("Pump")) - { - OutletFlow += Math.Abs(flowValueLmin); - return true; - } - } - else if (pipe.Id_ComponenteB == Nombre) - { - // Esta conexión llega al tanque - if (branchName.Contains(pipe.Id_ComponenteA) && branchName.Contains("Pump")) - { - InletFlow += Math.Abs(flowValueLmin); - return true; - } - } - } - - return false; - } private double GetDeltaTime() { @@ -1292,14 +1143,7 @@ namespace CtrEditor.ObjetosSim private string GetTankDescription() { - return TankType switch - { - HydraulicTankType.Suction => "Tanque de succión", - HydraulicTankType.Intermediate => "Tanque intermedio", - HydraulicTankType.Storage => "Tanque de almacenamiento", - HydraulicTankType.Process => "Tanque de proceso", - _ => "Tanque hidráulico" - }; + return "Tanque hidráulico"; } private bool IsFlowTowardsTank(string branchName) @@ -1377,124 +1221,6 @@ namespace CtrEditor.ObjetosSim - // Three-Section Tank Methods - - /// - /// Obtiene la altura total del tanque - /// - private double GetHeightFromVolume() - { - return Tamano * 100; // Convertir tamaño a píxeles/altura - } - - /// - /// Configura los niveles para el sistema de tres secciones - /// - public void ConfigureThreeSectionLevels() - { - if (!EnableThreeSection) return; - - try - { - lock (_levelLock) - { - var totalHeight = GetHeightFromVolume(); - var mixingHeight = MixingSectionHeight; - - // Validar que la altura de mezcla no sea mayor que la altura total - if (mixingHeight > totalHeight) - { - mixingHeight = totalHeight * 0.3; // Máximo 30% de la altura total - MixingSectionHeight = mixingHeight; - } - - // Calcular niveles basados en la altura actual del líquido - var currentLevel = CurrentLevelM; - - if (currentLevel <= mixingHeight) - { - // Solo sección de mezcla - SecondaryLevelM = 0.0; - PrimaryLevelM = currentLevel; - } - else - { - // Mezcla completa + nivel adicional - SecondaryLevelM = currentLevel - mixingHeight; - PrimaryLevelM = mixingHeight; - } - - UpdateThreeSectionPercentages(); - } - } - catch (Exception ex) - { - Debug.WriteLine($"Error configurando niveles de tres secciones: {ex.Message}"); - } - } - - /// - /// Actualiza los porcentajes de llenado para cada sección - /// - public void UpdateThreeSectionPercentages() - { - if (!EnableThreeSection) return; - - try - { - lock (_levelLock) - { - var totalHeight = GetHeightFromVolume(); - var mixingHeight = MixingSectionHeight; - var secondaryHeight = totalHeight - mixingHeight; - - // Calcular porcentajes - if (mixingHeight > 0) - { - PrimaryPercentage = (PrimaryLevelM / mixingHeight) * 100.0; - PrimaryPercentage = Math.Min(100.0, Math.Max(0.0, PrimaryPercentage)); - } - else - { - PrimaryPercentage = 0.0; - } - - if (secondaryHeight > 0) - { - SecondaryPercentage = (SecondaryLevelM / secondaryHeight) * 100.0; - SecondaryPercentage = Math.Min(100.0, Math.Max(0.0, SecondaryPercentage)); - } - else - { - SecondaryPercentage = 0.0; - } - } - } - catch (Exception ex) - { - Debug.WriteLine($"Error actualizando porcentajes de tres secciones: {ex.Message}"); - } - } - - /// - /// Actualiza los niveles basados en el nivel total actual - /// - public void UpdateThreeSectionLevels() - { - if (!EnableThreeSection) return; - - try - { - lock (_levelLock) - { - ConfigureThreeSectionLevels(); - } - } - catch (Exception ex) - { - Debug.WriteLine($"Error actualizando niveles de tres secciones: {ex.Message}"); - } - } diff --git a/ObjetosSim/HydraulicComponents/ucHydTank.xaml b/ObjetosSim/HydraulicComponents/ucHydTank.xaml index e70729d..2cc8f57 100644 --- a/ObjetosSim/HydraulicComponents/ucHydTank.xaml +++ b/ObjetosSim/HydraulicComponents/ucHydTank.xaml @@ -143,24 +143,6 @@ - - - - - - - - - - - - - - @@ -199,13 +181,6 @@ - - - - @@ -262,70 +237,36 @@ - + - - - - - - - - - - - - - - - - - - - + - - + - - + + - + - - + - - - - - - - - - - - + - diff --git a/Services/DebugConsoleServer.cs b/Services/DebugConsoleServer.cs index efd38f2..656cc0c 100644 --- a/Services/DebugConsoleServer.cs +++ b/Services/DebugConsoleServer.cs @@ -52,6 +52,7 @@ namespace CtrEditor.Services _isRunning = true; // Instalar el trace listener personalizado solo para Trace + // Nota: Debug.Listeners no existe en .NET Core/.NET 8 _traceListener = new DebugTraceListener(this); Trace.Listeners.Add(_traceListener); @@ -84,7 +85,7 @@ namespace CtrEditor.Services _isRunning = false; _cancellationTokenSource?.Cancel(); - // Remover el trace listener + // Remover el trace listener de Trace if (_traceListener != null) { Trace.Listeners.Remove(_traceListener); @@ -116,21 +117,52 @@ namespace CtrEditor.Services { while (_isRunning && !cancellationToken.IsCancellationRequested) { - Debug.WriteLine("[Debug Console Server] Esperando conexión de cliente..."); - var tcpClient = await _tcpListener.AcceptTcpClientAsync(); - _connectedClients.Add(tcpClient); + try + { + // Implementar timeout y cancelación manual para evitar freeze + var acceptTask = _tcpListener.AcceptTcpClientAsync(); + var delayTask = Task.Delay(1000, cancellationToken); // Check every 1s + + var completedTask = await Task.WhenAny(acceptTask, delayTask); + + if (completedTask == acceptTask && !cancellationToken.IsCancellationRequested) + { + var tcpClient = await acceptTask; + _connectedClients.Add(tcpClient); - Debug.WriteLine("[Debug Console Server] Cliente debug conectado"); + Debug.WriteLine("[Debug Console Server] Cliente debug conectado"); - // Manejar cliente en background - _ = Task.Run(async () => await HandleClientAsync(tcpClient, cancellationToken)); + // Manejar cliente en background + _ = Task.Run(async () => await HandleClientAsync(tcpClient, cancellationToken)); + } + else if (cancellationToken.IsCancellationRequested) + { + break; + } + // Si completedTask == delayTask, simplemente continúa el loop para verificar estado + } + catch (ObjectDisposedException) + { + // TcpListener fue cerrado, salir silenciosamente + break; + } + catch (Exception ex) + { + Debug.WriteLine($"[Debug Console Server] Error aceptando conexión: {ex.Message}"); + if (!_isRunning) break; + await Task.Delay(1000, cancellationToken); // Esperar antes de reintentar + } } } + catch (OperationCanceledException) + { + // Cancelación normal, no hacer nada + } catch (Exception ex) { if (_isRunning) { - Debug.WriteLine($"[Debug Console Server] Error aceptando conexión: {ex.Message}"); + Debug.WriteLine($"[Debug Console Server] Error en AcceptConnectionsAsync: {ex.Message}"); } } } @@ -318,8 +350,9 @@ namespace CtrEditor.Services } /// - /// TraceListener personalizado que captura mensajes de Debug.WriteLine + /// TraceListener personalizado que captura mensajes de Trace.WriteLine /// y los envía al servidor de debug console + /// Nota: Debug.WriteLine no está disponible en .NET Core/.NET 8 /// internal class DebugTraceListener : TraceListener {