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 { /// /// Tanque hidráulico con gestión dinámica de nivel y presión configurable /// 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 GetHydraulicNodes() { var nodes = new List(); // El tanque siempre es un nodo de presión fija si está configurado así if (IsFixedPressure) { // Convertir bar a Pa para el sistema hidráulico interno var pressurePa = TankPressure * 100000.0; nodes.Add(new HydraulicNodeDefinition(Nombre, true, pressurePa, GetTankDescription())); //Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F2} bar ({pressurePa:F0} Pa)"); } 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 flows, Dictionary pressures) { var deltaTime = GetDeltaTime(); // Obtener flujos conectados UpdateFlowsFromConnectedPipes(flows); // Actualizar presión (convertir de Pa a bar) if (pressures.ContainsKey(Nombre)) { CurrentPressure = pressures[Nombre] / 100000.0; // Pa a bar } // Actualizar nivel basado en balance de flujo UpdateLevelFromFlowBalance(deltaTime); if (VerboseLogging()) { Debug.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:F2}bar, Fluido={CurrentFluidDescription}"); } } public List GetHydraulicElements() { // Los tanques no generan elementos hidráulicos, solo nodos return new List(); } public void UpdateHydraulicProperties() { // Actualizar propiedades antes de la simulación SafeUpdateTankPressure(); SafeUpdateVolumeCalculations(); } // UI Properties /// /// Propiedades del fluido actual que está saliendo del tanque /// [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) }; } } /// /// Descripción del fluido actual /// [JsonIgnore] public string CurrentFluidDescription => CurrentOutputFluid.Description; /// /// Color del nivel de líquido basado en el fluido actual /// [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); } } /// /// Color del borde del nivel de líquido /// [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); } } /// /// 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 /// [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); } } /// /// 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) /// [JsonIgnore] public bool IsEmpty => CurrentVolumeL < 1.0; // Menos de 1L considerado vacío /// /// Densidad del fluido actual /// [JsonIgnore] public double CurrentFluidDensity => CurrentOutputFluid.Density; /// /// Viscosidad del fluido actual /// [JsonIgnore] public double CurrentFluidViscosity => CurrentOutputFluid.Viscosity; // 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) { Debug.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 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() .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 /// /// Se llama antes de la serialización para asegurar que el lock esté disponible /// 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; } } } }