CtrEditor/MainViewModel.cs

2512 lines
100 KiB
C#

using CtrEditor;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Input;
using Ookii.Dialogs.Wpf;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using CtrEditor.ObjetosSim;
using LibS7Adv; // Using stub implementation
using System.IO;
using Newtonsoft.Json;
using System.Windows;
using System.Text; // Para StringBuilder
using CtrEditor.Simulacion;
using CtrEditor.HydraulicSimulator; // Nuevo using para el simulador hidráulico
using CtrEditor.HydraulicSimulator.TSNet; // Integración TSNet
using System.Diagnostics;
using System.Reflection;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using Xceed.Wpf.Toolkit.PropertyGrid;
using CtrEditor.ObjetosSim.Extraccion_Datos;
using ClosedXML.Excel;
using System.Timers; // Para el nuevo timer de simulación más preciso
using CtrEditor.PopUps;
using System.Windows.Data;
using CommunityToolkit.Mvvm.Input;
using CtrEditor.Services; // Para MCPServer y DebugConsoleServer
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using CtrEditor.Serialization; // Add this line
using CtrEditor.Controls; // Add this using directive
using System.Linq;
using System.Windows.Media;
using CtrEditor.Services; // Add this for MCP Server
namespace CtrEditor
{
public partial class MainViewModel : ObservableObject
{
private readonly StateSerializer _stateSerializer;
public Stopwatch stopwatch_Sim;
private double stopwatch_SimPLC_last;
private double stopwatch_SimModel_last;
private double accumulatedSimTime;
private double accumulatedPlcTime;
private int simSampleCount;
private int plcSampleCount;
// Sistema adaptativo para timing de PLC
private double lastPlcExecutionTime = 0;
private double maxPlcExecutionTime = 0;
private int plcTimingAdaptationCounter = 0;
private const int PLC_ADAPTATION_SAMPLES = 10; // Evaluar cada 10 muestras
private const double MIN_PLC_INTERVAL = 10; // Mínimo intervalo PLC (ms)
private const double MAX_PLC_INTERVAL = 100; // Máximo intervalo PLC (ms)
private const double SIM_PRIORITY_TIME = 10; // Tiempo reservado para simulación (ms)
// Sistema adaptativo para timing de simulación
private double lastSimExecutionTime = 0;
private double maxSimExecutionTime = 0;
private int simTimingAdaptationCounter = 0;
private const int SIM_ADAPTATION_SAMPLES = 5; // Evaluar cada 5 muestras para más responsividad
private const double MIN_SIM_INTERVAL = 10; // Mínimo intervalo simulación (ms)
private const double MAX_SIM_INTERVAL = 100; // Máximo intervalo simulación (ms)
private const double SIM_BUFFER_TIME = 2; // Buffer de 2ms extra respecto al tiempo real
// Variables para promedio móvil de SimulationSpeed
private Queue<double> simSpeedSamples = new Queue<double>();
private double totalSimSpeedTime = 0;
private const double SIM_SPEED_WINDOW_MS = 500; // Ventana de 500ms para promedio
private float TiempoDesdeStartSimulacion;
private bool Debug_SimulacionCreado = false;
public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU();
// ELIMINADO: HydraulicSimulationManager - reemplazado por TSNetSimulationManager
public TSNetSimulationManager tsnetSimulationManager = new TSNetSimulationManager();
private readonly System.Timers.Timer _timerSimulacion; // Cambiado a System.Timers.Timer para mejor precisión
private readonly System.Timers.Timer _timerPLCUpdate; // Cambiado a System.Timers.Timer para mejor precisión
private readonly DispatcherTimer _timerDisplayUpdate;
private readonly DispatcherTimer _timer3DUpdate; // Nuevo timer para actualización 3D cuando simulación está detenida
private readonly System.Timers.Timer _timerDebugFlush; // Timer para flush automático del buffer de debug
private readonly System.Timers.Timer _timerTSNet; // Timer para TSNet automático cada 100ms
// Variables para tracking de IOException
private volatile bool _tsnetExecuting = false; // Flag para evitar ejecuciones simultáneas de TSNet
private DateTime _lastTSNetExecution = DateTime.MinValue; // Timestamp de última ejecución TSNet
private readonly TimeSpan _minTSNetInterval = TimeSpan.FromMilliseconds(50); // Intervalo mínimo entre ejecuciones
private int _ioExceptionCount = 0; // Contador de excepciones IOException para debugging
public Canvas MainCanvas;
// Manager para la visualización 3D
public BEPUVisualization3DManager Visualization3DManager { get; set; }
// Servidor MCP para control remoto
private MCPServer _mcpServer;
// Servidor de debug console para logging
private DebugConsoleServer _debugConsoleServer;
[ObservableProperty]
private int mcpServerPort = 5006;
[ObservableProperty]
private bool isConnected;
partial void OnPLCViewModelChanged(PLCViewModel value)
{
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]
private DatosDeTrabajo datosDeTrabajo;
[ObservableProperty]
private ObservableCollection<string> listaImagenes; // Publicación de las claves del diccionario
[ObservableProperty]
public ObservableCollection<TipoSimulable> listaOsBase;
[ObservableProperty]
public ObservableCollection<CategoriaNode> categoriasOsBase;
// Diccionario para almacenar datos expandidos de imágenes
public Dictionary<string, Models.ImageData> _imageDataDictionary = new Dictionary<string, Models.ImageData>();
// Comando para renombrar imagen
public ICommand RenameImageCommand { get; private set; }
public ICommand StartSimulationCommand { get; }
public ICommand StopSimulationCommand { get; }
public ICommand ItemDoubleClickCommand { get; private set; }
public ICommand TBStartSimulationCommand { get; }
public ICommand TBStopSimulationCommand { get; }
public ICommand TBStartFluidSimulationCommand { get; }
public ICommand TBStopFluidSimulationCommand { get; }
public ICommand TBSaveCommand { get; }
public ICommand TBExtractTagsCommand { get; }
public ICommand TBEliminarUserControlCommand { get; }
public ICommand TBDuplicarUserControlCommand { get; }
public ICommand OpenWorkDirectoryCommand { get; }
public ICommand TBEliminarTodosCommand { get; }
public ICommand TBEliminarAutoCreatedCommand { get; }
public ICommand TBEliminarClonedCommand { get; }
public ICommand TBAssingPagesCommand { get; }
public ICommand TBMultiPageExtractTagsCommand { get; }
public ICommand TBMultiPageAnalizeCommand { get; }
public ICommand TBMultiPageMatrixCommand { get; }
public ICommand TBLibraryManagerCommand { get; }
// Comandos para vista 3D
public ICommand TB3DViewTopCommand { get; }
public ICommand TB3DViewSideCommand { get; }
public ICommand TB3DViewFrontCommand { get; }
public ICommand TB3DViewIsometricCommand { get; }
// Comandos para TSNet - Temporalmente comentados
//public ICommand TBTestTSNetCommand { get; }
//public ICommand TBRunTSNetSimulationCommand { get; }
//public ICommand TBGenerateINPCommand { get; }
public ICommand TBTogglePLCConnectionCommand => new RelayCommand(() =>
{
if (IsConnected)
DisconnectPLC();
else
ConnectPLC();
});
// Evento que se dispara cuando se selecciona una nueva imagen
public event EventHandler<string> ImageSelected;
public event EventHandler<TickSimulacionEventArgs> TickSimulacion;
// Propiedades
private bool habilitarEliminarUserControl;
private MainWindow mainWindow;
private ObjectManipulationManager _objectManager; // Add this line
public MainWindow MainWindow
{
get => mainWindow;
set
{
mainWindow = value;
_objectManager = mainWindow._objectManager; // Initialize _objectManager
// Add the filter changed event handler here instead of in constructor
if (mainWindow.VisFilter != null)
{
mainWindow.VisFilter.FilterChanged += (s, e) => OnVisFilterChanged(e.FilterViewModel);
}
}
}
[ObservableProperty]
private float canvasLeft;
[ObservableProperty]
private float canvasTop;
[ObservableProperty]
private bool isSimulationRunning;
[ObservableProperty]
private bool hasUnsavedChanges;
[ObservableProperty]
private ObservableCollection<string> recentDirectories;
private bool inhibitSaveChangesControl;
public ICommand OpenRecentDirectoryCommand { get; private set; }
partial void OnIsSimulationRunningChanged(bool value)
{
CommandManager.InvalidateRequerySuggested(); // Notificar que el estado de los comandos ha cambiado
// Controlar el timer de actualización 3D
if (value)
{
// Simulación iniciada - detener timer 3D (la sincronización se hace en Step())
_timer3DUpdate.Stop();
}
else
{
// Simulación detenida - iniciar timer 3D para mantener sincronización
_timer3DUpdate.Start();
}
}
partial void OnHasUnsavedChangesChanged(bool value)
{
// Notificar el cambio del título indirectamente a través de directorioTrabajo
OnPropertyChanged(nameof(directorioTrabajo));
}
public string directorioTrabajo
{
get => EstadoPersistente.Instance.directorio;
set
{
if (value != null)
{
EstadoPersistente.Instance.directorio = value; // Actualizar el estado persistente
EstadoPersistente.Instance.GuardarEstado(); // Guardar el estado actualizado
DatosDeTrabajo.CargarImagenes();
ListaImagenes = new ObservableCollection<string>(DatosDeTrabajo.Imagenes.Keys); // Actualizar claves
// Si no hay imágenes en el directorio, copiar base.png desde los recursos
if (!ListaImagenes.Any())
{
CopiarImagenBase(value);
// Recargar las imágenes después de copiar la imagen base
DatosDeTrabajo.CargarImagenes();
ListaImagenes = new ObservableCollection<string>(DatosDeTrabajo.Imagenes.Keys); // Actualizar claves nuevamente
}
SelectedImage = null;
var x = ListaImagenes.FirstOrDefault(o => o == EstadoPersistente.Instance.imagen, null);
if (EstadoPersistente.Instance.imagen != null && EstadoPersistente.Instance.imagen.Length > 0 && x != null)
SelectedImage = EstadoPersistente.Instance.imagen;
else if (ListaImagenes.FirstOrDefault() != null)
SelectedImage = ListaImagenes.FirstOrDefault();
OnPropertyChanged(nameof(directorioTrabajo)); // Notificar el cambio de propiedad
OnPropertyChanged(nameof(ListaImagenes)); // Notificar que la lista de imágenes ha cambiado
AddToRecentDirectories(value);
}
}
}
// Función para copiar la imagen base desde los recursos
private void CopiarImagenBase(string directorio)
{
try
{
// Obtener el path del archivo base.png desde los recursos (dentro del ensamblado)
var assembly = Assembly.GetExecutingAssembly();
var resourcePath = "CtrEditor.Images.base.png"; // Ajusta el namespace y el path según tu proyecto
using (Stream resourceStream = assembly.GetManifestResourceStream(resourcePath))
{
if (resourceStream != null)
{
string destino = Path.Combine(directorio, "base.png");
using (FileStream fileStream = new FileStream(destino, FileMode.Create, FileAccess.Write))
{
resourceStream.CopyTo(fileStream);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show($"Error al copiar la imagen base: {ex.Message}");
}
}
[RelayCommand]
public void DebugWindow()
{
MainWindow.DebugWindow();
}
[ObservableProperty]
private PLCViewModel pLCViewModel;
[ObservableProperty]
private Models.WorkspaceConfiguration workspaceConfig;
[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)
{
StopSimulation();
ImageSelected?.Invoke(this, datosDeTrabajo.Imagenes[value]);
LoadStateObjetosSimulables();
EstadoPersistente.Instance.imagen = value;
EstadoPersistente.Instance.GuardarEstado();
HasUnsavedChanges = false;
}
}
[ObservableProperty]
private osBase selectedItemOsList;
partial void OnSelectedItemOsListChanged(osBase value)
{
// Enable delete and duplicate commands when either an individual item is selected
// or when there are multiple objects selected
habilitarEliminarUserControl = _objectManager.SelectedObjects.Count > 0;
}
[ObservableProperty]
private TipoSimulable selectedItem;
public ICollectionView ObjetosSimulablesAllPages { get; }
[ObservableProperty]
public ObservableCollection<osBase> objetosSimulables;
[ObservableProperty]
private bool isMultiSelectionActive;
partial void OnIsMultiSelectionActiveChanged(bool value)
{
_objectManager?.OnMultiSelectionModeChanged(value);
}
partial void OnObjetosSimulablesChanged(ObservableCollection<osBase> value)
{
if (value != null)
{
value.CollectionChanged += ObjetosSimulables_CollectionChanged;
UpdateVisFilterTypes();
}
}
private void ObjetosSimulables_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
UpdateVisFilterTypes();
}
private void UpdateVisFilterTypes()
{
MainWindow?.VisFilter?.UpdateAvailableTypes(ObjetosSimulables.Select(o => o.GetType()).Distinct());
MainWindow?.VisFilter?.UpdateAvailableTags(ObjetosSimulables);
}
//
// Constructor
//
private StateManager _stateManager;
public MainViewModel()
{
OpenWorkDirectoryCommand = new RelayCommand(OpenWorkDirectory);
datosDeTrabajo = new DatosDeTrabajo();
// Initialize ObjetosSimulables first
ObjetosSimulables = new ObservableCollection<osBase>();
ObjetosSimulables = new ObservableCollection<osBase>();
ListaOsBase = new ObservableCollection<TipoSimulable>();
CategoriasOsBase = new ObservableCollection<CategoriaNode>();
// Inicializa el PLCViewModel
PLCViewModel = new PLCViewModel();
_timerPLCUpdate = new System.Timers.Timer();
_timerPLCUpdate.Interval = 10; // 10ms - más preciso que DispatcherTimer
_timerPLCUpdate.Elapsed += OnRefreshEvent;
_timerPLCUpdate.AutoReset = true; // Reinicio automático
_timerPLCUpdate.SynchronizingObject = null; // No sincronizar automáticamente con UI
InitializeTipoSimulableList();
ItemDoubleClickCommand = new ParameterizedRelayCommand(ExecuteDoubleClick);
_timerSimulacion = new System.Timers.Timer();
_timerSimulacion.Interval = 15; // 15ms inicial - más conservador para permitir adaptación
_timerSimulacion.Elapsed += OnTickSimulacion;
_timerSimulacion.AutoReset = true; // Reinicio automático
_timerSimulacion.SynchronizingObject = null; // No sincronizar automáticamente con UI
// Nuevo timer para actualización de display
_timerDisplayUpdate = new DispatcherTimer();
_timerDisplayUpdate.Interval = TimeSpan.FromMilliseconds(250);
_timerDisplayUpdate.Tick += OnDisplayUpdate;
_timerDisplayUpdate.Start();
_timer3DUpdate = new DispatcherTimer();
_timer3DUpdate.Interval = TimeSpan.FromMilliseconds(20);
_timer3DUpdate.Tick += OnTick3DUpdate;
_timer3DUpdate.Start(); // Iniciar porque la simulación empieza detenida
// Timer para flush automático del buffer de debug (cada 5 minutos)
_timerDebugFlush = new System.Timers.Timer();
_timerDebugFlush.Interval = 300000; // 5 minutos = 300,000 ms
_timerDebugFlush.Elapsed += OnDebugFlushTimer;
_timerDebugFlush.AutoReset = true;
_timerDebugFlush.Start();
// Timer para TSNet automático (cada 100ms)
_timerTSNet = new System.Timers.Timer();
_timerTSNet.Interval = 100; // 100ms para TSNet
_timerTSNet.Elapsed += OnTSNetTimerElapsed;
_timerTSNet.AutoReset = true;
_timerTSNet.SynchronizingObject = null; // No sincronizar automáticamente con UI
StartSimulationCommand = new RelayCommand(StartSimulation);
StopSimulationCommand = new RelayCommand(StopSimulation);
TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning);
TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning);
TBSaveCommand = new RelayCommand(Save);
TBEliminarUserControlCommand = new RelayCommand(EliminarUserControl, () => habilitarEliminarUserControl);
TBDuplicarUserControlCommand = new RelayCommand(DuplicarUserControl, () => habilitarEliminarUserControl);
TBEliminarTodosCommand = new RelayCommand(EliminarTodosCommand);
TBEliminarAutoCreatedCommand = new RelayCommand(EliminarAutoCreatedCommand);
TBEliminarClonedCommand = new RelayCommand(EliminarClonedCommand);
TBAssingPagesCommand = new RelayCommand(AssingPagesCommand);
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
TBLibraryManagerCommand = new RelayCommand(ShowLibraryManager);
// Comandos para vista 3D
TB3DViewTopCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Top));
TB3DViewSideCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Side));
TB3DViewFrontCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Front));
TB3DViewIsometricCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Isometric));
TBToggle3DUpdateCommand = new RelayCommand(() => Is3DUpdateEnabled = !Is3DUpdateEnabled);
RenameImageCommand = new RelayCommand<string>(RenameImage);
// Comandos para TSNet - Temporalmente comentados para compilación
//TBTestTSNetCommand = new RelayCommand(() => TestTSNetIntegrationSync());
//TBRunTSNetSimulationCommand = new RelayCommand(() => RunTSNetSimulationSync());
//TBGenerateINPCommand = new RelayCommand(() => GenerateINPFileSync());
stopwatch_Sim = new Stopwatch();
stopwatch_Sim.Start();
ObjetosSimulables.CollectionChanged += (s, e) =>
{
if (e.Action != NotifyCollectionChangedAction.Move)
HasUnsavedChanges = true;
};
recentDirectories = new ObservableCollection<string>(EstadoPersistente.Instance.RecentDirectories);
OpenRecentDirectoryCommand = new RelayCommand<string>(OpenRecentDirectory);
_stateManager = new StateManager(EstadoPersistente.Instance.directorio, this);
_stateSerializer = new StateSerializer(this, datosDeTrabajo, simulationManager);
// Conectar DatosDeTrabajo con este ViewModel para el escaneo de imágenes
datosDeTrabajo.SetMainViewModel(this);
// NOTA: La conexión del manager 3D se hace en MainWindow_Loaded
// después de que se cree la instancia de BEPUVisualization3DManager
// Inicializar configuración del workspace
WorkspaceConfig = new Models.WorkspaceConfiguration();
// Configurar tracking de IOException
SetupIOExceptionTraking();
// Iniciar servidor MCP automáticamente
StartMcpServer();
}
/// <summary>
/// Configura el sistema de tracking de IOException mejorado para capturar excepciones de WPF
/// </summary>
private void SetupIOExceptionTraking()
{
// Manejador para excepciones de primera oportunidad (first-chance exceptions)
// Esto captura TODAS las IOException, incluso las que son manejadas internamente por WPF
AppDomain.CurrentDomain.FirstChanceException += (sender, e) =>
{
if (e.Exception is System.IO.IOException ioEx)
{
_ioExceptionCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss:fff");
System.Diagnostics.Debug.WriteLine($"[{timestamp}] 🔍 IOException #{_ioExceptionCount} detectada (First-Chance):");
System.Diagnostics.Debug.WriteLine($" Mensaje: {ioEx.Message}");
System.Diagnostics.Debug.WriteLine($" Source: {ioEx.Source}");
System.Diagnostics.Debug.WriteLine($" HResult: {ioEx.HResult}");
System.Diagnostics.Debug.WriteLine($" Simulación activa: {IsSimulationRunning}");
System.Diagnostics.Debug.WriteLine($" TSNet ejecutándose: {_tsnetExecuting}");
// Verificar si está relacionada con TSNet/Python
if (ioEx.StackTrace?.Contains("Python") == true ||
ioEx.StackTrace?.Contains("tsnet") == true ||
ioEx.Source?.Contains("Presentation") == true)
{
System.Diagnostics.Debug.WriteLine($" ⚠️ Posiblemente relacionada con TSNet/WPF");
}
}
};
System.Diagnostics.Debug.WriteLine("IOException tracking configurado - se capturarán todas las excepciones de E/S");
}
#region Workspace Configuration Management
/// <summary>
/// Captura el estado actual del workspace desde la UI
/// </summary>
public void CaptureWorkspaceState()
{
if (MainWindow == null) return;
try
{
// Capturar posición del GridSplitter (altura de la fila 3 que contiene la vista 3D)
var middleGrid = MainWindow.FindName("MiddleColumnGrid") as Grid;
if (middleGrid != null && middleGrid.RowDefinitions.Count > 3)
{
// Capturar la altura actual de la fila 3 (vista 3D) en píxeles
var row3ActualHeight = middleGrid.RowDefinitions[3].ActualHeight;
WorkspaceConfig.GridSplitterPosition = row3ActualHeight;
}
// Capturar estado del canvas y scroll
var scrollViewer = MainWindow.FindName("ImagenEnTrabajoScrollViewer") as ScrollViewer;
if (scrollViewer != null)
{
WorkspaceConfig.HorizontalScrollOffset = scrollViewer.HorizontalOffset;
WorkspaceConfig.VerticalScrollOffset = scrollViewer.VerticalOffset;
}
// Capturar transformaciones del canvas
var canvas = MainWindow.FindName("ImagenEnTrabajoCanvas") as Canvas;
if (canvas?.RenderTransform is TransformGroup transformGroup)
{
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
if (scaleTransform != null)
{
WorkspaceConfig.ZoomLevel = scaleTransform.ScaleX;
}
if (translateTransform != null)
{
WorkspaceConfig.CanvasOffsetX = translateTransform.X;
WorkspaceConfig.CanvasOffsetY = translateTransform.Y;
}
}
// Capturar centro del canvas
var centerPixels = MainWindow.ObtenerCentroCanvasPixels();
WorkspaceConfig.CanvasCenterX = centerPixels.X;
WorkspaceConfig.CanvasCenterY = centerPixels.Y;
// Capturar estado de Is3DUpdateEnabled
WorkspaceConfig.Is3DUpdateEnabled = Is3DUpdateEnabled;
}
catch (Exception ex)
{
// Log error but don't throw to avoid interrupting save process
System.Diagnostics.Debug.WriteLine($"Error capturing workspace state: {ex.Message}");
}
}
/// <summary>
/// Restaura el estado del workspace a la UI
/// </summary>
public void RestoreWorkspaceState()
{
if (MainWindow == null || WorkspaceConfig == null) return;
try
{
// Restaurar posición del GridSplitter (altura de la fila 3 que contiene la vista 3D)
var middleGrid = MainWindow.FindName("MiddleColumnGrid") as Grid;
if (middleGrid != null && middleGrid.RowDefinitions.Count > 3)
{
// Establecer la altura de la vista 3D en píxeles absolutos
middleGrid.RowDefinitions[3].Height = new GridLength(WorkspaceConfig.GridSplitterPosition, GridUnitType.Pixel);
}
// Restaurar estado del canvas y scroll
var scrollViewer = MainWindow.FindName("ImagenEnTrabajoScrollViewer") as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToHorizontalOffset(WorkspaceConfig.HorizontalScrollOffset);
scrollViewer.ScrollToVerticalOffset(WorkspaceConfig.VerticalScrollOffset);
}
// Restaurar transformaciones del canvas
var canvas = MainWindow.FindName("ImagenEnTrabajoCanvas") as Canvas;
if (canvas?.RenderTransform is TransformGroup transformGroup)
{
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
if (scaleTransform != null)
{
scaleTransform.ScaleX = WorkspaceConfig.ZoomLevel;
scaleTransform.ScaleY = WorkspaceConfig.ZoomLevel;
}
if (translateTransform != null)
{
translateTransform.X = WorkspaceConfig.CanvasOffsetX;
translateTransform.Y = WorkspaceConfig.CanvasOffsetY;
}
}
// Restaurar estado de Is3DUpdateEnabled
Is3DUpdateEnabled = WorkspaceConfig.Is3DUpdateEnabled;
}
catch (Exception ex)
{
// Log error but don't throw to avoid interrupting load process
System.Diagnostics.Debug.WriteLine($"Error restoring workspace state: {ex.Message}");
}
}
/// <summary>
/// Inicializa la configuración del workspace con valores por defecto
/// </summary>
public void InitializeDefaultWorkspaceConfig()
{
WorkspaceConfig = new Models.WorkspaceConfiguration();
}
#endregion
// 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
{
foreach (var obj in ObjetosSimulables)
{
bool isVisible = true;
// Apply Show All filter
if (!filter.ShowAll)
{
// Check type filter
var typeFilter = filter.TypeFilters.FirstOrDefault(tf => tf.Type == obj.GetType());
if (typeFilter == null || !typeFilter.IsSelected)
{
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;
}
obj.IsVisFilter = isVisible;
// Update Canvas object visibility
if (obj.VisualRepresentation != null)
{
obj.VisualRepresentation.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
}
}
}
public void LoadStateObjetosSimulables()
{
if (SelectedImage != null)
{
_stateSerializer.LoadState(SelectedImage);
// Registrar objetos hidráulicos cargados
RegisterLoadedHydraulicObjects();
// 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();
}
}
public void LoadInitialData()
{
// 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();
}
// Crear un nuevo Objeto
private void ExecuteDoubleClick(object parameter)
{
if (parameter is TipoSimulable tipoSimulable)
{
CrearObjetoSimulableEnCentroCanvas(tipoSimulable.Tipo);
}
}
public void CrearObjetoSimulableEnCentroCanvas(Type tipoSimulable)
{
var CentroCanvas = MainWindow.ObtenerCentroCanvasMeters();
CrearObjetoSimulable(tipoSimulable, CentroCanvas.X, CentroCanvas.Y);
}
public osBase CrearObjetoSimulable(Type tipoSimulable, float Left, float Top)
{
// Crear una nueva instancia del osBase correspondiente
osBase? NuevoOsBase = UserControlFactory.GetInstanceForType(tipoSimulable);
NuevoOsBase.Left = Left;
NuevoOsBase.Top = Top;
if (NuevoOsBase != null)
{
if (CrearUserControlDesdeObjetoSimulable(NuevoOsBase))
{
// Añadir el nuevo osBase a la colección de objetos simulables
ObjetosSimulables.Add(NuevoOsBase);
// Registrar en el simulador hidráulico si tiene componentes hidráulicos
RegisterHydraulicObjectIfNeeded(NuevoOsBase);
HasUnsavedChanges = true;
}
}
return NuevoOsBase;
}
// Crear UserControl desde osBase : Nuevo o desde Deserealizacion
public bool CrearUserControlDesdeObjetoSimulable(osBase osObjeto)
{
Type tipoObjeto = osObjeto.GetType();
// Obtén el UserControl correspondiente para el tipo de objeto
UserControl? userControl = UserControlFactory.GetControlForType(tipoObjeto);
if (userControl != null)
{
// Asignar los datos al UserControl - Usando TSNetSimulationManager
UserControlFactory.AssignDatos(userControl, osObjeto, simulationManager, tsnetSimulationManager);
osObjeto._mainViewModel = this;
if (osObjeto.Id == null) // Para los objetos salvados antes de usar UniqueID
osObjeto.Id = new UniqueId().ObtenerNuevaID();
MainWindow.AgregarRegistrarUserControlCanvas(userControl);
return true;
}
return false;
}
public void RemoverObjetoSimulable(osBase osObjeto)
{
if (osObjeto != null && ObjetosSimulables.Contains(osObjeto))
{
// Desregistrar del simulador hidráulico si estaba registrado
UnregisterHydraulicObjectIfNeeded(osObjeto);
ObjetosSimulables.Remove(osObjeto);
if (osObjeto.VisualRepresentation != null)
MainWindow.EliminarUserControlDelCanvas(osObjeto.VisualRepresentation);
HasUnsavedChanges = true;
}
}
private void DuplicarUserControl()
{
if (_objectManager.SelectedObjects.Count > 0)
{
// 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<osBase>();
// 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);
if (newObj != null)
{
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(() =>
{
// Select all newly created objects
foreach (var newObj in newObjects)
{
if (newObj.VisualRepresentation != null)
{
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))
{
_objectManager.SelectObject(newObj);
}
}
}
// 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();
}));
}
}
public osBase DuplicarObjeto(osBase objDuplicar, float OffsetX, float OffsetY)
{
StopSimulation();
DisconnectPLC();
osBase? NuevoObjetoDuplicado = null;
objDuplicar.SalvarDatosNoSerializables();
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.All
};
try
{
// Serializar
var serializedData = JsonConvert.SerializeObject(objDuplicar, settings);
// Duplicar
NuevoObjetoDuplicado = JsonConvert.DeserializeObject<osBase>(serializedData, settings);
if (NuevoObjetoDuplicado != null)
{
NuevoObjetoDuplicado.Id.ObtenerNuevaID();
string nombre = Regex.IsMatch(NuevoObjetoDuplicado.Nombre, @"_\d+$")
? Regex.Replace(NuevoObjetoDuplicado.Nombre, @"_\d+$", $"_{NuevoObjetoDuplicado.Id.Value}")
: NuevoObjetoDuplicado.Nombre + "_" + NuevoObjetoDuplicado.Id.Value;
NuevoObjetoDuplicado.Nombre = nombre;
NuevoObjetoDuplicado.Left += OffsetX;
NuevoObjetoDuplicado.Top += OffsetY;
ObjetosSimulables.Add(NuevoObjetoDuplicado);
CrearUserControlDesdeObjetoSimulable(NuevoObjetoDuplicado);
// Registrar en el simulador hidráulico si tiene componentes hidráulicos
RegisterHydraulicObjectIfNeeded(NuevoObjetoDuplicado);
HasUnsavedChanges = true;
}
}
catch
{
// Log error or handle it accordingly
}
finally
{
objDuplicar.RestaurarDatosNoSerializables();
}
return NuevoObjetoDuplicado;
}
private void EliminarUserControl()
{
_objectManager.EliminarObjetosSeleccionados();
SelectedItemOsList = null;
}
private void EliminarTodosCommand()
{
var objetosSimulablesCopy = new List<osBase>(ObjetosSimulables);
foreach (var obj in objetosSimulablesCopy)
RemoverObjetoSimulable(obj);
}
private void EliminarAutoCreatedCommand()
{
var osAutoCreated_List = ObjetosSimulables
.Where(o => o.Show_On_This_Page && o.AutoCreated)
.ToList();
foreach (var obj in osAutoCreated_List)
RemoverObjetoSimulable(obj);
}
private void EliminarClonedCommand()
{
var osCloned_List = ObjetosSimulables
.Where(o => o.Show_On_This_Page && o.Cloned)
.ToList();
foreach (var obj in osCloned_List)
RemoverObjetoSimulable(obj);
}
private void AssingPagesCommand()
{
var assignImagesWindow = new AssignImagesWindow();
var assignImagesViewModel = new AssignImagesViewModel();
assignImagesViewModel.Initialize(this, assignImagesWindow);
assignImagesWindow.DataContext = assignImagesViewModel;
assignImagesWindow.ShowDialog();
if (assignImagesWindow.DataContext is AssignImagesViewModel dialog && dialog.CloseOK)
SaveStateObjetosSimulables();
}
public async Task WaitForUIUpdateAsync()
{
await Task.Yield();
Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
}
private async void MultiPageAnalizeCommand()
{
var ImagenesSeleccionadas = new ObservableCollection<string>
{
SelectedImage
};
StopSimulation();
var selectPagesWindow = new SelectPages();
var selectPagesViewModel = new SelectPagesViewModel();
selectPagesViewModel.Initialize(this, selectPagesWindow, ref ImagenesSeleccionadas);
selectPagesWindow.DataContext = selectPagesViewModel;
selectPagesWindow.ShowDialog();
bool originalHasUnsavedChanges = HasUnsavedChanges;
HasUnsavedChanges = false;
try
{
if (selectPagesWindow.DataContext is SelectPagesViewModel dialog && dialog.CloseOK)
{
SaveStateObjetosSimulables(); // Guarda el estado antes de cambiar la imagen
foreach (var page in ImagenesSeleccionadas)
{
SelectedImage = page;
await WaitForUIUpdateAsync(); // Espera a que la UI se actualice
AnalizePageCommand();
await WaitForUIUpdateAsync(); // Espera a que la UI se actualice
SaveStateObjetosSimulables(); // Guarda el estado antes de cambiar la imagen
}
}
}
finally
{
HasUnsavedChanges = originalHasUnsavedChanges;
}
}
private void AnalizePageCommand()
{
foreach (var obj in ObjetosSimulables)
if (obj is osBuscarCoincidencias objBC)
if (objBC.Show_On_This_Page)
objBC.BuscarCoincidencias();
}
private void InitializeTipoSimulableList()
{
var baseType = typeof(osBase);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => type.IsSubclassOf(baseType) && !type.IsAbstract && typeof(IosBase).IsAssignableFrom(type));
var categorias = new Dictionary<string, CategoriaNode>();
foreach (var type in types)
{
var methodInfoNombre = type.GetMethod("NombreClase", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
var methodInfoCategoria = type.GetMethod("NombreCategoria", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
string nombre = methodInfoNombre != null ? methodInfoNombre.Invoke(null, null)?.ToString() : "Nombre no encontrado";
string categoria = methodInfoCategoria != null ? methodInfoCategoria.Invoke(null, null)?.ToString() : "Sin categoría";
var tipoSimulable = new TipoSimulable { Nombre = nombre, Tipo = type, Categoria = categoria };
ListaOsBase.Add(tipoSimulable);
// Crear o obtener el nodo de categoría
if (!categorias.ContainsKey(categoria))
{
categorias[categoria] = new CategoriaNode(categoria);
}
// Agregar el tipo simulable a la categoría correspondiente
categorias[categoria].Elementos.Add(tipoSimulable);
}
// Ordenar categorías y sus elementos
foreach (var categoria in categorias.Values.OrderBy(c => c.Nombre))
{
// Ordenar elementos dentro de cada categoría por nombre
var elementosOrdenados = categoria.Elementos.OrderBy(e => e.Nombre).ToList();
categoria.Elementos.Clear();
foreach (var elemento in elementosOrdenados)
{
categoria.Elementos.Add(elemento);
}
CategoriasOsBase.Add(categoria);
}
}
private void StartSimulation()
{
IsSimulationRunning = true;
// Resetear contadores adaptativos para timing limpio
ResetAdaptiveTimingCounters();
// Ocultar rectángulos de selección antes de iniciar la simulación
_objectManager.UpdateSelectionVisuals();
foreach (var objetoSimulable in ObjetosSimulables)
objetoSimulable.UpdateGeometryStart();
TiempoDesdeStartSimulacion = 0;
Debug_SimulacionCreado = true;
_timerSimulacion.Start();
_timerTSNet.Start(); // Iniciar TSNet automático
simulationManager.Start();
}
public void StopSimulation()
{
IsSimulationRunning = false;
foreach (var objetoSimulable in ObjetosSimulables)
objetoSimulable.SimulationStop();
// Reiniciar simulador hidráulico al detener la simulación
ResetHydraulicSimulation();
if (Debug_SimulacionCreado)
{
Debug_SimulacionCreado = false;
}
_timerSimulacion.Stop();
_timerTSNet.Stop(); // Detener TSNet automático
_timerPLCUpdate.Stop(); // También detener el timer PLC
// Resetear contadores adaptativos al detener
ResetAdaptiveTimingCounters();
// Restaurar los rectángulos de selección si hay objetos seleccionados
_objectManager.UpdateSelectionVisuals();
// Limpiar historial de undo al detener la simulación
MainWindow?.ClearUndoHistory();
}
private void OnTickSimulacion(object sender, System.Timers.ElapsedEventArgs e)
{
// Verificar si la aplicación todavía está disponible
if (Application.Current == null) return;
// Verificar si el dispatcher está disponible y no ha comenzado el shutdown
if (Application.Current.Dispatcher == null || Application.Current.Dispatcher.HasShutdownStarted)
return;
// Usar BeginInvoke para evitar bloqueos durante el uso del MCP
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
var executionStopwatch = Stopwatch.StartNew(); // ✅ NUEVO: Medir tiempo de ejecución del método
// ✅ MANTENER: Tiempo entre llamadas del timer para lógica interna
var timeBetweenCalls = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimModel_last;
stopwatch_SimModel_last = stopwatch_Sim.Elapsed.TotalMilliseconds;
// ✅ NUEVO: Actualizar velocidad de simulación con promedio móvil de 500ms
UpdateSimulationSpeedAverage(timeBetweenCalls);
// Acumular tiempo para el promedio (usando tiempo real del timer)
accumulatedSimTime += timeBetweenCalls;
simSampleCount++;
// Contador de tiempo desde el inicio de la simulación
if (TiempoDesdeStartSimulacion <= 1200)
TiempoDesdeStartSimulacion += (float)timeBetweenCalls;
// ✅ NUEVO: Optimización temprana para escenarios sin objetos
if (ObjetosSimulables?.Count > 0)
{
foreach (var objetoSimulable in ObjetosSimulables)
objetoSimulable.UpdateGeometryStep();
}
// Ejecutar simulación física BEPU
simulationManager.Step();
// ELIMINADO: hydraulicSimulationManager.Step() - Ahora solo usamos TSNet
// La simulación hidráulica se ejecuta por separado en _timerTSNet
// ✅ NUEVO: Solo crear la copia si hay objetos para procesar
if (ObjetosSimulables?.Count > 0)
{
var objetosSimulablesCopy = new List<osBase>(ObjetosSimulables);
foreach (var objetoSimulable in objetosSimulablesCopy)
{
if (!objetoSimulable.RemoverDesdeSimulacion)
objetoSimulable.UpdateControl((int)timeBetweenCalls);
else
RemoverObjetoSimulable(objetoSimulable);
}
}
executionStopwatch.Stop(); // ✅ CORREGIDO: Detener cronómetro de ejecución
// Sistema adaptativo de timing de simulación
AdaptSimulationTiming(executionStopwatch.Elapsed.TotalMilliseconds);
//Debug.WriteLine($"OnTickSimulacion execution time: {stopwatch.ElapsedMilliseconds} ms");
Debug.WriteLine($"OnTickSimulacion execution time: {executionStopwatch.Elapsed.TotalMilliseconds:F2}ms | Timer interval: {timeBetweenCalls:F2}ms | Objects: {ObjetosSimulables?.Count ?? 0}");
}
catch (Exception ex)
{
// Manejo de errores para evitar crashes en el timer
Debug.WriteLine($"[MainViewModel] Error en OnTickSimulacion: {ex.Message}");
}
}), DispatcherPriority.Background); // Usar prioridad Background para no interferir con MCP
}
/// <summary>
/// Timer para ejecutar TSNet automáticamente cada 100ms durante la simulación
/// </summary>
private async void OnTSNetTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// Solo ejecutar si la simulación está corriendo y hay objetos hidráulicos
if (!IsSimulationRunning)
return;
try
{
// Verificar que tsnetSimulationManager esté inicializado
if (tsnetSimulationManager == null)
{
Debug.WriteLine("TSNet Auto: tsnetSimulationManager es null, omitiendo ejecución");
return;
}
// Verificar si ya está ejecutándose para evitar concurrencia
if (tsnetSimulationManager.IsRunning)
{
Debug.WriteLine("TSNet Auto: Simulación ya en ejecución, omitiendo");
return;
}
// Contar objetos hidráulicos
var hydraulicObjects = ObjetosSimulables?.Where(obj => obj.GetType().Name.Contains("osHyd")).ToList();
if (hydraulicObjects == null || hydraulicObjects.Count == 0)
{
Debug.WriteLine("TSNet Auto: No hay objetos hidráulicos, omitiendo ejecución");
return;
}
// Resetear y registrar objetos hidráulicos
tsnetSimulationManager.ResetAllCalculatedValues();
foreach (var obj in hydraulicObjects)
{
tsnetSimulationManager.RegisterHydraulicObject(obj);
}
// Validar configuración
var configErrors = tsnetSimulationManager.ValidateAllConfigurations();
if (configErrors.Count > 0)
{
Debug.WriteLine($"TSNet Auto: Errores de configuración: {string.Join(", ", configErrors)}");
return;
}
// Reconstruir red y ejecutar simulación
tsnetSimulationManager.RebuildNetwork();
// Ejecutar simulación TSNet de forma asíncrona sin bloquear
var result = await tsnetSimulationManager.RunSimulationAsync();
if (result != null)
{
if (result.Success)
{
Debug.WriteLine($"TSNet Auto: Simulación exitosa con {hydraulicObjects.Count} objetos hidráulicos");
}
else
{
Debug.WriteLine($"TSNet Auto: Error en simulación: {result.Message}");
}
}
else
{
Debug.WriteLine("TSNet Auto: RunSimulationAsync devolvió null");
}
}
catch (NullReferenceException nullEx)
{
Debug.WriteLine($"TSNet Auto: Error de referencia nula: {nullEx.Message}");
Debug.WriteLine($"TSNet Auto: StackTrace: {nullEx.StackTrace}");
// Intentar reinicializar el simulador
try
{
tsnetSimulationManager = new TSNetSimulationManager();
Debug.WriteLine("TSNet Auto: tsnetSimulationManager reinicializado");
}
catch (Exception reinitEx)
{
Debug.WriteLine($"TSNet Auto: Error al reinicializar: {reinitEx.Message}");
}
}
catch (Exception ex)
{
Debug.WriteLine($"TSNet Auto: Excepción general: {ex.Message}");
Debug.WriteLine($"TSNet Auto: Tipo: {ex.GetType().Name}");
Debug.WriteLine($"TSNet Auto: StackTrace: {ex.StackTrace}");
}
}
private void ConnectPLC()
{
_timerPLCUpdate.Start();
PLCViewModel.Connect();
foreach (var objetoSimulable in ObjetosSimulables)
objetoSimulable.SetPLC(PLCViewModel);
}
public void DisconnectPLC()
{
PLCViewModel.Disconnect();
_timerPLCUpdate.Stop();
foreach (var objetoSimulable in ObjetosSimulables)
objetoSimulable.SetPLC(null);
// Limpiar historial de undo al desconectar del PLC
MainWindow?.ClearUndoHistory();
}
/// <summary>
/// Limpia todos los recursos y detiene todos los timers antes del cierre de la aplicación
/// </summary>
public void CleanupResources()
{
// Detener simulación si está corriendo
if (IsSimulationRunning)
{
StopSimulation();
}
// Detener todos los timers
_timerSimulacion?.Stop();
_timerPLCUpdate?.Stop();
_timer3DUpdate?.Stop();
_timerDisplayUpdate?.Stop();
_timerDebugFlush?.Stop();
_timerTSNet?.Stop();
// Desconectar PLC si está conectado
if (PLCViewModel?.IsConnected == true)
{
PLCViewModel.Disconnect();
}
// Limpiar servidor MCP
CleanupMcpServer();
}
private List<osBase> objetosSimulablesLlamados = new List<osBase>();
private void OnRefreshEvent(object sender, System.Timers.ElapsedEventArgs e)
{
// Verificar si la aplicación todavía está disponible
if (Application.Current == null) return;
// Verificar si el dispatcher está disponible y no ha comenzado el shutdown
if (Application.Current.Dispatcher == null || Application.Current.Dispatcher.HasShutdownStarted)
return;
// Usar BeginInvoke para evitar bloqueos durante el uso del MCP
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
var stopwatch = Stopwatch.StartNew(); // Start measuring time
if (PLCViewModel.IsConnected)
{
// Detener el cronómetro y obtener el tiempo transcurrido en milisegundos
var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimPLC_last;
stopwatch_SimPLC_last = stopwatch_Sim.Elapsed.TotalMilliseconds;
// Acumular tiempo para el promedio
accumulatedPlcTime += elapsedMilliseconds;
plcSampleCount++;
// Reiniciar el cronómetro para la próxima medición
var remainingObjetosSimulables = ObjetosSimulables.Except(objetosSimulablesLlamados).ToList();
foreach (var objetoSimulable in remainingObjetosSimulables)
{
var objStopwatch = Stopwatch.StartNew();
objetoSimulable.UpdatePLC(PLCViewModel, (int)elapsedMilliseconds);
objStopwatch.Stop();
objetosSimulablesLlamados.Add(objetoSimulable);
if (stopwatch.Elapsed.TotalMilliseconds >= 10)
break;
}
if (remainingObjetosSimulables.Count == 0)
{
objetosSimulablesLlamados.Clear();
}
}
stopwatch.Stop(); // Stop measuring time
// Sistema adaptativo de timing PLC
AdaptPlcTiming(stopwatch.Elapsed.TotalMilliseconds);
// Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms, Interval: {_timerPLCUpdate.Interval}ms");
}
catch (Exception ex)
{
// Log del error para el debugging
Debug.WriteLine($"Error en OnRefreshEvent: {ex.Message}");
// No relanzar la excepción para evitar crashear el timer
}
}), DispatcherPriority.Background);
}
/// <summary>
/// Evento del timer que hace flush automático del buffer de debug para evitar acumulación excesiva
/// </summary>
private void OnDebugFlushTimer(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
if (_debugConsoleServer != null)
{
var stats = _debugConsoleServer.GetBufferStats();
// Solo hacer flush si el buffer tiene un número significativo de mensajes
if (stats.messageCount > 500)
{
_debugConsoleServer.FlushMessageBuffer();
Debug.WriteLine($"[Debug Flush Timer] Buffer automático limpiado. Mensajes procesados: {stats.messageCount}");
}
else
{
Debug.WriteLine($"[Debug Flush Timer] Buffer saludable. Mensajes actuales: {stats.messageCount}");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"[Debug Flush Timer] Error durante flush automático: {ex.Message}");
}
}
/// <summary>
/// Evento del timer para actualización de display (UI)
/// </summary>
private void OnDisplayUpdate(object sender, EventArgs e)
{
try
{
// Actualizar elementos de UI que no requieren alta frecuencia
// Este timer corre a 250ms por lo que es adecuado para actualizaciones de UI menos críticas
// Se puede agregar lógica de actualización de UI aquí si es necesario
}
catch (Exception ex)
{
Debug.WriteLine($"[Display Update Timer] Error durante actualización de display: {ex.Message}");
}
}
/// <summary>
/// Evento del timer para actualización 3D cuando la simulación está detenida
/// </summary>
private void OnTick3DUpdate(object sender, EventArgs e)
{
try
{
// Mantener la visualización 3D actualizada cuando la simulación no está corriendo
if (!IsSimulationRunning && Visualization3DManager != null)
{
Visualization3DManager.SynchronizeWorld();
}
}
catch (Exception ex)
{
Debug.WriteLine($"[3D Update Timer] Error durante actualización 3D: {ex.Message}");
}
}
private void AdaptPlcTiming(double executionTime)
{
lastPlcExecutionTime = executionTime;
maxPlcExecutionTime = Math.Max(maxPlcExecutionTime, executionTime);
plcTimingAdaptationCounter++;
// Evaluar cada PLC_ADAPTATION_SAMPLES ejecuciones
if (plcTimingAdaptationCounter >= PLC_ADAPTATION_SAMPLES)
{
double currentInterval = _timerPLCUpdate.Interval;
double newInterval = currentInterval;
// Si el tiempo máximo de ejecución + tiempo para simulación > intervalo actual
if (maxPlcExecutionTime + SIM_PRIORITY_TIME > currentInterval)
{
// Incrementar intervalo para dar más tiempo
newInterval = Math.Min(maxPlcExecutionTime + SIM_PRIORITY_TIME + 5, MAX_PLC_INTERVAL);
// Debug.WriteLine($"PLC Timer: Aumentando intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)");
}
// Si hemos estado por debajo del umbral, podemos reducir el intervalo
else if (maxPlcExecutionTime + SIM_PRIORITY_TIME < currentInterval * 0.7)
{
newInterval = Math.Max(maxPlcExecutionTime + SIM_PRIORITY_TIME + 2, MIN_PLC_INTERVAL);
// Debug.WriteLine($"PLC Timer: Reduciendo intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)");
}
// Aplicar el nuevo intervalo si cambió
if (Math.Abs(newInterval - currentInterval) > 1)
{
_timerPLCUpdate.Interval = newInterval;
}
// Reset para el próximo período de evaluación
maxPlcExecutionTime = 0;
plcTimingAdaptationCounter = 0;
}
}
/// <summary>
/// Sistema adaptativo para el timing de simulación que mantiene 2ms de buffer extra
/// </summary>
private void AdaptSimulationTiming(double executionTime)
{
lastSimExecutionTime = executionTime;
maxSimExecutionTime = Math.Max(maxSimExecutionTime, executionTime);
simTimingAdaptationCounter++;
// Evaluar cada SIM_ADAPTATION_SAMPLES ejecuciones para responsividad rápida
if (simTimingAdaptationCounter >= SIM_ADAPTATION_SAMPLES)
{
double currentInterval = _timerSimulacion.Interval;
double newInterval = currentInterval;
// Calcular intervalo ideal: tiempo de ejecución máximo + buffer de 2ms
double idealInterval = maxSimExecutionTime + SIM_BUFFER_TIME;
// Si el tiempo de ejecución + buffer > intervalo actual, necesitamos más tiempo
if (idealInterval > currentInterval)
{
// Incrementar intervalo para evitar que el sistema se vuelva inusable
newInterval = Math.Min(idealInterval + 1, MAX_SIM_INTERVAL); // +1ms adicional de seguridad
//Debug.WriteLine($"Simulación Timer: Aumentando intervalo a {newInterval:F1}ms (ejecución máxima: {maxSimExecutionTime:F1}ms)");
}
// Si hemos estado muy por debajo del umbral, podemos reducir el intervalo
else if (idealInterval < currentInterval * 0.8)
{
newInterval = Math.Max(idealInterval, MIN_SIM_INTERVAL);
//Debug.WriteLine($"Simulación Timer: Reduciendo intervalo a {newInterval:F1}ms (ejecución máxima: {maxSimExecutionTime:F1}ms)");
}
// Aplicar el nuevo intervalo si cambió significativamente
if (Math.Abs(newInterval - currentInterval) > 0.5)
{
_timerSimulacion.Interval = newInterval;
//Debug.WriteLine($"Simulación Timer: Intervalo actualizado de {currentInterval:F1}ms a {newInterval:F1}ms");
}
// Reset para el próximo período de evaluación
maxSimExecutionTime = 0;
simTimingAdaptationCounter = 0;
}
}
/// <summary>
/// Actualiza el promedio móvil de velocidad de simulación en una ventana de 500ms
/// </summary>
private void UpdateSimulationSpeedAverage(double timeBetweenCalls)
{
// Agregar la nueva muestra
simSpeedSamples.Enqueue(timeBetweenCalls);
totalSimSpeedTime += timeBetweenCalls;
// Remover muestras antiguas que estén fuera de la ventana de 500ms
while (simSpeedSamples.Count > 0 && totalSimSpeedTime > SIM_SPEED_WINDOW_MS)
{
var oldestSample = simSpeedSamples.Dequeue();
totalSimSpeedTime -= oldestSample;
}
// Calcular y actualizar el promedio
if (simSpeedSamples.Count > 0)
{
SimulationSpeed = totalSimSpeedTime / simSpeedSamples.Count;
}
}
/// <summary>
/// Resetea todos los contadores adaptativos de timing para empezar con mediciones limpias
/// </summary>
private void ResetAdaptiveTimingCounters()
{
// Reset contadores de simulación
lastSimExecutionTime = 0;
maxSimExecutionTime = 0;
simTimingAdaptationCounter = 0;
SimulationSpeed = 0.0; // Reset del display de velocidad de simulación
// Reset promedio móvil de SimulationSpeed
simSpeedSamples.Clear();
totalSimSpeedTime = 0;
// Reset contadores de PLC
lastPlcExecutionTime = 0;
maxPlcExecutionTime = 0;
plcTimingAdaptationCounter = 0;
// Restablecer intervalos iniciales conservadores
_timerSimulacion.Interval = 15; // 15ms inicial
_timerPLCUpdate.Interval = 10; // 10ms inicial
Debug.WriteLine("Timing adaptativo: Contadores y intervalos reseteados");
}
private void OpenWorkDirectory()
{
var dialog = new VistaFolderBrowserDialog();
if (dialog.ShowDialog() == true) // Mostrar el diálogo y comprobar si el resultado es positivo
{
directorioTrabajo = dialog.SelectedPath; // Actualizar la propiedad que también actualiza el estado persistente
}
}
private void OpenRecentDirectory(string path)
{
if (Directory.Exists(path))
{
directorioTrabajo = path;
}
else
{
MessageBox.Show($"Directory not found: {path}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
RecentDirectories.Remove(path);
UpdateRecentDirectories();
}
}
private void UpdateRecentDirectories()
{
EstadoPersistente.Instance.RecentDirectories = RecentDirectories.ToList();
EstadoPersistente.Instance.GuardarEstado();
}
private void AddToRecentDirectories(string path)
{
// 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();
}
//
// Lista de osBase
//
public void Save()
{
SaveStateObjetosSimulables();
}
public void SaveStateObjetosSimulables()
{
if (SelectedImage != null)
{
_stateSerializer.SaveState(SelectedImage);
HasUnsavedChanges = false;
}
}
// Se cargan los datos de cada UserControl en el StackPanel
public void CargarPropiedadesosDatos(osBase selectedObject, Controls.PanelEdicionControl PanelEdicion, ResourceDictionary Resources)
{
if (selectedObject == null)
{
// Clear the property panel when no object is selected
PanelEdicion.ClearProperties();
}
else
{
PanelEdicion.CargarPropiedades(selectedObject);
}
}
private RelayCommand saveCommand;
public ICommand SaveCommand => saveCommand ??= new RelayCommand(Save);
private void Save(object commandParameter)
{
}
private RelayCommand exitCommand;
public ICommand ExitCommand => exitCommand ??= new RelayCommand(Exit);
private void Exit()
{
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();
}
Application.Current.Shutdown();
}
private async void MultiPageMatrixCommand()
{
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<string>
{
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)
{
var matrixPreviewWindow = new MatrixPreviewWindow();
var matrixPreviewViewModel = new MatrixPreviewViewModel();
matrixPreviewViewModel.Initialize(this, matrixPreviewWindow, ImagenesSeleccionadas);
matrixPreviewWindow.DataContext = matrixPreviewViewModel;
matrixPreviewWindow.ShowDialog();
}
}
finally
{
inhibitSaveChangesControl = false;
}
}
[ObservableProperty]
private double simulationSpeed;
[ObservableProperty]
private double plcUpdateSpeed;
[ObservableProperty]
private bool is3DUpdateEnabled = true;
public ICommand TBToggle3DUpdateCommand { get; }
partial void OnIs3DUpdateEnabledChanged(bool value)
{
// Sincronizar la propiedad con el simulationManager
if (simulationManager != null)
{
simulationManager.Is3DUpdateEnabled = value;
}
}
// Reemplazar la propiedad _multiPropertyEditorWindow por un diccionario
private Dictionary<string, Windows.MultiPropertyEditorWindow> _propertyEditorWindows = new();
public void ShowMultiPropertyEditor(IEnumerable<osBase> selectedObjects)
{
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) &&
existingWindow.IsVisible)
{
existingWindow.Activate();
return;
}
// Crear nueva ventana
var window = new Windows.MultiPropertyEditorWindow(objectsList, MainWindow);
window.Closed += (s, e) => _propertyEditorWindows.Remove(key);
_propertyEditorWindows[key] = window;
window.Show();
HasUnsavedChanges = true;
_objectManager.UpdateSelectionVisuals();
}
// Add this method to MainViewModel class
public void NotifySelectionChanged()
{
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();
DisconnectPLC();
// Actualizar la escala en el UnitConverter
PixelToMeter.Instance.calc.SetScale(newScale);
// Forzar redibujo completo del canvas y todos los objetos
ForceCanvasRedraw();
// Marcar como cambios no guardados ya que esto afecta la configuración de la imagen
HasUnsavedChanges = true;
// Limpiar historial de undo después de cambiar la escala
MainWindow?.ClearUndoHistory();
}
// Método para forzar el redibujo completo del canvas
private void ForceCanvasRedraw()
{
// Forzar actualización de todos los objetos simulables mediante PropertyChanged
foreach (var obj in ObjetosSimulables)
{
// Forzar la notificación de PropertyChanged para las propiedades de posición y tamaño
// Esto hará que los bindings y converters se recalculen automáticamente
obj.ForceUpdatePositionBindings();
}
// Forzar actualización del layout del canvas principal
MainWindow?.ImagenEnTrabajoCanvas?.InvalidateVisual();
MainWindow?.ImagenEnTrabajoCanvas?.UpdateLayout();
// Actualizar selecciones visuales si existen
_objectManager?.UpdateSelectionVisuals();
// Forzar actualización global del layout con prioridad de renderizado
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
{
MainWindow?.ImagenEnTrabajoCanvas?.InvalidateVisual();
MainWindow?.ImagenEnTrabajoCanvas?.UpdateLayout();
}));
}
// Diccionario para manejar ventanas de biblioteca múltiples
private Dictionary<string, PopUps.LibraryWindow> _libraryWindows = new();
private void ShowLibraryManager()
{
const string libraryWindowKey = "LibraryManager";
// Verificar si ya existe una ventana de biblioteca
if (_libraryWindows.TryGetValue(libraryWindowKey, out var existingWindow) &&
existingWindow.IsVisible)
{
existingWindow.Activate();
return;
}
// Crear nueva ventana modeless
var libraryWindow = new PopUps.LibraryWindow();
var libraryViewModel = new PopUps.LibraryWindowViewModel(this);
libraryWindow.DataContext = libraryViewModel;
libraryWindow.Owner = MainWindow;
// Manejar el cierre de la ventana
libraryWindow.Closed += (s, e) => _libraryWindows.Remove(libraryWindowKey);
// Registrar la ventana
_libraryWindows[libraryWindowKey] = libraryWindow;
// Mostrar como modeless (no modal)
libraryWindow.Show();
}
#region Hydraulic Simulation Management
/// <summary>
/// Registra un objeto en el simulador hidráulico si tiene componentes hidráulicos
/// </summary>
private void RegisterHydraulicObjectIfNeeded(osBase obj)
{
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
{
// Evitar doble registro - solo registrar si no está ya registrado
if (!tsnetSimulationManager.HydraulicObjects.Contains(obj))
{
tsnetSimulationManager.RegisterHydraulicObject(obj);
Debug.WriteLine($"Objeto hidráulico registrado en TSNet: {obj.Nombre}");
}
else
{
Debug.WriteLine($"Objeto hidráulico ya registrado en TSNet (omitido): {obj.Nombre}");
}
}
}
/// <summary>
/// Desregistra un objeto del simulador hidráulico si estaba registrado
/// </summary>
private void UnregisterHydraulicObjectIfNeeded(osBase obj)
{
// Desregistrar independientemente de si implementa la interfaz actualmente
// (podría haber cambiado desde que se registró)
tsnetSimulationManager.UnregisterHydraulicObject(obj);
Debug.WriteLine($"Objeto desregistrado del simulador hidráulico TSNet: {obj.Nombre}");
}
/// <summary>
/// Reinicia el simulador hidráulico
/// </summary>
public void ResetHydraulicSimulation()
{
tsnetSimulationManager.Reset();
Debug.WriteLine("Simulador hidráulico TSNet reiniciado");
}
/// <summary>
/// Obtiene estadísticas del simulador hidráulico
/// </summary>
public string GetHydraulicSimulationStats()
{
return tsnetSimulationManager.GetSimulationStats();
}
/// <summary>
/// Habilita/deshabilita la simulación hidráulica
/// </summary>
public void SetHydraulicSimulationEnabled(bool enabled)
{
tsnetSimulationManager.IsHydraulicSimulationEnabled = enabled;
Debug.WriteLine($"Simulación hidráulica TSNet {(enabled ? "habilitada" : "deshabilitada")}");
}
/// <summary>
/// Registra todos los objetos hidráulicos existentes después de cargar un proyecto
/// </summary>
private void RegisterLoadedHydraulicObjects()
{
// Limpiar registros previos
tsnetSimulationManager.ClearHydraulicObjects();
// Crear una lista temporal para evitar duplicados durante la carga
var objectsToRegister = new HashSet<osBase>();
// Registrar todos los objetos cargados que tengan componentes hidráulicos
foreach (var obj in ObjetosSimulables)
{
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
{
objectsToRegister.Add(obj);
}
}
// Registrar los objetos únicos
foreach (var obj in objectsToRegister)
{
tsnetSimulationManager.RegisterHydraulicObject(obj);
Debug.WriteLine($"Objeto hidráulico registrado en carga TSNet: {obj.Nombre}");
}
Debug.WriteLine($"Registrados {tsnetSimulationManager.HydraulicObjects.Count} objetos hidráulicos únicos tras cargar proyecto en TSNet");
}
#endregion
#region Hydraulic Network Management
/// <summary>
/// Invalidates the hydraulic network to force recalculation
/// </summary>
public void InvalidateHydraulicNetwork()
{
try
{
if (tsnetSimulationManager != null)
{
tsnetSimulationManager.InvalidateNetwork();
Debug.WriteLine("Red hidráulica TSNet invalidada y marcada para recálculo");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error al invalidar red hidráulica: {ex.Message}");
}
}
#endregion
#region MCP Server Management
/// <summary>
/// Inicia el servidor MCP
/// </summary>
private async void StartMcpServer()
{
try
{
if (_mcpServer != null)
{
_mcpServer.Dispose();
}
_mcpServer = new MCPServer(this, McpServerPort);
await _mcpServer.StartAsync();
Debug.WriteLine($"[MCP] Servidor MCP iniciado en puerto {McpServerPort}");
// Iniciar también el servidor de debug console
StartDebugConsoleServer();
}
catch (Exception ex)
{
Debug.WriteLine($"[MCP] Error iniciando servidor MCP: {ex.Message}");
MessageBox.Show($"Error iniciando servidor MCP: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Detiene el servidor MCP
/// </summary>
private void StopMcpServer()
{
try
{
_mcpServer?.Stop();
Debug.WriteLine("[MCP] Servidor MCP detenido");
// Detener también el servidor de debug console
StopDebugConsoleServer();
}
catch (Exception ex)
{
Debug.WriteLine($"[MCP] Error deteniendo servidor MCP: {ex.Message}");
}
}
/// <summary>
/// Limpia el servidor MCP al cerrar la aplicación
/// </summary>
private void CleanupMcpServer()
{
try
{
_mcpServer?.Dispose();
_mcpServer = null;
// Limpiar también el servidor de debug console
CleanupDebugConsoleServer();
}
catch (Exception ex)
{
Debug.WriteLine($"[MCP] Error limpiando servidor MCP: {ex.Message}");
}
}
/// <summary>
/// Inicia el servidor de debug console
/// </summary>
private void StartDebugConsoleServer()
{
try
{
if (_debugConsoleServer != null)
{
_debugConsoleServer.Dispose();
}
_debugConsoleServer = new DebugConsoleServer(5007); // Puerto fijo para debug
_debugConsoleServer.Start();
Debug.WriteLine("[Debug Console] Servidor de debug console iniciado en puerto 5007");
}
catch (Exception ex)
{
Debug.WriteLine($"[Debug Console] Error iniciando servidor de debug console: {ex.Message}");
}
}
/// <summary>
/// Detiene el servidor de debug console
/// </summary>
private void StopDebugConsoleServer()
{
try
{
_debugConsoleServer?.Stop();
Debug.WriteLine("[Debug Console] Servidor de debug console detenido");
}
catch (Exception ex)
{
Debug.WriteLine($"[Debug Console] Error deteniendo servidor de debug console: {ex.Message}");
}
}
/// <summary>
/// Limpia el servidor de debug console al cerrar la aplicación
/// </summary>
private void CleanupDebugConsoleServer()
{
try
{
_debugConsoleServer?.Dispose();
_debugConsoleServer = null;
}
catch (Exception ex)
{
Debug.WriteLine($"[Debug Console] Error limpiando servidor de debug console: {ex.Message}");
}
}
#endregion
#region TSNet Integration Methods
/// <summary>
/// Versión simplificada para testing
/// </summary>
public void TestTSNetIntegrationSync()
{
try
{
Debug.WriteLine("Iniciando prueba de integración TSNet...");
MessageBox.Show("Prueba TSNet iniciada - revisar Debug console", "TSNet Test", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Debug.WriteLine($"Error en prueba TSNet: {ex.Message}");
MessageBox.Show($"Error en prueba TSNet: {ex.Message}", "Error TSNet", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Versión mejorada para simulación TSNet sin fallbacks
/// IMPORTANTE: Solo usa TSNet para todos los cálculos hidráulicos
/// </summary>
public void RunTSNetSimulationSync()
{
try
{
Debug.WriteLine("=== Iniciando simulación TSNet completa ===");
// 1. Resetear todos los valores calculados anteriores
this.tsnetSimulationManager.ResetAllCalculatedValues();
Debug.WriteLine("TSNet: Valores calculados anteriores reseteados");
// 2. Registrar objetos hidráulicos con adaptadores
var hydraulicCount = 0;
foreach (var obj in ObjetosSimulables)
{
// Solo registrar objetos hidráulicos específicos
if (obj.GetType().Name.Contains("osHyd"))
{
this.tsnetSimulationManager.RegisterHydraulicObject(obj);
hydraulicCount++;
Debug.WriteLine($"TSNet: Registrado {obj.Nombre} ({obj.GetType().Name})");
}
}
// 3. Validar configuración de todos los adaptadores
var configErrors = this.tsnetSimulationManager.ValidateAllConfigurations();
if (configErrors.Count > 0)
{
var errorMsg = $"Errores de configuración encontrados:\n{string.Join("\n", configErrors)}";
Debug.WriteLine(errorMsg);
MessageBox.Show(errorMsg, "Errores de Configuración TSNet", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 4. Reconstruir red
this.tsnetSimulationManager.RebuildNetwork();
Debug.WriteLine("TSNet: Red hidráulica reconstruida");
// 5. Mostrar resumen de adaptadores creados
var summary = GetAdaptersSummary();
Debug.WriteLine(summary);
Debug.WriteLine($"=== Simulación TSNet preparada exitosamente ===");
MessageBox.Show($"Simulación TSNet lista:\n{summary}\n\nTodos los cálculos hidráulicos se realizarán exclusivamente en TSNet.",
"TSNet Fase 2 - Sin Fallbacks", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Debug.WriteLine($"Error ejecutando simulación TSNet: {ex.Message}");
MessageBox.Show($"Error ejecutando simulación TSNet: {ex.Message}", "Error TSNet", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Obtiene un resumen de los adaptadores creados
/// </summary>
private string GetAdaptersSummary()
{
var summary = new StringBuilder();
summary.AppendLine("Adaptadores TSNet creados:");
var tankCount = 0;
var pumpCount = 0;
var pipeCount = 0;
foreach (var obj in ObjetosSimulables)
{
var objectId = obj.Id.Value.ToString();
if (this.tsnetSimulationManager.GetTankAdapter(objectId) != null)
{
tankCount++;
summary.AppendLine($" - Tanque: {obj.Nombre}");
}
else if (this.tsnetSimulationManager.GetPumpAdapter(objectId) != null)
{
pumpCount++;
summary.AppendLine($" - Bomba: {obj.Nombre}");
}
else if (this.tsnetSimulationManager.GetPipeAdapter(objectId) != null)
{
pipeCount++;
summary.AppendLine($" - Tubería: {obj.Nombre}");
}
}
summary.AppendLine($"Total: {tankCount} tanques, {pumpCount} bombas, {pipeCount} tuberías");
return summary.ToString();
}
/// <summary>
/// Versión simplificada para generar INP
/// </summary>
private void GenerateINPFileSync()
{
try
{
Debug.WriteLine("Generando archivo INP...");
var hydraulicCount = 0;
foreach (var obj in ObjetosSimulables)
{
if (obj is IHydraulicComponent)
{
hydraulicCount++;
}
}
Debug.WriteLine($"INP generado con {hydraulicCount} objetos hidráulicos");
MessageBox.Show($"INP preparado con {hydraulicCount} objetos hidráulicos", "INP Test", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Debug.WriteLine($"Error generando archivo INP: {ex.Message}");
MessageBox.Show($"Error generando archivo INP: {ex.Message}", "Error INP", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Método de debugging para generar un reporte detallado de IOException y estado del sistema
/// </summary>
public void GenerateIOExceptionReport()
{
try
{
var finalTimestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
Debug.WriteLine($"\n=== REPORTE DE ESTADO ({finalTimestamp}) ===");
Debug.WriteLine($"Simulación ejecutándose: {IsSimulationRunning}");
Debug.WriteLine($"Timer Simulación activo: {_timerSimulacion?.Enabled}");
Debug.WriteLine($"Timer TSNet activo: {_timerTSNet?.Enabled}");
Debug.WriteLine($"Timer Display activo: {_timerDisplayUpdate?.IsEnabled}");
Debug.WriteLine($"Timer 3D activo: {_timer3DUpdate?.IsEnabled}");
Debug.WriteLine($"TSNet Manager inicializado: {tsnetSimulationManager != null}");
if (tsnetSimulationManager != null)
{
Debug.WriteLine($"TSNet ejecutándose: {tsnetSimulationManager.IsRunning}");
}
Debug.WriteLine($"Objetos simulables: {ObjetosSimulables?.Count ?? 0}");
var hydraulicObjects = ObjetosSimulables?.Where(obj =>
obj.GetType().Name.Contains("osHyd"))?.ToList();
Debug.WriteLine($"Objetos hidráulicos: {hydraulicObjects?.Count ?? 0}");
Debug.WriteLine($"=== FIN REPORTE ===\n");
}
catch (Exception ex)
{
Debug.WriteLine($"Error en GenerateIOExceptionReport: {ex.Message}");
}
}
#endregion
}
public class SimulationData
{
public ObservableCollection<osBase>? ObjetosSimulables { get; set; }
public UnitConverter? UnitConverter { get; set; }
public PLCViewModel? PLC_ConnectionData { get; set; }
// Nueva propiedad para almacenar los datos locales de objetos globales
public ObservableCollection<DatosLocales>? DatosLocalesObjetos { get; set; }
// Diccionario para almacenar datos expandidos de imágenes
public Dictionary<string, Models.ImageData>? ImageDataDictionary { get; set; }
// Configuración del entorno de trabajo para cada imagen
public Models.WorkspaceConfiguration? WorkspaceConfig { get; set; }
// Compatibilidad con versiones anteriores - OBSOLETO
[System.Obsolete("Use ImageDataDictionary instead")]
public Dictionary<string, string>? ImageCustomNames { get; set; }
}
public class TipoSimulable
{
public string? Nombre { get; set; }
public Type? Tipo { get; set; }
public string? Categoria { get; set; }
}
public partial class CategoriaNode : ObservableObject
{
[ObservableProperty]
private string nombre;
[ObservableProperty]
private ObservableCollection<TipoSimulable> elementos;
[ObservableProperty]
private bool isExpanded = true;
public CategoriaNode(string nombre)
{
this.nombre = nombre;
elementos = new ObservableCollection<TipoSimulable>();
}
}
public class TickSimulacionEventArgs : EventArgs
{
// Aquí puedes agregar propiedades o campos para pasar información adicional
// en el evento TickSimulacion
}
}