Refactor hydraulic components: Simplify pump and tank logic, remove unused properties, and enhance flow calculations

- Updated osHydPump.cs to replace NPSH and cavitation calculations with a simpler flow check.
- Modified pump status display to indicate whether the pump is pumping or not.
- Streamlined fluid updates in osHydPump.cs to only occur when there is flow.
- Refactored osHydTank.cs to consolidate flow handling and remove unnecessary three-section logic.
- Adjusted UI elements in ucHydTank.xaml to reflect changes in flow representation and removed deprecated visual indicators.
- Enhanced DebugConsoleServer.cs to improve client connection handling with timeout and error management.
This commit is contained in:
Miguel 2025-09-07 17:06:15 +02:00
parent b39c58e6d6
commit 3c9c0e2479
8 changed files with 403 additions and 594 deletions

View File

@ -1,5 +1,7 @@
# CtrEditor MCP Server - LLM Guide # CtrEditor MCP Server - LLM Guide
*MCP 2025-06-18 compliant | Compatible: Claude Desktop + Cursor*
## ⚡ Command Efficiency Tiers ## ⚡ Command Efficiency Tiers
### 🚀 **Ultra-Fast** (Use Liberally) ### 🚀 **Ultra-Fast** (Use Liberally)
@ -117,6 +119,11 @@ High-value, low-token patterns:
2. Check CtrEditor is running with `get_ctreditor_status` 2. Check CtrEditor is running with `get_ctreditor_status`
3. Verify MCP server is active on port 5006 3. Verify MCP server is active on port 5006
### MCP Client Issues
- **Red LED in Cursor**: Proxy now handles all standard MCP methods correctly
- **Connection timeout**: CtrEditor must be running for tool calls to work
- **Build errors**: Use `build_project` to see only error output
## 💡 Best Practices ## 💡 Best Practices
- Always use `get_simulation_status` before expensive operations - Always use `get_simulation_status` before expensive operations
@ -124,3 +131,4 @@ High-value, low-token patterns:
- Call `list_objects` only when object data is actually needed - Call `list_objects` only when object data is actually needed
- Stop simulation before major structural changes - Stop simulation before major structural changes
- Use appropriate units: meters, Pascal, m³/s - Use appropriate units: meters, Pascal, m³/s
- Proxy works with both Claude Desktop and Cursor (MCP 2025-06-18)

View File

@ -577,3 +577,159 @@ using CtrEditor.HydraulicSimulator; // Para IHydraulicComponent
- ✅ **Código limpio** - Eliminadas duplicaciones y mantenida una sola fuente de verdad por funcionalidad - ✅ **Código limpio** - Eliminadas duplicaciones y mantenida una sola fuente de verdad por funcionalidad
- ✅ **Compilación exitosa** - El proyecto compila completamente sin errores - ✅ **Compilación exitosa** - El proyecto compila completamente sin errores
## Corrección del Sistema de Debug Logging para .NET Core/.NET 8 (Enero 2025)
### Problema Identificado
El MCP proxy no estaba retornando los logs de la consola de debug de CtrEditor. El análisis reveló que el `DebugConsoleServer` solo escuchaba `Trace.WriteLine`, pero todo el código de CtrEditor usaba `Debug.WriteLine`. En .NET Core/.NET 8, `Debug.Listeners` no existe (solo disponible en .NET Framework), causando errores de compilación.
### Errores de Compilación
```
D:\Proyectos\VisualStudio\CtrEditor\Services\DebugConsoleServer.cs(57,23): error CS0117: 'Debug' no contiene una definición para 'Listeners'
D:\Proyectos\VisualStudio\CtrEditor\Services\DebugConsoleServer.cs(92,27): error CS0117: 'Debug' no contiene una definición para 'Listeners'
```
### Solución Implementada
**1. Corrección del DebugConsoleServer**
- **Antes**: Intentaba registrar `DebugTraceListener` en `Debug.Listeners` (no disponible en .NET Core)
- **Después**: Solo usa `Trace.Listeners` que sí está disponible en .NET Core/.NET 8
- **Comentarios actualizados**: Explicar que `Debug.WriteLine` no está disponible en .NET Core/.NET 8
**2. Migración de Logs Críticos a Trace.WriteLine**
Se cambiaron los logs más importantes del `HydraulicSimulationManager` de `Debug.WriteLine` a `Trace.WriteLine`:
```csharp
// Antes
Debug.WriteLine("HydraulicSimulationManager inicializado");
Debug.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}");
// Después
Trace.WriteLine("HydraulicSimulationManager inicializado");
Trace.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}");
```
### Logs Migrados a Trace.WriteLine
- Inicialización del HydraulicSimulationManager
- Errores de convergencia de simulación hidráulica
- Errores generales en HydraulicSimulationManager.Step
- Reinicio de simulación hidráulica
- Creación de bombas y tanques hidráulicos
### Validación del Sistema
**Test Completo Ejecutado:**
1. ✅ **Compilación exitosa**: Sin errores de `Debug.Listeners`
2. ✅ **CtrEditor iniciado**: Proceso PID 36724 funcionando
3. ✅ **Debug listener operativo**: Puerto 5007 conectado exitosamente
4. ✅ **Logs capturados**: Buffer conteniendo logs de `DebugConsoleServer`
**Resultado del Test:**
```
"matches_found": 3,
"matches": [
"[Debug Console Server] Conectado al servidor de debug de CtrEditor en puerto 5007",
"[Debug Console Server] Cliente debug conectado",
"[Debug Console Server] Esperando conexión de cliente..."
]
```
### Arquitectura Corregida
**Flujo de Logging .NET Core/.NET 8:**
1. `Trace.WriteLine()` en código de CtrEditor
2. → `DebugTraceListener` registrado en `Trace.Listeners`
3. → `DebugConsoleServer` TCP puerto 5007
4. → MCP Proxy debug listener
5. → Búsqueda y análisis de logs via MCP
### Beneficios de la Corrección
- **Compatibilidad .NET Core**: Sistema funciona en .NET 8 en lugar de solo .NET Framework
- **Logs visibles**: Los logs críticos del sistema hidráulico ahora son accesibles via MCP
- **Debugging mejorado**: Posibilidad de monitorear simulación hidráulica en tiempo real
- **Arquitectura limpia**: Solo usa APIs disponibles en la plataforma target
## Corrección Crítica del Freeze en I/O Completion Port
### Problema Identificado
La aplicación se congelaba en `System.Private.CoreLib.dll!System.Threading.PortableThreadPool.IOCompletionPoller.Poll()` debido a que `AcceptTcpClientAsync()` sin `CancellationToken` no puede ser cancelado, causando bloqueos indefinidos en el thread del I/O completion port cuando se intentaba detener el `DebugConsoleServer`.
**Stack trace del problema:**
```
System.Private.CoreLib.dll!System.Threading.PortableThreadPool.IOCompletionPoller.Poll()
Interop.Kernel32.GetQueuedCompletionStatusEx(..., Timeout.Infinite, ...)
```
### Causa Raíz
```csharp
// PROBLEMA: Sin cancelación en .NET Core/.NET 8
private async Task AcceptConnectionsAsync(CancellationToken cancellationToken)
{
while (_isRunning && !cancellationToken.IsCancellationRequested)
{
var tcpClient = await _tcpListener.AcceptTcpClientAsync(); // ⚠️ BLOQUEO INDEFINIDO
}
}
```
El método `AcceptTcpClientAsync()` sin parámetros no acepta `CancellationToken` en .NET Core, causando que el thread se quede esperando una conexión TCP indefinidamente.
### Solución Implementada
Implementación de timeout responsive con `Task.WhenAny()`:
```csharp
private async Task AcceptConnectionsAsync(CancellationToken cancellationToken)
{
try
{
while (_isRunning && !cancellationToken.IsCancellationRequested)
{
try
{
// Implementar timeout y cancelación manual para evitar freeze
var acceptTask = _tcpListener.AcceptTcpClientAsync();
var delayTask = Task.Delay(1000, cancellationToken); // Check every 1s
var completedTask = await Task.WhenAny(acceptTask, delayTask);
if (completedTask == acceptTask && !cancellationToken.IsCancellationRequested)
{
var tcpClient = await acceptTask;
_connectedClients.Add(tcpClient);
// Manejar cliente...
}
// Si delayTask completa, continúa el loop para verificar estado
}
catch (ObjectDisposedException) { break; } // TcpListener cerrado
catch (Exception ex)
{
if (!_isRunning) break;
await Task.Delay(1000, cancellationToken); // Esperar antes de reintentar
}
}
}
catch (OperationCanceledException) { /* Cancelación normal */ }
}
```
### Validación de la Corrección
**Test Completo Ejecutado:**
1. ✅ **Compilación exitosa**: Sin errores de compatibilidad
2. ✅ **CtrEditor iniciado**: Proceso PID 42876 funcionando correctamente
3. ✅ **Debug listener iniciado**: Puerto 5007 conectado sin problemas
4. ✅ **Sin freezes**: Aplicación responde a comandos inmediatamente
5. ✅ **Shutdown limpio**: Debug listener y CtrEditor se cierran correctamente
### Resultados
- ✅ **Eliminado freeze completo** - No más bloqueos en I/O completion port
- ✅ **Cancelación responsive** - Verificación de estado cada 1 segundo
- ✅ **Shutdown limpio** - Debug listener se detiene correctamente
- ✅ **Estabilidad mejorada** - Aplicación responde a comandos de stop inmediatamente
- ✅ **Manejo robusto de errores** - Captura `ObjectDisposedException` y `OperationCanceledException`
Esta corrección resuelve un problema crítico de estabilidad que impedía el uso seguro del sistema de debug logging.

View File

@ -55,7 +55,7 @@ namespace CtrEditor.HydraulicSimulator
public int MaxIterations { get; set; } = 300; public int MaxIterations { get; set; } = 300;
public double Tolerance { get; set; } = 1e-3; // Tolerancia más relajada para convergencia inicial public double Tolerance { get; set; } = 1e-3; // Tolerancia más relajada para convergencia inicial
public double RelaxationFactor { get; set; } = 0.8; public double RelaxationFactor { get; set; } = 0.8;
public bool VerboseOutput { get; set; } = false; // Activado para debugging public bool VerboseOutput { get; set; } = true; // Activado para debugging
/// <summary> /// <summary>
/// Contador de pasos de simulación para optimizaciones /// Contador de pasos de simulación para optimizaciones
@ -101,7 +101,7 @@ namespace CtrEditor.HydraulicSimulator
GlobalTime = 0.0f; GlobalTime = 0.0f;
Debug.WriteLine("HydraulicSimulationManager inicializado"); Trace.WriteLine("HydraulicSimulationManager inicializado");
} }
#endregion #endregion
@ -418,9 +418,9 @@ namespace CtrEditor.HydraulicSimulator
} }
else else
{ {
Debug.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}"); Trace.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}");
Debug.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}"); Trace.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}");
Debug.WriteLine($" Tolerancia requerida: {Tolerance:E6}"); Trace.WriteLine($" Tolerancia requerida: {Tolerance:E6}");
// Diagnóstico detallado deshabilitado para mejorar rendimiento // Diagnóstico detallado deshabilitado para mejorar rendimiento
/* /*
@ -452,7 +452,7 @@ namespace CtrEditor.HydraulicSimulator
ErrorMessage = $"Error en simulación hidráulica: {ex.Message}" ErrorMessage = $"Error en simulación hidráulica: {ex.Message}"
}; };
Debug.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}"); Trace.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}");
} }
finally finally
{ {
@ -660,9 +660,12 @@ namespace CtrEditor.HydraulicSimulator
/// </summary> /// </summary>
private void ApplyResultsToObject(osBase obj) private void ApplyResultsToObject(osBase obj)
{ {
Debug.WriteLine($"[HYDRAULIC] ApplyResultsToObject: {obj.Nombre} ({obj.GetType().Name})");
// Aplicar resultados usando las interfaces hidráulicas // Aplicar resultados usando las interfaces hidráulicas
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
{ {
Debug.WriteLine($"[HYDRAULIC] {obj.Nombre} is IHydraulicComponent with HasHydraulicComponents=true");
// Llamar al método de aplicación de resultados del objeto // Llamar al método de aplicación de resultados del objeto
hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures); hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures);
} }
@ -670,13 +673,21 @@ namespace CtrEditor.HydraulicSimulator
// Aplicación específica por tipo de interfaz // Aplicación específica por tipo de interfaz
if (obj is IHydraulicFlowReceiver flowReceiver) if (obj is IHydraulicFlowReceiver flowReceiver)
{ {
Debug.WriteLine($"[HYDRAULIC] {obj.Nombre} is IHydraulicFlowReceiver");
// Buscar caudal asociado al objeto // Buscar caudal asociado al objeto
string branchName = FindBranchNameForObject(obj); string branchName = FindBranchNameForObject(obj);
Debug.WriteLine($"[HYDRAULIC] Branch name for {obj.Nombre}: '{branchName}'");
if (!string.IsNullOrEmpty(branchName) && LastSolutionResult.Flows.ContainsKey(branchName)) if (!string.IsNullOrEmpty(branchName) && LastSolutionResult.Flows.ContainsKey(branchName))
{ {
double flow = LastSolutionResult.Flows[branchName]; double flow = LastSolutionResult.Flows[branchName];
Debug.WriteLine($"[HYDRAULIC] Setting flow {flow:F6} m³/s to {obj.Nombre}");
flowReceiver.SetFlow(flow); flowReceiver.SetFlow(flow);
} }
else
{
Debug.WriteLine($"[HYDRAULIC] No flow found for {obj.Nombre} with branch '{branchName}'");
Debug.WriteLine($"[HYDRAULIC] Available flows: {string.Join(", ", LastSolutionResult.Flows.Keys)}");
}
} }
if (obj is IHydraulicPressureReceiver pressureReceiver) if (obj is IHydraulicPressureReceiver pressureReceiver)
@ -791,7 +802,7 @@ namespace CtrEditor.HydraulicSimulator
ErrorMessage = "Simulación reiniciada" ErrorMessage = "Simulación reiniciada"
}; };
Debug.WriteLine("Simulación hidráulica reiniciada"); Trace.WriteLine("Simulación hidráulica reiniciada");
} }
/// <summary> /// <summary>
@ -874,7 +885,7 @@ namespace CtrEditor.HydraulicSimulator
HydraulicSimObjects.Add(pump); HydraulicSimObjects.Add(pump);
_networkNeedsRebuild = true; _networkNeedsRebuild = true;
Debug.WriteLine($"Bomba hidráulica creada: H={pumpHead}m, Q={maxFlow}m³/s"); Trace.WriteLine($"Bomba hidráulica creada: H={pumpHead}m, Q={maxFlow}m³/s");
return pump; return pump;
} }
@ -896,7 +907,7 @@ namespace CtrEditor.HydraulicSimulator
HydraulicSimObjects.Add(tank); HydraulicSimObjects.Add(tank);
_networkNeedsRebuild = true; _networkNeedsRebuild = true;
Debug.WriteLine($"Tanque hidráulico creado: P={pressure}Pa, Nivel={currentLevel}m"); Trace.WriteLine($"Tanque hidráulico creado: P={pressure}Pa, Nivel={currentLevel}m");
return tank; return tank;
} }

View File

@ -565,22 +565,38 @@ namespace CtrEditor.ObjetosSim
public override void UpdateGeometryStart() public override void UpdateGeometryStart()
{ {
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe // Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
Debug.WriteLine($"[DEBUG] {Nombre}: UpdateGeometryStart() - ComponenteA: '{Id_ComponenteA}', ComponenteB: '{Id_ComponenteB}'");
if (SimHydraulicPipe == null && !string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB)) if (SimHydraulicPipe == null && !string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
{ {
Debug.WriteLine($"[DEBUG] {Nombre}: Creating SimHydraulicPipe...");
SimHydraulicPipe = hydraulicSimulationManager.AddPipe(Length, Diameter, Roughness, Id_ComponenteA, Id_ComponenteB); SimHydraulicPipe = hydraulicSimulationManager.AddPipe(Length, Diameter, Roughness, Id_ComponenteA, Id_ComponenteB);
SimHydraulicPipe.SimObjectType = "HydraulicPipe"; if (SimHydraulicPipe != null)
SimHydraulicPipe.WpfObject = this; {
SimHydraulicPipe.Nombre = Nombre; SimHydraulicPipe.SimObjectType = "HydraulicPipe";
SimHydraulicPipe.WpfObject = this;
SimHydraulicPipe.Nombre = Nombre;
Debug.WriteLine($"[DEBUG] {Nombre}: SimHydraulicPipe created successfully");
}
else
{
Debug.WriteLine($"[DEBUG] {Nombre}: FAILED to create SimHydraulicPipe!");
}
} }
else if (SimHydraulicPipe != null) else if (SimHydraulicPipe != null)
{ {
// Actualizar propiedades si el objeto ya existe // Actualizar propiedades si el objeto ya existe
Debug.WriteLine($"[DEBUG] {Nombre}: Updating existing SimHydraulicPipe");
SimHydraulicPipe.Length = Length; SimHydraulicPipe.Length = Length;
SimHydraulicPipe.Diameter = Diameter; SimHydraulicPipe.Diameter = Diameter;
SimHydraulicPipe.Roughness = Roughness; SimHydraulicPipe.Roughness = Roughness;
SimHydraulicPipe.ComponenteAId = Id_ComponenteA; SimHydraulicPipe.ComponenteAId = Id_ComponenteA;
SimHydraulicPipe.ComponenteBId = Id_ComponenteB; SimHydraulicPipe.ComponenteBId = Id_ComponenteB;
} }
else
{
Debug.WriteLine($"[DEBUG] {Nombre}: Cannot create SimHydraulicPipe - missing connections or already exists");
}
ActualizarGeometrias(); ActualizarGeometrias();
} }
@ -631,18 +647,19 @@ namespace CtrEditor.ObjetosSim
// Actualizar propiedades desde la simulación hidráulica // Actualizar propiedades desde la simulación hidráulica
if (SimHydraulicPipe != null) if (SimHydraulicPipe != null)
{ {
double previousFlow = CurrentFlow;
CurrentFlow = SimHydraulicPipe.CurrentFlow; CurrentFlow = SimHydraulicPipe.CurrentFlow;
PressureDrop = SimHydraulicPipe.PressureDrop; PressureDrop = SimHydraulicPipe.PressureDrop;
// DEBUG: Log flujo para diagnosis // DEBUG: Log flujo para diagnosis
if (CurrentFlow != 0.0) if (CurrentFlow != 0.0 || CurrentFlow != previousFlow)
{ {
Debug.WriteLine($"[DEBUG] {Nombre}: Flow={CurrentFlow:F6} m³/s ({CurrentFlowLMin:F2} L/min)"); Debug.WriteLine($"[DEBUG] {Nombre}: Flow={CurrentFlow:F6} m³/s ({CurrentFlowLMin:F2} L/min) - Changed from {previousFlow:F6}");
} }
} }
else else
{ {
Debug.WriteLine($"[DEBUG] {Nombre}: SimHydraulicPipe is NULL!"); Debug.WriteLine($"[DEBUG] {Nombre}: SimHydraulicPipe is NULL! ComponenteA='{Id_ComponenteA}', ComponenteB='{Id_ComponenteB}'");
} }
// Actualizar propiedades del fluido cada ciclo // Actualizar propiedades del fluido cada ciclo

View File

@ -180,85 +180,22 @@ namespace CtrEditor.ObjetosSim
public double CurrentFlowLMin => CurrentFlow * 60000.0; // m³/s a L/min public double CurrentFlowLMin => CurrentFlow * 60000.0; // m³/s a L/min
[Category("📊 Estado Actual")] [Category("📊 Estado Actual")]
[DisplayName("NPSH Disponible")] [DisplayName("Tiene Flujo")]
[Description("NPSH disponible calculado (m)")] [Description("Indica si hay flujo en la bomba")]
[JsonIgnore] [JsonIgnore]
public double NPSHAvailable public bool HasFlow => Math.Abs(CurrentFlow) > 1e-6;
{
get
{
var (inletNodeName, _) = GetConnectedNodeNames();
if (!string.IsNullOrEmpty(inletNodeName) &&
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
{
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
return pump.CalculateNPSHAvailable(suctionPressure, hydraulicSimulationManager.SimulationFluid);
}
return 0.0;
}
}
[Category("📊 Estado Actual")] [Category("📊 Estado Actual")]
[DisplayName("Factor Cavitación")] [DisplayName("Estado")]
[Description("Factor de cavitación (1=sin cavitación, 0=cavitación total)")] [Description("Estado actual de la bomba")]
[JsonIgnore] [JsonIgnore]
public double CavitationFactor public string PumpStatus
{
get
{
var (inletNodeName, _) = GetConnectedNodeNames();
if (!string.IsNullOrEmpty(inletNodeName) &&
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
{
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
return pump.GetCavitationFactor(suctionPressure, hydraulicSimulationManager.SimulationFluid);
}
return 1.0;
}
}
[Category("📊 Estado Actual")]
[DisplayName("Puede Operar")]
[Description("Indica si la bomba puede operar sin cavitación")]
[JsonIgnore]
public bool CanOperateWithoutCavitation
{
get
{
var (inletNodeName, _) = GetConnectedNodeNames();
if (!string.IsNullOrEmpty(inletNodeName) &&
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
{
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
return pump.CanOperateWithoutCavitation(suctionPressure, hydraulicSimulationManager.SimulationFluid);
}
return false;
}
}
[Category("📊 Estado Actual")]
[DisplayName("Estado Cavitación")]
[Description("Estado actual de cavitación de la bomba")]
[JsonIgnore]
public string CavitationStatus
{ {
get get
{ {
if (!IsRunning) return "Bomba detenida"; if (!IsRunning) return "Bomba detenida";
if (HasFlow) return "✅ Bombeando";
var factor = CavitationFactor; return "⚠️ Sin flujo";
var canOperate = CanOperateWithoutCavitation;
var npshAvailable = NPSHAvailable;
if (canOperate && factor >= 0.9)
return "✅ Operación normal";
else if (factor >= 0.5)
return $"⚠️ Riesgo cavitación (Factor: {factor:F2})";
else
return $"❌ NPSH insuficiente ({npshAvailable:F2}m)";
} }
} }
@ -270,13 +207,9 @@ namespace CtrEditor.ObjetosSim
{ {
get get
{ {
if (!IsRunning) return "Bomba detenida";
var factor = CavitationFactor;
var npshAvailable = NPSHAvailable;
var flow = CurrentFlowLMin; var flow = CurrentFlowLMin;
var pressure = CurrentPressureBar;
return $"NPSH: {npshAvailable:F2}m | Factor: {factor:F2} | Flujo: {flow:F1} L/min"; return $"Flujo: {flow:F1} L/min | Presión: {pressure:F1} bar | {PumpStatus}";
} }
} }
@ -414,23 +347,38 @@ namespace CtrEditor.ObjetosSim
try try
{ {
// Buscar el componente conectado en la succión (entrada de la bomba) // Solo actualizar fluido si hay flujo
var suctionComponent = FindSuctionComponent(); if (HasFlow)
if (suctionComponent is osHydTank tank)
{ {
var sourceFluid = tank.CurrentOutputFluid; // Buscar el componente conectado en la succión
if (sourceFluid != null) var suctionComponent = FindSuctionComponent();
if (suctionComponent is osHydTank tank)
{ {
_currentFluid = sourceFluid.Clone(); 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(CurrentFluidType));
OnPropertyChanged(nameof(CurrentFluidDescription)); 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(); UpdatePumpColorFromFluid();
} }
} }
@ -450,36 +398,24 @@ namespace CtrEditor.ObjetosSim
return; return;
} }
// Si no hay flujo, verificar cavitación primero // Color basado solo en si hay flujo o no
if (Math.Abs(CurrentFlow) < 1e-6) if (HasFlow)
{ {
if (hydraulicSimulationManager?.EnableNPSHVerification == true) // Si hay flujo, usar color del fluido o verde por defecto
try
{ {
var cavitationFactor = CavitationFactor; var fluidColorHex = _currentFluid.Color;
if (cavitationFactor < 0.1) var fluidColor = (Color)System.Windows.Media.ColorConverter.ConvertFromString(fluidColorHex);
ColorButton_oculto = Brushes.Red; // Cavitación severa ColorButton_oculto = new SolidColorBrush(fluidColor);
else if (cavitationFactor < 0.5)
ColorButton_oculto = Brushes.Orange; // Riesgo de cavitación
else
ColorButton_oculto = Brushes.Yellow; // Standby
} }
else catch
{ {
ColorButton_oculto = Brushes.Yellow; // Standby ColorButton_oculto = Brushes.Green; // Color por defecto si hay flujo
} }
return;
} }
else
// Colorear según tipo de fluido bombeado
try
{ {
var fluidColorHex = _currentFluid.Color; ColorButton_oculto = Brushes.Yellow; // Sin flujo (standby o sin succión)
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
} }
} }
@ -527,14 +463,17 @@ namespace CtrEditor.ObjetosSim
CurrentPressure = SimHydraulicPump.CurrentPressure; CurrentPressure = SimHydraulicPump.CurrentPressure;
} }
// Actualizar propiedades del fluido cada ciclo // Actualizar propiedades del fluido basado en si hay flujo
UpdateFluidFromSuction(); UpdateFluidFromSuction();
// Actualizar propiedades de UI // Actualizar propiedades de UI
OnPropertyChanged(nameof(CurrentFlowLMin)); OnPropertyChanged(nameof(CurrentFlowLMin));
OnPropertyChanged(nameof(EffectiveFlowLMin)); OnPropertyChanged(nameof(EffectiveFlowLMin));
OnPropertyChanged(nameof(HasFlow));
OnPropertyChanged(nameof(PumpStatus));
OnPropertyChanged(nameof(DetailedStatus));
// Actualizar el color según el estado (ya se hace en UpdatePumpColorFromFluid) // Actualizar el color según el flujo
if (!IsRunning) if (!IsRunning)
{ {
ColorButton_oculto = Brushes.Gray; // Bomba apagada ColorButton_oculto = Brushes.Gray; // Bomba apagada
@ -611,36 +550,17 @@ namespace CtrEditor.ObjetosSim
return elements; return elements;
} }
// Crear bomba con parámetros actuales // Crear bomba estándar - el solver manejará las limitaciones de flujo
Element pump; var pump = new PumpHQ(
if (hydraulicSimulationManager.EnableNPSHVerification) h0: PumpHead,
{ q0: MaxFlow,
// Usar bomba con verificación de NPSH speedRel: effectiveSpeed,
pump = new PumpHQWithSuctionCheck( direction: PumpDirection
h0: PumpHead, );
q0: MaxFlow,
speedRel: effectiveSpeed,
direction: PumpDirection,
enableNpshCheck: true
);
}
else
{
// Usar bomba estándar
pump = new PumpHQ(
h0: PumpHead,
q0: MaxFlow,
speedRel: effectiveSpeed,
direction: PumpDirection
);
}
// Asignar nombres de nodos para verificación de NPSH // Asignar nombres de nodos
if (pump is PumpHQ pumpHQ) pump.InletNodeName = inletNode;
{ pump.OutletNodeName = outletNode;
pumpHQ.InletNodeName = inletNode;
pumpHQ.OutletNodeName = outletNode;
}
var pumpElement = new HydraulicElementDefinition( var pumpElement = new HydraulicElementDefinition(
$"{Nombre}_Pump", $"{Nombre}_Pump",
@ -661,7 +581,7 @@ namespace CtrEditor.ObjetosSim
public void UpdateHydraulicProperties() public void UpdateHydraulicProperties()
{ {
// Aquí se pueden hacer validaciones o ajustes antes de la simulación // Validaciones simples antes de la simulación
if (!IsRunning) if (!IsRunning)
{ {
SpeedRatio = 0.0; SpeedRatio = 0.0;
@ -670,10 +590,7 @@ namespace CtrEditor.ObjetosSim
if (SpeedRatio < 0.0) SpeedRatio = 0.0; if (SpeedRatio < 0.0) SpeedRatio = 0.0;
if (SpeedRatio > 1.0) SpeedRatio = 1.0; if (SpeedRatio > 1.0) SpeedRatio = 1.0;
// Las verificaciones de NPSH ahora se muestran a través de propiedades // El solver se encarga del resto - NPSH, limitaciones de flujo, etc.
// Se eliminaron los Debug.WriteLine para evitar saturación del sistema
//Debug.WriteLine($"Bomba {Nombre}: Velocidad={SpeedRatio:F2}, Funcionando={IsRunning}");
} }
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures) public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)

View File

@ -30,16 +30,12 @@ namespace CtrEditor.ObjetosSim
private double _tankPressure = 1.013; // bar (1 atm por defecto) private double _tankPressure = 1.013; // bar (1 atm por defecto)
private double _currentLevelM = 1.0; // m 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 _crossSectionalArea = 1.0; // m²
private double _currentVolumeL = 1000.0; // L private double _currentVolumeL = 1000.0; // L
private double _maxVolumeL = 2000.0; // L private double _maxVolumeL = 2000.0; // L
private double _inletFlow = 0.0; // L/min private double _netFlow = 0.0; // L/min (positivo = entrada, negativo = salida)
private double _outletFlow = 0.0; // L/min
private double _currentPressure = 1.013; // bar private double _currentPressure = 1.013; // bar
private bool _isFixedPressure = true; private bool _isFixedPressure = true;
private HydraulicTankType _tankType = HydraulicTankType.Intermediate;
private double _lastUpdateTime = 0.0; private double _lastUpdateTime = 0.0;
// Propiedades de fluido // Propiedades de fluido
@ -51,22 +47,15 @@ namespace CtrEditor.ObjetosSim
private MixingState _mixingState = MixingState.PrimaryOnly; private MixingState _mixingState = MixingState.PrimaryOnly;
private double _currentMixRatio = 0.0; // 0 = solo primario, 1 = solo secundario 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
[JsonIgnore] [JsonIgnore]
private object _levelLock = new object(); private object _levelLock = new object();
// Enums
public enum HydraulicTankType
{
[Description("Tanque de succión (inicio del sistema)")]
Suction,
[Description("Tanque intermedio (buffer)")]
Intermediate,
[Description("Tanque de almacenamiento")]
Storage,
[Description("Tanque de proceso")]
Process
}
// Properties // Properties
@ -84,42 +73,8 @@ namespace CtrEditor.ObjetosSim
[property: Name("Color")] [property: Name("Color")]
private Brush colorButton_oculto = Brushes.LightBlue; private Brush colorButton_oculto = Brushes.LightBlue;
// Three-section tank properties
[ObservableProperty]
[property: Category("🏭 Configuración Tanque")]
[property: Description("Habilitar sistema de tres secciones")]
[property: Name("Tres Secciones")]
private bool enableThreeSection = false;
[ObservableProperty]
[property: Category("🏭 Configuración Tanque")]
[property: Description("Altura de la sección de mezcla (m)")]
[property: Name("Altura Mezcla")]
private double mixingSectionHeight = 0.20;
[ObservableProperty]
[property: Category("📊 Niveles")]
[property: Description("Nivel secundario en metros")]
[property: Name("Nivel Secundario")]
private double secondaryLevelM = 0.0;
[ObservableProperty]
[property: Category("📊 Niveles")]
[property: Description("Nivel primario en metros")]
[property: Name("Nivel Primario")]
private double primaryLevelM = 0.0;
[ObservableProperty]
[property: Category("📊 Porcentajes")]
[property: Description("Porcentaje de llenado sección secundaria")]
[property: Name("% Secundario")]
private double secondaryPercentage = 0.0;
[ObservableProperty]
[property: Category("📊 Porcentajes")]
[property: Description("Porcentaje de llenado sección primaria")]
[property: Name("% Primario")]
private double primaryPercentage = 0.0;
@ -151,11 +106,6 @@ namespace CtrEditor.ObjetosSim
// Inicializar estado de mezcla // Inicializar estado de mezcla
UpdateMixingState(); UpdateMixingState();
// Inicializar sistema de tres secciones
if (EnableThreeSection)
{
ConfigureThreeSectionLevels();
}
// Debug: Confirmar que el constructor se ejecuta // 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: Nombre='{Nombre}', Tamaño={Tamano}, ZIndex={zIndex_fromFrames}, IsVisFilter={IsVisFilter}, Lock_movement={Lock_movement}");
@ -201,7 +151,9 @@ namespace CtrEditor.ObjetosSim
{ {
// Convertir bar a Pa para el sistema hidráulico interno // Convertir bar a Pa para el sistema hidráulico interno
var pressurePa = TankPressure * 100000.0; var pressurePa = TankPressure * 100000.0;
SimHydraulicTank = hydraulicSimulationManager.AddTank(pressurePa, CurrentLevelM, MaxLevelM, MinLevelM, CrossSectionalArea, IsFixedPressure); // Usar nivel basado solo en volumen primario para cálculos hidráulicos
var effectiveLevel = PrimaryVolumeL / (CrossSectionalArea * 1000.0);
SimHydraulicTank = hydraulicSimulationManager.AddTank(pressurePa, effectiveLevel, MaxLevelM, MinLevelM, CrossSectionalArea, IsFixedPressure);
SimHydraulicTank.SimObjectType = "HydraulicTank"; SimHydraulicTank.SimObjectType = "HydraulicTank";
SimHydraulicTank.WpfObject = this; SimHydraulicTank.WpfObject = this;
SimHydraulicTank.Nombre = Nombre; SimHydraulicTank.Nombre = Nombre;
@ -210,7 +162,9 @@ namespace CtrEditor.ObjetosSim
{ {
// Actualizar propiedades si el objeto ya existe (convertir bar a Pa) // Actualizar propiedades si el objeto ya existe (convertir bar a Pa)
SimHydraulicTank.TankPressure = TankPressure * 100000.0; SimHydraulicTank.TankPressure = TankPressure * 100000.0;
SimHydraulicTank.CurrentLevel = CurrentLevelM; // Usar nivel basado solo en volumen primario para cálculos hidráulicos
var effectiveLevel = PrimaryVolumeL / (CrossSectionalArea * 1000.0);
SimHydraulicTank.CurrentLevel = effectiveLevel;
SimHydraulicTank.MaxLevel = MaxLevelM; SimHydraulicTank.MaxLevel = MaxLevelM;
SimHydraulicTank.MinLevel = MinLevelM; SimHydraulicTank.MinLevel = MinLevelM;
SimHydraulicTank.CrossSectionalArea = CrossSectionalArea; SimHydraulicTank.CrossSectionalArea = CrossSectionalArea;
@ -224,8 +178,7 @@ namespace CtrEditor.ObjetosSim
if (SimHydraulicTank != null) if (SimHydraulicTank != null)
{ {
// Convertir de m³/s a L/min // Convertir de m³/s a L/min
InletFlow = SimHydraulicTank.InletFlow * 60000.0; _netFlow = (SimHydraulicTank.InletFlow - SimHydraulicTank.OutletFlow) * 60000.0;
OutletFlow = SimHydraulicTank.OutletFlow * 60000.0;
// Convertir de Pa a bar // Convertir de Pa a bar
CurrentPressure = SimHydraulicTank.CurrentPressure / 100000.0; CurrentPressure = SimHydraulicTank.CurrentPressure / 100000.0;
@ -246,21 +199,6 @@ namespace CtrEditor.ObjetosSim
// Properties - Configuration // Properties - Configuration
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Tipo de tanque")]
[Description("Función del tanque en el sistema hidráulico")]
public HydraulicTankType TankType
{
get => _tankType;
set
{
if (SetProperty(ref _tankType, value))
{
UpdateTankConfiguration();
InvalidateHydraulicNetwork();
}
}
}
[Category("🏗️ Configuración del Tanque")] [Category("🏗️ Configuración del Tanque")]
[DisplayName("Área sección transversal")] [DisplayName("Área sección transversal")]
@ -303,11 +241,14 @@ namespace CtrEditor.ObjetosSim
get => _maxLevelM; get => _maxLevelM;
set set
{ {
if (SetProperty(ref _maxLevelM, Math.Max(0.2, value))) if (SetProperty(ref _maxLevelM, Math.Max(0.1, value)))
{ {
if (_currentLevelM > _maxLevelM) // Asegurar que el nivel mínimo no sea mayor que el máximo
CurrentLevelM = _maxLevelM; if (_minLevelM >= _maxLevelM)
MinLevelM = _maxLevelM * 0.1; // 10% del máximo como mínimo
SafeUpdateVolumeCalculations(); SafeUpdateVolumeCalculations();
InvalidateHydraulicNetwork();
} }
} }
} }
@ -320,15 +261,29 @@ namespace CtrEditor.ObjetosSim
get => _minLevelM; get => _minLevelM;
set set
{ {
if (SetProperty(ref _minLevelM, Math.Max(0.0, Math.Min(value, _maxLevelM - 0.1)))) if (SetProperty(ref _minLevelM, Math.Max(0.0, value)))
{ {
if (_currentLevelM < _minLevelM) // Asegurar que el nivel mínimo no sea mayor que el máximo
CurrentLevelM = _minLevelM; if (_minLevelM >= _maxLevelM)
MaxLevelM = _minLevelM + 0.1; // Al menos 10cm más que el mínimo
SafeUpdateVolumeCalculations(); SafeUpdateVolumeCalculations();
InvalidateHydraulicNetwork();
} }
} }
} }
[Category("🏗️ Configuración del Tanque")]
[DisplayName("Tipo de tanque")]
[Description("Tipo de tanque (Standard, Storage, Process, etc.)")]
public string TankType
{
get => _tankType;
set => SetProperty(ref _tankType, value ?? "Standard");
}
// Properties - Pressure // Properties - Pressure
@ -380,7 +335,7 @@ namespace CtrEditor.ObjetosSim
EnsureLockInitialized(); EnsureLockInitialized();
lock (_levelLock) lock (_levelLock)
{ {
var clampedLevel = Math.Max(_minLevelM, Math.Min(_maxLevelM, value)); var clampedLevel = Math.Max(0.0, value);
if (SetProperty(ref _currentLevelM, clampedLevel)) if (SetProperty(ref _currentLevelM, clampedLevel))
{ {
SafeUpdateVolumeCalculations(); SafeUpdateVolumeCalculations();
@ -406,8 +361,8 @@ namespace CtrEditor.ObjetosSim
if (SetProperty(ref _currentVolumeL, clampedVolume)) if (SetProperty(ref _currentVolumeL, clampedVolume))
{ {
// Actualizar nivel basado en volumen // Actualizar nivel basado en volumen
_currentLevelM = _minLevelM + (clampedVolume / 1000.0) / _crossSectionalArea; _currentLevelM = (clampedVolume / 1000.0) / _crossSectionalArea;
_currentLevelM = Math.Max(_minLevelM, Math.Min(_maxLevelM, _currentLevelM)); _currentLevelM = Math.Max(0.0, _currentLevelM);
OnPropertyChanged(nameof(CurrentLevelM)); OnPropertyChanged(nameof(CurrentLevelM));
OnPropertyChanged(nameof(FillPercentage)); OnPropertyChanged(nameof(FillPercentage));
UpdateMixingState(); UpdateMixingState();
@ -420,33 +375,23 @@ namespace CtrEditor.ObjetosSim
[DisplayName("Porcentaje llenado")] [DisplayName("Porcentaje llenado")]
[Description("Porcentaje de llenado del tanque")] [Description("Porcentaje de llenado del tanque")]
[JsonIgnore] [JsonIgnore]
public double FillPercentage => (_currentLevelM - _minLevelM) / (_maxLevelM - _minLevelM) * 100.0; public double FillPercentage => MaxLevelM > 0 ? (_currentLevelM / MaxLevelM) * 100.0 : 0.0;
[Category("📊 Estado Actual")] [Category("📊 Estado Actual")]
[DisplayName("Flujo entrada")] [DisplayName("Flujo neto")]
[Description("Flujo de entrada actual (L/min)")] [Description("Flujo neto del tanque (L/min) - positivo=entrada, negativo=salida")]
[JsonIgnore] [JsonIgnore]
public double InletFlow public double NetFlow
{ {
get => _inletFlow; get => _netFlow;
private set => SetProperty(ref _inletFlow, value); private set => SetProperty(ref _netFlow, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Flujo salida")]
[Description("Flujo de salida actual (L/min)")]
[JsonIgnore]
public double OutletFlow
{
get => _outletFlow;
private set => SetProperty(ref _outletFlow, value);
} }
[Category("📊 Estado Actual")] [Category("📊 Estado Actual")]
[DisplayName("Balance flujo")] [DisplayName("Balance flujo")]
[Description("Balance de flujo (entrada - salida) (L/min)")] [Description("Balance de flujo neto (L/min)")]
[JsonIgnore] [JsonIgnore]
public double FlowBalance => InletFlow - OutletFlow; public double FlowBalance => NetFlow;
[Category("📊 Estado Actual")] [Category("📊 Estado Actual")]
[DisplayName("Presión actual")] [DisplayName("Presión actual")]
@ -712,7 +657,7 @@ namespace CtrEditor.ObjetosSim
if (VerboseLogging()) if (VerboseLogging())
{ {
Trace.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevelM:F2}m ({FillPercentage:F1}%), " + Trace.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevelM:F2}m ({FillPercentage:F1}%), " +
$"Flujo_entrada={InletFlow:F2}L/min, Flujo_salida={OutletFlow:F2}L/min, " + $"Flujo_neto={NetFlow:F2}L/min, " +
$"Balance={FlowBalance:F2}L/min, Presión={CurrentPressure:F3}bar ({CurrentPressure * 100000.0:F0}Pa), Fluido={CurrentFluidDescription}"); $"Balance={FlowBalance:F2}L/min, Presión={CurrentPressure:F3}bar ({CurrentPressure * 100000.0:F0}Pa), Fluido={CurrentFluidDescription}");
} }
} }
@ -801,24 +746,6 @@ namespace CtrEditor.ObjetosSim
} }
} }
/// <summary>
/// Indicador del tipo de tanque
/// </summary>
[JsonIgnore]
public string TankTypeIndicator
{
get
{
return TankType switch
{
HydraulicTankType.Suction => "S",
HydraulicTankType.Intermediate => "I",
HydraulicTankType.Storage => "A",
HydraulicTankType.Process => "P",
_ => "T"
};
}
}
/// <summary> /// <summary>
/// Color del balance de flujo /// Color del balance de flujo
@ -834,32 +761,7 @@ namespace CtrEditor.ObjetosSim
} }
} }
/// <summary>
/// Posición Y de la línea de nivel medio (50%)
/// </summary>
[JsonIgnore]
public double MidLevelY
{
get
{
var tankHeight = Tamano * 100; // Convertir a píxeles aproximados
return tankHeight * 0.5; // 50% de altura
}
}
/// <summary>
/// Posición Y de la línea de nivel mínimo
/// </summary>
[JsonIgnore]
public double MinLevelY
{
get
{
var tankHeight = Tamano * 100; // Convertir a píxeles aproximados
var minPercentage = (MinLevelM - MinLevelM) / (MaxLevelM - MinLevelM) * 100;
return tankHeight - (tankHeight * (minPercentage + 10) / 100); // 10% del nivel mínimo
}
}
/// <summary> /// <summary>
/// Indica si el tanque está vacío (contiene aire) /// Indica si el tanque está vacío (contiene aire)
@ -985,6 +887,16 @@ namespace CtrEditor.ObjetosSim
if (Alto <= 0) if (Alto <= 0)
Alto = 0.40f; 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(); SafeUpdateVolumeCalculations();
SafeUpdateTankPressure(); SafeUpdateTankPressure();
@ -1001,8 +913,8 @@ namespace CtrEditor.ObjetosSim
try try
{ {
// Calcular volumen total actual basado en nivel // Calcular volumen total actual basado en nivel
_currentVolumeL = (_currentLevelM - _minLevelM) * _crossSectionalArea * 1000.0; // convertir m³ a L _currentVolumeL = _currentLevelM * _crossSectionalArea * 1000.0; // convertir m³ a L
_maxVolumeL = (_maxLevelM - _minLevelM) * _crossSectionalArea * 1000.0; _maxVolumeL = 2.0 * _crossSectionalArea * 1000.0; // Assuming 2m as max height
// Asegurar que los volúmenes de fluidos no excedan el volumen total // Asegurar que los volúmenes de fluidos no excedan el volumen total
var totalFluidVolume = _primaryVolumeL + _secondaryVolumeL; var totalFluidVolume = _primaryVolumeL + _secondaryVolumeL;
@ -1030,6 +942,13 @@ namespace CtrEditor.ObjetosSim
{ {
CurrentPressure = TankPressure; 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
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1039,25 +958,6 @@ namespace CtrEditor.ObjetosSim
} }
} }
private void UpdateTankConfiguration()
{
// Configuración automática según el tipo de tanque
switch (TankType)
{
case HydraulicTankType.Suction:
// Tanques de succión típicamente a presión atmosférica
if (TankPressure < 0.5) // Si no se ha configurado manualmente
TankPressure = 1.013; // 1 atm
IsFixedPressure = true;
break;
case HydraulicTankType.Intermediate:
case HydraulicTankType.Storage:
case HydraulicTankType.Process:
// Estos pueden tener presión variable o fija según el proceso
break;
}
}
private void UpdateMixingState() private void UpdateMixingState()
{ {
@ -1140,11 +1040,6 @@ namespace CtrEditor.ObjetosSim
// Actualizar volúmenes de fluidos según el estado de mezcla // Actualizar volúmenes de fluidos según el estado de mezcla
UpdateFluidVolumesFromFlow(volumeChangeL); UpdateFluidVolumesFromFlow(volumeChangeL);
// Actualizar sistema de tres secciones si está habilitado
if (EnableThreeSection)
{
UpdateThreeSectionLevels();
}
// Debug para verificar cambios // Debug para verificar cambios
if (VerboseLogging() && Math.Abs(volumeChangeL) > 0.1) if (VerboseLogging() && Math.Abs(volumeChangeL) > 0.1)
@ -1200,8 +1095,7 @@ namespace CtrEditor.ObjetosSim
private void UpdateFlowsFromConnectedPipes(Dictionary<string, double> flows) private void UpdateFlowsFromConnectedPipes(Dictionary<string, double> flows)
{ {
InletFlow = 0.0; NetFlow = 0.0;
OutletFlow = 0.0;
// Buscar flujos en las ramas que involucren este tanque // Buscar flujos en las ramas que involucren este tanque
foreach (var flow in flows) foreach (var flow in flows)
@ -1210,65 +1104,22 @@ namespace CtrEditor.ObjetosSim
var flowValueM3s = flow.Value; // El flujo viene en m³/s var flowValueM3s = flow.Value; // El flujo viene en m³/s
var flowValueLmin = flowValueM3s * 60000.0; // Convertir a L/min var flowValueLmin = flowValueM3s * 60000.0; // Convertir a L/min
// Buscar si esta rama conecta este tanque // Buscar conexiones que involucren este tanque
if (branchName.Contains("Pump") && HasPumpConnection(branchName, flowValueLmin))
{
continue; // Ya manejado en HasPumpConnection
}
// Buscar otras conexiones directas al tanque por nombre
if (branchName.Contains(Nombre)) if (branchName.Contains(Nombre))
{ {
if (flowValueLmin > 0) // Determinar dirección del flujo basado en el nombre de la rama
if (IsFlowTowardsTank(branchName))
{ {
// Flujo positivo hacia o desde el tanque NetFlow += flowValueLmin; // Flujo hacia el tanque (positivo)
if (IsFlowTowardsTank(branchName)) }
{ else
InletFlow += flowValueLmin; {
} NetFlow -= flowValueLmin; // Flujo desde el tanque (negativo)
else
{
OutletFlow += flowValueLmin;
}
} }
} }
} }
} }
private bool HasPumpConnection(string branchName, double flowValueLmin)
{
if (_mainViewModel == null) return false;
// Buscar si hay una bomba conectada que esté generando este flujo
var connectedPipes = _mainViewModel.ObjetosSimulables
.OfType<osHydPipe>()
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
.ToList();
foreach (var pipe in connectedPipes)
{
if (pipe.Id_ComponenteA == Nombre)
{
// Esta conexión sale del tanque
if (branchName.Contains(pipe.Id_ComponenteB) && branchName.Contains("Pump"))
{
OutletFlow += Math.Abs(flowValueLmin);
return true;
}
}
else if (pipe.Id_ComponenteB == Nombre)
{
// Esta conexión llega al tanque
if (branchName.Contains(pipe.Id_ComponenteA) && branchName.Contains("Pump"))
{
InletFlow += Math.Abs(flowValueLmin);
return true;
}
}
}
return false;
}
private double GetDeltaTime() private double GetDeltaTime()
{ {
@ -1292,14 +1143,7 @@ namespace CtrEditor.ObjetosSim
private string GetTankDescription() private string GetTankDescription()
{ {
return TankType switch return "Tanque hidráulico";
{
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) private bool IsFlowTowardsTank(string branchName)
@ -1377,124 +1221,6 @@ namespace CtrEditor.ObjetosSim
// Three-Section Tank Methods
/// <summary>
/// Obtiene la altura total del tanque
/// </summary>
private double GetHeightFromVolume()
{
return Tamano * 100; // Convertir tamaño a píxeles/altura
}
/// <summary>
/// Configura los niveles para el sistema de tres secciones
/// </summary>
public void ConfigureThreeSectionLevels()
{
if (!EnableThreeSection) return;
try
{
lock (_levelLock)
{
var totalHeight = GetHeightFromVolume();
var mixingHeight = MixingSectionHeight;
// Validar que la altura de mezcla no sea mayor que la altura total
if (mixingHeight > totalHeight)
{
mixingHeight = totalHeight * 0.3; // Máximo 30% de la altura total
MixingSectionHeight = mixingHeight;
}
// Calcular niveles basados en la altura actual del líquido
var currentLevel = CurrentLevelM;
if (currentLevel <= mixingHeight)
{
// Solo sección de mezcla
SecondaryLevelM = 0.0;
PrimaryLevelM = currentLevel;
}
else
{
// Mezcla completa + nivel adicional
SecondaryLevelM = currentLevel - mixingHeight;
PrimaryLevelM = mixingHeight;
}
UpdateThreeSectionPercentages();
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error configurando niveles de tres secciones: {ex.Message}");
}
}
/// <summary>
/// Actualiza los porcentajes de llenado para cada sección
/// </summary>
public void UpdateThreeSectionPercentages()
{
if (!EnableThreeSection) return;
try
{
lock (_levelLock)
{
var totalHeight = GetHeightFromVolume();
var mixingHeight = MixingSectionHeight;
var secondaryHeight = totalHeight - mixingHeight;
// Calcular porcentajes
if (mixingHeight > 0)
{
PrimaryPercentage = (PrimaryLevelM / mixingHeight) * 100.0;
PrimaryPercentage = Math.Min(100.0, Math.Max(0.0, PrimaryPercentage));
}
else
{
PrimaryPercentage = 0.0;
}
if (secondaryHeight > 0)
{
SecondaryPercentage = (SecondaryLevelM / secondaryHeight) * 100.0;
SecondaryPercentage = Math.Min(100.0, Math.Max(0.0, SecondaryPercentage));
}
else
{
SecondaryPercentage = 0.0;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error actualizando porcentajes de tres secciones: {ex.Message}");
}
}
/// <summary>
/// Actualiza los niveles basados en el nivel total actual
/// </summary>
public void UpdateThreeSectionLevels()
{
if (!EnableThreeSection) return;
try
{
lock (_levelLock)
{
ConfigureThreeSectionLevels();
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error actualizando niveles de tres secciones: {ex.Message}");
}
}

View File

@ -143,24 +143,6 @@
</Rectangle> </Rectangle>
</Grid> </Grid>
<!-- Líneas de nivel mejoradas -->
<Canvas Margin="8">
<!-- Línea de nivel máximo -->
<Line X1="0" Y1="4" X2="{Binding ActualWidth, ElementName=rectTankContainer, Converter={StaticResource SubtractConverter}, ConverterParameter=16}" Y2="4"
Stroke="#FFB22222" StrokeThickness="2" StrokeDashArray="4,2" Opacity="0.8"/>
<TextBlock Canvas.Right="2" Canvas.Top="-2" Text="MAX" Foreground="#FFB22222" FontSize="6" FontWeight="Bold"/>
<!-- Línea de nivel medio -->
<Line X1="0" Y1="{Binding MidLevelY}" X2="{Binding ActualWidth, ElementName=rectTankContainer, Converter={StaticResource SubtractConverter}, ConverterParameter=16}" Y2="{Binding MidLevelY}"
Stroke="#FFFF8C00" StrokeThickness="1" StrokeDashArray="2,2" Opacity="0.6"/>
<TextBlock Canvas.Right="2" Canvas.Top="{Binding MidLevelY, Converter={StaticResource SubtractConverter}, ConverterParameter=8}"
Text="50%" Foreground="#FFFF8C00" FontSize="5"/>
<!-- Línea de nivel mínimo -->
<Line X1="0" Y1="{Binding MinLevelY}" X2="{Binding ActualWidth, ElementName=rectTankContainer, Converter={StaticResource SubtractConverter}, ConverterParameter=16}" Y2="{Binding MinLevelY}"
Stroke="#FFB22222" StrokeThickness="2" StrokeDashArray="4,2" Opacity="0.8"/>
<TextBlock Canvas.Right="2" Canvas.Bottom="2" Text="MIN" Foreground="#FFB22222" FontSize="6" FontWeight="Bold"/>
</Canvas>
<!-- Información central del tanque --> <!-- Información central del tanque -->
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
@ -199,13 +181,6 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Tipo de tanque -->
<Border Grid.Row="0" Background="#FF000080" CornerRadius="3" Padding="4,2">
<TextBlock Text="{Binding TankTypeIndicator}"
Foreground="White"
FontWeight="Bold"
FontSize="10" />
</Border>
<!-- Tipo de fluido actual --> <!-- Tipo de fluido actual -->
<Border Grid.Row="1" Background="#FF006400" CornerRadius="3" Padding="3,2" Margin="0,2,0,0"> <Border Grid.Row="1" Background="#FF006400" CornerRadius="3" Padding="3,2" Margin="0,2,0,0">
@ -262,70 +237,36 @@
</Border> </Border>
</StackPanel> </StackPanel>
<!-- Fila 2: Flujos de entrada y salida --> <!-- Fila 2: Flujo neto -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,2"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,2">
<!-- Flujo de entrada --> <!-- Flujo neto -->
<Border Background="#FF228B22" CornerRadius="3" Padding="4,3" Margin="2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="▼" Foreground="White" FontSize="9" FontWeight="Bold"/>
<TextBlock Text="{Binding InletFlow, StringFormat='{}{0:F1}'}"
Foreground="White" FontSize="8" FontWeight="SemiBold" Margin="2,0,0,0"/>
<TextBlock Text="L/min" Foreground="LightGreen" FontSize="6" Opacity="0.9" Margin="1,0,0,0"/>
</StackPanel>
</Border>
<!-- Flujo de salida -->
<Border Background="#FFDC143C" CornerRadius="3" Padding="4,3" Margin="2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="▲" Foreground="White" FontSize="9" FontWeight="Bold"/>
<TextBlock Text="{Binding OutletFlow, StringFormat='{}{0:F1}'}"
Foreground="White" FontSize="8" FontWeight="SemiBold" Margin="2,0,0,0"/>
<TextBlock Text="L/min" Foreground="LightPink" FontSize="6" Opacity="0.9" Margin="1,0,0,0"/>
</StackPanel>
</Border>
<!-- Balance de flujo -->
<Border Background="{Binding FlowBalanceColor}" CornerRadius="3" Padding="4,3" Margin="2"> <Border Background="{Binding FlowBalanceColor}" CornerRadius="3" Padding="4,3" Margin="2">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="Δ" Foreground="White" FontSize="9" FontWeight="Bold"/> <TextBlock Text="⇄" Foreground="White" FontSize="9" FontWeight="Bold"/>
<TextBlock Text="{Binding FlowBalance, StringFormat='{}{0:F1}'}" <TextBlock Text="{Binding NetFlow, StringFormat='{}{0:F1}'}"
Foreground="White" FontSize="8" FontWeight="SemiBold" Margin="2,0,0,0"/> Foreground="White" FontSize="8" FontWeight="SemiBold" Margin="2,0,0,0"/>
<TextBlock Text="L/min" Foreground="White" FontSize="6" Opacity="0.9" Margin="1,0,0,0"/> <TextBlock Text="L/min" Foreground="White" FontSize="6" Opacity="0.9" Margin="1,0,0,0"/>
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>
<!-- Fila 3: Información de tres secciones (solo si está habilitado) --> <!-- Fila 3: Volumen secundario y mix (simplificado) -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,2" <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,2">
Visibility="{Binding EnableThreeSection, Converter={StaticResource BooleanToVisibilityConverter}}">
<!-- Sección secundaria --> <!-- Volumen secundario -->
<Border Background="#AA1E90FF" CornerRadius="3" Padding="3,2" Margin="1"> <Border Background="#AA1E90FF" CornerRadius="3" Padding="3,2" Margin="1">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="S:" Foreground="White" FontSize="7" FontWeight="Bold"/> <TextBlock Text="Sec:" Foreground="White" FontSize="7" FontWeight="Bold"/>
<TextBlock Text="{Binding SecondaryLevelM, StringFormat='{}{0:F2}m'}" <TextBlock Text="{Binding SecondaryVolumeL, StringFormat='{}{0:F0}L'}"
Foreground="White" FontSize="7" Margin="1,0,0,0"/> Foreground="White" FontSize="7" Margin="1,0,0,0"/>
<TextBlock Text="{Binding SecondaryVolumeL, StringFormat=' {0:F0}L'}"
Foreground="LightCyan" FontSize="6" Opacity="0.9"/>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- Sección primaria --> <!-- Volumen de mezcla -->
<Border Background="#AAFF6347" CornerRadius="3" Padding="3,2" Margin="1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="P:" Foreground="White" FontSize="7" FontWeight="Bold"/>
<TextBlock Text="{Binding PrimaryLevelM, StringFormat='{}{0:F2}m'}"
Foreground="White" FontSize="7" Margin="1,0,0,0"/>
<TextBlock Text="{Binding PrimaryVolumeL, StringFormat=' {0:F0}L'}"
Foreground="LightYellow" FontSize="6" Opacity="0.9"/>
</StackPanel>
</Border>
<!-- Ratio de mezcla -->
<Border Background="#AA8B4513" CornerRadius="3" Padding="3,2" Margin="1"> <Border Background="#AA8B4513" CornerRadius="3" Padding="3,2" Margin="1">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="Mix:" Foreground="White" FontSize="7" FontWeight="Bold"/> <TextBlock Text="Mix:" Foreground="White" FontSize="7" FontWeight="Bold"/>
<TextBlock Text="{Binding CurrentMixRatio, StringFormat='{}{0:P0}'}" <TextBlock Text="{Binding MixingVolumeL, StringFormat='{}{0:F0}L'}"
Foreground="White" FontSize="7" Margin="1,0,0,0"/> Foreground="White" FontSize="7" Margin="1,0,0,0"/>
</StackPanel> </StackPanel>
</Border> </Border>

View File

@ -52,6 +52,7 @@ namespace CtrEditor.Services
_isRunning = true; _isRunning = true;
// Instalar el trace listener personalizado solo para Trace // Instalar el trace listener personalizado solo para Trace
// Nota: Debug.Listeners no existe en .NET Core/.NET 8
_traceListener = new DebugTraceListener(this); _traceListener = new DebugTraceListener(this);
Trace.Listeners.Add(_traceListener); Trace.Listeners.Add(_traceListener);
@ -84,7 +85,7 @@ namespace CtrEditor.Services
_isRunning = false; _isRunning = false;
_cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Cancel();
// Remover el trace listener // Remover el trace listener de Trace
if (_traceListener != null) if (_traceListener != null)
{ {
Trace.Listeners.Remove(_traceListener); Trace.Listeners.Remove(_traceListener);
@ -116,21 +117,52 @@ namespace CtrEditor.Services
{ {
while (_isRunning && !cancellationToken.IsCancellationRequested) while (_isRunning && !cancellationToken.IsCancellationRequested)
{ {
Debug.WriteLine("[Debug Console Server] Esperando conexión de cliente..."); try
var tcpClient = await _tcpListener.AcceptTcpClientAsync(); {
_connectedClients.Add(tcpClient); // Implementar timeout y cancelación manual para evitar freeze
var acceptTask = _tcpListener.AcceptTcpClientAsync();
var delayTask = Task.Delay(1000, cancellationToken); // Check every 1s
var completedTask = await Task.WhenAny(acceptTask, delayTask);
if (completedTask == acceptTask && !cancellationToken.IsCancellationRequested)
{
var tcpClient = await acceptTask;
_connectedClients.Add(tcpClient);
Debug.WriteLine("[Debug Console Server] Cliente debug conectado"); Debug.WriteLine("[Debug Console Server] Cliente debug conectado");
// Manejar cliente en background // Manejar cliente en background
_ = Task.Run(async () => await HandleClientAsync(tcpClient, cancellationToken)); _ = Task.Run(async () => await HandleClientAsync(tcpClient, cancellationToken));
}
else if (cancellationToken.IsCancellationRequested)
{
break;
}
// Si completedTask == delayTask, simplemente continúa el loop para verificar estado
}
catch (ObjectDisposedException)
{
// TcpListener fue cerrado, salir silenciosamente
break;
}
catch (Exception ex)
{
Debug.WriteLine($"[Debug Console Server] Error aceptando conexión: {ex.Message}");
if (!_isRunning) break;
await Task.Delay(1000, cancellationToken); // Esperar antes de reintentar
}
} }
} }
catch (OperationCanceledException)
{
// Cancelación normal, no hacer nada
}
catch (Exception ex) catch (Exception ex)
{ {
if (_isRunning) if (_isRunning)
{ {
Debug.WriteLine($"[Debug Console Server] Error aceptando conexión: {ex.Message}"); Debug.WriteLine($"[Debug Console Server] Error en AcceptConnectionsAsync: {ex.Message}");
} }
} }
} }
@ -318,8 +350,9 @@ namespace CtrEditor.Services
} }
/// <summary> /// <summary>
/// TraceListener personalizado que captura mensajes de Debug.WriteLine /// TraceListener personalizado que captura mensajes de Trace.WriteLine
/// y los envía al servidor de debug console /// y los envía al servidor de debug console
/// Nota: Debug.WriteLine no está disponible en .NET Core/.NET 8
/// </summary> /// </summary>
internal class DebugTraceListener : TraceListener internal class DebugTraceListener : TraceListener
{ {