using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; 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; namespace CtrEditor.ObjetosSim { /// /// Bomba hidráulica que implementa las interfaces del simulador hidráulico /// public partial class osHydPump : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase { // Referencia al objeto de simulación hidráulica específico private simHydraulicPump SimHydraulicPump; public static string NombreCategoria() => "Componentes Hidráulicos"; public static string NombreClase() { return "Bomba Hidráulica"; } // 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 de la bomba")] [property: Name("Tamaño")] public float tamano; public override void OnResize(float Delta_Width, float Delta_Height) { Tamano += Delta_Width + Delta_Height; } private double _pumpHead = 50.0; // metros private double _maxFlow = 0.01; // m³/s (36 m³/h) private double _speedRatio = 1.0; private bool _isRunning = true; private int _pumpDirection = 1; private double _currentFlow = 0.0; private double _currentPressure = 0.0; // Propiedades visuales específicas de la bomba [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Color visual de la bomba")] [property: Name("Color")] private Brush colorButton_oculto = Brushes.Blue; [Category("🔧 Bomba Hidráulica")] [DisplayName("Cabeza de bomba")] [Description("Cabeza de la bomba a caudal cero (m)")] public double PumpHead { get => _pumpHead; set { if (SetProperty(ref _pumpHead, Math.Max(0, value))) { InvalidateHydraulicNetwork(); } } } [Category("🔧 Bomba Hidráulica")] [DisplayName("Caudal máximo")] [Description("Caudal máximo de la bomba (m³/s)")] public double MaxFlow { get => _maxFlow; set { // Si el valor es mayor a 1, asumir que está en m³/h y convertir a m³/s double valueInM3s = value > 1.0 ? value / 3600.0 : value; if (SetProperty(ref _maxFlow, Math.Max(0.0001, valueInM3s))) { InvalidateHydraulicNetwork(); Debug.WriteLine($"Bomba {Nombre}: MaxFlow establecido a {_maxFlow:F6} m³/s ({_maxFlow * 3600:F2} m³/h)"); } } } [Category("🔧 Bomba Hidráulica")] [DisplayName("Velocidad relativa")] [Description("Velocidad relativa de la bomba (0.0 a 1.0)")] public double SpeedRatio { get => _speedRatio; set { SetProperty(ref _speedRatio, Math.Max(0.0, Math.Min(1.0, value))); } } [Category("🔧 Bomba Hidráulica")] [DisplayName("Funcionando")] [Description("Indica si la bomba está encendida")] public bool IsRunning { get => _isRunning; set { if (SetProperty(ref _isRunning, value)) { UpdatePumpImage(); } } } private void UpdatePumpImage() { if (IsRunning) ImageSource_oculta = ImageFromPath("/imagenes/pump_run.png"); else ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png"); } [Category("🔧 Bomba Hidráulica")] [DisplayName("Dirección")] [Description("Dirección de la bomba (+1 o -1)")] [ItemsSource(typeof(PumpDirectionItemsSource))] public int PumpDirection { get => _pumpDirection; set { if (SetProperty(ref _pumpDirection, value == -1 ? -1 : 1)) { InvalidateHydraulicNetwork(); } } } [Category("📊 Estado Actual")] [DisplayName("Caudal actual")] [Description("Caudal actual de la bomba (m³/s)")] [JsonIgnore] public double CurrentFlow { get => _currentFlow; private set => SetProperty(ref _currentFlow, value); } [Category("📊 Estado Actual")] [DisplayName("Presión actual")] [Description("Presión actual en la bomba (Pa)")] [JsonIgnore] public double CurrentPressure { get => _currentPressure; private set => SetProperty(ref _currentPressure, value); } [Category("📊 Estado Actual")] [DisplayName("Presión (bar)")] [Description("Presión actual en la bomba (bar)")] [JsonIgnore] public double CurrentPressureBar => CurrentPressure / 100000.0; [Category("📊 Estado Actual")] [DisplayName("Caudal (L/min)")] [Description("Caudal actual en L/min")] [JsonIgnore] public double CurrentFlowLMin => CurrentFlow * 60000.0; // m³/s a L/min [Category("📊 Estado Actual")] [DisplayName("NPSH Disponible")] [Description("NPSH disponible calculado (m)")] [JsonIgnore] public double NPSHAvailable { get { var (inletNodeName, _) = GetConnectedNodeNames(); if (!string.IsNullOrEmpty(inletNodeName) && hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true) { var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName]; var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo return pump.CalculateNPSHAvailable(suctionPressure, hydraulicSimulationManager.SimulationFluid); } return 0.0; } } [Category("📊 Estado Actual")] [DisplayName("Factor Cavitación")] [Description("Factor de cavitación (1=sin cavitación, 0=cavitación total)")] [JsonIgnore] public double CavitationFactor { get { var (inletNodeName, _) = GetConnectedNodeNames(); if (!string.IsNullOrEmpty(inletNodeName) && hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true) { var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName]; var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo return pump.GetCavitationFactor(suctionPressure, hydraulicSimulationManager.SimulationFluid); } return 1.0; } } [Category("📊 Estado Actual")] [DisplayName("Puede Operar")] [Description("Indica si la bomba puede operar sin cavitación")] [JsonIgnore] public bool CanOperateWithoutCavitation { get { var (inletNodeName, _) = GetConnectedNodeNames(); if (!string.IsNullOrEmpty(inletNodeName) && hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true) { var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName]; var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo return pump.CanOperateWithoutCavitation(suctionPressure, hydraulicSimulationManager.SimulationFluid); } return false; } } // 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 osHydPump() { Nombre = "Bomba Hidráulica"; Tamano = 0.30f; // Inicializar dimensiones usando las propiedades de osBase Ancho = 0.15f; Alto = 0.10f; Angulo = 0f; // Asegurar que el movimiento esté habilitado Lock_movement = false; ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png"); } public override void UpdateGeometryStart() { // Se llama cuando inicia la simulación - crear objeto hidráulico si no existe if (SimHydraulicPump == null) { SimHydraulicPump = hydraulicSimulationManager.AddPump(PumpHead, MaxFlow, SpeedRatio, IsRunning, PumpDirection); SimHydraulicPump.SimObjectType = "HydraulicPump"; SimHydraulicPump.WpfObject = this; SimHydraulicPump.Nombre = Nombre; } else { // Actualizar propiedades si el objeto ya existe SimHydraulicPump.PumpHead = PumpHead; SimHydraulicPump.MaxFlow = MaxFlow; SimHydraulicPump.SpeedRatio = SpeedRatio; SimHydraulicPump.IsRunning = IsRunning; SimHydraulicPump.PumpDirection = PumpDirection; } } 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 (SimHydraulicPump != null) { CurrentFlow = SimHydraulicPump.CurrentFlow; CurrentPressure = SimHydraulicPump.CurrentPressure; // Actualizar el color según el estado if (IsRunning) { if (hydraulicSimulationManager.EnableNPSHVerification) { var cavitationFactor = CavitationFactor; if (cavitationFactor < 0.1) ColorButton_oculto = Brushes.Red; // Cavitación severa else if (cavitationFactor < 0.5) ColorButton_oculto = Brushes.Orange; // Riesgo de cavitación else if (!CanOperateWithoutCavitation) ColorButton_oculto = Brushes.Yellow; // Condiciones límite else ColorButton_oculto = Brushes.Green; // Funcionamiento normal } else { ColorButton_oculto = Brushes.Green; // Sin verificación NPSH } } else { ColorButton_oculto = Brushes.Gray; // Bomba apagada } } } public override void ucLoaded() { // Objeto hidráulico se crea en UpdateGeometryStart cuando inicia la simulación base.ucLoaded(); UpdatePumpImage(); } public override void ucUnLoaded() { // Remover objeto hidráulico de la simulación if (SimHydraulicPump != null) { hydraulicSimulationManager.Remove(SimHydraulicPump); SimHydraulicPump = null; } } // IHydraulicComponent Implementation [JsonIgnore] public bool HasHydraulicComponents => true; public List GetHydraulicNodes() { var nodes = new List(); // Las bombas no crean nodos propios - deben estar conectadas a otros componentes para funcionar if (!HasConnectedComponents()) { Debug.WriteLine($"Bomba {Nombre}: Sin componentes conectados - no puede funcionar"); } else { //Debug.WriteLine($"Bomba {Nombre}: Conectada a otros componentes - lista para operar"); } return nodes; } public List GetHydraulicElements() { var elements = new List(); // Solo crear elementos si la bomba está conectada a otros componentes if (!HasConnectedComponents()) { Debug.WriteLine($"Bomba {Nombre}: Sin conexiones - no se puede crear elemento hidráulico"); return elements; } if (HasHydraulicComponents) { // Calcular velocidad efectiva basada en el estado de la bomba double effectiveSpeed = IsRunning ? SpeedRatio : 0.0; // Asegurar que la velocidad esté en rango válido effectiveSpeed = Math.Max(0.0, Math.Min(1.0, effectiveSpeed)); // Obtener los nombres de nodos correctos basados en las conexiones var (inletNode, outletNode) = GetConnectedNodeNames(); // Verificar que tenemos nodos válidos if (string.IsNullOrEmpty(inletNode) || string.IsNullOrEmpty(outletNode)) { Debug.WriteLine($"Bomba {Nombre}: Nodos de conexión inválidos - inlet: '{inletNode}', outlet: '{outletNode}'"); return elements; } // Crear bomba con parámetros actuales Element pump; if (hydraulicSimulationManager.EnableNPSHVerification) { // Usar bomba con verificación de NPSH pump = new PumpHQWithSuctionCheck( h0: PumpHead, q0: MaxFlow, speedRel: effectiveSpeed, direction: PumpDirection, enableNpshCheck: true ); } else { // Usar bomba estándar pump = new PumpHQ( h0: PumpHead, q0: MaxFlow, speedRel: effectiveSpeed, direction: PumpDirection ); } // Asignar nombres de nodos para verificación de NPSH if (pump is PumpHQ pumpHQ) { pumpHQ.InletNodeName = inletNode; pumpHQ.OutletNodeName = outletNode; } var pumpElement = new HydraulicElementDefinition( $"{Nombre}_Pump", inletNode, outletNode, pump, $"Bomba {Nombre}" ); elements.Add(pumpElement); Debug.WriteLine($"Bomba {Nombre}: Creando elemento hidráulico - H0={PumpHead}m, Q0={MaxFlow:F6}m³/s ({MaxFlow*3600:F2}m³/h), Velocidad={effectiveSpeed:F2}, Dirección={PumpDirection}"); Debug.WriteLine($"Bomba {Nombre}: Conectando {inletNode} -> {outletNode}"); } return elements; } public void UpdateHydraulicProperties() { // Aquí se pueden hacer validaciones o ajustes antes de la simulación if (!IsRunning) { SpeedRatio = 0.0; } if (SpeedRatio < 0.0) SpeedRatio = 0.0; if (SpeedRatio > 1.0) SpeedRatio = 1.0; // Verificar condiciones de NPSH si está habilitada la verificación if (hydraulicSimulationManager.EnableNPSHVerification && IsRunning) { var npshAvailable = NPSHAvailable; var cavitationFactor = CavitationFactor; if (cavitationFactor < 0.5) { Debug.WriteLine($"⚠️ Bomba {Nombre}: Riesgo de cavitación - Factor={cavitationFactor:F2}, NPSH_disponible={npshAvailable:F2}m"); } if (!CanOperateWithoutCavitation) { Debug.WriteLine($"❌ Bomba {Nombre}: NO puede operar sin cavitación - NPSH insuficiente"); } } //Debug.WriteLine($"Bomba {Nombre}: Velocidad={SpeedRatio:F2}, Funcionando={IsRunning}"); } public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) { // Buscar resultados para esta bomba string pumpBranchName = $"{Nombre}_Pump"; var (inletNodeName, outletNodeName) = GetConnectedNodeNames(); if (flows.ContainsKey(pumpBranchName)) { CurrentFlow = flows[pumpBranchName]; //Debug.WriteLine($"Bomba {Nombre}: Aplicando caudal={CurrentFlow:F6} m³/s ({CurrentFlowLMin:F2} L/min)"); } if (pressures.ContainsKey(inletNodeName)) { CurrentPressure = pressures[inletNodeName]; //Debug.WriteLine($"Bomba {Nombre}: Presión entrada={CurrentPressure:F2} Pa ({CurrentPressureBar:F2} bar)"); } else if (pressures.ContainsKey(outletNodeName)) { CurrentPressure = pressures[outletNodeName]; //Debug.WriteLine($"Bomba {Nombre}: Presión salida={CurrentPressure:F2} Pa ({CurrentPressureBar:F2} bar)"); } // Disparar PropertyChanged para actualizar el UI OnPropertyChanged(nameof(CurrentFlow)); OnPropertyChanged(nameof(CurrentFlowLMin)); OnPropertyChanged(nameof(CurrentPressure)); OnPropertyChanged(nameof(CurrentPressureBar)); } // IHydraulicFlowReceiver Implementation public void SetFlow(double flow) { CurrentFlow = flow; } public double GetFlow() { return CurrentFlow; } // IHydraulicPressureReceiver Implementation public void SetPressure(double pressure) { CurrentPressure = pressure; } public double GetPressure() { return CurrentPressure; } // Helper Methods /// /// Verifica si la bomba tiene componentes conectados a través de tuberías /// private bool HasConnectedComponents() { if (_mainViewModel == null) return false; // Buscar tuberías que conecten esta bomba con otros componentes var connectedPipes = _mainViewModel.ObjetosSimulables .OfType() .Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre) .ToList(); return connectedPipes.Any(); } /// /// Obtiene los nombres de nodos correctos para la bomba basándose en las conexiones /// private (string inletNode, string outletNode) GetConnectedNodeNames() { if (_mainViewModel == null) return (string.Empty, string.Empty); string inletNode = string.Empty; string outletNode = string.Empty; // Buscar tuberías conectadas a esta bomba var connectedPipes = _mainViewModel.ObjetosSimulables .OfType() .Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre) .ToList(); foreach (var pipe in connectedPipes) { if (pipe.Id_ComponenteB == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteA)) { // Esta bomba es el destino, el componente A es la fuente (inlet) inletNode = pipe.Id_ComponenteA; //Debug.WriteLine($"Bomba {Nombre}: Nodo inlet identificado como '{inletNode}'"); } else if (pipe.Id_ComponenteA == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteB)) { // Esta bomba es la fuente, el componente B es el destino (outlet) outletNode = pipe.Id_ComponenteB; //Debug.WriteLine($"Bomba {Nombre}: Nodo outlet identificado como '{outletNode}'"); } } return (inletNode, outletNode); } private void InvalidateHydraulicNetwork() { hydraulicSimulationManager?.InvalidateNetwork(); } } /// /// Proveedor de items para la dirección de la bomba /// public class PumpDirectionItemsSource : IItemsSource { public Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection GetValues() { var items = new Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection(); items.Add(1, "Adelante (+1)"); items.Add(-1, "Atrás (-1)"); return items; } } }