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
{
///
/// Tubería hidráulica que conecta componentes del sistema hidráulico
///
public partial class osHydPipe : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
{
// 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);
}
// Propiedades adicionales para información de fluido
private FluidProperties _currentFluid = new FluidProperties(FluidType.Air);
[Category("🧪 Fluido Actual")]
[DisplayName("Tipo de fluido")]
[Description("Tipo de fluido que atraviesa la tubería")]
[ReadOnly(true)]
public FluidType CurrentFluidType
{
get => _currentFluid.Type;
private set
{
if (_currentFluid.Type != value)
{
_currentFluid.Type = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentFluidDescription));
}
}
}
[Category("🧪 Fluido Actual")]
[DisplayName("Descripción")]
[Description("Descripción completa del fluido actual")]
[ReadOnly(true)]
public string CurrentFluidDescription => _currentFluid.Description;
[Category("🧪 Fluido Actual")]
[DisplayName("Temperatura (°C)")]
[Description("Temperatura del fluido en grados Celsius")]
[ReadOnly(true)]
[JsonProperty]
public double CurrentFluidTemperature => _currentFluid.Temperature;
[Category("🧪 Fluido Actual")]
[DisplayName("Brix (%)")]
[Description("Concentración en grados Brix (para jarabes)")]
[ReadOnly(true)]
[JsonProperty]
public double CurrentFluidBrix => _currentFluid.Brix;
[Category("🧪 Fluido Actual")]
[DisplayName("Concentración (%)")]
[Description("Concentración en porcentaje (para químicos)")]
[ReadOnly(true)]
[JsonProperty]
public double CurrentFluidConcentration => _currentFluid.Concentration;
[Category("🧪 Fluido Actual")]
[DisplayName("Densidad (kg/L)")]
[Description("Densidad del fluido en kg/L")]
[ReadOnly(true)]
[JsonProperty]
public double CurrentFluidDensity => _currentFluid.Density;
[Category("🧪 Fluido Actual")]
[DisplayName("Viscosidad (cP)")]
[Description("Viscosidad dinámica en centipoise")]
[ReadOnly(true)]
[JsonProperty]
public double CurrentFluidViscosity => _currentFluid.Viscosity;
[Category("🧪 Fluido Actual")]
[DisplayName("Velocidad (m/s)")]
[Description("Velocidad del fluido en la tubería")]
[ReadOnly(true)]
[JsonProperty]
public double FluidVelocity
{
get
{
if (Diameter <= 0) return 0;
var area = Math.PI * Math.Pow(Diameter / 2, 2); // m²
return Math.Abs(CurrentFlow) / area; // m/s
}
}
[Category("🧪 Fluido Actual")]
[DisplayName("Número de Reynolds")]
[Description("Número de Reynolds (Re = ρ × v × D / μ) - Caracteriza el régimen de flujo")]
[ReadOnly(true)]
[JsonProperty]
public double ReynoldsNumber
{
get
{
if (CurrentFluidViscosity <= 0 || Diameter <= 0) return 0;
// Re = (ρ × v × D) / μ
// ρ: densidad (kg/m³) - convertir de kg/L
// v: velocidad (m/s)
// D: diámetro (m)
// μ: viscosidad dinámica (Pa·s) - convertir de cP
var density_kg_m3 = CurrentFluidDensity * 1000; // kg/L → kg/m³
var viscosity_pa_s = CurrentFluidViscosity * 0.001; // cP → Pa·s
return (density_kg_m3 * FluidVelocity * Diameter) / viscosity_pa_s;
}
}
[Category("🧪 Fluido Actual")]
[DisplayName("Régimen de Flujo")]
[Description("Tipo de régimen basado en Reynolds (Laminar < 2300 < Turbulento)")]
[ReadOnly(true)]
[JsonProperty]
public string FlowRegime
{
get
{
var re = ReynoldsNumber;
return re switch
{
< 2300 => $"Laminar (Re = {re:F0})",
> 4000 => $"Turbulento (Re = {re:F0})",
_ => $"Transición (Re = {re:F0})"
};
}
}
// Método para exponer propiedades detalladas en serialización JSON
[JsonProperty("DetailedFluidProperties")]
[Browsable(false)]
public Dictionary DetailedFluidProperties => new Dictionary
{
["CurrentFluidTemperature"] = CurrentFluidTemperature,
["CurrentFluidBrix"] = CurrentFluidBrix,
["CurrentFluidConcentration"] = CurrentFluidConcentration,
["CurrentFluidDensity"] = CurrentFluidDensity,
["CurrentFluidViscosity"] = CurrentFluidViscosity,
["FluidVelocity"] = FluidVelocity,
["ReynoldsNumber"] = ReynoldsNumber,
["FlowRegime"] = FlowRegime
};
[Category("📊 Estado")]
[DisplayName("Flujo (L/min)")]
[Description("Flujo actual en litros por minuto")]
[ReadOnly(true)]
public double CurrentFlowLMin => Math.Abs(CurrentFlow) * 60000.0; // Conversión de m³/s a L/min
[Category("📊 Estado")]
[DisplayName("Dirección")]
[Description("Dirección del flujo")]
[ReadOnly(true)]
public string FlowDirection => CurrentFlow > 0 ? $"{Id_ComponenteA} → {Id_ComponenteB}" :
CurrentFlow < 0 ? $"{Id_ComponenteB} → {Id_ComponenteA}" : "Sin flujo";
[JsonIgnore]
public SolidColorBrush FluidColor
{
get
{
try
{
var colorHex = _currentFluid.Color;
return new SolidColorBrush((Color)ColorConverter.ConvertFromString(colorHex));
}
catch
{
return Brushes.Gray;
}
}
}
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 ActualizarTamaño { get; set; }
// IHydraulicPipe Implementation
// Ya implementadas las propiedades Length, Diameter, Roughness arriba
// 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)
{
Debug.WriteLine($"[PIPE] ApplyHydraulicResults for {Nombre}");
Debug.WriteLine($"[PIPE] Available flow keys: {string.Join(", ", flows.Keys)}");
// 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);
Debug.WriteLine($"[PIPE] fromNodeName: '{fromNodeName}', toNodeName: '{toNodeName}'");
if (!string.IsNullOrEmpty(fromNodeName) && !string.IsNullOrEmpty(toNodeName))
{
string branchKey = $"{fromNodeName}->{toNodeName}";
Debug.WriteLine($"[PIPE] Looking for branchKey: '{branchKey}'");
if (flows.TryGetValue(branchKey, out double flow))
{
CurrentFlow = flow;
Debug.WriteLine($"[PIPE] Found flow: {flow}");
}
else
{
Debug.WriteLine($"[PIPE] Flow not found for key '{branchKey}'");
// Try alternative patterns
string[] alternativeKeys = {
$"{toNodeName}->{fromNodeName}",
$"{Id_ComponenteA}_{Id_ComponenteB}_Pipe",
$"{Id_ComponenteB}_{Id_ComponenteA}_Pipe",
$"Pipe_{Id_ComponenteA}_{Id_ComponenteB}",
$"Pipe_{Id_ComponenteB}_{Id_ComponenteA}",
$"{Nombre}_Pipe",
$"Branch_{Nombre}"
};
bool foundFlow = false;
foreach (string altKey in alternativeKeys)
{
if (flows.TryGetValue(altKey, out double altFlow))
{
CurrentFlow = altFlow;
Debug.WriteLine($"[PIPE] Found flow with alternative key '{altKey}': {altFlow}");
foundFlow = true;
break;
}
}
// If still no flow found, try to get flow from connected pump
if (!foundFlow)
{
Debug.WriteLine($"[PIPE] No direct flow found, checking connected components");
// Check if connected to a pump
string[] pumpKeys = {
$"{Id_ComponenteA}_Pump",
$"{Id_ComponenteB}_Pump"
};
foreach (string pumpKey in pumpKeys)
{
if (flows.TryGetValue(pumpKey, out double pumpFlow))
{
CurrentFlow = pumpFlow;
Debug.WriteLine($"[PIPE] Using pump flow from '{pumpKey}': {pumpFlow}");
foundFlow = true;
break;
}
}
}
}
// 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()
{
// En el nuevo sistema unificado, el HydraulicSimulationManager se encarga
// de registrar automáticamente los objetos que implementan IHydraulicComponent
// No necesitamos crear objetos simHydraulic* separados
Debug.WriteLine($"[DEBUG] {Nombre}: UpdateGeometryStart() - ComponenteA: '{Id_ComponenteA}', ComponenteB: '{Id_ComponenteB}'");
ActualizarGeometrias();
}
public override void UpdateGeometryStep()
{
// Los objetos hidráulicos actualizan sus resultados
// a través de ApplyHydraulicResults() desde HydraulicSimulationManager
}
// Método para actualizar fluido desde componente fuente
public void UpdateFluidFromSource()
{
if (_mainViewModel?.ObjetosSimulables == null) return;
// Determinar componente fuente basado en dirección del flujo
string sourceComponentName = CurrentFlow >= 0 ? Id_ComponenteA : Id_ComponenteB;
var sourceComponent = _mainViewModel.ObjetosSimulables
.OfType()
.FirstOrDefault(c => c is osBase osb && osb.Nombre == sourceComponentName);
if (sourceComponent is osHydTank tank)
{
var sourceFluid = tank.CurrentOutputFluid;
if (sourceFluid != null)
{
_currentFluid = sourceFluid.Clone();
OnPropertyChanged(nameof(CurrentFluidType));
OnPropertyChanged(nameof(CurrentFluidDescription));
OnPropertyChanged(nameof(FluidColor));
}
}
// Actualizar color visual
if (Math.Abs(CurrentFlow) > 1e-6) // Hay flujo
{
ColorButton_oculto = FluidColor;
}
else
{
ColorButton_oculto = Brushes.Gray; // Sin flujo
}
}
public override void UpdateControl(int elapsedMilliseconds)
{
// En el nuevo sistema unificado, las propiedades se actualizan
// directamente a través de ApplyHydraulicResults()
// CurrentFlow y PressureDrop ya están actualizados por el solver
// Actualizar propiedades del fluido cada ciclo
UpdateFluidFromSource();
// Actualizar propiedades de UI
OnPropertyChanged(nameof(CurrentFlowLMin));
OnPropertyChanged(nameof(FlowDirection));
// Actualizar propiedades de fluido para serialización JSON
OnPropertyChanged(nameof(CurrentFluidTemperature));
OnPropertyChanged(nameof(CurrentFluidBrix));
OnPropertyChanged(nameof(CurrentFluidConcentration));
OnPropertyChanged(nameof(CurrentFluidDensity));
OnPropertyChanged(nameof(CurrentFluidViscosity));
OnPropertyChanged(nameof(FluidVelocity));
OnPropertyChanged(nameof(ReynoldsNumber));
OnPropertyChanged(nameof(FlowRegime));
}
public override void ucLoaded()
{
// En el nuevo sistema unificado, no necesitamos crear objetos separados
base.ucLoaded();
}
public override void ucUnLoaded()
{
// En el nuevo sistema unificado, el HydraulicSimulationManager
// maneja automáticamente el registro/desregistro de objetos
base.ucUnLoaded();
}
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
}
}
}