Skip to content

Commit 6805e32

Browse files
committed
MNT: refactor rocket drawing plot methods
1 parent f286413 commit 6805e32

File tree

1 file changed

+152
-140
lines changed

1 file changed

+152
-140
lines changed

rocketpy/plots/rocket_plots.py

Lines changed: 152 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,6 @@ def draw(self, vis_args=None):
189189
A full list of color names can be found at:
190190
https://matplotlib.org/stable/gallery/color/named_colors
191191
"""
192-
# TODO: we need to modularize this function, it is too big
193192
if vis_args is None:
194193
vis_args = {
195194
"background": "#EEEEEE",
@@ -212,10 +211,27 @@ def draw(self, vis_args=None):
212211
reverse = csys == 1
213212
self.rocket.aerodynamic_surfaces.sort_by_position(reverse=reverse)
214213

214+
drawn_surfaces = self._draw_aerodynamic_surfaces(ax, vis_args)
215+
last_radius, last_x = self._draw_tubes(ax, drawn_surfaces, vis_args)
216+
self._draw_motor(last_radius, last_x, ax, vis_args)
217+
self._draw_rail_buttons(ax, vis_args)
218+
self._draw_center_of_mass_and_pressure(ax)
219+
220+
plt.title("Rocket Representation")
221+
plt.xlim()
222+
plt.ylim([-self.rocket.radius * 4, self.rocket.radius * 6])
223+
plt.xlabel("Position (m)")
224+
plt.ylabel("Radius (m)")
225+
plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left")
226+
plt.tight_layout()
227+
plt.show()
228+
229+
def _draw_aerodynamic_surfaces(self, ax, vis_args):
230+
"""Draws the aerodynamic surfaces and saves the position of the points
231+
of interest for the tubes."""
215232
# List of drawn surfaces with the position of points of interest
216233
# and the radius of the rocket at that point
217234
drawn_surfaces = []
218-
219235
# Idea is to get the shape of each aerodynamic surface in their own
220236
# coordinate system and then plot them in the rocket coordinate system
221237
# using the position of each surface
@@ -225,113 +241,105 @@ def draw(self, vis_args=None):
225241

226242
for surface, position in self.rocket.aerodynamic_surfaces:
227243
if isinstance(surface, NoseCone):
228-
x_nosecone = -csys * surface.shape_vec[0] + position
229-
y_nosecone = surface.shape_vec[1]
230-
231-
ax.plot(
232-
x_nosecone,
233-
y_nosecone,
234-
color=vis_args["nose"],
235-
linewidth=vis_args["line_width"],
236-
)
237-
ax.plot(
238-
x_nosecone,
239-
-y_nosecone,
240-
color=vis_args["nose"],
241-
linewidth=vis_args["line_width"],
242-
)
243-
# close the nosecone
244-
ax.plot(
245-
[x_nosecone[-1], x_nosecone[-1]],
246-
[y_nosecone[-1], -y_nosecone[-1]],
247-
color=vis_args["nose"],
248-
linewidth=vis_args["line_width"],
249-
)
250-
251-
# Add the nosecone to the list of drawn surfaces
252-
drawn_surfaces.append(
253-
(surface, x_nosecone[-1], surface.rocket_radius, x_nosecone[-1])
254-
)
255-
244+
self._draw_nose_cone(ax, surface, position, drawn_surfaces, vis_args)
256245
elif isinstance(surface, Tail):
257-
x_tail = -csys * surface.shape_vec[0] + position
258-
y_tail = surface.shape_vec[1]
259-
260-
ax.plot(
261-
x_tail,
262-
y_tail,
263-
color=vis_args["tail"],
264-
linewidth=vis_args["line_width"],
265-
)
266-
ax.plot(
267-
x_tail,
268-
-y_tail,
269-
color=vis_args["tail"],
270-
linewidth=vis_args["line_width"],
271-
)
272-
# close above and below the tail
273-
ax.plot(
274-
[x_tail[-1], x_tail[-1]],
275-
[y_tail[-1], -y_tail[-1]],
276-
color=vis_args["tail"],
277-
linewidth=vis_args["line_width"],
278-
)
279-
ax.plot(
280-
[x_tail[0], x_tail[0]],
281-
[y_tail[0], -y_tail[0]],
282-
color=vis_args["tail"],
283-
linewidth=vis_args["line_width"],
284-
)
246+
self._draw_tail(ax, surface, position, drawn_surfaces, vis_args)
247+
elif isinstance(surface, Fins):
248+
self._draw_fins(ax, surface, position, drawn_surfaces, vis_args)
249+
return drawn_surfaces
250+
251+
def _draw_nose_cone(self, ax, surface, position, drawn_surfaces, vis_args):
252+
"""Draws the nosecone and saves the position of the points of interest
253+
for the tubes."""
254+
x_nosecone = -self.rocket._csys * surface.shape_vec[0] + position
255+
y_nosecone = surface.shape_vec[1]
256+
ax.plot(
257+
x_nosecone,
258+
y_nosecone,
259+
color=vis_args["nose"],
260+
linewidth=vis_args["line_width"],
261+
)
262+
ax.plot(
263+
x_nosecone,
264+
-y_nosecone,
265+
color=vis_args["nose"],
266+
linewidth=vis_args["line_width"],
267+
)
268+
# close the nosecone
269+
ax.plot(
270+
[x_nosecone[-1], x_nosecone[-1]],
271+
[y_nosecone[-1], -y_nosecone[-1]],
272+
color=vis_args["nose"],
273+
linewidth=vis_args["line_width"],
274+
)
275+
# Add the nosecone to the list of drawn surfaces
276+
drawn_surfaces.append(
277+
(surface, x_nosecone[-1], surface.rocket_radius, x_nosecone[-1])
278+
)
285279

286-
# Add the tail to the list of drawn surfaces
287-
drawn_surfaces.append(
288-
(surface, position, surface.bottom_radius, x_tail[-1])
289-
)
280+
def _draw_tail(self, ax, surface, position, drawn_surfaces, vis_args):
281+
"""Draws the tail and saves the position of the points of interest
282+
for the tubes."""
283+
x_tail = -self.rocket._csys * surface.shape_vec[0] + position
284+
y_tail = surface.shape_vec[1]
285+
ax.plot(
286+
x_tail, y_tail, color=vis_args["tail"], linewidth=vis_args["line_width"]
287+
)
288+
ax.plot(
289+
x_tail, -y_tail, color=vis_args["tail"], linewidth=vis_args["line_width"]
290+
)
291+
# close above and below the tail
292+
ax.plot(
293+
[x_tail[-1], x_tail[-1]],
294+
[y_tail[-1], -y_tail[-1]],
295+
color=vis_args["tail"],
296+
linewidth=vis_args["line_width"],
297+
)
298+
ax.plot(
299+
[x_tail[0], x_tail[0]],
300+
[y_tail[0], -y_tail[0]],
301+
color=vis_args["tail"],
302+
linewidth=vis_args["line_width"],
303+
)
304+
# Add the tail to the list of drawn surfaces
305+
drawn_surfaces.append((surface, position, surface.bottom_radius, x_tail[-1]))
306+
307+
def _draw_fins(self, ax, surface, position, drawn_surfaces, vis_args):
308+
"""Draws the fins and saves the position of the points of interest
309+
for the tubes."""
310+
num_fins = surface.n
311+
x_fin = -self.rocket._csys * surface.shape_vec[0] + position
312+
y_fin = surface.shape_vec[1] + surface.rocket_radius
313+
rotation_angles = [2 * np.pi * i / num_fins for i in range(num_fins)]
314+
315+
for angle in rotation_angles:
316+
# Create a rotation matrix for the current angle around the x-axis
317+
rotation_matrix = np.array([[1, 0], [0, np.cos(angle)]])
318+
319+
# Apply the rotation to the original fin points
320+
rotated_points_2d = np.dot(rotation_matrix, np.vstack((x_fin, y_fin)))
321+
322+
# Extract x and y coordinates of the rotated points
323+
x_rotated, y_rotated = rotated_points_2d
324+
325+
# Project points above the XY plane back into the XY plane (set z-coordinate to 0)
326+
x_rotated = np.where(
327+
rotated_points_2d[1] > 0, rotated_points_2d[0], x_rotated
328+
)
329+
y_rotated = np.where(
330+
rotated_points_2d[1] > 0, rotated_points_2d[1], y_rotated
331+
)
332+
ax.plot(
333+
x_rotated,
334+
y_rotated,
335+
color=vis_args["fins"],
336+
linewidth=vis_args["line_width"],
337+
)
290338

291-
# Draw fins
292-
elif isinstance(surface, Fins):
293-
num_fins = surface.n
294-
x_fin = -csys * surface.shape_vec[0] + position
295-
y_fin = surface.shape_vec[1] + surface.rocket_radius
296-
297-
# Calculate the rotation angles for the other two fins (symmetrically)
298-
rotation_angles = [2 * np.pi * i / num_fins for i in range(num_fins)]
299-
300-
# Apply rotation transformations to get points for the other fins in 2D space
301-
for angle in rotation_angles:
302-
# Create a rotation matrix for the current angle around the x-axis
303-
rotation_matrix = np.array([[1, 0], [0, np.cos(angle)]])
304-
305-
# Apply the rotation to the original fin points
306-
rotated_points_2d = np.dot(
307-
rotation_matrix, np.vstack((x_fin, y_fin))
308-
)
309-
310-
# Extract x and y coordinates of the rotated points
311-
x_rotated, y_rotated = rotated_points_2d
312-
313-
# Project points above the XY plane back into the XY plane (set z-coordinate to 0)
314-
x_rotated = np.where(
315-
rotated_points_2d[1] > 0, rotated_points_2d[0], x_rotated
316-
)
317-
y_rotated = np.where(
318-
rotated_points_2d[1] > 0, rotated_points_2d[1], y_rotated
319-
)
320-
321-
# Plot the fins
322-
ax.plot(
323-
x_rotated,
324-
y_rotated,
325-
color=vis_args["fins"],
326-
linewidth=vis_args["line_width"],
327-
)
328-
329-
# Add the fin to the list of drawn surfaces
330-
drawn_surfaces.append(
331-
(surface, position, surface.rocket_radius, x_rotated[-1])
332-
)
339+
drawn_surfaces.append((surface, position, surface.rocket_radius, x_rotated[-1]))
333340

334-
# Draw tubes
341+
def _draw_tubes(self, ax, drawn_surfaces, vis_args):
342+
"""Draws the tubes between the aerodynamic surfaces."""
335343
for i, d_surface in enumerate(drawn_surfaces):
336344
# Draw the tubes, from the end of the first surface to the beginning
337345
# of the next surface, with the radius of the rocket at that point
@@ -368,18 +376,41 @@ def draw(self, vis_args=None):
368376
color=vis_args["body"],
369377
linewidth=vis_args["line_width"],
370378
)
379+
return radius, last_x
371380

372-
# Draw motor
381+
def _draw_motor(self, last_radius, last_x, ax, vis_args):
382+
"""Draws the motor from motor patches"""
373383
total_csys = self.rocket._csys * self.rocket.motor._csys
374384
nozzle_position = (
375385
self.rocket.motor_position + self.rocket.motor.nozzle_position * total_csys
376386
)
377387

378-
# List of motor patches
388+
# Get motor patches translated to the correct position
389+
motor_patches = self._generate_motor_patches(total_csys, ax, vis_args)
390+
391+
# Draw patches
392+
if not isinstance(self.rocket.motor, EmptyMotor):
393+
# Add nozzle last so it is in front of the other patches
394+
nozzle = self.rocket.motor.plots._generate_nozzle(
395+
translate=(nozzle_position, 0), csys=self.rocket._csys
396+
)
397+
motor_patches += [nozzle]
398+
399+
outline = self.rocket.motor.plots._generate_motor_region(
400+
list_of_patches=motor_patches
401+
)
402+
# add outline first so it is behind the other patches
403+
ax.add_patch(outline)
404+
for patch in motor_patches:
405+
ax.add_patch(patch)
406+
407+
self._draw_nozzle_tube(last_radius, last_x, nozzle_position, ax, vis_args)
408+
409+
def _generate_motor_patches(self, total_csys, ax, vis_args):
410+
"""Generates motor patches for drawing"""
379411
motor_patches = []
380412

381-
# Get motor patches translated to the correct position
382-
if isinstance(self.rocket.motor, (SolidMotor)):
413+
if isinstance(self.rocket.motor, SolidMotor):
383414
grains_cm_position = (
384415
self.rocket.motor_position
385416
+ self.rocket.motor.grains_center_of_mass_position * total_csys
@@ -452,27 +483,17 @@ def draw(self, vis_args=None):
452483
)
453484
motor_patches += [tank]
454485

455-
# add nozzle last so it is in front of the other patches
456-
if not isinstance(self.rocket.motor, EmptyMotor):
457-
nozzle = self.rocket.motor.plots._generate_nozzle(
458-
translate=(nozzle_position, 0), csys=self.rocket._csys
459-
)
460-
motor_patches += [nozzle]
461-
outline = self.rocket.motor.plots._generate_motor_region(
462-
list_of_patches=motor_patches
463-
)
464-
# add outline first so it is behind the other patches
465-
ax.add_patch(outline)
466-
for patch in motor_patches:
467-
ax.add_patch(patch)
486+
return motor_patches
468487

488+
def _draw_nozzle_tube(self, last_radius, last_x, nozzle_position, ax, vis_args):
489+
"""Draws the tube from the last surface to the nozzle position."""
469490
# Check if nozzle is beyond the last surface, if so draw a tube
470491
# to it, with the radius of the last surface
471492
if self.rocket._csys == 1:
472493
if nozzle_position < last_x:
473494
x_tube = [last_x, nozzle_position]
474-
y_tube = [radius, radius]
475-
y_tube_negated = [-radius, -radius]
495+
y_tube = [last_radius, last_radius]
496+
y_tube_negated = [-last_radius, -last_radius]
476497

477498
ax.plot(
478499
x_tube,
@@ -489,8 +510,8 @@ def draw(self, vis_args=None):
489510
else: # if self.rocket._csys == -1:
490511
if nozzle_position > last_x:
491512
x_tube = [last_x, nozzle_position]
492-
y_tube = [radius, radius]
493-
y_tube_negated = [-radius, -radius]
513+
y_tube = [last_radius, last_radius]
514+
y_tube_negated = [-last_radius, -last_radius]
494515

495516
ax.plot(
496517
x_tube,
@@ -505,11 +526,12 @@ def draw(self, vis_args=None):
505526
linewidth=vis_args["line_width"],
506527
)
507528

508-
# Draw rail buttons
529+
def _draw_rail_buttons(self, ax, vis_args):
530+
"""Draws the rail buttons of the rocket."""
509531
try:
510532
buttons, pos = self.rocket.rail_buttons[0]
511533
lower = pos
512-
upper = pos + buttons.buttons_distance * csys
534+
upper = pos + buttons.buttons_distance * self.rocket._csys
513535
ax.scatter(
514536
lower, -self.rocket.radius, marker="s", color=vis_args["buttons"], s=15
515537
)
@@ -519,6 +541,8 @@ def draw(self, vis_args=None):
519541
except IndexError:
520542
pass
521543

544+
def _draw_center_of_mass_and_pressure(self, ax):
545+
"""Draws the center of mass and center of pressure of the rocket."""
522546
# Draw center of mass and center of pressure
523547
cm = self.rocket.center_of_mass(0)
524548
ax.scatter(cm, 0, color="#1565c0", label="Center of Mass", s=10)
@@ -528,18 +552,6 @@ def draw(self, vis_args=None):
528552
cp, 0, label="Static Center of Pressure", color="red", s=10, zorder=10
529553
)
530554

531-
# Set plot attributes
532-
plt.title("Rocket Representation")
533-
plt.xlim()
534-
plt.ylim([-self.rocket.radius * 4, self.rocket.radius * 6])
535-
plt.xlabel("Position (m)")
536-
plt.ylabel("Radius (m)")
537-
plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left")
538-
plt.tight_layout()
539-
plt.show()
540-
541-
return None
542-
543555
def all(self):
544556
"""Prints out all graphs available about the Rocket. It simply calls
545557
all the other plotter methods in this class.

0 commit comments

Comments
 (0)