Skip to content
59 changes: 59 additions & 0 deletions rocketpy/motors/motor.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import base64
import re
import tempfile
import warnings
import xml.etree.ElementTree as ET
from abc import ABC, abstractmethod
from functools import cached_property
from os import path

import numpy as np
import requests

from ..mathutils.function import Function, funcify_method
from ..plots.motor_plots import _MotorPlots
Expand Down Expand Up @@ -1914,6 +1917,62 @@ def load_from_rse_file(
coordinate_system_orientation=coordinate_system_orientation,
)

@staticmethod
def load_from_thrustcurve_api(name: str, **kwargs):
"""
Creates a Motor instance by downloading a .eng file from the ThrustCurve API
based on the given motor name.

Parameters
----------
name : str
The motor name according to the API (e.g., "Cesaroni_M1670").
**kwargs :
Additional arguments passed to the Motor constructor, such as dry_mass, nozzle_radius, etc.

Returns
-------
instance : cls
A new Motor instance initialized using the downloaded .eng file.
"""

base_url = "https://www.thrustcurve.org/api/v1"

# Step 1. Search motor
response = requests.get(f"{base_url}/search.json", params={"commonName": name})
response.raise_for_status()
data = response.json()

if not data.get("results"):
print("No motor found.")
return None

motor = data["results"][0]
motor_id = motor["motorId"]
designation = motor["designation"].replace("/", "-")
print(f"Motor found: {designation} ({motor['manufacturer']})")

# Step 2. Download the .eng file
dl_response = requests.get(
f"{base_url}/download.json",
params={"motorIds": motor_id, "format": "RASP", "data": "file"},
)
dl_response.raise_for_status()
data = dl_response.json()

data_base64 = data["results"][0]["data"]
data_bytes = base64.b64decode(data_base64)

# Step 3. Create the motor from the .eng file

with tempfile.NamedTemporaryFile(suffix=".eng", delete=True) as tmp_file:
tmp_file.write(data_bytes)
tmp_file.flush()

motor = GenericMotor.load_from_eng_file(tmp_file.name, **kwargs)

return motor

def all_info(self):
"""Prints out all data and graphs available about the Motor."""
# Print motor details
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/motors/test_genericmotor.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,50 @@ def test_load_from_rse_file(generic_motor):
assert thrust_curve[0][1] == 0.0 # First thrust point
assert thrust_curve[-1][0] == 2.2 # Last point of time
assert thrust_curve[-1][1] == 0.0 # Last thrust point


def test_load_from_thrustcurve_api(generic_motor):
"""Tests the GenericMotor.load_from_thrustcurve_api method.

Parameters
----------
generic_motor : rocketpy.GenericMotor
The GenericMotor object to be used in the tests.
"""
# using cesaroni data as example
burn_time = (0, 3.9)
dry_mass = 5.231 - 3.101 # 2.130 kg
propellant_initial_mass = 3.101
chamber_radius = 75 / 1000
chamber_height = 757 / 1000
nozzle_radius = chamber_radius * 0.85 # 85% of chamber radius

# Parameters from manual testing using the SolidMotor class as a reference
average_thrust = 1545.218
total_impulse = 6026.350
max_thrust = 2200.0
exhaust_velocity = 1943.357

# creating motor from .eng file
generic_motor = generic_motor.load_from_thrustcurve_api("M1670")

# testing relevant parameters
assert generic_motor.burn_time == burn_time
assert generic_motor.dry_mass == dry_mass
assert generic_motor.propellant_initial_mass == propellant_initial_mass
assert generic_motor.chamber_radius == chamber_radius
assert generic_motor.chamber_height == chamber_height
assert generic_motor.chamber_position == 0
assert generic_motor.average_thrust == pytest.approx(average_thrust)
assert generic_motor.total_impulse == pytest.approx(total_impulse)
assert generic_motor.exhaust_velocity.average(*burn_time) == pytest.approx(
exhaust_velocity
)
assert generic_motor.max_thrust == pytest.approx(max_thrust)
assert generic_motor.nozzle_radius == pytest.approx(nozzle_radius)

# testing thrust curve
_, _, points = Motor.import_eng("data/motors/cesaroni/Cesaroni_M1670.eng")
assert generic_motor.thrust.y_array == pytest.approx(
Function(points, "Time (s)", "Thrust (N)", "linear", "zero").y_array
)
Loading