CtrEditor/ObjetosSim/HydraulicComponents/osHydPipe.cs

502 lines
18 KiB
C#

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
{
/// <summary>
/// Tubería hidráulica que conecta componentes del sistema hidráulico
/// </summary>
public partial class osHydPipe : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
{
// Referencia al objeto de simulación hidráulica específico
private simHydraulicPipe SimHydraulicPipe;
// 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<IHydraulicComponent>))]
private string id_ComponenteA = "";
[ObservableProperty]
[property: Category("🔗 Conexiones")]
[property: Description("Segundo componente hidráulico conectado")]
[property: Name("Componente B")]
[property: ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
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);
}
// Component References
[JsonIgnore]
private IHydraulicComponent ComponenteA = null;
[JsonIgnore]
private IHydraulicComponent ComponenteB = null;
[JsonIgnore]
private PropertyChangedEventHandler componenteAPropertyChangedHandler;
[JsonIgnore]
private PropertyChangedEventHandler componenteBPropertyChangedHandler;
[JsonIgnore]
public Action<float, float> ActualizarTamaño { get; set; }
// IHydraulicPipe Implementation
// Ya implementadas las propiedades Length, Diameter, Roughness arriba
// IHydraulicComponent Implementation
public bool HasHydraulicComponents => true;
public List<HydraulicNodeDefinition> GetHydraulicNodes()
{
var nodes = new List<HydraulicNodeDefinition>();
// 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<HydraulicElementDefinition> GetHydraulicElements()
{
var elements = new List<HydraulicElementDefinition>();
// 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;
}
/// <summary>
/// Obtiene el nombre del nodo correcto para un componente dado
/// </summary>
/// <param name="componentName">Nombre del componente</param>
/// <param name="isSource">True si es el nodo origen de la tubería, False si es destino</param>
/// <returns>Nombre del nodo o string vacío si no se encuentra</returns>
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<string, double> flows, Dictionary<string, double> 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;
}
}
}
}
// IHydraulicFlowReceiver Implementation
public void SetFlow(double flow)
{
CurrentFlow = flow;
}
public double GetFlow()
{
return CurrentFlow;
}
// 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;
}
// 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;
}
}
}
// 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()
{
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
if (SimHydraulicPipe == null && !string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
{
SimHydraulicPipe = hydraulicSimulationManager.AddPipe(Length, Diameter, Roughness, Id_ComponenteA, Id_ComponenteB);
SimHydraulicPipe.SimObjectType = "HydraulicPipe";
SimHydraulicPipe.WpfObject = this;
SimHydraulicPipe.Nombre = Nombre;
}
else if (SimHydraulicPipe != null)
{
// Actualizar propiedades si el objeto ya existe
SimHydraulicPipe.Length = Length;
SimHydraulicPipe.Diameter = Diameter;
SimHydraulicPipe.Roughness = Roughness;
SimHydraulicPipe.ComponenteAId = Id_ComponenteA;
SimHydraulicPipe.ComponenteBId = Id_ComponenteB;
}
ActualizarGeometrias();
}
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 (SimHydraulicPipe != null)
{
CurrentFlow = SimHydraulicPipe.CurrentFlow;
PressureDrop = SimHydraulicPipe.PressureDrop;
}
}
public override void ucLoaded()
{
// Objeto hidráulico se crea en UpdateGeometryStart cuando inicia la simulación
base.ucLoaded();
}
public override void ucUnLoaded()
{
// Remover objeto hidráulico de la simulación
if (SimHydraulicPipe != null)
{
hydraulicSimulationManager.Remove(SimHydraulicPipe);
SimHydraulicPipe = null;
}
}
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;
}
// Constructor
public osHydPipe()
{
PipeId = Guid.NewGuid().ToString();
IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros
}
}
}