Compare commits

...

96 Commits

Author SHA1 Message Date
Miguel 256d86aca5 Se realizaron actualizaciones en múltiples clases para mejorar la legibilidad y la organización de las propiedades. Se añadieron nuevos atributos de descripción y nombre en varias clases, como ucBasicExample, ucTransporteCurva, y ucTransporteGuias, facilitando la identificación de los elementos en la interfaz de usuario. Además, se eliminaron archivos innecesarios del proyecto y se ajustaron las categorías de propiedades para una mejor clasificación. Estas mejoras optimizan la experiencia del usuario y la gestión de los objetos en la simulación. 2025-06-24 21:35:17 +02:00
Miguel 3bc314182c Se realizaron mejoras en la clase MainViewModel al agregar espacios en blanco para mejorar la legibilidad. En TagEditorAttribute, se añadieron nuevos atributos y se implementó la lógica para manejar nombres personalizados de propiedades. En UserControlFactory, se optimizó la obtención de nombres de propiedades y se eliminaron espacios en blanco innecesarios. Finalmente, se añadió un atributo Name en ucTransporteTTop para el coeficiente de fricción, mejorando la claridad en la interfaz de usuario. 2025-06-24 17:45:47 +02:00
Miguel 6928088691 Se añadieron nuevas propiedades y métodos en el control CircularSegment para gestionar guías visuales, incluyendo la distancia, grosor y color de las guías. Se implementó la lógica para dibujar las guías en el segmento circular, mejorando la visualización y personalización del control. 2025-06-24 17:32:17 +02:00
Miguel 81329e4c09 Se añadieron nuevas propiedades y métodos en la clase ucTransporteCurvaGuias para gestionar guías curvas, incluyendo la creación y actualización de segmentos de guías. Se implementaron validaciones para el número de segmentos y se mejoró la lógica de actualización de geometrías al cambiar propiedades relevantes. 2025-06-24 17:07:37 +02:00
Miguel b6f616f6cc Agregado del control TransportCurva con Guias 2025-06-24 16:53:03 +02:00
Miguel 1449544d71 Se actualizaron las propiedades en las clases osBase, ucCustomImage y ucVMmotorSim para utilizar el nuevo atributo [property: JsonIgnore], mejorando la gestión de la serialización JSON. Además, se ajustó la configuración del serializador en StateSerializer.cs para respetar los atributos JsonIgnore, optimizando la deserialización de objetos. 2025-06-24 11:17:34 +02:00
Miguel 5c2daaeb98 Se añadió un estilo global para TreeViewItem en App.xaml para evitar errores de binding. Se actualizaron las referencias de paquetes en CtrEditor.csproj, cambiando la versión de LiveChartsCore.SkiaSharpView.WPF y añadiendo SkiaSharp.Views.WPF. Se mejoró la gestión de carga de imágenes en osBase.cs y ucCustomImage.xaml.cs, implementando un manejo de errores más robusto y estableciendo imágenes por defecto en caso de fallos. Se ajustó el XAML de ucBoolTag para mejorar la conversión de color. Se implementó un convertidor seguro para ImageSource en StateSerializer.cs, mejorando la deserialización de imágenes. 2025-06-24 10:59:58 +02:00
Miguel 3af9ad99d8 Se añadió un nuevo parámetro de ángulo en el método CrearAnimacionStoryBoardTrasnporteCircular en la clase osBase, permitiendo ajustar la dirección de la animación según el valor del ángulo. Se actualizaron las llamadas a este método en ucTransporteCurva para incluir el nuevo parámetro, mejorando la lógica de animación en función de la dirección y el ángulo proporcionado. 2025-06-23 23:40:48 +02:00
Miguel da8d0516cb Se añadió la funcionalidad para crear y actualizar animaciones de transporte circular en la clase osBase y se integró en ucTransporteCurva. Se implementaron métodos para gestionar la dirección de las animaciones y se mejoró la lógica de actualización de geometrías. Además, se realizaron ajustes en el XAML de CircularSegment para incluir un patrón visual en el Path. 2025-06-23 22:10:00 +02:00
Miguel ac8773ebc7 Se realizaron mejoras en la gestión de objetos visuales en la clase ObjectManipulationManager. Se optimizó la lógica para purgar objetos eliminados y se mejoró la rotación de objetos, permitiendo rotaciones en incrementos de 45 grados al mantener presionada la tecla Shift. Además, se ajustaron espacios en blanco y se mejoró la legibilidad del código en varias secciones. 2025-06-23 21:36:53 +02:00
Miguel 75c507be4e Se añadieron nuevas propiedades relacionadas con el encoder en la clase ucVMmotorSim, permitiendo la lectura del valor actual de la posición del encoder y la habilitación de su uso. Se eliminaron instancias innecesarias de Stopwatch en las clases ucEncoderMotor y ucEncoderMotorLineal, optimizando el código. Además, se realizaron ajustes en la interfaz de usuario de ucBoolTag para incluir una opción de visualización de descripción. 2025-06-23 15:29:40 +02:00
Miguel fefc0a700d Se añadió un nuevo método para verificar si un objeto visual es un hijo visual de otro, mejorando la gestión de transformaciones en la clase osBase. Además, se implementaron mejoras en la obtención de coordenadas de rectángulos, incluyendo validaciones para asegurar que los rectángulos estén cargados y disponibles antes de realizar transformaciones. Se mejoró la lógica de creación de geometría en ucTransporteTTop, asegurando que el layout esté actualizado y manejando excepciones para evitar fallos en la creación de simulaciones. 2025-06-22 16:44:19 +02:00
Miguel 58781c13a3 Se implementó una nueva ventana de configuración de escala que permite a los usuarios ajustar la escala de simulación de manera modeless. Se añadió un temporizador para aplicar automáticamente los cambios de escala después de 0.5 segundos. Además, se mejoró la gestión de la escala en el ViewModel principal y se actualizaron los bindings de posición en osBase para asegurar una correcta visualización. Se incluyó un botón de "Aplicar" en la interfaz de usuario para facilitar la aplicación de cambios. 2025-06-18 21:23:39 +02:00
Miguel b48dbeb76e Se añadió un nuevo método para configurar la escala desde el menú contextual en MainViewModel, permitiendo a los usuarios ajustar la escala de simulación. Se implementó la lógica para detener simulaciones, actualizar la escala en el convertidor de unidades y forzar el redibujo del canvas. Además, se agregó una opción en el menú contextual de MainWindow para acceder a esta funcionalidad. Se mejoró la gestión de bindings de posición y tamaño en osBase para asegurar actualizaciones adecuadas tras cambios de escala. 2025-06-18 19:54:51 +02:00
Miguel ca70f66ff1 Se añadió la funcionalidad para cargar datos de imágenes desde archivos JSON en la clase DatosDeTrabajo, mejorando la gestión de imágenes. Se implementó un nuevo método para obtener configuraciones de serialización JSON y se mejoró la lógica de carga de datos, incluyendo compatibilidad con versiones anteriores. Además, se actualizó el método de obtención de nombres de imágenes en MainViewModel para incluir etiquetas, y se ajustó el convertidor correspondiente en ImageDisplayNameConverter. 2025-06-18 18:55:04 +02:00
Miguel 354b4a8acf Se mejoró la funcionalidad de renombrado de imágenes en la interfaz, integrando un PropertyGrid para editar propiedades de imágenes, incluyendo etiquetas. Se actualizó la lógica para eliminar entradas vacías y se modificó el diseño de la ventana de renombrado para una mejor experiencia de usuario. Además, se implementó un editor de etiquetas que permite gestionar etiquetas de manera más eficiente. 2025-06-18 15:20:26 +02:00
Miguel 909e438f5b Se añadió la capacidad de gestionar datos de imágenes en la clase DatosDeTrabajo, permitiendo la carga de datos desde archivos JSON y la integración con MainViewModel. Se implementó un nuevo método para establecer el ViewModel principal y se mejoró la lógica de renombrado de imágenes en la interfaz de usuario, incluyendo un comando para renombrar imágenes desde el contexto del ListBox. Además, se incorporó un convertidor para mostrar nombres de imágenes personalizados en la interfaz. 2025-06-18 13:40:49 +02:00
Miguel 9b710fcb00 Se implementó un sistema para gestionar múltiples ventanas de biblioteca, permitiendo la activación de una ventana existente o la creación de una nueva. Además, se mejoró la lógica de pegado de objetos, integrando la validación del contenido del portapapeles y la capacidad de pegar desde el portapapeles del sistema. Se añadió un método para recargar la imagen actual después de pegar en el proyecto actual, mejorando la experiencia del usuario al gestionar objetos en la biblioteca. 2025-06-18 11:49:22 +02:00
Miguel c03f6970d8 Se añadió la clase LibraryWindowSettings para gestionar la configuración de la ventana de la biblioteca, incluyendo propiedades para dimensiones y posición. Se implementó la persistencia de estas configuraciones al abrir y cerrar la ventana. Además, se mejoró la interfaz de usuario con un TreeView jerárquico para la gestión de bibliotecas y se añadieron comandos para crear y eliminar directorios de bibliotecas. Se implementó la selección múltiple de objetos en la ventana de la biblioteca, mejorando la experiencia del usuario al gestionar objetos. 2025-06-18 02:11:38 +02:00
Miguel c353f6c6ea Se añadió la funcionalidad de gestión de bibliotecas de objetos en la interfaz de usuario, incluyendo un nuevo comando para abrir la ventana de gestión de bibliotecas. Se incorporó una nueva propiedad en la clase EstadoPersistente para almacenar directorios de bibliotecas y se realizaron mejoras en la estructura del código para una mayor claridad y mantenimiento. 2025-06-18 01:24:28 +02:00
Miguel 67fa5eef3d Se implementó un sistema de filtrado por etiquetas en la interfaz de usuario, permitiendo a los usuarios buscar y seleccionar objetos basados en etiquetas personalizadas. Se añadieron nuevas propiedades y métodos en la clase osBase para gestionar etiquetas, así como mejoras en la lógica de actualización de filtros en función de los objetos disponibles. Además, se realizaron ajustes en la visualización y manejo de los filtros en el control osVisFilter. 2025-06-17 18:38:00 +02:00
Miguel 99248e9112 Se implementaron mejoras en la gestión de copias de objetos seleccionados, reutilizando la lógica de duplicación para crear copias serializables. Se agregó manejo de errores al intentar copiar objetos y se implementaron nuevas propiedades en la clase osBase para gestionar el punto de pivote en la rotación. Además, se aseguraron validaciones para evitar diámetros inválidos en la simulación, garantizando un comportamiento más robusto en la manipulación de objetos. 2025-06-17 17:35:35 +02:00
Miguel f85c707cfc Se añadieron mejoras en la gestión de la manipulación de objetos rotados, incluyendo métodos para adaptar los cursores y el comportamiento de los handles según la rotación del objeto. Se implementó la transformación de cambios del mouse en coordenadas locales para un redimensionamiento más intuitivo. Además, se documentaron las nuevas funcionalidades en la clase ObjectManipulationManager. 2025-06-14 22:48:00 +02:00
Miguel 88e6de77cb Se implementaron métodos para actualizar la posición relativa de los objetos en función de un FramePlate, incluyendo la gestión de la rotación orbital. Se añadieron propiedades para almacenar la posición relativa y el ángulo inicial, y se mejoró la lógica de actualización de posición al cambiar la posición del FramePlate. Además, se implementó un método para recalcular la posición relativa al mover los objetos, asegurando un comportamiento más preciso en la simulación. 2025-06-14 17:07:05 +02:00
Miguel 94b11cf068 Se implementó un sistema de gestión de historial de deshacer (undo) en la aplicación, permitiendo capturar y restaurar estados de objetos seleccionados. Se añadieron métodos para limpiar el historial y se mejoró la interfaz de usuario para mostrar información sobre el estado del historial de deshacer. Además, se realizaron ajustes en la lógica de manipulación de objetos para asegurar la correcta captura de estados antes de movimientos y redimensionamientos. 2025-06-14 16:47:25 +02:00
Miguel 20467c88ae Se agregaron nuevas propiedades y métodos para gestionar el ángulo de rotación en la clase ucFramePlate, incluyendo la implementación de encoders para la rotación. Se mejoró la actualización de la posición en función del valor del encoder de rotación y se implementaron cambios en la gestión de eventos para reflejar adecuadamente las modificaciones en el ángulo. 2025-06-13 23:50:19 +02:00
Miguel e935efb0cb Se agregó la función CanPasteFromClipboard para validar el contenido del portapapeles antes de pegar objetos, mejorando la gestión de errores y asegurando que solo se seleccionen objetos con representación visual válida. Se implementó un retraso en la selección para permitir el renderizado completo de los objetos pegados. 2025-06-13 23:28:50 +02:00
Miguel 16f5131803 Implementada la funcionalidad de copiar y pegar objetos seleccionados como JSON desde el portapapeles, incluyendo opciones para reemplazar IDs existentes. Se agregó manejo de errores y validación del contenido del portapapeles para asegurar la correcta deserialización de los objetos. 2025-06-13 22:33:13 +02:00
Miguel 883620b69d Implementada la funcionalidad de bloqueo y desbloqueo de objetos seleccionados en el menú contextual: se agregó un submenú para gestionar el estado de bloqueo de los objetos, actualizando visualmente la selección y marcando cambios sin guardar en el modelo de vista. 2025-06-13 22:07:37 +02:00
Miguel 9c4e87be9b Mejora en la normalización de ángulos en la clase Aether: se implementó un manejo más robusto de los ángulos para casos donde el arco cruza el límite 0/2π, asegurando un cálculo preciso de la superposición y la distancia a los bordes. 2025-06-13 20:59:09 +02:00
Miguel 380bc14b69 Implementada la función para forzar la actualización de bindings en el PropertyGrid antes de limpiar la selección y al cambiar el objeto seleccionado. Se mejoró la gestión del foco en el PropertyGrid para asegurar la correcta actualización de los datos. 2025-06-13 20:48:48 +02:00
Miguel 0d8780b16f Implementada la funcionalidad de bloqueo de movimiento para objetos: se agregó la propiedad 'lock_movement' en la clase osBase y se modificaron las funciones de manipulación de objetos para respetar este bloqueo, evitando redimensionamientos, movimientos y eliminaciones de objetos bloqueados. 2025-06-13 20:16:12 +02:00
Miguel b8d3c953e6 Actualización de la simulación de fluidos: se han agregado nuevos comandos para iniciar y detener la simulación de fluidos, así como métodos para manejar su inicio, detención y actualización. Se han modificado las referencias de Emgu.CV a una versión anterior y se han realizado ajustes en los archivos XAML para reflejar cambios en los espacios de nombres. 2025-06-13 19:52:43 +02:00
Miguel f42a4bb5d1 Compilado 2025-04-13 18:21:03 +02:00
Miguel 53af46ec06 Primeras funciones de Fluidos 2025-04-13 17:07:54 +02:00
Miguel d1ec333243 Agregada funciones de Fluidos 2025-04-13 16:42:18 +02:00
Miguel 20bdad509b Correccion errores de OCR 2025-04-02 17:34:51 +02:00
Miguel fe8f2119ce Mejorado de delete y dumplicate para objetos multiples 2025-03-31 13:40:28 +02:00
Miguel 736068619a Agregado de PaddleOCR y correccion de TagPattern 2025-03-27 14:50:51 +01:00
Miguel 98c5f2e6ff Creada las opciones de datos locales para las posiciones de los objetos globales 2025-03-26 21:12:04 +01:00
Miguel 211c518be6 Agregada la posibilidad de invertir la imagen ucCustomImage 2025-03-26 15:18:58 +01:00
Miguel 304bdb06d4 Agregado posibilidad al Frame de moverse horizontal y verticalmente. Agregado TransporteDualInverter para que sea comandado por dos inverters seleccionables desde un tag. Agregada funcionalidad de cambio de tamaño a las curvas. 2025-03-07 11:00:27 +01:00
Miguel f264efd9ce Cambiado Path de teseract a absoluto en la carpeta de la aplicacion 2025-03-01 23:28:29 +01:00
Miguel 3d70992b1a Creado Control osVisFilter 2025-02-26 11:37:19 +01:00
Miguel b6b078f8ce Cambiado funcionamiento de BuscarCoincidencias para guardar el clip original. 2025-02-25 21:36:07 +01:00
Miguel 3fe845b02f Creado Panel de Edicion de Propiedades para multiples objetos. Multiinstancia. 2025-02-25 14:34:11 +01:00
Miguel e14c28920f Creada una UserControl para PanelEdicion que permita la edicion de los objetos 2025-02-25 11:10:58 +01:00
Miguel 8a5ebe6ac6 Creado un UserControl con los objetos en TreeView para simplificar la seleccion de los objetos 2025-02-24 21:39:15 +01:00
Miguel 621ee8be39 CustomImage con imagen por defecto. Creada visualizcion de tiempo de ciclo. Modificada logica de Preserve_Outside_Transport. Agregada opcion a osFramePlate de showPlate 2025-02-24 16:33:27 +01:00
Miguel 5e95459e3e Agregado un nuevo objeto CustomImage 2025-02-24 11:37:52 +01:00
Miguel 5f680b3a7a Mejorado de las curvas. Se creo un overlapPercentage para las curvas. 2025-02-23 21:22:42 +01:00
Miguel d06607ccc6 Rectangulo de seleccion funcionando para seleccionar multiples objetos 2025-02-21 22:25:14 +01:00
Miguel 061007158d Agregada clase de Serializacion para separar la logica del MainViewModel 2025-02-21 15:12:10 +01:00
Miguel 0a52c543e6 Marcado en purpura el objeto tomado de referencia 2025-02-20 13:17:03 +01:00
Miguel 38ca212d9f Agregado movimiento con flechas de los objetos seleccionados 2025-02-19 21:27:33 +01:00
Miguel 326c615887 Agregado EqualWidth, EqualHeight, EqualAngle, JoinHorizontally, JoinVertically 2025-02-19 14:57:15 +01:00
Miguel 5ee91dd26a Mejorado de panning y zoom 2025-02-18 21:52:27 +01:00
Miguel 67c6464ea1 Mejorada la seleccion de objetos multiples 2025-02-18 18:37:46 +01:00
Miguel 3dab570f5d Multiseleccion funcionando 2025-02-18 18:08:55 +01:00
Miguel 89745d15bf Mejorada la seleccion de objetos. 2025-02-17 15:16:40 +01:00
Miguel 633cd71d00 Mejorado la descripcion de las extracciones de Tag con Patrones 2025-02-17 13:04:21 +01:00
Miguel 8573d942c4 Agregado exportacion a Excel desde la ventana de Analizar la Matriz. Agregada la correccion de las columnas de descripcion. 2025-02-15 22:38:12 +01:00
Miguel 3a2e87cd75 Agregada funcion de analizar la matriz de exportacion de tags. Creada un submenu para cargar los ultimos directorios ustilizados. Cambiado intercambio de datos para los Motores simulados a DINT 2025-02-14 14:04:29 +01:00
Miguel dc164e96ef Agregadas las opciones de consultar si guardar luego de hacer modificaciones y agregado el uso de la tecla Delete para borrar elementos 2025-02-13 16:52:33 +01:00
Miguel e63577e5c3 Modificado Zindex para soportar los Frame 2025-02-13 14:00:47 +01:00
Miguel 9f41401e40 Creado Encoder Lineal. Corregido error de inicio de tiempo en simulacion. Creado Frame Plate para que se muevan los objetos con un encoder lineal. Agregado a los transportes la actualizacion de geometrias en caso de que sean movidos por la interfaz. 2025-01-04 10:34:19 +01:00
Miguel 353b4b99e6 Agregada la funcion de crear el archivo base.png si no hay imagenes en el directorio elegido 2024-09-12 16:43:39 +02:00
Miguel 261fe679d8 Mejorado logica de Zindex para los Panel Plate y los ExtraccionTag. Las osGuia ahora al modificar la posicion se actualiza la simulacion en tiempo real. 2024-07-03 16:12:54 +02:00
Miguel c8abb98c7d Terminada las modificaciones de Movimiento / Angulo / Resize. Creado Enum para zindex 2024-06-30 18:17:44 +02:00
Miguel 8fddbb409b Cambios en la logica de Alto - Ancho y Angulo pasado a osBase 2024-06-30 14:32:32 +02:00
Miguel e09e4e710a Mejorada la implementacion de rotacion y redimensionado. 2024-06-28 19:47:08 +02:00
Miguel 2fe1af47dc Mejorado el Filtrado 2024-06-20 14:50:23 +02:00
Miguel f33273bbf6 Agregado de filtrado de objetossimulables. Hay mejorarlo. 2024-06-13 00:26:02 +02:00
Miguel dc01704da6 Si funcionar del todo con el TreeView 2024-06-11 19:43:12 +02:00
Miguel 51e70b706f Intenado usar TreeView 2024-06-11 13:29:00 +02:00
Miguel 759ee627e2 Mejorado de la exportacion a Excel 2024-06-11 12:30:27 +02:00
Miguel 922a46d616 Creando los botones de Seleccion multiple 2024-06-11 00:22:33 +02:00
Miguel 0f34e6cdaa Modificando BuscarCoincidencias 2024-06-10 11:07:25 +02:00
Miguel 77c9f3db5e Mejorado de Zoom y panning 2024-06-09 21:26:09 +02:00
Miguel 506ee16ae1 Terminado la logica de Groups y con un mecanismo antibucle basico. Faltaria controlar que los nombres de los objetos no puedan ser iguales. 2024-06-09 17:33:09 +02:00
Miguel 2187783fe2 Actualizados todos los objetos Transporte con la nueva logica 2024-06-09 10:55:21 +02:00
Miguel c58a264d38 Mejorando sistema de Links entre Objetos. Usando IItemsSource y suscribiendose al evento de cambio de Nombre. De esta forma se mantiene un enlace por string mas simple para serializar 2024-06-09 10:39:31 +02:00
Miguel 0305ae2506 Creado BuscarCoincidencias, implementando logica 2024-06-06 16:53:00 +02:00
Miguel 84725cc8d6 Separando los ObjetosSimulables para todas las paginas de los individuales 2024-06-05 22:27:53 +02:00
Miguel 84e7ac1c28 Agregado de ExtracionTag 2024-06-04 22:27:35 +02:00
Miguel 0410c87e93 Agregado del TextPlate 2024-06-04 17:33:00 +02:00
Miguel 1ce0371d18 Trabajando con los decimales 2024-06-02 19:14:35 +02:00
Miguel a1ecfca034 Con Float converter en el PropertyGrid 2024-06-02 17:04:58 +02:00
Miguel 47735ef00a Xceed PropetyGrid 2024-06-02 09:13:01 +02:00
Miguel 667cd18f5d Mejorado de CargarPropiedadesosDatos 2024-06-01 00:34:58 +02:00
Miguel c1ac20964e TransporteUnion Terminado 2024-05-31 19:25:24 +02:00
Miguel 268b66ad76 Creando GuiasUnion 2024-05-31 15:06:49 +02:00
Miguel c4892b1f36 Creado parametro para invertir direccion de marcha en los transportes. 2024-05-30 19:18:33 +02:00
Miguel 09980689fb Adaptacion con simCurve 2024-05-30 18:48:37 +02:00
Miguel 56f8630a65 Agregado Trace3 2024-05-27 10:34:20 +02:00
Miguel c66be28764 Creacion de la opcion EsFreno para los transportes 2024-05-26 11:50:25 +02:00
218 changed files with 31235 additions and 2678 deletions

26
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/net8.0-windows8.0/CtrEditor.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/CtrEditor.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/CtrEditor.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/CtrEditor.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@ -1,9 +1,33 @@
<Application x:Class="CtrEditor.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CtrEditor"
StartupUri="MainWindow.xaml">
<Application x:Class="CtrEditor.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CtrEditor"
xmlns:osExtraccion="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos"
xmlns:os="clr-namespace:CtrEditor.ObjetosSim" StartupUri="MainWindow.xaml">
<Application.Resources>
<local:MeterToPixelConverter x:Key="MeterToPixelConverter" />
<local:LevelToHeightMultiConverter x:Key="LevelToHeightMultiConverter" />
<local:WidthPercentageConverter x:Key="WidthPercentageConverter" />
<local:DistanceToMarginConverter x:Key="DistanceToMarginConverter" />
<local:MarginConverter x:Key="MarginConverter" />
<local:FloatToFormattedStringConverter x:Key="floatFormatter" />
<local:DoubleToFormattedStringConverter x:Key="doubleFormatter" />
<local:BrushToColorNameConverter x:Key="BrushToColorNameConverter" />
<local:VerticalPositionConverter x:Key="VerticalPositionConverter" />
<local:SumConverter x:Key="SumConverter" />
<local:ColorToBrushConverter x:Key="ColorToBrushConverter" />
<local:StringToBrushConverter x:Key="StringToBrushConverter" />
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosBuscarCoincidencias"
TargetType="{x:Type osExtraccion:osBuscarCoincidencias}" />
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosVMMotor" TargetType="{x:Type os:osVMmotorSim}" />
<local:UnsavedChangesConverter x:Key="UnsavedChangesConverter"/>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<!-- Estilo global para TreeViewItem para evitar errores de binding -->
<Style TargetType="TreeViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
</Application.Resources>
</Application>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,668 @@
[
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Ingresos E0.0",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Ingresos Plate Group",
"Tag_extract": "E3.0\n",
"Collumn_name": "IO",
"Collumn_number": 2,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 1202
},
"Show_On_This_Page": false,
"Left": 2.0471327,
"Top": 8.451157,
"Ancho": 1.3239104,
"Alto": 0.19122289,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_050.png",
"94102.80_SE00_Página_051.png",
"94102.80_SE00_Página_052.png",
"94102.80_SE00_Página_053.png",
"94102.80_SE00_Página_054.png",
"94102.80_SE00_Página_055.png",
"94102.80_SE00_Página_056.png",
"94102.80_SE00_Página_057.png",
"94102.80_SE00_Página_058.png",
"94102.80_SE00_Página_059.png",
"94102.80_SE00_Página_060.png",
"94102.80_SE00_Página_061.png",
"94102.80_SE00_Página_062.png",
"94102.80_SE00_Página_063.png",
"94102.80_SE00_Página_064.png",
"94102.80_SE00_Página_065.png",
"94102.80_SE00_Página_066.png",
"94102.80_SE00_Página_067.png",
"94102.80_SE00_Página_068.png",
"94102.80_SE00_Página_069.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Ingresos Desc Italiano",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Ingresos Plate Group",
"Tag_extract": "MARCIA ASCIUGATORE\n",
"Collumn_name": "Description Italian",
"Collumn_number": 3,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 1203
},
"Show_On_This_Page": false,
"Left": 2.0339866,
"Top": 9.252491,
"Ancho": 1.3151709,
"Alto": 0.26124278,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_050.png",
"94102.80_SE00_Página_051.png",
"94102.80_SE00_Página_052.png",
"94102.80_SE00_Página_053.png",
"94102.80_SE00_Página_054.png",
"94102.80_SE00_Página_055.png",
"94102.80_SE00_Página_056.png",
"94102.80_SE00_Página_057.png",
"94102.80_SE00_Página_058.png",
"94102.80_SE00_Página_059.png",
"94102.80_SE00_Página_060.png",
"94102.80_SE00_Página_061.png",
"94102.80_SE00_Página_062.png",
"94102.80_SE00_Página_063.png",
"94102.80_SE00_Página_064.png",
"94102.80_SE00_Página_065.png",
"94102.80_SE00_Página_066.png",
"94102.80_SE00_Página_067.png",
"94102.80_SE00_Página_068.png",
"94102.80_SE00_Página_069.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Ingresos Desc English",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Ingresos Plate Group",
"Tag_extract": "EMERGENCY PRESSED\nELECTRICAL CABINET\n",
"Collumn_name": "Description English",
"Collumn_number": 4,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 1204
},
"Show_On_This_Page": false,
"Left": 2.0329113,
"Top": 9.509013,
"Ancho": 1.323141,
"Alto": 0.2408835,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_050.png",
"94102.80_SE00_Página_051.png",
"94102.80_SE00_Página_052.png",
"94102.80_SE00_Página_053.png",
"94102.80_SE00_Página_054.png",
"94102.80_SE00_Página_055.png",
"94102.80_SE00_Página_056.png",
"94102.80_SE00_Página_057.png",
"94102.80_SE00_Página_058.png",
"94102.80_SE00_Página_059.png",
"94102.80_SE00_Página_060.png",
"94102.80_SE00_Página_061.png",
"94102.80_SE00_Página_062.png",
"94102.80_SE00_Página_063.png",
"94102.80_SE00_Página_064.png",
"94102.80_SE00_Página_065.png",
"94102.80_SE00_Página_066.png",
"94102.80_SE00_Página_067.png",
"94102.80_SE00_Página_068.png",
"94102.80_SE00_Página_069.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Ingresos Card",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Ingresos Plate Group",
"Tag_extract": "A45311\n",
"Collumn_name": "Card",
"Collumn_number": 5,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 1260
},
"Show_On_This_Page": false,
"Left": 2.2931812,
"Top": 9.125617,
"Ancho": 0.3932013,
"Alto": 0.11076093,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_050.png",
"94102.80_SE00_Página_051.png",
"94102.80_SE00_Página_052.png",
"94102.80_SE00_Página_053.png",
"94102.80_SE00_Página_054.png",
"94102.80_SE00_Página_055.png",
"94102.80_SE00_Página_056.png",
"94102.80_SE00_Página_057.png",
"94102.80_SE00_Página_058.png",
"94102.80_SE00_Página_059.png",
"94102.80_SE00_Página_060.png",
"94102.80_SE00_Página_061.png",
"94102.80_SE00_Página_062.png",
"94102.80_SE00_Página_063.png",
"94102.80_SE00_Página_064.png",
"94102.80_SE00_Página_065.png",
"94102.80_SE00_Página_066.png",
"94102.80_SE00_Página_067.png",
"94102.80_SE00_Página_068.png",
"94102.80_SE00_Página_069.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Ingresos Pin",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Ingresos Plate Group",
"Tag_extract": "",
"Collumn_name": "Pin",
"Collumn_number": 7,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 1264
},
"Show_On_This_Page": false,
"Left": 2.6119895,
"Top": 8.204032,
"Ancho": 0.16785541,
"Alto": 0.15966734,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_050.png",
"94102.80_SE00_Página_051.png",
"94102.80_SE00_Página_052.png",
"94102.80_SE00_Página_053.png",
"94102.80_SE00_Página_054.png",
"94102.80_SE00_Página_055.png",
"94102.80_SE00_Página_056.png",
"94102.80_SE00_Página_057.png",
"94102.80_SE00_Página_058.png",
"94102.80_SE00_Página_059.png",
"94102.80_SE00_Página_060.png",
"94102.80_SE00_Página_061.png",
"94102.80_SE00_Página_062.png",
"94102.80_SE00_Página_063.png",
"94102.80_SE00_Página_064.png",
"94102.80_SE00_Página_065.png",
"94102.80_SE00_Página_066.png",
"94102.80_SE00_Página_067.png",
"94102.80_SE00_Página_068.png",
"94102.80_SE00_Página_069.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osBuscarCoincidencias, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Ingresos Plate Group",
"Search_rectangles": [
"200.6319122314453,815.1671752929688,137.59445190429688,161.43350219726562",
"350.22589111328125,815.4871215820312,137.59445190429688,161.43350219726562",
"499.9798583984375,815.4871215820312,137.59445190429688,161.43350219726562",
"649.7338256835938,815.4871215820312,137.59445190429688,161.43350219726562",
"799.6477661132812,815.4871215820312,137.59445190429688,161.43350219726562",
"949.2417602539062,815.4871215820312,137.59445190429688,161.43350219726562",
"1099.15576171875,815.4871215820312,137.59445190429688,161.43350219726562",
"1248.90966796875,815.4871215820312,137.59445190429688,161.43350219726562"
],
"Search_templates": false,
"Export_ocr": false,
"Opacity_oculto": 0.1,
"Show_debug_ocr": false,
"Threshold": 0.6,
"Coincidencias": 8,
"Id": {
"Value": 1201
},
"Show_On_This_Page": false,
"Left": 2.0069747,
"Top": 8.15955,
"Ancho": 1.3769451,
"Alto": 1.6148156,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_050.png",
"94102.80_SE00_Página_051.png",
"94102.80_SE00_Página_052.png",
"94102.80_SE00_Página_053.png",
"94102.80_SE00_Página_054.png",
"94102.80_SE00_Página_055.png",
"94102.80_SE00_Página_056.png",
"94102.80_SE00_Página_057.png",
"94102.80_SE00_Página_058.png",
"94102.80_SE00_Página_059.png",
"94102.80_SE00_Página_060.png",
"94102.80_SE00_Página_061.png",
"94102.80_SE00_Página_062.png",
"94102.80_SE00_Página_063.png",
"94102.80_SE00_Página_064.png",
"94102.80_SE00_Página_065.png",
"94102.80_SE00_Página_066.png",
"94102.80_SE00_Página_067.png",
"94102.80_SE00_Página_068.png",
"94102.80_SE00_Página_069.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Page",
"Extraer": false,
"New_Row": false,
"Tag_extract": "690\n",
"Collumn_name": "Extraccion Tags",
"Collumn_number": 1,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 1231
},
"Show_On_This_Page": false,
"Left": 13.666122,
"Top": 10.413534,
"Ancho": 0.81436056,
"Alto": 0.2296054,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_050.png",
"94102.80_SE00_Página_051.png",
"94102.80_SE00_Página_052.png",
"94102.80_SE00_Página_053.png",
"94102.80_SE00_Página_054.png",
"94102.80_SE00_Página_055.png",
"94102.80_SE00_Página_056.png",
"94102.80_SE00_Página_057.png",
"94102.80_SE00_Página_058.png",
"94102.80_SE00_Página_059.png",
"94102.80_SE00_Página_060.png",
"94102.80_SE00_Página_061.png",
"94102.80_SE00_Página_062.png",
"94102.80_SE00_Página_063.png",
"94102.80_SE00_Página_064.png",
"94102.80_SE00_Página_065.png",
"94102.80_SE00_Página_066.png",
"94102.80_SE00_Página_067.png",
"94102.80_SE00_Página_068.png",
"94102.80_SE00_Página_069.png",
"94102.80_SE00_Página_070.png",
"94102.80_SE00_Página_071.png",
"94102.80_SE00_Página_072.png",
"94102.80_SE00_Página_073.png",
"94102.80_SE00_Página_074.png",
"94102.80_SE00_Página_075.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Salidas A0.0",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Salidas Plate Group",
"Tag_extract": "E3.0\n",
"Collumn_name": "IO",
"Collumn_number": 2,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 63
},
"Show_On_This_Page": false,
"Left": 2.038601,
"Top": 2.4818933,
"Ancho": 1.3239104,
"Alto": 0.19122289,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_070.png",
"94102.80_SE00_Página_071.png",
"94102.80_SE00_Página_072.png",
"94102.80_SE00_Página_073.png",
"94102.80_SE00_Página_074.png",
"94102.80_SE00_Página_075.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Salidas Desc Italiano",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Salidas Plate Group",
"Tag_extract": "MARCIA ASCIUGATORE\n",
"Collumn_name": "Description Italian",
"Collumn_number": 3,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 64
},
"Show_On_This_Page": false,
"Left": 2.0427365,
"Top": 1.3802159,
"Ancho": 1.3151709,
"Alto": 0.26124278,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_070.png",
"94102.80_SE00_Página_071.png",
"94102.80_SE00_Página_072.png",
"94102.80_SE00_Página_073.png",
"94102.80_SE00_Página_074.png",
"94102.80_SE00_Página_075.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Salidas Desc English",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Salidas Plate Group",
"Tag_extract": "EMERGENCY PRESSED\nELECTRICAL CABINET\n",
"Collumn_name": "Description English",
"Collumn_number": 4,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 65
},
"Show_On_This_Page": false,
"Left": 2.0372825,
"Top": 1.6332868,
"Ancho": 1.323141,
"Alto": 0.2408835,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_070.png",
"94102.80_SE00_Página_071.png",
"94102.80_SE00_Página_072.png",
"94102.80_SE00_Página_073.png",
"94102.80_SE00_Página_074.png",
"94102.80_SE00_Página_075.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Salidas Card",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Salidas Plate Group",
"Tag_extract": "A45311\n",
"Collumn_name": "Card",
"Collumn_number": 5,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 66
},
"Show_On_This_Page": false,
"Left": 2.3019588,
"Top": 1.9661419,
"Ancho": 0.3932013,
"Alto": 0.11076093,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_070.png",
"94102.80_SE00_Página_071.png",
"94102.80_SE00_Página_072.png",
"94102.80_SE00_Página_073.png",
"94102.80_SE00_Página_074.png",
"94102.80_SE00_Página_075.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Salidas Pin",
"Extraer": false,
"New_Row": false,
"Id_Search_Templates": "Salidas Plate Group",
"Tag_extract": "",
"Collumn_name": "Pin",
"Collumn_number": 7,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 68
},
"Show_On_This_Page": false,
"Left": 2.613817,
"Top": 2.771892,
"Ancho": 0.16785541,
"Alto": 0.15966734,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_070.png",
"94102.80_SE00_Página_071.png",
"94102.80_SE00_Página_072.png",
"94102.80_SE00_Página_073.png",
"94102.80_SE00_Página_074.png",
"94102.80_SE00_Página_075.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osBuscarCoincidencias, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Salidas Plate Group",
"Search_rectangles": [
"200.471923828125,134.23458862304688,137.59445190429688,161.43350219726562",
"350.22589111328125,134.39459228515625,137.59445190429688,161.43350219726562",
"499.8198547363281,134.55458068847656,137.59445190429688,161.43350219726562",
"649.5738525390625,134.55458068847656,137.59445190429688,161.43350219726562",
"799.3278198242188,134.55458068847656,137.59445190429688,161.43350219726562",
"949.2417602539062,134.55458068847656,137.59445190429688,161.43350219726562",
"1098.9957275390625,134.55458068847656,137.59445190429688,161.43350219726562",
"1248.749755859375,134.55458068847656,137.59445190429688,161.43350219726562",
"1398.503662109375,134.87457275390625,137.59445190429688,161.43350219726562"
],
"Search_templates": false,
"Export_ocr": false,
"Opacity_oculto": 0.1,
"Show_debug_ocr": false,
"Threshold": 0.6,
"Coincidencias": 9,
"Id": {
"Value": 69
},
"Show_On_This_Page": false,
"Left": 2.0056293,
"Top": 1.3495445,
"Ancho": 1.3769451,
"Alto": 1.6148156,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_070.png",
"94102.80_SE00_Página_071.png",
"94102.80_SE00_Página_072.png",
"94102.80_SE00_Página_073.png",
"94102.80_SE00_Página_074.png",
"94102.80_SE00_Página_075.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Descrip Pagina",
"Extraer": false,
"New_Row": false,
"Tag_extract": "690\n",
"Collumn_name": "Descrip Pagina",
"Collumn_number": 1,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 70
},
"Show_On_This_Page": false,
"Left": 7.490359,
"Top": 10.734316,
"Ancho": 1.8668232,
"Alto": 0.17921503,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_050.png",
"94102.80_SE00_Página_051.png",
"94102.80_SE00_Página_052.png",
"94102.80_SE00_Página_053.png",
"94102.80_SE00_Página_054.png",
"94102.80_SE00_Página_055.png",
"94102.80_SE00_Página_056.png",
"94102.80_SE00_Página_057.png",
"94102.80_SE00_Página_058.png",
"94102.80_SE00_Página_059.png",
"94102.80_SE00_Página_060.png",
"94102.80_SE00_Página_061.png",
"94102.80_SE00_Página_062.png",
"94102.80_SE00_Página_063.png",
"94102.80_SE00_Página_064.png",
"94102.80_SE00_Página_065.png",
"94102.80_SE00_Página_066.png",
"94102.80_SE00_Página_067.png",
"94102.80_SE00_Página_068.png",
"94102.80_SE00_Página_069.png",
"94102.80_SE00_Página_070.png",
"94102.80_SE00_Página_071.png",
"94102.80_SE00_Página_072.png",
"94102.80_SE00_Página_073.png",
"94102.80_SE00_Página_074.png",
"94102.80_SE00_Página_075.png"
],
"Cloned": false
},
{
"$type": "CtrEditor.ObjetosSim.Extraccion_Datos.osExtraccionTag, CtrEditor",
"AutoCreated": false,
"RemoverDesdeSimulacion": false,
"Nombre": "Comessa",
"Extraer": false,
"New_Row": false,
"Tag_extract": "690\n",
"Collumn_name": "Comessa",
"Collumn_number": 1,
"Copy_Number": 0,
"Show_Debug_Window": false,
"Opacity_oculto": 0.1,
"Id": {
"Value": 71
},
"Show_On_This_Page": false,
"Left": 11.119476,
"Top": 10.6297865,
"Ancho": 0.5749816,
"Alto": 0.14187858,
"Angulo": 0.0,
"Enable_On_All_Pages": true,
"ShowOnThisPagesList": [
"94102.80_SE00_Página_050.png",
"94102.80_SE00_Página_051.png",
"94102.80_SE00_Página_052.png",
"94102.80_SE00_Página_053.png",
"94102.80_SE00_Página_054.png",
"94102.80_SE00_Página_055.png",
"94102.80_SE00_Página_056.png",
"94102.80_SE00_Página_057.png",
"94102.80_SE00_Página_058.png",
"94102.80_SE00_Página_059.png",
"94102.80_SE00_Página_060.png",
"94102.80_SE00_Página_061.png",
"94102.80_SE00_Página_062.png",
"94102.80_SE00_Página_063.png",
"94102.80_SE00_Página_064.png",
"94102.80_SE00_Página_065.png",
"94102.80_SE00_Página_066.png",
"94102.80_SE00_Página_067.png",
"94102.80_SE00_Página_068.png",
"94102.80_SE00_Página_069.png",
"94102.80_SE00_Página_070.png",
"94102.80_SE00_Página_071.png",
"94102.80_SE00_Página_072.png",
"94102.80_SE00_Página_073.png",
"94102.80_SE00_Página_074.png",
"94102.80_SE00_Página_075.png"
],
"Cloned": false
}
]

View File

View File

@ -0,0 +1,22 @@
<UserControl x:Class="CtrEditor.Controls.ObjectHierarchyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:CtrEditor.Controls"
mc:Ignorable="d">
<Grid>
<TreeView x:Name="ObjectsTreeView" ItemsSource="{Binding RootNodes}"
SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding RelationshipType}" Margin="5,0,0,0" Foreground="Gray"
FontStyle="Italic" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</UserControl>

View File

@ -0,0 +1,97 @@
using CtrEditor.ObjetosSim;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows;
namespace CtrEditor.Controls
{
public partial class ObjectHierarchyView : UserControl
{
private MainViewModel _mainViewModel;
public ObservableCollection<OsTreeNode> RootNodes { get; set; }
public ObjectHierarchyView()
{
InitializeComponent();
RootNodes = new ObservableCollection<OsTreeNode>();
this.DataContext = this;
}
public void Initialize(MainViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
_mainViewModel.ObjetosSimulables.CollectionChanged += ObjetosSimulables_CollectionChanged;
UpdateTree();
}
private void ObjetosSimulables_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdateTree();
}
public void UpdateTree()
{
RootNodes.Clear();
// Agrupar por tipos
var groupsByType = _mainViewModel.ObjetosSimulables
.GroupBy(obj => obj.GetType());
foreach (var group in groupsByType)
{
// Usar GetTypeDisplayName para obtener el nombre de la clase
var typeNode = new OsTreeNode(OsTreeNode.GetTypeDisplayName(group.Key));
RootNodes.Add(typeNode);
foreach (var obj in group)
{
var objNode = new OsTreeNode(obj.Nombre, obj);
typeNode.Children.Add(objNode);
// Agregar relaciones group_Panel
if (!string.IsNullOrEmpty(obj.Group_Panel))
{
var linkedPanel = _mainViewModel.ObjetosSimulables
.FirstOrDefault(o => o.Nombre == obj.Group_Panel);
if (linkedPanel != null)
{
objNode.Children.Add(new OsTreeNode(linkedPanel.Nombre, linkedPanel, "(group_Panel)"));
}
}
// Agregar relaciones group_FramePanel
if (!string.IsNullOrEmpty(obj.Group_FramePanel))
{
var linkedFrame = _mainViewModel.ObjetosSimulables
.FirstOrDefault(o => o.Nombre == obj.Group_FramePanel);
if (linkedFrame != null)
{
objNode.Children.Add(new OsTreeNode(linkedFrame.Nombre, linkedFrame, "(group_FramePanel)"));
}
}
// Agregar objetos que hacen referencia a este objeto
var referencingObjects = _mainViewModel.ObjetosSimulables
.Where(o => o.Group_Panel == obj.Nombre || o.Group_FramePanel == obj.Nombre);
foreach (var refObj in referencingObjects)
{
var relationType = refObj.Group_Panel == obj.Nombre ? "(referenced by group_Panel)" : "(referenced by group_FramePanel)";
objNode.Children.Add(new OsTreeNode(refObj.Nombre, refObj, relationType));
}
}
}
}
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (e.NewValue is OsTreeNode node && node.AssociatedObject != null)
{
// Actualizar la selección en el MainViewModel
_mainViewModel.SelectedItemOsList = node.AssociatedObject;
}
}
}
}

36
Controls/OsTreeNode.cs Normal file
View File

@ -0,0 +1,36 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.ObjetosSim;
using System.Collections.ObjectModel;
using System.Reflection;
namespace CtrEditor
{
public partial class OsTreeNode : ObservableObject
{
[ObservableProperty]
private string name;
[ObservableProperty]
private ObservableCollection<OsTreeNode> children;
[ObservableProperty]
private osBase associatedObject;
[ObservableProperty]
private string relationshipType;
public OsTreeNode(string name, osBase obj = null, string relationship = "")
{
this.Name = name;
this.Children = new ObservableCollection<OsTreeNode>();
this.AssociatedObject = obj;
this.RelationshipType = relationship;
}
public static string GetTypeDisplayName(Type type)
{
var methodInfo = type.GetMethod("NombreClase", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
return methodInfo != null ? methodInfo.Invoke(null, null)?.ToString() : type.Name;
}
}
}

View File

@ -0,0 +1,131 @@
<UserControl x:Class="CtrEditor.Controls.PanelEdicionControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim"
xmlns:ObjetosExtraccion="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos"
mc:Ignorable="d">
<xctk:PropertyGrid x:Name="PropertyGridControl"
Margin="5"
AutoGenerateProperties="False"
ShowDescriptionByTooltip="True"
FocusManager.IsFocusScope="True">
<xctk:PropertyGrid.EditorDefinitions>
<!-- String -->
<xctk:EditorTemplateDefinition>
<xctk:EditorTemplateDefinition.TargetProperties>
<xctk:TargetPropertyType Type="{x:Type sys:String}" />
</xctk:EditorTemplateDefinition.TargetProperties>
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<TextBox Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
Text="{Binding Value, UpdateSourceTrigger=LostFocus}" />
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
<!-- Float -->
<xctk:EditorTemplateDefinition>
<xctk:EditorTemplateDefinition.TargetProperties>
<xctk:TargetPropertyType Type="{x:Type sys:Single}" />
</xctk:EditorTemplateDefinition.TargetProperties>
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<xctk:SingleUpDown Increment="0.01"
Text="{Binding Value, Converter={StaticResource floatFormatter}, UpdateSourceTrigger=LostFocus}" />
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
<!-- Double -->
<xctk:EditorTemplateDefinition>
<xctk:EditorTemplateDefinition.TargetProperties>
<xctk:TargetPropertyType Type="{x:Type sys:Double}" />
</xctk:EditorTemplateDefinition.TargetProperties>
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Converter={StaticResource doubleFormatter}, UpdateSourceTrigger=LostFocus}" />
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
<!-- Velocidad -->
<xctk:EditorTemplateDefinition TargetProperties="VelocidadActual">
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<xctk:SingleUpDown Increment="0.01" Background="Beige"
Text="{Binding Value, Converter={StaticResource floatFormatter}}"
FontWeight="Bold" />
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
<!-- File Dialog -->
<xctk:EditorTemplateDefinition TargetProperties="ImagePath">
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1" Content="..." Width="25" Margin="2,0,0,0"
Click="ImagePathButton_Click" />
</Grid>
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
<!-- osBuscarCoincidencias -->
<xctk:EditorTemplateDefinition>
<xctk:EditorTemplateDefinition.TargetProperties>
<xctk:TargetPropertyType Type="{x:Type ObjetosExtraccion:osBuscarCoincidencias}" />
</xctk:EditorTemplateDefinition.TargetProperties>
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.ObjetosSimulables,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource SubclassFilterConverterosBuscarCoincidencias}}"
SelectedItem="{Binding Value}" DisplayMemberPath="Nombre" IsEditable="True" />
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
<!-- osVMmotorSim -->
<xctk:EditorTemplateDefinition>
<xctk:EditorTemplateDefinition.TargetProperties>
<xctk:TargetPropertyType Type="{x:Type ObjetosSim:osVMmotorSim}" />
</xctk:EditorTemplateDefinition.TargetProperties>
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.ObjetosSimulables,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource SubclassFilterConverterosVMMotor}}"
SelectedItem="{Binding Value}" DisplayMemberPath="Nombre" />
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
<!-- osBase -->
<xctk:EditorTemplateDefinition>
<xctk:EditorTemplateDefinition.TargetProperties>
<xctk:TargetPropertyType Type="{x:Type ObjetosSim:osBase}" />
</xctk:EditorTemplateDefinition.TargetProperties>
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding DataContext.ObjetosSimulables, RelativeSource={RelativeSource AncestorType=Window}}"
SelectedItem="{Binding Value}" DisplayMemberPath="Nombre" />
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
</xctk:PropertyGrid.EditorDefinitions>
</xctk:PropertyGrid>
</UserControl>

View File

@ -0,0 +1,48 @@
using CtrEditor.ObjetosSim;
using Ookii.Dialogs.Wpf;
using System.Windows;
using System.Windows.Controls;
using Xceed.Wpf.Toolkit.PropertyGrid;
namespace CtrEditor.Controls
{
public partial class PanelEdicionControl : UserControl
{
public PanelEdicionControl()
{
InitializeComponent();
}
public void CargarPropiedades(osBase selectedObject)
{
UserControlFactory.CargarPropiedadesosDatos(selectedObject, PropertyGridControl);
}
// Add a new method to clear properties
public void ClearProperties()
{
UserControlFactory.LimpiarPropiedadesosDatos(PropertyGridControl);
}
public bool IsKeyboardFocusWithin => PropertyGridControl.IsKeyboardFocusWithin;
public PropertyGrid PropertyGrid => PropertyGridControl;
private void ImagePathButton_Click(object sender, RoutedEventArgs e)
{
var dlg = new VistaOpenFileDialog
{
Filter = "Image files (*.png;*.jpg;*.bmp)|*.png;*.jpg;*.bmp|All files (*.*)|*.*"
};
if (dlg.ShowDialog() == true)
{
// El DataContext de este botón es el PropertyItem asociado
if ((sender as Button)?.DataContext is PropertyItem propertyItem)
{
propertyItem.Value = dlg.FileName;
}
}
}
}
}

70
Controls/osVisFilter.xaml Normal file
View File

@ -0,0 +1,70 @@
<UserControl x:Class="CtrEditor.Controls.osVisFilter" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:CtrEditor.Controls"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="200">
<UserControl.Resources>
<Style x:Key="FilterCheckBoxStyle" TargetType="CheckBox">
<Setter Property="Margin" Value="0,2,0,2" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style x:Key="FilterHeaderStyle" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Margin" Value="0,5,0,2" />
</Style>
<Style x:Key="FilterSeparatorStyle" TargetType="Separator">
<Setter Property="Margin" Value="0,5,0,5" />
</Style>
</UserControl.Resources>
<Border BorderBrush="LightGray" BorderThickness="1" Padding="5">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel>
<!-- Fixed Filter Options -->
<TextBlock Text="Filter Options" Style="{StaticResource FilterHeaderStyle}" />
<CheckBox Content="All" IsChecked="{Binding ShowAll, Mode=TwoWay}"
Style="{StaticResource FilterCheckBoxStyle}" />
<CheckBox Content="Cloned" IsChecked="{Binding ShowCloned, Mode=TwoWay}"
Style="{StaticResource FilterCheckBoxStyle}" />
<CheckBox Content="Auto Created" IsChecked="{Binding ShowAutoCreated, Mode=TwoWay}"
Style="{StaticResource FilterCheckBoxStyle}" />
<CheckBox Content="Enable On All Pages" IsChecked="{Binding ShowEnableOnAllPages, Mode=TwoWay}"
Style="{StaticResource FilterCheckBoxStyle}" />
<CheckBox Content="Show On This Page" IsChecked="{Binding ShowOnThisPage, Mode=TwoWay}"
Style="{StaticResource FilterCheckBoxStyle}" />
<!-- Separator between fixed and dynamic options -->
<Separator Style="{StaticResource FilterSeparatorStyle}" />
<!-- Type Filter Options -->
<TextBlock Text="Object Types" Style="{StaticResource FilterHeaderStyle}" />
<ItemsControl ItemsSource="{Binding TypeFilters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding DisplayName}" IsChecked="{Binding IsSelected, Mode=TwoWay}"
Style="{StaticResource FilterCheckBoxStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Separator between types and tags -->
<Separator Style="{StaticResource FilterSeparatorStyle}" />
<!-- Tag Filter Options -->
<TextBlock Text="Tags" Style="{StaticResource FilterHeaderStyle}" />
<TextBox Margin="0,2,0,5"
Text="{Binding SearchTags, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ToolTip="Buscar etiquetas (use # antes de cada etiqueta, ej: #motor #bomba)"
FontSize="11" />
<ItemsControl ItemsSource="{Binding TagFilters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding DisplayName}" IsChecked="{Binding IsSelected, Mode=TwoWay}"
Style="{StaticResource FilterCheckBoxStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Border>
</UserControl>

View File

@ -0,0 +1,337 @@
using System.Collections.ObjectModel;
using System.ComponentModel; // Add this for PropertyChangedEventHandler and PropertyChangedEventArgs
using System.Reflection;
using System.Text.Json.Serialization;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.ObjetosSim;
namespace CtrEditor.Controls
{
public class FilterChangedEventArgs : EventArgs
{
public osVisFilterViewModel FilterViewModel { get; private set; }
public FilterChangedEventArgs(osVisFilterViewModel viewModel)
{
FilterViewModel = viewModel;
}
}
/// <summary>
/// Interaction logic for osVisFilter.xaml
/// </summary>
public partial class osVisFilter : UserControl
{
public osVisFilter()
{
InitializeComponent();
DataContext = FilterViewModel = new osVisFilterViewModel { Parent = this }; // Set the Parent property
}
public osVisFilterViewModel FilterViewModel { get; private set; }
public event EventHandler<FilterChangedEventArgs> FilterChanged;
public virtual void OnFilterChanged() // Changed from protected to public
{
FilterChanged?.Invoke(this, new FilterChangedEventArgs(FilterViewModel));
}
/// <summary>
/// Updates the type filters based on the provided types
/// </summary>
public void UpdateAvailableTypes(IEnumerable<Type> types)
{
FilterViewModel.UpdateTypeFilters(types);
}
/// <summary>
/// Updates the tag filters based on the provided objects
/// </summary>
public void UpdateAvailableTags(IEnumerable<osBase> objects)
{
FilterViewModel.UpdateTagFilters(objects);
}
}
/// <summary>
/// ViewModel for the osVisFilter control
/// </summary>
public partial class osVisFilterViewModel : ObservableObject
{
[ObservableProperty]
private bool showAll = true;
[ObservableProperty]
private bool showCloned;
[ObservableProperty]
private bool showAutoCreated;
[ObservableProperty]
private bool showEnableOnAllPages;
[ObservableProperty]
private bool showOnThisPage;
[ObservableProperty]
private ObservableCollection<TypeFilterItem> typeFilters = new ObservableCollection<TypeFilterItem>();
[ObservableProperty]
private ObservableCollection<TagFilterItem> tagFilters = new ObservableCollection<TagFilterItem>();
[ObservableProperty]
private string searchTags = "";
partial void OnShowAllChanged(bool value)
{
NotifyFilterChanged();
}
partial void OnShowClonedChanged(bool value)
{
NotifyFilterChanged();
}
partial void OnShowAutoCreatedChanged(bool value)
{
NotifyFilterChanged();
}
partial void OnShowEnableOnAllPagesChanged(bool value)
{
NotifyFilterChanged();
}
partial void OnShowOnThisPageChanged(bool value)
{
NotifyFilterChanged();
}
partial void OnTypeFiltersChanged(ObservableCollection<TypeFilterItem> value)
{
if (value != null)
{
foreach (var filter in value)
{
filter.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(TypeFilterItem.IsSelected))
{
NotifyFilterChanged();
}
};
}
}
}
partial void OnTagFiltersChanged(ObservableCollection<TagFilterItem> value)
{
if (value != null)
{
foreach (var filter in value)
{
filter.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(TagFilterItem.IsSelected))
{
NotifyFilterChanged();
}
};
}
}
}
partial void OnSearchTagsChanged(string value)
{
NotifyFilterChanged();
}
private void NotifyFilterChanged()
{
if (this.Parent is osVisFilter filter)
{
filter.OnFilterChanged();
}
}
[JsonIgnore]
public osVisFilter Parent { get; set; }
public osVisFilterViewModel()
{
// PropertyChanged listeners could be added here if needed
if (this.Parent is osVisFilter filter)
{
filter.OnFilterChanged();
}
}
/// <summary>
/// Updates the type filters based on the provided types
/// </summary>
public void UpdateTypeFilters(IEnumerable<Type> types)
{
// Remove types that are no longer present
var typesToRemove = TypeFilters
.Where(tf => !types.Contains(tf.Type))
.ToList();
foreach (var item in typesToRemove)
{
UnsubscribeFromTypeFilter(item);
TypeFilters.Remove(item);
}
// Add new types that aren't already in the list
foreach (var type in types)
{
if (!TypeFilters.Any(tf => tf.Type == type))
{
var newFilter = new TypeFilterItem(type)
{
DisplayName = GetTypeDisplayName(type),
IsSelected = true
};
SubscribeToTypeFilter(newFilter);
TypeFilters.Add(newFilter);
}
}
}
/// <summary>
/// Updates the tag filters based on the provided objects
/// </summary>
public void UpdateTagFilters(IEnumerable<osBase> objects)
{
// Get all unique tags from all objects
var allTags = objects
.SelectMany(obj => obj.ListaEtiquetas)
.Distinct()
.OrderBy(tag => tag)
.ToList();
// Remove tags that are no longer present
var tagsToRemove = TagFilters
.Where(tf => !allTags.Contains(tf.TagName))
.ToList();
foreach (var item in tagsToRemove)
{
UnsubscribeFromTagFilter(item);
TagFilters.Remove(item);
}
// Add new tags that aren't already in the list
foreach (var tag in allTags)
{
if (!TagFilters.Any(tf => tf.TagName == tag))
{
var newFilter = new TagFilterItem(tag)
{
IsSelected = true
};
SubscribeToTagFilter(newFilter);
TagFilters.Add(newFilter);
}
}
}
private void SubscribeToTypeFilter(TypeFilterItem filter)
{
filter.PropertyChanged += TypeFilter_PropertyChanged;
}
private void UnsubscribeFromTypeFilter(TypeFilterItem filter)
{
filter.PropertyChanged -= TypeFilter_PropertyChanged;
}
private void SubscribeToTagFilter(TagFilterItem filter)
{
filter.PropertyChanged += TagFilter_PropertyChanged;
}
private void UnsubscribeFromTagFilter(TagFilterItem filter)
{
filter.PropertyChanged -= TagFilter_PropertyChanged;
}
private void TypeFilter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TypeFilterItem.IsSelected))
{
NotifyFilterChanged();
}
}
private void TagFilter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TagFilterItem.IsSelected))
{
NotifyFilterChanged();
}
}
/// <summary>
/// Gets the display name for a type, using the NombreClase static method if available
/// </summary>
private string GetTypeDisplayName(Type type)
{
var methodInfo = type.GetMethod("NombreClase",
BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
return methodInfo != null ?
methodInfo.Invoke(null, null)?.ToString() :
type.Name;
}
}
/// <summary>
/// Represents a type filter item with selection state
/// </summary>
public partial class TypeFilterItem : ObservableObject
{
[ObservableProperty]
private bool isSelected = true;
[ObservableProperty]
private string displayName;
public Type Type { get; }
public TypeFilterItem(Type type)
{
Type = type;
DisplayName = type?.Name ?? "Unknown Type";
}
partial void OnIsSelectedChanged(bool value)
{
OnPropertyChanged(nameof(IsSelected));
}
}
/// <summary>
/// Represents a tag filter item with selection state
/// </summary>
public partial class TagFilterItem : ObservableObject
{
[ObservableProperty]
private bool isSelected;
public string TagName { get; }
public string DisplayName => "#" + TagName;
public TagFilterItem(string tagName)
{
TagName = tagName;
}
partial void OnIsSelectedChanged(bool value)
{
// Could add logic here if needed when selection changes
}
}
}

View File

View File

View File

@ -0,0 +1,28 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace CtrEditor.Converters
{
public class ImageDisplayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string imageName)
{
// Buscar el MainViewModel en el Application.Current.MainWindow.DataContext
if (Application.Current?.MainWindow?.DataContext is MainViewModel viewModel)
{
return viewModel.GetImageDisplayNameWithTags(imageName);
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace CtrEditor.Converters
{
public class RegionalFloatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is float floatValue)
{
// Usar la cultura actual para mostrar el número con el separador decimal correcto
return floatValue.ToString("N4", CultureInfo.CurrentCulture);
}
return value?.ToString() ?? string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string stringValue)
{
// Intentar parsing con la cultura actual primero
if (float.TryParse(stringValue, NumberStyles.Float, CultureInfo.CurrentCulture, out float result))
{
return result;
}
// Si falla, intentar con cultura invariante (punto como separador)
if (float.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out result))
{
return result;
}
// Si ambos fallan, intentar reemplazar punto por coma o viceversa
string adjustedString = stringValue.Replace(',', '.').Replace(".", CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
if (float.TryParse(adjustedString, NumberStyles.Float, CultureInfo.CurrentCulture, out result))
{
return result;
}
}
return 0.0f; // Valor por defecto si no se puede parsear
}
}
}

View File

@ -0,0 +1,27 @@
using System.Windows.Data;
using System.Windows;
namespace CtrEditor
{
public class UnsavedChangesConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string path)
{
var viewModel = Application.Current.MainWindow?.DataContext as MainViewModel;
if (viewModel?.HasUnsavedChanges == true)
{
return $"{path} *";
}
return path;
}
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,276 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows;
using System.Windows.Media;
namespace CtrEditor.Convertidores
{
public class HalfWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double width)
{
return (width / 2.0) - (double.Parse(parameter.ToString()) / 2.0);
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class WidthPercentageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double width)
{
return width * 0.25;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class LevelToHeightMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is float level && values[1] is double containerHeight)
{
return containerHeight * (level / 100);
}
return 0;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class BrushToColorNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is SolidColorBrush brush)
{
if (brush == Brushes.Red) return "Rojo";
if (brush == Brushes.Blue) return "Azul";
if (brush == Brushes.Black) return "Negro";
if (brush == Brushes.Green) return "Verde";
if (brush == Brushes.Gray) return "Gris";
}
return "Unknown";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string colorName)
{
switch (colorName)
{
case "Rojo":
return Brushes.Red;
case "Azul":
return Brushes.Blue;
case "Negro":
return Brushes.Black;
case "Verde":
return Brushes.Green;
case "Gris":
return Brushes.Gray;
}
}
return Brushes.Transparent;
}
}
public class PixelToMeter
{
// Instancia privada estática, parte del patrón Singleton
private static PixelToMeter? _instance;
public UnitConverter calc = new UnitConverter(0.01f);
// Propiedad pública estática para acceder a la instancia
public static PixelToMeter Instance
{
get
{
if (_instance == null)
{
_instance = new PixelToMeter();
}
return _instance;
}
}
}
public class MeterToPixelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return 0; // Aseguramos que el valor no sea nulo
// Convertimos el valor de entrada en un número flotante
float meters = System.Convert.ToSingle(value);
float factor = 1; // Valor por defecto del factor
// Si el parámetro no es nulo, intentamos convertirlo a float
if (parameter != null)
{
string paramStr = parameter.ToString();
// Normalizamos el parámetro para asegurar que el punto sea el separador decimal
paramStr = paramStr.Replace(',', '.');
// Utilizamos CultureInfo.InvariantCulture para evitar problemas con el separador decimal
if (float.TryParse(paramStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float parsedFactor))
{
factor = parsedFactor; // Asignamos el factor parseado si la conversión es exitosa
}
}
// Calculamos los píxeles llamando a la instancia de PixelToMeter y multiplicamos por el factor
return PixelToMeter.Instance.calc.MetersToPixels(meters) * factor;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return 0; // Aseguramos que el valor no sea nulo
// Convertimos el valor de entrada en un número flotante
float pixels = System.Convert.ToSingle(value);
float factor = 1; // Valor por defecto del factor
// Si el parámetro no es nulo, intentamos convertirlo a float
if (parameter != null)
{
string paramStr = parameter.ToString();
// Normalizamos el parámetro para asegurar que el punto sea el separador decimal
paramStr = paramStr.Replace(',', '.');
// Utilizamos CultureInfo.InvariantCulture para evitar problemas con el separador decimal
if (float.TryParse(paramStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float parsedFactor))
{
factor = parsedFactor; // Asignamos el factor parseado si la conversión es exitosa
}
}
return PixelToMeter.Instance.calc.PixelsToMeters(pixels) * factor;
}
}
public class DistanceToMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double distance)
{
return new Thickness(0, 0, 0, PixelToMeter.Instance.calc.MetersToPixels((float)distance)); // Ajustar Bottom a 'distance'
}
return new Thickness();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("ConvertBack is not supported.");
}
}
public class FloatToFormattedStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is float floatValue)
{
return floatValue.ToString("0.00", culture); // Formatear a dos decimales
}
return value; // Devolver el valor original si no es un float
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string stringValue && float.TryParse(stringValue, NumberStyles.Float, culture, out float result))
{
return result;
}
return value; // Devolver el valor original si no se puede convertir
}
}
public class DoubleToFormattedStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double floatValue)
{
return floatValue.ToString("0.00", culture); // Formatear a dos decimales
}
return value; // Devolver el valor original si no es un float
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string stringValue && double.TryParse(stringValue, NumberStyles.Float, culture, out double result))
{
return result;
}
return value; // Devolver el valor original si no se puede convertir
}
}
public class UnitConverter
{
// La escala representa cuántos metros hay en un píxel
public float Scale { get; private set; }
public UnitConverter(float scale)
{
if (scale <= 0)
throw new ArgumentException("Scale must be greater than zero.");
Scale = scale;
}
// Convierte una distancia en metros a píxeles
public float MetersToPixels(float meters)
{
return meters / Scale;
}
// Convierte una distancia en píxeles a metros
public float PixelsToMeters(float pixels)
{
return pixels * Scale;
}
// Configurar o ajustar la escala
public void SetScale(float newScale)
{
if (newScale <= 0)
throw new ArgumentException("Scale must be greater than zero.");
Scale = newScale;
}
}
}

11
CtrEditor.code-workspace Normal file
View File

@ -0,0 +1,11 @@
{
"folders": [
{
"path": "."
},
{
"path": "../Libraries/LibS7Adv"
}
],
"settings": {}
}

View File

@ -6,9 +6,20 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>True</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Documentation\PlantillaEstandarizacion.cs" />
<Compile Remove="ObjetosSim\ucBasicExample.xaml.cs" />
<Compile Remove="ObjetosSim\ucTransporteCurva.xaml.cs" />
<Compile Remove="Simulacion\FPhysics.cs" />
<Compile Remove="Simulacion\GeometrySimulator.cs" />
@ -17,45 +28,77 @@
<ItemGroup>
<None Remove="app2.png" />
<None Remove="CtrEditorE.png" />
<None Remove="Icons\allselect.png" />
<None Remove="Icons\analyze.png" />
<None Remove="Icons\app.256x256.ico" />
<None Remove="Icons\app.png" />
<None Remove="Icons\app2.128x128.ico" />
<None Remove="Icons\app2.256x256.ico" />
<None Remove="Icons\app2.png" />
<None Remove="Icons\borrar.png" />
<None Remove="Icons\choose.png" />
<None Remove="Icons\connect.png" />
<None Remove="Icons\CtrEditorA.png" />
<None Remove="Icons\CtrEditorC.png" />
<None Remove="Icons\CtrEditorE.png" />
<None Remove="Icons\disconnect.png" />
<None Remove="Icons\duplicate.png" />
<None Remove="Icons\extract.png" />
<None Remove="Icons\fotocelula.png" />
<None Remove="Icons\match.png" />
<None Remove="Icons\ocr.png" />
<None Remove="Icons\rotation.cur" />
<None Remove="Icons\rotation32x32.cur" />
<None Remove="Icons\rotationRx.cur" />
<None Remove="Icons\rotationSx.cur" />
<None Remove="Icons\save.png" />
<None Remove="Icons\start.png" />
<None Remove="Icons\stop.png" />
<None Remove="Icons\unselect.png" />
<None Remove="imagenes\filler.png" />
<None Remove="imagenes\gear.png" />
<None Remove="imagenes\motorNegro.png" />
<None Remove="imagenes\motorVerde.png" />
<None Remove="Images\base.png" />
<None Remove="motor2.png" />
<None Remove="tank.png" />
<None Remove="IA\appsettings.json" />
</ItemGroup>
<ItemGroup>
<Page Remove="ObjetosSim\ucBasicExample.xaml" />
<Page Remove="ObjetosSim\ucTransporteCurva.xaml" />
</ItemGroup>
<ItemGroup>
<None Include="Documentation\PlantillaEstandarizacion.cs" />
<None Include="ObjetosSim\ucBasicExample.xaml" />
<None Include="ObjetosSim\ucBasicExample.xaml.cs" />
<None Include="Simulacion\FPhysics.cs" />
<None Include="Simulacion\GeometrySimulator.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aether.Physics2D" Version="2.1.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="FarseerPhysics" Version="3.5.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
<PackageReference Include="Aether.Physics2D" Version="2.2.0" />
<PackageReference Include="ClosedXML" Version="0.105.0-rc" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Emgu.CV" Version="4.9.0.5494" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.9.0.5494" />
<PackageReference Include="Emgu.CV.UI" Version="4.9.0.5494" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.7.25104.5739" />
<PackageReference Include="LanguageDetection" Version="1.2.0" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc3.3" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
<PackageReference Include="SkiaSharp.Views.WPF" Version="2.88.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="PaddleOCRSharp" Version="4.5.0.1" />
<PackageReference Include="Tesseract" Version="5.2.0" />
<PackageReference Include="Tesseract.Drawing" Version="5.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Libraries\LibS7Adv\LibS7Adv.csproj" />
</ItemGroup>
<ItemGroup>
@ -68,23 +111,77 @@
<ItemGroup>
<Resource Include="app2.png" />
<Resource Include="CtrEditorE.png" />
<Resource Include="Icons\allselect.png" />
<Resource Include="Icons\analyze.png" />
<Resource Include="Icons\app.png" />
<Resource Include="Icons\app2.png" />
<Resource Include="Icons\borrar.png" />
<Resource Include="Icons\choose.png" />
<Resource Include="Icons\connect.png" />
<Resource Include="Icons\CtrEditorA.png" />
<Resource Include="Icons\CtrEditorC.png" />
<Resource Include="Icons\CtrEditorE.png" />
<Resource Include="Icons\disconnect.png" />
<Resource Include="Icons\duplicate.png" />
<Resource Include="Icons\extract.png">
<CopyToOutputDirectory></CopyToOutputDirectory>
</Resource>
<Resource Include="Icons\fotocelula.png" />
<Resource Include="Icons\match.png" />
<Resource Include="Icons\ocr.png" />
<Resource Include="Icons\rotationRx.cur" />
<Resource Include="Icons\rotationSx.cur" />
<Resource Include="Icons\save.png" />
<Resource Include="Icons\start.png" />
<Resource Include="Icons\stop.png" />
<Resource Include="Icons\unselect.png" />
<Resource Include="imagenes\filler.png" />
<Resource Include="imagenes\gear.png" />
<Resource Include="imagenes\motorNegro.png" />
<Resource Include="imagenes\motorVerde.png" />
<Resource Include="imagenes\tank.png" />
<EmbeddedResource Include="Images\base.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="IA\appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<None Update="Tesseract\eng.traineddata">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Tesseract\ita.traineddata">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Tesseract\spa.traineddata">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="IA\appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Folder Include="ObjetosSim\Fluids\" />
<Folder Include="paddleocr\cls\inference\" />
<Folder Include="paddleocr\det\inference\" />
<Folder Include="paddleocr\keys\" />
<Folder Include="paddleocr\rec\inference\" />
</ItemGroup>
</Project>

View File

@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34723.18
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CtrEditor", "CtrEditor.csproj", "{A4DC96D9-6C55-42ED-8E29-DCBC8D7AB831}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CtrEditor", "CtrEditor.csproj", "{A4DC96D9-6C55-42ED-8E29-DCBC8D7AB831}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibS7Adv", "..\Libraries\LibS7Adv\LibS7Adv.csproj", "{86A7FED2-AEB1-4766-819F-C6256FA7DD38}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -15,6 +17,10 @@ Global
{A4DC96D9-6C55-42ED-8E29-DCBC8D7AB831}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A4DC96D9-6C55-42ED-8E29-DCBC8D7AB831}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A4DC96D9-6C55-42ED-8E29-DCBC8D7AB831}.Release|Any CPU.Build.0 = Release|Any CPU
{86A7FED2-AEB1-4766-819F-C6256FA7DD38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{86A7FED2-AEB1-4766-819F-C6256FA7DD38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86A7FED2-AEB1-4766-819F-C6256FA7DD38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86A7FED2-AEB1-4766-819F-C6256FA7DD38}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

382
DataStates/StateManager.cs Normal file
View File

@ -0,0 +1,382 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Linq;
using System.Windows.Controls;
using System.Windows;
using CtrEditor.ObjetosSim;
using LibS7Adv;
namespace CtrEditor
{
/// <summary>
/// Metadata de una página, incluyendo información sobre la imagen y configuración
/// </summary>
public class PageMetadata
{
public string PageId { get; set; }
public string PageName { get; set; }
public string ImagePath { get; set; }
public DateTime LastModified { get; set; }
public Dictionary<string, object> CustomMetadata { get; set; } = new();
}
/// <summary>
/// Estado de un objeto dentro de una página específica
/// </summary>
public class PageObjectState
{
public string GlobalObjectId { get; set; }
public bool IsVisible { get; set; } = true;
public float Left { get; set; }
public float Top { get; set; }
public float Rotation { get; set; }
public Dictionary<string, object> CustomProperties { get; set; } = new();
}
/// <summary>
/// Estado de una página específica
/// </summary>
public class PageState
{
public string PageId { get; set; }
public string PageName { get; set; }
public List<osBase> LocalObjects { get; set; } = new();
public Dictionary<string, PageObjectState> GlobalObjectStates { get; set; } = new();
public Dictionary<string, object> PageMetadata { get; set; } = new();
}
/// <summary>
/// Estado global de la aplicación
/// </summary>
public class GlobalState
{
public List<osBase> SharedObjects { get; set; } = new();
public Dictionary<string, PageMetadata> PagesMetadata { get; set; } = new();
public PLCViewModel PLCConfiguration { get; set; }
public UnitConverter UnitConverter { get; set; }
public int LastUsedId { get; set; }
}
/// <summary>
/// Gestor principal de estados de la aplicación
/// </summary>
public class StateManager : IDisposable
{
private readonly string _basePath;
private GlobalState _globalState;
private Dictionary<string, PageState> _loadedPages;
private string _currentPageId;
private readonly object _lockObject = new();
private bool _hasUnsavedChanges;
private MainViewModel _mainViewModel;
public event EventHandler<string> PageStateChanged;
public event EventHandler GlobalStateChanged;
public StateManager(string basePath, MainViewModel mainViewModel)
{
_basePath = basePath;
_mainViewModel = mainViewModel;
_loadedPages = new Dictionary<string, PageState>();
Directory.CreateDirectory(basePath);
}
public async Task InitializeAsync()
{
await LoadGlobalStateAsync();
if (_mainViewModel.SelectedImage != null)
{
await LoadPageStateAsync(_mainViewModel.SelectedImage);
}
}
private JsonSerializerSettings GetSerializerSettings()
{
return new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
}
public async Task LoadGlobalStateAsync()
{
var path = Path.Combine(_basePath, "global.json");
if (File.Exists(path))
{
try
{
var json = await File.ReadAllTextAsync(path);
_globalState = JsonConvert.DeserializeObject<GlobalState>(json, GetSerializerSettings());
// Restaurar configuración global
_mainViewModel.PLCViewModel = _globalState.PLCConfiguration ?? new PLCViewModel();
if (_globalState.UnitConverter != null)
PixelToMeter.Instance.calc = _globalState.UnitConverter;
// No crear un nuevo UnitConverter si no hay uno guardado, mantener el actual
// Restaurar objetos globales
foreach (var obj in _globalState.SharedObjects)
{
await RestoreObjectAsync(obj);
}
}
catch (Exception ex)
{
// Log error y crear nuevo estado global
_globalState = new GlobalState();
}
}
else
{
_globalState = new GlobalState();
}
}
public async Task SaveGlobalStateAsync()
{
var path = Path.Combine(_basePath, "global.json");
var backupPath = Path.ChangeExtension(path, ".bak");
// Actualizar estado global
_globalState.PLCConfiguration = _mainViewModel.PLCViewModel;
_globalState.UnitConverter = PixelToMeter.Instance.calc;
// Crear backup
if (File.Exists(path))
{
File.Copy(path, backupPath, true);
}
try
{
// Preparar objetos para serialización
foreach (var obj in _globalState.SharedObjects)
{
obj.SalvarDatosNoSerializables();
}
var json = JsonConvert.SerializeObject(_globalState, GetSerializerSettings());
await File.WriteAllTextAsync(path, json);
_hasUnsavedChanges = false;
GlobalStateChanged?.Invoke(this, EventArgs.Empty);
}
finally
{
// Restaurar estado de objetos
foreach (var obj in _globalState.SharedObjects)
{
obj.RestaurarDatosNoSerializables();
}
}
}
public async Task<PageState> LoadPageStateAsync(string pageId)
{
// Retornar página cacheada si existe
if (_loadedPages.TryGetValue(pageId, out var cachedState))
{
return cachedState;
}
var path = Path.Combine(_basePath, $"page_{pageId}.json");
try
{
PageState pageState;
if (File.Exists(path))
{
var json = await File.ReadAllTextAsync(path);
pageState = JsonConvert.DeserializeObject<PageState>(json, GetSerializerSettings());
}
else
{
pageState = new PageState
{
PageId = pageId,
PageName = _globalState.PagesMetadata.GetValueOrDefault(pageId)?.PageName ?? pageId
};
}
_loadedPages[pageId] = pageState;
// Restaurar objetos locales
foreach (var obj in pageState.LocalObjects)
{
await RestoreObjectAsync(obj);
}
// Aplicar estados de objetos globales
ApplyGlobalObjectStates(pageState);
return pageState;
}
catch (Exception ex)
{
// Log error
throw;
}
}
private void ApplyGlobalObjectStates(PageState pageState)
{
foreach (var kvp in pageState.GlobalObjectStates)
{
var globalObj = _globalState.SharedObjects.FirstOrDefault(o => o.Id.Value.ToString() == kvp.Key);
if (globalObj != null)
{
var state = kvp.Value;
globalObj.Left = state.Left;
globalObj.Top = state.Top;
globalObj.Angulo = state.Rotation;
// Actualizar Show_On_This_Page que ahora maneja internamente showOnThisPagesList
if (state.IsVisible && !globalObj.Show_On_This_Page)
globalObj.Show_On_This_Page = true;
else if (!state.IsVisible && globalObj.Show_On_This_Page)
globalObj.Show_On_This_Page = false;
// Aplicar propiedades personalizadas
foreach (var prop in state.CustomProperties)
{
var property = globalObj.GetType().GetProperty(prop.Key);
if (property != null && property.CanWrite)
{
property.SetValue(globalObj, prop.Value);
}
}
}
}
}
private async Task RestoreObjectAsync(osBase obj)
{
if (obj != null)
{
obj.CheckData();
await Task.Run(() =>
{
Application.Current.Dispatcher.Invoke(() =>
{
CrearUserControlDesdeObjetoSimulable(obj);
});
});
}
}
private bool CrearUserControlDesdeObjetoSimulable(osBase osObjeto)
{
Type tipoObjeto = osObjeto.GetType();
UserControl userControl = UserControlFactory.GetControlForType(tipoObjeto);
if (userControl != null)
{
UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager);
osObjeto._mainViewModel = _mainViewModel;
if (osObjeto.Id == null)
{
osObjeto.Id = new UniqueId().ObtenerNuevaID();
}
_mainViewModel.MainWindow.AgregarRegistrarUserControlCanvas(userControl);
return true;
}
return false;
}
public async Task SavePageStateAsync(string pageId)
{
if (!_loadedPages.TryGetValue(pageId, out var pageState))
return;
var path = Path.Combine(_basePath, $"page_{pageId}.json");
var backupPath = Path.ChangeExtension(path, ".bak");
// Crear backup
if (File.Exists(path))
{
File.Copy(path, backupPath, true);
}
// Actualizar estado de objetos globales
pageState.GlobalObjectStates.Clear();
foreach (var obj in _mainViewModel.ObjetosSimulables.Where(o => o.Enable_On_All_Pages))
{
var currentPageId = _mainViewModel.SelectedImage;
pageState.GlobalObjectStates[obj.Id.Value.ToString()] = new PageObjectState
{
GlobalObjectId = obj.Id.Value.ToString(),
IsVisible = obj.Show_On_This_Page,
Left = obj.Left,
Top = obj.Top,
Rotation = obj.Angulo,
CustomProperties = CaptureCustomProperties(obj)
};
}
try
{
// Preparar objetos para serialización
foreach (var obj in pageState.LocalObjects)
{
obj.SalvarDatosNoSerializables();
}
var json = JsonConvert.SerializeObject(pageState, GetSerializerSettings());
await File.WriteAllTextAsync(path, json);
_hasUnsavedChanges = false;
PageStateChanged?.Invoke(this, pageId);
}
finally
{
// Restaurar estado de objetos
foreach (var obj in pageState.LocalObjects)
{
obj.RestaurarDatosNoSerializables();
}
}
}
private Dictionary<string, object> CaptureCustomProperties(osBase obj)
{
var customProps = new Dictionary<string, object>();
var properties = obj.GetType().GetProperties()
.Where(p => p.GetCustomAttributes(typeof(SerializeAttribute), true).Any());
foreach (var prop in properties)
{
customProps[prop.Name] = prop.GetValue(obj);
}
return customProps;
}
public async Task SaveAllAsync()
{
foreach (var pageId in _loadedPages.Keys)
{
await SavePageStateAsync(pageId);
}
await SaveGlobalStateAsync();
}
public void Dispose()
{
if (_hasUnsavedChanges)
{
SaveAllAsync().Wait();
}
}
}
/// <summary>
/// Atributo para marcar propiedades que deben ser serializadas como propiedades personalizadas
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class SerializeAttribute : Attribute { }
}

View File

@ -5,12 +5,14 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace CtrEditor
{
public class DatosDeTrabajo
{
public Dictionary<string, string> Imagenes { get; private set; }
private MainViewModel _mainViewModel;
public DatosDeTrabajo()
{
@ -18,6 +20,11 @@ namespace CtrEditor
CargarImagenes(); // Inicializar la carga de imágenes basada en el directorio persistente
}
public void SetMainViewModel(MainViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
}
public string ObtenerPathImagenConExtension(string key, string extension)
{
if (Imagenes.TryGetValue(key, out string imagePath))
@ -28,6 +35,13 @@ namespace CtrEditor
return null;
}
public string? ObtenerPathAllPages(string extension)
{
if (!string.IsNullOrEmpty(EstadoPersistente.Instance.directorio))
return Path.Combine(EstadoPersistente.Instance.directorio, "AllPages" + extension);
return null;
}
public void CargarImagenes()
{
Imagenes.Clear();
@ -44,7 +58,85 @@ namespace CtrEditor
Imagenes[nombreArchivo] = archivo;
}
}
// Cargar datos de imágenes existentes desde archivos JSON
if (_mainViewModel != null)
{
CargarDatosImagenesExistentes();
}
}
}
private void CargarDatosImagenesExistentes()
{
var jsonSerializerSettings = GetJsonSerializerSettings();
foreach (var imageName in Imagenes.Keys)
{
string jsonPath = ObtenerPathImagenConExtension(imageName, ".json");
if (!string.IsNullOrEmpty(jsonPath) && File.Exists(jsonPath))
{
try
{
// Cargar datos completos del archivo JSON
string jsonString = File.ReadAllText(jsonPath);
var simulationData = Newtonsoft.Json.JsonConvert.DeserializeObject<SimulationData>(jsonString, jsonSerializerSettings);
// Cargar datos de imágenes si existen en el archivo
if (simulationData?.ImageDataDictionary != null)
{
foreach (var imageDataEntry in simulationData.ImageDataDictionary)
{
// Solo cargar si no existe ya en el diccionario principal
if (!_mainViewModel._imageDataDictionary.ContainsKey(imageDataEntry.Key))
{
_mainViewModel._imageDataDictionary[imageDataEntry.Key] = imageDataEntry.Value;
}
}
}
// Compatibilidad con versiones anteriores (ImageCustomNames)
#pragma warning disable CS0618 // Type or member is obsolete
if (simulationData?.ImageCustomNames != null)
{
foreach (var customName in simulationData.ImageCustomNames)
{
var imageData = _mainViewModel.GetOrCreateImageData(customName.Key);
if (string.IsNullOrEmpty(imageData.CustomName)) // Solo actualizar si no tiene nombre personalizado
{
imageData.CustomName = customName.Value;
}
}
}
#pragma warning restore CS0618
}
catch (Exception ex)
{
// Log del error pero no fallar la carga completa
System.Diagnostics.Debug.WriteLine($"Error al cargar datos de imagen desde {jsonPath}: {ex.Message}");
// Como fallback, crear una instancia vacía
_mainViewModel.GetOrCreateImageData(imageName);
}
}
else
{
// Si no existe archivo JSON, crear instancia vacía
_mainViewModel.GetOrCreateImageData(imageName);
}
}
}
private Newtonsoft.Json.JsonSerializerSettings GetJsonSerializerSettings()
{
return new Newtonsoft.Json.JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto,
ObjectCreationHandling = Newtonsoft.Json.ObjectCreationHandling.Replace,
ConstructorHandling = Newtonsoft.Json.ConstructorHandling.AllowNonPublicDefaultConstructor
};
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,110 @@
# Biblioteca de Objetos Simulables
## Descripción
La biblioteca de objetos simulables es una nueva funcionalidad que permite gestionar y organizar objetos simulables en bibliotecas independientes del proyecto actual. Esto facilita la reutilización de objetos entre diferentes proyectos.
## Características Principales
### 1. Gestión de Directorios de Biblioteca
- **Agregar directorios**: Permite agregar nuevos directorios que contendrán bibliotecas de objetos
- **Eliminar directorios**: Permite remover directorios de la lista de bibliotecas
- **Persistencia global**: Los directorios se mantienen independientemente del proyecto actual
### 2. Bibliotecas de Objetos
- **Proyecto actual**: El proyecto actual siempre aparece como una biblioteca
- **Archivos JSON**: Cada archivo JSON en los directorios de biblioteca se muestra como una biblioteca independiente
- **Visualización jerárquica**: Los objetos se organizan por tipo en un TreeView
### 3. Sistema de Filtros
- **Filtros por tipo**: Permite mostrar/ocultar tipos específicos de objetos
- **Filtros por etiquetas**: Filtrado por etiquetas usando # (ej: #motor #bomba)
- **Filtros especiales**:
- Cloned (objetos clonados)
- Auto Created (objetos creados automáticamente)
- Enable On All Pages (objetos globales)
- Show On This Page (objetos visibles en página actual)
### 4. Operaciones de Objetos
- **Copiar (Ctrl+C)**: Copia objetos seleccionados al portapapeles interno
- **Pegar (Ctrl+V)**: Pega objetos del portapapeles a la biblioteca seleccionada
- **Eliminar**: Elimina objetos de bibliotecas (no disponible para proyecto actual)
- **Visualizar propiedades**: PropertyGrid de solo lectura para inspeccionar objetos
### 5. Gestión de Bibliotecas
- **Crear nueva biblioteca**: Crea un archivo JSON vacío como nueva biblioteca
- **Cargar automáticamente**: Las bibliotecas se cargan automáticamente al seleccionar directorios
## Cómo Usar
### Acceso
1. Haga clic en el botón **"Biblioteca"** en la barra de herramientas principal (al lado de "Assing Pages")
2. Se abrirá la ventana modal de gestión de bibliotecas
### Configurar Directorios de Biblioteca
1. En la columna izquierda "Directorios de Biblioteca", haga clic en **"Agregar"**
2. Seleccione la carpeta que contendrá sus bibliotecas de objetos
3. El directorio se agregará a la lista y se guardará automáticamente
### Navegar Bibliotecas
1. Seleccione un directorio de la lista en la columna izquierda
2. En la columna central aparecerán todas las bibliotecas (archivos JSON) encontradas
3. Seleccione una biblioteca para ver sus objetos organizados por tipo
### Filtrar Objetos
1. Use la sección "Filtros" para mostrar/ocultar tipos específicos
2. Use el campo de búsqueda para filtrar por etiquetas (ej: `#motor #conveyor`)
3. Active los filtros especiales según sea necesario
### Copiar/Pegar Objetos
1. **Para copiar**: Seleccione un objeto y presione Ctrl+C o haga clic en "Copiar"
2. **Para pegar**: Seleccione la biblioteca destino y presione Ctrl+V o haga clic en "Pegar"
3. Los objetos se copiarán con nuevos IDs únicos
### Crear Nueva Biblioteca
1. Seleccione un directorio de biblioteca
2. Haga clic en **"Nueva Biblioteca"**
3. Elija nombre y ubicación para el archivo JSON
4. Se creará una biblioteca vacía lista para usar
## Casos de Uso
### Biblioteca de Componentes Estándar
- Cree una biblioteca con motores, sensores y actuadores estándar
- Reutilice estos componentes en múltiples proyectos
- Mantenga configuraciones consistentes entre proyectos
### Plantillas de Sistemas
- Guarde sistemas completos (ej: línea de ensamblaje básica)
- Use como punto de partida para nuevos proyectos
- Facilite la estandarización de diseños
### Backup y Versioning
- Mantenga copias de objetos importantes en bibliotecas externas
- Cree versiones de componentes con diferentes configuraciones
- Facilite la colaboración entre equipos
## Estructura de Archivos
Las bibliotecas se almacenan como archivos JSON con la misma estructura que los archivos de proyecto:
```json
{
"ObjetosSimulables": [...],
"UnitConverter": {...},
"PLC_ConnectionData": {...}
}
```
## Notas Técnicas
- Los directorios de biblioteca se almacenan en `EstadoPersistente.Instance.LibraryDirectories`
- Los objetos se serializan usando Newtonsoft.Json con `TypeNameHandling.All`
- La funcionalidad es independiente del directorio de trabajo actual
- Los filtros utilizan el mismo sistema que la aplicación principal (`osVisFilter`)
## Limitaciones
- No se pueden editar objetos directamente en las bibliotecas (solo copia/pegar)
- La eliminación solo está disponible para bibliotecas externas (no proyecto actual)
- Los archivos JSON deben tener estructura válida de SimulationData

View File

@ -0,0 +1,145 @@
# Estandarización de Objetos derivados de osBase
## Criterios para NombreClase()
### Categorías de Objetos:
1. **Transporte**: Transportadores, cintas, curvas
2. **Sensores**: Fotocélulas, encoders, botones
3. **Actuadores**: Motores, válvulas
4. **Elementos Estáticos**: Guías, descartes, barreras
5. **Emuladores**: Generadores, llenadores, tanques
6. **Señales**: Tags analógicos, digitales
7. **Datos**: Extracción, búsqueda
8. **Decorativos**: Imágenes, marcos, textos
9. **Fluidos**: Tuberías, válvulas, sistemas
### Formato de Nombres:
- **Español**: Usar nombres descriptivos en español
- **Específico**: Incluir característica principal (ej: "Motor VetroMeccanica", "Transporte con Guías")
- **Consistente**: Mantener formato similar por categoría
## Categorías Estandarizadas para Propiedades
### 1. "Identificación"
- Nombre del objeto
- Etiquetas de clasificación
### 2. "Posición y Tamaño"
- Coordenadas (Left, Top)
- Dimensiones (Ancho, Alto, Ángulo)
- Bloqueo de movimiento
### 3. "Configuración"
- Parámetros principales del objeto
- Configuraciones específicas por tipo
### 4. "Simulación"
- Velocidades, fuerzas, coeficientes
- Parámetros físicos
### 5. "Enlace PLC"
- Tags de entrada y salida
- Conexiones con motores/sensores
### 6. "Información"
- Valores calculados
- Estados actuales
- Datos de depuración
### 7. "Apariencia"
- Colores, tamaños visuales
- Aspectos gráficos
## Ejemplos de Aplicación
### Motor VetroMeccanica:
```csharp
[ObservableProperty]
[property: Description("Velocidad actual del motor en Hz")]
[property: Category("Información")]
[property: Name("Velocidad Actual")]
public float velocidad;
[ObservableProperty]
[property: Description("Tiempo de rampa para acelerar/desacelerar")]
[property: Category("Configuración")]
[property: Name("Tiempo de Rampa")]
public float tiempoRampa;
```
### Fotocélula:
```csharp
[ObservableProperty]
[property: Description("Indica si la luz está cortada por un objeto")]
[property: Category("Información")]
[property: Name("Luz Cortada")]
bool luzCortada;
[ObservableProperty]
[property: Description("Ancho del haz de luz del sensor")]
[property: Category("Configuración")]
[property: Name("Ancho del Haz")]
float ancho_Haz_De_Luz;
```
## Plan de Implementación
### ✅ Fase 1: Estandarizar métodos `NombreClase()`
- [x] Crear mapeo de nombres descriptivos
- [x] Implementar ejemplos: TransporteTTop, Motor VetroMeccanica, Fotocélula, Tag Analógico
- [x] Generar script de automatización PowerShell
### ✅ Fase 2: Aplicar categorías y descripciones estándar
- [x] Definir 8 categorías estándar
- [x] Crear plantilla de implementación
- [x] Aplicar en objetos de ejemplo
### ✅ Fase 3: Implementación Clase por Clase (COMPLETADO)
- [x] **ucTransporteTTop** - Transporte TTOP
- [x] **ucVMmotorSim** - Motor VetroMeccanica
- [x] **ucPhotocell** - Fotocélula
- [x] **ucAnalogTag** - Tag Analógico
- [x] **ucCustomImage** - Imagen Personalizada
- [x] **ucBoolTag** - Tag Digital
- [x] **ucGuia** - Guía
- [x] **ucBotella** - Botella
- [x] **ucTransporteCurva** - Transporte Curva 90°
- [x] **ucBoton** - Botón
- [x] **ucBottGenerator** - Generador de Botellas
- [x] **ucDescarte** - Descarte
- [x] **ucFramePlate** - Marco de Panel (parcial)
### 📊 Estadísticas de Progreso
- **Clases procesadas**: 13
- **`using System.ComponentModel;` agregados**: 6 clases
- **Nombres de clase mejorados**: 13
- **Propiedades estandarizadas**: ~85 propiedades
## Archivos Generados
1. **`Scripts/EstandarizarObjetos.ps1`**: Script para automatizar cambios
2. **`Documentation/PlantillaEstandarizacion.cs`**: Plantilla con ejemplos
3. **`Documentation/EstandarizacionObjetos.md`**: Esta documentación
## Instrucciones de Uso
### Para aplicar automáticamente:
```powershell
cd CtrEditor
.\Scripts\EstandarizarObjetos.ps1
```
### Para aplicar manualmente:
1. Consultar `PlantillaEstandarizacion.cs`
2. Seguir el patrón de categorías establecido
3. Usar nombres descriptivos en español
## Objetos Ya Actualizados
- ✅ `ucTransporteTTop` → "Transporte TTOP"
- ✅ `ucVMmotorSim` → "Motor VetroMeccanica"
- ✅ `ucPhotocell` → "Fotocélula"
- ✅ `ucAnalogTag` → "Tag Analógico"
---
*Documento generado para estandarización del proyecto CtrEditor*

Binary file not shown.

View File

@ -0,0 +1,172 @@
/*
PLANTILLA DE ESTANDARIZACIÓN PARA OBJETOS osBase
Usar esta plantilla como guía para estandarizar cualquier objeto derivado de osBase
*/
namespace CtrEditor.ObjetosSim
{
public partial class osEjemplo : osBase, IosBase
{
// 1. MÉTODO NOMBRECLASE - Usar nombres descriptivos en español
public static string NombreClase()
{
return "Nombre Descriptivo del Objeto"; // Ej: "Motor VetroMeccanica", "Fotocélula", "Transporte TTOP"
}
private string nombre = NombreClase();
// 2. PROPIEDAD NOMBRE - Siempre en categoría "Identificación"
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
// 3. CATEGORÍAS ESTÁNDAR:
// IDENTIFICACIÓN - Información básica del objeto
[ObservableProperty]
[property: Category("Identificación")]
[property: Description("Descripción o comentario del objeto")]
[property: Name("Descripción")]
public string descripcion;
// POSICIÓN Y TAMAÑO - Heredadas de osBase (Left, Top, Ancho, Alto, Angulo)
// No necesitan redefinirse, ya están en osBase
// CONFIGURACIÓN - Parámetros principales del objeto
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad máxima del objeto")]
[property: Name("Velocidad Máxima")]
public float velocidadMaxima;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Habilita/deshabilita funcionalidad específica")]
[property: Name("Habilitar Función")]
public bool habilitarFuncion;
// SIMULACIÓN - Parámetros físicos y de simulación
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Velocidad actual en la simulación")]
[property: Name("Velocidad Actual")]
public float velocidadActual;
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Coeficiente de fricción")]
[property: Name("Coeficiente de Fricción")]
public float coeficienteFriccion;
// ENLACE PLC - Conexiones con PLC y otros objetos
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Dirección del tag en el PLC")]
[property: Name("Tag Principal")]
public string tagPrincipal;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Seleccionar motor para enlazar")]
[property: Name("Motor Enlazado")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
public string motorEnlazado;
// INFORMACIÓN - Valores calculados, estados, debug
[ObservableProperty]
[property: Category("Información")]
[property: Description("Estado actual del objeto")]
[property: Name("Estado")]
public bool estado;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Último valor calculado")]
[property: Name("Último Valor")]
public float ultimoValor;
// APARIENCIA - Aspectos visuales
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Tamaño visual del objeto")]
[property: Name("Tamaño")]
public float tamano;
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color del objeto")]
[property: Name("Color")]
public Brush color;
// ENCODER - Específico para motores con encoder
[ObservableProperty]
[property: Category("Encoder")]
[property: Description("Habilita el encoder")]
[property: Name("Con Encoder")]
public bool conEncoder;
[ObservableProperty]
[property: Category("Encoder")]
[property: Description("Posición actual del encoder")]
[property: Name("Posición Encoder")]
public float posicionEncoder;
}
}
/*
GUÍA DE CATEGORÍAS:
1. "Identificación"
- Nombre del objeto
- Descripción
- Etiquetas de clasificación
2. "Posición y Tamaño"
- Ya heredadas de osBase: Left, Top, Ancho, Alto, Angulo, Lock_movement
3. "Configuración"
- Parámetros principales del objeto
- Configuraciones específicas por tipo
- Valores que el usuario configura
4. "Simulación"
- Velocidades, fuerzas, coeficientes
- Parámetros físicos
- Comportamiento en simulación
5. "Enlace PLC"
- Tags de entrada y salida
- Conexiones con motores/sensores
- Direcciones de PLC
6. "Información"
- Valores calculados
- Estados actuales
- Datos de depuración
- Información de solo lectura
7. "Apariencia"
- Colores, tamaños visuales
- Aspectos gráficos
- Elementos de UI
8. "Encoder" (solo para motores)
- Configuración del encoder
- Valores del encoder
FORMATO DE DESCRIPCIONES:
- Usar español
- Ser descriptivo y claro
- Explicar qué hace la propiedad
- Evitar términos técnicos innecesarios
FORMATO DE NOMBRES:
- Usar español
- Formato "Título" (primera letra mayúscula)
- Ser conciso pero descriptivo
- Ej: "Velocidad Actual", "Tag Principal", "Con Encoder"
*/

View File

@ -0,0 +1,99 @@
# Guía de Uso: Sistema de Fluidos
## Introducción
El Sistema de Fluidos es una extensión para CtrEditor que permite simular el comportamiento de líquidos en elementos como tuberías, válvulas y tanques. Utiliza una implementación optimizada basada en el método SPH (Smoothed Particle Hydrodynamics) para la simulación de fluidos.
## Componentes del Sistema
El sistema de fluidos incluye los siguientes componentes:
1. **SistemaFluidos**: El componente principal que gestiona la simulación de partículas.
2. **TuberíaFluido**: Componente para crear tuberías por las que circula el fluido.
3. **ValvulaFluido**: Componente que permite regular el flujo del fluido.
## Añadir el Sistema de Fluidos
Para comenzar a utilizar el sistema de fluidos:
1. Inserte un componente "SistemaFluidos" en su diseño.
2. Configure el tamaño del área de simulación y la gravedad según sus necesidades.
3. Añada los componentes de tuberías y válvulas que necesite.
## Propiedades Principales
### SistemaFluidos
- **AnchoSimulacion/AltoSimulacion**: Dimensiones del área de simulación.
- **GravedadX/GravedadY**: Vector de gravedad para la simulación.
- **TamañoParticula**: Tamaño visual de las partículas de fluido.
- **ColorFluido**: Color base para las partículas de fluido.
- **OpacidadParticulas**: Transparencia de las partículas (0-1).
### TuberíaFluido
- **Diametro**: Diámetro de la tubería.
- **Color**: Color de la tubería.
- **ColorFluido**: Color del fluido dentro de la tubería.
### ValvulaFluido
- **Apertura**: Grado de apertura de la válvula (0=cerrada, 1=abierta).
- **DiametroTuberia**: Diámetro de las tuberías conectadas.
- **TagApertura**: Tag PLC para controlar la apertura.
## Integración con PLC
El sistema de fluidos permite integración con PLC mediante tags:
- **TagNivelTanque1**: Lectura/escritura del nivel de llenado del tanque.
- **TagAperturaValvula1**: Lectura/escritura de la apertura de la válvula.
## Añadir Fluido a la Simulación
Para añadir partículas de fluido:
1. Seleccione el componente SistemaFluidos.
2. Utilice el método `AgregarParticulasEnArea` para añadir partículas en una región.
Ejemplo:
```csharp
// Añadir 100 partículas en un área centrada en (x,y) con ancho y alto especificados
_sistemaFluidos.AgregarParticulasEnArea(new Vector2(x, y), ancho, alto, 100);
```
## Optimización
El Sistema de Fluidos utiliza `DrawingVisual` para una renderización eficiente, lo que permite simular miles de partículas con un impacto mínimo en el rendimiento.
## Limitaciones
- La simulación está optimizada para cantidades moderadas de partículas (hasta 10,000).
- Los contenedores (tuberías, válvulas) son aproximaciones y pueden presentar fugas o comportamientos inesperados con flujos muy rápidos.
## Recomendaciones
- Comience con pocas partículas y aumente gradualmente según sea necesario.
- Mantenga la configuración de gravedad en valores realistas.
- Para simular líquidos específicos, ajuste la visualización con el color y opacidad adecuados.
## Ejemplos de Uso
### Sistema Básico de Flujo
```
SistemaFluidos → TuberíaFluido → ValvulaFluido → TuberíaFluido → Tanque
```
### Circuito Cerrado
```
SistemaFluidos → TuberíaFluido → ValvulaFluido → TuberíaFluido → TuberíaFluido (retorno)
```
## Solución de Problemas
- **Partículas escapando de contenedores**: Verifique que los componentes estén correctamente posicionados y conectados.
- **Bajo rendimiento**: Reduzca el número de partículas o el tamaño visual.
- **Cambios de apertura de válvula no afectan el flujo**: Verifique la conexión PLC y la configuración de tags.

View File

@ -0,0 +1,623 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>PaddleOCRSharp</name>
</assembly>
<members>
<member name="T:PaddleOCRSharp.EngineBase">
<summary>
Base class for engine objects
</summary>
</member>
<member name="P:PaddleOCRSharp.EngineBase.PaddleOCRdllPath">
<summary>
Custom loading path for PaddleOCR.dll, default is empty. If specified, it needs to be assigned before engine instantiation.
</summary>
</member>
<member name="M:PaddleOCRSharp.EngineBase.#ctor">
<summary>
Initialization
</summary>
</member>
<member name="M:PaddleOCRSharp.EngineBase.GetDllDirectory">
<summary>
Get the current path of the program
</summary>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.EngineBase.GetRootDirectory">
<summary>
Get the current path of the program
</summary>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.EngineBase.ImageToBytes(System.Drawing.Image)">
<summary>
Convert Image to Byte[]
</summary>
<param name="image"></param>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.EngineBase.Dispose">
<summary>
Release memory
</summary>
</member>
<member name="M:PaddleOCRSharp.EngineBase.GetLastError">
<summary>
Get underlying error information
</summary>
<returns></returns>
</member>
<member name="T:PaddleOCRSharp.JsonHelper">
<summary>
Json helper class
</summary>
</member>
<member name="M:PaddleOCRSharp.JsonHelper.DeserializeObject``1(System.String)">
<summary>
Json deserialization
</summary>
<typeparam name="T"></typeparam>
<param name="json"></param>
<returns></returns>
</member>
<member name="T:PaddleOCRSharp.OCRModelConfig">
<summary>
Model configuration object
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRModelConfig.det_infer">
<summary>
det_infer model path
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRModelConfig.cls_infer">
<summary>
cls_infer model path
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRModelConfig.rec_infer">
<summary>
rec_infer model path
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRModelConfig.keys">
<summary>
Full path of ppocr_keys.txt file
</summary>
</member>
<member name="T:PaddleOCRSharp.StructureModelConfig">
<summary>
Table model configuration object
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureModelConfig.table_model_dir">
<summary>
table_model_dir model path
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureModelConfig.table_char_dict_path">
<summary>
Table recognition dictionary
</summary>
</member>
<member name="T:PaddleOCRSharp.OCRParameter">
<summary>
OCR recognition parameters
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.use_gpu">
<summary>
Whether to use GPU; default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.gpu_id">
<summary>
GPU id, effective when using GPU; default 0
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.gpu_mem">
<summary>
Requested GPU memory; default 4000
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.cpu_math_library_num_threads">
<summary>
Number of threads for CPU prediction. When the machine has sufficient cores, the higher this value, the faster the prediction; default 10
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.enable_mkldnn">
<summary>
Whether to use mkldnn library; default true
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det">
<summary>
Whether to perform text detection; default true
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.rec">
<summary>
Whether to perform text recognition; default true
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.cls">
<summary>
Whether to perform text orientation classification; default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.max_side_len">
<summary>
When the input image length and width are greater than 960, the image is scaled proportionally so that the longest side is 960; default 960
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det_db_thresh">
<summary>
Used to filter the binary image predicted by DB. Setting to 0.-0.3 has no significant effect on results; default 0.3
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det_db_box_thresh">
<summary>
DB post-processing threshold for filtering boxes. If detection has missing boxes, this can be reduced accordingly; default 0.5
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det_db_unclip_ratio">
<summary>
Represents the tightness of the text box. Smaller values mean the text box is closer to the text; default 1.6
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.use_dilation">
<summary>
Whether to use dilation on the output map, default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det_db_score_mode">
<summary>
true: use polygon box to calculate bbox; false: use rectangle box. Rectangle calculation is faster, polygon boxes are more accurate for curved text areas.
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.visualize">
<summary>
Whether to visualize the results. If true, the prediction results will be saved as an ocr_vis.png file in the current directory. Default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.use_angle_cls">
<summary>
Whether to use direction classifier, default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.cls_thresh">
<summary>
Score threshold for direction classifier, default 0.9
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.cls_batch_num">
<summary>
Direction classifier batchsize, default 1
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.rec_batch_num">
<summary>
Recognition model batchsize, default 6
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.rec_img_h">
<summary>
Recognition model input image height, default 48
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.rec_img_w">
<summary>
Recognition model input image width, default 320
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.show_img_vis">
<summary>
Whether to display prediction results, default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.use_tensorrt">
<summary>
When using GPU prediction, whether to enable tensorrt, default false
</summary>
</member>
<member name="T:PaddleOCRSharp.ModifyParameter">
<summary>
OCR modifiable parameters
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_det">
<summary>
Dynamically modify whether to detect. When OCRParameter.det=true, m_det can dynamically turn off the det parameter
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_rec">
<summary>
Dynamically modify whether to recognize. When OCRParameter.rec=true, m_rec can dynamically turn off the rec parameter
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_max_side_len">
<summary>
When the input image length and width are greater than 960, the image is scaled proportionally so that the longest side is 960; default 960
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_det_db_thresh">
<summary>
Used to filter the binary image predicted by DB. Setting to 0.-0.3 has no significant effect on results; default 0.3. Effective when m_det=true
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_det_db_box_thresh">
<summary>
DB post-processing threshold for filtering boxes. If detection has missing boxes, this can be reduced accordingly; default 0.5. Effective when m_det=true
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_det_db_unclip_ratio">
<summary>
Represents the tightness of the text box. Smaller values mean the text box is closer to the text; default 1.6. Effective when m_det=true
</summary>
</member>
<member name="T:PaddleOCRSharp.OCRResult">
<summary>
OCR recognition result
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRResult.TextBlocks">
<summary>
List of text blocks
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRResult.Text">
<summary>
Recognition result text
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRResult.JsonText">
<summary>
Recognition result text in Json format
</summary>
</member>
<member name="M:PaddleOCRSharp.OCRResult.ToString">
<summary>
Return string format
</summary>
</member>
<member name="T:PaddleOCRSharp.TextBlock">
<summary>
Recognized text block
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.BoxPoints">
<summary>
List of coordinate vertices around the text block
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.Text">
<summary>
Text block content
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.Score">
<summary>
Text recognition confidence
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.cls_score">
<summary>
Angle classification confidence
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.cls_label">
<summary>
Angle classification label
</summary>
</member>
<member name="M:PaddleOCRSharp.TextBlock.ToString">
<summary>
Return string format
</summary>
</member>
<member name="T:PaddleOCRSharp.OCRPoint">
<summary>
Point object
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRPoint.X">
<summary>
X coordinate, in pixels
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRPoint.Y">
<summary>
Y coordinate, in pixels
</summary>
</member>
<member name="M:PaddleOCRSharp.OCRPoint.#ctor">
<summary>
Default constructor
</summary>
</member>
<member name="M:PaddleOCRSharp.OCRPoint.#ctor(System.Int32,System.Int32)">
<summary>
Constructor
</summary>
<param name="x"></param>
<param name="y"></param>
</member>
<member name="M:PaddleOCRSharp.OCRPoint.ToString">
<summary>
Return string format
</summary>
</member>
<member name="T:PaddleOCRSharp.OCRStructureResult">
<summary>
OCR structured recognition result
</summary>
</member>
<member name="M:PaddleOCRSharp.OCRStructureResult.#ctor">
<summary>
Table recognition result
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRStructureResult.RowCount">
<summary>
Number of rows
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRStructureResult.ColCount">
<summary>
Number of columns
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRStructureResult.Cells">
<summary>
List of cells
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRStructureResult.TextBlocks">
<summary>
List of text blocks
</summary>
</member>
<member name="T:PaddleOCRSharp.StructureCells">
<summary>
Cell
</summary>
</member>
<member name="M:PaddleOCRSharp.StructureCells.#ctor">
<summary>
Cell constructor
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureCells.Row">
<summary>
Row number
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureCells.Col">
<summary>
Column number
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureCells.TextBlocks">
<summary>
Text blocks
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureCells.Text">
<summary>
Recognized text
</summary>
</member>
<member name="T:PaddleOCRSharp.PaddleOCREngine">
<summary>
PaddleOCR text recognition engine object
</summary>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.#ctor">
<summary>
Initialize OCR engine object with default parameters
</summary>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.#ctor(PaddleOCRSharp.OCRModelConfig)">
<summary>
Initialize OCR engine object with default parameters
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.#ctor(PaddleOCRSharp.OCRModelConfig,PaddleOCRSharp.OCRParameter)">
<summary>
PaddleOCR recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
<param name="parameter">Recognition parameters, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.#ctor(PaddleOCRSharp.OCRModelConfig,System.String)">
<summary>
PaddleOCR recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
<param name="parameterjson">Recognition parameters in json string format</param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.GetDefaultConfig(System.String)">
<summary>
Get default configuration
</summary>
<param name="rootpath">Root directory</param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectText(System.String)">
<summary>
Perform text recognition on image file
</summary>
<param name="imagefile">Image file</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectText(System.Drawing.Bitmap)">
<summary>
Perform text recognition on image object
</summary>
<param name="image">Image</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectText(System.Byte[])">
<summary>
Text recognition
</summary>
<param name="imagebyte">Image memory stream</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectTextBase64(System.String)">
<summary>
Text recognition
</summary>
<param name="imagebase64">Image base64</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectText(System.IntPtr,System.Int32,System.Int32,System.Int32)">
<summary>
Text recognition
</summary>
<param name="imgPtr">Image memory address</param>
<param name="nWidth">Image width</param>
<param name="nHeight">Image height</param>
<param name="nChannel">Image channel, usually 3 or 1</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.ConvertResult(System.IntPtr)">
<summary>
Result parsing
</summary>
<param name="ptrResult"></param>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectStructure(System.Drawing.Bitmap)">
<summary>
Structured text recognition
</summary>
<param name="image">Image</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.getzeroindexs(System.Int32[],System.Int32)">
<summary>
Calculate table splitting
</summary>
<param name="pixellist"></param>
<param name="thresholdtozero"></param>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.ModifyParameter(PaddleOCRSharp.ModifyParameter)">
<summary>
Dynamically modify parameters after initialization
</summary>
<param name="parameter">Modifiable parameter object</param>
<returns>Whether successful, calling before initialization will result in failure</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.EnableDetUseRect(System.Boolean)">
<summary>
Whether to enable rectangular processing of detection results. Single characters are easily detected as diamond shapes, processing them as rectangles can improve recognition accuracy. Only applicable for horizontal text.
</summary>
<param name="enable"></param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.Dispose">
<summary>
Release object
</summary>
</member>
<member name="T:PaddleOCRSharp.PaddleStructureEngine">
<summary>
PaddleOCR table recognition engine object
</summary>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.#ctor">
<summary>
PaddleStructureEngine recognition engine object initialization
</summary>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.#ctor(PaddleOCRSharp.StructureModelConfig)">
<summary>
PaddleStructureEngine recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.#ctor(PaddleOCRSharp.StructureModelConfig,PaddleOCRSharp.StructureParameter)">
<summary>
PaddleStructureEngine recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
<param name="parameter">Recognition parameters, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.#ctor(PaddleOCRSharp.StructureModelConfig,System.String)">
<summary>
PaddleStructureEngine recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
<param name="parameterjson">Recognition parameters in Json format, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.GetDefaultConfig(System.String)">
<summary>
Get default configuration
</summary>
<param name="rootpath">Root directory</param>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.StructureDetectFile(System.String)">
<summary>
Perform table text recognition on image file
</summary>
<param name="imagefile">Image file</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.StructureDetect(System.Drawing.Image)">
<summary>
Perform table text recognition on image object
</summary>
<param name="image">Image</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.StructureDetect(System.Byte[])">
<summary>
Perform table text recognition on image Byte array
</summary>
<param name="imagebyte">Image byte array</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.StructureDetectBase64(System.String)">
<summary>
Perform table text recognition on image Base64
</summary>
<param name="imagebase64">Image Base64</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.ConvertResult(System.IntPtr)">
<summary>
Result parsing
</summary>
<param name="ptrResult"></param>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.Dispose">
<summary>
Release object
</summary>
</member>
<member name="T:PaddleOCRSharp.StructureParameter">
<summary>
OCR recognition parameters
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureParameter.table_max_len">
<summary>
When the input image length and width are greater than 488, the image is scaled proportionally, default 488
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureParameter.merge_no_span_structure">
<summary>
Whether to merge empty cells
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureParameter.table_batch_num">
<summary>
Batch recognition quantity
</summary>
</member>
</members>
</doc>

View File

@ -0,0 +1,30 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace CtrEditor.FuncionesBase
{
public class BooleanToDoubleConverter : IValueConverter
{
public double TrueValue { get; set; } = -1;
public double FalseValue { get; set; } = 1;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return boolValue ? TrueValue : FalseValue;
}
return FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double doubleValue)
{
return Math.Abs(doubleValue - TrueValue) < double.Epsilon;
}
return false;
}
}
}

68
FuncionesBase/Idiomas.cs Normal file
View File

@ -0,0 +1,68 @@
using LanguageDetection;
using System.Collections.Generic;
using System.ComponentModel;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
namespace CtrEditor.FuncionesBase
{
public class Idiomas
{
public const string DEFAULT_LANGUAGE = "English";
private static readonly Dictionary<string, string> _languageMap = new Dictionary<string, string>
{
{ "en", "English" },
{ "es", "Spanish" },
{ "it", "Italian" },
{ "pt", "Portuguese" },
{ "fr", "French" },
{ "de", "German" }
};
private static readonly LanguageDetector _languageDetector;
static Idiomas()
{
_languageDetector = new LanguageDetector();
_languageDetector.AddLanguages("en", "es", "it", "pt", "fr", "de");
}
public static List<string> GetLanguageList()
{
return new List<string>(_languageMap.Values);
}
public static string DetectarIdioma(string texto)
{
if (string.IsNullOrEmpty(texto)) return DEFAULT_LANGUAGE;
try
{
string detectedLanguageCode = _languageDetector.Detect(texto);
return _languageMap.GetValueOrDefault(detectedLanguageCode, DEFAULT_LANGUAGE);
}
catch
{
return DEFAULT_LANGUAGE;
}
}
public static string GetLanguageCode(string languageName)
{
return _languageMap.FirstOrDefault(x => x.Value == languageName).Key ?? "en";
}
}
public class IdiomasItemsSource<T> : IItemsSource
{
public ItemCollection GetValues()
{
ItemCollection items = new ItemCollection();
foreach (string language in Idiomas.GetLanguageList())
{
items.Add(language);
}
return items;
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace CtrEditor.FuncionesBase
{
public struct MutableRect
{
public float Left { get; set; }
public float Top { get; set; }
public float Right { get; set; }
public float Bottom { get; set; }
public MutableRect(float left, float top, float right, float bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public MutableRect(Rect r)
{
Left = (float) r.Left;
Top = (float)r.Top;
Right = (float)r.Right;
Bottom = (float)r.Bottom;
}
public float Width => Right - Left;
public float Height => Bottom - Top;
}
}

153
FuncionesBase/TagPattern.cs Normal file
View File

@ -0,0 +1,153 @@
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
namespace CtrEditor.FuncionesBase
{
public class TagPattern
{
public const string DEFAULT_PATTERN = "DESCRIPCION";
private static readonly Dictionary<string, string> _patternDescriptions = new Dictionary<string, string>
{
{ "Descripcion", "First letter capitalized" },
{ "DESCRIPCION", "All uppercase text" },
{ "Siemens IO Input", "Format: Exxxxx.y (x: 0-65535, y: 0-7)" },
{ "Siemens IO Output", "Format: Axxxxx.y (x: 0-65535, y: 0-7)" },
{ "LETRASNUMEROS", "Format: ABC...123... (letters and numbers only, other chars become _)" },
{ "LETRASNUMEROS/*-+", "Format: ABC...123... (letters, numbers, and /*-+ chars only, other chars become _)" },
{ "LETRASNUMEROSESPACIOS", "Format: ABC...123... (letters, numbers and spaces only, other chars become _)" },
{ "LETRASNUMEROSESPACIOS/*-+", "Format: ABC...123... (letters, numbers, spaces, and /*-+ chars only, other chars become _)" },
{ "Numero", "Numeric value" }
};
public static List<string> GetPatternList()
{
return new List<string>(_patternDescriptions.Keys);
}
public static string ApplyPattern(string text, string pattern)
{
if (string.IsNullOrEmpty(text)) return text;
return pattern switch
{
"Descripcion" => ApplyDescripcionPattern(text),
"DESCRIPCION" => text.ToUpper(),
"Siemens IO Input" => ApplySiemensPattern(text, "E"),
"Siemens IO Output" => ApplySiemensPattern(text, "A"),
"LETRASNUMEROS" => ApplyLetrasNumerosPattern(text),
"LETRASNUMEROS/*-+" => ApplyLetrasNumerosSpecialPattern(text),
"LETRASNUMEROSESPACIOS" => ApplyLetrasNumerosEspaciosPattern(text),
"LETRASNUMEROSESPACIOS/*-+" => ApplyLetrasNumerosEspaciosSpecialPattern(text),
"Numero" => ApplyNumberPattern(text),
_ => text
};
}
private static string ApplyDescripcionPattern(string text)
{
if (string.IsNullOrEmpty(text)) return text;
return char.ToUpper(text[0]) + (text.Length > 1 ? text.Substring(1).ToLower() : "");
}
private static string ApplySiemensPattern(string text, string prefix)
{
var match = Regex.Match(text, $"{prefix}?(\\d{{1,5}})\\.?(\\d)?", RegexOptions.IgnoreCase);
if (match.Success)
{
int address = Math.Min(int.Parse(match.Groups[1].Value), 65535);
int bit = match.Groups[2].Success ?
Math.Min(int.Parse(match.Groups[2].Value), 7) : 0;
return $"{prefix}{address}.{bit}";
}
return $"{prefix}0.0"; // Default value if pattern doesn't match
}
private static string ApplyLetrasNumerosPattern(string text)
{
// Replace any character that is not a letter or digit with "_"
char[] result = new char[text.Length];
for (int i = 0; i < text.Length; i++)
{
if (char.IsLetter(text[i]) || char.IsDigit(text[i]))
result[i] = char.ToUpper(text[i]); // Convert letters to uppercase
else
result[i] = '_';
}
return new string(result);
}
private static string ApplyLetrasNumerosEspaciosPattern(string text)
{
// Replace any character that is not a letter, digit, or space with "_"
char[] result = new char[text.Length];
for (int i = 0; i < text.Length; i++)
{
if (char.IsLetter(text[i]) || char.IsDigit(text[i]) || char.IsWhiteSpace(text[i]))
result[i] = char.ToUpper(text[i]); // Convert letters to uppercase
else
result[i] = '_';
}
return new string(result);
}
private static string ApplyLetrasNumerosSpecialPattern(string text)
{
// Replace any character that is not a letter, digit, or one of /*-+ with "_"
char[] result = new char[text.Length];
for (int i = 0; i < text.Length; i++)
{
if (char.IsLetter(text[i]) || char.IsDigit(text[i]) || text[i] == '/' || text[i] == '*' || text[i] == '-' || text[i] == '+')
result[i] = char.ToUpper(text[i]); // Convert letters to uppercase
else
result[i] = '_';
}
return new string(result);
}
private static string ApplyLetrasNumerosEspaciosSpecialPattern(string text)
{
// Replace any character that is not a letter, digit, space, or one of /*-+ with "_"
char[] result = new char[text.Length];
for (int i = 0; i < text.Length; i++)
{
if (char.IsLetter(text[i]) || char.IsDigit(text[i]) || char.IsWhiteSpace(text[i]) ||
text[i] == '/' || text[i] == '*' || text[i] == '-' || text[i] == '+')
result[i] = char.ToUpper(text[i]); // Convert letters to uppercase
else
result[i] = '_';
}
return new string(result);
}
private static string ApplyNumberPattern(string text)
{
var match = Regex.Match(text, @"-?\d+\.?\d*");
return match.Success ? match.Value : "0";
}
}
public class TagPatternItemsSource<T> : IItemsSource
{
public ItemCollection GetValues()
{
ItemCollection items = new ItemCollection();
foreach (string pattern in TagPattern.GetPatternList())
{
items.Add(pattern);
}
return items;
}
}
}

View File

@ -0,0 +1,16 @@
namespace CtrEditor.FuncionesBase
{
public enum ZIndexEnum
{
Decorativos = 1,
Estaticos = 2,
Generadores = 3,
Guias = 4,
ExtraccionTag = 5,
Dinamicos = 10,
Descarte = 11,
Fotocelula = 12,
Trace = 13,
RectangulosPropiead = 14
}
}

7
IA/appsettings.json Normal file
View File

@ -0,0 +1,7 @@
{
"ApiKeys": {
"OpenAI": "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",
"Groq": "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",
"Grok": "xai-yR7J4s9VIchdjdsaIMP3zzZJBPa98pySDbavh6hEosG3eII9OtQhoKsNUofKUsKrvNUIW15N11BD5cEa"
}
}

421
IA/gtpask.cs Normal file
View File

@ -0,0 +1,421 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using LanguageDetection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using CtrEditor.FuncionesBase;
namespace GTPCorrgir
{
public class Opciones
{
public enum LLM_a_Usar
{
OpenAI,
Ollama,
Groq,
Grok
}
public enum modoDeUso
{
Corregir,
Ortografia,
Traducir_a_Ingles,
Traducir_a_Italiano,
Traducir_a_Espanol,
}
public Dictionary<LLM_a_Usar, string> nombreLLM = new Dictionary<LLM_a_Usar, string>
{
{ LLM_a_Usar.Ollama, "Ollama" },
{ LLM_a_Usar.Groq, "Groq" },
{ LLM_a_Usar.Grok, "Grok" },
{ LLM_a_Usar.OpenAI, "OpenAI" },
};
public LLM_a_Usar LLM { get; set; }
public modoDeUso modo { get; set; }
public string nombreDeLLM()
{
return nombreLLM[LLM];
}
public Opciones() { } // Changed from private to public
}
public class ApiSettings
{
public class ApiKeySection
{
public string OpenAI { get; set; }
public string Groq { get; set; }
public string Grok { get; set; }
}
public ApiKeySection ApiKeys { get; set; }
}
public class gtpask : IDisposable
{
private string _openAiApiKey;
private string _groqApiKey;
private string _grokApiKey;
private readonly HttpClient _httpClient;
private bool _disposed;
public string IdiomaDetectado { get; private set; }
public string TextoACorregir { get; set; }
public string TextoCorregido { get; private set; }
public string TextodeSistema { get; set; }
public gtpask()
{
try
{
_httpClient = new HttpClient();
LoadApiKeys();
InitializeHttpClient();
}
catch (Exception ex)
{
throw new ApplicationException("Failed to initialize gtpask", ex);
}
}
private void LoadApiKeys()
{
try
{
string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "IA/appsettings.json");
if (!File.Exists(configPath))
{
throw new FileNotFoundException("Configuration file (appsettings.json) not found.");
}
string jsonContent = File.ReadAllText(configPath);
var settings = JsonConvert.DeserializeObject<ApiSettings>(jsonContent);
_openAiApiKey = settings?.ApiKeys?.OpenAI;
_groqApiKey = settings?.ApiKeys?.Groq;
_grokApiKey = settings?.ApiKeys?.Grok;
ValidateApiKeys();
}
catch (Exception ex)
{
throw new ApplicationException("Failed to load API keys", ex);
}
}
private void ValidateApiKeys()
{
var missingKeys = new List<string>();
if (string.IsNullOrEmpty(_openAiApiKey)) missingKeys.Add("OpenAI");
if (string.IsNullOrEmpty(_groqApiKey)) missingKeys.Add("Groq");
if (string.IsNullOrEmpty(_grokApiKey)) missingKeys.Add("Grok");
if (missingKeys.Any())
{
string missingKeysStr = string.Join(", ", missingKeys);
throw new ApplicationException($"Missing API keys: {missingKeysStr}");
}
}
private void InitializeHttpClient()
{
_httpClient.Timeout = TimeSpan.FromSeconds(30);
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
private bool DetectarIdioma()
{
try
{
IdiomaDetectado = Idiomas.DetectarIdioma(TextoACorregir);
return IdiomaDetectado != "Unknown";
}
catch
{
return false;
}
}
private async Task ProcesarTextoConLLM( Opciones Modelo)
{
try
{
string respuestaLLM;
switch (Modelo.LLM)
{
case Opciones.LLM_a_Usar.OpenAI:
respuestaLLM = await CallOpenAiApi();
break;
case Opciones.LLM_a_Usar.Ollama:
respuestaLLM = await CallOllamaApi();
break;
case Opciones.LLM_a_Usar.Groq:
respuestaLLM = await CallGroqAiApi();
break;
case Opciones.LLM_a_Usar.Grok:
respuestaLLM = await CallGrokApi();
break;
default:
throw new ArgumentException("LLM no válido");
}
if (string.IsNullOrEmpty(respuestaLLM))
{
throw new ApplicationException("No se recibió respuesta del LLM");
}
TextoCorregido = respuestaLLM;
}
catch (Exception ex)
{
throw;
}
}
private async Task SimularCorreccion()
{
await Task.Delay(1000);
TextoCorregido = "Texto simulado de prueba";
}
private async Task<string> CallGrokApi()
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_grokApiKey}");
var requestData = new
{
messages = new[]
{
new { role = "system", content = TextodeSistema },
new { role = "user", content = TextoACorregir }
},
model = "grok-beta",
stream = false,
temperature = 0
};
return await EnviarSolicitudLLM("https://api.x.ai/v1/chat/completions", requestData);
}
catch (Exception ex)
{
throw;
}
}
private async Task<string> CallOllamaApi()
{
try
{
var requestData = new
{
model = "llama3.2:latest",
messages = new[]
{
new { role = "system", content = TextodeSistema },
new { role = "user", content = TextoACorregir }
},
stream = false
};
return await EnviarSolicitudLLM("http://127.0.0.1:11434/api/chat", requestData);
}
catch (Exception ex)
{
throw;
}
}
private async Task<string> CallOpenAiApi()
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_openAiApiKey}");
var requestData = new
{
model = "gpt-4o-mini",
messages = new[]
{
new { role = "system", content = TextodeSistema },
new { role = "user", content = TextoACorregir }
}
};
return await EnviarSolicitudLLM("https://api.openai.com/v1/chat/completions", requestData);
}
catch (Exception ex)
{
throw;
}
}
private async Task<string> CallGroqAiApi()
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_groqApiKey}");
var requestData = new
{
model = "llama-3.2-3b-preview",
messages = new[]
{
new { role = "system", content = TextodeSistema },
new { role = "user", content = TextoACorregir }
},
max_tokens = 2048,
stream = false
};
return await EnviarSolicitudLLM("https://api.groq.com/openai/v1/chat/completions", requestData);
}
catch (Exception ex)
{
throw;
}
}
private async Task<string> EnviarSolicitudLLM(string endpoint, object requestData)
{
try
{
var content = new StringContent(
JsonConvert.SerializeObject(requestData),
Encoding.UTF8,
"application/json"
);
using var response = await _httpClient.PostAsync(endpoint, content);
var responseContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Error en la solicitud HTTP: {response.StatusCode} - {responseContent}"
);
}
var data = JsonConvert.DeserializeObject<dynamic>(responseContent);
// Manejar diferentes formatos de respuesta según el LLM
if (endpoint.Contains("ollama"))
{
if (data.done == true && data.message != null)
{
return data.message.content;
}
throw new ApplicationException("Formato de respuesta de Ollama inválido");
}
else // OpenAI, Groq, Grok
{
if (data.choices != null && data.choices.Count > 0)
{
return data.choices[0].message.content;
}
throw new ApplicationException("No se encontró contenido en la respuesta del LLM");
}
}
catch (Exception ex)
{
throw;
}
}
public async Task CorregirTexto()
{
try
{
if (string.IsNullOrEmpty(TextoACorregir))
{
throw new ArgumentException("TextoACorregir cannot be null or empty");
}
if (string.IsNullOrEmpty(TextodeSistema))
{
throw new ArgumentException("TextodeSistema cannot be null or empty");
}
var opciones = new Opciones { LLM = Opciones.LLM_a_Usar.Grok };
await ProcesarTextoConLLM(opciones);
}
catch (Exception ex)
{
throw new LLMException("Error during text correction", ex);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_httpClient?.Dispose();
}
_disposed = true;
}
}
~gtpask()
{
Dispose(false);
}
}
// Clase auxiliar para manejar excepciones específicas de la aplicación
public class LLMException : Exception
{
public LLMException(string message) : base(message) { }
public LLMException(string message, Exception innerException) : base(message, innerException) { }
}
// Clase auxiliar para validación
public static class Validations
{
public static void ValidateNotNull(object value, string paramName)
{
if (value == null)
{
throw new ArgumentNullException(paramName);
}
}
public static void ValidateNotNullOrEmpty(string value, string paramName)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("Value cannot be null or empty", paramName);
}
}
}
}

View File

@ -0,0 +1,147 @@
import pandas as pd
import os
import logging
import json
import PyLibrary.funciones_comunes as fc
from openai import OpenAI
from openai_api_key import openai_api_key
# Definir el logger a nivel de módulo
logger = None
def corregir_texto_batch(text_pairs, logger):
"""
Corrige errores de OCR en lotes de pares de texto usando GPT-4.
Cada par consiste en dos versiones del mismo texto en diferentes idiomas.
Args:
text_pairs: Lista de tuplas (texto1, texto2) donde cada texto es una lectura OCR
logger: Logger para registrar el proceso
"""
client = OpenAI(api_key=openai_api_key())
system_prompt = """You are an OCR correction specialist. For each pair of texts, which are OCR readings of the same text in different languages, analyze and correct any obvious OCR errors.
Pay special attention to:
- Incorrectly joined words (missing spaces)
- Wrong character recognition (0 vs O, 1 vs I, etc.)
- Extra or missing characters
Return the corrected versions maintaining the general structure and meaning.
Input format: List of text pairs
Expected output format: List of corrected pairs in the same order
Example input: [["PULSANTE DI STARTNASTRI", "START PUSHBUTTONTTOP"]]
Example output: [["PULSANTE DI START NASTRI", "START PUSH BUTTON TOP"]]"""
try:
# Convertir los pares de texto a formato JSON
request_payload = json.dumps({"pairs": text_pairs})
logger.info(f"Solicitando corrección para el lote de textos:\n{request_payload}")
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": request_payload},
],
max_tokens=2000,
temperature=0.3,
)
# Procesar la respuesta
corrections = json.loads(response.choices[0].message.content)
logger.info(f"Correcciones recibidas:\n{corrections}")
return corrections["pairs"]
except Exception as e:
logger.error(f"Error en la corrección por lotes: {str(e)}")
return None
def procesar_archivo(config, archivo_entrada):
"""
Procesa el archivo Excel con los textos OCR y genera un nuevo archivo con las correcciones.
"""
logger.info(f"Iniciando procesamiento de {archivo_entrada}")
try:
# Leer el archivo de entrada
df = fc.read_dataframe_with_cleanup_retries(archivo_entrada)
if config.columna_origen1 not in df.columns or config.columna_origen2 not in df.columns:
logger.error(f"Columnas requeridas no encontradas en el archivo")
print(f"Error: Las columnas {config.columna_origen1} y/o {config.columna_origen2} no existen en el archivo")
return
# Filtrar filas donde ambas columnas tienen contenido
df_filtered = df.dropna(subset=[config.columna_origen1, config.columna_origen2])
df_filtered = df_filtered[
(df_filtered[config.columna_origen1].astype(str).str.strip() != '') &
(df_filtered[config.columna_origen2].astype(str).str.strip() != '')
]
# Crear columnas para textos corregidos
df[f"{config.columna_origen1}_Corregido"] = df[config.columna_origen1]
df[f"{config.columna_origen2}_Corregido"] = df[config.columna_origen2]
# Procesar en lotes de 10 pares
batch_size = 10
total_rows = len(df_filtered)
progress_bar = fc.ProgressBar(
total_rows, prefix="Procesando textos:", suffix="Completado"
)
for i in range(0, total_rows, batch_size):
batch_df = df_filtered.iloc[i:i+batch_size]
# Preparar pares de textos para el lote
text_pairs = [
[str(row[config.columna_origen1]).strip(), str(row[config.columna_origen2]).strip()]
for _, row in batch_df.iterrows()
]
# Obtener correcciones para el lote
corrections = corregir_texto_batch(text_pairs, logger)
if corrections:
# Aplicar correcciones al DataFrame original
for j, correction in enumerate(corrections):
idx = batch_df.index[j]
df.at[idx, f"{config.columna_origen1}_Corregido"] = correction[0]
df.at[idx, f"{config.columna_origen2}_Corregido"] = correction[1]
progress_bar.update(min(i + batch_size, total_rows))
progress_bar.finish()
# Guardar resultados
output_path = config.get_output_path()
fc.save_dataframe_with_retries(df, output_path)
logger.info(f"Archivo procesado y guardado en: {output_path}")
print(f"\nArchivo procesado y guardado en: {output_path}")
print(f"Se procesaron {total_rows} pares de texto.")
except Exception as e:
logger.error(f"Error durante el procesamiento: {str(e)}")
print(f"Error durante el procesamiento: {str(e)}")
def run(config):
global logger
logger = fc.configurar_logger(config.work_dir)
script_name = os.path.basename(__file__)
print(f"\rIniciando: {script_name}\r")
# Solicitar archivo de entrada
from tkinter import filedialog
archivo_entrada = filedialog.askopenfilename(
title="Seleccione el archivo con textos OCR",
filetypes=[("Excel files", "*.xls*")]
)
if archivo_entrada:
procesar_archivo(config, archivo_entrada)
else:
print("No se seleccionó ningún archivo para procesar.")
if __name__ == "__main__":
import menu_ocr_correction
menu_ocr_correction.main()

BIN
Icons/allselect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
Icons/analyze.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
Icons/choose.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
Icons/extract.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

9
Icons/fluid.svg Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Fluid</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="fluid" fill="#000000" fill-rule="nonzero">
<path d="M12,2 C8.1,2 5,5.1 5,9 C5,13.9 12,22 12,22 C12,22 19,13.9 19,9 C19,5.1 15.9,2 12,2 Z M12,11.5 C10.6,11.5 9.5,10.4 9.5,9 C9.5,7.6 10.6,6.5 12,6.5 C13.4,6.5 14.5,7.6 14.5,9 C14.5,10.4 13.4,11.5 12,11.5 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 622 B

BIN
Icons/match.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
Icons/ocr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
Icons/rotationRx.cur Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
Icons/rotationSx.cur Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
Icons/unselect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
Images/base.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,57 @@
<Window
xmlns:ctreditor="clr-namespace:CtrEditor"
<Window xmlns:ctreditor="clr-namespace:CtrEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Siemens="clr-namespace:CtrEditor.Siemens"
xmlns:convert="clr-namespace:CtrEditor.Convertidores"
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim" x:Class="CtrEditor.MainWindow"
Height="900" Width="1600" WindowState="Maximized"
ResizeMode="CanResize" Title="{Binding directorioTrabajo}" Icon="/app2.png">
xmlns:Siemens="clr-namespace:LibS7Adv;assembly=LibS7Adv"
xmlns:local="clr-namespace:CtrEditor"
xmlns:controls="clr-namespace:CtrEditor.Controls"
xmlns:uc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
xmlns:converters="clr-namespace:CtrEditor.Converters"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim"
xmlns:ObjetosExtraccion="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos"
x:Class="CtrEditor.MainWindow"
Height="900" Width="1600" WindowState="Maximized" ResizeMode="CanResize" Title="{Binding directorioTrabajo, Converter={StaticResource UnsavedChangesConverter}}"
Icon="/app2.png">
<Window.DataContext>
<ctreditor:MainViewModel/>
<ctreditor:MainViewModel />
</Window.DataContext>
<Window.Resources>
<convert:FloatToFormattedStringConverter x:Key="floatFormatter"/>
<convert:DoubleToFormattedStringConverter x:Key="doubleFormatter"/>
<convert:BrushToColorNameConverter x:Key="BrushToColorNameConverter"/>
<!-- Style for Start/Stop Button -->
<Style x:Key="StartStopButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSimulationRunning}" Value="True">
<Setter Property="Background" Value="LightGreen"/>
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- Style for Connect/Disconnect Button -->
<Style x:Key="ConnectDisconnectButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="5" />
<Setter Property="MinWidth" Value="80" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Background" Value="LightGreen"/>
<Setter Property="Background" Value="#90EE90" />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- Converter for PLC Connect/Disconnect button text -->
<local:ConnectStateToBtnTextConverter x:Key="ConnectStateToBtnTextConverter" />
<!-- Converter for PLC Connect/Disconnect image -->
<local:ConnectStateToImageConverter x:Key="ConnectStateToImageConverter" />
<!-- Converter for Image Display Names -->
<converters:ImageDisplayNameConverter x:Key="ImageDisplayNameConverter" />
</Window.Resources>
<Grid>
@ -44,8 +59,17 @@
<Menu VerticalAlignment="Top" HorizontalAlignment="Stretch">
<MenuItem Header="Projecto">
<MenuItem Header="Abrir Directorio de trabajo" Command="{Binding OpenWorkDirectoryCommand}" />
<MenuItem Header="Ultimos Directorios Utilizados" ItemsSource="{Binding RecentDirectories}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding}" Command="{Binding DataContext.OpenRecentDirectoryCommand, RelativeSource={RelativeSource AncestorType=MenuItem, AncestorLevel=2}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
<MenuItem Header="Iniciar Simulacion" Command="{Binding StartSimulationCommand}" />
<MenuItem Header="Detenet Simulacion" Command="{Binding StopSimulationCommand}" />
<MenuItem Header="Debug Window" Command="{Binding DebugWindowCommand}" />
<MenuItem Header="Guardar" Command="{Binding SaveCommand}" />
<MenuItem Header="Salir" Command="{Binding ExitCommand}" />
</MenuItem>
@ -54,27 +78,42 @@
<Grid Margin="0,20,0,0">
<!-- Margen superior para el menú -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" MinWidth="100"/>
<ColumnDefinition Width="8*" MinWidth="200"/>
<ColumnDefinition Width="2*" MinWidth="100"/>
<ColumnDefinition Width="1*" MinWidth="200" />
<ColumnDefinition Width="8*" MinWidth="200" />
<ColumnDefinition Width="2*" MinWidth="100" />
</Grid.ColumnDefinitions>
<!-- Primera Columna -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="ListaImagenes" Grid.Row="0" Margin="5" ItemsSource="{Binding ListaImagenes}" SelectedItem="{Binding SelectedImage}" />
<ListBox x:Name="ListaFunciones" Grid.Row="1" Margin="5" ItemsSource="{Binding ListaOsBase}" DisplayMemberPath="Nombre" SelectedItem="{Binding SelectedItem}">
<ListBox x:Name="ListaImagenes" Grid.Row="0" Margin="5" ItemsSource="{Binding ListaImagenes}"
SelectedItem="{Binding SelectedImage}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ImageDisplayNameConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Propiedades" Command="{Binding RenameImageCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<ListBox x:Name="ListaFunciones" Grid.Row="1" Margin="5" ItemsSource="{Binding ListaOsBase}"
DisplayMemberPath="Nombre" SelectedItem="{Binding SelectedItem}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding ItemDoubleClickCommand}" CommandParameter="{Binding SelectedItem}"/>
<i:InvokeCommandAction Command="{Binding ItemDoubleClickCommand}"
CommandParameter="{Binding SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
<Siemens:PLCControl x:Name="PLCSim" Grid.Row="2" Margin="5" DataContext="{Binding PLCViewModel}"/>
<Siemens:PLCControl x:Name="PLCSim" Grid.Row="2" Margin="5" DataContext="{Binding PLCViewModel}" />
</Grid>
<!-- GridSplitter -->
<GridSplitter Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" Width="5" Background="LightGray" />
@ -88,45 +127,100 @@
<ToolBarTray Grid.Row="0">
<ToolBar>
<Button Command="{Binding TBStartSimulationCommand}" ToolTip="Iniciar Simulación" Style="{StaticResource StartStopButtonStyle}">
<StackPanel>
<Image Source="Icons/start.png" Width="16" Height="16"/>
<TextBlock Text="Iniciar"/>
</StackPanel>
</Button>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding TBStartSimulationCommand}" ToolTip="Iniciar Simulación"
Style="{StaticResource StartStopButtonStyle}">
<StackPanel>
<Image Source="Icons/start.png" Width="24" Height="24" />
<TextBlock Text="Iniciar" />
</StackPanel>
</Button>
<!-- Grid para el indicador de Simulación -->
<Grid Width="15" Margin="2,0">
<TextBlock Text="{Binding SimulationSpeed, StringFormat={}{0:F1}}"
RenderTransformOrigin="0.5,0.5" TextAlignment="Center"
Background="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Grid>
</StackPanel>
<Button Command="{Binding TBStopSimulationCommand}" ToolTip="Detener Simulación">
<StackPanel>
<Image Source="Icons/stop.png" Width="16" Height="16"/>
<TextBlock Text="Detener"/>
<Image Source="Icons/stop.png" Width="24" Height="24" />
<TextBlock Text="Detener" />
</StackPanel>
</Button>
<Button Command="{Binding TBSaveCommand}" ToolTip="Guardar">
<StackPanel>
<Image Source="Icons/save.png" Width="16" Height="16"/>
<TextBlock Text="Guardar"/>
<Image Source="Icons/save.png" Width="24" Height="24" />
<TextBlock Text="Guardar" />
</StackPanel>
</Button>
<Button Command="{Binding TBConnectPLCCommand}" ToolTip="Conectar PLC" Style="{StaticResource ConnectDisconnectButtonStyle}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding TBTogglePLCConnectionCommand}"
ToolTip="{Binding IsConnected, Converter={StaticResource ConnectStateToBtnTextConverter}}"
Style="{StaticResource ConnectDisconnectButtonStyle}">
<StackPanel>
<Image Source="{Binding IsConnected, Converter={StaticResource ConnectStateToImageConverter}}"
Width="24" Height="24" />
<TextBlock Text="{Binding IsConnected, Converter={StaticResource ConnectStateToBtnTextConverter}}" />
</StackPanel>
</Button>
<!-- Grid para el indicador de PLC -->
<Grid Width="15" Margin="2,0">
<TextBlock Text="{Binding PlcUpdateSpeed, StringFormat={}{0:F1}}"
RenderTransformOrigin="0.5,0.5" TextAlignment="Center"
Background="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Grid>
</StackPanel>
<Button Command="{Binding TBMultiPageAnalizeCommand}"
ToolTip="Analyze Tags in multiple pages.">
<StackPanel>
<Image Source="Icons/connect.png" Width="16" Height="16"/>
<TextBlock Text="Conectar"/>
<Image Source="Icons/match.png" Width="24" Height="24" />
<TextBlock Text="Multi Page Analyze" />
</StackPanel>
</Button>
<Button Command="{Binding TBDisconnectPLCCommand}" ToolTip="Desconectar PLC">
<Button Command="{Binding TBMultiPageMatrixCommand}" ToolTip="Analyze Matrix (Multiple Pages)">
<StackPanel>
<Image Source="Icons/disconnect.png" Width="16" Height="16"/>
<TextBlock Text="Desconectar"/>
<Image Source="Icons/ocr.png" Width="24" Height="24" />
<TextBlock Text="Exportar Tags a Excel" />
</StackPanel>
</Button>
</ToolBar>
</ToolBarTray>
<ScrollViewer Grid.Row="1" x:Name="ImagenEnTrabajoScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" PanningMode="Both">
<Canvas x:Name="ImagenEnTrabajoCanvas" Margin="400">
<!-- El Margin puede ser ajustado según el espacio adicional que quieras proporcionar -->
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="1"/>
</Canvas.LayoutTransform>
<ScrollViewer Grid.Row="1" x:Name="ImagenEnTrabajoScrollViewer"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
PanningMode="Both"
PreviewKeyDown="ScrollViewer_PreviewKeyDown">
<Canvas x:Name="ImagenEnTrabajoCanvas"
Margin="0"
Background="Transparent"
Focusable="True"
FocusVisualStyle="{x:Null}"
KeyDown="Canvas_KeyDown">
<!-- Agregar Background="Transparent" para que capture los eventos del mouse y -->
<!-- asegurar que el Canvas reciba los eventos del botón derecho -->
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="CanvasScaleTransform" ScaleX="1" ScaleY="1" />
<TranslateTransform x:Name="CanvasTranslateTransform" />
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
</ScrollViewer>
</Grid>
@ -137,51 +231,120 @@
<!-- Tercera Columna -->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<!-- Altura ajustable para el ListBox -->
<RowDefinition Height="Auto" />
<!-- Espacio para el GridSplitter -->
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<!-- Altura ajustable para el PanelEdicion -->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ToolBarTray Grid.Row="0">
<ToolBar>
<Button Command="{Binding TBEliminarTodosCommand}" ToolTip="Eliminar Todos">
<StackPanel>
<Image Source="Icons/borrar.png" Width="16" Height="16" />
<TextBlock Text="All Objects" />
</StackPanel>
</Button>
<Button Command="{Binding TBEliminarAutoCreatedCommand}" ToolTip="Eliminar Auto">
<StackPanel>
<Image Source="Icons/borrar.png" Width="16" Height="16" />
<TextBlock Text="Auto Created" />
</StackPanel>
</Button>
<Button Command="{Binding TBEliminarClonedCommand}" ToolTip="Eliminar Cloned for tag Extraction">
<StackPanel>
<Image Source="Icons/borrar.png" Width="16" Height="16" />
<TextBlock Text="Cloned" />
</StackPanel>
</Button>
<Button Command="{Binding TBAssingPagesCommand}" ToolTip="Assing Pages">
<StackPanel>
<Image Source="Icons/choose.png" Width="16" Height="16" />
<TextBlock Text="Assing Pages" />
</StackPanel>
</Button>
<Button Command="{Binding TBLibraryManagerCommand}" ToolTip="Gestión de Biblioteca de Objetos">
<StackPanel>
<Image Source="Icons/app.png" Width="16" Height="16" />
<TextBlock Text="Biblioteca" />
</StackPanel>
</Button>
</ToolBar>
</ToolBarTray>
<!-- Filter Control -->
<Expander Grid.Row="1" Header="Filters" IsExpanded="False">
<controls:osVisFilter x:Name="VisFilter" Margin="5"/>
</Expander>
<!-- ListBox -->
<ListBox x:Name="ListaOs"
Grid.Row="0"
Margin="5"
ItemsSource="{Binding ObjetosSimulables}"
DisplayMemberPath="Nombre"
SelectedItem="{Binding SelectedItemOsList, Mode=TwoWay}"
SelectionChanged="ListaOs_SelectionChanged"/>
<Expander Grid.Row="2" Header="Objects List" IsExpanded="False">
<ListBox x:Name="ListaOs"
Margin="5"
MinHeight="100"
MaxHeight="300"
ItemsSource="{Binding ObjetosSimulables}"
SelectedItem="{Binding SelectedItemOsList, Mode=TwoWay}"
SelectionChanged="ListaOs_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Nombre}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Enable_On_All_Pages}" Value="True">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Enable_On_All_Pages}" Value="False">
<Setter Property="Foreground" Value="Black" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsVisFilter}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
<!-- Object Hierarchy -->
<Expander Grid.Row="3" Header="Object Hierarchy" IsExpanded="True">
<controls:ObjectHierarchyView x:Name="ObjectHierarchy"
Margin="5"
MinHeight="100"
MaxHeight="300"/>
</Expander>
<!-- GridSplitter -->
<GridSplitter Grid.Row="1"
Height="5"
HorizontalAlignment="Stretch"
Background="Gray"
ResizeDirection="Rows"
VerticalAlignment="Center"/>
<GridSplitter Grid.Row="4" Height="5"
HorizontalAlignment="Stretch"
Background="Gray"
ResizeDirection="Rows"
VerticalAlignment="Center" />
<!-- PanelEdicion -->
<ScrollViewer Grid.Row="2" Margin="5" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="PanelEdicion">
<!-- Aquí puedes agregar los controles para editar propiedades -->
</StackPanel>
</ScrollViewer>
<controls:PanelEdicionControl Grid.Row="5"
x:Name="PanelEdicion"
Margin="5"/>
<ToolBarTray Grid.Row="3">
<ToolBarTray Grid.Row="6">
<ToolBar>
<Button Command="{Binding TBEliminarUserControlCommand}" ToolTip="Eliminar Control">
<StackPanel>
<Image Source="Icons/borrar.png" Width="16" Height="16"/>
<TextBlock Text="Eliminar"/>
<Image Source="Icons/borrar.png" Width="16" Height="16" />
<TextBlock Text="Eliminar" />
</StackPanel>
</Button>
<Button Command="{Binding TBDuplicarUserControlCommand}" ToolTip="Duplicar Control">
<StackPanel>
<Image Source="Icons/duplicate.png" Width="16" Height="16"/>
<TextBlock Text="Duplicar"/>
<Image Source="Icons/duplicate.png" Width="16" Height="16" />
<TextBlock Text="Duplicar" />
</StackPanel>
</Button>
</ToolBar>

File diff suppressed because it is too large Load Diff

110
Models/ImageData.cs Normal file
View File

@ -0,0 +1,110 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using CtrEditor.ObjetosSim;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
using System.Linq;
namespace CtrEditor.Models
{
public class ImageData : INotifyPropertyChanged
{
private string _customName;
private List<string> _tags;
private string _etiquetas;
public string FileName { get; set; }
[DisplayName("Nombre personalizado")]
public string CustomName
{
get => _customName;
set
{
_customName = value;
OnPropertyChanged();
}
}
[Browsable(false)]
public List<string> Tags
{
get => _tags ?? new List<string>();
set
{
_tags = value;
_etiquetas = string.Join(" ", value.Select(tag => tag.StartsWith("#") ? tag : "#" + tag));
OnPropertyChanged();
OnPropertyChanged(nameof(Etiquetas));
}
}
[DisplayName("Etiquetas")]
[property: Editor(typeof(TagPropertyEditor), typeof(TagPropertyEditor))]
public string Etiquetas
{
get => _etiquetas ?? string.Empty;
set
{
_etiquetas = value ?? string.Empty;
// Convertir string de etiquetas a List<string>
if (string.IsNullOrWhiteSpace(_etiquetas))
{
_tags = new List<string>();
}
else
{
_tags = _etiquetas.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Select(tag => tag.StartsWith("#") ? tag.Substring(1) : tag)
.Where(tag => !string.IsNullOrWhiteSpace(tag))
.ToList();
}
OnPropertyChanged();
OnPropertyChanged(nameof(Tags));
}
}
/// <summary>
/// Lista de etiquetas sin el prefijo #, compatible con osBase
/// </summary>
[Browsable(false)]
public List<string> ListaEtiquetas
{
get
{
if (string.IsNullOrWhiteSpace(Etiquetas))
return new List<string>();
return Etiquetas.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Select(tag => tag.StartsWith("#") ? tag.Substring(1) : tag)
.Where(tag => !string.IsNullOrWhiteSpace(tag))
.ToList();
}
}
public string DisplayName => !string.IsNullOrEmpty(CustomName) ? CustomName : FileName;
public ImageData()
{
FileName = string.Empty;
_customName = string.Empty;
_tags = new List<string>();
_etiquetas = string.Empty;
}
public ImageData(string fileName, string customName = null, List<string> tags = null)
{
FileName = fileName;
_customName = customName ?? string.Empty;
_tags = tags ?? new List<string>();
_etiquetas = string.Join(" ", _tags.Select(tag => tag.StartsWith("#") ? tag : "#" + tag));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

268
ObjectAlignment.cs Normal file
View File

@ -0,0 +1,268 @@
using CtrEditor.ObjetosSim;
using System.Collections.ObjectModel;
using System.Windows;
namespace CtrEditor
{
public class ObjectAlignment
{
private readonly ObservableCollection<osBase> _selectedObjects;
private readonly osBase _referenceObject;
public ObjectAlignment(ObservableCollection<osBase> selectedObjects, osBase referenceObject = null)
{
_selectedObjects = selectedObjects;
_referenceObject = referenceObject ?? selectedObjects.FirstOrDefault();
}
public void AlignLeft()
{
if (_selectedObjects.Count <= 1) return;
float leftMost = _selectedObjects.Min(obj => obj.Left);
foreach (var obj in _selectedObjects)
{
obj.Left = leftMost;
}
}
public void AlignRight()
{
if (_selectedObjects.Count <= 1) return;
float rightMost = _selectedObjects.Max(obj => obj.Left + obj.Ancho);
foreach (var obj in _selectedObjects)
{
obj.Left = rightMost - obj.Ancho;
}
}
public void AlignTop()
{
if (_selectedObjects.Count <= 1) return;
float topMost = _selectedObjects.Min(obj => obj.Top);
foreach (var obj in _selectedObjects)
{
obj.Top = topMost;
}
}
public void AlignBottom()
{
if (_selectedObjects.Count <= 1) return;
float bottomMost = _selectedObjects.Max(obj => obj.Top + obj.Alto);
foreach (var obj in _selectedObjects)
{
obj.Top = bottomMost - obj.Alto;
}
}
public void AlignCenterHorizontally()
{
if (_selectedObjects.Count <= 1) return;
float averageY = _selectedObjects.Average(obj => obj.Top + obj.Alto / 2);
foreach (var obj in _selectedObjects)
{
obj.Top = averageY - obj.Alto / 2;
}
}
public void AlignCenterVertically()
{
if (_selectedObjects.Count <= 1) return;
float averageX = _selectedObjects.Average(obj => obj.Left + obj.Ancho / 2);
foreach (var obj in _selectedObjects)
{
obj.Left = averageX - obj.Ancho / 2;
}
}
public void DistributeHorizontally()
{
if (_selectedObjects.Count <= 2) return;
var objectsWithCenters = _selectedObjects
.Select(obj => new
{
Object = obj,
Center = GetObjectCenter(obj),
Dimensions = GetEffectiveDimensions(obj)
})
.OrderBy(x => x.Center.X)
.ToList();
float leftMost = (float)objectsWithCenters.First().Center.X;
float rightMost = (float)objectsWithCenters.Last().Center.X;
float totalDistance = rightMost - leftMost;
float spacing = totalDistance / (_selectedObjects.Count - 1);
for (int i = 1; i < objectsWithCenters.Count - 1; i++)
{
var obj = objectsWithCenters[i];
var targetX = leftMost + (spacing * i);
float deltaX = (float)(targetX - obj.Center.X);
obj.Object.Left += deltaX;
}
}
public void DistributeVertically()
{
if (_selectedObjects.Count <= 2) return;
var objectsWithCenters = _selectedObjects
.Select(obj => new
{
Object = obj,
Center = GetObjectCenter(obj),
Dimensions = GetEffectiveDimensions(obj)
})
.OrderBy(x => x.Center.Y)
.ToList();
float topMost = (float)objectsWithCenters.First().Center.Y;
float bottomMost = (float)objectsWithCenters.Last().Center.Y;
float totalDistance = bottomMost - topMost;
float spacing = totalDistance / (_selectedObjects.Count - 1);
for (int i = 1; i < objectsWithCenters.Count - 1; i++)
{
var obj = objectsWithCenters[i];
var targetY = topMost + (spacing * i);
float deltaY = (float)(targetY - obj.Center.Y);
obj.Object.Top += deltaY;
}
}
public void EqualWidth()
{
if (_selectedObjects.Count <= 1) return;
float referenceWidth = GetEffectiveDimensions(_referenceObject).Width;
foreach (var obj in _selectedObjects.Where(o => o != _referenceObject))
{
var currentDims = GetEffectiveDimensions(obj);
SetEffectiveDimensions(obj, referenceWidth, currentDims.Height);
}
}
public void EqualHeight()
{
if (_selectedObjects.Count <= 1) return;
float referenceHeight = GetEffectiveDimensions(_referenceObject).Height;
foreach (var obj in _selectedObjects.Where(o => o != _referenceObject))
{
var currentDims = GetEffectiveDimensions(obj);
SetEffectiveDimensions(obj, currentDims.Width, referenceHeight);
}
}
public void EqualAngle()
{
if (_selectedObjects.Count <= 1) return;
float referenceAngle = _referenceObject.Angulo;
foreach (var obj in _selectedObjects.Where(o => o != _referenceObject))
{
obj.Angulo = referenceAngle;
}
}
public void JoinHorizontally()
{
if (_selectedObjects.Count <= 1) return;
var sortedObjects = _selectedObjects
.OrderBy(obj => GetObjectCenter(obj).X)
.ToList();
for (int i = 1; i < sortedObjects.Count; i++)
{
var previousObj = sortedObjects[i - 1];
var currentObj = sortedObjects[i];
var previousCenter = GetObjectCenter(previousObj);
var currentCenter = GetObjectCenter(currentObj);
var previousDims = GetEffectiveDimensions(previousObj);
float offset = previousDims.Width / 2;
float newX = (float)(previousCenter.X + offset);
float deltaX = (float)(newX - (currentCenter.X - GetEffectiveDimensions(currentObj).Width / 2));
currentObj.Left += deltaX;
}
}
public void JoinVertically()
{
if (_selectedObjects.Count <= 1) return;
var sortedObjects = _selectedObjects
.OrderBy(obj => GetObjectCenter(obj).Y)
.ToList();
for (int i = 1; i < sortedObjects.Count; i++)
{
var previousObj = sortedObjects[i - 1];
var currentObj = sortedObjects[i];
var previousCenter = GetObjectCenter(previousObj);
var currentCenter = GetObjectCenter(currentObj);
var previousDims = GetEffectiveDimensions(previousObj);
float offset = previousDims.Height / 2;
float newY = (float)(previousCenter.Y + offset);
float deltaY = (float)(newY - (currentCenter.Y - GetEffectiveDimensions(currentObj).Height / 2));
currentObj.Top += deltaY;
}
}
private Point GetObjectCenter(osBase obj)
{
double angleRad = obj.Angulo * Math.PI / 180.0;
float centerX = obj.Left + (obj.Ancho / 2);
float centerY = obj.Top + (obj.Alto / 2);
if (obj.Angulo != 0)
{
var rotatedX = obj.Left + (Math.Cos(angleRad) * obj.Ancho / 2 - Math.Sin(angleRad) * obj.Alto / 2);
var rotatedY = obj.Top + (Math.Sin(angleRad) * obj.Ancho / 2 + Math.Cos(angleRad) * obj.Alto / 2);
return new Point(rotatedX, rotatedY);
}
return new Point(centerX, centerY);
}
private bool IsObjectVertical(osBase obj)
{
double normalizedAngle = obj.Angulo % 180;
if (normalizedAngle < 0) normalizedAngle += 180;
return normalizedAngle >= 45 && normalizedAngle < 135;
}
private (float Width, float Height) GetEffectiveDimensions(osBase obj)
{
if (IsObjectVertical(obj))
{
return (obj.Alto, obj.Ancho);
}
return (obj.Ancho, obj.Alto);
}
private void SetEffectiveDimensions(osBase obj, float width, float height)
{
if (IsObjectVertical(obj))
{
obj.Alto = width;
obj.Ancho = height;
}
else
{
obj.Ancho = width;
obj.Alto = height;
}
}
}
}

1512
ObjectManipulationManager.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucCustomImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
xmlns:funcionesbase="clr-namespace:CtrEditor.FuncionesBase">
<UserControl.DataContext>
<vm:osCustomImage />
</UserControl.DataContext>
<UserControl.Resources>
<funcionesbase:BooleanToDoubleConverter x:Key="FlipConverter" TrueValue="-1" FalseValue="1"/>
</UserControl.Resources>
<Grid RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform
ScaleX="{Binding Horizontal_Flip, Converter={StaticResource FlipConverter}}"
ScaleY="{Binding Vertical_Flip, Converter={StaticResource FlipConverter}}"/>
<RotateTransform Angle="{Binding Angulo}"/>
</TransformGroup>
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Source="{Binding ImageSource_oculta}"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Stretch="Fill"
RenderTransformOrigin="0.5,0.5" />
</Grid>
</UserControl>

View File

@ -0,0 +1,197 @@
using CommunityToolkit.Mvvm.ComponentModel;
using LibS7Adv;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
using Newtonsoft.Json;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// A custom image control that can display an image from a file path
/// </summary>
public partial class osCustomImage : osBase, IosBase
{
public static string NombreClase()
{
return "Imagen Personalizada";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
private string _imagePath;
[Description("Ruta del archivo de imagen")]
[Category("Configuración")]
[property: Name("Ruta de Imagen")]
public string ImagePath
{
get => _imagePath;
set
{
try
{
if (SetProperty(ref _imagePath, value))
{
OnImagePathChanged(value);
OnPropertyChanged(nameof(ImageSource_oculta));
}
}
catch
{
// Si hay error al establecer la propiedad, simplemente ignorarlo
// y establecer la imagen por defecto
try
{
SetProperty(ref _imagePath, value);
ImageSource_oculta = ImageFromPath("/Icons/unselect.png");
OnPropertyChanged(nameof(ImageSource_oculta));
}
catch
{
// En caso de error total, establecer valores seguros
_imagePath = value;
ImageSource_oculta = null;
}
}
}
}
[ObservableProperty]
[property: Description("Voltear la imagen horizontalmente")]
[property: Category("Configuración")]
[property: Name("Voltear Horizontal")]
private bool horizontal_Flip;
[ObservableProperty]
[property: Description("Voltear la imagen verticalmente")]
[property: Category("Configuración")]
[property: Name("Voltear Vertical")]
private bool vertical_Flip;
[ObservableProperty]
[property: JsonIgnore]
[property: Category("Apariencia")]
[property: Description("Imagen visual del objeto")]
[property: Name("Imagen")]
public ImageSource imageSource_oculta;
private void OnImagePathChanged(string value)
{
try
{
if (!string.IsNullOrEmpty(value))
{
ImageSource_oculta = ImageFromPath(value);
}
else
{
// Si no hay path, usar la imagen por defecto
ImageSource_oculta = ImageFromPath("/Icons/unselect.png");
}
}
catch
{
// Si hay cualquier error, usar la imagen por defecto
try
{
ImageSource_oculta = ImageFromPath("/Icons/unselect.png");
}
catch
{
// Si incluso la imagen por defecto falla, establecer como null
ImageSource_oculta = null;
}
}
}
partial void OnHorizontal_FlipChanged(bool value)
{
// Property changed handler will trigger UI update through binding
}
partial void OnVertical_FlipChanged(bool value)
{
// Property changed handler will trigger UI update through binding
}
public osCustomImage()
{
Ancho = 0.30f;
Alto = 0.30f;
// Establecer la imagen por defecto al crear el objeto
ImageSource_oculta = ImageFromPath("/Icons/unselect.png");
}
public override void ucLoaded()
{
base.ucLoaded();
try
{
if (!string.IsNullOrEmpty(ImagePath))
{
ImageSource_oculta = ImageFromPath(ImagePath);
}
else
{
// Si no hay path al cargar, usar la imagen por defecto
ImageSource_oculta = ImageFromPath("/Icons/unselect.png");
}
}
catch
{
// Si hay cualquier error, usar la imagen por defecto
try
{
ImageSource_oculta = ImageFromPath("/Icons/unselect.png");
}
catch
{
// Si incluso la imagen por defecto falla, establecer como null
ImageSource_oculta = null;
}
}
}
}
public partial class ucCustomImage : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucCustomImage()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -0,0 +1,31 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucFramePlate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osFramePlate Color_Titulo="Black" Alto_Titulo="0.1" Color="WhiteSmoke" />
</UserControl.DataContext>
<Grid>
<Grid.RenderTransform>
<RotateTransform Angle="{Binding Angulo}"
CenterX="{Binding RenderTransformCenterX}"
CenterY="{Binding RenderTransformCenterY}" />
</Grid.RenderTransform>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Viewbox Grid.Row="0" Height="{Binding Alto_Titulo, Converter={StaticResource MeterToPixelConverter}}">
<Label Content="{Binding Titulo}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold"
Foreground="{Binding Color_Titulo, Converter={StaticResource ColorToBrushConverter}}" />
</Viewbox>
<Rectangle Grid.Row="1" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" Stroke="Blue"
StrokeThickness="0.2"
Visibility="{Binding ShowPlate, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</UserControl>

View File

@ -0,0 +1,490 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.FuncionesBase;
using DocumentFormat.OpenXml.Spreadsheet;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using Color = System.Windows.Media.Color;
using Colors = System.Windows.Media.Colors;
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// Interaction logic for ucFramePlate.xaml
/// </summary>
///
public partial class osFramePlate : osBase, IosBase
{
[JsonIgnore]
public float offsetY;
[JsonIgnore]
public float offsetX;
[JsonIgnore]
public float offsetAngulo;
public static string NombreClase()
{
return "Marco de Panel";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[ObservableProperty]
[property: Description("Índice de capa para objetos en el marco")]
[property: Category("Configuración")]
[property: Name("Índice Z del Marco")]
int zindex_FramePlate;
partial void OnZindex_FramePlateChanged(int value)
{
UpdateZIndex(value);
}
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color de fondo del marco")]
[property: Name("Color")]
Color color;
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color del texto del título")]
[property: Name("Color del Título")]
Color color_Titulo;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Texto del título del marco")]
[property: Name("Título")]
string titulo;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Alto del área del título")]
[property: Name("Alto del Título")]
public float alto_Titulo;
// Encoder X
[ObservableProperty]
[property: Description("This is a link to a Encoder for X.")]
[property: Category("Encoders:")]
[property: ItemsSource(typeof(osBaseItemsSource<osEncoderMotorLineal>))]
private string encoder_X;
[ObservableProperty]
[property: Description("K Pulses per meter for Moving")]
[property: Category("Encoders:")]
public float k_encoder_X;
[ObservableProperty]
[property: ReadOnly(true)]
[property: Description("Actual Position X")]
[property: Category("Encoders:")]
public float encoder_X_Position;
[ObservableProperty]
[property: Description("X in meter offset Left. Position when the encoder is 0")]
[property: Category("Encoders:")]
public float offset_encoder_X;
// Encoder Y
[ObservableProperty]
[property: Description("This is a link to a Encoder for Y.")]
[property: Category("Encoders:")]
[property: ItemsSource(typeof(osBaseItemsSource<osEncoderMotorLineal>))]
private string encoder_Y;
[ObservableProperty]
[property: Description("K Pulses per meter for Moving")]
[property: Category("Encoders:")]
public float k_encoder_Y;
[ObservableProperty]
[property: ReadOnly(true)]
[property: Description("Actual Position Y")]
[property: Category("Encoders:")]
public float encoder_Y_Position;
[ObservableProperty]
[property: Description("Y in meter offset Top. Position when the encoder is 0")]
[property: Category("Encoders:")]
public float offset_encoder_Y;
// Encoder Rotation
[ObservableProperty]
[property: Description("This is a link to a Encoder for Rotation.")]
[property: Category("Encoders:")]
[property: ItemsSource(typeof(osBaseItemsSource<osEncoderMotorLineal>))]
private string encoder_Rotation;
[ObservableProperty]
[property: Description("K Pulses per degree for Rotation")]
[property: Category("Encoders:")]
public float k_encoder_Rotation;
[ObservableProperty]
[property: ReadOnly(true)]
[property: Description("Actual Rotation Angle")]
[property: Category("Encoders:")]
public float encoder_Rotation_Angle;
[ObservableProperty]
[property: Description("Angle in degrees offset. Angle when the encoder is 0")]
[property: Category("Encoders:")]
public float offset_encoder_Rotation;
partial void OnK_encoder_YChanged(float value)
{
UpdatePosition();
}
partial void OnOffset_encoder_YChanged(float value)
{
UpdatePosition();
}
partial void OnK_encoder_XChanged(float value)
{
UpdatePosition();
}
partial void OnOffset_encoder_XChanged(float value)
{
UpdatePosition();
}
partial void OnK_encoder_RotationChanged(float value)
{
UpdatePosition();
}
partial void OnOffset_encoder_RotationChanged(float value)
{
UpdatePosition();
}
[JsonIgnore]
private osEncoderMotorLineal EncoderX;
[JsonIgnore]
private osEncoderMotorLineal EncoderY;
[JsonIgnore]
private osEncoderMotorLineal EncoderRotation;
[JsonIgnore]
private bool isUpdatingFromEncoderX = false;
[JsonIgnore]
private bool isUpdatingFromEncoderY = false;
[JsonIgnore]
private bool isUpdatingFromEncoderRotation = false;
partial void OnEncoder_XChanged(string value)
{
if (EncoderX != null)
EncoderX.PropertyChanged -= OnEncoderXPropertyChanged;
if (_mainViewModel != null && value != null && value.Length > 0)
{
EncoderX = (osEncoderMotorLineal)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osEncoderMotorLineal && s.Nombre == value), null);
if (EncoderX != null)
EncoderX.PropertyChanged += OnEncoderXPropertyChanged;
}
}
partial void OnEncoder_YChanged(string value)
{
if (EncoderY != null)
EncoderY.PropertyChanged -= OnEncoderYPropertyChanged;
if (_mainViewModel != null && value != null && value.Length > 0)
{
EncoderY = (osEncoderMotorLineal)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osEncoderMotorLineal && s.Nombre == value), null);
if (EncoderY != null)
EncoderY.PropertyChanged += OnEncoderYPropertyChanged;
}
}
partial void OnEncoder_RotationChanged(string value)
{
if (EncoderRotation != null)
EncoderRotation.PropertyChanged -= OnEncoderRotationPropertyChanged;
if (_mainViewModel != null && value != null && value.Length > 0)
{
EncoderRotation = (osEncoderMotorLineal)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osEncoderMotorLineal && s.Nombre == value), null);
if (EncoderRotation != null)
EncoderRotation.PropertyChanged += OnEncoderRotationPropertyChanged;
}
}
private void OnEncoderXPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!isUpdatingFromEncoderX)
{
isUpdatingFromEncoderX = true;
// Actualizamos el nombre si este fue modificado
if (e.PropertyName == nameof(osEncoderMotorLineal.Nombre))
Encoder_X = ((osEncoderMotorLineal)sender).Nombre;
if (e.PropertyName == nameof(osEncoderMotorLineal.Valor_Actual))
{
UpdatePosition();
}
isUpdatingFromEncoderX = false;
}
}
private void OnEncoderYPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!isUpdatingFromEncoderY)
{
isUpdatingFromEncoderY = true;
// Actualizamos el nombre si este fue modificado
if (e.PropertyName == nameof(osEncoderMotorLineal.Nombre))
Encoder_Y = ((osEncoderMotorLineal)sender).Nombre;
if (e.PropertyName == nameof(osEncoderMotorLineal.Valor_Actual))
{
UpdatePosition();
}
isUpdatingFromEncoderY = false;
}
}
private void OnEncoderRotationPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!isUpdatingFromEncoderRotation)
{
isUpdatingFromEncoderRotation = true;
// Actualizamos el nombre si este fue modificado
if (e.PropertyName == nameof(osEncoderMotorLineal.Nombre))
Encoder_Rotation = ((osEncoderMotorLineal)sender).Nombre;
if (e.PropertyName == nameof(osEncoderMotorLineal.Valor_Actual))
{
UpdatePosition();
}
isUpdatingFromEncoderRotation = false;
}
}
public void UpdatePosition()
{
// Update X position if encoder is available
if (EncoderX != null && K_encoder_X != 0)
{
Encoder_X_Position = EncoderX.Valor_Actual;
Left = (Encoder_X_Position / k_encoder_X) + offset_encoder_X;
}
// Update Y position if encoder is available
if (EncoderY != null && K_encoder_Y != 0)
{
Encoder_Y_Position = EncoderY.Valor_Actual;
Top = (Encoder_Y_Position / k_encoder_Y) + offset_encoder_Y;
}
// Update rotation angle if encoder is available
if (EncoderRotation != null && K_encoder_Rotation != 0)
{
Encoder_Rotation_Angle = EncoderRotation.Valor_Actual;
Angulo = (Encoder_Rotation_Angle / k_encoder_Rotation) + offset_encoder_Rotation;
}
}
public override void TopChanging(float oldValue, float newValue)
{
offsetY = newValue - oldValue;
}
public override void LeftChanging(float oldValue, float newValue)
{
offsetX = newValue - oldValue;
}
public override void AnguloChanging(float oldValue, float newValue)
{
offsetAngulo = newValue - oldValue;
}
[ObservableProperty]
[property: Description("Show/Hide the plate background")]
[property: Category("Appearance:")]
private bool showPlate;
[ObservableProperty]
[property: Description("Use horizontal center as pivot point for rotation")]
[property: Category("Pivot:")]
private bool pivotCenterX;
[ObservableProperty]
[property: Description("Use vertical center as pivot point for rotation")]
[property: Category("Pivot:")]
private bool pivotCenterY;
partial void OnPivotCenterXChanged(bool value)
{
// Notificar cambio de pivot para actualizar objetos conectados
OnPropertyChanged(nameof(PivotCenterX));
// Notificar cambios en las propiedades de pivot para el RenderTransform
OnPropertyChanged(nameof(RenderTransformCenterX));
OnPropertyChanged(nameof(RenderTransformCenterY));
}
partial void OnPivotCenterYChanged(bool value)
{
// Notificar cambio de pivot para actualizar objetos conectados
OnPropertyChanged(nameof(PivotCenterY));
// Notificar cambios en las propiedades de pivot para el RenderTransform
OnPropertyChanged(nameof(RenderTransformCenterX));
OnPropertyChanged(nameof(RenderTransformCenterY));
}
public osFramePlate()
{
Ancho = 0.5f;
Alto = 0.5f;
Alto_Titulo = 0.2f;
Color = Colors.WhiteSmoke;
Titulo = "Frame";
Zindex_FramePlate = 0;
ShowPlate = true; // Default value
}
public override void AnchoChanged(float value)
{
base.AnchoChanged(value);
OnPropertyChanged(nameof(RenderTransformCenterX));
}
public override void AltoChanged(float value)
{
base.AltoChanged(value);
OnPropertyChanged(nameof(RenderTransformCenterY));
}
partial void OnAlto_TituloChanged(float value)
{
OnPropertyChanged(nameof(RenderTransformCenterY));
}
public override void ucLoaded()
{
base.ucLoaded();
// El UserControl se ha cargado
OnEncoder_XChanged(Encoder_X);
OnEncoder_YChanged(Encoder_Y);
OnEncoder_RotationChanged(Encoder_Rotation);
UpdateZIndex(Zindex_FramePlate);
}
public override void ucUnLoaded()
{
base.ucUnLoaded();
// El UserControl se esta eliminando
// eliminar el objeto de simulacion
}
/// <summary>
/// Calcula el punto de pivot basado en los checkboxes PivotCenterX y PivotCenterY
/// </summary>
/// <returns>Punto de pivot en coordenadas absolutas</returns>
public (float X, float Y) GetPivotPoint()
{
float pivotX = Left + (PivotCenterX ? Ancho / 2 : 0);
float pivotY = Top + (PivotCenterY ? Alto / 2 : 0);
return (pivotX, pivotY);
}
/// <summary>
/// Centro X para el RenderTransform en píxeles del control
/// </summary>
[JsonIgnore]
public double RenderTransformCenterX
{
get
{
if (PivotCenterX)
{
// Centro horizontal del rectángulo principal
return PixelToMeter.Instance.calc.MetersToPixels(Ancho) / 2.0;
}
else
{
// Left del rectángulo principal (0)
return 0.0;
}
}
}
/// <summary>
/// Centro Y para el RenderTransform en píxeles del control
/// </summary>
[JsonIgnore]
public double RenderTransformCenterY
{
get
{
// Altura del título y del rectángulo principal
double titleHeight = PixelToMeter.Instance.calc.MetersToPixels(Alto_Titulo);
double frameHeight = PixelToMeter.Instance.calc.MetersToPixels(Alto);
if (PivotCenterY)
{
// Centro vertical de todo el control (título + rectángulo)
return (titleHeight + frameHeight) / 2.0;
}
else
{
// Top absoluto del control (0)
return 0.0;
}
}
}
}
public partial class ucFramePlate : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucFramePlate()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Decorativos;
}
}
}

View File

@ -0,0 +1,29 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucTextPlate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osTextPlate Color_Titulo="Black" Alto_Titulo="0.1"/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Viewbox Grid.Row="0" Height="{Binding Alto_Titulo, Converter={StaticResource MeterToPixelConverter}}" >
<Label Content="{Binding Titulo}"
VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold"
Foreground="{Binding Color_Titulo, Converter={StaticResource ColorToBrushConverter}}"/>
</Viewbox>
<Rectangle Grid.Row="1" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" />
</Grid>
</UserControl>

View File

@ -0,0 +1,119 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.FuncionesBase;
using Newtonsoft.Json;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// Interaction logic for ucTextPlate.xaml
/// </summary>
///
public partial class osTextPlate : osBase, IosBase
{
[JsonIgnore]
public float offsetY;
[JsonIgnore]
public float offsetX;
public static string NombreClase()
{
return "Placa de Texto";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color de fondo de la placa")]
[property: Name("Color")]
Color color;
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color del texto del título")]
[property: Name("Color Título")]
Color color_Titulo;
[ObservableProperty]
[property: Category("Identificación")]
[property: Description("Texto del título")]
[property: Name("Título")]
string titulo;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Alto del área del título")]
[property: Name("Alto Título")]
public float alto_Titulo;
public override void TopChanging(float oldValue, float newValue)
{
offsetY = newValue - oldValue;
}
public override void LeftChanging(float oldValue, float newValue)
{
offsetX = newValue - oldValue;
}
public osTextPlate()
{
Ancho = 0.5f;
Alto = 0.5f;
Alto_Titulo = 0.2f;
Color = Colors.Gray;
Titulo = "Titulo";
}
public override void ucUnLoaded()
{
// El UserControl se esta eliminando
// eliminar el objeto de simulacion
}
}
public partial class ucTextPlate : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucTextPlate()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Decorativos;
}
}
}

View File

@ -1,12 +1,15 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucBotella"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:convert="clr-namespace:CtrEditor.Convertidores">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
</UserControl.Resources>
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
>
<UserControl.DataContext>
<vm:osBotella/>
</UserControl.DataContext>
<Ellipse Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
Stroke="red" Fill="Gray"
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"/>
Stroke="{Binding ColorButton_oculto}" Fill="Gray"
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" StrokeThickness="0.5"/>
</UserControl>

View File

@ -1,11 +1,14 @@
using System.Windows;
using System.Windows.Controls;
//using using Microsoft.Xna.Framework;
using CtrEditor.Convertidores;
using CtrEditor.Siemens;
using LibS7Adv;
using CtrEditor.Simulacion;
using CommunityToolkit.Mvvm.ComponentModel;
using nkast.Aether.Physics2D.Common;
using System.Windows.Media;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
@ -25,21 +28,77 @@ namespace CtrEditor.ObjetosSim
return "Botella";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
public override void OnMove(float LeftPixels, float TopPixels)
{
UpdateAfterMove();
}
public override void OnResize(float Delta_Width, float Delta_Height)
{
Diametro += Delta_Width;
}
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color visual de la botella")]
[property: Name("Color")]
private Brush colorButton_oculto;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Diámetro de la botella en metros")]
[property: Name("Diámetro")]
private float diametro;
partial void OnDiametroChanged(float value)
{
// Asegurar que el diámetro sea al menos 0.01m para evitar errores en la simulación
if (value < 0.01f)
{
diametro = 0.01f;
return;
}
SimGeometria?.SetDiameter(Diametro);
}
[ObservableProperty]
[property: Category("Información")]
[property: Description("Velocidad actual desde la simulación")]
[property: Name("Velocidad de Simulación")]
private string velocidad_desde_simulacion;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Inercia actual desde la simulación")]
[property: Name("Inercia de Simulación")]
private float inercia_desde_simulacion;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Conservar objeto cuando sale de transporte")]
[property: Name("Conservar Fuera de Transporte")]
private bool preserve_Outside_Transport;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Porcentaje de tracción con transporte")]
[property: Name("Porcentaje de Tracción")]
private float porcentaje_Traccion;
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Masa del objeto en kg")]
[property: Name("Masa")]
private float mass;
partial void OnMassChanged(float value)
{
@ -48,23 +107,22 @@ namespace CtrEditor.ObjetosSim
public Vector2 GetCentro()
{
return new Vector2 (Left+Diametro/2,Top+Diametro/2);
return new Vector2(Left + Diametro / 2, Top + Diametro / 2);
}
public void SetCentro(float x, float y)
{ Left = x; Top = y; }
public void SetCentro(Vector2 centro)
{
{
Left = centro.X - Diametro / 2;
Top = centro.Y - Diametro / 2;
Top = centro.Y - Diametro / 2;
}
private void ActualizarGeometrias()
{
if (SimGeometria != null)
if (SimGeometria != null && !RemoverDesdeSimulacion)
{
SimGeometria.SetDiameter(Diametro);
SimGeometria.SetPosition(GetCentro());
}
}
@ -73,39 +131,61 @@ namespace CtrEditor.ObjetosSim
{
Diametro = 0.10f;
Mass = 1;
ColorButton_oculto = Brushes.Gray;
Preserve_Outside_Transport = true;
}
public void UpdateAfterMove()
{
if (!RemoverDesdeSimulacion)
{
ActualizarGeometrias();
SimGeometria?.SetDiameter(Diametro);
}
}
public override void UpdateGeometryStart()
{
// Se llama antes de la simulacion
ActualizarGeometrias();
SimGeometria?.SetDiameter(Diametro);
}
public override void SimulationStop()
{
// Se llama al detener la simulacion. Util para detener Storyboards
}
public override void UpdateGeometryStep()
{
// Se llama antes de la simulacion
ActualizarGeometrias();
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds) { }
public override void UpdateControl(int elapsedMilliseconds)
{
SetCentro(SimGeometria.Center);
if (SimGeometria.isRestricted)
ColorButton_oculto = Brushes.Yellow;
else
{
if (SimGeometria.IsOnAnyTransport())
ColorButton_oculto = Brushes.Red;
else
ColorButton_oculto = Brushes.Gray;
}
if (SimGeometria.Descartar) // Ha sido marcada para remover
// Ha sido marcada para remover
if (SimGeometria.Descartar)
RemoverDesdeSimulacion = true;
// Eliminar la botella si esta fuera de un transporte
if (!Preserve_Outside_Transport && !SimGeometria.IsOnAnyTransport())
RemoverDesdeSimulacion = true;
Velocidad_desde_simulacion = SimGeometria.Body.LinearVelocity.ToString();
Inercia_desde_simulacion = SimGeometria.Body.Inertia;
Porcentaje_Traccion = SimGeometria.OverlapPercentage;
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
}
public override void ucUnLoaded()
@ -120,6 +200,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucBotella : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucBotella()
{
@ -136,20 +217,10 @@ namespace CtrEditor.ObjetosSim
Datos?.ucUnLoaded();
}
public void Resize(float width, float height) { }
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle) { }
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 10;
return ZIndexEnum.Dinamicos;
}
}
}

View File

@ -0,0 +1,14 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucBotellaCuello"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
<UserControl.DataContext>
<vm:osBotellaCuello/>
</UserControl.DataContext>
<Ellipse Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
Stroke="{Binding ColorButton_oculto}" Fill="Gray"
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" StrokeThickness="0.5"/>
</UserControl>

View File

@ -0,0 +1,200 @@
using System.Windows;
using System.Windows.Controls;
//using using Microsoft.Xna.Framework;
using LibS7Adv;
using CtrEditor.Simulacion;
using CommunityToolkit.Mvvm.ComponentModel;
using nkast.Aether.Physics2D.Common;
using System.Windows.Media;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// Interaction logic for ucBotellaCuelloCuello.xaml
/// </summary>
public partial class osBotellaCuello : osBase, IosBase
{
private simBotella SimGeometria;
// Otros datos y métodos relevantes para la simulación
public static string NombreClase()
{
return "Botella con Cuello";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
public override void TopChanged(float value)
{
UpdateAfterMove();
}
public override void LeftChanged(float value)
{
UpdateAfterMove();
}
public override void OnResize(float Delta_Width, float Delta_Height)
{
Diametro += Delta_Width;
}
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color del botón visual")]
[property: Name("Color")]
private Brush colorButton_oculto;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Diámetro de la botella")]
[property: Name("Diámetro")]
private float diametro;
partial void OnDiametroChanged(float value)
{
SimGeometria?.SetDiameter(Diametro);
}
[ObservableProperty]
[property: Category("Información")]
[property: Description("Velocidad desde simulación")]
[property: Name("Velocidad Simulación")]
private string velocidad_desde_simulacion;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Inercia desde simulación")]
[property: Name("Inercia Simulación")]
private float inercia_desde_simulacion;
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Masa de la botella")]
[property: Name("Masa")]
private float mass;
partial void OnMassChanged(float value)
{
SimGeometria?.SetMass(value);
}
public Vector2 GetCentro()
{
return new Vector2(Left + Diametro / 2, Top + Diametro / 2);
}
public void SetCentro(float x, float y)
{ Left = x; Top = y; }
public void SetCentro(Vector2 centro)
{
Left = centro.X - Diametro / 2;
Top = centro.Y - Diametro / 2;
}
private void ActualizarGeometrias()
{
if (SimGeometria != null)
{
SimGeometria.SetPosition(GetCentro());
}
}
public osBotellaCuello()
{
Diametro = 0.10f;
Mass = 1;
ColorButton_oculto = Brushes.Gray;
}
public void UpdateAfterMove()
{
ActualizarGeometrias();
SimGeometria?.SetDiameter(Diametro);
}
public override void UpdateGeometryStart()
{
// Se llama antes de la simulacion
ActualizarGeometrias();
SimGeometria?.SetDiameter(Diametro);
}
public override void UpdateGeometryStep()
{
// Se llama antes de la simulacion
ActualizarGeometrias();
}
public override void UpdateControl(int elapsedMilliseconds)
{
SetCentro(SimGeometria.Center);
if (SimGeometria.isRestricted)
ColorButton_oculto = Brushes.Yellow;
else
{
if (SimGeometria.isOnTransports > 0)
ColorButton_oculto = Brushes.Red;
else
ColorButton_oculto = Brushes.Gray;
}
if (SimGeometria.Descartar) // Ha sido marcada para remover
RemoverDesdeSimulacion = true;
Velocidad_desde_simulacion = SimGeometria.Body.LinearVelocity.ToString();
Inercia_desde_simulacion = SimGeometria.Body.Inertia;
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
}
public override void ucUnLoaded()
{
// El UserControl se esta eliminando
// eliminar el objeto de simulacion
simulationManager.Remove(SimGeometria);
}
}
public partial class ucBotellaCuello : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucBotellaCuello()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Dinamicos;
}
}
}

View File

@ -0,0 +1,24 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucBottGenerator"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
<UserControl.DataContext>
<vm:osFiller/>
</UserControl.DataContext>
<Grid>
<Image Source="/imagenes/gear.png"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Stretch="Uniform">
<Image.RenderTransform>
<RotateTransform Angle="{Binding Angulo}" />
</Image.RenderTransform>
</Image>
</Grid>
</UserControl>

View File

@ -0,0 +1,249 @@
using LibS7Adv;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Diagnostics;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// Interaction logic for ucBottGenerator.xaml
/// </summary>
public partial class osBottGenerator : osBase, IosBase
{
TimerTON_TOFF _TON_TOFF = new TimerTON_TOFF();
private float TiempoRestante;
public static string NombreClase()
{
return "Generador de Botellas";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre { get => nombre; set => SetProperty(ref nombre, value); }
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Desplazamiento horizontal del punto de salida")]
[property: Name("Offset Horizontal")]
private float offsetLeftSalida;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Desplazamiento vertical del punto de salida")]
[property: Name("Offset Vertical")]
private float offsetTopSalida;
[ObservableProperty]
[property: Description("Las botellas se destruirán si caen fuera del transporte")]
[property: Category("Configuración")]
[property: Name("Conservar Fuera de Transporte")]
private bool preserve_Outside_Transport;
[ObservableProperty]
[property: Description("Tag PLC para habilitar funcionamiento. 1 => siempre activo")]
[property: Category("Enlace PLC")]
[property: Name("Tag Consenso")]
private string tag_consenso;
[ObservableProperty]
[property: Description("Estado de consenso para funcionamiento")]
[property: Category("Simulación")]
[property: Name("Consenso")]
private bool consenso;
partial void OnConsensoChanged(bool value)
{
_TON_TOFF.Senal = value;
}
[ObservableProperty]
[property: Description("Consenso normalmente cerrado")]
[property: Category("Configuración")]
[property: Name("Consenso NC")]
private bool consenso_NC;
[ObservableProperty]
[property: Description("Habilitar filtro de consenso")]
[property: Category("Configuración")]
[property: Name("Filtro Habilitado")]
private bool consenso_Filtrado;
[ObservableProperty]
[property: Description("Tiempo de activación del filtro en segundos")]
[property: Category("Configuración")]
[property: Name("Tiempo ON (s)")]
float filtro_consenso_ON_s;
[ObservableProperty]
[property: Description("Tiempo de desactivación del filtro en segundos")]
[property: Category("Configuración")]
[property: Name("Tiempo OFF (s)")]
float filtro_consenso_OFF_s;
[ObservableProperty]
[property: Description("Señal de salida del filtro")]
[property: Category("Información")]
[property: Name("Salida Filtro")]
bool filter_Output;
[ObservableProperty]
[property: Description("Cantidad de botellas generadas por hora")]
[property: Category("Configuración")]
[property: Name("Botellas por Hora")]
private float botellas_hora;
partial void OnBotellas_horaChanged(float value)
{
Botellas_segundo = value / 3600;
}
[ObservableProperty]
[property: Description("Cantidad de botellas generadas por segundo")]
[property: Category("Configuración")]
[property: Name("Botellas por Segundo")]
private float botellas_segundo;
partial void OnBotellas_segundoChanged(float value)
{
Botellas_hora = value * 3600;
}
[ObservableProperty]
[property: Description("Velocidad actual como porcentaje")]
[property: Category("Simulación")]
[property: Name("Velocidad Actual (%)")]
private float velocidad_actual_percentual;
[ObservableProperty]
[property: Description("Diámetro de las botellas generadas")]
[property: Category("Configuración")]
[property: Name("Diámetro Botella")]
private float diametro_botella;
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (Consenso_NC)
Consenso = !LeerBitTag(Tag_consenso);
else
Consenso = LeerBitTag(Tag_consenso);
}
private bool HayEspacioParaNuevaBotella(float X, float Y)
{
float radioMinimo = Diametro_botella / 4; // Distancia mínima entre centros
float radioMinimoCuadrado = radioMinimo * radioMinimo;
// Buscar todas las botellas cercanas
foreach (var obj in _mainViewModel.ObjetosSimulables)
{
if (obj is osBotella)
{
float distanciaCuadrada = (float)(Math.Pow(obj.Left - X, 2) + Math.Pow(obj.Top - Y, 2));
if (distanciaCuadrada < radioMinimoCuadrado)
{
return false; // Hay una botella demasiado cerca
}
}
}
return true; // No hay botellas cercanas
}
public override void UpdateControl(int elapsedMilliseconds)
{
bool habilitado;
_TON_TOFF.Tiempo_ON_s = Filtro_consenso_ON_s;
_TON_TOFF.Tiempo_OFF_s = Filtro_consenso_OFF_s;
if (Consenso_Filtrado)
habilitado = _TON_TOFF.SenalFiltrada();
else
habilitado = Consenso;
Filter_Output = habilitado;
if (habilitado && Velocidad_actual_percentual > 0)
{
if (PrimeraActualizacion)
{
TiempoEntreBotellas = 3600 / (Botellas_hora * (Velocidad_actual_percentual / 100.0f));
TiempoRestante = TiempoEntreBotellas;
PrimeraActualizacion = false;
}
TiempoRestante -= elapsedMilliseconds / 1000.0f;
if (TiempoRestante <= 0)
{
// Validar que el diámetro sea al menos 0.01m para evitar errores en la simulación
if (Diametro_botella < 0.01f)
{
Diametro_botella = 0.01f;
}
var X = Left + OffsetLeftSalida;
var Y = Top + OffsetTopSalida;
if (HayEspacioParaNuevaBotella(X, Y))
{
var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
((osBotella)nuevaBotella).Diametro = Diametro_botella;
((osBotella)nuevaBotella).Preserve_Outside_Transport = Preserve_Outside_Transport;
nuevaBotella.AutoCreated = true;
// Recalcular el tiempo entre botellas por si cambió la velocidad
TiempoEntreBotellas = 3600 / (Botellas_hora * (Velocidad_actual_percentual / 100.0f));
TiempoRestante = TiempoEntreBotellas;
}
}
}
else
{
PrimeraActualizacion = true;
}
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
}
private float TiempoEntreBotellas; // Nuevo: almacena el intervalo calculado
private bool PrimeraActualizacion = true; // Nuevo: flag para la primera actualización
public override void SimulationStop()
{
PrimeraActualizacion = true; // Reset del flag al detener
base.SimulationStop();
}
}
public partial class ucBottGenerator : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucBottGenerator()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State)
{
}
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Generadores;
}
}
}

View File

@ -3,14 +3,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d"
xmlns:convert="clr-namespace:CtrEditor.Convertidores"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
</UserControl.Resources>
<UserControl.DataContext>
<vm:osFiller/>
@ -20,7 +15,7 @@
<Image Source="/imagenes/filler.png"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Stretch="Uniform">
Stretch="Fill">
<Image.RenderTransform>
<RotateTransform Angle="{Binding Angulo}" />
</Image.RenderTransform>

View File

@ -1,8 +1,11 @@
using CtrEditor.Convertidores;
using CtrEditor.Siemens;

using LibS7Adv;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Diagnostics;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
@ -11,6 +14,7 @@ namespace CtrEditor.ObjetosSim
/// </summary>
public partial class osFiller : osBase, IosBase
{
TimerTON_TOFF _TON_TOFF = new TimerTON_TOFF();
// Otros datos y métodos relevantes para la simulación
private float TiempoRestante;
@ -18,9 +22,13 @@ namespace CtrEditor.ObjetosSim
public static string NombreClase()
{
return "Filler";
return "Llenadora";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
@ -28,12 +36,55 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Offset horizontal para salida de botellas")]
[property: Name("Offset Left Salida")]
private float offsetLeftSalida;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Offset vertical para salida de botellas")]
[property: Name("Offset Top Salida")]
private float offsetTopSalida;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag PLC para consenso")]
[property: Name("Tag Consenso")]
private string tag_consenso;
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Estado del consenso")]
[property: Name("Consenso")]
private bool consenso;
partial void OnConsensoChanged(bool value)
{
_TON_TOFF.Senal = value;
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Consenso normalmente cerrado")]
[property: Name("Consenso NC")]
private bool consenso_NC;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Aplicar filtrado al consenso")]
[property: Name("Consenso Filtrado")]
private bool consenso_Filtrado;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Tiempo de filtro en segundos")]
[property: Name("Filtro Consenso (s)")]
float filtro_consenso_s;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Producción en botellas por hora")]
[property: Name("Botellas/Hora")]
private float botellas_hora;
partial void OnBotellas_horaChanged(float value)
@ -42,6 +93,9 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
[property: Category("Información")]
[property: Description("Producción en botellas por segundo")]
[property: Name("Botellas/Segundo")]
private float botellas_segundo;
partial void OnBotellas_segundoChanged(float value)
@ -50,17 +104,16 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Velocidad actual en porcentaje")]
[property: Name("Velocidad (%)")]
private float velocidad_actual_percentual;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Diámetro de las botellas generadas")]
[property: Name("Diámetro Botella")]
private float diametro_botella;
[ObservableProperty]
private string tag_consenso;
[ObservableProperty]
private float ancho;
[ObservableProperty]
private float alto;
[ObservableProperty]
private float angulo;
public osFiller()
{
@ -70,16 +123,29 @@ namespace CtrEditor.ObjetosSim
Velocidad_actual_percentual = 0;
Diametro_botella = 0.1f;
Botellas_hora = 10000;
Filtro_consenso_s = 1;
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds)
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
Consenso = LeerBitTag(Tag_consenso);
if (Consenso_NC)
Consenso = !LeerBitTag(Tag_consenso);
else
Consenso = LeerBitTag(Tag_consenso);
}
public override void UpdateControl(int elapsedMilliseconds)
{
if (Consenso && Velocidad_actual_percentual > 0)
bool habilitado;
_TON_TOFF.Tiempo_ON_s = _TON_TOFF.Tiempo_OFF_s = Filtro_consenso_s;
if (Consenso_Filtrado)
habilitado = _TON_TOFF.SenalFiltrada();
else
habilitado = Consenso;
if (habilitado && Velocidad_actual_percentual > 0)
{
TiempoRestante -= elapsedMilliseconds / 1000.0f;
if (TiempoRestante <= 0)
@ -108,10 +174,10 @@ namespace CtrEditor.ObjetosSim
if (distancia > distanciaMinima)
{
var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
((osBotella)nuevaBotella).Diametro = Diametro_botella;
osBotella nuevaBotella = (osBotella)_mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
nuevaBotella.Diametro = Diametro_botella;
nuevaBotella.AutoCreated = true;
UltimaBotella = (osBotella)nuevaBotella;
UltimaBotella = nuevaBotella;
}
}
}
@ -126,7 +192,7 @@ namespace CtrEditor.ObjetosSim
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
}
}
@ -134,6 +200,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucFiller : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucFiller()
{
@ -153,28 +220,14 @@ namespace CtrEditor.ObjetosSim
{
if (Datos is osFiller datos)
{
datos.Ancho = PixelToMeter.Instance.calc.PixelsToMeters(width);
datos.Alto = PixelToMeter.Instance.calc.PixelsToMeters(width);
datos.Ancho += PixelToMeter.Instance.calc.PixelsToMeters(width);
datos.Alto += PixelToMeter.Instance.calc.PixelsToMeters(height);
}
}
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle)
{
if (Datos != null)
if (Datos is osFiller datos)
datos.Angulo = Angle;
}
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 10;
return ZIndexEnum.Generadores;
}
}

View File

@ -3,16 +3,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
xmlns:convert="clr-namespace:CtrEditor.Convertidores">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
<convert:LevelToHeightMultiConverter x:Key="LevelToHeightMultiConverter"/>
<convert:WidthPercentageConverter x:Key="WidthPercentageConverter"/>
</UserControl.Resources>
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osTanque Alto="1" Ancho="1" Angulo="-4" />
@ -21,8 +13,8 @@
<Grid>
<Image x:Name="TankImage"
Source="/imagenes/tank.png"
Width="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Stretch="Uniform">
</Image>
<Slider

View File

@ -1,8 +1,9 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.Convertidores;
using CtrEditor.Siemens;
using LibS7Adv;
using System.Windows;
using System.Windows.Controls;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
@ -18,6 +19,10 @@ namespace CtrEditor.ObjetosSim
return "Tanque";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
@ -25,33 +30,69 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Capacidad total del tanque en litros")]
[property: Name("Capacidad (L)")]
public float capacidad_Litros;
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Estado de apertura del ingreso")]
[property: Name("Ingreso Abierto")]
public bool ingreso_Abierto;
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Estado de apertura de la salida")]
[property: Name("Salida Abierta")]
public bool salida_Abierta;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag PLC para nivel del tanque")]
[property: Name("Tag Nivel")]
public string tagNivel_Word;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag PLC para control de ingreso")]
[property: Name("Tag Ingreso")]
public string tagIngresoAbierto_Bool;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag PLC para control de salida")]
[property: Name("Tag Salida")]
public string tagSalidaAbierta_Bool;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad de llenado en L/min")]
[property: Name("Velocidad Ingreso")]
public float velocidad_Ingreso;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad de vaciado en L/min")]
[property: Name("Velocidad Salida")]
public float velocidad_Salida;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Valor mínimo escalado para salida PLC")]
[property: Name("Mínimo Escalado")]
public float min_OUT_Scaled;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Valor máximo escalado para salida PLC")]
[property: Name("Máximo Escalado")]
public float max_OUT_Scaled;
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Nivel actual del tanque en porcentaje")]
[property: Name("Nivel (%)")]
public float level;
[ObservableProperty]
public float ancho;
[ObservableProperty]
public float alto;
[ObservableProperty]
public float angulo;
public osTanque()
{
@ -61,7 +102,7 @@ namespace CtrEditor.ObjetosSim
Min_OUT_Scaled = 0;
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds)
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
EscribirWordTagScaled(TagNivel_Word, Level, 0, 100, Min_OUT_Scaled, Max_OUT_Scaled);
Ingreso_Abierto = LeerBitTag(TagIngresoAbierto_Bool);
@ -87,7 +128,7 @@ namespace CtrEditor.ObjetosSim
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
}
}
@ -95,6 +136,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucTanque : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucTanque()
{
@ -110,32 +152,10 @@ namespace CtrEditor.ObjetosSim
{
Datos?.ucUnLoaded();
}
public void Resize(float width, float height)
{
if (Datos is osTanque datos)
{
datos.Ancho = PixelToMeter.Instance.calc.PixelsToMeters(width);
datos.Alto = PixelToMeter.Instance.calc.PixelsToMeters(width);
}
}
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle)
{
if (Datos != null)
if (Datos is osTanque datos)
datos.Angulo = Angle;
}
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 10;
return ZIndexEnum.Generadores;
}
}
}

View File

@ -4,11 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
xmlns:convert="clr-namespace:CtrEditor.Convertidores">
mc:Ignorable="d">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
<Storyboard x:Key="PulsingStoryboard" RepeatBehavior="Forever">
<DoubleAnimation
Storyboard.TargetName="AnimatedEllipse"

View File

@ -1,11 +1,13 @@
using CtrEditor.Convertidores;
using CtrEditor.Siemens;

using LibS7Adv;
using CtrEditor.Simulacion;
using System.Windows;
using System.Windows.Controls;
using nkast.Aether.Physics2D.Common;
using System.Windows.Media.Animation;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
/// <summary>
@ -21,6 +23,10 @@ namespace CtrEditor.ObjetosSim
return "Descarte";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
@ -28,6 +34,9 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Diámetro del área de descarte en metros")]
[property: Name("Diámetro")]
private float diametro;
partial void OnDiametroChanged(float value)
@ -82,7 +91,7 @@ namespace CtrEditor.ObjetosSim
public override void UpdateGeometryStep()
{
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds)
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
}
@ -94,7 +103,7 @@ namespace CtrEditor.ObjetosSim
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
SimGeometria = simulationManager.AddDescarte(Diametro, GetCentro());
}
public override void ucUnLoaded()
@ -109,6 +118,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucDescarte : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucDescarte()
{
@ -129,28 +139,10 @@ namespace CtrEditor.ObjetosSim
{
Datos?.ucUnLoaded();
}
public void Resize(float width, float height)
{
if (Datos is osDescarte datos)
{
datos.Diametro = PixelToMeter.Instance.calc.PixelsToMeters(width);
}
}
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle)
{
}
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 10;
return ZIndexEnum.Descarte;
}
}

View File

@ -4,12 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
xmlns:convert="clr-namespace:CtrEditor.Convertidores">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
</UserControl.Resources>
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
<UserControl.DataContext>
<vm:osGuia/>

View File

@ -1,9 +1,10 @@
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.Convertidores;
using CtrEditor.Siemens;
using LibS7Adv;
using CtrEditor.Simulacion;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
@ -16,9 +17,13 @@ namespace CtrEditor.ObjetosSim
public static string NombreClase()
{
return "Guia";
return "Guía";
}
private string nombre = "Guia";
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
@ -26,20 +31,22 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
public float ancho;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Alto de la guía en metros")]
[property: Name("Alto de la Guía")]
public float altoGuia;
[ObservableProperty]
public float angulo;
private void ActualizarGeometrias()
{
if (_visualRepresentation is ucGuia uc)
UpdateOrCreateLine(SimGeometria, uc.Guia);
}
public override void OnMoveResizeRotate()
{
ActualizarGeometrias();
}
public osGuia()
{
Ancho = 1;
@ -57,12 +64,12 @@ namespace CtrEditor.ObjetosSim
public override void UpdateGeometryStep()
{
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds) { }
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) { }
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
if (_visualRepresentation is ucGuia uc)
SimGeometria = AddLine(simulationManager, uc.Guia);
@ -80,6 +87,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucGuia : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucGuia()
{
@ -95,29 +103,10 @@ namespace CtrEditor.ObjetosSim
{
Datos?.ucUnLoaded();
}
public void Resize(float width, float height)
{
if (Datos is osGuia datos)
datos.Ancho = PixelToMeter.Instance.calc.PixelsToMeters(width);
}
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle)
{
if (Datos != null)
if (Datos is osGuia datos)
datos.Angulo = Angle;
}
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 1;
return ZIndexEnum.Guias;
}
}

View File

@ -3,13 +3,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:localuc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
xmlns:convert="clr-namespace:CtrEditor.Convertidores"
xmlns:localuc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
</UserControl.Resources>
<UserControl.DataContext>
<vm:osTransporteCurva />

View File

@ -1,10 +1,12 @@
using System.Windows;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.Convertidores;
using CtrEditor.Siemens;
using LibS7Adv;
using CtrEditor.Simulacion;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CtrEditor.FuncionesBase;
using System.Text.Json.Serialization;
namespace CtrEditor.ObjetosSim
{
@ -13,21 +15,21 @@ namespace CtrEditor.ObjetosSim
/// </summary>
public partial class osTransporteCurva : osBase, IosBase
{
private float frictionCoefficient;
private float velMax50hz; // en metros por minuto
private float tiempoRampa;
private bool esMarcha;
private float _velocidadActual;
private osBase _osMotor = null;
private osBase Motor = null;
private simCurve Simulation_TransporteCurva;
private float _velocidadActual;
public static string NombreClase()
{
return "Transporte Curva 90";
return "Transporte Curva 90°";
}
private string nombre = "Transporte Curva 90";
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
@ -35,15 +37,110 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
private float radioExterno;
[ObservableProperty]
private float radioInterno;
[ObservableProperty]
private string motor;
[property: Category("Simulación")]
[property: Description("Velocidad actual del transporte en m/s")]
[property: Name("Velocidad Actual")]
public float velocidadActual;
partial void OnVelocidadActualChanged(float value)
{
SetSpeed();
}
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(AnguloFinal))]
public float angulo;
[property: Category("Configuración")]
[property: Description("Invierte el sentido de movimiento del transporte")]
[property: Name("Invertir Dirección")]
bool invertirDireccion;
partial void OnInvertirDireccionChanged(bool value)
{
SetSpeed();
if (_visualRepresentation is ucTransporteCurva uc)
{
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
}
void SetSpeed()
{
if (InvertirDireccion)
Simulation_TransporteCurva?.SetSpeed(-VelocidadActual);
else
Simulation_TransporteCurva?.SetSpeed(VelocidadActual);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Radio exterior de la curva en metros")]
[property: Name("Radio Exterior")]
private float radioExterno;
partial void OnRadioExternoChanged(float value)
{
// Update ancho and alto based on radioExterno
Ancho = value * 2;
Alto = value * 2;
// Ensure radioInterno maintains proper proportion if needed
if (RadioInterno >= value)
{
RadioInterno = value * 0.75f; // Default proportion
}
ActualizarGeometrias();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Radio interior de la curva en metros")]
[property: Name("Radio Interior")]
private float radioInterno;
[ObservableProperty]
[property: Description("Tag de bit para activar enlace con motor")]
[property: Category("Enlace PLC")]
[property: Name("Tag Activación Motor")]
string tag_ReleActivatedMotor;
[ObservableProperty]
[property: Description("Seleccionar motor para enlazar")]
[property: Category("Enlace PLC")]
[property: Name("Motor Enlazado")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_Motor;
[JsonIgnore]
private PropertyChangedEventHandler motorPropertyChangedHandler;
partial void OnId_MotorChanged(string value)
{
if (Motor != null && motorPropertyChangedHandler != null)
Motor.PropertyChanged -= motorPropertyChangedHandler;
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
Motor = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => s is osVMmotorSim motor && motor.Nombre == value);
if (Motor != null)
{
motorPropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_Motor = ((osVMmotorSim)sender).Nombre;
}
};
Motor.PropertyChanged += motorPropertyChangedHandler;
}
}
}
public override void AnguloChanged(float value)
{
OnPropertyChanged(nameof(AnguloFinal));
}
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(AnguloFinal))]
@ -52,39 +149,78 @@ namespace CtrEditor.ObjetosSim
[Hidden]
public float AnguloFinal
{
get => Angulo+Arco_en_grados;
}
public float VelocidadActual
{
get => _velocidadActual;
set
{
_velocidadActual = value;
Simulation_TransporteCurva?.SetSpeed(value);
OnPropertyChanged(nameof(VelocidadActual));
}
get => Angulo + Arco_en_grados;
}
private void ActualizarGeometrias()
{
if (_visualRepresentation is ucTransporteCurva uc)
{
UpdateCurve(Simulation_TransporteCurva, RadioInterno, RadioExterno, Angulo, Angulo+Arco_en_grados);
Simulation_TransporteCurva.Speed = VelocidadActual;
UpdateCurve(Simulation_TransporteCurva, RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
SetSpeed();
}
}
public float FrictionCoefficient { get => frictionCoefficient; set => frictionCoefficient = value; }
public float VelMax50hz { get => velMax50hz; set => velMax50hz = value; }
public float TiempoRampa { get => tiempoRampa; set => tiempoRampa = value; }
public bool EsMarcha { get => esMarcha; set => esMarcha = value; }
public override void OnMoveResizeRotate()
{
ActualizarGeometrias();
}
[ObservableProperty]
public float frictionCoefficient;
[ObservableProperty]
public float velMax50hz;
[ObservableProperty]
public float tiempoRampa;
[ObservableProperty]
public bool esMarcha;
public override void OnResize(float Delta_Width, float Delta_Height)
{
// Calculate the proportional change factor
float widthChangeFactor = (Ancho + Delta_Width) / Ancho;
float heightChangeFactor = (Alto + Delta_Height) / Alto;
// Use the average or minimum change factor to maintain aspect ratio
float changeFactor = Math.Min(widthChangeFactor, heightChangeFactor);
// Save the original radiuses for calculating position adjustments
float originalRadioExterno = RadioExterno;
// Apply the change factor to both radios
RadioExterno *= changeFactor;
RadioInterno *= changeFactor;
// Calculate position adjustment to keep the component centered
float radiusDifference = RadioExterno - originalRadioExterno;
// Adjust Left and Top to maintain center position
// We move by negative half the difference because the component expands outward
Left -= radiusDifference;
Top -= radiusDifference;
// Ensure minimums
if (RadioExterno < 0.1f)
RadioExterno = 0.1f;
if (RadioInterno < 0.05f)
RadioInterno = 0.05f;
// Ensure radioInterno is always less than radioExterno
if (RadioInterno >= RadioExterno)
RadioInterno = RadioExterno * 0.75f;
}
public osTransporteCurva()
{
RadioExterno = 1.3f;
RadioInterno = 1;
RadioInterno = 1f;
Ancho = RadioExterno * 2; // Set initial width based on external radius
Alto = RadioExterno * 2; // Set initial height based on external radius
Arco_en_grados = 90;
Tag_ReleActivatedMotor = "1";
}
public override void UpdateGeometryStart()
@ -92,27 +228,37 @@ namespace CtrEditor.ObjetosSim
// Se llama antes de la simulacion
ActualizarGeometrias();
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds)
public override void SimulationStop()
{
if (_osMotor != null)
// Se llama al detener la simulacion
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (Motor != null)
{
if (_osMotor is osVMmotorSim motor)
VelocidadActual = motor.Velocidad;
if (Motor is osVMmotorSim motor)
if (LeerBitTag(Tag_ReleActivatedMotor))
VelocidadActual = motor.Velocidad;
else
VelocidadActual = 0;
}
else
_osMotor = ObtenerLink(Motor, typeof(osVMmotorSim));
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
OnId_MotorChanged(Id_Motor); // Link Id_Motor = Motor
if (_visualRepresentation is ucTransporteCurva uc)
Simulation_TransporteCurva = AddCurve(RadioInterno,RadioExterno, Angulo, Angulo + Arco_en_grados);
// AddCurve(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 position)
{
Simulation_TransporteCurva = AddCurve(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
}
}
public override void ucUnLoaded()
{
@ -126,6 +272,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucTransporteCurva : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucTransporteCurva()
{
@ -141,35 +288,10 @@ namespace CtrEditor.ObjetosSim
{
Datos?.ucUnLoaded();
}
public void Resize(float RadioExterno, float RadioInterno)
{
if (Datos is osTransporteCurva datos)
{
if (RadioExterno > RadioInterno && RadioExterno > 0 && RadioInterno >= 0)
{
datos.RadioExterno = PixelToMeter.Instance.calc.PixelsToMeters(RadioExterno);
datos.RadioInterno = PixelToMeter.Instance.calc.PixelsToMeters(RadioInterno);
}
}
}
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle)
{
if (Datos != null)
if (Datos is osTransporteCurva datos)
datos.Angulo = Angle;
}
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 1;
return ZIndexEnum.Estaticos;
}
}

View File

@ -0,0 +1,24 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucTransporteCurvaGuias"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:localuc="clr-namespace:CtrEditor.ObjetosSim.UserControls" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osTransporteCurvaGuias />
</UserControl.DataContext>
<Canvas x:Name="MainCanvas">
<localuc:CircularSegment x:Name="Transporte" Angle="0"
OuterRadius="{Binding RadioExterno, Converter={StaticResource MeterToPixelConverter}}"
InnerRadius="{Binding RadioInterno, Converter={StaticResource MeterToPixelConverter}}"
StartAngle="{Binding Angulo}" EndAngle="{Binding AnguloFinal}"
ShowGuides="{Binding MostrarGuias}"
GuideDistance="{Binding DistanciaGuias, Converter={StaticResource MeterToPixelConverter}}"
GuideThickness="{Binding GrosorGuias, Converter={StaticResource MeterToPixelConverter}}"
GuideStroke="{Binding ColorGuiasBrush}" />
</Canvas>
</UserControl>

View File

@ -0,0 +1,510 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using LibS7Adv;
using CtrEditor.Simulacion;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CtrEditor.FuncionesBase;
using System.Text.Json.Serialization;
using System.Windows.Media;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// Interaction logic for ucTransporteCurvaGuias.xaml
/// </summary>
public partial class osTransporteCurvaGuias : osBase, IosBase
{
private osBase Motor = null;
private simCurve Simulation_TransporteCurvaGuias;
// Listas para almacenar los segmentos de las guías curvas
private List<simGuia> GuiasSuperiores = new List<simGuia>();
private List<simGuia> GuiasInferiores = new List<simGuia>();
private float _velocidadActual;
public static string NombreClase()
{
return "Transporte Curva con Guías";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Velocidad actual del transporte")]
[property: Name("Velocidad Actual")]
public float velocidadActual;
partial void OnVelocidadActualChanged(float value)
{
SetSpeed();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Invierte el sentido de movimiento")]
[property: Name("Invertir Dirección")]
bool invertirDireccion;
partial void OnInvertirDireccionChanged(bool value)
{
SetSpeed();
if (_visualRepresentation is ucTransporteCurvaGuias uc)
{
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
}
void SetSpeed()
{
if (InvertirDireccion)
Simulation_TransporteCurvaGuias?.SetSpeed(-VelocidadActual);
else
Simulation_TransporteCurvaGuias?.SetSpeed(VelocidadActual);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Radio externo de la curva")]
[property: Name("Radio Externo")]
private float radioExterno;
partial void OnRadioExternoChanged(float value)
{
// Update ancho and alto based on radioExterno
Ancho = value * 2;
Alto = value * 2;
// Ensure radioInterno maintains proper proportion if needed
if (RadioInterno >= value)
{
RadioInterno = value * 0.75f; // Default proportion
}
ActualizarGeometrias();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Radio interno de la curva")]
[property: Name("Radio Interno")]
private float radioInterno;
partial void OnRadioInternoChanged(float value)
{
ActualizarGeometrias();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Número de segmentos para las guías (máximo 20)")]
[property: Name("Segmentos Guías")]
private int numeroSegmentosGuias;
partial void OnNumeroSegmentosGuiasChanged(int value)
{
// Limitar entre 4 y 20 segmentos
if (value < 4) NumeroSegmentosGuias = 4;
if (value > 20) NumeroSegmentosGuias = 20;
ActualizarGeometrias();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Grosor de las guías")]
[property: Name("Grosor Guías")]
private float grosorGuias;
partial void OnGrosorGuiasChanged(float value)
{
ActualizarGeometrias();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Distancia de separación de las guías desde el borde")]
[property: Name("Distancia Guías")]
private float distanciaGuias;
partial void OnDistanciaGuiasChanged(float value)
{
ActualizarGeometrias();
}
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Mostrar guías visuales")]
[property: Name("Mostrar Guías")]
private bool mostrarGuias;
// Propiedad interna Brush para el binding
private Brush _colorGuiasBrush = new SolidColorBrush(Colors.DarkBlue);
[JsonIgnore]
public Brush ColorGuiasBrush
{
get => _colorGuiasBrush;
set
{
if (_colorGuiasBrush != value)
{
_colorGuiasBrush = value;
OnPropertyChanged(nameof(ColorGuiasBrush));
// Extraer el color del brush y actualizar la propiedad Color
if (value is SolidColorBrush solidBrush)
{
colorGuias = solidBrush.Color;
OnPropertyChanged(nameof(ColorGuias));
}
}
}
}
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color de las guías")]
[property: Name("Color Guías")]
private Color colorGuias = Colors.DarkBlue;
partial void OnColorGuiasChanged(Color value)
{
// Sincronizar con la propiedad Brush
ColorGuiasBrush = new SolidColorBrush(value);
}
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag para activar enlace con motor")]
[property: Name("Tag Activación Motor")]
string tag_ReleActivatedMotor;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Motor enlazado al transporte")]
[property: Name("Motor Enlazado")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_Motor;
[JsonIgnore]
private PropertyChangedEventHandler motorPropertyChangedHandler;
partial void OnId_MotorChanged(string value)
{
if (Motor != null && motorPropertyChangedHandler != null)
Motor.PropertyChanged -= motorPropertyChangedHandler;
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
Motor = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => s is osVMmotorSim motor && motor.Nombre == value);
if (Motor != null)
{
motorPropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_Motor = ((osVMmotorSim)sender).Nombre;
}
};
Motor.PropertyChanged += motorPropertyChangedHandler;
}
}
}
public override void AnguloChanged(float value)
{
OnPropertyChanged(nameof(AnguloFinal));
ActualizarGeometrias();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Arco de la curva en grados")]
[property: Name("Arco (grados)")]
[NotifyPropertyChangedFor(nameof(AnguloFinal))]
private float arco_en_grados;
partial void OnArco_en_gradosChanged(float value)
{
ActualizarGeometrias();
}
[Hidden]
public float AnguloFinal
{
get => Angulo + Arco_en_grados;
}
private void ActualizarGeometrias()
{
if (_visualRepresentation is ucTransporteCurvaGuias uc)
{
UpdateCurve(Simulation_TransporteCurvaGuias, RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
ActualizarGuiasCurvas();
SetSpeed();
}
}
private void ActualizarGuiasCurvas()
{
// Limpiar guías existentes
EliminarGuiasExistentes();
// Crear nuevas guías
CrearGuiasCurvas();
}
private void EliminarGuiasExistentes()
{
foreach (var guia in GuiasSuperiores)
{
simulationManager?.Remove(guia);
}
foreach (var guia in GuiasInferiores)
{
simulationManager?.Remove(guia);
}
GuiasSuperiores.Clear();
GuiasInferiores.Clear();
}
private void CrearGuiasCurvas()
{
if (NumeroSegmentosGuias < 4 || simulationManager == null)
return;
float radioGuiaSuperior = RadioExterno + DistanciaGuias;
float radioGuiaInferior = RadioInterno - DistanciaGuias;
// Asegurar que el radio interior de la guía no sea negativo
if (radioGuiaInferior < 0.01f)
radioGuiaInferior = 0.01f;
// Convertir ángulos a radianes
float anguloInicioRad = simBase.GradosARadianes(Angulo);
float anguloFinalRad = simBase.GradosARadianes(AnguloFinal);
float rangoAngular = anguloFinalRad - anguloInicioRad;
// Calcular el paso angular entre segmentos
float pasoAngular = rangoAngular / NumeroSegmentosGuias;
// Obtener el centro una vez para todo el método
nkast.Aether.Physics2D.Common.Vector2 centro = GetCurveCenterInMeter(RadioExterno);
// Crear segmentos para guía superior (externa)
for (int i = 0; i < NumeroSegmentosGuias; i++)
{
float angulo1 = anguloInicioRad + i * pasoAngular;
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
nkast.Aether.Physics2D.Common.Vector2 punto1 = new nkast.Aether.Physics2D.Common.Vector2(
radioGuiaSuperior * (float)Math.Cos(angulo1),
radioGuiaSuperior * (float)Math.Sin(angulo1)
);
nkast.Aether.Physics2D.Common.Vector2 punto2 = new nkast.Aether.Physics2D.Common.Vector2(
radioGuiaSuperior * (float)Math.Cos(angulo2),
radioGuiaSuperior * (float)Math.Sin(angulo2)
);
// Ajustar por la posición del objeto
punto1 += centro;
punto2 += centro;
simGuia guiaSegmento = simulationManager.AddLine(punto1, punto2);
GuiasSuperiores.Add(guiaSegmento);
}
// Crear segmentos para guía inferior (interna)
for (int i = 0; i < NumeroSegmentosGuias; i++)
{
float angulo1 = anguloInicioRad + i * pasoAngular;
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
nkast.Aether.Physics2D.Common.Vector2 punto1 = new nkast.Aether.Physics2D.Common.Vector2(
radioGuiaInferior * (float)Math.Cos(angulo1),
radioGuiaInferior * (float)Math.Sin(angulo1)
);
nkast.Aether.Physics2D.Common.Vector2 punto2 = new nkast.Aether.Physics2D.Common.Vector2(
radioGuiaInferior * (float)Math.Cos(angulo2),
radioGuiaInferior * (float)Math.Sin(angulo2)
);
// Ajustar por la posición del objeto
punto1 += centro;
punto2 += centro;
simGuia guiaSegmento = simulationManager.AddLine(punto1, punto2);
GuiasInferiores.Add(guiaSegmento);
}
}
public override void OnMoveResizeRotate()
{
ActualizarGeometrias();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Coeficiente de fricción")]
[property: Name("Coeficiente Fricción")]
public float frictionCoefficient;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad máxima a 50Hz")]
[property: Name("Velocidad Max 50Hz")]
public float velMax50hz;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Tiempo de rampa")]
[property: Name("Tiempo Rampa")]
public float tiempoRampa;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Estado de marcha")]
[property: Name("En Marcha")]
public bool esMarcha;
public override void OnResize(float Delta_Width, float Delta_Height)
{
// Calculate the proportional change factor
float widthChangeFactor = (Ancho + Delta_Width) / Ancho;
float heightChangeFactor = (Alto + Delta_Height) / Alto;
// Use the average or minimum change factor to maintain aspect ratio
float changeFactor = Math.Min(widthChangeFactor, heightChangeFactor);
// Save the original radiuses for calculating position adjustments
float originalRadioExterno = RadioExterno;
// Apply the change factor to both radios
RadioExterno *= changeFactor;
RadioInterno *= changeFactor;
// Calculate position adjustment to keep the component centered
float radiusDifference = RadioExterno - originalRadioExterno;
// Adjust Left and Top to maintain center position
// We move by negative half the difference because the component expands outward
Left -= radiusDifference;
Top -= radiusDifference;
// Ensure minimums
if (RadioExterno < 0.1f)
RadioExterno = 0.1f;
if (RadioInterno < 0.05f)
RadioInterno = 0.05f;
// Ensure radioInterno is always less than radioExterno
if (RadioInterno >= RadioExterno)
RadioInterno = RadioExterno * 0.75f;
}
public osTransporteCurvaGuias()
{
RadioExterno = 1.3f;
RadioInterno = 1f;
Ancho = RadioExterno * 2; // Set initial width based on external radius
Alto = RadioExterno * 2; // Set initial height based on external radius
Arco_en_grados = 90;
Tag_ReleActivatedMotor = "1";
NumeroSegmentosGuias = 12; // Valor por defecto
GrosorGuias = 0.03f;
DistanciaGuias = 0.05f;
MostrarGuias = true; // Mostrar guías por defecto
}
public override void UpdateGeometryStart()
{
// Se llama antes de la simulacion
ActualizarGeometrias();
}
public override void SimulationStop()
{
// Se llama al detener la simulacion
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (Motor != null)
{
if (Motor is osVMmotorSim motor)
if (LeerBitTag(Tag_ReleActivatedMotor))
VelocidadActual = motor.Velocidad;
else
VelocidadActual = 0;
}
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
OnId_MotorChanged(Id_Motor); // Link Id_Motor = Motor
if (_visualRepresentation is ucTransporteCurvaGuias uc)
{
Simulation_TransporteCurvaGuias = AddCurve(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
CrearGuiasCurvas(); // Crear las guías curvas
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
}
}
public override void ucUnLoaded()
{
// El UserControl se esta eliminando
// eliminar el objeto de simulacion
simulationManager?.Remove(Simulation_TransporteCurvaGuias);
EliminarGuiasExistentes();
}
}
public partial class ucTransporteCurvaGuias : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucTransporteCurvaGuias()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -4,13 +4,9 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
xmlns:convert="clr-namespace:CtrEditor.Convertidores"
mc:Ignorable="d">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
<convert:DistanceToMarginConverter x:Key="DistanceToMarginConverter"/>
<!-- Define the VisualBrush for the conveyor belt pattern -->
<VisualBrush x:Key="BeltBrush" TileMode="Tile" Viewport="0,0,20,10" ViewportUnits="Absolute" Viewbox="0,0,20,10" ViewboxUnits="Absolute">
<VisualBrush.Transform>
@ -20,8 +16,8 @@
</VisualBrush.Transform>
<VisualBrush.Visual>
<Canvas>
<Rectangle Fill="#FFBFBFBF" Width="10" Height="10"/>
<Rectangle Fill="LightGray" Width="10" Height="10" Canvas.Left="10"/>
<Rectangle Fill="LightGray" Width="10" Height="10" />
<Rectangle Fill="GhostWhite" Width="10" Height="10" Canvas.Left="10" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
@ -32,18 +28,37 @@
</UserControl.DataContext>
<Grid>
<Canvas>
<StackPanel x:Name="RectanglesContainer">
<StackPanel.RenderTransform>
<Canvas RenderTransformOrigin="0,0">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="{Binding Angulo}"/>
</StackPanel.RenderTransform>
<Rectangle x:Name="GuiaSuperior" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}" Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}" Fill="Blue"
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"/>
<Rectangle x:Name="Transporte" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}" Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"
Fill="{StaticResource BeltBrush}"/>
<Rectangle x:Name="GuiaInferior" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}" Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}" Fill="Blue"/>
<TranslateTransform/>
</TransformGroup>
</Canvas.RenderTransform>
<StackPanel x:Name="RectanglesContainer">
<Rectangle x:Name="GuiaSuperior" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"/>
<Rectangle x:Name="Transporte" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"
Fill="{StaticResource BeltBrush}"/>
<Rectangle x:Name="GuiaInferior" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"/>
</StackPanel>
<Viewbox Canvas.Top="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Stretch="Uniform">
<Label Content="{Binding Nombre}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" FontSize="18" Opacity="0.9"/>
</Viewbox>
</Canvas>
</Grid>
</UserControl>

View File

@ -1,9 +1,13 @@
using System.Windows;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.Convertidores;
using CtrEditor.Siemens;
using LibS7Adv;
using CtrEditor.Simulacion;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CtrEditor.FuncionesBase;
using System.Text.Json.Serialization;
namespace CtrEditor.ObjetosSim
{
@ -12,7 +16,7 @@ namespace CtrEditor.ObjetosSim
/// </summary>
public partial class osTransporteGuias : osBase, IosBase
{
private osBase _osMotor = null;
private osBase Motor = null;
private simTransporte? SimGeometria;
private simGuia? Guia_Superior;
@ -21,9 +25,13 @@ namespace CtrEditor.ObjetosSim
public static string NombreClase()
{
return "Transporte Guias";
return "Transporte con Guías";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
@ -31,45 +39,137 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
private float velocidadActual;
[property: Category("Simulación")]
[property: Description("Velocidad actual del transporte")]
[property: Name("Velocidad Actual")]
public float velocidadActual;
partial void OnVelocidadActualChanged(float value)
{
SimGeometria?.SetSpeed(value);
SetSpeed();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Invierte el sentido de movimiento")]
[property: Name("Invertir Dirección")]
bool invertirDireccion;
partial void OnInvertirDireccionChanged(bool value)
{
SetSpeed();
if (_visualRepresentation is ucTransporteGuias uc)
{
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
}
void SetSpeed()
{
if (InvertirDireccion)
SimGeometria?.SetSpeed(-VelocidadActual);
else
SimGeometria?.SetSpeed(VelocidadActual);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
[ObservableProperty]
public string motor;
partial void OnMotorChanged(string value)
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color del transporte")]
[property: Name("Color")]
Color color = Colors.Blue;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag para activar enlace con motor")]
[property: Name("Tag Activación Motor")]
string tag_ReleActivatedMotor;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Motor enlazado al transporte")]
[property: Name("Motor Enlazado")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_Motor;
[JsonIgnore]
private PropertyChangedEventHandler motorPropertyChangedHandler;
partial void OnId_MotorChanged(string value)
{
_osMotor = ObtenerLink(Motor, typeof(osVMmotorSim));
if (Motor != null && motorPropertyChangedHandler != null)
Motor.PropertyChanged -= motorPropertyChangedHandler;
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
Motor = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => s is osVMmotorSim motor && motor.Nombre == value);
if (Motor != null)
{
motorPropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_Motor = ((osVMmotorSim)sender).Nombre;
}
};
Motor.PropertyChanged += motorPropertyChangedHandler;
}
}
}
[ObservableProperty]
public float ancho;
[ObservableProperty]
public float alto;
partial void OnAltoChanged(float value)
public override void AltoChanged(float value)
{
ActualizarGeometrias();
}
[ObservableProperty]
public float angulo;
[property: Category("Configuración")]
[property: Description("Actuar como freno")]
[property: Name("Es Freno")]
public bool esFreno;
partial void OnEsFrenoChanged(bool value)
{
if (SimGeometria != null)
SimGeometria.isBrake = value;
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Coeficiente de fricción")]
[property: Name("Coeficiente Fricción")]
public float frictionCoefficient;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad máxima a 50Hz")]
[property: Name("Velocidad Max 50Hz")]
public float velMax50hz;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Tiempo de rampa")]
[property: Name("Tiempo Rampa")]
public float tiempoRampa;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Estado de marcha")]
[property: Name("En Marcha")]
public bool esMarcha;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Distancia entre guías")]
[property: Name("Distancia")]
private float distance;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Alto de las guías")]
[property: Name("Alto Guía")]
private float altoGuia;
private void ActualizarGeometrias()
@ -78,14 +178,18 @@ namespace CtrEditor.ObjetosSim
{
UpdateRectangle(SimGeometria, uc.Transporte, Alto, Ancho, Angulo);
UpdateOrCreateLine(Guia_Superior, uc.GuiaSuperior);
UpdateOrCreateLine(Guia_Inferior, uc.GuiaInferior) ;
UpdateOrCreateLine(Guia_Inferior, uc.GuiaInferior);
SimGeometria.DistanceGuide2Guide = Alto;
SimGeometria.Speed = VelocidadActual;
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
SimGeometria.isBrake = esFreno;
SetSpeed();
}
}
public override void OnMoveResizeRotate()
{
ActualizarGeometrias();
}
public osTransporteGuias()
{
@ -93,6 +197,7 @@ namespace CtrEditor.ObjetosSim
Alto = 0.10f;
AltoGuia = 0.03f;
Distance = 0.01f;
Tag_ReleActivatedMotor = "1";
}
public override void UpdateGeometryStart()
@ -100,26 +205,28 @@ namespace CtrEditor.ObjetosSim
// Se llama antes de la simulacion
ActualizarGeometrias();
}
public override void SimulationStop()
{
// Se llama al detener la simulacion
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds)
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (_osMotor != null)
{
if (_osMotor is osVMmotorSim motor)
VelocidadActual = motor.Velocidad;
} else if (Motor.Length > 0)
_osMotor = ObtenerLink(Motor, typeof(osVMmotorSim));
if (Motor != null)
if (Motor is osVMmotorSim id_motor)
if (LeerBitTag(Tag_ReleActivatedMotor))
VelocidadActual = id_motor.Velocidad;
else
VelocidadActual = 0;
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
@ -131,9 +238,9 @@ namespace CtrEditor.ObjetosSim
Guia_Superior = AddLine(simulationManager, uc.GuiaSuperior);
Guia_Inferior = AddLine(simulationManager, uc.GuiaInferior);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
}
Motor = Motor; // Forzar la busqueda
OnId_MotorChanged(Id_Motor); // Link Id_Motor = Motor
}
public override void ucUnLoaded()
{
@ -150,6 +257,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucTransporteGuias : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucTransporteGuias()
{
@ -165,30 +273,12 @@ namespace CtrEditor.ObjetosSim
{
Datos?.ucUnLoaded();
}
public void Resize(float width, float height)
{
if (Datos is osTransporteGuias datos)
datos.Ancho = PixelToMeter.Instance.calc.PixelsToMeters(width);
}
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle)
{
if (Datos != null)
if (Datos is osTransporteGuias datos)
datos.Angulo = Angle;
}
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 1;
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -0,0 +1,87 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucTransporteGuiasUnion"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
xmlns:uc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
mc:Ignorable="d">
<UserControl.Resources>
<!-- Define the VisualBrush for the conveyor belt pattern -->
<VisualBrush x:Key="BeltBrushA" TileMode="Tile" Viewport="0,0,20,10" ViewportUnits="Absolute" Viewbox="0,0,20,10" ViewboxUnits="Absolute">
<VisualBrush.Transform>
<TransformGroup>
<TranslateTransform/>
</TransformGroup>
</VisualBrush.Transform>
<VisualBrush.Visual>
<Canvas>
<Rectangle Fill="#FFBFBFBF" Width="10" Height="10"/>
<Rectangle Fill="LightGray" Width="10" Height="10" Canvas.Left="10"/>
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
<VisualBrush x:Key="BeltBrushB" TileMode="Tile" Viewport="0,0,20,10" ViewportUnits="Absolute" Viewbox="0,0,20,10" ViewboxUnits="Absolute">
<VisualBrush.Transform>
<TransformGroup>
<TranslateTransform/>
</TransformGroup>
</VisualBrush.Transform>
<VisualBrush.Visual>
<Canvas>
<Rectangle Fill="#FFBFBFBF" Width="10" Height="10"/>
<Rectangle Fill="LightGray" Width="10" Height="10" Canvas.Left="10"/>
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</UserControl.Resources>
<UserControl.DataContext>
<vm:osTransporteGuiasUnion Color="Red"/>
</UserControl.DataContext>
<Grid>
<Canvas x:Name="Canvas" RenderTransformOrigin="0,0">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="{Binding Angulo}"/>
<TranslateTransform/>
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle x:Name="TransporteA" Width="{Binding AnchoTransporte_oculto, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{StaticResource BeltBrushA}"
/>
<Rectangle x:Name="TransporteB" Width="{Binding AnchoTransporte_oculto, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{StaticResource BeltBrushB}"
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1.05}"
Canvas.Left="{Binding AnchoRecto, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
/>
<uc:ThreeLinesControl x:Name="GuiaSuperior" AnchoRecto="{Binding AnchoRecto, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
AnchoCentro="{Binding AnchoCentral, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
Altura="{Binding Alto, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
AltoGuia="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
Canvas.Top="{Binding Distance, Converter={StaticResource MeterToPixelConverter},ConverterParameter=-1}" Color="{Binding Color}"/>
<uc:ThreeLinesControl x:Name="GuiaInferior" AnchoRecto="{Binding AnchoRecto, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
AnchoCentro="{Binding AnchoCentral, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
Altura="{Binding Alto, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
AltoGuia="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter},ConverterParameter=1}"
Color="{Binding Color}">
<Canvas.Top>
<MultiBinding Converter="{StaticResource SumConverter}">
<Binding Path="Alto" Converter="{StaticResource MeterToPixelConverter}" ConverterParameter="1.0" />
<Binding Path="Distance" Converter="{StaticResource MeterToPixelConverter}" ConverterParameter="1.0" />
</MultiBinding>
</Canvas.Top>
</uc:ThreeLinesControl>
</Canvas>
</Grid>
</UserControl>

View File

@ -0,0 +1,462 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using CommunityToolkit.Mvvm.ComponentModel;
using LibS7Adv;
using CtrEditor.Simulacion;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CtrEditor.FuncionesBase;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// Interaction logic for ucTransporteGuiasUnion.xaml
/// </summary>
public partial class osTransporteGuiasUnion : osBase, IosBase
{
private osBase _osMotorA = null;
private osBase _osMotorB = null;
Dictionary<Rectangle, simTransporte> SimGeometriaT;
Dictionary<Rectangle, simGuia> SimGeometriaG;
Dictionary<Rectangle, Storyboard> Storyboards;
Dictionary<Rectangle, BoolReference> TransportsDirection;
Dictionary<Rectangle, FloatReference> TransportsVelocidad;
public static string NombreClase()
{
return "Transporte Guías Unión";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color del transporte")]
[property: Name("Color")]
Color color;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag para activar enlace con motor")]
[property: Name("Tag Activación Motor")]
string tag_ReleActivatedMotor;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Motor A enlazado")]
[property: Name("Motor A")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_MotorA;
partial void OnId_MotorAChanged(string value)
{
if (_osMotorA != null)
_osMotorA.PropertyChanged -= OnMotorPropertyChangedA;
if (_mainViewModel != null && value != null && value.Length > 0)
{
_osMotorA = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osVMmotorSim && s.Nombre == value), null);
if (_osMotorA != null)
_osMotorA.PropertyChanged += OnMotorPropertyChangedA;
}
}
private void OnMotorPropertyChangedA(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_MotorA = ((osVMmotorSim)sender).Nombre;
}
}
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Motor B enlazado")]
[property: Name("Motor B")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_MotorB;
partial void OnId_MotorBChanged(string value)
{
if (_osMotorB != null)
_osMotorB.PropertyChanged -= OnMotorPropertyChangedB;
if (_mainViewModel != null && value != null && value.Length > 0)
{
_osMotorB = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osVMmotorSim && s.Nombre == value), null);
if (_osMotorB != null)
_osMotorB.PropertyChanged += OnMotorPropertyChangedB;
}
}
private void OnMotorPropertyChangedB(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_MotorB = ((osVMmotorSim)sender).Nombre;
}
}
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Velocidad actual del motor A")]
[property: Name("Velocidad A")]
public float velocidadActualA;
partial void OnVelocidadActualAChanged(float value)
{
if (_visualRepresentation is ucTransporteGuiasUnion uc)
{
var transporte = uc.TransporteA;
SetSpeed(transporte);
}
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Invertir dirección del motor A")]
[property: Name("Invertir A")]
bool invertirDireccionA;
partial void OnInvertirDireccionAChanged(bool value)
{
if (_visualRepresentation is ucTransporteGuiasUnion uc)
{
var transporte = uc.TransporteA;
SetSpeed(transporte);
ActualizarStoryboards(transporte);
}
}
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Velocidad actual del motor B")]
[property: Name("Velocidad B")]
public float velocidadActualB;
partial void OnVelocidadActualBChanged(float value)
{
if (_visualRepresentation is ucTransporteGuiasUnion uc)
{
var transporte = uc.TransporteB;
SetSpeed(transporte);
}
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Invertir dirección del motor B")]
[property: Name("Invertir B")]
bool invertirDireccionB;
partial void OnInvertirDireccionBChanged(bool value)
{
if (_visualRepresentation is ucTransporteGuiasUnion uc)
{
var transporte = uc.TransporteB;
SetSpeed(transporte);
ActualizarStoryboards(transporte);
}
}
public override void OnResize(float Delta_Width, float Delta_Height)
{
AnchoRecto += Delta_Width;
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Ancho de las secciones rectas")]
[property: Name("Ancho Recto")]
public float anchoRecto;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Ancho de la sección central")]
[property: Name("Ancho Central")]
public float anchoCentral;
partial void OnAnchoRectoChanged(float value)
{
AnchoTransporte_oculto = anchoRecto + anchoCentral;
}
partial void OnAnchoCentralChanged(float value)
{
AnchoTransporte_oculto = anchoRecto + anchoCentral;
}
[ObservableProperty]
[property: Category("Información")]
[property: Description("Ancho total del transporte calculado")]
[property: Name("Ancho Total")]
public float anchoTransporte_oculto;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Coeficiente de fricción")]
[property: Name("Coeficiente Fricción")]
public float frictionCoefficient;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad máxima a 50Hz")]
[property: Name("Velocidad Max 50Hz")]
public float velMax50hz;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Tiempo de rampa")]
[property: Name("Tiempo Rampa")]
public float tiempoRampa;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Estado de marcha")]
[property: Name("En Marcha")]
public bool esMarcha;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Distancia entre elementos")]
[property: Name("Distancia")]
private float distance;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Alto de las guías")]
[property: Name("Alto Guía")]
private float altoGuia;
void ActualizarStoryboards(Rectangle transporte)
{
if (!Storyboards.Keys.Contains(transporte)) return;
var direccion = TransportsDirection[transporte].Value;
var velocidad = TransportsVelocidad[transporte].Value;
Storyboards[transporte] = CrearAnimacionMultiStoryBoardTrasnporte(Storyboards[transporte], transporte, direccion);
ActualizarAnimacionMultiStoryBoardTransporte(Storyboards[transporte], velocidad);
}
void SetSpeed(Rectangle transporte)
{
if (!Storyboards.Keys.Contains(transporte)) return;
var invertirDireccion = TransportsDirection[transporte].Value;
var velocidad = TransportsVelocidad[transporte].Value;
if (invertirDireccion)
SimGeometriaT[transporte]?.SetSpeed(-velocidad);
else
SimGeometriaT[transporte]?.SetSpeed(velocidad);
ActualizarAnimacionMultiStoryBoardTransporte(Storyboards[transporte], velocidad);
}
private void ActualizarGeometrias()
{
if (_visualRepresentation is ucTransporteGuiasUnion uc)
{
foreach (var transporte in SimGeometriaT)
{
UpdateRectangle(transporte.Value, transporte.Key, Alto, AnchoTransporte_oculto, Angulo);
ActualizarStoryboards(transporte.Key);
SetSpeed(transporte.Key);
}
foreach (var l in SimGeometriaG)
UpdateOrCreateLine(l.Value, l.Key);
}
}
public override void OnMoveResizeRotate()
{
ActualizarGeometrias();
}
public osTransporteGuiasUnion()
{
AnchoRecto = 0.5f;
AnchoCentral = 0.5f;
Alto = 0.10f;
AltoGuia = 0.03f;
Distance = 0.02f;
SimGeometriaT = new Dictionary<Rectangle, simTransporte>();
SimGeometriaG = new Dictionary<Rectangle, simGuia>();
Storyboards = new Dictionary<Rectangle, Storyboard>();
TransportsDirection = new Dictionary<Rectangle, BoolReference>();
TransportsVelocidad = new Dictionary<Rectangle, FloatReference>();
Tag_ReleActivatedMotor = "1";
}
public override void UpdateGeometryStart()
{
// Se llama antes de la simulacion
ActualizarGeometrias();
}
public override void SimulationStop()
{
// Se llama al detener la simulacion
if (_visualRepresentation is ucTransporteGuiasUnion uc)
{
SetSpeed(uc.TransporteB);
SetSpeed(uc.TransporteA);
}
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (_osMotorA != null)
{
if (_osMotorA is osVMmotorSim motor)
if (LeerBitTag(Tag_ReleActivatedMotor))
VelocidadActualA = motor.Velocidad;
else
VelocidadActualA = 0;
}
if (_osMotorB != null)
{
if (_osMotorB is osVMmotorSim motor)
if (LeerBitTag(Tag_ReleActivatedMotor))
VelocidadActualB = motor.Velocidad;
else
VelocidadActualB = 0;
}
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
if (_visualRepresentation is ucTransporteGuiasUnion uc)
{
foreach (var child in uc.Canvas.Children)
if (child is Rectangle rect)
if (rect.Name.StartsWith("Transporte"))
{
SimGeometriaT.Add(rect, AddRectangle(simulationManager, rect, Alto, AnchoTransporte_oculto, Angulo));
Storyboards.Add(rect, CrearAnimacionMultiStoryBoardTrasnporte(rect, false));
}
foreach (var child in uc.GuiaSuperior.Canvas.Children)
if (child is Rectangle rect)
SimGeometriaG.Add(rect, AddLine(simulationManager, rect));
foreach (var child in uc.GuiaInferior.Canvas.Children)
if (child is Rectangle rect)
SimGeometriaG.Add(rect, AddLine(simulationManager, rect));
TransportsDirection.Add(uc.TransporteA, new BoolReference(() => InvertirDireccionA, value => InvertirDireccionA = value));
TransportsDirection.Add(uc.TransporteB, new BoolReference(() => InvertirDireccionB, value => InvertirDireccionB = value));
TransportsVelocidad.Add(uc.TransporteA, new FloatReference(() => VelocidadActualA, value => VelocidadActualA = value));
TransportsVelocidad.Add(uc.TransporteB, new FloatReference(() => VelocidadActualB, value => VelocidadActualB = value));
}
OnId_MotorAChanged(Id_MotorA); // Link Id_Motor = Motor
OnId_MotorBChanged(Id_MotorB); // Link Id_Motor = Motor
}
public override void ucUnLoaded()
{
// El UserControl se esta eliminando
// eliminar el objeto de simulacion
foreach (var s in SimGeometriaT)
simulationManager.Remove(s.Value);
foreach (var s in SimGeometriaG)
simulationManager.Remove(s.Value);
}
}
public class BoolReference
{
private Func<bool> getter;
private Action<bool> setter;
public BoolReference(Func<bool> getter, Action<bool> setter)
{
this.getter = getter;
this.setter = setter;
}
public bool Value
{
get => getter();
set => setter(value);
}
}
public class FloatReference
{
private Func<float> getter;
private Action<float> setter;
public FloatReference(Func<float> getter, Action<float> setter)
{
this.getter = getter;
this.setter = setter;
}
public float Value
{
get => getter();
set => setter(value);
}
}
public partial class ucTransporteGuiasUnion : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucTransporteGuiasUnion()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -4,13 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
xmlns:convert="clr-namespace:CtrEditor.Convertidores">
mc:Ignorable="d">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
<!-- Define the VisualBrush for the conveyor belt pattern -->
<VisualBrush x:Key="BeltBrush" TileMode="Tile" Viewport="0,0,20,10" ViewportUnits="Absolute" Viewbox="0,0,20,10" ViewboxUnits="Absolute">
<VisualBrush.Transform>
@ -20,8 +17,8 @@
</VisualBrush.Transform>
<VisualBrush.Visual>
<Canvas>
<Rectangle Fill="Gray" Width="10" Height="10"/>
<Rectangle Fill="DarkGray" Width="10" Height="10" Canvas.Left="10"/>
<Rectangle Fill="LightGray" Width="10" Height="10"/>
<Rectangle Fill="GhostWhite" Width="10" Height="10" Canvas.Left="10"/>
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
@ -31,14 +28,25 @@
<vm:osTransporteTTop Ancho="2"/>
</UserControl.DataContext>
<Canvas>
<Rectangle x:Name="Transporte"
<Canvas RenderTransformOrigin="0,0">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform Angle="{Binding Angulo}" />
<TranslateTransform />
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle x:Name="Transporte"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{StaticResource BeltBrush}">
<Rectangle.RenderTransform>
<RotateTransform Angle="{Binding Angulo}"/>
</Rectangle.RenderTransform>
</Rectangle>
<Viewbox
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}" Stretch="Uniform">
<Label Content="{Binding Nombre}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold"
FontSize="18" Opacity="0.9" />
</Viewbox>
</Canvas>
</UserControl>

View File

@ -1,13 +1,12 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.Convertidores;
using CtrEditor.Siemens;
using LibS7Adv;
using CtrEditor.Simulacion;
using System.Windows.Input;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;
using CtrEditor.FuncionesBase;
using System.Text.Json.Serialization;
namespace CtrEditor.ObjetosSim
{
@ -18,58 +17,122 @@ namespace CtrEditor.ObjetosSim
public partial class osTransporteTTop : osBase, IosBase
{
private osBase _osMotor = null;
private simTransporte SimGeometria;
private osVMmotorSim Motor;
public static string NombreClase()
{
return "Transporte";
return "Transporte TTOP";
}
private string nombre = "Transporte TTOP";
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
private float velocidadActual;
public float VelocidadActual
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Velocidad actual del transporte en m/s")]
[property: Name("Velocidad Actual")]
public float velocidadActual;
partial void OnVelocidadActualChanged(float value)
{
get => velocidadActual;
set
SetSpeed();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Invierte el sentido de movimiento del transporte")]
[property: Name("Invertir Dirección")]
bool invertirDireccion;
partial void OnInvertirDireccionChanged(bool value)
{
SetSpeed();
if (_visualRepresentation is ucTransporteTTop uc)
{
if (value != velocidadActual)
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
}
void SetSpeed()
{
if (InvertirDireccion)
SimGeometria?.SetSpeed(-VelocidadActual);
else
SimGeometria?.SetSpeed(VelocidadActual);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
[ObservableProperty]
[property: Description("Tag de bit para activar enlace con motor")]
[property: Category("Enlace PLC")]
[property: Name("Tag Activación Motor")]
string tag_ReleActivatedMotor;
[ObservableProperty]
[property: Description("Seleccionar motor para enlazar")]
[property: Category("Enlace PLC")]
[property: Name("Motor Enlazado")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_Motor;
[JsonIgnore]
private PropertyChangedEventHandler motorPropertyChangedHandler;
partial void OnId_MotorChanged(string value)
{
if (Motor != null && motorPropertyChangedHandler != null)
Motor.PropertyChanged -= motorPropertyChangedHandler;
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
Motor = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => s is osVMmotorSim motor && motor.Nombre == value);
if (Motor != null)
{
velocidadActual = value;
SimGeometria?.SetSpeed(value);
SetProperty(ref velocidadActual, value);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
motorPropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_Motor = ((osVMmotorSim)sender).Nombre;
}
};
Motor.PropertyChanged += motorPropertyChangedHandler;
}
}
}
[ObservableProperty]
public string motor;
partial void OnMotorChanged(string value)
{
_osMotor = ObtenerLink(Motor, typeof(osVMmotorSim));
}
[ObservableProperty]
public float ancho;
[ObservableProperty]
public float alto;
[ObservableProperty]
public float angulo;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Coeficiente de fricción entre objetos y superficie")]
[property: Name("Coeficiente de Fricción")]
public float frictionCoefficient;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad máxima a 50Hz")]
[property: Name("Velocidad Máxima 50Hz")]
public float velMax50hz;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Tiempo de rampa para acelerar/desacelerar")]
[property: Name("Tiempo de Rampa")]
public float tiempoRampa;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Indica si el transporte está en marcha")]
[property: Name("En Marcha")]
public bool esMarcha;
@ -77,16 +140,22 @@ namespace CtrEditor.ObjetosSim
{
if (_visualRepresentation is ucTransporteTTop uc)
{
UpdateRectangle(SimGeometria, uc.Transporte,Alto,Ancho,Angulo);
SimGeometria.Speed = VelocidadActual;
UpdateRectangle(SimGeometria, uc.Transporte, Alto, Ancho, Angulo);
SetSpeed();
}
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
public override void OnMoveResizeRotate()
{
ActualizarGeometrias();
}
public osTransporteTTop()
{
{
Ancho = 1;
Alto = 0.10f;
Tag_ReleActivatedMotor = "1";
}
public override void SimulationStop()
@ -100,27 +169,73 @@ namespace CtrEditor.ObjetosSim
ActualizarGeometrias();
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds)
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (_osMotor != null)
{
if (_osMotor is osVMmotorSim motor)
VelocidadActual = motor.Velocidad;
}
else if (Motor.Length > 0)
_osMotor = ObtenerLink(Motor, typeof(osVMmotorSim));
if (Motor != null)
if (Motor is osVMmotorSim motor)
if (LeerBitTag(Tag_ReleActivatedMotor))
VelocidadActual = motor.Velocidad;
else
VelocidadActual = 0;
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
OnId_MotorChanged(Id_Motor); // Link Id_Motor = Motor
if (_visualRepresentation is ucTransporteTTop uc)
{
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte);
try
{
// Asegurar que el layout esté actualizado antes de crear la geometría
uc.UpdateLayout();
// Validar que el rectángulo esté disponible y tenga dimensiones válidas
if (uc.Transporte != null &&
(!double.IsNaN(uc.Transporte.ActualWidth) && uc.Transporte.ActualWidth > 0) ||
(!double.IsNaN(uc.Transporte.Width) && uc.Transporte.Width > 0))
{
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
}
else
{
// Si el rectángulo no está listo, intentar después del próximo layout
uc.Dispatcher.BeginInvoke(new Action(() =>
{
if (uc.Transporte != null && simulationManager != null)
{
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
}
}), System.Windows.Threading.DispatcherPriority.Loaded);
}
}
catch (Exception ex)
{
// Log del error para diagnóstico pero continuar sin fallar
System.Diagnostics.Debug.WriteLine($"Error al crear geometría de simulación: {ex.Message}");
// Intentar crear la geometría más tarde
uc.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
if (uc.Transporte != null && simulationManager != null)
{
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
}
}
catch (Exception ex2)
{
System.Diagnostics.Debug.WriteLine($"Segundo intento falló: {ex2.Message}");
}
}), System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}
}
}
public override void ucUnLoaded()
@ -135,6 +250,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucTransporteTTop : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucTransporteTTop()
{
@ -150,31 +266,16 @@ namespace CtrEditor.ObjetosSim
{
Datos?.ucUnLoaded();
}
public void Resize(float width, float height)
{
if (Datos is osTransporteTTop datos)
datos.Ancho = PixelToMeter.Instance.calc.PixelsToMeters(width);
}
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle) {
if (Datos != null)
if (Datos is osTransporteTTop datos)
datos.Angulo = Angle;
}
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 1;
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -0,0 +1,50 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucTransporteTTopDualInverter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.Resources>
<!-- Define the VisualBrush for the conveyor belt pattern -->
<VisualBrush x:Key="BeltBrush" TileMode="Tile" Viewport="0,0,20,10" ViewportUnits="Absolute" Viewbox="0,0,20,10"
ViewboxUnits="Absolute">
<VisualBrush.Transform>
<TransformGroup>
<TranslateTransform />
</TransformGroup>
</VisualBrush.Transform>
<VisualBrush.Visual>
<Canvas>
<Rectangle Fill="LightGray" Width="10" Height="10" />
<Rectangle Fill="GhostWhite" Width="10" Height="10" Canvas.Left="10" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</UserControl.Resources>
<UserControl.DataContext>
<vm:osTransporteTTop Ancho="2" />
</UserControl.DataContext>
<Canvas RenderTransformOrigin="0,0">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform Angle="{Binding Angulo}" />
<TranslateTransform />
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle x:Name="Transporte" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{StaticResource BeltBrush}">
</Rectangle>
<Viewbox Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}" Stretch="Uniform">
<Label Content="{Binding Nombre}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold"
FontSize="18" Opacity="0.9" />
</Viewbox>
</Canvas>
</UserControl>

View File

@ -0,0 +1,290 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.FuncionesBase;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using LibS7Adv;
using CtrEditor.Simulacion;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;
using CtrEditor.FuncionesBase;
using System.Text.Json.Serialization;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// Interaction logic for ucTransporteTTopDualInverter.xaml
/// </summary>
///
public partial class osTransporteTTopDualInverter : osBase, IosBase
{
private simTransporte SimGeometria;
private osVMmotorSim MotorA;
private osVMmotorSim MotorB;
public static string NombreClase()
{
return "Transporte TTOP Doble Inversor";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Velocidad actual del transporte")]
[property: Name("Velocidad Actual")]
public float velocidadActual;
partial void OnVelocidadActualChanged(float value)
{
SetSpeed();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Invierte el sentido de movimiento")]
[property: Name("Invertir Dirección")]
bool invertirDireccion;
partial void OnInvertirDireccionChanged(bool value)
{
SetSpeed();
if (_visualRepresentation is ucTransporteTTopDualInverter uc)
{
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
}
void SetSpeed()
{
if (InvertirDireccion)
SimGeometria?.SetSpeed(-VelocidadActual);
else
SimGeometria?.SetSpeed(VelocidadActual);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag para activar enlace con inversor A")]
[property: Name("Tag Activación Motor A")]
string tag_ReleActivatedMotor_A;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag para activar enlace con inversor B")]
[property: Name("Tag Activación Motor B")]
string tag_ReleActivatedMotor_B;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Enlace a inversor A")]
[property: Name("Motor A")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_Motor_A;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Enlace a inversor B")]
[property: Name("Motor B")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_Motor_B;
[JsonIgnore]
private PropertyChangedEventHandler motorPropertyChangedHandler;
partial void OnId_Motor_AChanged(string value)
{
if (MotorA != null && motorPropertyChangedHandler != null)
MotorA.PropertyChanged -= motorPropertyChangedHandler;
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
MotorA = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => s is osVMmotorSim motor && motor.Nombre == value);
if (MotorA != null)
{
motorPropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_Motor_A = ((osVMmotorSim)sender).Nombre;
}
};
MotorA.PropertyChanged += motorPropertyChangedHandler;
}
}
}
partial void OnId_Motor_BChanged(string value)
{
if (MotorB != null && motorPropertyChangedHandler != null)
MotorB.PropertyChanged -= motorPropertyChangedHandler;
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
{
MotorB = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => s is osVMmotorSim motor && motor.Nombre == value);
if (MotorB != null)
{
motorPropertyChangedHandler = (sender, e) =>
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_Motor_B = ((osVMmotorSim)sender).Nombre;
}
};
MotorB.PropertyChanged += motorPropertyChangedHandler;
}
}
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Coeficiente de fricción")]
[property: Name("Coeficiente Fricción")]
public float frictionCoefficient;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad máxima a 50Hz")]
[property: Name("Velocidad Max 50Hz")]
public float velMax50hz;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Tiempo de rampa")]
[property: Name("Tiempo Rampa")]
public float tiempoRampa;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Estado de marcha")]
[property: Name("En Marcha")]
public bool esMarcha;
private void ActualizarGeometrias()
{
if (_visualRepresentation is ucTransporteTTopDualInverter uc)
{
UpdateRectangle(SimGeometria, uc.Transporte, Alto, Ancho, Angulo);
SetSpeed();
}
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
public override void OnMoveResizeRotate()
{
ActualizarGeometrias();
}
public osTransporteTTopDualInverter()
{
Ancho = 1;
Alto = 0.10f;
Tag_ReleActivatedMotor_A = "1";
Tag_ReleActivatedMotor_B = "1";
}
public override void SimulationStop()
{
// Se llama al detener la simulacion
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
public override void UpdateGeometryStart()
{
// Se llama antes de la simulacion
ActualizarGeometrias();
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (LeerBitTag(Tag_ReleActivatedMotor_A))
{
if (MotorA != null)
if (MotorA is osVMmotorSim motor)
VelocidadActual = motor.Velocidad;
else
VelocidadActual = 0;
}
else if (LeerBitTag(Tag_ReleActivatedMotor_B))
{
if (MotorB != null)
if (MotorB is osVMmotorSim motor)
VelocidadActual = motor.Velocidad;
else
VelocidadActual = 0;
}
else
VelocidadActual = 0;
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
OnId_Motor_AChanged(Id_Motor_A); // Link Id_Motor = Motor
OnId_Motor_BChanged(Id_Motor_B); // Link Id_Motor = Motor
if (_visualRepresentation is ucTransporteTTopDualInverter uc)
{
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
}
}
public override void ucUnLoaded()
{
// El UserControl se esta eliminando
// eliminar el objeto de simulacion
simulationManager.Remove(SimGeometria);
}
}
public partial class ucTransporteTTopDualInverter : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucTransporteTTopDualInverter()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -4,34 +4,40 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
xmlns:convert="clr-namespace:CtrEditor.Convertidores">
mc:Ignorable="d">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
</UserControl.Resources>
<UserControl.DataContext>
<vm:osVMmotorSim ImageSource_oculta="/imagenes/motorNegro.png" />
<vm:osVMmotorSim ImageSource_oculta="/imagenes/motorNegro.png"/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid RenderTransformOrigin="0,0">
<Grid.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Angulo}"/>
</TransformGroup>
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Content="{Binding Nombre}"
<Image Grid.Column="0" Source="{Binding ImageSource_oculta}"
Width="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
Stretch="Uniform"/>
<Viewbox Grid.Column="1" Stretch="Uniform">
<Label Content="{Binding Nombre}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Transparent"
Foreground="Black"/>
<Image Grid.Row="1" Source="{Binding ImageSource_oculta}"
Width="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
Stretch="Uniform"/>
Foreground="Black"
Opacity="0.5"/>
</Viewbox>
</Grid>
</UserControl>

View File

@ -1,10 +1,13 @@
using CtrEditor.Convertidores;
using CtrEditor.Siemens;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.FuncionesBase;
using LibS7Adv;
using Newtonsoft.Json;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Newtonsoft.Json;
using CommunityToolkit.Mvvm.ComponentModel;
namespace CtrEditor.ObjetosSim
{
@ -17,32 +20,84 @@ namespace CtrEditor.ObjetosSim
{
// Otros datos y métodos relevantes para la simulación
private VMSimMotor motState = new VMSimMotor();
public static string NombreClase()
{
return "VetroMeccanica Motor";
return "Motor VetroMeccanica";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[JsonIgnore]
[ObservableProperty]
[property: JsonIgnore]
[property: Category("Apariencia")]
[property: Description("Imagen visual del motor")]
[property: Name("Imagen")]
public ImageSource imageSource_oculta;
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Tamaño visual del motor")]
[property: Name("Tamaño")]
public float tamano;
[ObservableProperty]
public float maxRatedHz;
public override void OnResize(float Delta_Width, float Delta_Height)
{
Tamano += Delta_Width + Delta_Height;
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Tiempo de actualización en milisegundos")]
[property: Name("Tiempo de Actualización")]
public float refresh_Time_ms;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad proporcional en porcentaje")]
[property: Name("Velocidad Proporcional")]
public float proporcional_Speed;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Velocidad máxima para rampa")]
[property: Name("Velocidad Máxima Rampa")]
public float max_Speed_for_Ramp;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Contacto normalmente cerrado de trip del VFD")]
[property: Name("VFD Trip NC")]
bool vFD_Trip_NC;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Tiempo de rampa para acelerar/desacelerar")]
[property: Name("Tiempo de Rampa")]
public float tiempoRampa;
[ObservableProperty]
[property: Description("Habilita lectura del encoder del motor simulado en PLC")]
[property: Category("Encoder")]
[property: Name("Motor con Encoder")]
bool motor_With_Encoder;
[ObservableProperty]
[property: Description("Valor actual de la posición del encoder")]
[property: Category("Encoder")]
[property: Name("Posición Actual")]
public float actual_Position;
partial void OnTiempoRampaChanged(float value)
{
if (value < 0.1f)
@ -51,15 +106,50 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
public bool encendido;
[property: Category("Información")]
[property: Description("Estado de encendido del motor")]
[property: Name("Encendido")]
bool encendido;
[ObservableProperty]
public int pLC_NumeroMotor;
[property: Category("Enlace PLC")]
[property: Description("Número del motor en el PLC")]
[property: Name("Número de Motor PLC")]
int pLC_NumeroMotor;
partial void OnPLC_NumeroMotorChanged(int value)
{
if (PLC_DB_Motor == 0)
{
PLC_DB_Motor = PLC_NumeroMotor - 30 + 300;
}
}
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Bloque de datos del motor en el PLC")]
[property: Name("DB del Motor")]
int pLC_DB_Motor;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Relación de transmisión")]
[property: Name("Ratio")]
public float ratio;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Velocidad actual del motor")]
[property: Name("Velocidad")]
public float velocidad;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Invierte el sentido de giro")]
[property: Name("Sentido Contrario")]
public bool sentido_contrario;
partial void OnVelocidadChanged(float value)
{
if (value > 0)
@ -72,27 +162,43 @@ namespace CtrEditor.ObjetosSim
{
Tamano = 0.30f;
PLC_NumeroMotor = 31;
MaxRatedHz = 100;
Proporcional_Speed = 100;
Max_Speed_for_Ramp = 100;
TiempoRampa = 3;
ImageSource_oculta = ImageFromPath("/imagenes/motor2.png");
}
Refresh_Time_ms = 500;
}
public override void UpdateGeometryStart()
{
// Se llama antes de la simulacion
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds) {
motState.UpdatePLC(plc,PLC_NumeroMotor,Encendido, elapsedMilliseconds);
Velocidad = motState.STATUS_VFD_ACT_Speed_Hz / 10;
[JsonIgnore]
private float elapsedTimeAccumulator = 0;
public override void UpdatePLC(PLCViewModel plc, int TotalMilliseconds)
{
elapsedTimeAccumulator += TotalMilliseconds;
float randomFactor = (float)(new Random().NextDouble() * 0.1); // 10% random factor
float adjustedRefreshTime = Refresh_Time_ms * (1 + randomFactor);
if (elapsedTimeAccumulator >= adjustedRefreshTime)
{
motState.UpdatePLC(plc, this, TotalMilliseconds);
elapsedTimeAccumulator = 0;
}
Velocidad = (Proporcional_Speed / 100) * (motState.STATUS_VFD_ACT_Speed_Hz / 10);
Sentido_contrario = motState.OUT_Reversal;
}
public override void UpdateControl(int elapsedMilliseconds)
public override void UpdateControl(int TotalMilliseconds)
{
// Calculamos la velocidad
motState.UpdateSpeed(MaxRatedHz,TiempoRampa, elapsedMilliseconds);
motState.UpdateSpeed(Max_Speed_for_Ramp, TiempoRampa, TotalMilliseconds);
}
@ -100,7 +206,8 @@ namespace CtrEditor.ObjetosSim
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
OnVelocidadChanged(Velocidad);
}
}
@ -108,6 +215,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucVMmotorSim : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucVMmotorSim()
{
@ -123,21 +231,12 @@ namespace CtrEditor.ObjetosSim
{
Datos?.ucUnLoaded();
}
public void Resize(float width, float height) { }
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle) { }
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 10;
return ZIndexEnum.Estaticos;
}
}
public class VMSimMotor
@ -153,26 +252,38 @@ namespace CtrEditor.ObjetosSim
public bool OUT_Reversal;
public float OUT_OUT_VFD_REQ_Speed_Hz;
public void UpdatePLC(PLCModel plc, int NumeroMotor, bool Encendido, int elapsedMilliseconds)
public void UpdatePLC(PLCViewModel plc, osVMmotorSim Data, int TotalMilliseconds)
{
var index = 0;
switch (NumeroMotor)
{
case < 100:
index = (int)NumeroMotor - 30 + 300;
break;
}
var DB_Motor = Data.PLC_DB_Motor;
OUT_Run = plc.LeerTagBool($"\"DB MotorSimulate\".Motors[{index}].OUT.Run");
OUT_Reversal = plc.LeerTagBool($"\"DB MotorSimulate\".Motors[{index}].OUT.\"Reversal Direction\"");
OUT_OUT_VFD_REQ_Speed_Hz = (float)plc.LeerTagInt16($"\"DB MotorSimulate\".Motors[{index}].OUT.OUT_VFD_REQ_Speed_Hz");
if (DB_Motor == 0)
return;
// Add timestamp to trace when the read occurs
var timestamp = DateTime.Now;
if (Data.Motor_With_Encoder)
Data.Actual_Position = (float)plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ActualPosition");
else
Data.Actual_Position = 0;
// Read ControlWord and track the raw response
var rawResponse = plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ControlWord");
int controlWord = rawResponse ?? 0;
var control = VMMotorBitPacker.UnpackControlWord(controlWord);
// Update local state from ControlWord
OUT_Run = control.run;
OUT_Stop = !control.stop;
OUT_Reversal = control.reversal;
OUT_OUT_VFD_REQ_Speed_Hz = control.reqSpeedHz;
if (Encendido)
// Update motor state based on enable status
if (Data.Encendido)
{
_STATUS_VFD_Ready = true;
Motor_Running = true;
Motor_Running = OUT_Run && !OUT_Stop;
STATUS_VFD_Trip = false;
STATUS_VFD_Warning = false;
STATUS_VFD_Coasting = false;
@ -186,19 +297,27 @@ namespace CtrEditor.ObjetosSim
STATUS_VFD_Coasting = false;
}
plc.EscribirTagBool($"\"DB MotorSimulate\".Motors[{index}].STATUS_VFD_Ready", _STATUS_VFD_Ready);
plc.EscribirTagBool($"\"DB MotorSimulate\".Motors[{index}].Motor_Running", Motor_Running);
plc.EscribirTagBool($"\"DB MotorSimulate\".Motors[{index}].STATUS_VFD_Trip", STATUS_VFD_Trip);
plc.EscribirTagBool($"\"DB MotorSimulate\".Motors[{index}].STATUS_VFD_Warning", STATUS_VFD_Warning);
plc.EscribirTagBool($"\"DB MotorSimulate\".Motors[{index}].STATUS_VFD_Coasting", STATUS_VFD_Coasting);
if (Data.VFD_Trip_NC)
STATUS_VFD_Trip = !STATUS_VFD_Trip;
plc.EscribirTagInt16($"\"DB MotorSimulate\".Motors[{index}].STATUS_VFD_ACT_Speed_Hz", (int)STATUS_VFD_ACT_Speed_Hz);
// Pack all status bits and speed into StatusWord
int statusWord = VMMotorBitPacker.PackStatusWord(
_STATUS_VFD_Ready,
Motor_Running,
STATUS_VFD_Trip,
STATUS_VFD_Warning,
STATUS_VFD_Coasting,
(short)STATUS_VFD_ACT_Speed_Hz
);
// Write StatusWord in one operation
plc.EscribirTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].StatusWord", statusWord);
}
private float CalcSpeedRamp(float MaxRatedHz, float TiempoRampa, float actual, float expected, int elapsedMilliseconds)
private float CalcSpeedRamp(float max_Speed_for_Ramp, float TiempoRampa, float actual, float expected, int TotalMilliseconds)
{
float hzIncrementsRamp = (MaxRatedHz * 10) / (TiempoRampa * (1000.0f / elapsedMilliseconds));
float hzIncrementsRamp = (max_Speed_for_Ramp * 10) / (TiempoRampa * (1000.0f / TotalMilliseconds));
float delta = expected - actual;
// Conrtolar si la diferencia no es mayor de lo que falta
if (Math.Abs(hzIncrementsRamp) > Math.Abs(delta))
@ -209,12 +328,14 @@ namespace CtrEditor.ObjetosSim
return hzIncrementsRamp;
}
public void UpdateSpeed(float MaxRatedHz, float TiempoRampa, int elapsedMilliseconds)
public void UpdateSpeed(float max_Speed_for_Ramp, float TiempoRampa, int TotalMilliseconds)
{
// Calculamos la velocidad
STATUS_VFD_ACT_Speed_Hz += CalcSpeedRamp(MaxRatedHz, TiempoRampa, STATUS_VFD_ACT_Speed_Hz, OUT_OUT_VFD_REQ_Speed_Hz, elapsedMilliseconds);
STATUS_VFD_ACT_Speed_Hz += CalcSpeedRamp(max_Speed_for_Ramp, TiempoRampa, STATUS_VFD_ACT_Speed_Hz, OUT_OUT_VFD_REQ_Speed_Hz, TotalMilliseconds);
}
}
}

View File

@ -0,0 +1,23 @@
<UserControl x:Class="CtrEditor.ObjetosSim.Extraccion_Datos.ucBuscarCoincidencias"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos" mc:Ignorable="d"
Visibility="{Binding Show_On_This_Page, Converter={StaticResource BoolToVisibilityConverter}}">
<UserControl.DataContext>
<vm:osBuscarCoincidencias />
</UserControl.DataContext>
<Canvas>
<Rectangle x:Name="Area" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Opacity="{Binding Opacity_oculto}" Fill="Yellow" Stroke="Black">
<Rectangle.RenderTransform>
<RotateTransform Angle="{Binding Angulo}" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
</UserControl>

View File

@ -0,0 +1,752 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using System.IO;
using System.Windows.Media.Imaging;
using Emgu.CV.CvEnum;
using Emgu.CV;
using System.Drawing;
using Image = System.Windows.Controls.Image;
using Rectangle = System.Windows.Shapes.Rectangle;
using Size = System.Drawing.Size;
using Ookii.Dialogs.Wpf;
using Rect = System.Windows.Rect;
using System.ComponentModel;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;
using ClosedXML.Excel;
using Colors = System.Windows.Media.Colors;
using CtrEditor.FuncionesBase;
using System.Drawing;
using System.Windows.Shapes;
using System.Drawing.Imaging;
using Emgu.CV.Structure;
namespace CtrEditor.ObjetosSim.Extraccion_Datos
{
/// <summary>
/// Represents a template search control that identifies similar patterns in images and creates tag extraction clones.
/// This class is designed to work with OCR extraction by finding visual patterns and creating copies of extraction tags
/// at each matching location.
/// </summary>
/// <remarks>
/// Key functionalities:
/// - Template matching using OpenCV
/// - Automatic tag cloning at found locations
/// - OCR text extraction from matched regions
/// - Export capabilities to Excel
///
/// Workflow:
/// 1. User creates a search template by positioning and sizing the control over a pattern
/// 2. Links extraction tags to this template using Id_Search_Templates
/// 3. Activates search_templates to find similar patterns
/// 4. The system automatically:
/// - Searches for visual matches in the image
/// - Creates clones of linked extraction tags at each match
/// - Assigns incremental copy_Number to organize rows in exports
/// - Performs OCR on each cloned tag location
///
/// Properties:
/// - search_templates: Triggers the pattern search process
/// - threshold: Minimum similarity threshold for pattern matching
/// - coincidencias: Number of matches found (readonly)
/// - show_debug_ocr: Shows debug windows during OCR process
/// - export_ocr: Triggers OCR text export for all matches
///
/// Usage example:
/// 1. Position the search template over a repeating pattern
/// 2. Create extraction tags and link them to this template
/// 3. Set threshold value (default usually works well)
/// 4. Activate search_templates to find matches and create clones
/// </remarks>
public partial class osBuscarCoincidencias : osBase, IosBase
{
[JsonIgnore]
public float offsetY;
[JsonIgnore]
public float offsetX;
[ObservableProperty]
List<Rect> search_rectangles;
public static string NombreClase()
{
return "Buscador de Plantillas";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Ejecutar búsqueda de plantillas")]
[property: Name("Buscar Plantillas")]
bool search_templates;
partial void OnSearch_templatesChanged(bool oldValue, bool newValue)
{
if (Search_templates)
BuscarCoincidencias();
Search_templates = false;
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Capturar imagen de referencia")]
[property: Name("Tomar Clip")]
bool tomarClip;
// En lugar de almacenar Mat directamente, guardaremos una representación serializable
[ObservableProperty]
[property: Category("Información")]
[property: Description("Datos de región capturada")]
[property: Name("Datos Región")]
byte[] capturedRegionData;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Indica si la región fue capturada")]
[property: Name("Región Capturada")]
[property: ReadOnly(true)]
bool regionCapturada;
// Para uso interno (no serializado)
[JsonIgnore]
private Mat _capturedRegion;
// Propiedades para almacenar las dimensiones de la captura
[ObservableProperty]
[property: Category("Información")]
[property: Description("Ancho de región capturada")]
[property: Name("Ancho Capturado")]
int capturedWidth;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Alto de región capturada")]
[property: Name("Alto Capturado")]
int capturedHeight;
[ObservableProperty]
[property: Category("Datos")]
[property: Description("Exportar datos OCR")]
[property: Name("Exportar OCR")]
bool export_ocr;
[ObservableProperty]
[property: Category("Datos")]
[property: Description("Texto exportado OCR")]
[property: Name("Texto Export OCR")]
string text_export_ocr;
// Esta propiedad manejará la conversión entre Mat y datos serializables
[JsonIgnore]
public Mat CapturedRegion
{
get
{
if (_capturedRegion == null && CapturedRegionData != null && CapturedRegionData.Length > 0)
{
// Recrear Mat desde los datos almacenados
_capturedRegion = BytesToMat(CapturedRegionData, CapturedWidth, CapturedHeight);
}
return _capturedRegion;
}
set
{
if (value != null)
{
// Convertir Mat a bytes para serialización
CapturedRegionData = MatToBytes(value);
CapturedWidth = value.Width;
CapturedHeight = value.Height;
_capturedRegion = value;
}
else
{
CapturedRegionData = null;
CapturedWidth = 0;
CapturedHeight = 0;
_capturedRegion = null;
}
}
}
partial void OnTomarClipChanged(bool oldValue, bool newValue)
{
if (tomarClip)
{
CapturarRegionActual();
TomarClip = false; // Resetear el flag después de la captura
}
}
// Método para capturar la región actual
private void CapturarRegionActual()
{
Application.Current.Dispatcher.Invoke(() =>
{
if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo)
{
if (imagenDeFondo.Source is BitmapSource bitmapSource)
{
// Obtener los DPI de la imagen original
float originalDpiX = (float)bitmapSource.DpiX;
float originalDpiY = (float)bitmapSource.DpiY;
// Estándar DPI en el que el Canvas renderiza la imagen
float canvasDpiX = 96;
float canvasDpiY = 96;
// Calcular el ratio de escala entre el Canvas y la imagen original
float scaleFactorX = originalDpiX / canvasDpiX;
float scaleFactorY = originalDpiY / canvasDpiY;
// Ajustar las coordenadas de recorte en función del ratio de escala
int x = (int)MeterToPixels(Left * scaleFactorX);
int y = (int)MeterToPixels(Top * scaleFactorY);
int width = (int)MeterToPixels(Ancho * scaleFactorX);
int height = (int)MeterToPixels(Alto * scaleFactorY);
// Validar y ajustar el tamaño del recorte para que se mantenga dentro de los límites de la imagen
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x + width > bitmapSource.PixelWidth) width = bitmapSource.PixelWidth - x;
if (y + height > bitmapSource.PixelHeight) height = bitmapSource.PixelHeight - y;
// Recortar el área deseada utilizando las coordenadas ajustadas
CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height));
// Capturar la región y almacenarla
if (_capturedRegion != null)
{
_capturedRegion.Dispose(); // Liberar recursos previos
}
// Usar la propiedad que maneja la serialización
CapturedRegion = BitmapSourceToMat(croppedBitmap);
// Actualizar el estado
RegionCapturada = true;
MessageBox.Show("Región capturada correctamente.", "Información", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
});
}
// Métodos para convertir entre Mat y bytes
private byte[] MatToBytes(Mat mat)
{
if (mat == null)
return null;
// Asegurar que tenemos un formato consistente para serialización
Mat bgr = new Mat();
if (mat.NumberOfChannels != 3)
{
CvInvoke.CvtColor(mat, bgr, mat.NumberOfChannels == 1 ?
ColorConversion.Gray2Bgr : ColorConversion.Bgra2Bgr);
}
else
{
bgr = mat.Clone();
}
// Convertir a un formato que pueda ser serializado
using (MemoryStream ms = new MemoryStream())
{
// Convertir Mat a Bitmap
Bitmap bitmap = bgr.ToBitmap();
// Guardar como PNG (sin pérdida de calidad)
bitmap.Save(ms, ImageFormat.Png);
// Liberar recursos
bitmap.Dispose();
if (bgr != mat)
bgr.Dispose();
return ms.ToArray();
}
}
private Mat BytesToMat(byte[] bytes, int width, int height)
{
if (bytes == null || bytes.Length == 0)
return null;
try
{
using (MemoryStream ms = new MemoryStream(bytes))
{
// Cargar imagen desde bytes
Bitmap bitmap = (Bitmap)System.Drawing.Image.FromStream(ms);
// Convertir Bitmap a Mat
Image<Bgr, byte> img = bitmap.ToImage<Bgr, byte>();
// Liberar recursos
bitmap.Dispose();
// Si las dimensiones no coinciden, redimensionar
if (img.Width != width || img.Height != height)
{
CvInvoke.Resize(img.Mat, img.Mat, new Size(width, height));
}
return img.Mat;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error al reconstruir Mat: {ex.Message}");
return null;
}
}
// Sobrescribir los métodos de cambio de tamaño para limpiar la región capturada
public override void AnchoChanged(float newValue)
{
base.AnchoChanged(newValue);
//LimpiarRegionCapturada();
}
public override void AltoChanged(float newValue)
{
base.AnchoChanged(newValue);
// LimpiarRegionCapturada();
}
private void LimpiarRegionCapturada()
{
if (_capturedRegion != null)
{
_capturedRegion.Dispose();
_capturedRegion = null;
}
// Usar la propiedad para manejar la serialización
CapturedRegion = null;
RegionCapturada = false;
}
partial void OnExport_ocrChanged(bool value)
{
if (Export_ocr)
{
Text_export_ocr = "";
if (!string.IsNullOrEmpty(Nombre) && _mainViewModel != null)
{
foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
{
if (objetoSimulable != this && objetoSimulable.Group_Panel == Nombre)
{
if (objetoSimulable is osExtraccionTag osExtraccionTag)
{
osExtraccionTag.CaptureImageAreaAndDoOCR();
Text_export_ocr += osExtraccionTag.Tag_extract;
}
}
}
}
}
Export_ocr = false;
}
public override void TopChanging(float oldValue, float newValue)
{
offsetY = newValue - oldValue;
}
public override void LeftChanging(float oldValue, float newValue)
{
offsetX = newValue - oldValue;
}
[ObservableProperty]
[property: Category("Tag Extraction:")]
string tag_extract;
[ObservableProperty]
[property: Category("Tag Extraction:")]
string clase;
[ObservableProperty]
[property: Category("Tag Extraction:")]
string tag_name;
[ObservableProperty]
float opacity_oculto;
[ObservableProperty]
[property: Category("Tag Extraction:")]
bool show_debug_ocr;
[ObservableProperty]
[property: Category("Tag Extraction:")]
float threshold;
[ObservableProperty]
[property: Category("Tag Extraction:")]
[property: ReadOnly(true)]
int coincidencias;
public osBuscarCoincidencias()
{
Ancho = 1;
Alto = 1;
Angulo = 0;
Opacity_oculto = 0.1f;
Threshold = 0.6f;
}
private void ShowPreviewWindow(Stream imageStream)
{
// Create a new window for preview
Window previewWindow = new Window
{
Title = "Preview Captured Image",
Width = 500,
Height = 500,
Content = new Image
{
Source = BitmapFrame.Create(imageStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad),
Stretch = Stretch.Uniform
}
};
previewWindow.ShowDialog();
}
public async void BuscarCoincidencias()
{
var progressDialog = new ProgressDialog
{
WindowTitle = "Procesando",
Text = "Buscando coincidencias...",
ShowTimeRemaining = true,
ShowCancelButton = false
};
progressDialog.DoWork += (sender, e) => BuscarCoincidenciasAsync(progressDialog);
progressDialog.RunWorkerCompleted += (sender, e) =>
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
};
progressDialog.Show();
}
// Modificar el método BuscarCoincidenciasAsync para usar la región capturada si está disponible
private void BuscarCoincidenciasAsync(ProgressDialog progressDialog)
{
// Reset the Canvas children
Application.Current.Dispatcher.Invoke(() =>
{
var clearShapes = _mainViewModel.MainCanvas.Children.OfType<System.Windows.Shapes.Shape>().Where(s => s.Tag as string == "BuscarCoincidencias").ToList();
foreach (var shape in clearShapes)
{
_mainViewModel.MainCanvas.Children.Remove(shape);
}
if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo)
{
// Asegurarse de que la imagen origen está disponible
if (imagenDeFondo.Source is BitmapSource bitmapSource)
{
progressDialog.ReportProgress(10);
Mat templateMat;
int width, height;
float scaleFactorX, scaleFactorY;
bool deleteTemplateMat = false;
// Usar la región capturada si existe, de lo contrario capturar la región actual
if (CapturedRegion != null && RegionCapturada)
{
// Usar la región almacenada (ya deserializada)
templateMat = CapturedRegion.Clone();
deleteTemplateMat = true;
width = templateMat.Width;
height = templateMat.Height;
// Para mantener la relación con la imagen original
float originalDpiX = (float)bitmapSource.DpiX;
float originalDpiY = (float)bitmapSource.DpiY;
float canvasDpiX = 96;
float canvasDpiY = 96;
scaleFactorX = originalDpiX / canvasDpiX;
scaleFactorY = originalDpiY / canvasDpiY;
}
else
{
// Obtener los DPI de la imagen original
float originalDpiX = (float)bitmapSource.DpiX;
float originalDpiY = (float)bitmapSource.DpiY;
// Estándar DPI en el que el Canvas renderiza la imagen
float canvasDpiX = 96;
float canvasDpiY = 96;
// Calcular el ratio de escala entre el Canvas y la imagen original
scaleFactorX = originalDpiX / canvasDpiX;
scaleFactorY = originalDpiY / canvasDpiY;
// Ajustar las coordenadas de recorte en función del ratio de escala
int x = (int)MeterToPixels(Left * scaleFactorX);
int y = (int)MeterToPixels(Top * scaleFactorY);
width = (int)MeterToPixels(Ancho * scaleFactorX);
height = (int)MeterToPixels(Alto * scaleFactorY);
// Validar y ajustar el tamaño del recorte para que se mantenga dentro de los límites de la imagen
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x + width > bitmapSource.PixelWidth) width = bitmapSource.PixelWidth - x;
if (y + height > bitmapSource.PixelHeight) height = bitmapSource.PixelHeight - y;
// Recortar el área deseada utilizando las coordenadas ajustadas
CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height));
// Convertir CroppedBitmap a Mat directamente
templateMat = BitmapSourceToMat(croppedBitmap);
deleteTemplateMat = true;
}
int scale = 4;
// Convertir la plantilla a escala de grises y redimensionarla
Mat templateGray = new Mat();
CvInvoke.CvtColor(templateMat, templateGray, ColorConversion.Bgr2Gray);
Mat templateGrayResized = new Mat();
CvInvoke.Resize(templateGray, templateGrayResized, new Size(templateGray.Width / scale, templateGray.Height / scale), 0, 0, Inter.Linear);
progressDialog.ReportProgress(20);
// El resto del código permanece igual...
// Cargar la imagen principal completa en un Mat
Mat mainImageMat = BitmapSourceToMat(bitmapSource);
// Convertir la imagen principal a escala de grises y redimensionarla
Mat mainImageGray = new Mat();
CvInvoke.CvtColor(mainImageMat, mainImageGray, ColorConversion.Bgr2Gray);
Mat mainImageGrayResized = new Mat();
CvInvoke.Resize(mainImageGray, mainImageGrayResized, new Size(mainImageGray.Width / scale, mainImageGray.Height / scale), 0, 0, Inter.Linear);
progressDialog.ReportProgress(50);
// Realizar la coincidencia de plantillas
Mat result = new Mat();
CvInvoke.MatchTemplate(mainImageGray, templateGray, result, TemplateMatchingType.CcoeffNormed);
// Establecer un umbral de coincidencia
if (Threshold < 0.4)
Threshold = 0.4f;
double threshold = Threshold;
int ConteoPositivos = 0;
// Lista para mantener áreas ya aceptadas
if (search_rectangles != null)
search_rectangles.Clear();
else
search_rectangles = new List<Rect>();
// Obtener los puntos que superan el umbral
float[] resultArray = result.GetData(false) as float[];
if (resultArray != null)
{
for (int i = 0; i < resultArray.Length; i++)
{
if (resultArray[i] >= threshold)
{
int row = i / result.Cols;
int col = i % result.Cols;
Rect newRect = new Rect();
newRect.X = col / scaleFactorX;
newRect.Y = row / scaleFactorY;
newRect.Width = width / scaleFactorX;
newRect.Height = height / scaleFactorY;
// Crear un rectángulo para la coincidencia actual
Rectangle matchRect = new Rectangle
{
Stroke = new SolidColorBrush(Colors.Red),
StrokeThickness = 2,
Width = newRect.Width,
Height = newRect.Height,
Tag = "BuscarCoincidencias"
};
Canvas.SetLeft(matchRect, newRect.X);
Canvas.SetTop(matchRect, newRect.Y);
// Verificar si la coincidencia actual está dentro de algún rectángulo aceptado
bool isOverlap = search_rectangles.Any(r =>
r.IntersectsWith(newRect)
);
// Si no hay superposición, agregar el rectángulo al Canvas y a la lista de aceptados
if (!isOverlap)
{
Canvas.SetZIndex(matchRect, 40);
_mainViewModel.MainCanvas.Children.Add(matchRect);
search_rectangles.Add(newRect);
ConteoPositivos++;
Coincidencias = ConteoPositivos;
progressDialog.ReportProgress(90);
if (ConteoPositivos > 20)
{
// Liberar recursos antes de salir
if (deleteTemplateMat) templateMat.Dispose();
templateGray.Dispose();
templateGrayResized.Dispose();
mainImageMat.Dispose();
mainImageGray.Dispose();
mainImageGrayResized.Dispose();
result.Dispose();
return;
}
}
}
}
PopularTagExtraction();
}
// Limpiar recursos
if (deleteTemplateMat) templateMat.Dispose();
templateGray.Dispose();
templateGrayResized.Dispose();
mainImageMat.Dispose();
mainImageGray.Dispose();
mainImageGrayResized.Dispose();
result.Dispose();
}
}
});
}
public void PopularTagExtraction()
{
var objetosSimulablesCopy = new List<osBase>(_mainViewModel.ObjetosSimulables);
foreach (var obj in objetosSimulablesCopy)
if (obj is osExtraccionTag objExtraccionTag)
if (objExtraccionTag.Id_Search_Templates == this.Nombre && objExtraccionTag.Cloned)
_mainViewModel.RemoverObjetoSimulable(objExtraccionTag);
var objetosSimulables2Copy = new List<osBase>(_mainViewModel.ObjetosSimulables);
// Saltar el primer rectángulo en el foreach
int Row = 0;
foreach (var rectangle in search_rectangles) //.Skip(1))
{
float offsetX = PixelsToMeters((float)rectangle.X) - Left;
float offsetY = PixelsToMeters((float)rectangle.Y) - Top;
osExtraccionTag newObj = null;
foreach (var eTag in objetosSimulables2Copy)
{
if (eTag is osExtraccionTag objExtraccionTag)
{
if (objExtraccionTag.Id_Search_Templates == this.Nombre)
{
newObj = (osExtraccionTag)_mainViewModel.DuplicarObjeto(objExtraccionTag, offsetX, offsetY);
if (newObj != null)
{
newObj.Cloned = true;
newObj.Cloned_from = objExtraccionTag.Id;
newObj.Copy_Number = Row;
newObj.Enable_On_All_Pages = false;
if (newObj.Extraer)
objExtraccionTag.CaptureImageAreaAndDoOCR();
}
}
}
}
Row++;
}
}
public static int FindFirstEmptyRow(IXLWorksheet worksheet)
{
var lastRowUsed = worksheet.LastRowUsed();
return lastRowUsed == null ? 1 : lastRowUsed.RowNumber() + 1;
}
// Método para convertir BitmapSource a Mat
private Mat BitmapSourceToMat(BitmapSource bitmapSource)
{
if (bitmapSource == null)
throw new ArgumentNullException(nameof(bitmapSource));
// Convierte BitmapSource a Bitmap
Bitmap bitmap;
using (MemoryStream outStream = new MemoryStream())
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bitmapSource));
enc.Save(outStream);
bitmap = new Bitmap(outStream);
}
// Convierte directamente a Mat usando Image<Bgr, byte>
Image<Bgr, byte> image = bitmap.ToImage<Bgr, byte>();
return image.Mat;
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
}
}
public partial class ucBuscarCoincidencias : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucBuscarCoincidencias()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -0,0 +1,35 @@
<UserControl x:Class="CtrEditor.ObjetosSim.Extraccion_Datos.ucExtraccionTag"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos"
mc:Ignorable="d"
Visibility="{Binding Show_On_This_Page, Converter={StaticResource BoolToVisibilityConverter}}">
<UserControl.DataContext>
<vm:osExtraccionTag/>
</UserControl.DataContext>
<Canvas>
<Rectangle x:Name="Area" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Opacity="{Binding Opacity_oculto}" Fill="Green" Stroke="Black">
</Rectangle>
<Viewbox Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}">
<Label Content="{Binding Angulo}" VerticalAlignment="Top" HorizontalAlignment="Center"
RenderTransformOrigin="0.5,0.5" Opacity="0.1">
<Label.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform Angle="{Binding Angulo}" />
<TranslateTransform />
</TransformGroup>
</Label.RenderTransform>
</Label>
</Viewbox>
</Canvas>
</UserControl>

View File

@ -0,0 +1,300 @@
using ClosedXML.Excel;
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
namespace CtrEditor.ObjetosSim.Extraccion_Datos
{
/// <summary>
/// Interaction logic for ucExtraccionTag.xaml
/// </summary>
///
public partial class osExtraccionTag : osBase, IosBase
{
public static string NombreClase()
{
return "Extractor de Tags";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set
{
if (Collumn_name == null || Collumn_name.Length == 0)
Collumn_name = value;
SetProperty(ref nombre, value);
}
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Ejecutar extracción de tag")]
[property: Name("Extraer")]
bool extraer;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Eliminar saltos de línea del texto extraído")]
[property: Name("Eliminar Enter")]
bool eliminar_enters;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Idioma para reconocimiento OCR")]
[property: Name("Idioma OCR")]
[property: ItemsSource(typeof(IdiomasItemsSource<Idiomas>))]
string idioma_Extraccion;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Patrón de extracción de texto")]
[property: Name("Tipo de Patrón")]
[property: ItemsSource(typeof(TagPatternItemsSource<TagPattern>))]
string pattern_Type;
public override void TopChanged(float value)
{
base.TopChanged(value);
if (Extraer) ResetTimer();
}
public override void LeftChanged(float value)
{
base.LeftChanged(value);
if (Extraer) ResetTimer();
}
partial void OnExtraerChanged(bool value)
{
if (Extraer)
ResetTimer();
}
public override void OnResize(float Delta_Width, float Delta_Height)
{
if (Extraer)
ResetTimer();
}
public override void OnMove(float LeftPixels, float TopPixels)
{
if (Extraer)
ResetTimer();
}
public override void OnRotate(float Angle)
{
if (Extraer)
ResetTimer();
}
public override void OnTimerAfterMovement()
{
Angulo = (float)Math.Round(Angulo / 90) * 90;
if (Extraer)
CaptureImageAreaAndDoOCR();
Extraer = false;
}
private osBuscarCoincidencias Search_Templates;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Plantillas de búsqueda asociadas")]
[property: Name("Plantillas Búsqueda")]
[property: ItemsSource(typeof(osBaseItemsSource<osBuscarCoincidencias>))]
string id_Search_Templates;
partial void OnId_Search_TemplatesChanged(string value)
{
if (Search_Templates != null)
Search_Templates.PropertyChanged -= OnMotorPropertyChanged;
if (_mainViewModel != null && value != null && value.Length > 0)
{
Search_Templates = (osBuscarCoincidencias)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osBuscarCoincidencias && s.Nombre == value), null);
if (Search_Templates != null)
Search_Templates.PropertyChanged += OnMotorPropertyChanged;
}
}
private void OnMotorPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(osBuscarCoincidencias.Nombre))
{
Id_Search_Templates = ((osBuscarCoincidencias)sender).Nombre;
}
}
[ObservableProperty]
[property: Category("Datos")]
[property: Description("Texto extraído mediante OCR")]
[property: Name("Tag Extraído")]
string tag_extract;
[ObservableProperty]
[property: Category("Datos")]
[property: Description("Clase del objeto para exportación")]
[property: Name("Clase")]
string clase;
[ObservableProperty]
[property: Category("Datos")]
[property: Description("Nombre de la columna para exportación")]
[property: Name("Nombre Columna")]
string collumn_name;
[ObservableProperty]
[property: Category("Datos")]
[property: Description("Número de columna Excel")]
[property: Name("Número Columna")]
int collumn_number;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Número de copia del objeto")]
[property: Name("Número de Copia")]
[property: ReadOnly(true)]
int copy_Number;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Mostrar ventana de depuración OCR")]
[property: Name("Mostrar Debug")]
bool show_Debug_Window;
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Opacidad del objeto")]
[property: Name("Opacidad")]
float opacity_oculto;
public osExtraccionTag()
{
Ancho = 1;
Alto = 1;
Angulo = 0;
Opacity_oculto = 0.1f;
Idioma_Extraccion = Idiomas.DEFAULT_LANGUAGE;
Pattern_Type = TagPattern.DEFAULT_PATTERN;
Eliminar_enters = true;
}
public void CaptureImageAreaAndDoOCR()
{
string extractedText = CaptureImageAreaAndDoOCRPPaddle(Left, Top, Ancho, Alto, Angulo, Show_Debug_Window);
// Clean up the extracted text if eliminar_enters is true
if (Eliminar_enters && !string.IsNullOrEmpty(extractedText))
{
// Replace all types of line endings with spaces
extractedText = extractedText.Replace("\r\n", " ")
.Replace("\n", " ")
.Replace("\r", " ");
// Replace multiple consecutive spaces with a single space
extractedText = System.Text.RegularExpressions.Regex.Replace(extractedText, @"\s+", " ");
// Trim spaces at the beginning and end
extractedText = extractedText.Trim();
}
// Apply the selected pattern
extractedText = TagPattern.ApplyPattern(extractedText, Pattern_Type);
Tag_extract = extractedText;
// Set default language to English if not set
if (string.IsNullOrEmpty(Idioma_Extraccion))
{
Idioma_Extraccion = Idiomas.DEFAULT_LANGUAGE;
}
}
public int ExportToExcel(IXLWorksheet worksheet, int row, int colBase)
{
// Agregar Tag
worksheet.Cell(row + 2, Collumn_number + colBase).Value = Tag_extract;
return Collumn_number + colBase;
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
OnId_Search_TemplatesChanged(Id_Search_Templates); // Actualizar Link
}
}
public partial class ucExtraccionTag : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucExtraccionTag()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Resize(float width, float height)
{
if (Datos is osExtraccionTag datos)
{
datos.Ancho += PixelToMeter.Instance.calc.PixelsToMeters(width);
datos.Alto += PixelToMeter.Instance.calc.PixelsToMeters(height);
}
}
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public float Angle()
{
if (Datos != null)
if (Datos is osExtraccionTag datos)
return datos.Angulo;
return 0f;
}
public void Rotate(float Angle)
{
if (Datos != null)
if (Datos is osExtraccionTag datos)
datos.Angulo += Angle;
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.ExtraccionTag;
}
}
}

View File

@ -4,20 +4,23 @@
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:ei="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:convert="clr-namespace:CtrEditor.Convertidores"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
</UserControl.Resources>
<UserControl.DataContext>
<vm:osBoton Color="#FFADE6C0" ColorButton_oculto="#FFC72323"/>
<vm:osBoton Color="#FFADE6C0" ColorButton="#FFC72323" Color_Pressed="#FF9A9A9A" />
</UserControl.DataContext>
<Grid>
<Canvas RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="{Binding Angulo}"/>
<TranslateTransform/>
</TransformGroup>
</Canvas.RenderTransform>
<Border x:Name="BackgroundRectangle"
BorderBrush="Black"
BorderThickness="2"
@ -26,16 +29,24 @@
Height="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=1.5}"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Background="Gray"/>
<Ellipse Fill="{Binding ColorButton_oculto}"
Background="{Binding Color_Pressed, Converter={StaticResource ColorToBrushConverter}}"/>
<Ellipse Fill="{Binding ColorButton, Converter={StaticResource ColorToBrushConverter}}"
Stroke="Black"
StrokeThickness="2"
Width="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
Canvas.Top="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"
MouseLeftButtonUp="Ellipse_MouseLeftButtonUp">
</Ellipse>
</Grid>
<Viewbox Height="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"
Width="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}">
<Label Content="{Binding Button_Name}"
VerticalAlignment="Top" HorizontalAlignment="Center"
Foreground="{Binding Color_Titulo, Converter={StaticResource ColorToBrushConverter}}"/>
</Viewbox>
</Canvas>
</UserControl>

View File

@ -1,10 +1,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.Convertidores;
using CtrEditor.Siemens;
using LibS7Adv;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
@ -17,9 +18,13 @@ namespace CtrEditor.ObjetosSim
public static string NombreClase()
{
return "Boton";
return "Botón";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
@ -27,31 +32,89 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Texto mostrado en el botón")]
[property: Name("Texto del Botón")]
string button_Name;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Tipo normalmente cerrado")]
[property: Name("Normalmente Cerrado")]
public bool tipo_NC;
[ObservableProperty]
private Brush color;
[property: Hidden]
[property: Category("Apariencia")]
[property: Description("Color cuando está presionado")]
[property: Name("Color Presionado")]
Color color_Pressed;
[ObservableProperty]
private Brush colorButton_oculto;
[property: Category("Apariencia")]
[property: Description("Color del texto del botón")]
[property: Name("Color del Texto")]
Color color_Titulo;
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color principal del botón")]
[property: Name("Color")]
Color color;
partial void OnColorChanged(Color value)
{
OnEstadoChanged(Estado);
}
[ObservableProperty]
[property: Hidden]
[property: Category("Apariencia")]
[property: Description("Color actual del botón")]
[property: Name("Color Actual")]
private Color colorButton;
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Tamaño visual del botón")]
[property: Name("Tamaño")]
public float tamano;
public override void OnResize(float Delta_Width, float Delta_Height)
{
Tamano += Delta_Width + Delta_Height;
}
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Estado actual del botón")]
[property: Name("Estado")]
public bool estado;
partial void OnEstadoChanged(bool value)
{
if (value)
ColorButton_oculto = Brushes.LightGreen;
Color_Pressed = Colors.LightGreen;
else
ColorButton_oculto = Color;
if (!tipo_NC)
Color_Pressed = Colors.Gray;
if (!Tipo_NC)
EscribirBitTag(Tag, value);
else
EscribirBitTag(Tag, !value);
}
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Dirección del tag en el PLC")]
[property: Name("Tag PLC")]
public string tag;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag para controlar luz indicadora")]
[property: Name("Tag Luz")]
public string tag_Luz;
public void ButtonDownCommand()
{
Estado = true;
@ -67,7 +130,10 @@ namespace CtrEditor.ObjetosSim
Estado = false;
Tag = "M50.0";
// Set initial color
Color = Brushes.LightBlue;
Color = Colors.LightBlue;
color_Titulo = Colors.Black;
button_Name = "TAG";
Color_Pressed = Colors.Gray;
}
public override void UpdateGeometryStart()
@ -75,9 +141,19 @@ namespace CtrEditor.ObjetosSim
// Se llama antes de la simulacion
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds)
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (LeerBitTag(Tag_Luz))
ColorButton = ObtenerColorMasClaroYSaturado(Color, 0.3, 0.5);
else ColorButton = Color;
}
public override void UpdatePLCPrimerCiclo()
{
// Escribimos el valor actual al iniciar la conexion
// Esto es util para NC principalmente
OnEstadoChanged(Estado);
}
public override void UpdateControl(int elapsedMilliseconds)
@ -88,7 +164,8 @@ namespace CtrEditor.ObjetosSim
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
base.ucLoaded();
ColorButton = Color;
}
public override void ucUnLoaded()
{
@ -101,6 +178,7 @@ namespace CtrEditor.ObjetosSim
public partial class ucBoton : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucBoton()
{
@ -140,21 +218,11 @@ namespace CtrEditor.ObjetosSim
e.Handled = true; // Evita que el evento se propague
}
}
public void Resize(float width, float height) { }
public void Move(float LeftPixels, float TopPixels)
{
if (Datos != null)
{
Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
}
}
public void Rotate(float Angle) { }
public void Highlight(bool State) { }
public int ZIndex()
public ZIndexEnum ZIndex_Base()
{
return 10;
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -0,0 +1,16 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucEncoderMotor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osEncoderMotor />
</UserControl.DataContext>
<Grid Width="50" Height="50">
<Ellipse Fill="{Binding Color_oculto}" Stroke="DarkGray" StrokeThickness="2" Width="30" Height="30" />
</Grid>
</UserControl>

View File

@ -0,0 +1,173 @@
// EncoderMotorViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using System.Windows.Media;
using System.ComponentModel;
using LibS7Adv;
using System.Diagnostics;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CtrEditor.FuncionesBase;
using System.Windows;
using System.Windows.Controls;
namespace CtrEditor.ObjetosSim
{
public partial class osEncoderMotor : osBase, IosBase
{
private osBase Motor = null;
public static string NombreClase()
{
return "Encoder de Motor";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color visual del encoder")]
[property: Name("Color")]
private Brush color_oculto;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Velocidad actual del motor")]
[property: Name("Velocidad Actual")]
public float velocidadActual;
[ObservableProperty]
[property: Category("Encoder")]
[property: Description("Pulsos por vuelta del encoder")]
[property: Name("Pulsos por Vuelta")]
public float pulsos_Por_Vuelta;
[ObservableProperty]
[property: Category("Encoder")]
[property: Description("Ratio de giros por 50Hz")]
[property: Name("Ratio Giros 50Hz")]
public float ratio_Giros_50hz;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Valor actual del encoder")]
[property: Name("Valor Actual")]
public float valor_Actual;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Motor enlazado al encoder")]
[property: Name("Motor Enlazado")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_Motor;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag PLC para valor del encoder")]
[property: Name("Tag Valor")]
string tag_Valor;
partial void OnId_MotorChanged(string value)
{
if (Motor != null)
Motor.PropertyChanged -= OnMotorPropertyChanged;
if (_mainViewModel != null && value != null && value.Length > 0)
{
Motor = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osVMmotorSim && s.Nombre == value), null);
if (Motor != null)
Motor.PropertyChanged += OnMotorPropertyChanged;
}
}
private void OnMotorPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_Motor = ((osVMmotorSim)sender).Nombre;
}
}
public osEncoderMotor()
{
Pulsos_Por_Vuelta = 360; // Por defecto, un pulso por grado
Ratio_Giros_50hz = 1; // Por defecto, 1 giro por cada 50Hz
Color_oculto = Brushes.Gray;
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (Motor != null && Motor is osVMmotorSim motor)
{
VelocidadActual = motor.Velocidad;
// Calcular giros por segundo basado en la velocidad actual
// velocidad * ratio_giros_50hz / 100 nos da los giros por segundo a esa velocidad
float girosPorSegundo = (VelocidadActual * Ratio_Giros_50hz) / 100f;
// Considerar el sentido de giro
if (motor.Sentido_contrario)
girosPorSegundo = -girosPorSegundo;
// Calcular incremento de pulsos para este ciclo
float segundosTranscurridos = elapsedMilliseconds / 1000f;
float incrementoPulsos = girosPorSegundo * Pulsos_Por_Vuelta * segundosTranscurridos;
// Actualizar valor del encoder
Valor_Actual = (Valor_Actual + incrementoPulsos) % Pulsos_Por_Vuelta;
if (Valor_Actual < 0) Valor_Actual += Pulsos_Por_Vuelta;
// Actualizar color basado en si está girando
Color_oculto = Math.Abs(girosPorSegundo) > 0.01f ? Brushes.LightGreen : Brushes.Gray;
// Escribir valor al PLC si hay tag configurado
if (!string.IsNullOrEmpty(Tag_Valor))
{
EscribirWordTagScaled(Tag_Valor, Valor_Actual, 0, Pulsos_Por_Vuelta, 0, 27648);
}
}
}
public override void ucLoaded()
{
base.ucLoaded();
OnId_MotorChanged(Id_Motor);
}
}
public partial class ucEncoderMotor : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucEncoderMotor()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
}
}

View File

@ -0,0 +1,16 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucEncoderMotorLineal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osEncoderMotorLineal />
</UserControl.DataContext>
<Grid Width="50" Height="50">
<Ellipse Fill="{Binding Color_oculto}" Stroke="DarkGray" StrokeThickness="2" Width="30" Height="30" />
</Grid>
</UserControl>

View File

@ -0,0 +1,184 @@
// EncoderMotorLinealViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using System.Windows.Media;
using System.ComponentModel;
using LibS7Adv;
using System.Diagnostics;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CtrEditor.FuncionesBase;
using System.Windows;
using System.Windows.Controls;
namespace CtrEditor.ObjetosSim
{
public partial class osEncoderMotorLineal : osBase, IosBase
{
private osBase Motor = null;
public static string NombreClase()
{
return "Encoder Motor Lineal";
}
private string nombre = NombreClase();
[property: Category("Identificación")]
[property: Description("Nombre identificativo del objeto")]
[property: Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[ObservableProperty]
[property: Category("Apariencia")]
[property: Description("Color visual del encoder")]
[property: Name("Color")]
private Brush color_oculto;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Velocidad actual del motor")]
[property: Name("Velocidad Actual")]
public float velocidadActual;
[ObservableProperty]
[property: Category("Encoder")]
[property: Description("Pulsos por Hz por segundo (K)")]
[property: Name("Pulsos por Hz")]
public float pulsos_Por_Hz;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Valor actual del encoder")]
[property: Name("Valor Actual")]
public float valor_Actual;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Motor enlazado al encoder")]
[property: Name("Motor Enlazado")]
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_Motor;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag PLC para escribir valor del encoder")]
[property: Name("Tag Valor Escritura")]
string tag_Valor;
[ObservableProperty]
[property: Category("Enlace PLC")]
[property: Description("Tag PLC para leer valor del encoder (tiene prioridad sobre motor)")]
[property: Name("Tag Valor Lectura")]
string tag_ReadValor;
partial void OnId_MotorChanged(string value)
{
if (Motor != null)
Motor.PropertyChanged -= OnMotorPropertyChanged;
if (_mainViewModel != null && value != null && value.Length > 0)
{
Motor = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osVMmotorSim && s.Nombre == value), null);
if (Motor != null)
Motor.PropertyChanged += OnMotorPropertyChanged;
}
}
private void OnMotorPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
{
Id_Motor = ((osVMmotorSim)sender).Nombre;
}
}
public osEncoderMotorLineal()
{
Pulsos_Por_Hz = 3000; // Por defecto, un pulso por grado
Color_oculto = Brushes.Gray;
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
{
if (Tag_ReadValor != null && Tag_ReadValor.Length > 0)
{
// Bypass motor and read directly from tag
var value = LeerDINTTag(tag_ReadValor);
if (value != null)
{
Valor_Actual = (int)value;
return;
}
}
else if (Motor != null && Motor is osVMmotorSim motor)
{
VelocidadActual = motor.Velocidad;
// Calcular giros por segundo basado en la velocidad actual
float pulsosPorHz = (VelocidadActual * Pulsos_Por_Hz) / 100f;
// Considerar el sentido de giro
if (motor.Sentido_contrario)
pulsosPorHz = -pulsosPorHz;
// Calcular incremento de pulsos para este ciclo
float segundosTranscurridos = elapsedMilliseconds / 1000f;
float incrementoPulsos = pulsosPorHz * segundosTranscurridos;
// Actualizar valor del encoder
if (motor.Motor_With_Encoder)
Valor_Actual = motor.Actual_Position;
else
Valor_Actual = (Valor_Actual + incrementoPulsos);
// Actualizar color basado en si está girando
Color_oculto = Math.Abs(pulsosPorHz) > 0.01f ? Brushes.LightGreen : Brushes.Gray;
// Escribir valor al PLC si hay tag configurado
if (!string.IsNullOrEmpty(Tag_Valor))
{
EscribirDINTTag(tag_Valor, (int)Valor_Actual);
}
}
}
public override void ucLoaded()
{
base.ucLoaded();
OnId_MotorChanged(Id_Motor);
}
}
public partial class ucEncoderMotorLineal : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucEncoderMotorLineal()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Datos?.ucLoaded();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Datos?.ucUnLoaded();
}
public void Highlight(bool State) { }
public ZIndexEnum ZIndex_Base()
{
return ZIndexEnum.Estaticos;
}
}
}

Some files were not shown because too many files have changed in this diff Show More