Compare commits

..

No commits in common. "75c507be4e5ba0499d6fc71e8cbcc6f0260d99f8" and "9b710fcb000c2d3463cad7682c5bfbbdca210f20" have entirely different histories.

26 changed files with 108 additions and 1248 deletions

26
.vscode/launch.json vendored
View File

@ -1,26 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/net8.0-windows8.0/CtrEditor.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
.vscode/tasks.json vendored
View File

@ -1,41 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/CtrEditor.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/CtrEditor.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/CtrEditor.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@ -1,28 +0,0 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace CtrEditor.Converters
{
public class ImageDisplayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string imageName)
{
// Buscar el MainViewModel en el Application.Current.MainWindow.DataContext
if (Application.Current?.MainWindow?.DataContext is MainViewModel viewModel)
{
return viewModel.GetImageDisplayNameWithTags(imageName);
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,46 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace CtrEditor.Converters
{
public class RegionalFloatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is float floatValue)
{
// Usar la cultura actual para mostrar el número con el separador decimal correcto
return floatValue.ToString("N4", CultureInfo.CurrentCulture);
}
return value?.ToString() ?? string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string stringValue)
{
// Intentar parsing con la cultura actual primero
if (float.TryParse(stringValue, NumberStyles.Float, CultureInfo.CurrentCulture, out float result))
{
return result;
}
// Si falla, intentar con cultura invariante (punto como separador)
if (float.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out result))
{
return result;
}
// Si ambos fallan, intentar reemplazar punto por coma o viceversa
string adjustedString = stringValue.Replace(',', '.').Replace(".", CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
if (float.TryParse(adjustedString, NumberStyles.Float, CultureInfo.CurrentCulture, out result))
{
return result;
}
}
return 0.0f; // Valor por defecto si no se puede parsear
}
}
}

View File

@ -118,7 +118,8 @@ namespace CtrEditor
_mainViewModel.PLCViewModel = _globalState.PLCConfiguration ?? new PLCViewModel();
if (_globalState.UnitConverter != null)
PixelToMeter.Instance.calc = _globalState.UnitConverter;
// No crear un nuevo UnitConverter si no hay uno guardado, mantener el actual
else
PixelToMeter.Instance.calc = new UnitConverter(1.0f); // Valor por defecto
// Restaurar objetos globales
foreach (var obj in _globalState.SharedObjects)

View File

@ -5,14 +5,12 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace CtrEditor
{
public class DatosDeTrabajo
{
public Dictionary<string, string> Imagenes { get; private set; }
private MainViewModel _mainViewModel;
public DatosDeTrabajo()
{
@ -20,11 +18,6 @@ namespace CtrEditor
CargarImagenes(); // Inicializar la carga de imágenes basada en el directorio persistente
}
public void SetMainViewModel(MainViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
}
public string ObtenerPathImagenConExtension(string key, string extension)
{
if (Imagenes.TryGetValue(key, out string imagePath))
@ -58,85 +51,7 @@ namespace CtrEditor
Imagenes[nombreArchivo] = archivo;
}
}
// Cargar datos de imágenes existentes desde archivos JSON
if (_mainViewModel != null)
{
CargarDatosImagenesExistentes();
}
}
}
private void CargarDatosImagenesExistentes()
{
var jsonSerializerSettings = GetJsonSerializerSettings();
foreach (var imageName in Imagenes.Keys)
{
string jsonPath = ObtenerPathImagenConExtension(imageName, ".json");
if (!string.IsNullOrEmpty(jsonPath) && File.Exists(jsonPath))
{
try
{
// Cargar datos completos del archivo JSON
string jsonString = File.ReadAllText(jsonPath);
var simulationData = Newtonsoft.Json.JsonConvert.DeserializeObject<SimulationData>(jsonString, jsonSerializerSettings);
// Cargar datos de imágenes si existen en el archivo
if (simulationData?.ImageDataDictionary != null)
{
foreach (var imageDataEntry in simulationData.ImageDataDictionary)
{
// Solo cargar si no existe ya en el diccionario principal
if (!_mainViewModel._imageDataDictionary.ContainsKey(imageDataEntry.Key))
{
_mainViewModel._imageDataDictionary[imageDataEntry.Key] = imageDataEntry.Value;
}
}
}
// Compatibilidad con versiones anteriores (ImageCustomNames)
#pragma warning disable CS0618 // Type or member is obsolete
if (simulationData?.ImageCustomNames != null)
{
foreach (var customName in simulationData.ImageCustomNames)
{
var imageData = _mainViewModel.GetOrCreateImageData(customName.Key);
if (string.IsNullOrEmpty(imageData.CustomName)) // Solo actualizar si no tiene nombre personalizado
{
imageData.CustomName = customName.Value;
}
}
}
#pragma warning restore CS0618
}
catch (Exception ex)
{
// Log del error pero no fallar la carga completa
System.Diagnostics.Debug.WriteLine($"Error al cargar datos de imagen desde {jsonPath}: {ex.Message}");
// Como fallback, crear una instancia vacía
_mainViewModel.GetOrCreateImageData(imageName);
}
}
else
{
// Si no existe archivo JSON, crear instancia vacía
_mainViewModel.GetOrCreateImageData(imageName);
}
}
}
private Newtonsoft.Json.JsonSerializerSettings GetJsonSerializerSettings()
{
return new Newtonsoft.Json.JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto,
ObjectCreationHandling = Newtonsoft.Json.ObjectCreationHandling.Replace,
ConstructorHandling = Newtonsoft.Json.ConstructorHandling.AllowNonPublicDefaultConstructor
};
}
}
}

View File

@ -81,12 +81,6 @@ namespace CtrEditor
[ObservableProperty]
public ObservableCollection<TipoSimulable> listaOsBase;
// Diccionario para almacenar datos expandidos de imágenes
public Dictionary<string, Models.ImageData> _imageDataDictionary = new Dictionary<string, Models.ImageData>();
// Comando para renombrar imagen
public ICommand RenameImageCommand { get; private set; }
public ICommand StartSimulationCommand { get; }
public ICommand StopSimulationCommand { get; }
public ICommand ItemDoubleClickCommand { get; private set; }
@ -389,7 +383,6 @@ namespace CtrEditor
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
TBLibraryManagerCommand = new RelayCommand(ShowLibraryManager);
RenameImageCommand = new RelayCommand<string>(RenameImage);
stopwatch_Sim = new Stopwatch();
stopwatch_Sim.Start();
@ -405,73 +398,6 @@ namespace CtrEditor
_stateManager = new StateManager(EstadoPersistente.Instance.directorio, this);
_stateSerializer = new StateSerializer(this, datosDeTrabajo, simulationManager);
// Conectar DatosDeTrabajo con este ViewModel para el escaneo de imágenes
datosDeTrabajo.SetMainViewModel(this);
}
// Métodos para manejo de datos de imágenes
private void RenameImage(string imageName)
{
if (string.IsNullOrEmpty(imageName)) return;
var imageData = GetOrCreateImageData(imageName);
var dialog = new PopUps.RenameImageWindow(imageName, imageData);
dialog.Owner = MainWindow;
if (dialog.ShowDialog() == true)
{
// El dialog ya ha modificado directamente el imageData
// Solo necesitamos verificar si debemos remover la entrada si está vacía
if (string.IsNullOrEmpty(imageData.CustomName) &&
imageData.Tags.Count == 0 &&
string.IsNullOrEmpty(imageData.Etiquetas))
{
_imageDataDictionary.Remove(imageName);
}
// Forzar actualización del UI usando CollectionViewSource
var collectionView = System.Windows.Data.CollectionViewSource.GetDefaultView(ListaImagenes);
collectionView?.Refresh();
HasUnsavedChanges = true;
}
}
public string GetImageDisplayName(string imageName)
{
if (_imageDataDictionary.TryGetValue(imageName, out var imageData))
{
return imageData.DisplayName;
}
return imageName;
}
public string GetImageDisplayNameWithTags(string imageName)
{
if (_imageDataDictionary.TryGetValue(imageName, out var imageData))
{
var displayName = imageData.DisplayName;
var tags = imageData.Etiquetas;
if (!string.IsNullOrWhiteSpace(tags))
{
return $"{displayName} {tags}";
}
return displayName;
}
return imageName;
}
public Models.ImageData GetOrCreateImageData(string imageName)
{
if (!_imageDataDictionary.TryGetValue(imageName, out var imageData))
{
imageData = new Models.ImageData(imageName);
_imageDataDictionary[imageName] = imageData;
}
return imageData;
}
private void OnVisFilterChanged(osVisFilterViewModel filter) // Changed signature to accept viewmodel directly
@ -1236,77 +1162,6 @@ namespace CtrEditor
OnPropertyChanged(nameof(SelectedItemOsList));
}
// Diccionario para manejar la ventana de configuración de escala
private PopUps.ScaleConfigWindow? _scaleConfigWindow;
// Método para configurar la escala desde el menú contextual
public void ConfigureScale()
{
// Verificar si ya existe una ventana de configuración de escala
if (_scaleConfigWindow != null && _scaleConfigWindow.IsVisible)
{
_scaleConfigWindow.Activate();
return;
}
var currentScale = PixelToMeter.Instance.calc.Scale;
_scaleConfigWindow = new PopUps.ScaleConfigWindow(currentScale, this);
_scaleConfigWindow.Owner = MainWindow;
// Manejar el cierre de la ventana
_scaleConfigWindow.Closed += (s, e) => _scaleConfigWindow = null;
// Mostrar como modeless (no modal)
_scaleConfigWindow.Show();
}
// Método público para aplicar la escala desde la ventana modeless
public void ApplyScale(float newScale)
{
// Detener simulaciones antes de cambiar la escala
StopSimulation();
StopFluidSimulation();
DisconnectPLC();
// Actualizar la escala en el UnitConverter
PixelToMeter.Instance.calc.SetScale(newScale);
// Forzar redibujo completo del canvas y todos los objetos
ForceCanvasRedraw();
// Marcar como cambios no guardados ya que esto afecta la configuración de la imagen
HasUnsavedChanges = true;
// Limpiar historial de undo después de cambiar la escala
MainWindow?.ClearUndoHistory();
}
// Método para forzar el redibujo completo del canvas
private void ForceCanvasRedraw()
{
// Forzar actualización de todos los objetos simulables mediante PropertyChanged
foreach (var obj in ObjetosSimulables)
{
// Forzar la notificación de PropertyChanged para las propiedades de posición y tamaño
// Esto hará que los bindings y converters se recalculen automáticamente
obj.ForceUpdatePositionBindings();
}
// Forzar actualización del layout del canvas principal
MainWindow?.ImagenEnTrabajoCanvas?.InvalidateVisual();
MainWindow?.ImagenEnTrabajoCanvas?.UpdateLayout();
// Actualizar selecciones visuales si existen
_objectManager?.UpdateSelectionVisuals();
// Forzar actualización global del layout con prioridad de renderizado
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
{
MainWindow?.ImagenEnTrabajoCanvas?.InvalidateVisual();
MainWindow?.ImagenEnTrabajoCanvas?.UpdateLayout();
}));
}
// Diccionario para manejar ventanas de biblioteca múltiples
private Dictionary<string, PopUps.LibraryWindow> _libraryWindows = new();
@ -1347,13 +1202,6 @@ namespace CtrEditor
// Nueva propiedad para almacenar los datos locales de objetos globales
public ObservableCollection<DatosLocales>? DatosLocalesObjetos { get; set; }
// Diccionario para almacenar datos expandidos de imágenes
public Dictionary<string, Models.ImageData>? ImageDataDictionary { get; set; }
// Compatibilidad con versiones anteriores - OBSOLETO
[System.Obsolete("Use ImageDataDictionary instead")]
public Dictionary<string, string>? ImageCustomNames { get; set; }
}
public class TipoSimulable

View File

@ -6,7 +6,6 @@
xmlns:local="clr-namespace:CtrEditor"
xmlns:controls="clr-namespace:CtrEditor.Controls"
xmlns:uc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
xmlns:converters="clr-namespace:CtrEditor.Converters"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim"
@ -49,9 +48,6 @@
<!-- Converter for PLC Connect/Disconnect image -->
<local:ConnectStateToImageConverter x:Key="ConnectStateToImageConverter" />
<!-- Converter for Image Display Names -->
<converters:ImageDisplayNameConverter x:Key="ImageDisplayNameConverter" />
</Window.Resources>
<Grid>
@ -91,18 +87,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="ListaImagenes" Grid.Row="0" Margin="5" ItemsSource="{Binding ListaImagenes}"
SelectedItem="{Binding SelectedImage}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ImageDisplayNameConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Propiedades" Command="{Binding RenameImageCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
SelectedItem="{Binding SelectedImage}" />
<ListBox x:Name="ListaFunciones" Grid.Row="1" Margin="5" ItemsSource="{Binding ListaOsBase}"
DisplayMemberPath="Nombre" SelectedItem="{Binding SelectedItem}">
<i:Interaction.Triggers>

View File

@ -328,7 +328,7 @@ namespace CtrEditor
{
// Siempre trabajar con selección única para las propiedades
CargarPropiedadesosDatos(selectedObject);
_objectManager.SelectObject(selectedObject);
}
else if (e.RemovedItems.Count > 0 && e.AddedItems.Count == 0)
@ -414,7 +414,7 @@ namespace CtrEditor
{
// Capturar estado antes de mover
_objectManager.CaptureUndoState();
// Mover todos los objetos primero
foreach (var obj in _objectManager.SelectedObjects)
{
@ -472,7 +472,7 @@ namespace CtrEditor
if (DataContext is MainViewModel viewModel)
{
viewModel.CargarPropiedadesosDatos(selectedObject, PanelEdicion, Resources);
// If no object is selected, make sure to clear the properties panel
if (selectedObject == null)
{
@ -712,19 +712,12 @@ namespace CtrEditor
contextMenu.Items.Add(lockSubmenu);
contextMenu.Items.Add(new Separator());
}
// Agregar opción de configurar escala
var scaleConfigItem = new MenuItem { Header = "Configurar Escala..." };
scaleConfigItem.Click += (s, e) => viewModel.ConfigureScale();
contextMenu.Items.Add(scaleConfigItem);
contextMenu.Items.Add(new Separator());
// Agregar información del sistema de undo
var undoHistoryCount = _objectManager.GetUndoHistoryCount();
var canUndo = _objectManager.CanUndo();
var undoInfoItem = new MenuItem
{
var undoInfoItem = new MenuItem
{
Header = $"Undo: {undoHistoryCount}/3 estados ({(canUndo ? "Ctrl+Z disponible" : "No disponible")})",
IsEnabled = false,
FontStyle = FontStyles.Italic
@ -827,7 +820,7 @@ namespace CtrEditor
{
// 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
@ -861,7 +854,7 @@ namespace CtrEditor
string jsonString = JsonConvert.SerializeObject(objectsCopy, settings);
Clipboard.SetText(jsonString);
Console.WriteLine($"Copiados {objectsCopy.Count} objeto(s) al portapapeles");
}
}
@ -906,7 +899,7 @@ namespace CtrEditor
try
{
string jsonString = Clipboard.GetText();
// Validación básica del JSON
if (string.IsNullOrWhiteSpace(jsonString) || (!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
{
@ -991,11 +984,11 @@ namespace CtrEditor
{
// 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
@ -1012,7 +1005,7 @@ namespace CtrEditor
viewModel.ObjetosSimulables.Add(obj);
viewModel.CrearUserControlDesdeObjetoSimulable(obj);
viewModel.HasUnsavedChanges = true;
newlyPastedObjects.Add(obj);
}
catch (Exception ex)
@ -1024,10 +1017,10 @@ namespace CtrEditor
// 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(() =>
{
@ -1038,7 +1031,7 @@ namespace CtrEditor
{
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))
{
@ -1046,10 +1039,10 @@ namespace CtrEditor
}
}
}
// Forzar otra actualización del layout antes de actualizar visuales
ImagenEnTrabajoCanvas.UpdateLayout();
// Actualizar SelectedItemOsList si hay objetos pegados
if (newlyPastedObjects.Count > 0)
{
@ -1058,13 +1051,13 @@ namespace CtrEditor
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");
}
}
@ -1082,9 +1075,9 @@ namespace CtrEditor
try
{
string jsonString = Clipboard.GetText();
// Validación básica del JSON
if (string.IsNullOrWhiteSpace(jsonString) ||
if (string.IsNullOrWhiteSpace(jsonString) ||
(!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
{
return false;

View File

@ -1,110 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using CtrEditor.ObjetosSim;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
using System.Linq;
namespace CtrEditor.Models
{
public class ImageData : INotifyPropertyChanged
{
private string _customName;
private List<string> _tags;
private string _etiquetas;
public string FileName { get; set; }
[DisplayName("Nombre personalizado")]
public string CustomName
{
get => _customName;
set
{
_customName = value;
OnPropertyChanged();
}
}
[Browsable(false)]
public List<string> Tags
{
get => _tags ?? new List<string>();
set
{
_tags = value;
_etiquetas = string.Join(" ", value.Select(tag => tag.StartsWith("#") ? tag : "#" + tag));
OnPropertyChanged();
OnPropertyChanged(nameof(Etiquetas));
}
}
[DisplayName("Etiquetas")]
[property: Editor(typeof(TagPropertyEditor), typeof(TagPropertyEditor))]
public string Etiquetas
{
get => _etiquetas ?? string.Empty;
set
{
_etiquetas = value ?? string.Empty;
// Convertir string de etiquetas a List<string>
if (string.IsNullOrWhiteSpace(_etiquetas))
{
_tags = new List<string>();
}
else
{
_tags = _etiquetas.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Select(tag => tag.StartsWith("#") ? tag.Substring(1) : tag)
.Where(tag => !string.IsNullOrWhiteSpace(tag))
.ToList();
}
OnPropertyChanged();
OnPropertyChanged(nameof(Tags));
}
}
/// <summary>
/// Lista de etiquetas sin el prefijo #, compatible con osBase
/// </summary>
[Browsable(false)]
public List<string> ListaEtiquetas
{
get
{
if (string.IsNullOrWhiteSpace(Etiquetas))
return new List<string>();
return Etiquetas.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Select(tag => tag.StartsWith("#") ? tag.Substring(1) : tag)
.Where(tag => !string.IsNullOrWhiteSpace(tag))
.ToList();
}
}
public string DisplayName => !string.IsNullOrEmpty(CustomName) ? CustomName : FileName;
public ImageData()
{
FileName = string.Empty;
_customName = string.Empty;
_tags = new List<string>();
_etiquetas = string.Empty;
}
public ImageData(string fileName, string customName = null, List<string> tags = null)
{
FileName = fileName;
_customName = customName ?? string.Empty;
_tags = tags ?? new List<string>();
_etiquetas = string.Join(" ", _tags.Select(tag => tag.StartsWith("#") ? tag : "#" + tag));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@ -202,7 +202,7 @@ namespace CtrEditor
PurgeDeletedObjects();
// Asegurarse de que el canvas haya actualizado su layout
_canvas.UpdateLayout();
RemoveResizeRectangles();
if (_selectedObjects.Any())
{

View File

@ -26,7 +26,7 @@ namespace CtrEditor.ObjetosSim
return "Transporte";
}
private string nombre = "Transporte TTOP";
[property: Category("Id:")]
public override string Nombre
{
@ -77,7 +77,7 @@ namespace CtrEditor.ObjetosSim
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
string id_Motor;
[JsonIgnore]
[JsonIgnore]
private PropertyChangedEventHandler motorPropertyChangedHandler;
partial void OnId_MotorChanged(string value)
@ -169,54 +169,8 @@ namespace CtrEditor.ObjetosSim
if (_visualRepresentation is ucTransporteTTop uc)
{
try
{
// Asegurar que el layout esté actualizado antes de crear la geometría
uc.UpdateLayout();
// Validar que el rectángulo esté disponible y tenga dimensiones válidas
if (uc.Transporte != null &&
(!double.IsNaN(uc.Transporte.ActualWidth) && uc.Transporte.ActualWidth > 0) ||
(!double.IsNaN(uc.Transporte.Width) && uc.Transporte.Width > 0))
{
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
}
else
{
// Si el rectángulo no está listo, intentar después del próximo layout
uc.Dispatcher.BeginInvoke(new Action(() =>
{
if (uc.Transporte != null && simulationManager != null)
{
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
}
}), System.Windows.Threading.DispatcherPriority.Loaded);
}
}
catch (Exception ex)
{
// Log del error para diagnóstico pero continuar sin fallar
System.Diagnostics.Debug.WriteLine($"Error al crear geometría de simulación: {ex.Message}");
// Intentar crear la geometría más tarde
uc.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
if (uc.Transporte != null && simulationManager != null)
{
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
}
}
catch (Exception ex2)
{
System.Diagnostics.Debug.WriteLine($"Segundo intento falló: {ex2.Message}");
}
}), System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
}
}
public override void ucUnLoaded()

View File

@ -2,13 +2,11 @@
using CtrEditor.FuncionesBase;
using LibS7Adv;
using Newtonsoft.Json;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace CtrEditor.ObjetosSim
{
/// <summary>
@ -61,16 +59,6 @@ namespace CtrEditor.ObjetosSim
[ObservableProperty]
public float tiempoRampa;
[ObservableProperty]
[property: Description("Enable read of the Motor encoder simulated on PLC.")]
[property: Category("Encoder:")]
bool motor_With_Encoder;
[ObservableProperty]
[property: Description("Actual Value of the encoder position.")]
[property: Category("Encoder:")]
public float actual_Position;
partial void OnTiempoRampaChanged(float value)
{
if (value < 0.1f)
@ -216,14 +204,9 @@ namespace CtrEditor.ObjetosSim
// Add timestamp to trace when the read occurs
var timestamp = DateTime.Now;
if (Data.Motor_With_Encoder)
Data.Actual_Position = (float)plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ActualPosition");
else
Data.Actual_Position = 0;
// Read ControlWord and track the raw response
var rawResponse = plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ControlWord");
// Read ControlWord and track the raw response
var rawResponse = plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ControlWord");
int controlWord = rawResponse ?? 0;
var control = VMMotorBitPacker.UnpackControlWord(controlWord);

View File

@ -14,6 +14,8 @@ namespace CtrEditor.ObjetosSim
public partial class osEncoderMotor : osBase, IosBase
{
private osBase Motor = null;
private Stopwatch Stopwatch = new Stopwatch();
private double stopwatch_last = 0;
public static string NombreClase()
{
@ -84,6 +86,7 @@ namespace CtrEditor.ObjetosSim
Pulsos_Por_Vuelta = 360; // Por defecto, un pulso por grado
Ratio_Giros_50hz = 1; // Por defecto, 1 giro por cada 50Hz
Color_oculto = Brushes.Gray;
Stopwatch.Start();
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)

View File

@ -14,6 +14,8 @@ namespace CtrEditor.ObjetosSim
public partial class osEncoderMotorLineal : osBase, IosBase
{
private osBase Motor = null;
private Stopwatch Stopwatch = new Stopwatch();
private double stopwatch_last = 0;
public static string NombreClase()
{
@ -84,6 +86,7 @@ namespace CtrEditor.ObjetosSim
{
Pulsos_Por_Hz = 3000; // Por defecto, un pulso por grado
Color_oculto = Brushes.Gray;
Stopwatch.Start();
}
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
@ -97,8 +100,7 @@ namespace CtrEditor.ObjetosSim
Valor_Actual = (int)value;
return;
}
}
else if (Motor != null && Motor is osVMmotorSim motor)
} else if (Motor != null && Motor is osVMmotorSim motor)
{
VelocidadActual = motor.Velocidad;
@ -114,10 +116,7 @@ namespace CtrEditor.ObjetosSim
float incrementoPulsos = pulsosPorHz * segundosTranscurridos;
// Actualizar valor del encoder
if (motor.Motor_With_Encoder)
Valor_Actual = motor.Actual_Position;
else
Valor_Actual = (Valor_Actual + incrementoPulsos);
Valor_Actual = (Valor_Actual + incrementoPulsos);
// Actualizar color basado en si está girando
Color_oculto = Math.Abs(pulsosPorHz) > 0.01f ? Brushes.LightGreen : Brushes.Gray;

View File

@ -5,7 +5,6 @@ using System.Windows.Data;
using CtrEditor.PopUps;
using Xceed.Wpf.Toolkit.PropertyGrid;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
using CtrEditor.Models;
namespace CtrEditor.ObjetosSim
{
@ -51,18 +50,18 @@ namespace CtrEditor.ObjetosSim
{
try
{
// Obtener el MainWindow para acceder a todos los objetos
var mainWindow = Application.Current.Windows
.OfType<MainWindow>()
.FirstOrDefault();
if (mainWindow?.DataContext is MainViewModel mainViewModel)
// Obtener el objeto que se está editando
if (propertyItem.Instance is osBase osObject)
{
// Determinar si el objeto es osBase o ImageData
if (propertyItem.Instance is osBase osObject)
// Obtener el MainWindow para acceder a todos los objetos
var mainWindow = Application.Current.Windows
.OfType<MainWindow>()
.FirstOrDefault();
if (mainWindow?.DataContext is MainViewModel mainViewModel)
{
// Abrir el editor de etiquetas para osBase
var tagEditor = new TagEditorWindow(osObject, mainViewModel.ObjetosSimulables, mainViewModel._imageDataDictionary);
// Abrir el editor de etiquetas
var tagEditor = new TagEditorWindow(osObject, mainViewModel.ObjetosSimulables);
tagEditor.Owner = mainWindow;
if (tagEditor.ShowDialog() == true)
@ -72,22 +71,6 @@ namespace CtrEditor.ObjetosSim
textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
}
}
else if (propertyItem.Instance is ImageData imageData)
{
// Para ImageData, crear un osBase temporal para usar el editor
var tempOsBase = new osTextPlate();
tempOsBase.Etiquetas = imageData.Etiquetas;
var tagEditor = new TagEditorWindow(tempOsBase, mainViewModel.ObjetosSimulables, mainViewModel._imageDataDictionary);
tagEditor.Owner = mainWindow;
if (tagEditor.ShowDialog() == true)
{
// Actualizar ImageData con las nuevas etiquetas
imageData.Etiquetas = tempOsBase.Etiquetas;
textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
}
}
}
}
catch (Exception ex)

View File

@ -1,14 +1,17 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucBoolTag" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:ei="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl x:Class="CtrEditor.ObjetosSim.ucBoolTag"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:ei="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osBoolTag Show_Description="True" />
<vm:osBoolTag/>
</UserControl.DataContext>
<Canvas RenderTransformOrigin="0.5,0.5" ToolTip="{Binding Descripcion}">
<Canvas RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform />
@ -23,14 +26,18 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border x:Name="BackgroundRectangle" BorderBrush="Black" BorderThickness="2" CornerRadius="10,0,0,10"
Width="16" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center"
Background="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" />
<Label Content="{Binding Descripcion}" Grid.Column="1" VerticalAlignment="Center"
Background="{Binding Color}"
Visibility="{Binding Show_Description, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Border x:Name="BackgroundRectangle"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="10,0,0,10"
Width="30"
Height="40"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Background="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"/>
<Label Content="{Binding Descripcion}" Grid.Column="1" VerticalAlignment="Center" Background="{Binding Color}" />
<CheckBox Grid.Column="2" VerticalAlignment="Center" IsChecked="{Binding Estado}"
Background="{Binding Color_oculto}" />
Background="{Binding Color_oculto}" />
</Grid>
</Canvas>
</UserControl>

View File

@ -39,9 +39,6 @@ namespace CtrEditor.ObjetosSim
[ObservableProperty]
public bool estado;
[ObservableProperty]
public bool show_Description;
partial void OnEstadoChanged(bool value)
{
EscribirBitTag(Tag, value);
@ -72,7 +69,6 @@ namespace CtrEditor.ObjetosSim
tag = "%M50.0";
Descripcion = "Nombre del Tag";
Color = Colors.LightBlue;
Show_Description = true;
}
public override void UpdatePLCPrimerCiclo()

View File

@ -7,7 +7,7 @@ using LibS7Adv;
using nkast.Aether.Physics2D.Common;
using PaddleOCRSharp;
using Siemens.Simatic.Simulation.Runtime;
using System.ComponentModel; // Para poder usar [property: Category ...
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Windows;
@ -1195,21 +1195,6 @@ namespace CtrEditor.ObjetosSim
CanvasSetTopinMeter(Top);
}
/// <summary>
/// Fuerza la actualización de todos los bindings de posición y tamaño
/// disparando PropertyChanged para Left, Top, Ancho, Alto
/// </summary>
public void ForceUpdatePositionBindings()
{
// Para Left y Top, actualizar directamente el Canvas ya que tienen lógica especial en OnChanged
CanvasSetLeftinMeter(Left);
CanvasSetTopinMeter(Top);
// Para Ancho y Alto, disparar PropertyChanged para que los bindings se actualicen
OnPropertyChanged(nameof(Ancho));
OnPropertyChanged(nameof(Alto));
}
public bool LeerBitTag(string Tag)
@ -1364,65 +1349,13 @@ namespace CtrEditor.ObjetosSim
return transformGroup;
}
private static bool IsVisualChild(Visual child, Visual parent)
{
if (child == null || parent == null)
return false;
try
{
// Intentar obtener el ancestro común
var transform = child.TransformToAncestor(parent);
return transform != null;
}
catch (InvalidOperationException)
{
return false;
}
}
public (Vector2 TopLeft, Vector2 BottomRight) GetRectangleCoordinatesInMeter(Rectangle rect)
{
if (rect == null)
return (new Vector2(0, 0), new Vector2(0, 0));
var _canvasLeft = CanvasGetLeftinMeter();
var _canvasTop = CanvasGetTopinMeter();
try
if (rect != null)
{
// Verificar que tanto el rectángulo como la representación visual estén cargados
if (!rect.IsLoaded || _visualRepresentation == null || !_visualRepresentation.IsLoaded)
{
// Fallback: usar las propiedades básicas de posición si la transformación no está disponible
float rectWidth = (float)rect.Width;
float rectHeight = (float)rect.Height;
if (double.IsNaN(rectWidth) || rectWidth == 0)
rectWidth = Ancho * PixelToMeter.Instance.calc.MetersToPixels(1);
if (double.IsNaN(rectHeight) || rectHeight == 0)
rectHeight = Alto * PixelToMeter.Instance.calc.MetersToPixels(1);
return (new Vector2(_canvasLeft, _canvasTop),
new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth),
_canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectHeight)));
}
// Forzar actualización del layout
_visualRepresentation.UpdateLayout();
// Verificar que el rectángulo sea un descendiente visual de _visualRepresentation
if (!IsVisualChild(rect, _visualRepresentation))
{
// Fallback: usar las dimensiones básicas
float rectWidth = (float)rect.ActualWidth;
float rectHeight = (float)rect.ActualHeight;
return (new Vector2(_canvasLeft, _canvasTop),
new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth),
_canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectHeight)));
}
var _canvasLeft = CanvasGetLeftinMeter();
var _canvasTop = CanvasGetTopinMeter();
// Obtiene la transformada del objeto visual
GeneralTransform transform = rect.TransformToAncestor(_visualRepresentation);
@ -1431,26 +1364,10 @@ namespace CtrEditor.ObjetosSim
Point topLeft = transform.Transform(new Point(0, 0));
Point bottomRight = transform.Transform(new Point(rect.ActualWidth, rect.ActualHeight));
return (new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.X) + _canvasLeft,
PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.Y) + _canvasTop),
new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.X) + _canvasLeft,
PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.Y) + _canvasTop));
}
catch (InvalidOperationException)
{
// Fallback en caso de error de transformación
float rectWidth = (float)rect.ActualWidth;
float rectHeight = (float)rect.ActualHeight;
if (double.IsNaN(rectWidth) || rectWidth == 0)
rectWidth = Ancho * PixelToMeter.Instance.calc.MetersToPixels(1);
if (double.IsNaN(rectHeight) || rectHeight == 0)
rectHeight = Alto * PixelToMeter.Instance.calc.MetersToPixels(1);
return (new Vector2(_canvasLeft, _canvasTop),
new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth),
_canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectHeight)));
return (new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.Y) + _canvasTop),
new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.Y) + _canvasTop));
}
else return (new Vector2(0, 0), new Vector2(0, 0));
}
public (Vector2 Start, Vector2 End) GetCenterLineVectors(Rectangle rect)
@ -1464,56 +1381,27 @@ namespace CtrEditor.ObjetosSim
// Usar Dispatcher para asegurar la ejecución en el hilo correcto
_visualRepresentation?.Dispatcher.Invoke(() =>
{
try
// Asegúrate de que el control está en el árbol visual y actualizado
if (_visualRepresentation.IsLoaded && rect.IsLoaded)
{
// Asegúrate de que el control está en el árbol visual y actualizado
if (_visualRepresentation.IsLoaded && rect.IsLoaded && IsVisualChild(rect, _visualRepresentation))
{
_visualRepresentation.UpdateLayout();
_visualRepresentation.UpdateLayout();
var _canvasLeft = CanvasGetLeftinMeter();
var _canvasTop = CanvasGetTopinMeter();
var transform = rect.TransformToAncestor(_visualRepresentation);
// Puntos en coordenadas locales del rectángulo no rotado
Point startLocal = new Point(0, rect.ActualHeight / 2);
Point endLocal = new Point(rect.ActualWidth, rect.ActualHeight / 2);
// Transformar estos puntos al sistema de coordenadas del ancestro
Point transformedStart = transform.Transform(startLocal);
Point transformedEnd = transform.Transform(endLocal);
// Convierte a unidades de Farseer (metros en este caso)
start = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.Y) + _canvasTop);
end = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.Y) + _canvasTop);
}
else
{
// Fallback: usar coordenadas básicas
var _canvasLeft = CanvasGetLeftinMeter();
var _canvasTop = CanvasGetTopinMeter();
float rectWidth = (float)rect.ActualWidth;
if (double.IsNaN(rectWidth) || rectWidth == 0)
rectWidth = Ancho * PixelToMeter.Instance.calc.MetersToPixels(1);
start = new Vector2(_canvasLeft, _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 2));
end = new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth), _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 2));
}
}
catch (InvalidOperationException)
{
// Fallback en caso de error de transformación
var _canvasLeft = CanvasGetLeftinMeter();
var _canvasTop = CanvasGetTopinMeter();
float rectWidth = (float)rect.ActualWidth;
if (double.IsNaN(rectWidth) || rectWidth == 0)
rectWidth = Ancho * PixelToMeter.Instance.calc.MetersToPixels(1);
var transform = rect.TransformToAncestor(_visualRepresentation);
start = new Vector2(_canvasLeft, _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 2));
end = new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth), _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 2));
// Puntos en coordenadas locales del rectángulo no rotado
Point startLocal = new Point(0, rect.ActualHeight / 2);
Point endLocal = new Point(rect.ActualWidth, rect.ActualHeight / 2);
// Transformar estos puntos al sistema de coordenadas del ancestro
Point transformedStart = transform.Transform(startLocal);
Point transformedEnd = transform.Transform(endLocal);
// Convierte a unidades de Farseer (metros en este caso)
start = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.Y) + _canvasTop);
end = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.Y) + _canvasTop);
}
});

View File

@ -1,57 +0,0 @@
<Window x:Class="CtrEditor.PopUps.RenameImageWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="Editar Información de Imagen" Height="400" Width="500"
WindowStartupLocation="CenterOwner" ResizeMode="CanResize">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Archivo original:" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBlock Grid.Row="1" x:Name="OriginalNameTextBlock" Margin="0,0,0,15" Foreground="Gray"/>
<GroupBox Grid.Row="2" Header="Propiedades de la Imagen" Margin="0,0,0,15">
<xctk:PropertyGrid x:Name="PropertyGridControl"
ShowSortOptions="False"
ShowSearchBox="False"
ShowSummary="True"
ShowAdvancedOptions="False"
ShowTitle="False"
Margin="5">
<xctk:PropertyGrid.EditorDefinitions>
<xctk:EditorTemplateDefinition TargetProperties="Etiquetas">
<xctk:EditorTemplateDefinition.EditingTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Stretch"/>
<Button Grid.Column="1"
Content="..."
Width="25"
Margin="2,0,0,0"
ToolTip="Abrir editor de etiquetas"
Click="TagEditorButton_Click"/>
</Grid>
</DataTemplate>
</xctk:EditorTemplateDefinition.EditingTemplate>
</xctk:EditorTemplateDefinition>
</xctk:PropertyGrid.EditorDefinitions>
</xctk:PropertyGrid>
</GroupBox>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="OkButton" Content="Aceptar" Width="80" Margin="0,0,10,0" IsDefault="True" Click="OkButton_Click"/>
<Button x:Name="CancelButton" Content="Cancelar" Width="80" IsCancel="True" Click="CancelButton_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -1,82 +0,0 @@
using System.Windows;
using CtrEditor.Models;
using CtrEditor.ObjetosSim;
using Xceed.Wpf.Toolkit.PropertyGrid;
namespace CtrEditor.PopUps
{
public partial class RenameImageWindow : Window
{
public ImageData ImageData { get; private set; }
public bool IsOkClicked { get; private set; }
public RenameImageWindow(string originalName, ImageData imageData = null)
{
InitializeComponent();
OriginalNameTextBlock.Text = originalName;
// Crear o usar ImageData existente
ImageData = imageData ?? new ImageData
{
FileName = originalName,
CustomName = string.Empty,
Etiquetas = string.Empty
};
// Configurar PropertyGrid
PropertyGridControl.SelectedObject = ImageData;
Focus();
}
private void TagEditorButton_Click(object sender, RoutedEventArgs e)
{
try
{
// Obtener el MainWindow para acceder a todos los objetos
var mainWindow = Application.Current.Windows
.OfType<MainWindow>()
.FirstOrDefault();
if (mainWindow?.DataContext is MainViewModel mainViewModel)
{
// Para ImageData, crear un osBase temporal para usar el editor
var tempOsBase = new osTextPlate();
tempOsBase.Etiquetas = ImageData.Etiquetas;
var tagEditor = new TagEditorWindow(tempOsBase, mainViewModel.ObjetosSimulables, mainViewModel._imageDataDictionary);
tagEditor.Owner = this;
if (tagEditor.ShowDialog() == true)
{
// Actualizar ImageData con las nuevas etiquetas
ImageData.Etiquetas = tempOsBase.Etiquetas;
// Forzar actualización del PropertyGrid
PropertyGridControl.Update();
}
}
}
catch (Exception ex)
{
MessageBox.Show($"Error al abrir el editor de etiquetas: {ex.Message}",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
IsOkClicked = true;
DialogResult = true;
Close();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
IsOkClicked = false;
DialogResult = false;
Close();
}
}
}

View File

@ -1,111 +0,0 @@
<Window x:Class="CtrEditor.PopUps.ScaleConfigWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:CtrEditor.Converters"
Title="Configurar Escala"
Height="280"
Width="450"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize">
<Window.Resources>
<converters:RegionalFloatConverter x:Key="RegionalFloatConverter"/>
</Window.Resources>
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Título -->
<TextBlock Grid.Row="0"
Text="Configuración de Escala de Conversión"
FontWeight="Bold"
FontSize="14"
Margin="0,0,0,15"/>
<!-- Escala actual -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="Escala actual: " VerticalAlignment="Center" Width="120"/>
<TextBlock VerticalAlignment="Center" FontWeight="Bold">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} m/pixel">
<Binding Path="CurrentScale" Converter="{StaticResource RegionalFloatConverter}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<!-- Nueva escala -->
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="Nueva escala: " VerticalAlignment="Center" Width="120"/>
<TextBox x:Name="ScaleTextBox"
Text="{Binding NewScale, Converter={StaticResource RegionalFloatConverter}, UpdateSourceTrigger=PropertyChanged}"
Width="100"
VerticalAlignment="Center"
Margin="0,0,5,0"/>
<TextBlock Text="m/pixel" VerticalAlignment="Center"/>
</StackPanel>
<!-- Presets -->
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="0,0,0,15">
<TextBlock Text="Presets comunes:" FontWeight="SemiBold" Margin="0,0,0,5"/>
<WrapPanel>
<Button Content="1:1 (0.01)" Command="{Binding SetPresetCommand}" CommandParameter="0.01" Margin="0,0,5,5"/>
<Button Content="1:10 (0.001)" Command="{Binding SetPresetCommand}" CommandParameter="0.001" Margin="0,0,5,5"/>
<Button Content="1:100 (0.0001)" Command="{Binding SetPresetCommand}" CommandParameter="0.0001" Margin="0,0,5,5"/>
<Button Content="1 px = 1 cm (0.01)" Command="{Binding SetPresetCommand}" CommandParameter="0.01" Margin="0,0,5,5"/>
<Button Content="1 px = 1 mm (0.001)" Command="{Binding SetPresetCommand}" CommandParameter="0.001" Margin="0,0,5,5"/>
</WrapPanel>
</StackPanel>
<!-- Información de ayuda -->
<Border Grid.Row="5"
Background="LightYellow"
BorderBrush="Orange"
BorderThickness="1"
Padding="10"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="Información:" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBlock TextWrapping="Wrap">
<Run Text="La escala define cuántos metros representa cada píxel en el canvas."/>
<LineBreak/>
<Run Text="• Valores menores = objetos más pequeños en pantalla"/>
<LineBreak/>
<Run Text="• Valores mayores = objetos más grandes en pantalla"/>
<LineBreak/>
<Run Text="• Ejemplo: 0.01 significa que 1 píxel = 1 centímetro"/>
<LineBreak/>
<Run Text="• Los cambios se aplican automáticamente después de 0.5 segundos"/>
</TextBlock>
</StackPanel>
</Border>
<!-- Botones -->
<StackPanel Grid.Row="6"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Aplicar"
Command="{Binding ApplyCommand}"
Width="80"
Height="30"
Margin="0,0,10,0"/>
<Button Content="Aceptar"
Command="{Binding AcceptCommand}"
Width="80"
Height="30"
Margin="0,0,10,0"
IsDefault="True"/>
<Button Content="Cancelar"
Command="{Binding CancelCommand}"
Width="80"
Height="30"
IsCancel="True"/>
</StackPanel>
</Grid>
</Window>

View File

@ -1,146 +0,0 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace CtrEditor.PopUps
{
public partial class ScaleConfigWindow : Window
{
public ScaleConfigWindow(float currentScale, MainViewModel mainViewModel)
{
InitializeComponent();
DataContext = new ScaleConfigViewModel(currentScale, this, mainViewModel);
}
public float NewScale => ((ScaleConfigViewModel)DataContext).NewScale;
}
public partial class ScaleConfigViewModel : ObservableObject
{
private readonly ScaleConfigWindow _window;
private readonly MainViewModel _mainViewModel;
private float _originalScale;
private DispatcherTimer _autoApplyTimer;
[ObservableProperty]
private float currentScale;
[ObservableProperty]
private float newScale;
public ICommand SetPresetCommand { get; }
public ICommand AcceptCommand { get; }
public ICommand CancelCommand { get; }
public ICommand ApplyCommand { get; }
public ScaleConfigViewModel(float currentScale, ScaleConfigWindow window, MainViewModel mainViewModel)
{
_window = window;
_mainViewModel = mainViewModel;
_originalScale = currentScale;
CurrentScale = currentScale;
NewScale = currentScale;
// Inicializar timer para aplicación automática
_autoApplyTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(500) // 500ms de delay
};
_autoApplyTimer.Tick += (s, e) =>
{
_autoApplyTimer.Stop();
if (NewScale > 0 && NewScale != CurrentScale)
{
Apply();
}
};
SetPresetCommand = new RelayCommand<object>(SetPreset);
AcceptCommand = new RelayCommand(Accept, CanAccept);
CancelCommand = new RelayCommand(Cancel);
ApplyCommand = new RelayCommand(Apply, CanAccept);
}
private void SetPreset(object parameter)
{
if (parameter != null && float.TryParse(parameter.ToString(),
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture,
out float presetValue))
{
NewScale = presetValue;
}
}
private bool CanAccept()
{
return NewScale > 0; // Solo verificar que sea positivo
}
private void Accept()
{
if (NewScale <= 0)
{
MessageBox.Show("La escala debe ser un valor positivo mayor que cero.",
"Error de Validación",
MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
// Aplicar la escala una última vez antes de cerrar
Apply();
// Cerrar la ventana
_window.Close();
}
private void Apply()
{
if (NewScale <= 0)
{
MessageBox.Show("La escala debe ser un valor positivo mayor que cero.",
"Error de Validación",
MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
// Aplicar la escala inmediatamente
_mainViewModel.ApplyScale(NewScale);
// Actualizar la escala actual para reflejar el cambio
CurrentScale = NewScale;
}
private void Cancel()
{
// Restaurar la escala original
_mainViewModel.ApplyScale(_originalScale);
// Cerrar la ventana
_window.Close();
}
partial void OnNewScaleChanged(float value)
{
// Forzar la re-evaluación del comando Accept cuando el valor cambia
CommandManager.InvalidateRequerySuggested();
// Reiniciar el timer para aplicación automática (verificar que esté inicializado)
if (_autoApplyTimer != null)
{
_autoApplyTimer.Stop();
if (value > 0 && value != CurrentScale)
{
_autoApplyTimer.Start();
}
}
}
}
}

View File

@ -5,7 +5,6 @@ using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CtrEditor.ObjetosSim;
using CtrEditor.Models;
namespace CtrEditor.PopUps
{
@ -14,10 +13,10 @@ namespace CtrEditor.PopUps
/// </summary>
public partial class TagEditorWindow : Window
{
public TagEditorWindow(osBase objeto, IEnumerable<osBase> todosLosObjetos, Dictionary<string, ImageData> imageDataDictionary = null)
public TagEditorWindow(osBase objeto, IEnumerable<osBase> todosLosObjetos)
{
InitializeComponent();
DataContext = new TagEditorViewModel(objeto, todosLosObjetos, this, imageDataDictionary);
DataContext = new TagEditorViewModel(objeto, todosLosObjetos, this);
}
private void TxtNuevaEtiqueta_KeyDown(object sender, KeyEventArgs e)
@ -47,7 +46,6 @@ namespace CtrEditor.PopUps
{
private readonly osBase _objeto;
private readonly IEnumerable<osBase> _todosLosObjetos;
private readonly Dictionary<string, ImageData> _imageDataDictionary;
private readonly TagEditorWindow _window;
private readonly List<string> _etiquetasOriginales;
@ -66,11 +64,10 @@ namespace CtrEditor.PopUps
public ICommand AplicarCommand { get; }
public ICommand CancelarCommand { get; }
public TagEditorViewModel(osBase objeto, IEnumerable<osBase> todosLosObjetos, TagEditorWindow window, Dictionary<string, ImageData> imageDataDictionary = null)
public TagEditorViewModel(osBase objeto, IEnumerable<osBase> todosLosObjetos, TagEditorWindow window)
{
_objeto = objeto;
_todosLosObjetos = todosLosObjetos;
_imageDataDictionary = imageDataDictionary;
_window = window;
// Guardar las etiquetas originales para poder cancelar
@ -114,22 +111,9 @@ namespace CtrEditor.PopUps
var todasLasEtiquetas = _todosLosObjetos
.SelectMany(obj => obj.ListaEtiquetas)
.Distinct()
.OrderBy(tag => tag)
.ToList();
// Agregar etiquetas de las imágenes si está disponible el diccionario
if (_imageDataDictionary != null)
{
var etiquetasImagenes = _imageDataDictionary.Values
.SelectMany(imageData => imageData.ListaEtiquetas)
.Distinct();
todasLasEtiquetas.AddRange(etiquetasImagenes);
todasLasEtiquetas = todasLasEtiquetas.Distinct().ToList();
}
// Ordenar todas las etiquetas
todasLasEtiquetas = todasLasEtiquetas.OrderBy(tag => tag).ToList();
// Mostrar solo las que no están en el objeto actual
var etiquetasObjetoActual = _objeto.ListaEtiquetas;
foreach (var tag in todasLasEtiquetas.Except(etiquetasObjetoActual))

View File

@ -50,7 +50,7 @@ namespace CtrEditor.Serialization
obj.RestaurarDesdeSnapshotGlobal();
}
// Prepara para serializaci<EFBFBD>n
// Prepara para serialización
obj.SalvarDatosNoSerializables();
// Separa objetos globales y locales
@ -64,14 +64,13 @@ namespace CtrEditor.Serialization
}
}
// Paso 2: Guardar datos de la p<EFBFBD>gina actual
// Paso 2: Guardar datos de la página actual
var dataToSerialize = new SimulationData
{
ObjetosSimulables = objetosSimulables.Count > 0 ? objetosSimulables : null,
UnitConverter = PixelToMeter.Instance.calc,
PLC_ConnectionData = _mainViewModel.PLCViewModel,
DatosLocalesObjetos = datosLocalesObjetos.Count > 0 ? datosLocalesObjetos : null,
ImageDataDictionary = _mainViewModel._imageDataDictionary.Count > 0 ? _mainViewModel._imageDataDictionary : null
DatosLocalesObjetos = datosLocalesObjetos.Count > 0 ? datosLocalesObjetos : null
};
var path = _datosDeTrabajo.ObtenerPathImagenConExtension(selectedImage, ".json");
@ -113,11 +112,11 @@ namespace CtrEditor.Serialization
// Paso 1: Cargar objetos globales
LoadAllPagesState(settings);
// Paso 2: Cargar datos de la p<EFBFBD>gina actual
// Paso 2: Cargar datos de la página actual
var simulationData = LoadCurrentPageState(selectedImage, settings);
// Paso 3: Crear snapshots de los objetos globales
// Nota: No filtramos por Enable_Local_Data aqu<EFBFBD> ya que se establecer<65> despu<70>s
// Nota: No filtramos por Enable_Local_Data aquí ya que se establecerá después
foreach (var obj in _mainViewModel.ObjetosSimulables)
{
if (obj.Enable_On_All_Pages)
@ -135,7 +134,7 @@ namespace CtrEditor.Serialization
obj.Enable_Local_Data = false;
}
// Luego aplicar los datos locales, esto activar<EFBFBD> Enable_Local_Data autom<6F>ticamente
// Luego aplicar los datos locales, esto activará Enable_Local_Data automáticamente
foreach (var datosLocales in simulationData.DatosLocalesObjetos)
{
var objetoGlobal = _mainViewModel.ObjetosSimulables.FirstOrDefault(
@ -187,31 +186,7 @@ namespace CtrEditor.Serialization
else
_mainViewModel.PLCViewModel = new PLCViewModel();
// Solo sobrescribir el UnitConverter si existe uno guardado
if (simulationData.UnitConverter != null)
PixelToMeter.Instance.calc = simulationData.UnitConverter;
// Cargar datos de imágenes
if (simulationData.ImageDataDictionary != null)
{
_mainViewModel._imageDataDictionary.Clear();
foreach (var imageDataEntry in simulationData.ImageDataDictionary)
{
_mainViewModel._imageDataDictionary[imageDataEntry.Key] = imageDataEntry.Value;
}
}
// Compatibilidad con versiones anteriores (ImageCustomNames)
#pragma warning disable CS0618 // Type or member is obsolete
if (simulationData.ImageCustomNames != null)
{
foreach (var customName in simulationData.ImageCustomNames)
{
var imageData = _mainViewModel.GetOrCreateImageData(customName.Key);
imageData.CustomName = customName.Value;
}
}
#pragma warning restore CS0618
PixelToMeter.Instance.calc = simulationData.UnitConverter;
}
}
return simulationData;

View File

@ -314,7 +314,7 @@ namespace CtrEditor
{
if (values.Length == 2 && values[0] is float value1 && values[1] is float value2)
{
return (double)(value1 + value2);
return (double) (value1 + value2);
}
return DependencyProperty.UnsetValue;
}
@ -435,14 +435,7 @@ namespace CtrEditor
{
// Instancia privada estática, parte del patrón Singleton
private static PixelToMeter? _instance;
public UnitConverter calc;
// Constructor privado para el patrón Singleton
private PixelToMeter()
{
// Solo inicializar con valor por defecto si no se ha configurado antes
calc = new UnitConverter(0.01f);
}
public UnitConverter calc = new UnitConverter(0.01f);
// Propiedad pública estática para acceder a la instancia
public static PixelToMeter Instance