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.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 debe ser un nodo de presión fija para servir como referencia del sistema
// Presión = presión atmosférica + presión hidrostática
double pressureReference = 101325.0 + TankPressure; // Pa (1 atm + presión hidrostática)
nodes.Add(new HydraulicNodeDefinition(Nombre, true, pressureReference, "Tanque de descarga (referencia)"));
Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {pressureReference:F0} Pa ({pressureReference/100000:F2} bar)");
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
}
}