Refactor project structure and remove unused files
- Updated CtrEditor.code-workspace to correct folder paths. - Removed detailed analysis of critical issues from MemoriadeEvolucion.md. - Deleted README_Canvas_Reference.md as it was no longer needed. - Refactored ScreenshotManager.cs to improve bounding box calculations and remove auto-zoom functionality. - Deleted analyze_canvas_screenshot.py and generate_canvas_reference.py scripts as they are no longer in use. - Removed requirements.txt since associated scripts were deleted.
This commit is contained in:
parent
fa637eb323
commit
d8c03fdc39
|
@ -11,6 +11,9 @@
|
|||
},
|
||||
{
|
||||
"path": "../../Scripts/MCP_Proxy"
|
||||
},
|
||||
{
|
||||
"path": "../../Scripts/TSNET"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
|
|
|
@ -737,14 +737,3 @@ Esta corrección resuelve un problema crítico de estabilidad que impedía el us
|
|||
|
||||
* Se identificó y resolvió un problema crítico de sincronización en el servidor MCP durante la inicialización de CtrEditor. El error "MPC -32603: CtrEditor not available" ocurría porque aunque CtrEditor se iniciaba correctamente, el servidor MCP interno (puerto 5006) requiere 30-60 segundos adicionales para estar completamente operativo. El servidor se inicia automáticamente en el constructor del MainViewModel pero no acepta conexiones inmediatamente. La solución documentada es esperar este tiempo de inicialización antes de intentar operaciones MCP como `create_object`. Una vez inicializado, el método CreateObject funciona perfectamente para crear objetos como osHydPump con todas sus propiedades. Este conocimiento es crucial para el uso correcto del sistema MCP y evita falsas alarmas sobre conectividad.
|
||||
|
||||
## Problemas Críticos Identificados en ScreenshotManager
|
||||
|
||||
### Problema de Canvas Completo con Baja Resolución
|
||||
El sistema genera imágenes masivas (3.4MB) con baja resolución útil debido al tamaño de canvas de 78x54 metros cuando los objetos están concentrados en área pequeña (39-46m x 19-21m). La densidad de píxeles por metro es insuficiente para ver detalles de los componentes.
|
||||
|
||||
### Problema de Captura de Objetos en Blanco
|
||||
Las capturas de objetos específicos resultan en archivos prácticamente vacíos (2.5KB) por tres causas principales en ScreenshotManager.cs: (1) VisualBrush falla en WPF bajo ciertas condiciones (líneas 446-451), (2) factor de escala 2.0 problemático causa problemas de memoria (líneas 406-409), y (3) elementos con coordenadas NaN causan fallos en intersección de rectángulos (líneas 438-442).
|
||||
|
||||
### Soluciones Recomendadas
|
||||
Se proponen tres mejoras: (1) implementar auto-zoom para canvas que detecte área con objetos visibles y capture solo esa región con padding adicional, (2) reemplazar VisualBrush con DrawingVisual para renderizado directo y reducir scaleFactor a 1.0, y (3) mejorar validación de posiciones de elementos verificando NaN, infinito y tamaños válidos antes del cálculo de intersección. Estas correcciones resolverían ambos problemas críticos del sistema de capturas.
|
||||
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
# Sistema de Imagen de Referencia para Canvas de CtrEditor
|
||||
|
||||
Este sistema permite generar y analizar imágenes de fondo de referencia para el canvas de CtrEditor, facilitando la detección de problemas de resolución y posicionamiento en las capturas de pantalla.
|
||||
|
||||
## 📁 Archivos del Sistema
|
||||
|
||||
- `generate_canvas_reference.py` - Generador de imagen de fondo con QR codes
|
||||
- `analyze_canvas_screenshot.py` - Analizador de capturas con QR codes
|
||||
- `requirements.txt` - Dependencias Python necesarias
|
||||
- `canvas_reference_background.png` - Imagen generada (3915×2726 px)
|
||||
|
||||
## 🚀 Instalación y Uso
|
||||
|
||||
### 1. Instalar Dependencias
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. Generar Imagen de Referencia
|
||||
|
||||
```bash
|
||||
python generate_canvas_reference.py
|
||||
```
|
||||
|
||||
Esto crea `canvas_reference_background.png` con:
|
||||
- **Dimensiones**: 3915×2726 píxeles (78.31×54.53 metros)
|
||||
- **Resolución**: 50 píxeles por metro
|
||||
- **8 QR codes** en posiciones estratégicas
|
||||
- **Grilla de coordenadas** cada 1 metro (gruesas cada 5m)
|
||||
- **4 zonas de color** para identificación visual
|
||||
- **Área de objetos** marcada en rojo
|
||||
|
||||
### 3. Usar en CtrEditor
|
||||
|
||||
1. Cargar `canvas_reference_background.png` como imagen de fondo del canvas
|
||||
2. Tomar capturas de pantalla (completas o parciales)
|
||||
3. Analizar las capturas con el script analizador
|
||||
|
||||
### 4. Analizar Capturas
|
||||
|
||||
```bash
|
||||
python analyze_canvas_screenshot.py screenshot.png
|
||||
```
|
||||
|
||||
El analizador detecta automáticamente:
|
||||
- **Posición real** de QR codes vs posición esperada
|
||||
- **Factor de escala** actual vs original
|
||||
- **Distorsiones** en resolución horizontal/vertical
|
||||
- **Problemas de posicionamiento** en área de objetos
|
||||
|
||||
## 🎯 QR Codes y Sus Posiciones
|
||||
|
||||
| Etiqueta | Posición (metros) | Propósito |
|
||||
|----------|-------------------|-----------|
|
||||
| TL | (5, 5) | Esquina superior izquierda |
|
||||
| TR | (70, 5) | Esquina superior derecha |
|
||||
| BL | (5, 45) | Esquina inferior izquierda |
|
||||
| BR | (70, 45) | Esquina inferior derecha |
|
||||
| CENTER | (39, 27) | Centro del canvas |
|
||||
| OBJECTS | (42, 20) | Área donde están los objetos |
|
||||
| CAL1 | (20, 15) | Punto de calibración 1 |
|
||||
| CAL2 | (55, 35) | Punto de calibración 2 |
|
||||
|
||||
## 📊 Información en QR Codes
|
||||
|
||||
Cada QR contiene datos JSON con:
|
||||
|
||||
```json
|
||||
{
|
||||
"position": [42, 20],
|
||||
"label": "OBJECTS",
|
||||
"canvas_size": [78.31, 54.53],
|
||||
"image_size": [3915, 2726],
|
||||
"pixels_per_meter": 50,
|
||||
"timestamp": "2025-01-09T...",
|
||||
"type": "CtrEditor_Canvas_Reference"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 Ejemplos de Análisis
|
||||
|
||||
### Captura Normal
|
||||
```bash
|
||||
$ python analyze_canvas_screenshot.py debug_full_canvas.png
|
||||
|
||||
📸 Analizando imagen: debug_full_canvas.png
|
||||
📐 Tamaño de imagen: 3915 × 2726 píxeles
|
||||
🔍 QR codes encontrados: 8
|
||||
|
||||
🎯 QR #1: TL
|
||||
📍 Posición esperada: [5, 5] metros
|
||||
📱 Encontrado en píxeles: (250, 250)
|
||||
📊 Escala original: 50.0 px/m
|
||||
📊 Escala actual: 50.0 px/m
|
||||
📊 Factor de escala: 1.000
|
||||
```
|
||||
|
||||
### Captura con Problemas
|
||||
```bash
|
||||
🎯 QR #1: OBJECTS
|
||||
📍 Posición esperada: [42, 20] metros
|
||||
📱 Encontrado en píxeles: (1890, 850)
|
||||
📊 Escala original: 50.0 px/m
|
||||
📊 Escala actual: 25.2 px/m
|
||||
📊 Factor de escala: 0.504
|
||||
⚠️ PROBLEMA: Resolución reducida al 50.4%
|
||||
```
|
||||
|
||||
## 🛠️ Resolución de Problemas Detectados
|
||||
|
||||
### Factor de Escala < 1.0
|
||||
- **Causa**: Canvas se renderiza a menor resolución
|
||||
- **Solución**: Ajustar factor de escala en ScreenshotManager.cs
|
||||
|
||||
### QR Codes Faltantes
|
||||
- **Causa**: Área de captura muy pequeña o desplazada
|
||||
- **Solución**: Verificar coordenadas de área de captura
|
||||
|
||||
### Distorsión Horizontal/Vertical
|
||||
- **Causa**: Aspect ratio incorrecto
|
||||
- **Solución**: Verificar cálculos de píxeles vs metros
|
||||
|
||||
## 🎨 Características de la Imagen
|
||||
|
||||
### Zonas de Color
|
||||
- **Zona 1** (Superior izquierda): Rojo muy claro
|
||||
- **Zona 2** (Superior derecha): Verde muy claro
|
||||
- **Zona 3** (Inferior izquierda): Azul muy claro
|
||||
- **Zona 4** (Inferior derecha): Amarillo muy claro
|
||||
|
||||
### Patrones de Referencia
|
||||
- **Círculos concéntricos** en el centro (radios 2, 4, 6, 8, 10m)
|
||||
- **Líneas finas** para detectar aliasing
|
||||
- **Grilla precisa** con coordenadas etiquetadas
|
||||
|
||||
### Área de Objetos
|
||||
Rectángulo rojo que marca la región donde están ubicados los objetos del análisis:
|
||||
- **Coordenadas**: 39.92, 19.76 → 44.99, 20.47 metros
|
||||
- **Tamaño**: 5.07 × 0.71 metros
|
||||
|
||||
## 📋 Flujo de Trabajo Recomendado
|
||||
|
||||
1. **Generar** imagen de referencia
|
||||
2. **Cargar** como fondo en CtrEditor
|
||||
3. **Tomar** capturas problemáticas
|
||||
4. **Analizar** con script automático
|
||||
5. **Identificar** problemas específicos
|
||||
6. **Corregir** código ScreenshotManager.cs
|
||||
7. **Validar** con nuevas capturas
|
||||
|
||||
## 🤖 Integración con LLM
|
||||
|
||||
El sistema está diseñado para que los LLMs puedan:
|
||||
- **Leer automáticamente** los QR codes de las capturas
|
||||
- **Detectar problemas** de escala y posicionamiento
|
||||
- **Generar reportes** específicos de cada captura
|
||||
- **Sugerir correcciones** basadas en los datos detectados
|
||||
|
||||
Este enfoque automatiza completamente la detección de problemas en el sistema de capturas de CtrEditor.
|
|
@ -65,8 +65,8 @@ namespace CtrEditor.Services
|
|||
if (!targetObjects.Any())
|
||||
throw new ArgumentException($"No se encontraron objetos visibles con los IDs: {string.Join(", ", objectIds)}");
|
||||
|
||||
// SOLUCIÓN 2: Usar bounding box mejorado para objetos con dimensiones 0.0
|
||||
var boundingBox = CalculateObjectsBoundingBoxImproved(targetObjects);
|
||||
// Calcular bounding box usando la misma lógica que ObjectManipulationManager
|
||||
var boundingBox = CalculateObjectsBoundingBox(targetObjects);
|
||||
|
||||
// Aplicar padding
|
||||
var captureArea = new ScreenshotArea
|
||||
|
@ -234,53 +234,21 @@ namespace CtrEditor.Services
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captura screenshot de todo el canvas con auto-zoom inteligente
|
||||
/// 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>
|
||||
/// <param name="autoZoom">Si usar auto-zoom a área con objetos (por defecto true)</param>
|
||||
/// <returns>Resultado de la captura</returns>
|
||||
public ScreenshotResult CaptureFullCanvas(
|
||||
string filename = null,
|
||||
bool includeBackground = true,
|
||||
bool saveToFile = true,
|
||||
bool returnBase64 = true,
|
||||
bool autoZoom = true)
|
||||
bool returnBase64 = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// SOLUCIÓN 1: Auto-zoom inteligente
|
||||
if (autoZoom)
|
||||
{
|
||||
var visibleObjects = _mainViewModel.ObjetosSimulables
|
||||
.Where(o => o.Show_On_This_Page).ToList();
|
||||
|
||||
if (visibleObjects.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
var bounds = CalculateObjectsBoundingBoxImproved(visibleObjects);
|
||||
var padding = Math.Max(2.0f, Math.Max(bounds.Width, bounds.Height) * 0.2f);
|
||||
|
||||
Trace.WriteLine($"[ScreenshotManager] Auto-zoom detectado: área {bounds.Width:F1}×{bounds.Height:F1}m, padding {padding:F1}m");
|
||||
|
||||
return CaptureArea(
|
||||
bounds.Left - padding,
|
||||
bounds.Top - padding,
|
||||
bounds.Width + (padding * 2),
|
||||
bounds.Height + (padding * 2),
|
||||
filename, includeBackground, saveToFile, returnBase64);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine($"[ScreenshotManager] Auto-zoom falló: {ex.Message}, usando canvas completo");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback a canvas completo
|
||||
var bitmap = CaptureEntireCanvas(includeBackground);
|
||||
|
||||
var canvasWidthMeters = PixelToMeter.Instance.calc.PixelsToMeters((float)_canvas.ActualWidth);
|
||||
|
@ -389,88 +357,6 @@ namespace CtrEditor.Services
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SOLUCIÓN 2: Calcula el bounding box mejorado que maneja objetos con dimensiones 0.0
|
||||
/// </summary>
|
||||
private ScreenshotArea CalculateObjectsBoundingBoxImproved(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)
|
||||
{
|
||||
// Obtener dimensiones reales del objeto
|
||||
float objWidth = obj.Ancho;
|
||||
float objHeight = obj.Alto;
|
||||
|
||||
// SOLUCIÓN 2: Manejar objetos con dimensiones 0.0 (como botellas)
|
||||
if (objWidth <= 0.0f || objHeight <= 0.0f)
|
||||
{
|
||||
// Usar dimensiones por defecto basadas en el tipo de objeto
|
||||
if (obj.GetType().Name == "osBotella")
|
||||
{
|
||||
objWidth = 0.2f; // 20cm por defecto para botellas
|
||||
objHeight = 0.2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
objWidth = Math.Max(objWidth, 0.1f); // Mínimo 10cm
|
||||
objHeight = Math.Max(objHeight, 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
float objLeft = obj.Left - objWidth / 2; // Centrar el objeto
|
||||
float objTop = obj.Top - objHeight / 2;
|
||||
float objRight = obj.Left + objWidth / 2;
|
||||
float objBottom = obj.Top + objHeight / 2;
|
||||
|
||||
left = Math.Min(left, objLeft);
|
||||
top = Math.Min(top, objTop);
|
||||
right = Math.Max(right, objRight);
|
||||
bottom = Math.Max(bottom, objBottom);
|
||||
|
||||
Trace.WriteLine($"[ScreenshotManager] Object {obj.Id.Value} ({obj.GetType().Name}) - Original: {obj.Ancho}×{obj.Alto}, Used: {objWidth}×{objHeight}");
|
||||
}
|
||||
|
||||
if (left == float.MaxValue)
|
||||
throw new InvalidOperationException("No se encontraron objetos válidos para calcular el bounding box");
|
||||
|
||||
var finalWidth = right - left;
|
||||
var finalHeight = bottom - top;
|
||||
|
||||
// Asegurar dimensiones mínimas
|
||||
if (finalWidth < 0.5f)
|
||||
{
|
||||
var center = (left + right) / 2;
|
||||
left = center - 0.25f;
|
||||
right = center + 0.25f;
|
||||
finalWidth = 0.5f;
|
||||
}
|
||||
|
||||
if (finalHeight < 0.5f)
|
||||
{
|
||||
var center = (top + bottom) / 2;
|
||||
top = center - 0.25f;
|
||||
bottom = center + 0.25f;
|
||||
finalHeight = 0.5f;
|
||||
}
|
||||
|
||||
Trace.WriteLine($"[ScreenshotManager] Bounding box mejorado - L:{left:F2} T:{top:F2} W:{finalWidth:F2} H:{finalHeight:F2}");
|
||||
|
||||
return new ScreenshotArea
|
||||
{
|
||||
Left = left,
|
||||
Top = top,
|
||||
Width = finalWidth,
|
||||
Height = finalHeight
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captura un área específica del canvas
|
||||
/// </summary>
|
||||
|
@ -509,21 +395,19 @@ namespace CtrEditor.Services
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// SOLUCIÓN 3: Captura un rectángulo específico del canvas usando DrawingVisual (sin VisualBrush)
|
||||
/// 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");
|
||||
|
||||
// SOLUCIÓN 3: Reducir scaleFactor de 2.0 a 1.0 para evitar problemas de memoria
|
||||
var scaleFactor = 1.0; // Reducido para mayor estabilidad
|
||||
// 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;
|
||||
|
||||
Trace.WriteLine($"[ScreenshotManager] Capturando {renderWidth}×{renderHeight} px (scaleFactor: {scaleFactor})");
|
||||
|
||||
var renderBitmap = new RenderTargetBitmap(
|
||||
renderWidth,
|
||||
renderHeight,
|
||||
|
@ -531,7 +415,7 @@ namespace CtrEditor.Services
|
|||
dpi,
|
||||
PixelFormats.Pbgra32);
|
||||
|
||||
// SOLUCIÓN 3 CORREGIDA: Enfoque híbrido - Canvas temporal con VisualBrush mejorado
|
||||
// Crear canvas temporal para renderizado
|
||||
var tempCanvas = new Canvas()
|
||||
{
|
||||
Width = captureRect.Width * scaleFactor,
|
||||
|
@ -541,113 +425,52 @@ namespace CtrEditor.Services
|
|||
|
||||
tempCanvas.RenderTransform = new ScaleTransform(scaleFactor, scaleFactor);
|
||||
|
||||
// Forzar actualización de layout del canvas principal antes de capturar
|
||||
_canvas.UpdateLayout();
|
||||
_canvas.InvalidateVisual();
|
||||
|
||||
// Renderizar elementos con VisualBrush mejorado
|
||||
int elementCount = 0;
|
||||
// Clonar elementos visibles que intersectan con el área de captura
|
||||
foreach (UIElement child in _canvas.Children)
|
||||
{
|
||||
if (ValidateElementForCapture(child, captureRect))
|
||||
if (child.Visibility == Visibility.Visible)
|
||||
{
|
||||
try
|
||||
{
|
||||
var left = Canvas.GetLeft(child);
|
||||
var top = Canvas.GetTop(child);
|
||||
|
||||
// SOLUCIÓN 3: Validación mejorada de coordenadas
|
||||
if (double.IsNaN(left)) left = 0;
|
||||
if (double.IsNaN(top)) top = 0;
|
||||
if (double.IsInfinity(left) || double.IsInfinity(top)) continue;
|
||||
|
||||
var elementRect = new Rect(left, top, child.RenderSize.Width, child.RenderSize.Height);
|
||||
var elementRect = new Rect(
|
||||
double.IsNaN(left) ? 0 : left,
|
||||
double.IsNaN(top) ? 0 : top,
|
||||
child.RenderSize.Width,
|
||||
child.RenderSize.Height);
|
||||
|
||||
if (captureRect.IntersectsWith(elementRect))
|
||||
{
|
||||
// Forzar actualización del UserControl
|
||||
child.UpdateLayout();
|
||||
child.InvalidateVisual();
|
||||
|
||||
// CORRECCIÓN: Calcular posición relativa dentro del área de captura
|
||||
var relativeLeft = (elementRect.X - captureRect.X) * scaleFactor;
|
||||
var relativeTop = (elementRect.Y - captureRect.Y) * scaleFactor;
|
||||
|
||||
// Verificar que la posición relativa esté dentro del canvas temporal
|
||||
var tempCanvasWidth = captureRect.Width * scaleFactor;
|
||||
var tempCanvasHeight = captureRect.Height * scaleFactor;
|
||||
|
||||
var elementWidth = child.RenderSize.Width * scaleFactor;
|
||||
var elementHeight = child.RenderSize.Height * scaleFactor;
|
||||
|
||||
// Verificar si el elemento está realmente dentro del área visible
|
||||
if (relativeLeft < tempCanvasWidth && relativeTop < tempCanvasHeight &&
|
||||
relativeLeft + elementWidth > 0 && relativeTop + elementHeight > 0)
|
||||
{
|
||||
// Usar VisualBrush mejorado con manejo robusto de errores
|
||||
try
|
||||
{
|
||||
var visualBrush = new VisualBrush(child)
|
||||
{
|
||||
Stretch = Stretch.None,
|
||||
AlignmentX = AlignmentX.Left,
|
||||
AlignmentY = AlignmentY.Top,
|
||||
ViewboxUnits = BrushMappingMode.Absolute,
|
||||
Viewbox = new Rect(0, 0, child.RenderSize.Width, child.RenderSize.Height)
|
||||
AlignmentY = AlignmentY.Top
|
||||
};
|
||||
|
||||
var rect = new Rectangle()
|
||||
{
|
||||
Width = elementWidth,
|
||||
Height = elementHeight,
|
||||
Width = child.RenderSize.Width * scaleFactor,
|
||||
Height = child.RenderSize.Height * scaleFactor,
|
||||
Fill = visualBrush
|
||||
};
|
||||
|
||||
Canvas.SetLeft(rect, relativeLeft);
|
||||
Canvas.SetTop(rect, relativeTop);
|
||||
Canvas.SetLeft(rect, (elementRect.X - captureRect.X) * scaleFactor);
|
||||
Canvas.SetTop(rect, (elementRect.Y - captureRect.Y) * scaleFactor);
|
||||
|
||||
tempCanvas.Children.Add(rect);
|
||||
elementCount++;
|
||||
|
||||
Trace.WriteLine($"[ScreenshotManager] ✅ Renderizado {child.GetType().Name} - Absoluta:({elementRect.X:F1},{elementRect.Y:F1}) → Relativa:({relativeLeft:F1},{relativeTop:F1})");
|
||||
}
|
||||
catch (Exception brushEx)
|
||||
{
|
||||
Trace.WriteLine($"[ScreenshotManager] Error con VisualBrush para {child.GetType().Name}: {brushEx.Message}");
|
||||
|
||||
// Fallback: Rectángulo de placeholder visible
|
||||
var placeholder = new Rectangle()
|
||||
{
|
||||
Width = elementWidth,
|
||||
Height = elementHeight,
|
||||
Fill = Brushes.LightBlue,
|
||||
Stroke = Brushes.DarkBlue,
|
||||
StrokeThickness = 2,
|
||||
Opacity = 0.7
|
||||
};
|
||||
|
||||
Canvas.SetLeft(placeholder, relativeLeft);
|
||||
Canvas.SetTop(placeholder, relativeTop);
|
||||
tempCanvas.Children.Add(placeholder);
|
||||
elementCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.WriteLine($"[ScreenshotManager] ⚠️ UserControl {child.GetType().Name} fuera del área visible - Rel:({relativeLeft:F1},{relativeTop:F1}) Canvas:({tempCanvasWidth:F1}×{tempCanvasHeight:F1})");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine($"[ScreenshotManager] Error procesando elemento {child.GetType().Name}: {ex.Message}");
|
||||
Debug.WriteLine($"[ScreenshotManager] Error procesando elemento: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Trace.WriteLine($"[ScreenshotManager] Renderizados {elementCount} elementos UserControl");
|
||||
|
||||
// Renderizar canvas temporal mejorado
|
||||
// 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));
|
||||
|
@ -657,29 +480,6 @@ namespace CtrEditor.Services
|
|||
return renderBitmap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SOLUCIÓN 3: Valida si un elemento debe ser incluido en la captura
|
||||
/// </summary>
|
||||
private bool ValidateElementForCapture(UIElement element, Rect captureRect)
|
||||
{
|
||||
if (element.Visibility != Visibility.Visible)
|
||||
return false;
|
||||
|
||||
if (element.RenderSize.Width <= 0 || element.RenderSize.Height <= 0)
|
||||
return false;
|
||||
|
||||
var left = Canvas.GetLeft(element);
|
||||
var top = Canvas.GetTop(element);
|
||||
|
||||
// Validación mejorada de posiciones
|
||||
if (double.IsNaN(left) || double.IsNaN(top) ||
|
||||
double.IsInfinity(left) || double.IsInfinity(top))
|
||||
return false;
|
||||
|
||||
var elementRect = new Rect(left, top, element.RenderSize.Width, element.RenderSize.Height);
|
||||
return captureRect.IntersectsWith(elementRect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Procesa la salida del screenshot (guardar archivo y/o generar base64)
|
||||
/// </summary>
|
||||
|
|
|
@ -1,220 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Analizador de capturas de pantalla del canvas con QR codes de referencia
|
||||
Lee QR codes de las capturas para detectar problemas de resolución y posicionamiento.
|
||||
|
||||
Uso: python analyze_canvas_screenshot.py <imagen.png>
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from PIL import Image
|
||||
import cv2
|
||||
import numpy as np
|
||||
from pyzbar import pyzbar
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def analyze_qr_codes(image_path):
|
||||
"""Analiza los QR codes en una imagen y extrae información de posición"""
|
||||
try:
|
||||
# Cargar imagen
|
||||
image = Image.open(image_path)
|
||||
image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
||||
|
||||
# Detectar QR codes
|
||||
qr_codes = pyzbar.decode(image_cv)
|
||||
|
||||
if not qr_codes:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "No se encontraron QR codes en la imagen",
|
||||
}
|
||||
|
||||
results = {
|
||||
"status": "success",
|
||||
"image_path": image_path,
|
||||
"image_size": image.size,
|
||||
"analysis_timestamp": datetime.now().isoformat(),
|
||||
"qr_codes_found": len(qr_codes),
|
||||
"qr_data": [],
|
||||
}
|
||||
|
||||
print(f"📸 Analizando imagen: {image_path}")
|
||||
print(f"📐 Tamaño de imagen: {image.size[0]} × {image.size[1]} píxeles")
|
||||
print(f"🔍 QR codes encontrados: {len(qr_codes)}")
|
||||
print()
|
||||
|
||||
for i, qr in enumerate(qr_codes):
|
||||
try:
|
||||
# Decodificar datos JSON del QR
|
||||
qr_json = json.loads(qr.data.decode("utf-8"))
|
||||
|
||||
# Información de posición del QR en la imagen
|
||||
qr_rect = qr.rect
|
||||
qr_center_pixels = (
|
||||
qr_rect.left + qr_rect.width // 2,
|
||||
qr_rect.top + qr_rect.height // 2,
|
||||
)
|
||||
|
||||
# Información del QR
|
||||
qr_info = {
|
||||
"index": i + 1,
|
||||
"label": qr_json.get("label", "Unknown"),
|
||||
"expected_position_meters": qr_json.get("position", [0, 0]),
|
||||
"found_position_pixels": qr_center_pixels,
|
||||
"qr_size_pixels": (qr_rect.width, qr_rect.height),
|
||||
"original_data": qr_json,
|
||||
}
|
||||
|
||||
results["qr_data"].append(qr_info)
|
||||
|
||||
print(f"🎯 QR #{i+1}: {qr_info['label']}")
|
||||
print(
|
||||
f" 📍 Posición esperada: {qr_info['expected_position_meters']} metros"
|
||||
)
|
||||
print(f" 📱 Encontrado en píxeles: {qr_center_pixels}")
|
||||
print(f" 📏 Tamaño QR: {qr_rect.width} × {qr_rect.height} px")
|
||||
|
||||
# Calcular escala si tenemos la información original
|
||||
if "pixels_per_meter" in qr_json:
|
||||
original_ppm = qr_json["pixels_per_meter"]
|
||||
expected_canvas_size = qr_json.get("canvas_size", [78.31, 54.53])
|
||||
|
||||
# Calcular escala actual basada en el canvas
|
||||
if image.size[0] > 0 and expected_canvas_size[0] > 0:
|
||||
current_ppm = image.size[0] / expected_canvas_size[0]
|
||||
scale_factor = current_ppm / original_ppm
|
||||
|
||||
print(f" 📊 Escala original: {original_ppm:.1f} px/m")
|
||||
print(f" 📊 Escala actual: {current_ppm:.1f} px/m")
|
||||
print(f" 📊 Factor de escala: {scale_factor:.3f}")
|
||||
|
||||
qr_info["scale_analysis"] = {
|
||||
"original_pixels_per_meter": original_ppm,
|
||||
"current_pixels_per_meter": current_ppm,
|
||||
"scale_factor": scale_factor,
|
||||
}
|
||||
|
||||
print()
|
||||
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
print(f"❌ Error decodificando QR #{i+1}: {e}")
|
||||
continue
|
||||
|
||||
# Análisis de distribución espacial
|
||||
if len(results["qr_data"]) >= 2:
|
||||
print("📏 Análisis de distribución espacial:")
|
||||
analyze_spatial_distribution(results["qr_data"], results)
|
||||
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error analizando imagen: {e}"}
|
||||
|
||||
|
||||
def analyze_spatial_distribution(qr_data, results):
|
||||
"""Analiza la distribución espacial de los QR codes para detectar distorsiones"""
|
||||
|
||||
# Buscar QR codes de esquinas conocidas para análisis de distorsión
|
||||
corners = {}
|
||||
for qr in qr_data:
|
||||
label = qr["label"]
|
||||
if label in ["TL", "TR", "BL", "BR"]: # Top-Left, Top-Right, etc.
|
||||
corners[label] = qr
|
||||
|
||||
if len(corners) >= 2:
|
||||
print(" 🔍 Detectando distorsión basada en esquinas...")
|
||||
|
||||
# Calcular distancias esperadas vs reales
|
||||
if "TL" in corners and "TR" in corners:
|
||||
tl_pos = corners["TL"]["found_position_pixels"]
|
||||
tr_pos = corners["TR"]["found_position_pixels"]
|
||||
width_pixels = abs(tr_pos[0] - tl_pos[0])
|
||||
|
||||
# Comparar con ancho esperado del canvas
|
||||
if corners["TL"]["original_data"].get("canvas_size"):
|
||||
expected_width_meters = corners["TL"]["original_data"]["canvas_size"][0]
|
||||
current_ppm = width_pixels / expected_width_meters
|
||||
print(
|
||||
f" 📐 Ancho detectado: {width_pixels} px = {expected_width_meters}m"
|
||||
)
|
||||
print(f" 📊 Resolución horizontal: {current_ppm:.1f} px/m")
|
||||
|
||||
if "TL" in corners and "BL" in corners:
|
||||
tl_pos = corners["TL"]["found_position_pixels"]
|
||||
bl_pos = corners["BL"]["found_position_pixels"]
|
||||
height_pixels = abs(bl_pos[1] - tl_pos[1])
|
||||
|
||||
if corners["TL"]["original_data"].get("canvas_size"):
|
||||
expected_height_meters = corners["TL"]["original_data"]["canvas_size"][
|
||||
1
|
||||
]
|
||||
current_ppm = height_pixels / expected_height_meters
|
||||
print(
|
||||
f" 📐 Alto detectado: {height_pixels} px = {expected_height_meters}m"
|
||||
)
|
||||
print(f" 📊 Resolución vertical: {current_ppm:.1f} px/m")
|
||||
|
||||
# Verificar área de objetos si existe el QR "OBJECTS"
|
||||
objects_qr = next((qr for qr in qr_data if qr["label"] == "OBJECTS"), None)
|
||||
if objects_qr:
|
||||
obj_pos = objects_qr["found_position_pixels"]
|
||||
print(f" 🎯 Área de objetos detectada en: {obj_pos} px")
|
||||
|
||||
# Calcular si está en la posición esperada relativa
|
||||
expected_pos = objects_qr["expected_position_meters"]
|
||||
print(f" 🎯 Posición esperada: {expected_pos} metros")
|
||||
|
||||
|
||||
def main():
|
||||
"""Función principal"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Analiza capturas del canvas con QR de referencia"
|
||||
)
|
||||
parser.add_argument("image", help="Ruta a la imagen de captura a analizar")
|
||||
parser.add_argument("--output", "-o", help="Archivo JSON de salida para resultados")
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="Salida detallada")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Verificar que existe la imagen
|
||||
try:
|
||||
with open(args.image, "rb"):
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
print(f"❌ Error: No se encontró la imagen '{args.image}'")
|
||||
return 1
|
||||
|
||||
# Analizar imagen
|
||||
results = analyze_qr_codes(args.image)
|
||||
|
||||
if results["status"] == "error":
|
||||
print(f"❌ {results['message']}")
|
||||
return 1
|
||||
|
||||
# Mostrar resumen
|
||||
print("=" * 60)
|
||||
print("📋 RESUMEN DEL ANÁLISIS:")
|
||||
print(f"✅ QR codes válidos encontrados: {results['qr_codes_found']}")
|
||||
|
||||
if results["qr_data"]:
|
||||
labels_found = [qr["label"] for qr in results["qr_data"]]
|
||||
print(f"🏷️ Etiquetas detectadas: {', '.join(labels_found)}")
|
||||
|
||||
# Guardar resultados si se especifica
|
||||
if args.output:
|
||||
try:
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(results, f, indent=2)
|
||||
print(f"💾 Resultados guardados en: {args.output}")
|
||||
except Exception as e:
|
||||
print(f"❌ Error guardando resultados: {e}")
|
||||
|
||||
print("✅ Análisis completado")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
|
@ -1,335 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generador de imagen de fondo de referencia para CtrEditor Canvas
|
||||
Crea una imagen PNG con QR codes, grillas y patrones para detectar problemas de resolución y posicionamiento.
|
||||
|
||||
Uso: python generate_canvas_reference.py
|
||||
Salida: canvas_reference_background.png
|
||||
"""
|
||||
|
||||
import qrcode
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import json
|
||||
from datetime import datetime
|
||||
import math
|
||||
|
||||
# Configuración del canvas (basado en análisis previo)
|
||||
CANVAS_WIDTH_METERS = 78.31
|
||||
CANVAS_HEIGHT_METERS = 54.53
|
||||
|
||||
# Configuración de imagen (alta resolución para detectar problemas de escalado)
|
||||
PIXELS_PER_METER = 50 # 50 píxeles por metro = buena resolución para análisis
|
||||
IMAGE_WIDTH = int(CANVAS_WIDTH_METERS * PIXELS_PER_METER)
|
||||
IMAGE_HEIGHT = int(CANVAS_HEIGHT_METERS * PIXELS_PER_METER)
|
||||
|
||||
# Colores de referencia
|
||||
BACKGROUND_COLOR = (240, 248, 255) # Alice Blue - fondo suave
|
||||
GRID_COLOR = (200, 200, 200) # Gris claro para grilla
|
||||
MAJOR_GRID_COLOR = (150, 150, 150) # Gris medio para grilla principal
|
||||
TEXT_COLOR = (50, 50, 50) # Gris oscuro para texto
|
||||
QR_BACKGROUND = (255, 255, 255) # Blanco para QR
|
||||
ZONE_COLORS = [
|
||||
(255, 240, 240), # Rojo muy claro
|
||||
(240, 255, 240), # Verde muy claro
|
||||
(240, 240, 255), # Azul muy claro
|
||||
(255, 255, 240), # Amarillo muy claro
|
||||
]
|
||||
|
||||
|
||||
def create_qr_code(data, size_pixels):
|
||||
"""Crea un QR code con los datos especificados"""
|
||||
qr = qrcode.QRCode(
|
||||
version=1,
|
||||
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||
box_size=max(
|
||||
1, size_pixels // 25
|
||||
), # Ajustar tamaño de caja según el tamaño del QR
|
||||
border=1,
|
||||
)
|
||||
qr.add_data(data)
|
||||
qr.make(fit=True)
|
||||
|
||||
qr_img = qr.make_image(fill_color="black", back_color="white")
|
||||
return qr_img.resize((size_pixels, size_pixels), Image.Resampling.NEAREST)
|
||||
|
||||
|
||||
def meters_to_pixels(meters):
|
||||
"""Convierte metros a píxeles"""
|
||||
return int(meters * PIXELS_PER_METER)
|
||||
|
||||
|
||||
def pixels_to_meters(pixels):
|
||||
"""Convierte píxeles a metros"""
|
||||
return pixels / PIXELS_PER_METER
|
||||
|
||||
|
||||
def draw_grid(draw, image_width, image_height):
|
||||
"""Dibuja la grilla de referencia"""
|
||||
# Grilla menor cada 1 metro
|
||||
for x_meters in range(0, int(CANVAS_WIDTH_METERS) + 1):
|
||||
x_pixels = meters_to_pixels(x_meters)
|
||||
if x_pixels < image_width:
|
||||
color = MAJOR_GRID_COLOR if x_meters % 5 == 0 else GRID_COLOR
|
||||
width = 2 if x_meters % 5 == 0 else 1
|
||||
draw.line(
|
||||
[(x_pixels, 0), (x_pixels, image_height)], fill=color, width=width
|
||||
)
|
||||
|
||||
for y_meters in range(0, int(CANVAS_HEIGHT_METERS) + 1):
|
||||
y_pixels = meters_to_pixels(y_meters)
|
||||
if y_pixels < image_height:
|
||||
color = MAJOR_GRID_COLOR if y_meters % 5 == 0 else GRID_COLOR
|
||||
width = 2 if y_meters % 5 == 0 else 1
|
||||
draw.line([(0, y_pixels), (image_width, y_pixels)], fill=color, width=width)
|
||||
|
||||
|
||||
def draw_coordinate_labels(draw, font):
|
||||
"""Dibuja etiquetas de coordenadas"""
|
||||
# Etiquetas cada 5 metros
|
||||
for x_meters in range(0, int(CANVAS_WIDTH_METERS) + 1, 5):
|
||||
x_pixels = meters_to_pixels(x_meters)
|
||||
if x_pixels < IMAGE_WIDTH - 50:
|
||||
draw.text((x_pixels + 2, 2), f"{x_meters}m", fill=TEXT_COLOR, font=font)
|
||||
draw.text(
|
||||
(x_pixels + 2, IMAGE_HEIGHT - 20),
|
||||
f"{x_meters}m",
|
||||
fill=TEXT_COLOR,
|
||||
font=font,
|
||||
)
|
||||
|
||||
for y_meters in range(0, int(CANVAS_HEIGHT_METERS) + 1, 5):
|
||||
y_pixels = meters_to_pixels(y_meters)
|
||||
if y_pixels < IMAGE_HEIGHT - 20:
|
||||
draw.text((2, y_pixels + 2), f"{y_meters}m", fill=TEXT_COLOR, font=font)
|
||||
draw.text(
|
||||
(IMAGE_WIDTH - 40, y_pixels + 2),
|
||||
f"{y_meters}m",
|
||||
fill=TEXT_COLOR,
|
||||
font=font,
|
||||
)
|
||||
|
||||
|
||||
def create_zone_backgrounds(image):
|
||||
"""Crea fondos de colores suaves en diferentes zonas para identificación visual"""
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# Dividir canvas en 4 zonas
|
||||
zone_width = IMAGE_WIDTH // 2
|
||||
zone_height = IMAGE_HEIGHT // 2
|
||||
|
||||
zones = [
|
||||
(0, 0, zone_width, zone_height), # Superior izquierda
|
||||
(zone_width, 0, IMAGE_WIDTH, zone_height), # Superior derecha
|
||||
(0, zone_height, zone_width, IMAGE_HEIGHT), # Inferior izquierda
|
||||
(zone_width, zone_height, IMAGE_WIDTH, IMAGE_HEIGHT), # Inferior derecha
|
||||
]
|
||||
|
||||
for i, (x1, y1, x2, y2) in enumerate(zones):
|
||||
# Crear overlay semi-transparente
|
||||
overlay = Image.new("RGBA", (x2 - x1, y2 - y1), ZONE_COLORS[i] + (30,))
|
||||
image.paste(overlay, (x1, y1), overlay)
|
||||
|
||||
|
||||
def place_qr_codes(image):
|
||||
"""Coloca QR codes en posiciones estratégicas con información de posición"""
|
||||
qr_positions = [
|
||||
# Esquinas
|
||||
(5, 5, "TL"), # Top-Left
|
||||
(70, 5, "TR"), # Top-Right
|
||||
(5, 45, "BL"), # Bottom-Left
|
||||
(70, 45, "BR"), # Bottom-Right
|
||||
# Centro y puntos de interés
|
||||
(39, 27, "CENTER"), # Centro del canvas
|
||||
(42, 20, "OBJECTS"), # Área donde están los objetos según análisis
|
||||
# Puntos de calibración adicionales
|
||||
(20, 15, "CAL1"),
|
||||
(55, 35, "CAL2"),
|
||||
]
|
||||
|
||||
qr_size_pixels = meters_to_pixels(3) # QR de 3x3 metros
|
||||
|
||||
for x_meters, y_meters, label in qr_positions:
|
||||
# Información del QR
|
||||
qr_data = {
|
||||
"position": [x_meters, y_meters],
|
||||
"label": label,
|
||||
"canvas_size": [CANVAS_WIDTH_METERS, CANVAS_HEIGHT_METERS],
|
||||
"image_size": [IMAGE_WIDTH, IMAGE_HEIGHT],
|
||||
"pixels_per_meter": PIXELS_PER_METER,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"type": "CtrEditor_Canvas_Reference",
|
||||
}
|
||||
|
||||
# Crear QR
|
||||
qr_img = create_qr_code(json.dumps(qr_data), qr_size_pixels)
|
||||
|
||||
# Calcular posición en píxeles (centrado)
|
||||
x_pixels = meters_to_pixels(x_meters) - qr_size_pixels // 2
|
||||
y_pixels = meters_to_pixels(y_meters) - qr_size_pixels // 2
|
||||
|
||||
# Asegurar que el QR esté dentro de los límites
|
||||
x_pixels = max(0, min(x_pixels, IMAGE_WIDTH - qr_size_pixels))
|
||||
y_pixels = max(0, min(y_pixels, IMAGE_HEIGHT - qr_size_pixels))
|
||||
|
||||
# Fondo blanco para el QR
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.rectangle(
|
||||
[
|
||||
x_pixels - 5,
|
||||
y_pixels - 5,
|
||||
x_pixels + qr_size_pixels + 5,
|
||||
y_pixels + qr_size_pixels + 5,
|
||||
],
|
||||
fill=QR_BACKGROUND,
|
||||
outline=TEXT_COLOR,
|
||||
width=2,
|
||||
)
|
||||
|
||||
# Pegar QR
|
||||
image.paste(qr_img, (x_pixels, y_pixels))
|
||||
|
||||
# Etiqueta debajo del QR
|
||||
try:
|
||||
font = ImageFont.truetype("arial.ttf", 20)
|
||||
except:
|
||||
font = ImageFont.load_default()
|
||||
|
||||
draw.text(
|
||||
(x_pixels + 5, y_pixels + qr_size_pixels + 8),
|
||||
f"{label}\n({x_meters}, {y_meters})m",
|
||||
fill=TEXT_COLOR,
|
||||
font=font,
|
||||
)
|
||||
|
||||
|
||||
def draw_scale_patterns(image):
|
||||
"""Dibuja patrones de diferentes escalas para detectar problemas de resolución"""
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# Patrón de círculos concéntricos en el centro
|
||||
center_x = meters_to_pixels(CANVAS_WIDTH_METERS / 2)
|
||||
center_y = meters_to_pixels(CANVAS_HEIGHT_METERS / 2)
|
||||
|
||||
# Círculos cada 2 metros de radio
|
||||
for radius_meters in range(2, 12, 2):
|
||||
radius_pixels = meters_to_pixels(radius_meters)
|
||||
draw.ellipse(
|
||||
[
|
||||
center_x - radius_pixels,
|
||||
center_y - radius_pixels,
|
||||
center_x + radius_pixels,
|
||||
center_y + radius_pixels,
|
||||
],
|
||||
outline=TEXT_COLOR,
|
||||
width=1,
|
||||
)
|
||||
|
||||
# Etiqueta del radio
|
||||
label_x = center_x + radius_pixels - 20
|
||||
label_y = center_y - 10
|
||||
draw.text((label_x, label_y), f"{radius_meters}m", fill=TEXT_COLOR)
|
||||
|
||||
# Patrones de líneas finas para detectar aliasing
|
||||
for i in range(0, 10):
|
||||
y = meters_to_pixels(10 + i * 0.2)
|
||||
draw.line(
|
||||
[(meters_to_pixels(60), y), (meters_to_pixels(75), y)],
|
||||
fill=TEXT_COLOR,
|
||||
width=1,
|
||||
)
|
||||
|
||||
|
||||
def create_reference_image():
|
||||
"""Función principal que crea la imagen de referencia"""
|
||||
print(f"Generando imagen de referencia del canvas...")
|
||||
print(
|
||||
f"Dimensiones del canvas: {CANVAS_WIDTH_METERS} × {CANVAS_HEIGHT_METERS} metros"
|
||||
)
|
||||
print(f"Dimensiones de imagen: {IMAGE_WIDTH} × {IMAGE_HEIGHT} píxeles")
|
||||
print(f"Resolución: {PIXELS_PER_METER} píxeles por metro")
|
||||
|
||||
# Crear imagen base
|
||||
image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT), BACKGROUND_COLOR)
|
||||
|
||||
# Crear fondos de zona
|
||||
create_zone_backgrounds(image)
|
||||
|
||||
# Crear objeto draw
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# Dibujar grilla
|
||||
draw_grid(draw, IMAGE_WIDTH, IMAGE_HEIGHT)
|
||||
|
||||
# Fuente para texto
|
||||
try:
|
||||
font = ImageFont.truetype("arial.ttf", 16)
|
||||
title_font = ImageFont.truetype("arial.ttf", 24)
|
||||
except:
|
||||
font = ImageFont.load_default()
|
||||
title_font = ImageFont.load_default()
|
||||
|
||||
# Etiquetas de coordenadas
|
||||
draw_coordinate_labels(draw, font)
|
||||
|
||||
# Título e información
|
||||
title_text = "CtrEditor Canvas Reference Background"
|
||||
info_text = f"{CANVAS_WIDTH_METERS}m × {CANVAS_HEIGHT_METERS}m | {PIXELS_PER_METER} px/m | {datetime.now().strftime('%Y-%m-%d %H:%M')}"
|
||||
|
||||
draw.text((20, 20), title_text, fill=TEXT_COLOR, font=title_font)
|
||||
draw.text((20, 50), info_text, fill=TEXT_COLOR, font=font)
|
||||
|
||||
# Colocar QR codes
|
||||
place_qr_codes(image)
|
||||
|
||||
# Patrones de escala
|
||||
draw_scale_patterns(image)
|
||||
|
||||
# Información de los objetos conocidos (área de interés)
|
||||
objects_area = {"left": 39.92, "top": 19.76, "right": 44.99, "bottom": 20.47}
|
||||
|
||||
# Dibujar rectángulo de área de objetos
|
||||
x1 = meters_to_pixels(objects_area["left"])
|
||||
y1 = meters_to_pixels(objects_area["top"])
|
||||
x2 = meters_to_pixels(objects_area["right"])
|
||||
y2 = meters_to_pixels(objects_area["bottom"])
|
||||
|
||||
draw.rectangle([x1, y1, x2, y2], outline=(255, 0, 0), width=3)
|
||||
draw.text((x1, y1 - 25), "ÁREA DE OBJETOS", fill=(255, 0, 0), font=font)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def main():
|
||||
"""Función principal"""
|
||||
try:
|
||||
# Crear imagen
|
||||
image = create_reference_image()
|
||||
|
||||
# Guardar imagen
|
||||
filename = "canvas_reference_background.png"
|
||||
image.save(filename, "PNG", optimize=True)
|
||||
|
||||
print(f"\n✅ Imagen generada exitosamente: {filename}")
|
||||
print(f"📐 Tamaño de archivo: {image.size}")
|
||||
print(f"🎯 QR codes contienen información de posición y calibración")
|
||||
print(f"📏 Grilla cada 1m (líneas gruesas cada 5m)")
|
||||
print(f"🎨 4 zonas de color para identificación visual")
|
||||
print(f"📍 Área de objetos marcada en rojo")
|
||||
|
||||
# Información para uso
|
||||
print(f"\n📋 Para usar en CtrEditor:")
|
||||
print(f"1. Cargar '{filename}' como imagen de fondo del canvas")
|
||||
print(f"2. Tomar capturas de pantalla y analizar los QR codes")
|
||||
print(f"3. Verificar grilla y patrones para detectar problemas de escala")
|
||||
print(f"4. Los QR codes contienen información JSON con coordenadas exactas")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error generando imagen: {e}")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
|
@ -1,8 +0,0 @@
|
|||
# Dependencias para el generador de imagen de referencia del canvas
|
||||
Pillow>=9.0.0
|
||||
qrcode[pil]>=7.0.0
|
||||
numpy>=1.21.0
|
||||
|
||||
# Dependencias adicionales para el analizador de capturas
|
||||
opencv-python>=4.5.0
|
||||
pyzbar>=0.1.9
|
Loading…
Reference in New Issue