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 { /// /// Tanque hidráulico con gestión dinámica de nivel y presión configurable /// public partial class osHydTank : osBase, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase { // Referencia al objeto de simulación hidráulica específico private simHydraulicTank SimHydraulicTank; 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: 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() { // Inicializar el lock primero para thread safety EnsureLockInitialized(); try { 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; // No cargar imagen aquí - se carga en ucLoaded() // para evitar problemas de serialización // Cálculos seguros SafeUpdateVolumeCalculations(); IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros SafeUpdateTankPressure(); // 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}"); } catch (Exception ex) { Debug.WriteLine($"Error in osHydTank constructor: {ex.Message}"); // Inicializar valores por defecto mínimos InitializeDefaults(); } } // 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() { // Se llama cuando inicia la simulación - crear objeto hidráulico si no existe if (SimHydraulicTank == null) { SimHydraulicTank = hydraulicSimulationManager.AddTank(TankPressure, CurrentLevel, MaxLevel, MinLevel, CrossSectionalArea, IsFixedPressure); SimHydraulicTank.SimObjectType = "HydraulicTank"; SimHydraulicTank.WpfObject = this; SimHydraulicTank.Nombre = Nombre; } else { // Actualizar propiedades si el objeto ya existe SimHydraulicTank.TankPressure = TankPressure; SimHydraulicTank.CurrentLevel = CurrentLevel; SimHydraulicTank.MaxLevel = MaxLevel; SimHydraulicTank.MinLevel = MinLevel; SimHydraulicTank.CrossSectionalArea = CrossSectionalArea; SimHydraulicTank.IsFixedPressure = IsFixedPressure; } } public override void UpdateGeometryStep() { // Los objetos hidráulicos actualizan sus resultados // a través de ApplySimulationResults() desde HydraulicSimulationManager } public override void UpdateControl(int elapsedMilliseconds) { // Actualizar propiedades desde la simulación hidráulica if (SimHydraulicTank != null) { InletFlow = SimHydraulicTank.InletFlow; OutletFlow = SimHydraulicTank.OutletFlow; CurrentPressure = SimHydraulicTank.CurrentPressure; // Actualizar nivel del tanque basado en el balance de flujo double deltaTime = elapsedMilliseconds / 1000.0; // Convertir a segundos if (deltaTime > 0) { UpdateLevelFromFlowBalance(deltaTime); } } // 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))) { SafeUpdateVolumeCalculations(); 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; SafeUpdateVolumeCalculations(); } } } [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; SafeUpdateVolumeCalculations(); } } } // 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)) { SafeUpdateTankPressure(); 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))) { SafeUpdateTankPressure(); 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)) { SafeUpdateVolumeCalculations(); 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 GetHydraulicNodes() { var nodes = new List(); // 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 GetHydraulicElements() { // Los tanques no generan elementos hidráulicos, solo nodos return new List(); } public void UpdateHydraulicProperties() { // Actualizar propiedades antes de la simulación SafeUpdateTankPressure(); SafeUpdateVolumeCalculations(); } public void ApplyHydraulicResults(Dictionary flows, Dictionary 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 /// /// Color del nivel de líquido /// [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); } } /// /// Color del borde del nivel de líquido /// [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); } } /// /// Indica si hay conexión de entrada /// [JsonIgnore] public bool HasInletConnection => !string.IsNullOrEmpty(ConnectedInletPipe); /// /// Indica si hay conexión de salida /// [JsonIgnore] public bool HasOutletConnection => !string.IsNullOrEmpty(ConnectedOutletPipe); /// /// Indicador del tipo de tanque /// [JsonIgnore] public string TankTypeIndicator { get { return TankType switch { HydraulicTankType.Suction => "S", HydraulicTankType.Intermediate => "I", HydraulicTankType.Storage => "A", HydraulicTankType.Process => "P", _ => "T" }; } } /// /// Balance de flujo en L/min /// [JsonIgnore] public double FlowBalanceLMin => FlowBalance * 60000; // m³/s a L/min /// /// Color del balance de flujo /// [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); } } /// /// Posición Y de la línea de nivel medio (50%) /// [JsonIgnore] public double MidLevelY { get { var tankHeight = Tamano * 100; // Convertir a píxeles aproximados return tankHeight * 0.5; // 50% de altura } } /// /// Posición Y de la línea de nivel mínimo /// [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 void InitializeDefaults() { // Valores mínimos para que el objeto sea funcional try { if (string.IsNullOrEmpty(nombre)) nombre = "Tanque Hidráulico"; if (Tamano <= 0) Tamano = 1.0f; if (Ancho <= 0) Ancho = 0.30f; if (Alto <= 0) Alto = 0.40f; SafeUpdateVolumeCalculations(); SafeUpdateTankPressure(); Debug.WriteLine("osHydTank: Initialized with default values"); } catch (Exception ex) { Debug.WriteLine($"Error in InitializeDefaults: {ex.Message}"); } } private void SafeUpdateVolumeCalculations() { try { _currentVolume = _currentLevel * _crossSectionalArea; _maxVolume = _maxLevel * _crossSectionalArea; } catch (Exception ex) { Debug.WriteLine($"Error in SafeUpdateVolumeCalculations: {ex.Message}"); // Usar valores por defecto seguros _currentVolume = 1.0; _maxVolume = 2.0; } } private void SafeUpdateTankPressure() { try { if (IsFixedPressure) { CurrentPressure = TankPressure; } } catch (Exception ex) { Debug.WriteLine($"Error in SafeUpdateTankPressure: {ex.Message}"); // Usar presión atmosférica por defecto _currentPressure = 101325.0; } } 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 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 SafeUpdateVolumeCalculations(); SafeUpdateTankPressure(); } public void Inicializar(int valorInicial) { // Inicialización con valor inicial SafeUpdateVolumeCalculations(); SafeUpdateTankPressure(); } 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 /// /// Se llama antes de la serialización para asegurar que el lock esté disponible /// 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(); Debug.WriteLine($"osHydTank.ucLoaded(): Componente hidráulico cargado correctamente"); // Inicialización específica del tanque hidráulico SafeUpdateVolumeCalculations(); SafeUpdateTankPressure(); // 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() { // Remover objeto hidráulico de la simulación if (SimHydraulicTank != null) { hydraulicSimulationManager.Remove(SimHydraulicTank); SimHydraulicTank = null; } } } }