Compare commits
9 Commits
380bc14b69
...
99248e9112
Author | SHA1 | Date |
---|---|---|
|
99248e9112 | |
|
f85c707cfc | |
|
88e6de77cb | |
|
94b11cf068 | |
|
20467c88ae | |
|
e935efb0cb | |
|
16f5131803 | |
|
883620b69d | |
|
9c4e87be9b |
|
@ -447,6 +447,9 @@ namespace CtrEditor
|
|||
{
|
||||
OnVisFilterChanged(MainWindow.VisFilter.FilterViewModel);
|
||||
}
|
||||
|
||||
// Limpiar historial de undo al cargar un estado desde archivo
|
||||
MainWindow?.ClearUndoHistory();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,6 +458,9 @@ 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
|
||||
|
@ -794,6 +800,9 @@ 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>
|
||||
|
@ -869,6 +878,8 @@ 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>();
|
||||
|
|
|
@ -11,6 +11,11 @@ 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
|
||||
{
|
||||
|
@ -407,6 +412,9 @@ 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)
|
||||
{
|
||||
|
@ -426,6 +434,23 @@ 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)
|
||||
|
@ -498,6 +523,11 @@ 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
|
||||
|
@ -628,8 +658,72 @@ 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;
|
||||
|
@ -672,6 +766,21 @@ 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;
|
||||
|
@ -699,6 +808,314 @@ 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
|
||||
|
|
|
@ -10,6 +10,58 @@ 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,
|
||||
|
@ -27,6 +79,19 @@ 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
|
||||
|
@ -76,6 +141,11 @@ 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;
|
||||
|
@ -314,19 +384,22 @@ 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.SizeNWSE, Brushes.Blue),
|
||||
(new Point(rectBox.Right + offset, rectBox.Bottom + offset), "BottomRight", cursors.ResizeBoth, 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.SizeNS, Brushes.Blue),
|
||||
(new Point(rectBox.Left + rectBox.Width / 2, rectBox.Bottom + offset), "BottomCenter", cursors.ResizeVertical, 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.SizeWE, Brushes.Blue)
|
||||
(new Point(rectBox.Right + offset, rectBox.Top + rectBox.Height / 2), "CenterRight", cursors.ResizeHorizontal, Brushes.Blue)
|
||||
};
|
||||
|
||||
foreach (var (Position, Tag, Cursor, Stroke) in positions)
|
||||
|
@ -338,6 +411,43 @@ 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
|
||||
|
@ -615,6 +725,9 @@ namespace CtrEditor
|
|||
return;
|
||||
}
|
||||
|
||||
// Capturar estado antes de iniciar redimensionamiento
|
||||
CaptureUndoState();
|
||||
|
||||
_currentDraggingRectangle = sender as Rectangle;
|
||||
_lastMousePosition = e.GetPosition(_canvas);
|
||||
_currentDraggingRectangle.CaptureMouse();
|
||||
|
@ -625,25 +738,49 @@ namespace CtrEditor
|
|||
}
|
||||
|
||||
var userControl = sender as UserControl;
|
||||
if (userControl != null)
|
||||
if (userControl != null && userControl.DataContext is osBase datos)
|
||||
{
|
||||
if (userControl.DataContext is osBase datos)
|
||||
{
|
||||
HandleObjectSelection(userControl, datos);
|
||||
bool isControlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
||||
|
||||
// Actualizar el estado de objetos bloqueados después de la selección
|
||||
hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
||||
// Determinar qué objetos serán afectados por el movimiento
|
||||
List<osBase> objectsToMove = new List<osBase>();
|
||||
|
||||
if (!isControlPressed)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Solo iniciar el arrastre si no se presionó Ctrl y no hay objetos bloqueados
|
||||
if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && !hasLockedObjects)
|
||||
// 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);
|
||||
}
|
||||
else if (hasLockedObjects)
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Si hay objetos bloqueados, no permitir el inicio del arrastre
|
||||
e.Handled = true;
|
||||
|
@ -837,6 +974,39 @@ 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
|
||||
{
|
||||
|
@ -850,6 +1020,21 @@ 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();
|
||||
|
@ -876,8 +1061,53 @@ namespace CtrEditor
|
|||
|
||||
private void HandleResizeForObject(osBase obj, Point currentPosition, bool activateSizeWidth, bool activateSizeHeight)
|
||||
{
|
||||
double widthChange = activateSizeWidth ? currentPosition.X - _startPointUserControl.X : 0;
|
||||
double heightChange = activateSizeHeight ? currentPosition.Y - _startPointUserControl.Y : 0;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
obj.Resize(widthChange, heightChange);
|
||||
}
|
||||
|
@ -929,6 +1159,9 @@ namespace CtrEditor
|
|||
return; // No alinear si hay objetos bloqueados
|
||||
}
|
||||
|
||||
// Capturar estado antes de alinear
|
||||
CaptureUndoState();
|
||||
|
||||
var alignment = new ObjectAlignment(_selectedObjects, _selectedObjects.FirstOrDefault());
|
||||
|
||||
switch (alignmentType)
|
||||
|
@ -1090,5 +1323,149 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,12 +5,15 @@
|
|||
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" />
|
||||
|
@ -24,6 +27,5 @@
|
|||
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" Stroke="Blue"
|
||||
StrokeThickness="0.2"
|
||||
Visibility="{Binding ShowPlate, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -24,6 +24,8 @@ namespace CtrEditor.ObjetosSim
|
|||
public float offsetY;
|
||||
[JsonIgnore]
|
||||
public float offsetX;
|
||||
[JsonIgnore]
|
||||
public float offsetAngulo;
|
||||
|
||||
public static string NombreClase()
|
||||
{
|
||||
|
@ -103,6 +105,29 @@ 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();
|
||||
|
@ -123,18 +148,34 @@ 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)
|
||||
|
@ -159,6 +200,18 @@ 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)
|
||||
|
@ -195,12 +248,30 @@ 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_Y_Position = EncoderY.Valor_Actual;
|
||||
Encoder_X_Position = EncoderX.Valor_Actual;
|
||||
Left = (Encoder_X_Position / k_encoder_X) + offset_encoder_X;
|
||||
}
|
||||
// Update Y position if encoder is available
|
||||
|
@ -209,6 +280,12 @@ 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)
|
||||
|
@ -220,11 +297,44 @@ 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;
|
||||
|
@ -236,12 +346,30 @@ 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);
|
||||
}
|
||||
|
||||
|
@ -252,6 +380,63 @@ 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
|
||||
|
|
|
@ -51,6 +51,12 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -133,6 +133,12 @@ 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;
|
||||
|
||||
|
|
|
@ -106,7 +106,11 @@ namespace CtrEditor.ObjetosSim
|
|||
LeftChanging(oldValue, newValue);
|
||||
}
|
||||
|
||||
public virtual void LeftChanged(float value) { }
|
||||
public virtual void LeftChanged(float value)
|
||||
{
|
||||
// Actualizar posición relativa si el movimiento no viene del FramePlate
|
||||
UpdateFramePlateRelativePosition();
|
||||
}
|
||||
public virtual void LeftChanging(float oldValue, float newValue) { }
|
||||
|
||||
[ObservableProperty]
|
||||
|
@ -124,7 +128,11 @@ namespace CtrEditor.ObjetosSim
|
|||
TopChanging(oldValue, newValue);
|
||||
}
|
||||
|
||||
public virtual void TopChanged(float value) { }
|
||||
public virtual void TopChanged(float value)
|
||||
{
|
||||
// Actualizar posición relativa si el movimiento no viene del FramePlate
|
||||
UpdateFramePlateRelativePosition();
|
||||
}
|
||||
public virtual void TopChanging(float oldValue, float newValue) { }
|
||||
|
||||
|
||||
|
@ -160,7 +168,13 @@ 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)
|
||||
|
@ -283,6 +297,32 @@ 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)
|
||||
|
@ -294,8 +334,25 @@ 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)
|
||||
|
@ -316,16 +373,25 @@ namespace CtrEditor.ObjetosSim
|
|||
if (e.PropertyName == nameof(osFramePlate.Nombre))
|
||||
Group_Panel = ((osFramePlate)sender).Nombre;
|
||||
|
||||
if (e.PropertyName == nameof(osFramePlate.Top))
|
||||
if (e.PropertyName == nameof(osFramePlate.Top) ||
|
||||
e.PropertyName == nameof(osFramePlate.Left))
|
||||
{
|
||||
Top += ((osFramePlate)sender).offsetY;
|
||||
OnMoveResizeRotate();
|
||||
UpdateOrbitalPosition();
|
||||
}
|
||||
|
||||
if (e.PropertyName == nameof(osFramePlate.Left))
|
||||
if (e.PropertyName == nameof(osFramePlate.Angulo))
|
||||
{
|
||||
Left += ((osFramePlate)sender).offsetX;
|
||||
OnMoveResizeRotate();
|
||||
// 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();
|
||||
}
|
||||
|
||||
if (e.PropertyName == nameof(osFramePlate.Zindex_FramePlate))
|
||||
|
@ -335,6 +401,96 @@ 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)
|
||||
{
|
||||
|
@ -992,6 +1148,8 @@ namespace CtrEditor.ObjetosSim
|
|||
CanvasSetTopinMeter(Top);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public bool LeerBitTag(string Tag)
|
||||
{
|
||||
if (this._plc == null) return false;
|
||||
|
|
|
@ -138,23 +138,58 @@ 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 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)
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
@ -485,6 +520,8 @@ 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;
|
||||
|
@ -564,6 +601,8 @@ 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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue