From 99248e9112c79f5ab566a38e77489c6ed73bb894 Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 17 Jun 2025 17:35:35 +0200 Subject: [PATCH] =?UTF-8?q?Se=20implementaron=20mejoras=20en=20la=20gesti?= =?UTF-8?q?=C3=B3n=20de=20copias=20de=20objetos=20seleccionados,=20reutili?= =?UTF-8?q?zando=20la=20l=C3=B3gica=20de=20duplicaci=C3=B3n=20para=20crear?= =?UTF-8?q?=20copias=20serializables.=20Se=20agreg=C3=B3=20manejo=20de=20e?= =?UTF-8?q?rrores=20al=20intentar=20copiar=20objetos=20y=20se=20implementa?= =?UTF-8?q?ron=20nuevas=20propiedades=20en=20la=20clase=20osBase=20para=20?= =?UTF-8?q?gestionar=20el=20punto=20de=20pivote=20en=20la=20rotaci=C3=B3n.?= =?UTF-8?q?=20Adem=C3=A1s,=20se=20aseguraron=20validaciones=20para=20evita?= =?UTF-8?q?r=20di=C3=A1metros=20inv=C3=A1lidos=20en=20la=20simulaci=C3=B3n?= =?UTF-8?q?,=20garantizando=20un=20comportamiento=20m=C3=A1s=20robusto=20e?= =?UTF-8?q?n=20la=20manipulaci=C3=B3n=20de=20objetos.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MainWindow.xaml.cs | 210 +++++++++--------- ObjetosSim/Decorativos/ucFramePlate.xaml | 4 +- ObjetosSim/Decorativos/ucFramePlate.xaml.cs | 102 +++++++++ ObjetosSim/Dinamicos/ucBotella.xaml.cs | 6 + ObjetosSim/Emuladores/ucBottGenerator.xaml.cs | 6 + ObjetosSim/osBase.cs | 88 +++++++- Simulacion/Aether.cs | 4 + 7 files changed, 304 insertions(+), 116 deletions(-) diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 94110f9..9e190c6 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -16,7 +16,6 @@ using System.Text.RegularExpressions; using System.Collections.Generic; using System; using System.Linq; -using System.Threading.Tasks; namespace CtrEditor { @@ -817,38 +816,46 @@ namespace CtrEditor try { - var objectsToCopy = new List(); - - // Preparar objetos para serialización - foreach (var obj in _objectManager.SelectedObjects) + if (DataContext is MainViewModel viewModel) { - obj.SalvarDatosNoSerializables(); - objectsToCopy.Add(obj); - } + // Usar la misma lógica que DuplicarObjeto pero solo para crear copias para serialización + var objectsCopy = new List(); + + 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}"); + } + } - // Crear configuración de serialización igual a la usada en el guardado - var settings = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Ignore, - TypeNameHandling = TypeNameHandling.Auto, - ObjectCreationHandling = ObjectCreationHandling.Replace, - ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor - }; + if (objectsCopy.Count == 0) + { + MessageBox.Show("No se pudieron copiar los objetos seleccionados.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } - // Serializar objetos - string jsonString = JsonConvert.SerializeObject(objectsToCopy, settings); + // Serializar usando las mismas configuraciones que DuplicarObjeto + var settings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + TypeNameHandling = TypeNameHandling.All // Igual que en DuplicarObjeto + }; - // Copiar al portapapeles - Clipboard.SetText(jsonString); - - // Mostrar mensaje de confirmación - Console.WriteLine($"Copiados {objectsToCopy.Count} objeto(s) al portapapeles"); - - // Restaurar datos no serializables - foreach (var obj in _objectManager.SelectedObjects) - { - obj.RestaurarDatosNoSerializables(); + string jsonString = JsonConvert.SerializeObject(objectsCopy, settings); + Clipboard.SetText(jsonString); + + Console.WriteLine($"Copiados {objectsCopy.Count} objeto(s) al portapapeles"); } } catch (Exception ex) @@ -857,6 +864,33 @@ namespace CtrEditor } } + 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(serializedData, settings); + } + finally + { + original.RestaurarDatosNoSerializables(); + } + + return copy; + } + private void PasteObjectsFromJson(bool replaceExistingIds = false) { if (!Clipboard.ContainsText()) @@ -945,27 +979,21 @@ namespace CtrEditor obj.Left += offsetX; obj.Top += offsetY; - // Manejar IDs + // Manejar IDs usando la misma lógica que DuplicarObjeto if (!replaceExistingIds) { - // Generar nuevo ID (comportamiento por defecto) + // Generar nuevo ID y actualizar nombre (igual que en DuplicarObjeto) obj.Id.ObtenerNuevaID(); - // Actualizar nombre si contiene ID - if (obj.Nombre.Contains("_")) - { - var parts = obj.Nombre.Split('_'); - if (parts.Length > 1 && int.TryParse(parts.Last(), out _)) - { - obj.Nombre = string.Join("_", parts.Take(parts.Length - 1)) + "_" + obj.Id.Value; - } - } + string nombre = Regex.IsMatch(obj.Nombre, @"_\d+$") + ? Regex.Replace(obj.Nombre, @"_\d+$", $"_{obj.Id.Value}") + : obj.Nombre + "_" + obj.Id.Value; + + obj.Nombre = nombre; } - // Si replaceExistingIds es true, mantener los IDs originales - - // Verificar que el objeto no existe ya si estamos reemplazando IDs - if (replaceExistingIds) + else { + // Reemplazar objeto existente con mismo ID var existingObj = viewModel.ObjetosSimulables.FirstOrDefault(o => o.Id.Value == obj.Id.Value); if (existingObj != null) { @@ -976,6 +1004,7 @@ namespace CtrEditor obj.CheckData(); viewModel.ObjetosSimulables.Add(obj); viewModel.CrearUserControlDesdeObjetoSimulable(obj); + viewModel.HasUnsavedChanges = true; newlyPastedObjects.Add(obj); } @@ -985,78 +1014,49 @@ namespace CtrEditor } } - // Forzar actualización del layout para asegurar que los objetos estén renderizados + // 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 Task.Delay para dar más tiempo al renderizado completo - Task.Delay(100).ContinueWith(_ => + // Usar dispatcher con la misma prioridad que DuplicarUserControl + Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => { - Application.Current.Dispatcher.Invoke(() => + // Seleccionar todos los objetos pegados + foreach (var newObj in newlyPastedObjects) { - try + if (newObj.VisualRepresentation != null) { - // Forzar una segunda actualización del layout - ImagenEnTrabajoCanvas.UpdateLayout(); + double left = Canvas.GetLeft(newObj.VisualRepresentation); + double top = Canvas.GetTop(newObj.VisualRepresentation); - // Validar que los objetos tengan representación visual antes de seleccionar - var validObjects = newlyPastedObjects.Where(obj => - obj.VisualRepresentation != null && - ImagenEnTrabajoCanvas.Children.Contains(obj.VisualRepresentation)).ToList(); - - Console.WriteLine($"Intentando seleccionar {validObjects.Count} de {newlyPastedObjects.Count} objetos pegados"); - - if (validObjects.Count > 0) + // 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)) { - // Limpiar selección actual primero - _objectManager.ClearSelection(); - - // Seleccionar los objetos válidos uno por uno con verificación - foreach (var obj in validObjects) - { - try - { - if (obj.VisualRepresentation != null) - { - Console.WriteLine($"Seleccionando objeto: {obj.Nombre} (ID: {obj.Id.Value})"); - _objectManager.SelectObject(obj); - } - } - catch (Exception objEx) - { - Console.WriteLine($"Error al seleccionar objeto {obj.Nombre}: {objEx.Message}"); - } - } - - // Actualizar visuales de selección con manejo de errores - try - { - _objectManager.UpdateSelectionVisuals(); - Console.WriteLine($"Seleccionados exitosamente {_objectManager.SelectedObjects.Count} objetos"); - } - catch (Exception visEx) - { - Console.WriteLine($"Error al actualizar visuales de selección: {visEx.Message}"); - Console.WriteLine($"Stack trace: {visEx.StackTrace}"); - } - } - else - { - Console.WriteLine("No se pudieron seleccionar los objetos pegados - representación visual no válida"); - foreach (var obj in newlyPastedObjects) - { - Console.WriteLine($"Objeto {obj.Nombre}: VisualRep={obj.VisualRepresentation != null}, EnCanvas={obj.VisualRepresentation != null && ImagenEnTrabajoCanvas.Children.Contains(obj.VisualRepresentation)}"); - } + _objectManager.SelectObject(newObj); } } - catch (Exception ex) + } + + // 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) { - Console.WriteLine($"Error general al seleccionar objetos pegados: {ex.Message}"); - Console.WriteLine($"Stack trace: {ex.StackTrace}"); + vm.SelectedItemOsList = newlyPastedObjects.LastOrDefault(); } - }); - }); - - viewModel.HasUnsavedChanges = true; + } + + // 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"); } diff --git a/ObjetosSim/Decorativos/ucFramePlate.xaml b/ObjetosSim/Decorativos/ucFramePlate.xaml index a5ffbee..c1585b4 100644 --- a/ObjetosSim/Decorativos/ucFramePlate.xaml +++ b/ObjetosSim/Decorativos/ucFramePlate.xaml @@ -10,7 +10,9 @@ - + diff --git a/ObjetosSim/Decorativos/ucFramePlate.xaml.cs b/ObjetosSim/Decorativos/ucFramePlate.xaml.cs index f7b15a9..b2bc865 100644 --- a/ObjetosSim/Decorativos/ucFramePlate.xaml.cs +++ b/ObjetosSim/Decorativos/ucFramePlate.xaml.cs @@ -307,6 +307,34 @@ namespace CtrEditor.ObjetosSim [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; @@ -318,6 +346,23 @@ 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(); @@ -335,6 +380,63 @@ namespace CtrEditor.ObjetosSim // eliminar el objeto de simulacion } + /// + /// Calcula el punto de pivot basado en los checkboxes PivotCenterX y PivotCenterY + /// + /// Punto de pivot en coordenadas absolutas + public (float X, float Y) GetPivotPoint() + { + float pivotX = Left + (PivotCenterX ? Ancho / 2 : 0); + float pivotY = Top + (PivotCenterY ? Alto / 2 : 0); + return (pivotX, pivotY); + } + + /// + /// Centro X para el RenderTransform en píxeles del control + /// + [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; + } + } + } + + /// + /// Centro Y para el RenderTransform en píxeles del control + /// + [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 diff --git a/ObjetosSim/Dinamicos/ucBotella.xaml.cs b/ObjetosSim/Dinamicos/ucBotella.xaml.cs index 4dcbd49..04f0efa 100644 --- a/ObjetosSim/Dinamicos/ucBotella.xaml.cs +++ b/ObjetosSim/Dinamicos/ucBotella.xaml.cs @@ -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); } diff --git a/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs b/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs index e73cb3a..0985029 100644 --- a/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs +++ b/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs @@ -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; diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs index 8ec5b76..b196ea6 100644 --- a/ObjetosSim/osBase.cs +++ b/ObjetosSim/osBase.cs @@ -313,6 +313,16 @@ namespace CtrEditor.ObjetosSim [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) @@ -325,9 +335,12 @@ namespace CtrEditor.ObjetosSim FramePlate.PropertyChanged += OnFramePlatePropertyChanged; UpdateZIndex(FramePlate.Zindex_FramePlate); - // Calcular posición relativa inicial respecto al FramePlate - FramePlate_RelativeX = Left - FramePlate.Left; - FramePlate_RelativeY = Top - FramePlate.Top; + // 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; } } @@ -337,6 +350,8 @@ namespace CtrEditor.ObjetosSim FramePlate_RelativeX = 0; FramePlate_RelativeY = 0; FramePlate_InitialAngle = 0; + FramePlate_PivotX = 0; + FramePlate_PivotY = 0; } } @@ -366,8 +381,17 @@ namespace CtrEditor.ObjetosSim if (e.PropertyName == nameof(osFramePlate.Angulo)) { - UpdateOrbitalPosition(); + // 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)) @@ -381,6 +405,9 @@ namespace CtrEditor.ObjetosSim { 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; @@ -395,10 +422,18 @@ namespace CtrEditor.ObjetosSim float rotatedX = cosAngle * FramePlate_RelativeX - sinAngle * FramePlate_RelativeY; float rotatedY = sinAngle * FramePlate_RelativeX + cosAngle * FramePlate_RelativeY; - // Calcular nueva posición absoluta - Left = FramePlate.Left + rotatedX; - Top = FramePlate.Top + rotatedY; + // 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(); } @@ -407,13 +442,16 @@ namespace CtrEditor.ObjetosSim // 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 FramePlate - float currentRelativeX = Left - FramePlate.Left; - float currentRelativeY = Top - FramePlate.Top; + // 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 @@ -425,6 +463,34 @@ namespace CtrEditor.ObjetosSim } } + 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) { @@ -1082,6 +1148,8 @@ namespace CtrEditor.ObjetosSim CanvasSetTopinMeter(Top); } + + public bool LeerBitTag(string Tag) { if (this._plc == null) return false; diff --git a/Simulacion/Aether.cs b/Simulacion/Aether.cs index 639fcc0..3010485 100644 --- a/Simulacion/Aether.cs +++ b/Simulacion/Aether.cs @@ -520,6 +520,8 @@ namespace CtrEditor.Simulacion _world = world; ListOnTransports = new List(); _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; @@ -599,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 }