CtrEditor/ObjetosSim/HydraulicComponents/osHydTank.cs

1373 lines
47 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Media;
using CtrEditor.HydraulicSimulator;
using CtrEditor.ObjetosSim;
using CtrEditor.FuncionesBase;
using HydraulicSimulator.Models;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CommunityToolkit.Mvvm.ComponentModel;
using LibS7Adv;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// Tanque hidráulico con gestión dinámica de nivel y presión configurable
/// </summary>
public partial class osHydTank : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
{
// Referencia al objeto de simulación hidráulica específico
private simHydraulicTank SimHydraulicTank;
public static string NombreCategoria() => "Componentes Hidráulicos";
public static string NombreClase() => "Tanque Hidráulico";
// Private Fields
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 _currentPressure = 1.013; // bar
private bool _isFixedPressure = true;
private HydraulicTankType _tankType = HydraulicTankType.Intermediate;
private double _lastUpdateTime = 0.0;
// Propiedades de fluido
private FluidProperties _primaryFluid = new FluidProperties(FluidType.Water, 20.0);
private FluidProperties _secondaryFluid = new FluidProperties(FluidType.Syrup, 20.0, 65.0);
private double _primaryVolumeL = 800.0; // L
private double _secondaryVolumeL = 200.0; // L
private double _mixingVolumeL = 100.0; // L de transición para mezcla
private MixingState _mixingState = MixingState.PrimaryOnly;
private double _currentMixRatio = 0.0; // 0 = solo primario, 1 = solo secundario
[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
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Tamaño visual del tanque")]
[property: Name("Tamaño")]
public float tamano;
// Propiedades visuales específicas del tanque
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Color visual del tanque")]
[property: Name("Color")]
private Brush colorButton_oculto = Brushes.LightBlue;
// Constructor
public osHydTank()
{
// Inicializar el lock primero para thread safety
EnsureLockInitialized();
try
{
Nombre = "Tanque Hidráulico";
Tamano = 1.0f; // Usar un tamaño mayor para mayor visibilidad
// Inicializar dimensiones usando las propiedades de osBase
Ancho = 0.30f;
Alto = 0.40f;
Angulo = 0f;
// Asegurar que el movimiento esté habilitado
Lock_movement = false;
// No cargar imagen aquí - se carga en ucLoaded()
// para evitar problemas de serialización
// Cálculos seguros
SafeUpdateVolumeCalculations();
IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros
SafeUpdateTankPressure();
// Inicializar estado de mezcla
UpdateMixingState(); // 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}");
}
catch (Exception ex)
{
Debug.WriteLine($"Error in osHydTank constructor: {ex.Message}");
// Inicializar valores por defecto mínimos
InitializeDefaults();
}
}
// Constructor y Métodos Base
private string nombre = NombreClase();
[Category("🏷️ Identificación")]
[Description("Nombre identificativo del objeto")]
[Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
public override void OnMove(float LeftPixels, float TopPixels)
{
// Método básico de movimiento como otros objetos
// Debug para verificar que se llama
Debug.WriteLine($"osHydTank.OnMove: LeftPixels={LeftPixels}, TopPixels={TopPixels}, Left={Left}, Top={Top}");
}
public override void OnResize(float Delta_Width, float Delta_Height)
{
Tamano += Delta_Width + Delta_Height;
}
public override void UpdateGeometryStart()
{
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
if (SimHydraulicTank == null)
{
// Convertir bar a Pa para el sistema hidráulico interno
var pressurePa = TankPressure * 100000.0;
SimHydraulicTank = hydraulicSimulationManager.AddTank(pressurePa, CurrentLevelM, MaxLevelM, MinLevelM, CrossSectionalArea, IsFixedPressure);
SimHydraulicTank.SimObjectType = "HydraulicTank";
SimHydraulicTank.WpfObject = this;
SimHydraulicTank.Nombre = Nombre;
}
else
{
// Actualizar propiedades si el objeto ya existe (convertir bar a Pa)
SimHydraulicTank.TankPressure = TankPressure * 100000.0;
SimHydraulicTank.CurrentLevel = CurrentLevelM;
SimHydraulicTank.MaxLevel = MaxLevelM;
SimHydraulicTank.MinLevel = MinLevelM;
SimHydraulicTank.CrossSectionalArea = CrossSectionalArea;
SimHydraulicTank.IsFixedPressure = IsFixedPressure;
}
}
public override void UpdateControl(int elapsedMilliseconds)
{
// Actualizar propiedades desde la simulación hidráulica
if (SimHydraulicTank != null)
{
// Convertir de m³/s a L/min
InletFlow = SimHydraulicTank.InletFlow * 60000.0;
OutletFlow = SimHydraulicTank.OutletFlow * 60000.0;
// Convertir de Pa a bar
CurrentPressure = SimHydraulicTank.CurrentPressure / 100000.0;
// Actualizar nivel del tanque basado en el balance de flujo
double deltaTime = elapsedMilliseconds / 60000.0; // Convertir a minutos
if (deltaTime > 0)
{
UpdateLevelFromFlowBalance(deltaTime);
}
}
// Actualizar el color según el fluido actual
UpdateMixingState();
}
// 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")]
[Description("Área de la sección transversal del tanque (m²)")]
public double CrossSectionalArea
{
get => _crossSectionalArea;
set
{
if (SetProperty(ref _crossSectionalArea, Math.Max(0.1, value)))
{
SafeUpdateVolumeCalculations();
InvalidateHydraulicNetwork();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Volumen máximo")]
[Description("Volumen máximo del tanque (L)")]
public double MaxVolumeL
{
get => _maxVolumeL;
set
{
if (SetProperty(ref _maxVolumeL, Math.Max(10, value)))
{
if (_currentVolumeL > _maxVolumeL)
CurrentVolumeL = _maxVolumeL;
SafeUpdateVolumeCalculations();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Nivel máximo")]
[Description("Nivel máximo permitido en el tanque (m)")]
public double MaxLevelM
{
get => _maxLevelM;
set
{
if (SetProperty(ref _maxLevelM, Math.Max(0.2, value)))
{
if (_currentLevelM > _maxLevelM)
CurrentLevelM = _maxLevelM;
SafeUpdateVolumeCalculations();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Nivel mínimo")]
[Description("Nivel mínimo permitido en el tanque (m)")]
public double MinLevelM
{
get => _minLevelM;
set
{
if (SetProperty(ref _minLevelM, Math.Max(0.0, Math.Min(value, _maxLevelM - 0.1))))
{
if (_currentLevelM < _minLevelM)
CurrentLevelM = _minLevelM;
SafeUpdateVolumeCalculations();
}
}
}
// Properties - Pressure
[Category("🔧 Sistema de Presión")]
[DisplayName("Presión fija")]
[Description("Si está habilitado, la presión se mantiene constante")]
public bool IsFixedPressure
{
get => _isFixedPressure;
set
{
if (SetProperty(ref _isFixedPressure, value))
{
SafeUpdateTankPressure();
InvalidateHydraulicNetwork();
}
}
}
[Category("🔧 Sistema de Presión")]
[DisplayName("Presión del tanque")]
[Description("Presión mantenida en el tanque (bar). En sistemas reales controlada por PID")]
public double TankPressure
{
get => _tankPressure;
set
{
if (SetProperty(ref _tankPressure, Math.Max(0, value)))
{
SafeUpdateTankPressure();
InvalidateHydraulicNetwork();
}
}
}
// Properties - Current State
[Category("📊 Estado Actual")]
[DisplayName("Nivel actual")]
[Description("Nivel actual de líquido en el tanque (m)")]
public double CurrentLevelM
{
get => _currentLevelM;
set
{
EnsureLockInitialized();
lock (_levelLock)
{
var clampedLevel = Math.Max(_minLevelM, Math.Min(_maxLevelM, value));
if (SetProperty(ref _currentLevelM, clampedLevel))
{
SafeUpdateVolumeCalculations();
OnPropertyChanged(nameof(FillPercentage));
UpdateMixingState();
}
}
}
}
[Category("📊 Estado Actual")]
[DisplayName("Volumen actual")]
[Description("Volumen actual de líquido en el tanque (L)")]
public double CurrentVolumeL
{
get => _currentVolumeL;
set
{
EnsureLockInitialized();
lock (_levelLock)
{
var clampedVolume = Math.Max(0, Math.Min(_maxVolumeL, value));
if (SetProperty(ref _currentVolumeL, clampedVolume))
{
// Actualizar nivel basado en volumen
_currentLevelM = _minLevelM + (clampedVolume / 1000.0) / _crossSectionalArea;
_currentLevelM = Math.Max(_minLevelM, Math.Min(_maxLevelM, _currentLevelM));
OnPropertyChanged(nameof(CurrentLevelM));
OnPropertyChanged(nameof(FillPercentage));
UpdateMixingState();
}
}
}
}
[Category("📊 Estado Actual")]
[DisplayName("Porcentaje llenado")]
[Description("Porcentaje de llenado del tanque")]
[JsonIgnore]
public double FillPercentage => (_currentLevelM - _minLevelM) / (_maxLevelM - _minLevelM) * 100.0;
[Category("📊 Estado Actual")]
[DisplayName("Flujo entrada")]
[Description("Flujo de entrada actual (L/min)")]
[JsonIgnore]
public double InletFlow
{
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);
}
[Category("📊 Estado Actual")]
[DisplayName("Balance flujo")]
[Description("Balance de flujo (entrada - salida) (L/min)")]
[JsonIgnore]
public double FlowBalance => InletFlow - OutletFlow;
[Category("📊 Estado Actual")]
[DisplayName("Presión actual")]
[Description("Presión actual en el tanque (bar)")]
[JsonIgnore]
public double CurrentPressure
{
get => _currentPressure;
private set => SetProperty(ref _currentPressure, value);
}
// Properties - Fluids and Mixing
[Category("🧪 Fluido Primario")]
[DisplayName("Tipo de fluido")]
[Description("Tipo del fluido primario en el tanque")]
public FluidType PrimaryFluidType
{
get => _primaryFluid.Type;
set
{
if (_primaryFluid.Type != value)
{
_primaryFluid.Type = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentFluidDescription));
InvalidateHydraulicNetwork();
}
}
}
[Category("🧪 Fluido Primario")]
[DisplayName("Temperatura")]
[Description("Temperatura del fluido primario (°C)")]
public double PrimaryTemperature
{
get => _primaryFluid.Temperature;
set
{
if (Math.Abs(_primaryFluid.Temperature - value) > 0.01)
{
_primaryFluid.Temperature = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentFluidDescription));
}
}
}
[Category("🧪 Fluido Primario")]
[DisplayName("Brix")]
[Description("Grados Brix para jarabe (% sacarosa)")]
public double PrimaryBrix
{
get => _primaryFluid.Brix;
set
{
var clampedValue = Math.Max(0, Math.Min(80, value));
if (Math.Abs(_primaryFluid.Brix - clampedValue) > 0.01)
{
_primaryFluid.Brix = clampedValue;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentFluidDescription));
}
}
}
[Category("🧪 Fluido Primario")]
[DisplayName("Concentración")]
[Description("Concentración para soda cáustica (%)")]
public double PrimaryConcentration
{
get => _primaryFluid.Concentration;
set
{
var clampedValue = Math.Max(0, Math.Min(50, value));
if (Math.Abs(_primaryFluid.Concentration - clampedValue) > 0.01)
{
_primaryFluid.Concentration = clampedValue;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentFluidDescription));
}
}
}
[Category("🧪 Fluido Primario")]
[DisplayName("Volumen")]
[Description("Volumen del fluido primario (L)")]
public double PrimaryVolumeL
{
get => _primaryVolumeL;
set
{
if (SetProperty(ref _primaryVolumeL, Math.Max(0, value)))
{
UpdateMixingState();
OnPropertyChanged(nameof(CurrentFluidDescription));
}
}
}
[Category("🧪 Fluido Secundario")]
[DisplayName("Tipo de fluido")]
[Description("Tipo del fluido secundario en el tanque")]
public FluidType SecondaryFluidType
{
get => _secondaryFluid.Type;
set
{
if (_secondaryFluid.Type != value)
{
_secondaryFluid.Type = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentFluidDescription));
InvalidateHydraulicNetwork();
}
}
}
[Category("🧪 Fluido Secundario")]
[DisplayName("Temperatura")]
[Description("Temperatura del fluido secundario (°C)")]
public double SecondaryTemperature
{
get => _secondaryFluid.Temperature;
set
{
if (Math.Abs(_secondaryFluid.Temperature - value) > 0.01)
{
_secondaryFluid.Temperature = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentFluidDescription));
}
}
}
[Category("🧪 Fluido Secundario")]
[DisplayName("Brix")]
[Description("Grados Brix para jarabe (% sacarosa)")]
public double SecondaryBrix
{
get => _secondaryFluid.Brix;
set
{
var clampedValue = Math.Max(0, Math.Min(80, value));
if (Math.Abs(_secondaryFluid.Brix - clampedValue) > 0.01)
{
_secondaryFluid.Brix = clampedValue;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentFluidDescription));
}
}
}
[Category("🧪 Fluido Secundario")]
[DisplayName("Concentración")]
[Description("Concentración para soda cáustica (%)")]
public double SecondaryConcentration
{
get => _secondaryFluid.Concentration;
set
{
var clampedValue = Math.Max(0, Math.Min(50, value));
if (Math.Abs(_secondaryFluid.Concentration - clampedValue) > 0.01)
{
_secondaryFluid.Concentration = clampedValue;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentFluidDescription));
}
}
}
[Category("🧪 Fluido Secundario")]
[DisplayName("Volumen")]
[Description("Volumen del fluido secundario (L)")]
public double SecondaryVolumeL
{
get => _secondaryVolumeL;
set
{
if (SetProperty(ref _secondaryVolumeL, Math.Max(0, value)))
{
UpdateMixingState();
OnPropertyChanged(nameof(CurrentFluidDescription));
}
}
}
[Category("🔀 Control de Mezcla")]
[DisplayName("Volumen de mezcla")]
[Description("Volumen de transición durante la mezcla (L)")]
public double MixingVolumeL
{
get => _mixingVolumeL;
set => SetProperty(ref _mixingVolumeL, Math.Max(0, value));
}
[Category("🔀 Control de Mezcla")]
[DisplayName("Estado de mezcla")]
[Description("Estado actual del proceso de mezcla")]
[JsonIgnore]
public MixingState MixingState
{
get => _mixingState;
private set => SetProperty(ref _mixingState, value);
}
[Category("🔀 Control de Mezcla")]
[DisplayName("Ratio de mezcla")]
[Description("Ratio actual de mezcla (0=primario, 1=secundario)")]
[JsonIgnore]
public double CurrentMixRatio
{
get => _currentMixRatio;
private set => SetProperty(ref _currentMixRatio, value);
}
// IHydraulicComponent Implementation
[JsonIgnore]
public bool HasHydraulicComponents => true;
public List<HydraulicNodeDefinition> GetHydraulicNodes()
{
var nodes = new List<HydraulicNodeDefinition>();
// El tanque siempre es un nodo de presión fija si está configurado así
if (IsFixedPressure)
{
// TankPressure ya está en Pa, no necesita conversión
var pressurePa = TankPressure;
nodes.Add(new HydraulicNodeDefinition(Nombre, true, pressurePa, GetTankDescription()));
//Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F0} Pa ({TankPressure/100000.0:F2} bar)");
}
else
{
nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, GetTankDescription()));
//Debug.WriteLine($"Tanque {Nombre}: Nodo de presión libre creado");
}
return nodes;
}
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
{
var deltaTime = GetDeltaTime();
// Obtener flujos conectados
UpdateFlowsFromConnectedPipes(flows);
// Actualizar presión (convertir de Pa a bar para consistencia)
if (pressures.ContainsKey(Nombre))
{
CurrentPressure = pressures[Nombre] / 100000.0; // Convertir Pa a bar
}
// Actualizar nivel basado en balance de flujo
UpdateLevelFromFlowBalance(deltaTime);
if (VerboseLogging())
{
Trace.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevelM:F2}m ({FillPercentage:F1}%), " +
$"Flujo_entrada={InletFlow:F2}L/min, Flujo_salida={OutletFlow:F2}L/min, " +
$"Balance={FlowBalance:F2}L/min, Presión={CurrentPressure:F3}bar ({CurrentPressure * 100000.0:F0}Pa), Fluido={CurrentFluidDescription}");
}
}
public List<HydraulicElementDefinition> GetHydraulicElements()
{
// Los tanques no generan elementos hidráulicos, solo nodos
return new List<HydraulicElementDefinition>();
}
public void UpdateHydraulicProperties()
{
// Actualizar propiedades antes de la simulación
SafeUpdateTankPressure();
SafeUpdateVolumeCalculations();
}
// UI Properties
/// <summary>
/// Propiedades del fluido actual que está saliendo del tanque
/// </summary>
[JsonIgnore]
public FluidProperties CurrentOutputFluid
{
get
{
return MixingState switch
{
MixingState.Empty => new FluidProperties(FluidType.Air),
MixingState.PrimaryOnly => _primaryFluid.Clone(),
MixingState.SecondaryOnly => _secondaryFluid.Clone(),
MixingState.EmptyingSecondary => _secondaryFluid.Clone(),
MixingState.Mixing => _primaryFluid.MixWith(_secondaryFluid, CurrentMixRatio),
MixingState.EmptyingPrimary => _primaryFluid.Clone(),
_ => new FluidProperties(FluidType.Air)
};
}
}
/// <summary>
/// Descripción del fluido actual
/// </summary>
[JsonIgnore]
public string CurrentFluidDescription => CurrentOutputFluid.Description;
/// <summary>
/// Color del nivel de líquido basado en el fluido actual
/// </summary>
[JsonIgnore]
public SolidColorBrush LevelColor
{
get
{
var fluid = CurrentOutputFluid;
var colorHex = fluid.Color;
// Convertir hex a color y ajustar según nivel
var percentage = FillPercentage;
var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex);
// Oscurecer si nivel es bajo
if (percentage < 20)
{
color = Color.FromRgb((byte)(color.R * 0.7), (byte)(color.G * 0.7), (byte)(color.B * 0.7));
}
return new SolidColorBrush(color);
}
}
/// <summary>
/// Color del borde del nivel de líquido
/// </summary>
[JsonIgnore]
public SolidColorBrush LevelBorderColor
{
get
{
var percentage = FillPercentage;
if (percentage < 20) return new SolidColorBrush(Colors.DarkRed);
if (percentage < 50) return new SolidColorBrush(Colors.DarkOrange);
return new SolidColorBrush(Colors.DarkBlue);
}
}
/// <summary>
/// Indicador del tipo de tanque
/// </summary>
[JsonIgnore]
public string TankTypeIndicator
{
get
{
return TankType switch
{
HydraulicTankType.Suction => "S",
HydraulicTankType.Intermediate => "I",
HydraulicTankType.Storage => "A",
HydraulicTankType.Process => "P",
_ => "T"
};
}
}
/// <summary>
/// Color del balance de flujo
/// </summary>
[JsonIgnore]
public SolidColorBrush FlowBalanceColor
{
get
{
var balance = FlowBalance;
if (Math.Abs(balance) < 0.1) return new SolidColorBrush(Colors.Gray);
return balance > 0 ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
}
}
/// <summary>
/// Posición Y de la línea de nivel medio (50%)
/// </summary>
[JsonIgnore]
public double MidLevelY
{
get
{
var tankHeight = Tamano * 100; // Convertir a píxeles aproximados
return tankHeight * 0.5; // 50% de altura
}
}
/// <summary>
/// Posición Y de la línea de nivel mínimo
/// </summary>
[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
}
}
/// <summary>
/// Indica si el tanque está vacío (contiene aire)
/// </summary>
[JsonIgnore]
public bool IsEmpty => CurrentVolumeL < 1.0; // Menos de 1L considerado vacío
/// <summary>
/// Densidad del fluido actual
/// </summary>
[JsonIgnore]
public double CurrentFluidDensity => CurrentOutputFluid.Density;
/// <summary>
/// Viscosidad del fluido actual
/// </summary>
[JsonIgnore]
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 void InitializeDefaults()
{
// Valores mínimos para que el objeto sea funcional
try
{
if (string.IsNullOrEmpty(nombre))
nombre = "Tanque Hidráulico";
if (Tamano <= 0)
Tamano = 1.0f;
if (Ancho <= 0)
Ancho = 0.30f;
if (Alto <= 0)
Alto = 0.40f;
SafeUpdateVolumeCalculations();
SafeUpdateTankPressure();
Debug.WriteLine("osHydTank: Initialized with default values");
}
catch (Exception ex)
{
Debug.WriteLine($"Error in InitializeDefaults: {ex.Message}");
}
}
private void SafeUpdateVolumeCalculations()
{
try
{
// Calcular volumen total actual basado en nivel
_currentVolumeL = (_currentLevelM - _minLevelM) * _crossSectionalArea * 1000.0; // convertir m³ a L
_maxVolumeL = (_maxLevelM - _minLevelM) * _crossSectionalArea * 1000.0;
// Asegurar que los volúmenes de fluidos no excedan el volumen total
var totalFluidVolume = _primaryVolumeL + _secondaryVolumeL;
if (totalFluidVolume > _currentVolumeL)
{
var ratio = _currentVolumeL / totalFluidVolume;
_primaryVolumeL *= ratio;
_secondaryVolumeL *= ratio;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in SafeUpdateVolumeCalculations: {ex.Message}");
// Usar valores por defecto seguros
_currentVolumeL = 1000.0;
_maxVolumeL = 2000.0;
}
}
private void SafeUpdateTankPressure()
{
try
{
if (IsFixedPressure)
{
CurrentPressure = TankPressure;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in SafeUpdateTankPressure: {ex.Message}");
// Usar presión atmosférica por defecto
_currentPressure = 1.013;
}
}
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()
{
var totalVolume = _primaryVolumeL + _secondaryVolumeL;
if (totalVolume < 1.0) // Menos de 1L considerado vacío
{
MixingState = MixingState.Empty;
CurrentMixRatio = 0.0;
return;
}
if (_secondaryVolumeL <= 0)
{
MixingState = MixingState.PrimaryOnly;
CurrentMixRatio = 0.0;
}
else if (_primaryVolumeL <= 0)
{
MixingState = MixingState.SecondaryOnly;
CurrentMixRatio = 1.0;
}
else
{
// Determinar estado de mezcla basado en volúmenes
var secondaryRatio = _secondaryVolumeL / totalVolume;
if (_secondaryVolumeL <= _mixingVolumeL && _primaryVolumeL > _mixingVolumeL)
{
// Vaciando secundario
MixingState = MixingState.EmptyingSecondary;
CurrentMixRatio = secondaryRatio;
}
else if (_primaryVolumeL <= _mixingVolumeL && _secondaryVolumeL > _mixingVolumeL)
{
// Vaciando primario
MixingState = MixingState.EmptyingPrimary;
CurrentMixRatio = secondaryRatio;
}
else if (_secondaryVolumeL <= _mixingVolumeL && _primaryVolumeL <= _mixingVolumeL)
{
// Mezclando ambos
MixingState = MixingState.Mixing;
CurrentMixRatio = secondaryRatio;
}
else
{
// Determinar cual predomina
if (secondaryRatio > 0.7)
{
MixingState = MixingState.SecondaryOnly;
CurrentMixRatio = 1.0;
}
else if (secondaryRatio < 0.3)
{
MixingState = MixingState.PrimaryOnly;
CurrentMixRatio = 0.0;
}
else
{
MixingState = MixingState.Mixing;
CurrentMixRatio = secondaryRatio;
}
}
}
}
private void UpdateLevelFromFlowBalance(double deltaTime)
{
if (deltaTime <= 0 || Math.Abs(deltaTime) > 1.0) // Evitar deltas muy grandes
return;
// Balance de masa: cambio_volumen = (flujo_entrada - flujo_salida) * tiempo
var volumeChangeL = FlowBalance * deltaTime; // L/min * min = L
// Actualizar volumen total
var newVolumeL = CurrentVolumeL + volumeChangeL;
CurrentVolumeL = Math.Max(0, Math.Min(_maxVolumeL, newVolumeL));
// Actualizar volúmenes de fluidos según el estado de mezcla
UpdateFluidVolumesFromFlow(volumeChangeL);
// Debug para verificar cambios
if (VerboseLogging() && Math.Abs(volumeChangeL) > 0.1)
{
Trace.WriteLine($"Tanque {Nombre}: Δt={deltaTime:F3}min, ΔVolumen={volumeChangeL:F2}L, " +
$"Volumen={CurrentVolumeL:F1}L, Estado={MixingState}, Fluido={CurrentFluidDescription}");
}
}
private void UpdateFluidVolumesFromFlow(double volumeChangeL)
{
if (Math.Abs(volumeChangeL) < 0.01) return; // Cambio muy pequeño
if (volumeChangeL > 0)
{
// Flujo de entrada - agregar al fluido primario por defecto
_primaryVolumeL += volumeChangeL;
}
else
{
// Flujo de salida - quitar según el estado de mezcla
var volumeToRemove = Math.Abs(volumeChangeL);
switch (MixingState)
{
case MixingState.SecondaryOnly:
case MixingState.EmptyingSecondary:
_secondaryVolumeL = Math.Max(0, _secondaryVolumeL - volumeToRemove);
break;
case MixingState.PrimaryOnly:
case MixingState.EmptyingPrimary:
_primaryVolumeL = Math.Max(0, _primaryVolumeL - volumeToRemove);
break;
case MixingState.Mixing:
// Quitar proporcionalmente
var totalVolume = _primaryVolumeL + _secondaryVolumeL;
if (totalVolume > 0)
{
var primaryRatio = _primaryVolumeL / totalVolume;
var secondaryRatio = _secondaryVolumeL / totalVolume;
_primaryVolumeL = Math.Max(0, _primaryVolumeL - volumeToRemove * primaryRatio);
_secondaryVolumeL = Math.Max(0, _secondaryVolumeL - volumeToRemove * secondaryRatio);
}
break;
}
}
UpdateMixingState();
}
private void UpdateFlowsFromConnectedPipes(Dictionary<string, double> flows)
{
InletFlow = 0.0;
OutletFlow = 0.0;
// Buscar flujos en las ramas que involucren este tanque
foreach (var flow in flows)
{
var branchName = flow.Key;
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
if (branchName.Contains(Nombre))
{
if (flowValueLmin > 0)
{
// Flujo positivo hacia o desde el tanque
if (IsFlowTowardsTank(branchName))
{
InletFlow += flowValueLmin;
}
else
{
OutletFlow += flowValueLmin;
}
}
}
}
}
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<osHydPipe>()
.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()
{
var currentTime = Environment.TickCount64 / 60000.0; // convertir a minutos
var deltaTime = _lastUpdateTime > 0 ? currentTime - _lastUpdateTime : 0.0;
_lastUpdateTime = currentTime;
// Limitar delta time para evitar saltos grandes
return Math.Min(deltaTime, 0.1); // máximo 0.1 minutos (6 segundos)
}
private bool VerboseLogging()
{
return _mainViewModel?.hydraulicSimulationManager?.VerboseOutput ?? false;
}
private void InvalidateHydraulicNetwork()
{
_mainViewModel?.hydraulicSimulationManager?.InvalidateNetwork();
}
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"
};
}
private bool IsFlowTowardsTank(string branchName)
{
// Determinar la dirección del flujo basándose en el nombre de la rama
return branchName.EndsWith($" -> {Nombre}") || branchName.EndsWith($"_{Nombre}");
}
// IHydraulicFlowReceiver Implementation
public void SetFlow(double flow)
{
// Este método puede ser llamado por tuberías conectadas
// La lógica de flujo se maneja en ApplyHydraulicResults
}
public double GetFlow()
{
return FlowBalance;
}
// IHydraulicPressureReceiver Implementation
public void SetPressure(double pressure)
{
if (!IsFixedPressure)
{
CurrentPressure = pressure;
}
}
public double GetPressure()
{
return CurrentPressure;
}
// ZIndex Implementation
public int zIndex_fromFrames { get; set; } = 2;
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
// IosBase Implementation
public void Start()
{
// Inicialización del tanque para la simulación
SafeUpdateVolumeCalculations();
SafeUpdateTankPressure();
}
public void Inicializar(int valorInicial)
{
// Inicialización con valor inicial
SafeUpdateVolumeCalculations();
SafeUpdateTankPressure();
}
// Disposing no longer needed for connections since we removed them
public void Disposing()
{
// Cleanup if needed for fluids
}
// Serialization Support
/// <summary>
/// Se llama antes de la serialización para asegurar que el lock esté disponible
/// </summary>
private void EnsureLockInitialized()
{
if (_levelLock == null)
_levelLock = new object();
}
// osBase Overrides
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
Debug.WriteLine($"osHydTank.ucLoaded(): Componente hidráulico cargado correctamente");
// Inicialización específica del tanque hidráulico
SafeUpdateVolumeCalculations();
SafeUpdateTankPressure();
// Debug: Confirmar que el UserControl se está cargando
Debug.WriteLine($"osHydTank.ucLoaded(): Tanque '{Nombre}' cargado - Tamaño: {Tamano}, ZIndex: {zIndex_fromFrames}");
}
public override void ucUnLoaded()
{
// Remover objeto hidráulico de la simulación
if (SimHydraulicTank != null)
{
hydraulicSimulationManager.Remove(SimHydraulicTank);
SimHydraulicTank = null;
}
}
}
}