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>
|
<Application.Resources>
|
||||||
<local:MeterToPixelConverter x:Key="MeterToPixelConverter" />
|
<local:MeterToPixelConverter x:Key="MeterToPixelConverter" />
|
||||||
<local:LevelToHeightMultiConverter x:Key="LevelToHeightMultiConverter" />
|
<local:LevelToHeightMultiConverter x:Key="LevelToHeightMultiConverter" />
|
||||||
|
<local:PercentageToHeightConverter x:Key="PercentageToHeightConverter" />
|
||||||
<local:WidthPercentageConverter x:Key="WidthPercentageConverter" />
|
<local:WidthPercentageConverter x:Key="WidthPercentageConverter" />
|
||||||
<local:DistanceToMarginConverter x:Key="DistanceToMarginConverter" />
|
<local:DistanceToMarginConverter x:Key="DistanceToMarginConverter" />
|
||||||
<local:MarginConverter x:Key="MarginConverter" />
|
<local:MarginConverter x:Key="MarginConverter" />
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
<local:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
<local:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
||||||
<local:StringToBrushConverter x:Key="StringToBrushConverter" />
|
<local:StringToBrushConverter x:Key="StringToBrushConverter" />
|
||||||
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||||
|
<local:BoolToColorConverter x:Key="BoolToColorConverter" />
|
||||||
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosBuscarCoincidencias"
|
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosBuscarCoincidencias"
|
||||||
TargetType="{x:Type osExtraccion:osBuscarCoincidencias}" />
|
TargetType="{x:Type osExtraccion:osBuscarCoincidencias}" />
|
||||||
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosVMMotor" TargetType="{x:Type os:osVMmotorSim}" />
|
<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
|
- **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
|
- **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="Auto" />
|
||||||
<RowDefinition Height="3*" />
|
<RowDefinition Height="3*" />
|
||||||
<RowDefinition Height="5" />
|
<RowDefinition Height="5" />
|
||||||
<RowDefinition Height="2*" />
|
<RowDefinition Height="0" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<ToolBarTray Grid.Row="0">
|
<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
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,12 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
|
||||||
[property: Name("Color")]
|
[property: Name("Color")]
|
||||||
private Brush colorButton_oculto = Brushes.Blue;
|
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")]
|
[Category("🔧 Bomba Hidráulica")]
|
||||||
[DisplayName("Cabeza de bomba")]
|
[DisplayName("Cabeza de bomba")]
|
||||||
[Description("Cabeza de la bomba a caudal cero (m)")]
|
[Description("Cabeza de la bomba a caudal cero (m)")]
|
||||||
|
|
|
@ -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:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
mc:Ignorable="d"
|
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
d:DesignHeight="60" d:DesignWidth="80">
|
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim.HydraulicComponents"
|
||||||
<Grid>
|
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 -->
|
<!-- Fondo de la bomba -->
|
||||||
<Ellipse x:Name="PumpBackground"
|
<Ellipse x:Name="PumpBackground"
|
||||||
Fill="LightSteelBlue"
|
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Fill="{Binding ColorButton_oculto}"
|
||||||
Stroke="DarkSlateGray"
|
Stroke="DarkSlateGray"
|
||||||
StrokeThickness="2"/>
|
StrokeThickness="2"/>
|
||||||
|
<!-- Indicador de dirección con tamaño proporcional -->
|
||||||
<!-- Indicador de dirección -->
|
|
||||||
<Polygon x:Name="DirectionArrow"
|
<Polygon x:Name="DirectionArrow"
|
||||||
Fill="DarkBlue"
|
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">
|
RenderTransformOrigin="0.5,0.5">
|
||||||
<Polygon.RenderTransform>
|
<Polygon.RenderTransform>
|
||||||
<RotateTransform x:Name="ArrowRotation" Angle="0"/>
|
<ScaleTransform ScaleX="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
ScaleY="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"/>
|
||||||
</Polygon.RenderTransform>
|
</Polygon.RenderTransform>
|
||||||
</Polygon>
|
</Polygon>
|
||||||
|
|
||||||
<!-- Texto de identificación -->
|
<!-- Texto de identificación con Viewbox para escalado -->
|
||||||
<TextBlock x:Name="PumpLabel"
|
<Viewbox Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Text="P"
|
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
HorizontalAlignment="Center"
|
Stretch="Uniform">
|
||||||
|
<Label Content="{Binding Nombre}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
FontWeight="Bold"
|
FontWeight="Bold"
|
||||||
FontSize="16"
|
FontSize="18"
|
||||||
Foreground="White"
|
Opacity="0.9"
|
||||||
Margin="0,0,0,0"/>
|
Foreground="White"/>
|
||||||
|
</Viewbox>
|
||||||
|
|
||||||
<!-- Indicador de estado (LED) -->
|
<!-- Indicador de estado (LED) con posición proporcional -->
|
||||||
<Ellipse x:Name="StatusLED"
|
<Ellipse x:Name="StatusLED"
|
||||||
Width="8"
|
Width="8"
|
||||||
Height="8"
|
Height="8"
|
||||||
Fill="Red"
|
Fill="{Binding IsRunning, Converter={StaticResource BoolToColorConverter}}"
|
||||||
HorizontalAlignment="Right"
|
Canvas.Right="5"
|
||||||
VerticalAlignment="Top"
|
Canvas.Top="5"/>
|
||||||
Margin="0,5,5,0"/>
|
|
||||||
|
|
||||||
<!-- Conexiones de entrada y salida -->
|
<!-- Conexiones de entrada y salida con posiciones proporcionales -->
|
||||||
<Rectangle x:Name="InletConnection"
|
<Rectangle x:Name="InletConnection"
|
||||||
Width="10"
|
Width="10"
|
||||||
Height="4"
|
Height="4"
|
||||||
Fill="DarkSlateGray"
|
Fill="Green"
|
||||||
HorizontalAlignment="Left"
|
Canvas.Left="-5"
|
||||||
VerticalAlignment="Center"
|
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"/>
|
||||||
Margin="-5,0,0,0"/>
|
|
||||||
|
|
||||||
<Rectangle x:Name="OutletConnection"
|
<Rectangle x:Name="OutletConnection"
|
||||||
Width="10"
|
Width="10"
|
||||||
Height="4"
|
Height="4"
|
||||||
Fill="DarkSlateGray"
|
Fill="Red"
|
||||||
HorizontalAlignment="Right"
|
Canvas.Right="-5"
|
||||||
VerticalAlignment="Center"
|
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"/>
|
||||||
Margin="0,0,-5,0"/>
|
|
||||||
|
|
||||||
<!-- Información de estado (opcional, visible en modo debug) -->
|
</Canvas>
|
||||||
<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>
|
</UserControl>
|
||||||
|
|
|
@ -140,12 +140,11 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
|
||||||
{
|
{
|
||||||
if (pump.PumpDirection == -1)
|
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);
|
DirectionArrow.Fill = new SolidColorBrush(Colors.Orange);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ArrowRotation.Angle = 0;
|
|
||||||
DirectionArrow.Fill = new SolidColorBrush(Colors.DarkBlue);
|
DirectionArrow.Fill = new SolidColorBrush(Colors.DarkBlue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,18 +154,8 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateStatusInfo(osHydPump pump)
|
private void UpdateStatusInfo(osHydPump pump)
|
||||||
{
|
{
|
||||||
FlowText.Text = $"{pump.CurrentFlowLMin:F1} L/min";
|
// La información de estado se mostrará a través de la etiqueta principal
|
||||||
PressureText.Text = $"{pump.CurrentPressureBar:F2} bar";
|
// El StatusLED ya está configurado para mostrar el estado IsRunning
|
||||||
|
|
||||||
// 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
|
#region IDataContainer Implementation
|
||||||
|
@ -192,24 +181,13 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
|
||||||
protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e)
|
protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnMouseEnter(e);
|
base.OnMouseEnter(e);
|
||||||
if (!_isHighlighted)
|
// No hay elementos adicionales para mostrar en la implementación actual
|
||||||
{
|
|
||||||
// Mostrar información al pasar el mouse
|
|
||||||
StatusInfo.Visibility = Visibility.Visible;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e)
|
protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnMouseLeave(e);
|
base.OnMouseLeave(e);
|
||||||
if (!_isHighlighted && Datos is osHydPump pump)
|
// No hay elementos adicionales para ocultar en la implementación actual
|
||||||
{
|
|
||||||
// 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()
|
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
|
public class BrushToColorNameConverter : IValueConverter
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue