0

I'm trying to rotate a 3D cube in matplotlib using FuncAnimation. Instead of just showing a single rendering of a cube that would result in an animation it just paints over itself however, for some kind of 70's fabric art.

After many other false starts this is the closest I got but clearly I'm not using FuncAnimation correctly. I'd be grateful for any hints and explanations where am I going wrong. (This is running in a Jupyter notebook)

%matplotlib notebook
import numpy as np
from numpy import sin, cos, pi
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation

rot_num = 1 # number of rotations
smoothness = 90 # how many steps per rotation
# Define corners of a cube
cube = np.array([[0,0,1],[1,0,1],[1,1,1],[0,1,1],[0,0,0],[1,0,0],[1,1,0],[0,1,0]])
angles = np.linspace(0, rot_num*2*pi, smoothness*rot_num)
points = np.zeros(shape=(len(cube), 3, len(angles)),  dtype=np.float16)
# Calculate all the points needed for rotation
for i in range(len(points)):
    newX = cube[i,0] * cos(angles) - cube[i,2] * sin(angles)
    newY = cube[i,1]
    newZ = cube[i,2] * cos(angles) + cube[i,0] * sin(angles)
    points[i,0] = newX
    points[i,1] = newY
    points[i,2] = newZ
# Define the vertices/lines of the cube using corners, with color
cube_v = [[points[0], points[1], "green"],
          [points[1], points[2], "green"],
          [points[2], points[3], "green"],
          [points[3], points[0], "green"],
          [points[0], points[4], "blue"],
          [points[1], points[5], "blue"],
          [points[2], points[6], "blue"],
          [points[3], points[7], "blue"],
          [points[4], points[5], "red"],
          [points[5], points[6], "red"],
          [points[6], points[7], "red"],
          [points[7], points[4], "red"]]
    
fig = plt.figure()
plt.rcParams["figure.figsize"] = 9,9
ax = fig.add_subplot(111, projection="3d", autoscale_on=True)
ax.grid()
ax.set_title('3D Animation')
ax.set_xlim3d([-2.0, 2.0])
ax.set_xlabel('X')
ax.set_ylim3d([-2.0, 2.0])
ax.set_ylabel('Y')
ax.set_zlim3d([-2.0, 2.0])
ax.set_zlabel('Z')

def update(i): 
    for vertex in cube_v:
        line = ax.plot([vertex[0][0][i], vertex[1][0][i]], 
                       [vertex[0][1][i], vertex[1][1][i]], 
                       [vertex[0][2][i], vertex[1][2][i]], 
                       vertex[2])
        
ani = animation.FuncAnimation(fig, update, frames=len(angles), interval=20, blit=False, repeat=False)
plt.show()
1
  • you have to clear() plot/figure/ax before you create new plot(). Or you have to create/plot line only once and later only replace data in existing line Commented Aug 24, 2020 at 21:00

2 Answers 2

1

You can use ax.clear() to clear all before drawing new cube

def update(i):
    ax.clear()
    
    for vertex in cube_v:
        line = ax.plot([vertex[0][0][i], vertex[1][0][i]], 
                       [vertex[0][1][i], vertex[1][1][i]], 
                       [vertex[0][2][i], vertex[1][2][i]], 
                       vertex[2])

but this also removes other settings so it will rescale axis, remove title, etc. It is not so good solution with your settings.


You can keep all lines on list and remove() them before drawing new cube

all_lines = []

def update(i):
    #global all_lines
    
    # remove previous lines
    for line in all_lines:
        line.remove()
    
    # clear list     
    #all_lines = []    # needs `global`
    all_lines.clear()  # doesn't need `global`
    
    # draw new lines
    for vertex in cube_v:
        line = ax.plot([vertex[0][0][i], vertex[1][0][i]], 
                       [vertex[0][1][i], vertex[1][1][i]], 
                       [vertex[0][2][i], vertex[1][2][i]], 
                       vertex[2])

        # plot may create many lines so it gives list (even for single line)
        all_lines.append(line[0])

Third method can be to create empty lines at start and later only replace .data in lines. I don't have example for this method because it would need more changes.


It seems this method not work with Jupyter but only using normal python script.py

The smoothest animation gives me blit=True. I have to return list with new lines and it will automatically remove older lines and put new lines. Probably it uses optimized method to replace items so it is so smooth.

def update(i):
    all_lines = []
    
    for vertex in cube_v:
        line = ax.plot([vertex[0][0][i], vertex[1][0][i]], 
                       [vertex[0][1][i], vertex[1][1][i]], 
                       [vertex[0][2][i], vertex[1][2][i]], 
                       vertex[2])

        # plot may create many lines so it gives list (even for single line)
        all_lines.append(line[0])
        
    return all_lines

ani = animation.FuncAnimation(fig, update, 
                              frames=len(angles), 
                              interval=20, 
                              blit=True,  # <-- True
                              repeat=False)
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot, this was very useful as it got me on the right track. The third method sent me to what seems to be the best solution as it avoids lots of redrawing.
0

Thanks to Furas here is the solution I came up with, which is exactly to just replace the data in an initial line and letting FuncAnimation handle the rest. This page helped as well https://brushingupscience.com/2016/06/21/matplotlib-animations-the-easy-way/

# initialize with first set of lines and color
lines = [ax.plot([vertex[0][0][0], vertex[1][0][0]], 
                 [vertex[0][1][0], vertex[1][1][0]], 
                 [vertex[0][2][0], vertex[1][2][0]], 
                 vertex[2]) for vertex in cube_v]

# only change the x,y,z coordinates and return updated object
def update(i):
    out = [lines[v][0].set_data_3d([vertex[0][0][i], vertex[1][0][i]], 
                                   [vertex[0][1][i], vertex[1][1][i]], 
                                   [vertex[0][2][i], vertex[1][2][i]]) for v, vertex in enumerate(cube_v)]
    return lines

ani = animation.FuncAnimation(fig, update, frames=len(angles), interval=10, blit=True, repeat=False)
plt.show() 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.