|
1 | 1 | # pylint: disable=too-many-lines |
2 | 2 | import math |
| 3 | +import time |
3 | 4 | import warnings |
4 | 5 | from copy import deepcopy |
5 | 6 | from functools import cached_property |
@@ -4039,7 +4040,202 @@ def export_kml( |
4039 | 4040 | color=color, |
4040 | 4041 | altitude_mode=altitude_mode, |
4041 | 4042 | ) |
| 4043 | + def animate_trajectory(self, file_name, start=0, stop=None, time_step=0.1, **kwargs): |
| 4044 | + """ |
| 4045 | + 6-DOF Animation of the flight trajectory using Vedo. |
| 4046 | +
|
| 4047 | + Parameters |
| 4048 | + ---------- |
| 4049 | + file_name : str |
| 4050 | + 3D object file representing your rocket, usually in .stl format. |
| 4051 | + Example: "rocket.stl" |
| 4052 | + start : int, float, optional |
| 4053 | + Time for starting animation, in seconds. Default is 0. |
| 4054 | + stop : int, float, optional |
| 4055 | + Time for ending animation, in seconds. If None, uses self.t_final. |
| 4056 | + Default is None. |
| 4057 | + time_step : float, optional |
| 4058 | + Time step for data interpolation in the animation. Default is 0.1. |
| 4059 | + **kwargs : dict, optional |
| 4060 | + Additional keyword arguments to be passed to vedo.Plotter.show(). |
| 4061 | + Common arguments: |
| 4062 | + - azimuth (float): Rotation in degrees around the vertical axis. |
| 4063 | + - elevation (float): Rotation in degrees above the horizon. |
| 4064 | + - roll (float): Rotation in degrees around the view axis. |
| 4065 | + - zoom (float): Zoom level (default 1). |
| 4066 | +
|
| 4067 | + Returns |
| 4068 | + ------- |
| 4069 | + None |
| 4070 | +
|
| 4071 | + Raises |
| 4072 | + ------ |
| 4073 | + ImportError |
| 4074 | + If the 'vedo' package is not installed. |
| 4075 | + |
| 4076 | + Notes |
| 4077 | + ----- |
| 4078 | + This feature requires the 'vedo' package. Install it with: |
| 4079 | + pip install rocketpy[animation] |
| 4080 | + """ |
| 4081 | + try: |
| 4082 | + from vedo import Box, Line, Mesh, Plotter, settings |
| 4083 | + except ImportError as e: |
| 4084 | + raise ImportError( |
| 4085 | + "The animation feature requires the 'vedo' package. " |
| 4086 | + "Install it with:\n" |
| 4087 | + " pip install rocketpy[animation]\n" |
| 4088 | + "Or directly:\n" |
| 4089 | + " pip install vedo>=2024.5.1" |
| 4090 | + ) from e |
| 4091 | + |
| 4092 | + # Enable interaction if needed |
| 4093 | + try: |
| 4094 | + settings.allow_interaction = True |
| 4095 | + except AttributeError: |
| 4096 | + pass # Not available in newer versions of vedo |
| 4097 | + |
| 4098 | + # Handle stop time |
| 4099 | + if stop is None: |
| 4100 | + stop = self.t_final |
| 4101 | + |
| 4102 | + # Define the world bounds based on trajectory |
| 4103 | + max_x = max(self.x[:, 1]) |
| 4104 | + max_y = max(self.y[:, 1]) |
| 4105 | + # Use simple logic for bounds |
| 4106 | + world = Box( |
| 4107 | + pos=[self.x(start), self.y(start), self.apogee], |
| 4108 | + length=max_x * 2 if max_x != 0 else 1000, |
| 4109 | + width=max_y * 2 if max_y != 0 else 1000, |
| 4110 | + height=self.apogee, |
| 4111 | + ).wireframe() |
| 4112 | + |
| 4113 | + # Load rocket mesh |
| 4114 | + rocket = Mesh(file_name).c("green") |
| 4115 | + rocket.pos(self.x(start), self.y(start), 0).add_trail(n=len(self.x[:, 1])) |
| 4116 | + # Create trail |
| 4117 | + trail_points = [[self.x(t), self.y(t), self.z(t) - self.env.elevation] |
| 4118 | + for t in np.arange(start, stop, time_step)] |
| 4119 | + trail = Line(trail_points, c="k", alpha=0.5) |
| 4120 | + # Setup Plotter |
| 4121 | + plt = Plotter(axes=1, interactive=False) |
| 4122 | + plt.show(world, rocket, __doc__, viewup="z", **kwargs) |
| 4123 | + |
| 4124 | + # Animation Loop |
| 4125 | + for t in np.arange(start, stop, time_step): |
| 4126 | + # Calculate rotation angle and vector from quaternions |
| 4127 | + # Note: This simple rotation logic mimics the old branch. |
| 4128 | + # Ideally, vedo handles orientation via matrix, but we stick |
| 4129 | + # to the provided logic for now. |
| 4130 | + |
| 4131 | + # e0 is the scalar part of the quaternion |
| 4132 | + angle = np.arccos(2 * self.e0(t)**2 - 1) |
| 4133 | + k = np.sin(angle / 2) if np.sin(angle / 2) != 0 else 1 |
| 4134 | + |
| 4135 | + # Update position and rotation |
| 4136 | + # Adjusting for ground elevation |
| 4137 | + rocket.pos(self.x(t), self.y(t), self.z(t) - self.env.elevation) |
| 4138 | + rocket.rotate_x(self.e1(t) / k) |
| 4139 | + rocket.rotate_y(self.e2(t) / k) |
| 4140 | + rocket.rotate_z(self.e3(t) / k) |
| 4141 | + |
| 4142 | + # update the scene |
| 4143 | + plt.show(world, rocket, trail) |
| 4144 | + |
| 4145 | + # slow down to make animation visible |
| 4146 | + start_pause = time.time() |
| 4147 | + while time.time() - start_pause < time_step: |
| 4148 | + plt.render() |
| 4149 | + |
| 4150 | + if getattr(plt, 'escaped', False): |
| 4151 | + break |
| 4152 | + |
| 4153 | + plt.interactive().close() |
| 4154 | + return None |
| 4155 | + |
| 4156 | + def animate_rotate(self, file_name, start=0, stop=None, time_step=0.1, **kwargs): |
| 4157 | + """ |
| 4158 | + Animation of rocket attitude (rotation) during the flight. |
4042 | 4159 |
|
| 4160 | + Parameters |
| 4161 | + ---------- |
| 4162 | + file_name : str |
| 4163 | + 3D object file representing your rocket, usually in .stl format. |
| 4164 | + start : int, float, optional |
| 4165 | + Time for starting animation, in seconds. Default is 0. |
| 4166 | + stop : int, float, optional |
| 4167 | + Time for ending animation, in seconds. If None, uses self.t_final. |
| 4168 | + Default is None. |
| 4169 | + time_step : float, optional |
| 4170 | + Time step for data interpolation. Default is 0.1. |
| 4171 | + **kwargs : dict, optional |
| 4172 | + Additional keyword arguments to be passed to vedo.Plotter.show(). |
| 4173 | + Common arguments: |
| 4174 | + - azimuth (float): Rotation in degrees around the vertical axis. |
| 4175 | + - elevation (float): Rotation in degrees above the horizon. |
| 4176 | + - roll (float): Rotation in degrees around the view axis. |
| 4177 | + - zoom (float): Zoom level (default 1). |
| 4178 | + |
| 4179 | + Returns |
| 4180 | + ------- |
| 4181 | + None |
| 4182 | +
|
| 4183 | + Raises |
| 4184 | + ------ |
| 4185 | + ImportError |
| 4186 | + If the 'vedo' package is not installed. |
| 4187 | + """ |
| 4188 | + try: |
| 4189 | + from vedo import Box, Mesh, Plotter, settings |
| 4190 | + except ImportError as e: |
| 4191 | + raise ImportError( |
| 4192 | + "The animation feature requires the 'vedo' package. " |
| 4193 | + "Install it with:\n" |
| 4194 | + " pip install rocketpy[animation]\n" |
| 4195 | + ) from e |
| 4196 | + |
| 4197 | + # Enable interaction if needed |
| 4198 | + try: |
| 4199 | + settings.allow_interaction = True |
| 4200 | + except AttributeError: |
| 4201 | + pass # Not available in newer versions of vedo |
| 4202 | + |
| 4203 | + if stop is None: |
| 4204 | + stop = self.t_final |
| 4205 | + |
| 4206 | + # Smaller box for rotation view |
| 4207 | + world = Box( |
| 4208 | + pos=[self.x(start), self.y(start), self.apogee], |
| 4209 | + length=max(self.x[:, 1]) * 0.2, |
| 4210 | + width=max(self.y[:, 1]) * 0.2, |
| 4211 | + height=self.apogee * 0.1, |
| 4212 | + ).wireframe() |
| 4213 | + |
| 4214 | + rocket = Mesh(file_name).c("green") |
| 4215 | + # Initialize at origin relative to view |
| 4216 | + rocket.pos(self.x(start), self.y(start), 0).add_trail(n=len(self.x[:, 1])) |
| 4217 | + |
| 4218 | + plt = Plotter(axes=1, interactive=False) |
| 4219 | + plt.show(world, rocket, __doc__, viewup="z", **kwargs) |
| 4220 | + |
| 4221 | + for t in np.arange(start, stop, time_step): |
| 4222 | + angle = np.arccos(2 * self.e0(t)**2 - 1) |
| 4223 | + k = np.sin(angle / 2) if np.sin(angle / 2) != 0 else 1 |
| 4224 | + |
| 4225 | + # Keep position static (relative start) to observe only rotation |
| 4226 | + rocket.pos(self.x(start), self.y(start), 0) |
| 4227 | + rocket.rotate_x(self.e1(t) / k) |
| 4228 | + rocket.rotate_y(self.e2(t) / k) |
| 4229 | + rocket.rotate_z(self.e3(t) / k) |
| 4230 | + |
| 4231 | + plt.show(world, rocket) |
| 4232 | + |
| 4233 | + if getattr(plt, 'escaped', False): |
| 4234 | + break |
| 4235 | + |
| 4236 | + plt.interactive().close() |
| 4237 | + return None |
| 4238 | + |
4043 | 4239 | def info(self): |
4044 | 4240 | """Prints out a summary of the data available about the Flight.""" |
4045 | 4241 | self.prints.all() |
|
0 commit comments