-
-
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?
Enh/motor thrustcurve api #870
Conversation
|
I really like this implementation!! |
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.
Pull Request Overview
This PR adds a new method load_from_thrustcurve_api to the GenericMotor class that allows users to download motor data directly from the ThrustCurve.org API by providing a motor name. This feature simplifies motor initialization by eliminating the need to manually download .eng files.
- Adds API integration with ThrustCurve.org to download motor data
- Implements a new static method that searches for motors and downloads their
.engfiles - Includes comprehensive test coverage for the new functionality
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| rocketpy/motors/motor.py | Adds new static method load_from_thrustcurve_api with required imports (base64, tempfile, requests) to enable downloading and loading motor data from ThrustCurve API |
| tests/unit/motors/test_genericmotor.py | Adds test case to verify the new API loading functionality works correctly with Cesaroni M1670 motor data |
|
I see 2 major problems: 1 - Documentation: we should add a description of how to use the new feature. |
ff57272 to
2770ba2
Compare
…with clean imports
2770ba2 to
da39fcb
Compare
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.
Pull Request Overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #870 +/- ##
===========================================
+ Coverage 80.27% 80.31% +0.04%
===========================================
Files 104 104
Lines 12769 12809 +40
===========================================
+ Hits 10250 10288 +38
- Misses 2519 2521 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…urve_api with exception testing
|
@Gui-FernandesBR Thank you for going over the code. I just updated the latest commit to run make format for import/order/style cleanup and added tests for exception handling in load_from_thrustcurve_api. |
… genericmotors.rst file
|
Hi all, We’ve updated the PR based on your feedback. Could you please check if it looks good now or if anything else needs improvement ? |
@Marchma0 can you check why linters are not passing? Also, if you could please mark previous comments as "resolved", that would make our review easier |
|
We updated the code, i think we resolved pylint errors. |
|
Do you think there is something else to modify ? @Gui-FernandesBR |
29a7aaa to
142eaf8
Compare
|
Sorry for the last commit, it should be good now. Can you check @Gui-FernandesBR please |
| when the user does not have all the information required to build a ``SolidMotor`` yet. | ||
|
|
||
| The ``load_from_thrustcurve_api`` method | ||
| --------------------------------------- |
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 |
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 |
Gui-FernandesBR
left a comment
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.
Very good implementation.
Please update the CHANGELOG so we can proceed with the merge.
For future developments... I don't think the file should really be temporary...
We should save the downloaded file in a cache folder so we reuse it in future calls instead of downloading through the API again.
Would you like to open a new PR implementing that?
I know OSMnx has a very good cache system that could somehow inspire this implementation: https://github.com/gboeing/osmnx
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.
Pull Request Overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.
| --------------- | ||
| - The method first performs a search on ThrustCurve using the provided name. | ||
| If no results are returned a :class:`ValueError` is raised. | ||
| - 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 "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 |
| @staticmethod | ||
| def call_thrustcurve_api(name: str): |
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 call_thrustcurve_api method is marked as @staticmethod but it's only called from load_from_thrustcurve_api at line 2013 using GenericMotor.call_thrustcurve_api(name). This method could be marked as a private method (e.g., _call_thrustcurve_api) since it's an internal implementation detail not intended for public use. The docstring doesn't indicate it's part of the public API, and the method name doesn't follow common naming patterns for public utility methods.
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.
I agree
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.
I also agree, I would not leave this method as a public member of the class. A simple _ should fix it.
| Behavior notes | ||
| --------------- | ||
| - The method first performs a search on ThrustCurve using the provided name. | ||
| If no results are returned a :class:`ValueError` is raised. |
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
| - The method first performs a search on ThrustCurve using the provided name. | ||
| If no results are returned a :class:`ValueError` is raised. | ||
| - If a motor is found the method requests the .eng file in RASP format, decodes | ||
| it and temporarily writes it to disk; a ``GenericMotor`` is then constructed |
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
| 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) | ||
|
|
||
|
|
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 assert_motor_specs function duplicates the same test values found in the test_load_from_eng_file function (lines 148-159). This creates maintenance burden - if these expected values need to be updated, they must be changed in two places. Consider extracting these as module-level constants or creating a fixture that can be reused by both functions.
| 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
|
|
||
| Testing advice | ||
| --------------- | ||
| - ``pytest``'s ``caplog`` or ``capfd`` to assert on log/warning output. |
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. |
| ---------- | ||
| name : str | ||
| Motor name to search on ThrustCurve (example: | ||
| ``"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.
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 |
| ``"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 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. |
| ValueError | ||
| If the API search returns no motor, or if the download endpoint returns no | ||
| .eng file or empty/invalid data. | ||
| requests.exceptions.RequestException |
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. |
|
|
||
| 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. |
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 blank line before the "Signature" subsection heading. According to reStructuredText conventions, there should be a blank line between the end of a note/admonition block and the next heading.
| prefer using local `.eng` files. | |
| prefer using local `.eng` files. |
|
I believe we are almost done. I will approave and merge the PR once you solve all the remainig comments and make sure the CHANGELOG is up to date. Once again ty for the brillant implementation |
Pull request type
Checklist
black rocketpy/ tests/) has passed locallypytest tests -m slow --runslow) have passed locallyCurrent behavior
#661
New behavior
We added the function to load a motor from the thrustcruve API and we tested it.
Breaking change