Построить куб, используя pyqtgraph в python - PullRequest
0 голосов
/ 04 ноября 2018

Я хотел бы построить движущиеся кубы, используя pyqtgraph в python (обновить их позиции на основе заданного набора данных). У меня есть следующий пример кода, но я не знаю, как построить куб вместо сферы, используемой в коде. Кроме того, я очень смущен, почему этот код не работает во второй раз? Я должен закрыть свой spyder, затем открыть его снова или перезапустить ядро, чтобы запустить код во второй раз. (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 Ответ

0 голосов
/ 04 ноября 2018

Импорт:

from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np

Хорошо, во-первых, похоже, вы не можете запустить два QApplication одновременно. Так что вместо

app = QtGui.QApplication([])

запись

app = QtGui.QApplication.instance()
if app is None:
    app = QtGui.QApplication([])

Это создаст новый экземпляр вместо нового приложения только в том случае, если он уже существует.

Теперь для рисования сетки с pyqtgraph. Вы должны определить вершины своей сетки. Для куба это восемь углов:

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

Это только положения углов в декартовых координатах.

Теперь мы должны определить лица. Это треугольники, поэтому нам нужно 12 из них для куба. Мы даем углы треугольников в позициях массивов вершин, которые должны охватывать треугольник:

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]])

Там вполне может быть алгоритм; сейчас я просто написал это от руки; возможно я обновлю позже.

Теперь мы должны определить цвета граней. Нам нужны значения RGBA для каждого лица; Я только сделал это красным:

colors = np.array([[1,0,0,1] for i in range(12)])

С этим мы можем создать GLMeshItem. Я рисую края черным, чтобы вы могли видеть треугольники:

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_()

Куб можно перемещать с помощью:

cube.translate(x,y,z)

Он будет автоматически обновляться. И, конечно, вы можете иметь несколько экземпляров куба в списке.

РЕДАКТИРОВАТЬ Я действительно нашел уродливый алгоритм. Он использует идею, что хотя бы одна координата всегда должна быть одинаковой, если треугольники находятся на поверхности куба. Новый код:

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_()

РЕДАКТИРОВАТЬ 2 Ошибка в pyqtgraph из-за внутреннего деления на ноль. Если вы перейдете к файлу MeshData.py, вы найдете функцию vertexNormals. Я изменил первое if заявление на:

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

Теперь он не выдает никаких ошибок. Это потому что:

  • Я не пользуюсь, казалось бы, устаревшим /=
  • Я просто continue, если вектор является нулевым вектором
...