Compare commits
8 Commits
d1ec7f4d12
...
4f2a109332
Author | SHA1 | Date |
---|---|---|
|
4f2a109332 | |
|
cad650b3d0 | |
|
3b953b7998 | |
|
18017db56a | |
|
b44bdc5ece | |
|
6887ede5e0 | |
|
62b45ebf1c | |
|
f7f49d5df0 |
|
@ -30,3 +30,91 @@ simTransporte : Es un box por donde las botellas pueden desplazarse usando un tr
|
||||||
|
|
||||||
* Se ha mejorado el sistema de guías curvas (`ucTransporteCurvaGuias`) para incluir apertura en cono en los extremos de entrada y salida. Se agregó el parámetro `AnguloAperturaGuias` (por defecto 5 grados) que permite configurar la apertura modificando los radios de las guías en los puntos extremos. En lugar de cambiar ángulos, se reduce el radio de la guía superior (externa) y se aumenta el radio de la guía inferior (interna) en los segmentos inicial y final, creando naturalmente la apertura en cono. La modificación del radio se calcula usando `Math.Sin(anguloApertura)` para obtener el desplazamiento apropiado. Esta apertura facilita la entrada y salida de botellas del transporte curvo, reduciendo atascos y mejorando el flujo de materiales manteniendo la continuidad geométrica de las guías.
|
* Se ha mejorado el sistema de guías curvas (`ucTransporteCurvaGuias`) para incluir apertura en cono en los extremos de entrada y salida. Se agregó el parámetro `AnguloAperturaGuias` (por defecto 5 grados) que permite configurar la apertura modificando los radios de las guías en los puntos extremos. En lugar de cambiar ángulos, se reduce el radio de la guía superior (externa) y se aumenta el radio de la guía inferior (interna) en los segmentos inicial y final, creando naturalmente la apertura en cono. La modificación del radio se calcula usando `Math.Sin(anguloApertura)` para obtener el desplazamiento apropiado. Esta apertura facilita la entrada y salida de botellas del transporte curvo, reduciendo atascos y mejorando el flujo de materiales manteniendo la continuidad geométrica de las guías.
|
||||||
|
|
||||||
|
* Se ha implementado un sistema de fricción dinámica que simula el comportamiento de fricción estática vs dinámica para contactos botella-transporte y botella-curva. El sistema calcula el deslizamiento relativo entre la botella y la superficie del transporte: cuando el deslizamiento es bajo (< 0.05 m/s), se aplica fricción estática alta para un arrastre efectivo; cuando el deslizamiento es alto, se aplica fricción dinámica menor para permitir un deslizamiento suave. Para curvas, se calcula la velocidad de superficie usando el producto vectorial (v = ω × r). Para contactos botella-botella, se usa detección de profundidad de penetración para aplicar materiales ultra-suaves cuando hay alta presión, previniendo acumulaciones explosivas. Este enfoque proactivo reemplaza el sistema reactivo de `PressureBuildup` y proporciona un comportamiento más físicamente realista y estable en acumulaciones de botellas.
|
||||||
|
|
||||||
|
* Se ha reemplazado el sistema `ProcessPressureSystem` por un nuevo sistema de calibración visual `HighSlippery` que permite ajustar las fricciones de cada componente. Cada botella tiene una propiedad `HighSlippery` (rango 0-9) con sistema de heatup/cooldown que se incrementa durante deslizamiento y se decrementa durante adherencia. El valor se muestra como número en el centro de cada botella para calibración visual. Los `SpringSettings` se mantienen >= 30 para evitar compresiones irreales. El sistema permite calibrar las tasas de heatup/cooldown específicas para transportes (0.2/0.1) y curvas (0.15/0.08), y el umbral de cambio estático/dinámico (HighSlippery > 3). Este sistema elimina el problema del cooldown que reactivaba prematuramente la fricción estática.
|
||||||
|
|
||||||
|
# Sistema de Limitación de Fuerzas para Contactos Múltiples Simultáneos (ELIMINADO)
|
||||||
|
|
||||||
|
## Problema Identificado
|
||||||
|
Durante la simulación de "trenes largos" de botellas que impactan simultáneamente, se generaban fuerzas armónicas irreales que no ocurren en la vida real. Aunque tengas 50 botellas empujando, la fuerza máxima está limitada por la resistencia de los materiales.
|
||||||
|
|
||||||
|
## Solución Inicial Implementada (OBSOLETA)
|
||||||
|
|
||||||
|
### 1. Sistema de Tracking de Densidad de Contactos
|
||||||
|
- **Discretización espacial**: El espacio se dividía en regiones de 0.5m x 0.5m x 0.5m
|
||||||
|
- **Contador por región**: Se contaba el número de contactos simultáneos en cada región
|
||||||
|
- **Límite de contactos**: Máximo 10 contactos antes de aplicar limitación de fuerzas
|
||||||
|
|
||||||
|
### 2. Factor de Escala Dinámico
|
||||||
|
```csharp
|
||||||
|
// Sin limitación hasta 10 contactos
|
||||||
|
if (contactCount <= 10) return 1.0f;
|
||||||
|
|
||||||
|
// Reducción progresiva: 5% por cada contacto extra
|
||||||
|
var scaleFactor = 1.0f - (excessContacts * 0.05f);
|
||||||
|
return Math.Max(scaleFactor, 0.3f); // Mínimo 30% de fuerza original
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Aplicación Integral
|
||||||
|
- **SpringSettings**: Se escalaban proporcionalmente (frecuencia reducida)
|
||||||
|
- **MaximumRecoveryVelocity**: Se reducía proporcionalmente
|
||||||
|
- **Todos los tipos de contacto**: Botella-botella, botella-transporte, botella-curva, etc.
|
||||||
|
|
||||||
|
## Razón para la Eliminación
|
||||||
|
Este sistema se **eliminó completamente** porque:
|
||||||
|
- Era un enfoque indirecto modificando parámetros de material
|
||||||
|
- El nuevo sistema de **intervención post-solver** es más efectivo
|
||||||
|
- La cancelación directa de velocidades Z es más elegante y simple
|
||||||
|
- Reducía la complejidad del código sin comprometer la funcionalidad
|
||||||
|
|
||||||
|
# Sistema de Intervención Post-Solver para Control de Velocidades Z
|
||||||
|
|
||||||
|
## Problema Identificado
|
||||||
|
El sistema anterior de limitación de fuerzas pre-solver no controlaba directamente las velocidades Z excesivas generadas por el solver de BEPU. Las botellas seguían elevándose debido a fuerzas de separación acumuladas que se aplicaban después de las restricciones iniciales. Además, el sistema de `forceScale` para contactos múltiples era un enfoque indirecto que no atacaba la causa raíz del problema.
|
||||||
|
|
||||||
|
## Solución Implementada
|
||||||
|
|
||||||
|
### 1. Enfoque Post-Solver como "Fusible de Energía"
|
||||||
|
- **Timing crítico**: Intervención en `OnSubstepEnded` - después del solver, antes de la integración de posición
|
||||||
|
- **Fusible de energía**: Elimina velocidades Z excesivas sin interferir con el comportamiento normal del solver
|
||||||
|
- **Preservación de física**: Permite al solver trabajar con fuerzas realistas, interviniendo solo cuando es necesario
|
||||||
|
|
||||||
|
### 2. Control de Velocidades Z Granular
|
||||||
|
```csharp
|
||||||
|
// Límites configurables
|
||||||
|
const float maxUpwardVelocity = 0.15f; // Anti-elevación
|
||||||
|
const float maxDownwardVelocity = 2.0f; // Anti-hundimiento
|
||||||
|
const float zDampingFactor = 0.7f; // Amortiguamiento suave
|
||||||
|
|
||||||
|
// Intervención directa en velocidades
|
||||||
|
if (velocity.Linear.Z > maxUpwardVelocity) {
|
||||||
|
velocity.Linear.Z = maxUpwardVelocity;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Corrección de Posición Extrema
|
||||||
|
- **Detección de altura excesiva**: Si la botella está muy por encima del nivel normal
|
||||||
|
- **Corrección suave**: Aplicar velocidad correctiva hacia abajo (máximo 0.5 m/s)
|
||||||
|
- **Cálculo proporcional**: Velocidad correctiva basada en la altura excesiva
|
||||||
|
|
||||||
|
## Simplificación del Sistema
|
||||||
|
|
||||||
|
### 1. Eliminación de `ApplyZForceLimitation`
|
||||||
|
- **Antes**: Limitación pre-solver que interfería con el comportamiento normal
|
||||||
|
- **Ahora**: Intervención post-solver más directa y efectiva
|
||||||
|
|
||||||
|
### 2. Simplificación de `IntegrateVelocity`
|
||||||
|
- **Antes**: Control complejo de velocidades Z durante la integración
|
||||||
|
- **Ahora**: Solo control angular básico (anti-vuelco) y damping en Z
|
||||||
|
- **Beneficio**: Menor interferencia con la física normal de BEPU
|
||||||
|
|
||||||
|
## Beneficios del Enfoque Post-Solver
|
||||||
|
- **Más efectivo**: Intervención después de que el solver haya calculado todas las fuerzas
|
||||||
|
- **Menos interferencia**: Permite comportamiento físico normal hasta el punto de intervención
|
||||||
|
- **Más directo**: Control directo de velocidades en lugar de modificar parámetros de material
|
||||||
|
- **Más predecible**: Comportamiento determinista independiente de las configuraciones del solver
|
||||||
|
- **Mantenimiento de rigidez**: Las fuerzas normales en XY permanecen intactas
|
||||||
|
- **Solución integral**: Resuelve tanto el problema de elevación como el de contactos múltiples simultáneos
|
||||||
|
- **Código más simple**: Elimina la complejidad del sistema de tracking de densidad de contactos
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Sistema de Persistencia de Configuración del Entorno de Trabajo
|
||||||
|
|
||||||
|
## Resumen
|
||||||
|
Se ha implementado un sistema completo para guardar y restaurar la configuración del entorno de trabajo para cada imagen, incluyendo la posición del GridSplitter, el estado del canvas (zoom, posición, offset) y el estado de `Is3DUpdateEnabled`.
|
||||||
|
|
||||||
|
## Archivos Creados/Modificados
|
||||||
|
|
||||||
|
### 1. Nuevo Archivo: `Models/WorkspaceConfiguration.cs`
|
||||||
|
- **Propósito**: Contiene la configuración del entorno de trabajo
|
||||||
|
- **Propiedades principales**:
|
||||||
|
- `GridSplitterPosition`: Altura del GridSplitter que separa vista 2D y 3D
|
||||||
|
- `CanvasCenterX/Y`: Posición central del canvas en píxeles
|
||||||
|
- `ZoomLevel`: Nivel de zoom actual del canvas
|
||||||
|
- `CanvasOffsetX/Y`: Offset de transformación del canvas
|
||||||
|
- `Is3DUpdateEnabled`: Estado de las actualizaciones 3D
|
||||||
|
- `HorizontalScrollOffset/VerticalScrollOffset`: Posición del scroll
|
||||||
|
|
||||||
|
### 2. Modificado: `MainViewModel.cs`
|
||||||
|
- **Agregado**: Propiedad `WorkspaceConfig` para manejar la configuración
|
||||||
|
- **Nuevos métodos**:
|
||||||
|
- `CaptureWorkspaceState()`: Captura el estado actual desde la UI
|
||||||
|
- `RestoreWorkspaceState()`: Restaura el estado a la UI
|
||||||
|
- `InitializeDefaultWorkspaceConfig()`: Inicializa configuración por defecto
|
||||||
|
|
||||||
|
### 3. Modificado: `SimulationData` (en MainViewModel.cs)
|
||||||
|
- **Agregado**: Propiedad `WorkspaceConfig` para persistir la configuración junto con cada imagen
|
||||||
|
|
||||||
|
### 4. Modificado: `Serialization/StateSerializer.cs`
|
||||||
|
- **Modificado `SaveState()`**: Ahora captura automáticamente el estado del workspace antes de guardar
|
||||||
|
- **Modificado `LoadCurrentPageState()`**: Carga y restaura la configuración del workspace
|
||||||
|
|
||||||
|
### 5. Modificado: `MainWindow.xaml`
|
||||||
|
- **Agregado**: Nombres a los elementos Grid para poder accederlos desde código:
|
||||||
|
- `x:Name="MainGrid"` al Grid principal
|
||||||
|
- `x:Name="MiddleColumnGrid"` al Grid de la segunda columna
|
||||||
|
|
||||||
|
## Funcionamiento
|
||||||
|
|
||||||
|
### Guardado Automático
|
||||||
|
1. Cuando se guarda el estado de una imagen (`SaveState()`), el sistema automáticamente:
|
||||||
|
- Captura la posición actual del GridSplitter
|
||||||
|
- Captura el estado actual del canvas (zoom, posición, scroll)
|
||||||
|
- Captura el estado de `Is3DUpdateEnabled`
|
||||||
|
- Guarda toda esta información junto con los datos de la imagen
|
||||||
|
|
||||||
|
### Carga Automática
|
||||||
|
1. Cuando se carga una imagen (`LoadState()`), el sistema:
|
||||||
|
- Carga la configuración del workspace guardada para esa imagen
|
||||||
|
- Restaura automáticamente todos los valores a la UI
|
||||||
|
- Si no hay configuración guardada, usa valores por defecto
|
||||||
|
|
||||||
|
### Persistencia por Imagen
|
||||||
|
- Cada imagen tiene su propia configuración independiente
|
||||||
|
- La configuración se guarda en el mismo archivo JSON que los objetos simulables
|
||||||
|
- Compatible con versiones anteriores (si no hay configuración, usa valores por defecto)
|
||||||
|
|
||||||
|
## Características Técnicas
|
||||||
|
|
||||||
|
### Manejo de Errores
|
||||||
|
- Todos los métodos de captura y restauración tienen manejo de excepciones
|
||||||
|
- Los errores se registran en Debug pero no interrumpen el proceso de guardado/carga
|
||||||
|
|
||||||
|
### Compatibilidad
|
||||||
|
- Compatible con archivos existentes (retro-compatibilidad)
|
||||||
|
- Si un archivo no tiene configuración del workspace, se crea una por defecto
|
||||||
|
|
||||||
|
### Uso del Dispatcher
|
||||||
|
- La restauración del estado usa `Dispatcher.BeginInvoke()` para asegurar que la UI esté lista
|
||||||
|
- Prioridad `Background` para no bloquear la interfaz
|
||||||
|
|
||||||
|
## Beneficios
|
||||||
|
|
||||||
|
1. **Consistencia**: Cada imagen mantiene su configuración visual específica
|
||||||
|
2. **Productividad**: Los usuarios no necesitan reconfigurar la vista cada vez que cambian de imagen
|
||||||
|
3. **Flexibilidad**: Diferentes imágenes pueden tener diferentes configuraciones de trabajo
|
||||||
|
4. **Transparencia**: El sistema funciona automáticamente sin intervención del usuario
|
||||||
|
5. **Robustez**: Manejo de errores y compatibilidad con versiones anteriores
|
||||||
|
|
||||||
|
## Uso
|
||||||
|
El sistema es completamente automático. Los usuarios simplemente trabajan normalmente y:
|
||||||
|
- Al cambiar de imagen, la configuración se guarda automáticamente
|
||||||
|
- Al seleccionar una imagen, su configuración se restaura automáticamente
|
||||||
|
- El estado de `Is3DUpdateEnabled` se mantiene independiente para cada imagen
|
||||||
|
- La posición del GridSplitter se recuerda para cada imagen
|
||||||
|
|
||||||
|
## Notas de Implementación
|
||||||
|
- Se usa `TransformGroup`, `ScaleTransform` y `TranslateTransform` para manejar el canvas
|
||||||
|
- El GridSplitter se maneja a través de las `RowDefinitions` del Grid de la segunda columna
|
||||||
|
- La configuración se serializa junto con los datos de simulación en formato JSON
|
||||||
|
- Se mantiene la estructura existente del sistema de serialización
|
139
MainViewModel.cs
139
MainViewModel.cs
|
@ -24,7 +24,8 @@ using System.Text.RegularExpressions;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using CtrEditor.Serialization; // Add this line
|
using CtrEditor.Serialization; // Add this line
|
||||||
using CtrEditor.Controls; // Add this using directive
|
using CtrEditor.Controls; // Add this using directive
|
||||||
using CtrEditor.PopUps; // Add this using directive
|
using System.Linq;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
|
||||||
namespace CtrEditor
|
namespace CtrEditor
|
||||||
|
@ -273,6 +274,9 @@ namespace CtrEditor
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private PLCViewModel pLCViewModel;
|
private PLCViewModel pLCViewModel;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private Models.WorkspaceConfiguration workspaceConfig;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string selectedImage;
|
private string selectedImage;
|
||||||
|
|
||||||
|
@ -442,8 +446,138 @@ namespace CtrEditor
|
||||||
|
|
||||||
// NOTA: La conexión del manager 3D se hace en MainWindow_Loaded
|
// NOTA: La conexión del manager 3D se hace en MainWindow_Loaded
|
||||||
// después de que se cree la instancia de BEPUVisualization3DManager
|
// después de que se cree la instancia de BEPUVisualization3DManager
|
||||||
|
|
||||||
|
// Inicializar configuración del workspace
|
||||||
|
WorkspaceConfig = new Models.WorkspaceConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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
|
// Métodos para manejo de datos de imágenes
|
||||||
private void RenameImage(string imageName)
|
private void RenameImage(string imageName)
|
||||||
{
|
{
|
||||||
|
@ -1385,6 +1519,9 @@ namespace CtrEditor
|
||||||
// Diccionario para almacenar datos expandidos de imágenes
|
// Diccionario para almacenar datos expandidos de imágenes
|
||||||
public Dictionary<string, Models.ImageData>? ImageDataDictionary { get; set; }
|
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
|
// Compatibilidad con versiones anteriores - OBSOLETO
|
||||||
[System.Obsolete("Use ImageDataDictionary instead")]
|
[System.Obsolete("Use ImageDataDictionary instead")]
|
||||||
public Dictionary<string, string>? ImageCustomNames { get; set; }
|
public Dictionary<string, string>? ImageCustomNames { get; set; }
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<Grid Margin="0,20,0,0">
|
<Grid x:Name="MainGrid" Margin="0,20,0,0">
|
||||||
<!-- Margen superior para el menú -->
|
<!-- Margen superior para el menú -->
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="1*" MinWidth="200" />
|
<ColumnDefinition Width="1*" MinWidth="200" />
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
<GridSplitter Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" Width="5" Background="LightGray" />
|
<GridSplitter Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" Width="5" Background="LightGray" />
|
||||||
|
|
||||||
<!-- Segunda Columna -->
|
<!-- Segunda Columna -->
|
||||||
<Grid Grid.Column="1">
|
<Grid x:Name="MiddleColumnGrid" Grid.Column="1">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="3*" />
|
<RowDefinition Height="3*" />
|
||||||
|
|
|
@ -107,6 +107,18 @@ namespace CtrEditor
|
||||||
|
|
||||||
// Conectar el manager 3D con el simulation manager (orden correcto de inicialización)
|
// Conectar el manager 3D con el simulation manager (orden correcto de inicialización)
|
||||||
viewModel.simulationManager.Visualization3DManager = _visualization3DManager;
|
viewModel.simulationManager.Visualization3DManager = _visualization3DManager;
|
||||||
|
|
||||||
|
// ✅ NUEVO: Si ya hay una imagen de fondo cargada, agregarla al viewport 3D
|
||||||
|
if (imagenDeFondo?.Source is BitmapImage existingImage)
|
||||||
|
{
|
||||||
|
// Necesitamos la ruta original de la imagen, usar el UriSource si está disponible
|
||||||
|
var imageUri = existingImage.UriSource?.LocalPath ?? existingImage.UriSource?.ToString();
|
||||||
|
if (!string.IsNullOrEmpty(imageUri))
|
||||||
|
{
|
||||||
|
_visualization3DManager.SetBackgroundImage(imageUri, existingImage.PixelWidth, existingImage.PixelHeight);
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] ✅ Imagen existente agregada al viewport 3D: {imageUri}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +180,12 @@ namespace CtrEditor
|
||||||
Canvas.SetLeft(imagenDeFondo, 0);
|
Canvas.SetLeft(imagenDeFondo, 0);
|
||||||
Canvas.SetTop(imagenDeFondo, 0);
|
Canvas.SetTop(imagenDeFondo, 0);
|
||||||
|
|
||||||
|
// ✅ NUEVO: Agregar la imagen de fondo al viewport 3D con la misma escala
|
||||||
|
if (_visualization3DManager != null)
|
||||||
|
{
|
||||||
|
_visualization3DManager.SetBackgroundImage(imagePath, bitmap.Width, bitmap.Height);
|
||||||
|
}
|
||||||
|
|
||||||
// Eliminar solo los ROIs, no la imagen de fondo ni el área de panning
|
// Eliminar solo los ROIs, no la imagen de fondo ni el área de panning
|
||||||
for (int i = ImagenEnTrabajoCanvas.Children.Count - 1; i >= 0; i--)
|
for (int i = ImagenEnTrabajoCanvas.Children.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
|
@ -367,12 +385,10 @@ namespace CtrEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void MainWindow_KeyDown(object sender, KeyEventArgs e)
|
private void MainWindow_KeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
// Only force canvas focus if PanelEdicion doesn't have focus
|
// Only force canvas focus if PanelEdicion doesn't have focus AND PLCControl doesn't have focus
|
||||||
if (!PanelEdicion.IsKeyboardFocusWithin)
|
if (!PanelEdicion.IsKeyboardFocusWithin && !PLCSim.IsKeyboardFocusWithin)
|
||||||
{
|
{
|
||||||
if (!ImagenEnTrabajoCanvas.IsFocused)
|
if (!ImagenEnTrabajoCanvas.IsFocused)
|
||||||
{
|
{
|
||||||
|
@ -539,6 +555,59 @@ namespace CtrEditor
|
||||||
_objectManager.ClearUndoHistory();
|
_objectManager.ClearUndoHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Establece la imagen de fondo en el viewport 3D
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imagePath">Ruta de la imagen de fondo</param>
|
||||||
|
public void SetBackgroundImage3D(string imagePath)
|
||||||
|
{
|
||||||
|
if (_visualization3DManager != null && imagenDeFondo?.Source is BitmapImage bitmap)
|
||||||
|
{
|
||||||
|
_visualization3DManager.SetBackgroundImage(imagePath, bitmap.Width, bitmap.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Remueve la imagen de fondo del viewport 3D
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveBackgroundImage3D()
|
||||||
|
{
|
||||||
|
_visualization3DManager?.RemoveBackgroundImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Sincroniza la imagen de fondo existente con el viewport 3D
|
||||||
|
/// Útil cuando se necesita actualizar la imagen 3D basándose en la imagen 2D actual
|
||||||
|
/// </summary>
|
||||||
|
public void SyncBackgroundImage3D()
|
||||||
|
{
|
||||||
|
if (_visualization3DManager == null) return;
|
||||||
|
|
||||||
|
if (imagenDeFondo?.Source is BitmapImage existingImage)
|
||||||
|
{
|
||||||
|
// Intentar obtener la ruta de la imagen desde diferentes fuentes
|
||||||
|
var imageUri = existingImage.UriSource?.LocalPath ??
|
||||||
|
existingImage.UriSource?.ToString() ??
|
||||||
|
existingImage.BaseUri?.LocalPath;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(imageUri))
|
||||||
|
{
|
||||||
|
_visualization3DManager.SetBackgroundImage(imageUri, existingImage.PixelWidth, existingImage.PixelHeight);
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] ✅ Imagen sincronizada con viewport 3D");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] ⚠️ No se pudo obtener la ruta de la imagen para sincronizar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Si no hay imagen cargada, remover la imagen de fondo 3D
|
||||||
|
_visualization3DManager.RemoveBackgroundImage();
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] ✅ Imagen de fondo 3D removida (no hay imagen 2D)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
// Aceptar el evento si viene del canvas o de la imagen de fondo
|
// Aceptar el evento si viene del canvas o de la imagen de fondo
|
||||||
|
@ -750,8 +819,8 @@ namespace CtrEditor
|
||||||
|
|
||||||
private void Canvas_KeyDown(object sender, KeyEventArgs e)
|
private void Canvas_KeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
// Only handle if PanelEdicion doesn't have focus
|
// Only handle if PanelEdicion doesn't have focus AND PLCControl doesn't have focus
|
||||||
if (!PanelEdicion.IsKeyboardFocusWithin)
|
if (!PanelEdicion.IsKeyboardFocusWithin && !PLCSim.IsKeyboardFocusWithin)
|
||||||
{
|
{
|
||||||
HandleKeyDown(e);
|
HandleKeyDown(e);
|
||||||
}
|
}
|
||||||
|
@ -759,8 +828,8 @@ namespace CtrEditor
|
||||||
|
|
||||||
private void ScrollViewer_PreviewKeyDown(object sender, KeyEventArgs e)
|
private void ScrollViewer_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
// Only handle if PanelEdicion doesn't have focus
|
// Only handle if PanelEdicion doesn't have focus AND PLCControl doesn't have focus
|
||||||
if (!PanelEdicion.IsKeyboardFocusWithin &&
|
if (!PanelEdicion.IsKeyboardFocusWithin && !PLCSim.IsKeyboardFocusWithin &&
|
||||||
(e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up || e.Key == Key.Down))
|
(e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up || e.Key == Key.Down))
|
||||||
{
|
{
|
||||||
HandleKeyDown(e);
|
HandleKeyDown(e);
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace CtrEditor.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuración del entorno de trabajo que se guarda por imagen.
|
||||||
|
/// Incluye posiciones de splitters, canvas, zoom y otras configuraciones del workspace.
|
||||||
|
/// </summary>
|
||||||
|
public partial class WorkspaceConfiguration : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Height of the 3D view row in pixels")]
|
||||||
|
[property: Category("Splitters:")]
|
||||||
|
private double gridSplitterPosition = 100.0; // Valor por defecto en píxeles
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("X position of the canvas center in pixels")]
|
||||||
|
[property: Category("Canvas Position:")]
|
||||||
|
private double canvasCenterX = 0.0;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Y position of the canvas center in pixels")]
|
||||||
|
[property: Category("Canvas Position:")]
|
||||||
|
private double canvasCenterY = 0.0;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Current zoom level of the canvas")]
|
||||||
|
[property: Category("Canvas View:")]
|
||||||
|
private double zoomLevel = 1.0;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("X offset of the canvas")]
|
||||||
|
[property: Category("Canvas View:")]
|
||||||
|
private double canvasOffsetX = 0.0;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Y offset of the canvas")]
|
||||||
|
[property: Category("Canvas View:")]
|
||||||
|
private double canvasOffsetY = 0.0;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Whether 3D updates are enabled")]
|
||||||
|
[property: Category("3D Settings:")]
|
||||||
|
private bool is3DUpdateEnabled = true;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Horizontal scroll position of the canvas")]
|
||||||
|
[property: Category("Canvas View:")]
|
||||||
|
private double horizontalScrollOffset = 0.0;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Vertical scroll position of the canvas")]
|
||||||
|
[property: Category("Canvas View:")]
|
||||||
|
private double verticalScrollOffset = 0.0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor por defecto con valores iniciales
|
||||||
|
/// </summary>
|
||||||
|
public WorkspaceConfiguration()
|
||||||
|
{
|
||||||
|
// Valores por defecto ya establecidos en las propiedades
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea una copia de la configuración actual
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Nueva instancia con los mismos valores</returns>
|
||||||
|
public WorkspaceConfiguration Clone()
|
||||||
|
{
|
||||||
|
return new WorkspaceConfiguration
|
||||||
|
{
|
||||||
|
GridSplitterPosition = this.GridSplitterPosition,
|
||||||
|
CanvasCenterX = this.CanvasCenterX,
|
||||||
|
CanvasCenterY = this.CanvasCenterY,
|
||||||
|
ZoomLevel = this.ZoomLevel,
|
||||||
|
CanvasOffsetX = this.CanvasOffsetX,
|
||||||
|
CanvasOffsetY = this.CanvasOffsetY,
|
||||||
|
Is3DUpdateEnabled = this.Is3DUpdateEnabled,
|
||||||
|
HorizontalScrollOffset = this.HorizontalScrollOffset,
|
||||||
|
VerticalScrollOffset = this.VerticalScrollOffset
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copia los valores de otra configuración
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">Configuración fuente</param>
|
||||||
|
public void CopyFrom(WorkspaceConfiguration source)
|
||||||
|
{
|
||||||
|
if (source == null) return;
|
||||||
|
|
||||||
|
GridSplitterPosition = source.GridSplitterPosition;
|
||||||
|
CanvasCenterX = source.CanvasCenterX;
|
||||||
|
CanvasCenterY = source.CanvasCenterY;
|
||||||
|
ZoomLevel = source.ZoomLevel;
|
||||||
|
CanvasOffsetX = source.CanvasOffsetX;
|
||||||
|
CanvasOffsetY = source.CanvasOffsetY;
|
||||||
|
Is3DUpdateEnabled = source.Is3DUpdateEnabled;
|
||||||
|
HorizontalScrollOffset = source.HorizontalScrollOffset;
|
||||||
|
VerticalScrollOffset = source.VerticalScrollOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Color = System.Windows.Media.Color;
|
using Color = System.Windows.Media.Color;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace CtrEditor
|
namespace CtrEditor
|
||||||
{
|
{
|
||||||
|
@ -602,9 +603,24 @@ namespace CtrEditor
|
||||||
var panelEdicion = _mainWindow.PanelEdicion;
|
var panelEdicion = _mainWindow.PanelEdicion;
|
||||||
if (panelEdicion != null && panelEdicion.IsKeyboardFocusWithin)
|
if (panelEdicion != null && panelEdicion.IsKeyboardFocusWithin)
|
||||||
{
|
{
|
||||||
|
// Solo forzar actualización si NO es el PLCControl el que tiene el foco
|
||||||
|
var plcControl = _mainWindow.PLCSim;
|
||||||
|
if (plcControl != null && plcControl.IsKeyboardFocusWithin)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("PLCControl tiene foco - NO forzar actualización");
|
||||||
|
System.IO.File.AppendAllText("c:\\temp\\debug_focus.txt",
|
||||||
|
$"{DateTime.Now}: PLCControl tiene foco - NO forzar actualización\n");
|
||||||
|
// No forzar actualización si el PLCControl tiene el foco
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("PanelEdicion tiene foco - forzar actualización");
|
||||||
|
System.IO.File.AppendAllText("c:\\temp\\debug_focus.txt",
|
||||||
|
$"{DateTime.Now}: PanelEdicion tiene foco - forzar actualización\n");
|
||||||
UserControlFactory.ForzarActualizacionBindings(panelEdicion.PropertyGrid);
|
UserControlFactory.ForzarActualizacionBindings(panelEdicion.PropertyGrid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var obj in _selectedObjects.ToList())
|
foreach (var obj in _selectedObjects.ToList())
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,8 +5,21 @@
|
||||||
<vm:osBotella />
|
<vm:osBotella />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
<Ellipse Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
<Ellipse Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Stroke="{Binding ColorButton_oculto}" Fill="Gray"
|
Stroke="{Binding ColorButton_oculto}" Fill="Gray"
|
||||||
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" StrokeThickness="0.5" />
|
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" StrokeThickness="0.5" />
|
||||||
|
|
||||||
|
<!-- ✅ NUEVO: Número del nivel de deslizamiento en el centro -->
|
||||||
|
<TextBlock Text="{Binding NivelDeslizamiento, StringFormat='{}{0:F0}'}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="4"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="White"
|
||||||
|
Background="Gray"
|
||||||
|
Padding="1"
|
||||||
|
Opacity="0.8" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
</UserControl>
|
</UserControl>
|
|
@ -93,6 +93,12 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("En Transporte con Freno")]
|
[property: Name("En Transporte con Freno")]
|
||||||
private bool enTransporteConFreno;
|
private bool enTransporteConFreno;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Category("Calibración")]
|
||||||
|
[property: Description("Nivel de deslizamiento/presión (0-9) para calibración de fricción")]
|
||||||
|
[property: Name("Nivel de Deslizamiento")]
|
||||||
|
private float nivelDeslizamiento;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Simulación")]
|
[property: Category("Simulación")]
|
||||||
[property: Description("Masa del objeto en kg")]
|
[property: Description("Masa del objeto en kg")]
|
||||||
|
@ -174,7 +180,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
// Sistema de colores jerarquizado para diferentes estados
|
// Sistema de colores jerarquizado para diferentes estados
|
||||||
if (SimGeometria.isOnBrakeTransport)
|
if (SimGeometria.isOnBrakeTransport)
|
||||||
ColorButton_oculto = Brushes.Blue; // En transporte con freno (prioridad sobre presión)
|
ColorButton_oculto = Brushes.Blue; // En transporte con freno (prioridad sobre presión)
|
||||||
else if (!SimGeometria.isOnBrakeTransport && SimGeometria.PressureBuildup>1)
|
else if (!SimGeometria.isOnBrakeTransport && SimGeometria.ContactPressure > 0)
|
||||||
ColorButton_oculto = Brushes.Red; // La botella tiene mucha presion
|
ColorButton_oculto = Brushes.Red; // La botella tiene mucha presion
|
||||||
else
|
else
|
||||||
ColorButton_oculto = Brushes.Gray; // 5. Estado libre
|
ColorButton_oculto = Brushes.Gray; // 5. Estado libre
|
||||||
|
@ -189,6 +195,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
Inercia_desde_simulacion = SimGeometria.Mass; // En BEPU usamos masa en lugar de inercia directa
|
Inercia_desde_simulacion = SimGeometria.Mass; // En BEPU usamos masa en lugar de inercia directa
|
||||||
Porcentaje_Traccion = SimGeometria.OverlapPercentage;
|
Porcentaje_Traccion = SimGeometria.OverlapPercentage;
|
||||||
EnTransporteConFreno = SimGeometria.isOnBrakeTransport; // Actualizar estado de freno
|
EnTransporteConFreno = SimGeometria.isOnBrakeTransport; // Actualizar estado de freno
|
||||||
|
NivelDeslizamiento = 10-SimGeometria.forceScale*10; // ✅ NUEVO: Mostrar nivel de deslizamiento para calibración
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ucLoaded()
|
public override void ucLoaded()
|
||||||
|
|
|
@ -53,6 +53,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("Tag Consenso")]
|
[property: Name("Tag Consenso")]
|
||||||
private string tag_consenso;
|
private string tag_consenso;
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Simulación")]
|
[property: Category("Simulación")]
|
||||||
[property: Description("Estado del consenso")]
|
[property: Description("Estado del consenso")]
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using CtrEditor.Simulacion;
|
using CtrEditor.Simulacion;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using CtrEditor.ObjetosSim.UserControls;
|
using CtrEditor.ObjetosSim.UserControls;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Input;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Xceed.Wpf.Toolkit.PropertyGrid;
|
using Xceed.Wpf.Toolkit.PropertyGrid;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
@ -101,7 +102,27 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Método 1: Forzar el foco fuera del PropertyGrid para activar LostFocus
|
// Verificar si el PropertyGrid pertenece al PLCControl para no interferir con su edición
|
||||||
|
var plcControl = FindParentOfType<LibS7Adv.PLCControl>(propertyGrid);
|
||||||
|
if (plcControl != null)
|
||||||
|
{
|
||||||
|
// Debug - log que encontramos el PLCControl
|
||||||
|
System.Diagnostics.Debug.WriteLine("PLCControl detectado - saltando forzado de foco");
|
||||||
|
System.IO.File.AppendAllText("c:\\temp\\debug_focus.txt",
|
||||||
|
$"{DateTime.Now}: PLCControl detectado - saltando forzado de foco\n");
|
||||||
|
// Si es del PLCControl, solo forzar actualización de bindings sin robar el foco
|
||||||
|
var plcBindingExpression = BindingOperations.GetBindingExpression(propertyGrid, PropertyGrid.SelectedObjectProperty);
|
||||||
|
plcBindingExpression?.UpdateSource();
|
||||||
|
ForzarActualizacionControlesEditores(propertyGrid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug - log que NO es PLCControl
|
||||||
|
System.Diagnostics.Debug.WriteLine("NO es PLCControl - aplicando forzado de foco normal");
|
||||||
|
System.IO.File.AppendAllText("c:\\temp\\debug_focus.txt",
|
||||||
|
$"{DateTime.Now}: NO es PLCControl - aplicando forzado de foco normal\n");
|
||||||
|
|
||||||
|
// Método 1: Forzar el foco fuera del PropertyGrid para activar LostFocus (solo para PropertyGrid principal)
|
||||||
if (propertyGrid.IsKeyboardFocusWithin)
|
if (propertyGrid.IsKeyboardFocusWithin)
|
||||||
{
|
{
|
||||||
// Crear un elemento temporal para robar el foco
|
// Crear un elemento temporal para robar el foco
|
||||||
|
@ -121,8 +142,8 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
|
|
||||||
// Método 2: Forzar la actualización de todos los bindings del PropertyGrid
|
// Método 2: Forzar la actualización de todos los bindings del PropertyGrid
|
||||||
var bindingExpression = BindingOperations.GetBindingExpression(propertyGrid, PropertyGrid.SelectedObjectProperty);
|
var propertyGridBindingExpression = BindingOperations.GetBindingExpression(propertyGrid, PropertyGrid.SelectedObjectProperty);
|
||||||
bindingExpression?.UpdateSource();
|
propertyGridBindingExpression?.UpdateSource();
|
||||||
|
|
||||||
// Método 3: Buscar controles de edición activos y forzar su actualización
|
// Método 3: Buscar controles de edición activos y forzar su actualización
|
||||||
ForzarActualizacionControlesEditores(propertyGrid);
|
ForzarActualizacionControlesEditores(propertyGrid);
|
||||||
|
@ -232,6 +253,25 @@ namespace CtrEditor.ObjetosSim
|
||||||
propertyGrid.SelectedObject = selectedObject;
|
propertyGrid.SelectedObject = selectedObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Busca un control padre de un tipo específico en el árbol visual
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">El tipo de control padre a buscar</typeparam>
|
||||||
|
/// <param name="child">El control hijo desde donde empezar la búsqueda</param>
|
||||||
|
/// <returns>El control padre del tipo especificado o null si no se encuentra</returns>
|
||||||
|
private static T FindParentOfType<T>(DependencyObject child) where T : DependencyObject
|
||||||
|
{
|
||||||
|
DependencyObject parent = VisualTreeHelper.GetParent(child);
|
||||||
|
|
||||||
|
if (parent == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (parent is T parentOfType)
|
||||||
|
return parentOfType;
|
||||||
|
|
||||||
|
return FindParentOfType<T>(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,12 +65,16 @@ namespace CtrEditor.Serialization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paso 2: Guardar datos de la p<>gina actual
|
// Paso 2: Capturar estado del workspace antes de guardar
|
||||||
|
_mainViewModel.CaptureWorkspaceState();
|
||||||
|
|
||||||
|
// Paso 3: Guardar datos de la página actual
|
||||||
var dataToSerialize = new SimulationData
|
var dataToSerialize = new SimulationData
|
||||||
{
|
{
|
||||||
ObjetosSimulables = objetosSimulables.Count > 0 ? objetosSimulables : null,
|
ObjetosSimulables = objetosSimulables.Count > 0 ? objetosSimulables : null,
|
||||||
UnitConverter = PixelToMeter.Instance.calc,
|
UnitConverter = PixelToMeter.Instance.calc,
|
||||||
PLC_ConnectionData = _mainViewModel.PLCViewModel,
|
PLC_ConnectionData = _mainViewModel.PLCViewModel,
|
||||||
|
WorkspaceConfig = _mainViewModel.WorkspaceConfig,
|
||||||
DatosLocalesObjetos = datosLocalesObjetos.Count > 0 ? datosLocalesObjetos : null,
|
DatosLocalesObjetos = datosLocalesObjetos.Count > 0 ? datosLocalesObjetos : null,
|
||||||
ImageDataDictionary = _mainViewModel._imageDataDictionary.Count > 0 ? _mainViewModel._imageDataDictionary : null
|
ImageDataDictionary = _mainViewModel._imageDataDictionary.Count > 0 ? _mainViewModel._imageDataDictionary : null
|
||||||
};
|
};
|
||||||
|
@ -114,9 +118,16 @@ namespace CtrEditor.Serialization
|
||||||
// Paso 1: Cargar objetos globales
|
// Paso 1: Cargar objetos globales
|
||||||
LoadAllPagesState(settings);
|
LoadAllPagesState(settings);
|
||||||
|
|
||||||
// Paso 2: Cargar datos de la p<EFBFBD>gina actual
|
// Paso 2: Cargar datos de la página actual
|
||||||
var simulationData = LoadCurrentPageState(selectedImage, settings);
|
var simulationData = LoadCurrentPageState(selectedImage, settings);
|
||||||
|
|
||||||
|
// Paso 2.1: Restaurar configuración del workspace
|
||||||
|
// Usar un dispatcher para asegurar que la UI esté lista
|
||||||
|
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
_mainViewModel.RestoreWorkspaceState();
|
||||||
|
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||||
|
|
||||||
// Paso 3: Crear snapshots de los objetos globales
|
// Paso 3: Crear snapshots de los objetos globales
|
||||||
// Nota: No filtramos por Enable_Local_Data aqu<71> ya que se establecer<65> despu<70>s
|
// Nota: No filtramos por Enable_Local_Data aqu<71> ya que se establecer<65> despu<70>s
|
||||||
foreach (var obj in _mainViewModel.ObjetosSimulables)
|
foreach (var obj in _mainViewModel.ObjetosSimulables)
|
||||||
|
@ -188,6 +199,12 @@ namespace CtrEditor.Serialization
|
||||||
else
|
else
|
||||||
_mainViewModel.PLCViewModel = new PLCViewModel();
|
_mainViewModel.PLCViewModel = new PLCViewModel();
|
||||||
|
|
||||||
|
// Cargar configuración del workspace
|
||||||
|
if (simulationData.WorkspaceConfig is not null)
|
||||||
|
_mainViewModel.WorkspaceConfig = simulationData.WorkspaceConfig;
|
||||||
|
else
|
||||||
|
_mainViewModel.InitializeDefaultWorkspaceConfig();
|
||||||
|
|
||||||
// Solo sobrescribir el UnitConverter si existe uno guardado
|
// Solo sobrescribir el UnitConverter si existe uno guardado
|
||||||
if (simulationData.UnitConverter != null)
|
if (simulationData.UnitConverter != null)
|
||||||
PixelToMeter.Instance.calc = simulationData.UnitConverter;
|
PixelToMeter.Instance.calc = simulationData.UnitConverter;
|
||||||
|
|
|
@ -87,14 +87,16 @@ namespace CtrEditor.Simulacion
|
||||||
|
|
||||||
public bool ConfigureContactManifold<TManifold>(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : unmanaged, IContactManifold<TManifold>
|
public bool ConfigureContactManifold<TManifold>(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : unmanaged, IContactManifold<TManifold>
|
||||||
{
|
{
|
||||||
// ✅ CONFIGURACIÓN BÁSICA de materiales físicos
|
// ✅ CONFIGURACIÓN BÁSICA de materiales físicos con valores rígidos pero estables
|
||||||
pairMaterial = new PairMaterialProperties
|
pairMaterial = new PairMaterialProperties
|
||||||
{
|
{
|
||||||
FrictionCoefficient = 0.3f,
|
FrictionCoefficient = 0.3f,
|
||||||
MaximumRecoveryVelocity = 1f,
|
MaximumRecoveryVelocity = 0.8f, // ✅ AJUSTADO: Velocidad de separación moderada
|
||||||
SpringSettings = new SpringSettings(80, 6)
|
SpringSettings = new SpringSettings(120, 8) // ✅ AJUSTADO: Más rígido pero bien amortiguado
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ✅ ELIMINADO: Sistema de limitación de fuerzas forceScale - reemplazado por intervención post-solver
|
||||||
|
|
||||||
if (_simulationManager != null)
|
if (_simulationManager != null)
|
||||||
{
|
{
|
||||||
var botellaA = GetBotellaFromCollidable(pair.A);
|
var botellaA = GetBotellaFromCollidable(pair.A);
|
||||||
|
@ -124,9 +126,7 @@ namespace CtrEditor.Simulacion
|
||||||
// ✅ CONTACTO BOTELLA-TRANSPORTE: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
|
// ✅ CONTACTO BOTELLA-TRANSPORTE: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
|
||||||
if (botella != null && (transportA != null || transportB != null))
|
if (botella != null && (transportA != null || transportB != null))
|
||||||
{
|
{
|
||||||
// La velocidad del cuerpo cinemático del transporte se establece directamente
|
// ✅ ELIMINADO: botella.forceScale = forceScale;
|
||||||
// en la clase simTransporte. El motor de físicas se encarga del resto.
|
|
||||||
|
|
||||||
// ✅ NUEVO: Lógica de control de presión/flotación
|
// ✅ NUEVO: Lógica de control de presión/flotación
|
||||||
botella.IsTouchingTransport = true;
|
botella.IsTouchingTransport = true;
|
||||||
var botellaCollidable = GetBotellaFromCollidable(pair.A) != null ? pair.A : pair.B;
|
var botellaCollidable = GetBotellaFromCollidable(pair.A) != null ? pair.A : pair.B;
|
||||||
|
@ -135,16 +135,11 @@ namespace CtrEditor.Simulacion
|
||||||
var body = _simulationManager.simulation.Bodies.GetBodyReference(botellaCollidable.BodyHandle);
|
var body = _simulationManager.simulation.Bodies.GetBodyReference(botellaCollidable.BodyHandle);
|
||||||
botella.LastTransportCollisionZ = body.Pose.Position.Z;
|
botella.LastTransportCollisionZ = body.Pose.Position.Z;
|
||||||
}
|
}
|
||||||
// Decrementamos la presión acumulada al tocar un transporte. Se reduce más rápido de lo que aumenta.
|
|
||||||
botella.PressureBuildup = Math.Max(0, botella.PressureBuildup - 0.1f);
|
|
||||||
|
|
||||||
// ✅ Fricción ajustada para un arrastre firme pero no excesivo.
|
|
||||||
var transport = transportA ?? transportB;
|
var transport = transportA ?? transportB;
|
||||||
if (transport.isBrake)
|
if (transport.isBrake)
|
||||||
{
|
{
|
||||||
botella.PressureBuildup = 0;
|
|
||||||
// ✅ NUEVO: Centrar la botella en el transporte de frenado la primera vez que entra.
|
// ✅ NUEVO: Centrar la botella en el transporte de frenado la primera vez que entra.
|
||||||
// if (!botella.isOnBrakeTransport)
|
|
||||||
{
|
{
|
||||||
if (botella.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(botella.BodyHandle) &&
|
if (botella.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(botella.BodyHandle) &&
|
||||||
transport.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(transport.BodyHandle))
|
transport.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(transport.BodyHandle))
|
||||||
|
@ -172,35 +167,24 @@ namespace CtrEditor.Simulacion
|
||||||
}
|
}
|
||||||
|
|
||||||
botella.isOnBrakeTransport = true;
|
botella.isOnBrakeTransport = true;
|
||||||
pairMaterial.FrictionCoefficient = 14f;
|
pairMaterial.FrictionCoefficient = 8f;
|
||||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
|
||||||
pairMaterial.SpringSettings = new SpringSettings(80, 8);
|
pairMaterial.SpringSettings = new SpringSettings(30, 1f); // ✅ REMOVIDO: * forceScale
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (botella.PressureBuildup > minPressureToLostFriction)
|
|
||||||
{
|
|
||||||
botella.isOnBrakeTransport = false;
|
|
||||||
pairMaterial.FrictionCoefficient = 0.1f;
|
|
||||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
|
||||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
botella.isOnBrakeTransport = false;
|
botella.isOnBrakeTransport = false;
|
||||||
pairMaterial.FrictionCoefficient = 1.2f;
|
// ✅ NUEVO: Usar fricción dinámica basada en deslizamiento
|
||||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
pairMaterial.FrictionCoefficient = CalculateTransportFriction(botella, transport, botellaCollidable);
|
||||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
|
||||||
}
|
pairMaterial.SpringSettings = new SpringSettings(30, 1f); // ✅ REMOVIDO: * forceScale
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// ✅ CONTACTO BOTELLA-CURVA: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
|
// ✅ CONTACTO BOTELLA-CURVA: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
|
||||||
else if (botella != null && (curveA != null || curveB != null))
|
else if (botella != null && (curveA != null || curveB != null))
|
||||||
{
|
{
|
||||||
// El motor de físicas usará la velocidad angular del cuerpo cinemático de la curva
|
// ✅ ELIMINADO: botella.forceScale = forceScale;
|
||||||
// para calcular las fuerzas de fricción y arrastrar la botella.
|
|
||||||
|
|
||||||
// ✅ NUEVO: Lógica de control de presión/flotación
|
// ✅ NUEVO: Lógica de control de presión/flotación
|
||||||
botella.IsTouchingTransport = true;
|
botella.IsTouchingTransport = true;
|
||||||
botella.isOnBrakeTransport = false;
|
botella.isOnBrakeTransport = false;
|
||||||
|
@ -210,59 +194,42 @@ namespace CtrEditor.Simulacion
|
||||||
var body = _simulationManager.simulation.Bodies.GetBodyReference(botellaCollidable.BodyHandle);
|
var body = _simulationManager.simulation.Bodies.GetBodyReference(botellaCollidable.BodyHandle);
|
||||||
botella.LastTransportCollisionZ = body.Pose.Position.Z;
|
botella.LastTransportCollisionZ = body.Pose.Position.Z;
|
||||||
}
|
}
|
||||||
// Decrementamos la presión acumulada al tocar una curva.
|
|
||||||
botella.PressureBuildup = Math.Max(0, botella.PressureBuildup - 0.1f);
|
|
||||||
|
|
||||||
// ✅ Fricción ajustada para un arrastre firme pero no excesivo.
|
// ✅ NUEVO: Usar fricción dinámica basada en deslizamiento
|
||||||
if (botella.PressureBuildup > minPressureToLostFriction)
|
var curve = curveA ?? curveB;
|
||||||
{
|
pairMaterial.FrictionCoefficient = CalculateCurveFriction(botella, curve, botellaCollidable);
|
||||||
pairMaterial.FrictionCoefficient = 0.1f;
|
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
|
||||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
pairMaterial.SpringSettings = new SpringSettings(30, 1f); // ✅ REMOVIDO: * forceScale
|
||||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
|
||||||
}else
|
|
||||||
{
|
|
||||||
pairMaterial.FrictionCoefficient = 1f;
|
|
||||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
|
||||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción
|
// ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción
|
||||||
else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null))
|
else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null))
|
||||||
{
|
{
|
||||||
// Fricción baja para las guías, deben deslizar, no arrastrar.
|
// Fricción baja para las guías, deben deslizar, no arrastrar.
|
||||||
pairMaterial.FrictionCoefficient = 0.01f;
|
pairMaterial.FrictionCoefficient = 0.01f;
|
||||||
pairMaterial.MaximumRecoveryVelocity = 2f;
|
pairMaterial.MaximumRecoveryVelocity = 0.95f; // ✅ REMOVIDO: * forceScale
|
||||||
pairMaterial.SpringSettings = new SpringSettings(20, 1);
|
pairMaterial.SpringSettings = new SpringSettings(40, 1f); // ✅ REMOVIDO: * forceScale
|
||||||
}
|
}
|
||||||
// ✅ NUEVO: CONTACTO BOTELLA-BOTELLA: Configuración más suave para evitar el comportamiento "pegajoso".
|
// ✅ NUEVO: CONTACTO BOTELLA-BOTELLA: Lógica de presión proactiva basada en la penetración.
|
||||||
else if (botellaA != null && botellaB != null)
|
else if (botellaA != null && botellaB != null)
|
||||||
{
|
{
|
||||||
if (botella.isOnBrakeTransport)
|
// ✅ NUEVO: Usar método especializado para contactos botella-botella
|
||||||
{
|
CalculateBottlePression(ref manifold, botellaA, botellaB);
|
||||||
pairMaterial.FrictionCoefficient = 2.0f; // Un poco menos de fricción.
|
|
||||||
pairMaterial.MaximumRecoveryVelocity = 1.5f; // Menos rebote.
|
|
||||||
pairMaterial.SpringSettings = new SpringSettings(80, 1f); // Muelle MUCHO más suave y críticamente amortiguado.
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pairMaterial.FrictionCoefficient = 0.01f; // Un poco menos de fricción.
|
|
||||||
pairMaterial.MaximumRecoveryVelocity = 1.5f; // Menos rebote.
|
|
||||||
pairMaterial.SpringSettings = new SpringSettings(20, 0.5f); // Muelle MUCHO más suave y críticamente amortiguado.
|
|
||||||
}
|
|
||||||
// Si una botella tiene presion esta presion es transmitia a el resto para que el resto pierda el coeficiente de friccion sobre los transportes
|
|
||||||
if (botellaA.PressureBuildup > 0 && botellaB.PressureBuildup == 0)
|
|
||||||
botellaB.PressureBuildup = botellaA.PressureBuildup;
|
|
||||||
if (botellaB.PressureBuildup > 0 && botellaA.PressureBuildup == 0)
|
|
||||||
botellaA.PressureBuildup = botellaB.PressureBuildup;
|
|
||||||
|
|
||||||
|
// ✅ NUEVO: Limitar fuerzas basadas en profundidad de penetración
|
||||||
|
//var maxPenetrationDepth = GetMaxPenetrationDepth(ref manifold);
|
||||||
|
// Fuerzas normales para contactos superficiales - botellas rígidas
|
||||||
|
pairMaterial.FrictionCoefficient = 0.02f;
|
||||||
|
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
|
||||||
|
pairMaterial.SpringSettings = new SpringSettings(40, 10f); // ✅ REMOVIDO: * forceScale
|
||||||
}
|
}
|
||||||
// Ajustes básicos para otras botellas
|
// Ajustes básicos para otras botellas
|
||||||
else if (botella != null)
|
else if (botella != null)
|
||||||
{
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigureContactManifold] ERROR: contacto no resuelto!");
|
||||||
// Fricción moderada para colisiones entre botellas.
|
// Fricción moderada para colisiones entre botellas.
|
||||||
pairMaterial.FrictionCoefficient = 0.3f;
|
pairMaterial.FrictionCoefficient = 0f;
|
||||||
pairMaterial.MaximumRecoveryVelocity = 1f;
|
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
|
||||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
pairMaterial.SpringSettings = new SpringSettings(80, 1); // ✅ REMOVIDO: * forceScale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,9 +315,174 @@ namespace CtrEditor.Simulacion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado directamente en el manifold
|
/// ✅ NUEVO: Calcula la fricción dinámica para contactos botella-transporte basada en el deslizamiento relativo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
private float CalculateTransportFriction(simBotella botella, simTransporte transport, CollidableReference botellaCollidable)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (botella?.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(botella.BodyHandle) &&
|
||||||
|
transport?.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(transport.BodyHandle))
|
||||||
|
{
|
||||||
|
var bottleBody = _simulationManager.simulation.Bodies.GetBodyReference(botella.BodyHandle);
|
||||||
|
var transportBody = _simulationManager.simulation.Bodies.GetBodyReference(transport.BodyHandle);
|
||||||
|
|
||||||
|
var bottleVelocity = bottleBody.Velocity.Linear;
|
||||||
|
var transportVelocity = transport.CalcularVelocidadCinematica(); // La velocidad cinemática se actualiza en OnSubstepStarted
|
||||||
|
|
||||||
|
var slipVelocity = bottleVelocity - transportVelocity;
|
||||||
|
slipVelocity.Z = 0; // Ignoramos el deslizamiento vertical
|
||||||
|
var slipSpeed = slipVelocity.Length();
|
||||||
|
|
||||||
|
float slipSpeedThreshold = transportVelocity.Length()*0.85f; // m/s - umbral para cambiar de fricción estática a dinámica
|
||||||
|
const float heatupRate = 1f; // Qué tan rápido sube HighSlippery
|
||||||
|
const float cooldownRate = 1f; // Qué tan rápido baja HighSlippery
|
||||||
|
const float staticFriction = 0.50f;
|
||||||
|
const float dynamicFriction = 0.30f;
|
||||||
|
|
||||||
|
// Sistema de heatup/cooldown para HighSlippery
|
||||||
|
if (slipSpeed > slipSpeedThreshold) // && botella.ContactPressure > 0
|
||||||
|
botella.HighSlippery = Math.Min(9f, botella.HighSlippery + heatupRate);
|
||||||
|
else
|
||||||
|
botella.HighSlippery = Math.Max(0f, botella.HighSlippery - cooldownRate);
|
||||||
|
|
||||||
|
// Calcular fricción basada en HighSlippery
|
||||||
|
// HighSlippery > 3 = fricción dinámica, <= 3 = fricción estática
|
||||||
|
if (botella.ContactPressure > 0)
|
||||||
|
return dynamicFriction / 10;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (botella.HighSlippery > 1f)
|
||||||
|
return dynamicFriction;
|
||||||
|
else
|
||||||
|
return staticFriction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[CalculateTransportFriction] Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valor por defecto si hay algún error
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Calcula la fricción dinámica para contactos botella-curva basada en el deslizamiento relativo
|
||||||
|
/// </summary>
|
||||||
|
private float CalculateCurveFriction(simBotella botella, simCurve curve, CollidableReference botellaCollidable)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (botella?.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(botella.BodyHandle) &&
|
||||||
|
curve?.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle))
|
||||||
|
{
|
||||||
|
var bottleBody = _simulationManager.simulation.Bodies.GetBodyReference(botella.BodyHandle);
|
||||||
|
var curveBody = _simulationManager.simulation.Bodies.GetBodyReference(curve.BodyHandle);
|
||||||
|
|
||||||
|
// Aproximamos la velocidad de la superficie de la curva en la posición de la botella.
|
||||||
|
// v = ω x r
|
||||||
|
var r = bottleBody.Pose.Position - curveBody.Pose.Position;
|
||||||
|
var curveVelocityAtPoint = Vector3.Cross(curve.CalcularVelocidadCinematica(), r);
|
||||||
|
|
||||||
|
var slipVelocity = bottleBody.Velocity.Linear - curveVelocityAtPoint;
|
||||||
|
slipVelocity.Z = 0; // Ignoramos el deslizamiento vertical
|
||||||
|
var slipSpeed = slipVelocity.Length();
|
||||||
|
|
||||||
|
float slipSpeedThreshold = curveVelocityAtPoint.Length() * 0.85f; // m/s
|
||||||
|
const float heatupRate = 1f; // Qué tan rápido sube HighSlippery en curvas
|
||||||
|
const float cooldownRate = 1f; // Qué tan rápido baja HighSlippery en curvas
|
||||||
|
const float staticFriction = 0.50f;
|
||||||
|
const float dynamicFriction = 0.30f;
|
||||||
|
|
||||||
|
// Sistema de heatup/cooldown para HighSlippery
|
||||||
|
if (slipSpeed > slipSpeedThreshold) // && botella.ContactPressure > 0
|
||||||
|
botella.HighSlippery = Math.Min(9f, botella.HighSlippery + heatupRate);
|
||||||
|
else
|
||||||
|
botella.HighSlippery = Math.Max(0f, botella.HighSlippery - cooldownRate);
|
||||||
|
|
||||||
|
// Calcular fricción basada en HighSlippery
|
||||||
|
// HighSlippery > 3 = fricción dinámica, <= 3 = fricción estática
|
||||||
|
if (botella.ContactPressure > 0)
|
||||||
|
return dynamicFriction / 10;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (botella.HighSlippery > 1f)
|
||||||
|
return dynamicFriction;
|
||||||
|
else
|
||||||
|
return staticFriction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[CalculateCurveFriction] Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valor por defecto si hay algún error
|
||||||
|
return 0.8f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Calcula los parámetros de material para contactos botella-botella basado en la profundidad de penetración
|
||||||
|
/// </summary>
|
||||||
|
private void CalculateBottlePression<TManifold>(ref TManifold manifold, simBotella botellaA, simBotella botellaB) where TManifold : unmanaged, IContactManifold<TManifold>
|
||||||
|
{
|
||||||
|
var pairMaterial = new PairMaterialProperties();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const float penetrationDepthThreshold = 0.001f; // Umbral de penetración en metros para detectar alta presión.
|
||||||
|
|
||||||
|
// Iteramos sobre todos los puntos de contacto en el manifold.
|
||||||
|
for (int i = 0; i < manifold.Count; ++i)
|
||||||
|
{
|
||||||
|
// Si la profundidad de la penetración supera nuestro umbral,
|
||||||
|
// consideramos que este contacto está bajo alta presión.
|
||||||
|
if (manifold.GetDepth(i) > penetrationDepthThreshold)
|
||||||
|
{
|
||||||
|
botellaA.ContactPressure = Math.Max(botellaA.ContactPressure, manifold.GetDepth(i) - penetrationDepthThreshold);
|
||||||
|
botellaB.ContactPressure = Math.Max(botellaB.ContactPressure, manifold.GetDepth(i) - penetrationDepthThreshold);
|
||||||
|
// break; // Un punto de alta presión es suficiente.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[CalculateBottleToBottleMaterial] Error: {ex.Message}");
|
||||||
|
// Valores por defecto seguros
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVA FUNCIÓN: Obtiene la profundidad máxima de penetración en un manifold
|
||||||
|
/// </summary>
|
||||||
|
private float GetMaxPenetrationDepth<TManifold>(ref TManifold manifold) where TManifold : unmanaged, IContactManifold<TManifold>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
float maxDepth = 0f;
|
||||||
|
for (int i = 0; i < manifold.Count; ++i)
|
||||||
|
{
|
||||||
|
var depth = manifold.GetDepth(i);
|
||||||
|
if (depth > maxDepth)
|
||||||
|
{
|
||||||
|
maxDepth = depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxDepth;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[GetMaxPenetrationDepth] Error: {ex.Message}");
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold)
|
public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold)
|
||||||
|
@ -396,7 +528,8 @@ namespace CtrEditor.Simulacion
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ REESCRITO COMPLETAMENTE: para corregir errores de compilación y aplicar la lógica de amortiguamiento de forma segura.
|
// ✅ SIMPLIFICADO: IntegrateVelocity ahora solo aplica física básica
|
||||||
|
// El control de velocidades Z se maneja en OnSubstepEnded (post-solver)
|
||||||
public void IntegrateVelocity(Vector<int> bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector<int> integrationMask, int workerIndex, Vector<float> dt, ref BodyVelocityWide velocity)
|
public void IntegrateVelocity(Vector<int> bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector<int> integrationMask, int workerIndex, Vector<float> dt, ref BodyVelocityWide velocity)
|
||||||
{
|
{
|
||||||
// 1. APLICAR GRAVEDAD Y AMORTIGUAMIENTO ESTÁNDAR (VECTORIZADO)
|
// 1. APLICAR GRAVEDAD Y AMORTIGUAMIENTO ESTÁNDAR (VECTORIZADO)
|
||||||
|
@ -411,7 +544,7 @@ namespace CtrEditor.Simulacion
|
||||||
velocity.Linear *= linearDampingFactor;
|
velocity.Linear *= linearDampingFactor;
|
||||||
velocity.Angular *= angularDampingFactor;
|
velocity.Angular *= angularDampingFactor;
|
||||||
|
|
||||||
// 2. LÓGICA PERSONALIZADA PARA BOTELLAS (ESCALAR)
|
// 2. LÓGICA PERSONALIZADA MÍNIMA PARA BOTELLAS (ESCALAR)
|
||||||
const float bottleExtraZAngularDamping = 0.8f;
|
const float bottleExtraZAngularDamping = 0.8f;
|
||||||
var bottleZAngularDampingFactor = MathF.Pow(MathHelper.Clamp(1 - bottleExtraZAngularDamping, 0, 1), dt[0]);
|
var bottleZAngularDampingFactor = MathF.Pow(MathHelper.Clamp(1 - bottleExtraZAngularDamping, 0, 1), dt[0]);
|
||||||
|
|
||||||
|
@ -427,13 +560,27 @@ namespace CtrEditor.Simulacion
|
||||||
var handle = _simulationManager.simulation.Bodies.ActiveSet.IndexToHandle[bodyIndex];
|
var handle = _simulationManager.simulation.Bodies.ActiveSet.IndexToHandle[bodyIndex];
|
||||||
if (_simulationManager.CollidableData.TryGetValue(handle.Value, out simBase baseObj) && baseObj is simBotella)
|
if (_simulationManager.CollidableData.TryGetValue(handle.Value, out simBase baseObj) && baseObj is simBotella)
|
||||||
{
|
{
|
||||||
// B. DAMPING ANGULAR EXTRA EN EJE Z
|
// EXTRAER VELOCIDADES DEL PAQUETE A ARRAYS
|
||||||
// Para modificar un solo carril de un Vector<T>, la forma más segura es extraer
|
var angularX = new float[Vector<float>.Count];
|
||||||
// los valores a un array, modificar el índice y crear un nuevo Vector<T>.
|
var angularY = new float[Vector<float>.Count];
|
||||||
var zVel = new float[Vector<float>.Count];
|
var angularZ = new float[Vector<float>.Count];
|
||||||
velocity.Angular.Z.CopyTo(zVel, 0);
|
|
||||||
zVel[i] *= bottleZAngularDampingFactor;
|
velocity.Angular.X.CopyTo(angularX, 0);
|
||||||
velocity.Angular.Z = new Vector<float>(zVel);
|
velocity.Angular.Y.CopyTo(angularY, 0);
|
||||||
|
velocity.Angular.Z.CopyTo(angularZ, 0);
|
||||||
|
|
||||||
|
// CONTROL ANGULAR BÁSICO PARA BOTELLAS
|
||||||
|
// Anti-vuelco: Anular rotaciones que tumben la botella
|
||||||
|
angularX[i] = 0;
|
||||||
|
angularY[i] = 0;
|
||||||
|
|
||||||
|
// Damping angular extra en Z
|
||||||
|
angularZ[i] *= bottleZAngularDampingFactor;
|
||||||
|
|
||||||
|
// RECONSTRUIR LOS VECTORES 'WIDE' CON LOS VALORES MODIFICADOS
|
||||||
|
velocity.Angular.X = new Vector<float>(angularX);
|
||||||
|
velocity.Angular.Y = new Vector<float>(angularY);
|
||||||
|
velocity.Angular.Z = new Vector<float>(angularZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,6 +634,8 @@ namespace CtrEditor.Simulacion
|
||||||
public Dictionary<int, simBase> CollidableData = new Dictionary<int, simBase>();
|
public Dictionary<int, simBase> CollidableData = new Dictionary<int, simBase>();
|
||||||
public float GlobalTime { get; private set; }
|
public float GlobalTime { get; private set; }
|
||||||
|
|
||||||
|
// ✅ ELIMINADO: Sistema de limitación de fuerzas - reemplazado por intervención post-solver
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Obtiene el objeto simBase correspondiente a un BodyHandle
|
/// Obtiene el objeto simBase correspondiente a un BodyHandle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -511,6 +660,10 @@ namespace CtrEditor.Simulacion
|
||||||
// ✅ NUEVOS - gestión automática de clasificación
|
// ✅ NUEVOS - gestión automática de clasificación
|
||||||
private void RegisterObjectHandle(simBase obj)
|
private void RegisterObjectHandle(simBase obj)
|
||||||
{
|
{
|
||||||
|
if (obj != null && obj.BodyHandle.Value >= 0)
|
||||||
|
{
|
||||||
|
CollidableData[obj.BodyHandle.Value] = obj;
|
||||||
|
}
|
||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case simBotella bottle:
|
case simBotella bottle:
|
||||||
|
@ -540,6 +693,10 @@ namespace CtrEditor.Simulacion
|
||||||
|
|
||||||
private void UnregisterObjectHandle(simBase obj)
|
private void UnregisterObjectHandle(simBase obj)
|
||||||
{
|
{
|
||||||
|
if (obj != null && obj.BodyHandle.Value >= 0)
|
||||||
|
{
|
||||||
|
CollidableData.Remove(obj.BodyHandle.Value);
|
||||||
|
}
|
||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case simBotella bottle:
|
case simBotella bottle:
|
||||||
|
@ -624,8 +781,8 @@ namespace CtrEditor.Simulacion
|
||||||
// ✅ MODIFICADO: Pasar referencia de this al PoseIntegratorCallbacks
|
// ✅ MODIFICADO: Pasar referencia de this al PoseIntegratorCallbacks
|
||||||
var poseIntegratorCallbacks = new PoseIntegratorCallbacks(
|
var poseIntegratorCallbacks = new PoseIntegratorCallbacks(
|
||||||
gravity: new Vector3(0, 0, -9.81f), // Gravedad en Z
|
gravity: new Vector3(0, 0, -9.81f), // Gravedad en Z
|
||||||
linearDamping: 0.999f, // Amortiguamiento lineal suave
|
linearDamping: 0.03f, // 0.999f, // Amortiguamiento lineal suave
|
||||||
angularDamping: 0.995f, // Amortiguamiento angular más fuerte para detener rotaciones
|
angularDamping: 0.03f, // 0.995f, // Amortiguamiento angular más fuerte para detener rotaciones
|
||||||
simulationManager: this // ✅ NUEVA REFERENCIA
|
simulationManager: this // ✅ NUEVA REFERENCIA
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -656,10 +813,71 @@ namespace CtrEditor.Simulacion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ✅ NUEVO: Se ejecuta al final de cada sub-paso de la simulación, pero antes de la integración de la pose.
|
/// ✅ NUEVO: Se ejecuta al final de cada sub-paso de la simulación, pero antes de la integración de la pose.
|
||||||
/// Pone a cero las velocidades de los cuerpos cinemáticos para evitar que el PoseIntegrator mueva su posición.
|
/// Actúa como "fusible de energía" para eliminar velocidades Z excesivas y mantener objetos cinemáticos estáticos.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnSubstepEnded(int substepIndex)
|
private void OnSubstepEnded(int substepIndex)
|
||||||
{
|
{
|
||||||
|
// 1. INTERVENCIÓN POST-SOLVER: Control de velocidades Z para botellas
|
||||||
|
// Esto actúa como un "fusible de energía" que elimina velocidades Z excesivas
|
||||||
|
// después de que el solver haya hecho su trabajo
|
||||||
|
foreach (var botella in Cuerpos.OfType<simBotella>())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (botella?.BodyHandle.Value >= 0 && simulation?.Bodies?.BodyExists(botella.BodyHandle) == true)
|
||||||
|
{
|
||||||
|
var body = simulation.Bodies.GetBodyReference(botella.BodyHandle);
|
||||||
|
var velocity = body.Velocity;
|
||||||
|
var pose = body.Pose;
|
||||||
|
|
||||||
|
// CONFIGURACIÓN DE LÍMITES
|
||||||
|
const float maxUpwardVelocity = 0f; // Velocidad Z máxima hacia arriba (m/s)
|
||||||
|
const float maxXYVelocity = 1.0f; // Límite de velocidad en el plano XY (m/s)
|
||||||
|
|
||||||
|
// LIMITAR VELOCIDADES Z EXCESIVAS
|
||||||
|
if (velocity.Linear.Z > maxUpwardVelocity)
|
||||||
|
{
|
||||||
|
// Cancelar velocidades hacia arriba excesivas (anti-elevación)
|
||||||
|
velocity.Linear.Z = 0;
|
||||||
|
pose.Position.Z = botella.Radius + simBase.zPos_Transporte + simBase.zAltura_Transporte;
|
||||||
|
body.Pose = pose; // Se actualiza la pose completa
|
||||||
|
}
|
||||||
|
|
||||||
|
// LIMITAR VELOCIDAD MÁXIMA EN PLANO XY
|
||||||
|
var velocityXY = new Vector2(velocity.Linear.X, velocity.Linear.Y);
|
||||||
|
if (velocityXY.LengthSquared() > maxXYVelocity * maxXYVelocity)
|
||||||
|
{
|
||||||
|
var speedXY = velocityXY.Length();
|
||||||
|
var scale = maxXYVelocity / speedXY;
|
||||||
|
velocity.Linear.X *= scale;
|
||||||
|
velocity.Linear.Y *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar siempre la velocidad, ya que puede haber sido modificada en Z o XY
|
||||||
|
body.Velocity = velocity;
|
||||||
|
|
||||||
|
//// CONTROL DE POSICIÓN EXTREMA
|
||||||
|
//// Si la botella está muy por encima del nivel normal, aplicar corrección
|
||||||
|
//var maxAllowedZ = simBase.zPos_Transporte + simBase.zAltura_Transporte + botella.Radius * 2;
|
||||||
|
|
||||||
|
//if (position.Z > maxAllowedZ)
|
||||||
|
//{
|
||||||
|
// // Aplicar velocidad correctiva hacia abajo
|
||||||
|
// var excessHeight = position.Z - maxAllowedZ;
|
||||||
|
// var correctionVelocity = -Math.Min(excessHeight * 5f, 0.5f); // Máximo 0.5 m/s hacia abajo
|
||||||
|
// velocity.Linear.Z = correctionVelocity;
|
||||||
|
// body.Velocity = velocity;
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[OnSubstepEnded] Error procesando botella {botella?.BodyHandle}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. LIMPIAR VELOCIDADES DE OBJETOS CINEMÁTICOS
|
||||||
|
// Mantener transportes y curvas estáticos después del solver
|
||||||
foreach (var transport in Cuerpos.OfType<simTransporte>())
|
foreach (var transport in Cuerpos.OfType<simTransporte>())
|
||||||
{
|
{
|
||||||
if (transport.BodyHandle.Value >= 0 && simulation.Bodies.BodyExists(transport.BodyHandle))
|
if (transport.BodyHandle.Value >= 0 && simulation.Bodies.BodyExists(transport.BodyHandle))
|
||||||
|
@ -799,8 +1017,13 @@ namespace CtrEditor.Simulacion
|
||||||
foreach (var botella in Cuerpos.OfType<simBotella>())
|
foreach (var botella in Cuerpos.OfType<simBotella>())
|
||||||
{
|
{
|
||||||
botella.IsTouchingTransport = false;
|
botella.IsTouchingTransport = false;
|
||||||
|
botella.ContactPressure = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ ELIMINADO: Reset del mapa de densidad de contactos ya no necesario
|
||||||
|
|
||||||
|
// ✅ ELIMINADO: ApplyZForceLimitation - ahora usamos intervención post-solver en OnSubstepEnded
|
||||||
|
|
||||||
// ✅ CONSERVAR - validación de deltaTime
|
// ✅ CONSERVAR - validación de deltaTime
|
||||||
var currentTime = stopwatch.Elapsed.TotalMilliseconds;
|
var currentTime = stopwatch.Elapsed.TotalMilliseconds;
|
||||||
var deltaTime = (float)((currentTime - stopwatch_last) / 1000.0);
|
var deltaTime = (float)((currentTime - stopwatch_last) / 1000.0);
|
||||||
|
@ -857,27 +1080,26 @@ namespace CtrEditor.Simulacion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ✅ CONSERVAR - limitación de rotación y mantener botellas despiertas
|
//// ✅ CONSERVAR - limitación de rotación y mantener botellas despiertas
|
||||||
foreach (var cuerpo in Cuerpos.OfType<simBotella>().ToList())
|
//foreach (var cuerpo in Cuerpos.OfType<simBotella>().ToList())
|
||||||
{
|
//{
|
||||||
try
|
// try
|
||||||
{
|
// {
|
||||||
if (simulation.Bodies.BodyExists(cuerpo.BodyHandle))
|
// if (simulation.Bodies.BodyExists(cuerpo.BodyHandle))
|
||||||
{
|
// {
|
||||||
// cuerpo.LimitRotationToXYPlane();
|
// // cuerpo.LimitRotationToXYPlane();
|
||||||
// simulation.Awakener.AwakenBody(cuerpo.BodyHandle);
|
// // simulation.Awakener.AwakenBody(cuerpo.BodyHandle);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
catch (Exception ex)
|
// catch (Exception ex)
|
||||||
{
|
// {
|
||||||
// Error limiting rotation for bottle - continue
|
// // Error limiting rotation for bottle - continue
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
// ✅ CONSERVAR - sistemas que funcionan bien
|
// ✅ CONSERVAR - sistemas que funcionan bien
|
||||||
ProcessBarreraContacts();
|
ProcessBarreraContacts();
|
||||||
ProcessDescarteContacts();
|
ProcessDescarteContacts();
|
||||||
ProcessPressureSystem();
|
|
||||||
ProcessCleanupSystem();
|
ProcessCleanupSystem();
|
||||||
|
|
||||||
// ✅ SIMPLIFICAR - limpiar solo contactos que usamos
|
// ✅ SIMPLIFICAR - limpiar solo contactos que usamos
|
||||||
|
@ -904,59 +1126,7 @@ namespace CtrEditor.Simulacion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ✅ NUEVO: Sistema para controlar la "presión" y evitar que las botellas floten
|
|
||||||
/// Si una botella no toca un transporte por varios frames, se reestablece su altura.
|
|
||||||
/// </summary>
|
|
||||||
private void ProcessPressureSystem()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var botellas = Cuerpos.OfType<simBotella>().ToList();
|
|
||||||
|
|
||||||
foreach (var botella in botellas)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (botella == null || !simulation.Bodies.BodyExists(botella.BodyHandle))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!botella.IsTouchingTransport && botella.LastTransportCollisionZ.HasValue)
|
|
||||||
{
|
|
||||||
var body = simulation.Bodies.GetBodyReference(botella.BodyHandle);
|
|
||||||
var currentZ = body.Pose.Position.Z;
|
|
||||||
|
|
||||||
// ✅ NUEVA CONDICIÓN: Solo actuar si la botella ha subido
|
|
||||||
if (currentZ > botella.LastTransportCollisionZ.Value)
|
|
||||||
{
|
|
||||||
// Incrementar el contador de presión.
|
|
||||||
botella.PressureBuildup++;
|
|
||||||
|
|
||||||
// Si se acumula suficiente presión (más de 2 frames sin contacto y subiendo),
|
|
||||||
// es un indicio de que está flotando. La reestablecemos a su última Z conocida.
|
|
||||||
if (botella.PressureBuildup > 2)
|
|
||||||
{
|
|
||||||
var pose = body.Pose;
|
|
||||||
pose.Position.Z = botella.LastTransportCollisionZ.Value;
|
|
||||||
body.Pose = pose;
|
|
||||||
|
|
||||||
// Opcional: resetear el contador para no aplicarlo constantemente en cada frame
|
|
||||||
// botella.PressureBuildup = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"[ProcessPressureSystem] Error processing bottle {botella?.BodyHandle}: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"[ProcessPressureSystem] Critical error: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ✅ NUEVO: Marca un objeto para eliminación diferida (más seguro)
|
/// ✅ NUEVO: Marca un objeto para eliminación diferida (más seguro)
|
||||||
|
@ -1414,6 +1584,11 @@ namespace CtrEditor.Simulacion
|
||||||
return lineStart + lineDirection * projectionLength;
|
return lineStart + lineDirection * projectionLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ ELIMINADO: Funciones del sistema de limitación de fuerzas forceScale
|
||||||
|
// GetContactRegion, RegisterContactAndGetForceScale, GetContactPosition
|
||||||
|
|
||||||
|
// ✅ ELIMINADO: ApplyZForceLimitation - reemplazado por intervención post-solver en OnSubstepEnded
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
|
|
|
@ -10,6 +10,7 @@ using System.Linq;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
namespace CtrEditor.Simulacion
|
namespace CtrEditor.Simulacion
|
||||||
{
|
{
|
||||||
|
@ -80,6 +81,9 @@ namespace CtrEditor.Simulacion
|
||||||
// ✅ NUEVO: Diccionario para gestionar animaciones activas
|
// ✅ NUEVO: Diccionario para gestionar animaciones activas
|
||||||
private Dictionary<simBase, Storyboard> activeAnimations;
|
private Dictionary<simBase, Storyboard> activeAnimations;
|
||||||
|
|
||||||
|
// ✅ NUEVO: Referencia al modelo visual de la imagen de fondo
|
||||||
|
private ModelVisual3D? _backgroundImageVisual;
|
||||||
|
|
||||||
// ✅ CORREGIDO: Flag de debug para mostrar triángulos individuales de curvas (true temporalmente para verificar)
|
// ✅ CORREGIDO: Flag de debug para mostrar triángulos individuales de curvas (true temporalmente para verificar)
|
||||||
public static bool DebugShowIndividualTriangles { get; set; } = false;
|
public static bool DebugShowIndividualTriangles { get; set; } = false;
|
||||||
|
|
||||||
|
@ -847,6 +851,9 @@ namespace CtrEditor.Simulacion
|
||||||
}
|
}
|
||||||
simBaseToModelMap.Clear();
|
simBaseToModelMap.Clear();
|
||||||
lastKnownDimensions.Clear();
|
lastKnownDimensions.Clear();
|
||||||
|
|
||||||
|
// ✅ NUEVO: También limpiar la imagen de fondo
|
||||||
|
RemoveBackgroundImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1578,6 +1585,117 @@ namespace CtrEditor.Simulacion
|
||||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR recreando geometría: {ex.Message}");
|
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR recreando geometría: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Agrega la imagen de fondo como un plano 2D estático en el viewport 3D
|
||||||
|
/// con la misma escala que el canvas WPF usando las convenciones WPF-BEPU correctas
|
||||||
|
///
|
||||||
|
/// Esta funcionalidad permite tener coherencia visual entre el canvas 2D WPF y la visualización 3D:
|
||||||
|
/// - La imagen se posiciona en el plano XY con Z=0
|
||||||
|
/// - Usa el sistema de conversión PixelToMeter para mantener la escala correcta
|
||||||
|
/// - Aplica las conversiones WPF-BEPU: WPF (x,y) -> BEPU (x,-y)
|
||||||
|
/// - Se aplica como textura difusa en un rectángulo plano
|
||||||
|
/// - Respeta las convenciones: WPF Y hacia abajo, BEPU Y hacia arriba
|
||||||
|
///
|
||||||
|
/// Ejemplo de uso:
|
||||||
|
/// SetBackgroundImage(@"C:\imagen.png", 1920, 1080);
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imagePath">Ruta absoluta de la imagen de fondo</param>
|
||||||
|
/// <param name="canvasWidth">Ancho del canvas WPF en píxeles</param>
|
||||||
|
/// <param name="canvasHeight">Alto del canvas WPF en píxeles</param>
|
||||||
|
public void SetBackgroundImage(string imagePath, double canvasWidth, double canvasHeight)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Remover imagen de fondo anterior si existe
|
||||||
|
RemoveBackgroundImage();
|
||||||
|
|
||||||
|
// Convertir dimensiones de píxeles a metros usando el sistema de conversión
|
||||||
|
float widthInMeters = PixelToMeter.Instance.calc.PixelsToMeters((float)canvasWidth);
|
||||||
|
float heightInMeters = PixelToMeter.Instance.calc.PixelsToMeters((float)canvasHeight);
|
||||||
|
|
||||||
|
// Crear la geometría del plano
|
||||||
|
var meshBuilder = new MeshBuilder();
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Usar convenciones WPF-BEPU correctas
|
||||||
|
// En WPF: (0,0) = esquina superior izquierda, Y hacia abajo
|
||||||
|
// En BEPU: Y hacia arriba, entonces convertimos WPF (x,y) -> BEPU (x,-y)
|
||||||
|
|
||||||
|
// Definir las esquinas en coordenadas WPF
|
||||||
|
var wpfTopLeft = new System.Numerics.Vector2(0, 0);
|
||||||
|
var wpfTopRight = new System.Numerics.Vector2(widthInMeters, 0);
|
||||||
|
var wpfBottomRight = new System.Numerics.Vector2(widthInMeters, heightInMeters);
|
||||||
|
var wpfBottomLeft = new System.Numerics.Vector2(0, heightInMeters);
|
||||||
|
|
||||||
|
// Convertir a coordenadas BEPU usando CoordinateConverter
|
||||||
|
var bepuTopLeft = CoordinateConverter.WpfToBepuVector2(wpfTopLeft);
|
||||||
|
var bepuTopRight = CoordinateConverter.WpfToBepuVector2(wpfTopRight);
|
||||||
|
var bepuBottomRight = CoordinateConverter.WpfToBepuVector2(wpfBottomRight);
|
||||||
|
var bepuBottomLeft = CoordinateConverter.WpfToBepuVector2(wpfBottomLeft);
|
||||||
|
|
||||||
|
// Crear los puntos 3D con Z=0
|
||||||
|
var p1 = new Point3D(bepuTopLeft.X, bepuTopLeft.Y, 0); // Esquina superior izquierda WPF
|
||||||
|
var p2 = new Point3D(bepuTopRight.X, bepuTopRight.Y, 0); // Esquina superior derecha WPF
|
||||||
|
var p3 = new Point3D(bepuBottomRight.X, bepuBottomRight.Y, 0); // Esquina inferior derecha WPF
|
||||||
|
var p4 = new Point3D(bepuBottomLeft.X, bepuBottomLeft.Y, 0); // Esquina inferior izquierda WPF
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Crear quad con orientación correcta
|
||||||
|
// Orden antihorario para normal hacia arriba (visible desde arriba)
|
||||||
|
meshBuilder.AddQuad(p1, p2, p3, p4);
|
||||||
|
|
||||||
|
// Crear el material con la imagen como textura
|
||||||
|
var imageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
|
||||||
|
var imageBrush = new ImageBrush(imageSource)
|
||||||
|
{
|
||||||
|
Stretch = Stretch.Fill,
|
||||||
|
TileMode = TileMode.None
|
||||||
|
};
|
||||||
|
|
||||||
|
var frontMaterial = new DiffuseMaterial(imageBrush);
|
||||||
|
var backMaterial = new DiffuseMaterial(imageBrush); // ✅ NUEVO: Material para la cara trasera
|
||||||
|
|
||||||
|
// Crear el modelo 3D con material en ambas caras
|
||||||
|
var geometry = meshBuilder.ToMesh();
|
||||||
|
var model = new GeometryModel3D(geometry, frontMaterial)
|
||||||
|
{
|
||||||
|
BackMaterial = backMaterial // ✅ NUEVO: Hace visible desde ambos lados
|
||||||
|
};
|
||||||
|
|
||||||
|
// Crear el visual 3D
|
||||||
|
_backgroundImageVisual = new ModelVisual3D { Content = model };
|
||||||
|
|
||||||
|
// Agregar al viewport
|
||||||
|
viewport3D.Children.Add(_backgroundImageVisual);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] ✅ Imagen de fondo agregada con convenciones WPF-BEPU: {widthInMeters:F2}m x {heightInMeters:F2}m");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] WPF (0,0) -> BEPU ({bepuTopLeft.X:F2},{bepuTopLeft.Y:F2})");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] WPF ({widthInMeters:F2},{heightInMeters:F2}) -> BEPU ({bepuBottomRight.X:F2},{bepuBottomRight.Y:F2})");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] ❌ ERROR agregando imagen de fondo: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Remueve la imagen de fondo del viewport 3D
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveBackgroundImage()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_backgroundImageVisual != null && viewport3D.Children.Contains(_backgroundImageVisual))
|
||||||
|
{
|
||||||
|
viewport3D.Children.Remove(_backgroundImageVisual);
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] ✅ Imagen de fondo removida");
|
||||||
|
}
|
||||||
|
_backgroundImageVisual = null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[3D Background] ❌ ERROR removiendo imagen de fondo: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CameraView
|
public enum CameraView
|
||||||
|
|
|
@ -33,9 +33,13 @@ namespace CtrEditor.Simulacion
|
||||||
|
|
||||||
// ✅ NUEVO: Propiedades para control de flotación/presión
|
// ✅ NUEVO: Propiedades para control de flotación/presión
|
||||||
public float? LastTransportCollisionZ { get; set; }
|
public float? LastTransportCollisionZ { get; set; }
|
||||||
public float PressureBuildup { get; set; } = 0;
|
|
||||||
public bool IsTouchingTransport { get; set; } = false;
|
public bool IsTouchingTransport { get; set; } = false;
|
||||||
|
|
||||||
|
// ✅ NUEVO: Sistema de fricción dinámica con cooldown/heatup
|
||||||
|
public float HighSlippery { get; set; } = 0f; // Rango 0-9, donde 9 es máxima presión/deslizamiento
|
||||||
|
public float ContactPressure { get; set; } = 0f; // Rango 0-9, donde 9 es mínima presión/deslizamiento
|
||||||
|
public float forceScale { get; set; } = 1f;
|
||||||
|
|
||||||
// ✅ NUEVO: Propiedades para la física personalizada
|
// ✅ NUEVO: Propiedades para la física personalizada
|
||||||
public float BaseInverseMass { get; private set; }
|
public float BaseInverseMass { get; private set; }
|
||||||
public Vector3 InitialPosition3D { get; private set; }
|
public Vector3 InitialPosition3D { get; private set; }
|
||||||
|
|
|
@ -70,6 +70,23 @@ namespace CtrEditor.Simulacion
|
||||||
ActualizarVelocidadCinematica();
|
ActualizarVelocidadCinematica();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Vector3 CalcularVelocidadCinematica()
|
||||||
|
{
|
||||||
|
float effectiveRadius = (_innerRadius + _outerRadius) / 2.0f;
|
||||||
|
if (effectiveRadius > 0.001f)
|
||||||
|
{
|
||||||
|
// La velocidad tangencial (Speed) se convierte a velocidad angular (ω = v / r)
|
||||||
|
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
|
||||||
|
float angularSpeed = (Speed / SpeedConversionFactor) / effectiveRadius;
|
||||||
|
|
||||||
|
// La rotación es alrededor del eje Z en el sistema de coordenadas de BEPU
|
||||||
|
return new Vector3(0, 0, angularSpeed);
|
||||||
|
}
|
||||||
|
return Vector3.Zero; // Si no hay cuerpo o radio efectivo es muy pequeño, no hay velocidad angular
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ✅ NUEVO: Aplica la velocidad angular al cuerpo cinemático de BEPU.
|
/// ✅ NUEVO: Aplica la velocidad angular al cuerpo cinemático de BEPU.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -79,20 +96,7 @@ namespace CtrEditor.Simulacion
|
||||||
{
|
{
|
||||||
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
|
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
|
||||||
float effectiveRadius = (_innerRadius + _outerRadius) / 2.0f;
|
body.Velocity.Angular = CalcularVelocidadCinematica();
|
||||||
if (effectiveRadius > 0.001f)
|
|
||||||
{
|
|
||||||
// La velocidad tangencial (Speed) se convierte a velocidad angular (ω = v / r)
|
|
||||||
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
|
|
||||||
float angularSpeed = (Speed / SpeedConversionFactor) / effectiveRadius;
|
|
||||||
|
|
||||||
// La rotación es alrededor del eje Z en el sistema de coordenadas de BEPU
|
|
||||||
body.Velocity.Angular = new Vector3(0, 0, angularSpeed);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
body.Velocity.Angular = Vector3.Zero;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,14 @@ namespace CtrEditor.Simulacion
|
||||||
ActualizarVelocidadCinematica();
|
ActualizarVelocidadCinematica();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Aplica la velocidad al cuerpo cinemático de BEPU.
|
||||||
|
/// </summary>
|
||||||
|
public Vector3 CalcularVelocidadCinematica()
|
||||||
|
{
|
||||||
|
return this.DirectionVector * (this.Speed / SpeedConversionFactor);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ✅ NUEVO: Aplica la velocidad al cuerpo cinemático de BEPU.
|
/// ✅ NUEVO: Aplica la velocidad al cuerpo cinemático de BEPU.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -158,7 +166,7 @@ namespace CtrEditor.Simulacion
|
||||||
{
|
{
|
||||||
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
|
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
|
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
|
||||||
body.Velocity.Linear = this.DirectionVector * (this.Speed / SpeedConversionFactor);
|
body.Velocity.Linear = CalcularVelocidadCinematica();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue