Compare commits

..

No commits in common. "99248e9112c79f5ab566a38e77489c6ed73bb894" and "380bc14b6911d03a974d6441b952694b28a28556" have entirely different histories.

9 changed files with 42 additions and 1243 deletions

View File

@ -447,9 +447,6 @@ namespace CtrEditor
{
OnVisFilterChanged(MainWindow.VisFilter.FilterViewModel);
}
// Limpiar historial de undo al cargar un estado desde archivo
MainWindow?.ClearUndoHistory();
}
}
@ -458,9 +455,6 @@ namespace CtrEditor
{
// Suponiendo que "SelectedImage" es una propiedad que al establecerse dispara "ImageSelected"
directorioTrabajo = EstadoPersistente.Instance.directorio;
// Limpiar historial de undo al cargar datos iniciales
MainWindow?.ClearUndoHistory();
}
// Crear un nuevo Objeto
@ -800,9 +794,6 @@ namespace CtrEditor
// Restaurar los rectángulos de selección si hay objetos seleccionados
_objectManager.UpdateSelectionVisuals();
// Limpiar historial de undo al detener la simulación
MainWindow?.ClearUndoHistory();
}
/// <summary>
@ -878,8 +869,6 @@ namespace CtrEditor
foreach (var objetoSimulable in ObjetosSimulables)
objetoSimulable.SetPLC(null);
// Limpiar historial de undo al desconectar del PLC
MainWindow?.ClearUndoHistory();
}
private List<osBase> objetosSimulablesLlamados = new List<osBase>();

View File

@ -11,11 +11,6 @@ using System.Windows.Threading;
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
using UserControl = System.Windows.Controls.UserControl;
using CtrEditor.Controls; // Add this using statement
using Newtonsoft.Json;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System;
using System.Linq;
namespace CtrEditor
{
@ -412,9 +407,6 @@ namespace CtrEditor
private void MoveSelectedObjects(float deltaX, float deltaY)
{
// Capturar estado antes de mover
_objectManager.CaptureUndoState();
// Mover todos los objetos primero
foreach (var obj in _objectManager.SelectedObjects)
{
@ -434,23 +426,6 @@ namespace CtrEditor
}
}
private void LockSelectedObjects(bool lockState)
{
foreach (var selectedObject in _objectManager.SelectedObjects)
{
selectedObject.Lock_movement = lockState;
}
// Actualizar los visuales de selección para reflejar el cambio de estado
_objectManager.UpdateSelectionVisuals();
// Marcar como cambios sin guardar
if (DataContext is MainViewModel viewModel)
{
viewModel.HasUnsavedChanges = true;
}
}
private void Canvas_MouseEnter(object sender, MouseEventArgs e)
{
if (e.OriginalSource == ImagenEnTrabajoCanvas)
@ -523,11 +498,6 @@ namespace CtrEditor
debugWindow.Show();
}
public void ClearUndoHistory()
{
_objectManager.ClearUndoHistory();
}
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
// Aceptar el evento si viene del canvas o de la imagen de fondo
@ -658,72 +628,8 @@ namespace CtrEditor
contextMenu.Items.Add(editPropertiesItem);
contextMenu.Items.Add(new Separator());
// Agregar opción de copiar
var copyMenuItem = new MenuItem { Header = "Copiar (Ctrl+C)" };
copyMenuItem.Click += (s, e) => CopySelectedObjectsAsJson();
contextMenu.Items.Add(copyMenuItem);
contextMenu.Items.Add(new Separator());
}
// Agregar opciones de pegado
if (CanPasteFromClipboard())
{
var pasteMenuItem = new MenuItem { Header = "Pegar (Ctrl+V)" };
pasteMenuItem.Click += (s, e) => PasteObjectsFromJson();
contextMenu.Items.Add(pasteMenuItem);
var pasteReplaceMenuItem = new MenuItem { Header = "Pegar con Reemplazo" };
pasteReplaceMenuItem.Click += (s, e) => PasteObjectsFromJson(true);
contextMenu.Items.Add(pasteReplaceMenuItem);
contextMenu.Items.Add(new Separator());
}
// Opciones de bloqueo/desbloqueo - mostrar siempre que haya objetos seleccionados
if (_objectManager.SelectedObjects.Count > 0)
{
var lockSubmenu = new MenuItem { Header = "Bloqueo" };
// Verificar el estado actual de los objetos
int totalObjects = _objectManager.SelectedObjects.Count;
int lockedObjects = _objectManager.SelectedObjects.Count(obj => obj.Lock_movement);
int unlockedObjects = totalObjects - lockedObjects;
var lockAllItem = new MenuItem { Header = $"Bloquear Objetos ({unlockedObjects} desbloqueados)" };
lockAllItem.Click += (s, e) => LockSelectedObjects(true);
lockAllItem.IsEnabled = unlockedObjects > 0;
lockSubmenu.Items.Add(lockAllItem);
var unlockAllItem = new MenuItem { Header = $"Desbloquear Objetos ({lockedObjects} bloqueados)" };
unlockAllItem.Click += (s, e) => LockSelectedObjects(false);
unlockAllItem.IsEnabled = lockedObjects > 0;
lockSubmenu.Items.Add(unlockAllItem);
// Mostrar estado actual si hay mezcla
if (lockedObjects > 0 && unlockedObjects > 0)
{
lockSubmenu.Items.Add(new Separator());
var statusItem = new MenuItem { Header = $"Estado: {lockedObjects} bloqueados, {unlockedObjects} libres", IsEnabled = false };
lockSubmenu.Items.Add(statusItem);
}
contextMenu.Items.Add(lockSubmenu);
contextMenu.Items.Add(new Separator());
}
// Agregar información del sistema de undo
var undoHistoryCount = _objectManager.GetUndoHistoryCount();
var canUndo = _objectManager.CanUndo();
var undoInfoItem = new MenuItem
{
Header = $"Undo: {undoHistoryCount}/3 estados ({(canUndo ? "Ctrl+Z disponible" : "No disponible")})",
IsEnabled = false,
FontStyle = FontStyles.Italic
};
contextMenu.Items.Add(undoInfoItem);
contextMenu.IsOpen = true;
contextMenu.PlacementTarget = ImagenEnTrabajoCanvas;
contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint;
@ -766,21 +672,6 @@ namespace CtrEditor
_objectManager.RemoveResizeRectangles();
e.Handled = true;
}
else if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.C)
{
CopySelectedObjectsAsJson();
e.Handled = true;
}
else if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.V)
{
PasteObjectsFromJson();
e.Handled = true;
}
else if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.Z)
{
_objectManager.PerformUndo();
e.Handled = true;
}
else if (_objectManager.SelectedObjects.Any())
{
const float moveDistance = 0.01f;
@ -808,314 +699,6 @@ namespace CtrEditor
}
public Image ImagenDeFondo => imagenDeFondo;
private void CopySelectedObjectsAsJson()
{
if (!_objectManager.SelectedObjects.Any())
return;
try
{
if (DataContext is MainViewModel viewModel)
{
// Usar la misma lógica que DuplicarObjeto pero solo para crear copias para serialización
var objectsCopy = new List<osBase>();
foreach (var originalObj in _objectManager.SelectedObjects)
{
try
{
// Reutilizar la lógica existente de DuplicarObjeto sin añadir a ObjetosSimulables
var tempCopy = CreateCopyUsingExistingLogic(viewModel, originalObj);
if (tempCopy != null)
{
objectsCopy.Add(tempCopy);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error al crear copia de {originalObj.Nombre}: {ex.Message}");
}
}
if (objectsCopy.Count == 0)
{
MessageBox.Show("No se pudieron copiar los objetos seleccionados.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// Serializar usando las mismas configuraciones que DuplicarObjeto
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.All // Igual que en DuplicarObjeto
};
string jsonString = JsonConvert.SerializeObject(objectsCopy, settings);
Clipboard.SetText(jsonString);
Console.WriteLine($"Copiados {objectsCopy.Count} objeto(s) al portapapeles");
}
}
catch (Exception ex)
{
MessageBox.Show($"Error al copiar objetos: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private osBase CreateCopyUsingExistingLogic(MainViewModel viewModel, osBase original)
{
// Usar exactamente la misma lógica que DuplicarObjeto pero sin añadir a la colección
osBase copy = null;
original.SalvarDatosNoSerializables();
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.All // Igual que en DuplicarObjeto
};
try
{
var serializedData = JsonConvert.SerializeObject(original, settings);
copy = JsonConvert.DeserializeObject<osBase>(serializedData, settings);
}
finally
{
original.RestaurarDatosNoSerializables();
}
return copy;
}
private void PasteObjectsFromJson(bool replaceExistingIds = false)
{
if (!Clipboard.ContainsText())
return;
try
{
string jsonString = Clipboard.GetText();
// Validación básica del JSON
if (string.IsNullOrWhiteSpace(jsonString) || (!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
{
MessageBox.Show("El contenido del portapapeles no parece ser un JSON válido.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// Crear configuración de deserialización
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
List<osBase> pastedObjects = null;
// Intentar deserializar como lista de objetos
try
{
pastedObjects = JsonConvert.DeserializeObject<List<osBase>>(jsonString, settings);
}
catch
{
// Si falla, intentar como objeto individual
try
{
var singleObject = JsonConvert.DeserializeObject<osBase>(jsonString, settings);
if (singleObject != null)
{
pastedObjects = new List<osBase> { singleObject };
}
}
catch
{
MessageBox.Show("No se pudo deserializar el contenido del portapapeles como objetos válidos.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
}
if (pastedObjects == null || !pastedObjects.Any())
{
MessageBox.Show("No se encontraron objetos válidos en el portapapeles.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// Obtener posición del mouse en el canvas
var mousePosition = Mouse.GetPosition(ImagenEnTrabajoCanvas);
var mousePositionMeters = (
PixelToMeter.Instance.calc.PixelsToMeters((float)mousePosition.X),
PixelToMeter.Instance.calc.PixelsToMeters((float)mousePosition.Y)
);
// Calcular el centro de los objetos pegados para posicionarlos en el mouse
var centerX = pastedObjects.Average(obj => obj.Left);
var centerY = pastedObjects.Average(obj => obj.Top);
var offsetX = mousePositionMeters.Item1 - centerX;
var offsetY = mousePositionMeters.Item2 - centerY;
if (DataContext is MainViewModel viewModel)
{
viewModel.StopSimulation();
viewModel.DisconnectPLC();
// Limpiar selección actual
_objectManager.ClearSelection();
var newlyPastedObjects = new List<osBase>();
foreach (var obj in pastedObjects)
{
try
{
// Aplicar offset de posición
obj.Left += offsetX;
obj.Top += offsetY;
// Manejar IDs usando la misma lógica que DuplicarObjeto
if (!replaceExistingIds)
{
// Generar nuevo ID y actualizar nombre (igual que en DuplicarObjeto)
obj.Id.ObtenerNuevaID();
string nombre = Regex.IsMatch(obj.Nombre, @"_\d+$")
? Regex.Replace(obj.Nombre, @"_\d+$", $"_{obj.Id.Value}")
: obj.Nombre + "_" + obj.Id.Value;
obj.Nombre = nombre;
}
else
{
// Reemplazar objeto existente con mismo ID
var existingObj = viewModel.ObjetosSimulables.FirstOrDefault(o => o.Id.Value == obj.Id.Value);
if (existingObj != null)
{
viewModel.RemoverObjetoSimulable(existingObj);
}
}
obj.CheckData();
viewModel.ObjetosSimulables.Add(obj);
viewModel.CrearUserControlDesdeObjetoSimulable(obj);
viewModel.HasUnsavedChanges = true;
newlyPastedObjects.Add(obj);
}
catch (Exception ex)
{
MessageBox.Show($"Error al procesar objeto {obj.Nombre}: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
// Usar la misma lógica de selección que DuplicarUserControl
// Limpiar selección antes de seleccionar los nuevos objetos
_objectManager.ClearSelection();
// Forzar actualización completa del layout
ImagenEnTrabajoCanvas.UpdateLayout();
// Usar dispatcher con la misma prioridad que DuplicarUserControl
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
{
// Seleccionar todos los objetos pegados
foreach (var newObj in newlyPastedObjects)
{
if (newObj.VisualRepresentation != null)
{
double left = Canvas.GetLeft(newObj.VisualRepresentation);
double top = Canvas.GetTop(newObj.VisualRepresentation);
// Solo añadir a selección si el objeto tiene coordenadas válidas
if (!double.IsNaN(left) && !double.IsNaN(top) && !double.IsInfinity(left) && !double.IsInfinity(top))
{
_objectManager.SelectObject(newObj);
}
}
}
// Forzar otra actualización del layout antes de actualizar visuales
ImagenEnTrabajoCanvas.UpdateLayout();
// Actualizar SelectedItemOsList si hay objetos pegados
if (newlyPastedObjects.Count > 0)
{
if (DataContext is MainViewModel vm)
{
vm.SelectedItemOsList = newlyPastedObjects.LastOrDefault();
}
}
// Actualizar visuales de selección
_objectManager.UpdateSelectionVisuals();
Console.WriteLine($"Pegados y seleccionados {newlyPastedObjects.Count} objeto(s)");
}));
Console.WriteLine($"Pegados {newlyPastedObjects.Count} objeto(s) desde el portapapeles");
}
}
catch (Exception ex)
{
MessageBox.Show($"Error al pegar objetos: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private bool CanPasteFromClipboard()
{
if (!Clipboard.ContainsText())
return false;
try
{
string jsonString = Clipboard.GetText();
// Validación básica del JSON
if (string.IsNullOrWhiteSpace(jsonString) ||
(!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
{
return false;
}
// Intentar una deserialización rápida para validar
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
// Intentar deserializar como lista de objetos
try
{
var pastedObjects = JsonConvert.DeserializeObject<List<osBase>>(jsonString, settings);
return pastedObjects != null && pastedObjects.Any();
}
catch
{
// Si falla como lista, intentar como objeto individual
try
{
var singleObject = JsonConvert.DeserializeObject<osBase>(jsonString, settings);
return singleObject != null;
}
catch
{
return false;
}
}
}
catch
{
return false;
}
}
}
public class FloatValidationRule : ValidationRule

View File

@ -10,58 +10,6 @@ using Color = System.Windows.Media.Color;
namespace CtrEditor
{
// Clase para almacenar el estado básico de un objeto para undo
public class ObjectState
{
public int ObjectId { get; set; }
public float Left { get; set; }
public float Top { get; set; }
public float Angle { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public ObjectState(osBase obj)
{
ObjectId = obj.Id.Value;
Left = obj.Left;
Top = obj.Top;
Angle = obj.Angulo;
Width = obj.Ancho;
Height = obj.Alto;
}
public ObjectState(ObjectState other)
{
ObjectId = other.ObjectId;
Left = other.Left;
Top = other.Top;
Angle = other.Angle;
Width = other.Width;
Height = other.Height;
}
}
// Clase para almacenar un estado completo del workspace
public class UndoState
{
public List<ObjectState> ObjectStates { get; set; }
public DateTime Timestamp { get; set; }
public UndoState()
{
ObjectStates = new List<ObjectState>();
Timestamp = DateTime.Now;
}
public UndoState(IEnumerable<osBase> objects) : this()
{
foreach (var obj in objects)
{
ObjectStates.Add(new ObjectState(obj));
}
}
}
public enum AlignmentType
{
Left,
@ -79,19 +27,6 @@ namespace CtrEditor
JoinVertically
}
/// <summary>
/// Gestor de manipulación de objetos que maneja la selección, movimiento, rotación y redimensionamiento de objetos.
///
/// MEJORAS PARA OBJETOS ROTADOS:
/// - HandleResizeForObject: Aplica transformaciones matemáticas para convertir los cambios del mouse
/// desde coordenadas del canvas a coordenadas locales del objeto rotado.
/// - DetermineHandleMode: Adapta el comportamiento de los handles según la rotación del objeto.
/// - GetAdaptiveCursors: Cambia los cursores de los handles para reflejar la orientación actual del objeto.
///
/// Estas mejoras hacen que el redimensionamiento sea más intuitivo cuando los objetos están rotados,
/// ya que los handles responden de acuerdo a la orientación del objeto en lugar de las coordenadas
/// absolutas del canvas.
/// </summary>
public class ObjectManipulationManager
{
private enum ResizeMode
@ -141,11 +76,6 @@ namespace CtrEditor
private bool _isRectangleSelectionActive;
private bool _selectedObjectsAreVisible;
// Sistema de Undo
private Stack<UndoState> _undoHistory = new Stack<UndoState>();
private const int MaxUndoSteps = 3;
private bool _isApplyingUndo = false;
public bool IsRectangleSelectionActive
{
get => _isRectangleSelectionActive;
@ -384,22 +314,19 @@ namespace CtrEditor
// Calcular el offset para posicionar los manejadores fuera del rectángulo
double offset = rectSize / 2;
// Determinar cursores adaptativos según la rotación del objeto
var cursors = GetAdaptiveCursors();
var positions = new List<(Point Position, string Tag, Cursor Cursor, Brush Stroke)>
{
// Esquinas - mover hacia afuera del rectángulo
(new Point(rectBox.Left - offset, rectBox.Top - offset), "TopLeft", Cursors.Arrow, Brushes.Gray),
(new Point(rectBox.Right + offset, rectBox.Top - offset), "TopRight", rotationCursorRx, Brushes.Red),
(new Point(rectBox.Left - offset, rectBox.Bottom + offset), "BottomLeft", rotationCursorSx, Brushes.DarkRed),
(new Point(rectBox.Right + offset, rectBox.Bottom + offset), "BottomRight", cursors.ResizeBoth, Brushes.Blue),
(new Point(rectBox.Right + offset, rectBox.Bottom + offset), "BottomRight", Cursors.SizeNWSE, Brushes.Blue),
// Centros de los bordes - mover hacia afuera del rectángulo
(new Point(rectBox.Left + rectBox.Width / 2, rectBox.Top - offset), "TopCenter", Cursors.Arrow, Brushes.Gray),
(new Point(rectBox.Left + rectBox.Width / 2, rectBox.Bottom + offset), "BottomCenter", cursors.ResizeVertical, Brushes.Blue),
(new Point(rectBox.Left + rectBox.Width / 2, rectBox.Bottom + offset), "BottomCenter", Cursors.SizeNS, Brushes.Blue),
(new Point(rectBox.Left - offset, rectBox.Top + rectBox.Height / 2), "CenterLeft", rotationCursorRx, Brushes.Red),
(new Point(rectBox.Right + offset, rectBox.Top + rectBox.Height / 2), "CenterRight", cursors.ResizeHorizontal, Brushes.Blue)
(new Point(rectBox.Right + offset, rectBox.Top + rectBox.Height / 2), "CenterRight", Cursors.SizeWE, Brushes.Blue)
};
foreach (var (Position, Tag, Cursor, Stroke) in positions)
@ -411,43 +338,6 @@ namespace CtrEditor
}
}
private (Cursor ResizeHorizontal, Cursor ResizeVertical, Cursor ResizeBoth) GetAdaptiveCursors()
{
// Para un solo objeto o múltiples objetos con la misma rotación
var firstObject = _selectedObjects.FirstOrDefault();
if (firstObject == null)
return (Cursors.SizeWE, Cursors.SizeNS, Cursors.SizeNWSE);
double angle = Math.Abs(firstObject.Angulo % 360);
// Si el objeto no está significativamente rotado, usar cursores estándar
if (angle < 15 || (angle > 345 && angle < 360) ||
(angle > 165 && angle < 195))
{
return (Cursors.SizeWE, Cursors.SizeNS, Cursors.SizeNWSE);
}
else if ((angle > 75 && angle < 105) || (angle > 255 && angle < 285))
{
// Objeto rotado 90 grados - intercambiar cursores
return (Cursors.SizeNS, Cursors.SizeWE, Cursors.SizeNESW);
}
else if ((angle > 30 && angle < 60) || (angle > 210 && angle < 240))
{
// Objeto rotado aproximadamente 45 grados
return (Cursors.SizeNESW, Cursors.SizeNWSE, Cursors.SizeAll);
}
else if ((angle > 120 && angle < 150) || (angle > 300 && angle < 330))
{
// Objeto rotado aproximadamente 135 grados
return (Cursors.SizeNWSE, Cursors.SizeNESW, Cursors.SizeAll);
}
else
{
// Para otros ángulos, usar cursores generales
return (Cursors.SizeAll, Cursors.SizeAll, Cursors.SizeAll);
}
}
private Rectangle CreateResizeHandle(double rectSize, string tag, Cursor cursor, Brush stroke)
{
var handle = new Rectangle
@ -725,9 +615,6 @@ namespace CtrEditor
return;
}
// Capturar estado antes de iniciar redimensionamiento
CaptureUndoState();
_currentDraggingRectangle = sender as Rectangle;
_lastMousePosition = e.GetPosition(_canvas);
_currentDraggingRectangle.CaptureMouse();
@ -738,49 +625,25 @@ namespace CtrEditor
}
var userControl = sender as UserControl;
if (userControl != null && userControl.DataContext is osBase datos)
if (userControl != null)
{
bool isControlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
// Determinar qué objetos serán afectados por el movimiento
List<osBase> objectsToMove = new List<osBase>();
if (!isControlPressed)
if (userControl.DataContext is osBase datos)
{
// Si no está Ctrl presionado y vamos a hacer dragging
if (_selectedObjects.Contains(datos))
{
// Si el objeto ya está seleccionado, vamos a mover todos los seleccionados
objectsToMove.AddRange(_selectedObjects);
}
else
{
// Si no está seleccionado, solo vamos a mover este objeto
objectsToMove.Add(datos);
}
HandleObjectSelection(userControl, datos);
// Verificar si alguno de los objetos a mover está bloqueado
bool willMoveLockedObjects = objectsToMove.Any(obj => obj.Lock_movement);
if (!willMoveLockedObjects)
{
// Capturar estado antes de iniciar movimiento
CaptureUndoStateForObjects(objectsToMove);
userControl.CaptureMouse();
_currentDraggingControl = userControl;
_isMovingUserControl = true;
InitializeDrag(e);
}
// Actualizar el estado de objetos bloqueados después de la selección
hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
}
// Manejar la selección después de capturar el estado
HandleObjectSelection(userControl, datos);
// Actualizar el estado de objetos bloqueados después de la selección
hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
if (hasLockedObjects && !isControlPressed)
// Solo iniciar el arrastre si no se presionó Ctrl y no hay objetos bloqueados
if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && !hasLockedObjects)
{
userControl.CaptureMouse();
_currentDraggingControl = userControl;
_isMovingUserControl = true;
InitializeDrag(e);
}
else if (hasLockedObjects)
{
// Si hay objetos bloqueados, no permitir el inicio del arrastre
e.Handled = true;
@ -974,39 +837,6 @@ namespace CtrEditor
}
private HandleMode DetermineHandleMode(string resizeDirection)
{
// Si tenemos múltiples objetos seleccionados con diferentes rotaciones,
// usar el comportamiento por defecto
if (_selectedObjects.Count > 1)
{
var angles = _selectedObjects.Select(obj => Math.Abs(obj.Angulo % 360)).Distinct().ToList();
if (angles.Count > 1) // Diferentes ángulos
{
return DetermineHandleModeDefault(resizeDirection);
}
}
// Para un solo objeto o múltiples objetos con la misma rotación
var firstObject = _selectedObjects.FirstOrDefault();
if (firstObject == null)
return HandleMode.None;
double angle = Math.Abs(firstObject.Angulo % 360);
// Si el objeto no está significativamente rotado, usar el comportamiento por defecto
if (angle < 15 || (angle > 345 && angle < 360) ||
(angle > 75 && angle < 105) ||
(angle > 165 && angle < 195) ||
(angle > 255 && angle < 285))
{
return DetermineHandleModeDefault(resizeDirection);
}
// Para objetos rotados significativamente, adaptar los handles
return DetermineHandleModeRotated(resizeDirection, angle);
}
private HandleMode DetermineHandleModeDefault(string resizeDirection)
{
return resizeDirection switch
{
@ -1020,21 +850,6 @@ namespace CtrEditor
};
}
private HandleMode DetermineHandleModeRotated(string resizeDirection, double angle)
{
// Para objetos rotados, hacer que todos los handles de redimensionamiento funcionen como los corners
// Esto es mucho más intuitivo ya que el usuario puede redimensionar en cualquier dirección
// sin preocuparse por la orientación específica del objeto
return resizeDirection switch
{
"TopLeft" => HandleMode.None,
"TopRight" or "BottomLeft" or "CenterLeft" => HandleMode.Rotate,
"BottomRight" or "BottomCenter" or "CenterRight" or "TopCenter" => HandleMode.ResizeBoth,
_ => HandleMode.None
};
}
private void HandleResize(Point currentPosition, HandleMode mode)
{
PurgeDeletedObjects();
@ -1061,53 +876,8 @@ namespace CtrEditor
private void HandleResizeForObject(osBase obj, Point currentPosition, bool activateSizeWidth, bool activateSizeHeight)
{
// Calcular el cambio del mouse en coordenadas del canvas
double deltaX = currentPosition.X - _startPointUserControl.X;
double deltaY = currentPosition.Y - _startPointUserControl.Y;
// Inicializar variables de cambio
double widthChange = 0;
double heightChange = 0;
// Si el objeto no está rotado, usar el método original
if (Math.Abs(obj.Angulo) < 0.1) // Umbral pequeño para considerar "sin rotación"
{
widthChange = activateSizeWidth ? deltaX : 0;
heightChange = activateSizeHeight ? deltaY : 0;
}
else
{
// Para objetos rotados, transformar los cambios del mouse según la orientación del objeto
// Convertir ángulo de grados a radianes
double angleRad = obj.Angulo * Math.PI / 180.0;
// Calcular los componentes de cambio transformados según la rotación
// Rotar el vector de cambio por el ángulo negativo del objeto para obtener
// los cambios en el sistema de coordenadas local del objeto
double cos = Math.Cos(-angleRad);
double sin = Math.Sin(-angleRad);
double localDeltaX = deltaX * cos - deltaY * sin;
double localDeltaY = deltaX * sin + deltaY * cos;
// Determinar qué componente aplicar según el tipo de redimensionamiento
if (activateSizeWidth && activateSizeHeight)
{
// Redimensionamiento proporcional - usar la componente más grande transformada
widthChange = localDeltaX;
heightChange = localDeltaY;
}
else if (activateSizeWidth)
{
// Solo redimensionar ancho - usar componente X transformada
widthChange = localDeltaX;
}
else if (activateSizeHeight)
{
// Solo redimensionar alto - usar componente Y transformada
heightChange = localDeltaY;
}
}
double widthChange = activateSizeWidth ? currentPosition.X - _startPointUserControl.X : 0;
double heightChange = activateSizeHeight ? currentPosition.Y - _startPointUserControl.Y : 0;
obj.Resize(widthChange, heightChange);
}
@ -1159,9 +929,6 @@ namespace CtrEditor
return; // No alinear si hay objetos bloqueados
}
// Capturar estado antes de alinear
CaptureUndoState();
var alignment = new ObjectAlignment(_selectedObjects, _selectedObjects.FirstOrDefault());
switch (alignmentType)
@ -1323,149 +1090,5 @@ namespace CtrEditor
UpdateSelectionRectangle(currentPoint);
}
}
// Métodos del sistema de Undo
public void CaptureUndoState()
{
// Solo capturar estado si no estamos aplicando un undo y si es permitido
if (_isApplyingUndo || !CanPerformUndo())
return;
// Solo capturar estado si hay objetos seleccionados
if (_selectedObjects.Count == 0)
return;
if (_mainWindow.DataContext is MainViewModel viewModel)
{
// Capturar solo el estado de los objetos seleccionados
var undoState = new UndoState(_selectedObjects);
// Mantener solo los últimos MaxUndoSteps estados
if (_undoHistory.Count >= MaxUndoSteps)
{
// Convertir a lista, remover el más antiguo, y volver a crear el stack
var tempList = _undoHistory.ToList();
tempList.Reverse(); // Invertir para mantener el orden correcto
tempList.RemoveAt(0); // Remover el más antiguo
_undoHistory.Clear();
foreach (var state in tempList)
{
_undoHistory.Push(state);
}
}
_undoHistory.Push(undoState);
Console.WriteLine($"Estado capturado para undo de {_selectedObjects.Count} objetos. Historial: {_undoHistory.Count} estados");
}
}
// Método para capturar el estado de objetos específicos
public void CaptureUndoStateForObjects(IEnumerable<osBase> objects)
{
// Solo capturar estado si no estamos aplicando un undo y si es permitido
if (_isApplyingUndo || !CanPerformUndo())
return;
var objectsList = objects.ToList();
if (objectsList.Count == 0)
return;
if (_mainWindow.DataContext is MainViewModel viewModel)
{
// Capturar solo el estado de los objetos especificados
var undoState = new UndoState(objectsList);
// Mantener solo los últimos MaxUndoSteps estados
if (_undoHistory.Count >= MaxUndoSteps)
{
// Convertir a lista, remover el más antiguo, y volver a crear el stack
var tempList = _undoHistory.ToList();
tempList.Reverse(); // Invertir para mantener el orden correcto
tempList.RemoveAt(0); // Remover el más antiguo
_undoHistory.Clear();
foreach (var state in tempList)
{
_undoHistory.Push(state);
}
}
_undoHistory.Push(undoState);
Console.WriteLine($"Estado capturado para undo de {objectsList.Count} objetos específicos. Historial: {_undoHistory.Count} estados");
}
}
public bool CanPerformUndo()
{
if (_mainWindow.DataContext is MainViewModel viewModel)
{
// Solo permitir undo si no estamos en simulación ni conectados
return !viewModel.IsSimulationRunning && !viewModel.IsConnected;
}
return false;
}
public bool CanUndo()
{
return CanPerformUndo() && _undoHistory.Count > 0;
}
public void PerformUndo()
{
if (!CanUndo())
return;
if (_mainWindow.DataContext is MainViewModel viewModel)
{
_isApplyingUndo = true;
try
{
var undoState = _undoHistory.Pop();
Console.WriteLine($"Aplicando undo. Estados restantes: {_undoHistory.Count}");
// Aplicar el estado a cada objeto
foreach (var objectState in undoState.ObjectStates)
{
var obj = viewModel.ObjetosSimulables.FirstOrDefault(o => o.Id.Value == objectState.ObjectId);
if (obj != null)
{
// Aplicar solo los parámetros básicos
obj.Left = objectState.Left;
obj.Top = objectState.Top;
obj.Angulo = objectState.Angle;
obj.Ancho = objectState.Width;
obj.Alto = objectState.Height;
// Actualizar la posición visual
obj.OnMoveResizeRotate();
}
}
// Actualizar los visuales de selección
UpdateSelectionVisuals();
// Marcar como cambios sin guardar
viewModel.HasUnsavedChanges = true;
Console.WriteLine("Undo aplicado exitosamente");
}
finally
{
_isApplyingUndo = false;
}
}
}
public void ClearUndoHistory()
{
_undoHistory.Clear();
Console.WriteLine("Historial de undo limpiado");
}
// Método de conveniencia para obtener información del historial
public int GetUndoHistoryCount()
{
return _undoHistory.Count;
}
}
}

View File

@ -5,15 +5,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osFramePlate Color_Titulo="Black" Alto_Titulo="0.1" Color="WhiteSmoke" />
</UserControl.DataContext>
<Grid>
<Grid.RenderTransform>
<RotateTransform Angle="{Binding Angulo}"
CenterX="{Binding RenderTransformCenterX}"
CenterY="{Binding RenderTransformCenterY}" />
</Grid.RenderTransform>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
@ -27,5 +24,6 @@
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" Stroke="Blue"
StrokeThickness="0.2"
Visibility="{Binding ShowPlate, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</UserControl>

View File

@ -24,8 +24,6 @@ namespace CtrEditor.ObjetosSim
public float offsetY;
[JsonIgnore]
public float offsetX;
[JsonIgnore]
public float offsetAngulo;
public static string NombreClase()
{
@ -105,29 +103,6 @@ namespace CtrEditor.ObjetosSim
[property: Category("Encoders:")]
public float offset_encoder_Y;
// Encoder Rotation
[ObservableProperty]
[property: Description("This is a link to a Encoder for Rotation.")]
[property: Category("Encoders:")]
[property: ItemsSource(typeof(osBaseItemsSource<osEncoderMotorLineal>))]
private string encoder_Rotation;
[ObservableProperty]
[property: Description("K Pulses per degree for Rotation")]
[property: Category("Encoders:")]
public float k_encoder_Rotation;
[ObservableProperty]
[property: ReadOnly(true)]
[property: Description("Actual Rotation Angle")]
[property: Category("Encoders:")]
public float encoder_Rotation_Angle;
[ObservableProperty]
[property: Description("Angle in degrees offset. Angle when the encoder is 0")]
[property: Category("Encoders:")]
public float offset_encoder_Rotation;
partial void OnK_encoder_YChanged(float value)
{
UpdatePosition();
@ -148,34 +123,18 @@ namespace CtrEditor.ObjetosSim
UpdatePosition();
}
partial void OnK_encoder_RotationChanged(float value)
{
UpdatePosition();
}
partial void OnOffset_encoder_RotationChanged(float value)
{
UpdatePosition();
}
[JsonIgnore]
private osEncoderMotorLineal EncoderX;
[JsonIgnore]
private osEncoderMotorLineal EncoderY;
[JsonIgnore]
private osEncoderMotorLineal EncoderRotation;
[JsonIgnore]
private bool isUpdatingFromEncoderX = false;
[JsonIgnore]
private bool isUpdatingFromEncoderY = false;
[JsonIgnore]
private bool isUpdatingFromEncoderRotation = false;
partial void OnEncoder_XChanged(string value)
{
if (EncoderX != null)
@ -200,18 +159,6 @@ namespace CtrEditor.ObjetosSim
}
}
partial void OnEncoder_RotationChanged(string value)
{
if (EncoderRotation != null)
EncoderRotation.PropertyChanged -= OnEncoderRotationPropertyChanged;
if (_mainViewModel != null && value != null && value.Length > 0)
{
EncoderRotation = (osEncoderMotorLineal)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osEncoderMotorLineal && s.Nombre == value), null);
if (EncoderRotation != null)
EncoderRotation.PropertyChanged += OnEncoderRotationPropertyChanged;
}
}
private void OnEncoderXPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!isUpdatingFromEncoderX)
@ -248,30 +195,12 @@ namespace CtrEditor.ObjetosSim
}
}
private void OnEncoderRotationPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!isUpdatingFromEncoderRotation)
{
isUpdatingFromEncoderRotation = true;
// Actualizamos el nombre si este fue modificado
if (e.PropertyName == nameof(osEncoderMotorLineal.Nombre))
Encoder_Rotation = ((osEncoderMotorLineal)sender).Nombre;
if (e.PropertyName == nameof(osEncoderMotorLineal.Valor_Actual))
{
UpdatePosition();
}
isUpdatingFromEncoderRotation = false;
}
}
public void UpdatePosition()
{
// Update X position if encoder is available
if (EncoderX != null && K_encoder_X != 0)
{
Encoder_X_Position = EncoderX.Valor_Actual;
Encoder_Y_Position = EncoderY.Valor_Actual;
Left = (Encoder_X_Position / k_encoder_X) + offset_encoder_X;
}
// Update Y position if encoder is available
@ -280,12 +209,6 @@ namespace CtrEditor.ObjetosSim
Encoder_Y_Position = EncoderY.Valor_Actual;
Top = (Encoder_Y_Position / k_encoder_Y) + offset_encoder_Y;
}
// Update rotation angle if encoder is available
if (EncoderRotation != null && K_encoder_Rotation != 0)
{
Encoder_Rotation_Angle = EncoderRotation.Valor_Actual;
Angulo = (Encoder_Rotation_Angle / k_encoder_Rotation) + offset_encoder_Rotation;
}
}
public override void TopChanging(float oldValue, float newValue)
@ -297,44 +220,11 @@ namespace CtrEditor.ObjetosSim
offsetX = newValue - oldValue;
}
public override void AnguloChanging(float oldValue, float newValue)
{
offsetAngulo = newValue - oldValue;
}
[ObservableProperty]
[property: Description("Show/Hide the plate background")]
[property: Category("Appearance:")]
private bool showPlate;
[ObservableProperty]
[property: Description("Use horizontal center as pivot point for rotation")]
[property: Category("Pivot:")]
private bool pivotCenterX;
[ObservableProperty]
[property: Description("Use vertical center as pivot point for rotation")]
[property: Category("Pivot:")]
private bool pivotCenterY;
partial void OnPivotCenterXChanged(bool value)
{
// Notificar cambio de pivot para actualizar objetos conectados
OnPropertyChanged(nameof(PivotCenterX));
// Notificar cambios en las propiedades de pivot para el RenderTransform
OnPropertyChanged(nameof(RenderTransformCenterX));
OnPropertyChanged(nameof(RenderTransformCenterY));
}
partial void OnPivotCenterYChanged(bool value)
{
// Notificar cambio de pivot para actualizar objetos conectados
OnPropertyChanged(nameof(PivotCenterY));
// Notificar cambios en las propiedades de pivot para el RenderTransform
OnPropertyChanged(nameof(RenderTransformCenterX));
OnPropertyChanged(nameof(RenderTransformCenterY));
}
public osFramePlate()
{
Ancho = 0.5f;
@ -346,30 +236,12 @@ namespace CtrEditor.ObjetosSim
ShowPlate = true; // Default value
}
public override void AnchoChanged(float value)
{
base.AnchoChanged(value);
OnPropertyChanged(nameof(RenderTransformCenterX));
}
public override void AltoChanged(float value)
{
base.AltoChanged(value);
OnPropertyChanged(nameof(RenderTransformCenterY));
}
partial void OnAlto_TituloChanged(float value)
{
OnPropertyChanged(nameof(RenderTransformCenterY));
}
public override void ucLoaded()
{
base.ucLoaded();
// El UserControl se ha cargado
OnEncoder_XChanged(Encoder_X);
OnEncoder_YChanged(Encoder_Y);
OnEncoder_RotationChanged(Encoder_Rotation);
UpdateZIndex(Zindex_FramePlate);
}
@ -380,63 +252,6 @@ namespace CtrEditor.ObjetosSim
// eliminar el objeto de simulacion
}
/// <summary>
/// Calcula el punto de pivot basado en los checkboxes PivotCenterX y PivotCenterY
/// </summary>
/// <returns>Punto de pivot en coordenadas absolutas</returns>
public (float X, float Y) GetPivotPoint()
{
float pivotX = Left + (PivotCenterX ? Ancho / 2 : 0);
float pivotY = Top + (PivotCenterY ? Alto / 2 : 0);
return (pivotX, pivotY);
}
/// <summary>
/// Centro X para el RenderTransform en píxeles del control
/// </summary>
[JsonIgnore]
public double RenderTransformCenterX
{
get
{
if (PivotCenterX)
{
// Centro horizontal del rectángulo principal
return PixelToMeter.Instance.calc.MetersToPixels(Ancho) / 2.0;
}
else
{
// Left del rectángulo principal (0)
return 0.0;
}
}
}
/// <summary>
/// Centro Y para el RenderTransform en píxeles del control
/// </summary>
[JsonIgnore]
public double RenderTransformCenterY
{
get
{
// Altura del título y del rectángulo principal
double titleHeight = PixelToMeter.Instance.calc.MetersToPixels(Alto_Titulo);
double frameHeight = PixelToMeter.Instance.calc.MetersToPixels(Alto);
if (PivotCenterY)
{
// Centro vertical de todo el control (título + rectángulo)
return (titleHeight + frameHeight) / 2.0;
}
else
{
// Top absoluto del control (0)
return 0.0;
}
}
}
}
public partial class ucFramePlate : UserControl, IDataContainer

View File

@ -51,12 +51,6 @@ namespace CtrEditor.ObjetosSim
partial void OnDiametroChanged(float value)
{
// Asegurar que el diámetro sea al menos 0.01m para evitar errores en la simulación
if (value < 0.01f)
{
diametro = 0.01f;
return;
}
SimGeometria?.SetDiameter(Diametro);
}

View File

@ -133,12 +133,6 @@ namespace CtrEditor.ObjetosSim
TiempoRestante -= elapsedMilliseconds / 1000.0f;
if (TiempoRestante <= 0)
{
// Validar que el diámetro sea al menos 0.01m para evitar errores en la simulación
if (Diametro_botella < 0.01f)
{
Diametro_botella = 0.01f;
}
var X = Left + OffsetLeftSalida;
var Y = Top + OffsetTopSalida;

View File

@ -106,11 +106,7 @@ namespace CtrEditor.ObjetosSim
LeftChanging(oldValue, newValue);
}
public virtual void LeftChanged(float value)
{
// Actualizar posición relativa si el movimiento no viene del FramePlate
UpdateFramePlateRelativePosition();
}
public virtual void LeftChanged(float value) { }
public virtual void LeftChanging(float oldValue, float newValue) { }
[ObservableProperty]
@ -128,11 +124,7 @@ namespace CtrEditor.ObjetosSim
TopChanging(oldValue, newValue);
}
public virtual void TopChanged(float value)
{
// Actualizar posición relativa si el movimiento no viene del FramePlate
UpdateFramePlateRelativePosition();
}
public virtual void TopChanged(float value) { }
public virtual void TopChanging(float oldValue, float newValue) { }
@ -168,13 +160,7 @@ namespace CtrEditor.ObjetosSim
{
AnguloChanged(value);
}
partial void OnAnguloChanging(float oldValue, float newValue)
{
AnguloChanging(oldValue, newValue);
}
public virtual void AnguloChanged(float value) { }
public virtual void AnguloChanging(float oldValue, float newValue) { }
public void Resize(double width, double height)
@ -297,32 +283,6 @@ namespace CtrEditor.ObjetosSim
[JsonIgnore]
private bool isUpdatingFromFramePlate = false;
// Variables para rotación orbital alrededor del FramePlate
[JsonIgnore]
[ObservableProperty]
[property: Hidden]
private float framePlate_RelativeX;
[JsonIgnore]
[ObservableProperty]
[property: Hidden]
private float framePlate_RelativeY;
[JsonIgnore]
[ObservableProperty]
[property: Hidden]
private float framePlate_InitialAngle;
[JsonIgnore]
[ObservableProperty]
[property: Hidden]
private float framePlate_PivotX;
[JsonIgnore]
[ObservableProperty]
[property: Hidden]
private float framePlate_PivotY;
partial void OnGroup_FramePanelChanged(string value)
{
if (FramePlate != null)
@ -334,25 +294,8 @@ namespace CtrEditor.ObjetosSim
{
FramePlate.PropertyChanged += OnFramePlatePropertyChanged;
UpdateZIndex(FramePlate.Zindex_FramePlate);
// Calcular punto de pivot y posición relativa inicial
var pivotPoint = FramePlate.GetPivotPoint();
FramePlate_PivotX = pivotPoint.X;
FramePlate_PivotY = pivotPoint.Y;
FramePlate_RelativeX = Left - FramePlate_PivotX;
FramePlate_RelativeY = Top - FramePlate_PivotY;
FramePlate_InitialAngle = FramePlate.Angulo;
}
}
else
{
// Reset relative position when disconnecting
FramePlate_RelativeX = 0;
FramePlate_RelativeY = 0;
FramePlate_InitialAngle = 0;
FramePlate_PivotX = 0;
FramePlate_PivotY = 0;
}
}
protected void UpdateZIndex(int zIndex)
@ -373,25 +316,16 @@ namespace CtrEditor.ObjetosSim
if (e.PropertyName == nameof(osFramePlate.Nombre))
Group_Panel = ((osFramePlate)sender).Nombre;
if (e.PropertyName == nameof(osFramePlate.Top) ||
e.PropertyName == nameof(osFramePlate.Left))
if (e.PropertyName == nameof(osFramePlate.Top))
{
UpdateOrbitalPosition();
Top += ((osFramePlate)sender).offsetY;
OnMoveResizeRotate();
}
if (e.PropertyName == nameof(osFramePlate.Angulo))
if (e.PropertyName == nameof(osFramePlate.Left))
{
// Primero actualizar el ángulo del objeto
Angulo += ((osFramePlate)sender).offsetAngulo;
// Luego actualizar la posición orbital con el ángulo ya actualizado
UpdateOrbitalPosition();
}
if (e.PropertyName == nameof(osFramePlate.PivotCenterX) ||
e.PropertyName == nameof(osFramePlate.PivotCenterY))
{
// Cuando cambia el pivot, recalcular posición relativa
RecalculateRelativePositionForNewPivot();
Left += ((osFramePlate)sender).offsetX;
OnMoveResizeRotate();
}
if (e.PropertyName == nameof(osFramePlate.Zindex_FramePlate))
@ -401,96 +335,6 @@ namespace CtrEditor.ObjetosSim
}
}
private void UpdateOrbitalPosition()
{
if (FramePlate == null) return;
// Actualizar punto de pivot actual (puede haber cambiado por los checkboxes)
var currentPivot = FramePlate.GetPivotPoint();
// Calcular el ángulo de rotación total desde la posición inicial
float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
// Convertir ángulo a radianes
float angleRad = deltaAngle * (float)Math.PI / 180.0f;
// Calcular la nueva posición orbital usando rotación de matriz
float cosAngle = (float)Math.Cos(angleRad);
float sinAngle = (float)Math.Sin(angleRad);
// Rotar la posición relativa
float rotatedX = cosAngle * FramePlate_RelativeX - sinAngle * FramePlate_RelativeY;
float rotatedY = sinAngle * FramePlate_RelativeX + cosAngle * FramePlate_RelativeY;
// Calcular nueva posición absoluta usando el punto de pivot actual
float newLeft = currentPivot.X + rotatedX;
float newTop = currentPivot.Y + rotatedY;
// Actualizar directamente los campos sin disparar eventos de cambio
SetProperty(ref left, newLeft);
SetProperty(ref top, newTop);
// Forzar actualización de la posición visual
ActualizarLeftTop();
// Llamar a OnMoveResizeRotate para actualizar la física
OnMoveResizeRotate();
}
private void UpdateFramePlateRelativePosition()
{
// Solo actualizar si está conectado a un FramePlate y el movimiento NO viene del FramePlate
if (FramePlate != null && !isUpdatingFromFramePlate)
{
// Obtener punto de pivot actual
var currentPivot = FramePlate.GetPivotPoint();
// Recalcular posición relativa considerando la rotación actual del FramePlate
float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
float angleRad = deltaAngle * (float)Math.PI / 180.0f;
// Calcular la posición relativa actual respecto al pivot del FramePlate
float currentRelativeX = Left - currentPivot.X;
float currentRelativeY = Top - currentPivot.Y;
// Si el FramePlate está rotado, necesitamos "desrotar" la posición para obtener
// la posición relativa en el sistema de coordenadas original
float cosAngle = (float)Math.Cos(-angleRad);
float sinAngle = (float)Math.Sin(-angleRad);
FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY;
FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY;
}
}
private void RecalculateRelativePositionForNewPivot()
{
if (FramePlate == null) return;
// Obtener posición absoluta actual del objeto (antes del cambio de pivot)
float currentLeft = Left;
float currentTop = Top;
// Obtener nuevo punto de pivot
var newPivot = FramePlate.GetPivotPoint();
// Calcular nueva posición relativa respecto al nuevo pivot
// Considerar la rotación actual para obtener posición relativa en sistema original
float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
float angleRad = deltaAngle * (float)Math.PI / 180.0f;
// Posición relativa actual respecto al nuevo pivot
float currentRelativeX = currentLeft - newPivot.X;
float currentRelativeY = currentTop - newPivot.Y;
// "Desrotar" para obtener coordenadas en el sistema original
float cosAngle = (float)Math.Cos(-angleRad);
float sinAngle = (float)Math.Sin(-angleRad);
FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY;
FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY;
}
private void ShowPreviewWindow(Stream imageStream)
{
@ -1148,8 +992,6 @@ namespace CtrEditor.ObjetosSim
CanvasSetTopinMeter(Top);
}
public bool LeerBitTag(string Tag)
{
if (this._plc == null) return false;

View File

@ -138,58 +138,23 @@ namespace CtrEditor.Simulacion
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 all angles to the same range [0, 2π]
float normalizedBottleAngle = bottleAngle < 0 ? bottleAngle + 2 * (float)Math.PI : bottleAngle;
float normalizedStartAngle = _startAngle < 0 ? _startAngle + 2 * (float)Math.PI : _startAngle;
float normalizedEndAngle = _endAngle < 0 ? _endAngle + 2 * (float)Math.PI : _endAngle;
// Handle case where the arc crosses the 0/2π boundary
if (normalizedStartAngle > normalizedEndAngle)
{
// Arc crosses 0/2π boundary - check if bottle is in either part of the arc
if (!(normalizedBottleAngle >= normalizedStartAngle || normalizedBottleAngle <= normalizedEndAngle))
return 0;
}
else
{
// Normal case - arc doesn't cross boundary
if (normalizedBottleAngle < normalizedStartAngle || normalizedBottleAngle > normalizedEndAngle)
return 0;
}
// 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 using normalized angles
float angleFromStart, angleToEnd, totalAngle;
if (normalizedStartAngle > normalizedEndAngle)
{
// Arc crosses 0/2π boundary
totalAngle = (2 * (float)Math.PI - normalizedStartAngle) + normalizedEndAngle;
if (normalizedBottleAngle >= normalizedStartAngle)
{
angleFromStart = normalizedBottleAngle - normalizedStartAngle;
angleToEnd = (2 * (float)Math.PI - normalizedBottleAngle) + normalizedEndAngle;
}
else
{
angleFromStart = (2 * (float)Math.PI - normalizedStartAngle) + normalizedBottleAngle;
angleToEnd = normalizedEndAngle - normalizedBottleAngle;
}
}
else
{
// Normal case
angleFromStart = normalizedBottleAngle - normalizedStartAngle;
angleToEnd = normalizedEndAngle - normalizedBottleAngle;
totalAngle = normalizedEndAngle - normalizedStartAngle;
}
// 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;
@ -520,8 +485,6 @@ namespace CtrEditor.Simulacion
_world = world;
ListOnTransports = new List<simBase>();
_deferredActions = deferredActions;
// Asegurar que el diámetro sea al menos 0.01m para evitar errores
diameter = Min(diameter, 0.01f);
Radius = diameter / 2;
_mass = mass;
_activeJoint = null;
@ -601,8 +564,6 @@ namespace CtrEditor.Simulacion
public void SetDiameter(float diameter)
{
// Asegurar que el diámetro sea al menos 0.01m para evitar errores
diameter = Min(diameter, 0.01f);
Radius = diameter / 2;
Create(Body.Position); // Recrear el círculo con el nuevo tamaño
}