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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PopUps/TagEditorWindow.xaml.cs b/PopUps/TagEditorWindow.xaml.cs
new file mode 100644
index 0000000..53acc43
--- /dev/null
+++ b/PopUps/TagEditorWindow.xaml.cs
@@ -0,0 +1,215 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Input;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using CtrEditor.ObjetosSim;
+
+namespace CtrEditor.PopUps
+{
+ ///
+ /// Interaction logic for TagEditorWindow.xaml
+ ///
+ public partial class TagEditorWindow : Window
+ {
+ public TagEditorWindow(osBase objeto, IEnumerable todosLosObjetos)
+ {
+ InitializeComponent();
+ DataContext = new TagEditorViewModel(objeto, todosLosObjetos, this);
+ }
+
+ private void TxtNuevaEtiqueta_KeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Enter)
+ {
+ if (DataContext is TagEditorViewModel viewModel)
+ {
+ viewModel.AgregarEtiquetaCommand.Execute(null);
+ }
+ }
+ }
+ }
+
+ public class TagInfo
+ {
+ public string TagName { get; set; }
+ public string DisplayName => "#" + TagName;
+
+ public TagInfo(string tagName)
+ {
+ TagName = tagName;
+ }
+ }
+
+ public partial class TagEditorViewModel : ObservableObject
+ {
+ private readonly osBase _objeto;
+ private readonly IEnumerable _todosLosObjetos;
+ private readonly TagEditorWindow _window;
+ private readonly List _etiquetasOriginales;
+
+ [ObservableProperty]
+ private string nuevaEtiqueta = "";
+
+ [ObservableProperty]
+ private ObservableCollection etiquetasObjeto = new();
+
+ [ObservableProperty]
+ private ObservableCollection etiquetasDisponibles = new();
+
+ public ICommand AgregarEtiquetaCommand { get; }
+ public ICommand AgregarEtiquetaExistenteCommand { get; }
+ public ICommand RemoverEtiquetaCommand { get; }
+ public ICommand AplicarCommand { get; }
+ public ICommand CancelarCommand { get; }
+
+ public TagEditorViewModel(osBase objeto, IEnumerable todosLosObjetos, TagEditorWindow window)
+ {
+ _objeto = objeto;
+ _todosLosObjetos = todosLosObjetos;
+ _window = window;
+
+ // Guardar las etiquetas originales para poder cancelar
+ _etiquetasOriginales = objeto.ListaEtiquetas.ToList();
+
+ // Inicializar comandos
+ AgregarEtiquetaCommand = new RelayCommand(AgregarEtiqueta, () => !string.IsNullOrWhiteSpace(NuevaEtiqueta));
+ AgregarEtiquetaExistenteCommand = new RelayCommand(AgregarEtiquetaExistente);
+ RemoverEtiquetaCommand = new RelayCommand(RemoverEtiqueta);
+ AplicarCommand = new RelayCommand(Aplicar);
+ CancelarCommand = new RelayCommand(Cancelar);
+
+ // Cargar datos iniciales
+ CargarEtiquetasObjeto();
+ CargarEtiquetasDisponibles();
+
+ // Suscribirse al cambio de NuevaEtiqueta para actualizar el comando
+ PropertyChanged += (s, e) =>
+ {
+ if (e.PropertyName == nameof(NuevaEtiqueta))
+ {
+ (AgregarEtiquetaCommand as IRelayCommand)?.NotifyCanExecuteChanged();
+ }
+ };
+ }
+
+ private void CargarEtiquetasObjeto()
+ {
+ EtiquetasObjeto.Clear();
+ foreach (var tag in _objeto.ListaEtiquetas.OrderBy(t => t))
+ {
+ EtiquetasObjeto.Add(new TagInfo(tag));
+ }
+ }
+
+ private void CargarEtiquetasDisponibles()
+ {
+ EtiquetasDisponibles.Clear();
+
+ // Obtener todas las etiquetas únicas de todos los objetos
+ var todasLasEtiquetas = _todosLosObjetos
+ .SelectMany(obj => obj.ListaEtiquetas)
+ .Distinct()
+ .OrderBy(tag => tag)
+ .ToList();
+
+ // Mostrar solo las que no están en el objeto actual
+ var etiquetasObjetoActual = _objeto.ListaEtiquetas;
+ foreach (var tag in todasLasEtiquetas.Except(etiquetasObjetoActual))
+ {
+ EtiquetasDisponibles.Add(new TagInfo(tag));
+ }
+ }
+
+ private void AgregarEtiqueta()
+ {
+ if (string.IsNullOrWhiteSpace(NuevaEtiqueta))
+ return;
+
+ // Limpiar la etiqueta (remover # si existe y convertir a minúsculas)
+ string tagLimpia = NuevaEtiqueta.Trim();
+ if (tagLimpia.StartsWith("#"))
+ tagLimpia = tagLimpia.Substring(1);
+
+ tagLimpia = tagLimpia.ToLowerInvariant();
+
+ // Validar que no esté vacía después de limpiar
+ if (string.IsNullOrWhiteSpace(tagLimpia))
+ return;
+
+ // Verificar que no existe ya
+ if (_objeto.ListaEtiquetas.Contains(tagLimpia))
+ return;
+
+ // Agregar la etiqueta al objeto
+ var etiquetasActuales = string.IsNullOrWhiteSpace(_objeto.Etiquetas) ?
+ new List() :
+ _objeto.Etiquetas.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
+
+ etiquetasActuales.Add("#" + tagLimpia);
+ _objeto.Etiquetas = string.Join(" ", etiquetasActuales);
+
+ // Actualizar las listas
+ CargarEtiquetasObjeto();
+ CargarEtiquetasDisponibles();
+
+ // Limpiar el campo
+ NuevaEtiqueta = "";
+ }
+
+ private void AgregarEtiquetaExistente(string tagName)
+ {
+ if (string.IsNullOrWhiteSpace(tagName) || _objeto.ListaEtiquetas.Contains(tagName))
+ return;
+
+ // Agregar la etiqueta al objeto
+ var etiquetasActuales = string.IsNullOrWhiteSpace(_objeto.Etiquetas) ?
+ new List() :
+ _objeto.Etiquetas.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
+
+ etiquetasActuales.Add("#" + tagName);
+ _objeto.Etiquetas = string.Join(" ", etiquetasActuales);
+
+ // Actualizar las listas
+ CargarEtiquetasObjeto();
+ CargarEtiquetasDisponibles();
+ }
+
+ private void RemoverEtiqueta(string tagName)
+ {
+ if (string.IsNullOrWhiteSpace(tagName))
+ return;
+
+ // Remover la etiqueta del objeto
+ var etiquetasActuales = _objeto.Etiquetas
+ .Split(' ', StringSplitOptions.RemoveEmptyEntries)
+ .Where(tag => !tag.Equals("#" + tagName, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ _objeto.Etiquetas = string.Join(" ", etiquetasActuales);
+
+ // Actualizar las listas
+ CargarEtiquetasObjeto();
+ CargarEtiquetasDisponibles();
+ }
+
+ private void Aplicar()
+ {
+ _window.DialogResult = true;
+ _window.Close();
+ }
+
+ private void Cancelar()
+ {
+ // Restaurar las etiquetas originales
+ var etiquetasOriginalesString = _etiquetasOriginales.Any() ?
+ string.Join(" ", _etiquetasOriginales.Select(tag => "#" + tag)) :
+ "";
+ _objeto.Etiquetas = etiquetasOriginalesString;
+
+ _window.DialogResult = false;
+ _window.Close();
+ }
+ }
+}
\ No newline at end of file