Refactor hydraulic components: Updated osHydPump and added osHydTank class with dynamic level and pressure management. Enhanced XAML for tank visualization and implemented converters for UI binding. Improved error handling in converters and added tests for hydraulic component detection.

This commit is contained in:
Miguel 2025-09-05 15:41:17 +02:00
parent 70bc74fa7d
commit 0147e010d3
13 changed files with 1436 additions and 79 deletions

View File

@ -1,7 +1,9 @@
<Application x:Class="CtrEditor.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" <Application x:Class="CtrEditor.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CtrEditor" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CtrEditor"
xmlns:osExtraccion="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos" xmlns:osExtraccion="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos"
xmlns:os="clr-namespace:CtrEditor.ObjetosSim" StartupUri="MainWindow.xaml"> xmlns:os="clr-namespace:CtrEditor.ObjetosSim"
xmlns:converters="clr-namespace:CtrEditor.Converters"
StartupUri="MainWindow.xaml">
<Application.Resources> <Application.Resources>
<local:MeterToPixelConverter x:Key="MeterToPixelConverter" /> <local:MeterToPixelConverter x:Key="MeterToPixelConverter" />
@ -24,6 +26,14 @@
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosVMMotor" TargetType="{x:Type os:osVMmotorSim}" /> <local:SubclassFilterConverter x:Key="SubclassFilterConverterosVMMotor" TargetType="{x:Type os:osVMmotorSim}" />
<local:UnsavedChangesConverter x:Key="UnsavedChangesConverter"/> <local:UnsavedChangesConverter x:Key="UnsavedChangesConverter"/>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> <local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<converters:TankLevelToHeightConverter x:Key="TankLevelToHeightConverter"/>
<!-- DropShadowEffect para efectos de sombra en texto -->
<DropShadowEffect x:Key="DropShadowEffect"
Color="Black"
BlurRadius="3"
ShadowDepth="1"
Opacity="0.5"/>
<!-- Estilo global para TreeViewItem para evitar errores de binding --> <!-- Estilo global para TreeViewItem para evitar errores de binding -->
<Style TargetType="TreeViewItem"> <Style TargetType="TreeViewItem">

View File

@ -193,11 +193,20 @@ namespace CtrEditor.Controls
var newFilter = new TypeFilterItem(type) var newFilter = new TypeFilterItem(type)
{ {
DisplayName = GetTypeDisplayName(type), DisplayName = GetTypeDisplayName(type),
IsSelected = true IsSelected = true // Nuevos tipos empiezan seleccionados por defecto
}; };
SubscribeToTypeFilter(newFilter); SubscribeToTypeFilter(newFilter);
TypeFilters.Add(newFilter); TypeFilters.Add(newFilter);
} }
else
{
// Asegurar que los tipos existentes de componentes hidráulicos estén seleccionados
var existingFilter = TypeFilters.FirstOrDefault(tf => tf.Type == type);
if (existingFilter != null && IsHydraulicComponentType(type) && !existingFilter.IsSelected)
{
existingFilter.IsSelected = true;
}
}
} }
} }
@ -286,6 +295,17 @@ namespace CtrEditor.Controls
methodInfo.Invoke(null, null)?.ToString() : methodInfo.Invoke(null, null)?.ToString() :
type.Name; type.Name;
} }
/// <summary>
/// Verifica si un tipo es un componente hidráulico
/// </summary>
private bool IsHydraulicComponentType(Type type)
{
// Verificar si el tipo implementa IHydraulicComponent o está en el namespace de componentes hidráulicos
return type.Namespace != null &&
(type.Namespace.Contains("HydraulicComponents") ||
type.GetInterfaces().Any(i => i.Name.Contains("Hydraulic")));
}
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,121 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Data;
namespace CtrEditor.Converters
{
/// <summary>
/// Converter para calcular la altura del nivel de líquido en un tanque
/// basado en el porcentaje de llenado y el tamaño del tanque
/// </summary>
public class TankLevelToHeightConverter : IMultiValueConverter
{
public TankLevelToHeightConverter()
{
Console.WriteLine("TankLevelToHeightConverter: Constructor called");
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Debug: Registrar valores de entrada
Console.WriteLine($"TankLevelToHeightConverter.Convert: values.Length={values?.Length ?? 0}");
if (values != null)
{
for (int i = 0; i < values.Length; i++)
{
Console.WriteLine($" values[{i}]: {values[i]} (Type: {values[i]?.GetType().Name ?? "null"})");
}
}
if (values.Length != 2)
{
Console.WriteLine("TankLevelToHeightConverter.Convert: Returning 0.0 - wrong values count");
return 0.0;
}
// Convertir flexiblemente los valores a double
if (!TryConvertToDouble(values[0], out double fillPercentage) ||
!TryConvertToDouble(values[1], out double tankSize))
{
Console.WriteLine("TankLevelToHeightConverter.Convert: Conversion failed - unable to convert values to double");
return 0.0;
}
Console.WriteLine($"TankLevelToHeightConverter.Convert: fillPercentage={fillPercentage}, tankSize={tankSize}");
try
{
// Calcular altura basada en el porcentaje de llenado
// tankSize está en metros, convertir a píxeles (aproximación: 100 píxeles por metro)
var tankHeightPixels = tankSize * 100.0;
// Altura del líquido = porcentaje de llenado * altura total del tanque
var liquidHeight = (fillPercentage / 100.0) * tankHeightPixels;
// Aplicar margen interno (restar 8 píxeles para los márgenes del XAML)
var availableHeight = tankHeightPixels - 8.0; // 4px arriba + 4px abajo
var adjustedHeight = (fillPercentage / 100.0) * availableHeight;
// Asegurar que la altura sea positiva y no exceda el contenedor
var result = Math.Max(0.0, Math.Min(adjustedHeight, availableHeight));
Console.WriteLine($"TankLevelToHeightConverter.Convert: Returning {result}");
return result;
}
catch (Exception ex)
{
// En caso de error, retornar 0
Console.WriteLine($"TankLevelToHeightConverter.Convert: Exception - {ex.Message}");
return 0.0;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException("TankLevelToHeightConverter no soporta conversión inversa");
}
/// <summary>
/// Convierte un valor a double de manera flexible
/// </summary>
private static bool TryConvertToDouble(object value, out double result)
{
result = 0.0;
if (value == null)
return false;
if (value is double d)
{
result = d;
return true;
}
if (value is float f)
{
result = f;
return true;
}
if (value is int i)
{
result = i;
return true;
}
if (value is decimal dec)
{
result = (double)dec;
return true;
}
// Intentar conversión de string
if (value is string str && double.TryParse(str, out result))
{
return true;
}
return false;
}
}
}

View File

@ -360,7 +360,7 @@ Aunque la simulación funcionaba correctamente y los valores se calculaban bien,
<!-- DataContext se establece desde el objeto padre, no aquí --> <!-- DataContext se establece desde el objeto padre, no aquí -->
``` ```
### Resultado Final ### Resultado Final del Sistema Hidráulico
Sistema hidráulico completamente funcional: Sistema hidráulico completamente funcional:
- ✅ Convergencia estable del solver con residuales < 1e-3 - ✅ Convergencia estable del solver con residuales < 1e-3
- ✅ Registro único de objetos (sin duplicados) - ✅ Registro único de objetos (sin duplicados)
@ -369,6 +369,102 @@ Sistema hidráulico completamente funcional:
- ✅ Bomba genera flujo y presión reales según configuración - ✅ Bomba genera flujo y presión reales según configuración
- ✅ **UserControl muestra valores actualizados en tiempo real** - ✅ **UserControl muestra valores actualizados en tiempo real**
## Nuevo Tanque Hidráulico Avanzado (osHydTank)
### Arquitectura Modular Implementada
Se siguió el enfoque arquitectónico de **"Tanques como Terminales + Pipes como Conectores"** para crear un sistema más realista y mantenible:
```
[Tanque Succión] → [Pipe] → [Bomba] → [Pipe] → [Válvula] → [Pipe] → [Tanque Descarga]
```
### Características del osHydTank
**Gestión Dinámica de Nivel:**
- Nivel calculado en base a flujos de entrada y salida
- Seguimiento de volumen actual vs. máximo/mínimo
- Indicadores visuales de estado del tanque
- Detección automática de desbordamiento y vaciado
**Presión Configurable:**
- Presión independiente del nivel (simulando PID externo)
- Opción de presión fija o variable
- Diferentes tipos de tanque: Succión, Intermedio, Almacenamiento, Proceso
**Interfaces Hidráulicas:**
- `IHydraulicComponent`: Integración con red hidráulica
- `IHydraulicFlowReceiver`: Recepción de flujos calculados
- `IHydraulicPressureReceiver`: Actualización de presiones
**UserControl Avanzado:**
- Visualización en tiempo real del nivel de líquido
- Indicadores de tipo de tanque y conexiones
- Balance de flujo con códigos de color
- Conversión automática de TankLevelToHeightConverter
## Solución de Problemas de Visualización osHydTank
### Problema
El componente `osHydTank` no se mostraba correctamente en el canvas a pesar de registrarse exitosamente en el sistema hidráulico. Los logs mostraban errores de binding en el MultiBinding del rectángulo de nivel del líquido.
### Análisis
1. **Error de Converter**: El XAML usaba `TankLevelToHeightConverter` que tenía incompatibilidades de tipos
2. **Falta de inicialización**: El UserControl `ucHydTank` no llamaba a `ucLoaded()`/`ucUnLoaded()` como otros componentes
3. **Binding incorrecto**: No se convertía `Tamano` a píxeles antes del MultiBinding
### Solución Implementada
1. **Cambio de Converter**: Reemplazamos `TankLevelToHeightConverter` por `LevelToHeightMultiConverter` (que funciona en otros componentes)
2. **Conversión a Píxeles**: Agregamos `MeterToPixelConverter` a `Tamano` antes del MultiBinding
3. **Ciclo de Vida**: Implementamos las llamadas correctas a `ucLoaded()`/`ucUnLoaded()` en `ucHydTank.xaml.cs`
### Cambios Técnicos
- **XAML**: `<Binding Path="Tamano" Converter="{StaticResource MeterToPixelConverter}"/>`
- **UserControl**: Agregado `Unloaded += OnUserControlUnloaded` y llamadas a `ucLoaded()`/`ucUnLoaded()`
- **Converter Corregido**: `LevelToHeightMultiConverter` ahora usa `System.Convert.ToDouble()` para flexibilidad de tipos
- **Patrón consistente** con `ucHydPump`, `ucHydDischargeTank` y `ucHydPipe`
### Detalle del Error del Converter
El `LevelToHeightMultiConverter` esperaba tipos específicos (`float` y `double`) pero recibía tipos invertidos:
- `FillPercentage`: `double` (esperaba `float`)
- `MeterToPixelConverter` output: `float` (esperaba `double`)
**Solución**: Implementar función `TryConvertToDouble()` robusta que maneja:
- Valores especiales de WPF (`DependencyProperty.UnsetValue`, `MS.Internal.NamedObject`)
- Múltiples tipos numéricos (`double`, `float`, `int`, `decimal`)
- Conversión segura de strings
- Verificación `IConvertible` antes de usar `System.Convert.ToDouble()`
### Validación de Arquitectura
El sistema ahora requiere que:
- Todo circuito hidráulico **inicie con un tanque**
- Todo circuito hidráulico **termine con un tanque**
- Los `pipes` sean los únicos conectores entre componentes
- Cada componente tenga **responsabilidad única**
### Beneficios del Nuevo Diseño
**Realismo Físico:**
- Refleja sistemas hidráulicos reales donde tanques son puntos de referencia
- Presión independiente del nivel (como en sistemas industriales)
- Gestión de volumen dinámica
**Escalabilidad:**
- Patrón consistente para nuevos componentes hidráulicos
- Fácil conexión de válvulas, filtros, intercambiadores de calor
- Sistema preparado para redes complejas
**Mantenibilidad:**
- Principio de responsabilidad única por componente
- Interfaces claras y bien definidas
- Debugging simplificado con logging detallado
**Flexibilidad:**
- Diferentes tipos de tanques para diferentes funciones
- Configuración de presión independiente
- Conectividad modular via pipes
La bomba ahora debe mostrar correctamente: La bomba ahora debe mostrar correctamente:
- **Presión actual** en bar (ej: "1.0 bar") - **Presión actual** en bar (ej: "1.0 bar")
- **Caudal actual** en L/min (ej: "165.8 L/min") - **Caudal actual** en L/min (ej: "165.8 L/min")

43
FilterDebugTest.cs Normal file
View File

@ -0,0 +1,43 @@
using System;
using System.Linq;
using CtrEditor.ObjetosSim.HydraulicComponents;
namespace CtrEditor.Test
{
class FilterDebugTest
{
static void TestHydraulicComponentDetection()
{
// Test hydraulic component detection logic like in osVisFilter
var tankType = typeof(osHydTank);
var pipeType = typeof(osHydPipe);
Console.WriteLine("=== Tank Type Analysis ===");
Console.WriteLine($"Tank Type: {tankType.FullName}");
Console.WriteLine($"Tank Namespace: {tankType.Namespace}");
Console.WriteLine($"Has HydraulicComponents in namespace: {tankType.Namespace?.Contains("HydraulicComponents")}");
Console.WriteLine($"Tank Interfaces: {string.Join(", ", tankType.GetInterfaces().Select(i => i.Name))}");
Console.WriteLine($"Has Hydraulic interface: {tankType.GetInterfaces().Any(i => i.Name.Contains("Hydraulic"))}");
Console.WriteLine("\n=== Pipe Type Analysis ===");
Console.WriteLine($"Pipe Type: {pipeType.FullName}");
Console.WriteLine($"Pipe Namespace: {pipeType.Namespace}");
Console.WriteLine($"Has HydraulicComponents in namespace: {pipeType.Namespace?.Contains("HydraulicComponents")}");
Console.WriteLine($"Pipe Interfaces: {string.Join(", ", pipeType.GetInterfaces().Select(i => i.Name))}");
Console.WriteLine($"Has Hydraulic interface: {pipeType.GetInterfaces().Any(i => i.Name.Contains("Hydraulic"))}");
// Test the exact logic from IsHydraulicComponentType
Console.WriteLine("\n=== IsHydraulicComponentType Logic Test ===");
bool tankIsHydraulic = tankType.Namespace != null &&
(tankType.Namespace.Contains("HydraulicComponents") ||
tankType.GetInterfaces().Any(i => i.Name.Contains("Hydraulic")));
bool pipeIsHydraulic = pipeType.Namespace != null &&
(pipeType.Namespace.Contains("HydraulicComponents") ||
pipeType.GetInterfaces().Any(i => i.Name.Contains("Hydraulic")));
Console.WriteLine($"Tank IsHydraulicComponentType: {tankIsHydraulic}");
Console.WriteLine($"Pipe IsHydraulicComponentType: {pipeIsHydraulic}");
}
}
}

View File

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

View File

@ -441,6 +441,7 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
public osHydPipe() public osHydPipe()
{ {
PipeId = Guid.NewGuid().ToString(); PipeId = Guid.NewGuid().ToString();
IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros
} }
#endregion #endregion

View File

@ -18,7 +18,7 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
/// </summary> /// </summary>
public partial class osHydPump : osBase, IHydraulicPump, IosBase public partial class osHydPump : osBase, IHydraulicPump, IosBase
{ {
public static string NombreCategoria() => "Hidraulico"; public static string NombreCategoria() => "Componentes Hidráulicos";
public static string NombreClase() public static string NombreClase()
{ {
@ -53,31 +53,13 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
private double _currentFlow = 0.0; private double _currentFlow = 0.0;
private double _currentPressure = 0.0; private double _currentPressure = 0.0;
// Propiedades visuales // Propiedades visuales específicas de la bomba
[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] [ObservableProperty]
[property: Category("🎨 Apariencia")] [property: Category("🎨 Apariencia")]
[property: Description("Color visual de la bomba")] [property: Description("Color visual de la bomba")]
[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)")]
@ -209,15 +191,19 @@ namespace CtrEditor.ObjetosSim.HydraulicComponents
set => SetProperty(ref nombre, value); 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 osHydPump() public osHydPump()
{ {
Nombre = "Bomba Hidráulica"; Nombre = "Bomba Hidráulica";
Tamano = 0.30f; Tamano = 0.30f;
// Inicializar dimensiones usando las propiedades de osBase
Ancho = 0.15f;
Alto = 0.10f;
Angulo = 0f;
// Asegurar que el movimiento esté habilitado
Lock_movement = false;
// Inicializar posición por defecto
Left = 0.0f;
Top = 0.0f;
ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png"); ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png");
} }

View File

@ -0,0 +1,790 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
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 hidráulico con gestión dinámica de nivel y presión configurable
/// </summary>
public partial class osHydTank : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
{
public static string NombreCategoria() => "Componentes Hidráulicos";
public static string NombreClase() => "Tanque Hidráulico";
// Private Fields
private double _tankPressure = 101325.0; // Pa (1 atm por defecto)
private double _currentLevel = 1.0; // m
private double _maxLevel = 2.0; // m
private double _minLevel = 0.1; // m
private double _crossSectionalArea = 1.0; // m²
private double _currentVolume = 1.0; // m³
private double _maxVolume = 2.0; // m³
private double _inletFlow = 0.0; // m³/s
private double _outletFlow = 0.0; // m³/s
private double _currentPressure = 101325.0; // Pa
private bool _isFixedPressure = true;
private HydraulicTankType _tankType = HydraulicTankType.Intermediate;
private string _connectedInletPipe = "";
private string _connectedOutletPipe = "";
private double _lastUpdateTime = 0.0;
private readonly object _levelLock = new object();
private PropertyChangedEventHandler? inletPropertyChangedHandler;
private PropertyChangedEventHandler? outletPropertyChangedHandler;
// Enums
public enum HydraulicTankType
{
[Description("Tanque de succión (inicio del sistema)")]
Suction,
[Description("Tanque intermedio (buffer)")]
Intermediate,
[Description("Tanque de almacenamiento")]
Storage,
[Description("Tanque de proceso")]
Process
}
// Properties
[ObservableProperty]
[property: JsonIgnore]
[property: Category("Apariencia")]
[property: Description("Imagen visual")]
[property: Name("Imagen")]
public ImageSource imageSource_oculta;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Tamaño visual del tanque")]
[property: Name("Tamaño")]
public float tamano;
// Propiedades visuales específicas del tanque
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Color visual del tanque")]
[property: Name("Color")]
private Brush colorButton_oculto = Brushes.LightBlue;
// Constructor
public osHydTank()
{
Nombre = "Tanque Hidráulico";
Tamano = 1.0f; // Usar un tamaño mayor para mayor visibilidad
// Inicializar dimensiones usando las propiedades de osBase
Ancho = 0.30f;
Alto = 0.40f;
Angulo = 0f;
// Asegurar que el movimiento esté habilitado
Lock_movement = false;
// Inicializar posición por defecto
ImageSource_oculta = ImageFromPath("/imagenes/tank.png");
_currentVolume = _currentLevel * _crossSectionalArea;
_maxVolume = _maxLevel * _crossSectionalArea;
IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros
UpdateTankPressure();
// Debug: Confirmar que el constructor se ejecuta
Debug.WriteLine($"osHydTank Constructor: Nombre='{Nombre}', Tamaño={Tamano}, ZIndex={zIndex_fromFrames}, IsVisFilter={IsVisFilter}, Lock_movement={Lock_movement}");
}
// 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 OnResize(float Delta_Width, float Delta_Height)
{
Tamano += Delta_Width + Delta_Height;
}
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 del tanque
var percentage = FillPercentage;
if (percentage < 20)
ColorButton_oculto = Brushes.Red;
else if (percentage < 50)
ColorButton_oculto = Brushes.Orange;
else if (percentage < 80)
ColorButton_oculto = Brushes.Yellow;
else
ColorButton_oculto = Brushes.LightBlue;
}
// Properties - Configuration
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Tipo de tanque")]
[Description("Función del tanque en el sistema hidráulico")]
public HydraulicTankType TankType
{
get => _tankType;
set
{
if (SetProperty(ref _tankType, value))
{
UpdateTankConfiguration();
InvalidateHydraulicNetwork();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Área sección transversal")]
[Description("Área de la sección transversal del tanque (m²)")]
public double CrossSectionalArea
{
get => _crossSectionalArea;
set
{
if (SetProperty(ref _crossSectionalArea, Math.Max(0.1, value)))
{
UpdateVolumeCalculations();
InvalidateHydraulicNetwork();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Nivel máximo")]
[Description("Nivel máximo permitido en el tanque (m)")]
public double MaxLevel
{
get => _maxLevel;
set
{
if (SetProperty(ref _maxLevel, Math.Max(0.2, value)))
{
if (_currentLevel > _maxLevel)
CurrentLevel = _maxLevel;
UpdateVolumeCalculations();
}
}
}
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Nivel mínimo")]
[Description("Nivel mínimo permitido en el tanque (m)")]
public double MinLevel
{
get => _minLevel;
set
{
if (SetProperty(ref _minLevel, Math.Max(0.0, Math.Min(value, _maxLevel - 0.1))))
{
if (_currentLevel < _minLevel)
CurrentLevel = _minLevel;
UpdateVolumeCalculations();
}
}
}
// Properties - Pressure
[Category("🔧 Sistema de Presión")]
[DisplayName("Presión fija")]
[Description("Si está habilitado, la presión se mantiene constante")]
public bool IsFixedPressure
{
get => _isFixedPressure;
set
{
if (SetProperty(ref _isFixedPressure, value))
{
UpdateTankPressure();
InvalidateHydraulicNetwork();
}
}
}
[Category("🔧 Sistema de Presión")]
[DisplayName("Presión del tanque")]
[Description("Presión mantenida en el tanque (Pa). En sistemas reales controlada por PID")]
public double TankPressure
{
get => _tankPressure;
set
{
if (SetProperty(ref _tankPressure, Math.Max(0, value)))
{
UpdateTankPressure();
InvalidateHydraulicNetwork();
}
}
}
[Category("🔧 Sistema de Presión")]
[DisplayName("Presión (bar)")]
[Description("Presión del tanque en bar")]
[JsonIgnore]
public double TankPressureBar
{
get => TankPressure / 100000.0;
set => TankPressure = value * 100000.0;
}
// Properties - Current State
[Category("📊 Estado Actual")]
[DisplayName("Nivel actual")]
[Description("Nivel actual de líquido en el tanque (m)")]
[JsonIgnore]
public double CurrentLevel
{
get => _currentLevel;
private set
{
lock (_levelLock)
{
var clampedLevel = Math.Max(_minLevel, Math.Min(_maxLevel, value));
if (SetProperty(ref _currentLevel, clampedLevel))
{
UpdateVolumeCalculations();
OnPropertyChanged(nameof(FillPercentage));
}
}
}
}
[Category("📊 Estado Actual")]
[DisplayName("Volumen actual")]
[Description("Volumen actual de líquido en el tanque (m³)")]
[JsonIgnore]
public double CurrentVolume
{
get => _currentVolume;
private set => SetProperty(ref _currentVolume, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Volumen máximo")]
[Description("Volumen máximo del tanque (m³)")]
[JsonIgnore]
public double MaxVolume
{
get => _maxVolume;
private set => SetProperty(ref _maxVolume, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Porcentaje llenado")]
[Description("Porcentaje de llenado del tanque")]
[JsonIgnore]
public double FillPercentage => (_currentLevel - _minLevel) / (_maxLevel - _minLevel) * 100.0;
[Category("📊 Estado Actual")]
[DisplayName("Flujo entrada")]
[Description("Flujo de entrada actual (m³/s)")]
[JsonIgnore]
public double InletFlow
{
get => _inletFlow;
private set => SetProperty(ref _inletFlow, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Flujo salida")]
[Description("Flujo de salida actual (m³/s)")]
[JsonIgnore]
public double OutletFlow
{
get => _outletFlow;
private set => SetProperty(ref _outletFlow, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Balance flujo")]
[Description("Balance de flujo (entrada - salida) (m³/s)")]
[JsonIgnore]
public double FlowBalance => InletFlow - OutletFlow;
[Category("📊 Estado Actual")]
[DisplayName("Presión actual")]
[Description("Presión actual en el tanque (Pa)")]
[JsonIgnore]
public double CurrentPressure
{
get => _currentPressure;
private set => SetProperty(ref _currentPressure, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Presión actual (bar)")]
[Description("Presión actual en bar")]
[JsonIgnore]
public double CurrentPressureBar => CurrentPressure / 100000.0;
// Properties - Connections
[Category("🔗 Conexiones")]
[DisplayName("Tubería entrada")]
[Description("Nombre de la tubería conectada a la entrada")]
public string ConnectedInletPipe
{
get => _connectedInletPipe;
set
{
if (SetProperty(ref _connectedInletPipe, value ?? ""))
{
ManageInletConnection();
InvalidateHydraulicNetwork();
}
}
}
[Category("🔗 Conexiones")]
[DisplayName("Tubería salida")]
[Description("Nombre de la tubería conectada a la salida")]
public string ConnectedOutletPipe
{
get => _connectedOutletPipe;
set
{
if (SetProperty(ref _connectedOutletPipe, value ?? ""))
{
ManageOutletConnection();
InvalidateHydraulicNetwork();
}
}
}
// IHydraulicComponent Implementation
[JsonIgnore]
public bool HasHydraulicComponents => true;
public List<HydraulicNodeDefinition> GetHydraulicNodes()
{
var nodes = new List<HydraulicNodeDefinition>();
// El tanque siempre es un nodo de presión fija si está configurado así
if (IsFixedPressure)
{
nodes.Add(new HydraulicNodeDefinition(Nombre, true, TankPressure, GetTankDescription()));
Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F0} Pa ({TankPressureBar:F2} bar)");
}
else
{
nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, GetTankDescription()));
Debug.WriteLine($"Tanque {Nombre}: Nodo de presión libre creado");
}
return nodes;
}
public List<HydraulicElementDefinition> GetHydraulicElements()
{
// Los tanques no generan elementos hidráulicos, solo nodos
return new List<HydraulicElementDefinition>();
}
public void UpdateHydraulicProperties()
{
// Actualizar propiedades antes de la simulación
UpdateTankPressure();
UpdateVolumeCalculations();
}
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
{
var deltaTime = GetDeltaTime();
// Obtener flujos conectados
UpdateFlowsFromConnectedPipes(flows);
// Actualizar presión
if (pressures.ContainsKey(Nombre))
{
CurrentPressure = pressures[Nombre];
}
// Actualizar nivel basado en balance de flujo
UpdateLevelFromFlowBalance(deltaTime);
if (VerboseLogging())
{
Debug.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevel:F2}m ({FillPercentage:F1}%), " +
$"Flujo_entrada={InletFlow:F6}m³/s, Flujo_salida={OutletFlow:F6}m³/s, " +
$"Balance={FlowBalance:F6}m³/s, Presión={CurrentPressure:F0}Pa");
}
}
// UI Properties
/// <summary>
/// Color del nivel de líquido
/// </summary>
[JsonIgnore]
public SolidColorBrush LevelColor
{
get
{
var percentage = FillPercentage;
if (percentage < 20) return new SolidColorBrush(Colors.Red);
if (percentage < 50) return new SolidColorBrush(Colors.Orange);
if (percentage < 80) return new SolidColorBrush(Colors.Yellow);
return new SolidColorBrush(Colors.LightBlue);
}
}
/// <summary>
/// Color del borde del nivel de líquido
/// </summary>
[JsonIgnore]
public SolidColorBrush LevelBorderColor
{
get
{
var percentage = FillPercentage;
if (percentage < 20) return new SolidColorBrush(Colors.DarkRed);
if (percentage < 50) return new SolidColorBrush(Colors.DarkOrange);
if (percentage < 80) return new SolidColorBrush(Colors.DarkGoldenrod);
return new SolidColorBrush(Colors.DarkBlue);
}
}
/// <summary>
/// Indica si hay conexión de entrada
/// </summary>
[JsonIgnore]
public bool HasInletConnection => !string.IsNullOrEmpty(ConnectedInletPipe);
/// <summary>
/// Indica si hay conexión de salida
/// </summary>
[JsonIgnore]
public bool HasOutletConnection => !string.IsNullOrEmpty(ConnectedOutletPipe);
/// <summary>
/// Indicador del tipo de tanque
/// </summary>
[JsonIgnore]
public string TankTypeIndicator
{
get
{
return TankType switch
{
HydraulicTankType.Suction => "S",
HydraulicTankType.Intermediate => "I",
HydraulicTankType.Storage => "A",
HydraulicTankType.Process => "P",
_ => "T"
};
}
}
/// <summary>
/// Balance de flujo en L/min
/// </summary>
[JsonIgnore]
public double FlowBalanceLMin => FlowBalance * 60000; // m³/s a L/min
/// <summary>
/// Color del balance de flujo
/// </summary>
[JsonIgnore]
public SolidColorBrush FlowBalanceColor
{
get
{
var balance = FlowBalance;
if (Math.Abs(balance) < 0.0001) return new SolidColorBrush(Colors.Gray);
return balance > 0 ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
}
}
/// <summary>
/// Posición Y de la línea de nivel medio (50%)
/// </summary>
[JsonIgnore]
public double MidLevelY
{
get
{
var tankHeight = Tamano * 100; // Convertir a píxeles aproximados
return tankHeight * 0.5; // 50% de altura
}
}
/// <summary>
/// Posición Y de la línea de nivel mínimo
/// </summary>
[JsonIgnore]
public double MinLevelY
{
get
{
var tankHeight = Tamano * 100; // Convertir a píxeles aproximados
var minPercentage = (MinLevel - MinLevel) / (MaxLevel - MinLevel) * 100;
return tankHeight - (tankHeight * (minPercentage + 10) / 100); // 10% del nivel mínimo
}
}
// Private Methods
private string GetTankDescription()
{
return TankType switch
{
HydraulicTankType.Suction => "Tanque de succión",
HydraulicTankType.Intermediate => "Tanque intermedio",
HydraulicTankType.Storage => "Tanque de almacenamiento",
HydraulicTankType.Process => "Tanque de proceso",
_ => "Tanque hidráulico"
};
}
private void UpdateTankConfiguration()
{
// Configuración automática según el tipo de tanque
switch (TankType)
{
case HydraulicTankType.Suction:
// Tanques de succión típicamente a presión atmosférica
if (TankPressure < 50000) // Si no se ha configurado manualmente
TankPressure = 101325.0; // 1 atm
IsFixedPressure = true;
break;
case HydraulicTankType.Intermediate:
case HydraulicTankType.Storage:
case HydraulicTankType.Process:
// Estos pueden tener presión variable o fija según el proceso
break;
}
}
private void UpdateVolumeCalculations()
{
CurrentVolume = CurrentLevel * CrossSectionalArea;
MaxVolume = MaxLevel * CrossSectionalArea;
}
private void UpdateTankPressure()
{
if (IsFixedPressure)
{
CurrentPressure = TankPressure;
}
}
private void UpdateFlowsFromConnectedPipes(Dictionary<string, double> flows)
{
InletFlow = 0.0;
OutletFlow = 0.0;
// Buscar flujos en las ramas conectadas
foreach (var flow in flows)
{
if (flow.Key.Contains(Nombre))
{
// Determinar si es flujo de entrada o salida según la dirección
if (flow.Key.EndsWith($" -> {Nombre}"))
{
InletFlow += Math.Max(0, flow.Value); // Solo flujos positivos hacia el tanque
}
else if (flow.Key.StartsWith($"{Nombre} -> "))
{
OutletFlow += Math.Max(0, flow.Value); // Solo flujos positivos desde el tanque
}
}
}
}
private void UpdateLevelFromFlowBalance(double deltaTime)
{
if (deltaTime <= 0) return;
// Balance de masa: cambio_volumen = (flujo_entrada - flujo_salida) * tiempo
var volumeChange = FlowBalance * deltaTime;
var levelChange = volumeChange / CrossSectionalArea;
// Actualizar nivel con límites
CurrentLevel = Math.Max(MinLevel, Math.Min(MaxLevel, CurrentLevel + levelChange));
}
private double GetDeltaTime()
{
var currentTime = Environment.TickCount64 / 1000.0; // segundos
var deltaTime = _lastUpdateTime > 0 ? currentTime - _lastUpdateTime : 0.0;
_lastUpdateTime = currentTime;
// Limitar delta time para evitar saltos grandes
return Math.Min(deltaTime, 0.1); // máximo 100ms
}
private bool VerboseLogging()
{
return _mainViewModel?.hydraulicSimulationManager?.VerboseOutput ?? false;
}
private void ManageInletConnection()
{
// Gestionar conexión de entrada
// Implementar lógica de conexión específica si es necesario
}
private void ManageOutletConnection()
{
// Gestionar conexión de salida
// Implementar lógica de conexión específica si es necesario
}
private void InvalidateHydraulicNetwork()
{
_mainViewModel?.hydraulicSimulationManager?.InvalidateNetwork();
}
// IHydraulicFlowReceiver Implementation
public void SetFlow(double flow)
{
// Este método puede ser llamado por tuberías conectadas
// La lógica de flujo se maneja en ApplyHydraulicResults
}
public double GetFlow()
{
return FlowBalance;
}
// IHydraulicPressureReceiver Implementation
public void SetPressure(double pressure)
{
if (!IsFixedPressure)
{
CurrentPressure = pressure;
}
}
public double GetPressure()
{
return CurrentPressure;
}
// ZIndex Implementation
public int zIndex_fromFrames { get; set; } = 2;
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
// IosBase Implementation
public void Start()
{
// Inicialización del tanque para la simulación
UpdateVolumeCalculations();
UpdateTankPressure();
}
public void Inicializar(int valorInicial)
{
// Inicialización con valor inicial
UpdateVolumeCalculations();
UpdateTankPressure();
}
public void Disposing()
{
// Desconectar event handlers si existen
if (inletPropertyChangedHandler != null)
{
// Aquí se desconectarían los handlers cuando se implementen las conexiones
// Similar a como lo hace osHydDischargeTank
}
if (outletPropertyChangedHandler != null)
{
// Desconectar outlet handler cuando se implemente
}
}
// osBase Overrides
public override void ucLoaded()
{
// Inicialización cuando se carga el UserControl
UpdateVolumeCalculations();
UpdateTankPressure();
// Debug: Confirmar que el UserControl se está cargando
Debug.WriteLine($"osHydTank.ucLoaded(): Tanque '{Nombre}' cargado - Tamaño: {Tamano}, ZIndex: {zIndex_fromFrames}");
}
public override void ucUnLoaded()
{
// Los tanques hidráulicos no tienen geometría que limpiar
}
}
}

View File

@ -0,0 +1,135 @@
<UserControl x:Class="CtrEditor.ObjetosSim.HydraulicComponents.ucHydTank"
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:local="clr-namespace:CtrEditor.ObjetosSim"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim.HydraulicComponents"
mc:Ignorable="d">
<!-- DataContext se establece desde el objeto padre, no aquí -->
<Canvas RenderTransformOrigin="0,0">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform Angle="{Binding Angulo}" />
<TranslateTransform />
</TransformGroup>
</Canvas.RenderTransform>
<!-- Contenedor principal del tanque -->
<Grid Width="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
Canvas.Left="0" Canvas.Top="0">
<!-- Fondo del tanque (contenedor vacío) -->
<Rectangle x:Name="rectTankContainer"
Fill="LightGray"
Stroke="DarkGray"
StrokeThickness="2"
RadiusX="5"
RadiusY="5"/>
<!-- Nivel del líquido -->
<Rectangle x:Name="rectLevel"
Fill="{Binding LevelColor}"
Stroke="{Binding LevelBorderColor}"
StrokeThickness="1"
RadiusX="3"
RadiusY="3"
VerticalAlignment="Bottom"
Margin="4,4,4,4">
<Rectangle.Height>
<MultiBinding Converter="{StaticResource LevelToHeightMultiConverter}">
<Binding Path="FillPercentage"/>
<Binding Path="Tamano" Converter="{StaticResource MeterToPixelConverter}"/>
</MultiBinding>
</Rectangle.Height>
</Rectangle>
<!-- Líneas de nivel (marcas visuales) -->
<Canvas>
<!-- Línea de nivel máximo -->
<Line X1="0" Y1="8" X2="{Binding ActualWidth, ElementName=rectTankContainer}" Y2="8"
Stroke="Red" StrokeThickness="1" StrokeDashArray="2,2" Opacity="0.7"/>
<!-- Línea de nivel medio -->
<Line X1="0" Y1="{Binding MidLevelY}" X2="{Binding ActualWidth, ElementName=rectTankContainer}" Y2="{Binding MidLevelY}"
Stroke="Orange" StrokeThickness="1" StrokeDashArray="2,2" Opacity="0.5"/>
<!-- Línea de nivel mínimo -->
<Line X1="0" Y1="{Binding MinLevelY}" X2="{Binding ActualWidth, ElementName=rectTankContainer}" Y2="{Binding MinLevelY}"
Stroke="Red" StrokeThickness="1" StrokeDashArray="2,2" Opacity="0.7"/>
</Canvas>
<!-- Indicadores de conexión -->
<!-- Entrada (parte superior) -->
<Ellipse Width="8" Height="8"
Fill="Green"
Stroke="DarkGreen"
StrokeThickness="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="2,2,0,0"
Visibility="{Binding HasInletConnection, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<!-- Salida (parte inferior) -->
<Ellipse Width="8" Height="8"
Fill="Blue"
Stroke="DarkBlue"
StrokeThickness="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0,0,2,2"
Visibility="{Binding HasOutletConnection, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<!-- Texto del porcentaje de llenado -->
<TextBlock Text="{Binding FillPercentage, StringFormat='{}{0:F0}%'}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White"
FontWeight="Bold"
FontSize="10"
Effect="{StaticResource DropShadowEffect}"/>
<!-- Indicador de tipo de tanque -->
<TextBlock Text="{Binding TankTypeIndicator}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Foreground="Navy"
FontWeight="Bold"
FontSize="8"
Margin="2,0,0,0"/>
</Grid>
<!-- Panel de información -->
<Grid Canvas.Top="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=1.05}"
Width="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
Canvas.Left="0">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="0,2,0,0">
<!-- Presión actual -->
<TextBlock Text="{Binding CurrentPressureBar, StringFormat='{}{0:F1} bar'}"
Foreground="White" Background="Blue"
Padding="2" Margin="1" FontSize="8"/>
<!-- Nivel actual -->
<TextBlock Text="{Binding CurrentLevel, StringFormat='{}{0:F2} m'}"
Foreground="White" Background="Green"
Padding="2" Margin="1" FontSize="8"/>
<!-- Balance de flujo -->
<TextBlock Text="{Binding FlowBalanceLMin, StringFormat='{}{0:F1} L/min'}"
Foreground="White"
Background="{Binding FlowBalanceColor}"
Padding="2" Margin="1" FontSize="8"/>
</StackPanel>
</Grid>
</Canvas>
</UserControl>

View File

@ -0,0 +1,126 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using Newtonsoft.Json;
using System.Diagnostics;
using CtrEditor.ObjetosSim;
using CtrEditor.FuncionesBase;
namespace CtrEditor.ObjetosSim.HydraulicComponents
{
/// <summary>
/// UserControl para el tanque hidráulico osHydTank
/// </summary>
public partial class ucHydTank : UserControl, IDataContainer
{
#region IDataContainer Implementation
[JsonIgnore]
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; } = 0;
private bool _isHighlighted = false;
#endregion
public ucHydTank()
{
InitializeComponent();
Loaded += OnUserControlLoaded;
Unloaded += OnUserControlUnloaded;
DataContextChanged += OnDataContextChanged;
// Debug: Confirmar que el UserControl se está creando
Debug.WriteLine("ucHydTank Constructor: UserControl del tanque hidráulico creado");
}
private void OnUserControlUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (DataContext is osHydTank tank)
{
Datos = tank;
}
}
private void OnUserControlLoaded(object sender, RoutedEventArgs e)
{
// Debug: Confirmar que el UserControl se está cargando
Debug.WriteLine($"ucHydTank.OnUserControlLoaded: DataContext = {DataContext?.GetType().Name ?? "null"}");
// Llamar a ucLoaded() usando la propiedad Datos
Datos?.ucLoaded();
// Asegurar que el DataContext esté configurado correctamente
if (Datos is osHydTank tank)
{
Debug.WriteLine($"ucHydTank.OnUserControlLoaded: Tanque '{tank.Nombre}' conectado - Tamaño: {tank.Tamano}");
// Suscribirse a cambios de propiedades para actualizar la UI
tank.PropertyChanged += Tank_PropertyChanged;
UpdateVisualProperties();
}
else
{
Debug.WriteLine("ucHydTank.OnUserControlLoaded: ERROR - Datos no es osHydTank o es null");
}
}
private void Tank_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// Actualizar propiedades visuales cuando cambien propiedades relevantes
if (e.PropertyName == nameof(osHydTank.FillPercentage) ||
e.PropertyName == nameof(osHydTank.CurrentLevel) ||
e.PropertyName == nameof(osHydTank.FlowBalance) ||
e.PropertyName == nameof(osHydTank.TankType) ||
e.PropertyName == nameof(osHydTank.ConnectedInletPipe) ||
e.PropertyName == nameof(osHydTank.ConnectedOutletPipe))
{
UpdateVisualProperties();
}
}
private void UpdateVisualProperties()
{
if (Datos is osHydTank tank)
{
// Forzar actualización del UI - las propiedades se actualizan automáticamente
// cuando cambian las propiedades base gracias al binding
this.InvalidateVisual();
}
}
#region IDataContainer Implementation Methods
public void Highlight(bool state)
{
_isHighlighted = state;
if (state)
{
// Resaltar el contenedor del tanque con borde amarillo y más grueso
rectTankContainer.Stroke = new SolidColorBrush(Colors.Yellow);
rectTankContainer.StrokeThickness = 4;
}
else
{
// Volver al estado normal
rectTankContainer.Stroke = new SolidColorBrush(Colors.DarkGray);
rectTankContainer.StrokeThickness = 2;
}
}
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
#endregion
}
}

View File

@ -380,11 +380,79 @@ namespace CtrEditor
{ {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{ {
if (values[0] is float level && values[1] is double containerHeight) if (values?.Length >= 2)
{ {
return containerHeight * (level / 100); // Verificar que los valores sean válidos antes de convertir
if (!TryConvertToDouble(values[0], out double level) ||
!TryConvertToDouble(values[1], out double containerHeight))
{
return 0.0; // Retornar 0 si no se pueden convertir los valores
} }
return 0;
var result = containerHeight * (level / 100.0);
return result;
}
return 0.0;
}
private static bool TryConvertToDouble(object value, out double result)
{
result = 0.0;
// Verificar valores nulos o especiales de WPF
if (value == null ||
value == DependencyProperty.UnsetValue ||
value.GetType().Name.Contains("NamedObject"))
{
return false;
}
// Intentar conversión directa para tipos numéricos
if (value is double d)
{
result = d;
return true;
}
if (value is float f)
{
result = f;
return true;
}
if (value is int i)
{
result = i;
return true;
}
if (value is decimal dec)
{
result = (double)dec;
return true;
}
// Intentar conversión de string
if (value is string str && double.TryParse(str, out result))
{
return true;
}
// Último intento: usar Convert.ToDouble solo si es IConvertible
if (value is IConvertible)
{
try
{
result = System.Convert.ToDouble(value, CultureInfo.InvariantCulture);
return true;
}
catch
{
return false;
}
}
return false;
} }
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)

View File

@ -17,16 +17,23 @@ namespace CtrEditor.Test
var pipe = new osHydPipe(); var pipe = new osHydPipe();
var tank = new osHydDischargeTank(); var tank = new osHydDischargeTank();
var pump = new osHydPump(); var pump = new osHydPump();
var hydTank = new osHydTank();
// Probar la corrección - debería ser true para todos // Probar la corrección - debería ser true para todos
Console.WriteLine($"osHydPipe implements IHydraulicComponent: {typeof(IHydraulicComponent).IsAssignableFrom(pipe.GetType())}"); Console.WriteLine($"osHydPipe implements IHydraulicComponent: {typeof(IHydraulicComponent).IsAssignableFrom(pipe.GetType())}");
Console.WriteLine($"osHydDischargeTank implements IHydraulicComponent: {typeof(IHydraulicComponent).IsAssignableFrom(tank.GetType())}"); Console.WriteLine($"osHydDischargeTank implements IHydraulicComponent: {typeof(IHydraulicComponent).IsAssignableFrom(tank.GetType())}");
Console.WriteLine($"osHydPump implements IHydraulicComponent: {typeof(IHydraulicComponent).IsAssignableFrom(pump.GetType())}"); Console.WriteLine($"osHydPump implements IHydraulicComponent: {typeof(IHydraulicComponent).IsAssignableFrom(pump.GetType())}");
Console.WriteLine($"osHydTank implements IHydraulicComponent: {typeof(IHydraulicComponent).IsAssignableFrom(hydTank.GetType())}");
// Verificar NombreCategoria y NombreClase
Console.WriteLine($"osHydTank.NombreCategoria(): {osHydTank.NombreCategoria()}");
Console.WriteLine($"osHydTank.NombreClase(): {osHydTank.NombreClase()}");
// La comparación anterior que fallaba // La comparación anterior que fallaba
Console.WriteLine($"osHydPipe.GetType() == typeof(IHydraulicComponent): {pipe.GetType() == typeof(IHydraulicComponent)}"); Console.WriteLine($"osHydPipe.GetType() == typeof(IHydraulicComponent): {pipe.GetType() == typeof(IHydraulicComponent)}");
Console.WriteLine($"osHydDischargeTank.GetType() == typeof(IHydraulicComponent): {tank.GetType() == typeof(IHydraulicComponent)}"); Console.WriteLine($"osHydDischargeTank.GetType() == typeof(IHydraulicComponent): {tank.GetType() == typeof(IHydraulicComponent)}");
Console.WriteLine($"osHydPump.GetType() == typeof(IHydraulicComponent): {pump.GetType() == typeof(IHydraulicComponent)}"); Console.WriteLine($"osHydPump.GetType() == typeof(IHydraulicComponent): {pump.GetType() == typeof(IHydraulicComponent)}");
Console.WriteLine($"osHydTank.GetType() == typeof(IHydraulicComponent): {hydTank.GetType() == typeof(IHydraulicComponent)}");
} }
} }
} }