From 0410c87e93e5510d5d5751499c0f928e66c4891e Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 4 Jun 2024 17:33:00 +0200 Subject: [PATCH] Agregado del TextPlate --- CtrEditor.csproj | 8 +- MainViewModel.cs | 24 +- MainWindow.xaml | 17 +- ObjetosSim/Decorativos/ucTextPlate.xaml | 29 +++ ObjetosSim/Decorativos/ucTextPlate.xaml.cs | 153 +++++++++++++ ObjetosSim/Emuladores/ucBottGenerator.xaml | 24 ++ ObjetosSim/Emuladores/ucBottGenerator.xaml.cs | 208 ++++++++++++++++++ ObjetosSim/Estaticos/ucTransporteGuias.xaml | 39 +++- .../Estaticos/ucTransporteGuias.xaml.cs | 11 +- .../Estaticos/ucTransporteGuiasUnion.xaml | 7 +- .../Estaticos/ucTransporteGuiasUnion.xaml.cs | 2 +- ObjetosSim/Estaticos/ucVMmotorSim.xaml | 36 +-- ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs | 10 +- ObjetosSim/SensoresComandos/ucBoton.xaml | 20 +- ObjetosSim/SensoresComandos/ucBoton.xaml.cs | 17 +- ObjetosSim/SensoresComandos/ucPhotocell.xaml | 24 +- ObjetosSim/UserControlFactory.cs | 11 +- .../UserControls/ThreeLinesControl.xaml.cs | 21 +- ObjetosSim/osBase.cs | 16 +- ObjetosSim/ucBasicExample.xaml | 58 ++--- ObjetosSim/ucBasicExample.xaml.cs | 158 ++++++++----- XAMLhelpers.cs | 4 +- imagenes/gear.png | Bin 0 -> 19118 bytes 23 files changed, 757 insertions(+), 140 deletions(-) create mode 100644 ObjetosSim/Decorativos/ucTextPlate.xaml create mode 100644 ObjetosSim/Decorativos/ucTextPlate.xaml.cs create mode 100644 ObjetosSim/Emuladores/ucBottGenerator.xaml create mode 100644 ObjetosSim/Emuladores/ucBottGenerator.xaml.cs create mode 100644 imagenes/gear.png diff --git a/CtrEditor.csproj b/CtrEditor.csproj index 6e88f75..b9c0c5b 100644 --- a/CtrEditor.csproj +++ b/CtrEditor.csproj @@ -10,7 +10,11 @@ - True + False + + + + False @@ -40,6 +44,7 @@ + @@ -92,6 +97,7 @@ + diff --git a/MainViewModel.cs b/MainViewModel.cs index 0db044f..c6fd2a0 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -15,6 +15,7 @@ using System.Diagnostics; using System.Reflection; using CommunityToolkit.Mvvm.ComponentModel; using Xceed.Wpf.Toolkit.PropertyGrid; +using System.Text.RegularExpressions; namespace CtrEditor @@ -282,7 +283,28 @@ namespace CtrEditor var NuevoObjetoDuplicado = JsonConvert.DeserializeObject(serializedData, settings); if (NuevoObjetoDuplicado != null) { - NuevoObjetoDuplicado.Nombre += "_Duplicado"; + string nombre = NuevoObjetoDuplicado.Nombre; + + // Expresión regular para identificar un nombre que termina con _número + Regex regex = new Regex(@"_(\d+)$"); + + if (regex.IsMatch(nombre)) + { + // Extraer el número actual y sumarle 1 + var match = regex.Match(nombre); + int numeroActual = int.Parse(match.Groups[1].Value); + int nuevoNumero = numeroActual + 1; + + // Reemplazar el número en el nombre + nombre = regex.Replace(nombre, $"_{nuevoNumero}"); + } + else + { + // Si no termina con _número, añadir _1 + nombre += "_1"; + } + + NuevoObjetoDuplicado.Nombre = nombre; NuevoObjetoDuplicado.Left += 0.5f; NuevoObjetoDuplicado.Top += 0.5f; ObjetosSimulables.Add(NuevoObjetoDuplicado); diff --git a/MainWindow.xaml b/MainWindow.xaml index 0501d22..a7780c7 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -168,11 +168,24 @@ + + + + + + + + + + + + + - + @@ -184,7 +197,7 @@ - + diff --git a/ObjetosSim/Decorativos/ucTextPlate.xaml b/ObjetosSim/Decorativos/ucTextPlate.xaml new file mode 100644 index 0000000..489d4bc --- /dev/null +++ b/ObjetosSim/Decorativos/ucTextPlate.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ObjetosSim/Decorativos/ucTextPlate.xaml.cs b/ObjetosSim/Decorativos/ucTextPlate.xaml.cs new file mode 100644 index 0000000..2974eb9 --- /dev/null +++ b/ObjetosSim/Decorativos/ucTextPlate.xaml.cs @@ -0,0 +1,153 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media.Animation; +using System.Windows.Media; +using CommunityToolkit.Mvvm.ComponentModel; + +using CtrEditor.Siemens; +using CtrEditor.Simulacion; +using System.Windows.Input; + + +namespace CtrEditor.ObjetosSim +{ + /// + /// Interaction logic for ucTextPlate.xaml + /// + /// + + public partial class osTextPlate : osBase, IosBase + { + + public static string NombreClase() + { + return "Plate"; + } + private string nombre = NombreClase(); + public override string Nombre + { + get => nombre; + set => SetProperty(ref nombre, value); + } + + [ObservableProperty] + Color color; + [ObservableProperty] + Color color_Titulo; + + [ObservableProperty] + string titulo; + [ObservableProperty] + public float alto_Titulo; + + + [ObservableProperty] + public float ancho; + [ObservableProperty] + public float alto; + [ObservableProperty] + public float angulo; + + public override void TopChanging(float oldValue, float newValue) { + UpdateChildsTop(newValue - oldValue); + } + public override void LeftChanging(float oldValue, float newValue) { + UpdateChildsLeft(newValue - oldValue); + } + + protected void UpdateChildsTop(float offsetY) + { + if (!string.IsNullOrEmpty(Nombre) && _mainViewModel != null) + { + foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables) + { + if (objetoSimulable != this && objetoSimulable.Group_Panel == Nombre) + { + objetoSimulable.Top += offsetY; + } + } + } + } + protected void UpdateChildsLeft(float offsetX) + { + if (!string.IsNullOrEmpty(Nombre) && _mainViewModel != null) + { + foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables) + { + if (objetoSimulable != this && objetoSimulable.Group_Panel == Nombre) + { + objetoSimulable.Left += offsetX; + } + } + } + } + + + 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 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 Resize(float width, float height) + { + if (Datos is osTextPlate 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 void Rotate(float Angle) + { + if (Datos != null) + if (Datos is osTextPlate datos) + datos.Angulo = Angle; + } + public void Highlight(bool State) { } + public int ZIndex() + { + return 1; + } + + } +} + + + diff --git a/ObjetosSim/Emuladores/ucBottGenerator.xaml b/ObjetosSim/Emuladores/ucBottGenerator.xaml new file mode 100644 index 0000000..24fc082 --- /dev/null +++ b/ObjetosSim/Emuladores/ucBottGenerator.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs b/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs new file mode 100644 index 0000000..f2cf9d0 --- /dev/null +++ b/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs @@ -0,0 +1,208 @@ +using CtrEditor.Siemens; +using System.Windows; +using System.Windows.Controls; +using CommunityToolkit.Mvvm.ComponentModel; +using System.Diagnostics; + +namespace CtrEditor.ObjetosSim +{ + /// + /// Interaction logic for ucBottGenerator.xaml + /// + public partial class osBottGenerator : osBase, IosBase + { + TimerTON_TOFF _TON_TOFF = new TimerTON_TOFF(); + // Otros datos y métodos relevantes para la simulación + + private float TiempoRestante; + private osBotella UltimaBotella; + + public static string NombreClase() + { + return "BottGenerator"; + } + private string nombre = NombreClase(); + public override string Nombre + { + get => nombre; + set => SetProperty(ref nombre, value); + } + + [ObservableProperty] + private float offsetLeftSalida; + [ObservableProperty] + private float offsetTopSalida; + + [ObservableProperty] + private string tag_consenso; + [ObservableProperty] + private bool consenso; + + partial void OnConsensoChanged(bool value) + { + _TON_TOFF.Senal = value; + } + [ObservableProperty] + private bool consenso_NC; + [ObservableProperty] + private bool consenso_Filtrado; + [ObservableProperty] + float filtro_consenso_s; + + [ObservableProperty] + private float botellas_hora; + + partial void OnBotellas_horaChanged(float value) + { + Botellas_segundo = value / 3600; + } + + [ObservableProperty] + private float botellas_segundo; + + partial void OnBotellas_segundoChanged(float value) + { + Botellas_hora = value * 3600; + } + + [ObservableProperty] + private float velocidad_actual_percentual; + [ObservableProperty] + private float diametro_botella; + [ObservableProperty] + private float ancho; + [ObservableProperty] + private float alto; + [ObservableProperty] + private float angulo; + + public osBottGenerator() + { + Ancho = 0.30f; + Alto = 0.30f; + Angulo = 0; + Velocidad_actual_percentual = 0; + Diametro_botella = 0.1f; + Botellas_hora = 10000; + Filtro_consenso_s = 1; + } + + public override void UpdatePLC(PLCModel plc, int elapsedMilliseconds) + { + if (Consenso_NC) + Consenso = !LeerBitTag(Tag_consenso); + else + Consenso = LeerBitTag(Tag_consenso); + } + + public override void UpdateControl(int elapsedMilliseconds) + { + 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) + { + TiempoRestante += 3600 / (Botellas_hora * (Velocidad_actual_percentual / 100.0f)); + + var X = Left + OffsetLeftSalida; + var Y = Top + OffsetTopSalida; + + if (UltimaBotella != null && UltimaBotella.RemoverDesdeSimulacion) + UltimaBotella = null; + + if (UltimaBotella == null) + { + // No hay botellas, se puede crear una nueva directamente + var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y); + ((osBotella)nuevaBotella).Diametro = Diametro_botella; + nuevaBotella.AutoCreated = true; + UltimaBotella = (osBotella)nuevaBotella; + } + else + { + // Calcular la distancia entre el centro de la última botella y la nueva posición + float distancia = (float)Math.Sqrt(Math.Pow(UltimaBotella.Left - X, 2) + Math.Pow(UltimaBotella.Top - Y, 2)); + float distanciaMinima = Diametro_botella / 2; // Asumiendo que el diámetro de la nueva botella es similar + + if (distancia > distanciaMinima) + { + var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y); + ((osBotella)nuevaBotella).Diametro = Diametro_botella; + nuevaBotella.AutoCreated = true; + UltimaBotella = (osBotella)nuevaBotella; + } + } + } + } + else + { + TiempoRestante = 0; + } + } + + public override void ucLoaded() + { + // El UserControl ya se ha cargado y podemos obtener las coordenadas para + // crear el objeto de simulacion + ActualizarLeftTop(); + } + + } + + public partial class ucBottGenerator : UserControl, IDataContainer + { + public osBase? Datos { 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 Resize(float width, float height) + { + if (Datos is osBottGenerator 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 osBottGenerator datos) + datos.Angulo = Angle; + } + public void Highlight(bool State) { } + public int ZIndex() + { + return 10; + } + } + +} diff --git a/ObjetosSim/Estaticos/ucTransporteGuias.xaml b/ObjetosSim/Estaticos/ucTransporteGuias.xaml index e39b5d1..2e4b7ea 100644 --- a/ObjetosSim/Estaticos/ucTransporteGuias.xaml +++ b/ObjetosSim/Estaticos/ucTransporteGuias.xaml @@ -28,18 +28,37 @@ - - - + + + + + - - - - + + + + + + + + + + + + diff --git a/ObjetosSim/Estaticos/ucTransporteGuias.xaml.cs b/ObjetosSim/Estaticos/ucTransporteGuias.xaml.cs index 268d817..0649551 100644 --- a/ObjetosSim/Estaticos/ucTransporteGuias.xaml.cs +++ b/ObjetosSim/Estaticos/ucTransporteGuias.xaml.cs @@ -1,5 +1,6 @@ using System.Windows; using System.Windows.Controls; +using System.Windows.Media; using CommunityToolkit.Mvvm.ComponentModel; using CtrEditor.Siemens; using CtrEditor.Simulacion; @@ -60,6 +61,9 @@ namespace CtrEditor.ObjetosSim } + [ObservableProperty] + Color color = Colors.Blue; + [ObservableProperty] public string motor; @@ -110,10 +114,9 @@ namespace CtrEditor.ObjetosSim UpdateOrCreateLine(Guia_Superior, uc.GuiaSuperior); UpdateOrCreateLine(Guia_Inferior, uc.GuiaInferior) ; - SimGeometria.DistanceGuide2Guide = Alto; - SimGeometria.Speed = VelocidadActual; + SimGeometria.DistanceGuide2Guide = Alto; SimGeometria.isBrake = esFreno; - ActualizarAnimacionStoryBoardTransporte(VelocidadActual); + SetSpeed(); } } @@ -123,7 +126,7 @@ namespace CtrEditor.ObjetosSim Ancho = 1; Alto = 0.10f; AltoGuia = 0.03f; - Distance = 0.01f; + Distance = 0.01f; } public override void UpdateGeometryStart() diff --git a/ObjetosSim/Estaticos/ucTransporteGuiasUnion.xaml b/ObjetosSim/Estaticos/ucTransporteGuiasUnion.xaml index 6cdeae4..d38e034 100644 --- a/ObjetosSim/Estaticos/ucTransporteGuiasUnion.xaml +++ b/ObjetosSim/Estaticos/ucTransporteGuiasUnion.xaml @@ -39,7 +39,7 @@ - + @@ -67,14 +67,13 @@ 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="Blue"/> + Canvas.Top="{Binding Distance, Converter={StaticResource MeterToPixelConverter},ConverterParameter=-1}" Color="{Binding Color}"/> + Color="{Binding Color}"> diff --git a/ObjetosSim/Estaticos/ucTransporteGuiasUnion.xaml.cs b/ObjetosSim/Estaticos/ucTransporteGuiasUnion.xaml.cs index e7f2b19..a1b317e 100644 --- a/ObjetosSim/Estaticos/ucTransporteGuiasUnion.xaml.cs +++ b/ObjetosSim/Estaticos/ucTransporteGuiasUnion.xaml.cs @@ -36,7 +36,7 @@ namespace CtrEditor.ObjetosSim } [ObservableProperty] - string color; + Color color; [ObservableProperty] public string motorA; diff --git a/ObjetosSim/Estaticos/ucVMmotorSim.xaml b/ObjetosSim/Estaticos/ucVMmotorSim.xaml index 1da6a69..b591197 100644 --- a/ObjetosSim/Estaticos/ucVMmotorSim.xaml +++ b/ObjetosSim/Estaticos/ucVMmotorSim.xaml @@ -9,25 +9,35 @@ - + - - - - - + + + + + + + + + + + - + diff --git a/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs b/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs index 707d9f2..5830ef9 100644 --- a/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs +++ b/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs @@ -35,6 +35,9 @@ namespace CtrEditor.ObjetosSim [ObservableProperty] public ImageSource imageSource_oculta; + [ObservableProperty] + public float angulo; + [ObservableProperty] public float tamano; [ObservableProperty] @@ -105,6 +108,7 @@ namespace CtrEditor.ObjetosSim // El UserControl ya se ha cargado y podemos obtener las coordenadas para // crear el objeto de simulacion ActualizarLeftTop(); + OnVelocidadChanged(Velocidad); } } @@ -136,7 +140,11 @@ namespace CtrEditor.ObjetosSim Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels); } } - public void Rotate(float Angle) { } + public void Rotate(float Angle) { + if (Datos != null) + if (Datos is osVMmotorSim datos) + datos.Angulo = Angle; + } public void Highlight(bool State) { } public int ZIndex() { diff --git a/ObjetosSim/SensoresComandos/ucBoton.xaml b/ObjetosSim/SensoresComandos/ucBoton.xaml index 58b4837..36a88e7 100644 --- a/ObjetosSim/SensoresComandos/ucBoton.xaml +++ b/ObjetosSim/SensoresComandos/ucBoton.xaml @@ -12,7 +12,15 @@ - + + + + + + + + + - + + + + diff --git a/ObjetosSim/SensoresComandos/ucBoton.xaml.cs b/ObjetosSim/SensoresComandos/ucBoton.xaml.cs index 49795a1..6232a26 100644 --- a/ObjetosSim/SensoresComandos/ucBoton.xaml.cs +++ b/ObjetosSim/SensoresComandos/ucBoton.xaml.cs @@ -26,9 +26,15 @@ namespace CtrEditor.ObjetosSim set => SetProperty(ref nombre, value); } + [ObservableProperty] + string button_Name; + [ObservableProperty] public bool tipo_NC; + [ObservableProperty] + Color color_Titulo; + [ObservableProperty] Color color; @@ -41,6 +47,10 @@ namespace CtrEditor.ObjetosSim private Color colorButton_oculto; [ObservableProperty] public float tamano; + + [ObservableProperty] + public float angulo; + [ObservableProperty] public bool estado; @@ -75,6 +85,8 @@ namespace CtrEditor.ObjetosSim Tag = "M50.0"; // Set initial color Color = Colors.LightBlue; + color_Titulo = Colors.Black; + button_Name = "TAG"; } public override void UpdateGeometryStart() @@ -163,7 +175,10 @@ namespace CtrEditor.ObjetosSim Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels); } } - public void Rotate(float Angle) { } + public void Rotate(float Angle) { + if (Datos is osBoton osBotonData) + osBotonData.Angulo = Angle; + } public void Highlight(bool State) { } public int ZIndex() { diff --git a/ObjetosSim/SensoresComandos/ucPhotocell.xaml b/ObjetosSim/SensoresComandos/ucPhotocell.xaml index 749f434..315f67e 100644 --- a/ObjetosSim/SensoresComandos/ucPhotocell.xaml +++ b/ObjetosSim/SensoresComandos/ucPhotocell.xaml @@ -16,27 +16,29 @@ - + - + - + - + + + Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.1}" + Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=2}" + VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,0,0" Grid.Column="1"/> - + @@ -46,8 +48,8 @@ - + \ No newline at end of file diff --git a/ObjetosSim/UserControlFactory.cs b/ObjetosSim/UserControlFactory.cs index 28a5158..867d156 100644 --- a/ObjetosSim/UserControlFactory.cs +++ b/ObjetosSim/UserControlFactory.cs @@ -92,8 +92,8 @@ namespace CtrEditor.ObjetosSim // Add properties dynamically based on attributes var properties = TypeDescriptor.GetProperties(selectedObject) .Cast() - .Where(prop => !prop.Attributes.OfType().Any()); - + .Where(prop => !prop.Attributes.OfType().Any()).OrderBy(prop => prop.Name); + foreach (var property in properties) { var displayNameAttr = property.Attributes.OfType().FirstOrDefault(); @@ -112,8 +112,11 @@ namespace CtrEditor.ObjetosSim { propertyDefinition.DisplayName = property.Name.Replace("_", " "); } - - propertyGrid.PropertyDefinitions.Add(propertyDefinition); + if (property.PropertyType == typeof(double) || property.PropertyType == typeof(float) || property.PropertyType == typeof(string) || property.PropertyType == typeof(int) || + property.PropertyType == typeof(bool) || property.PropertyType == typeof(Color)) + { + propertyGrid.PropertyDefinitions.Add(propertyDefinition); + } } } diff --git a/ObjetosSim/UserControls/ThreeLinesControl.xaml.cs b/ObjetosSim/UserControls/ThreeLinesControl.xaml.cs index 21be1f1..03c10a7 100644 --- a/ObjetosSim/UserControls/ThreeLinesControl.xaml.cs +++ b/ObjetosSim/UserControls/ThreeLinesControl.xaml.cs @@ -23,7 +23,7 @@ namespace CtrEditor.ObjetosSim.UserControls { InitializeComponent(); this.Loaded += ThreeLinesControl_Loaded; - Color = Brushes.Gray; + //Color = Colors.Gray; } private void ThreeLinesControl_Loaded(object sender, RoutedEventArgs e) @@ -31,8 +31,14 @@ namespace CtrEditor.ObjetosSim.UserControls CreateOrUpdateRectangles(); } - [ObservableProperty] - private Brush color; + public Color Color + { + get { return (Color)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + public static readonly DependencyProperty ColorProperty = + DependencyProperty.Register("Color", typeof(Color), typeof(ThreeLinesControl), new PropertyMetadata(Colors.Red, OnDimensionsChanged)); public double AnchoRecto { @@ -85,7 +91,7 @@ namespace CtrEditor.ObjetosSim.UserControls { Width = AnchoRecto, Height = AltoGuia, - Fill = Color + Fill = new SolidColorBrush(Color) }; Canvas.SetLeft(_liz, 0); Canvas.SetTop(_liz, -AltoGuia / 2); @@ -96,7 +102,7 @@ namespace CtrEditor.ObjetosSim.UserControls { Width = CalculateLcWidth(), Height = AltoGuia, - Fill = Color, + Fill = new SolidColorBrush(Color), RenderTransformOrigin = new Point(0, 0.5) }; _lc.RenderTransform = new RotateTransform(GetRotationAngle(AnchoCentro, Altura)); @@ -109,7 +115,7 @@ namespace CtrEditor.ObjetosSim.UserControls { Width = AnchoRecto, Height = AltoGuia, - Fill = Color + Fill = new SolidColorBrush(Color) }; Canvas.SetLeft(_lde, AnchoRecto + AnchoCentro); Canvas.SetTop(_lde, Altura - AltoGuia / 2); @@ -120,12 +126,14 @@ namespace CtrEditor.ObjetosSim.UserControls // Actualizar Liz _liz.Width = AnchoRecto; _liz.Height = AltoGuia; + _liz.Fill = new SolidColorBrush(Color); Canvas.SetLeft(_liz, 0); Canvas.SetTop(_liz, -AltoGuia / 2); // Actualizar Lc _lc.Width = CalculateLcWidth(); _lc.Height = AltoGuia; + _lc.Fill = new SolidColorBrush(Color); _lc.RenderTransform = new RotateTransform(GetRotationAngle(AnchoCentro, Altura)); Canvas.SetLeft(_lc, AnchoRecto); Canvas.SetTop(_lc, -AltoGuia / 2); @@ -133,6 +141,7 @@ namespace CtrEditor.ObjetosSim.UserControls // Actualizar Lde _lde.Width = AnchoRecto; _lde.Height = AltoGuia; + _lde.Fill = new SolidColorBrush(Color); Canvas.SetLeft(_lde, AnchoRecto + AnchoCentro); Canvas.SetTop(_lde, Altura - AltoGuia / 2); } diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs index 4f76524..6dab284 100644 --- a/ObjetosSim/osBase.cs +++ b/ObjetosSim/osBase.cs @@ -68,18 +68,33 @@ namespace CtrEditor.ObjetosSim 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] private float top; + [ObservableProperty] + private string group_Panel; + 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) { } /// /// Usado para saber cuando un objeto fue creado manualmente o por algun generador @@ -176,7 +191,6 @@ namespace CtrEditor.ObjetosSim [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 diff --git a/ObjetosSim/ucBasicExample.xaml b/ObjetosSim/ucBasicExample.xaml index 2779c15..7b050ee 100644 --- a/ObjetosSim/ucBasicExample.xaml +++ b/ObjetosSim/ucBasicExample.xaml @@ -1,37 +1,41 @@  + 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"> - + + + + + + + + + + + + + + - + - - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/ObjetosSim/ucBasicExample.xaml.cs b/ObjetosSim/ucBasicExample.xaml.cs index efd7ab8..48b2ac1 100644 --- a/ObjetosSim/ucBasicExample.xaml.cs +++ b/ObjetosSim/ucBasicExample.xaml.cs @@ -1,11 +1,14 @@ - -using CtrEditor.Siemens; -using System.Windows; +using System.Windows; using System.Windows.Controls; +using System.Windows.Media.Animation; using System.Windows.Media; -using Newtonsoft.Json; using CommunityToolkit.Mvvm.ComponentModel; +using CtrEditor.Siemens; +using CtrEditor.Simulacion; +using System.Windows.Input; + + namespace CtrEditor.ObjetosSim { /// @@ -15,80 +18,112 @@ namespace CtrEditor.ObjetosSim public partial class osBasicExample : osBase, IosBase { + private osBase _osMotor = null; - // Otros datos y métodos relevantes para la simulación + private simTransporte SimGeometria; public static string NombreClase() { - return "Ejemplo"; + return "Example"; } - private string nombre = NombreClase(); + private string nombre = "Transporte TTOP"; public override string Nombre { get => nombre; set => SetProperty(ref nombre, value); } - [JsonIgnore] [ObservableProperty] - public ImageSource imageSource_oculta; + public float velocidadActual; - [ObservableProperty] - public float tamano; - [ObservableProperty] - public float maxRatedHz; - - [ObservableProperty] - public float tiempoRampa; - - partial void OnTiempoRampaChanged(float value) + partial void OnVelocidadActualChanged(float value) { - if (value < 0.1f) - value = 0.1f; - tiempoRampa = value; + SetSpeed(); } [ObservableProperty] - public bool encendido; - [ObservableProperty] - public int pLC_NumeroMotor; - [ObservableProperty] - public float ratio; + bool invertirDireccion; - [ObservableProperty] - public float velocidad; - - partial void OnVelocidadChanged(float value) + partial void OnInvertirDireccionChanged(bool value) { - if (value > 0) - ImageSource_oculta = ImageFromPath("/imagenes/motorVerde.png"); + SetSpeed(); + if (_visualRepresentation is ucBasicExample uc) + { + CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion); + ActualizarAnimacionStoryBoardTransporte(VelocidadActual); + } + } + + void SetSpeed() + { + if (InvertirDireccion) + SimGeometria?.SetSpeed(-VelocidadActual); else - ImageSource_oculta = ImageFromPath("/imagenes/motorNegro.png"); + SimGeometria?.SetSpeed(VelocidadActual); + ActualizarAnimacionStoryBoardTransporte(VelocidadActual); + } + + + [ObservableProperty] + public string motor; + + partial void OnMotorChanged(string value) + { + _osMotor = ObtenerLink(Motor, typeof(osVMmotorSim)); + } + + [ObservableProperty] + public float ancho; + [ObservableProperty] + public float alto; + [ObservableProperty] + public float angulo; + [ObservableProperty] + public float frictionCoefficient; + [ObservableProperty] + public float velMax50hz; + [ObservableProperty] + public float tiempoRampa; + [ObservableProperty] + public bool esMarcha; + + + private void ActualizarGeometrias() + { + if (_visualRepresentation is ucBasicExample uc) + { + UpdateRectangle(SimGeometria, uc.Transporte, Alto, Ancho, Angulo); + SetSpeed(); + } + ActualizarAnimacionStoryBoardTransporte(VelocidadActual); } public osBasicExample() { - Tamano = 0.30f; - PLC_NumeroMotor = 31; - MaxRatedHz = 100; - TiempoRampa = 3; - ImageSource_oculta = ImageFromPath("/imagenes/motor2.png"); + Ancho = 1; + Alto = 0.10f; } + 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(PLCModel plc, int elapsedMilliseconds) { - - } - - public override void UpdateControl(int elapsedMilliseconds) - { - // Calculamos la velocidad - + if (_osMotor != null) + { + if (_osMotor is osVMmotorSim motor) + VelocidadActual = motor.Velocidad; + } + else if (Motor.Length > 0) + _osMotor = ObtenerLink(Motor, typeof(osVMmotorSim)); } public override void ucLoaded() @@ -97,7 +132,19 @@ namespace CtrEditor.ObjetosSim // crear el objeto de simulacion ActualizarLeftTop(); + if (_visualRepresentation is ucBasicExample 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 ucBasicExample : UserControl, IDataContainer @@ -118,7 +165,11 @@ namespace CtrEditor.ObjetosSim { Datos?.ucUnLoaded(); } - public void Resize(float width, float height) { } + public void Resize(float width, float height) + { + if (Datos is osBasicExample datos) + datos.Ancho = PixelToMeter.Instance.calc.PixelsToMeters(width); + } public void Move(float LeftPixels, float TopPixels) { if (Datos != null) @@ -127,11 +178,20 @@ namespace CtrEditor.ObjetosSim Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels); } } - public void Rotate(float Angle) { } + public void Rotate(float Angle) + { + if (Datos != null) + if (Datos is osBasicExample datos) + datos.Angulo = Angle; + } public void Highlight(bool State) { } public int ZIndex() { - return 10; + return 1; } + } } + + + diff --git a/XAMLhelpers.cs b/XAMLhelpers.cs index 5b7deb3..97575ab 100644 --- a/XAMLhelpers.cs +++ b/XAMLhelpers.cs @@ -385,14 +385,14 @@ namespace CtrEditor { if (value is double floatValue) { - return floatValue.ToString("0.00", culture); // Formatear a dos decimales + return floatValue.ToString("0.00", culture).Replace('.', ','); // 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)) + if (value is string stringValue && double.TryParse(stringValue.Replace(',', '.'), NumberStyles.Float, culture, out double result)) { return result; } diff --git a/imagenes/gear.png b/imagenes/gear.png new file mode 100644 index 0000000000000000000000000000000000000000..f31d36481db223f21291173a8f3eef0c9a83fc89 GIT binary patch literal 19118 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 R5pYXEXMBG27M`T#{0**OT2=r6 literal 0 HcmV?d00001