Skip to content

Commit 0643505

Browse files
committed
ENH: individual fin in rocket draw
1 parent c1ae511 commit 0643505

1 file changed

Lines changed: 56 additions & 40 deletions

File tree

rocketpy/plots/rocket_plots.py

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import matplotlib.pyplot as plt
22
import numpy as np
33

4+
from rocketpy.mathutils.vector_matrix import Vector
45
from rocketpy.motors import EmptyMotor, HybridMotor, LiquidMotor, SolidMotor
56
from rocketpy.rocket.aero_surface import Fin, Fins, NoseCone, Tail
6-
from rocketpy.rocket.aero_surface import Fins, NoseCone, Tail
77
from rocketpy.rocket.aero_surface.generic_surface import GenericSurface
88

99
from .plot_helpers import show_or_save_plot
@@ -178,7 +178,7 @@ def draw(self, vis_args=None, plane="xz", *, filename=None):
178178
and webp (these are the formats supported by matplotlib).
179179
"""
180180

181-
self.__validate_aerodynamic_surfaces()
181+
self.__validate_aerodynamic_surfaces(plane)
182182

183183
if vis_args is None:
184184
vis_args = {
@@ -198,9 +198,9 @@ def draw(self, vis_args=None, plane="xz", *, filename=None):
198198

199199
csys = self.rocket._csys
200200
reverse = csys == 1
201-
self.rocket.aerodynamic_surfaces.sort_by_position(reverse=reverse)
201+
surfaces = self.rocket.aerodynamic_surfaces.sort_by_position(reverse=reverse)
202202

203-
drawn_surfaces = self._draw_aerodynamic_surfaces(ax, vis_args, plane)
203+
drawn_surfaces = self._draw_aerodynamic_surfaces(ax, vis_args, plane, surfaces)
204204
last_radius, last_x = self._draw_tubes(ax, drawn_surfaces, vis_args)
205205
self._draw_motor(last_radius, last_x, ax, vis_args)
206206
self._draw_rail_buttons(ax, vis_args)
@@ -216,13 +216,15 @@ def draw(self, vis_args=None, plane="xz", *, filename=None):
216216
plt.tight_layout()
217217
show_or_save_plot(filename)
218218

219-
def __validate_aerodynamic_surfaces(self):
219+
def __validate_aerodynamic_surfaces(self, plane):
220220
if not self.rocket.aerodynamic_surfaces:
221221
raise ValueError(
222222
"The rocket must have at least one aerodynamic surface to be drawn."
223223
)
224+
if plane != "xz" and plane != "yz":
225+
raise ValueError("The plane must be 'xz' or 'yz'. The default is 'xz'.")
224226

225-
def _draw_aerodynamic_surfaces(self, ax, vis_args, plane):
227+
def _draw_aerodynamic_surfaces(self, ax, vis_args, plane, surfaces):
226228
"""Draws the aerodynamic surfaces and saves the position of the points
227229
of interest for the tubes."""
228230
# List of drawn surfaces with the position of points of interest
@@ -235,15 +237,17 @@ def _draw_aerodynamic_surfaces(self, ax, vis_args, plane):
235237
# diameter changes. The final point of the last surface is the final
236238
# point of the last tube
237239

238-
for surface, position in self.rocket.aerodynamic_surfaces:
240+
for surface, position in surfaces:
239241
if isinstance(surface, NoseCone):
240242
self._draw_nose_cone(ax, surface, position.z, drawn_surfaces, vis_args)
241243
elif isinstance(surface, Tail):
242244
self._draw_tail(ax, surface, position.z, drawn_surfaces, vis_args)
243245
elif isinstance(surface, Fins):
244-
self._draw_fins(ax, surface, position.z, drawn_surfaces, vis_args)
246+
self._draw_fins(
247+
ax, surface, position.z, drawn_surfaces, vis_args, plane
248+
)
245249
elif isinstance(surface, Fin):
246-
self._draw_fin(ax, surface, position.z, drawn_surfaces, vis_args)
250+
self._draw_fin(ax, surface, position, drawn_surfaces, vis_args, plane)
247251
elif isinstance(surface, GenericSurface):
248252
self._draw_generic_surface(
249253
ax, surface, position, drawn_surfaces, vis_args, plane
@@ -306,13 +310,15 @@ def _draw_tail(self, ax, surface, position, drawn_surfaces, vis_args):
306310
# Add the tail to the list of drawn surfaces
307311
drawn_surfaces.append((surface, position, surface.bottom_radius, x_tail[-1]))
308312

309-
def _draw_fins(self, ax, surface, position, drawn_surfaces, vis_args):
313+
def _draw_fins(self, ax, surface, position, drawn_surfaces, vis_args, plane):
310314
"""Draws the fins and saves the position of the points of interest
311315
for the tubes."""
312316
num_fins = surface.n
313317
x_fin = -self.rocket._csys * surface.shape_vec[0] + position
314318
y_fin = surface.shape_vec[1] + surface.rocket_radius
315-
rotation_angles = [2 * np.pi * i / num_fins for i in range(num_fins)]
319+
rotation_angles = np.array([2 * np.pi * i / num_fins for i in range(num_fins)])
320+
if plane == "xz":
321+
rotation_angles -= np.pi / 2
316322

317323
for angle in rotation_angles:
318324
# Create a rotation matrix for the current angle around the x-axis
@@ -324,13 +330,6 @@ def _draw_fins(self, ax, surface, position, drawn_surfaces, vis_args):
324330
# Extract x and y coordinates of the rotated points
325331
x_rotated, y_rotated = rotated_points_2d
326332

327-
# Project points above the XY plane back into the XY plane (set z-coordinate to 0)
328-
x_rotated = np.where(
329-
rotated_points_2d[1] > 0, rotated_points_2d[0], x_rotated
330-
)
331-
y_rotated = np.where(
332-
rotated_points_2d[1] > 0, rotated_points_2d[1], y_rotated
333-
)
334333
ax.plot(
335334
x_rotated,
336335
y_rotated,
@@ -340,34 +339,55 @@ def _draw_fins(self, ax, surface, position, drawn_surfaces, vis_args):
340339

341340
drawn_surfaces.append((surface, position, surface.rocket_radius, x_rotated[-1]))
342341

343-
def _draw_fin(self, ax, surface, position, drawn_surfaces, vis_args):
344-
"""Draws the fins and saves the position of the points of interest
345-
for the tubes."""
342+
def _draw_fin(self, ax, surface, position, drawn_surfaces, vis_args, plane):
343+
"""Draws individual fins."""
346344

347-
x_fin = -self.rocket._csys * surface.shape_vec[0] + position
348-
y_fin = surface.shape_vec[1] + surface.rocket_radius
349-
angle = surface.angular_position
345+
# Get shape vec
346+
xs = surface.shape_vec[0]
347+
ys = surface.shape_vec[1]
348+
zs = np.zeros_like(xs)
349+
350+
# Define shape in fin coordinate system
351+
x_fin = -zs
352+
y_fin = ys
353+
z_fin = xs
354+
points = np.column_stack((x_fin, y_fin, z_fin))
355+
356+
# Move drawing coordinates to center of fin for cant angle rotation
357+
xd = np.array([0, 0, max(xs) / 2])
358+
points -= xd
350359

351-
# Create a rotation matrix for the angle around the x-axis
352-
rotation_matrix = np.array([[1, 0], [0, np.cos(angle)]])
360+
# Rotate to body coordinate system
361+
for i, p in enumerate(points):
362+
points[i] = surface._rotation_fin_to_body @ Vector(p)
353363

354-
# Apply the rotation to the original fin points
355-
rotated_points_2d = np.dot(rotation_matrix, np.vstack((x_fin, y_fin)))
364+
rotated_xd = surface._rotation_fin_to_body @ Vector(xd)
365+
points += np.array(rotated_xd)
356366

357-
# Extract x and y coordinates of the rotated points
358-
x_rotated, y_rotated = rotated_points_2d
367+
# Back to the drawing system
368+
x_fin_rotated = points[:, 0]
369+
y_fin_rotated = points[:, 1]
370+
z_fin_rotated = points[:, 2]
371+
372+
if plane == "xz":
373+
x_rotated = self.rocket._csys * z_fin_rotated + position.z
374+
y_rotated = x_fin_rotated + position.x
375+
elif plane == "yz":
376+
x_rotated = self.rocket._csys * z_fin_rotated + position.z
377+
y_rotated = y_fin_rotated + position.y
378+
else: # pragma: no cover
379+
raise ValueError("Plane must be 'xz' or 'yz'.")
359380

360-
# Project points above the XY plane back into the XY plane (set z-coordinate to 0)
361-
x_rotated = np.where(rotated_points_2d[1] > 0, rotated_points_2d[0], x_rotated)
362-
y_rotated = np.where(rotated_points_2d[1] > 0, rotated_points_2d[1], y_rotated)
363381
ax.plot(
364382
x_rotated,
365383
y_rotated,
366384
color=vis_args["fins"],
367385
linewidth=vis_args["line_width"],
368386
)
369387

370-
drawn_surfaces.append((surface, position, surface.rocket_radius, x_rotated[-1]))
388+
drawn_surfaces.append(
389+
(surface, position.z, surface.rocket_radius, x_rotated[-1])
390+
)
371391

372392
def _draw_generic_surface(
373393
self,
@@ -390,8 +410,6 @@ def _draw_generic_surface(
390410
x_pos = position[2]
391411
# y position of the surface is the y position in the plot
392412
y_pos = position[1]
393-
else: # pragma: no cover
394-
raise ValueError("Plane must be 'xz' or 'yz'.")
395413

396414
ax.scatter(
397415
x_pos,
@@ -470,9 +488,7 @@ def _draw_motor(self, last_radius, last_x, ax, vis_args):
470488

471489
self._draw_nozzle_tube(last_radius, last_x, nozzle_position, ax, vis_args)
472490

473-
def _generate_motor_patches(
474-
self, total_csys, ax
475-
): # pylint: disable=unused-argument
491+
def _generate_motor_patches(self, total_csys, ax):
476492
"""Generates motor patches for drawing"""
477493
motor_patches = []
478494

@@ -680,7 +696,7 @@ def all(self):
680696

681697
# Rocket draw
682698
if len(self.rocket.aerodynamic_surfaces) > 0:
683-
print("\nRocket Draw")
699+
print("\nRocket Drawing")
684700
print("-" * 40)
685701
self.draw()
686702

0 commit comments

Comments
 (0)