Refactor TSNet Hydraulic Simulation Integration
- Implemented node name sanitization in TSNetINPGenerator to ensure compatibility with EPANET INP format. - Enhanced TSNetSimulationManager to manage hydraulic components and elements more robustly, including improved error handling and logging. - Replaced HydraulicSimulationManager with TSNetSimulationManager in MainViewModel, updating all relevant references and methods. - Improved handling of IOException tracking in MainViewModel for better debugging and stability during simulation runs. - Updated osHydPipe, osHydPump, and osHydTank classes to utilize sanitized node names for hydraulic nodes. - Added new methods for resetting and clearing hydraulic objects in TSNetSimulationManager. - Enhanced UserControlFactory to support the new TSNetSimulationManager without assigning the old hydraulic simulation manager.
This commit is contained in:
parent
1b21f86886
commit
51d0f36187
|
@ -272,7 +272,7 @@ namespace CtrEditor
|
|||
|
||||
if (userControl != null)
|
||||
{
|
||||
UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager, _mainViewModel.hydraulicSimulationManager);
|
||||
UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager, _mainViewModel.tsnetSimulationManager);
|
||||
osObjeto._mainViewModel = _mainViewModel;
|
||||
|
||||
if (osObjeto.Id == null)
|
||||
|
|
|
@ -228,14 +228,36 @@ namespace CtrEditor.HydraulicSimulator
|
|||
|
||||
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);
|
||||
|
||||
if (VerboseOutput)
|
||||
try
|
||||
{
|
||||
Trace.WriteLine($"Rama agregada: {elemDef.Name} " +
|
||||
$"({elemDef.FromNode} -> {elemDef.ToNode})");
|
||||
// Validar que los nodos existan antes de agregar la rama
|
||||
if (!Network.Nodes.ContainsKey(elemDef.FromNode))
|
||||
{
|
||||
Debug.WriteLine($"ERROR: Nodo '{elemDef.FromNode}' no existe. Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Network.Nodes.ContainsKey(elemDef.ToNode))
|
||||
{
|
||||
Debug.WriteLine($"ERROR: Nodo '{elemDef.ToNode}' no existe. Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Crear rama con el elemento
|
||||
var elements = new List<Element> { elemDef.Element };
|
||||
Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name);
|
||||
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Trace.WriteLine($"Rama agregada: {elemDef.Name} " +
|
||||
$"({elemDef.FromNode} -> {elemDef.ToNode})");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error agregando rama {elemDef.Name}: {ex.Message}");
|
||||
Debug.WriteLine($" FromNode: '{elemDef.FromNode}', ToNode: '{elemDef.ToNode}'");
|
||||
Debug.WriteLine($" Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,17 +298,43 @@ try:
|
|||
# Cargar el modelo usando WNTR (TSNet usa WNTR internamente)
|
||||
wn = wntr.network.WaterNetworkModel(r'{inpFilePath}')
|
||||
|
||||
# Configurar simulación transitoria
|
||||
wn.set_time(duration=10.0, dt=0.01) # 10 segundos, dt=0.01s
|
||||
# Configurar WNTR options para headloss formula
|
||||
wn.options.hydraulic.headloss = 'D-W' # Darcy-Weisbach
|
||||
wn.options.time.duration = 10.0 # 10 segundos de duración
|
||||
wn.options.time.hydraulic_timestep = 0.01 # Paso de tiempo de 0.01 segundos
|
||||
wn.options.time.report_timestep = 0.01 # Reportar cada 0.01 segundos
|
||||
|
||||
# Ejecutar simulación
|
||||
results = tsnet.simulation.run_transient_simulation(wn, results_dir=r'{outputDir}')
|
||||
# CORRECCIÓN: TSNet.TransientModel necesita el archivo INP, no el objeto WaterNetworkModel
|
||||
# Convertir a modelo transient de TSNet usando el archivo INP directamente
|
||||
tm = tsnet.network.TransientModel(r'{inpFilePath}')
|
||||
|
||||
# Ejecutar simulación usando la API correcta de TSNet
|
||||
try:
|
||||
# Método correcto de TSNet
|
||||
results = tsnet.simulation.MOCSimulator(tm, results_obj='results', friction='steady')
|
||||
print('Simulación TSNet completada exitosamente')
|
||||
print(f'Resultados generados en modelo transient')
|
||||
|
||||
# Guardar resultados si es necesario
|
||||
print('Directorio de resultados: ' + r'{outputDir}')
|
||||
|
||||
except Exception as tsnet_error:
|
||||
print('Error en TSNet: ' + str(tsnet_error))
|
||||
# Fallback a simulación básica con WNTR
|
||||
try:
|
||||
import wntr.sim
|
||||
sim = wntr.sim.EpanetSimulator(wn)
|
||||
results = sim.run_sim()
|
||||
print('Ejecutada simulación básica WNTR (fallback)')
|
||||
except Exception as wntr_error:
|
||||
print('Error en WNTR fallback: ' + str(wntr_error))
|
||||
raise tsnet_error
|
||||
|
||||
print('Simulación completada exitosamente')
|
||||
print(r'Resultados guardados en: {outputDir}')
|
||||
print('Resultados guardados en: ' + r'{outputDir}')
|
||||
|
||||
except Exception as e:
|
||||
print(f'Error en simulación TSNet: {{e}}')
|
||||
print('Error en simulación TSNet: ' + str(e))
|
||||
raise
|
||||
";
|
||||
|
||||
|
|
|
@ -90,7 +90,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
{
|
||||
var elevation = GetNodeElevation(node);
|
||||
var demand = GetNodeDemand(node);
|
||||
content.AppendLine($" {node.Name,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t{demand.ToString("F2", CultureInfo.InvariantCulture)} \t;");
|
||||
var sanitizedName = SanitizeNodeName(node.Name);
|
||||
content.AppendLine($" {sanitizedName,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t{demand.ToString("F2", CultureInfo.InvariantCulture)} \t;");
|
||||
}
|
||||
|
||||
content.AppendLine();
|
||||
|
@ -104,7 +105,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
foreach (var node in _network.Nodes.Values.Where(n => n.FixedP && !IsTank(n)))
|
||||
{
|
||||
var head = PressureToHead(node.P);
|
||||
content.AppendLine($" {node.Name,-15}\t{head.ToString("F2", CultureInfo.InvariantCulture)} \t;");
|
||||
var sanitizedName = SanitizeNodeName(node.Name);
|
||||
content.AppendLine($" {sanitizedName,-15}\t{head.ToString("F2", CultureInfo.InvariantCulture)} \t;");
|
||||
}
|
||||
|
||||
content.AppendLine();
|
||||
|
@ -120,7 +122,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
foreach (var node in tankNodes)
|
||||
{
|
||||
var elevation = GetNodeElevation(node);
|
||||
content.AppendLine($" {node.Name,-15}\t{elevation:F2} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t");
|
||||
var sanitizedName = SanitizeNodeName(node.Name);
|
||||
content.AppendLine($" {sanitizedName,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t");
|
||||
}
|
||||
|
||||
content.AppendLine();
|
||||
|
@ -141,7 +144,10 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
var diameter = element.D * 1000; // Usar D en lugar de Diameter, convertir a mm
|
||||
var roughness = element.Rough * 1000; // Usar Rough en lugar de Roughness, convertir a mm
|
||||
|
||||
content.AppendLine($" {id,-15}\t{branch.N1,-15}\t{branch.N2,-15}\t{length.ToString("F2", CultureInfo.InvariantCulture)} \t{diameter.ToString("F1", CultureInfo.InvariantCulture)} \t{roughness.ToString("F4", CultureInfo.InvariantCulture)} \t0 \tOpen");
|
||||
var sanitizedN1 = SanitizeNodeName(branch.N1);
|
||||
var sanitizedN2 = SanitizeNodeName(branch.N2);
|
||||
|
||||
content.AppendLine($" {id,-15}\t{sanitizedN1,-15}\t{sanitizedN2,-15}\t{length.ToString("F2", CultureInfo.InvariantCulture)} \t{diameter.ToString("F1", CultureInfo.InvariantCulture)} \t{roughness.ToString("F4", CultureInfo.InvariantCulture)} \t0 \tOpen");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,7 +165,10 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
foreach (var element in branch.Elements.OfType<PumpHQ>())
|
||||
{
|
||||
var id = $"PUMP{pumpId}";
|
||||
content.AppendLine($" {id,-15}\t{branch.N1,-15}\t{branch.N2,-15}\tHEAD CURVE{pumpId}");
|
||||
var sanitizedN1 = SanitizeNodeName(branch.N1);
|
||||
var sanitizedN2 = SanitizeNodeName(branch.N2);
|
||||
|
||||
content.AppendLine($" {id,-15}\t{sanitizedN1,-15}\t{sanitizedN2,-15}\tHEAD CURVE{pumpId}");
|
||||
pumpId++;
|
||||
}
|
||||
}
|
||||
|
@ -213,9 +222,9 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
var maxHead = element.H0;
|
||||
var maxFlow = element.H0 / 10; // Estimación simple
|
||||
|
||||
content.AppendLine($" CURVE{curveId} \t0 \t{maxHead:F2}");
|
||||
content.AppendLine($" CURVE{curveId} \t{maxFlow/2:F2} \t{maxHead*0.8:F2}");
|
||||
content.AppendLine($" CURVE{curveId} \t{maxFlow:F2} \t{maxHead*0.5:F2}");
|
||||
content.AppendLine($" CURVE{curveId} \t0 \t{maxHead.ToString("F2", CultureInfo.InvariantCulture)}");
|
||||
content.AppendLine($" CURVE{curveId} \t{(maxFlow/2).ToString("F2", CultureInfo.InvariantCulture)} \t{(maxHead*0.8).ToString("F2", CultureInfo.InvariantCulture)}");
|
||||
content.AppendLine($" CURVE{curveId} \t{maxFlow.ToString("F2", CultureInfo.InvariantCulture)} \t{(maxHead*0.5).ToString("F2", CultureInfo.InvariantCulture)}");
|
||||
|
||||
curveId++;
|
||||
}
|
||||
|
@ -231,7 +240,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
|
||||
foreach (var node in _network.Nodes.Values)
|
||||
{
|
||||
content.AppendLine($" {node.Name,-15}\t0.0");
|
||||
var sanitizedName = SanitizeNodeName(node.Name);
|
||||
content.AppendLine($" {sanitizedName,-15}\t0.0");
|
||||
}
|
||||
|
||||
content.AppendLine();
|
||||
|
@ -292,7 +302,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
int x = 0, y = 0;
|
||||
foreach (var node in _network.Nodes.Values)
|
||||
{
|
||||
content.AppendLine($" {node.Name,-15}\t{x:F2} \t{y:F2}");
|
||||
var sanitizedName = SanitizeNodeName(node.Name);
|
||||
content.AppendLine($" {sanitizedName,-15}\t{x.ToString("F2", CultureInfo.InvariantCulture)} \t{y.ToString("F2", CultureInfo.InvariantCulture)}");
|
||||
x += 1000;
|
||||
if (x > 5000)
|
||||
{
|
||||
|
@ -341,6 +352,45 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
return Math.Sqrt(valve.KvFull / 100.0) * 0.1; // en metros
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitiza nombres de nodos para compatibilidad con EPANET INP
|
||||
/// </summary>
|
||||
private string SanitizeNodeName(string nodeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(nodeName))
|
||||
return "UnknownNode";
|
||||
|
||||
// Reemplazar espacios y caracteres especiales con guiones bajos
|
||||
var sanitized = nodeName
|
||||
.Replace(" ", "_")
|
||||
.Replace("á", "a")
|
||||
.Replace("é", "e")
|
||||
.Replace("í", "i")
|
||||
.Replace("ó", "o")
|
||||
.Replace("ú", "u")
|
||||
.Replace("ü", "u")
|
||||
.Replace("ñ", "n")
|
||||
.Replace("Á", "A")
|
||||
.Replace("É", "E")
|
||||
.Replace("Í", "I")
|
||||
.Replace("Ó", "O")
|
||||
.Replace("Ú", "U")
|
||||
.Replace("Ü", "U")
|
||||
.Replace("Ñ", "N");
|
||||
|
||||
// Remover caracteres no válidos para EPANET
|
||||
var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray();
|
||||
var result = new string(validChars);
|
||||
|
||||
// Asegurar que empiece con una letra
|
||||
if (!string.IsNullOrEmpty(result) && !char.IsLetter(result[0]))
|
||||
{
|
||||
result = "Node_" + result;
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(result) ? "UnknownNode" : result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,11 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
/// </summary>
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si la red necesita ser reconstruida
|
||||
/// </summary>
|
||||
public bool NetworkNeedsRebuild { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Evento disparado cuando se completa una simulación
|
||||
/// </summary>
|
||||
|
@ -224,12 +229,73 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
{
|
||||
Network = new HydraulicNetwork();
|
||||
|
||||
// Procesar objetos IHydraulicComponent
|
||||
foreach (var obj in HydraulicObjects)
|
||||
var hydraulicComponents = HydraulicObjects.OfType<IHydraulicComponent>().ToList();
|
||||
|
||||
// Primera pasada: Agregar TODOS los nodos
|
||||
Debug.WriteLine("TSNet: Primera pasada - agregando todos los nodos...");
|
||||
foreach (var component in hydraulicComponents)
|
||||
{
|
||||
if (obj is IHydraulicComponent hydraulicComponent)
|
||||
try
|
||||
{
|
||||
ProcessHydraulicComponent(hydraulicComponent);
|
||||
var nodes = component.GetHydraulicNodes();
|
||||
foreach (var nodeDefinition in nodes)
|
||||
{
|
||||
if (nodeDefinition.IsFixedPressure)
|
||||
{
|
||||
Network.AddNode(nodeDefinition.Name, nodeDefinition.Pressure);
|
||||
}
|
||||
else
|
||||
{
|
||||
Network.AddNode(nodeDefinition.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error agregando nodos del componente {component}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLine($"TSNet: Nodos agregados - Total: {Network.Nodes.Count}");
|
||||
Debug.WriteLine($"TSNet: Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
|
||||
|
||||
// Segunda pasada: Agregar TODOS los elementos
|
||||
Debug.WriteLine("TSNet: Segunda pasada - agregando todos los elementos...");
|
||||
foreach (var component in hydraulicComponents)
|
||||
{
|
||||
try
|
||||
{
|
||||
var elements = component.GetHydraulicElements();
|
||||
foreach (var elementDefinition in elements)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Verificar que los nodos existan antes de agregar el elemento
|
||||
if (Network.Nodes.ContainsKey(elementDefinition.FromNode) &&
|
||||
Network.Nodes.ContainsKey(elementDefinition.ToNode))
|
||||
{
|
||||
Network.AddElement(elementDefinition.Element,
|
||||
elementDefinition.FromNode,
|
||||
elementDefinition.ToNode,
|
||||
elementDefinition.Name);
|
||||
|
||||
Debug.WriteLine($"Rama agregada: {elementDefinition.Name} ({elementDefinition.FromNode} -> {elementDefinition.ToNode})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"ERROR: Nodo '{elementDefinition.FromNode}' o '{elementDefinition.ToNode}' no existe. " +
|
||||
$"Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
|
||||
}
|
||||
}
|
||||
catch (Exception elementEx)
|
||||
{
|
||||
Debug.WriteLine($"Error agregando elemento {elementDefinition.Name}: {elementEx.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error obteniendo elementos del componente {component}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -616,8 +682,29 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
var elements = component.GetHydraulicElements();
|
||||
foreach (var elementDefinition in elements)
|
||||
{
|
||||
// TODO: Convertir elementDefinition a Element y agregar a la red
|
||||
// Por ahora creamos elementos básicos
|
||||
try
|
||||
{
|
||||
// Verificar que los nodos existan antes de agregar el elemento
|
||||
if (Network.Nodes.ContainsKey(elementDefinition.FromNode) &&
|
||||
Network.Nodes.ContainsKey(elementDefinition.ToNode))
|
||||
{
|
||||
Network.AddElement(elementDefinition.Element,
|
||||
elementDefinition.FromNode,
|
||||
elementDefinition.ToNode,
|
||||
elementDefinition.Name);
|
||||
|
||||
Debug.WriteLine($"Rama agregada: {elementDefinition.Name} ({elementDefinition.FromNode} -> {elementDefinition.ToNode})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"ERROR: Nodo '{elementDefinition.FromNode}' o '{elementDefinition.ToNode}' no existe. " +
|
||||
$"Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
|
||||
}
|
||||
}
|
||||
catch (Exception elementEx)
|
||||
{
|
||||
Debug.WriteLine($"Error agregando elemento {elementDefinition.Name}: {elementEx.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -646,6 +733,55 @@ namespace CtrEditor.HydraulicSimulator.TSNet
|
|||
|
||||
#endregion
|
||||
|
||||
#region Additional Methods for Compatibility
|
||||
|
||||
/// <summary>
|
||||
/// Reinicia el simulador hidráulico (equivalente a Reset del HydraulicSimulationManager)
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
ResetAllCalculatedValues();
|
||||
Debug.WriteLine("TSNet: Simulador reiniciado");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene estadísticas de la simulación
|
||||
/// </summary>
|
||||
public string GetSimulationStats()
|
||||
{
|
||||
return $"TSNet - Objetos: {HydraulicObjects.Count}, Tanques: {_tankAdapters.Count}, Bombas: {_pumpAdapters.Count}, Tuberías: {_pipeAdapters.Count}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Propiedad para habilitar/deshabilitar la simulación hidráulica
|
||||
/// </summary>
|
||||
public bool IsHydraulicSimulationEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Limpia todos los objetos hidráulicos
|
||||
/// </summary>
|
||||
public void ClearHydraulicObjects()
|
||||
{
|
||||
HydraulicObjects.Clear();
|
||||
_objectMapping.Clear();
|
||||
_tankAdapters.Clear();
|
||||
_pumpAdapters.Clear();
|
||||
_pipeAdapters.Clear();
|
||||
Network = new HydraulicNetwork();
|
||||
Debug.WriteLine("TSNet: Todos los objetos hidráulicos limpiados");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalida la red para forzar reconstrucción
|
||||
/// </summary>
|
||||
public void InvalidateNetwork()
|
||||
{
|
||||
NetworkNeedsRebuild = true;
|
||||
Debug.WriteLine("TSNet: Red invalidada y marcada para reconstrucción");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
|
|
178
MainViewModel.cs
178
MainViewModel.cs
|
@ -77,7 +77,7 @@ namespace CtrEditor
|
|||
private bool Debug_SimulacionCreado = false;
|
||||
|
||||
public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU();
|
||||
public HydraulicSimulationManager hydraulicSimulationManager = new HydraulicSimulationManager();
|
||||
// ELIMINADO: HydraulicSimulationManager - reemplazado por TSNetSimulationManager
|
||||
public TSNetSimulationManager tsnetSimulationManager = new TSNetSimulationManager();
|
||||
|
||||
private readonly System.Timers.Timer _timerSimulacion; // Cambiado a System.Timers.Timer para mejor precisión
|
||||
|
@ -87,6 +87,12 @@ namespace CtrEditor
|
|||
private readonly System.Timers.Timer _timerDebugFlush; // Timer para flush automático del buffer de debug
|
||||
private readonly System.Timers.Timer _timerTSNet; // Timer para TSNet automático cada 100ms
|
||||
|
||||
// Variables para tracking de IOException
|
||||
private volatile bool _tsnetExecuting = false; // Flag para evitar ejecuciones simultáneas de TSNet
|
||||
private DateTime _lastTSNetExecution = DateTime.MinValue; // Timestamp de última ejecución TSNet
|
||||
private readonly TimeSpan _minTSNetInterval = TimeSpan.FromMilliseconds(50); // Intervalo mínimo entre ejecuciones
|
||||
private int _ioExceptionCount = 0; // Contador de excepciones IOException para debugging
|
||||
|
||||
public Canvas MainCanvas;
|
||||
|
||||
// Manager para la visualización 3D
|
||||
|
@ -525,10 +531,46 @@ namespace CtrEditor
|
|||
// Inicializar configuración del workspace
|
||||
WorkspaceConfig = new Models.WorkspaceConfiguration();
|
||||
|
||||
// Configurar tracking de IOException
|
||||
SetupIOExceptionTraking();
|
||||
|
||||
// Iniciar servidor MCP automáticamente
|
||||
StartMcpServer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configura el sistema de tracking de IOException mejorado para capturar excepciones de WPF
|
||||
/// </summary>
|
||||
private void SetupIOExceptionTraking()
|
||||
{
|
||||
// Manejador para excepciones de primera oportunidad (first-chance exceptions)
|
||||
// Esto captura TODAS las IOException, incluso las que son manejadas internamente por WPF
|
||||
AppDomain.CurrentDomain.FirstChanceException += (sender, e) =>
|
||||
{
|
||||
if (e.Exception is System.IO.IOException ioEx)
|
||||
{
|
||||
_ioExceptionCount++;
|
||||
var timestamp = DateTime.Now.ToString("HH:mm:ss:fff");
|
||||
System.Diagnostics.Debug.WriteLine($"[{timestamp}] 🔍 IOException #{_ioExceptionCount} detectada (First-Chance):");
|
||||
System.Diagnostics.Debug.WriteLine($" Mensaje: {ioEx.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($" Source: {ioEx.Source}");
|
||||
System.Diagnostics.Debug.WriteLine($" HResult: {ioEx.HResult}");
|
||||
System.Diagnostics.Debug.WriteLine($" Simulación activa: {IsSimulationRunning}");
|
||||
System.Diagnostics.Debug.WriteLine($" TSNet ejecutándose: {_tsnetExecuting}");
|
||||
|
||||
// Verificar si está relacionada con TSNet/Python
|
||||
if (ioEx.StackTrace?.Contains("Python") == true ||
|
||||
ioEx.StackTrace?.Contains("tsnet") == true ||
|
||||
ioEx.Source?.Contains("Presentation") == true)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($" ⚠️ Posiblemente relacionada con TSNet/WPF");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("IOException tracking configurado - se capturarán todas las excepciones de E/S");
|
||||
}
|
||||
|
||||
#region Workspace Configuration Management
|
||||
|
||||
/// <summary>
|
||||
|
@ -869,8 +911,8 @@ namespace CtrEditor
|
|||
|
||||
if (userControl != null)
|
||||
{
|
||||
// Asignar los datos al UserControl
|
||||
UserControlFactory.AssignDatos(userControl, osObjeto, simulationManager, hydraulicSimulationManager);
|
||||
// Asignar los datos al UserControl - Usando TSNetSimulationManager
|
||||
UserControlFactory.AssignDatos(userControl, osObjeto, simulationManager, tsnetSimulationManager);
|
||||
osObjeto._mainViewModel = this;
|
||||
if (osObjeto.Id == null) // Para los objetos salvados antes de usar UniqueID
|
||||
osObjeto.Id = new UniqueId().ObtenerNuevaID();
|
||||
|
@ -1254,8 +1296,8 @@ namespace CtrEditor
|
|||
// Ejecutar simulación física BEPU
|
||||
simulationManager.Step();
|
||||
|
||||
// Ejecutar simulación hidráulica
|
||||
hydraulicSimulationManager.Step((float)(timeBetweenCalls / 1000.0)); // Convertir ms a segundos
|
||||
// ELIMINADO: hydraulicSimulationManager.Step() - Ahora solo usamos TSNet
|
||||
// La simulación hidráulica se ejecuta por separado en _timerTSNet
|
||||
|
||||
// ✅ NUEVO: Solo crear la copia si hay objetos para procesar
|
||||
if (ObjetosSimulables?.Count > 0)
|
||||
|
@ -1277,7 +1319,7 @@ namespace CtrEditor
|
|||
AdaptSimulationTiming(executionStopwatch.Elapsed.TotalMilliseconds);
|
||||
|
||||
//Debug.WriteLine($"OnTickSimulacion execution time: {stopwatch.ElapsedMilliseconds} ms");
|
||||
//Debug.WriteLine($"OnTickSimulacion execution time: {executionStopwatch.Elapsed.TotalMilliseconds:F2}ms | Timer interval: {timeBetweenCalls:F2}ms | Objects: {ObjetosSimulables?.Count ?? 0}");
|
||||
Debug.WriteLine($"OnTickSimulacion execution time: {executionStopwatch.Elapsed.TotalMilliseconds:F2}ms | Timer interval: {timeBetweenCalls:F2}ms | Objects: {ObjetosSimulables?.Count ?? 0}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -1293,15 +1335,32 @@ namespace CtrEditor
|
|||
private async void OnTSNetTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
// Solo ejecutar si la simulación está corriendo y hay objetos hidráulicos
|
||||
if (!IsSimulationRunning || tsnetSimulationManager.IsRunning)
|
||||
if (!IsSimulationRunning)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// Verificar que tsnetSimulationManager esté inicializado
|
||||
if (tsnetSimulationManager == null)
|
||||
{
|
||||
Debug.WriteLine("TSNet Auto: tsnetSimulationManager es null, omitiendo ejecución");
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar si ya está ejecutándose para evitar concurrencia
|
||||
if (tsnetSimulationManager.IsRunning)
|
||||
{
|
||||
Debug.WriteLine("TSNet Auto: Simulación ya en ejecución, omitiendo");
|
||||
return;
|
||||
}
|
||||
|
||||
// Contar objetos hidráulicos
|
||||
var hydraulicObjects = ObjetosSimulables?.Where(obj => obj.GetType().Name.Contains("osHyd")).ToList();
|
||||
if (hydraulicObjects == null || hydraulicObjects.Count == 0)
|
||||
{
|
||||
Debug.WriteLine("TSNet Auto: No hay objetos hidráulicos, omitiendo ejecución");
|
||||
return;
|
||||
}
|
||||
|
||||
// Resetear y registrar objetos hidráulicos
|
||||
tsnetSimulationManager.ResetAllCalculatedValues();
|
||||
|
@ -1325,18 +1384,43 @@ namespace CtrEditor
|
|||
// Ejecutar simulación TSNet de forma asíncrona sin bloquear
|
||||
var result = await tsnetSimulationManager.RunSimulationAsync();
|
||||
|
||||
if (result.Success)
|
||||
if (result != null)
|
||||
{
|
||||
Debug.WriteLine($"TSNet Auto: Simulación exitosa con {hydraulicObjects.Count} objetos hidráulicos");
|
||||
if (result.Success)
|
||||
{
|
||||
Debug.WriteLine($"TSNet Auto: Simulación exitosa con {hydraulicObjects.Count} objetos hidráulicos");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"TSNet Auto: Error en simulación: {result.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"TSNet Auto: Error en simulación: {result.Message}");
|
||||
Debug.WriteLine("TSNet Auto: RunSimulationAsync devolvió null");
|
||||
}
|
||||
}
|
||||
catch (NullReferenceException nullEx)
|
||||
{
|
||||
Debug.WriteLine($"TSNet Auto: Error de referencia nula: {nullEx.Message}");
|
||||
Debug.WriteLine($"TSNet Auto: StackTrace: {nullEx.StackTrace}");
|
||||
|
||||
// Intentar reinicializar el simulador
|
||||
try
|
||||
{
|
||||
tsnetSimulationManager = new TSNetSimulationManager();
|
||||
Debug.WriteLine("TSNet Auto: tsnetSimulationManager reinicializado");
|
||||
}
|
||||
catch (Exception reinitEx)
|
||||
{
|
||||
Debug.WriteLine($"TSNet Auto: Error al reinicializar: {reinitEx.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"TSNet Auto: Excepción: {ex.Message}");
|
||||
Debug.WriteLine($"TSNet Auto: Excepción general: {ex.Message}");
|
||||
Debug.WriteLine($"TSNet Auto: Tipo: {ex.GetType().Name}");
|
||||
Debug.WriteLine($"TSNet Auto: StackTrace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1965,14 +2049,14 @@ namespace CtrEditor
|
|||
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
|
||||
{
|
||||
// Evitar doble registro - solo registrar si no está ya registrado
|
||||
if (!hydraulicSimulationManager.HydraulicObjects.Contains(obj))
|
||||
if (!tsnetSimulationManager.HydraulicObjects.Contains(obj))
|
||||
{
|
||||
hydraulicSimulationManager.RegisterHydraulicObject(obj);
|
||||
Debug.WriteLine($"Objeto hidráulico registrado: {obj.Nombre}");
|
||||
tsnetSimulationManager.RegisterHydraulicObject(obj);
|
||||
Debug.WriteLine($"Objeto hidráulico registrado en TSNet: {obj.Nombre}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Objeto hidráulico ya registrado (omitido): {obj.Nombre}");
|
||||
Debug.WriteLine($"Objeto hidráulico ya registrado en TSNet (omitido): {obj.Nombre}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1984,8 +2068,8 @@ namespace CtrEditor
|
|||
{
|
||||
// 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}");
|
||||
tsnetSimulationManager.UnregisterHydraulicObject(obj);
|
||||
Debug.WriteLine($"Objeto desregistrado del simulador hidráulico TSNet: {obj.Nombre}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1993,8 +2077,8 @@ namespace CtrEditor
|
|||
/// </summary>
|
||||
public void ResetHydraulicSimulation()
|
||||
{
|
||||
hydraulicSimulationManager.Reset();
|
||||
Debug.WriteLine("Simulador hidráulico reiniciado");
|
||||
tsnetSimulationManager.Reset();
|
||||
Debug.WriteLine("Simulador hidráulico TSNet reiniciado");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -2002,7 +2086,7 @@ namespace CtrEditor
|
|||
/// </summary>
|
||||
public string GetHydraulicSimulationStats()
|
||||
{
|
||||
return hydraulicSimulationManager.GetSimulationStats();
|
||||
return tsnetSimulationManager.GetSimulationStats();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -2010,8 +2094,8 @@ namespace CtrEditor
|
|||
/// </summary>
|
||||
public void SetHydraulicSimulationEnabled(bool enabled)
|
||||
{
|
||||
hydraulicSimulationManager.IsHydraulicSimulationEnabled = enabled;
|
||||
Debug.WriteLine($"Simulación hidráulica {(enabled ? "habilitada" : "deshabilitada")}");
|
||||
tsnetSimulationManager.IsHydraulicSimulationEnabled = enabled;
|
||||
Debug.WriteLine($"Simulación hidráulica TSNet {(enabled ? "habilitada" : "deshabilitada")}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -2020,7 +2104,7 @@ namespace CtrEditor
|
|||
private void RegisterLoadedHydraulicObjects()
|
||||
{
|
||||
// Limpiar registros previos
|
||||
hydraulicSimulationManager.ClearHydraulicObjects();
|
||||
tsnetSimulationManager.ClearHydraulicObjects();
|
||||
|
||||
// Crear una lista temporal para evitar duplicados durante la carga
|
||||
var objectsToRegister = new HashSet<osBase>();
|
||||
|
@ -2037,11 +2121,11 @@ namespace CtrEditor
|
|||
// Registrar los objetos únicos
|
||||
foreach (var obj in objectsToRegister)
|
||||
{
|
||||
hydraulicSimulationManager.RegisterHydraulicObject(obj);
|
||||
Debug.WriteLine($"Objeto hidráulico registrado en carga: {obj.Nombre}");
|
||||
tsnetSimulationManager.RegisterHydraulicObject(obj);
|
||||
Debug.WriteLine($"Objeto hidráulico registrado en carga TSNet: {obj.Nombre}");
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Registrados {hydraulicSimulationManager.HydraulicObjects.Count} objetos hidráulicos únicos tras cargar proyecto");
|
||||
Debug.WriteLine($"Registrados {tsnetSimulationManager.HydraulicObjects.Count} objetos hidráulicos únicos tras cargar proyecto en TSNet");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -2055,10 +2139,10 @@ namespace CtrEditor
|
|||
{
|
||||
try
|
||||
{
|
||||
if (hydraulicSimulationManager != null)
|
||||
if (tsnetSimulationManager != null)
|
||||
{
|
||||
hydraulicSimulationManager.InvalidateNetwork();
|
||||
Debug.WriteLine("Red hidráulica invalidada y marcada para recálculo");
|
||||
tsnetSimulationManager.InvalidateNetwork();
|
||||
Debug.WriteLine("Red hidráulica TSNet invalidada y marcada para recálculo");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -2333,6 +2417,41 @@ namespace CtrEditor
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Método de debugging para generar un reporte detallado de IOException y estado del sistema
|
||||
/// </summary>
|
||||
public void GenerateIOExceptionReport()
|
||||
{
|
||||
try
|
||||
{
|
||||
var finalTimestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
Debug.WriteLine($"\n=== REPORTE DE ESTADO ({finalTimestamp}) ===");
|
||||
Debug.WriteLine($"Simulación ejecutándose: {IsSimulationRunning}");
|
||||
Debug.WriteLine($"Timer Simulación activo: {_timerSimulacion?.Enabled}");
|
||||
Debug.WriteLine($"Timer TSNet activo: {_timerTSNet?.Enabled}");
|
||||
Debug.WriteLine($"Timer Display activo: {_timerDisplayUpdate?.IsEnabled}");
|
||||
Debug.WriteLine($"Timer 3D activo: {_timer3DUpdate?.IsEnabled}");
|
||||
Debug.WriteLine($"TSNet Manager inicializado: {tsnetSimulationManager != null}");
|
||||
|
||||
if (tsnetSimulationManager != null)
|
||||
{
|
||||
Debug.WriteLine($"TSNet ejecutándose: {tsnetSimulationManager.IsRunning}");
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Objetos simulables: {ObjetosSimulables?.Count ?? 0}");
|
||||
|
||||
var hydraulicObjects = ObjetosSimulables?.Where(obj =>
|
||||
obj.GetType().Name.Contains("osHyd"))?.ToList();
|
||||
Debug.WriteLine($"Objetos hidráulicos: {hydraulicObjects?.Count ?? 0}");
|
||||
|
||||
Debug.WriteLine($"=== FIN REPORTE ===\n");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error en GenerateIOExceptionReport: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
@ -2356,7 +2475,6 @@ namespace CtrEditor
|
|||
[System.Obsolete("Use ImageDataDictionary instead")]
|
||||
public Dictionary<string, string>? ImageCustomNames { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class TipoSimulable
|
||||
|
|
|
@ -346,16 +346,40 @@ namespace CtrEditor.ObjetosSim
|
|||
// Verificar que la tubería esté conectada a dos componentes
|
||||
if (!string.IsNullOrEmpty(Id_ComponenteA) && !string.IsNullOrEmpty(Id_ComponenteB))
|
||||
{
|
||||
// Crear elemento pipe con propiedades correctas
|
||||
var pipeElement = new Pipe(Length, Diameter, Roughness);
|
||||
try
|
||||
{
|
||||
// Resolver los nombres de los componentes conectados
|
||||
var nodeNameA = ResolveComponentNodeName(Id_ComponenteA);
|
||||
var nodeNameB = ResolveComponentNodeName(Id_ComponenteB);
|
||||
|
||||
elements.Add(new HydraulicElementDefinition(
|
||||
$"PIPE_{Nombre}",
|
||||
Id_ComponenteA,
|
||||
Id_ComponenteB,
|
||||
pipeElement,
|
||||
$"Tubería - L:{Length:F1}m, D:{Diameter*1000:F0}mm, Rough:{Roughness*1000:F2}mm"
|
||||
));
|
||||
if (!string.IsNullOrEmpty(nodeNameA) && !string.IsNullOrEmpty(nodeNameB))
|
||||
{
|
||||
// Crear elemento pipe con propiedades correctas
|
||||
var pipeElement = new Pipe(Length, Diameter, Roughness);
|
||||
|
||||
elements.Add(new HydraulicElementDefinition(
|
||||
$"PIPE_{Nombre}",
|
||||
nodeNameA,
|
||||
nodeNameB,
|
||||
pipeElement,
|
||||
$"Tubería - L:{Length:F1}m, D:{Diameter*1000:F0}mm, Rough:{Roughness*1000:F2}mm"
|
||||
));
|
||||
|
||||
if (VerboseOutput)
|
||||
{
|
||||
Debug.WriteLine($"Pipe {Nombre}: Conectando nodos '{nodeNameA}' -> '{nodeNameB}'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Warning: Pipe {Nombre} no pudo resolver nombres de nodos. " +
|
||||
$"ComponenteA='{Id_ComponenteA}', ComponenteB='{Id_ComponenteB}'");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error en GetHydraulicElements para pipe {Nombre}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
|
@ -371,6 +395,209 @@ namespace CtrEditor.ObjetosSim
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resuelve el ID de un componente al nombre del nodo hidráulico correspondiente
|
||||
/// THREAD-SAFE: No accede a objetos WPF desde background threads
|
||||
/// </summary>
|
||||
private string ResolveComponentNodeName(string componentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Debug: Log the original componentId
|
||||
Debug.WriteLine($"Pipe {Nombre}: Resolviendo componentId '{componentId}'");
|
||||
|
||||
// Primero intentar usar el ID directamente como nombre (para compatibilidad)
|
||||
if (!string.IsNullOrEmpty(componentId))
|
||||
{
|
||||
// Para evitar problemas de threading, usar el Dispatcher para acceder a objetos UI
|
||||
MainViewModel mainViewModel = null;
|
||||
|
||||
// Solo acceder a la UI desde el thread principal
|
||||
if (Application.Current?.Dispatcher?.CheckAccess() == true)
|
||||
{
|
||||
mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Si estamos en un background thread, usar Invoke para acceder de forma segura
|
||||
Application.Current?.Dispatcher?.Invoke(() =>
|
||||
{
|
||||
mainViewModel = Application.Current?.MainWindow?.DataContext as MainViewModel;
|
||||
});
|
||||
}
|
||||
|
||||
if (mainViewModel?.ObjetosSimulables != null)
|
||||
{
|
||||
List<osBase> hydraulicComponents = null;
|
||||
|
||||
// Obtener componentes de forma thread-safe
|
||||
if (Application.Current?.Dispatcher?.CheckAccess() == true)
|
||||
{
|
||||
hydraulicComponents = mainViewModel.ObjetosSimulables
|
||||
.OfType<IHydraulicComponent>()
|
||||
.Where(comp => comp is osBase)
|
||||
.Cast<osBase>()
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current?.Dispatcher?.Invoke(() =>
|
||||
{
|
||||
hydraulicComponents = mainViewModel.ObjetosSimulables
|
||||
.OfType<IHydraulicComponent>()
|
||||
.Where(comp => comp is osBase)
|
||||
.Cast<osBase>()
|
||||
.ToList();
|
||||
});
|
||||
}
|
||||
|
||||
if (hydraulicComponents != null)
|
||||
{
|
||||
// Debug: Log available components
|
||||
Debug.WriteLine($"Pipe {Nombre}: Componentes hidráulicos disponibles:");
|
||||
foreach (var comp in hydraulicComponents)
|
||||
{
|
||||
Debug.WriteLine($" - Nombre: '{comp.Nombre}', ID: '{comp.Id?.Value}'");
|
||||
}
|
||||
|
||||
// Buscar por nombre primero (caso más común)
|
||||
var componentByName = hydraulicComponents
|
||||
.FirstOrDefault(comp => comp.Nombre == componentId);
|
||||
|
||||
if (componentByName != null)
|
||||
{
|
||||
Debug.WriteLine($"Pipe {Nombre}: Encontrado componente por nombre: '{componentByName.Nombre}'");
|
||||
|
||||
// Verificar si es una bomba (requiere resolución especial de nodos)
|
||||
if (componentByName is osHydPump)
|
||||
{
|
||||
var sanitizedName = SanitizeNodeName(componentByName.Nombre);
|
||||
|
||||
// Determinar si esta tubería está conectada a la entrada o salida de la bomba
|
||||
// Basándonos en si la bomba es origen (ComponenteA) o destino (ComponenteB) de esta tubería
|
||||
if (componentId == Id_ComponenteA)
|
||||
{
|
||||
// La bomba es ComponenteA (origen de esta tubería)
|
||||
// Por lo tanto, conectamos desde la salida de la bomba (NODE_B)
|
||||
Debug.WriteLine($"Pipe {Nombre}: Bomba como origen - conectando desde salida: NODE_B_{sanitizedName}");
|
||||
return $"NODE_B_{sanitizedName}";
|
||||
}
|
||||
else if (componentId == Id_ComponenteB)
|
||||
{
|
||||
// La bomba es ComponenteB (destino de esta tubería)
|
||||
// Por lo tanto, conectamos hacia la entrada de la bomba (NODE_A)
|
||||
Debug.WriteLine($"Pipe {Nombre}: Bomba como destino - conectando hacia entrada: NODE_A_{sanitizedName}");
|
||||
return $"NODE_A_{sanitizedName}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: usar salida por defecto
|
||||
Debug.WriteLine($"Pipe {Nombre}: Usando salida por defecto de bomba: NODE_B_{sanitizedName}");
|
||||
return $"NODE_B_{sanitizedName}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Para tanques y otras componentes, usar el nombre sanitizado
|
||||
return SanitizeNodeName(componentByName.Nombre);
|
||||
}
|
||||
}
|
||||
|
||||
// Si no se encuentra por nombre, buscar por ID único (convertir string a int)
|
||||
if (int.TryParse(componentId, out int idValue))
|
||||
{
|
||||
var componentById = hydraulicComponents
|
||||
.FirstOrDefault(comp => comp.Id?.Value == idValue);
|
||||
|
||||
if (componentById != null)
|
||||
{
|
||||
Debug.WriteLine($"Pipe {Nombre}: Encontrado componente por ID {idValue}: '{componentById.Nombre}'");
|
||||
|
||||
// Verificar si es una bomba (requiere resolución especial de nodos)
|
||||
if (componentById is osHydPump)
|
||||
{
|
||||
var sanitizedName = SanitizeNodeName(componentById.Nombre);
|
||||
|
||||
// Determinar si esta tubería está conectada a la entrada o salida de la bomba
|
||||
if (componentId == Id_ComponenteA)
|
||||
{
|
||||
Debug.WriteLine($"Pipe {Nombre}: Bomba como origen (ID) - conectando desde salida: NODE_B_{sanitizedName}");
|
||||
return $"NODE_B_{sanitizedName}";
|
||||
}
|
||||
else if (componentId == Id_ComponenteB)
|
||||
{
|
||||
Debug.WriteLine($"Pipe {Nombre}: Bomba como destino (ID) - conectando hacia entrada: NODE_A_{sanitizedName}");
|
||||
return $"NODE_A_{sanitizedName}";
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Pipe {Nombre}: Usando salida por defecto de bomba (ID): NODE_B_{sanitizedName}");
|
||||
return $"NODE_B_{sanitizedName}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return SanitizeNodeName(componentById.Nombre);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Último intento: usar el componentId directamente si es un nombre válido
|
||||
Debug.WriteLine($"Pipe {Nombre}: No se encontró componente, usando '{componentId}' directamente");
|
||||
return SanitizeNodeName(componentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Pipe {Nombre}: MainViewModel o ObjetosSimulables es null");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Pipe {Nombre}: componentId está vacío");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error resolviendo componente '{componentId}': {ex.Message}");
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitiza nombres de nodos para compatibilidad con EPANET INP
|
||||
/// </summary>
|
||||
private string SanitizeNodeName(string nodeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(nodeName))
|
||||
return string.Empty;
|
||||
|
||||
// Reemplazar espacios y caracteres especiales con guiones bajos
|
||||
var sanitized = nodeName
|
||||
.Replace(" ", "_")
|
||||
.Replace("á", "a")
|
||||
.Replace("é", "e")
|
||||
.Replace("í", "i")
|
||||
.Replace("ó", "o")
|
||||
.Replace("ú", "u")
|
||||
.Replace("ü", "u")
|
||||
.Replace("ñ", "n")
|
||||
.Replace("Á", "A")
|
||||
.Replace("É", "E")
|
||||
.Replace("Í", "I")
|
||||
.Replace("Ó", "O")
|
||||
.Replace("Ú", "U")
|
||||
.Replace("Ü", "U")
|
||||
.Replace("Ñ", "N");
|
||||
|
||||
// Remover caracteres no válidos para EPANET
|
||||
var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray();
|
||||
return new string(validChars);
|
||||
}
|
||||
|
||||
private bool VerboseOutput => true; // Habilitar salida detallada para debugging
|
||||
|
||||
// Implementación de IHydraulicFlowReceiver
|
||||
public void SetFlow(double flow)
|
||||
{
|
||||
|
|
|
@ -275,7 +275,7 @@ namespace CtrEditor.ObjetosSim
|
|||
if (IsRunning && HasFlow)
|
||||
ImageSource_oculta = ImageFromPath("/imagenes/pump_run.png");
|
||||
else if (IsRunning)
|
||||
ImageSource_oculta = ImageFromPath("/imagenes/pump_idle.png");
|
||||
ImageSource_oculta = ImageFromPath("/imagenes/pump_run.png"); // Usar pump_run como fallback para idle
|
||||
else
|
||||
ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png");
|
||||
}
|
||||
|
@ -324,16 +324,75 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public List<HydraulicNodeDefinition> GetHydraulicNodes()
|
||||
{
|
||||
// Las bombas no crean nodos propios, se conectan entre nodos existentes
|
||||
return new List<HydraulicNodeDefinition>();
|
||||
var nodes = new List<HydraulicNodeDefinition>();
|
||||
|
||||
// Sanitizar el nombre para compatibilidad con EPANET
|
||||
var sanitizedName = SanitizeNodeName(Nombre);
|
||||
|
||||
// Crear nodos de entrada y salida para la bomba
|
||||
nodes.Add(new HydraulicNodeDefinition(
|
||||
$"NODE_A_{sanitizedName}",
|
||||
false,
|
||||
null,
|
||||
$"Nodo entrada bomba {sanitizedName}"));
|
||||
|
||||
nodes.Add(new HydraulicNodeDefinition(
|
||||
$"NODE_B_{sanitizedName}",
|
||||
false,
|
||||
null,
|
||||
$"Nodo salida bomba {sanitizedName}"));
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitiza nombres de nodos para compatibilidad con EPANET INP
|
||||
/// </summary>
|
||||
private string SanitizeNodeName(string nodeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(nodeName))
|
||||
return "UnknownPump";
|
||||
|
||||
// Reemplazar espacios y caracteres especiales con guiones bajos
|
||||
var sanitized = nodeName
|
||||
.Replace(" ", "_")
|
||||
.Replace("á", "a")
|
||||
.Replace("é", "e")
|
||||
.Replace("í", "i")
|
||||
.Replace("ó", "o")
|
||||
.Replace("ú", "u")
|
||||
.Replace("ü", "u")
|
||||
.Replace("ñ", "n")
|
||||
.Replace("Á", "A")
|
||||
.Replace("É", "E")
|
||||
.Replace("Í", "I")
|
||||
.Replace("Ó", "O")
|
||||
.Replace("Ú", "U")
|
||||
.Replace("Ü", "U")
|
||||
.Replace("Ñ", "N");
|
||||
|
||||
// Remover caracteres no válidos para EPANET
|
||||
var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray();
|
||||
var result = new string(validChars);
|
||||
|
||||
// Asegurar que empiece con una letra
|
||||
if (!string.IsNullOrEmpty(result) && !char.IsLetter(result[0]))
|
||||
{
|
||||
result = "Pump_" + result;
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(result) ? "UnknownPump" : result;
|
||||
}
|
||||
|
||||
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Sanitizar el nombre para compatibilidad con EPANET
|
||||
var sanitizedName = SanitizeNodeName(Nombre);
|
||||
|
||||
// Buscar resultados de esta bomba en TSNet
|
||||
var pumpElementName = $"PUMP_{Nombre}";
|
||||
var pumpElementName = $"PUMP_{sanitizedName}";
|
||||
if (flows.ContainsKey(pumpElementName))
|
||||
{
|
||||
var pumpFlow = flows[pumpElementName];
|
||||
|
@ -365,13 +424,16 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
if (IsRunning)
|
||||
{
|
||||
// Sanitizar el nombre para compatibilidad con EPANET
|
||||
var sanitizedName = SanitizeNodeName(Nombre);
|
||||
|
||||
// Crear elemento de bomba para TSNet
|
||||
var pumpElement = new PumpHQ(PumpHead, MaxFlow);
|
||||
|
||||
elements.Add(new HydraulicElementDefinition(
|
||||
$"PUMP_{Nombre}",
|
||||
$"NODE_A_{Nombre}",
|
||||
$"NODE_B_{Nombre}",
|
||||
$"PUMP_{sanitizedName}",
|
||||
$"NODE_A_{sanitizedName}",
|
||||
$"NODE_B_{sanitizedName}",
|
||||
pumpElement,
|
||||
$"Bomba hidráulica - Head: {PumpHead:F1}m, Flow: {MaxFlow * 3600:F1} m³/h"
|
||||
));
|
||||
|
|
|
@ -432,19 +432,63 @@ namespace CtrEditor.ObjetosSim
|
|||
public List<HydraulicNodeDefinition> GetHydraulicNodes()
|
||||
{
|
||||
var nodes = new List<HydraulicNodeDefinition>();
|
||||
// Sanitizar el nombre para compatibilidad con EPANET
|
||||
var sanitizedName = SanitizeNodeName(Nombre);
|
||||
// Tanque como nodo libre por defecto
|
||||
nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, $"Tanque hidráulico - {FluidDescription}"));
|
||||
nodes.Add(new HydraulicNodeDefinition(sanitizedName, false, null, $"Tanque hidráulico - {FluidDescription}"));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitiza nombres de nodos para compatibilidad con EPANET INP
|
||||
/// </summary>
|
||||
private string SanitizeNodeName(string nodeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(nodeName))
|
||||
return "UnknownTank";
|
||||
|
||||
// Reemplazar espacios y caracteres especiales con guiones bajos
|
||||
var sanitized = nodeName
|
||||
.Replace(" ", "_")
|
||||
.Replace("á", "a")
|
||||
.Replace("é", "e")
|
||||
.Replace("í", "i")
|
||||
.Replace("ó", "o")
|
||||
.Replace("ú", "u")
|
||||
.Replace("ü", "u")
|
||||
.Replace("ñ", "n")
|
||||
.Replace("Á", "A")
|
||||
.Replace("É", "E")
|
||||
.Replace("Í", "I")
|
||||
.Replace("Ó", "O")
|
||||
.Replace("Ú", "U")
|
||||
.Replace("Ü", "U")
|
||||
.Replace("Ñ", "N");
|
||||
|
||||
// Remover caracteres no válidos para EPANET
|
||||
var validChars = sanitized.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray();
|
||||
var result = new string(validChars);
|
||||
|
||||
// Asegurar que empiece con una letra
|
||||
if (!string.IsNullOrEmpty(result) && !char.IsLetter(result[0]))
|
||||
{
|
||||
result = "Tank_" + result;
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(result) ? "UnknownTank" : result;
|
||||
}
|
||||
|
||||
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Sanitizar el nombre para compatibilidad con EPANET
|
||||
var sanitizedName = SanitizeNodeName(Nombre);
|
||||
|
||||
// Actualizar presión desde TSNet
|
||||
if (pressures.ContainsKey(Nombre))
|
||||
if (pressures.ContainsKey(sanitizedName))
|
||||
{
|
||||
var pressurePa = pressures[Nombre];
|
||||
var pressurePa = pressures[sanitizedName];
|
||||
CurrentPressure = pressurePa / 100000.0; // Convertir Pa a bar
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Windows.Controls;
|
||||
using CtrEditor.HydraulicSimulator;
|
||||
using CtrEditor.HydraulicSimulator.TSNet;
|
||||
using CtrEditor.Simulacion;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
|
@ -90,6 +91,19 @@ namespace CtrEditor.ObjetosSim
|
|||
}
|
||||
}
|
||||
|
||||
// Sobrecarga para TSNetSimulationManager - simplemente no asigna el hydraulicSimulationManager
|
||||
public static void AssignDatos(UserControl userControl, osBase datos, SimulationManagerBEPU simulationManager, TSNetSimulationManager tsnetSimulationManager)
|
||||
{
|
||||
if (userControl is IDataContainer dataContainer)
|
||||
{
|
||||
dataContainer.Datos = datos;
|
||||
userControl.DataContext = datos;
|
||||
datos.VisualRepresentation = userControl;
|
||||
datos.simulationManager = simulationManager;
|
||||
// No asignamos hydraulicSimulationManager para TSNet - se maneja de manera diferente
|
||||
}
|
||||
}
|
||||
|
||||
public static void LimpiarPropiedadesosDatos(PropertyGrid propertyGrid)
|
||||
{
|
||||
// Forzar la actualización de bindings pendientes antes de limpiar
|
||||
|
|
Loading…
Reference in New Issue