2512 lines
100 KiB
C#
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
|
|
}
|
|
|
|
}
|