66from abc import ABC , abstractmethod
77from functools import cached_property
88from os import path , remove
9+ from pathlib import Path
910
1011import numpy as np
1112import requests
1516from ..prints .motor_prints import _MotorPrints
1617from ..tools import parallel_axis_theorem_from_com , tuple_handler
1718
18-
1919# pylint: disable=too-many-public-methods
20+ # ThrustCurve API cache
21+ CACHE_DIR = Path .home () / ".rocketpy_cache"
22+ CACHE_DIR .mkdir (exist_ok = True )
23+
24+
2025class Motor (ABC ):
2126 """Abstract class to specify characteristics and useful operations for
2227 motors. Cannot be instantiated.
@@ -1918,7 +1923,7 @@ def load_from_rse_file(
19181923 )
19191924
19201925 @staticmethod
1921- def _call_thrustcurve_api (name : str ):
1926+ def _call_thrustcurve_api (name : str , no_cache : bool = False ):
19221927 """
19231928 Download a .eng file from the ThrustCurve API
19241929 based on the given motor name.
@@ -1929,6 +1934,8 @@ def _call_thrustcurve_api(name: str):
19291934 The motor name according to the API (e.g., "Cesaroni_M1670" or "M1670").
19301935 Both manufacturer-prefixed and shorthand names are commonly used; if multiple
19311936 motors match the search, the first result is used.
1937+ no_cache : bool, optional
1938+ If True, forces a new API fetch even if the motor is cached.
19321939
19331940 Returns
19341941 -------
@@ -1941,9 +1948,21 @@ def _call_thrustcurve_api(name: str):
19411948 If no motor is found or if the downloaded .eng data is missing.
19421949 requests.exceptions.RequestException
19431950 If a network or HTTP error occurs during the API call.
1951+ Notes
1952+ -----
1953+ - The cache prevents multiple network requests for the same motor name across sessions.
1954+ - Cached files are stored in `~/.rocketpy_cache` and reused unless `no_cache=True`.
1955+ - Filenames are sanitized to avoid invalid characters.
19441956 """
1945- base_url = "https://www.thrustcurve.org/api/v1"
1957+ # File path in the cache
1958+ safe_name = re .sub (r"[^A-Za-z0-9_.-]" , "_" , name )
1959+ cache_file = CACHE_DIR / f"{ safe_name } .eng.b64"
19461960
1961+ # Use cached file if it exists and no_cache is False
1962+ if cache_file .exists () and not no_cache :
1963+ return cache_file .read_text ()
1964+
1965+ base_url = "https://www.thrustcurve.org/api/v1"
19471966 # Step 1. Search motor
19481967 response = requests .get (f"{ base_url } /search.json" , params = {"commonName" : name })
19491968 response .raise_for_status ()
@@ -1979,10 +1998,11 @@ def _call_thrustcurve_api(name: str):
19791998 raise ValueError (
19801999 f"Downloaded .eng data for motor '{ name } ' is empty or invalid."
19812000 )
2001+ cache_file .write_text (data_base64 )
19822002 return data_base64
19832003
19842004 @staticmethod
1985- def load_from_thrustcurve_api (name : str , ** kwargs ):
2005+ def load_from_thrustcurve_api (name : str , no_cache : bool = False , ** kwargs ):
19862006 """
19872007 Creates a Motor instance by downloading a .eng file from the ThrustCurve API
19882008 based on the given motor name.
@@ -2010,7 +2030,7 @@ def load_from_thrustcurve_api(name: str, **kwargs):
20102030 If a network or HTTP error occurs during the API call.
20112031 """
20122032
2013- data_base64 = GenericMotor ._call_thrustcurve_api (name )
2033+ data_base64 = GenericMotor ._call_thrustcurve_api (name , no_cache = no_cache )
20142034 data_bytes = base64 .b64decode (data_base64 )
20152035
20162036 # Step 3. Create the motor from the .eng file
0 commit comments