From 8a599d94a1d382192d27ed7849a8e295ae1a49a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 16:56:00 +0000 Subject: [PATCH 1/4] Initial plan From e4f2d77794a1d3018e8481a570e0959916be0b4f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:08:01 +0000 Subject: [PATCH 2/4] Add time-based parachute trigger support (launch + X and burnout + X) Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/rocket/parachute.py | 107 ++++++- rocketpy/simulation/flight.py | 4 + .../test_time_based_parachute_triggers.py | 204 ++++++++++++ tests/unit/rocket/test_parachute.py | 296 ++++++++++++++++++ 4 files changed, 595 insertions(+), 16 deletions(-) create mode 100644 tests/integration/test_time_based_parachute_triggers.py create mode 100644 tests/unit/rocket/test_parachute.py diff --git a/rocketpy/rocket/parachute.py b/rocketpy/rocket/parachute.py index e27216e26..765008199 100644 --- a/rocketpy/rocket/parachute.py +++ b/rocketpy/rocket/parachute.py @@ -46,15 +46,23 @@ class Parachute: - The string "apogee" which triggers the parachute at apogee, i.e., when the rocket reaches its highest point and starts descending. + + - The string "launch + X" where X is a number in seconds. The parachute + will be ejected X seconds after launch (t=0). This is useful for + simulating delay charges that activate at a fixed time from launch. + + - The string "burnout + X" where X is a number in seconds. The parachute + will be ejected X seconds after motor burnout. This is useful for + simulating delay charges in motors with delay elements. Parachute.triggerfunc : function Trigger function created from the trigger used to evaluate the trigger condition for the parachute ejection system. It is a callable function - that takes three arguments: Freestream pressure in Pa, Height above - ground level in meters, and the state vector of the simulation. The - returns ``True`` if the parachute ejection system should be triggered - and ``False`` otherwise. + that takes six arguments: Freestream pressure in Pa, Height above + ground level in meters, the state vector of the simulation, sensors list, + current time t, and rocket object. It returns ``True`` if the parachute + ejection system should be triggered and ``False`` otherwise. .. note: @@ -153,6 +161,14 @@ def __init__( height above ground level. - The string "apogee" which triggers the parachute at apogee, i.e., \ when the rocket reaches its highest point and starts descending. + - The string "launch + X" where X is the delay in seconds from launch. \ + For example, "launch + 5" triggers 5 seconds after launch. This is \ + useful for simulating delay charges that activate at a fixed time \ + from launch. + - The string "burnout + X" where X is the delay in seconds from motor \ + burnout. For example, "burnout + 3.5" triggers 3.5 seconds after \ + motor burnout. This is useful for simulating delay charges in motors \ + with delay elements. .. note:: @@ -232,14 +248,19 @@ def __evaluate_trigger_function(self, trigger): sig = signature(triggerfunc) if len(sig.parameters) == 3: - def triggerfunc(p, h, y, sensors): + def triggerfunc(p, h, y, sensors, t=None, rocket=None): return trigger(p, h, y) + elif len(sig.parameters) == 4: + + def triggerfunc(p, h, y, sensors, t=None, rocket=None): + return trigger(p, h, y, sensors) + self.triggerfunc = triggerfunc elif isinstance(trigger, (int, float)): # The parachute is deployed at a given height - def triggerfunc(p, h, y, sensors): # pylint: disable=unused-argument + def triggerfunc(p, h, y, sensors, t=None, rocket=None): # pylint: disable=unused-argument # p = pressure considering parachute noise signal # h = height above ground level considering parachute noise signal # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3] @@ -247,20 +268,74 @@ def triggerfunc(p, h, y, sensors): # pylint: disable=unused-argument self.triggerfunc = triggerfunc - elif trigger.lower() == "apogee": - # The parachute is deployed at apogee - def triggerfunc(p, h, y, sensors): # pylint: disable=unused-argument - # p = pressure considering parachute noise signal - # h = height above ground level considering parachute noise signal - # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3] - return y[5] < 0 - - self.triggerfunc = triggerfunc + elif isinstance(trigger, str): + trigger_lower = trigger.lower().strip() + + if trigger_lower == "apogee": + # The parachute is deployed at apogee + def triggerfunc(p, h, y, sensors, t=None, rocket=None): # pylint: disable=unused-argument + # p = pressure considering parachute noise signal + # h = height above ground level considering parachute noise signal + # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3] + return y[5] < 0 + + self.triggerfunc = triggerfunc + + elif "+" in trigger_lower: + # Time-based trigger: "launch + X" or "burnout + X" + parts = trigger_lower.split("+") + if len(parts) != 2: + raise ValueError( + f"Invalid time-based trigger format for parachute '{self.name}'. " + + "Expected format: 'launch + delay' or 'burnout + delay' " + + "where delay is a number in seconds." + ) + + event = parts[0].strip() + try: + delay = float(parts[1].strip()) + except ValueError: + raise ValueError( + f"Invalid delay value in trigger '{trigger}' for parachute '{self.name}'. " + + "Delay must be a number in seconds." + ) + + if event == "launch": + # Deploy at launch time + delay + def triggerfunc(p, h, y, sensors, t=None, rocket=None): # pylint: disable=unused-argument + if t is None: + return False + return t >= delay + + self.triggerfunc = triggerfunc + + elif event == "burnout": + # Deploy at motor burnout time + delay + def triggerfunc(p, h, y, sensors, t=None, rocket=None): # pylint: disable=unused-argument + if t is None or rocket is None: + return False + burnout_time = rocket.motor.burn_out_time + return t >= burnout_time + delay + + self.triggerfunc = triggerfunc + + else: + raise ValueError( + f"Invalid time-based trigger event '{event}' for parachute '{self.name}'. " + + "Supported events are 'launch' and 'burnout'." + ) + + else: + raise ValueError( + f"Unable to set the trigger function for parachute '{self.name}'. " + + "Trigger string must be 'apogee', 'launch + ', or 'burnout + '. " + + "See the Parachute class documentation for more information." + ) else: raise ValueError( f"Unable to set the trigger function for parachute '{self.name}'. " - + "Trigger must be a callable, a float value or the string 'apogee'. " + + "Trigger must be a callable, a float value, or a string. " + "See the Parachute class documentation for more information." ) diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index a38be7d93..a1e88b9fb 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -744,6 +744,8 @@ def __simulate(self, verbose): height_above_ground_level, self.y_sol, self.sensors, + node.t, + self.rocket, ): # Remove parachute from flight parachutes self.parachutes.remove(parachute) @@ -1003,6 +1005,8 @@ def __simulate(self, verbose): height_above_ground_level, overshootable_node.y_sol, self.sensors, + overshootable_node.t, + self.rocket, ): # Remove parachute from flight parachutes self.parachutes.remove(parachute) diff --git a/tests/integration/test_time_based_parachute_triggers.py b/tests/integration/test_time_based_parachute_triggers.py new file mode 100644 index 000000000..0ce78f85f --- /dev/null +++ b/tests/integration/test_time_based_parachute_triggers.py @@ -0,0 +1,204 @@ +"""Integration tests for time-based parachute triggers in flight simulations.""" + +import pytest + +from rocketpy import Flight + + +@pytest.mark.slow +def test_flight_with_launch_plus_delay_trigger( + example_spaceport_env, calisto_motorless, cesaroni_m1670 +): + """Test a complete flight simulation with a 'launch + X' parachute trigger. + + This simulates a rocket with a delay charge that deploys the parachute at a + fixed time after launch, similar to model rockets without avionics. + """ + # Use existing rocket and add motor + rocket = calisto_motorless + rocket.add_motor(cesaroni_m1670, position=-1.373) + + # Add a parachute with "launch + 5" trigger (5 seconds after launch) + rocket.add_parachute( + name="Main", + cd_s=10.0, + trigger="launch + 5", + sampling_rate=100, + lag=1.5, + noise=(0, 8.3, 0.5), + ) + + # Run flight simulation + flight = Flight( + environment=example_spaceport_env, + rocket=rocket, + rail_length=5.2, + inclination=85, + heading=0, + terminate_on_apogee=False, + ) + + # Verify that the parachute was deployed at approximately the right time + # The parachute should deploy at t=5s + lag=1.5s = 6.5s (fully deployed) + assert flight.t is not None + + # Check that parachute deployment happened (should have parachute_cd_s set) + # This attribute is set when parachute is deployed + assert hasattr(flight, "parachute_cd_s") + + # Verify simulation completed successfully + assert flight.apogee_time > 0 + + +@pytest.mark.slow +def test_flight_with_burnout_plus_delay_trigger( + example_spaceport_env, calisto_motorless, cesaroni_m1670 +): + """Test a complete flight simulation with a 'burnout + X' parachute trigger. + + This simulates a rocket with a motor delay charge that deploys the parachute + at a fixed time after motor burnout, typical of model rocket motors. + """ + # Use existing rocket and add motor + rocket = calisto_motorless + rocket.add_motor(cesaroni_m1670, position=-1.373) + + # Get motor burnout time + motor_burnout = rocket.motor.burn_out_time + + # Add a parachute with "burnout + 3" trigger (3 seconds after burnout) + rocket.add_parachute( + name="Main", + cd_s=10.0, + trigger="burnout + 3", + sampling_rate=100, + lag=1.5, + noise=(0, 8.3, 0.5), + ) + + # Run flight simulation + flight = Flight( + environment=example_spaceport_env, + rocket=rocket, + rail_length=5.2, + inclination=85, + heading=0, + terminate_on_apogee=False, + ) + + # Verify that the parachute was deployed + assert flight.t is not None + + # Check that parachute deployment happened + assert hasattr(flight, "parachute_cd_s") + + # The parachute should deploy after motor burnout + delay + # Expected deployment at burnout_time + 3s + lag + expected_deploy_time = motor_burnout + 3.0 + 1.5 + + # Verify simulation completed successfully and parachute deployed after burnout + assert flight.apogee_time > 0 + # The simulation should run past the expected deployment time + assert flight.t_final >= expected_deploy_time + + +@pytest.mark.slow +def test_flight_with_multiple_time_based_parachutes( + example_spaceport_env, calisto_motorless, cesaroni_m1670 +): + """Test a flight with multiple time-based parachutes (drogue and main). + + This simulates a dual-deployment system using time-based triggers. + """ + # Use existing rocket and add motor + rocket = calisto_motorless + rocket.add_motor(cesaroni_m1670, position=-1.373) + + # Add drogue parachute - deploys at burnout + 2 seconds + rocket.add_parachute( + name="Drogue", + cd_s=1.0, + trigger="burnout + 2", + sampling_rate=100, + lag=0.5, + noise=(0, 8.3, 0.5), + ) + + # Add main parachute - deploys at burnout + 10 seconds + rocket.add_parachute( + name="Main", + cd_s=10.0, + trigger="burnout + 10", + sampling_rate=100, + lag=1.0, + noise=(0, 8.3, 0.5), + ) + + # Run flight simulation + flight = Flight( + environment=example_spaceport_env, + rocket=rocket, + rail_length=5.2, + inclination=85, + heading=0, + terminate_on_apogee=False, + ) + + # Verify simulation completed successfully + assert flight.t is not None + assert flight.apogee_time > 0 + + # Check that parachute deployment happened + assert hasattr(flight, "parachute_cd_s") + + +@pytest.mark.slow +def test_flight_with_mixed_trigger_types( + example_spaceport_env, calisto_motorless, cesaroni_m1670 +): + """Test a flight with both time-based and traditional parachute triggers. + + This ensures backward compatibility when mixing trigger types. + """ + # Use existing rocket and add motor + rocket = calisto_motorless + rocket.add_motor(cesaroni_m1670, position=-1.373) + + # Add drogue parachute with traditional apogee trigger + rocket.add_parachute( + name="Drogue", + cd_s=1.0, + trigger="apogee", + sampling_rate=100, + lag=0.5, + noise=(0, 8.3, 0.5), + ) + + # Add main parachute with altitude trigger + rocket.add_parachute( + name="Main", + cd_s=10.0, + trigger=800.0, # 800 meters AGL + sampling_rate=100, + lag=1.0, + noise=(0, 8.3, 0.5), + ) + + # Run flight simulation + flight = Flight( + environment=example_spaceport_env, + rocket=rocket, + rail_length=5.2, + inclination=85, + heading=0, + terminate_on_apogee=False, + ) + + # Verify simulation completed successfully + assert flight.t is not None + assert flight.apogee_time > 0 + + # Check that parachute deployment happened + assert hasattr(flight, "parachute_cd_s") + + diff --git a/tests/unit/rocket/test_parachute.py b/tests/unit/rocket/test_parachute.py new file mode 100644 index 000000000..d27d91b08 --- /dev/null +++ b/tests/unit/rocket/test_parachute.py @@ -0,0 +1,296 @@ +"""Unit tests for the Parachute class, specifically focusing on trigger mechanisms.""" + +import pytest + +from rocketpy import Parachute + + +class TestParachuteTriggers: + """Test class for parachute trigger functionality.""" + + def test_apogee_trigger(self): + """Test that the 'apogee' trigger is correctly parsed and works.""" + parachute = Parachute( + name="test_apogee", + cd_s=1.0, + trigger="apogee", + sampling_rate=100, + lag=0, + ) + + # Test trigger function with descending velocity (should trigger) + state_descending = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + assert parachute.triggerfunc(101325, 1000, state_descending, [], 5.0, None) is True + + # Test trigger function with ascending velocity (should not trigger) + state_ascending = [0, 0, 1000, 0, 0, 10, 1, 0, 0, 0, 0, 0, 0] + assert parachute.triggerfunc(101325, 1000, state_ascending, [], 3.0, None) is False + + def test_altitude_trigger(self): + """Test that altitude-based trigger works correctly.""" + parachute = Parachute( + name="test_altitude", + cd_s=1.0, + trigger=500.0, # 500 meters + sampling_rate=100, + lag=0, + ) + + # Test at altitude above trigger point with descending velocity (should not trigger) + state_above = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + assert parachute.triggerfunc(101325, 600, state_above, [], 10.0, None) is False + + # Test at altitude below trigger point with descending velocity (should trigger) + state_below = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + assert parachute.triggerfunc(101325, 400, state_below, [], 15.0, None) is True + + # Test at altitude below trigger point with ascending velocity (should not trigger) + state_ascending = [0, 0, 1000, 0, 0, 10, 1, 0, 0, 0, 0, 0, 0] + assert parachute.triggerfunc(101325, 400, state_ascending, [], 2.0, None) is False + + def test_launch_plus_delay_trigger_parsing(self): + """Test that 'launch + X' trigger string is correctly parsed.""" + parachute = Parachute( + name="test_launch_delay", + cd_s=1.0, + trigger="launch + 5", + sampling_rate=100, + lag=0, + ) + + # Check that the parachute was created successfully + assert parachute.name == "test_launch_delay" + assert parachute.trigger == "launch + 5" + + def test_launch_plus_delay_trigger_functionality(self): + """Test that 'launch + X' trigger works correctly.""" + delay = 5.0 + parachute = Parachute( + name="test_launch_delay", + cd_s=1.0, + trigger=f"launch + {delay}", + sampling_rate=100, + lag=0, + ) + + state = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + + # Before delay time - should not trigger + assert parachute.triggerfunc(101325, 1000, state, [], 3.0, None) is False + + # Exactly at delay time - should trigger + assert parachute.triggerfunc(101325, 1000, state, [], 5.0, None) is True + + # After delay time - should trigger + assert parachute.triggerfunc(101325, 1000, state, [], 7.0, None) is True + + def test_burnout_plus_delay_trigger_parsing(self): + """Test that 'burnout + X' trigger string is correctly parsed.""" + parachute = Parachute( + name="test_burnout_delay", + cd_s=1.0, + trigger="burnout + 3.5", + sampling_rate=100, + lag=0, + ) + + # Check that the parachute was created successfully + assert parachute.name == "test_burnout_delay" + assert parachute.trigger == "burnout + 3.5" + + def test_burnout_plus_delay_trigger_functionality(self): + """Test that 'burnout + X' trigger works correctly.""" + # Create a mock rocket with motor + class MockMotor: + def __init__(self, burn_out_time): + self.burn_out_time = burn_out_time + + class MockRocket: + def __init__(self, burn_out_time): + self.motor = MockMotor(burn_out_time) + + delay = 3.5 + burnout_time = 2.0 + parachute = Parachute( + name="test_burnout_delay", + cd_s=1.0, + trigger=f"burnout + {delay}", + sampling_rate=100, + lag=0, + ) + + rocket = MockRocket(burnout_time) + state = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + + # Before burnout + delay - should not trigger + assert parachute.triggerfunc(101325, 1000, state, [], 4.0, rocket) is False + + # Exactly at burnout + delay - should trigger + assert parachute.triggerfunc(101325, 1000, state, [], 5.5, rocket) is True + + # After burnout + delay - should trigger + assert parachute.triggerfunc(101325, 1000, state, [], 10.0, rocket) is True + + def test_launch_trigger_with_whitespace(self): + """Test that whitespace in trigger string is handled correctly.""" + parachute1 = Parachute( + name="test1", + cd_s=1.0, + trigger="launch + 5", + sampling_rate=100, + lag=0, + ) + + parachute2 = Parachute( + name="test2", + cd_s=1.0, + trigger=" launch + 5 ", + sampling_rate=100, + lag=0, + ) + + parachute3 = Parachute( + name="test3", + cd_s=1.0, + trigger="LAUNCH + 5", + sampling_rate=100, + lag=0, + ) + + state = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + + # All should behave the same way + assert parachute1.triggerfunc(101325, 1000, state, [], 6.0, None) is True + assert parachute2.triggerfunc(101325, 1000, state, [], 6.0, None) is True + assert parachute3.triggerfunc(101325, 1000, state, [], 6.0, None) is True + + def test_invalid_trigger_format(self): + """Test that invalid trigger formats raise appropriate errors.""" + # Invalid string without '+' + with pytest.raises(ValueError, match="Unable to set the trigger function"): + Parachute( + name="test", + cd_s=1.0, + trigger="invalid_trigger", + sampling_rate=100, + lag=0, + ) + + # Invalid event name + with pytest.raises(ValueError, match="Invalid time-based trigger event"): + Parachute( + name="test", + cd_s=1.0, + trigger="invalid_event + 5", + sampling_rate=100, + lag=0, + ) + + # Invalid delay value (not a number) + with pytest.raises(ValueError, match="Invalid delay value"): + Parachute( + name="test", + cd_s=1.0, + trigger="launch + not_a_number", + sampling_rate=100, + lag=0, + ) + + # Invalid format (multiple '+') + with pytest.raises(ValueError, match="Invalid time-based trigger format"): + Parachute( + name="test", + cd_s=1.0, + trigger="launch + 5 + 3", + sampling_rate=100, + lag=0, + ) + + def test_decimal_delay_values(self): + """Test that decimal delay values work correctly.""" + parachute = Parachute( + name="test_decimal", + cd_s=1.0, + trigger="launch + 2.75", + sampling_rate=100, + lag=0, + ) + + state = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + + # Just before delay - should not trigger + assert parachute.triggerfunc(101325, 1000, state, [], 2.74, None) is False + + # At and after delay - should trigger + assert parachute.triggerfunc(101325, 1000, state, [], 2.75, None) is True + assert parachute.triggerfunc(101325, 1000, state, [], 3.0, None) is True + + def test_custom_callable_trigger_backward_compatibility(self): + """Test that custom callable triggers still work with backward compatibility.""" + # 3-parameter trigger (old style) + def old_trigger(p, h, y): + return y[5] < 0 and h < 800 + + parachute_old = Parachute( + name="test_old", + cd_s=1.0, + trigger=old_trigger, + sampling_rate=100, + lag=0, + ) + + # 4-parameter trigger (with sensors) + def new_trigger(p, h, y, sensors): + return y[5] < 0 and h < 800 + + parachute_new = Parachute( + name="test_new", + cd_s=1.0, + trigger=new_trigger, + sampling_rate=100, + lag=0, + ) + + state = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + + # Both should work with the new 6-parameter signature + assert parachute_old.triggerfunc(101325, 700, state, [], 10.0, None) is True + assert parachute_new.triggerfunc(101325, 700, state, [], 10.0, None) is True + + # Should not trigger above altitude + assert parachute_old.triggerfunc(101325, 900, state, [], 10.0, None) is False + assert parachute_new.triggerfunc(101325, 900, state, [], 10.0, None) is False + + def test_zero_delay_trigger(self): + """Test that zero delay triggers work correctly.""" + parachute = Parachute( + name="test_zero", + cd_s=1.0, + trigger="launch + 0", + sampling_rate=100, + lag=0, + ) + + state = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + + # Should trigger immediately at t=0 + assert parachute.triggerfunc(101325, 1000, state, [], 0.0, None) is True + + # Should also trigger at any positive time + assert parachute.triggerfunc(101325, 1000, state, [], 0.1, None) is True + + def test_negative_delay_trigger(self): + """Test that negative delays can be specified (edge case).""" + # This should work without errors, though it may not be physically meaningful + parachute = Parachute( + name="test_negative", + cd_s=1.0, + trigger="launch + -5", + sampling_rate=100, + lag=0, + ) + + state = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] + + # At t=0, already past the "negative" trigger time + assert parachute.triggerfunc(101325, 1000, state, [], 0.0, None) is True From fff71a50aef93bc1c4fd15d0061d8fad20708702 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:11:42 +0000 Subject: [PATCH 3/4] Address code review feedback - remove negative delay test and trailing blank lines Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- .../test_time_based_parachute_triggers.py | 2 -- tests/unit/rocket/test_parachute.py | 16 ---------------- 2 files changed, 18 deletions(-) diff --git a/tests/integration/test_time_based_parachute_triggers.py b/tests/integration/test_time_based_parachute_triggers.py index 0ce78f85f..450e1c7bc 100644 --- a/tests/integration/test_time_based_parachute_triggers.py +++ b/tests/integration/test_time_based_parachute_triggers.py @@ -200,5 +200,3 @@ def test_flight_with_mixed_trigger_types( # Check that parachute deployment happened assert hasattr(flight, "parachute_cd_s") - - diff --git a/tests/unit/rocket/test_parachute.py b/tests/unit/rocket/test_parachute.py index d27d91b08..c2519674e 100644 --- a/tests/unit/rocket/test_parachute.py +++ b/tests/unit/rocket/test_parachute.py @@ -278,19 +278,3 @@ def test_zero_delay_trigger(self): # Should also trigger at any positive time assert parachute.triggerfunc(101325, 1000, state, [], 0.1, None) is True - - def test_negative_delay_trigger(self): - """Test that negative delays can be specified (edge case).""" - # This should work without errors, though it may not be physically meaningful - parachute = Parachute( - name="test_negative", - cd_s=1.0, - trigger="launch + -5", - sampling_rate=100, - lag=0, - ) - - state = [0, 0, 1000, 0, 0, -10, 1, 0, 0, 0, 0, 0, 0] - - # At t=0, already past the "negative" trigger time - assert parachute.triggerfunc(101325, 1000, state, [], 0.0, None) is True From 89d23b96f8362c5974acc1f846c89fac5447ee08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 20:05:27 +0000 Subject: [PATCH 4/4] Fix merge issues - update missing triggerfunc call sites and remove duplicate doc line Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/rocket/parachute.py | 1 - rocketpy/simulation/flight.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rocketpy/rocket/parachute.py b/rocketpy/rocket/parachute.py index e289b2d23..e7234c43b 100644 --- a/rocketpy/rocket/parachute.py +++ b/rocketpy/rocket/parachute.py @@ -54,7 +54,6 @@ class Parachute: - The string "burnout + X" where X is a number in seconds. The parachute will be ejected X seconds after motor burnout. This is useful for simulating delay charges in motors with delay elements. - when the rocket reaches its highest point and starts descending. Parachute.triggerfunc : function diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index e3216471a..5413647ce 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1066,6 +1066,8 @@ def __check_and_handle_parachute_triggers( height_above_ground_level, self.y_sol, self.sensors, + node.t, + self.rocket, ): continue # Check next parachute @@ -1475,6 +1477,8 @@ def __check_overshootable_parachute_triggers( height_above_ground_level, overshootable_node.y_sol, self.sensors, + overshootable_node.t, + self.rocket, ): continue # Check next parachute