from itertools import repeat
from ..geom.vector import Vector
from ..color import Color
def add_vertex(vertices, new_vert):
[docs] '''
Modifies `vertices` in-place by appending the given `new_vert`.
Returns the index number of the new vertex.
Loads of Shape-modifying algorithms seem to need this function. Can't make
it a method because they often haven't constructed the shape instance yet.
'''
vertices.append(new_vert)
return len(vertices) - 1
class Face(object):
[docs] '''
A single flat face that forms part of a Shape. Attributes are the params
to the constructor below, plus:
`normal`: A Vector, perpendicular to the face
.. function:: __init__(indices, color, shape, source='unknown')
`indices`: a list of int indices into the parent shape's vertex list
`color`: an instance of Color
`shape`: a reference to the parent Shape
`source`: a descriptive string. These can be used when writing
algorithms that modify shapes, to select certain faces to operate
on.
.. function:: __getitem__(n)
Return the nth index, as an integer.
.. function:: __iter__()
Iterate through `indices`
.. function:: __len__()
Return the length of `indices`
'''
def __init__(self, indices, color, shape, source='unknown'):
[docs] self.indices = indices
self.color = color
self.shape = shape
self.source = source
self.normal = self.get_normal()
def __getitem__(self, index):
[docs] return self.indices[index % len(self.indices)]
def __iter__(self):
[docs] return self.indices.__iter__()
def __len__(self):
[docs] return len(self.indices)
def get_normal(self):
[docs] '''
Return the unit normal vector at right angles to this face.
Note that the direction of the normal will be reversed if the
face's winding is reversed.
'''
v0 = self.shape.vertices[self.indices[0]]
v1 = self.shape.vertices[self.indices[1]]
v2 = self.shape.vertices[self.indices[2]]
a = v0 - v1
b = v2 - v1
normal = b.cross(a).normalized()
return normal
@property
def centroid(self):
[docs] '''
Warning: Not an accurate centroid, just the mean vertex position
'''
return sum(
[self.shape.vertices[i] for i in self], Vector.Origin
) / len(self.indices)
class Shape(object):
[docs] '''
Defines a polyhedron, a 3D shape with flat faces and straight edges.
.. function:: __init__(vertices, faces, colors, name='unknown')
`vertices`: a list of Vector points in 3D space, relative to the
shape's center point.
`faces`: a list of faces, where each face is a list of integer indices
into the vertices list. The referenced vertices of a single
face must form a coplanar ring defining the face's edges. Duplicate
indices do not have to be given at the start and end of each face,
the closed loop is implied.
`colors`: a single Color which is applied to every face, or a sequence
of colors, one for each face.
`name`: the 'source' attribute to be applied to each face.
See the source for factory functions like
:func:`~gloopy.shapes.cube.Cube` for examples of constructing Shapes.
'''
def __init__(self, vertices, faces, colors, name='unknown'):
[docs]
# sanity checks
len_verts = len(vertices)
for face in faces:
assert len(face) >= 3
for index in face:
assert 0 <= index < len_verts
# convert vertices from tuple to Vector if required
if len(vertices) > 0 and not isinstance(vertices[0], Vector):
vertices = [Vector(*v) for v in vertices]
# if color is a single color, then convert it to a sequence of
# identical colors, one for each face
if isinstance(colors, Color):
colors = repeat(colors)
self.vertices = vertices
self.faces = [
Face(face, color, self, source=name)
for face, color in zip(faces, colors)
]
def __repr__(self):
return '<Shape %d verts, %d faces>' % (
len(self.vertices), len(self.faces),
)
def get_edges(self):
[docs] '''
Return a set of pairs, each pair represents indices that start and end
an edge. Contents of each pair is sorted. e.g Tetrahedron:
{ (0, 1), (1, 2), (0, 2), (0, 3), (1, 3), (2, 3), }
'''
edges = set()
for face in self.faces:
for i in xrange(len(face)):
edges.add( tuple(sorted((face[i], face[i+1]))) )
return edges
def replace_face(self, index, new_faces):
[docs] '''
Replace the face at position 'index' in self.faces with the list of
Face instances in 'new_faces'
'''
self.faces[index] = new_faces.pop()
while new_faces:
self.faces.append( new_faces.pop() )