Michael Michael - 2 months ago 13
Python Question

Python: Animating a vector using mplot3d and animation

I'm trying to make a 3d plot with Matplotlib and the animation package from matplotlib. In addition, the animation should be a part of a Gui generated using PyQt and Qt-Designer. Currently I'm stuck on using the "animation.Funcnimation()" correctly, at least i think so...
So here is my code:

import sys
from PyQt4.uic import loadUiType
from PyQt4 import QtGui

from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import animation

import numpy as np
import Quaternion as qt

Ui_MainWindow, QMainWindow = loadUiType('Newsphere.ui')

class Kinematic(Ui_MainWindow, QMainWindow):
def __init__(self):
super(Kinematic, self).__init__()
self.setupUi(self)

self.fig = plt.figure()
self.ax = self.fig.add_subplot(111,projection = '3d')
self.fig.tight_layout()

self.ani = animation.FuncAnimation(self.fig, self.update,
init_func=self.setup_plot, blit=True)

self.canvas = FigureCanvas(self.fig)
self.mplvl.addWidget(self.canvas)
self.canvas.draw()

def setup_plot(self):
self.ax.view_init(40, 45)

self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
self.ax.set_zlabel('Z')

self.ax.set_xlim3d(-1,1)
self.ax.set_ylim3d(-1,1)
self.ax.set_zlim3d(-1,1)

g_x = np.matrix([[1.0],[0.0],[0.0]])
g_y = np.matrix([[0.0],[1.0],[0.0]])
g_z = np.matrix([[0.0],[0.0],[1.0]])

self.ax.plot([0,g_x[0]], [0,g_x[1]], [0,g_x[2]], label='$X_0$')
self.ax.plot([0,g_y[0]], [0,g_y[1]], [0,g_y[2]], label='$Y_0$')
self.ax.plot([0,g_z[0]], [0,g_z[1]], [0,g_z[2]], label='$Z_0$')

self.vek, = self.ax.plot([0,-1], [0,0], [0,0], label='$g \cdot R$', animated=True)

self.ax.legend(loc='best')
self.ax.scatter(0,0,0, color='k')

return self.vek,

def update(self, i):
b = self.bslider.value() / 100

g = np.matrix([[1.0],[0.0],[0.0]])
q = np.array([0,b,0.5,0])

R = qt.QtoR(q)
x, y, z = R*g

self.vek, = self.ax.plot([0,x], [0,y], [0,z], label='$g \cdot R$', animated=True) #the rotated vector
return self.vek,

if __name__ == '__main__':

app = QtGui.QApplication(sys.argv)

main = Kinematic()
main.show()

sys.exit(app.exec_())


You won't be able to run it by just copy-paste because you don't have the file "Newsphere.ui" (Line 13) and the Quaternion.py (Line 11). So when I run it, I get the following (actually like I wish!):
Coordinate system

My goal is now to draw a vector (Line 50) and animate it (Line 66) using data which I get from the Gui-slider (Line 58). Can anyone help me with this? I'm stuck with this for days!

Answer

So if someone is interested in a solution of the mentioned problem, here we go: (again it is not a code for copy-paste because of the missing 'Newsphere.ui', but I try to explain the important snippets)

import sys
from PyQt4.uic import loadUiType
from PyQt4 import QtGui

from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import animation

import numpy as np

Ui_MainWindow, QMainWindow = loadUiType('Newsphere.ui')

class Kinematic(Ui_MainWindow, QMainWindow):

    def __init__(self):
        super(Kinematic, self).__init__()
        self.setupUi(self)

        self.fig = plt.figure()
        self.ax = self.fig.add_subplot(111,projection = '3d')
        self.fig.tight_layout()
        self.ax.view_init(40, -45)

        # dashed coordinate system
        self.ax.plot([0,1], [0,0], [0,0], label='$X_0$', linestyle="dashed")
        self.ax.plot([0,0], [0,-1], [0,0], label='$Y_0$', linestyle="dashed")
        self.ax.plot([0,0], [0,0], [0,1], label='$Z_0$', linestyle="dashed")

        self.ax.set_xlim3d(-1,1)
        self.ax.set_ylim3d(-1,1)
        self.ax.set_zlim3d(-1,1)

        self.ax.set_xlabel('X')
        self.ax.set_ylabel('Y')
        self.ax.set_zlabel('Z')

        self.ax.scatter(0,0,0, color='k') # black origin dot

        self.canvas = FigureCanvas(self.fig)
        self.mplvl.addWidget(self.canvas)

        self.ani = animation.FuncAnimation(self.fig, self.data_gen, init_func=self.setup_plot, blit=True)

    def setup_plot(self):

        self.ax.legend(loc='best')

        self.vek = self.ax.quiver(0, 0, 0, 0, 0, 0, label='$g \cdot R$', pivot="tail", color="black")

        return self.vek,

    def data_gen(self, i):

        b = self.bslider.value() / 100

        vx, vy, vz = np.cos(b), np.sin(b), 0

        self.vek = self.ax.quiver(0, 0, 0, vx, vy, vz, label='$g \cdot R$', pivot="tail", color="black")

        self.canvas.draw()

        return self.vek,

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)

    main = Kinematic()
    main.show()

    sys.exit(app.exec_())

By running the code I get this.

I generated a GUI file named 'Newsphere.ui' following this tutorial. Basically it contains just a Widget, a QSlider and a QSpinBox. The Widget has the layout-name "mplvl", which occures in line 41. This adds the generated figure to the widget (I think so...). The QSlider is connected with the QSpinBox (done in QtDesigner) and has the name "bslider", line 55. So in this line the slider-value gets divided by 100 because I didn't found a slider that generates me a float-value. The key-line for me was line 61, where the canvas is drawn. Now the animation.FuncAnimation (line 43) draws a new vector when I change the slider value, compare the pics.

If there are questions or suggestions for improvement please ask/post. I'm still working on this ;)