diff --git a/Controls/osVisFilter.xaml b/Controls/osVisFilter.xaml index 5f3a2d1..f59fb64 100644 --- a/Controls/osVisFilter.xaml +++ b/Controls/osVisFilter.xaml @@ -46,6 +46,24 @@ + + + + + + + + + + + + + + diff --git a/Controls/osVisFilter.xaml.cs b/Controls/osVisFilter.xaml.cs index ff2dca2..643c57b 100644 --- a/Controls/osVisFilter.xaml.cs +++ b/Controls/osVisFilter.xaml.cs @@ -46,6 +46,14 @@ namespace CtrEditor.Controls { FilterViewModel.UpdateTypeFilters(types); } + + /// + /// Updates the tag filters based on the provided objects + /// + public void UpdateAvailableTags(IEnumerable objects) + { + FilterViewModel.UpdateTagFilters(objects); + } } /// @@ -71,6 +79,12 @@ namespace CtrEditor.Controls [ObservableProperty] private ObservableCollection typeFilters = new ObservableCollection(); + [ObservableProperty] + private ObservableCollection tagFilters = new ObservableCollection(); + + [ObservableProperty] + private string searchTags = ""; + partial void OnShowAllChanged(bool value) { NotifyFilterChanged(); @@ -113,6 +127,28 @@ namespace CtrEditor.Controls } } + partial void OnTagFiltersChanged(ObservableCollection value) + { + if (value != null) + { + foreach (var filter in value) + { + filter.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(TagFilterItem.IsSelected)) + { + NotifyFilterChanged(); + } + }; + } + } + } + + partial void OnSearchTagsChanged(string value) + { + NotifyFilterChanged(); + } + private void NotifyFilterChanged() { if (this.Parent is osVisFilter filter) @@ -120,7 +156,7 @@ namespace CtrEditor.Controls filter.OnFilterChanged(); } } - + [JsonIgnore] public osVisFilter Parent { get; set; } @@ -165,6 +201,44 @@ namespace CtrEditor.Controls } } + /// + /// Updates the tag filters based on the provided objects + /// + public void UpdateTagFilters(IEnumerable objects) + { + // Get all unique tags from all objects + var allTags = objects + .SelectMany(obj => obj.ListaEtiquetas) + .Distinct() + .OrderBy(tag => tag) + .ToList(); + + // Remove tags that are no longer present + var tagsToRemove = TagFilters + .Where(tf => !allTags.Contains(tf.TagName)) + .ToList(); + + foreach (var item in tagsToRemove) + { + UnsubscribeFromTagFilter(item); + TagFilters.Remove(item); + } + + // Add new tags that aren't already in the list + foreach (var tag in allTags) + { + if (!TagFilters.Any(tf => tf.TagName == tag)) + { + var newFilter = new TagFilterItem(tag) + { + IsSelected = true + }; + SubscribeToTagFilter(newFilter); + TagFilters.Add(newFilter); + } + } + } + private void SubscribeToTypeFilter(TypeFilterItem filter) { filter.PropertyChanged += TypeFilter_PropertyChanged; @@ -175,6 +249,16 @@ namespace CtrEditor.Controls filter.PropertyChanged -= TypeFilter_PropertyChanged; } + private void SubscribeToTagFilter(TagFilterItem filter) + { + filter.PropertyChanged += TagFilter_PropertyChanged; + } + + private void UnsubscribeFromTagFilter(TagFilterItem filter) + { + filter.PropertyChanged -= TagFilter_PropertyChanged; + } + private void TypeFilter_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(TypeFilterItem.IsSelected)) @@ -183,6 +267,14 @@ namespace CtrEditor.Controls } } + private void TagFilter_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(TagFilterItem.IsSelected)) + { + NotifyFilterChanged(); + } + } + /// /// Gets the display name for a type, using the NombreClase static method if available /// @@ -220,4 +312,26 @@ namespace CtrEditor.Controls OnPropertyChanged(nameof(IsSelected)); } } + + /// + /// Represents a tag filter item with selection state + /// + public partial class TagFilterItem : ObservableObject + { + [ObservableProperty] + private bool isSelected; + + public string TagName { get; } + public string DisplayName => "#" + TagName; + + public TagFilterItem(string tagName) + { + TagName = tagName; + } + + partial void OnIsSelectedChanged(bool value) + { + // Could add logic here if needed when selection changes + } + } } \ No newline at end of file diff --git a/MainViewModel.cs b/MainViewModel.cs index fe041c0..679c6f6 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -127,7 +127,7 @@ namespace CtrEditor private ObjectManipulationManager _objectManager; // Add this line public MainWindow MainWindow - { + { get => mainWindow; set { @@ -250,10 +250,10 @@ namespace CtrEditor { if (HasUnsavedChanges && !inhibitSaveChangesControl) { - var result = MessageBox.Show("There are unsaved changes. Do you want to save them?", - "Save Changes", + var result = MessageBox.Show("There are unsaved changes. Do you want to save them?", + "Save Changes", MessageBoxButton.YesNo); - + if (result == MessageBoxResult.Yes) { SaveStateObjetosSimulables(); @@ -283,7 +283,7 @@ namespace CtrEditor habilitarEliminarUserControl = _objectManager.SelectedObjects.Count > 0; } - + [ObservableProperty] private TipoSimulable selectedItem; @@ -317,6 +317,7 @@ namespace CtrEditor private void UpdateVisFilterTypes() { MainWindow?.VisFilter?.UpdateAvailableTypes(ObjetosSimulables.Select(o => o.GetType()).Distinct()); + MainWindow?.VisFilter?.UpdateAvailableTags(ObjetosSimulables); } // @@ -331,9 +332,9 @@ namespace CtrEditor datosDeTrabajo = new DatosDeTrabajo(); // Initialize ObjetosSimulables first - ObjetosSimulables = new ObservableCollection(); + ObjetosSimulables = new ObservableCollection(); - ObjetosSimulables = new ObservableCollection(); + ObjetosSimulables = new ObservableCollection(); ListaOsBase = new ObservableCollection(); @@ -342,7 +343,7 @@ namespace CtrEditor _timerPLCUpdate = new DispatcherTimer(); _timerPLCUpdate.Interval = TimeSpan.FromMilliseconds(10); // Restaurado a 10ms - _timerPLCUpdate.Tick += OnRefreshEvent; + _timerPLCUpdate.Tick += OnRefreshEvent; InitializeTipoSimulableList(); @@ -363,7 +364,7 @@ namespace CtrEditor TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning); TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning); - + // Inicializar simulación de fluidos FluidSimulation = new SimulationFluidsViewModel(this); TBStartFluidSimulationCommand = new RelayCommand(StartFluidSimulation, () => !FluidSimulation.IsFluidSimulationRunning); @@ -412,16 +413,46 @@ namespace CtrEditor isVisible = false; } + // Check tag filters + if (filter.TagFilters.Any() && filter.TagFilters.Any(tf => tf.IsSelected)) + { + var selectedTags = filter.TagFilters.Where(tf => tf.IsSelected).Select(tf => tf.TagName).ToList(); + bool hasMatchingTag = obj.ListaEtiquetas.Any(tag => selectedTags.Contains(tag)); + if (!hasMatchingTag) + { + isVisible = false; + } + } + + // Check search tags + if (!string.IsNullOrWhiteSpace(filter.SearchTags)) + { + var searchTags = filter.SearchTags + .Split(' ', StringSplitOptions.RemoveEmptyEntries) + .Where(tag => tag.StartsWith("#") && tag.Length > 1) + .Select(tag => tag.Substring(1).ToLowerInvariant()) + .ToList(); + + if (searchTags.Any()) + { + bool hasMatchingSearchTag = searchTags.Any(searchTag => obj.ListaEtiquetas.Contains(searchTag)); + if (!hasMatchingSearchTag) + { + isVisible = false; + } + } + } + // Check other filters if (filter.ShowCloned && !obj.Cloned) isVisible = false; - + if (filter.ShowAutoCreated && !obj.AutoCreated) isVisible = false; - + if (filter.ShowEnableOnAllPages && !obj.Enable_On_All_Pages) isVisible = false; - + if (filter.ShowOnThisPage && !obj.Show_On_This_Page) isVisible = false; } @@ -441,13 +472,13 @@ namespace CtrEditor if (SelectedImage != null) { _stateSerializer.LoadState(SelectedImage); - + // Aplicar los filtros actuales a los objetos recién cargados if (MainWindow?.VisFilter?.FilterViewModel != null) { OnVisFilterChanged(MainWindow.VisFilter.FilterViewModel); } - + // Limpiar historial de undo al cargar un estado desde archivo MainWindow?.ClearUndoHistory(); } @@ -458,7 +489,7 @@ namespace CtrEditor { // Suponiendo que "SelectedImage" es una propiedad que al establecerse dispara "ImageSelected" directorioTrabajo = EstadoPersistente.Instance.directorio; - + // Limpiar historial de undo al cargar datos iniciales MainWindow?.ClearUndoHistory(); } @@ -538,17 +569,17 @@ namespace CtrEditor { // Create a copy of the selected objects to avoid issues during iteration var objectsToDuplicate = _objectManager.SelectedObjects.ToList(); - + // Clear current selection before duplicating _objectManager.ClearSelection(); - + // Track all newly created objects var newObjects = new List(); - + // Duplicate each object with a small offset float offsetX = 0.5f; float offsetY = 0.5f; - + foreach (var objToDuplicate in objectsToDuplicate) { var newObj = DuplicarObjeto(objToDuplicate, offsetX, offsetY); @@ -557,10 +588,10 @@ namespace CtrEditor newObjects.Add(newObj); } } - + // Force a complete layout update to ensure all controls are positioned MainWindow.ImagenEnTrabajoCanvas.UpdateLayout(); - + // Use a dispatcher to delay the selection until the UI has had time to fully render Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => { @@ -571,7 +602,7 @@ namespace CtrEditor { double left = Canvas.GetLeft(newObj.VisualRepresentation); double top = Canvas.GetTop(newObj.VisualRepresentation); - + // Only add to selection if the object has valid coordinates if (!double.IsNaN(left) && !double.IsNaN(top) && !double.IsInfinity(left) && !double.IsInfinity(top)) { @@ -579,17 +610,17 @@ namespace CtrEditor } } } - + // Force another layout update before updating selection visuals MainWindow.ImagenEnTrabajoCanvas.UpdateLayout(); - + // Update SelectedItemOsList if there are newly created objects if (newObjects.Count > 0) { // Set to the last duplicated object so it's visible in the property panel SelectedItemOsList = newObjects.LastOrDefault(); } - + // Now update selection visuals _objectManager.UpdateSelectionVisuals(); })); @@ -665,7 +696,7 @@ namespace CtrEditor .Where(o => o.Show_On_This_Page && o.AutoCreated) .ToList(); foreach (var obj in osAutoCreated_List) - RemoverObjetoSimulable(obj); + RemoverObjetoSimulable(obj); } private void EliminarClonedCommand() @@ -674,7 +705,7 @@ namespace CtrEditor .Where(o => o.Show_On_This_Page && o.Cloned) .ToList(); foreach (var obj in osCloned_List) - RemoverObjetoSimulable(obj); + RemoverObjetoSimulable(obj); } private void AssingPagesCommand() @@ -765,10 +796,10 @@ namespace CtrEditor private void StartSimulation() { - // Detener simulación de fluidos si está ejecutándose - StopFluidSimulation(); + // Detener simulación de fluidos si está ejecutándose + StopFluidSimulation(); - IsSimulationRunning = true; + IsSimulationRunning = true; // Ocultar rectángulos de selección antes de iniciar la simulación _objectManager.UpdateSelectionVisuals(); @@ -795,36 +826,36 @@ namespace CtrEditor { simulationManager.Debug_ClearSimulationShapes(); Debug_SimulacionCreado = false; - } - _timerSimulacion.Stop(); + } + _timerSimulacion.Stop(); - // Restaurar los rectángulos de selección si hay objetos seleccionados - _objectManager.UpdateSelectionVisuals(); - - // Limpiar historial de undo al detener la simulación - MainWindow?.ClearUndoHistory(); - } + // Restaurar los rectángulos de selección si hay objetos seleccionados + _objectManager.UpdateSelectionVisuals(); - /// - /// Inicia la simulación de fluidos independiente - /// - public void StartFluidSimulation() - { - // Detener simulación física si está ejecutándose - StopSimulation(); - - FluidSimulation.StartFluidSimulation(); - CommandManager.InvalidateRequerySuggested(); - } + // Limpiar historial de undo al detener la simulación + MainWindow?.ClearUndoHistory(); + } - /// - /// Detiene la simulación de fluidos independiente - /// - public void StopFluidSimulation() - { - FluidSimulation.StopFluidSimulation(); - CommandManager.InvalidateRequerySuggested(); - } + /// + /// Inicia la simulación de fluidos independiente + /// + public void StartFluidSimulation() + { + // Detener simulación física si está ejecutándose + StopSimulation(); + + FluidSimulation.StartFluidSimulation(); + CommandManager.InvalidateRequerySuggested(); + } + + /// + /// Detiene la simulación de fluidos independiente + /// + public void StopFluidSimulation() + { + FluidSimulation.StopFluidSimulation(); + CommandManager.InvalidateRequerySuggested(); + } private void OnTickSimulacion(object sender, EventArgs e) { @@ -833,7 +864,7 @@ namespace CtrEditor // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimModel_last; stopwatch_SimModel_last = stopwatch_Sim.Elapsed.TotalMilliseconds; - + // Acumular tiempo para el promedio accumulatedSimTime += elapsedMilliseconds; simSampleCount++; @@ -920,7 +951,7 @@ namespace CtrEditor } stopwatch.Stop(); // Stop measuring time - // Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms"); + // Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms"); } private void OpenWorkDirectory() @@ -956,16 +987,16 @@ namespace CtrEditor { // Remove the path if it already exists RecentDirectories.Remove(path); - + // Add the new path at the beginning RecentDirectories.Insert(0, path); - + // Keep only the last 10 entries while (RecentDirectories.Count > 10) { RecentDirectories.RemoveAt(RecentDirectories.Count - 1); } - + UpdateRecentDirectories(); } @@ -1103,9 +1134,9 @@ namespace CtrEditor var objectsList = selectedObjects.ToList(); // Crear una clave única basada en los IDs de los objetos seleccionados string key = string.Join("_", objectsList.Select(o => o.Id.Value).OrderBy(id => id)); - + // Verificar si ya existe una ventana para esta selección - if (_propertyEditorWindows.TryGetValue(key, out var existingWindow) && + if (_propertyEditorWindows.TryGetValue(key, out var existingWindow) && existingWindow.IsVisible) { existingWindow.Activate(); @@ -1116,7 +1147,7 @@ namespace CtrEditor var window = new Windows.MultiPropertyEditorWindow(objectsList, MainWindow); window.Closed += (s, e) => _propertyEditorWindows.Remove(key); _propertyEditorWindows[key] = window; - + window.Show(); HasUnsavedChanges = true; _objectManager.UpdateSelectionVisuals(); diff --git a/ObjetosSim/TagEditorAttribute.cs b/ObjetosSim/TagEditorAttribute.cs new file mode 100644 index 0000000..f1c4265 --- /dev/null +++ b/ObjetosSim/TagEditorAttribute.cs @@ -0,0 +1,91 @@ +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using CtrEditor.PopUps; +using Xceed.Wpf.Toolkit.PropertyGrid; +using Xceed.Wpf.Toolkit.PropertyGrid.Editors; + +namespace CtrEditor.ObjetosSim +{ + /// + /// Atributo para marcar propiedades que deben usar el editor de etiquetas + /// + public class TagEditorAttribute : Attribute + { + public TagEditorAttribute() { } + } + + /// + /// Editor personalizado para etiquetas en PropertyGrid + /// + public class TagPropertyEditor : ITypeEditor + { + public FrameworkElement ResolveEditor(PropertyItem propertyItem) + { + var grid = new Grid(); + grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); + grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + + // TextBox para mostrar/editar etiquetas directamente + var textBox = new TextBox(); + textBox.SetBinding(TextBox.TextProperty, new Binding("Value") + { + Source = propertyItem, + Mode = BindingMode.TwoWay, + UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged + }); + Grid.SetColumn(textBox, 0); + + // Botón para abrir el editor modal + var button = new Button + { + Content = "...", + Width = 25, + Margin = new Thickness(2, 0, 0, 0), + ToolTip = "Abrir editor de etiquetas" + }; + + button.Click += (sender, e) => + { + try + { + // Obtener el objeto que se está editando + if (propertyItem.Instance is osBase osObject) + { + // Obtener el MainWindow para acceder a todos los objetos + var mainWindow = Application.Current.Windows + .OfType() + .FirstOrDefault(); + + if (mainWindow?.DataContext is MainViewModel mainViewModel) + { + // Abrir el editor de etiquetas + var tagEditor = new TagEditorWindow(osObject, mainViewModel.ObjetosSimulables); + tagEditor.Owner = mainWindow; + + if (tagEditor.ShowDialog() == true) + { + // La ventana ya actualiza el objeto directamente + // Actualizar el textbox con el nuevo valor + textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget(); + } + } + } + } + catch (Exception ex) + { + MessageBox.Show($"Error al abrir el editor de etiquetas: {ex.Message}", + "Error", MessageBoxButton.OK, MessageBoxImage.Error); + } + }; + + Grid.SetColumn(button, 1); + + grid.Children.Add(textBox); + grid.Children.Add(button); + + return grid; + } + } +} \ No newline at end of file diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs index b196ea6..b2c4f05 100644 --- a/ObjetosSim/osBase.cs +++ b/ObjetosSim/osBase.cs @@ -106,8 +106,8 @@ namespace CtrEditor.ObjetosSim LeftChanging(oldValue, newValue); } - public virtual void LeftChanged(float value) - { + public virtual void LeftChanged(float value) + { // Actualizar posición relativa si el movimiento no viene del FramePlate UpdateFramePlateRelativePosition(); } @@ -128,8 +128,8 @@ namespace CtrEditor.ObjetosSim TopChanging(oldValue, newValue); } - public virtual void TopChanged(float value) - { + public virtual void TopChanged(float value) + { // Actualizar posición relativa si el movimiento no viene del FramePlate UpdateFramePlateRelativePosition(); } @@ -176,6 +176,52 @@ namespace CtrEditor.ObjetosSim public virtual void AnguloChanged(float value) { } public virtual void AnguloChanging(float oldValue, float newValue) { } + [ObservableProperty] + [property: Description("Etiquetas para clasificar el objeto. Use # antes de cada etiqueta (ej: #motor #bomba #critico)")] + [property: Category("General:")] + [property: Editor(typeof(TagPropertyEditor), typeof(TagPropertyEditor))] + [property: TagEditor] + private string etiquetas = ""; + + partial void OnEtiquetasChanged(string value) + { + EtiquetasChanged(value); + // Update the visibility filters when tags change + _mainViewModel?.MainWindow?.VisFilter?.UpdateAvailableTags(_mainViewModel.ObjetosSimulables); + } + + public virtual void EtiquetasChanged(string value) { } + + /// + /// Obtiene la lista de etiquetas parseadas desde el string + /// + [JsonIgnore] + public List ListaEtiquetas + { + get + { + if (string.IsNullOrWhiteSpace(Etiquetas)) + return new List(); + + return Etiquetas + .Split(' ', StringSplitOptions.RemoveEmptyEntries) + .Where(tag => tag.StartsWith("#") && tag.Length > 1) + .Select(tag => tag.Substring(1).ToLowerInvariant()) + .Distinct() + .ToList(); + } + } + + /// + /// Verifica si el objeto tiene una etiqueta específica + /// + /// La etiqueta a buscar (sin el #) + /// True si el objeto tiene la etiqueta + public bool TieneEtiqueta(string etiqueta) + { + return ListaEtiquetas.Contains(etiqueta.ToLowerInvariant()); + } + public void Resize(double width, double height) { @@ -334,7 +380,7 @@ namespace CtrEditor.ObjetosSim { FramePlate.PropertyChanged += OnFramePlatePropertyChanged; UpdateZIndex(FramePlate.Zindex_FramePlate); - + // Calcular punto de pivot y posición relativa inicial var pivotPoint = FramePlate.GetPivotPoint(); FramePlate_PivotX = pivotPoint.X; @@ -373,7 +419,7 @@ namespace CtrEditor.ObjetosSim if (e.PropertyName == nameof(osFramePlate.Nombre)) Group_Panel = ((osFramePlate)sender).Nombre; - if (e.PropertyName == nameof(osFramePlate.Top) || + if (e.PropertyName == nameof(osFramePlate.Top) || e.PropertyName == nameof(osFramePlate.Left)) { UpdateOrbitalPosition(); @@ -387,7 +433,7 @@ namespace CtrEditor.ObjetosSim UpdateOrbitalPosition(); } - if (e.PropertyName == nameof(osFramePlate.PivotCenterX) || + if (e.PropertyName == nameof(osFramePlate.PivotCenterX) || e.PropertyName == nameof(osFramePlate.PivotCenterY)) { // Cuando cambia el pivot, recalcular posición relativa @@ -407,32 +453,32 @@ namespace CtrEditor.ObjetosSim // Actualizar punto de pivot actual (puede haber cambiado por los checkboxes) var currentPivot = FramePlate.GetPivotPoint(); - + // Calcular el ángulo de rotación total desde la posición inicial float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle; - + // Convertir ángulo a radianes float angleRad = deltaAngle * (float)Math.PI / 180.0f; - + // Calcular la nueva posición orbital usando rotación de matriz float cosAngle = (float)Math.Cos(angleRad); float sinAngle = (float)Math.Sin(angleRad); - + // Rotar la posición relativa float rotatedX = cosAngle * FramePlate_RelativeX - sinAngle * FramePlate_RelativeY; float rotatedY = sinAngle * FramePlate_RelativeX + cosAngle * FramePlate_RelativeY; - + // Calcular nueva posición absoluta usando el punto de pivot actual float newLeft = currentPivot.X + rotatedX; float newTop = currentPivot.Y + rotatedY; - + // Actualizar directamente los campos sin disparar eventos de cambio SetProperty(ref left, newLeft); SetProperty(ref top, newTop); - + // Forzar actualización de la posición visual ActualizarLeftTop(); - + // Llamar a OnMoveResizeRotate para actualizar la física OnMoveResizeRotate(); } @@ -444,20 +490,20 @@ namespace CtrEditor.ObjetosSim { // Obtener punto de pivot actual var currentPivot = FramePlate.GetPivotPoint(); - + // Recalcular posición relativa considerando la rotación actual del FramePlate float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle; float angleRad = deltaAngle * (float)Math.PI / 180.0f; - + // Calcular la posición relativa actual respecto al pivot del FramePlate float currentRelativeX = Left - currentPivot.X; float currentRelativeY = Top - currentPivot.Y; - + // Si el FramePlate está rotado, necesitamos "desrotar" la posición para obtener // la posición relativa en el sistema de coordenadas original float cosAngle = (float)Math.Cos(-angleRad); float sinAngle = (float)Math.Sin(-angleRad); - + FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY; FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY; } @@ -470,23 +516,23 @@ namespace CtrEditor.ObjetosSim // Obtener posición absoluta actual del objeto (antes del cambio de pivot) float currentLeft = Left; float currentTop = Top; - + // Obtener nuevo punto de pivot var newPivot = FramePlate.GetPivotPoint(); - + // Calcular nueva posición relativa respecto al nuevo pivot // Considerar la rotación actual para obtener posición relativa en sistema original float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle; float angleRad = deltaAngle * (float)Math.PI / 180.0f; - + // Posición relativa actual respecto al nuevo pivot float currentRelativeX = currentLeft - newPivot.X; float currentRelativeY = currentTop - newPivot.Y; - + // "Desrotar" para obtener coordenadas en el sistema original float cosAngle = (float)Math.Cos(-angleRad); float sinAngle = (float)Math.Sin(-angleRad); - + FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY; FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY; } @@ -666,7 +712,8 @@ namespace CtrEditor.ObjetosSim // Filtrar texto por lista de caracteres permitidos var filteredBlocks = result.TextBlocks .Where(block => block.Score > 0.5) // Solo bloques con confianza > 50% - .Select(block => new { + .Select(block => new + { Text = new string(block.Text.Where(c => allowedChars.Contains(c)).ToArray()), Score = block.Score }) diff --git a/PopUps/TagEditorWindow.xaml b/PopUps/TagEditorWindow.xaml new file mode 100644 index 0000000..62fa57b --- /dev/null +++ b/PopUps/TagEditorWindow.xaml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +