Compare commits
5 Commits
d1ec333243
...
380bc14b69
Author | SHA1 | Date |
---|---|---|
|
380bc14b69 | |
|
0d8780b16f | |
|
b8d3c953e6 | |
|
f42a4bb5d1 | |
|
53af46ec06 |
|
@ -25,6 +25,8 @@ namespace CtrEditor.Controls
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsKeyboardFocusWithin => PropertyGridControl.IsKeyboardFocusWithin;
|
public bool IsKeyboardFocusWithin => PropertyGridControl.IsKeyboardFocusWithin;
|
||||||
|
|
||||||
|
public PropertyGrid PropertyGrid => PropertyGridControl;
|
||||||
|
|
||||||
private void ImagePathButton_Click(object sender, RoutedEventArgs e)
|
private void ImagePathButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -80,9 +80,9 @@
|
||||||
<PackageReference Include="Aether.Physics2D" Version="2.2.0" />
|
<PackageReference Include="Aether.Physics2D" Version="2.2.0" />
|
||||||
<PackageReference Include="ClosedXML" Version="0.105.0-rc" />
|
<PackageReference Include="ClosedXML" Version="0.105.0-rc" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="Emgu.CV" Version="4.10.0.5680" />
|
<PackageReference Include="Emgu.CV" Version="4.9.0.5494" />
|
||||||
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.10.0.5680" />
|
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.9.0.5494" />
|
||||||
<PackageReference Include="Emgu.CV.UI" Version="4.10.0.5680" />
|
<PackageReference Include="Emgu.CV.UI" Version="4.9.0.5494" />
|
||||||
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.7.25104.5739" />
|
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.7.25104.5739" />
|
||||||
<PackageReference Include="LanguageDetection" Version="1.2.0" />
|
<PackageReference Include="LanguageDetection" Version="1.2.0" />
|
||||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc4.5" />
|
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc4.5" />
|
||||||
|
@ -174,6 +174,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Folder Include="ObjetosSim\Fluids\" />
|
||||||
<Folder Include="paddleocr\cls\inference\" />
|
<Folder Include="paddleocr\cls\inference\" />
|
||||||
<Folder Include="paddleocr\det\inference\" />
|
<Folder Include="paddleocr\det\inference\" />
|
||||||
<Folder Include="paddleocr\keys\" />
|
<Folder Include="paddleocr\keys\" />
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Guía de Uso: Sistema de Fluidos
|
||||||
|
|
||||||
|
## Introducción
|
||||||
|
|
||||||
|
El Sistema de Fluidos es una extensión para CtrEditor que permite simular el comportamiento de líquidos en elementos como tuberías, válvulas y tanques. Utiliza una implementación optimizada basada en el método SPH (Smoothed Particle Hydrodynamics) para la simulación de fluidos.
|
||||||
|
|
||||||
|
## Componentes del Sistema
|
||||||
|
|
||||||
|
El sistema de fluidos incluye los siguientes componentes:
|
||||||
|
|
||||||
|
1. **SistemaFluidos**: El componente principal que gestiona la simulación de partículas.
|
||||||
|
2. **TuberíaFluido**: Componente para crear tuberías por las que circula el fluido.
|
||||||
|
3. **ValvulaFluido**: Componente que permite regular el flujo del fluido.
|
||||||
|
|
||||||
|
## Añadir el Sistema de Fluidos
|
||||||
|
|
||||||
|
Para comenzar a utilizar el sistema de fluidos:
|
||||||
|
|
||||||
|
1. Inserte un componente "SistemaFluidos" en su diseño.
|
||||||
|
2. Configure el tamaño del área de simulación y la gravedad según sus necesidades.
|
||||||
|
3. Añada los componentes de tuberías y válvulas que necesite.
|
||||||
|
|
||||||
|
## Propiedades Principales
|
||||||
|
|
||||||
|
### SistemaFluidos
|
||||||
|
|
||||||
|
- **AnchoSimulacion/AltoSimulacion**: Dimensiones del área de simulación.
|
||||||
|
- **GravedadX/GravedadY**: Vector de gravedad para la simulación.
|
||||||
|
- **TamañoParticula**: Tamaño visual de las partículas de fluido.
|
||||||
|
- **ColorFluido**: Color base para las partículas de fluido.
|
||||||
|
- **OpacidadParticulas**: Transparencia de las partículas (0-1).
|
||||||
|
|
||||||
|
### TuberíaFluido
|
||||||
|
|
||||||
|
- **Diametro**: Diámetro de la tubería.
|
||||||
|
- **Color**: Color de la tubería.
|
||||||
|
- **ColorFluido**: Color del fluido dentro de la tubería.
|
||||||
|
|
||||||
|
### ValvulaFluido
|
||||||
|
|
||||||
|
- **Apertura**: Grado de apertura de la válvula (0=cerrada, 1=abierta).
|
||||||
|
- **DiametroTuberia**: Diámetro de las tuberías conectadas.
|
||||||
|
- **TagApertura**: Tag PLC para controlar la apertura.
|
||||||
|
|
||||||
|
## Integración con PLC
|
||||||
|
|
||||||
|
El sistema de fluidos permite integración con PLC mediante tags:
|
||||||
|
|
||||||
|
- **TagNivelTanque1**: Lectura/escritura del nivel de llenado del tanque.
|
||||||
|
- **TagAperturaValvula1**: Lectura/escritura de la apertura de la válvula.
|
||||||
|
|
||||||
|
## Añadir Fluido a la Simulación
|
||||||
|
|
||||||
|
Para añadir partículas de fluido:
|
||||||
|
|
||||||
|
1. Seleccione el componente SistemaFluidos.
|
||||||
|
2. Utilice el método `AgregarParticulasEnArea` para añadir partículas en una región.
|
||||||
|
|
||||||
|
Ejemplo:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Añadir 100 partículas en un área centrada en (x,y) con ancho y alto especificados
|
||||||
|
_sistemaFluidos.AgregarParticulasEnArea(new Vector2(x, y), ancho, alto, 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Optimización
|
||||||
|
|
||||||
|
El Sistema de Fluidos utiliza `DrawingVisual` para una renderización eficiente, lo que permite simular miles de partículas con un impacto mínimo en el rendimiento.
|
||||||
|
|
||||||
|
## Limitaciones
|
||||||
|
|
||||||
|
- La simulación está optimizada para cantidades moderadas de partículas (hasta 10,000).
|
||||||
|
- Los contenedores (tuberías, válvulas) son aproximaciones y pueden presentar fugas o comportamientos inesperados con flujos muy rápidos.
|
||||||
|
|
||||||
|
## Recomendaciones
|
||||||
|
|
||||||
|
- Comience con pocas partículas y aumente gradualmente según sea necesario.
|
||||||
|
- Mantenga la configuración de gravedad en valores realistas.
|
||||||
|
- Para simular líquidos específicos, ajuste la visualización con el color y opacidad adecuados.
|
||||||
|
|
||||||
|
## Ejemplos de Uso
|
||||||
|
|
||||||
|
### Sistema Básico de Flujo
|
||||||
|
|
||||||
|
```
|
||||||
|
SistemaFluidos → TuberíaFluido → ValvulaFluido → TuberíaFluido → Tanque
|
||||||
|
```
|
||||||
|
|
||||||
|
### Circuito Cerrado
|
||||||
|
|
||||||
|
```
|
||||||
|
SistemaFluidos → TuberíaFluido → ValvulaFluido → TuberíaFluido → TuberíaFluido (retorno)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Solución de Problemas
|
||||||
|
|
||||||
|
- **Partículas escapando de contenedores**: Verifique que los componentes estén correctamente posicionados y conectados.
|
||||||
|
- **Bajo rendimiento**: Reduzca el número de partículas o el tamaño visual.
|
||||||
|
- **Cambios de apertura de válvula no afectan el flujo**: Verifique la conexión PLC y la configuración de tags.
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>Fluid</title>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="fluid" fill="#000000" fill-rule="nonzero">
|
||||||
|
<path d="M12,2 C8.1,2 5,5.1 5,9 C5,13.9 12,22 12,22 C12,22 19,13.9 19,9 C19,5.1 15.9,2 12,2 Z M12,11.5 C10.6,11.5 9.5,10.4 9.5,9 C9.5,7.6 10.6,6.5 12,6.5 C13.4,6.5 14.5,7.6 14.5,9 C14.5,10.4 13.4,11.5 12,11.5 Z" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 622 B |
|
@ -1,4 +1,5 @@
|
||||||
using System.ComponentModel;
|
using CtrEditor;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Ookii.Dialogs.Wpf;
|
using Ookii.Dialogs.Wpf;
|
||||||
|
@ -83,8 +84,12 @@ namespace CtrEditor
|
||||||
public ICommand StopSimulationCommand { get; }
|
public ICommand StopSimulationCommand { get; }
|
||||||
public ICommand ItemDoubleClickCommand { get; private set; }
|
public ICommand ItemDoubleClickCommand { get; private set; }
|
||||||
|
|
||||||
|
public SimulationFluidsViewModel FluidSimulation { get; private set; }
|
||||||
|
|
||||||
public ICommand TBStartSimulationCommand { get; }
|
public ICommand TBStartSimulationCommand { get; }
|
||||||
public ICommand TBStopSimulationCommand { get; }
|
public ICommand TBStopSimulationCommand { get; }
|
||||||
|
public ICommand TBStartFluidSimulationCommand { get; }
|
||||||
|
public ICommand TBStopFluidSimulationCommand { get; }
|
||||||
public ICommand TBSaveCommand { get; }
|
public ICommand TBSaveCommand { get; }
|
||||||
public ICommand TBExtractTagsCommand { get; }
|
public ICommand TBExtractTagsCommand { get; }
|
||||||
|
|
||||||
|
@ -358,6 +363,11 @@ namespace CtrEditor
|
||||||
|
|
||||||
TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning);
|
TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning);
|
||||||
TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning);
|
TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning);
|
||||||
|
|
||||||
|
// Inicializar simulación de fluidos
|
||||||
|
FluidSimulation = new SimulationFluidsViewModel(this);
|
||||||
|
TBStartFluidSimulationCommand = new RelayCommand(StartFluidSimulation, () => !FluidSimulation.IsFluidSimulationRunning);
|
||||||
|
TBStopFluidSimulationCommand = new RelayCommand(StopFluidSimulation, () => FluidSimulation.IsFluidSimulationRunning);
|
||||||
TBSaveCommand = new RelayCommand(Save);
|
TBSaveCommand = new RelayCommand(Save);
|
||||||
|
|
||||||
TBEliminarUserControlCommand = new RelayCommand(EliminarUserControl, () => habilitarEliminarUserControl);
|
TBEliminarUserControlCommand = new RelayCommand(EliminarUserControl, () => habilitarEliminarUserControl);
|
||||||
|
@ -749,7 +759,10 @@ namespace CtrEditor
|
||||||
|
|
||||||
private void StartSimulation()
|
private void StartSimulation()
|
||||||
{
|
{
|
||||||
IsSimulationRunning = true;
|
// Detener simulación de fluidos si está ejecutándose
|
||||||
|
StopFluidSimulation();
|
||||||
|
|
||||||
|
IsSimulationRunning = true;
|
||||||
|
|
||||||
// Ocultar rectángulos de selección antes de iniciar la simulación
|
// Ocultar rectángulos de selección antes de iniciar la simulación
|
||||||
_objectManager.UpdateSelectionVisuals();
|
_objectManager.UpdateSelectionVisuals();
|
||||||
|
@ -776,12 +789,33 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
simulationManager.Debug_ClearSimulationShapes();
|
simulationManager.Debug_ClearSimulationShapes();
|
||||||
Debug_SimulacionCreado = false;
|
Debug_SimulacionCreado = false;
|
||||||
}
|
}
|
||||||
_timerSimulacion.Stop();
|
_timerSimulacion.Stop();
|
||||||
|
|
||||||
// Restaurar los rectángulos de selección si hay objetos seleccionados
|
// Restaurar los rectángulos de selección si hay objetos seleccionados
|
||||||
_objectManager.UpdateSelectionVisuals();
|
_objectManager.UpdateSelectionVisuals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inicia la simulación de fluidos independiente
|
||||||
|
/// </summary>
|
||||||
|
public void StartFluidSimulation()
|
||||||
|
{
|
||||||
|
// Detener simulación física si está ejecutándose
|
||||||
|
StopSimulation();
|
||||||
|
|
||||||
|
FluidSimulation.StartFluidSimulation();
|
||||||
|
CommandManager.InvalidateRequerySuggested();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detiene la simulación de fluidos independiente
|
||||||
|
/// </summary>
|
||||||
|
public void StopFluidSimulation()
|
||||||
|
{
|
||||||
|
FluidSimulation.StopFluidSimulation();
|
||||||
|
CommandManager.InvalidateRequerySuggested();
|
||||||
|
}
|
||||||
|
|
||||||
private void OnTickSimulacion(object sender, EventArgs e)
|
private void OnTickSimulacion(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -185,6 +185,9 @@ namespace CtrEditor
|
||||||
double rectHighlightSize = 1;
|
double rectHighlightSize = 1;
|
||||||
RemoveResizeRectangles();
|
RemoveResizeRectangles();
|
||||||
|
|
||||||
|
// Verificar si hay objetos bloqueados
|
||||||
|
bool hasLockedObjects = selectedObjects.Any(obj => obj.Lock_movement);
|
||||||
|
|
||||||
// Calcular el bounding box que contenga todos los objetos seleccionados
|
// Calcular el bounding box que contenga todos los objetos seleccionados
|
||||||
Rect boundingBox = CalculateTotalBoundingBox(selectedObjects);
|
Rect boundingBox = CalculateTotalBoundingBox(selectedObjects);
|
||||||
if (_selectedObjectsAreVisible) {
|
if (_selectedObjectsAreVisible) {
|
||||||
|
@ -200,19 +203,23 @@ namespace CtrEditor
|
||||||
boundingBox.Top + boundingBox.Height / 2
|
boundingBox.Top + boundingBox.Height / 2
|
||||||
);
|
);
|
||||||
|
|
||||||
// Selection rectangle
|
// Siempre mostrar el rectángulo de selección
|
||||||
Rectangle selectionRect = CreateSelectionRectangle(rectBox, rectHighlightSize);
|
Rectangle selectionRect = CreateSelectionRectangle(rectBox, rectHighlightSize, hasLockedObjects);
|
||||||
_resizeRectangles.Add(selectionRect);
|
_resizeRectangles.Add(selectionRect);
|
||||||
_canvas.Children.Add(selectionRect);
|
_canvas.Children.Add(selectionRect);
|
||||||
|
|
||||||
// Load rotation cursors
|
// Solo agregar handles de manipulación si no hay objetos bloqueados
|
||||||
Cursor rotationCursorRx = new Cursor(Application.GetResourceStream(
|
if (!hasLockedObjects)
|
||||||
new Uri("pack://application:,,,/CtrEditor;component/Icons/rotationRx.cur")).Stream);
|
{
|
||||||
Cursor rotationCursorSx = new Cursor(Application.GetResourceStream(
|
// Load rotation cursors
|
||||||
new Uri("pack://application:,,,/CtrEditor;component/Icons/rotationSx.cur")).Stream);
|
Cursor rotationCursorRx = new Cursor(Application.GetResourceStream(
|
||||||
|
new Uri("pack://application:,,,/CtrEditor;component/Icons/rotationRx.cur")).Stream);
|
||||||
|
Cursor rotationCursorSx = new Cursor(Application.GetResourceStream(
|
||||||
|
new Uri("pack://application:,,,/CtrEditor;component/Icons/rotationSx.cur")).Stream);
|
||||||
|
|
||||||
// Add resize/rotation handles
|
// Add resize/rotation handles
|
||||||
AddResizeHandles(rectBox, 10, rotationCursorRx, rotationCursorSx);
|
AddResizeHandles(rectBox, 10, rotationCursorRx, rotationCursorSx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +255,7 @@ namespace CtrEditor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Rectangle CreateSelectionRectangle(FuncionesBase.MutableRect rectBox, double rectHighlightSize)
|
private Rectangle CreateSelectionRectangle(FuncionesBase.MutableRect rectBox, double rectHighlightSize, bool hasLockedObjects = false)
|
||||||
{
|
{
|
||||||
if (double.IsInfinity(rectBox.Width) || double.IsInfinity(rectBox.Height) ||
|
if (double.IsInfinity(rectBox.Width) || double.IsInfinity(rectBox.Height) ||
|
||||||
double.IsNaN(rectBox.Width) || double.IsNaN(rectBox.Height))
|
double.IsNaN(rectBox.Width) || double.IsNaN(rectBox.Height))
|
||||||
|
@ -256,16 +263,33 @@ namespace CtrEditor
|
||||||
throw new ArgumentException("Invalid dimensions for selection rectangle.");
|
throw new ArgumentException("Invalid dimensions for selection rectangle.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cambiar el estilo visual dependiendo si hay objetos bloqueados
|
||||||
|
Brush strokeBrush;
|
||||||
|
DoubleCollection dashArray;
|
||||||
|
|
||||||
|
if (hasLockedObjects)
|
||||||
|
{
|
||||||
|
// Estilo para objetos bloqueados (rojo/naranja con líneas más separadas)
|
||||||
|
strokeBrush = new SolidColorBrush(Color.FromArgb(180, 255, 140, 0)); // Naranja
|
||||||
|
dashArray = new DoubleCollection(new double[] { 5, 3 }); // Líneas más largas
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Estilo normal para objetos no bloqueados (azul)
|
||||||
|
strokeBrush = new SolidColorBrush(Color.FromArgb(180, 0, 120, 215)); // Azul
|
||||||
|
dashArray = new DoubleCollection(new double[] { 3, 3 }); // Líneas normales
|
||||||
|
}
|
||||||
|
|
||||||
var rect = new Rectangle
|
var rect = new Rectangle
|
||||||
{
|
{
|
||||||
Width = rectBox.Width + (rectHighlightSize * 2),
|
Width = rectBox.Width + (rectHighlightSize * 2),
|
||||||
Height = rectBox.Height + (rectHighlightSize * 2),
|
Height = rectBox.Height + (rectHighlightSize * 2),
|
||||||
Fill = Brushes.Transparent,
|
Fill = Brushes.Transparent,
|
||||||
Stroke = new SolidColorBrush(Color.FromArgb(180, 0, 120, 215)),
|
Stroke = strokeBrush,
|
||||||
StrokeThickness = 1.5,
|
StrokeThickness = 1.5,
|
||||||
Tag = "Selection",
|
Tag = "Selection",
|
||||||
IsHitTestVisible = false,
|
IsHitTestVisible = false,
|
||||||
StrokeDashArray = new DoubleCollection(new double[] { 3, 3 })
|
StrokeDashArray = dashArray
|
||||||
};
|
};
|
||||||
|
|
||||||
Canvas.SetLeft(rect, rectBox.Left - rectHighlightSize);
|
Canvas.SetLeft(rect, rectBox.Left - rectHighlightSize);
|
||||||
|
@ -460,6 +484,17 @@ namespace CtrEditor
|
||||||
|
|
||||||
public void ClearSelection()
|
public void ClearSelection()
|
||||||
{
|
{
|
||||||
|
// Forzar la actualización de bindings pendientes antes de limpiar la selección
|
||||||
|
if (_mainWindow.DataContext is MainViewModel viewModel && viewModel.SelectedItemOsList != null)
|
||||||
|
{
|
||||||
|
// Obtener el PropertyGrid del PanelEdicion y forzar actualización de bindings
|
||||||
|
var panelEdicion = _mainWindow.PanelEdicion;
|
||||||
|
if (panelEdicion != null && panelEdicion.IsKeyboardFocusWithin)
|
||||||
|
{
|
||||||
|
UserControlFactory.ForzarActualizacionBindings(panelEdicion.PropertyGrid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var obj in _selectedObjects.ToList())
|
foreach (var obj in _selectedObjects.ToList())
|
||||||
{
|
{
|
||||||
DeselectObject(obj);
|
DeselectObject(obj);
|
||||||
|
@ -467,9 +502,9 @@ namespace CtrEditor
|
||||||
RemoveAllSelectionHighlights();
|
RemoveAllSelectionHighlights();
|
||||||
RemoveResizeRectangles();
|
RemoveResizeRectangles();
|
||||||
|
|
||||||
if (_mainWindow.DataContext is MainViewModel viewModel)
|
if (_mainWindow.DataContext is MainViewModel viewModel2)
|
||||||
{
|
{
|
||||||
viewModel.SelectedItemOsList = null;
|
viewModel2.SelectedItemOsList = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,8 +603,18 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
if (!_isDrawingCanvas)
|
if (!_isDrawingCanvas)
|
||||||
{
|
{
|
||||||
|
// Verificar si hay objetos bloqueados en la selección actual
|
||||||
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
||||||
|
|
||||||
if (_resizeRectangles != null && _resizeRectangles.Contains(sender))
|
if (_resizeRectangles != null && _resizeRectangles.Contains(sender))
|
||||||
{
|
{
|
||||||
|
// Si hay objetos bloqueados, no permitir redimensionamiento
|
||||||
|
if (hasLockedObjects)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_currentDraggingRectangle = sender as Rectangle;
|
_currentDraggingRectangle = sender as Rectangle;
|
||||||
_lastMousePosition = e.GetPosition(_canvas);
|
_lastMousePosition = e.GetPosition(_canvas);
|
||||||
_currentDraggingRectangle.CaptureMouse();
|
_currentDraggingRectangle.CaptureMouse();
|
||||||
|
@ -585,16 +630,24 @@ namespace CtrEditor
|
||||||
if (userControl.DataContext is osBase datos)
|
if (userControl.DataContext is osBase datos)
|
||||||
{
|
{
|
||||||
HandleObjectSelection(userControl, datos);
|
HandleObjectSelection(userControl, datos);
|
||||||
|
|
||||||
|
// Actualizar el estado de objetos bloqueados después de la selección
|
||||||
|
hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solo iniciar el arrastre si no se presionó Ctrl
|
// Solo iniciar el arrastre si no se presionó Ctrl y no hay objetos bloqueados
|
||||||
if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
|
if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && !hasLockedObjects)
|
||||||
{
|
{
|
||||||
userControl.CaptureMouse();
|
userControl.CaptureMouse();
|
||||||
_currentDraggingControl = userControl;
|
_currentDraggingControl = userControl;
|
||||||
_isMovingUserControl = true;
|
_isMovingUserControl = true;
|
||||||
InitializeDrag(e);
|
InitializeDrag(e);
|
||||||
}
|
}
|
||||||
|
else if (hasLockedObjects)
|
||||||
|
{
|
||||||
|
// Si hay objetos bloqueados, no permitir el inicio del arrastre
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -641,6 +694,14 @@ namespace CtrEditor
|
||||||
private void HandleDrag(Point currentPosition)
|
private void HandleDrag(Point currentPosition)
|
||||||
{
|
{
|
||||||
PurgeDeletedObjects();
|
PurgeDeletedObjects();
|
||||||
|
|
||||||
|
// Verificar si hay objetos bloqueados antes de mover
|
||||||
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
||||||
|
if (hasLockedObjects)
|
||||||
|
{
|
||||||
|
return; // No mover si hay objetos bloqueados
|
||||||
|
}
|
||||||
|
|
||||||
var dx = currentPosition.X - _startPointUserControl.X;
|
var dx = currentPosition.X - _startPointUserControl.X;
|
||||||
var dy = currentPosition.Y - _startPointUserControl.Y;
|
var dy = currentPosition.Y - _startPointUserControl.Y;
|
||||||
|
|
||||||
|
@ -792,6 +853,14 @@ namespace CtrEditor
|
||||||
private void HandleResize(Point currentPosition, HandleMode mode)
|
private void HandleResize(Point currentPosition, HandleMode mode)
|
||||||
{
|
{
|
||||||
PurgeDeletedObjects();
|
PurgeDeletedObjects();
|
||||||
|
|
||||||
|
// Verificar si hay objetos bloqueados antes de redimensionar
|
||||||
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
||||||
|
if (hasLockedObjects)
|
||||||
|
{
|
||||||
|
return; // No redimensionar si hay objetos bloqueados
|
||||||
|
}
|
||||||
|
|
||||||
RemoveAllSelectionHighlights(); // Remover antes de redimensionar
|
RemoveAllSelectionHighlights(); // Remover antes de redimensionar
|
||||||
|
|
||||||
foreach (var selectedObject in _selectedObjects)
|
foreach (var selectedObject in _selectedObjects)
|
||||||
|
@ -816,6 +885,14 @@ namespace CtrEditor
|
||||||
private void HandleRotation(Point currentPosition)
|
private void HandleRotation(Point currentPosition)
|
||||||
{
|
{
|
||||||
PurgeDeletedObjects();
|
PurgeDeletedObjects();
|
||||||
|
|
||||||
|
// Verificar si hay objetos bloqueados antes de rotar
|
||||||
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
||||||
|
if (hasLockedObjects)
|
||||||
|
{
|
||||||
|
return; // No rotar si hay objetos bloqueados
|
||||||
|
}
|
||||||
|
|
||||||
RemoveAllSelectionHighlights(); // Remover antes de rotar
|
RemoveAllSelectionHighlights(); // Remover antes de rotar
|
||||||
|
|
||||||
// Calcular el ángulo respecto al centro del bounding box que contiene todos los objetos seleccionados
|
// Calcular el ángulo respecto al centro del bounding box que contiene todos los objetos seleccionados
|
||||||
|
@ -845,6 +922,13 @@ namespace CtrEditor
|
||||||
PurgeDeletedObjects();
|
PurgeDeletedObjects();
|
||||||
if (_selectedObjects.Count <= 1) return;
|
if (_selectedObjects.Count <= 1) return;
|
||||||
|
|
||||||
|
// Verificar si hay objetos bloqueados antes de alinear
|
||||||
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
||||||
|
if (hasLockedObjects)
|
||||||
|
{
|
||||||
|
return; // No alinear si hay objetos bloqueados
|
||||||
|
}
|
||||||
|
|
||||||
var alignment = new ObjectAlignment(_selectedObjects, _selectedObjects.FirstOrDefault());
|
var alignment = new ObjectAlignment(_selectedObjects, _selectedObjects.FirstOrDefault());
|
||||||
|
|
||||||
switch (alignmentType)
|
switch (alignmentType)
|
||||||
|
@ -901,6 +985,13 @@ namespace CtrEditor
|
||||||
var viewModel = _mainWindow.DataContext as MainViewModel;
|
var viewModel = _mainWindow.DataContext as MainViewModel;
|
||||||
if (viewModel == null) return;
|
if (viewModel == null) return;
|
||||||
|
|
||||||
|
// Verificar si hay objetos bloqueados antes de eliminar
|
||||||
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
||||||
|
if (hasLockedObjects)
|
||||||
|
{
|
||||||
|
return; // No eliminar si hay objetos bloqueados
|
||||||
|
}
|
||||||
|
|
||||||
// Crear una copia de la lista para evitar modificaciones durante la iteración
|
// Crear una copia de la lista para evitar modificaciones durante la iteración
|
||||||
var objectsToRemove = _selectedObjects.ToList();
|
var objectsToRemove = _selectedObjects.ToList();
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ using System.Globalization;
|
||||||
using Xceed.Wpf.Toolkit;
|
using Xceed.Wpf.Toolkit;
|
||||||
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
|
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
|
||||||
using CtrEditor.ObjetosSim.Extraccion_Datos;
|
using CtrEditor.ObjetosSim.Extraccion_Datos;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
|
@ -88,14 +89,90 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
public static void LimpiarPropiedadesosDatos(PropertyGrid propertyGrid)
|
public static void LimpiarPropiedadesosDatos(PropertyGrid propertyGrid)
|
||||||
{
|
{
|
||||||
|
// Forzar la actualización de bindings pendientes antes de limpiar
|
||||||
|
ForzarActualizacionBindings(propertyGrid);
|
||||||
|
|
||||||
// Clear previous properties
|
// Clear previous properties
|
||||||
propertyGrid.SelectedObject = null;
|
propertyGrid.SelectedObject = null;
|
||||||
propertyGrid.PropertyDefinitions.Clear();
|
propertyGrid.PropertyDefinitions.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ForzarActualizacionBindings(PropertyGrid propertyGrid)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Método 1: Forzar el foco fuera del PropertyGrid para activar LostFocus
|
||||||
|
if (propertyGrid.IsKeyboardFocusWithin)
|
||||||
|
{
|
||||||
|
// Crear un elemento temporal para robar el foco
|
||||||
|
var tempButton = new System.Windows.Controls.Button();
|
||||||
|
var parent = propertyGrid.Parent as Panel;
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
parent.Children.Add(tempButton);
|
||||||
|
tempButton.Focus();
|
||||||
|
parent.Children.Remove(tempButton);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Alternativa: mover el foco al PropertyGrid mismo
|
||||||
|
propertyGrid.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método 2: Forzar la actualización de todos los bindings del PropertyGrid
|
||||||
|
var bindingExpression = BindingOperations.GetBindingExpression(propertyGrid, PropertyGrid.SelectedObjectProperty);
|
||||||
|
bindingExpression?.UpdateSource();
|
||||||
|
|
||||||
|
// Método 3: Buscar controles de edición activos y forzar su actualización
|
||||||
|
ForzarActualizacionControlesEditores(propertyGrid);
|
||||||
|
|
||||||
|
// Pequeña pausa para permitir que se procesen los eventos
|
||||||
|
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(
|
||||||
|
System.Windows.Threading.DispatcherPriority.Background,
|
||||||
|
new Action(() => { }));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log del error si es necesario, pero no interrumpir el flujo
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Error al forzar actualización de bindings: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ForzarActualizacionControlesEditores(DependencyObject parent)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||||
|
{
|
||||||
|
var child = VisualTreeHelper.GetChild(parent, i);
|
||||||
|
|
||||||
|
// Buscar TextBox, ComboBox, y otros controles de edición comunes
|
||||||
|
if (child is TextBox textBox)
|
||||||
|
{
|
||||||
|
var expression = textBox.GetBindingExpression(TextBox.TextProperty);
|
||||||
|
expression?.UpdateSource();
|
||||||
|
}
|
||||||
|
else if (child is ComboBox comboBox)
|
||||||
|
{
|
||||||
|
var expression = comboBox.GetBindingExpression(ComboBox.SelectedValueProperty);
|
||||||
|
expression?.UpdateSource();
|
||||||
|
var textExpression = comboBox.GetBindingExpression(ComboBox.TextProperty);
|
||||||
|
textExpression?.UpdateSource();
|
||||||
|
}
|
||||||
|
else if (child is CheckBox checkBox)
|
||||||
|
{
|
||||||
|
var expression = checkBox.GetBindingExpression(CheckBox.IsCheckedProperty);
|
||||||
|
expression?.UpdateSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursión para buscar en controles hijos
|
||||||
|
ForzarActualizacionControlesEditores(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void CargarPropiedadesosDatos(osBase selectedObject, PropertyGrid propertyGrid)
|
public static void CargarPropiedadesosDatos(osBase selectedObject, PropertyGrid propertyGrid)
|
||||||
{
|
{
|
||||||
|
// Forzar la actualización de bindings pendientes antes de cambiar el objeto
|
||||||
|
ForzarActualizacionBindings(propertyGrid);
|
||||||
|
|
||||||
// Limpia las propiedades previas
|
// Limpia las propiedades previas
|
||||||
propertyGrid.SelectedObject = null;
|
propertyGrid.SelectedObject = null;
|
||||||
|
|
|
@ -78,6 +78,12 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
public UniqueId Id { get; set; }
|
public UniqueId Id { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Lock object to mouse movement.")]
|
||||||
|
[property: Category("Layout:")]
|
||||||
|
private bool lock_movement;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Description("X coordinate.")]
|
[property: Description("X coordinate.")]
|
||||||
[property: Category("Layout:")]
|
[property: Category("Layout:")]
|
||||||
|
|
|
@ -0,0 +1,312 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CtrEditor.FuncionesBase;
|
||||||
|
using CtrEditor.Simulacion.Fluids;
|
||||||
|
using CtrEditor.Simulacion.Fluids.Components;
|
||||||
|
using nkast.Aether.Physics2D.Common;
|
||||||
|
using Color = System.Windows.Media.Color;
|
||||||
|
using Siemens.Simatic.Simulation.Runtime;
|
||||||
|
using LibS7Adv;
|
||||||
|
|
||||||
|
namespace CtrEditor.ObjetosSim
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel para el sistema de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public partial class osSistemaFluidos : osBase, IosBase
|
||||||
|
{
|
||||||
|
// Referencia a la simulación de fluidos
|
||||||
|
public SimulacionFluidos _simFluidos;
|
||||||
|
|
||||||
|
// Tamaño del área de simulación
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Ancho del área de simulación en metros")]
|
||||||
|
[property: Category("Simulación:")]
|
||||||
|
private float anchoSimulacion = 10.0f;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Alto del área de simulación en metros")]
|
||||||
|
[property: Category("Simulación:")]
|
||||||
|
private float altoSimulacion = 10.0f;
|
||||||
|
|
||||||
|
// Propiedades del fluido
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Tamaño visual de las partículas")]
|
||||||
|
[property: Category("Visual:")]
|
||||||
|
private float tamañoParticula = 0.01f;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Color del fluido")]
|
||||||
|
[property: Category("Visual:")]
|
||||||
|
private Color colorFluido = Colors.CornflowerBlue;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Opacidad de las partículas")]
|
||||||
|
[property: Category("Visual:")]
|
||||||
|
private double opacidadParticulas = 0.7;
|
||||||
|
|
||||||
|
// Propiedades de gravedad
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Gravedad en X (m/s²)")]
|
||||||
|
[property: Category("Física:")]
|
||||||
|
private float gravedadX = 0.0f;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Gravedad en Y (m/s²)")]
|
||||||
|
[property: Category("Física:")]
|
||||||
|
private float gravedadY = 9.8f;
|
||||||
|
|
||||||
|
partial void OnGravedadXChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGravedad();
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnGravedadYChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGravedad();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estadísticas de la simulación
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Número de partículas")]
|
||||||
|
[property: Category("Estadísticas:")]
|
||||||
|
private int numeroParticulas;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Rendimiento en FPS")]
|
||||||
|
[property: Category("Estadísticas:")]
|
||||||
|
private double fps;
|
||||||
|
|
||||||
|
// Referencia a componentes (solo para la función Add)
|
||||||
|
private List<IContenedorFluido> _contenedores = new List<IContenedorFluido>();
|
||||||
|
|
||||||
|
// Nombre de la clase para identificación
|
||||||
|
public static string NombreClase()
|
||||||
|
{
|
||||||
|
return "SistemaFluidos";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string nombre = NombreClase();
|
||||||
|
public override string Nombre
|
||||||
|
{
|
||||||
|
get => nombre;
|
||||||
|
set => SetProperty(ref nombre, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Métodos para interactuar con la simulación
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agrega partículas en un punto específico
|
||||||
|
/// </summary>
|
||||||
|
public void AgregarParticula(Vector2 posicion)
|
||||||
|
{
|
||||||
|
_simFluidos?.AgregarParticula(posicion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agrega múltiples partículas en un área
|
||||||
|
/// </summary>
|
||||||
|
public void AgregarParticulasEnArea(Vector2 centro, float ancho, float alto, int cantidad)
|
||||||
|
{
|
||||||
|
_simFluidos?.AgregarParticulasEnArea(centro, ancho, alto, cantidad);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un nuevo tanque y lo agrega a la simulación
|
||||||
|
/// </summary>
|
||||||
|
public Tanque CrearTanque(Vector2 posicion, float ancho, float alto)
|
||||||
|
{
|
||||||
|
if (_simFluidos == null) return null;
|
||||||
|
|
||||||
|
Tanque tanque = new Tanque(posicion, ancho, alto, (int)(AnchoSimulacion * 100));
|
||||||
|
_simFluidos.AgregarContenedor(tanque);
|
||||||
|
_contenedores.Add(tanque);
|
||||||
|
return tanque;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea una nueva tubería
|
||||||
|
/// </summary>
|
||||||
|
public Tuberia CrearTuberia(float diametro)
|
||||||
|
{
|
||||||
|
if (_simFluidos == null) return null;
|
||||||
|
|
||||||
|
Tuberia tuberia = new Tuberia(diametro, (int)(AnchoSimulacion * 100));
|
||||||
|
_simFluidos.AgregarContenedor(tuberia);
|
||||||
|
_contenedores.Add(tuberia);
|
||||||
|
return tuberia;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea una nueva válvula
|
||||||
|
/// </summary>
|
||||||
|
public Valvula CrearValvula(Vector2 posicion, float diametro, float apertura = 1.0f)
|
||||||
|
{
|
||||||
|
if (_simFluidos == null) return null;
|
||||||
|
|
||||||
|
Valvula valvula = new Valvula(posicion, diametro, apertura, (int)(AnchoSimulacion * 100));
|
||||||
|
_simFluidos.AgregarContenedor(valvula);
|
||||||
|
_contenedores.Add(valvula);
|
||||||
|
return valvula;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Elimina un componente de la simulación
|
||||||
|
/// </summary>
|
||||||
|
public void EliminarComponente(IContenedorFluido componente)
|
||||||
|
{
|
||||||
|
if (_simFluidos == null || componente == null) return;
|
||||||
|
|
||||||
|
_simFluidos.RemoverContenedor(componente);
|
||||||
|
_contenedores.Remove(componente);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limpia todas las partículas de la simulación
|
||||||
|
/// </summary>
|
||||||
|
public void LimpiarParticulas()
|
||||||
|
{
|
||||||
|
_simFluidos?.LimpiarParticulas();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza el vector de gravedad según las propiedades
|
||||||
|
/// </summary>
|
||||||
|
private void ActualizarGravedad()
|
||||||
|
{
|
||||||
|
if (_simFluidos != null)
|
||||||
|
{
|
||||||
|
_simFluidos.AjustarGravedad(new Vector2(GravedadX, GravedadY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor de la clase
|
||||||
|
/// </summary>
|
||||||
|
public osSistemaFluidos()
|
||||||
|
{
|
||||||
|
// Inicializar propiedades básicas
|
||||||
|
Ancho = 1.0f;
|
||||||
|
Alto = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Métodos sobrescritos de osBase
|
||||||
|
|
||||||
|
public override void UpdateGeometryStart()
|
||||||
|
{
|
||||||
|
// Crear la simulación de fluidos si es necesario
|
||||||
|
if (_simFluidos == null)
|
||||||
|
{
|
||||||
|
_simFluidos = new SimulacionFluidos(
|
||||||
|
AnchoSimulacion,
|
||||||
|
AltoSimulacion,
|
||||||
|
10000, // Máximo de partículas
|
||||||
|
new Vector2(GravedadX, GravedadY)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateGeometryStep()
|
||||||
|
{
|
||||||
|
// No es necesario actualizar en cada paso
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateControl(int elapsedMilliseconds)
|
||||||
|
{
|
||||||
|
// Actualizar estadísticas
|
||||||
|
if (_simFluidos != null)
|
||||||
|
{
|
||||||
|
NumeroParticulas = _simFluidos.ParticlesCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Llamado cuando se inicia la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public void OnFluidSimulationStart()
|
||||||
|
{
|
||||||
|
// Crear la simulación de fluidos si es necesario
|
||||||
|
UpdateGeometryStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Llamado cuando se detiene la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public void OnFluidSimulationStop()
|
||||||
|
{
|
||||||
|
// Detener recursos si es necesario
|
||||||
|
SimulationStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateFluidSimulation(float deltaTime)
|
||||||
|
{
|
||||||
|
// Actualizar la simulación con el delta time
|
||||||
|
if (_simFluidos != null)
|
||||||
|
{
|
||||||
|
_simFluidos.Actualizar(deltaTime);
|
||||||
|
|
||||||
|
// Actualizar el control visual
|
||||||
|
UpdateControl((int)(deltaTime * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SimulationStop()
|
||||||
|
{
|
||||||
|
// Limpiar recursos si es necesario cuando se detiene la simulación
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucLoaded()
|
||||||
|
{
|
||||||
|
base.ucLoaded();
|
||||||
|
|
||||||
|
// Inicializar la simulación de fluidos si es necesario
|
||||||
|
UpdateGeometryStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucUnLoaded()
|
||||||
|
{
|
||||||
|
// Limpiar recursos
|
||||||
|
_simFluidos = null;
|
||||||
|
_contenedores.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementación para las conexiones con PLC
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Tag de lectura/escritura del nivel del Tanque 1")]
|
||||||
|
[property: Category("PLC:")]
|
||||||
|
private string tagNivelTanque1;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Tag de lectura/escritura de la apertura de la Válvula 1")]
|
||||||
|
[property: Category("PLC:")]
|
||||||
|
private string tagAperturaValvula1;
|
||||||
|
|
||||||
|
// Referencia a componentes típicos para integración con PLC
|
||||||
|
private Tanque _tanque1;
|
||||||
|
private Valvula _valvula1;
|
||||||
|
|
||||||
|
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||||
|
{
|
||||||
|
// Ejemplo de integración con PLC para la válvula
|
||||||
|
if (_valvula1 != null && !string.IsNullOrEmpty(TagAperturaValvula1))
|
||||||
|
{
|
||||||
|
float aperturaValvula = LeerWordTagScaled(TagAperturaValvula1) / 100.0f;
|
||||||
|
_valvula1.Apertura = Math.Clamp(aperturaValvula, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ejemplo de escritura del nivel del tanque al PLC
|
||||||
|
if (_tanque1 != null && !string.IsNullOrEmpty(TagNivelTanque1))
|
||||||
|
{
|
||||||
|
float nivelTanque = 0; // Implementar cálculo real del nivel
|
||||||
|
EscribirWordTagScaled(TagNivelTanque1, nivelTanque * 100, 0, 100, 0, 27648);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucSistemaFluidos"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
|
||||||
|
|
||||||
|
<UserControl.DataContext>
|
||||||
|
<vm:osSistemaFluidos/>
|
||||||
|
</UserControl.DataContext>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid x:Name="ContenedorVisual" Background="Transparent">
|
||||||
|
<!-- El DrawingVisual renderizará las partículas aquí -->
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Panel informativo opcional que se puede ocultar -->
|
||||||
|
<Border Padding="5"
|
||||||
|
Background="#60000000"
|
||||||
|
CornerRadius="5"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="5">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{Binding Nombre}"
|
||||||
|
Foreground="White"
|
||||||
|
FontWeight="Bold"/>
|
||||||
|
<TextBlock Text="{Binding NumeroParticulas, StringFormat='Partículas: {0}'}"
|
||||||
|
Foreground="White"/>
|
||||||
|
<TextBlock Text="{Binding Fps, StringFormat='FPS: {0:F1}'}"
|
||||||
|
Foreground="White"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
|
@ -0,0 +1,188 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using CtrEditor.FuncionesBase;
|
||||||
|
using CtrEditor.Simulacion.Fluids;
|
||||||
|
using tainicom.Aether.Physics2D.Fluids;
|
||||||
|
|
||||||
|
namespace CtrEditor.ObjetosSim
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lógica para ucSistemaFluidos.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ucSistemaFluidos : UserControl, IDataContainer
|
||||||
|
{
|
||||||
|
public osBase? Datos { get; set; }
|
||||||
|
public int zIndex_fromFrames { get; set; }
|
||||||
|
|
||||||
|
// Host para el sistema visual de partículas
|
||||||
|
private ParticulasFluidoHost _hostVisual;
|
||||||
|
|
||||||
|
// Timer para medir FPS
|
||||||
|
private DateTime _ultimaActualizacion = DateTime.Now;
|
||||||
|
private int _contadorFrames = 0;
|
||||||
|
private double _fps = 0;
|
||||||
|
|
||||||
|
public ucSistemaFluidos()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.Loaded += OnLoaded;
|
||||||
|
this.Unloaded += OnUnloaded;
|
||||||
|
|
||||||
|
// Inicializar el host visual y agregarlo al contenedor
|
||||||
|
_hostVisual = new ParticulasFluidoHost();
|
||||||
|
ContenedorVisual.Children.Add(_hostVisual);
|
||||||
|
|
||||||
|
// Configurar actualizaciones periódicas para FPS
|
||||||
|
CompositionTarget.Rendering += OnRendering;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRendering(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_contadorFrames++;
|
||||||
|
|
||||||
|
// Calcular FPS cada segundo
|
||||||
|
TimeSpan elapsed = DateTime.Now - _ultimaActualizacion;
|
||||||
|
if (elapsed.TotalSeconds >= 1)
|
||||||
|
{
|
||||||
|
_fps = _contadorFrames / elapsed.TotalSeconds;
|
||||||
|
_contadorFrames = 0;
|
||||||
|
_ultimaActualizacion = DateTime.Now;
|
||||||
|
|
||||||
|
if (Datos is osSistemaFluidos vm)
|
||||||
|
{
|
||||||
|
vm.Fps = _fps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar la visualización de partículas
|
||||||
|
ActualizarVisualizacion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucUnLoaded();
|
||||||
|
CompositionTarget.Rendering -= OnRendering;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Highlight(bool State) { }
|
||||||
|
|
||||||
|
public ZIndexEnum ZIndex_Base()
|
||||||
|
{
|
||||||
|
return ZIndexEnum.Dinamicos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza la visualización de partículas
|
||||||
|
/// </summary>
|
||||||
|
private void ActualizarVisualizacion()
|
||||||
|
{
|
||||||
|
if (Datos is osSistemaFluidos viewModel)
|
||||||
|
{
|
||||||
|
// Obtener la simulación activa desde el ViewModel
|
||||||
|
var simulacion = viewModel._simFluidos?.SistemaFluido;
|
||||||
|
if (simulacion != null)
|
||||||
|
{
|
||||||
|
// Configurar propiedades visuales
|
||||||
|
float tamañoParticula = viewModel.TamañoParticula;
|
||||||
|
Color colorFluido = viewModel.ColorFluido;
|
||||||
|
|
||||||
|
// Actualizar la visualización
|
||||||
|
_hostVisual.ActualizarParticulasFluido(simulacion.Particles, tamañoParticula, colorFluido, viewModel.OpacidadParticulas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clase que maneja el rendering optimizado de partículas usando DrawingVisual
|
||||||
|
/// </summary>
|
||||||
|
public class ParticulasFluidoHost : FrameworkElement
|
||||||
|
{
|
||||||
|
private readonly DrawingVisual _visual = new DrawingVisual();
|
||||||
|
private readonly MeterToPixelConverter _converter = new MeterToPixelConverter();
|
||||||
|
|
||||||
|
public ParticulasFluidoHost()
|
||||||
|
{
|
||||||
|
AddVisualChild(_visual);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int VisualChildrenCount => 1;
|
||||||
|
|
||||||
|
protected override Visual GetVisualChild(int index)
|
||||||
|
{
|
||||||
|
if (index != 0)
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
|
||||||
|
return _visual;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza la visualización de las partículas de fluido
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="particulas">Lista de partículas a visualizar</param>
|
||||||
|
/// <param name="tamañoParticula">Tamaño de las partículas en metros</param>
|
||||||
|
/// <param name="colorFluido">Color base del fluido</param>
|
||||||
|
/// <param name="opacidadBase">Opacidad base de las partículas</param>
|
||||||
|
public void ActualizarParticulasFluido(IEnumerable<Particle> particulas,
|
||||||
|
float tamañoParticula,
|
||||||
|
Color colorFluido,
|
||||||
|
double opacidadBase)
|
||||||
|
{
|
||||||
|
using (DrawingContext dc = _visual.RenderOpen())
|
||||||
|
{
|
||||||
|
foreach (var particula in particulas)
|
||||||
|
{
|
||||||
|
// Ignorar partículas inactivas u ocultas
|
||||||
|
if (particula == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Convertir posición a píxeles
|
||||||
|
float x = (float)_converter.Convert((particula.Position.X / 100) + 5, null, null, null);
|
||||||
|
float y = (float)_converter.Convert(particula.Position.Y / 100, null, null, null);
|
||||||
|
float radio = (float)_converter.Convert(tamañoParticula / 2, null, null, null);
|
||||||
|
|
||||||
|
// Calcular opacidad basada en densidad
|
||||||
|
double opacidad = Math.Clamp(particula.Density / 15.0, 0.4, 0.9) * opacidadBase;
|
||||||
|
|
||||||
|
// Calcular color basado en velocidad (opcional)
|
||||||
|
Color colorFinal = colorFluido;
|
||||||
|
double velocidad = Math.Sqrt(particula.Velocity.X * particula.Velocity.X +
|
||||||
|
particula.Velocity.Y * particula.Velocity.Y);
|
||||||
|
|
||||||
|
if (velocidad > 50)
|
||||||
|
{
|
||||||
|
// Partículas más rápidas tienden a ser más claras
|
||||||
|
float factor = Math.Min(1.0f, (float)velocidad / 400);
|
||||||
|
colorFinal = MezclarColores(colorFluido, Colors.White, factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
SolidColorBrush brush = new SolidColorBrush(colorFinal) { Opacity = opacidad };
|
||||||
|
|
||||||
|
// Dibujar partícula
|
||||||
|
dc.DrawEllipse(brush, null, new Point(x, y), radio, radio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mezcla dos colores según un factor (0-1)
|
||||||
|
/// </summary>
|
||||||
|
private Color MezclarColores(Color color1, Color color2, float factor)
|
||||||
|
{
|
||||||
|
byte r = (byte)(color1.R + (color2.R - color1.R) * factor);
|
||||||
|
byte g = (byte)(color1.G + (color2.G - color1.G) * factor);
|
||||||
|
byte b = (byte)(color1.B + (color2.B - color1.B) * factor);
|
||||||
|
return Color.FromRgb(r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucTuberiaFluido"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
|
xmlns:ctr="clr-namespace:CtrEditor">
|
||||||
|
|
||||||
|
<UserControl.DataContext>
|
||||||
|
<local:osTuberiaFluido/>
|
||||||
|
</UserControl.DataContext>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Path x:Name="TuberiaPath"
|
||||||
|
Data="{Binding PathData}"
|
||||||
|
StrokeEndLineCap="Round"
|
||||||
|
StrokeStartLineCap="Round"
|
||||||
|
StrokeLineJoin="Round">
|
||||||
|
<Path.Effect>
|
||||||
|
<BlurEffect Radius="0.5"/>
|
||||||
|
</Path.Effect>
|
||||||
|
</Path>
|
||||||
|
|
||||||
|
<!-- Visualización opcional de la densidad del fluido dentro de la tubería -->
|
||||||
|
<Path x:Name="FluidoPath"
|
||||||
|
Data="{Binding PathData}"
|
||||||
|
Opacity="{Binding DensidadFluido}"
|
||||||
|
StrokeEndLineCap="Round"
|
||||||
|
StrokeStartLineCap="Round"
|
||||||
|
StrokeLineJoin="Round">
|
||||||
|
</Path>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
|
@ -0,0 +1,258 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using CtrEditor.FuncionesBase;
|
||||||
|
using CtrEditor.Simulacion.Fluids.Components;
|
||||||
|
using nkast.Aether.Physics2D.Common;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace CtrEditor.ObjetosSim
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lógica para ucTuberiaFluido.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ucTuberiaFluido : UserControl, IDataContainer
|
||||||
|
{
|
||||||
|
public osBase? Datos { get; set; }
|
||||||
|
public int zIndex_fromFrames { get; set; }
|
||||||
|
|
||||||
|
public ucTuberiaFluido()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.Loaded += OnLoaded;
|
||||||
|
this.Unloaded += OnUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucUnLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Highlight(bool State) { }
|
||||||
|
|
||||||
|
public ZIndexEnum ZIndex_Base()
|
||||||
|
{
|
||||||
|
return ZIndexEnum.Estaticos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel para la tubería de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public partial class osTuberiaFluido : osBase, IosBase
|
||||||
|
{
|
||||||
|
// Tubería en la simulación de fluidos
|
||||||
|
private Tuberia _tuberia;
|
||||||
|
|
||||||
|
// Referencia al sistema de fluidos
|
||||||
|
private osSistemaFluidos _sistemaFluidos;
|
||||||
|
|
||||||
|
// Propiedades visuales
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Diámetro de la tubería en metros")]
|
||||||
|
[property: Category("Dimensiones:")]
|
||||||
|
private float diametro = 0.05f;
|
||||||
|
|
||||||
|
partial void OnDiametroChanged(float value)
|
||||||
|
{
|
||||||
|
// Actualizar geometría si la tubería ya existe
|
||||||
|
if (_tuberia != null)
|
||||||
|
{
|
||||||
|
// No es posible cambiar el diámetro directamente, recrear la tubería
|
||||||
|
ReconstruirTuberia();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Diámetro interno para visualización del fluido")]
|
||||||
|
[property: Category("Visual:")]
|
||||||
|
private float diametroInterno;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Color de la tubería")]
|
||||||
|
[property: Category("Visual:")]
|
||||||
|
private System.Windows.Media.Color color = System.Windows.Media.Colors.Gray;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Color del fluido")]
|
||||||
|
[property: Category("Visual:")]
|
||||||
|
private System.Windows.Media.Color colorFluido = System.Windows.Media.Colors.CornflowerBlue;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Datos del path para dibujar la tubería")]
|
||||||
|
[property: Category("Interno:")]
|
||||||
|
private string pathData;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Densidad del fluido (0-1)")]
|
||||||
|
[property: Category("Simulación:")]
|
||||||
|
private double densidadFluido = 0.7;
|
||||||
|
|
||||||
|
// Lista de puntos que forman la tubería
|
||||||
|
private List<Vector2> _puntos = new List<Vector2>();
|
||||||
|
|
||||||
|
// Nombre de la clase para identificación
|
||||||
|
public static string NombreClase()
|
||||||
|
{
|
||||||
|
return "TuberiaFluido";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string nombre = NombreClase();
|
||||||
|
public override string Nombre
|
||||||
|
{
|
||||||
|
get => nombre;
|
||||||
|
set => SetProperty(ref nombre, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agrega un punto a la tubería
|
||||||
|
/// </summary>
|
||||||
|
public void AgregarPunto(float x, float y)
|
||||||
|
{
|
||||||
|
_puntos.Add(new Vector2(x, y));
|
||||||
|
ActualizarPathData();
|
||||||
|
|
||||||
|
// Si la tubería ya está creada, agregar el punto a la simulación
|
||||||
|
if (_tuberia != null)
|
||||||
|
{
|
||||||
|
_tuberia.AgregarPunto(new Vector2(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza la representación visual de la tubería
|
||||||
|
/// </summary>
|
||||||
|
private void ActualizarPathData()
|
||||||
|
{
|
||||||
|
if (_puntos.Count < 2) return;
|
||||||
|
|
||||||
|
var converter = new MeterToPixelConverter();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// Iniciar el path
|
||||||
|
sb.Append($"M {converter.Convert(_puntos[0].X, null, null, null)} {converter.Convert(_puntos[0].Y, null, null, null)} ");
|
||||||
|
|
||||||
|
// Añadir los demás puntos
|
||||||
|
for (int i = 1; i < _puntos.Count; i++)
|
||||||
|
{
|
||||||
|
sb.Append($"L {converter.Convert(_puntos[i].X, null, null, null)} {converter.Convert(_puntos[i].Y, null, null, null)} ");
|
||||||
|
}
|
||||||
|
|
||||||
|
PathData = sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reconstruye la tubería en la simulación
|
||||||
|
/// </summary>
|
||||||
|
private void ReconstruirTuberia()
|
||||||
|
{
|
||||||
|
if (_sistemaFluidos != null)
|
||||||
|
{
|
||||||
|
// Eliminar la tubería existente
|
||||||
|
if (_tuberia != null)
|
||||||
|
{
|
||||||
|
_sistemaFluidos.EliminarComponente(_tuberia);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear nueva tubería
|
||||||
|
_tuberia = _sistemaFluidos.CrearTuberia(Diametro);
|
||||||
|
|
||||||
|
// Agregar todos los puntos existentes
|
||||||
|
foreach (var punto in _puntos)
|
||||||
|
{
|
||||||
|
_tuberia.AgregarPunto(punto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public osTuberiaFluido()
|
||||||
|
{
|
||||||
|
DiametroInterno = Diametro * 0.8f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnMove(float LeftPixels, float TopPixels)
|
||||||
|
{
|
||||||
|
// Mover todos los puntos de la tubería
|
||||||
|
if (_puntos.Count > 0)
|
||||||
|
{
|
||||||
|
Vector2 delta = new Vector2(Left - CanvasGetLeftinMeter(), Top - CanvasGetTopinMeter());
|
||||||
|
|
||||||
|
for (int i = 0; i < _puntos.Count; i++)
|
||||||
|
{
|
||||||
|
_puntos[i] += delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActualizarPathData();
|
||||||
|
ReconstruirTuberia();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucLoaded()
|
||||||
|
{
|
||||||
|
base.ucLoaded();
|
||||||
|
|
||||||
|
// Buscar el sistema de fluidos en la simulación
|
||||||
|
if (_mainViewModel?.ObjetosSimulables != null)
|
||||||
|
{
|
||||||
|
foreach (var obj in _mainViewModel.ObjetosSimulables)
|
||||||
|
{
|
||||||
|
if (obj is osSistemaFluidos sistemaFluidos)
|
||||||
|
{
|
||||||
|
_sistemaFluidos = sistemaFluidos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si no hay puntos, agregar dos puntos iniciales
|
||||||
|
if (_puntos.Count == 0)
|
||||||
|
{
|
||||||
|
_puntos.Add(new Vector2(Left, Top));
|
||||||
|
_puntos.Add(new Vector2(Left + Ancho, Top));
|
||||||
|
ActualizarPathData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear la tubería en la simulación
|
||||||
|
ReconstruirTuberia();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucUnLoaded()
|
||||||
|
{
|
||||||
|
// Eliminar la tubería de la simulación
|
||||||
|
if (_sistemaFluidos != null && _tuberia != null)
|
||||||
|
{
|
||||||
|
_sistemaFluidos.EliminarComponente(_tuberia);
|
||||||
|
_tuberia = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateControl(int elapsedMilliseconds)
|
||||||
|
{
|
||||||
|
// Actualizar la densidad del fluido basada en la simulación
|
||||||
|
if (_sistemaFluidos != null && _sistemaFluidos._simFluidos != null && _puntos.Count > 0)
|
||||||
|
{
|
||||||
|
// Calcular el centro aproximado de la tubería
|
||||||
|
Vector2 centro = Vector2.Zero;
|
||||||
|
foreach (var punto in _puntos)
|
||||||
|
{
|
||||||
|
centro += punto;
|
||||||
|
}
|
||||||
|
centro /= _puntos.Count;
|
||||||
|
|
||||||
|
// Obtener densidad en esa posición
|
||||||
|
DensidadFluido = _sistemaFluidos._simFluidos.ObtenerDensidadEnPosicion(centro);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucValvulaFluido"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:CtrEditor.ObjetosSim">
|
||||||
|
|
||||||
|
<UserControl.DataContext>
|
||||||
|
<local:osValvulaFluido/>
|
||||||
|
</UserControl.DataContext>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Canvas RenderTransformOrigin="0.5,0.5">
|
||||||
|
<Canvas.RenderTransform>
|
||||||
|
<TransformGroup>
|
||||||
|
<RotateTransform Angle="{Binding Angulo}"/>
|
||||||
|
</TransformGroup>
|
||||||
|
</Canvas.RenderTransform>
|
||||||
|
|
||||||
|
<!-- Cuerpo de la válvula -->
|
||||||
|
<Rectangle Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||||
|
RadiusX="5" RadiusY="5"
|
||||||
|
Canvas.Left="{Binding OffsetXRectangulo, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Canvas.Top="{Binding OffsetYRectangulo, Converter={StaticResource MeterToPixelConverter}}"/>
|
||||||
|
|
||||||
|
<!-- Indicador de apertura -->
|
||||||
|
<Rectangle Width="{Binding AperturaVisual, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Height="{Binding GrosorIndicador, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Fill="{Binding ColorIndicador, Converter={StaticResource ColorToBrushConverter}}"
|
||||||
|
Canvas.Left="{Binding OffsetXIndicador, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Canvas.Top="{Binding OffsetYIndicador, Converter={StaticResource MeterToPixelConverter}}"/>
|
||||||
|
|
||||||
|
<!-- Texto con el valor numérico de apertura -->
|
||||||
|
<TextBlock Text="{Binding ValorApertura, StringFormat='{}{0:P0}'}"
|
||||||
|
Canvas.Left="{Binding OffsetXTexto, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Canvas.Top="{Binding OffsetYTexto, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="White"
|
||||||
|
FontSize="12"/>
|
||||||
|
</Canvas>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
|
@ -0,0 +1,206 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using CtrEditor.FuncionesBase;
|
||||||
|
using CtrEditor.Simulacion.Fluids.Components;
|
||||||
|
using nkast.Aether.Physics2D.Common;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Color = System.Windows.Media.Color;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using LibS7Adv;
|
||||||
|
|
||||||
|
namespace CtrEditor.ObjetosSim
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lógica para ucValvulaFluido.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ucValvulaFluido : UserControl, IDataContainer
|
||||||
|
{
|
||||||
|
public osBase? Datos { get; set; }
|
||||||
|
public int zIndex_fromFrames { get; set; }
|
||||||
|
|
||||||
|
public ucValvulaFluido()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.Loaded += OnLoaded;
|
||||||
|
this.Unloaded += OnUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Datos?.ucUnLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Highlight(bool State) { }
|
||||||
|
|
||||||
|
public ZIndexEnum ZIndex_Base()
|
||||||
|
{
|
||||||
|
return ZIndexEnum.Estaticos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel para la válvula de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public partial class osValvulaFluido : osBase, IosBase
|
||||||
|
{
|
||||||
|
// Válvula en la simulación de fluidos
|
||||||
|
private Valvula _valvula;
|
||||||
|
|
||||||
|
// Referencia al sistema de fluidos
|
||||||
|
private osSistemaFluidos _sistemaFluidos;
|
||||||
|
|
||||||
|
// Propiedades dimensionales
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Category("Dimensiones:")]
|
||||||
|
[property: Description("Ancho de la válvula en metros")]
|
||||||
|
private float ancho = 0.1f;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Alto de la válvula en metros")]
|
||||||
|
[property: Category("Dimensiones:")]
|
||||||
|
private float alto = 0.06f;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Diámetro de la tubería conectada")]
|
||||||
|
[property: Category("Dimensiones:")]
|
||||||
|
private float diametroTuberia = 0.05f;
|
||||||
|
|
||||||
|
// Propiedades visuales
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Color de la válvula")]
|
||||||
|
[property: Category("Visual:")]
|
||||||
|
private Color color = Colors.Silver;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Color del indicador de apertura")]
|
||||||
|
[property: Category("Visual:")]
|
||||||
|
private Color colorIndicador = Colors.CornflowerBlue;
|
||||||
|
|
||||||
|
// Propiedades de funcionamiento
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Apertura de la válvula (0-1)")]
|
||||||
|
[property: Category("Operación:")]
|
||||||
|
private float apertura = 1.0f;
|
||||||
|
|
||||||
|
partial void OnAperturaChanged(float value)
|
||||||
|
{
|
||||||
|
Apertura = Math.Clamp(value, 0, 1);
|
||||||
|
|
||||||
|
// Actualizar la válvula en la simulación
|
||||||
|
if (_valvula != null)
|
||||||
|
{
|
||||||
|
_valvula.Apertura = Apertura;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag PLC para la válvula
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Tag de lectura/escritura de la apertura (0-100%)")]
|
||||||
|
[property: Category("PLC:")]
|
||||||
|
private string tagApertura;
|
||||||
|
|
||||||
|
// Propiedades calculadas para visualización
|
||||||
|
public float AperturaVisual => Ancho * 0.8f * Apertura;
|
||||||
|
public float GrosorIndicador => Alto * 0.2f;
|
||||||
|
public float ValorApertura => Apertura;
|
||||||
|
|
||||||
|
// Propiedades para posicionamiento de elementos
|
||||||
|
public float OffsetXRectangulo => -Ancho / 2;
|
||||||
|
public float OffsetYRectangulo => -Alto / 2;
|
||||||
|
public float OffsetXIndicador => -Ancho * 0.4f;
|
||||||
|
public float OffsetYIndicador => -GrosorIndicador / 2;
|
||||||
|
public float OffsetXTexto => -Ancho * 0.15f;
|
||||||
|
public float OffsetYTexto => Alto * 0.1f;
|
||||||
|
|
||||||
|
// Nombre de la clase para identificación
|
||||||
|
public static string NombreClase()
|
||||||
|
{
|
||||||
|
return "ValvulaFluido";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string nombre = NombreClase();
|
||||||
|
public override string Nombre
|
||||||
|
{
|
||||||
|
get => nombre;
|
||||||
|
set => SetProperty(ref nombre, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public osValvulaFluido()
|
||||||
|
{
|
||||||
|
// Constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnMove(float LeftPixels, float TopPixels)
|
||||||
|
{
|
||||||
|
if (_valvula != null)
|
||||||
|
{
|
||||||
|
// Actualizar posición de la válvula en la simulación
|
||||||
|
_valvula.ActualizarPosicion(new Vector2(Left, Top));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRotate(float Delta_Angle)
|
||||||
|
{
|
||||||
|
// La rotación visual ya está manejada por el XAML
|
||||||
|
// No necesita actualizaciones adicionales en la simulación
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucLoaded()
|
||||||
|
{
|
||||||
|
base.ucLoaded();
|
||||||
|
|
||||||
|
// Buscar el sistema de fluidos en la simulación
|
||||||
|
if (_mainViewModel?.ObjetosSimulables != null)
|
||||||
|
{
|
||||||
|
foreach (var obj in _mainViewModel.ObjetosSimulables)
|
||||||
|
{
|
||||||
|
if (obj is osSistemaFluidos sistemaFluidos)
|
||||||
|
{
|
||||||
|
_sistemaFluidos = sistemaFluidos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear la válvula en la simulación
|
||||||
|
if (_sistemaFluidos != null)
|
||||||
|
{
|
||||||
|
_valvula = _sistemaFluidos.CrearValvula(
|
||||||
|
new Vector2(Left, Top),
|
||||||
|
DiametroTuberia,
|
||||||
|
Apertura
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ucUnLoaded()
|
||||||
|
{
|
||||||
|
// Eliminar la válvula de la simulación
|
||||||
|
if (_sistemaFluidos != null && _valvula != null)
|
||||||
|
{
|
||||||
|
_sistemaFluidos.EliminarComponente(_valvula);
|
||||||
|
_valvula = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||||
|
{
|
||||||
|
// Manejar la comunicación con PLC
|
||||||
|
if (!string.IsNullOrEmpty(TagApertura))
|
||||||
|
{
|
||||||
|
float aperturaPlc = LeerWordTagScaled(TagApertura) / 100.0f;
|
||||||
|
Apertura = Math.Clamp(aperturaPlc, 0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using nkast.Aether.Physics2D.Common;
|
||||||
|
using CtrEditor.Simulacion.Fluids;
|
||||||
|
using CtrEditor.ObjetosSim;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gestor para la simulación de fluidos independiente de la simulación física principal
|
||||||
|
/// </summary>
|
||||||
|
public class FluidSimulationManager
|
||||||
|
{
|
||||||
|
private SimulacionFluidos _simulacion;
|
||||||
|
private readonly List<Action> _deferredActions = new List<Action>();
|
||||||
|
private readonly List<osSistemaFluidos> _sistemasRegistrados = new List<osSistemaFluidos>();
|
||||||
|
private float _defaultWidth = 10.0f;
|
||||||
|
private float _defaultHeight = 10.0f;
|
||||||
|
|
||||||
|
private bool _isRunning = false;
|
||||||
|
private Stopwatch _stopwatch;
|
||||||
|
private double _lastUpdateTime;
|
||||||
|
|
||||||
|
public bool IsRunning => _isRunning;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public FluidSimulationManager()
|
||||||
|
{
|
||||||
|
_stopwatch = new Stopwatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inicializa la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
if (_simulacion == null)
|
||||||
|
{
|
||||||
|
_simulacion = new SimulacionFluidos(
|
||||||
|
_defaultWidth,
|
||||||
|
_defaultHeight,
|
||||||
|
10000, // max partículas
|
||||||
|
new Vector2(0, 9.8f) // gravedad por defecto
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inicia la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
if (!_isRunning)
|
||||||
|
{
|
||||||
|
_isRunning = true;
|
||||||
|
_stopwatch.Start();
|
||||||
|
_lastUpdateTime = _stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
|
||||||
|
// Notificar a los sistemas de fluidos
|
||||||
|
foreach (var sistema in _sistemasRegistrados)
|
||||||
|
{
|
||||||
|
sistema.OnFluidSimulationStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detiene la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
if (_isRunning)
|
||||||
|
{
|
||||||
|
_isRunning = false;
|
||||||
|
_stopwatch.Stop();
|
||||||
|
_stopwatch.Reset();
|
||||||
|
|
||||||
|
// Notificar a los sistemas de fluidos
|
||||||
|
foreach (var sistema in _sistemasRegistrados)
|
||||||
|
{
|
||||||
|
sistema.OnFluidSimulationStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza un paso de la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public void Step()
|
||||||
|
{
|
||||||
|
if (!_isRunning) return;
|
||||||
|
|
||||||
|
// Calcular delta time
|
||||||
|
double currentTime = _stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
float deltaTime = (float)(currentTime - _lastUpdateTime) / 1000.0f;
|
||||||
|
_lastUpdateTime = currentTime;
|
||||||
|
|
||||||
|
// Asegurar que deltaTime no es demasiado grande (evita inestabilidades)
|
||||||
|
if (deltaTime > 0.05f) deltaTime = 0.05f;
|
||||||
|
|
||||||
|
// Procesar acciones diferidas
|
||||||
|
foreach (var action in _deferredActions)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
_deferredActions.Clear();
|
||||||
|
|
||||||
|
// Actualizar la simulación
|
||||||
|
_simulacion?.Actualizar(deltaTime);
|
||||||
|
|
||||||
|
// Notificar a los sistemas de fluidos
|
||||||
|
foreach (var sistema in _sistemasRegistrados)
|
||||||
|
{
|
||||||
|
sistema.UpdateFluidSimulation(deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registra un sistema de fluidos para ser actualizado
|
||||||
|
/// </summary>
|
||||||
|
public void RegisterFluidSystem(osSistemaFluidos sistema)
|
||||||
|
{
|
||||||
|
if (!_sistemasRegistrados.Contains(sistema))
|
||||||
|
{
|
||||||
|
_sistemasRegistrados.Add(sistema);
|
||||||
|
|
||||||
|
// Si el sistema ya está corriendo, notificar al nuevo sistema
|
||||||
|
if (_isRunning)
|
||||||
|
{
|
||||||
|
sistema.OnFluidSimulationStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Elimina un sistema de fluidos del registro
|
||||||
|
/// </summary>
|
||||||
|
public void UnregisterFluidSystem(osSistemaFluidos sistema)
|
||||||
|
{
|
||||||
|
_sistemasRegistrados.Remove(sistema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Añade una acción para ser ejecutada en el próximo paso de simulación
|
||||||
|
/// </summary>
|
||||||
|
public void AddDeferredAction(Action action)
|
||||||
|
{
|
||||||
|
_deferredActions.Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene la instancia de simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public SimulacionFluidos GetSimulacion()
|
||||||
|
{
|
||||||
|
if (_simulacion == null)
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
return _simulacion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limpia los recursos de la simulación
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
_simulacion = null;
|
||||||
|
_sistemasRegistrados.Clear();
|
||||||
|
_deferredActions.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,412 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using nkast.Aether.Physics2D.Common;
|
||||||
|
using tainicom.Aether.Physics2D.Fluids;
|
||||||
|
using CtrEditor.Simulacion.Fluids.Components;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion.Fluids.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Clase base para todos los componentes de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ComponenteFluido : IContenedorFluido
|
||||||
|
{
|
||||||
|
public Vector2 Posicion { get; set; }
|
||||||
|
public bool EsActivo { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convierte una posición de metros/WPF a sistema de coordenadas de FluidSystem2
|
||||||
|
/// </summary>
|
||||||
|
protected Vector2 ConvertirPosicion(Vector2 posicion, int worldWidth)
|
||||||
|
{
|
||||||
|
return new Vector2(
|
||||||
|
posicion.X * 100 - worldWidth/2,
|
||||||
|
posicion.Y * 100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementación en clases derivadas para restricciones específicas
|
||||||
|
/// </summary>
|
||||||
|
public abstract void RestringirParticula(Particle particula);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tanque para contener fluidos
|
||||||
|
/// </summary>
|
||||||
|
public class Tanque : ComponenteFluido
|
||||||
|
{
|
||||||
|
public float Ancho { get; set; }
|
||||||
|
public float Alto { get; set; }
|
||||||
|
private int _worldWidth;
|
||||||
|
|
||||||
|
// Coordenadas ajustadas al sistema de FluidSystem2
|
||||||
|
private float _xMin, _xMax, _yMin, _yMax;
|
||||||
|
private float _coefRebote = 0.3f; // Factor de rebote en colisiones
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor para un tanque
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="posicion">Posición del centro del tanque</param>
|
||||||
|
/// <param name="ancho">Ancho del tanque</param>
|
||||||
|
/// <param name="alto">Alto del tanque</param>
|
||||||
|
/// <param name="worldWidth">Ancho del mundo en cm (para conversión de coordenadas)</param>
|
||||||
|
public Tanque(Vector2 posicion, float ancho, float alto, int worldWidth)
|
||||||
|
{
|
||||||
|
Posicion = posicion;
|
||||||
|
Ancho = ancho;
|
||||||
|
Alto = alto;
|
||||||
|
_worldWidth = worldWidth;
|
||||||
|
|
||||||
|
ActualizarLimites();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza los límites internos del tanque después de cambiar la posición o tamaño
|
||||||
|
/// </summary>
|
||||||
|
public void ActualizarLimites()
|
||||||
|
{
|
||||||
|
// Convertir a coordenadas internas
|
||||||
|
_xMin = (Posicion.X - Ancho/2) * 100 - _worldWidth/2;
|
||||||
|
_xMax = (Posicion.X + Ancho/2) * 100 - _worldWidth/2;
|
||||||
|
_yMin = Posicion.Y * 100;
|
||||||
|
_yMax = (Posicion.Y + Alto) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restringe las partículas para que permanezcan dentro del tanque
|
||||||
|
/// </summary>
|
||||||
|
public override void RestringirParticula(Particle particula)
|
||||||
|
{
|
||||||
|
if (!EsActivo) return;
|
||||||
|
|
||||||
|
// Comprobar si la partícula está dentro del área del tanque
|
||||||
|
bool dentroX = particula.Position.X >= _xMin && particula.Position.X <= _xMax;
|
||||||
|
bool dentroY = particula.Position.Y >= _yMin && particula.Position.Y <= _yMax;
|
||||||
|
|
||||||
|
if (dentroX && dentroY)
|
||||||
|
{
|
||||||
|
// La partícula está dentro del tanque, comprobar si está cerca de los bordes
|
||||||
|
if (particula.Position.X < _xMin + 5)
|
||||||
|
{
|
||||||
|
particula.Position.X = _xMin + 5;
|
||||||
|
particula.Velocity.X *= -_coefRebote;
|
||||||
|
}
|
||||||
|
else if (particula.Position.X > _xMax - 5)
|
||||||
|
{
|
||||||
|
particula.Position.X = _xMax - 5;
|
||||||
|
particula.Velocity.X *= -_coefRebote;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (particula.Position.Y < _yMin + 5)
|
||||||
|
{
|
||||||
|
particula.Position.Y = _yMin + 5;
|
||||||
|
particula.Velocity.Y *= -_coefRebote;
|
||||||
|
}
|
||||||
|
else if (particula.Position.Y > _yMax - 5)
|
||||||
|
{
|
||||||
|
particula.Position.Y = _yMax - 5;
|
||||||
|
particula.Velocity.Y *= -_coefRebote;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene el nivel de llenado del tanque en porcentaje
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="simulacion">Instancia activa de la simulación</param>
|
||||||
|
/// <returns>Porcentaje de llenado (0-1)</returns>
|
||||||
|
public float ObtenerNivelLlenado()
|
||||||
|
{
|
||||||
|
// Sería necesario acceder a las partículas para calcular esto
|
||||||
|
// Implementación en clases derivadas específicas
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Representa un segmento de tubo para fluidos
|
||||||
|
/// </summary>
|
||||||
|
public class SegmentoTubo
|
||||||
|
{
|
||||||
|
public Vector2 Inicio { get; }
|
||||||
|
public Vector2 Fin { get; }
|
||||||
|
public float Radio { get; }
|
||||||
|
|
||||||
|
public SegmentoTubo(Vector2 inicio, Vector2 fin, float radio)
|
||||||
|
{
|
||||||
|
Inicio = inicio;
|
||||||
|
Fin = fin;
|
||||||
|
Radio = radio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contiene y restringe el movimiento de una partícula dentro del tubo
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Verdadero si se aplicó alguna restricción</returns>
|
||||||
|
public bool ContenerParticula(Particle particula)
|
||||||
|
{
|
||||||
|
// Calcular distancia de la partícula al segmento de línea
|
||||||
|
Vector2 pos = new Vector2(particula.Position.X, particula.Position.Y);
|
||||||
|
float distancia = DistanciaPuntoALinea(pos, Inicio, Fin);
|
||||||
|
|
||||||
|
// Si la distancia es mayor que el radio, aplicar restricción
|
||||||
|
if (distancia > Radio)
|
||||||
|
{
|
||||||
|
// Calcular punto más cercano en el segmento
|
||||||
|
Vector2 puntoMasCercano = PuntoMasCercanoEnLinea(pos, Inicio, Fin);
|
||||||
|
|
||||||
|
// Dirección desde partícula hacia punto más cercano en el centro del tubo
|
||||||
|
Vector2 direccion = VectorUtils.NormalizeVector(puntoMasCercano - pos);
|
||||||
|
|
||||||
|
// Mover la partícula al interior del tubo
|
||||||
|
particula.Position.X = puntoMasCercano.X - direccion.X * Radio * 0.9f;
|
||||||
|
particula.Position.Y = puntoMasCercano.Y - direccion.Y * Radio * 0.9f;
|
||||||
|
|
||||||
|
// Ajustar velocidad para simular rebote
|
||||||
|
float velocidadNormal = VectorUtils.DotProduct(
|
||||||
|
new Vector2(particula.Velocity.X, particula.Velocity.Y),
|
||||||
|
direccion);
|
||||||
|
|
||||||
|
particula.Velocity.X -= direccion.X * velocidadNormal * 1.8f;
|
||||||
|
particula.Velocity.Y -= direccion.Y * velocidadNormal * 1.8f;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // La partícula está dentro del tubo
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calcula la distancia de un punto a una línea
|
||||||
|
/// </summary>
|
||||||
|
private float DistanciaPuntoALinea(Vector2 punto, Vector2 lineaInicio, Vector2 lineaFin)
|
||||||
|
{
|
||||||
|
// Vector que representa la dirección de la línea
|
||||||
|
Vector2 linea = lineaFin - lineaInicio;
|
||||||
|
float longitud = linea.Length();
|
||||||
|
|
||||||
|
if (longitud < 0.0001f)
|
||||||
|
return VectorUtils.DistanceBetween(punto, lineaInicio); // Es un punto, no una línea
|
||||||
|
|
||||||
|
// Normalizar el vector de la línea
|
||||||
|
Vector2 lineaNormalizada = linea / longitud;
|
||||||
|
|
||||||
|
// Vector desde el inicio de la línea hasta el punto
|
||||||
|
Vector2 puntoDesdeInicio = punto - lineaInicio;
|
||||||
|
|
||||||
|
// Proyectar puntoDesdeInicio sobre la línea
|
||||||
|
float proyeccion = VectorUtils.DotProduct(puntoDesdeInicio, lineaNormalizada);
|
||||||
|
|
||||||
|
// Limitar la proyección al segmento de línea
|
||||||
|
proyeccion = Math.Max(0, Math.Min(longitud, proyeccion));
|
||||||
|
|
||||||
|
// Punto más cercano en la línea
|
||||||
|
Vector2 puntoMasCercano = lineaInicio + lineaNormalizada * proyeccion;
|
||||||
|
|
||||||
|
// Distancia desde el punto hasta el punto más cercano
|
||||||
|
return VectorUtils.DistanceBetween(punto, puntoMasCercano);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calcula el punto más cercano en una línea desde un punto dado
|
||||||
|
/// </summary>
|
||||||
|
private Vector2 PuntoMasCercanoEnLinea(Vector2 punto, Vector2 lineaInicio, Vector2 lineaFin)
|
||||||
|
{
|
||||||
|
// Vector que representa la dirección de la línea
|
||||||
|
Vector2 linea = lineaFin - lineaInicio;
|
||||||
|
float longitud = linea.Length();
|
||||||
|
|
||||||
|
if (longitud < 0.0001f)
|
||||||
|
return lineaInicio; // Es un punto, no una línea
|
||||||
|
|
||||||
|
// Normalizar el vector de la línea
|
||||||
|
Vector2 lineaNormalizada = linea / longitud;
|
||||||
|
|
||||||
|
// Vector desde el inicio de la línea hasta el punto
|
||||||
|
Vector2 puntoDesdeInicio = punto - lineaInicio;
|
||||||
|
|
||||||
|
// Proyectar puntoDesdeInicio sobre la línea
|
||||||
|
float proyeccion;
|
||||||
|
Vector2.Dot(ref puntoDesdeInicio, ref lineaNormalizada, out proyeccion);
|
||||||
|
|
||||||
|
// Limitar la proyección al segmento de línea
|
||||||
|
proyeccion = Math.Max(0, Math.Min(longitud, proyeccion));
|
||||||
|
|
||||||
|
// Punto más cercano en la línea
|
||||||
|
return lineaInicio + lineaNormalizada * proyeccion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tubería para conducir fluidos entre puntos
|
||||||
|
/// </summary>
|
||||||
|
public class Tuberia : ComponenteFluido
|
||||||
|
{
|
||||||
|
private List<SegmentoTubo> _segmentos = new List<SegmentoTubo>();
|
||||||
|
private List<Vector2> _puntos = new List<Vector2>();
|
||||||
|
public float Diametro { get; private set; }
|
||||||
|
private int _worldWidth;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor para una tubería
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="diametro">Diámetro de la tubería</param>
|
||||||
|
/// <param name="worldWidth">Ancho del mundo en cm (para conversión de coordenadas)</param>
|
||||||
|
public Tuberia(float diametro, int worldWidth)
|
||||||
|
{
|
||||||
|
Diametro = diametro;
|
||||||
|
_worldWidth = worldWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agrega un punto a la tubería, creando segmentos automáticamente
|
||||||
|
/// </summary>
|
||||||
|
public void AgregarPunto(Vector2 punto)
|
||||||
|
{
|
||||||
|
// Convertir punto a coordenadas internas
|
||||||
|
Vector2 puntoAjustado = ConvertirPosicion(punto, _worldWidth);
|
||||||
|
|
||||||
|
// Si ya hay puntos, crear un segmento con el punto anterior
|
||||||
|
if (_puntos.Count > 0)
|
||||||
|
{
|
||||||
|
_segmentos.Add(new SegmentoTubo(
|
||||||
|
_puntos[_puntos.Count - 1],
|
||||||
|
puntoAjustado,
|
||||||
|
Diametro * 100 / 2 // Radio en unidades internas
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
_puntos.Add(puntoAjustado);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restringe las partículas para que permanezcan dentro de la tubería
|
||||||
|
/// </summary>
|
||||||
|
public override void RestringirParticula(Particle particula)
|
||||||
|
{
|
||||||
|
if (!EsActivo) return;
|
||||||
|
|
||||||
|
// Comprobar si la partícula está cerca de la tubería
|
||||||
|
Vector2 posParticula = new Vector2(particula.Position.X, particula.Position.Y);
|
||||||
|
float distanciaMinima = float.MaxValue;
|
||||||
|
SegmentoTubo segmentoMasCercano = null;
|
||||||
|
|
||||||
|
// Encontrar el segmento más cercano
|
||||||
|
foreach (var segmento in _segmentos)
|
||||||
|
{
|
||||||
|
float distancia = DistanciaPuntoASegmento(posParticula, segmento.Inicio, segmento.Fin);
|
||||||
|
if (distancia < distanciaMinima)
|
||||||
|
{
|
||||||
|
distanciaMinima = distancia;
|
||||||
|
segmentoMasCercano = segmento;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar restricciones si hay un segmento cercano
|
||||||
|
if (segmentoMasCercano != null)
|
||||||
|
{
|
||||||
|
segmentoMasCercano.ContenerParticula(particula);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calcula la distancia desde un punto a un segmento de línea
|
||||||
|
/// </summary>
|
||||||
|
private float DistanciaPuntoASegmento(Vector2 punto, Vector2 segmentoInicio, Vector2 segmentoFin)
|
||||||
|
{
|
||||||
|
// Vector dirección del segmento
|
||||||
|
Vector2 segmento = segmentoFin - segmentoInicio;
|
||||||
|
float longitudCuadrada = segmento.LengthSquared();
|
||||||
|
|
||||||
|
if (longitudCuadrada < 0.0001f)
|
||||||
|
return VectorUtils.DistanceBetween(punto, segmentoInicio); // Es un punto, no un segmento
|
||||||
|
|
||||||
|
// Calcular proyección del punto sobre el segmento
|
||||||
|
float t = VectorUtils.DotProduct(punto - segmentoInicio, segmento) / longitudCuadrada;
|
||||||
|
t = Math.Max(0, Math.Min(1, t));
|
||||||
|
|
||||||
|
// Punto más cercano en el segmento
|
||||||
|
Vector2 proyeccion = segmentoInicio + t * segmento;
|
||||||
|
|
||||||
|
// Distancia desde el punto hasta la proyección
|
||||||
|
return VectorUtils.DistanceBetween(punto, proyeccion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Válvula para controlar el flujo de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public class Valvula : ComponenteFluido
|
||||||
|
{
|
||||||
|
public float Apertura { get; set; } // 0.0 = cerrada, 1.0 = abierta
|
||||||
|
public float Diametro { get; set; }
|
||||||
|
private int _worldWidth;
|
||||||
|
private Vector2 _posicionAjustada;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor para una válvula
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="posicion">Posición de la válvula</param>
|
||||||
|
/// <param name="diametro">Diámetro del conducto de la válvula</param>
|
||||||
|
/// <param name="apertura">Apertura inicial (0-1)</param>
|
||||||
|
/// <param name="worldWidth">Ancho del mundo en cm (para conversión de coordenadas)</param>
|
||||||
|
public Valvula(Vector2 posicion, float diametro, float apertura, int worldWidth)
|
||||||
|
{
|
||||||
|
Posicion = posicion;
|
||||||
|
Diametro = diametro;
|
||||||
|
Apertura = Math.Clamp(apertura, 0, 1);
|
||||||
|
_worldWidth = worldWidth;
|
||||||
|
|
||||||
|
_posicionAjustada = ConvertirPosicion(posicion, worldWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza la posición ajustada después de cambiar la posición
|
||||||
|
/// </summary>
|
||||||
|
public void ActualizarPosicion(Vector2 nuevaPosicion)
|
||||||
|
{
|
||||||
|
Posicion = nuevaPosicion;
|
||||||
|
_posicionAjustada = ConvertirPosicion(nuevaPosicion, _worldWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restringe y modula el flujo de partículas a través de la válvula
|
||||||
|
/// </summary>
|
||||||
|
public override void RestringirParticula(Particle particula)
|
||||||
|
{
|
||||||
|
if (!EsActivo) return;
|
||||||
|
|
||||||
|
// Calcular distancia al centro de la válvula
|
||||||
|
Vector2 posParticula = new Vector2(particula.Position.X, particula.Position.Y);
|
||||||
|
float distancia = VectorUtils.DistanceBetween(_posicionAjustada, posParticula);
|
||||||
|
float radioValvula = Diametro * 100 / 2; // Radio en unidades internas
|
||||||
|
|
||||||
|
// Verificar si la partícula está dentro del radio de acción de la válvula
|
||||||
|
if (distancia < radioValvula * 1.2)
|
||||||
|
{
|
||||||
|
// Si la válvula está cerrada o casi cerrada, bloquear paso
|
||||||
|
if (Apertura < 0.05f)
|
||||||
|
{
|
||||||
|
// Rechazar partícula
|
||||||
|
Vector2 direccion = VectorUtils.NormalizeVector(posParticula - _posicionAjustada);
|
||||||
|
particula.Position.X = _posicionAjustada.X + direccion.X * (radioValvula * 1.2f);
|
||||||
|
particula.Position.Y = _posicionAjustada.Y + direccion.Y * (radioValvula * 1.2f);
|
||||||
|
|
||||||
|
// Reducir velocidad significativamente
|
||||||
|
particula.Velocity.X *= 0.1f;
|
||||||
|
particula.Velocity.Y *= 0.1f;
|
||||||
|
}
|
||||||
|
// Si está parcialmente abierta, reducir velocidad proporcionalmente
|
||||||
|
else if (Apertura < 0.9f)
|
||||||
|
{
|
||||||
|
// Calcular factor de reducción basado en la apertura
|
||||||
|
float factorReduccion = Apertura * Apertura; // Relación no lineal
|
||||||
|
|
||||||
|
// Aplicar resistencia proporcional a la apertura
|
||||||
|
particula.Velocity.X *= factorReduccion;
|
||||||
|
particula.Velocity.Y *= factorReduccion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
using System;
|
||||||
|
using nkast.Aether.Physics2D.Common;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion.Fluids.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utilidades para operaciones con Vector2 de nkast.Aether.Physics2D.Common
|
||||||
|
/// </summary>
|
||||||
|
public static class VectorUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Calcula la distancia entre dos vectores
|
||||||
|
/// </summary>
|
||||||
|
public static float DistanceBetween(Vector2 v1, Vector2 v2)
|
||||||
|
{
|
||||||
|
float result;
|
||||||
|
Vector2 v1Ref = v1;
|
||||||
|
Vector2 v2Ref = v2;
|
||||||
|
Vector2.Distance(ref v1Ref, ref v2Ref, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calcula el producto punto entre dos vectores
|
||||||
|
/// </summary>
|
||||||
|
public static float DotProduct(Vector2 v1, Vector2 v2)
|
||||||
|
{
|
||||||
|
float result;
|
||||||
|
Vector2 v1Ref = v1;
|
||||||
|
Vector2 v2Ref = v2;
|
||||||
|
Vector2.Dot(ref v1Ref, ref v2Ref, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normaliza un vector y retorna el resultado
|
||||||
|
/// </summary>
|
||||||
|
public static Vector2 NormalizeVector(Vector2 v)
|
||||||
|
{
|
||||||
|
Vector2 result;
|
||||||
|
float length = v.Length();
|
||||||
|
|
||||||
|
if (length < 1e-6f)
|
||||||
|
return new Vector2(0, 0);
|
||||||
|
|
||||||
|
Vector2.Divide(ref v, length, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calcula la reflexión de un vector respecto a una normal
|
||||||
|
/// </summary>
|
||||||
|
public static Vector2 Reflect(Vector2 vector, Vector2 normal)
|
||||||
|
{
|
||||||
|
Vector2 normalized = NormalizeVector(normal);
|
||||||
|
float dot;
|
||||||
|
Vector2 vectorRef = vector;
|
||||||
|
Vector2 normalizedRef = normalized;
|
||||||
|
Vector2.Dot(ref vectorRef, ref normalizedRef, out dot);
|
||||||
|
|
||||||
|
Vector2 result;
|
||||||
|
Vector2.Multiply(ref normalizedRef, 2f * dot, out result);
|
||||||
|
Vector2 vectorRef2 = vector;
|
||||||
|
Vector2 resultRef = result;
|
||||||
|
Vector2.Subtract(ref vectorRef2, ref resultRef, out result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using nkast.Aether.Physics2D.Common;
|
||||||
|
using tainicom.Aether.Physics2D.Fluids;
|
||||||
|
using CtrEditor.Simulacion.Fluids.Components;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion.Fluids
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Clase principal que gestiona la simulación de fluidos basada en FluidSystem2
|
||||||
|
/// </summary>
|
||||||
|
public class SimulacionFluidos
|
||||||
|
{
|
||||||
|
public FluidSystem2 SistemaFluido { get; private set; }
|
||||||
|
private List<IContenedorFluido> _contenedores = new List<IContenedorFluido>();
|
||||||
|
private int _worldWidth;
|
||||||
|
private int _worldHeight;
|
||||||
|
|
||||||
|
public int ParticlesCount => SistemaFluido?.ParticlesCount ?? 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor para el sistema de simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ancho">Ancho del área de simulación en metros</param>
|
||||||
|
/// <param name="alto">Alto del área de simulación en metros</param>
|
||||||
|
/// <param name="maxParticulas">Número máximo de partículas</param>
|
||||||
|
/// <param name="gravedad">Vector de gravedad</param>
|
||||||
|
public SimulacionFluidos(float ancho, float alto, int maxParticulas, Vector2 gravedad)
|
||||||
|
{
|
||||||
|
// Convertir parámetros a unidades internas de FluidSystem2
|
||||||
|
_worldWidth = (int)(ancho * 100); // Convertir a cm
|
||||||
|
_worldHeight = (int)(alto * 100); // Convertir a cm
|
||||||
|
|
||||||
|
// Inicializar el sistema
|
||||||
|
SistemaFluido = new FluidSystem2(
|
||||||
|
gravedad,
|
||||||
|
maxParticulas,
|
||||||
|
_worldWidth,
|
||||||
|
_worldHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configurar comportamiento del fluido
|
||||||
|
SistemaFluido.ElasticityEnabled = true;
|
||||||
|
SistemaFluido.PlasticityEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Método para agregar una partícula al sistema
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="posicion">Posición en metros</param>
|
||||||
|
public void AgregarParticula(Vector2 posicion)
|
||||||
|
{
|
||||||
|
// Convertir a sistema de coordenadas de FluidSystem2
|
||||||
|
float xAjustado = posicion.X * 100 - _worldWidth/2;
|
||||||
|
float yAjustado = posicion.Y * 100;
|
||||||
|
|
||||||
|
SistemaFluido.AddParticle(new Vector2(xAjustado, yAjustado));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agrega múltiples partículas en un área rectangular
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="centro">Centro del área</param>
|
||||||
|
/// <param name="ancho">Ancho del área</param>
|
||||||
|
/// <param name="alto">Alto del área</param>
|
||||||
|
/// <param name="cantidad">Cantidad de partículas a agregar</param>
|
||||||
|
public void AgregarParticulasEnArea(Vector2 centro, float ancho, float alto, int cantidad)
|
||||||
|
{
|
||||||
|
Random rnd = new Random();
|
||||||
|
for (int i = 0; i < cantidad; i++)
|
||||||
|
{
|
||||||
|
float x = centro.X - ancho/2 + (float)rnd.NextDouble() * ancho;
|
||||||
|
float y = centro.Y - alto/2 + (float)rnd.NextDouble() * alto;
|
||||||
|
AgregarParticula(new Vector2(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agrega un contenedor al sistema de fluidos
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contenedor">Implementación de IContenedorFluido</param>
|
||||||
|
public void AgregarContenedor(IContenedorFluido contenedor)
|
||||||
|
{
|
||||||
|
_contenedores.Add(contenedor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Elimina un contenedor del sistema de fluidos
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contenedor">Contenedor a eliminar</param>
|
||||||
|
public void RemoverContenedor(IContenedorFluido contenedor)
|
||||||
|
{
|
||||||
|
_contenedores.Remove(contenedor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza la simulación avanzando un paso de tiempo
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deltaTime">Tiempo transcurrido en segundos</param>
|
||||||
|
public void Actualizar(float deltaTime)
|
||||||
|
{
|
||||||
|
// Aplicar restricciones de contenedores personalizados
|
||||||
|
foreach (var contenedor in _contenedores)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < SistemaFluido.ParticlesCount; i++)
|
||||||
|
{
|
||||||
|
var particula = SistemaFluido.Particles[i];
|
||||||
|
contenedor.RestringirParticula(particula);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar la física del sistema de fluidos
|
||||||
|
SistemaFluido.Update(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene la densidad del fluido en una posición específica
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="posicion">Posición en metros</param>
|
||||||
|
/// <returns>Densidad relativa de fluido (0-1)</returns>
|
||||||
|
public float ObtenerDensidadEnPosicion(Vector2 posicion)
|
||||||
|
{
|
||||||
|
// Convertir a sistema de coordenadas de FluidSystem2
|
||||||
|
float xAjustado = posicion.X * 100 - _worldWidth/2;
|
||||||
|
float yAjustado = posicion.Y * 100;
|
||||||
|
Vector2 posAjustada = new Vector2(xAjustado, yAjustado);
|
||||||
|
|
||||||
|
float densidadTotal = 0;
|
||||||
|
int particulasCercanas = 0;
|
||||||
|
|
||||||
|
// Buscar partículas cercanas y sumar sus densidades
|
||||||
|
foreach (var p in SistemaFluido.Particles)
|
||||||
|
{
|
||||||
|
float distancia;
|
||||||
|
Vector2 posAjustadaRef = posAjustada;
|
||||||
|
Vector2 positionRef = p.Position;
|
||||||
|
Vector2.Distance(ref posAjustadaRef, ref positionRef, out distancia);
|
||||||
|
if (distancia < FluidSystem2.InfluenceRadius)
|
||||||
|
{
|
||||||
|
densidadTotal += p.Density;
|
||||||
|
particulasCercanas++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcular densidad promedio, normalizada
|
||||||
|
if (particulasCercanas > 0)
|
||||||
|
return Math.Min(1.0f, densidadTotal / (particulasCercanas * FluidSystem2.DensityRest * 1.5f));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ajusta la gravedad del sistema
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gravedad">Nuevo vector de gravedad</param>
|
||||||
|
public void AjustarGravedad(Vector2 gravedad)
|
||||||
|
{
|
||||||
|
SistemaFluido.Gravity = gravedad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limpia todas las partículas del sistema
|
||||||
|
/// </summary>
|
||||||
|
public void LimpiarParticulas()
|
||||||
|
{
|
||||||
|
// No hay método clear() directo en FluidSystem2, recreamos el sistema
|
||||||
|
SistemaFluido = new FluidSystem2(
|
||||||
|
SistemaFluido.Gravity,
|
||||||
|
SistemaFluido.MaxParticleLimit,
|
||||||
|
_worldWidth,
|
||||||
|
_worldHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restaurar configuración
|
||||||
|
SistemaFluido.ElasticityEnabled = true;
|
||||||
|
SistemaFluido.PlasticityEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interfaz para contenedores de fluido personalizados como tubos, tanques, etc.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContenedorFluido
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Aplica restricciones a una partícula para mantenerla dentro o fuera del contenedor
|
||||||
|
/// </summary>
|
||||||
|
void RestringirParticula(Particle particula);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CtrEditor.Simulacion.Fluids;
|
||||||
|
using nkast.Aether.Physics2D.Common;
|
||||||
|
using CtrEditor.ObjetosSim;
|
||||||
|
|
||||||
|
namespace CtrEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel para controlar la simulación de fluidos de forma independiente
|
||||||
|
/// </summary>
|
||||||
|
public partial class SimulationFluidsViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private MainViewModel _mainViewModel;
|
||||||
|
private readonly DispatcherTimer _timerSimulacionFluidos;
|
||||||
|
private Stopwatch _stopwatch;
|
||||||
|
private double _lastUpdateTime;
|
||||||
|
|
||||||
|
// Propiedades observables
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool isFluidSimulationRunning;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float fps;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int particulasCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mainViewModel">Referencia al ViewModel principal</param>
|
||||||
|
public SimulationFluidsViewModel(MainViewModel mainViewModel)
|
||||||
|
{
|
||||||
|
_mainViewModel = mainViewModel;
|
||||||
|
|
||||||
|
// Inicializar timer para simulación de fluidos
|
||||||
|
_timerSimulacionFluidos = new DispatcherTimer();
|
||||||
|
_timerSimulacionFluidos.Interval = TimeSpan.FromMilliseconds(16); // ~60fps
|
||||||
|
_timerSimulacionFluidos.Tick += OnTickSimulacionFluidos;
|
||||||
|
|
||||||
|
_stopwatch = new Stopwatch();
|
||||||
|
_stopwatch.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inicia la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public void StartFluidSimulation()
|
||||||
|
{
|
||||||
|
if (IsFluidSimulationRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsFluidSimulationRunning = true;
|
||||||
|
|
||||||
|
// Notificar a todos los objetos que usan fluidos que inicie su simulación
|
||||||
|
foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
|
||||||
|
{
|
||||||
|
if (objetoSimulable is osSistemaFluidos sistemaFluidos)
|
||||||
|
{
|
||||||
|
sistemaFluidos.UpdateGeometryStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastUpdateTime = _stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
_timerSimulacionFluidos.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detiene la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
public void StopFluidSimulation()
|
||||||
|
{
|
||||||
|
if (!IsFluidSimulationRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsFluidSimulationRunning = false;
|
||||||
|
_timerSimulacionFluidos.Stop();
|
||||||
|
|
||||||
|
// Notificar a todos los objetos que usan fluidos que detenga su simulación
|
||||||
|
foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
|
||||||
|
{
|
||||||
|
if (objetoSimulable is osSistemaFluidos sistemaFluidos)
|
||||||
|
{
|
||||||
|
sistemaFluidos.SimulationStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evento que se dispara cada tick de la simulación de fluidos
|
||||||
|
/// </summary>
|
||||||
|
private void OnTickSimulacionFluidos(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Calcular delta time
|
||||||
|
double currentTime = _stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
float deltaTime = (float)(currentTime - _lastUpdateTime) / 1000.0f; // convertir a segundos
|
||||||
|
_lastUpdateTime = currentTime;
|
||||||
|
|
||||||
|
int totalParticleCount = 0;
|
||||||
|
|
||||||
|
// Actualizar todos los sistemas de fluidos
|
||||||
|
foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
|
||||||
|
{
|
||||||
|
if (objetoSimulable is osSistemaFluidos sistemaFluidos && sistemaFluidos.Show_On_This_Page)
|
||||||
|
{
|
||||||
|
// Actualizar la simulación con el deltaTime calculado
|
||||||
|
if (sistemaFluidos._simFluidos != null)
|
||||||
|
{
|
||||||
|
sistemaFluidos._simFluidos.Actualizar(deltaTime);
|
||||||
|
totalParticleCount += sistemaFluidos._simFluidos.ParticlesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar controles visuales
|
||||||
|
sistemaFluidos.UpdateControl((int)(deltaTime * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar estadísticas
|
||||||
|
Fps = 1.0f / Math.Max(deltaTime, 0.001f);
|
||||||
|
ParticulasCount = totalParticleCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue