ENH: new dimensions route to get rocket geometry#71
ENH: new dimensions route to get rocket geometry#71aasitvora99 wants to merge 10 commits intomasterfrom
Conversation
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>
📝 WalkthroughWalkthroughAdds 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
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, ...}
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, ...}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
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.
| # 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 == ( |
There was a problem hiding this comment.
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.
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.
7037785
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
src/services/rocket.py (1)
287-296: Broadexcept Exception— tighten if possible.Ruff BLE001 flags these two blocks. The pragma comments acknowledge the intent, but catching
AttributeError/TypeError/ValueErrorspecifically would protect against swallowing bugs (e.g., a typo calling a nonexistent method returning aKeyboardInterrupt-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 formatpass. 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_regionare 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_regionin 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
_plotsdependency.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 lintis 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 lintwith 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
📒 Files selected for processing (13)
requirements.txtsrc/controllers/motor.pysrc/controllers/rocket.pysrc/models/motor.pysrc/models/sub/tanks.pysrc/routes/motor.pysrc/routes/rocket.pysrc/services/motor.pysrc/services/rocket.pysrc/views/drawing.pysrc/views/motor.pysrc/views/rocket.pytests/unit/test_routes/conftest.py
| gunicorn | ||
| uvicorn | ||
| git+https://github.com/RocketPy-Team/RocketPy.git@develop | ||
| rocketpy |
There was a problem hiding this comment.
🧩 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:
- 1: https://pypi.org/project/rocketpy/
- 2: https://github.com/RocketPy-Team/RocketPy/
- 3: https://github.com/RocketPy-Team/RocketPy/releases/tag/v1.12.0
- 4: https://github.com/RocketPy-Team/RocketPy/blob/master/CHANGELOG.md
🏁 Script executed:
# First, check the current state of requirements.txt
cat requirements.txtRepository: 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/nullRepository: 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 -100Repository: 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/nullRepository: RocketPy-Team/Infinity-API
Length of output: 84
🏁 Script executed:
# Look for any comments or docs mentioning rocketpy version
rg "rocketpy.*version|version.*rocketpy" -iRepository: RocketPy-Team/Infinity-API
Length of output: 52
🏁 Script executed:
# Check pyproject.toml for rocketpy version specification
cat pyproject.tomlRepository: 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/nullRepository: RocketPy-Team/Infinity-API
Length of output: 238
🏁 Script executed:
# Check git log with short format to see recent changes
git log --oneline | head -20Repository: 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/nullRepository: 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 -20Repository: 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 -20Repository: 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 -A2Repository: 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.
| 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), | ||
| ) |
There was a problem hiding this comment.
🧩 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 -C2Repository: 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 -nRepository: 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 -cRepository: 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 -20Repository: 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).
| from src.views.rocket import ( | ||
| RocketSimulation, | ||
| RocketDrawingGeometry, | ||
| NoseConeGeometry, | ||
| TailGeometry, | ||
| FinsGeometry, | ||
| FinOutline, | ||
| TubeGeometry, | ||
| MotorDrawingGeometry, | ||
| MotorPatch, | ||
| RailButtonsGeometry, | ||
| SensorGeometry, | ||
| DrawingBounds, | ||
| ) |
There was a problem hiding this comment.
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.
| 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.
| 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 |
There was a problem hiding this comment.
🧩 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 -200Repository: RocketPy-Team/Infinity-API
Length of output: 8521
🏁 Script executed:
rg -n "def _build_rail_buttons" -A 20 src/services/rocket.pyRepository: RocketPy-Team/Infinity-API
Length of output: 914
🏁 Script executed:
rg -n "def _build.*surface\|for.*surface\|aerodynamic" -A 10 src/services/rocket.pyRepository: RocketPy-Team/Infinity-API
Length of output: 52
🏁 Script executed:
rg -n "position\." src/services/rocket.py | head -20Repository: RocketPy-Team/Infinity-API
Length of output: 109
🏁 Script executed:
rg -n "from.*Vector|import.*Vector" src/services/rocket.pyRepository: 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 -30Repository: RocketPy-Team/Infinity-API
Length of output: 688
🏁 Script executed:
head -100 src/services/rocket.pyRepository: RocketPy-Team/Infinity-API
Length of output: 3098
🏁 Script executed:
rg -n "Vector|normal_vector" src/services/rocket.py | head -20Repository: 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 -40Repository: RocketPy-Team/Infinity-API
Length of output: 52
🏁 Script executed:
web_search
rocketpy library sensor position vector documentationRepository: RocketPy-Team/Infinity-API
Length of output: 167
🏁 Script executed:
find . -type f -name "*test*.py" 2>/dev/null | head -10Repository: 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:
- 1: https://docs.rocketpy.org/en/latest/notebooks/sensors.html
- 2: https://docs.rocketpy.org/en/develop/notebooks/sensors.html
- 3: https://docs.rocketpy.org/en/latest/_modules/rocketpy/rocket/rocket.html
- 4: https://docs.rocketpy.org/en/latest/_modules/rocketpy/rocket/components.html
- 5: https://docs.rocketpy.org/en/latest/_modules/rocketpy/sensors/accelerometer.html
- 6: https://docs.rocketpy.org/en/latest/_modules/rocketpy/sensors/gyroscope.html
- 7: https://docs.rocketpy.org/en/latest/_modules/rocketpy/sensors/gnss_receiver.html
🏁 Script executed:
rg -n "\.x\|\.y\|\.z" src/services/rocket.py | head -20Repository: RocketPy-Team/Infinity-API
Length of output: 52
🏁 Script executed:
rg -n "\[0\]\|\[1\]\|\[2\]" src/services/rocket.py | grep "pos\|position\|normal" | head -20Repository: RocketPy-Team/Infinity-API
Length of output: 52
🏁 Script executed:
rg -n "\.z|\.y|\.x" src/services/rocket.pyRepository: RocketPy-Team/Infinity-API
Length of output: 599
🏁 Script executed:
grep -n "pos\[0\]\|pos\[1\]\|pos\[2\]" src/services/rocket.pyRepository: RocketPy-Team/Infinity-API
Length of output: 224
🏁 Script executed:
sed -n '380,416p' src/services/rocket.pyRepository: 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.
Implements https://github.com/RocketPy-Team/jarvis-ts/issues/51
Summary by CodeRabbit
New Features
/motors/{motor_id}/drawing-geometryand/rockets/{rocket_id}/drawing-geometryendpoints to retrieve detailed drawing geometry.Bug Fixes
Chores