Refactor hydraulic components: removed ucPumpExample, added ucHydDischargeTank and ucHydPipe with corresponding logic and XAML. Introduced new converters for UI bindings and created a simple hydraulic system example with testing capabilities.

This commit is contained in:
Miguel 2025-09-04 17:29:20 +02:00
parent d968aa9a2f
commit 42c9ab9449
18 changed files with 1587 additions and 754 deletions

View File

@ -6,6 +6,7 @@
<Application.Resources>
<local:MeterToPixelConverter x:Key="MeterToPixelConverter" />
<local:LevelToHeightMultiConverter x:Key="LevelToHeightMultiConverter" />
<local:PercentageToHeightConverter x:Key="PercentageToHeightConverter" />
<local:WidthPercentageConverter x:Key="WidthPercentageConverter" />
<local:DistanceToMarginConverter x:Key="DistanceToMarginConverter" />
<local:MarginConverter x:Key="MarginConverter" />
@ -17,6 +18,7 @@
<local:ColorToBrushConverter x:Key="ColorToBrushConverter" />
<local:StringToBrushConverter x:Key="StringToBrushConverter" />
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<local:BoolToColorConverter x:Key="BoolToColorConverter" />
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosBuscarCoincidencias"
TargetType="{x:Type osExtraccion:osBuscarCoincidencias}" />
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosVMMotor" TargetType="{x:Type os:osVMmotorSim}" />

View File

@ -118,3 +118,158 @@ if (velocity.Linear.Z > maxUpwardVelocity) {
- **Solución integral**: Resuelve tanto el problema de elevación como el de contactos múltiples simultáneos
- **Código más simple**: Elimina la complejidad del sistema de tracking de densidad de contactos
# Sistema Hidráulico Integrado con HydraulicSimulationManager
## Arquitectura del Sistema Hidráulico
Se ha implementado un sistema de simulación hidráulica completo que funciona en paralelo con la simulación física BEPU. El sistema está basado en el `HydraulicSimulationManager` que actúa como puente entre los objetos gráficos derivados de `osBase` y el motor de simulación hidráulica.
### Componentes Principales
#### HydraulicSimulationManager
- **Función**: Gestiona el ciclo de vida y la comunicación entre objetos hidráulicos y el solver
- **Integración**: Se ejecuta en paralelo con BEPU en el timer loop principal de MainViewModel
- **Responsabilidades**:
- Registro automático de objetos que implementan `IHydraulicComponent`
- Construcción dinámica de la red hidráulica
- Aplicación de resultados a los objetos tras cada iteración del solver
#### Interfaces Hidráulicas
- **IHydraulicComponent**: Interfaz base para todos los objetos hidráulicos
- **IHydraulicPump**: Para bombas con curvas H-Q
- **IHydraulicPipe**: Para tuberías con pérdidas por fricción
- **IHydraulicTank**: Para tanques con cálculo de nivel dinámico
- **IHydraulicValve**: Para válvulas con coeficiente Kv
## Mejoras Implementadas en Objetos Hidráulicos
### 1. Sistema de Conexiones Mejorado en osHydPipe
**Problema Original**: El sistema de conexiones era limitado y no seguía el patrón establecido en otros objetos del sistema.
**Solución Implementada**:
```csharp
// Antes - Sistema limitado
[ObservableProperty] string id_InletComponent = "";
[ObservableProperty] string id_OutletComponent = "";
// Después - Patrón consistente
[ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
[ObservableProperty] string id_ComponenteA = "";
[ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
[ObservableProperty] string id_ComponenteB = "";
```
**Beneficios**:
- **Filtrado automático**: Solo muestra objetos que implementan `IHydraulicComponent` en el PropertyGrid
- **Consistencia**: Sigue el mismo patrón usado en `ucTransporteGuias` con `Motor`
- **Mantenimiento automático**: PropertyChangedHandlers mantienen sincronizados los nombres
- **Validación**: Solo crea elementos hidráulicos cuando ambos componentes están conectados
### 2. Lógica de Nodos Corregida
**Problema Original**: Las tuberías intentaban crear sus propios nodos, generando duplicación y conflictos.
**Solución Implementada**:
```csharp
// Las tuberías no crean nodos propios
public List<HydraulicNodeDefinition> GetHydraulicNodes()
{
// Los nodos son creados por los componentes conectados (tanques, bombas, etc.)
return new List<HydraulicNodeDefinition>();
}
// Solo conectan nodos existentes
public List<HydraulicElementDefinition> GetHydraulicElements()
{
if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
{
var pipeElement = new Pipe(Length, Diameter, Roughness);
// Conecta directamente usando nombres de componentes como nodos
return new List<HydraulicElementDefinition> {
new HydraulicElementDefinition($"{Nombre}_Pipe", Id_ComponenteA, Id_ComponenteB, pipeElement)
};
}
return new List<HydraulicElementDefinition>();
}
```
### 3. Integración Completa con ItemsSource
**En osHydDischargeTank**:
```csharp
[Category("🔗 Conexiones")]
[Name("Componente Entrada")]
[ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
[ObservableProperty] string id_InletComponent = "";
```
## Flujo de Simulación Hidráulica
### 1. Registro Automático
- Los objetos que implementan `IHydraulicComponent` se registran automáticamente en `HydraulicSimulationManager`
- El sistema detecta cuando hay cambios y marca la red para reconstrucción (`_networkNeedsRebuild = true`)
### 2. Construcción de Red
- **BuildNodesFromObjects()**: Crea nodos basándose en las definiciones de cada objeto
- **BuildBranchesFromObjects()**: Crea elementos (tuberías, bombas, válvulas) que conectan los nodos
- La red resultante es compatible con el solver `HydraulicNetwork.Solve()`
### 3. Resolución y Aplicación
- **UpdateObjectProperties()**: Actualiza propiedades antes del solver (apertura de válvulas, velocidad de bombas, etc.)
- **Network.Solve()**: Resuelve el sistema de ecuaciones usando el algoritmo iterativo
- **ApplyResultsToObjects()**: Aplica caudales y presiones calculados a cada objeto
## Patrón de Conexión Establecido
El sistema establecido para conexiones sigue este patrón consistente:
```csharp
// 1. Propiedad string con ItemsSource filtrado
[ItemsSource(typeof(osBaseItemsSource<IInterfaceEspecifica>))]
[ObservableProperty] string id_ComponenteConectado = "";
// 2. Referencia privada al objeto real
[JsonIgnore] private IInterfaceEspecifica ComponenteConectado = null;
// 3. PropertyChangedHandler para mantener sincronización
[JsonIgnore] private PropertyChangedEventHandler componentePropertyChangedHandler;
// 4. Método OnChanged que maneja la conexión
partial void OnId_ComponenteConectadoChanged(string value)
{
// Desconectar anterior si existe
if (ComponenteConectado != null && componentePropertyChangedHandler != null)
((INotifyPropertyChanged)ComponenteConectado).PropertyChanged -= componentePropertyChangedHandler;
// Buscar y conectar nuevo componente
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
ComponenteConectado = (IInterfaceEspecifica)_mainViewModel.ObjetosSimulables
.FirstOrDefault(s => s is IInterfaceEspecifica comp &&
((osBase)comp).Nombre == value);
// Establecer handler para sincronización de nombres
if (ComponenteConectado != null)
{
componentePropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osBase.Nombre))
Id_ComponenteConectado = ((osBase)sender).Nombre;
};
((INotifyPropertyChanged)ComponenteConectado).PropertyChanged += componentePropertyChangedHandler;
}
}
}
```
## Beneficios del Sistema Integrado
- **Doble simulación**: Simulación física (BEPU) y hidráulica funcionan en paralelo sin interferencia
- **Interfaces unificadas**: Todos los objetos hidráulicos implementan interfaces consistentes
- **Red dinámica**: La red hidráulica se reconstruye automáticamente cuando cambian las conexiones
- **Resultados bidireccionales**: Los resultados hidráulicos pueden influir en la simulación física y viceversa
- **Patrón consistente**: Todas las conexiones siguen el mismo patrón establecido
- **Mantenimiento automático**: Los nombres y referencias se mantienen sincronizados automáticamente

View File

@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using HydraulicSimulator.Models;
using CtrEditor.HydraulicSimulator;
using CtrEditor.ObjetosSim.HydraulicComponents;
namespace CtrEditor.HydraulicSimulator.Examples
{
/// <summary>
/// Ejemplo de uso de los componentes hidráulicos según la documentación
/// Muestra cómo crear una red simple: Bomba -> Tubería -> Tanque de Descarga
/// </summary>
public class SimpleHydraulicSystemExample
{
/// <summary>
/// Crea un sistema hidráulico simple con bomba, tubería y tanque
/// </summary>
public static void RunExample()
{
Console.WriteLine("🚀 Iniciando ejemplo de sistema hidráulico simple");
Console.WriteLine("Sistema: Bomba -> Tubería -> Tanque de Descarga");
Console.WriteLine();
// 1. Crear la red hidráulica
var network = new HydraulicNetwork();
// 2. Agregar nodos según la documentación
network.AddNode("SUMINISTRO", 0); // Tanque de suministro a presión atmosférica
network.AddNode("BOMBA_OUT"); // Salida de bomba (presión calculada)
network.AddNode("TANQUE_DESCARGA", 0); // Tanque de descarga libre
// 3. Crear elementos hidráulicos
var pump = new PumpHQ(
h0: 80, // Cabeza a caudal cero: 80 metros
q0: 0.01, // Caudal a cabeza cero: 0.01 m³/s (36 m³/h)
speedRel: 1.0, // Velocidad nominal
direction: 1 // Dirección normal
);
var pipe = new Pipe(
length: 50, // 50 metros de longitud
diameter: 0.08, // 80mm de diámetro
roughness: 0.0015 // Rugosidad del acero comercial
);
// 4. Conectar elementos en ramas según la documentación
network.AddBranch("SUMINISTRO", "BOMBA_OUT",
new List<Element> { pump }, "Bomba_Principal");
network.AddBranch("BOMBA_OUT", "TANQUE_DESCARGA",
new List<Element> { pipe }, "Tuberia_Descarga");
// 5. Resolver la red
Console.WriteLine("⚙️ Resolviendo red hidráulica...");
var result = network.Solve(
maxIterations: 200,
tolerance: 1e-4,
verbose: true
);
// 6. Mostrar resultados
if (result.Converged)
{
Console.WriteLine("✅ Simulación exitosa!");
Console.WriteLine($"Convergió en {result.Iterations} iteraciones");
Console.WriteLine();
ShowResults(result);
ShowComponentAnalysis(result, pump, pipe);
}
else
{
Console.WriteLine($"❌ No convergió después de {result.Iterations} iteraciones");
Console.WriteLine($"Error final: {result.Residual:E6}");
}
}
private static void ShowResults(SolutionResult result)
{
Console.WriteLine("📊 RESULTADOS DE LA SIMULACIÓN:");
Console.WriteLine("═══════════════════════════════");
// Flujos
Console.WriteLine("🌊 Flujos:");
foreach (var flow in result.Flows)
{
double flowM3h = flow.Value * 3600; // Convertir a m³/h
double flowLmin = flow.Value * 60000; // Convertir a L/min
Console.WriteLine($" {flow.Key}:");
Console.WriteLine($" {flow.Value:F6} m³/s = {flowM3h:F2} m³/h = {flowLmin:F1} L/min");
}
Console.WriteLine();
// Presiones
Console.WriteLine("📈 Presiones:");
foreach (var pressure in result.Pressures)
{
double pressureBar = pressure.Value / 100000.0; // Convertir a bar
double pressureKPa = pressure.Value / 1000.0; // Convertir a kPa
Console.WriteLine($" {pressure.Key}:");
Console.WriteLine($" {pressure.Value:F0} Pa = {pressureKPa:F1} kPa = {pressureBar:F3} bar");
}
}
private static void ShowComponentAnalysis(SolutionResult result, PumpHQ pump, Pipe pipe)
{
Console.WriteLine();
Console.WriteLine("🔧 ANÁLISIS DE COMPONENTES:");
Console.WriteLine("═══════════════════════════");
// Análisis de la bomba
if (result.Flows.TryGetValue("Bomba_Principal", out double pumpFlow))
{
double pumpFlowM3h = pumpFlow * 3600;
double pumpHead = pump.Dp(pumpFlow, Fluid.Water20C) / (1000 * 9.81); // Convertir Pa a metros
double pumpPower = pumpFlow * pump.Dp(pumpFlow, Fluid.Water20C) / 1000; // Potencia hidráulica en kW
Console.WriteLine("💨 Bomba:");
Console.WriteLine($" Caudal: {pumpFlowM3h:F1} m³/h");
Console.WriteLine($" Cabeza: {pumpHead:F1} m");
Console.WriteLine($" Potencia hidráulica: {pumpPower:F2} kW");
}
// Análisis de la tubería
if (result.Flows.TryGetValue("Tuberia_Descarga", out double pipeFlow))
{
double pipePressureDrop = pipe.Dp(pipeFlow, Fluid.Water20C);
double pipeHeadLoss = pipePressureDrop / (1000 * 9.81); // Convertir Pa a metros
double velocity = Math.Abs(pipeFlow) / (Math.PI * (pipe.D * pipe.D) / 4.0);
Console.WriteLine();
Console.WriteLine("🚰 Tubería:");
Console.WriteLine($" Caudal: {pipeFlow * 3600:F1} m³/h");
Console.WriteLine($" Velocidad: {velocity:F2} m/s");
Console.WriteLine($" Pérdida de carga: {pipeHeadLoss:F2} m");
Console.WriteLine($" Pérdida de presión: {pipePressureDrop / 1000:F1} kPa");
}
}
/// <summary>
/// Ejemplo de cómo los objetos gráficos osHyd* se integrarían en la simulación
/// </summary>
public static void ShowGraphicObjectsIntegration()
{
Console.WriteLine();
Console.WriteLine("🎨 INTEGRACIÓN CON OBJETOS GRÁFICOS:");
Console.WriteLine("═══════════════════════════════════");
Console.WriteLine();
Console.WriteLine("Los objetos creados (osHydPump, osHydPipe, osHydDischargeTank)");
Console.WriteLine("se integran automáticamente en el sistema de la siguiente manera:");
Console.WriteLine();
Console.WriteLine("1. 📋 Registro automático:");
Console.WriteLine(" - Se registran por reflexión usando NombreCategoria() = \"Hidráulicos\"");
Console.WriteLine(" - Aparecen en la categoría \"Hidráulicos\" del editor");
Console.WriteLine();
Console.WriteLine("2. 🔗 Sistema de conexiones:");
Console.WriteLine(" - Usan ItemsSource<IHydraulicComponent> para filtrar componentes");
Console.WriteLine(" - Similar al sistema Motor/Transporte existente");
Console.WriteLine(" - Conexiones: Id_InletComponent, Id_OutletComponent");
Console.WriteLine();
Console.WriteLine("3. 🚫 No participan en Bepu:");
Console.WriteLine(" - Override Start() sin crear geometrías Bepu");
Console.WriteLine(" - Solo participan en el simulador hidráulico");
Console.WriteLine();
Console.WriteLine("4. 📊 Integración con HydraulicSimulationManager:");
Console.WriteLine(" - Implementan IHydraulicComponent para GetHydraulicNodes()");
Console.WriteLine(" - Implementan GetHydraulicElements() para el solver");
Console.WriteLine(" - Reciben resultados via ApplyHydraulicResults()");
Console.WriteLine();
Console.WriteLine("5. 🎯 Ejemplo de configuración:");
Console.WriteLine(" osHydPump (Nombre=\"Bomba_01\", H0=80m, Q0=36m³/h)");
Console.WriteLine(" ↓ (Id_OutletComponent=\"Tuberia_01\")");
Console.WriteLine(" osHydPipe (Nombre=\"Tuberia_01\", L=50m, D=80mm)");
Console.WriteLine(" ↓ (Id_OutletComponent=\"Tanque_01\")");
Console.WriteLine(" osHydDischargeTank (Nombre=\"Tanque_01\", Area=1m²)");
}
}
}

View File

@ -0,0 +1,46 @@
using System;
using CtrEditor.HydraulicSimulator.Examples;
namespace CtrEditor.HydraulicSimulator.Tests
{
/// <summary>
/// Clase para probar rápidamente los componentes hidráulicos
/// Ejecutar desde el método Main o desde tests unitarios
/// </summary>
public static class QuickHydraulicTest
{
/// <summary>
/// Ejecuta una prueba rápida del sistema hidráulico
/// </summary>
public static void RunQuickTest()
{
try
{
Console.WriteLine("🧪 PRUEBA RÁPIDA DEL SISTEMA HIDRÁULICO");
Console.WriteLine("═══════════════════════════════════════");
Console.WriteLine();
// Ejecutar ejemplo simple
SimpleHydraulicSystemExample.RunExample();
// Mostrar información de integración
SimpleHydraulicSystemExample.ShowGraphicObjectsIntegration();
Console.WriteLine();
Console.WriteLine("✅ Prueba completada exitosamente!");
Console.WriteLine();
Console.WriteLine("📝 PRÓXIMOS PASOS:");
Console.WriteLine("1. Agregar los componentes al editor visual");
Console.WriteLine("2. Configurar conexiones entre componentes");
Console.WriteLine("3. Integrar con HydraulicSimulationManager");
Console.WriteLine("4. Probar simulación en tiempo real");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Error en la prueba: {ex.Message}");
Console.WriteLine($"Stack trace: {ex.StackTrace}");
}
}
}
}

View File

@ -134,7 +134,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="3*" />
<RowDefinition Height="5" />
<RowDefinition Height="2*" />
<RowDefinition Height="0" />
</Grid.RowDefinitions>
<ToolBarTray Grid.Row="0">

View File

@ -0,0 +1,403 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Media;
using CtrEditor.HydraulicSimulator;
using CtrEditor.ObjetosSim;
using CtrEditor.FuncionesBase;
using HydraulicSimulator.Models;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CommunityToolkit.Mvvm.ComponentModel;
using LibS7Adv;
namespace CtrEditor.ObjetosSim.HydraulicComponents
{
/// <summary>
/// Tanque de descarga hidráulico con cálculo de nivel dinámico
/// </summary>
public partial class osHydDischargeTank : osBase, IHydraulicTank, IosBase
{
#region Properties
private double _area = 1.0; // m²
private double _currentVolume = 0.5; // m³
private double _maxVolume = 2.0; // m³
private double _minVolume = 0.0; // m³
private double _currentHeight = 0.0;
private double _currentPressure = 0.0;
// Propiedades visuales
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Ancho visual del tanque en metros")]
[property: Name("Ancho")]
private float ancho = 0.5f;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Alto visual del tanque en metros")]
[property: Name("Alto")]
private float alto = 0.8f;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Color visual del tanque")]
[property: Name("Color")]
private Brush colorButton_oculto = Brushes.LightBlue;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Ángulo de rotación del tanque en grados")]
[property: Name("Ángulo")]
private float angulo = 0f;
[Category("🔧 Tanque Hidráulico")]
[Description("Área de la base del tanque en m² (alias para compatibilidad)")]
[Name("Área Base (m²)")]
public double Area
{
get => _area;
set => SetProperty(ref _area, Math.Max(0.1, value));
}
[Category("🔧 Tanque Hidráulico")]
[Description("Presión del tanque en Pa")]
[Name("Presión Tanque (Pa)")]
public double TankPressure
{
get => _currentPressure;
set => SetProperty(ref _currentPressure, value);
}
[Category("🔧 Tanque Hidráulico")]
[Description("Nivel actual del tanque en metros")]
[Name("Nivel (m)")]
public double Level
{
get => _currentHeight;
set
{
SetProperty(ref _currentHeight, value);
CurrentVolume = value * Area;
}
}
[Category("🔧 Tanque Hidráulico")]
[Description("Área de la sección transversal del tanque en m²")]
[Name("Área Sección (m²)")]
public double CrossSectionalArea
{
get => _area;
set => SetProperty(ref _area, Math.Max(0.1, value));
}
[Category("🔧 Tanque Hidráulico")]
[Description("Indica si el tanque tiene presión fija")]
[Name("Presión Fija")]
[ReadOnly(true)]
public bool IsFixedPressure => false; // Los tanques de descarga tienen presión calculada
[Category("🔧 Tanque Hidráulico")]
[Description("Volumen actual del tanque en m³")]
[Name("Volumen Actual (m³)")]
public double CurrentVolume
{
get => _currentVolume;
set => SetProperty(ref _currentVolume, Math.Max(MinVolume, Math.Min(MaxVolume, value)));
}
[Category("🔧 Tanque Hidráulico")]
[Description("Volumen máximo del tanque en m³")]
[Name("Volumen Máximo (m³)")]
public double MaxVolume
{
get => _maxVolume;
set => SetProperty(ref _maxVolume, Math.Max(CurrentVolume, value));
}
[Category("🔧 Tanque Hidráulico")]
[Description("Volumen mínimo del tanque en m³")]
[Name("Volumen Mínimo (m³)")]
public double MinVolume
{
get => _minVolume;
set => SetProperty(ref _minVolume, Math.Max(0.0, Math.Min(CurrentVolume, value)));
}
[ObservableProperty]
[property: Category("🔗 Conexiones")]
[property: Description("Componente conectado en la entrada")]
[property: Name("Componente Entrada")]
[property: ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
private string id_InletComponent = "";
[Category("📊 Estado")]
[Description("Altura actual del líquido en metros")]
[Name("Altura Actual (m)")]
[ReadOnly(true)]
public double CurrentHeight
{
get => _currentHeight;
set => SetProperty(ref _currentHeight, value);
}
[Category("📊 Estado")]
[Description("Presión hidrostática en el fondo del tanque en Pa")]
[Name("Presión Fondo (Pa)")]
[ReadOnly(true)]
public double CurrentPressure
{
get => _currentPressure;
set => SetProperty(ref _currentPressure, value);
}
[Category("📊 Estado")]
[Description("Porcentaje de llenado del tanque")]
[Name("Nivel (%)")]
[ReadOnly(true)]
public double FillPercentage => MaxVolume > 0 ? (CurrentVolume / MaxVolume) * 100.0 : 0.0;
#endregion
#region Component References
[JsonIgnore]
private IHydraulicComponent InletComponent = null;
[JsonIgnore]
private PropertyChangedEventHandler inletPropertyChangedHandler;
[JsonIgnore]
public Action<float, float> ActualizarTamaño { get; set; }
#endregion
#region IHydraulicComponent Implementation
public bool HasHydraulicComponents => true;
public List<HydraulicNodeDefinition> GetHydraulicNodes()
{
var nodes = new List<HydraulicNodeDefinition>();
// El tanque de descarga es un nodo con presión libre (calculada)
nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, "Tanque de descarga"));
return nodes;
}
public List<HydraulicElementDefinition> GetHydraulicElements()
{
// Los tanques no son elementos, son nodos
return new List<HydraulicElementDefinition>();
}
public void UpdateHydraulicProperties()
{
// Actualizar presión basada en el nivel actual
UpdateHeightAndPressure();
}
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
{
// Actualizar presión del tanque
if (pressures.TryGetValue(Nombre, out double pressure))
{
TankPressure = pressure;
// Calcular nivel basado en la presión hidrostática
Level = pressure / (1000.0 * 9.81); // Asumiendo densidad del agua
}
// Calcular flujo neto hacia el tanque
double netFlow = 0.0;
foreach (var flow in flows)
{
if (flow.Key.EndsWith($"->{Nombre}"))
netFlow += flow.Value; // Flujo entrante
else if (flow.Key.StartsWith($"{Nombre}->"))
netFlow -= flow.Value; // Flujo saliente
}
// Aquí podrías actualizar el volumen en función del tiempo
// Esto se haría en el update loop principal de la simulación
}
#endregion
#region IHydraulicPressureReceiver Implementation
public void SetPressure(double pressure)
{
TankPressure = pressure;
// Actualizar nivel basado en presión hidrostática
Level = pressure / (1000.0 * 9.81);
}
public double GetPressure()
{
return TankPressure;
}
#endregion
#region Connection Management
partial void OnId_InletComponentChanged(string value)
{
if (InletComponent != null && inletPropertyChangedHandler != null)
((INotifyPropertyChanged)InletComponent).PropertyChanged -= inletPropertyChangedHandler;
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
InletComponent = (IHydraulicComponent)_mainViewModel.ObjetosSimulables
.FirstOrDefault(s => s is IHydraulicComponent comp &&
((osBase)comp).Nombre == value);
if (InletComponent != null)
{
inletPropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osBase.Nombre))
{
Id_InletComponent = ((osBase)sender).Nombre;
}
};
((INotifyPropertyChanged)InletComponent).PropertyChanged += inletPropertyChangedHandler;
}
}
}
#endregion
#region Volume and Level Calculations
private void UpdateHeightAndPressure()
{
Level = Area > 0 ? CurrentVolume / Area : 0.0;
// Presión hidrostática en el fondo del tanque (densidad del agua = 1000 kg/m³)
TankPressure = 1000.0 * 9.81 * Level;
}
/// <summary>
/// Actualiza el volumen del tanque basado en flujos netos según la documentación
/// </summary>
/// <param name="flows">Diccionario de flujos de la simulación</param>
/// <param name="deltaTime">Intervalo de tiempo en segundos</param>
public void UpdateVolume(Dictionary<string, double> flows, double deltaTime)
{
double netFlow = 0.0;
// Sumar flujos entrantes, restar salientes
foreach (var flow in flows)
{
if (flow.Key.EndsWith($"->{Nombre}"))
netFlow += flow.Value; // Entrante
else if (flow.Key.StartsWith($"{Nombre}->"))
netFlow -= flow.Value; // Saliente
}
// Actualizar volumen
CurrentVolume += netFlow * deltaTime;
CurrentVolume = Math.Max(MinVolume, Math.Min(MaxVolume, CurrentVolume));
// Actualizar altura y presión
UpdateHeightAndPressure();
}
/// <summary>
/// Calcula la presión en el fondo del tanque según la documentación
/// </summary>
/// <param name="density">Densidad del fluido (kg/m³)</param>
/// <returns>Presión hidrostática en Pa</returns>
public double BottomPressure(double density = 1000.0)
{
return density * 9.81 * Level; // ρgh
}
#endregion
#region osBase Implementation
public static string NombreCategoria() => "Componentes Hidráulicos";
public static string NombreClase() => "Tanque de Descarga";
private string nombre = NombreClase();
[Category("Identificación")]
[Description("Nombre identificativo del objeto")]
[Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
public override void AltoChanged(float value)
{
ActualizarGeometrias();
}
public override void AnchoChanged(float value)
{
ActualizarGeometrias();
}
public void Start()
{
// Los tanques no participan en la simulación física Bepu
// Solo en el sistema hidráulico
UpdateHeightAndPressure();
}
public override void UpdateGeometryStart()
{
ActualizarGeometrias();
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
// Los tanques pueden tener sensores de nivel
// Implementar aquí si es necesario
}
private void ActualizarGeometrias()
{
try
{
// Actualizar geometría visual
if (this.ActualizarTamaño != null)
this.ActualizarTamaño(Ancho, Alto);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error actualizando geometría: {ex.Message}");
}
}
public void Inicializar(int valorInicial)
{
UpdateHeightAndPressure();
OnId_InletComponentChanged(Id_InletComponent);
}
public void Disposing()
{
if (InletComponent != null && inletPropertyChangedHandler != null)
((INotifyPropertyChanged)InletComponent).PropertyChanged -= inletPropertyChangedHandler;
}
#endregion
#region Constructor
public osHydDischargeTank()
{
UpdateHeightAndPressure();
}
#endregion
}
}

View File

@ -0,0 +1,382 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Media;
using CtrEditor.HydraulicSimulator;
using CtrEditor.ObjetosSim;
using CtrEditor.FuncionesBase;
using HydraulicSimulator.Models;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CommunityToolkit.Mvvm.ComponentModel;
using LibS7Adv;
namespace CtrEditor.ObjetosSim.HydraulicComponents
{
/// <summary>
/// Tubería hidráulica que conecta componentes del sistema hidráulico
/// </summary>
public partial class osHydPipe : osBase, IHydraulicPipe, IosBase
{
#region Properties
private double _length = 1.0; // metros
private double _diameter = 0.05; // metros (50mm)
private double _roughness = 4.5e-5; // metros - rugosidad del acero comercial
private double _currentFlow = 0.0;
private double _pressureDrop = 0.0;
// Propiedades visuales
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Ancho visual de la tubería en metros")]
[property: Name("Ancho")]
private float ancho = 1.0f;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Alto visual de la tubería en metros")]
[property: Name("Alto")]
private float alto = 0.05f;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Color visual de la tubería")]
[property: Name("Color")]
private Brush colorButton_oculto = Brushes.Gray;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Ángulo de rotación de la tubería en grados")]
[property: Name("Ángulo")]
private float angulo = 0f;
[Category("🔧 Tubería Hidráulica")]
[Description("Longitud de la tubería en metros")]
[Name("Longitud (m)")]
public double Length
{
get => _length;
set => SetProperty(ref _length, Math.Max(0.1, value));
}
[Category("🔧 Tubería Hidráulica")]
[Description("Diámetro interno de la tubería en metros")]
[Name("Diámetro (m)")]
public double Diameter
{
get => _diameter;
set => SetProperty(ref _diameter, Math.Max(0.001, value));
}
[Category("🔧 Tubería Hidráulica")]
[Description("Rugosidad del material en metros")]
[Name("Rugosidad (m)")]
public double Roughness
{
get => _roughness;
set => SetProperty(ref _roughness, Math.Max(1e-6, value));
}
[ObservableProperty]
[property: Category("🔗 Conexiones")]
[property: Description("Primer componente hidráulico conectado")]
[property: Name("Componente A")]
[property: ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
private string id_ComponenteA = "";
[ObservableProperty]
[property: Category("🔗 Conexiones")]
[property: Description("Segundo componente hidráulico conectado")]
[property: Name("Componente B")]
[property: ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
private string id_ComponenteB = "";
[Category("📊 Estado")]
[Description("Flujo actual a través de la tubería en m³/s")]
[Name("Flujo Actual (m³/s)")]
[ReadOnly(true)]
public double CurrentFlow
{
get => _currentFlow;
set => SetProperty(ref _currentFlow, value);
}
[Category("📊 Estado")]
[Description("Pérdida de presión en la tubería en Pa")]
[Name("Pérdida Presión (Pa)")]
[ReadOnly(true)]
public double PressureDrop
{
get => _pressureDrop;
set => SetProperty(ref _pressureDrop, value);
}
private string pipeId = "";
public string PipeId
{
get => pipeId;
set => SetProperty(ref pipeId, value);
}
#endregion
#region Component References
[JsonIgnore]
private IHydraulicComponent ComponenteA = null;
[JsonIgnore]
private IHydraulicComponent ComponenteB = null;
[JsonIgnore]
private PropertyChangedEventHandler componenteAPropertyChangedHandler;
[JsonIgnore]
private PropertyChangedEventHandler componenteBPropertyChangedHandler;
[JsonIgnore]
public Action<float, float> ActualizarTamaño { get; set; }
#endregion
#region IHydraulicPipe Implementation
// Ya implementadas las propiedades Length, Diameter, Roughness arriba
#endregion
#region IHydraulicComponent Implementation
public bool HasHydraulicComponents => true;
public List<HydraulicNodeDefinition> GetHydraulicNodes()
{
var nodes = new List<HydraulicNodeDefinition>();
// Las tuberías conectan componentes existentes, no crean nodos propios
// Los nodos son creados por los componentes conectados (tanques, bombas, etc.)
// La tubería solo actúa como elemento que conecta nodos existentes
return nodes;
}
public List<HydraulicElementDefinition> GetHydraulicElements()
{
var elements = new List<HydraulicElementDefinition>();
// Solo crear elemento si ambos componentes están conectados
if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
{
// Crear el elemento Pipe según la documentación
var pipeElement = new Pipe(Length, Diameter, Roughness);
elements.Add(new HydraulicElementDefinition(
name: $"{Nombre}_Pipe",
fromNode: Id_ComponenteA, // Usar el nombre del componente A como nodo origen
toNode: Id_ComponenteB, // Usar el nombre del componente B como nodo destino
element: pipeElement,
description: $"Tubería {Nombre} - L:{Length:F2}m, D:{Diameter*1000:F0}mm ({Id_ComponenteA} → {Id_ComponenteB})"
));
}
return elements;
}
public void UpdateHydraulicProperties()
{
// Actualizar propiedades antes de la simulación si es necesario
// Por ejemplo, cambios dinámicos en diámetro o rugosidad
}
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
{
// Solo procesar si ambos componentes están conectados
if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
{
string branchKey = $"{Id_ComponenteA}->{Id_ComponenteB}";
if (flows.TryGetValue(branchKey, out double flow))
{
CurrentFlow = flow;
}
// Calcular pérdida de presión entre nodos
if (pressures.TryGetValue(Id_ComponenteA, out double pressureA) &&
pressures.TryGetValue(Id_ComponenteB, out double pressureB))
{
PressureDrop = pressureA - pressureB;
}
}
}
#endregion
#region IHydraulicFlowReceiver Implementation
public void SetFlow(double flow)
{
CurrentFlow = flow;
}
public double GetFlow()
{
return CurrentFlow;
}
#endregion
#region IHydraulicPressureReceiver Implementation
public void SetPressure(double pressure)
{
// Para tuberías, la presión se maneja a través de los nodos
}
public double GetPressure()
{
return PressureDrop;
}
#endregion
#region Connection Management
partial void OnId_ComponenteAChanged(string value)
{
if (ComponenteA != null && componenteAPropertyChangedHandler != null)
((INotifyPropertyChanged)ComponenteA).PropertyChanged -= componenteAPropertyChangedHandler;
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
ComponenteA = (IHydraulicComponent)_mainViewModel.ObjetosSimulables
.FirstOrDefault(s => s is IHydraulicComponent comp &&
((osBase)comp).Nombre == value);
if (ComponenteA != null)
{
componenteAPropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osBase.Nombre))
{
Id_ComponenteA = ((osBase)sender).Nombre;
}
};
((INotifyPropertyChanged)ComponenteA).PropertyChanged += componenteAPropertyChangedHandler;
}
}
}
partial void OnId_ComponenteBChanged(string value)
{
if (ComponenteB != null && componenteBPropertyChangedHandler != null)
((INotifyPropertyChanged)ComponenteB).PropertyChanged -= componenteBPropertyChangedHandler;
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
ComponenteB = (IHydraulicComponent)_mainViewModel.ObjetosSimulables
.FirstOrDefault(s => s is IHydraulicComponent comp &&
((osBase)comp).Nombre == value);
if (ComponenteB != null)
{
componenteBPropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osBase.Nombre))
{
Id_ComponenteB = ((osBase)sender).Nombre;
}
};
((INotifyPropertyChanged)ComponenteB).PropertyChanged += componenteBPropertyChangedHandler;
}
}
}
#endregion
#region osBase Implementation
public static string NombreCategoria() => "Componentes Hidráulicos";
public static string NombreClase() => "Tubería Hidráulica";
private string nombre = NombreClase();
[Category("Identificación")]
[Description("Nombre identificativo del objeto")]
[Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
public override void AltoChanged(float value)
{
ActualizarGeometrias();
}
public override void AnchoChanged(float value)
{
ActualizarGeometrias();
}
public void Start()
{
// Las tuberías no participan en la simulación física Bepu
// Solo en el sistema hidráulico
}
public override void UpdateGeometryStart()
{
ActualizarGeometrias();
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
// Las tuberías no tienen control PLC directo
}
private void ActualizarGeometrias()
{
try
{
// Actualizar geometría visual
if (this.ActualizarTamaño != null)
this.ActualizarTamaño(Ancho, Alto);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error actualizando geometría: {ex.Message}");
}
}
public void Inicializar(int valorInicial)
{
PipeId = $"Pipe_{Nombre}_{valorInicial}";
OnId_ComponenteAChanged(Id_ComponenteA);
OnId_ComponenteBChanged(Id_ComponenteB);
}
public void Disposing()
{
if (ComponenteA != null && componenteAPropertyChangedHandler != null)
((INotifyPropertyChanged)ComponenteA).PropertyChanged -= componenteAPropertyChangedHandler;
if (ComponenteB != null && componenteBPropertyChangedHandler != null)
((INotifyPropertyChanged)ComponenteB).PropertyChanged -= componenteBPropertyChangedHandler;
}
#endregion
#region Constructor
public osHydPipe()
{
PipeId = Guid.NewGuid().ToString();
}
#endregion
}
}

View File

@ -47,6 +47,12 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
[property: Name("Color")]
private Brush colorButton_oculto = Brushes.Blue;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Ángulo de rotación de la bomba en grados")]
[property: Name("Ángulo")]
private float angulo = 0f;
[Category("🔧 Bomba Hidráulica")]
[DisplayName("Cabeza de bomba")]
[Description("Cabeza de la bomba a caudal cero (m)")]

View File

@ -1,364 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Media;
using CtrEditor.HydraulicSimulator;
using CtrEditor.ObjetosSim;
using CtrEditor.FuncionesBase;
using HydraulicSimulator.Models;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CommunityToolkit.Mvvm.ComponentModel;
namespace CtrEditor.ObjetosSim.HydraulicComponents
{
/// <summary>
/// Bomba hidráulica que implementa las interfaces del simulador hidráulico
/// </summary>
public partial class osHydPump : osBase, IHydraulicPump, IosBase
{
#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 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("Color visual de la bomba")]
[property: Name("Color")]
private Brush colorButton_oculto = Brushes.Blue;
[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)
{
// Los objetos hidráulicos no necesitan actualizar geometría física
}
public override void OnResize(float Delta_Width, float Delta_Height)
{
Ancho += Delta_Width;
Alto += Delta_Height;
}
public osHydPump()
{
Nombre = "Bomba Hidráulica";
}
public override void UpdateGeometryStart()
{
// Los objetos hidráulicos se registran automáticamente
// cuando inicia la simulación a través de las interfaces
}
public override void UpdateGeometryStep()
{
// Los objetos hidráulicos actualizan sus resultados
// a través de ApplyHydraulicResults()
}
public override void UpdateControl(int elapsedMilliseconds)
{
// Actualizar el color según el estado
if (IsRunning)
ColorButton_oculto = Brushes.Green;
else
ColorButton_oculto = Brushes.Gray;
}
public override void ucLoaded()
{
// Los objetos hidráulicos no necesitan geometría física
base.ucLoaded();
}
public override void ucUnLoaded()
{
// Los objetos hidráulicos no tienen geometría que limpiar
}
#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,101 @@
<UserControl x:Class="CtrEditor.ObjetosSim.HydraulicComponents.ucHydDischargeTank"
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"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim.HydraulicComponents"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osHydDischargeTank Ancho="0.5" Alto="0.8"/>
</UserControl.DataContext>
<Canvas RenderTransformOrigin="0,0">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform Angle="{Binding Angulo}" />
<TranslateTransform />
</TransformGroup>
</Canvas.RenderTransform>
<!-- Cuerpo del tanque -->
<Rectangle x:Name="rectTank"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{Binding ColorButton_oculto}"
Stroke="Black"
StrokeThickness="2"
RadiusX="5"
RadiusY="5"/>
<!-- Nivel del líquido con altura proporcional al nivel -->
<Rectangle x:Name="rectLevel"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=6}"
Canvas.Left="3"
Canvas.Bottom="3">
<Rectangle.Height>
<MultiBinding Converter="{StaticResource LevelToHeightMultiConverter}">
<Binding Path="FillPercentage" />
<Binding Path="Alto" Converter="{StaticResource MeterToPixelConverter}" />
</MultiBinding>
</Rectangle.Height>
<Rectangle.Fill>Blue</Rectangle.Fill>
<Rectangle.Opacity>0.6</Rectangle.Opacity>
</Rectangle>
<!-- Etiqueta con Viewbox para escalado -->
<Viewbox Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="20"
Stretch="Uniform"
Canvas.Top="2">
<Label Content="{Binding Nombre}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
FontSize="14"
Opacity="0.9"
Foreground="White"/>
</Viewbox>
<!-- Indicador de conexión entrada -->
<Ellipse x:Name="ConnectionIn"
Fill="Green"
Width="8"
Height="8"
Canvas.Left="-4"
Canvas.Bottom="10"/>
<!-- Indicador de nivel en porcentaje -->
<Border x:Name="LevelIndicator"
Background="White"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="2"
Canvas.Right="2"
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}">
<TextBlock x:Name="LevelText"
Text="{Binding FillPercentage, StringFormat='{}{0:F0}%'}"
FontSize="6"
Foreground="Black"
Margin="2"/>
</Border>
<!-- Indicador de volumen -->
<Border x:Name="VolumeIndicator"
Background="Yellow"
CornerRadius="2"
Visibility="Collapsed"
Canvas.Bottom="2"
Canvas.Left="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}">
<TextBlock x:Name="VolumeText"
Text="{Binding CurrentVolume, StringFormat='{}{0:F1}m³'}"
FontSize="5"
Foreground="Black"
Margin="1"/>
</Border>
</Canvas>
</UserControl>

View File

@ -0,0 +1,70 @@
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
using CtrEditor.ObjetosSim;
using CtrEditor.FuncionesBase;
namespace CtrEditor.ObjetosSim.HydraulicComponents
{
/// <summary>
/// Interaction logic for ucHydDischargeTank.xaml
/// </summary>
public partial class ucHydDischargeTank : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; } = 0;
private bool _isHighlighted = false;
public ucHydDischargeTank()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
DataContextChanged += OnDataContextChanged;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (DataContext is osHydDischargeTank tank)
{
Datos = tank;
}
}
#region IDataContainer Implementation
public void Highlight(bool state)
{
_isHighlighted = state;
if (state)
{
rectTank.Stroke = new SolidColorBrush(Colors.Yellow);
rectTank.StrokeThickness = 3;
}
else
{
rectTank.Stroke = new SolidColorBrush(Colors.DarkSlateGray);
rectTank.StrokeThickness = 2;
}
}
#endregion
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -0,0 +1,78 @@
<UserControl x:Class="CtrEditor.ObjetosSim.HydraulicComponents.ucHydPipe"
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"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim.HydraulicComponents"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osHydPipe Ancho="2.0" Alto="0.2"/>
</UserControl.DataContext>
<Canvas RenderTransformOrigin="0,0">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform Angle="{Binding Angulo}" />
<TranslateTransform />
</TransformGroup>
</Canvas.RenderTransform>
<!-- Cuerpo de la tubería -->
<Rectangle x:Name="rectPipe"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{Binding ColorButton_oculto}"
Stroke="Black"
StrokeThickness="2"
RadiusX="5"
RadiusY="5"/>
<!-- Etiqueta con Viewbox para escalado -->
<Viewbox Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Stretch="Uniform">
<Label Content="{Binding Nombre}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
FontSize="18"
Opacity="0.9"
Foreground="White"/>
</Viewbox>
<!-- Indicador de conexión entrada -->
<Ellipse x:Name="ConnectionIn"
Fill="Green"
Width="8"
Height="8"
Canvas.Left="-4"
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"/>
<!-- Indicador de conexión salida -->
<Ellipse x:Name="ConnectionOut"
Fill="Red"
Width="8"
Height="8"
Canvas.Right="-4"
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"/>
<!-- Indicador de flujo -->
<Border x:Name="FlowIndicator"
Background="Yellow"
CornerRadius="2"
Visibility="Collapsed"
Canvas.Top="-20"
Canvas.Left="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}">
<TextBlock x:Name="FlowText"
Text="0.00"
FontSize="6"
Foreground="Black"
Margin="2"/>
</Border>
</Canvas>
</UserControl>

View File

@ -0,0 +1,70 @@
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
using CtrEditor.ObjetosSim;
using CtrEditor.FuncionesBase;
namespace CtrEditor.ObjetosSim.HydraulicComponents
{
/// <summary>
/// Interaction logic for ucHydPipe.xaml
/// </summary>
public partial class ucHydPipe : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; } = 0;
private bool _isHighlighted = false;
public ucHydPipe()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
DataContextChanged += OnDataContextChanged;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (DataContext is osHydPipe pipe)
{
Datos = pipe;
}
}
#region IDataContainer Implementation
public void Highlight(bool state)
{
_isHighlighted = state;
if (state)
{
rectPipe.Stroke = new SolidColorBrush(Colors.Yellow);
rectPipe.StrokeThickness = 3;
}
else
{
rectPipe.Stroke = new SolidColorBrush(Colors.DarkSlateGray);
rectPipe.StrokeThickness = 2;
}
}
#endregion
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -3,82 +3,77 @@
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>
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim.HydraulicComponents"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osHydPump Ancho="0.15" Alto="0.10"/>
</UserControl.DataContext>
<Canvas RenderTransformOrigin="0,0">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform Angle="{Binding Angulo}" />
<TranslateTransform />
</TransformGroup>
</Canvas.RenderTransform>
<!-- Fondo de la bomba -->
<Ellipse x:Name="PumpBackground"
Fill="LightSteelBlue"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{Binding ColorButton_oculto}"
Stroke="DarkSlateGray"
StrokeThickness="2"/>
<!-- Indicador de dirección -->
<!-- Indicador de dirección con tamaño proporcional -->
<Polygon x:Name="DirectionArrow"
Fill="DarkBlue"
Points="30,25 50,35 30,45"
Points="0.5,0.3 0.8,0.5 0.5,0.7"
RenderTransformOrigin="0.5,0.5">
<Polygon.RenderTransform>
<RotateTransform x:Name="ArrowRotation" Angle="0"/>
<ScaleTransform ScaleX="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
ScaleY="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"/>
</Polygon.RenderTransform>
</Polygon>
<!-- Texto de identificación -->
<TextBlock x:Name="PumpLabel"
Text="P"
HorizontalAlignment="Center"
<!-- Texto de identificación con Viewbox para escalado -->
<Viewbox Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Stretch="Uniform">
<Label Content="{Binding Nombre}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
FontSize="16"
Foreground="White"
Margin="0,0,0,0"/>
FontSize="18"
Opacity="0.9"
Foreground="White"/>
</Viewbox>
<!-- Indicador de estado (LED) -->
<!-- Indicador de estado (LED) con posición proporcional -->
<Ellipse x:Name="StatusLED"
Width="8"
Height="8"
Fill="Red"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,5,5,0"/>
Fill="{Binding IsRunning, Converter={StaticResource BoolToColorConverter}}"
Canvas.Right="5"
Canvas.Top="5"/>
<!-- Conexiones de entrada y salida -->
<!-- Conexiones de entrada y salida con posiciones proporcionales -->
<Rectangle x:Name="InletConnection"
Width="10"
Height="4"
Fill="DarkSlateGray"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="-5,0,0,0"/>
Fill="Green"
Canvas.Left="-5"
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"/>
<Rectangle x:Name="OutletConnection"
Width="10"
Height="4"
Fill="DarkSlateGray"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0,0,-5,0"/>
Fill="Red"
Canvas.Right="-5"
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"/>
<!-- 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>
</Canvas>
</UserControl>

View File

@ -140,12 +140,11 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
{
if (pump.PumpDirection == -1)
{
ArrowRotation.Angle = 180; // Invertir flecha
// Cambiar el color de la flecha para indicar dirección inversa
DirectionArrow.Fill = new SolidColorBrush(Colors.Orange);
}
else
{
ArrowRotation.Angle = 0;
DirectionArrow.Fill = new SolidColorBrush(Colors.DarkBlue);
}
}
@ -155,18 +154,8 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
/// </summary>
private void UpdateStatusInfo(osHydPump 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;
}
// La información de estado se mostrará a través de la etiqueta principal
// El StatusLED ya está configurado para mostrar el estado IsRunning
}
#region IDataContainer Implementation
@ -192,24 +181,13 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
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;
}
// No hay elementos adicionales para mostrar en la implementación actual
}
protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e)
{
base.OnMouseLeave(e);
if (!_isHighlighted && Datos is osHydPump 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;
}
}
// No hay elementos adicionales para ocultar en la implementación actual
}
public ZIndexEnum ZIndex_Base()

View File

@ -1,84 +0,0 @@
<UserControl x:Class="CtrEditor.ObjetosSim.HydraulicComponents.ucHydPump"
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

@ -1,225 +0,0 @@
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 ucHydPump : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; } = 0;
private Storyboard? _rotationAnimation;
private bool _isHighlighted = false;
public ucHydPump()
{
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 osHydPump pump)
{
Datos = pump;
pump.PropertyChanged += OnPumpPropertyChanged;
UpdateVisualState();
}
}
private void OnPumpPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(osHydPump.IsRunning) ||
e.PropertyName == nameof(osHydPump.SpeedRatio) ||
e.PropertyName == nameof(osHydPump.PumpDirection) ||
e.PropertyName == nameof(osHydPump.CurrentFlow) ||
e.PropertyName == nameof(osHydPump.CurrentPressure))
{
UpdateVisualState();
}
}
/// <summary>
/// Actualiza el estado visual de la bomba
/// </summary>
private void UpdateVisualState()
{
if (Datos is not osHydPump 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(osHydPump 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(osHydPump 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(osHydPump 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(osHydPump 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 osHydPump 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;
}
~ucHydPump()
{
_rotationAnimation?.Stop();
}
}
}

View File

@ -393,6 +393,47 @@ namespace CtrEditor
}
}
public class PercentageToHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is float percentage)
{
// Assume the tank height is 100 pixels by default, or use parameter if provided
double tankHeight = 100;
if (parameter is double paramHeight)
tankHeight = paramHeight;
else if (parameter is string paramStr && double.TryParse(paramStr, out double parsed))
tankHeight = parsed;
return tankHeight * (percentage / 100.0);
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class BoolToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool isTrue)
{
return isTrue ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
}
return new SolidColorBrush(Colors.Gray);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class BrushToColorNameConverter : IValueConverter
{