Skip to content
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pymongo>=4.15
jsonpickle
gunicorn
uvicorn
git+https://github.com/RocketPy-Team/RocketPy.git@develop
rocketpy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

latest rocketpy pypi version release notes

💡 Result:

Latest RocketPy PyPI version (as of 2026-04-23): 1.12.0, released 2026-03-20. Release notes (RocketPy v1.12.0): - Added: acceptance tests for 3DOF flight simulation based on Bella Lui rocket; background map auto-download functionality to Monte Carlo plots; net thrust addition to 3DOF in flight class; 3DOF lateral motion improvement; multi-dimensional drag coefficient support (Cd as function of M, Re, α); save functionality to _MonteCarloPlots.all; animations for motor propellant mass and tank fluid volumes; rail button bending moments calculation in Flight class; bootstrapping for confidence interval estimation; built-in FlightComparator for validating simulations against external data; persistent caching for ThrustCurve API; axial_acceleration attribute to Flight class; custom warning when no motor or aerosurface; thrustcurve API integration to retrieve motor engine data; compatibility with MERRA-2 atmosphere reanalysis files; enable only radial burning. - Changed: improve multi-variable drag compatibility and regular-grid handling (with related tests/docs); replace if/elif chains with match statement; refactor Flight class to improve time node handling and sensor/controllers. - Fixed: restore Rocket.power_off_drag and Rocket.power_on_drag as Function objects while preserving raw inputs; add explicit timeouts to ThrustCurve API requests; fix hard-coded radius value for parachute added mass calculation; fix documentation build; energy_data plot not working for 3DOF sims; CSV column header spacing in FlightDataExporter; incorrect iteration count in parallel Monte Carlo simulation; missing titles in roll parameter plots for fin sets; duplicate _controllers in Flight.TimeNodes.merge; incorrect Jacobian in only_radial_burn branch of SolidMotor.evaluate_geometry (also plus repeated timeout fix mention). How to check on your machine: - pip install --upgrade rocketpy - python -c "import pkgutil,rocketpy; import importlib.metadata as m; print(m.version('rocketpy'))"

Citations:


🏁 Script executed:

# First, check the current state of requirements.txt
cat requirements.txt

Repository: RocketPy-Team/Infinity-API

Length of output: 332


🏁 Script executed:

# Search for the claimed private API usages in service files
rg "_generate_combustion_chamber|_generate_grains|_generate_positioned_tanks|_generate_nozzle|_generate_motor_region|_csys|shape_vec|sort_by_position" src/services/

Repository: RocketPy-Team/Infinity-API

Length of output: 3063


🏁 Script executed:

# Check if there are any version pins or comments in requirements.txt or setup files
fd -e txt -e in | xargs grep -l "rocketpy" 2>/dev/null

Repository: RocketPy-Team/Infinity-API

Length of output: 86


🏁 Script executed:

# Check git history of requirements.txt to see if there was a previous pin
git log -p requirements.txt | head -100

Repository: RocketPy-Team/Infinity-API

Length of output: 52


🏁 Script executed:

# Check for version info in CI/deployment configs
fd -e yml -e yaml -e toml | xargs grep -l "rocketpy" 2>/dev/null

Repository: RocketPy-Team/Infinity-API

Length of output: 84


🏁 Script executed:

# Look for any comments or docs mentioning rocketpy version
rg "rocketpy.*version|version.*rocketpy" -i

Repository: RocketPy-Team/Infinity-API

Length of output: 52


🏁 Script executed:

# Check pyproject.toml for rocketpy version specification
cat pyproject.toml

Repository: RocketPy-Team/Infinity-API

Length of output: 1773


🏁 Script executed:

# Check for CI workflows that might reveal tested versions
fd -path ".github/workflows/*" -o -path ".gitlab-ci.yml" | xargs cat 2>/dev/null

Repository: RocketPy-Team/Infinity-API

Length of output: 238


🏁 Script executed:

# Check git log with short format to see recent changes
git log --oneline | head -20

Repository: RocketPy-Team/Infinity-API

Length of output: 127


🏁 Script executed:

# Check if there are tests that might indicate validated versions
find tests -name "*.py" -type f | head -5 | xargs grep -l "rocketpy\|_generate\|_csys" 2>/dev/null

Repository: RocketPy-Team/Infinity-API

Length of output: 176


🏁 Script executed:

# Look for any recent commits or PRs mentioning rocketpy version
git log --all --grep="rocketpy\|version\|pin" --oneline 2>/dev/null | head -20

Repository: RocketPy-Team/Infinity-API

Length of output: 1040


🏁 Script executed:

# Check if there's a lock file (poetry.lock, requirements.lock, etc.)
fd -e "lock" -o -e "frozen" | head -20

Repository: RocketPy-Team/Infinity-API

Length of output: 186


🏁 Script executed:

# Search for any validation or testing comments near the API usage
rg "validate|tested|version" src/services/motor.py src/services/rocket.py -B2 -A2

Repository: RocketPy-Team/Infinity-API

Length of output: 52


Pin rocketpy to a known-good version.

The service layer relies heavily on RocketPy private APIs (motor.plots._generate_combustion_chamber, _generate_grains, _generate_positioned_tanks, _generate_nozzle, _generate_motor_region, plus rocket._csys, surface.shape_vec, rocket.aerodynamic_surfaces.sort_by_position, motor._csys, etc. — see src/services/motor.py and src/services/rocket.py). Switching from a git-pinned develop to an unpinned rocketpy means any future PyPI release that refactors these internals will silently break /drawing-geometry (and potentially existing simulation flows) on the next environment rebuild.

Pin to the exact version you validated against, and bump deliberately.

🔧 Proposed fix
-rocketpy
+rocketpy==<validated version>  # pinned: service depends on private _MotorPlots/_RocketPlots helpers
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@requirements.txt` at line 12, Replace the unpinned "rocketpy" entry in
requirements.txt with an exact, validated version to prevent accidental
upgrades; pin to the specific release you tested (for example 0.x.y) so future
PyPI changes that alter private APIs used in src/services/motor.py and
src/services/rocket.py (e.g., motor.plots._generate_combustion_chamber,
_generate_grains, _generate_positioned_tanks, _generate_nozzle,
_generate_motor_region, rocket._csys, surface.shape_vec,
rocket.aerodynamic_surfaces.sort_by_position, motor._csys) won’t break the
drawing-geometry/ simulation flows, and update the project’s upgrade notes to
bump this pin deliberately when you validate a new release.

uptrace
opentelemetry.instrumentation.fastapi
opentelemetry.instrumentation.requests
Expand Down
26 changes: 25 additions & 1 deletion src/controllers/motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
ControllerBase,
controller_exception_handler,
)
from src.views.motor import MotorSimulation
from src.views.motor import MotorSimulation, MotorDrawingGeometryView
from src.models.motor import MotorModel
from src.services.motor import MotorService

Expand Down Expand Up @@ -57,3 +57,27 @@ async def get_motor_simulation(self, motor_id: str) -> MotorSimulation:
motor = await self.get_motor_by_id(motor_id)
motor_service = MotorService.from_motor_model(motor.motor)
return motor_service.get_motor_simulation()

@controller_exception_handler
async def get_motor_drawing_geometry(
self, motor_id: str
) -> MotorDrawingGeometryView:
"""
Build the motor-only drawing-geometry payload for a persisted motor.

Renders the motor at its own coordinate origin (motor_position=0,
parent_csys=1) so the playground can show a motor in isolation.

Args:
motor_id: str

Returns:
views.MotorDrawingGeometryView

Raises:
HTTP 404 Not Found: If the motor does not exist in the database.
HTTP 422: If the motor has no drawable geometry.
"""
motor = await self.get_motor_by_id(motor_id)
motor_service = MotorService.from_motor_model(motor.motor)
return motor_service.get_drawing_geometry()
27 changes: 26 additions & 1 deletion src/controllers/rocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
ControllerBase,
controller_exception_handler,
)
from src.views.rocket import RocketSimulation, RocketCreated
from src.views.rocket import (
RocketSimulation,
RocketCreated,
RocketDrawingGeometry,
)
from src.models.motor import MotorModel
from src.models.rocket import (
RocketModel,
Expand Down Expand Up @@ -75,6 +79,27 @@ async def get_rocketpy_rocket_binary(self, rocket_id: str) -> bytes:
rocket_service = RocketService.from_rocket_model(rocket.rocket)
return rocket_service.get_rocket_binary()

@controller_exception_handler
async def get_rocket_drawing_geometry(
self, rocket_id: str
) -> RocketDrawingGeometry:
"""
Build the drawing geometry payload for a persisted rocket.

Args:
rocket_id: str

Returns:
views.RocketDrawingGeometry

Raises:
HTTP 404 Not Found: If the rocket does not exist in the database.
HTTP 422: If the rocket has no aerodynamic surfaces to draw.
"""
rocket = await self.get_rocket_by_id(rocket_id)
rocket_service = RocketService.from_rocket_model(rocket.rocket)
return rocket_service.get_drawing_geometry()

@controller_exception_handler
async def get_rocket_simulation(
self,
Expand Down
16 changes: 16 additions & 0 deletions src/models/motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ def validate_motor_kind(self):
)
return self

@model_validator(mode='after')
def validate_dry_inertia_for_kind(self):
# RocketPy's SolidMotor/LiquidMotor/HybridMotor require dry_inertia with no default.
# Only GenericMotor accepts (0, 0, 0). Surface a clear error at the API boundary
# instead of letting RocketPy crash deep in construction.
if self.motor_kind != MotorKinds.GENERIC and self.dry_inertia == (
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this validator exists (for future reviewers)

RocketPy itself enforces non-zero dry_inertia for every non-Generic motor kind. Verified against the installed rocketpy package:

Motor class dry_inertia default
SolidMotor required (no default)
LiquidMotor required (no default)
HybridMotor required (no default)
GenericMotor (0, 0, 0)

dry_inertia is the motor's dry-mass inertia tensor (I_11, I_22, I_33) in kg·m² about center_of_dry_mass_position. Rocketpy consumes it directly in Motor.__init__:

# rocketpy/motors/motor.py
inertia = (*dry_inertia, 0, 0, 0) if len(dry_inertia) == 3 else dry_inertia
self.dry_I_11 = inertia[0]
self.dry_I_22 = inertia[1]
self.dry_I_33 = inertia[2]

These feed the 6-DOF flight integrator's angular equations of motion. Passing (0, 0, 0) for a Solid/Liquid/Hybrid motor doesn't just fail fast in rocketpy — historically it could silently produce a motor with zero rotational inertia, corrupting every downstream simulation (pitch/yaw response, static margin, stability).

Our MotorModel.dry_inertia defaults to (0, 0, 0) for schema convenience. This validator converts what would otherwise be either (a) a deep rocketpy construction crash or (b) a silent simulation-correctness bug into a clear 422 at the API boundary, with a motor-kind-specific error message telling the client exactly what to provide.

GenericMotor is rocketpy's "black-box thrust curve" motor, explicitly designed for the case where the caller doesn't have real inertia data — so it's the one kind where (0, 0, 0) is legitimate, and the validator allows it.

0,
0,
0,
):
raise ValueError(
f"dry_inertia is required for {self.motor_kind} motors "
f"and must be explicitly provided (cannot be (0, 0, 0))."
)
return self

@staticmethod
def UPDATED():
return
Expand Down
3 changes: 2 additions & 1 deletion src/models/sub/tanks.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class MotorTank(BaseModel):
liquid: TankFluids
flux_time: Tuple[float, float]
position: float
discretize: int
# discretize is optional in RocketPy's Tank classes (defaults to 100).
discretize: int = 100

# Level based tank parameters
liquid_height: Optional[float] = None
Expand Down
22 changes: 22 additions & 0 deletions src/routes/motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
MotorSimulation,
MotorCreated,
MotorRetrieved,
MotorDrawingGeometryView,
)
from src.models.motor import MotorModel
from src.dependencies import MotorControllerDep
Expand Down Expand Up @@ -138,3 +139,24 @@ async def get_motor_simulation(
"""
with tracer.start_as_current_span("get_motor_simulation"):
return await controller.get_motor_simulation(motor_id)


@router.get("/{motor_id}/drawing-geometry")
async def get_motor_drawing_geometry(
motor_id: str,
controller: MotorControllerDep,
) -> MotorDrawingGeometryView:
"""
Returns motor-only drawing geometry so a frontend can render the
motor in isolation. The payload mirrors what the motor portion of
`rocketpy.Rocket.draw()` would produce, but at the motor's own
coordinate origin rather than embedded inside a rocket.

Use `GET /rockets/{rocket_id}/drawing-geometry` instead when the
motor should be shown inside a complete rocket.

## Args
``` motor_id: Motor ID ```
"""
with tracer.start_as_current_span("get_motor_drawing_geometry"):
return await controller.get_motor_drawing_geometry(motor_id)
21 changes: 21 additions & 0 deletions src/routes/rocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
RocketSimulation,
RocketCreated,
RocketRetrieved,
RocketDrawingGeometry,
)
from src.models.rocket import (
RocketModel,
Expand Down Expand Up @@ -181,3 +182,23 @@ async def simulate_rocket(
"""
with tracer.start_as_current_span("get_rocket_simulation"):
return await controller.get_rocket_simulation(rocket_id)


@router.get("/{rocket_id}/drawing-geometry")
async def get_rocket_drawing_geometry(
rocket_id: str,
controller: RocketControllerDep,
) -> RocketDrawingGeometry:
"""
Returns structured drawing geometry for the rocket so that a frontend
can redraw exactly what rocketpy.Rocket.draw() would render.

Response contains shape coordinate arrays for each aerodynamic surface,
tube segments, motor polygons (nozzle, chamber, grains, tanks, outline),
rail-button positions, CG/CP at t=0, sensors, and overall drawing bounds.

## Args
``` rocket_id: Rocket ID ```
"""
with tracer.start_as_current_span("get_rocket_drawing_geometry"):
return await controller.get_rocket_drawing_geometry(rocket_id)
Loading
Loading