Implement Python execution support and enhance hydraulic component management
- Added Python execution capabilities via MCPServer, allowing scripts to interact with CtrEditor objects. - Introduced methods for executing Python code and retrieving help on available objects. - Updated osHydPump and osHydTank classes to remove direct hydraulic object management, leveraging the new unified hydraulic simulation system. - Created documentation for Python integration and usage examples. - Developed a script for analyzing screenshots to detect circular objects and assess their centering. - Implemented tests for Python execution functionality to ensure proper integration and response handling. - Addressed potential freeze issues in MCPServer and StateManager by improving async handling and cancellation support.
This commit is contained in:
parent
3c9c0e2479
commit
181a3db41c
|
@ -103,6 +103,8 @@
|
|||
<PackageReference Include="Emgu.CV.UI" Version="4.9.0.5494" />
|
||||
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.7.25104.5739" />
|
||||
<PackageReference Include="HelixToolkit.Wpf" Version="2.27.0" />
|
||||
<PackageReference Include="IronPython" Version="3.4.2" />
|
||||
<PackageReference Include="IronPython.StdLib" Version="3.4.2" />
|
||||
<PackageReference Include="LanguageDetection" Version="1.2.0" />
|
||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc3.3" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||
|
|
|
@ -258,12 +258,9 @@ namespace CtrEditor
|
|||
if (obj != null)
|
||||
{
|
||||
obj.CheckData();
|
||||
await Task.Run(() =>
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
CrearUserControlDesdeObjetoSimulable(obj);
|
||||
});
|
||||
CrearUserControlDesdeObjetoSimulable(obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -369,7 +366,16 @@ namespace CtrEditor
|
|||
{
|
||||
if (_hasUnsavedChanges)
|
||||
{
|
||||
SaveAllAsync().Wait();
|
||||
try
|
||||
{
|
||||
// Usar TaskScheduler.Default para evitar deadlocks en UI thread
|
||||
Task.Run(async () => await SaveAllAsync()).Wait(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error en lugar de fallar el dispose
|
||||
System.Diagnostics.Debug.WriteLine($"[StateManager] Error en Dispose: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
### 🟡 **Medium** (Use When Needed)
|
||||
- `search_debug_log` → 50-200 tokens (max_lines=3-10)
|
||||
- `build_project` → 100-500 tokens (errors only)
|
||||
- `execute_python` → 50-500 tokens (depends on script)
|
||||
- `python_help` → 100-300 tokens
|
||||
|
||||
### 🔴 **Heavy** (Use Sparingly)
|
||||
- `list_objects` → 500-2000+ tokens
|
||||
|
@ -27,6 +29,7 @@
|
|||
```
|
||||
|
||||
### Build & Debug
|
||||
Para los Debug se usa DebugTraceListener que es soportado por WPF
|
||||
```json
|
||||
{"tool": "build_project", "parameters": {}}
|
||||
{"tool": "start_debug_listener", "parameters": {}}
|
||||
|
@ -49,6 +52,16 @@
|
|||
{"tool": "delete_objects", "parameters": {"ids": ["123", "456"]}}
|
||||
```
|
||||
|
||||
### Python Debug Scripts ⚡ NEW
|
||||
```json
|
||||
{"tool": "execute_python", "parameters": {"code": "print(f'Total objects: {len(objects)}')"}}
|
||||
{"tool": "execute_python", "parameters": {
|
||||
"code": "result = [obj for obj in objects if 'Pump' in str(type(obj))]",
|
||||
"return_variables": ["result"]
|
||||
}}
|
||||
{"tool": "python_help", "parameters": {"object_name": "app"}}
|
||||
```
|
||||
|
||||
## ⚡ Optimal Workflows
|
||||
|
||||
### Quick Development Cycle
|
||||
|
@ -56,12 +69,14 @@
|
|||
{"tool": "get_ctreditor_status", "parameters": {}}
|
||||
{"tool": "build_project", "parameters": {}}
|
||||
{"tool": "start_simulation", "parameters": {}}
|
||||
{"tool": "execute_python", "parameters": {"code": "print(f'Objects: {len(objects)}, Running: {app.IsSimulationRunning}')"}}
|
||||
{"tool": "get_simulation_status", "parameters": {}}
|
||||
```
|
||||
|
||||
### Bug Investigation
|
||||
```json
|
||||
{"tool": "search_debug_log", "parameters": {"pattern": "error|exception", "max_lines": 3}}
|
||||
{"tool": "execute_python", "parameters": {"code": "print([type(obj).__name__ for obj in objects[:5]])"}}
|
||||
{"tool": "get_simulation_status", "parameters": {}}
|
||||
```
|
||||
|
||||
|
@ -73,6 +88,12 @@
|
|||
|
||||
## 📊 Key Object Properties
|
||||
|
||||
### Python Debug Variables
|
||||
- **app**: MainViewModel (simulation state, canvas, objects)
|
||||
- **canvas**: MainCanvas (Width, Height, visual elements)
|
||||
- **objects**: ObservableCollection of all simulable objects
|
||||
- **get_objects()**: Returns List<object> for easier manipulation
|
||||
|
||||
### Hydraulic Components
|
||||
- **osHydTank**: `TankPressure`, `MaxLevel`, `MinLevel`, `IsFixedPressure`
|
||||
- **osHydPump**: `PumpHead`, `MaxFlow`, `SpeedRatio`, `IsRunning`
|
||||
|
@ -104,6 +125,26 @@ High-value, low-token patterns:
|
|||
- **Performance**: `"fps|slow|memory"` (max_lines=5)
|
||||
- **Simulation**: `"simulation.*error|physics.*fail"` (max_lines=3)
|
||||
|
||||
## 🐍 Python Debug Examples
|
||||
|
||||
### Quick Inspections
|
||||
```python
|
||||
# Object count by type
|
||||
types = {}
|
||||
for obj in objects:
|
||||
t = type(obj).__name__
|
||||
types[t] = types.get(t, 0) + 1
|
||||
print(types)
|
||||
|
||||
# Canvas info
|
||||
print(f"Canvas: {canvas.Width}x{canvas.Height}")
|
||||
print(f"Simulation: {app.IsSimulationRunning}")
|
||||
|
||||
# Find specific objects
|
||||
pumps = [obj for obj in objects if 'Pump' in str(type(obj))]
|
||||
print(f"Found {len(pumps)} pumps")
|
||||
```
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### Build Issues
|
||||
|
@ -129,6 +170,8 @@ High-value, low-token patterns:
|
|||
- Always use `get_simulation_status` before expensive operations
|
||||
- Use specific patterns in `search_debug_log` with low max_lines
|
||||
- Call `list_objects` only when object data is actually needed
|
||||
- **NEW**: Use `execute_python` for quick object inspections instead of `list_objects`
|
||||
- Stop simulation before major structural changes
|
||||
- Use appropriate units: meters, Pascal, m³/s
|
||||
- **Python scripts**: Keep under 30 seconds, use `return_variables` for results
|
||||
- Proxy works with both Claude Desktop and Cursor (MCP 2025-06-18)
|
||||
|
|
|
@ -733,3 +733,7 @@ private async Task AcceptConnectionsAsync(CancellationToken cancellationToken)
|
|||
|
||||
Esta corrección resuelve un problema crítico de estabilidad que impedía el uso seguro del sistema de debug logging.
|
||||
|
||||
* Se ha mejorado significativamente el sistema de captura de imágenes para hacerlo útil con LLMs. La función `take_screenshot` ahora incluye parámetros avanzados: `return_base64` (por defecto true) para devolver la imagen en formato base64 compatible con LLMs, `save_file` para controlar si guardar archivo, y opciones de área específica (x, y, width, height en metros). El proxy MCP detecta automáticamente respuestas con imágenes base64 y las reformatea usando el tipo "image" de MCP, permitiendo que los LLMs analizen visualmente el estado del canvas. La implementación mantiene alta resolución con factor de escala dinámico y soporta tanto captura completa como parcial del canvas. Esto permite análisis automático de simulaciones por IA y debugging visual avanzado.
|
||||
|
||||
* Se identificó y resolvió un problema crítico de sincronización en el servidor MCP durante la inicialización de CtrEditor. El error "MPC -32603: CtrEditor not available" ocurría porque aunque CtrEditor se iniciaba correctamente, el servidor MCP interno (puerto 5006) requiere 30-60 segundos adicionales para estar completamente operativo. El servidor se inicia automáticamente en el constructor del MainViewModel pero no acepta conexiones inmediatamente. La solución documentada es esperar este tiempo de inicialización antes de intentar operaciones MCP como `create_object`. Una vez inicializado, el método CreateObject funciona perfectamente para crear objetos como osHydPump con todas sus propiedades. Este conocimiento es crucial para el uso correcto del sistema MCP y evita falsas alarmas sobre conectividad.
|
||||
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# Soluciones Implementadas para Freezes en CtrEditor
|
||||
|
||||
## Problema Identificado
|
||||
La aplicación CtrEditor experimentaba freezes relacionados con I/O Completion Ports, específicamente en:
|
||||
```
|
||||
System.Private.CoreLib.dll!System.Threading.PortableThreadPool.IOCompletionPoller.Poll()
|
||||
Interop.Kernel32.GetQueuedCompletionStatusEx(..., Timeout.Infinite, ...)
|
||||
```
|
||||
|
||||
## Causas Raíz y Soluciones Implementadas
|
||||
|
||||
### 1. **MCPServer - AcceptTcpClientAsync Sin Cancelación**
|
||||
**Archivo:** `Services/MCPServer.cs`
|
||||
**Problema:** El método `AcceptTcpClientAsync()` sin `CancellationToken` causaba bloqueos indefinidos en el I/O completion port.
|
||||
|
||||
**Solución Implementada:**
|
||||
```csharp
|
||||
// ANTES (PROBLEMÁTICO)
|
||||
var tcpClient = await _tcpListener.AcceptTcpClientAsync();
|
||||
|
||||
// DESPUÉS (CORREGIDO)
|
||||
var acceptTask = _tcpListener.AcceptTcpClientAsync();
|
||||
var delayTask = Task.Delay(1000, cancellationToken);
|
||||
var completedTask = await Task.WhenAny(acceptTask, delayTask);
|
||||
|
||||
if (completedTask == acceptTask && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var tcpClient = await acceptTask;
|
||||
// Procesar cliente...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **StateManager - Bloqueo en Dispose**
|
||||
**Archivo:** `DataStates/StateManager.cs`
|
||||
**Problema:** Uso de `.Wait()` en método `Dispose()` puede causar deadlocks.
|
||||
|
||||
**Solución Implementada:**
|
||||
```csharp
|
||||
// ANTES (PROBLEMÁTICO)
|
||||
SaveAllAsync().Wait();
|
||||
|
||||
// DESPUÉS (CORREGIDO)
|
||||
Task.Run(async () => await SaveAllAsync()).Wait(TimeSpan.FromSeconds(10));
|
||||
```
|
||||
|
||||
### 3. **StateManager - Antipatrón Task.Run + Dispatcher.Invoke**
|
||||
**Archivo:** `DataStates/StateManager.cs`
|
||||
**Problema:** Patrón ineficiente que puede causar contención de hilos.
|
||||
|
||||
**Solución Implementada:**
|
||||
```csharp
|
||||
// ANTES (PROBLEMÁTICO)
|
||||
await Task.Run(() =>
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
CrearUserControlDesdeObjetoSimulable(obj);
|
||||
});
|
||||
});
|
||||
|
||||
// DESPUÉS (CORREGIDO)
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
CrearUserControlDesdeObjetoSimulable(obj);
|
||||
});
|
||||
```
|
||||
|
||||
### 4. **HttpClient - Falta de ConfigureAwait(false)**
|
||||
**Archivo:** `IA/gtpask.cs`
|
||||
**Problema:** Operaciones HTTP sin `ConfigureAwait(false)` pueden causar deadlocks en UI thread.
|
||||
|
||||
**Solución Implementada:**
|
||||
```csharp
|
||||
// ANTES (PROBLEMÁTICO)
|
||||
using var response = await _httpClient.PostAsync(endpoint, content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// DESPUÉS (CORREGIDO)
|
||||
using var response = await _httpClient.PostAsync(endpoint, content).ConfigureAwait(false);
|
||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
```
|
||||
|
||||
## Resultado
|
||||
- Eliminación de bloqueos indefinitos en I/O Completion Ports
|
||||
- Prevención de deadlocks en operaciones de red y UI
|
||||
- Mejora en la capacidad de cancelación de operaciones TCP
|
||||
- Manejo más robusto de operaciones asíncronas
|
||||
|
||||
## Verificación
|
||||
- ✅ Proyecto compila sin errores
|
||||
- ✅ DebugConsoleServer ya tenía la misma corrección implementada
|
||||
- ✅ Aplicados timeouts y manejo de excepciones apropiados
|
||||
|
||||
## Próximos Pasos Recomendados
|
||||
1. Probar la aplicación en escenarios que anteriormente causaban freezes
|
||||
2. Monitorear logs para verificar que las operaciones TCP se cancelan correctamente
|
||||
3. Considerar aplicar `ConfigureAwait(false)` a otras operaciones asíncronas en el proyecto
|
||||
4. Revisar otros usos de operaciones síncronas en contextos asíncronos
|
||||
|
||||
## Archivos Modificados
|
||||
- `Services/MCPServer.cs` - Corrección de AcceptTcpClientAsync
|
||||
- `DataStates/StateManager.cs` - Corrección de Dispose y antipatrón Task.Run
|
||||
- `IA/gtpask.cs` - Agregado de ConfigureAwait(false) para operaciones HTTP
|
|
@ -8,7 +8,8 @@ using CtrEditor.ObjetosSim;
|
|||
namespace CtrEditor.HydraulicSimulator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gestiona la simulación hidráulica similar a SimulationManagerBEPU
|
||||
/// Gestiona la simulación hidráulica de manera unificada, similar a SimulationManagerBEPU
|
||||
/// Utiliza solo el patrón osBase + IHydraulicComponent + ApplyHydraulicResults
|
||||
/// </summary>
|
||||
public class HydraulicSimulationManager : IDisposable
|
||||
{
|
||||
|
@ -53,9 +54,9 @@ namespace CtrEditor.HydraulicSimulator
|
|||
/// Parámetros del solver
|
||||
/// </summary>
|
||||
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;
|
||||
public double RelaxationFactor { get; set; } = 0.8;
|
||||
public bool VerboseOutput { get; set; } = true; // Activado para debugging
|
||||
public bool VerboseOutput { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Contador de pasos de simulación para optimizaciones
|
||||
|
@ -123,7 +124,7 @@ namespace CtrEditor.HydraulicSimulator
|
|||
_objectMapping[hydraulicObject.Nombre] = hydraulicObject;
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
// Debug.WriteLine($"Objeto hidráulico registrado: {hydraulicObject.Nombre}");
|
||||
Trace.WriteLine($"Objeto hidráulico registrado: {hydraulicObject.Nombre}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +142,7 @@ namespace CtrEditor.HydraulicSimulator
|
|||
_objectMapping.Remove(hydraulicObject.Nombre);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
// Debug.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}");
|
||||
Trace.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,7 +155,7 @@ namespace CtrEditor.HydraulicSimulator
|
|||
_objectMapping.Clear();
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
// Debug.WriteLine("Todos los objetos hidráulicos han sido limpiados");
|
||||
Trace.WriteLine("Todos los objetos hidráulicos han sido limpiados");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -169,7 +170,7 @@ namespace CtrEditor.HydraulicSimulator
|
|||
if (!_networkNeedsRebuild)
|
||||
return;
|
||||
|
||||
// Debug.WriteLine("Reconstruyendo red hidráulica...");
|
||||
Trace.WriteLine("Reconstruyendo red hidráulica...");
|
||||
|
||||
// Crear nueva red
|
||||
Network = new HydraulicNetwork(SimulationFluid);
|
||||
|
@ -180,31 +181,7 @@ namespace CtrEditor.HydraulicSimulator
|
|||
|
||||
_networkNeedsRebuild = false;
|
||||
|
||||
// Debug.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas");
|
||||
|
||||
// Verbose output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine("=== DETALLES DE LA RED HIDRÁULICA ===");
|
||||
Debug.WriteLine("Nodos:");
|
||||
foreach (var node in Network.Nodes)
|
||||
{
|
||||
string pressureInfo = node.Value.FixedP ? $"Presión fija: {node.Value.P:F0} Pa" : "Presión libre";
|
||||
Debug.WriteLine($" - {node.Key}: {pressureInfo}");
|
||||
}
|
||||
Debug.WriteLine("Ramas:");
|
||||
foreach (var branch in Network.Branches)
|
||||
{
|
||||
Debug.WriteLine($" - {branch.Name}: {branch.N1} -> {branch.N2} ({branch.Elements.Count} elementos)");
|
||||
foreach (var element in branch.Elements)
|
||||
{
|
||||
Debug.WriteLine($" * {element.GetType().Name}");
|
||||
}
|
||||
}
|
||||
Debug.WriteLine("=== FIN DETALLES RED ===");
|
||||
}
|
||||
*/
|
||||
Trace.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -225,26 +202,14 @@ namespace CtrEditor.HydraulicSimulator
|
|||
// Agregar nodo a la red
|
||||
Network.AddNode(nodeDef.Name, nodeDef.IsFixedPressure ? nodeDef.Pressure : null);
|
||||
|
||||
// Verbose output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine($"Nodo agregado: {nodeDef.Name} " +
|
||||
Trace.WriteLine($"Nodo agregado: {nodeDef.Name} " +
|
||||
$"(Presión fija: {nodeDef.IsFixedPressure}, " +
|
||||
$"Presión: {nodeDef.Pressure?.ToString() ?? "libre"})");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Objeto sin interfaz hidráulica - crear nodos básicos por compatibilidad
|
||||
string nodeName = $"Node_{obj.Nombre}";
|
||||
bool isFixedPressure = DetermineIfFixedPressure(obj);
|
||||
double? pressure = isFixedPressure ? GetObjectPressure(obj) : null;
|
||||
|
||||
Network.AddNode(nodeName, pressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,30 +232,11 @@ namespace CtrEditor.HydraulicSimulator
|
|||
var elements = new List<Element> { elemDef.Element };
|
||||
Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name);
|
||||
|
||||
// Verbose output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine($"Rama agregada: {elemDef.Name} " +
|
||||
Trace.WriteLine($"Rama agregada: {elemDef.Name} " +
|
||||
$"({elemDef.FromNode} -> {elemDef.ToNode})");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Objeto sin interfaz hidráulica - crear elementos básicos por compatibilidad
|
||||
var elements = CreateHydraulicElementsFromObject(obj);
|
||||
|
||||
if (elements.Any())
|
||||
{
|
||||
string fromNode = GetSourceNode(obj);
|
||||
string toNode = GetTargetNode(obj);
|
||||
|
||||
if (!string.IsNullOrEmpty(fromNode) && !string.IsNullOrEmpty(toNode))
|
||||
{
|
||||
Network.AddBranch(fromNode, toNode, elements, $"Branch_{obj.Nombre}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -298,71 +244,12 @@ namespace CtrEditor.HydraulicSimulator
|
|||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods for Object Analysis
|
||||
|
||||
/// <summary>
|
||||
/// Determina si un objeto debe tener presión fija
|
||||
/// </summary>
|
||||
private bool DetermineIfFixedPressure(osBase obj)
|
||||
{
|
||||
// Implementación que será extendida con tipos específicos
|
||||
// Por ejemplo: tanques, descargas atmosféricas = presión fija
|
||||
// Uniones, conexiones = presión libre
|
||||
|
||||
return false; // Por defecto, presión libre
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene la presión de un objeto (si tiene presión fija)
|
||||
/// </summary>
|
||||
private double GetObjectPressure(osBase obj)
|
||||
{
|
||||
// Implementación que será extendida
|
||||
return 0.0; // Por defecto, presión atmosférica
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea elementos hidráulicos basándose en un objeto
|
||||
/// </summary>
|
||||
private List<Element> CreateHydraulicElementsFromObject(osBase obj)
|
||||
{
|
||||
var elements = new List<Element>();
|
||||
|
||||
// Implementación que será extendida con tipos específicos
|
||||
// Por ejemplo:
|
||||
// - Si es una bomba: crear PumpHQ
|
||||
// - Si es una tubería: crear Pipe
|
||||
// - Si es una válvula: crear ValveKv
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el nodo fuente de un objeto
|
||||
/// </summary>
|
||||
private string GetSourceNode(osBase obj)
|
||||
{
|
||||
// Implementación que será extendida
|
||||
return $"Node_{obj.Nombre}_In";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el nodo destino de un objeto
|
||||
/// </summary>
|
||||
private string GetTargetNode(osBase obj)
|
||||
{
|
||||
// Implementación que será extendida
|
||||
return $"Node_{obj.Nombre}_Out";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Simulation Step
|
||||
|
||||
/// <summary>
|
||||
/// Ejecuta un paso de simulación hidráulica (equivalente al Step de BEPU)
|
||||
/// </summary>
|
||||
public void Step(float deltaTime = 0.016f) // ~60 FPS por defecto
|
||||
public void Step(float deltaTime = 0.01f) // 10ms por defecto (100 FPS hidráulico)
|
||||
{
|
||||
if (!IsHydraulicSimulationEnabled)
|
||||
return;
|
||||
|
@ -394,49 +281,19 @@ namespace CtrEditor.HydraulicSimulator
|
|||
|
||||
if (LastSolutionResult.Converged)
|
||||
{
|
||||
// Aplicar resultados a los objetos
|
||||
// Aplicar resultados a los objetos usando ApplyHydraulicResults
|
||||
ApplyResultsToObjects();
|
||||
|
||||
// Verbose output de resultados deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput && _stepCount % 300 == 0) // Log cada 5 segundos aprox
|
||||
if (VerboseOutput && _stepCount % 100 == 0) // Log cada segundo aprox
|
||||
{
|
||||
//Debug.WriteLine("=== RESULTADOS SIMULACIÓN HIDRÁULICA ===");
|
||||
//Debug.WriteLine("Flujos:");
|
||||
foreach (var flow in LastSolutionResult.Flows)
|
||||
{
|
||||
Debug.WriteLine($" {flow.Key}: {flow.Value:F6} m³/s ({flow.Value * 3600:F2} m³/h)");
|
||||
}
|
||||
Debug.WriteLine("Presiones:");
|
||||
foreach (var pressure in LastSolutionResult.Pressures)
|
||||
{
|
||||
Debug.WriteLine($" {pressure.Key}: {pressure.Value:F0} Pa ({pressure.Value / 100000:F2} bar)");
|
||||
}
|
||||
Debug.WriteLine("=== FIN RESULTADOS ===");
|
||||
Trace.WriteLine($"Simulación hidráulica - Paso {_stepCount}: Convergió exitosamente");
|
||||
}
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}");
|
||||
Trace.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}");
|
||||
Trace.WriteLine($" Tolerancia requerida: {Tolerance:E6}");
|
||||
|
||||
// Diagnóstico detallado deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput && _stepCount % 60 == 0) // Log detallado cada segundo aprox
|
||||
{
|
||||
Debug.WriteLine("=== DIAGNÓSTICO CONVERGENCIA ===");
|
||||
Debug.WriteLine($"Nodos en red: {Network.Nodes.Count}");
|
||||
Debug.WriteLine($"Ramas en red: {Network.Branches.Count}");
|
||||
foreach (var node in Network.Nodes)
|
||||
{
|
||||
string info = node.Value.FixedP ? $"FIJA={node.Value.P:F0}Pa" : "LIBRE";
|
||||
Debug.WriteLine($" Nodo {node.Key}: {info}");
|
||||
}
|
||||
Debug.WriteLine("=== FIN DIAGNÓSTICO ===");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -458,14 +315,11 @@ namespace CtrEditor.HydraulicSimulator
|
|||
{
|
||||
_stopwatch.Stop();
|
||||
|
||||
// Logging de tiempo deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput && _stepCount % 60 == 0) // Log cada segundo aprox
|
||||
if (VerboseOutput && _stepCount % 100 == 0) // Log cada segundo aprox
|
||||
{
|
||||
Debug.WriteLine($"Simulación hidráulica - Paso {_stepCount}: {_stopwatch.ElapsedMilliseconds}ms, " +
|
||||
Trace.WriteLine($"Simulación hidráulica - Paso {_stepCount}: {_stopwatch.ElapsedMilliseconds}ms, " +
|
||||
$"Objetos: {HydraulicObjects.Count}, Convergió: {LastSolutionResult.Converged}");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -474,289 +328,29 @@ namespace CtrEditor.HydraulicSimulator
|
|||
/// </summary>
|
||||
private void UpdateObjectProperties()
|
||||
{
|
||||
// Actualizar objetos hidráulicos específicos
|
||||
foreach (var simObj in HydraulicSimObjects)
|
||||
{
|
||||
simObj.UpdateProperties();
|
||||
}
|
||||
|
||||
// Actualizar objetos hidráulicos generales (legacy)
|
||||
// Actualizar objetos hidráulicos antes de la simulación
|
||||
foreach (var obj in HydraulicObjects)
|
||||
{
|
||||
// Aquí se implementará la actualización de propiedades específicas
|
||||
// Por ejemplo: estado de válvulas, velocidad de bombas, etc.
|
||||
UpdateObjectHydraulicProperties(obj);
|
||||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
hydraulicComponent.UpdateHydraulicProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza las propiedades hidráulicas específicas de un objeto
|
||||
/// </summary>
|
||||
private void UpdateObjectHydraulicProperties(osBase obj)
|
||||
{
|
||||
// Actualizar propiedades basándose en las interfaces implementadas
|
||||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
// Llamar al método de actualización del objeto
|
||||
hydraulicComponent.UpdateHydraulicProperties();
|
||||
}
|
||||
|
||||
// Actualización específica por tipo de objeto
|
||||
if (obj is IHydraulicPump pump)
|
||||
{
|
||||
// Verificar estado de la bomba, velocidad, etc.
|
||||
UpdatePumpProperties(pump);
|
||||
}
|
||||
else if (obj is IHydraulicValve valve)
|
||||
{
|
||||
// Verificar apertura de la válvula
|
||||
UpdateValveProperties(valve);
|
||||
}
|
||||
else if (obj is IHydraulicTank tank)
|
||||
{
|
||||
// Actualizar nivel y presión del tanque
|
||||
UpdateTankProperties(tank);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza propiedades específicas de bombas
|
||||
/// </summary>
|
||||
private void UpdatePumpProperties(IHydraulicPump pump)
|
||||
{
|
||||
// Aquí se pueden hacer validaciones y ajustes específicos de bombas
|
||||
// Por ejemplo, limitar velocidad, verificar estado de operación, etc.
|
||||
|
||||
if (pump.SpeedRatio < 0.0) pump.SpeedRatio = 0.0;
|
||||
if (pump.SpeedRatio > 1.0) pump.SpeedRatio = 1.0;
|
||||
|
||||
// Debug output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Trace.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " +
|
||||
$"Funcionando={pump.IsRunning}, Dirección={pump.PumpDirection}");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza propiedades específicas de válvulas
|
||||
/// </summary>
|
||||
private void UpdateValveProperties(IHydraulicValve valve)
|
||||
{
|
||||
// Validar apertura de válvula
|
||||
if (valve.Opening < 0.0) valve.Opening = 0.0;
|
||||
if (valve.Opening > 1.0) valve.Opening = 1.0;
|
||||
|
||||
// Debug output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine($"Válvula {valve.GetType().Name}: Apertura={valve.Opening:F2}, " +
|
||||
$"Cerrada={valve.IsClosed}, Abierta={valve.IsFullyOpen}");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza propiedades específicas de tanques
|
||||
/// </summary>
|
||||
private void UpdateTankProperties(IHydraulicTank tank)
|
||||
{
|
||||
// Calcular presión basada en nivel si no es presión fija
|
||||
if (!tank.IsFixedPressure)
|
||||
{
|
||||
// P = ρgh + P_atmosferica (resultado en Pa)
|
||||
double pressureFromLevel = SimulationFluid.Rho * 9.80665 * tank.Level; // + presión atmosférica
|
||||
// Convertir de Pa a bar (1 bar = 100000 Pa)
|
||||
tank.TankPressure = pressureFromLevel / 100000.0;
|
||||
}
|
||||
|
||||
// Debug output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Trace.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " +
|
||||
$"Presión={tank.TankPressure:F3}bar ({tank.TankPressure * 100000.0:F0}Pa), PresionFija={tank.IsFixedPressure}");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aplica los resultados de la simulación a los objetos
|
||||
/// Aplica los resultados de la simulación a los objetos usando ApplyHydraulicResults
|
||||
/// Este es el único punto donde se actualizan los resultados, siguiendo el patrón BEPU
|
||||
/// </summary>
|
||||
private void ApplyResultsToObjects()
|
||||
{
|
||||
// Aplicar resultados a objetos hidráulicos específicos
|
||||
foreach (var simObj in HydraulicSimObjects)
|
||||
{
|
||||
ApplyResultsToSimObject(simObj);
|
||||
simObj.ApplySimulationResults();
|
||||
}
|
||||
|
||||
// Aplicar resultados a objetos hidráulicos generales (legacy)
|
||||
// Aplicar resultados a todos los objetos hidráulicos usando ApplyHydraulicResults
|
||||
foreach (var obj in HydraulicObjects)
|
||||
{
|
||||
// Aplicar caudales y presiones calculados al objeto
|
||||
ApplyResultsToObject(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aplica los resultados de la simulación a un objeto específico
|
||||
/// </summary>
|
||||
private void ApplyResultsToSimObject(simHydraulicBase simObj)
|
||||
{
|
||||
// Aplicar resultados específicos según el tipo de objeto
|
||||
if (simObj is simHydraulicPump pump)
|
||||
{
|
||||
// Buscar flujo y presión de la bomba en los resultados
|
||||
string pumpBranchName = $"{pump.Nombre}_Pump";
|
||||
if (LastSolutionResult.Flows.TryGetValue(pumpBranchName, out double flow))
|
||||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
pump.CurrentFlow = flow;
|
||||
hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures);
|
||||
}
|
||||
|
||||
string inletNodeName = $"{pump.Nombre}_In";
|
||||
if (LastSolutionResult.Pressures.TryGetValue(inletNodeName, out double pressure))
|
||||
{
|
||||
pump.CurrentPressure = pressure;
|
||||
}
|
||||
}
|
||||
else if (simObj is simHydraulicTank tank)
|
||||
{
|
||||
// Buscar flujos de entrada y salida del tanque
|
||||
// Esto requeriría mapeo de tuberías conectadas
|
||||
// Por ahora, aplicar presión del tanque
|
||||
string tankNodeName = $"{tank.Nombre}_Tank";
|
||||
if (LastSolutionResult.Pressures.TryGetValue(tankNodeName, out double pressure))
|
||||
{
|
||||
tank.CurrentPressure = pressure;
|
||||
}
|
||||
}
|
||||
else if (simObj is simHydraulicPipe pipe)
|
||||
{
|
||||
// Buscar flujo a través de la tubería
|
||||
string pipeBranchName = $"{pipe.Nombre}_Pipe";
|
||||
if (LastSolutionResult.Flows.TryGetValue(pipeBranchName, out double flow))
|
||||
{
|
||||
pipe.CurrentFlow = flow;
|
||||
}
|
||||
|
||||
// Calcular pérdida de presión
|
||||
string fromNode = pipe.ComponenteAId;
|
||||
string toNode = pipe.ComponenteBId;
|
||||
if (LastSolutionResult.Pressures.TryGetValue(fromNode, out double pressureA) &&
|
||||
LastSolutionResult.Pressures.TryGetValue(toNode, out double pressureB))
|
||||
{
|
||||
pipe.PressureDrop = pressureA - pressureB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aplica los resultados a un objeto específico
|
||||
/// </summary>
|
||||
private void ApplyResultsToObject(osBase obj)
|
||||
{
|
||||
Debug.WriteLine($"[HYDRAULIC] ApplyResultsToObject: {obj.Nombre} ({obj.GetType().Name})");
|
||||
|
||||
// Aplicar resultados usando las interfaces hidráulicas
|
||||
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
|
||||
hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures);
|
||||
}
|
||||
|
||||
// Aplicación específica por tipo de interfaz
|
||||
if (obj is IHydraulicFlowReceiver flowReceiver)
|
||||
{
|
||||
Debug.WriteLine($"[HYDRAULIC] {obj.Nombre} is IHydraulicFlowReceiver");
|
||||
// Buscar caudal asociado al objeto
|
||||
string branchName = FindBranchNameForObject(obj);
|
||||
Debug.WriteLine($"[HYDRAULIC] Branch name for {obj.Nombre}: '{branchName}'");
|
||||
if (!string.IsNullOrEmpty(branchName) && LastSolutionResult.Flows.ContainsKey(branchName))
|
||||
{
|
||||
double flow = LastSolutionResult.Flows[branchName];
|
||||
Debug.WriteLine($"[HYDRAULIC] Setting flow {flow:F6} m³/s to {obj.Nombre}");
|
||||
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)
|
||||
{
|
||||
// Buscar presión asociada al objeto
|
||||
string nodeName = FindNodeNameForObject(obj);
|
||||
if (!string.IsNullOrEmpty(nodeName) && LastSolutionResult.Pressures.ContainsKey(nodeName))
|
||||
{
|
||||
double pressure = LastSolutionResult.Pressures[nodeName];
|
||||
pressureReceiver.SetPressure(pressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encuentra el nombre de rama asociado a un objeto
|
||||
/// </summary>
|
||||
private string FindBranchNameForObject(osBase obj)
|
||||
{
|
||||
// Buscar en las ramas de la red
|
||||
foreach (var branch in Network.Branches)
|
||||
{
|
||||
if (branch.Name.Contains(obj.Nombre))
|
||||
{
|
||||
return branch.Name;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback a nombre por defecto
|
||||
return $"Branch_{obj.Nombre}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encuentra el nombre de nodo asociado a un objeto
|
||||
/// </summary>
|
||||
private string FindNodeNameForObject(osBase obj)
|
||||
{
|
||||
// Buscar en los nodos de la red
|
||||
foreach (var nodeKvp in Network.Nodes)
|
||||
{
|
||||
if (nodeKvp.Key.Contains(obj.Nombre))
|
||||
{
|
||||
return nodeKvp.Key;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback a nombre por defecto
|
||||
return $"Node_{obj.Nombre}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Establece el caudal en un objeto
|
||||
/// </summary>
|
||||
private void SetObjectFlow(osBase obj, double flow)
|
||||
{
|
||||
if (obj is IHydraulicFlowReceiver flowReceiver)
|
||||
{
|
||||
flowReceiver.SetFlow(flow);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Establece la presión en un objeto
|
||||
/// </summary>
|
||||
private void SetObjectPressure(osBase obj, double pressure)
|
||||
{
|
||||
if (obj is IHydraulicPressureReceiver pressureReceiver)
|
||||
{
|
||||
pressureReceiver.SetPressure(pressure);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -812,17 +406,10 @@ namespace CtrEditor.HydraulicSimulator
|
|||
{
|
||||
EnableNPSHVerification = enableNPSH;
|
||||
|
||||
// Actualizar todas las bombas existentes con los nuevos parámetros
|
||||
foreach (var obj in HydraulicObjects)
|
||||
{
|
||||
if (obj is osHydPump pump)
|
||||
{
|
||||
// Los parámetros se aplicarán cuando se reconstruya la red
|
||||
InvalidateNetwork();
|
||||
}
|
||||
}
|
||||
// Invalidar la red para que se reconstruya con los nuevos parámetros
|
||||
InvalidateNetwork();
|
||||
|
||||
Debug.WriteLine($"Verificación NPSH {(enableNPSH ? "habilitada" : "deshabilitada")}: NPSH_req={npshRequired}m, P_vapor={vaporPressure}Pa");
|
||||
Trace.WriteLine($"Verificación NPSH {(enableNPSH ? "habilitada" : "deshabilitada")}: NPSH_req={npshRequired}m, P_vapor={vaporPressure}Pa");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -852,7 +439,7 @@ namespace CtrEditor.HydraulicSimulator
|
|||
Network = null;
|
||||
_stopwatch?.Stop();
|
||||
|
||||
Debug.WriteLine("HydraulicSimulationManager disposed");
|
||||
Trace.WriteLine("HydraulicSimulationManager disposed");
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
@ -860,103 +447,5 @@ namespace CtrEditor.HydraulicSimulator
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hydraulic Object Creation
|
||||
|
||||
/// <summary>
|
||||
/// Lista de objetos hidráulicos específicos del simulador
|
||||
/// </summary>
|
||||
public List<simHydraulicBase> HydraulicSimObjects { get; private set; } = new List<simHydraulicBase>();
|
||||
|
||||
/// <summary>
|
||||
/// Crea una bomba hidráulica en la simulación
|
||||
/// </summary>
|
||||
public simHydraulicPump AddPump(double pumpHead, double maxFlow, double speedRatio, bool isRunning, int direction)
|
||||
{
|
||||
var pump = new simHydraulicPump(this)
|
||||
{
|
||||
PumpHead = pumpHead,
|
||||
MaxFlow = maxFlow,
|
||||
SpeedRatio = speedRatio,
|
||||
IsRunning = isRunning,
|
||||
PumpDirection = direction
|
||||
};
|
||||
|
||||
HydraulicSimObjects.Add(pump);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Trace.WriteLine($"Bomba hidráulica creada: H={pumpHead}m, Q={maxFlow}m³/s");
|
||||
return pump;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea un tanque hidráulico en la simulación
|
||||
/// </summary>
|
||||
public simHydraulicTank AddTank(double pressure, double currentLevel, double maxLevel, double minLevel, double crossSectionalArea, bool isFixedPressure)
|
||||
{
|
||||
var tank = new simHydraulicTank(this)
|
||||
{
|
||||
TankPressure = pressure,
|
||||
CurrentLevel = currentLevel,
|
||||
MaxLevel = maxLevel,
|
||||
MinLevel = minLevel,
|
||||
CrossSectionalArea = crossSectionalArea,
|
||||
IsFixedPressure = isFixedPressure
|
||||
};
|
||||
|
||||
HydraulicSimObjects.Add(tank);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Trace.WriteLine($"Tanque hidráulico creado: P={pressure}Pa, Nivel={currentLevel}m");
|
||||
return tank;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea una tubería hidráulica en la simulación
|
||||
/// </summary>
|
||||
public simHydraulicPipe AddPipe(double length, double diameter, double roughness, string componenteAId, string componenteBId)
|
||||
{
|
||||
var pipe = new simHydraulicPipe(this)
|
||||
{
|
||||
Length = length,
|
||||
Diameter = diameter,
|
||||
Roughness = roughness,
|
||||
ComponenteAId = componenteAId,
|
||||
ComponenteBId = componenteBId
|
||||
};
|
||||
|
||||
HydraulicSimObjects.Add(pipe);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Debug.WriteLine($"Tubería hidráulica creada: L={length}m, D={diameter}m");
|
||||
return pipe;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remueve un objeto hidráulico de la simulación
|
||||
/// </summary>
|
||||
public void Remove(simHydraulicBase hydraulicObject)
|
||||
{
|
||||
if (hydraulicObject != null && HydraulicSimObjects.Contains(hydraulicObject))
|
||||
{
|
||||
HydraulicSimObjects.Remove(hydraulicObject);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Debug.WriteLine($"Objeto hidráulico removido: {hydraulicObject.SimObjectType}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limpia todos los objetos hidráulicos específicos
|
||||
/// </summary>
|
||||
public void ClearHydraulicSimObjects()
|
||||
{
|
||||
HydraulicSimObjects.Clear();
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Debug.WriteLine("Todos los objetos hidráulicos específicos han sido limpiados");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -307,9 +307,9 @@ namespace GTPCorrgir
|
|||
"application/json"
|
||||
);
|
||||
|
||||
using var response = await _httpClient.PostAsync(endpoint, content);
|
||||
using var response = await _httpClient.PostAsync(endpoint, content).ConfigureAwait(false);
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
|
|
|
@ -20,8 +20,6 @@ namespace CtrEditor.ObjetosSim
|
|||
/// </summary>
|
||||
public partial class osHydPipe : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
|
||||
{
|
||||
// Referencia al objeto de simulación hidráulica específico
|
||||
private simHydraulicPipe SimHydraulicPipe;
|
||||
// Properties
|
||||
|
||||
private double _length = 1.0; // metros
|
||||
|
@ -419,6 +417,9 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
||||
{
|
||||
Debug.WriteLine($"[PIPE] ApplyHydraulicResults for {Nombre}");
|
||||
Debug.WriteLine($"[PIPE] Available flow keys: {string.Join(", ", flows.Keys)}");
|
||||
|
||||
// Solo procesar si ambos componentes están conectados
|
||||
if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
|
||||
{
|
||||
|
@ -426,13 +427,67 @@ namespace CtrEditor.ObjetosSim
|
|||
string fromNodeName = GetNodeNameForComponent(Id_ComponenteA, true);
|
||||
string toNodeName = GetNodeNameForComponent(Id_ComponenteB, false);
|
||||
|
||||
Debug.WriteLine($"[PIPE] fromNodeName: '{fromNodeName}', toNodeName: '{toNodeName}'");
|
||||
|
||||
if (!string.IsNullOrEmpty(fromNodeName) && !string.IsNullOrEmpty(toNodeName))
|
||||
{
|
||||
string branchKey = $"{fromNodeName}->{toNodeName}";
|
||||
Debug.WriteLine($"[PIPE] Looking for branchKey: '{branchKey}'");
|
||||
|
||||
if (flows.TryGetValue(branchKey, out double flow))
|
||||
{
|
||||
CurrentFlow = flow;
|
||||
Debug.WriteLine($"[PIPE] Found flow: {flow}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"[PIPE] Flow not found for key '{branchKey}'");
|
||||
|
||||
// Try alternative patterns
|
||||
string[] alternativeKeys = {
|
||||
$"{toNodeName}->{fromNodeName}",
|
||||
$"{Id_ComponenteA}_{Id_ComponenteB}_Pipe",
|
||||
$"{Id_ComponenteB}_{Id_ComponenteA}_Pipe",
|
||||
$"Pipe_{Id_ComponenteA}_{Id_ComponenteB}",
|
||||
$"Pipe_{Id_ComponenteB}_{Id_ComponenteA}",
|
||||
$"{Nombre}_Pipe",
|
||||
$"Branch_{Nombre}"
|
||||
};
|
||||
|
||||
bool foundFlow = false;
|
||||
foreach (string altKey in alternativeKeys)
|
||||
{
|
||||
if (flows.TryGetValue(altKey, out double altFlow))
|
||||
{
|
||||
CurrentFlow = altFlow;
|
||||
Debug.WriteLine($"[PIPE] Found flow with alternative key '{altKey}': {altFlow}");
|
||||
foundFlow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If still no flow found, try to get flow from connected pump
|
||||
if (!foundFlow)
|
||||
{
|
||||
Debug.WriteLine($"[PIPE] No direct flow found, checking connected components");
|
||||
|
||||
// Check if connected to a pump
|
||||
string[] pumpKeys = {
|
||||
$"{Id_ComponenteA}_Pump",
|
||||
$"{Id_ComponenteB}_Pump"
|
||||
};
|
||||
|
||||
foreach (string pumpKey in pumpKeys)
|
||||
{
|
||||
if (flows.TryGetValue(pumpKey, out double pumpFlow))
|
||||
{
|
||||
CurrentFlow = pumpFlow;
|
||||
Debug.WriteLine($"[PIPE] Using pump flow from '{pumpKey}': {pumpFlow}");
|
||||
foundFlow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calcular pérdida de presión entre nodos
|
||||
|
@ -564,47 +619,17 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public override void UpdateGeometryStart()
|
||||
{
|
||||
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
|
||||
// En el nuevo sistema unificado, el HydraulicSimulationManager se encarga
|
||||
// de registrar automáticamente los objetos que implementan IHydraulicComponent
|
||||
// No necesitamos crear objetos simHydraulic* separados
|
||||
Debug.WriteLine($"[DEBUG] {Nombre}: UpdateGeometryStart() - ComponenteA: '{Id_ComponenteA}', ComponenteB: '{Id_ComponenteB}'");
|
||||
|
||||
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);
|
||||
if (SimHydraulicPipe != null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// Actualizar propiedades si el objeto ya existe
|
||||
Debug.WriteLine($"[DEBUG] {Nombre}: Updating existing SimHydraulicPipe");
|
||||
SimHydraulicPipe.Length = Length;
|
||||
SimHydraulicPipe.Diameter = Diameter;
|
||||
SimHydraulicPipe.Roughness = Roughness;
|
||||
SimHydraulicPipe.ComponenteAId = Id_ComponenteA;
|
||||
SimHydraulicPipe.ComponenteBId = Id_ComponenteB;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] {Nombre}: Cannot create SimHydraulicPipe - missing connections or already exists");
|
||||
}
|
||||
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStep()
|
||||
{
|
||||
// Los objetos hidráulicos actualizan sus resultados
|
||||
// a través de ApplySimulationResults() desde HydraulicSimulationManager
|
||||
// a través de ApplyHydraulicResults() desde HydraulicSimulationManager
|
||||
}
|
||||
|
||||
// Método para actualizar fluido desde componente fuente
|
||||
|
@ -644,23 +669,9 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
// Actualizar propiedades desde la simulación hidráulica
|
||||
if (SimHydraulicPipe != null)
|
||||
{
|
||||
double previousFlow = CurrentFlow;
|
||||
CurrentFlow = SimHydraulicPipe.CurrentFlow;
|
||||
PressureDrop = SimHydraulicPipe.PressureDrop;
|
||||
|
||||
// DEBUG: Log flujo para diagnosis
|
||||
if (CurrentFlow != 0.0 || CurrentFlow != previousFlow)
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] {Nombre}: Flow={CurrentFlow:F6} m³/s ({CurrentFlowLMin:F2} L/min) - Changed from {previousFlow:F6}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] {Nombre}: SimHydraulicPipe is NULL! ComponenteA='{Id_ComponenteA}', ComponenteB='{Id_ComponenteB}'");
|
||||
}
|
||||
// En el nuevo sistema unificado, las propiedades se actualizan
|
||||
// directamente a través de ApplyHydraulicResults()
|
||||
// CurrentFlow y PressureDrop ya están actualizados por el solver
|
||||
|
||||
// Actualizar propiedades del fluido cada ciclo
|
||||
UpdateFluidFromSource();
|
||||
|
@ -682,18 +693,15 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public override void ucLoaded()
|
||||
{
|
||||
// Objeto hidráulico se crea en UpdateGeometryStart cuando inicia la simulación
|
||||
// En el nuevo sistema unificado, no necesitamos crear objetos separados
|
||||
base.ucLoaded();
|
||||
}
|
||||
|
||||
public override void ucUnLoaded()
|
||||
{
|
||||
// Remover objeto hidráulico de la simulación
|
||||
if (SimHydraulicPipe != null)
|
||||
{
|
||||
hydraulicSimulationManager.Remove(SimHydraulicPipe);
|
||||
SimHydraulicPipe = null;
|
||||
}
|
||||
// En el nuevo sistema unificado, el HydraulicSimulationManager
|
||||
// maneja automáticamente el registro/desregistro de objetos
|
||||
base.ucUnLoaded();
|
||||
}
|
||||
|
||||
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||
|
|
|
@ -18,9 +18,6 @@ namespace CtrEditor.ObjetosSim
|
|||
/// </summary>
|
||||
public partial class osHydPump : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
|
||||
{
|
||||
// Referencia al objeto de simulación hidráulica específico
|
||||
private simHydraulicPump SimHydraulicPump;
|
||||
|
||||
public static string NombreCategoria() => "Componentes Hidráulicos";
|
||||
|
||||
public static string NombreClase()
|
||||
|
@ -315,29 +312,15 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public override void UpdateGeometryStart()
|
||||
{
|
||||
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
|
||||
if (SimHydraulicPump == null)
|
||||
{
|
||||
SimHydraulicPump = hydraulicSimulationManager.AddPump(PumpHead, MaxFlow, SpeedRatio, IsRunning, PumpDirection);
|
||||
SimHydraulicPump.SimObjectType = "HydraulicPump";
|
||||
SimHydraulicPump.WpfObject = this;
|
||||
SimHydraulicPump.Nombre = Nombre;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Actualizar propiedades si el objeto ya existe
|
||||
SimHydraulicPump.PumpHead = PumpHead;
|
||||
SimHydraulicPump.MaxFlow = MaxFlow;
|
||||
SimHydraulicPump.SpeedRatio = SpeedRatio;
|
||||
SimHydraulicPump.IsRunning = IsRunning;
|
||||
SimHydraulicPump.PumpDirection = PumpDirection;
|
||||
}
|
||||
// En el nuevo sistema unificado, el HydraulicSimulationManager se encarga
|
||||
// de registrar automáticamente los objetos que implementan IHydraulicComponent
|
||||
// No necesitamos crear objetos simHydraulic* separados
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStep()
|
||||
{
|
||||
// Los objetos hidráulicos actualizan sus resultados
|
||||
// a través de ApplySimulationResults() desde HydraulicSimulationManager
|
||||
// a través de ApplyHydraulicResults() desde HydraulicSimulationManager
|
||||
}
|
||||
|
||||
// Método para actualizar fluido desde tanque de succión
|
||||
|
@ -456,12 +439,9 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
// Actualizar propiedades desde la simulación hidráulica
|
||||
if (SimHydraulicPump != null)
|
||||
{
|
||||
CurrentFlow = SimHydraulicPump.CurrentFlow;
|
||||
CurrentPressure = SimHydraulicPump.CurrentPressure;
|
||||
}
|
||||
// En el nuevo sistema unificado, las propiedades se actualizan
|
||||
// directamente a través de ApplyHydraulicResults()
|
||||
// CurrentFlow y CurrentPressure ya están actualizados
|
||||
|
||||
// Actualizar propiedades del fluido basado en si hay flujo
|
||||
UpdateFluidFromSuction();
|
||||
|
@ -482,19 +462,16 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public override void ucLoaded()
|
||||
{
|
||||
// Objeto hidráulico se crea en UpdateGeometryStart cuando inicia la simulación
|
||||
// En el nuevo sistema unificado, no necesitamos crear objetos separados
|
||||
base.ucLoaded();
|
||||
UpdatePumpImage();
|
||||
}
|
||||
|
||||
public override void ucUnLoaded()
|
||||
{
|
||||
// Remover objeto hidráulico de la simulación
|
||||
if (SimHydraulicPump != null)
|
||||
{
|
||||
hydraulicSimulationManager.Remove(SimHydraulicPump);
|
||||
SimHydraulicPump = null;
|
||||
}
|
||||
// En el nuevo sistema unificado, el HydraulicSimulationManager
|
||||
// maneja automáticamente el registro/desregistro de objetos
|
||||
base.ucUnLoaded();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ namespace CtrEditor.ObjetosSim
|
|||
/// </summary>
|
||||
public partial class osHydTank : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
|
||||
{
|
||||
// Referencia al objeto de simulación hidráulica específico
|
||||
private simHydraulicTank SimHydraulicTank;
|
||||
public static string NombreCategoria() => "Componentes Hidráulicos";
|
||||
|
||||
public static string NombreClase() => "Tanque Hidráulico";
|
||||
|
@ -146,49 +144,21 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public override void UpdateGeometryStart()
|
||||
{
|
||||
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
|
||||
if (SimHydraulicTank == null)
|
||||
{
|
||||
// Convertir bar a Pa para el sistema hidráulico interno
|
||||
var pressurePa = TankPressure * 100000.0;
|
||||
// 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.WpfObject = this;
|
||||
SimHydraulicTank.Nombre = Nombre;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Actualizar propiedades si el objeto ya existe (convertir bar a Pa)
|
||||
SimHydraulicTank.TankPressure = TankPressure * 100000.0;
|
||||
// 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.MinLevel = MinLevelM;
|
||||
SimHydraulicTank.CrossSectionalArea = CrossSectionalArea;
|
||||
SimHydraulicTank.IsFixedPressure = IsFixedPressure;
|
||||
}
|
||||
// En el nuevo sistema unificado, no necesitamos crear objetos hidráulicos
|
||||
// El componente se registra automáticamente como IHydraulicComponent
|
||||
}
|
||||
|
||||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
// Actualizar propiedades desde la simulación hidráulica
|
||||
if (SimHydraulicTank != null)
|
||||
// En el nuevo sistema unificado, las propiedades se actualizan
|
||||
// directamente a través de ApplyHydraulicResults()
|
||||
// _netFlow y CurrentPressure ya están actualizados por el solver
|
||||
|
||||
// Actualizar nivel del tanque basado en el balance de flujo
|
||||
double deltaTime = elapsedMilliseconds / 60000.0; // Convertir a minutos
|
||||
if (deltaTime > 0)
|
||||
{
|
||||
// Convertir de m³/s a L/min
|
||||
_netFlow = (SimHydraulicTank.InletFlow - SimHydraulicTank.OutletFlow) * 60000.0;
|
||||
|
||||
// Convertir de Pa a bar
|
||||
CurrentPressure = SimHydraulicTank.CurrentPressure / 100000.0;
|
||||
|
||||
// Actualizar nivel del tanque basado en el balance de flujo
|
||||
double deltaTime = elapsedMilliseconds / 60000.0; // Convertir a minutos
|
||||
if (deltaTime > 0)
|
||||
{
|
||||
UpdateLevelFromFlowBalance(deltaTime);
|
||||
}
|
||||
UpdateLevelFromFlowBalance(deltaTime);
|
||||
}
|
||||
|
||||
// Actualizar el color según el fluido actual
|
||||
|
@ -1257,12 +1227,9 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public override void ucUnLoaded()
|
||||
{
|
||||
// Remover objeto hidráulico de la simulación
|
||||
if (SimHydraulicTank != null)
|
||||
{
|
||||
hydraulicSimulationManager.Remove(SimHydraulicTank);
|
||||
SimHydraulicTank = null;
|
||||
}
|
||||
// En el nuevo sistema unificado, el HydraulicSimulationManager
|
||||
// maneja automáticamente el registro/desregistro de objetos
|
||||
base.ucUnLoaded();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
# Python Execution in CtrEditor via MCP
|
||||
|
||||
Esta funcionalidad permite ejecutar scripts Python directamente dentro de CtrEditor para debug y testing, con acceso completo a los objetos de la aplicación.
|
||||
|
||||
## Características
|
||||
|
||||
- **Ejecución de Python sin instalaciones externas**: Usa IronPython 3.4.2 integrado
|
||||
- **Acceso completo a objetos**: MainViewModel, Canvas, ObjetosSimulables
|
||||
- **Timeout configurable**: Previene scripts que se cuelguen
|
||||
- **Variables de retorno**: Obtén valores específicos del script
|
||||
- **Help integrado**: Documentación automática de objetos disponibles
|
||||
|
||||
## Objetos Disponibles en Python
|
||||
|
||||
### Variables Globales
|
||||
- `app`: MainViewModel - El modelo de vista principal de la aplicación
|
||||
- `canvas`: Canvas - El canvas principal donde se muestran los objetos
|
||||
- `objects`: ObservableCollection - Colección de todos los objetos simulables
|
||||
- `get_objects()`: Función que retorna una lista de todos los objetos simulables
|
||||
|
||||
### Librerías Disponibles
|
||||
- `sys`, `math`, `time`, `json`, `random`
|
||||
|
||||
## Herramientas MCP Disponibles
|
||||
|
||||
### execute_python
|
||||
Ejecuta código Python con acceso a objetos de CtrEditor.
|
||||
|
||||
**Parámetros:**
|
||||
- `code` (requerido): El código Python a ejecutar
|
||||
- `return_variables` (opcional): Lista de nombres de variables a retornar
|
||||
- `timeout_seconds` (opcional): Timeout en segundos (default: 30)
|
||||
|
||||
**Ejemplo:**
|
||||
```json
|
||||
{
|
||||
"name": "execute_python",
|
||||
"arguments": {
|
||||
"code": "count = len(objects)\nprint(f'Total objects: {count}')",
|
||||
"return_variables": ["count"],
|
||||
"timeout_seconds": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### python_help
|
||||
Obtiene ayuda sobre objetos y métodos disponibles.
|
||||
|
||||
**Parámetros:**
|
||||
- `object_name` (opcional): Nombre específico del objeto para obtener ayuda
|
||||
|
||||
**Ejemplos:**
|
||||
```json
|
||||
{
|
||||
"name": "python_help",
|
||||
"arguments": {}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "python_help",
|
||||
"arguments": {
|
||||
"object_name": "app"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ejemplos de Uso
|
||||
|
||||
### 1. Contar objetos
|
||||
```python
|
||||
# Contar todos los objetos
|
||||
total = len(objects)
|
||||
print(f"Total objects: {total}")
|
||||
|
||||
# Filtrar objetos por tipo
|
||||
conveyors = [obj for obj in objects if 'Conveyor' in str(type(obj))]
|
||||
print(f"Conveyors: {len(conveyors)}")
|
||||
```
|
||||
|
||||
### 2. Inspeccionar canvas
|
||||
```python
|
||||
# Obtener información del canvas
|
||||
width = canvas.Width
|
||||
height = canvas.Height
|
||||
print(f"Canvas size: {width}x{height}")
|
||||
|
||||
# Verificar estado de simulación
|
||||
is_running = app.IsSimulationRunning
|
||||
print(f"Simulation running: {is_running}")
|
||||
```
|
||||
|
||||
### 3. Modificar objetos (con precaución)
|
||||
```python
|
||||
# Buscar un objeto específico
|
||||
for obj in objects:
|
||||
if hasattr(obj, 'Nombre') and obj.Nombre == "MiObjeto":
|
||||
print(f"Found object: {obj.Nombre}")
|
||||
# Modificar propiedades si es necesario
|
||||
if hasattr(obj, 'Visible'):
|
||||
obj.Visible = not obj.Visible
|
||||
break
|
||||
```
|
||||
|
||||
### 4. Debugging avanzado
|
||||
```python
|
||||
# Obtener propiedades de todos los objetos
|
||||
for i, obj in enumerate(objects):
|
||||
obj_type = type(obj).__name__
|
||||
properties = [prop for prop in dir(obj) if not prop.startswith('_')]
|
||||
print(f"Object {i}: {obj_type} - {len(properties)} properties")
|
||||
```
|
||||
|
||||
## Seguridad y Precauciones
|
||||
|
||||
⚠️ **IMPORTANTE**: Esta funcionalidad está diseñada solo para debug y testing. NO está pensada para uso en producción.
|
||||
|
||||
- El código Python se ejecuta en el mismo proceso que CtrEditor
|
||||
- Tiene acceso completo a todos los objetos de la aplicación
|
||||
- No hay restricciones de seguridad implementadas
|
||||
- Use solo en entornos de desarrollo controlados
|
||||
|
||||
## Configuración
|
||||
|
||||
Las herramientas se configuran automáticamente cuando se inicia el MCPServer. No se requiere configuración adicional.
|
||||
|
||||
## Solución de Problemas
|
||||
|
||||
### Error: "Python environment initialization failed"
|
||||
- Verifica que IronPython esté correctamente instalado via NuGet
|
||||
- Revisa los logs de debug para más detalles
|
||||
|
||||
### Error: "Python execution timed out"
|
||||
- Reduce la complejidad del script
|
||||
- Aumenta el timeout_seconds si es necesario
|
||||
- Verifica que no haya bucles infinitos
|
||||
|
||||
### Variables no encontradas
|
||||
- Usa `python_help` para ver qué objetos están disponibles
|
||||
- Verifica que los objetos existan antes de usarlos
|
||||
- Usa `hasattr()` para verificar propiedades antes de accederlas
|
||||
|
||||
## Logging
|
||||
|
||||
Todos los eventos de ejecución Python se registran en el log de debug de CtrEditor con el prefijo `[MCP Server]`.
|
|
@ -19,6 +19,8 @@ using System.Windows.Media;
|
|||
using System.Windows.Shapes;
|
||||
using System.Windows.Controls;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using IronPython.Hosting;
|
||||
using Microsoft.Scripting.Hosting;
|
||||
|
||||
namespace CtrEditor.Services
|
||||
{
|
||||
|
@ -40,6 +42,11 @@ namespace CtrEditor.Services
|
|||
private long _totalSimulationMilliseconds;
|
||||
private bool _lastSimulationStatus;
|
||||
|
||||
// Python execution support
|
||||
private ScriptEngine _pythonEngine;
|
||||
private ScriptScope _pythonScope;
|
||||
private readonly object _pythonLock = new object();
|
||||
|
||||
public MCPServer(MainViewModel mainViewModel, int port = 5006)
|
||||
{
|
||||
_mainViewModel = mainViewModel ?? throw new ArgumentNullException(nameof(mainViewModel));
|
||||
|
@ -50,6 +57,9 @@ namespace CtrEditor.Services
|
|||
_simulationStopwatch = new Stopwatch();
|
||||
_totalSimulationMilliseconds = 0;
|
||||
_lastSimulationStatus = false;
|
||||
|
||||
// Initialize Python environment
|
||||
InitializePythonEnvironment();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -357,6 +367,42 @@ namespace CtrEditor.Services
|
|||
type = "object",
|
||||
properties = new { }
|
||||
}
|
||||
},
|
||||
new {
|
||||
name = "execute_python",
|
||||
description = "Execute Python code with access to CtrEditor objects and canvas for debugging",
|
||||
inputSchema = new {
|
||||
type = "object",
|
||||
properties = new {
|
||||
code = new {
|
||||
type = "string",
|
||||
description = "Python code to execute. Has access to 'app' (MainViewModel), 'canvas', 'objects' (simulable objects), and common libraries."
|
||||
},
|
||||
return_variables = new {
|
||||
type = "array",
|
||||
items = new { type = "string" },
|
||||
description = "Optional list of variable names to return from Python execution"
|
||||
},
|
||||
timeout_seconds = new {
|
||||
type = "number",
|
||||
description = "Execution timeout in seconds. Defaults to 30."
|
||||
}
|
||||
},
|
||||
required = new[] { "code" }
|
||||
}
|
||||
},
|
||||
new {
|
||||
name = "python_help",
|
||||
description = "Get help about available Python objects and methods in CtrEditor context",
|
||||
inputSchema = new {
|
||||
type = "object",
|
||||
properties = new {
|
||||
object_name = new {
|
||||
type = "string",
|
||||
description = "Optional specific object to get help for (e.g., 'app', 'canvas', 'objects')"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -432,6 +478,8 @@ namespace CtrEditor.Services
|
|||
"take_screenshot" => TakeScreenshot(arguments),
|
||||
"save_project" => SaveProject(),
|
||||
"reset_simulation_timing" => ResetSimulationTiming(),
|
||||
"execute_python" => ExecutePython(arguments),
|
||||
"python_help" => GetPythonHelp(arguments),
|
||||
_ => throw new ArgumentException($"Unknown tool: {toolName}")
|
||||
};
|
||||
}
|
||||
|
@ -1183,6 +1231,415 @@ namespace CtrEditor.Services
|
|||
|
||||
#endregion
|
||||
|
||||
#region Python Execution Support
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Python environment with enhanced libraries and thread-safe print function
|
||||
/// </summary>
|
||||
private void InitializePythonEnvironment()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set console output encoding to avoid codepage issues
|
||||
try
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore encoding setup errors
|
||||
}
|
||||
|
||||
_pythonEngine = Python.CreateEngine();
|
||||
_pythonScope = _pythonEngine.CreateScope();
|
||||
|
||||
// Set up enhanced search paths for IronPython.StdLib
|
||||
var searchPaths = _pythonEngine.GetSearchPaths();
|
||||
|
||||
// Add current directory and common library paths
|
||||
var currentDir = Directory.GetCurrentDirectory();
|
||||
searchPaths.Add(currentDir);
|
||||
searchPaths.Add(System.IO.Path.Combine(currentDir, "lib"));
|
||||
searchPaths.Add(System.IO.Path.Combine(currentDir, "Lib"));
|
||||
|
||||
_pythonEngine.SetSearchPaths(searchPaths);
|
||||
|
||||
// Import basic libraries and set up global variables
|
||||
var setupScript = @"
|
||||
import sys
|
||||
|
||||
# Fix encoding issues before importing anything else
|
||||
try:
|
||||
import codecs
|
||||
# Override the problematic codepage lookup
|
||||
def search_function(encoding):
|
||||
if 'codepage' in encoding.lower():
|
||||
return codecs.lookup('utf-8')
|
||||
return None
|
||||
codecs.register(search_function)
|
||||
except:
|
||||
pass
|
||||
|
||||
import clr
|
||||
import math
|
||||
import time
|
||||
import json
|
||||
import random
|
||||
|
||||
# Add .NET types
|
||||
clr.AddReference('System')
|
||||
clr.AddReference('System.Core')
|
||||
clr.AddReference('PresentationCore')
|
||||
clr.AddReference('PresentationFramework')
|
||||
|
||||
from System import Console, Text
|
||||
from System.Text import StringBuilder
|
||||
|
||||
# Create completely isolated print system to avoid encoding issues
|
||||
_print_buffer = StringBuilder()
|
||||
|
||||
def safe_print(*args, **kwargs):
|
||||
'''Completely isolated print function that avoids all encoding issues'''
|
||||
try:
|
||||
separator = kwargs.get('sep', ' ')
|
||||
end_char = kwargs.get('end', '\n')
|
||||
|
||||
# Convert all arguments to strings safely
|
||||
text_parts = []
|
||||
for arg in args:
|
||||
try:
|
||||
text_parts.append(str(arg))
|
||||
except:
|
||||
text_parts.append('<unprintable>')
|
||||
|
||||
text = separator.join(text_parts) + end_char
|
||||
|
||||
# Store in our isolated buffer
|
||||
_print_buffer.Append(text)
|
||||
|
||||
# Also write to debug output for monitoring
|
||||
try:
|
||||
import System.Diagnostics
|
||||
System.Diagnostics.Debug.WriteLine('[Python] ' + text.rstrip())
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
try:
|
||||
_print_buffer.Append(f'Print error: {e}\n')
|
||||
except:
|
||||
pass
|
||||
|
||||
# Completely replace print function - no fallback to original
|
||||
print = safe_print
|
||||
|
||||
# Helper function to get print output
|
||||
def get_print_output():
|
||||
'''Get accumulated print output and clear buffer'''
|
||||
try:
|
||||
output = _print_buffer.ToString()
|
||||
_print_buffer.Clear()
|
||||
return output
|
||||
except:
|
||||
return ''
|
||||
|
||||
def get_objects():
|
||||
'''Helper function to get all simulable objects as a list'''
|
||||
try:
|
||||
return list(objects) if objects else []
|
||||
except:
|
||||
return []
|
||||
";
|
||||
|
||||
_pythonEngine.Execute(setupScript, _pythonScope);
|
||||
|
||||
Debug.WriteLine("[MCP Server] Python environment initialized successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[MCP Server] Error initializing Python environment: {ex.Message}");
|
||||
Debug.WriteLine($"[MCP Server] Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Python code with access to CtrEditor objects
|
||||
/// </summary>
|
||||
private object ExecutePython(JObject arguments)
|
||||
{
|
||||
lock (_pythonLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
var code = arguments["code"]?.ToString();
|
||||
if (string.IsNullOrEmpty(code))
|
||||
throw new ArgumentException("Code is required");
|
||||
|
||||
var returnVariables = arguments["return_variables"]?.ToObject<string[]>() ?? new string[0];
|
||||
var timeoutSeconds = arguments["timeout_seconds"]?.ToObject<int>() ?? 30;
|
||||
|
||||
// Set up context variables with thread-safe access
|
||||
return Application.Current.Dispatcher.Invoke<object>(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set global variables for Python script
|
||||
_pythonScope.SetVariable("app", _mainViewModel);
|
||||
_pythonScope.SetVariable("canvas", _mainViewModel.MainCanvas);
|
||||
_pythonScope.SetVariable("objects", _mainViewModel.ObjetosSimulables);
|
||||
|
||||
// Execute the Python code directly on UI thread to avoid cross-thread issues
|
||||
// Note: This runs synchronously on UI thread but IronPython is generally fast
|
||||
_pythonEngine.Execute(code, _pythonScope);
|
||||
|
||||
// Get print output
|
||||
var printOutput = "";
|
||||
try
|
||||
{
|
||||
var getPrintOutput = _pythonScope.GetVariable("get_print_output");
|
||||
if (getPrintOutput != null)
|
||||
{
|
||||
var result = _pythonEngine.Operations.Invoke(getPrintOutput);
|
||||
printOutput = result?.ToString() ?? "";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[MCP Server] Error getting print output: {ex.Message}");
|
||||
}
|
||||
|
||||
// Collect return variables
|
||||
var returnValues = new Dictionary<string, object>();
|
||||
foreach (var varName in returnVariables)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pythonScope.ContainsVariable(varName))
|
||||
{
|
||||
var value = _pythonScope.GetVariable(varName);
|
||||
returnValues[varName] = ConvertPythonObject(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
returnValues[varName] = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
returnValues[varName] = $"Error getting variable: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
output = printOutput,
|
||||
variables = returnValues,
|
||||
execution_time_ms = "< 1000"
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[MCP Server] Python execution error: {ex.Message}");
|
||||
return new
|
||||
{
|
||||
success = false,
|
||||
error = ex.Message,
|
||||
error_type = ex.GetType().Name
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new
|
||||
{
|
||||
success = false,
|
||||
error = ex.Message,
|
||||
error_type = ex.GetType().Name
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Python objects to JSON-serializable .NET objects with better type handling
|
||||
/// </summary>
|
||||
private object ConvertPythonObject(dynamic pythonObj)
|
||||
{
|
||||
if (pythonObj == null) return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Handle basic .NET types
|
||||
if (pythonObj is string || pythonObj is int || pythonObj is double ||
|
||||
pythonObj is bool || pythonObj is decimal)
|
||||
{
|
||||
return pythonObj;
|
||||
}
|
||||
|
||||
// Handle System.Single (float) conversion
|
||||
if (pythonObj is float || pythonObj.GetType() == typeof(System.Single))
|
||||
{
|
||||
return Convert.ToDouble(pythonObj);
|
||||
}
|
||||
|
||||
// Handle nullable types
|
||||
if (pythonObj.GetType().IsGenericType &&
|
||||
pythonObj.GetType().GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
var underlyingValue = pythonObj.HasValue ? pythonObj.Value : null;
|
||||
return underlyingValue != null ? ConvertPythonObject(underlyingValue) : null;
|
||||
}
|
||||
|
||||
// Handle collections
|
||||
if (pythonObj is System.Collections.IEnumerable enumerable && !(pythonObj is string))
|
||||
{
|
||||
var list = new List<object>();
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
list.Add(ConvertPythonObject(item));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// Handle objects with simple properties
|
||||
var type = pythonObj.GetType();
|
||||
if (type.IsClass && !type.FullName.StartsWith("System."))
|
||||
{
|
||||
var properties = new Dictionary<string, object>();
|
||||
var allProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
var validProps = new List<PropertyInfo>();
|
||||
|
||||
// Filter properties manually to avoid lambda issues
|
||||
foreach (var prop in allProps)
|
||||
{
|
||||
if (prop.CanRead && prop.GetIndexParameters().Length == 0)
|
||||
{
|
||||
validProps.Add(prop);
|
||||
if (validProps.Count >= 20) // Limit to prevent infinite recursion
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var prop in validProps)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = prop.GetValue(pythonObj);
|
||||
properties[prop.Name] = ConvertPythonObject(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
properties[prop.Name] = $"<Error reading {prop.Name}>";
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
// Fallback: convert to string
|
||||
return pythonObj.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"<Conversion error: {ex.Message}>";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets help information about available Python objects and methods
|
||||
/// </summary>
|
||||
private object GetPythonHelp(JObject arguments)
|
||||
{
|
||||
try
|
||||
{
|
||||
var objectName = arguments["object_name"]?.ToString();
|
||||
|
||||
var helpInfo = new Dictionary<string, object>
|
||||
{
|
||||
["available_objects"] = new
|
||||
{
|
||||
app = "MainViewModel - Main application view model with all CtrEditor functionality",
|
||||
canvas = "Canvas - Main canvas where objects are displayed",
|
||||
objects = "ObservableCollection<osBase> - Collection of all simulable objects",
|
||||
get_objects = "Function() - Helper function that returns objects as a Python list"
|
||||
},
|
||||
["available_libraries"] = new[]
|
||||
{
|
||||
"sys - System-specific parameters and functions",
|
||||
"math - Mathematical functions",
|
||||
"time - Time-related functions",
|
||||
"json - JSON encoder and decoder",
|
||||
"random - Random number generation",
|
||||
"clr - .NET CLR integration"
|
||||
},
|
||||
["common_usage_patterns"] = new[]
|
||||
{
|
||||
"len(objects) - Get number of objects",
|
||||
"objects[0].Id.Value - Get ID of first object",
|
||||
"app.IsSimulationRunning - Check if simulation is running",
|
||||
"canvas.Width, canvas.Height - Get canvas dimensions",
|
||||
"print('Hello') - Print to output (thread-safe)"
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(objectName))
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
object targetObject = objectName.ToLower() switch
|
||||
{
|
||||
"app" => _mainViewModel,
|
||||
"canvas" => _mainViewModel.MainCanvas,
|
||||
"objects" => _mainViewModel.ObjetosSimulables,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (targetObject != null)
|
||||
{
|
||||
var type = targetObject.GetType();
|
||||
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m => !m.IsSpecialName && m.DeclaringType != typeof(object))
|
||||
.Take(20)
|
||||
.Select(m => $"{m.Name}({string.Join(", ", m.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"))})")
|
||||
.ToArray();
|
||||
|
||||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.CanRead)
|
||||
.Take(20)
|
||||
.Select(p => $"{p.Name} : {p.PropertyType.Name}")
|
||||
.ToArray();
|
||||
|
||||
helpInfo[$"{objectName}_methods"] = methods;
|
||||
helpInfo[$"{objectName}_properties"] = properties;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
helpInfo["error"] = $"Error getting help for {objectName}: {ex.Message}";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
help = helpInfo
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new
|
||||
{
|
||||
success = false,
|
||||
error = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
|
@ -1215,6 +1672,20 @@ namespace CtrEditor.Services
|
|||
{
|
||||
Stop();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
|
||||
// Clean up Python resources
|
||||
try
|
||||
{
|
||||
// ScriptScope doesn't have Dispose, just clear variables
|
||||
_pythonScope?.RemoveVariable("app");
|
||||
_pythonScope?.RemoveVariable("canvas");
|
||||
_pythonScope?.RemoveVariable("objects");
|
||||
_pythonEngine?.Runtime?.Shutdown();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[MCP Server] Error disposing Python resources: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -0,0 +1,296 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script para analizar screenshots de CtrEditor y detectar la posición de objetos circulares.
|
||||
Esto nos permite verificar objetivamente si los objetos están centrados en las imágenes.
|
||||
"""
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import glob
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def detect_circles_in_image(image_path):
|
||||
"""
|
||||
Detecta círculos en una imagen y devuelve sus posiciones y el análisis de centrado.
|
||||
"""
|
||||
# Leer la imagen
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
return None, f"No se pudo cargar la imagen: {image_path}"
|
||||
|
||||
# Convertir a escala de grises
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Aplicar desenfoque para reducir ruido
|
||||
gray_blurred = cv2.medianBlur(gray, 3)
|
||||
|
||||
# Detectar círculos usando HoughCircles con parámetros más sensibles
|
||||
circles = cv2.HoughCircles(
|
||||
gray_blurred,
|
||||
cv2.HOUGH_GRADIENT,
|
||||
dp=1,
|
||||
minDist=20, # Reducido para detectar círculos más cercanos
|
||||
param1=30, # Reducido para ser más sensible
|
||||
param2=15, # Reducido para detectar círculos menos definidos
|
||||
minRadius=3, # Reducido para círculos muy pequeños
|
||||
maxRadius=30, # Reducido para círculos pequeños como botellas
|
||||
)
|
||||
|
||||
img_height, img_width = img.shape[:2]
|
||||
img_center_x = img_width / 2
|
||||
img_center_y = img_height / 2
|
||||
|
||||
result = {
|
||||
"image_path": image_path,
|
||||
"image_size": (img_width, img_height),
|
||||
"image_center": (img_center_x, img_center_y),
|
||||
"circles_found": 0,
|
||||
"circles": [],
|
||||
"analysis": {},
|
||||
}
|
||||
|
||||
if circles is not None:
|
||||
circles = np.round(circles[0, :]).astype("int")
|
||||
result["circles_found"] = len(circles)
|
||||
|
||||
for i, (x, y, r) in enumerate(circles):
|
||||
# Calcular distancia del centro de la imagen
|
||||
distance_from_center = np.sqrt(
|
||||
(x - img_center_x) ** 2 + (y - img_center_y) ** 2
|
||||
)
|
||||
|
||||
# Calcular desplazamiento en X e Y
|
||||
offset_x = x - img_center_x
|
||||
offset_y = y - img_center_y
|
||||
|
||||
circle_info = {
|
||||
"id": i + 1,
|
||||
"center": (x, y),
|
||||
"radius": r,
|
||||
"distance_from_image_center": distance_from_center,
|
||||
"offset_x": offset_x,
|
||||
"offset_y": offset_y,
|
||||
"is_centered": distance_from_center < 10, # Tolerancia de 10 píxeles
|
||||
}
|
||||
|
||||
result["circles"].append(circle_info)
|
||||
|
||||
# Análisis general
|
||||
if len(circles) == 1:
|
||||
circle = result["circles"][0]
|
||||
result["analysis"] = {
|
||||
"single_object": True,
|
||||
"is_properly_centered": circle["is_centered"],
|
||||
"centering_error_pixels": circle["distance_from_image_center"],
|
||||
"horizontal_offset": circle["offset_x"],
|
||||
"vertical_offset": circle["offset_y"],
|
||||
}
|
||||
elif len(circles) > 1:
|
||||
# Para múltiples objetos, calcular el centroide
|
||||
centroid_x = np.mean([c["center"][0] for c in result["circles"]])
|
||||
centroid_y = np.mean([c["center"][1] for c in result["circles"]])
|
||||
centroid_distance = np.sqrt(
|
||||
(centroid_x - img_center_x) ** 2 + (centroid_y - img_center_y) ** 2
|
||||
)
|
||||
|
||||
result["analysis"] = {
|
||||
"single_object": False,
|
||||
"objects_count": len(circles),
|
||||
"group_centroid": (centroid_x, centroid_y),
|
||||
"group_is_centered": centroid_distance < 15,
|
||||
"group_centering_error": centroid_distance,
|
||||
}
|
||||
|
||||
return result, None
|
||||
|
||||
|
||||
def create_visual_analysis(image_path, analysis_result):
|
||||
"""
|
||||
Crea una imagen con anotaciones visuales mostrando los círculos detectados y el análisis.
|
||||
"""
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
return None
|
||||
|
||||
img_height, img_width = img.shape[:2]
|
||||
img_center_x = int(img_width / 2)
|
||||
img_center_y = int(img_height / 2)
|
||||
|
||||
# Dibujar centro de la imagen
|
||||
cv2.circle(img, (img_center_x, img_center_y), 3, (0, 255, 0), -1) # Verde
|
||||
cv2.circle(img, (img_center_x, img_center_y), 10, (0, 255, 0), 1) # Verde
|
||||
|
||||
# Dibujar círculos detectados
|
||||
for circle in analysis_result["circles"]:
|
||||
x, y, r = circle["center"][0], circle["center"][1], circle["radius"]
|
||||
|
||||
# Círculo detectado en azul
|
||||
cv2.circle(img, (x, y), r, (255, 0, 0), 2)
|
||||
cv2.circle(img, (x, y), 2, (255, 0, 0), -1)
|
||||
|
||||
# Línea desde el centro de la imagen al círculo
|
||||
cv2.line(img, (img_center_x, img_center_y), (x, y), (0, 0, 255), 1)
|
||||
|
||||
# Texto con información
|
||||
text = f"ID:{circle['id']} Dist:{circle['distance_from_image_center']:.1f}px"
|
||||
cv2.putText(
|
||||
img, text, (x + r + 5, y), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1
|
||||
)
|
||||
|
||||
# Texto con análisis general
|
||||
if analysis_result["analysis"].get("single_object"):
|
||||
status = (
|
||||
"CENTRADO"
|
||||
if analysis_result["analysis"]["is_properly_centered"]
|
||||
else "DESCENTRADO"
|
||||
)
|
||||
error = analysis_result["analysis"]["centering_error_pixels"]
|
||||
text = f"Status: {status} (Error: {error:.1f}px)"
|
||||
cv2.putText(
|
||||
img, text, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1
|
||||
)
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def analyze_all_screenshots(screenshots_dir):
|
||||
"""
|
||||
Analiza todas las imágenes de screenshot en el directorio especificado.
|
||||
Filtra las imágenes que ya fueron analizadas y limpia archivos antiguos.
|
||||
"""
|
||||
# Buscar archivos de imagen
|
||||
image_extensions = ["*.png", "*.jpg", "*.jpeg", "*.bmp"]
|
||||
all_image_files = []
|
||||
|
||||
for ext in image_extensions:
|
||||
all_image_files.extend(glob.glob(os.path.join(screenshots_dir, ext)))
|
||||
|
||||
# FILTRAR: Excluir imágenes que comienzan con "analyzed_"
|
||||
image_files = []
|
||||
analyzed_files = []
|
||||
|
||||
for image_path in all_image_files:
|
||||
filename = os.path.basename(image_path)
|
||||
if filename.startswith("analyzed_"):
|
||||
analyzed_files.append(image_path)
|
||||
else:
|
||||
image_files.append(image_path)
|
||||
|
||||
# LIMPIAR: Borrar imágenes analizadas anteriores para evitar confusión
|
||||
for analyzed_file in analyzed_files:
|
||||
try:
|
||||
os.remove(analyzed_file)
|
||||
print(f"Eliminado archivo anterior: {os.path.basename(analyzed_file)}")
|
||||
except Exception as e:
|
||||
print(f"No se pudo eliminar {analyzed_file}: {e}")
|
||||
|
||||
print(f"Encontradas {len(image_files)} imágenes nuevas en {screenshots_dir}")
|
||||
print("=" * 80)
|
||||
|
||||
results = []
|
||||
|
||||
for image_path in sorted(image_files):
|
||||
filename = os.path.basename(image_path)
|
||||
print(f"\nAnalizando: {filename}")
|
||||
|
||||
result, error = detect_circles_in_image(image_path)
|
||||
|
||||
if error:
|
||||
print(f" ERROR: {error}")
|
||||
continue
|
||||
|
||||
results.append(result)
|
||||
|
||||
# Mostrar resultado del análisis
|
||||
print(f" Tamaño imagen: {result['image_size'][0]}x{result['image_size'][1]}")
|
||||
print(
|
||||
f" Centro imagen: ({result['image_center'][0]:.1f}, {result['image_center'][1]:.1f})"
|
||||
)
|
||||
print(f" Círculos detectados: {result['circles_found']}")
|
||||
|
||||
if result["circles_found"] > 0:
|
||||
for circle in result["circles"]:
|
||||
print(
|
||||
f" Círculo {circle['id']}: centro=({circle['center'][0]}, {circle['center'][1]}), "
|
||||
f"radio={circle['radius']}, distancia_centro={circle['distance_from_image_center']:.1f}px"
|
||||
)
|
||||
print(
|
||||
f" Desplazamiento: X={circle['offset_x']:+.1f}px, Y={circle['offset_y']:+.1f}px"
|
||||
)
|
||||
print(f" ¿Centrado?: {'SÍ' if circle['is_centered'] else 'NO'}")
|
||||
|
||||
if "single_object" in result["analysis"]:
|
||||
if result["analysis"]["single_object"]:
|
||||
status = (
|
||||
"CORRECTO"
|
||||
if result["analysis"]["is_properly_centered"]
|
||||
else "INCORRECTO"
|
||||
)
|
||||
print(
|
||||
f" RESULTADO: {status} - Error de centrado: {result['analysis']['centering_error_pixels']:.1f}px"
|
||||
)
|
||||
else:
|
||||
status = (
|
||||
"CORRECTO"
|
||||
if result["analysis"]["group_is_centered"]
|
||||
else "INCORRECTO"
|
||||
)
|
||||
print(
|
||||
f" RESULTADO GRUPO: {status} - Error: {result['analysis']['group_centering_error']:.1f}px"
|
||||
)
|
||||
|
||||
# Crear imagen con anotaciones
|
||||
annotated_img = create_visual_analysis(image_path, result)
|
||||
if annotated_img is not None:
|
||||
output_path = os.path.join(screenshots_dir, f"analyzed_{filename}")
|
||||
cv2.imwrite(output_path, annotated_img)
|
||||
print(f" Imagen anotada guardada: analyzed_{filename}")
|
||||
|
||||
print("=" * 80)
|
||||
print(f"\nRESUMEN GENERAL:")
|
||||
|
||||
centered_count = 0
|
||||
total_single_objects = 0
|
||||
|
||||
for result in results:
|
||||
if result["analysis"].get("single_object"):
|
||||
total_single_objects += 1
|
||||
if result["analysis"]["is_properly_centered"]:
|
||||
centered_count += 1
|
||||
|
||||
if total_single_objects > 0:
|
||||
success_rate = (centered_count / total_single_objects) * 100
|
||||
print(
|
||||
f"Objetos individuales correctamente centrados: {centered_count}/{total_single_objects} ({success_rate:.1f}%)"
|
||||
)
|
||||
|
||||
if success_rate < 100:
|
||||
print("⚠️ PROBLEMA DETECTADO: Los objetos NO están correctamente centrados")
|
||||
else:
|
||||
print("✅ ÉXITO: Todos los objetos están correctamente centrados")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Directorio donde están los screenshots
|
||||
screenshots_dir = r"C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giorgio in Bosco\SimCtrEditor\screenshots"
|
||||
|
||||
if not os.path.exists(screenshots_dir):
|
||||
print(f"ERROR: El directorio {screenshots_dir} no existe")
|
||||
print("Por favor, ajusta la ruta en el script")
|
||||
exit(1)
|
||||
|
||||
print("🔍 ANALIZADOR DE CENTRADO DE OBJETOS EN SCREENSHOTS")
|
||||
print("Este script detecta círculos en imágenes y verifica si están centrados")
|
||||
print("=" * 80)
|
||||
|
||||
try:
|
||||
results = analyze_all_screenshots(screenshots_dir)
|
||||
except Exception as e:
|
||||
print(f"ERROR durante el análisis: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify Python execution capability in CtrEditor via MCP
|
||||
"""
|
||||
|
||||
import json
|
||||
import socket
|
||||
import time
|
||||
|
||||
|
||||
def send_mcp_request(host="localhost", port=5006, method="tools/call", params=None):
|
||||
"""Send MCP request to CtrEditor"""
|
||||
try:
|
||||
# Create socket connection
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(10)
|
||||
sock.connect((host, port))
|
||||
|
||||
# Prepare request
|
||||
request = {"jsonrpc": "2.0", "id": 1, "method": method, "params": params or {}}
|
||||
|
||||
# Send request
|
||||
message = json.dumps(request) + "\n"
|
||||
sock.send(message.encode("utf-8"))
|
||||
|
||||
# Receive response
|
||||
response_data = ""
|
||||
while True:
|
||||
chunk = sock.recv(4096).decode("utf-8")
|
||||
if not chunk:
|
||||
break
|
||||
response_data += chunk
|
||||
if "\n" in response_data:
|
||||
break
|
||||
|
||||
sock.close()
|
||||
|
||||
# Parse response
|
||||
if response_data.strip():
|
||||
return json.loads(response_data.strip())
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error sending request: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def test_python_execution():
|
||||
"""Test Python execution functionality"""
|
||||
print("Testing Python execution in CtrEditor...")
|
||||
|
||||
# Test 1: Simple Python code
|
||||
print("\n1. Testing simple Python execution...")
|
||||
params = {
|
||||
"name": "execute_python",
|
||||
"arguments": {
|
||||
"code": "result = 2 + 2\nprint(f'Result: {result}')",
|
||||
"return_variables": ["result"],
|
||||
},
|
||||
}
|
||||
|
||||
response = send_mcp_request(params=params)
|
||||
if response:
|
||||
print(f"Response: {json.dumps(response, indent=2)}")
|
||||
else:
|
||||
print("No response received")
|
||||
|
||||
# Test 2: Access CtrEditor objects
|
||||
print("\n2. Testing CtrEditor object access...")
|
||||
params = {
|
||||
"name": "execute_python",
|
||||
"arguments": {
|
||||
"code": """
|
||||
# Access CtrEditor objects
|
||||
object_count = len(objects) if objects else 0
|
||||
canvas_info = f"Canvas size: {canvas.Width}x{canvas.Height}" if canvas else "No canvas"
|
||||
app_info = f"App type: {type(app).__name__}" if app else "No app"
|
||||
|
||||
print(f"Object count: {object_count}")
|
||||
print(canvas_info)
|
||||
print(app_info)
|
||||
""",
|
||||
"return_variables": ["object_count", "canvas_info", "app_info"],
|
||||
},
|
||||
}
|
||||
|
||||
response = send_mcp_request(params=params)
|
||||
if response:
|
||||
print(f"Response: {json.dumps(response, indent=2)}")
|
||||
else:
|
||||
print("No response received")
|
||||
|
||||
# Test 3: Get Python help
|
||||
print("\n3. Testing Python help...")
|
||||
params = {"name": "python_help", "arguments": {}}
|
||||
|
||||
response = send_mcp_request(params=params)
|
||||
if response:
|
||||
print(f"Response: {json.dumps(response, indent=2)}")
|
||||
else:
|
||||
print("No response received")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_python_execution()
|
Loading…
Reference in New Issue