diff --git a/Converters/ConnectionConverters.cs b/Converters/ConnectionConverters.cs new file mode 100644 index 0000000..e69de29 diff --git a/CtrEditor.csproj b/CtrEditor.csproj index 787b3d7..81cb2ab 100644 --- a/CtrEditor.csproj +++ b/CtrEditor.csproj @@ -44,6 +44,8 @@ + + @@ -121,6 +123,8 @@ + + diff --git a/DataStates/StateManager.cs b/DataStates/StateManager.cs new file mode 100644 index 0000000..7387186 --- /dev/null +++ b/DataStates/StateManager.cs @@ -0,0 +1,383 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; +using Newtonsoft.Json; +using System.Linq; +using System.Windows.Controls; +using System.Windows; +using CtrEditor.ObjetosSim; +using LibS7Adv; + +namespace CtrEditor +{ + /// + /// Metadata de una página, incluyendo información sobre la imagen y configuración + /// + public class PageMetadata + { + public string PageId { get; set; } + public string PageName { get; set; } + public string ImagePath { get; set; } + public DateTime LastModified { get; set; } + public Dictionary CustomMetadata { get; set; } = new(); + } + + /// + /// Estado de un objeto dentro de una página específica + /// + public class PageObjectState + { + public string GlobalObjectId { get; set; } + public bool IsVisible { get; set; } = true; + public float Left { get; set; } + public float Top { get; set; } + public float Rotation { get; set; } + public Dictionary CustomProperties { get; set; } = new(); + } + + /// + /// Estado de una página específica + /// + public class PageState + { + public string PageId { get; set; } + public string PageName { get; set; } + public List LocalObjects { get; set; } = new(); + public Dictionary GlobalObjectStates { get; set; } = new(); + public Dictionary PageMetadata { get; set; } = new(); + } + + /// + /// Estado global de la aplicación + /// + public class GlobalState + { + public List SharedObjects { get; set; } = new(); + public Dictionary PagesMetadata { get; set; } = new(); + public PLCViewModel PLCConfiguration { get; set; } + public UnitConverter UnitConverter { get; set; } + public int LastUsedId { get; set; } + } + + /// + /// Gestor principal de estados de la aplicación + /// + public class StateManager : IDisposable + { + private readonly string _basePath; + private GlobalState _globalState; + private Dictionary _loadedPages; + private string _currentPageId; + private readonly object _lockObject = new(); + private bool _hasUnsavedChanges; + private MainViewModel _mainViewModel; + + public event EventHandler PageStateChanged; + public event EventHandler GlobalStateChanged; + + public StateManager(string basePath, MainViewModel mainViewModel) + { + _basePath = basePath; + _mainViewModel = mainViewModel; + _loadedPages = new Dictionary(); + Directory.CreateDirectory(basePath); + } + + public async Task InitializeAsync() + { + await LoadGlobalStateAsync(); + if (_mainViewModel.SelectedImage != null) + { + await LoadPageStateAsync(_mainViewModel.SelectedImage); + } + } + + private JsonSerializerSettings GetSerializerSettings() + { + return new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Auto, + ObjectCreationHandling = ObjectCreationHandling.Replace, + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore + }; + } + + public async Task LoadGlobalStateAsync() + { + var path = Path.Combine(_basePath, "global.json"); + if (File.Exists(path)) + { + try + { + var json = await File.ReadAllTextAsync(path); + _globalState = JsonConvert.DeserializeObject(json, GetSerializerSettings()); + + // Restaurar configuración global + _mainViewModel.PLCViewModel = _globalState.PLCConfiguration ?? new PLCViewModel(); + if (_globalState.UnitConverter != null) + PixelToMeter.Instance.calc = _globalState.UnitConverter; + else + PixelToMeter.Instance.calc = new UnitConverter(1.0f); // Valor por defecto + + // Restaurar objetos globales + foreach (var obj in _globalState.SharedObjects) + { + await RestoreObjectAsync(obj); + } + } + catch (Exception ex) + { + // Log error y crear nuevo estado global + _globalState = new GlobalState(); + } + } + else + { + _globalState = new GlobalState(); + } + } + + public async Task SaveGlobalStateAsync() + { + var path = Path.Combine(_basePath, "global.json"); + var backupPath = Path.ChangeExtension(path, ".bak"); + + // Actualizar estado global + _globalState.PLCConfiguration = _mainViewModel.PLCViewModel; + _globalState.UnitConverter = PixelToMeter.Instance.calc; + + // Crear backup + if (File.Exists(path)) + { + File.Copy(path, backupPath, true); + } + + try + { + // Preparar objetos para serialización + foreach (var obj in _globalState.SharedObjects) + { + obj.SalvarDatosNoSerializables(); + } + + var json = JsonConvert.SerializeObject(_globalState, GetSerializerSettings()); + await File.WriteAllTextAsync(path, json); + _hasUnsavedChanges = false; + GlobalStateChanged?.Invoke(this, EventArgs.Empty); + } + finally + { + // Restaurar estado de objetos + foreach (var obj in _globalState.SharedObjects) + { + obj.RestaurarDatosNoSerializables(); + } + } + } + + public async Task LoadPageStateAsync(string pageId) + { + // Retornar página cacheada si existe + if (_loadedPages.TryGetValue(pageId, out var cachedState)) + { + return cachedState; + } + + var path = Path.Combine(_basePath, $"page_{pageId}.json"); + try + { + PageState pageState; + if (File.Exists(path)) + { + var json = await File.ReadAllTextAsync(path); + pageState = JsonConvert.DeserializeObject(json, GetSerializerSettings()); + } + else + { + pageState = new PageState + { + PageId = pageId, + PageName = _globalState.PagesMetadata.GetValueOrDefault(pageId)?.PageName ?? pageId + }; + } + + _loadedPages[pageId] = pageState; + + // Restaurar objetos locales + foreach (var obj in pageState.LocalObjects) + { + await RestoreObjectAsync(obj); + } + + // Aplicar estados de objetos globales + ApplyGlobalObjectStates(pageState); + + return pageState; + } + catch (Exception ex) + { + // Log error + throw; + } + } + + private void ApplyGlobalObjectStates(PageState pageState) + { + foreach (var kvp in pageState.GlobalObjectStates) + { + var globalObj = _globalState.SharedObjects.FirstOrDefault(o => o.Id.Value.ToString() == kvp.Key); + if (globalObj != null) + { + var state = kvp.Value; + globalObj.Left = state.Left; + globalObj.Top = state.Top; + globalObj.Angulo = state.Rotation; + + // Actualizar Show_On_This_Page que ahora maneja internamente showOnThisPagesList + if (state.IsVisible && !globalObj.Show_On_This_Page) + globalObj.Show_On_This_Page = true; + else if (!state.IsVisible && globalObj.Show_On_This_Page) + globalObj.Show_On_This_Page = false; + + // Aplicar propiedades personalizadas + foreach (var prop in state.CustomProperties) + { + var property = globalObj.GetType().GetProperty(prop.Key); + if (property != null && property.CanWrite) + { + property.SetValue(globalObj, prop.Value); + } + } + } + } + } + + private async Task RestoreObjectAsync(osBase obj) + { + if (obj != null) + { + obj.CheckData(); + await Task.Run(() => + { + Application.Current.Dispatcher.Invoke(() => + { + CrearUserControlDesdeObjetoSimulable(obj); + }); + }); + } + } + + private bool CrearUserControlDesdeObjetoSimulable(osBase osObjeto) + { + Type tipoObjeto = osObjeto.GetType(); + UserControl userControl = UserControlFactory.GetControlForType(tipoObjeto); + + if (userControl != null) + { + UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager); + osObjeto._mainViewModel = _mainViewModel; + + if (osObjeto.Id == null) + { + osObjeto.Id = new UniqueId().ObtenerNuevaID(); + } + + _mainViewModel.MainWindow.AgregarRegistrarUserControlCanvas(userControl); + return true; + } + return false; + } + + public async Task SavePageStateAsync(string pageId) + { + if (!_loadedPages.TryGetValue(pageId, out var pageState)) + return; + + var path = Path.Combine(_basePath, $"page_{pageId}.json"); + var backupPath = Path.ChangeExtension(path, ".bak"); + + // Crear backup + if (File.Exists(path)) + { + File.Copy(path, backupPath, true); + } + + // Actualizar estado de objetos globales + pageState.GlobalObjectStates.Clear(); + foreach (var obj in _mainViewModel.ObjetosSimulables.Where(o => o.Enable_On_All_Pages)) + { + var currentPageId = _mainViewModel.SelectedImage; + pageState.GlobalObjectStates[obj.Id.Value.ToString()] = new PageObjectState + { + GlobalObjectId = obj.Id.Value.ToString(), + IsVisible = obj.Show_On_This_Page, + Left = obj.Left, + Top = obj.Top, + Rotation = obj.Angulo, + CustomProperties = CaptureCustomProperties(obj) + }; + } + + try + { + // Preparar objetos para serialización + foreach (var obj in pageState.LocalObjects) + { + obj.SalvarDatosNoSerializables(); + } + + var json = JsonConvert.SerializeObject(pageState, GetSerializerSettings()); + await File.WriteAllTextAsync(path, json); + _hasUnsavedChanges = false; + PageStateChanged?.Invoke(this, pageId); + } + finally + { + // Restaurar estado de objetos + foreach (var obj in pageState.LocalObjects) + { + obj.RestaurarDatosNoSerializables(); + } + } + } + + private Dictionary CaptureCustomProperties(osBase obj) + { + var customProps = new Dictionary(); + var properties = obj.GetType().GetProperties() + .Where(p => p.GetCustomAttributes(typeof(SerializeAttribute), true).Any()); + + foreach (var prop in properties) + { + customProps[prop.Name] = prop.GetValue(obj); + } + + return customProps; + } + + public async Task SaveAllAsync() + { + foreach (var pageId in _loadedPages.Keys) + { + await SavePageStateAsync(pageId); + } + await SaveGlobalStateAsync(); + } + + public void Dispose() + { + if (_hasUnsavedChanges) + { + SaveAllAsync().Wait(); + } + } + } + + /// + /// Atributo para marcar propiedades que deben ser serializadas como propiedades personalizadas + /// + [AttributeUsage(AttributeTargets.Property)] + public class SerializeAttribute : Attribute { } +} \ No newline at end of file diff --git a/FuncionesBase/Idiomas.cs b/FuncionesBase/Idiomas.cs new file mode 100644 index 0000000..8d2756a --- /dev/null +++ b/FuncionesBase/Idiomas.cs @@ -0,0 +1,68 @@ +using LanguageDetection; +using System.Collections.Generic; +using System.ComponentModel; +using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; + +namespace CtrEditor.FuncionesBase +{ + public class Idiomas + { + public const string DEFAULT_LANGUAGE = "English"; + + private static readonly Dictionary _languageMap = new Dictionary + { + { "en", "English" }, + { "es", "Spanish" }, + { "it", "Italian" }, + { "pt", "Portuguese" }, + { "fr", "French" }, + { "de", "German" } + }; + + private static readonly LanguageDetector _languageDetector; + + static Idiomas() + { + _languageDetector = new LanguageDetector(); + _languageDetector.AddLanguages("en", "es", "it", "pt", "fr", "de"); + } + + public static List GetLanguageList() + { + return new List(_languageMap.Values); + } + + public static string DetectarIdioma(string texto) + { + if (string.IsNullOrEmpty(texto)) return DEFAULT_LANGUAGE; + + try + { + string detectedLanguageCode = _languageDetector.Detect(texto); + return _languageMap.GetValueOrDefault(detectedLanguageCode, DEFAULT_LANGUAGE); + } + catch + { + return DEFAULT_LANGUAGE; + } + } + + public static string GetLanguageCode(string languageName) + { + return _languageMap.FirstOrDefault(x => x.Value == languageName).Key ?? "en"; + } + } + + public class IdiomasItemsSource : IItemsSource + { + public ItemCollection GetValues() + { + ItemCollection items = new ItemCollection(); + foreach (string language in Idiomas.GetLanguageList()) + { + items.Add(language); + } + return items; + } + } +} diff --git a/FuncionesBase/TagPattern.cs b/FuncionesBase/TagPattern.cs new file mode 100644 index 0000000..649defa --- /dev/null +++ b/FuncionesBase/TagPattern.cs @@ -0,0 +1,99 @@ +using System; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; + +namespace CtrEditor.FuncionesBase +{ + public class TagPattern + { + public const string DEFAULT_PATTERN = "DESCRIPCION"; + + private static readonly Dictionary _patternDescriptions = new Dictionary + { + { "Descripcion", "First letter capitalized" }, + { "DESCRIPCION", "All uppercase text" }, + { "Siemens IO Input", "Format: Exxxxx.y (x: 0-65535, y: 0-7)" }, + { "Siemens IO Output", "Format: Axxxxx.y (x: 0-65535, y: 0-7)" }, + { "LETRASNUMEROS", "Format: ABC...123... (1-10 letters + numbers)" }, + { "Numero", "Numeric value" } + }; + + public static List GetPatternList() + { + return new List(_patternDescriptions.Keys); + } + + public static string ApplyPattern(string text, string pattern) + { + if (string.IsNullOrEmpty(text)) return text; + + return pattern switch + { + "Descripcion" => ApplyDescripcionPattern(text), + "DESCRIPCION" => text.ToUpper(), + "Siemens IO Input" => ApplySiemensPattern(text, "E"), + "Siemens IO Output" => ApplySiemensPattern(text, "A"), + "LETRASNUMEROS" => ApplyLetrasNumerosPattern(text), + "Numero" => ApplyNumberPattern(text), + _ => text + }; + } + + private static string ApplyDescripcionPattern(string text) + { + if (string.IsNullOrEmpty(text)) return text; + return char.ToUpper(text[0]) + (text.Length > 1 ? text.Substring(1).ToLower() : ""); + } + + private static string ApplySiemensPattern(string text, string prefix) + { + var match = Regex.Match(text, $"{prefix}?(\\d{{1,5}})\\.?(\\d)?", RegexOptions.IgnoreCase); + if (match.Success) + { + int address = Math.Min(int.Parse(match.Groups[1].Value), 65535); + int bit = match.Groups[2].Success ? + Math.Min(int.Parse(match.Groups[2].Value), 7) : 0; + return $"{prefix}{address}.{bit}"; + } + return $"{prefix}0.0"; // Default value if pattern doesn't match + } + + private static string ApplyLetrasNumerosPattern(string text) + { + // Extract letters and numbers from the text + var letters = new string(text.Where(c => char.IsLetter(c)).Take(10).ToArray()); + var numbers = new string(text.Where(c => char.IsDigit(c)).ToArray()); + + // If no letters found, return "A" as default + if (string.IsNullOrEmpty(letters)) + letters = "A"; + + // If no numbers found, return "0" as default + if (string.IsNullOrEmpty(numbers)) + numbers = "0"; + + // Combine letters (uppercase) and numbers + return $"{letters.ToUpper()}{numbers}"; + } + + private static string ApplyNumberPattern(string text) + { + var match = Regex.Match(text, @"-?\d+\.?\d*"); + return match.Success ? match.Value : "0"; + } + } + + public class TagPatternItemsSource : IItemsSource + { + public ItemCollection GetValues() + { + ItemCollection items = new ItemCollection(); + foreach (string pattern in TagPattern.GetPatternList()) + { + items.Add(pattern); + } + return items; + } + } +} diff --git a/IA/gtpask.cs b/IA/gtpask.cs index 3afbaf2..c08a982 100644 --- a/IA/gtpask.cs +++ b/IA/gtpask.cs @@ -9,6 +9,7 @@ using LanguageDetection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Diagnostics; +using CtrEditor.FuncionesBase; namespace GTPCorrgir { @@ -70,15 +71,6 @@ namespace GTPCorrgir private string _grokApiKey; private readonly HttpClient _httpClient; private bool _disposed; - private readonly LanguageDetector _languageDetector; - - private readonly Dictionary _languageMap = new Dictionary - { - { "en", "English" }, - { "es", "Spanish" }, - { "it", "Italian" }, - { "pt", "Portuguese" } - }; public string IdiomaDetectado { get; private set; } public string TextoACorregir { get; set; } @@ -90,8 +82,6 @@ namespace GTPCorrgir try { _httpClient = new HttpClient(); - _languageDetector = new LanguageDetector(); - _languageDetector.AddLanguages("en", "es", "it", "pt"); LoadApiKeys(); InitializeHttpClient(); @@ -153,20 +143,15 @@ namespace GTPCorrgir { try { - - string detectedLanguageCode = _languageDetector.Detect(TextoACorregir); - IdiomaDetectado = _languageMap.GetValueOrDefault(detectedLanguageCode, "Desconocido"); - - return IdiomaDetectado != "Desconocido"; + IdiomaDetectado = Idiomas.DetectarIdioma(TextoACorregir); + return IdiomaDetectado != "Unknown"; } - catch (Exception ex) + catch { return false; } } - - private async Task ProcesarTextoConLLM( Opciones Modelo) { try diff --git a/Icons/match.png b/Icons/match.png new file mode 100644 index 0000000..358ddf4 Binary files /dev/null and b/Icons/match.png differ diff --git a/Icons/ocr.png b/Icons/ocr.png new file mode 100644 index 0000000..3ecec7d Binary files /dev/null and b/Icons/ocr.png differ diff --git a/MainViewModel.cs b/MainViewModel.cs index 65f49a0..285a2f6 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -43,9 +43,24 @@ namespace CtrEditor public Canvas MainCanvas; - public bool IsConnected + [ObservableProperty] + private bool isConnected; + + partial void OnPLCViewModelChanged(PLCViewModel value) { - get => PLCViewModel.IsConnected; + if (value != null) + { + value.PropertyChanged += PLCViewModel_PropertyChanged; + IsConnected = value.IsConnected; + } + } + + private void PLCViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(PLCViewModel.IsConnected)) + { + IsConnected = PLCViewModel.IsConnected; + } } [ObservableProperty] @@ -64,8 +79,6 @@ namespace CtrEditor public ICommand TBStartSimulationCommand { get; } public ICommand TBStopSimulationCommand { get; } public ICommand TBSaveCommand { get; } - public ICommand TBConnectPLCCommand { get; } - public ICommand TBDisconnectPLCCommand { get; } public ICommand TBExtractTagsCommand { get; } public ICommand TBEliminarUserControlCommand { get; } @@ -79,9 +92,17 @@ namespace CtrEditor public ICommand TBAssingPagesCommand { get; } public ICommand TBMultiPageExtractTagsCommand { get; } public ICommand TBMultiPageAnalizeCommand { get; } - public ICommand TBAnalyzeMatrixCommand { get; } public ICommand TBMultiPageMatrixCommand { get; } + + public ICommand TBTogglePLCConnectionCommand => new RelayCommand(() => + { + if (IsConnected) + DisconnectPLC(); + else + ConnectPLC(); + }); + // Evento que se dispara cuando se selecciona una nueva imagen public event EventHandler ImageSelected; public event EventHandler TickSimulacion; @@ -202,27 +223,24 @@ namespace CtrEditor [ObservableProperty] private string selectedImage; + partial void OnSelectedImageChanging(string? oldValue, string newValue) + { + if (HasUnsavedChanges && !inhibitSaveChangesControl) + { + var result = MessageBox.Show("There are unsaved changes. Do you want to save them?", + "Save Changes", + MessageBoxButton.YesNo); + + if (result == MessageBoxResult.Yes) + { + SaveStateObjetosSimulables(); + } + } + } partial void OnSelectedImageChanged(string value) { if (value != null) { - if (HasUnsavedChanges && !inhibitSaveChangesControl) - { - var result = MessageBox.Show("There are unsaved changes. Do you want to save them?", - "Save Changes", - MessageBoxButton.YesNoCancel); - - if (result == MessageBoxResult.Cancel) - { - OnPropertyChanged(nameof(SelectedImage)); // Restore previous selection - return; - } - else if (result == MessageBoxResult.Yes) - { - SaveStateObjetosSimulables(); - } - } - StopSimulation(); ImageSelected?.Invoke(this, datosDeTrabajo.Imagenes[value]); LoadStateObjetosSimulables(); @@ -271,6 +289,8 @@ namespace CtrEditor // Constructor // + private StateManager _stateManager; + public MainViewModel() { OpenWorkDirectoryCommand = new RelayCommand(OpenWorkDirectory); @@ -289,7 +309,7 @@ namespace CtrEditor PLCViewModel = new PLCViewModel(); _timerPLCUpdate = new DispatcherTimer(); - _timerPLCUpdate.Interval = TimeSpan.FromMilliseconds(50); // ajusta el intervalo según sea necesario + _timerPLCUpdate.Interval = TimeSpan.FromMilliseconds(10); // ajusta el intervalo según sea necesario _timerPLCUpdate.Tick += OnRefreshEvent; InitializeTipoSimulableList(); @@ -306,21 +326,15 @@ namespace CtrEditor TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning); TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning); TBSaveCommand = new RelayCommand(Save); - TBConnectPLCCommand = new RelayCommand(ConnectPLC, () => !PLCViewModel.IsConnected); - TBDisconnectPLCCommand = new RelayCommand(DisconnectPLC, () => PLCViewModel.IsConnected); TBEliminarUserControlCommand = new RelayCommand(EliminarUserControl, () => habilitarEliminarUserControl); TBDuplicarUserControlCommand = new RelayCommand(DuplicarUserControl, () => habilitarEliminarUserControl); - TBExtractTagsCommand = new RelayCommand(ExtraerTags); - TBEliminarTodosCommand = new RelayCommand(EliminarTodosCommand); TBEliminarAutoCreatedCommand = new RelayCommand(EliminarAutoCreatedCommand); TBEliminarClonedCommand = new RelayCommand(EliminarClonedCommand); TBAssingPagesCommand = new RelayCommand(AssingPagesCommand); - TBMultiPageExtractTagsCommand = new RelayCommand(MultiPageExtractTagsCommand); TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand); - TBAnalyzeMatrixCommand = new RelayCommand(AnalyzeMatrixCommand); TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand); stopwatch_Sim = new Stopwatch(); @@ -334,6 +348,8 @@ namespace CtrEditor recentDirectories = new ObservableCollection(EstadoPersistente.Instance.RecentDirectories); OpenRecentDirectoryCommand = new RelayCommand(OpenRecentDirectory); + + _stateManager = new StateManager(EstadoPersistente.Instance.directorio, this); } private void OsListFilter_PropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -544,51 +560,6 @@ namespace CtrEditor Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle); } - - private async void MultiPageExtractTagsCommand() - { - if (HasUnsavedChanges) - { - var result = MessageBox.Show("There are unsaved changes. Do you want to save them?", - "Save Changes", - MessageBoxButton.YesNoCancel); - - if (result == MessageBoxResult.Cancel) - return; - else if (result == MessageBoxResult.Yes) - SaveStateObjetosSimulables(); - } - - var ImagenesSeleccionadas = new ObservableCollection - { - SelectedImage - }; - - StopSimulation(); - - var selectPagesWindow = new SelectPages(); - var selectPagesViewModel = new SelectPagesViewModel(); - selectPagesViewModel.Initialize(this, selectPagesWindow, ref ImagenesSeleccionadas); - selectPagesWindow.DataContext = selectPagesViewModel; - selectPagesWindow.ShowDialog(); - - inhibitSaveChangesControl = true; - try - { - if (selectPagesWindow.DataContext is SelectPagesViewModel dialog && dialog.CloseOK) - foreach (var page in ImagenesSeleccionadas) - { - SelectedImage = page; - await WaitForUIUpdateAsync(); // Espera a que la UI se actualice - ExtraerTags(); - } - } - finally - { - inhibitSaveChangesControl = false; - } - } - private async void MultiPageAnalizeCommand() { var ImagenesSeleccionadas = new ObservableCollection @@ -641,148 +612,6 @@ namespace CtrEditor } - - /// - /// Extrae y formatea las etiquetas de los objetos simulables y las guarda en un archivo Excel. - /// - private void ExtraerTags() - { - // Obtiene la ruta del archivo Excel donde se guardarán los datos. - var filePath = DatosDeTrabajo.ObtenerPathAllPages(".xlsx"); - - try - { - // Crea o abre un libro de Excel. - XLWorkbook workbook = File.Exists(filePath) ? new XLWorkbook(filePath) : new XLWorkbook(); - var sheetName = "TagsExtracted"; - // Obtiene o crea la hoja de trabajo "TagsExtracted". - var worksheet = workbook.Worksheets.Contains(sheetName) ? workbook.Worksheet(sheetName) : workbook.Worksheets.Add(sheetName); - var lastRowUsed = worksheet.LastRowUsed(); - // Determina la fila en la que se empezarán a escribir los datos. - int rowOffset = lastRowUsed == null ? 2 : lastRowUsed.RowNumber() + 1; - - // Determina la columna fija más alta. - List columnasOcupadas = new List(); - int actualMaxCol = 0; - int col = 0; - - // Filtrar los objetos de tipo osExtraccionTag y crear una nueva lista - var osBuscarCoincidencias_List = ObjetosSimulables - .OfType() - .Where(tag => tag.Show_On_This_Page) - .ToList(); - var osExtraccionTagBaseGrouped_List = ObjetosSimulables - .OfType() - .Where(tag => tag.Show_On_This_Page && !tag.Cloned && tag.Id_Search_Templates != null && tag.Id_Search_Templates != "") - .ToList(); - var osExtraccionTagBaseFix_List = ObjetosSimulables - .OfType() - .Where(tag => tag.Show_On_This_Page && !tag.Cloned && (tag.Id_Search_Templates == null || tag.Id_Search_Templates == "")) - .ToList(); - - var osExtraccionTagCloned_List = ObjetosSimulables - .OfType() - .Where(tag => tag.Show_On_This_Page && tag.Cloned) - .ToList(); - - // Columnas Fijas para los Tags no agrupados que no son clonados - foreach (var objExtraccionTag in osExtraccionTagBaseFix_List) - if ((string.IsNullOrEmpty(objExtraccionTag.Id_Search_Templates)) && !objExtraccionTag.Cloned) - { - col = objExtraccionTag.Collumn_number; - if (col == 0 || columnasOcupadas.Contains(col)) - col = ++actualMaxCol; - else - actualMaxCol = Math.Max(actualMaxCol, col); - - columnasOcupadas.Add(col); - objExtraccionTag.Collumn_number = col; - } - - // Tags Agrupados no Clonados - foreach (var objBC in osBuscarCoincidencias_List) - foreach (var objExtraccionTag in osExtraccionTagBaseGrouped_List) - if (objExtraccionTag.Id_Search_Templates == objBC.Nombre && !objExtraccionTag.Cloned) - { - col = objExtraccionTag.Collumn_number; - if (col == 0 || columnasOcupadas.Contains(col)) - col = ++actualMaxCol; - else - actualMaxCol = Math.Max(actualMaxCol, col); - - columnasOcupadas.Add(col); - objExtraccionTag.Collumn_number = col; - } - - int RowToRender = 0; - // Cloned Tag - Asignar las mismas columnas - foreach (var oFrom in osExtraccionTagBaseGrouped_List) - foreach (var oCloned in osExtraccionTagCloned_List) - { - if (oCloned.Cloned_from == oFrom.Id) - oCloned.Collumn_number = oFrom.Collumn_number; - RowToRender = Math.Max(RowToRender, oCloned.Copy_Number); - } - - // Render Rows - for (int row = 0; row < RowToRender; row++) - { - // Render Fix tags - foreach (var TagFixs in osExtraccionTagBaseFix_List) - { - col = TagFixs.Collumn_number; - if (worksheet.Cell(1, col).IsEmpty()) - worksheet.Cell(1, col).Value = TagFixs.Collumn_name; - TagFixs.CaptureImageAreaAndDoOCR(); - worksheet.Cell(row + rowOffset, col).Value = TagFixs.Tag_extract; - } - // Render Cloned tags - foreach (var TagCloned in osExtraccionTagCloned_List) - { - if (TagCloned.Copy_Number == row) // Estamos en la fila correcta - { - col = TagCloned.Collumn_number; - if (worksheet.Cell(1, col).IsEmpty()) - worksheet.Cell(1, col).Value = TagCloned.Collumn_name; - - TagCloned.CaptureImageAreaAndDoOCR(); - worksheet.Cell(row + rowOffset, col).Value = TagCloned.Tag_extract; - } - } - } - - - // Formatear los títulos en la fila 1 - var titleRow = worksheet.Row(1); - titleRow.Style.Font.Bold = true; - titleRow.Style.Fill.BackgroundColor = XLColor.LightGray; - titleRow.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; - - // Auto dimensionado de las columnas utilizadas - worksheet.Columns().AdjustToContents(); - - // Guarda el libro de Excel. - workbook.SaveAs(filePath); - HasUnsavedChanges = false; - } - catch (IOException ex) - { - // Muestra un diálogo de error si ocurre una excepción de IO. - var dialog = new TaskDialog - { - WindowTitle = "IOException", - MainInstruction = "Error al acceder al archivo", - Content = $"El proceso no puede acceder al archivo '{filePath}' porque está siendo utilizado por otro proceso.", - ExpandedInformation = ex.ToString(), - MainIcon = TaskDialogIcon.Error, - ButtonStyle = TaskDialogButtonStyle.Standard - }; - dialog.Buttons.Add(new TaskDialogButton(ButtonType.Ok)); - dialog.ShowDialog(); - } - } - - private void InitializeTipoSimulableList() { var baseType = typeof(osBase); @@ -1142,15 +971,6 @@ namespace CtrEditor Application.Current.Shutdown(); } - private void AnalyzeMatrixCommand() - { - var matrixPreviewWindow = new MatrixPreviewWindow(); - var matrixPreviewViewModel = new MatrixPreviewViewModel(); - matrixPreviewViewModel.Initialize(this, matrixPreviewWindow); - matrixPreviewWindow.DataContext = matrixPreviewViewModel; - matrixPreviewWindow.ShowDialog(); - } - private async void MultiPageMatrixCommand() { if (HasUnsavedChanges) diff --git a/MainWindow.xaml b/MainWindow.xaml index 761712c..921e08e 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -27,13 +27,21 @@ + + + + + + @@ -116,42 +124,29 @@ - - - - + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index fc4802f..a1719f3 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -288,6 +288,15 @@ namespace CtrEditor new Tuple(new Point(rectBox.Right, rectBox.Top + rectBox.Height / 2), "CenterRight") }; + // Add validation before setting Canvas position + void SetCanvasPosition(UIElement element, double left, double top) + { + if (!double.IsInfinity(left) && !double.IsNaN(left)) + Canvas.SetLeft(element, left); + if (!double.IsInfinity(top) && !double.IsNaN(top)) + Canvas.SetTop(element, top); + } + foreach (var position in positions) { Rectangle rect = new Rectangle @@ -337,8 +346,8 @@ namespace CtrEditor break; } - Canvas.SetLeft(rect, position.Item1.X - rectSize / 2); - Canvas.SetTop(rect, position.Item1.Y - rectSize / 2); + // Replace direct Canvas.Set calls with the validation method + SetCanvasPosition(rect, position.Item1.X - rectSize / 2, position.Item1.Y - rectSize / 2); rect.MouseLeftButtonDown += UserControl_MouseLeftButtonDown; rect.MouseMove += UserControl_MouseMove; diff --git a/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs b/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs index 7521d7c..6616a41 100644 --- a/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs +++ b/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs @@ -123,8 +123,6 @@ namespace CtrEditor.ObjetosSim public override void UpdatePLC(PLCViewModel plc, int TotalMilliseconds) { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); elapsedTimeAccumulator += TotalMilliseconds; float randomFactor = (float)(new Random().NextDouble() * 0.1); // 10% random factor @@ -135,12 +133,9 @@ namespace CtrEditor.ObjetosSim motState.UpdatePLC(plc, this, TotalMilliseconds); elapsedTimeAccumulator = 0; } - Velocidad = (Proporcional_Speed / 100) * (motState.STATUS_VFD_ACT_Speed_Hz / 10); Sentido_contrario = motState.OUT_Reversal; - stopwatch.Stop(); - Debug.WriteLine($" osVMmotorSim : {Nombre} : {stopwatch.Elapsed.TotalMilliseconds} ms"); } public override void UpdateControl(int TotalMilliseconds) diff --git a/ObjetosSim/Extraccion Datos/ucBuscarCoincidencias.xaml.cs b/ObjetosSim/Extraccion Datos/ucBuscarCoincidencias.xaml.cs index 590b00f..d1424ae 100644 --- a/ObjetosSim/Extraccion Datos/ucBuscarCoincidencias.xaml.cs +++ b/ObjetosSim/Extraccion Datos/ucBuscarCoincidencias.xaml.cs @@ -27,9 +27,40 @@ using Emgu.CV.Structure; namespace CtrEditor.ObjetosSim.Extraccion_Datos { /// - /// Interaction logic for ucBuscarCoincidencias.xaml + /// Represents a template search control that identifies similar patterns in images and creates tag extraction clones. + /// This class is designed to work with OCR extraction by finding visual patterns and creating copies of extraction tags + /// at each matching location. /// + /// + /// Key functionalities: + /// - Template matching using OpenCV + /// - Automatic tag cloning at found locations + /// - OCR text extraction from matched regions + /// - Export capabilities to Excel /// + /// Workflow: + /// 1. User creates a search template by positioning and sizing the control over a pattern + /// 2. Links extraction tags to this template using Id_Search_Templates + /// 3. Activates search_templates to find similar patterns + /// 4. The system automatically: + /// - Searches for visual matches in the image + /// - Creates clones of linked extraction tags at each match + /// - Assigns incremental copy_Number to organize rows in exports + /// - Performs OCR on each cloned tag location + /// + /// Properties: + /// - search_templates: Triggers the pattern search process + /// - threshold: Minimum similarity threshold for pattern matching + /// - coincidencias: Number of matches found (readonly) + /// - show_debug_ocr: Shows debug windows during OCR process + /// - export_ocr: Triggers OCR text export for all matches + /// + /// Usage example: + /// 1. Position the search template over a repeating pattern + /// 2. Create extraction tags and link them to this template + /// 3. Set threshold value (default usually works well) + /// 4. Activate search_templates to find matches and create clones + /// public partial class osBuscarCoincidencias : osBase, IosBase { @@ -362,9 +393,6 @@ namespace CtrEditor.ObjetosSim.Extraccion_Datos } } Row++; - if (newObj != null) - newObj.New_Row = true; - } } diff --git a/ObjetosSim/Extraccion Datos/ucExtraccionTag.xaml.cs b/ObjetosSim/Extraccion Datos/ucExtraccionTag.xaml.cs index 2631580..13654cc 100644 --- a/ObjetosSim/Extraccion Datos/ucExtraccionTag.xaml.cs +++ b/ObjetosSim/Extraccion Datos/ucExtraccionTag.xaml.cs @@ -36,7 +36,18 @@ namespace CtrEditor.ObjetosSim.Extraccion_Datos bool extraer; [ObservableProperty] - bool new_Row; + [property: Category("Tag Extraction:")] + bool eliminar_enters; + + [ObservableProperty] + [property: Category("Tag Extraction:")] + [property: ItemsSource(typeof(IdiomasItemsSource))] + string idioma_Extraccion; + + [ObservableProperty] + [property: Category("Tag Extraction:")] + [property: ItemsSource(typeof(TagPatternItemsSource))] + string pattern_Type; public override void TopChanged(float value) { @@ -147,11 +158,39 @@ namespace CtrEditor.ObjetosSim.Extraccion_Datos Alto = 1; Angulo = 0; Opacity_oculto = 0.1f; + Idioma_Extraccion = Idiomas.DEFAULT_LANGUAGE; + Pattern_Type = TagPattern.DEFAULT_PATTERN; } public void CaptureImageAreaAndDoOCR() { - Tag_extract = CaptureImageAreaAndDoOCR(Left, Top, Ancho, Alto, Angulo, Show_Debug_Window); + string extractedText = CaptureImageAreaAndDoOCR(Left, Top, Ancho, Alto, Angulo, Show_Debug_Window); + + // Clean up the extracted text if eliminar_enters is true + if (Eliminar_enters && !string.IsNullOrEmpty(extractedText)) + { + // Replace all types of line endings with spaces + extractedText = extractedText.Replace("\r\n", " ") + .Replace("\n", " ") + .Replace("\r", " "); + + // Replace multiple consecutive spaces with a single space + extractedText = System.Text.RegularExpressions.Regex.Replace(extractedText, @"\s+", " "); + + // Trim spaces at the beginning and end + extractedText = extractedText.Trim(); + } + + // Apply the selected pattern + extractedText = TagPattern.ApplyPattern(extractedText, Pattern_Type); + + Tag_extract = extractedText; + + // Set default language to English if not set + if (string.IsNullOrEmpty(Idioma_Extraccion)) + { + Idioma_Extraccion = Idiomas.DEFAULT_LANGUAGE; + } } public int ExportToExcel(IXLWorksheet worksheet, int row, int colBase) diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs index e26f732..df4cdce 100644 --- a/ObjetosSim/osBase.cs +++ b/ObjetosSim/osBase.cs @@ -312,7 +312,7 @@ namespace CtrEditor.ObjetosSim if (e.PropertyName == nameof(osFramePlate.Left)) { Left += ((osFramePlate)sender).offsetX; - OnMoveResizeRotate(); + OnMoveResizeRotate(); } if (e.PropertyName == nameof(osFramePlate.Zindex_FramePlate)) diff --git a/PopUps/AssignImagesWindow.xaml.cs b/PopUps/AssignImagesWindow.xaml.cs index aec6359..a2e485f 100644 --- a/PopUps/AssignImagesWindow.xaml.cs +++ b/PopUps/AssignImagesWindow.xaml.cs @@ -12,7 +12,7 @@ namespace CtrEditor.PopUps /// Interaction logic for AssignImagesWindow.xaml /// public partial class AssignImagesWindow : Window - { + { public AssignImagesWindow() { InitializeComponent(); diff --git a/PopUps/ColumnSelectionDialog.xaml.cs b/PopUps/ColumnSelectionDialog.xaml.cs index a7cc3d0..a34de8d 100644 --- a/PopUps/ColumnSelectionDialog.xaml.cs +++ b/PopUps/ColumnSelectionDialog.xaml.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Windows; +using CtrEditor.FuncionesBase; namespace CtrEditor.PopUps { @@ -12,15 +13,7 @@ namespace CtrEditor.PopUps public string SelectedSourceLanguage { get; private set; } public string SelectedTargetLanguage { get; private set; } - private static readonly List SupportedLanguages = new List - { - "English", - "Spanish", - "Italian", - "French", - "German", - "Portuguese" - }; + private static readonly List SupportedLanguages = Idiomas.GetLanguageList(); private string _sourceColumnLabel = "Idioma 1"; private string _targetColumnLabel = "Idioma 2"; diff --git a/PopUps/MatrixPreviewWindow.xaml b/PopUps/MatrixPreviewWindow.xaml index bf7a0be..c3db2b4 100644 --- a/PopUps/MatrixPreviewWindow.xaml +++ b/PopUps/MatrixPreviewWindow.xaml @@ -1,7 +1,7 @@ + Title="Exportar Tags Extraidos" Height="600" Width="800"> @@ -70,23 +70,23 @@ HorizontalAlignment="Right" Margin="5" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"> -