CtrEditor/ObjetosSim/HydraulicComponents/osHydTank.cs

819 lines
26 KiB
C#

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 hidráulico con gestión dinámica de nivel y presión configurable
/// </summary>
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 = 101325.0; // Pa (1 atm por defecto)
private double _currentLevel = 1.0; // m
private double _maxLevel = 2.0; // m
private double _minLevel = 0.1; // m
private double _crossSectionalArea = 1.0; // m²
private double _currentVolume = 1.0; // m³
private double _maxVolume = 2.0; // m³
private double _inletFlow = 0.0; // m³/s
private double _outletFlow = 0.0; // m³/s
private double _currentPressure = 101325.0; // Pa
private bool _isFixedPressure = true;
private HydraulicTankType _tankType = HydraulicTankType.Intermediate;
private string _connectedInletPipe = "";
private string _connectedOutletPipe = "";
private double _lastUpdateTime = 0.0;
[JsonIgnore]
private object _levelLock = new object();
private PropertyChangedEventHandler? inletPropertyChangedHandler;
private PropertyChangedEventHandler? outletPropertyChangedHandler;
// 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: JsonIgnore]
[property: Category("Apariencia")]
[property: Description("Imagen visual")]
[property: Name("Imagen")]
public ImageSource imageSource_oculta;
[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()
{
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;
ImageSource_oculta = ImageFromPath("/imagenes/tank.png");
_currentVolume = _currentLevel * _crossSectionalArea;
_maxVolume = _maxLevel * _crossSectionalArea;
IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros
UpdateTankPressure();
// Inicializar el lock para thread safety
EnsureLockInitialized();
// 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}");
}
// 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()
{
// Los objetos hidráulicos se registran automáticamente
// cuando inicia la simulación a través de las interfaces
}
public override void UpdateGeometryStep()
{
// Los objetos hidráulicos actualizan sus resultados
// a través de ApplyHydraulicResults()
}
public override void UpdateControl(int elapsedMilliseconds)
{
// Actualizar el color según el estado del tanque
var percentage = FillPercentage;
if (percentage < 20)
ColorButton_oculto = Brushes.Red;
else if (percentage < 50)
ColorButton_oculto = Brushes.Orange;
else if (percentage < 80)
ColorButton_oculto = Brushes.Yellow;
else
ColorButton_oculto = Brushes.LightBlue;
}
// 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)))
{
UpdateVolumeCalculations();
InvalidateHydraulicNetwork();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Nivel máximo")]
[Description("Nivel máximo permitido en el tanque (m)")]
public double MaxLevel
{
get => _maxLevel;
set
{
if (SetProperty(ref _maxLevel, Math.Max(0.2, value)))
{
if (_currentLevel > _maxLevel)
CurrentLevel = _maxLevel;
UpdateVolumeCalculations();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Nivel mínimo")]
[Description("Nivel mínimo permitido en el tanque (m)")]
public double MinLevel
{
get => _minLevel;
set
{
if (SetProperty(ref _minLevel, Math.Max(0.0, Math.Min(value, _maxLevel - 0.1))))
{
if (_currentLevel < _minLevel)
CurrentLevel = _minLevel;
UpdateVolumeCalculations();
}
}
}
// 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))
{
UpdateTankPressure();
InvalidateHydraulicNetwork();
}
}
}
[Category("🔧 Sistema de Presión")]
[DisplayName("Presión del tanque")]
[Description("Presión mantenida en el tanque (Pa). En sistemas reales controlada por PID")]
public double TankPressure
{
get => _tankPressure;
set
{
if (SetProperty(ref _tankPressure, Math.Max(0, value)))
{
UpdateTankPressure();
InvalidateHydraulicNetwork();
}
}
}
[Category("🔧 Sistema de Presión")]
[DisplayName("Presión (bar)")]
[Description("Presión del tanque en bar")]
[JsonIgnore]
public double TankPressureBar
{
get => TankPressure / 100000.0;
set => TankPressure = value * 100000.0;
}
// Properties - Current State
[Category("📊 Estado Actual")]
[DisplayName("Nivel actual")]
[Description("Nivel actual de líquido en el tanque (m)")]
[JsonIgnore]
public double CurrentLevel
{
get => _currentLevel;
private set
{
EnsureLockInitialized();
lock (_levelLock)
{
var clampedLevel = Math.Max(_minLevel, Math.Min(_maxLevel, value));
if (SetProperty(ref _currentLevel, clampedLevel))
{
UpdateVolumeCalculations();
OnPropertyChanged(nameof(FillPercentage));
}
}
}
}
[Category("📊 Estado Actual")]
[DisplayName("Volumen actual")]
[Description("Volumen actual de líquido en el tanque (m³)")]
[JsonIgnore]
public double CurrentVolume
{
get => _currentVolume;
private set => SetProperty(ref _currentVolume, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Volumen máximo")]
[Description("Volumen máximo del tanque (m³)")]
[JsonIgnore]
public double MaxVolume
{
get => _maxVolume;
private set => SetProperty(ref _maxVolume, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Porcentaje llenado")]
[Description("Porcentaje de llenado del tanque")]
[JsonIgnore]
public double FillPercentage => (_currentLevel - _minLevel) / (_maxLevel - _minLevel) * 100.0;
[Category("📊 Estado Actual")]
[DisplayName("Flujo entrada")]
[Description("Flujo de entrada actual (m³/s)")]
[JsonIgnore]
public double InletFlow
{
get => _inletFlow;
private set => SetProperty(ref _inletFlow, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Flujo salida")]
[Description("Flujo de salida actual (m³/s)")]
[JsonIgnore]
public double OutletFlow
{
get => _outletFlow;
private set => SetProperty(ref _outletFlow, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Balance flujo")]
[Description("Balance de flujo (entrada - salida) (m³/s)")]
[JsonIgnore]
public double FlowBalance => InletFlow - OutletFlow;
[Category("📊 Estado Actual")]
[DisplayName("Presión actual")]
[Description("Presión actual en el tanque (Pa)")]
[JsonIgnore]
public double CurrentPressure
{
get => _currentPressure;
private set => SetProperty(ref _currentPressure, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Presión actual (bar)")]
[Description("Presión actual en bar")]
[JsonIgnore]
public double CurrentPressureBar => CurrentPressure / 100000.0;
// Properties - Connections
[Category("🔗 Conexiones")]
[DisplayName("Tubería entrada")]
[Description("Nombre de la tubería conectada a la entrada")]
public string ConnectedInletPipe
{
get => _connectedInletPipe;
set
{
if (SetProperty(ref _connectedInletPipe, value ?? ""))
{
ManageInletConnection();
InvalidateHydraulicNetwork();
}
}
}
[Category("🔗 Conexiones")]
[DisplayName("Tubería salida")]
[Description("Nombre de la tubería conectada a la salida")]
public string ConnectedOutletPipe
{
get => _connectedOutletPipe;
set
{
if (SetProperty(ref _connectedOutletPipe, value ?? ""))
{
ManageOutletConnection();
InvalidateHydraulicNetwork();
}
}
}
// IHydraulicComponent Implementation
[JsonIgnore]
public bool HasHydraulicComponents => true;
public List<HydraulicNodeDefinition> GetHydraulicNodes()
{
var nodes = new List<HydraulicNodeDefinition>();
// El tanque siempre es un nodo de presión fija si está configurado así
if (IsFixedPressure)
{
nodes.Add(new HydraulicNodeDefinition(Nombre, true, TankPressure, GetTankDescription()));
Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F0} Pa ({TankPressureBar:F2} bar)");
}
else
{
nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, GetTankDescription()));
Debug.WriteLine($"Tanque {Nombre}: Nodo de presión libre creado");
}
return nodes;
}
public List<HydraulicElementDefinition> GetHydraulicElements()
{
// Los tanques no generan elementos hidráulicos, solo nodos
return new List<HydraulicElementDefinition>();
}
public void UpdateHydraulicProperties()
{
// Actualizar propiedades antes de la simulación
UpdateTankPressure();
UpdateVolumeCalculations();
}
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
{
var deltaTime = GetDeltaTime();
// Obtener flujos conectados
UpdateFlowsFromConnectedPipes(flows);
// Actualizar presión
if (pressures.ContainsKey(Nombre))
{
CurrentPressure = pressures[Nombre];
}
// Actualizar nivel basado en balance de flujo
UpdateLevelFromFlowBalance(deltaTime);
if (VerboseLogging())
{
Debug.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevel:F2}m ({FillPercentage:F1}%), " +
$"Flujo_entrada={InletFlow:F6}m³/s, Flujo_salida={OutletFlow:F6}m³/s, " +
$"Balance={FlowBalance:F6}m³/s, Presión={CurrentPressure:F0}Pa");
}
}
// UI Properties
/// <summary>
/// Color del nivel de líquido
/// </summary>
[JsonIgnore]
public SolidColorBrush LevelColor
{
get
{
var percentage = FillPercentage;
if (percentage < 20) return new SolidColorBrush(Colors.Red);
if (percentage < 50) return new SolidColorBrush(Colors.Orange);
if (percentage < 80) return new SolidColorBrush(Colors.Yellow);
return new SolidColorBrush(Colors.LightBlue);
}
}
/// <summary>
/// Color del borde del nivel de líquido
/// </summary>
[JsonIgnore]
public SolidColorBrush LevelBorderColor
{
get
{
var percentage = FillPercentage;
if (percentage < 20) return new SolidColorBrush(Colors.DarkRed);
if (percentage < 50) return new SolidColorBrush(Colors.DarkOrange);
if (percentage < 80) return new SolidColorBrush(Colors.DarkGoldenrod);
return new SolidColorBrush(Colors.DarkBlue);
}
}
/// <summary>
/// Indica si hay conexión de entrada
/// </summary>
[JsonIgnore]
public bool HasInletConnection => !string.IsNullOrEmpty(ConnectedInletPipe);
/// <summary>
/// Indica si hay conexión de salida
/// </summary>
[JsonIgnore]
public bool HasOutletConnection => !string.IsNullOrEmpty(ConnectedOutletPipe);
/// <summary>
/// Indicador del tipo de tanque
/// </summary>
[JsonIgnore]
public string TankTypeIndicator
{
get
{
return TankType switch
{
HydraulicTankType.Suction => "S",
HydraulicTankType.Intermediate => "I",
HydraulicTankType.Storage => "A",
HydraulicTankType.Process => "P",
_ => "T"
};
}
}
/// <summary>
/// Balance de flujo en L/min
/// </summary>
[JsonIgnore]
public double FlowBalanceLMin => FlowBalance * 60000; // m³/s a L/min
/// <summary>
/// Color del balance de flujo
/// </summary>
[JsonIgnore]
public SolidColorBrush FlowBalanceColor
{
get
{
var balance = FlowBalance;
if (Math.Abs(balance) < 0.0001) return new SolidColorBrush(Colors.Gray);
return balance > 0 ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
}
}
/// <summary>
/// Posición Y de la línea de nivel medio (50%)
/// </summary>
[JsonIgnore]
public double MidLevelY
{
get
{
var tankHeight = Tamano * 100; // Convertir a píxeles aproximados
return tankHeight * 0.5; // 50% de altura
}
}
/// <summary>
/// Posición Y de la línea de nivel mínimo
/// </summary>
[JsonIgnore]
public double MinLevelY
{
get
{
var tankHeight = Tamano * 100; // Convertir a píxeles aproximados
var minPercentage = (MinLevel - MinLevel) / (MaxLevel - MinLevel) * 100;
return tankHeight - (tankHeight * (minPercentage + 10) / 100); // 10% del nivel mínimo
}
}
// Private Methods
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 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 < 50000) // Si no se ha configurado manualmente
TankPressure = 101325.0; // 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 UpdateVolumeCalculations()
{
CurrentVolume = CurrentLevel * CrossSectionalArea;
MaxVolume = MaxLevel * CrossSectionalArea;
}
private void UpdateTankPressure()
{
if (IsFixedPressure)
{
CurrentPressure = TankPressure;
}
}
private void UpdateFlowsFromConnectedPipes(Dictionary<string, double> flows)
{
InletFlow = 0.0;
OutletFlow = 0.0;
// Buscar flujos en las ramas conectadas
foreach (var flow in flows)
{
if (flow.Key.Contains(Nombre))
{
// Determinar si es flujo de entrada o salida según la dirección
if (flow.Key.EndsWith($" -> {Nombre}"))
{
InletFlow += Math.Max(0, flow.Value); // Solo flujos positivos hacia el tanque
}
else if (flow.Key.StartsWith($"{Nombre} -> "))
{
OutletFlow += Math.Max(0, flow.Value); // Solo flujos positivos desde el tanque
}
}
}
}
private void UpdateLevelFromFlowBalance(double deltaTime)
{
if (deltaTime <= 0) return;
// Balance de masa: cambio_volumen = (flujo_entrada - flujo_salida) * tiempo
var volumeChange = FlowBalance * deltaTime;
var levelChange = volumeChange / CrossSectionalArea;
// Actualizar nivel con límites
CurrentLevel = Math.Max(MinLevel, Math.Min(MaxLevel, CurrentLevel + levelChange));
}
private double GetDeltaTime()
{
var currentTime = Environment.TickCount64 / 1000.0; // segundos
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 100ms
}
private bool VerboseLogging()
{
return _mainViewModel?.hydraulicSimulationManager?.VerboseOutput ?? false;
}
private void ManageInletConnection()
{
// Gestionar conexión de entrada
// Implementar lógica de conexión específica si es necesario
}
private void ManageOutletConnection()
{
// Gestionar conexión de salida
// Implementar lógica de conexión específica si es necesario
}
private void InvalidateHydraulicNetwork()
{
_mainViewModel?.hydraulicSimulationManager?.InvalidateNetwork();
}
// 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
UpdateVolumeCalculations();
UpdateTankPressure();
}
public void Inicializar(int valorInicial)
{
// Inicialización con valor inicial
UpdateVolumeCalculations();
UpdateTankPressure();
}
public void Disposing()
{
// Desconectar event handlers si existen
if (inletPropertyChangedHandler != null)
{
// Aquí se desconectarían los handlers cuando se implementen las conexiones
// Similar a como lo hace osHydDischargeTank
}
if (outletPropertyChangedHandler != null)
{
// Desconectar outlet handler cuando se implemente
}
}
// Serialization Support
/// <summary>
/// Se llama antes de la serialización para asegurar que el lock esté disponible
/// </summary>
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();
// Inicialización específica del tanque hidráulico
UpdateVolumeCalculations();
UpdateTankPressure();
// 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()
{
// Los tanques hidráulicos no tienen geometría que limpiar
}
}
}