Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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