|
23 | 23 | from rocketpy.rocket.components import Components |
24 | 24 | from rocketpy.rocket.parachute import Parachute |
25 | 25 | from rocketpy.tools import parallel_axis_theorem_from_com |
| 26 | +from functools import cached_property |
| 27 | +from rocketpy.mathutils.function import funcify_method |
26 | 28 |
|
27 | | -class BaseRocket: |
28 | | - """Base class for a rocket model with minimal attributes and methods.""" |
29 | | - def __init__(self, mass): |
30 | | - self.mass = mass |
31 | | - self.motor = None |
32 | | - def add_motor(self, motor): |
33 | | - self.motor = motor |
34 | | - self.evaluate_total_mass() |
35 | | - def evaluate_total_mass(self): |
36 | | - if self.motor: |
37 | | - return self.mass + self.motor.total_mass |
38 | | - return self.mass |
39 | | - |
40 | | - |
41 | | -class PointMassRocket(BaseRocket): |
42 | | - """Rocket modeled as a point mass for 3-DOF simulations.""" |
43 | | - |
44 | | - def __init__(self, mass, drag_coefficient=0.4, radius=0.05): |
45 | | - super().__init__(mass) |
46 | | - |
47 | | - # Basic configuration |
48 | | - self.drag_coefficient = drag_coefficient |
49 | | - self.radius = radius # in meters |
50 | | - self.area = math.pi * self.radius**2 |
51 | | - |
52 | | - # Coordinate system configuration |
53 | | - self.coordinate_system_orientation = "tail_to_nose" |
54 | | - self.center_of_dry_mass_position = 0 # arbitrary for point mass |
55 | | - self.dry_mass = mass # dry_mass = structure + dry motor, here it's same as base mass |
56 | | - self.nozzle_position = 0 # or another reference point like -1 * length/2 |
57 | | - |
58 | | - # Components |
59 | | - self.parachutes = [] |
60 | | - self._controllers = [] |
61 | | - self.air_brakes = [] |
62 | | - self.sensors = Components() |
63 | | - self.aerodynamic_surfaces = Components() |
64 | | - self.rail_buttons = Components() |
65 | | - self.surfaces_cp_to_cdm = {} |
66 | | - |
67 | | - # Drag models |
68 | | - self.power_on_drag = Function( |
69 | | - drag_coefficient, |
70 | | - "Mach Number", |
71 | | - "Power-On Drag Coefficient", |
72 | | - interpolation="constant" |
73 | | - ) |
74 | | - self.power_off_drag = Function( |
75 | | - drag_coefficient * 1.2, |
76 | | - "Mach Number", |
77 | | - "Power-Off Drag Coefficient", |
78 | | - interpolation="constant" |
79 | | - ) |
80 | | - def add_parachute( |
81 | | - self, name, cd_s, trigger, sampling_rate=100, lag=0, noise=(0, 0, 0) |
82 | | - ): |
83 | | - parachute = Parachute(name, cd_s, trigger, sampling_rate, lag, noise) |
84 | | - self.parachutes.append(parachute) |
85 | | - self.total_mass = None |
86 | | - self.evaluate_total_mass() |
87 | | - return self.parachutes[-1] |
88 | | - |
89 | | - def evaluate_total_mass(self): |
90 | | - """Returns Function of total mass (dry + motor).""" |
91 | | - if self.motor is None: |
92 | | - print("Please associate this rocket with a motor!") |
93 | | - return False |
94 | | - |
95 | | - motor_mass_func = ( |
96 | | - self.motor.total_mass if hasattr(self.motor.total_mass, "get_value_opt") |
97 | | - else Function(lambda t: self.motor.total_mass) |
98 | | - ) |
99 | | - |
100 | | - self.total_mass = Function( |
101 | | - lambda t: self.mass + motor_mass_func(t), |
102 | | - inputs="Time (s)", |
103 | | - outputs="Total Mass (Rocket + Motor + Propellant) (kg)", |
104 | | - title="Total Mass (Rocket + Motor + Propellant)" |
105 | | - ) |
106 | | - return self.total_mass |
107 | | - |
108 | | - |
109 | 29 | # pylint: disable=too-many-instance-attributes, too-many-public-methods, too-many-instance-attributes |
110 | 30 | class Rocket: |
111 | 31 | """Keeps rocket information. |
@@ -2085,3 +2005,119 @@ def from_dict(cls, data): |
2085 | 2005 | ) |
2086 | 2006 |
|
2087 | 2007 | return rocket |
| 2008 | + |
| 2009 | +class PointMassRocket(Rocket): |
| 2010 | + """Rocket modeled as a point mass for 3-DOF simulations.""" |
| 2011 | + |
| 2012 | + def __init__(self, mass, radius=0.05): |
| 2013 | + self, |
| 2014 | + radius: float = 0, |
| 2015 | + mass: float = 0, |
| 2016 | + center_of_mass: float = 0, |
| 2017 | + power_off_drag = 0, |
| 2018 | + power_on_drag = 0, |
| 2019 | + super().__init__( |
| 2020 | + radius=radius, |
| 2021 | + mass=mass, |
| 2022 | + inertia=(0, 0, 0), |
| 2023 | + center_of_mass_without_motor=center_of_mass, |
| 2024 | + power_off_drag=power_off_drag, |
| 2025 | + power_on_drag=power_on_drag, |
| 2026 | + ) |
| 2027 | + @cached_property |
| 2028 | + @funcify_method("Time (s)", "I_xx (kg·m²)") |
| 2029 | + def I_11(self) -> Function: |
| 2030 | + """Returns the moment of inertia around the x-axis for a point mass (always 0).""" |
| 2031 | + return Function(0) |
| 2032 | + |
| 2033 | + @cached_property |
| 2034 | + @funcify_method("Time (s)", "I_22 (kg·m²)") |
| 2035 | + def I_22(self) -> Function: |
| 2036 | + """Returns the moment of inertia around the y-axis for a point mass (always 0).""" |
| 2037 | + return Function(0) |
| 2038 | + |
| 2039 | + @cached_property |
| 2040 | + @funcify_method("Time (s)", "I_33 (kg·m²)") |
| 2041 | + def I_33(self) -> Function: |
| 2042 | + """Returns the moment of inertia around the z-axis for a point mass (always 0).""" |
| 2043 | + return Function(0) |
| 2044 | + |
| 2045 | + @cached_property |
| 2046 | + @funcify_method("Time (s)", "I_12 (kg·m²)") |
| 2047 | + def I_12(self) -> Function: |
| 2048 | + """Returns the product of inertia I_xy for a point mass (always 0).""" |
| 2049 | + return Function(0) |
| 2050 | + |
| 2051 | + @cached_property |
| 2052 | + @funcify_method("Time (s)", "I_13 (kg·m²)") |
| 2053 | + def I_13(self) -> Function: |
| 2054 | + """Returns the product of inertia I_xz for a point mass (always 0).""" |
| 2055 | + return Function(0) |
| 2056 | + |
| 2057 | + @cached_property |
| 2058 | + @funcify_method("Time (s)", "I_23 (kg·m²)") |
| 2059 | + def I_23(self) -> Function: |
| 2060 | + """Returns the product of inertia I_yz for a point mass (always 0).""" |
| 2061 | + return Function(0) |
| 2062 | + |
| 2063 | + @property |
| 2064 | + def dry_I_11(self) -> float: |
| 2065 | + """Returns the dry moment of inertia around the x-axis for a point mass (always 0).""" |
| 2066 | + return 0.0 |
| 2067 | + |
| 2068 | + @property |
| 2069 | + def dry_I_22(self) -> float: |
| 2070 | + """Returns the dry moment of inertia around the y-axis for a point mass (always 0).""" |
| 2071 | + return 0.0 |
| 2072 | + |
| 2073 | + @property |
| 2074 | + def dry_I_33(self) -> float: |
| 2075 | + """Returns the dry moment of inertia around the z-axis for a point mass (always 0).""" |
| 2076 | + return 0.0 |
| 2077 | + |
| 2078 | + @property |
| 2079 | + def dry_I_12(self) -> float: |
| 2080 | + """Returns the dry product of inertia I_xy for a point mass (always 0).""" |
| 2081 | + return 0.0 |
| 2082 | + |
| 2083 | + @property |
| 2084 | + def dry_I_13(self) -> float: |
| 2085 | + """Returns the dry product of inertia I_xz for a point mass (always 0).""" |
| 2086 | + return 0.0 |
| 2087 | + |
| 2088 | + @property |
| 2089 | + def dry_I_23(self) -> float: |
| 2090 | + """Returns the dry product of inertia I_yz for a point mass (always 0).""" |
| 2091 | + return 0.0 |
| 2092 | + |
| 2093 | + def evaluate_inertias(self): |
| 2094 | + """Calculates and returns the rocket's inertias. For a PointMassRocket, these are always zero.""" |
| 2095 | + self.I_11 = self.I_22 = self.I_33 = Function(0) |
| 2096 | + self.I_12 = self.I_13 = self.I_23 = Function(0) |
| 2097 | + return (0, 0, 0, 0, 0, 0) |
| 2098 | + |
| 2099 | + def evaluate_dry_inertias(self): |
| 2100 | + """Calculates and returns the rocket's dry inertias. For a PointMassRocket, these are always zero.""" |
| 2101 | + self.dry_I_11 = self.dry_I_22 = self.dry_I_33 = 0.0 |
| 2102 | + self.dry_I_12 = self.dry_I_13 = self.dry_I_23 = 0.0 |
| 2103 | + return (0, 0, 0, 0, 0, 0) |
| 2104 | + |
| 2105 | + @property |
| 2106 | + def center_of_mass(self) -> Function: |
| 2107 | + """Returns the center of mass for a PointMassRocket. |
| 2108 | + If a motor is attached, this will be the motor's center of mass. |
| 2109 | + Otherwise, it will be the `center_of_mass_without_motor`. |
| 2110 | + """ |
| 2111 | + if self.motor and not isinstance(self.motor, EmptyMotor): |
| 2112 | + return self.motor_center_of_mass_position |
| 2113 | + return Function(self.center_of_mass_without_motor) |
| 2114 | + |
| 2115 | + @property |
| 2116 | + def center_of_dry_mass_position(self) -> float: |
| 2117 | + """Returns the center of dry mass position for a PointMassRocket. |
| 2118 | + If a motor is attached, this will be the motor's center of dry mass position. |
| 2119 | + Otherwise, it will be the `center_of_mass_without_motor`. |
| 2120 | + """ |
| 2121 | + if self.motor and not isinstance(self.motor, EmptyMotor): |
| 2122 | + return self.motor_center_of_dry_mass_position |
| 2123 | + return self.center_of_mass_without_motor |
0 commit comments