using System; using System.Collections.Generic; using System.ComponentModel; 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 { /// /// Tubería hidráulica que conecta componentes del sistema hidráulico /// public partial class osHydPipe : osBase, IHydraulicPipe, IosBase { #region Properties private double _length = 1.0; // metros private double _diameter = 0.05; // metros (50mm) private double _roughness = 4.5e-5; // metros - rugosidad del acero comercial private double _currentFlow = 0.0; private double _pressureDrop = 0.0; // Propiedades visuales [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Ancho visual de la tubería en metros")] [property: Name("Ancho")] private float ancho = 1.0f; [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Alto visual de la tubería en metros")] [property: Name("Alto")] private float alto = 0.05f; [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Color visual de la tubería")] [property: Name("Color")] private Brush colorButton_oculto = Brushes.Gray; [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Ángulo de rotación de la tubería en grados")] [property: Name("Ángulo")] private float angulo = 0f; [Category("🔧 Tubería Hidráulica")] [Description("Longitud de la tubería en metros")] [Name("Longitud (m)")] public double Length { get => _length; set => SetProperty(ref _length, Math.Max(0.1, value)); } [Category("🔧 Tubería Hidráulica")] [Description("Diámetro interno de la tubería en metros")] [Name("Diámetro (m)")] public double Diameter { get => _diameter; set => SetProperty(ref _diameter, Math.Max(0.001, value)); } [Category("🔧 Tubería Hidráulica")] [Description("Rugosidad del material en metros")] [Name("Rugosidad (m)")] public double Roughness { get => _roughness; set => SetProperty(ref _roughness, Math.Max(1e-6, value)); } [ObservableProperty] [property: Category("🔗 Conexiones")] [property: Description("Primer componente hidráulico conectado")] [property: Name("Componente A")] [property: ItemsSource(typeof(osBaseItemsSource))] private string id_ComponenteA = ""; [ObservableProperty] [property: Category("🔗 Conexiones")] [property: Description("Segundo componente hidráulico conectado")] [property: Name("Componente B")] [property: ItemsSource(typeof(osBaseItemsSource))] private string id_ComponenteB = ""; [Category("📊 Estado")] [Description("Flujo actual a través de la tubería en m³/s")] [Name("Flujo Actual (m³/s)")] [ReadOnly(true)] public double CurrentFlow { get => _currentFlow; set => SetProperty(ref _currentFlow, value); } [Category("📊 Estado")] [Description("Pérdida de presión en la tubería en Pa")] [Name("Pérdida Presión (Pa)")] [ReadOnly(true)] public double PressureDrop { get => _pressureDrop; set => SetProperty(ref _pressureDrop, value); } private string pipeId = ""; public string PipeId { get => pipeId; set => SetProperty(ref pipeId, value); } #endregion #region Component References [JsonIgnore] private IHydraulicComponent ComponenteA = null; [JsonIgnore] private IHydraulicComponent ComponenteB = null; [JsonIgnore] private PropertyChangedEventHandler componenteAPropertyChangedHandler; [JsonIgnore] private PropertyChangedEventHandler componenteBPropertyChangedHandler; [JsonIgnore] public Action ActualizarTamaño { get; set; } #endregion #region IHydraulicPipe Implementation // Ya implementadas las propiedades Length, Diameter, Roughness arriba #endregion #region IHydraulicComponent Implementation public bool HasHydraulicComponents => true; public List GetHydraulicNodes() { var nodes = new List(); // Las tuberías conectan componentes existentes, no crean nodos propios // Los nodos son creados por los componentes conectados (tanques, bombas, etc.) // La tubería solo actúa como elemento que conecta nodos existentes return nodes; } public List GetHydraulicElements() { var elements = new List(); // Solo crear elemento si ambos componentes están conectados if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB)) { // Obtener los nombres de nodos correctos para cada componente string fromNodeName = GetNodeNameForComponent(Id_ComponenteA, true); // true = es el nodo origen string toNodeName = GetNodeNameForComponent(Id_ComponenteB, false); // false = es el nodo destino if (!string.IsNullOrEmpty(fromNodeName) && !string.IsNullOrEmpty(toNodeName)) { // Crear el elemento Pipe según la documentación var pipeElement = new Pipe(Length, Diameter, Roughness); elements.Add(new HydraulicElementDefinition( name: $"{Nombre}_Pipe", fromNode: fromNodeName, toNode: toNodeName, element: pipeElement, description: $"Tubería {Nombre} - L:{Length:F2}m, D:{Diameter*1000:F0}mm ({fromNodeName} → {toNodeName})" )); } } return elements; } /// /// Obtiene el nombre del nodo correcto para un componente dado /// /// Nombre del componente /// True si es el nodo origen de la tubería, False si es destino /// Nombre del nodo o string vacío si no se encuentra private string GetNodeNameForComponent(string componentName, bool isSource) { if (string.IsNullOrEmpty(componentName) || _mainViewModel == null) return ""; // Buscar el componente hidráulico var component = _mainViewModel.ObjetosSimulables .FirstOrDefault(s => s is IHydraulicComponent comp && ((osBase)comp).Nombre == componentName) as IHydraulicComponent; if (component == null) return ""; // Obtener los nodos que crea este componente var nodes = component.GetHydraulicNodes(); if (nodes == null || !nodes.Any()) return ""; // Determinar qué nodo usar según el tipo de componente var componentType = component.GetType(); // Para tanques: siempre usar su único nodo if (componentType.Name.Contains("Tank")) { return nodes.FirstOrDefault()?.Name ?? ""; } // Para bombas: usar nodo de entrada o salida según corresponda if (componentType.Name.Contains("Pump")) { if (isSource) { // Si la tubería sale desde este componente, usar el nodo de salida de la bomba return nodes.FirstOrDefault(n => n.Name.EndsWith("_Out"))?.Name ?? ""; } else { // Si la tubería llega a este componente, usar el nodo de entrada de la bomba return nodes.FirstOrDefault(n => n.Name.EndsWith("_In"))?.Name ?? ""; } } // Para otros tipos de componentes, usar el primer nodo disponible return nodes.FirstOrDefault()?.Name ?? ""; } public void UpdateHydraulicProperties() { // Actualizar propiedades antes de la simulación si es necesario // Por ejemplo, cambios dinámicos en diámetro o rugosidad } public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) { // Solo procesar si ambos componentes están conectados if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB)) { // Obtener los nombres de nodos correctos string fromNodeName = GetNodeNameForComponent(Id_ComponenteA, true); string toNodeName = GetNodeNameForComponent(Id_ComponenteB, false); if (!string.IsNullOrEmpty(fromNodeName) && !string.IsNullOrEmpty(toNodeName)) { string branchKey = $"{fromNodeName}->{toNodeName}"; if (flows.TryGetValue(branchKey, out double flow)) { CurrentFlow = flow; } // Calcular pérdida de presión entre nodos if (pressures.TryGetValue(fromNodeName, out double pressureA) && pressures.TryGetValue(toNodeName, out double pressureB)) { PressureDrop = pressureA - pressureB; } } } } #endregion #region IHydraulicFlowReceiver Implementation public void SetFlow(double flow) { CurrentFlow = flow; } public double GetFlow() { return CurrentFlow; } #endregion #region IHydraulicPressureReceiver Implementation public void SetPressure(double pressure) { // Para tuberías, la presión se maneja a través de los nodos } public double GetPressure() { return PressureDrop; } #endregion #region Connection Management partial void OnId_ComponenteAChanged(string value) { if (ComponenteA != null && componenteAPropertyChangedHandler != null) ((INotifyPropertyChanged)ComponenteA).PropertyChanged -= componenteAPropertyChangedHandler; if (_mainViewModel != null && !string.IsNullOrEmpty(value)) { ComponenteA = (IHydraulicComponent)_mainViewModel.ObjetosSimulables .FirstOrDefault(s => s is IHydraulicComponent comp && ((osBase)comp).Nombre == value); if (ComponenteA != null) { componenteAPropertyChangedHandler = (sender, e) => { if (e.PropertyName == nameof(osBase.Nombre)) { Id_ComponenteA = ((osBase)sender).Nombre; } }; ((INotifyPropertyChanged)ComponenteA).PropertyChanged += componenteAPropertyChangedHandler; } } } partial void OnId_ComponenteBChanged(string value) { if (ComponenteB != null && componenteBPropertyChangedHandler != null) ((INotifyPropertyChanged)ComponenteB).PropertyChanged -= componenteBPropertyChangedHandler; if (_mainViewModel != null && !string.IsNullOrEmpty(value)) { ComponenteB = (IHydraulicComponent)_mainViewModel.ObjetosSimulables .FirstOrDefault(s => s is IHydraulicComponent comp && ((osBase)comp).Nombre == value); if (ComponenteB != null) { componenteBPropertyChangedHandler = (sender, e) => { if (e.PropertyName == nameof(osBase.Nombre)) { Id_ComponenteB = ((osBase)sender).Nombre; } }; ((INotifyPropertyChanged)ComponenteB).PropertyChanged += componenteBPropertyChangedHandler; } } } #endregion #region osBase Implementation public static string NombreCategoria() => "Componentes Hidráulicos"; public static string NombreClase() => "Tubería Hidráulica"; 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 AltoChanged(float value) { ActualizarGeometrias(); } public override void AnchoChanged(float value) { ActualizarGeometrias(); } public void Start() { // Las tuberías no participan en la simulación física Bepu // Solo en el sistema hidráulico } public override void UpdateGeometryStart() { ActualizarGeometrias(); } public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) { // Las tuberías no tienen control PLC directo } private void ActualizarGeometrias() { try { // Actualizar geometría visual if (this.ActualizarTamaño != null) this.ActualizarTamaño(Ancho, Alto); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error actualizando geometría: {ex.Message}"); } } public void Inicializar(int valorInicial) { PipeId = $"Pipe_{Nombre}_{valorInicial}"; OnId_ComponenteAChanged(Id_ComponenteA); OnId_ComponenteBChanged(Id_ComponenteB); } public void Disposing() { if (ComponenteA != null && componenteAPropertyChangedHandler != null) ((INotifyPropertyChanged)ComponenteA).PropertyChanged -= componenteAPropertyChangedHandler; if (ComponenteB != null && componenteBPropertyChangedHandler != null) ((INotifyPropertyChanged)ComponenteB).PropertyChanged -= componenteBPropertyChangedHandler; } #endregion #region Constructor public osHydPipe() { PipeId = Guid.NewGuid().ToString(); IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros } #endregion } }