From 3f2106152469400471bbf38cff9725cd9a99843e Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 4 Sep 2025 15:14:47 +0200 Subject: [PATCH] Agregada base Hidraulica --- .../HydraulicSimulationManager.cs | 681 ++++++++++++++++++ HydraulicSimulator/IHydraulicInterfaces.cs | 301 ++++++++ MainViewModel.cs | 93 +++ .../HydraulicComponents/osPumpExample.cs | 469 ++++++++++++ .../HydraulicComponents/ucPumpExample.xaml | 84 +++ .../HydraulicComponents/ucPumpExample.xaml.cs | 225 ++++++ Simulacion/BEPU.cs | 8 + Simulacion/simPumpExample.cs | 197 +++++ 8 files changed, 2058 insertions(+) create mode 100644 HydraulicSimulator/HydraulicSimulationManager.cs create mode 100644 HydraulicSimulator/IHydraulicInterfaces.cs create mode 100644 ObjetosSim/HydraulicComponents/osPumpExample.cs create mode 100644 ObjetosSim/HydraulicComponents/ucPumpExample.xaml create mode 100644 ObjetosSim/HydraulicComponents/ucPumpExample.xaml.cs create mode 100644 Simulacion/simPumpExample.cs diff --git a/HydraulicSimulator/HydraulicSimulationManager.cs b/HydraulicSimulator/HydraulicSimulationManager.cs new file mode 100644 index 0000000..8b763f8 --- /dev/null +++ b/HydraulicSimulator/HydraulicSimulationManager.cs @@ -0,0 +1,681 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using HydraulicSimulator.Models; +using CtrEditor.ObjetosSim; + +namespace CtrEditor.HydraulicSimulator +{ + /// + /// Gestiona la simulación hidráulica similar a SimulationManagerBEPU + /// + public class HydraulicSimulationManager : IDisposable + { + #region Properties and Fields + + /// + /// Red hidráulica principal que contiene todos los nodos y ramas + /// + public HydraulicNetwork Network { get; private set; } + + /// + /// Lista de objetos simulables que tienen componentes hidráulicos + /// + public List HydraulicObjects { get; private set; } + + /// + /// Fluido utilizado en la simulación (agua por defecto) + /// + public Fluid SimulationFluid { get; set; } + + /// + /// Resultado de la última simulación + /// + public SolutionResult LastSolutionResult { get; private set; } + + /// + /// Tiempo global de la simulación hidráulica + /// + public float GlobalTime { get; private set; } + + /// + /// Indica si la simulación hidráulica está habilitada + /// + public bool IsHydraulicSimulationEnabled { get; set; } = true; + + /// + /// Parámetros del solver + /// + public int MaxIterations { get; set; } = 100; + public double Tolerance { get; set; } = 1e-3; + public double RelaxationFactor { get; set; } = 0.1; + public bool VerboseOutput { get; set; } = false; + + /// + /// Contador de pasos de simulación para optimizaciones + /// + private int _stepCount = 0; + + /// + /// Cronómetro para medir tiempo de ejecución + /// + private Stopwatch _stopwatch; + + /// + /// Diccionario para mapear IDs de objetos a elementos hidráulicos + /// + private Dictionary _objectMapping; + + /// + /// Indica si hay cambios pendientes en la red que requieren reconstrucción + /// + private bool _networkNeedsRebuild = true; + + #endregion + + #region Constructor + + /// + /// Constructor del gestor de simulación hidráulica + /// + public HydraulicSimulationManager() + { + // Inicializar componentes + SimulationFluid = Fluid.Water20C; // Agua a 20°C por defecto + Network = new HydraulicNetwork(SimulationFluid); + HydraulicObjects = new List(); + _objectMapping = new Dictionary(); + _stopwatch = new Stopwatch(); + + // Inicializar resultado vacío + LastSolutionResult = new SolutionResult(false) + { + ErrorMessage = "Simulación no ejecutada" + }; + + GlobalTime = 0.0f; + + Debug.WriteLine("HydraulicSimulationManager inicializado"); + } + + #endregion + + #region Object Management + + /// + /// Registra un objeto simulable que tiene componentes hidráulicos + /// + /// Objeto con componentes hidráulicos + public void RegisterHydraulicObject(osBase hydraulicObject) + { + if (hydraulicObject == null) + return; + + if (!HydraulicObjects.Contains(hydraulicObject)) + { + HydraulicObjects.Add(hydraulicObject); + _objectMapping[hydraulicObject.Nombre] = hydraulicObject; + _networkNeedsRebuild = true; + + Debug.WriteLine($"Objeto hidráulico registrado: {hydraulicObject.Nombre}"); + } + } + + /// + /// Desregistra un objeto simulable hidráulico + /// + /// Objeto a desregistrar + public void UnregisterHydraulicObject(osBase hydraulicObject) + { + if (hydraulicObject == null) + return; + + if (HydraulicObjects.Remove(hydraulicObject)) + { + _objectMapping.Remove(hydraulicObject.Nombre); + _networkNeedsRebuild = true; + + Debug.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}"); + } + } + + /// + /// Limpia todos los objetos hidráulicos registrados + /// + public void ClearHydraulicObjects() + { + HydraulicObjects.Clear(); + _objectMapping.Clear(); + _networkNeedsRebuild = true; + + Debug.WriteLine("Todos los objetos hidráulicos han sido limpiados"); + } + + #endregion + + #region Network Building + + /// + /// Reconstruye la red hidráulica basada en los objetos registrados + /// + private void RebuildNetwork() + { + if (!_networkNeedsRebuild) + return; + + Debug.WriteLine("Reconstruyendo red hidráulica..."); + + // Crear nueva red + Network = new HydraulicNetwork(SimulationFluid); + + // Agregar nodos y elementos basados en los objetos hidráulicos + BuildNodesFromObjects(); + BuildBranchesFromObjects(); + + _networkNeedsRebuild = false; + + Debug.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas"); + } + + /// + /// Construye los nodos de la red basándose en los objetos hidráulicos + /// + private void BuildNodesFromObjects() + { + foreach (var obj in HydraulicObjects) + { + // Verificar si el objeto implementa la interfaz hidráulica + if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) + { + // Obtener nodos definidos por el objeto + var nodeDefinitions = hydraulicComponent.GetHydraulicNodes(); + + foreach (var nodeDef in nodeDefinitions) + { + // Agregar nodo a la red + Network.AddNode(nodeDef.Name, nodeDef.IsFixedPressure ? nodeDef.Pressure : null); + + if (VerboseOutput) + { + Debug.WriteLine($"Nodo agregado: {nodeDef.Name} " + + $"(Presión fija: {nodeDef.IsFixedPressure}, " + + $"Presión: {nodeDef.Pressure?.ToString() ?? "libre"})"); + } + } + } + else + { + // Objeto sin interfaz hidráulica - crear nodos básicos por compatibilidad + string nodeName = $"Node_{obj.Nombre}"; + bool isFixedPressure = DetermineIfFixedPressure(obj); + double? pressure = isFixedPressure ? GetObjectPressure(obj) : null; + + Network.AddNode(nodeName, pressure); + } + } + } + + /// + /// Construye las ramas (conexiones) entre nodos + /// + private void BuildBranchesFromObjects() + { + foreach (var obj in HydraulicObjects) + { + // Verificar si el objeto implementa la interfaz hidráulica + if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) + { + // Obtener elementos definidos por el objeto + var elementDefinitions = hydraulicComponent.GetHydraulicElements(); + + foreach (var elemDef in elementDefinitions) + { + // Crear rama con el elemento + var elements = new List { elemDef.Element }; + Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name); + + if (VerboseOutput) + { + Debug.WriteLine($"Rama agregada: {elemDef.Name} " + + $"({elemDef.FromNode} -> {elemDef.ToNode})"); + } + } + } + else + { + // Objeto sin interfaz hidráulica - crear elementos básicos por compatibilidad + var elements = CreateHydraulicElementsFromObject(obj); + + if (elements.Any()) + { + string fromNode = GetSourceNode(obj); + string toNode = GetTargetNode(obj); + + if (!string.IsNullOrEmpty(fromNode) && !string.IsNullOrEmpty(toNode)) + { + Network.AddBranch(fromNode, toNode, elements, $"Branch_{obj.Nombre}"); + } + } + } + } + } + + #endregion + + #region Helper Methods for Object Analysis + + /// + /// Determina si un objeto debe tener presión fija + /// + private bool DetermineIfFixedPressure(osBase obj) + { + // Implementación que será extendida con tipos específicos + // Por ejemplo: tanques, descargas atmosféricas = presión fija + // Uniones, conexiones = presión libre + + return false; // Por defecto, presión libre + } + + /// + /// Obtiene la presión de un objeto (si tiene presión fija) + /// + private double GetObjectPressure(osBase obj) + { + // Implementación que será extendida + return 0.0; // Por defecto, presión atmosférica + } + + /// + /// Crea elementos hidráulicos basándose en un objeto + /// + private List CreateHydraulicElementsFromObject(osBase obj) + { + var elements = new List(); + + // Implementación que será extendida con tipos específicos + // Por ejemplo: + // - Si es una bomba: crear PumpHQ + // - Si es una tubería: crear Pipe + // - Si es una válvula: crear ValveKv + + return elements; + } + + /// + /// Obtiene el nodo fuente de un objeto + /// + private string GetSourceNode(osBase obj) + { + // Implementación que será extendida + return $"Node_{obj.Nombre}_In"; + } + + /// + /// Obtiene el nodo destino de un objeto + /// + private string GetTargetNode(osBase obj) + { + // Implementación que será extendida + return $"Node_{obj.Nombre}_Out"; + } + + #endregion + + #region Simulation Step + + /// + /// Ejecuta un paso de simulación hidráulica (equivalente al Step de BEPU) + /// + public void Step(float deltaTime = 0.016f) // ~60 FPS por defecto + { + if (!IsHydraulicSimulationEnabled) + return; + + _stopwatch.Restart(); + + try + { + // Actualizar tiempo global + GlobalTime += deltaTime; + _stepCount++; + + // Reconstruir red si es necesario + RebuildNetwork(); + + // Solo resolver si tenemos objetos hidráulicos + if (HydraulicObjects.Count > 0 && Network.Branches.Count > 0) + { + // Actualizar propiedades de objetos antes de resolver + UpdateObjectProperties(); + + // Resolver la red hidráulica + LastSolutionResult = Network.Solve( + MaxIterations, + Tolerance, + RelaxationFactor, + VerboseOutput + ); + + if (LastSolutionResult.Converged) + { + // Aplicar resultados a los objetos + ApplyResultsToObjects(); + } + else if (VerboseOutput) + { + Debug.WriteLine($"Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}"); + } + } + else + { + // Sin objetos hidráulicos, resultado exitoso pero vacío + LastSolutionResult = new SolutionResult(true); + } + } + catch (Exception ex) + { + LastSolutionResult = new SolutionResult(false) + { + ErrorMessage = $"Error en simulación hidráulica: {ex.Message}" + }; + + Debug.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}"); + } + finally + { + _stopwatch.Stop(); + + if (VerboseOutput && _stepCount % 60 == 0) // Log cada segundo aprox + { + Debug.WriteLine($"Simulación hidráulica - Paso {_stepCount}: {_stopwatch.ElapsedMilliseconds}ms, " + + $"Objetos: {HydraulicObjects.Count}, Convergió: {LastSolutionResult.Converged}"); + } + } + } + + /// + /// Actualiza las propiedades de los objetos antes de resolver la red + /// + private void UpdateObjectProperties() + { + foreach (var obj in HydraulicObjects) + { + // Aquí se implementará la actualización de propiedades específicas + // Por ejemplo: estado de válvulas, velocidad de bombas, etc. + UpdateObjectHydraulicProperties(obj); + } + } + + /// + /// Actualiza las propiedades hidráulicas específicas de un objeto + /// + private void UpdateObjectHydraulicProperties(osBase obj) + { + // Actualizar propiedades basándose en las interfaces implementadas + if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) + { + // Llamar al método de actualización del objeto + hydraulicComponent.UpdateHydraulicProperties(); + } + + // Actualización específica por tipo de objeto + if (obj is IHydraulicPump pump) + { + // Verificar estado de la bomba, velocidad, etc. + UpdatePumpProperties(pump); + } + else if (obj is IHydraulicValve valve) + { + // Verificar apertura de la válvula + UpdateValveProperties(valve); + } + else if (obj is IHydraulicTank tank) + { + // Actualizar nivel y presión del tanque + UpdateTankProperties(tank); + } + } + + /// + /// Actualiza propiedades específicas de bombas + /// + private void UpdatePumpProperties(IHydraulicPump pump) + { + // Aquí se pueden hacer validaciones y ajustes específicos de bombas + // Por ejemplo, limitar velocidad, verificar estado de operación, etc. + + if (pump.SpeedRatio < 0.0) pump.SpeedRatio = 0.0; + if (pump.SpeedRatio > 1.0) pump.SpeedRatio = 1.0; + + if (VerboseOutput) + { + Debug.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " + + $"Funcionando={pump.IsRunning}, Dirección={pump.PumpDirection}"); + } + } + + /// + /// Actualiza propiedades específicas de válvulas + /// + private void UpdateValveProperties(IHydraulicValve valve) + { + // Validar apertura de válvula + if (valve.Opening < 0.0) valve.Opening = 0.0; + if (valve.Opening > 1.0) valve.Opening = 1.0; + + if (VerboseOutput) + { + Debug.WriteLine($"Válvula {valve.GetType().Name}: Apertura={valve.Opening:F2}, " + + $"Cerrada={valve.IsClosed}, Abierta={valve.IsFullyOpen}"); + } + } + + /// + /// Actualiza propiedades específicas de tanques + /// + private void UpdateTankProperties(IHydraulicTank tank) + { + // Calcular presión basada en nivel si no es presión fija + if (!tank.IsFixedPressure) + { + // P = ρgh + P_atmosferica + double pressureFromLevel = SimulationFluid.Rho * 9.80665 * tank.Level; // + presión atmosférica + tank.TankPressure = pressureFromLevel; + } + + if (VerboseOutput) + { + Debug.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " + + $"Presión={tank.TankPressure:F0}Pa, PresionFija={tank.IsFixedPressure}"); + } + } + + /// + /// Aplica los resultados de la simulación a los objetos + /// + private void ApplyResultsToObjects() + { + foreach (var obj in HydraulicObjects) + { + // Aplicar caudales y presiones calculados al objeto + ApplyResultsToObject(obj); + } + } + + /// + /// Aplica los resultados a un objeto específico + /// + private void ApplyResultsToObject(osBase obj) + { + // Aplicar resultados usando las interfaces hidráulicas + if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) + { + // Llamar al método de aplicación de resultados del objeto + hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures); + } + + // Aplicación específica por tipo de interfaz + if (obj is IHydraulicFlowReceiver flowReceiver) + { + // Buscar caudal asociado al objeto + string branchName = FindBranchNameForObject(obj); + if (!string.IsNullOrEmpty(branchName) && LastSolutionResult.Flows.ContainsKey(branchName)) + { + double flow = LastSolutionResult.Flows[branchName]; + flowReceiver.SetFlow(flow); + } + } + + if (obj is IHydraulicPressureReceiver pressureReceiver) + { + // Buscar presión asociada al objeto + string nodeName = FindNodeNameForObject(obj); + if (!string.IsNullOrEmpty(nodeName) && LastSolutionResult.Pressures.ContainsKey(nodeName)) + { + double pressure = LastSolutionResult.Pressures[nodeName]; + pressureReceiver.SetPressure(pressure); + } + } + } + + /// + /// Encuentra el nombre de rama asociado a un objeto + /// + private string FindBranchNameForObject(osBase obj) + { + // Buscar en las ramas de la red + foreach (var branch in Network.Branches) + { + if (branch.Name.Contains(obj.Nombre)) + { + return branch.Name; + } + } + + // Fallback a nombre por defecto + return $"Branch_{obj.Nombre}"; + } + + /// + /// Encuentra el nombre de nodo asociado a un objeto + /// + private string FindNodeNameForObject(osBase obj) + { + // Buscar en los nodos de la red + foreach (var nodeKvp in Network.Nodes) + { + if (nodeKvp.Key.Contains(obj.Nombre)) + { + return nodeKvp.Key; + } + } + + // Fallback a nombre por defecto + return $"Node_{obj.Nombre}"; + } + + /// + /// Establece el caudal en un objeto + /// + private void SetObjectFlow(osBase obj, double flow) + { + if (obj is IHydraulicFlowReceiver flowReceiver) + { + flowReceiver.SetFlow(flow); + } + } + + /// + /// Establece la presión en un objeto + /// + private void SetObjectPressure(osBase obj, double pressure) + { + if (obj is IHydraulicPressureReceiver pressureReceiver) + { + pressureReceiver.SetPressure(pressure); + } + } + + #endregion + + #region Public Methods + + /// + /// Fuerza la reconstrucción de la red en el próximo paso + /// + public void InvalidateNetwork() + { + _networkNeedsRebuild = true; + } + + /// + /// Obtiene estadísticas de la simulación + /// + public string GetSimulationStats() + { + return $"Objetos hidráulicos: {HydraulicObjects.Count}\n" + + $"Nodos: {Network.Nodes.Count}\n" + + $"Ramas: {Network.Branches.Count}\n" + + $"Última simulación convergió: {LastSolutionResult.Converged}\n" + + $"Iteraciones: {LastSolutionResult.Iterations}\n" + + $"Residual: {LastSolutionResult.Residual:E3}\n" + + $"Tiempo global: {GlobalTime:F2}s\n" + + $"Pasos: {_stepCount}"; + } + + /// + /// Reinicia la simulación + /// + public void Reset() + { + GlobalTime = 0.0f; + _stepCount = 0; + _networkNeedsRebuild = true; + + // Reinicializar resultado + LastSolutionResult = new SolutionResult(false) + { + ErrorMessage = "Simulación reiniciada" + }; + + Debug.WriteLine("Simulación hidráulica reiniciada"); + } + + #endregion + + #region IDisposable + + private bool _disposed = false; + + /// + /// Libera recursos del gestor de simulación + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + // Limpiar recursos gestionados + HydraulicObjects?.Clear(); + _objectMapping?.Clear(); + Network = null; + _stopwatch?.Stop(); + + Debug.WriteLine("HydraulicSimulationManager disposed"); + } + + _disposed = true; + } + } + + #endregion + } +} diff --git a/HydraulicSimulator/IHydraulicInterfaces.cs b/HydraulicSimulator/IHydraulicInterfaces.cs new file mode 100644 index 0000000..bfcce9d --- /dev/null +++ b/HydraulicSimulator/IHydraulicInterfaces.cs @@ -0,0 +1,301 @@ +using HydraulicSimulator.Models; +using System.Collections.Generic; + +namespace CtrEditor.HydraulicSimulator +{ + /// + /// Interfaz base para objetos que tienen componentes hidráulicos + /// + public interface IHydraulicComponent + { + /// + /// Indica si este objeto tiene componentes hidráulicos activos + /// + bool HasHydraulicComponents { get; } + + /// + /// Obtiene los nodos hidráulicos asociados a este objeto + /// + /// Lista de definiciones de nodos + List GetHydraulicNodes(); + + /// + /// Obtiene los elementos hidráulicos asociados a este objeto + /// + /// Lista de definiciones de elementos hidráulicos + List GetHydraulicElements(); + + /// + /// Actualiza las propiedades hidráulicas del objeto antes de la simulación + /// + void UpdateHydraulicProperties(); + + /// + /// Aplica los resultados de la simulación hidráulica al objeto + /// + /// Caudales calculados + /// Presiones calculadas + void ApplyHydraulicResults(Dictionary flows, Dictionary pressures); + } + + /// + /// Interfaz para objetos que pueden recibir información de caudal + /// + public interface IHydraulicFlowReceiver + { + /// + /// Establece el caudal actual del objeto + /// + /// Caudal en m³/s + void SetFlow(double flow); + + /// + /// Obtiene el caudal actual del objeto + /// + /// Caudal en m³/s + double GetFlow(); + } + + /// + /// Interfaz para objetos que pueden recibir información de presión + /// + public interface IHydraulicPressureReceiver + { + /// + /// Establece la presión actual del objeto + /// + /// Presión en Pa + void SetPressure(double pressure); + + /// + /// Obtiene la presión actual del objeto + /// + /// Presión en Pa + double GetPressure(); + } + + /// + /// Interfaz para objetos que son bombas hidráulicas + /// + public interface IHydraulicPump : IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver + { + /// + /// Cabeza de la bomba a caudal cero (m) + /// + double PumpHead { get; set; } + + /// + /// Caudal máximo de la bomba (m³/s) + /// + double MaxFlow { get; set; } + + /// + /// Velocidad relativa de la bomba (0.0 a 1.0) + /// + double SpeedRatio { get; set; } + + /// + /// Indica si la bomba está encendida + /// + bool IsRunning { get; set; } + + /// + /// Dirección de la bomba (+1 o -1) + /// + int PumpDirection { get; set; } + } + + /// + /// Interfaz para objetos que son válvulas hidráulicas + /// + public interface IHydraulicValve : IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver + { + /// + /// Coeficiente Kv de la válvula completamente abierta (m³/h/√bar) + /// + double KvFull { get; set; } + + /// + /// Apertura actual de la válvula (0.0 a 1.0) + /// + double Opening { get; set; } + + /// + /// Indica si la válvula está completamente cerrada + /// + bool IsClosed { get; } + + /// + /// Indica si la válvula está completamente abierta + /// + bool IsFullyOpen { get; } + } + + /// + /// Interfaz para objetos que son tuberías hidráulicas + /// + public interface IHydraulicPipe : IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver + { + /// + /// Longitud de la tubería (m) + /// + double Length { get; set; } + + /// + /// Diámetro interno de la tubería (m) + /// + double Diameter { get; set; } + + /// + /// Rugosidad absoluta de la tubería (m) + /// + double Roughness { get; set; } + } + + /// + /// Interfaz para objetos que son tanques hidráulicos + /// + public interface IHydraulicTank : IHydraulicComponent, IHydraulicPressureReceiver + { + /// + /// Presión del tanque (Pa) + /// + double TankPressure { get; set; } + + /// + /// Nivel actual del tanque (m) + /// + double Level { get; set; } + + /// + /// Área de la sección transversal del tanque (m²) + /// + double CrossSectionalArea { get; set; } + + /// + /// Indica si el tanque tiene presión fija + /// + bool IsFixedPressure { get; } + } + + /// + /// Definición de un nodo hidráulico + /// + public class HydraulicNodeDefinition + { + /// + /// Nombre único del nodo + /// + public string Name { get; set; } + + /// + /// Indica si el nodo tiene presión fija + /// + public bool IsFixedPressure { get; set; } + + /// + /// Presión del nodo (Pa), si es de presión fija + /// + public double? Pressure { get; set; } + + /// + /// Descripción del nodo + /// + public string Description { get; set; } = ""; + + public HydraulicNodeDefinition(string name, bool isFixedPressure = false, double? pressure = null, string description = "") + { + Name = name; + IsFixedPressure = isFixedPressure; + Pressure = pressure; + Description = description; + } + } + + /// + /// Definición de un elemento hidráulico + /// + public class HydraulicElementDefinition + { + /// + /// Nombre único del elemento + /// + public string Name { get; set; } + + /// + /// Nodo de origen + /// + public string FromNode { get; set; } + + /// + /// Nodo de destino + /// + public string ToNode { get; set; } + + /// + /// Elemento hidráulico + /// + public Element Element { get; set; } + + /// + /// Descripción del elemento + /// + public string Description { get; set; } = ""; + + public HydraulicElementDefinition(string name, string fromNode, string toNode, Element element, string description = "") + { + Name = name; + FromNode = fromNode; + ToNode = toNode; + Element = element; + Description = description; + } + } + + /// + /// Tipos de elementos hidráulicos + /// + public enum HydraulicElementType + { + Pipe, + Pump, + Valve, + MinorLoss, + Tank, + Junction + } + + /// + /// Configuración de un elemento hidráulico + /// + public class HydraulicElementConfiguration + { + public HydraulicElementType Type { get; set; } + public Dictionary Parameters { get; set; } = new Dictionary(); + + public HydraulicElementConfiguration(HydraulicElementType type) + { + Type = type; + } + + /// + /// Establece un parámetro de configuración + /// + public void SetParameter(string key, object value) + { + Parameters[key] = value; + } + + /// + /// Obtiene un parámetro de configuración + /// + public T GetParameter(string key, T defaultValue = default(T)) + { + if (Parameters.ContainsKey(key) && Parameters[key] is T) + { + return (T)Parameters[key]; + } + return defaultValue; + } + } +} diff --git a/MainViewModel.cs b/MainViewModel.cs index 6c39140..6225b44 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -11,6 +11,7 @@ using System.IO; using Newtonsoft.Json; using System.Windows; using CtrEditor.Simulacion; +using CtrEditor.HydraulicSimulator; // Nuevo using para el simulador hidráulico using System.Diagnostics; using System.Reflection; using System.Linq; @@ -58,6 +59,7 @@ namespace CtrEditor private bool Debug_SimulacionCreado = false; public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU(); + public HydraulicSimulationManager hydraulicSimulationManager = new HydraulicSimulationManager(); private readonly System.Timers.Timer _timerSimulacion; // Cambiado a System.Timers.Timer para mejor precisión private readonly System.Timers.Timer _timerPLCUpdate; // Cambiado a System.Timers.Timer para mejor precisión @@ -737,6 +739,9 @@ namespace CtrEditor { _stateSerializer.LoadState(SelectedImage); + // Registrar objetos hidráulicos cargados + RegisterLoadedHydraulicObjects(); + // Aplicar los filtros actuales a los objetos recién cargados if (MainWindow?.VisFilter?.FilterViewModel != null) { @@ -787,6 +792,10 @@ namespace CtrEditor { // Añadir el nuevo osBase a la colección de objetos simulables ObjetosSimulables.Add(NuevoOsBase); + + // Registrar en el simulador hidráulico si tiene componentes hidráulicos + RegisterHydraulicObjectIfNeeded(NuevoOsBase); + HasUnsavedChanges = true; } } @@ -820,6 +829,9 @@ namespace CtrEditor { if (osObjeto != null && ObjetosSimulables.Contains(osObjeto)) { + // Desregistrar del simulador hidráulico si estaba registrado + UnregisterHydraulicObjectIfNeeded(osObjeto); + ObjetosSimulables.Remove(osObjeto); if (osObjeto.VisualRepresentation != null) MainWindow.EliminarUserControlDelCanvas(osObjeto.VisualRepresentation); @@ -925,6 +937,10 @@ namespace CtrEditor NuevoObjetoDuplicado.Top += OffsetY; ObjetosSimulables.Add(NuevoObjetoDuplicado); CrearUserControlDesdeObjetoSimulable(NuevoObjetoDuplicado); + + // Registrar en el simulador hidráulico si tiene componentes hidráulicos + RegisterHydraulicObjectIfNeeded(NuevoObjetoDuplicado); + HasUnsavedChanges = true; } } @@ -1113,6 +1129,9 @@ namespace CtrEditor foreach (var objetoSimulable in ObjetosSimulables) objetoSimulable.SimulationStop(); + // Reiniciar simulador hidráulico al detener la simulación + ResetHydraulicSimulation(); + if (Debug_SimulacionCreado) { Debug_SimulacionCreado = false; @@ -1158,7 +1177,11 @@ namespace CtrEditor objetoSimulable.UpdateGeometryStep(); } + // Ejecutar simulación física BEPU simulationManager.Step(); + + // Ejecutar simulación hidráulica + hydraulicSimulationManager.Step((float)(timeBetweenCalls / 1000.0)); // Convertir ms a segundos // ✅ NUEVO: Solo crear la copia si hay objetos para procesar if (ObjetosSimulables?.Count > 0) @@ -1642,6 +1665,76 @@ namespace CtrEditor } } + #region Hydraulic Simulation Management + + /// + /// Registra un objeto en el simulador hidráulico si tiene componentes hidráulicos + /// + private void RegisterHydraulicObjectIfNeeded(osBase obj) + { + if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) + { + hydraulicSimulationManager.RegisterHydraulicObject(obj); + Debug.WriteLine($"Objeto hidráulico registrado: {obj.Nombre}"); + } + } + + /// + /// Desregistra un objeto del simulador hidráulico si estaba registrado + /// + private void UnregisterHydraulicObjectIfNeeded(osBase obj) + { + // Desregistrar independientemente de si implementa la interfaz actualmente + // (podría haber cambiado desde que se registró) + hydraulicSimulationManager.UnregisterHydraulicObject(obj); + Debug.WriteLine($"Objeto desregistrado del simulador hidráulico: {obj.Nombre}"); + } + + /// + /// Reinicia el simulador hidráulico + /// + public void ResetHydraulicSimulation() + { + hydraulicSimulationManager.Reset(); + Debug.WriteLine("Simulador hidráulico reiniciado"); + } + + /// + /// Obtiene estadísticas del simulador hidráulico + /// + public string GetHydraulicSimulationStats() + { + return hydraulicSimulationManager.GetSimulationStats(); + } + + /// + /// Habilita/deshabilita la simulación hidráulica + /// + public void SetHydraulicSimulationEnabled(bool enabled) + { + hydraulicSimulationManager.IsHydraulicSimulationEnabled = enabled; + Debug.WriteLine($"Simulación hidráulica {(enabled ? "habilitada" : "deshabilitada")}"); + } + + /// + /// Registra todos los objetos hidráulicos existentes después de cargar un proyecto + /// + private void RegisterLoadedHydraulicObjects() + { + // Limpiar registros previos + hydraulicSimulationManager.ClearHydraulicObjects(); + + // Registrar todos los objetos cargados que tengan componentes hidráulicos + foreach (var obj in ObjetosSimulables) + { + RegisterHydraulicObjectIfNeeded(obj); + } + + Debug.WriteLine($"Registrados {hydraulicSimulationManager.HydraulicObjects.Count} objetos hidráulicos tras cargar proyecto"); + } + + #endregion + } public class SimulationData diff --git a/ObjetosSim/HydraulicComponents/osPumpExample.cs b/ObjetosSim/HydraulicComponents/osPumpExample.cs new file mode 100644 index 0000000..be3de42 --- /dev/null +++ b/ObjetosSim/HydraulicComponents/osPumpExample.cs @@ -0,0 +1,469 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Controls; +using System.Numerics; +using System.Windows.Media; +using CtrEditor.HydraulicSimulator; +using CtrEditor.ObjetosSim; +using CtrEditor.FuncionesBase; +using CtrEditor.Simulacion; +using HydraulicSimulator.Models; +using Newtonsoft.Json; +using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace CtrEditor.ObjetosSim.HydraulicComponents +{ + /// + /// Ejemplo de bomba hidráulica que implementa las interfaces del simulador hidráulico + /// + public partial class osPumpExample : osBase, IHydraulicPump, IosBase + { + private simPumpExample SimGeometria; + #region Properties + + 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 físicas y visuales + [ObservableProperty] + [property: Category("🎨 Apariencia")] + [property: Description("Ancho de la bomba en metros")] + [property: Name("Ancho")] + private float ancho = 0.15f; + + [ObservableProperty] + [property: Category("🎨 Apariencia")] + [property: Description("Alto de la bomba en metros")] + [property: Name("Alto")] + private float alto = 0.10f; + + [ObservableProperty] + [property: Category("🎨 Apariencia")] + [property: Description("Profundidad de la bomba en metros")] + [property: Name("Profundidad")] + private float profundidad = 0.08f; + + [ObservableProperty] + [property: Category("🎨 Apariencia")] + [property: Description("Color visual de la bomba")] + [property: Name("Color")] + private Brush colorButton_oculto = Brushes.Blue; + + [ObservableProperty] + [property: Category("⚖️ Física")] + [property: Description("Masa del objeto en kg")] + [property: Name("Masa")] + private float mass = 5.0f; + + partial void OnAnchoChanged(float value) + { + SimGeometria?.SetDimensions(new Vector3(value, Alto, Profundidad)); + } + + partial void OnAltoChanged(float value) + { + SimGeometria?.SetDimensions(new Vector3(Ancho, value, Profundidad)); + } + + partial void OnProfundidadChanged(float value) + { + SimGeometria?.SetDimensions(new Vector3(Ancho, Alto, value)); + } + + partial void OnMassChanged(float value) + { + SimGeometria?.SetMass(value); + } + + [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 + { + if (SetProperty(ref _maxFlow, Math.Max(0.001, value))) + { + InvalidateHydraulicNetwork(); + } + } + } + + [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 + { + SetProperty(ref _isRunning, value); + } + } + + [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 + + #endregion + + #region 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 override void OnMove(float LeftPixels, float TopPixels) + { + UpdateAfterMove(); + } + + public override void OnResize(float Delta_Width, float Delta_Height) + { + Ancho += Delta_Width; + Alto += Delta_Height; + } + + public Vector2 GetCentro() + { + return new Vector2(Left + Ancho / 2, Top + Alto / 2); + } + + public void SetCentro(float x, float y) + { + Left = x - Ancho / 2; + Top = y - Alto / 2; + } + + public void SetCentro(Vector2 centro) + { + Left = centro.X - Ancho / 2; + Top = centro.Y - Alto / 2; + } + + private void ActualizarGeometrias() + { + if (SimGeometria != null && !RemoverDesdeSimulacion) + { + SimGeometria.SetPosition(GetCentro()); + } + } + + public osPumpExample() + { + Nombre = "Bomba Hidráulica"; + } + + public void UpdateAfterMove() + { + if (!RemoverDesdeSimulacion) + { + ActualizarGeometrias(); + SimGeometria?.SetDimensions(new Vector3(Ancho, Alto, Profundidad)); + } + } + + public override void UpdateGeometryStart() + { + // Se llama cuando inicia la simulación - crear geometría si no existe + if (SimGeometria == null) + { + SimGeometria = simulationManager.AddPumpExample(new Vector3(Ancho, Alto, Profundidad), GetCentro(), Mass); + SimGeometria.SimObjectType = "PumpExample"; + SimGeometria.WpfObject = this; + } + else + { + ActualizarGeometrias(); + SimGeometria?.SetDimensions(new Vector3(Ancho, Alto, Profundidad)); + SimGeometria?.SetMass(Mass); + } + } + + public override void UpdateGeometryStep() + { + // Se llama durante cada paso de la simulación + if (SimGeometria != null) + { + // Actualizar posición desde la simulación hacia WPF + SetCentro(SimGeometria.Center); + + // Actualizar propiedades hidráulicas en la simulación + SimGeometria.UpdateHydraulicProperties(PumpHead, MaxFlow, SpeedRatio, IsRunning, PumpDirection); + + // Obtener resultados de la simulación hidráulica + CurrentFlow = SimGeometria.GetCurrentFlow(); + CurrentPressure = SimGeometria.GetCurrentPressure(); + } + } + + public override void UpdateControl(int elapsedMilliseconds) + { + if (SimGeometria != null) + { + SetCentro(SimGeometria.Center); + + // Actualizar el color según el estado + if (IsRunning) + ColorButton_oculto = Brushes.Green; + else + ColorButton_oculto = Brushes.Gray; + + // Actualizar valores actuales desde la simulación + CurrentFlow = SimGeometria.GetCurrentFlow(); + CurrentPressure = SimGeometria.GetCurrentPressure(); + } + } + + public override void ucLoaded() + { + // El UserControl ya se ha cargado y podemos obtener las coordenadas para + // crear el objeto de simulacion + base.ucLoaded(); + SimGeometria = simulationManager.AddPumpExample(new Vector3(Ancho, Alto, Profundidad), GetCentro(), Mass); + } + + public override void ucUnLoaded() + { + // El UserControl se esta eliminando + // eliminar el objeto de simulacion + simulationManager.Remove(SimGeometria); + } + + #endregion + + #region IHydraulicComponent Implementation + + [JsonIgnore] + public bool HasHydraulicComponents => true; + + public List GetHydraulicNodes() + { + var nodes = new List + { + new HydraulicNodeDefinition($"{Nombre}_In", false, null, "Entrada de la bomba"), + new HydraulicNodeDefinition($"{Nombre}_Out", false, null, "Salida de la bomba") + }; + + return nodes; + } + + public List GetHydraulicElements() + { + var elements = new List(); + + if (HasHydraulicComponents) + { + // Crear bomba con parámetros actuales + var pump = new PumpHQ( + h0: PumpHead, + q0: MaxFlow, + speedRel: IsRunning ? SpeedRatio : 0.0, // Si no está funcionando, velocidad = 0 + direction: PumpDirection + ); + + var pumpElement = new HydraulicElementDefinition( + $"{Nombre}_Pump", + $"{Nombre}_In", + $"{Nombre}_Out", + pump, + $"Bomba {Nombre}" + ); + + elements.Add(pumpElement); + } + + 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; + + 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"; + string inletNodeName = $"{Nombre}_In"; + string outletNodeName = $"{Nombre}_Out"; + + if (flows.ContainsKey(pumpBranchName)) + { + CurrentFlow = flows[pumpBranchName]; + } + + if (pressures.ContainsKey(inletNodeName)) + { + CurrentPressure = pressures[inletNodeName]; + } + else if (pressures.ContainsKey(outletNodeName)) + { + CurrentPressure = pressures[outletNodeName]; + } + } + + #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) + { + CurrentPressure = pressure; + } + + public double GetPressure() + { + return CurrentPressure; + } + + #endregion + + #region Helper Methods + + /// + /// Invalida la red hidráulica para forzar reconstrucción + /// + private void InvalidateHydraulicNetwork() + { + // Si tenemos acceso al MainViewModel, invalidar la red + if (_mainViewModel?.hydraulicSimulationManager != null) + { + _mainViewModel.hydraulicSimulationManager.InvalidateNetwork(); + } + } + + #endregion + + #region Static Interface Implementation + + public static string NombreClase() => "Bomba Hidráulica"; + public static string NombreCategoria() => "Componentes Hidráulicos"; + + #endregion + } + + /// + /// 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; + } + } +} diff --git a/ObjetosSim/HydraulicComponents/ucPumpExample.xaml b/ObjetosSim/HydraulicComponents/ucPumpExample.xaml new file mode 100644 index 0000000..4987766 --- /dev/null +++ b/ObjetosSim/HydraulicComponents/ucPumpExample.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ObjetosSim/HydraulicComponents/ucPumpExample.xaml.cs b/ObjetosSim/HydraulicComponents/ucPumpExample.xaml.cs new file mode 100644 index 0000000..3b70f75 --- /dev/null +++ b/ObjetosSim/HydraulicComponents/ucPumpExample.xaml.cs @@ -0,0 +1,225 @@ +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Animation; +using CtrEditor.ObjetosSim; +using CtrEditor.FuncionesBase; + +namespace CtrEditor.ObjetosSim.HydraulicComponents +{ + /// + /// UserControl para la bomba hidráulica de ejemplo + /// + public partial class ucPumpExample : UserControl, IDataContainer + { + public osBase? Datos { get; set; } + public int zIndex_fromFrames { get; set; } = 0; + + private Storyboard? _rotationAnimation; + private bool _isHighlighted = false; + + public ucPumpExample() + { + InitializeComponent(); + this.Loaded += OnLoaded; + this.Unloaded += OnUnloaded; + DataContextChanged += OnDataContextChanged; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + Datos?.ucLoaded(); + UpdateVisualState(); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + Datos?.ucUnLoaded(); + } + + private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (DataContext is osPumpExample pump) + { + Datos = pump; + pump.PropertyChanged += OnPumpPropertyChanged; + UpdateVisualState(); + } + } + + private void OnPumpPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(osPumpExample.IsRunning) || + e.PropertyName == nameof(osPumpExample.SpeedRatio) || + e.PropertyName == nameof(osPumpExample.PumpDirection) || + e.PropertyName == nameof(osPumpExample.CurrentFlow) || + e.PropertyName == nameof(osPumpExample.CurrentPressure)) + { + UpdateVisualState(); + } + } + + /// + /// Actualiza el estado visual de la bomba + /// + private void UpdateVisualState() + { + if (Datos is not osPumpExample pump) return; + + try + { + Dispatcher.BeginInvoke(() => + { + UpdateStatusLED(pump); + UpdateRotationAnimation(pump); + UpdateDirectionArrow(pump); + UpdateStatusInfo(pump); + }); + } + catch (Exception ex) + { + Debug.WriteLine($"Error updating pump visual state: {ex.Message}"); + } + } + + /// + /// Actualiza el LED de estado + /// + private void UpdateStatusLED(osPumpExample pump) + { + if (pump.IsRunning && pump.SpeedRatio > 0) + { + StatusLED.Fill = new SolidColorBrush(Colors.LimeGreen); + } + else + { + StatusLED.Fill = new SolidColorBrush(Colors.Red); + } + } + + /// + /// Actualiza la animación de rotación + /// + private void UpdateRotationAnimation(osPumpExample pump) + { + _rotationAnimation?.Stop(); + + if (pump.IsRunning && pump.SpeedRatio > 0) + { + // Crear animación de rotación + var rotateTransform = new RotateTransform(); + PumpBackground.RenderTransform = rotateTransform; + PumpBackground.RenderTransformOrigin = new Point(0.5, 0.5); + + var animation = new DoubleAnimation + { + From = 0, + To = 360, + Duration = TimeSpan.FromSeconds(2.0 / pump.SpeedRatio), // Más rápido con mayor velocidad + RepeatBehavior = RepeatBehavior.Forever + }; + + _rotationAnimation = new Storyboard(); + Storyboard.SetTarget(animation, rotateTransform); + Storyboard.SetTargetProperty(animation, new PropertyPath(RotateTransform.AngleProperty)); + _rotationAnimation.Children.Add(animation); + _rotationAnimation.Begin(); + } + else + { + PumpBackground.RenderTransform = null; + } + } + + /// + /// Actualiza la flecha de dirección + /// + private void UpdateDirectionArrow(osPumpExample pump) + { + if (pump.PumpDirection == -1) + { + ArrowRotation.Angle = 180; // Invertir flecha + DirectionArrow.Fill = new SolidColorBrush(Colors.Orange); + } + else + { + ArrowRotation.Angle = 0; + DirectionArrow.Fill = new SolidColorBrush(Colors.DarkBlue); + } + } + + /// + /// Actualiza la información de estado + /// + private void UpdateStatusInfo(osPumpExample pump) + { + FlowText.Text = $"{pump.CurrentFlowLMin:F1} L/min"; + PressureText.Text = $"{pump.CurrentPressureBar:F2} bar"; + + // Mostrar información si hay datos relevantes + if (Math.Abs(pump.CurrentFlow) > 0.001 || Math.Abs(pump.CurrentPressure) > 1000) + { + StatusInfo.Visibility = Visibility.Visible; + } + else + { + StatusInfo.Visibility = Visibility.Collapsed; + } + } + + #region IDataContainer Implementation + + public void Highlight(bool state) + { + _isHighlighted = state; + + if (state) + { + PumpBackground.Stroke = new SolidColorBrush(Colors.Yellow); + PumpBackground.StrokeThickness = 3; + } + else + { + PumpBackground.Stroke = new SolidColorBrush(Colors.DarkSlateGray); + PumpBackground.StrokeThickness = 2; + } + } + + #endregion + + protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e) + { + base.OnMouseEnter(e); + if (!_isHighlighted) + { + // Mostrar información al pasar el mouse + StatusInfo.Visibility = Visibility.Visible; + } + } + + protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e) + { + base.OnMouseLeave(e); + if (!_isHighlighted && Datos is osPumpExample pump) + { + // Ocultar información si no hay datos relevantes + if (Math.Abs(pump.CurrentFlow) < 0.001 && Math.Abs(pump.CurrentPressure) < 1000) + { + StatusInfo.Visibility = Visibility.Collapsed; + } + } + } + + public ZIndexEnum ZIndex_Base() + { + return ZIndexEnum.Estaticos; + } + + ~ucPumpExample() + { + _rotationAnimation?.Stop(); + } + } +} diff --git a/Simulacion/BEPU.cs b/Simulacion/BEPU.cs index 30315b9..4ddd3e4 100644 --- a/Simulacion/BEPU.cs +++ b/Simulacion/BEPU.cs @@ -1168,6 +1168,14 @@ namespace CtrEditor.Simulacion return botella; } + public simPumpExample AddPumpExample(Vector3 dimensions, Vector2 position, float mass) + { + var pump = new simPumpExample(simulation, _deferredActions, dimensions, position, mass); + Cuerpos.Add(pump); + RegisterObjectHandle(pump); + return pump; + } + public simTransporte AddRectangle(float width, float height, Vector2 position, float angle) { var transporte = new simTransporte(simulation, _deferredActions, width, height, position, angle, this); diff --git a/Simulacion/simPumpExample.cs b/Simulacion/simPumpExample.cs new file mode 100644 index 0000000..30b01ec --- /dev/null +++ b/Simulacion/simPumpExample.cs @@ -0,0 +1,197 @@ +using BepuPhysics.Collidables; +using BepuPhysics.Constraints; +using BepuPhysics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using CtrEditor.HydraulicSimulator; + +namespace CtrEditor.Simulacion +{ + public class simPumpExample : simBase + { + public float Width; + public float Height; + public float Depth; + private float _mass; + + // Propiedades hidráulicas + public double PumpHead { get; set; } = 50.0; + public double MaxFlow { get; set; } = 0.01; + public double SpeedRatio { get; set; } = 1.0; + public bool IsRunning { get; set; } = true; + public int PumpDirection { get; set; } = 1; + + // Estado actual de la simulación hidráulica + public double CurrentFlow { get; set; } = 0.0; + public double CurrentPressure { get; set; } = 0.0; + + public simPumpExample(Simulation simulation, List deferredActions, Vector3 dimensions, Vector2 position, float mass) + { + _simulation = simulation; + Width = dimensions.X; + Height = dimensions.Y; + Depth = dimensions.Z; + _mass = mass; + + // Usar COORDINATECONVERTER para conversión centralizada + var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Depth / 2f + zPos_Transporte + zAltura_Transporte); + Create(position3D); + } + + public float CenterX + { + get + { + return GetPosition().X; + } + set + { + var pos = GetPosition(); + SetPosition(value, pos.Y, pos.Z); + } + } + + public float CenterY + { + get + { + // Usar COORDINATECONVERTER para conversión centralizada + return CoordinateConverter.BepuYToWpfY(GetPosition().Y); + } + set + { + var pos = GetPosition(); + // Usar COORDINATECONVERTER para conversión centralizada + SetPosition(pos.X, CoordinateConverter.WpfYToBepuY(value), pos.Z); + } + } + + public Vector2 Center + { + get + { + var pos3D = GetPosition(); + // Usar COORDINATECONVERTER para conversión centralizada + return CoordinateConverter.BepuVector3ToWpfVector2(pos3D); + } + set + { + // Mantener la Z actual, solo cambiar X, Y + var currentPos = GetPosition(); + // Usar COORDINATECONVERTER para conversión centralizada + SetPosition(value.X, CoordinateConverter.WpfYToBepuY(value.Y), currentPos.Z); + } + } + + public float Mass + { + get + { + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); + return 1f / bodyReference.LocalInertia.InverseMass; + } + return _mass; + } + set + { + _mass = value; + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var box = new Box(Width, Height, Depth); + var inertia = box.ComputeInertia(_mass); + _simulation.Bodies.SetLocalInertia(BodyHandle, inertia); + } + } + } + + private void Create(Vector3 position) + { + RemoverBody(); + + var box = new Box(Width, Height, Depth); + var shapeIndex = _simulation.Shapes.Add(box); + + // Crear el cuerpo estático (las bombas no se mueven físicamente) + var inertia = box.ComputeInertia(_mass); + var bodyDescription = BodyDescription.CreateKinematic( + new RigidPose(position), + new CollidableDescription(shapeIndex, 0), + new BodyActivityDescription(-1f) + ); + + BodyHandle = _simulation.Bodies.Add(bodyDescription); + _bodyCreated = true; + + // Registrar en el diccionario del SimulationManager + if (_simulationManager != null) + _simulationManager.CollidableData[BodyHandle.Value] = this; + } + + public void SetDimensions(Vector3 dimensions) + { + Width = dimensions.X; + Height = dimensions.Y; + Depth = dimensions.Z; + + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var box = new Box(Width, Height, Depth); + var shapeIndex = _simulation.Shapes.Add(box); + ChangeBodyShape(shapeIndex); + } + } + + public void SetMass(float mass) + { + Mass = mass; + } + + /// + /// Actualiza las propiedades hidráulicas de la bomba + /// + public void UpdateHydraulicProperties(double pumpHead, double maxFlow, double speedRatio, bool isRunning, int direction) + { + PumpHead = pumpHead; + MaxFlow = maxFlow; + SpeedRatio = speedRatio; + IsRunning = isRunning; + PumpDirection = direction; + } + + /// + /// Aplica los resultados de la simulación hidráulica + /// + public void ApplyHydraulicResults(double flow, double pressure) + { + CurrentFlow = flow; + CurrentPressure = pressure; + } + + /// + /// Obtiene el caudal actual de la bomba + /// + public double GetCurrentFlow() + { + return CurrentFlow; + } + + /// + /// Obtiene la presión actual de la bomba + /// + public double GetCurrentPressure() + { + return CurrentPressure; + } + + public new void RemoverBody() + { + base.RemoverBody(); + } + } +}