1392 lines
48 KiB
C#
1392 lines
48 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Windows.Media;
|
|
using CtrEditor.HydraulicSimulator;
|
|
using CtrEditor.HydraulicSimulator.TSNet.Components;
|
|
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
|
|
{
|
|
/// <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 = 1.013; // bar (1 atm por defecto)
|
|
private double _currentLevelM = 1.0; // m
|
|
private double _crossSectionalArea = 1.0; // m²
|
|
private double _currentVolumeL = 1000.0; // L
|
|
private double _maxVolumeL = 2000.0; // L
|
|
private double _netFlow = 0.0; // L/min (positivo = entrada, negativo = salida)
|
|
private double _currentPressure = 1.013; // bar
|
|
private bool _isFixedPressure = true;
|
|
private double _lastUpdateTime = 0.0;
|
|
|
|
// Propiedades de fluido
|
|
private FluidProperties _primaryFluid = new FluidProperties(FluidType.Water, 20.0);
|
|
private FluidProperties _secondaryFluid = new FluidProperties(FluidType.Syrup, 20.0, 65.0);
|
|
private double _primaryVolumeL = 800.0; // L
|
|
private double _secondaryVolumeL = 200.0; // L
|
|
private double _mixingVolumeL = 100.0; // L de transición para mezcla
|
|
private MixingState _mixingState = MixingState.PrimaryOnly;
|
|
private double _currentMixRatio = 0.0; // 0 = solo primario, 1 = solo secundario
|
|
|
|
// Tank configuration fields
|
|
private double _maxLevelM = 2.0; // m - máximo nivel del tanque
|
|
private double _minLevelM = 0.0; // m - mínimo nivel del tanque
|
|
private string _tankType = "Standard"; // Tipo de tanque
|
|
|
|
// TSNet Integration
|
|
[JsonIgnore]
|
|
public TSNetTankAdapter? TSNetAdapter { get; private set; }
|
|
|
|
[JsonIgnore]
|
|
private object _levelLock = new object();
|
|
|
|
|
|
|
|
|
|
// Properties
|
|
|
|
[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()
|
|
{
|
|
// Inicializar el lock primero para thread safety
|
|
EnsureLockInitialized();
|
|
|
|
try
|
|
{
|
|
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;
|
|
|
|
// No inicializar TSNet Adapter aquí - se creará cuando se registre con TSNetSimulationManager
|
|
// TSNetAdapter = new TSNetTankAdapter(this);
|
|
|
|
// No cargar imagen aquí - se carga en ucLoaded()
|
|
// para evitar problemas de serialización
|
|
|
|
// Cálculos seguros
|
|
SafeUpdateVolumeCalculations();
|
|
IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros
|
|
SafeUpdateTankPressure();
|
|
|
|
// Inicializar estado de mezcla
|
|
UpdateMixingState();
|
|
|
|
|
|
// 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}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error in osHydTank constructor: {ex.Message}");
|
|
// Inicializar valores por defecto mínimos
|
|
InitializeDefaults();
|
|
}
|
|
}
|
|
|
|
|
|
// Constructor y Métodos Base
|
|
|
|
private string nombre = NombreClase();
|
|
|
|
[Category("🏷️ Identificación")]
|
|
[Description("Nombre identificativo del objeto")]
|
|
[Name("Nombre")]
|
|
public override string Nombre
|
|
{
|
|
get => nombre;
|
|
set => SetProperty(ref nombre, value);
|
|
}
|
|
|
|
public override void OnMove(float LeftPixels, float TopPixels)
|
|
{
|
|
// Método básico de movimiento como otros objetos
|
|
// Debug para verificar que se llama
|
|
Debug.WriteLine($"osHydTank.OnMove: LeftPixels={LeftPixels}, TopPixels={TopPixels}, Left={Left}, Top={Top}");
|
|
}
|
|
|
|
public override void OnResize(float Delta_Width, float Delta_Height)
|
|
{
|
|
Tamano += Delta_Width + Delta_Height;
|
|
}
|
|
|
|
public override void UpdateGeometryStart()
|
|
{
|
|
// En el nuevo sistema unificado, no necesitamos crear objetos hidráulicos
|
|
// El componente se registra automáticamente como IHydraulicComponent
|
|
}
|
|
|
|
public override void UpdateControl(int elapsedMilliseconds)
|
|
{
|
|
// En el nuevo sistema TSNet, los valores provienen exclusivamente de TSNet
|
|
// No realizamos cálculos internos - solo actualizamos propiedades de UI
|
|
|
|
// Los resultados de TSNet ya están aplicados en ApplyHydraulicResults()
|
|
// Solo actualizamos propiedades derivadas y visuales
|
|
|
|
try
|
|
{
|
|
// Actualizar propiedades de UI y mezcla
|
|
UpdateMixingState();
|
|
|
|
// Actualizar propiedades calculadas para UI
|
|
OnPropertyChanged(nameof(FillPercentage));
|
|
OnPropertyChanged(nameof(FlowBalance));
|
|
OnPropertyChanged(nameof(CurrentPressurePa));
|
|
|
|
// Actualizar color visual basado en estado
|
|
UpdateTankColorFromFluid();
|
|
|
|
// Debug periódico cada 5 segundos
|
|
if (Environment.TickCount % 5000 < elapsedMilliseconds)
|
|
{
|
|
Debug.WriteLine($"Tank {Nombre}: Level={CurrentLevelM:F2}m, Volume={CurrentVolumeL:F0}L, Flow={NetFlow:F1}L/min, Pressure={CurrentPressure:F2}bar");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error in Tank {Nombre} UpdateControl: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Properties - Configuration
|
|
|
|
|
|
[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)))
|
|
{
|
|
SafeUpdateVolumeCalculations();
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🏗️ Configuración del Tanque")]
|
|
[DisplayName("Volumen máximo")]
|
|
[Description("Volumen máximo del tanque (L)")]
|
|
public double MaxVolumeL
|
|
{
|
|
get => _maxVolumeL;
|
|
set
|
|
{
|
|
if (SetProperty(ref _maxVolumeL, Math.Max(10, value)))
|
|
{
|
|
if (_currentVolumeL > _maxVolumeL)
|
|
CurrentVolumeL = _maxVolumeL;
|
|
SafeUpdateVolumeCalculations();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🏗️ Configuración del Tanque")]
|
|
[DisplayName("Nivel máximo")]
|
|
[Description("Nivel máximo permitido en el tanque (m)")]
|
|
public double MaxLevelM
|
|
{
|
|
get => _maxLevelM;
|
|
set
|
|
{
|
|
if (SetProperty(ref _maxLevelM, Math.Max(0.1, value)))
|
|
{
|
|
// Asegurar que el nivel mínimo no sea mayor que el máximo
|
|
if (_minLevelM >= _maxLevelM)
|
|
MinLevelM = _maxLevelM * 0.1; // 10% del máximo como mínimo
|
|
|
|
SafeUpdateVolumeCalculations();
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🏗️ Configuración del Tanque")]
|
|
[DisplayName("Nivel mínimo")]
|
|
[Description("Nivel mínimo permitido en el tanque (m)")]
|
|
public double MinLevelM
|
|
{
|
|
get => _minLevelM;
|
|
set
|
|
{
|
|
if (SetProperty(ref _minLevelM, Math.Max(0.0, value)))
|
|
{
|
|
// Asegurar que el nivel mínimo no sea mayor que el máximo
|
|
if (_minLevelM >= _maxLevelM)
|
|
MaxLevelM = _minLevelM + 0.1; // Al menos 10cm más que el mínimo
|
|
|
|
SafeUpdateVolumeCalculations();
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🏗️ Configuración del Tanque")]
|
|
[DisplayName("Tipo de tanque")]
|
|
[Description("Tipo de tanque (Standard, Storage, Process, etc.)")]
|
|
public string TankType
|
|
{
|
|
get => _tankType;
|
|
set => SetProperty(ref _tankType, value ?? "Standard");
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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))
|
|
{
|
|
SafeUpdateTankPressure();
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🔧 Sistema de Presión")]
|
|
[DisplayName("Presión del tanque")]
|
|
[Description("Presión mantenida en el tanque (bar). En sistemas reales controlada por PID")]
|
|
public double TankPressure
|
|
{
|
|
get => _tankPressure;
|
|
set
|
|
{
|
|
// Asegurar que el valor esté en bar y sea razonable
|
|
var valueInBar = value > 1000 ? value / 100000.0 : value; // Si viene en Pa, convertir a bar
|
|
valueInBar = Math.Max(0.1, Math.Min(10.0, valueInBar)); // Limitar entre 0.1 y 10 bar
|
|
|
|
if (SetProperty(ref _tankPressure, valueInBar))
|
|
{
|
|
SafeUpdateTankPressure();
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Properties - Current State
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Nivel actual")]
|
|
[Description("Nivel actual de líquido en el tanque (m)")]
|
|
public double CurrentLevelM
|
|
{
|
|
get => _currentLevelM;
|
|
set
|
|
{
|
|
EnsureLockInitialized();
|
|
lock (_levelLock)
|
|
{
|
|
var clampedLevel = Math.Max(0.0, value);
|
|
if (SetProperty(ref _currentLevelM, clampedLevel))
|
|
{
|
|
SafeUpdateVolumeCalculations();
|
|
OnPropertyChanged(nameof(FillPercentage));
|
|
UpdateMixingState();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Volumen actual")]
|
|
[Description("Volumen actual de líquido en el tanque (L)")]
|
|
public double CurrentVolumeL
|
|
{
|
|
get => _currentVolumeL;
|
|
set
|
|
{
|
|
EnsureLockInitialized();
|
|
lock (_levelLock)
|
|
{
|
|
var clampedVolume = Math.Max(0, Math.Min(_maxVolumeL, value));
|
|
if (SetProperty(ref _currentVolumeL, clampedVolume))
|
|
{
|
|
// Actualizar nivel basado en volumen
|
|
_currentLevelM = (clampedVolume / 1000.0) / _crossSectionalArea;
|
|
_currentLevelM = Math.Max(0.0, _currentLevelM);
|
|
OnPropertyChanged(nameof(CurrentLevelM));
|
|
OnPropertyChanged(nameof(FillPercentage));
|
|
UpdateMixingState();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Porcentaje llenado")]
|
|
[Description("Porcentaje de llenado del tanque")]
|
|
[JsonIgnore]
|
|
public double FillPercentage => MaxLevelM > 0 ? (_currentLevelM / MaxLevelM) * 100.0 : 0.0;
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Flujo neto")]
|
|
[Description("Flujo neto del tanque (L/min) - positivo=entrada, negativo=salida")]
|
|
[JsonIgnore]
|
|
public double NetFlow
|
|
{
|
|
get => _netFlow;
|
|
private set => SetProperty(ref _netFlow, value);
|
|
}
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Balance flujo")]
|
|
[Description("Balance de flujo neto (L/min)")]
|
|
[JsonIgnore]
|
|
public double FlowBalance => NetFlow;
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Presión actual")]
|
|
[Description("Presión actual en el tanque (bar)")]
|
|
[JsonIgnore]
|
|
public double CurrentPressure
|
|
{
|
|
get => _currentPressure;
|
|
private set => SetProperty(ref _currentPressure, value);
|
|
}
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Presión (Pa)")]
|
|
[Description("Presión actual en Pascal")]
|
|
[JsonIgnore]
|
|
public double CurrentPressurePa => CurrentPressure * 100000.0;
|
|
|
|
|
|
|
|
// Properties - Fluids and Mixing
|
|
|
|
[Category("🧪 Fluido Primario")]
|
|
[DisplayName("Tipo de fluido")]
|
|
[Description("Tipo del fluido primario en el tanque")]
|
|
public FluidType PrimaryFluidType
|
|
{
|
|
get => _primaryFluid.Type;
|
|
set
|
|
{
|
|
if (_primaryFluid.Type != value)
|
|
{
|
|
_primaryFluid.Type = value;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido Primario")]
|
|
[DisplayName("Temperatura")]
|
|
[Description("Temperatura del fluido primario (°C)")]
|
|
public double PrimaryTemperature
|
|
{
|
|
get => _primaryFluid.Temperature;
|
|
set
|
|
{
|
|
if (Math.Abs(_primaryFluid.Temperature - value) > 0.01)
|
|
{
|
|
_primaryFluid.Temperature = value;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido Primario")]
|
|
[DisplayName("Brix")]
|
|
[Description("Grados Brix para jarabe (% sacarosa)")]
|
|
public double PrimaryBrix
|
|
{
|
|
get => _primaryFluid.Brix;
|
|
set
|
|
{
|
|
var clampedValue = Math.Max(0, Math.Min(80, value));
|
|
if (Math.Abs(_primaryFluid.Brix - clampedValue) > 0.01)
|
|
{
|
|
_primaryFluid.Brix = clampedValue;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido Primario")]
|
|
[DisplayName("Concentración")]
|
|
[Description("Concentración para soda cáustica (%)")]
|
|
public double PrimaryConcentration
|
|
{
|
|
get => _primaryFluid.Concentration;
|
|
set
|
|
{
|
|
var clampedValue = Math.Max(0, Math.Min(50, value));
|
|
if (Math.Abs(_primaryFluid.Concentration - clampedValue) > 0.01)
|
|
{
|
|
_primaryFluid.Concentration = clampedValue;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido Primario")]
|
|
[DisplayName("Volumen")]
|
|
[Description("Volumen del fluido primario (L)")]
|
|
public double PrimaryVolumeL
|
|
{
|
|
get => _primaryVolumeL;
|
|
set
|
|
{
|
|
if (SetProperty(ref _primaryVolumeL, Math.Max(0, value)))
|
|
{
|
|
UpdateMixingState();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido Secundario")]
|
|
[DisplayName("Tipo de fluido")]
|
|
[Description("Tipo del fluido secundario en el tanque")]
|
|
public FluidType SecondaryFluidType
|
|
{
|
|
get => _secondaryFluid.Type;
|
|
set
|
|
{
|
|
if (_secondaryFluid.Type != value)
|
|
{
|
|
_secondaryFluid.Type = value;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido Secundario")]
|
|
[DisplayName("Temperatura")]
|
|
[Description("Temperatura del fluido secundario (°C)")]
|
|
public double SecondaryTemperature
|
|
{
|
|
get => _secondaryFluid.Temperature;
|
|
set
|
|
{
|
|
if (Math.Abs(_secondaryFluid.Temperature - value) > 0.01)
|
|
{
|
|
_secondaryFluid.Temperature = value;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido Secundario")]
|
|
[DisplayName("Brix")]
|
|
[Description("Grados Brix para jarabe (% sacarosa)")]
|
|
public double SecondaryBrix
|
|
{
|
|
get => _secondaryFluid.Brix;
|
|
set
|
|
{
|
|
var clampedValue = Math.Max(0, Math.Min(80, value));
|
|
if (Math.Abs(_secondaryFluid.Brix - clampedValue) > 0.01)
|
|
{
|
|
_secondaryFluid.Brix = clampedValue;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido Secundario")]
|
|
[DisplayName("Concentración")]
|
|
[Description("Concentración para soda cáustica (%)")]
|
|
public double SecondaryConcentration
|
|
{
|
|
get => _secondaryFluid.Concentration;
|
|
set
|
|
{
|
|
var clampedValue = Math.Max(0, Math.Min(50, value));
|
|
if (Math.Abs(_secondaryFluid.Concentration - clampedValue) > 0.01)
|
|
{
|
|
_secondaryFluid.Concentration = clampedValue;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido Secundario")]
|
|
[DisplayName("Volumen")]
|
|
[Description("Volumen del fluido secundario (L)")]
|
|
public double SecondaryVolumeL
|
|
{
|
|
get => _secondaryVolumeL;
|
|
set
|
|
{
|
|
if (SetProperty(ref _secondaryVolumeL, Math.Max(0, value)))
|
|
{
|
|
UpdateMixingState();
|
|
OnPropertyChanged(nameof(CurrentFluidDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🔀 Control de Mezcla")]
|
|
[DisplayName("Volumen de mezcla")]
|
|
[Description("Volumen de transición durante la mezcla (L)")]
|
|
public double MixingVolumeL
|
|
{
|
|
get => _mixingVolumeL;
|
|
set => SetProperty(ref _mixingVolumeL, Math.Max(0, value));
|
|
}
|
|
|
|
[Category("🔀 Control de Mezcla")]
|
|
[DisplayName("Estado de mezcla")]
|
|
[Description("Estado actual del proceso de mezcla")]
|
|
[JsonIgnore]
|
|
public MixingState MixingState
|
|
{
|
|
get => _mixingState;
|
|
private set => SetProperty(ref _mixingState, value);
|
|
}
|
|
|
|
[Category("🔀 Control de Mezcla")]
|
|
[DisplayName("Ratio de mezcla")]
|
|
[Description("Ratio actual de mezcla (0=primario, 1=secundario)")]
|
|
[JsonIgnore]
|
|
public double CurrentMixRatio
|
|
{
|
|
get => _currentMixRatio;
|
|
private set => SetProperty(ref _currentMixRatio, value);
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
{
|
|
// Convertir TankPressure de bar a Pa para el solver
|
|
var pressurePa = TankPressure * 100000.0; // bar a Pa
|
|
nodes.Add(new HydraulicNodeDefinition(Nombre, true, pressurePa, GetTankDescription()));
|
|
//Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {pressurePa:F0} Pa ({TankPressure:F2} bar)");
|
|
}
|
|
else
|
|
{
|
|
nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, GetTankDescription()));
|
|
//Debug.WriteLine($"Tanque {Nombre}: Nodo de presión libre creado");
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
|
{
|
|
try
|
|
{
|
|
// Aplicar resultados a través del TSNet Adapter
|
|
if (TSNetAdapter?.Results != null)
|
|
{
|
|
// Actualizar presión desde TSNet
|
|
if (pressures.ContainsKey(TSNetAdapter.TankId))
|
|
{
|
|
var pressurePa = pressures[TSNetAdapter.TankId];
|
|
CurrentPressure = pressurePa / 100000.0; // Convertir Pa a bar
|
|
TSNetAdapter.Results.CalculatedPressureBar = CurrentPressure;
|
|
|
|
Debug.WriteLine($"Tank {Nombre}: Presión TSNet={pressurePa:F0} Pa = {CurrentPressure:F3} bar");
|
|
}
|
|
|
|
// Calcular flujo neto desde pipes conectadas
|
|
var netFlowM3s = CalculateNetFlowFromConnectedPipes(flows);
|
|
NetFlow = netFlowM3s * 60000.0; // Convertir m³/s a L/min
|
|
TSNetAdapter.Results.NetFlowM3s = NetFlow;
|
|
|
|
// Actualizar nivel basado en balance de flujo solo si hay cambio
|
|
if (Math.Abs(NetFlow) > 0.001) // Umbral mínimo de 0.001 L/min
|
|
{
|
|
var deltaTime = GetDeltaTime();
|
|
UpdateLevelFromFlowBalance(deltaTime);
|
|
}
|
|
|
|
// Actualizar resultados del adapter
|
|
TSNetAdapter.Results.CalculatedLevelM = CurrentLevelM;
|
|
TSNetAdapter.Results.CalculatedVolumeL = CurrentVolumeL;
|
|
TSNetAdapter.Results.Timestamp = DateTime.Now;
|
|
TSNetAdapter.Results.Status = $"Level: {CurrentLevelM:F2}m, Flow: {NetFlow:F1}L/min";
|
|
}
|
|
|
|
// Notificar cambios para UI
|
|
OnPropertyChanged(nameof(CurrentLevelM));
|
|
OnPropertyChanged(nameof(CurrentVolumeL));
|
|
OnPropertyChanged(nameof(FillPercentage));
|
|
OnPropertyChanged(nameof(NetFlow));
|
|
OnPropertyChanged(nameof(CurrentPressure));
|
|
|
|
// Debug periódico
|
|
if (Environment.TickCount % 2000 < 50) // Cada ~2 segundos
|
|
{
|
|
Debug.WriteLine($"Tank {Nombre}: Level={CurrentLevelM:F2}m, NetFlow={NetFlow:F1}L/min, Pressure={CurrentPressure:F3}bar");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error en Tank {Nombre} ApplyHydraulicResults: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
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
|
|
SafeUpdateTankPressure();
|
|
SafeUpdateVolumeCalculations();
|
|
}
|
|
|
|
|
|
|
|
// UI Properties
|
|
|
|
/// <summary>
|
|
/// Propiedades del fluido actual que está saliendo del tanque
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public FluidProperties CurrentOutputFluid
|
|
{
|
|
get
|
|
{
|
|
return MixingState switch
|
|
{
|
|
MixingState.Empty => new FluidProperties(FluidType.Air),
|
|
MixingState.PrimaryOnly => _primaryFluid.Clone(),
|
|
MixingState.SecondaryOnly => _secondaryFluid.Clone(),
|
|
MixingState.EmptyingSecondary => _secondaryFluid.Clone(),
|
|
MixingState.Mixing => _primaryFluid.MixWith(_secondaryFluid, CurrentMixRatio),
|
|
MixingState.EmptyingPrimary => _primaryFluid.Clone(),
|
|
_ => new FluidProperties(FluidType.Air)
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Descripción del fluido actual
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public string CurrentFluidDescription => CurrentOutputFluid.Description;
|
|
|
|
/// <summary>
|
|
/// Color del nivel de líquido basado en el fluido actual
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public SolidColorBrush LevelColor
|
|
{
|
|
get
|
|
{
|
|
var fluid = CurrentOutputFluid;
|
|
var colorHex = fluid.Color;
|
|
|
|
// Convertir hex a color y ajustar según nivel
|
|
var percentage = FillPercentage;
|
|
var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex);
|
|
|
|
// Oscurecer si nivel es bajo
|
|
if (percentage < 20)
|
|
{
|
|
color = Color.FromRgb((byte)(color.R * 0.7), (byte)(color.G * 0.7), (byte)(color.B * 0.7));
|
|
}
|
|
|
|
return new SolidColorBrush(color);
|
|
}
|
|
}
|
|
|
|
/// <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);
|
|
return new SolidColorBrush(Colors.DarkBlue);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Color del balance de flujo
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public SolidColorBrush FlowBalanceColor
|
|
{
|
|
get
|
|
{
|
|
var balance = FlowBalance;
|
|
if (Math.Abs(balance) < 0.1) return new SolidColorBrush(Colors.Gray);
|
|
return balance > 0 ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Indica si el tanque está vacío (contiene aire)
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public bool IsEmpty => CurrentVolumeL < 1.0; // Menos de 1L considerado vacío
|
|
|
|
/// <summary>
|
|
/// Densidad del fluido actual
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public double CurrentFluidDensity => CurrentOutputFluid.Density;
|
|
|
|
/// <summary>
|
|
/// Viscosidad del fluido actual
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public double CurrentFluidViscosity => CurrentOutputFluid.Viscosity;
|
|
|
|
/// <summary>
|
|
/// Porcentaje de altura para la sección secundaria (abajo) - para display visual
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public double SecondaryDisplayPercentage
|
|
{
|
|
get
|
|
{
|
|
var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL;
|
|
if (totalVolume == 0) return 0;
|
|
return (_secondaryVolumeL / totalVolume) * FillPercentage / 100.0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Porcentaje de altura para la sección de mezcla (medio) - para display visual
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public double MixDisplayPercentage
|
|
{
|
|
get
|
|
{
|
|
var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL;
|
|
if (totalVolume == 0) return 0;
|
|
return (_mixingVolumeL / totalVolume) * FillPercentage / 100.0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Porcentaje de altura para la sección primaria (arriba) - para display visual
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public double PrimaryDisplayPercentage
|
|
{
|
|
get
|
|
{
|
|
var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL;
|
|
if (totalVolume == 0) return 0;
|
|
return (_primaryVolumeL / totalVolume) * FillPercentage / 100.0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Color para la sección secundaria
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public SolidColorBrush SecondaryLevelColor
|
|
{
|
|
get
|
|
{
|
|
var colorHex = _secondaryFluid.Color;
|
|
var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex);
|
|
return new SolidColorBrush(color);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Color para la sección de mezcla
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public SolidColorBrush MixLevelColor
|
|
{
|
|
get
|
|
{
|
|
var mixedFluid = _primaryFluid.MixWith(_secondaryFluid, 0.5); // Mezcla 50-50
|
|
var colorHex = mixedFluid.Color;
|
|
var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex);
|
|
return new SolidColorBrush(color);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Color para la sección primaria
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public SolidColorBrush PrimaryLevelColor
|
|
{
|
|
get
|
|
{
|
|
var colorHex = _primaryFluid.Color;
|
|
var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex);
|
|
return new SolidColorBrush(color);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Private Methods
|
|
|
|
private void InitializeDefaults()
|
|
{
|
|
// Valores mínimos para que el objeto sea funcional
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(nombre))
|
|
nombre = "Tanque Hidráulico";
|
|
|
|
if (Tamano <= 0)
|
|
Tamano = 1.0f;
|
|
|
|
if (Ancho <= 0)
|
|
Ancho = 0.30f;
|
|
|
|
if (Alto <= 0)
|
|
Alto = 0.40f;
|
|
|
|
// Inicializar configuraciones del tanque
|
|
if (_maxLevelM <= 0)
|
|
_maxLevelM = 2.0;
|
|
|
|
if (_minLevelM < 0 || _minLevelM >= _maxLevelM)
|
|
_minLevelM = 0.0;
|
|
|
|
if (string.IsNullOrEmpty(_tankType))
|
|
_tankType = "Standard";
|
|
|
|
SafeUpdateVolumeCalculations();
|
|
SafeUpdateTankPressure();
|
|
|
|
Debug.WriteLine("osHydTank: Initialized with default values");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error in InitializeDefaults: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void SafeUpdateVolumeCalculations()
|
|
{
|
|
try
|
|
{
|
|
// Calcular volumen total actual basado en nivel
|
|
_currentVolumeL = _currentLevelM * _crossSectionalArea * 1000.0; // convertir m³ a L
|
|
_maxVolumeL = 2.0 * _crossSectionalArea * 1000.0; // Assuming 2m as max height
|
|
|
|
// Asegurar que los volúmenes de fluidos no excedan el volumen total
|
|
var totalFluidVolume = _primaryVolumeL + _secondaryVolumeL;
|
|
if (totalFluidVolume > _currentVolumeL)
|
|
{
|
|
var ratio = _currentVolumeL / totalFluidVolume;
|
|
_primaryVolumeL *= ratio;
|
|
_secondaryVolumeL *= ratio;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error in SafeUpdateVolumeCalculations: {ex.Message}");
|
|
// Usar valores por defecto seguros
|
|
_currentVolumeL = 1000.0;
|
|
_maxVolumeL = 2000.0;
|
|
}
|
|
}
|
|
|
|
private void SafeUpdateTankPressure()
|
|
{
|
|
try
|
|
{
|
|
if (IsFixedPressure)
|
|
{
|
|
// TankPressure está en bar, CurrentPressure también debe estar en bar
|
|
CurrentPressure = TankPressure;
|
|
}
|
|
else
|
|
{
|
|
// Calcular presión basada solo en volumen primario (para cálculos hidráulicos)
|
|
var primaryLevel = PrimaryVolumeL / (CrossSectionalArea * 1000.0); // Nivel equivalente del volumen primario
|
|
var hydrostaticPressure = primaryLevel * 9.81 * CurrentFluidDensity / 100000.0; // Convertir a bar
|
|
CurrentPressure = 1.013 + hydrostaticPressure; // Presión atmosférica + hidráulica (todo en bar)
|
|
}
|
|
|
|
// Debug para verificar las unidades
|
|
Debug.WriteLine($"Tanque {Nombre}: TankPressure={TankPressure:F3} bar, CurrentPressure={CurrentPressure:F3} bar");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error in SafeUpdateTankPressure: {ex.Message}");
|
|
// Usar presión atmosférica por defecto (en bar)
|
|
_currentPressure = 1.013;
|
|
}
|
|
}
|
|
|
|
|
|
private void UpdateMixingState()
|
|
{
|
|
var totalVolume = _primaryVolumeL + _secondaryVolumeL;
|
|
|
|
if (totalVolume < 1.0) // Menos de 1L considerado vacío
|
|
{
|
|
MixingState = MixingState.Empty;
|
|
CurrentMixRatio = 0.0;
|
|
return;
|
|
}
|
|
|
|
if (_secondaryVolumeL <= 0)
|
|
{
|
|
MixingState = MixingState.PrimaryOnly;
|
|
CurrentMixRatio = 0.0;
|
|
}
|
|
else if (_primaryVolumeL <= 0)
|
|
{
|
|
MixingState = MixingState.SecondaryOnly;
|
|
CurrentMixRatio = 1.0;
|
|
}
|
|
else
|
|
{
|
|
// Determinar estado de mezcla basado en volúmenes
|
|
var secondaryRatio = _secondaryVolumeL / totalVolume;
|
|
|
|
if (_secondaryVolumeL <= _mixingVolumeL && _primaryVolumeL > _mixingVolumeL)
|
|
{
|
|
// Vaciando secundario
|
|
MixingState = MixingState.EmptyingSecondary;
|
|
CurrentMixRatio = secondaryRatio;
|
|
}
|
|
else if (_primaryVolumeL <= _mixingVolumeL && _secondaryVolumeL > _mixingVolumeL)
|
|
{
|
|
// Vaciando primario
|
|
MixingState = MixingState.EmptyingPrimary;
|
|
CurrentMixRatio = secondaryRatio;
|
|
}
|
|
else if (_secondaryVolumeL <= _mixingVolumeL && _primaryVolumeL <= _mixingVolumeL)
|
|
{
|
|
// Mezclando ambos
|
|
MixingState = MixingState.Mixing;
|
|
CurrentMixRatio = secondaryRatio;
|
|
}
|
|
else
|
|
{
|
|
// Determinar cual predomina
|
|
if (secondaryRatio > 0.7)
|
|
{
|
|
MixingState = MixingState.SecondaryOnly;
|
|
CurrentMixRatio = 1.0;
|
|
}
|
|
else if (secondaryRatio < 0.3)
|
|
{
|
|
MixingState = MixingState.PrimaryOnly;
|
|
CurrentMixRatio = 0.0;
|
|
}
|
|
else
|
|
{
|
|
MixingState = MixingState.Mixing;
|
|
CurrentMixRatio = secondaryRatio;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateLevelFromFlowBalance(double deltaTime)
|
|
{
|
|
if (deltaTime <= 0 || Math.Abs(deltaTime) > 1.0) // Evitar deltas muy grandes
|
|
return;
|
|
|
|
// Balance de masa: cambio_volumen = (flujo_entrada - flujo_salida) * tiempo
|
|
var volumeChangeL = FlowBalance * deltaTime; // L/min * min = L
|
|
|
|
// Actualizar volumen total
|
|
var newVolumeL = CurrentVolumeL + volumeChangeL;
|
|
CurrentVolumeL = Math.Max(0, Math.Min(_maxVolumeL, newVolumeL));
|
|
|
|
// Actualizar volúmenes de fluidos según el estado de mezcla
|
|
UpdateFluidVolumesFromFlow(volumeChangeL);
|
|
|
|
|
|
// Debug para verificar cambios
|
|
if (VerboseLogging() && Math.Abs(volumeChangeL) > 0.1)
|
|
{
|
|
Trace.WriteLine($"Tanque {Nombre}: Δt={deltaTime:F3}min, ΔVolumen={volumeChangeL:F2}L, " +
|
|
$"Volumen={CurrentVolumeL:F1}L, Estado={MixingState}, Fluido={CurrentFluidDescription}");
|
|
}
|
|
}
|
|
|
|
private void UpdateFluidVolumesFromFlow(double volumeChangeL)
|
|
{
|
|
if (Math.Abs(volumeChangeL) < 0.01) return; // Cambio muy pequeño
|
|
|
|
if (volumeChangeL > 0)
|
|
{
|
|
// Flujo de entrada - agregar al fluido primario por defecto
|
|
_primaryVolumeL += volumeChangeL;
|
|
}
|
|
else
|
|
{
|
|
// Flujo de salida - quitar según el estado de mezcla
|
|
var volumeToRemove = Math.Abs(volumeChangeL);
|
|
|
|
switch (MixingState)
|
|
{
|
|
case MixingState.SecondaryOnly:
|
|
case MixingState.EmptyingSecondary:
|
|
_secondaryVolumeL = Math.Max(0, _secondaryVolumeL - volumeToRemove);
|
|
break;
|
|
|
|
case MixingState.PrimaryOnly:
|
|
case MixingState.EmptyingPrimary:
|
|
_primaryVolumeL = Math.Max(0, _primaryVolumeL - volumeToRemove);
|
|
break;
|
|
|
|
case MixingState.Mixing:
|
|
// Quitar proporcionalmente
|
|
var totalVolume = _primaryVolumeL + _secondaryVolumeL;
|
|
if (totalVolume > 0)
|
|
{
|
|
var primaryRatio = _primaryVolumeL / totalVolume;
|
|
var secondaryRatio = _secondaryVolumeL / totalVolume;
|
|
|
|
_primaryVolumeL = Math.Max(0, _primaryVolumeL - volumeToRemove * primaryRatio);
|
|
_secondaryVolumeL = Math.Max(0, _secondaryVolumeL - volumeToRemove * secondaryRatio);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
UpdateMixingState();
|
|
}
|
|
|
|
private void UpdateFlowsFromConnectedPipes(Dictionary<string, double> flows)
|
|
{
|
|
NetFlow = 0.0;
|
|
|
|
// Buscar flujos en las ramas que involucren este tanque
|
|
foreach (var flow in flows)
|
|
{
|
|
var branchName = flow.Key;
|
|
var flowValueM3s = flow.Value; // El flujo viene en m³/s
|
|
var flowValueLmin = flowValueM3s * 60000.0; // Convertir a L/min
|
|
|
|
// Buscar conexiones que involucren este tanque
|
|
if (branchName.Contains(Nombre))
|
|
{
|
|
// Determinar dirección del flujo basado en el nombre de la rama
|
|
if (IsFlowTowardsTank(branchName))
|
|
{
|
|
NetFlow += flowValueLmin; // Flujo hacia el tanque (positivo)
|
|
}
|
|
else
|
|
{
|
|
NetFlow -= flowValueLmin; // Flujo desde el tanque (negativo)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private double GetDeltaTime()
|
|
{
|
|
var currentTime = Environment.TickCount64 / 60000.0; // convertir a minutos
|
|
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 0.1 minutos (6 segundos)
|
|
}
|
|
|
|
private bool VerboseLogging()
|
|
{
|
|
return _mainViewModel?.hydraulicSimulationManager?.VerboseOutput ?? false;
|
|
}
|
|
|
|
private void InvalidateHydraulicNetwork()
|
|
{
|
|
_mainViewModel?.hydraulicSimulationManager?.InvalidateNetwork();
|
|
}
|
|
|
|
private string GetTankDescription()
|
|
{
|
|
return "Tanque hidráulico";
|
|
}
|
|
|
|
private bool IsFlowTowardsTank(string branchName)
|
|
{
|
|
// Determinar la dirección del flujo basándose en el nombre de la rama
|
|
return branchName.EndsWith($" -> {Nombre}") || branchName.EndsWith($"_{Nombre}");
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
SafeUpdateVolumeCalculations();
|
|
SafeUpdateTankPressure();
|
|
}
|
|
|
|
public void Inicializar(int valorInicial)
|
|
{
|
|
// Inicialización con valor inicial
|
|
SafeUpdateVolumeCalculations();
|
|
SafeUpdateTankPressure();
|
|
}
|
|
|
|
// Disposing no longer needed for connections since we removed them
|
|
public void Disposing()
|
|
{
|
|
// Cleanup if needed for fluids
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Calcula el flujo neto desde pipes conectadas usando resultados de TSNet
|
|
/// </summary>
|
|
private double CalculateNetFlowFromConnectedPipes(Dictionary<string, double> flows)
|
|
{
|
|
double netFlow = 0.0;
|
|
|
|
if (_mainViewModel?.ObjetosSimulables == null) return netFlow;
|
|
|
|
try
|
|
{
|
|
// Buscar todas las pipes conectadas a este tanque
|
|
var connectedPipes = _mainViewModel.ObjetosSimulables
|
|
.OfType<osHydPipe>()
|
|
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
|
|
.ToList();
|
|
|
|
foreach (var pipe in connectedPipes)
|
|
{
|
|
// Buscar el flujo de la pipe en los resultados de TSNet
|
|
var pipeElementName = $"{pipe.Nombre}_Pipe";
|
|
if (flows.ContainsKey(pipeElementName))
|
|
{
|
|
var pipeFlow = flows[pipeElementName]; // m³/s
|
|
|
|
// Determinar dirección: positivo si entra al tanque, negativo si sale
|
|
if (pipe.Id_ComponenteB == Nombre)
|
|
{
|
|
// La pipe va hacia este tanque
|
|
netFlow += pipeFlow;
|
|
}
|
|
else if (pipe.Id_ComponenteA == Nombre)
|
|
{
|
|
// La pipe sale desde este tanque
|
|
netFlow -= pipeFlow;
|
|
}
|
|
}
|
|
}
|
|
|
|
return netFlow;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error calculating net flow for tank {Nombre}: {ex.Message}");
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Actualiza el color del tanque basado en el fluido actual
|
|
/// </summary>
|
|
private void UpdateTankColorFromFluid()
|
|
{
|
|
try
|
|
{
|
|
var currentFluid = CurrentOutputFluid;
|
|
if (currentFluid != null)
|
|
{
|
|
var colorHex = currentFluid.Color;
|
|
var color = (Color)ColorConverter.ConvertFromString(colorHex);
|
|
ColorButton_oculto = new SolidColorBrush(color);
|
|
}
|
|
else
|
|
{
|
|
ColorButton_oculto = Brushes.LightBlue; // Color por defecto
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
ColorButton_oculto = Brushes.LightBlue; // Color por defecto en caso de error
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Serialization Support
|
|
|
|
/// <summary>
|
|
/// Se llama antes de la serialización para asegurar que el lock esté disponible
|
|
/// </summary>
|
|
private void EnsureLockInitialized()
|
|
{
|
|
if (_levelLock == null)
|
|
_levelLock = new object();
|
|
}
|
|
|
|
// osBase Overrides
|
|
|
|
public override void ucLoaded()
|
|
{
|
|
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
|
|
// crear el objeto de simulacion
|
|
base.ucLoaded();
|
|
|
|
// Reinicializar TSNet Adapter si es necesario
|
|
if (TSNetAdapter == null)
|
|
{
|
|
TSNetAdapter = new TSNetTankAdapter(this);
|
|
Debug.WriteLine($"Tank {Nombre}: TSNetAdapter inicializado en ucLoaded");
|
|
}
|
|
|
|
Debug.WriteLine($"osHydTank.ucLoaded(): Componente hidráulico cargado correctamente");
|
|
|
|
// Inicialización específica del tanque hidráulico
|
|
SafeUpdateVolumeCalculations();
|
|
SafeUpdateTankPressure();
|
|
|
|
// 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()
|
|
{
|
|
// En el nuevo sistema unificado, el HydraulicSimulationManager
|
|
// maneja automáticamente el registro/desregistro de objetos
|
|
base.ucUnLoaded();
|
|
}
|
|
|
|
|
|
}
|
|
}
|