450 lines
16 KiB
C#
450 lines
16 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.HydraulicComponents
|
|
{
|
|
/// <summary>
|
|
/// Tubería hidráulica que conecta componentes del sistema hidráulico
|
|
/// </summary>
|
|
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<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);
|
|
}
|
|
|
|
#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<float, float> 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<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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#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
|
|
}
|
|
}
|