Compare commits
No commits in common. "af839f42016dcb8ae34518145eefbcaa237278b3" and "1e6ad6377e06631bc409f4c3677d87d05f0ef9b7" have entirely different histories.
af839f4201
...
1e6ad6377e
14
App.xaml
14
App.xaml
|
@ -1,14 +1,11 @@
|
|||
<Application x:Class="CtrEditor.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CtrEditor"
|
||||
xmlns:osExtraccion="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos"
|
||||
xmlns:os="clr-namespace:CtrEditor.ObjetosSim"
|
||||
xmlns:converters="clr-namespace:CtrEditor.Converters"
|
||||
StartupUri="MainWindow.xaml">
|
||||
xmlns:os="clr-namespace:CtrEditor.ObjetosSim" StartupUri="MainWindow.xaml">
|
||||
|
||||
<Application.Resources>
|
||||
<local:MeterToPixelConverter x:Key="MeterToPixelConverter" />
|
||||
<local:LevelToHeightMultiConverter x:Key="LevelToHeightMultiConverter" />
|
||||
<local:PercentageToHeightConverter x:Key="PercentageToHeightConverter" />
|
||||
<local:WidthPercentageConverter x:Key="WidthPercentageConverter" />
|
||||
<local:DistanceToMarginConverter x:Key="DistanceToMarginConverter" />
|
||||
<local:MarginConverter x:Key="MarginConverter" />
|
||||
|
@ -20,20 +17,11 @@
|
|||
<local:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
||||
<local:StringToBrushConverter x:Key="StringToBrushConverter" />
|
||||
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<local:BoolToColorConverter x:Key="BoolToColorConverter" />
|
||||
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosBuscarCoincidencias"
|
||||
TargetType="{x:Type osExtraccion:osBuscarCoincidencias}" />
|
||||
<local:SubclassFilterConverter x:Key="SubclassFilterConverterosVMMotor" TargetType="{x:Type os:osVMmotorSim}" />
|
||||
<local:UnsavedChangesConverter x:Key="UnsavedChangesConverter"/>
|
||||
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||
<converters:TankLevelToHeightConverter x:Key="TankLevelToHeightConverter"/>
|
||||
|
||||
<!-- DropShadowEffect para efectos de sombra en texto -->
|
||||
<DropShadowEffect x:Key="DropShadowEffect"
|
||||
Color="Black"
|
||||
BlurRadius="3"
|
||||
ShadowDepth="1"
|
||||
Opacity="0.5"/>
|
||||
|
||||
<!-- Estilo global para TreeViewItem para evitar errores de binding -->
|
||||
<Style TargetType="TreeViewItem">
|
||||
|
|
|
@ -193,20 +193,11 @@ namespace CtrEditor.Controls
|
|||
var newFilter = new TypeFilterItem(type)
|
||||
{
|
||||
DisplayName = GetTypeDisplayName(type),
|
||||
IsSelected = true // Nuevos tipos empiezan seleccionados por defecto
|
||||
IsSelected = true
|
||||
};
|
||||
SubscribeToTypeFilter(newFilter);
|
||||
TypeFilters.Add(newFilter);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Asegurar que los tipos existentes de componentes hidráulicos estén seleccionados
|
||||
var existingFilter = TypeFilters.FirstOrDefault(tf => tf.Type == type);
|
||||
if (existingFilter != null && IsHydraulicComponentType(type) && !existingFilter.IsSelected)
|
||||
{
|
||||
existingFilter.IsSelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -295,17 +286,6 @@ namespace CtrEditor.Controls
|
|||
methodInfo.Invoke(null, null)?.ToString() :
|
||||
type.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica si un tipo es un componente hidráulico
|
||||
/// </summary>
|
||||
private bool IsHydraulicComponentType(Type type)
|
||||
{
|
||||
// Verificar si el tipo implementa IHydraulicComponent o está en el namespace de componentes hidráulicos
|
||||
return type.Namespace != null &&
|
||||
(type.Namespace.Contains("HydraulicComponents") ||
|
||||
type.GetInterfaces().Any(i => i.Name.Contains("Hydraulic")));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace CtrEditor.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converter para calcular la altura del nivel de líquido en un tanque
|
||||
/// basado en el porcentaje de llenado y el tamaño del tanque
|
||||
/// </summary>
|
||||
public class TankLevelToHeightConverter : IMultiValueConverter
|
||||
{
|
||||
public TankLevelToHeightConverter()
|
||||
{
|
||||
Console.WriteLine("TankLevelToHeightConverter: Constructor called");
|
||||
}
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
// Debug: Registrar valores de entrada
|
||||
Console.WriteLine($"TankLevelToHeightConverter.Convert: values.Length={values?.Length ?? 0}");
|
||||
if (values != null)
|
||||
{
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
Console.WriteLine($" values[{i}]: {values[i]} (Type: {values[i]?.GetType().Name ?? "null"})");
|
||||
}
|
||||
}
|
||||
|
||||
if (values.Length != 2)
|
||||
{
|
||||
Console.WriteLine("TankLevelToHeightConverter.Convert: Returning 0.0 - wrong values count");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Convertir flexiblemente los valores a double
|
||||
if (!TryConvertToDouble(values[0], out double fillPercentage) ||
|
||||
!TryConvertToDouble(values[1], out double tankSize))
|
||||
{
|
||||
Console.WriteLine("TankLevelToHeightConverter.Convert: Conversion failed - unable to convert values to double");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Console.WriteLine($"TankLevelToHeightConverter.Convert: fillPercentage={fillPercentage}, tankSize={tankSize}");
|
||||
|
||||
try
|
||||
{
|
||||
// Calcular altura basada en el porcentaje de llenado
|
||||
// tankSize está en metros, convertir a píxeles (aproximación: 100 píxeles por metro)
|
||||
var tankHeightPixels = tankSize * 100.0;
|
||||
|
||||
// Altura del líquido = porcentaje de llenado * altura total del tanque
|
||||
var liquidHeight = (fillPercentage / 100.0) * tankHeightPixels;
|
||||
|
||||
// Aplicar margen interno (restar 8 píxeles para los márgenes del XAML)
|
||||
var availableHeight = tankHeightPixels - 8.0; // 4px arriba + 4px abajo
|
||||
var adjustedHeight = (fillPercentage / 100.0) * availableHeight;
|
||||
|
||||
// Asegurar que la altura sea positiva y no exceda el contenedor
|
||||
var result = Math.Max(0.0, Math.Min(adjustedHeight, availableHeight));
|
||||
Console.WriteLine($"TankLevelToHeightConverter.Convert: Returning {result}");
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// En caso de error, retornar 0
|
||||
Console.WriteLine($"TankLevelToHeightConverter.Convert: Exception - {ex.Message}");
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException("TankLevelToHeightConverter no soporta conversión inversa");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convierte un valor a double de manera flexible
|
||||
/// </summary>
|
||||
private static bool TryConvertToDouble(object value, out double result)
|
||||
{
|
||||
result = 0.0;
|
||||
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
if (value is double d)
|
||||
{
|
||||
result = d;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is float f)
|
||||
{
|
||||
result = f;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is int i)
|
||||
{
|
||||
result = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is decimal dec)
|
||||
{
|
||||
result = (double)dec;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Intentar conversión de string
|
||||
if (value is string str && double.TryParse(str, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -74,9 +74,6 @@
|
|||
<None Remove="imagenes\gear.png" />
|
||||
<None Remove="imagenes\motorNegro.png" />
|
||||
<None Remove="imagenes\motorVerde.png" />
|
||||
<None Remove="imagenes\pump.png" />
|
||||
<None Remove="imagenes\pump_run.png" />
|
||||
<None Remove="imagenes\pump_stop.png" />
|
||||
<None Remove="Images\base.png" />
|
||||
<None Remove="motor2.png" />
|
||||
<None Remove="tank.png" />
|
||||
|
@ -153,8 +150,6 @@
|
|||
<Resource Include="imagenes\gear.png" />
|
||||
<Resource Include="imagenes\motorNegro.png" />
|
||||
<Resource Include="imagenes\motorVerde.png" />
|
||||
<Resource Include="imagenes\pump_run.png" />
|
||||
<Resource Include="imagenes\pump_stop.png" />
|
||||
<Resource Include="imagenes\tank.png" />
|
||||
<EmbeddedResource Include="Images\base.png" />
|
||||
</ItemGroup>
|
||||
|
@ -193,6 +188,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="ObjetosSim\HydraulicSimulator\" />
|
||||
<Folder Include="paddleocr\cls\inference\" />
|
||||
<Folder Include="paddleocr\det\inference\" />
|
||||
<Folder Include="paddleocr\keys\" />
|
||||
|
|
|
@ -275,7 +275,7 @@ namespace CtrEditor
|
|||
|
||||
if (userControl != null)
|
||||
{
|
||||
UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager, _mainViewModel.hydraulicSimulationManager);
|
||||
UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager);
|
||||
osObjeto._mainViewModel = _mainViewModel;
|
||||
|
||||
if (osObjeto.Id == null)
|
||||
|
|
|
@ -1,374 +0,0 @@
|
|||
# Sistema de Componentes Hidráulicos - CtrEditor
|
||||
|
||||
## 📋 Resumen del Sistema
|
||||
|
||||
Este documento describe todos los componentes necesarios para implementar un sistema hidráulico completo en CtrEditor. El sistema está diseñado con una arquitectura modular que permite simular circuitos hidráulicos industriales complejos.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Componentes Existentes
|
||||
|
||||
### ✅ **Tanque Hidráulico** - `osHydTank`
|
||||
- **Archivo**: `osHydTank.cs` / `ucHydTank.xaml`
|
||||
- **Descripción**: Tanque hidráulico con gestión dinámica de nivel y presión configurable
|
||||
- **Tipos**: Suction, Intermediate, Storage, Process
|
||||
- **Características**:
|
||||
- Múltiples tipos de tanque via enum `HydraulicTankType`
|
||||
- Presión fija o variable (`IsFixedPressure`)
|
||||
- Gestión completa de flujos entrada/salida
|
||||
- Cálculos dinámicos de nivel y volumen
|
||||
- Propiedades visuales para UI (colores, indicadores)
|
||||
- Conexiones configurables de entrada y salida
|
||||
|
||||
### ✅ **Tubería Hidráulica** - `osHydPipe`
|
||||
- **Archivo**: `osHydPipe.cs` / `ucHydPipe.xaml`
|
||||
- **Descripción**: Tubería para transporte de fluido hidráulico
|
||||
- **Características**:
|
||||
- Cálculo de pérdidas de carga por fricción
|
||||
- Diferentes materiales y rugosidades
|
||||
- Diámetros configurables
|
||||
- Longitud variable
|
||||
- Resistencia hidráulica calculada
|
||||
|
||||
### ✅ **Bomba Hidráulica** - `osHydPump`
|
||||
- **Archivo**: `osHydPump.cs` / `ucHydPump.xaml`
|
||||
- **Descripción**: Bomba para generar presión y caudal en el sistema
|
||||
- **Tipos**: Centrifugal, Positive Displacement, Variable Displacement
|
||||
- **Características**:
|
||||
- Curvas características configurables
|
||||
- Control de velocidad variable
|
||||
- Eficiencia energética
|
||||
- Protección contra cavitación
|
||||
- Control PID integrado
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Componentes de Control (Por Implementar)
|
||||
|
||||
### **Válvulas** - `osHydValve`
|
||||
- **Archivo**: `osHydValve.cs` / `ucHydValve.xaml`
|
||||
- **Descripción**: Válvula general configurable para control de flujo
|
||||
- **Tipos**:
|
||||
- `Ball` - Válvula de bola (on/off)
|
||||
- `Gate` - Válvula de compuerta
|
||||
- `Globe` - Válvula de globo
|
||||
- `Check` - Válvula de retención
|
||||
- `Relief` - Válvula de alivio
|
||||
- `Throttle` - Válvula de estrangulación
|
||||
- **Estados**: `Open`, `Closed`, `Partial`
|
||||
- **Propiedades**:
|
||||
- Coeficiente de flujo (Cv)
|
||||
- Pérdida de presión
|
||||
- Posición del actuador
|
||||
- Tiempo de operación
|
||||
|
||||
### **Actuadores Hidráulicos** - `osHydActuator`
|
||||
- **Archivo**: `osHydActuator.cs` / `ucHydActuator.xaml`
|
||||
- **Descripción**: Cilindros y actuadores hidráulicos
|
||||
- **Tipos**:
|
||||
- `SingleAction` - Cilindro de simple efecto
|
||||
- `DoubleAction` - Cilindro de doble efecto
|
||||
- `Rotary` - Actuador rotativo
|
||||
- **Propiedades**:
|
||||
- Diámetro del pistón
|
||||
- Carrera (stroke)
|
||||
- Fuerza desarrollada
|
||||
- Velocidad de actuación
|
||||
- Posición actual
|
||||
|
||||
---
|
||||
|
||||
## 📊 Instrumentación (Por Implementar)
|
||||
|
||||
### **Sensor de Presión** - `osHydPressureSensor`
|
||||
- **Archivo**: `osHydPressureSensor.cs` / `ucHydPressureSensor.xaml`
|
||||
- **Descripción**: Medición de presión en el sistema
|
||||
- **Propiedades**:
|
||||
- Rango de medición (0-1000 bar)
|
||||
- Precisión (±0.1%)
|
||||
- Tiempo de respuesta
|
||||
- Señal de salida (4-20mA, 0-10V)
|
||||
- Calibración automática
|
||||
|
||||
### **Sensor de Caudal** - `osHydFlowSensor`
|
||||
- **Archivo**: `osHydFlowSensor.cs` / `ucHydFlowSensor.xaml`
|
||||
- **Descripción**: Medición de caudal volumétrico
|
||||
- **Tipos**: Electromagnetic, Turbine, Vortex, Ultrasonic
|
||||
- **Propiedades**:
|
||||
- Rango de caudal (L/min)
|
||||
- Pérdida de presión
|
||||
- Precisión de medición
|
||||
- Compensación de temperatura
|
||||
|
||||
### **Sensor de Nivel** - `osHydLevelSensor`
|
||||
- **Archivo**: `osHydLevelSensor.cs` / `ucHydLevelSensor.xaml`
|
||||
- **Descripción**: Medición de nivel en tanques
|
||||
- **Tipos**: Ultrasonic, Radar, Capacitive, Float
|
||||
- **Propiedades**:
|
||||
- Altura de medición
|
||||
- Zona muerta
|
||||
- Material del tanque
|
||||
- Constante dieléctrica del fluido
|
||||
|
||||
### **Sensor de Temperatura** - `osHydTemperatureSensor`
|
||||
- **Archivo**: `osHydTemperatureSensor.cs` / `ucHydTemperatureSensor.xaml`
|
||||
- **Descripción**: Medición de temperatura del fluido
|
||||
- **Tipos**: Thermocouple, RTD, Thermistor
|
||||
- **Propiedades**:
|
||||
- Rango de temperatura (-40°C a +200°C)
|
||||
- Tiempo de respuesta térmica
|
||||
- Exactitud (±0.1°C)
|
||||
- Inmersión requerida
|
||||
|
||||
---
|
||||
|
||||
## 🔀 Conexiones y Distribución (Por Implementar)
|
||||
|
||||
### **Conexión en T** - `osHydTee`
|
||||
- **Archivo**: `osHydTee.cs` / `ucHydTee.xaml`
|
||||
- **Descripción**: Conexión de tres vías para dividir flujo
|
||||
- **Propiedades**:
|
||||
- Diámetro de entrada
|
||||
- Diámetros de salida
|
||||
- Coeficiente de pérdida
|
||||
- Material de construcción
|
||||
|
||||
### **Codo 90°** - `osHydElbow`
|
||||
- **Archivo**: `osHydElbow.cs` / `ucHydElbow.xaml`
|
||||
- **Descripción**: Cambio de dirección a 90 grados
|
||||
- **Propiedades**:
|
||||
- Radio de curvatura
|
||||
- Ángulo (45°, 90°, custom)
|
||||
- Factor de pérdida K
|
||||
- Diámetro interno
|
||||
|
||||
### **Conexión Cruzada** - `osHydCross`
|
||||
- **Archivo**: `osHydCross.cs` / `ucHydCross.xaml`
|
||||
- **Descripción**: Conexión de cuatro vías
|
||||
- **Propiedades**:
|
||||
- Configuración de flujos
|
||||
- Pérdidas por turbulencia
|
||||
- Diámetros de conexión
|
||||
|
||||
### **Reductor de Diámetro** - `osHydReducer`
|
||||
- **Archivo**: `osHydReducer.cs` / `ucHydReducer.xaml`
|
||||
- **Descripción**: Cambio gradual de diámetro de tubería
|
||||
- **Tipos**: Concentric, Eccentric
|
||||
- **Propiedades**:
|
||||
- Diámetro de entrada
|
||||
- Diámetro de salida
|
||||
- Ángulo de reducción
|
||||
- Pérdida por expansión/contracción
|
||||
|
||||
### **Distribuidor Multiple** - `osHydManifold`
|
||||
- **Archivo**: `osHydManifold.cs` / `ucHydManifold.xaml`
|
||||
- **Descripción**: Distribuidor de múltiples salidas
|
||||
- **Propiedades**:
|
||||
- Número de salidas (2-12)
|
||||
- Distribución de caudal
|
||||
- Válvulas integradas opcionales
|
||||
- Presión de entrada común
|
||||
|
||||
---
|
||||
|
||||
## 🎛️ Control Avanzado (Por Implementar)
|
||||
|
||||
### **Regulador de Presión** - `osHydPressureRegulator`
|
||||
- **Archivo**: `osHydPressureRegulator.cs` / `ucHydPressureRegulator.xaml`
|
||||
- **Descripción**: Control automático de presión
|
||||
- **Propiedades**:
|
||||
- Presión de consigna (setpoint)
|
||||
- Banda proporcional
|
||||
- Control PID integrado
|
||||
- Rango de regulación
|
||||
- Precisión de control
|
||||
|
||||
### **Regulador de Caudal** - `osHydFlowRegulator`
|
||||
- **Archivo**: `osHydFlowRegulator.cs` / `ucHydFlowRegulator.xaml`
|
||||
- **Descripción**: Control automático de caudal
|
||||
- **Propiedades**:
|
||||
- Caudal de consigna
|
||||
- Compensación de presión
|
||||
- Tiempo de respuesta
|
||||
- Estabilidad de flujo
|
||||
|
||||
### **Válvula de Alivio** - `osHydPressureRelief`
|
||||
- **Archivo**: `osHydPressureRelief.cs` / `ucHydPressureRelief.xaml`
|
||||
- **Descripción**: Protección contra sobrepresión
|
||||
- **Propiedades**:
|
||||
- Presión de apertura
|
||||
- Presión de cierre
|
||||
- Capacidad de descarga
|
||||
- Tipo de pilotaje
|
||||
|
||||
---
|
||||
|
||||
## 🌡️ Gestión Térmica (Por Implementar)
|
||||
|
||||
### **Filtro Hidráulico** - `osHydFilter`
|
||||
- **Archivo**: `osHydFilter.cs` / `ucHydFilter.xaml`
|
||||
- **Descripción**: Filtración de partículas y contaminantes
|
||||
- **Tipos**:
|
||||
- `Suction` - Filtro de aspiración
|
||||
- `Return` - Filtro de retorno
|
||||
- `Pressure` - Filtro de presión
|
||||
- `Bypass` - Filtro de bypass
|
||||
- **Propiedades**:
|
||||
- Grado de filtración (micrones)
|
||||
- Capacidad de retención
|
||||
- Pérdida de presión
|
||||
- Indicador de saturación
|
||||
|
||||
### **Enfriador** - `osHydCooler`
|
||||
- **Archivo**: `osHydCooler.cs` / `ucHydCooler.xaml`
|
||||
- **Descripción**: Enfriamiento del fluido hidráulico
|
||||
- **Tipos**: Air-cooled, Water-cooled
|
||||
- **Propiedades**:
|
||||
- Capacidad de enfriamiento (kW)
|
||||
- Temperatura de entrada/salida
|
||||
- Caudal de fluido refrigerante
|
||||
- Eficiencia térmica
|
||||
|
||||
### **Calentador** - `osHydHeater`
|
||||
- **Archivo**: `osHydHeater.cs` / `ucHydHeater.xaml`
|
||||
- **Descripción**: Calentamiento del fluido hidráulico
|
||||
- **Propiedades**:
|
||||
- Potencia de calentamiento (kW)
|
||||
- Temperatura objetivo
|
||||
- Control de temperatura
|
||||
- Protección contra sobrecalentamiento
|
||||
|
||||
### **Intercambiador de Calor** - `osHydHeatExchanger`
|
||||
- **Archivo**: `osHydHeatExchanger.cs` / `ucHydHeatExchanger.xaml`
|
||||
- **Descripción**: Intercambio térmico entre fluidos
|
||||
- **Tipos**: Plate, Shell-and-tube, Coaxial
|
||||
- **Propiedades**:
|
||||
- Coeficiente de transferencia térmica
|
||||
- Área de intercambio
|
||||
- Configuración de flujos
|
||||
- Eficiencia térmica
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Componentes Especializados (Por Implementar)
|
||||
|
||||
### **Acumulador Hidráulico** - `osHydAccumulator`
|
||||
- **Archivo**: `osHydAccumulator.cs` / `ucHydAccumulator.xaml`
|
||||
- **Descripción**: Almacenamiento de energía hidráulica
|
||||
- **Tipos**:
|
||||
- `Bladder` - Membrana elastomérica
|
||||
- `Piston` - Pistón separador
|
||||
- `Diaphragm` - Diafragma metálico
|
||||
- **Propiedades**:
|
||||
- Volumen total
|
||||
- Presión de precarga
|
||||
- Volumen útil
|
||||
- Tiempo de descarga
|
||||
|
||||
### **Motor Hidráulico** - `osHydMotor`
|
||||
- **Archivo**: `osHydMotor.cs` / `ucHydMotor.xaml`
|
||||
- **Descripción**: Conversión de energía hidráulica a mecánica rotativa
|
||||
- **Tipos**:
|
||||
- `Gear` - Motor de engranajes
|
||||
- `Vane` - Motor de paletas
|
||||
- `Piston` - Motor de pistones
|
||||
- `Radial` - Motor radial
|
||||
- **Propiedades**:
|
||||
- Cilindrada (cm³/rev)
|
||||
- Velocidad nominal (rpm)
|
||||
- Par motor (Nm)
|
||||
- Eficiencia volumétrica/mecánica
|
||||
|
||||
---
|
||||
|
||||
## 📋 Plan de Implementación
|
||||
|
||||
### **Fase 1 - Componentes Básicos** ⭐⭐⭐
|
||||
1. `osHydValve` (Ball, Check, Relief)
|
||||
2. `osHydPressureSensor`
|
||||
3. `osHydFlowSensor`
|
||||
4. `osHydTee`
|
||||
|
||||
### **Fase 2 - Control Intermedio** ⭐⭐
|
||||
5. `osHydActuator`
|
||||
6. `osHydPressureRegulator`
|
||||
7. `osHydFilter`
|
||||
8. `osHydManifold`
|
||||
|
||||
### **Fase 3 - Componentes Avanzados** ⭐
|
||||
9. `osHydAccumulator`
|
||||
10. `osHydMotor`
|
||||
11. `osHydCooler`
|
||||
12. `osHydHeatExchanger`
|
||||
|
||||
### **Fase 4 - Instrumentación Completa**
|
||||
13. `osHydLevelSensor`
|
||||
14. `osHydTemperatureSensor`
|
||||
15. `osHydFlowRegulator`
|
||||
16. Componentes de conexión restantes
|
||||
|
||||
---
|
||||
|
||||
## 💡 Arquitectura de Interfaces
|
||||
|
||||
### **Interfaces Base**
|
||||
```csharp
|
||||
// Control de flujo
|
||||
public interface IHydraulicFlowController
|
||||
{
|
||||
double FlowCoefficient { get; set; }
|
||||
double PressureDrop { get; }
|
||||
ValveState CurrentState { get; set; }
|
||||
}
|
||||
|
||||
// Actuadores
|
||||
public interface IHydraulicActuator
|
||||
{
|
||||
double Force { get; }
|
||||
double Stroke { get; set; }
|
||||
double Velocity { get; }
|
||||
ActuatorType Type { get; }
|
||||
}
|
||||
|
||||
// Sensores
|
||||
public interface IHydraulicSensor
|
||||
{
|
||||
double MeasuredValue { get; }
|
||||
string Units { get; }
|
||||
bool IsCalibrated { get; set; }
|
||||
double Accuracy { get; }
|
||||
}
|
||||
|
||||
// Componentes térmicos
|
||||
public interface IHydraulicThermalComponent
|
||||
{
|
||||
double ThermalCapacity { get; }
|
||||
double HeatTransferCoefficient { get; }
|
||||
double OperatingTemperature { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Objetivos del Sistema
|
||||
|
||||
- **Modularidad**: Cada componente es independiente y reutilizable
|
||||
- **Realismo**: Cálculos basados en principios de ingeniería hidráulica
|
||||
- **Flexibilidad**: Configuración adaptable a diferentes aplicaciones
|
||||
- **Integración**: Compatible con el sistema de simulación existente
|
||||
- **Escalabilidad**: Fácil adición de nuevos componentes
|
||||
- **Documentación**: Cada componente bien documentado y ejemplificado
|
||||
|
||||
---
|
||||
|
||||
## 📚 Referencias Técnicas
|
||||
|
||||
- ISO 1219-1: Símbolos gráficos para esquemas de circuitos hidráulicos
|
||||
- ISO 4413: Transmisiones hidráulicas - Reglas generales y requisitos de seguridad
|
||||
- NFPA T3.5.17: Hidráulica industrial - Estándares de filtración
|
||||
- API 610: Bombas centrífugas para servicios de refinería petroquímica
|
||||
|
||||
---
|
||||
|
||||
*Documento generado automáticamente - CtrEditor v2024*
|
||||
*Última actualización: Septiembre 2025*
|
|
@ -118,354 +118,3 @@ if (velocity.Linear.Z > maxUpwardVelocity) {
|
|||
- **Solución integral**: Resuelve tanto el problema de elevación como el de contactos múltiples simultáneos
|
||||
- **Código más simple**: Elimina la complejidad del sistema de tracking de densidad de contactos
|
||||
|
||||
# Sistema Hidráulico Integrado con HydraulicSimulationManager
|
||||
|
||||
## Arquitectura del Sistema Hidráulico
|
||||
|
||||
Se ha implementado un sistema de simulación hidráulica completo que funciona en paralelo con la simulación física BEPU. El sistema está basado en el `HydraulicSimulationManager` que actúa como puente entre los objetos gráficos derivados de `osBase` y el motor de simulación hidráulica.
|
||||
|
||||
### Componentes Principales
|
||||
|
||||
#### HydraulicSimulationManager
|
||||
- **Función**: Gestiona el ciclo de vida y la comunicación entre objetos hidráulicos y el solver
|
||||
- **Integración**: Se ejecuta en paralelo con BEPU en el timer loop principal de MainViewModel
|
||||
- **Responsabilidades**:
|
||||
- Registro automático de objetos que implementan `IHydraulicComponent`
|
||||
- Construcción dinámica de la red hidráulica
|
||||
- Aplicación de resultados a los objetos tras cada iteración del solver
|
||||
|
||||
#### Interfaces Hidráulicas
|
||||
- **IHydraulicComponent**: Interfaz base para todos los objetos hidráulicos
|
||||
- **IHydraulicPump**: Para bombas con curvas H-Q
|
||||
- **IHydraulicPipe**: Para tuberías con pérdidas por fricción
|
||||
- **IHydraulicTank**: Para tanques con cálculo de nivel dinámico
|
||||
- **IHydraulicValve**: Para válvulas con coeficiente Kv
|
||||
|
||||
## Mejoras Implementadas en Objetos Hidráulicos
|
||||
|
||||
### 1. Sistema de Conexiones Mejorado en osHydPipe
|
||||
|
||||
**Problema Original**: El sistema de conexiones era limitado y no seguía el patrón establecido en otros objetos del sistema.
|
||||
|
||||
**Solución Implementada**:
|
||||
```csharp
|
||||
// Antes - Sistema limitado
|
||||
[ObservableProperty] string id_InletComponent = "";
|
||||
[ObservableProperty] string id_OutletComponent = "";
|
||||
|
||||
// Después - Patrón consistente
|
||||
[ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
|
||||
[ObservableProperty] string id_ComponenteA = "";
|
||||
|
||||
[ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
|
||||
[ObservableProperty] string id_ComponenteB = "";
|
||||
```
|
||||
|
||||
**Beneficios**:
|
||||
- **Filtrado automático**: Solo muestra objetos que implementan `IHydraulicComponent` en el PropertyGrid
|
||||
- **Consistencia**: Sigue el mismo patrón usado en `ucTransporteGuias` con `Motor`
|
||||
- **Mantenimiento automático**: PropertyChangedHandlers mantienen sincronizados los nombres
|
||||
- **Validación**: Solo crea elementos hidráulicos cuando ambos componentes están conectados
|
||||
|
||||
### 2. Lógica de Nodos Corregida
|
||||
|
||||
**Problema Original**: Las tuberías intentaban crear sus propios nodos, generando duplicación y conflictos.
|
||||
|
||||
**Solución Implementada**:
|
||||
```csharp
|
||||
// Las tuberías no crean nodos propios
|
||||
public List<HydraulicNodeDefinition> GetHydraulicNodes()
|
||||
{
|
||||
// Los nodos son creados por los componentes conectados (tanques, bombas, etc.)
|
||||
return new List<HydraulicNodeDefinition>();
|
||||
}
|
||||
|
||||
// Solo conectan nodos existentes
|
||||
public List<HydraulicElementDefinition> GetHydraulicElements()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
|
||||
{
|
||||
var pipeElement = new Pipe(Length, Diameter, Roughness);
|
||||
// Conecta directamente usando nombres de componentes como nodos
|
||||
return new List<HydraulicElementDefinition> {
|
||||
new HydraulicElementDefinition($"{Nombre}_Pipe", Id_ComponenteA, Id_ComponenteB, pipeElement)
|
||||
};
|
||||
}
|
||||
return new List<HydraulicElementDefinition>();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Integración Completa con ItemsSource
|
||||
|
||||
**En osHydDischargeTank**:
|
||||
```csharp
|
||||
[Category("🔗 Conexiones")]
|
||||
[Name("Componente Entrada")]
|
||||
[ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
|
||||
[ObservableProperty] string id_InletComponent = "";
|
||||
```
|
||||
|
||||
## Flujo de Simulación Hidráulica
|
||||
|
||||
### 1. Registro Automático
|
||||
- Los objetos que implementan `IHydraulicComponent` se registran automáticamente en `HydraulicSimulationManager`
|
||||
- El sistema detecta cuando hay cambios y marca la red para reconstrucción (`_networkNeedsRebuild = true`)
|
||||
|
||||
### 2. Construcción de Red
|
||||
- **BuildNodesFromObjects()**: Crea nodos basándose en las definiciones de cada objeto
|
||||
- **BuildBranchesFromObjects()**: Crea elementos (tuberías, bombas, válvulas) que conectan los nodos
|
||||
- La red resultante es compatible con el solver `HydraulicNetwork.Solve()`
|
||||
|
||||
### 3. Resolución y Aplicación
|
||||
- **UpdateObjectProperties()**: Actualiza propiedades antes del solver (apertura de válvulas, velocidad de bombas, etc.)
|
||||
- **Network.Solve()**: Resuelve el sistema de ecuaciones usando el algoritmo iterativo
|
||||
- **ApplyResultsToObjects()**: Aplica caudales y presiones calculados a cada objeto
|
||||
|
||||
## Patrón de Conexión Establecido
|
||||
|
||||
El sistema establecido para conexiones sigue este patrón consistente:
|
||||
|
||||
```csharp
|
||||
// 1. Propiedad string con ItemsSource filtrado
|
||||
[ItemsSource(typeof(osBaseItemsSource<IInterfaceEspecifica>))]
|
||||
[ObservableProperty] string id_ComponenteConectado = "";
|
||||
|
||||
// 2. Referencia privada al objeto real
|
||||
[JsonIgnore] private IInterfaceEspecifica ComponenteConectado = null;
|
||||
|
||||
// 3. PropertyChangedHandler para mantener sincronización
|
||||
[JsonIgnore] private PropertyChangedEventHandler componentePropertyChangedHandler;
|
||||
|
||||
// 4. Método OnChanged que maneja la conexión
|
||||
partial void OnId_ComponenteConectadoChanged(string value)
|
||||
{
|
||||
// Desconectar anterior si existe
|
||||
if (ComponenteConectado != null && componentePropertyChangedHandler != null)
|
||||
((INotifyPropertyChanged)ComponenteConectado).PropertyChanged -= componentePropertyChangedHandler;
|
||||
|
||||
// Buscar y conectar nuevo componente
|
||||
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
|
||||
{
|
||||
ComponenteConectado = (IInterfaceEspecifica)_mainViewModel.ObjetosSimulables
|
||||
.FirstOrDefault(s => s is IInterfaceEspecifica comp &&
|
||||
((osBase)comp).Nombre == value);
|
||||
|
||||
// Establecer handler para sincronización de nombres
|
||||
if (ComponenteConectado != null)
|
||||
{
|
||||
componentePropertyChangedHandler = (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(osBase.Nombre))
|
||||
Id_ComponenteConectado = ((osBase)sender).Nombre;
|
||||
};
|
||||
((INotifyPropertyChanged)ComponenteConectado).PropertyChanged += componentePropertyChangedHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Beneficios del Sistema Integrado
|
||||
|
||||
- **Doble simulación**: Simulación física (BEPU) y hidráulica funcionan en paralelo sin interferencia
|
||||
- **Interfaces unificadas**: Todos los objetos hidráulicos implementan interfaces consistentes
|
||||
- **Red dinámica**: La red hidráulica se reconstruye automáticamente cuando cambian las conexiones
|
||||
- **Resultados bidireccionales**: Los resultados hidráulicos pueden influir en la simulación física y viceversa
|
||||
- **Patrón consistente**: Todas las conexiones siguen el mismo patrón establecido
|
||||
- **Mantenimiento automático**: Los nombres y referencias se mantienen sincronizados automáticamente
|
||||
|
||||
## Resolución de Problemas de Simulación Hidráulica - Bomba con Caudal Cero
|
||||
|
||||
### Problema Identificado
|
||||
La bomba hidráulica mostraba velocidad=1.0 y funcionando=true pero registraba 0 caudal y 0 presión constantemente.
|
||||
|
||||
### Causas Encontradas
|
||||
1. **Doble registro de objetos**: Los objetos hidráulicos se registraban dos veces - una al cargar cada objeto individualmente y otra al ejecutar `RegisterLoadedHydraulicObjects()` después de cargar el proyecto completo.
|
||||
|
||||
2. **Unidades incorrectas en MaxFlow**: La configuración JSON tenía `MaxFlow: 10.0` pero el código manejaba valores en m³/s. El valor 10.0 se interpretaba como 10 m³/s (36,000 m³/h) en lugar de 10 m³/h.
|
||||
|
||||
3. **Falta de información de debugging**: El `VerboseOutput` estaba desactivado, ocultando información crítica sobre la construcción de la red y convergencia del solver.
|
||||
|
||||
### Soluciones Implementadas
|
||||
|
||||
#### 1. Prevención de Doble Registro
|
||||
Modificado `RegisterHydraulicObjectIfNeeded()` en `MainViewModel.cs` para verificar si el objeto ya está registrado antes de agregarlo:
|
||||
```csharp
|
||||
if (!hydraulicSimulationManager.HydraulicObjects.Contains(obj))
|
||||
{
|
||||
hydraulicSimulationManager.RegisterHydraulicObject(obj);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Corrección de Unidades MaxFlow
|
||||
Implementado en `osHydPump.cs` conversión automática de unidades para `MaxFlow`:
|
||||
- Si el valor > 1.0, se asume que está en m³/h y se convierte a m³/s (dividiendo por 3600)
|
||||
- Se agregó logging detallado mostrando ambas unidades para verificación
|
||||
|
||||
#### 3. Activación de Verbose Output
|
||||
- Habilitado `VerboseOutput = true` por defecto en `HydraulicSimulationManager`
|
||||
- Agregado logging detallado de construcción de red mostrando todos los nodos y ramas
|
||||
- Implementado logging periódico de resultados (cada 5 segundos aprox.) mostrando flujos y presiones
|
||||
- Mejorado logging de errores de convergencia con detalles de iteraciones y residual
|
||||
|
||||
#### 4. Debugging Mejorado
|
||||
Los logs ahora muestran:
|
||||
- Detalles completos de nodos (presión fija/libre, valores de presión)
|
||||
- Ramas con elementos y tipos de componentes
|
||||
- Resultados de flujos en m³/s y m³/h
|
||||
- Presiones en Pa y bar
|
||||
- Estado de convergencia del solver con detalles de error
|
||||
|
||||
### Corrección Final: Convergencia del Solver Hidráulico
|
||||
|
||||
#### Problema de Convergencia
|
||||
El solver no convergía porque el sistema carecía de suficientes nodos de presión fija:
|
||||
- La bomba tenía nodos de entrada y salida libres
|
||||
- Solo el tanque de descarga tenía presión fija
|
||||
- El solver necesita al menos dos puntos de referencia para sistemas complejos
|
||||
|
||||
#### Solución Implementada
|
||||
|
||||
**1. Nodo de Succión en la Bomba**
|
||||
```csharp
|
||||
// En GetHydraulicNodes() de osHydPump
|
||||
double suctionPressure = 101325.0; // Pa (1 atm)
|
||||
nodes.Add(new HydraulicNodeDefinition($"{Nombre}_In", true, suctionPressure, "Entrada de la bomba (succión)"));
|
||||
```
|
||||
|
||||
**2. Parámetros Optimizados del Solver**
|
||||
- MaxIterations: 200 → 300
|
||||
- Tolerance: 1e-4 → 1e-3 (más relajada para convergencia inicial)
|
||||
- RelaxationFactor: 0.8 (mantiene estabilidad)
|
||||
|
||||
**3. Sistema de Referencias Hidráulicas**
|
||||
- Nodo de succión: 101325 Pa (presión atmosférica)
|
||||
- Nodo de descarga: 101325 + TankPressure Pa (atmosférica + hidrostática)
|
||||
- Esto simula un sistema real con tanque de succión y descarga
|
||||
|
||||
### Corrección Final: Problema del DataContext en UI
|
||||
|
||||
#### Problema Identificado
|
||||
Aunque la simulación funcionaba correctamente y los valores se calculaban bien, estos no se mostraban en el UserControl de la bomba porque:
|
||||
- El UserControl tenía su propio `DataContext` definido en el XAML
|
||||
- Esto creaba una instancia separada de `osHydPump` solo para el UI
|
||||
- Los valores se actualizaban en el objeto real, pero el UI mostraba el objeto falso
|
||||
|
||||
#### Solución Implementada
|
||||
```xml
|
||||
<!-- Eliminado el DataContext problemático -->
|
||||
<!-- <UserControl.DataContext>
|
||||
<vm:osHydPump ImageSource_oculta="/imagenes/pump_run.png" />
|
||||
</UserControl.DataContext> -->
|
||||
|
||||
<!-- DataContext se establece desde el objeto padre, no aquí -->
|
||||
```
|
||||
|
||||
### Resultado Final del Sistema Hidráulico
|
||||
Sistema hidráulico completamente funcional:
|
||||
- ✅ Convergencia estable del solver con residuales < 1e-3
|
||||
- ✅ Registro único de objetos (sin duplicados)
|
||||
- ✅ Conversión automática de unidades (m³/h ↔ m³/s)
|
||||
- ✅ Logging detallado para monitoreo y debugging
|
||||
- ✅ Bomba genera flujo y presión reales según configuración
|
||||
- ✅ **UserControl muestra valores actualizados en tiempo real**
|
||||
|
||||
## Nuevo Tanque Hidráulico Avanzado (osHydTank)
|
||||
|
||||
### Arquitectura Modular Implementada
|
||||
|
||||
Se siguió el enfoque arquitectónico de **"Tanques como Terminales + Pipes como Conectores"** para crear un sistema más realista y mantenible:
|
||||
|
||||
```
|
||||
[Tanque Succión] → [Pipe] → [Bomba] → [Pipe] → [Válvula] → [Pipe] → [Tanque Descarga]
|
||||
```
|
||||
|
||||
### Características del osHydTank
|
||||
|
||||
**Gestión Dinámica de Nivel:**
|
||||
- Nivel calculado en base a flujos de entrada y salida
|
||||
- Seguimiento de volumen actual vs. máximo/mínimo
|
||||
- Indicadores visuales de estado del tanque
|
||||
- Detección automática de desbordamiento y vaciado
|
||||
|
||||
**Presión Configurable:**
|
||||
- Presión independiente del nivel (simulando PID externo)
|
||||
- Opción de presión fija o variable
|
||||
- Diferentes tipos de tanque: Succión, Intermedio, Almacenamiento, Proceso
|
||||
|
||||
**Interfaces Hidráulicas:**
|
||||
- `IHydraulicComponent`: Integración con red hidráulica
|
||||
- `IHydraulicFlowReceiver`: Recepción de flujos calculados
|
||||
- `IHydraulicPressureReceiver`: Actualización de presiones
|
||||
|
||||
**UserControl Avanzado:**
|
||||
- Visualización en tiempo real del nivel de líquido
|
||||
- Indicadores de tipo de tanque y conexiones
|
||||
- Balance de flujo con códigos de color
|
||||
- Conversión automática de TankLevelToHeightConverter
|
||||
|
||||
## Solución de Problemas de Visualización osHydTank
|
||||
|
||||
### Problema
|
||||
El componente `osHydTank` no se mostraba correctamente en el canvas a pesar de registrarse exitosamente en el sistema hidráulico. Los logs mostraban errores de binding en el MultiBinding del rectángulo de nivel del líquido.
|
||||
|
||||
### Análisis
|
||||
1. **Error de Converter**: El XAML usaba `TankLevelToHeightConverter` que tenía incompatibilidades de tipos
|
||||
2. **Falta de inicialización**: El UserControl `ucHydTank` no llamaba a `ucLoaded()`/`ucUnLoaded()` como otros componentes
|
||||
3. **Binding incorrecto**: No se convertía `Tamano` a píxeles antes del MultiBinding
|
||||
|
||||
### Solución Implementada
|
||||
1. **Cambio de Converter**: Reemplazamos `TankLevelToHeightConverter` por `LevelToHeightMultiConverter` (que funciona en otros componentes)
|
||||
2. **Conversión a Píxeles**: Agregamos `MeterToPixelConverter` a `Tamano` antes del MultiBinding
|
||||
3. **Ciclo de Vida**: Implementamos las llamadas correctas a `ucLoaded()`/`ucUnLoaded()` en `ucHydTank.xaml.cs`
|
||||
|
||||
### Cambios Técnicos
|
||||
- **XAML**: `<Binding Path="Tamano" Converter="{StaticResource MeterToPixelConverter}"/>`
|
||||
- **UserControl**: Agregado `Unloaded += OnUserControlUnloaded` y llamadas a `ucLoaded()`/`ucUnLoaded()`
|
||||
- **Converter Corregido**: `LevelToHeightMultiConverter` ahora usa `System.Convert.ToDouble()` para flexibilidad de tipos
|
||||
- **Patrón consistente** con `ucHydPump`, `ucHydDischargeTank` y `ucHydPipe`
|
||||
|
||||
### Detalle del Error del Converter
|
||||
El `LevelToHeightMultiConverter` esperaba tipos específicos (`float` y `double`) pero recibía tipos invertidos:
|
||||
- `FillPercentage`: `double` (esperaba `float`)
|
||||
- `MeterToPixelConverter` output: `float` (esperaba `double`)
|
||||
|
||||
**Solución**: Implementar función `TryConvertToDouble()` robusta que maneja:
|
||||
- Valores especiales de WPF (`DependencyProperty.UnsetValue`, `MS.Internal.NamedObject`)
|
||||
- Múltiples tipos numéricos (`double`, `float`, `int`, `decimal`)
|
||||
- Conversión segura de strings
|
||||
- Verificación `IConvertible` antes de usar `System.Convert.ToDouble()`
|
||||
|
||||
### Validación de Arquitectura
|
||||
|
||||
El sistema ahora requiere que:
|
||||
- Todo circuito hidráulico **inicie con un tanque**
|
||||
- Todo circuito hidráulico **termine con un tanque**
|
||||
- Los `pipes` sean los únicos conectores entre componentes
|
||||
- Cada componente tenga **responsabilidad única**
|
||||
|
||||
### Beneficios del Nuevo Diseño
|
||||
|
||||
**Realismo Físico:**
|
||||
- Refleja sistemas hidráulicos reales donde tanques son puntos de referencia
|
||||
- Presión independiente del nivel (como en sistemas industriales)
|
||||
- Gestión de volumen dinámica
|
||||
|
||||
**Escalabilidad:**
|
||||
- Patrón consistente para nuevos componentes hidráulicos
|
||||
- Fácil conexión de válvulas, filtros, intercambiadores de calor
|
||||
- Sistema preparado para redes complejas
|
||||
|
||||
**Mantenibilidad:**
|
||||
- Principio de responsabilidad única por componente
|
||||
- Interfaces claras y bien definidas
|
||||
- Debugging simplificado con logging detallado
|
||||
|
||||
**Flexibilidad:**
|
||||
- Diferentes tipos de tanques para diferentes funciones
|
||||
- Configuración de presión independiente
|
||||
- Conectividad modular via pipes
|
||||
|
||||
La bomba ahora debe mostrar correctamente:
|
||||
- **Presión actual** en bar (ej: "1.0 bar")
|
||||
- **Caudal actual** en L/min (ej: "165.8 L/min")
|
||||
|
||||
|
|
|
@ -1,950 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using HydraulicSimulator.Models;
|
||||
using CtrEditor.ObjetosSim;
|
||||
|
||||
namespace CtrEditor.HydraulicSimulator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gestiona la simulación hidráulica similar a SimulationManagerBEPU
|
||||
/// </summary>
|
||||
public class HydraulicSimulationManager : IDisposable
|
||||
{
|
||||
#region Properties and Fields
|
||||
|
||||
/// <summary>
|
||||
/// Red hidráulica principal que contiene todos los nodos y ramas
|
||||
/// </summary>
|
||||
public HydraulicNetwork Network { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Lista de objetos simulables que tienen componentes hidráulicos
|
||||
/// </summary>
|
||||
public List<osBase> HydraulicObjects { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fluido utilizado en la simulación (agua por defecto)
|
||||
/// </summary>
|
||||
public Fluid SimulationFluid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resultado de la última simulación
|
||||
/// </summary>
|
||||
public SolutionResult LastSolutionResult { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tiempo global de la simulación hidráulica
|
||||
/// </summary>
|
||||
public float GlobalTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si la simulación hidráulica está habilitada
|
||||
/// </summary>
|
||||
public bool IsHydraulicSimulationEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indica si la verificación de NPSH está habilitada
|
||||
/// </summary>
|
||||
public bool EnableNPSHVerification { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Parámetros del solver
|
||||
/// </summary>
|
||||
public int MaxIterations { get; set; } = 300;
|
||||
public double Tolerance { get; set; } = 1e-3; // Tolerancia más relajada para convergencia inicial
|
||||
public double RelaxationFactor { get; set; } = 0.8;
|
||||
public bool VerboseOutput { get; set; } = false; // Activado para debugging
|
||||
|
||||
/// <summary>
|
||||
/// Contador de pasos de simulación para optimizaciones
|
||||
/// </summary>
|
||||
private int _stepCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Cronómetro para medir tiempo de ejecución
|
||||
/// </summary>
|
||||
private Stopwatch _stopwatch;
|
||||
|
||||
/// <summary>
|
||||
/// Diccionario para mapear IDs de objetos a elementos hidráulicos
|
||||
/// </summary>
|
||||
private Dictionary<string, osBase> _objectMapping;
|
||||
|
||||
/// <summary>
|
||||
/// Indica si hay cambios pendientes en la red que requieren reconstrucción
|
||||
/// </summary>
|
||||
private bool _networkNeedsRebuild = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Constructor del gestor de simulación hidráulica
|
||||
/// </summary>
|
||||
public HydraulicSimulationManager()
|
||||
{
|
||||
// Inicializar componentes
|
||||
SimulationFluid = Fluid.Water20C; // Agua a 20°C por defecto
|
||||
Network = new HydraulicNetwork(SimulationFluid);
|
||||
HydraulicObjects = new List<osBase>();
|
||||
_objectMapping = new Dictionary<string, osBase>();
|
||||
_stopwatch = new Stopwatch();
|
||||
|
||||
// Inicializar resultado vacío
|
||||
LastSolutionResult = new SolutionResult(false)
|
||||
{
|
||||
ErrorMessage = "Simulación no ejecutada"
|
||||
};
|
||||
|
||||
GlobalTime = 0.0f;
|
||||
|
||||
Debug.WriteLine("HydraulicSimulationManager inicializado");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Management
|
||||
|
||||
/// <summary>
|
||||
/// Registra un objeto simulable que tiene componentes hidráulicos
|
||||
/// </summary>
|
||||
/// <param name="hydraulicObject">Objeto con componentes hidráulicos</param>
|
||||
public void RegisterHydraulicObject(osBase hydraulicObject)
|
||||
{
|
||||
if (hydraulicObject == null)
|
||||
return;
|
||||
|
||||
if (!HydraulicObjects.Contains(hydraulicObject))
|
||||
{
|
||||
HydraulicObjects.Add(hydraulicObject);
|
||||
_objectMapping[hydraulicObject.Nombre] = hydraulicObject;
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
// Debug.WriteLine($"Objeto hidráulico registrado: {hydraulicObject.Nombre}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Desregistra un objeto simulable hidráulico
|
||||
/// </summary>
|
||||
/// <param name="hydraulicObject">Objeto a desregistrar</param>
|
||||
public void UnregisterHydraulicObject(osBase hydraulicObject)
|
||||
{
|
||||
if (hydraulicObject == null)
|
||||
return;
|
||||
|
||||
if (HydraulicObjects.Remove(hydraulicObject))
|
||||
{
|
||||
_objectMapping.Remove(hydraulicObject.Nombre);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
// Debug.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limpia todos los objetos hidráulicos registrados
|
||||
/// </summary>
|
||||
public void ClearHydraulicObjects()
|
||||
{
|
||||
HydraulicObjects.Clear();
|
||||
_objectMapping.Clear();
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
// Debug.WriteLine("Todos los objetos hidráulicos han sido limpiados");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Network Building
|
||||
|
||||
/// <summary>
|
||||
/// Reconstruye la red hidráulica basada en los objetos registrados
|
||||
/// </summary>
|
||||
private void RebuildNetwork()
|
||||
{
|
||||
if (!_networkNeedsRebuild)
|
||||
return;
|
||||
|
||||
// Debug.WriteLine("Reconstruyendo red hidráulica...");
|
||||
|
||||
// Crear nueva red
|
||||
Network = new HydraulicNetwork(SimulationFluid);
|
||||
|
||||
// Agregar nodos y elementos basados en los objetos hidráulicos
|
||||
BuildNodesFromObjects();
|
||||
BuildBranchesFromObjects();
|
||||
|
||||
_networkNeedsRebuild = false;
|
||||
|
||||
// Debug.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas");
|
||||
|
||||
// Verbose output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine("=== DETALLES DE LA RED HIDRÁULICA ===");
|
||||
Debug.WriteLine("Nodos:");
|
||||
foreach (var node in Network.Nodes)
|
||||
{
|
||||
string pressureInfo = node.Value.FixedP ? $"Presión fija: {node.Value.P:F0} Pa" : "Presión libre";
|
||||
Debug.WriteLine($" - {node.Key}: {pressureInfo}");
|
||||
}
|
||||
Debug.WriteLine("Ramas:");
|
||||
foreach (var branch in Network.Branches)
|
||||
{
|
||||
Debug.WriteLine($" - {branch.Name}: {branch.N1} -> {branch.N2} ({branch.Elements.Count} elementos)");
|
||||
foreach (var element in branch.Elements)
|
||||
{
|
||||
Debug.WriteLine($" * {element.GetType().Name}");
|
||||
}
|
||||
}
|
||||
Debug.WriteLine("=== FIN DETALLES RED ===");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construye los nodos de la red basándose en los objetos hidráulicos
|
||||
/// </summary>
|
||||
private void BuildNodesFromObjects()
|
||||
{
|
||||
foreach (var obj in HydraulicObjects)
|
||||
{
|
||||
// Verificar si el objeto implementa la interfaz hidráulica
|
||||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
// Obtener nodos definidos por el objeto
|
||||
var nodeDefinitions = hydraulicComponent.GetHydraulicNodes();
|
||||
|
||||
foreach (var nodeDef in nodeDefinitions)
|
||||
{
|
||||
// Agregar nodo a la red
|
||||
Network.AddNode(nodeDef.Name, nodeDef.IsFixedPressure ? nodeDef.Pressure : null);
|
||||
|
||||
// Verbose output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine($"Nodo agregado: {nodeDef.Name} " +
|
||||
$"(Presión fija: {nodeDef.IsFixedPressure}, " +
|
||||
$"Presión: {nodeDef.Pressure?.ToString() ?? "libre"})");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Objeto sin interfaz hidráulica - crear nodos básicos por compatibilidad
|
||||
string nodeName = $"Node_{obj.Nombre}";
|
||||
bool isFixedPressure = DetermineIfFixedPressure(obj);
|
||||
double? pressure = isFixedPressure ? GetObjectPressure(obj) : null;
|
||||
|
||||
Network.AddNode(nodeName, pressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construye las ramas (conexiones) entre nodos
|
||||
/// </summary>
|
||||
private void BuildBranchesFromObjects()
|
||||
{
|
||||
foreach (var obj in HydraulicObjects)
|
||||
{
|
||||
// Verificar si el objeto implementa la interfaz hidráulica
|
||||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
// Obtener elementos definidos por el objeto
|
||||
var elementDefinitions = hydraulicComponent.GetHydraulicElements();
|
||||
|
||||
foreach (var elemDef in elementDefinitions)
|
||||
{
|
||||
// Crear rama con el elemento
|
||||
var elements = new List<Element> { elemDef.Element };
|
||||
Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name);
|
||||
|
||||
// Verbose output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine($"Rama agregada: {elemDef.Name} " +
|
||||
$"({elemDef.FromNode} -> {elemDef.ToNode})");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Objeto sin interfaz hidráulica - crear elementos básicos por compatibilidad
|
||||
var elements = CreateHydraulicElementsFromObject(obj);
|
||||
|
||||
if (elements.Any())
|
||||
{
|
||||
string fromNode = GetSourceNode(obj);
|
||||
string toNode = GetTargetNode(obj);
|
||||
|
||||
if (!string.IsNullOrEmpty(fromNode) && !string.IsNullOrEmpty(toNode))
|
||||
{
|
||||
Network.AddBranch(fromNode, toNode, elements, $"Branch_{obj.Nombre}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods for Object Analysis
|
||||
|
||||
/// <summary>
|
||||
/// Determina si un objeto debe tener presión fija
|
||||
/// </summary>
|
||||
private bool DetermineIfFixedPressure(osBase obj)
|
||||
{
|
||||
// Implementación que será extendida con tipos específicos
|
||||
// Por ejemplo: tanques, descargas atmosféricas = presión fija
|
||||
// Uniones, conexiones = presión libre
|
||||
|
||||
return false; // Por defecto, presión libre
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene la presión de un objeto (si tiene presión fija)
|
||||
/// </summary>
|
||||
private double GetObjectPressure(osBase obj)
|
||||
{
|
||||
// Implementación que será extendida
|
||||
return 0.0; // Por defecto, presión atmosférica
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea elementos hidráulicos basándose en un objeto
|
||||
/// </summary>
|
||||
private List<Element> CreateHydraulicElementsFromObject(osBase obj)
|
||||
{
|
||||
var elements = new List<Element>();
|
||||
|
||||
// Implementación que será extendida con tipos específicos
|
||||
// Por ejemplo:
|
||||
// - Si es una bomba: crear PumpHQ
|
||||
// - Si es una tubería: crear Pipe
|
||||
// - Si es una válvula: crear ValveKv
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el nodo fuente de un objeto
|
||||
/// </summary>
|
||||
private string GetSourceNode(osBase obj)
|
||||
{
|
||||
// Implementación que será extendida
|
||||
return $"Node_{obj.Nombre}_In";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el nodo destino de un objeto
|
||||
/// </summary>
|
||||
private string GetTargetNode(osBase obj)
|
||||
{
|
||||
// Implementación que será extendida
|
||||
return $"Node_{obj.Nombre}_Out";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Simulation Step
|
||||
|
||||
/// <summary>
|
||||
/// Ejecuta un paso de simulación hidráulica (equivalente al Step de BEPU)
|
||||
/// </summary>
|
||||
public void Step(float deltaTime = 0.016f) // ~60 FPS por defecto
|
||||
{
|
||||
if (!IsHydraulicSimulationEnabled)
|
||||
return;
|
||||
|
||||
_stopwatch.Restart();
|
||||
|
||||
try
|
||||
{
|
||||
// Actualizar tiempo global
|
||||
GlobalTime += deltaTime;
|
||||
_stepCount++;
|
||||
|
||||
// Reconstruir red si es necesario
|
||||
RebuildNetwork();
|
||||
|
||||
// Solo resolver si tenemos objetos hidráulicos
|
||||
if (HydraulicObjects.Count > 0 && Network.Branches.Count > 0)
|
||||
{
|
||||
// Actualizar propiedades de objetos antes de resolver
|
||||
UpdateObjectProperties();
|
||||
|
||||
// Resolver la red hidráulica
|
||||
LastSolutionResult = Network.Solve(
|
||||
MaxIterations,
|
||||
Tolerance,
|
||||
RelaxationFactor,
|
||||
VerboseOutput
|
||||
);
|
||||
|
||||
if (LastSolutionResult.Converged)
|
||||
{
|
||||
// Aplicar resultados a los objetos
|
||||
ApplyResultsToObjects();
|
||||
|
||||
// Verbose output de resultados deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput && _stepCount % 300 == 0) // Log cada 5 segundos aprox
|
||||
{
|
||||
//Debug.WriteLine("=== RESULTADOS SIMULACIÓN HIDRÁULICA ===");
|
||||
//Debug.WriteLine("Flujos:");
|
||||
foreach (var flow in LastSolutionResult.Flows)
|
||||
{
|
||||
Debug.WriteLine($" {flow.Key}: {flow.Value:F6} m³/s ({flow.Value * 3600:F2} m³/h)");
|
||||
}
|
||||
Debug.WriteLine("Presiones:");
|
||||
foreach (var pressure in LastSolutionResult.Pressures)
|
||||
{
|
||||
Debug.WriteLine($" {pressure.Key}: {pressure.Value:F0} Pa ({pressure.Value / 100000:F2} bar)");
|
||||
}
|
||||
Debug.WriteLine("=== FIN RESULTADOS ===");
|
||||
}
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}");
|
||||
Debug.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}");
|
||||
Debug.WriteLine($" Tolerancia requerida: {Tolerance:E6}");
|
||||
|
||||
// Diagnóstico detallado deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput && _stepCount % 60 == 0) // Log detallado cada segundo aprox
|
||||
{
|
||||
Debug.WriteLine("=== DIAGNÓSTICO CONVERGENCIA ===");
|
||||
Debug.WriteLine($"Nodos en red: {Network.Nodes.Count}");
|
||||
Debug.WriteLine($"Ramas en red: {Network.Branches.Count}");
|
||||
foreach (var node in Network.Nodes)
|
||||
{
|
||||
string info = node.Value.FixedP ? $"FIJA={node.Value.P:F0}Pa" : "LIBRE";
|
||||
Debug.WriteLine($" Nodo {node.Key}: {info}");
|
||||
}
|
||||
Debug.WriteLine("=== FIN DIAGNÓSTICO ===");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sin objetos hidráulicos, resultado exitoso pero vacío
|
||||
LastSolutionResult = new SolutionResult(true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LastSolutionResult = new SolutionResult(false)
|
||||
{
|
||||
ErrorMessage = $"Error en simulación hidráulica: {ex.Message}"
|
||||
};
|
||||
|
||||
Debug.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_stopwatch.Stop();
|
||||
|
||||
// Logging de tiempo deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput && _stepCount % 60 == 0) // Log cada segundo aprox
|
||||
{
|
||||
Debug.WriteLine($"Simulación hidráulica - Paso {_stepCount}: {_stopwatch.ElapsedMilliseconds}ms, " +
|
||||
$"Objetos: {HydraulicObjects.Count}, Convergió: {LastSolutionResult.Converged}");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza las propiedades de los objetos antes de resolver la red
|
||||
/// </summary>
|
||||
private void UpdateObjectProperties()
|
||||
{
|
||||
// Actualizar objetos hidráulicos específicos
|
||||
foreach (var simObj in HydraulicSimObjects)
|
||||
{
|
||||
simObj.UpdateProperties();
|
||||
}
|
||||
|
||||
// Actualizar objetos hidráulicos generales (legacy)
|
||||
foreach (var obj in HydraulicObjects)
|
||||
{
|
||||
// Aquí se implementará la actualización de propiedades específicas
|
||||
// Por ejemplo: estado de válvulas, velocidad de bombas, etc.
|
||||
UpdateObjectHydraulicProperties(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza las propiedades hidráulicas específicas de un objeto
|
||||
/// </summary>
|
||||
private void UpdateObjectHydraulicProperties(osBase obj)
|
||||
{
|
||||
// Actualizar propiedades basándose en las interfaces implementadas
|
||||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
// Llamar al método de actualización del objeto
|
||||
hydraulicComponent.UpdateHydraulicProperties();
|
||||
}
|
||||
|
||||
// Actualización específica por tipo de objeto
|
||||
if (obj is IHydraulicPump pump)
|
||||
{
|
||||
// Verificar estado de la bomba, velocidad, etc.
|
||||
UpdatePumpProperties(pump);
|
||||
}
|
||||
else if (obj is IHydraulicValve valve)
|
||||
{
|
||||
// Verificar apertura de la válvula
|
||||
UpdateValveProperties(valve);
|
||||
}
|
||||
else if (obj is IHydraulicTank tank)
|
||||
{
|
||||
// Actualizar nivel y presión del tanque
|
||||
UpdateTankProperties(tank);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza propiedades específicas de bombas
|
||||
/// </summary>
|
||||
private void UpdatePumpProperties(IHydraulicPump pump)
|
||||
{
|
||||
// Aquí se pueden hacer validaciones y ajustes específicos de bombas
|
||||
// Por ejemplo, limitar velocidad, verificar estado de operación, etc.
|
||||
|
||||
if (pump.SpeedRatio < 0.0) pump.SpeedRatio = 0.0;
|
||||
if (pump.SpeedRatio > 1.0) pump.SpeedRatio = 1.0;
|
||||
|
||||
// Debug output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " +
|
||||
$"Funcionando={pump.IsRunning}, Dirección={pump.PumpDirection}");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza propiedades específicas de válvulas
|
||||
/// </summary>
|
||||
private void UpdateValveProperties(IHydraulicValve valve)
|
||||
{
|
||||
// Validar apertura de válvula
|
||||
if (valve.Opening < 0.0) valve.Opening = 0.0;
|
||||
if (valve.Opening > 1.0) valve.Opening = 1.0;
|
||||
|
||||
// Debug output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine($"Válvula {valve.GetType().Name}: Apertura={valve.Opening:F2}, " +
|
||||
$"Cerrada={valve.IsClosed}, Abierta={valve.IsFullyOpen}");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza propiedades específicas de tanques
|
||||
/// </summary>
|
||||
private void UpdateTankProperties(IHydraulicTank tank)
|
||||
{
|
||||
// Calcular presión basada en nivel si no es presión fija
|
||||
if (!tank.IsFixedPressure)
|
||||
{
|
||||
// P = ρgh + P_atmosferica
|
||||
double pressureFromLevel = SimulationFluid.Rho * 9.80665 * tank.Level; // + presión atmosférica
|
||||
tank.TankPressure = pressureFromLevel;
|
||||
}
|
||||
|
||||
// Debug output deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " +
|
||||
$"Presión={tank.TankPressure:F0}Pa, PresionFija={tank.IsFixedPressure}");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aplica los resultados de la simulación a los objetos
|
||||
/// </summary>
|
||||
private void ApplyResultsToObjects()
|
||||
{
|
||||
// Aplicar resultados a objetos hidráulicos específicos
|
||||
foreach (var simObj in HydraulicSimObjects)
|
||||
{
|
||||
ApplyResultsToSimObject(simObj);
|
||||
simObj.ApplySimulationResults();
|
||||
}
|
||||
|
||||
// Aplicar resultados a objetos hidráulicos generales (legacy)
|
||||
foreach (var obj in HydraulicObjects)
|
||||
{
|
||||
// Aplicar caudales y presiones calculados al objeto
|
||||
ApplyResultsToObject(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aplica los resultados de la simulación a un objeto específico
|
||||
/// </summary>
|
||||
private void ApplyResultsToSimObject(simHydraulicBase simObj)
|
||||
{
|
||||
// Aplicar resultados específicos según el tipo de objeto
|
||||
if (simObj is simHydraulicPump pump)
|
||||
{
|
||||
// Buscar flujo y presión de la bomba en los resultados
|
||||
string pumpBranchName = $"{pump.Nombre}_Pump";
|
||||
if (LastSolutionResult.Flows.TryGetValue(pumpBranchName, out double flow))
|
||||
{
|
||||
pump.CurrentFlow = flow;
|
||||
}
|
||||
|
||||
string inletNodeName = $"{pump.Nombre}_In";
|
||||
if (LastSolutionResult.Pressures.TryGetValue(inletNodeName, out double pressure))
|
||||
{
|
||||
pump.CurrentPressure = pressure;
|
||||
}
|
||||
}
|
||||
else if (simObj is simHydraulicTank tank)
|
||||
{
|
||||
// Buscar flujos de entrada y salida del tanque
|
||||
// Esto requeriría mapeo de tuberías conectadas
|
||||
// Por ahora, aplicar presión del tanque
|
||||
string tankNodeName = $"{tank.Nombre}_Tank";
|
||||
if (LastSolutionResult.Pressures.TryGetValue(tankNodeName, out double pressure))
|
||||
{
|
||||
tank.CurrentPressure = pressure;
|
||||
}
|
||||
}
|
||||
else if (simObj is simHydraulicPipe pipe)
|
||||
{
|
||||
// Buscar flujo a través de la tubería
|
||||
string pipeBranchName = $"{pipe.Nombre}_Pipe";
|
||||
if (LastSolutionResult.Flows.TryGetValue(pipeBranchName, out double flow))
|
||||
{
|
||||
pipe.CurrentFlow = flow;
|
||||
}
|
||||
|
||||
// Calcular pérdida de presión
|
||||
string fromNode = pipe.ComponenteAId;
|
||||
string toNode = pipe.ComponenteBId;
|
||||
if (LastSolutionResult.Pressures.TryGetValue(fromNode, out double pressureA) &&
|
||||
LastSolutionResult.Pressures.TryGetValue(toNode, out double pressureB))
|
||||
{
|
||||
pipe.PressureDrop = pressureA - pressureB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aplica los resultados a un objeto específico
|
||||
/// </summary>
|
||||
private void ApplyResultsToObject(osBase obj)
|
||||
{
|
||||
// Aplicar resultados usando las interfaces hidráulicas
|
||||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
// Llamar al método de aplicación de resultados del objeto
|
||||
hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures);
|
||||
}
|
||||
|
||||
// Aplicación específica por tipo de interfaz
|
||||
if (obj is IHydraulicFlowReceiver flowReceiver)
|
||||
{
|
||||
// Buscar caudal asociado al objeto
|
||||
string branchName = FindBranchNameForObject(obj);
|
||||
if (!string.IsNullOrEmpty(branchName) && LastSolutionResult.Flows.ContainsKey(branchName))
|
||||
{
|
||||
double flow = LastSolutionResult.Flows[branchName];
|
||||
flowReceiver.SetFlow(flow);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj is IHydraulicPressureReceiver pressureReceiver)
|
||||
{
|
||||
// Buscar presión asociada al objeto
|
||||
string nodeName = FindNodeNameForObject(obj);
|
||||
if (!string.IsNullOrEmpty(nodeName) && LastSolutionResult.Pressures.ContainsKey(nodeName))
|
||||
{
|
||||
double pressure = LastSolutionResult.Pressures[nodeName];
|
||||
pressureReceiver.SetPressure(pressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encuentra el nombre de rama asociado a un objeto
|
||||
/// </summary>
|
||||
private string FindBranchNameForObject(osBase obj)
|
||||
{
|
||||
// Buscar en las ramas de la red
|
||||
foreach (var branch in Network.Branches)
|
||||
{
|
||||
if (branch.Name.Contains(obj.Nombre))
|
||||
{
|
||||
return branch.Name;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback a nombre por defecto
|
||||
return $"Branch_{obj.Nombre}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encuentra el nombre de nodo asociado a un objeto
|
||||
/// </summary>
|
||||
private string FindNodeNameForObject(osBase obj)
|
||||
{
|
||||
// Buscar en los nodos de la red
|
||||
foreach (var nodeKvp in Network.Nodes)
|
||||
{
|
||||
if (nodeKvp.Key.Contains(obj.Nombre))
|
||||
{
|
||||
return nodeKvp.Key;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback a nombre por defecto
|
||||
return $"Node_{obj.Nombre}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Establece el caudal en un objeto
|
||||
/// </summary>
|
||||
private void SetObjectFlow(osBase obj, double flow)
|
||||
{
|
||||
if (obj is IHydraulicFlowReceiver flowReceiver)
|
||||
{
|
||||
flowReceiver.SetFlow(flow);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Establece la presión en un objeto
|
||||
/// </summary>
|
||||
private void SetObjectPressure(osBase obj, double pressure)
|
||||
{
|
||||
if (obj is IHydraulicPressureReceiver pressureReceiver)
|
||||
{
|
||||
pressureReceiver.SetPressure(pressure);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Fuerza la reconstrucción de la red en el próximo paso
|
||||
/// </summary>
|
||||
public void InvalidateNetwork()
|
||||
{
|
||||
_networkNeedsRebuild = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene estadísticas de la simulación
|
||||
/// </summary>
|
||||
public string GetSimulationStats()
|
||||
{
|
||||
return $"Objetos hidráulicos: {HydraulicObjects.Count}\n" +
|
||||
$"Nodos: {Network.Nodes.Count}\n" +
|
||||
$"Ramas: {Network.Branches.Count}\n" +
|
||||
$"Última simulación convergió: {LastSolutionResult.Converged}\n" +
|
||||
$"Iteraciones: {LastSolutionResult.Iterations}\n" +
|
||||
$"Residual: {LastSolutionResult.Residual:E3}\n" +
|
||||
$"Tiempo global: {GlobalTime:F2}s\n" +
|
||||
$"Pasos: {_stepCount}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reinicia la simulación
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
GlobalTime = 0.0f;
|
||||
_stepCount = 0;
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
// Reinicializar resultado
|
||||
LastSolutionResult = new SolutionResult(false)
|
||||
{
|
||||
ErrorMessage = "Simulación reiniciada"
|
||||
};
|
||||
|
||||
Debug.WriteLine("Simulación hidráulica reiniciada");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configura los parámetros de verificación de NPSH
|
||||
/// </summary>
|
||||
public void ConfigureNPSHSettings(bool enableNPSH, double npshRequired = 3.0, double vaporPressure = 2337.0, double suctionLosses = 0.5)
|
||||
{
|
||||
EnableNPSHVerification = enableNPSH;
|
||||
|
||||
// Actualizar todas las bombas existentes con los nuevos parámetros
|
||||
foreach (var obj in HydraulicObjects)
|
||||
{
|
||||
if (obj is osHydPump pump)
|
||||
{
|
||||
// Los parámetros se aplicarán cuando se reconstruya la red
|
||||
InvalidateNetwork();
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Verificación NPSH {(enableNPSH ? "habilitada" : "deshabilitada")}: NPSH_req={npshRequired}m, P_vapor={vaporPressure}Pa");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Libera recursos del gestor de simulación
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Limpiar recursos gestionados
|
||||
HydraulicObjects?.Clear();
|
||||
_objectMapping?.Clear();
|
||||
Network = null;
|
||||
_stopwatch?.Stop();
|
||||
|
||||
Debug.WriteLine("HydraulicSimulationManager disposed");
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hydraulic Object Creation
|
||||
|
||||
/// <summary>
|
||||
/// Lista de objetos hidráulicos específicos del simulador
|
||||
/// </summary>
|
||||
public List<simHydraulicBase> HydraulicSimObjects { get; private set; } = new List<simHydraulicBase>();
|
||||
|
||||
/// <summary>
|
||||
/// Crea una bomba hidráulica en la simulación
|
||||
/// </summary>
|
||||
public simHydraulicPump AddPump(double pumpHead, double maxFlow, double speedRatio, bool isRunning, int direction)
|
||||
{
|
||||
var pump = new simHydraulicPump(this)
|
||||
{
|
||||
PumpHead = pumpHead,
|
||||
MaxFlow = maxFlow,
|
||||
SpeedRatio = speedRatio,
|
||||
IsRunning = isRunning,
|
||||
PumpDirection = direction
|
||||
};
|
||||
|
||||
HydraulicSimObjects.Add(pump);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Debug.WriteLine($"Bomba hidráulica creada: H={pumpHead}m, Q={maxFlow}m³/s");
|
||||
return pump;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea un tanque hidráulico en la simulación
|
||||
/// </summary>
|
||||
public simHydraulicTank AddTank(double pressure, double currentLevel, double maxLevel, double minLevel, double crossSectionalArea, bool isFixedPressure)
|
||||
{
|
||||
var tank = new simHydraulicTank(this)
|
||||
{
|
||||
TankPressure = pressure,
|
||||
CurrentLevel = currentLevel,
|
||||
MaxLevel = maxLevel,
|
||||
MinLevel = minLevel,
|
||||
CrossSectionalArea = crossSectionalArea,
|
||||
IsFixedPressure = isFixedPressure
|
||||
};
|
||||
|
||||
HydraulicSimObjects.Add(tank);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Debug.WriteLine($"Tanque hidráulico creado: P={pressure}Pa, Nivel={currentLevel}m");
|
||||
return tank;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea una tubería hidráulica en la simulación
|
||||
/// </summary>
|
||||
public simHydraulicPipe AddPipe(double length, double diameter, double roughness, string componenteAId, string componenteBId)
|
||||
{
|
||||
var pipe = new simHydraulicPipe(this)
|
||||
{
|
||||
Length = length,
|
||||
Diameter = diameter,
|
||||
Roughness = roughness,
|
||||
ComponenteAId = componenteAId,
|
||||
ComponenteBId = componenteBId
|
||||
};
|
||||
|
||||
HydraulicSimObjects.Add(pipe);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Debug.WriteLine($"Tubería hidráulica creada: L={length}m, D={diameter}m");
|
||||
return pipe;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remueve un objeto hidráulico de la simulación
|
||||
/// </summary>
|
||||
public void Remove(simHydraulicBase hydraulicObject)
|
||||
{
|
||||
if (hydraulicObject != null && HydraulicSimObjects.Contains(hydraulicObject))
|
||||
{
|
||||
HydraulicSimObjects.Remove(hydraulicObject);
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Debug.WriteLine($"Objeto hidráulico removido: {hydraulicObject.SimObjectType}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limpia todos los objetos hidráulicos específicos
|
||||
/// </summary>
|
||||
public void ClearHydraulicSimObjects()
|
||||
{
|
||||
HydraulicSimObjects.Clear();
|
||||
_networkNeedsRebuild = true;
|
||||
|
||||
Debug.WriteLine("Todos los objetos hidráulicos específicos han sido limpiados");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
using HydraulicSimulator.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CtrEditor.HydraulicSimulator
|
||||
{
|
||||
/// <summary>
|
||||
/// Interfaz base para objetos que tienen componentes hidráulicos
|
||||
/// </summary>
|
||||
public interface IHydraulicComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Indica si este objeto tiene componentes hidráulicos activos
|
||||
/// </summary>
|
||||
bool HasHydraulicComponents { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene los nodos hidráulicos asociados a este objeto
|
||||
/// </summary>
|
||||
/// <returns>Lista de definiciones de nodos</returns>
|
||||
List<HydraulicNodeDefinition> GetHydraulicNodes();
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene los elementos hidráulicos asociados a este objeto
|
||||
/// </summary>
|
||||
/// <returns>Lista de definiciones de elementos hidráulicos</returns>
|
||||
List<HydraulicElementDefinition> GetHydraulicElements();
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza las propiedades hidráulicas del objeto antes de la simulación
|
||||
/// </summary>
|
||||
void UpdateHydraulicProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Aplica los resultados de la simulación hidráulica al objeto
|
||||
/// </summary>
|
||||
/// <param name="flows">Caudales calculados</param>
|
||||
/// <param name="pressures">Presiones calculadas</param>
|
||||
void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interfaz para objetos que pueden recibir información de caudal
|
||||
/// </summary>
|
||||
public interface IHydraulicFlowReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Establece el caudal actual del objeto
|
||||
/// </summary>
|
||||
/// <param name="flow">Caudal en m³/s</param>
|
||||
void SetFlow(double flow);
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el caudal actual del objeto
|
||||
/// </summary>
|
||||
/// <returns>Caudal en m³/s</returns>
|
||||
double GetFlow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interfaz para objetos que pueden recibir información de presión
|
||||
/// </summary>
|
||||
public interface IHydraulicPressureReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Establece la presión actual del objeto
|
||||
/// </summary>
|
||||
/// <param name="pressure">Presión en Pa</param>
|
||||
void SetPressure(double pressure);
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene la presión actual del objeto
|
||||
/// </summary>
|
||||
/// <returns>Presión en Pa</returns>
|
||||
double GetPressure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interfaz para objetos que son bombas hidráulicas
|
||||
/// </summary>
|
||||
public interface IHydraulicPump : IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Cabeza de la bomba a caudal cero (m)
|
||||
/// </summary>
|
||||
double PumpHead { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Caudal máximo de la bomba (m³/s)
|
||||
/// </summary>
|
||||
double MaxFlow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Velocidad relativa de la bomba (0.0 a 1.0)
|
||||
/// </summary>
|
||||
double SpeedRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si la bomba está encendida
|
||||
/// </summary>
|
||||
bool IsRunning { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dirección de la bomba (+1 o -1)
|
||||
/// </summary>
|
||||
int PumpDirection { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interfaz para objetos que son válvulas hidráulicas
|
||||
/// </summary>
|
||||
public interface IHydraulicValve : IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Coeficiente Kv de la válvula completamente abierta (m³/h/√bar)
|
||||
/// </summary>
|
||||
double KvFull { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Apertura actual de la válvula (0.0 a 1.0)
|
||||
/// </summary>
|
||||
double Opening { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si la válvula está completamente cerrada
|
||||
/// </summary>
|
||||
bool IsClosed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si la válvula está completamente abierta
|
||||
/// </summary>
|
||||
bool IsFullyOpen { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interfaz para objetos que son tuberías hidráulicas
|
||||
/// </summary>
|
||||
public interface IHydraulicPipe : IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Longitud de la tubería (m)
|
||||
/// </summary>
|
||||
double Length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Diámetro interno de la tubería (m)
|
||||
/// </summary>
|
||||
double Diameter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rugosidad absoluta de la tubería (m)
|
||||
/// </summary>
|
||||
double Roughness { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interfaz para objetos que son tanques hidráulicos
|
||||
/// </summary>
|
||||
public interface IHydraulicTank : IHydraulicComponent, IHydraulicPressureReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Presión del tanque (Pa)
|
||||
/// </summary>
|
||||
double TankPressure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nivel actual del tanque (m)
|
||||
/// </summary>
|
||||
double Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Área de la sección transversal del tanque (m²)
|
||||
/// </summary>
|
||||
double CrossSectionalArea { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si el tanque tiene presión fija
|
||||
/// </summary>
|
||||
bool IsFixedPressure { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definición de un nodo hidráulico
|
||||
/// </summary>
|
||||
public class HydraulicNodeDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Nombre único del nodo
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si el nodo tiene presión fija
|
||||
/// </summary>
|
||||
public bool IsFixedPressure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Presión del nodo (Pa), si es de presión fija
|
||||
/// </summary>
|
||||
public double? Pressure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Descripción del nodo
|
||||
/// </summary>
|
||||
public string Description { get; set; } = "";
|
||||
|
||||
public HydraulicNodeDefinition(string name, bool isFixedPressure = false, double? pressure = null, string description = "")
|
||||
{
|
||||
Name = name;
|
||||
IsFixedPressure = isFixedPressure;
|
||||
Pressure = pressure;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definición de un elemento hidráulico
|
||||
/// </summary>
|
||||
public class HydraulicElementDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Nombre único del elemento
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nodo de origen
|
||||
/// </summary>
|
||||
public string FromNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nodo de destino
|
||||
/// </summary>
|
||||
public string ToNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Elemento hidráulico
|
||||
/// </summary>
|
||||
public Element Element { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Descripción del elemento
|
||||
/// </summary>
|
||||
public string Description { get; set; } = "";
|
||||
|
||||
public HydraulicElementDefinition(string name, string fromNode, string toNode, Element element, string description = "")
|
||||
{
|
||||
Name = name;
|
||||
FromNode = fromNode;
|
||||
ToNode = toNode;
|
||||
Element = element;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tipos de elementos hidráulicos
|
||||
/// </summary>
|
||||
public enum HydraulicElementType
|
||||
{
|
||||
Pipe,
|
||||
Pump,
|
||||
Valve,
|
||||
MinorLoss,
|
||||
Tank,
|
||||
Junction
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuración de un elemento hidráulico
|
||||
/// </summary>
|
||||
public class HydraulicElementConfiguration
|
||||
{
|
||||
public HydraulicElementType Type { get; set; }
|
||||
public Dictionary<string, object> Parameters { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
public HydraulicElementConfiguration(HydraulicElementType type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Establece un parámetro de configuración
|
||||
/// </summary>
|
||||
public void SetParameter(string key, object value)
|
||||
{
|
||||
Parameters[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene un parámetro de configuración
|
||||
/// </summary>
|
||||
public T GetParameter<T>(string key, T defaultValue = default(T))
|
||||
{
|
||||
if (Parameters.ContainsKey(key) && Parameters[key] is T)
|
||||
{
|
||||
return (T)Parameters[key];
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -68,29 +68,6 @@ namespace HydraulicSimulator.Models
|
|||
Branches.Add(new Branch(n1, n2, elements, name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza las presiones en bombas con verificación de NPSH
|
||||
/// </summary>
|
||||
private void UpdatePumpPressures()
|
||||
{
|
||||
var currentPressures = new Dictionary<string, double>();
|
||||
foreach (var kvp in Nodes)
|
||||
{
|
||||
currentPressures[kvp.Key] = kvp.Value.P;
|
||||
}
|
||||
|
||||
foreach (var branch in Branches)
|
||||
{
|
||||
foreach (var element in branch.Elements)
|
||||
{
|
||||
if (element is PumpHQWithSuctionCheck pumpWithCheck)
|
||||
{
|
||||
pumpWithCheck.UpdatePressures(currentPressures);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SolutionResult Solve(int maxIterations = 100, double tolerance = 1e-3,
|
||||
double relaxationFactor = 0.1, bool verbose = false)
|
||||
{
|
||||
|
@ -125,9 +102,6 @@ namespace HydraulicSimulator.Models
|
|||
// Iteración global sobre presiones nodales
|
||||
for (it = 0; it < maxIterations; it++)
|
||||
{
|
||||
// Actualizar presiones en bombas con verificación de NPSH
|
||||
UpdatePumpPressures();
|
||||
|
||||
// 1) con presiones actuales, resolvés q de cada rama
|
||||
foreach (var b in Branches)
|
||||
{
|
||||
|
@ -176,9 +150,8 @@ namespace HydraulicSimulator.Models
|
|||
}
|
||||
|
||||
normR = R.Length > 0 ? R.Max(Math.Abs) : 0.0;
|
||||
// Console output deshabilitado para mejorar rendimiento
|
||||
// if (verbose)
|
||||
// Console.WriteLine($"it {it}: |R|_inf={normR:E3}");
|
||||
if (verbose)
|
||||
Console.WriteLine($"it {it}: |R|_inf={normR:E3}");
|
||||
|
||||
if (normR < tolerance)
|
||||
break;
|
||||
|
@ -321,8 +294,6 @@ namespace HydraulicSimulator.Models
|
|||
/// </summary>
|
||||
public void Report()
|
||||
{
|
||||
// Reporte deshabilitado para mejorar rendimiento
|
||||
/*
|
||||
Console.WriteLine("== Nodos (Pa) ==");
|
||||
foreach (var kvp in Nodes)
|
||||
{
|
||||
|
@ -336,7 +307,6 @@ namespace HydraulicSimulator.Models
|
|||
{
|
||||
Console.WriteLine($"{b.Name,15}: {b.Q,10:E6}");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace HydraulicSimulator.Models
|
|||
{
|
||||
/// <summary>
|
||||
/// Bomba con curva H(Q)=H0*(1-(Q/Q0)²) y ley de afinidad con velocidad relativa
|
||||
/// Incluye verificación de NPSH y condiciones de succión
|
||||
/// </summary>
|
||||
public class PumpHQ : Element
|
||||
{
|
||||
|
@ -13,15 +12,6 @@ namespace HydraulicSimulator.Models
|
|||
public double SpeedRel { get; set; } = 1.0; // n / n_nominal
|
||||
public int Direction { get; set; } = 1; // +1 si impulsa de i->j, -1 si al revés
|
||||
|
||||
// Propiedades para verificación de NPSH
|
||||
public double NPSHRequired { get; set; } = 3.0; // m, NPSH requerido típico
|
||||
public double VaporPressure { get; set; } = 2337.0; // Pa, presión de vapor del agua a 20°C
|
||||
public double SuctionLosses { get; set; } = 0.5; // m, pérdidas en la succión
|
||||
|
||||
// Referencias a las presiones de los nodos para verificación
|
||||
public string InletNodeName { get; set; }
|
||||
public string OutletNodeName { get; set; }
|
||||
|
||||
public PumpHQ(double h0, double q0, double speedRel = 1.0, int direction = 1)
|
||||
{
|
||||
H0 = h0;
|
||||
|
@ -39,215 +29,22 @@ namespace HydraulicSimulator.Models
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula el NPSH disponible basado en la presión de succión
|
||||
/// </summary>
|
||||
public double CalculateNPSHAvailable(double suctionPressure, Fluid fluid)
|
||||
{
|
||||
// NPSH disponible = (Presión absoluta de succión - Presión de vapor) / (ρ * g) - Pérdidas
|
||||
var npshAvailable = (suctionPressure - VaporPressure) / (fluid.Rho * 9.80665) - SuctionLosses;
|
||||
return Math.Max(0, npshAvailable); // No puede ser negativo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica si la bomba puede operar sin cavitación
|
||||
/// </summary>
|
||||
public bool CanOperateWithoutCavitation(double suctionPressure, Fluid fluid)
|
||||
{
|
||||
var npshAvailable = CalculateNPSHAvailable(suctionPressure, fluid);
|
||||
return npshAvailable >= NPSHRequired;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula el factor de reducción por cavitación (0 = cavitación total, 1 = sin cavitación)
|
||||
/// </summary>
|
||||
public double GetCavitationFactor(double suctionPressure, Fluid fluid)
|
||||
{
|
||||
var npshAvailable = CalculateNPSHAvailable(suctionPressure, fluid);
|
||||
var ratio = npshAvailable / NPSHRequired;
|
||||
|
||||
if (ratio >= 1.0) return 1.0; // Sin cavitación
|
||||
if (ratio <= 0.1) return 0.0; // Cavitación severa
|
||||
|
||||
// Transición suave entre 0.1 y 1.0
|
||||
return Math.Pow(ratio, 2); // Curva cuadrática para transición suave
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica si la bomba puede superar la presión de descarga
|
||||
/// </summary>
|
||||
public bool CanOvercomeDischargePressure(double suctionPressure, double dischargePressure, Fluid fluid)
|
||||
{
|
||||
var (h0s, _) = Scaled;
|
||||
var maxPressureRise = h0s * fluid.Rho * 9.80665; // Máxima presión que puede agregar la bomba
|
||||
var requiredPressureRise = dischargePressure - suctionPressure;
|
||||
|
||||
return maxPressureRise >= requiredPressureRise;
|
||||
}
|
||||
|
||||
public override double Dp(double q, Fluid fluid)
|
||||
{
|
||||
var (h0s, q0s) = Scaled;
|
||||
|
||||
// Si la velocidad es muy baja o la bomba está apagada, no genera presión
|
||||
if (SpeedRel < 0.01)
|
||||
return 0.0;
|
||||
|
||||
// Limitamos fuera de rango para estabilidad
|
||||
var qq = Math.Max(-q0s * 0.999, Math.Min(q0s * 0.999, q));
|
||||
var h = h0s * (1.0 - Math.Pow(qq / q0s, 2));
|
||||
|
||||
// Calcular presión diferencial base
|
||||
var dpBase = -Direction * fluid.Rho * 9.80665 * h;
|
||||
|
||||
// Aplicar factor de cavitación si tenemos información de presión de succión
|
||||
// Nota: Esto requiere que el simulador pase las presiones de los nodos
|
||||
// Por ahora, asumimos operación normal, pero el factor se aplicará en el simulador
|
||||
|
||||
return dpBase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Versión mejorada de Dp que considera presiones de succión y descarga
|
||||
/// </summary>
|
||||
public double DpWithSuctionCheck(double q, Fluid fluid, double suctionPressure, double dischargePressure)
|
||||
{
|
||||
var (h0s, q0s) = Scaled;
|
||||
|
||||
// Si la velocidad es muy baja o la bomba está apagada, no genera presión
|
||||
if (SpeedRel < 0.01)
|
||||
return 0.0;
|
||||
|
||||
// Verificar si puede superar la presión de descarga
|
||||
if (!CanOvercomeDischargePressure(suctionPressure, dischargePressure, fluid))
|
||||
{
|
||||
// La bomba no puede vencer la presión de descarga, flujo cero o negativo
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Verificar NPSH y aplicar factor de cavitación
|
||||
var cavitationFactor = GetCavitationFactor(suctionPressure, fluid);
|
||||
|
||||
if (cavitationFactor < 0.1)
|
||||
{
|
||||
// Cavitación severa, bomba no puede operar efectivamente
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Limitamos fuera de rango para estabilidad
|
||||
var qq = Math.Max(-q0s * 0.999, Math.Min(q0s * 0.999, q));
|
||||
var h = h0s * (1.0 - Math.Pow(qq / q0s, 2));
|
||||
|
||||
// Aplicar factor de cavitación
|
||||
h *= cavitationFactor;
|
||||
|
||||
var dp = -Direction * fluid.Rho * 9.80665 * h;
|
||||
|
||||
// dp es negativo si la bomba agrega presión en el sentido de la rama
|
||||
return dp;
|
||||
}
|
||||
|
||||
public override double DdpDq(double q, Fluid fluid)
|
||||
{
|
||||
var (h0s, q0s) = Scaled;
|
||||
|
||||
// Si la velocidad es muy baja, derivada es cero
|
||||
if (SpeedRel < 0.01)
|
||||
return 1e-12;
|
||||
|
||||
var dhDq = -2.0 * h0s * q / (q0s * q0s);
|
||||
return -Direction * fluid.Rho * 9.80665 * dhDq + 1e-12;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Versión mejorada de DdpDq que considera cavitación
|
||||
/// </summary>
|
||||
public double DdpDqWithSuctionCheck(double q, Fluid fluid, double suctionPressure, double dischargePressure)
|
||||
{
|
||||
var (h0s, q0s) = Scaled;
|
||||
|
||||
// Si la velocidad es muy baja, derivada es cero
|
||||
if (SpeedRel < 0.01)
|
||||
return 1e-12;
|
||||
|
||||
// Verificar condiciones de operación
|
||||
if (!CanOvercomeDischargePressure(suctionPressure, dischargePressure, fluid))
|
||||
return 1e-12;
|
||||
|
||||
var cavitationFactor = GetCavitationFactor(suctionPressure, fluid);
|
||||
if (cavitationFactor < 0.1)
|
||||
return 1e-12;
|
||||
|
||||
var dhDq = -2.0 * h0s * q / (q0s * q0s);
|
||||
dhDq *= cavitationFactor; // Aplicar factor de cavitación
|
||||
|
||||
return -Direction * fluid.Rho * 9.80665 * dhDq + 1e-12;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extensión de PumpHQ que considera verificaciones de NPSH durante la simulación
|
||||
/// </summary>
|
||||
public class PumpHQWithSuctionCheck : PumpHQ
|
||||
{
|
||||
private readonly Dictionary<string, double> _pressures;
|
||||
private bool _npshCheckEnabled;
|
||||
|
||||
public PumpHQWithSuctionCheck(double h0, double q0, double speedRel = 1.0, int direction = 1,
|
||||
Dictionary<string, double> pressures = null, bool enableNpshCheck = true)
|
||||
: base(h0, q0, speedRel, direction)
|
||||
{
|
||||
_pressures = pressures ?? new Dictionary<string, double>();
|
||||
_npshCheckEnabled = enableNpshCheck;
|
||||
}
|
||||
|
||||
public void UpdatePressures(Dictionary<string, double> pressures)
|
||||
{
|
||||
_pressures.Clear();
|
||||
if (pressures != null)
|
||||
{
|
||||
foreach (var kvp in pressures)
|
||||
{
|
||||
_pressures[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override double Dp(double q, Fluid fluid)
|
||||
{
|
||||
// Si no hay verificación de NPSH habilitada, usar el comportamiento base
|
||||
if (!_npshCheckEnabled || _pressures == null || string.IsNullOrEmpty(InletNodeName) || string.IsNullOrEmpty(OutletNodeName))
|
||||
{
|
||||
return base.Dp(q, fluid);
|
||||
}
|
||||
|
||||
// Obtener presiones de succión y descarga
|
||||
if (_pressures.TryGetValue(InletNodeName, out double suctionPressure) &&
|
||||
_pressures.TryGetValue(OutletNodeName, out double dischargePressure))
|
||||
{
|
||||
return DpWithSuctionCheck(q, fluid, suctionPressure, dischargePressure);
|
||||
}
|
||||
|
||||
// Si no tenemos presiones, usar comportamiento base
|
||||
return base.Dp(q, fluid);
|
||||
}
|
||||
|
||||
public override double DdpDq(double q, Fluid fluid)
|
||||
{
|
||||
// Si no hay verificación de NPSH habilitada, usar el comportamiento base
|
||||
if (!_npshCheckEnabled || _pressures == null || string.IsNullOrEmpty(InletNodeName) || string.IsNullOrEmpty(OutletNodeName))
|
||||
{
|
||||
return base.DdpDq(q, fluid);
|
||||
}
|
||||
|
||||
// Obtener presiones de succión y descarga
|
||||
if (_pressures.TryGetValue(InletNodeName, out double suctionPressure) &&
|
||||
_pressures.TryGetValue(OutletNodeName, out double dischargePressure))
|
||||
{
|
||||
return DdpDqWithSuctionCheck(q, fluid, suctionPressure, dischargePressure);
|
||||
}
|
||||
|
||||
// Si no tenemos presiones, usar comportamiento base
|
||||
return base.DdpDq(q, fluid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,179 +0,0 @@
|
|||
using System.Numerics;
|
||||
using System.ComponentModel;
|
||||
using CtrEditor.ObjetosSim;
|
||||
|
||||
namespace CtrEditor.HydraulicSimulator
|
||||
{
|
||||
/// <summary>
|
||||
/// Clase base para objetos hidráulicos en la simulación
|
||||
/// Similar a simBase pero para el sistema hidráulico
|
||||
/// </summary>
|
||||
public class simHydraulicBase
|
||||
{
|
||||
public string SimObjectType { get; set; } = "";
|
||||
public osBase WpfObject { get; set; }
|
||||
public string Nombre { get; set; }
|
||||
public bool Descartar { get; set; } = false;
|
||||
|
||||
protected HydraulicSimulationManager _hydraulicManager;
|
||||
|
||||
public simHydraulicBase(HydraulicSimulationManager hydraulicManager)
|
||||
{
|
||||
_hydraulicManager = hydraulicManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Método virtual para actualizar propiedades específicas del objeto
|
||||
/// </summary>
|
||||
public virtual void UpdateProperties()
|
||||
{
|
||||
// Implementación base - puede ser sobrescrita
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Método virtual para aplicar resultados de la simulación al objeto WPF
|
||||
/// </summary>
|
||||
public virtual void ApplySimulationResults()
|
||||
{
|
||||
// Implementación base - puede ser sobrescrita
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representa una bomba hidráulica en la simulación
|
||||
/// </summary>
|
||||
public class simHydraulicPump : simHydraulicBase
|
||||
{
|
||||
public double PumpHead { get; set; }
|
||||
public double MaxFlow { get; set; }
|
||||
public double SpeedRatio { get; set; }
|
||||
public bool IsRunning { get; set; }
|
||||
public int PumpDirection { get; set; }
|
||||
|
||||
// Resultados de la simulación
|
||||
public double CurrentFlow { get; set; }
|
||||
public double CurrentPressure { get; set; }
|
||||
|
||||
public simHydraulicPump(HydraulicSimulationManager hydraulicManager) : base(hydraulicManager)
|
||||
{
|
||||
SimObjectType = "HydraulicPump";
|
||||
}
|
||||
|
||||
public override void UpdateProperties()
|
||||
{
|
||||
// Actualizar propiedades específicas de la bomba
|
||||
if (WpfObject is IHydraulicPump pump)
|
||||
{
|
||||
PumpHead = pump.PumpHead;
|
||||
MaxFlow = pump.MaxFlow;
|
||||
SpeedRatio = pump.SpeedRatio;
|
||||
IsRunning = pump.IsRunning;
|
||||
PumpDirection = pump.PumpDirection;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ApplySimulationResults()
|
||||
{
|
||||
// Aplicar resultados al objeto WPF
|
||||
if (WpfObject is IHydraulicPump pump)
|
||||
{
|
||||
pump.SetFlow(CurrentFlow);
|
||||
pump.SetPressure(CurrentPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representa un tanque hidráulico en la simulación
|
||||
/// </summary>
|
||||
public class simHydraulicTank : simHydraulicBase
|
||||
{
|
||||
public double TankPressure { get; set; }
|
||||
public double CurrentLevel { get; set; }
|
||||
public double MaxLevel { get; set; }
|
||||
public double MinLevel { get; set; }
|
||||
public double CrossSectionalArea { get; set; }
|
||||
public bool IsFixedPressure { get; set; }
|
||||
|
||||
// Resultados de la simulación
|
||||
public double InletFlow { get; set; }
|
||||
public double OutletFlow { get; set; }
|
||||
public double CurrentPressure { get; set; }
|
||||
|
||||
public simHydraulicTank(HydraulicSimulationManager hydraulicManager) : base(hydraulicManager)
|
||||
{
|
||||
SimObjectType = "HydraulicTank";
|
||||
}
|
||||
|
||||
public override void UpdateProperties()
|
||||
{
|
||||
// Actualizar propiedades específicas del tanque
|
||||
if (WpfObject is osHydTank tank)
|
||||
{
|
||||
TankPressure = tank.TankPressure;
|
||||
CurrentLevel = tank.CurrentLevel;
|
||||
MaxLevel = tank.MaxLevel;
|
||||
MinLevel = tank.MinLevel;
|
||||
CrossSectionalArea = tank.CrossSectionalArea;
|
||||
IsFixedPressure = tank.IsFixedPressure;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ApplySimulationResults()
|
||||
{
|
||||
// Aplicar resultados al objeto WPF
|
||||
if (WpfObject is IHydraulicFlowReceiver flowReceiver)
|
||||
{
|
||||
flowReceiver.SetFlow(InletFlow - OutletFlow); // Flujo neto
|
||||
}
|
||||
|
||||
if (WpfObject is IHydraulicPressureReceiver pressureReceiver)
|
||||
{
|
||||
pressureReceiver.SetPressure(CurrentPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representa una tubería hidráulica en la simulación
|
||||
/// </summary>
|
||||
public class simHydraulicPipe : simHydraulicBase
|
||||
{
|
||||
public double Length { get; set; }
|
||||
public double Diameter { get; set; }
|
||||
public double Roughness { get; set; }
|
||||
public string ComponenteAId { get; set; }
|
||||
public string ComponenteBId { get; set; }
|
||||
|
||||
// Resultados de la simulación
|
||||
public double CurrentFlow { get; set; }
|
||||
public double PressureDrop { get; set; }
|
||||
|
||||
public simHydraulicPipe(HydraulicSimulationManager hydraulicManager) : base(hydraulicManager)
|
||||
{
|
||||
SimObjectType = "HydraulicPipe";
|
||||
}
|
||||
|
||||
public override void UpdateProperties()
|
||||
{
|
||||
// Actualizar propiedades específicas de la tubería
|
||||
if (WpfObject is IHydraulicPipe pipe)
|
||||
{
|
||||
Length = pipe.Length;
|
||||
Diameter = pipe.Diameter;
|
||||
Roughness = pipe.Roughness;
|
||||
// Obtener IDs de componentes conectados (esto se manejará en el manager)
|
||||
}
|
||||
}
|
||||
|
||||
public override void ApplySimulationResults()
|
||||
{
|
||||
// Aplicar resultados al objeto WPF
|
||||
if (WpfObject is IHydraulicPipe pipe)
|
||||
{
|
||||
pipe.SetFlow(CurrentFlow);
|
||||
pipe.SetPressure(PressureDrop); // En tuberías, "presión" es la pérdida de presión
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
116
MainViewModel.cs
116
MainViewModel.cs
|
@ -11,7 +11,6 @@ using System.IO;
|
|||
using Newtonsoft.Json;
|
||||
using System.Windows;
|
||||
using CtrEditor.Simulacion;
|
||||
using CtrEditor.HydraulicSimulator; // Nuevo using para el simulador hidráulico
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
@ -59,7 +58,6 @@ namespace CtrEditor
|
|||
private bool Debug_SimulacionCreado = false;
|
||||
|
||||
public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU();
|
||||
public HydraulicSimulationManager hydraulicSimulationManager = new HydraulicSimulationManager();
|
||||
|
||||
private readonly System.Timers.Timer _timerSimulacion; // Cambiado a System.Timers.Timer para mejor precisión
|
||||
private readonly System.Timers.Timer _timerPLCUpdate; // Cambiado a System.Timers.Timer para mejor precisión
|
||||
|
@ -739,9 +737,6 @@ namespace CtrEditor
|
|||
{
|
||||
_stateSerializer.LoadState(SelectedImage);
|
||||
|
||||
// Registrar objetos hidráulicos cargados
|
||||
RegisterLoadedHydraulicObjects();
|
||||
|
||||
// Aplicar los filtros actuales a los objetos recién cargados
|
||||
if (MainWindow?.VisFilter?.FilterViewModel != null)
|
||||
{
|
||||
|
@ -792,10 +787,6 @@ namespace CtrEditor
|
|||
{
|
||||
// Añadir el nuevo osBase a la colección de objetos simulables
|
||||
ObjetosSimulables.Add(NuevoOsBase);
|
||||
|
||||
// Registrar en el simulador hidráulico si tiene componentes hidráulicos
|
||||
RegisterHydraulicObjectIfNeeded(NuevoOsBase);
|
||||
|
||||
HasUnsavedChanges = true;
|
||||
}
|
||||
}
|
||||
|
@ -813,7 +804,7 @@ namespace CtrEditor
|
|||
if (userControl != null)
|
||||
{
|
||||
// Asignar los datos al UserControl
|
||||
UserControlFactory.AssignDatos(userControl, osObjeto, simulationManager, hydraulicSimulationManager);
|
||||
UserControlFactory.AssignDatos(userControl, osObjeto, simulationManager);
|
||||
osObjeto._mainViewModel = this;
|
||||
if (osObjeto.Id == null) // Para los objetos salvados antes de usar UniqueID
|
||||
osObjeto.Id = new UniqueId().ObtenerNuevaID();
|
||||
|
@ -829,9 +820,6 @@ namespace CtrEditor
|
|||
{
|
||||
if (osObjeto != null && ObjetosSimulables.Contains(osObjeto))
|
||||
{
|
||||
// Desregistrar del simulador hidráulico si estaba registrado
|
||||
UnregisterHydraulicObjectIfNeeded(osObjeto);
|
||||
|
||||
ObjetosSimulables.Remove(osObjeto);
|
||||
if (osObjeto.VisualRepresentation != null)
|
||||
MainWindow.EliminarUserControlDelCanvas(osObjeto.VisualRepresentation);
|
||||
|
@ -937,10 +925,6 @@ namespace CtrEditor
|
|||
NuevoObjetoDuplicado.Top += OffsetY;
|
||||
ObjetosSimulables.Add(NuevoObjetoDuplicado);
|
||||
CrearUserControlDesdeObjetoSimulable(NuevoObjetoDuplicado);
|
||||
|
||||
// Registrar en el simulador hidráulico si tiene componentes hidráulicos
|
||||
RegisterHydraulicObjectIfNeeded(NuevoObjetoDuplicado);
|
||||
|
||||
HasUnsavedChanges = true;
|
||||
}
|
||||
}
|
||||
|
@ -1129,9 +1113,6 @@ namespace CtrEditor
|
|||
foreach (var objetoSimulable in ObjetosSimulables)
|
||||
objetoSimulable.SimulationStop();
|
||||
|
||||
// Reiniciar simulador hidráulico al detener la simulación
|
||||
ResetHydraulicSimulation();
|
||||
|
||||
if (Debug_SimulacionCreado)
|
||||
{
|
||||
Debug_SimulacionCreado = false;
|
||||
|
@ -1177,12 +1158,8 @@ namespace CtrEditor
|
|||
objetoSimulable.UpdateGeometryStep();
|
||||
}
|
||||
|
||||
// Ejecutar simulación física BEPU
|
||||
simulationManager.Step();
|
||||
|
||||
// Ejecutar simulación hidráulica
|
||||
hydraulicSimulationManager.Step((float)(timeBetweenCalls / 1000.0)); // Convertir ms a segundos
|
||||
|
||||
// ✅ NUEVO: Solo crear la copia si hay objetos para procesar
|
||||
if (ObjetosSimulables?.Count > 0)
|
||||
{
|
||||
|
@ -1665,97 +1642,6 @@ namespace CtrEditor
|
|||
}
|
||||
}
|
||||
|
||||
#region Hydraulic Simulation Management
|
||||
|
||||
/// <summary>
|
||||
/// Registra un objeto en el simulador hidráulico si tiene componentes hidráulicos
|
||||
/// </summary>
|
||||
private void RegisterHydraulicObjectIfNeeded(osBase obj)
|
||||
{
|
||||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
// Evitar doble registro - solo registrar si no está ya registrado
|
||||
if (!hydraulicSimulationManager.HydraulicObjects.Contains(obj))
|
||||
{
|
||||
hydraulicSimulationManager.RegisterHydraulicObject(obj);
|
||||
Debug.WriteLine($"Objeto hidráulico registrado: {obj.Nombre}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Objeto hidráulico ya registrado (omitido): {obj.Nombre}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Desregistra un objeto del simulador hidráulico si estaba registrado
|
||||
/// </summary>
|
||||
private void UnregisterHydraulicObjectIfNeeded(osBase obj)
|
||||
{
|
||||
// Desregistrar independientemente de si implementa la interfaz actualmente
|
||||
// (podría haber cambiado desde que se registró)
|
||||
hydraulicSimulationManager.UnregisterHydraulicObject(obj);
|
||||
Debug.WriteLine($"Objeto desregistrado del simulador hidráulico: {obj.Nombre}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reinicia el simulador hidráulico
|
||||
/// </summary>
|
||||
public void ResetHydraulicSimulation()
|
||||
{
|
||||
hydraulicSimulationManager.Reset();
|
||||
Debug.WriteLine("Simulador hidráulico reiniciado");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene estadísticas del simulador hidráulico
|
||||
/// </summary>
|
||||
public string GetHydraulicSimulationStats()
|
||||
{
|
||||
return hydraulicSimulationManager.GetSimulationStats();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Habilita/deshabilita la simulación hidráulica
|
||||
/// </summary>
|
||||
public void SetHydraulicSimulationEnabled(bool enabled)
|
||||
{
|
||||
hydraulicSimulationManager.IsHydraulicSimulationEnabled = enabled;
|
||||
Debug.WriteLine($"Simulación hidráulica {(enabled ? "habilitada" : "deshabilitada")}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registra todos los objetos hidráulicos existentes después de cargar un proyecto
|
||||
/// </summary>
|
||||
private void RegisterLoadedHydraulicObjects()
|
||||
{
|
||||
// Limpiar registros previos
|
||||
hydraulicSimulationManager.ClearHydraulicObjects();
|
||||
|
||||
// Crear una lista temporal para evitar duplicados durante la carga
|
||||
var objectsToRegister = new HashSet<osBase>();
|
||||
|
||||
// Registrar todos los objetos cargados que tengan componentes hidráulicos
|
||||
foreach (var obj in ObjetosSimulables)
|
||||
{
|
||||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
objectsToRegister.Add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Registrar los objetos únicos
|
||||
foreach (var obj in objectsToRegister)
|
||||
{
|
||||
hydraulicSimulationManager.RegisterHydraulicObject(obj);
|
||||
Debug.WriteLine($"Objeto hidráulico registrado en carga: {obj.Nombre}");
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Registrados {hydraulicSimulationManager.HydraulicObjects.Count} objetos hidráulicos únicos tras cargar proyecto");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
public class SimulationData
|
||||
|
|
|
@ -134,7 +134,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="3*" />
|
||||
<RowDefinition Height="5" />
|
||||
<RowDefinition Height="0" />
|
||||
<RowDefinition Height="2*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ToolBarTray Grid.Row="0">
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using CtrEditor.ObjetosSim;
|
||||
using CtrEditor.Simulacion;
|
||||
using CtrEditor.HydraulicSimulator;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
|
@ -972,37 +971,7 @@ namespace CtrEditor
|
|||
try
|
||||
{
|
||||
var serializedData = JsonConvert.SerializeObject(original, settings);
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - Serialized data: {serializedData}");
|
||||
|
||||
copy = JsonConvert.DeserializeObject<osBase>(serializedData, settings);
|
||||
|
||||
if (copy == null)
|
||||
{
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - Deserialización devolvió null para tipo: {original.GetType().Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - Copia exitosa de tipo: {copy.GetType().Name}");
|
||||
}
|
||||
}
|
||||
catch (JsonSerializationException jsonEx)
|
||||
{
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - Error de serialización JSON: {jsonEx.Message}");
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - StackTrace: {jsonEx.StackTrace}");
|
||||
if (jsonEx.InnerException != null)
|
||||
{
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - Inner Exception: {jsonEx.InnerException.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - Error general: {ex.Message}");
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - Tipo de excepción: {ex.GetType().Name}");
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - StackTrace: {ex.StackTrace}");
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Debug.WriteLine($"CreateCopyUsingExistingLogic - Inner Exception: {ex.InnerException.Message}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -1251,6 +1220,5 @@ namespace CtrEditor
|
|||
else
|
||||
return new ValidationResult(false, "Ingrese un número válido.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-windows8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="CtrEditor.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,501 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Media;
|
||||
using CtrEditor.HydraulicSimulator;
|
||||
using CtrEditor.ObjetosSim;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using HydraulicSimulator.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using LibS7Adv;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
/// <summary>
|
||||
/// Tubería hidráulica que conecta componentes del sistema hidráulico
|
||||
/// </summary>
|
||||
public partial class osHydPipe : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
|
||||
{
|
||||
// Referencia al objeto de simulación hidráulica específico
|
||||
private simHydraulicPipe SimHydraulicPipe;
|
||||
// Properties
|
||||
|
||||
private double _length = 1.0; // metros
|
||||
private double _diameter = 0.05; // metros (50mm)
|
||||
private double _roughness = 4.5e-5; // metros - rugosidad del acero comercial
|
||||
private double _currentFlow = 0.0;
|
||||
private double _pressureDrop = 0.0;
|
||||
|
||||
// Propiedades visuales
|
||||
[ObservableProperty]
|
||||
[property: Category("🎨 Apariencia")]
|
||||
[property: Description("Ancho visual de la tubería en metros")]
|
||||
[property: Name("Ancho")]
|
||||
private float ancho = 1.0f;
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("🎨 Apariencia")]
|
||||
[property: Description("Alto visual de la tubería en metros")]
|
||||
[property: Name("Alto")]
|
||||
private float alto = 0.05f;
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("🎨 Apariencia")]
|
||||
[property: Description("Color visual de la tubería")]
|
||||
[property: Name("Color")]
|
||||
private Brush colorButton_oculto = Brushes.Gray;
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("🎨 Apariencia")]
|
||||
[property: Description("Ángulo de rotación de la tubería en grados")]
|
||||
[property: Name("Ángulo")]
|
||||
private float angulo = 0f;
|
||||
|
||||
[Category("🔧 Tubería Hidráulica")]
|
||||
[Description("Longitud de la tubería en metros")]
|
||||
[Name("Longitud (m)")]
|
||||
public double Length
|
||||
{
|
||||
get => _length;
|
||||
set => SetProperty(ref _length, Math.Max(0.1, value));
|
||||
}
|
||||
|
||||
[Category("🔧 Tubería Hidráulica")]
|
||||
[Description("Diámetro interno de la tubería en metros")]
|
||||
[Name("Diámetro (m)")]
|
||||
public double Diameter
|
||||
{
|
||||
get => _diameter;
|
||||
set => SetProperty(ref _diameter, Math.Max(0.001, value));
|
||||
}
|
||||
|
||||
[Category("🔧 Tubería Hidráulica")]
|
||||
[Description("Rugosidad del material en metros")]
|
||||
[Name("Rugosidad (m)")]
|
||||
public double Roughness
|
||||
{
|
||||
get => _roughness;
|
||||
set => SetProperty(ref _roughness, Math.Max(1e-6, value));
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("🔗 Conexiones")]
|
||||
[property: Description("Primer componente hidráulico conectado")]
|
||||
[property: Name("Componente A")]
|
||||
[property: ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
|
||||
private string id_ComponenteA = "";
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("🔗 Conexiones")]
|
||||
[property: Description("Segundo componente hidráulico conectado")]
|
||||
[property: Name("Componente B")]
|
||||
[property: ItemsSource(typeof(osBaseItemsSource<IHydraulicComponent>))]
|
||||
private string id_ComponenteB = "";
|
||||
|
||||
[Category("📊 Estado")]
|
||||
[Description("Flujo actual a través de la tubería en m³/s")]
|
||||
[Name("Flujo Actual (m³/s)")]
|
||||
[ReadOnly(true)]
|
||||
public double CurrentFlow
|
||||
{
|
||||
get => _currentFlow;
|
||||
set => SetProperty(ref _currentFlow, value);
|
||||
}
|
||||
|
||||
[Category("📊 Estado")]
|
||||
[Description("Pérdida de presión en la tubería en Pa")]
|
||||
[Name("Pérdida Presión (Pa)")]
|
||||
[ReadOnly(true)]
|
||||
public double PressureDrop
|
||||
{
|
||||
get => _pressureDrop;
|
||||
set => SetProperty(ref _pressureDrop, value);
|
||||
}
|
||||
|
||||
private string pipeId = "";
|
||||
public string PipeId
|
||||
{
|
||||
get => pipeId;
|
||||
set => SetProperty(ref pipeId, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Component References
|
||||
|
||||
[JsonIgnore]
|
||||
private IHydraulicComponent ComponenteA = null;
|
||||
|
||||
[JsonIgnore]
|
||||
private IHydraulicComponent ComponenteB = null;
|
||||
|
||||
[JsonIgnore]
|
||||
private PropertyChangedEventHandler componenteAPropertyChangedHandler;
|
||||
|
||||
[JsonIgnore]
|
||||
private PropertyChangedEventHandler componenteBPropertyChangedHandler;
|
||||
|
||||
[JsonIgnore]
|
||||
public Action<float, float> ActualizarTamaño { get; set; }
|
||||
|
||||
|
||||
|
||||
// IHydraulicPipe Implementation
|
||||
|
||||
// Ya implementadas las propiedades Length, Diameter, Roughness arriba
|
||||
|
||||
|
||||
|
||||
// IHydraulicComponent Implementation
|
||||
|
||||
public bool HasHydraulicComponents => true;
|
||||
|
||||
public List<HydraulicNodeDefinition> GetHydraulicNodes()
|
||||
{
|
||||
var nodes = new List<HydraulicNodeDefinition>();
|
||||
|
||||
// Las tuberías conectan componentes existentes, no crean nodos propios
|
||||
// Los nodos son creados por los componentes conectados (tanques, bombas, etc.)
|
||||
// La tubería solo actúa como elemento que conecta nodos existentes
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public List<HydraulicElementDefinition> GetHydraulicElements()
|
||||
{
|
||||
var elements = new List<HydraulicElementDefinition>();
|
||||
|
||||
// Solo crear elemento si ambos componentes están conectados
|
||||
if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
|
||||
{
|
||||
// Obtener los nombres de nodos correctos para cada componente
|
||||
string fromNodeName = GetNodeNameForComponent(Id_ComponenteA, true); // true = es el nodo origen
|
||||
string toNodeName = GetNodeNameForComponent(Id_ComponenteB, false); // false = es el nodo destino
|
||||
|
||||
if (!string.IsNullOrEmpty(fromNodeName) && !string.IsNullOrEmpty(toNodeName))
|
||||
{
|
||||
// Crear el elemento Pipe según la documentación
|
||||
var pipeElement = new Pipe(Length, Diameter, Roughness);
|
||||
|
||||
elements.Add(new HydraulicElementDefinition(
|
||||
name: $"{Nombre}_Pipe",
|
||||
fromNode: fromNodeName,
|
||||
toNode: toNodeName,
|
||||
element: pipeElement,
|
||||
description: $"Tubería {Nombre} - L:{Length:F2}m, D:{Diameter*1000:F0}mm ({fromNodeName} → {toNodeName})"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el nombre del nodo correcto para un componente dado
|
||||
/// </summary>
|
||||
/// <param name="componentName">Nombre del componente</param>
|
||||
/// <param name="isSource">True si es el nodo origen de la tubería, False si es destino</param>
|
||||
/// <returns>Nombre del nodo o string vacío si no se encuentra</returns>
|
||||
private string GetNodeNameForComponent(string componentName, bool isSource)
|
||||
{
|
||||
if (string.IsNullOrEmpty(componentName) || _mainViewModel == null)
|
||||
return "";
|
||||
|
||||
// Buscar el componente hidráulico
|
||||
var component = _mainViewModel.ObjetosSimulables
|
||||
.FirstOrDefault(s => s is IHydraulicComponent comp &&
|
||||
((osBase)comp).Nombre == componentName) as IHydraulicComponent;
|
||||
|
||||
if (component == null)
|
||||
return "";
|
||||
|
||||
// Obtener los nodos que crea este componente
|
||||
var nodes = component.GetHydraulicNodes();
|
||||
if (nodes == null || !nodes.Any())
|
||||
return "";
|
||||
|
||||
// Determinar qué nodo usar según el tipo de componente
|
||||
var componentType = component.GetType();
|
||||
|
||||
// Para tanques: siempre usar su único nodo
|
||||
if (componentType.Name.Contains("Tank"))
|
||||
{
|
||||
return nodes.FirstOrDefault()?.Name ?? "";
|
||||
}
|
||||
|
||||
// Para bombas: usar nodo de entrada o salida según corresponda
|
||||
if (componentType.Name.Contains("Pump"))
|
||||
{
|
||||
if (isSource)
|
||||
{
|
||||
// Si la tubería sale desde este componente, usar el nodo de salida de la bomba
|
||||
return nodes.FirstOrDefault(n => n.Name.EndsWith("_Out"))?.Name ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Si la tubería llega a este componente, usar el nodo de entrada de la bomba
|
||||
return nodes.FirstOrDefault(n => n.Name.EndsWith("_In"))?.Name ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
// Para otros tipos de componentes, usar el primer nodo disponible
|
||||
return nodes.FirstOrDefault()?.Name ?? "";
|
||||
}
|
||||
|
||||
public void UpdateHydraulicProperties()
|
||||
{
|
||||
// Actualizar propiedades antes de la simulación si es necesario
|
||||
// Por ejemplo, cambios dinámicos en diámetro o rugosidad
|
||||
}
|
||||
|
||||
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
||||
{
|
||||
// Solo procesar si ambos componentes están conectados
|
||||
if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
|
||||
{
|
||||
// Obtener los nombres de nodos correctos
|
||||
string fromNodeName = GetNodeNameForComponent(Id_ComponenteA, true);
|
||||
string toNodeName = GetNodeNameForComponent(Id_ComponenteB, false);
|
||||
|
||||
if (!string.IsNullOrEmpty(fromNodeName) && !string.IsNullOrEmpty(toNodeName))
|
||||
{
|
||||
string branchKey = $"{fromNodeName}->{toNodeName}";
|
||||
|
||||
if (flows.TryGetValue(branchKey, out double flow))
|
||||
{
|
||||
CurrentFlow = flow;
|
||||
}
|
||||
|
||||
// Calcular pérdida de presión entre nodos
|
||||
if (pressures.TryGetValue(fromNodeName, out double pressureA) &&
|
||||
pressures.TryGetValue(toNodeName, out double pressureB))
|
||||
{
|
||||
PressureDrop = pressureA - pressureB;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IHydraulicFlowReceiver Implementation
|
||||
|
||||
public void SetFlow(double flow)
|
||||
{
|
||||
CurrentFlow = flow;
|
||||
}
|
||||
|
||||
public double GetFlow()
|
||||
{
|
||||
return CurrentFlow;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IHydraulicPressureReceiver Implementation
|
||||
|
||||
public void SetPressure(double pressure)
|
||||
{
|
||||
// Para tuberías, la presión se maneja a través de los nodos
|
||||
}
|
||||
|
||||
public double GetPressure()
|
||||
{
|
||||
return PressureDrop;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Connection Management
|
||||
|
||||
partial void OnId_ComponenteAChanged(string value)
|
||||
{
|
||||
if (ComponenteA != null && componenteAPropertyChangedHandler != null)
|
||||
((INotifyPropertyChanged)ComponenteA).PropertyChanged -= componenteAPropertyChangedHandler;
|
||||
|
||||
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
|
||||
{
|
||||
ComponenteA = (IHydraulicComponent)_mainViewModel.ObjetosSimulables
|
||||
.FirstOrDefault(s => s is IHydraulicComponent comp &&
|
||||
((osBase)comp).Nombre == value);
|
||||
|
||||
if (ComponenteA != null)
|
||||
{
|
||||
componenteAPropertyChangedHandler = (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(osBase.Nombre))
|
||||
{
|
||||
Id_ComponenteA = ((osBase)sender).Nombre;
|
||||
}
|
||||
};
|
||||
((INotifyPropertyChanged)ComponenteA).PropertyChanged += componenteAPropertyChangedHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnId_ComponenteBChanged(string value)
|
||||
{
|
||||
if (ComponenteB != null && componenteBPropertyChangedHandler != null)
|
||||
((INotifyPropertyChanged)ComponenteB).PropertyChanged -= componenteBPropertyChangedHandler;
|
||||
|
||||
if (_mainViewModel != null && !string.IsNullOrEmpty(value))
|
||||
{
|
||||
ComponenteB = (IHydraulicComponent)_mainViewModel.ObjetosSimulables
|
||||
.FirstOrDefault(s => s is IHydraulicComponent comp &&
|
||||
((osBase)comp).Nombre == value);
|
||||
|
||||
if (ComponenteB != null)
|
||||
{
|
||||
componenteBPropertyChangedHandler = (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(osBase.Nombre))
|
||||
{
|
||||
Id_ComponenteB = ((osBase)sender).Nombre;
|
||||
}
|
||||
};
|
||||
((INotifyPropertyChanged)ComponenteB).PropertyChanged += componenteBPropertyChangedHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// osBase Implementation
|
||||
|
||||
public static string NombreCategoria() => "Componentes Hidráulicos";
|
||||
|
||||
public static string NombreClase() => "Tubería Hidráulica";
|
||||
|
||||
private string nombre = NombreClase();
|
||||
|
||||
[Category("Identificación")]
|
||||
[Description("Nombre identificativo del objeto")]
|
||||
[Name("Nombre")]
|
||||
public override string Nombre
|
||||
{
|
||||
get => nombre;
|
||||
set => SetProperty(ref nombre, value);
|
||||
}
|
||||
|
||||
public override void AltoChanged(float value)
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
public override void AnchoChanged(float value)
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// Las tuberías no participan en la simulación física Bepu
|
||||
// Solo en el sistema hidráulico
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStart()
|
||||
{
|
||||
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
|
||||
if (SimHydraulicPipe == null && !string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
|
||||
{
|
||||
SimHydraulicPipe = hydraulicSimulationManager.AddPipe(Length, Diameter, Roughness, Id_ComponenteA, Id_ComponenteB);
|
||||
SimHydraulicPipe.SimObjectType = "HydraulicPipe";
|
||||
SimHydraulicPipe.WpfObject = this;
|
||||
SimHydraulicPipe.Nombre = Nombre;
|
||||
}
|
||||
else if (SimHydraulicPipe != null)
|
||||
{
|
||||
// Actualizar propiedades si el objeto ya existe
|
||||
SimHydraulicPipe.Length = Length;
|
||||
SimHydraulicPipe.Diameter = Diameter;
|
||||
SimHydraulicPipe.Roughness = Roughness;
|
||||
SimHydraulicPipe.ComponenteAId = Id_ComponenteA;
|
||||
SimHydraulicPipe.ComponenteBId = Id_ComponenteB;
|
||||
}
|
||||
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStep()
|
||||
{
|
||||
// Los objetos hidráulicos actualizan sus resultados
|
||||
// a través de ApplySimulationResults() desde HydraulicSimulationManager
|
||||
}
|
||||
|
||||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
// Actualizar propiedades desde la simulación hidráulica
|
||||
if (SimHydraulicPipe != null)
|
||||
{
|
||||
CurrentFlow = SimHydraulicPipe.CurrentFlow;
|
||||
PressureDrop = SimHydraulicPipe.PressureDrop;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ucLoaded()
|
||||
{
|
||||
// Objeto hidráulico se crea en UpdateGeometryStart cuando inicia la simulación
|
||||
base.ucLoaded();
|
||||
}
|
||||
|
||||
public override void ucUnLoaded()
|
||||
{
|
||||
// Remover objeto hidráulico de la simulación
|
||||
if (SimHydraulicPipe != null)
|
||||
{
|
||||
hydraulicSimulationManager.Remove(SimHydraulicPipe);
|
||||
SimHydraulicPipe = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||
{
|
||||
// Las tuberías no tienen control PLC directo
|
||||
}
|
||||
|
||||
private void ActualizarGeometrias()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Actualizar geometría visual
|
||||
if (this.ActualizarTamaño != null)
|
||||
this.ActualizarTamaño(Ancho, Alto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error actualizando geometría: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Inicializar(int valorInicial)
|
||||
{
|
||||
PipeId = $"Pipe_{Nombre}_{valorInicial}";
|
||||
OnId_ComponenteAChanged(Id_ComponenteA);
|
||||
OnId_ComponenteBChanged(Id_ComponenteB);
|
||||
}
|
||||
|
||||
public void Disposing()
|
||||
{
|
||||
if (ComponenteA != null && componenteAPropertyChangedHandler != null)
|
||||
((INotifyPropertyChanged)ComponenteA).PropertyChanged -= componenteAPropertyChangedHandler;
|
||||
|
||||
if (ComponenteB != null && componenteBPropertyChangedHandler != null)
|
||||
((INotifyPropertyChanged)ComponenteB).PropertyChanged -= componenteBPropertyChangedHandler;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Constructor
|
||||
|
||||
public osHydPipe()
|
||||
{
|
||||
PipeId = Guid.NewGuid().ToString();
|
||||
IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,613 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Media;
|
||||
using CtrEditor.HydraulicSimulator;
|
||||
using CtrEditor.ObjetosSim;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using HydraulicSimulator.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
/// <summary>
|
||||
/// Bomba hidráulica que implementa las interfaces del simulador hidráulico
|
||||
/// </summary>
|
||||
public partial class osHydPump : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
|
||||
{
|
||||
// Referencia al objeto de simulación hidráulica específico
|
||||
private simHydraulicPump SimHydraulicPump;
|
||||
|
||||
public static string NombreCategoria() => "Componentes Hidráulicos";
|
||||
|
||||
public static string NombreClase()
|
||||
{
|
||||
return "Bomba Hidráulica";
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
[ObservableProperty]
|
||||
[property: JsonIgnore]
|
||||
[property: Category("Apariencia")]
|
||||
[property: Description("Imagen visual")]
|
||||
[property: Name("Imagen")]
|
||||
public ImageSource imageSource_oculta;
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("🎨 Apariencia")]
|
||||
[property: Description("Tamaño visual de la bomba")]
|
||||
[property: Name("Tamaño")]
|
||||
public float tamano;
|
||||
|
||||
public override void OnResize(float Delta_Width, float Delta_Height)
|
||||
{
|
||||
Tamano += Delta_Width + Delta_Height;
|
||||
}
|
||||
|
||||
private double _pumpHead = 50.0; // metros
|
||||
private double _maxFlow = 0.01; // m³/s (36 m³/h)
|
||||
private double _speedRatio = 1.0;
|
||||
private bool _isRunning = true;
|
||||
private int _pumpDirection = 1;
|
||||
private double _currentFlow = 0.0;
|
||||
private double _currentPressure = 0.0;
|
||||
|
||||
// Propiedades visuales específicas de la bomba
|
||||
[ObservableProperty]
|
||||
[property: Category("🎨 Apariencia")]
|
||||
[property: Description("Color visual de la bomba")]
|
||||
[property: Name("Color")]
|
||||
private Brush colorButton_oculto = Brushes.Blue;
|
||||
|
||||
[Category("🔧 Bomba Hidráulica")]
|
||||
[DisplayName("Cabeza de bomba")]
|
||||
[Description("Cabeza de la bomba a caudal cero (m)")]
|
||||
public double PumpHead
|
||||
{
|
||||
get => _pumpHead;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _pumpHead, Math.Max(0, value)))
|
||||
{
|
||||
InvalidateHydraulicNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("🔧 Bomba Hidráulica")]
|
||||
[DisplayName("Caudal máximo")]
|
||||
[Description("Caudal máximo de la bomba (m³/s)")]
|
||||
public double MaxFlow
|
||||
{
|
||||
get => _maxFlow;
|
||||
set
|
||||
{
|
||||
// Si el valor es mayor a 1, asumir que está en m³/h y convertir a m³/s
|
||||
double valueInM3s = value > 1.0 ? value / 3600.0 : value;
|
||||
if (SetProperty(ref _maxFlow, Math.Max(0.0001, valueInM3s)))
|
||||
{
|
||||
InvalidateHydraulicNetwork();
|
||||
Debug.WriteLine($"Bomba {Nombre}: MaxFlow establecido a {_maxFlow:F6} m³/s ({_maxFlow * 3600:F2} m³/h)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("🔧 Bomba Hidráulica")]
|
||||
[DisplayName("Velocidad relativa")]
|
||||
[Description("Velocidad relativa de la bomba (0.0 a 1.0)")]
|
||||
public double SpeedRatio
|
||||
{
|
||||
get => _speedRatio;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _speedRatio, Math.Max(0.0, Math.Min(1.0, value)));
|
||||
}
|
||||
}
|
||||
|
||||
[Category("🔧 Bomba Hidráulica")]
|
||||
[DisplayName("Funcionando")]
|
||||
[Description("Indica si la bomba está encendida")]
|
||||
public bool IsRunning
|
||||
{
|
||||
get => _isRunning;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isRunning, value))
|
||||
{
|
||||
UpdatePumpImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePumpImage()
|
||||
{
|
||||
if (IsRunning)
|
||||
ImageSource_oculta = ImageFromPath("/imagenes/pump_run.png");
|
||||
else
|
||||
ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png");
|
||||
}
|
||||
|
||||
[Category("🔧 Bomba Hidráulica")]
|
||||
[DisplayName("Dirección")]
|
||||
[Description("Dirección de la bomba (+1 o -1)")]
|
||||
[ItemsSource(typeof(PumpDirectionItemsSource))]
|
||||
public int PumpDirection
|
||||
{
|
||||
get => _pumpDirection;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _pumpDirection, value == -1 ? -1 : 1))
|
||||
{
|
||||
InvalidateHydraulicNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Caudal actual")]
|
||||
[Description("Caudal actual de la bomba (m³/s)")]
|
||||
[JsonIgnore]
|
||||
public double CurrentFlow
|
||||
{
|
||||
get => _currentFlow;
|
||||
private set => SetProperty(ref _currentFlow, value);
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Presión actual")]
|
||||
[Description("Presión actual en la bomba (Pa)")]
|
||||
[JsonIgnore]
|
||||
public double CurrentPressure
|
||||
{
|
||||
get => _currentPressure;
|
||||
private set => SetProperty(ref _currentPressure, value);
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Presión (bar)")]
|
||||
[Description("Presión actual en la bomba (bar)")]
|
||||
[JsonIgnore]
|
||||
public double CurrentPressureBar => CurrentPressure / 100000.0;
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Caudal (L/min)")]
|
||||
[Description("Caudal actual en L/min")]
|
||||
[JsonIgnore]
|
||||
public double CurrentFlowLMin => CurrentFlow * 60000.0; // m³/s a L/min
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("NPSH Disponible")]
|
||||
[Description("NPSH disponible calculado (m)")]
|
||||
[JsonIgnore]
|
||||
public double NPSHAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
var (inletNodeName, _) = GetConnectedNodeNames();
|
||||
if (!string.IsNullOrEmpty(inletNodeName) &&
|
||||
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
|
||||
{
|
||||
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
|
||||
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
|
||||
return pump.CalculateNPSHAvailable(suctionPressure, hydraulicSimulationManager.SimulationFluid);
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Factor Cavitación")]
|
||||
[Description("Factor de cavitación (1=sin cavitación, 0=cavitación total)")]
|
||||
[JsonIgnore]
|
||||
public double CavitationFactor
|
||||
{
|
||||
get
|
||||
{
|
||||
var (inletNodeName, _) = GetConnectedNodeNames();
|
||||
if (!string.IsNullOrEmpty(inletNodeName) &&
|
||||
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
|
||||
{
|
||||
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
|
||||
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
|
||||
return pump.GetCavitationFactor(suctionPressure, hydraulicSimulationManager.SimulationFluid);
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Puede Operar")]
|
||||
[Description("Indica si la bomba puede operar sin cavitación")]
|
||||
[JsonIgnore]
|
||||
public bool CanOperateWithoutCavitation
|
||||
{
|
||||
get
|
||||
{
|
||||
var (inletNodeName, _) = GetConnectedNodeNames();
|
||||
if (!string.IsNullOrEmpty(inletNodeName) &&
|
||||
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
|
||||
{
|
||||
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
|
||||
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
|
||||
return pump.CanOperateWithoutCavitation(suctionPressure, hydraulicSimulationManager.SimulationFluid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Constructor y Métodos Base
|
||||
|
||||
private string nombre = NombreClase();
|
||||
|
||||
[Category("🏷️ Identificación")]
|
||||
[Description("Nombre identificativo del objeto")]
|
||||
[Name("Nombre")]
|
||||
public override string Nombre
|
||||
{
|
||||
get => nombre;
|
||||
set => SetProperty(ref nombre, value);
|
||||
}
|
||||
|
||||
public osHydPump()
|
||||
{
|
||||
Nombre = "Bomba Hidráulica";
|
||||
Tamano = 0.30f;
|
||||
// Inicializar dimensiones usando las propiedades de osBase
|
||||
Ancho = 0.15f;
|
||||
Alto = 0.10f;
|
||||
Angulo = 0f;
|
||||
// Asegurar que el movimiento esté habilitado
|
||||
Lock_movement = false;
|
||||
ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png");
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStart()
|
||||
{
|
||||
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
|
||||
if (SimHydraulicPump == null)
|
||||
{
|
||||
SimHydraulicPump = hydraulicSimulationManager.AddPump(PumpHead, MaxFlow, SpeedRatio, IsRunning, PumpDirection);
|
||||
SimHydraulicPump.SimObjectType = "HydraulicPump";
|
||||
SimHydraulicPump.WpfObject = this;
|
||||
SimHydraulicPump.Nombre = Nombre;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Actualizar propiedades si el objeto ya existe
|
||||
SimHydraulicPump.PumpHead = PumpHead;
|
||||
SimHydraulicPump.MaxFlow = MaxFlow;
|
||||
SimHydraulicPump.SpeedRatio = SpeedRatio;
|
||||
SimHydraulicPump.IsRunning = IsRunning;
|
||||
SimHydraulicPump.PumpDirection = PumpDirection;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStep()
|
||||
{
|
||||
// Los objetos hidráulicos actualizan sus resultados
|
||||
// a través de ApplySimulationResults() desde HydraulicSimulationManager
|
||||
}
|
||||
|
||||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
// Actualizar propiedades desde la simulación hidráulica
|
||||
if (SimHydraulicPump != null)
|
||||
{
|
||||
CurrentFlow = SimHydraulicPump.CurrentFlow;
|
||||
CurrentPressure = SimHydraulicPump.CurrentPressure;
|
||||
|
||||
// Actualizar el color según el estado
|
||||
if (IsRunning)
|
||||
{
|
||||
if (hydraulicSimulationManager.EnableNPSHVerification)
|
||||
{
|
||||
var cavitationFactor = CavitationFactor;
|
||||
if (cavitationFactor < 0.1)
|
||||
ColorButton_oculto = Brushes.Red; // Cavitación severa
|
||||
else if (cavitationFactor < 0.5)
|
||||
ColorButton_oculto = Brushes.Orange; // Riesgo de cavitación
|
||||
else if (!CanOperateWithoutCavitation)
|
||||
ColorButton_oculto = Brushes.Yellow; // Condiciones límite
|
||||
else
|
||||
ColorButton_oculto = Brushes.Green; // Funcionamiento normal
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorButton_oculto = Brushes.Green; // Sin verificación NPSH
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorButton_oculto = Brushes.Gray; // Bomba apagada
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ucLoaded()
|
||||
{
|
||||
// Objeto hidráulico se crea en UpdateGeometryStart cuando inicia la simulación
|
||||
base.ucLoaded();
|
||||
UpdatePumpImage();
|
||||
}
|
||||
|
||||
public override void ucUnLoaded()
|
||||
{
|
||||
// Remover objeto hidráulico de la simulación
|
||||
if (SimHydraulicPump != null)
|
||||
{
|
||||
hydraulicSimulationManager.Remove(SimHydraulicPump);
|
||||
SimHydraulicPump = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IHydraulicComponent Implementation
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasHydraulicComponents => true;
|
||||
|
||||
public List<HydraulicNodeDefinition> GetHydraulicNodes()
|
||||
{
|
||||
var nodes = new List<HydraulicNodeDefinition>();
|
||||
|
||||
// Las bombas no crean nodos propios - deben estar conectadas a otros componentes para funcionar
|
||||
if (!HasConnectedComponents())
|
||||
{
|
||||
Debug.WriteLine($"Bomba {Nombre}: Sin componentes conectados - no puede funcionar");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.WriteLine($"Bomba {Nombre}: Conectada a otros componentes - lista para operar");
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public List<HydraulicElementDefinition> GetHydraulicElements()
|
||||
{
|
||||
var elements = new List<HydraulicElementDefinition>();
|
||||
|
||||
// Solo crear elementos si la bomba está conectada a otros componentes
|
||||
if (!HasConnectedComponents())
|
||||
{
|
||||
Debug.WriteLine($"Bomba {Nombre}: Sin conexiones - no se puede crear elemento hidráulico");
|
||||
return elements;
|
||||
}
|
||||
|
||||
if (HasHydraulicComponents)
|
||||
{
|
||||
// Calcular velocidad efectiva basada en el estado de la bomba
|
||||
double effectiveSpeed = IsRunning ? SpeedRatio : 0.0;
|
||||
|
||||
// Asegurar que la velocidad esté en rango válido
|
||||
effectiveSpeed = Math.Max(0.0, Math.Min(1.0, effectiveSpeed));
|
||||
|
||||
// Obtener los nombres de nodos correctos basados en las conexiones
|
||||
var (inletNode, outletNode) = GetConnectedNodeNames();
|
||||
|
||||
// Verificar que tenemos nodos válidos
|
||||
if (string.IsNullOrEmpty(inletNode) || string.IsNullOrEmpty(outletNode))
|
||||
{
|
||||
Debug.WriteLine($"Bomba {Nombre}: Nodos de conexión inválidos - inlet: '{inletNode}', outlet: '{outletNode}'");
|
||||
return elements;
|
||||
}
|
||||
|
||||
// Crear bomba con parámetros actuales
|
||||
Element pump;
|
||||
if (hydraulicSimulationManager.EnableNPSHVerification)
|
||||
{
|
||||
// Usar bomba con verificación de NPSH
|
||||
pump = new PumpHQWithSuctionCheck(
|
||||
h0: PumpHead,
|
||||
q0: MaxFlow,
|
||||
speedRel: effectiveSpeed,
|
||||
direction: PumpDirection,
|
||||
enableNpshCheck: true
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Usar bomba estándar
|
||||
pump = new PumpHQ(
|
||||
h0: PumpHead,
|
||||
q0: MaxFlow,
|
||||
speedRel: effectiveSpeed,
|
||||
direction: PumpDirection
|
||||
);
|
||||
}
|
||||
|
||||
// Asignar nombres de nodos para verificación de NPSH
|
||||
if (pump is PumpHQ pumpHQ)
|
||||
{
|
||||
pumpHQ.InletNodeName = inletNode;
|
||||
pumpHQ.OutletNodeName = outletNode;
|
||||
}
|
||||
|
||||
var pumpElement = new HydraulicElementDefinition(
|
||||
$"{Nombre}_Pump",
|
||||
inletNode,
|
||||
outletNode,
|
||||
pump,
|
||||
$"Bomba {Nombre}"
|
||||
);
|
||||
|
||||
elements.Add(pumpElement);
|
||||
|
||||
Debug.WriteLine($"Bomba {Nombre}: Creando elemento hidráulico - H0={PumpHead}m, Q0={MaxFlow:F6}m³/s ({MaxFlow*3600:F2}m³/h), Velocidad={effectiveSpeed:F2}, Dirección={PumpDirection}");
|
||||
Debug.WriteLine($"Bomba {Nombre}: Conectando {inletNode} -> {outletNode}");
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public void UpdateHydraulicProperties()
|
||||
{
|
||||
// Aquí se pueden hacer validaciones o ajustes antes de la simulación
|
||||
if (!IsRunning)
|
||||
{
|
||||
SpeedRatio = 0.0;
|
||||
}
|
||||
|
||||
if (SpeedRatio < 0.0) SpeedRatio = 0.0;
|
||||
if (SpeedRatio > 1.0) SpeedRatio = 1.0;
|
||||
|
||||
// Verificar condiciones de NPSH si está habilitada la verificación
|
||||
if (hydraulicSimulationManager.EnableNPSHVerification && IsRunning)
|
||||
{
|
||||
var npshAvailable = NPSHAvailable;
|
||||
var cavitationFactor = CavitationFactor;
|
||||
|
||||
if (cavitationFactor < 0.5)
|
||||
{
|
||||
Debug.WriteLine($"⚠️ Bomba {Nombre}: Riesgo de cavitación - Factor={cavitationFactor:F2}, NPSH_disponible={npshAvailable:F2}m");
|
||||
}
|
||||
|
||||
if (!CanOperateWithoutCavitation)
|
||||
{
|
||||
Debug.WriteLine($"❌ Bomba {Nombre}: NO puede operar sin cavitación - NPSH insuficiente");
|
||||
}
|
||||
}
|
||||
|
||||
//Debug.WriteLine($"Bomba {Nombre}: Velocidad={SpeedRatio:F2}, Funcionando={IsRunning}");
|
||||
}
|
||||
|
||||
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
||||
{
|
||||
// Buscar resultados para esta bomba
|
||||
string pumpBranchName = $"{Nombre}_Pump";
|
||||
var (inletNodeName, outletNodeName) = GetConnectedNodeNames();
|
||||
|
||||
if (flows.ContainsKey(pumpBranchName))
|
||||
{
|
||||
CurrentFlow = flows[pumpBranchName];
|
||||
//Debug.WriteLine($"Bomba {Nombre}: Aplicando caudal={CurrentFlow:F6} m³/s ({CurrentFlowLMin:F2} L/min)");
|
||||
}
|
||||
|
||||
if (pressures.ContainsKey(inletNodeName))
|
||||
{
|
||||
CurrentPressure = pressures[inletNodeName];
|
||||
//Debug.WriteLine($"Bomba {Nombre}: Presión entrada={CurrentPressure:F2} Pa ({CurrentPressureBar:F2} bar)");
|
||||
}
|
||||
else if (pressures.ContainsKey(outletNodeName))
|
||||
{
|
||||
CurrentPressure = pressures[outletNodeName];
|
||||
//Debug.WriteLine($"Bomba {Nombre}: Presión salida={CurrentPressure:F2} Pa ({CurrentPressureBar:F2} bar)");
|
||||
}
|
||||
|
||||
// Disparar PropertyChanged para actualizar el UI
|
||||
OnPropertyChanged(nameof(CurrentFlow));
|
||||
OnPropertyChanged(nameof(CurrentFlowLMin));
|
||||
OnPropertyChanged(nameof(CurrentPressure));
|
||||
OnPropertyChanged(nameof(CurrentPressureBar));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IHydraulicFlowReceiver Implementation
|
||||
|
||||
public void SetFlow(double flow)
|
||||
{
|
||||
CurrentFlow = flow;
|
||||
}
|
||||
|
||||
public double GetFlow()
|
||||
{
|
||||
return CurrentFlow;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IHydraulicPressureReceiver Implementation
|
||||
|
||||
public void SetPressure(double pressure)
|
||||
{
|
||||
CurrentPressure = pressure;
|
||||
}
|
||||
|
||||
public double GetPressure()
|
||||
{
|
||||
return CurrentPressure;
|
||||
}
|
||||
|
||||
// Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Verifica si la bomba tiene componentes conectados a través de tuberías
|
||||
/// </summary>
|
||||
private bool HasConnectedComponents()
|
||||
{
|
||||
if (_mainViewModel == null) return false;
|
||||
|
||||
// Buscar tuberías que conecten esta bomba con otros componentes
|
||||
var connectedPipes = _mainViewModel.ObjetosSimulables
|
||||
.OfType<osHydPipe>()
|
||||
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
|
||||
.ToList();
|
||||
|
||||
return connectedPipes.Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene los nombres de nodos correctos para la bomba basándose en las conexiones
|
||||
/// </summary>
|
||||
private (string inletNode, string outletNode) GetConnectedNodeNames()
|
||||
{
|
||||
if (_mainViewModel == null)
|
||||
return (string.Empty, string.Empty);
|
||||
|
||||
string inletNode = string.Empty;
|
||||
string outletNode = string.Empty;
|
||||
|
||||
// Buscar tuberías conectadas a esta bomba
|
||||
var connectedPipes = _mainViewModel.ObjetosSimulables
|
||||
.OfType<osHydPipe>()
|
||||
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
|
||||
.ToList();
|
||||
|
||||
foreach (var pipe in connectedPipes)
|
||||
{
|
||||
if (pipe.Id_ComponenteB == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteA))
|
||||
{
|
||||
// Esta bomba es el destino, el componente A es la fuente (inlet)
|
||||
inletNode = pipe.Id_ComponenteA;
|
||||
//Debug.WriteLine($"Bomba {Nombre}: Nodo inlet identificado como '{inletNode}'");
|
||||
}
|
||||
else if (pipe.Id_ComponenteA == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteB))
|
||||
{
|
||||
// Esta bomba es la fuente, el componente B es el destino (outlet)
|
||||
outletNode = pipe.Id_ComponenteB;
|
||||
//Debug.WriteLine($"Bomba {Nombre}: Nodo outlet identificado como '{outletNode}'");
|
||||
}
|
||||
}
|
||||
|
||||
return (inletNode, outletNode);
|
||||
}
|
||||
|
||||
private void InvalidateHydraulicNetwork()
|
||||
{
|
||||
hydraulicSimulationManager?.InvalidateNetwork();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Proveedor de items para la dirección de la bomba
|
||||
/// </summary>
|
||||
public class PumpDirectionItemsSource : IItemsSource
|
||||
{
|
||||
public Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection GetValues()
|
||||
{
|
||||
var items = new Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection();
|
||||
items.Add(1, "Adelante (+1)");
|
||||
items.Add(-1, "Atrás (-1)");
|
||||
return items;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,986 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Windows.Media;
|
||||
using CtrEditor.HydraulicSimulator;
|
||||
using CtrEditor.ObjetosSim;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using HydraulicSimulator.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using LibS7Adv;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
/// <summary>
|
||||
/// Tanque hidráulico con gestión dinámica de nivel y presión configurable
|
||||
/// </summary>
|
||||
public partial class osHydTank : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
|
||||
{
|
||||
// Referencia al objeto de simulación hidráulica específico
|
||||
private simHydraulicTank SimHydraulicTank;
|
||||
public static string NombreCategoria() => "Componentes Hidráulicos";
|
||||
|
||||
public static string NombreClase() => "Tanque Hidráulico";
|
||||
|
||||
// Private Fields
|
||||
|
||||
private double _tankPressure = 101325.0; // Pa (1 atm por defecto)
|
||||
private double _currentLevel = 1.0; // m
|
||||
private double _maxLevel = 2.0; // m
|
||||
private double _minLevel = 0.1; // m
|
||||
private double _crossSectionalArea = 1.0; // m²
|
||||
private double _currentVolume = 1.0; // m³
|
||||
private double _maxVolume = 2.0; // m³
|
||||
private double _inletFlow = 0.0; // m³/s
|
||||
private double _outletFlow = 0.0; // m³/s
|
||||
private double _currentPressure = 101325.0; // Pa
|
||||
private bool _isFixedPressure = true;
|
||||
private HydraulicTankType _tankType = HydraulicTankType.Intermediate;
|
||||
private string _connectedInletPipe = "";
|
||||
private string _connectedOutletPipe = "";
|
||||
private double _lastUpdateTime = 0.0;
|
||||
[JsonIgnore]
|
||||
private object _levelLock = new object();
|
||||
private PropertyChangedEventHandler? inletPropertyChangedHandler;
|
||||
private PropertyChangedEventHandler? outletPropertyChangedHandler;
|
||||
|
||||
|
||||
// Enums
|
||||
public enum HydraulicTankType
|
||||
{
|
||||
[Description("Tanque de succión (inicio del sistema)")]
|
||||
Suction,
|
||||
[Description("Tanque intermedio (buffer)")]
|
||||
Intermediate,
|
||||
[Description("Tanque de almacenamiento")]
|
||||
Storage,
|
||||
[Description("Tanque de proceso")]
|
||||
Process
|
||||
}
|
||||
|
||||
|
||||
// Properties
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("🎨 Apariencia")]
|
||||
[property: Description("Tamaño visual del tanque")]
|
||||
[property: Name("Tamaño")]
|
||||
public float tamano;
|
||||
|
||||
// Propiedades visuales específicas del tanque
|
||||
[ObservableProperty]
|
||||
[property: Category("🎨 Apariencia")]
|
||||
[property: Description("Color visual del tanque")]
|
||||
[property: Name("Color")]
|
||||
private Brush colorButton_oculto = Brushes.LightBlue;
|
||||
|
||||
|
||||
|
||||
// Constructor
|
||||
public osHydTank()
|
||||
{
|
||||
// Inicializar el lock primero para thread safety
|
||||
EnsureLockInitialized();
|
||||
|
||||
try
|
||||
{
|
||||
Nombre = "Tanque Hidráulico";
|
||||
Tamano = 1.0f; // Usar un tamaño mayor para mayor visibilidad
|
||||
// Inicializar dimensiones usando las propiedades de osBase
|
||||
Ancho = 0.30f;
|
||||
Alto = 0.40f;
|
||||
Angulo = 0f;
|
||||
// Asegurar que el movimiento esté habilitado
|
||||
Lock_movement = false;
|
||||
|
||||
// No cargar imagen aquí - se carga en ucLoaded()
|
||||
// para evitar problemas de serialización
|
||||
|
||||
// Cálculos seguros
|
||||
SafeUpdateVolumeCalculations();
|
||||
IsVisFilter = true; // Asegurar que el componente hidráulico sea visible en filtros
|
||||
SafeUpdateTankPressure();
|
||||
|
||||
// Debug: Confirmar que el constructor se ejecuta
|
||||
Debug.WriteLine($"osHydTank Constructor: Nombre='{Nombre}', Tamaño={Tamano}, ZIndex={zIndex_fromFrames}, IsVisFilter={IsVisFilter}, Lock_movement={Lock_movement}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error in osHydTank constructor: {ex.Message}");
|
||||
// Inicializar valores por defecto mínimos
|
||||
InitializeDefaults();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Constructor y Métodos Base
|
||||
|
||||
private string nombre = NombreClase();
|
||||
|
||||
[Category("🏷️ Identificación")]
|
||||
[Description("Nombre identificativo del objeto")]
|
||||
[Name("Nombre")]
|
||||
public override string Nombre
|
||||
{
|
||||
get => nombre;
|
||||
set => SetProperty(ref nombre, value);
|
||||
}
|
||||
|
||||
public override void OnMove(float LeftPixels, float TopPixels)
|
||||
{
|
||||
// Método básico de movimiento como otros objetos
|
||||
// Debug para verificar que se llama
|
||||
Debug.WriteLine($"osHydTank.OnMove: LeftPixels={LeftPixels}, TopPixels={TopPixels}, Left={Left}, Top={Top}");
|
||||
}
|
||||
|
||||
public override void OnResize(float Delta_Width, float Delta_Height)
|
||||
{
|
||||
Tamano += Delta_Width + Delta_Height;
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStart()
|
||||
{
|
||||
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
|
||||
if (SimHydraulicTank == null)
|
||||
{
|
||||
SimHydraulicTank = hydraulicSimulationManager.AddTank(TankPressure, CurrentLevel, MaxLevel, MinLevel, CrossSectionalArea, IsFixedPressure);
|
||||
SimHydraulicTank.SimObjectType = "HydraulicTank";
|
||||
SimHydraulicTank.WpfObject = this;
|
||||
SimHydraulicTank.Nombre = Nombre;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Actualizar propiedades si el objeto ya existe
|
||||
SimHydraulicTank.TankPressure = TankPressure;
|
||||
SimHydraulicTank.CurrentLevel = CurrentLevel;
|
||||
SimHydraulicTank.MaxLevel = MaxLevel;
|
||||
SimHydraulicTank.MinLevel = MinLevel;
|
||||
SimHydraulicTank.CrossSectionalArea = CrossSectionalArea;
|
||||
SimHydraulicTank.IsFixedPressure = IsFixedPressure;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStep()
|
||||
{
|
||||
// Los objetos hidráulicos actualizan sus resultados
|
||||
// a través de ApplySimulationResults() desde HydraulicSimulationManager
|
||||
}
|
||||
|
||||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
// Actualizar propiedades desde la simulación hidráulica
|
||||
if (SimHydraulicTank != null)
|
||||
{
|
||||
InletFlow = SimHydraulicTank.InletFlow;
|
||||
OutletFlow = SimHydraulicTank.OutletFlow;
|
||||
CurrentPressure = SimHydraulicTank.CurrentPressure;
|
||||
|
||||
// Actualizar nivel del tanque basado en el balance de flujo
|
||||
double deltaTime = elapsedMilliseconds / 1000.0; // Convertir a segundos
|
||||
if (deltaTime > 0)
|
||||
{
|
||||
UpdateLevelFromFlowBalance(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar el color según el estado del tanque
|
||||
var percentage = FillPercentage;
|
||||
if (percentage < 20)
|
||||
ColorButton_oculto = Brushes.Red;
|
||||
else if (percentage < 50)
|
||||
ColorButton_oculto = Brushes.Orange;
|
||||
else if (percentage < 80)
|
||||
ColorButton_oculto = Brushes.Yellow;
|
||||
else
|
||||
ColorButton_oculto = Brushes.LightBlue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Properties - Configuration
|
||||
|
||||
[Category("🏗️ Configuración del Tanque")]
|
||||
[DisplayName("Tipo de tanque")]
|
||||
[Description("Función del tanque en el sistema hidráulico")]
|
||||
public HydraulicTankType TankType
|
||||
{
|
||||
get => _tankType;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _tankType, value))
|
||||
{
|
||||
UpdateTankConfiguration();
|
||||
InvalidateHydraulicNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("🏗️ Configuración del Tanque")]
|
||||
[DisplayName("Área sección transversal")]
|
||||
[Description("Área de la sección transversal del tanque (m²)")]
|
||||
public double CrossSectionalArea
|
||||
{
|
||||
get => _crossSectionalArea;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _crossSectionalArea, Math.Max(0.1, value)))
|
||||
{
|
||||
SafeUpdateVolumeCalculations();
|
||||
InvalidateHydraulicNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("🏗️ Configuración del Tanque")]
|
||||
[DisplayName("Nivel máximo")]
|
||||
[Description("Nivel máximo permitido en el tanque (m)")]
|
||||
public double MaxLevel
|
||||
{
|
||||
get => _maxLevel;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _maxLevel, Math.Max(0.2, value)))
|
||||
{
|
||||
if (_currentLevel > _maxLevel)
|
||||
CurrentLevel = _maxLevel;
|
||||
SafeUpdateVolumeCalculations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("🏗️ Configuración del Tanque")]
|
||||
[DisplayName("Nivel mínimo")]
|
||||
[Description("Nivel mínimo permitido en el tanque (m)")]
|
||||
public double MinLevel
|
||||
{
|
||||
get => _minLevel;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _minLevel, Math.Max(0.0, Math.Min(value, _maxLevel - 0.1))))
|
||||
{
|
||||
if (_currentLevel < _minLevel)
|
||||
CurrentLevel = _minLevel;
|
||||
SafeUpdateVolumeCalculations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Properties - Pressure
|
||||
|
||||
[Category("🔧 Sistema de Presión")]
|
||||
[DisplayName("Presión fija")]
|
||||
[Description("Si está habilitado, la presión se mantiene constante")]
|
||||
public bool IsFixedPressure
|
||||
{
|
||||
get => _isFixedPressure;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isFixedPressure, value))
|
||||
{
|
||||
SafeUpdateTankPressure();
|
||||
InvalidateHydraulicNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("🔧 Sistema de Presión")]
|
||||
[DisplayName("Presión del tanque")]
|
||||
[Description("Presión mantenida en el tanque (Pa). En sistemas reales controlada por PID")]
|
||||
public double TankPressure
|
||||
{
|
||||
get => _tankPressure;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _tankPressure, Math.Max(0, value)))
|
||||
{
|
||||
SafeUpdateTankPressure();
|
||||
InvalidateHydraulicNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("🔧 Sistema de Presión")]
|
||||
[DisplayName("Presión (bar)")]
|
||||
[Description("Presión del tanque en bar")]
|
||||
[JsonIgnore]
|
||||
public double TankPressureBar
|
||||
{
|
||||
get => TankPressure / 100000.0;
|
||||
set => TankPressure = value * 100000.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Properties - Current State
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Nivel actual")]
|
||||
[Description("Nivel actual de líquido en el tanque (m)")]
|
||||
[JsonIgnore]
|
||||
public double CurrentLevel
|
||||
{
|
||||
get => _currentLevel;
|
||||
private set
|
||||
{
|
||||
EnsureLockInitialized();
|
||||
lock (_levelLock)
|
||||
{
|
||||
var clampedLevel = Math.Max(_minLevel, Math.Min(_maxLevel, value));
|
||||
if (SetProperty(ref _currentLevel, clampedLevel))
|
||||
{
|
||||
SafeUpdateVolumeCalculations();
|
||||
OnPropertyChanged(nameof(FillPercentage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Volumen actual")]
|
||||
[Description("Volumen actual de líquido en el tanque (m³)")]
|
||||
[JsonIgnore]
|
||||
public double CurrentVolume
|
||||
{
|
||||
get => _currentVolume;
|
||||
private set => SetProperty(ref _currentVolume, value);
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Volumen máximo")]
|
||||
[Description("Volumen máximo del tanque (m³)")]
|
||||
[JsonIgnore]
|
||||
public double MaxVolume
|
||||
{
|
||||
get => _maxVolume;
|
||||
private set => SetProperty(ref _maxVolume, value);
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Porcentaje llenado")]
|
||||
[Description("Porcentaje de llenado del tanque")]
|
||||
[JsonIgnore]
|
||||
public double FillPercentage => (_currentLevel - _minLevel) / (_maxLevel - _minLevel) * 100.0;
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Flujo entrada")]
|
||||
[Description("Flujo de entrada actual (m³/s)")]
|
||||
[JsonIgnore]
|
||||
public double InletFlow
|
||||
{
|
||||
get => _inletFlow;
|
||||
private set => SetProperty(ref _inletFlow, value);
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Flujo salida")]
|
||||
[Description("Flujo de salida actual (m³/s)")]
|
||||
[JsonIgnore]
|
||||
public double OutletFlow
|
||||
{
|
||||
get => _outletFlow;
|
||||
private set => SetProperty(ref _outletFlow, value);
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Balance flujo")]
|
||||
[Description("Balance de flujo (entrada - salida) (m³/s)")]
|
||||
[JsonIgnore]
|
||||
public double FlowBalance => InletFlow - OutletFlow;
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Presión actual")]
|
||||
[Description("Presión actual en el tanque (Pa)")]
|
||||
[JsonIgnore]
|
||||
public double CurrentPressure
|
||||
{
|
||||
get => _currentPressure;
|
||||
private set => SetProperty(ref _currentPressure, value);
|
||||
}
|
||||
|
||||
[Category("📊 Estado Actual")]
|
||||
[DisplayName("Presión actual (bar)")]
|
||||
[Description("Presión actual en bar")]
|
||||
[JsonIgnore]
|
||||
public double CurrentPressureBar => CurrentPressure / 100000.0;
|
||||
|
||||
|
||||
|
||||
// Properties - Connections
|
||||
|
||||
[Category("🔗 Conexiones")]
|
||||
[DisplayName("Tubería entrada")]
|
||||
[Description("Nombre de la tubería conectada a la entrada")]
|
||||
public string ConnectedInletPipe
|
||||
{
|
||||
get => _connectedInletPipe;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _connectedInletPipe, value ?? ""))
|
||||
{
|
||||
ManageInletConnection();
|
||||
InvalidateHydraulicNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("🔗 Conexiones")]
|
||||
[DisplayName("Tubería salida")]
|
||||
[Description("Nombre de la tubería conectada a la salida")]
|
||||
public string ConnectedOutletPipe
|
||||
{
|
||||
get => _connectedOutletPipe;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _connectedOutletPipe, value ?? ""))
|
||||
{
|
||||
ManageOutletConnection();
|
||||
InvalidateHydraulicNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IHydraulicComponent Implementation
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasHydraulicComponents => true;
|
||||
|
||||
public List<HydraulicNodeDefinition> GetHydraulicNodes()
|
||||
{
|
||||
var nodes = new List<HydraulicNodeDefinition>();
|
||||
|
||||
// El tanque siempre es un nodo de presión fija si está configurado así
|
||||
if (IsFixedPressure)
|
||||
{
|
||||
nodes.Add(new HydraulicNodeDefinition(Nombre, true, TankPressure, GetTankDescription()));
|
||||
//Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F0} Pa ({TankPressureBar:F2} bar)");
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, GetTankDescription()));
|
||||
//Debug.WriteLine($"Tanque {Nombre}: Nodo de presión libre creado");
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public List<HydraulicElementDefinition> GetHydraulicElements()
|
||||
{
|
||||
// Los tanques no generan elementos hidráulicos, solo nodos
|
||||
return new List<HydraulicElementDefinition>();
|
||||
}
|
||||
|
||||
public void UpdateHydraulicProperties()
|
||||
{
|
||||
// Actualizar propiedades antes de la simulación
|
||||
SafeUpdateTankPressure();
|
||||
SafeUpdateVolumeCalculations();
|
||||
}
|
||||
|
||||
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
||||
{
|
||||
var deltaTime = GetDeltaTime();
|
||||
|
||||
// Obtener flujos conectados
|
||||
UpdateFlowsFromConnectedPipes(flows);
|
||||
|
||||
// Actualizar presión
|
||||
if (pressures.ContainsKey(Nombre))
|
||||
{
|
||||
CurrentPressure = pressures[Nombre];
|
||||
}
|
||||
|
||||
// Actualizar nivel basado en balance de flujo
|
||||
UpdateLevelFromFlowBalance(deltaTime);
|
||||
|
||||
if (VerboseLogging())
|
||||
{
|
||||
Debug.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevel:F2}m ({FillPercentage:F1}%), " +
|
||||
$"Flujo_entrada={InletFlow:F6}m³/s, Flujo_salida={OutletFlow:F6}m³/s, " +
|
||||
$"Balance={FlowBalance:F6}m³/s, Presión={CurrentPressure:F0}Pa");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// UI Properties
|
||||
|
||||
/// <summary>
|
||||
/// Color del nivel de líquido
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public SolidColorBrush LevelColor
|
||||
{
|
||||
get
|
||||
{
|
||||
var percentage = FillPercentage;
|
||||
if (percentage < 20) return new SolidColorBrush(Colors.Red);
|
||||
if (percentage < 50) return new SolidColorBrush(Colors.Orange);
|
||||
if (percentage < 80) return new SolidColorBrush(Colors.Yellow);
|
||||
return new SolidColorBrush(Colors.LightBlue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Color del borde del nivel de líquido
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public SolidColorBrush LevelBorderColor
|
||||
{
|
||||
get
|
||||
{
|
||||
var percentage = FillPercentage;
|
||||
if (percentage < 20) return new SolidColorBrush(Colors.DarkRed);
|
||||
if (percentage < 50) return new SolidColorBrush(Colors.DarkOrange);
|
||||
if (percentage < 80) return new SolidColorBrush(Colors.DarkGoldenrod);
|
||||
return new SolidColorBrush(Colors.DarkBlue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indica si hay conexión de entrada
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool HasInletConnection => !string.IsNullOrEmpty(ConnectedInletPipe);
|
||||
|
||||
/// <summary>
|
||||
/// Indica si hay conexión de salida
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool HasOutletConnection => !string.IsNullOrEmpty(ConnectedOutletPipe);
|
||||
|
||||
/// <summary>
|
||||
/// Indicador del tipo de tanque
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string TankTypeIndicator
|
||||
{
|
||||
get
|
||||
{
|
||||
return TankType switch
|
||||
{
|
||||
HydraulicTankType.Suction => "S",
|
||||
HydraulicTankType.Intermediate => "I",
|
||||
HydraulicTankType.Storage => "A",
|
||||
HydraulicTankType.Process => "P",
|
||||
_ => "T"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Balance de flujo en L/min
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double FlowBalanceLMin => FlowBalance * 60000; // m³/s a L/min
|
||||
|
||||
/// <summary>
|
||||
/// Color del balance de flujo
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public SolidColorBrush FlowBalanceColor
|
||||
{
|
||||
get
|
||||
{
|
||||
var balance = FlowBalance;
|
||||
if (Math.Abs(balance) < 0.0001) return new SolidColorBrush(Colors.Gray);
|
||||
return balance > 0 ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posición Y de la línea de nivel medio (50%)
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double MidLevelY
|
||||
{
|
||||
get
|
||||
{
|
||||
var tankHeight = Tamano * 100; // Convertir a píxeles aproximados
|
||||
return tankHeight * 0.5; // 50% de altura
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posición Y de la línea de nivel mínimo
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double MinLevelY
|
||||
{
|
||||
get
|
||||
{
|
||||
var tankHeight = Tamano * 100; // Convertir a píxeles aproximados
|
||||
var minPercentage = (MinLevel - MinLevel) / (MaxLevel - MinLevel) * 100;
|
||||
return tankHeight - (tankHeight * (minPercentage + 10) / 100); // 10% del nivel mínimo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Private Methods
|
||||
|
||||
private void InitializeDefaults()
|
||||
{
|
||||
// Valores mínimos para que el objeto sea funcional
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(nombre))
|
||||
nombre = "Tanque Hidráulico";
|
||||
|
||||
if (Tamano <= 0)
|
||||
Tamano = 1.0f;
|
||||
|
||||
if (Ancho <= 0)
|
||||
Ancho = 0.30f;
|
||||
|
||||
if (Alto <= 0)
|
||||
Alto = 0.40f;
|
||||
|
||||
SafeUpdateVolumeCalculations();
|
||||
SafeUpdateTankPressure();
|
||||
|
||||
Debug.WriteLine("osHydTank: Initialized with default values");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error in InitializeDefaults: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void SafeUpdateVolumeCalculations()
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentVolume = _currentLevel * _crossSectionalArea;
|
||||
_maxVolume = _maxLevel * _crossSectionalArea;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error in SafeUpdateVolumeCalculations: {ex.Message}");
|
||||
// Usar valores por defecto seguros
|
||||
_currentVolume = 1.0;
|
||||
_maxVolume = 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
private void SafeUpdateTankPressure()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsFixedPressure)
|
||||
{
|
||||
CurrentPressure = TankPressure;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error in SafeUpdateTankPressure: {ex.Message}");
|
||||
// Usar presión atmosférica por defecto
|
||||
_currentPressure = 101325.0;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetTankDescription()
|
||||
{
|
||||
return TankType switch
|
||||
{
|
||||
HydraulicTankType.Suction => "Tanque de succión",
|
||||
HydraulicTankType.Intermediate => "Tanque intermedio",
|
||||
HydraulicTankType.Storage => "Tanque de almacenamiento",
|
||||
HydraulicTankType.Process => "Tanque de proceso",
|
||||
_ => "Tanque hidráulico"
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateTankConfiguration()
|
||||
{
|
||||
// Configuración automática según el tipo de tanque
|
||||
switch (TankType)
|
||||
{
|
||||
case HydraulicTankType.Suction:
|
||||
// Tanques de succión típicamente a presión atmosférica
|
||||
if (TankPressure < 50000) // Si no se ha configurado manualmente
|
||||
TankPressure = 101325.0; // 1 atm
|
||||
IsFixedPressure = true;
|
||||
break;
|
||||
|
||||
case HydraulicTankType.Intermediate:
|
||||
case HydraulicTankType.Storage:
|
||||
case HydraulicTankType.Process:
|
||||
// Estos pueden tener presión variable o fija según el proceso
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVolumeCalculations()
|
||||
{
|
||||
CurrentVolume = CurrentLevel * CrossSectionalArea;
|
||||
MaxVolume = MaxLevel * CrossSectionalArea;
|
||||
}
|
||||
|
||||
private void UpdateTankPressure()
|
||||
{
|
||||
if (IsFixedPressure)
|
||||
{
|
||||
CurrentPressure = TankPressure;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFlowsFromConnectedPipes(Dictionary<string, double> flows)
|
||||
{
|
||||
InletFlow = 0.0;
|
||||
OutletFlow = 0.0;
|
||||
|
||||
// Buscar flujos en las ramas que involucren este tanque
|
||||
foreach (var flow in flows)
|
||||
{
|
||||
var branchName = flow.Key;
|
||||
var flowValue = flow.Value;
|
||||
|
||||
// Buscar si esta rama conecta este tanque
|
||||
if (branchName.Contains("Pump") && HasPumpConnection(branchName, flowValue))
|
||||
{
|
||||
continue; // Ya manejado en HasPumpConnection
|
||||
}
|
||||
|
||||
// Buscar otras conexiones directas al tanque por nombre
|
||||
if (branchName.Contains(Nombre))
|
||||
{
|
||||
if (flowValue > 0)
|
||||
{
|
||||
// Flujo positivo hacia o desde el tanque
|
||||
if (IsFlowTowardsTank(branchName))
|
||||
{
|
||||
InletFlow += flowValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
OutletFlow += flowValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasPumpConnection(string branchName, double flowValue)
|
||||
{
|
||||
if (_mainViewModel == null) return false;
|
||||
|
||||
// Buscar si hay una bomba conectada que esté generando este flujo
|
||||
var connectedPipes = _mainViewModel.ObjetosSimulables
|
||||
.OfType<osHydPipe>()
|
||||
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
|
||||
.ToList();
|
||||
|
||||
foreach (var pipe in connectedPipes)
|
||||
{
|
||||
if (pipe.Id_ComponenteA == Nombre)
|
||||
{
|
||||
// Esta conexión sale del tanque
|
||||
if (branchName.Contains(pipe.Id_ComponenteB) && branchName.Contains("Pump"))
|
||||
{
|
||||
OutletFlow += Math.Abs(flowValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (pipe.Id_ComponenteB == Nombre)
|
||||
{
|
||||
// Esta conexión llega al tanque
|
||||
if (branchName.Contains(pipe.Id_ComponenteA) && branchName.Contains("Pump"))
|
||||
{
|
||||
InletFlow += Math.Abs(flowValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsFlowTowardsTank(string branchName)
|
||||
{
|
||||
// Determinar la dirección del flujo basándose en el nombre de la rama
|
||||
return branchName.EndsWith($" -> {Nombre}") || branchName.EndsWith($"_{Nombre}");
|
||||
}
|
||||
|
||||
private void UpdateLevelFromFlowBalance(double deltaTime)
|
||||
{
|
||||
if (deltaTime <= 0 || Math.Abs(deltaTime) > 1.0) // Evitar deltas muy grandes
|
||||
return;
|
||||
|
||||
// Balance de masa: cambio_volumen = (flujo_entrada - flujo_salida) * tiempo
|
||||
var volumeChange = FlowBalance * deltaTime;
|
||||
var levelChange = volumeChange / CrossSectionalArea;
|
||||
|
||||
// Actualizar nivel con límites
|
||||
var newLevel = CurrentLevel + levelChange;
|
||||
CurrentLevel = Math.Max(MinLevel, Math.Min(MaxLevel, newLevel));
|
||||
|
||||
// Debug para verificar cambios
|
||||
if (VerboseLogging() && Math.Abs(levelChange) > 0.0001)
|
||||
{
|
||||
Debug.WriteLine($"Tanque {Nombre}: Δt={deltaTime:F3}s, ΔVolumen={volumeChange:F6}m³, ΔNivel={levelChange:F6}m, Nivel={CurrentLevel:F3}m");
|
||||
}
|
||||
}
|
||||
|
||||
private double GetDeltaTime()
|
||||
{
|
||||
var currentTime = Environment.TickCount64 / 1000.0; // segundos
|
||||
var deltaTime = _lastUpdateTime > 0 ? currentTime - _lastUpdateTime : 0.0;
|
||||
_lastUpdateTime = currentTime;
|
||||
|
||||
// Limitar delta time para evitar saltos grandes
|
||||
return Math.Min(deltaTime, 0.1); // máximo 100ms
|
||||
}
|
||||
|
||||
private bool VerboseLogging()
|
||||
{
|
||||
return _mainViewModel?.hydraulicSimulationManager?.VerboseOutput ?? false;
|
||||
}
|
||||
|
||||
private void ManageInletConnection()
|
||||
{
|
||||
// Gestionar conexión de entrada
|
||||
// Implementar lógica de conexión específica si es necesario
|
||||
}
|
||||
|
||||
private void ManageOutletConnection()
|
||||
{
|
||||
// Gestionar conexión de salida
|
||||
// Implementar lógica de conexión específica si es necesario
|
||||
}
|
||||
|
||||
private void InvalidateHydraulicNetwork()
|
||||
{
|
||||
_mainViewModel?.hydraulicSimulationManager?.InvalidateNetwork();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IHydraulicFlowReceiver Implementation
|
||||
|
||||
public void SetFlow(double flow)
|
||||
{
|
||||
// Este método puede ser llamado por tuberías conectadas
|
||||
// La lógica de flujo se maneja en ApplyHydraulicResults
|
||||
}
|
||||
|
||||
public double GetFlow()
|
||||
{
|
||||
return FlowBalance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IHydraulicPressureReceiver Implementation
|
||||
|
||||
public void SetPressure(double pressure)
|
||||
{
|
||||
if (!IsFixedPressure)
|
||||
{
|
||||
CurrentPressure = pressure;
|
||||
}
|
||||
}
|
||||
|
||||
public double GetPressure()
|
||||
{
|
||||
return CurrentPressure;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ZIndex Implementation
|
||||
|
||||
public int zIndex_fromFrames { get; set; } = 2;
|
||||
|
||||
public ZIndexEnum ZIndex_Base()
|
||||
{
|
||||
return ZIndexEnum.Estaticos;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IosBase Implementation
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// Inicialización del tanque para la simulación
|
||||
SafeUpdateVolumeCalculations();
|
||||
SafeUpdateTankPressure();
|
||||
}
|
||||
|
||||
public void Inicializar(int valorInicial)
|
||||
{
|
||||
// Inicialización con valor inicial
|
||||
SafeUpdateVolumeCalculations();
|
||||
SafeUpdateTankPressure();
|
||||
}
|
||||
|
||||
public void Disposing()
|
||||
{
|
||||
// Desconectar event handlers si existen
|
||||
if (inletPropertyChangedHandler != null)
|
||||
{
|
||||
// Aquí se desconectarían los handlers cuando se implementen las conexiones
|
||||
// Similar a como lo hace osHydDischargeTank
|
||||
}
|
||||
|
||||
if (outletPropertyChangedHandler != null)
|
||||
{
|
||||
// Desconectar outlet handler cuando se implemente
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Serialization Support
|
||||
|
||||
/// <summary>
|
||||
/// Se llama antes de la serialización para asegurar que el lock esté disponible
|
||||
/// </summary>
|
||||
private void EnsureLockInitialized()
|
||||
{
|
||||
if (_levelLock == null)
|
||||
_levelLock = new object();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// osBase Overrides
|
||||
|
||||
public override void ucLoaded()
|
||||
{
|
||||
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
|
||||
// crear el objeto de simulacion
|
||||
base.ucLoaded();
|
||||
|
||||
Debug.WriteLine($"osHydTank.ucLoaded(): Componente hidráulico cargado correctamente");
|
||||
|
||||
// Inicialización específica del tanque hidráulico
|
||||
SafeUpdateVolumeCalculations();
|
||||
SafeUpdateTankPressure();
|
||||
|
||||
// Debug: Confirmar que el UserControl se está cargando
|
||||
Debug.WriteLine($"osHydTank.ucLoaded(): Tanque '{Nombre}' cargado - Tamaño: {Tamano}, ZIndex: {zIndex_fromFrames}");
|
||||
}
|
||||
|
||||
public override void ucUnLoaded()
|
||||
{
|
||||
// Remover objeto hidráulico de la simulación
|
||||
if (SimHydraulicTank != null)
|
||||
{
|
||||
hydraulicSimulationManager.Remove(SimHydraulicTank);
|
||||
SimHydraulicTank = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
<UserControl x:Class="CtrEditor.ObjetosSim.ucHydPipe"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.DataContext>
|
||||
<vm:osHydPipe Ancho="2.0" Alto="0.2"/>
|
||||
</UserControl.DataContext>
|
||||
|
||||
<Canvas RenderTransformOrigin="0,0">
|
||||
<Canvas.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="{Binding Angulo}" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Canvas.RenderTransform>
|
||||
|
||||
<!-- Cuerpo de la tubería -->
|
||||
<Rectangle x:Name="rectPipe"
|
||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Fill="{Binding ColorButton_oculto}"
|
||||
Stroke="Black"
|
||||
StrokeThickness="2"
|
||||
RadiusX="5"
|
||||
RadiusY="5"/>
|
||||
|
||||
<!-- Etiqueta con Viewbox para escalado -->
|
||||
<Viewbox Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Stretch="Uniform">
|
||||
<Label Content="{Binding Nombre}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
FontSize="18"
|
||||
Opacity="0.9"
|
||||
Foreground="White"/>
|
||||
</Viewbox>
|
||||
|
||||
<!-- Indicador de conexión entrada -->
|
||||
<Ellipse x:Name="ConnectionIn"
|
||||
Fill="Green"
|
||||
Width="8"
|
||||
Height="8"
|
||||
Canvas.Left="-4"
|
||||
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"/>
|
||||
|
||||
<!-- Indicador de conexión salida -->
|
||||
<Ellipse x:Name="ConnectionOut"
|
||||
Fill="Red"
|
||||
Width="8"
|
||||
Height="8"
|
||||
Canvas.Right="-4"
|
||||
Canvas.Top="{Binding Alto, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}"/>
|
||||
|
||||
<!-- Indicador de flujo -->
|
||||
<Border x:Name="FlowIndicator"
|
||||
Background="Yellow"
|
||||
CornerRadius="2"
|
||||
Visibility="Collapsed"
|
||||
Canvas.Top="-20"
|
||||
Canvas.Left="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=0.5}">
|
||||
<TextBlock x:Name="FlowText"
|
||||
Text="0.00"
|
||||
FontSize="6"
|
||||
Foreground="Black"
|
||||
Margin="2"/>
|
||||
</Border>
|
||||
|
||||
</Canvas>
|
||||
</UserControl>
|
|
@ -1,70 +0,0 @@
|
|||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
using CtrEditor.ObjetosSim;
|
||||
using CtrEditor.FuncionesBase;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ucHydPipe.xaml
|
||||
/// </summary>
|
||||
public partial class ucHydPipe : UserControl, IDataContainer
|
||||
{
|
||||
public osBase? Datos { get; set; }
|
||||
public int zIndex_fromFrames { get; set; } = 0;
|
||||
|
||||
private bool _isHighlighted = false;
|
||||
|
||||
public ucHydPipe()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.Loaded += OnLoaded;
|
||||
this.Unloaded += OnUnloaded;
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Datos?.ucLoaded();
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Datos?.ucUnLoaded();
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (DataContext is osHydPipe pipe)
|
||||
{
|
||||
Datos = pipe;
|
||||
}
|
||||
}
|
||||
|
||||
#region IDataContainer Implementation
|
||||
|
||||
public void Highlight(bool state)
|
||||
{
|
||||
_isHighlighted = state;
|
||||
|
||||
if (state)
|
||||
{
|
||||
rectPipe.Stroke = new SolidColorBrush(Colors.Yellow);
|
||||
rectPipe.StrokeThickness = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
rectPipe.Stroke = new SolidColorBrush(Colors.DarkSlateGray);
|
||||
rectPipe.StrokeThickness = 2;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public ZIndexEnum ZIndex_Base()
|
||||
{
|
||||
return ZIndexEnum.Estaticos;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<UserControl x:Class="CtrEditor.ObjetosSim.ucHydPump"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
|
||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<!-- DataContext se establece desde el objeto padre, no aquí -->
|
||||
|
||||
<Grid RenderTransformOrigin="0,0">
|
||||
<Grid.RenderTransform>
|
||||
<TransformGroup>
|
||||
<RotateTransform Angle="{Binding Angulo}"/>
|
||||
</TransformGroup>
|
||||
</Grid.RenderTransform>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Imagen de la bomba -->
|
||||
<Image Grid.Row="0" Source="{Binding ImageSource_oculta}"
|
||||
Width="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Stretch="Uniform"/>
|
||||
|
||||
<!-- Panel de información con presión y caudal -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding CurrentPressureBar, StringFormat='{}{0:F1} bar'}"
|
||||
Foreground="White" Background="Blue" Padding="2" Margin="1" FontSize="8"/>
|
||||
<TextBlock Text="{Binding CurrentFlowLMin, StringFormat='{}{0:F1} L/min'}"
|
||||
Foreground="White" Background="Green" Padding="2" Margin="1" FontSize="8"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
|
@ -1,53 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using CtrEditor.ObjetosSim;
|
||||
using CtrEditor.FuncionesBase;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
/// <summary>
|
||||
/// UserControl para la bomba hidráulica
|
||||
/// </summary>
|
||||
public partial class ucHydPump : UserControl, IDataContainer
|
||||
{
|
||||
public osBase? Datos { get; set; }
|
||||
public int zIndex_fromFrames { get; set; } = 0;
|
||||
|
||||
private bool _isHighlighted = false;
|
||||
|
||||
public ucHydPump()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
#region IDataContainer Implementation
|
||||
|
||||
public void Highlight(bool state)
|
||||
{
|
||||
_isHighlighted = state;
|
||||
// Aquí se podría agregar lógica de resaltado si fuera necesario
|
||||
}
|
||||
|
||||
public ZIndexEnum ZIndex_Base()
|
||||
{
|
||||
return ZIndexEnum.Estaticos;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
<UserControl x:Class="CtrEditor.ObjetosSim.ucHydTank"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
|
||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<!-- DataContext se establece desde el objeto padre, no aquí -->
|
||||
<UserControl.DataContext>
|
||||
<vm:osHydTank />
|
||||
</UserControl.DataContext>
|
||||
|
||||
<Canvas RenderTransformOrigin="0,0">
|
||||
<Canvas.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="{Binding Angulo}" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Canvas.RenderTransform>
|
||||
|
||||
<!-- Contenedor principal del tanque -->
|
||||
<Grid Width="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}">
|
||||
|
||||
<!-- Fondo del tanque (contenedor vacío) -->
|
||||
<Rectangle x:Name="rectTankContainer"
|
||||
Fill="LightGray"
|
||||
Stroke="DarkGray"
|
||||
StrokeThickness="2"
|
||||
RadiusX="5"
|
||||
RadiusY="5"/>
|
||||
|
||||
<!-- Nivel del líquido -->
|
||||
<Rectangle x:Name="rectLevel"
|
||||
Fill="{Binding LevelColor}"
|
||||
Stroke="{Binding LevelBorderColor}"
|
||||
StrokeThickness="1"
|
||||
RadiusX="3"
|
||||
RadiusY="3"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="4,4,4,4">
|
||||
<Rectangle.Height>
|
||||
<MultiBinding Converter="{StaticResource LevelToHeightMultiConverter}">
|
||||
<Binding Path="FillPercentage"/>
|
||||
<Binding Path="Tamano" Converter="{StaticResource MeterToPixelConverter}"/>
|
||||
</MultiBinding>
|
||||
</Rectangle.Height>
|
||||
</Rectangle>
|
||||
|
||||
<!-- Líneas de nivel (marcas visuales) -->
|
||||
<Canvas>
|
||||
<!-- Línea de nivel máximo -->
|
||||
<Line X1="0" Y1="8" X2="{Binding ActualWidth, ElementName=rectTankContainer}" Y2="8"
|
||||
Stroke="Red" StrokeThickness="1" StrokeDashArray="2,2" Opacity="0.7"/>
|
||||
|
||||
<!-- Línea de nivel medio -->
|
||||
<Line X1="0" Y1="{Binding MidLevelY}" X2="{Binding ActualWidth, ElementName=rectTankContainer}" Y2="{Binding MidLevelY}"
|
||||
Stroke="Orange" StrokeThickness="1" StrokeDashArray="2,2" Opacity="0.5"/>
|
||||
|
||||
<!-- Línea de nivel mínimo -->
|
||||
<Line X1="0" Y1="{Binding MinLevelY}" X2="{Binding ActualWidth, ElementName=rectTankContainer}" Y2="{Binding MinLevelY}"
|
||||
Stroke="Red" StrokeThickness="1" StrokeDashArray="2,2" Opacity="0.7"/>
|
||||
</Canvas>
|
||||
|
||||
<!-- Texto del porcentaje de llenado -->
|
||||
<TextBlock Text="{Binding FillPercentage, StringFormat='{}{0:F0}%'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="White"
|
||||
FontWeight="Bold"
|
||||
FontSize="10"
|
||||
Effect="{StaticResource DropShadowEffect}"/>
|
||||
|
||||
<!-- Indicador de tipo de tanque -->
|
||||
<TextBlock Text="{Binding TankTypeIndicator}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Foreground="Navy"
|
||||
FontWeight="Bold"
|
||||
FontSize="8"
|
||||
Margin="2,0,0,0"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Panel de información -->
|
||||
<Grid Canvas.Top="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}, ConverterParameter=1.05}"
|
||||
Width="{Binding Tamano, Converter={StaticResource MeterToPixelConverter}}">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,2,0,0">
|
||||
|
||||
<!-- Presión actual -->
|
||||
<TextBlock Text="{Binding CurrentPressureBar, StringFormat='{}{0:F1} bar'}"
|
||||
Foreground="White" Background="Blue"
|
||||
Padding="2" Margin="1" FontSize="8"/>
|
||||
|
||||
<!-- Nivel actual -->
|
||||
<TextBlock Text="{Binding CurrentLevel, StringFormat='{}{0:F2} m'}"
|
||||
Foreground="White" Background="Green"
|
||||
Padding="2" Margin="1" FontSize="8"/>
|
||||
|
||||
<!-- Balance de flujo -->
|
||||
<TextBlock Text="{Binding FlowBalanceLMin, StringFormat='{}{0:F1} L/min'}"
|
||||
Foreground="White"
|
||||
Background="{Binding FlowBalanceColor}"
|
||||
Padding="2" Margin="1" FontSize="8"/>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Canvas>
|
||||
|
||||
</UserControl>
|
|
@ -1,126 +0,0 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using System.Diagnostics;
|
||||
using CtrEditor.ObjetosSim;
|
||||
using CtrEditor.FuncionesBase;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
/// <summary>
|
||||
/// UserControl para el tanque hidráulico osHydTank
|
||||
/// </summary>
|
||||
public partial class ucHydTank : UserControl, IDataContainer
|
||||
{
|
||||
#region IDataContainer Implementation
|
||||
|
||||
[JsonIgnore]
|
||||
public osBase? Datos { get; set; }
|
||||
|
||||
public int zIndex_fromFrames { get; set; } = 0;
|
||||
|
||||
private bool _isHighlighted = false;
|
||||
|
||||
#endregion
|
||||
|
||||
public ucHydTank()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += OnUserControlLoaded;
|
||||
Unloaded += OnUserControlUnloaded;
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
|
||||
// Debug: Confirmar que el UserControl se está creando
|
||||
Debug.WriteLine("ucHydTank Constructor: UserControl del tanque hidráulico creado");
|
||||
}
|
||||
|
||||
private void OnUserControlUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Datos?.ucUnLoaded();
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (DataContext is osHydTank tank)
|
||||
{
|
||||
Datos = tank;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUserControlLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Debug: Confirmar que el UserControl se está cargando
|
||||
Debug.WriteLine($"ucHydTank.OnUserControlLoaded: DataContext = {DataContext?.GetType().Name ?? "null"}");
|
||||
|
||||
// Llamar a ucLoaded() usando la propiedad Datos
|
||||
Datos?.ucLoaded();
|
||||
|
||||
// Asegurar que el DataContext esté configurado correctamente
|
||||
if (Datos is osHydTank tank)
|
||||
{
|
||||
Debug.WriteLine($"ucHydTank.OnUserControlLoaded: Tanque '{tank.Nombre}' conectado - Tamaño: {tank.Tamano}");
|
||||
// Suscribirse a cambios de propiedades para actualizar la UI
|
||||
tank.PropertyChanged += Tank_PropertyChanged;
|
||||
UpdateVisualProperties();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("ucHydTank.OnUserControlLoaded: ERROR - Datos no es osHydTank o es null");
|
||||
}
|
||||
}
|
||||
|
||||
private void Tank_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
// Actualizar propiedades visuales cuando cambien propiedades relevantes
|
||||
if (e.PropertyName == nameof(osHydTank.FillPercentage) ||
|
||||
e.PropertyName == nameof(osHydTank.CurrentLevel) ||
|
||||
e.PropertyName == nameof(osHydTank.FlowBalance) ||
|
||||
e.PropertyName == nameof(osHydTank.TankType) ||
|
||||
e.PropertyName == nameof(osHydTank.ConnectedInletPipe) ||
|
||||
e.PropertyName == nameof(osHydTank.ConnectedOutletPipe))
|
||||
{
|
||||
UpdateVisualProperties();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisualProperties()
|
||||
{
|
||||
if (Datos is osHydTank tank)
|
||||
{
|
||||
// Forzar actualización del UI - las propiedades se actualizan automáticamente
|
||||
// cuando cambian las propiedades base gracias al binding
|
||||
this.InvalidateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
#region IDataContainer Implementation Methods
|
||||
|
||||
public void Highlight(bool state)
|
||||
{
|
||||
_isHighlighted = state;
|
||||
|
||||
if (state)
|
||||
{
|
||||
// Resaltar el contenedor del tanque con borde amarillo y más grueso
|
||||
rectTankContainer.Stroke = new SolidColorBrush(Colors.Yellow);
|
||||
rectTankContainer.StrokeThickness = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Volver al estado normal
|
||||
rectTankContainer.Stroke = new SolidColorBrush(Colors.DarkGray);
|
||||
rectTankContainer.StrokeThickness = 2;
|
||||
}
|
||||
}
|
||||
|
||||
public ZIndexEnum ZIndex_Base()
|
||||
{
|
||||
return ZIndexEnum.Estaticos;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Windows.Controls;
|
||||
using CtrEditor.HydraulicSimulator;
|
||||
using CtrEditor.Simulacion;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
|
@ -78,7 +77,7 @@ namespace CtrEditor.ObjetosSim
|
|||
return instance;
|
||||
}
|
||||
|
||||
public static void AssignDatos(UserControl userControl, osBase datos, SimulationManagerBEPU simulationManager, HydraulicSimulationManager hydraulicSimulationManager = null)
|
||||
public static void AssignDatos(UserControl userControl, osBase datos, SimulationManagerBEPU simulationManager)
|
||||
{
|
||||
if (userControl is IDataContainer dataContainer)
|
||||
{
|
||||
|
@ -86,7 +85,6 @@ namespace CtrEditor.ObjetosSim
|
|||
userControl.DataContext = datos;
|
||||
datos.VisualRepresentation = userControl;
|
||||
datos.simulationManager = simulationManager;
|
||||
datos.hydraulicSimulationManager = hydraulicSimulationManager;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using CtrEditor.HydraulicSimulator;
|
||||
using CtrEditor.Serialization;
|
||||
using CtrEditor.Services;
|
||||
using CtrEditor.Simulacion;
|
||||
|
@ -46,22 +45,19 @@ namespace CtrEditor.ObjetosSim
|
|||
private MainViewModel? _mainViewModel;
|
||||
private UserControl? VisualRepresentation;
|
||||
private SimulationManagerBEPU? simulationManager;
|
||||
private HydraulicSimulationManager? hydraulicSimulationManager;
|
||||
|
||||
public DataSaveToSerialize(MainViewModel a, UserControl b, SimulationManagerBEPU c, HydraulicSimulationManager d = null)
|
||||
public DataSaveToSerialize(MainViewModel a, UserControl b, SimulationManagerBEPU c)
|
||||
{
|
||||
_mainViewModel = a;
|
||||
VisualRepresentation = b;
|
||||
simulationManager = c;
|
||||
hydraulicSimulationManager = d;
|
||||
}
|
||||
|
||||
public void DataRestoreAfterSerialize(out MainViewModel a, out UserControl b, out SimulationManagerBEPU c, out HydraulicSimulationManager d)
|
||||
public void DataRestoreAfterSerialize(out MainViewModel a, out UserControl b, out SimulationManagerBEPU c)
|
||||
{
|
||||
a = _mainViewModel;
|
||||
b = VisualRepresentation;
|
||||
c = simulationManager;
|
||||
d = hydraulicSimulationManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1058,23 +1054,16 @@ namespace CtrEditor.ObjetosSim
|
|||
[JsonIgnore]
|
||||
public SimulationManagerBEPU simulationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Link al Simulador hidráulico.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public HydraulicSimulationManager hydraulicSimulationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Prepara la clase para ser serializable poniendo a null los objetos que tienen referencias circulares
|
||||
/// Luego se debe llamar a RestaurarDatosNoSerializables para restaurar el estado original
|
||||
/// </summary>
|
||||
public void SalvarDatosNoSerializables()
|
||||
{
|
||||
DataSave = new DataSaveToSerialize(_mainViewModel, _visualRepresentation, simulationManager, hydraulicSimulationManager);
|
||||
DataSave = new DataSaveToSerialize(_mainViewModel, _visualRepresentation, simulationManager);
|
||||
_mainViewModel = null;
|
||||
_visualRepresentation = null;
|
||||
simulationManager = null;
|
||||
hydraulicSimulationManager = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1083,7 +1072,7 @@ namespace CtrEditor.ObjetosSim
|
|||
public void RestaurarDatosNoSerializables()
|
||||
{
|
||||
if (DataSave == null) return;
|
||||
DataSave.DataRestoreAfterSerialize(out _mainViewModel, out _visualRepresentation, out simulationManager, out hydraulicSimulationManager);
|
||||
DataSave.DataRestoreAfterSerialize(out _mainViewModel, out _visualRepresentation, out simulationManager);
|
||||
}
|
||||
/// <summary>
|
||||
/// Se llama una unica vez al conectar con el PLC.
|
||||
|
@ -1930,30 +1919,9 @@ namespace CtrEditor.ObjetosSim
|
|||
items.Add("");
|
||||
foreach (var obj in objetosSimulables)
|
||||
{
|
||||
try
|
||||
if (obj.GetType() == typeof(T))
|
||||
{
|
||||
// Verificar si el tipo T es una interfaz
|
||||
if (typeof(T).IsInterface)
|
||||
{
|
||||
// Para interfaces, verificar si el objeto implementa la interfaz
|
||||
if (typeof(T).IsAssignableFrom(obj.GetType()))
|
||||
{
|
||||
items.Add(obj.Nombre);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Para clases concretas, usar comparación directa de tipos
|
||||
if (obj.GetType() == typeof(T))
|
||||
{
|
||||
items.Add(obj.Nombre);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the exception but continue processing other objects
|
||||
System.Diagnostics.Debug.WriteLine($"Error checking type compatibility for {obj?.GetType()?.Name}: {ex.Message}");
|
||||
items.Add(obj.Nombre);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -270,7 +270,6 @@ namespace CtrEditor.Serialization
|
|||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DefaultValueHandling = DefaultValueHandling.Include, // Incluir valores por defecto
|
||||
TypeNameHandling = TypeNameHandling.Auto,
|
||||
ObjectCreationHandling = ObjectCreationHandling.Replace,
|
||||
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
|
||||
|
|
|
@ -1,197 +0,0 @@
|
|||
using BepuPhysics.Collidables;
|
||||
using BepuPhysics.Constraints;
|
||||
using BepuPhysics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CtrEditor.HydraulicSimulator;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
public class simPumpExample : simBase
|
||||
{
|
||||
public float Width;
|
||||
public float Height;
|
||||
public float Depth;
|
||||
private float _mass;
|
||||
|
||||
// Propiedades hidráulicas
|
||||
public double PumpHead { get; set; } = 50.0;
|
||||
public double MaxFlow { get; set; } = 0.01;
|
||||
public double SpeedRatio { get; set; } = 1.0;
|
||||
public bool IsRunning { get; set; } = true;
|
||||
public int PumpDirection { get; set; } = 1;
|
||||
|
||||
// Estado actual de la simulación hidráulica
|
||||
public double CurrentFlow { get; set; } = 0.0;
|
||||
public double CurrentPressure { get; set; } = 0.0;
|
||||
|
||||
public simPumpExample(Simulation simulation, List<Action> deferredActions, Vector3 dimensions, Vector2 position, float mass)
|
||||
{
|
||||
_simulation = simulation;
|
||||
Width = dimensions.X;
|
||||
Height = dimensions.Y;
|
||||
Depth = dimensions.Z;
|
||||
_mass = mass;
|
||||
|
||||
// Usar COORDINATECONVERTER para conversión centralizada
|
||||
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Depth / 2f + zPos_Transporte + zAltura_Transporte);
|
||||
Create(position3D);
|
||||
}
|
||||
|
||||
public float CenterX
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetPosition().X;
|
||||
}
|
||||
set
|
||||
{
|
||||
var pos = GetPosition();
|
||||
SetPosition(value, pos.Y, pos.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public float CenterY
|
||||
{
|
||||
get
|
||||
{
|
||||
// Usar COORDINATECONVERTER para conversión centralizada
|
||||
return CoordinateConverter.BepuYToWpfY(GetPosition().Y);
|
||||
}
|
||||
set
|
||||
{
|
||||
var pos = GetPosition();
|
||||
// Usar COORDINATECONVERTER para conversión centralizada
|
||||
SetPosition(pos.X, CoordinateConverter.WpfYToBepuY(value), pos.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get
|
||||
{
|
||||
var pos3D = GetPosition();
|
||||
// Usar COORDINATECONVERTER para conversión centralizada
|
||||
return CoordinateConverter.BepuVector3ToWpfVector2(pos3D);
|
||||
}
|
||||
set
|
||||
{
|
||||
// Mantener la Z actual, solo cambiar X, Y
|
||||
var currentPos = GetPosition();
|
||||
// Usar COORDINATECONVERTER para conversión centralizada
|
||||
SetPosition(value.X, CoordinateConverter.WpfYToBepuY(value.Y), currentPos.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public float Mass
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
return 1f / bodyReference.LocalInertia.InverseMass;
|
||||
}
|
||||
return _mass;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mass = value;
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var box = new Box(Width, Height, Depth);
|
||||
var inertia = box.ComputeInertia(_mass);
|
||||
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Create(Vector3 position)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
var box = new Box(Width, Height, Depth);
|
||||
var shapeIndex = _simulation.Shapes.Add(box);
|
||||
|
||||
// Crear el cuerpo estático (las bombas no se mueven físicamente)
|
||||
var inertia = box.ComputeInertia(_mass);
|
||||
var bodyDescription = BodyDescription.CreateKinematic(
|
||||
new RigidPose(position),
|
||||
new CollidableDescription(shapeIndex, 0),
|
||||
new BodyActivityDescription(-1f)
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true;
|
||||
|
||||
// Registrar en el diccionario del SimulationManager
|
||||
if (_simulationManager != null)
|
||||
_simulationManager.CollidableData[BodyHandle.Value] = this;
|
||||
}
|
||||
|
||||
public void SetDimensions(Vector3 dimensions)
|
||||
{
|
||||
Width = dimensions.X;
|
||||
Height = dimensions.Y;
|
||||
Depth = dimensions.Z;
|
||||
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var box = new Box(Width, Height, Depth);
|
||||
var shapeIndex = _simulation.Shapes.Add(box);
|
||||
ChangeBodyShape(shapeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMass(float mass)
|
||||
{
|
||||
Mass = mass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza las propiedades hidráulicas de la bomba
|
||||
/// </summary>
|
||||
public void UpdateHydraulicProperties(double pumpHead, double maxFlow, double speedRatio, bool isRunning, int direction)
|
||||
{
|
||||
PumpHead = pumpHead;
|
||||
MaxFlow = maxFlow;
|
||||
SpeedRatio = speedRatio;
|
||||
IsRunning = isRunning;
|
||||
PumpDirection = direction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aplica los resultados de la simulación hidráulica
|
||||
/// </summary>
|
||||
public void ApplyHydraulicResults(double flow, double pressure)
|
||||
{
|
||||
CurrentFlow = flow;
|
||||
CurrentPressure = pressure;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el caudal actual de la bomba
|
||||
/// </summary>
|
||||
public double GetCurrentFlow()
|
||||
{
|
||||
return CurrentFlow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene la presión actual de la bomba
|
||||
/// </summary>
|
||||
public double GetCurrentPressure()
|
||||
{
|
||||
return CurrentPressure;
|
||||
}
|
||||
|
||||
public new void RemoverBody()
|
||||
{
|
||||
base.RemoverBody();
|
||||
}
|
||||
}
|
||||
}
|
115
XAMLhelpers.cs
115
XAMLhelpers.cs
|
@ -380,123 +380,14 @@ namespace CtrEditor
|
|||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values?.Length >= 2)
|
||||
if (values[0] is float level && values[1] is double containerHeight)
|
||||
{
|
||||
// Verificar que los valores sean válidos antes de convertir
|
||||
if (!TryConvertToDouble(values[0], out double level) ||
|
||||
!TryConvertToDouble(values[1], out double containerHeight))
|
||||
{
|
||||
return 0.0; // Retornar 0 si no se pueden convertir los valores
|
||||
}
|
||||
|
||||
var result = containerHeight * (level / 100.0);
|
||||
return result;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private static bool TryConvertToDouble(object value, out double result)
|
||||
{
|
||||
result = 0.0;
|
||||
|
||||
// Verificar valores nulos o especiales de WPF
|
||||
if (value == null ||
|
||||
value == DependencyProperty.UnsetValue ||
|
||||
value.GetType().Name.Contains("NamedObject"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Intentar conversión directa para tipos numéricos
|
||||
if (value is double d)
|
||||
{
|
||||
result = d;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is float f)
|
||||
{
|
||||
result = f;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is int i)
|
||||
{
|
||||
result = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is decimal dec)
|
||||
{
|
||||
result = (double)dec;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Intentar conversión de string
|
||||
if (value is string str && double.TryParse(str, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Último intento: usar Convert.ToDouble solo si es IConvertible
|
||||
if (value is IConvertible)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = System.Convert.ToDouble(value, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class PercentageToHeightConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is float percentage)
|
||||
{
|
||||
// Assume the tank height is 100 pixels by default, or use parameter if provided
|
||||
double tankHeight = 100;
|
||||
if (parameter is double paramHeight)
|
||||
tankHeight = paramHeight;
|
||||
else if (parameter is string paramStr && double.TryParse(paramStr, out double parsed))
|
||||
tankHeight = parsed;
|
||||
|
||||
return tankHeight * (percentage / 100.0);
|
||||
return containerHeight * (level / 100);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class BoolToColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isTrue)
|
||||
{
|
||||
return isTrue ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
|
||||
}
|
||||
return new SolidColorBrush(Colors.Gray);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Loading…
Reference in New Issue