using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using CtrEditor.HydraulicSimulator;
using CtrEditor.HydraulicSimulator.TSNet.Components;
using CtrEditor.ObjetosSim;
using CtrEditor.FuncionesBase;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CommunityToolkit.Mvvm.ComponentModel;
using LibS7Adv;
namespace CtrEditor.ObjetosSim
{
///
/// Tanque hidráulico simplificado para integración con TSNet
///
public partial class osHydTank : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
{
public static string NombreCategoria() => "Componentes Hidráulicos";
public static string NombreClase() => "Tanque Hidráulico";
// Tipos de fluido disponibles
public enum FluidTypeId
{
Water = 0,
SyrupBrix40 = 1
}
// Private Fields básicos para TSNet
private double _elevation = 0.0; // m - elevación del fondo del tanque
private double _initialLevel = 1.0; // m - nivel inicial
private double _minLevel = 0.0; // m - nivel mínimo
private double _maxLevel = 2.0; // m - nivel máximo
private double _diameter = 1.0; // m - diámetro del tanque
private FluidTypeId _fluidType = FluidTypeId.Water;
// Estado actual del tanque (desde TSNet)
private double _currentLevel = 1.0; // m
private double _currentPressure = 1.013; // bar
private double _currentFlow = 0.0; // m³/s
// TSNet Integration
[JsonIgnore]
public TSNetTankAdapter? TSNetAdapter { get; private set; }
// Propiedades visuales
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Tamaño visual del tanque")]
[property: Name("Tamaño")]
public float tamano;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Color visual del tanque")]
[property: Name("Color")]
private Brush colorButton_oculto = Brushes.LightBlue;
// Constructor
public osHydTank()
{
try
{
Nombre = "Tanque Hidráulico";
Tamano = 1.0f;
Ancho = 0.30f;
Alto = 0.40f;
Angulo = 0f;
Lock_movement = false;
Debug.WriteLine($"osHydTank Constructor completado correctamente");
}
catch (Exception ex)
{
Debug.WriteLine($"Error en constructor osHydTank: {ex.Message}");
Nombre = "Tanque Hidráulico";
Tamano = 1.0f;
}
}
// Propiedades de configuración del tanque
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Elevación")]
[Description("Elevación del fondo del tanque (m)")]
public double Elevation
{
get => _elevation;
set
{
if (SetProperty(ref _elevation, value))
{
InvalidateHydraulicNetwork();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Nivel inicial")]
[Description("Nivel inicial del agua en el tanque (m)")]
public double InitialLevel
{
get => _initialLevel;
set
{
if (SetProperty(ref _initialLevel, Math.Max(0, value)))
{
InvalidateHydraulicNetwork();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Nivel mínimo")]
[Description("Nivel mínimo del tanque (m)")]
public double MinLevel
{
get => _minLevel;
set
{
if (SetProperty(ref _minLevel, Math.Max(0, value)))
{
InvalidateHydraulicNetwork();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Nivel máximo")]
[Description("Nivel máximo del tanque (m)")]
public double MaxLevel
{
get => _maxLevel;
set
{
if (SetProperty(ref _maxLevel, Math.Max(_minLevel + 0.1, value)))
{
InvalidateHydraulicNetwork();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Diámetro")]
[Description("Diámetro del tanque (m)")]
public double Diameter
{
get => _diameter;
set
{
if (SetProperty(ref _diameter, Math.Max(0.1, value)))
{
InvalidateHydraulicNetwork();
}
}
}
[Category("🧪 Fluido")]
[DisplayName("Tipo de fluido")]
[Description("Tipo de fluido en el tanque")]
public FluidTypeId FluidType
{
get => _fluidType;
set
{
if (SetProperty(ref _fluidType, value))
{
UpdateTankColorFromFluid();
OnPropertyChanged(nameof(FluidDescription));
}
}
}
[Category("🧪 Fluido")]
[DisplayName("Descripción del fluido")]
[Description("Descripción del tipo de fluido")]
[ReadOnly(true)]
public string FluidDescription
{
get
{
return FluidType switch
{
FluidTypeId.Water => "Agua",
FluidTypeId.SyrupBrix40 => "Jarabe Brix 40%",
_ => "Desconocido"
};
}
}
// Estado actual (desde TSNet)
[Category("📊 Estado Actual")]
[DisplayName("Nivel actual")]
[Description("Nivel actual del agua en el tanque (m)")]
[ReadOnly(true)]
public double CurrentLevel
{
get => _currentLevel;
private set => SetProperty(ref _currentLevel, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Presión actual")]
[Description("Presión actual en el tanque (bar)")]
[ReadOnly(true)]
public double CurrentPressure
{
get => _currentPressure;
private set => SetProperty(ref _currentPressure, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Flujo actual")]
[Description("Flujo actual hacia/desde el tanque (m³/s)")]
[ReadOnly(true)]
public double CurrentFlow
{
get => _currentFlow;
private set => SetProperty(ref _currentFlow, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Volumen actual")]
[Description("Volumen actual en el tanque (L)")]
[ReadOnly(true)]
public double CurrentVolume
{
get
{
var area = Math.PI * Math.Pow(_diameter / 2, 2); // m²
return _currentLevel * area * 1000; // L
}
}
[Category("📊 Estado Actual")]
[DisplayName("Porcentaje de llenado")]
[Description("Porcentaje de llenado del tanque")]
[ReadOnly(true)]
public double FillPercentage
{
get
{
if (_maxLevel <= _minLevel) return 0;
return Math.Max(0, Math.Min(100, (_currentLevel - _minLevel) / (_maxLevel - _minLevel) * 100));
}
}
///
/// Nivel actual en metros (alias para compatibilidad)
///
[Category("📊 Estado Actual")]
[DisplayName("Nivel actual (m)")]
[Description("Nivel actual del líquido en metros")]
public double CurrentLevelM
{
get => _currentLevel;
set => SetProperty(ref _currentLevel, value);
}
///
/// Balance de flujo neto del tanque
///
[Category("📊 Estado Actual")]
[DisplayName("Balance de flujo")]
[Description("Balance neto de flujo del tanque (positivo = entrada, negativo = salida)")]
[ReadOnly(true)]
public double FlowBalance => _currentFlow;
///
/// Tipo de tanque para clasificación
///
[Category("📊 Estado Actual")]
[DisplayName("Tipo de tanque")]
[Description("Tipo de tanque para clasificación")]
[ReadOnly(true)]
public string TankType => "Simplified";
///
/// Descripción actual del fluido
///
[Category("📊 Estado Actual")]
[DisplayName("Descripción fluido actual")]
[Description("Descripción detallada del fluido actual")]
[ReadOnly(true)]
public string CurrentFluidDescription => FluidDescription;
///
/// Nivel máximo en metros (alias para compatibilidad)
///
[Category("📏 Dimensiones")]
[DisplayName("Nivel máximo (m)")]
[Description("Nivel máximo del tanque en metros")]
public double MaxLevelM
{
get => _maxLevel;
set => SetProperty(ref _maxLevel, value);
}
///
/// Nivel mínimo en metros (alias para compatibilidad)
///
[Category("📏 Dimensiones")]
[DisplayName("Nivel mínimo (m)")]
[Description("Nivel mínimo del tanque en metros")]
public double MinLevelM
{
get => _minLevel;
set => SetProperty(ref _minLevel, value);
}
///
/// Área de sección transversal del tanque
///
[Category("📏 Dimensiones")]
[DisplayName("Área sección transversal")]
[Description("Área de sección transversal del tanque (m²)")]
public double CrossSectionalArea
{
get => Math.PI * Math.Pow(_diameter / 2.0, 2);
set => _diameter = Math.Sqrt(value / Math.PI) * 2.0; // Calcular diámetro desde área
}
///
/// Presión del tanque
///
[Category("📊 Estado Actual")]
[DisplayName("Presión del tanque")]
[Description("Presión actual del tanque (Pa)")]
public double TankPressure { get; set; } = 101325.0; // 1 atm por defecto
///
/// Si el tanque tiene presión fija
///
[Category("⚙️ Configuración")]
[DisplayName("Presión fija")]
[Description("Indica si el tanque mantiene presión fija")]
public bool IsFixedPressure { get; set; } = false;
// Métodos básicos
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)
{
// Movimiento básico
}
public override void OnResize(float Delta_Width, float Delta_Height)
{
Tamano += Delta_Width + Delta_Height;
}
public override void UpdateGeometryStart()
{
// El componente se registra automáticamente como IHydraulicComponent
}
public override void UpdateControl(int elapsedMilliseconds)
{
// Los valores provienen de TSNet
OnPropertyChanged(nameof(CurrentVolume));
OnPropertyChanged(nameof(FillPercentage));
UpdateTankColorFromFluid();
}
private void UpdateTankColorFromFluid()
{
ColorButton_oculto = FluidType switch
{
FluidTypeId.Water => Brushes.LightBlue,
FluidTypeId.SyrupBrix40 => Brushes.Orange,
_ => Brushes.Gray
};
}
private void InvalidateHydraulicNetwork()
{
// Invalidar la red hidráulica para que se recalcule
var mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel;
mainViewModel?.InvalidateHydraulicNetwork();
}
// Implementación de interfaces hidráulicas
public void ApplyHydraulicPressure(double pressurePa)
{
CurrentPressure = pressurePa / 100000.0; // Pa a bar
}
public void ApplyHydraulicFlow(double flowM3s)
{
CurrentFlow = flowM3s;
}
public void UpdateHydraulicState(double timeSeconds)
{
// TSNet maneja el estado
}
// Métodos para TSNet
public void UpdateFromTSNetResults(double level, double pressure, double flow)
{
CurrentLevel = Math.Max(_minLevel, Math.Min(_maxLevel, level));
CurrentPressure = pressure / 100000.0; // Pa a bar
CurrentFlow = flow;
}
// Crear nodo para TSNet
public TSNetTankAdapter CreateTSNetAdapter()
{
TSNetAdapter = new TSNetTankAdapter(this);
return TSNetAdapter;
}
// Implementación de IHydraulicComponent
[JsonIgnore]
public bool HasHydraulicComponents => true;
public List GetHydraulicNodes()
{
var nodes = new List();
// Sanitizar el nombre para compatibilidad con EPANET
var sanitizedName = SanitizeNodeName(Nombre);
// Tanque como nodo libre por defecto
nodes.Add(new HydraulicNodeDefinition(sanitizedName, false, null, $"Tanque hidráulico - {FluidDescription}"));
return nodes;
}
///
/// Sanitiza nombres de nodos para compatibilidad con EPANET INP
///
private string SanitizeNodeName(string nodeName)
{
if (string.IsNullOrEmpty(nodeName))
return "UnknownTank";
// Reemplazar espacios y caracteres especiales con guiones bajos
var sanitized = nodeName
.Replace(" ", "_")
.Replace("á", "a")
.Replace("é", "e")
.Replace("í", "i")
.Replace("ó", "o")
.Replace("ú", "u")
.Replace("ü", "u")
.Replace("ñ", "n")
.Replace("Á", "A")
.Replace("É", "E")
.Replace("Í", "I")
.Replace("Ó", "O")
.Replace("Ú", "U")
.Replace("Ü", "U")
.Replace("Ñ", "N");
// Remover caracteres no válidos para EPANET
var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray();
var result = new string(validChars);
// Asegurar que empiece con una letra
if (!string.IsNullOrEmpty(result) && !char.IsLetter(result[0]))
{
result = "Tank_" + result;
}
return string.IsNullOrEmpty(result) ? "UnknownTank" : result;
}
public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures)
{
try
{
// Sanitizar el nombre para compatibilidad con EPANET
var sanitizedName = SanitizeNodeName(Nombre);
// Actualizar presión desde TSNet
if (pressures.ContainsKey(sanitizedName))
{
var pressurePa = pressures[sanitizedName];
CurrentPressure = pressurePa / 100000.0; // Convertir Pa a bar
}
// Calcular flujo neto desde pipes conectadas
var netFlowM3s = CalculateNetFlowFromConnectedPipes(flows);
CurrentFlow = netFlowM3s;
// Actualizar nivel basado en balance de flujo
UpdateLevelFromFlowBalance(netFlowM3s);
}
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
}
// Implementación de IHydraulicFlowReceiver
public void SetFlow(double flow)
{
CurrentFlow = flow;
}
public double GetFlow()
{
return CurrentFlow;
}
// Implementación de IHydraulicPressureReceiver
public void SetPressure(double pressure)
{
CurrentPressure = pressure / 100000.0; // Pa a bar
}
public double GetPressure()
{
return CurrentPressure * 100000.0; // bar a Pa
}
// Implementación de IosBase
public void Start()
{
// Inicialización del tanque para la simulación
}
public void Inicializar(int valorInicial)
{
// Inicialización con valor inicial
}
public void Disposing()
{
// Limpieza si es necesaria
}
public int zIndex_fromFrames { get; set; } = 2;
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
public override void CopyFrom(osBase source)
{
if (source is osHydTank sourceTank)
{
Elevation = sourceTank.Elevation;
InitialLevel = sourceTank.InitialLevel;
MinLevel = sourceTank.MinLevel;
MaxLevel = sourceTank.MaxLevel;
Diameter = sourceTank.Diameter;
FluidType = sourceTank.FluidType;
}
base.CopyFrom(source);
}
// Métodos privados auxiliares
private double CalculateNetFlowFromConnectedPipes(Dictionary flows)
{
double netFlow = 0.0;
var mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel;
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_{pipe.Nombre}";
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;
}
}
private void UpdateLevelFromFlowBalance(double netFlowM3s)
{
if (Math.Abs(netFlowM3s) < 1e-6) return; // Flujo muy pequeño
// Calcular área transversal
var area = Math.PI * Math.Pow(_diameter / 2, 2); // m²
// Tiempo de simulación (asumimos 1 segundo por simplicidad)
var deltaTime = 1.0; // segundos
// Cambio de nivel = flujo volumétrico * tiempo / área
var deltaLevel = (netFlowM3s * deltaTime) / area; // m
// Actualizar nivel actual
var newLevel = _currentLevel + deltaLevel;
CurrentLevel = Math.Max(_minLevel, Math.Min(_maxLevel, newLevel));
}
}
}