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();
+ }
+ }
+}