Compare commits

...

2 Commits

7 changed files with 383 additions and 130 deletions

11
CtrEditor.code-workspace Normal file
View File

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

View File

@ -746,7 +746,7 @@ namespace CtrEditor
} }
stopwatch.Stop(); // Stop measuring time stopwatch.Stop(); // Stop measuring time
Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms"); // Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms");
} }
private void OpenWorkDirectory() private void OpenWorkDirectory()

View File

@ -6,6 +6,7 @@ using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading; using System.Windows.Threading;
using MouseEventArgs = System.Windows.Input.MouseEventArgs; using MouseEventArgs = System.Windows.Input.MouseEventArgs;
using UserControl = System.Windows.Controls.UserControl; using UserControl = System.Windows.Controls.UserControl;
@ -61,6 +62,9 @@ namespace CtrEditor
// Importante: Agregar el evento para el menú contextual // Importante: Agregar el evento para el menú contextual
ImagenEnTrabajoCanvas.MouseRightButtonDown += Canvas_MouseRightButtonDown; ImagenEnTrabajoCanvas.MouseRightButtonDown += Canvas_MouseRightButtonDown;
// Asegurarse de que estos eventos estén conectados
ImagenEnTrabajoCanvas.MouseUp += Canvas_MouseUp_Panning;
} }
private void MainWindow_Loaded(object sender, RoutedEventArgs e) private void MainWindow_Loaded(object sender, RoutedEventArgs e)
@ -147,20 +151,36 @@ namespace CtrEditor
{ {
if (e.LeftButton == MouseButtonState.Pressed && !_isDrawingCanvas) if (e.LeftButton == MouseButtonState.Pressed && !_isDrawingCanvas)
{ {
// Solo permitir el panning si el clic fue en el canvas o en la imagen de fondo // Verificar si el clic fue en el canvas, imagen de fondo o en el rectángulo de selección
// y no en otros controles if (e.Source == ImagenEnTrabajoCanvas ||
if (e.Source == ImagenEnTrabajoCanvas || e.Source == imagenDeFondo) e.Source == imagenDeFondo ||
(e.Source is Rectangle rect && rect == _objectManager._selectionRectangle))
{ {
ImagenEnTrabajoCanvas.Focus(); // Asegurar que el canvas tiene el foco ImagenEnTrabajoCanvas.Focus();
_isDraggingCanvas = true;
_lastMousePosition = e.GetPosition(ImagenEnTrabajoScrollViewer);
ImagenEnTrabajoCanvas.CaptureMouse();
Mouse.OverrideCursor = Cursors.Hand;
if (DataContext is MainViewModel viewModel) if (_objectManager.IsRectangleSelectionActive)
{ {
viewModel.SelectedItemOsList = null; if (!_objectManager.HasActiveSelectionRectangle)
_objectManager.RemoveResizeRectangles(); {
_objectManager.StartRectangleSelection(e.GetPosition(ImagenEnTrabajoCanvas));
}
else
{
_objectManager.FinishRectangleSelection(e.GetPosition(ImagenEnTrabajoCanvas));
}
}
else
{
_isDraggingCanvas = true;
_lastMousePosition = e.GetPosition(ImagenEnTrabajoScrollViewer);
ImagenEnTrabajoCanvas.CaptureMouse();
Mouse.OverrideCursor = Cursors.Hand;
if (DataContext is MainViewModel viewModel)
{
viewModel.SelectedItemOsList = null;
_objectManager.RemoveResizeRectangles();
}
} }
e.Handled = true; e.Handled = true;
} }
@ -169,7 +189,12 @@ namespace CtrEditor
private void Canvas_MouseMove_Panning(object sender, MouseEventArgs e) private void Canvas_MouseMove_Panning(object sender, MouseEventArgs e)
{ {
if (_isDraggingCanvas && e.LeftButton == MouseButtonState.Pressed) if (_objectManager.IsRectangleSelectionActive && _objectManager.HasActiveSelectionRectangle)
{
var currentPoint = e.GetPosition(ImagenEnTrabajoCanvas);
_objectManager.UpdateRectangleSelection(currentPoint);
}
else if (_isDraggingCanvas && e.LeftButton == MouseButtonState.Pressed)
{ {
var currentPosition = e.GetPosition(ImagenEnTrabajoScrollViewer); var currentPosition = e.GetPosition(ImagenEnTrabajoScrollViewer);
var dx = currentPosition.X - _lastMousePosition.X; var dx = currentPosition.X - _lastMousePosition.X;
@ -436,59 +461,116 @@ namespace CtrEditor
private void ShowContextMenu(Point position) private void ShowContextMenu(Point position)
{ {
var contextMenu = new ContextMenu(); var contextMenu = new ContextMenu();
var multiSelectMenuItem = new MenuItem
{
Header = "Modo Multi-Selección",
IsCheckable = true,
StaysOpenOnClick = false
};
if (DataContext is MainViewModel viewModel) if (DataContext is MainViewModel viewModel)
{ {
multiSelectMenuItem.IsChecked = viewModel.IsMultiSelectionActive; // Multi-selection checkbox
var multiSelectMenuItem = new MenuItem
{
Header = "Modo Multi-Selección",
IsCheckable = true,
StaysOpenOnClick = false,
IsChecked = viewModel.IsMultiSelectionActive
};
multiSelectMenuItem.Click += (s, e) => multiSelectMenuItem.Click += (s, e) =>
{ {
viewModel.IsMultiSelectionActive = multiSelectMenuItem.IsChecked; viewModel.IsMultiSelectionActive = multiSelectMenuItem.IsChecked;
// Si se desactiva la multi-selección, desactivar también la selección por rectángulo
if (!multiSelectMenuItem.IsChecked)
_objectManager.IsRectangleSelectionActive = false;
}; };
// Solo mostrar opciones de alineación si hay objetos seleccionados contextMenu.Items.Add(multiSelectMenuItem);
if (_objectManager.SelectedObjects.Count > 1)
// Mostrar opción de selección por rectángulo solo cuando multi-selección está activa
if (viewModel.IsMultiSelectionActive)
{ {
var alignmentMenu = new MenuItem { Header = "Alinear" }; var rectangleSelectMenuItem = new MenuItem
var sizeMenu = new MenuItem { Header = "Igualar Tamaño" }; {
var joinMenu = new MenuItem { Header = "Unir" }; Header = "Selección por Rectángulo",
IsCheckable = true,
IsChecked = _objectManager.IsRectangleSelectionActive,
StaysOpenOnClick = false
};
// Opciones de alineación rectangleSelectMenuItem.Click += (s, e) =>
alignmentMenu.Items.Add(new MenuItem { Header = "Alinear a la Izquierda", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.Left)) }); {
alignmentMenu.Items.Add(new MenuItem { Header = "Alinear a la Derecha", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.Right)) }); _objectManager.IsRectangleSelectionActive = rectangleSelectMenuItem.IsChecked;
alignmentMenu.Items.Add(new MenuItem { Header = "Alinear Arriba", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.Top)) }); _objectManager.IsDraggingCanvas = false; // Desactivar panning cuando se activa la selección por rectángulo
alignmentMenu.Items.Add(new MenuItem { Header = "Alinear Abajo", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.Bottom)) }); };
alignmentMenu.Items.Add(new Separator());
alignmentMenu.Items.Add(new MenuItem { Header = "Centrar Horizontalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.CenterHorizontally)) });
alignmentMenu.Items.Add(new MenuItem { Header = "Centrar Verticalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.CenterVertically)) });
alignmentMenu.Items.Add(new Separator());
alignmentMenu.Items.Add(new MenuItem { Header = "Distribuir Horizontalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.DistributeHorizontally)) });
alignmentMenu.Items.Add(new MenuItem { Header = "Distribuir Verticalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.DistributeVertically)) });
// Opciones de igualar tamaño contextMenu.Items.Add(rectangleSelectMenuItem);
sizeMenu.Items.Add(new MenuItem { Header = "Igualar Ancho", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.EqualWidth)) });
sizeMenu.Items.Add(new MenuItem { Header = "Igualar Alto", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.EqualHeight)) });
sizeMenu.Items.Add(new MenuItem { Header = "Igualar Ángulo", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.EqualAngle)) });
// Opciones de unir
joinMenu.Items.Add(new MenuItem { Header = "Unir Horizontalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.JoinHorizontally)) });
joinMenu.Items.Add(new MenuItem { Header = "Unir Verticalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.JoinVertically)) });
contextMenu.Items.Add(alignmentMenu);
contextMenu.Items.Add(sizeMenu);
contextMenu.Items.Add(joinMenu);
contextMenu.Items.Add(new Separator());
} }
contextMenu.Items.Add(multiSelectMenuItem); // Si hay objetos seleccionados, agregar opciones de alineación
if (_objectManager.SelectedObjects.Count > 1)
{
contextMenu.Items.Add(new Separator());
var alignSubmenu = new MenuItem { Header = "Alinear" };
// Alineación horizontal
var alignLeftItem = new MenuItem { Header = "Izquierda" };
alignLeftItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Left);
alignSubmenu.Items.Add(alignLeftItem);
var alignRightItem = new MenuItem { Header = "Derecha" };
alignRightItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Right);
alignSubmenu.Items.Add(alignRightItem);
var alignCenterHItem = new MenuItem { Header = "Centro Horizontal" };
alignCenterHItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.CenterHorizontally);
alignSubmenu.Items.Add(alignCenterHItem);
// Alineación vertical
alignSubmenu.Items.Add(new Separator());
var alignTopItem = new MenuItem { Header = "Arriba" };
alignTopItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Top);
alignSubmenu.Items.Add(alignTopItem);
var alignBottomItem = new MenuItem { Header = "Abajo" };
alignBottomItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Bottom);
alignSubmenu.Items.Add(alignBottomItem);
var alignCenterVItem = new MenuItem { Header = "Centro Vertical" };
alignCenterVItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.CenterVertically);
alignSubmenu.Items.Add(alignCenterVItem);
// Distribución
alignSubmenu.Items.Add(new Separator());
var distributeHItem = new MenuItem { Header = "Distribuir Horizontalmente" };
distributeHItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.DistributeHorizontally);
alignSubmenu.Items.Add(distributeHItem);
var distributeVItem = new MenuItem { Header = "Distribuir Verticalmente" };
distributeVItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.DistributeVertically);
alignSubmenu.Items.Add(distributeVItem);
// Igualar tamaños
alignSubmenu.Items.Add(new Separator());
var equalWidthItem = new MenuItem { Header = "Igualar Ancho" };
equalWidthItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.EqualWidth);
alignSubmenu.Items.Add(equalWidthItem);
var equalHeightItem = new MenuItem { Header = "Igualar Alto" };
equalHeightItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.EqualHeight);
alignSubmenu.Items.Add(equalHeightItem);
var equalAngleItem = new MenuItem { Header = "Igualar Ángulo" };
equalAngleItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.EqualAngle);
alignSubmenu.Items.Add(equalAngleItem);
contextMenu.Items.Add(alignSubmenu);
}
contextMenu.IsOpen = true;
contextMenu.PlacementTarget = ImagenEnTrabajoCanvas; contextMenu.PlacementTarget = ImagenEnTrabajoCanvas;
contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint; contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint;
contextMenu.IsOpen = true;
} }
} }
@ -553,6 +635,8 @@ namespace CtrEditor
} }
} }
} }
public Image ImagenDeFondo => imagenDeFondo;
} }
public class FloatValidationRule : ValidationRule public class FloatValidationRule : ValidationRule

View File

@ -68,11 +68,35 @@ namespace CtrEditor
private ObservableCollection<osBase> _selectedObjects = new ObservableCollection<osBase>(); private ObservableCollection<osBase> _selectedObjects = new ObservableCollection<osBase>();
private List<Rectangle> _selectionHighlightRectangles = new List<Rectangle>(); // Add this line private List<Rectangle> _selectionHighlightRectangles = new List<Rectangle>(); // Add this line
private List<(UserControl Control, Rectangle Highlight)> _selectionHighlightPairs = new List<(UserControl, Rectangle)>(); private List<(UserControl Control, Rectangle Highlight)> _selectionHighlightPairs = new List<(UserControl, Rectangle)>();
private Point _rectangleStart;
public Rectangle _selectionRectangle; // Cambiado a private
private ScrollViewer _scrollViewer; // Add this line
private Image _backgroundImage; // Add this line
internal bool IsDraggingCanvas { get; set; }
private bool _isRectangleSelectionActive;
public bool IsRectangleSelectionActive
{
get => _isRectangleSelectionActive;
set
{
_isRectangleSelectionActive = value;
if (value)
{
// Al activar la selección por rectángulo, desactivar el panning
IsDraggingCanvas = false;
}
}
}
// Propiedad pública para verificar si hay un rectángulo de selección activo
public bool HasActiveSelectionRectangle => _selectionRectangle != null;
public ObjectManipulationManager(MainWindow mainWindow, Canvas canvas) public ObjectManipulationManager(MainWindow mainWindow, Canvas canvas)
{ {
_mainWindow = mainWindow; _mainWindow = mainWindow;
_canvas = canvas; _canvas = canvas;
_scrollViewer = mainWindow.ImagenEnTrabajoScrollViewer; // Add this line
_backgroundImage = mainWindow.ImagenDeFondo; // Add this line
} }
private void PurgeDeletedObjects() private void PurgeDeletedObjects()
@ -843,5 +867,81 @@ namespace CtrEditor
viewModel.HasUnsavedChanges = true; viewModel.HasUnsavedChanges = true;
} }
} }
public void StartRectangleSelection(Point startPoint)
{
_rectangleStart = startPoint;
_selectionRectangle = new Rectangle
{
Stroke = Brushes.Blue,
StrokeThickness = 1,
Fill = new SolidColorBrush(Color.FromArgb(50, 0, 0, 255))
};
Canvas.SetLeft(_selectionRectangle, startPoint.X);
Canvas.SetTop(_selectionRectangle, startPoint.Y);
Canvas.SetZIndex(_selectionRectangle, (int)ZIndexEnum.RectangulosPropiead);
_canvas.Children.Add(_selectionRectangle);
}
private void UpdateSelectionRectangle(Point currentPoint)
{
double left = Math.Min(_rectangleStart.X, currentPoint.X);
double top = Math.Min(_rectangleStart.Y, currentPoint.Y);
double width = Math.Abs(currentPoint.X - _rectangleStart.X);
double height = Math.Abs(currentPoint.Y - _rectangleStart.Y);
_selectionRectangle.Width = width;
_selectionRectangle.Height = height;
Canvas.SetLeft(_selectionRectangle, left);
Canvas.SetTop(_selectionRectangle, top);
}
public void FinishRectangleSelection(Point currentPoint)
{
if (_selectionRectangle != null)
{
var left = Canvas.GetLeft(_selectionRectangle);
var top = Canvas.GetTop(_selectionRectangle);
var right = left + _selectionRectangle.Width;
var bottom = top + _selectionRectangle.Height;
var selectionBounds = new Rect(new Point(left, top), new Point(right, bottom));
var itemsToProcess = _canvas.Children.OfType<UserControl>().Where(child => child is IDataContainer).ToList();
foreach (var child in itemsToProcess)
{
var childBounds = GetElementBounds(child);
if (selectionBounds.Contains(childBounds) || selectionBounds.IntersectsWith(childBounds))
{
if (child.DataContext is osBase osObject)
{
SelectObject(osObject);
}
}
}
_canvas.Children.Remove(_selectionRectangle);
_selectionRectangle = null;
UpdateSelectionVisuals();
}
}
private Rect GetElementBounds(FrameworkElement element)
{
var bounds = VisualTreeHelper.GetDescendantBounds(element);
var transform = element.TransformToAncestor(_canvas);
return transform.TransformBounds(bounds);
}
public void UpdateRectangleSelection(Point currentPoint)
{
if (_selectionRectangle != null)
{
UpdateSelectionRectangle(currentPoint);
}
}
} }
} }

View File

@ -14,46 +14,36 @@ namespace CtrEditor.ObjetosSim
public partial class osBottGenerator : osBase, IosBase public partial class osBottGenerator : osBase, IosBase
{ {
TimerTON_TOFF _TON_TOFF = new TimerTON_TOFF(); TimerTON_TOFF _TON_TOFF = new TimerTON_TOFF();
// Otros datos y métodos relevantes para la simulación
private float TiempoRestante; private float TiempoRestante;
private osBotella UltimaBotella;
public static string NombreClase() public static string NombreClase()
{ {
return "BottGenerator"; return "BottGenerator";
} }
private string nombre = NombreClase(); private string nombre = NombreClase();
public override string Nombre public override string Nombre { get => nombre; set => SetProperty(ref nombre, value); }
{
get => nombre;
set => SetProperty(ref nombre, value);
}
[ObservableProperty] [ObservableProperty]
private float offsetLeftSalida; private float offsetLeftSalida;
[ObservableProperty] [ObservableProperty]
private float offsetTopSalida; private float offsetTopSalida;
[ObservableProperty] [ObservableProperty]
[property: Description("The bottle will be destroyed if fall outside transport.")] [property: Description("The bottle will be destroyed if fall outside transport.")]
[property: Category("Enable to Run:")] [property: Category("Enable to Run:")]
private bool preserve_Outside_Transport; 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")]
[property: Category("Enable to Run:")] [property: Category("Enable to Run:")]
private string tag_consenso; private string tag_consenso;
[ObservableProperty] [ObservableProperty]
[property: Description("Consense to run.")] [property: Description("Consense to run.")]
[property: Category("Enable to Run:")] [property: Category("Enable to Run:")]
private bool consenso; private bool consenso;
partial void OnConsensoChanged(bool value) partial void OnConsensoChanged(bool value)
{ {
_TON_TOFF.Senal = value; _TON_TOFF.Senal = value;
} }
[ObservableProperty] [ObservableProperty]
[property: Description("Consense is Normally close.")] [property: Description("Consense is Normally close.")]
[property: Category("Enable to Run:")] [property: Category("Enable to Run:")]
@ -74,10 +64,8 @@ namespace CtrEditor.ObjetosSim
[property: Description("Filter OUT signal.")] [property: Description("Filter OUT signal.")]
[property: Category("Enable to Run:")] [property: Category("Enable to Run:")]
bool filter_Output; bool filter_Output;
[ObservableProperty] [ObservableProperty]
private float botellas_hora; private float botellas_hora;
partial void OnBotellas_horaChanged(float value) partial void OnBotellas_horaChanged(float value)
{ {
Botellas_segundo = value / 3600; Botellas_segundo = value / 3600;
@ -85,7 +73,6 @@ namespace CtrEditor.ObjetosSim
[ObservableProperty] [ObservableProperty]
private float botellas_segundo; private float botellas_segundo;
partial void OnBotellas_segundoChanged(float value) partial void OnBotellas_segundoChanged(float value)
{ {
Botellas_hora = value * 3600; Botellas_hora = value * 3600;
@ -95,21 +82,6 @@ namespace CtrEditor.ObjetosSim
private float velocidad_actual_percentual; private float velocidad_actual_percentual;
[ObservableProperty] [ObservableProperty]
private float diametro_botella; private float diametro_botella;
public osBottGenerator()
{
Ancho = 0.30f;
Alto = 0.30f;
Angulo = 0;
Velocidad_actual_percentual = 100;
Diametro_botella = 0.1f;
Botellas_hora = 10000;
Filtro_consenso_ON_s = 1;
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)
{ {
if (Consenso_NC) if (Consenso_NC)
@ -118,67 +90,68 @@ namespace CtrEditor.ObjetosSim
Consenso = LeerBitTag(Tag_consenso); Consenso = LeerBitTag(Tag_consenso);
} }
private bool HayEspacioParaNuevaBotella(float X, float Y)
{
float radioMinimo = Diametro_botella / 4; // Distancia mínima entre centros
float radioMinimoCuadrado = radioMinimo * radioMinimo;
// Buscar todas las botellas cercanas
foreach (var obj in _mainViewModel.ObjetosSimulables)
{
if (obj is osBotella)
{
float distanciaCuadrada = (float)(Math.Pow(obj.Left - X, 2) + Math.Pow(obj.Top - Y, 2));
if (distanciaCuadrada < radioMinimoCuadrado)
{
return false; // Hay una botella demasiado cerca
}
}
}
return true; // No hay botellas cercanas
}
public override void UpdateControl(int elapsedMilliseconds) public override void UpdateControl(int elapsedMilliseconds)
{ {
bool habilitado; bool habilitado;
_TON_TOFF.Tiempo_ON_s = Filtro_consenso_ON_s; _TON_TOFF.Tiempo_ON_s = Filtro_consenso_ON_s;
_TON_TOFF.Tiempo_OFF_s = Filtro_consenso_OFF_s; _TON_TOFF.Tiempo_OFF_s = Filtro_consenso_OFF_s;
if (Consenso_Filtrado) if (Consenso_Filtrado)
habilitado = _TON_TOFF.SenalFiltrada(); habilitado = _TON_TOFF.SenalFiltrada();
else else
habilitado = Consenso; habilitado = Consenso;
Filter_Output = habilitado; Filter_Output = habilitado;
if (habilitado && Velocidad_actual_percentual > 0) if (habilitado && Velocidad_actual_percentual > 0)
{ {
if (PrimeraActualizacion)
{
TiempoEntreBotellas = 3600 / (Botellas_hora * (Velocidad_actual_percentual / 100.0f));
TiempoRestante = TiempoEntreBotellas;
PrimeraActualizacion = false;
}
TiempoRestante -= elapsedMilliseconds / 1000.0f; TiempoRestante -= elapsedMilliseconds / 1000.0f;
if (TiempoRestante <= 0) if (TiempoRestante <= 0)
{ {
bool BotellaCreada = false;
var X = Left + OffsetLeftSalida; var X = Left + OffsetLeftSalida;
var Y = Top + OffsetTopSalida; var Y = Top + OffsetTopSalida;
if (UltimaBotella != null && UltimaBotella.RemoverDesdeSimulacion) if (HayEspacioParaNuevaBotella(X, Y))
UltimaBotella = null;
if (UltimaBotella == null)
{ {
// 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).Preserve_Outside_Transport = Preserve_Outside_Transport;
((osBotella)nuevaBotella).AutoCreated = true;
nuevaBotella.AutoCreated = true; nuevaBotella.AutoCreated = true;
UltimaBotella = (osBotella)nuevaBotella;
BotellaCreada = true;
}
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 / 4; // Asumiendo que el diámetro de la nueva botella es similar
if (distancia > distanciaMinima) // Recalcular el tiempo entre botellas por si cambió la velocidad
{ TiempoEntreBotellas = 3600 / (Botellas_hora * (Velocidad_actual_percentual / 100.0f));
osBotella nuevaBotella = (osBotella)_mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y); TiempoRestante = TiempoEntreBotellas;
nuevaBotella.Diametro = Diametro_botella;
((osBotella)nuevaBotella).Preserve_Outside_Transport = Preserve_Outside_Transport;
nuevaBotella.AutoCreated = true;
UltimaBotella = nuevaBotella;
BotellaCreada = true;
}
} }
if (BotellaCreada)
TiempoRestante += 3600 / (Botellas_hora * (Velocidad_actual_percentual / 100.0f));
} }
} }
else else
{ {
TiempoRestante = 0; PrimeraActualizacion = true;
} }
} }
@ -189,6 +162,13 @@ namespace CtrEditor.ObjetosSim
base.ucLoaded(); base.ucLoaded();
} }
private float TiempoEntreBotellas; // Nuevo: almacena el intervalo calculado
private bool PrimeraActualizacion = true; // Nuevo: flag para la primera actualización
public override void SimulationStop()
{
PrimeraActualizacion = true; // Reset del flag al detener
base.SimulationStop();
}
} }
public partial class ucBottGenerator : UserControl, IDataContainer public partial class ucBottGenerator : UserControl, IDataContainer
@ -202,19 +182,24 @@ namespace CtrEditor.ObjetosSim
this.Loaded += OnLoaded; this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded; this.Unloaded += OnUnloaded;
} }
private void OnLoaded(object sender, RoutedEventArgs e) private void OnLoaded(object sender, RoutedEventArgs e)
{ {
Datos?.ucLoaded(); Datos?.ucLoaded();
} }
private void OnUnloaded(object sender, RoutedEventArgs e) private void OnUnloaded(object sender, RoutedEventArgs e)
{ {
Datos?.ucUnLoaded(); Datos?.ucUnLoaded();
} }
public void Highlight(bool State) { }
public void Highlight(bool State)
{
}
public ZIndexEnum ZIndex_Base() public ZIndexEnum ZIndex_Base()
{ {
return ZIndexEnum.Generadores; return ZIndexEnum.Generadores;
} }
} }
}
}

View File

@ -202,10 +202,15 @@ namespace CtrEditor.ObjetosSim
if (DB_Motor == 0) if (DB_Motor == 0)
return; return;
// Read ControlWord in one operation // Add timestamp to trace when the read occurs
int controlWord = plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ControlWord") ?? 0; var timestamp = DateTime.Now;
// Read ControlWord and track the raw response
var rawResponse = plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ControlWord");
int controlWord = rawResponse ?? 0;
var control = VMMotorBitPacker.UnpackControlWord(controlWord); var control = VMMotorBitPacker.UnpackControlWord(controlWord);
// Update local state from ControlWord // Update local state from ControlWord
OUT_Run = control.run; OUT_Run = control.run;
OUT_Stop = control.stop; OUT_Stop = control.stop;
@ -233,6 +238,7 @@ namespace CtrEditor.ObjetosSim
if (Data.VFD_Trip_NC) if (Data.VFD_Trip_NC)
STATUS_VFD_Trip = !STATUS_VFD_Trip; STATUS_VFD_Trip = !STATUS_VFD_Trip;
// Pack all status bits and speed into StatusWord // Pack all status bits and speed into StatusWord
int statusWord = VMMotorBitPacker.PackStatusWord( int statusWord = VMMotorBitPacker.PackStatusWord(
_STATUS_VFD_Ready, _STATUS_VFD_Ready,

View File

@ -128,6 +128,43 @@ namespace CtrEditor.Simulacion
return verticesList; return verticesList;
} }
private float CalculateAngleOverlap(Fixture bottle)
{
// Get bottle position relative to curve center
Vector2 centerToBottle = bottle.Body.Position - Body.Position;
// Calculate angle of bottle relative to curve center (in radians)
float bottleAngle = (float)Math.Atan2(centerToBottle.Y, centerToBottle.X);
// Normalize angle to be positive
if (bottleAngle < 0)
bottleAngle += 2 * (float)Math.PI;
// If bottle is outside the angle range, return 0
if (bottleAngle < _startAngle || bottleAngle > _endAngle)
return 0;
// Calculate distance from center
float distanceToCenter = centerToBottle.Length();
// If bottle is outside radius range, return 0
if (distanceToCenter < _innerRadius || distanceToCenter > _outerRadius)
return 0;
// Calculate how far the bottle is from the edges
float angleFromStart = bottleAngle - _startAngle;
float angleToEnd = _endAngle - bottleAngle;
// Use the minimum distance to either edge to calculate overlap
float minAngleDistance = Math.Min(angleFromStart, angleToEnd);
float totalAngle = _endAngle - _startAngle;
// Calculate overlap percentage based on angle proximity to edges
float overlapPercentage = Math.Min(minAngleDistance / (totalAngle * 0.1f), 1.0f);
return overlapPercentage;
}
public void ApplyCurveEffect(Fixture bottle) public void ApplyCurveEffect(Fixture bottle)
{ {
Vector2 centerToBottle = bottle.Body.Position - Body.Position; Vector2 centerToBottle = bottle.Body.Position - Body.Position;
@ -135,17 +172,46 @@ namespace CtrEditor.Simulacion
if (distanceToCenter >= _innerRadius && distanceToCenter <= _outerRadius) if (distanceToCenter >= _innerRadius && distanceToCenter <= _outerRadius)
{ {
// Calcular la velocidad tangencial // Calculate overlap percentage
float speedMetersPerSecond = Speed / 60.0f; float overlapPercentage = CalculateAngleOverlap(bottle);
float angularVelocity = speedMetersPerSecond / distanceToCenter;
// Vector tangente (perpendicular al radio) // Only apply effect if there's overlap
Vector2 tangent = new Vector2(-centerToBottle.Y, centerToBottle.X); if (overlapPercentage > 0)
tangent.Normalize(); {
// Calculate the tangential velocity
float speedMetersPerSecond = Speed / 60.0f;
// Velocidad deseada // Vector tangent (perpendicular to radius)
Vector2 desiredVelocity = tangent * angularVelocity * distanceToCenter; Vector2 tangent = new Vector2(-centerToBottle.Y, centerToBottle.X);
bottle.Body.LinearVelocity = desiredVelocity; tangent.Normalize();
// Adjust tangent direction based on speed sign
if (speedMetersPerSecond < 0)
tangent = -tangent;
// Current velocity magnitude
float currentSpeed = bottle.Body.LinearVelocity.Length();
// Desired conveyor speed
float conveyorSpeed = Math.Abs(speedMetersPerSecond);
// Use the larger of the two speeds as base speed
float targetSpeed = Math.Max(currentSpeed, conveyorSpeed);
// Lerp between current direction and curve direction
Vector2 currentDir = bottle.Body.LinearVelocity;
if (currentDir.LengthSquared() > 0)
currentDir.Normalize();
else
currentDir = tangent;
// Interpolate between current direction and curve direction
Vector2 newDirection = currentDir * (1 - overlapPercentage) + tangent * overlapPercentage;
newDirection.Normalize();
// Apply new velocity with combined speed
bottle.Body.LinearVelocity = newDirection * targetSpeed;
}
} }
} }
} }
@ -574,8 +640,9 @@ namespace CtrEditor.Simulacion
foreach (var transporte in ListOnTransports) foreach (var transporte in ListOnTransports)
{ {
if (transporte is simTransporte conveyorRect) if (transporte is simTransporte conveyorRect)
if (ApplyConveyorEffect(deltaTime_s, conveyorRect)) ApplyConveyorEffect(deltaTime_s, conveyorRect);
break; // if (ApplyConveyorEffect(deltaTime_s, conveyorRect))
// break;
if (transporte is simCurve conveyorCurve) if (transporte is simCurve conveyorCurve)
conveyorCurve.ApplyCurveEffect(Body.FixtureList[0]); conveyorCurve.ApplyCurveEffect(Body.FixtureList[0]);
} }