diff --git a/HydraulicSimulator/TSNet/TSNetINPGenerator.cs b/HydraulicSimulator/TSNet/TSNetINPGenerator.cs index 9066c7c..d5f518d 100644 --- a/HydraulicSimulator/TSNet/TSNetINPGenerator.cs +++ b/HydraulicSimulator/TSNet/TSNetINPGenerator.cs @@ -120,7 +120,7 @@ namespace CtrEditor.HydraulicSimulator.TSNet foreach (var node in tankNodes) { var elevation = GetNodeElevation(node); - content.AppendLine($" {node.Name,-15}\t{elevation:F2} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t;"); + content.AppendLine($" {node.Name,-15}\t{elevation:F2} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t"); } content.AppendLine(); @@ -158,8 +158,9 @@ namespace CtrEditor.HydraulicSimulator.TSNet { foreach (var element in branch.Elements.OfType()) { - var id = $"PUMP{pumpId++}"; + var id = $"PUMP{pumpId}"; content.AppendLine($" {id,-15}\t{branch.N1,-15}\t{branch.N2,-15}\tHEAD CURVE{pumpId}"); + pumpId++; } } diff --git a/MainViewModel.cs b/MainViewModel.cs index 174e430..ea618db 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -2046,6 +2046,29 @@ namespace CtrEditor #endregion + #region Hydraulic Network Management + + /// + /// Invalidates the hydraulic network to force recalculation + /// + public void InvalidateHydraulicNetwork() + { + try + { + if (hydraulicSimulationManager != null) + { + hydraulicSimulationManager.InvalidateNetwork(); + Debug.WriteLine("Red hidráulica invalidada y marcada para recálculo"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error al invalidar red hidráulica: {ex.Message}"); + } + } + + #endregion + #region MCP Server Management /// diff --git a/ObjetosSim/HydraulicComponents/osHydPipe.cs b/ObjetosSim/HydraulicComponents/osHydPipe.cs index 931e637..6f791f4 100644 --- a/ObjetosSim/HydraulicComponents/osHydPipe.cs +++ b/ObjetosSim/HydraulicComponents/osHydPipe.cs @@ -3,36 +3,42 @@ 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 HydraulicSimulator.Models; using Newtonsoft.Json; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; using CommunityToolkit.Mvvm.ComponentModel; -using LibS7Adv; +using HydraulicSimulator.Models; namespace CtrEditor.ObjetosSim { /// - /// Tubería hidráulica que conecta componentes del sistema hidráulico + /// Tubería hidráulica simplificada para integración con TSNet /// public partial class osHydPipe : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase { + public static string NombreCategoria() => "Componentes Hidráulicos"; + + public static string NombreClase() => "Tubería Hidráulica"; + + // Private fields para TSNet + private double _length = 1.0; // m - longitud de la tubería + private double _diameter = 0.05; // m - diámetro interno + private double _roughness = 4.5e-5; // m - rugosidad del material + + // Estado actual (desde TSNet) + private double _currentFlow = 0.0; // m³/s - flujo actual + private double _pressureDrop = 0.0; // Pa - caída de presión + private double _velocity = 0.0; // m/s - velocidad del fluido + // TSNet Integration [JsonIgnore] - public TSNetPipeAdapter TSNetAdapter { get; private set; } - - // Properties - - private double _length = 1.0; // metros - private double _diameter = 0.05; // metros (50mm) - private double _roughness = 4.5e-5; // metros - rugosidad del acero comercial - private double _currentFlow = 0.0; - private double _pressureDrop = 0.0; - + public TSNetPipeAdapter? TSNetAdapter { get; private set; } + // Propiedades visuales [ObservableProperty] [property: Category("🎨 Apariencia")] @@ -57,34 +63,8 @@ namespace CtrEditor.ObjetosSim [property: Description("Ángulo de rotación de la tubería en grados")] [property: Name("Ángulo")] private float angulo = 0f; - - [Category("🔧 Tubería Hidráulica")] - [Description("Longitud de la tubería en metros")] - [Name("Longitud (m)")] - public double Length - { - get => _length; - set => SetProperty(ref _length, Math.Max(0.1, value)); - } - - [Category("🔧 Tubería Hidráulica")] - [Description("Diámetro interno de la tubería en metros")] - [Name("Diámetro (m)")] - public double Diameter - { - get => _diameter; - set => SetProperty(ref _diameter, Math.Max(0.001, value)); - } - - [Category("🔧 Tubería Hidráulica")] - [Description("Rugosidad del material en metros")] - [Name("Rugosidad (m)")] - public double Roughness - { - get => _roughness; - set => SetProperty(ref _roughness, Math.Max(1e-6, value)); - } + // Propiedades de conexión [ObservableProperty] [property: Category("🔗 Conexiones")] [property: Description("Primer componente hidráulico conectado")] @@ -99,515 +79,127 @@ namespace CtrEditor.ObjetosSim [property: ItemsSource(typeof(osBaseItemsSource))] private string id_ComponenteB = ""; - [Category("📊 Estado")] - [Description("Flujo actual a través de la tubería en m³/s")] - [Name("Flujo Actual (m³/s)")] + // Constructor + public osHydPipe() + { + try + { + Nombre = "Tubería Hidráulica"; + Ancho = 1.0f; + Alto = 0.05f; + Angulo = 0f; + Lock_movement = false; + + Debug.WriteLine($"osHydPipe Constructor completado correctamente"); + } + catch (Exception ex) + { + Debug.WriteLine($"Error en constructor osHydPipe: {ex.Message}"); + Nombre = "Tubería Hidráulica"; + Ancho = 1.0f; + Alto = 0.05f; + } + } + + // Propiedades de configuración de la tubería + [Category("🔧 Tubería Hidráulica")] + [DisplayName("Longitud")] + [Description("Longitud de la tubería en metros")] + public double Length + { + get => _length; + set + { + if (SetProperty(ref _length, Math.Max(0.1, value))) + { + InvalidateHydraulicNetwork(); + } + } + } + + [Category("🔧 Tubería Hidráulica")] + [DisplayName("Diámetro")] + [Description("Diámetro interno de la tubería en metros")] + public double Diameter + { + get => _diameter; + set + { + if (SetProperty(ref _diameter, Math.Max(0.001, value))) + { + InvalidateHydraulicNetwork(); + } + } + } + + [Category("🔧 Tubería Hidráulica")] + [DisplayName("Rugosidad")] + [Description("Rugosidad del material en metros")] + public double Roughness + { + get => _roughness; + set + { + if (SetProperty(ref _roughness, Math.Max(1e-6, value))) + { + InvalidateHydraulicNetwork(); + } + } + } + + // Estado actual (desde TSNet) + [Category("📊 Estado Actual")] + [DisplayName("Flujo actual")] + [Description("Flujo actual a través de la tubería (m³/s)")] [ReadOnly(true)] public double CurrentFlow { get => _currentFlow; - set => SetProperty(ref _currentFlow, value); + private set => SetProperty(ref _currentFlow, value); } - [Category("📊 Estado")] - [Description("Pérdida de presión en la tubería en Pa")] - [Name("Pérdida Presión (Pa)")] + [Category("📊 Estado Actual")] + [DisplayName("Caída de presión")] + [Description("Caída de presión en la tubería (Pa)")] [ReadOnly(true)] public double PressureDrop { get => _pressureDrop; - set => SetProperty(ref _pressureDrop, value); + private set => SetProperty(ref _pressureDrop, value); } - // Propiedades adicionales para información de fluido - private FluidProperties _currentFluid = new FluidProperties(FluidType.Air); - - [Category("🧪 Fluido Actual")] - [DisplayName("Tipo de fluido")] - [Description("Tipo de fluido que atraviesa la tubería")] + [Category("📊 Estado Actual")] + [DisplayName("Velocidad")] + [Description("Velocidad del fluido en la tubería (m/s)")] [ReadOnly(true)] - public FluidType CurrentFluidType - { - get => _currentFluid.Type; - private set - { - if (_currentFluid.Type != value) - { - _currentFluid.Type = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(CurrentFluidDescription)); - } - } - } - - [Category("🧪 Fluido Actual")] - [DisplayName("Descripción")] - [Description("Descripción completa del fluido actual")] - [ReadOnly(true)] - public string CurrentFluidDescription => _currentFluid.Description; - - [Category("🧪 Fluido Actual")] - [DisplayName("Temperatura (°C)")] - [Description("Temperatura del fluido en grados Celsius")] - [ReadOnly(true)] - [JsonProperty] - public double CurrentFluidTemperature => _currentFluid.Temperature; - - [Category("🧪 Fluido Actual")] - [DisplayName("Brix (%)")] - [Description("Concentración en grados Brix (para jarabes)")] - [ReadOnly(true)] - [JsonProperty] - public double CurrentFluidBrix => _currentFluid.Brix; - - [Category("🧪 Fluido Actual")] - [DisplayName("Concentración (%)")] - [Description("Concentración en porcentaje (para químicos)")] - [ReadOnly(true)] - [JsonProperty] - public double CurrentFluidConcentration => _currentFluid.Concentration; - - [Category("🧪 Fluido Actual")] - [DisplayName("Densidad (kg/L)")] - [Description("Densidad del fluido en kg/L")] - [ReadOnly(true)] - [JsonProperty] - public double CurrentFluidDensity => _currentFluid.Density; - - [Category("🧪 Fluido Actual")] - [DisplayName("Viscosidad (cP)")] - [Description("Viscosidad dinámica en centipoise")] - [ReadOnly(true)] - [JsonProperty] - public double CurrentFluidViscosity => _currentFluid.Viscosity; - - [Category("🧪 Fluido Actual")] - [DisplayName("Velocidad (m/s)")] - [Description("Velocidad del fluido en la tubería")] - [ReadOnly(true)] - [JsonProperty] public double FluidVelocity { - get - { - if (Diameter <= 0) return 0; - var area = Math.PI * Math.Pow(Diameter / 2, 2); // m² - return Math.Abs(CurrentFlow) / area; // m/s - } + get => _velocity; + private set => SetProperty(ref _velocity, value); } - [Category("🧪 Fluido Actual")] - [DisplayName("Número de Reynolds")] - [Description("Número de Reynolds (Re = ρ × v × D / μ) - Caracteriza el régimen de flujo")] - [ReadOnly(true)] - [JsonProperty] - public double ReynoldsNumber - { - get - { - if (CurrentFluidViscosity <= 0 || Diameter <= 0) return 0; - - // Re = (ρ × v × D) / μ - // ρ: densidad (kg/m³) - convertir de kg/L - // v: velocidad (m/s) - // D: diámetro (m) - // μ: viscosidad dinámica (Pa·s) - convertir de cP - - var density_kg_m3 = CurrentFluidDensity * 1000; // kg/L → kg/m³ - var viscosity_pa_s = CurrentFluidViscosity * 0.001; // cP → Pa·s - - return (density_kg_m3 * FluidVelocity * Diameter) / viscosity_pa_s; - } - } - - [Category("🧪 Fluido Actual")] - [DisplayName("Régimen de Flujo")] - [Description("Tipo de régimen basado en Reynolds (Laminar < 2300 < Turbulento)")] - [ReadOnly(true)] - [JsonProperty] - public string FlowRegime - { - get - { - var re = ReynoldsNumber; - return re switch - { - < 2300 => $"Laminar (Re = {re:F0})", - > 4000 => $"Turbulento (Re = {re:F0})", - _ => $"Transición (Re = {re:F0})" - }; - } - } - - // Método para exponer propiedades detalladas en serialización JSON - [JsonProperty("DetailedFluidProperties")] - [Browsable(false)] - public Dictionary DetailedFluidProperties => new Dictionary - { - ["CurrentFluidTemperature"] = CurrentFluidTemperature, - ["CurrentFluidBrix"] = CurrentFluidBrix, - ["CurrentFluidConcentration"] = CurrentFluidConcentration, - ["CurrentFluidDensity"] = CurrentFluidDensity, - ["CurrentFluidViscosity"] = CurrentFluidViscosity, - ["FluidVelocity"] = FluidVelocity, - ["ReynoldsNumber"] = ReynoldsNumber, - ["FlowRegime"] = FlowRegime - }; - - [Category("📊 Estado")] + [Category("📊 Estado Actual")] [DisplayName("Flujo (L/min)")] - [Description("Flujo actual en litros por minuto")] + [Description("Flujo actual en L/min")] [ReadOnly(true)] - public double CurrentFlowLMin => Math.Abs(CurrentFlow) * 60000.0; // Conversión de m³/s a L/min - - [Category("📊 Estado")] - [DisplayName("Dirección")] - [Description("Dirección del flujo")] + public double CurrentFlowLMin => Math.Abs(CurrentFlow) * 60000.0; // m³/s a L/min + + [Category("📊 Estado Actual")] + [DisplayName("Caída presión (bar)")] + [Description("Caída de presión en bar")] [ReadOnly(true)] - public string FlowDirection => CurrentFlow > 0 ? $"{Id_ComponenteA} → {Id_ComponenteB}" : - CurrentFlow < 0 ? $"{Id_ComponenteB} → {Id_ComponenteA}" : "Sin flujo"; - - [JsonIgnore] - public SolidColorBrush FluidColor - { - get - { - try - { - var colorHex = _currentFluid.Color; - return new SolidColorBrush((Color)ColorConverter.ConvertFromString(colorHex)); - } - catch - { - return Brushes.Gray; - } - } - } + public double PressureDropBar => PressureDrop / 100000.0; // Pa a bar - private string pipeId = ""; - public string PipeId - { - get => pipeId; - set => SetProperty(ref pipeId, value); - } - - - - // Component References - - [JsonIgnore] - private IHydraulicComponent ComponenteA = null; - - [JsonIgnore] - private IHydraulicComponent ComponenteB = null; - - [JsonIgnore] - private PropertyChangedEventHandler componenteAPropertyChangedHandler; - - [JsonIgnore] - private PropertyChangedEventHandler componenteBPropertyChangedHandler; - - [JsonIgnore] - public Action ActualizarTamaño { get; set; } - - - - // IHydraulicPipe Implementation - - // Ya implementadas las propiedades Length, Diameter, Roughness arriba - - - - // IHydraulicComponent Implementation - - public bool HasHydraulicComponents => true; - - public List GetHydraulicNodes() - { - var nodes = new List(); - - // Las tuberías conectan componentes existentes, no crean nodos propios - // Los nodos son creados por los componentes conectados (tanques, bombas, etc.) - // La tubería solo actúa como elemento que conecta nodos existentes - - return nodes; - } - - public List GetHydraulicElements() - { - var elements = new List(); - - // Solo crear elemento si ambos componentes están conectados - if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB)) - { - // Obtener los nombres de nodos correctos para cada componente - string fromNodeName = GetNodeNameForComponent(Id_ComponenteA, true); // true = es el nodo origen - string toNodeName = GetNodeNameForComponent(Id_ComponenteB, false); // false = es el nodo destino - - if (!string.IsNullOrEmpty(fromNodeName) && !string.IsNullOrEmpty(toNodeName)) - { - // Crear el elemento Pipe según la documentación - var pipeElement = new Pipe(Length, Diameter, Roughness); - - elements.Add(new HydraulicElementDefinition( - name: $"{Nombre}_Pipe", - fromNode: fromNodeName, - toNode: toNodeName, - element: pipeElement, - description: $"Tubería {Nombre} - L:{Length:F2}m, D:{Diameter*1000:F0}mm ({fromNodeName} → {toNodeName})" - )); - } - } - - return elements; - } - - /// - /// Obtiene el nombre del nodo correcto para un componente dado - /// - /// Nombre del componente - /// True si es el nodo origen de la tubería, False si es destino - /// Nombre del nodo o string vacío si no se encuentra - private string GetNodeNameForComponent(string componentName, bool isSource) - { - if (string.IsNullOrEmpty(componentName) || _mainViewModel == null) - return ""; - - // Buscar el componente hidráulico - var component = _mainViewModel.ObjetosSimulables - .FirstOrDefault(s => s is IHydraulicComponent comp && - ((osBase)comp).Nombre == componentName) as IHydraulicComponent; - - if (component == null) - return ""; - - // Obtener los nodos que crea este componente - var nodes = component.GetHydraulicNodes(); - if (nodes == null || !nodes.Any()) - return ""; - - // Determinar qué nodo usar según el tipo de componente - var componentType = component.GetType(); - - // Para tanques: siempre usar su único nodo - if (componentType.Name.Contains("Tank")) - { - return nodes.FirstOrDefault()?.Name ?? ""; - } - - // Para bombas: usar nodo de entrada o salida según corresponda - if (componentType.Name.Contains("Pump")) - { - if (isSource) - { - // Si la tubería sale desde este componente, usar el nodo de salida de la bomba - return nodes.FirstOrDefault(n => n.Name.EndsWith("_Out"))?.Name ?? ""; - } - else - { - // Si la tubería llega a este componente, usar el nodo de entrada de la bomba - return nodes.FirstOrDefault(n => n.Name.EndsWith("_In"))?.Name ?? ""; - } - } - - // Para otros tipos de componentes, usar el primer nodo disponible - return nodes.FirstOrDefault()?.Name ?? ""; - } - - public void UpdateHydraulicProperties() - { - // Actualizar propiedades antes de la simulación si es necesario - // Por ejemplo, cambios dinámicos en diámetro o rugosidad - } - - public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) - { - try - { - // Aplicar resultados a través del TSNet Adapter - if (TSNetAdapter?.Results != null) - { - // Buscar flujo usando el nombre del elemento de TSNet - string pipeElementName = $"{Nombre}_Pipe"; - - if (flows.ContainsKey(pipeElementName)) - { - var flowM3s = flows[pipeElementName]; - CurrentFlow = flowM3s; - TSNetAdapter.Results.CalculatedFlowM3s = flowM3s; - - Debug.WriteLine($"Pipe {Nombre}: TSNet Flow={flowM3s:F6} m³/s ({CurrentFlowLMin:F2} L/min)"); - } - else - { - // Intentar claves alternativas basadas en componentes conectados - var alternativeKeys = new[] - { - $"{Id_ComponenteA}_{Id_ComponenteB}_Pipe", - $"Pipe_{Id_ComponenteA}_{Id_ComponenteB}", - TSNetAdapter.PipeId, - $"Branch_{Nombre}" - }; - - bool flowFound = false; - foreach (string altKey in alternativeKeys) - { - if (flows.ContainsKey(altKey)) - { - var flowM3s = flows[altKey]; - CurrentFlow = flowM3s; - TSNetAdapter.Results.CalculatedFlowM3s = flowM3s; - flowFound = true; - break; - } - } - - if (!flowFound) - { - CurrentFlow = 0.0; - TSNetAdapter.Results.CalculatedFlowM3s = 0.0; - } - } - - // Calcular pérdida de presión entre nodos conectados - if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB)) - { - var fromNodeName = GetNodeNameForComponent(Id_ComponenteA, true); - var toNodeName = GetNodeNameForComponent(Id_ComponenteB, false); - - if (pressures.ContainsKey(fromNodeName) && pressures.ContainsKey(toNodeName)) - { - var pressureA = pressures[fromNodeName]; - var pressureB = pressures[toNodeName]; - PressureDrop = pressureA - pressureB; - TSNetAdapter.Results.PressureDropBar = PressureDrop; - - Debug.WriteLine($"Pipe {Nombre}: Pressure drop={PressureDrop:F0} Pa ({fromNodeName} → {toNodeName})"); - } - } - - // Actualizar resultados del adapter - TSNetAdapter.Results.Timestamp = DateTime.Now; - TSNetAdapter.Results.FlowStatus = $"Flow: {CurrentFlowLMin:F1}L/min, Drop: {PressureDrop:F1}Pa"; - - // Actualizar fluido desde componente fuente - UpdateFluidFromSource(); - } - - // Notificar cambios para UI - OnPropertyChanged(nameof(CurrentFlow)); - OnPropertyChanged(nameof(CurrentFlowLMin)); - OnPropertyChanged(nameof(PressureDrop)); - OnPropertyChanged(nameof(FlowDirection)); - OnPropertyChanged(nameof(FluidVelocity)); - OnPropertyChanged(nameof(ReynoldsNumber)); - OnPropertyChanged(nameof(FlowRegime)); - - // Debug periódico - if (Environment.TickCount % 2000 < 50) // Cada ~2 segundos - { - Debug.WriteLine($"Pipe {Nombre}: Flow={CurrentFlowLMin:F1}L/min, Drop={PressureDrop:F1}Pa, {FlowDirection}"); - } - } - catch (Exception ex) - { - Debug.WriteLine($"Error en Pipe {Nombre} ApplyHydraulicResults: {ex.Message}"); - } - } - - - - // IHydraulicFlowReceiver Implementation - - public void SetFlow(double flow) - { - CurrentFlow = flow; - } - - public double GetFlow() - { - return CurrentFlow; - } - - - - // IHydraulicPressureReceiver Implementation - - public void SetPressure(double pressure) - { - // Para tuberías, la presión se maneja a través de los nodos - } - - public double GetPressure() - { - return PressureDrop; - } - - - - // Connection Management - - partial void OnId_ComponenteAChanged(string value) - { - if (ComponenteA != null && componenteAPropertyChangedHandler != null) - ((INotifyPropertyChanged)ComponenteA).PropertyChanged -= componenteAPropertyChangedHandler; - - if (_mainViewModel != null && !string.IsNullOrEmpty(value)) - { - ComponenteA = (IHydraulicComponent)_mainViewModel.ObjetosSimulables - .FirstOrDefault(s => s is IHydraulicComponent comp && - ((osBase)comp).Nombre == value); - - if (ComponenteA != null) - { - componenteAPropertyChangedHandler = (sender, e) => - { - if (e.PropertyName == nameof(osBase.Nombre)) - { - Id_ComponenteA = ((osBase)sender).Nombre; - } - }; - ((INotifyPropertyChanged)ComponenteA).PropertyChanged += componenteAPropertyChangedHandler; - } - } - } - - partial void OnId_ComponenteBChanged(string value) - { - if (ComponenteB != null && componenteBPropertyChangedHandler != null) - ((INotifyPropertyChanged)ComponenteB).PropertyChanged -= componenteBPropertyChangedHandler; - - if (_mainViewModel != null && !string.IsNullOrEmpty(value)) - { - ComponenteB = (IHydraulicComponent)_mainViewModel.ObjetosSimulables - .FirstOrDefault(s => s is IHydraulicComponent comp && - ((osBase)comp).Nombre == value); - - if (ComponenteB != null) - { - componenteBPropertyChangedHandler = (sender, e) => - { - if (e.PropertyName == nameof(osBase.Nombre)) - { - Id_ComponenteB = ((osBase)sender).Nombre; - } - }; - ((INotifyPropertyChanged)ComponenteB).PropertyChanged += componenteBPropertyChangedHandler; - } - } - } - - - - // osBase Implementation - - public static string NombreCategoria() => "Componentes Hidráulicos"; - - public static string NombreClase() => "Tubería Hidráulica"; + [Category("📊 Estado Actual")] + [DisplayName("Tiene flujo")] + [Description("Indica si hay flujo en la tubería")] + [ReadOnly(true)] + public bool HasFlow => Math.Abs(CurrentFlow) > 1e-6; + // Métodos básicos private string nombre = NombreClase(); - [Category("Identificación")] + [Category("🏷️ Identificación")] [Description("Nombre identificativo del objeto")] [Name("Nombre")] public override string Nombre @@ -616,199 +208,225 @@ namespace CtrEditor.ObjetosSim set => SetProperty(ref nombre, value); } - public override void AltoChanged(float value) + public override void OnMove(float LeftPixels, float TopPixels) { - ActualizarGeometrias(); + // Movimiento básico } - public override void AnchoChanged(float value) + public override void OnResize(float Delta_Width, float Delta_Height) { - ActualizarGeometrias(); - } - - public void Start() - { - // Las tuberías no participan en la simulación física Bepu - // Solo en el sistema hidráulico + Ancho += Delta_Width; + Alto += Delta_Height; } public override void UpdateGeometryStart() { - // En el nuevo sistema unificado, el HydraulicSimulationManager se encarga - // de registrar automáticamente los objetos que implementan IHydraulicComponent - // No necesitamos crear objetos simHydraulic* separados - Debug.WriteLine($"[DEBUG] {Nombre}: UpdateGeometryStart() - ComponenteA: '{Id_ComponenteA}', ComponenteB: '{Id_ComponenteB}'"); - ActualizarGeometrias(); - } - - public override void UpdateGeometryStep() - { - // Los objetos hidráulicos actualizan sus resultados - // a través de ApplyHydraulicResults() desde HydraulicSimulationManager - } - - // Método para actualizar fluido desde componente fuente - public void UpdateFluidFromSource() - { - if (_mainViewModel?.ObjetosSimulables == null) return; - - // Determinar componente fuente basado en dirección del flujo - string sourceComponentName = CurrentFlow >= 0 ? Id_ComponenteA : Id_ComponenteB; - - var sourceComponent = _mainViewModel.ObjetosSimulables - .OfType() - .FirstOrDefault(c => c is osBase osb && osb.Nombre == sourceComponentName); - - if (sourceComponent is osHydTank tank) - { - var sourceFluid = tank.CurrentOutputFluid; - if (sourceFluid != null) - { - _currentFluid = sourceFluid.Clone(); - OnPropertyChanged(nameof(CurrentFluidType)); - OnPropertyChanged(nameof(CurrentFluidDescription)); - OnPropertyChanged(nameof(FluidColor)); - } - } - - // Actualizar color visual - if (Math.Abs(CurrentFlow) > 1e-6) // Hay flujo - { - ColorButton_oculto = FluidColor; - } - else - { - ColorButton_oculto = Brushes.Gray; // Sin flujo - } + // 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 valores provienen de TSNet + OnPropertyChanged(nameof(CurrentFlowLMin)); + OnPropertyChanged(nameof(PressureDropBar)); + OnPropertyChanged(nameof(HasFlow)); + UpdatePipeColor(); + } + + private void UpdatePipeColor() + { + // Cambiar color según el flujo + if (HasFlow) + { + var flowPercent = Math.Min(1.0, CurrentFlowLMin / 100.0); // normalizar a 100 L/min + var intensity = (byte)(255 * flowPercent); + ColorButton_oculto = new SolidColorBrush(Color.FromRgb(intensity, (byte)(255 - intensity), 0)); + } + else + { + ColorButton_oculto = 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) + { + // La caída de presión se calcula en TSNet + } + + public void ApplyHydraulicFlow(double flowM3s) + { + CurrentFlow = flowM3s; + + // Calcular velocidad + if (Diameter > 0) + { + var area = Math.PI * Math.Pow(Diameter / 2, 2); // m² + FluidVelocity = Math.Abs(CurrentFlow) / area; // m/s + } + } + + public void UpdateHydraulicState(double timeSeconds) + { + // TSNet maneja el estado + } + + // Métodos para TSNet + public void UpdateFromTSNetResults(double flow, double pressureDrop) + { + CurrentFlow = flow; + PressureDrop = pressureDrop; + + // Calcular velocidad + if (Diameter > 0) + { + var area = Math.PI * Math.Pow(Diameter / 2, 2); // m² + FluidVelocity = Math.Abs(CurrentFlow) / area; // m/s + } + } + + // Crear adapter para TSNet + public TSNetPipeAdapter CreateTSNetAdapter() + { + TSNetAdapter = new TSNetPipeAdapter(this); + return TSNetAdapter; + } + + // Implementación de IHydraulicComponent + [JsonIgnore] + public bool HasHydraulicComponents => true; + + public List GetHydraulicNodes() + { + // Las tuberías no crean nodos propios, se conectan entre nodos existentes + return new List(); + } + + public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) + { try { - // Los resultados de TSNet ya están aplicados en ApplyHydraulicResults() - // Solo actualizamos propiedades derivadas y visuales - - // Actualizar propiedades del fluido cada ciclo - UpdateFluidFromSource(); - - // Actualizar propiedades de UI calculadas - OnPropertyChanged(nameof(CurrentFlowLMin)); - OnPropertyChanged(nameof(FlowDirection)); - OnPropertyChanged(nameof(FluidVelocity)); - OnPropertyChanged(nameof(ReynoldsNumber)); - OnPropertyChanged(nameof(FlowRegime)); - - // Actualizar color visual basado en fluido - UpdatePipeColorFromFluid(); - - // Debug periódico cada 5 segundos - if (Environment.TickCount % 5000 < elapsedMilliseconds) + // Buscar resultados de esta tubería en TSNet + var pipeElementName = $"PIPE_{Nombre}"; + if (flows.ContainsKey(pipeElementName)) { - Debug.WriteLine($"Pipe {Nombre}: Flow={CurrentFlowLMin:F1}L/min, Drop={PressureDrop:F1}Pa, {FlowDirection}"); + var pipeFlow = flows[pipeElementName]; + ApplyHydraulicFlow(pipeFlow); + } + + // Calcular caída de presión si tenemos datos de los nodos conectados + if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB)) + { + if (pressures.ContainsKey(Id_ComponenteA) && pressures.ContainsKey(Id_ComponenteB)) + { + var pressureA = pressures[Id_ComponenteA]; + var pressureB = pressures[Id_ComponenteB]; + PressureDrop = Math.Abs(pressureA - pressureB); + } } } catch (Exception ex) { - Debug.WriteLine($"Error in Pipe {Nombre} UpdateControl: {ex.Message}"); + Debug.WriteLine($"Error en Pipe {Nombre} ApplyHydraulicResults: {ex.Message}"); } } - public override void ucLoaded() + public List GetHydraulicElements() { - // En el nuevo sistema unificado, no necesitamos crear objetos separados - base.ucLoaded(); - } - - public override void ucUnLoaded() - { - // En el nuevo sistema unificado, el HydraulicSimulationManager - // maneja automáticamente el registro/desregistro de objetos - base.ucUnLoaded(); - } - - public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) - { - // Las tuberías no tienen control PLC directo - } - - private void ActualizarGeometrias() - { - try + var elements = new List(); + + // Verificar que la tubería esté conectada a dos componentes + if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB)) { - // Actualizar geometría visual - if (this.ActualizarTamaño != null) - this.ActualizarTamaño(Ancho, Alto); + // Crear elemento pipe con propiedades correctas + var pipeElement = new Pipe(Length, Diameter, Roughness); + + elements.Add(new HydraulicElementDefinition( + $"PIPE_{Nombre}", + Id_ComponenteA, + Id_ComponenteB, + pipeElement, + $"Tubería - L:{Length:F1}m, D:{Diameter*1000:F0}mm, Rough:{Roughness*1000:F2}mm" + )); } - catch (Exception ex) + + return elements; + } + + public void UpdateHydraulicProperties() + { + // Actualizar propiedades antes de la simulación + // Validar conexiones + if (string.IsNullOrEmpty(Id_ComponenteA) || string.IsNullOrEmpty(Id_ComponenteB)) { - System.Diagnostics.Debug.WriteLine($"Error actualizando geometría: {ex.Message}"); + Debug.WriteLine($"Warning: Tubería {Nombre} no está completamente conectada"); } } + // Implementación de IHydraulicFlowReceiver + public void SetFlow(double flow) + { + ApplyHydraulicFlow(flow); + } + + public double GetFlow() + { + return CurrentFlow; + } + + // Implementación de IHydraulicPressureReceiver + public void SetPressure(double pressure) + { + // Las tuberías no tienen presión propia, solo caída de presión + } + + public double GetPressure() + { + return PressureDrop; + } + + // Implementación de IosBase + public void Start() + { + // Inicialización de la tubería para la simulación + } + public void Inicializar(int valorInicial) { - PipeId = $"Pipe_{Nombre}_{valorInicial}"; - OnId_ComponenteAChanged(Id_ComponenteA); - OnId_ComponenteBChanged(Id_ComponenteB); + // Inicialización con valor inicial } public void Disposing() { - if (ComponenteA != null && componenteAPropertyChangedHandler != null) - ((INotifyPropertyChanged)ComponenteA).PropertyChanged -= componenteAPropertyChangedHandler; - - if (ComponenteB != null && componenteBPropertyChangedHandler != null) - ((INotifyPropertyChanged)ComponenteB).PropertyChanged -= componenteBPropertyChangedHandler; + // Limpieza si es necesaria } + public int zIndex_fromFrames { get; set; } = 1; - - /// - /// Actualiza el color de la tubería basado en el fluido que transporta - /// - private void UpdatePipeColorFromFluid() + public ZIndexEnum ZIndex_Base() { - try - { - if (Math.Abs(CurrentFlow) < 1e-6) - { - ColorButton_oculto = Brushes.Gray; // Sin flujo - return; - } - - // Color basado en el fluido actual - var fluidColor = FluidColor; - if (fluidColor != null) - { - ColorButton_oculto = fluidColor; - } - else - { - // Color por defecto según dirección de flujo - ColorButton_oculto = CurrentFlow > 0 ? Brushes.Blue : Brushes.Red; - } - } - catch - { - ColorButton_oculto = Brushes.Gray; // Color por defecto en caso de error - } + return ZIndexEnum.Estaticos; } - // Constructor - - public osHydPipe() + public override void CopyFrom(osBase source) { - PipeId = Guid.NewGuid().ToString(); - IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros - - // No inicializar TSNet Adapter aquí - se creará cuando se registre con TSNetSimulationManager - // TSNetAdapter = new TSNetPipeAdapter(this); + if (source is osHydPipe sourcePipe) + { + Length = sourcePipe.Length; + Diameter = sourcePipe.Diameter; + Roughness = sourcePipe.Roughness; + Id_ComponenteA = sourcePipe.Id_ComponenteA; + Id_ComponenteB = sourcePipe.Id_ComponenteB; + } + base.CopyFrom(source); } - - } } diff --git a/ObjetosSim/HydraulicComponents/osHydPump.cs b/ObjetosSim/HydraulicComponents/osHydPump.cs index cd290f7..de0ec69 100644 --- a/ObjetosSim/HydraulicComponents/osHydPump.cs +++ b/ObjetosSim/HydraulicComponents/osHydPump.cs @@ -3,39 +3,56 @@ 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 HydraulicSimulator.Models; using Newtonsoft.Json; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; using CommunityToolkit.Mvvm.ComponentModel; +using HydraulicSimulator.Models; namespace CtrEditor.ObjetosSim { /// - /// Bomba hidráulica que implementa las interfaces del simulador hidráulico + /// Dirección de funcionamiento de la bomba + /// + public enum PumpDirection + { + Forward = 1, + Reverse = -1 + } + + /// + /// Bomba hidráulica simplificada para integración con TSNet /// public partial class osHydPump : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase { public static string NombreCategoria() => "Componentes Hidráulicos"; - public static string NombreClase() - { - return "Bomba Hidráulica"; - } + public static string NombreClase() => "Bomba Hidráulica"; + + // Private fields para TSNet + private double _pumpHead = 50.0; // m - cabeza de la bomba + private double _maxFlow = 0.01; // m³/s - flujo máximo + private double _efficiency = 0.85; // eficiencia de la bomba + private bool _isRunning = true; // bomba funcionando + + // Estado actual (desde TSNet) + private double _currentFlow = 0.0; // m³/s - flujo actual + private double _currentHead = 0.0; // m - cabeza actual + private double _currentPressure = 0.0; // Pa - presión actual // TSNet Integration [JsonIgnore] - public TSNetPumpAdapter TSNetAdapter { get; private set; } - - // Properties + public TSNetPumpAdapter? TSNetAdapter { get; private set; } + // Propiedades visuales [ObservableProperty] [property: JsonIgnore] - [property: Category("Apariencia")] + [property: Category("🎨 Apariencia")] [property: Description("Imagen visual")] [property: Name("Imagen")] public ImageSource imageSource_oculta; @@ -46,26 +63,37 @@ namespace CtrEditor.ObjetosSim [property: Name("Tamaño")] public float tamano; - public override void OnResize(float Delta_Width, float Delta_Height) - { - Tamano += Delta_Width + Delta_Height; - } - - private double _pumpHead = 50.0; // metros - private double _maxFlow = 0.01; // m³/s (36 m³/h) - private double _speedRatio = 1.0; - private bool _isRunning = true; - private int _pumpDirection = 1; - private double _currentFlow = 0.0; - private double _currentPressure = 0.0; - - // Propiedades visuales específicas de la bomba [ObservableProperty] [property: Category("🎨 Apariencia")] [property: Description("Color visual de la bomba")] [property: Name("Color")] private Brush colorButton_oculto = Brushes.Blue; - + + // Constructor + public osHydPump() + { + try + { + Nombre = "Bomba Hidráulica"; + Tamano = 1.0f; + Ancho = 0.25f; + Alto = 0.25f; + Angulo = 0f; + Lock_movement = false; + + UpdatePumpImage(); + + Debug.WriteLine($"osHydPump Constructor completado correctamente"); + } + catch (Exception ex) + { + Debug.WriteLine($"Error en constructor osHydPump: {ex.Message}"); + Nombre = "Bomba Hidráulica"; + Tamano = 1.0f; + } + } + + // Propiedades de configuración de la bomba [Category("🔧 Bomba Hidráulica")] [DisplayName("Cabeza de bomba")] [Description("Cabeza de la bomba a caudal cero (m)")] @@ -82,8 +110,8 @@ namespace CtrEditor.ObjetosSim } [Category("🔧 Bomba Hidráulica")] - [DisplayName("Caudal máximo")] - [Description("Caudal máximo de la bomba (m³/s)")] + [DisplayName("Flujo máximo")] + [Description("Flujo máximo de la bomba (m³/s)")] public double MaxFlow { get => _maxFlow; @@ -98,16 +126,19 @@ namespace CtrEditor.ObjetosSim } } } - + [Category("🔧 Bomba Hidráulica")] - [DisplayName("Velocidad relativa")] - [Description("Velocidad relativa de la bomba (0.0 a 1.0)")] - public double SpeedRatio + [DisplayName("Eficiencia")] + [Description("Eficiencia de la bomba (0.0 a 1.0)")] + public double Efficiency { - get => _speedRatio; + get => _efficiency; set { - SetProperty(ref _speedRatio, Math.Max(0.0, Math.Min(1.0, value))); + if (SetProperty(ref _efficiency, Math.Max(0.1, Math.Min(1.0, value)))) + { + InvalidateHydraulicNetwork(); + } } } @@ -122,182 +153,87 @@ namespace CtrEditor.ObjetosSim if (SetProperty(ref _isRunning, value)) { UpdatePumpImage(); - } - } - } - - private void UpdatePumpImage() - { - if (IsRunning) - ImageSource_oculta = ImageFromPath("/imagenes/pump_run.png"); - else - ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png"); - } - - [Category("🔧 Bomba Hidráulica")] - [DisplayName("Dirección")] - [Description("Dirección de la bomba (+1 o -1)")] - [ItemsSource(typeof(PumpDirectionItemsSource))] - public int PumpDirection - { - get => _pumpDirection; - set - { - if (SetProperty(ref _pumpDirection, value == -1 ? -1 : 1)) - { InvalidateHydraulicNetwork(); } } } - + + // Estado actual (desde TSNet) [Category("📊 Estado Actual")] - [DisplayName("Caudal actual")] - [Description("Caudal actual de la bomba (m³/s)")] - [JsonIgnore] + [DisplayName("Flujo actual")] + [Description("Flujo actual de la bomba (m³/s)")] + [ReadOnly(true)] public double CurrentFlow { get => _currentFlow; private set => SetProperty(ref _currentFlow, value); } + [Category("📊 Estado Actual")] + [DisplayName("Cabeza actual")] + [Description("Cabeza actual de la bomba (m)")] + [ReadOnly(true)] + public double CurrentHead + { + get => _currentHead; + private set => SetProperty(ref _currentHead, value); + } + [Category("📊 Estado Actual")] [DisplayName("Presión actual")] - [Description("Presión actual en la bomba (bar)")] - [JsonIgnore] + [Description("Presión actual en la bomba (Pa)")] + [ReadOnly(true)] 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; - [Category("📊 Estado Actual")] [DisplayName("Presión (bar)")] [Description("Presión actual en la bomba (bar)")] - [JsonIgnore] - public double CurrentPressureBar => CurrentPressure; // Ya está en bar internamente + [ReadOnly(true)] + public double CurrentPressureBar => CurrentPressure / 100000.0; [Category("📊 Estado Actual")] - [DisplayName("Caudal (L/min)")] - [Description("Caudal actual en L/min")] - [JsonIgnore] + [DisplayName("Flujo (L/min)")] + [Description("Flujo actual en L/min")] + [ReadOnly(true)] public double CurrentFlowLMin => CurrentFlow * 60000.0; // m³/s a L/min [Category("📊 Estado Actual")] [DisplayName("Tiene Flujo")] [Description("Indica si hay flujo en la bomba")] - [JsonIgnore] + [ReadOnly(true)] public bool HasFlow => Math.Abs(CurrentFlow) > 1e-6; - - [Category("📊 Estado Actual")] - [DisplayName("Estado")] - [Description("Estado actual de la bomba")] - [JsonIgnore] - public string PumpStatus + + /// + /// Relación de velocidad de la bomba + /// + [Category("⚙️ Configuración")] + [DisplayName("Relación de velocidad")] + [Description("Relación de velocidad de la bomba (0.0 - 1.0)")] + public double SpeedRatio { - get - { - if (!IsRunning) return "Bomba detenida"; - if (HasFlow) return "✅ Bombeando"; - return "⚠️ Sin flujo"; - } + get => _speedRatio; + set => SetProperty(ref _speedRatio, Math.Max(0.0, Math.Min(1.0, value))); } + private double _speedRatio = 1.0; - [Category("📊 Estado Actual")] - [DisplayName("Estado Detallado")] - [Description("Información detallada del estado operacional")] - [JsonIgnore] - public string DetailedStatus + /// + /// Dirección de la bomba + /// + [Category("⚙️ Configuración")] + [DisplayName("Dirección bomba")] + [Description("Dirección de funcionamiento de la bomba")] + public PumpDirection PumpDirection { - get - { - var flow = CurrentFlowLMin; - var pressure = CurrentPressureBar; - return $"Flujo: {flow:F1} L/min | Presión: {pressure:F1} bar | {PumpStatus}"; - } + get => _pumpDirection; + set => SetProperty(ref _pumpDirection, value); } + private PumpDirection _pumpDirection = PumpDirection.Forward; - // Propiedades de fluido actual - private FluidProperties _currentFluid = new FluidProperties(FluidType.Air); - - [Category("🧪 Fluido Actual")] - [DisplayName("Tipo de fluido")] - [Description("Tipo de fluido que está bombeando")] - [ReadOnly(true)] - public FluidType CurrentFluidType => _currentFluid.Type; - - [Category("🧪 Fluido Actual")] - [DisplayName("Descripción")] - [Description("Descripción del fluido que se está bombeando")] - [ReadOnly(true)] - public string CurrentFluidDescription => _currentFluid.Description; - - [Category("📊 Estado Actual")] - [DisplayName("Factor Viscosidad")] - [Description("Factor de eficiencia debido a la viscosidad del fluido")] - [ReadOnly(true)] - public double ViscosityEffect - { - get - { - // Factor basado en viscosidad del fluido vs agua - var waterViscosity = 0.001; // Pa·s - var currentViscosity = _currentFluid.Viscosity; - return Math.Max(0.1, Math.Min(1.0, waterViscosity / currentViscosity)); - } - } - - [Category("📊 Estado Actual")] - [DisplayName("Flujo Efectivo (L/min)")] - [Description("Flujo real ajustado por viscosidad")] - [ReadOnly(true)] - public double EffectiveFlowLMin => CurrentFlowLMin * ViscosityEffect; - - [Category("📊 Estado Actual")] - [DisplayName("Temperatura (°C)")] - [Description("Temperatura del fluido bombeado")] - [ReadOnly(true)] - public double FluidTemperature => _currentFluid.Temperature; - - [Category("📊 Estado Actual")] - [DisplayName("Densidad (kg/L)")] - [Description("Densidad del fluido bombeado")] - [ReadOnly(true)] - public double FluidDensity => _currentFluid.Density; - - [Category("📊 Estado Actual")] - [DisplayName("Brix (%)")] - [Description("Concentración en grados Brix (para jarabes)")] - [ReadOnly(true)] - public double FluidBrix => _currentFluid.Brix; - - [Category("📊 Estado Actual")] - [DisplayName("Concentración (%)")] - [Description("Concentración en porcentaje (para químicos)")] - [ReadOnly(true)] - public double FluidConcentration => _currentFluid.Concentration; - - [Category("📊 Estado Actual")] - [DisplayName("Viscosidad (cP)")] - [Description("Viscosidad dinámica en centipoise")] - [ReadOnly(true)] - public double FluidViscosity => _currentFluid.Viscosity; - - [Category("📊 Estado Actual")] - [DisplayName("Descripción del Fluido")] - [Description("Descripción completa del fluido")] - [ReadOnly(true)] - public string FluidDescription => _currentFluid.Description; - - - - // Constructor y Métodos Base - + // Métodos básicos private string nombre = NombreClase(); [Category("🏷️ Identificación")] @@ -309,380 +245,112 @@ namespace CtrEditor.ObjetosSim set => SetProperty(ref nombre, value); } - public osHydPump() + public override void OnMove(float LeftPixels, float TopPixels) { - Nombre = "Bomba Hidráulica"; - 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; - ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png"); - - // No inicializar TSNet Adapter aquí - se creará cuando se registre con TSNetSimulationManager - // TSNetAdapter = new TSNetPumpAdapter(this); + // Movimiento básico + } + + public override void OnResize(float Delta_Width, float Delta_Height) + { + Tamano += Delta_Width + Delta_Height; } public override void UpdateGeometryStart() { - // En el nuevo sistema unificado, el HydraulicSimulationManager se encarga - // de registrar automáticamente los objetos que implementan IHydraulicComponent - // No necesitamos crear objetos simHydraulic* separados - } - - public override void UpdateGeometryStep() - { - // Los objetos hidráulicos actualizan sus resultados - // a través de ApplyHydraulicResults() desde HydraulicSimulationManager - } - - // Método para actualizar fluido desde tanque de succión - public void UpdateFluidFromSuction() - { - if (_mainViewModel?.ObjetosSimulables == null) return; - - try - { - // Solo actualizar fluido si hay flujo - if (HasFlow) - { - // Buscar el componente conectado en la succión - var suctionComponent = FindSuctionComponent(); - - if (suctionComponent is osHydTank tank) - { - var sourceFluid = tank.CurrentOutputFluid; - if (sourceFluid != null) - { - _currentFluid = sourceFluid.Clone(); - OnPropertyChanged(nameof(CurrentFluidType)); - OnPropertyChanged(nameof(CurrentFluidDescription)); - OnPropertyChanged(nameof(ViscosityEffect)); - OnPropertyChanged(nameof(EffectiveFlowLMin)); - OnPropertyChanged(nameof(FluidTemperature)); - OnPropertyChanged(nameof(FluidDensity)); - - // Actualizar color de la bomba según el fluido - UpdatePumpColorFromFluid(); - } - } - } - else - { - // Si no hay flujo, mantener aire como fluido - if (_currentFluid.Type != FluidType.Air) - { - _currentFluid = new FluidProperties(FluidType.Air); - OnPropertyChanged(nameof(CurrentFluidType)); - OnPropertyChanged(nameof(CurrentFluidDescription)); - UpdatePumpColorFromFluid(); - } - } - } - catch (Exception ex) - { - // Mantener fluido por defecto en caso de error - System.Diagnostics.Debug.WriteLine($"Error updating pump fluid: {ex.Message}"); - } - } - - private void UpdatePumpColorFromFluid() - { - if (!IsRunning) - { - ColorButton_oculto = Brushes.Gray; - return; - } - - // Color basado solo en si hay flujo o no - if (HasFlow) - { - // Si hay flujo, usar color del fluido o verde por defecto - try - { - var fluidColorHex = _currentFluid.Color; - var fluidColor = (Color)System.Windows.Media.ColorConverter.ConvertFromString(fluidColorHex); - ColorButton_oculto = new SolidColorBrush(fluidColor); - } - catch - { - ColorButton_oculto = Brushes.Green; // Color por defecto si hay flujo - } - } - else - { - ColorButton_oculto = Brushes.Yellow; // Sin flujo (standby o sin succión) - } - } - - private IHydraulicComponent FindSuctionComponent() - { - // Buscar tuberías conectadas a esta bomba - var connectedPipes = _mainViewModel.ObjetosSimulables - .OfType() - .Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre) - .ToList(); - - foreach (var pipe in connectedPipes) - { - // La succión es donde el flujo viene HACIA la bomba - string suctionComponentName = null; - - if (pipe.Id_ComponenteB == Nombre && pipe.CurrentFlow >= 0) - { - // Flujo de A hacia B (hacia esta bomba) - suctionComponentName = pipe.Id_ComponenteA; - } - else if (pipe.Id_ComponenteA == Nombre && pipe.CurrentFlow < 0) - { - // Flujo de B hacia A (hacia esta bomba) - suctionComponentName = pipe.Id_ComponenteB; - } - - if (!string.IsNullOrEmpty(suctionComponentName)) - { - return _mainViewModel.ObjetosSimulables - .OfType() - .FirstOrDefault(c => c is osBase osb && osb.Nombre == suctionComponentName); - } - } - - return null; + // 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 - - try - { - // Los resultados de TSNet ya están aplicados en ApplyHydraulicResults() - // Solo actualizamos propiedades derivadas y visuales - - // Actualizar propiedades del fluido basado en si hay flujo - UpdateFluidFromSuction(); - - // Actualizar propiedades de UI calculadas - OnPropertyChanged(nameof(CurrentFlowLMin)); - OnPropertyChanged(nameof(EffectiveFlowLMin)); - OnPropertyChanged(nameof(HasFlow)); - OnPropertyChanged(nameof(PumpStatus)); - OnPropertyChanged(nameof(DetailedStatus)); - OnPropertyChanged(nameof(ViscosityEffect)); - - // Actualizar color visual basado en estado - UpdatePumpColorFromFluid(); - - // Debug periódico cada 5 segundos - if (Environment.TickCount % 5000 < elapsedMilliseconds) - { - Debug.WriteLine($"Pump {Nombre}: Flow={CurrentFlowLMin:F1}L/min, Pressure={CurrentPressureBar:F2}bar, Status={PumpStatus}"); - } - } - catch (Exception ex) - { - Debug.WriteLine($"Error in Pump {Nombre} UpdateControl: {ex.Message}"); - } - } - - public override void ucLoaded() - { - // En el nuevo sistema unificado, no necesitamos crear objetos separados - base.ucLoaded(); - - // No reinicializar TSNet Adapter aquí - se creará cuando se registre con TSNetSimulationManager - // if (TSNetAdapter == null) - // { - // TSNetAdapter = new TSNetPumpAdapter(this); - // Debug.WriteLine($"Pump {Nombre}: TSNetAdapter inicializado en ucLoaded"); - // } + // Los valores provienen de TSNet + OnPropertyChanged(nameof(CurrentPressureBar)); + OnPropertyChanged(nameof(CurrentFlowLMin)); + OnPropertyChanged(nameof(HasFlow)); UpdatePumpImage(); } - public override void ucUnLoaded() + private void UpdatePumpImage() { - // En el nuevo sistema unificado, el HydraulicSimulationManager - // maneja automáticamente el registro/desregistro de objetos - base.ucUnLoaded(); + if (IsRunning && HasFlow) + ImageSource_oculta = ImageFromPath("/imagenes/pump_run.png"); + else if (IsRunning) + ImageSource_oculta = ImageFromPath("/imagenes/pump_idle.png"); + else + ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png"); } - - - // IHydraulicComponent Implementation - + 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; + } + + public void ApplyHydraulicFlow(double flowM3s) + { + CurrentFlow = flowM3s; + } + + public void UpdateHydraulicState(double timeSeconds) + { + // TSNet maneja el estado + } + + // Métodos para TSNet + public void UpdateFromTSNetResults(double flow, double head, double pressure) + { + CurrentFlow = flow; + CurrentHead = head; + CurrentPressure = pressure; + } + + // Crear adapter para TSNet + public TSNetPumpAdapter CreateTSNetAdapter() + { + TSNetAdapter = new TSNetPumpAdapter(this); + return TSNetAdapter; + } + + // Implementación de IHydraulicComponent [JsonIgnore] public bool HasHydraulicComponents => true; - + public List GetHydraulicNodes() { - var nodes = new List(); - - // Las bombas no crean nodos propios - deben estar conectadas a otros componentes para funcionar - if (!HasConnectedComponents()) - { - Debug.WriteLine($"Bomba {Nombre}: Sin componentes conectados - no puede funcionar"); - } - else - { - //Debug.WriteLine($"Bomba {Nombre}: Conectada a otros componentes - lista para operar"); - } - - return nodes; + // Las bombas no crean nodos propios, se conectan entre nodos existentes + return new List(); } - - public List GetHydraulicElements() - { - var elements = new List(); - - // Solo crear elementos si la bomba está conectada a otros componentes - if (!HasConnectedComponents()) - { - Debug.WriteLine($"Bomba {Nombre}: Sin conexiones - no se puede crear elemento hidráulico"); - return elements; - } - - if (HasHydraulicComponents) - { - // Calcular velocidad efectiva basada en el estado de la bomba - double effectiveSpeed = IsRunning ? SpeedRatio : 0.0; - - // Asegurar que la velocidad esté en rango válido - effectiveSpeed = Math.Max(0.0, Math.Min(1.0, effectiveSpeed)); - - // Obtener los nombres de nodos correctos basados en las conexiones - var (inletNode, outletNode) = GetConnectedNodeNames(); - - // Verificar que tenemos nodos válidos - if (string.IsNullOrEmpty(inletNode) || string.IsNullOrEmpty(outletNode)) - { - Debug.WriteLine($"Bomba {Nombre}: Nodos de conexión inválidos - inlet: '{inletNode}', outlet: '{outletNode}'"); - return elements; - } - - // Crear bomba estándar - el solver manejará las limitaciones de flujo - var pump = new PumpHQ( - h0: PumpHead, - q0: MaxFlow, - speedRel: effectiveSpeed, - direction: PumpDirection - ); - - // Asignar nombres de nodos - pump.InletNodeName = inletNode; - pump.OutletNodeName = outletNode; - - var pumpElement = new HydraulicElementDefinition( - $"{Nombre}_Pump", - inletNode, - outletNode, - pump, - $"Bomba {Nombre}" - ); - - elements.Add(pumpElement); - - Debug.WriteLine($"Bomba {Nombre}: Creando elemento hidráulico - H0={PumpHead}m, Q0={MaxFlow:F6}m³/s ({MaxFlow*3600:F2}m³/h), Velocidad={effectiveSpeed:F2}, Dirección={PumpDirection}"); - Debug.WriteLine($"Bomba {Nombre}: Conectando {inletNode} -> {outletNode}"); - } - - return elements; - } - - public void UpdateHydraulicProperties() - { - // Validaciones simples antes de la simulación - if (!IsRunning) - { - SpeedRatio = 0.0; - } - - if (SpeedRatio < 0.0) SpeedRatio = 0.0; - if (SpeedRatio > 1.0) SpeedRatio = 1.0; - - // El solver se encarga del resto - NPSH, limitaciones de flujo, etc. - } - + public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) { try { - // Aplicar resultados a través del TSNet Adapter - if (TSNetAdapter?.Results != null) + // Buscar resultados de esta bomba en TSNet + var pumpElementName = $"PUMP_{Nombre}"; + if (flows.ContainsKey(pumpElementName)) { - // Buscar resultados para esta bomba usando el NodeId del adapter - string pumpElementName = $"{Nombre}_Pump"; - - // Actualizar flujo desde TSNet - if (flows.ContainsKey(pumpElementName)) - { - var flowM3s = flows[pumpElementName]; - CurrentFlow = flowM3s; - TSNetAdapter.Results.CalculatedFlowM3s = flowM3s; - - Debug.WriteLine($"Pump {Nombre}: TSNet Flow={flowM3s:F6} m³/s ({CurrentFlowLMin:F2} L/min)"); - } - else - { - // Intentar con NodeId del adapter - if (flows.ContainsKey(TSNetAdapter.NodeId)) - { - var flowM3s = flows[TSNetAdapter.NodeId]; - CurrentFlow = flowM3s; - TSNetAdapter.Results.CalculatedFlowM3s = flowM3s; - } - else - { - CurrentFlow = 0.0; - TSNetAdapter.Results.CalculatedFlowM3s = 0.0; - } - } - - // Actualizar presión desde TSNet - var (inletNode, outletNode) = GetConnectedNodeNames(); - if (!string.IsNullOrEmpty(outletNode) && pressures.ContainsKey(outletNode)) - { - var pressurePa = pressures[outletNode]; - CurrentPressure = pressurePa / 100000.0; // Convertir Pa a bar - TSNetAdapter.Results.OutletPressureBar = CurrentPressure; - - Debug.WriteLine($"Pump {Nombre}: TSNet Pressure={pressurePa:F0} Pa = {CurrentPressure:F3} bar"); - } - else if (!string.IsNullOrEmpty(inletNode) && pressures.ContainsKey(inletNode)) - { - var pressurePa = pressures[inletNode]; - CurrentPressure = pressurePa / 100000.0; - TSNetAdapter.Results.InletPressureBar = CurrentPressure; - } - - // Actualizar estado del adapter - TSNetAdapter.Results.IsOperating = IsRunning; - TSNetAdapter.Results.CalculatedEfficiency = CurrentFlow * SpeedRatio; - TSNetAdapter.Results.Timestamp = DateTime.Now; - TSNetAdapter.Results.OperationalStatus = $"Flow: {CurrentFlowLMin:F1}L/min, Pressure: {CurrentPressureBar:F2}bar"; - - // Actualizar el fluido desde el tanque de succión si hay flujo - if (HasFlow) - { - UpdateFluidFromSuction(); - } + var pumpFlow = flows[pumpElementName]; + CurrentFlow = pumpFlow; } - - // Notificar cambios para UI - OnPropertyChanged(nameof(CurrentFlow)); - OnPropertyChanged(nameof(CurrentFlowLMin)); - OnPropertyChanged(nameof(CurrentPressure)); - OnPropertyChanged(nameof(HasFlow)); - OnPropertyChanged(nameof(PumpStatus)); - OnPropertyChanged(nameof(DetailedStatus)); - - // Debug periódico - if (Environment.TickCount % 2000 < 50) // Cada ~2 segundos + + // Calcular cabeza basada en la diferencia de presión + // Esto se podría calcular desde TSNet directamente si está disponible + if (Math.Abs(CurrentFlow) > 1e-6) { - Debug.WriteLine($"Pump {Nombre}: Flow={CurrentFlowLMin:F1}L/min, Pressure={CurrentPressureBar:F2}bar, Running={IsRunning}"); + // Estimación simplificada de la cabeza actual + CurrentHead = PumpHead * (1.0 - (CurrentFlow / MaxFlow)); + CurrentHead = Math.Max(0, CurrentHead); + } + else + { + CurrentHead = IsRunning ? PumpHead : 0; } } catch (Exception ex) @@ -690,192 +358,93 @@ namespace CtrEditor.ObjetosSim Debug.WriteLine($"Error en Pump {Nombre} ApplyHydraulicResults: {ex.Message}"); } } - - - - // IHydraulicFlowReceiver Implementation - + + public List GetHydraulicElements() + { + var elements = new List(); + + if (IsRunning) + { + // Crear elemento de bomba para TSNet + var pumpElement = new PumpHQ(PumpHead, MaxFlow); + + elements.Add(new HydraulicElementDefinition( + $"PUMP_{Nombre}", + $"NODE_A_{Nombre}", + $"NODE_B_{Nombre}", + pumpElement, + $"Bomba hidráulica - Head: {PumpHead:F1}m, Flow: {MaxFlow * 3600:F1} m³/h" + )); + } + + return elements; + } + + public void UpdateHydraulicProperties() + { + // Actualizar propiedades antes de la simulación + if (!IsRunning) + { + CurrentFlow = 0; + CurrentHead = 0; + } + } + + // Implementación de IHydraulicFlowReceiver public void SetFlow(double flow) { CurrentFlow = flow; } - + public double GetFlow() { return CurrentFlow; } - - - - // IHydraulicPressureReceiver Implementation - + + // Implementación de IHydraulicPressureReceiver public void SetPressure(double pressure) { CurrentPressure = pressure; } - + public double GetPressure() { return CurrentPressure; } - - // Helper Methods - - /// - /// Verifica si la bomba tiene componentes conectados a través de tuberías - /// - private bool HasConnectedComponents() + + // Implementación de IosBase + public void Start() { - if (_mainViewModel == null) return false; - - // Buscar tuberías que conecten esta bomba con otros componentes - // CORREGIDO: Usar Nombre en lugar de ID para buscar conexiones - var connectedPipes = _mainViewModel.ObjetosSimulables - .OfType() - .Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre) - .ToList(); - - return connectedPipes.Any(); - } - - /// - /// Obtiene los nombres de nodos correctos para la bomba basándose en las conexiones - /// - private (string inletNode, string outletNode) GetConnectedNodeNames() - { - if (_mainViewModel == null) - return (string.Empty, string.Empty); - - string inletNode = string.Empty; - string outletNode = string.Empty; - - // Buscar tuberías conectadas a esta bomba - // CORREGIDO: Usar Nombre en lugar de ID para buscar conexiones - var connectedPipes = _mainViewModel.ObjetosSimulables - .OfType() - .Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre) - .ToList(); - - foreach (var pipe in connectedPipes) - { - if (pipe.Id_ComponenteB == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteA)) - { - // Esta bomba es el destino, el componente A es la fuente (inlet) - inletNode = ResolveComponentIdToNodeName(pipe.Id_ComponenteA); - //Debug.WriteLine($"Bomba {Nombre}: Nodo inlet identificado como '{inletNode}'"); - } - else if (pipe.Id_ComponenteA == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteB)) - { - // Esta bomba es la fuente, el componente B es el destino (outlet) - outletNode = ResolveComponentIdToNodeName(pipe.Id_ComponenteB); - //Debug.WriteLine($"Bomba {Nombre}: Nodo outlet identificado como '{outletNode}'"); - } - } - - return (inletNode, outletNode); - } - - /// - /// Resuelve un ID de componente al nombre de nodo hidráulico correspondiente - /// - private string ResolveComponentIdToNodeName(string componentId) - { - if (string.IsNullOrEmpty(componentId) || _mainViewModel == null) - return string.Empty; - - // CORREGIDO: Buscar el componente por NOMBRE (no por ID numérico) - // Los pipes usan nombres de componentes como "Tanque Origen", "Bomba Principal" - var component = _mainViewModel.ObjetosSimulables - .FirstOrDefault(obj => obj.Nombre == componentId); - - if (component == null) - return string.Empty; - - // Para tanques, el nombre del nodo es el nombre del tanque - if (component is osHydTank tank) - { - return tank.Nombre; - } - - // Para tuberías, necesitamos seguir la conexión hasta encontrar un tanque terminal - if (component is osHydPipe pipe) - { - // Las tuberías NO son nodos - debemos encontrar el tanque terminal - return FindTerminalNodeFromPipe(pipe, componentId); - } - - // Para otros tipos de componentes hidráulicos - if (component is IHydraulicComponent hydComponent) - { - return component.Nombre; - } - - return string.Empty; + // Inicialización de la bomba para la simulación } - /// - /// Encuentra el nodo terminal (tanque) siguiendo la cadena desde una tubería - /// - private string FindTerminalNodeFromPipe(osHydPipe startPipe, string originComponentId) + public void Inicializar(int valorInicial) { - var visited = new HashSet(); - var queue = new Queue(); - queue.Enqueue(startPipe); - visited.Add(startPipe.Id.Value.ToString()); - - while (queue.Count > 0) + // Inicialización con valor inicial + } + + public void Disposing() + { + // Limpieza si es necesaria + } + + public int zIndex_fromFrames { get; set; } = 2; + + public ZIndexEnum ZIndex_Base() + { + return ZIndexEnum.Estaticos; + } + + public override void CopyFrom(osBase source) + { + if (source is osHydPump sourcePump) { - var currentPipe = queue.Dequeue(); - - // Verificar ambos extremos de la tubería - var connectionIds = new[] { currentPipe.Id_ComponenteA, currentPipe.Id_ComponenteB }; - - foreach (var connectionId in connectionIds) - { - if (string.IsNullOrEmpty(connectionId) || connectionId == originComponentId) - continue; - - var connectedComponent = _mainViewModel.ObjetosSimulables - .FirstOrDefault(obj => obj.Id.Value.ToString() == connectionId); - - // Si encontramos un tanque, ese es nuestro nodo terminal - if (connectedComponent is osHydTank tank) - { - Debug.WriteLine($"Bomba {Nombre}: Nodo terminal encontrado: {tank.Nombre}"); - return tank.Nombre; - } - - // Si encontramos otra tubería, continuar la búsqueda - if (connectedComponent is osHydPipe nextPipe && !visited.Contains(nextPipe.Id.Value.ToString())) - { - queue.Enqueue(nextPipe); - visited.Add(nextPipe.Id.Value.ToString()); - } - } + PumpHead = sourcePump.PumpHead; + MaxFlow = sourcePump.MaxFlow; + Efficiency = sourcePump.Efficiency; + IsRunning = sourcePump.IsRunning; } - - Debug.WriteLine($"Bomba {Nombre}: No se pudo encontrar nodo terminal desde tubería {startPipe.Nombre}"); - return string.Empty; - } - - private void InvalidateHydraulicNetwork() - { - _mainViewModel?.hydraulicSimulationManager?.InvalidateNetwork(); - } - - } - - /// - /// Proveedor de items para la dirección de la bomba - /// - public class PumpDirectionItemsSource : IItemsSource - { - public Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection GetValues() - { - var items = new Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection(); - items.Add(1, "Adelante (+1)"); - items.Add(-1, "Atrás (-1)"); - return items; + base.CopyFrom(source); } } } diff --git a/ObjetosSim/HydraulicComponents/osHydTank.cs b/ObjetosSim/HydraulicComponents/osHydTank.cs index d06b5ff..4976543 100644 --- a/ObjetosSim/HydraulicComponents/osHydTank.cs +++ b/ObjetosSim/HydraulicComponents/osHydTank.cs @@ -3,12 +3,12 @@ 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 HydraulicSimulator.Models; using Newtonsoft.Json; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; using CommunityToolkit.Mvvm.ComponentModel; @@ -17,7 +17,7 @@ using LibS7Adv; namespace CtrEditor.ObjetosSim { /// - /// Tanque hidráulico con gestión dinámica de nivel y presión configurable + /// Tanque hidráulico simplificado para integración con TSNet /// public partial class osHydTank : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase { @@ -25,108 +25,323 @@ namespace CtrEditor.ObjetosSim public static string NombreClase() => "Tanque Hidráulico"; - // Private Fields + // Tipos de fluido disponibles + public enum FluidTypeId + { + Water = 0, + SyrupBrix40 = 1 + } + + // Private Fields básicos para TSNet + private double _elevation = 0.0; // m - elevación del fondo del tanque + private double _initialLevel = 1.0; // m - nivel inicial + private double _minLevel = 0.0; // m - nivel mínimo + private double _maxLevel = 2.0; // m - nivel máximo + private double _diameter = 1.0; // m - diámetro del tanque + private FluidTypeId _fluidType = FluidTypeId.Water; - 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) + // Estado actual del tanque (desde TSNet) + private double _currentLevel = 1.0; // m 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 + private double _currentFlow = 0.0; // m³/s // TSNet Integration [JsonIgnore] public TSNetTankAdapter? TSNetAdapter { get; private set; } - - [JsonIgnore] - private object _levelLock = new object(); - - - - - // Properties + // Propiedades visuales [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 + Tamano = 1.0f; 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}"); + Debug.WriteLine($"osHydTank Constructor completado correctamente"); } catch (Exception ex) { - Debug.WriteLine($"Error in osHydTank constructor: {ex.Message}"); - // Inicializar valores por defecto mínimos - InitializeDefaults(); + Debug.WriteLine($"Error en constructor osHydTank: {ex.Message}"); + Nombre = "Tanque Hidráulico"; + Tamano = 1.0f; } } - - // Constructor y Métodos Base + // Propiedades de configuración del tanque + [Category("🏗️ Configuración del Tanque")] + [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 del Tanque")] + [DisplayName("Nivel inicial")] + [Description("Nivel inicial del agua en el tanque (m)")] + public double InitialLevel + { + get => _initialLevel; + set + { + if (SetProperty(ref _initialLevel, Math.Max(0, value))) + { + InvalidateHydraulicNetwork(); + } + } + } + + [Category("🏗️ Configuración del Tanque")] + [DisplayName("Nivel mínimo")] + [Description("Nivel mínimo del tanque (m)")] + public double MinLevel + { + get => _minLevel; + set + { + if (SetProperty(ref _minLevel, Math.Max(0, value))) + { + InvalidateHydraulicNetwork(); + } + } + } + + [Category("🏗️ Configuración del Tanque")] + [DisplayName("Nivel máximo")] + [Description("Nivel máximo del tanque (m)")] + public double MaxLevel + { + get => _maxLevel; + set + { + if (SetProperty(ref _maxLevel, Math.Max(_minLevel + 0.1, value))) + { + InvalidateHydraulicNetwork(); + } + } + } + + [Category("🏗️ Configuración del Tanque")] + [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 (desde TSNet) + [Category("📊 Estado Actual")] + [DisplayName("Nivel actual")] + [Description("Nivel actual del agua en el tanque (m)")] + [ReadOnly(true)] + public double CurrentLevel + { + get => _currentLevel; + private set => SetProperty(ref _currentLevel, value); + } + + [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 <= _minLevel) return 0; + return Math.Max(0, Math.Min(100, (_currentLevel - _minLevel) / (_maxLevel - _minLevel) * 100)); + } + } + + /// + /// Nivel actual en metros (alias para compatibilidad) + /// + [Category("📊 Estado Actual")] + [DisplayName("Nivel actual (m)")] + [Description("Nivel actual del líquido en metros")] + public double CurrentLevelM + { + get => _currentLevel; + set => SetProperty(ref _currentLevel, value); + } + + /// + /// Balance de flujo neto del tanque + /// + [Category("📊 Estado Actual")] + [DisplayName("Balance de flujo")] + [Description("Balance neto de flujo del tanque (positivo = entrada, negativo = salida)")] + [ReadOnly(true)] + public double FlowBalance => _currentFlow; + + /// + /// Tipo de tanque para clasificación + /// + [Category("📊 Estado Actual")] + [DisplayName("Tipo de tanque")] + [Description("Tipo de tanque para clasificación")] + [ReadOnly(true)] + public string TankType => "Simplified"; + + /// + /// Descripción actual del fluido + /// + [Category("📊 Estado Actual")] + [DisplayName("Descripción fluido actual")] + [Description("Descripción detallada del fluido actual")] + [ReadOnly(true)] + public string CurrentFluidDescription => FluidDescription; + + /// + /// Nivel máximo en metros (alias para compatibilidad) + /// + [Category("📏 Dimensiones")] + [DisplayName("Nivel máximo (m)")] + [Description("Nivel máximo del tanque en metros")] + public double MaxLevelM + { + get => _maxLevel; + set => SetProperty(ref _maxLevel, value); + } + + /// + /// Nivel mínimo en metros (alias para compatibilidad) + /// + [Category("📏 Dimensiones")] + [DisplayName("Nivel mínimo (m)")] + [Description("Nivel mínimo del tanque en metros")] + public double MinLevelM + { + get => _minLevel; + set => SetProperty(ref _minLevel, value); + } + + /// + /// Área de sección transversal del tanque + /// + [Category("📏 Dimensiones")] + [DisplayName("Área sección transversal")] + [Description("Área de sección transversal del tanque (m²)")] + public double CrossSectionalArea + { + get => Math.PI * Math.Pow(_diameter / 2.0, 2); + set => _diameter = Math.Sqrt(value / Math.PI) * 2.0; // Calcular diámetro desde área + } + + /// + /// Presión del tanque + /// + [Category("📊 Estado Actual")] + [DisplayName("Presión del tanque")] + [Description("Presión actual del tanque (Pa)")] + public double TankPressure { get; set; } = 101325.0; // 1 atm por defecto + + /// + /// Si el tanque tiene presión fija + /// + [Category("⚙️ Configuración")] + [DisplayName("Presión fija")] + [Description("Indica si el tanque mantiene presión fija")] + public bool IsFixedPressure { get; set; } = false; + + // Métodos básicos private string nombre = NombreClase(); [Category("🏷️ Identificación")] @@ -140,9 +355,7 @@ namespace CtrEditor.ObjetosSim 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}"); + // Movimiento básico } public override void OnResize(float Delta_Width, float Delta_Height) @@ -152,493 +365,75 @@ namespace CtrEditor.ObjetosSim 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 valores provienen de TSNet + OnPropertyChanged(nameof(CurrentVolume)); + OnPropertyChanged(nameof(FillPercentage)); - // Los resultados de TSNet ya están aplicados en ApplyHydraulicResults() - // Solo actualizamos propiedades derivadas y visuales - - try + UpdateTankColorFromFluid(); + } + + private void UpdateTankColorFromFluid() + { + ColorButton_oculto = FluidType switch { - // 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}"); - } + FluidTypeId.Water => Brushes.LightBlue, + FluidTypeId.SyrupBrix40 => Brushes.Orange, + _ => Brushes.Gray + }; } - - - // 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 + private void InvalidateHydraulicNetwork() { - get => _crossSectionalArea; - set - { - if (SetProperty(ref _crossSectionalArea, Math.Max(0.1, value))) - { - SafeUpdateVolumeCalculations(); - InvalidateHydraulicNetwork(); - } - } + // Invalidar la red hidráulica para que se recalcule + var mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel; + mainViewModel?.InvalidateHydraulicNetwork(); } - [Category("🏗️ Configuración del Tanque")] - [DisplayName("Volumen máximo")] - [Description("Volumen máximo del tanque (L)")] - public double MaxVolumeL + // Implementación de interfaces hidráulicas + public void ApplyHydraulicPressure(double pressurePa) { - get => _maxVolumeL; - set - { - if (SetProperty(ref _maxVolumeL, Math.Max(10, value))) - { - if (_currentVolumeL > _maxVolumeL) - CurrentVolumeL = _maxVolumeL; - SafeUpdateVolumeCalculations(); - } - } + CurrentPressure = pressurePa / 100000.0; // Pa a bar } - [Category("🏗️ Configuración del Tanque")] - [DisplayName("Nivel máximo")] - [Description("Nivel máximo permitido en el tanque (m)")] - public double MaxLevelM + public void ApplyHydraulicFlow(double flowM3s) { - 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(); - } - } + CurrentFlow = flowM3s; } - [Category("🏗️ Configuración del Tanque")] - [DisplayName("Nivel mínimo")] - [Description("Nivel mínimo permitido en el tanque (m)")] - public double MinLevelM + public void UpdateHydraulicState(double timeSeconds) { - 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(); - } - } + // TSNet maneja el estado } - [Category("🏗️ Configuración del Tanque")] - [DisplayName("Tipo de tanque")] - [Description("Tipo de tanque (Standard, Storage, Process, etc.)")] - public string TankType + // Métodos para TSNet + public void UpdateFromTSNetResults(double level, double pressure, double flow) { - get => _tankType; - set => SetProperty(ref _tankType, value ?? "Standard"); + CurrentLevel = Math.Max(_minLevel, Math.Min(_maxLevel, level)); + CurrentPressure = pressure / 100000.0; // Pa a bar + CurrentFlow = flow; } - - - - - // Properties - Pressure - - [Category("🔧 Sistema de Presión")] - [DisplayName("Presión fija")] - [Description("Si está habilitado, la presión se mantiene constante")] - public bool IsFixedPressure + // Crear nodo para TSNet + public TSNetTankAdapter CreateTSNetAdapter() { - get => _isFixedPressure; - set - { - if (SetProperty(ref _isFixedPressure, value)) - { - SafeUpdateTankPressure(); - InvalidateHydraulicNetwork(); - } - } + TSNetAdapter = new TSNetTankAdapter(this); + return TSNetAdapter; } - [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 - + // Implementación de IHydraulicComponent [JsonIgnore] public bool HasHydraulicComponents => true; public List GetHydraulicNodes() { var nodes = new List(); - - // 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"); - } - + // Tanque como nodo libre por defecto + nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, $"Tanque hidráulico - {FluidDescription}")); return nodes; } @@ -646,50 +441,19 @@ namespace CtrEditor.ObjetosSim { try { - // Aplicar resultados a través del TSNet Adapter - if (TSNetAdapter?.Results != null) + // Actualizar presión desde TSNet + if (pressures.ContainsKey(Nombre)) { - // 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"; + var pressurePa = pressures[Nombre]; + CurrentPressure = pressurePa / 100000.0; // Convertir Pa a bar } - // Notificar cambios para UI - OnPropertyChanged(nameof(CurrentLevelM)); - OnPropertyChanged(nameof(CurrentVolumeL)); - OnPropertyChanged(nameof(FillPercentage)); - OnPropertyChanged(nameof(NetFlow)); - OnPropertyChanged(nameof(CurrentPressure)); + // Calcular flujo neto desde pipes conectadas + var netFlowM3s = CalculateNetFlowFromConnectedPipes(flows); + CurrentFlow = netFlowM3s; - // 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"); - } + // Actualizar nivel basado en balance de flujo + UpdateLevelFromFlowBalance(netFlowM3s); } catch (Exception ex) { @@ -706,576 +470,79 @@ namespace CtrEditor.ObjetosSim public void UpdateHydraulicProperties() { // Actualizar propiedades antes de la simulación - SafeUpdateTankPressure(); - SafeUpdateVolumeCalculations(); } - - - // UI Properties - - /// - /// Propiedades del fluido actual que está saliendo del tanque - /// - [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) - }; - } - } - - /// - /// Descripción del fluido actual - /// - [JsonIgnore] - public string CurrentFluidDescription => CurrentOutputFluid.Description; - - /// - /// Color del nivel de líquido basado en el fluido actual - /// - [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); - } - } - - /// - /// Color del borde del nivel de líquido - /// - [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); - } - } - - - /// - /// Color del balance de flujo - /// - [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); - } - } - - - - /// - /// Indica si el tanque está vacío (contiene aire) - /// - [JsonIgnore] - public bool IsEmpty => CurrentVolumeL < 1.0; // Menos de 1L considerado vacío - - /// - /// Densidad del fluido actual - /// - [JsonIgnore] - public double CurrentFluidDensity => CurrentOutputFluid.Density; - - /// - /// Viscosidad del fluido actual - /// - [JsonIgnore] - public double CurrentFluidViscosity => CurrentOutputFluid.Viscosity; - - /// - /// Porcentaje de altura para la sección secundaria (abajo) - para display visual - /// - [JsonIgnore] - public double SecondaryDisplayPercentage - { - get - { - var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL; - if (totalVolume == 0) return 0; - return (_secondaryVolumeL / totalVolume) * FillPercentage / 100.0; - } - } - - /// - /// Porcentaje de altura para la sección de mezcla (medio) - para display visual - /// - [JsonIgnore] - public double MixDisplayPercentage - { - get - { - var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL; - if (totalVolume == 0) return 0; - return (_mixingVolumeL / totalVolume) * FillPercentage / 100.0; - } - } - - /// - /// Porcentaje de altura para la sección primaria (arriba) - para display visual - /// - [JsonIgnore] - public double PrimaryDisplayPercentage - { - get - { - var totalVolume = _primaryVolumeL + _secondaryVolumeL + _mixingVolumeL; - if (totalVolume == 0) return 0; - return (_primaryVolumeL / totalVolume) * FillPercentage / 100.0; - } - } - - /// - /// Color para la sección secundaria - /// - [JsonIgnore] - public SolidColorBrush SecondaryLevelColor - { - get - { - var colorHex = _secondaryFluid.Color; - var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorHex); - return new SolidColorBrush(color); - } - } - - /// - /// Color para la sección de mezcla - /// - [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); - } - } - - /// - /// Color para la sección primaria - /// - [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 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 - + // Implementación de IHydraulicFlowReceiver public void SetFlow(double flow) { - // Este método puede ser llamado por tuberías conectadas - // La lógica de flujo se maneja en ApplyHydraulicResults + CurrentFlow = flow; } public double GetFlow() { - return FlowBalance; + return CurrentFlow; } - - - // IHydraulicPressureReceiver Implementation - + // Implementación de IHydraulicPressureReceiver public void SetPressure(double pressure) { - if (!IsFixedPressure) - { - CurrentPressure = pressure; - } + CurrentPressure = pressure / 100000.0; // Pa a bar } public double GetPressure() { - return CurrentPressure; + return CurrentPressure * 100000.0; // bar a Pa } - + // Implementación de IosBase + public void Start() + { + // Inicialización del tanque para la simulación + } + + public void Inicializar(int valorInicial) + { + // Inicialización con valor inicial + } + + public void Disposing() + { + // Limpieza si es necesaria + } - // ZIndex Implementation - public int zIndex_fromFrames { get; set; } = 2; public ZIndexEnum ZIndex_Base() { return ZIndexEnum.Estaticos; } - - - // IosBase Implementation - - public void Start() + public override void CopyFrom(osBase source) { - // Inicialización del tanque para la simulación - SafeUpdateVolumeCalculations(); - SafeUpdateTankPressure(); + if (source is osHydTank sourceTank) + { + Elevation = sourceTank.Elevation; + InitialLevel = sourceTank.InitialLevel; + MinLevel = sourceTank.MinLevel; + MaxLevel = sourceTank.MaxLevel; + Diameter = sourceTank.Diameter; + FluidType = sourceTank.FluidType; + } + base.CopyFrom(source); } - 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 - } - - - - - - - /// - /// Calcula el flujo neto desde pipes conectadas usando resultados de TSNet - /// + // Métodos privados auxiliares private double CalculateNetFlowFromConnectedPipes(Dictionary flows) { double netFlow = 0.0; - if (_mainViewModel?.ObjetosSimulables == null) return netFlow; + var mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel; + if (mainViewModel?.ObjetosSimulables == null) return netFlow; try { // Buscar todas las pipes conectadas a este tanque - var connectedPipes = _mainViewModel.ObjetosSimulables + var connectedPipes = mainViewModel.ObjetosSimulables .OfType() .Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre) .ToList(); @@ -1283,7 +550,7 @@ namespace CtrEditor.ObjetosSim foreach (var pipe in connectedPipes) { // Buscar el flujo de la pipe en los resultados de TSNet - var pipeElementName = $"{pipe.Nombre}_Pipe"; + var pipeElementName = $"PIPE_{pipe.Nombre}"; if (flows.ContainsKey(pipeElementName)) { var pipeFlow = flows[pipeElementName]; // m³/s @@ -1311,81 +578,22 @@ namespace CtrEditor.ObjetosSim } } - /// - /// Actualiza el color del tanque basado en el fluido actual - /// - private void UpdateTankColorFromFluid() + private void UpdateLevelFromFlowBalance(double netFlowM3s) { - 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 - } + if (Math.Abs(netFlowM3s) < 1e-6) return; // Flujo muy pequeño + + // Calcular área transversal + var area = Math.PI * Math.Pow(_diameter / 2, 2); // m² + + // Tiempo de simulación (asumimos 1 segundo por simplicidad) + var deltaTime = 1.0; // segundos + + // Cambio de nivel = flujo volumétrico * tiempo / área + var deltaLevel = (netFlowM3s * deltaTime) / area; // m + + // Actualizar nivel actual + var newLevel = _currentLevel + deltaLevel; + CurrentLevel = Math.Max(_minLevel, Math.Min(_maxLevel, newLevel)); } - - - - - - - - - // Serialization Support - - /// - /// Se llama antes de la serialización para asegurar que el lock esté disponible - /// - 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(); - } - - } } diff --git a/ObjetosSim/HydraulicComponents/ucHydPipe.xaml b/ObjetosSim/HydraulicComponents/ucHydPipe.xaml index e66b195..00d4d7a 100644 --- a/ObjetosSim/HydraulicComponents/ucHydPipe.xaml +++ b/ObjetosSim/HydraulicComponents/ucHydPipe.xaml @@ -7,9 +7,7 @@ xmlns:vm="clr-namespace:CtrEditor.ObjetosSim" mc:Ignorable="d"> - - - + @@ -29,9 +27,13 @@ Stroke="Black" StrokeThickness="2" RadiusX="5" - RadiusY="5"/> + RadiusY="5"> + + + + - + @@ -46,17 +48,43 @@ - + Background="#AA000000" + CornerRadius="3" + Padding="3,1" + Canvas.Top="-25" + Canvas.Left="5"> + + + + + + + + + + + + + + + + + + + + + + diff --git a/ObjetosSim/HydraulicComponents/ucHydPump.xaml b/ObjetosSim/HydraulicComponents/ucHydPump.xaml index 9d73d66..68e6499 100644 --- a/ObjetosSim/HydraulicComponents/ucHydPump.xaml +++ b/ObjetosSim/HydraulicComponents/ucHydPump.xaml @@ -7,7 +7,7 @@ xmlns:vm="clr-namespace:CtrEditor.ObjetosSim" mc:Ignorable="d"> - + @@ -22,18 +22,47 @@ - + + + + + + + + + + + + + + + + + + - - - - + + + + + + + \ No newline at end of file diff --git a/ObjetosSim/HydraulicComponents/ucHydTank.xaml b/ObjetosSim/HydraulicComponents/ucHydTank.xaml index 2cc8f57..18b126c 100644 --- a/ObjetosSim/HydraulicComponents/ucHydTank.xaml +++ b/ObjetosSim/HydraulicComponents/ucHydTank.xaml @@ -7,10 +7,7 @@ xmlns:vm="clr-namespace:CtrEditor.ObjetosSim" mc:Ignorable="d"> - - - - + @@ -22,18 +19,21 @@ - + - + + RadiusY="8"> + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + + @@ -178,29 +86,18 @@ - - - - - + + + - - - + + + @@ -209,69 +106,15 @@ - - - + - - + - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs index 568e3d5..4172be2 100644 --- a/ObjetosSim/osBase.cs +++ b/ObjetosSim/osBase.cs @@ -1867,6 +1867,28 @@ namespace CtrEditor.ObjetosSim System.Diagnostics.Debug.WriteLine($"[Dimensions] Cleared ALL stored dimensions"); } + /// + /// Copia propiedades comunes desde otro objeto osBase + /// + /// Objeto fuente para copiar + public virtual void CopyFrom(osBase source) + { + if (source == null) return; + + try + { + // Copiar propiedades básicas comunes que están disponibles en osBase + Nombre = source.Nombre; + + // Nota: X, Y, Width, Height no están definidas en osBase base + // Las clases derivadas pueden override este método para copiar sus propiedades específicas + } + catch (Exception ex) + { + Debug.WriteLine($"Error en CopyFrom: {ex.Message}"); + } + } + } public class UniqueId {