from __future__ import division import pprint ''' http://en.wikipedia.org/wiki/Polygon_mesh Polygon meshes may be represented in a variety of ways, using different methods to store the vertex, edge and face data. These include: - Face-vertex - Winged-edge - Half-edge - Quad-edge - Corner-tables - Vertex-vertex - Face-vertex We have chosen to use a winged-edge style mesh for our purpopses. ''' def cross(a, b): i = a.y * b.z - a.z * b.y j = a.z * b.x - a.x * b.z k = a.x * b.y - a.y * b.x return Vertex(i, j, k) def _centroid(verts): xs = [vertex.x for vertex in verts] ys = [vertex.y for vertex in verts] zs = [vertex.z for vertex in verts] # average each vertex component x = sum(xs) / len(xs) y = sum(ys) / len(ys) z = sum(zs) / len(zs) return Vertex(x, y, z) class Vertex(object): ''' A vertex is a position along with other information such as color, normal vector and texture coordinates. For the sake of our algorithms, we will only worry about the (x, y, z) float positions. Eventually we will also keep track of weights. ''' def __init__(self, x=0.0, y=0.0, z=0.0): self.x = x self.y = y self.z = z def __eq__(self, other): if(self.x == other.x and self.y == other.y and self.z == other.z): return True else: return False def __add__(self, other): # for now just assume type(other) = Vertex... bad, I know return Vertex(self.x + other.x, self.y + other.y, self.z + other.z) def __radd__(self, other): return other + self def __mul__(self, other): if isinstance(other, Vertex): return cross(self, other) elif isinstance(other, (float, int)): return Vertex(self.x * other, self.y * other, self.z * other) else: raise TypeError("{0} has an unexpected type: {1}".format( other, type(other))) def __rmul__(self, other): return self.__mul__(other) def __div__(self, other): # same assumption as __mult__ return Vertex(self.x / other, self.y / other, self.z / other) __truediv__ = __div__ def __neg__(self): return Vertex(-self.x, -self.y, -self.z) def __unicode__(self): return pprint.pformat([self.x, self.y, self.z]) __str__ = __unicode__ __repr__ = __unicode__ class Edge(object): def __init__(self, v1, v2): self.v1 = v1 self.v2 = v2 @property def centroid(self): return _centroid([self.v1, self.v2]) def __unicode__(self): return pprint.pformat((self.v1, self.v2)) __str__ = __unicode__ __repr__ = __unicode__ class Face(object): ''' A face is a closed set of edges, in which a triangle face has three edges, and a quad face has four edges. Blender stores a face as a collection of the verts that compose it, so we shall store them thusly as well. Blender historicaly only supported faces with an edge count of three or four. We will cross the N-gon bridge in the future. ''' def __init__(self, verts=None): self.verts = verts or [] def __unicode__(self): return pprint.pformat(self.verts) __str__ = __unicode__ __repr__ = __unicode__ @property def centroid(self): return _centroid(self.verts) class PolygonMesh(object): ''' A polygon object is a collection of the following lists: - a list containing the 3-space vertex information - a list containing the edge indices ([0, 1] means first and second elements of the vertices list) - a list of the faces (indices of the vertices in a given face) - connectivity information (eventually will be calculated given the verts, edges, and faces) ''' def __init__(self, *args, **kwargs): self.vertices = kwargs['vertices'] self.edges = kwargs['edges'] self.faces = kwargs['faces'] # the strategy for the following members involves lazy-instantiating # them if they weren't passing them in: self._faces_for_edge = kwargs.get('faces for edge', None) self._edges_for_face = kwargs.get('edges for face', None) self._edges_for_vert = kwargs.get('edges for vert', None) self._faces_for_vert = kwargs.get('faces for vert', None) @property def faces_for_edge(self): """returns a list of face indices for a given edge index. Intended to be used in the following way: >>> # vs, es, fs, ffe, eff, efv, ffv from blender or similar >>> mesh = Polygon(vs, es, fs, ffe, eff, efv, ffv) >>> mesh.faces_for_edge[0] [0, 1] >>> [self.face[i] for i in mesh.faces_for_edge[1]] [, ] where 0 and 1 are indices into the face list """ if self._faces_for_edge is None: # TODO: eventually support generating this ourselves ... raise AttributeError("Not yet implemented if you don't explicitly pass this in") return self._faces_for_edge @property def edges_for_face(self): """returns a list of edge indices for a given face index.""" if self._edges_for_face is None: # TODO: eventually support generating this ourselves ... raise AttributeError("Not yet implemented if you don't explicitly pass this in") return self._edges_for_face @property def edges_for_vert(self): """returns a list of edge indices for a given vertex index.""" if self._edges_for_vert is None: # TODO: eventually support generating this ourselves ... raise AttributeError("Not yet implemented if you don't explicitly pass this in") return self._edges_for_vert @property def faces_for_vert(self): """returns a list of face indices for a given vert index.""" if self._faces_for_vert is None: # TODO: eventually support generating this ourselves ... raise AttributeError("Not yet implemented if you don't explicitly pass this in") return self._faces_for_vert def __unicode__(self): # TODO: perhaps also add connectivity here? d = { 'vertices': self.vertices, 'edges': self.edges, 'faces': self.faces, } return pprint.pformat(d) __str__ = __unicode__ __repr__ = __unicode__