using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Windows.Media; using CtrEditor.HydraulicSimulator; using CtrEditor.HydraulicSimulator.TSNet.Components; 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 { 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 _crossSectionalArea = 1.0; // m² private double _currentVolumeL = 1000.0; // L private double _maxVolumeL = 2000.0; // L private double _netFlow = 0.0; // L/min (positivo = entrada, negativo = salida) private double _currentPressure = 1.013; // bar private bool _isFixedPressure = true; 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 // Tank configuration fields private double _maxLevelM = 2.0; // m - máximo nivel del tanque private double _minLevelM = 0.0; // m - mínimo nivel del tanque private string _tankType = "Standard"; // Tipo de tanque // TSNet Integration [JsonIgnore] public TSNetTankAdapter? TSNetAdapter { get; private set; } [JsonIgnore] private object _levelLock = new object(); // 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 inicializar TSNet Adapter aquí - se creará cuando se registre con TSNetSimulationManager // TSNetAdapter = new TSNetTankAdapter(this); // 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() { // En el nuevo sistema unificado, no necesitamos crear objetos hidráulicos // El componente se registra automáticamente como IHydraulicComponent } public override void UpdateControl(int elapsedMilliseconds) { // En el nuevo sistema TSNet, los valores provienen exclusivamente de TSNet // No realizamos cálculos internos - solo actualizamos propiedades de UI // Los resultados de TSNet ya están aplicados en ApplyHydraulicResults() // Solo actualizamos propiedades derivadas y visuales try { // Actualizar propiedades de UI y mezcla UpdateMixingState(); // Actualizar propiedades calculadas para UI OnPropertyChanged(nameof(FillPercentage)); OnPropertyChanged(nameof(FlowBalance)); OnPropertyChanged(nameof(CurrentPressurePa)); // Actualizar color visual basado en estado UpdateTankColorFromFluid(); // Debug periódico cada 5 segundos if (Environment.TickCount % 5000 < elapsedMilliseconds) { Debug.WriteLine($"Tank {Nombre}: Level={CurrentLevelM:F2}m, Volume={CurrentVolumeL:F0}L, Flow={NetFlow:F1}L/min, Pressure={CurrentPressure:F2}bar"); } } catch (Exception ex) { Debug.WriteLine($"Error in Tank {Nombre} UpdateControl: {ex.Message}"); } } // Properties - Configuration [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.1, value))) { // Asegurar que el nivel mínimo no sea mayor que el máximo if (_minLevelM >= _maxLevelM) MinLevelM = _maxLevelM * 0.1; // 10% del máximo como mínimo SafeUpdateVolumeCalculations(); InvalidateHydraulicNetwork(); } } } [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, value))) { // Asegurar que el nivel mínimo no sea mayor que el máximo if (_minLevelM >= _maxLevelM) MaxLevelM = _minLevelM + 0.1; // Al menos 10cm más que el mínimo SafeUpdateVolumeCalculations(); InvalidateHydraulicNetwork(); } } } [Category("🏗️ Configuración del Tanque")] [DisplayName("Tipo de tanque")] [Description("Tipo de tanque (Standard, Storage, Process, etc.)")] public string TankType { get => _tankType; set => SetProperty(ref _tankType, value ?? "Standard"); } // Properties - Pressure [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 { // Asegurar que el valor esté en bar y sea razonable var valueInBar = value > 1000 ? value / 100000.0 : value; // Si viene en Pa, convertir a bar valueInBar = Math.Max(0.1, Math.Min(10.0, valueInBar)); // Limitar entre 0.1 y 10 bar if (SetProperty(ref _tankPressure, valueInBar)) { 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(0.0, 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 = (clampedVolume / 1000.0) / _crossSectionalArea; _currentLevelM = Math.Max(0.0, _currentLevelM); OnPropertyChanged(nameof(CurrentLevelM)); OnPropertyChanged(nameof(FillPercentage)); UpdateMixingState(); } } } } [Category("📊 Estado Actual")] [DisplayName("Porcentaje llenado")] [Description("Porcentaje de llenado del tanque")] [JsonIgnore] public double FillPercentage => MaxLevelM > 0 ? (_currentLevelM / MaxLevelM) * 100.0 : 0.0; [Category("📊 Estado Actual")] [DisplayName("Flujo neto")] [Description("Flujo neto del tanque (L/min) - positivo=entrada, negativo=salida")] [JsonIgnore] public double NetFlow { get => _netFlow; private set => SetProperty(ref _netFlow, value); } [Category("📊 Estado Actual")] [DisplayName("Balance flujo")] [Description("Balance de flujo neto (L/min)")] [JsonIgnore] public double FlowBalance => NetFlow; [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); } [Category("📊 Estado Actual")] [DisplayName("Presión (Pa)")] [Description("Presión actual en Pascal")] [JsonIgnore] public double CurrentPressurePa => CurrentPressure * 100000.0; // 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 TankPressure de bar a Pa para el solver var pressurePa = TankPressure * 100000.0; // bar a Pa nodes.Add(new HydraulicNodeDefinition(Nombre, true, pressurePa, GetTankDescription())); //Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {pressurePa:F0} Pa ({TankPressure: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 flows, Dictionary pressures) { try { // Aplicar resultados a través del TSNet Adapter if (TSNetAdapter?.Results != null) { // Actualizar presión desde TSNet if (pressures.ContainsKey(TSNetAdapter.TankId)) { var pressurePa = pressures[TSNetAdapter.TankId]; CurrentPressure = pressurePa / 100000.0; // Convertir Pa a bar TSNetAdapter.Results.CalculatedPressureBar = CurrentPressure; Debug.WriteLine($"Tank {Nombre}: Presión TSNet={pressurePa:F0} Pa = {CurrentPressure:F3} bar"); } // Calcular flujo neto desde pipes conectadas var netFlowM3s = CalculateNetFlowFromConnectedPipes(flows); NetFlow = netFlowM3s * 60000.0; // Convertir m³/s a L/min TSNetAdapter.Results.NetFlowM3s = NetFlow; // Actualizar nivel basado en balance de flujo solo si hay cambio if (Math.Abs(NetFlow) > 0.001) // Umbral mínimo de 0.001 L/min { var deltaTime = GetDeltaTime(); UpdateLevelFromFlowBalance(deltaTime); } // Actualizar resultados del adapter TSNetAdapter.Results.CalculatedLevelM = CurrentLevelM; TSNetAdapter.Results.CalculatedVolumeL = CurrentVolumeL; TSNetAdapter.Results.Timestamp = DateTime.Now; TSNetAdapter.Results.Status = $"Level: {CurrentLevelM:F2}m, Flow: {NetFlow:F1}L/min"; } // Notificar cambios para UI OnPropertyChanged(nameof(CurrentLevelM)); OnPropertyChanged(nameof(CurrentVolumeL)); OnPropertyChanged(nameof(FillPercentage)); OnPropertyChanged(nameof(NetFlow)); OnPropertyChanged(nameof(CurrentPressure)); // Debug periódico if (Environment.TickCount % 2000 < 50) // Cada ~2 segundos { Debug.WriteLine($"Tank {Nombre}: Level={CurrentLevelM:F2}m, NetFlow={NetFlow:F1}L/min, Pressure={CurrentPressure:F3}bar"); } } catch (Exception ex) { Debug.WriteLine($"Error en Tank {Nombre} ApplyHydraulicResults: {ex.Message}"); } } 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); } } /// /// 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); } } /// /// 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; /// /// 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 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; // Inicializar configuraciones del tanque if (_maxLevelM <= 0) _maxLevelM = 2.0; if (_minLevelM < 0 || _minLevelM >= _maxLevelM) _minLevelM = 0.0; if (string.IsNullOrEmpty(_tankType)) _tankType = "Standard"; SafeUpdateVolumeCalculations(); SafeUpdateTankPressure(); 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 * _crossSectionalArea * 1000.0; // convertir m³ a L _maxVolumeL = 2.0 * _crossSectionalArea * 1000.0; // Assuming 2m as max height // Asegurar que los volúmenes de fluidos no excedan el volumen total var totalFluidVolume = _primaryVolumeL + _secondaryVolumeL; 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) { // TankPressure está en bar, CurrentPressure también debe estar en bar CurrentPressure = TankPressure; } else { // Calcular presión basada solo en volumen primario (para cálculos hidráulicos) var primaryLevel = PrimaryVolumeL / (CrossSectionalArea * 1000.0); // Nivel equivalente del volumen primario var hydrostaticPressure = primaryLevel * 9.81 * CurrentFluidDensity / 100000.0; // Convertir a bar CurrentPressure = 1.013 + hydrostaticPressure; // Presión atmosférica + hidráulica (todo en bar) } // Debug para verificar las unidades Debug.WriteLine($"Tanque {Nombre}: TankPressure={TankPressure:F3} bar, CurrentPressure={CurrentPressure:F3} bar"); } catch (Exception ex) { Debug.WriteLine($"Error in SafeUpdateTankPressure: {ex.Message}"); // Usar presión atmosférica por defecto (en bar) _currentPressure = 1.013; } } 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 flows) { NetFlow = 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 conexiones que involucren este tanque if (branchName.Contains(Nombre)) { // Determinar dirección del flujo basado en el nombre de la rama if (IsFlowTowardsTank(branchName)) { NetFlow += flowValueLmin; // Flujo hacia el tanque (positivo) } else { NetFlow -= flowValueLmin; // Flujo desde el tanque (negativo) } } } } 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 "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 } /// /// Calcula el flujo neto desde pipes conectadas usando resultados de TSNet /// private double CalculateNetFlowFromConnectedPipes(Dictionary flows) { double netFlow = 0.0; if (_mainViewModel?.ObjetosSimulables == null) return netFlow; try { // Buscar todas las pipes conectadas a este tanque var connectedPipes = _mainViewModel.ObjetosSimulables .OfType() .Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre) .ToList(); foreach (var pipe in connectedPipes) { // Buscar el flujo de la pipe en los resultados de TSNet var pipeElementName = $"{pipe.Nombre}_Pipe"; if (flows.ContainsKey(pipeElementName)) { var pipeFlow = flows[pipeElementName]; // m³/s // Determinar dirección: positivo si entra al tanque, negativo si sale if (pipe.Id_ComponenteB == Nombre) { // La pipe va hacia este tanque netFlow += pipeFlow; } else if (pipe.Id_ComponenteA == Nombre) { // La pipe sale desde este tanque netFlow -= pipeFlow; } } } return netFlow; } catch (Exception ex) { Debug.WriteLine($"Error calculating net flow for tank {Nombre}: {ex.Message}"); return 0.0; } } /// /// Actualiza el color del tanque basado en el fluido actual /// private void UpdateTankColorFromFluid() { try { var currentFluid = CurrentOutputFluid; if (currentFluid != null) { var colorHex = currentFluid.Color; var color = (Color)ColorConverter.ConvertFromString(colorHex); ColorButton_oculto = new SolidColorBrush(color); } else { ColorButton_oculto = Brushes.LightBlue; // Color por defecto } } catch { ColorButton_oculto = Brushes.LightBlue; // Color por defecto en caso de error } } // 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(); // Reinicializar TSNet Adapter si es necesario if (TSNetAdapter == null) { TSNetAdapter = new TSNetTankAdapter(this); Debug.WriteLine($"Tank {Nombre}: TSNetAdapter inicializado en 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() { // En el nuevo sistema unificado, el HydraulicSimulationManager // maneja automáticamente el registro/desregistro de objetos base.ucUnLoaded(); } } }