Skip to content

ENH: new dimensions route to get rocket geometry#71

Open
aasitvora99 wants to merge 10 commits intomasterfrom
enh/rocket-drawing-geometry
Open

ENH: new dimensions route to get rocket geometry#71
aasitvora99 wants to merge 10 commits intomasterfrom
enh/rocket-drawing-geometry

Conversation

@aasitvora99
Copy link
Copy Markdown
Member

@aasitvora99 aasitvora99 commented Apr 20, 2026

Implements https://github.com/RocketPy-Team/jarvis-ts/issues/51

Summary by CodeRabbit

  • New Features

    • Added /motors/{motor_id}/drawing-geometry and /rockets/{rocket_id}/drawing-geometry endpoints to retrieve detailed drawing geometry.
    • Motor tank discretization parameter is now optional (defaults to 100).
  • Bug Fixes

    • Added validation to prevent motor dry inertia from being zero for SOLID, LIQUID, or HYBRID motor types.
  • Chores

    • Updated RocketPy dependency from Git branch to standard PyPI package.

aasitvora99 and others added 2 commits April 20, 2026 12:40
Exposes structured drawing geometry that mirrors rocketpy.Rocket.draw(),
so clients can redraw a rocket using the same shape math rocketpy uses
without server-side rendering or duplicated geometry logic in the UI.

Response carries per-surface shape_x/shape_y arrays, body tube segments,
motor patch polygons (nozzle, chamber, grains, tanks, outline), rail
button positions, sensors, t=0 CG/CP, and drawing bounds. Coordinates
are already transformed into the draw frame rocketpy uses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

Adds drawing geometry endpoints for motors and rockets by introducing new view types, controller methods, and service logic to render shapes and bounds. Also pins RocketPy to PyPI, makes MotorTank.discretize optional, and enforces dry_inertia validation for solid/liquid/hybrid motors.

Changes

Cohort / File(s) Summary
Dependency Management
requirements.txt
Replaces Git-based RocketPy dependency with standard PyPI package requirement.
Motor Drawing Geometry
src/controllers/motor.py, src/routes/motor.py, src/services/motor.py
Adds get_motor_drawing_geometry endpoint that returns motor-only geometry with computed bounds; service converts RocketPy patches to coordinate lists and handles motor outline/nozzle rendering.
Rocket Drawing Geometry
src/controllers/rocket.py, src/routes/rocket.py, src/services/rocket.py
Adds get_rocket_drawing_geometry endpoint that constructs full rocket geometry including aerodynamic surfaces, tubes, rail buttons, sensors, and center-of-mass/CP; delegates motor geometry to motor service.
View & Drawing Models
src/views/drawing.py, src/views/motor.py, src/views/rocket.py
Introduces shared drawing.py module with Pydantic geometry types (NoseConeGeometry, FinsGeometry, MotorDrawingGeometry, DrawingBounds, etc.); adds MotorDrawingGeometryView and RocketDrawingGeometry API response types.
Model Validation & Defaults
src/models/motor.py, src/models/sub/tanks.py
Adds post-validation to enforce non-zero dry_inertia for solid/liquid/hybrid motors; makes MotorTank.discretize optional with default value 100.
Test Fixtures
tests/unit/test_routes/conftest.py
Updates stub_motor_dump fixture to use non-zero dry_inertia to satisfy new validation rules.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant MotorCtrl as Motor Controller
    participant MotorSvc as Motor Service
    participant RocketPy
    
    Client->>MotorCtrl: GET /motors/{motor_id}/drawing-geometry
    MotorCtrl->>MotorCtrl: Load motor by ID from DB
    MotorCtrl->>MotorSvc: Construct MotorService(motor_model)
    MotorCtrl->>MotorSvc: get_drawing_geometry()
    MotorSvc->>MotorSvc: build_drawing_geometry()
    MotorSvc->>RocketPy: Convert motor to RocketPy instance
    RocketPy-->>MotorSvc: Matplotlib polygon patches
    MotorSvc->>MotorSvc: Extract {x, y} coords from patches
    MotorSvc->>MotorSvc: Compute bounds (nozzle radius, outline)
    MotorSvc-->>MotorCtrl: MotorDrawingGeometryView (patches + bounds)
    MotorCtrl-->>Client: 200 OK {motor, bounds, ...}
Loading
sequenceDiagram
    participant Client
    participant RocketCtrl as Rocket Controller
    participant RocketSvc as Rocket Service
    participant MotorSvc as Motor Service
    participant RocketPy
    
    Client->>RocketCtrl: GET /rockets/{rocket_id}/drawing-geometry
    RocketCtrl->>RocketCtrl: Load rocket by ID from DB
    RocketCtrl->>RocketSvc: Construct RocketService(rocket_model)
    RocketCtrl->>RocketSvc: get_drawing_geometry()
    RocketSvc->>RocketPy: Convert rocket to RocketPy instance
    RocketPy-->>RocketSvc: Aerodynamic surfaces, tubes, components
    RocketSvc->>RocketSvc: Build nose cones, tails, fins geometry
    RocketSvc->>RocketSvc: Build tube segments and rail buttons
    RocketSvc->>MotorSvc: _build_motor_geometry() for motor section
    MotorSvc-->>RocketSvc: MotorDrawingGeometry (or None)
    RocketSvc->>RocketSvc: Compute overall bounds (all components)
    RocketSvc-->>RocketCtrl: RocketDrawingGeometry (all geom + bounds)
    RocketCtrl-->>Client: 200 OK {nose_cones, fins, tubes, motor, ...}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • phmbressan
  • GabrielBarberini

Poem

🐰✨ Drawing geometry with care so bright,
Motors spin and rockets take flight!
Patches morph to coordinates fine,
Bounds computed, shapes align—
A canvas of physics in digital delight! 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a new drawing geometry route for rockets, which is the primary feature implemented across the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enh/rocket-drawing-geometry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Align MotorModel and MotorTank with RocketPy's actual constructor
requirements so invalid motors are rejected at the API boundary with a
clear error rather than crashing deep inside RocketPy at simulate time.

- MotorModel.validate_dry_inertia_for_kind: SOLID / LIQUID / HYBRID
  motors in RocketPy require dry_inertia with no default. Only
  GenericMotor accepts (0, 0, 0). Reject the default tuple for every
  kind except GENERIC with a message the user can act on.
- MotorTank.discretize: change to Optional[int] = 100 to match the
  RocketPy Tank classes' default. Forms can now omit the field and still
  submit successfully.
- stub_motor_dump fixture: use dry_inertia=[0.1, 0.1, 0.1] so tests that
  override motor_kind to SOLID / LIQUID / HYBRID still pass the new
  validator without each having to add a dry_inertia override locally.
RocketPy's rocket.draw() does not draw a combustion chamber for
GenericMotor because _MotorPlots._generate_combustion_chamber reads
grain-only attributes (grain_initial_height, grain_outer_radius, etc.)
that GenericMotor lacks — it only emits a nozzle. Users who populate
chamber_radius / chamber_height / chamber_position then saw no chamber
in the jarvis playground.

Add a GenericMotor branch in RocketService._build_motor_geometry that
constructs an equivalent rectangular chamber patch from the chamber_*
fields. Vertex ordering mirrors _generate_combustion_chamber so the
patch flows through _generate_motor_region for outline assembly the
same way a SolidMotor chamber does.

Patch is emitted with role='chamber', flowing through the existing
drawingMotorSchema + GeometryRocket renderer without frontend changes.
@aasitvora99 aasitvora99 marked this pull request as ready for review April 22, 2026 17:55
@aasitvora99 aasitvora99 changed the title Enh/rocket drawing geometry ENH: new dimensions route to get rocket geometry Apr 22, 2026
Comment thread src/models/motor.py
# 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.

phmbressan
phmbressan previously approved these changes Apr 23, 2026
Add a standalone GET /motors/{id}/drawing-geometry that returns
exactly the motor patches rocketpy's Rocket.draw() would produce,
rendered at the motor's own coordinate origin rather than embedded
inside a rocket. Lets the jarvis playground show 'rocket + motor'
and 'motor only' as distinct views without duplicating render logic
client-side.

Refactor:
- Extracted motor-drawing code out of RocketService._build_motor_geometry
  into MotorService.build_drawing_geometry(motor_position, parent_csys)
  and a public MotorService.get_drawing_geometry() wrapper. One code
  path now serves both endpoints; RocketService.build_motor_geometry
  is a two-line delegate.
- Moved shared helpers (_polygon_xy, _rebuild_polygon,
  _build_generic_chamber_patch) from src/services/rocket.py to
  src/services/motor.py where they logically belong.
- Moved shared drawing view types (NoseConeGeometry, TailGeometry,
  FinOutline, FinsGeometry, TubeGeometry, MotorPatch,
  MotorDrawingGeometry, RailButtonsGeometry, SensorGeometry,
  DrawingBounds) from src/views/rocket.py to a new
  src/views/drawing.py so both views/rocket.py and views/motor.py
  can depend on them without a circular import. views/rocket.py
  re-exports them for backwards compatibility with callers that
  still import from the old location.

Surface:
- New src/views/motor.py::MotorDrawingGeometryView: thin response
  envelope with motor (MotorDrawingGeometry), bounds (DrawingBounds),
  coordinate_system_orientation. ser_json_exclude_none matches the
  rocket response convention.
- New MotorController.get_motor_drawing_geometry(motor_id) method
  returning the view. 422 when the motor has no drawable patches
  (EmptyMotor edge); 404 from the existing get_motor_by_id path.
- New route GET /motors/{motor_id}/drawing-geometry.

Verified end-to-end against real rocketpy for every motor kind:
- Generic: 3 patches (chamber, nozzle, outline)
- Solid:   9 patches (chamber, 7x grain, nozzle, outline)
- Liquid:  3 patches (tank, nozzle, outline)
- Hybrid:  6 patches (chamber, grain, tank, nozzle, outline)
Rocket-embedded drawing unchanged (regression-tested: rocket.motor
remains at rocket.motor_position; bounds, patch roles, and patch
counts identical to pre-refactor).

Full suite: 156/156 passing.
@aasitvora99 aasitvora99 dismissed stale reviews from phmbressan and GabrielBarberini via 7037785 April 23, 2026 18:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
src/services/rocket.py (1)

287-296: Broad except Exception — tighten if possible.

Ruff BLE001 flags these two blocks. The pragma comments acknowledge the intent, but catching AttributeError / TypeError / ValueError specifically would protect against swallowing bugs (e.g., a typo calling a nonexistent method returning a KeyboardInterrupt-like error) while still handling the "rocket not fully built" case. Low priority since the pragma already signals intent.

♻️ Suggested narrowing
-        try:
-            center_of_mass = float(rocket.center_of_mass(0))
-        except (
-            Exception
-        ):  # pragma: no cover - defensive; rocket may not be fully built
-            center_of_mass = None
-        try:
-            cp_position = float(rocket.cp_position(0))
-        except Exception:  # pragma: no cover
-            cp_position = None
+        try:
+            center_of_mass = float(rocket.center_of_mass(0))
+        except (AttributeError, TypeError, ValueError):  # pragma: no cover
+            center_of_mass = None
+        try:
+            cp_position = float(rocket.cp_position(0))
+        except (AttributeError, TypeError, ValueError):  # pragma: no cover
+            cp_position = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/rocket.py` around lines 287 - 296, The two broad except blocks
around rocket.center_of_mass(0) and rocket.cp_position(0) should be narrowed to
specific exceptions so we don't swallow unexpected errors; catch only
AttributeError, TypeError, and ValueError (or the subset that matches the
possible failure modes) for the calls to center_of_mass and cp_position in place
of "except Exception" and keep the existing fallback to set center_of_mass or
cp_position to None when those specific exceptions occur.
src/services/motor.py (2)

282-282: Line exceeds 79-character limit.

Likely to be rewrapped by Black/Ruff on next make format pass. Pre-wrap to avoid stylistic churn:

♻️ Proposed wrap
-        fallback_radius = float(getattr(self._motor, "nozzle_radius", 0.0) or 0.0)
+        fallback_radius = float(
+            getattr(self._motor, "nozzle_radius", 0.0) or 0.0
+        )

As per coding guidelines: "Target Python 3.12 and enforce 79-character line length using Black, Ruff, and Pylint".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/motor.py` at line 282, The line assigning fallback_radius is
over 79 characters; break it into a shorter, PEP8-compliant expression using the
same symbols (fallback_radius, self._motor, "nozzle_radius", getattr) so it
stays within the 79-char limit—for example, compute the attribute value into a
short temporary variable or use a parenthesized expression split across two
lines, then cast to float as before to preserve behavior.

327-396: Heavy coupling to RocketPy private plot APIs — worth documenting.

motor.plots._generate_combustion_chamber, _generate_grains, _generate_positioned_tanks, _generate_nozzle, and _generate_motor_region are all private (_-prefixed) RocketPy internals. Any rocketpy minor-version bump can silently break this endpoint. Since you've already pinned rocketpy and wrapped _generate_motor_region in a defensive try/except, the immediate risk is contained, but consider:

  • Adding a RocketPy version assertion at import-time (or module load) so incompatibilities surface loudly rather than as 500s at request time.
  • Filing an upstream issue asking for a public "geometry export" hook so this service can eventually shed the _plots dependency.

Not blocking — just flagging for future maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/motor.py` around lines 327 - 396, This code relies on RocketPy
private plot functions (_generate_combustion_chamber, _generate_grains,
_generate_positioned_tanks, _generate_nozzle, _generate_motor_region) which can
break across RocketPy minor versions; add an import-time version assertion in
this module that checks rocketpy.__version__ (or other canonical version symbol)
against the pinned version constant and raises a clear ImportError/RuntimeError
if mismatched so incompatibility surfaces at load-time rather than failing at
request time, and add a short TODO comment referencing filing an upstream
request for a public geometry-export hook.
src/views/rocket.py (1)

21-37: Optional nit: sort __all__ to silence Ruff RUF022.

Low priority, but since make lint is part of the project's required workflow, sorting the list keeps the diff noise-free on future autofix runs.

♻️ Proposed sort
 __all__ = [
-    "RocketSimulation",
-    "RocketView",
-    "RocketCreated",
-    "RocketRetrieved",
-    "RocketDrawingGeometry",
-    "NoseConeGeometry",
-    "TailGeometry",
-    "FinOutline",
-    "FinsGeometry",
-    "TubeGeometry",
-    "MotorPatch",
-    "MotorDrawingGeometry",
-    "RailButtonsGeometry",
-    "SensorGeometry",
-    "DrawingBounds",
+    "DrawingBounds",
+    "FinOutline",
+    "FinsGeometry",
+    "MotorDrawingGeometry",
+    "MotorPatch",
+    "NoseConeGeometry",
+    "RailButtonsGeometry",
+    "RocketCreated",
+    "RocketDrawingGeometry",
+    "RocketRetrieved",
+    "RocketSimulation",
+    "RocketView",
+    "SensorGeometry",
+    "TailGeometry",
+    "TubeGeometry",
 ]

As per coding guidelines: "Execute make lint with Flake8 and Pylint against repository presets".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/views/rocket.py` around lines 21 - 37, The __all__ list in this module is
unsorted which triggers Ruff RUF022; update the module-level __all__ definition
(the list containing "RocketSimulation", "RocketView", "RocketCreated",
"RocketRetrieved", "RocketDrawingGeometry", "NoseConeGeometry", "TailGeometry",
"FinOutline", "FinsGeometry", "TubeGeometry", "MotorPatch",
"MotorDrawingGeometry", "RailButtonsGeometry", "SensorGeometry",
"DrawingBounds") to be alphabetically sorted (e.g., arrange the string entries
in ascending lexicographic order) so linting passes and future autofixes don't
churn the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@requirements.txt`:
- 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.

In `@src/services/motor.py`:
- Around line 302-315: The long expression setting fallback_radius should be
split to satisfy the 79-character limit: extract the getattr call into a short
temporary variable (e.g., nozzle_radius_raw = getattr(self._motor,
"nozzle_radius", 0.0) or 0.0) then compute fallback_radius =
float(nozzle_radius_raw); update this in the get_drawing_geometry logic that
references self._motor and fallback_radius (preserving the EmptyMotor
early-return behavior and use of MotorDrawingGeometry).

In `@src/services/rocket.py`:
- Around line 23-36: The import list in src/services/rocket.py unnecessarily
includes MotorPatch which is not used here (motor patches are created in
MotorService.build_drawing_geometry); remove MotorPatch from the from
src.views.rocket import (...) statement so the module only imports the actually
used symbols (e.g., RocketSimulation, RocketDrawingGeometry, NoseConeGeometry,
TailGeometry, FinsGeometry, FinOutline, TubeGeometry, MotorDrawingGeometry,
RailButtonsGeometry, SensorGeometry, DrawingBounds) to satisfy Pylint.
- Around line 397-416: The _build_sensors function currently accesses sensor
positions by indexing (pos[0], pos[1], pos[2]) which is inconsistent with the
attribute-based access used elsewhere (e.g., _build_rail_buttons); update
_build_sensors to read position components via pos.x, pos.y, pos.z and ensure
SensorGeometry construction uses those floats (keep existing normal handling and
name retrieval intact) so the function (_build_sensors) matches the attribute
access pattern used across the codebase.

---

Nitpick comments:
In `@src/services/motor.py`:
- Line 282: The line assigning fallback_radius is over 79 characters; break it
into a shorter, PEP8-compliant expression using the same symbols
(fallback_radius, self._motor, "nozzle_radius", getattr) so it stays within the
79-char limit—for example, compute the attribute value into a short temporary
variable or use a parenthesized expression split across two lines, then cast to
float as before to preserve behavior.
- Around line 327-396: This code relies on RocketPy private plot functions
(_generate_combustion_chamber, _generate_grains, _generate_positioned_tanks,
_generate_nozzle, _generate_motor_region) which can break across RocketPy minor
versions; add an import-time version assertion in this module that checks
rocketpy.__version__ (or other canonical version symbol) against the pinned
version constant and raises a clear ImportError/RuntimeError if mismatched so
incompatibility surfaces at load-time rather than failing at request time, and
add a short TODO comment referencing filing an upstream request for a public
geometry-export hook.

In `@src/services/rocket.py`:
- Around line 287-296: The two broad except blocks around
rocket.center_of_mass(0) and rocket.cp_position(0) should be narrowed to
specific exceptions so we don't swallow unexpected errors; catch only
AttributeError, TypeError, and ValueError (or the subset that matches the
possible failure modes) for the calls to center_of_mass and cp_position in place
of "except Exception" and keep the existing fallback to set center_of_mass or
cp_position to None when those specific exceptions occur.

In `@src/views/rocket.py`:
- Around line 21-37: The __all__ list in this module is unsorted which triggers
Ruff RUF022; update the module-level __all__ definition (the list containing
"RocketSimulation", "RocketView", "RocketCreated", "RocketRetrieved",
"RocketDrawingGeometry", "NoseConeGeometry", "TailGeometry", "FinOutline",
"FinsGeometry", "TubeGeometry", "MotorPatch", "MotorDrawingGeometry",
"RailButtonsGeometry", "SensorGeometry", "DrawingBounds") to be alphabetically
sorted (e.g., arrange the string entries in ascending lexicographic order) so
linting passes and future autofixes don't churn the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a70859d5-5343-4be8-82a4-338ad11401bc

📥 Commits

Reviewing files that changed from the base of the PR and between eff3b9d and 7037785.

📒 Files selected for processing (13)
  • requirements.txt
  • src/controllers/motor.py
  • src/controllers/rocket.py
  • src/models/motor.py
  • src/models/sub/tanks.py
  • src/routes/motor.py
  • src/routes/rocket.py
  • src/services/motor.py
  • src/services/rocket.py
  • src/views/drawing.py
  • src/views/motor.py
  • src/views/rocket.py
  • tests/unit/test_routes/conftest.py

Comment thread requirements.txt
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.

Comment thread src/services/motor.py
Comment on lines +302 to +315
motor = self._motor
total_csys = parent_csys * motor._csys
nozzle_position = motor_position + motor.nozzle_position * total_csys

if isinstance(motor, EmptyMotor):
return (
MotorDrawingGeometry(
type="empty",
position=float(motor_position),
nozzle_position=float(nozzle_position),
patches=[],
),
float(nozzle_position),
)
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether EmptyMotor exposes nozzle_radius and nozzle_position at all.
rg -nP --type=py -C3 '\bclass\s+EmptyMotor\b'

Repository: RocketPy-Team/Infinity-API

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Find where EmptyMotor is imported or used in the repository
rg -n "EmptyMotor" --type=py -C2

Repository: RocketPy-Team/Infinity-API

Length of output: 1087


🏁 Script executed:

#!/bin/bash
# Read the actual code in src/services/motor.py around the flagged lines
head -320 src/services/motor.py | tail -50 | cat -n

Repository: RocketPy-Team/Infinity-API

Length of output: 2413


🏁 Script executed:

#!/bin/bash
# Check line 282 specifically for length
sed -n '282p' src/services/motor.py | wc -c

Repository: RocketPy-Team/Infinity-API

Length of output: 72


🏁 Script executed:

#!/bin/bash
# Find the import statement for EmptyMotor
rg -n "from|import" src/services/motor.py | head -20

Repository: RocketPy-Team/Infinity-API

Length of output: 983


Line 282 exceeds the 79-character limit — reformat to comply with Black/Ruff.

The line fallback_radius = float(getattr(self._motor, "nozzle_radius", 0.0) or 0.0) is 83 characters. The EmptyMotor early-return behavior is intentional and correctly documented: it returns a valid MotorDrawingGeometry object with empty patches rather than None, allowing the fallback radius mechanism in get_drawing_geometry to handle bounds computation. This is proper design and does not trigger the 422 error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/motor.py` around lines 302 - 315, The long expression setting
fallback_radius should be split to satisfy the 79-character limit: extract the
getattr call into a short temporary variable (e.g., nozzle_radius_raw =
getattr(self._motor, "nozzle_radius", 0.0) or 0.0) then compute fallback_radius
= float(nozzle_radius_raw); update this in the get_drawing_geometry logic that
references self._motor and fallback_radius (preserving the EmptyMotor
early-return behavior and use of MotorDrawingGeometry).

Comment thread src/services/rocket.py
Comment on lines +23 to +36
from src.views.rocket import (
RocketSimulation,
RocketDrawingGeometry,
NoseConeGeometry,
TailGeometry,
FinsGeometry,
FinOutline,
TubeGeometry,
MotorDrawingGeometry,
MotorPatch,
RailButtonsGeometry,
SensorGeometry,
DrawingBounds,
)
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 | 🟡 Minor

Remove unused MotorPatch import (Pylint failing).

Pipeline is failing on MotorPatch being imported but not used in this file. Motor patches are built inside MotorService.build_drawing_geometry — this module never constructs one directly.

🐛 Proposed fix
 from src.views.rocket import (
     RocketSimulation,
     RocketDrawingGeometry,
     NoseConeGeometry,
     TailGeometry,
     FinsGeometry,
     FinOutline,
     TubeGeometry,
     MotorDrawingGeometry,
-    MotorPatch,
     RailButtonsGeometry,
     SensorGeometry,
     DrawingBounds,
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from src.views.rocket import (
RocketSimulation,
RocketDrawingGeometry,
NoseConeGeometry,
TailGeometry,
FinsGeometry,
FinOutline,
TubeGeometry,
MotorDrawingGeometry,
MotorPatch,
RailButtonsGeometry,
SensorGeometry,
DrawingBounds,
)
from src.views.rocket import (
RocketSimulation,
RocketDrawingGeometry,
NoseConeGeometry,
TailGeometry,
FinsGeometry,
FinOutline,
TubeGeometry,
MotorDrawingGeometry,
RailButtonsGeometry,
SensorGeometry,
DrawingBounds,
)
🧰 Tools
🪛 GitHub Actions: Pylint

[warning] 23-23: pylint (W0611): Unused MotorPatch imported from src.views.rocket (unused-import)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/rocket.py` around lines 23 - 36, The import list in
src/services/rocket.py unnecessarily includes MotorPatch which is not used here
(motor patches are created in MotorService.build_drawing_geometry); remove
MotorPatch from the from src.views.rocket import (...) statement so the module
only imports the actually used symbols (e.g., RocketSimulation,
RocketDrawingGeometry, NoseConeGeometry, TailGeometry, FinsGeometry, FinOutline,
TubeGeometry, MotorDrawingGeometry, RailButtonsGeometry, SensorGeometry,
DrawingBounds) to satisfy Pylint.

Comment thread src/services/rocket.py
Comment on lines +397 to +416
def _build_sensors(self) -> list[SensorGeometry]:
rocket = self._rocket
sensors: list[SensorGeometry] = []
for sensor_pos in getattr(rocket, "sensors", []) or []:
sensor = sensor_pos[0]
pos = sensor_pos[1]
normal = getattr(sensor, "normal_vector", None)
normal_tuple = (
(float(normal.x), float(normal.y), float(normal.z))
if normal is not None
else (0.0, 0.0, 0.0)
)
sensors.append(
SensorGeometry(
name=getattr(sensor, "name", None),
position=(float(pos[0]), float(pos[1]), float(pos[2])),
normal=normal_tuple,
)
)
return sensors
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how rocketpy stores sensor positions vs. rail buttons / aero surfaces.
rg -nP --type=py -C3 '\bsensors\s*=' 
rg -nP --type=py -C3 '\bclass\s+Components\b'
rg -nP --type=py -C3 'add_sensor|sensors\.add'

Repository: RocketPy-Team/Infinity-API

Length of output: 960


🏁 Script executed:

cat -n src/services/rocket.py | head -450 | tail -200

Repository: RocketPy-Team/Infinity-API

Length of output: 8521


🏁 Script executed:

rg -n "def _build_rail_buttons" -A 20 src/services/rocket.py

Repository: RocketPy-Team/Infinity-API

Length of output: 914


🏁 Script executed:

rg -n "def _build.*surface\|for.*surface\|aerodynamic" -A 10 src/services/rocket.py

Repository: RocketPy-Team/Infinity-API

Length of output: 52


🏁 Script executed:

rg -n "position\." src/services/rocket.py | head -20

Repository: RocketPy-Team/Infinity-API

Length of output: 109


🏁 Script executed:

rg -n "from.*Vector|import.*Vector" src/services/rocket.py

Repository: RocketPy-Team/Infinity-API

Length of output: 52


🏁 Script executed:

rg -n "rocket\.sensors\|rocket\.rail_buttons" -B 2 -A 2 src/

Repository: RocketPy-Team/Infinity-API

Length of output: 52


🏁 Script executed:

grep -n "def _build_rail_buttons\|def _build_sensors\|rocket.sensors\|rail_buttons" src/services/rocket.py | head -30

Repository: RocketPy-Team/Infinity-API

Length of output: 688


🏁 Script executed:

head -100 src/services/rocket.py

Repository: RocketPy-Team/Infinity-API

Length of output: 3098


🏁 Script executed:

rg -n "Vector|normal_vector" src/services/rocket.py | head -20

Repository: RocketPy-Team/Infinity-API

Length of output: 133


🏁 Script executed:

fd -type f -name "*test*" -o -name "*spec*" | grep -E "\.(py|md)$"

Repository: RocketPy-Team/Infinity-API

Length of output: 240


🏁 Script executed:

rg -n "sensors\|rail_buttons" tests/ -B 2 -A 2 2>/dev/null | head -40

Repository: RocketPy-Team/Infinity-API

Length of output: 52


🏁 Script executed:

web_search
rocketpy library sensor position vector documentation

Repository: RocketPy-Team/Infinity-API

Length of output: 167


🏁 Script executed:

find . -type f -name "*test*.py" 2>/dev/null | head -10

Repository: RocketPy-Team/Infinity-API

Length of output: 458


🏁 Script executed:

rg -n "sensors" tests/ -B 2 -A 2 2>/dev/null || echo "No tests directory or no matches found"

Repository: RocketPy-Team/Infinity-API

Length of output: 108


🏁 Script executed:

cat -n src/services/rocket.py | grep -A 5 -B 5 "def _build_sensors\|def _build_rail_buttons"

Repository: RocketPy-Team/Infinity-API

Length of output: 941


🌐 Web query:

rocketpy sensor position vector indexing attribute access

💡 Result:

In RocketPy, sensors are added to a Rocket instance using rocket.add_sensor(sensor, position), where position is a float (assumed along z-axis), tuple/list (x,y,z), or Vector, relative to the rocket's coordinate system origin (typically center of mass without motor). The rocket.sensors is a Components instance storing sensors and their positions as namedtuples (component, position). Access sensors via rocket.sensors.get_components for the list of sensor objects, or rocket.sensors.get_positions for the list of position Vectors. Iterate over rocket.sensors._components for paired access: for c in rocket.sensors.components: sensor = c.component; pos = c.position. The position vector (relative_position) is passed to sensor.measure during simulation, used to compute sensor-specific acceleration/position from rocket CoM. For example, in Accelerometer/Gyroscope/GnssReceiver source code, relative_position is a np.array/Vector from rocket CoM to sensor. No direct public attribute like sensor.position exists on individual sensors; position is managed by the rocket's Components. Example usage from docs: sensors = rocket.sensors.get_components; first_sensor = sensors[0]; first_pos = rocket.sensors.get_positions[0]. Indexing via lists from get methods. Sensors under experimental development; API may change.

Citations:


🏁 Script executed:

rg -n "\.x\|\.y\|\.z" src/services/rocket.py | head -20

Repository: RocketPy-Team/Infinity-API

Length of output: 52


🏁 Script executed:

rg -n "\[0\]\|\[1\]\|\[2\]" src/services/rocket.py | grep "pos\|position\|normal" | head -20

Repository: RocketPy-Team/Infinity-API

Length of output: 52


🏁 Script executed:

rg -n "\.z|\.y|\.x" src/services/rocket.py

Repository: RocketPy-Team/Infinity-API

Length of output: 599


🏁 Script executed:

grep -n "pos\[0\]\|pos\[1\]\|pos\[2\]" src/services/rocket.py

Repository: RocketPy-Team/Infinity-API

Length of output: 224


🏁 Script executed:

sed -n '380,416p' src/services/rocket.py

Repository: RocketPy-Team/Infinity-API

Length of output: 1431


Use consistent position access pattern with attribute access instead of indexing.

In _build_rail_buttons (line 386) and elsewhere, position Vectors are accessed via attributes (pos.z, normal.x), but _build_sensors (line 412) uses indexing (pos[0], pos[1], pos[2]). Both syntaxes work with rocketpy's Vector, but align sensor position access to use .x, .y, .z for consistency throughout the codebase.

Suggested fix
    position=(float(pos.x), float(pos.y), float(pos.z)),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/rocket.py` around lines 397 - 416, The _build_sensors function
currently accesses sensor positions by indexing (pos[0], pos[1], pos[2]) which
is inconsistent with the attribute-based access used elsewhere (e.g.,
_build_rail_buttons); update _build_sensors to read position components via
pos.x, pos.y, pos.z and ensure SensorGeometry construction uses those floats
(keep existing normal handling and name retrieval intact) so the function
(_build_sensors) matches the attribute access pattern used across the codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants