Implement ScreenshotManager for enhanced screenshot functionality
- Added ScreenshotManager class to centralize screenshot capturing logic. - Implemented methods to capture specific objects, areas, and full canvas. - Updated MCPServer to utilize ScreenshotManager for taking screenshots. - Introduced new API method to take screenshots of specific objects by their IDs. - Enhanced error handling and result reporting for screenshot operations. - Refactored TakeScreenshot and added TakeObjectScreenshot methods in MCPServer. - Updated analyze_screenshots.py to use a relative path for screenshots directory. - Removed outdated test script for Python execution.
This commit is contained in:
parent
181a3db41c
commit
3153f38068
|
@ -1,170 +0,0 @@
|
||||||
# CtrEditor MCP Server Documentation
|
|
||||||
|
|
||||||
## 🎯 Overview
|
|
||||||
|
|
||||||
The CtrEditor MCP (Model Context Protocol) Server transforms the CtrEditor WPF application into a programmable automation platform for debugging, testing, and simulation control. This server enables remote control of all major application features through JSON-RPC style communication.
|
|
||||||
|
|
||||||
## 🚀 Architecture
|
|
||||||
|
|
||||||
### Communication Flow
|
|
||||||
```
|
|
||||||
LLM/Client ↔ MCP Proxy (Python) ↔ CtrEditor MCP Server (C#) ↔ WPF Application
|
|
||||||
```
|
|
||||||
|
|
||||||
### Connection Setup
|
|
||||||
- **Protocol**: TCP Socket on port 5006
|
|
||||||
- **Format**: JSON-RPC style messages
|
|
||||||
- **Proxy**: Python proxy for VS Code Copilot integration
|
|
||||||
|
|
||||||
### VS Code Configuration
|
|
||||||
```json
|
|
||||||
"ctreditor": {
|
|
||||||
"command": "C:/Users/migue/miniconda3/envs/mcp_proxy/python.exe",
|
|
||||||
"args": [
|
|
||||||
"d:/Proyectos/Scripts/MCP_Proxy/mcp_proxy.py",
|
|
||||||
"--host", "localhost",
|
|
||||||
"--port", "5006",
|
|
||||||
"--name", "CtrEditor",
|
|
||||||
"--description", "CtrEditor WPF Application MCP Server for debugging and testing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📋 Available Tools
|
|
||||||
|
|
||||||
### 🔧 Object Management
|
|
||||||
|
|
||||||
#### `list_objects`
|
|
||||||
**Description**: Get complete list of all simulation objects
|
|
||||||
**Parameters**: None
|
|
||||||
**Returns**: Array of objects with full metadata (ID, name, type, position, properties)
|
|
||||||
|
|
||||||
#### `create_object`
|
|
||||||
**Description**: Create a new simulation object
|
|
||||||
**Parameters**:
|
|
||||||
- `type` (string): Object type (e.g., "osHydTank", "osHydPump")
|
|
||||||
- `x` (number): X position in meters
|
|
||||||
- `y` (number): Y position in meters
|
|
||||||
|
|
||||||
#### `update_object`
|
|
||||||
**Description**: Modify properties of existing object
|
|
||||||
**Parameters**:
|
|
||||||
- `id` (string): Object ID
|
|
||||||
- `properties` (object): JSON object with properties to update
|
|
||||||
|
|
||||||
#### `delete_objects`
|
|
||||||
**Description**: Remove objects from simulation
|
|
||||||
**Parameters**:
|
|
||||||
- `ids` (array): Array of object IDs to delete
|
|
||||||
|
|
||||||
#### `list_object_types`
|
|
||||||
**Description**: Get list of all creatable object types
|
|
||||||
**Parameters**: None
|
|
||||||
|
|
||||||
### ⚙️ Simulation Control
|
|
||||||
|
|
||||||
#### `start_simulation`
|
|
||||||
**Description**: Start the physics simulation
|
|
||||||
**Parameters**: None
|
|
||||||
|
|
||||||
#### `stop_simulation`
|
|
||||||
**Description**: Stop the physics simulation
|
|
||||||
**Parameters**: None
|
|
||||||
|
|
||||||
#### `get_simulation_status`
|
|
||||||
**Description**: Get current simulation state
|
|
||||||
**Parameters**: None
|
|
||||||
**Returns**: Status info (running, object count, etc.)
|
|
||||||
|
|
||||||
### 🔌 PLC Integration
|
|
||||||
|
|
||||||
#### `get_plc_status`
|
|
||||||
**Description**: Get PLC connection status
|
|
||||||
**Parameters**: None
|
|
||||||
**Returns**: Connection state and status information
|
|
||||||
|
|
||||||
### 📸 Visual Documentation
|
|
||||||
|
|
||||||
#### `take_screenshot`
|
|
||||||
**Description**: Capture canvas screenshot with high resolution
|
|
||||||
**Parameters** (all optional):
|
|
||||||
- `x` (number): X position in meters for partial capture
|
|
||||||
- `y` (number): Y position in meters for partial capture
|
|
||||||
- `width` (number): Width in meters for partial capture
|
|
||||||
- `height` (number): Height in meters for partial capture
|
|
||||||
- `include_background` (boolean): Include canvas background (default: false)
|
|
||||||
|
|
||||||
**Features**:
|
|
||||||
- **High Resolution**: 2x scale for full canvas, 3x for partial captures
|
|
||||||
- **Auto-Directory**: Saves to `/screenshots/` subdirectory
|
|
||||||
- **Detailed Response**: File size, dimensions, timestamps, paths
|
|
||||||
|
|
||||||
### 💾 Project Management
|
|
||||||
|
|
||||||
#### `save_project`
|
|
||||||
**Description**: Save current project state
|
|
||||||
**Parameters**: None
|
|
||||||
|
|
||||||
## 🎨 Key Features
|
|
||||||
|
|
||||||
### 📊 Rich Object Information
|
|
||||||
- **Complete Properties**: All object properties in JSON format
|
|
||||||
- **Simulation Data**: Real-time physics and hydraulic states
|
|
||||||
- **Coordinate System**: All positions in engineering meters
|
|
||||||
- **Type Safety**: Full .NET type information preserved
|
|
||||||
|
|
||||||
### 🖼️ Advanced Screenshots
|
|
||||||
- **Meter-Based Coordinates**: Natural engineering units
|
|
||||||
- **High Resolution**: Anti-aliased, print-quality images
|
|
||||||
- **Flexible Areas**: Full canvas or custom rectangular regions
|
|
||||||
- **Background Control**: With/without canvas background
|
|
||||||
|
|
||||||
### 🔄 Real-Time Monitoring
|
|
||||||
- **Live Updates**: Monitor simulation state changes
|
|
||||||
- **Property Tracking**: Observe dynamic property changes during simulation
|
|
||||||
- **Performance Data**: Track flow rates, pressures, speeds, etc.
|
|
||||||
|
|
||||||
### 🛡️ Error Handling
|
|
||||||
- **Detailed Errors**: Comprehensive error messages with context
|
|
||||||
- **Safe Operations**: Thread-safe UI operations via Dispatcher
|
|
||||||
- **Graceful Degradation**: Handles connection issues and timeouts
|
|
||||||
|
|
||||||
## 🎯 Use Cases
|
|
||||||
|
|
||||||
### 🔬 Simulation Debugging
|
|
||||||
1. **Pre-Simulation**: Inspect object configurations and positions
|
|
||||||
2. **Runtime Monitoring**: Track dynamic properties during simulation
|
|
||||||
3. **Post-Analysis**: Compare before/after states
|
|
||||||
4. **Visual Verification**: Screenshot specific areas for documentation
|
|
||||||
|
|
||||||
### 🤖 Automated Testing
|
|
||||||
- **Regression Testing**: Verify simulation behavior consistency
|
|
||||||
- **Performance Benchmarking**: Monitor simulation performance metrics
|
|
||||||
- **Configuration Validation**: Ensure proper object setup
|
|
||||||
- **Visual Regression**: Compare screenshots across versions
|
|
||||||
|
|
||||||
### 📚 Documentation Generation
|
|
||||||
- **Auto-Documentation**: Extract object configurations automatically
|
|
||||||
- **Visual Documentation**: Generate annotated screenshots
|
|
||||||
- **State Reports**: Create detailed simulation state reports
|
|
||||||
- **Training Materials**: Generate step-by-step simulation guides
|
|
||||||
|
|
||||||
## 🔧 Technical Implementation
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
- **MCPServer.cs**: Main TCP server with JSON-RPC handling
|
|
||||||
- **Tool Handlers**: Specialized handlers for each operation type
|
|
||||||
- **Thread Safety**: UI operations via WPF Dispatcher
|
|
||||||
- **Error Management**: Comprehensive exception handling
|
|
||||||
|
|
||||||
### Performance Optimizations
|
|
||||||
- **Lazy Connection**: Connect only when needed
|
|
||||||
- **Efficient Serialization**: Optimized JSON generation
|
|
||||||
- **Memory Management**: Proper resource cleanup
|
|
||||||
- **High-Resolution Rendering**: Optimized screenshot generation
|
|
||||||
|
|
||||||
### Security Considerations
|
|
||||||
- **Local-Only**: Bound to localhost for security
|
|
||||||
- **No File System Access**: Limited to application operations
|
|
||||||
- **Controlled Operations**: No dangerous system operations
|
|
||||||
- **Sandboxed**: Isolated from system configuration
|
|
|
@ -1,126 +0,0 @@
|
||||||
# CtrEditor MCP Server - LLM Guide
|
|
||||||
|
|
||||||
## ⚡ Command Efficiency Tiers
|
|
||||||
|
|
||||||
### 🚀 **Ultra-Fast** (Use Liberally)
|
|
||||||
- `get_simulation_status` → ~25 tokens, instant
|
|
||||||
- `get_ctreditor_status` → ~20 tokens, instant
|
|
||||||
- `start/stop_simulation` → ~15 tokens, 2-3 sec
|
|
||||||
|
|
||||||
### 🟡 **Medium** (Use When Needed)
|
|
||||||
- `search_debug_log` → 50-200 tokens (max_lines=3-10)
|
|
||||||
- `build_project` → 100-500 tokens (errors only)
|
|
||||||
|
|
||||||
### 🔴 **Heavy** (Use Sparingly)
|
|
||||||
- `list_objects` → 500-2000+ tokens
|
|
||||||
- `take_screenshot` → 100-300 tokens + file
|
|
||||||
|
|
||||||
## 🔧 Core Operations
|
|
||||||
|
|
||||||
### Process Management
|
|
||||||
```json
|
|
||||||
{"tool": "start_ctreditor", "parameters": {}}
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build & Debug
|
|
||||||
```json
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
{"tool": "start_debug_listener", "parameters": {}}
|
|
||||||
{"tool": "search_debug_log", "parameters": {"pattern": "error|exception", "max_lines": 5}}
|
|
||||||
{"tool": "stop_debug_listener", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Simulation Control
|
|
||||||
```json
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
{"tool": "start_simulation", "parameters": {}}
|
|
||||||
{"tool": "stop_simulation", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Object Management
|
|
||||||
```json
|
|
||||||
{"tool": "list_objects", "parameters": {}}
|
|
||||||
{"tool": "create_object", "parameters": {"type": "osHydPump", "x": 10, "y": 5}}
|
|
||||||
{"tool": "update_object", "parameters": {"id": "123", "properties": {"PumpHead": 75.0}}}
|
|
||||||
{"tool": "delete_objects", "parameters": {"ids": ["123", "456"]}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚡ Optimal Workflows
|
|
||||||
|
|
||||||
### Quick Development Cycle
|
|
||||||
```json
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
{"tool": "start_simulation", "parameters": {}}
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bug Investigation
|
|
||||||
```json
|
|
||||||
{"tool": "search_debug_log", "parameters": {"pattern": "error|exception", "max_lines": 3}}
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### System Recovery
|
|
||||||
```json
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
{"tool": "start_ctreditor", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 Key Object Properties
|
|
||||||
|
|
||||||
### Hydraulic Components
|
|
||||||
- **osHydTank**: `TankPressure`, `MaxLevel`, `MinLevel`, `IsFixedPressure`
|
|
||||||
- **osHydPump**: `PumpHead`, `MaxFlow`, `SpeedRatio`, `IsRunning`
|
|
||||||
- **osHydPipe**: `Length`, `Diameter`, `CurrentFlow`, `PressureDrop`
|
|
||||||
|
|
||||||
### Common Properties
|
|
||||||
- **Position**: `Left`, `Top` (meters)
|
|
||||||
- **Dimensions**: `Ancho`, `Alto` (meters)
|
|
||||||
- **Connections**: `Id_ComponenteA`, `Id_ComponenteB`
|
|
||||||
|
|
||||||
## 🖼️ Screenshots
|
|
||||||
|
|
||||||
### Full Canvas
|
|
||||||
```json
|
|
||||||
{"tool": "take_screenshot", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Targeted Area
|
|
||||||
```json
|
|
||||||
{"tool": "take_screenshot", "parameters": {
|
|
||||||
"x": 39.0, "y": 19.0, "width": 7.0, "height": 3.0
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔍 Debug Search Patterns
|
|
||||||
|
|
||||||
High-value, low-token patterns:
|
|
||||||
- **Critical Errors**: `"error|exception|fail"` (max_lines=3)
|
|
||||||
- **Performance**: `"fps|slow|memory"` (max_lines=5)
|
|
||||||
- **Simulation**: `"simulation.*error|physics.*fail"` (max_lines=3)
|
|
||||||
|
|
||||||
## 🚨 Troubleshooting
|
|
||||||
|
|
||||||
### Build Issues
|
|
||||||
```json
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
{"tool": "clean_project", "parameters": {}}
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Connection Issues
|
|
||||||
1. Use `get_simulation_status` to test connectivity
|
|
||||||
2. Check CtrEditor is running with `get_ctreditor_status`
|
|
||||||
3. Verify MCP server is active on port 5006
|
|
||||||
|
|
||||||
## 💡 Best Practices
|
|
||||||
|
|
||||||
- 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
|
|
||||||
- Stop simulation before major structural changes
|
|
||||||
- Use appropriate units: meters, Pascal, m³/s
|
|
|
@ -1,789 +0,0 @@
|
||||||
# CtrEditor MCP Server - LLM Guide
|
|
||||||
|
|
||||||
## ⚡ Command Efficiency Tiers
|
|
||||||
|
|
||||||
### 🚀 **Ultra-Fast** (Use Liberally)
|
|
||||||
- `get_simulation_status` → ~25 tokens, instant
|
|
||||||
- `get_ctreditor_status` → ~20 tokens, instant
|
|
||||||
- `start/stop_simulation` → ~15 tokens, 2-3 sec
|
|
||||||
|
|
||||||
### 🟡 **Medium** (Use When Needed)
|
|
||||||
- `search_debug_log` → 50-200 tokens (max_lines=3-10)
|
|
||||||
- `build_project` → 100-500 tokens (errors only)
|
|
||||||
|
|
||||||
### 🔴 **Heavy** (Use Sparingly)
|
|
||||||
- `list_objects` → 500-2000+ tokens
|
|
||||||
- `take_screenshot` → 100-300 tokens + file
|
|
||||||
|
|
||||||
## <20> Core Operations
|
|
||||||
|
|
||||||
### Process Management
|
|
||||||
```json
|
|
||||||
{"tool": "start_ctreditor", "parameters": {}}
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build & Debug
|
|
||||||
```json
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
{"tool": "start_debug_listener", "parameters": {}}
|
|
||||||
{"tool": "search_debug_log", "parameters": {"pattern": "error|exception", "max_lines": 5}}
|
|
||||||
{"tool": "stop_debug_listener", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Simulation Control
|
|
||||||
```json
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
{"tool": "start_simulation", "parameters": {}}
|
|
||||||
{"tool": "stop_simulation", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Object Management
|
|
||||||
```json
|
|
||||||
{"tool": "list_objects", "parameters": {}}
|
|
||||||
{"tool": "create_object", "parameters": {"type": "osHydPump", "x": 10, "y": 5}}
|
|
||||||
{"tool": "update_object", "parameters": {"id": "123", "properties": {"PumpHead": 75.0}}}
|
|
||||||
{"tool": "delete_objects", "parameters": {"ids": ["123", "456"]}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚡ Optimal Workflows
|
|
||||||
|
|
||||||
### Quick Development Cycle
|
|
||||||
```json
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
{"tool": "start_simulation", "parameters": {}}
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bug Investigation
|
|
||||||
```json
|
|
||||||
{"tool": "search_debug_log", "parameters": {"pattern": "error|exception", "max_lines": 3}}
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### System Recovery
|
|
||||||
```json
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
{"tool": "start_ctreditor", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🎯 Micro-Workflows (Ultra-Efficient)
|
|
||||||
|
|
||||||
#### **Instant Status Check (1-2 seconds, <50 tokens)**
|
|
||||||
```json
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
Use this before any operation - tells you everything you need to know
|
|
||||||
|
|
||||||
#### **Quick Error Scan (3-5 seconds, <100 tokens)**
|
|
||||||
```json
|
|
||||||
{"tool": "search_debug_log", "parameters": {
|
|
||||||
"pattern": "error|fail|exception",
|
|
||||||
"max_lines": 3
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
Surgical error detection without token waste
|
|
||||||
|
|
||||||
#### **Smart Object Count Verification (1 second, ~25 tokens)**
|
|
||||||
```json
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
Check object_count property instead of calling list_objects
|
|
||||||
|
|
||||||
#### **Efficient Build Verification (5-10 seconds, filtered output)**
|
|
||||||
```json
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
```
|
|
||||||
Only returns errors - ignores verbose build output
|
|
||||||
|
|
||||||
### <20> Optimized Recovery Patterns
|
|
||||||
|
|
||||||
#### **Process Recovery (Fast)**
|
|
||||||
```json
|
|
||||||
// 1. Check status (instant)
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
|
|
||||||
// 2. If needed, restart (5-10 seconds)
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
{"tool": "start_ctreditor", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Simulation Recovery (Ultra-Fast)**
|
|
||||||
```json
|
|
||||||
// 1. Quick reset (3 seconds total)
|
|
||||||
{"tool": "stop_simulation", "parameters": {}}
|
|
||||||
{"tool": "start_simulation", "parameters": {}}
|
|
||||||
|
|
||||||
// 2. Verify (instant)
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Debug Recovery (Minimal)**
|
|
||||||
```json
|
|
||||||
{"tool": "stop_debug_listener", "parameters": {}}
|
|
||||||
{"tool": "start_debug_listener", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🧹 Cleanup (Minimal Token Usage)
|
|
||||||
```
|
|
||||||
{"tool": "stop_simulation", "parameters": {}} // if running
|
|
||||||
{"tool": "save_project", "parameters": {}} // if changes made
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}} // when done
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 Object Property Deep Dive
|
|
||||||
|
|
||||||
### Hydraulic Components
|
|
||||||
|
|
||||||
#### **osHydTank** (Hydraulic Tank)
|
|
||||||
Key Properties to Monitor:
|
|
||||||
- `TankType`: Type of tank (1=standard)
|
|
||||||
- `CrossSectionalArea`: Tank cross-section in m²
|
|
||||||
- `MaxLevel`, `MinLevel`: Level limits in meters
|
|
||||||
- `TankPressure`: Pressure in Pascal
|
|
||||||
- `IsFixedPressure`: Whether pressure is constant
|
|
||||||
|
|
||||||
#### **osHydPump** (Hydraulic Pump)
|
|
||||||
Key Properties to Monitor:
|
|
||||||
- `PumpHead`: Pump head in meters
|
|
||||||
- `MaxFlow`: Maximum flow rate in m³/s
|
|
||||||
- `SpeedRatio`: Speed ratio (0.0-1.0)
|
|
||||||
- `IsRunning`: Pump operational state
|
|
||||||
- `PumpDirection`: Flow direction (1=forward, -1=reverse)
|
|
||||||
|
|
||||||
#### **osHydPipe** (Hydraulic Pipe)
|
|
||||||
Key Properties to Monitor:
|
|
||||||
- `Length`: Pipe length in meters
|
|
||||||
- `Diameter`: Internal diameter in meters
|
|
||||||
- `Roughness`: Surface roughness
|
|
||||||
- `CurrentFlow`: Current flow rate in m³/s
|
|
||||||
- `PressureDrop`: Pressure loss across pipe
|
|
||||||
- `Id_ComponenteA`, `Id_ComponenteB`: Connected components
|
|
||||||
|
|
||||||
### Dynamic Properties
|
|
||||||
During simulation, monitor these changing values:
|
|
||||||
- Flow rates in pipes (`CurrentFlow`)
|
|
||||||
- Pressure drops (`PressureDrop`)
|
|
||||||
- Tank levels (if applicable)
|
|
||||||
- Pump performance metrics
|
|
||||||
|
|
||||||
## 🖼️ Screenshots
|
|
||||||
|
|
||||||
### Full Canvas
|
|
||||||
```json
|
|
||||||
{"tool": "take_screenshot", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Targeted Area
|
|
||||||
```json
|
|
||||||
{"tool": "take_screenshot", "parameters": {
|
|
||||||
"x": 39.0, "y": 19.0, "width": 7.0, "height": 3.0
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔍 Debug Search Patterns
|
|
||||||
|
|
||||||
High-value, low-token patterns:
|
|
||||||
- **Critical Errors**: `"error|exception|fail"` (max_lines=3)
|
|
||||||
- **Performance**: `"fps|slow|memory"` (max_lines=5)
|
|
||||||
- **Simulation**: `"simulation.*error|physics.*fail"` (max_lines=3)
|
|
||||||
|
|
||||||
## <20> Troubleshooting
|
|
||||||
|
|
||||||
### Build Issues
|
|
||||||
```json
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
{"tool": "clean_project", "parameters": {}}
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Connection Issues
|
|
||||||
1. Use `get_simulation_status` to test connectivity
|
|
||||||
2. Check CtrEditor is running with `get_ctreditor_status`
|
|
||||||
3. Verify MCP server is active on port 5006
|
|
||||||
|
|
||||||
## 💡 Best Practices
|
|
||||||
|
|
||||||
- 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
|
|
||||||
- Stop simulation before major structural changes
|
|
||||||
- Use appropriate units: meters, Pascal, m³/s
|
|
||||||
"pattern": "error|exception",
|
|
||||||
"max_lines": 20
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Check buffer stats
|
|
||||||
{"tool": "get_debug_stats", "parameters": {}}
|
|
||||||
|
|
||||||
// Stop monitoring
|
|
||||||
{"tool": "stop_debug_listener", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Combined Development Workflow
|
|
||||||
```json
|
|
||||||
// 1. Ensure clean environment
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
{"tool": "clean_project", "parameters": {}}
|
|
||||||
|
|
||||||
// 2. Build and check for errors
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
|
|
||||||
// 3. Start CtrEditor with monitoring
|
|
||||||
{"tool": "start_debug_listener", "parameters": {}}
|
|
||||||
{"tool": "start_ctreditor", "parameters": {}}
|
|
||||||
|
|
||||||
// 4. Monitor for issues
|
|
||||||
{"tool": "search_debug_log", "parameters": {
|
|
||||||
"pattern": "error|exception|fail",
|
|
||||||
"max_lines": 10
|
|
||||||
}}
|
|
||||||
|
|
||||||
// 5. Test simulation
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
{"tool": "start_simulation", "parameters": {}}
|
|
||||||
|
|
||||||
// 6. Search for simulation issues
|
|
||||||
{"tool": "search_debug_log", "parameters": {
|
|
||||||
"pattern": "simulation|physics",
|
|
||||||
"max_lines": 15
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Enhanced Debugging Workflows (NEW)
|
|
||||||
|
|
||||||
### 1. Build Error Investigation
|
|
||||||
```
|
|
||||||
1. build_project - Get compilation errors only (filtered)
|
|
||||||
2. search_debug_log(pattern="error|warning", max_lines=50) - Find related debug info
|
|
||||||
3. start_ctreditor - Launch for testing
|
|
||||||
4. get_debug_stats - Monitor for new issues
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Runtime Issue Investigation
|
|
||||||
```
|
|
||||||
1. start_debug_listener - Begin monitoring
|
|
||||||
2. start_simulation - Trigger the issue
|
|
||||||
3. search_debug_log(pattern="exception|error", last_n_lines=200) - Find problems
|
|
||||||
4. search_debug_log(pattern="stack trace", max_lines=10) - Get stack traces
|
|
||||||
5. stop_simulation - Stop for analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Performance Analysis
|
|
||||||
```
|
|
||||||
1. clear_debug_buffer - Start with clean slate
|
|
||||||
2. start_simulation - Begin performance test
|
|
||||||
3. search_debug_log(pattern="fps|performance|slow", max_lines=20) - Find performance issues
|
|
||||||
4. search_debug_log(pattern="memory|allocation", max_lines=15) - Check memory usage
|
|
||||||
5. get_debug_stats - Overall statistics
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Component Behavior Analysis
|
|
||||||
```
|
|
||||||
1. search_debug_log(pattern="pump|flow|pressure", max_lines=30) - Hydraulic behavior
|
|
||||||
2. search_debug_log(pattern="simulation.*object", max_lines=25) - Object interactions
|
|
||||||
3. take_screenshot() - Visual verification
|
|
||||||
4. list_objects - Current property values
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Pre-Simulation Analysis
|
|
||||||
```
|
|
||||||
1. list_objects - Document initial state
|
|
||||||
2. take_screenshot() - Visual documentation
|
|
||||||
3. Analyze object configurations and connections
|
|
||||||
4. Verify proper component setup
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Simulation Monitoring
|
|
||||||
```
|
|
||||||
1. start_simulation - Begin simulation
|
|
||||||
2. get_simulation_status - Confirm running
|
|
||||||
3. list_objects - Monitor dynamic properties
|
|
||||||
4. take_screenshot(area) - Capture specific components
|
|
||||||
5. stop_simulation - End when needed
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. Performance Investigation
|
|
||||||
```
|
|
||||||
1. list_objects - Get baseline measurements
|
|
||||||
2. start_simulation - Run simulation
|
|
||||||
3. Wait 5-10 seconds for stabilization
|
|
||||||
4. list_objects - Compare with baseline
|
|
||||||
5. take_screenshot() - Document final state
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8. Component Analysis
|
|
||||||
```
|
|
||||||
1. take_screenshot(component_area) - High-res component view
|
|
||||||
2. list_objects - Get detailed properties
|
|
||||||
3. update_object - Modify parameters if needed
|
|
||||||
4. start_simulation - Test changes
|
|
||||||
5. take_screenshot(component_area) - Compare results
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📈 Simulation Data Interpretation
|
|
||||||
|
|
||||||
### Flow Analysis
|
|
||||||
- **Positive Flow**: Normal direction flow
|
|
||||||
- **Negative Flow**: Reverse flow direction
|
|
||||||
- **Zero Flow**: No flow (blockage or equilibrium)
|
|
||||||
|
|
||||||
### Pressure Analysis
|
|
||||||
- **High Pressure Drop**: Potential flow restriction
|
|
||||||
- **Low Pressure Drop**: Free-flowing condition
|
|
||||||
- **Pressure Patterns**: Indicate system behavior
|
|
||||||
|
|
||||||
### Pump Performance
|
|
||||||
- **SpeedRatio**: Pump operation intensity
|
|
||||||
- **IsRunning**: Operational state
|
|
||||||
- **PumpDirection**: Flow direction control
|
|
||||||
|
|
||||||
## ⚡ Rapid Iteration Patterns (NEW)
|
|
||||||
|
|
||||||
### Lightning Fast Debug Cycle
|
|
||||||
**Single-Command Quick Check (5 seconds):**
|
|
||||||
```json
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
- Instantly know: running state, object count, visibility
|
|
||||||
- Use this before any other operation
|
|
||||||
|
|
||||||
### Minimal Token Development Loop
|
|
||||||
**Ultra-Efficient 3-Step Pattern:**
|
|
||||||
```json
|
|
||||||
// 1. Quick status (minimal tokens)
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
|
|
||||||
// 2. Smart build (errors only, no full log)
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
|
|
||||||
// 3. Target-specific debug search (specific pattern)
|
|
||||||
{"tool": "search_debug_log", "parameters": {
|
|
||||||
"pattern": "error|exception",
|
|
||||||
"max_lines": 5
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Zero-Waste Object Analysis
|
|
||||||
**Smart Object Inspection (instead of full list_objects):**
|
|
||||||
```json
|
|
||||||
// Instead of listing all objects, target specific ones:
|
|
||||||
{"tool": "list_objects", "parameters": {}}
|
|
||||||
// Then filter client-side by type/name/properties
|
|
||||||
```
|
|
||||||
|
|
||||||
### Micro-Screenshot Strategy
|
|
||||||
**Targeted Visual Validation (saves bandwidth):**
|
|
||||||
```json
|
|
||||||
// Full canvas only when needed
|
|
||||||
{"tool": "take_screenshot", "parameters": {}}
|
|
||||||
|
|
||||||
// For specific components (higher detail, smaller file):
|
|
||||||
{"tool": "take_screenshot", "parameters": {
|
|
||||||
"x": 42.0, "y": 20.0, "width": 2.0, "height": 2.0
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## <20> Token-Optimized Workflows
|
|
||||||
|
|
||||||
### Development Iteration (Minimal Tokens)
|
|
||||||
```
|
|
||||||
1. get_ctreditor_status (1 call - instant)
|
|
||||||
2. build_project (errors only - no verbose output)
|
|
||||||
3. start_simulation (1 call - instant feedback)
|
|
||||||
4. get_simulation_status (verify running)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bug Investigation (Surgical Precision)
|
|
||||||
```
|
|
||||||
1. search_debug_log(pattern="specific_error", max_lines=3)
|
|
||||||
2. list_objects (only if object data needed)
|
|
||||||
3. take_screenshot (targeted area only)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance Testing (Streamlined)
|
|
||||||
```
|
|
||||||
1. clear_debug_buffer (clean slate)
|
|
||||||
2. start_simulation
|
|
||||||
3. search_debug_log(pattern="fps|performance", max_lines=10)
|
|
||||||
4. stop_simulation
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Smart Command Selection Guide
|
|
||||||
|
|
||||||
### When to Use Each Function:
|
|
||||||
- **get_ctreditor_status**: Always first command (2 tokens response)
|
|
||||||
- **build_project**: Only when code changed (filtered output)
|
|
||||||
- **search_debug_log**: Instead of full console dump (targeted)
|
|
||||||
- **get_simulation_status**: Quick health check (3 tokens response)
|
|
||||||
- **list_objects**: Only when need object data (can be large)
|
|
||||||
- **take_screenshot**: Last resort for visual confirmation
|
|
||||||
|
|
||||||
### Command Efficiency Ranking:
|
|
||||||
1. 🟢 **Ultra-Fast** (use liberally): `get_ctreditor_status`, `get_simulation_status`
|
|
||||||
2. 🟡 **Fast** (use when needed): `start/stop_simulation`, `search_debug_log`
|
|
||||||
3. 🟠 **Medium** (use selectively): `build_project`, `save_project`
|
|
||||||
4. 🔴 **Heavy** (use sparingly): `list_objects`, `take_screenshot`
|
|
||||||
|
|
||||||
## <20>🚨 Enhanced Troubleshooting Guide (NEW)
|
|
||||||
|
|
||||||
### Process Management Issues
|
|
||||||
If CtrEditor won't start or crashes:
|
|
||||||
1. `get_ctreditor_status` - Check current process state (instant)
|
|
||||||
2. `stop_ctreditor` - Ensure clean termination
|
|
||||||
3. `build_project` - Check for compilation issues (errors only)
|
|
||||||
4. `start_ctreditor` - Launch fresh instance
|
|
||||||
5. `search_debug_log` - Monitor for startup issues (targeted pattern)
|
|
||||||
|
|
||||||
### Build Problems (Smart Filtering)
|
|
||||||
Critical Build Error Patterns:
|
|
||||||
- **CS#### errors**: Compilation issues (fix immediately)
|
|
||||||
- **MSB3027/MSB3021**: File lock errors (CtrEditor running)
|
|
||||||
- **MSB#### warnings**: Build system (usually safe to ignore)
|
|
||||||
|
|
||||||
Quick Build Fix Flow:
|
|
||||||
```json
|
|
||||||
// 1. Check if app is blocking compilation
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
|
|
||||||
// 2. Stop if running
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
|
|
||||||
// 3. Clean and rebuild
|
|
||||||
{"tool": "clean_project", "parameters": {}}
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Debug Console Issues (Connection Problems)
|
|
||||||
Fast Debug Recovery:
|
|
||||||
```json
|
|
||||||
// 1. Check connection health
|
|
||||||
{"tool": "get_debug_stats", "parameters": {}}
|
|
||||||
|
|
||||||
// 2. Reset connection
|
|
||||||
{"tool": "stop_debug_listener", "parameters": {}}
|
|
||||||
{"tool": "start_debug_listener", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Connection Issues
|
|
||||||
If MCP commands fail:
|
|
||||||
1. Use `get_simulation_status` to test connectivity (fastest)
|
|
||||||
2. Check CtrEditor is running with `get_ctreditor_status`
|
|
||||||
3. Verify MCP server is active on port 5006
|
|
||||||
|
|
||||||
### Simulation Issues (Rapid Response)
|
|
||||||
If simulation behaves unexpectedly:
|
|
||||||
```json
|
|
||||||
// Quick diagnostic sequence
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
{"tool": "search_debug_log", "parameters": {
|
|
||||||
"pattern": "simulation|physics|error",
|
|
||||||
"max_lines": 5
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data Inconsistencies
|
|
||||||
Efficient data validation:
|
|
||||||
1. Use `get_simulation_status` for quick object count check
|
|
||||||
2. Only call `list_objects` if counts don't match expectations
|
|
||||||
3. Use targeted screenshots for visual verification
|
|
||||||
|
|
||||||
## 💡 Enhanced Best Practices (UPDATED)
|
|
||||||
|
|
||||||
### 🚀 Token Efficiency (Critical for Performance)
|
|
||||||
|
|
||||||
#### **Ultra-Lightweight Commands (Use Liberally)**
|
|
||||||
- `get_ctreditor_status` → 15-25 tokens response
|
|
||||||
- `get_simulation_status` → 20-30 tokens response
|
|
||||||
- `start/stop_simulation` → 10-15 tokens response
|
|
||||||
|
|
||||||
#### **Medium Weight Commands (Use When Needed)**
|
|
||||||
- `search_debug_log` → 50-200 tokens (with max_lines=5-10)
|
|
||||||
- `build_project` → 100-500 tokens (errors only, filtered)
|
|
||||||
- `create/update/delete_objects` → 30-100 tokens each
|
|
||||||
|
|
||||||
#### **Heavy Commands (Use Sparingly)**
|
|
||||||
- `list_objects` → 500-2000+ tokens (depends on object count)
|
|
||||||
- `take_screenshot` → 100-300 tokens metadata + file
|
|
||||||
|
|
||||||
### ⚡ Speed Optimization Patterns
|
|
||||||
|
|
||||||
#### **Lightning-Fast Status Check (2-3 seconds)**
|
|
||||||
```json
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
Returns: running state, object count, visibility in minimal tokens
|
|
||||||
|
|
||||||
#### **Smart Error Detection (5-10 seconds)**
|
|
||||||
```json
|
|
||||||
{"tool": "search_debug_log", "parameters": {
|
|
||||||
"pattern": "error|exception|fail",
|
|
||||||
"max_lines": 3
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
Targeted error search instead of full console dump
|
|
||||||
|
|
||||||
#### **Efficient Object Management**
|
|
||||||
```json
|
|
||||||
// Instead of list_objects -> update -> list_objects again:
|
|
||||||
// 1. Single targeted update
|
|
||||||
{"tool": "update_object", "parameters": {"id": "123", "properties": {...}}}
|
|
||||||
|
|
||||||
// 2. Quick verification with status
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🎯 Workflow Optimization
|
|
||||||
|
|
||||||
#### **Development Iteration (Under 30 seconds)**
|
|
||||||
```
|
|
||||||
1. get_ctreditor_status (instant)
|
|
||||||
2. build_project (filtered errors)
|
|
||||||
3. start_simulation (instant)
|
|
||||||
4. search_debug_log (targeted, max_lines=5)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Bug Investigation (Under 15 seconds)**
|
|
||||||
```
|
|
||||||
1. search_debug_log (specific error pattern, max_lines=3)
|
|
||||||
2. get_simulation_status (confirm state)
|
|
||||||
3. targeted screenshot (only if visual needed)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Performance Testing (Under 20 seconds)**
|
|
||||||
```
|
|
||||||
1. clear_debug_buffer
|
|
||||||
2. start_simulation
|
|
||||||
3. search_debug_log(pattern="fps|slow", max_lines=5)
|
|
||||||
4. stop_simulation
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Smart Debug Search Patterns
|
|
||||||
|
|
||||||
#### **High-Value, Low-Token Patterns**
|
|
||||||
- **Critical Errors**: `"error|exception|fail|crash"` (max_lines=3)
|
|
||||||
- **Performance Issues**: `"fps|slow|memory|lag"` (max_lines=5)
|
|
||||||
- **Simulation Problems**: `"simulation.*error|physics.*fail"` (max_lines=3)
|
|
||||||
- **Object Issues**: `"object.*error|create.*fail|update.*error"` (max_lines=3)
|
|
||||||
|
|
||||||
#### **Efficient Search Strategy**
|
|
||||||
```json
|
|
||||||
// Good: Specific, targeted
|
|
||||||
{"pattern": "pump.*error", "max_lines": 3}
|
|
||||||
|
|
||||||
// Avoid: Too broad, token-heavy
|
|
||||||
{"pattern": ".*", "max_lines": 100}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 📊 Data Minimization Techniques
|
|
||||||
|
|
||||||
#### **Before Calling list_objects (Heavy)**
|
|
||||||
Ask yourself:
|
|
||||||
1. Do I need ALL objects or just count? → Use `get_simulation_status`
|
|
||||||
2. Do I need specific object? → Search by type/name client-side
|
|
||||||
3. Do I need visual confirmation? → Use targeted `take_screenshot`
|
|
||||||
|
|
||||||
#### **Before Taking Screenshots**
|
|
||||||
Consider:
|
|
||||||
1. Is object data sufficient? → Use `list_objects` for position/properties
|
|
||||||
2. Do I need full canvas? → Use area parameters for specific region
|
|
||||||
3. Is this for documentation? → Full canvas OK
|
|
||||||
4. Is this for debugging? → Targeted area preferred
|
|
||||||
|
|
||||||
### 🔧 Advanced Efficiency Tips
|
|
||||||
|
|
||||||
#### **Batch Operations (When Possible)**
|
|
||||||
```json
|
|
||||||
// Good: Single call for multiple deletes
|
|
||||||
{"tool": "delete_objects", "parameters": {"ids": ["1", "2", "3"]}}
|
|
||||||
|
|
||||||
// Avoid: Multiple single calls
|
|
||||||
// delete_objects(id="1"), delete_objects(id="2"), delete_objects(id="3")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **State Caching Strategy**
|
|
||||||
- Cache `get_simulation_status` results for 5-10 seconds
|
|
||||||
- Only call `list_objects` when object count changes
|
|
||||||
- Re-use screenshot data for multiple analyses
|
|
||||||
|
|
||||||
#### **Conditional Command Execution**
|
|
||||||
```json
|
|
||||||
// Check state before expensive operations
|
|
||||||
if (simulation_status.is_running) {
|
|
||||||
// Only then do expensive analysis
|
|
||||||
list_objects();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🚨 Anti-Patterns (Avoid These)
|
|
||||||
|
|
||||||
#### **Token Wasters**
|
|
||||||
- ❌ Calling `list_objects` repeatedly without state change
|
|
||||||
- ❌ Using `search_debug_log` without specific patterns
|
|
||||||
- ❌ Taking full screenshots for simple status checks
|
|
||||||
- ❌ Building project when no code changes made
|
|
||||||
|
|
||||||
#### **Time Wasters**
|
|
||||||
- ❌ Not checking `get_ctreditor_status` before operations
|
|
||||||
- ❌ Using `stop_ctreditor` → `start_ctreditor` for simple resets
|
|
||||||
- ❌ Clearing debug buffer unnecessarily
|
|
||||||
- ❌ Taking screenshots without area parameters for small checks
|
|
||||||
|
|
||||||
### 🎯 Context-Aware Command Selection
|
|
||||||
|
|
||||||
#### **Development Context**
|
|
||||||
Use: `build_project`, `search_debug_log`, `get_ctreditor_status`
|
|
||||||
|
|
||||||
#### **Simulation Testing Context**
|
|
||||||
Use: `start/stop_simulation`, `get_simulation_status`, targeted `search_debug_log`
|
|
||||||
|
|
||||||
#### **Object Analysis Context**
|
|
||||||
Use: `list_objects` (when needed), `update_object`, `get_simulation_status`
|
|
||||||
|
|
||||||
#### **Visual Documentation Context**
|
|
||||||
Use: `take_screenshot`, `list_objects` (for positions), `save_project`
|
|
||||||
|
|
||||||
### 🔄 Recovery Patterns (Fast Error Recovery)
|
|
||||||
|
|
||||||
#### **Process Recovery (5 seconds)**
|
|
||||||
```json
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
// If not running:
|
|
||||||
{"tool": "start_ctreditor", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Simulation Recovery (3 seconds)**
|
|
||||||
```json
|
|
||||||
{"tool": "stop_simulation", "parameters": {}}
|
|
||||||
{"tool": "start_simulation", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Debug Recovery (2 seconds)**
|
|
||||||
```json
|
|
||||||
{"tool": "stop_debug_listener", "parameters": {}}
|
|
||||||
{"tool": "start_debug_listener", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Safety
|
|
||||||
- Always verify critical changes with `get_simulation_status`
|
|
||||||
- Use appropriate units for all parameters (meters, Pascal, m³/s)
|
|
||||||
- Stop simulation before major structural changes
|
|
||||||
- Save project after significant modifications
|
|
||||||
|
|
||||||
## 📋 Real-World Testing Results (VERIFIED PATTERNS)
|
|
||||||
|
|
||||||
### ✅ **Proven Efficient Patterns (From Live Testing)**
|
|
||||||
|
|
||||||
#### **Complete System Test (19/19 functions verified)**
|
|
||||||
```json
|
|
||||||
// 1. Process verification (instant)
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
|
|
||||||
// 2. System assessment (25 tokens)
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
|
|
||||||
// 3. Object discovery (when needed)
|
|
||||||
{"tool": "list_objects", "parameters": {}}
|
|
||||||
|
|
||||||
// 4. Visual confirmation (targeted)
|
|
||||||
{"tool": "take_screenshot", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Hydraulic System Analysis (Real Example)**
|
|
||||||
**Found Objects** (from actual test):
|
|
||||||
- 2 × Hydraulic Tanks (osHydTank): `TankPressure: 101000.0 Pascal`
|
|
||||||
- 1 × Hydraulic Pump (osHydPump): `PumpHead: 75.0m, MaxFlow: 0.015 m³/s`
|
|
||||||
- 2 × Hydraulic Pipes (osHydPipe): `Diameter: 0.05m, Length: 1.0m`
|
|
||||||
|
|
||||||
**Key Properties Verified**:
|
|
||||||
- Flow rates: `CurrentFlow: 0.0` (static state)
|
|
||||||
- Pressure drops: `PressureDrop: 0.0` (no flow)
|
|
||||||
- Connections: `Id_ComponenteA/B` linking system components
|
|
||||||
|
|
||||||
#### **Build Error Handling (Real Example)**
|
|
||||||
**Actual Error Encountered**: MSB3027/MSB3021 - File locked by CtrEditor process
|
|
||||||
**Smart Response Pattern**:
|
|
||||||
```json
|
|
||||||
// 1. Check if process is blocking
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
|
|
||||||
// 2. If running, stop for clean build
|
|
||||||
{"tool": "stop_ctreditor", "parameters": {}}
|
|
||||||
|
|
||||||
// 3. Clean and rebuild
|
|
||||||
{"tool": "clean_project", "parameters": {}}
|
|
||||||
{"tool": "build_project", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Object Manipulation Workflow (Verified)**
|
|
||||||
```json
|
|
||||||
// 1. Create object (tested: osTextPlate, ID: 307325)
|
|
||||||
{"tool": "create_object", "parameters": {
|
|
||||||
"type": "osTextPlate", "x": 46, "y": 21
|
|
||||||
}}
|
|
||||||
|
|
||||||
// 2. Update properties (verified)
|
|
||||||
{"tool": "update_object", "parameters": {
|
|
||||||
"id": "307325",
|
|
||||||
"properties": {"Text": "Sistema Hidráulico", "TextColor": "#FF0000FF"}
|
|
||||||
}}
|
|
||||||
|
|
||||||
// 3. Verify with status check (efficient)
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
|
|
||||||
// 4. Clean up when done
|
|
||||||
{"tool": "delete_objects", "parameters": {"ids": ["307325"]}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 📊 **Performance Metrics (Actual Results)**
|
|
||||||
- **Screenshot**: 3.4MB file, 78×54 meter canvas captured
|
|
||||||
- **Object types**: 33 types across 9 categories available
|
|
||||||
- **Process startup**: ~5-10 seconds from stop to ready
|
|
||||||
- **Simulation control**: Instant start/stop responses
|
|
||||||
- **Debug search**: <2 seconds for targeted patterns
|
|
||||||
|
|
||||||
### 🎯 **Object Type Categories (Complete List)**
|
|
||||||
1. **Componentes Hidráulicos** (3): Pumps, Tanks, Pipes
|
|
||||||
2. **Decorativos** (3): Images, Frames, Text Plates
|
|
||||||
3. **Dinamicos** (2): Bottles, Bottle with Neck
|
|
||||||
4. **Emuladores** (3): Bottle Generator, Filler, Tank
|
|
||||||
5. **Estaticos** (9): Conveyors, Guides, Motors, Curves
|
|
||||||
6. **Extraccion Datos** (2): Template Search, Tag Extraction
|
|
||||||
7. **SensoresComandos** (6): Buttons, Encoders, Photocells, Sensors
|
|
||||||
8. **TagsSignals** (3): Consensus, Analog Tags, Digital Tags
|
|
||||||
9. **Traces** (2): Signal Tracers
|
|
||||||
|
|
||||||
### 🔍 **Debug Patterns That Work (Tested)**
|
|
||||||
```json
|
|
||||||
// Critical errors (high value)
|
|
||||||
{"pattern": "error|exception|fail", "max_lines": 3}
|
|
||||||
|
|
||||||
// Performance issues
|
|
||||||
{"pattern": "fps|slow|memory", "max_lines": 5}
|
|
||||||
|
|
||||||
// Simulation specific
|
|
||||||
{"pattern": "simulation|physics", "max_lines": 5}
|
|
||||||
|
|
||||||
// Object operations
|
|
||||||
{"pattern": "object.*create|update.*error", "max_lines": 3}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ⚡ **Fastest Diagnostic Sequence (Under 10 seconds)**
|
|
||||||
```json
|
|
||||||
// 1. Health check (instant)
|
|
||||||
{"tool": "get_simulation_status", "parameters": {}}
|
|
||||||
|
|
||||||
// 2. Error scan (2-3 seconds)
|
|
||||||
{"tool": "search_debug_log", "parameters": {
|
|
||||||
"pattern": "error|fail", "max_lines": 3
|
|
||||||
}}
|
|
||||||
|
|
||||||
// 3. Process verification if needed (instant)
|
|
||||||
{"tool": "get_ctreditor_status", "parameters": {}}
|
|
||||||
```
|
|
||||||
|
|
||||||
This sequence gives you complete system health in minimal time and tokens.
|
|
103
FREEZE_FIXES.md
103
FREEZE_FIXES.md
|
@ -1,103 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,146 +0,0 @@
|
||||||
# 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]`.
|
|
|
@ -36,6 +36,7 @@ namespace CtrEditor.Services
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
private bool _isRunning;
|
private bool _isRunning;
|
||||||
private readonly object _lockObject = new object();
|
private readonly object _lockObject = new object();
|
||||||
|
private ScreenshotManager _screenshotManager;
|
||||||
|
|
||||||
// Simulation timing tracking
|
// Simulation timing tracking
|
||||||
private readonly Stopwatch _simulationStopwatch;
|
private readonly Stopwatch _simulationStopwatch;
|
||||||
|
@ -58,10 +59,30 @@ namespace CtrEditor.Services
|
||||||
_totalSimulationMilliseconds = 0;
|
_totalSimulationMilliseconds = 0;
|
||||||
_lastSimulationStatus = false;
|
_lastSimulationStatus = false;
|
||||||
|
|
||||||
|
// ScreenshotManager se inicializará de forma lazy cuando se necesite
|
||||||
|
_screenshotManager = null;
|
||||||
|
|
||||||
// Initialize Python environment
|
// Initialize Python environment
|
||||||
InitializePythonEnvironment();
|
InitializePythonEnvironment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene el ScreenshotManager, inicializándolo si es necesario
|
||||||
|
/// </summary>
|
||||||
|
private ScreenshotManager GetScreenshotManager()
|
||||||
|
{
|
||||||
|
if (_screenshotManager == null)
|
||||||
|
{
|
||||||
|
var canvas = _mainViewModel?.MainCanvas;
|
||||||
|
if (canvas == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Canvas no está disponible. Asegúrate de que la UI esté completamente cargada.");
|
||||||
|
}
|
||||||
|
_screenshotManager = new ScreenshotManager(_mainViewModel, canvas);
|
||||||
|
}
|
||||||
|
return _screenshotManager;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inicia el servidor MCP TCP
|
/// Inicia el servidor MCP TCP
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -209,6 +230,7 @@ namespace CtrEditor.Services
|
||||||
new { name = "get_simulation_status", description = "Get current simulation status" },
|
new { name = "get_simulation_status", description = "Get current simulation status" },
|
||||||
new { name = "get_plc_status", description = "Get PLC connection status" },
|
new { name = "get_plc_status", description = "Get PLC connection status" },
|
||||||
new { name = "take_screenshot", description = "Take a screenshot of the canvas" },
|
new { name = "take_screenshot", description = "Take a screenshot of the canvas" },
|
||||||
|
new { name = "take_object_screenshot", description = "Take a screenshot of specific objects by their IDs" },
|
||||||
new { name = "save_project", description = "Save the current project" },
|
new { name = "save_project", description = "Save the current project" },
|
||||||
new { name = "reset_simulation_timing", description = "Reset simulation timing counters" }
|
new { name = "reset_simulation_timing", description = "Reset simulation timing counters" }
|
||||||
}
|
}
|
||||||
|
@ -340,10 +362,41 @@ namespace CtrEditor.Services
|
||||||
height = new {
|
height = new {
|
||||||
type = "number",
|
type = "number",
|
||||||
description = "Height in meters for partial capture (optional)"
|
description = "Height in meters for partial capture (optional)"
|
||||||
|
},
|
||||||
|
center_coordinates = new {
|
||||||
|
type = "boolean",
|
||||||
|
description = "If true, x and y parameters represent the center of the capture area instead of top-left corner. Defaults to false for backward compatibility."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
new {
|
||||||
|
name = "take_object_screenshot",
|
||||||
|
description = "Take a screenshot of specific objects by their IDs. This automatically calculates the bounding box of the specified objects and captures only that area with optional padding.",
|
||||||
|
inputSchema = new {
|
||||||
|
type = "object",
|
||||||
|
properties = new {
|
||||||
|
object_ids = new {
|
||||||
|
type = "array",
|
||||||
|
items = new { type = "string" },
|
||||||
|
description = "Array of object IDs to capture. Can be a single object or multiple objects."
|
||||||
|
},
|
||||||
|
padding_meters = new {
|
||||||
|
type = "number",
|
||||||
|
description = "Additional padding around objects in meters. Defaults to 0.5 meters."
|
||||||
|
},
|
||||||
|
filename = new {
|
||||||
|
type = "string",
|
||||||
|
description = "Optional filename for the screenshot. Defaults to timestamp-based name."
|
||||||
|
},
|
||||||
|
include_background = new {
|
||||||
|
type = "boolean",
|
||||||
|
description = "Whether to include canvas background image. Defaults to false."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required = new[] { "object_ids" }
|
||||||
|
}
|
||||||
|
},
|
||||||
new {
|
new {
|
||||||
name = "start_simulation",
|
name = "start_simulation",
|
||||||
description = "Start the physics simulation",
|
description = "Start the physics simulation",
|
||||||
|
@ -476,6 +529,7 @@ namespace CtrEditor.Services
|
||||||
"get_simulation_status" => GetSimulationStatus(),
|
"get_simulation_status" => GetSimulationStatus(),
|
||||||
"get_plc_status" => GetPlcStatus(),
|
"get_plc_status" => GetPlcStatus(),
|
||||||
"take_screenshot" => TakeScreenshot(arguments),
|
"take_screenshot" => TakeScreenshot(arguments),
|
||||||
|
"take_object_screenshot" => TakeObjectScreenshot(arguments),
|
||||||
"save_project" => SaveProject(),
|
"save_project" => SaveProject(),
|
||||||
"reset_simulation_timing" => ResetSimulationTiming(),
|
"reset_simulation_timing" => ResetSimulationTiming(),
|
||||||
"execute_python" => ExecutePython(arguments),
|
"execute_python" => ExecutePython(arguments),
|
||||||
|
@ -966,72 +1020,85 @@ namespace CtrEditor.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toma una captura de pantalla del canvas
|
/// Toma una captura de pantalla del canvas usando el nuevo ScreenshotManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private object TakeScreenshot(JObject arguments)
|
private object TakeScreenshot(JObject arguments)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Parámetros de screenshot
|
var filename = arguments["filename"]?.ToString();
|
||||||
var filename = arguments["filename"]?.ToString() ?? $"screenshot_{DateTime.Now:yyyyMMdd_HHmmss}.png";
|
var includeBackground = arguments["include_background"]?.ToObject<bool>() ?? false;
|
||||||
var includeBackground = arguments["include_background"]?.ToObject<bool>() ?? false; // Por defecto false
|
var saveFile = arguments["save_file"]?.ToObject<bool>() ?? true;
|
||||||
|
var returnBase64 = arguments["return_base64"]?.ToObject<bool>() ?? true;
|
||||||
|
|
||||||
var x = arguments["x"]?.ToObject<float?>();
|
var x = arguments["x"]?.ToObject<float?>();
|
||||||
var y = arguments["y"]?.ToObject<float?>();
|
var y = arguments["y"]?.ToObject<float?>();
|
||||||
var width = arguments["width"]?.ToObject<float?>();
|
var width = arguments["width"]?.ToObject<float?>();
|
||||||
var height = arguments["height"]?.ToObject<float?>();
|
var height = arguments["height"]?.ToObject<float?>();
|
||||||
|
var centerCoordinates = arguments["center_coordinates"]?.ToObject<bool>() ?? false;
|
||||||
|
|
||||||
// Asegurar extensión .png
|
// Obtener ScreenshotManager de forma lazy
|
||||||
if (!filename.ToLower().EndsWith(".png"))
|
var screenshotManager = GetScreenshotManager();
|
||||||
filename += ".png";
|
ScreenshotResult result;
|
||||||
|
|
||||||
// Crear subdirectorio screenshots
|
if (x.HasValue && y.HasValue && width.HasValue && height.HasValue)
|
||||||
var screenshotsDir = System.IO.Path.Combine(EstadoPersistente.Instance.directorio, "screenshots");
|
{
|
||||||
Directory.CreateDirectory(screenshotsDir);
|
// Captura de área específica
|
||||||
|
if (centerCoordinates)
|
||||||
// Obtener ruta completa - siempre en subdirectorio screenshots a menos que sea ruta absoluta
|
{
|
||||||
var fullPath = System.IO.Path.IsPathRooted(filename) ? filename : System.IO.Path.Combine(screenshotsDir, filename);
|
result = screenshotManager.CaptureCenteredArea(
|
||||||
|
x.Value, y.Value, width.Value, height.Value,
|
||||||
// Obtener información del canvas para detalles
|
filename, includeBackground, saveFile, returnBase64);
|
||||||
var canvas = _mainViewModel.MainCanvas;
|
}
|
||||||
var canvasWidth = canvas?.ActualWidth ?? 0;
|
else
|
||||||
var canvasHeight = canvas?.ActualHeight ?? 0;
|
{
|
||||||
var canvasWidthMeters = PixelToMeter.Instance.calc.PixelsToMeters((float)canvasWidth);
|
result = screenshotManager.CaptureArea(
|
||||||
var canvasHeightMeters = PixelToMeter.Instance.calc.PixelsToMeters((float)canvasHeight);
|
x.Value, y.Value, width.Value, height.Value,
|
||||||
|
filename, includeBackground, saveFile, returnBase64);
|
||||||
// Tomar screenshot
|
}
|
||||||
var success = TakeCanvasScreenshot(fullPath, includeBackground, x, y, width, height);
|
}
|
||||||
|
else
|
||||||
if (success)
|
{
|
||||||
|
// Captura de todo el canvas
|
||||||
|
result = screenshotManager.CaptureFullCanvas(
|
||||||
|
filename, includeBackground, saveFile, returnBase64);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Success)
|
||||||
{
|
{
|
||||||
var fileInfo = new FileInfo(fullPath);
|
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
success = true,
|
success = true,
|
||||||
filename = System.IO.Path.GetFileName(fullPath),
|
filename = result.FileName,
|
||||||
full_path = fullPath,
|
full_path = result.FilePath,
|
||||||
directory = System.IO.Path.GetDirectoryName(fullPath),
|
directory = result.Directory,
|
||||||
file_size_bytes = fileInfo.Length,
|
file_size_bytes = result.FileSizeBytes,
|
||||||
timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
timestamp = result.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
message = "Screenshot saved successfully",
|
message = "Screenshot saved successfully",
|
||||||
|
base64_data = returnBase64 ? result.Base64Data : null,
|
||||||
canvas_info = new
|
canvas_info = new
|
||||||
{
|
{
|
||||||
canvas_width_pixels = canvasWidth,
|
canvas_width_pixels = _mainViewModel.MainCanvas?.ActualWidth ?? 0,
|
||||||
canvas_height_pixels = canvasHeight,
|
canvas_height_pixels = _mainViewModel.MainCanvas?.ActualHeight ?? 0,
|
||||||
canvas_width_meters = Math.Round(canvasWidthMeters, 3),
|
canvas_width_meters = result.CaptureArea?.Width ?? 0,
|
||||||
canvas_height_meters = Math.Round(canvasHeightMeters, 3)
|
canvas_height_meters = result.CaptureArea?.Height ?? 0
|
||||||
},
|
},
|
||||||
capture_info = new
|
capture_info = new
|
||||||
{
|
{
|
||||||
include_background = includeBackground,
|
include_background = result.IncludeBackground,
|
||||||
area_specified = x.HasValue && y.HasValue && width.HasValue && height.HasValue,
|
area_specified = x.HasValue && y.HasValue && width.HasValue && height.HasValue,
|
||||||
area = x.HasValue ? new {
|
area = result.CaptureArea != null ? new
|
||||||
x = Math.Round(x.Value, 3),
|
{
|
||||||
y = Math.Round(y.Value, 3),
|
x = Math.Round(result.CaptureArea.Left, 3),
|
||||||
width = Math.Round(width.Value, 3),
|
y = Math.Round(result.CaptureArea.Top, 3),
|
||||||
height = Math.Round(height.Value, 3),
|
width = Math.Round(result.CaptureArea.Width, 3),
|
||||||
|
height = Math.Round(result.CaptureArea.Height, 3),
|
||||||
|
center_x = Math.Round(result.CaptureArea.CenterX, 3),
|
||||||
|
center_y = Math.Round(result.CaptureArea.CenterY, 3),
|
||||||
units = "meters"
|
units = "meters"
|
||||||
} : null,
|
} : null,
|
||||||
capture_type = x.HasValue ? "partial" : "full_canvas"
|
capture_type = result.CaptureType.ToString().ToLower(),
|
||||||
|
center_coordinates = centerCoordinates
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1040,14 +1107,15 @@ namespace CtrEditor.Services
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
success = false,
|
success = false,
|
||||||
error = "Failed to capture screenshot",
|
error = result.ErrorMessage,
|
||||||
attempted_path = fullPath
|
error_type = result.ErrorType
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new {
|
return new
|
||||||
|
{
|
||||||
success = false,
|
success = false,
|
||||||
error = ex.Message,
|
error = ex.Message,
|
||||||
error_type = ex.GetType().Name
|
error_type = ex.GetType().Name
|
||||||
|
@ -1076,156 +1144,96 @@ namespace CtrEditor.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Screenshot Implementation
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toma una captura de pantalla del canvas
|
/// Toma una captura de pantalla de objetos específicos por sus IDs usando el nuevo ScreenshotManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool TakeCanvasScreenshot(string filePath, bool includeBackground = false,
|
private object TakeObjectScreenshot(JObject arguments)
|
||||||
float? x = null, float? y = null, float? width = null, float? height = null)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var canvas = _mainViewModel.MainCanvas;
|
var objectIds = arguments["object_ids"]?.ToObject<string[]>();
|
||||||
if (canvas == null)
|
if (objectIds == null || objectIds.Length == 0)
|
||||||
|
throw new ArgumentException("object_ids is required and must not be empty");
|
||||||
|
|
||||||
|
var paddingMeters = arguments["padding_meters"]?.ToObject<float>() ?? 0.5f;
|
||||||
|
var filename = arguments["filename"]?.ToString();
|
||||||
|
var includeBackground = arguments["include_background"]?.ToObject<bool>() ?? false;
|
||||||
|
var saveFile = arguments["save_file"]?.ToObject<bool>() ?? true;
|
||||||
|
var returnBase64 = arguments["return_base64"]?.ToObject<bool>() ?? true;
|
||||||
|
|
||||||
|
// Obtener ScreenshotManager de forma lazy y usar el nuevo método
|
||||||
|
var screenshotManager = GetScreenshotManager();
|
||||||
|
var result = screenshotManager.CaptureObjects(
|
||||||
|
objectIds, paddingMeters, filename, includeBackground, saveFile, returnBase64);
|
||||||
|
|
||||||
|
if (result.Success)
|
||||||
{
|
{
|
||||||
Debug.WriteLine("[MCP Server] Canvas is null");
|
return new
|
||||||
return false;
|
{
|
||||||
}
|
success = true,
|
||||||
|
filename = result.FileName,
|
||||||
// Asegurar que el canvas esté renderizado
|
full_path = result.FilePath,
|
||||||
canvas.UpdateLayout();
|
directory = result.Directory,
|
||||||
|
file_size_bytes = result.FileSizeBytes,
|
||||||
// Determinar el área a capturar
|
timestamp = result.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
Rect captureRect;
|
message = "Object screenshot saved successfully",
|
||||||
if (x.HasValue && y.HasValue && width.HasValue && height.HasValue)
|
base64_data = returnBase64 ? result.Base64Data : null,
|
||||||
{
|
captured_objects = result.CapturedObjects.Select(obj => new
|
||||||
// Convertir coordenadas de metros a píxeles
|
{
|
||||||
var pixelX = PixelToMeter.Instance.calc.MetersToPixels(x.Value);
|
id = obj.Id,
|
||||||
var pixelY = PixelToMeter.Instance.calc.MetersToPixels(y.Value);
|
name = obj.Name,
|
||||||
var pixelWidth = PixelToMeter.Instance.calc.MetersToPixels(width.Value);
|
type = obj.Type,
|
||||||
var pixelHeight = PixelToMeter.Instance.calc.MetersToPixels(height.Value);
|
position = new { x = obj.Left, y = obj.Top },
|
||||||
|
center = new { x = obj.CenterX, y = obj.CenterY },
|
||||||
captureRect = new Rect(pixelX, pixelY, pixelWidth, pixelHeight);
|
dimensions = new { width = obj.Width, height = obj.Height }
|
||||||
|
}).ToArray(),
|
||||||
|
bounding_box = new
|
||||||
|
{
|
||||||
|
left = Math.Round(result.BoundingBox.Left, 3),
|
||||||
|
top = Math.Round(result.BoundingBox.Top, 3),
|
||||||
|
right = Math.Round(result.BoundingBox.Left + result.BoundingBox.Width, 3),
|
||||||
|
bottom = Math.Round(result.BoundingBox.Top + result.BoundingBox.Height, 3),
|
||||||
|
center_x = Math.Round(result.BoundingBox.CenterX, 3),
|
||||||
|
center_y = Math.Round(result.BoundingBox.CenterY, 3),
|
||||||
|
width = Math.Round(result.BoundingBox.Width, 3),
|
||||||
|
height = Math.Round(result.BoundingBox.Height, 3)
|
||||||
|
},
|
||||||
|
capture_area = new
|
||||||
|
{
|
||||||
|
left = Math.Round(result.CaptureArea.Left, 3),
|
||||||
|
top = Math.Round(result.CaptureArea.Top, 3),
|
||||||
|
center_x = Math.Round(result.CaptureArea.CenterX, 3),
|
||||||
|
center_y = Math.Round(result.CaptureArea.CenterY, 3),
|
||||||
|
width = Math.Round(result.CaptureArea.Width, 3),
|
||||||
|
height = Math.Round(result.CaptureArea.Height, 3),
|
||||||
|
padding_meters = result.PaddingMeters
|
||||||
|
},
|
||||||
|
capture_info = new
|
||||||
|
{
|
||||||
|
include_background = result.IncludeBackground,
|
||||||
|
capture_type = result.CaptureType.ToString().ToLower(),
|
||||||
|
objects_count = result.CapturedObjects.Count
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Capturar todo el canvas
|
return new
|
||||||
captureRect = new Rect(0, 0, canvas.ActualWidth, canvas.ActualHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validar dimensiones
|
|
||||||
if (captureRect.Width <= 0 || captureRect.Height <= 0)
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"[MCP Server] Invalid capture dimensions: {captureRect}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.WriteLine($"[MCP Server] Capturing area: {captureRect}, Canvas size: {canvas.ActualWidth}x{canvas.ActualHeight}");
|
|
||||||
|
|
||||||
// Crear RenderTargetBitmap con alta resolución
|
|
||||||
// Usar factor de escala para mejorar calidad en capturas parciales
|
|
||||||
var scaleFactor = (x.HasValue && y.HasValue) ? 3.0 : 2.0; // Mayor escala para áreas parciales
|
|
||||||
var renderWidth = Math.Max(1, (int)(captureRect.Width * scaleFactor));
|
|
||||||
var renderHeight = Math.Max(1, (int)(captureRect.Height * scaleFactor));
|
|
||||||
var dpi = 96 * scaleFactor; // Aumentar DPI proporcionalmente
|
|
||||||
|
|
||||||
var renderBitmap = new RenderTargetBitmap(
|
|
||||||
renderWidth,
|
|
||||||
renderHeight,
|
|
||||||
dpi, // DPI X
|
|
||||||
dpi, // DPI Y
|
|
||||||
PixelFormats.Pbgra32);
|
|
||||||
|
|
||||||
// Crear un Canvas temporal para renderizado con escala mejorada
|
|
||||||
var tempCanvas = new Canvas()
|
|
||||||
{
|
|
||||||
Width = captureRect.Width * scaleFactor,
|
|
||||||
Height = captureRect.Height * scaleFactor,
|
|
||||||
Background = includeBackground ? canvas.Background : Brushes.White
|
|
||||||
};
|
|
||||||
|
|
||||||
// Aplicar escala al canvas
|
|
||||||
tempCanvas.RenderTransform = new ScaleTransform(scaleFactor, scaleFactor);
|
|
||||||
|
|
||||||
// Clonar elementos visibles del canvas principal
|
|
||||||
foreach (UIElement child in canvas.Children)
|
|
||||||
{
|
|
||||||
if (child.Visibility == Visibility.Visible)
|
|
||||||
{
|
{
|
||||||
try
|
success = false,
|
||||||
{
|
error = result.ErrorMessage,
|
||||||
// Obtener posición del elemento
|
error_type = result.ErrorType
|
||||||
var left = Canvas.GetLeft(child);
|
};
|
||||||
var top = Canvas.GetTop(child);
|
|
||||||
|
|
||||||
// Verificar si está en el área de captura
|
|
||||||
var elementRect = new Rect(
|
|
||||||
double.IsNaN(left) ? 0 : left,
|
|
||||||
double.IsNaN(top) ? 0 : top,
|
|
||||||
child.RenderSize.Width,
|
|
||||||
child.RenderSize.Height);
|
|
||||||
|
|
||||||
if (captureRect.IntersectsWith(elementRect) || (!x.HasValue && !y.HasValue))
|
|
||||||
{
|
|
||||||
// Crear una representación visual del elemento
|
|
||||||
var visualBrush = new VisualBrush(child)
|
|
||||||
{
|
|
||||||
Stretch = Stretch.None,
|
|
||||||
AlignmentX = AlignmentX.Left,
|
|
||||||
AlignmentY = AlignmentY.Top
|
|
||||||
};
|
|
||||||
|
|
||||||
var rect = new Rectangle()
|
|
||||||
{
|
|
||||||
Width = child.RenderSize.Width * scaleFactor,
|
|
||||||
Height = child.RenderSize.Height * scaleFactor,
|
|
||||||
Fill = visualBrush
|
|
||||||
};
|
|
||||||
|
|
||||||
// Posicionar relativo al área de captura con escala
|
|
||||||
Canvas.SetLeft(rect, (elementRect.X - captureRect.X) * scaleFactor);
|
|
||||||
Canvas.SetTop(rect, (elementRect.Y - captureRect.Y) * scaleFactor);
|
|
||||||
|
|
||||||
tempCanvas.Children.Add(rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"[MCP Server] Error processing child element: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forzar layout del canvas temporal con las nuevas dimensiones escaladas
|
|
||||||
var scaledSize = new Size(captureRect.Width * scaleFactor, captureRect.Height * scaleFactor);
|
|
||||||
tempCanvas.Measure(scaledSize);
|
|
||||||
tempCanvas.Arrange(new Rect(0, 0, scaledSize.Width, scaledSize.Height));
|
|
||||||
tempCanvas.UpdateLayout();
|
|
||||||
|
|
||||||
// Renderizar
|
|
||||||
renderBitmap.Render(tempCanvas);
|
|
||||||
|
|
||||||
// Guardar imagen
|
|
||||||
var encoder = new PngBitmapEncoder();
|
|
||||||
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
|
|
||||||
|
|
||||||
Directory.CreateDirectory(System.IO.Path.GetDirectoryName(filePath));
|
|
||||||
using (var fileStream = new FileStream(filePath, FileMode.Create))
|
|
||||||
{
|
|
||||||
encoder.Save(fileStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.WriteLine($"[MCP Server] Screenshot saved successfully: {filePath}");
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"[MCP Server] Error taking screenshot: {ex.Message}");
|
return new
|
||||||
Debug.WriteLine($"[MCP Server] Stack trace: {ex.StackTrace}");
|
{
|
||||||
return false;
|
success = false,
|
||||||
|
error = ex.Message,
|
||||||
|
error_type = ex.GetType().Name
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,625 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using CtrEditor.ObjetosSim;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace CtrEditor.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gestor centralizado de capturas de pantalla del canvas.
|
||||||
|
/// Proporciona funcionalidades para capturar:
|
||||||
|
/// 1. Objetos específicos por ID
|
||||||
|
/// 2. Áreas específicas en coordenadas de metros
|
||||||
|
/// 3. Todo el canvas
|
||||||
|
///
|
||||||
|
/// Utiliza la misma lógica que ObjectManipulationManager para calcular
|
||||||
|
/// las dimensiones reales de los objetos en el canvas.
|
||||||
|
/// </summary>
|
||||||
|
public class ScreenshotManager
|
||||||
|
{
|
||||||
|
private readonly MainViewModel _mainViewModel;
|
||||||
|
private readonly Canvas _canvas;
|
||||||
|
private readonly string _defaultScreenshotsDirectory;
|
||||||
|
|
||||||
|
public ScreenshotManager(MainViewModel mainViewModel, Canvas canvas)
|
||||||
|
{
|
||||||
|
_mainViewModel = mainViewModel ?? throw new ArgumentNullException(nameof(mainViewModel));
|
||||||
|
_canvas = canvas ?? throw new ArgumentNullException(nameof(canvas));
|
||||||
|
_defaultScreenshotsDirectory = System.IO.Path.Combine(EstadoPersistente.Instance.directorio, "screenshots");
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captura screenshot de objetos específicos por sus IDs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objectIds">Array de IDs de objetos a capturar</param>
|
||||||
|
/// <param name="paddingMeters">Padding adicional alrededor de los objetos en metros</param>
|
||||||
|
/// <param name="filename">Nombre del archivo (opcional)</param>
|
||||||
|
/// <param name="includeBackground">Si incluir imagen de fondo</param>
|
||||||
|
/// <param name="saveToFile">Si guardar el archivo</param>
|
||||||
|
/// <param name="returnBase64">Si retornar la imagen como base64</param>
|
||||||
|
/// <returns>Resultado de la captura</returns>
|
||||||
|
public ScreenshotResult CaptureObjects(
|
||||||
|
string[] objectIds,
|
||||||
|
float paddingMeters = 0.5f,
|
||||||
|
string filename = null,
|
||||||
|
bool includeBackground = false,
|
||||||
|
bool saveToFile = true,
|
||||||
|
bool returnBase64 = true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (objectIds == null || objectIds.Length == 0)
|
||||||
|
throw new ArgumentException("objectIds no puede estar vacío");
|
||||||
|
|
||||||
|
// Buscar objetos por ID
|
||||||
|
var targetObjects = FindObjectsByIds(objectIds);
|
||||||
|
if (!targetObjects.Any())
|
||||||
|
throw new ArgumentException($"No se encontraron objetos visibles con los IDs: {string.Join(", ", objectIds)}");
|
||||||
|
|
||||||
|
// Calcular bounding box usando la misma lógica que ObjectManipulationManager
|
||||||
|
var boundingBox = CalculateObjectsBoundingBox(targetObjects);
|
||||||
|
|
||||||
|
// Aplicar padding
|
||||||
|
var captureArea = new ScreenshotArea
|
||||||
|
{
|
||||||
|
Left = boundingBox.Left - paddingMeters,
|
||||||
|
Top = boundingBox.Top - paddingMeters,
|
||||||
|
Width = boundingBox.Width + (paddingMeters * 2),
|
||||||
|
Height = boundingBox.Height + (paddingMeters * 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Asegurar dimensiones mínimas
|
||||||
|
captureArea.Width = Math.Max(captureArea.Width, 0.1f);
|
||||||
|
captureArea.Height = Math.Max(captureArea.Height, 0.1f);
|
||||||
|
|
||||||
|
// Capturar la imagen
|
||||||
|
var bitmap = CaptureCanvasArea(captureArea, includeBackground);
|
||||||
|
|
||||||
|
// Preparar resultado
|
||||||
|
var result = new ScreenshotResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Bitmap = bitmap,
|
||||||
|
CaptureType = ScreenshotType.Objects,
|
||||||
|
CapturedObjects = targetObjects.Select(obj => new CapturedObjectInfo
|
||||||
|
{
|
||||||
|
Id = obj.Id.Value.ToString(),
|
||||||
|
Name = obj.Nombre,
|
||||||
|
Type = obj.GetType().Name,
|
||||||
|
Left = obj.Left,
|
||||||
|
Top = obj.Top,
|
||||||
|
Width = obj.Ancho,
|
||||||
|
Height = obj.Alto,
|
||||||
|
CenterX = obj.Left + obj.Ancho / 2,
|
||||||
|
CenterY = obj.Top + obj.Alto / 2
|
||||||
|
}).ToList(),
|
||||||
|
BoundingBox = new AreaInfo
|
||||||
|
{
|
||||||
|
Left = boundingBox.Left,
|
||||||
|
Top = boundingBox.Top,
|
||||||
|
Width = boundingBox.Width,
|
||||||
|
Height = boundingBox.Height,
|
||||||
|
CenterX = boundingBox.Left + boundingBox.Width / 2,
|
||||||
|
CenterY = boundingBox.Top + boundingBox.Height / 2
|
||||||
|
},
|
||||||
|
CaptureArea = new AreaInfo
|
||||||
|
{
|
||||||
|
Left = captureArea.Left,
|
||||||
|
Top = captureArea.Top,
|
||||||
|
Width = captureArea.Width,
|
||||||
|
Height = captureArea.Height,
|
||||||
|
CenterX = captureArea.Left + captureArea.Width / 2,
|
||||||
|
CenterY = captureArea.Top + captureArea.Height / 2
|
||||||
|
},
|
||||||
|
PaddingMeters = paddingMeters,
|
||||||
|
IncludeBackground = includeBackground
|
||||||
|
};
|
||||||
|
|
||||||
|
// Guardar archivo y/o generar base64
|
||||||
|
return ProcessScreenshotOutput(result, filename, saveToFile, returnBase64);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new ScreenshotResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = ex.Message,
|
||||||
|
ErrorType = ex.GetType().Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captura screenshot de un área específica en coordenadas de metros
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">Coordenada X izquierda en metros</param>
|
||||||
|
/// <param name="top">Coordenada Y superior en metros</param>
|
||||||
|
/// <param name="width">Ancho en metros</param>
|
||||||
|
/// <param name="height">Alto en metros</param>
|
||||||
|
/// <param name="filename">Nombre del archivo (opcional)</param>
|
||||||
|
/// <param name="includeBackground">Si incluir imagen de fondo</param>
|
||||||
|
/// <param name="saveToFile">Si guardar el archivo</param>
|
||||||
|
/// <param name="returnBase64">Si retornar la imagen como base64</param>
|
||||||
|
/// <returns>Resultado de la captura</returns>
|
||||||
|
public ScreenshotResult CaptureArea(
|
||||||
|
float left,
|
||||||
|
float top,
|
||||||
|
float width,
|
||||||
|
float height,
|
||||||
|
string filename = null,
|
||||||
|
bool includeBackground = false,
|
||||||
|
bool saveToFile = true,
|
||||||
|
bool returnBase64 = true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (width <= 0 || height <= 0)
|
||||||
|
throw new ArgumentException("Width y height deben ser mayores que 0");
|
||||||
|
|
||||||
|
var captureArea = new ScreenshotArea
|
||||||
|
{
|
||||||
|
Left = left,
|
||||||
|
Top = top,
|
||||||
|
Width = width,
|
||||||
|
Height = height
|
||||||
|
};
|
||||||
|
|
||||||
|
var bitmap = CaptureCanvasArea(captureArea, includeBackground);
|
||||||
|
|
||||||
|
var result = new ScreenshotResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Bitmap = bitmap,
|
||||||
|
CaptureType = ScreenshotType.Area,
|
||||||
|
CaptureArea = new AreaInfo
|
||||||
|
{
|
||||||
|
Left = left,
|
||||||
|
Top = top,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
CenterX = left + width / 2,
|
||||||
|
CenterY = top + height / 2
|
||||||
|
},
|
||||||
|
IncludeBackground = includeBackground
|
||||||
|
};
|
||||||
|
|
||||||
|
return ProcessScreenshotOutput(result, filename, saveToFile, returnBase64);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new ScreenshotResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = ex.Message,
|
||||||
|
ErrorType = ex.GetType().Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captura screenshot de un área centrada en coordenadas específicas
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="centerX">Coordenada X del centro en metros</param>
|
||||||
|
/// <param name="centerY">Coordenada Y del centro en metros</param>
|
||||||
|
/// <param name="width">Ancho en metros</param>
|
||||||
|
/// <param name="height">Alto en metros</param>
|
||||||
|
/// <param name="filename">Nombre del archivo (opcional)</param>
|
||||||
|
/// <param name="includeBackground">Si incluir imagen de fondo</param>
|
||||||
|
/// <param name="saveToFile">Si guardar el archivo</param>
|
||||||
|
/// <param name="returnBase64">Si retornar la imagen como base64</param>
|
||||||
|
/// <returns>Resultado de la captura</returns>
|
||||||
|
public ScreenshotResult CaptureCenteredArea(
|
||||||
|
float centerX,
|
||||||
|
float centerY,
|
||||||
|
float width,
|
||||||
|
float height,
|
||||||
|
string filename = null,
|
||||||
|
bool includeBackground = false,
|
||||||
|
bool saveToFile = true,
|
||||||
|
bool returnBase64 = true)
|
||||||
|
{
|
||||||
|
var left = centerX - width / 2;
|
||||||
|
var top = centerY - height / 2;
|
||||||
|
|
||||||
|
return CaptureArea(left, top, width, height, filename, includeBackground, saveToFile, returnBase64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captura screenshot de todo el canvas
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">Nombre del archivo (opcional)</param>
|
||||||
|
/// <param name="includeBackground">Si incluir imagen de fondo</param>
|
||||||
|
/// <param name="saveToFile">Si guardar el archivo</param>
|
||||||
|
/// <param name="returnBase64">Si retornar la imagen como base64</param>
|
||||||
|
/// <returns>Resultado de la captura</returns>
|
||||||
|
public ScreenshotResult CaptureFullCanvas(
|
||||||
|
string filename = null,
|
||||||
|
bool includeBackground = true,
|
||||||
|
bool saveToFile = true,
|
||||||
|
bool returnBase64 = true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bitmap = CaptureEntireCanvas(includeBackground);
|
||||||
|
|
||||||
|
var canvasWidthMeters = PixelToMeter.Instance.calc.PixelsToMeters((float)_canvas.ActualWidth);
|
||||||
|
var canvasHeightMeters = PixelToMeter.Instance.calc.PixelsToMeters((float)_canvas.ActualHeight);
|
||||||
|
|
||||||
|
var result = new ScreenshotResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Bitmap = bitmap,
|
||||||
|
CaptureType = ScreenshotType.FullCanvas,
|
||||||
|
CaptureArea = new AreaInfo
|
||||||
|
{
|
||||||
|
Left = 0,
|
||||||
|
Top = 0,
|
||||||
|
Width = canvasWidthMeters,
|
||||||
|
Height = canvasHeightMeters,
|
||||||
|
CenterX = canvasWidthMeters / 2,
|
||||||
|
CenterY = canvasHeightMeters / 2
|
||||||
|
},
|
||||||
|
IncludeBackground = includeBackground
|
||||||
|
};
|
||||||
|
|
||||||
|
return ProcessScreenshotOutput(result, filename, saveToFile, returnBase64);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new ScreenshotResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = ex.Message,
|
||||||
|
ErrorType = ex.GetType().Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Helper Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Busca objetos por sus IDs
|
||||||
|
/// </summary>
|
||||||
|
private List<osBase> FindObjectsByIds(string[] objectIds)
|
||||||
|
{
|
||||||
|
var allObjects = _mainViewModel.ObjetosSimulables.ToList();
|
||||||
|
var result = new List<osBase>();
|
||||||
|
|
||||||
|
foreach (var objectId in objectIds)
|
||||||
|
{
|
||||||
|
var obj = allObjects.FirstOrDefault(o => o.Id.Value.ToString() == objectId);
|
||||||
|
if (obj != null && obj.Show_On_This_Page)
|
||||||
|
{
|
||||||
|
result.Add(obj);
|
||||||
|
Console.WriteLine($"DEBUG: Object {objectId} ADDED - Type: {obj.GetType().Name}, Left: {obj.Left}, Top: {obj.Top}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"DEBUG: Object {objectId} REJECTED - Found: {obj != null}, Show_On_This_Page: {obj?.Show_On_This_Page}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"DEBUG: Total objects found: {result.Count}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calcula el bounding box de un conjunto de objetos usando coordenadas directas
|
||||||
|
/// </summary>
|
||||||
|
private ScreenshotArea CalculateObjectsBoundingBox(List<osBase> objects)
|
||||||
|
{
|
||||||
|
if (!objects.Any())
|
||||||
|
throw new ArgumentException("La lista de objetos no puede estar vacía");
|
||||||
|
|
||||||
|
float left = float.MaxValue;
|
||||||
|
float top = float.MaxValue;
|
||||||
|
float right = float.MinValue;
|
||||||
|
float bottom = float.MinValue;
|
||||||
|
|
||||||
|
foreach (var obj in objects)
|
||||||
|
{
|
||||||
|
// Usar coordenadas directas del objeto (ya están en metros)
|
||||||
|
float objLeft = obj.Left;
|
||||||
|
float objTop = obj.Top;
|
||||||
|
float objRight = obj.Left + obj.Ancho;
|
||||||
|
float objBottom = obj.Top + obj.Alto;
|
||||||
|
|
||||||
|
left = Math.Min(left, objLeft);
|
||||||
|
top = Math.Min(top, objTop);
|
||||||
|
right = Math.Max(right, objRight);
|
||||||
|
bottom = Math.Max(bottom, objBottom);
|
||||||
|
|
||||||
|
Console.WriteLine($"DEBUG: Object {obj.Id.Value} bounds - L:{objLeft} T:{objTop} R:{objRight} B:{objBottom}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left == float.MaxValue) // No se encontraron objetos válidos
|
||||||
|
throw new InvalidOperationException("No se encontraron objetos válidos para calcular el bounding box");
|
||||||
|
|
||||||
|
Console.WriteLine($"DEBUG: Final bounding box - L:{left} T:{top} W:{right - left} H:{bottom - top}");
|
||||||
|
|
||||||
|
return new ScreenshotArea
|
||||||
|
{
|
||||||
|
Left = left,
|
||||||
|
Top = top,
|
||||||
|
Width = right - left,
|
||||||
|
Height = bottom - top
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captura un área específica del canvas
|
||||||
|
/// </summary>
|
||||||
|
private RenderTargetBitmap CaptureCanvasArea(ScreenshotArea area, bool includeBackground)
|
||||||
|
{
|
||||||
|
// Asegurar que el canvas esté actualizado
|
||||||
|
_canvas.UpdateLayout();
|
||||||
|
|
||||||
|
// Convertir área de metros a píxeles
|
||||||
|
var pixelLeft = PixelToMeter.Instance.calc.MetersToPixels(area.Left);
|
||||||
|
var pixelTop = PixelToMeter.Instance.calc.MetersToPixels(area.Top);
|
||||||
|
var pixelWidth = PixelToMeter.Instance.calc.MetersToPixels(area.Width);
|
||||||
|
var pixelHeight = PixelToMeter.Instance.calc.MetersToPixels(area.Height);
|
||||||
|
|
||||||
|
var captureRect = new Rect(pixelLeft, pixelTop, pixelWidth, pixelHeight);
|
||||||
|
|
||||||
|
// Validar que el área esté dentro del canvas
|
||||||
|
var canvasRect = new Rect(0, 0, _canvas.ActualWidth, _canvas.ActualHeight);
|
||||||
|
if (!canvasRect.IntersectsWith(captureRect))
|
||||||
|
throw new ArgumentException("El área especificada está fuera del canvas");
|
||||||
|
|
||||||
|
// Intersectar con el canvas para evitar áreas fuera de límites
|
||||||
|
captureRect.Intersect(canvasRect);
|
||||||
|
|
||||||
|
return CaptureCanvasRect(captureRect, includeBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captura todo el canvas
|
||||||
|
/// </summary>
|
||||||
|
private RenderTargetBitmap CaptureEntireCanvas(bool includeBackground)
|
||||||
|
{
|
||||||
|
_canvas.UpdateLayout();
|
||||||
|
var canvasRect = new Rect(0, 0, _canvas.ActualWidth, _canvas.ActualHeight);
|
||||||
|
return CaptureCanvasRect(canvasRect, includeBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captura un rectángulo específico del canvas en coordenadas de píxeles
|
||||||
|
/// </summary>
|
||||||
|
private RenderTargetBitmap CaptureCanvasRect(Rect captureRect, bool includeBackground)
|
||||||
|
{
|
||||||
|
if (captureRect.Width <= 0 || captureRect.Height <= 0)
|
||||||
|
throw new ArgumentException("Las dimensiones de captura deben ser mayores que 0");
|
||||||
|
|
||||||
|
// Configurar escala para alta calidad
|
||||||
|
var scaleFactor = 2.0; // Factor de escala para mejor calidad
|
||||||
|
var renderWidth = Math.Max(1, (int)(captureRect.Width * scaleFactor));
|
||||||
|
var renderHeight = Math.Max(1, (int)(captureRect.Height * scaleFactor));
|
||||||
|
var dpi = 96 * scaleFactor;
|
||||||
|
|
||||||
|
var renderBitmap = new RenderTargetBitmap(
|
||||||
|
renderWidth,
|
||||||
|
renderHeight,
|
||||||
|
dpi,
|
||||||
|
dpi,
|
||||||
|
PixelFormats.Pbgra32);
|
||||||
|
|
||||||
|
// Crear canvas temporal para renderizado
|
||||||
|
var tempCanvas = new Canvas()
|
||||||
|
{
|
||||||
|
Width = captureRect.Width * scaleFactor,
|
||||||
|
Height = captureRect.Height * scaleFactor,
|
||||||
|
Background = includeBackground ? _canvas.Background : Brushes.White
|
||||||
|
};
|
||||||
|
|
||||||
|
tempCanvas.RenderTransform = new ScaleTransform(scaleFactor, scaleFactor);
|
||||||
|
|
||||||
|
// Clonar elementos visibles que intersectan con el área de captura
|
||||||
|
foreach (UIElement child in _canvas.Children)
|
||||||
|
{
|
||||||
|
if (child.Visibility == Visibility.Visible)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var left = Canvas.GetLeft(child);
|
||||||
|
var top = Canvas.GetTop(child);
|
||||||
|
|
||||||
|
var elementRect = new Rect(
|
||||||
|
double.IsNaN(left) ? 0 : left,
|
||||||
|
double.IsNaN(top) ? 0 : top,
|
||||||
|
child.RenderSize.Width,
|
||||||
|
child.RenderSize.Height);
|
||||||
|
|
||||||
|
if (captureRect.IntersectsWith(elementRect))
|
||||||
|
{
|
||||||
|
var visualBrush = new VisualBrush(child)
|
||||||
|
{
|
||||||
|
Stretch = Stretch.None,
|
||||||
|
AlignmentX = AlignmentX.Left,
|
||||||
|
AlignmentY = AlignmentY.Top
|
||||||
|
};
|
||||||
|
|
||||||
|
var rect = new Rectangle()
|
||||||
|
{
|
||||||
|
Width = child.RenderSize.Width * scaleFactor,
|
||||||
|
Height = child.RenderSize.Height * scaleFactor,
|
||||||
|
Fill = visualBrush
|
||||||
|
};
|
||||||
|
|
||||||
|
Canvas.SetLeft(rect, (elementRect.X - captureRect.X) * scaleFactor);
|
||||||
|
Canvas.SetTop(rect, (elementRect.Y - captureRect.Y) * scaleFactor);
|
||||||
|
|
||||||
|
tempCanvas.Children.Add(rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"[ScreenshotManager] Error procesando elemento: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderizar canvas temporal
|
||||||
|
var scaledSize = new Size(captureRect.Width * scaleFactor, captureRect.Height * scaleFactor);
|
||||||
|
tempCanvas.Measure(scaledSize);
|
||||||
|
tempCanvas.Arrange(new Rect(0, 0, scaledSize.Width, scaledSize.Height));
|
||||||
|
tempCanvas.UpdateLayout();
|
||||||
|
|
||||||
|
renderBitmap.Render(tempCanvas);
|
||||||
|
return renderBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Procesa la salida del screenshot (guardar archivo y/o generar base64)
|
||||||
|
/// </summary>
|
||||||
|
private ScreenshotResult ProcessScreenshotOutput(ScreenshotResult result, string filename, bool saveToFile, bool returnBase64)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Generar nombre de archivo si no se proporcionó
|
||||||
|
if (string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
var typePrefix = result.CaptureType.ToString().ToLower();
|
||||||
|
filename = $"{typePrefix}_screenshot_{DateTime.Now:yyyyMMdd_HHmmss}.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asegurar extensión .png
|
||||||
|
if (!filename.ToLower().EndsWith(".png"))
|
||||||
|
filename += ".png";
|
||||||
|
|
||||||
|
// Guardar archivo si se solicita
|
||||||
|
if (saveToFile)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_defaultScreenshotsDirectory);
|
||||||
|
var fullPath = System.IO.Path.IsPathRooted(filename) ? filename : System.IO.Path.Combine(_defaultScreenshotsDirectory, filename);
|
||||||
|
|
||||||
|
var encoder = new PngBitmapEncoder();
|
||||||
|
encoder.Frames.Add(BitmapFrame.Create(result.Bitmap));
|
||||||
|
|
||||||
|
using (var fileStream = new FileStream(fullPath, FileMode.Create))
|
||||||
|
{
|
||||||
|
encoder.Save(fileStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfo = new FileInfo(fullPath);
|
||||||
|
result.FilePath = fullPath;
|
||||||
|
result.FileName = System.IO.Path.GetFileName(fullPath);
|
||||||
|
result.Directory = System.IO.Path.GetDirectoryName(fullPath);
|
||||||
|
result.FileSizeBytes = fileInfo.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generar base64 si se solicita
|
||||||
|
if (returnBase64)
|
||||||
|
{
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
var encoder = new PngBitmapEncoder();
|
||||||
|
encoder.Frames.Add(BitmapFrame.Create(result.Bitmap));
|
||||||
|
encoder.Save(memoryStream);
|
||||||
|
result.Base64Data = Convert.ToBase64String(memoryStream.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Timestamp = DateTime.Now;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Success = false;
|
||||||
|
result.ErrorMessage = $"Error procesando salida: {ex.Message}";
|
||||||
|
result.ErrorType = ex.GetType().Name;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Data Classes
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resultado de una operación de screenshot
|
||||||
|
/// </summary>
|
||||||
|
public class ScreenshotResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
public string ErrorType { get; set; }
|
||||||
|
public RenderTargetBitmap Bitmap { get; set; }
|
||||||
|
public ScreenshotType CaptureType { get; set; }
|
||||||
|
public string FilePath { get; set; }
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public string Directory { get; set; }
|
||||||
|
public long FileSizeBytes { get; set; }
|
||||||
|
public string Base64Data { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public bool IncludeBackground { get; set; }
|
||||||
|
public List<CapturedObjectInfo> CapturedObjects { get; set; } = new List<CapturedObjectInfo>();
|
||||||
|
public AreaInfo BoundingBox { get; set; }
|
||||||
|
public AreaInfo CaptureArea { get; set; }
|
||||||
|
public float PaddingMeters { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Información de un objeto capturado
|
||||||
|
/// </summary>
|
||||||
|
public class CapturedObjectInfo
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public float Left { get; set; }
|
||||||
|
public float Top { get; set; }
|
||||||
|
public float Width { get; set; }
|
||||||
|
public float Height { get; set; }
|
||||||
|
public float CenterX { get; set; }
|
||||||
|
public float CenterY { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Información de un área
|
||||||
|
/// </summary>
|
||||||
|
public class AreaInfo
|
||||||
|
{
|
||||||
|
public float Left { get; set; }
|
||||||
|
public float Top { get; set; }
|
||||||
|
public float Width { get; set; }
|
||||||
|
public float Height { get; set; }
|
||||||
|
public float CenterX { get; set; }
|
||||||
|
public float CenterY { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Área de screenshot en coordenadas de metros
|
||||||
|
/// </summary>
|
||||||
|
internal class ScreenshotArea
|
||||||
|
{
|
||||||
|
public float Left { get; set; }
|
||||||
|
public float Top { get; set; }
|
||||||
|
public float Width { get; set; }
|
||||||
|
public float Height { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tipos de captura de screenshot
|
||||||
|
/// </summary>
|
||||||
|
public enum ScreenshotType
|
||||||
|
{
|
||||||
|
Objects,
|
||||||
|
Area,
|
||||||
|
FullCanvas
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
|
@ -276,7 +276,9 @@ def analyze_all_screenshots(screenshots_dir):
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Directorio donde están los screenshots
|
# Directorio donde están los screenshots
|
||||||
screenshots_dir = r"C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giorgio in Bosco\SimCtrEditor\screenshots"
|
screenshots_dir = os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)), "screenshots"
|
||||||
|
)
|
||||||
|
|
||||||
if not os.path.exists(screenshots_dir):
|
if not os.path.exists(screenshots_dir):
|
||||||
print(f"ERROR: El directorio {screenshots_dir} no existe")
|
print(f"ERROR: El directorio {screenshots_dir} no existe")
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
#!/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