diff --git a/CtrEditor.code-workspace b/CtrEditor.code-workspace new file mode 100644 index 0000000..c0cd4b5 --- /dev/null +++ b/CtrEditor.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "../Libraries/LibS7Adv" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/MainViewModel.cs b/MainViewModel.cs index f9b6018..4f38103 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -746,7 +746,7 @@ namespace CtrEditor } stopwatch.Stop(); // Stop measuring time - Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms"); + // Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms"); } private void OpenWorkDirectory() diff --git a/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs b/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs index 4c25a79..e73cb3a 100644 --- a/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs +++ b/ObjetosSim/Emuladores/ucBottGenerator.xaml.cs @@ -14,46 +14,36 @@ namespace CtrEditor.ObjetosSim public partial class osBottGenerator : osBase, IosBase { TimerTON_TOFF _TON_TOFF = new TimerTON_TOFF(); - // Otros datos y métodos relevantes para la simulación - private float TiempoRestante; - private osBotella UltimaBotella; - public static string NombreClase() { return "BottGenerator"; } + private string nombre = NombreClase(); - public override string Nombre - { - get => nombre; - set => SetProperty(ref nombre, value); - } + public override string Nombre { get => nombre; set => SetProperty(ref nombre, value); } [ObservableProperty] private float offsetLeftSalida; [ObservableProperty] private float offsetTopSalida; - [ObservableProperty] [property: Description("The bottle will be destroyed if fall outside transport.")] [property: Category("Enable to Run:")] private bool preserve_Outside_Transport; - [ObservableProperty] [property: Description("PLC tag for consense to run. 1 => always")] [property: Category("Enable to Run:")] private string tag_consenso; - [ObservableProperty] [property: Description("Consense to run.")] [property: Category("Enable to Run:")] private bool consenso; - partial void OnConsensoChanged(bool value) { _TON_TOFF.Senal = value; } + [ObservableProperty] [property: Description("Consense is Normally close.")] [property: Category("Enable to Run:")] @@ -74,10 +64,8 @@ namespace CtrEditor.ObjetosSim [property: Description("Filter OUT signal.")] [property: Category("Enable to Run:")] bool filter_Output; - [ObservableProperty] private float botellas_hora; - partial void OnBotellas_horaChanged(float value) { Botellas_segundo = value / 3600; @@ -85,7 +73,6 @@ namespace CtrEditor.ObjetosSim [ObservableProperty] private float botellas_segundo; - partial void OnBotellas_segundoChanged(float value) { Botellas_hora = value * 3600; @@ -95,21 +82,6 @@ namespace CtrEditor.ObjetosSim private float velocidad_actual_percentual; [ObservableProperty] private float diametro_botella; - - - public osBottGenerator() - { - Ancho = 0.30f; - Alto = 0.30f; - Angulo = 0; - Velocidad_actual_percentual = 100; - Diametro_botella = 0.1f; - Botellas_hora = 10000; - Filtro_consenso_ON_s = 1; - Filtro_consenso_OFF_s = 0.1f; - Preserve_Outside_Transport = false; - } - public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) { if (Consenso_NC) @@ -118,67 +90,68 @@ namespace CtrEditor.ObjetosSim Consenso = LeerBitTag(Tag_consenso); } + private bool HayEspacioParaNuevaBotella(float X, float Y) + { + float radioMinimo = Diametro_botella / 4; // Distancia mínima entre centros + float radioMinimoCuadrado = radioMinimo * radioMinimo; + + // Buscar todas las botellas cercanas + foreach (var obj in _mainViewModel.ObjetosSimulables) + { + if (obj is osBotella) + { + float distanciaCuadrada = (float)(Math.Pow(obj.Left - X, 2) + Math.Pow(obj.Top - Y, 2)); + if (distanciaCuadrada < radioMinimoCuadrado) + { + return false; // Hay una botella demasiado cerca + } + } + } + return true; // No hay botellas cercanas + } + public override void UpdateControl(int elapsedMilliseconds) { bool habilitado; - _TON_TOFF.Tiempo_ON_s = Filtro_consenso_ON_s; _TON_TOFF.Tiempo_OFF_s = Filtro_consenso_OFF_s; - if (Consenso_Filtrado) habilitado = _TON_TOFF.SenalFiltrada(); else habilitado = Consenso; - Filter_Output = habilitado; if (habilitado && Velocidad_actual_percentual > 0) { + if (PrimeraActualizacion) + { + TiempoEntreBotellas = 3600 / (Botellas_hora * (Velocidad_actual_percentual / 100.0f)); + TiempoRestante = TiempoEntreBotellas; + PrimeraActualizacion = false; + } + TiempoRestante -= elapsedMilliseconds / 1000.0f; if (TiempoRestante <= 0) { - bool BotellaCreada = false; var X = Left + OffsetLeftSalida; var Y = Top + OffsetTopSalida; - if (UltimaBotella != null && UltimaBotella.RemoverDesdeSimulacion) - UltimaBotella = null; - - if (UltimaBotella == null) + if (HayEspacioParaNuevaBotella(X, Y)) { - // No hay botellas, se puede crear una nueva directamente var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y); ((osBotella)nuevaBotella).Diametro = Diametro_botella; ((osBotella)nuevaBotella).Preserve_Outside_Transport = Preserve_Outside_Transport; - ((osBotella)nuevaBotella).AutoCreated = true; nuevaBotella.AutoCreated = true; - UltimaBotella = (osBotella)nuevaBotella; - BotellaCreada = true; - } - else - { - // Calcular la distancia entre el centro de la última botella y la nueva posición - float distancia = (float)Math.Sqrt(Math.Pow(UltimaBotella.Left - X, 2) + Math.Pow(UltimaBotella.Top - Y, 2)); - float distanciaMinima = Diametro_botella / 4; // Asumiendo que el diámetro de la nueva botella es similar - if (distancia > distanciaMinima) - { - osBotella nuevaBotella = (osBotella)_mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y); - nuevaBotella.Diametro = Diametro_botella; - ((osBotella)nuevaBotella).Preserve_Outside_Transport = Preserve_Outside_Transport; - nuevaBotella.AutoCreated = true; - UltimaBotella = nuevaBotella; - BotellaCreada = true; - } + // Recalcular el tiempo entre botellas por si cambió la velocidad + TiempoEntreBotellas = 3600 / (Botellas_hora * (Velocidad_actual_percentual / 100.0f)); + TiempoRestante = TiempoEntreBotellas; } - if (BotellaCreada) - TiempoRestante += 3600 / (Botellas_hora * (Velocidad_actual_percentual / 100.0f)); - } } else { - TiempoRestante = 0; + PrimeraActualizacion = true; } } @@ -189,6 +162,13 @@ namespace CtrEditor.ObjetosSim base.ucLoaded(); } + private float TiempoEntreBotellas; // Nuevo: almacena el intervalo calculado + private bool PrimeraActualizacion = true; // Nuevo: flag para la primera actualización + public override void SimulationStop() + { + PrimeraActualizacion = true; // Reset del flag al detener + base.SimulationStop(); + } } public partial class ucBottGenerator : UserControl, IDataContainer @@ -202,19 +182,24 @@ namespace CtrEditor.ObjetosSim this.Loaded += OnLoaded; this.Unloaded += OnUnloaded; } + private void OnLoaded(object sender, RoutedEventArgs e) { Datos?.ucLoaded(); } + private void OnUnloaded(object sender, RoutedEventArgs e) { Datos?.ucUnLoaded(); } - public void Highlight(bool State) { } + + public void Highlight(bool State) + { + } + public ZIndexEnum ZIndex_Base() { return ZIndexEnum.Generadores; } } - -} +} \ No newline at end of file diff --git a/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs b/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs index 6616a41..f8074e5 100644 --- a/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs +++ b/ObjetosSim/Estaticos/ucVMmotorSim.xaml.cs @@ -202,10 +202,15 @@ namespace CtrEditor.ObjetosSim if (DB_Motor == 0) return; - // Read ControlWord in one operation - int controlWord = plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ControlWord") ?? 0; + // Add timestamp to trace when the read occurs + var timestamp = DateTime.Now; + + // Read ControlWord and track the raw response + var rawResponse = plc.LeerTagDInt($"\"DB MotorSimulate\".Motors[{DB_Motor}].ControlWord"); + int controlWord = rawResponse ?? 0; var control = VMMotorBitPacker.UnpackControlWord(controlWord); + // Update local state from ControlWord OUT_Run = control.run; OUT_Stop = control.stop; @@ -233,6 +238,7 @@ namespace CtrEditor.ObjetosSim if (Data.VFD_Trip_NC) STATUS_VFD_Trip = !STATUS_VFD_Trip; + // Pack all status bits and speed into StatusWord int statusWord = VMMotorBitPacker.PackStatusWord( _STATUS_VFD_Ready, diff --git a/Simulacion/Aether.cs b/Simulacion/Aether.cs index 9617d37..fa439a3 100644 --- a/Simulacion/Aether.cs +++ b/Simulacion/Aether.cs @@ -128,6 +128,43 @@ namespace CtrEditor.Simulacion return verticesList; } + private float CalculateAngleOverlap(Fixture bottle) + { + // Get bottle position relative to curve center + Vector2 centerToBottle = bottle.Body.Position - Body.Position; + + // Calculate angle of bottle relative to curve center (in radians) + float bottleAngle = (float)Math.Atan2(centerToBottle.Y, centerToBottle.X); + + // Normalize angle to be positive + if (bottleAngle < 0) + bottleAngle += 2 * (float)Math.PI; + + // If bottle is outside the angle range, return 0 + if (bottleAngle < _startAngle || bottleAngle > _endAngle) + return 0; + + // Calculate distance from center + float distanceToCenter = centerToBottle.Length(); + + // If bottle is outside radius range, return 0 + if (distanceToCenter < _innerRadius || distanceToCenter > _outerRadius) + return 0; + + // Calculate how far the bottle is from the edges + float angleFromStart = bottleAngle - _startAngle; + float angleToEnd = _endAngle - bottleAngle; + + // Use the minimum distance to either edge to calculate overlap + float minAngleDistance = Math.Min(angleFromStart, angleToEnd); + float totalAngle = _endAngle - _startAngle; + + // Calculate overlap percentage based on angle proximity to edges + float overlapPercentage = Math.Min(minAngleDistance / (totalAngle * 0.1f), 1.0f); + + return overlapPercentage; + } + public void ApplyCurveEffect(Fixture bottle) { Vector2 centerToBottle = bottle.Body.Position - Body.Position; @@ -135,17 +172,46 @@ namespace CtrEditor.Simulacion if (distanceToCenter >= _innerRadius && distanceToCenter <= _outerRadius) { - // Calcular la velocidad tangencial - float speedMetersPerSecond = Speed / 60.0f; - float angularVelocity = speedMetersPerSecond / distanceToCenter; + // Calculate overlap percentage + float overlapPercentage = CalculateAngleOverlap(bottle); - // Vector tangente (perpendicular al radio) - Vector2 tangent = new Vector2(-centerToBottle.Y, centerToBottle.X); - tangent.Normalize(); + // Only apply effect if there's overlap + if (overlapPercentage > 0) + { + // Calculate the tangential velocity + float speedMetersPerSecond = Speed / 60.0f; - // Velocidad deseada - Vector2 desiredVelocity = tangent * angularVelocity * distanceToCenter; - bottle.Body.LinearVelocity = desiredVelocity; + // Vector tangent (perpendicular to radius) + Vector2 tangent = new Vector2(-centerToBottle.Y, centerToBottle.X); + tangent.Normalize(); + + // Adjust tangent direction based on speed sign + if (speedMetersPerSecond < 0) + tangent = -tangent; + + // Current velocity magnitude + float currentSpeed = bottle.Body.LinearVelocity.Length(); + + // Desired conveyor speed + float conveyorSpeed = Math.Abs(speedMetersPerSecond); + + // Use the larger of the two speeds as base speed + float targetSpeed = Math.Max(currentSpeed, conveyorSpeed); + + // Lerp between current direction and curve direction + Vector2 currentDir = bottle.Body.LinearVelocity; + if (currentDir.LengthSquared() > 0) + currentDir.Normalize(); + else + currentDir = tangent; + + // Interpolate between current direction and curve direction + Vector2 newDirection = currentDir * (1 - overlapPercentage) + tangent * overlapPercentage; + newDirection.Normalize(); + + // Apply new velocity with combined speed + bottle.Body.LinearVelocity = newDirection * targetSpeed; + } } } } @@ -574,8 +640,9 @@ namespace CtrEditor.Simulacion foreach (var transporte in ListOnTransports) { if (transporte is simTransporte conveyorRect) - if (ApplyConveyorEffect(deltaTime_s, conveyorRect)) - break; + ApplyConveyorEffect(deltaTime_s, conveyorRect); + // if (ApplyConveyorEffect(deltaTime_s, conveyorRect)) + // break; if (transporte is simCurve conveyorCurve) conveyorCurve.ApplyCurveEffect(Body.FixtureList[0]); }