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.
This commit is contained in:
parent
353b4b99e6
commit
9f41401e40
|
@ -75,14 +75,14 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Aether.Physics2D" Version="2.1.0" />
|
<PackageReference Include="Aether.Physics2D" Version="2.1.0" />
|
||||||
<PackageReference Include="ClosedXML" Version="0.104.0-preview2" />
|
<PackageReference Include="ClosedXML" Version="0.104.2" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="Emgu.CV" Version="4.9.0.5494" />
|
<PackageReference Include="Emgu.CV" Version="4.9.0.5494" />
|
||||||
<PackageReference Include="Emgu.CV.runtime.windows" 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="Emgu.CV.UI" Version="4.9.0.5494" />
|
||||||
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.6.0" />
|
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.6.1" />
|
||||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc2" />
|
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc4.5" />
|
||||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
|
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||||
<PackageReference Include="Tesseract" Version="5.2.0" />
|
<PackageReference Include="Tesseract" Version="5.2.0" />
|
||||||
|
|
|
@ -704,6 +704,7 @@ namespace CtrEditor
|
||||||
Debug_SimulacionCreado = true;
|
Debug_SimulacionCreado = true;
|
||||||
|
|
||||||
_timerSimulacion.Start();
|
_timerSimulacion.Start();
|
||||||
|
simulationManager.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StopSimulation()
|
private void StopSimulation()
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<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.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" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
|
@ -0,0 +1,153 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
public static string NombreClase()
|
||||||
|
{
|
||||||
|
return "Frame 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;
|
||||||
|
|
||||||
|
// Encoder
|
||||||
|
[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: Description("X in meter offset Left. Position when the encoder is 0")]
|
||||||
|
[property: Category("Encoders:")]
|
||||||
|
public float offset_encoder_X;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private osEncoderMotorLineal EncoderX;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private float EncoderXValue;
|
||||||
|
|
||||||
|
partial void OnEncoder_XChanged(string value)
|
||||||
|
{
|
||||||
|
if (_mainViewModel != null && value != null && value.Length > 0)
|
||||||
|
{
|
||||||
|
EncoderX = (osEncoderMotorLineal)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osEncoderMotorLineal && s.Nombre == value), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateControl(int elapsedMilliseconds)
|
||||||
|
{
|
||||||
|
if (EncoderX == null)
|
||||||
|
return;
|
||||||
|
if (K_encoder_X == 0)
|
||||||
|
return;
|
||||||
|
if (EncoderXValue != EncoderX.Valor_Actual)
|
||||||
|
Left = (EncoderX.Valor_Actual / k_encoder_X) + offset_encoder_X;
|
||||||
|
EncoderXValue = EncoderX.Valor_Actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void TopChanging(float oldValue, float newValue)
|
||||||
|
{
|
||||||
|
offsetY = newValue - oldValue;
|
||||||
|
}
|
||||||
|
public override void LeftChanging(float oldValue, float newValue)
|
||||||
|
{
|
||||||
|
offsetX = newValue - oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public osFramePlate()
|
||||||
|
{
|
||||||
|
Ancho = 0.5f;
|
||||||
|
Alto = 0.5f;
|
||||||
|
Alto_Titulo = 0.2f;
|
||||||
|
Color = Colors.WhiteSmoke;
|
||||||
|
Titulo = "Frame";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucLoaded()
|
||||||
|
{
|
||||||
|
base.ucLoaded();
|
||||||
|
// El UserControl se ha cargado
|
||||||
|
OnEncoder_XChanged(Encoder_X);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucUnLoaded()
|
||||||
|
{
|
||||||
|
base.ucUnLoaded();
|
||||||
|
// El UserControl se esta eliminando
|
||||||
|
// eliminar el objeto de simulacion
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class ucFramePlate : UserControl, IDataContainer
|
||||||
|
{
|
||||||
|
public osBase? Datos { 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()
|
||||||
|
{
|
||||||
|
return ZIndexEnum.Decorativos;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
Diametro = 0.10f;
|
Diametro = 0.10f;
|
||||||
Mass = 1;
|
Mass = 1;
|
||||||
ColorButton_oculto = Brushes.Gray;
|
ColorButton_oculto = Brushes.Gray;
|
||||||
|
Preserve_Outside_Transport = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateAfterMove()
|
public void UpdateAfterMove()
|
||||||
|
|
|
@ -35,6 +35,10 @@ namespace CtrEditor.ObjetosSim
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private float offsetTopSalida;
|
private float offsetTopSalida;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("The bottle will be destroyed if fall outside transport.")]
|
||||||
|
[property: Category("Enable to Run:")]
|
||||||
|
private bool preserve_Outside_Transport;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Description("PLC tag for consense to run. 1 => always")]
|
[property: Description("PLC tag for consense to run. 1 => always")]
|
||||||
|
@ -103,6 +107,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
Botellas_hora = 10000;
|
Botellas_hora = 10000;
|
||||||
Filtro_consenso_ON_s = 1;
|
Filtro_consenso_ON_s = 1;
|
||||||
Filtro_consenso_OFF_s = 0.1f;
|
Filtro_consenso_OFF_s = 0.1f;
|
||||||
|
Preserve_Outside_Transport = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||||
|
@ -144,6 +149,8 @@ namespace CtrEditor.ObjetosSim
|
||||||
// No hay botellas, se puede crear una nueva directamente
|
// No hay botellas, se puede crear una nueva directamente
|
||||||
var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
|
var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
|
||||||
((osBotella)nuevaBotella).Diametro = Diametro_botella;
|
((osBotella)nuevaBotella).Diametro = Diametro_botella;
|
||||||
|
((osBotella)nuevaBotella).Preserve_Outside_Transport = Preserve_Outside_Transport;
|
||||||
|
((osBotella)nuevaBotella).AutoCreated = true;
|
||||||
nuevaBotella.AutoCreated = true;
|
nuevaBotella.AutoCreated = true;
|
||||||
UltimaBotella = (osBotella)nuevaBotella;
|
UltimaBotella = (osBotella)nuevaBotella;
|
||||||
BotellaCreada = true;
|
BotellaCreada = true;
|
||||||
|
@ -158,6 +165,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
osBotella nuevaBotella = (osBotella)_mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
|
osBotella nuevaBotella = (osBotella)_mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
|
||||||
nuevaBotella.Diametro = Diametro_botella;
|
nuevaBotella.Diametro = Diametro_botella;
|
||||||
|
((osBotella)nuevaBotella).Preserve_Outside_Transport = Preserve_Outside_Transport;
|
||||||
nuevaBotella.AutoCreated = true;
|
nuevaBotella.AutoCreated = true;
|
||||||
UltimaBotella = nuevaBotella;
|
UltimaBotella = nuevaBotella;
|
||||||
BotellaCreada = true;
|
BotellaCreada = true;
|
||||||
|
|
|
@ -110,6 +110,11 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnMoveResizeRotate()
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public float frictionCoefficient;
|
public float frictionCoefficient;
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
</VisualBrush.Transform>
|
</VisualBrush.Transform>
|
||||||
<VisualBrush.Visual>
|
<VisualBrush.Visual>
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Rectangle Fill="#FFBFBFBF" Width="10" Height="10"/>
|
<Rectangle Fill="LightGray" Width="10" Height="10" />
|
||||||
<Rectangle Fill="LightGray" Width="10" Height="10" Canvas.Left="10"/>
|
<Rectangle Fill="GhostWhite" Width="10" Height="10" Canvas.Left="10" />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</VisualBrush.Visual>
|
</VisualBrush.Visual>
|
||||||
</VisualBrush>
|
</VisualBrush>
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Stretch="Uniform">
|
Stretch="Uniform">
|
||||||
<Label Content="{Binding Nombre}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" FontSize="18" Opacity="0.5"/>
|
<Label Content="{Binding Nombre}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" FontSize="18" Opacity="0.9"/>
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,10 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnMoveResizeRotate()
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
public osTransporteGuias()
|
public osTransporteGuias()
|
||||||
{
|
{
|
||||||
|
|
|
@ -227,6 +227,11 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnMoveResizeRotate()
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public osTransporteGuiasUnion()
|
public osTransporteGuiasUnion()
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
</VisualBrush.Transform>
|
</VisualBrush.Transform>
|
||||||
<VisualBrush.Visual>
|
<VisualBrush.Visual>
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Rectangle Fill="Gray" Width="10" Height="10"/>
|
<Rectangle Fill="LightGray" Width="10" Height="10"/>
|
||||||
<Rectangle Fill="DarkGray" Width="10" Height="10" Canvas.Left="10"/>
|
<Rectangle Fill="GhostWhite" Width="10" Height="10" Canvas.Left="10"/>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</VisualBrush.Visual>
|
</VisualBrush.Visual>
|
||||||
</VisualBrush>
|
</VisualBrush>
|
||||||
|
@ -28,14 +28,25 @@
|
||||||
<vm:osTransporteTTop Ancho="2"/>
|
<vm:osTransporteTTop Ancho="2"/>
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
|
|
||||||
<Canvas>
|
<Canvas RenderTransformOrigin="0,0">
|
||||||
|
<Canvas.RenderTransform>
|
||||||
|
<TransformGroup>
|
||||||
|
<ScaleTransform />
|
||||||
|
<SkewTransform />
|
||||||
|
<RotateTransform Angle="{Binding Angulo}" />
|
||||||
|
<TranslateTransform />
|
||||||
|
</TransformGroup>
|
||||||
|
</Canvas.RenderTransform>
|
||||||
<Rectangle x:Name="Transporte"
|
<Rectangle x:Name="Transporte"
|
||||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Fill="{StaticResource BeltBrush}">
|
Fill="{StaticResource BeltBrush}">
|
||||||
<Rectangle.RenderTransform>
|
|
||||||
<RotateTransform Angle="{Binding Angulo}"/>
|
|
||||||
</Rectangle.RenderTransform>
|
|
||||||
</Rectangle>
|
</Rectangle>
|
||||||
|
<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>
|
</Canvas>
|
||||||
</UserControl>
|
</UserControl>
|
|
@ -116,6 +116,11 @@ namespace CtrEditor.ObjetosSim
|
||||||
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
|
ActualizarAnimacionStoryBoardTransporte(VelocidadActual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnMoveResizeRotate()
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
public osTransporteTTop()
|
public osTransporteTTop()
|
||||||
{
|
{
|
||||||
Ancho = 1;
|
Ancho = 1;
|
||||||
|
|
|
@ -86,6 +86,10 @@ namespace CtrEditor.ObjetosSim
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public float velocidad;
|
public float velocidad;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public bool sentido_contrario;
|
||||||
|
|
||||||
|
|
||||||
partial void OnVelocidadChanged(float value)
|
partial void OnVelocidadChanged(float value)
|
||||||
{
|
{
|
||||||
if (value > 0)
|
if (value > 0)
|
||||||
|
@ -113,6 +117,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
motState.UpdatePLC(plc, this, elapsedMilliseconds);
|
motState.UpdatePLC(plc, this, elapsedMilliseconds);
|
||||||
Velocidad = (Proporcional_Speed / 100) * (motState.STATUS_VFD_ACT_Speed_Hz / 10);
|
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 elapsedMilliseconds)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucEncoderMotor"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<UserControl.DataContext>
|
||||||
|
<vm:osEncoderMotor />
|
||||||
|
</UserControl.DataContext>
|
||||||
|
|
||||||
|
<Grid Width="50" Height="50">
|
||||||
|
<Ellipse Fill="{Binding Color_oculto}" Stroke="DarkGray" StrokeThickness="2" Width="30" Height="30" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
|
@ -0,0 +1,160 @@
|
||||||
|
// EncoderMotorViewModel.cs
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using LibS7Adv;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||||
|
using CtrEditor.FuncionesBase;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace CtrEditor.ObjetosSim
|
||||||
|
{
|
||||||
|
public partial class osEncoderMotor : osBase, IosBase
|
||||||
|
{
|
||||||
|
private osBase Motor = null;
|
||||||
|
private Stopwatch Stopwatch = new Stopwatch();
|
||||||
|
private double stopwatch_last = 0;
|
||||||
|
|
||||||
|
public static string NombreClase()
|
||||||
|
{
|
||||||
|
return "Encoder Motor";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string nombre = NombreClase();
|
||||||
|
public override string Nombre
|
||||||
|
{
|
||||||
|
get => nombre;
|
||||||
|
set => SetProperty(ref nombre, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private Brush color_oculto;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public float velocidadActual;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Pulsos por vuelta del encoder")]
|
||||||
|
[property: Category("Encoder Config:")]
|
||||||
|
public float pulsos_Por_Vuelta;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Ratio de giros por 50Hz")]
|
||||||
|
[property: Category("Encoder Config:")]
|
||||||
|
public float ratio_Giros_50hz;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Valor actual del encoder")]
|
||||||
|
[property: Category("Encoder Status:")]
|
||||||
|
public float valor_Actual;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Link to Motor")]
|
||||||
|
[property: Category("PLC link:")]
|
||||||
|
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
|
||||||
|
string id_Motor;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Tag para escribir el valor del encoder")]
|
||||||
|
[property: Category("PLC link:")]
|
||||||
|
string tag_Valor;
|
||||||
|
|
||||||
|
partial void OnId_MotorChanged(string value)
|
||||||
|
{
|
||||||
|
if (Motor != null)
|
||||||
|
Motor.PropertyChanged -= OnMotorPropertyChanged;
|
||||||
|
if (_mainViewModel != null && value != null && value.Length > 0)
|
||||||
|
{
|
||||||
|
Motor = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osVMmotorSim && s.Nombre == value), null);
|
||||||
|
if (Motor != null)
|
||||||
|
Motor.PropertyChanged += OnMotorPropertyChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMotorPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
|
||||||
|
{
|
||||||
|
Id_Motor = ((osVMmotorSim)sender).Nombre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public osEncoderMotor()
|
||||||
|
{
|
||||||
|
Pulsos_Por_Vuelta = 360; // Por defecto, un pulso por grado
|
||||||
|
Ratio_Giros_50hz = 1; // Por defecto, 1 giro por cada 50Hz
|
||||||
|
Color_oculto = Brushes.Gray;
|
||||||
|
Stopwatch.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||||
|
{
|
||||||
|
if (Motor != null && Motor is osVMmotorSim motor)
|
||||||
|
{
|
||||||
|
VelocidadActual = motor.Velocidad;
|
||||||
|
|
||||||
|
// Calcular giros por segundo basado en la velocidad actual
|
||||||
|
// velocidad * ratio_giros_50hz / 100 nos da los giros por segundo a esa velocidad
|
||||||
|
float girosPorSegundo = (VelocidadActual * Ratio_Giros_50hz) / 100f;
|
||||||
|
|
||||||
|
// Considerar el sentido de giro
|
||||||
|
if (motor.Sentido_contrario)
|
||||||
|
girosPorSegundo = -girosPorSegundo;
|
||||||
|
|
||||||
|
// Calcular incremento de pulsos para este ciclo
|
||||||
|
float segundosTranscurridos = elapsedMilliseconds / 1000f;
|
||||||
|
float incrementoPulsos = girosPorSegundo * Pulsos_Por_Vuelta * segundosTranscurridos;
|
||||||
|
|
||||||
|
// Actualizar valor del encoder
|
||||||
|
Valor_Actual = (Valor_Actual + incrementoPulsos) % Pulsos_Por_Vuelta;
|
||||||
|
if (Valor_Actual < 0) Valor_Actual += Pulsos_Por_Vuelta;
|
||||||
|
|
||||||
|
// Actualizar color basado en si está girando
|
||||||
|
Color_oculto = Math.Abs(girosPorSegundo) > 0.01f ? Brushes.LightGreen : Brushes.Gray;
|
||||||
|
|
||||||
|
// Escribir valor al PLC si hay tag configurado
|
||||||
|
if (!string.IsNullOrEmpty(Tag_Valor))
|
||||||
|
{
|
||||||
|
EscribirWordTagScaled(Tag_Valor, Valor_Actual, 0, Pulsos_Por_Vuelta, 0, 27648);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucLoaded()
|
||||||
|
{
|
||||||
|
base.ucLoaded();
|
||||||
|
OnId_MotorChanged(Id_Motor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class ucEncoderMotor : UserControl, IDataContainer
|
||||||
|
{
|
||||||
|
public osBase? Datos { get; set; }
|
||||||
|
|
||||||
|
public ucEncoderMotor()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.Loaded += OnLoaded;
|
||||||
|
this.Unloaded += OnUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucUnLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Highlight(bool State) { }
|
||||||
|
|
||||||
|
public ZIndexEnum ZIndex()
|
||||||
|
{
|
||||||
|
return ZIndexEnum.Estaticos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucEncoderMotorLineal"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<UserControl.DataContext>
|
||||||
|
<vm:osEncoderMotorLineal />
|
||||||
|
</UserControl.DataContext>
|
||||||
|
|
||||||
|
<Grid Width="50" Height="50">
|
||||||
|
<Ellipse Fill="{Binding Color_oculto}" Stroke="DarkGray" StrokeThickness="2" Width="30" Height="30" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
|
@ -0,0 +1,152 @@
|
||||||
|
// EncoderMotorLinealViewModel.cs
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using LibS7Adv;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||||
|
using CtrEditor.FuncionesBase;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace CtrEditor.ObjetosSim
|
||||||
|
{
|
||||||
|
public partial class osEncoderMotorLineal : osBase, IosBase
|
||||||
|
{
|
||||||
|
private osBase Motor = null;
|
||||||
|
private Stopwatch Stopwatch = new Stopwatch();
|
||||||
|
private double stopwatch_last = 0;
|
||||||
|
|
||||||
|
public static string NombreClase()
|
||||||
|
{
|
||||||
|
return "Encoder Motor Lineal";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string nombre = NombreClase();
|
||||||
|
public override string Nombre
|
||||||
|
{
|
||||||
|
get => nombre;
|
||||||
|
set => SetProperty(ref nombre, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private Brush color_oculto;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public float velocidadActual;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Pulsos por Hz por segundo : K")]
|
||||||
|
[property: Category("Encoder Config:")]
|
||||||
|
public float pulsos_Por_Hz;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Valor actual del encoder")]
|
||||||
|
[property: Category("Encoder Status:")]
|
||||||
|
public float valor_Actual;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Link to Motor")]
|
||||||
|
[property: Category("PLC link:")]
|
||||||
|
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
|
||||||
|
string id_Motor;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Tag para escribir el valor del encoder")]
|
||||||
|
[property: Category("PLC link:")]
|
||||||
|
string tag_Valor;
|
||||||
|
|
||||||
|
partial void OnId_MotorChanged(string value)
|
||||||
|
{
|
||||||
|
if (Motor != null)
|
||||||
|
Motor.PropertyChanged -= OnMotorPropertyChanged;
|
||||||
|
if (_mainViewModel != null && value != null && value.Length > 0)
|
||||||
|
{
|
||||||
|
Motor = (osVMmotorSim)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osVMmotorSim && s.Nombre == value), null);
|
||||||
|
if (Motor != null)
|
||||||
|
Motor.PropertyChanged += OnMotorPropertyChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMotorPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(osVMmotorSim.Nombre))
|
||||||
|
{
|
||||||
|
Id_Motor = ((osVMmotorSim)sender).Nombre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public osEncoderMotorLineal()
|
||||||
|
{
|
||||||
|
Pulsos_Por_Hz = 3000; // Por defecto, un pulso por grado
|
||||||
|
Color_oculto = Brushes.Gray;
|
||||||
|
Stopwatch.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||||
|
{
|
||||||
|
if (Motor != null && Motor is osVMmotorSim motor)
|
||||||
|
{
|
||||||
|
VelocidadActual = motor.Velocidad;
|
||||||
|
|
||||||
|
// Calcular giros por segundo basado en la velocidad actual
|
||||||
|
float pulsosPorHz = (VelocidadActual * Pulsos_Por_Hz) / 100f;
|
||||||
|
|
||||||
|
// Considerar el sentido de giro
|
||||||
|
if (motor.Sentido_contrario)
|
||||||
|
pulsosPorHz = -pulsosPorHz;
|
||||||
|
|
||||||
|
// Calcular incremento de pulsos para este ciclo
|
||||||
|
float segundosTranscurridos = elapsedMilliseconds / 1000f;
|
||||||
|
float incrementoPulsos = pulsosPorHz * segundosTranscurridos;
|
||||||
|
|
||||||
|
// Actualizar valor del encoder
|
||||||
|
Valor_Actual = (Valor_Actual + incrementoPulsos);
|
||||||
|
|
||||||
|
// Actualizar color basado en si está girando
|
||||||
|
Color_oculto = Math.Abs(pulsosPorHz) > 0.01f ? Brushes.LightGreen : Brushes.Gray;
|
||||||
|
|
||||||
|
// Escribir valor al PLC si hay tag configurado
|
||||||
|
if (!string.IsNullOrEmpty(Tag_Valor))
|
||||||
|
{
|
||||||
|
EscribirDINTTag(tag_Valor, (int)Valor_Actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucLoaded()
|
||||||
|
{
|
||||||
|
base.ucLoaded();
|
||||||
|
OnId_MotorChanged(Id_Motor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class ucEncoderMotorLineal : UserControl, IDataContainer
|
||||||
|
{
|
||||||
|
public osBase? Datos { get; set; }
|
||||||
|
|
||||||
|
public ucEncoderMotorLineal()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.Loaded += OnLoaded;
|
||||||
|
this.Unloaded += OnUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucUnLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Highlight(bool State) { }
|
||||||
|
|
||||||
|
public ZIndexEnum ZIndex()
|
||||||
|
{
|
||||||
|
return ZIndexEnum.Estaticos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,12 +53,26 @@ namespace CtrEditor.ObjetosSim
|
||||||
b = VisualRepresentation;
|
b = VisualRepresentation;
|
||||||
c = simulationManager;
|
c = simulationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract partial class osBase : ObservableObject
|
public abstract partial class osBase : ObservableObject
|
||||||
{
|
{
|
||||||
public virtual string Nombre { get; set; } = "osBase";
|
public virtual string Nombre { get; set; } = "osBase";
|
||||||
|
|
||||||
|
public osBase()
|
||||||
|
{
|
||||||
|
if (float.IsNaN(Left))
|
||||||
|
{
|
||||||
|
Left = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (float.IsNaN(Top))
|
||||||
|
{
|
||||||
|
Top = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private Storyboard _storyboard;
|
private Storyboard _storyboard;
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
|
@ -210,7 +224,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
Id = new UniqueId().ObtenerNuevaID();
|
Id = new UniqueId().ObtenerNuevaID();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group
|
// Group as FacePlate
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Description("This is a link to a faceplate. It works like an anchor.")]
|
[property: Description("This is a link to a faceplate. It works like an anchor.")]
|
||||||
[property: Category("Group:")]
|
[property: Category("Group:")]
|
||||||
|
@ -253,6 +267,56 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Group as FacePlate
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("This is a link to a FromPlate that moves automatically. It works like an anchor.")]
|
||||||
|
[property: Category("Group:")]
|
||||||
|
[property: ItemsSource(typeof(osBaseItemsSource<osFramePlate>))]
|
||||||
|
private string group_FramePanel;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private osFramePlate FramePlate;
|
||||||
|
[JsonIgnore]
|
||||||
|
private bool isUpdatingFromFramePlate = false;
|
||||||
|
|
||||||
|
partial void OnGroup_FramePanelChanged(string value)
|
||||||
|
{
|
||||||
|
if (FramePlate != null)
|
||||||
|
FramePlate.PropertyChanged -= OnFramePlatePropertyChanged;
|
||||||
|
if (_mainViewModel != null && value != null && value.Length > 0)
|
||||||
|
{
|
||||||
|
FramePlate = (osFramePlate)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osFramePlate && s.Nombre == value), null);
|
||||||
|
if (FramePlate != null)
|
||||||
|
FramePlate.PropertyChanged += OnFramePlatePropertyChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFramePlatePropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!isUpdatingFromFramePlate)
|
||||||
|
{
|
||||||
|
isUpdatingFromFramePlate = true;
|
||||||
|
|
||||||
|
if (e.PropertyName == nameof(osFramePlate.Nombre))
|
||||||
|
Group_Panel = ((osFramePlate)sender).Nombre;
|
||||||
|
|
||||||
|
if (e.PropertyName == nameof(osFramePlate.Top))
|
||||||
|
{
|
||||||
|
Top += ((osFramePlate)sender).offsetY;
|
||||||
|
OnMoveResizeRotate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.PropertyName == nameof(osFramePlate.Left))
|
||||||
|
{
|
||||||
|
Left += ((osFramePlate)sender).offsetX;
|
||||||
|
OnMoveResizeRotate();
|
||||||
|
}
|
||||||
|
|
||||||
|
isUpdatingFromFramePlate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void ShowPreviewWindow(Stream imageStream)
|
private void ShowPreviewWindow(Stream imageStream)
|
||||||
{
|
{
|
||||||
// Create a new window for preview
|
// Create a new window for preview
|
||||||
|
@ -535,6 +599,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
ActualizarLeftTop();
|
ActualizarLeftTop();
|
||||||
OnGroup_PanelChanged(Group_Panel); // Establece el link y se suscribe a los eventos
|
OnGroup_PanelChanged(Group_Panel); // Establece el link y se suscribe a los eventos
|
||||||
|
OnGroup_FramePanelChanged(Group_FramePanel); // Establece el link y se suscribe a los eventos
|
||||||
Show_On_This_Page = Show_On_This_Page; // Update data
|
Show_On_This_Page = Show_On_This_Page; // Update data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -687,6 +752,17 @@ namespace CtrEditor.ObjetosSim
|
||||||
_plc.EscribirBool(Tag, Value);
|
_plc.EscribirBool(Tag, Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void EscribirDINTTag(string Tag, float Value)
|
||||||
|
{
|
||||||
|
if (_plc == null) return;
|
||||||
|
if (!string.IsNullOrEmpty(Tag))
|
||||||
|
{
|
||||||
|
SDataValue plcData = new SDataValue();
|
||||||
|
plcData.Int32 = (int)Value;
|
||||||
|
_plc.EscribirTag(Tag, plcData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void EscribirWordTagScaled(string Tag, float Value, float IN_scale_Min, float IN_scale_Max, float OUT_scale_Min, float OUT_scale_Max)
|
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 (_plc == null) return;
|
||||||
|
|
|
@ -726,6 +726,12 @@ namespace CtrEditor.Simulacion
|
||||||
// ******************************************************************************************************************************************
|
// ******************************************************************************************************************************************
|
||||||
// ******************************************************************************************************************************************
|
// ******************************************************************************************************************************************
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
stopwatch.Start();
|
||||||
|
stopwatch_last = stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
public void Step()
|
public void Step()
|
||||||
{
|
{
|
||||||
// Detener el cronómetro y obtener el tiempo transcurrido en milisegundos
|
// Detener el cronómetro y obtener el tiempo transcurrido en milisegundos
|
||||||
|
|
Loading…
Reference in New Issue