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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,10 @@ cython_debug/
#.idea/

# VSCode config
.vscode/
.vscode/

# context specific ignores
.context

# lint
.pylint.d/
6 changes: 5 additions & 1 deletion src/models/motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

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.

🗄️ 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.py

Repository: 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 -n

Repository: 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 GENERIC burn_time in the model validator.

POST /motors builds MotorModel, then MotorRepository.create_motor persists motor.model_dump(exclude_none=True). The GENERIC burn_time check lives only in MotorService.from_motor_model, so a malformed GENERIC motor can be stored and fail later. Add the same POST-level check used for solid/grain parameters and other motor-kind requirements in MotorModel.validate_motor_kind.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/models/motor.py` around lines 23 - 27, Update
MotorModel.validate_motor_kind to reject GENERIC motors when burn_time is None,
alongside the existing motor-kind requirements. Ensure this validation occurs
during POST model construction before MotorRepository.create_motor persists
model_dump(exclude_none=True), while preserving validation behavior for other
motor kinds.

nozzle_radius: float
dry_mass: float
dry_inertia: Tuple[float, float, float] = (0, 0, 0)
Expand Down
75 changes: 71 additions & 4 deletions src/models/sub/tanks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from enum import Enum
from typing import Optional, Tuple, List
from pydantic import BaseModel
from typing import Annotated, List, Literal, Optional, Tuple, Union

from pydantic import BaseModel, Field, model_validator


class TankKinds(str, Enum):
Expand All @@ -10,14 +11,64 @@ class TankKinds(str, Enum):
ULLAGE: str = "ULLAGE"


# Scalar: constant density (kg/m^3).
# [(temp_K, density_kg_m3)]: temperature-dependent (LOX / N2O).
# No pressure dependence.
DensityInput = Union[float, List[Tuple[float, float]]]


class TankFluids(BaseModel):
name: str
density: float
density: DensityInput


class CustomTankGeometry(BaseModel):
geometry_kind: Literal["custom"] = "custom"
geometry: List[Tuple[Tuple[float, float], float]]


class CylindricalTankGeometry(BaseModel):
geometry_kind: Literal["cylindrical"] = "cylindrical"
radius: float
height: float
spherical_caps: bool = False


class SphericalTankGeometry(BaseModel):
geometry_kind: Literal["spherical"] = "spherical"
radius: float


TankGeometryInput = Annotated[
Union[
CustomTankGeometry,
CylindricalTankGeometry,
SphericalTankGeometry,
],
Field(discriminator="geometry_kind"),
]


# Map tank_kind → tuple of MotorTank field names that rocketpy's
# corresponding Tank subclass requires.
_REQUIRED_FIELDS_BY_TANK_KIND = {
TankKinds.MASS_FLOW: (
"initial_liquid_mass",
"initial_gas_mass",
"liquid_mass_flow_rate_in",
"liquid_mass_flow_rate_out",
"gas_mass_flow_rate_in",
"gas_mass_flow_rate_out",
),
TankKinds.LEVEL: ("liquid_height",),
TankKinds.ULLAGE: ("ullage",),
TankKinds.MASS: ("liquid_mass", "gas_mass"),
}


class MotorTank(BaseModel):
# Required parameters
geometry: List[Tuple[Tuple[float, float], float]]
geometry: TankGeometryInput
gas: TankFluids
liquid: TankFluids
flux_time: Tuple[float, float]
Expand Down Expand Up @@ -48,3 +99,19 @@ class MotorTank(BaseModel):

# Computed parameters
tank_kind: TankKinds = TankKinds.MASS_FLOW

@model_validator(mode='after')
def validate_tank_kind_fields(self):
# reject incoherent payloads at the API boundary
# instead of letting rocketpy crash during Tank construction.
missing = [
field
for field in _REQUIRED_FIELDS_BY_TANK_KIND[self.tank_kind]
if getattr(self, field) is None
]
if missing:
raise ValueError(
f"tank_kind={self.tank_kind.value} requires: "
f"{', '.join(missing)}"
)
return self
25 changes: 25 additions & 0 deletions src/routes/motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,31 @@ async def create_motor(

## Args
``` models.Motor JSON ```

For liquid/hybrid motors the `tanks` field supports three geometry
kinds via the `geometry_kind` discriminator and a scalar-or-sampled
fluid `density`:

```
{
"motor_kind": "LIQUID",
...
"tanks": [{
"geometry": {
"geometry_kind": "cylindrical", // or "spherical", "custom"
"radius": 0.1, "height": 0.5
},
"liquid": {
"name": "LOX",
"density": [[90.0, 1141.0], [120.0, 1091.0]] // or scalar
},
"gas": {"name": "N2", "density": 1.2},
"tank_kind": "LEVEL",
"liquid_height": 0.25,
...
}]
}
```
"""
with tracer.start_as_current_span("create_motor"):
return await controller.post_motor(motor)
Expand Down
33 changes: 29 additions & 4 deletions src/services/flight.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,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

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.

🗄️ 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.

_extract_fluid_density collapses function-backed tank gas and liquid density to one value at 273.15 K, then import_flight_from_rpy persists that scalar. A later simulation rebuilds fluids from those persisted samples and ignores the original temperature-dependent density. Extract representable (T_K, density) samples into DensityInput, or reject the import for non-representable density functions instead of changing the flight model.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/flight.py` around lines 230 - 244, Update _extract_fluid_density
and its import_flight_from_rpy caller to preserve temperature-dependent
densities by converting representable Function values into DensityInput (T_K,
density) samples; do not collapse them to a scalar at 273.15 K. If a density
function cannot be represented by the API’s sampled schema, reject the import
explicitly rather than persisting an inaccurate scalar.


@staticmethod
def _extract_tanks(motor) -> list[MotorTank]:
tanks: list[MotorTank] = []
Expand All @@ -240,20 +256,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

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.

🗄️ 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:

rocketpy geometry TankGeometry geometry attribute cylindrical spherical piecewise

💡 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}')
PY

Repository: 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))
PY

Repository: RocketPy-Team/Infinity-API

Length of output: 1101


Preserve position-dependent tank geometry during import.

_extract_tanks() collapses each shape into a geometry_kind: "custom" segment whose radius is sampled at position == 0. For spherical tanks, the radius depends on tank height, so the imported geometry changes volume and later .rpy-loaded motor simulations. Preserve the discriminated cylindrical/spherical forms, or serialize the full variable-radius custom geometry instead of collapsing it to a constant-radius segment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/flight.py` around lines 262 - 271, Update _extract_tanks() and
the geometry_segments construction to preserve position-dependent tank geometry
during import: do not sample each function only at position 0 or collapse
cylindrical/spherical shapes into constant-radius custom segments. Retain the
discriminated cylindrical/spherical representation, or serialize the complete
variable-radius custom geometry so spherical tank radii remain height-dependent
and downstream .rpy motor simulations preserve volume.


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,
Expand Down
97 changes: 88 additions & 9 deletions src/services/motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,32 @@
from rocketpy.motors.solid_motor import SolidMotor
from rocketpy.motors.liquid_motor import LiquidMotor
from rocketpy.motors.hybrid_motor import HybridMotor
from rocketpy import (
CylindricalTank,
Fluid,
Function,
SphericalTank,
TankGeometry,
)
from rocketpy.motors import (
EmptyMotor,
GenericMotor,
LevelBasedTank,
MassBasedTank,
MassFlowRateBasedTank,
UllageBasedTank,
TankGeometry,
)

from fastapi import HTTPException, status

from src import logger
from src.models.sub.tanks import TankKinds
from src.models.sub.tanks import (
CustomTankGeometry,
CylindricalTankGeometry,
SphericalTankGeometry,
TankFluids,
TankKinds,
)
from src.models.motor import MotorKinds, MotorModel
from src.views.motor import MotorSimulation, MotorDrawingGeometryView
from src.views.drawing import (
Expand All @@ -31,6 +43,58 @@
from src.utils import collect_attributes


def _build_rocketpy_tank_geometry(geometry):
"""Convert an API geometry model into a rocketpy geometry object.

Dispatch mirrors the discriminated union in
``src.models.sub.tanks.TankGeometryInput``.
"""
if isinstance(geometry, CylindricalTankGeometry):
return CylindricalTank(
radius=geometry.radius,
height=geometry.height,
spherical_caps=geometry.spherical_caps,
)
if isinstance(geometry, SphericalTankGeometry):
return SphericalTank(radius=geometry.radius)
if isinstance(geometry, CustomTankGeometry):
return TankGeometry(geometry_dict=dict(geometry.geometry))
raise ValueError(
f"Unsupported tank geometry kind: {type(geometry).__name__}"
)


def _build_rocketpy_fluid(fluids: TankFluids) -> Fluid:
"""Convert an API TankFluids into a rocketpy Fluid.

Scalar density is passed through (Fluid stores it as a constant).
Sampled density is converted to a 1D Temperature → Density Function
and wrapped in a ``(T, P)`` callable because rocketpy's Fluid expects
density to be a function of both temperature and pressure. Pressure
is ignored here intentionally; only temperature-dependent density
is supported in this iteration.
"""
density = fluids.density
if isinstance(density, list):
temperature_to_density = Function(
source=density,
interpolation='linear',
extrapolation='natural',
inputs=['Temperature (K)'],
outputs='Density (kg/m^3)',
)

def density_callable(temperature, pressure): # noqa: ARG001
# pylint: disable=unused-argument
# Rocketpy's Fluid wraps this into a 2-input Function of
# (T, P); pressure is accepted for signature compatibility
# but intentionally ignored in this iteration.
return temperature_to_density.get_value(temperature)

return Fluid(name=fluids.name, density=density_callable)
return Fluid(name=fluids.name, density=density)


# ---------------------------------------------------------------------------
# Drawing-geometry helpers (module-level, shared by the motor and rocket
# drawing paths). Kept private with the `_` prefix; consumers should go
Expand Down Expand Up @@ -108,7 +172,6 @@ def from_motor_model(cls, motor: MotorModel) -> Self:

motor_core = {
"thrust_source": motor.thrust_source,
"burn_time": motor.burn_time,
"nozzle_radius": motor.nozzle_radius,
"dry_mass": motor.dry_mass,
"dry_inertia": motor.dry_inertia,
Expand All @@ -117,6 +180,13 @@ def from_motor_model(cls, motor: MotorModel) -> Self:
"interpolation_method": motor.interpolation_method,
"reshape_thrust_curve": reshape_thrust_curve,
}
# Only forward optional rocketpy args when the client supplied them.
# Leaving them out lets rocketpy pick its own default (burn_time
# auto-detected from thrust_source span; nozzle_position = 0).
if motor.burn_time is not None:
motor_core["burn_time"] = motor.burn_time
if motor.nozzle_position is not None:
motor_core["nozzle_position"] = motor.nozzle_position

match MotorKinds(motor.motor_kind):
case MotorKinds.LIQUID:
Expand Down Expand Up @@ -166,25 +236,34 @@ def from_motor_model(cls, motor: MotorModel) -> Self:
**optional_params,
)
case _:
# GenericMotor requires burn_time even though it's optional
# for the other motor kinds — surface the constraint at the
# API boundary instead of letting rocketpy raise a
# confusing stack trace deeper in construction.
if motor.burn_time is None:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="burn_time is required for generic motors.",
)
# nozzle_position is already forwarded via motor_core when
# the client supplied it; GenericMotor's own default (0)
# applies otherwise.
rocketpy_motor = GenericMotor(
**motor_core,
chamber_radius=motor.chamber_radius,
chamber_height=motor.chamber_height,
chamber_position=motor.chamber_position,
propellant_initial_mass=motor.propellant_initial_mass,
nozzle_position=motor.nozzle_position,
)

if motor.motor_kind not in (MotorKinds.SOLID, MotorKinds.GENERIC):
for tank in motor.tanks or []:
tank_core = {
"name": tank.name,
"geometry": TankGeometry(
geometry_dict=dict(tank.geometry)
),
"geometry": _build_rocketpy_tank_geometry(tank.geometry),
"flux_time": tank.flux_time,
"gas": tank.gas,
"liquid": tank.liquid,
"gas": _build_rocketpy_fluid(tank.gas),
"liquid": _build_rocketpy_fluid(tank.liquid),
"discretize": tank.discretize,
}

Expand Down
Loading
Loading