Compare commits

...

120 Commits

Author SHA1 Message Date
Miguel d1ec7f4d12 Se mejoró el sistema de guías curvas para incluir apertura en cono en los extremos, facilitando la entrada y salida de botellas. Se añadió un nuevo parámetro `AnguloAperturaGuias` para ajustar la apertura, optimizando el flujo de materiales. Además, se implementaron nuevas propiedades en `ucBottGenerator` para gestionar la distancia mínima sin botellas y se realizaron ajustes en la lógica de colisiones y visualización en 3D. 2025-07-06 00:07:36 +02:00
Miguel 83fc828a4c Se implementaron mejoras en la visualización y animación de transportes en movimiento, incluyendo un indicador visual que cambia de color según el estado de movimiento. Se añadió un sistema de animaciones automáticas utilizando StoryBoard de WPF, que combina rotación y pulsación de color para los transportes activos. Además, se ajustaron las propiedades de presión en las botellas para mejorar la gestión de fricción y se optimizó la lógica de animación y limpieza de objetos en la simulación. 2025-07-05 20:20:59 +02:00
Miguel c91b1419b4 Se implementó un sistema para controlar la presión y evitar que las botellas floten en la simulación. Ahora, las botellas registran su última altura de contacto con transportes y ajustan su posición Z si no están en contacto durante varios frames. Además, se centraron las botellas en el eje longitudinal de los transportes al entrar en contacto con transportes de frenado. Se añadieron nuevas propiedades en simBotella para gestionar la presión y el estado de contacto. 2025-07-05 18:43:28 +02:00
Miguel 71c08d8047 Se ajustaron los coeficientes de fricción y configuraciones de resorte en la simulación de botellas, diferenciando entre transportes con y sin freno. Además, se modificó la visibilidad de triángulos en la visualización 3D, mejorando la iluminación y corrigiendo la creación de mallas continuas a partir de triángulos de BEPU. Se eliminaron métodos obsoletos relacionados con la geometría de curvas, optimizando la lógica de creación de mallas. 2025-07-05 17:53:22 +02:00
Miguel eb6ed62d5b Eliminación del archivo "Memoria de evolución" y cambios en la creación de botellas en la simulación. Se reemplazó el método AddCircle por AddBotella en ucBotella y ucBotellaCuello, y se ajustaron propiedades en simBotella para mejorar la gestión de inercia y fricción. Se eliminaron referencias a barreras físicas en BEPU, optimizando la lógica de colisiones y ajustando parámetros de fricción para mejorar la simulación. 2025-07-05 17:04:39 +02:00
Miguel de0e3ce5f0 Eliminacion de LinearAxisMotor por solo velocity y fricciones normales en los cuerpos 2025-07-05 15:24:54 +02:00
Miguel 3eee0e3d9b Se ajustó el coeficiente de fricción en múltiples secciones del archivo BEPU, reduciéndolo de 0.3f a 0.01f para mejorar la simulación de transportes y curvas. Además, se corrigió el acceso al factor de conversión de velocidad, reemplazando la constante por una propiedad estática en simBase. Se eliminaron métodos obsoletos relacionados con la configuración de velocidad en simCurve y simTransporte, optimizando la lógica de actualización de motores. 2025-07-04 23:20:53 +02:00
Miguel a6cbd8c4ab Se realizaron mejoras en la gestión de barreras en el sistema de simulación. Se eliminó la dependencia de cuerpos físicos para las barreras, implementando un sistema de detección basado en RayCast. Se actualizaron los métodos de creación y actualización de barreras, así como su visualización en 3D, optimizando la representación del haz de luz. Además, se simplificó la lógica de detección y se eliminaron métodos obsoletos relacionados con la geometría de barreras. 2025-07-04 22:45:26 +02:00
Miguel 095228144a Se comentaron líneas de depuración en el archivo BEPU para mejorar la legibilidad y evitar la salida innecesaria de información durante la ejecución. Estos cambios no afectan la lógica del programa, pero optimizan el rendimiento al reducir el ruido en los registros de depuración. 2025-07-04 10:20:10 +02:00
Miguel ab8066d1e8 Se añadieron nuevas propiedades y métodos en BEPU para gestionar la velocidad de las curvas en metros por segundo, incluyendo la capacidad de invertir y detener la curva. Se simplificó la lógica de configuración de velocidad y se corrigió el uso de la propiedad SpeedMetersPerSecond en la creación de motores dinámicos. Además, se eliminaron comentarios y código obsoleto, optimizando la estructura del archivo. 2025-07-03 23:50:20 +02:00
Miguel f431ede7bd Se añadió una referencia al SimulationManagerBEPU en las clases simTransporte y simCurve. Se implementaron nuevos métodos para limpiar restricciones de botellas conectadas antes de eliminar cuerpos y para obtener botellas conectadas a un elemento específico. Además, se simplificó el método RemoverBody y se mejoró la gestión de restricciones internas, optimizando la lógica de eliminación y limpieza de datos. 2025-07-03 23:11:48 +02:00
Miguel a00183c4f6 Se corrigió el factor de conversión de velocidad en BEPU y se simplificó la creación de motores dinámicos, utilizando la dirección tangencial calculada. Se añadió un nuevo límite de distancia para curvas y se implementó la eliminación segura de este límite al remover motores. Además, se optimizó la lógica de actualización de motores, eliminando métodos obsoletos y mejorando la gestión de propiedades internas. 2025-07-03 21:56:25 +02:00
Miguel ba073a9e80 Se simplificó el método RemoverBody en simTransporte y simCurve, confiando en BEPU para la limpieza de constraints. Se implementaron nuevos métodos para la creación y actualización de motores, mejorando la gestión de motores dinámicos. Además, se optimizó la verificación de contacto entre botellas y transportes/curvas, y se introdujo un sistema de eliminación diferida para objetos, mejorando la seguridad y eficiencia del código. Se eliminaron métodos obsoletos relacionados con la depuración de triángulos en BEPU. 2025-07-03 17:47:21 +02:00
Miguel c1584e8d55 Se añadió una nueva librería para la simulación y se realizaron mejoras en la gestión de motores en simTransporte y simCurve. Se unificaron métodos para la creación de motores lineales y angulares, optimizando la lógica de detección de colisiones y la visualización de triángulos en 3D. Además, se corrigieron errores en la extracción de triángulos de BEPU, asegurando que se utilicen coordenadas locales para evitar transformaciones duplicadas. Se implementaron métodos para activar y desactivar el modo de depuración de triángulos, mejorando la experiencia de visualización. 2025-07-03 11:47:09 +02:00
Miguel 501f0ffb9b Implementación de un sistema simplificado para la gestión de motores en simCurve, unificando métodos y eliminando redundancias. Se introdujeron nuevas propiedades para el manejo de motores lineales y angulares, y se mejoró la creación de triángulos en la simulación. Además, se optimizó la visualización 3D extrayendo triángulos reales de BEPU y se añadieron métodos para depuración y verificación de triángulos válidos. Se eliminaron métodos y propiedades obsoletas, mejorando la eficiencia del código. 2025-07-03 01:47:46 +02:00
Miguel 4ff93a5802 Antes de cambios en simCurve 2025-07-02 23:39:22 +02:00
Miguel e38adc9f56 Se eliminaron archivos de documentación innecesarios en el proyecto y se realizaron ajustes en la visibilidad de métodos en la clase osBase, cambiando de privado a protegido. Se implementó un nuevo método para verificar las dimensiones de los descartes y se mejoró la lógica de actualización de geometrías en ucDescarte y ucGuia, utilizando un sistema inteligente para optimizar el rendimiento. Además, se corrigieron errores en la conversión de coordenadas y se restauró el factor de conversión de velocidad en BEPU. 2025-07-02 17:19:31 +02:00
Miguel fd215bc677 Intento con LinearAxisMotor 2025-07-02 15:17:50 +02:00
Miguel 3e53a51e8b Se añadió un comando para alternar la actualización 3D en MainViewModel, permitiendo habilitar o deshabilitar esta función para mejorar el rendimiento. Se implementó la lógica correspondiente en la visualización 3D y se eliminaron propiedades redundantes en varias clases, optimizando la gestión de objetos en la simulación. 2025-07-01 23:08:05 +02:00
Miguel 3773da0ee3 Se añadió un nuevo manager para la visualización 3D en MainWindow y se conectó con el simulation manager. Además, se implementaron métodos para manejar cambios de posición en ucTransporteCurvaGuias, actualizando la geometría en BEPU y sincronizando con la visualización 3D. Se mejoró la creación de guías desde dos puntos y se corrigieron errores en la actualización de curvas. 2025-07-01 20:02:23 +02:00
Miguel fbac81ec45 Codigo adaptado base aun errores 2025-07-01 19:47:10 +02:00
Miguel 121e586d53 Inicio de Migracion de 2D a 3D 2025-07-01 19:03:06 +02:00
Miguel d259f53081 Antes de cambiar Motor de Fisica 2025-06-26 19:25:44 +02:00
Miguel 2cb90ec2dc Se añadieron configuraciones para Hot Reload en CtrEditor.csproj y se ajustaron las propiedades de depuración en los grupos de configuración. En MainViewModel.cs, se corrigió un error en el tiempo de simulación. En Aether.cs, se implementaron nuevas propiedades y métodos para gestionar la presión entre botellas, mejorando la lógica de colisiones y evitando superposiciones. Estas mejoras optimizan la simulación y la interacción entre objetos. 2025-06-25 13:59:07 +02:00
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
213 changed files with 36012 additions and 4451 deletions

4
.cursor/rules/reglas.mdc Normal file
View File

@ -0,0 +1,4 @@
---
alwaysApply: true
---
Quisiera que con los conocimientos importantes y desiciones importantes que hemos adquirido y utilizado los agregues a Documentation\MemoriadeEvolucion.md manteniendo el estilo que ya tenemos de texto simple sin demasiado codigo y una semantica resumida.

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

@ -0,0 +1,33 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace CtrEditor.Converters
{
public class BooleanToLocalizedTextConverter : IValueConverter
{
public string TrueText { get; set; } = "True";
public string FalseText { get; set; } = "False";
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return boolValue ? TrueText : FalseText;
}
return FalseText;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string text)
{
if (text == TrueText)
return true;
if (text == FalseText)
return false;
}
return false;
}
}
}

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;
}
}
}

14
CtrEditor.code-workspace Normal file
View File

@ -0,0 +1,14 @@
{
"folders": [
{
"path": "."
},
{
"path": "../Libraries/LibS7Adv"
},
{
"path": "../Librerias/bepuphysics2-master"
}
],
"settings": {}
}

View File

@ -6,38 +6,78 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<PlatformTarget>x64</PlatformTarget>
<!-- Configuración para Hot Reload -->
<EnableHotReload>true</EnableHotReload>
<HotReloadLogger>true</HotReloadLogger>
<ForceGenerateMetadataAssemblyFormat>true</ForceGenerateMetadataAssemblyFormat>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>False</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<Optimize>True</Optimize>
<DefineConstants>TRACE</DefineConstants>
<DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Compile Remove="ObjetosSim\Fluids\**" />
<EmbeddedResource Remove="ObjetosSim\Fluids\**" />
<None Remove="ObjetosSim\Fluids\**" />
<Page Remove="ObjetosSim\Fluids\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Documentation\BEPU Forces.cs" />
<Compile Remove="Documentation\PlantillaEstandarizacion.cs" />
<Compile Remove="ObjetosSim\ucTransporteCurva.xaml.cs" />
<Compile Remove="Simulacion\FPhysics.cs" />
<Compile Remove="Simulacion\GeometrySimulator.cs" />
</ItemGroup>
<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>
@ -45,17 +85,31 @@
</ItemGroup>
<ItemGroup>
<None Include="Simulacion\FPhysics.cs" />
<None Include="Simulacion\GeometrySimulator.cs" />
<None Include="Documentation\BEPU Forces.cs" />
<None Include="Documentation\PlantillaEstandarizacion.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="BepuPhysics" Version="2.5.0-beta.26" />
<PackageReference Include="BepuUtilities" Version="2.5.0-beta.26" />
<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="HelixToolkit.Wpf" Version="2.27.0" />
<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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Libraries\LibS7Adv\LibS7Adv.csproj" />
</ItemGroup>
<ItemGroup>
@ -68,23 +122,76 @@
<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="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

2616
Documentation/BEPU Forces.cs Normal file

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,937 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using BulletSharp;
using BulletSharp.Math;
namespace CtrEditor.Simulacion
{
public class simBase
{
public RigidBody Body { get; protected set; }
public DiscreteDynamicsWorld _world;
public void RemoverBody()
{
if (Body != null && _world != null)
{
_world.RemoveRigidBody(Body);
Body?.Dispose();
Body = null;
}
}
public static float Min(float Value, float Min = 0.01f)
{
return Value < Min ? Min : Value;
}
public static float GradosARadianes(float grados)
{
return (float)(grados * (Math.PI / 180));
}
public static float RadianesAGrados(float radianes)
{
return (float)(radianes * (180 / Math.PI));
}
public void SetPosition(float x, float y)
{
if (Body != null)
{
var transform = Body.WorldTransform;
transform.Origin = new Vector3(x, y, 0);
Body.WorldTransform = transform;
Body.Activate();
}
}
public void SetPosition(Vector3 centro)
{
try
{
if (Body != null)
{
var transform = Body.WorldTransform;
transform.Origin = centro;
Body.WorldTransform = transform;
Body.Activate();
}
}
catch
{
}
}
public Vector3 GetPosition()
{
return Body?.WorldTransform.Origin ?? Vector3.Zero;
}
}
public class simCurve : simBase
{
private float _innerRadius;
private float _outerRadius;
private float _startAngle;
private float _endAngle;
public float Speed { get; set; }
private List<Action> _deferredActions;
public simCurve(DiscreteDynamicsWorld world, List<Action> deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector3 position)
{
_world = world;
_deferredActions = deferredActions;
_innerRadius = innerRadius;
_outerRadius = outerRadius;
_startAngle = GradosARadianes(startAngle);
_endAngle = GradosARadianes(endAngle);
Create(position);
}
public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector3 position)
{
if (_world == null)
return;
_innerRadius = innerRadius;
_outerRadius = outerRadius;
_startAngle = GradosARadianes(startAngle);
_endAngle = GradosARadianes(endAngle);
Create(position);
}
public void Create(Vector3 position)
{
RemoverBody();
// Crear una forma compuesta para la curva
var compoundShape = new CompoundShape();
// Crear segmentos de la curva como cajas pequeñas
int segments = 32;
float angleStep = (_endAngle - _startAngle) / segments;
float thickness = 0.1f; // Grosor de la curva
for (int i = 0; i < segments; i++)
{
float angle = _startAngle + i * angleStep;
float midRadius = (_innerRadius + _outerRadius) / 2f;
float segmentWidth = (_outerRadius - _innerRadius);
float segmentLength = midRadius * angleStep;
var segmentShape = new BoxShape(segmentLength / 2f, segmentWidth / 2f, thickness / 2f);
var segmentTransform = Matrix.Identity;
segmentTransform.Origin = new Vector3(
midRadius * (float)Math.Cos(angle),
midRadius * (float)Math.Sin(angle),
0
);
segmentTransform = Matrix.RotationZ(angle) * segmentTransform;
compoundShape.AddChildShape(segmentTransform, segmentShape);
}
var motionState = new DefaultMotionState(Matrix.Translation(position));
var rbInfo = new RigidBodyConstructionInfo(0, motionState, compoundShape, Vector3.Zero);
Body = new RigidBody(rbInfo);
Body.UserObject = this;
Body.CollisionFlags |= CollisionFlags.NoContactResponse; // Sensor
_world.AddRigidBody(Body);
}
public void SetSpeed(float speed)
{
Speed = speed;
}
public void ApplyCurveEffect(RigidBody bottle)
{
Vector3 centerToBottle = bottle.WorldTransform.Origin - Body.WorldTransform.Origin;
float distanceToCenter = centerToBottle.Length;
if (distanceToCenter >= _innerRadius && distanceToCenter <= _outerRadius)
{
float overlapPercentage = CalculateAngleOverlap(bottle);
if (overlapPercentage > 0)
{
float speedMetersPerSecond = Speed / 60.0f;
Vector3 tangent = new Vector3(-centerToBottle.Y, centerToBottle.X, 0);
tangent.Normalize();
if (speedMetersPerSecond < 0)
tangent = -tangent;
Vector3 currentVelocity = bottle.LinearVelocity;
float currentSpeed = currentVelocity.Length;
float conveyorSpeed = Math.Abs(speedMetersPerSecond);
float targetSpeed = Math.Max(currentSpeed, conveyorSpeed);
Vector3 currentDir = currentVelocity;
if (currentDir.LengthSquared > 0)
currentDir.Normalize();
else
currentDir = tangent;
Vector3 newDirection = currentDir * (1 - overlapPercentage) + tangent * overlapPercentage;
newDirection.Normalize();
bottle.LinearVelocity = newDirection * targetSpeed;
}
}
}
private float CalculateAngleOverlap(RigidBody bottle)
{
Vector3 centerToBottle = bottle.WorldTransform.Origin - Body.WorldTransform.Origin;
float bottleAngle = (float)Math.Atan2(centerToBottle.Y, centerToBottle.X);
float normalizedBottleAngle = bottleAngle < 0 ? bottleAngle + 2 * (float)Math.PI : bottleAngle;
float normalizedStartAngle = _startAngle < 0 ? _startAngle + 2 * (float)Math.PI : _startAngle;
float normalizedEndAngle = _endAngle < 0 ? _endAngle + 2 * (float)Math.PI : _endAngle;
if (normalizedStartAngle > normalizedEndAngle)
{
if (!(normalizedBottleAngle >= normalizedStartAngle || normalizedBottleAngle <= normalizedEndAngle))
return 0;
}
else
{
if (normalizedBottleAngle < normalizedStartAngle || normalizedBottleAngle > normalizedEndAngle)
return 0;
}
float distanceToCenter = centerToBottle.Length;
if (distanceToCenter < _innerRadius || distanceToCenter > _outerRadius)
return 0;
float angleFromStart, angleToEnd, totalAngle;
if (normalizedStartAngle > normalizedEndAngle)
{
totalAngle = (2 * (float)Math.PI - normalizedStartAngle) + normalizedEndAngle;
if (normalizedBottleAngle >= normalizedStartAngle)
{
angleFromStart = normalizedBottleAngle - normalizedStartAngle;
angleToEnd = (2 * (float)Math.PI - normalizedBottleAngle) + normalizedEndAngle;
}
else
{
angleFromStart = (2 * (float)Math.PI - normalizedStartAngle) + normalizedBottleAngle;
angleToEnd = normalizedEndAngle - normalizedBottleAngle;
}
}
else
{
angleFromStart = normalizedBottleAngle - normalizedStartAngle;
angleToEnd = normalizedEndAngle - normalizedBottleAngle;
totalAngle = normalizedEndAngle - normalizedStartAngle;
}
float minAngleDistance = Math.Min(angleFromStart, angleToEnd);
float overlapPercentage = Math.Min(minAngleDistance / (totalAngle * 0.1f), 1.0f);
return overlapPercentage;
}
}
public class simDescarte : simBase
{
private float _radius;
private List<Action> _deferredActions;
public simDescarte(DiscreteDynamicsWorld world, List<Action> deferredActions, float diameter, Vector3 position)
{
_world = world;
_deferredActions = deferredActions;
_radius = diameter / 2;
Create(position);
}
public void SetDiameter(float diameter)
{
if (diameter <= 0)
diameter = 0.01f;
_radius = diameter / 2;
Create(Body.WorldTransform.Origin);
}
public void Create(Vector3 position)
{
RemoverBody();
var shape = new CylinderShape(_radius, _radius, 0.1f); // Cilindro plano para simular círculo 2D
var motionState = new DefaultMotionState(Matrix.Translation(position));
var rbInfo = new RigidBodyConstructionInfo(0, motionState, shape, Vector3.Zero);
Body = new RigidBody(rbInfo);
Body.UserObject = this;
Body.CollisionFlags |= CollisionFlags.NoContactResponse; // Sensor
_world.AddRigidBody(Body);
}
}
public class simTransporte : simBase
{
public float Speed { get; set; }
public float Friction { get; set; }
public float DistanceGuide2Guide { get; set; }
public bool isBrake { get; set; }
public bool TransportWithGuides = false;
private List<Action> _deferredActions;
public float Width { get; set; }
public float Height { get; set; }
public simTransporte(DiscreteDynamicsWorld world, List<Action> deferredActions, float width, float height, Vector3 position, float angle = 0)
{
_world = world;
_deferredActions = deferredActions;
Create(width, height, position, angle);
}
public float Angle
{
get
{
var rotation = Body.WorldTransform.GetBasis();
return RadianesAGrados((float)Math.Atan2(rotation.M21, rotation.M11));
}
set
{
var transform = Body.WorldTransform;
transform.Basis = Matrix3x3.CreateRotationZ(GradosARadianes(value));
Body.WorldTransform = transform;
}
}
public new void SetPosition(float x, float y)
{
var transform = Body.WorldTransform;
transform.Origin = new Vector3(x, y, 0);
Body.WorldTransform = transform;
}
public void SetSpeed(float speed)
{
Speed = speed;
}
public void SetDimensions(float width, float height)
{
RemoverBody();
Create(width, height, Body.WorldTransform.Origin, Angle);
}
public void Create(float width, float height, Vector3 position, float angle = 0)
{
RemoverBody();
Width = width;
Height = height;
Friction = 0.1f;
var shape = new BoxShape(width / 2, height / 2, 0.05f); // Caja plana para simular rectángulo 2D
var transform = Matrix.Translation(position) * Matrix.RotationZ(GradosARadianes(angle));
var motionState = new DefaultMotionState(transform);
var rbInfo = new RigidBodyConstructionInfo(0, motionState, shape, Vector3.Zero);
Body = new RigidBody(rbInfo);
Body.UserObject = this;
Body.CollisionFlags |= CollisionFlags.NoContactResponse; // Sensor
_world.AddRigidBody(Body);
}
}
public class simBarrera : simBase
{
public float Distancia;
public int LuzCortada;
public bool LuzCortadaNeck;
public bool DetectNeck;
public List<simBotella> ListSimBotellaContact;
float _height;
private List<Action> _deferredActions;
public simBarrera(DiscreteDynamicsWorld world, List<Action> deferredActions, float width, float height, Vector3 position, float angle = 0, bool detectectNeck = false)
{
_world = world;
_height = Min(height);
DetectNeck = detectectNeck;
_deferredActions = deferredActions;
ListSimBotellaContact = new List<simBotella>();
Create(Min(width), _height, position, angle);
}
public float Angle
{
get
{
var rotation = Body.WorldTransform.GetBasis();
return RadianesAGrados((float)Math.Atan2(rotation.M21, rotation.M11));
}
set
{
var transform = Body.WorldTransform;
transform.Basis = Matrix3x3.CreateRotationZ(GradosARadianes(value));
Body.WorldTransform = transform;
}
}
public new void SetPosition(float x, float y)
{
var transform = Body.WorldTransform;
transform.Origin = new Vector3(x, y, 0);
Body.WorldTransform = transform;
}
public void SetDimensions(float width, float height)
{
RemoverBody();
Create(Min(width), Min(height), Body.WorldTransform.Origin, Angle);
}
public void Create(float width, float height, Vector3 position, float angle = 0, bool detectectNeck = false)
{
RemoverBody();
_height = Min(height);
DetectNeck = detectectNeck;
var shape = new BoxShape(Min(width) / 2, _height / 2, 0.05f);
var transform = Matrix.Translation(position) * Matrix.RotationZ(GradosARadianes(angle));
var motionState = new DefaultMotionState(transform);
var rbInfo = new RigidBodyConstructionInfo(0, motionState, shape, Vector3.Zero);
Body = new RigidBody(rbInfo);
Body.UserObject = this;
Body.CollisionFlags |= CollisionFlags.NoContactResponse; // Sensor
_world.AddRigidBody(Body);
LuzCortada = 0;
}
public void CheckIfNecksIsTouching()
{
if (LuzCortada == 0)
return;
foreach (var botella in ListSimBotellaContact)
{
Vector3 sensorCenter = Body.WorldTransform.Origin;
float halfHeight = _height / 2;
var rotation = Body.WorldTransform.GetBasis();
float cos = rotation.M11;
float sin = rotation.M21;
Vector3 lineStart = sensorCenter + new Vector3(-halfHeight * cos, halfHeight * sin, 0);
Vector3 lineEnd = sensorCenter + new Vector3(halfHeight * cos, -halfHeight * sin, 0);
Vector3 fixtureCenter = botella.Body.WorldTransform.Origin;
Vector3 closestPoint = ProjectPointOntoLine(fixtureCenter, lineStart, lineEnd);
float distance = (closestPoint - fixtureCenter).Length;
Distancia = distance;
if (distance <= botella._neckRadius)
{
LuzCortadaNeck = true;
return;
}
}
LuzCortadaNeck = false;
}
private Vector3 ProjectPointOntoLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd)
{
Vector3 lineDirection = lineEnd - lineStart;
lineDirection.Normalize();
Vector3 pointToLineStart = point - lineStart;
float projectionLength = Vector3.Dot(pointToLineStart, lineDirection);
return lineStart + projectionLength * lineDirection;
}
}
public class simGuia : simBase
{
private List<Action> _deferredActions;
public simGuia(DiscreteDynamicsWorld world, List<Action> deferredActions, Vector3 start, Vector3 end)
{
_world = world;
_deferredActions = deferredActions;
Create(start, end);
}
public void Create(Vector3 start, Vector3 end)
{
RemoverBody();
Vector3 center = (start + end) / 2;
Vector3 direction = end - start;
float length = direction.Length;
var shape = new BoxShape(length / 2, 0.01f, 0.05f); // Línea como caja muy delgada
float angle = (float)Math.Atan2(direction.Y, direction.X);
var transform = Matrix.Translation(center) * Matrix.RotationZ(angle);
var motionState = new DefaultMotionState(transform);
var rbInfo = new RigidBodyConstructionInfo(0, motionState, shape, Vector3.Zero);
Body = new RigidBody(rbInfo);
Body.UserObject = this;
_world.AddRigidBody(Body);
}
public void UpdateVertices(Vector3 newStart, Vector3 newEnd)
{
Create(newStart, newEnd);
}
}
public class simBotella : simBase
{
public float Radius;
private float _mass;
public bool Descartar = false;
public int isOnTransports;
public List<simBase> ListOnTransports;
public bool isRestricted;
public bool isNoMoreRestricted;
public float OriginalMass;
public simTransporte ConveyorRestrictedTo;
public RigidBody axisRestrictedBy;
public float OverlapPercentage;
public float _neckRadius;
private List<Action> _deferredActions;
private DiscreteDynamicsWorld _worldRef;
public simBotella(DiscreteDynamicsWorld world, List<Action> deferredActions, float diameter, Vector3 position, float mass, float neckRadius = 0)
{
_world = world;
_worldRef = world;
ListOnTransports = new List<simBase>();
_deferredActions = deferredActions;
diameter = Min(diameter, 0.01f);
Radius = diameter / 2;
_mass = mass;
if (neckRadius <= 0)
neckRadius = diameter / 4;
_neckRadius = neckRadius;
Create(position);
}
public float CenterX => Body.WorldTransform.Origin.X;
public float CenterY => Body.WorldTransform.Origin.Y;
public Vector3 Center => Body.WorldTransform.Origin;
public float Mass
{
get
{
if (_mass <= 0)
_mass = 1;
return _mass;
}
set
{
_mass = value;
}
}
private void Create(Vector3 position)
{
RemoverBody();
isOnTransports = 0;
var shape = new CylinderShape(Radius, Radius, 0.1f); // Cilindro para simular círculo 2D
var motionState = new DefaultMotionState(Matrix.Translation(position));
Vector3 inertia;
shape.CalculateLocalInertia(_mass, out inertia);
var rbInfo = new RigidBodyConstructionInfo(_mass, motionState, shape, inertia);
Body = new RigidBody(rbInfo);
Body.UserObject = this;
Body.SetDamping(3f, 1f); // Linear y angular damping
Body.SetSleepingThresholds(0, 0); // No dormir
Body.CcdMotionThreshold = 1e-7f;
Body.CcdSweptSphereRadius = Radius * 0.2f;
_world.AddRigidBody(Body);
}
public void SetDiameter(float diameter)
{
diameter = Min(diameter, 0.01f);
Radius = diameter / 2;
Create(Body.WorldTransform.Origin);
}
public void SetMass(float mass)
{
Mass = mass;
}
public void ApplyConveyorSpeed(float deltaTime_s)
{
foreach (var transporte in ListOnTransports)
{
if (transporte is simTransporte conveyorRect)
ApplyConveyorEffect(deltaTime_s, conveyorRect);
if (transporte is simCurve conveyorCurve)
conveyorCurve.ApplyCurveEffect(Body);
}
}
private bool ApplyConveyorEffect(float deltaTime_s, simTransporte conveyor)
{
float overlapPercentage = CalculateOverlapedArea(this, conveyor);
OverlapPercentage = overlapPercentage;
float speedMetersPerSecond = conveyor.Speed / 60.0f;
var rotation = conveyor.Body.WorldTransform.GetBasis();
Vector3 desiredVelocity = new Vector3(rotation.M11, rotation.M21, 0) * speedMetersPerSecond;
Vector3 currentVelocity = Body.LinearVelocity;
Vector3 velocityDifference = desiredVelocity - currentVelocity;
float proporcionalVelocityNeeded = 1 - CalculateProportion(currentVelocity, desiredVelocity);
float frictionCoefficient;
switch (proporcionalVelocityNeeded)
{
case > 0.3f:
frictionCoefficient = conveyor.Friction * overlapPercentage;
break;
default:
frictionCoefficient = proporcionalVelocityNeeded * overlapPercentage;
break;
}
if (isRestricted && conveyor == ConveyorRestrictedTo && overlapPercentage > 0.5f)
{
Body.LinearVelocity = desiredVelocity;
return true;
}
Body.LinearVelocity += frictionCoefficient * desiredVelocity;
return false;
}
public float CalculateOverlapedArea(simBotella botella, simTransporte conveyor)
{
// Implementación simplificada del cálculo de área superpuesta
Vector3 bottlePos = botella.Body.WorldTransform.Origin;
Vector3 conveyorPos = conveyor.Body.WorldTransform.Origin;
// Transformar posición de botella al espacio local del transportador
Matrix invTransform = conveyor.Body.WorldTransform.Inverse();
Vector3 localBottlePos = Vector3.TransformCoordinate(bottlePos, invTransform);
// Verificar si la botella está dentro del rectángulo del transportador
float halfWidth = conveyor.Width / 2;
float halfHeight = conveyor.Height / 2;
if (Math.Abs(localBottlePos.X) <= halfWidth + botella.Radius &&
Math.Abs(localBottlePos.Y) <= halfHeight + botella.Radius)
{
// Calcular área superpuesta aproximada
float overlapX = Math.Max(0, Math.Min(halfWidth, localBottlePos.X + botella.Radius) - Math.Max(-halfWidth, localBottlePos.X - botella.Radius));
float overlapY = Math.Max(0, Math.Min(halfHeight, localBottlePos.Y + botella.Radius) - Math.Max(-halfHeight, localBottlePos.Y - botella.Radius));
float overlapArea = overlapX * overlapY;
float bottleArea = (float)(Math.PI * botella.Radius * botella.Radius);
return Math.Min(1.0f, overlapArea / bottleArea);
}
return 0;
}
public static float CalculateProportion(Vector3 currentVelocity, Vector3 desiredVelocity)
{
float dotProduct = Vector3.Dot(desiredVelocity, currentVelocity);
float magnitudeV1Squared = desiredVelocity.LengthSquared;
if (magnitudeV1Squared == 0)
return 0;
float proportion = dotProduct / magnitudeV1Squared;
return proportion;
}
public void CenterFixtureOnConveyor()
{
if (!isRestricted || ConveyorRestrictedTo == null)
return;
Vector3 conveyorCenter = ConveyorRestrictedTo.Body.WorldTransform.Origin;
float halfDistance = ConveyorRestrictedTo.DistanceGuide2Guide / 2;
var rotation = ConveyorRestrictedTo.Body.WorldTransform.GetBasis();
Vector3 offset = new Vector3(halfDistance * rotation.M11, halfDistance * rotation.M21, 0);
Vector3 lineStart = conveyorCenter - offset;
Vector3 lineEnd = conveyorCenter + offset;
Vector3 fixtureCenter = Body.WorldTransform.Origin;
Vector3 closestPoint = ProjectPointOntoLine(fixtureCenter, lineStart, lineEnd);
var transform = Body.WorldTransform;
transform.Origin = closestPoint;
Body.WorldTransform = transform;
}
private Vector3 ProjectPointOntoLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd)
{
Vector3 lineDirection = lineEnd - lineStart;
lineDirection.Normalize();
Vector3 pointToLineStart = point - lineStart;
float projectionLength = Vector3.Dot(pointToLineStart, lineDirection);
return lineStart + projectionLength * lineDirection;
}
public bool IsOnAnyTransport()
{
return isOnTransports > 0;
}
}
public class SimulationManagerFP
{
private DiscreteDynamicsWorld world;
private Canvas simulationCanvas;
public List<simBase> Cuerpos;
public List<Action> _deferredActions;
private Stopwatch stopwatch;
private double stopwatch_last;
// Componentes de Bullet Physics
private DefaultCollisionConfiguration collisionConfiguration;
private CollisionDispatcher dispatcher;
private DbvtBroadphase broadphase;
private SequentialImpulseConstraintSolver solver;
public Canvas DebugCanvas { get => simulationCanvas; set => simulationCanvas = value; }
public SimulationManagerFP()
{
// Inicializar Bullet Physics
collisionConfiguration = new DefaultCollisionConfiguration();
dispatcher = new CollisionDispatcher(collisionConfiguration);
broadphase = new DbvtBroadphase();
solver = new SequentialImpulseConstraintSolver();
world = new DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
world.Gravity = new Vector3(0, 0, 0); // Sin gravedad para simular 2D
Cuerpos = new List<simBase>();
_deferredActions = new List<Action>();
stopwatch = new Stopwatch();
stopwatch.Start();
}
public void Clear()
{
if (world.NumCollisionObjects > 0)
{
for (int i = world.NumCollisionObjects - 1; i >= 0; i--)
{
var obj = world.CollisionObjectArray[i];
var body = RigidBody.Upcast(obj);
if (body != null && body.MotionState != null)
{
body.MotionState.Dispose();
}
world.RemoveCollisionObject(obj);
obj.Dispose();
}
}
if (Cuerpos.Count > 0)
Cuerpos.Clear();
}
public void Start()
{
stopwatch.Start();
stopwatch_last = stopwatch.Elapsed.TotalMilliseconds;
}
public void Step()
{
float elapsedMilliseconds = (float)(stopwatch.Elapsed.TotalMilliseconds - stopwatch_last);
stopwatch_last = stopwatch.Elapsed.TotalMilliseconds;
world.StepSimulation(elapsedMilliseconds / 1000.0f, 10);
// Detectar colisiones manualmente para sensores
CheckCollisions();
foreach (var cuerpo in Cuerpos)
{
if (cuerpo is simBotella botella)
{
botella.ApplyConveyorSpeed(elapsedMilliseconds / 1000);
if (botella.isRestricted)
{
botella.CenterFixtureOnConveyor();
botella.Body.SetMassProps(100, Vector3.Zero);
}
else if (botella.isNoMoreRestricted)
{
botella.isNoMoreRestricted = false;
botella.Body.SetMassProps(botella.OriginalMass, Vector3.Zero);
}
}
else if (cuerpo is simBarrera barrera)
barrera.CheckIfNecksIsTouching();
}
foreach (var action in _deferredActions)
{
action();
}
_deferredActions.Clear();
}
private void CheckCollisions()
{
int numManifolds = world.Dispatcher.NumManifolds;
for (int i = 0; i < numManifolds; i++)
{
PersistentManifold contactManifold = world.Dispatcher.GetManifoldByIndexInternal(i);
var obA = contactManifold.Body0;
var obB = contactManifold.Body1;
var bodyA = RigidBody.Upcast(obA);
var bodyB = RigidBody.Upcast(obB);
if (bodyA?.UserObject is simBotella botella && bodyB?.UserObject is simBase other)
{
HandleCollision(botella, other, bodyB);
}
else if (bodyB?.UserObject is simBotella botella2 && bodyA?.UserObject is simBase other2)
{
HandleCollision(botella2, other2, bodyA);
}
}
}
private void HandleCollision(simBotella botella, simBase other, RigidBody otherBody)
{
switch (other)
{
case simBarrera sensor:
if (!sensor.ListSimBotellaContact.Contains(botella))
{
sensor.LuzCortada += 1;
sensor.ListSimBotellaContact.Add(botella);
}
break;
case simCurve curve:
if (!botella.ListOnTransports.Contains(curve))
{
botella.isOnTransports += 1;
botella.ListOnTransports.Add(curve);
}
break;
case simDescarte:
botella.Descartar = true;
break;
case simTransporte conveyor:
if (!botella.ListOnTransports.Contains(conveyor))
{
botella.isOnTransports += 1;
botella.ListOnTransports.Add(conveyor);
if (conveyor.TransportWithGuides && conveyor.isBrake)
{
botella.ConveyorRestrictedTo = conveyor;
botella.axisRestrictedBy = otherBody;
botella.OriginalMass = botella.Body.Mass;
botella.isRestricted = true;
botella.isNoMoreRestricted = false;
}
}
break;
}
}
public void Remove(simBase Objeto)
{
if (Objeto != null)
{
Objeto.RemoverBody();
Cuerpos.Remove(Objeto);
}
}
public simCurve AddCurve(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector3 position)
{
simCurve curva = new simCurve(world, _deferredActions, innerRadius, outerRadius, startAngle, endAngle, position);
Cuerpos.Add(curva);
return curva;
}
public simBotella AddCircle(float diameter, Vector3 position, float mass)
{
simBotella circle = new simBotella(world, _deferredActions, diameter, position, mass);
Cuerpos.Add(circle);
return circle;
}
public simTransporte AddRectangle(float width, float height, Vector3 position, float angle)
{
simTransporte rectangle = new simTransporte(world, _deferredActions, width, height, position, angle);
Cuerpos.Add(rectangle);
return rectangle;
}
public simBarrera AddBarrera(float width, float height, Vector3 position, float angle, bool detectarCuello)
{
simBarrera rectangle = new simBarrera(world, _deferredActions, width, height, position, angle, detectarCuello);
Cuerpos.Add(rectangle);
return rectangle;
}
public simGuia AddLine(Vector3 start, Vector3 end)
{
simGuia line = new simGuia(world, _deferredActions, start, end);
Cuerpos.Add(line);
return line;
}
public simDescarte AddDescarte(float diameter, Vector3 position)
{
simDescarte descarte = new simDescarte(world, _deferredActions, diameter, position);
Cuerpos.Add(descarte);
return descarte;
}
~SimulationManagerFP()
{
Clear();
world?.Dispose();
solver?.Dispose();
broadphase?.Dispose();
dispatcher?.Dispose();
collisionConfiguration?.Dispose();
}
}
}

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*

View File

@ -0,0 +1,32 @@
### Aqui se mantiene la memoria de evolucion de las distintas decisiones que fueron tomadas y porque
BEPU.cs : SimulationManagerBEPU gestor de la simulacion con el motor BEPUphysics y punto de creacion y modificacion de los objetos dentro del mundo que se derivan des simBase
simBase : Clase base que da un marco a el resto de los objetos
simBarrera : Simula una fotocelula con un espejo usando RayCast
simBotella : Simula una botella que puede transitar por los transportes simTransporte o simCurve
simCurve : Simula una curva o arco de curva de transporte
simDescarte : Permite la eliminacion localizada de botellas en un punto del mundo
simGuia : Es un box con el que las botellas pueden colisionar y cambiar de direccion.
simTransporte : Es un box por donde las botellas pueden desplazarse usando un truco de aplicar la Velocity.Linear pero sin integrar esta velocidad para que no se mueva el objeto transporte.
* Se usaron esferas en vez de cilindros para mejorar la eficiencia. En el Debug3D si se usan cilindros. **Revertido**: Se ha vuelto a usar `Cylinder` para las `simBotella` ya que el nuevo sistema de fricción debería prevenir la rotación indeseada que ocurría con los `LinearAxisMotor`.
* Se reemplazó el sistema de `LinearAxisMotor` que actuaba sobre las `simBotella` por un sistema basado en fricción. Los transportes (`simTransporte` y `simCurve`) ahora son cuerpos cinemáticos que no se mueven de su sitio. Para lograr que arrastren a las botellas, se les asigna una velocidad (`Velocity.Linear` o `Velocity.Angular`) justo antes de que el solver se ejecute (en `OnSubstepStarted`) y se les quita justo después (en `OnSubstepEnded`). Esto permite que los cuerpos cinemáticos transmitan su velocidad a través de la fricción durante la simulación, pero evita que el `PoseIntegrator` los desplace de su posición original, ya que su velocidad es cero cuando se integra la pose.
* Para aumentar la estabilidad de las `simBotella` y evitar que roten descontroladamente sobre su eje Z al ser arrastradas, se implementó un callback `IntegrateVelocity` personalizado. Este callback identifica las botellas durante la integración y les aplica un amortiguamiento angular (`AngularDamping`) adicional solo en el eje Z. Se descartó la idea inicial de modificar dinámicamente la masa de las botellas dentro de este callback, ya que la arquitectura de BEPUphysics no permite cambiar la masa o la inercia de un cuerpo durante la fase de integración de velocidad.
* Originalmente se habia puesto todos los objetos en awaken para poder usar las colisiones constantemente incluso con objetos en modo sleep para que los simBarrera puedan detectar las colisiones. Ahora que se usar RayCast podemos dejar que las simBotellas se duerman
* La unica clase que se ha terminado de refactorizar respecto a el cambio de coordenadas es simBarrera y ucPhotocell. El concepto es poder separar usando metodos de SimulationManagerBEPU y estructuras como BarreraData la conversion de coordenadas de WPF a coordenadas BEPU. Para esto se usa CoordinateConverter que permite bidireccionalmente convertir las coordenadas pero esto solo se debe usar en SimulationManagerBEPU las clases derivadas de osBase solo deben manejar coordenadas WPF, mientras que las clases dervadas de simBase solo deben almacenar y usar coordenadas BEPU. La z tambien es algo que se debe transferir a SimulationManagerBEPU ya que los objetos simBase deberian recibir tambien sus coordenadas de Z desde SimulationManagerBEPU y ser SimulationManagerBEPU el que gestione las Z.
* Se ha implementado un sistema para evitar que las botellas (`simBotella`) "floten" o se eleven de manera irreal por la acumulación de presión en la simulación. Cada botella ahora registra si está en contacto con un transporte y almacena la última coordenada Z válida durante dicho contacto. Si la botella deja de tener contacto con transportes por varios frames consecutivos, se incrementa un contador de "presión". Al superar un umbral, el sistema reestablece la posición Z de la botella a su última altura conocida, previniendo la flotación. Este contador de presión se decrementa rápidamente al volver a hacer contacto con un transporte.
* Cuando una botella (`simBotella`) entra en contacto con un transporte de frenado (`simTransporte` con `isBrake = true`), su posición se ajusta automáticamente para centrarla en el eje longitudinal del transporte. Esto se realiza una única vez, en el primer contacto, para asegurar un acoplamiento suave y predecible. La posición de la botella se proyecta sobre la línea central del transporte y su velocidad lateral se anula, evitando que la botella se desvíe mientras frena y se alinea con el flujo de salida.
* Se ha implementado un indicador visual simple para mostrar cuando los transportes (`simTransporte`) están en movimiento. El sistema detecta automáticamente si un transporte tiene velocidad (`Speed > 0.001`) y cambia su color a verde brillante. Los transportes detenidos se muestran en verde normal. Esta funcionalidad se integra en el sistema de materiales del `BEPUVisualization3DManager` y se actualiza automáticamente cuando cambia el estado del transporte, proporcionando una retroalimentación visual inmediata del estado de operación.
* Se ha implementado un sistema de animaciones automáticas usando StoryBoard de WPF para los transportes en movimiento. Los transportes activos muestran una animación continua que combina: (1) rotación sutil muy lenta alrededor del eje Z (20 segundos por vuelta completa) y (2) pulsación cíclica del color del material (1.5 segundos por ciclo). Las animaciones se crean y destruyen automáticamente según el estado del transporte, sin necesidad de actualización manual en cada frame. El sistema gestiona las animaciones activas en un diccionario y las limpia correctamente cuando se eliminan objetos. Se resolvió el problema de `InvalidOperationException` al animar brushes inmutables creando una función `CreateAnimatableMaterial` que genera materiales específicamente diseñados para ser animados sin estar "frozen", proporcionando una experiencia visual fluida y eficiente.
* Se ha mejorado el sistema de guías curvas (`ucTransporteCurvaGuias`) para incluir apertura en cono en los extremos de entrada y salida. Se agregó el parámetro `AnguloAperturaGuias` (por defecto 5 grados) que permite configurar la apertura modificando los radios de las guías en los puntos extremos. En lugar de cambiar ángulos, se reduce el radio de la guía superior (externa) y se aumenta el radio de la guía inferior (interna) en los segmentos inicial y final, creando naturalmente la apertura en cono. La modificación del radio se calcula usando `Math.Sin(anguloApertura)` para obtener el desplazamiento apropiado. Esta apertura facilita la entrada y salida de botellas del transporte curvo, reduciendo atascos y mejorando el flujo de materiales manteniendo la continuidad geométrica de las guías.

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,58 @@
<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:helix="http://helix-toolkit.org/wpf"
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 +60,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 +79,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" />
@ -83,52 +123,168 @@
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="3*" />
<RowDefinition Height="5" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<ToolBarTray Grid.Row="0">
<ToolBar>
<Button Command="{Binding TBStartSimulationCommand}" ToolTip="Iniciar Simulación" Style="{StaticResource StartStopButtonStyle}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding TBStartSimulationCommand}" ToolTip="Iniciar Simulación"
Style="{StaticResource StartStopButtonStyle}">
<StackPanel>
<Image Source="Icons/start.png" Width="16" Height="16"/>
<TextBlock Text="Iniciar"/>
<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="Icons/connect.png" Width="16" Height="16"/>
<TextBlock Text="Conectar"/>
<Image Source="{Binding IsConnected, Converter={StaticResource ConnectStateToImageConverter}}"
Width="24" Height="24" />
<TextBlock Text="{Binding IsConnected, Converter={StaticResource ConnectStateToBtnTextConverter}}" />
</StackPanel>
</Button>
<Button Command="{Binding TBDisconnectPLCCommand}" ToolTip="Desconectar PLC">
<!-- 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/disconnect.png" Width="16" Height="16"/>
<TextBlock Text="Desconectar"/>
<Image Source="Icons/match.png" Width="24" Height="24" />
<TextBlock Text="Multi Page Analyze" />
</StackPanel>
</Button>
<Button Command="{Binding TBMultiPageMatrixCommand}" ToolTip="Analyze Matrix (Multiple Pages)">
<StackPanel>
<Image Source="Icons/ocr.png" Width="24" Height="24" />
<TextBlock Text="Exportar Tags a Excel" />
</StackPanel>
</Button>
<Button Command="{Binding TBToggle3DUpdateCommand}" ToolTip="Activar/Desactivar actualización Debug 3D para mejorar rendimiento">
<StackPanel>
<Image Source="Icons/app.png" Width="24" Height="24" />
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="Debug3D: {0}">
<Binding Path="Is3DUpdateEnabled">
<Binding.Converter>
<converters:BooleanToLocalizedTextConverter TrueText="Activado" FalseText="Desactivado" />
</Binding.Converter>
</Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding Is3DUpdateEnabled}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
<DataTrigger Binding="{Binding Is3DUpdateEnabled}" Value="False">
<Setter Property="Background" Value="LightCoral" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</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>
<!-- Separador entre vistas -->
<GridSplitter Grid.Row="2" Height="5" HorizontalAlignment="Stretch" Background="DarkGray"
ResizeDirection="Rows" VerticalAlignment="Center" />
<!-- Vista 3D (Inferior) -->
<Border Grid.Row="3" BorderBrush="Gray" BorderThickness="1" Margin="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header del panel 3D -->
<TextBlock Grid.Row="0" Text="Debug 3D - Mundo BEPU" Background="DarkGray" Foreground="White"
Padding="5" FontWeight="Bold" />
<!-- HelixViewport3D -->
<helix:HelixViewport3D x:Name="Debug3DViewport" Grid.Row="1" ShowCoordinateSystem="True"
ShowFrameRate="True" CoordinateSystemLabelZ="Z - Altura" EnableCurrentPosition="True"
ShowFieldOfView="True" CalculateCursorPosition="True" IsManipulationEnabled="True"
Orthographic="True">
<helix:DefaultLights />
<helix:GridLinesVisual3D Width="100" Length="100" MinorDistance="1" Thickness="0.01"
MajorDistance="7" />
<!-- Contenido del viewport se configurará desde code-behind -->
</helix:HelixViewport3D>
</Grid>
</Border>
</Grid>
<!-- GridSplitter -->
@ -137,51 +293,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 -->
<Expander Grid.Row="2" Header="Objects List" IsExpanded="False">
<ListBox x:Name="ListaOs"
Grid.Row="0"
Margin="5"
MinHeight="100"
MaxHeight="300"
ItemsSource="{Binding ObjetosSimulables}"
DisplayMemberPath="Nombre"
SelectedItem="{Binding SelectedItemOsList, Mode=TwoWay}"
SelectionChanged="ListaOs_SelectionChanged"/>
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"
<GridSplitter Grid.Row="4" Height="5"
HorizontalAlignment="Stretch"
Background="Gray"
ResizeDirection="Rows"
VerticalAlignment="Center"/>
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,12 @@
<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>
<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: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,12 @@
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.Numerics;
using System.Windows.Media;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
namespace CtrEditor.ObjetosSim
{
@ -25,21 +26,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("Información")]
[property: Description("Porcentaje de tracción con transporte")]
[property: Name("Porcentaje de Tracción")]
private float porcentaje_Traccion;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Indica si la botella está en un transporte con freno")]
[property: Name("En Transporte con Freno")]
private bool enTransporteConFreno;
[ObservableProperty]
[property: Category("Simulación")]
[property: Description("Masa del objeto en kg")]
[property: Name("Masa")]
private float mass;
partial void OnMassChanged(float value)
{
@ -48,11 +105,11 @@ 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; }
{ Left = x - Diametro / 2; Top = y - Diametro / 2; }
public void SetCentro(Vector2 centro)
{
@ -62,9 +119,8 @@ namespace CtrEditor.ObjetosSim
private void ActualizarGeometrias()
{
if (SimGeometria != null)
if (SimGeometria != null && !RemoverDesdeSimulacion)
{
SimGeometria.SetDiameter(Diametro);
SimGeometria.SetPosition(GetCentro());
}
}
@ -73,40 +129,74 @@ namespace CtrEditor.ObjetosSim
{
Diametro = 0.10f;
Mass = 1;
ColorButton_oculto = Brushes.Gray;
}
public void UpdateAfterMove()
{
if (!RemoverDesdeSimulacion)
{
ActualizarGeometrias();
SimGeometria?.SetDiameter(Diametro);
}
}
public override void UpdateGeometryStart()
{
// Se llama antes de la simulacion
ActualizarGeometrias();
}
public override void SimulationStop()
// Se llama cuando inicia la simulación - crear geometría si no existe
if (SimGeometria == null)
{
// Se llama al detener la simulacion. Util para detener Storyboards
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
SimGeometria.SimObjectType = "Botella";
SimGeometria.WpfObject = this;
}
else
{
ActualizarGeometrias();
SimGeometria?.SetDiameter(Diametro);
SimGeometria?.SetMass(Mass);
}
}
public override void UpdateGeometryStep()
{
// Se llama antes de la simulacion
ActualizarGeometrias();
// Se llama durante cada paso de la simulación
if (SimGeometria != null)
{
// Actualizar posición desde la simulación hacia WPF
SetCentro(SimGeometria.Center);
}
}
public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds) { }
public override void UpdateControl(int elapsedMilliseconds)
{
SetCentro(SimGeometria.Center);
if (SimGeometria.Descartar) // Ha sido marcada para remover
// Sistema de colores jerarquizado para diferentes estados
if (SimGeometria.isOnBrakeTransport)
ColorButton_oculto = Brushes.Blue; // En transporte con freno (prioridad sobre presión)
else if (!SimGeometria.isOnBrakeTransport && SimGeometria.PressureBuildup>1)
ColorButton_oculto = Brushes.Red; // La botella tiene mucha presion
else
ColorButton_oculto = Brushes.Gray; // 5. Estado libre
// Ha sido marcada para remover
if (SimGeometria.Descartar)
RemoverDesdeSimulacion = true;
// ✅ ELIMINADO: Lógica redundante - BEPU.cs ya elimina botellas que caen en Z automáticamente
Velocidad_desde_simulacion = SimGeometria.GetLinearVelocity().ToString();
Inercia_desde_simulacion = SimGeometria.Mass; // En BEPU usamos masa en lugar de inercia directa
Porcentaje_Traccion = SimGeometria.OverlapPercentage;
EnTransporteConFreno = SimGeometria.isOnBrakeTransport; // Actualizar estado de freno
}
public override void ucLoaded()
{
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
ActualizarLeftTop();
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
base.ucLoaded();
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
}
public override void ucUnLoaded()
{
@ -120,6 +210,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 +227,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,220 @@
using System.Windows;
using System.Windows.Controls;
//using using Microsoft.Xna.Framework;
using LibS7Adv;
using CtrEditor.Simulacion;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Numerics;
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("Información")]
[property: Description("Porcentaje de tracción con transporte")]
[property: Name("Porcentaje de Tracción")]
private float porcentaje_Traccion;
[ObservableProperty]
[property: Category("Información")]
[property: Description("Indica si la botella está en un transporte con freno")]
[property: Name("En Transporte con Freno")]
private bool enTransporteConFreno;
[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);
// Sistema de colores jerarquizado para diferentes estados
if (SimGeometria.isRestricted)
ColorButton_oculto = Brushes.Yellow; // Estado restringido (prioridad alta)
else if (SimGeometria.isOnBrakeTransport)
ColorButton_oculto = Brushes.Blue; // En transporte con freno - NUEVO ESTADO
else if (SimGeometria.IsOnAnyTransport())
ColorButton_oculto = Brushes.Red; // En transporte normal
else
ColorButton_oculto = Brushes.Gray; // Estado libre
// Ha sido marcada para remover
if (SimGeometria.Descartar)
RemoverDesdeSimulacion = true;
// ✅ ELIMINADO: Lógica redundante - BEPU.cs ya elimina botellas que caen en Z automáticamente
Velocidad_desde_simulacion = SimGeometria.GetLinearVelocity().ToString();
Inercia_desde_simulacion = SimGeometria.Mass; // En BEPU usamos masa en lugar de inercia directa
Porcentaje_Traccion = SimGeometria.OverlapPercentage;
EnTransporteConFreno = SimGeometria.isOnBrakeTransport; // Actualizar estado de freno
}
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.AddBotella(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,20 @@
<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:osBottGenerator />
</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,269 @@
using LibS7Adv;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Diagnostics;
using CtrEditor.FuncionesBase;
using System.ComponentModel;
using DocumentFormat.OpenXml.Spreadsheet;
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;
// ✅ ELIMINADO: preserve_Outside_Transport - Redundante con ProcessCleanupSystem de BEPU
// Las botellas que caen en Z son eliminadas automáticamente por BEPU.cs
[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("Distancia libre minima sin botellas sobre el transporte para colocar otra botella")]
[property: Category("Simulación")]
[property: Name("Diametro libre minimo")]
private float distancia_libre;
[ObservableProperty]
[property: Description("Diámetro de las botellas generadas")]
[property: Category("Configuración")]
[property: Name("Diámetro Botella")]
private float diametro_botella;
public osBottGenerator()
{
Ancho = 0.40f;
Alto = 0.40f;
Angulo = 0;
Velocidad_actual_percentual = 100;
Diametro_botella = 0.1f;
Botellas_hora = 10000;
Consenso = true;
Distancia_libre = 0.1f;
}
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 = Distancia_libre; // 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;
// ✅ ELIMINADO: Preserve_Outside_Transport - Ya no es necesario
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)
{
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

@ -1,43 +1,31 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucDescarte"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<UserControl x:Class="CtrEditor.ObjetosSim.ucDescarte" 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.ObjetosSim"
mc:Ignorable="d"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
xmlns:convert="clr-namespace:CtrEditor.Convertidores">
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim" mc:Ignorable="d">
<UserControl.Resources>
<convert:MeterToPixelConverter x:Key="MeterToPixelConverter"/>
<Storyboard x:Key="PulsingStoryboard" RepeatBehavior="Forever">
<DoubleAnimation
Storyboard.TargetName="AnimatedEllipse"
<DoubleAnimation Storyboard.TargetName="AnimatedEllipse"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
<DoubleAnimation
Storyboard.TargetName="AnimatedEllipse"
<DoubleAnimation Storyboard.TargetName="AnimatedEllipse"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
</Storyboard>
</UserControl.Resources>
<UserControl.DataContext>
<vm:osDescarte/>
<vm:osDescarte />
</UserControl.DataContext>
<Grid>
<Ellipse
Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
Stroke="Yellow"
Fill="Black"
Opacity="0.5"/>
<Ellipse x:Name="AnimatedEllipse"
Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
Stroke="Blue"
Fill="Transparent"
RenderTransformOrigin="0.5,0.5">
<Ellipse Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" Stroke="Yellow"
Fill="Black" Opacity="0.5" />
<Ellipse x:Name="AnimatedEllipse" Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" Stroke="Blue"
Fill="Transparent" RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform />

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;
using System.Numerics;
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,11 +34,14 @@ 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)
{
SimGeometria?.SetDiameter(Diametro);
ActualizarGeometrias();
}
public Vector2 GetCentro()
@ -64,8 +73,15 @@ namespace CtrEditor.ObjetosSim
{
if (SimGeometria != null)
{
// ✅ SISTEMA INTELIGENTE: Solo recrear si el diámetro cambió
if (HasDiscardDimensionsChanged(SimGeometria, Diametro))
{
System.Diagnostics.Debug.WriteLine($"[osDescarte] Recreando descarte por cambio de diámetro");
SimGeometria.SetDiameter(Diametro);
SimGeometria.SetPosition(GetCentro());
}
// ✅ USAR MÉTODO COORDINATECONVERTER
SimGeometria.UpdateFromWpfCenter(GetCentro());
}
}
@ -82,7 +98,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 +110,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 +125,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 +146,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,11 @@
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;
using System.Numerics;
namespace CtrEditor.ObjetosSim
{
@ -16,9 +18,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,18 +32,41 @@ namespace CtrEditor.ObjetosSim
}
[ObservableProperty]
public float ancho;
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Grosor de la guía en metros")]
[property: Name("Grosor de la Guía")]
public float altoGuia;
[ObservableProperty]
public float angulo;
private void ActualizarGeometrias()
{
if (_visualRepresentation is ucGuia uc)
UpdateOrCreateLine(SimGeometria, uc.Guia);
if (_visualRepresentation is ucGuia uc && SimGeometria != null)
{
var topLeft = new Vector2(Left, Top);
// ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
if (HasGuideDimensionsChanged(SimGeometria, Ancho, AltoGuia))
{
//System.Diagnostics.Debug.WriteLine($"[osGuia] Recreando guía por cambio de dimensiones: {Ancho}x{AltoGuia}");
// ✅ RECREAR COMPLETAMENTE: Las dimensiones cambiaron
SimGeometria.Create(Ancho, AltoGuia, topLeft, Angulo);
SimGeometria.SetDimensions(Ancho, AltoGuia);
}
else
{
// System.Diagnostics.Debug.WriteLine($"[osGuia] Solo actualizando posición/rotación: Left={Left}, Top={Top}, Angulo={Angulo}");
// ✅ SOLO ACTUALIZAR POSICIÓN/ROTACIÓN: Usar dimensiones reales para conversión correcta
SimGeometria.UpdateFromWpfParameters(topLeft, Angulo, Ancho, AltoGuia);
}
}
}
public override void OnMoveResizeRotate()
{
ActualizarGeometrias();
}
public osGuia()
@ -57,12 +86,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);
@ -74,12 +103,27 @@ namespace CtrEditor.ObjetosSim
simulationManager?.Remove(SimGeometria);
}
public override void AnchoChanged(float value)
{
ActualizarGeometrias();
}
public override void AnguloChanged(float value)
{
ActualizarGeometrias();
}
// Método llamado cuando cambia AltoGuia
partial void OnAltoGuiaChanged(float value)
{
ActualizarGeometrias();
}
}
public partial class ucGuia : UserControl, IDataContainer
{
public osBase? Datos { get; set; }
public int zIndex_fromFrames { get; set; }
public ucGuia()
{
@ -95,29 +139,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,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:localuc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
xmlns:convert="clr-namespace:CtrEditor.Convertidores"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
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 />
@ -17,7 +12,9 @@
<Canvas x:Name="MainCanvas">
<localuc:CircularSegment x:Name="Transporte" Angle="0" OuterRadius="{Binding RadioExterno, Converter={StaticResource MeterToPixelConverter}}" InnerRadius="{Binding RadioInterno, Converter={StaticResource MeterToPixelConverter}}"
<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}" />
</Canvas>
</UserControl>

View File

@ -1,10 +1,13 @@
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;
using System.Numerics;
namespace CtrEditor.ObjetosSim
{
@ -13,21 +16,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 +38,155 @@ 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();
}
partial void OnRadioInternoChanged(float value)
{
// Ensure radioInterno is always less than radioExterno
if (value >= RadioExterno)
{
RadioInterno = RadioExterno * 0.75f; // Maintain proportion
return;
}
ActualizarGeometrias();
}
partial void OnArco_en_gradosChanged(float value)
{
OnPropertyChanged(nameof(AnguloFinal));
ActualizarGeometrias();
}
// Manejar cambios de posición usando métodos virtuales de osBase
public override void LeftChanging(float oldValue, float newValue)
{
base.LeftChanging(oldValue, newValue);
ActualizarPosicionBEPU();
}
public override void TopChanging(float oldValue, float newValue)
{
base.TopChanging(oldValue, newValue);
ActualizarPosicionBEPU();
}
private void ActualizarPosicionBEPU()
{
if (Simulation_TransporteCurva != null)
{
// ✅ CORRIGIDO: startAngle = Angulo, endAngle = Angulo + Arco_en_grados (como Aether)
var topLeft = new Vector2(Left, Top);
Simulation_TransporteCurva.Create(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados, topLeft, 0);
// Sincronizar con la visualización 3D tras la actualización
simulationManager?.Visualization3DManager?.SynchronizeWorld();
}
}
[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));
ActualizarGeometrias();
}
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(AnguloFinal))]
@ -52,39 +195,89 @@ 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;
if (Simulation_TransporteCurva != null)
{
// ✅ CORRIGIDO: startAngle = Angulo, endAngle = Angulo + Arco_en_grados (como Aether)
var topLeft = new Vector2(Left, Top);
Simulation_TransporteCurva.Create(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados, topLeft, 0);
// Sincronizar con la visualización 3D tras la actualización
simulationManager?.Visualization3DManager?.SynchronizeWorld();
}
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;
// Actualizar geometrías en BEPU después del redimensionamiento
ActualizarGeometrias();
}
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 +285,39 @@ 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)
{
if (_osMotor is osVMmotorSim motor)
VelocidadActual = motor.Velocidad;
// 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
_osMotor = ObtenerLink(Motor, typeof(osVMmotorSim));
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 ucTransporteCurva uc)
Simulation_TransporteCurva = AddCurve(RadioInterno,RadioExterno, Angulo, Angulo + Arco_en_grados);
// AddCurve(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 position)
{
// ✅ CORRIGIDO: startAngle = Angulo, endAngle = Angulo + Arco_en_grados (como Aether)
var topLeft = new Vector2(Left, Top);
Simulation_TransporteCurva = simulationManager?.AddCurve(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados, topLeft, 0);
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
}
}
public override void ucUnLoaded()
{
@ -126,6 +331,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 +347,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,622 @@
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;
using System.Numerics;
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("Ángulo de apertura en grados para los extremos de las guías")]
[property: Name("Ángulo Apertura Guías")]
private float anguloAperturaGuias = 5f;
partial void OnAnguloAperturaGuiasChanged(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)
{
OnPropertyChanged(nameof(AnguloFinal));
ActualizarGeometrias();
}
// ✅ NUEVO: Manejar cambios de posición usando métodos virtuales de osBase
public override void LeftChanging(float oldValue, float newValue)
{
base.LeftChanging(oldValue, newValue);
ActualizarPosicionBEPU();
}
public override void TopChanging(float oldValue, float newValue)
{
base.TopChanging(oldValue, newValue);
ActualizarPosicionBEPU();
}
/// <summary>
/// ✅ CORREGIDO: Actualizar posición en BEPU usando métodos de conversión apropiados
/// </summary>
private void ActualizarPosicionBEPU()
{
if (Simulation_TransporteCurvaGuias != null)
{
// ✅ USAR MÉTODOS DE COORDINATECONVERTER
var topLeft = new Vector2(Left, Top);
Simulation_TransporteCurvaGuias.UpdateFromWpfParameters(topLeft, 0f, RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
// Recrear las guías en la nueva posición
ActualizarGuiasCurvas();
// Sincronizar con la visualización 3D tras la actualización
simulationManager?.Visualization3DManager?.SynchronizeWorld();
}
}
[Hidden]
public float AnguloFinal
{
get => Angulo + Arco_en_grados;
}
private void ActualizarGeometrias()
{
if (_visualRepresentation is ucTransporteCurvaGuias uc)
{
// ✅ USAR MÉTODO DE COORDINATECONVERTER PARA ACTUALIZACIÓN COMPLETA
if (Simulation_TransporteCurvaGuias != null)
{
var topLeft = new Vector2(Left, Top);
Simulation_TransporteCurvaGuias.UpdateFromWpfParameters(topLeft, 0f, 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;
// ✅ CORREGIDO: Convertir ángulos a radianes usando método estándar
float anguloInicioRad = simBase.GradosARadianes(Angulo);
float anguloFinalRad = simBase.GradosARadianes(AnguloFinal);
float rangoAngular = anguloFinalRad - anguloInicioRad;
float anguloAperturaRad = simBase.GradosARadianes(AnguloAperturaGuias);
// Calcular el paso angular entre segmentos
float pasoAngular = rangoAngular / NumeroSegmentosGuias;
// Obtener el centro una vez para todo el método
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;
// Calcular radios ajustados para crear apertura en cono
// Para guía superior: reducir radio en los extremos para crear apertura
float radio1 = radioGuiaSuperior;
float radio2 = radioGuiaSuperior;
// Calcular reducción del radio basada en el ángulo de apertura
float reduccionRadio = radioGuiaSuperior * (float)Math.Sin(anguloAperturaRad/10);
if (InvertirDireccion && i == 0) // Primer segmento
radio1 += reduccionRadio;
if (!InvertirDireccion && i == NumeroSegmentosGuias - 1) // Último segmento
radio2 += reduccionRadio;
Vector2 punto1 = new Vector2(
radio1 * (float)Math.Cos(angulo1),
radio1 * (float)Math.Sin(angulo1)
);
Vector2 punto2 = new Vector2(
radio2 * (float)Math.Cos(angulo2),
radio2 * (float)Math.Sin(angulo2)
);
// Ajustar por la posición del objeto
punto1 += centro;
punto2 += centro;
simGuia guiaSegmento = CrearGuiaDesdeDosPuntos(punto1, punto2);
if (guiaSegmento != null)
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;
// Calcular radios ajustados para crear apertura en cono
// Para guía inferior: aumentar radio en los extremos para crear apertura
float radio1 = radioGuiaInferior;
float radio2 = radioGuiaInferior;
// Calcular aumento del radio basada en el ángulo de apertura
float aumentoRadio = radioGuiaInferior * (float)Math.Sin(anguloAperturaRad/10);
if (InvertirDireccion && i == 0) // Primer segmento
radio1 -= aumentoRadio;
if (InvertirDireccion && i == NumeroSegmentosGuias - 1) // Último segmento
radio2 -= aumentoRadio;
Vector2 punto1 = new Vector2(
radio1 * (float)Math.Cos(angulo1),
radio1 * (float)Math.Sin(angulo1)
);
Vector2 punto2 = new Vector2(
radio2 * (float)Math.Cos(angulo2),
radio2 * (float)Math.Sin(angulo2)
);
// Ajustar por la posición del objeto
punto1 += centro;
punto2 += centro;
simGuia guiaSegmento = CrearGuiaDesdeDosPuntos(punto1, punto2);
if (guiaSegmento != null)
GuiasInferiores.Add(guiaSegmento);
}
}
/// <summary>
/// ✅ CORREGIDO: Método helper para crear una guía desde dos puntos usando conversiones apropiadas
/// Convierte dos puntos Vector2 a los parámetros requeridos por AddLine
/// </summary>
private simGuia CrearGuiaDesdeDosPuntos(Vector2 punto1, Vector2 punto2)
{
try
{
// Calcular la longitud entre los dos puntos
var direccion = punto2 - punto1;
float longitud = direccion.Length();
if (longitud < 0.001f) // Evitar líneas de longitud cero
return null;
// ✅ CORREGIDO: Calcular el ángulo de la línea correctamente para WPF
float angulo = (float)Math.Atan2(direccion.Y, direccion.X) * 180f / (float)Math.PI;
// ✅ USAR punto1 como topLeft - simulationManager.AddLine ya maneja las conversiones WPF->BEPU
return simulationManager.AddLine(longitud, GrosorGuias, punto1, angulo);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error creando guía desde dos puntos: {ex.Message}");
return null;
}
}
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;
// ✅ NUEVO: Actualizar geometrías en BEPU después del redimensionamiento
ActualizarGeometrias();
}
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)
{
// ✅ CORREGIDO: Usar simulationManager?.AddCurve con conversión WPF correcta
var topLeft = new Vector2(Left, Top);
Simulation_TransporteCurvaGuias = simulationManager?.AddCurve(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados, topLeft, 0);
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

@ -2,48 +2,64 @@
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: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"/>
<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 x:Key="BeltBrush" TileMode="Tile" Viewport="0,0,20,10" ViewportUnits="Absolute" Viewbox="0,0,20,10"
ViewboxUnits="Absolute">
<VisualBrush.Transform>
<TransformGroup>
<TranslateTransform/>
<TranslateTransform />
</TransformGroup>
</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>
</UserControl.Resources>
<UserControl.DataContext>
<vm:osTransporteGuias/>
<vm:osTransporteGuias />
</UserControl.DataContext>
<Grid>
<Canvas>
<Canvas RenderTransformOrigin="0,0">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform Angle="{Binding Angulo}" />
<TranslateTransform />
</TransformGroup>
</Canvas.RenderTransform>
<StackPanel x:Name="RectanglesContainer">
<StackPanel.RenderTransform>
<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}}"
<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="Blue"/>
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,15 @@
using System.Windows;
using System.ComponentModel;
using System.Linq;
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;
using Siemens.Simatic.Simulation.Runtime;
namespace CtrEditor.ObjetosSim
{
@ -12,7 +18,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 +27,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,68 +41,286 @@ 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;
public override void AltoChanged(float value)
{
ActualizarGeometrias();
}
partial void OnAltoChanged(float value)
public override void AnchoChanged(float value)
{
ActualizarGeometrias();
}
public override void AnguloChanged(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)
{
System.Diagnostics.Debug.WriteLine($"[🔧 FRENO CAMBIO] Transporte '{Nombre}' - EsFreno cambiado a: {value}");
ActualizarGeometrias();
// Verificar que la asignación se realizó correctamente
if (SimGeometria != null)
{
System.Diagnostics.Debug.WriteLine($"[✅ FRENO SYNC] SimGeometria.isBrake = {SimGeometria.isBrake}");
}
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Coeficiente de fricción")]
[property: Name("Coeficiente Fricción")]
public float frictionCoefficient;
partial void OnFrictionCoefficientChanged(float value)
{
ActualizarGeometrias();
}
[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("Información")]
[property: Description("Información sobre botellas en el transporte con freno")]
[property: Name("Info Botellas Freno")]
public string infoBotellasFreno = "Sin información";
/// <summary>
/// Actualiza la información de botellas en el transporte con freno
/// </summary>
public void ActualizarInfoBotellasFreno()
{
if (SimGeometria?.isBrake == true && simulationManager != null)
{
// Contar botellas marcadas como en transporte con freno
var botellasEnFreno = simulationManager.Cuerpos
.OfType<simBotella>()
.Where(b => b.isOnBrakeTransport)
.Count();
InfoBotellasFreno = $"Botellas con freno: {botellasEnFreno}";
}
else if (SimGeometria?.isBrake == false)
{
InfoBotellasFreno = "Transporte SIN freno";
}
else
{
InfoBotellasFreno = "Sin información";
}
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Distancia entre guías")]
[property: Name("Distancia")]
private float distance;
partial void OnDistanceChanged(float value)
{
ActualizarGeometrias();
}
[ObservableProperty]
[property: Category("Configuración")]
[property: Description("Alto de las guías")]
[property: Name("Alto Guía")]
private float altoGuia;
partial void OnAltoGuiaChanged(float value)
{
ActualizarGeometrias();
}
private void ActualizarGeometrias()
{
if (_visualRepresentation is ucTransporteGuias uc)
{
UpdateRectangle(SimGeometria, uc.Transporte, Alto, Ancho, Angulo);
UpdateOrCreateLine(Guia_Superior, uc.GuiaSuperior);
UpdateOrCreateLine(Guia_Inferior, uc.GuiaInferior) ;
SimGeometria.DistanceGuide2Guide = Alto;
SimGeometria.Speed = VelocidadActual;
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
// Actualizar guías con método específico para transporte
UpdateTransportGuide(Guia_Superior, uc.GuiaSuperior, isTopGuide: true);
UpdateTransportGuide(Guia_Inferior, uc.GuiaInferior, isTopGuide: false);
if (SimGeometria != null)
{
SimGeometria.TransportWithGuides = true;
SimGeometria.DistanceGuide2Guide = Distance;
SimGeometria.isBrake = EsFreno; // Usar propiedad generada
SimGeometria.Friction = FrictionCoefficient; // Usar propiedad generada
System.Diagnostics.Debug.WriteLine($"[🔄 ACTUALIZAR GEOMETRIAS] Transporte '{Nombre}' - TransportWithGuides={SimGeometria.TransportWithGuides}, DistanceGuide2Guide={SimGeometria.DistanceGuide2Guide:F3}, isBrake={SimGeometria.isBrake}, Friction={SimGeometria.Friction:F3}, Speed={SimGeometria.Speed:F1}");
// Actualizar información de botellas si es un transporte con freno
ActualizarInfoBotellasFreno();
}
SetSpeed();
}
}
/// <summary>
/// Actualiza una guía específica del transporte (superior o inferior)
/// </summary>
/// <param name="simGuia">Objeto de simulación de la guía</param>
/// <param name="wpfRect">Rectángulo WPF de la guía</param>
/// <param name="isTopGuide">true para guía superior, false para inferior</param>
private void UpdateTransportGuide(simGuia simGuia, System.Windows.Shapes.Rectangle wpfRect, bool isTopGuide)
{
if (simGuia != null && wpfRect != null)
{
// Actualizar propiedades específicas de la guía
simGuia.UpdateProperties(Ancho, AltoGuia, Angulo);
// Calcular posición usando el rectángulo WPF actual
var topLeft2D = GetRectangleTopLeft(wpfRect);
// Crear/actualizar la guía con las dimensiones correctas
simGuia.Create(Ancho, AltoGuia, topLeft2D, Angulo);
System.Diagnostics.Debug.WriteLine($"[UpdateTransportGuide] {(isTopGuide ? "Superior" : "Inferior")} - Pos: ({topLeft2D.X:F3}, {topLeft2D.Y:F3}), Ancho: {Ancho:F3}, Alto: {AltoGuia:F3}, Ángulo: {Angulo:F1}°");
}
}
/// <summary>
/// Crea una guía específica del transporte (superior o inferior)
/// </summary>
/// <param name="wpfRect">Rectángulo WPF de la guía</param>
/// <param name="isTopGuide">true para guía superior, false para inferior</param>
/// <returns>Objeto simGuia creado</returns>
private simGuia CreateTransportGuide(System.Windows.Shapes.Rectangle wpfRect, bool isTopGuide)
{
if (wpfRect != null)
{
// Calcular posición usando el rectángulo WPF actual
var topLeft2D = GetRectangleTopLeft(wpfRect);
// Crear la guía con las dimensiones del transporte
var simGuia = simulationManager.AddLine(Ancho, AltoGuia, topLeft2D, Angulo);
System.Diagnostics.Debug.WriteLine($"[CreateTransportGuide] {(isTopGuide ? "Superior" : "Inferior")} creada - Pos: ({topLeft2D.X:F3}, {topLeft2D.Y:F3}), Ancho: {Ancho:F3}, Alto: {AltoGuia:F3}, Ángulo: {Angulo:F1}°");
return simGuia;
}
return null;
}
public override void OnMoveResizeRotate()
{
ActualizarGeometrias();
}
public osTransporteGuias()
{
Ancho = 1;
Alto = 0.10f;
AltoGuia = 0.03f;
Distance = 0.01f;
Distance = 0.5f;
Tag_ReleActivatedMotor = "1";
}
public override void UpdateGeometryStart()
@ -100,40 +328,58 @@ 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 (Motor != null)
if (Motor is osVMmotorSim id_motor)
if (LeerBitTag(Tag_ReleActivatedMotor))
VelocidadActual = id_motor.Velocidad;
else
VelocidadActual = 0;
// Actualizar información de botellas en tiempo real si es un transporte con freno
if (EsFreno)
{
if (_osMotor is osVMmotorSim motor)
VelocidadActual = motor.Velocidad;
} else if (Motor.Length > 0)
_osMotor = ObtenerLink(Motor, typeof(osVMmotorSim));
ActualizarInfoBotellasFreno();
}
}
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
if (_visualRepresentation is ucTransporteGuias uc)
{
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
SimGeometria.TransportWithGuides = true;
SimGeometria.DistanceGuide2Guide = Alto;
Guia_Superior = AddLine(simulationManager, uc.GuiaSuperior);
Guia_Inferior = AddLine(simulationManager, uc.GuiaInferior);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte);
// CORREGIR: Asegurar que todas las propiedades se inicialicen correctamente
if (SimGeometria != null)
{
SimGeometria.TransportWithGuides = true; // Siempre true para TransporteGuias
SimGeometria.DistanceGuide2Guide = Distance; // Usar Distance en lugar de Alto
SimGeometria.isBrake = EsFreno; // Usar propiedad generada
SimGeometria.Friction = FrictionCoefficient; // Usar propiedad generada
}
Motor = Motor; // Forzar la busqueda
// Crear guías usando método específico para transporte
Guia_Superior = CreateTransportGuide(uc.GuiaSuperior, isTopGuide: true);
Guia_Inferior = CreateTransportGuide(uc.GuiaInferior, isTopGuide: false);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
SetSpeed(); // Aplicar velocidad inicial
}
OnId_MotorChanged(Id_Motor); // Link Id_Motor = Motor
}
public override void ucUnLoaded()
{
@ -150,6 +396,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 +412,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>
<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,13 @@
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;
using System.Numerics;
namespace CtrEditor.ObjetosSim
{
@ -18,58 +18,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)
{
if (value != velocidadActual)
SetSpeed();
if (_visualRepresentation is ucTransporteTTop uc)
{
velocidadActual = value;
SimGeometria?.SetSpeed(value);
SetProperty(ref velocidadActual, value);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
}
}
[ObservableProperty]
public string motor;
partial void OnMotorChanged(string value)
void SetSpeed()
{
_osMotor = ObtenerLink(Motor, typeof(osVMmotorSim));
if (InvertirDireccion)
SimGeometria?.SetSpeed(-VelocidadActual);
else
SimGeometria?.SetSpeed(VelocidadActual);
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
}
[ObservableProperty]
public float ancho;
[property: Description("Tag de bit para activar enlace con motor")]
[property: Category("Enlace PLC")]
[property: Name("Tag Activación Motor")]
string tag_ReleActivatedMotor;
[ObservableProperty]
public float alto;
[ObservableProperty]
public float angulo;
[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;
}
}
}
[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 +141,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 +170,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)
if (Motor != null)
if (Motor is osVMmotorSim motor)
if (LeerBitTag(Tag_ReleActivatedMotor))
VelocidadActual = motor.Velocidad;
}
else if (Motor.Length > 0)
_osMotor = ObtenerLink(Motor, typeof(osVMmotorSim));
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)
{
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);
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 +251,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 +267,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>
<Label Grid.Row="0" Content="{Binding Nombre}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Transparent"
Foreground="Black"/>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Row="1" Source="{Binding ImageSource_oculta}"
<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"
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
{
@ -22,27 +25,79 @@ namespace CtrEditor.ObjetosSim
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,9 +162,11 @@ 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()
@ -82,17 +174,31 @@ namespace CtrEditor.ObjetosSim
// 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

@ -5,19 +5,22 @@
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: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>

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