Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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