using System; using System.Collections.Generic; using System.ComponentModel; 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.HydraulicComponents { /// /// Tanque de descarga hidráulico con cálculo de nivel dinámico /// public partial class osHydDischargeTank : osBase, IHydraulicTank, IosBase { #region Properties private double _area = 1.0; // m² private double _currentVolume = 0.5; // m³ private double _maxVolume = 2.0; // m³ private double _minVolume = 0.0; // m³ private double _currentHeight = 0.0; private double _currentPressure = 0.0; // Propiedades visuales [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Ancho visual del tanque en metros")] [property: Name("Ancho")] private float ancho = 0.5f; [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Alto visual del tanque en metros")] [property: Name("Alto")] private float alto = 0.8f; [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Color visual del tanque")] [property: Name("Color")] private Brush colorButton_oculto = Brushes.LightBlue; [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Ángulo de rotación del tanque en grados")] [property: Name("Ángulo")] private float angulo = 0f; [Category("🔧 Tanque Hidráulico")] [Description("Área de la base del tanque en m² (alias para compatibilidad)")] [Name("Área Base (m²)")] public double Area { get => _area; set => SetProperty(ref _area, Math.Max(0.1, value)); } [Category("🔧 Tanque Hidráulico")] [Description("Presión del tanque en Pa")] [Name("Presión Tanque (Pa)")] public double TankPressure { get => _currentPressure; set => SetProperty(ref _currentPressure, value); } [Category("🔧 Tanque Hidráulico")] [Description("Nivel actual del tanque en metros")] [Name("Nivel (m)")] public double Level { get => _currentHeight; set { SetProperty(ref _currentHeight, value); CurrentVolume = value * Area; } } [Category("🔧 Tanque Hidráulico")] [Description("Área de la sección transversal del tanque en m²")] [Name("Área Sección (m²)")] public double CrossSectionalArea { get => _area; set => SetProperty(ref _area, Math.Max(0.1, value)); } [Category("🔧 Tanque Hidráulico")] [Description("Indica si el tanque tiene presión fija")] [Name("Presión Fija")] [ReadOnly(true)] public bool IsFixedPressure => false; // Los tanques de descarga tienen presión calculada [Category("🔧 Tanque Hidráulico")] [Description("Volumen actual del tanque en m³")] [Name("Volumen Actual (m³)")] public double CurrentVolume { get => _currentVolume; set => SetProperty(ref _currentVolume, Math.Max(MinVolume, Math.Min(MaxVolume, value))); } [Category("🔧 Tanque Hidráulico")] [Description("Volumen máximo del tanque en m³")] [Name("Volumen Máximo (m³)")] public double MaxVolume { get => _maxVolume; set => SetProperty(ref _maxVolume, Math.Max(CurrentVolume, value)); } [Category("🔧 Tanque Hidráulico")] [Description("Volumen mínimo del tanque en m³")] [Name("Volumen Mínimo (m³)")] public double MinVolume { get => _minVolume; set => SetProperty(ref _minVolume, Math.Max(0.0, Math.Min(CurrentVolume, value))); } [ObservableProperty] [property: Category("🔗 Conexiones")] [property: Description("Componente conectado en la entrada")] [property: Name("Componente Entrada")] [property: ItemsSource(typeof(osBaseItemsSource))] private string id_InletComponent = ""; [Category("📊 Estado")] [Description("Altura actual del líquido en metros")] [Name("Altura Actual (m)")] [ReadOnly(true)] public double CurrentHeight { get => _currentHeight; set => SetProperty(ref _currentHeight, value); } [Category("📊 Estado")] [Description("Presión hidrostática en el fondo del tanque en Pa")] [Name("Presión Fondo (Pa)")] [ReadOnly(true)] public double CurrentPressure { get => _currentPressure; set => SetProperty(ref _currentPressure, value); } [Category("📊 Estado")] [Description("Porcentaje de llenado del tanque")] [Name("Nivel (%)")] [ReadOnly(true)] public double FillPercentage => MaxVolume > 0 ? (CurrentVolume / MaxVolume) * 100.0 : 0.0; #endregion #region Component References [JsonIgnore] private IHydraulicComponent InletComponent = null; [JsonIgnore] private PropertyChangedEventHandler inletPropertyChangedHandler; [JsonIgnore] public Action ActualizarTamaño { get; set; } #endregion #region IHydraulicComponent Implementation public bool HasHydraulicComponents => true; public List GetHydraulicNodes() { var nodes = new List(); // El tanque de descarga es un nodo con presión libre (calculada) nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, "Tanque de descarga")); return nodes; } public List GetHydraulicElements() { // Los tanques no son elementos, son nodos return new List(); } public void UpdateHydraulicProperties() { // Actualizar presión basada en el nivel actual UpdateHeightAndPressure(); } public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) { // Actualizar presión del tanque if (pressures.TryGetValue(Nombre, out double pressure)) { TankPressure = pressure; // Calcular nivel basado en la presión hidrostática Level = pressure / (1000.0 * 9.81); // Asumiendo densidad del agua } // Calcular flujo neto hacia el tanque double netFlow = 0.0; foreach (var flow in flows) { if (flow.Key.EndsWith($"->{Nombre}")) netFlow += flow.Value; // Flujo entrante else if (flow.Key.StartsWith($"{Nombre}->")) netFlow -= flow.Value; // Flujo saliente } // Aquí podrías actualizar el volumen en función del tiempo // Esto se haría en el update loop principal de la simulación } #endregion #region IHydraulicPressureReceiver Implementation public void SetPressure(double pressure) { TankPressure = pressure; // Actualizar nivel basado en presión hidrostática Level = pressure / (1000.0 * 9.81); } public double GetPressure() { return TankPressure; } #endregion #region Connection Management partial void OnId_InletComponentChanged(string value) { if (InletComponent != null && inletPropertyChangedHandler != null) ((INotifyPropertyChanged)InletComponent).PropertyChanged -= inletPropertyChangedHandler; if (_mainViewModel != null && !string.IsNullOrEmpty(value)) { InletComponent = (IHydraulicComponent)_mainViewModel.ObjetosSimulables .FirstOrDefault(s => s is IHydraulicComponent comp && ((osBase)comp).Nombre == value); if (InletComponent != null) { inletPropertyChangedHandler = (sender, e) => { if (e.PropertyName == nameof(osBase.Nombre)) { Id_InletComponent = ((osBase)sender).Nombre; } }; ((INotifyPropertyChanged)InletComponent).PropertyChanged += inletPropertyChangedHandler; } } } #endregion #region Volume and Level Calculations private void UpdateHeightAndPressure() { Level = Area > 0 ? CurrentVolume / Area : 0.0; // Presión hidrostática en el fondo del tanque (densidad del agua = 1000 kg/m³) TankPressure = 1000.0 * 9.81 * Level; } /// /// Actualiza el volumen del tanque basado en flujos netos según la documentación /// /// Diccionario de flujos de la simulación /// Intervalo de tiempo en segundos public void UpdateVolume(Dictionary flows, double deltaTime) { double netFlow = 0.0; // Sumar flujos entrantes, restar salientes foreach (var flow in flows) { if (flow.Key.EndsWith($"->{Nombre}")) netFlow += flow.Value; // Entrante else if (flow.Key.StartsWith($"{Nombre}->")) netFlow -= flow.Value; // Saliente } // Actualizar volumen CurrentVolume += netFlow * deltaTime; CurrentVolume = Math.Max(MinVolume, Math.Min(MaxVolume, CurrentVolume)); // Actualizar altura y presión UpdateHeightAndPressure(); } /// /// Calcula la presión en el fondo del tanque según la documentación /// /// Densidad del fluido (kg/m³) /// Presión hidrostática en Pa public double BottomPressure(double density = 1000.0) { return density * 9.81 * Level; // ρgh } #endregion #region osBase Implementation public static string NombreCategoria() => "Componentes Hidráulicos"; public static string NombreClase() => "Tanque de Descarga"; 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 AltoChanged(float value) { ActualizarGeometrias(); } public override void AnchoChanged(float value) { ActualizarGeometrias(); } public void Start() { // Los tanques no participan en la simulación física Bepu // Solo en el sistema hidráulico UpdateHeightAndPressure(); } public override void UpdateGeometryStart() { ActualizarGeometrias(); } public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) { // Los tanques pueden tener sensores de nivel // Implementar aquí si es necesario } private void ActualizarGeometrias() { try { // Actualizar geometría visual if (this.ActualizarTamaño != null) this.ActualizarTamaño(Ancho, Alto); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error actualizando geometría: {ex.Message}"); } } public void Inicializar(int valorInicial) { UpdateHeightAndPressure(); OnId_InletComponentChanged(Id_InletComponent); } public void Disposing() { if (InletComponent != null && inletPropertyChangedHandler != null) ((INotifyPropertyChanged)InletComponent).PropertyChanged -= inletPropertyChangedHandler; } #endregion #region Constructor public osHydDischargeTank() { UpdateHeightAndPressure(); } #endregion } }