diff --git a/src/services/environment.py b/src/services/environment.py index 4f1137c..31a69db 100644 --- a/src/services/environment.py +++ b/src/services/environment.py @@ -3,9 +3,9 @@ import dill from rocketpy.environment.environment import Environment as RocketPyEnvironment -from rocketpy.utilities import get_instance_attributes from src.models.environment import EnvironmentModel from src.views.environment import EnvironmentSimulation +from src.utils import rocketpy_encoder, DiscretizeConfig class EnvironmentService: @@ -50,7 +50,9 @@ def get_environment_simulation(self) -> EnvironmentSimulation: EnvironmentSimulation """ - attributes = get_instance_attributes(self.environment) + attributes = rocketpy_encoder( + self.environment, DiscretizeConfig.for_environment() + ) env_simulation = EnvironmentSimulation(**attributes) return env_simulation diff --git a/src/services/flight.py b/src/services/flight.py index fdce768..564a4a9 100644 --- a/src/services/flight.py +++ b/src/services/flight.py @@ -3,12 +3,12 @@ import dill from rocketpy.simulation.flight import Flight as RocketPyFlight -from rocketpy.utilities import get_instance_attributes from src.services.environment import EnvironmentService from src.services.rocket import RocketService from src.models.flight import FlightModel from src.views.flight import FlightSimulation +from src.utils import rocketpy_encoder, DiscretizeConfig class FlightService: @@ -55,7 +55,9 @@ def get_flight_simulation(self) -> FlightSimulation: Returns: FlightSimulation """ - attributes = get_instance_attributes(self.flight) + attributes = rocketpy_encoder( + self.flight, DiscretizeConfig.for_flight() + ) flight_simulation = FlightSimulation(**attributes) return flight_simulation diff --git a/src/services/motor.py b/src/services/motor.py index 5b2acde..d96cccc 100644 --- a/src/services/motor.py +++ b/src/services/motor.py @@ -6,7 +6,6 @@ from rocketpy.motors.solid_motor import SolidMotor from rocketpy.motors.liquid_motor import LiquidMotor from rocketpy.motors.hybrid_motor import HybridMotor -from rocketpy.utilities import get_instance_attributes from rocketpy import ( LevelBasedTank, MassBasedTank, @@ -18,6 +17,7 @@ from src.models.sub.tanks import TankKinds from src.models.motor import MotorKinds, MotorModel from src.views.motor import MotorSimulation +from src.utils import rocketpy_encoder, DiscretizeConfig class MotorService: @@ -140,7 +140,7 @@ def get_motor_simulation(self) -> MotorSimulation: Returns: MotorSimulation """ - attributes = get_instance_attributes(self.motor) + attributes = rocketpy_encoder(self.motor, DiscretizeConfig.for_motor()) motor_simulation = MotorSimulation(**attributes) return motor_simulation diff --git a/src/services/rocket.py b/src/services/rocket.py index 65bbfe3..878e9e0 100644 --- a/src/services/rocket.py +++ b/src/services/rocket.py @@ -11,13 +11,13 @@ Fins as RocketPyFins, Tail as RocketPyTail, ) -from rocketpy.utilities import get_instance_attributes from src import logger from src.models.rocket import RocketModel, Parachute from src.models.sub.aerosurfaces import NoseCone, Tail, Fins from src.services.motor import MotorService from src.views.rocket import RocketSimulation +from src.utils import rocketpy_encoder, DiscretizeConfig class RocketService: @@ -107,7 +107,9 @@ def get_rocket_simulation(self) -> RocketSimulation: Returns: RocketSimulation """ - attributes = get_instance_attributes(self.rocket) + attributes = rocketpy_encoder( + self.rocket, DiscretizeConfig.for_rocket() + ) rocket_simulation = RocketSimulation(**attributes) return rocket_simulation diff --git a/src/utils.py b/src/utils.py index d31d747..340733d 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,48 +1,113 @@ -# fork of https://github.com/encode/starlette/blob/master/starlette/middleware/gzip.py import gzip import io +import logging +import json +import copy -from typing import Annotated, NoReturn, Any -import numpy as np +from typing import NoReturn, Tuple -from pydantic import PlainSerializer +from rocketpy import Function +from rocketpy._encoders import RocketPyEncoder from starlette.datastructures import Headers, MutableHeaders from starlette.types import ASGIApp, Message, Receive, Scope, Send +logger = logging.getLogger(__name__) -def to_python_primitive(v: Any) -> Any: + +class DiscretizeConfig: """ - Convert complex types to Python primitives. + Configuration class for RocketPy function discretization. - Args: - v: Any value, particularly those with a 'source' attribute - containing numpy arrays or generic types. + This class allows easy configuration of discretization parameters + for different types of RocketPy objects and their callable attributes. + """ - Returns: - The primitive representation of the input value. + def __init__( + self, bounds: Tuple[float, float] = (0, 10), samples: int = 200 + ): + self.bounds = bounds + self.samples = samples + + @classmethod + def for_environment(cls) -> 'DiscretizeConfig': + return cls(bounds=(0, 50000), samples=100) + + @classmethod + def for_motor(cls) -> 'DiscretizeConfig': + return cls(bounds=(0, 10), samples=150) + + @classmethod + def for_rocket(cls) -> 'DiscretizeConfig': + return cls(bounds=(0, 1), samples=100) + + @classmethod + def for_flight(cls) -> 'DiscretizeConfig': + return cls(bounds=(0, 30), samples=200) + + +def rocketpy_encoder(obj, config: DiscretizeConfig = DiscretizeConfig()): """ - if hasattr(v, "source"): - if isinstance(v.source, np.ndarray): - return v.source.tolist() + Encode a RocketPy object using official RocketPy encoders. - if isinstance(v.source, (np.generic,)): - return v.source.item() + This function creates a copy of the object, discretizes callable Function + attributes on the copy, and then uses RocketPy's official RocketPyEncoder for + complete object serialization. The original object remains unchanged. - return str(v.source) + Args: + obj: RocketPy object (Environment, Motor, Rocket, Flight) + config: DiscretizeConfig object with discretization parameters (optional) - if isinstance(v, (np.generic,)): - return v.item() + Returns: + Dictionary of encoded attributes + """ - if isinstance(v, (np.ndarray,)): - return v.tolist() + # Create a copy to avoid mutating the original object + obj_copy = copy.deepcopy(obj) + + for attr_name in dir(obj_copy): + if attr_name.startswith('_'): + continue + + try: + attr_value = getattr(obj_copy, attr_name) + except Exception: + continue + + if callable(attr_value) and isinstance(attr_value, Function): + try: + discretized_func = Function(attr_value.source) + discretized_func.set_discrete( + lower=config.bounds[0], + upper=config.bounds[1], + samples=config.samples, + mutate_self=True, + ) - return str(v) + setattr(obj_copy, attr_name, discretized_func) + except Exception as e: + logger.warning(f"Failed to discretize {attr_name}: {e}") -AnyToPrimitive = Annotated[ - Any, - PlainSerializer(to_python_primitive), -] + try: + json_str = json.dumps( + obj_copy, + cls=RocketPyEncoder, + include_outputs=True, + include_function_data=True, + ) + return json.loads(json_str) + except Exception as e: + logger.warning(f"Failed to encode with RocketPyEncoder: {e}") + attributes = {} + for attr_name in dir(obj_copy): + if not attr_name.startswith('_'): + try: + attr_value = getattr(obj_copy, attr_name) + if not callable(attr_value): + attributes[attr_name] = str(attr_value) + except Exception: + continue + return attributes class RocketPyGZipMiddleware: @@ -70,6 +135,7 @@ async def __call__( class GZipResponder: + # fork of https://github.com/encode/starlette/blob/master/starlette/middleware/gzip.py def __init__( self, app: ASGIApp, minimum_size: int, compresslevel: int = 9 ) -> None: diff --git a/src/views/environment.py b/src/views/environment.py index 4283d52..445c79f 100644 --- a/src/views/environment.py +++ b/src/views/environment.py @@ -1,12 +1,24 @@ -from typing import Optional +from typing import Optional, Any from datetime import datetime, timedelta +from pydantic import ConfigDict from src.views.interface import ApiBaseView from src.models.environment import EnvironmentModel -from src.utils import AnyToPrimitive class EnvironmentSimulation(ApiBaseView): + """ + Environment simulation view that handles dynamically encoded RocketPy Environment attributes. + + Uses the new rocketpy_encoder which may return different attributes based on the + actual RocketPy Environment object. The model allows extra fields to accommodate + any new attributes that might be encoded. + """ + + model_config = ConfigDict(extra='allow', arbitrary_types_allowed=True) + message: str = "Environment successfully simulated" + + # Core Environment attributes (always present) latitude: Optional[float] = None longitude: Optional[float] = None elevation: Optional[float] = 1 @@ -26,27 +38,29 @@ class EnvironmentSimulation(ApiBaseView): date: Optional[datetime] = datetime.today() + timedelta(days=1) local_date: Optional[datetime] = datetime.today() + timedelta(days=1) datetime_date: Optional[datetime] = datetime.today() + timedelta(days=1) - ellipsoid: Optional[AnyToPrimitive] = None - barometric_height: Optional[AnyToPrimitive] = None - barometric_height_ISA: Optional[AnyToPrimitive] = None - pressure: Optional[AnyToPrimitive] = None - pressure_ISA: Optional[AnyToPrimitive] = None - temperature: Optional[AnyToPrimitive] = None - temperature_ISA: Optional[AnyToPrimitive] = None - density: Optional[AnyToPrimitive] = None - speed_of_sound: Optional[AnyToPrimitive] = None - dynamic_viscosity: Optional[AnyToPrimitive] = None - gravity: Optional[AnyToPrimitive] = None - somigliana_gravity: Optional[AnyToPrimitive] = None - wind_speed: Optional[AnyToPrimitive] = None - wind_direction: Optional[AnyToPrimitive] = None - wind_heading: Optional[AnyToPrimitive] = None - wind_velocity_x: Optional[AnyToPrimitive] = None - wind_velocity_y: Optional[AnyToPrimitive] = None - calculate_earth_radius: Optional[AnyToPrimitive] = None - decimal_degrees_to_arc_seconds: Optional[AnyToPrimitive] = None - geodesic_to_utm: Optional[AnyToPrimitive] = None - utm_to_geodesic: Optional[AnyToPrimitive] = None + + # Function attributes (discretized by rocketpy_encoder, serialized by RocketPyEncoder) + ellipsoid: Optional[Any] = None + barometric_height: Optional[Any] = None + barometric_height_ISA: Optional[Any] = None + pressure: Optional[Any] = None + pressure_ISA: Optional[Any] = None + temperature: Optional[Any] = None + temperature_ISA: Optional[Any] = None + density: Optional[Any] = None + speed_of_sound: Optional[Any] = None + dynamic_viscosity: Optional[Any] = None + gravity: Optional[Any] = None + somigliana_gravity: Optional[Any] = None + wind_speed: Optional[Any] = None + wind_direction: Optional[Any] = None + wind_heading: Optional[Any] = None + wind_velocity_x: Optional[Any] = None + wind_velocity_y: Optional[Any] = None + calculate_earth_radius: Optional[Any] = None + decimal_degrees_to_arc_seconds: Optional[Any] = None + geodesic_to_utm: Optional[Any] = None + utm_to_geodesic: Optional[Any] = None class EnvironmentView(EnvironmentModel): diff --git a/src/views/flight.py b/src/views/flight.py index 1a82f98..4e7a411 100644 --- a/src/views/flight.py +++ b/src/views/flight.py @@ -1,156 +1,106 @@ -from typing import Optional +from typing import Optional, Any +from pydantic import ConfigDict from src.models.flight import FlightModel from src.views.interface import ApiBaseView from src.views.rocket import RocketView, RocketSimulation from src.views.environment import EnvironmentSimulation -from src.utils import AnyToPrimitive class FlightSimulation(RocketSimulation, EnvironmentSimulation): + """ + Flight simulation view that handles dynamically encoded RocketPy Flight attributes. + + Inherits from both RocketSimulation and EnvironmentSimulation, and adds flight-specific + attributes. Uses the new rocketpy_encoder which may return different attributes based + on the actual RocketPy Flight object. The model allows extra fields to accommodate + any new attributes that might be encoded. + """ + + model_config = ConfigDict(extra='allow', arbitrary_types_allowed=True) + message: str = "Flight successfully simulated" - name: Optional[str] = None - max_time: Optional[int] = None - min_time_step: Optional[int] = None - max_time_step: Optional[AnyToPrimitive] = None - equations_of_motion: Optional[str] = None - heading: Optional[int] = None - inclination: Optional[int] = None - initial_solution: Optional[list] = None - effective_1rl: Optional[float] = None - effective_2rl: Optional[float] = None - out_of_rail_time: Optional[float] = None - out_of_rail_time_index: Optional[int] = None - parachute_cd_s: Optional[float] = None + + # Core Flight attributes (always present) rail_length: Optional[float] = None - rtol: Optional[float] = None - t: Optional[float] = None - t_final: Optional[float] = None - t_initial: Optional[int] = None + inclination: Optional[float] = None + heading: Optional[float] = None terminate_on_apogee: Optional[bool] = None - time_overshoot: Optional[bool] = None - latitude: Optional[AnyToPrimitive] = None - longitude: Optional[AnyToPrimitive] = None - M1: Optional[AnyToPrimitive] = None - M2: Optional[AnyToPrimitive] = None - M3: Optional[AnyToPrimitive] = None - R1: Optional[AnyToPrimitive] = None - R2: Optional[AnyToPrimitive] = None - R3: Optional[AnyToPrimitive] = None - acceleration: Optional[AnyToPrimitive] = None - aerodynamic_bending_moment: Optional[AnyToPrimitive] = None - aerodynamic_drag: Optional[AnyToPrimitive] = None - aerodynamic_lift: Optional[AnyToPrimitive] = None - aerodynamic_spin_moment: Optional[AnyToPrimitive] = None - alpha1: Optional[AnyToPrimitive] = None - alpha2: Optional[AnyToPrimitive] = None - alpha3: Optional[AnyToPrimitive] = None - altitude: Optional[AnyToPrimitive] = None - angle_of_attack: Optional[AnyToPrimitive] = None - apogee: Optional[AnyToPrimitive] = None - apogee_freestream_speed: Optional[AnyToPrimitive] = None - apogee_state: Optional[AnyToPrimitive] = None - apogee_time: Optional[AnyToPrimitive] = None - apogee_x: Optional[AnyToPrimitive] = None - apogee_y: Optional[AnyToPrimitive] = None - atol: Optional[AnyToPrimitive] = None - attitude_angle: Optional[AnyToPrimitive] = None - attitude_frequency_response: Optional[AnyToPrimitive] = None - attitude_vector_x: Optional[AnyToPrimitive] = None - attitude_vector_y: Optional[AnyToPrimitive] = None - attitude_vector_z: Optional[AnyToPrimitive] = None - ax: Optional[AnyToPrimitive] = None - ay: Optional[AnyToPrimitive] = None - az: Optional[AnyToPrimitive] = None - bearing: Optional[AnyToPrimitive] = None - drag_power: Optional[AnyToPrimitive] = None - drift: Optional[AnyToPrimitive] = None - dynamic_pressure: Optional[AnyToPrimitive] = None - e0: Optional[AnyToPrimitive] = None - e1: Optional[AnyToPrimitive] = None - e2: Optional[AnyToPrimitive] = None - e3: Optional[AnyToPrimitive] = None - free_stream_speed: Optional[AnyToPrimitive] = None - frontal_surface_wind: Optional[AnyToPrimitive] = None - function_evaluations: Optional[AnyToPrimitive] = None - function_evaluations_per_time_step: Optional[AnyToPrimitive] = None - horizontal_speed: Optional[AnyToPrimitive] = None - impact_state: Optional[AnyToPrimitive] = None - impact_velocity: Optional[AnyToPrimitive] = None - initial_stability_margin: Optional[AnyToPrimitive] = None - kinetic_energy: Optional[AnyToPrimitive] = None - lateral_attitude_angle: Optional[AnyToPrimitive] = None - lateral_surface_wind: Optional[AnyToPrimitive] = None - mach_number: Optional[AnyToPrimitive] = None - max_acceleration: Optional[AnyToPrimitive] = None - max_acceleration_power_off: Optional[AnyToPrimitive] = None - max_acceleration_power_off_time: Optional[AnyToPrimitive] = None - max_acceleration_power_on: Optional[AnyToPrimitive] = None - max_acceleration_power_on_time: Optional[AnyToPrimitive] = None - max_acceleration_time: Optional[AnyToPrimitive] = None - max_dynamic_pressure: Optional[AnyToPrimitive] = None - max_dynamic_pressure_time: Optional[AnyToPrimitive] = None - max_mach_number: Optional[AnyToPrimitive] = None - max_mach_number_time: Optional[AnyToPrimitive] = None - max_rail_button1_normal_force: Optional[AnyToPrimitive] = None - max_rail_button1_shear_force: Optional[AnyToPrimitive] = None - max_rail_button2_normal_force: Optional[AnyToPrimitive] = None - max_rail_button2_shear_force: Optional[AnyToPrimitive] = None - max_reynolds_number: Optional[AnyToPrimitive] = None - max_reynolds_number_time: Optional[AnyToPrimitive] = None - max_speed: Optional[AnyToPrimitive] = None - max_speed_time: Optional[AnyToPrimitive] = None - max_stability_margin: Optional[AnyToPrimitive] = None - max_stability_margin_time: Optional[AnyToPrimitive] = None - max_total_pressure: Optional[AnyToPrimitive] = None - max_total_pressure_time: Optional[AnyToPrimitive] = None - min_stability_margin: Optional[AnyToPrimitive] = None - min_stability_margin_time: Optional[AnyToPrimitive] = None - omega1_frequency_response: Optional[AnyToPrimitive] = None - omega2_frequency_response: Optional[AnyToPrimitive] = None - omega3_frequency_response: Optional[AnyToPrimitive] = None - out_of_rail_stability_margin: Optional[AnyToPrimitive] = None - out_of_rail_state: Optional[AnyToPrimitive] = None - out_of_rail_velocity: Optional[AnyToPrimitive] = None - parachute_events: Optional[AnyToPrimitive] = None - path_angle: Optional[AnyToPrimitive] = None - phi: Optional[AnyToPrimitive] = None - potential_energy: Optional[AnyToPrimitive] = None - psi: Optional[AnyToPrimitive] = None - rail_button1_normal_force: Optional[AnyToPrimitive] = None - rail_button1_shear_force: Optional[AnyToPrimitive] = None - rail_button2_normal_force: Optional[AnyToPrimitive] = None - rail_button2_shear_force: Optional[AnyToPrimitive] = None - reynolds_number: Optional[AnyToPrimitive] = None - rotational_energy: Optional[AnyToPrimitive] = None - solution: Optional[AnyToPrimitive] = None - solution_array: Optional[AnyToPrimitive] = None - speed: Optional[AnyToPrimitive] = None - stability_margin: Optional[AnyToPrimitive] = None - static_margin: Optional[AnyToPrimitive] = None - stream_velocity_x: Optional[AnyToPrimitive] = None - stream_velocity_y: Optional[AnyToPrimitive] = None - stream_velocity_z: Optional[AnyToPrimitive] = None - theta: Optional[AnyToPrimitive] = None - thrust_power: Optional[AnyToPrimitive] = None - time: Optional[AnyToPrimitive] = None - time_steps: Optional[AnyToPrimitive] = None - total_energy: Optional[AnyToPrimitive] = None - total_pressure: Optional[AnyToPrimitive] = None - translational_energy: Optional[AnyToPrimitive] = None - vx: Optional[AnyToPrimitive] = None - vy: Optional[AnyToPrimitive] = None - vz: Optional[AnyToPrimitive] = None - w1: Optional[AnyToPrimitive] = None - w2: Optional[AnyToPrimitive] = None - w3: Optional[AnyToPrimitive] = None - x: Optional[AnyToPrimitive] = None - x_impact: Optional[AnyToPrimitive] = None - y: Optional[AnyToPrimitive] = None - y_impact: Optional[AnyToPrimitive] = None - y_sol: Optional[AnyToPrimitive] = None - z: Optional[AnyToPrimitive] = None - z_impact: Optional[AnyToPrimitive] = None - flight_phases: Optional[AnyToPrimitive] = None + initial_solution: Optional[list] = None + rocket: Optional[RocketSimulation] = None + environment: Optional[EnvironmentSimulation] = None + + # Key Flight Function attributes (discretized by rocketpy_encoder, serialized by RocketPyEncoder) + + # Position and trajectory + latitude: Optional[Any] = None + longitude: Optional[Any] = None + altitude: Optional[Any] = None + x: Optional[Any] = None + y: Optional[Any] = None + z: Optional[Any] = None + + # Velocity components + vx: Optional[Any] = None + vy: Optional[Any] = None + vz: Optional[Any] = None + speed: Optional[Any] = None + + # Key flight metrics + apogee: Optional[Any] = None + apogee_time: Optional[Any] = None + apogee_x: Optional[Any] = None + apogee_y: Optional[Any] = None + x_impact: Optional[Any] = None + y_impact: Optional[Any] = None + z_impact: Optional[Any] = None + impact_velocity: Optional[Any] = None + + # Acceleration and forces + acceleration: Optional[Any] = None + max_acceleration: Optional[Any] = None + max_acceleration_time: Optional[Any] = None + aerodynamic_drag: Optional[Any] = None + aerodynamic_lift: Optional[Any] = None + + # Flight dynamics + mach_number: Optional[Any] = None + max_mach_number: Optional[Any] = None + max_mach_number_time: Optional[Any] = None + angle_of_attack: Optional[Any] = None + dynamic_pressure: Optional[Any] = None + max_dynamic_pressure: Optional[Any] = None + + # Time and simulation data + time: Optional[Any] = None + solution: Optional[Any] = None + + # Function attributes (discretized by rocketpy_encoder, serialized by RocketPyEncoder) + angular_position: Optional[Any] = None + attitude_angle: Optional[Any] = None + attitude_vector_x: Optional[Any] = None + attitude_vector_y: Optional[Any] = None + attitude_vector_z: Optional[Any] = None + trajectory: Optional[Any] = None + velocity: Optional[Any] = None + acceleration_power_on: Optional[Any] = None + acceleration_power_off: Optional[Any] = None + stream_velocity: Optional[Any] = None + free_stream_speed: Optional[Any] = None + apogee_freestream_speed: Optional[Any] = None + reynolds_number: Optional[Any] = None + total_pressure: Optional[Any] = None + rail_button_normal_force: Optional[Any] = None + max_rail_button_normal_force: Optional[Any] = None + rail_button_shear_force: Optional[Any] = None + max_rail_button_shear_force: Optional[Any] = None + rotational_energy: Optional[Any] = None + translational_energy: Optional[Any] = None + kinetic_energy: Optional[Any] = None + potential_energy: Optional[Any] = None + total_energy: Optional[Any] = None + thrust_power: Optional[Any] = None + drag_power: Optional[Any] = None + drift: Optional[Any] = None class FlightView(FlightModel): diff --git a/src/views/motor.py b/src/views/motor.py index 9f73a17..26ae176 100644 --- a/src/views/motor.py +++ b/src/views/motor.py @@ -1,73 +1,70 @@ -from typing import List, Optional -from pydantic import BaseModel +from typing import Optional, Any +from pydantic import ConfigDict from src.views.interface import ApiBaseView from src.models.motor import MotorModel -from src.utils import AnyToPrimitive -class MotorSimulation(BaseModel): +class MotorSimulation(ApiBaseView): + """ + Motor simulation view that handles dynamically encoded RocketPy Motor attributes. + + Uses the new rocketpy_encoder which may return different attributes based on the + actual RocketPy Motor object. The model allows extra fields to accommodate + any new attributes that might be encoded. + """ + + model_config = ConfigDict(extra='allow', arbitrary_types_allowed=True) + message: str = "Motor successfully simulated" - average_thrust: Optional[float] = None - burn_duration: Optional[float] = None - burn_out_time: Optional[float] = None + + # Core Motor attributes (always present) burn_start_time: Optional[float] = None - center_of_dry_mass_position: Optional[float] = None - coordinate_system_orientation: str = 'nozzle_to_combustion_chamber' - dry_I_11: Optional[float] = None - dry_I_12: Optional[float] = None - dry_I_13: Optional[float] = None - dry_I_22: Optional[float] = None - dry_I_23: Optional[float] = None - dry_I_33: Optional[float] = None + burn_out_time: Optional[float] = None dry_mass: Optional[float] = None - grain_burn_out: Optional[float] = None - grain_density: Optional[float] = None - grain_initial_height: Optional[float] = None - grain_initial_inner_radius: Optional[float] = None - grain_initial_mass: Optional[float] = None - grain_initial_volume: Optional[float] = None + dry_inertia: Optional[tuple] = None + center_of_dry_mass_position: Optional[float] = None + grains_center_of_mass_position: Optional[float] = None grain_number: Optional[int] = None + grain_density: Optional[float] = None grain_outer_radius: Optional[float] = None - grain_separation: Optional[float] = None - grains_center_of_mass_position: Optional[float] = None - interpolate: Optional[str] = None - max_thrust: Optional[float] = None - max_thrust_time: Optional[float] = None - nozzle_position: Optional[float] = None + grain_initial_inner_radius: Optional[float] = None + grain_initial_height: Optional[float] = None nozzle_radius: Optional[float] = None - propellant_initial_mass: Optional[float] = None - throat_area: Optional[float] = None throat_radius: Optional[float] = None - thrust_source: Optional[List[List[float]]] = None - total_impulse: Optional[float] = None - Kn: Optional[AnyToPrimitive] = None - I_11: Optional[AnyToPrimitive] = None - I_12: Optional[AnyToPrimitive] = None - I_13: Optional[AnyToPrimitive] = None - I_22: Optional[AnyToPrimitive] = None - I_23: Optional[AnyToPrimitive] = None - I_33: Optional[AnyToPrimitive] = None - burn_area: Optional[AnyToPrimitive] = None - burn_rate: Optional[AnyToPrimitive] = None - burn_time: Optional[AnyToPrimitive] = None - center_of_mass: Optional[AnyToPrimitive] = None - center_of_propellant_mass: Optional[AnyToPrimitive] = None - exhaust_velocity: Optional[AnyToPrimitive] = None - grain_height: Optional[AnyToPrimitive] = None - grain_volume: Optional[AnyToPrimitive] = None - grain_inner_radius: Optional[AnyToPrimitive] = None - mass_flow_rate: Optional[AnyToPrimitive] = None - propellant_I_11: Optional[AnyToPrimitive] = None - propellant_I_12: Optional[AnyToPrimitive] = None - propellant_I_13: Optional[AnyToPrimitive] = None - propellant_I_22: Optional[AnyToPrimitive] = None - propellant_I_23: Optional[AnyToPrimitive] = None - propellant_I_33: Optional[AnyToPrimitive] = None - propellant_mass: Optional[AnyToPrimitive] = None - reshape_thrust_curve: Optional[AnyToPrimitive] = None - total_mass: Optional[AnyToPrimitive] = None - total_mass_flow_rate: Optional[AnyToPrimitive] = None - thrust: Optional[AnyToPrimitive] = None + nozzle_position: Optional[float] = None + coordinate_system_orientation: Optional[str] = None + motor_kind: Optional[str] = None + interpolate: Optional[str] = None + + # Function attributes (discretized by rocketpy_encoder, serialized by RocketPyEncoder) + Kn: Optional[Any] = None + I_11: Optional[Any] = None + I_12: Optional[Any] = None + I_13: Optional[Any] = None + I_22: Optional[Any] = None + I_23: Optional[Any] = None + I_33: Optional[Any] = None + burn_area: Optional[Any] = None + burn_rate: Optional[Any] = None + burn_time: Optional[Any] = None + center_of_mass: Optional[Any] = None + center_of_propellant_mass: Optional[Any] = None + exhaust_velocity: Optional[Any] = None + grain_height: Optional[Any] = None + grain_volume: Optional[Any] = None + grain_inner_radius: Optional[Any] = None + mass_flow_rate: Optional[Any] = None + propellant_I_11: Optional[Any] = None + propellant_I_12: Optional[Any] = None + propellant_I_13: Optional[Any] = None + propellant_I_22: Optional[Any] = None + propellant_I_23: Optional[Any] = None + propellant_I_33: Optional[Any] = None + propellant_mass: Optional[Any] = None + reshape_thrust_curve: Optional[Any] = None + total_mass: Optional[Any] = None + total_mass_flow_rate: Optional[Any] = None + thrust: Optional[Any] = None class MotorView(MotorModel): diff --git a/src/views/rocket.py b/src/views/rocket.py index becee4a..f6cb474 100644 --- a/src/views/rocket.py +++ b/src/views/rocket.py @@ -1,41 +1,52 @@ -from typing import Optional +from typing import Optional, Any +from pydantic import ConfigDict from src.models.rocket import RocketModel from src.views.interface import ApiBaseView from src.views.motor import MotorView, MotorSimulation -from src.utils import AnyToPrimitive class RocketSimulation(MotorSimulation): + """ + Rocket simulation view that handles dynamically encoded RocketPy Rocket attributes. + + Inherits from MotorSimulation and adds rocket-specific attributes. Uses the new + rocketpy_encoder which may return different attributes based on the actual + RocketPy Rocket object. The model allows extra fields to accommodate any new + attributes that might be encoded. + """ + + model_config = ConfigDict(extra='allow', arbitrary_types_allowed=True) + message: str = "Rocket successfully simulated" - area: Optional[float] = None - coordinate_system_orientation: str = 'tail_to_nose' + + # Core Rocket attributes (always present) + radius: Optional[float] = None + mass: Optional[float] = None + inertia: Optional[tuple] = None + power_off_drag: Optional[float] = None + power_on_drag: Optional[float] = None center_of_mass_without_motor: Optional[float] = None - motor_center_of_dry_mass_position: Optional[float] = None - motor_position: Optional[float] = None - nozzle_position: Optional[float] = None - nozzle_to_cdm: Optional[float] = None - cp_eccentricity_x: Optional[float] = None - cp_eccentricity_y: Optional[float] = None - thrust_eccentricity_x: Optional[float] = None - thrust_eccentricity_y: Optional[float] = None - I_11_without_motor: Optional[AnyToPrimitive] = None - I_12_without_motor: Optional[AnyToPrimitive] = None - I_13_without_motor: Optional[AnyToPrimitive] = None - I_22_without_motor: Optional[AnyToPrimitive] = None - I_23_without_motor: Optional[AnyToPrimitive] = None - I_33_without_motor: Optional[AnyToPrimitive] = None - check_parachute_trigger: Optional[AnyToPrimitive] = None - com_to_cdm_function: Optional[AnyToPrimitive] = None - cp_position: Optional[AnyToPrimitive] = None - motor_center_of_mass_position: Optional[AnyToPrimitive] = None - nozzle_gyration_tensor: Optional[AnyToPrimitive] = None - power_off_drag: Optional[AnyToPrimitive] = None - power_on_drag: Optional[AnyToPrimitive] = None - reduced_mass: Optional[AnyToPrimitive] = None - stability_margin: Optional[AnyToPrimitive] = None - static_margin: Optional[AnyToPrimitive] = None - thrust_to_weight: Optional[AnyToPrimitive] = None - total_lift_coeff_der: Optional[AnyToPrimitive] = None + coordinate_system_orientation: Optional[str] = None + parachutes: Optional[list] = None + motor: Optional[MotorSimulation] = None + + # Function attributes (discretized by rocketpy_encoder, serialized by RocketPyEncoder) + I_11_without_motor: Optional[Any] = None + I_12_without_motor: Optional[Any] = None + I_13_without_motor: Optional[Any] = None + I_22_without_motor: Optional[Any] = None + I_23_without_motor: Optional[Any] = None + I_33_without_motor: Optional[Any] = None + check_parachute_trigger: Optional[Any] = None + com_to_cdm_function: Optional[Any] = None + cp_position: Optional[Any] = None + motor_center_of_mass_position: Optional[Any] = None + nozzle_gyration_tensor: Optional[Any] = None + reduced_mass: Optional[Any] = None + stability_margin: Optional[Any] = None + static_margin: Optional[Any] = None + thrust_to_weight: Optional[Any] = None + total_lift_coeff_der: Optional[Any] = None class RocketView(RocketModel):