Se implementaron mejoras en la gestión de copias de objetos seleccionados, reutilizando la lógica de duplicación para crear copias serializables. Se agregó manejo de errores al intentar copiar objetos y se implementaron nuevas propiedades en la clase osBase para gestionar el punto de pivote en la rotación. Además, se aseguraron validaciones para evitar diámetros inválidos en la simulación, garantizando un comportamiento más robusto en la manipulación de objetos.

This commit is contained in:
Miguel 2025-06-17 17:35:35 +02:00
parent f85c707cfc
commit 99248e9112
7 changed files with 304 additions and 116 deletions

View File

@ -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<osBase>();
// 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<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}");
}
}
// Crear configuración de serialización igual a la usada en el guardado
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.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
TypeNameHandling = TypeNameHandling.All // Igual que en DuplicarObjeto
};
// Serializar objetos
string jsonString = JsonConvert.SerializeObject(objectsToCopy, settings);
// Copiar al portapapeles
string jsonString = JsonConvert.SerializeObject(objectsCopy, settings);
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();
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<osBase>(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;
}
}
}
// Si replaceExistingIds es true, mantener los IDs originales
string nombre = Regex.IsMatch(obj.Nombre, @"_\d+$")
? Regex.Replace(obj.Nombre, @"_\d+$", $"_{obj.Id.Value}")
: obj.Nombre + "_" + obj.Id.Value;
// Verificar que el objeto no existe ya si estamos reemplazando IDs
if (replaceExistingIds)
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)
{
@ -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
ImagenEnTrabajoCanvas.UpdateLayout();
// Usar Task.Delay para dar más tiempo al renderizado completo
Task.Delay(100).ContinueWith(_ =>
{
Application.Current.Dispatcher.Invoke(() =>
{
try
{
// Forzar una segunda actualización del layout
ImagenEnTrabajoCanvas.UpdateLayout();
// 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)
{
// Limpiar selección actual primero
// Usar la misma lógica de selección que DuplicarUserControl
// Limpiar selección antes de seleccionar los nuevos objetos
_objectManager.ClearSelection();
// Seleccionar los objetos válidos uno por uno con verificación
foreach (var obj in validObjects)
// Forzar actualización completa del layout
ImagenEnTrabajoCanvas.UpdateLayout();
// Usar dispatcher con la misma prioridad que DuplicarUserControl
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
{
try
// Seleccionar todos los objetos pegados
foreach (var newObj in newlyPastedObjects)
{
if (obj.VisualRepresentation != null)
if (newObj.VisualRepresentation != null)
{
Console.WriteLine($"Seleccionando objeto: {obj.Nombre} (ID: {obj.Id.Value})");
_objectManager.SelectObject(obj);
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);
}
}
catch (Exception objEx)
{
Console.WriteLine($"Error al seleccionar objeto {obj.Nombre}: {objEx.Message}");
}
}
// Actualizar visuales de selección con manejo de errores
try
// 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($"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)}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error general al seleccionar objetos pegados: {ex.Message}");
Console.WriteLine($"Stack trace: {ex.StackTrace}");
}
});
});
viewModel.HasUnsavedChanges = true;
Console.WriteLine($"Pegados y seleccionados {newlyPastedObjects.Count} objeto(s)");
}));
Console.WriteLine($"Pegados {newlyPastedObjects.Count} objeto(s) desde el portapapeles");
}

View File

@ -10,7 +10,9 @@
</UserControl.DataContext>
<Grid>
<Grid.RenderTransform>
<RotateTransform Angle="{Binding Angulo}" />
<RotateTransform Angle="{Binding Angulo}"
CenterX="{Binding RenderTransformCenterX}"
CenterY="{Binding RenderTransformCenterY}" />
</Grid.RenderTransform>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />

View File

@ -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
}
/// <summary>
/// Calcula el punto de pivot basado en los checkboxes PivotCenterX y PivotCenterY
/// </summary>
/// <returns>Punto de pivot en coordenadas absolutas</returns>
public (float X, float Y) GetPivotPoint()
{
float pivotX = Left + (PivotCenterX ? Ancho / 2 : 0);
float pivotY = Top + (PivotCenterY ? Alto / 2 : 0);
return (pivotX, pivotY);
}
/// <summary>
/// Centro X para el RenderTransform en píxeles del control
/// </summary>
[JsonIgnore]
public double RenderTransformCenterX
{
get
{
if (PivotCenterX)
{
// Centro horizontal del rectángulo principal
return PixelToMeter.Instance.calc.MetersToPixels(Ancho) / 2.0;
}
else
{
// Left del rectángulo principal (0)
return 0.0;
}
}
}
/// <summary>
/// Centro Y para el RenderTransform en píxeles del control
/// </summary>
[JsonIgnore]
public double RenderTransformCenterY
{
get
{
// Altura del título y del rectángulo principal
double titleHeight = PixelToMeter.Instance.calc.MetersToPixels(Alto_Titulo);
double frameHeight = PixelToMeter.Instance.calc.MetersToPixels(Alto);
if (PivotCenterY)
{
// Centro vertical de todo el control (título + rectángulo)
return (titleHeight + frameHeight) / 2.0;
}
else
{
// Top absoluto del control (0)
return 0.0;
}
}
}
}
public partial class ucFramePlate : UserControl, IDataContainer

View File

@ -51,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);
}

View File

@ -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;

View File

@ -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;

View File

@ -520,6 +520,8 @@ namespace CtrEditor.Simulacion
_world = world;
ListOnTransports = new List<simBase>();
_deferredActions = deferredActions;
// Asegurar que el diámetro sea al menos 0.01m para evitar errores
diameter = Min(diameter, 0.01f);
Radius = diameter / 2;
_mass = mass;
_activeJoint = null;
@ -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
}