feat: Add detailed status properties and margin converter for tank sections in hydraulic components
This commit is contained in:
parent
380fb5ba50
commit
322d335edd
1
App.xaml
1
App.xaml
|
@ -27,6 +27,7 @@
|
||||||
<local:UnsavedChangesConverter x:Key="UnsavedChangesConverter"/>
|
<local:UnsavedChangesConverter x:Key="UnsavedChangesConverter"/>
|
||||||
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||||
<converters:TankLevelToHeightConverter x:Key="TankLevelToHeightConverter"/>
|
<converters:TankLevelToHeightConverter x:Key="TankLevelToHeightConverter"/>
|
||||||
|
<converters:TankSectionMarginConverter x:Key="TankSectionMarginConverter"/>
|
||||||
|
|
||||||
<!-- DropShadowEffect para efectos de sombra en texto -->
|
<!-- DropShadowEffect para efectos de sombra en texto -->
|
||||||
<DropShadowEffect x:Key="DropShadowEffect"
|
<DropShadowEffect x:Key="DropShadowEffect"
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace CtrEditor.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converter para calcular el margen de las secciones del tanque
|
||||||
|
/// basado en los porcentajes de las secciones previas
|
||||||
|
/// </summary>
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
MainViewModel.cs
115
MainViewModel.cs
|
@ -62,7 +62,7 @@ namespace CtrEditor
|
||||||
private double maxSimExecutionTime = 0;
|
private double maxSimExecutionTime = 0;
|
||||||
private int simTimingAdaptationCounter = 0;
|
private int simTimingAdaptationCounter = 0;
|
||||||
private const int SIM_ADAPTATION_SAMPLES = 5; // Evaluar cada 5 muestras para más responsividad
|
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 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
|
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 System.Timers.Timer _timerPLCUpdate; // Cambiado a System.Timers.Timer para mejor precisión
|
||||||
private readonly DispatcherTimer _timerDisplayUpdate;
|
private readonly DispatcherTimer _timerDisplayUpdate;
|
||||||
private readonly DispatcherTimer _timer3DUpdate; // Nuevo timer para actualización 3D cuando simulación está detenida
|
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;
|
public Canvas MainCanvas;
|
||||||
|
|
||||||
|
@ -438,6 +439,13 @@ namespace CtrEditor
|
||||||
_timer3DUpdate.Interval = TimeSpan.FromMilliseconds(20);
|
_timer3DUpdate.Interval = TimeSpan.FromMilliseconds(20);
|
||||||
_timer3DUpdate.Tick += OnTick3DUpdate;
|
_timer3DUpdate.Tick += OnTick3DUpdate;
|
||||||
_timer3DUpdate.Start(); // Iniciar porque la simulación empieza detenida
|
_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);
|
StartSimulationCommand = new RelayCommand(StartSimulation);
|
||||||
StopSimulationCommand = new RelayCommand(StopSimulation);
|
StopSimulationCommand = new RelayCommand(StopSimulation);
|
||||||
|
@ -1270,6 +1278,8 @@ namespace CtrEditor
|
||||||
_timerSimulacion?.Stop();
|
_timerSimulacion?.Stop();
|
||||||
_timerPLCUpdate?.Stop();
|
_timerPLCUpdate?.Stop();
|
||||||
_timer3DUpdate?.Stop();
|
_timer3DUpdate?.Stop();
|
||||||
|
_timerDisplayUpdate?.Stop();
|
||||||
|
_timerDebugFlush?.Stop();
|
||||||
|
|
||||||
// Desconectar PLC si está conectado
|
// Desconectar PLC si está conectado
|
||||||
if (PLCViewModel?.IsConnected == true)
|
if (PLCViewModel?.IsConnected == true)
|
||||||
|
@ -1333,6 +1343,72 @@ namespace CtrEditor
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evento del timer que hace flush automático del buffer de debug para evitar acumulación excesiva
|
||||||
|
/// </summary>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evento del timer para actualización de display (UI)
|
||||||
|
/// </summary>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evento del timer para actualización 3D cuando la simulación está detenida
|
||||||
|
/// </summary>
|
||||||
|
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)
|
private void AdaptPlcTiming(double executionTime)
|
||||||
{
|
{
|
||||||
lastPlcExecutionTime = executionTime;
|
lastPlcExecutionTime = executionTime;
|
||||||
|
@ -1350,13 +1426,13 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
// Incrementar intervalo para dar más tiempo
|
// Incrementar intervalo para dar más tiempo
|
||||||
newInterval = Math.Min(maxPlcExecutionTime + SIM_PRIORITY_TIME + 5, MAX_PLC_INTERVAL);
|
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
|
// Si hemos estado por debajo del umbral, podemos reducir el intervalo
|
||||||
else if (maxPlcExecutionTime + SIM_PRIORITY_TIME < currentInterval * 0.7)
|
else if (maxPlcExecutionTime + SIM_PRIORITY_TIME < currentInterval * 0.7)
|
||||||
{
|
{
|
||||||
newInterval = Math.Max(maxPlcExecutionTime + SIM_PRIORITY_TIME + 2, MIN_PLC_INTERVAL);
|
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ó
|
// Aplicar el nuevo intervalo si cambió
|
||||||
|
@ -1394,20 +1470,20 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
// Incrementar intervalo para evitar que el sistema se vuelva inusable
|
// Incrementar intervalo para evitar que el sistema se vuelva inusable
|
||||||
newInterval = Math.Min(idealInterval + 1, MAX_SIM_INTERVAL); // +1ms adicional de seguridad
|
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
|
// Si hemos estado muy por debajo del umbral, podemos reducir el intervalo
|
||||||
else if (idealInterval < currentInterval * 0.8)
|
else if (idealInterval < currentInterval * 0.8)
|
||||||
{
|
{
|
||||||
newInterval = Math.Max(idealInterval, MIN_SIM_INTERVAL);
|
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
|
// Aplicar el nuevo intervalo si cambió significativamente
|
||||||
if (Math.Abs(newInterval - currentInterval) > 0.5)
|
if (Math.Abs(newInterval - currentInterval) > 0.5)
|
||||||
{
|
{
|
||||||
_timerSimulacion.Interval = newInterval;
|
_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
|
// 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
|
// Reemplazar la propiedad _multiPropertyEditorWindow por un diccionario
|
||||||
private Dictionary<string, Windows.MultiPropertyEditorWindow> _propertyEditorWindows = new();
|
private Dictionary<string, Windows.MultiPropertyEditorWindow> _propertyEditorWindows = new();
|
||||||
|
|
||||||
|
@ -1758,16 +1817,6 @@ namespace CtrEditor
|
||||||
libraryWindow.Show();
|
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
|
#region Hydraulic Simulation Management
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -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
|
// Propiedades de fluido actual
|
||||||
private FluidProperties _currentFluid = new FluidProperties(FluidType.Air);
|
private FluidProperties _currentFluid = new FluidProperties(FluidType.Air);
|
||||||
|
|
||||||
|
@ -629,22 +670,8 @@ namespace CtrEditor.ObjetosSim
|
||||||
if (SpeedRatio < 0.0) SpeedRatio = 0.0;
|
if (SpeedRatio < 0.0) SpeedRatio = 0.0;
|
||||||
if (SpeedRatio > 1.0) SpeedRatio = 1.0;
|
if (SpeedRatio > 1.0) SpeedRatio = 1.0;
|
||||||
|
|
||||||
// Verificar condiciones de NPSH si está habilitada la verificación
|
// Las verificaciones de NPSH ahora se muestran a través de propiedades
|
||||||
if (hydraulicSimulationManager.EnableNPSHVerification && IsRunning)
|
// Se eliminaron los Debug.WriteLine para evitar saturación del sistema
|
||||||
{
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Debug.WriteLine($"Bomba {Nombre}: Velocidad={SpeedRatio:F2}, Funcionando={IsRunning}");
|
//Debug.WriteLine($"Bomba {Nombre}: Velocidad={SpeedRatio:F2}, Funcionando={IsRunning}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -834,6 +834,91 @@ namespace CtrEditor.ObjetosSim
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double CurrentFluidViscosity => CurrentOutputFluid.Viscosity;
|
public double CurrentFluidViscosity => CurrentOutputFluid.Viscosity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Porcentaje de altura para la sección secundaria (abajo) - para display visual
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public double SecondaryDisplayPercentage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL;
|
||||||
|
if (totalVolume == 0) return 0;
|
||||||
|
return (_secondaryVolumeL / totalVolume) * FillPercentage / 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Porcentaje de altura para la sección de mezcla (medio) - para display visual
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public double MixDisplayPercentage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL;
|
||||||
|
if (totalVolume == 0) return 0;
|
||||||
|
return (_mixingVolumeL / totalVolume) * FillPercentage / 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Porcentaje de altura para la sección primaria (arriba) - para display visual
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public double PrimaryDisplayPercentage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL;
|
||||||
|
if (totalVolume == 0) return 0;
|
||||||
|
return (_primaryVolumeL / totalVolume) * FillPercentage / 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Color para la sección secundaria
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public SolidColorBrush SecondaryLevelColor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var colorHex = _secondaryFluid.Color;
|
||||||
|
var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex);
|
||||||
|
return new SolidColorBrush(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Color para la sección de mezcla
|
||||||
|
/// </summary>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Color para la sección primaria
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public SolidColorBrush PrimaryLevelColor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var colorHex = _primaryFluid.Color;
|
||||||
|
var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex);
|
||||||
|
return new SolidColorBrush(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Private Methods
|
// Private Methods
|
||||||
|
|
|
@ -34,23 +34,70 @@
|
||||||
RadiusX="5"
|
RadiusX="5"
|
||||||
RadiusY="5"/>
|
RadiusY="5"/>
|
||||||
|
|
||||||
<!-- Nivel del líquido -->
|
<!-- Nivel del líquido - 3 secciones -->
|
||||||
<Rectangle x:Name="rectLevel"
|
|
||||||
Fill="{Binding LevelColor}"
|
<!-- Sección secundaria (abajo) -->
|
||||||
Stroke="{Binding LevelBorderColor}"
|
<Rectangle x:Name="rectSecondaryLevel"
|
||||||
StrokeThickness="1"
|
Fill="{Binding SecondaryLevelColor}"
|
||||||
|
Stroke="DarkBlue"
|
||||||
|
StrokeThickness="0.5"
|
||||||
RadiusX="3"
|
RadiusX="3"
|
||||||
RadiusY="3"
|
RadiusY="3"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
Margin="4,4,4,4">
|
Margin="4,4,4,4">
|
||||||
<Rectangle.Height>
|
<Rectangle.Height>
|
||||||
<MultiBinding Converter="{StaticResource TankLevelToHeightConverter}">
|
<MultiBinding Converter="{StaticResource TankLevelToHeightConverter}">
|
||||||
<Binding Path="FillPercentage"/>
|
<Binding Path="SecondaryDisplayPercentage"/>
|
||||||
<Binding Path="Tamano"/>
|
<Binding Path="Tamano"/>
|
||||||
</MultiBinding>
|
</MultiBinding>
|
||||||
</Rectangle.Height>
|
</Rectangle.Height>
|
||||||
</Rectangle>
|
</Rectangle>
|
||||||
|
|
||||||
|
<!-- Sección de mezcla (medio) -->
|
||||||
|
<Rectangle x:Name="rectMixLevel"
|
||||||
|
Fill="{Binding MixLevelColor}"
|
||||||
|
Stroke="Purple"
|
||||||
|
StrokeThickness="0.5"
|
||||||
|
RadiusX="3"
|
||||||
|
RadiusY="3"
|
||||||
|
VerticalAlignment="Bottom">
|
||||||
|
<Rectangle.Height>
|
||||||
|
<MultiBinding Converter="{StaticResource TankLevelToHeightConverter}">
|
||||||
|
<Binding Path="MixDisplayPercentage"/>
|
||||||
|
<Binding Path="Tamano"/>
|
||||||
|
</MultiBinding>
|
||||||
|
</Rectangle.Height>
|
||||||
|
<Rectangle.Margin>
|
||||||
|
<MultiBinding Converter="{StaticResource TankSectionMarginConverter}">
|
||||||
|
<Binding Path="SecondaryDisplayPercentage"/>
|
||||||
|
<Binding Path="Tamano"/>
|
||||||
|
</MultiBinding>
|
||||||
|
</Rectangle.Margin>
|
||||||
|
</Rectangle>
|
||||||
|
|
||||||
|
<!-- Sección primaria (arriba) -->
|
||||||
|
<Rectangle x:Name="rectPrimaryLevel"
|
||||||
|
Fill="{Binding PrimaryLevelColor}"
|
||||||
|
Stroke="DarkGreen"
|
||||||
|
StrokeThickness="0.5"
|
||||||
|
RadiusX="3"
|
||||||
|
RadiusY="3"
|
||||||
|
VerticalAlignment="Bottom">
|
||||||
|
<Rectangle.Height>
|
||||||
|
<MultiBinding Converter="{StaticResource TankLevelToHeightConverter}">
|
||||||
|
<Binding Path="PrimaryDisplayPercentage"/>
|
||||||
|
<Binding Path="Tamano"/>
|
||||||
|
</MultiBinding>
|
||||||
|
</Rectangle.Height>
|
||||||
|
<Rectangle.Margin>
|
||||||
|
<MultiBinding Converter="{StaticResource TankSectionMarginConverter}">
|
||||||
|
<Binding Path="SecondaryDisplayPercentage"/>
|
||||||
|
<Binding Path="MixDisplayPercentage"/>
|
||||||
|
<Binding Path="Tamano"/>
|
||||||
|
</MultiBinding>
|
||||||
|
</Rectangle.Margin>
|
||||||
|
</Rectangle>
|
||||||
|
|
||||||
<!-- Líneas de nivel (marcas visuales) -->
|
<!-- Líneas de nivel (marcas visuales) -->
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<!-- Línea de nivel máximo -->
|
<!-- Línea de nivel máximo -->
|
||||||
|
|
|
@ -26,6 +26,11 @@ namespace CtrEditor.Services
|
||||||
|
|
||||||
// Custom TraceListener para capturar Debug.WriteLine
|
// Custom TraceListener para capturar Debug.WriteLine
|
||||||
private DebugTraceListener _traceListener;
|
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)
|
public DebugConsoleServer(int port = 5007)
|
||||||
{
|
{
|
||||||
|
@ -175,6 +180,10 @@ namespace CtrEditor.Services
|
||||||
{
|
{
|
||||||
if (_messageQueue.TryDequeue(out string message))
|
if (_messageQueue.TryDequeue(out string message))
|
||||||
{
|
{
|
||||||
|
lock (_bufferLock)
|
||||||
|
{
|
||||||
|
_messageCount = Math.Max(0, _messageCount - 1);
|
||||||
|
}
|
||||||
await BroadcastMessageAsync(message);
|
await BroadcastMessageAsync(message);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -244,7 +253,60 @@ namespace CtrEditor.Services
|
||||||
{
|
{
|
||||||
if (_isRunning)
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limpia el buffer de mensajes para evitar acumulación excesiva
|
||||||
|
/// </summary>
|
||||||
|
public void FlushMessageBuffer()
|
||||||
|
{
|
||||||
|
lock (_bufferLock)
|
||||||
|
{
|
||||||
|
// Mantener solo los últimos 100 mensajes
|
||||||
|
var messagesToKeep = new List<string>();
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene estadísticas del buffer de mensajes
|
||||||
|
/// </summary>
|
||||||
|
public (int messageCount, int maxBufferSize) GetBufferStats()
|
||||||
|
{
|
||||||
|
lock (_bufferLock)
|
||||||
|
{
|
||||||
|
return (_messageCount, MAX_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue