Compare commits
7 Commits
9b710fcb00
...
75c507be4e
Author | SHA1 | Date |
---|---|---|
|
75c507be4e | |
|
fefc0a700d | |
|
58781c13a3 | |
|
b48dbeb76e | |
|
ca70f66ff1 | |
|
354b4a8acf | |
|
909e438f5b |
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -118,8 +118,7 @@ namespace CtrEditor
|
||||||
_mainViewModel.PLCViewModel = _globalState.PLCConfiguration ?? new PLCViewModel();
|
_mainViewModel.PLCViewModel = _globalState.PLCConfiguration ?? new PLCViewModel();
|
||||||
if (_globalState.UnitConverter != null)
|
if (_globalState.UnitConverter != null)
|
||||||
PixelToMeter.Instance.calc = _globalState.UnitConverter;
|
PixelToMeter.Instance.calc = _globalState.UnitConverter;
|
||||||
else
|
// No crear un nuevo UnitConverter si no hay uno guardado, mantener el actual
|
||||||
PixelToMeter.Instance.calc = new UnitConverter(1.0f); // Valor por defecto
|
|
||||||
|
|
||||||
// Restaurar objetos globales
|
// Restaurar objetos globales
|
||||||
foreach (var obj in _globalState.SharedObjects)
|
foreach (var obj in _globalState.SharedObjects)
|
||||||
|
|
|
@ -5,12 +5,14 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CtrEditor
|
namespace CtrEditor
|
||||||
{
|
{
|
||||||
public class DatosDeTrabajo
|
public class DatosDeTrabajo
|
||||||
{
|
{
|
||||||
public Dictionary<string, string> Imagenes { get; private set; }
|
public Dictionary<string, string> Imagenes { get; private set; }
|
||||||
|
private MainViewModel _mainViewModel;
|
||||||
|
|
||||||
public DatosDeTrabajo()
|
public DatosDeTrabajo()
|
||||||
{
|
{
|
||||||
|
@ -18,6 +20,11 @@ namespace CtrEditor
|
||||||
CargarImagenes(); // Inicializar la carga de imágenes basada en el directorio persistente
|
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)
|
public string ObtenerPathImagenConExtension(string key, string extension)
|
||||||
{
|
{
|
||||||
if (Imagenes.TryGetValue(key, out string imagePath))
|
if (Imagenes.TryGetValue(key, out string imagePath))
|
||||||
|
@ -51,7 +58,85 @@ namespace CtrEditor
|
||||||
Imagenes[nombreArchivo] = archivo;
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
152
MainViewModel.cs
152
MainViewModel.cs
|
@ -81,6 +81,12 @@ namespace CtrEditor
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public ObservableCollection<TipoSimulable> listaOsBase;
|
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 StartSimulationCommand { get; }
|
||||||
public ICommand StopSimulationCommand { get; }
|
public ICommand StopSimulationCommand { get; }
|
||||||
public ICommand ItemDoubleClickCommand { get; private set; }
|
public ICommand ItemDoubleClickCommand { get; private set; }
|
||||||
|
@ -383,6 +389,7 @@ namespace CtrEditor
|
||||||
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
|
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
|
||||||
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
|
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
|
||||||
TBLibraryManagerCommand = new RelayCommand(ShowLibraryManager);
|
TBLibraryManagerCommand = new RelayCommand(ShowLibraryManager);
|
||||||
|
RenameImageCommand = new RelayCommand<string>(RenameImage);
|
||||||
|
|
||||||
stopwatch_Sim = new Stopwatch();
|
stopwatch_Sim = new Stopwatch();
|
||||||
stopwatch_Sim.Start();
|
stopwatch_Sim.Start();
|
||||||
|
@ -398,6 +405,73 @@ namespace CtrEditor
|
||||||
|
|
||||||
_stateManager = new StateManager(EstadoPersistente.Instance.directorio, this);
|
_stateManager = new StateManager(EstadoPersistente.Instance.directorio, this);
|
||||||
_stateSerializer = new StateSerializer(this, datosDeTrabajo, simulationManager);
|
_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
|
private void OnVisFilterChanged(osVisFilterViewModel filter) // Changed signature to accept viewmodel directly
|
||||||
|
@ -1162,6 +1236,77 @@ namespace CtrEditor
|
||||||
OnPropertyChanged(nameof(SelectedItemOsList));
|
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
|
// Diccionario para manejar ventanas de biblioteca múltiples
|
||||||
private Dictionary<string, PopUps.LibraryWindow> _libraryWindows = new();
|
private Dictionary<string, PopUps.LibraryWindow> _libraryWindows = new();
|
||||||
|
|
||||||
|
@ -1202,6 +1347,13 @@ namespace CtrEditor
|
||||||
|
|
||||||
// Nueva propiedad para almacenar los datos locales de objetos globales
|
// Nueva propiedad para almacenar los datos locales de objetos globales
|
||||||
public ObservableCollection<DatosLocales>? DatosLocalesObjetos { get; set; }
|
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
|
public class TipoSimulable
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
xmlns:local="clr-namespace:CtrEditor"
|
xmlns:local="clr-namespace:CtrEditor"
|
||||||
xmlns:controls="clr-namespace:CtrEditor.Controls"
|
xmlns:controls="clr-namespace:CtrEditor.Controls"
|
||||||
xmlns:uc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
|
xmlns:uc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
|
||||||
|
xmlns:converters="clr-namespace:CtrEditor.Converters"
|
||||||
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
|
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
|
||||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim"
|
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
|
@ -48,6 +49,9 @@
|
||||||
<!-- Converter for PLC Connect/Disconnect image -->
|
<!-- Converter for PLC Connect/Disconnect image -->
|
||||||
<local:ConnectStateToImageConverter x:Key="ConnectStateToImageConverter" />
|
<local:ConnectStateToImageConverter x:Key="ConnectStateToImageConverter" />
|
||||||
|
|
||||||
|
<!-- Converter for Image Display Names -->
|
||||||
|
<converters:ImageDisplayNameConverter x:Key="ImageDisplayNameConverter" />
|
||||||
|
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
|
@ -87,7 +91,18 @@
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<ListBox x:Name="ListaImagenes" Grid.Row="0" Margin="5" ItemsSource="{Binding ListaImagenes}"
|
<ListBox x:Name="ListaImagenes" Grid.Row="0" Margin="5" ItemsSource="{Binding ListaImagenes}"
|
||||||
SelectedItem="{Binding SelectedImage}" />
|
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>
|
||||||
<ListBox x:Name="ListaFunciones" Grid.Row="1" Margin="5" ItemsSource="{Binding ListaOsBase}"
|
<ListBox x:Name="ListaFunciones" Grid.Row="1" Margin="5" ItemsSource="{Binding ListaOsBase}"
|
||||||
DisplayMemberPath="Nombre" SelectedItem="{Binding SelectedItem}">
|
DisplayMemberPath="Nombre" SelectedItem="{Binding SelectedItem}">
|
||||||
<i:Interaction.Triggers>
|
<i:Interaction.Triggers>
|
||||||
|
|
|
@ -328,7 +328,7 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
// Siempre trabajar con selección única para las propiedades
|
// Siempre trabajar con selección única para las propiedades
|
||||||
CargarPropiedadesosDatos(selectedObject);
|
CargarPropiedadesosDatos(selectedObject);
|
||||||
|
|
||||||
_objectManager.SelectObject(selectedObject);
|
_objectManager.SelectObject(selectedObject);
|
||||||
}
|
}
|
||||||
else if (e.RemovedItems.Count > 0 && e.AddedItems.Count == 0)
|
else if (e.RemovedItems.Count > 0 && e.AddedItems.Count == 0)
|
||||||
|
@ -414,7 +414,7 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
// Capturar estado antes de mover
|
// Capturar estado antes de mover
|
||||||
_objectManager.CaptureUndoState();
|
_objectManager.CaptureUndoState();
|
||||||
|
|
||||||
// Mover todos los objetos primero
|
// Mover todos los objetos primero
|
||||||
foreach (var obj in _objectManager.SelectedObjects)
|
foreach (var obj in _objectManager.SelectedObjects)
|
||||||
{
|
{
|
||||||
|
@ -472,7 +472,7 @@ namespace CtrEditor
|
||||||
if (DataContext is MainViewModel viewModel)
|
if (DataContext is MainViewModel viewModel)
|
||||||
{
|
{
|
||||||
viewModel.CargarPropiedadesosDatos(selectedObject, PanelEdicion, Resources);
|
viewModel.CargarPropiedadesosDatos(selectedObject, PanelEdicion, Resources);
|
||||||
|
|
||||||
// If no object is selected, make sure to clear the properties panel
|
// If no object is selected, make sure to clear the properties panel
|
||||||
if (selectedObject == null)
|
if (selectedObject == null)
|
||||||
{
|
{
|
||||||
|
@ -712,12 +712,19 @@ namespace CtrEditor
|
||||||
contextMenu.Items.Add(lockSubmenu);
|
contextMenu.Items.Add(lockSubmenu);
|
||||||
contextMenu.Items.Add(new Separator());
|
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
|
// Agregar información del sistema de undo
|
||||||
var undoHistoryCount = _objectManager.GetUndoHistoryCount();
|
var undoHistoryCount = _objectManager.GetUndoHistoryCount();
|
||||||
var canUndo = _objectManager.CanUndo();
|
var canUndo = _objectManager.CanUndo();
|
||||||
var undoInfoItem = new MenuItem
|
var undoInfoItem = new MenuItem
|
||||||
{
|
{
|
||||||
Header = $"Undo: {undoHistoryCount}/3 estados ({(canUndo ? "Ctrl+Z disponible" : "No disponible")})",
|
Header = $"Undo: {undoHistoryCount}/3 estados ({(canUndo ? "Ctrl+Z disponible" : "No disponible")})",
|
||||||
IsEnabled = false,
|
IsEnabled = false,
|
||||||
FontStyle = FontStyles.Italic
|
FontStyle = FontStyles.Italic
|
||||||
|
@ -820,7 +827,7 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
// Usar la misma lógica que DuplicarObjeto pero solo para crear copias para serialización
|
// Usar la misma lógica que DuplicarObjeto pero solo para crear copias para serialización
|
||||||
var objectsCopy = new List<osBase>();
|
var objectsCopy = new List<osBase>();
|
||||||
|
|
||||||
foreach (var originalObj in _objectManager.SelectedObjects)
|
foreach (var originalObj in _objectManager.SelectedObjects)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -854,7 +861,7 @@ namespace CtrEditor
|
||||||
|
|
||||||
string jsonString = JsonConvert.SerializeObject(objectsCopy, settings);
|
string jsonString = JsonConvert.SerializeObject(objectsCopy, settings);
|
||||||
Clipboard.SetText(jsonString);
|
Clipboard.SetText(jsonString);
|
||||||
|
|
||||||
Console.WriteLine($"Copiados {objectsCopy.Count} objeto(s) al portapapeles");
|
Console.WriteLine($"Copiados {objectsCopy.Count} objeto(s) al portapapeles");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -899,7 +906,7 @@ namespace CtrEditor
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string jsonString = Clipboard.GetText();
|
string jsonString = Clipboard.GetText();
|
||||||
|
|
||||||
// Validación básica del JSON
|
// Validación básica del JSON
|
||||||
if (string.IsNullOrWhiteSpace(jsonString) || (!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
|
if (string.IsNullOrWhiteSpace(jsonString) || (!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
|
||||||
{
|
{
|
||||||
|
@ -984,11 +991,11 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
// Generar nuevo ID y actualizar nombre (igual que en DuplicarObjeto)
|
// Generar nuevo ID y actualizar nombre (igual que en DuplicarObjeto)
|
||||||
obj.Id.ObtenerNuevaID();
|
obj.Id.ObtenerNuevaID();
|
||||||
|
|
||||||
string nombre = Regex.IsMatch(obj.Nombre, @"_\d+$")
|
string nombre = Regex.IsMatch(obj.Nombre, @"_\d+$")
|
||||||
? Regex.Replace(obj.Nombre, @"_\d+$", $"_{obj.Id.Value}")
|
? Regex.Replace(obj.Nombre, @"_\d+$", $"_{obj.Id.Value}")
|
||||||
: obj.Nombre + "_" + obj.Id.Value;
|
: obj.Nombre + "_" + obj.Id.Value;
|
||||||
|
|
||||||
obj.Nombre = nombre;
|
obj.Nombre = nombre;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1005,7 +1012,7 @@ namespace CtrEditor
|
||||||
viewModel.ObjetosSimulables.Add(obj);
|
viewModel.ObjetosSimulables.Add(obj);
|
||||||
viewModel.CrearUserControlDesdeObjetoSimulable(obj);
|
viewModel.CrearUserControlDesdeObjetoSimulable(obj);
|
||||||
viewModel.HasUnsavedChanges = true;
|
viewModel.HasUnsavedChanges = true;
|
||||||
|
|
||||||
newlyPastedObjects.Add(obj);
|
newlyPastedObjects.Add(obj);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -1017,10 +1024,10 @@ namespace CtrEditor
|
||||||
// Usar la misma lógica de selección que DuplicarUserControl
|
// Usar la misma lógica de selección que DuplicarUserControl
|
||||||
// Limpiar selección antes de seleccionar los nuevos objetos
|
// Limpiar selección antes de seleccionar los nuevos objetos
|
||||||
_objectManager.ClearSelection();
|
_objectManager.ClearSelection();
|
||||||
|
|
||||||
// Forzar actualización completa del layout
|
// Forzar actualización completa del layout
|
||||||
ImagenEnTrabajoCanvas.UpdateLayout();
|
ImagenEnTrabajoCanvas.UpdateLayout();
|
||||||
|
|
||||||
// Usar dispatcher con la misma prioridad que DuplicarUserControl
|
// Usar dispatcher con la misma prioridad que DuplicarUserControl
|
||||||
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
|
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
|
||||||
{
|
{
|
||||||
|
@ -1031,7 +1038,7 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
double left = Canvas.GetLeft(newObj.VisualRepresentation);
|
double left = Canvas.GetLeft(newObj.VisualRepresentation);
|
||||||
double top = Canvas.GetTop(newObj.VisualRepresentation);
|
double top = Canvas.GetTop(newObj.VisualRepresentation);
|
||||||
|
|
||||||
// Solo añadir a selección si el objeto tiene coordenadas válidas
|
// 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))
|
if (!double.IsNaN(left) && !double.IsNaN(top) && !double.IsInfinity(left) && !double.IsInfinity(top))
|
||||||
{
|
{
|
||||||
|
@ -1039,10 +1046,10 @@ namespace CtrEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forzar otra actualización del layout antes de actualizar visuales
|
// Forzar otra actualización del layout antes de actualizar visuales
|
||||||
ImagenEnTrabajoCanvas.UpdateLayout();
|
ImagenEnTrabajoCanvas.UpdateLayout();
|
||||||
|
|
||||||
// Actualizar SelectedItemOsList si hay objetos pegados
|
// Actualizar SelectedItemOsList si hay objetos pegados
|
||||||
if (newlyPastedObjects.Count > 0)
|
if (newlyPastedObjects.Count > 0)
|
||||||
{
|
{
|
||||||
|
@ -1051,13 +1058,13 @@ namespace CtrEditor
|
||||||
vm.SelectedItemOsList = newlyPastedObjects.LastOrDefault();
|
vm.SelectedItemOsList = newlyPastedObjects.LastOrDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar visuales de selección
|
// Actualizar visuales de selección
|
||||||
_objectManager.UpdateSelectionVisuals();
|
_objectManager.UpdateSelectionVisuals();
|
||||||
|
|
||||||
Console.WriteLine($"Pegados y seleccionados {newlyPastedObjects.Count} objeto(s)");
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1075,9 +1082,9 @@ namespace CtrEditor
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string jsonString = Clipboard.GetText();
|
string jsonString = Clipboard.GetText();
|
||||||
|
|
||||||
// Validación básica del JSON
|
// Validación básica del JSON
|
||||||
if (string.IsNullOrWhiteSpace(jsonString) ||
|
if (string.IsNullOrWhiteSpace(jsonString) ||
|
||||||
(!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
|
(!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -202,7 +202,7 @@ namespace CtrEditor
|
||||||
PurgeDeletedObjects();
|
PurgeDeletedObjects();
|
||||||
// Asegurarse de que el canvas haya actualizado su layout
|
// Asegurarse de que el canvas haya actualizado su layout
|
||||||
_canvas.UpdateLayout();
|
_canvas.UpdateLayout();
|
||||||
|
|
||||||
RemoveResizeRectangles();
|
RemoveResizeRectangles();
|
||||||
if (_selectedObjects.Any())
|
if (_selectedObjects.Any())
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
return "Transporte";
|
return "Transporte";
|
||||||
}
|
}
|
||||||
private string nombre = "Transporte TTOP";
|
private string nombre = "Transporte TTOP";
|
||||||
|
|
||||||
[property: Category("Id:")]
|
[property: Category("Id:")]
|
||||||
public override string Nombre
|
public override string Nombre
|
||||||
{
|
{
|
||||||
|
@ -77,7 +77,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
|
[property: ItemsSource(typeof(osBaseItemsSource<osVMmotorSim>))]
|
||||||
string id_Motor;
|
string id_Motor;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private PropertyChangedEventHandler motorPropertyChangedHandler;
|
private PropertyChangedEventHandler motorPropertyChangedHandler;
|
||||||
|
|
||||||
partial void OnId_MotorChanged(string value)
|
partial void OnId_MotorChanged(string value)
|
||||||
|
@ -169,8 +169,54 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
if (_visualRepresentation is ucTransporteTTop uc)
|
if (_visualRepresentation is ucTransporteTTop uc)
|
||||||
{
|
{
|
||||||
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
|
try
|
||||||
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
|
{
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public override void ucUnLoaded()
|
public override void ucUnLoaded()
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
using CtrEditor.FuncionesBase;
|
using CtrEditor.FuncionesBase;
|
||||||
using LibS7Adv;
|
using LibS7Adv;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -59,6 +61,16 @@ namespace CtrEditor.ObjetosSim
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public float tiempoRampa;
|
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)
|
partial void OnTiempoRampaChanged(float value)
|
||||||
{
|
{
|
||||||
if (value < 0.1f)
|
if (value < 0.1f)
|
||||||
|
@ -204,9 +216,14 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
// Add timestamp to trace when the read occurs
|
// Add timestamp to trace when the read occurs
|
||||||
var timestamp = DateTime.Now;
|
var timestamp = DateTime.Now;
|
||||||
|
|
||||||
// Read ControlWord and track the raw response
|
if (Data.Motor_With_Encoder)
|
||||||
var rawResponse = plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ControlWord");
|
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");
|
||||||
int controlWord = rawResponse ?? 0;
|
int controlWord = rawResponse ?? 0;
|
||||||
var control = VMMotorBitPacker.UnpackControlWord(controlWord);
|
var control = VMMotorBitPacker.UnpackControlWord(controlWord);
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
public partial class osEncoderMotor : osBase, IosBase
|
public partial class osEncoderMotor : osBase, IosBase
|
||||||
{
|
{
|
||||||
private osBase Motor = null;
|
private osBase Motor = null;
|
||||||
private Stopwatch Stopwatch = new Stopwatch();
|
|
||||||
private double stopwatch_last = 0;
|
|
||||||
|
|
||||||
public static string NombreClase()
|
public static string NombreClase()
|
||||||
{
|
{
|
||||||
|
@ -86,7 +84,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
Pulsos_Por_Vuelta = 360; // Por defecto, un pulso por grado
|
Pulsos_Por_Vuelta = 360; // Por defecto, un pulso por grado
|
||||||
Ratio_Giros_50hz = 1; // Por defecto, 1 giro por cada 50Hz
|
Ratio_Giros_50hz = 1; // Por defecto, 1 giro por cada 50Hz
|
||||||
Color_oculto = Brushes.Gray;
|
Color_oculto = Brushes.Gray;
|
||||||
Stopwatch.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||||
|
|
|
@ -14,8 +14,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
public partial class osEncoderMotorLineal : osBase, IosBase
|
public partial class osEncoderMotorLineal : osBase, IosBase
|
||||||
{
|
{
|
||||||
private osBase Motor = null;
|
private osBase Motor = null;
|
||||||
private Stopwatch Stopwatch = new Stopwatch();
|
|
||||||
private double stopwatch_last = 0;
|
|
||||||
|
|
||||||
public static string NombreClase()
|
public static string NombreClase()
|
||||||
{
|
{
|
||||||
|
@ -86,7 +84,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
Pulsos_Por_Hz = 3000; // Por defecto, un pulso por grado
|
Pulsos_Por_Hz = 3000; // Por defecto, un pulso por grado
|
||||||
Color_oculto = Brushes.Gray;
|
Color_oculto = Brushes.Gray;
|
||||||
Stopwatch.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||||
|
@ -100,7 +97,8 @@ namespace CtrEditor.ObjetosSim
|
||||||
Valor_Actual = (int)value;
|
Valor_Actual = (int)value;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (Motor != null && Motor is osVMmotorSim motor)
|
}
|
||||||
|
else if (Motor != null && Motor is osVMmotorSim motor)
|
||||||
{
|
{
|
||||||
VelocidadActual = motor.Velocidad;
|
VelocidadActual = motor.Velocidad;
|
||||||
|
|
||||||
|
@ -116,7 +114,10 @@ namespace CtrEditor.ObjetosSim
|
||||||
float incrementoPulsos = pulsosPorHz * segundosTranscurridos;
|
float incrementoPulsos = pulsosPorHz * segundosTranscurridos;
|
||||||
|
|
||||||
// Actualizar valor del encoder
|
// Actualizar valor del encoder
|
||||||
Valor_Actual = (Valor_Actual + incrementoPulsos);
|
if (motor.Motor_With_Encoder)
|
||||||
|
Valor_Actual = motor.Actual_Position;
|
||||||
|
else
|
||||||
|
Valor_Actual = (Valor_Actual + incrementoPulsos);
|
||||||
|
|
||||||
// Actualizar color basado en si está girando
|
// Actualizar color basado en si está girando
|
||||||
Color_oculto = Math.Abs(pulsosPorHz) > 0.01f ? Brushes.LightGreen : Brushes.Gray;
|
Color_oculto = Math.Abs(pulsosPorHz) > 0.01f ? Brushes.LightGreen : Brushes.Gray;
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Windows.Data;
|
||||||
using CtrEditor.PopUps;
|
using CtrEditor.PopUps;
|
||||||
using Xceed.Wpf.Toolkit.PropertyGrid;
|
using Xceed.Wpf.Toolkit.PropertyGrid;
|
||||||
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
|
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
|
||||||
|
using CtrEditor.Models;
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
|
@ -50,18 +51,18 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Obtener el objeto que se está editando
|
// Obtener el MainWindow para acceder a todos los objetos
|
||||||
if (propertyItem.Instance is osBase osObject)
|
var mainWindow = Application.Current.Windows
|
||||||
{
|
.OfType<MainWindow>()
|
||||||
// Obtener el MainWindow para acceder a todos los objetos
|
.FirstOrDefault();
|
||||||
var mainWindow = Application.Current.Windows
|
|
||||||
.OfType<MainWindow>()
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (mainWindow?.DataContext is MainViewModel mainViewModel)
|
if (mainWindow?.DataContext is MainViewModel mainViewModel)
|
||||||
|
{
|
||||||
|
// Determinar si el objeto es osBase o ImageData
|
||||||
|
if (propertyItem.Instance is osBase osObject)
|
||||||
{
|
{
|
||||||
// Abrir el editor de etiquetas
|
// Abrir el editor de etiquetas para osBase
|
||||||
var tagEditor = new TagEditorWindow(osObject, mainViewModel.ObjetosSimulables);
|
var tagEditor = new TagEditorWindow(osObject, mainViewModel.ObjetosSimulables, mainViewModel._imageDataDictionary);
|
||||||
tagEditor.Owner = mainWindow;
|
tagEditor.Owner = mainWindow;
|
||||||
|
|
||||||
if (tagEditor.ShowDialog() == true)
|
if (tagEditor.ShowDialog() == true)
|
||||||
|
@ -71,6 +72,22 @@ namespace CtrEditor.ObjetosSim
|
||||||
textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
|
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)
|
catch (Exception ex)
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
<UserControl x:Class="CtrEditor.ObjetosSim.ucBoolTag"
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucBoolTag" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
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:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:ei="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ei="http://schemas.microsoft.com/xaml/behaviors"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
mc:Ignorable="d">
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<vm:osBoolTag/>
|
<vm:osBoolTag Show_Description="True" />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
<Canvas RenderTransformOrigin="0.5,0.5">
|
<Canvas RenderTransformOrigin="0.5,0.5" ToolTip="{Binding Descripcion}">
|
||||||
<Canvas.RenderTransform>
|
<Canvas.RenderTransform>
|
||||||
<TransformGroup>
|
<TransformGroup>
|
||||||
<ScaleTransform />
|
<ScaleTransform />
|
||||||
|
@ -26,18 +23,14 @@
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Border x:Name="BackgroundRectangle"
|
<Border x:Name="BackgroundRectangle" BorderBrush="Black" BorderThickness="2" CornerRadius="10,0,0,10"
|
||||||
BorderBrush="Black"
|
Width="16" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
BorderThickness="2"
|
Background="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" />
|
||||||
CornerRadius="10,0,0,10"
|
<Label Content="{Binding Descripcion}" Grid.Column="1" VerticalAlignment="Center"
|
||||||
Width="30"
|
Background="{Binding Color}"
|
||||||
Height="40"
|
Visibility="{Binding Show_Description, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||||
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}"
|
<CheckBox Grid.Column="2" VerticalAlignment="Center" IsChecked="{Binding Estado}"
|
||||||
Background="{Binding Color_oculto}" />
|
Background="{Binding Color_oculto}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
@ -39,6 +39,9 @@ namespace CtrEditor.ObjetosSim
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public bool estado;
|
public bool estado;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public bool show_Description;
|
||||||
|
|
||||||
partial void OnEstadoChanged(bool value)
|
partial void OnEstadoChanged(bool value)
|
||||||
{
|
{
|
||||||
EscribirBitTag(Tag, value);
|
EscribirBitTag(Tag, value);
|
||||||
|
@ -69,6 +72,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
tag = "%M50.0";
|
tag = "%M50.0";
|
||||||
Descripcion = "Nombre del Tag";
|
Descripcion = "Nombre del Tag";
|
||||||
Color = Colors.LightBlue;
|
Color = Colors.LightBlue;
|
||||||
|
Show_Description = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePLCPrimerCiclo()
|
public override void UpdatePLCPrimerCiclo()
|
||||||
|
|
|
@ -7,7 +7,7 @@ using LibS7Adv;
|
||||||
using nkast.Aether.Physics2D.Common;
|
using nkast.Aether.Physics2D.Common;
|
||||||
using PaddleOCRSharp;
|
using PaddleOCRSharp;
|
||||||
using Siemens.Simatic.Simulation.Runtime;
|
using Siemens.Simatic.Simulation.Runtime;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel; // Para poder usar [property: Category ...
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
@ -1195,6 +1195,21 @@ namespace CtrEditor.ObjetosSim
|
||||||
CanvasSetTopinMeter(Top);
|
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)
|
public bool LeerBitTag(string Tag)
|
||||||
|
@ -1349,13 +1364,65 @@ namespace CtrEditor.ObjetosSim
|
||||||
return transformGroup;
|
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)
|
public (Vector2 TopLeft, Vector2 BottomRight) GetRectangleCoordinatesInMeter(Rectangle rect)
|
||||||
{
|
{
|
||||||
if (rect != null)
|
if (rect == null)
|
||||||
|
return (new Vector2(0, 0), new Vector2(0, 0));
|
||||||
|
|
||||||
|
var _canvasLeft = CanvasGetLeftinMeter();
|
||||||
|
var _canvasTop = CanvasGetTopinMeter();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var _canvasLeft = CanvasGetLeftinMeter();
|
// Verificar que tanto el rectángulo como la representación visual estén cargados
|
||||||
var _canvasTop = CanvasGetTopinMeter();
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
// Obtiene la transformada del objeto visual
|
// Obtiene la transformada del objeto visual
|
||||||
GeneralTransform transform = rect.TransformToAncestor(_visualRepresentation);
|
GeneralTransform transform = rect.TransformToAncestor(_visualRepresentation);
|
||||||
|
@ -1364,10 +1431,26 @@ namespace CtrEditor.ObjetosSim
|
||||||
Point topLeft = transform.Transform(new Point(0, 0));
|
Point topLeft = transform.Transform(new Point(0, 0));
|
||||||
Point bottomRight = transform.Transform(new Point(rect.ActualWidth, rect.ActualHeight));
|
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),
|
return (new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.X) + _canvasLeft,
|
||||||
new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.Y) + _canvasTop));
|
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)));
|
||||||
}
|
}
|
||||||
else return (new Vector2(0, 0), new Vector2(0, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Vector2 Start, Vector2 End) GetCenterLineVectors(Rectangle rect)
|
public (Vector2 Start, Vector2 End) GetCenterLineVectors(Rectangle rect)
|
||||||
|
@ -1381,27 +1464,56 @@ namespace CtrEditor.ObjetosSim
|
||||||
// Usar Dispatcher para asegurar la ejecución en el hilo correcto
|
// Usar Dispatcher para asegurar la ejecución en el hilo correcto
|
||||||
_visualRepresentation?.Dispatcher.Invoke(() =>
|
_visualRepresentation?.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
// Asegúrate de que el control está en el árbol visual y actualizado
|
try
|
||||||
if (_visualRepresentation.IsLoaded && rect.IsLoaded)
|
|
||||||
{
|
{
|
||||||
_visualRepresentation.UpdateLayout();
|
// Asegúrate de que el control está en el árbol visual y actualizado
|
||||||
|
if (_visualRepresentation.IsLoaded && rect.IsLoaded && IsVisualChild(rect, _visualRepresentation))
|
||||||
|
{
|
||||||
|
_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 _canvasLeft = CanvasGetLeftinMeter();
|
||||||
var _canvasTop = CanvasGetTopinMeter();
|
var _canvasTop = CanvasGetTopinMeter();
|
||||||
|
|
||||||
var transform = rect.TransformToAncestor(_visualRepresentation);
|
float rectWidth = (float)rect.ActualWidth;
|
||||||
|
if (double.IsNaN(rectWidth) || rectWidth == 0)
|
||||||
|
rectWidth = Ancho * PixelToMeter.Instance.calc.MetersToPixels(1);
|
||||||
|
|
||||||
// Puntos en coordenadas locales del rectángulo no rotado
|
start = new Vector2(_canvasLeft, _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 2));
|
||||||
Point startLocal = new Point(0, rect.ActualHeight / 2);
|
end = new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth), _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,82 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,146 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ using System.Windows.Input;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CtrEditor.ObjetosSim;
|
using CtrEditor.ObjetosSim;
|
||||||
|
using CtrEditor.Models;
|
||||||
|
|
||||||
namespace CtrEditor.PopUps
|
namespace CtrEditor.PopUps
|
||||||
{
|
{
|
||||||
|
@ -13,10 +14,10 @@ namespace CtrEditor.PopUps
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class TagEditorWindow : Window
|
public partial class TagEditorWindow : Window
|
||||||
{
|
{
|
||||||
public TagEditorWindow(osBase objeto, IEnumerable<osBase> todosLosObjetos)
|
public TagEditorWindow(osBase objeto, IEnumerable<osBase> todosLosObjetos, Dictionary<string, ImageData> imageDataDictionary = null)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = new TagEditorViewModel(objeto, todosLosObjetos, this);
|
DataContext = new TagEditorViewModel(objeto, todosLosObjetos, this, imageDataDictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TxtNuevaEtiqueta_KeyDown(object sender, KeyEventArgs e)
|
private void TxtNuevaEtiqueta_KeyDown(object sender, KeyEventArgs e)
|
||||||
|
@ -46,6 +47,7 @@ namespace CtrEditor.PopUps
|
||||||
{
|
{
|
||||||
private readonly osBase _objeto;
|
private readonly osBase _objeto;
|
||||||
private readonly IEnumerable<osBase> _todosLosObjetos;
|
private readonly IEnumerable<osBase> _todosLosObjetos;
|
||||||
|
private readonly Dictionary<string, ImageData> _imageDataDictionary;
|
||||||
private readonly TagEditorWindow _window;
|
private readonly TagEditorWindow _window;
|
||||||
private readonly List<string> _etiquetasOriginales;
|
private readonly List<string> _etiquetasOriginales;
|
||||||
|
|
||||||
|
@ -64,10 +66,11 @@ namespace CtrEditor.PopUps
|
||||||
public ICommand AplicarCommand { get; }
|
public ICommand AplicarCommand { get; }
|
||||||
public ICommand CancelarCommand { get; }
|
public ICommand CancelarCommand { get; }
|
||||||
|
|
||||||
public TagEditorViewModel(osBase objeto, IEnumerable<osBase> todosLosObjetos, TagEditorWindow window)
|
public TagEditorViewModel(osBase objeto, IEnumerable<osBase> todosLosObjetos, TagEditorWindow window, Dictionary<string, ImageData> imageDataDictionary = null)
|
||||||
{
|
{
|
||||||
_objeto = objeto;
|
_objeto = objeto;
|
||||||
_todosLosObjetos = todosLosObjetos;
|
_todosLosObjetos = todosLosObjetos;
|
||||||
|
_imageDataDictionary = imageDataDictionary;
|
||||||
_window = window;
|
_window = window;
|
||||||
|
|
||||||
// Guardar las etiquetas originales para poder cancelar
|
// Guardar las etiquetas originales para poder cancelar
|
||||||
|
@ -111,9 +114,22 @@ namespace CtrEditor.PopUps
|
||||||
var todasLasEtiquetas = _todosLosObjetos
|
var todasLasEtiquetas = _todosLosObjetos
|
||||||
.SelectMany(obj => obj.ListaEtiquetas)
|
.SelectMany(obj => obj.ListaEtiquetas)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.OrderBy(tag => tag)
|
|
||||||
.ToList();
|
.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
|
// Mostrar solo las que no están en el objeto actual
|
||||||
var etiquetasObjetoActual = _objeto.ListaEtiquetas;
|
var etiquetasObjetoActual = _objeto.ListaEtiquetas;
|
||||||
foreach (var tag in todasLasEtiquetas.Except(etiquetasObjetoActual))
|
foreach (var tag in todasLasEtiquetas.Except(etiquetasObjetoActual))
|
||||||
|
|
|
@ -50,7 +50,7 @@ namespace CtrEditor.Serialization
|
||||||
obj.RestaurarDesdeSnapshotGlobal();
|
obj.RestaurarDesdeSnapshotGlobal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepara para serialización
|
// Prepara para serializaci<EFBFBD>n
|
||||||
obj.SalvarDatosNoSerializables();
|
obj.SalvarDatosNoSerializables();
|
||||||
|
|
||||||
// Separa objetos globales y locales
|
// Separa objetos globales y locales
|
||||||
|
@ -64,13 +64,14 @@ namespace CtrEditor.Serialization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paso 2: Guardar datos de la página actual
|
// Paso 2: Guardar datos de la p<EFBFBD>gina actual
|
||||||
var dataToSerialize = new SimulationData
|
var dataToSerialize = new SimulationData
|
||||||
{
|
{
|
||||||
ObjetosSimulables = objetosSimulables.Count > 0 ? objetosSimulables : null,
|
ObjetosSimulables = objetosSimulables.Count > 0 ? objetosSimulables : null,
|
||||||
UnitConverter = PixelToMeter.Instance.calc,
|
UnitConverter = PixelToMeter.Instance.calc,
|
||||||
PLC_ConnectionData = _mainViewModel.PLCViewModel,
|
PLC_ConnectionData = _mainViewModel.PLCViewModel,
|
||||||
DatosLocalesObjetos = datosLocalesObjetos.Count > 0 ? datosLocalesObjetos : null
|
DatosLocalesObjetos = datosLocalesObjetos.Count > 0 ? datosLocalesObjetos : null,
|
||||||
|
ImageDataDictionary = _mainViewModel._imageDataDictionary.Count > 0 ? _mainViewModel._imageDataDictionary : null
|
||||||
};
|
};
|
||||||
|
|
||||||
var path = _datosDeTrabajo.ObtenerPathImagenConExtension(selectedImage, ".json");
|
var path = _datosDeTrabajo.ObtenerPathImagenConExtension(selectedImage, ".json");
|
||||||
|
@ -112,11 +113,11 @@ namespace CtrEditor.Serialization
|
||||||
// Paso 1: Cargar objetos globales
|
// Paso 1: Cargar objetos globales
|
||||||
LoadAllPagesState(settings);
|
LoadAllPagesState(settings);
|
||||||
|
|
||||||
// Paso 2: Cargar datos de la página actual
|
// Paso 2: Cargar datos de la p<EFBFBD>gina actual
|
||||||
var simulationData = LoadCurrentPageState(selectedImage, settings);
|
var simulationData = LoadCurrentPageState(selectedImage, settings);
|
||||||
|
|
||||||
// Paso 3: Crear snapshots de los objetos globales
|
// Paso 3: Crear snapshots de los objetos globales
|
||||||
// Nota: No filtramos por Enable_Local_Data aquí ya que se establecerá después
|
// Nota: No filtramos por Enable_Local_Data aqu<EFBFBD> ya que se establecer<65> despu<70>s
|
||||||
foreach (var obj in _mainViewModel.ObjetosSimulables)
|
foreach (var obj in _mainViewModel.ObjetosSimulables)
|
||||||
{
|
{
|
||||||
if (obj.Enable_On_All_Pages)
|
if (obj.Enable_On_All_Pages)
|
||||||
|
@ -134,7 +135,7 @@ namespace CtrEditor.Serialization
|
||||||
obj.Enable_Local_Data = false;
|
obj.Enable_Local_Data = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Luego aplicar los datos locales, esto activará Enable_Local_Data automáticamente
|
// Luego aplicar los datos locales, esto activar<EFBFBD> Enable_Local_Data autom<6F>ticamente
|
||||||
foreach (var datosLocales in simulationData.DatosLocalesObjetos)
|
foreach (var datosLocales in simulationData.DatosLocalesObjetos)
|
||||||
{
|
{
|
||||||
var objetoGlobal = _mainViewModel.ObjetosSimulables.FirstOrDefault(
|
var objetoGlobal = _mainViewModel.ObjetosSimulables.FirstOrDefault(
|
||||||
|
@ -186,7 +187,31 @@ namespace CtrEditor.Serialization
|
||||||
else
|
else
|
||||||
_mainViewModel.PLCViewModel = new PLCViewModel();
|
_mainViewModel.PLCViewModel = new PLCViewModel();
|
||||||
|
|
||||||
PixelToMeter.Instance.calc = simulationData.UnitConverter;
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return simulationData;
|
return simulationData;
|
||||||
|
|
|
@ -314,7 +314,7 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
if (values.Length == 2 && values[0] is float value1 && values[1] is float value2)
|
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;
|
return DependencyProperty.UnsetValue;
|
||||||
}
|
}
|
||||||
|
@ -435,7 +435,14 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
// Instancia privada estática, parte del patrón Singleton
|
// Instancia privada estática, parte del patrón Singleton
|
||||||
private static PixelToMeter? _instance;
|
private static PixelToMeter? _instance;
|
||||||
public UnitConverter calc = new UnitConverter(0.01f);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
// Propiedad pública estática para acceder a la instancia
|
// Propiedad pública estática para acceder a la instancia
|
||||||
public static PixelToMeter Instance
|
public static PixelToMeter Instance
|
||||||
|
|
Loading…
Reference in New Issue