问题
I would like to plot moving cubes using pyqtgraph in python (update their positions based on given dataset). I have a sample code as follow, but I don't know how to plot cube instead of the sphere used in the code. Also, I'm very confused that why this code doesn't work for the second time running? I must close my spyder then open it again or restart kernel to run the code for the second time running. (win10, python3.7 spyder3.3.1)
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 20
w.show()
w.setWindowTitle('pyqtgraph example: GLScatterPlotItem')
g = gl.GLGridItem()
w.addItem(g)
##
## First example is a set of points with pxMode=False
## These demonstrate the ability to have points with real size down to a very small scale
##
pos = np.empty((53, 3))
size = np.empty((53))
color = np.empty((53, 4))
pos[0] = (1,0,0); size[0] = 0.5; color[0] = (1.0, 0.0, 0.0, 0.5)
pos[1] = (0,1,0); size[1] = 0.2; color[1] = (0.0, 0.0, 1.0, 0.5)
pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5)
z = 0.5
d = 6.0
for i in range(3,53):
pos[i] = (0,0,z)
size[i] = 2./d
color[i] = (0.0, 1.0, 0.0, 0.5)
z *= 0.5
d *= 2.0
sp1 = gl.GLScatterPlotItem(pos=pos, size=size, color=color, pxMode=False)
sp1.translate(5,5,0)
w.addItem(sp1)
##
## Second example shows a volume of points with rapidly updating color
## and pxMode=True
##
pos = np.random.random(size=(100000,3))
pos *= [10,-10,10]
pos[0] = (0,0,0)
color = np.ones((pos.shape[0], 4))
d2 = (pos**2).sum(axis=1)**0.5
size = np.random.random(size=pos.shape[0])*10
sp2 = gl.GLScatterPlotItem(pos=pos, color=(1,1,1,1), size=size)
phase = 0.
w.addItem(sp2)
##
## Third example shows a grid of points with rapidly updating position
## and pxMode = False
##
pos3 = np.zeros((100,100,3))
pos3[:,:,:2] = np.mgrid[:100, :100].transpose(1,2,0) * [-0.1,0.1]
pos3 = pos3.reshape(10000,3)
d3 = (pos3**2).sum(axis=1)**0.5
sp3 = gl.GLScatterPlotItem(pos=pos3, color=(1,1,1,.3), size=0.1, pxMode=False)
w.addItem(sp3)
def update():
## update volume colors
global phase, sp2, d2
s = -np.cos(d2*2+phase)
color = np.empty((len(d2),4), dtype=np.float32)
color[:,3] = np.clip(s * 0.1, 0, 1)
color[:,0] = np.clip(s * 3.0, 0, 1)
color[:,1] = np.clip(s * 1.0, 0, 1)
color[:,2] = np.clip(s ** 3, 0, 1)
sp2.setData(color=color)
phase -= 0.1
## update surface positions and colors
global sp3, d3, pos3
z = -np.cos(d3*2+phase)
pos3[:,2] = z
color = np.empty((len(d3),4), dtype=np.float32)
color[:,3] = 0.3
color[:,0] = np.clip(z * 3.0, 0, 1)
color[:,1] = np.clip(z * 1.0, 0, 1)
color[:,2] = np.clip(z ** 3, 0, 1)
sp3.setData(pos=pos3, color=color)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(50)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
回答1:
Imports:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
Okay first of all, seemingly you cannot run two QApplication
s at the same time. So instead of
app = QtGui.QApplication([])
write
app = QtGui.QApplication.instance()
if app is None:
app = QtGui.QApplication([])
This will only create a new instance instead of a new Application in case one already exists.
Now to mesh drawing with pyqtgraph
. You have to define the vertexes of your mesh. For a cube these are the eight corners:
vertexes = np.array([[1, 0, 0], #0
[0, 0, 0], #1
[0, 1, 0], #2
[0, 0, 1], #3
[1, 1, 0], #4
[1, 1, 1], #5
[0, 1, 1], #6
[1, 0, 1]])#7
These are just the positions of the corners in cartesian coordinates.
Now we have to define faces. These are triangles so we need 12 of those for a cube. We give the corners of the triangles in the array positions of the vertexes that shall span the triangle:
faces = np.array([[1,0,7], [1,3,7],
[1,2,4], [1,0,4],
[1,2,6], [1,3,6],
[0,4,5], [0,7,5],
[2,4,5], [2,6,5],
[3,6,5], [3,7,5]])
There may very well be an algorithm to it; for now I just wrote it by hand; maybe I will update later.
Now we have to define colors of the faces. We need RGBA values for every face; I just made it red:
colors = np.array([[1,0,0,1] for i in range(12)])
With this we can create a GLMeshItem
. I draw the edges in black so you can see the triangles:
cube = gl.GLMeshItem(vertexes=vertexes, faces=faces, faceColors=colors,
drawEdges=True, edgeColor=(0, 0, 0, 1))
Now we just need to add the item and start the mainloop:
w.addItem(cube)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
You can move the cube with:
cube.translate(x,y,z)
It will automatically update. And of course you can have several cube instances in a list.
EDIT I actually found an ugly algorithm. It uses the idea, that at least one coordinate must always be the same if the triangles are on the surface of the cube. New code is:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
import itertools
app = QtGui.QApplication.instance()
if app is None:
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 20
w.show()
w.setWindowTitle('A cube')
vertexes = np.array(list(itertools.product(range(2),repeat=3)))
faces = []
for i in range(2):
temp = np.where(vertexes==i)
for j in range(3):
temp2 = temp[0][np.where(temp[1]==j)]
for k in range(2):
faces.append([temp2[0],temp2[1+k],temp2[3]])
faces = np.array(faces)
colors = np.array([[1,0,0,1] for i in range(12)])
cube = gl.GLMeshItem(vertexes=vertexes, faces=faces, faceColors=colors,
drawEdges=True, edgeColor=(0, 0, 0, 1))
w.addItem(cube)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
EDIT 2 The error in pyqtgraph
is because of internal zero division. If you go to the file MeshData.py
you will find a function vertexNormals
. I changed the first if
statement to:
if self._vertexNormals is None:
faceNorms = self.faceNormals()
vertFaces = self.vertexFaces()
self._vertexNormals = np.empty(self._vertexes.shape, dtype=float)
for vindex in xrange(self._vertexes.shape[0]):
faces = vertFaces[vindex]
if len(faces) == 0:
self._vertexNormals[vindex] = (0,0,0)
continue
norms = faceNorms[faces] ## get all face normals
norm = norms.sum(axis=0) ## sum normals
if all(norm==0):
self._vertexNormals[vindex] = norm
continue
#norm /= (norm**2).sum()**0.5 ## and re-normalize
np.true_divide(norm, (norm**2).sum()**0.5, out=norm, casting='unsafe')
self._vertexNormals[vindex] = norm
Now it doesn't throw any errors. This is because:
- I don't use seemingly deprecated
/=
- I just
continue
if the vector is a zero vector
来源:https://stackoverflow.com/questions/53136301/plot-cube-using-pyqtgraph-in-python