549 lines
19 KiB
C#
549 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Media;
|
|
using CtrEditor.HydraulicSimulator;
|
|
using CtrEditor.HydraulicSimulator.TSNet.Components;
|
|
using CtrEditor.ObjetosSim;
|
|
using CtrEditor.FuncionesBase;
|
|
using Newtonsoft.Json;
|
|
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using LibS7Adv;
|
|
|
|
namespace CtrEditor.ObjetosSim
|
|
{
|
|
/// <summary>
|
|
/// Tanque hidráulico SIMPLIFICADO - solo propiedades esenciales
|
|
/// </summary>
|
|
public partial class osHydTank : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
|
|
{
|
|
public static string NombreCategoria() => "Componentes Hidráulicos";
|
|
public static string NombreClase() => "Tanque Hidráulico";
|
|
|
|
// Tipos de fluido disponibles
|
|
public enum FluidTypeId
|
|
{
|
|
Water = 0,
|
|
SyrupBrix40 = 1
|
|
}
|
|
|
|
// SOLO campos esenciales
|
|
private double _elevation = 0.0; // m - elevación del fondo del tanque
|
|
private double _maxLevel = 2.0; // m - nivel máximo
|
|
private double _diameter = 1.0; // m - diámetro del tanque
|
|
private FluidTypeId _fluidType = FluidTypeId.Water;
|
|
|
|
// Estado actual del tanque - ÚNICO NIVEL QUE IMPORTA
|
|
private double _currentLevel = 1.0; // m - nivel actual
|
|
private double _currentPressure = 1.013; // bar
|
|
private double _currentFlow = 0.0; // m³/s
|
|
|
|
// TSNet Integration
|
|
[JsonIgnore]
|
|
public TSNetTankAdapter? TSNetAdapter { get; private set; }
|
|
|
|
// Propiedades visuales
|
|
[ObservableProperty]
|
|
[property: Category("🎨 Apariencia")]
|
|
[property: Description("Tamaño visual del tanque")]
|
|
[property: Name("Tamaño")]
|
|
public float tamano;
|
|
|
|
[ObservableProperty]
|
|
[property: Category("🎨 Apariencia")]
|
|
[property: Description("Color visual del tanque")]
|
|
[property: Name("Color")]
|
|
private Brush colorButton_oculto = Brushes.LightBlue;
|
|
|
|
// Constructor
|
|
public osHydTank()
|
|
{
|
|
try
|
|
{
|
|
Nombre = "Tanque Hidráulico";
|
|
Tamano = 1.0f;
|
|
Ancho = 0.30f;
|
|
Alto = 0.40f;
|
|
Angulo = 0f;
|
|
Lock_movement = false;
|
|
|
|
Debug.WriteLine($"osHydTank Constructor completado correctamente");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error en constructor osHydTank: {ex.Message}");
|
|
Nombre = "Tanque Hidráulico";
|
|
Tamano = 1.0f;
|
|
}
|
|
}
|
|
|
|
// ===== PROPIEDADES ESENCIALES - SIMPLIFICADAS =====
|
|
|
|
[Category("🏗️ Configuración")]
|
|
[DisplayName("Elevación")]
|
|
[Description("Elevación del fondo del tanque (m)")]
|
|
public double Elevation
|
|
{
|
|
get => _elevation;
|
|
set
|
|
{
|
|
if (SetProperty(ref _elevation, value))
|
|
{
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🏗️ Configuración")]
|
|
[DisplayName("Nivel máximo")]
|
|
[Description("Nivel máximo del tanque (m)")]
|
|
public double MaxLevel
|
|
{
|
|
get => _maxLevel;
|
|
set
|
|
{
|
|
var newMaxLevel = Math.Max(0.1, value);
|
|
if (SetProperty(ref _maxLevel, newMaxLevel))
|
|
{
|
|
// Si el nivel actual está por encima del nuevo máximo, ajustarlo
|
|
if (_currentLevel > _maxLevel)
|
|
{
|
|
var oldCurrent = _currentLevel;
|
|
_currentLevel = _maxLevel;
|
|
Debug.WriteLine($"⚠️ osHydTank {Nombre}: CurrentLevel ajustado de {oldCurrent} a {_currentLevel} debido a cambio de MaxLevel");
|
|
OnPropertyChanged(nameof(CurrentLevel));
|
|
}
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🏗️ Configuración")]
|
|
[DisplayName("Diámetro")]
|
|
[Description("Diámetro del tanque (m)")]
|
|
public double Diameter
|
|
{
|
|
get => _diameter;
|
|
set
|
|
{
|
|
if (SetProperty(ref _diameter, Math.Max(0.1, value)))
|
|
{
|
|
InvalidateHydraulicNetwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido")]
|
|
[DisplayName("Tipo de fluido")]
|
|
[Description("Tipo de fluido en el tanque")]
|
|
public FluidTypeId FluidType
|
|
{
|
|
get => _fluidType;
|
|
set
|
|
{
|
|
if (SetProperty(ref _fluidType, value))
|
|
{
|
|
UpdateTankColorFromFluid();
|
|
OnPropertyChanged(nameof(FluidDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("🧪 Fluido")]
|
|
[DisplayName("Descripción del fluido")]
|
|
[Description("Descripción del tipo de fluido")]
|
|
[ReadOnly(true)]
|
|
public string FluidDescription
|
|
{
|
|
get
|
|
{
|
|
return FluidType switch
|
|
{
|
|
FluidTypeId.Water => "Agua",
|
|
FluidTypeId.SyrupBrix40 => "Jarabe Brix 40%",
|
|
_ => "Desconocido"
|
|
};
|
|
}
|
|
}
|
|
|
|
// ===== ESTADO ACTUAL - SIMPLIFICADO =====
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Nivel actual")]
|
|
[Description("Nivel actual del agua en el tanque (m)")]
|
|
public double CurrentLevel
|
|
{
|
|
get => _currentLevel;
|
|
set
|
|
{
|
|
// Validación automática: limitar entre 0 y MaxLevel
|
|
var clampedValue = Math.Max(0, Math.Min(_maxLevel, value));
|
|
|
|
if (Math.Abs(value - clampedValue) > 1e-6)
|
|
{
|
|
Debug.WriteLine($"⚠️ osHydTank {Nombre}: CurrentLevel ({value}) ajustado a rango válido [0, {_maxLevel}] → {clampedValue}");
|
|
}
|
|
|
|
if (SetProperty(ref _currentLevel, clampedValue))
|
|
{
|
|
OnPropertyChanged(nameof(FillPercentage));
|
|
OnPropertyChanged(nameof(CurrentVolume));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Presión actual")]
|
|
[Description("Presión actual en el tanque (bar)")]
|
|
[ReadOnly(true)]
|
|
public double CurrentPressure
|
|
{
|
|
get => _currentPressure;
|
|
private set => SetProperty(ref _currentPressure, value);
|
|
}
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Flujo actual")]
|
|
[Description("Flujo actual hacia/desde el tanque (m³/s)")]
|
|
[ReadOnly(true)]
|
|
public double CurrentFlow
|
|
{
|
|
get => _currentFlow;
|
|
private set => SetProperty(ref _currentFlow, value);
|
|
}
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Volumen actual")]
|
|
[Description("Volumen actual en el tanque (L)")]
|
|
[ReadOnly(true)]
|
|
public double CurrentVolume
|
|
{
|
|
get
|
|
{
|
|
var area = Math.PI * Math.Pow(_diameter / 2, 2); // m²
|
|
return _currentLevel * area * 1000; // L
|
|
}
|
|
}
|
|
|
|
[Category("📊 Estado Actual")]
|
|
[DisplayName("Porcentaje de llenado")]
|
|
[Description("Porcentaje de llenado del tanque")]
|
|
[ReadOnly(true)]
|
|
public double FillPercentage
|
|
{
|
|
get
|
|
{
|
|
if (_maxLevel <= 0) return 0;
|
|
return Math.Max(0, Math.Min(100, (_currentLevel / _maxLevel) * 100));
|
|
}
|
|
}
|
|
|
|
// ===== PROPIEDADES TÉCNICAS PARA TSNET =====
|
|
|
|
public double TankPressure { get; set; } = 101325.0; // 1 atm por defecto
|
|
public bool IsFixedPressure { get; set; } = false;
|
|
|
|
// ===== MÉTODOS BÁSICOS =====
|
|
|
|
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)
|
|
{
|
|
// Movimiento básico
|
|
}
|
|
|
|
public override void OnResize(float Delta_Width, float Delta_Height)
|
|
{
|
|
Tamano += Delta_Width + Delta_Height;
|
|
}
|
|
|
|
public override void UpdateGeometryStart()
|
|
{
|
|
// El componente se registra automáticamente como IHydraulicComponent
|
|
}
|
|
|
|
public override void UpdateControl(int elapsedMilliseconds)
|
|
{
|
|
// Los valores provienen de TSNet
|
|
OnPropertyChanged(nameof(CurrentVolume));
|
|
OnPropertyChanged(nameof(FillPercentage));
|
|
UpdateTankColorFromFluid();
|
|
}
|
|
|
|
private void UpdateTankColorFromFluid()
|
|
{
|
|
ColorButton_oculto = FluidType switch
|
|
{
|
|
FluidTypeId.Water => Brushes.LightBlue,
|
|
FluidTypeId.SyrupBrix40 => Brushes.Orange,
|
|
_ => Brushes.Gray
|
|
};
|
|
}
|
|
|
|
private void InvalidateHydraulicNetwork()
|
|
{
|
|
// Invalidar la red hidráulica para que se recalcule
|
|
var mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel;
|
|
mainViewModel?.InvalidateHydraulicNetwork();
|
|
}
|
|
|
|
// ===== IMPLEMENTACIÓN DE INTERFACES HIDRÁULICAS =====
|
|
|
|
public void ApplyHydraulicPressure(double pressurePa)
|
|
{
|
|
CurrentPressure = pressurePa / 100000.0; // Pa a bar
|
|
}
|
|
|
|
public void ApplyHydraulicFlow(double flowM3s)
|
|
{
|
|
CurrentFlow = flowM3s;
|
|
}
|
|
|
|
public void UpdateHydraulicState(double timeSeconds)
|
|
{
|
|
// TSNet maneja el estado
|
|
}
|
|
|
|
public void UpdateFromTSNetResults(double level, double pressure, double flow)
|
|
{
|
|
CurrentLevel = Math.Max(0, Math.Min(_maxLevel, level));
|
|
CurrentPressure = pressure / 100000.0; // Pa a bar
|
|
CurrentFlow = flow;
|
|
}
|
|
|
|
public TSNetTankAdapter CreateTSNetAdapter()
|
|
{
|
|
TSNetAdapter = new TSNetTankAdapter(this);
|
|
return TSNetAdapter;
|
|
}
|
|
|
|
[JsonIgnore]
|
|
public bool HasHydraulicComponents => true;
|
|
|
|
public List<HydraulicNodeDefinition> GetHydraulicNodes()
|
|
{
|
|
var nodes = new List<HydraulicNodeDefinition>();
|
|
var sanitizedName = SanitizeNodeName(Nombre);
|
|
nodes.Add(new HydraulicNodeDefinition(sanitizedName, false, null, $"Tanque hidráulico - {FluidDescription}"));
|
|
return nodes;
|
|
}
|
|
|
|
private string SanitizeNodeName(string nodeName)
|
|
{
|
|
if (string.IsNullOrEmpty(nodeName))
|
|
return "UnknownTank";
|
|
|
|
var sanitized = nodeName
|
|
.Replace(" ", "_")
|
|
.Replace("á", "a").Replace("é", "e").Replace("í", "i").Replace("ó", "o").Replace("ú", "u")
|
|
.Replace("ñ", "n").Replace("Á", "A").Replace("É", "E").Replace("Í", "I").Replace("Ó", "O")
|
|
.Replace("Ú", "U").Replace("Ñ", "N");
|
|
|
|
var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray();
|
|
var result = new string(validChars);
|
|
|
|
if (!string.IsNullOrEmpty(result) && !char.IsLetter(result[0]))
|
|
{
|
|
result = "Tank_" + result;
|
|
}
|
|
|
|
return string.IsNullOrEmpty(result) ? "UnknownTank" : result;
|
|
}
|
|
|
|
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine($"osHydTank {Nombre} - ApplyHydraulicResults iniciado");
|
|
|
|
var searchPatterns = new[]
|
|
{
|
|
Nombre,
|
|
SanitizeNodeName(Nombre),
|
|
$"TANK_{Nombre}",
|
|
$"Tank_{Nombre}",
|
|
Nombre.Replace(" ", "_")
|
|
};
|
|
|
|
// Buscar presión del tanque
|
|
foreach (var pattern in searchPatterns)
|
|
{
|
|
if (pressures.ContainsKey(pattern))
|
|
{
|
|
CurrentPressure = pressures[pattern] / 100000.0; // Pa a bar
|
|
Debug.WriteLine($"osHydTank {Nombre} - Presión encontrada: {CurrentPressure:F6} bar");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Calcular flujo neto
|
|
var netFlowM3s = CalculateNetFlowFromConnectedPipes(flows);
|
|
CurrentFlow = netFlowM3s;
|
|
Debug.WriteLine($"osHydTank {Nombre} - Flujo neto: {netFlowM3s:F6} m³/s");
|
|
|
|
// Actualizar nivel basado en balance de flujo
|
|
UpdateLevelFromFlowBalance(netFlowM3s);
|
|
Debug.WriteLine($"osHydTank {Nombre} - Nivel después de balance: {CurrentLevel:F6} m");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error en osHydTank {Nombre} ApplyHydraulicResults: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public List<HydraulicElementDefinition> GetHydraulicElements()
|
|
{
|
|
return new List<HydraulicElementDefinition>();
|
|
}
|
|
|
|
public void UpdateHydraulicProperties()
|
|
{
|
|
// Actualizar propiedades antes de la simulación
|
|
}
|
|
|
|
public void SetFlow(double flow) => CurrentFlow = flow;
|
|
public double GetFlow() => CurrentFlow;
|
|
public void SetPressure(double pressure) => CurrentPressure = pressure / 100000.0;
|
|
public double GetPressure() => CurrentPressure * 100000.0;
|
|
|
|
// ===== IMPLEMENTACIÓN DE IOSBASE =====
|
|
|
|
public void Start() { }
|
|
public void Inicializar(int valorInicial) { }
|
|
public void Disposing() { }
|
|
public int zIndex_fromFrames { get; set; } = 2;
|
|
public ZIndexEnum ZIndex_Base() => ZIndexEnum.Estaticos;
|
|
|
|
public override void CopyFrom(osBase source)
|
|
{
|
|
if (source is osHydTank sourceTank)
|
|
{
|
|
Elevation = sourceTank.Elevation;
|
|
MaxLevel = sourceTank.MaxLevel;
|
|
Diameter = sourceTank.Diameter;
|
|
FluidType = sourceTank.FluidType;
|
|
CurrentLevel = sourceTank.CurrentLevel;
|
|
}
|
|
base.CopyFrom(source);
|
|
}
|
|
|
|
// ===== MÉTODOS PRIVADOS AUXILIARES =====
|
|
|
|
private double CalculateNetFlowFromConnectedPipes(Dictionary<string, double> flows)
|
|
{
|
|
double netFlow = 0.0;
|
|
|
|
var mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel;
|
|
if (mainViewModel?.ObjetosSimulables == null) return netFlow;
|
|
|
|
try
|
|
{
|
|
var connectedPipes = mainViewModel.ObjetosSimulables
|
|
.OfType<osHydPipe>()
|
|
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
|
|
.ToList();
|
|
|
|
foreach (var pipe in connectedPipes)
|
|
{
|
|
var pipeElementName = $"PIPE_{pipe.Nombre}";
|
|
if (flows.ContainsKey(pipeElementName))
|
|
{
|
|
var pipeFlow = flows[pipeElementName];
|
|
|
|
if (pipe.Id_ComponenteB == Nombre)
|
|
{
|
|
netFlow += pipeFlow; // Entra al tanque
|
|
}
|
|
else if (pipe.Id_ComponenteA == Nombre)
|
|
{
|
|
netFlow -= pipeFlow; // Sale del tanque
|
|
}
|
|
}
|
|
}
|
|
|
|
return netFlow;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error calculating net flow for tank {Nombre}: {ex.Message}");
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
private void UpdateLevelFromFlowBalance(double netFlowM3s)
|
|
{
|
|
if (Math.Abs(netFlowM3s) < 1e-6) return;
|
|
|
|
var area = Math.PI * Math.Pow(_diameter / 2, 2); // m²
|
|
var deltaTime = 1.0; // segundos
|
|
var deltaLevel = (netFlowM3s * deltaTime) / area; // m
|
|
var newLevel = _currentLevel + deltaLevel;
|
|
CurrentLevel = Math.Max(0, Math.Min(_maxLevel, newLevel));
|
|
}
|
|
|
|
// ===== PROPIEDADES DE COMPATIBILIDAD =====
|
|
// Estas propiedades mapean las propiedades simplificadas a los nombres legacy
|
|
|
|
[JsonIgnore]
|
|
[Category("🔧 Compatibilidad")]
|
|
[DisplayName("Nivel actual (m)")]
|
|
[Description("Nivel actual del agua (compatibilidad legacy)")]
|
|
[ReadOnly(true)]
|
|
public double CurrentLevelM => CurrentLevel;
|
|
|
|
[JsonIgnore]
|
|
[Category("🔧 Compatibilidad")]
|
|
[DisplayName("Nivel máximo (m)")]
|
|
[Description("Nivel máximo del tanque (compatibilidad legacy)")]
|
|
[ReadOnly(true)]
|
|
public double MaxLevelM => MaxLevel;
|
|
|
|
[JsonIgnore]
|
|
[Category("🔧 Compatibilidad")]
|
|
[DisplayName("Nivel mínimo (m)")]
|
|
[Description("Nivel mínimo = 0 (siempre)")]
|
|
[ReadOnly(true)]
|
|
public double MinLevelM => 0.0;
|
|
|
|
[JsonIgnore]
|
|
[Category("🔧 Compatibilidad")]
|
|
[DisplayName("Área transversal")]
|
|
[Description("Área transversal del tanque (m²)")]
|
|
[ReadOnly(true)]
|
|
public double CrossSectionalArea => Math.PI * Math.Pow(_diameter / 2, 2);
|
|
|
|
[JsonIgnore]
|
|
[Category("🔧 Compatibilidad")]
|
|
[DisplayName("Balance de flujo")]
|
|
[Description("Balance de flujo actual (m³/s)")]
|
|
[ReadOnly(true)]
|
|
public double FlowBalance => CurrentFlow;
|
|
|
|
[JsonIgnore]
|
|
[Category("🔧 Compatibilidad")]
|
|
[DisplayName("Tipo de tanque")]
|
|
[Description("Tipo de tanque (siempre simplificado)")]
|
|
[ReadOnly(true)]
|
|
public string TankType => "Simplified";
|
|
|
|
[JsonIgnore]
|
|
[Category("🔧 Compatibilidad")]
|
|
[DisplayName("Descripción del fluido actual")]
|
|
[Description("Descripción del fluido en el tanque")]
|
|
[ReadOnly(true)]
|
|
public string CurrentFluidDescription => FluidDescription;
|
|
}
|
|
} |