Skip to content

Commit 52bfc54

Browse files
authored
Merge branch 'develop' into copilot/enhance-drag-curve-functionality
2 parents 8572b1e + 8c732d4 commit 52bfc54

File tree

13 files changed

+570
-61
lines changed

13 files changed

+570
-61
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ Attention: The newest changes should be on top -->
3232

3333
### Added
3434

35+
- ENH: Enable only radial burning [#815](https://github.com/RocketPy-Team/RocketPy/pull/815)
36+
- ENH: Add thrustcurve api integration to retrieve motor eng data [#870](https://github.com/RocketPy-Team/RocketPy/pull/870)
37+
- ENH: Custom Exception errors and messages [#285](https://github.com/RocketPy-Team/RocketPy/issues/285)
38+
3539
### Changed
3640

3741
### Fixed
@@ -73,6 +77,7 @@ Attention: The newest changes should be on top -->
7377
## [v1.10.0] - 2025-05-16
7478

7579
### Added
80+
7681
- ENH: Support for ND arithmetic in Function class. [#810] (https://github.com/RocketPy-Team/RocketPy/pull/810)
7782
- ENH: allow users to provide custom samplers [#803](https://github.com/RocketPy-Team/RocketPy/pull/803)
7883
- ENH: Implement Multivariate Rejection Sampling (MRS) [#738] (https://github.com/RocketPy-Team/RocketPy/pull/738)

docs/user/motors/genericmotor.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,30 @@ note that the user can still provide the parameters manually if needed.
106106
The ``load_from_eng_file`` method is a very useful tool for simulating motors \
107107
when the user does not have all the information required to build a ``SolidMotor`` yet.
108108

109+
The ``load_from_thrustcurve_api`` method
110+
----------------------------------------
111+
112+
The ``GenericMotor`` class provides a convenience loader that downloads a temporary
113+
`.eng` file from the ThrustCurve.org public API and builds a ``GenericMotor``
114+
instance from it. This is useful when you know a motor designation (for example
115+
``"M1670"``) but do not want to manually download and
116+
save the `.eng` file.
117+
118+
.. note::
119+
120+
This method performs network requests to the ThrustCurve API. Use it only
121+
when you have network access. For automated testing or reproducible runs,
122+
prefer using local `.eng` files.
123+
124+
Example
125+
-------
126+
127+
.. jupyter-execute::
128+
129+
from rocketpy.motors import GenericMotor
130+
131+
# Build a motor by name (requires network access)
132+
motor = GenericMotor.load_from_thrustcurve_api("M1670")
133+
134+
# Use the motor as usual
135+
motor.info()

rocketpy/motors/hybrid_motor.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,12 @@ class HybridMotor(Motor):
193193
HybridMotor.reference_pressure : int, float
194194
Atmospheric pressure in Pa at which the thrust data was recorded.
195195
It will allow to obtain the net thrust in the Flight class.
196+
SolidMotor.only_radial_burn : bool
197+
If True, grain regression is restricted to radial burn only (inner radius growth).
198+
Grain length remains constant throughout the burn. Default is True.
196199
"""
197200

201+
# pylint: disable=too-many-arguments
198202
def __init__( # pylint: disable=too-many-arguments
199203
self,
200204
thrust_source,
@@ -216,6 +220,7 @@ def __init__( # pylint: disable=too-many-arguments
216220
interpolation_method="linear",
217221
coordinate_system_orientation="nozzle_to_combustion_chamber",
218222
reference_pressure=None,
223+
only_radial_burn=True,
219224
):
220225
"""Initialize Motor class, process thrust curve and geometrical
221226
parameters and store results.
@@ -313,6 +318,11 @@ class Function. Thrust units are Newtons.
313318
"nozzle_to_combustion_chamber".
314319
reference_pressure : int, float, optional
315320
Atmospheric pressure in Pa at which the thrust data was recorded.
321+
only_radial_burn : boolean, optional
322+
If True, inhibits the grain from burning axially, only computing
323+
radial burn. If False, allows the grain to also burn
324+
axially. May be useful for axially inhibited grains or hybrid motors.
325+
Default is False.
316326
317327
Returns
318328
-------
@@ -364,6 +374,7 @@ class Function. Thrust units are Newtons.
364374
interpolation_method,
365375
coordinate_system_orientation,
366376
reference_pressure,
377+
only_radial_burn,
367378
)
368379

369380
self.positioned_tanks = self.liquid.positioned_tanks

rocketpy/motors/motor.py

Lines changed: 119 additions & 1 deletion
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
6-
from os import path
8+
from os import path, remove
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,121 @@ def load_from_rse_file(
19141917
coordinate_system_orientation=coordinate_system_orientation,
19151918
)
19161919

1920+
@staticmethod
1921+
def _call_thrustcurve_api(name: str):
1922+
"""
1923+
Download 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" or "M1670").
1930+
Both manufacturer-prefixed and shorthand names are commonly used; if multiple
1931+
motors match the search, the first result is used.
1932+
1933+
Returns
1934+
-------
1935+
data_base64 : str
1936+
The .eng file of the motor in base64
1937+
1938+
Raises
1939+
------
1940+
ValueError
1941+
If no motor is found or if the downloaded .eng data is missing.
1942+
requests.exceptions.RequestException
1943+
If a network or HTTP error occurs during the API call.
1944+
"""
1945+
base_url = "https://www.thrustcurve.org/api/v1"
1946+
1947+
# Step 1. Search motor
1948+
response = requests.get(f"{base_url}/search.json", params={"commonName": name})
1949+
response.raise_for_status()
1950+
data = response.json()
1951+
1952+
if not data.get("results"):
1953+
raise ValueError(
1954+
f"No motor found for name '{name}'. "
1955+
"Please verify the motor name format (e.g., 'Cesaroni_M1670' or 'M1670') and try again."
1956+
)
1957+
1958+
motor_info = data["results"][0]
1959+
motor_id = motor_info.get("motorId")
1960+
# NOTE: commented bc we don't use it, but keeping for possible future use
1961+
# designation = motor_info.get("designation", "").replace("/", "-")
1962+
# manufacturer = motor_info.get("manufacturer", "")
1963+
1964+
# Step 2. Download the .eng file
1965+
dl_response = requests.get(
1966+
f"{base_url}/download.json",
1967+
params={"motorIds": motor_id, "format": "RASP", "data": "file"},
1968+
)
1969+
dl_response.raise_for_status()
1970+
dl_data = dl_response.json()
1971+
1972+
if not dl_data.get("results"):
1973+
raise ValueError(
1974+
f"No .eng file found for motor '{name}' in the ThrustCurve API."
1975+
)
1976+
1977+
data_base64 = dl_data["results"][0].get("data")
1978+
if not data_base64:
1979+
raise ValueError(
1980+
f"Downloaded .eng data for motor '{name}' is empty or invalid."
1981+
)
1982+
return data_base64
1983+
1984+
@staticmethod
1985+
def load_from_thrustcurve_api(name: str, **kwargs):
1986+
"""
1987+
Creates a Motor instance by downloading a .eng file from the ThrustCurve API
1988+
based on the given motor name.
1989+
1990+
Parameters
1991+
----------
1992+
name : str
1993+
The motor name according to the API (e.g., "Cesaroni_M1670" or "M1670").
1994+
Both manufacturer-prefixed and shorthand names are commonly used; if multiple
1995+
motors match the search, the first result is used.
1996+
**kwargs :
1997+
Additional arguments passed to the Motor constructor or loader, such as
1998+
dry_mass, nozzle_radius, etc.
1999+
2000+
Returns
2001+
-------
2002+
instance : GenericMotor
2003+
A new GenericMotor instance initialized using the downloaded .eng file.
2004+
2005+
Raises
2006+
------
2007+
ValueError
2008+
If no motor is found or if the downloaded .eng data is missing.
2009+
requests.exceptions.RequestException
2010+
If a network or HTTP error occurs during the API call.
2011+
"""
2012+
2013+
data_base64 = GenericMotor._call_thrustcurve_api(name)
2014+
data_bytes = base64.b64decode(data_base64)
2015+
2016+
# Step 3. Create the motor from the .eng file
2017+
tmp_path = None
2018+
try:
2019+
# create a temporary file that persists until we explicitly remove it
2020+
with tempfile.NamedTemporaryFile(suffix=".eng", delete=False) as tmp_file:
2021+
tmp_file.write(data_bytes)
2022+
tmp_file.flush()
2023+
tmp_path = tmp_file.name
2024+
2025+
return GenericMotor.load_from_eng_file(tmp_path, **kwargs)
2026+
finally:
2027+
# Ensuring the temporary file is removed
2028+
if tmp_path and path.exists(tmp_path):
2029+
try:
2030+
remove(tmp_path)
2031+
except OSError:
2032+
# If cleanup fails, don't raise: we don't want to mask prior exceptions.
2033+
pass
2034+
19172035
def all_info(self):
19182036
"""Prints out all data and graphs available about the Motor."""
19192037
# Print motor details

0 commit comments

Comments
 (0)