Skip to content

feat: Implement 3D flight animation methods using Vedo (Issue #523)#909

Open
GuilhermeAsura wants to merge 14 commits intoRocketPy-Team:developfrom
GuilhermeAsura:feature/add_animation_methods
Open

feat: Implement 3D flight animation methods using Vedo (Issue #523)#909
GuilhermeAsura wants to merge 14 commits intoRocketPy-Team:developfrom
GuilhermeAsura:feature/add_animation_methods

Conversation

@GuilhermeAsura
Copy link

Pull request type

  • Code changes (bugfix, features)
  • Code maintenance (refactoring, formatting, tests)

Checklist

  • Tests for the changes have been added (if needed)
  • Docs have been reviewed and added / updated
  • Lint (black rocketpy/ tests/) has passed locally
  • All tests (pytest tests -m slow --runslow) have passed locally
  • CHANGELOG.md has been updated (if relevant)

Current behavior

Currently, the Flight class lacks built-in methods for 3D visualization of the simulation results. Users wishing to visualize the rocket's trajectory or attitude must export data and use external tools or write custom scripts. This addresses Issue #523.

New behavior

This PR integrates 3D visualization capabilities directly into the Flight class using the vedo library.

Key Changes:

  1. New Methods in Flight:
    • animate_trajectory(file_name, start, stop, time_step): Visualizes the 6-DOF translation of the rocket relative to the ground.
    • animate_rotate(file_name, start, stop, time_step): Visualizes the specific attitude (rotation) of the rocket during flight.
  2. Optional Dependency:
    • Added vedo as an optional dependency in pyproject.toml under the [animation] key.
    • Users can install it via pip install rocketpy[animation].
  3. Error Handling:
    • Both methods check for the existence of vedo and raise a descriptive ImportError with installation instructions if it is missing.
  4. Refactoring:
    • Ported original logic from the legacy animate_flight branch to match the current Flight class structure (e.g., removing deprecated postProcess calls).

Breaking change

  • No

Additional information

Acknowledgements

This feature was originally developed by Patrick Sampaio in the animate_flight branch. This PR adapts that work to the modern develop branch structure and newer RocketPy architecture.

Verification

A modular verification suite was added in tests/animation_verification/ to generate a dummy 3D model and test the invocation of the animation methods.

trajectory_animation

@GuilhermeAsura GuilhermeAsura requested a review from a team as a code owner December 6, 2025 03:50
@Gui-FernandesBR
Copy link
Member

@GuilhermeAsura could you please fix tests and linters on CI?

@GuilhermeAsura
Copy link
Author

@GuilhermeAsura could you please fix tests and linters on CI?

I'm on it!

@Gui-FernandesBR Gui-FernandesBR force-pushed the feature/add_animation_methods branch from 4a29817 to aebdf6a Compare December 9, 2025 00:25
Copy link
Member

@Gui-FernandesBR Gui-FernandesBR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the code coverall looks good, but needs refactor.

We can still add it to the next release (this month)

@Gui-FernandesBR Gui-FernandesBR linked an issue Dec 9, 2025 that may be closed by this pull request
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 7 changed files in this pull request and generated 21 comments.

rocket.rotate_z(self.e3(t) / k)

plt.show(world, rocket)

Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment contains a typo: "slow down to make animation visible" should better explain what's happening. However, more importantly, there's no actual pause mechanism in the animate_rotate method (unlike animate_trajectory), which means the rotation animation will run at maximum speed without frame-rate control. Add a similar timing control as in animate_trajectory to maintain consistent animation speed.

Suggested change
# Pause to maintain consistent animation speed and make each frame visible
time.sleep(time_step)

Copilot uses AI. Check for mistakes.
Comment on lines +4207 to +4210
world = Box(
pos=[self.x(start), self.y(start), self.apogee],
length=max(self.x[:, 1]) * 0.2,
width=max(self.y[:, 1]) * 0.2,
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential issue with Box dimensions: When trajectory has small or zero displacement in x or y directions, the world box can have zero or very small dimensions (e.g., max_x * 0.2 when max_x is small). This could result in invisible or improperly sized bounding boxes. Add minimum dimension checks to ensure the box remains visible, e.g., max(self.x[:, 1]) * 0.2 if max(self.x[:, 1]) > 10 else 10.

Suggested change
world = Box(
pos=[self.x(start), self.y(start), self.apogee],
length=max(self.x[:, 1]) * 0.2,
width=max(self.y[:, 1]) * 0.2,
min_box_dim = 10 # meters, minimum box dimension for visibility
world = Box(
pos=[self.x(start), self.y(start), self.apogee],
length=max(max(self.x[:, 1]) * 0.2, min_box_dim),
width=max(max(self.y[:, 1]) * 0.2, min_box_dim),

Copilot uses AI. Check for mistakes.
rocket.pos(self.x(start), self.y(start), 0).add_trail(n=len(self.x[:, 1]))

plt = Plotter(axes=1, interactive=False)
plt.show(world, rocket, __doc__, viewup="z", **kwargs)
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of __doc__ as a display string is incorrect. __doc__ refers to the module/function's docstring and will display a large block of text. If you want to show a title or message, pass a proper string like "Rocket Rotation Animation" instead.

Suggested change
plt.show(world, rocket, __doc__, viewup="z", **kwargs)
plt.show(world, rocket, "Rocket Rotation Animation", viewup="z", **kwargs)

Copilot uses AI. Check for mistakes.
# Handle stop time
if stop is None:
stop = self.t_final

Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing input validation: The method doesn't validate that start < stop or that these values are within the valid time range [0, self.t_final]. Add validation to raise a ValueError if start >= stop or if either value is outside the valid simulation time range.

Suggested change
# Validate start and stop times
if (
start < 0
or stop < 0
or start > self.t_final
or stop > self.t_final
or start >= stop
):
raise ValueError(
f"Invalid animation time range: start={start}, stop={stop}. "
f"Both must be within [0, {self.t_final}] and start < stop."
)

Copilot uses AI. Check for mistakes.
height=self.apogee * 0.1,
).wireframe()

rocket = Mesh(file_name).c("green")
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing file validation: The method doesn't check if the specified STL file exists before attempting to load it. Add a check to raise a FileNotFoundError with a helpful message if the file doesn't exist.

Copilot uses AI. Check for mistakes.
Comment on lines +4132 to +4140
angle = np.arccos(2 * self.e0(t)**2 - 1)
k = np.sin(angle / 2) if np.sin(angle / 2) != 0 else 1

# Update position and rotation
# Adjusting for ground elevation
rocket.pos(self.x(t), self.y(t), self.z(t) - self.env.elevation)
rocket.rotate_x(self.e1(t) / k)
rocket.rotate_y(self.e2(t) / k)
rocket.rotate_z(self.e3(t) / k)
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The quaternion-to-rotation conversion logic has several issues:

  1. The rotation is being applied incrementally in each animation frame without resetting the mesh orientation. This will cause cumulative rotation errors as rotate_x/y/z methods apply rotations relative to the current orientation, not the initial state.

  2. The quaternion-to-axis-angle conversion formula appears incorrect. For a unit quaternion (e0, e1, e2, e3), the angle should be 2 * arccos(e0), not arccos(2*e0^2 - 1).

  3. The rotation axis components should be normalized by sin(angle/2), which equals sqrt(e1^2 + e2^2 + e3^2), not the computed k value.

To fix this, you should either:

  • Reset the mesh orientation before each frame and apply the absolute rotation, or
  • Use vedo's orientation() method with a rotation matrix derived from the quaternion, or
  • Store the initial mesh orientation and compute relative rotations properly.

The same issue exists in the animate_rotate method at lines 4222-4229.

Copilot uses AI. Check for mistakes.
Comment on lines +4117 to +4119
trail_points = [[self.x(t), self.y(t), self.z(t) - self.env.elevation]
for t in np.arange(start, stop, time_step)]
trail = Line(trail_points, c="k", alpha=0.5)
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trail is created using all trajectory points from start to stop before the animation loop, but it's only added to the scene once at line 4143. This means the trail shows the entire future path before the rocket animates, which may not be the desired behavior. Consider either:

  1. Building the trail incrementally during the animation loop, or
  2. Removing the trail entirely if it's meant to show only the past trajectory, or
  3. Clarifying in documentation that the trail shows the full trajectory path upfront.

Copilot uses AI. Check for mistakes.
# to the provided logic for now.

# e0 is the scalar part of the quaternion
angle = np.arccos(2 * self.e0(t)**2 - 1)
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential numerical instability: np.arccos(2 * self.e0(t)**2 - 1) can fail if the argument is slightly outside [-1, 1] due to numerical precision. Use np.clip() to ensure the argument is within valid range: np.arccos(np.clip(2 * self.e0(t)**2 - 1, -1, 1)).

Suggested change
angle = np.arccos(2 * self.e0(t)**2 - 1)
angle = np.arccos(np.clip(2 * self.e0(t)**2 - 1, -1, 1))

Copilot uses AI. Check for mistakes.
plt.show(world, rocket, __doc__, viewup="z", **kwargs)

for t in np.arange(start, stop, time_step):
angle = np.arccos(2 * self.e0(t)**2 - 1)
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential numerical instability: Same issue as in animate_trajectory. Use np.clip() to ensure the arccos argument is within valid range: np.arccos(np.clip(2 * self.e0(t)**2 - 1, -1, 1)).

Suggested change
angle = np.arccos(2 * self.e0(t)**2 - 1)
angle = np.arccos(np.clip(2 * self.e0(t)**2 - 1, -1, 1))

Copilot uses AI. Check for mistakes.
).wireframe()

# Load rocket mesh
rocket = Mesh(file_name).c("green")
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing file validation: The method doesn't check if the specified STL file exists before attempting to load it. Add a check to raise a FileNotFoundError with a helpful message if the file doesn't exist, similar to the pattern used elsewhere in the codebase.

Copilot uses AI. Check for mistakes.
@Gui-FernandesBR
Copy link
Member

Before we merge this PR, some points should be addressed:

  • all plotting methods should be defined withing the plots/ folder
  • the vedo package should be added as an optional requirement
  • Unit tests are needed
  • CHANGELOG.md should be updated
  • The code should be formatted and linted
  • All the comments raised by reviewers should be addressed

- Implemented 3D flight trajectory and attitude animations in the Flight plots layer.
- Added methods `animate_trajectory` and `animate_rotate` to visualize rocket flight.
- Included validation for animation inputs and error handling for missing STL files.
- Updated documentation to reflect new animation features and installation requirements.
- Added optional dependency for `vedo` in `requirements-optional.txt`.
- Created a default STL model for the rocket.
- Removed outdated animation verification tests and replaced them with unit tests for new animation methods.
@Gui-FernandesBR Gui-FernandesBR self-assigned this Mar 14, 2026
@codecov
Copy link

codecov bot commented Mar 14, 2026

Codecov Report

❌ Patch coverage is 88.88889% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.16%. Comparing base (9cf3dd4) to head (8160450).
⚠️ Report is 45 commits behind head on develop.

Files with missing lines Patch % Lines
rocketpy/plots/flight_plots.py 88.88% 14 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #909      +/-   ##
===========================================
+ Coverage    80.27%   81.16%   +0.88%     
===========================================
  Files          104      107       +3     
  Lines        12769    14029    +1260     
===========================================
+ Hits         10250    11386    +1136     
- Misses        2519     2643     +124     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENH: adds an 3D animation of flight trajectory

3 participants