-
-
Notifications
You must be signed in to change notification settings - Fork 3
feat(motor,flight): tank geometry + fluid density inputs, KML export #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
64b2ea9
c4ef363
45032d8
bf0c365
6836650
edf06e5
e56e6d4
8d1027e
dc8acf2
ff59ada
5b545b0
73e0789
f0a5903
99c6214
7234995
4109660
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,11 @@ class MotorModel(ApiBaseModel): | |
|
|
||
| # Required parameters | ||
| thrust_source: List[List[float]] | ||
| burn_time: float | ||
| # burn_time is optional for Liquid/Hybrid/Solid motors — rocketpy | ||
| # auto-detects the burn window from the thrust_source array span. | ||
| # GenericMotor still requires it; the motor service re-raises an | ||
| # explicit error when the GENERIC path receives None. | ||
| burn_time: Optional[float] = None | ||
|
Comment on lines
+23
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Check whether MotorController.post_motor builds the rocketpy motor synchronously.
fd -e py motor.py --full-path src/controllers | xargs -I{} sh -c 'echo "--- {} ---"; cat -n {}'Repository: RocketPy-Team/Infinity-API Length of output: 3177 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Tracked controller files:"
git ls-files 'src/controllers/*.py'
echo "--- MotorController methods ---"
rg -n "def (create|create_?motor|post|get|update|delete|add|upsert|fetch)|motor_id|motor\.motor|MotorModel|get_motor_by_id" src/controllers/motor.py
echo "--- motor.py line count ---"
wc -l src/controllers/motor.pyRepository: RocketPy-Team/Infinity-API Length of output: 1224 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "--- src/controllers/interface.py relevant sections ---"
wc -l src/controllers/interface.py
sed -n '1,220p' src/controllers/interface.py | cat -n
echo "--- src/api.py imports and registered motor/controller routes ---"
wc -l src/api.py
rg -n "MotorController|motor|controllers|router|BaseController|create" src/api.py -C 3
echo "--- MotorModel and service validation snippets ---"
wc -l src/models/motor.py src/services/motor.py
sed -n '1,130p' src/models/motor.py | cat -n
sed -n '220,260p' src/services/motor.py | cat -nRepository: RocketPy-Team/Infinity-API Length of output: 14542 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "--- create_motor methods in motor controllers/repositories ---"
rg -n "def create_motor|create_motor\\(" src/controllers src/repositories src/routes -C 6
echo "--- motor router outline and relevant snippets ---"
wc -l src/routes/motor.py
sed -n '1,220p' src/routes/motor.py | cat -n
echo "--- motor repository filenames and create_motor snippets ---"
git ls-files 'src/repositories/*motor*' 'src/repositories/motor.py'
fd -e py 'motor|Motor' src/repositories -x sh -c 'echo "--- $1 ---"; rg -n "create_motor|create_model|NAME" "$1" -C 5' sh {}Repository: RocketPy-Team/Infinity-API Length of output: 8123 Enforce
🤖 Prompt for AI Agents |
||
| nozzle_radius: float | ||
| dry_mass: float | ||
| dry_inertia: Tuple[float, float, float] = (0, 0, 0) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,12 @@ | ||
| import json | ||
| import os | ||
| import tempfile | ||
| from typing import Self, Tuple | ||
|
|
||
| import numpy as np | ||
|
|
||
| from rocketpy.simulation.flight import Flight as RocketPyFlight | ||
| from rocketpy.simulation.flight_data_exporter import FlightDataExporter | ||
| from rocketpy._encoders import RocketPyEncoder, RocketPyDecoder | ||
| from rocketpy.mathutils.function import Function | ||
| from rocketpy.motors.solid_motor import SolidMotor | ||
|
|
@@ -224,6 +227,22 @@ def _to_float(value) -> float: | |
| case _: | ||
| return float(value) | ||
|
|
||
| @staticmethod | ||
| def _extract_fluid_density(fluid): | ||
| """Project a rocketpy Fluid's density back onto the API schema. | ||
|
|
||
| The API accepts either a scalar or a list of (T_K, density) | ||
| samples. Rocketpy may store density as either a raw scalar or a | ||
| ``Function`` wrapping a 2D ``(T, P) -> density`` callable. A | ||
| full sample round-trip is not supported in this iteration; | ||
| Function-valued densities are collapsed to a scalar evaluated | ||
| at rocketpy's default reference (273.15 K, 101325 Pa). | ||
| """ | ||
| density = fluid.density | ||
| if isinstance(density, Function): | ||
| return float(density(273.15, 101325)) | ||
| return density | ||
|
Comment on lines
+230
to
+244
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Files matching flight.py:"
fd -a 'flight.py$' . || true
echo
echo "Outline src/services/flight.py:"
if [ -f src/services/flight.py ]; then
wc -l src/services/flight.py
ast-grep outline src/services/flight.py --view expanded | sed -n '1,220p'
fi
echo
echo "Relevant sections:"
for start in 1 180 210 280 380 460 540; do
end=$((start+120))
echo "--- src/services/flight.py $start-$end"
sed -n "${start},${end}p" src/services/flight.py
done
echo
echo "Search DensityInput and extract_models:"
rg -n "DensityInput|extract_models|import_flight_from_rpy|_extract_fluid_density|density_input|fluid_dens|fluid" src tests | sed -n '1,240p'Repository: RocketPy-Team/Infinity-API Length of output: 33062 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "src/controllers/flight.py relevant section:"
sed -n '180,240p' src/controllers/flight.py | cat -n
echo
echo "src/services/motor.py fluid construction:"
sed -n '55,100p' src/services/motor.py | cat -n
sed -n '250,275p' src/services/motor.py | cat -n
echo
echo "MotorService round-trip usages:"
rg -n "from_models|from_motor_model|extract_models|get_models|_build_rocketpy_fluid|Fluid\\(" src/services/motor.py src/services/flight.py src/services/*.py | sed -n '1,220p'
echo
echo "Search for create flight from model round-trip:"
rg -n "FlightService\\.(from_flight_model|extract_models)|import_flight_from_rpy|from_motor_model" src tests | sed -n '1,220p'Repository: RocketPy-Team/Infinity-API Length of output: 10363 Preserve temperature-sampled fluid density during import.
🤖 Prompt for AI Agents |
||
|
|
||
| @staticmethod | ||
| def _extract_tanks(motor) -> list[MotorTank]: | ||
| tanks: list[MotorTank] = [] | ||
|
|
@@ -240,20 +259,29 @@ def _extract_tanks(motor) -> list[MotorTank]: | |
| case _: | ||
| tank_kind = TankKinds.MASS_FLOW | ||
|
|
||
| geometry = [ | ||
| # Geometry round-trip is lossy: even if the client originally | ||
| # sent a cylindrical/spherical geometry, we discretise it back | ||
| # to the generic piecewise form on read. Every rocketpy tank | ||
| # geometry exposes its internal piecewise dict via | ||
| # `tank.geometry.geometry`, so this path covers all three | ||
| # geometry subclasses uniformly. | ||
| geometry_segments = [ | ||
| (bounds, float(func(0))) | ||
| for bounds, func in tank.geometry.geometry.items() | ||
| ] | ||
|
Comment on lines
+262
to
271
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== relevant files =="
git ls-files | rg '(^src/services/flight.py$|rocketpy|tank|geometry)' | sed -n '1,120p'
echo
echo "== outline src/services/flight.py around import/export =="
ast-grep outline src/services/flight.py --view expanded 2>/dev/null | sed -n '1,220p' | rg -n 'def|class|flight|tank|import|serde|geometry' || true
echo
echo "== lines 220-310 =="
sed -n '220,310p' src/services/flight.py | nl -ba -v220
echo
echo "== search geometry classes/config =="
rg -n "class .*Tank|Geometry|geometry\.geometry|TankGeometry|Tank|tank\.geometry|rocketpy" src tests -g '*.py' | sed -n '1,240p'Repository: RocketPy-Team/Infinity-API Length of output: 1357 🌐 Web query:
💡 Result: In RocketPy, the TankGeometry class is used to define the geometry of an axi-symmetric tank by specifying its radius as a function of its height along the axis of symmetry [1][2]. The geometry is defined using a dictionary provided to the geometry_dict parameter [3][4]. This dictionary allows for the construction of complex, piecewise defined shapes [1][5]. Key details regarding the TankGeometry attribute: 1. The geometry attribute is a dictionary where each key is a disjoint tuple (lower_bound, upper_bound) representing a domain along the tank's symmetry axis in meters [1][3]. 2. The corresponding values are functions (typically Python lambdas or RocketPy.Function objects) that map the tank's height to its radius at that specific section [1][2]. 3. Because the dictionary can contain multiple disjoint domains, the tank's overall radius profile is effectively a piecewise-defined function [1][5][6]. While users can manually define geometries using this dictionary approach for arbitrary shapes, RocketPy also provides convenience classes such as CylindricalTank and SphericalTank for standard configurations [3][4]. For example, a spherical tank can be defined using the geometry dictionary by specifying a radius function such as lambda h: (radius2 - h2)(1/2) over the domain (-radius, radius) [1][2]. The internal logic of TankGeometry then computes the overall radius, area, and volume functions based on these inputs [1][5]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== src/services/flight.py lines 247-330 =="
sed -n '247,330p' src/services/flight.py
echo
echo "== src/models/sub/tanks.py =="
cat -n src/models/sub/tanks.py
echo
echo "== imports and serialization helpers =="
sed -n '1,120p' src/services/flight.py
echo
echo "== tank field serialization references =="
rg -n "MotorTank|tank|geometry|_extract_tanks|rpy|from_rpy" src -g '*.py' | sed -n '1,260p'Repository: RocketPy-Team/Infinity-API Length of output: 28255 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import ast
from pathlib import Path
src = Path('src/services/flight.py').read_text()
tree = ast.parse(src, filename='src/services/flight.py')
for node in tree.body:
if node.name == 'FlightService':
for cls in node.body:
if isinstance(cls, ast.FunctionDef) and cls.name == '_extract_tanks':
src_snippet = src.splitlines()[cls.lineno-1:cls.end_lineno]
print(f'function: {cls.name} lines {cls.lineno}-{cls.end_lineno}')
lines = [
('geometry_segments =', any('geometry_segments = [' in line for line in src_snippet)),
('evaluating func(0) explicitly:', any('func(0)' in line for line in src_snippet)),
('custom geometry_kind:', any('geometry_kind": "custom"' in line for line in src_snippet)),
('tank geometry dict:', any('tank.geometry.geometry.items()' in line for line in src_snippet)),
]
for label, ok in lines:
print(f'{label}: {ok!s}')
print('target block:')
for i, line in enumerate(src_snippet, start=cls.lineno):
print(f'{i}: {line}')
PYRepository: RocketPy-Team/Infinity-API Length of output: 442 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== inspect _build_rocketpy_tank_geometry =="
sed -n '40,62p' src/services/motor.py
echo
echo "== inspect _generate_positioned_tanks if present in installed rocketpy =="
python3 - <<'PY'
import inspect
try:
import rocketpy
from rocketpy import LevelBasedTank, MassBasedTank, UllageBasedTank
print('rocketpy imported', getattr(rocketpy, '__version__', ''))
for name, cls in [('LevelBasedTank', LevelBasedTank), ('MassBasedTank', MassBasedTank), ('UllageBasedTank', UllageBasedTank)]:
print(f'\n{name}:')
print(inspect.signature(cls))
print('methods:', sorted([m for m in dir(cls) if not m.startswith('_') and callable(getattr(cls, m))]))
if hasattr(cls, 'plots') and hasattr(getattr(cls, 'plots'), '_generate_positioned_tanks'):
print(inspect.getsource(cls.plots._generate_positioned_tanks))
except Exception as exc:
print(type(exc).__name__ + ': ' + str(exc))
PYRepository: RocketPy-Team/Infinity-API Length of output: 1101 Preserve position-dependent tank geometry during import.
🤖 Prompt for AI Agents |
||
|
|
||
| data: dict = { | ||
| "geometry": geometry, | ||
| "geometry": { | ||
| "geometry_kind": "custom", | ||
| "geometry": geometry_segments, | ||
| }, | ||
| "gas": TankFluids( | ||
| name=tank.gas.name, | ||
| density=tank.gas.density, | ||
| density=FlightService._extract_fluid_density(tank.gas), | ||
| ), | ||
| "liquid": TankFluids( | ||
| name=tank.liquid.name, | ||
| density=tank.liquid.density, | ||
| density=FlightService._extract_fluid_density(tank.liquid), | ||
| ), | ||
| "flux_time": tank.flux_time, | ||
| "position": position, | ||
|
|
@@ -474,6 +502,25 @@ def get_flight_simulation(self) -> FlightSimulation: | |
| flight_simulation = FlightSimulation(**encoded_attributes) | ||
| return flight_simulation | ||
|
|
||
| def get_flight_kml(self) -> bytes: | ||
| """ | ||
| Get the flight trajectory as a KML file for Google Earth. | ||
|
|
||
| Returns: | ||
| bytes (UTF-8 encoded KML) | ||
| """ | ||
| with tempfile.NamedTemporaryFile( | ||
| suffix=".kml", delete=False | ||
| ) as tmp: | ||
| tmp_path = tmp.name | ||
| try: | ||
| FlightDataExporter(self.flight).export_kml(file_name=tmp_path) | ||
| with open(tmp_path, "rb") as fh: | ||
| return fh.read() | ||
| finally: | ||
| if os.path.exists(tmp_path): | ||
| os.unlink(tmp_path) | ||
|
|
||
| def get_flight_rpy(self) -> bytes: | ||
| """ | ||
| Get the portable JSON ``.rpy`` representation of the flight. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: RocketPy-Team/Infinity-API
Length of output: 50382
🏁 Script executed:
Repository: RocketPy-Team/Infinity-API
Length of output: 46105
Move KML generation off the event-loop thread.
get_flight_kmlstill runsFlightService.from_flight_model(...).get_flight_kml()synchronously after awaiting the database read, and the controller is awaited by the FastAPI route handler. The export creates a temp file, reads it, and deletes it synchronously, so a long KML export blocks the shared event loop.Run the complete synchronous export path in a worker thread.
🤖 Prompt for AI Agents