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:
parent
70bc74fa7d
commit
0147e010d3
12
App.xaml
12
App.xaml
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
|
@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue