From 00418e49219aeecfbee93a1d6d4ffe13a2ca11d8 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 10 Mar 2014 23:04:10 -0400 Subject: [PATCH] Allow GLMeshItem to draw edges from MeshData with face-indexed vertexes. --- CHANGELOG | 1 + examples/GLMeshItem.py | 56 +++----------- pyqtgraph/opengl/MeshData.py | 111 ++++++++------------------- pyqtgraph/opengl/items/GLMeshItem.py | 8 +- 4 files changed, 51 insertions(+), 125 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 079a8bf6..bf9112cc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -67,6 +67,7 @@ pyqtgraph-0.9.9 [unreleased] - Fixed PySide crash caused by emitting signal from GraphicsObject.itemChange - Fixed possible infinite loop from FiniteCache - Allow images with NaN in ImageView + - MeshData can generate edges from face-indexed vertexes pyqtgraph-0.9.8 2013-11-24 diff --git a/examples/GLMeshItem.py b/examples/GLMeshItem.py index f017f19b..1caa3490 100644 --- a/examples/GLMeshItem.py +++ b/examples/GLMeshItem.py @@ -53,19 +53,24 @@ m1.translate(5, 5, 0) m1.setGLOptions('additive') w.addItem(m1) + ## Example 2: ## Array of vertex positions, three per face +verts = np.empty((36, 3, 3), dtype=np.float32) +theta = np.linspace(0, 2*np.pi, 37)[:-1] +verts[:,0] = np.vstack([2*np.cos(theta), 2*np.sin(theta), [0]*36]).T +verts[:,1] = np.vstack([4*np.cos(theta+0.2), 4*np.sin(theta+0.2), [-1]*36]).T +verts[:,2] = np.vstack([4*np.cos(theta-0.2), 4*np.sin(theta-0.2), [1]*36]).T + ## Colors are specified per-vertex - -verts = verts[faces] ## Same mesh geometry as example 2, but now we are passing in 12 vertexes colors = np.random.random(size=(verts.shape[0], 3, 4)) -#colors[...,3] = 1.0 - -m2 = gl.GLMeshItem(vertexes=verts, vertexColors=colors, smooth=False, shader='balloon') +m2 = gl.GLMeshItem(vertexes=verts, vertexColors=colors, smooth=False, shader='balloon', + drawEdges=True, edgeColor=(1, 1, 0, 1)) m2.translate(-5, 5, 0) w.addItem(m2) + ## Example 3: ## sphere @@ -79,7 +84,7 @@ colors[:,1] = np.linspace(0, 1, colors.shape[0]) md.setFaceColors(colors) m3 = gl.GLMeshItem(meshdata=md, smooth=False)#, shader='balloon') -m3.translate(-5, -5, 0) +m3.translate(5, -5, 0) w.addItem(m3) @@ -114,45 +119,6 @@ w.addItem(m6) - -def psi(i, j, k, offset=(25, 25, 50)): - x = i-offset[0] - y = j-offset[1] - z = k-offset[2] - th = np.arctan2(z, (x**2+y**2)**0.5) - phi = np.arctan2(y, x) - r = (x**2 + y**2 + z **2)**0.5 - a0 = 1 - #ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th) - ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) - - return ps - - #return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2 - - -print("Generating scalar field..") -data = np.abs(np.fromfunction(psi, (50,50,100))) - - -#data = np.fromfunction(lambda i,j,k: np.sin(0.2*((i-25)**2+(j-15)**2+k**2)**0.5), (50,50,50)); -# print("Generating isosurface..") -# verts = pg.isosurface(data, data.max()/4.) -# print dir(gl.MeshData) -# md = gl.GLMeshItem(vertexes=verts) -# -# colors = np.ones((md.vertexes(indexed='faces').shape[0], 4), dtype=float) -# colors[:,3] = 0.3 -# colors[:,2] = np.linspace(0, 1, colors.shape[0]) -# m1 = gl.GLMeshItem(meshdata=md, color=colors, smooth=False) -# -# w.addItem(m1) -# m1.translate(-25, -25, -20) -# -# m2 = gl.GLMeshItem(vertexes=verts, color=colors, smooth=True) -# -# w.addItem(m2) -# m2.translate(-25, -25, -50) diff --git a/pyqtgraph/opengl/MeshData.py b/pyqtgraph/opengl/MeshData.py index 74771255..34a6e3fc 100644 --- a/pyqtgraph/opengl/MeshData.py +++ b/pyqtgraph/opengl/MeshData.py @@ -84,64 +84,11 @@ class MeshData(object): if faceColors is not None: self.setFaceColors(faceColors) - #self.setFaces(vertexes=vertexes, faces=faces, vertexColors=vertexColors, faceColors=faceColors) - - - #def setFaces(self, vertexes=None, faces=None, vertexColors=None, faceColors=None): - #""" - #Set the faces in this data set. - #Data may be provided either as an Nx3x3 array of floats (9 float coordinate values per face):: - - #faces = [ [(x, y, z), (x, y, z), (x, y, z)], ... ] - - #or as an Nx3 array of ints (vertex integers) AND an Mx3 array of floats (3 float coordinate values per vertex):: - - #faces = [ (p1, p2, p3), ... ] - #vertexes = [ (x, y, z), ... ] - - #""" - #if not isinstance(vertexes, np.ndarray): - #vertexes = np.array(vertexes) - #if vertexes.dtype != np.float: - #vertexes = vertexes.astype(float) - #if faces is None: - #self._setIndexedFaces(vertexes, vertexColors, faceColors) - #else: - #self._setUnindexedFaces(faces, vertexes, vertexColors, faceColors) - ##print self.vertexes().shape - ##print self.faces().shape - - - #def setMeshColor(self, color): - #"""Set the color of the entire mesh. This removes any per-face or per-vertex colors.""" - #color = fn.Color(color) - #self._meshColor = color.glColor() - #self._vertexColors = None - #self._faceColors = None - - - #def __iter__(self): - #"""Iterate over all faces, yielding a list of three tuples [(position, normal, color), ...] for each face.""" - #vnorms = self.vertexNormals() - #vcolors = self.vertexColors() - #for i in range(self._faces.shape[0]): - #face = [] - #for j in [0,1,2]: - #vind = self._faces[i,j] - #pos = self._vertexes[vind] - #norm = vnorms[vind] - #if vcolors is None: - #color = self._meshColor - #else: - #color = vcolors[vind] - #face.append((pos, norm, color)) - #yield face - - #def __len__(self): - #return len(self._faces) - def faces(self): - """Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh.""" + """Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh. + + If faces have not been computed for this mesh, the function returns None. + """ return self._faces def edges(self): @@ -161,8 +108,6 @@ class MeshData(object): self.resetNormals() self._vertexColorsIndexedByFaces = None self._faceColorsIndexedByFaces = None - - def vertexes(self, indexed=None): """Return an array (N,3) of the positions of vertexes in the mesh. @@ -207,7 +152,6 @@ class MeshData(object): self._vertexNormalsIndexedByFaces = None self._faceNormals = None self._faceNormalsIndexedByFaces = None - def hasFaceIndexedData(self): """Return True if this object already has vertex positions indexed by face""" @@ -229,7 +173,6 @@ class MeshData(object): if v is not None: return True return False - def faceNormals(self, indexed=None): """ @@ -366,7 +309,6 @@ class MeshData(object): ## This is done by collapsing into a list of 'unique' vertexes (difference < 1e-14) ## I think generally this should be discouraged.. - faces = self._vertexesIndexedByFaces verts = {} ## used to remember the index of each vertex position self._faces = np.empty(faces.shape[:2], dtype=np.uint) @@ -427,22 +369,35 @@ class MeshData(object): #pass def _computeEdges(self): - ## generate self._edges from self._faces - #print self._faces - nf = len(self._faces) - edges = np.empty(nf*3, dtype=[('i', np.uint, 2)]) - edges['i'][0:nf] = self._faces[:,:2] - edges['i'][nf:2*nf] = self._faces[:,1:3] - edges['i'][-nf:,0] = self._faces[:,2] - edges['i'][-nf:,1] = self._faces[:,0] - - # sort per-edge - mask = edges['i'][:,0] > edges['i'][:,1] - edges['i'][mask] = edges['i'][mask][:,::-1] - - # remove duplicate entries - self._edges = np.unique(edges)['i'] - #print self._edges + if not self.hasFaceIndexedData: + ## generate self._edges from self._faces + nf = len(self._faces) + edges = np.empty(nf*3, dtype=[('i', np.uint, 2)]) + edges['i'][0:nf] = self._faces[:,:2] + edges['i'][nf:2*nf] = self._faces[:,1:3] + edges['i'][-nf:,0] = self._faces[:,2] + edges['i'][-nf:,1] = self._faces[:,0] + + # sort per-edge + mask = edges['i'][:,0] > edges['i'][:,1] + edges['i'][mask] = edges['i'][mask][:,::-1] + + # remove duplicate entries + self._edges = np.unique(edges)['i'] + #print self._edges + elif self._vertexesIndexedByFaces is not None: + verts = self._vertexesIndexedByFaces + edges = np.empty((verts.shape[0], 3, 2), dtype=np.uint) + nf = verts.shape[0] + edges[:,0,0] = np.arange(nf) * 3 + edges[:,0,1] = edges[:,0,0] + 1 + edges[:,1,0] = edges[:,0,1] + edges[:,1,1] = edges[:,1,0] + 1 + edges[:,2,0] = edges[:,1,1] + edges[:,2,1] = edges[:,0,0] + self._edges = edges + else: + raise Exception("MeshData cannot generate edges--no faces in this data.") def save(self): diff --git a/pyqtgraph/opengl/items/GLMeshItem.py b/pyqtgraph/opengl/items/GLMeshItem.py index c80fd488..55e75942 100644 --- a/pyqtgraph/opengl/items/GLMeshItem.py +++ b/pyqtgraph/opengl/items/GLMeshItem.py @@ -153,8 +153,12 @@ class GLMeshItem(GLGraphicsItem): self.colors = md.faceColors(indexed='faces') if self.opts['drawEdges']: - self.edges = md.edges() - self.edgeVerts = md.vertexes() + if not md.hasFaceIndexedData(): + self.edges = md.edges() + self.edgeVerts = md.vertexes() + else: + self.edges = md.edges() + self.edgeVerts = md.vertexes(indexed='faces') return def paint(self):