From 00418e49219aeecfbee93a1d6d4ffe13a2ca11d8 Mon Sep 17 00:00:00 2001
From: Luke Campagnola <luke.campagnola@gmail.com>
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):
-- 
GitLab