From 749f0f7eff05ed9ee3e25a1f83767ecec8ce2216 Mon Sep 17 00:00:00 2001 From: Miguel Date: Sat, 6 Sep 2025 19:10:20 +0200 Subject: [PATCH] Add comprehensive documentation and technical specifications for Hydraulic Simulator and Fluid Management System - Introduced detailed documentation for the Hydraulic Simulator, covering architecture, components, usage examples, and integration with graphical objects. - Added technical specifications for the Fluid Management System, including calculation algorithms, performance specifications, implementation details, and testing strategies. - Implemented FluidProperties class to encapsulate fluid characteristics and behaviors, including density, viscosity, and mixing capabilities. - Established enums for FluidType and MixingState to enhance code clarity and maintainability. --- Documentation/Hidraulic/BestPracticesGuide.md | 450 ++++++++++ .../Hidraulic/FluidManagementSystem.md | 288 +++++++ .../{ => Hidraulic}/HydraulicComponents.md | 0 .../{ => Hidraulic}/HydraulicSimulator DOC.md | 0 .../Hidraulic/TechnicalSpecifications.md | 517 +++++++++++ .../HydraulicComponents/FluidProperties.cs | 184 ++++ ObjetosSim/HydraulicComponents/osHydTank.cs | 803 ++++++++++++------ .../HydraulicComponents/ucHydTank.xaml.cs | 5 +- 8 files changed, 1993 insertions(+), 254 deletions(-) create mode 100644 Documentation/Hidraulic/BestPracticesGuide.md create mode 100644 Documentation/Hidraulic/FluidManagementSystem.md rename Documentation/{ => Hidraulic}/HydraulicComponents.md (100%) rename Documentation/{ => Hidraulic}/HydraulicSimulator DOC.md (100%) create mode 100644 Documentation/Hidraulic/TechnicalSpecifications.md create mode 100644 ObjetosSim/HydraulicComponents/FluidProperties.cs diff --git a/Documentation/Hidraulic/BestPracticesGuide.md b/Documentation/Hidraulic/BestPracticesGuide.md new file mode 100644 index 0000000..4b71c82 --- /dev/null +++ b/Documentation/Hidraulic/BestPracticesGuide.md @@ -0,0 +1,450 @@ +# 📚 Guía de Mejores Prácticas - Sistema de Fluidos CtrEditor + +## 🎯 Introducción + +Esta guía proporciona las mejores prácticas, casos de uso comunes y ejemplos prácticos para el nuevo sistema de gestión de fluidos de CtrEditor. Está dirigida a ingenieros de proceso, técnicos de automatización y desarrolladores que implementen simulaciones hidráulicas industriales. + +--- + +## 🏭 Casos de Uso Industriales + +### **Caso 1: Preparación de Jarabe Simple** +```csharp +// Escenario: Dilución de jarabe concentrado para bebidas +var syrupTank = new osHydTank() +{ + Nombre = "Tanque de Jarabe", + CrossSectionalArea = 2.0, // m² + MaxLevel = 3.0, // m + + // Jarabe concentrado inicial + PrimaryFluid = new FluidProperties + { + FluidType = FluidType.Syrup, + ConcentrationBrix = 65.0, // Jarabe concentrado + Temperature = 85.0 // Temperatura de proceso + }, + + // Agua para dilución + SecondaryFluid = new FluidProperties + { + FluidType = FluidType.Water, + ConcentrationBrix = 0.0, + Temperature = 15.0 // Agua fría + }, + + CurrentLevelM = 2.0, + TankPressureBar = 1.2, + MixingState = MixingState.Gradual, + MixingMotorRpm = 25.0 +}; +``` + +### **Caso 2: Sistema CIP Automatizado** +```csharp +// Escenario: Limpieza automática de tuberías de proceso +var cipTank = new osHydTank() +{ + Nombre = "Tanque CIP - Soda Cáustica", + CrossSectionalArea = 1.5, + MaxLevel = 2.5, + + // Solución de limpieza + PrimaryFluid = new FluidProperties + { + FluidType = FluidType.CausticSoda, + ConcentrationBrix = 0.0, + Temperature = 75.0 // Temperatura para limpieza efectiva + }, + + CurrentLevelM = 2.0, + TankPressureBar = 2.5, // Presión elevada para circulación + MixingState = MixingState.Active, + MixingMotorRpm = 50.0 // Mezcla vigorosa +}; +``` + +### **Caso 3: Tanque de Almacenamiento Multi-Producto** +```csharp +// Escenario: Tanque que puede manejar diferentes productos +var storageTank = new osHydTank() +{ + Nombre = "Tanque Almacén Flexible", + CrossSectionalArea = 5.0, // Tanque grande + MaxLevel = 4.0, + + // Inicialmente vacío (aire) + PrimaryFluid = new FluidProperties + { + FluidType = FluidType.Air, + ConcentrationBrix = 0.0, + Temperature = 20.0 + }, + + CurrentLevelM = 0.1, // Prácticamente vacío + TankPressureBar = 1.013, // Presión atmosférica + MixingState = MixingState.Idle +}; +``` + +--- + +## 🔧 Mejores Prácticas de Configuración + +### **Gestión de Temperaturas** + +#### ✅ **Buenas Prácticas** +```csharp +// Rangos de temperatura apropiados por tipo de fluido +var waterTemp = 5.0; // a 95.0 °C +var syrupTemp = 60.0; // a 120.0 °C (proceso) +var cipTemp = 65.0; // a 85.0 °C (efectividad de limpieza) +``` + +#### ❌ **Prácticas a Evitar** +```csharp +// NO usar temperaturas extremas sin justificación +var badTemp1 = -10.0; // Congelación no manejada +var badTemp2 = 150.0; // Por encima del punto de ebullición +``` + +### **Control de Concentración Brix** + +#### ✅ **Rangos Recomendados** +```csharp +// Jarabes comerciales típicos +var lightSyrup = 25.0; // Jarabe ligero +var mediumSyrup = 45.0; // Jarabe medio +var heavySyrup = 65.0; // Jarabe pesado +var concentrate = 85.0; // Concentrado máximo +``` + +#### ⚠️ **Límites de Seguridad** +```csharp +// Verificar límites antes de asignar +if (brixValue >= 0.0 && brixValue <= 100.0) +{ + fluid.ConcentrationBrix = brixValue; +} +``` + +### **Estados de Mezcla Apropiados** + +#### **Mezcla Gradual** - Para transiciones suaves +```csharp +tank.MixingState = MixingState.Gradual; +tank.MixingMotorRpm = 15.0; // RPM bajas para transición gradual +``` + +#### **Mezcla Activa** - Para homogeneización rápida +```csharp +tank.MixingState = MixingState.Active; +tank.MixingMotorRpm = 45.0; // RPM altas para mezcla vigorosa +``` + +#### **Sin Mezcla** - Para almacenamiento +```csharp +tank.MixingState = MixingState.Idle; +tank.MixingMotorRpm = 0.0; // Motor apagado +``` + +--- + +## 📊 Monitoreo y Control + +### **Indicadores Clave de Rendimiento (KPIs)** + +#### **Eficiencia de Mezcla** +```csharp +// Monitorear la homogeneidad de la mezcla +public double MixingEfficiency +{ + get + { + if (MixingState == MixingState.Idle) return 0.0; + + // Basado en tiempo de mezcla y RPM + var timefactor = MixingTimeMinutes / 10.0; // 10 min = 100% + var rpmFactor = MixingMotorRpm / 50.0; // 50 RPM = 100% + + return Math.Min(timeFactory * rpmFactor, 1.0) * 100.0; + } +} +``` + +#### **Calidad del Producto** +```csharp +// Verificar que la concentración esté en rango objetivo +public bool IsWithinQualitySpec(double targetBrix, double tolerance = 2.0) +{ + return Math.Abs(PrimaryFluid.ConcentrationBrix - targetBrix) <= tolerance; +} +``` + +#### **Consumo Energético** +```csharp +// Estimar consumo de energía del motor de mezcla +public double EnergyConsumptionKW +{ + get + { + if (MixingState == MixingState.Idle) return 0.0; + + // Potencia base + factor de RPM + var basePower = 1.5; // kW + var rpmFactor = MixingMotorRpm / 100.0; + + return basePower * rpmFactor * rpmFactor; // Potencia cuadrática con RPM + } +} +``` + +--- + +## 🔄 Secuencias de Proceso Típicas + +### **Secuencia 1: Preparación de Producto** +```csharp +public async Task PrepareProduct(double targetBrix, double targetVolume) +{ + // 1. Verificar tanque vacío + if (CurrentLevelM > MinLevel + 0.1) + throw new InvalidOperationException("Tank must be empty"); + + // 2. Cargar concentrado + PrimaryFluid = new FluidProperties + { + FluidType = FluidType.Syrup, + ConcentrationBrix = 85.0, + Temperature = 85.0 + }; + CurrentLevelM = 0.5; // Nivel inicial de concentrado + + // 3. Agregar agua para dilución + SecondaryFluid = new FluidProperties + { + FluidType = FluidType.Water, + Temperature = 15.0 + }; + + // 4. Iniciar mezcla gradual + MixingState = MixingState.Gradual; + MixingMotorRpm = 20.0; + + // 5. Monitorear hasta alcanzar objetivo + while (PrimaryFluid.ConcentrationBrix > targetBrix + 1.0) + { + await Task.Delay(1000); // Simular tiempo de proceso + // El sistema actualiza automáticamente la concentración + } + + // 6. Homogeneización final + MixingState = MixingState.Active; + MixingMotorRpm = 40.0; + await Task.Delay(5000); // 5 segundos de mezcla vigorosa + + // 7. Finalizar + MixingState = MixingState.Idle; +} +``` + +### **Secuencia 2: Limpieza CIP** +```csharp +public async Task CipCleaningCycle() +{ + // Fase 1: Pre-enjuague con agua + await PreRinse(); + + // Fase 2: Limpieza con soda cáustica + PrimaryFluid = new FluidProperties + { + FluidType = FluidType.CausticSoda, + Temperature = 75.0 + }; + + MixingState = MixingState.Active; + MixingMotorRpm = 50.0; + await Task.Delay(15000); // 15 segundos de limpieza + + // Fase 3: Enjuague final + await FinalRinse(); + + // Fase 4: Drenaje y secado + CurrentLevelM = MinLevel; + PrimaryFluid = new FluidProperties { FluidType = FluidType.Air }; + MixingState = MixingState.Idle; +} +``` + +--- + +## ⚠️ Consideraciones de Seguridad + +### **Límites Operativos** +```csharp +// Verificaciones de seguridad antes de cambios +public bool IsSafeToOperate() +{ + // Verificar presión + if (TankPressureBar > 5.0) + return false; // Presión demasiado alta + + // Verificar temperatura + if (PrimaryFluid.Temperature > 100.0 && PrimaryFluid.FluidType == FluidType.Water) + return false; // Agua sobrecalentada + + // Verificar nivel + if (CurrentLevelM > MaxLevel * 0.95) + return false; // Riesgo de desbordamiento + + return true; +} +``` + +### **Protecciones del Sistema** +```csharp +// Parada de emergencia +public void EmergencyStop() +{ + MixingState = MixingState.Idle; + MixingMotorRpm = 0.0; + + // Registrar evento + Logger.Warning($"Emergency stop activated on tank {Nombre}"); +} + +// Verificación de compatibilidad química +public bool AreFluidCompatible(FluidType fluid1, FluidType fluid2) +{ + // Verificar combinaciones seguras + var incompatible = new[] + { + (FluidType.CausticSoda, FluidType.Syrup) // Reacción química posible + }; + + return !incompatible.Contains((fluid1, fluid2)) && + !incompatible.Contains((fluid2, fluid1)); +} +``` + +--- + +## 📈 Optimización y Rendimiento + +### **Estrategias de Optimización** + +#### **Optimización Energética** +```csharp +// Perfil de RPM optimizado para eficiencia +public double OptimalMixingRpm(double viscosity) +{ + // RPM más bajas para fluidos más viscosos + return viscosity switch + { + <= 0.001 => 50.0, // Agua: RPM altas + <= 0.01 => 35.0, // Jarabes ligeros: RPM medias + <= 0.1 => 20.0, // Jarabes pesados: RPM bajas + _ => 15.0 // Muy viscoso: RPM mínimas + }; +} +``` + +#### **Gestión de Tiempo de Proceso** +```csharp +// Calcular tiempo óptimo de mezcla +public TimeSpan CalculateMixingTime(double volumeL, double targetHomogeneity = 0.95) +{ + var baseTime = volumeL / 1000.0 * 2.0; // 2 min por m³ + var viscosityFactor = PrimaryFluid.Viscosity / 0.001; // Factor de viscosidad + var rpmFactor = 50.0 / MixingMotorRpm; // Factor de velocidad + + var totalMinutes = baseTime * viscosityFactor * rpmFactor; + return TimeSpan.FromMinutes(totalMinutes); +} +``` + +--- + +## 🔍 Troubleshooting y Diagnóstico + +### **Problemas Comunes y Soluciones** + +#### **Problema**: Mezcla no homogénea +```csharp +// Diagnóstico automático +public string DiagnoseMixingIssues() +{ + if (MixingMotorRpm < 10.0) + return "RPM too low - increase mixing speed"; + + if (PrimaryFluid.Viscosity > 0.1) + return "High viscosity - extend mixing time or increase temperature"; + + if (Math.Abs(PrimaryFluid.Temperature - SecondaryFluid.Temperature) > 30.0) + return "Large temperature difference - preheat secondary fluid"; + + return "Mixing parameters within normal range"; +} +``` + +#### **Problema**: Concentración fuera de especificación +```csharp +// Corrección automática de concentración +public void AdjustConcentration(double targetBrix) +{ + var currentBrix = PrimaryFluid.ConcentrationBrix; + + if (currentBrix < targetBrix) + { + // Agregar concentrado + SecondaryFluid = new FluidProperties + { + FluidType = FluidType.Syrup, + ConcentrationBrix = 85.0, + Temperature = PrimaryFluid.Temperature + }; + } + else if (currentBrix > targetBrix) + { + // Agregar agua + SecondaryFluid = new FluidProperties + { + FluidType = FluidType.Water, + Temperature = PrimaryFluid.Temperature + }; + } + + MixingState = MixingState.Gradual; +} +``` + +--- + +## 📋 Checklist de Validación + +### **Pre-Operación** +- [ ] Verificar que el tanque esté limpio +- [ ] Confirmar compatibilidad de fluidos +- [ ] Verificar rangos de temperatura +- [ ] Comprobar límites de presión +- [ ] Validar niveles mínimos/máximos + +### **Durante Operación** +- [ ] Monitorear estabilidad de temperatura +- [ ] Verificar progreso de mezcla +- [ ] Controlar consumo energético +- [ ] Registrar parámetros clave +- [ ] Vigilar alarmas del sistema + +### **Post-Operación** +- [ ] Verificar calidad del producto final +- [ ] Documentar resultados del proceso +- [ ] Limpiar y preparar para siguiente lote +- [ ] Actualizar registros de mantenimiento +- [ ] Analizar eficiencia del proceso + +--- + +*Documento de Mejores Prácticas* +*Versión: 1.0 - Septiembre 2025* +*Autor: Sistema de Documentación CtrEditor* diff --git a/Documentation/Hidraulic/FluidManagementSystem.md b/Documentation/Hidraulic/FluidManagementSystem.md new file mode 100644 index 0000000..8f1aead --- /dev/null +++ b/Documentation/Hidraulic/FluidManagementSystem.md @@ -0,0 +1,288 @@ +# 🧪 Sistema de Gestión de Fluidos - CtrEditor + +En base a : + +"Quisiera mejorar el sistema hidraulico, quiero eliminar los parametros inncesarios de #file:osHydTank.cs y usar para todo el sistema hidraulico bar en vez PA, ademas quisiera poder modificar el nivel o los litros actuales del tanque y el tipo de fluido actual. Los fluidos pueden ser agua o jarabe con sacarosa en donde se permite usar el brix. quiero lograr que el motor hidraulico sea suficientemente correcto pero principalmente simple de usar. El objetivo de la aplicacion es una rapida simulacion de distintos fluidos hacia un mixer industrial, que puede recibir agua, jarabe o un mix o liquido con soda caustica para el cip. El fluido puede tener temperaturas diferentes. No estoy enfocado en los transitorios muy rapidos pero si en transitorios de mezcla como por ejemplo agua y luego jarabe. La idea es que el tanque puede tener una funcion de mezcla gradual, el tanque debe permitir tener un fluido principal y otro secundario y cantidad de litros de cada uno. Luego el secundario se vaciara primero y en un momento se mezclara ambos fluidos por una cantidad de litros de mezcla hasta que luego quedara solo el segundo fluido. En los pipe solo puede haber un solo tipo de fluido, que es el que esta saliendo actualmente del tanque que puede ser del tipo primario o secundario o mezcla durante los litros de mezcla. La idea es que las propiedades de fluido son transmitidas desde el tanque a el pipe segun el sentido del pipe actualmente ya que esto depende de el equilibrio de flujo actual en los pipes dado por las presiones de los tanques y por la presion de las bombas que generan un diferencial de presion. Los pipe en realidad no tiene sentido este es definido en funcion del flujo. Esto significa que segun el flujo el tipo de fluido se toma de la interfaz A o B . Los tanques en realidad solo tiene una presion definida y un nivel y un tipo primario / secundario de fluido. Las bombas son generadoras de presiones en un sentido con una cuva y con control de falta de nivel de fluido. Cuando un tanque no tiene nivel el fluido es de tipo aire. Este fluido hace que las bombas no puedan generar mas presion. Acepto sugerencias." + + +## 📋 Resumen del Sistema + +Este documento describe el nuevo sistema avanzado de gestión de fluidos implementado en CtrEditor. El sistema permite manejar múltiples tipos de fluidos industriales con capacidades de mezcla, control de temperatura y concentración, diseñado específicamente para simulaciones de procesos industriales alimentarios y sistemas CIP (Clean In Place). + +--- + +## 🎯 Arquitectura del Sistema + +### **FluidProperties** - Sistema Central de Fluidos +```csharp +public class FluidProperties +{ + public FluidType FluidType { get; set; } = FluidType.Water; + public double ConcentrationBrix { get; set; } = 0.0; + public double Temperature { get; set; } = 20.0; + public SolidColorBrush FluidColor { get; } + public double Density { get; } + public double Viscosity { get; } +} +``` + +### **Tipos de Fluidos Soportados** +```csharp +public enum FluidType +{ + Air, // Aire (sistema vacío) + Water, // Agua (procesos básicos) + Syrup, // Jarabe con sacarosa (industria alimentaria) + CausticSoda, // Soda cáustica (sistemas CIP) + Mix // Mezcla de fluidos +} +``` + +--- + +## 🔧 Nuevas Funcionalidades del Tanque Hidráulico + +### **Gestión de Fluidos Dual** +- **Fluido Primario**: Principal contenido del tanque +- **Fluido Secundario**: Para operaciones de mezcla +- **Mezcla Gradual**: Algoritmo de combinación automática + +### **Control de Mezcla Industrial** +```csharp +public enum MixingState +{ + Idle, // Motor de mezcla apagado + Active, // Mezcla activa a RPM constantes + Gradual // Mezcla gradual automática +} +``` + +### **Propiedades de Control Editables** +- **CurrentLevelM**: Nivel actual en metros (editable) +- **CurrentVolumeL**: Volumen actual en litros (editable) +- **TankPressureBar**: Presión en bar (convertido de Pa) +- **MixingMotorRpm**: Velocidad del motor de mezcla (0-100 RPM) + +--- + +## 📊 Unidades de Medida Actualizadas + +### **Presión** +- **Antigua**: Pascales (Pa) +- **Nueva**: Bar (más práctica para aplicaciones industriales) +- **Conversión**: 1 bar = 100,000 Pa + +### **Flujo** +- **Antigua**: m³/s (metros cúbicos por segundo) +- **Nueva**: L/min (litros por minuto) +- **Conversión**: 1 m³/s = 60,000 L/min + +### **Volumen** +- **Unidades**: Litros (L) +- **Conversión**: m³ × 1000 = L + +--- + +## 🧪 Cálculos de Propiedades de Fluidos + +### **Densidad (kg/m³)** +```csharp +public double Density => FluidType switch +{ + FluidType.Air => 1.225, + FluidType.Water => 1000.0, + FluidType.Syrup => 1000.0 + (ConcentrationBrix * 6.0), // Aumenta con Brix + FluidType.CausticSoda => 1530.0, // NaOH concentrado + FluidType.Mix => CalculateMixedDensity(), + _ => 1000.0 +}; +``` + +### **Viscosidad (Pa·s)** +```csharp +public double Viscosity => FluidType switch +{ + FluidType.Air => 1.81e-5, + FluidType.Water => 0.001, + FluidType.Syrup => 0.001 * Math.Pow(10, ConcentrationBrix / 25.0), // Exponencial con Brix + FluidType.CausticSoda => 0.003, // Más viscoso que agua + FluidType.Mix => CalculateMixedViscosity(), + _ => 0.001 +}; +``` + +### **Colores Distintivos** +- **Agua**: Azul claro (`#87CEEB`) +- **Jarabe**: Marrón dorado (`#DAA520`) +- **Soda Cáustica**: Púrpura (`#800080`) +- **Aire**: Gris claro (`#D3D3D3`) +- **Mezcla**: Color calculado por interpolación + +--- + +## 🔄 Sistema de Mezcla Gradual + +### **Algoritmo de Mezcla** +```csharp +public FluidProperties MixWith(FluidProperties other, double ratio) +{ + // Ratio: 0.0 = 100% this, 1.0 = 100% other + return new FluidProperties + { + FluidType = FluidType.Mix, + ConcentrationBrix = Lerp(this.ConcentrationBrix, other.ConcentrationBrix, ratio), + Temperature = Lerp(this.Temperature, other.Temperature, ratio) + }; +} +``` + +### **Estados de Mezcla** +1. **Idle**: Sin mezcla activa +2. **Active**: Mezcla continua a RPM constantes +3. **Gradual**: Transición automática de fluido secundario a primario + +--- + +## 🏭 Aplicaciones Industriales + +### **Industria Alimentaria** +- **Jarabes**: Control preciso de concentración Brix (0-100%) +- **Temperaturas**: Rango operativo de 5°C a 120°C +- **Mezcla**: Dilución controlada de jarabes concentrados + +### **Sistemas CIP (Clean In Place)** +- **Soda Cáustica**: Para limpieza de tuberías y tanques +- **Agua de Enjuague**: Etapas de pre y post lavado +- **Control de Temperatura**: Limpieza a temperaturas elevadas (60-85°C) + +### **Procesos de Mezcla** +- **Control de RPM**: 0-100 RPM para diferentes tipos de mezcla +- **Tiempo de Mezcla**: Control mediante estados de mezcla +- **Homogeneización**: Algoritmos de mezcla gradual + +--- + +## 🔧 Migración desde el Sistema Anterior + +### **Parámetros Eliminados** +- `ConnectedInletPipe` / `ConnectedOutletPipe`: Reemplazado por gestión automática +- Unidades en Pa: Convertidas automáticamente a bar +- Flujos en m³/s: Convertidos automáticamente a L/min + +### **Nuevas Propiedades** +```csharp +// Gestión de fluidos +public FluidProperties PrimaryFluid { get; set; } +public FluidProperties SecondaryFluid { get; set; } + +// Control de mezcla +public MixingState MixingState { get; set; } +public double MixingMotorRpm { get; set; } + +// Unidades actualizadas +public double TankPressureBar { get; set; } +public double CurrentVolumeL { get; set; } +public double FlowRateInletLMin { get; set; } +public double FlowRateOutletLMin { get; set; } +``` + +### **Compatibilidad** +- Conversión automática de unidades existentes +- Valores por defecto para nuevas propiedades +- Fluido primario inicializado como agua a 20°C + +--- + +## 📋 Lineamientos de Implementación + +### **1. Inicialización de Tanques** +```csharp +// Nuevo tanque con fluido predeterminado +var tank = new osHydTank() +{ + PrimaryFluid = new FluidProperties + { + FluidType = FluidType.Water, + Temperature = 20.0 + }, + CurrentLevelM = 1.0, + TankPressureBar = 1.013, // Presión atmosférica + MixingState = MixingState.Idle +}; +``` + +### **2. Operaciones de Mezcla** +```csharp +// Preparar mezcla de jarabe +tank.SecondaryFluid = new FluidProperties +{ + FluidType = FluidType.Syrup, + ConcentrationBrix = 65.0, + Temperature = 85.0 +}; +tank.MixingState = MixingState.Gradual; +tank.MixingMotorRpm = 25.0; +``` + +### **3. Sistemas CIP** +```csharp +// Configurar limpieza con soda cáustica +tank.PrimaryFluid = new FluidProperties +{ + FluidType = FluidType.CausticSoda, + Temperature = 75.0 +}; +tank.MixingState = MixingState.Active; +tank.MixingMotorRpm = 50.0; +``` + +--- + +## 🎯 Ventajas del Nuevo Sistema + +### **Precisión Industrial** +- Unidades estándar de la industria (bar, L/min) +- Cálculos precisos de propiedades de fluidos +- Control de concentración y temperatura + +### **Flexibilidad Operativa** +- Múltiples tipos de fluidos soportados +- Operaciones de mezcla complejas +- Estados de mezcla configurables + +### **Facilidad de Uso** +- Propiedades editables directamente +- Colores distintivos para identificación visual +- Conversiones automáticas de unidades + +### **Aplicabilidad Real** +- Diseñado para procesos industriales reales +- Soporte para sistemas CIP +- Escalabilidad para diferentes industrias + +--- + +## 📈 Futuras Expansiones + +### **Fluidos Adicionales** +- Alcoholes (etanol, isopropanol) +- Ácidos (acético, cítrico) +- Detergentes industriales +- Gases industriales + +### **Propiedades Avanzadas** +- pH y conductividad +- Punto de ebullición/congelación +- Tensión superficial +- Compatibilidad química + +### **Control Avanzado** +- Perfiles de temperatura +- Recetas de mezcla automáticas +- Optimización energética +- Trazabilidad de lotes + +--- + +*Documento actualizado: Septiembre 2025* +*Versión del Sistema: 2.0* diff --git a/Documentation/HydraulicComponents.md b/Documentation/Hidraulic/HydraulicComponents.md similarity index 100% rename from Documentation/HydraulicComponents.md rename to Documentation/Hidraulic/HydraulicComponents.md diff --git a/Documentation/HydraulicSimulator DOC.md b/Documentation/Hidraulic/HydraulicSimulator DOC.md similarity index 100% rename from Documentation/HydraulicSimulator DOC.md rename to Documentation/Hidraulic/HydraulicSimulator DOC.md diff --git a/Documentation/Hidraulic/TechnicalSpecifications.md b/Documentation/Hidraulic/TechnicalSpecifications.md new file mode 100644 index 0000000..c7dd202 --- /dev/null +++ b/Documentation/Hidraulic/TechnicalSpecifications.md @@ -0,0 +1,517 @@ +# 🔬 Especificaciones Técnicas - Sistema de Fluidos CtrEditor + +## 📋 Resumen Técnico + +Este documento proporciona las especificaciones técnicas detalladas del sistema de gestión de fluidos de CtrEditor, incluyendo algoritmos de cálculo, especificaciones de rendimiento, límites del sistema y detalles de implementación para desarrolladores. + +--- + +## 🧮 Algoritmos de Cálculo + +### **Cálculo de Densidad por Tipo de Fluido** + +#### **Agua** +```csharp +// Densidad del agua en función de la temperatura (kg/m³) +public static double WaterDensity(double temperatureC) +{ + // Ecuación polinomial para agua pura (0-100°C) + var t = temperatureC; + return 1000.0 - 0.0178 * t - 0.0000676 * t * t + 0.0000001 * t * t * t; +} +``` + +#### **Jarabe de Sacarosa** +```csharp +// Densidad del jarabe basada en concentración Brix y temperatura +public static double SyrupDensity(double brix, double temperatureC) +{ + // Correlación estándar para jarabes de sacarosa + var densityAt20C = 1000.0 + (brix * 3.86) + (brix * brix * 0.0166); + + // Corrección por temperatura (kg/m³/°C) + var tempCorrection = (temperatureC - 20.0) * (-0.3); + + return densityAt20C + tempCorrection; +} +``` + +#### **Soda Cáustica (NaOH)** +```csharp +// Densidad de solución de NaOH en función de concentración y temperatura +public static double CausticSodaDensity(double concentrationPercent, double temperatureC) +{ + // Para NaOH al 50% (concentración típica industrial) + var baseDesity = 1530.0; // kg/m³ a 20°C + + // Corrección por temperatura + var tempCorrection = (temperatureC - 20.0) * (-0.8); + + return baseDensity + tempCorrection; +} +``` + +### **Cálculo de Viscosidad** + +#### **Viscosidad de Jarabes (Ecuación de Arrhenius)** +```csharp +public static double SyrupViscosity(double brix, double temperatureC) +{ + // Parámetros de la ecuación de Arrhenius para sacarosa + var A = 1.68e-6; // Factor pre-exponencial + var B = 0.0295; // Dependencia de concentración + var Ea = 16500.0; // Energía de activación (J/mol) + var R = 8.314; // Constante de gas (J/mol·K) + var T = temperatureC + 273.15; // Temperatura absoluta (K) + + // Viscosidad base del agua + var waterVisc = 0.001 * Math.Exp(1301.0 / T - 1.5); + + // Factor de concentración + var concFactor = Math.Exp(B * brix); + + // Factor de temperatura + var tempFactor = Math.Exp(Ea / (R * T)); + + return waterVisc * concFactor * tempFactor; +} +``` + +### **Algoritmo de Mezcla Gradual** + +#### **Interpolación Ponderada para Propiedades** +```csharp +public class GradualMixingAlgorithm +{ + public static FluidProperties CalculateMixture( + FluidProperties primary, + FluidProperties secondary, + double mixingProgress) // 0.0 a 1.0 + { + return new FluidProperties + { + FluidType = FluidType.Mix, + + // Interpolación lineal de concentración + ConcentrationBrix = Lerp( + primary.ConcentrationBrix, + secondary.ConcentrationBrix, + mixingProgress), + + // Mezcla térmica (conservación de energía) + Temperature = ThermalMixing( + primary.Temperature, primary.Density, + secondary.Temperature, secondary.Density, + mixingProgress) + }; + } + + private static double ThermalMixing( + double temp1, double density1, + double temp2, double density2, + double ratio) + { + // Capacidad calorífica específica (J/kg·K) + var cp1 = 4186.0; // Agua/jarabe + var cp2 = 4186.0; + + // Balance de energía térmica + var energy1 = (1.0 - ratio) * density1 * cp1 * temp1; + var energy2 = ratio * density2 * cp2 * temp2; + var totalMass = (1.0 - ratio) * density1 + ratio * density2; + + return (energy1 + energy2) / (totalMass * cp1); + } +} +``` + +--- + +## ⚙️ Especificaciones de Rendimiento + +### **Límites del Sistema** + +#### **Rangos Operativos** +| Parámetro | Mínimo | Máximo | Unidad | Notas | +|-----------|--------|--------|--------|-------| +| Temperatura | -5.0 | 120.0 | °C | Rango industrial estándar | +| Presión | 0.1 | 10.0 | bar | Sistemas de baja-media presión | +| Concentración Brix | 0.0 | 100.0 | % | Sacarosa en solución acuosa | +| Nivel de Tanque | 0.0 | 10.0 | m | Tanques industriales típicos | +| Volumen | 0.0 | 100,000 | L | Desde laboratorio hasta planta | +| RPM de Mezcla | 0.0 | 100.0 | RPM | Mezcladores industriales | + +#### **Precisión de Cálculos** +| Cálculo | Precisión | Tolerancia | +|---------|-----------|------------| +| Densidad | ±0.1% | ±1.0 kg/m³ | +| Viscosidad | ±2.0% | Dependiente de fluido | +| Temperatura de Mezcla | ±0.1°C | Conservación energética | +| Concentración Final | ±0.5% | Balance de masa | + +### **Rendimiento Computacional** + +#### **Tiempos de Respuesta Objetivo** +```csharp +// Benchmarks de rendimiento +public class PerformanceSpecs +{ + public static readonly TimeSpan FluidCalculation = TimeSpan.FromMilliseconds(1); + public static readonly TimeSpan MixingUpdate = TimeSpan.FromMilliseconds(5); + public static readonly TimeSpan TankSimulation = TimeSpan.FromMilliseconds(10); + public static readonly TimeSpan NetworkUpdate = TimeSpan.FromMilliseconds(50); +} +``` + +#### **Uso de Memoria** +- **FluidProperties**: ~200 bytes por instancia +- **osHydTank**: ~8 KB por tanque (incluyendo UI) +- **Simulación Completa**: <100 MB para 1000 componentes + +--- + +## 🔧 Detalles de Implementación + +### **Estructura de Datos Optimizada** + +#### **FluidProperties - Implementación Interna** +```csharp +[Serializable] +public sealed class FluidProperties : INotifyPropertyChanged, ICloneable +{ + // Campos privados para rendimiento + private FluidType _fluidType = FluidType.Water; + private double _concentrationBrix = 0.0; + private double _temperature = 20.0; + + // Cache para propiedades calculadas + private double? _cachedDensity; + private double? _cachedViscosity; + private SolidColorBrush? _cachedColor; + + // Invalidación de cache + private void InvalidateCache() + { + _cachedDensity = null; + _cachedViscosity = null; + _cachedColor = null; + } + + // Propiedades con lazy evaluation + public double Density + { + get + { + if (!_cachedDensity.HasValue) + _cachedDensity = CalculateDensity(); + return _cachedDensity.Value; + } + } +} +``` + +### **Optimizaciones de Cálculo** + +#### **Lookup Tables para Funciones Costosas** +```csharp +public static class FluidLookupTables +{ + // Tabla pre-calculada para viscosidad de jarabes + private static readonly double[,] SyrupViscosityTable = + PrecomputeSyrupViscosity(); + + private static double[,] PrecomputeSyrupViscosity() + { + var table = new double[101, 121]; // Brix × Temperatura + + for (int brix = 0; brix <= 100; brix++) + { + for (int temp = 0; temp <= 120; temp++) + { + table[brix, temp] = CalculateExactSyrupViscosity(brix, temp); + } + } + + return table; + } + + public static double GetSyrupViscosity(double brix, double temperature) + { + // Interpolación bilineal en la tabla + return BilinearInterpolation(SyrupViscosityTable, brix, temperature); + } +} +``` + +### **Gestión de Estados de Mezcla** + +#### **Máquina de Estados para Mezcla** +```csharp +public class MixingStateMachine +{ + private MixingState _currentState = MixingState.Idle; + private DateTime _stateStartTime = DateTime.Now; + private double _mixingProgress = 0.0; + + public void Update(TimeSpan deltaTime) + { + switch (_currentState) + { + case MixingState.Idle: + // No action required + break; + + case MixingState.Active: + UpdateActiveMixing(deltaTime); + break; + + case MixingState.Gradual: + UpdateGradualMixing(deltaTime); + break; + } + } + + private void UpdateGradualMixing(TimeSpan deltaTime) + { + var mixingRate = CalculateMixingRate(); + _mixingProgress += mixingRate * deltaTime.TotalSeconds; + + if (_mixingProgress >= 1.0) + { + CompleteMixing(); + TransitionTo(MixingState.Idle); + } + } +} +``` + +--- + +## 📊 Validación y Testing + +### **Test Cases Críticos** + +#### **Conservación de Masa** +```csharp +[Test] +public void TestMassConservation() +{ + var tank = new osHydTank() + { + CrossSectionalArea = 1.0, + PrimaryFluid = new FluidProperties + { + FluidType = FluidType.Water, + Temperature = 20.0 + } + }; + + var initialMass = tank.CurrentVolumeL * tank.PrimaryFluid.Density / 1000.0; + + // Agregar fluido secundario + tank.SecondaryFluid = new FluidProperties + { + FluidType = FluidType.Syrup, + ConcentrationBrix = 65.0, + Temperature = 85.0 + }; + + // Simular mezcla completa + tank.MixingState = MixingState.Active; + SimulateMixing(tank, TimeSpan.FromMinutes(10)); + + var finalMass = tank.CurrentVolumeL * tank.PrimaryFluid.Density / 1000.0; + + // Verificar conservación de masa (±0.1%) + Assert.AreEqual(initialMass, finalMass, initialMass * 0.001); +} +``` + +#### **Conservación de Energía Térmica** +```csharp +[Test] +public void TestThermalEnergyConservation() +{ + // Mezcla de agua caliente y fría + var hotWater = new FluidProperties + { + FluidType = FluidType.Water, + Temperature = 80.0 + }; + + var coldWater = new FluidProperties + { + FluidType = FluidType.Water, + Temperature = 20.0 + }; + + // Mezcla 50/50 + var mixture = hotWater.MixWith(coldWater, 0.5); + + // Temperatura esperada: ~50°C + Assert.AreEqual(50.0, mixture.Temperature, 0.1); +} +``` + +### **Benchmarks de Rendimiento** + +#### **Stress Test de Simulación** +```csharp +[Test] +[Category("Performance")] +public void StressTest_1000Tanks() +{ + var tanks = new List(); + var stopwatch = Stopwatch.StartNew(); + + // Crear 1000 tanques + for (int i = 0; i < 1000; i++) + { + tanks.Add(CreateRandomTank()); + } + + stopwatch.Stop(); + Assert.Less(stopwatch.ElapsedMilliseconds, 5000); // <5 segundos + + // Simular un ciclo completo + stopwatch.Restart(); + + foreach (var tank in tanks) + { + tank.UpdateFluidProperties(); + tank.CalculateFlowRates(); + } + + stopwatch.Stop(); + Assert.Less(stopwatch.ElapsedMilliseconds, 100); // <100ms por ciclo +} +``` + +--- + +## 🔒 Consideraciones de Seguridad y Robustez + +### **Validación de Entrada** + +#### **Sanitización de Parámetros** +```csharp +public static class InputValidation +{ + public static double ValidateTemperature(double temp, string paramName) + { + if (double.IsNaN(temp) || double.IsInfinity(temp)) + throw new ArgumentException($"Invalid temperature: {temp}", paramName); + + if (temp < -273.15) + throw new ArgumentOutOfRangeException(paramName, "Temperature below absolute zero"); + + if (temp > 1000.0) + throw new ArgumentOutOfRangeException(paramName, "Temperature too high for system"); + + return temp; + } + + public static double ValidateBrix(double brix, string paramName) + { + if (double.IsNaN(brix) || double.IsInfinity(brix)) + throw new ArgumentException($"Invalid Brix value: {brix}", paramName); + + return Math.Clamp(brix, 0.0, 100.0); + } +} +``` + +### **Manejo de Errores de Cálculo** + +#### **Recuperación de Errores Numéricos** +```csharp +public static class SafeCalculations +{ + public static double SafeDivision(double numerator, double denominator, double fallback = 0.0) + { + if (Math.Abs(denominator) < 1e-10) + return fallback; + + var result = numerator / denominator; + + if (double.IsNaN(result) || double.IsInfinity(result)) + return fallback; + + return result; + } + + public static double SafeExponential(double exponent, double maxResult = 1e6) + { + if (exponent > Math.Log(maxResult)) + return maxResult; + + if (exponent < -50.0) // Underflow protection + return 0.0; + + return Math.Exp(exponent); + } +} +``` + +--- + +## 📈 Métricas de Calidad del Código + +### **Cobertura de Tests** +- **Objetivo**: >95% cobertura de líneas +- **Crítico**: 100% para algoritmos de cálculo +- **Tests de Integración**: Escenarios completos de proceso + +### **Complejidad Ciclomática** +- **Máximo**: 10 por método +- **Promedio**: <5 por clase +- **Refactoring**: Automático cuando se exceden límites + +### **Documentación** +- **XML Documentation**: 100% de métodos públicos +- **Ejemplos de Uso**: Para cada funcionalidad principal +- **Diagramas de Flujo**: Para algoritmos complejos + +--- + +## 🔄 Versionado y Compatibilidad + +### **Evolución del API** + +#### **Versionado Semántico** +- **Major**: Cambios incompatibles (ej: 1.x → 2.x) +- **Minor**: Nuevas funcionalidades compatibles (ej: 2.1 → 2.2) +- **Patch**: Correcciones de bugs (ej: 2.1.0 → 2.1.1) + +#### **Migración de Datos** +```csharp +public class FluidDataMigration +{ + public static FluidProperties MigrateFromV1(LegacyFluidData legacy) + { + return new FluidProperties + { + FluidType = MapLegacyType(legacy.Type), + Temperature = legacy.TempCelsius, + ConcentrationBrix = legacy.Concentration ?? 0.0 + }; + } + + private static FluidType MapLegacyType(string legacyType) + { + return legacyType.ToLower() switch + { + "water" => FluidType.Water, + "syrup" => FluidType.Syrup, + "cleaning" => FluidType.CausticSoda, + _ => FluidType.Water + }; + } +} +``` + +--- + +*Especificaciones Técnicas Detalladas* +*Versión: 2.0 - Septiembre 2025* +*Documento de Referencia para Desarrolladores* diff --git a/ObjetosSim/HydraulicComponents/FluidProperties.cs b/ObjetosSim/HydraulicComponents/FluidProperties.cs new file mode 100644 index 0000000..991e59c --- /dev/null +++ b/ObjetosSim/HydraulicComponents/FluidProperties.cs @@ -0,0 +1,184 @@ +using System; +using System.ComponentModel; +using Newtonsoft.Json; + +namespace CtrEditor.ObjetosSim +{ + /// + /// Tipos de fluido en el sistema hidráulico + /// + public enum FluidType + { + [Description("Aire (tanque vacío)")] + Air, + [Description("Agua")] + Water, + [Description("Jarabe con sacarosa")] + Syrup, + [Description("Soda cáustica (CIP)")] + CausticSoda, + [Description("Mezcla")] + Mix + } + + /// + /// Propiedades de un fluido en el sistema + /// + [JsonObject(MemberSerialization.OptIn)] + public class FluidProperties + { + [JsonProperty] + public FluidType Type { get; set; } = FluidType.Water; + + [JsonProperty] + public double Temperature { get; set; } = 20.0; // °C + + [JsonProperty] + public double Brix { get; set; } = 0.0; // % para jarabes + + [JsonProperty] + public double Concentration { get; set; } = 0.0; // % para soda cáustica + + [JsonProperty] + public double Volume { get; set; } = 0.0; // Litros + + /// + /// Densidad del fluido en kg/L + /// + [JsonIgnore] + public double Density + { + get + { + return Type switch + { + FluidType.Air => 0.0012, // kg/L a 20°C + FluidType.Water => 1.0 - (Temperature - 20) * 0.0002, // Aproximación + FluidType.Syrup => 1.0 + (Brix * 0.004), // Aproximación: +0.4% densidad por grado Brix + FluidType.CausticSoda => 1.0 + (Concentration * 0.01), // Aproximación + FluidType.Mix => 1.0, // Se calculará en runtime + _ => 1.0 + }; + } + } + + /// + /// Viscosidad dinámica en cP (centipoise) + /// + [JsonIgnore] + public double Viscosity + { + get + { + return Type switch + { + FluidType.Air => 0.018, + FluidType.Water => Math.Max(0.3, 1.0 - (Temperature - 20) * 0.025), // Disminuye con temperatura + FluidType.Syrup => Math.Max(1.0, 1.0 + Math.Pow(Brix / 10.0, 2.5)), // Aumenta exponencialmente con Brix + FluidType.CausticSoda => Math.Max(0.8, 1.0 + Concentration * 0.02), + FluidType.Mix => 1.0, // Se calculará en runtime + _ => 1.0 + }; + } + } + + /// + /// Color representativo del fluido para UI + /// + [JsonIgnore] + public string Color + { + get + { + return Type switch + { + FluidType.Air => "#E0E0E0", + FluidType.Water => "#87CEEB", + FluidType.Syrup => $"#{Math.Min(255, 139 + (int)(Brix * 2)):X2}{Math.Min(255, 69 + (int)(Brix)):X2}19", // Marrón más intenso con más Brix + FluidType.CausticSoda => "#FF6347", + FluidType.Mix => "#DDA0DD", + _ => "#CCCCCC" + }; + } + } + + /// + /// Descripción del fluido + /// + [JsonIgnore] + public string Description + { + get + { + return Type switch + { + FluidType.Air => "Aire", + FluidType.Water => $"Agua {Temperature:F1}°C", + FluidType.Syrup => $"Jarabe {Brix:F1}°Bx {Temperature:F1}°C", + FluidType.CausticSoda => $"NaOH {Concentration:F1}% {Temperature:F1}°C", + FluidType.Mix => $"Mezcla {Temperature:F1}°C", + _ => "Fluido" + }; + } + } + + public FluidProperties() + { + } + + public FluidProperties(FluidType type, double temperature = 20.0, double brix = 0.0, double concentration = 0.0) + { + Type = type; + Temperature = temperature; + Brix = brix; + Concentration = concentration; + } + + /// + /// Crea una copia de las propiedades del fluido + /// + public FluidProperties Clone() + { + return new FluidProperties(Type, Temperature, Brix, Concentration) { Volume = Volume }; + } + + /// + /// Mezcla este fluido con otro fluido + /// + public FluidProperties MixWith(FluidProperties other, double mixRatio) + { + if (other == null || mixRatio <= 0) return this.Clone(); + if (mixRatio >= 1) return other.Clone(); + + var mixed = new FluidProperties + { + Type = FluidType.Mix, + Temperature = Temperature * (1 - mixRatio) + other.Temperature * mixRatio, + Brix = Brix * (1 - mixRatio) + other.Brix * mixRatio, + Concentration = Concentration * (1 - mixRatio) + other.Concentration * mixRatio, + Volume = Volume + other.Volume + }; + + return mixed; + } + } + + /// + /// Estado de mezcla del tanque + /// + public enum MixingState + { + [Description("Solo fluido primario")] + PrimaryOnly, + [Description("Solo fluido secundario")] + SecondaryOnly, + [Description("Vaciando secundario")] + EmptyingSecondary, + [Description("Mezclando")] + Mixing, + [Description("Vaciando primario")] + EmptyingPrimary, + [Description("Vacío")] + Empty + } +} diff --git a/ObjetosSim/HydraulicComponents/osHydTank.cs b/ObjetosSim/HydraulicComponents/osHydTank.cs index 9a908f4..1fa392f 100644 --- a/ObjetosSim/HydraulicComponents/osHydTank.cs +++ b/ObjetosSim/HydraulicComponents/osHydTank.cs @@ -28,25 +28,31 @@ namespace CtrEditor.ObjetosSim // Private Fields - private double _tankPressure = 101325.0; // Pa (1 atm por defecto) - private double _currentLevel = 1.0; // m - private double _maxLevel = 2.0; // m - private double _minLevel = 0.1; // m + private double _tankPressure = 1.013; // bar (1 atm por defecto) + private double _currentLevelM = 1.0; // m + private double _maxLevelM = 2.0; // m + private double _minLevelM = 0.1; // m private double _crossSectionalArea = 1.0; // m² - private double _currentVolume = 1.0; // m³ - private double _maxVolume = 2.0; // m³ - private double _inletFlow = 0.0; // m³/s - private double _outletFlow = 0.0; // m³/s - private double _currentPressure = 101325.0; // Pa + private double _currentVolumeL = 1000.0; // L + private double _maxVolumeL = 2000.0; // L + private double _inletFlow = 0.0; // L/min + private double _outletFlow = 0.0; // L/min + private double _currentPressure = 1.013; // bar private bool _isFixedPressure = true; private HydraulicTankType _tankType = HydraulicTankType.Intermediate; - private string _connectedInletPipe = ""; - private string _connectedOutletPipe = ""; 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 + [JsonIgnore] private object _levelLock = new object(); - private PropertyChangedEventHandler? inletPropertyChangedHandler; - private PropertyChangedEventHandler? outletPropertyChangedHandler; // Enums @@ -100,12 +106,13 @@ namespace CtrEditor.ObjetosSim // 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(); - - // Debug: Confirmar que el constructor se ejecuta + // Cálculos seguros + SafeUpdateVolumeCalculations(); + IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros + SafeUpdateTankPressure(); + + // Inicializar estado de mezcla + UpdateMixingState(); // Debug: Confirmar que el constructor se ejecuta Debug.WriteLine($"osHydTank Constructor: Nombre='{Nombre}', Tamaño={Tamano}, ZIndex={zIndex_fromFrames}, IsVisFilter={IsVisFilter}, Lock_movement={Lock_movement}"); } catch (Exception ex) @@ -147,56 +154,47 @@ namespace CtrEditor.ObjetosSim // Se llama cuando inicia la simulación - crear objeto hidráulico si no existe if (SimHydraulicTank == null) { - SimHydraulicTank = hydraulicSimulationManager.AddTank(TankPressure, CurrentLevel, MaxLevel, MinLevel, CrossSectionalArea, IsFixedPressure); + // Convertir bar a Pa para el sistema hidráulico interno + var pressurePa = TankPressure * 100000.0; + SimHydraulicTank = hydraulicSimulationManager.AddTank(pressurePa, CurrentLevelM, MaxLevelM, MinLevelM, CrossSectionalArea, IsFixedPressure); SimHydraulicTank.SimObjectType = "HydraulicTank"; SimHydraulicTank.WpfObject = this; SimHydraulicTank.Nombre = Nombre; } else { - // Actualizar propiedades si el objeto ya existe - SimHydraulicTank.TankPressure = TankPressure; - SimHydraulicTank.CurrentLevel = CurrentLevel; - SimHydraulicTank.MaxLevel = MaxLevel; - SimHydraulicTank.MinLevel = MinLevel; + // Actualizar propiedades si el objeto ya existe (convertir bar a Pa) + SimHydraulicTank.TankPressure = TankPressure * 100000.0; + SimHydraulicTank.CurrentLevel = CurrentLevelM; + SimHydraulicTank.MaxLevel = MaxLevelM; + SimHydraulicTank.MinLevel = MinLevelM; SimHydraulicTank.CrossSectionalArea = CrossSectionalArea; SimHydraulicTank.IsFixedPressure = IsFixedPressure; } } - public override void UpdateGeometryStep() - { - // Los objetos hidráulicos actualizan sus resultados - // a través de ApplySimulationResults() desde HydraulicSimulationManager - } - public override void UpdateControl(int elapsedMilliseconds) { // Actualizar propiedades desde la simulación hidráulica if (SimHydraulicTank != null) { - InletFlow = SimHydraulicTank.InletFlow; - OutletFlow = SimHydraulicTank.OutletFlow; - CurrentPressure = SimHydraulicTank.CurrentPressure; + // Convertir de m³/s a L/min + InletFlow = SimHydraulicTank.InletFlow * 60000.0; + OutletFlow = SimHydraulicTank.OutletFlow * 60000.0; + + // Convertir de Pa a bar + CurrentPressure = SimHydraulicTank.CurrentPressure / 100000.0; // Actualizar nivel del tanque basado en el balance de flujo - double deltaTime = elapsedMilliseconds / 1000.0; // Convertir a segundos + double deltaTime = elapsedMilliseconds / 60000.0; // Convertir a minutos if (deltaTime > 0) { UpdateLevelFromFlowBalance(deltaTime); } } - // Actualizar el color según el estado del tanque - var percentage = FillPercentage; - if (percentage < 20) - ColorButton_oculto = Brushes.Red; - else if (percentage < 50) - ColorButton_oculto = Brushes.Orange; - else if (percentage < 80) - ColorButton_oculto = Brushes.Yellow; - else - ColorButton_oculto = Brushes.LightBlue; + // Actualizar el color según el fluido actual + UpdateMixingState(); } @@ -236,17 +234,34 @@ namespace CtrEditor.ObjetosSim } [Category("🏗️ Configuración del Tanque")] - [DisplayName("Nivel máximo")] - [Description("Nivel máximo permitido en el tanque (m)")] - public double MaxLevel + [DisplayName("Volumen máximo")] + [Description("Volumen máximo del tanque (L)")] + public double MaxVolumeL { - get => _maxLevel; + get => _maxVolumeL; set { - if (SetProperty(ref _maxLevel, Math.Max(0.2, value))) + if (SetProperty(ref _maxVolumeL, Math.Max(10, value))) { - if (_currentLevel > _maxLevel) - CurrentLevel = _maxLevel; + if (_currentVolumeL > _maxVolumeL) + CurrentVolumeL = _maxVolumeL; + SafeUpdateVolumeCalculations(); + } + } + } + + [Category("🏗️ Configuración del Tanque")] + [DisplayName("Nivel máximo")] + [Description("Nivel máximo permitido en el tanque (m)")] + public double MaxLevelM + { + get => _maxLevelM; + set + { + if (SetProperty(ref _maxLevelM, Math.Max(0.2, value))) + { + if (_currentLevelM > _maxLevelM) + CurrentLevelM = _maxLevelM; SafeUpdateVolumeCalculations(); } } @@ -255,15 +270,15 @@ namespace CtrEditor.ObjetosSim [Category("🏗️ Configuración del Tanque")] [DisplayName("Nivel mínimo")] [Description("Nivel mínimo permitido en el tanque (m)")] - public double MinLevel + public double MinLevelM { - get => _minLevel; + get => _minLevelM; set { - if (SetProperty(ref _minLevel, Math.Max(0.0, Math.Min(value, _maxLevel - 0.1)))) + if (SetProperty(ref _minLevelM, Math.Max(0.0, Math.Min(value, _maxLevelM - 0.1)))) { - if (_currentLevel < _minLevel) - CurrentLevel = _minLevel; + if (_currentLevelM < _minLevelM) + CurrentLevelM = _minLevelM; SafeUpdateVolumeCalculations(); } } @@ -291,7 +306,7 @@ namespace CtrEditor.ObjetosSim [Category("🔧 Sistema de Presión")] [DisplayName("Presión del tanque")] - [Description("Presión mantenida en el tanque (Pa). En sistemas reales controlada por PID")] + [Description("Presión mantenida en el tanque (bar). En sistemas reales controlada por PID")] public double TankPressure { get => _tankPressure; @@ -305,16 +320,6 @@ namespace CtrEditor.ObjetosSim } } - [Category("🔧 Sistema de Presión")] - [DisplayName("Presión (bar)")] - [Description("Presión del tanque en bar")] - [JsonIgnore] - public double TankPressureBar - { - get => TankPressure / 100000.0; - set => TankPressure = value * 100000.0; - } - // Properties - Current State @@ -322,20 +327,20 @@ namespace CtrEditor.ObjetosSim [Category("📊 Estado Actual")] [DisplayName("Nivel actual")] [Description("Nivel actual de líquido en el tanque (m)")] - [JsonIgnore] - public double CurrentLevel + public double CurrentLevelM { - get => _currentLevel; - private set + get => _currentLevelM; + set { EnsureLockInitialized(); lock (_levelLock) { - var clampedLevel = Math.Max(_minLevel, Math.Min(_maxLevel, value)); - if (SetProperty(ref _currentLevel, clampedLevel)) + var clampedLevel = Math.Max(_minLevelM, Math.Min(_maxLevelM, value)); + if (SetProperty(ref _currentLevelM, clampedLevel)) { SafeUpdateVolumeCalculations(); OnPropertyChanged(nameof(FillPercentage)); + UpdateMixingState(); } } } @@ -343,33 +348,38 @@ namespace CtrEditor.ObjetosSim [Category("📊 Estado Actual")] [DisplayName("Volumen actual")] - [Description("Volumen actual de líquido en el tanque (m³)")] - [JsonIgnore] - public double CurrentVolume + [Description("Volumen actual de líquido en el tanque (L)")] + public double CurrentVolumeL { - get => _currentVolume; - private set => SetProperty(ref _currentVolume, value); - } - - [Category("📊 Estado Actual")] - [DisplayName("Volumen máximo")] - [Description("Volumen máximo del tanque (m³)")] - [JsonIgnore] - public double MaxVolume - { - get => _maxVolume; - private set => SetProperty(ref _maxVolume, value); + 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 = _minLevelM + (clampedVolume / 1000.0) / _crossSectionalArea; + _currentLevelM = Math.Max(_minLevelM, Math.Min(_maxLevelM, _currentLevelM)); + OnPropertyChanged(nameof(CurrentLevelM)); + OnPropertyChanged(nameof(FillPercentage)); + UpdateMixingState(); + } + } + } } [Category("📊 Estado Actual")] [DisplayName("Porcentaje llenado")] [Description("Porcentaje de llenado del tanque")] [JsonIgnore] - public double FillPercentage => (_currentLevel - _minLevel) / (_maxLevel - _minLevel) * 100.0; + public double FillPercentage => (_currentLevelM - _minLevelM) / (_maxLevelM - _minLevelM) * 100.0; [Category("📊 Estado Actual")] [DisplayName("Flujo entrada")] - [Description("Flujo de entrada actual (m³/s)")] + [Description("Flujo de entrada actual (L/min)")] [JsonIgnore] public double InletFlow { @@ -379,7 +389,7 @@ namespace CtrEditor.ObjetosSim [Category("📊 Estado Actual")] [DisplayName("Flujo salida")] - [Description("Flujo de salida actual (m³/s)")] + [Description("Flujo de salida actual (L/min)")] [JsonIgnore] public double OutletFlow { @@ -389,13 +399,13 @@ namespace CtrEditor.ObjetosSim [Category("📊 Estado Actual")] [DisplayName("Balance flujo")] - [Description("Balance de flujo (entrada - salida) (m³/s)")] + [Description("Balance de flujo (entrada - salida) (L/min)")] [JsonIgnore] public double FlowBalance => InletFlow - OutletFlow; [Category("📊 Estado Actual")] [DisplayName("Presión actual")] - [Description("Presión actual en el tanque (Pa)")] + [Description("Presión actual en el tanque (bar)")] [JsonIgnore] public double CurrentPressure { @@ -403,48 +413,213 @@ namespace CtrEditor.ObjetosSim private set => SetProperty(ref _currentPressure, value); } - [Category("📊 Estado Actual")] - [DisplayName("Presión actual (bar)")] - [Description("Presión actual en bar")] - [JsonIgnore] - public double CurrentPressureBar => CurrentPressure / 100000.0; - - // Properties - Connections + // Properties - Fluids and Mixing - [Category("🔗 Conexiones")] - [DisplayName("Tubería entrada")] - [Description("Nombre de la tubería conectada a la entrada")] - public string ConnectedInletPipe + [Category("🧪 Fluido Primario")] + [DisplayName("Tipo de fluido")] + [Description("Tipo del fluido primario en el tanque")] + public FluidType PrimaryFluidType { - get => _connectedInletPipe; + get => _primaryFluid.Type; set { - if (SetProperty(ref _connectedInletPipe, value ?? "")) + if (_primaryFluid.Type != value) { - ManageInletConnection(); + _primaryFluid.Type = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(CurrentFluidDescription)); InvalidateHydraulicNetwork(); } } } - [Category("🔗 Conexiones")] - [DisplayName("Tubería salida")] - [Description("Nombre de la tubería conectada a la salida")] - public string ConnectedOutletPipe + [Category("🧪 Fluido Primario")] + [DisplayName("Temperatura")] + [Description("Temperatura del fluido primario (°C)")] + public double PrimaryTemperature { - get => _connectedOutletPipe; + get => _primaryFluid.Temperature; set { - if (SetProperty(ref _connectedOutletPipe, value ?? "")) + if (Math.Abs(_primaryFluid.Temperature - value) > 0.01) { - ManageOutletConnection(); + _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 @@ -459,8 +634,10 @@ namespace CtrEditor.ObjetosSim // El tanque siempre es un nodo de presión fija si está configurado así if (IsFixedPressure) { - nodes.Add(new HydraulicNodeDefinition(Nombre, true, TankPressure, GetTankDescription())); - //Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F0} Pa ({TankPressureBar:F2} bar)"); + // Convertir bar a Pa para el sistema hidráulico interno + var pressurePa = TankPressure * 100000.0; + nodes.Add(new HydraulicNodeDefinition(Nombre, true, pressurePa, GetTankDescription())); + //Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F2} bar ({pressurePa:F0} Pa)"); } else { @@ -471,6 +648,30 @@ namespace CtrEditor.ObjetosSim return nodes; } + public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) + { + var deltaTime = GetDeltaTime(); + + // Obtener flujos conectados + UpdateFlowsFromConnectedPipes(flows); + + // Actualizar presión (convertir de Pa a bar) + if (pressures.ContainsKey(Nombre)) + { + CurrentPressure = pressures[Nombre] / 100000.0; // Pa a bar + } + + // Actualizar nivel basado en balance de flujo + UpdateLevelFromFlowBalance(deltaTime); + + if (VerboseLogging()) + { + Debug.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevelM:F2}m ({FillPercentage:F1}%), " + + $"Flujo_entrada={InletFlow:F2}L/min, Flujo_salida={OutletFlow:F2}L/min, " + + $"Balance={FlowBalance:F2}L/min, Presión={CurrentPressure:F2}bar, Fluido={CurrentFluidDescription}"); + } + } + public List GetHydraulicElements() { // Los tanques no generan elementos hidráulicos, solo nodos @@ -484,47 +685,59 @@ namespace CtrEditor.ObjetosSim SafeUpdateVolumeCalculations(); } - public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures) - { - var deltaTime = GetDeltaTime(); - - // Obtener flujos conectados - UpdateFlowsFromConnectedPipes(flows); - - // Actualizar presión - if (pressures.ContainsKey(Nombre)) - { - CurrentPressure = pressures[Nombre]; - } - - // Actualizar nivel basado en balance de flujo - UpdateLevelFromFlowBalance(deltaTime); - - if (VerboseLogging()) - { - Debug.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevel:F2}m ({FillPercentage:F1}%), " + - $"Flujo_entrada={InletFlow:F6}m³/s, Flujo_salida={OutletFlow:F6}m³/s, " + - $"Balance={FlowBalance:F6}m³/s, Presión={CurrentPressure:F0}Pa"); - } - } - // UI Properties /// - /// Color del nivel de líquido + /// 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; - if (percentage < 20) return new SolidColorBrush(Colors.Red); - if (percentage < 50) return new SolidColorBrush(Colors.Orange); - if (percentage < 80) return new SolidColorBrush(Colors.Yellow); - return new SolidColorBrush(Colors.LightBlue); + 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); } } @@ -539,23 +752,10 @@ namespace CtrEditor.ObjetosSim var percentage = FillPercentage; if (percentage < 20) return new SolidColorBrush(Colors.DarkRed); if (percentage < 50) return new SolidColorBrush(Colors.DarkOrange); - if (percentage < 80) return new SolidColorBrush(Colors.DarkGoldenrod); return new SolidColorBrush(Colors.DarkBlue); } } - /// - /// Indica si hay conexión de entrada - /// - [JsonIgnore] - public bool HasInletConnection => !string.IsNullOrEmpty(ConnectedInletPipe); - - /// - /// Indica si hay conexión de salida - /// - [JsonIgnore] - public bool HasOutletConnection => !string.IsNullOrEmpty(ConnectedOutletPipe); - /// /// Indicador del tipo de tanque /// @@ -575,12 +775,6 @@ namespace CtrEditor.ObjetosSim } } - /// - /// Balance de flujo en L/min - /// - [JsonIgnore] - public double FlowBalanceLMin => FlowBalance * 60000; // m³/s a L/min - /// /// Color del balance de flujo /// @@ -590,7 +784,7 @@ namespace CtrEditor.ObjetosSim get { var balance = FlowBalance; - if (Math.Abs(balance) < 0.0001) return new SolidColorBrush(Colors.Gray); + if (Math.Abs(balance) < 0.1) return new SolidColorBrush(Colors.Gray); return balance > 0 ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red); } } @@ -617,11 +811,29 @@ namespace CtrEditor.ObjetosSim get { var tankHeight = Tamano * 100; // Convertir a píxeles aproximados - var minPercentage = (MinLevel - MinLevel) / (MaxLevel - MinLevel) * 100; + var minPercentage = (MinLevelM - MinLevelM) / (MaxLevelM - MinLevelM) * 100; return tankHeight - (tankHeight * (minPercentage + 10) / 100); // 10% del nivel mínimo } } + /// + /// 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; + // Private Methods @@ -658,15 +870,25 @@ namespace CtrEditor.ObjetosSim { try { - _currentVolume = _currentLevel * _crossSectionalArea; - _maxVolume = _maxLevel * _crossSectionalArea; + // Calcular volumen total actual basado en nivel + _currentVolumeL = (_currentLevelM - _minLevelM) * _crossSectionalArea * 1000.0; // convertir m³ a L + _maxVolumeL = (_maxLevelM - _minLevelM) * _crossSectionalArea * 1000.0; + + // 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 - _currentVolume = 1.0; - _maxVolume = 2.0; + _currentVolumeL = 1000.0; + _maxVolumeL = 2000.0; } } @@ -683,22 +905,10 @@ namespace CtrEditor.ObjetosSim { Debug.WriteLine($"Error in SafeUpdateTankPressure: {ex.Message}"); // Usar presión atmosférica por defecto - _currentPressure = 101325.0; + _currentPressure = 1.013; } } - private string GetTankDescription() - { - return TankType switch - { - HydraulicTankType.Suction => "Tanque de succión", - HydraulicTankType.Intermediate => "Tanque intermedio", - HydraulicTankType.Storage => "Tanque de almacenamiento", - HydraulicTankType.Process => "Tanque de proceso", - _ => "Tanque hidráulico" - }; - } - private void UpdateTankConfiguration() { // Configuración automática según el tipo de tanque @@ -706,8 +916,8 @@ namespace CtrEditor.ObjetosSim { case HydraulicTankType.Suction: // Tanques de succión típicamente a presión atmosférica - if (TankPressure < 50000) // Si no se ha configurado manualmente - TankPressure = 101325.0; // 1 atm + if (TankPressure < 0.5) // Si no se ha configurado manualmente + TankPressure = 1.013; // 1 atm IsFixedPressure = true; break; @@ -719,20 +929,139 @@ namespace CtrEditor.ObjetosSim } } - private void UpdateVolumeCalculations() + private void UpdateMixingState() { - CurrentVolume = CurrentLevel * CrossSectionalArea; - MaxVolume = MaxLevel * CrossSectionalArea; + 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 UpdateTankPressure() + private void UpdateLevelFromFlowBalance(double deltaTime) { - if (IsFixedPressure) + 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) { - CurrentPressure = TankPressure; + Debug.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) { InletFlow = 0.0; @@ -742,10 +1071,11 @@ namespace CtrEditor.ObjetosSim foreach (var flow in flows) { var branchName = flow.Key; - var flowValue = flow.Value; + var flowValueM3s = flow.Value; // El flujo viene en m³/s + var flowValueLmin = flowValueM3s * 60000.0; // Convertir a L/min // Buscar si esta rama conecta este tanque - if (branchName.Contains("Pump") && HasPumpConnection(branchName, flowValue)) + if (branchName.Contains("Pump") && HasPumpConnection(branchName, flowValueLmin)) { continue; // Ya manejado en HasPumpConnection } @@ -753,23 +1083,23 @@ namespace CtrEditor.ObjetosSim // Buscar otras conexiones directas al tanque por nombre if (branchName.Contains(Nombre)) { - if (flowValue > 0) + if (flowValueLmin > 0) { // Flujo positivo hacia o desde el tanque if (IsFlowTowardsTank(branchName)) { - InletFlow += flowValue; + InletFlow += flowValueLmin; } else { - OutletFlow += flowValue; + OutletFlow += flowValueLmin; } } } } } - private bool HasPumpConnection(string branchName, double flowValue) + private bool HasPumpConnection(string branchName, double flowValueLmin) { if (_mainViewModel == null) return false; @@ -786,7 +1116,7 @@ namespace CtrEditor.ObjetosSim // Esta conexión sale del tanque if (branchName.Contains(pipe.Id_ComponenteB) && branchName.Contains("Pump")) { - OutletFlow += Math.Abs(flowValue); + OutletFlow += Math.Abs(flowValueLmin); return true; } } @@ -795,7 +1125,7 @@ namespace CtrEditor.ObjetosSim // Esta conexión llega al tanque if (branchName.Contains(pipe.Id_ComponenteA) && branchName.Contains("Pump")) { - InletFlow += Math.Abs(flowValue); + InletFlow += Math.Abs(flowValueLmin); return true; } } @@ -803,41 +1133,15 @@ namespace CtrEditor.ObjetosSim return false; } - - 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}"); - } - - 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 volumeChange = FlowBalance * deltaTime; - var levelChange = volumeChange / CrossSectionalArea; - - // Actualizar nivel con límites - var newLevel = CurrentLevel + levelChange; - CurrentLevel = Math.Max(MinLevel, Math.Min(MaxLevel, newLevel)); - - // Debug para verificar cambios - if (VerboseLogging() && Math.Abs(levelChange) > 0.0001) - { - Debug.WriteLine($"Tanque {Nombre}: Δt={deltaTime:F3}s, ΔVolumen={volumeChange:F6}m³, ΔNivel={levelChange:F6}m, Nivel={CurrentLevel:F3}m"); - } - } private double GetDeltaTime() { - var currentTime = Environment.TickCount64 / 1000.0; // segundos + 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 100ms + return Math.Min(deltaTime, 0.1); // máximo 0.1 minutos (6 segundos) } private bool VerboseLogging() @@ -845,23 +1149,29 @@ namespace CtrEditor.ObjetosSim return _mainViewModel?.hydraulicSimulationManager?.VerboseOutput ?? false; } - private void ManageInletConnection() - { - // Gestionar conexión de entrada - // Implementar lógica de conexión específica si es necesario - } - - private void ManageOutletConnection() - { - // Gestionar conexión de salida - // Implementar lógica de conexión específica si es necesario - } - private void InvalidateHydraulicNetwork() { _mainViewModel?.hydraulicSimulationManager?.InvalidateNetwork(); } + private string GetTankDescription() + { + return TankType switch + { + HydraulicTankType.Suction => "Tanque de succión", + HydraulicTankType.Intermediate => "Tanque intermedio", + HydraulicTankType.Storage => "Tanque de almacenamiento", + HydraulicTankType.Process => "Tanque de proceso", + _ => "Tanque hidráulico" + }; + } + + private 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 @@ -923,19 +1233,10 @@ namespace CtrEditor.ObjetosSim SafeUpdateTankPressure(); } + // Disposing no longer needed for connections since we removed them public void Disposing() { - // Desconectar event handlers si existen - if (inletPropertyChangedHandler != null) - { - // Aquí se desconectarían los handlers cuando se implementen las conexiones - // Similar a como lo hace osHydDischargeTank - } - - if (outletPropertyChangedHandler != null) - { - // Desconectar outlet handler cuando se implemente - } + // Cleanup if needed for fluids } diff --git a/ObjetosSim/HydraulicComponents/ucHydTank.xaml.cs b/ObjetosSim/HydraulicComponents/ucHydTank.xaml.cs index eada20c..3a7a848 100644 --- a/ObjetosSim/HydraulicComponents/ucHydTank.xaml.cs +++ b/ObjetosSim/HydraulicComponents/ucHydTank.xaml.cs @@ -76,11 +76,10 @@ namespace CtrEditor.ObjetosSim { // Actualizar propiedades visuales cuando cambien propiedades relevantes if (e.PropertyName == nameof(osHydTank.FillPercentage) || - e.PropertyName == nameof(osHydTank.CurrentLevel) || + e.PropertyName == nameof(osHydTank.CurrentLevelM) || e.PropertyName == nameof(osHydTank.FlowBalance) || e.PropertyName == nameof(osHydTank.TankType) || - e.PropertyName == nameof(osHydTank.ConnectedInletPipe) || - e.PropertyName == nameof(osHydTank.ConnectedOutletPipe)) + e.PropertyName == nameof(osHydTank.CurrentFluidDescription)) { UpdateVisualProperties(); }