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:
parent
b39c58e6d6
commit
3c9c0e2479
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue