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 - solo propiedades esenciales /// 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 } // SOLO campos esenciales private double _elevation = 0.0; // m - elevación del fondo del tanque 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 - ÚNICO NIVEL QUE IMPORTA private double _currentLevel = 1.0; // m - nivel actual 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 ESENCIALES - SIMPLIFICADAS ===== [Category("🏗️ Configuración")] [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")] [DisplayName("Nivel máximo")] [Description("Nivel máximo del tanque (m)")] public double MaxLevel { get => _maxLevel; set { var newMaxLevel = Math.Max(0.1, value); if (SetProperty(ref _maxLevel, newMaxLevel)) { // Si el nivel actual está por encima del nuevo máximo, ajustarlo if (_currentLevel > _maxLevel) { var oldCurrent = _currentLevel; _currentLevel = _maxLevel; Debug.WriteLine($"⚠️ osHydTank {Nombre}: CurrentLevel ajustado de {oldCurrent} a {_currentLevel} debido a cambio de MaxLevel"); OnPropertyChanged(nameof(CurrentLevel)); } InvalidateHydraulicNetwork(); } } } [Category("🏗️ Configuración")] [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 - SIMPLIFICADO ===== [Category("📊 Estado Actual")] [DisplayName("Nivel actual")] [Description("Nivel actual del agua en el tanque (m)")] public double CurrentLevel { get => _currentLevel; set { // Validación automática: limitar entre 0 y MaxLevel var clampedValue = Math.Max(0, Math.Min(_maxLevel, value)); if (Math.Abs(value - clampedValue) > 1e-6) { Debug.WriteLine($"⚠️ osHydTank {Nombre}: CurrentLevel ({value}) ajustado a rango válido [0, {_maxLevel}] → {clampedValue}"); } if (SetProperty(ref _currentLevel, clampedValue)) { OnPropertyChanged(nameof(FillPercentage)); OnPropertyChanged(nameof(CurrentVolume)); } } } [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 <= 0) return 0; return Math.Max(0, Math.Min(100, (_currentLevel / _maxLevel) * 100)); } } // ===== PROPIEDADES TÉCNICAS PARA TSNET ===== public double TankPressure { get; set; } = 101325.0; // 1 atm por defecto 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 } public void UpdateFromTSNetResults(double level, double pressure, double flow) { CurrentLevel = Math.Max(0, Math.Min(_maxLevel, level)); CurrentPressure = pressure / 100000.0; // Pa a bar CurrentFlow = flow; } public TSNetTankAdapter CreateTSNetAdapter() { TSNetAdapter = new TSNetTankAdapter(this); return TSNetAdapter; } [JsonIgnore] public bool HasHydraulicComponents => true; public List GetHydraulicNodes() { var nodes = new List(); var sanitizedName = SanitizeNodeName(Nombre); nodes.Add(new HydraulicNodeDefinition(sanitizedName, false, null, $"Tanque hidráulico - {FluidDescription}")); return nodes; } private string SanitizeNodeName(string nodeName) { if (string.IsNullOrEmpty(nodeName)) return "UnknownTank"; var sanitized = nodeName .Replace(" ", "_") .Replace("á", "a").Replace("é", "e").Replace("í", "i").Replace("ó", "o").Replace("ú", "u") .Replace("ñ", "n").Replace("Á", "A").Replace("É", "E").Replace("Í", "I").Replace("Ó", "O") .Replace("Ú", "U").Replace("Ñ", "N"); var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray(); var result = new string(validChars); 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 { Debug.WriteLine($"osHydTank {Nombre} - ApplyHydraulicResults iniciado"); var searchPatterns = new[] { Nombre, SanitizeNodeName(Nombre), $"TANK_{Nombre}", $"Tank_{Nombre}", Nombre.Replace(" ", "_") }; // Buscar presión del tanque foreach (var pattern in searchPatterns) { if (pressures.ContainsKey(pattern)) { CurrentPressure = pressures[pattern] / 100000.0; // Pa a bar Debug.WriteLine($"osHydTank {Nombre} - Presión encontrada: {CurrentPressure:F6} bar"); break; } } // Calcular flujo neto var netFlowM3s = CalculateNetFlowFromConnectedPipes(flows); CurrentFlow = netFlowM3s; Debug.WriteLine($"osHydTank {Nombre} - Flujo neto: {netFlowM3s:F6} m³/s"); // Actualizar nivel basado en balance de flujo UpdateLevelFromFlowBalance(netFlowM3s); Debug.WriteLine($"osHydTank {Nombre} - Nivel después de balance: {CurrentLevel:F6} m"); } catch (Exception ex) { Debug.WriteLine($"Error en osHydTank {Nombre} ApplyHydraulicResults: {ex.Message}"); } } public List GetHydraulicElements() { return new List(); } public void UpdateHydraulicProperties() { // Actualizar propiedades antes de la simulación } public void SetFlow(double flow) => CurrentFlow = flow; public double GetFlow() => CurrentFlow; public void SetPressure(double pressure) => CurrentPressure = pressure / 100000.0; public double GetPressure() => CurrentPressure * 100000.0; // ===== IMPLEMENTACIÓN DE IOSBASE ===== public void Start() { } public void Inicializar(int valorInicial) { } public void Disposing() { } public int zIndex_fromFrames { get; set; } = 2; public ZIndexEnum ZIndex_Base() => ZIndexEnum.Estaticos; public override void CopyFrom(osBase source) { if (source is osHydTank sourceTank) { Elevation = sourceTank.Elevation; MaxLevel = sourceTank.MaxLevel; Diameter = sourceTank.Diameter; FluidType = sourceTank.FluidType; CurrentLevel = sourceTank.CurrentLevel; } 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 { var connectedPipes = mainViewModel.ObjetosSimulables .OfType() .Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre) .ToList(); foreach (var pipe in connectedPipes) { var pipeElementName = $"PIPE_{pipe.Nombre}"; if (flows.ContainsKey(pipeElementName)) { var pipeFlow = flows[pipeElementName]; if (pipe.Id_ComponenteB == Nombre) { netFlow += pipeFlow; // Entra al tanque } else if (pipe.Id_ComponenteA == Nombre) { netFlow -= pipeFlow; // Sale del tanque } } } 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; var area = Math.PI * Math.Pow(_diameter / 2, 2); // m² var deltaTime = 1.0; // segundos var deltaLevel = (netFlowM3s * deltaTime) / area; // m var newLevel = _currentLevel + deltaLevel; CurrentLevel = Math.Max(0, Math.Min(_maxLevel, newLevel)); } // ===== PROPIEDADES DE COMPATIBILIDAD ===== // Estas propiedades mapean las propiedades simplificadas a los nombres legacy [JsonIgnore] [Category("🔧 Compatibilidad")] [DisplayName("Nivel actual (m)")] [Description("Nivel actual del agua (compatibilidad legacy)")] [ReadOnly(true)] public double CurrentLevelM => CurrentLevel; [JsonIgnore] [Category("🔧 Compatibilidad")] [DisplayName("Nivel máximo (m)")] [Description("Nivel máximo del tanque (compatibilidad legacy)")] [ReadOnly(true)] public double MaxLevelM => MaxLevel; [JsonIgnore] [Category("🔧 Compatibilidad")] [DisplayName("Nivel mínimo (m)")] [Description("Nivel mínimo = 0 (siempre)")] [ReadOnly(true)] public double MinLevelM => 0.0; [JsonIgnore] [Category("🔧 Compatibilidad")] [DisplayName("Área transversal")] [Description("Área transversal del tanque (m²)")] [ReadOnly(true)] public double CrossSectionalArea => Math.PI * Math.Pow(_diameter / 2, 2); [JsonIgnore] [Category("🔧 Compatibilidad")] [DisplayName("Balance de flujo")] [Description("Balance de flujo actual (m³/s)")] [ReadOnly(true)] public double FlowBalance => CurrentFlow; [JsonIgnore] [Category("🔧 Compatibilidad")] [DisplayName("Tipo de tanque")] [Description("Tipo de tanque (siempre simplificado)")] [ReadOnly(true)] public string TankType => "Simplified"; [JsonIgnore] [Category("🔧 Compatibilidad")] [DisplayName("Descripción del fluido actual")] [Description("Descripción del fluido en el tanque")] [ReadOnly(true)] public string CurrentFluidDescription => FluidDescription; } }