55
66from rocketpy .simulation .flight import Flight as RocketPyFlight
77from rocketpy ._encoders import RocketPyEncoder , RocketPyDecoder
8+ from rocketpy .mathutils .function import Function
89from rocketpy .motors .solid_motor import SolidMotor
910from rocketpy .motors .liquid_motor import LiquidMotor
1011from rocketpy .motors .hybrid_motor import HybridMotor
12+ from rocketpy import (
13+ LevelBasedTank ,
14+ MassBasedTank ,
15+ UllageBasedTank ,
16+ )
1117from rocketpy .rocket .aero_surface import (
1218 NoseCone as RocketPyNoseCone ,
1319 TrapezoidalFins as RocketPyTrapezoidalFins ,
2733 Tail ,
2834 Parachute ,
2935)
36+ from src .models .sub .tanks import MotorTank , TankFluids , TankKinds
3037from src .views .flight import FlightSimulation
3138from src .views .rocket import RocketSimulation
3239from src .views .motor import MotorSimulation
@@ -169,22 +176,27 @@ def _extract_motor(motor) -> MotorModel:
169176 ),
170177 }
171178
179+ grain_fields = {
180+ "grain_number" : motor .grain_number ,
181+ "grain_density" : motor .grain_density ,
182+ "grain_outer_radius" : motor .grain_outer_radius ,
183+ "grain_initial_inner_radius" : (motor .grain_initial_inner_radius ),
184+ "grain_initial_height" : motor .grain_initial_height ,
185+ "grain_separation" : motor .grain_separation ,
186+ "grains_center_of_mass_position" : (
187+ motor .grains_center_of_mass_position
188+ ),
189+ "throat_radius" : motor .throat_radius ,
190+ }
191+
172192 match kind :
173- case MotorKinds .SOLID | MotorKinds .HYBRID :
174- data |= {
175- "grain_number" : motor .grain_number ,
176- "grain_density" : motor .grain_density ,
177- "grain_outer_radius" : (motor .grain_outer_radius ),
178- "grain_initial_inner_radius" : (
179- motor .grain_initial_inner_radius
180- ),
181- "grain_initial_height" : (motor .grain_initial_height ),
182- "grain_separation" : motor .grain_separation ,
183- "grains_center_of_mass_position" : (
184- motor .grains_center_of_mass_position
185- ),
186- "throat_radius" : motor .throat_radius ,
187- }
193+ case MotorKinds .SOLID :
194+ data |= grain_fields
195+ case MotorKinds .HYBRID :
196+ data |= grain_fields
197+ data ["tanks" ] = FlightService ._extract_tanks (motor )
198+ case MotorKinds .LIQUID :
199+ data ["tanks" ] = FlightService ._extract_tanks (motor )
188200 case MotorKinds .GENERIC :
189201 data |= {
190202 "chamber_radius" : getattr (motor , "chamber_radius" , None ),
@@ -202,6 +214,83 @@ def _extract_motor(motor) -> MotorModel:
202214
203215 return MotorModel (** data )
204216
217+ @staticmethod
218+ def _to_float (value ) -> float :
219+ """Extract a plain float from a RocketPy Function or scalar."""
220+ match value :
221+ case Function ():
222+ return float (value (0 ))
223+ case _:
224+ return float (value )
225+
226+ @staticmethod
227+ def _extract_tanks (motor ) -> list [MotorTank ]:
228+ tanks : list [MotorTank ] = []
229+ for entry in motor .positioned_tanks :
230+ tank , position = entry ["tank" ], entry ["position" ]
231+
232+ match tank :
233+ case LevelBasedTank ():
234+ tank_kind = TankKinds .LEVEL
235+ case MassBasedTank ():
236+ tank_kind = TankKinds .MASS
237+ case UllageBasedTank ():
238+ tank_kind = TankKinds .ULLAGE
239+ case _:
240+ tank_kind = TankKinds .MASS_FLOW
241+
242+ geometry = [
243+ (bounds , float (func (0 )))
244+ for bounds , func in tank .geometry .geometry .items ()
245+ ]
246+
247+ data : dict = {
248+ "geometry" : geometry ,
249+ "gas" : TankFluids (
250+ name = tank .gas .name ,
251+ density = tank .gas .density ,
252+ ),
253+ "liquid" : TankFluids (
254+ name = tank .liquid .name ,
255+ density = tank .liquid .density ,
256+ ),
257+ "flux_time" : tank .flux_time ,
258+ "position" : position ,
259+ "discretize" : tank .discretize ,
260+ "tank_kind" : tank_kind ,
261+ "name" : tank .name ,
262+ }
263+
264+ _f = FlightService ._to_float
265+ match tank_kind :
266+ case TankKinds .LEVEL :
267+ data ["liquid_height" ] = _f (tank .liquid_height )
268+ case TankKinds .MASS :
269+ data ["liquid_mass" ] = _f (tank .liquid_mass )
270+ data ["gas_mass" ] = _f (tank .gas_mass )
271+ case TankKinds .MASS_FLOW :
272+ data |= {
273+ "gas_mass_flow_rate_in" : _f (
274+ tank .gas_mass_flow_rate_in
275+ ),
276+ "gas_mass_flow_rate_out" : _f (
277+ tank .gas_mass_flow_rate_out
278+ ),
279+ "liquid_mass_flow_rate_in" : _f (
280+ tank .liquid_mass_flow_rate_in
281+ ),
282+ "liquid_mass_flow_rate_out" : _f (
283+ tank .liquid_mass_flow_rate_out
284+ ),
285+ "initial_liquid_mass" : (tank .initial_liquid_mass ),
286+ "initial_gas_mass" : (tank .initial_gas_mass ),
287+ }
288+ case TankKinds .ULLAGE :
289+ data ["ullage" ] = _f (tank .ullage )
290+
291+ tanks .append (MotorTank (** data ))
292+ return tanks
293+
205294 @staticmethod
206295 def _drag_to_list (fn ) -> list :
207296 match getattr (fn , "source" , None ):
@@ -293,6 +382,8 @@ def _extract_rocket(rocket, motor_model: MotorModel) -> RocketModel:
293382 rocket .I_33_without_motor ,
294383 )
295384
385+ # Schema requires at least one Fins entry; n=0 means
386+ # no physical fins (safe for downstream aero calculations).
296387 default_fins = [
297388 Fins (
298389 fins_kind = "trapezoidal" ,
@@ -359,7 +450,7 @@ def _extract_flight(
359450 )
360451
361452 # ------------------------------------------------------------------
362- # Simulation & binary
453+ # Simulation & export
363454 # ------------------------------------------------------------------
364455
365456 def get_flight_simulation (self ) -> FlightSimulation :
@@ -432,7 +523,7 @@ def generate_notebook(flight_id: str) -> dict:
432523 "metadata" : {},
433524 "outputs" : [],
434525 "source" : [
435- "from rocketpy.utilities import " " load_from_rpy\n " ,
526+ "from rocketpy.utilities import load_from_rpy\n " ,
436527 "import matplotlib\n " ,
437528 ],
438529 },
0 commit comments