// PARCHE PRIORITARIO 3: Mejoras en osHydTank para agregar selector de tipo de salida using System; using System.ComponentModel; using Newtonsoft.Json; using CtrEditor.HydraulicSimulator; namespace CtrEditor.ObjetosSim { /// /// Tipo de flujo que sale del tanque /// public enum TankOutputMode { [Description("Solo fluido primario")] Primary = 0, [Description("Solo fluido secundario")] Secondary = 1, [Description("Mezcla proporcional")] Mixed = 2, [Description("Automático según volúmenes")] Automatic = 3 } public partial class osHydTank { private TankOutputMode _outputMode = TankOutputMode.Automatic; [Category("🔄 Control de Salida")] [DisplayName("Modo de salida")] [Description("Selecciona qué fluido sale del tanque")] public TankOutputMode OutputMode { get => _outputMode; set { if (SetProperty(ref _outputMode, value)) { OnPropertyChanged(nameof(CurrentOutputFluid)); OnPropertyChanged(nameof(CurrentFluidDescription)); OnPropertyChanged(nameof(OutputModeDescription)); InvalidateHydraulicNetwork(); } } } [Category("🔄 Control de Salida")] [DisplayName("Descripción modo")] [Description("Descripción del modo de salida actual")] [ReadOnly(true)] public string OutputModeDescription { get { return OutputMode switch { TankOutputMode.Primary => $"Salida: {PrimaryFluidType} ({PrimaryVolumeL:F1}L)", TankOutputMode.Secondary => $"Salida: {SecondaryFluidType} ({SecondaryVolumeL:F1}L)", TankOutputMode.Mixed => $"Salida: Mezcla ({PrimaryVolumeL + SecondaryVolumeL:F1}L total)", TankOutputMode.Automatic => GetAutomaticModeDescription(), _ => "Modo desconocido" }; } } [Category("📊 Niveles Específicos")] [DisplayName("Nivel Primario (m)")] [Description("Nivel específico del fluido primario")] [ReadOnly(true)] public double PrimaryLevelM { get { if (CrossSectionalArea <= 0) return 0; return PrimaryVolumeL / 1000.0 / CrossSectionalArea; } } [Category("📊 Niveles Específicos")] [DisplayName("Nivel Secundario (m)")] [Description("Nivel específico del fluido secundario")] [ReadOnly(true)] public double SecondaryLevelM { get { if (CrossSectionalArea <= 0) return 0; return SecondaryVolumeL / 1000.0 / CrossSectionalArea; } } [Category("📊 Niveles Específicos")] [DisplayName("Proporción Primario")] [Description("Porcentaje del fluido primario en el total")] [ReadOnly(true)] public double PrimaryPercentage { get { var totalVolume = PrimaryVolumeL + SecondaryVolumeL; return totalVolume > 0 ? (PrimaryVolumeL / totalVolume) * 100.0 : 0.0; } } [Category("📊 Niveles Específicos")] [DisplayName("Proporción Secundario")] [Description("Porcentaje del fluido secundario en el total")] [ReadOnly(true)] public double SecondaryPercentage { get { var totalVolume = PrimaryVolumeL + SecondaryVolumeL; return totalVolume > 0 ? (SecondaryVolumeL / totalVolume) * 100.0 : 0.0; } } // Propiedades del fluido actual basado en OutputMode public FluidProperties GetCurrentOutputFluidByMode() { return OutputMode switch { TankOutputMode.Primary => GetPrimaryOutput(), TankOutputMode.Secondary => GetSecondaryOutput(), TankOutputMode.Mixed => GetMixedOutput(), TankOutputMode.Automatic => GetAutomaticOutput(), _ => new FluidProperties(FluidType.Air) }; } private FluidProperties GetPrimaryOutput() { return PrimaryVolumeL > 0 ? _primaryFluid.Clone() : new FluidProperties(FluidType.Air); } private FluidProperties GetSecondaryOutput() { return SecondaryVolumeL > 0 ? _secondaryFluid.Clone() : new FluidProperties(FluidType.Air); } private FluidProperties GetMixedOutput() { if (PrimaryVolumeL <= 0 && SecondaryVolumeL <= 0) return new FluidProperties(FluidType.Air); if (PrimaryVolumeL <= 0) return _secondaryFluid.Clone(); if (SecondaryVolumeL <= 0) return _primaryFluid.Clone(); // Calcular ratio de mezcla basado en volúmenes var totalVolume = PrimaryVolumeL + SecondaryVolumeL; var mixRatio = SecondaryVolumeL / totalVolume; return _primaryFluid.MixWith(_secondaryFluid, mixRatio); } private FluidProperties GetAutomaticOutput() { // Lógica automática: secundario primero, luego mezcla, luego primario if (SecondaryVolumeL > 0 && PrimaryVolumeL > 0) { // Si hay ambos, usar la lógica del MixingState original return MixingState switch { MixingState.EmptyingSecondary => _secondaryFluid.Clone(), MixingState.Mixing => _primaryFluid.MixWith(_secondaryFluid, CurrentMixRatio), MixingState.EmptyingPrimary => _primaryFluid.Clone(), _ => GetMixedOutput() }; } else if (SecondaryVolumeL > 0) { return _secondaryFluid.Clone(); } else if (PrimaryVolumeL > 0) { return _primaryFluid.Clone(); } else { return new FluidProperties(FluidType.Air); } } private string GetAutomaticModeDescription() { if (SecondaryVolumeL > 0 && PrimaryVolumeL > 0) { return $"Auto: {SecondaryFluidType}→Mezcla→{PrimaryFluidType}"; } else if (SecondaryVolumeL > 0) { return $"Auto: {SecondaryFluidType} ({SecondaryVolumeL:F1}L)"; } else if (PrimaryVolumeL > 0) { return $"Auto: {PrimaryFluidType} ({PrimaryVolumeL:F1}L)"; } else { return "Auto: Vacío"; } } // Método mejorado para actualizar volúmenes durante el flujo private void UpdateVolumeFromFlowWithMode(double deltaTimeSec) { var flowOutLMin = OutletFlow; var flowInLMin = InletFlow; var netFlowLMin = flowInLMin - flowOutLMin; var deltaVolumeL = netFlowLMin * deltaTimeSec / 60.0; if (deltaVolumeL < 0) // Salida neta { var outVolumeL = Math.Abs(deltaVolumeL); ReduceVolumesByOutputMode(outVolumeL); } else if (deltaVolumeL > 0) // Entrada neta { // Por defecto, el fluido que entra se añade al primario // TODO: Implementar lógica para determinar tipo de fluido entrante basado en la fuente PrimaryVolumeL += deltaVolumeL; } // Actualizar nivel total y otras propiedades CurrentVolumeL = PrimaryVolumeL + SecondaryVolumeL; CurrentLevelM = CurrentVolumeL / 1000.0 / Math.Max(0.1, CrossSectionalArea); // Notificar cambios en las propiedades calculadas OnPropertyChanged(nameof(PrimaryLevelM)); OnPropertyChanged(nameof(SecondaryLevelM)); OnPropertyChanged(nameof(PrimaryPercentage)); OnPropertyChanged(nameof(SecondaryPercentage)); OnPropertyChanged(nameof(OutputModeDescription)); } private void ReduceVolumesByOutputMode(double outVolumeL) { switch (OutputMode) { case TankOutputMode.Primary: PrimaryVolumeL = Math.Max(0, PrimaryVolumeL - outVolumeL); break; case TankOutputMode.Secondary: SecondaryVolumeL = Math.Max(0, SecondaryVolumeL - outVolumeL); break; case TankOutputMode.Mixed: ReduceVolumeProportionally(outVolumeL); break; case TankOutputMode.Automatic: ReduceVolumeByMixingState(outVolumeL); break; } } private void ReduceVolumeProportionally(double outVolumeL) { var totalVol = PrimaryVolumeL + SecondaryVolumeL; if (totalVol > 0) { var primaryRatio = PrimaryVolumeL / totalVol; var secondaryRatio = SecondaryVolumeL / totalVol; PrimaryVolumeL = Math.Max(0, PrimaryVolumeL - (outVolumeL * primaryRatio)); SecondaryVolumeL = Math.Max(0, SecondaryVolumeL - (outVolumeL * secondaryRatio)); } } private void ReduceVolumeByMixingState(double outVolumeL) { // Usar la lógica original del MixingState switch (MixingState) { case MixingState.EmptyingSecondary: SecondaryVolumeL = Math.Max(0, SecondaryVolumeL - outVolumeL); break; case MixingState.Mixing: ReduceVolumeProportionally(outVolumeL); break; case MixingState.EmptyingPrimary: PrimaryVolumeL = Math.Max(0, PrimaryVolumeL - outVolumeL); break; default: // Si hay secundario, vaciarlo primero if (SecondaryVolumeL > 0) { SecondaryVolumeL = Math.Max(0, SecondaryVolumeL - outVolumeL); } else { PrimaryVolumeL = Math.Max(0, PrimaryVolumeL - outVolumeL); } break; } } } }