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.Collections.Generic;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace CtrEditor namespace CtrEditor
{ {
@ -817,38 +816,46 @@ namespace CtrEditor
try try
{ {
var objectsToCopy = new List<osBase>(); if (DataContext is MainViewModel viewModel)
// Preparar objetos para serialización
foreach (var obj in _objectManager.SelectedObjects)
{ {
obj.SalvarDatosNoSerializables(); // Usar la misma lógica que DuplicarObjeto pero solo para crear copias para serialización
objectsToCopy.Add(obj); 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)
var settings = new JsonSerializerSettings {
{ MessageBox.Show("No se pudieron copiar los objetos seleccionados.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
Formatting = Formatting.Indented, return;
NullValueHandling = NullValueHandling.Ignore, }
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
// Serializar objetos // Serializar usando las mismas configuraciones que DuplicarObjeto
string jsonString = JsonConvert.SerializeObject(objectsToCopy, settings); var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.All // Igual que en DuplicarObjeto
};
// Copiar al portapapeles string jsonString = JsonConvert.SerializeObject(objectsCopy, settings);
Clipboard.SetText(jsonString); Clipboard.SetText(jsonString);
// Mostrar mensaje de confirmación Console.WriteLine($"Copiados {objectsCopy.Count} objeto(s) al portapapeles");
Console.WriteLine($"Copiados {objectsToCopy.Count} objeto(s) al portapapeles");
// Restaurar datos no serializables
foreach (var obj in _objectManager.SelectedObjects)
{
obj.RestaurarDatosNoSerializables();
} }
} }
catch (Exception ex) 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) private void PasteObjectsFromJson(bool replaceExistingIds = false)
{ {
if (!Clipboard.ContainsText()) if (!Clipboard.ContainsText())
@ -945,27 +979,21 @@ namespace CtrEditor
obj.Left += offsetX; obj.Left += offsetX;
obj.Top += offsetY; obj.Top += offsetY;
// Manejar IDs // Manejar IDs usando la misma lógica que DuplicarObjeto
if (!replaceExistingIds) if (!replaceExistingIds)
{ {
// Generar nuevo ID (comportamiento por defecto) // Generar nuevo ID y actualizar nombre (igual que en DuplicarObjeto)
obj.Id.ObtenerNuevaID(); obj.Id.ObtenerNuevaID();
// Actualizar nombre si contiene ID string nombre = Regex.IsMatch(obj.Nombre, @"_\d+$")
if (obj.Nombre.Contains("_")) ? Regex.Replace(obj.Nombre, @"_\d+$", $"_{obj.Id.Value}")
{ : obj.Nombre + "_" + obj.Id.Value;
var parts = obj.Nombre.Split('_');
if (parts.Length > 1 && int.TryParse(parts.Last(), out _)) obj.Nombre = nombre;
{
obj.Nombre = string.Join("_", parts.Take(parts.Length - 1)) + "_" + obj.Id.Value;
}
}
} }
// Si replaceExistingIds es true, mantener los IDs originales else
// Verificar que el objeto no existe ya si estamos reemplazando IDs
if (replaceExistingIds)
{ {
// Reemplazar objeto existente con mismo ID
var existingObj = viewModel.ObjetosSimulables.FirstOrDefault(o => o.Id.Value == obj.Id.Value); var existingObj = viewModel.ObjetosSimulables.FirstOrDefault(o => o.Id.Value == obj.Id.Value);
if (existingObj != null) if (existingObj != null)
{ {
@ -976,6 +1004,7 @@ namespace CtrEditor
obj.CheckData(); obj.CheckData();
viewModel.ObjetosSimulables.Add(obj); viewModel.ObjetosSimulables.Add(obj);
viewModel.CrearUserControlDesdeObjetoSimulable(obj); viewModel.CrearUserControlDesdeObjetoSimulable(obj);
viewModel.HasUnsavedChanges = true;
newlyPastedObjects.Add(obj); 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(); ImagenEnTrabajoCanvas.UpdateLayout();
// Usar Task.Delay para dar más tiempo al renderizado completo // Usar dispatcher con la misma prioridad que DuplicarUserControl
Task.Delay(100).ContinueWith(_ => 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 double left = Canvas.GetLeft(newObj.VisualRepresentation);
ImagenEnTrabajoCanvas.UpdateLayout(); double top = Canvas.GetTop(newObj.VisualRepresentation);
// Validar que los objetos tengan representación visual antes de seleccionar // Solo añadir a selección si el objeto tiene coordenadas válidas
var validObjects = newlyPastedObjects.Where(obj => if (!double.IsNaN(left) && !double.IsNaN(top) && !double.IsInfinity(left) && !double.IsInfinity(top))
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 _objectManager.SelectObject(newObj);
_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)}");
}
} }
} }
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}"); vm.SelectedItemOsList = newlyPastedObjects.LastOrDefault();
Console.WriteLine($"Stack trace: {ex.StackTrace}");
} }
}); }
});
// Actualizar visuales de selección
viewModel.HasUnsavedChanges = true; _objectManager.UpdateSelectionVisuals();
Console.WriteLine($"Pegados y seleccionados {newlyPastedObjects.Count} objeto(s)");
}));
Console.WriteLine($"Pegados {newlyPastedObjects.Count} objeto(s) desde el portapapeles"); Console.WriteLine($"Pegados {newlyPastedObjects.Count} objeto(s) desde el portapapeles");
} }

View File

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

View File

@ -307,6 +307,34 @@ namespace CtrEditor.ObjetosSim
[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;
@ -318,6 +346,23 @@ 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();
@ -335,6 +380,63 @@ 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

View File

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

View File

@ -133,6 +133,12 @@ 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;

View File

@ -313,6 +313,16 @@ namespace CtrEditor.ObjetosSim
[property: Hidden] [property: Hidden]
private float framePlate_InitialAngle; 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)
@ -325,9 +335,12 @@ namespace CtrEditor.ObjetosSim
FramePlate.PropertyChanged += OnFramePlatePropertyChanged; FramePlate.PropertyChanged += OnFramePlatePropertyChanged;
UpdateZIndex(FramePlate.Zindex_FramePlate); UpdateZIndex(FramePlate.Zindex_FramePlate);
// Calcular posición relativa inicial respecto al FramePlate // Calcular punto de pivot y posición relativa inicial
FramePlate_RelativeX = Left - FramePlate.Left; var pivotPoint = FramePlate.GetPivotPoint();
FramePlate_RelativeY = Top - FramePlate.Top; FramePlate_PivotX = pivotPoint.X;
FramePlate_PivotY = pivotPoint.Y;
FramePlate_RelativeX = Left - FramePlate_PivotX;
FramePlate_RelativeY = Top - FramePlate_PivotY;
FramePlate_InitialAngle = FramePlate.Angulo; FramePlate_InitialAngle = FramePlate.Angulo;
} }
} }
@ -337,6 +350,8 @@ namespace CtrEditor.ObjetosSim
FramePlate_RelativeX = 0; FramePlate_RelativeX = 0;
FramePlate_RelativeY = 0; FramePlate_RelativeY = 0;
FramePlate_InitialAngle = 0; FramePlate_InitialAngle = 0;
FramePlate_PivotX = 0;
FramePlate_PivotY = 0;
} }
} }
@ -366,8 +381,17 @@ namespace CtrEditor.ObjetosSim
if (e.PropertyName == nameof(osFramePlate.Angulo)) if (e.PropertyName == nameof(osFramePlate.Angulo))
{ {
UpdateOrbitalPosition(); // Primero actualizar el ángulo del objeto
Angulo += ((osFramePlate)sender).offsetAngulo; 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)) if (e.PropertyName == nameof(osFramePlate.Zindex_FramePlate))
@ -381,6 +405,9 @@ namespace CtrEditor.ObjetosSim
{ {
if (FramePlate == null) return; 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 // Calcular el ángulo de rotación total desde la posición inicial
float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle; float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
@ -395,10 +422,18 @@ namespace CtrEditor.ObjetosSim
float rotatedX = cosAngle * FramePlate_RelativeX - sinAngle * FramePlate_RelativeY; float rotatedX = cosAngle * FramePlate_RelativeX - sinAngle * FramePlate_RelativeY;
float rotatedY = sinAngle * FramePlate_RelativeX + cosAngle * FramePlate_RelativeY; float rotatedY = sinAngle * FramePlate_RelativeX + cosAngle * FramePlate_RelativeY;
// Calcular nueva posición absoluta // Calcular nueva posición absoluta usando el punto de pivot actual
Left = FramePlate.Left + rotatedX; float newLeft = currentPivot.X + rotatedX;
Top = FramePlate.Top + rotatedY; 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(); OnMoveResizeRotate();
} }
@ -407,13 +442,16 @@ namespace CtrEditor.ObjetosSim
// Solo actualizar si está conectado a un FramePlate y el movimiento NO viene del FramePlate // Solo actualizar si está conectado a un FramePlate y el movimiento NO viene del FramePlate
if (FramePlate != null && !isUpdatingFromFramePlate) if (FramePlate != null && !isUpdatingFromFramePlate)
{ {
// Obtener punto de pivot actual
var currentPivot = FramePlate.GetPivotPoint();
// Recalcular posición relativa considerando la rotación actual del FramePlate // Recalcular posición relativa considerando la rotación actual del FramePlate
float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle; float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
float angleRad = deltaAngle * (float)Math.PI / 180.0f; float angleRad = deltaAngle * (float)Math.PI / 180.0f;
// Calcular la posición relativa actual respecto al FramePlate // Calcular la posición relativa actual respecto al pivot del FramePlate
float currentRelativeX = Left - FramePlate.Left; float currentRelativeX = Left - currentPivot.X;
float currentRelativeY = Top - FramePlate.Top; float currentRelativeY = Top - currentPivot.Y;
// Si el FramePlate está rotado, necesitamos "desrotar" la posición para obtener // Si el FramePlate está rotado, necesitamos "desrotar" la posición para obtener
// la posición relativa en el sistema de coordenadas original // 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) private void ShowPreviewWindow(Stream imageStream)
{ {
@ -1082,6 +1148,8 @@ 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;

View File

@ -520,6 +520,8 @@ 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;
@ -599,6 +601,8 @@ 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
} }