diff --git a/App.xaml b/App.xaml index bd9f193..4b8a7f4 100644 --- a/App.xaml +++ b/App.xaml @@ -27,6 +27,7 @@ + + /// Converter para calcular el margen de las secciones del tanque + /// basado en los porcentajes de las secciones previas + /// + public class TankSectionMarginConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values == null || values.Length < 2) + return new Thickness(4, 4, 4, 4); + + // Convertir valores a double + if (!TryConvertToDouble(values[values.Length - 1], out double tankSize)) + return new Thickness(4, 4, 4, 4); + + // Calcular la altura total acumulada de las secciones previas + double accumulatedPercentage = 0.0; + for (int i = 0; i < values.Length - 1; i++) // -1 para excluir tankSize + { + if (TryConvertToDouble(values[i], out double percentage)) + { + accumulatedPercentage += percentage; + } + } + + try + { + // Convertir tankSize a píxeles (100 píxeles por metro aproximadamente) + var tankHeightPixels = tankSize * 100.0; + + // Calcular altura acumulada en píxeles + var accumulatedHeightPixels = (accumulatedPercentage / 100.0) * tankHeightPixels; + + // Calcular margen bottom basado en la altura acumulada + var marginBottom = 4.0 + accumulatedHeightPixels; + + return new Thickness(4, 4, 4, marginBottom); + } + catch (Exception) + { + return new Thickness(4, 4, 4, 4); + } + } + + private bool TryConvertToDouble(object value, out double result) + { + result = 0.0; + + if (value == null) + return false; + + // Intentar conversión directa si ya es double + if (value is double d) + { + result = d; + return true; + } + + // Intentar conversión desde float + if (value is float f) + { + result = f; + return true; + } + + // Intentar conversión desde int + if (value is int i) + { + result = i; + return true; + } + + // Intentar parsing desde string + if (value is string s) + { + if (double.TryParse(s.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out result)) + return true; + } + + return false; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException("ConvertBack no está implementado para TankSectionMarginConverter"); + } + } +} diff --git a/MainViewModel.cs b/MainViewModel.cs index ab70b6f..42f60f7 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -62,7 +62,7 @@ namespace CtrEditor private double maxSimExecutionTime = 0; private int simTimingAdaptationCounter = 0; private const int SIM_ADAPTATION_SAMPLES = 5; // Evaluar cada 5 muestras para más responsividad - private const double MIN_SIM_INTERVAL = 8; // Mínimo intervalo simulación (ms) + private const double MIN_SIM_INTERVAL = 10; // Mínimo intervalo simulación (ms) private const double MAX_SIM_INTERVAL = 100; // Máximo intervalo simulación (ms) private const double SIM_BUFFER_TIME = 2; // Buffer de 2ms extra respecto al tiempo real @@ -76,6 +76,7 @@ namespace CtrEditor private readonly System.Timers.Timer _timerPLCUpdate; // Cambiado a System.Timers.Timer para mejor precisión private readonly DispatcherTimer _timerDisplayUpdate; 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 public Canvas MainCanvas; @@ -438,6 +439,13 @@ namespace CtrEditor _timer3DUpdate.Interval = TimeSpan.FromMilliseconds(20); _timer3DUpdate.Tick += OnTick3DUpdate; _timer3DUpdate.Start(); // Iniciar porque la simulación empieza detenida + + // Timer para flush automático del buffer de debug (cada 5 minutos) + _timerDebugFlush = new System.Timers.Timer(); + _timerDebugFlush.Interval = 300000; // 5 minutos = 300,000 ms + _timerDebugFlush.Elapsed += OnDebugFlushTimer; + _timerDebugFlush.AutoReset = true; + _timerDebugFlush.Start(); StartSimulationCommand = new RelayCommand(StartSimulation); StopSimulationCommand = new RelayCommand(StopSimulation); @@ -1270,6 +1278,8 @@ namespace CtrEditor _timerSimulacion?.Stop(); _timerPLCUpdate?.Stop(); _timer3DUpdate?.Stop(); + _timerDisplayUpdate?.Stop(); + _timerDebugFlush?.Stop(); // Desconectar PLC si está conectado if (PLCViewModel?.IsConnected == true) @@ -1333,6 +1343,72 @@ namespace CtrEditor }); } + /// + /// Evento del timer que hace flush automático del buffer de debug para evitar acumulación excesiva + /// + private void OnDebugFlushTimer(object sender, System.Timers.ElapsedEventArgs e) + { + try + { + if (_debugConsoleServer != null) + { + var stats = _debugConsoleServer.GetBufferStats(); + + // Solo hacer flush si el buffer tiene un número significativo de mensajes + if (stats.messageCount > 500) + { + _debugConsoleServer.FlushMessageBuffer(); + Debug.WriteLine($"[Debug Flush Timer] Buffer automático limpiado. Mensajes procesados: {stats.messageCount}"); + } + else + { + Debug.WriteLine($"[Debug Flush Timer] Buffer saludable. Mensajes actuales: {stats.messageCount}"); + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"[Debug Flush Timer] Error durante flush automático: {ex.Message}"); + } + } + + /// + /// Evento del timer para actualización de display (UI) + /// + private void OnDisplayUpdate(object sender, EventArgs e) + { + try + { + // Actualizar elementos de UI que no requieren alta frecuencia + // Este timer corre a 250ms por lo que es adecuado para actualizaciones de UI menos críticas + + // Se puede agregar lógica de actualización de UI aquí si es necesario + } + catch (Exception ex) + { + Debug.WriteLine($"[Display Update Timer] Error durante actualización de display: {ex.Message}"); + } + } + + /// + /// Evento del timer para actualización 3D cuando la simulación está detenida + /// + private void OnTick3DUpdate(object sender, EventArgs e) + { + try + { + // Mantener la visualización 3D actualizada cuando la simulación no está corriendo + if (!IsSimulationRunning && Visualization3DManager != null) + { + Visualization3DManager.SynchronizeWorld(); + } + } + catch (Exception ex) + { + Debug.WriteLine($"[3D Update Timer] Error durante actualización 3D: {ex.Message}"); + } + } + private void AdaptPlcTiming(double executionTime) { lastPlcExecutionTime = executionTime; @@ -1350,13 +1426,13 @@ namespace CtrEditor { // Incrementar intervalo para dar más tiempo newInterval = Math.Min(maxPlcExecutionTime + SIM_PRIORITY_TIME + 5, MAX_PLC_INTERVAL); - Debug.WriteLine($"PLC Timer: Aumentando intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)"); + // Debug.WriteLine($"PLC Timer: Aumentando intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)"); } // Si hemos estado por debajo del umbral, podemos reducir el intervalo else if (maxPlcExecutionTime + SIM_PRIORITY_TIME < currentInterval * 0.7) { newInterval = Math.Max(maxPlcExecutionTime + SIM_PRIORITY_TIME + 2, MIN_PLC_INTERVAL); - Debug.WriteLine($"PLC Timer: Reduciendo intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)"); + // Debug.WriteLine($"PLC Timer: Reduciendo intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)"); } // Aplicar el nuevo intervalo si cambió @@ -1394,20 +1470,20 @@ namespace CtrEditor { // Incrementar intervalo para evitar que el sistema se vuelva inusable newInterval = Math.Min(idealInterval + 1, MAX_SIM_INTERVAL); // +1ms adicional de seguridad - Debug.WriteLine($"Simulación Timer: Aumentando intervalo a {newInterval:F1}ms (ejecución máxima: {maxSimExecutionTime:F1}ms)"); + //Debug.WriteLine($"Simulación Timer: Aumentando intervalo a {newInterval:F1}ms (ejecución máxima: {maxSimExecutionTime:F1}ms)"); } // Si hemos estado muy por debajo del umbral, podemos reducir el intervalo else if (idealInterval < currentInterval * 0.8) { newInterval = Math.Max(idealInterval, MIN_SIM_INTERVAL); - Debug.WriteLine($"Simulación Timer: Reduciendo intervalo a {newInterval:F1}ms (ejecución máxima: {maxSimExecutionTime:F1}ms)"); + //Debug.WriteLine($"Simulación Timer: Reduciendo intervalo a {newInterval:F1}ms (ejecución máxima: {maxSimExecutionTime:F1}ms)"); } // Aplicar el nuevo intervalo si cambió significativamente if (Math.Abs(newInterval - currentInterval) > 0.5) { _timerSimulacion.Interval = newInterval; - Debug.WriteLine($"Simulación Timer: Intervalo actualizado de {currentInterval:F1}ms a {newInterval:F1}ms"); + //Debug.WriteLine($"Simulación Timer: Intervalo actualizado de {currentInterval:F1}ms a {newInterval:F1}ms"); } // Reset para el próximo período de evaluación @@ -1607,23 +1683,6 @@ namespace CtrEditor } } - private void OnDisplayUpdate(object? sender, EventArgs e) - { - if (simSampleCount > 0) - { - SimulationSpeed = accumulatedSimTime / simSampleCount; - accumulatedSimTime = 0; - simSampleCount = 0; - } - - if (plcSampleCount > 0) - { - PlcUpdateSpeed = accumulatedPlcTime / plcSampleCount; - accumulatedPlcTime = 0; - plcSampleCount = 0; - } - } - // Reemplazar la propiedad _multiPropertyEditorWindow por un diccionario private Dictionary _propertyEditorWindows = new(); @@ -1758,16 +1817,6 @@ namespace CtrEditor libraryWindow.Show(); } - private void OnTick3DUpdate(object? sender, EventArgs e) - { - // Solo actualizar Helix3D si la simulación está detenida y la actualización 3D está habilitada - // Cuando la simulación está corriendo, la sincronización se hace en simulationManager.Step() - if (!IsSimulationRunning && Is3DUpdateEnabled && Visualization3DManager != null) - { - Visualization3DManager.SynchronizeWorld(); - } - } - #region Hydraulic Simulation Management /// diff --git a/ObjetosSim/HydraulicComponents/osHydPump.cs b/ObjetosSim/HydraulicComponents/osHydPump.cs index 6e09f56..5595e4e 100644 --- a/ObjetosSim/HydraulicComponents/osHydPump.cs +++ b/ObjetosSim/HydraulicComponents/osHydPump.cs @@ -239,6 +239,47 @@ namespace CtrEditor.ObjetosSim } } + [Category("📊 Estado Actual")] + [DisplayName("Estado Cavitación")] + [Description("Estado actual de cavitación de la bomba")] + [JsonIgnore] + public string CavitationStatus + { + 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)"; + } + } + + [Category("📊 Estado Actual")] + [DisplayName("Estado Detallado")] + [Description("Información detallada del estado operacional")] + [JsonIgnore] + public string DetailedStatus + { + 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"; + } + } + // Propiedades de fluido actual private FluidProperties _currentFluid = new FluidProperties(FluidType.Air); @@ -629,22 +670,8 @@ namespace CtrEditor.ObjetosSim if (SpeedRatio < 0.0) SpeedRatio = 0.0; if (SpeedRatio > 1.0) SpeedRatio = 1.0; - // Verificar condiciones de NPSH si está habilitada la verificación - if (hydraulicSimulationManager.EnableNPSHVerification && IsRunning) - { - var npshAvailable = NPSHAvailable; - var cavitationFactor = CavitationFactor; - - if (cavitationFactor < 0.5) - { - Debug.WriteLine($"⚠️ Bomba {Nombre}: Riesgo de cavitación - Factor={cavitationFactor:F2}, NPSH_disponible={npshAvailable:F2}m"); - } - - if (!CanOperateWithoutCavitation) - { - Debug.WriteLine($"❌ Bomba {Nombre}: NO puede operar sin cavitación - NPSH insuficiente"); - } - } + // 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}"); } diff --git a/ObjetosSim/HydraulicComponents/osHydTank.cs b/ObjetosSim/HydraulicComponents/osHydTank.cs index 91fe4f2..0c60084 100644 --- a/ObjetosSim/HydraulicComponents/osHydTank.cs +++ b/ObjetosSim/HydraulicComponents/osHydTank.cs @@ -834,6 +834,91 @@ namespace CtrEditor.ObjetosSim [JsonIgnore] public double CurrentFluidViscosity => CurrentOutputFluid.Viscosity; + /// + /// Porcentaje de altura para la sección secundaria (abajo) - para display visual + /// + [JsonIgnore] + public double SecondaryDisplayPercentage + { + get + { + var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL; + if (totalVolume == 0) return 0; + return (_secondaryVolumeL / totalVolume) * FillPercentage / 100.0; + } + } + + /// + /// Porcentaje de altura para la sección de mezcla (medio) - para display visual + /// + [JsonIgnore] + public double MixDisplayPercentage + { + get + { + var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL; + if (totalVolume == 0) return 0; + return (_mixingVolumeL / totalVolume) * FillPercentage / 100.0; + } + } + + /// + /// Porcentaje de altura para la sección primaria (arriba) - para display visual + /// + [JsonIgnore] + public double PrimaryDisplayPercentage + { + get + { + var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL; + if (totalVolume == 0) return 0; + return (_primaryVolumeL / totalVolume) * FillPercentage / 100.0; + } + } + + /// + /// Color para la sección secundaria + /// + [JsonIgnore] + public SolidColorBrush SecondaryLevelColor + { + get + { + var colorHex = _secondaryFluid.Color; + var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex); + return new SolidColorBrush(color); + } + } + + /// + /// Color para la sección de mezcla + /// + [JsonIgnore] + public SolidColorBrush MixLevelColor + { + get + { + var mixedFluid = _primaryFluid.MixWith(_secondaryFluid, 0.5); // Mezcla 50-50 + var colorHex = mixedFluid.Color; + var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex); + return new SolidColorBrush(color); + } + } + + /// + /// Color para la sección primaria + /// + [JsonIgnore] + public SolidColorBrush PrimaryLevelColor + { + get + { + var colorHex = _primaryFluid.Color; + var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex); + return new SolidColorBrush(color); + } + } + // Private Methods diff --git a/ObjetosSim/HydraulicComponents/ucHydTank.xaml b/ObjetosSim/HydraulicComponents/ucHydTank.xaml index ba37973..1ceee4e 100644 --- a/ObjetosSim/HydraulicComponents/ucHydTank.xaml +++ b/ObjetosSim/HydraulicComponents/ucHydTank.xaml @@ -34,23 +34,70 @@ RadiusX="5" RadiusY="5"/> - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Services/DebugConsoleServer.cs b/Services/DebugConsoleServer.cs index b9d0fb5..efd38f2 100644 --- a/Services/DebugConsoleServer.cs +++ b/Services/DebugConsoleServer.cs @@ -26,6 +26,11 @@ namespace CtrEditor.Services // Custom TraceListener para capturar Debug.WriteLine private DebugTraceListener _traceListener; + + // Control de buffer para evitar acumulación excesiva + private const int MAX_BUFFER_SIZE = 1000; + private int _messageCount = 0; + private readonly object _bufferLock = new object(); public DebugConsoleServer(int port = 5007) { @@ -175,6 +180,10 @@ namespace CtrEditor.Services { if (_messageQueue.TryDequeue(out string message)) { + lock (_bufferLock) + { + _messageCount = Math.Max(0, _messageCount - 1); + } await BroadcastMessageAsync(message); } else @@ -244,7 +253,60 @@ namespace CtrEditor.Services { if (_isRunning) { - _messageQueue.Enqueue(message); + lock (_bufferLock) + { + _messageQueue.Enqueue(message); + _messageCount++; + + // Si el buffer se está llenando demasiado, hacer un flush automático + if (_messageCount > MAX_BUFFER_SIZE) + { + FlushMessageBuffer(); + } + } + } + } + + /// + /// Limpia el buffer de mensajes para evitar acumulación excesiva + /// + public void FlushMessageBuffer() + { + lock (_bufferLock) + { + // Mantener solo los últimos 100 mensajes + var messagesToKeep = new List(); + var count = 0; + + while (_messageQueue.TryDequeue(out string message) && count < 100) + { + messagesToKeep.Insert(0, message); // Insertar al inicio para mantener orden + count++; + } + + // Limpiar completamente la cola + while (_messageQueue.TryDequeue(out _)) { } + + // Volver a agregar los mensajes que queremos mantener + foreach (var message in messagesToKeep) + { + _messageQueue.Enqueue(message); + } + + _messageCount = messagesToKeep.Count; + + Debug.WriteLine($"[Debug Console Server] Buffer flushed. Messages kept: {_messageCount}"); + } + } + + /// + /// Obtiene estadísticas del buffer de mensajes + /// + public (int messageCount, int maxBufferSize) GetBufferStats() + { + lock (_bufferLock) + { + return (_messageCount, MAX_BUFFER_SIZE); } }