Agregada base Hidraulica

This commit is contained in:
Miguel 2025-09-04 15:14:47 +02:00
parent 1e6ad6377e
commit 3f21061524
8 changed files with 2058 additions and 0 deletions

View File

@ -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
{
/// <summary>
/// Gestiona la simulación hidráulica similar a SimulationManagerBEPU
/// </summary>
public class HydraulicSimulationManager : IDisposable
{
#region Properties and Fields
/// <summary>
/// Red hidráulica principal que contiene todos los nodos y ramas
/// </summary>
public HydraulicNetwork Network { get; private set; }
/// <summary>
/// Lista de objetos simulables que tienen componentes hidráulicos
/// </summary>
public List<osBase> HydraulicObjects { get; private set; }
/// <summary>
/// Fluido utilizado en la simulación (agua por defecto)
/// </summary>
public Fluid SimulationFluid { get; set; }
/// <summary>
/// Resultado de la última simulación
/// </summary>
public SolutionResult LastSolutionResult { get; private set; }
/// <summary>
/// Tiempo global de la simulación hidráulica
/// </summary>
public float GlobalTime { get; private set; }
/// <summary>
/// Indica si la simulación hidráulica está habilitada
/// </summary>
public bool IsHydraulicSimulationEnabled { get; set; } = true;
/// <summary>
/// Parámetros del solver
/// </summary>
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;
/// <summary>
/// Contador de pasos de simulación para optimizaciones
/// </summary>
private int _stepCount = 0;
/// <summary>
/// Cronómetro para medir tiempo de ejecución
/// </summary>
private Stopwatch _stopwatch;
/// <summary>
/// Diccionario para mapear IDs de objetos a elementos hidráulicos
/// </summary>
private Dictionary<string, osBase> _objectMapping;
/// <summary>
/// Indica si hay cambios pendientes en la red que requieren reconstrucción
/// </summary>
private bool _networkNeedsRebuild = true;
#endregion
#region Constructor
/// <summary>
/// Constructor del gestor de simulación hidráulica
/// </summary>
public HydraulicSimulationManager()
{
// Inicializar componentes
SimulationFluid = Fluid.Water20C; // Agua a 20°C por defecto
Network = new HydraulicNetwork(SimulationFluid);
HydraulicObjects = new List<osBase>();
_objectMapping = new Dictionary<string, osBase>();
_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
/// <summary>
/// Registra un objeto simulable que tiene componentes hidráulicos
/// </summary>
/// <param name="hydraulicObject">Objeto con componentes hidráulicos</param>
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}");
}
}
/// <summary>
/// Desregistra un objeto simulable hidráulico
/// </summary>
/// <param name="hydraulicObject">Objeto a desregistrar</param>
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}");
}
}
/// <summary>
/// Limpia todos los objetos hidráulicos registrados
/// </summary>
public void ClearHydraulicObjects()
{
HydraulicObjects.Clear();
_objectMapping.Clear();
_networkNeedsRebuild = true;
Debug.WriteLine("Todos los objetos hidráulicos han sido limpiados");
}
#endregion
#region Network Building
/// <summary>
/// Reconstruye la red hidráulica basada en los objetos registrados
/// </summary>
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");
}
/// <summary>
/// Construye los nodos de la red basándose en los objetos hidráulicos
/// </summary>
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);
}
}
}
/// <summary>
/// Construye las ramas (conexiones) entre nodos
/// </summary>
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<Element> { 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
/// <summary>
/// Determina si un objeto debe tener presión fija
/// </summary>
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
}
/// <summary>
/// Obtiene la presión de un objeto (si tiene presión fija)
/// </summary>
private double GetObjectPressure(osBase obj)
{
// Implementación que será extendida
return 0.0; // Por defecto, presión atmosférica
}
/// <summary>
/// Crea elementos hidráulicos basándose en un objeto
/// </summary>
private List<Element> CreateHydraulicElementsFromObject(osBase obj)
{
var elements = new List<Element>();
// 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;
}
/// <summary>
/// Obtiene el nodo fuente de un objeto
/// </summary>
private string GetSourceNode(osBase obj)
{
// Implementación que será extendida
return $"Node_{obj.Nombre}_In";
}
/// <summary>
/// Obtiene el nodo destino de un objeto
/// </summary>
private string GetTargetNode(osBase obj)
{
// Implementación que será extendida
return $"Node_{obj.Nombre}_Out";
}
#endregion
#region Simulation Step
/// <summary>
/// Ejecuta un paso de simulación hidráulica (equivalente al Step de BEPU)
/// </summary>
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}");
}
}
}
/// <summary>
/// Actualiza las propiedades de los objetos antes de resolver la red
/// </summary>
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);
}
}
/// <summary>
/// Actualiza las propiedades hidráulicas específicas de un objeto
/// </summary>
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);
}
}
/// <summary>
/// Actualiza propiedades específicas de bombas
/// </summary>
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}");
}
}
/// <summary>
/// Actualiza propiedades específicas de válvulas
/// </summary>
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}");
}
}
/// <summary>
/// Actualiza propiedades específicas de tanques
/// </summary>
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}");
}
}
/// <summary>
/// Aplica los resultados de la simulación a los objetos
/// </summary>
private void ApplyResultsToObjects()
{
foreach (var obj in HydraulicObjects)
{
// Aplicar caudales y presiones calculados al objeto
ApplyResultsToObject(obj);
}
}
/// <summary>
/// Aplica los resultados a un objeto específico
/// </summary>
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);
}
}
}
/// <summary>
/// Encuentra el nombre de rama asociado a un objeto
/// </summary>
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}";
}
/// <summary>
/// Encuentra el nombre de nodo asociado a un objeto
/// </summary>
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}";
}
/// <summary>
/// Establece el caudal en un objeto
/// </summary>
private void SetObjectFlow(osBase obj, double flow)
{
if (obj is IHydraulicFlowReceiver flowReceiver)
{
flowReceiver.SetFlow(flow);
}
}
/// <summary>
/// Establece la presión en un objeto
/// </summary>
private void SetObjectPressure(osBase obj, double pressure)
{
if (obj is IHydraulicPressureReceiver pressureReceiver)
{
pressureReceiver.SetPressure(pressure);
}
}
#endregion
#region Public Methods
/// <summary>
/// Fuerza la reconstrucción de la red en el próximo paso
/// </summary>
public void InvalidateNetwork()
{
_networkNeedsRebuild = true;
}
/// <summary>
/// Obtiene estadísticas de la simulación
/// </summary>
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}";
}
/// <summary>
/// Reinicia la simulación
/// </summary>
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;
/// <summary>
/// Libera recursos del gestor de simulación
/// </summary>
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
}
}

View File

@ -0,0 +1,301 @@
using HydraulicSimulator.Models;
using System.Collections.Generic;
namespace CtrEditor.HydraulicSimulator
{
/// <summary>
/// Interfaz base para objetos que tienen componentes hidráulicos
/// </summary>
public interface IHydraulicComponent
{
/// <summary>
/// Indica si este objeto tiene componentes hidráulicos activos
/// </summary>
bool HasHydraulicComponents { get; }
/// <summary>
/// Obtiene los nodos hidráulicos asociados a este objeto
/// </summary>
/// <returns>Lista de definiciones de nodos</returns>
List<HydraulicNodeDefinition> GetHydraulicNodes();
/// <summary>
/// Obtiene los elementos hidráulicos asociados a este objeto
/// </summary>
/// <returns>Lista de definiciones de elementos hidráulicos</returns>
List<HydraulicElementDefinition> GetHydraulicElements();
/// <summary>
/// Actualiza las propiedades hidráulicas del objeto antes de la simulación
/// </summary>
void UpdateHydraulicProperties();
/// <summary>
/// Aplica los resultados de la simulación hidráulica al objeto
/// </summary>
/// <param name="flows">Caudales calculados</param>
/// <param name="pressures">Presiones calculadas</param>
void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures);
}
/// <summary>
/// Interfaz para objetos que pueden recibir información de caudal
/// </summary>
public interface IHydraulicFlowReceiver
{
/// <summary>
/// Establece el caudal actual del objeto
/// </summary>
/// <param name="flow">Caudal en m³/s</param>
void SetFlow(double flow);
/// <summary>
/// Obtiene el caudal actual del objeto
/// </summary>
/// <returns>Caudal en m³/s</returns>
double GetFlow();
}
/// <summary>
/// Interfaz para objetos que pueden recibir información de presión
/// </summary>
public interface IHydraulicPressureReceiver
{
/// <summary>
/// Establece la presión actual del objeto
/// </summary>
/// <param name="pressure">Presión en Pa</param>
void SetPressure(double pressure);
/// <summary>
/// Obtiene la presión actual del objeto
/// </summary>
/// <returns>Presión en Pa</returns>
double GetPressure();
}
/// <summary>
/// Interfaz para objetos que son bombas hidráulicas
/// </summary>
public interface IHydraulicPump : IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver
{
/// <summary>
/// Cabeza de la bomba a caudal cero (m)
/// </summary>
double PumpHead { get; set; }
/// <summary>
/// Caudal máximo de la bomba (m³/s)
/// </summary>
double MaxFlow { get; set; }
/// <summary>
/// Velocidad relativa de la bomba (0.0 a 1.0)
/// </summary>
double SpeedRatio { get; set; }
/// <summary>
/// Indica si la bomba está encendida
/// </summary>
bool IsRunning { get; set; }
/// <summary>
/// Dirección de la bomba (+1 o -1)
/// </summary>
int PumpDirection { get; set; }
}
/// <summary>
/// Interfaz para objetos que son válvulas hidráulicas
/// </summary>
public interface IHydraulicValve : IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver
{
/// <summary>
/// Coeficiente Kv de la válvula completamente abierta (m³/h/√bar)
/// </summary>
double KvFull { get; set; }
/// <summary>
/// Apertura actual de la válvula (0.0 a 1.0)
/// </summary>
double Opening { get; set; }
/// <summary>
/// Indica si la válvula está completamente cerrada
/// </summary>
bool IsClosed { get; }
/// <summary>
/// Indica si la válvula está completamente abierta
/// </summary>
bool IsFullyOpen { get; }
}
/// <summary>
/// Interfaz para objetos que son tuberías hidráulicas
/// </summary>
public interface IHydraulicPipe : IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver
{
/// <summary>
/// Longitud de la tubería (m)
/// </summary>
double Length { get; set; }
/// <summary>
/// Diámetro interno de la tubería (m)
/// </summary>
double Diameter { get; set; }
/// <summary>
/// Rugosidad absoluta de la tubería (m)
/// </summary>
double Roughness { get; set; }
}
/// <summary>
/// Interfaz para objetos que son tanques hidráulicos
/// </summary>
public interface IHydraulicTank : IHydraulicComponent, IHydraulicPressureReceiver
{
/// <summary>
/// Presión del tanque (Pa)
/// </summary>
double TankPressure { get; set; }
/// <summary>
/// Nivel actual del tanque (m)
/// </summary>
double Level { get; set; }
/// <summary>
/// Área de la sección transversal del tanque (m²)
/// </summary>
double CrossSectionalArea { get; set; }
/// <summary>
/// Indica si el tanque tiene presión fija
/// </summary>
bool IsFixedPressure { get; }
}
/// <summary>
/// Definición de un nodo hidráulico
/// </summary>
public class HydraulicNodeDefinition
{
/// <summary>
/// Nombre único del nodo
/// </summary>
public string Name { get; set; }
/// <summary>
/// Indica si el nodo tiene presión fija
/// </summary>
public bool IsFixedPressure { get; set; }
/// <summary>
/// Presión del nodo (Pa), si es de presión fija
/// </summary>
public double? Pressure { get; set; }
/// <summary>
/// Descripción del nodo
/// </summary>
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;
}
}
/// <summary>
/// Definición de un elemento hidráulico
/// </summary>
public class HydraulicElementDefinition
{
/// <summary>
/// Nombre único del elemento
/// </summary>
public string Name { get; set; }
/// <summary>
/// Nodo de origen
/// </summary>
public string FromNode { get; set; }
/// <summary>
/// Nodo de destino
/// </summary>
public string ToNode { get; set; }
/// <summary>
/// Elemento hidráulico
/// </summary>
public Element Element { get; set; }
/// <summary>
/// Descripción del elemento
/// </summary>
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;
}
}
/// <summary>
/// Tipos de elementos hidráulicos
/// </summary>
public enum HydraulicElementType
{
Pipe,
Pump,
Valve,
MinorLoss,
Tank,
Junction
}
/// <summary>
/// Configuración de un elemento hidráulico
/// </summary>
public class HydraulicElementConfiguration
{
public HydraulicElementType Type { get; set; }
public Dictionary<string, object> Parameters { get; set; } = new Dictionary<string, object>();
public HydraulicElementConfiguration(HydraulicElementType type)
{
Type = type;
}
/// <summary>
/// Establece un parámetro de configuración
/// </summary>
public void SetParameter(string key, object value)
{
Parameters[key] = value;
}
/// <summary>
/// Obtiene un parámetro de configuración
/// </summary>
public T GetParameter<T>(string key, T defaultValue = default(T))
{
if (Parameters.ContainsKey(key) && Parameters[key] is T)
{
return (T)Parameters[key];
}
return defaultValue;
}
}
}

View File

@ -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,8 +1177,12 @@ 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
/// <summary>
/// Registra un objeto en el simulador hidráulico si tiene componentes hidráulicos
/// </summary>
private void RegisterHydraulicObjectIfNeeded(osBase obj)
{
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
{
hydraulicSimulationManager.RegisterHydraulicObject(obj);
Debug.WriteLine($"Objeto hidráulico registrado: {obj.Nombre}");
}
}
/// <summary>
/// Desregistra un objeto del simulador hidráulico si estaba registrado
/// </summary>
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}");
}
/// <summary>
/// Reinicia el simulador hidráulico
/// </summary>
public void ResetHydraulicSimulation()
{
hydraulicSimulationManager.Reset();
Debug.WriteLine("Simulador hidráulico reiniciado");
}
/// <summary>
/// Obtiene estadísticas del simulador hidráulico
/// </summary>
public string GetHydraulicSimulationStats()
{
return hydraulicSimulationManager.GetSimulationStats();
}
/// <summary>
/// Habilita/deshabilita la simulación hidráulica
/// </summary>
public void SetHydraulicSimulationEnabled(bool enabled)
{
hydraulicSimulationManager.IsHydraulicSimulationEnabled = enabled;
Debug.WriteLine($"Simulación hidráulica {(enabled ? "habilitada" : "deshabilitada")}");
}
/// <summary>
/// Registra todos los objetos hidráulicos existentes después de cargar un proyecto
/// </summary>
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

View File

@ -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
{
/// <summary>
/// Ejemplo de bomba hidráulica que implementa las interfaces del simulador hidráulico
/// </summary>
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<HydraulicNodeDefinition> GetHydraulicNodes()
{
var nodes = new List<HydraulicNodeDefinition>
{
new HydraulicNodeDefinition($"{Nombre}_In", false, null, "Entrada de la bomba"),
new HydraulicNodeDefinition($"{Nombre}_Out", false, null, "Salida de la bomba")
};
return nodes;
}
public List<HydraulicElementDefinition> GetHydraulicElements()
{
var elements = new List<HydraulicElementDefinition>();
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<string, double> flows, Dictionary<string, double> 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
/// <summary>
/// Invalida la red hidráulica para forzar reconstrucción
/// </summary>
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
}
/// <summary>
/// Proveedor de items para la dirección de la bomba
/// </summary>
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;
}
}
}

View File

@ -0,0 +1,84 @@
<UserControl x:Class="CtrEditor.ObjetosSim.HydraulicComponents.ucPumpExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="80">
<Grid>
<!-- Fondo de la bomba -->
<Ellipse x:Name="PumpBackground"
Fill="LightSteelBlue"
Stroke="DarkSlateGray"
StrokeThickness="2"/>
<!-- Indicador de dirección -->
<Polygon x:Name="DirectionArrow"
Fill="DarkBlue"
Points="30,25 50,35 30,45"
RenderTransformOrigin="0.5,0.5">
<Polygon.RenderTransform>
<RotateTransform x:Name="ArrowRotation" Angle="0"/>
</Polygon.RenderTransform>
</Polygon>
<!-- Texto de identificación -->
<TextBlock x:Name="PumpLabel"
Text="P"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
FontSize="16"
Foreground="White"
Margin="0,0,0,0"/>
<!-- Indicador de estado (LED) -->
<Ellipse x:Name="StatusLED"
Width="8"
Height="8"
Fill="Red"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,5,5,0"/>
<!-- Conexiones de entrada y salida -->
<Rectangle x:Name="InletConnection"
Width="10"
Height="4"
Fill="DarkSlateGray"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="-5,0,0,0"/>
<Rectangle x:Name="OutletConnection"
Width="10"
Height="4"
Fill="DarkSlateGray"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0,0,-5,0"/>
<!-- Información de estado (opcional, visible en modo debug) -->
<Border x:Name="StatusInfo"
Background="Black"
Opacity="0.8"
CornerRadius="3"
Visibility="Collapsed"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="5">
<StackPanel Orientation="Vertical" Margin="3">
<TextBlock x:Name="FlowText"
Text="0.0 L/min"
Foreground="White"
FontSize="8"
HorizontalAlignment="Center"/>
<TextBlock x:Name="PressureText"
Text="0.0 bar"
Foreground="White"
FontSize="8"
HorizontalAlignment="Center"/>
</StackPanel>
</Border>
</Grid>
</UserControl>

View File

@ -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
{
/// <summary>
/// UserControl para la bomba hidráulica de ejemplo
/// </summary>
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();
}
}
/// <summary>
/// Actualiza el estado visual de la bomba
/// </summary>
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}");
}
}
/// <summary>
/// Actualiza el LED de estado
/// </summary>
private void UpdateStatusLED(osPumpExample pump)
{
if (pump.IsRunning && pump.SpeedRatio > 0)
{
StatusLED.Fill = new SolidColorBrush(Colors.LimeGreen);
}
else
{
StatusLED.Fill = new SolidColorBrush(Colors.Red);
}
}
/// <summary>
/// Actualiza la animación de rotación
/// </summary>
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;
}
}
/// <summary>
/// Actualiza la flecha de dirección
/// </summary>
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);
}
}
/// <summary>
/// Actualiza la información de estado
/// </summary>
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();
}
}
}

View File

@ -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);

View File

@ -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<Action> 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;
}
/// <summary>
/// Actualiza las propiedades hidráulicas de la bomba
/// </summary>
public void UpdateHydraulicProperties(double pumpHead, double maxFlow, double speedRatio, bool isRunning, int direction)
{
PumpHead = pumpHead;
MaxFlow = maxFlow;
SpeedRatio = speedRatio;
IsRunning = isRunning;
PumpDirection = direction;
}
/// <summary>
/// Aplica los resultados de la simulación hidráulica
/// </summary>
public void ApplyHydraulicResults(double flow, double pressure)
{
CurrentFlow = flow;
CurrentPressure = pressure;
}
/// <summary>
/// Obtiene el caudal actual de la bomba
/// </summary>
public double GetCurrentFlow()
{
return CurrentFlow;
}
/// <summary>
/// Obtiene la presión actual de la bomba
/// </summary>
public double GetCurrentPressure()
{
return CurrentPressure;
}
public new void RemoverBody()
{
base.RemoverBody();
}
}
}