Skip to content

Commit 05ab711

Browse files
committed
Add the function to create a motor from the API of thrustcurve and the test
1 parent 170e89c commit 05ab711

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

rocketpy/motors/motor.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import base64
12
import re
3+
import tempfile
24
import warnings
35
import xml.etree.ElementTree as ET
46
from abc import ABC, abstractmethod
57
from functools import cached_property
68
from os import path
79

810
import numpy as np
11+
import requests
912

1013
from ..mathutils.function import Function, funcify_method
1114
from ..plots.motor_plots import _MotorPlots
@@ -1914,6 +1917,62 @@ def load_from_rse_file(
19141917
coordinate_system_orientation=coordinate_system_orientation,
19151918
)
19161919

1920+
@staticmethod
1921+
def load_from_thrustcurve_api(name: str, **kwargs):
1922+
"""
1923+
Creates a Motor instance by downloading a .eng file from the ThrustCurve API
1924+
based on the given motor name.
1925+
1926+
Parameters
1927+
----------
1928+
name : str
1929+
The motor name according to the API (e.g., "Cesaroni_M1670").
1930+
**kwargs :
1931+
Additional arguments passed to the Motor constructor, such as dry_mass, nozzle_radius, etc.
1932+
1933+
Returns
1934+
-------
1935+
instance : cls
1936+
A new Motor instance initialized using the downloaded .eng file.
1937+
"""
1938+
1939+
base_url = "https://www.thrustcurve.org/api/v1"
1940+
1941+
# Step 1. Search motor
1942+
response = requests.get(f"{base_url}/search.json", params={"commonName": name})
1943+
response.raise_for_status()
1944+
data = response.json()
1945+
1946+
if not data.get("results"):
1947+
print("No motor found.")
1948+
return None
1949+
1950+
motor = data["results"][0]
1951+
motor_id = motor["motorId"]
1952+
designation = motor["designation"].replace("/", "-")
1953+
print(f"Motor found: {designation} ({motor['manufacturer']})")
1954+
1955+
# Step 2. Download the .eng file
1956+
dl_response = requests.get(
1957+
f"{base_url}/download.json",
1958+
params={"motorIds": motor_id, "format": "RASP", "data": "file"},
1959+
)
1960+
dl_response.raise_for_status()
1961+
data = dl_response.json()
1962+
1963+
data_base64 = data["results"][0]["data"]
1964+
data_bytes = base64.b64decode(data_base64)
1965+
1966+
# Step 3. Create the motor from the .eng file
1967+
1968+
with tempfile.NamedTemporaryFile(suffix=".eng", delete=True) as tmp_file:
1969+
tmp_file.write(data_bytes)
1970+
tmp_file.flush()
1971+
1972+
motor = GenericMotor.load_from_eng_file(tmp_file.name, **kwargs)
1973+
1974+
return motor
1975+
19171976
def all_info(self):
19181977
"""Prints out all data and graphs available about the Motor."""
19191978
# Print motor details

tests/unit/motors/test_genericmotor.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,50 @@ def test_load_from_rse_file(generic_motor):
211211
assert thrust_curve[0][1] == 0.0 # First thrust point
212212
assert thrust_curve[-1][0] == 2.2 # Last point of time
213213
assert thrust_curve[-1][1] == 0.0 # Last thrust point
214+
215+
216+
def test_load_from_thrustcurve_api(generic_motor):
217+
"""Tests the GenericMotor.load_from_thrustcurve_api method.
218+
219+
Parameters
220+
----------
221+
generic_motor : rocketpy.GenericMotor
222+
The GenericMotor object to be used in the tests.
223+
"""
224+
# using cesaroni data as example
225+
burn_time = (0, 3.9)
226+
dry_mass = 5.231 - 3.101 # 2.130 kg
227+
propellant_initial_mass = 3.101
228+
chamber_radius = 75 / 1000
229+
chamber_height = 757 / 1000
230+
nozzle_radius = chamber_radius * 0.85 # 85% of chamber radius
231+
232+
# Parameters from manual testing using the SolidMotor class as a reference
233+
average_thrust = 1545.218
234+
total_impulse = 6026.350
235+
max_thrust = 2200.0
236+
exhaust_velocity = 1943.357
237+
238+
# creating motor from .eng file
239+
generic_motor = generic_motor.load_from_thrustcurve_api("M1670")
240+
241+
# testing relevant parameters
242+
assert generic_motor.burn_time == burn_time
243+
assert generic_motor.dry_mass == dry_mass
244+
assert generic_motor.propellant_initial_mass == propellant_initial_mass
245+
assert generic_motor.chamber_radius == chamber_radius
246+
assert generic_motor.chamber_height == chamber_height
247+
assert generic_motor.chamber_position == 0
248+
assert generic_motor.average_thrust == pytest.approx(average_thrust)
249+
assert generic_motor.total_impulse == pytest.approx(total_impulse)
250+
assert generic_motor.exhaust_velocity.average(*burn_time) == pytest.approx(
251+
exhaust_velocity
252+
)
253+
assert generic_motor.max_thrust == pytest.approx(max_thrust)
254+
assert generic_motor.nozzle_radius == pytest.approx(nozzle_radius)
255+
256+
# testing thrust curve
257+
_, _, points = Motor.import_eng("data/motors/cesaroni/Cesaroni_M1670.eng")
258+
assert generic_motor.thrust.y_array == pytest.approx(
259+
Function(points, "Time (s)", "Thrust (N)", "linear", "zero").y_array
260+
)

0 commit comments

Comments
 (0)