CtrEditor/ObjetosSim/HydraulicComponents/osHydDischargeTank.cs

409 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// Tanque de descarga hidráulico con cálculo de nivel dinámico
/// </summary>
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<IHydraulicComponent>))]
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<float, float> ActualizarTamaño { get; set; }
#endregion
#region IHydraulicComponent Implementation
public bool HasHydraulicComponents => true;
public List<HydraulicNodeDefinition> GetHydraulicNodes()
{
var nodes = new List<HydraulicNodeDefinition>();
// 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<HydraulicElementDefinition> GetHydraulicElements()
{
// Los tanques no son elementos, son nodos
return new List<HydraulicElementDefinition>();
}
public void UpdateHydraulicProperties()
{
// Actualizar presión basada en el nivel actual
UpdateHeightAndPressure();
}
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> 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;
}
/// <summary>
/// Actualiza el volumen del tanque basado en flujos netos según la documentación
/// </summary>
/// <param name="flows">Diccionario de flujos de la simulación</param>
/// <param name="deltaTime">Intervalo de tiempo en segundos</param>
public void UpdateVolume(Dictionary<string, double> 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();
}
/// <summary>
/// Calcula la presión en el fondo del tanque según la documentación
/// </summary>
/// <param name="density">Densidad del fluido (kg/m³)</param>
/// <returns>Presión hidrostática en Pa</returns>
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
}
}