using CommunityToolkit.Mvvm.ComponentModel; using CtrEditor.FuncionesBase; using CtrEditor.Simulacion; using LibS7Adv; using nkast.Aether.Physics2D.Common; using Siemens.Simatic.Simulation.Runtime; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Shapes; using Tesseract; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; using ItemCollection = Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection; using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute; namespace CtrEditor.ObjetosSim { public interface IosBase { static abstract string NombreClase(); } public interface IDataContainer { [JsonIgnore] osBase? Datos { get; set; } void Highlight(bool State); ZIndexEnum ZIndex(); } public class DataSaveToSerialize { private MainViewModel? _mainViewModel; private UserControl? VisualRepresentation; private SimulationManagerFP? simulationManager; public DataSaveToSerialize(MainViewModel a, UserControl b, SimulationManagerFP c) { _mainViewModel = a; VisualRepresentation = b; simulationManager = c; } public void DataRestoreAfterSerialize(out MainViewModel a, out UserControl b, out SimulationManagerFP c) { a = _mainViewModel; b = VisualRepresentation; c = simulationManager; } } public abstract partial class osBase : ObservableObject { public virtual string Nombre { get; set; } = "osBase"; [JsonIgnore] private Storyboard _storyboard; [JsonIgnore] private System.Threading.Timer timer = null; public UniqueId Id { get; set; } [ObservableProperty] [property: Description("X coordinate.")] [property: Category("Layout:")] private float left; public void CheckData() { if (Id is null) Id = new UniqueId().ObtenerNuevaID(); } partial void OnLeftChanged(float value) { CanvasSetLeftinMeter(value); LeftChanged(value); } partial void OnLeftChanging(float oldValue, float newValue) { LeftChanging(oldValue, newValue); } public virtual void LeftChanged(float value) { } public virtual void LeftChanging(float oldValue, float newValue) { } [ObservableProperty] [property: Description("Y coordinate.")] [property: Category("Layout:")] private float top; partial void OnTopChanged(float value) { CanvasSetTopinMeter(value); TopChanged(value); } partial void OnTopChanging(float oldValue, float newValue) { TopChanging(oldValue, newValue); } public virtual void TopChanged(float value) { } public virtual void TopChanging(float oldValue, float newValue) { } [ObservableProperty] [property: Description("Widht.")] [property: Category("Layout:")] private float ancho; partial void OnAnchoChanged(float value) { AnchoChanged(value); } public virtual void AnchoChanged(float value) { } [ObservableProperty] [property: Description("Height.")] [property: Category("Layout:")] private float alto; partial void OnAltoChanged(float value) { AltoChanged(value); } public virtual void AltoChanged(float value) { } [ObservableProperty] [property: Description("Angle.")] [property: Category("Layout:")] private float angulo; partial void OnAnguloChanged(float value) { AnguloChanged(value); } public virtual void AnguloChanged(float value) { } public void Resize(double width, double height) { Resize((float)width, (float)height); } public void Resize(float Delta_W_pixels, float Delta_H_pixels) { var Delta_W = PixelToMeter.Instance.calc.PixelsToMeters(Delta_W_pixels); var Delta_H = PixelToMeter.Instance.calc.PixelsToMeters(Delta_H_pixels); OnResize(Delta_W, Delta_H); OnMoveResizeRotate(); if ((Angulo >= 45 && Angulo <= 135) || (Angulo >= 225 && Angulo <= 315)) { Ancho += Delta_H; Alto += Delta_W; } else { Ancho += Delta_W; Alto += Delta_H; } if (Ancho < 0.01f) Ancho = 0.01f; if (Alto < 0.01f) Alto = 0.01f; } public virtual void OnResize(float Delta_Width, float Delta_Height) { } public void Move(double LeftPixels, double TopPixels) { Move((float)LeftPixels, (float)TopPixels); } public void Move(float LeftPixels, float TopPixels) { Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels); Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels); OnMove(LeftPixels, TopPixels); OnMoveResizeRotate(); } public virtual void OnMove(float LeftPixels, float TopPixels) { } public void Rotate(double Delta_Angle) { Rotate((float)Delta_Angle); } public void Rotate(float Delta_Angle) { Angulo += Delta_Angle; if (Angulo <= -360) Angulo += 360; if (Angulo >= 360) Angulo -= 360; OnRotate(Delta_Angle); OnMoveResizeRotate(); } public virtual void OnRotate(float Delta_Angle) { } public virtual void OnMoveResizeRotate() { } public void InicializaNuevoObjeto() { Id = new UniqueId().ObtenerNuevaID(); } // Group [ObservableProperty] [property: Description("This is a link to a faceplate. It works like an anchor.")] [property: Category("Group:")] [property: ItemsSource(typeof(osBaseItemsSource))] private string group_Panel; [JsonIgnore] private osTextPlate TextPlate; [JsonIgnore] private bool isUpdatingFromTextPlate = false; partial void OnGroup_PanelChanged(string value) { if (TextPlate != null) TextPlate.PropertyChanged -= OnTextPlatePropertyChanged; if (_mainViewModel != null && value != null && value.Length > 0) { TextPlate = (osTextPlate)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osTextPlate && s.Nombre == value), null); if (TextPlate != null) TextPlate.PropertyChanged += OnTextPlatePropertyChanged; } } private void OnTextPlatePropertyChanged(object sender, PropertyChangedEventArgs e) { if (!isUpdatingFromTextPlate) { isUpdatingFromTextPlate = true; if (e.PropertyName == nameof(osTextPlate.Nombre)) Group_Panel = ((osTextPlate)sender).Nombre; if (e.PropertyName == nameof(osTextPlate.Top)) Top += ((osTextPlate)sender).offsetY; if (e.PropertyName == nameof(osTextPlate.Left)) Left += ((osTextPlate)sender).offsetX; isUpdatingFromTextPlate = false; } } 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 string CaptureImageAreaAndDoOCR(float Left, float Top, float Ancho, float Alto, float Angulo = 0, bool ShowPreview = false) { if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo) { if (imagenDeFondo.Source is BitmapSource bitmapSource) { float originalDpiX = (float)bitmapSource.DpiX; float originalDpiY = (float)bitmapSource.DpiY; float canvasDpiX = 96; float canvasDpiY = 96; float scaleFactorX = originalDpiX / canvasDpiX; float scaleFactorY = originalDpiY / canvasDpiY; int x = (int)MeterToPixels(Left * scaleFactorX); int y = (int)MeterToPixels(Top * scaleFactorY); int width = (int)MeterToPixels(Ancho * scaleFactorX); int height = (int)MeterToPixels(Alto * scaleFactorY); 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; CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height)); TransformedBitmap transformedBitmap = new TransformedBitmap(); transformedBitmap.BeginInit(); transformedBitmap.Source = croppedBitmap; if (Angulo != 0) { RotateTransform rotateTransform = new RotateTransform(-Angulo); transformedBitmap.Transform = rotateTransform; } transformedBitmap.EndInit(); PngBitmapEncoder encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(transformedBitmap)); using (MemoryStream memoryStream = new MemoryStream()) { encoder.Save(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); if (ShowPreview) ShowPreviewWindow(memoryStream); using (var bmp = new System.Drawing.Bitmap(memoryStream)) { int targetDpi = 400; var resizedBmp = new System.Drawing.Bitmap(bmp, new System.Drawing.Size(bmp.Width * targetDpi / (int)originalDpiX, bmp.Height * targetDpi / (int)originalDpiY)); using (var msResized = new MemoryStream()) { resizedBmp.Save(msResized, System.Drawing.Imaging.ImageFormat.Png); msResized.Seek(0, SeekOrigin.Begin); using (var img = Pix.LoadFromMemory(msResized.ToArray())) using (var engine = new TesseractEngine(@"./Tesseract", "eng", EngineMode.Default)) { // Configuraciones para mejorar el OCR de una sola letra engine.SetVariable("tessedit_char_whitelist", " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-."); // Lista blanca de caracteres var result = engine.Process(img); return result.GetText(); } } } } } } return ""; } // Método para obtener un color más claro y saturado public static Color ObtenerColorMasClaroYSaturado(Color colorOriginal, double incrementoL, double incrementoS) { return ColorUtils.ObtenerColorMasClaroYSaturado(colorOriginal, incrementoL, incrementoS); } // Método para obtener un color más claro public Color ObtenerColorMasClaro(Color colorOriginal, double incremento) { incremento = Math.Clamp(incremento, 0, 1); // Asegurarse de que el incremento esté entre 0 y 1 byte r = (byte)(colorOriginal.R + (255 - colorOriginal.R) * incremento); byte g = (byte)(colorOriginal.G + (255 - colorOriginal.G) * incremento); byte b = (byte)(colorOriginal.B + (255 - colorOriginal.B) * incremento); return Color.FromArgb(colorOriginal.A, r, g, b); } // All Pages Objects [NotifyPropertyChangedFor(nameof(Show_On_This_Page))] [ObservableProperty] [property: Description("Enable this object to be used in all pages.")] [property: Category("Layout:")] private bool enable_On_All_Pages; partial void OnEnable_On_All_PagesChanged(bool value) { if (!value) Show_On_This_Page = true; } [ObservableProperty] [property: Hidden] private List showOnThisPagesList; private bool show_On_This_Page; [property: Category("Layout:")] public bool Show_On_This_Page { get { if (!Enable_On_All_Pages) { showOnThisPagesList?.Clear(); showOnThisPagesList = null; show_On_This_Page = true; } else { if (showOnThisPagesList == null) { showOnThisPagesList = new List { _mainViewModel?.SelectedImage }; } show_On_This_Page = _mainViewModel?.SelectedImage != null && showOnThisPagesList.Contains(_mainViewModel.SelectedImage); } return show_On_This_Page; } set { if (Enable_On_All_Pages) { if (showOnThisPagesList == null) { showOnThisPagesList = new List(); } if (_mainViewModel?.SelectedImage == null) return; if (value) { if (!showOnThisPagesList.Contains(_mainViewModel.SelectedImage)) { showOnThisPagesList.Add(_mainViewModel.SelectedImage); } } else { showOnThisPagesList.Remove(_mainViewModel.SelectedImage); } } SetProperty(ref show_On_This_Page, value); } } [ObservableProperty] [property: Description("Autocreated and cloned with Search Templates")] [property: Category("Tag Extraction:")] bool cloned; [ObservableProperty] [property: Description("Autocreated and cloned with Search Templates")] [property: Category("Tag Extraction:")] [property: Hidden] UniqueId cloned_from; private async void TimerCallback(object state) { await Task.Delay(500); // Esperar 0.5 segundos antes de ejecutar la función Application.Current.Dispatcher.Invoke(() => { // Realiza tus cambios en la interfaz de usuario aquí OnTimerAfterMovement(); }); } /// /// Este timer se usa para llamar a OnTimerAfterMovement luego de que han pasado 500ms sin que se /// llame a ResetTimer. /// Esto es util para no actualizar las simulaciones o controles mientras se esta moviendo aun el control. /// Se debe hacer overrride de OnTimerAfterMovement y dentro de esta funcion llamar a la actualizacion que se /// requiere hacer una vez terminado el movimiento del control por parte del usuario. /// public void ResetTimer() { if (timer == null) timer = new System.Threading.Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite); timer.Change(500, Timeout.Infinite); } public virtual void OnTimerAfterMovement() { } /// /// Usado para saber cuando un objeto fue creado manualmente o por algun generador /// Mas adelante la idea es poder eliminar o tratar de manera diferente a estos objetos /// public bool AutoCreated = false; /// /// Son los objetos marcados para ser eliminados luego de se detectados por un sensor del mundo fisico /// Se deben eliminar fuera de la simulacion /// public bool RemoverDesdeSimulacion = false; // La simulacion indica que se debe remover [JsonIgnore] private DataSaveToSerialize DataSave; /// /// Link al UserControl. Inicializado en el evento OnLoaded /// [JsonIgnore] protected UserControl? _visualRepresentation = null; /// /// Link al control PLC. /// Inicializado en el metodo SetPLC /// [JsonIgnore] protected PLCViewModel? _plc = null; /// /// Se llama luego de cada Step. Esto permite actualziar las posiciones graficas de cada objeto que se esta dibujando /// con las nuevas posiciones calculadas luego de la simulacion fisica. /// /// public virtual void UpdateControl(int elapsedMilliseconds) { } /// /// Se llama antes de comenzar la simulacion con el boton de Iniciar simulacion. /// La idea es actualizar los objetos en el motor fisico antes de comenzar la simulacion fisica. /// public virtual void UpdateGeometryStart() { } /// /// Se llama al terminar la simulacion con el boton de Detener simulacion. /// Se puede utilizar para detener los storyboard o alguna otra simulacion independiente /// public virtual void SimulationStop() { } /// /// Se llama antes de cada Step al World de la simulacion fisica. /// Permite actualizar el estado de los objetos de la simulacion fisica /// public virtual void UpdateGeometryStep() { } /// /// Es llamada en cada Tick del reloj establecido del PLC /// cuando la conexion con el PLC esta establecida. /// /// /// public virtual void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) { } /// /// El UserControl ya se ha cargado y podemos obtener las coordenadas para /// crear el objeto de simulacion /// public virtual void ucLoaded() { ActualizarLeftTop(); OnGroup_PanelChanged(Group_Panel); // Establece el link y se suscribe a los eventos Show_On_This_Page = Show_On_This_Page; // Update data } /// /// El UserControl se esta eliminando /// eliminar el objeto de simulacion /// public virtual void ucUnLoaded() { } [JsonIgnore] public MainViewModel _mainViewModel; /// /// Link al UserControl. Inicializado en el evento OnLoaded /// [JsonIgnore] public UserControl? VisualRepresentation { get => _visualRepresentation; set => _visualRepresentation = value; } /// /// Link al Simualdor fisico. /// [JsonIgnore] public SimulationManagerFP simulationManager; /// /// Prepara la clase para ser serializable poniendo a null los objetos que tienen referencias circulares /// Luego se debe llamar a RestaurarDatosNoSerializables para restaurar el estado original /// public void SalvarDatosNoSerializables() { DataSave = new DataSaveToSerialize(_mainViewModel, _visualRepresentation, simulationManager); _mainViewModel = null; _visualRepresentation = null; simulationManager = null; } /// /// Restaura los links de los objetos que se eliminaron temporalmente para serializar. /// public void RestaurarDatosNoSerializables() { if (DataSave == null) return; DataSave.DataRestoreAfterSerialize(out _mainViewModel, out _visualRepresentation, out simulationManager); } /// /// Se llama una unica vez al conectar con el PLC. /// /// public void SetPLC(PLCViewModel plc) { _plc = plc; UpdatePLCPrimerCiclo(); } /// /// Se llama una unica vez al conectar con el PLC /// public virtual void UpdatePLCPrimerCiclo() { } protected Storyboard CrearAnimacionMultiStoryBoardTrasnporte(Rectangle transporte, bool invertirDireccion) { if (_visualRepresentation == null) return null; if (transporte == null) return null; Storyboard storyboard = new Storyboard(); var animation = new DoubleAnimation { From = invertirDireccion ? 20 : 0, To = invertirDireccion ? 0 : 20, // Total Pixels Brush Duration = TimeSpan.FromSeconds(PixelToMeter.Instance.calc.PixelsToMeters(20) * 60), RepeatBehavior = RepeatBehavior.Forever }; Storyboard.SetTarget(animation, transporte); Storyboard.SetTargetProperty(animation, new PropertyPath("(Rectangle.Fill).(VisualBrush.Transform).(TransformGroup.Children)[0].(TranslateTransform.X)")); storyboard.Children.Add(animation); storyboard.Begin(); storyboard.SetSpeedRatio(0); return storyboard; } protected Storyboard CrearAnimacionMultiStoryBoardTrasnporte(Storyboard storyboard, Rectangle transporte, bool invertirDireccion) { // Detener y eliminar el storyboard existente si hay uno if (storyboard != null) { storyboard.Stop(); storyboard.Children.Clear(); } return CrearAnimacionMultiStoryBoardTrasnporte(transporte, invertirDireccion); } protected void ActualizarAnimacionMultiStoryBoardTransporte(Storyboard storyboard, float velocidadActual) { if (_visualRepresentation == null) return; if (storyboard == null) return; if (!_mainViewModel.IsSimulationRunning) storyboard.SetSpeedRatio(0); else storyboard.SetSpeedRatio(Math.Abs(velocidadActual)); } protected void CrearAnimacionStoryBoardTrasnporte(Rectangle transporte, bool invertirDireccion) { _storyboard = CrearAnimacionMultiStoryBoardTrasnporte(_storyboard, transporte, invertirDireccion); } protected void ActualizarAnimacionStoryBoardTransporte(float velocidadActual) { ActualizarAnimacionMultiStoryBoardTransporte(_storyboard, velocidadActual); } /// /// Actualiza Left Top del objeto respecto al Canvas padre. /// public void ActualizarLeftTop() { CanvasSetLeftinMeter(Left); CanvasSetTopinMeter(Top); } public bool LeerBitTag(string Tag) { if (this._plc == null) return false; if (!string.IsNullOrEmpty(Tag)) { if (Tag == "1") return true; else if (Tag == "0") return false; if (_plc != null) return _plc.LeerTagBool(Tag); } return false; } /// /// Si la conexion con el plc esta activa y el Tag no es nulo escribe el bit en el PLC /// /// /// public void EscribirBitTag(string Tag, bool Value) { if (_plc == null) return; if (!string.IsNullOrEmpty(Tag)) if (_plc != null) _plc.EscribirBool(Tag, Value); } public void EscribirWordTagScaled(string Tag, float Value, float IN_scale_Min, float IN_scale_Max, float OUT_scale_Min, float OUT_scale_Max) { if (_plc == null) return; if (!string.IsNullOrEmpty(Tag)) { SDataValue plcData = new SDataValue(); plcData.UInt16 = (ushort)((Value - IN_scale_Min) / (IN_scale_Max - IN_scale_Min) * (OUT_scale_Max - OUT_scale_Min) + OUT_scale_Min); _plc.EscribirTag(Tag, plcData); } } public float LeerWordTagScaled(string Tag, float IN_scale_Min = 0, float IN_scale_Max = 27648, float OUT_scale_Min = 0, float OUT_scale_Max = 100) { if (_plc == null) return 0; if (!string.IsNullOrEmpty(Tag)) { if (float.TryParse(Tag, out float v)) return v; if (_plc != null) { SDataValue plcData = _plc.LeerTag(Tag); float Value = plcData.UInt16; // WORD return (Value - IN_scale_Min) / (IN_scale_Max - IN_scale_Min) * (OUT_scale_Max - OUT_scale_Min) + OUT_scale_Min; } } return 0; } public float LeerWordTag(string Tag) { if (_plc == null) return 0; if (!string.IsNullOrEmpty(Tag)) { if (float.TryParse(Tag, out float v)) return v; if (_plc != null) { SDataValue plcData = _plc.LeerTag(Tag); float Value = plcData.UInt16; // WORD return Value; } } return 0; } public void CanvasSetLeftinMeter(float left) { if (_visualRepresentation != null) Canvas.SetLeft(_visualRepresentation, PixelToMeter.Instance.calc.MetersToPixels(left)); } public float CanvasGetLeftinMeter() { if (_visualRepresentation != null) return PixelToMeter.Instance.calc.PixelsToMeters((float)Canvas.GetLeft(_visualRepresentation)); else return 0f; } public void CanvasSetTopinMeter(float top) { if (_visualRepresentation != null) Canvas.SetTop(_visualRepresentation, PixelToMeter.Instance.calc.MetersToPixels(top)); } public float CanvasGetTopinMeter() { if (_visualRepresentation != null) return PixelToMeter.Instance.calc.PixelsToMeters((float)System.Windows.Controls.Canvas.GetTop(_visualRepresentation)); else return 0f; } public float PixelsToMeters(float pixel) { return PixelToMeter.Instance.calc.PixelsToMeters(pixel); } public Vector2 PixelsToMeters(Vector2 pixel) { return new Vector2(PixelsToMeters(pixel.X), PixelsToMeters(pixel.Y)); } public float MeterToPixels(float meter) { return PixelToMeter.Instance.calc.MetersToPixels(meter); } public Vector2 MeterToPixels(Vector2 meter) { return new Vector2(MeterToPixels(meter.X), MeterToPixels(meter.Y)); } private static GeneralTransform GetTransformToRoot(Visual visual) { // Crear una transformación acumulativa inicial GeneralTransformGroup transformGroup = new GeneralTransformGroup(); // Recorrer hacia arriba el árbol visual, acumulando las transformaciones while (visual != null) { GeneralTransform transform = visual.TransformToAncestor(visual); transformGroup.Children.Insert(0, transform); visual = VisualTreeHelper.GetParent(visual) as Visual; } return transformGroup; } public (Vector2 TopLeft, Vector2 BottomRight) GetRectangleCoordinatesInMeter(Rectangle rect) { if (rect != null) { var _canvasLeft = CanvasGetLeftinMeter(); var _canvasTop = CanvasGetTopinMeter(); // Obtiene la transformada del objeto visual GeneralTransform transform = rect.TransformToAncestor(_visualRepresentation); // Obtiene la posición absoluta Point topLeft = transform.Transform(new Point(0, 0)); Point bottomRight = transform.Transform(new Point(rect.ActualWidth, rect.ActualHeight)); return (new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.Y) + _canvasTop), new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.Y) + _canvasTop)); } else return (new Vector2(0, 0), new Vector2(0, 0)); } public (Vector2 Start, Vector2 End) GetCenterLineVectors(Rectangle rect) { if (rect == null) return (new Vector2(0, 0), new Vector2(0, 0)); Vector2 start = new Vector2(0, 0); Vector2 end = new Vector2(0, 0); // Usar Dispatcher para asegurar la ejecución en el hilo correcto _visualRepresentation?.Dispatcher.Invoke(() => { // Asegúrate de que el control está en el árbol visual y actualizado if (_visualRepresentation.IsLoaded && rect.IsLoaded) { _visualRepresentation.UpdateLayout(); var _canvasLeft = CanvasGetLeftinMeter(); var _canvasTop = CanvasGetTopinMeter(); var transform = rect.TransformToAncestor(_visualRepresentation); // Puntos en coordenadas locales del rectángulo no rotado Point startLocal = new Point(0, rect.ActualHeight / 2); Point endLocal = new Point(rect.ActualWidth, rect.ActualHeight / 2); // Transformar estos puntos al sistema de coordenadas del ancestro Point transformedStart = transform.Transform(startLocal); Point transformedEnd = transform.Transform(endLocal); // Convierte a unidades de Farseer (metros en este caso) start = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.Y) + _canvasTop); end = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.Y) + _canvasTop); } }); return (start, end); } public Vector2 GetCurveCenterInMeter(float RadioExterno) { var _canvasLeft = CanvasGetLeftinMeter(); var _canvasTop = CanvasGetTopinMeter(); // El centro del Canvas double centerX = RadioExterno + _canvasLeft; double centerY = RadioExterno + _canvasTop; // Convertir a Vector2 return new Vector2((float)centerX, (float)centerY); } public Vector2 GetRectangleCenter(Rectangle wpfRect) { var coords = GetRectangleCoordinatesInMeter(wpfRect); Vector2 topLeft = coords.TopLeft; Vector2 bottomRight = coords.BottomRight; // Calcular el centro, ancho y alto en metros return new Vector2((topLeft.X + bottomRight.X) / 2, (topLeft.Y + bottomRight.Y) / 2); } public void UpdateRectangle(simTransporte simRect, Rectangle wpfRect, float Alto, float Ancho, float Angulo) { if (simRect != null) simRect.Create(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo); } public void UpdateRectangle(simBarrera simRect, Rectangle wpfRect, float Alto, float Ancho, float Angulo) { if (simRect != null) simRect.Create(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo); } public void UpdateCurve(simCurve curva, float RadioInterno, float RadioExterno, float startAngle, float endAngle) { curva.Create(RadioInterno, RadioExterno, startAngle, endAngle, GetCurveCenterInMeter(RadioExterno)); } public simCurve AddCurve(float RadioInterno, float RadioExterno, float startAngle, float endAngle) { return simulationManager.AddCurve(RadioInterno, RadioExterno, startAngle, endAngle, GetCurveCenterInMeter(RadioExterno)); } public simTransporte AddRectangle(SimulationManagerFP simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo) { return simulationManager.AddRectangle(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo); } public simBarrera AddBarrera(SimulationManagerFP simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo, bool detectarCuello) { return simulationManager.AddBarrera(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo, detectarCuello); } public void UpdateOrCreateLine(simGuia simGuia, Rectangle wpfRect) { if (simGuia != null) { var coords = GetCenterLineVectors(wpfRect); // Crear o actualizar simRectangle simGuia.Create(coords.Start, coords.End); // asumiendo que el ángulo inicial es 0 } } public simGuia AddLine(SimulationManagerFP simulationManager, Rectangle wpfRect) { var coords = GetCenterLineVectors(wpfRect); return simulationManager.AddLine(coords.Start, coords.End); } public ImageSource ImageFromPath(string value) { if (value is string stringValue) { return new BitmapImage(new Uri(stringValue, UriKind.RelativeOrAbsolute)); } return null; } static protected T GetLastElement(List list) where T : class { if (list.Count > 0) { return list[list.Count - 1]; } else { return null; } } } public class UniqueId { public int Value { get; set; } public UniqueId ObtenerNuevaID() { Value = EstadoPersistente.Instance.newid; if (Value == 0) Value++; return this; } public void NoId() { // No ID == NULL Value = 0; } // Sobrecarga del operador ++ public static UniqueId operator ++(UniqueId id) { if (id == null) { id = new UniqueId(); } id.Value = EstadoPersistente.Instance.newid; if (id.Value == 0) id.Value++; return id; } // Sobrecarga del operador == public static bool operator ==(UniqueId id1, UniqueId id2) { if (ReferenceEquals(id1, null) || ReferenceEquals(id2, null)) return false; return id1.Value == id2.Value; } // Sobrecarga del operador != public static bool operator !=(UniqueId id1, UniqueId id2) { if (ReferenceEquals(id1, null) || ReferenceEquals(id2, null)) return true; return id1.Value != id2.Value; } // Sobrescribir Equals y GetHashCode también es una buena práctica public override bool Equals(object obj) { if (obj is UniqueId id) { return Value == id.Value; } return false; } public override int GetHashCode() { return Value.GetHashCode(); } } public class osBaseItemsSource : IItemsSource { public ItemCollection GetValues() { ItemCollection items = new ItemCollection(); var viewModel = (MainViewModel)App.Current.MainWindow.DataContext; var objetosSimulables = viewModel.ObjetosSimulables; items.Add(""); foreach (var obj in objetosSimulables) { if (obj.GetType() == typeof(T)) { items.Add(obj.Nombre); } } return items; } } public class TimerTON_TOFF { Stopwatch _stopwatch_ON = new Stopwatch(); Stopwatch _stopwatch_OFF = new Stopwatch(); private bool _senalFiltrada; public float Tiempo_ON_s { get; set; } public float Tiempo_OFF_s { get; set; } private bool senal; public bool Senal { get => senal; set { senal = value; if (value) { ReiniciarTimer(_stopwatch_ON); StopTimer(_stopwatch_OFF); } else { ReiniciarTimer(_stopwatch_OFF); StopTimer(_stopwatch_ON); } } } public TimerTON_TOFF() { Senal = false; Tiempo_ON_s = 1; Tiempo_OFF_s = 1; } public bool SenalFiltrada() { if (_stopwatch_ON.ElapsedMilliseconds > (Tiempo_ON_s * 1000)) _senalFiltrada = true; if (_stopwatch_OFF.ElapsedMilliseconds > (Tiempo_OFF_s * 1000)) _senalFiltrada = false; return _senalFiltrada; } void ReiniciarTimer(Stopwatch timer) { timer.Start(); } void StopTimer(Stopwatch timer) { timer.Reset(); timer.Stop(); } } [AttributeUsage(AttributeTargets.Property)] public class HiddenAttribute : Attribute { } public class ColorUtils { // Convertir RGB a HSL public static void RgbToHsl(Color color, out double h, out double s, out double l) { double r = color.R / 255.0; double g = color.G / 255.0; double b = color.B / 255.0; double max = Math.Max(r, Math.Max(g, b)); double min = Math.Min(r, Math.Min(g, b)); h = s = l = (max + min) / 2.0; if (max == min) { h = s = 0.0; // Gris } else { double d = max - min; s = l > 0.5 ? d / (2.0 - max - min) : d / (max + min); if (max == r) h = (g - b) / d + (g < b ? 6.0 : 0.0); else if (max == g) h = (b - r) / d + 2.0; else h = (r - g) / d + 4.0; h /= 6.0; } } // Convertir HSL a RGB public static Color HslToRgb(double h, double s, double l) { double r, g, b; if (s == 0.0) { r = g = b = l; // Gris } else { Func hue2rgb = (p, q, t) => { if (t < 0.0) t += 1.0; if (t > 1.0) t -= 1.0; if (t < 1.0 / 6.0) return p + (q - p) * 6.0 * t; if (t < 1.0 / 2.0) return q; if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6.0; return p; }; double q = l < 0.5 ? l * (1.0 + s) : l + s - l * s; double p = 2.0 * l - q; r = hue2rgb(p, q, h + 1.0 / 3.0); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1.0 / 3.0); } return Color.FromArgb(255, (byte)(r * 255.0), (byte)(g * 255.0), (byte)(b * 255.0)); } // Método para obtener un color más claro y saturado public static Color ObtenerColorMasClaroYSaturado(Color colorOriginal, double incrementoL, double incrementoS) { RgbToHsl(colorOriginal, out double h, out double s, out double l); l = Math.Clamp(l + incrementoL, 0.0, 1.0); s = Math.Clamp(s + incrementoS, 0.0, 1.0); return HslToRgb(h, s, l); } } }