Compare commits
No commits in common. "99248e9112c79f5ab566a38e77489c6ed73bb894" and "380bc14b6911d03a974d6441b952694b28a28556" have entirely different histories.
99248e9112
...
380bc14b69
|
@ -447,9 +447,6 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
OnVisFilterChanged(MainWindow.VisFilter.FilterViewModel);
|
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"
|
// Suponiendo que "SelectedImage" es una propiedad que al establecerse dispara "ImageSelected"
|
||||||
directorioTrabajo = EstadoPersistente.Instance.directorio;
|
directorioTrabajo = EstadoPersistente.Instance.directorio;
|
||||||
|
|
||||||
// Limpiar historial de undo al cargar datos iniciales
|
|
||||||
MainWindow?.ClearUndoHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear un nuevo Objeto
|
// Crear un nuevo Objeto
|
||||||
|
@ -800,9 +794,6 @@ namespace CtrEditor
|
||||||
|
|
||||||
// Restaurar los rectángulos de selección si hay objetos seleccionados
|
// Restaurar los rectángulos de selección si hay objetos seleccionados
|
||||||
_objectManager.UpdateSelectionVisuals();
|
_objectManager.UpdateSelectionVisuals();
|
||||||
|
|
||||||
// Limpiar historial de undo al detener la simulación
|
|
||||||
MainWindow?.ClearUndoHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -878,8 +869,6 @@ namespace CtrEditor
|
||||||
foreach (var objetoSimulable in ObjetosSimulables)
|
foreach (var objetoSimulable in ObjetosSimulables)
|
||||||
objetoSimulable.SetPLC(null);
|
objetoSimulable.SetPLC(null);
|
||||||
|
|
||||||
// Limpiar historial de undo al desconectar del PLC
|
|
||||||
MainWindow?.ClearUndoHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<osBase> objetosSimulablesLlamados = new List<osBase>();
|
private List<osBase> objetosSimulablesLlamados = new List<osBase>();
|
||||||
|
|
|
@ -11,11 +11,6 @@ using System.Windows.Threading;
|
||||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||||
using UserControl = System.Windows.Controls.UserControl;
|
using UserControl = System.Windows.Controls.UserControl;
|
||||||
using CtrEditor.Controls; // Add this using statement
|
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
|
namespace CtrEditor
|
||||||
{
|
{
|
||||||
|
@ -412,9 +407,6 @@ namespace CtrEditor
|
||||||
|
|
||||||
private void MoveSelectedObjects(float deltaX, float deltaY)
|
private void MoveSelectedObjects(float deltaX, float deltaY)
|
||||||
{
|
{
|
||||||
// Capturar estado antes de mover
|
|
||||||
_objectManager.CaptureUndoState();
|
|
||||||
|
|
||||||
// Mover todos los objetos primero
|
// Mover todos los objetos primero
|
||||||
foreach (var obj in _objectManager.SelectedObjects)
|
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)
|
private void Canvas_MouseEnter(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.OriginalSource == ImagenEnTrabajoCanvas)
|
if (e.OriginalSource == ImagenEnTrabajoCanvas)
|
||||||
|
@ -523,11 +498,6 @@ namespace CtrEditor
|
||||||
debugWindow.Show();
|
debugWindow.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearUndoHistory()
|
|
||||||
{
|
|
||||||
_objectManager.ClearUndoHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
// Aceptar el evento si viene del canvas o de la imagen de fondo
|
// 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(editPropertiesItem);
|
||||||
|
|
||||||
contextMenu.Items.Add(new Separator());
|
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.IsOpen = true;
|
||||||
contextMenu.PlacementTarget = ImagenEnTrabajoCanvas;
|
contextMenu.PlacementTarget = ImagenEnTrabajoCanvas;
|
||||||
contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint;
|
contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint;
|
||||||
|
@ -766,21 +672,6 @@ namespace CtrEditor
|
||||||
_objectManager.RemoveResizeRectangles();
|
_objectManager.RemoveResizeRectangles();
|
||||||
e.Handled = true;
|
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())
|
else if (_objectManager.SelectedObjects.Any())
|
||||||
{
|
{
|
||||||
const float moveDistance = 0.01f;
|
const float moveDistance = 0.01f;
|
||||||
|
@ -808,314 +699,6 @@ namespace CtrEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
public Image ImagenDeFondo => imagenDeFondo;
|
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
|
public class FloatValidationRule : ValidationRule
|
||||||
|
|
|
@ -10,58 +10,6 @@ using Color = System.Windows.Media.Color;
|
||||||
|
|
||||||
namespace CtrEditor
|
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
|
public enum AlignmentType
|
||||||
{
|
{
|
||||||
Left,
|
Left,
|
||||||
|
@ -79,19 +27,6 @@ namespace CtrEditor
|
||||||
JoinVertically
|
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
|
public class ObjectManipulationManager
|
||||||
{
|
{
|
||||||
private enum ResizeMode
|
private enum ResizeMode
|
||||||
|
@ -141,11 +76,6 @@ namespace CtrEditor
|
||||||
private bool _isRectangleSelectionActive;
|
private bool _isRectangleSelectionActive;
|
||||||
private bool _selectedObjectsAreVisible;
|
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
|
public bool IsRectangleSelectionActive
|
||||||
{
|
{
|
||||||
get => _isRectangleSelectionActive;
|
get => _isRectangleSelectionActive;
|
||||||
|
@ -384,22 +314,19 @@ namespace CtrEditor
|
||||||
// Calcular el offset para posicionar los manejadores fuera del rectángulo
|
// Calcular el offset para posicionar los manejadores fuera del rectángulo
|
||||||
double offset = rectSize / 2;
|
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)>
|
var positions = new List<(Point Position, string Tag, Cursor Cursor, Brush Stroke)>
|
||||||
{
|
{
|
||||||
// Esquinas - mover hacia afuera del rectángulo
|
// Esquinas - mover hacia afuera del rectángulo
|
||||||
(new Point(rectBox.Left - offset, rectBox.Top - offset), "TopLeft", Cursors.Arrow, Brushes.Gray),
|
(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.Right + offset, rectBox.Top - offset), "TopRight", rotationCursorRx, Brushes.Red),
|
||||||
(new Point(rectBox.Left - offset, rectBox.Bottom + offset), "BottomLeft", rotationCursorSx, Brushes.DarkRed),
|
(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
|
// 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.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.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)
|
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)
|
private Rectangle CreateResizeHandle(double rectSize, string tag, Cursor cursor, Brush stroke)
|
||||||
{
|
{
|
||||||
var handle = new Rectangle
|
var handle = new Rectangle
|
||||||
|
@ -725,9 +615,6 @@ namespace CtrEditor
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capturar estado antes de iniciar redimensionamiento
|
|
||||||
CaptureUndoState();
|
|
||||||
|
|
||||||
_currentDraggingRectangle = sender as Rectangle;
|
_currentDraggingRectangle = sender as Rectangle;
|
||||||
_lastMousePosition = e.GetPosition(_canvas);
|
_lastMousePosition = e.GetPosition(_canvas);
|
||||||
_currentDraggingRectangle.CaptureMouse();
|
_currentDraggingRectangle.CaptureMouse();
|
||||||
|
@ -738,49 +625,25 @@ namespace CtrEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
var userControl = sender as UserControl;
|
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);
|
if (userControl.DataContext is osBase datos)
|
||||||
|
{
|
||||||
|
HandleObjectSelection(userControl, datos);
|
||||||
|
|
||||||
// Determinar qué objetos serán afectados por el movimiento
|
// Actualizar el estado de objetos bloqueados después de la selección
|
||||||
List<osBase> objectsToMove = new List<osBase>();
|
hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar si alguno de los objetos a mover está bloqueado
|
// Solo iniciar el arrastre si no se presionó Ctrl y no hay objetos bloqueados
|
||||||
bool willMoveLockedObjects = objectsToMove.Any(obj => obj.Lock_movement);
|
if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && !hasLockedObjects)
|
||||||
|
|
||||||
if (!willMoveLockedObjects)
|
|
||||||
{
|
{
|
||||||
// Capturar estado antes de iniciar movimiento
|
|
||||||
CaptureUndoStateForObjects(objectsToMove);
|
|
||||||
|
|
||||||
userControl.CaptureMouse();
|
userControl.CaptureMouse();
|
||||||
_currentDraggingControl = userControl;
|
_currentDraggingControl = userControl;
|
||||||
_isMovingUserControl = true;
|
_isMovingUserControl = true;
|
||||||
InitializeDrag(e);
|
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
|
// Si hay objetos bloqueados, no permitir el inicio del arrastre
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
@ -974,39 +837,6 @@ namespace CtrEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
private HandleMode DetermineHandleMode(string resizeDirection)
|
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
|
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)
|
private void HandleResize(Point currentPosition, HandleMode mode)
|
||||||
{
|
{
|
||||||
PurgeDeletedObjects();
|
PurgeDeletedObjects();
|
||||||
|
@ -1061,53 +876,8 @@ namespace CtrEditor
|
||||||
|
|
||||||
private void HandleResizeForObject(osBase obj, Point currentPosition, bool activateSizeWidth, bool activateSizeHeight)
|
private void HandleResizeForObject(osBase obj, Point currentPosition, bool activateSizeWidth, bool activateSizeHeight)
|
||||||
{
|
{
|
||||||
// Calcular el cambio del mouse en coordenadas del canvas
|
double widthChange = activateSizeWidth ? currentPosition.X - _startPointUserControl.X : 0;
|
||||||
double deltaX = currentPosition.X - _startPointUserControl.X;
|
double heightChange = activateSizeHeight ? currentPosition.Y - _startPointUserControl.Y : 0;
|
||||||
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);
|
obj.Resize(widthChange, heightChange);
|
||||||
}
|
}
|
||||||
|
@ -1159,9 +929,6 @@ namespace CtrEditor
|
||||||
return; // No alinear si hay objetos bloqueados
|
return; // No alinear si hay objetos bloqueados
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capturar estado antes de alinear
|
|
||||||
CaptureUndoState();
|
|
||||||
|
|
||||||
var alignment = new ObjectAlignment(_selectedObjects, _selectedObjects.FirstOrDefault());
|
var alignment = new ObjectAlignment(_selectedObjects, _selectedObjects.FirstOrDefault());
|
||||||
|
|
||||||
switch (alignmentType)
|
switch (alignmentType)
|
||||||
|
@ -1323,149 +1090,5 @@ namespace CtrEditor
|
||||||
UpdateSelectionRectangle(currentPoint);
|
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,15 +5,12 @@
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<vm:osFramePlate Color_Titulo="Black" Alto_Titulo="0.1" Color="WhiteSmoke" />
|
<vm:osFramePlate Color_Titulo="Black" Alto_Titulo="0.1" Color="WhiteSmoke" />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RenderTransform>
|
|
||||||
<RotateTransform Angle="{Binding Angulo}"
|
|
||||||
CenterX="{Binding RenderTransformCenterX}"
|
|
||||||
CenterY="{Binding RenderTransformCenterY}" />
|
|
||||||
</Grid.RenderTransform>
|
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
@ -27,5 +24,6 @@
|
||||||
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" Stroke="Blue"
|
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" Stroke="Blue"
|
||||||
StrokeThickness="0.2"
|
StrokeThickness="0.2"
|
||||||
Visibility="{Binding ShowPlate, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
Visibility="{Binding ShowPlate, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
|
@ -24,8 +24,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
public float offsetY;
|
public float offsetY;
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public float offsetX;
|
public float offsetX;
|
||||||
[JsonIgnore]
|
|
||||||
public float offsetAngulo;
|
|
||||||
|
|
||||||
public static string NombreClase()
|
public static string NombreClase()
|
||||||
{
|
{
|
||||||
|
@ -105,29 +103,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Category("Encoders:")]
|
[property: Category("Encoders:")]
|
||||||
public float offset_encoder_Y;
|
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)
|
partial void OnK_encoder_YChanged(float value)
|
||||||
{
|
{
|
||||||
UpdatePosition();
|
UpdatePosition();
|
||||||
|
@ -148,34 +123,18 @@ namespace CtrEditor.ObjetosSim
|
||||||
UpdatePosition();
|
UpdatePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnK_encoder_RotationChanged(float value)
|
|
||||||
{
|
|
||||||
UpdatePosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnOffset_encoder_RotationChanged(float value)
|
|
||||||
{
|
|
||||||
UpdatePosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private osEncoderMotorLineal EncoderX;
|
private osEncoderMotorLineal EncoderX;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private osEncoderMotorLineal EncoderY;
|
private osEncoderMotorLineal EncoderY;
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
private osEncoderMotorLineal EncoderRotation;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private bool isUpdatingFromEncoderX = false;
|
private bool isUpdatingFromEncoderX = false;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private bool isUpdatingFromEncoderY = false;
|
private bool isUpdatingFromEncoderY = false;
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
private bool isUpdatingFromEncoderRotation = false;
|
|
||||||
|
|
||||||
partial void OnEncoder_XChanged(string value)
|
partial void OnEncoder_XChanged(string value)
|
||||||
{
|
{
|
||||||
if (EncoderX != null)
|
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)
|
private void OnEncoderXPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isUpdatingFromEncoderX)
|
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()
|
public void UpdatePosition()
|
||||||
{
|
{
|
||||||
// Update X position if encoder is available
|
// Update X position if encoder is available
|
||||||
if (EncoderX != null && K_encoder_X != 0)
|
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;
|
Left = (Encoder_X_Position / k_encoder_X) + offset_encoder_X;
|
||||||
}
|
}
|
||||||
// Update Y position if encoder is available
|
// Update Y position if encoder is available
|
||||||
|
@ -280,12 +209,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
Encoder_Y_Position = EncoderY.Valor_Actual;
|
Encoder_Y_Position = EncoderY.Valor_Actual;
|
||||||
Top = (Encoder_Y_Position / k_encoder_Y) + offset_encoder_Y;
|
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)
|
public override void TopChanging(float oldValue, float newValue)
|
||||||
|
@ -297,44 +220,11 @@ namespace CtrEditor.ObjetosSim
|
||||||
offsetX = newValue - oldValue;
|
offsetX = newValue - oldValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AnguloChanging(float oldValue, float newValue)
|
|
||||||
{
|
|
||||||
offsetAngulo = newValue - oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Description("Show/Hide the plate background")]
|
[property: Description("Show/Hide the plate background")]
|
||||||
[property: Category("Appearance:")]
|
[property: Category("Appearance:")]
|
||||||
private bool showPlate;
|
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()
|
public osFramePlate()
|
||||||
{
|
{
|
||||||
Ancho = 0.5f;
|
Ancho = 0.5f;
|
||||||
|
@ -346,30 +236,12 @@ namespace CtrEditor.ObjetosSim
|
||||||
ShowPlate = true; // Default value
|
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()
|
public override void ucLoaded()
|
||||||
{
|
{
|
||||||
base.ucLoaded();
|
base.ucLoaded();
|
||||||
// El UserControl se ha cargado
|
// El UserControl se ha cargado
|
||||||
OnEncoder_XChanged(Encoder_X);
|
OnEncoder_XChanged(Encoder_X);
|
||||||
OnEncoder_YChanged(Encoder_Y);
|
OnEncoder_YChanged(Encoder_Y);
|
||||||
OnEncoder_RotationChanged(Encoder_Rotation);
|
|
||||||
UpdateZIndex(Zindex_FramePlate);
|
UpdateZIndex(Zindex_FramePlate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,63 +252,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
// eliminar el objeto de simulacion
|
// 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
|
public partial class ucFramePlate : UserControl, IDataContainer
|
||||||
|
|
|
@ -51,12 +51,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
partial void OnDiametroChanged(float value)
|
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);
|
SimGeometria?.SetDiameter(Diametro);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,12 +133,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
TiempoRestante -= elapsedMilliseconds / 1000.0f;
|
TiempoRestante -= elapsedMilliseconds / 1000.0f;
|
||||||
if (TiempoRestante <= 0)
|
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 X = Left + OffsetLeftSalida;
|
||||||
var Y = Top + OffsetTopSalida;
|
var Y = Top + OffsetTopSalida;
|
||||||
|
|
||||||
|
|
|
@ -106,11 +106,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
LeftChanging(oldValue, newValue);
|
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) { }
|
public virtual void LeftChanging(float oldValue, float newValue) { }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
@ -128,11 +124,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
TopChanging(oldValue, newValue);
|
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) { }
|
public virtual void TopChanging(float oldValue, float newValue) { }
|
||||||
|
|
||||||
|
|
||||||
|
@ -168,13 +160,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
AnguloChanged(value);
|
AnguloChanged(value);
|
||||||
}
|
}
|
||||||
partial void OnAnguloChanging(float oldValue, float newValue)
|
|
||||||
{
|
|
||||||
AnguloChanging(oldValue, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void AnguloChanged(float value) { }
|
public virtual void AnguloChanged(float value) { }
|
||||||
public virtual void AnguloChanging(float oldValue, float newValue) { }
|
|
||||||
|
|
||||||
|
|
||||||
public void Resize(double width, double height)
|
public void Resize(double width, double height)
|
||||||
|
@ -297,32 +283,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private bool isUpdatingFromFramePlate = false;
|
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)
|
partial void OnGroup_FramePanelChanged(string value)
|
||||||
{
|
{
|
||||||
if (FramePlate != null)
|
if (FramePlate != null)
|
||||||
|
@ -334,25 +294,8 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
FramePlate.PropertyChanged += OnFramePlatePropertyChanged;
|
FramePlate.PropertyChanged += OnFramePlatePropertyChanged;
|
||||||
UpdateZIndex(FramePlate.Zindex_FramePlate);
|
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)
|
protected void UpdateZIndex(int zIndex)
|
||||||
|
@ -373,25 +316,16 @@ namespace CtrEditor.ObjetosSim
|
||||||
if (e.PropertyName == nameof(osFramePlate.Nombre))
|
if (e.PropertyName == nameof(osFramePlate.Nombre))
|
||||||
Group_Panel = ((osFramePlate)sender).Nombre;
|
Group_Panel = ((osFramePlate)sender).Nombre;
|
||||||
|
|
||||||
if (e.PropertyName == nameof(osFramePlate.Top) ||
|
if (e.PropertyName == nameof(osFramePlate.Top))
|
||||||
e.PropertyName == nameof(osFramePlate.Left))
|
|
||||||
{
|
{
|
||||||
UpdateOrbitalPosition();
|
Top += ((osFramePlate)sender).offsetY;
|
||||||
|
OnMoveResizeRotate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.PropertyName == nameof(osFramePlate.Angulo))
|
if (e.PropertyName == nameof(osFramePlate.Left))
|
||||||
{
|
{
|
||||||
// Primero actualizar el ángulo del objeto
|
Left += ((osFramePlate)sender).offsetX;
|
||||||
Angulo += ((osFramePlate)sender).offsetAngulo;
|
OnMoveResizeRotate();
|
||||||
// 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))
|
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)
|
private void ShowPreviewWindow(Stream imageStream)
|
||||||
{
|
{
|
||||||
|
@ -1148,8 +992,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
CanvasSetTopinMeter(Top);
|
CanvasSetTopinMeter(Top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public bool LeerBitTag(string Tag)
|
public bool LeerBitTag(string Tag)
|
||||||
{
|
{
|
||||||
if (this._plc == null) return false;
|
if (this._plc == null) return false;
|
||||||
|
|
|
@ -138,58 +138,23 @@ namespace CtrEditor.Simulacion
|
||||||
Vector2 centerToBottle = bottle.Body.Position - Body.Position;
|
Vector2 centerToBottle = bottle.Body.Position - Body.Position;
|
||||||
// Calculate angle of bottle relative to curve center (in radians)
|
// Calculate angle of bottle relative to curve center (in radians)
|
||||||
float bottleAngle = (float)Math.Atan2(centerToBottle.Y, centerToBottle.X);
|
float bottleAngle = (float)Math.Atan2(centerToBottle.Y, centerToBottle.X);
|
||||||
|
// Normalize angle to be positive
|
||||||
// Normalize all angles to the same range [0, 2π]
|
if (bottleAngle < 0)
|
||||||
float normalizedBottleAngle = bottleAngle < 0 ? bottleAngle + 2 * (float)Math.PI : bottleAngle;
|
bottleAngle += 2 * (float)Math.PI;
|
||||||
float normalizedStartAngle = _startAngle < 0 ? _startAngle + 2 * (float)Math.PI : _startAngle;
|
// If bottle is outside the angle range, return 0
|
||||||
float normalizedEndAngle = _endAngle < 0 ? _endAngle + 2 * (float)Math.PI : _endAngle;
|
if (bottleAngle < _startAngle || bottleAngle > _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;
|
return 0;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Normal case - arc doesn't cross boundary
|
|
||||||
if (normalizedBottleAngle < normalizedStartAngle || normalizedBottleAngle > normalizedEndAngle)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// Calculate distance from center
|
// Calculate distance from center
|
||||||
float distanceToCenter = centerToBottle.Length();
|
float distanceToCenter = centerToBottle.Length();
|
||||||
// If bottle is outside radius range, return 0
|
// If bottle is outside radius range, return 0
|
||||||
if (distanceToCenter < _innerRadius || distanceToCenter > _outerRadius)
|
if (distanceToCenter < _innerRadius || distanceToCenter > _outerRadius)
|
||||||
return 0;
|
return 0;
|
||||||
// Calculate how far the bottle is from the edges using normalized angles
|
// Calculate how far the bottle is from the edges
|
||||||
float angleFromStart, angleToEnd, totalAngle;
|
float angleFromStart = bottleAngle - _startAngle;
|
||||||
|
float angleToEnd = _endAngle - bottleAngle;
|
||||||
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
|
// Use the minimum distance to either edge to calculate overlap
|
||||||
float minAngleDistance = Math.Min(angleFromStart, angleToEnd);
|
float minAngleDistance = Math.Min(angleFromStart, angleToEnd);
|
||||||
|
float totalAngle = _endAngle - _startAngle;
|
||||||
// Calculate overlap percentage based on angle proximity to edges
|
// Calculate overlap percentage based on angle proximity to edges
|
||||||
float overlapPercentage = Math.Min(minAngleDistance / (totalAngle * 0.1f), 1.0f);
|
float overlapPercentage = Math.Min(minAngleDistance / (totalAngle * 0.1f), 1.0f);
|
||||||
return overlapPercentage;
|
return overlapPercentage;
|
||||||
|
@ -520,8 +485,6 @@ namespace CtrEditor.Simulacion
|
||||||
_world = world;
|
_world = world;
|
||||||
ListOnTransports = new List<simBase>();
|
ListOnTransports = new List<simBase>();
|
||||||
_deferredActions = deferredActions;
|
_deferredActions = deferredActions;
|
||||||
// Asegurar que el diámetro sea al menos 0.01m para evitar errores
|
|
||||||
diameter = Min(diameter, 0.01f);
|
|
||||||
Radius = diameter / 2;
|
Radius = diameter / 2;
|
||||||
_mass = mass;
|
_mass = mass;
|
||||||
_activeJoint = null;
|
_activeJoint = null;
|
||||||
|
@ -601,8 +564,6 @@ namespace CtrEditor.Simulacion
|
||||||
|
|
||||||
public void SetDiameter(float diameter)
|
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;
|
Radius = diameter / 2;
|
||||||
Create(Body.Position); // Recrear el círculo con el nuevo tamaño
|
Create(Body.Position); // Recrear el círculo con el nuevo tamaño
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue