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:
parent
d968aa9a2f
commit
42c9ab9449
2
App.xaml
2
App.xaml
|
@ -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}" />
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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²)");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -46,6 +46,12 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
|
|||
[property: Description("Color visual de la bomba")]
|
||||
[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")]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
FontWeight="Bold"
|
||||
FontSize="16"
|
||||
Foreground="White"
|
||||
Margin="0,0,0,0"/>
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue