-
-
Notifications
You must be signed in to change notification settings - Fork 211
Enh/motor thrustcurve api #870
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
05ab711
da39fcb
9fdc704
d123b47
d6c5dee
142eaf8
361ffab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -106,3 +106,83 @@ note that the user can still provide the parameters manually if needed. | |||||||||||||||||||||||||||||
| The ``load_from_eng_file`` method is a very useful tool for simulating motors \ | ||||||||||||||||||||||||||||||
| when the user does not have all the information required to build a ``SolidMotor`` yet. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| The ``load_from_thrustcurve_api`` method | ||||||||||||||||||||||||||||||
| --------------------------------------- | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| The ``GenericMotor`` class provides a convenience loader that downloads a temporary | ||||||||||||||||||||||||||||||
| `.eng` file from the ThrustCurve.org public API and builds a ``GenericMotor`` | ||||||||||||||||||||||||||||||
| instance from it. This is useful when you know a motor designation (for example | ||||||||||||||||||||||||||||||
| ``"M1670"``) but do not want to manually download and | ||||||||||||||||||||||||||||||
| save the `.eng` file. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| .. note:: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| This method performs network requests to the ThrustCurve API. Use it only | ||||||||||||||||||||||||||||||
| when you have network access. For automated testing or reproducible runs, | ||||||||||||||||||||||||||||||
| prefer using local `.eng` files. | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| prefer using local `.eng` files. | |
| prefer using local `.eng` files. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| prefer using local `.eng` files. | |
| Signature | |
| prefer using local `.eng` files. | |
| Signature |
Copilot
AI
Nov 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after the closing parenthesis and before "Only".
| ``"M1670"``).Only shorthand names are accepted (e.g. ``"M1670"``, not | |
| ``"M1670"``). Only shorthand names are accepted (e.g. ``"M1670"``, not |
Copilot
AI
Nov 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation states "Only shorthand names are accepted (e.g. \"M1670\", not \"Cesaroni M1670\")", but the code docstring at line 1929-1931 says "Both manufacturer-prefixed and shorthand names are commonly used". These statements are contradictory. Based on the code implementation using the "commonName" parameter, both formats should work, so this documentation is inaccurate.
| Motor name to search on ThrustCurve (example: | |
| ``"M1670"``).Only shorthand names are accepted (e.g. ``"M1670"``, not | |
| ``"Cesaroni M1670"``). | |
| when multiple matches occur the first result returned by the API is used. | |
| Motor name to search on ThrustCurve (examples: | |
| ``"M1670"`` or ``"Cesaroni M1670"``). Both shorthand and manufacturer-prefixed | |
| names are accepted. When multiple matches occur, the first result returned by | |
| the API is used. |
Copilot
AI
Nov 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first letter of "when" should be capitalized as "When" since it starts a new sentence. Also missing space after the closing parenthesis.
| ``"M1670"``).Only shorthand names are accepted (e.g. ``"M1670"``, not | |
| ``"Cesaroni M1670"``). | |
| when multiple matches occur the first result returned by the API is used. | |
| ``"M1670"``). Only shorthand names are accepted (e.g. ``"M1670"``, not | |
| ``"Cesaroni M1670"``). | |
| When multiple matches occur the first result returned by the API is used. |
Copilot
AI
Nov 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The requests.exceptions.RequestException line is incomplete - it's missing a description of when this exception is raised. It should include text similar to what's in the code docstring: "If a network or HTTP error occurs during the API call."
| requests.exceptions.RequestException | |
| requests.exceptions.RequestException | |
| If a network or HTTP error occurs during the API call to ThrustCurve. |
Copilot
AI
Nov 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The phrase "If no results are returned a :class:ValueError is raised" is missing a comma after "returned". It should read: "If no results are returned, a :class:ValueError is raised."
| If no results are returned a :class:`ValueError` is raised. | |
| If no results are returned, a :class:`ValueError` is raised. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true
Copilot
AI
Nov 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The phrase "If a motor is found the method requests" is missing a comma after "found". It should read: "If a motor is found, the method requests..."
| - If a motor is found the method requests the .eng file in RASP format, decodes | |
| - If a motor is found, the method requests the .eng file in RASP format, decodes |
Copilot
AI
Nov 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The phrase "it and temporarily writes it to disk; a GenericMotor is then constructed" uses a semicolon incorrectly. Consider using a period or comma instead: "it and temporarily writes it to disk. A GenericMotor is then constructed..." or "it and temporarily writes it to disk, and a GenericMotor is then constructed..."
| it and temporarily writes it to disk; a ``GenericMotor`` is then constructed | |
| it and temporarily writes it to disk. A ``GenericMotor`` is then constructed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true
Copilot
AI
Nov 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "Testing advice" section is incomplete. It starts with "- pytest's caplog or capfd to assert on log/warning output." which is a sentence fragment. This should be a complete sentence, such as: "- Use pytest's caplog or capfd to assert on log/warning output."
| - ``pytest``'s ``caplog`` or ``capfd`` to assert on log/warning output. | |
| - Use ``pytest``'s ``caplog`` or ``capfd`` to assert on log/warning output. |
| 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 | ||||||
| from os import path, remove | ||||||
|
|
||||||
| import numpy as np | ||||||
| import requests | ||||||
|
|
||||||
| from ..mathutils.function import Function, funcify_method | ||||||
| from ..plots.motor_plots import _MotorPlots | ||||||
|
|
@@ -1914,6 +1917,121 @@ def load_from_rse_file( | |||||
| coordinate_system_orientation=coordinate_system_orientation, | ||||||
| ) | ||||||
|
|
||||||
| @staticmethod | ||||||
| def call_thrustcurve_api(name: str): | ||||||
|
Comment on lines
+1920
to
+1921
|
||||||
| """ | ||||||
| Download 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" or "M1670"). | ||||||
| Both manufacturer-prefixed and shorthand names are commonly used; if multiple | ||||||
| motors match the search, the first result is used. | ||||||
|
|
||||||
| Returns | ||||||
| ------- | ||||||
| data_base64 : String | ||||||
|
||||||
| data_base64 : String | |
| data_base64 : str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true
Marchma0 marked this conversation as resolved.
Show resolved
Hide resolved
Marchma0 marked this conversation as resolved.
Show resolved
Hide resolved
Gui-FernandesBR marked this conversation as resolved.
Show resolved
Hide resolved
Gui-FernandesBR marked this conversation as resolved.
Show resolved
Hide resolved
Marchma0 marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,8 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import base64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import scipy.integrate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from rocketpy import Function, Motor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -211,3 +214,114 @@ 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class MockResponse: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Mocked response for requests.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def __init__(self, json_data): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._json_data = json_data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def json(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return self._json_data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def raise_for_status(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _mock_get(search_results=None, download_results=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Return a mock_get function with predefined search/download results.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _get(url, **_kwargs): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if "search.json" in url: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return MockResponse(search_results or {"results": []}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if "download.json" in url: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return MockResponse(download_results or {"results": []}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise RuntimeError(f"Unexpected URL: {url}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _get | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def assert_motor_specs(motor): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| burn_time = (0, 3.9) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dry_mass = 2.130 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| propellant_initial_mass = 3.101 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chamber_radius = 75 / 1000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chamber_height = 757 / 1000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nozzle_radius = chamber_radius * 0.85 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| average_thrust = 1545.218 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| total_impulse = 6026.350 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| max_thrust = 2200.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exhaust_velocity = 1943.357 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.burn_time == burn_time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.dry_mass == dry_mass | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.propellant_initial_mass == propellant_initial_mass | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.chamber_radius == chamber_radius | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.chamber_height == chamber_height | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.chamber_position == 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.average_thrust == pytest.approx(average_thrust) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.total_impulse == pytest.approx(total_impulse) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.exhaust_velocity.average(*burn_time) == pytest.approx(exhaust_velocity) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.max_thrust == pytest.approx(max_thrust) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert motor.nozzle_radius == pytest.approx(nozzle_radius) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+245
to
+269
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def assert_motor_specs(motor): | |
| burn_time = (0, 3.9) | |
| dry_mass = 2.130 | |
| propellant_initial_mass = 3.101 | |
| chamber_radius = 75 / 1000 | |
| chamber_height = 757 / 1000 | |
| nozzle_radius = chamber_radius * 0.85 | |
| average_thrust = 1545.218 | |
| total_impulse = 6026.350 | |
| max_thrust = 2200.0 | |
| exhaust_velocity = 1943.357 | |
| assert motor.burn_time == burn_time | |
| assert motor.dry_mass == dry_mass | |
| assert motor.propellant_initial_mass == propellant_initial_mass | |
| assert motor.chamber_radius == chamber_radius | |
| assert motor.chamber_height == chamber_height | |
| assert motor.chamber_position == 0 | |
| assert motor.average_thrust == pytest.approx(average_thrust) | |
| assert motor.total_impulse == pytest.approx(total_impulse) | |
| assert motor.exhaust_velocity.average(*burn_time) == pytest.approx(exhaust_velocity) | |
| assert motor.max_thrust == pytest.approx(max_thrust) | |
| assert motor.nozzle_radius == pytest.approx(nozzle_radius) | |
| # Module-level constant for expected motor specs | |
| EXPECTED_MOTOR_SPECS = { | |
| "burn_time": (0, 3.9), | |
| "dry_mass": 2.130, | |
| "propellant_initial_mass": 3.101, | |
| "chamber_radius": 75 / 1000, | |
| "chamber_height": 757 / 1000, | |
| "nozzle_radius": (75 / 1000) * 0.85, | |
| "average_thrust": 1545.218, | |
| "total_impulse": 6026.350, | |
| "max_thrust": 2200.0, | |
| "exhaust_velocity": 1943.357, | |
| "chamber_position": 0, | |
| } | |
| def assert_motor_specs(motor): | |
| specs = EXPECTED_MOTOR_SPECS | |
| assert motor.burn_time == specs["burn_time"] | |
| assert motor.dry_mass == specs["dry_mass"] | |
| assert motor.propellant_initial_mass == specs["propellant_initial_mass"] | |
| assert motor.chamber_radius == specs["chamber_radius"] | |
| assert motor.chamber_height == specs["chamber_height"] | |
| assert motor.chamber_position == specs["chamber_position"] | |
| assert motor.average_thrust == pytest.approx(specs["average_thrust"]) | |
| assert motor.total_impulse == pytest.approx(specs["total_impulse"]) | |
| assert motor.exhaust_velocity.average(*specs["burn_time"]) == pytest.approx(specs["exhaust_velocity"]) | |
| assert motor.max_thrust == pytest.approx(specs["max_thrust"]) | |
| assert motor.nozzle_radius == pytest.approx(specs["nozzle_radius"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.